我正在组装一个原型,它可以通过aiortc使用WebRTC将视频从一个Python脚本传输到另一个脚本.
我重复了来自Python aiortc存储库的the webcam个示例(使用--play-from标志从视频文件而不是网络摄像头中流),并try 将client.js脚本移植到Python.我在那里是mostly,但ICE连接从未(成功)完成,并在大约30秒后从IN_PROGRESS
到FAILED
.我接收并可以订阅客户端脚本中的曲目,但从未开始从webcam.py脚本接收流.
Webcam.py脚本保持不变,以下是我的client.py脚本:
import asyncio
import json
import requests
import time
from aiortc import (
MediaStreamTrack,
RTCConfiguration,
RTCIceServer,
RTCPeerConnection,
RTCSessionDescription,
)
from aiortc.contrib.media import MediaPlayer, MediaRelay
from loguru import logger
relay = MediaRelay()
class VideoTransformTrack(MediaStreamTrack):
"""
A video stream track that transforms frames from an another track.
"""
kind = "video"
def __init__(self, track):
if track.kind != "video":
raise Exception("Unsupported kind: %s" % (track.kind))
super().__init__() # don't forget this!
logger.info("track kind: %s" % track.kind)
self.track = track
async def recv(self):
logger.info("Attempt to receive frame ...")
frame = await self.track.recv()
logger.info("Received frame: %s" % (frame))
async def run():
# config = RTCConfiguration(
# iceServers=[
# RTCIceServer(
# urls=[
# "stun:stun.l.google.com:19302",
# "stun:stun1.l.google.com:19302",
# "stun:stun2.l.google.com:19302",
# "stun:stun3.l.google.com:19302",
# "stun:stun4.l.google.com:19302",
# ]
# ),
# ]
# )
pc = RTCPeerConnection()
pc.addTransceiver("video", direction="recvonly")
@pc.on("track")
def on_track(track):
"""
Track has been received from client.
"""
logger.info("Track %s received" % track)
pc.addTrack(
VideoTransformTrack(
relay.subscribe(track),
)
)
@track.on("ended")
async def on_ended():
logger.info("Track ended: %s" % track)
offer = await pc.createOffer()
await pc.setLocalDescription(offer)
while True:
logger.info("icegatheringstate: %s" % pc.iceGatheringState)
if pc.iceGatheringState == "complete":
break
offer2 = pc.localDescription
data = {
"sdp": offer2.sdp,
"type": offer2.type,
}
response = requests.post(
"http://localhost:8080/offer",
headers={"Content-Type": "application/json"},
json=data,
)
response_data = response.json()
await pc.setRemoteDescription(
RTCSessionDescription(sdp=response_data["sdp"], type=response_data["type"])
)
while True:
time.sleep(2)
logger.info("pc.iceGatheringState: %s" % pc.iceGatheringState)
logger.info("pc.connectionState : %s" % pc.connectionState)
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(run())
来自示例存储库的(全功能)lient.js脚本的相关部分:
var config = {
sdpSemantics: 'unified-plan'
};
pc = new RTCPeerConnection(config);
negotiate();
function negotiate() {
pc.addTransceiver('video', {direction: 'recvonly'});
pc.addTransceiver('audio', {direction: 'recvonly'});
return pc.createOffer().then(function(offer) {
return pc.setLocalDescription(offer);
}).then(function() {
// wait for ICE gathering to complete
return new Promise(function(resolve) {
if (pc.iceGatheringState === 'complete') {
resolve();
} else {
function checkState() {
if (pc.iceGatheringState === 'complete') {
pc.removeEventListener('icegatheringstatechange', checkState);
resolve();
}
}
pc.addEventListener('icegatheringstatechange', checkState);
}
});
}).then(function() {
var offer = pc.localDescription;
return fetch('/offer', {
body: JSON.stringify({
sdp: offer.sdp,
type: offer.type,
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
});
}).then(function(response) {
return response.json();
}).then(function(answer) {
return pc.setRemoteDescription(answer);
}).catch(function(e) {
alert(e);
});
}
我是不是在翻译中遗漏了一些明显的东西,或者我需要采取什么额外的步骤来满足等式中的aiortc方面?另外,人们如何调试这类问题?我用得不多,但到目前为止,WebRTC给我的印象是有点像个黑匣子.
注意:我已经try 在两端显式配置STUN服务器,但似乎没有任何效果,我的理解是aiortc默认使用Google STUN服务器.
环境:
- Debian 11
- Python3.9.2
- AIORTC 1.3.2
最新情况: 通过比较Chrome使用client.js生成的报价和Python使用client.py生成的报价,我在调试这方面可能取得了一些进展.Sdp的内容似乎有一些细微的不同:client.js引用了我的isp提供的IP,而client.py引用了0.0.0.0.我try 过在所有情况下使用显式IP,但这似乎 destruct 了一切.这可能是在转移注意力,但似乎有可能这里的错误配置可能是根本问题.