Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Goと学ぶ音声ファイルの中身 (20220629-go-inside-of-audio-files)

Goと学ぶ音声ファイルの中身 (20220629-go-inside-of-audio-files)

Go言語の勉強会で登壇したときのスライドです。

2022年6月29日にBitkeyさんとVoicyの共催でGo勉強会を開催しました。

https://bitkey.connpass.com/event/248608/

内容は以下です。

- 非圧縮音声ファイルとして最も一般的なWavがどのようなデータ構造になっているか解説
- 便利なライブラリを使わずにGoでWavを読み込むコードの例を紹介

リンク集

Voicy Tech Story Vol.2Voicyのエンジニアチームによる技術書典。音声の再生/編集ができるライブラリbeepの紹介のスライドで紹介している本。
https://techbookfest.org/product/5189896473935872

Voicy Tech Story Vol.3今回の発表では紹介していないが、自分も執筆に参加している技術書典。担当した章のタイトルは「社長の声で言いたい放題! 〜音声変換 Voice Conversion〜」
https://techbookfest.org/product/5675561475112960

voi-chordVoicyのエンジニアメンバーが音声発信をするVoicyチャンネル。テックを愛するVoicy社員がテクノロジーやエンジニアリングといったテーマを中心に自由に語り合うゆるふわチャンネルです。
https://voicy.jp/channel/1305

Koki Senda

June 29, 2022
Tweet

More Decks by Koki Senda

Other Decks in Programming

Transcript

  1. 自己紹介 ▪ 千田 航己 (せんだこうき) • せんちゃん • @thousanda, @thousan_da

    ▪ SRE • AWSやKubernetesのリソースを管理 • 基盤システムを開発・保守 /運用 ▪ 学生時代 • ディープラーニングを用いた音声変換の研究 ▪ 好きな食べ物 • ROYCE’の生チョコレート [オーレ] 2
  2. Goで音声ファイルを読み込みたい 便利なライブラリを使うと簡単 ▪ beep • 音声の再生や編集ができるライブラリ • ファイルオープンしてデコードするだけで音声ファイルが読み込める 4 import

    ( "os" "github.com/faiface/beep/wav" ) // ファイルオープン f, err := os.Open("sample.wav") // 読み込み (デコード) streamer, format, err := wav.Decode(f) defer streamer.Close() 技術書典に詳しく書いてあります
  3. Goで音声ファイルを読み込みたい 便利なライブラリを使うと簡単 ▪ beep • 音声の再生や編集ができるライブラリ • ファイルオープンしてデコードするだけで音声ファイルが読み込める 5 import

    ( "os" "github.com/faiface/beep/wav" ) // ファイルオープン f, err := os.Open("sample.wav") // 読み込み (デコード) streamer, format, err := wav.Decode(f) defer streamer.Close() 技術書典に詳しく書いてあります
  4. WAVファイルとRIFF形式 ▪ 実際のWavファイルの先頭部分 ▪ RIFF形式 • チャンク = RIFFの基本単位 ◦

    ID : 識別子 ◦ Size : Dataのバイト数 (0x00049A84 = 301700 Byte) ◦ Data : データ本体 ([]byte{0x57, 0x41, 0x56, …}) 9 チャンクの組み合わせでファイルを表現する ‘R’ ‘I’ ‘F’ ‘F’ / 84 9A 04 00 / 57 41 56 … ID Size Data
  5. WAVファイルのデータ構造 ▪ RIFFチャンク • ID: []byte{‘R’, ‘I’, ‘F’, ‘F’} (固定文字列)

    • Size: 301700 • Data: WAVEチャンク ◦ ID: []byte{‘W’, ‘A’, ‘V’, ‘E’} ◦ サブチャンク1 ◦ サブチャンク2 ◦ ・・・ 11
  6. WAVファイルのデータ構造 ▪ RIFFチャンク • ID: []byte{‘R’, ‘I’, ‘F’, ‘F’} (固定文字列)

    • Size: 301700 • Data: WAVEチャンク ◦ ID: []byte{‘W’, ‘A’, ‘V’, ‘E’} ◦ formatチャンク • ID: []byte{‘f’, ‘m’, ‘t’, ‘ ’} • Size: 16 • Data: サンプリング周波数などの情報が決まった順番で続く ◦ dataチャンク • ID: []byte{‘d’, ’a’, ’t’, ’a’} • Size: 301664 • Data: 波形データ本体 ◦ ・・・ 12
  7. WAVファイルのデータ構造 ▪ RIFFチャンク • ID: []byte{‘R’, ‘I’, ‘F’, ‘F’} (固定文字列)

    • Size: 301700 • Data: WAVEチャンク ◦ ID: []byte{‘W’, ‘A’, ‘V’, ‘E’} ◦ formatチャンク • ID: []byte{‘f’, ‘m’, ‘t’, ‘ ’} • Size: 16 • Data: サンプリング周波数などの情報が決まった順番で続く ◦ dataチャンク • ID: []byte{‘d’, ’a’, ’t’, ’a’} • Size: 301664 • Data: 波形データ本体 13 構造的に書いたが、実際は前から順に保存されているだけ
  8. WAVを読むコードの設計 ▪ func readWav() • func readWavFormat() ◦ // fmtチャンクを読み込む

    • func readWavData() ◦ // dataチャンクを読み込む ▪ func readChunk() • // チャンクを読み込む汎用的な関数 • // readWavFormatとreadWavDataの内部で使用 15
  9. 実行結果 ▪ メタデータ • formatチャンク ▪ データ本体 • dataチャンク 16

    WavFormat { ID: "fmt " Size: 16 AudioFmt: 1 ChNum: 1 SmplRate: 48000 ByteRate: 96000 BlockSize: 2 BitPerSmpl: 16 } WavData { ID: "data" Size: 301664 Data: [9 0 11 0 11 0 12 0 11 0 ...] }
  10. ▪ func readChunk() • チャンクを読み込む汎用的な関数 • readWavFormatとreadWavDataの内部で使用 readChunkの実装 17 type

    Chunk struct { ID string Size uint32 Data []byte } func readChunk(b []byte) (*Chunk, error) { id := string(b[:4]) size := binary.LittleEndian.Uint32(b[4:8]) data := b[8 : 8+size] return &Chunk{ ID: id, Size: size, Data: data, }, nil }
  11. WAVを読むコードの設計 ▪ func readWav() • func readWavFormat() ◦ // fmtチャンクを読み込む

    • func readWavData() ◦ // dataチャンクを読み込む ▪ func readChunk() • // チャンクを読み込む汎用的な関数 • // readWavFormatとreadWavDataの内部で使用 18
  12. readWavFormatの実装 ▪ RIFFチャンク • ID: []byte{‘R’, ‘I’, ‘F’, ‘F’} (固定文字列)

    • Size: 301700 • Data: WAVEチャンク ◦ ID: []byte{‘W’, ‘A’, ‘V’, ‘E’} ◦ formatチャンク • ID: []byte{‘f’, ‘m’, ‘t’, ‘ ’} • Size: 16 • Data ◦ dataチャンク • ID: []byte{‘d’, ’a’, ’t’, ’a’} • Size: 301664 • Data 19 func readWavFormat(b []byte) (*WavFormat, error) { chunk, err := readChunk(b) if chunk.ID != "fmt " { return nil, fmt.Errorf("not \"fmt \", found %s", chunk.ID) } id := chunk.ID size := chunk.Size audioFmt := binary.LittleEndian.Uint16(chunk.Data[:2]) chNum := binary.LittleEndian.Uint16(chunk.Data[2:4]) smplRate := binary.LittleEndian.Uint32(chunk.Data[4:8]) byteRate := binary.LittleEndian.Uint32(chunk.Data[8:12]) blockSize := binary.LittleEndian.Uint16(chunk.Data[12:14]) bitPerSmpl := binary.LittleEndian.Uint16(chunk.Data[14:16]) return &WavFormat{ ID: id, Size: size, AudioFmt: audioFmt, ChNum: chNum, SmplRate: smplRate, ByteRate: byteRate, BlockSize: blockSize, BitPerSmpl: bitPerSmpl, }, nil }
  13. readWavDataの実装 ▪ RIFFチャンク • ID: []byte{‘R’, ‘I’, ‘F’, ‘F’} (固定文字列)

    • Size: 301700 • Data: WAVEチャンク ◦ ID: []byte{‘W’, ‘A’, ‘V’, ‘E’} ◦ formatチャンク • ID: []byte{‘f’, ‘m’, ‘t’, ‘ ’} • Size: 16 • Data ◦ dataチャンク • ID: []byte{‘d’, ’a’, ’t’, ’a’} • Size: 301664 • Data 20 func readWavData(b []byte, offset uint32) (*WavData, error) { chunk, err := readChunk(b[offset:]) return &WavData{ ID: chunk.ID, Size: chunk.Size, Data: chunk.Data, }, nil }
  14. むすび ▪ 音声ファイルの中身についてGoと学びました • WAVファイルという非圧縮の音声ファイルについて • Goでライブラリに頼らずに WAVファイルを読み込んでみた ▪ 今後の課題

    • 必須じゃない情報が含まれている場合に対応する • ReadAllでファイルを丸ごと読み込むのではなく、 Streamを使う • (便利なライブラリに頼る ) ▪ 今日話せなかった内容 • 音声ファイルの種類 • binaryパッケージ 21
  15. 参考 ▪ SlidesCodeHighlighter • https://romannurik.github.io/SlidesCodeHighlighter/ ▪ Voicy Tech Story Vol.2

    • https://techbookfest.org/product/5189896473935872 ▪ beep • https://github.com/faiface/beep ▪ 音ファイル(拡張子:WAVファイル)のデータ構造について • https://www.youfit.co.jp/archives/1418 ▪ Wikipedia • WAV https://en.wikipedia.org/wiki/WAV • RIFF https://en.wikipedia.org/wiki/Resource_Interchange_File_Format 23
  16. 音声ファイルの種類 ▪ 非圧縮 • 圧縮されていない • WAV, AIFF など ▪

    可逆圧縮 (ロスレス) • 圧縮されているが、元に戻すことができる • FLAC, ALAC など ▪ 非可逆圧縮 • 圧縮されており、元に戻すことはできない • 人間の知覚的に聞こえ方への影響が少ない情報をカットしている • MP3, AAC など 25 元に戻せる 元に戻せない 非圧縮音源 非圧縮音源 非圧縮音源 可逆圧縮音源 非可逆圧縮音源
  17. WAVを読むコードの実装 ▪ readChunk • チャンクを読み込む汎用的な関数 ▪ readWav • 以下を順に呼ぶ ◦

    readRIFF ◦ readWavFormat ◦ readWavData • それぞれの内部では readChunkが使われている 27 type Wav struct { WavFormat WavData } func readWav(b []byte) (*Wav, error) { riff, err := readRIFF(b) if riff.Format != "WAVE" { return nil, fmt.Errorf("not \"WAVE\", found %s", riff.Format) } // フォーマットチャンク wfmt, err := readWavFormat(riff.Data[4:]) // データチャンク wdata, err := readWavData(riff.Data, 4+8+wfmt.Size) return &Wav{ WavFormat: *wfmt, WavData: *wdata, }, nil }
  18. WAVを読むコードの実装 ▪ readChunk • チャンクを読み込む汎用的な関数 ▪ readWav • 以下を順に呼ぶ ◦

    readRIFF ◦ readWavFormat ◦ readWavData • それぞれの内部では readChunkが使われている 28 type Wav struct { WavFormat WavData } func readWav(b []byte) (*Wav, error) { riff, err := readRIFF(b) if riff.Format != "WAVE" { return nil, fmt.Errorf("not \"WAVE\", found %s", riff.Format) } // フォーマットチャンク wfmt, err := readWavFormat(riff.Data[4:]) // データチャンク wdata, err := readWavData(riff.Data, 4+8+wfmt.Size) return &Wav{ WavFormat: *wfmt, WavData: *wdata, }, nil }
  19. WAVを読むコードの実装 ▪ readChunk • チャンクを読み込む汎用的な関数 ▪ readWav • 以下を順に呼ぶ ◦

    readRIFF ◦ readWavFormat ◦ readWavData • それぞれの内部では readChunkが使われている 29 type Wav struct { WavFormat WavData } func readWav(b []byte) (*Wav, error) { riff, err := readRIFF(b) if riff.Format != "WAVE" { return nil, fmt.Errorf("not \"WAVE\", found %s", riff.Format) } // フォーマットチャンク wfmt, err := readWavFormat(riff.Data[4:]) // データチャンク wdata, err := readWavData(riff.Data, 4+8+wfmt.Size) return &Wav{ WavFormat: *wfmt, WavData: *wdata, }, nil }
  20. WAVを読むコードの実装 ▪ readChunk • チャンクを読み込む汎用的な関数 ▪ readWav • 以下を順に呼ぶ ◦

    readRIFF ◦ readWavFormat ◦ readWavData • それぞれの内部では readChunkが使われている 30 type Wav struct { WavFormat WavData } func readWav(b []byte) (*Wav, error) { riff, err := readRIFF(b) if riff.Format != "WAVE" { return nil, fmt.Errorf("not \"WAVE\", found %s", riff.Format) } // フォーマットチャンク wfmt, err := readWavFormat(riff.Data[4:]) // データチャンク wdata, err := readWavData(riff.Data, 4+8+wfmt.Size) return &Wav{ WavFormat: *wfmt, WavData: *wdata, }, nil }
  21. readWavFormatの実装 ▪ RIFFチャンク • ID: []byte{‘R’, ‘I’, ‘F’, ‘F’} (固定文字列)

    • Size: 301700 • Data: WAVEチャンク ◦ ID: []byte{‘W’, ‘A’, ‘V’, ‘E’} ◦ formatチャンク • ID: []byte{‘f’, ‘m’, ‘t’, ‘ ’} • Size: 16 • Data ◦ dataチャンク • ID: []byte{‘d’, ’a’, ’t’, ’a’} • Size: 301664 • Data 31 func readRIFF(b []byte) (*RIFF, error) { chunk, err := readChunk(b) if chunk.ID != "RIFF" { return nil, fmt.Errorf("not RIFF, found %s", chunk.ID) } return &RIFF{ ID: chunk.ID, Size: chunk.Size, Data: chunk.Data, }, nil }
  22. バイトオーダー ▪ 複数バイトのデータを格納するときに、データを格納する順番 ▪ 例: []byte{0x12, 0x34, 0x56, 0x78} ▪

    ビッグエンディアン • 0x12, 0x34, 0x56, 0x78 と格納する • ざっくりメリット ◦ 人間が読むのと同じ順番 ▪ リトルエンディアン • 0x78, 0x56, 0x34, 0x12 と格納する • ざっくりメリット ◦ indexを使って、値を (78 * 8^0) + (56 * 8^1) + (34 * 8^2) * (12 * 8^3) と計算できる 32