前端中部分数据流之间的相互转化


一、基本介绍

1.ArrayBuffer

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区。

它是一个字节数组,通常在其他语言中称为“byte array”。

你不能直接操作 ArrayBuffer 的内容,而是要通过 TypedArray类型数组)对象或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。

它的含义类似 NodeJs 中的 Buffer 。简单来说,我们可以通过 ArrayBuffer 来开辟一段二进制数据空间,但是它只能通过 TypedArray 或者 DataView 来进行操作。

简单用法:

//创建一个长度为 8 的 ArrayBuffer ,此时开辟一个固定 8 个字节的缓冲区也就是 64 位
const buffer = new ArrayBuffer(8);

console.log(buffer.byteLength); // 8

length 大于 Number.MAX_SAFE_INTEGER(>= 2 \*\* 53) 或为负数,则抛出一个 RangeError 异常。

作用:

从 XHR、File API、Canvas, WebGL 等等各种地方,读取了一大串字节流,如果用 JS 里的 Array 去存,又浪费,又低效,

于是为了配合这些新的 API 增强 JS 的二进制处理能力,就有了 ArrayBuffer

创建 ArrayBuffer 的时候,就相当于申请了一块内存, 不能(也不方便)直接用它,

所以也就有了 TypedArray ,比如 Uint32Array, Int16Array, Int8Array, Float32Array 等等。

1.1.TypedArray

一个类型化数组(TypedArray)对象描述了一个底层的二进制数据缓冲区(binary data buffer)的一个类数组视图(view)。

稍微翻译下上边的话,也就是说可以通过 TypedArray 来操作 ArrayBuffer 的实例。

事实上,没有名为 TypedArray 的全局属性,也没有一个名为 TypedArray 的构造函数。相反,有许多不同的全局属性,它们的值是特定元素类型的类型化数组构造函数

这句话简单来讲,你可以将 TypedArray 理解为一种接口的形式。所谓 TypedArray 它并不包含具体的实现而是代表一系列具有相同特性(类数组视图)的集合概念。

也就是说 TypedArray 不可被直接实例化,本身也无法访问。但是它有很多种不同的实现方式。

特点:

  • TypedArray 内的成员只能是同一类型,都是数字,Array 可以存储任意元素
  • TypedArray 成员是连续的,不会有空位,Array 可以有空位
  • TypedArray 成员的默认值为 0,Array 的默认值为空
  • TypedArray 定义时长度固定不可动态增加或减小,Array 长度可变
  • TypedArray 只是视图,本身不存储数据,数据都存储在底层的 ArrayBuffer 中,要获取底层对象必须使用 buffer 属性
  • TypedArray Array.isArray() 返回 false
  • TypedArray 可以直接操作内存,不需要进行类型转换,所以比数组快

创建构造函数:

// 下面代码是语法格式,不能直接运行,
// TypedArray 关键字需要替换为底部列出的构造函数。
new TypedArray(); // ES2017中新增
new TypedArray(length);
new TypedArray(typedArray);
new TypedArray(object);
new TypedArray(buffer [, byteOffset [, length]]);

TypedArray 指的是以下的其中之一:

类型 值范围 大小(bytes) 描述 Web IDL 类型 等价的 C 类型
Int8Array -128 到 127 1 8 位有符号整型(补码) byte int8_t
Uint8Array 0 到 255 1 8 位无符号整型 octet uint8_t
Uint8ClampedArray 0 到 255 1 8 位无符号整型(一定在 0 到 255 之间) octet uint8_t
Int16Array -32768 到 32767 2 16 位有符号整型(补码) short int16_t
Uint16Array 0 到 65535 2 16 位无符号整型 unsigned short uint16_t
Int32Array -2147483648 到 2147483647 4 32 位有符号整型(补码) long int32_t
Uint32Array 0 到 4294967295 4 32 位无符号整型 unsigned long uint32_t
Float32Array -3.4E383.4E38 并且 1.2E-38 是最小的正数 4 32 位 IEEE 浮点数(7 位有效数字,例如 1.234567 unrestricted float float
Float64Array -1.8E3081.8E308 并且 5E-324 是最小的正数 8 64 位 IEEE 浮点数(16 位有效数字,例如 1.23456789012345 unrestricted double double
BigInt64Array -263 到 263 - 1 8 64 位有符号整数(补码) bigint int64_t (signed long long)
BigUint64Array 0 到 264 - 1 8 64 位无符号整型 bigint uint64_t (unsigned long long)
  • Unit8Array 指的是,把 ArrayBuffer 的每个 byte(8-bit) 当作一个单独的无符号整型数字 (0 - 255)
  • Unit16Array 表示为使用 16 bits (2 bytes) 表示一个无符号整型 (0 ~ 2^16-1) 的数的数组
  • Int8Array 表示使用 8 bits 表示一个有符号整型 (-128 ~ 127)
  • Float32Array 表示使用 32 bits 表示一个浮点数
  • Unit7ClampedArray 在 0 ~ 255 范围内和 Unit8Array 是一样的,对超出范围的处理有所不同,和图像处理相关(一般像素范围也是 0 ~ 255)

示例:

// 创建8个字节长度的缓存冲
const buffer = new ArrayBuffer(8);
// 将buffer转化为Uint8Array
// Uint8Array中每一个元素表示一个字节(8位)
const uint8Array = new Uint8Array(buffer);

1.2.DataView

DataView 视图是一个可以从二进制 ArrayBuffer 对象中读写多种数值类型的底层接口,使用它时,不用考虑不同平台的字节序(endianness)问题。

相较于 TypedArrayDataView 对于 ArrayBuffer 的操作更加灵活。

TypedArray 中操作二进制 ArrayBuffer 时每个元素占用的字节大小是固定的,要么每个元素占用 8 位、16 位或者 32 位。

DataView 对于 ArrayBuffer 的操作就显得更加灵活了,我们可以通过 DataViewArrayBuffer 中自由的读写多种数据类型,从而控制字节顺序。

简单来讲,想较与 TypedArray 每个元素中固定的字节大小,我们可以通过 DataView 来自由的操作 ArrayBuffer

创建 DataView:

new DataView(buffer [, byteOffset [, byteLength]])

// 创建8个字节长度的缓存冲
const buffer = new ArrayBuffer(8);

// 根据传入的buffer 从第一个字节开始,并且字节长度为匹配buffer的长度
const dataView = new DataView(buffer);

// 将DataView中偏移量为0个字节的字节,也就是第一个字节设置为十进制的1
dataView.setUint8(0, 1);
// 将DataView中偏移量为1个字节的字节,也就是第二个字节设置为十进制的2
dataView.setUint8(1, 2);

创建 DataView 支持传入三个参数:

  • buffer :为必填,它支持传入一个 ArrayBuffer 表示 DataView 中的源数据。
  • byteOffset :选填,它表示创建 DataView 时开头从 buffer 的哪个字节开始,可以作为启始偏移量。未指定时,默认从第一个字节开始。
  • btyeLength :选填,它表示创建该 DataView 时的长度,当不传递默认时表示匹配 buffer 的长度。

1.3.TypedArray 和 DataView 的设计目的

ArrayBuffer 对象的各种 TypedArray 视图,是用来向网卡、声卡之类的本机设备传送数据,所以使用本机的字节序就可以了;

DataView 视图的设计目的,是用来处理网络设备传来的数据,所以大端字节序或小端字节序是可以自行设定的。

1.4.ArrayBuffer、TypedArray、DataView 关系图

2.Blob

Blob 对象表示一个不可变、原始数据的类文件对象。它的数据可以按文本或二进制的格式进行读取,也可以转换成 ReadableStream 来用于数据操作。

注意 File 对象是继承与 blob

创建如下:

const aBlob = new Blob(array, options);

Blob() 构造函数返回一个新的 Blob 对象。 blob 的内容由参数数组中给出的值的串联组成。
通过 new Blob 可以创建一个新的 blob 对象实例,构造函数支持接受两个参数:

  • array: 是一个由 ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 BlobDOMStrings 会被编码为 UTF-8
  • options: 是一个对象,它拥有如下属性:
    • type:默认值为 "",它代表了将会被放入到 blob 中的数组内容的 MIME 类型。
    • endings:默认值为"transparent",用于指定包含行结束符\n 的字符串如何被写入。 它是以下两个值中的一个: "native",代表行结束符会被更改为适合宿主操作系统文件系统的换行符,或者 "transparent",代表会保持 blob 中保存的结束符不变

常见的 MIME 类型:

MIME 类型 说明
text/plain 纯文本文档
text/html HTML 文档
text/javascript JavaScript 文件
text/css CSS 文件
application/json JSON 文件
application/pdf PDF 文件
application/xml XML 文件
image/jpeg JPEG 图像
image/png PNG 图像
image/gif GIF 图像
image/svg+xml SVG 图像
audio MP3 文件
video MP4 文件

创建一个 blob 对象:

const name = JSON.stringify({ name: "tianyichuxin" });

// 传入DOMString创建blob
const blob = new Blob([name], {
    type: "application/json",
});

// log: 23 utf8中一个英文代表一个字节
console.log(blob.size);

const buffer = new ArrayBuffer(8);

// 传入ArrayBuffer创建blob
const bufferToBlob = new Blob([buffer]);

// log: 8
console.log(bufferToBlob.size);

部分方法:

  • slice() 从 Blob 中截取一部分并返回一个新的 Blob(用法同数组的 slice)
  • arrayBuffer() 返回一个以二进制形式展现的 promise
  • stream() 返回一个 ReadableStream 对象
  • text() 返回一个文本形式的 promise

2.1.读取 blob 内容

FileReader 对象允许 Web 应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 FileBlob 对象指定要读取的文件或数据。

const name = JSON.stringify({
    name: "tianyichuxin",
}); // 传入DOMString创建blob
const blob = new Blob([name], {
    type: "application/json",
});
/**
 *
 * @param {*} blob blob 对象
 * @param {*} type 输出的结果
 */
function getBlobByType(blob, type) {
    const fileReader = new FileReader(blob);
    switch (type) {
        // 读取文件的 ArrayBuffer 数据对象.
        case "arrayBuffer":
            fileReader.readAsArrayBuffer(blob);
            break;
        // 读取文件为的字符串
        case "DOMstring":
            fileReader.readAsText(blob, "utf8");
            break;
        // 读取文件为data: URL格式的Base64字符串
        case "dataUrl":
            fileReader.readAsDataURL(blob);
            break;
        // 读取文件为文件的原始二进制数据(已废弃不推荐使用)
        case "binaryString":
            fileReader.readAsBinaryString(blob);
            break;
        default:
            break;
    }
    return new Promise((resolve) => {
        // 当文件读取完成时候触发
        fileReader.onload = (e) => {
            // 获取最终读取结果
            const result = e.target.result;
            resolve(result);
        };
    });
}
// ArrayBuffer 对象
getBlobByType(blob, "arrayBuffer").then((res) => console.log(res));
// {"name":"tianyichuxin"}
getBlobByType(blob, "DOMstring").then((res) => console.log(res));
// data:application/json;base64,eyJuYW1lIjoiMTlRSW5nZmVuZyJ9
getBlobByType(blob, "dataUrl").then((res) => console.log(res));
// {"name":"tianyichuxin"}

getBlobByType(blob, "binaryString").then((res) => console.log(res));

通常情况下, File 对象是来自用户在一个 input 元素上选择文件后返回的 FileList 对象,也可以是来自由拖放操作生成的 DataTransfer 对象,或者来自 HTMLCanvasElement 上的 mozGetAsFile() API。

简单来说,File 就是基于 Blob 而来。它拥有 Blob 的所有功能的同时扩展了一系列关于文件的属性。

3.File

File 就是文件,继承自 Blob,也是二进制对象,也有自己特有的属性和方法,通常用在
<input type="file">选择的 FileList 对象,或者是使用拖拽操作产生的的 DataTransfer 对象。

File 对象是特殊类型的 Blob,且可以用在任意的 Blob 类型的 context 中。比如说, FileReader, URL.createObjectURL(), createImageBitmap(), 及 XMLHttpRequest.send() 都能处理 BlobFile

创建如下:

const file = new File(array, name[, options])
  • array:是一个由 ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成,DOMStrings 会被编码为 UTF-8。
  • name:表示文件名称,或者文件路径。
  • options:是一个可选,它可能会指定如下两个属性:
    • type:默认值为 "",内容的 MIME 类型。
    • lastModified:数值,表示文件最后修改时间的 Unix 时间戳(毫秒)。默认值为Date.now()

3.1.ArrayBuffer 和 Blob/File 的关系

4.ObjectURL

ObjectURL 是用于引用任何数据的简单 URL 字符串,即用一个 URL 来访问某个数据资源。比如可以创建某个图片的 ObjectURL 值,然后设置该值为图片标签的 src,即可展示该图片。

ObjectURL 本质上是一个临时的,用于访问某个资源的路径。它常常用来展示文件,比如图片、Excel、PDF 文件、视频文件等。

大多数情况下,我们可以看到一些网页内部可以看到一些诸如此类的 Blob Url:

<video src="blod:https://www.baidu.com/dsadafa78HJKHGSasd2452kS98sds"

关于 Blob URL/Object URL 其实它们是一种伪协议,允许将 BlobFile 对象用作图像、二进制数据下载链接等的 URL 源。它的好处其实有很多。

平常我们并不可以直接处理 Image 标签之类的原始二进制数据,所以对于图片等需要 url 作为源的标签通常做法是将图片上传到服务器上得到一个 url 从而通过 URL 加载二进制数据。

与其上传二进制数据,然后通过 URL 将其返回,不如使用 Blob/Object Url 无需额外的步骤,使用浏览器本地 Api 即可直接访问数据而不需要通过服务器来上传数据。

相比Base64 字符串编码也可以解决上述问题, Blob 是纯二进制字节数组,不会像 Data-URI 那样有任何显着的开销,这使得它们处理起来更快更小。

同时这些 URL 只能在浏览器的单个实例和同一会话(即页面/文档的生命周期)中本地使用,这意味者离开当前浏览器实例这个 URL 就会失效。

我们可以通过 URL.createObjectURL(object) 来创建对应的 Object URL,这个方法会返回一个 DOMString 字符串,其中包含一个表示参数中给出的对象的URL

同时,这个 URL 的生命周期和创建它的窗口中的 document 绑定。这个新的 URL 对象表示指定的 File 对象或 Blob 对象。

在创建时候它会接受一个参数:

  • object 表示用于创建 URL 的 File对象、Blob对象或者 MediaSource对象。

同样它会返回一个DOMString包含了一个对象URL,该 URL 可用于指定源 object 的内容。

返回的 DOMString 格式为 blob:<origin>/<uuid>

当在你的网页上不再使用通过 URL.createObjectURL(object) 创建的 URL 时,记得使用 URL.revokeObjectURL(url) 来主动释放它们。

通过 Object URL 下载图片:

HTML 部分代码

<input type="file" id="input" /> <img id="img" />

js 部分代码

const input = document.getElementById("input");
input.onchange = (e) => {
    const url = URL.createObjectURL(e.target.files[0]);

    // 实现下载
    const a = document.createElement("a");
    a.href = url;
    a.download = "img";
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
};
const name = JSON.stringify({
    name: "tianyichuxin",
});

通过 Blob 下载:

// 传入DOMString创建blob
const blob = new Blob([name], {
    type: "application/json",
});

// 创建 Object Url
const url = URL.createObjectURL(blob);

const aLink = document.createElement("a");

// href属性
aLink.href = url;
// 定义下载的文件名
aLink.download = "name.json";

// 派发a链接的点击事件
aLink.dispatchEvent(new MouseEvent("click"));

// 下载完成后,释放 URL.createObjectURL() 创建的 URL 对象。
URL.revokeObjectURL(url);

4.1.ArrayBuffer 和 Blob 、Object URL 的关系

5.Buffer

在 Node.JS 中,Buffer是一种全局对象,用于表示二进制数据。Buffer对象可以直接访问和修改其内容,因此它比BlobArrayBuffer更适合网络编程或文件系统操作。

在Node.js v6.0之前创建Buffer对象直接使用new Buffer()构造函数来创建对象实例,但是Buffer对内存的权限操作相比很大,可以直接捕获一些敏感信息。例如,new Buffer(arg) 的构造函数可以接受多种类型的参数,这可能导致意外的行为,尤其是在处理用户提供的输入时。

所以在v6.0以后,官方文档里面 new Buffer() 构造函数被弃用,并在后续版本中被完全移除。建议使用 Buffer.from() 接口去创建Buffer对象。

const data = "Hello, world!";
const buffer = Buffer.from(data, "utf8");

ArrayBuffer 对象用来表示通用的、固定长度的原始二进制数据缓冲区,是一个字节数组,可读但不可直接写。

Buffer 是 Node.JS 中用于操作 ArrayBuffer 的视图,是 TypedArray 的一种。是 Uint8Array 实例,但是与 TypedArray 有微小的不同。(buffer 可以类比为视图)Buffer.from()TypedArray.from() 有着不同的实现。

具体来说,typedArray.form()可接受第二个参数为映射函数,Buffer.form()不行。

创建的常用方法:

  • Buffer.from(array):返回包含给定八位字节数组的副本的新 buffer
  • Buffer.from(buffer):返回包含给定 buffer 副本的新 buffer
  • Buffer.from(arrayBuffer[, betyOffset[, len]]):返回与给定 arrayBuffer 共享同一段内存的新 Buffer
  • Buffer.from(string):返回包含给定字符串副本的新 Buffer
  • Buffer.alloc(size):返回一个指定大小的新建的已经初始化的 Buffer

5.1.Buffer 转 ArrayBuffer

使用Buffer.buffer属性即可

const buffer = Buffer.from("Hello, world!", "utf8");
const arrayBuffer = buffer.buffer;

5.2.ArrayBuffer 转 Buffer

使用Buffer.from()静态方法

const arrayBuffer = new ArrayBuffer(8);
const buffer = Buffer.from(arrayBuffer);

6.AudioBuffer

ArrayBuffer 里面的数据并没有分类,AudioBuffer 接口表示存在内存里的一段短小的音频资源,利用 AudioContextdecodeAudioData()方法从一个音频文件构建,或者利用 AudioContext.createBuffer()从原始数据构建。把音频放入 AudioBuffer 后,可以传入到一个 AudioBufferSourceNode 进行播放。

从一个音频文件构建AudioCOntext.decodeAudioData() -放入-> AudioBuffer -播放-> AudioBufferSourceNode

从原始数据构建AudioCOntext.createBuffer() -放入-> AudioBuffer -播放-> AudioBufferSourceNode

创建如下:

var audioCtx = new AudioContext();
audioCtx.decodeAudioData(arrBuffer, function (audioBuffer) {
    // audioBuffer就是AudioBuffer
});

属性:

  • AudioBuffer.sampleRate:存储在缓存区的 PCM 数据的采样率:浮点数,单位为 sample/s。
  • AudioBuffer.length:返回存储在缓存区的 PCM 数据的采样帧率:整形。
  • AudioBuffer.duration:返回存储在缓存区的 PCM 数据的时长:双精度型(单位为秒)。
  • AudioBuffer.numberOfChannels:返回存储在缓存区的 PCM 数据的通道数:整形。

方法:

  • AudioBuffer.getChannelData():返回一个 Float32Array,包含了带有频道的 PCM 数据,由频道参数定义(有 0 代表第一个频道)
  • AudioBuffer.copyFromChannel():从 AudioBuffer 的指定频道复制到数组终端。
  • AudioBuffer.copyToChannel():复制样品到原数组的 AudioBuffer 的指定频道

7.Base64

Base64 是一组相似的二进制到文本(binary-to-text)的编码规则。一个常见应用是对二进制数据进行编码,以便将其纳入 dataURL

在前端经常会碰到,格式是 data:[<mediatype>][;base64],<data>

在 JavaScript 中,有两个函数被分别用来处理解码和编码 Base64 字符串:

  • atob():解码通过 Base-64 编码的字符串数据(“atob”应读作“ASCII to binary”)
  • btoa():从二进制数据“字符串”创建一个 Base-64 编码的 ASCII 字符串(“btoa”应读作“binary to ASCII”)

优点:

  • 可以将二进制数据(比如图片)转化为可打印字符,方便传输数据。
  • 对数据进行简单的加密,肉眼是安全的。
  • 如果是在 html 或者 css 处理图片,可以减少 http 请求。

缺点:

  • 内容编码后体积变大, 至少大 1/3。因为是三字节变成四个字节,当只有一个字节的时候,也至少会变成三个字节。
  • 编码和解码需要额外工作量。

用 Bse64 展示图片:

HTML 部分代码

<!-- 读取文件,用 Bse64 展示图片 -->
<input type="file" id="input" />
<img id="img" />

js 部分代码

const input = document.getElementById("input");
const img = document.getElementById("img");

input.onchange = (e) => {
    const reader = new FileReader();
    reader.readAsDataURL(e.target.files[0]);
    reader.onload = (e) => {
        img.src = e.target.result;
        console.log(e.target.result);
    };
};

8.FileReader

FileReader 对象允许 Web 应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容

以下为 FileReader 的对象方法:

方法 说明
FileReader.abort() 中止读取操作。在返回时,readyState 属性为 DONE
FileReader.readAsArrayBuffer() 开始读取指定的 Blob 中的内容。完成后,result 属性中保存的将是被读取文件的 ArrayBuffer 数据对象。
FileReader.readAsBinaryString() 开始读取指定的 Blob 中的内容。完成后,result 属性中将包含所读取文件的原始二进制数据
FileReader.readAsDataURL() 开始读取指定的 Blob 中的内容。完成后,result 属性中将包含一个 data: URL 格式的 Base64 字符串以表示所读取文件的内容
FileReader.readAsText() 开始读取指定的 Blob 中的内容。一旦完成,result 属性中将包含一个字符串以表示所读取的文件内容

二、相关转换

1.Blob 与 File 互转

1.1.Blob 转 File

File 对象其实是特殊类型的 Blob,且可以用在任意的 Blob 类型的上下文中。比如说,FileReader, URL.createObjectURL(), createImageBitmap(), 及 XMLHttpRequest.send() 都能处理 BlobFile

File 接口也继承了 Blob 接口的属性。这两个东西互转感觉没必要,如果要转的话,可以利用 FileReader 作为桥梁,先转成 ArrayBuffer,然后在转成相应的 Blob 或者 File

const blob = new Blob(["blob文件"], { type: "text/plain" });
// blob 转 file
const file = new File([blob], "test", { type: blob.type });
console.log("file:", file);

1.2.File 转 Blob

const file = new File(["文件对象"], "test", { type: "text/plain" });
// file 转 blob
const blob = new Blob([file], { type: file.type });
console.log("blob:", blob);

2.File、Blob、img 与 Base64 互转

2.1.File、Blob 转 Base64

//file为 File对象 或 Blob对象
function fileToDataURL(file) {
    let reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = function (e) {
        return reader.result;
    };
}

2.2.img 转 Base64

// 本地图片转base64 ,注意链接是本地链接不是网络地址
const img2base64 = (imgUrl) => {
    let image = new Image();
    image.src = imgUrl;
    return new Promise((resolve) => {
        image.onload = () => {
            let canvas = document.createElement("canvas");
            canvas.width = image.width;
            canvas.height = image.height;
            var context = canvas.getContext("2d");
            context.drawImage(image, 0, 0, image.width, image.height);
            let dataUrl = canvas.toDataUrl("image/png");
            resolve(dataUrl);
        };
    });
};
// 获取base64
img2base64("./logo.png").then((res) => {
    console.log(res);
});

2.3.Base64 转 Blob、File

// Base64 转为 Blob
function dataURLToBlob(fileDataUrl) {
    let arr = fileDataUrl.split(","),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);
    while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], { type: mime });
}

// Base64 转为 File
function dataURLToFile(fileDataUrl, filename) {
    let arr = fileDataUrl.split(","),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);
    while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new File([u8arr], filename, { type: mime });
}

3.File、Blob 转 Object URL

// object:用于创建 URL 的 File 对象、Blob 对象
const objectUrl = URL.createObjectURL(object);

4.File、Blob 与 ArrayBuffer 互转

File/Blob 转成 ArrayBuffer 需要借助 FileReader 类。

4.1.File/Blob 转 ArrayBuffer

// file为 File对象 或 Blob对象
function fileToArrayBuffer(file) {
    const reader = new FileReader();
    reader.readAsArrayBuffer(file);
    reader.onload = () => {
        return reader.result;
    };
}

ArrayBuffer 转成 File/Blob 直接调用 new File/Blob 构造函数

4.2.ArrayBuffer 转 File

function bufToFile(buf, filename) {
    return new File([buf], filename);
}

4.3.ArrayBuffer 转 blob

function bufToBlob(buf, mimeType = "") {
    return new Blob([buf], { type: mimeType });
}

Blob 函数的第二个参数与 File 函数的第二个参数略有不同,Blob 是一个对象,对象中有一个 type 属性,默认值为 "",它代表了将会被放入到 blob 中的数组内容的 MIME 类型。Blob 的第一个参数也是一个由 ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的数组。

DOMString 是 DOM 字符串,比如:hey!。它的 type 则是:text/html

5.Blob 转 audio

使用URL.createObjectURLnew Audio进行转换

const url = URL.createObjectURL(blob);
const audioUrl = new Audio(url);

6.File 转 audioBuffer

在 Web 网页中,用户选择的文件是个 file 对象,我们可以将这个文件对象转换成 Blob、ArrayBuffer 或者 Base64。
在音频处理这里,都是使用ArrayBuffer这个数据类型。

file.onchange = function (event) {
    var file = event.target.files[0];
    // 开始识别
    var reader = new FileReader();
    reader.onload = function (event) {
        var arrBuffer = event.target.result;
        // arrBuffer就是包含音频数据的ArrayBuffer对象
    };
    reader.readAsArrayBuffer(file);
};

使用的是readAsArrayBuffer()方法,无论是 MP3 格式、OGG 格式还是 WAV 格式,都可以转换成 ArrayBuffer 类型。

7.audioBuffer 转 Blob

export function audioBufferToWav(buffer: AudioBuffer, opt?: any) {
    opt = opt || {};
    const numChannels = buffer.numberOfChannels;
    const sampleRate = opt.sampleRate || buffer.sampleRate;
    const format = opt.float32 ? 3 : 1;
    const bitDepth = format === 3 ? 32 : 16;
    let result;
    if (numChannels === 2) {
        result = interleave(buffer.getChannelData(0), buffer.getChannelData(1));
    } else {
        result = buffer.getChannelData(0);
    }

    return encodeWAV(result, format, sampleRate, numChannels, bitDepth);
}

function encodeWAV(samples: Float32Array, format: number, sampleRate: number, numChannels: number, bitDepth: number) {
    const bytesPerSample = bitDepth / 8;
    const blockAlign = numChannels * bytesPerSample;

    let buffer = new ArrayBuffer(44 + samples.length * bytesPerSample);
    let view = new DataView(buffer);

    writeString(view, 0, "RIFF");
    view.setUint32(4, 36 + samples.length * bytesPerSample, true);
    writeString(view, 8, "WAVE");
    writeString(view, 12, "fmt ");
    view.setUint32(16, 16, true);
    view.setUint16(20, format, true);
    view.setUint16(22, numChannels, true);
    view.setUint32(24, sampleRate, true);
    view.setUint32(28, sampleRate * blockAlign, true);
    view.setUint16(32, blockAlign, true);
    view.setUint16(34, bitDepth, true);
    writeString(view, 36, "data");
    view.setUint32(40, samples.length * bytesPerSample, true);
    if (format === 1) {
        floatTo16BitPCM(view, 44, samples);
    } else {
        writeFloat32(view, 44, samples);
    }

    return buffer;
}

function interleave(inputL: Float32Array, inputR: Float32Array) {
    let length = inputL.length + inputR.length;
    let result = new Float32Array(length);

    let index = 0;
    let inputIndex = 0;

    while (index < length) {
        result[index++] = inputL[inputIndex];
        result[index++] = inputR[inputIndex];
        inputIndex++;
    }
    return result;
}

function writeFloat32(output: DataView, offset: number, input: Float32Array) {
    for (let i = 0; i < input.length; i++, offset += 4) {
        output.setFloat32(offset, input[i], true);
    }
}

function floatTo16BitPCM(output: DataView, offset: number, input: Float32Array) {
    for (let i = 0; i < input.length; i++, offset += 2) {
        let s = Math.max(-1, Math.min(1, input[i]));
        output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
    }
}

function writeString(view: DataView, offset: number, string: string) {
    for (let i = 0; i < string.length; i++) {
        view.setUint8(offset + i, string.charCodeAt(i));
    }
}

Arraybuffer 加头

const _writeString = (view: DataView, offset: number = 0, value: string = "") => {
    _.forEach(value.split(""), (char) => view.setUint8(offset++, char.charCodeAt(0)));
};

export const AddRiffHeader = (data: ArrayBuffer, sampleRate: number = 16000, numberOfChannels: number = 1, bitsPerSample = 16) => {
    const headerSize = 44;
    const dataID = "data";
    const dataSize = data.byteLength;
    const fmtChunkID = "fmt ";
    const fmtPcmChunkSize = 16;
    const audioPcmFormat = 1;
    const blockAlign = bitsPerSample / 8;
    const riffChunkID = "RIFF";
    const riffChunkSize = 4 + (8 + fmtPcmChunkSize) + (8 + dataSize);
    const riffChunkFormat = "WAVE";

    const header = new ArrayBuffer(headerSize);
    const headerView = new DataView(header);

    _writeString(headerView, 0, riffChunkID);
    headerView.setUint32(4, riffChunkSize, true);
    _writeString(headerView, 8, riffChunkFormat);
    _writeString(headerView, 12, fmtChunkID);
    headerView.setUint32(16, fmtPcmChunkSize, true);

    headerView.setUint16(20, audioPcmFormat, true);
    headerView.setUint16(22, numberOfChannels, true);
    headerView.setUint32(24, sampleRate, true);
    headerView.setUint32(28, sampleRate * blockAlign, true);
    headerView.setUint16(32, blockAlign, true);
    headerView.setUint16(34, bitsPerSample, true);
    _writeString(headerView, 36, dataID);
    headerView.setUint32(40, dataSize, true);

    const result = new Uint8Array(header.byteLength + data.byteLength);
    result.set(new Uint8Array(header), 0);
    result.set(new Uint8Array(data), header.byteLength);
    return result.buffer;
};

8.AudioBuffer 转换成 WAV 格式

使用 npm 已有包
安装包:npm install audiobuffer-to-wav --save

用例:

var toWav = require("audiobuffer-to-wav");
var xhr = require("xhr");
var context = new AudioContext();

// request the MP3 as binary arraybuffer
xhr(
    {
        uri: "audio/track.mp3",
        responseType: "arraybuffer",
    },
    function (err, body, resp) {
        if (err) throw err;
        // decode the MP3 arraybuffer into an AudioBuffer
        audioContext.decodeAudioData(resp, function (buffer) {
            // encode AudioBuffer to WAV ArrayBuffer
            var wav = toWav(buffer); //实际上是在audio前加wav的头

            // do something with the WAV ArrayBuffer ...
        });
    }
);

引用:
audiobuffer-to-wav

9.ArrayBuffer 转 AudioBuffer

这里的ArrayBuffer相对于把音频文件数组化了,ArrayBuffer 里面的数据并没有分类,统一分解了,想要准确提取某一截音频数据,提取不出来,才需要转换成 AudioBuffer,AudioBuffer是一个仅仅包含音频数据的数据对象,是 Web Audio API 中的一个概念。

使用 AudioContext 对象的decodeAudioData()方法,代码如下:

var audioCtx = new AudioContext();

audioCtx.decodeAudioData(arrBuffer, function (audioBuffer) {
    // audioBuffer就是AudioBuffer
});

创建一个空的 AudioBuffer,复制现有的通道数据前 3 秒的数据,然后复制的内容写入到这个空的 AudioBuffer

// 声道数量和采样率
var channels = audioBuffer.numberOfChannels;
var rate = audioBuffer.sampleRate;

// 截取前3秒
var startOffset = 0;
var endOffset = rate * 3;
// 3秒对应的帧数
var frameCount = endOffset - startOffset;

// 创建同样采用率、同样声道数量,长度是前3秒的空的AudioBuffer
var newAudioBuffer = new AudioContext().createBuffer(channels, endOffset - startOffset, rate);
// 创建临时的Array存放复制的buffer数据
var anotherArray = new Float32Array(frameCount);
// 声道的数据的复制和写入
var offset = 0;
for (var channel = 0; channel < channels; channel++) {
    audioBuffer.copyFromChannel(anotherArray, channel, startOffset);
    newAudioBuffer.copyToChannel(anotherArray, channel, offset);
}

// newAudioBuffer就是全新的复制的3秒长度的AudioBuffer对象

总结:

以上相关流之间的各种转化,可根据需要在下面图片中找对应的例子

参考:


文章作者: 弈心
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 弈心 !
评论
  目录