Wizard Notes

Python, JavaScript を使った音楽信号分析の技術録、作曲活動に関する雑記

Python:audioread で mp3, aac, m4a 形式のオーディオファイルをNumPy配列として読み込む

以下の記事にありますように、Python ではオーディオファイルの読み込むライブラリが複数あります

しかし,.mp3, .aac, .m4a といった圧縮コーデックを読み込むものは多くありません

www.wizard-notes.com

今回紹介する audioread では ffmpeg のようなバックエンドを呼び出すことで.mp3, .aac, .m4a といった圧縮コーデックもPython上で読み込むことができます*1

合わせて、.mp3, .aac, .m4a などのオーディオファイルをNumPy配列として読み込むサンプルコードもご紹介します

pypi.org

audioread の使い方

インストール

pip install audioread

ライセンス

MIT License

バックエンド

audioread では以下のようなソフトウェアを使うことで様々な拡張子のオーディオファイルを読み込むことができます。

利用方法

基本的な利用方法は以下のようになっています。

信号をブロックごとに読み込み、bufに格納しています。

ここで,bufはバイト型であることに注意してください。

with audioread.audio_open(filepath) as f:
    print(f.channels, f.samplerate, f.duration)
    for buf in f:
        do_something(buf)

流石にバイト型を直接使うわけにはいかないので、NumPy配列などに変換する処理が必要になります

サンプルコード(NumPy配列として読み込み)

librosa.load の実装を参考にサンプルコードを作成しました。

処理の流れと解説:

  1. ```audioread.audio_open(filepath) を実行
    • デコードに使うバックエンドのヘルパーを取得します
  2. 1D-NumPy配列として読み込み
    1. バイト型のバッファをブロックごとに読み込む
    2. np.frombufferでバイト型を16-bit PCM として Numpy配列に変換する
    3. scale を算出し、信号の振幅値の範囲を変換
      • [-32768, 32767] から [-1.0, 1.0)
  3. 利用するブロック(フレーム)以外を捨てる
  4. 1D-NumPy配列をチャネル数で分割
    • 最終的に(チャネル数, 信号長)の信号とする
    • y.reshape((-1, n_channels))-1は、n_channelsで分割する場合に0次元目の要素数がいくつになるか自動で算出する
# -*- coding: utf-8 -*-
import numpy as np
import audioread


def load_audioread(filepath, offset=0, duration=None, n_bytes=2):
    # (1) 
    with audioread.audio_open(filepath) as f:
        sr         = f.samplerate
        n_channels = f.channels

        s_start = int(np.round(sr * offset)) * n_channels
        if duration is None:
            s_end = np.inf
        else:
            s_end = s_start + (int(np.round(sr * duration)) * n_channels)

        # (2) Convert bytebuffer to numpy-1d array and rescale [-32768, 32767] to [-1.0, 1.0)
        scale = 1.0 / float(1 << ((8 * n_bytes) - 1))
        frame_list = [scale * np.frombuffer(frame, f"<i{n_bytes}").astype(np.float32) for frame in f]

        # (3) Select use frames
        y = [None for _ in range(len(frame_list))]
        n = 0
        start_idx, end_idx = 0, -1
        for k, frame in enumerate(frame_list):
            n_prev = n
            n = n + len(frame)
    
            if n < s_start:
                start_idx += 1
                continue

            if s_end < n_prev:
                end_idx = k - start_idx
                break
                
            if s_end < n:
                frame = frame[: s_end - n_prev]
            if n_prev <= s_start <= n:
                frame = frame[(s_start - n_prev) :]
                
            y[k] = frame
        
        if end_idx != -1:
            y = y[start_idx:end_idx]
        else:
            y = y[start_idx::]
            
        y = np.concatenate(y)
        
        # (4) channel-split 
        if n_channels > 1:
            y = y.reshape((-1, n_channels)).T

    return y, sr

まとめ

Pythonで .mp3, .aac, .m4a の読み込みができる ライブラリ audioread を紹介しました。

他のライブラリでは対象とされてないことが多い、.mp3, .aac, .m4a 形式のオーディオファイルを扱えるところがメリットです。

ただし、他のオーディオファイルの読み込みを行うライブラリと比べると、バイト型からの変換が必要となります。

バイト型からの変換は、サンプルコードのように自前で用意しても良いのですが、NumPy配列として読み込みたい場合は librosa.load を使う方が手っ取り早かったりします。((librosa.load は条件によってはaudioreadを利用するため))

*1:16ビットの信号に限定