Операционная система Linux|Kylin реализует многоканальное воспроизведение RTMP|RTSP

Linux

техническое образование

Будь то платформа 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, и относительно мало дополнительных решений. Желающие могут обращаться к нему по мере необходимости.