5、视频H.264编码实战:H.264编码参数详解与编码器配置

各位同学,今天我们来聊聊H.264编码实战。说实话,H.264这块内容,我在刚入行那会儿也觉得挺头疼的。参数多,配置复杂,稍不注意就踩坑。但别担心,今天我把这些年的经验都掏出来,咱们一起捋一遍。

5.1 H.264编码核心参数:Profile、Level、Bitrate

这三个参数,说白了就是编码器的"三驾马车"。你配置得好,视频又清晰又流畅;配不好,要么画质糊成一片,要么码率飙得飞起。

5.1.1 Profile(档次)

Profile定义了编码器支持哪些工具集。H.264主要有三个档次:

Profile 特点 适用场景
Baseline 最基础,支持I/P帧,不支持B帧 视频通话、低延迟场景
Main 支持B帧,压缩率更高 标准视频播放
High 支持8x8变换,压缩率最高 高清视频、蓝光

我个人习惯在直播场景用Baseline Profile。为什么?因为B帧会引入延迟,而直播最怕的就是延迟。我曾经在一个项目中,为了追求压缩率用了Main Profile,结果推流延迟从1秒飙到了3秒,用户直接炸了。嗯,从那以后我就记住了:低延迟场景,老老实实用Baseline。

5.1.2 Level(级别)

Level限制了视频的分辨率、帧率和码率上限。比如Level 3.0支持的最大分辨率是720x576@30fps,码率上限10Mbps。Level 4.0则支持1920x1080@30fps,码率上限20Mbps。

关键点:Level不是越高越好。你想想看,如果手机只支持Level 3.0解码,你推个Level 4.0的流,对方直接黑屏。所以要根据目标设备来选。

5.1.3 Bitrate(码率)

码率决定了视频的清晰度。码率越高,画质越好,但文件也越大。这里有个常见的误区:很多人以为码率越高越好。其实不是的,码率超过一定阈值后,画质提升就微乎其微了。

我建议的码率配置参考:

  • 480p:500-800 kbps
  • 720p:1-2 Mbps
  • 1080p:3-5 Mbps

当然,这取决于视频内容。如果是静态画面,码率可以低一些;如果是运动剧烈的画面(比如球赛),码率得往上调。

5.2 编码器配置与启动

好了,理论说完了,咱们直接上代码。Android平台上,我们通常用MediaCodec来做硬件编码。

5.2.1 创建编码器

MediaCodec encoder = MediaCodec.createEncoderByType("video/avc");
MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height);

// 配置参数
format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2); // 关键帧间隔2秒
format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);

// 设置Profile和Level
format.setInteger("profile", MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline);
format.setInteger("level", MediaCodecInfo.CodecLevel.AVCLevel31);

encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

小技巧:KEY_I_FRAME_INTERVAL这个参数很关键。直播场景我一般设2秒,这样用户切换频道时能快速看到画面。但如果你做的是视频录制,可以设5秒甚至更长,压缩率会更好。

5.2.2 启动编码器

encoder.start();

// 获取输入Surface
Surface inputSurface = encoder.createInputSurface();

// 将Camera预览数据送到Surface
camera.setPreviewTexture(new SurfaceTexture(10));
camera.startPreview();

这里有个坑,我曾经踩过。createInputSurface()必须在start()之后调用,否则会报错。嗯,别问我怎么知道的,都是血泪史。

5.3 编码数据回调与封装

编码器启动后,我们需要不断从输出缓冲区取数据。这个过程,说白了就是个生产者-消费者模型。

5.3.1 获取编码数据

MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputIndex = encoder.dequeueOutputBuffer(bufferInfo, 10000);

while (outputIndex >= 0) {
    ByteBuffer outputBuffer = encoder.getOutputBuffer(outputIndex);
    
    // 判断是否为关键帧
    boolean isKeyFrame = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0;
    
    // 处理编码数据
    processEncodedData(outputBuffer, bufferInfo, isKeyFrame);
    
    encoder.releaseOutputBuffer(outputIndex, false);
    outputIndex = encoder.dequeueOutputBuffer(bufferInfo, 10000);
}

5.3.2 封装成RTMP包

拿到H.264裸数据后,我们需要封装成RTMP能识别的格式。RTMP要求每个视频包前面加上4字节的长度前缀。

private void processEncodedData(ByteBuffer buffer, MediaCodec.BufferInfo info, boolean isKeyFrame) {
    byte[] data = new byte[info.size];
    buffer.position(info.offset);
    buffer.get(data, 0, info.size);
    
    if (isKeyFrame) {
        // 关键帧前面需要加上SPS/PPS
        byte[] sps = getSpsData();
        byte[] pps = getPpsData();
        sendRtmpPacket(sps);
        sendRtmpPacket(pps);
    }
    
    // 发送视频数据
    sendRtmpPacket(data);
}

注意:SPS和PPS是解码器需要的参数集,必须在关键帧之前发送。我见过不少新手在这里栽跟头,关键帧发了但没带SPS/PPS,结果解码器直接罢工。

5.4 知识体系总览

为了让大家更直观地理解整个编码流程,我画了一张图:

H.264编码实战知识体系 编码参数配置 Profile Baseline / Main / High Level 分辨率/帧率限制 Bitrate 码率控制策略 关键帧间隔 I帧间隔设置 编码器操作流程 1. createEncoderByType 2. configure 3. start 4. createInputSurface 5. 输入数据 6. dequeueOutputBuffer 7. 处理输出数据 数据封装与传输 H.264裸数据 SPS/PPS提取 关键帧判断 4字节长度前缀 RTMP封装 网络发送 三个模块环环相扣,参数配置影响编码质量,编码器操作决定数据流向,封装传输确保数据可用

这张图把整个流程串起来了。你从左边开始,先配好参数,然后操作编码器,最后把数据封装发送。每一步都有坑,但只要你理解了原理,其实都不难。

好了,今天的实战内容就到这里。记住,编码这件事,理论要懂,代码要练,踩坑要记。下次遇到问题,你就能从容应对了。


公众号:蓝海资料掘金营,微信deep3321