如何使用Python接收“icecast”互联网广播流以便即时播放?

2024-09-30 22:21:55 发布

您现在位置:Python中文网/ 问答频道 /正文

我想获取一个互联网音频/广播流(特别是Longplayer,单击直接流URL)并用python播放它。你知道吗

它最好是后台的,这样脚本就可以继续运行它的主循环。(例如,作为游戏背景音乐或其他东西,尽管Pyglet、PyGame等人可能会为此提供自己的工具。)

我见过一些过时的例子,比如用requests录制互联网广播并将其转储到一个文件中,但这并不完全是我想要的,答案的评论似乎有关于requests存在问题的争论?(见here

我愿意使用任何你能pip的软件包,只要它能与python3.X一起工作就行(目前使用3.6纯粹是因为我还没有集中精力安装3.7)

重申一下,我不想保存流,只要立即播放就行了(如果需要的话,可以使用缓冲?)返回给用户。这最好是在不阻塞脚本的情况下进行的,我认为这需要多线程/多处理,但这是仅次于播放的。)


Tags: 文件工具答案脚本游戏url互联网音频
1条回答
网友
1楼 · 发布于 2024-09-30 22:21:55

这类看似简单的问题似乎总是如此,关键在于细节。最后我写了一些代码来解决这个问题。可以使用python3 -m pip install ffmpeg-python PyOpenAL安装pip依赖项。代码的工作流程可分为两个步骤:

  1. 代码必须从在线流下载mp3文件数据的二进制块,并将它们转换为原始PCM数据(基本上是有符号的uint16振幅值)进行播放。这是使用the ffmpeg-python library完成的,它是FFmpeg的包装器。这个包装器在一个单独的进程中运行FFmpeg,所以这里不会发生阻塞。你知道吗
  2. 然后,代码必须将这些块排队等待播放。这是使用PyOpenAL完成的,它是OpenAL的包装器。创建设备和上下文以启用音频播放后,将创建一个三维定位源。该源连续地与缓冲区(模拟“环形缓冲区”)排队,缓冲区中填充了从FFmpeg传入的数据。这从第一步开始就在一个单独的线程上运行,使得下载新的音频块独立于音频块播放运行。你知道吗

下面是代码的样子(带有一些注释)。请让我知道,如果你有任何问题的代码或任何其他部分的答案。你知道吗

import ctypes
import ffmpeg
import numpy as np
from openal.al import *
from openal.alc import *
from queue import Queue, Empty
from threading import Thread
import time
from urllib.request import urlopen

def init_audio():
    #Create an OpenAL device and context.
    device_name = alcGetString(None, ALC_DEFAULT_DEVICE_SPECIFIER)
    device = alcOpenDevice(device_name)
    context = alcCreateContext(device, None)
    alcMakeContextCurrent(context)
    return (device, context)

def create_audio_source():
    #Create an OpenAL source.
    source = ctypes.c_uint()
    alGenSources(1, ctypes.pointer(source))
    return source

def create_audio_buffers(num_buffers):
    #Create a ctypes array of OpenAL buffers.
    buffers = (ctypes.c_uint * num_buffers)()
    buffers_ptr = ctypes.cast(
        ctypes.pointer(buffers), 
        ctypes.POINTER(ctypes.c_uint),
    )
    alGenBuffers(num_buffers, buffers_ptr)
    return buffers_ptr

def fill_audio_buffer(buffer_id, chunk):
    #Fill an OpenAL buffer with a chunk of PCM data.
    alBufferData(buffer_id, AL_FORMAT_STEREO16, chunk, len(chunk), 44100)

def get_audio_chunk(process, chunk_size):
    #Fetch a chunk of PCM data from the FFMPEG process.
    return process.stdout.read(chunk_size)

def play_audio(process):
    #Queues up PCM chunks for playing through OpenAL
    num_buffers = 4
    chunk_size = 8192
    device, context = init_audio()
    source = create_audio_source()
    buffers = create_audio_buffers(num_buffers)

    #Initialize the OpenAL buffers with some chunks
    for i in range(num_buffers):
        buffer_id = ctypes.c_uint(buffers[i])
        chunk = get_audio_chunk(process, chunk_size)
        fill_audio_buffer(buffer_id, chunk)

    #Queue the OpenAL buffers into the OpenAL source and start playing sound!
    alSourceQueueBuffers(source, num_buffers, buffers)
    alSourcePlay(source)
    num_used_buffers = ctypes.pointer(ctypes.c_int())

    while True:
        #Check if any buffers are used up/processed and refill them with data.
        alGetSourcei(source, AL_BUFFERS_PROCESSED, num_used_buffers)
        if num_used_buffers.contents.value != 0:
            used_buffer_id = ctypes.c_uint()
            used_buffer_ptr = ctypes.pointer(used_buffer_id)
            alSourceUnqueueBuffers(source, 1, used_buffer_ptr)
            chunk = get_audio_chunk(process, chunk_size)
            fill_audio_buffer(used_buffer_id, chunk)
            alSourceQueueBuffers(source, 1, used_buffer_ptr)

if __name__ == "__main__":    
    url = "http://icecast.spc.org:8000/longplayer"

    #Run FFMPEG in a separate process using subprocess, so it is non-blocking
    process = (
        ffmpeg
        .input(url)
        .output("pipe:", format='s16le', acodec='pcm_s16le', ac=2, ar=44100, loglevel="quiet")
        .run_async(pipe_stdout=True)
    )

    #Run audio playing OpenAL code in a separate thread
    thread = Thread(target=play_audio, args=(process,), daemon=True)
    thread.start()

    #Some example code to show that this is not being blocked by the audio.
    start = time.time()
    while True:
        print(time.time() - start)

相关问题 更多 >