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

使用aiortc搭建Python WebRTC本地推流服务器遇错误排查

问题分析与修复方案

核心问题原因

  1. 客户端Offer缺少视频媒体约束:客户端创建Offer时未明确声明接收视频,导致SDP中没有视频媒体描述(m-line),服务器添加视频轨道后无法匹配,触发ValueError: None is not in list错误。
  2. 服务器端未正确处理ICE候选:未收集ICE候选者,导致Answer的SDP中没有本地网络地址信息,无法建立P2P连接。
  3. 服务器响应格式错误:直接返回字典而非aiohttp要求的JSON响应对象,可能导致客户端解析异常。

具体修复步骤

1. 修复客户端JavaScript代码

创建Offer时添加媒体约束,明确告知服务器需要接收视频:

pc = new RTCPeerConnection({
    iceServers: [] // 本地网络无需ICE服务器
});

// 监听远程轨道,渲染到视频元素
pc.ontrack = event => {
    if (event.track.kind === 'video') {
        video.srcObject = event.streams[0];
    }
};

// 创建带视频接收约束的Offer
const offer = await pc.createOffer({
    offerToReceiveVideo: true,
    offerToReceiveAudio: false // 不需要音频可关闭
});
await pc.setLocalDescription(offer);

// 发送Offer到服务器并处理Answer
const response = await fetch('/offer', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        sdp: pc.localDescription.sdp,
        type: pc.localDescription.type
    })
});

const answer = await response.json();
await pc.setRemoteDescription(new RTCSessionDescription(answer));

2. 修复服务器端Python代码

  • 用web.json_response包装响应
  • 监听ICE候选事件(确保SDP包含本地地址)
  • 补全BouncingBallTrack的recv方法基础实现

修改后的服务器代码:

import asyncio
from aiortc import RTCPeerConnection, RTCSessionDescription, VideoStreamTrack
from av import VideoFrame
import numpy as np
import cv2
from aiohttp import web

class BouncingBallTrack(VideoStreamTrack):
    def __init__(self):
        super().__init__()
        self.width = 640
        self.height = 480
        # 初始化球的位置和速度
        self.x, self.y = 100, 100
        self.vx, self.vy = 5, 5
        self.radius = 20

    async def recv(self):
        # 创建黑色背景帧
        frame = np.zeros((self.height, self.width, 3), dtype=np.uint8)
        # 绘制绿色球
        cv2.circle(frame, (self.x, self.y), self.radius, (0, 255, 0), -1)
        # 更新球的位置
        self.x += self.vx
        self.y += self.vy
        # 边界碰撞检测
        if self.x + self.radius > self.width or self.x - self.radius < 0:
            self.vx *= -1
        if self.y + self.radius > self.height or self.y - self.radius < 0:
            self.vy *= -1
        # 转换为VideoFrame格式
        new_frame = VideoFrame.from_ndarray(frame, format='bgr24')
        new_frame.pts = self.pts
        new_frame.time_base = self.time_base
        self.pts += 1
        return new_frame

async def offer(request):
    params = await request.json()
    offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])

    pc = RTCPeerConnection()

    # 添加本地视频轨道
    pc.addTrack(BouncingBallTrack())

    # 监听ICE候选,收集完成后SDP会包含本地地址
    @pc.on("icecandidate")
    def on_icecandidate(candidate):
        if not candidate:
            pass

    await pc.setRemoteDescription(offer)
    answer = await pc.createAnswer()
    await pc.setLocalDescription(answer)

    # 返回标准JSON响应
    return web.json_response({
        "sdp": pc.localDescription.sdp,
        "type": pc.localDescription.type
    })

if __name__ == "__main__":
    app = web.Application()
    app.router.add_post("/offer", offer)
    app.router.add_static('/', path='static', name='static')
    web.run_app(app, port=8080)

3. 验证要点

  • 客户端Offer的SDP中会新增m=video行,服务器添加的视频轨道能与之匹配
  • 服务器Answer的SDP会包含本地ICE候选(如a=candidate:xxx),用于本地网络P2P连接
  • 服务器不再抛出ValueError,浏览器能正常接收并显示跳动的绿色球视频流

内容的提问来源于stack exchange,提问作者Vitaliy Tsirkunov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 04:37:33