Linux | Kirin operating system realizes multi-channel RTMP|RTSP playback

Posted by mhoward on Fri, 14 Jan 2022 14:33:10 +0100

Technical background

Whether it is Windows or Linux, multi-channel playback demands are very common, such as camera display in macro scenes such as smart construction site, exhibition hall and education. For the points needing attention in the development of RTSP or RTMP live player, please refer to the previous blog. In general, there are the following points:

1. Low latency: most RTSP plays are aimed at live scenes. Therefore, if the delay is too large, for example, in the monitoring industry, the client only sees when the thieves are gone, or when others have pressed the doorbell for a few seconds, the master can see the image, which seriously affects the experience. Therefore, low latency is a very important indicator to measure a good RTSP player, At present, the RTSP playback delay of Daniel live SDK is controlled at hundreds of milliseconds, and the VLC is controlled at a few seconds. This delay is a long low delay, such as running for 1 day, a week, a month or even longer;

2. Audio and video synchronization or jump: some developers do not even do audio and video synchronization in order to pursue low delay experience. They get audio video and play it directly, resulting in a/v synchronization and time stamp jumping;

3. Support multiple instances: a good player needs to support simultaneous playback of multiple audio and video data, such as 4-8-9-16-32 window;

4. Support buffer time setting: in some scenarios with network jitter, the player needs to support accurate buffer time setting, generally speaking, in milliseconds;

5. Playback and recording of H.265: in addition to H.264, it also needs to support H.265. At present, there are more and more RTSP H.265 cameras on the market, and RTSP players supporting H.265 are imminent. In addition, playing H.265 alone is not enough, but also needs to be able to record the data of H.265;

6. TCP/UDP mode switching: considering that many servers only support TCP or UDP mode, a good RTSP player needs to support automatic switching of TCP/UDP mode;

7. Mute support: for example, when playing RTSP streams in multiple windows, if each audio is played, the experience is very bad, so the real-time mute function is very necessary;

8. Video view rotation: many cameras cause image inversion due to installation restrictions, so a good RTSP player should support real-time rotation of video view (0 ° 90 ° 180 ° 270 °), horizontal inversion and vertical inversion;

9. Support audio/video data output after decoding (optional): Daniel live SDK has contacted many developers and hopes to obtain YUV or RGB data and analyze algorithms such as face matching while playing, so audio and video callback is optional;

10. Snapshot: it is necessary to capture interesting or important pictures in real time;

11. Network jitter processing (such as network disconnection and reconnection): basic functions, which will not be repeated;

12. Cross platform: for a good player, cross platform (Windows/Android/iOS) is necessary. At least for the sake of subsequent scalability, this aspect is considered during development. At present, the RTSP player of Daniel live SDK perfectly supports the above platforms;

13. Long term operation stability: when it comes to stability, many developers disagree. In fact, stability is the most basic premise of a good product and can not be ignored!
14. Video recording: in the process of playing, video clips of interest are recorded at any time for archiving or other secondary processing;

15. log information recording: the overall process mechanism provides real-time feedback, and does not log more, but some important logs, such as errors during playback, are not allowed;

16. Real time download speed feedback: you can see the real-time download speed feedback to monitor the network status;

17. Abnormal state handling: such as handling various scenarios such as network disconnection, network jitter, incoming call, return after switching to the background, etc.

code implementation

This article is based on Daniel live SDK( official )Taking Linux platform as an example, this paper introduces RTMP or RTSP stream multiplex playback integration.

int main(int argc, char *argv[])
{
	XInitThreads(); // X supports multithreading and must be called

	NT_SDKLogInit();

	// SDK initialization
	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]);
		// The first way is not muted, and all others are muted
		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;
				}
			}
		}
	}
}

Start playing package

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());

	// Audio parameters
	player_api_->SetMute(handle_->Handle(), is_mute ? 1 : 0);
	player_api_->SetIsOutputAudioDevice(handle_->Handle(), 1);
	player_api_->SetAudioOutputLayer(handle_->Handle(), 0); // You can choose one of the two to play with plug or alsa

	// Video parameters
	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;
}

stop playing

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;
}

Video width height callback

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);
}

Real time snapshot

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);
}

Real time mute

void NT_PlayerSDKWrapper::SetMute(bool is_mute)
{
	if (is_playing_ && handle_)
	{
		player_api_->SetMute(handle_->Handle(), is_mute?1:0);
	}
}

Set drawing mode

void NT_PlayerSDKWrapper::SetRenderScaleMode(int render_scale_mode)
{
	if (is_playing_ && handle_)
	{
		player_api_->SetRenderScaleMode(handle_->Handle(), render_scale_mode);
	}
}

Set dekeyframe only

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);
	}
}

Handler management

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();
	}
}

Other interfaces such as video recording will not be repeated and can be consistent with Windows platform.

summary

Multi channel RTMP or RTSP playback involves performance, audio and video synchronization between multiple channels, long-time playback stability and other issues. There are few materials available for reference and few optional schemes on Linux platform. Those interested can refer to them as appropriate.