- Вопрос или проблема
- Предыстория:
- Проблема:
- Сервер: stream.py
- Клиент: client.py
- Что работает:
- Проблема:
- С чем мне нужна помощь:
- Ответ или решение
- Проблема с WebRTC Video Streaming Server на Python: Зацикливание на Получении Кадров
- Введение
- Описание проблемы
- Анализ кода сервера
- Потенциальные причины проблемы
- Предложения по решению
- Заключение
Вопрос или проблема
Предыстория:
Я работаю над сервером видеостриминга WebRTC с использованием aiortc и aiohttp на Python. Цель состоит в том, чтобы сервер захватывал кадры с 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 дорожка для видеостриминга
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 # Временной интервал между кадрами
if not self.cap.isOpened():
logger.error(f"Не удалось открыть поток камеры: {self.camera_url}")
else:
logger.info(f"Поток камеры успешно открыт: {self.camera_url}")
async def recv(self):
"""Захват и обработка кадров с камеры."""
logger.info("Вход в метод recv() для захвата кадра")
await asyncio.sleep(self.frame_interval)
# Захват кадра
ret, frame = self.cap.read()
if not ret:
logger.warning("Не удалось считать кадр, отправляем пустой кадр...")
frame = np.zeros((480, 640, 3), dtype=np.uint8) # Заглушка черный кадр
else:
logger.info("Успешно захвачен кадр с камеры")
# Конвертация кадра для 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("Кадр готов к отправке")
return video_frame
async def offer(request):
"""Обработать входящее предложение WebRTC и отправить ответ."""
try:
logger.info("Получен запрос предложения от клиента")
params = await request.json()
logger.info(f"Разобрано предложение: {params}")
offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
pc = RTCPeerConnection()
async def on_iceconnectionstatechange():
logger.info(f"Состояние ICE соединения: {pc.iceConnectionState}")
if pc.iceConnectionState == "completed":
logger.info("ICE соединение успешно завершено!")
elif pc.iceConnectionState == "failed":
logger.error("ICE соединение провалено.")
pc.on("iceconnectionstatechange", on_iceconnectionstatechange)
# Добавить видеодорожку в соединение
player = VideoProcessorTrack(camera_url="http://129.125.136.20/axis-cgi/mjpg/video.cgi?camera=1", desired_fps=15)
logger.info("Добавление видеодорожки в соединение")
pc.addTrack(player)
# Логировать каждый раз, когда мы получаем запрос на кадр
@pc.on("track")
async def on_track(track):
logger.info(f"Дорожка {track.kind} получена на сервере")
# Обработка обмена предложения/ответа WebRTC
await pc.setRemoteDescription(offer)
answer = await pc.createAnswer()
await pc.setLocalDescription(answer)
logger.info("Локальное описание установлено с ответом")
return web.json_response({"sdp": pc.localDescription.sdp, "type": pc.localDescription.type})
except Exception as e:
logger.error(f"Ошибка в обработке предложения: {e}")
return web.Response(text="Внутренняя ошибка сервера", status=500)
app = web.Application()
app.router.add_post("/offer", offer)
if __name__ == "__main__":
logger.info("Запуск сервера видеостриминга WebRTC...")
web.run_app(app, port=8081)
Клиент: client.py
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"Получение дорожки: {track.kind}")
if track.kind == "video":
cv2.namedWindow("WebRTC Stream", cv2.WINDOW_NORMAL)
while True:
try:
frame = await track.recv() # Получение кадра из дорожки
img = frame.to_ndarray(format="bgr24") # Конвертация кадра в массив numpy
logger.info(f"Кадр получен: {img.shape}")
# Отображение кадра с помощью OpenCV
cv2.imshow("WebRTC Stream", img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break # Выход при нажатии 'q'
except Exception as e:
logger.error(f"Ошибка при обработке кадра: {e}")
break # Прерывание цикла в случае ошибок
cv2.destroyAllWindows() # Закрытие окна по завершении
track.stop()
# Создание предложения WebRTC
offer = await pc.createOffer()
await pc.setLocalDescription(offer)
# Отправка предложения на сервер
offer_payload = {
"sdp": pc.localDescription.sdp,
"type": pc.localDescription.type,
}
logger.info("Отправка предложения на сервер")
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"Ошибка сервера: {response.status}")
return
answer = await response.json()
logger.info("Получен ответ от сервера")
await pc.setRemoteDescription(RTCSessionDescription(sdp=answer["sdp"], type=answer["type"]))
logger.info("Удаленное описание установлено на клиенте")
# Поддержание соединения открытым
await asyncio.Future()
if __name__ == "__main__":
asyncio.run(run_client())
Что работает:
- Клиент успешно отправляет предложение на сервер, а сервер отвечает ответом.
- Переговоры ICE завершаются успешно, и находится валидная пара кандидатов.
- Клиент логирует получение дорожки: видео, указывая, что сервер отправляет видеодорожку.
Проблема:
Несмотря на успешное соединение ICE и обмен медиа-дорожками, клиент зависает на:
frame = await track.recv()
Кажется, что метод recv() в VideoProcessorTrack сервера (который захватывает и отправляет кадры) никогда не срабатывает. Это означает, что клиент ждет кадры, которые никогда не отправляются.
С чем мне нужна помощь:
- Почему метод recv() на сервере не срабатывает?
- Есть ли что-то неправильное в переговорах дорожки WebRTC между клиентом и сервером, что мешает серверу отправлять кадры?
- Как я могу правильно запустить сервер, чтобы он начал отправлять кадры клиенту?
Любая помощь или советы будут очень полезны!
Ответ или решение
Проблема с WebRTC Video Streaming Server на Python: Зацикливание на Получении Кадров
Введение
Вы работаете над сервером видеостриминга с использованием WebRTC, и ваша цель заключается в захвате кадров с IP-камеры, их обработке и передаче клиенту. При этом вы столкнулись с проблемой: несмотря на успешное завершение ICE-торга и получение треков на стороне клиента, клиент не может получить видеокадры, поскольку метод recv()
на сервере, вероятно, не вызывается.
В этой статье мы подробно рассмотрим структуру вашего кода, выявим потенциальные причины проблемы и предложим пути ее решения.
Описание проблемы
Сервер, основанный на ваших кодах stream.py
и client.py
, успешно обрабатывает сигнализацию WebRTC, однако клиент застревает в ожидании:
frame = await track.recv()
Это указывает на то, что метод recv()
в классе VideoProcessorTrack
не вызывается, и клиент ожидает кадры, которые так и не были отправлены.
Анализ кода сервера
Давайте проанализируем ключевые моменты в вашем серверном коде:
-
Инициализация трека: Ваша реализация
VideoProcessorTrack
выглядит корректно. Вы открываете поток камеры и устанавливаете нужные параметры для частоты кадров, что также выглядит разумно. -
Метод
recv()
: Этот метод должен захватывать кадры и отправлять их клиенту. Обратите внимание на операторawait asyncio.sleep(self.frame_interval)
— он заставляет сервер ждать перед захватом следующего кадра, что в целом нормально. Однако, если задержка из-за обработки кадров слишком велика или возникает блокировка, это может привести к застреванию. -
Логирование: У вас есть логирование, что хорошо. Однако, убедитесь, что сообщения об успешном захвате кадров и отправке их клиентам действительно печатаются.
Потенциальные причины проблемы
-
Проблемы с захватом кадров: Убедитесь, что ваша IP-камера действительно доступна и передает данные. Используйте простой скрипт OpenCV для проверки:
import cv2 cap = cv2.VideoCapture("http://129.125.136.20/axis-cgi/mjpg/video.cgi?camera=1") while True: ret, frame = cap.read() if not ret: print("Не удалось захватить кадр") break cv2.imshow("Frame", frame) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()
-
Отсутствие вызова метода
recv()
: Если не происходит вызов методаrecv()
из-за неправильного состояния WebRTC-соединения или событий ICE, это также может стать причиной проблемы. Убедитесь, что вы правильно создали и добавили трек к объектуRTCPeerConnection
. -
Задержка в I/O операциях: Возможно, есть блокировки в вашем коде, которые мешают выполнению
recv()
. Если у вас есть другие задачи вasyncio
, попробуйте их отключить и протестировать только видеопоток.
Предложения по решению
-
Отладка метода
recv()
: Добавьте больше логов внутри методаrecv()
, чтобы видеть, вызывается ли он вообще на сервере. Если он не вызывается, возможно, стоит проверить создание и регистрацию трека. -
Асинхронность: Убедитесь, что функции не блокируют основной поток. Используйте
asyncio
для управления асинхронностью должным образом. -
Упрощение формы: Для тестирования можно временно упростить серверный код, исключив часть логики и оставить только базовый опыт с
recv()
, чтобы проверить, действительно ли проблема кроется в самом WebRTC или в других частях кода. -
Проверка на стороне клиента: Убедитесь, что клиент будет готов получать кадры. Это подразумевает, что клиент присоединился к серверу и установил все необходимые соединения.
Заключение
Работа с WebRTC может быть сложной из-за асинхронности и неопределенности сетевых состояний. Однако, следуя указанным шагам и предложенным решениям, вы сможете выявить и устранить проблему, из-за которой ваш видео стриминг застревает на стадии получения кадров. Будьте внимательны к логам и экспериментируйте с кодом, чтобы найти оптимальное решение. Надеюсь, что эти советы помогут вам успешно завершить вашу реализацию видеостриминга.