フロントエンドで音声データを扱うときの基礎知識


最近音声データを触ることが多かったので、基本的な用語の説明だけしていく。

sampleRate

単位 : Hz
1秒間のデータを分割する数。
6000Hzだと、0.01秒ごとに分割。
この分割した1つ1つのデータをサンプリングと呼ぶ。
CDは44.1KHz。ChromeのStreamingも44.1KHz。

bitDepth

単位 : bit
デフォルト : 16bit
サンプリングに対して与える情報量。
何bit与えるかを示す。

channels

音をどの程度分けるか。多いほど立体感のある音が作れる。
1 なら モノラル, 2 なら ステレオ。
基本的には、音声なら 1, 音楽なら 2 以上。

sampleRateを修正するJavaScript

48KHzを16KHz(LINEAR16)で保存するには、 downSampleBuffer(buff, 48000, 16000) で呼び出す。

export const downsampleBuffer = (buffer, sampleRate, outSampleRate) => {
  if (outSampleRate > sampleRate) {
    console.error('downsampling rate show be smaller than original sample rate');
  }

  const sampleRateRatio = sampleRate / outSampleRate;
  const newLength = Math.round(buffer.length / sampleRateRatio);
  const result = new Int16Array(newLength);
  let offsetResult = 0;
  let offsetBuffer = 0;
  // bpsを縮める処理 (n byte分のデータを合算して、n byteで割る)
  while (offsetResult < result.length) {
    const nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);

    // 次のoffsetまでの合計を保存
    let accum = 0;
    let count = 0;
    for (let i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i += 1) {
      accum += buffer[i];
      count += 1;
    }

    // 16進数で保存 (LINEAR16でストリーミングするため)
    result[offsetResult] = Math.min(1, accum / count) * 0x7FFF;
    offsetResult += 1;
    offsetBuffer = nextOffsetBuffer;
  }
  return result.buffer;
};

PCM (pulse code modulation)

圧縮していない生データのこと。
拡張子 raw はPCMである。
rawデータに sampleRate, bitDepth, channelsなどのメタ情報をヘッダに加えたものがwavファイル。 と思っていたが、wavファイルはrawデータ以外も使えるらしい。

WAVファイルのヘッダ情報

http://soundfile.sapp.org/doc/WaveFormat/

Int16Array (16bitのデータをwavに変換するJavaScript)

export const createWavHeader = (view, config) => {
  const {
    sampleRate, bitDepth, channel, dataLength,
  } = config;
  writeUTFBytes(view, 0, 'RIFF');

  // file length
  view.setUint32(4, dataLength + 32, true);
  // set file size at the end
  writeUTFBytes(view, 8, 'WAVE');
  // FMT sub-chunk
  writeUTFBytes(view, 12, 'fmt ');
  // chunk size
  view.setUint32(16, 16, true);
  // format code
  view.setUint16(20, 1, true);
  // channels : (current use monoral : 1)
  view.setUint16(22, channel, true);
  // sampling rate
  view.setUint32(24, sampleRate, true);
  // data rate
  view.setUint32(28, sampleRate * 2 * channel, true);
  // data block size
  view.setUint16(32, channel * 2, true);
  // bits per sample (bitDepth)
  view.setUint16(34, bitDepth, true);
  // data sub-chunk
  writeUTFBytes(view, 36, 'data');
  // DUMMY data chunk length (set real value on export)
  view.setUint32(40, dataLength, true);
};

export const exportWAV = (
  channel, buffers /* Int16Array */, wavConf = { sampleRate: AUDIO_SAMPLE_RATE, bitDepth: AUDIO_BIT_DEPTH }
) => {
  const dataLength = buffers.length * channel * 2;
  const buffer = new ArrayBuffer(44 + dataLength);
  const view = new DataView(buffer);

  // copy WAV header data into the array buffer
  createWavHeader(view, { ...wavConf, channel, dataLength });

  // write audio data
  writeFromInputBuffers(view, channel, 44, buffers);
  return new Blob([view], { type: 'audio/wav' });
};

音声認識系でよく利用される形で録音

$ brew install sox

音声認識サービスの自動認証は、wavファイルのヘッダ形式を見て、データが違うとエラーになる。

  • LINEAR16(16bit)
  • 16kHz
  • モノラル
rec --rate 16k --bits 16 --channels 1 test.wav

30秒だけ録音

rec --rate 16k --bits 16 --channels 3 test.wav trim 0 30

ハイレゾ( High-Resolution Audio)

ビットレートがめちゃくちゃ高いぜということ。

凄さ sampleRate(KHz) bitDepth(bit) channels bitRate(kbps)
通常 44.1 16 1 705.6
通常 44.1 16 1 705.6
ハイレゾ1 96 24 2 4608
ハイレゾ2 192 24 2 9216