麒麟操作系统|Linux下低延时RTMP|RTSP直播播放实现

160 阅读3分钟

背景

国产操作系统多为以Linux为基础二次开发的操作系统。2014年4月8日起,美国微软公司停止了对Windows XP SP3操作系统提供服务支持,这引起了社会和广大用户的广泛关注和对信息安全的担忧。而2020年对Windows7服务支持的终止再一次推动了国产系统的发展。
工信部对此表示,将继续加大力度,支持Linux的国产操作系统的研发和应用,并希望用户可以使用国产操作系统。随着信息技术和互联网的快速发展普及,电子商务已经成为不可抗拒的现代商业潮流,云计算、大数据应用日趋成熟,但随之带来了许多问题和挑战。为全面响应国家“互联网+”战略的提出和深入贯彻落实国家“十二五”规划纲要,帮助传统企业开展“商务智慧转型”,加强电子商务深入应用,特别是移动电子商务发展中的环境保障建设,促进电子商务行业健康有序发展,使电子商务相关的技术和经济、法律和规则、诚信和信誉及如何建立一个安全、可靠、可信的电子商务环境,保障电子商务活动中系统、交易的安全性,信息的保密性,已经成为当前亟待需要探讨和解决的重要课题。

国产操作系统|Linux下RTMP|RTSP直播播放

在发布国产操作系统|Linux平台的RTMP|RTSP直播播放SDK之前,大牛直播SDK(官方)的直播播放SDK无需赘述,采用自研内核框架,功能齐全、高稳定、超低延迟、超低资源占用,覆盖Windows、Android和iOS平台。

本次发布的可用于国产操作系统和Linux上的的RTMP|RTSP直播播放SDK, 视频绘制使用XLib相关库实现, 音频输出使用PulseAudio和Alsa Lib实现,除了常规功能如实时静音、快照、buffer time设定、网络自动重连等,RTMP支持扩展H265播放, RTSP也支持H265播放。

播放器接口和调用都比较简单,集成复杂度低,且不依赖于QT。

相关DEMO

大牛直播SDK发布的Linux平台播放器SDK支持多实例播放,以单个窗体播放为例,相关代码如下:

const char* player_url_ = "rtsp://admin:daniulive12345@192.168.0.120:554/h264/ch1/main/av_stream";

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

	display_ = display;

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

	main_wid_ = XCreateSimpleWindow(display, root, 0, 0, main_w, main_h, 0, white_pixel, black_pixel);
	if (!main_wid_)
	{
		fprintf(stderr, "Cannot Create Main Window\n");
		player_api.UnInit();
		XCloseDisplay(display);
		return 0;
	}

	XSelectInput(display, main_wid_, StructureNotifyMask | KeyPressMask);

	auto sub_wid = CreateSubWindow(display, screen, main_wid_);
	if (!sub_wid)
	{
		fprintf(stderr, "Cannot Create Render Window\n");
		player_api.UnInit();

		XDestroyWindow(display, main_wid_);
		XCloseDisplay(display);
		return 0;
	}

	XMapWindow(display, main_wid_);
	XStoreName(display, main_wid_, win_base_title);
	XMapWindow(display, sub_wid);

	NT_HANDLE handle = nullptr;

	// 打开一个播放实例,可以Open多个播放实例, 然后播放多路
	if (NT_ERC_OK != player_api.Open(&handle, 0, nullptr))
	{
		player_api.UnInit();

		fprintf(stderr, "player_api.Open failed!\n");

		XDestroyWindow(display, sub_wid);
		XDestroyWindow(display, main_wid_);
		XCloseDisplay(display);

		return 0;
	}

	player_api.SetEventCallBack(handle, nullptr, &NT_OnSDKEventHandle);
	player_api.SetVideoSizeCallBack(handle, nullptr, &NT_SDKVideoSizeHandle);

	player_api.SetReportDownloadSpeed(handle, 1, 5); // 5秒上报一次下载速度

	player_api.SetRtspTimeout(handle, 15);
	player_api.SetRtspAutoSwitchTcpUdp(handle, 1);

	player_api.SetBuffer(handle, 0); // 设置缓存
	player_api.SetIsOutputAudioDevice(handle, 1);
	player_api.SetAudioOutputLayer(handle, 0); // 使用pluse 或者 alsa播放, 两个可以选择一个
	//player_api.SetAudioVolume(handle, 100);

	player_api.SetURL(handle, player_url_); // 设置播放地址, rtsp或者rtmp地址

	player_api.SetXDisplay(handle, display);
	player_api.SetXScreenNumber(handle, screen);
	player_api.SetRenderXWindow(handle, sub_wid); // 设置绘制的X窗口
	player_api.SetRenderScaleMode(handle, 1); // 按比例绘制或者全填充
	player_api.SetRenderTextureScaleFilterMode(handle, 3); 
	player_api.SetFastStartup(handle, 1);
	player_api.SetLowLatencyMode(handle, 0);

	if (NT_ERC_OK != player_api.StartPlay(handle))
	{
		player_api.Close(handle);
		handle = nullptr;

		player_api.UnInit();
		fprintf(stderr, "player_api.StartPlay failed!\n");

		XDestroyWindow(display, sub_wid);
		XDestroyWindow(display, main_wid_);
		XCloseDisplay(display);

		return 0;
	}

	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;

						XMoveResizeWindow(display, sub_wid, 0, 0, main_w-4, main_h-4);
					}
				}
				else
				{
					if (sub_wid == xev.xconfigure.window)
					{
						player_api.OnWindowSize(handle, 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");

					if (handle != nullptr)
					{
						player_api.StopPlay(handle); // 停止播放
						player_api.Close(handle);
						handle = nullptr;
					}

					XDestroyWindow(display, sub_wid);
					XDestroyWindow(display, main_wid_);
					XCloseDisplay(display);

					player_api.UnInit();

					fprintf(stdout, "Close Player....\n");
					return 0;
				}
			}
		}
	}
}

日志设置和SDK Init相关

void NT_SDKLogInit()
{
	SmartLogAPI log_api;
	memset(&log_api, 0, sizeof(log_api));
	GetSmartLogAPI(&log_api);

	log_api.SetLevel(SL_INFO_LEVEL);
	log_api.SetPath((NT_PVOID)"./");
}

bool NT_PlayerSDKInit(SmartPlayerSDKAPI& player_api)
{
	memset(&player_api, 0, sizeof(player_api));
	GetSmartPlayerSDKAPI(&player_api);

	auto ret = player_api.Init(0, nullptr);
	if (NT_ERC_OK != ret)
	{
		fprintf(stderr, "player_api.Init failed!\n");
		return false;
	}
	else
	{
		fprintf(stdout, "player_api.Init ok!\n");
	}

	return true;
}

窗体相关

Display* display_ = nullptr;
Window   main_wid_ = None;

const char* win_base_title = "Rtmp/Rtsp Live Player Demo";

int EventPoll(int fd, bool is_write, int timeout_ms)
{
	int result;

	do
	{
		struct pollfd info;

		info.fd = fd;
		if (is_write)
		{
			info.events = POLLOUT;
		}
		else
		{
			info.events = POLLIN | POLLPRI;
		}

		result = poll(&info, 1, timeout_ms);

	} while (result < 0 && errno == EINTR);

	return result;
}

bool MY_X11_Pending(Display* display, int timeout_ms)
{
	XFlush(display);
	if (XEventsQueued(display, QueuedAlready) > 0)
	{
		return true;
	}

	if (EventPoll(ConnectionNumber(display), false, timeout_ms))
	{
		if (XPending(display) > 0)
		{
			return true;
		}
	}

	return false;
}

Window CreateSubWindow(Display* display, int screen, Window parent)
{
	XWindowAttributes  parent_win_att;
	XGetWindowAttributes(display, parent, &parent_win_att);
	fprintf(stdout, "parent w:%d, h:%d\n", parent_win_att.width, parent_win_att.height);

	XSetWindowAttributes swa;
	swa.border_pixel = WhitePixel(display, screen);
	swa.event_mask = KeyPressMask | StructureNotifyMask;

	return XCreateWindow(display, parent, 0, 0, parent_win_att.width-4, parent_win_att.height-4,
		2, parent_win_att.depth, InputOutput, parent_win_att.visual, CWEventMask | CWBorderPixel, &swa);
}

Event回调

void NT_OnSDKEventHandle(NT_HANDLE handle, NT_PVOID user_data,
	NT_UINT32 event_id,
	NT_INT64  param1,
	NT_INT64  param2,
	NT_UINT64 param3,
	NT_PCSTR  param4,
	NT_PCSTR  param5,
	NT_PVOID  param6
	)
{
	if (NT_SP_E_EVENT_ID_DOWNLOAD_SPEED == event_id)
	{
		fprintf(stdout, "OnSDKEventHandle handle:%p speed:%lldkbps, %lldKB/s. \r", handle, (param1 * 8) / 1000, param1 / 1024);
		fflush(stdout);
	}
}

视频分辨率回调

void NT_SDKVideoSizeHandle(NT_HANDLE handle, NT_PVOID userData,
	NT_INT32 width, NT_INT32 height)
{
	if (display_ && main_wid_)
	{
		std::ostringstream ss;
		ss << win_base_title << "  [Video Size: " << width << "*" << height << " ]";

		XStoreName(display_, main_wid_, ss.str().c_str());
	}
}

相关界面

总结

国产操作系统|Linux下的RTMP、RTSP直播播放,经实际测试,延迟和Windows平台一样,毫秒级,随着国产操作系统在无纸化同屏等行业的推进,越来越多的场景需要这样一款稳定性高延迟低的RTMP|RTSP播放器,本文抛砖引玉,感兴趣的开发者可酌情参考。