1. Android Camera数据采集与YUV格式解析
在Android平台上使用Camera API采集视频数据是编码流程的第一步。我遇到过不少开发者在这一步就卡壳,主要问题集中在Camera2 API的复杂配置和YUV数据格式的理解上。这里分享几个实战经验:
Camera2 API的基本工作流程需要先创建CameraManager实例,然后通过openCamera方法打开指定摄像头。记得在AndroidManifest.xml中添加相机权限:
<uses-permission android:name="android.permission.CAMERA" />采集到的原始数据通常是NV21或YUV_420_888格式。这里有个坑我踩过多次:不同设备支持的预览格式可能不同。建议在初始化时检查支持的格式:
StreamConfigurationMap map = characteristics.get( CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); int[] formats = map.getOutputFormats();YUV420格式的数据排列方式很关键。以NV21为例,它的内存布局是:先存储所有Y分量(亮度),然后是交错的VU分量(色度)。一个分辨率为640x480的帧,其Y分量大小为640x480字节,UV分量各为320x240字节,总大小是width×height×1.5。
2. MediaCodec编码器配置实战
创建编码器时,我强烈建议使用异步模式。同步模式虽然直观,但在高帧率场景下容易阻塞主线程。以下是创建H.264编码器的标准流程:
MediaFormat format = MediaFormat.createVideoFormat( MediaFormat.MIMETYPE_VIDEO_AVC, width, height); format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); format.setInteger(MediaFormat.KEY_FRAME_RATE, fps); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); MediaCodec encoder = MediaCodec.createEncoderByType( MediaFormat.MIMETYPE_VIDEO_AVC); encoder.setCallback(new MyCallback()); encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);这里有几个参数需要特别注意:
- 比特率(BIT_RATE):建议根据分辨率设置,1080p视频通常设为4-8Mbps
- 关键帧间隔(I_FRAME_INTERVAL):直播场景建议设为1-2秒,录播场景可以适当增大
- 颜色格式(COLOR_FORMAT):COLOR_FormatYUV420Flexible是最通用的选择
3. YUV到ByteBuffer的转换技巧
Camera输出的YUV数据通常需要转换后才能送入编码器。这里分享一个高效的转换方法:
private void convertYUV420ToNV21(Image image, byte[] output) { ByteBuffer yBuffer = image.getPlanes()[0].getBuffer(); ByteBuffer uBuffer = image.getPlanes()[1].getBuffer(); ByteBuffer vBuffer = image.getPlanes()[2].getBuffer(); int ySize = yBuffer.remaining(); int uSize = uBuffer.remaining(); int vSize = vBuffer.remaining(); yBuffer.get(output, 0, ySize); vBuffer.get(output, ySize, vSize); uBuffer.get(output, ySize + vSize, uSize); }转换时要注意:
- 不同设备的Image对象可能有不同的padding值
- UV分量的采样率通常是Y分量的1/2
- 建议使用ByteBuffer而不是byte[]处理大数据量,效率更高
4. 编码与MP4封装全流程
完整的编码流程需要MediaCodec和MediaMuxer配合工作。以下是核心步骤:
- 启动编码器:
encoder.start();- 在onInputBufferAvailable回调中喂数据:
@Override public void onInputBufferAvailable(MediaCodec codec, int index) { ByteBuffer buffer = codec.getInputBuffer(index); // 填充YUV数据 codec.queueInputBuffer(index, 0, data.length, timestamp, 0); }- 处理编码输出:
@Override public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) { ByteBuffer encodedData = codec.getOutputBuffer(index); muxer.writeSampleData(trackIndex, encodedData, info); codec.releaseOutputBuffer(index, false); }- 封装MP4文件:
MediaMuxer muxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); int trackIndex = muxer.addTrack(encoder.getOutputFormat()); muxer.start();常见问题排查:
- 如果遇到"Failed to add track"错误,检查MediaFormat是否包含csd-0和csd-1参数
- 视频不同步问题通常是由于时间戳计算错误导致的
- 输出文件损坏可能是没有正确发送END_OF_STREAM标志
5. 性能优化与实战技巧
经过多个项目的实践,我总结出几个提升编码效率的方法:
内存优化:
- 复用ByteBuffer对象,避免频繁内存分配
- 使用Surface输入模式代替ByteBuffer模式(需要API 18+)
- 设置合适的缓冲区数量:
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, width * height * 3 / 2);参数调优:
- 动态比特率调整:
Bundle params = new Bundle(); params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, newBitrate); encoder.setParameters(params);- 低延迟配置:
format.setInteger(MediaFormat.KEY_LATENCY, 1);异常处理: 编码过程中常见的异常包括:
- CodecException:通常由配置错误引起
- IllegalStateException:多线程访问导致
- MediaCodec.CodecError:硬件资源不足
建议在回调中实现健壮的错误处理:
@Override public void onError(MediaCodec codec, MediaCodec.CodecException e) { Log.e(TAG, "Encoder error: " + e.getDiagnosticInfo()); // 重启编码器或通知上层 }6. 音视频同步实现方案
如果需要加入音频,同步是关键。我常用的时间戳同步方案是:
- 使用系统时钟作为基准:
long startTime = System.nanoTime() / 1000;- 计算视频时间戳:
long videoPts = (System.nanoTime() / 1000 - startTime);- 音频时间戳对齐:
audioBufferInfo.presentationTimeUs = (System.nanoTime() / 1000 - startTime);同步时要注意:
- 音频数据通常采用AAC格式,采样率设为44100Hz
- 视频关键帧间隔不宜设置过大
- 使用MediaSync类可以实现更精确的同步
在实际项目中,我发现使用单独的同步线程来管理时间戳比依赖编码器内部计时更可靠,特别是在处理高帧率视频时。