python拉流 rtmp 视频文件 音频播放

项目需求是实现python程序的拉流,最先想到的是使用ffmpeg,在python中它的库名字叫做ffmpeg-python,调用方式类似于命令行传参,通过管道的形式读写音视频原数据,但音频进程只能处理音频,视频进程只能处理视频,因为是原始数据,不包含时间戳等时间同步所依赖的可靠信息,尝试过后被弃用。
本文使用的是PyAv库,Python的PyAV库提供了强大的功能,用于音频和视频文件的解码、编码、处理和播放。尽管这个库的详细文档和资料相对较少,但通过推测和多次尝试,我最终成功地实现了流媒体的拉取。以下是我在使用PyAV过程中的一些经验分享。

由于是拉流,理论上来说不需要做额外的音视频同步,只需要根据媒体类型按需处理即可。

贴代码:

import av
import cv2
import numpy as np
import pyaudio
import wave

rtmp_url = 'rtmp://192.168.0.134:1935/'

try:
    # 打开输入流
    container = av.open(rtmp_url)

    # 获取音频和视频流
    video_stream = next((s for s in container.streams if s.type == 'video'), None)
    audio_stream = next((s for s in container.streams if s.type == 'audio'), None)

    if video_stream is None or audio_stream is None:
        # 如果第一次获取失败,尝试再次获取
        container.close()  # 关闭之前打开的容器

        # 再次打开输入流
        container = av.open(rtmp_url)

        # 第二次尝试获取音频和视频流
        video_stream = next((s for s in container.streams if s.type == 'video'), None)
        audio_stream = next((s for s in container.streams if s.type == 'audio'), None)

        if video_stream is None or audio_stream is None:
            raise ValueError("No video or audio stream found in the container after retrying.")

    # 初始化视频流处理
    cv2.namedWindow('Video Frame', cv2.WINDOW_NORMAL)

    # 初始化音频流处理
    p = pyaudio.PyAudio()
    sample_rate = audio_stream.codec_context.sample_rate
    channels = audio_stream.codec_context.channels
    format = pyaudio.paFloat32
    try:
        stream = p.open(format=format,
                        channels=1,
                        rate=sample_rate,
                        output=True,
                        output_device_index=3)
    except OSError as e:
        raise IOError(f"无法打开设备 {27}: {str(e)}")


    # 初始化Wave文件
    # wave_file = wave.open('output_audio.wav', 'wb')
    # wave_file.setnchannels(channels)
    # wave_file.setsampwidth(p.get_sample_size(format))
    # wave_file.setframerate(sample_rate)

    # 处理音视频流
    for packet in container.demux(video_stream, audio_stream):
        if packet.stream.type == 'video':
            for frame in packet.decode():
                img = frame.to_ndarray(format='bgr24')
                cv2.imshow('Video Frame', img)

                # 按下 'q' 键退出
                if cv2.waitKey(1) & 0xFF == ord('q'):
                    break

        elif packet.stream.type == 'audio':
            for frame in packet.decode():
                audio_data = frame.to_ndarray()
                # 如果音频数据为双声道
                if channels == 2:
                    # 将双声道数据转换为单声道数据
                    audio_data_left = audio_data[::2]  # 左声道数据
                    audio_data_right = audio_data[1::2]  # 右声道数据
                    audio_data_mono = (audio_data_left + audio_data_right) / 2  # 混合成单声道数据

                    # 将单声道数据转换回字节数据并写入流
                    audio_data_mono_bytes = audio_data_mono.astype(np.float32).tobytes()
                    stream.write(audio_data_mono_bytes)
                else:
                    # 单声道数据直接写入流
                    stream.write(audio_data.tobytes())
                # wave_file.writeframes(audio_data)

finally:
    # 关闭所有打开的资源
    cv2.destroyAllWindows()
    if 'stream' in locals() and stream.is_active():
        stream.stop_stream()
        stream.close()
    if 'p' in locals():
        p.terminate()
    if 'container' in locals():
        container.close()
    # if 'wave_file' in locals():
    #     wave_file.close()

基本流程在注释中已经很详细了,这里对本次实践中的一些心得体会做一下总结:

1.不论执行过程中是否抛出异常,在结尾的资源释放必须要有,实测下来如果没有及时释放,在下一次执行时候可能会出现拉不到流的情况

2.为避免拉不到流,我在脚本中添加了重试功能:一方面做了相应资源的关闭,一方面再次尝试拉流

3.拉流的前提是流地址可用,可以使用VLC、VMIX或者OBS进行测试,测试教程网上有很多,不再赘述

4.关于音频:使用的是pyaudio库,其中p.open中有一个output_device_index参数,制定了播出设备的下标,如果不需要指定也可以将传参删除,会自动选择一个当前机器的可用播出设备

5.实测下来,音频int16的单声道、立体声播放都没有问题,float32的双声道会出现杂音、音调升高等问题,单声道则不会,所以我在我的脚本中进行了判断,将立体声混合为了单声道,由于负责工作内容较多,没有时间研究这个问题,有了解这个情况出现的原因的伙伴,欢迎在评论区讨论

6.可以下载MonaServer作为rtmp服务器,安装简单,使用便捷,用来测试非常友好,使用obs进行推流即可

7.关于视频:使用opencv进行显示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值