cocuh's note

type(あうとぷっと) -> 駄文

pythonからlibvorbisを叩いてoggをデコードする

oggをデコードしたいと思ったときにpyoggがpython3に対応していなかった(というかメンテが止まっていた)ので自力でデコードしたいと思って書いたものです。

Cでデコーダーを書く

いろいろな書き方があるようでサイトによってまちまちなのですが こちらの書き方を参考にすると、vorbis/vorbisfile.hの4つの関数を叩くとデコードできることがわかります。 詳しいコードはリンクを参照してもらえればわかるので省略します。叩く順番と関数は以下です。

  1. ov_open - 開く
  2. ov_info - 情報取得
  3. ov_read - デコード
  4. ov_clear - 後始末

Cythonでデコーダーを書く

先ほどのリンクをそのままCythonに落とした感じです。

  • vorbistest.pyx
cimport vorbistest as h
from libc.stdio cimport (
    FILE,
    fopen,
)

DEF BUFFER_SIZE = 32768

def decode(name):
    cdef int endian = 0 # 0 for little endian, 1 for big endian
    cdef int bitStream
    cdef char array[BUFFER_SIZE] # arrayという名のbuffer
    
    cdef FILE *f = fopen(name, "rb")
    cdef h.vorbis_info *pinfo
    
    cdef OggVorbis_File oggFile
    
    h.ov_open(f, &oggFile, NULL, 0)
    pinfo = h.ov_info(&oggFile, -1)
    
    print("version:%d"%pinfo.version)
    print("channels:%d"%pinfo.channels)
    print("rate:%d"%pinfo.rate)
    
    res = {
        'width':2, # pinfoにないのでwaveのwidthどれが良いのかわからず、
        'rate':pinfo.rate,
        'channels':pinfo.channels,
    }
    
    import io
    buf = io.BytesIO()
    
    cdef long load_length
    load_length = h.ov_read(&oggFile, array, BUFFER_SIZE, endian, 2, 1, &bitStream)
    while load_length>0:
        buf.write(array[:load_length])
        load_length = h.ov_read(&oggFile, array, BUFFER_SIZE, endian, 2, 1, &bitStream)
        pass
    h.ov_clear(&oggFile)
    
    buf.seek(0)
    res['data'] = buf
    return res
from libc.stdio cimport FILE

cdef extern from "vorbis/codec.h":
    # vorbis/codec.hを見ながら型を書く
    # vorbistest.pyxでvorbis_infoの中身を参照しているので書く必要がある
    ctypedef struct vorbis_info:
        int version
        int channels
        long rate
        
        long bitrate_upper
        long bitrate_nominal
        long bitrate_lower
        long bitrate_window
        
        void *codec_setup
        
cdef extern from "vorbis/vorbisfile.h":
    # vorbis/vorbisfile.hを見ながら型を書く
    ctypedef struct OggVorbis_File:
        pass
    int ov_open(FILE *f, OggVorbis_File *vf, char *initial, long ibytes)
    long ov_read(OggVorbis_File *fv, char *buffer, int length,
                      int bigendianp, int word, int sgned, int *bitstream)
    vorbis_info *ov_info(OggVorbis_File *vf, int link)
    int ov_clear(OggVorbis_File *vf)
  • setup.py
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext


setup(
    cmdclass={'build_ext': build_ext},
    ext_modules=[Extension('vorbistest', ['vorbistest.pyx'],
                           libraries=['vorbisfile'],
    )]
)
  • test.py
# pythonから叩くのでテスト用pythonコード
# 音を鳴らすためにpyaudioを叩いてます
import vorbistest
import pyaudio
import wave

file_path = b"/tmp/hoge.ogg"

res = vorbistest.decode(file_path) #ここでデコード

print(res)

channels = res['channels']
rate = res['rate']
width = res['width']
data = res['data']
p = pyaudio.PyAudio()
CHUNK = 1024
stream = p.open(format=p.get_format_from_width(width),
                channels=channels,
                rate=rate,
                output=True)
    
buffer = data.read(CHUNK) # python3 ではbytes型
while buffer != b'':
    stream.write(buffer) # audio側にwrite(ここで鳴らしている
    buffer = data.read(CHUNK) # バッファにread

# 後始末
stream.stop_stream()
stream.close()
p.terminate()

試す

cythonとlibvorbis(とpyaudio)が必要ですので別途installしてください。 お好きなoggファイルを/tmp/hoge.oggに置いて

$ python setup.py build_ext -i
running build_ext
cythoning vorbistest.pyx to vorbistest.c
building 'vorbistest' extension
gcc -pthread -Wno-unused-result -DDYNAMIC_ANNOTATIONS_ENABLED=1 -DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -march=x86-64 -mtune=generic -O2 -pipe -fstack-protector --param=ssp-buffer-size=4 -fPIC -I/usr/include/python3.3m -c vorbistest.c -o build/temp.linux-x86_64-3.3/vorbistest.o
gcc (略)
$ python test.py
version:0
channels:2
rate:44100
{'width': 2, 'channels': 2, 'rate': 44100, 'data': <_io.BytesIO object at 0x7f6079546258>}
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm_dmix.c:1022:(snd_pcm_dmix_open) unable to open slave
connect(2) call to /dev/shm/jack-1000/default/jack_0 failed (err=No such file or directory)
attempt to connect to server failed
(再生される)

よっしゃ٩( 'ω' )و

ということでPythonからcython経由でlibvorbisを叩いてoggのデコードができました。 私の環境はalsa+pulseaudioで動かしていますが、alsaのみの環境でも動くことを確認しました。 Linux上で叩いているのでMacOSWindowsでは確認していませんが、ソースコードコンパイルpyaudioを入れることができていれば動くと思います。

こちらの記事はqiitaと同時投稿されたものです

http://qiita.com/cocu/items/94a98cb312af7694ffd6