Python: mp3 通过 ffmpeg 管道和 wave.open(f,'r') 到 alsaaudio

Python: mp3 to alsaaudio through ffmpeg pipe and wave.open(f,'r')

我正在尝试使用 ffmpeg 将 mp3 解码为 wav:

import alsaaudio
import wave
from subprocess import Popen, PIPE

with open('filename.mp3', 'rb') as infile:
    p=Popen(['ffmpeg', '-i', '-', '-f', 'wav', '-'], stdin=infile, stdout=PIPE)
    ...

接下来我想将数据从 p.stdout.read() 重定向到 wave.open(file, r) 以使用 readframes(n) 和其他方法。但我不能,因为 wave.open(file,'r') 中的 'file' 只能是文件名或打开的文件指针。

    ...
    file = wave.open(p.stdout.read(),'r')
    card='default'
    device=alsaaudio.PCM(card=card)
    device.setchannels(file.getnchannels())
    device.setrate(file.getframerate())
    device.setformat(alsaaudio.PCM_FORMAT_S16_LE)
    device.setsetperiodsize(320)
    data = file.readframes(320)
    while data:
        device.write(data)
        data = file.readframes(320)

我得到了:

TypeError: file() argument 1 must be encoded string without NULL bytes, not str

那么wave.open()可以处理来自p.stdout.read()的数据吗? 制作临时 .wav 文件不是解决方案。

对不起我的英语。 谢谢。

更新

感谢 PM 2Ring 的热播 io.BytesIO。

但是生成的代码不起作用。

import alsaaudio
import wave
from subprocess import Popen, PIPE

with open('sometrack.mp3', 'rb') as infile:
        p=Popen(['ffmpeg', '-i', '-', '-f','wav', '-'], stdin=infile , stdout=PIPE , stderr=PIPE)
        fobj = io.BytesIO(p.stdout.read())
fwave = wave.open(fobj, 'rb')

跟踪:

File "./script.py", line x, in <module>
  fwave = wave.open(fobj, 'rb')
File "/usr/lib/python2.7/wave.py", line x, in open
  return Wave_read(f)
File "/usr/lib/python2.7/wave.py", line x, in __init__
  self.initfp(f)
File "/usr/lib/python2.7/wave.py", line x, in initfp
  raise Error, 'not a WAVE file'
wave.Error: not a WAVE file

来自 /usr/lib/python2.7/wave.py:

...
self._file = Chunk(file, bigendian = 0)
if self._file.getname() != 'RIFF':
    raise Error, 'file does not start with RIFF id'
if self._file.read(4) != 'WAVE':
    raise Error, 'not a WAVE file'
...

由于 'bad' self._file 个对象,检查失败。

在 /usr/lib/python2.7/chunk.py 我找到了问题的根源:

...
try:
    self.chunksize = struct.unpack(strflag+'L', file.read(4))[0]
except struct.error:
    raise EOFError
...

因为struct.unpack(strflag+'L', file.read(4))[0] returns 0。 但是这个功能工作正常。

如指定的那样here:

"5-8 字节 - 文件大小(整数) 整个文件的大小 - 8 字节,以字节为单位(32 位整数)。 通常,您会在创建后填写此内容。" 这就是为什么我的脚本不起作用。 wave.open 和其他函数无法处理我的文件对象,因为 self.chunksize = 0。看起来 ffmpeg 在使用 PIPE 时无法插入文件大小。

解决方案

很简单。 我更改了 Chunk class:

的初始化函数

之后:

...
try:
    self.chunksize = struct.unpack(strflag+'L', file.read(4))[0]
except struct.error:
    raise EOFError
...

之前:

...
try:
    self.chunksize = struct.unpack(strflag+'L', file.read(4))[0]
    currtell = file.tell()
    if self.chunksize == 0:
        file.seek(0)
        file.read(currtell)
        self.chunksize = len(file.read())-4
        file.seek(0)
        file.read(currtell)
except struct.error:
    raise EOFError
...

当然,对原始模块的编辑是错误的。所以我为 2 classes Chunk 和 Wave_read.

创建了自定义分叉

您可以找到完整的工作代码但不稳定 here

对不起我糟糕的英语。

谢谢。

如您所述,wave.open(file,'r')file arg 只能是文件名或打开的文件指针。但是 p.stdout.read() 是文件 p.stdout 内容 作为字符串。但是,您不能只将 p.stdout 文件传递​​给 wave.open(),因为 wave.open() 需要一个 seekable 类似文件的对象,而且您通常可以' t seek() 在管道上。

但是您可以 做的是将您从p.stdout.read() 读取的字节包装在io.BytesIO 对象中。生成的对象将像文件一样运行,并且可以搜索。

FWIW,使用 file 作为变量名不是一个好主意,因为它是 Python 2 中内置类型的名称;我想用它没问题Python 3,但我还是会避免它。

警告:未经测试Python 2.6+ 代码

fbytes = io.BytesIO(p.stdout.read())
fwave = wave.open(fbytes, 'r')

如果您使用 -f s16le 而不是 -f wav 将您的音频文件转换为原始音频而不是 wav,您可能根本不需要 wave.open,您可以直接流式传输到 ALSA。我只在 PyAudio 上试过了,它成功了。