pythonからlibvorbisを叩いてoggをデコードする
oggをデコードしたいと思ったときにpyoggがpython3に対応していなかった(というかメンテが止まっていた)ので自力でデコードしたいと思って書いたものです。
Cでデコーダーを書く
いろいろな書き方があるようでサイトによってまちまちなのですが
こちらの書き方を参考にすると、vorbis/vorbisfile.h
の4つの関数を叩くとデコードできることがわかります。
詳しいコードはリンクを参照してもらえればわかるので省略します。叩く順番と関数は以下です。
ov_open
- 開くov_info
- 情報取得ov_read
- デコード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
- vorbis.pxd
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上で叩いているのでMacOSやWindowsでは確認していませんが、ソースコードのコンパイルとpyaudioを入れることができていれば動くと思います。