第23章:Camera与视频编码——MediaCodec与Camera的Buffer共享、视频编码参数配置、HDR视频录制、视频防抖与Camera协同、实战:实现4K HDR视频录制
各位好,欢迎来到第23章。这一章我们聊点硬核的——把Camera拍到的画面,变成真正能存下来的视频文件。
说实话,很多做Camera驱动的人,做到YUV数据出来就觉得完事了。但实际产品中,用户要的是视频,不是裸数据。我见过太多项目,Camera预览跑得飞起,一到录像就卡成PPT。为什么?因为Camera和编码器之间没配合好。
这一章,我们就来彻底搞定这件事。
23.1 MediaCodec与Camera的Buffer共享
先问一个问题:Camera HAL层输出的buffer,怎么送到MediaCodec去编码?
最笨的办法:把数据从GPU/ISP拷贝到CPU内存,再交给编码器。但4K 60fps的数据量有多大?一帧大约12MB,一秒720MB。你拷贝一次试试?CPU直接跑满,手机发烫,掉帧是必然的。
所以,正确的做法是——Buffer共享。
23.1.1 共享机制的核心:Surface与ANativeWindow
Android的MediaCodec支持从Surface直接消费数据。什么意思?就是Camera HAL层输出的buffer,可以直接“喂”给编码器的Surface,中间零拷贝。
流程是这样的:
- MediaCodec配置编码器时,设置一个Surface作为输出目标。
- Camera HAL层通过ANativeWindow接口,把buffer queue注册到编码器的Surface上。
- HAL层填充完一帧数据后,通过dequeue/queue机制交给编码器。
- 编码器直接从GraphicBuffer中读取数据,编码输出。
嗯,这里要注意:Camera HAL层必须支持GRALLOC_USAGE_HW_VIDEO_ENCODER这个usage flag。否则分配出来的buffer编码器用不了。
关键代码片段: 在HAL层配置buffer时,记得加上这个flag
// Camera HAL层,配置Stream时
stream->usage = GRALLOC_USAGE_SW_READ_OFTEN |
GRALLOC_USAGE_HW_TEXTURE |
GRALLOC_USAGE_HW_VIDEO_ENCODER; // 这个不能少
我曾经在一个项目里,忘了加这个flag,结果编码器拿到的buffer全是花屏。查了两天才发现是usage没配对。这种坑,踩过一次就记住了。
23.1.2 共享的Buffer格式要求
不是所有格式编码器都吃。常见的编码器只认NV12或NV21。如果你HAL层输出的是YV12或者YUV420P,编码器会直接报错。
| HAL输出格式 | 编码器支持情况 | 备注 |
|---|---|---|
| NV12 | ✅ 广泛支持 | 首选,性能最好 |
| NV21 | ✅ 支持 | 部分平台兼容 |
| YV12 | ❌ 不支持 | 需要格式转换 |
| YUV420P | ❌ 不支持 | 需要格式转换 |
所以,你在HAL层配置Camera输出格式时,直接选NV12就对了。别折腾别的格式,省得给自己找麻烦。
23.2 视频编码参数配置
参数配置是门学问。配好了,画质好、文件小;配不好,要么模糊要么文件巨大。
23.2.1 码率控制
码率决定了视频的清晰度。码率太低,画面全是马赛克;码率太高,文件太大,存储和传输都扛不住。
我个人习惯这样配:
- 4K 30fps:码率设40-60 Mbps
- 1080p 30fps:码率设15-25 Mbps
- 720p 30fps:码率设8-12 Mbps
当然,这取决于场景。如果是静态场景,码率可以低一些;如果是运动场景(比如拍赛车),码率要往上加。
小技巧: 使用BITRATE_MODE_VBR(可变码率)比CBR(固定码率)画质更好。VBR会在复杂场景自动提高码率,简单场景降低码率。我一般都用VBR。
23.2.2 帧率配置
帧率要和Camera的帧率匹配。Camera输出30fps,编码器就设30fps。如果Camera输出60fps,编码器设30fps,那就会丢一半的帧,视频看起来一卡一卡的。
反过来,Camera输出30fps,编码器设60fps,编码器会重复插入帧,浪费码率。
所以,帧率一定要保持一致。这个在HAL层和MediaCodec配置时都要确认。
23.2.3 GOP(关键帧间隔)
GOP就是两个I帧之间的间隔。I帧是关键帧,可以独立解码;P帧和B帧依赖前面的帧。
GOP设得越小,视频越容易seek(快进快退),但文件越大。GOP设得越大,文件越小,但seek时会有延迟。
我一般这样配:
- 普通录制:GOP = 30(每秒一个I帧)
- 直播/实时场景:GOP = 15(每0.5秒一个I帧,降低延迟)
- 监控/存储:GOP = 60(每2秒一个I帧,节省空间)
注意: 有些编码器不支持任意GOP值。比如某些硬件编码器只支持GOP为2的幂次方(16、32、64)。配置前先查一下编码器的能力。
23.3 HDR视频录制(HLG/PQ)
HDR是现在的主流。iPhone、三星、华为都在推。Android从10开始原生支持HDR录制。
HDR有两种主流标准:
- HLG(Hybrid Log-Gamma):兼容SDR显示设备,适合广播电视和直播
- PQ(Perceptual Quantization):也叫ST 2084,适合电影级制作,需要HDR显示器
在Camera HAL层,HDR录制需要做两件事:
- Sensor输出HDR数据:比如10-bit RAW或者多帧合成的HDR YUV
- 传递HDR元数据:包括色彩空间、传输函数、色域等
23.3.1 HAL层配置HDR
在配置Camera Stream时,需要设置android.request.availableCapabilities包含DYNAMIC_RANGE_PROFILE,然后指定HDR profile。
// HAL层配置HDR
camera_metadata_entry_t entry;
entry.tag = ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES;
entry.data.i32[0] = ANDROID_DYNAMIC_RANGE_PROFILE_HLG; // 或者PQ
entry.count = 1;
同时,编码器也要支持HDR。MediaCodec需要配置COLOR_RANGE_LIMITED和对应的色彩空间。
MediaCodec HDR配置示例:
MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height);
format.setInteger(MediaFormat.KEY_BIT_RATE, 60000000);
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
// HDR相关
format.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT2020);
format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_HLG);
format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
这里要注意:不是所有编码器都支持HDR。高通平台从SM8250(骁龙865)开始支持,联发科从天玑1000开始。老平台就别想了。
23.4 视频防抖(EIS/OIS)与Camera协同
防抖分两种:
- OIS(光学防抖):硬件层面,通过镜头移动补偿抖动。HAL层只需要使能即可。
- EIS(电子防抖):软件层面,通过裁剪画面+陀螺仪数据补偿。这个需要HAL层和编码器配合。
23.4.1 EIS与编码器的协同
EIS的原理是:Sensor输出比最终视频更大的画面(比如输出16:10,最终裁剪成16:9),然后根据陀螺仪数据,每帧动态调整裁剪区域。
这就带来一个问题:编码器收到的画面,是裁剪后的还是裁剪前的?
答案是:裁剪后的。HAL层或者ISP在做EIS时,已经把画面裁剪好了,编码器只负责编码最终画面。
但这里有个坑:EIS需要额外的buffer margin。比如你要输出4K视频,Sensor可能需要输出5K的画面,多出来的部分用于裁剪补偿。这会导致带宽和功耗上升。
我的经验: 在低端平台上,EIS和4K HDR同时开,很容易带宽不够。我建议先做性能摸底,看看ISP和编码器能不能扛住。扛不住的话,要么降分辨率,要么关EIS。
23.4.2 OIS的配置
OIS相对简单。在HAL层使能OIS即可:
// 使能OIS
camera_metadata_entry_t entry;
entry.tag = ANDROID_CONTROL_OIS_MODE;
entry.data.i32[0] = ANDROID_CONTROL_OIS_MODE_ON;
entry.count = 1;
OIS对编码器没有直接影响。但要注意:OIS工作时,Sensor的FOV(视场角)会略微变化,因为镜头在移动。这个变化很小,一般不用管。
23.5 实战:实现4K HDR视频录制
好了,理论讲完了。我们来写一个完整的流程。
23.5.1 整体流程
先画个图,让大家看清楚整个链路:
23.5.2 关键代码实现
下面是一个简化的实现流程。注意,这里只展示核心逻辑,实际产品中还要处理错误、状态机等。
// 1. 配置Camera HAL层
void configureCameraHDR() {
// 设置输出格式为NV12
stream.format = HAL_PIXEL_FORMAT_YCbCr_420_SP;
stream.usage = GRALLOC_USAGE_HW_VIDEO_ENCODER;
stream.width = 3840;
stream.height = 2160;
// 设置HDR profile
metadata.add(ANDROID_REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES,
ANDROID_DYNAMIC_RANGE_PROFILE_HLG);
// 使能EIS
metadata.add(ANDROID_CONTROL_VIDEO_STABILIZATION_MODE,
ANDROID_CONTROL_VIDEO_STABILIZATION_MODE_ON);
}
// 2. 配置MediaCodec
void configureMediaCodec(Surface surface) {
MediaFormat format = MediaFormat.createVideoFormat(
"video/hevc", 3840, 2160); // H.265编码
format.setInteger(MediaFormat.KEY_BIT_RATE, 60000000); // 60Mbps
format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); // GOP=30
format.setInteger(MediaFormat.KEY_BITRATE_MODE,
MediaFormat.BITRATE_MODE_VBR);
// HDR元数据
format.setInteger(MediaFormat.KEY_COLOR_STANDARD,
MediaFormat.COLOR_STANDARD_BT2020);
format.setInteger(MediaFormat.KEY_COLOR_TRANSFER,
MediaFormat.COLOR_TRANSFER_HLG);
format.setInteger(MediaFormat.KEY_COLOR_RANGE,
MediaFormat.COLOR_RANGE_LIMITED);
// 配置编码器
MediaCodec codec = MediaCodec.createEncoderByType("video/hevc");
codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
codec.setInputSurface(surface); // 关键:设置输入Surface
codec.start();
}
// 3. 启动Camera预览到Surface
void startCameraPreview(Surface surface) {
// CameraManager.openCamera(...)
// CameraDevice.createCaptureSession(
// Arrays.asList(surface), // 直接传编码器的Surface
// ...);
}
23.5.3 验证与调试
录制完成后,怎么验证HDR是否生效?
- 用MediaInfo工具查看视频文件的色彩信息,确认是BT.2020 + HLG
- 在支持HDR的显示器上播放,看亮度范围是否正常
- 检查编码器输出的SEI NALU,看是否包含了HDR元数据
常见问题: 录出来的视频在普通显示器上颜色发灰?这是正常的。因为SDR显示器无法正确映射HDR的亮度范围。你需要用HDR显示器或者支持HDR转SDR的播放器来看。
好了,这一章的内容就到这里。4K HDR录制涉及的东西不少,但核心就是三点:Buffer共享、参数配置、元数据传递。把这三点搞明白,其他都是细节。
希望各位在实际项目中,能少踩坑,一次点亮。
公众号:蓝海资料掘金营,微信deep3321