十年前,大牛直播SDK发布了跨平台的RTMP、RTSP毫秒级低延迟播放器,随着AI的爆发式普及和发展,加之大多视觉算法分析,都是用在Python下,Python下对视频流延迟的要求越来越高,本文将深入解析基于Python实现的RTSP/RTMP播放器,探讨其代码结构、实现原理以及优化策略,先看使用场景:
Python下的RTMP、RTSP播放器延迟可以做到多低?以大牛直播SDK的Windows平台RTMP推送模块采集毫秒计数器窗口,然后推RTMP流到NGINX服务器,Python播放器拉流播放,整体延迟如下:
听说图片信服力不够?那就看视频:
实际上,几年前就有一些开发者自己基于我们的Windows播放器SDK提供的接口,实现了Python下的低延迟播放,这个时候我们官方发布Python的demo,实际上一点儿也不意外,或者说本身难度也不大,因为底层都还是调了C接口,只是根据Python的调用规范做了下接口的转换,下面,我们从代码结构和功能这块,针对Python下的播放器实现,做个大概的说明:
整个播放器基于VideoPlayer
类构建,它封装了播放器的核心逻辑和功能。其中,__init__
方法初始化了播放器的各种属性和组件,包括用户界面(UI)元素、回调函数、事件队列等。
toggle_play
方法实现播放与停止的切换。在播放过程中,调用init_common_sdk_param
方法初始化SDK参数,设置缓冲区大小、渲染模式等,并通过SetRenderWindow
将视频渲染窗口与GUI界面的画布关联起来。在停止播放时,调用StopPlay
方法,并清除帧队列以确保线程安全退出。# SmartPlayerPythonDemo.py
# Created by daniusdk.com
# WeChat: xinsheng120
def start_playback(self):
if not self.player_handle or not self.player_handle.value:
self.update_status("play handle is None")
return
print(f"start_playback")
self.init_common_sdk_param()
hwnd = ctypes.c_void_p(self.canvas.winfo_id())
print(f"Canvas hwnd: 0x{hwnd.value:x}")
if self.smart_player_sdk_api.SetRenderWindow(self.player_handle, hwnd) != NTBaseCodeDefine.NT_ERC_OK:
self.update_status("设置渲染窗口失败")
return
# 设置硬解码
if self.hardware_decode.get():
self.smart_player_sdk_api.SetH264HardwareDecoder(self.player_handle, 1 if self.is_support_h264_hardware_decoder else 0, 0)
self.smart_player_sdk_api.SetH265HardwareDecoder(self.player_handle, 1 if self.is_support_h265_hardware_decoder else 0, 0)
self.smart_player_sdk_api.SetAudioVolume(self.player_handle, int(self.volume_scale.get()))
if self.smart_player_sdk_api.StartPlay(self.player_handle) != NTBaseCodeDefine.NT_ERC_OK:
self.update_status("开始播放失败")
return
if self.is_enable_frame_callback:
# 启动帧处理线程
self.stop_event.clear()
self.frame_thread = threading.Thread(target=self.process_frames, daemon=True)
self.frame_thread.start()
self.is_playing = True
self.play_btn.config(text="停止")
self.update_status("正在播放...")
def stop_playback(self):
if not self.player_handle or not self.player_handle.value:
return
self.smart_player_sdk_api.StopPlay(self.player_handle)
self.is_playing = False
self.play_btn.config(text="播放")
self.update_status("已停止")
SetH264HardwareDecoder
和SetH265HardwareDecoder
方法启用硬件解码,以提高播放性能。
# 设置硬解码
if self.hardware_decode.get():
self.smart_player_sdk_api.SetH264HardwareDecoder(self.player_handle, 1 if self.is_support_h264_hardware_decoder else 0, 0)
self.smart_player_sdk_api.SetH265HardwareDecoder(self.player_handle, 1 if self.is_support_h265_hardware_decoder else 0, 0)
start_recording
和stop_recording
方法实现录像功能。在录像前,需要配置录像参数,如文件名规则、保存目录等。
def toggle_record(self):
if not self.is_recording:
self.start_recording()
else:
self.stop_recording()
def start_recording(self):
if not self.player_handle or not self.player_handle.value:
self.update_status("play handle is None")
return
# 确保记录配置已初始化
if self.record_config is None:
messagebox.showinfo("提示", "请先配置录像参数")
self.show_record_config() # 打开配置对话框
return
print(f"start_recording")
self.init_common_sdk_param()
# 设置录像参数
ruler = NT_SP_RecorderFileNameRuler()
ruler.type_ = 0
if self.record_config is not None:
ruler.file_name_prefix_ = self.record_config["file_prefix"].encode()
ruler.append_date_ = 1 if self.record_config["is_append_date"] else 0
ruler.append_time_ = 1 if self.record_config["is_append_time"] else 0
self.smart_player_sdk_api.SetRecorderDirectoryW(
self.player_handle,
ctypes.c_wchar_p(self.record_config["dir_path"])
)
self.smart_player_sdk_api.SetRecorderFileNameRuler(self.player_handle, byref(ruler))
self.smart_player_sdk_api.SetRecorderVideo(self.player_handle,
c_int(1 if self.record_config["is_record_video"] else 0))
self.smart_player_sdk_api.SetRecorderAudio(self.player_handle,
c_int(1 if self.record_config["is_record_audio"] else 0))
if self.smart_player_sdk_api.StartRecorder(self.player_handle) == NTBaseCodeDefine.NT_ERC_OK:
self.is_recording = True
self.record_btn.config(text="停止录像")
self.update_status("录像中...")
else:
self.update_status("启动录像失败")
def stop_recording(self):
if not self.player_handle or not self.player_handle.value:
return
print(f"stop_recording Player handle: 0x{self.player_handle.value:x}")
self.smart_player_sdk_api.StopRecorder(self.player_handle)
self.is_recording = False
self.record_btn.config(text="录像")
self.update_status("录像已停止")
capture_image
方法调用SDK的CaptureImage
接口实现截图,并通过回调函数处理截图结果。
def capture_image(self):
if not self.player_handle or not self.player_handle.value:
return
filename = f"capture_{datetime.now().strftime('%Y%m%d%H%M%S')}.png"
# 定义回调函数
def capture_callback(handle, user_data, result, file_name):
status = "成功" if result == NTBaseCodeDefine.NT_ERC_OK else "失败"
self.capture_queue.put((result, file_name.decode('utf-8') if file_name else filename))
# 创建回调对象并保持引用
self.capture_image_cb = CAPTURE_IMAGE_CALLBACK(capture_callback)
# 调用SDK接口
ret = self.smart_player_sdk_api.CaptureImage(
self.player_handle,
filename.encode('utf-8'),
None,
self.capture_image_cb)
if ret != NTBaseCodeDefine.NT_ERC_OK:
self.update_status("截图请求发送失败")
大牛直播SDK的Python下的RTMP、RTSP播放器通过调用智能播放器SDK的StartPlay
方法开始播放。在播放过程中,SDK会通过回调函数向帧队列中推送视频帧数据。帧处理线程从队列中获取帧数据,并将其转换为图像格式供UI界面显示。
SetEventCallBack
设置事件回调函数,事件信息将被传递到主线程进行处理。
SetRecorderCallBack
设置录像回调函数,录像信息将被传递到主线程进行处理。
UI界面基于Tkinter构建,包括视频画布、控制按钮、输入框等组件。通过pack
方法和grid
方法对组件进行布局和定位,实现了一个简洁直观的用户界面。
scale
控件实现音量调节功能,用户可以实时调整播放音量。
低延迟播放器通过优化数据处理和传输过程,实现了更低的延迟。这对于需要实时交互的应用场景非常重要,如监控、互动式直播、视频会议等。观众可以几乎实时地观看直播内容、参与互动,大大提升了实时性和互动性体验。
RTSP和RTMP协议支持多种媒体格式和编码方式,具有很强的兼容性。无论是常见的H.264、H.265视频编码格式,还是AAC、PCMA、PCMU等音频编码格式,低延迟播放器都能很好地支持,能够适应不同设备和系统的需求。
低延迟播放器能够适应不同的网络环境,包括TCP和UDP传输方式。TCP保证了传输的可靠性,适用于对数据准确性要求较高的场景;UDP则具有较低的延迟和较高的传输效率,适用于对实时性要求较高的场景。此外,播放器还支持自适应调整播放策略,如在网络带宽不足时自动降低视频的分辨率或帧率,以保证视频的流畅播放。
现代播放器通常利用硬件加速来提高播放性能。这可以通过使用专用的硬件解码器和图形加速器来实现,以加快解码和渲染过程,从而降低延迟。例如,在解码过程中,采用高效的解码算法,充分利用硬件加速功能,如GPU加速,以快速处理大量的音视频数据。
低延迟播放器通过有效的缓存管理来减少延迟。它会使用较小的缓冲区,并采用动态缓冲策略,使缓存保持最小化,从而减少播放器响应时间。同时,支持设置缓冲时间,以应对网络抖动等不稳定情况,确保播放的流畅性。
低延迟播放器致力于实现快速的起播时间。它可以使用预加载技术,提前缓存部分音视频数据,并在用户点击播放时立即开始播放,从而缩短起播延迟。
低延迟播放器支持多实例播放,适用于需要同时监控多个视频源的场景。这不仅满足了不同用户的需求,还保证了低延迟性能。
Python下的RTMP、RTSP播放器,实际上还是调用的C接口的,所以Windows平台播放器支持的功能,Python下依然支持,完善的功能,覆盖了95%以上的使用场景。
大牛直播SDK做Python下的低延迟RTMP、RTSP播放器,除了常规播放外,更多的是为了方便做Python下的视觉算法对接处理。
一、回调函数的实现
定义回调函数 在播放器的代码中,定义一个回调函数来接收YUV或RGB数据。例如:
def video_frame_callback(self, handle, user_data, status, frame):
if not frame:
return
frame_data = frame.contents
if frame_data.format_ == NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FORMAT_YUV420P.value:
# 处理YUV420P数据
yuv_data = bytes(ctypes.cast(frame_data.plane0_, ctypes.POINTER(ctypes.c_ubyte * frame_data.size_)).contents)
self.process_yuv_frame(yuv_data, frame_data.width_, frame_data.height_)
elif frame_data.format_ == NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FORMAT_RGB24.value:
# 处理RGB24数据
rgb_data = bytes(ctypes.cast(frame_data.plane0_, ctypes.POINTER(ctypes.c_ubyte * frame_data.size_)).contents)
self.process_rgb_frame(rgb_data, frame_data.width_, frame_data.height_)
注册回调函数 在播放器初始化时,将回调函数注册到播放器SDK中:
self.frame_cb = VIDEO_FRAME_CALLBACK(self.video_frame_callback)
self.smart_player_sdk_api.SetVideoFrameCallBack(self.player_handle,
NT_SP_E_VIDEO_FRAME_FORMAT.NT_SP_E_VIDEO_FRAME_FORMAT_YUV420P.value,
None, self.frame_cb)
二、视觉算法的对接
YUV数据的处理
def process_yuv_frame(self, yuv_data, width, height):
# 将YUV数据转换为numpy数组
yuv_array = np.frombuffer(yuv_data, dtype=np.uint8).reshape((height * 3 // 2, width))
# 调用视觉算法
result = self.visual_algorithm.process_yuv(yuv_array)
# 在主线程更新UI
self.root.after(0, self.update_visual_result, result)
RGB数据的处理
如果视觉算法需要RGB数据,可以将YUV数据转换为RGB数据,或者直接使用回调函数中的RGB数据:
def process_rgb_frame(self, rgb_data, width, height):
# 将RGB数据转换为numpy数组
rgb_array = np.frombuffer(rgb_data, dtype=np.uint8).reshape((height, width, 3))
# 调用视觉算法
result = self.visual_algorithm.process_rgb(rgb_array)
# 在主线程更新UI
self.root.after(0, self.update_visual_result, result)
视觉算法的实现
定义一个视觉算法类,包含处理YUV和RGB数据的方法:
class VisualAlgorithm:
def process_yuv(self, yuv_array):
# 在这里实现视觉算法对YUV数据的处理
pass
def process_rgb(self, rgb_array):
# 在这里实现视觉算法对RGB数据的处理
pass
三、在播放器中集成视觉算法
初始化视觉算法 在播放器的初始化方法中,创建视觉算法的实例:
def __init__(self, root):
# 其他初始化代码
self.visual_algorithm = VisualAlgorithm()
更新视觉结果 定义一个方法来在UI上更新视觉算法的结果:
def update_visual_result(self, result):
# 在这里更新UI以显示视觉算法的结果
pass
通过以上步骤,我们可以轻松将YUV或RGB数据回调与视觉算法对接,在播放器中实现视觉算法的功能。
基于Python实现的RTSP/RTMP播放器具有简单易用、功能丰富、可扩展性强等特点。通过对代码结构和实现原理的深入解析,可以帮助开发者更好地理解和优化播放器,毫秒级的播放体验和解码后yuv或rgb数据回调模式,提高了实时直播场景下,Python环境下AI算法处理的效率,以上抛砖引玉,感兴趣的开发者,可以单独跟我沟通探讨。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有