Python下的毫秒级延迟RTSP|RTMP播放器技术探究和AI视觉算法对接

​引言

十年前,大牛直播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

    python 复制代码
    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

    lua 复制代码
    self.smart_player_sdk_api.StopPlay(self.player_handle)
    self.is_playing = False
    self.play_btn.config(text="播放")
    self.update_status("已停止")
  • 硬件解码 :根据设备支持情况,通过SetH264HardwareDecoderSetH265HardwareDecoder方法启用硬件解码,以提高播放性能。

    设置硬解码

    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_recordingstop_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

    python 复制代码
    # 确保记录配置已初始化
    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

    python 复制代码
    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

    python 复制代码
    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界面设计

UI界面基于Tkinter构建,包括视频画布、控制按钮、输入框等组件。通过pack方法和grid方法对组件进行布局和定位,实现了一个简洁直观的用户界面。

优化与扩展策略

性能优化

  • 硬件加速 :充分利用硬件解码功能,提高视频解码效率。

  • 异步线程处理 :通过多线程技术异步处理视频帧数据,避免阻塞主线程,提高播放流畅度。

  • 缓冲区管理 :合理设置缓冲区大小和超时时间,以平衡播放质量和网络延迟。

功能扩展

  • 添加音量调节滑块 :通过scale控件实现音量调节功能,用户可以实时调整播放音量。

  • 改进录像回调处理 :在录像回调函数中添加更多处理逻辑,如自动分割文件、上传录像文件等。

  • 支持更多视频格式 :通过扩展SDK接口或添加新的解码器,支持更多视频格式的播放。

Python播放器技术优势

1. 低延迟性

低延迟播放器通过优化数据处理和传输过程,实现了更低的延迟。这对于需要实时交互的应用场景非常重要,如监控、互动式直播、视频会议等。观众可以几乎实时地观看直播内容、参与互动,大大提升了实时性和互动性体验。

2. 高兼容性和灵活性

RTSP和RTMP协议支持多种媒体格式和编码方式,具有很强的兼容性。无论是常见的H.264、H.265视频编码格式,还是AAC、PCMA、PCMU等音频编码格式,低延迟播放器都能很好地支持,能够适应不同设备和系统的需求。

3. 支持多种网络环境

低延迟播放器能够适应不同的网络环境,包括TCP和UDP传输方式。TCP保证了传输的可靠性,适用于对数据准确性要求较高的场景;UDP则具有较低的延迟和较高的传输效率,适用于对实时性要求较高的场景。此外,播放器还支持自适应调整播放策略,如在网络带宽不足时自动降低视频的分辨率或帧率,以保证视频的流畅播放。

4. 硬件加速

现代播放器通常利用硬件加速来提高播放性能。这可以通过使用专用的硬件解码器和图形加速器来实现,以加快解码和渲染过程,从而降低延迟。例如,在解码过程中,采用高效的解码算法,充分利用硬件加速功能,如GPU加速,以快速处理大量的音视频数据。

5. 高效的缓冲管理

低延迟播放器通过有效的缓存管理来减少延迟。它会使用较小的缓冲区,并采用动态缓冲策略,使缓存保持最小化,从而减少播放器响应时间。同时,支持设置缓冲时间,以应对网络抖动等不稳定情况,确保播放的流畅性。

6. 快速起播

低延迟播放器致力于实现快速的起播时间。它可以使用预加载技术,提前缓存部分音视频数据,并在用户点击播放时立即开始播放,从而缩短起播延迟。

7. 支持多实例播放

低延迟播放器支持多实例播放,适用于需要同时监控多个视频源的场景。这不仅满足了不同用户的需求,还保证了低延迟性能。

8. 功能丰富

Python下的RTMP、RTSP播放器,实际上还是调用的C接口的,所以Windows平台播放器支持的功能,Python下依然支持,完善的功能,覆盖了95%以上的使用场景。

如何实现AI视觉算法对接?

大牛直播SDK做Python下的低延迟RTMP、RTSP播放器,除了常规播放外,更多的是为了方便做Python下的视觉算法对接处理。

一、回调函数的实现

  1. 定义回调函数 在播放器的代码中,定义一个回调函数来接收YUV或RGB数据。例如:

    ini 复制代码
    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_)
  2. 注册回调函数 在播放器初始化时,将回调函数注册到播放器SDK中:

    python 复制代码
    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)

二、视觉算法的对接

  1. YUV数据的处理

    • 如果视觉算法需要YUV数据,可以直接将回调函数中的YUV数据传递给算法:

      python 复制代码
      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)
  2. RGB数据的处理

    • 如果视觉算法需要RGB数据,可以将YUV数据转换为RGB数据,或者直接使用回调函数中的RGB数据:

      ruby 复制代码
      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)
  3. 视觉算法的实现

    • 定义一个视觉算法类,包含处理YUV和RGB数据的方法:

      ruby 复制代码
      class VisualAlgorithm:
          def process_yuv(self, yuv_array):
              # 在这里实现视觉算法对YUV数据的处理
              pass
      
          def process_rgb(self, rgb_array):
              # 在这里实现视觉算法对RGB数据的处理
              pass

三、在播放器中集成视觉算法

  1. 初始化视觉算法 在播放器的初始化方法中,创建视觉算法的实例:

    ruby 复制代码
    def __init__(self, root):
        # 其他初始化代码
        self.visual_algorithm = VisualAlgorithm()
  2. 更新视觉结果 定义一个方法来在UI上更新视觉算法的结果:

    python 复制代码
    def update_visual_result(self, result):
        # 在这里更新UI以显示视觉算法的结果
        pass

通过以上步骤,我们可以轻松将YUV或RGB数据回调与视觉算法对接,在播放器中实现视觉算法的功能。

结论

基于Python实现的RTSP/RTMP播放器具有简单易用、功能丰富、可扩展性强等特点。通过对代码结构和实现原理的深入解析,可以帮助开发者更好地理解和优化播放器,毫秒级的播放体验和解码后yuv或rgb数据回调模式,提高了实时直播场景下,Python环境下AI算法处理的效率,以上抛砖引玉,感兴趣的开发者,可以单独跟我沟通探讨。

相关推荐
chenchao_shenzhen3 天前
RK3568嵌入式音视频硬件编解码4K 60帧 rkmpp FFmpeg7.1 音视频开发
ffmpeg·音视频·rk3588·音视频开发·嵌入式开发·瑞芯微rk3568·硬件编解码
码流怪侠4 天前
Google SoundStream音频编解码器技术解析
深度学习·音视频开发
字节跳动视频云技术团队5 天前
基于 DiT 大模型与字体级分割的视频字幕无痕擦除方案,助力短剧出海
aigc·音视频开发·视频编码
音视频牛哥6 天前
跨平台轻量级RTSP服务模块技术详解与内网低延迟直播实践
音视频开发·视频编码·直播
aqi007 天前
FFmpeg开发笔记(八十)使用百变魔音AiSound实现变声特效
android·ffmpeg·音视频·直播·流媒体
aqi008 天前
FFmpeg开发笔记(七十九)专注于视频弹幕功能的国产弹弹播放器
android·ffmpeg·音视频·直播·流媒体
音视频牛哥11 天前
SmartMediaKit 模块化音视频框架实战指南:场景链路 + 能力矩阵全解析
音视频开发·视频编码·直播
子龙_11 天前
JS解析wav音频数据并使用wasm加速
前端·javascript·音视频开发
泉城老铁12 天前
Spring Boot + Vue + ZLMediaKit 实现 RTSP 拉流播放的完整方案
java·vue.js·音视频开发