Обробка потокового відео RTSP у реальному часі
| Поле | Значення |
|---|---|
| Категорія | Камери та потокове передавання |
| Складність | Середній |
| Орієнтовний час читання | 5-10 minutes |
| Мітки | rtsp, h264, h265, streaming, input-group, live-input |
Це перший розділ, де вхідні дані надходять ззовні програми. У попередніх розділах створювалися тестові зображення або зчитувалися файли з диска; тут кадри надходять безперервно з мережевого потоку, і ви обробляєте їх так швидко, як тільки можете. Механізм реалізовано у вигляді багаторазово використовуваного фрагмента Graph, RtspDecodedInput, який об’єднує весь початковий етап обробки RTSP-потоку в необроблені кадри за допомогою єдиного інтерфейсу.
У цьому розділі навмисно зупиняються на етапі «отримання декодованих кадрів». Подальша обробка цих кадрів за допомогою Model описана в інших розділах (001 – для одноразов ого запуску моделі, 007 – для інтеграції моделі в конвеєр, 015 – для вбудовування моделі в граф). Наприкінці ви підключитеся до URL-адреси RTSP і виведете розмір тензора кожного декодованого кадру, що підтвердить наявність потоку даних.
Це лише споживач. Щоб опублікувати потік, запустіть окремий RTSP-сервер (наприклад, mediamtx) і вкажіть --url на нього.
Покроковий огляд
Налаштування RTSP-клієнта
RtspDecodedInputOptions налаштовує джерело та декодер. url вибирає
rtsp://... джерело. codec вибирає кодек для кодування. H.264 є кодеком за замовчуванням;
у цьому посібнику також підтримуються avc, h265 і hevc, де AVC еквівалентний H.264, а HEVC еквівалентний H.265.
Встановіть source_fps, якщо ви вже знаєте частоту кадрів джерела. Якщо ви не вкажете це значення, цей посібник відкриє джерело RTSP за допомогою OpenCV, зчитає заявлену частоту кадрів і передасть виявлене значення в RtspDecodedInput. Сама група не перевіряє URL-адресу. Лише для перевірки шляху потрібен OpenCV, а версія Python імпортує його за потреби, тому передача --source-fps працює без нього; для перевірки встановіть його за допомогою pip install opencv-python. Для H.265, Neat передає це значення в параметри розібраного потоку та конфігурацію декодера. Це не змінює частоту кадрів. Потік H.265 повинен використовувати профіль HEVC Main, 8 біт, вхід 4:2:0.
Встановлення tcp = true забезпечує передачу RTP через TCP. TCP зберігає порядок і повторно передає втрачені сегменти, що може зменшити видимі втрати порівняно з UDP, але може збільшити затримку під час відновлення втрачених даних.
// Configure the URL, codec, source cadence, and RTSP transport.
simaai::neat::nodes::groups::RtspDecodedInputOptions rtsp_opt;
rtsp_opt.url = url;
rtsp_opt.codec = parse_codec(codec_name);
rtsp_opt.source_fps = source_fps;
rtsp_opt.tcp = true;
Складіть граф
Створіть Graph лише з двох етапів: фрагмент RtspDecodedInput (джерело) і простий Output вузол (кінцева точка для отримання даних). Додавання фрагмента – це єдина операція add(...); він внутрішньо розширюється до елементів з’єднання/розпакування/декодування, тому ваша композиція залишається н а рівні задуму. Оскільки вхідні дані генеруються всередині конвеєра, ми використовуємо перевантажену версію build(RunOptions{}), яка не приймає початковий зразок: немає потреби передавати build() початковий кадр, оскільки потік генерує їх.
// Build a Graph whose only stages are the RTSP group and an Output node.
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::groups::RtspDecodedInput(rtsp_opt));
graph.add(simaai::neat::nodes::Output());
auto run = graph.build(simaai::neat::RunOptions{});
Отримання декодованих кадрів
Після запуску конвеєра, у циклі виконуйте pull(...) з тайм-аутом. Кожне успішне отримання даних повертає Sample, тензор якого є одним декодованим к адром. У цьому навчальному посібнику використовується вихід NV12 за замовчуванням, представлений як логічний [H, W] тензор з метаданими площин Y і UV. Якщо отримання даних не повертає нічого (або повертає порожній тензор), виводиться повідомлення frame=N rtsp_timeout і цикл переривається – це зазвичай означає, що URL-адреса неправильна або потік не передає дані. Тайм-аут запобігає зависанню програми через непрацюючий потік.
Кадр витягується за допомогою tensors_from_sample(*sample, true); цикл перевіряє наявність порожнього списку перед читанням shape.
for (int i = 0; i < frames; ++i) {
auto sample = run.pull(/*timeout_ms=*/5000);
if (!sample.has_value() || simaai::neat::tensors_from_sample(*sample, true).empty()) {
std::cout << "frame=" << i << " rtsp_timeout\n";
break;
}
const auto tensors = simaai::neat::tensors_from_sample(*sample, true);
const auto& shape = tensors.front().shape;
std::cout << "frame=" << i << " shape=[";
for (std::size_t d = 0; d < shape.size(); ++d) {
std::cout << shape[d] << (d + 1 < shape.size() ? ", " : "");
}
std::cout << "]\n";
}
Запуск
У цьому розділі використовується потоковий сигнал RTSP у реальному часі, тому ви повинні надати доступний
--urlЯкщо у вас немає камери, опублікуйте сумісне відео через RTSP-сервер і вкажіть. --url у ньому. Запустіть команди Python і C++ (попередньо скомпільовані)
з __Neat встановити в кореневу директорію **(директорію, яка містить) share/ і
lib/); запустіть команди збірки з вихідного коду з кореневої директорії репозиторію.
Автоматизований тест регресії запускає обидва кодеки. Він зчитує перше доступне
URL-адресу з SIMANEAT_TEST_RTSP_H264_URL або SIMANEAT_TEST_RTSP_H264_URLSі
з SIMANEAT_TEST_RTSP_H265_URL або SIMANEAT_TEST_RTSP_H265_URLS. Тест
перевіряє кожне джерело та передає виявлену кількість кадрів за секунду (FPS) до групи RTSP.
C++ (prebuilt):
./lib/sima-neat/tutorials/tutorial_018_consume_rtsp_stream \
--url rtsp://host:port/stream --codec h265 --source-fps 30 --frames 5
C++ (build from source):
./build.sh --target tutorial_018_consume_rtsp_stream
./build/tutorials-standalone/tutorial_018_consume_rtsp_stream \
--url rtsp://host:port/stream --codec h265 --source-fps 30 --frames 5
Очікуваний результат (форма залежить від роздільної здатності потоку та формату декодера):
frame=0 shape=[720, 1280]
frame=1 shape=[720, 1280]
frame=2 shape=[720, 1280]
frame=3 shape=[720, 1280]
frame=4 shape=[720, 1280]
Якщо до потоку неможливо отримати доступ, замість нього ви побачите frame=0 rtsp_timeout. Щоб інтегрувати вихідний код C++ з цього розділу у власний проєкт за допомогою спеціального файлу CMakeLists.txt (додаткова тека не потрібна), див. розділ Як запускати навчальні матеріали на головній сторінці.
Повний початковий код
Показати повні програми
// Consume a live H.264 or H.265 RTSP stream via RtspDecodedInput.
//
// The fragment handles RTSP connect, codec-specific depacketize/parse, and
// hardware decode. This chapter is about the input fragment only.
//
// Usage:
// tutorial_018_consume_rtsp_stream --url rtsp://host/path
// [--codec h264|avc|h265|hevc] [--source-fps 30] [--frames 5]
#include "neat.h"
#include "nodes/groups/RtspDecodedInput.h"
#include <opencv2/videoio.hpp>
#include <cmath>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <string>
namespace {
bool get_arg(int argc, char** argv, const std::string& key, std::string& out) {
for (int i = 1; i + 1 < argc; ++i) {
if (key == argv[i]) {
out = argv[i + 1];
return true;
}
}
return false;
}
int parse_int_arg(int argc, char** argv, const std::string& key, int def) {
std::string v;
if (!get_arg(argc, argv, key, v))
return def;
return std::stoi(v);
}
simaai::neat::nodes::groups::RtspCodec parse_codec(const std::string& value) {
using simaai::neat::nodes::groups::RtspCodec;
if (value == "h264" || value == "avc")
return RtspCodec::H264;
if (value == "h265" || value == "hevc")
return RtspCodec::H265;
throw std::invalid_argument("--codec must be h264, avc, h265, or hevc");
}
int probe_source_fps(const std::string& url) {
cv::VideoCapture capture(url);
if (!capture.isOpened()) {
throw std::runtime_error("failed to open RTSP source for FPS probe");
}
const int fps = static_cast<int>(std::lround(capture.get(cv::CAP_PROP_FPS)));
capture.release();
if (fps <= 0) {
throw std::runtime_error("failed to probe a positive RTSP source FPS");
}
return fps;
}
} // namespace
int main(int argc, char** argv) {
try {
std::string url;
if (!get_arg(argc, argv, "--url", url)) {
std::cerr << "Usage: tutorial_018_consume_rtsp_stream --url <rtsp://...> "
"[--codec h264|avc|h265|hevc] [--source-fps <n>] [--frames <n>]\n";
return 1;
}
std::string codec_name = "h264";
get_arg(argc, argv, "--codec", codec_name);
const int frames = parse_int_arg(argc, argv, "--frames", 5);
int source_fps = parse_int_arg(argc, argv, "--source-fps", -1);
if (source_fps != -1 && source_fps <= 0) {
throw std::invalid_argument("--source-fps must be positive");
}
if (source_fps == -1) {
source_fps = probe_source_fps(url);
}
// CORE LOGIC
// Configure the URL, codec, source cadence, and RTSP transport.
simaai::neat::nodes::groups::RtspDecodedInputOptions rtsp_opt;
rtsp_opt.url = url;
rtsp_opt.codec = parse_codec(codec_name);
rtsp_opt.source_fps = source_fps;
rtsp_opt.tcp = true;
// Build a Graph whose only stages are the RTSP group and an Output node.
simaai::neat::Graph graph;
graph.add(simaai::neat::nodes::groups::RtspDecodedInput(rtsp_opt));
graph.add(simaai::neat::nodes::Output());
auto run = graph.build(simaai::neat::RunOptions{});
for (int i = 0; i < frames; ++i) {
auto sample = run.pull(/*timeout_ms=*/5000);
if (!sample.has_value() || simaai::neat::tensors_from_sample(*sample, true).empty()) {
std::cout << "frame=" << i << " rtsp_timeout\n";
break;
}
const auto tensors = simaai::neat::tensors_from_sample(*sample, true);
const auto& shape = tensors.front().shape;
std::cout << "frame=" << i << " shape=[";
for (std::size_t d = 0; d < shape.size(); ++d) {
std::cout << shape[d] << (d + 1 < shape.size() ? ", " : "");
}
std::cout << "]\n";
}
return 0;
} catch (const std::exception& e) {
std::cerr << "[FAIL] " << e.what() << "\n";
return 1;
}
}