基于 SmartMediaKit 在Linux平台实现屏幕/摄像头采集与RTMP低延迟直播推流
一、前言
随着互联网直播行业的飞速发展,低延迟、高稳定性的推流技术已成为各类直播平台的核心竞争力。在Linux平台上实现高质量的屏幕录制与直播推流,涉及到视频采集、音频采集、编码、封装与网络传输等多个复杂环节。本文将结合大牛直播SDK(SmartMediaKit)的实际Demo代码,深入剖析如何在Linux环境下实现屏幕/摄像头采集,并通过RTMP协议实现低延迟直播推流。
大牛直播SDK(SmartMediaKit)是一套成熟的跨平台音视频处理SDK,在Linux平台上提供了完整的音视频采集、编码、推流解决方案,支持RTMP/RTSP等主流协议,具备低延迟、高性能、功能全面、架构灵活等突出优势。
二、RTMP协议简介与推流技术背景
2.1 RTMP协议概述
RTMP(Real-Time Messaging Protocol,实时消息传输协议)最初由Macromedia(后被Adobe收购)设计开发,是目前互联网直播领域最主流的推流协议之一。其核心特性包括:
- 基于TCP的可靠传输:RTMP运行在TCP之上,保证数据的可靠到达,避免直播流丢包导致的画面损坏。
- 多路复用(Multiplexing):RTMP通过Chunk流机制,将音频、视频、控制消息复用在同一条TCP连接上,减少连接开销。
- Chunk分片机制:RTMP将大消息拆分为若干Chunk进行传输,默认Chunk Size为128字节(可协商调整),有效降低单个大包对实时性的影响。
- 握手与会话管理:RTMP定义了严格的三次握手流程(C0/C1/C2、S0/S1/S2),并通过AMF(Action Message Format)编码进行流控与元数据交换。
- 低延迟特性:相比HLS协议动辄5~30秒的延迟,RTMP在良好网络环境下可实现1~3秒的端到端延迟,是实时互动直播的首选协议。
2.2 RTMP推流整体架构

一个完整的RTMP推流链路如下:
[采集端]
视频源(屏幕/摄像头/窗口)→ 视频编码(H.264/H.265)
音频源(麦克风/扬声器) → 音频编码(AAC/Speex)
↓
封装为RTMP Chunk Stream
↓
通过TCP发送至RTMP服务器
↓
[RTMP服务器](如nginx-rtmp、SRS等)
↓
[播放端](RTMP/HLS/WebRTC转发)
大牛直播SDK将上述整个链路封装得极为简洁,开发者只需关注业务层配置,无需处理底层协议细节。
三、大牛直播SDK(SmartMediaKit)核心能力
3.1 SDK架构设计
大牛直播SDK采用C风格的函数指针结构体作为API接口层(NT_SmartPublisherSDKAPI),这一设计有以下突出优势:
- ABI稳定性:通过函数指针结构体暴露接口,SDK内部实现可以独立更新,不影响已编译的上层应用。
- 跨语言调用友好:C接口天然支持C++、Python、Go等多种语言的FFI调用。
- 模块解耦:日志模块(
SmartLogAPI)、图像处理模块(NT_SmartPublisherImageSDKAPI)与核心推流模块相互独立,各司其职。
SDK通过以下方式初始化并获取API:
NT_SmartPublisherSDKAPI push_api;
memset(&push_api, 0, sizeof(push_api));
NT_GetSmartPublisherSDKAPI(&push_api);
auto ret = push_api.Init(0, nullptr);
if (NT_ERC_OK != ret) {
fprintf(stderr, "push_api.Init failed!\n");
return false;
}
memset清零是必要的安全操作——如果SDK版本更新后新增了函数指针,旧版本的二进制直接调用新版SDK时,未被填充的指针将为NULL,程序可以做安全判断而不会崩溃。
3.2 视频源选项(NT_PB_E_VIDEO_OPTION)
SDK提供了极为灵活的视频源选择机制:
| 选项 | 说明 |
|---|---|
NT_PB_E_VIDEO_OPTION_SCREEN |
采集整个桌面屏幕 |
NT_PB_E_VIDEO_OPTION_CAMERA |
采集USB/V4L2摄像头 |
NT_PB_E_VIDEO_OPTION_WINDOW |
采集指定X11窗口 |
NT_PB_E_VIDEO_OPTION_LAYER |
多路视频合成(叠加层模式) |
NT_PB_E_VIDEO_OPTION_ENCODED_DATA |
直接输入已编码的H.264/H.265数据 |
NT_PB_E_VIDEO_OPTION_NO_VIDEO |
纯音频推流 |
3.3 音频源选项(NT_PB_E_AUDIO_OPTION)
| 选项 | 说明 |
|---|---|
NT_PB_E_AUDIO_OPTION_CAPTURE_MIC |
采集麦克风(ALSA或PulseAudio) |
NT_PB_E_AUDIO_OPTION_CAPTURE_SPEAKER |
采集系统扬声器输出 |
NT_PB_E_AUDIO_OPTION_CAPTURE_MIC_SPEAKER_MIXER |
麦克风+扬声器混音 |
NT_PB_E_AUDIO_OPTION_EXTERNAL_PCM_DATA |
外部PCM数据输入 |
NT_PB_E_AUDIO_OPTION_TWO_EXTERNAL_PCM_MIXER |
两路外部PCM混音 |
四、Linux平台采集与推流实战详解
4.1 日志系统初始化
在SDK初始化之前,应当先初始化日志系统,方便问题定位:
void LogInit()
{
SmartLogAPI log_api;
memset(&log_api, 0, sizeof(log_api));
GetSmartLogAPI(&log_api);
log_api.SetLevel(SL_INFO_LEVEL);
log_api.SetPath((NT_PVOID)"./");
}
日志等级从低到高依次为:SL_TRACE_LEVEL → SL_DEBUG_LEVEL → SL_INFO_LEVEL → SL_WARNING_LEVEL → SL_ERROR_LEVEL → SL_FATAL_LEVEL。生产环境建议设置为SL_INFO_LEVEL或SL_WARNING_LEVEL,避免日志I/O影响推流性能。
4.2 音频设备枚举与选择
Linux平台的音频采集体系较为复杂,SDK同时支持ALSA和PulseAudio两套子系统:
// 优先检测PulseAudio设备
NT_INT32 pulse_device_number = 0;
if (NT_ERC_OK == push_api->GetAuidoInputDeviceNumber(2, &pulse_device_number)) {
fprintf(stdout, "Pulse device num:%d\n", pulse_device_number);
char device_name[512];
for (auto i = 0; i < pulse_device_number; ++i) {
if (NT_ERC_OK == push_api->GetAuidoInputDeviceName(2, i, device_name, 512)) {
fprintf(stdout, "index:%d name:%s\n", i, device_name);
}
}
}
// 若PulseAudio不可用,回退到ALSA
NT_INT32 alsa_device_number = 0;
if (pulse_device_number < 1) {
if (NT_ERC_OK == push_api->GetAuidoInputDeviceNumber(1, &alsa_device_number)) {
fprintf(stdout, "Alsa device num:%d\n", alsa_device_number);
// ... 枚举设备名
}
}
// 检测是否支持扬声器采集
NT_INT32 capture_speaker_flag = 0;
push_api->IsCanCaptureSpeaker(2, &capture_speaker_flag);
SDK设计亮点:GetAuidoInputDeviceNumber的第一个参数audio_input_layer(1=ALSA, 2=PulseAudio)使得SDK可以在同一套接口下透明切换音频子系统,开发者无需关心底层差异。
4.3 X11窗口采集与枚举
SDK对X11窗口采集提供了完整支持,包括窗口列表的动态枚举:
NT_INT32 is_support_window_capture = 0;
push_api->IsCaptureXWindowSupported(NULL, &is_support_window_capture);
if (is_support_window_capture) {
NT_INT32 win_count = 0;
if (NT_ERC_OK == push_api->UpdateCaptureXWindowList(NULL, &win_count) && win_count > 0) {
for (auto i = 0; i < win_count; ++i) {
NT_UINT64 wid;
char title[512];
if (NT_ERC_OK == push_api->GetCaptureXWindowInfo(i, &wid, title,
sizeof(title) / sizeof(char))) {
x_win_list.push_back(wid);
fprintf(stdout, "wid:%llu, title:%s\n", wid, title);
}
}
}
}
获得窗口列表后,通过以下接口指定要采集的窗口:
if (video_option == NT_PB_E_VIDEO_OPTION_WINDOW) {
if (!x_win_list.empty()) {
push_api->SetCaptureXWindow(push_handle, x_win_list.back());
}
}
4.4 PipeWire屏幕/窗口采集支持
在现代Linux桌面环境(如GNOME on Wayland)中,传统的X11截屏方式已逐渐被PipeWire所取代。大牛直播SDK对PipeWire提供了完整支持,这在同类SDK中极为难得:
// 启用PipeWire屏幕采集
NT_PB_EnablePipeWireScreenCapturer(push_handle, 1);
NT_PB_DisablePipeWireScreenCapturerDMABuffer(push_handle, 0); // 启用DMA-BUF共享
// 配置XDG Desktop Portal会话持久化模式
// 0: 不持久化(每次都需要用户授权)
// 2: 权限持久保留,直到显式撤销
NT_PB_SetPipeWireScreenCapturerXDGDPortalSessionPersistMode(push_handle, 0);
// 恢复上次会话Token(避免重复弹出授权对话框)
{
FILE* fp_token = fopen(pw_screen_capturer_session_restore_token_file_name, "r");
if (fp_token) {
char token_buf[128];
memset(token_buf, 0, sizeof(token_buf));
if (fgets(token_buf, sizeof(token_buf), fp_token)) {
if (token_buf[0])
NT_PB_SetPipeWireScreenCapturerXDGDPortalSessionRestoreToken(push_handle, token_buf);
}
fclose(fp_token);
}
}
// 启用PipeWire窗口采集
NT_PB_EnablePipeWireWindowCapturer(push_handle, 1);
NT_PB_DisablePipeWireWindowCapturerDMABuffer(push_handle, 0);
通过事件回调机制,SDK会将PipeWire会话的Restore Token回传给应用层,以便持久化存储:
void OnSDKEventHandle(NT_HANDLE handle, NT_PVOID user_data,
NT_UINT32 event_id, NT_INT64 param1, NT_INT64 param2,
NT_UINT64 param3, NT_UINT64 param4,
NT_PCSTR param5, NT_PCSTR param6, NT_PVOID param7)
{
if (NT_PB_E_EVENT_ID_PIPEWIRE_SCREEN_CAPTURER_XDG_DPORTAL_SESSION_RESTORE_TOKEN == event_id) {
if (param5 && *param5) {
fprintf(stdout, "Screen capturer session restore token:{%s}, mode:%lld\n", param5, param1);
if (2 == param1 || 1 == param1) {
// 持久化存储Token
FILE* fp_token = fopen(pw_screen_capturer_session_restore_token_file_name, "w");
if (fp_token) {
fputs(param5, fp_token);
fclose(fp_token);
}
}
}
}
else if (NT_PB_E_EVENT_ID_PIPEWIRE_SCREEN_CAPTURER_XDG_DPORTAL_REQUEST_CANCELLED == event_id) {
fprintf(stdout, "User cancelled screen capturer request, handle=%p\n", handle);
}
// 更多事件处理...
}
4.5 多显示器(XRandR)支持
对于多显示器环境,SDK通过XRandR接口精准锁定采集目标:
std::unique_ptr<NT_PB_XRR_MonitorBaseInfo[]> xrr_monitors(new (std::nothrow) NT_PB_XRR_MonitorBaseInfo[4]);
if (xrr_monitors) {
NT_INT32 xrr_monitors_count = 0;
auto xrr_monitors_ret = NT_PB_GetXRRMonitors(nullptr, xrr_monitors.get(), 4, &xrr_monitors_count);
if (NT_ERC_OK == xrr_monitors_ret) {
for (int i = 0; i < xrr_monitors_count; ++i) {
const auto& m = xrr_monitors[i];
if (m.primary_) {
// 自动选择主显示器采集
NT_PB_SetCaptureXRRMonitor(push_handle, m.id_);
break;
}
}
}
}
NT_PB_XRR_MonitorBaseInfo结构体包含了显示器的位置(x_, y_)、分辨率(width_, height_)、物理尺寸(m_width_, m_height_)及名称,便于应用层实现精细的多屏管理。
五、视频合成层(Layer)系统深度解析
大牛直播SDK的视频合成层(Layer)系统是其最具特色的功能之一,支持将多路视频源(屏幕、摄像头、图片、外部数据等)以叠加合成的方式输出为一路视频流,常见于直播间的PiP(画中画)场景。
5.1 Layer类型体系
NT_PB_E_LAYER_TYPE_SCREEN → 屏幕层(NT_PB_ScreenLayerConfigV2)
NT_PB_E_LAYER_TYPE_CAMERA → 摄像头层(NT_PB_CameraLayerConfig)
NT_PB_E_LAYER_TYPE_RGBA_RECTANGLE → RGBA矩形填充层(NT_PB_RGBARectangleLayerConfig)
NT_PB_E_LAYER_TYPE_IMAGE → 图片层/PNG水印(NT_PB_ImageLayerConfig)
NT_PB_E_LAYER_TYPE_EXTERNAL_VIDEO_FRAME → 外部视频帧层(NT_PB_ExternalVideoFrameLayerConfig)
NT_PB_E_LAYER_TYPE_WINDOW → X11窗口层(NT_PB_WindowLayerConfig)
5.2 Layer配置封装设计
Demo中提供了一套优雅的C++模板封装(nt_layer_conf_wrapper.h),将各种Layer类型的初始化细节封装在layer_conf_wrapper模板类中:
template <typename T, NT_INT32 LayerType>
class layer_conf_wrapper : public layer_conf_wrapper_base {
public:
layer_conf_wrapper(NT_INT32 index, bool is_enable,
NT_INT32 region_x, NT_INT32 region_y,
NT_INT32 region_w, NT_INT32 region_h)
{
memset(&conf_, 0, sizeof(conf_));
conf_.base_.type_ = LayerType;
conf_.base_.offset_ = offsetof(T, base_); // 关键:反向寻址偏移量
conf_.base_.cb_size_ = sizeof(conf_);
conf_.base_.enable_ = is_enable ? 1 : 0;
conf_.base_.index_ = index;
conf_.base_.region_ = { region_x, region_y, region_w, region_h };
}
T conf_;
};
设计亮点:NT_PB_LayerBaseConfig中的offset_字段存储了base_成员相对于外层结构体起始地址的偏移量。SDK内部只接收NT_PB_LayerBaseConfig*指针,但通过(ActualType*)((char*)p - offset_)即可还原出完整的具体类型指针,实现了类型安全的多态分发,而无需虚函数或RTTI开销。
5.3 实际多层合成配置示例
下面是Demo中的完整Layer配置过程,构建了一个1280×720的合成画面:
if (NT_PB_E_VIDEO_OPTION_LAYER == video_option)
{
std::vector<std::shared_ptr<nt_pb_sdk::layer_conf_wrapper_base>> layer_confs;
auto index = 0;
// 第0层:RGBA矩形背景(保证帧率,浅灰色填充)
auto rgba_layer_c0 = std::make_shared<nt_pb_sdk::RGBARectangleLayerConfigWrapper>(
index++, true, 0, 0, 1280, 720);
rgba_layer_c0->conf_.red_ = 200;
rgba_layer_c0->conf_.green_ = 200;
rgba_layer_c0->conf_.blue_ = 200;
rgba_layer_c0->conf_.alpha_ = 255;
layer_confs.push_back(rgba_layer_c0);
// 第1层:X11窗口(左半部分640×360)
if (!x_win_list.empty()) {
auto window_layer_c1 = std::make_shared<nt_pb_sdk::WindowLayerConfigWrapper>(
index++, true, 0, 0, 640, 360);
window_layer_c1->conf_.xwindow_ = x_win_list.back();
layer_confs.push_back(window_layer_c1);
}
// 第2层:摄像头(右上角640×360)
if (!cameras.empty()) {
auto camera_layer_c1 = std::make_shared<nt_pb_sdk::CameraLayerConfigWrapper>(
index++, true, 640, 0, 640, 360);
strcpy(camera_layer_c1->conf_.device_unique_id_, cameras.front().id_.c_str());
camera_layer_c1->conf_.is_flip_horizontal_ = 0;
camera_layer_c1->conf_.is_flip_vertical_ = 0;
camera_layer_c1->conf_.rotate_degress_ = 0;
layer_confs.push_back(camera_layer_c1);
// 若有第二个摄像头,在右上角叠加小画面
if (cameras.size() > 1) {
auto camera_layer_c2 = std::make_shared<nt_pb_sdk::CameraLayerConfigWrapper>(
index++, true, 640, 0, 320, 240);
strcpy(camera_layer_c2->conf_.device_unique_id_, cameras.back().id_.c_str());
layer_confs.push_back(camera_layer_c2);
}
}
// 第3层:PNG图片/贴图(324×300,位于右侧中部)
auto image_layer1 = std::make_shared<nt_pb_sdk::ImageLayerConfigWrapper>(
index++, true, 650, 120, 324, 300);
strcpy(image_layer1->conf_.file_name_utf8_, "./testpng/tca.png");
layer_confs.push_back(image_layer1);
// 第4层:另一张PNG图片(左下角182×138)
auto image_layer2 = std::make_shared<nt_pb_sdk::ImageLayerConfigWrapper>(
index++, true, 120, 380, 182, 138);
strcpy(image_layer2->conf_.file_name_utf8_, "./testpng/t4.png");
layer_confs.push_back(image_layer2);
// 提取base指针数组,提交给SDK
std::vector<const NT_PB_LayerBaseConfig*> layer_base_confs;
for (const auto& i : layer_confs) {
layer_base_confs.push_back(i->getBase());
}
if (NT_ERC_OK != push_api->SetLayersConfig(push_handle, 0,
layer_base_confs.data(), layer_base_confs.size(), 0, nullptr)) {
push_api->Close(push_handle);
return nullptr;
}
}
合成布局示意图:
┌─────────────────────────────────────────┐
│ 背景层(灰色 1280×720) │
│ ┌──────────┐ ┌──────────────────────┐ │
│ │ X11窗口 │ │ 摄像头画面 │ │
│ │ 640×360 │ │ 640×360 │ │
│ └──────────┘ └──────────────────────┘ │
│ ┌────────────────┐ ┌───────────────┐ │
│ │ PNG图片2 │ │ PNG图片1 │ │
│ │ 182×138 │ │ 324×300 │ │
│ └────────────────┘ └───────────────┘ │
└─────────────────────────────────────────┘
六、视频编码器精细化配置
SDK对H.264/H.265编码器提供了极为精细的控制接口:
push_api->SetFrameRate(push_handle, dst_fps); // 帧率,如25fps
// 选择编码器:软件编码(OpenH264)
// type=0:软编, type=1:硬编; encoder_id=1:OpenH264; codec_id:H264/H265
push_api->SetVideoEncoder(push_handle, 0, 1, NT_MEDIA_CODEC_ID_H264, 0);
// 码率控制(CBR/VBR)
push_api->SetVideoBitRate(push_handle, 2000); // 平均码率2000kbps
push_api->SetVideoQuality(push_handle, 26); // QP值,范围[1-50],越小质量越好
push_api->SetVideoMaxBitRate(push_handle, 4000); // 峰值码率上限4000kbps
// OpenH264特定参数
push_api->SetVideoEncoderSpecialInt32Option(push_handle, "usage_type", 0); // 0:摄像头, 1:屏幕
push_api->SetVideoEncoderSpecialInt32Option(push_handle, "rc_mode", 1); // 0:质量模式, 1:码率模式
push_api->SetVideoEncoderSpecialInt32Option(push_handle, "enable_frame_skip", 0); // 禁用跳帧
// 关键帧间隔(GOP Size):2倍帧率=2秒一个关键帧
push_api->SetVideoKeyFrameInterval(push_handle, dst_fps * 2);
// H.264 Profile
push_api->SetVideoEncoderProfile(push_handle, 3); // 3: High Profile
// 编码速度(1-6,值越大速度越快,质量越低)
push_api->SetVideoEncoderSpeed(push_handle, 3);
关于码率控制模式的选择建议:
- 屏幕录制推流场景:推荐使用
rc_mode=0(质量模式)+SetVideoQuality,画面静止时自动降低码率,动态场景自动提升,带宽利用更高效。 - 摄像头直播场景:推荐使用
rc_mode=1(码率模式)+SetVideoBitRate+SetVideoMaxBitRate,保证码率稳定,适合弱网环境。 - OpenH264的
usage_type建议按场景正确设置:屏幕内容大量直线与文字,usage_type=1会启用屏幕编码优化。
七、音频处理链配置
7.1 音频设备绑定
if (pulse_device_number > 0) {
push_api->SetAudioInputLayer(push_handle, 2); // 使用PulseAudio
push_api->SetAuidoInputDeviceId(push_handle, 0);
} else if (alsa_device_number > 0) {
push_api->SetAudioInputLayer(push_handle, 1); // 使用ALSA
push_api->SetAuidoInputDeviceId(push_handle, 0);
}
7.2 音频处理算法
SDK内置了音频处理算法,对于直播场景至关重要:
// 回声消除(AEC):消除扬声器声音被麦克风拾入导致的回声
// delay参数:麦克风与扬声器之间的延迟,单位ms,0表示自动检测
push_api->SetEchoCancellation(push_handle, 1, 0);
// 噪声抑制(NS):滤除环境噪音
push_api->SetNoiseSuppression(push_handle, 1);
// 自动增益控制(AGC):自动调节麦克风音量
push_api->SetAGC(push_handle, 1);
// 语音活动检测(VAD):静音段自动检测
push_api->SetVAD(push_handle, 1);
// 多路音频音量独立控制
push_api->SetInputAudioVolume(push_handle, 0, 1.0); // 麦克风音量100%
push_api->SetInputAudioVolume(push_handle, 1, 0.2); // 扬声器/混音20%
// 音频编码格式:1=AAC(直播标配),2=Speex
push_api->SetPublisherAudioCodecType(push_handle, 1);
八、RTMP推流启动与预览
8.1 推流URL配置与启动
// 设置推流URL(支持多URL同时推流,多次调用SetURL即可)
if (NT_ERC_OK != push_api->SetURL(push_handle, "rtmp://192.168.0.107:1935/live/test1", NULL)) {
push_api->Close(push_handle);
return nullptr;
}
// 启动推流
if (NT_ERC_OK != push_api->StartPublisher(push_handle, NULL)) {
push_api->Close(push_handle);
return nullptr;
}
RTMP URL格式说明:rtmp://<server_ip>:<port>/<app_name>/<stream_key>
server_ip:RTMP服务器地址(如nginx-rtmp、SRS、阿里云/腾讯云直播域名)port:默认1935app_name:RTMP应用名,通常为livestream_key:流名称,CDN场景下通常含有鉴权参数
8.2 本地预览
SDK支持将编码前的原始帧渲染到X11窗口,开发者无需自己实现OpenGL或XRender渲染:
// sub_wid 是创建的X11子窗口
push_api.SetPreviewXWindow(push_handle, "", sub_wid);
push_api.StartPreview(push_handle, 0, nullptr);
8.3 推流生命周期管理
// 停止预览
push_api.StopPreview(push_handle);
// 停止推流(会等待所有缓存数据发送完毕)
push_api.StopPublisher(push_handle);
// 关闭句柄,释放内部资源
push_api.Close(push_handle);
push_handle = nullptr;
// 全局反初始化(进程退出前调用)
push_api.UnInit();
九、信号处理与线程安全
Demo对SIGINT和SIGFPE均实现了完善的sigaction处理:
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_sigaction = OnSaSigaction;
act.sa_flags = SA_SIGINFO; // 使用SA_SIGINFO以获取siginfo_t详细信息
sigaction(SIGINT, &act, NULL); // Ctrl+C优雅退出
sigaction(SIGFPE, &act, NULL); // 浮点异常捕获
// X11多线程支持:必须在XOpenDisplay之前调用
XInitThreads();
XInitThreads()调用是使用X11多线程的必要前提——SDK内部会在独立线程中进行X11渲染操作,若未调用此函数,将导致随机崩溃。
十、主事件循环设计
Demo采用轮询+短超时的X11事件循环,兼顾响应性与CPU效率:
while (!g_is_exit)
{
// MY_X11_Pending内部先检查队列,若无事件则poll等待最多10ms
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 (xev.type == KeyPress) {
if (xev.xkey.keycode == XKeysymToKeycode(display, XK_Escape)) {
g_is_exit = true;
}
}
}
}
MY_X11_Pending通过poll系统调用实现了精确的超时等待,避免了XNextEvent阻塞导致无法响应退出信号的问题,同时10ms的超时参数保证了主循环每秒最多100次迭代,CPU占用极低。
十一、大牛直播SDK(SmartMediaKit)核心优势总结
11.1 低延迟
- 直接对接RTMP协议栈,推流链路短,在局域网环境下端到端延迟可低至100-200ms
- 视频编码器采用低延迟模式(
enable_frame_skip=0保证帧完整性,rc_mode=1保证码率稳定) - 事件回调机制异步处理,不阻塞推流线程
11.2 高性能
- SDK内部采用多线程架构:采集线程、编码线程、发送线程相互独立,充分利用多核CPU
- PipeWire DMA-BUF支持:屏幕帧数据可通过GPU内存直接共享,避免CPU拷贝,大幅降低系统负载
- 支持硬件编码(H.264/H.265硬编),进一步解放CPU资源
11.3 功能全面
| 功能维度 | 支持情况 |
|---|---|
| 视频源 | 屏幕、摄像头、窗口、外部YUV/RGB数据 |
| 音频源 | 麦克风、扬声器、外部PCM、多路混音 |
| 视频编码 | H.264 |
| 音频编码 | AAC、Speex |
| 推流协议 | RTMP、RTSP推流、RTSP服务器 |
| 录像 | 本地MP4录制,文件自动切割 |
| 视频合成 | 多层叠加(屏幕+摄像头+图片+窗口) |
| 桌面适配 | X11、XRandR多显示器 |
| 音频处理 | AEC、NS、AGC、VAD |
11.4 架构灵活
- API函数指针结构体:支持运行时动态加载,便于插件化集成
- Layer合成系统:各层独立配置、动态启停(
EnableLayer),支持运行时更新区域(UpdateLayerRegion) - 多URL同时推流:一路采集,多路分发,CDN多源同步
- 外部数据输入接口:
PostVideoEncodedData、PostAudioPCMData等接口支持与其他采集/处理框架对接
十二、完整推流流程总结
1. XInitThreads() → X11多线程支持
2. LogInit() → 日志系统初始化
3. push_api.Init() → SDK全局初始化
4. 枚举音频设备 → GetAuidoInputDeviceNumber/GetAuidoInputDeviceName
5. 检测窗口采集支持 → IsCaptureXWindowSupported
6. 枚举X窗口列表 → UpdateCaptureXWindowList/GetCaptureXWindowInfo
7. 枚举摄像头设备 → GetVideoCaptureDeviceNumber/GetVideoCaptureDeviceInfo
8. push_api.Open() → 创建推流实例,指定视频/音频源类型
9. SetEventCallBack() → 注册事件回调
10. [可选] SetLayersConfig() → 配置多层合成
11. NT_PB_GetXRRMonitors() → 获取显示器列表,选择采集目标
12. NT_PB_EnablePipeWire*() → 启用PipeWire采集支持
13. 配置视频参数 → SetFrameRate/SetVideoEncoder/SetVideoBitRate...
14. 配置音频参数 → SetAudioInputLayer/SetEchoCancellation...
15. SetURL() → 配置推流URL
16. StartPublisher() → 启动推流
17. SetPreviewXWindow() → [可选] 配置本地预览窗口
18. StartPreview() → [可选] 启动预览
19. 主事件循环 → 处理X11事件,等待退出信号
20. StopPreview() → 停止预览
21. StopPublisher() → 停止推流
22. push_api.Close() → 关闭句柄
23. push_api.UnInit() → SDK全局反初始化
十三、编译与运行
13.1 编译依
# 安装必要依赖
sudo apt-get install libx11-dev libxrandr-dev
# 确保gcc/g++ >= 5.5
gcc --version
13.2 编译
# ARM64本机编译
make NT_ENABLE_EXTERNAL_LIB=no NT_CROSS_COMPILE_PREFIX=
# x86_64交叉编译ARM64
make
13.3 运行
# 前台运行
./SmartPublisherSDKDemo
# 后台运行(推荐生产环境)
nohup ./SmartPublisherSDKDemo >/dev/null 2>&1 &
# 优雅停止后台进程
kill -s SIGINT <pid>
十四、总结
本文结合大牛直播SDK(SmartMediaKit)的真实Demo代码,深入分析了Linux平台下RTMP推流的完整技术链路。从X11/PipeWire屏幕采集、摄像头枚举、多显示器适配,到多层视频合成、H.264编码精调、音频处理,再到RTMP推流启动与生命周期管理,大牛直播SDK以简洁的API设计覆盖了直播推流技术的方方面面。
对于需要在Linux平台实现高质量直播推流的开发者,大牛直播SDK(SmartMediaKit)提供了一个成熟、可靠、高性能的技术基座,能够大幅缩短开发周期,同时保证生产级别的稳定性与低延迟性能。
📎 CSDN官方博客:音视频牛哥-CSDN博客
更多推荐
所有评论(0)