我有一些UTF-8编码的数据生活在Javascript Uint8Array元素的范围内。是否有一种有效的方法来解码这些到一个常规的javascript字符串(我相信javascript使用16位Unicode)?我不想一次添加一个字符,因为字符串连接会变得CPU密集。


当前回答

在NodeJS中,我们有缓冲区可用,使用它们进行字符串转换非常容易。更好的是,它很容易将Uint8Array转换为Buffer。试试这段代码,它为我在节点基本上任何转换涉及Uint8Arrays:

let str = Buffer.from(uint8arr.buffer).toString();

我们只是从Uint8Array中提取ArrayBuffer,然后将其转换为适当的NodeJS Buffer。然后我们将Buffer转换为一个字符串(如果你愿意,你可以使用十六进制或base64编码)。

如果我们想从一个字符串转换回Uint8Array,那么我们会这样做:

let uint8arr = new Uint8Array(Buffer.from(str));

注意,如果你在转换为字符串时声明了一个像base64这样的编码,那么如果你使用base64或任何其他你使用的编码,你就必须使用Buffer.from(str, "base64")。

这将在没有模块的浏览器中不起作用!NodeJS的Buffer在浏览器中是不存在的,所以除非你在浏览器中添加Buffer功能,否则这个方法是行不通的。这其实很容易做到,只要使用像这样的模块,它既小又快!

其他回答

做什么@Sudhir说,然后得到一个字符串,逗号分隔的数字列表使用:

for (var i=0; i<unitArr.byteLength; i++) {
            myString += String.fromCharCode(unitArr[i])
        }

这会给你想要的字符串, 如果还相关的话

来自Encoding标准的TextEncoder和TextDecoder,由stringencoding库填充,在字符串和ArrayBuffers之间转换:

var uint8array = new TextEncoder().encode("someString");
var string = new TextDecoder().decode(uint8array);

使用base64作为编码格式效果很好。这就是它是如何实现通过url在Firefox发送传递秘密。您将需要base64-js包。下面是Send源代码中的函数:

const b64 = require("base64-js")

function arrayToB64(array) {
  return b64.fromByteArray(array).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "")
}

function b64ToArray(str) {
  return b64.toByteArray(str + "===".slice((str.length + 3) % 4))
}
class UTF8{
static encode(str:string){return new UTF8().encode(str)}
static decode(data:Uint8Array){return new UTF8().decode(data)}

private EOF_byte:number = -1;
private EOF_code_point:number = -1;
private encoderError(code_point) {
    console.error("UTF8 encoderError",code_point)
}
private decoderError(fatal, opt_code_point?):number {
    if (fatal) console.error("UTF8 decoderError",opt_code_point)
    return opt_code_point || 0xFFFD;
}
private inRange(a:number, min:number, max:number) {
    return min <= a && a <= max;
}
private div(n:number, d:number) {
    return Math.floor(n / d);
}
private stringToCodePoints(string:string) {
    /** @type {Array.<number>} */
    let cps = [];
    // Based on http://www.w3.org/TR/WebIDL/#idl-DOMString
    let i = 0, n = string.length;
    while (i < string.length) {
        let c = string.charCodeAt(i);
        if (!this.inRange(c, 0xD800, 0xDFFF)) {
            cps.push(c);
        } else if (this.inRange(c, 0xDC00, 0xDFFF)) {
            cps.push(0xFFFD);
        } else { // (inRange(c, 0xD800, 0xDBFF))
            if (i == n - 1) {
                cps.push(0xFFFD);
            } else {
                let d = string.charCodeAt(i + 1);
                if (this.inRange(d, 0xDC00, 0xDFFF)) {
                    let a = c & 0x3FF;
                    let b = d & 0x3FF;
                    i += 1;
                    cps.push(0x10000 + (a << 10) + b);
                } else {
                    cps.push(0xFFFD);
                }
            }
        }
        i += 1;
    }
    return cps;
}

private encode(str:string):Uint8Array {
    let pos:number = 0;
    let codePoints = this.stringToCodePoints(str);
    let outputBytes = [];

    while (codePoints.length > pos) {
        let code_point:number = codePoints[pos++];

        if (this.inRange(code_point, 0xD800, 0xDFFF)) {
            this.encoderError(code_point);
        }
        else if (this.inRange(code_point, 0x0000, 0x007f)) {
            outputBytes.push(code_point);
        } else {
            let count = 0, offset = 0;
            if (this.inRange(code_point, 0x0080, 0x07FF)) {
                count = 1;
                offset = 0xC0;
            } else if (this.inRange(code_point, 0x0800, 0xFFFF)) {
                count = 2;
                offset = 0xE0;
            } else if (this.inRange(code_point, 0x10000, 0x10FFFF)) {
                count = 3;
                offset = 0xF0;
            }

            outputBytes.push(this.div(code_point, Math.pow(64, count)) + offset);

            while (count > 0) {
                let temp = this.div(code_point, Math.pow(64, count - 1));
                outputBytes.push(0x80 + (temp % 64));
                count -= 1;
            }
        }
    }
    return new Uint8Array(outputBytes);
}

private decode(data:Uint8Array):string {
    let fatal:boolean = false;
    let pos:number = 0;
    let result:string = "";
    let code_point:number;
    let utf8_code_point = 0;
    let utf8_bytes_needed = 0;
    let utf8_bytes_seen = 0;
    let utf8_lower_boundary = 0;

    while (data.length > pos) {
        let _byte = data[pos++];

        if (_byte == this.EOF_byte) {
            if (utf8_bytes_needed != 0) {
                code_point = this.decoderError(fatal);
            } else {
                code_point = this.EOF_code_point;
            }
        } else {
            if (utf8_bytes_needed == 0) {
                if (this.inRange(_byte, 0x00, 0x7F)) {
                    code_point = _byte;
                } else {
                    if (this.inRange(_byte, 0xC2, 0xDF)) {
                        utf8_bytes_needed = 1;
                        utf8_lower_boundary = 0x80;
                        utf8_code_point = _byte - 0xC0;
                    } else if (this.inRange(_byte, 0xE0, 0xEF)) {
                        utf8_bytes_needed = 2;
                        utf8_lower_boundary = 0x800;
                        utf8_code_point = _byte - 0xE0;
                    } else if (this.inRange(_byte, 0xF0, 0xF4)) {
                        utf8_bytes_needed = 3;
                        utf8_lower_boundary = 0x10000;
                        utf8_code_point = _byte - 0xF0;
                    } else {
                        this.decoderError(fatal);
                    }
                    utf8_code_point = utf8_code_point * Math.pow(64, utf8_bytes_needed);
                    code_point = null;
                }
            } else if (!this.inRange(_byte, 0x80, 0xBF)) {
                utf8_code_point = 0;
                utf8_bytes_needed = 0;
                utf8_bytes_seen = 0;
                utf8_lower_boundary = 0;
                pos--;
                code_point = this.decoderError(fatal, _byte);
            } else {
                utf8_bytes_seen += 1;
                utf8_code_point = utf8_code_point + (_byte - 0x80) * Math.pow(64, utf8_bytes_needed - utf8_bytes_seen);

                if (utf8_bytes_seen !== utf8_bytes_needed) {
                    code_point = null;
                } else {
                    let cp = utf8_code_point;
                    let lower_boundary = utf8_lower_boundary;
                    utf8_code_point = 0;
                    utf8_bytes_needed = 0;
                    utf8_bytes_seen = 0;
                    utf8_lower_boundary = 0;
                    if (this.inRange(cp, lower_boundary, 0x10FFFF) && !this.inRange(cp, 0xD800, 0xDFFF)) {
                        code_point = cp;
                    } else {
                        code_point = this.decoderError(fatal, _byte);
                    }
                }

            }
        }
        //Decode string
        if (code_point !== null && code_point !== this.EOF_code_point) {
            if (code_point <= 0xFFFF) {
                if (code_point > 0)result += String.fromCharCode(code_point);
            } else {
                code_point -= 0x10000;
                result += String.fromCharCode(0xD800 + ((code_point >> 10) & 0x3ff));
                result += String.fromCharCode(0xDC00 + (code_point & 0x3ff));
            }
        }
    }
    return result;
}

`

对于香草,浏览器端,从麦克风录音,base64函数为我工作(我必须实现一个音频发送功能到聊天)。

      const ui8a =  new Uint8Array(e.target.result);
      const string = btoa(ui8a);
      const ui8a_2 = atob(string).split(',');

现在是完整代码。感谢Bryan Jennings & breakspirit@py4u.net提供的代码。

https://medium.com/@bryanjenningz/how-to-record-and-play-audio-in-javascript-faa1b2b3e49b

https://www.py4u.net/discuss/282499

index . html

<html>
  <head>
    <title>Record Audio Test</title>
    <meta name="encoding" charset="utf-8" />
  </head>
  <body>
    <h1>Audio Recording Test</h1>
    <script src="index.js"></script>
    <button id="action" onclick="start()">Start</button>
    <button id="stop" onclick="stop()">Stop</button>
    <button id="play" onclick="play()">Listen</button>
  </body>
</html>

index.js:

const recordAudio = () =>
  new Promise(async resolve => {
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
    const mediaRecorder = new MediaRecorder(stream);
    const audioChunks = [];

    mediaRecorder.addEventListener("dataavailable", event => {
      audioChunks.push(event.data);
    });

    const start = () => mediaRecorder.start();

    const stop = () =>
      new Promise(resolve => {
        mediaRecorder.addEventListener("stop", () => {
          const audioBlob = new Blob(audioChunks);
          const audioUrl = URL.createObjectURL(audioBlob);
          const audio = new Audio(audioUrl);
          const play = () => audio.play();
          resolve({ audioBlob, audioUrl, play });
        });

        mediaRecorder.stop();
      });

    resolve({ start, stop });
  });

let recorder = null;
let audio = null;
const sleep = time => new Promise(resolve => setTimeout(resolve, time));

const start = async () => {
  recorder = await recordAudio();
  recorder.start();
}

const stop = async () => {
  audio = await recorder.stop();
  read(audio.audioUrl);
}

const play = ()=> {
  audio.play();
}

const read = (blobUrl)=> {

  var xhr = new XMLHttpRequest;
  xhr.responseType = 'blob';
  
  xhr.onload = function() {
      var recoveredBlob = xhr.response;
      const reader = new FileReader();
      // This fires after the blob has been read/loaded.
      reader.addEventListener('loadend', (e) => {

          const ui8a =  new Uint8Array(e.target.result);
          const string = btoa(ui8a);
          const ui8a_2 = atob(string).split(',');
          
          playByteArray(ui8a_2);
      });
      // Start reading the blob as text.
      reader.readAsArrayBuffer(recoveredBlob);
  };
  // get the blob through blob url 
  xhr.open('GET', blobUrl);
  xhr.send();
}

window.onload = init;
var context;    // Audio context
var buf;        // Audio buffer

function init() {
  if (!window.AudioContext) {
      if (!window.webkitAudioContext) {
          alert("Your browser does not support any AudioContext and cannot play back this audio.");
          return;
      }
        window.AudioContext = window.webkitAudioContext;
    }

    context = new AudioContext();
}

function playByteArray(byteArray) {

    var arrayBuffer = new ArrayBuffer(byteArray.length);
    var bufferView = new Uint8Array(arrayBuffer);
    for (i = 0; i < byteArray.length; i++) {
      bufferView[i] = byteArray[i];
    }

    context.decodeAudioData(arrayBuffer, function(buffer) {
        buf = buffer;
        play2();
    });
}

// Play the loaded file
function play2() {
    // Create a source node from the buffer
    var source = context.createBufferSource();
    source.buffer = buf;
    // Connect to the final output node (the speakers)
    source.connect(context.destination);
    // Play immediately
    source.start(0);
}