4. 编码器初始化流程:创建编码器、配置参数、设置回调

编码器初始化,说白了就是告诉系统:「我要开始干活了,给我准备好硬件资源。」这一步要是没做好,后面编码性能直接拉胯。我见过不少团队,花大把时间调编码参数,结果初始化流程里埋了个坑,性能始终上不去。

咱们今天就把这个流程拆开揉碎了讲。核心就三步:创建编码器实例 → 配置编码参数 → 设置回调函数。顺序不能乱,参数不能错。

核心原则:硬件编码器初始化是一次性投入。初始化做对了,后面编码流畅;初始化做错了,后面全是坑。

4.1 创建编码器实例

在 WebRTC 里,创建硬件编码器通常走 VideoEncoderFactory 接口。我个人习惯用 CreateVideoEncoder 方法,传入编码器类型(比如 H264、VP8、VP9)。

// 伪代码示例:创建编码器实例
std::unique_ptr<VideoEncoder> encoder;
VideoCodecType codec_type = VideoCodecType::kH264;

// 通过编码器工厂创建
encoder = encoder_factory->CreateVideoEncoder(codec_type);
if (!encoder) {
  // 创建失败,回退到软件编码
  LOG(ERROR) << "硬件编码器创建失败,尝试软件编码";
  encoder = fallback_encoder_factory->CreateVideoEncoder(codec_type);
}

这里有个关键点:创建失败不要直接崩溃。我在项目中遇到过,某款手机硬件编码器在特定系统版本上返回 nullptr,如果不做降级处理,用户直接黑屏。所以一定要准备软件编码作为保底方案。

注意:创建编码器实例时,系统会检查硬件能力。如果当前设备不支持硬件编码,或者编码器资源被其他应用占满,返回 nullptr 是正常现象。不要假设硬件编码器一定存在。

4.2 配置编码参数

编码器创建成功后,下一步就是配置参数。这一步最容易被忽视,也最容易出问题。配置参数的核心结构是 VideoCodec 和 VideoEncoder::Settings。

我一般按这个顺序配置:

  1. 基础分辨率:width × height,必须 16 对齐
  2. 帧率:目标帧率,通常 30fps 或 60fps
  3. 码率控制:min_bitrate、target_bitrate、max_bitrate
  4. 编码模式:实时通信场景用 CBR,录制场景用 VBR
  5. 硬件特定参数:如 H264 的 profile、level、gop_size
// 配置编码参数
VideoCodec codec_settings;
codec_settings.codecType = VideoCodecType::kH264;
codec_settings.width = 1280;
codec_settings.height = 720;
codec_settings.maxFramerate = 30;

// 码率配置(单位:bps)
codec_settings.startBitrate = 2000;   // 2Mbps
codec_settings.minBitrate = 500;      // 500Kbps
codec_settings.maxBitrate = 4000;     // 4Mbps

// H264 特定参数
codec_settings.H264().profile = H264Profile::kProfileConstrainedBaseline;
codec_settings.H264().level = H264Level::kLevel3_1;
codec_settings.H264().packetizationMode = H264PacketizationMode::kNonInterleaved;

// 应用配置
VideoEncoder::Settings encoder_settings(
    VideoEncoder::Capabilities(/* loss_notification */ true),
    /* number_of_cores */ 4,
    /* max_payload_size */ 1460);
encoder->InitEncode(&codec_settings, encoder_settings);

经验之谈:码率配置别拍脑袋。我建议先跑一遍带宽估计,拿到实际可用带宽再设 target_bitrate。设太高,编码器会丢帧;设太低,画质糊成一片。我曾经在一个弱网项目里,把 max_bitrate 设成带宽的 1.5 倍,结果编码器频繁丢帧,用户反馈视频卡顿。后来改成 1.2 倍,问题解决。

4.3 设置回调函数

回调函数是编码器和业务层之间的桥梁。编码器编码完一帧数据,通过回调通知你。WebRTC 里主要用 EncodedImageCallback 接口。

回调函数里通常做三件事:

  • 接收编码后的数据:拿到 EncodedImage 结构体
  • 时间戳处理:保证编码帧和原始帧的时间戳对应
  • 丢包/重传逻辑:如果开启了 NACK,需要在这里处理
// 自定义回调类
class MyEncodedCallback : public EncodedImageCallback {
 public:
  Result OnEncodedImage(const EncodedImage& encoded_image,
                        const CodecSpecificInfo* codec_specific_info) override {
    // 检查编码结果
    if (encoded_image._encodedWidth == 0 || encoded_image._encodedHeight == 0) {
      LOG(WARNING) << "编码器返回空帧,可能硬件资源不足";
      return Result(Result::ERROR);
    }

    // 处理编码后的数据
    ProcessEncodedFrame(encoded_image);
    
    // 记录编码耗时(性能监控)
    int64_t encode_time_ms = (rtc::TimeMillis() - encoded_image.capture_time_ms_);
    if (encode_time_ms > 50) {
      LOG(WARNING) << "编码耗时过长: " << encode_time_ms << "ms";
    }

    return Result(Result::OK);
  }
};

// 注册回调
MyEncodedCallback callback;
encoder->RegisterEncodeCompleteCallback(&callback);

注意:回调函数是在编码器的工作线程里执行的。不要在回调里做耗时操作,比如写文件、网络发送。否则会阻塞编码器,导致后续帧排队。我见过有人直接在回调里写日志到磁盘,结果编码帧率从 30fps 掉到 5fps。

4.4 初始化流程全景图

下面这张图把整个初始化流程串起来了。你可以看到,每一步都有分支判断,不是一条直线走到底。

编码器初始化流程全景图 开始初始化 创建编码器实例 创建成功? 回退到软件编码 配置编码参数 分辨率/帧率 码率控制 编码模式 硬件特定参数 设置编码回调 初始化完成

4.5 常见踩坑点

初始化流程看着简单,实际项目里坑不少。我列几个常见的:

问题 现象 原因 解决方案
分辨率未对齐 编码器返回失败 硬件编码器要求宽高为 16 的倍数 初始化前做对齐处理
码率设置不合理 编码器频繁丢帧 max_bitrate 远高于硬件能力 参考硬件规格设置码率
回调阻塞 编码帧率下降 回调里做了耗时操作 回调只做数据传递
重复初始化 内存泄漏或崩溃 未释放旧编码器就创建新实例 先 Release 再 Init

避坑指南:我曾经在一个跨平台项目里,Android 端初始化正常,iOS 端一直崩溃。查了两天才发现,iOS 的硬件编码器要求 InitEncode 必须在主线程调用。所以,初始化前一定要确认平台对线程的要求。

4.6 初始化后的验证

初始化完成不代表万事大吉。我建议做一次快速验证:

  • 编码一帧测试:传入一帧黑屏数据,看回调是否正常触发
  • 检查编码输出:确认 EncodedImage 的 size 不为 0
  • 监控初始化耗时:硬件编码器初始化通常 10-50ms,超过 100ms 说明有问题