aiortc 0.9.27离线安装与WebRTC服务器推流实践
简介:aiortc-0.9.27.tar.gz 是 PyPI 官网发布的 Python 异步实时通信库安装包,面向需要在 Python 环境中实现 WebRTC 音视频通话、数据通道等功能的开发者。它基于 asyncio 框架,可帮助 Python 开发者绕过传统同步 I/O 的限制,快速构建视频会议、在线教育、实时文件共享等应用。整个压缩包共 139 个文件,大小 1.1MB,包含 71 个 py 源码文件、39 个 bin 测试数据文件(如 H.264 与 RTP 抓包样本)、16 个 rst 文档、2 个 html 及配置文件等,便于学习协议实现与调试网络传输。包内目录结构清晰,既有核心库代码,也有用于测试的 SCTP/ICE 信令样例,适合有一定 Python 基础、想深入 WebRTC 底层机制的开发者。目前已有 138 人学习下载。通过阅读源码与运行附带样例,读者可以掌握 RTCPeerConnection、RTCDataChannel、ICE/STUN/TURN 等关键组件的实际用法,并参考 bin 文件中的抓包数据理解媒体流封装细节,为后续二次开发或集成实时通信功能打下扎实基础。
1. 为什么要在 PyPI 上下载 aiortc 0.9.27 的 tar 包
当你需要在一个没有 GUI 的服务器上处理 WebRTC 音视频流量,或者想用 Python 直接推流到浏览器时,aiortc 几乎是唯一的开源选择。它把 WebRTC 通话、音视频编解码、ICE/DTLS/SRTP 协议族都封装成了 asyncio 接口,比用 C++ 的 libwebrtc 重新封装省去大量重复劳动。我碰上 aiortc 0.9.27,是因为线上环境已经用它稳定跑了一年传输流,后来要离线部署时发现 PyPI 上已经找不到这个旧版本的可编译文件。从 PyPI 官网下载 tar.gz 再手工安装,不只是因为仓库归档,更多是要拿到和 Python 版本、系统库完全匹配的源码包,避免后续 pip install 解析依赖时自动升级到不兼容的大版本。这篇内容适合正在评估 aiortc、或者已被依赖问题缠住的 Python 后端工程师,读完后能自己下手装好、跑通、调参数。
2. 从 PyPI 官网下载并安装 aiortc 0.9.27
2.1 在 PyPI 页面定位 0.9.27 的源码包
PyPI 上每个项目的 Release History 都会保留所有历史文件。直接访问 https://pypi.org/project/aiortc/0.9.27/ ,页面底部的 Download files 区域会列出 aiortc-0.9.27.tar.gz 、 aiortc-0.9.27-py3-none-any.whl 以及可选的签名文件。源码包以 .tar.gz 结尾,里面包含 setup.py 、 src/aiortc/ 、 tests/ ,比 wheel 更适合在自定义环境中重新编译。优先点 .tar.gz ,然后右键复制链接地址,用 wget 或 curl 拉取。文件名里的 0.9.27 是版本号,不要和 Python 的 __version__ 搞混,后者在导入后读取。
wget https://files.pythonhosted.org/packages/.../aiortc-0.9.27.tar.gz
链接中的 files.pythonhosted.org 是 PyPI 的实际文件分发地址。参数不重要,关键是用浏览器访问 PyPI 项目页面后,从 Download files 区找到对应文件的真实存储路径。直接把页面上的下载链接复制下来是最不容易出错的,手工拼接 URL 经常会因为路径哈希过期而失效。
2.2 用 pip 下载指定版本并保留依赖源码
除了浏览器手动下载,更工程化的是直接用 pip download 拉取指定版本的全部依赖到本地目录,这样后续换一台内网机器也能离线安装。我一般用以下命令:
python -m pip download aiortc==0.9.27 \
--no-binary=:all: \
--dest=./offline_packages
--no-binary=:all: 强制所有包都下载源码包而不是预编译的 wheel,这个参数在目标机器架构与当前机器不一致时特别有用。 --dest=./offline_packages 指定输出文件夹,命令执行后会把 aiortc 和它声明的依赖(PyAV、cryptography、websockets、aioquic、google-crc32c 等)一起下载下来。注意: pip download 不会下载 Python 解释器和系统动态库,那些还是得靠目标环境的包管理器解决。
如果只是想快速拿到单个源码包,也可以:
python -m pip download aiortc==0.9.27 --no-binary=:all: --no-deps -d ./pkg
--no-deps 让它不要拉取任何依赖,避免临时目录里多出一堆互相关联的源码。适合只用来阅读 aiortc 内部实现。
2.3 校验文件哈希并安装到虚拟环境
PyPI 页面的 Download files 区域每个文件旁边都会列出 SHA256 值。下载完成后,用 sha256sum 对本地文件算一次哈希,和页面上显示的哈希逐字符比对。这一步在从镜像站或者不信任的缓存中下载时特别重要。
sha256sum aiortc-0.9.27.tar.gz
# 输出结果应与 PyPI 页面中的 SHA256 完全一致
校验通过后,先创建一个干净虚拟环境,再安装本地文件:
python -m venv .venv
source .venv/bin/activate
pip install ./offline_packages/aiortc-0.9.27.tar.gz
带 ./ 的路径会让 pip 把它当作本地文件处理,而不是从 PyPI 重新找版本。如果之前已经下载了全部依赖,可以加上 --find-links ./offline_packages --no-index 来完全离线安装:
pip install ./aiortc-0.9.27.tar.gz \
--find-links ./offline_packages \
--no-index
--no-index 禁止 pip 访问 PyPI,只从 --find-links 指定的本地目录寻找依赖。使用场景很清楚:生产环境不连外网,或者需要统一各机器的依赖版本。
2.4 提前准备编译工具和原生库
aiortc 0.9.27 在源码安装时会构建 _cffi 相关的二进制扩展,所以系统里必须有 C 编译器和几个关键库。在 Ubuntu/Debian 上我通常会先执行:
sudo apt-get install -y build-essential \
libavcodec-dev libavformat-dev libavutil-dev \
libswscale-dev libavdevice-dev \
libopus-dev libvpx-dev libsrtp2-dev
其中 libav* 是 PyAV 编译时要用的 FFmpeg 开发头文件, libopus-dev 提供 Opus 音频编码支持, libvpx-dev 提供 VP8/VP9 编码器, libsrtp2-dev 用于 DTLS 和 SRTP 的原生实现。macOS 上可以用 Homebrew 装同样的包: brew install ffmpeg opus libvpx libsrtp 。缺少这些库时,编译会在 pip install 阶段直接报 fatal error: 'libavutil/avutil.h' file not found ,这类报错不是代码问题,而是原生依赖缺失。
安装完成后,用这样一个命令验证 aiortc 是否能被正常导入:
python -c "import aiortc; print(aiortc.__version__)"
正常会输出 0.9.27 。如果出现 ImportError: cannot import name 'RTCRtpSender' 等不明确错误,多半是安装过程混进了旧版本残留,把虚拟环境删掉重建一般能解决。
3. 用 aiortc 0.9.27 跑通第一个 WebRTC 通话
3.1 创建 RTCPeerConnection 并交换 SDP 信令
aiortc 的入口是 RTCPeerConnection ,每个连接实例代表一个 WebRTC 对端。与浏览器不同,服务器端没有自动的信令通道,你需要自己负责把 offer、answer 和 ICE candidate 通过 WebSocket、HTTP 或 Redis pub/sub 转发给对端。下面是一个最小化的 offer 侧代码:
import asyncio
from aiortc import RTCPeerConnection, RTCSessionDescription
async def make_offer():
pc = RTCPeerConnection()
async def on_track(track):
print(f"收到远端轨道 {track.kind}")
pc.on("track", on_track)
# 创建本轮通话的 offer
offer = await pc.createOffer()
await pc.setLocalDescription(offer)
# 把这个 SDP 发送给对端(通过网络发送,这里只是打印)
print(offer.sdp)
# 等待对端 answer 返回后,回填到本地
answer_sdp = await wait_for_answer(offer.sdp)
await pc.setRemoteDescription(RTCSessionDescription(sdp=answer_sdp, type="answer"))
return pc
这段代码里, createOffer() 会在底层做一次候选收集,尽管在网络状况复杂时 ICE 聚合需要一些时间。 setLocalDescription 之后, pc.localDescription.sdp 就是需要投递给对端的文本,里面包含会话描述和部分候选。 RTCSessionDescription 的两个关键参数是 sdp 和 type , type 必须是 offer 、 answer 、 pranswer 或 rollback 中的一个。实际生产里,answer 的 SDP 通常是从对端的 API 接口里拿到的,这里只演示回填逻辑。
3.2 从本地媒体文件读流并添加为发送轨道
绝大多数 WebRTC 服务器需要将本地文件或摄像头画面发送给浏览器。aiortc 提供的 MediaPlayer 可以封装媒体文件,然后通过 addTrack 把音频和视频轨道都挂到连接上:
from aiortc.contrib.media import MediaPlayer
player = MediaPlayer("./input.mp4", options={"framerate": "30"})
pc.addTrack(player.video)
pc.addTrack(player.audio)
MediaPlayer 的默认行为是循环播放一个文件流,每帧都复用从 FFmpeg 解出来的原始数据。这种方式的好处是节省内存,坏处是没法动态修改码率。如果你不想要循环播放,可以在初始化后手动停掉:
player.video.stop()
addTrack 一次只能添加一条轨道,且必须发生在创建 offer 之前,否则浏览器对端会拒绝应答。若需要添加屏幕分享或自定义动态生成的画面,可以继承 VideoStreamTrack 重写 recv 方法,下面这段代码生成每秒 30 帧的红色画面:
import fractions
import av
from aiortc import VideoStreamTrack
class RedTrack(VideoStreamTrack):
def __init__(self):
super().__init__()
self.counter = 0
async def recv(self):
self.counter += 1
frame = av.VideoFrame.from_ndarray(
__import__("numpy").zeros((720, 1280, 3), dtype="uint8"),
format="bgr24"
)
frame.pts = self.counter
frame.time_base = fractions.Fraction(1, 30)
return frame
pc.addTrack(RedTrack())
这里复用了 av.VideoFrame 来组装帧数据, pts 必须单调递增且以 time_base 为刻度。如果 pts 从 0 开始且 time_base 是 1/30,那么 aiortc 就会认为帧率是 30fps。实际开发中,从摄像头或采集卡拿到的帧可能需要先做灰度转换或尺寸缩放,这部分用 PyAV 的滤波器效率很高。
3.3 用数据通道传业务消息
WebRTC 数据通道不占用音视频轨,但它可以传任意字节流,适合做文本聊天、游戏操作同步或信令兜底。aiortc 的创建方式有两种:一是在连接建立前主动创建,二是监听远端发来的通道。
async def setup_data_channel(pc):
# 主动创建一条名为 "chat" 的数据通道
channel = pc.createDataChannel("chat")
@channel.on("open")
def on_open():
channel.send("hello from python!")
@channel.on("message")
def on_message(message):
print(f"收到对端消息: {message}")
createDataChannel 有很多可选参数,比如 ordered=False 、 maxRetransmits=0 表示非可靠传输,适合实时游戏位置同步;默认 ordered=True 则保证消息按序到达,和 TCP 的保证语义类似。数据通道的事件回调是同步函数,不要在 on_message 里直接写 await 阻塞逻辑,如果需要做异步处理,用 asyncio.create_task 把耗时操作丢到后台。
跑通这个最小通话后,你就已经跨越了 aiortc 最陡峭的入门门槛:信令交换和轨道添加。
4. 参数调优:码率、编码器和连接状态控制
4.1 限制发送带宽与动态调整视频码率
WebRTC 的拥塞控制算法会自动根据 RTT 和丢包率调整码率,但在服务器端推流时,你往往需要手动设置一个上限,防止带宽被某个连接占满。aiortc 里最直接的方式是修改 RTCRtpSender 的 setParameters :
sender = pc.addTrack(video_track)
params = sender.getParameters()
params.encodings[0].maxBitrate = 500_000 # 500 kbps
await sender.setParameters(params)
设置发生在 addTrack 之后、创建 offer 之前,效果会体现在 SDP 的 b=AS 字段上。 maxBitrate 的单位是 bits per second,500_000 就是 500 kbps。如果你在通话中途想降码率,同样调用 setParameters 即可,浏览器端会在一到两秒内反应出来。需要说明的是,这个参数只约束发送方的编码器输出,不控制接收分辨率,如果对端请求的 SDP 分辨率很高,码率限制依然生效,但画面会给人糊的感觉。
码率开关和帧率的配合也值得关注。对低运动的视频,比如 PPT 共享或静态监控画面,可以降低帧率但保持码率不变,主观体验反而更好。aiortc 中可以通过修改视频轨道属性来影响帧率,但最稳定的做法是利用 SDP 中的 framerate 属性。
4.2 在 H.264 与 VP8 编码器之间显式选择
aiortc 0.9.27 默认使用环境中可用的编码器,可能是 OpenH264、x264 或 VP8。如果你发现对端浏览器显示“无法解码”,那往往是因为编码器依赖的库没有装全。要显式控制编码器,可以在创建 offer 前通过 transceiver 的 getCapabilities() 查询,然后用 setCodecPreferences 锁定:
from aiortc.rtp import RTCRtpCodecCapability
import asyncio
async def prefer_codec(pc, codec_name):
transceivers = pc.getTransceivers()
if not transceivers:
return
cap = transceivers[0].receiver.getCapabilities()
codecs = [c for c in cap.codecs if c.mimeType.lower() == f"video/{codec_name}".lower()]
await transceivers[0].setCodecPreferences(codecs)
调用时:
pc = RTCPeerConnection()
# 添加轨道...
await prefer_codec(pc, "h264")
mimeType 的值会类似 video/H264 ,需要留意大小写。锁定 H.264 后,如果目标机器与实际支持能力不匹配,创建 SDP 时会报 InvalidModificationError ,排查方向是看本机 libopenh264 是否可用。实战中我更推荐同时安装 openh264 和 libx264 ,让 aiortc 在运行时自己挑一个双方都支持的格式,而不是写死偏好。
4.3 监控连接状态与 ICE 重启
WebRTC 网络环境很复杂,掉线后自动恢复是服务器端必须具备的能力。aiortc 的 connectionState 属性会经历 new → connecting → connected → disconnected → failed 这几个状态。我习惯在收到 disconnected 时启动 5 秒超时,超时后主动做 ICE 重启:
@pc.on("connectionstatechange")
async def on_connection_change():
if pc.connectionState == "failed":
await asyncio.sleep(0.1)
await pc.restartIce()
elif pc.connectionState == "disconnected":
await asyncio.sleep(5)
if pc.connectionState == "disconnected":
await pc.restartIce()
restartIce() 调用后,需要重新创建 offer 并发送给远端,否则只在本端更换候选没有意义。在浏览器端,对端会收到带有 ice-ufrag 改变的 SDP,从而自动完成候选切换。这个机制对于长期占用连接的应用很关键,比如直播推流或视频会议。
丢包率和 RTT 的观测与码率控制在 aiortc 0.9.27 中并不直接暴露到公开 API,但在信令服务器中维护连接状态列表是可行的。每秒轮询一次 pc.connectionState ,把状态变化写入日志,能帮你定位网络抖动是发生在 NAT 穿透还是链路丢包阶段。
5. 排错实战:版本冲突、编译错误与日志验证技巧
5.1 依赖冲突导致运行时报错
aiortc 0.9.27 依赖 PyAV 、 cryptography 、 aioquic 、 websockets 、 google-crc32c 等。最容易踩的坑是两个:一是 websockets 版本过高导致 handshake 行为不一致,二是 cryptography 大版本升级后导致传输握手失败。安装时指定与 0.9.27 兼容的版本范围更稳:
pip install "aiortc==0.9.27" \
"websockets>=9,<11" \
"cryptography>=2.8,<37"
如果已经遇到与 cryptography 的链接报错,直接降级重装:
pip uninstall -y cryptography
pip install cryptography==36.0.2
这个问题一般出现在同时安装了大量新依赖的机器学习环境里,排查时先看完整堆栈里是否提到 libssl 或 Crypto ,基本就是它了。
5.2 DTLS 握手失败与日志抓取
当对端连接一直卡在 connecting 且状态变为 failed 时,多半是 UDP 端口被封或者 DTLS 证书验证失败。先确认防火墙没有挡住 50000-50100 之类的本地端口段,aiortc 在初始化时会在默认区间随机选取 UDP 端口,而不是固定端口。可以用 strace 或者 tcpdump 看有没有入站包:
sudo tcpdump -i eth0 udp portrange 50000-50100
排查时开启 aiortc 自己的日志很大用处:
AIORTC_DEBUG=DEBUG python your_app.py
或调用代码:
import logging
logging.basicConfig(level=logging.DEBUG)
日志里会打印出 RTCP packet received 、 DTLS handshake in progress 等信息。如果看到 alert unknown_ca ,说明对端不信任本端证书,检查你是否自定义了 RTCConfiguration 中的证书指纹。大部分测试环境直接用自签证书能过,但生产环境需要让信令服务器交换证书指纹。
5.3 解析并发送自定义帧时的常见问题
在自定义视频轨道里,最容易出的问题是 pts 不是单调递增。aiortc 在 RTCRtpSender 内部会检查帧的时间戳,如果 pts 倒退,编码器会丢掉帧或者推流卡住。解决方式是内部维护一个自增计数器:
class FrameSource:
def __init__(self):
self._pts = 0
async def recv(self):
self._pts += 1
frame = av.VideoFrame.from_ndarray(np.zeros((720, 1280, 3), dtype="uint8"), "bgr24")
frame.pts = self._pts
frame.time_base = fractions.Fraction(1, 30)
return frame
还有另一个容易忽视的坑: VideoStreamTrack 的 recv 应该一秒钟最多被调用帧率次数,如果底层采集速度跟不上,就必须阻塞等待或丢帧,否则当 CPU 跟不上时,RTP 包的时间戳会乱掉。常见做法是记录上下帧之间的 wallclock 时间,小于间隔时用 await asyncio.sleep 睡眠补足。
5.4 在浏览器端与 ffmpeg 同时调试
最后留一个我在工作中常用的验证手段:不用浏览器,直接让 ffmpeg 打一个 sdp 文件来连接 aiortc,能快速测试服务器端的推流能力。把 aiortc 输出的 SDP 保存成 server.sdp`,用以下命令播放:
ffplay -protocol_whitelist file,rtp,udp server.sdp
遇到播放黑屏时,注意看 SDP 里的 a=rtpmap 行,确认 payload 类型和时钟频率是否正确。这种调试方式可以绕开浏览器的加密握手细节,直接验证底层的 RTP 转发是否顺畅。如果 ffplay 能出声出画但浏览器不行,再回来检查证书和 TURN 配置——往往是传输层面没问题,而是安全策略阻塞了媒体流。
把这几条排错路径提前备好,你在 PyPI 上下载的这个 tar 包就能在多种网络环境中真正稳定跑起来。
火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。
更多推荐
所有评论(0)