如何接收“icecast"使用 Python 立即播放的互联网广播流? [英] How to receive "icecast" internet radio stream for immediate playback with Python?

查看:32
本文介绍了如何接收“icecast"使用 Python 立即播放的互联网广播流?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想使用互联网音频/广播流(特别是 Longplayer,单击直接流 URL)并使用 python 播放.

I'm wanting to take an internet audio/radio stream (specifically Longplayer, click for direct stream URL) and play it with python.

最好是后台运行的,这样脚本才能继续运行它的主循环.(例如作为游戏背景音乐之类的,虽然 Pyglet、PyGame 等人可能会为此提供他们自己的工具.)

It's preferable that it's backgrounded, such that the script able to continue running its main loop. (e.g. as game background music or something, though Pyglet, PyGame et al. may provide their own tools for that.)

我见过一些可能过时的例子,使用requests录制网络广播并将其转储到文件中,但这并不是我想要的,而且答案的评论似乎有关于 requests 存在问题的争论?(请参阅此处)

I've seen some likely out of date examples of recording internet radio using requests and dumping it into a file but this isn't exactly what I want and the answers' comments seemed to have arguments about requests being problematic among other things? (see here)

我愿意使用任何可以pip 的包,只要它适用于 Python 3.X.(目前使用 3.6 纯粹是因为我还没有集中精力安装 3.7)

I'm open to using any packages you can pip so long as it works with Python 3.X. (Currently using 3.6 purely because I haven't gathered the effort to install 3.7 yet)

重申一下,我不想保存流,只需立即播放(或在需要时进行缓冲?)返回给用户.这最好不要阻塞脚本,我想这需要多线程/多处理,但这只是获得播放的次要.)

To reiterate, I don't want to save the stream, just play it immediately (or with buffering if that's needed?) back to the user. This is preferably without blocking the script, which I imagine would need multithreadng/multiprocessing but this is secondary to just getting playback.)

推荐答案

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

As it always seems to be the case with these kinds of apparently simple questions, the devil is in the details. I ended up writing some code that should solve this question. The pip dependencies can be installed using python3 -m pip install ffmpeg-python PyOpenAL. The workflow of the code can be divided into two steps:

  1. 代码必须从在线流下载二进制 mp3 文件数据块,并将它们转换为原始 PCM 数据(基本有符号的 uint16_t 幅度值)以进行播放.这是使用 ffmpeg-python 库完成的,它是 FFmpeg.此包装器在单独的进程中运行 FFmpeg,因此此处不会发生阻塞.
  2. 然后代码必须将这些块排队以进行播放.这是使用 PyOpenAL 完成的,它是 OpenAL.创建设备和上下文以启用音频播放后,将创建 3d 定位源.该源连续与缓冲区(模拟环形缓冲区")排队,这些缓冲区填充有从 FFmpeg 管道输入的数据.这与第一步在单独的线程上运行,使得下载新音频块独立于音频块播放运行.
  1. The code must download binary chunks of mp3 file data from an online stream and convert them to raw PCM data (basically signed uint16_t amplitude values) for playback. This is done using the ffmpeg-python library, which is a wrapper for FFmpeg. This wrapper runs FFmpeg in a separate process, so no blocking occurs here.
  2. The code must then queue these chunks for playback. This is done using PyOpenAL, which is a wrapper for OpenAL. After creating a device and context to enable audio playback, a 3d-positioned source is created. This source is continuously queued with buffers (simulating a "ring buffer") that are filled with data piped in from FFmpeg. This runs on a separate thread from the first step, making downloading new audio chunks run independently from audio chunk playback.

这是代码的样子(有一些注释).如果您对代码或此答案的任何其他部分有任何疑问,请告诉我.

Here is what that code looks like (with some commenting). Please let me know if you have any questions about the code or any other part of this answer.

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)

这篇关于如何接收“icecast"使用 Python 立即播放的互联网广播流?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆