为了账号安全,请及时绑定邮箱和手机立即绑定

使用Python和WebRTC打造实时视频流应用

The image depicts a sequence diagram illustrating the process of establishing a WebRTC connection between a local machine and a remote server.

点对点的, 直接通信

想深入了解代码,请查看 GitHub 上的实时视频流媒体项目仓库 (https://github.com/eknathmali/Real-Time-Video-Streaming-with-WebRTC-and-Python)。

接下来是介绍:让我们开始吧

在这篇文章中,我将指导你完成使用Python设置WebRTC连接的过程,以便从一台机器向另一台机器传输摄像头画面。WebRTC(一种Web实时通信技术)支持点对点通讯,非常适合实时视频流等任务。学完这篇教程后,你将拥有一个从远程服务器到本地机器传输视频帧的工作示例。

前提条件:

  • 已在发送方和接收方的机器上安装了Python。
  • 了解基本的Python编程。
  • 能够访问终端或命令行界面。

设置环境指南:

首先,我们需要安装必要的库。可以使用以下命令行来安装所需的依赖:

请运行以下命令来安装aiortc和opencv-python库:

pip install aiortc opencv-python
发送方脚本( 远程计算机)

发送方程序从摄像头获取视频帧并将视频帧发送给接收方。下面是sender.py代码:

import asyncio  
import cv2  
from aiortc import RTCPeerConnection, RTCSessionDescription, VideoStreamTrack  
from aiortc.contrib.signaling import TcpSocketSignaling  
from av import VideoFrame  
import fractions  
from datetime import datetime  

class CustomVideoStreamTrack(VideoStreamTrack):  
    def __init__(self, camera_id):  
        super().__init__()  
        self.cap = cv2.VideoCapture(camera_id)  
        self.frame_count = 0  

    async def recv(self):  
        self.frame_count += 1  
        print(f"发送帧 {self.frame_count}")  
        ret, frame = self.cap.read()  
        if not ret:  
            print("从摄像头读取帧失败")  
            return None  
        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  
        video_frame = VideoFrame.from_ndarray(frame, format="rgb24")  
        video_frame.pts = self.frame_count  
        video_frame.time_base = fractions.Fraction(1, 30)  # 使用分数来设置时间基准  
        # 添加时间戳到帧  
        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]  # 包含毫秒的当前时间  
        cv2.putText(frame, timestamp, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2, cv2.LINE_AA)  

        frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  
        video_frame = VideoFrame.from_ndarray(frame, format="rgb24")  
        video_frame.pts = self.frame_count  
        video_frame.time_base = fractions.Fraction(1, 30)  # 使用分数来设置时间基准  
        return video_frame  

async def setup_webrtc_and_run(ip_address, port, camera_id):  
    signaling = TcpSocketSignaling(ip_address, port)  
    pc = RTCPeerConnection()  
    video_sender = CustomVideoStreamTrack(camera_id)  
    pc.addTrack(video_sender)  

    try:  
        await signaling.connect()  

        @pc.on("datachannel")  
        def on_datachannel(channel):  
            print(f"数据信道建立: {channel.label}")  

        @pc.on("connectionstatechange")  
        async def on_connectionstatechange():  
            print(f"连接状态是 {pc.connectionState}")  
            if pc.connectionState == "connected":  
                print("WebRTC 连接成功建立")  

        offer = await pc.createOffer()  
        await pc.setLocalDescription(offer)  
        await signaling.send(pc.localDescription)  

        while True:  
            obj = await signaling.receive()  
            if isinstance(obj, RTCSessionDescription):  
                await pc.setRemoteDescription(obj)  
                print("远端描述设置完成")  
            elif obj is None:  
                print("信令结束")  
                break  
        print("关闭连接")  
    finally:  
        await pc.close()  

async def main():  
    ip_address = "xxx.xxx.xx.xx"  # 远端服务器/机器的 IP 地址  
    port = 9999  
    camera_id = 2  # 将此更改为适当的摄像头ID  
    await setup_webrtc_and_run(ip_address, port, camera_id)  

if __name__ == "__main__":  
    asyncio.run(main())
接收脚本:(本地设备)

接收端脚本从发送方接收视频帧并显示这些帧。下面是receiver.py脚本

    import asyncio  
    import cv2  
    import numpy as np  
    from aiortc import RTCPeerConnection, RTCSessionDescription, MediaStreamTrack  
    from aiortc.contrib.signaling import TcpSocketSignaling  
    from av import VideoFrame  
    from datetime import datetime, timedelta  

    class VideoReceiver:  
        def __init__(self):  
            self.track = None  

        async def handle_track(self, track):  
            print("处理帧")  
            self.track = track  
            frame_count = 0  
            while True:  
                try:  
                    print("等待帧...")  
                    frame = await asyncio.wait_for(track.recv(), timeout=5.0)  
                    frame_count += 1  
                    print(f"接收第 {frame_count} 帧")  

                    if isinstance(frame, VideoFrame):  
                        print(f"帧类型: VideoFrame, pts: {frame.pts}, time_base: {frame.time_base}")  
                        frame = frame.to_ndarray(format="bgr24")  
                    elif isinstance(frame, np.ndarray):  
                        print(f"帧类型: numpy 数组")  
                    else:  
                        print(f"意外的帧类型: {type(frame)}")  
                        continue  

                     # 在帧上添加时间戳  
                    current_time = datetime.now()  
                    new_time = current_time - timedelta(seconds=55)  
                    timestamp = new_time.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]  
                    cv2.putText(frame, timestamp, (10, frame.shape[0] - 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2, cv2.LINE_AA)  
                    cv2.imwrite(f"imgs/received_frame_{frame_count}.jpg", frame)  
                    print(f"将帧 {frame_count} 保存至文件")  
                    cv2.imshow("Frame", frame)  

                    # 按 'q' 键退出程序  
                    if cv2.waitKey(1) & 0xFF == ord('q'):  
                        break  
                except asyncio.TimeoutError:  
                    print("等待帧超时,继续等待...")  
                except Exception as e:  
                    print(f"handle_track 错误: {str(e)}")  
                    if "Connection" in str(e):  
                        break  
            print("退出 handle_track")  
    async def run(pc, signaling):  
        await signaling.connect()  

        @pc.on("track")  
        def on_track(track):  
            if isinstance(track, MediaStreamTrack):  
                print(f"接收 {track.kind} 类型的轨道")  
                asyncio.ensure_future(video_receiver.handle_track(track))  

        @pc.on("datachannel")  
        def on_datachannel(channel):  
            print(f"数据通道 {channel.label} 已建立")  

        @pc.on("connectionstatechange")  
        async def on_connectionstatechange():  
            print(f"连接状态是 {pc.connectionState}")  
            if pc.connectionState == "connected":  
                print("WebRTC 连接成功建立")  

        print("等待发送方的 offer...")  
        offer = await signaling.receive()  
        print("接收 offer")  
        await pc.setRemoteDescription(offer)  
        print("设置远端描述")  

        answer = await pc.createAnswer()  
        print("创建 answer")  
        await pc.setLocalDescription(answer)  
        print("设置本地描述")  

        await signaling.send(pc.localDescription)  
        print("将 answer 发回给发送方")  

        print("等待连接建立...")  
        while pc.connectionState != "connected":  
            await asyncio.sleep(0.1)  

        print("连接建立,等待帧...")  
        await asyncio.sleep(100)  # 等待接收帧 35 秒  

        print("关闭连接")  

    async def main():  
        signaling = TcpSocketSignaling("192.168.30.40", 9999)  
        pc = RTCPeerConnection()  

        global video_receiver  
        video_receiver = VideoReceiver()  

        try:  
            await run(pc, signaling)  
        except Exception as e:  
            print(f"main 出现错误: {str(e)}")  
        finally:  
            print("关闭 peer 连接")  
            await pc.close()  

    if __name__ == "__main__":  
        asyncio.run(main())

接下来,运行这些脚本吧:

  1. 在带有摄像头的远程计算机/设备上运行发送程序命令:
运行Python脚本
python sender.py

2. 在你想要显示视频帧的机器上运行接收脚本程序。

    python receiver.py

这条命令是用来运行一个名为receiver.py的Python脚本。

最后

恭喜!您已成功建立了一个WebRTC连接,使用Python从一台机器向另一台机器实时传输摄像头画面。这个例子可以作为开发更高级应用程序的基础,例如远程监控和视频会议。您可以根据需要对此项目进行自定义和扩展。

更多资源:

WebRTC 是一个开放框架,它让浏览器支持实时通信(RTC)功能。webrtc.org 示例 - aiortc 文档提供了一些示例,这些示例非常适合初学者上手。 OpenCV: OpenCV模块库 - 编辑描述

联系方式:

请联系我们一下,如有问题或意见,请您提出。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消