项目需求是实现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进行显示