техническое образование
Будь то платформа Windows или Linux, требования к многоканальному воспроизведению очень распространены, например, отображение камеры в макросценариях, таких как умные строительные площадки, выставочные залы, образование и т. д. Для моментов, на которые необходимо обратить внимание при разработке проигрывателей прямых трансляций RTSP или RTMP, пожалуйста, обратитесь к предыдущему блогу.Там есть следующие моменты:
1. Низкая задержка:Большинство воспроизведений RTSP ориентировано на сценарии прямой трансляции, поэтому, если задержка слишком велика, например, в индустрии видеонаблюдения, клиент увидит вора только после того, как все воры уйдут, или хост увидит изображение только после того, как кто-то звонил в дверь в течение нескольких секунд, что серьезно повлияет на опыт. , поэтому низкая задержка является очень важным показателем для измерения хорошего RTSP-плеера. В настоящее время задержка воспроизведения RTSP в Daniel Live SDK контролируется на уровне нескольких сотен миллисекунд, а VLC — в несколько секунд.долгий, низкая задержка, например, бег на 1 день, неделю, месяц и даже дольше;
**2. Аудио- и видеосинхронизация или скачки: ** Некоторые разработчики даже не выполняют аудио- и видеосинхронизацию в погоне за малой задержкой, а воспроизводят напрямую аудио-видео, что приводит к синхронизации аудио/видео и скачкам временных меток;
**3.Поддержка нескольких экземпляров: **Хороший проигрыватель должен поддерживать одновременное воспроизведение нескольких аудио- и видеоданных, например 4-8-9-16-32 окон;
4. Поддержка настройки буферного времени: в некоторых сценариях с дрожанием сети проигрыватель должен поддерживать точные настройки времени буферизации, обычно в миллисекундах;
** 5. Воспроизведение и запись H.265: ** В дополнение к H.264 также необходимо поддерживать H. 265. В настоящее время на рынке появляется все больше и больше камер RTSP H.265 и проигрывателя RTSP. не за горами поддержка H.265.Кроме того, просто воспроизводить H.265 недостаточно, необходимо также иметь возможность записывать данные H.265;
**6. Переключение режима TCP/UDP:** Учитывая, что многие серверы поддерживают только режим TCP или UDP, хороший проигрыватель RTSP должен поддерживать автоматическое переключение режима TCP/UDP;
** 7. Поддержка отключения звука: ** Например, при воспроизведении потоков RTSP в нескольких окнах, если воспроизводится каждый звук, впечатление очень плохое, поэтому функция отключения звука в реальном времени очень необходима;
**8. Вращение просмотра видео: большинство камер вызывают инвертирование изображения из-за ограничений установки, поэтому хороший проигрыватель RTSP должен поддерживать, например, вращение просмотра видео в реальном времени (0° 90° 180° 270°), горизонтальную инверсию, вертикальный реверс;
**9.Поддержка вывода аудио/видеоданных после декодирования (опционально): **Daniu Live SDK связался со многими разработчиками, надеясь получить данные YUV или RGB во время воспроизведения и выполнить алгоритмический анализ, такой как сопоставление лиц, поэтому аудио и видео обратные вызовы необязательны;
**10.Снимок: **Интересные или важные снимки, очень важно делать их в режиме реального времени;
**11. Обработка сетевого джиттера (например, отключение и повторное подключение): **Основные функции, без подробностей;
**12. Кроссплатформенность:** нужен хороший плеер, кроссплатформенность (Windows/Android/iOS), хотя бы ради последующей масштабируемости, при разработке есть соображения на этот счет. SDK RTSP-плеер отлично поддерживает вышеперечисленные платформы;
13. Долгосрочная стабильность работы: Что касается стабильности, многие разработчики не согласны. На самом деле, для хорошего продукта стабильность является самой основной предпосылкой, и ее нельзя игнорировать!
**14.Запись видео: **В процессе воспроизведения вы в любой момент можете записывать интересующие вас видеофрагменты, архивировать или выполнять другую вторичную обработку;
** 15. Запись информации журнала: ** Общий механизм процесса возвращается в режиме реального времени, не так много журналов, но не некоторые важные журналы, такие как ошибки во время воспроизведения;
** 16. Обратная связь по скорости загрузки в режиме реального времени: ** Вы можете видеть обратную связь по скорости загрузки в режиме реального времени, чтобы отслеживать состояние сети;
**17. Обработка ненормального состояния: ** Например, обработка различных сценариев, таких как отключение сети, дрожание сети, входящие вызовы и возврат после переключения в фоновый режим во время воспроизведения.
Код
В этой статье используется Daniel Live SDK (официальный) Платформа Linux в качестве примера для внедрения интеграции потокового мультиплексирования RTMP или RTSP.
int main(int argc, char *argv[])
{
XInitThreads(); // X支持多线程, 必须调用
NT_SDKLogInit();
// SDK初始化
SmartPlayerSDKAPI player_api;
if (!NT_PlayerSDKInit(player_api))
{
fprintf(stderr, "SDK init failed.\n");
return 0;
}
auto display = XOpenDisplay(nullptr);
if (!display)
{
fprintf(stderr, "Cannot connect to X server\n");
player_api.UnInit();
return 0;
}
auto screen = DefaultScreen(display);
auto root = XRootWindow(display, screen);
XWindowAttributes root_win_att;
if (!XGetWindowAttributes(display, root, &root_win_att))
{
fprintf(stderr, "Get Root window attri failed\n");
player_api.UnInit();
XCloseDisplay(display);
return 0;
}
if (root_win_att.width < 100 || root_win_att.height < 100)
{
fprintf(stderr, "Root window size error.\n");
player_api.UnInit();
XCloseDisplay(display);
return 0;
}
fprintf(stdout, "Root Window Size:%d*%d\n", root_win_att.width, root_win_att.height);
int main_w = root_win_att.width / 2, main_h = root_win_att.height/2;
auto black_pixel = BlackPixel(display, screen);
auto white_pixel = WhitePixel(display, screen);
auto main_wid = XCreateSimpleWindow(display, root, 0, 0, main_w, main_h, 0, white_pixel, black_pixel);
if (!main_wid)
{
player_api.UnInit();
XCloseDisplay(display);
fprintf(stderr, "Cannot create main windows\n");
return 0;
}
XSelectInput(display, main_wid, StructureNotifyMask | KeyPressMask);
XMapWindow(display, main_wid);
XStoreName(display, main_wid, win_base_title);
std::vector<std::shared_ptr<NT_PlayerSDKWrapper> > players;
for (auto url: players_url_)
{
auto i = std::make_shared<NT_PlayerSDKWrapper>(&player_api);
i->SetDisplay(display);
i->SetScreen(screen);
i->SetURL(url);
players.push_back(i);
if ( players.size() > 3 )
break;
}
auto border_w = 2;
std::vector<NT_LayoutRect> layout_rects;
SubWindowsLayout(main_w, main_h, border_w, static_cast<int>(players.size()), layout_rects);
for (auto i = 0; i < static_cast<int>(players.size()); ++i)
{
assert(players[i]);
players[i]->SetWindow(CreateSubWindow(display, screen, main_wid, layout_rects[i], border_w));
}
for (const auto& i : players)
{
assert(i);
if (i->GetWindow())
XMapWindow(display, i->GetWindow());
}
for (auto i = 0; i < static_cast<int>(players.size()); ++i)
{
assert(players[i]);
// 第一路不静音, 其他全部静音
players[i]->Start(0, i!=0, 1, false);
//players[i]->Start(0, false, 1, false);
}
while (true)
{
while (MY_X11_Pending(display, 10))
{
XEvent xev;
memset(&xev, 0, sizeof(xev));
XNextEvent(display, &xev);
if (xev.type == ConfigureNotify)
{
if (xev.xconfigure.window == main_wid)
{
if (xev.xconfigure.width != main_w || xev.xconfigure.height != main_h)
{
main_w = xev.xconfigure.width;
main_h = xev.xconfigure.height;
SubWindowsLayout(main_w, main_h, border_w, static_cast<int>(players.size()), layout_rects);
for (auto i = 0; i < static_cast<int>(players.size()); ++i)
{
if (players[i]->GetWindow())
{
XMoveResizeWindow(display, players[i]->GetWindow(), layout_rects[i].x_, layout_rects[i].y_, layout_rects[i].w_, layout_rects[i].h_);
}
}
}
}
else
{
for (const auto& i: players)
{
assert(i);
if (i->GetWindow() && i->GetWindow() == xev.xconfigure.window)
{
i->OnWindowSize(xev.xconfigure.width, xev.xconfigure.height);
}
}
}
}
else if (xev.type == KeyPress)
{
if (xev.xkey.keycode == XKeysymToKeycode(display, XK_Escape))
{
fprintf(stdout, "ESC Key Press\n");
for (const auto& i : players)
{
i->Stop();
if (i->GetWindow())
{
XDestroyWindow(display, i->GetWindow());
i->SetWindow(None);
}
}
players.clear();
XDestroyWindow(display, main_wid);
XCloseDisplay(display);
player_api.UnInit();
fprintf(stdout, "Close Players....\n");
return 0;
}
}
}
}
}
начать играть пакет
bool NT_PlayerSDKWrapper::Start(int buffer, bool is_mute, int render_scale_mode, bool is_only_dec_key_frame)
{
if (is_playing_)
return false;
if (url_.empty())
return false;
if (!OpenHandle(url_, buffer))
return false;
assert(handle_ && handle_->Handle());
// 音频参数
player_api_->SetMute(handle_->Handle(), is_mute ? 1 : 0);
player_api_->SetIsOutputAudioDevice(handle_->Handle(), 1);
player_api_->SetAudioOutputLayer(handle_->Handle(), 0); // 使用pluse 或者 alsa播放, 两个可以选择一个
// 视频参数
player_api_->SetVideoSizeCallBack(handle_->Handle(), this, &NT_Player_SDK_WRAPPER_OnVideoSizeHandle);
player_api_->SetXDisplay(handle_->Handle(), display_);
player_api_->SetXScreenNumber(handle_->Handle(),screen_);
player_api_->SetRenderXWindow(handle_->Handle(), window_);
player_api_->SetRenderScaleMode(handle_->Handle(), render_scale_mode);
player_api_->SetRenderTextureScaleFilterMode(handle_->Handle(), 3);
player_api_->SetOnlyDecodeVideoKeyFrame(handle_->Handle(), is_only_dec_key_frame ? 1 : 0);
auto ret = player_api_->StartPlay(handle_->Handle());
if (NT_ERC_OK != ret)
{
ResetHandle();
return false;
}
is_playing_ = true;
return true;
}
Остановить воспроизведение
void NT_PlayerSDKWrapper::Stop()
{
if (!is_playing_)
return;
assert(handle_);
player_api_->StopPlay(handle_->Handle());
video_width_ = 0;
video_height_ = 0;
ResetHandle();
is_playing_ = false;
}
Обратный вызов ширины и высоты видео
extern "C" NT_VOID NT_CALLBACK NT_Player_SDK_WRAPPER_OnVideoSizeHandle(NT_HANDLE handle, NT_PVOID user_data,
NT_INT32 width, NT_INT32 height)
{
auto sdk_wrapper = reinterpret_cast<NT_PlayerSDKWrapper*>(user_data);
if (nullptr == sdk_wrapper)
return;
sdk_wrapper->VideoSizeHandle(handle, width, height);
}
Живой снимок
extern "C" NT_VOID NT_CALLBACK NT_Player_SDK_WRAPPER_OnCaptureImageCallBack(NT_HANDLE handle, NT_PVOID user_data, NT_UINT32 result, NT_PCSTR file_name)
{
auto sdk_wrapper = reinterpret_cast<NT_PlayerSDKWrapper*>(user_data);
if (nullptr == sdk_wrapper)
return;
sdk_wrapper->CaptureImageHandle(handle, result, file_name);
}
Отключение звука в режиме реального времени
void NT_PlayerSDKWrapper::SetMute(bool is_mute)
{
if (is_playing_ && handle_)
{
player_api_->SetMute(handle_->Handle(), is_mute?1:0);
}
}
установить режим рисования
void NT_PlayerSDKWrapper::SetRenderScaleMode(int render_scale_mode)
{
if (is_playing_ && handle_)
{
player_api_->SetRenderScaleMode(handle_->Handle(), render_scale_mode);
}
}
Установить только ключевые кадры
void NT_PlayerSDKWrapper::SetOnlyDecodeVideoKeyFrame(bool is_only_dec_key_frame)
{
if (is_playing_ && handle_)
{
player_api_->SetOnlyDecodeVideoKeyFrame(handle_->Handle(), is_only_dec_key_frame ? 1 : 0);
}
}
Управление обработчиком
bool NT_PlayerSDKWrapper::OpenHandle(const std::string& url, int buffer)
{
if (handle_)
{
if (handle_->IsOpened()
&& handle_->URL() == url)
{
return true;
}
}
ResetHandle();
auto handle = std::make_shared<NT_SDK_HandleWrapper>(player_api_);
if (!handle->Open(url, buffer))
{
return false;
}
handle_ = handle;
handle_->AddEventHandler(shared_from_this());
return true;
}
void NT_PlayerSDKWrapper::ResetHandle()
{
if (handle_)
{
handle_->RemoveHandler(this);
handle_.reset();
}
}
Другие интерфейсы, такие как видеозапись, повторно описываться не будут, но платформа Windows та же самая.
Суммировать
Многоканальное воспроизведение RTMP или RTSP связано с такими проблемами, как производительность, синхронизация аудио и видео между несколькими каналами и долговременная стабильность воспроизведения.Существует относительно немного материалов, на которые можно ссылаться по платформе Linux, и относительно мало дополнительных решений. Желающие могут обращаться к нему по мере необходимости.