我正在组装一个原型,它可以通过aiortc使用WebRTC将视频从一个Python脚本传输到另一个脚本.

我重复了来自Python aiortc存储库的the webcam个示例(使用--play-from标志从视频文件而不是网络摄像头中流),并try 将client.js脚本移植到Python.我在那里是mostly,但ICE连接从未(成功)完成,并在大约30秒后从IN_PROGRESSFAILED.我接收并可以订阅客户端脚本中的曲目,但从未开始从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);
    });
}

enter image description here

我是不是在翻译中遗漏了一些明显的东西,或者我需要采取什么额外的步骤来满足等式中的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 了一切.这可能是在转移注意力,但似乎有可能这里的错误配置可能是根本问题.

推荐答案

在Ayncio run routine 中设置为time.sleep会阻塞主线程并冻结事件循环.

你应该用await asyncio.sleep(2)来代替.

Test

快速测试您的示例代码:然后打印您的client.py代码:

2022-08-07 14:39:42.291 | INFO | __main__:run:105 - pc.iceGatheringState: complete
2022-08-07 14:39:42.291 | INFO | __main__:run:106 - pc.connectionState : connected

同样,webcam.py随后打印到调试控制台:

INFO:aioice.ice:connection(0) ICE completed
Connection state is connected

Javascript相关问答推荐

单击子元素时关闭父元素(JS)

Javascript,部分重排序数组

togglePopover()不打开但不关闭原生HTML popover'

MongoDB中的引用

角色 map 集/spritebook动画,用户输入不停止在键上相位器3

将自定义排序应用于角形数字数组

WebRTC关闭navigator. getUserMedia正确

setcallback是什么时候放到macrotask队列上的?

显示图—如何在图例项上添加删除线效果?

如何使用子字符串在数组中搜索重复项

Use Location位置成员在HashRouter内为空

钛中的onClick事件需要在两次点击之间等待几秒钟

面对代码中的错误作为前端与后端的集成

如何在一个对象Java脚本中获取不同键的重复值?

react -原生向量-图标笔划宽度

在JavaScript中,有没有一种方法可以迭代字符串的词法标记?

AG-GRIDreact 显示布尔值而不是复选框

Chart.js Hover线条在鼠标离开时不会消失

如何在Web项目中同步语音合成和文本 colored颜色 更改

脚本语法错误只是一个字符串,而不是一个对象?