Я работаю над сервером потокового видео WebRTC, используя aiortc и aiohttp. > на питоне. Цель состоит в том, чтобы сервер захватывал кадры с IP-камеры, обрабатывал их и затем передавал их клиенту. Для целей тестирования это упрощенная версия кода, в которой кадры захватываются с IP-камеры и напрямую передаются клиенту без дополнительной обработки (в оригинальной версии кадры проходят обработку перед отправкой клиенту).
Проблема:
Несмотря на успешное согласование ICE и прием трека на стороне клиента, клиент зависает при попытке получить видеокадры, и кажется, что серверный метод Recv() (который захватывает и отправляет кадры) никогда не запускается.
Я объясню настройку и поделюсь соответствующим кодом как для сервера (stream.py), так и для клиента. (client.py).
Сервер: stream.py
Код: Выделить всё
import cv2
import asyncio
import logging
import time
import numpy as np
from aiortc import VideoStreamTrack, RTCPeerConnection, RTCSessionDescription
from aiortc.mediastreams import VideoFrame
from aiohttp import web
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("WebRTC")
# WebRTC track for video streaming
class VideoProcessorTrack(VideoStreamTrack):
def __init__(self, camera_url, desired_fps=15):
super().__init__()
self.camera_url = camera_url
self.cap = cv2.VideoCapture(self.camera_url)
self.desired_fps = desired_fps
self.frame_interval = 1.0 / self.desired_fps # Time interval between frames
if not self.cap.isOpened():
logger.error(f"Failed to open camera stream: {self.camera_url}")
else:
logger.info(f"Camera stream opened successfully: {self.camera_url}")
async def recv(self):
"""Capture and process frames from the camera."""
logger.info("Entering recv() method to capture frame")
await asyncio.sleep(self.frame_interval)
# Capture frame
ret, frame = self.cap.read()
if not ret:
logger.warning("Failed to read frame, sending empty frame...")
frame = np.zeros((480, 640, 3), dtype=np.uint8) # Dummy black frame
else:
logger.info("Successfully captured a frame from the camera")
# Convert frame for WebRTC
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
video_frame = VideoFrame.from_ndarray(frame_rgb, format="rgb24")
video_frame.pts = time.time()
video_frame.time_base = 1 / self.desired_fps
logger.info("Frame is ready to be sent")
return video_frame
async def offer(request):
"""Handle incoming WebRTC offer and send the answer."""
try:
logger.info("Received offer request from the client")
params = await request.json()
logger.info(f"Parsed offer: {params}")
offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
pc = RTCPeerConnection()
async def on_iceconnectionstatechange():
logger.info(f"ICE connection state is {pc.iceConnectionState}")
if pc.iceConnectionState == "completed":
logger.info("ICE connection completed successfully!")
elif pc.iceConnectionState == "failed":
logger.error("ICE connection failed.")
pc.on("iceconnectionstatechange", on_iceconnectionstatechange)
# Add video track to peer connection
player = VideoProcessorTrack(camera_url="http://129.125.136.20/axis-cgi/mjpg/video.cgi?camera=1", desired_fps=15)
logger.info("Adding video track to peer connection")
pc.addTrack(player)
# Log every time we receive a frame request
@pc.on("track")
async def on_track(track):
logger.info(f"Track {track.kind} received on the server")
# Handle the WebRTC offer/answer exchange
await pc.setRemoteDescription(offer)
answer = await pc.createAnswer()
await pc.setLocalDescription(answer)
logger.info("Local description set with the answer")
return web.json_response({"sdp": pc.localDescription.sdp, "type": pc.localDescription.type})
except Exception as e:
logger.error(f"Error in offer handling: {e}")
return web.Response(text="Internal Server Error", status=500)
app = web.Application()
app.router.add_post("/offer", offer)
if __name__ == "__main__":
logger.info("Starting WebRTC streaming server...")
web.run_app(app, port=8081)
Код: Выделить всё
import asyncio
import cv2
from aiortc import RTCPeerConnection, RTCSessionDescription, MediaStreamTrack
import aiohttp
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("WebRTC Client")
class DummyVideoTrack(MediaStreamTrack):
kind = "video"
async def recv(self):
return None
async def run_client():
pc = RTCPeerConnection()
dummy_track = DummyVideoTrack()
pc.addTrack(dummy_track)
@pc.on("track")
async def on_track(track):
logger.info(f"Receiving track: {track.kind}")
if track.kind == "video":
cv2.namedWindow("WebRTC Stream", cv2.WINDOW_NORMAL)
while True:
try:
frame = await track.recv() # Receive frame from track
img = frame.to_ndarray(format="bgr24") # Convert frame to numpy array
logger.info(f"Frame received: {img.shape}")
# Display the frame using OpenCV
cv2.imshow("WebRTC Stream", img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break # Exit on 'q'
except Exception as e:
logger.error(f"Error processing frame: {e}")
break # Break loop in case of errors
cv2.destroyAllWindows() # Close the window when done
track.stop()
# Create WebRTC offer
offer = await pc.createOffer()
await pc.setLocalDescription(offer)
# Send offer to server
offer_payload = {
"sdp": pc.localDescription.sdp,
"type": pc.localDescription.type,
}
logger.info("Sending offer to server")
async with aiohttp.ClientSession() as session:
async with session.post("http://localhost:8081/offer", json=offer_payload) as response:
if response.status != 200:
logger.error(f"Server error: {response.status}")
return
answer = await response.json()
logger.info("Received answer from server")
await pc.setRemoteDescription(RTCSessionDescription(sdp=answer["sdp"], type=answer["type"]))
logger.info("Remote description set on client")
# Keep connection open
await asyncio.Future()
if __name__ == "__main__":
asyncio.run(run_client())
- Клиент успешно отправляет предложение на сервер, и сервер
отвечает с ответом. - Согласование ICE завершается успешно, и найдена допустимая пара-кандидат.
- Клиент регистрирует дорожку приема: видео с указанием сервера отправляет видеодорожку.
Несмотря на успешное соединение ICE и обмен медиа-дорожками, клиент застревает на:
Код: Выделить всё
frame = await track.recv()
В чем мне нужна помощь:
- Почему не запускается ли метод Recv() на сервере?
- Что-то не так с согласованием отслеживания WebRTC между клиентом и сервером, что не позволяет серверу отправлять кадры?
Как правильно заставить сервер начать отправку кадров клиенту?
Подробнее здесь: https://stackoverflow.com/questions/790 ... ing-frames