Linux|麒麟操做系統實現多路RTMP|RTSP播放

技術背景

不管是Windows平臺仍是Linux,多路播放訴求很是廣泛,好比針對智慧工地、展館、教育等宏觀場景下的攝像頭展現,關於RTSP或RTMP直播播放器開發須要注意的點,可參考以前博客,總的來講有如下一些點:算法

1. 低延遲:大多數RTSP的播放都面向直播場景,因此,若是延遲過大,好比監控行業,小偷都走了,客戶端纔看到,或者別人已經按過門鈴幾秒,主人才看到圖像,嚴重影響體驗,因此,低延遲是衡量一個好的RTSP播放器很是重要的指標,目前大牛直播SDK的RTSP播放延遲控制在幾百毫秒,VLC在幾秒,這個延遲,是長時間的低延遲,好比運行1天、一週、一個月甚至更久;windows

2. 音視頻同步或跳轉:有些開發者爲了追求低延遲體驗,甚至不作音視頻同步,拿到audio video直接播放,致使a/v不一樣步,還有就是時間戳亂跳;api

3. 支持多實例:一個好的播放器,須要支持同時播放多路音視頻數據,好比4-8-9-16-32窗口;服務器

4. 支持buffer time設置:在一些有網絡抖動的場景,播放器須要支持精準的buffer time設置,通常來講,以毫秒計;網絡

5. H.265的播放和錄製:除了H.264,還須要支持H.265,目前市面上的RTSP H.265攝像頭愈來愈多,支持H.265的RTSP播放器迫在眉睫,此外,單純的播放H.265還不夠,還須要能夠能把H.265的數據能錄製下來;多線程

6. TCP/UDP模式切換:考慮到好多服務器僅支持TCP或UDP模式,一個好的RTSP播放器須要支持TCP/UDP模式自動切換;app

7. 靜音支持:好比,多窗口播放RTSP流,若是每一個audio都播放出來,體驗很是很差,因此實時靜音功能很是必要;ide

8. 視頻view旋轉:好多攝像頭因爲安裝限制,致使圖像倒置,因此一個好的RTSP播放器應該支持如視頻view實時旋轉(0° 90° 180° 270°)、水平反轉、垂直反轉;性能

9. 支持解碼後audio/video數據輸出(可選):大牛直播SDK接觸到好多開發者,但願能在播放的同時,獲取到YUV或RGB數據,進行人臉匹配等算法分析,因此音視頻回調可選;this

10. 快照:感興趣或重要的畫面,實時截取下來很是必要;

11. 網絡抖動處理(如斷網重連):基本功能,再也不贅述;

12. 跨平臺:一個好的播放器,跨平臺(Windows/Android/iOS)頗有必要,起碼爲了後續擴展性考慮,開發的時候,有這方面的考慮,目前大牛直播SDK的RTSP播放器,完美支持以上平臺;

13. 長期運行穩定性:提到穩定性,好多開發者不覺得然,實際上,一個好的產品,穩定是最基本的前提,不容忽視!
14. 能夠錄像:播放的過程當中,隨時錄製下來感興趣的視頻片段,存檔或其餘二次處理;

15. log信息記錄:總體流程機制實時反饋,很少打log,可是不能一些重要的log,如播放過程當中出錯等;

16. download速度實時反饋:能夠看到實時下載速度反饋,以此來監聽網絡狀態;

17. 異常狀態處理:如播放的過程當中,斷網、網絡抖動、來電話、切後臺後返回等各類場景的處理。

代碼實現

本文以大牛直播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);
	}
}

Handler管理

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平臺可參考的資料比較少,可選的方案比較少,感興趣的可酌情參考。

相關文章
相關標籤/搜索