一、前言

随着互联网直播行业的飞速发展,低延迟、高稳定性的推流技术已成为各类直播平台的核心竞争力。在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_LEVELSL_DEBUG_LEVELSL_INFO_LEVELSL_WARNING_LEVELSL_ERROR_LEVELSL_FATAL_LEVEL。生产环境建议设置为SL_INFO_LEVELSL_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:默认1935
  • app_name:RTMP应用名,通常为live
  • stream_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多源同步
  • 外部数据输入接口PostVideoEncodedDataPostAudioPCMData等接口支持与其他采集/处理框架对接

十二、完整推流流程总结

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博客

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐