通过 omxplayer 从 Python 进程的内存中播放 MP3,而不写入磁盘

Playing a MP3 from a Python process's memory via omxplayer, without writing to disk

以下代码接收 MP3,将其写入磁盘并使用 OMXPlayer 播放。我想消除在播放之前将 MP3 写入磁盘的需要。

song = response.content
file = open("temp.mp3", "wb")
file.write(song)
file.close()
response.close()
play_song_subprocess = subprocess.call(['omxplayer', '-o', 'local', '--vol', '-500', 'temp.mp3'])

如何消除 file.write()? 我想做这样的事情:

song = response.content
play_song_subprocess = subprocess.call(['omxplayer', '-o', 'local', '--vol', '-500', song])

但这会导致以下错误: 嵌入空字节

读者的背景故事

在聊天和评论中建立:

  • cat temp.mp3 | omxplayer -o local --vol -500 /dev/stdin 导致段错误。
  • omxplayer -o local --vol -500 /dev/fd/3 3< <(cat temp.mp3) 工作正常。

因此,我们可以将 MP3 的数据传入...但不能传入标准输入(omxplayer 用于控制:暂停、提前退出等)。


方法 1:使用 Shell 进行文件描述符整理

这等同于“方法 3”,但不是使用非常新的和现代的 Python 功能来进行 FD 争论 in-process,而是启动 /bin/sh 的副本以完成工作(因此将与更旧的 Python 版本一起工作)。

play_from_stdin_sh = '''
exec 3<&0                                     # copy stdin to FD 3
exec </dev/tty || exec </dev/null             # make stdin now be /dev/tty or /dev/null
exec omxplayer -o local --vol -500 /dev/fd/3  # play data from FD 3
'''
p = subprocess.Popen(['sh', '-c', play_from_stdin_sh], stdin=subprocess.POPEN)
p.communicate(song)  # passes data in "song" as stdin to the copy of sh

因为omxplayer希望使用标准输入从用户那里获取指令,我们需要使用不同的文件描述符来传递其内容。因此,虽然我们有 Python 解释器在 stdin 上传递内容,然后我们有一个 shell 将 stdin 复制到 FD 3 并用句柄或 /dev/tty 或 [=19] 替换原始 stdin =] 在调用 omxplayer.

之前

方法 2:使用命名管道

关于这是否在“不写入磁盘”约束上作弊存在一点疑问。它不会将任何 MP3 数据写入磁盘,但它 确实 创建一个文件系统对象,两个进程都可以打开该对象作为相互连接的一种方式,即使数据写入该对象直接在进程之间流动,而不写入磁盘。

import tempfile, os, os.path, shutil, subprocess

fifo_dir = None
try:
  fifo_dir = tempfile.mkdtemp('mp3-fifodir')
  fifo_name = os.path.join(fifo_dir, 'fifo.mp3')
  os.mkfifo(fifo_name)
  # now, we start omxplayer, and tell it to read from the FIFO
  # as long as it opens it in read mode, it should just hang until something opens
  # ...the write side of the FIFO, writes content to it, and then closes it.
  p = subprocess.Popen(['omxplayer', '-o', 'local', '--vol', '-500', fifo_name])
  # this doesn't actually write content to a file on disk! instead, it's written directly
  # ...to the omxplayer process's handle on the other side of the FIFO.
  fifo_fd = open(fifo_name, 'w')
  fifo_fd.write(song)
  fifo_fd.close()
  p.wait()
finally:
  shutil.rmtree(fifo_dir)

方法 3:在 Python

中使用 preexec_fn

我们可以使用 Popen 对象的 preexec_fn 参数实现方法 1 在本机 Python 中使用 shell 的文件描述符整理。考虑:

import os, subprocess

def move_stdin():
  os.dup2(0, 3)                       # copy our stdin -- FD 0 -- to FD 3
  try:
    newstdin = open('/dev/tty', 'r')  # open /dev/tty...
    os.dup2(newstdin.fileno(), 0)     # ...and attach it to FD 0.
  except IOError:
    newstdin = open('/dev/null', 'r') # Couldn't do that? Open /dev/null...
    os.dup2(newstdin.fileno(), 0)     # ...and attach it to FD 0.

p = subprocess.Popen(['omxplayer', '-o', 'local', '--vol', '-500', '/dev/fd/3'],
                     stdin=subprocess.PIPE, preexec_fn=move_stdin, pass_fds=[0,1,2,3])
p.communicate(song)