TypedArray用途

759 阅读4分钟

简介

TypedArray 是 JavaScript 中的一种数据类型,用于表示一个固定长度的二进制数据缓冲区。它提供了一种有效的方式来操作和处理二进制数据,特别适用于前端音视频业务。本文将介绍 TypedArray 的基本用法以及在前端音视频业务中的几个应用场景。

基本用法

TypedArray 可以表示不同类型的二进制数据,如整数、浮点数等。它包括以下几种类型:

  • Int8Array: 8 位带符号整数
  • Uint8Array: 8 位无符号整数
  • Int16Array: 16 位带符号整数
  • Uint16Array: 16 位无符号整数
  • Int32Array: 32 位带符号整数
  • Uint32Array: 32 位无符号整数
  • Float32Array: 32 位浮点数
  • Float64Array: 64 位浮点数

以下是创建 TypedArray 的示例代码:

// 创建一个长度为 4 的 Int32Array
const intArray = new Int32Array(4);

// 创建一个包含指定数组的 Uint8Array
const uintArray = new Uint8Array([10, 20, 30, 40]);

// 创建一个共享指定 ArrayBuffer 的 Float32Array
const floatArray = new Float32Array(buffer);

通过访问 TypedArray 实例的属性和方法,我们可以对其中的数据进行读取和修改。例如:

// 修改 Int32Array 中的值
intArray[0] = 42;

// 读取 Uint8Array 中的值
const value = uintArray[2];

前端音视频业务中的应用场景

在前端音频解码过程中,通常需要对原始音频数据进行解析和处理。TypedArray 提供了高效的方式来处理音频样本数据。

例如,有一段音频存储在一个ArrayBuffer中,采样率是44100HZ,位深16bit,单声道。用修改字节的方式把这段音频音量增大一倍,如何做?

  1. 从ArrayBuffer中获取音频数据。
  2. 将音频数据解码为PCM格式。
  3. 遍历PCM数据,并将每个样本乘以2(增大一倍的音量)。
  4. 如果样本值超出了16位有符号整数的范围(-32768 到 32767),则进行截断或饱和处理。
  5. 将修改后的PCM数据重新编码为ArrayBuffer。
// 假设您已经有一个名为audioData 的ArrayBuffer

// 创建一个Int16Array来读取和修改音频数据
const dataView = new Int16Array(audioData);

// 计算样本数
const sampleCount = dataView.length;

// 创建一个新的Int16Array来存储修改后的音频数据
const modifiedDataView = new Int16Array(sampleCount);

// 遍历每个样本并增大音量
for (let i = 0; i < sampleCount; i++) {
  // 读取样本值
  const sample = dataView[i];

  // 增大音量一倍
  const modifiedSample = sample * 2;

  // 截断或饱和处理样本值
  const finalSample = Math.max(-32768, Math.min(modifiedSample, 32767));

  // 写入修改后的样本值到新的Int16Array
  modifiedDataView[i] = finalSample;
}

// modifiedDataView.buffer 现在包含音量增大一倍的音频数据的ArrayBuffer

当然你也可以用DataView来实现上面的逻辑

// 假设您已经有一个名为audioData 的ArrayBuffer

// 创建一个Int16Array来读取和修改音频数据
const dataView = new Int16Array(audioData);

// 计算样本数
const sampleCount = dataView.length;

// 创建一个新的Int16Array来存储修改后的音频数据
const modifiedDataView = new Int16Array(sampleCount);

// 遍历每个样本并增大音量
for (let i = 0; i < sampleCount; i++) {
  // 读取样本值
  const sample = dataView[i];

  // 增大音量一倍
  const modifiedSample = sample * 2;

  // 截断或饱和处理样本值
  const finalSample = Math.max(-32768, Math.min(modifiedSample, 32767));

  // 写入修改后的样本值到新的Int16Array
  modifiedDataView[i] = finalSample;
}

// modifiedDataView.buffer 现在包含音量增大一倍的音频数据的ArrayBuffer

用TypedArray和DataView各有什么优势呢?

TypedArray 的优点:

  1. 直观性:TypedArray 提供了更直观的数据访问方式,通过索引来访问和修改数据,不需要手动计算偏移量。
  2. 更高的性能:TypedArray 在访问和修改数据时更加高效,因为它们使用底层的二进制数据表示,并且直接操作原始内存。
  3. 内存优化:TypedArray 使用固定的数据类型,它们的内存占用更加紧凑,不会存在字节对齐等额外开销。

DataView 的优点:

  1. 灵活性:DataView 提供了更灵活的数据解析和访问方式,可以处理不同的数据类型和字节顺序,适用于更复杂的数据操作。
  2. 兼容性:DataView 支持更广泛的平台和浏览器,包括旧版浏览器和一些特定环境。
  3. 跨平台和跨语言:DataView 适用于跨平台和跨语言的场景,可以与其他语言和平台进行数据交换。

图片渲染

在前端视频渲染中,通常需要对视频帧进行解码和处理。TypedArray 可以用来存储视频帧的像素数据,然后通过 Canvas API 或 WebGL 进行渲染。例如,创建一个 Uint8Array 来表示图片中的像素数据,就可以对图像中的每一个像素精准控制。

// 创建一个Uint8Array,表示一个10x10的图像,每个像素使用RGB三个通道表示
const imageWidth = 10;
const imageHeight = 10;
const numChannels = 3; // RGB三个通道

const imageDataSize = imageWidth * imageHeight * numChannels;
const imageArray = new Uint8Array(imageDataSize);

// 设置图像像素数据
for (let y = 0; y < imageHeight; y++) {
  for (let x = 0; x < imageWidth; x++) {
    const pixelIndex = (y * imageWidth + x) * numChannels;

    // 设置红色通道的值
    imageArray[pixelIndex] = 255;

    // 设置绿色通道的值
    imageArray[pixelIndex + 1] = 0;

    // 设置蓝色通道的值
    imageArray[pixelIndex + 2] = 0;
  }
}

// 读取图像像素数据
for (let y = 0; y < imageHeight; y++) {
  for (let x = 0; x < imageWidth; x++) {
    const pixelIndex = (y * imageWidth + x) * numChannels;

    // 获取红色通道的值
    const red = imageArray[pixelIndex];

    // 获取绿色通道的值
    const green = imageArray[pixelIndex + 1];

    // 获取蓝色通道的值
    const blue = imageArray[pixelIndex + 2];

    console.log(`Pixel at (${x}, ${y}): RGB(${red}, ${green}, ${blue})`);
  }
}

数据计算

Uint32Array (32位无符号整数) 是一种TypedArray,它以无符号的32位整数形式存储数据。Uint32Array常用于处理大范围的数值计算、图像处理和网络通信等领域。

下面展示一下如何使用Uint32Array进行简单的数值计算。

// 创建一个Uint32Array,存储一组数值
const data = new Uint32Array(10);
data[0] = 100;
data[1] = 200;
data[2] = 300;
data[3] = 400;
data[4] = 500;
data[5] = 600;
data[6] = 700;
data[7] = 800;
data[8] = 900;
data[9] = 1000;

// 计算数据的总和
let sum = 0;
for (let i = 0; i < data.length; i++) {
  sum += data[i];
}

console.log('Sum:', sum);

// 查找最大值和最小值
let max = data[0];
let min = data[0];

for (let i = 1; i < data.length; i++) {
  if (data[i] > max) {
    max = data[i];
  }

  if (data[i] < min) {
    min = data[i];
  }
}

console.log('Max:', max);
console.log('Min:', min);

可能有人说不需要用Uint32Array,普通数组一样可以实现这样的功能啊,那Uint32Array有什么优势呢?

  1. 内存占用:Uint32Array使用无符号32位整数来存储数据,每个元素占用4个字节。相比之下,普通数组在JavaScript中默认使用64位浮点数表示数字,每个元素占用8个字节。因此,使用Uint32Array可以显著减少内存占用,特别是在处理大量数据时,可以节省大量内存空间。
  2. 性能优化:Uint32Array是一种类型化数组,底层以原生二进制数据存储,提供了更高效的访问和操作。相比之下,普通数组是动态的,可以包含任意类型的数据,因此在访问和操作时需要进行类型检查和转换,会带来一定的性能损耗。Uint32Array的操作通常比普通数组更快。
  3. 数值范围:Uint32Array存储的是无符号32位整数,可以表示的数值范围是0到2^32-1,更适合处理大范围的数值计算。而普通数组中的数值在JavaScript中使用64位浮点数表示,受到浮点数精度和数值范围的限制。
  4. 类型约束:Uint32Array是一种类型化数组,它的元素类型是固定的,只能存储无符号32位整数。这种类型约束可以提供更强的数据一致性和类型安全,避免了因为类型混乱导致的错误。

总结

通过使用 TypedArray,可以高效地处理和操作二进制数据,想想你工作中有哪些场景可以用上一用呢?