You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为PJSUA2 v2.13的SIP VoIP音频通话实现视频功能?

PJSUA2 2.13 + Qt Python 扩展视频通话实现方案

一、核心配置修改

首先在Endpoint初始化阶段启用视频支持,配置基础参数:

from pjsua2 import *

# 初始化Endpoint
ep = Endpoint()
ep.libCreate()
ep_config = EpConfig()

# 启用视频并配置设备
ep_config.mediaConfig.enableVideo = True
ep_config.mediaConfig.videoConfig.defaultCaptureDev = -1  # 使用系统默认摄像头
ep_config.mediaConfig.videoConfig.defaultRenderDev = -1  # 使用系统默认渲染设备
# 设置视频编码优先级(H264兼容性更广,按需调整)
ep_config.mediaConfig.videoCodecs = ["H264/90000", "VP8/90000"]

# 初始化库
ep.libInit(ep_config)

# 可选:枚举并指定摄像头
vid_dev_mgr = ep.vidDevManager()
capture_devs = vid_dev_mgr.enumCaptureDev()
if capture_devs:
    ep_config.mediaConfig.videoConfig.defaultCaptureDev = capture_devs[0].id

二、Qt视频渲染控件实现

自定义QWidget子类处理视频帧转换与UI渲染,确保线程安全更新:

from PyQt5.QtWidgets import QWidget
from PyQt5.QtGui import QImage, QPainter
from PyQt5.QtCore import Qt, QMetaObject

class VideoDisplay(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._current_frame = None

    def update_frame(self, pj_frame):
        # 转换PJSUA2的YUV420P帧到QImage
        width = pj_frame.width
        height = pj_frame.height
        
        # 提取帧数据
        y_buf = pj_frame.buf[0].read().bytes
        u_buf = pj_frame.buf[1].read().bytes
        v_buf = pj_frame.buf[2].read().bytes

        # 初始化RGB32格式QImage
        img = QImage(width, height, QImage.Format_RGB32)
        ptr = img.bits()
        ptr.setsize(img.byteCount())
        
        # 注意:实际生产环境需用高效YUV转RGB算法(可参考pjmedia_yuv2rgb或OpenCV)
        # 此处省略具体转换逻辑,仅保留框架
        
        self._current_frame = img
        # 强制在Qt主线程更新UI
        QMetaObject.invokeMethod(self, "update", Qt.QueuedConnection)

    def paintEvent(self, event):
        if self._current_frame:
            painter = QPainter(self)
            # 按比例缩放渲染视频
            painter.drawImage(self.rect(), self._current_frame.scaled(self.size(), Qt.KeepAspectRatio))

三、Call类扩展视频回调

继承Call类,重写视频帧接收方法,绑定Qt渲染控件:

class SIPCall(Call):
    def __init__(self, account, call_id=PJSUA_INVALID_ID):
        super().__init__(account, call_id)
        self.video_widget = None  # 关联Qt视频显示控件

    def onVideoFrameReceived(self, prm, frame):
        # 收到视频帧时更新绑定的控件
        if self.video_widget:
            self.video_widget.update_frame(frame)

四、呼叫与接听的视频参数配置

发起或接听呼叫时,必须明确启用视频流:

发起视频呼叫

# 假设已初始化Account实例acc
call_op_param = CallOpParam()
call_op_param.opt.audioCount = 1
call_op_param.opt.videoCount = 1  # 启用视频流
call_op_param.opt.streamFlags = PJSUA2_STREAM_FLAG_VIDEO

call = SIPCall(acc)
# 绑定本地视频显示控件
call.video_widget = self.local_video
call.makeCall("sip:target@your-sip-server.com", call_op_param)

接听视频呼叫

class SIPAccount(Account):
    def onIncomingCall(self, prm):
        call = SIPCall(self, prm.callId)
        answer_param = CallOpParam()
        answer_param.opt.audioCount = 1
        answer_param.opt.videoCount = 1
        call.answer(answer_param)
        # 绑定远端视频显示控件
        call.video_widget = self.parent().findChild(VideoDisplay, "remote_video")

五、常见问题排查

  • 视频支持未启用:确认PJSUA2编译时添加--enable-video参数,依赖库(如ffmpeg、v4l2)已正确安装
  • 摄像头权限:Linux下确保用户属于video组,macOS/Windows需在系统隐私设置中允许应用访问摄像头
  • 编码协商失败:查看PJSUA2日志(ep.libLogWrite()输出),确认双方支持的视频编码一致
  • UI更新异常:视频帧回调运行在PJSUA2线程,必须用QMetaObject.invokeMethod在Qt主线程更新控件
  • NAT穿越问题:视频流需更大带宽,确保STUN/TURN服务器配置正确,可通过ep_config.uaConfig.stunServer设置

内容的提问来源于stack exchange,提问作者Marcello Romagnoli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 20:57:06