news 2026/4/23 21:59:57

Android音视频入门:从Camera API到MediaCodec,一步步搞定摄像头H264编码(附完整Demo)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android音视频入门:从Camera API到MediaCodec,一步步搞定摄像头H264编码(附完整Demo)

Android音视频开发实战:Camera API与MediaCodec编码全流程解析

第一次接触Android音视频开发时,面对Camera API、YUV格式转换、MediaCodec编码这些概念,确实容易让人望而生畏。记得去年接手一个智能门铃项目时,我在摄像头数据采集和编码环节踩遍了所有能踩的坑——画面倒置、绿屏、编码失败...这些经历让我深刻理解,音视频开发需要的是系统化的知识图谱和实战经验。本文将带你完整走通从摄像头采集到H264编码的全流程,重点解决三个核心问题:如何正确获取摄像头数据?为什么要处理YUV格式转换?MediaCodec编码有哪些隐藏陷阱?

1. 开发环境准备与基础架构

1.1 项目配置与权限管理

在AndroidManifest.xml中声明摄像头权限只是第一步,现代Android开发更需要关注运行时权限管理和设备兼容性:

<uses-permission android:name="android.permission.CAMERA" /> <uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" />

动态权限申请的最佳实践应当包含以下要素:

  • 权限拒绝后的优雅降级处理
  • 权限说明对话框的自定义文案
  • 设备无摄像头时的备用方案
private fun checkCameraPermission() { when { ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED -> initCamera() shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> { // 显示自定义解释对话框 showPermissionExplanationDialog() } else -> requestPermissions( arrayOf(Manifest.permission.CAMERA), CAMERA_PERMISSION_REQUEST_CODE ) } }

1.2 相机预览界面搭建

SurfaceView仍是摄像头预览的最佳选择,但需要注意:

  • 表面生命周期回调的精确控制
  • 合适的预览尺寸选择策略
  • 横竖屏切换时的处理逻辑
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder mHolder; private Camera mCamera; public CameraPreview(Context context, Camera camera) { super(context); mCamera = camera; mHolder = getHolder(); mHolder.addCallback(this); mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } @Override public void surfaceCreated(SurfaceHolder holder) { try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); } catch (IOException e) { Log.d(TAG, "Error setting camera preview: " + e.getMessage()); } } }

2. 摄像头数据采集与YUV处理

2.1 Camera API的深度使用

现代Android开发推荐使用Camera2 API,但传统Camera API在兼容性方面仍有优势。关键配置点包括:

  • 预览格式设置为ImageFormat.NV21
  • 合适的预览帧率配置(15/30fps)
  • 自动对焦和白平衡设置
Camera.Parameters parameters = mCamera.getParameters(); List<Integer> formats = parameters.getSupportedPreviewFormats(); if (formats.contains(ImageFormat.NV21)) { parameters.setPreviewFormat(ImageFormat.NV21); } List<Camera.Size> sizes = parameters.getSupportedPreviewSizes(); Camera.Size optimalSize = getOptimalPreviewSize(sizes, width, height); parameters.setPreviewSize(optimalSize.width, optimalSize.height); mCamera.setParameters(parameters); mCamera.setPreviewCallback(previewCallback);

2.2 YUV格式详解与转换技巧

Android摄像头输出的NV21格式与MediaCodec需要的NV12格式差异主要体现在UV分量排列上:

格式特性NV21 (YUV420SP)NV12 (YUV420SP)
Y分量排列完整平面存储完整平面存储
UV排列VU交替UV交替
内存布局YYYY...VUVU...YYYY...UVUV...

转换代码的关键优化点:

public static byte[] nv21ToNv12(byte[] nv21, int width, int height) { byte[] nv12 = new byte[nv21.length]; int frameSize = width * height; // Y分量直接复制 System.arraycopy(nv21, 0, nv12, 0, frameSize); // UV分量交错处理 for (int i = frameSize; i < nv21.length; i += 2) { nv12[i] = nv21[i + 1]; // U分量 nv12[i + 1] = nv21[i]; // V分量 } return nv12; }

2.3 画面旋转处理方案

摄像头传感器方向与屏幕方向不一致会导致画面旋转,常见解决方案对比:

  1. Camera.setDisplayOrientation()

    • 仅影响预览显示
    • 不改变输出数据方向
  2. 数据层面旋转

    • 需要手动处理YUV数据
    • 性能开销较大但效果彻底
public static byte[] rotateNV21(byte[] input, int width, int height, int rotation) { byte[] output = new byte[input.length]; boolean swap = (rotation == 90 || rotation == 270); boolean flip = (rotation == 90 || rotation == 180); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // 计算旋转后的坐标 int xo = x, yo = y; int w = width, h = height; if (swap) { xo = y; yo = x; w = height; h = width; } if (flip) { xo = w - xo - 1; yo = h - yo - 1; } // 处理Y分量 output[yo * w + xo] = input[y * width + x]; // 处理UV分量... } } return output; }

3. MediaCodec编码实战

3.1 H264编码器配置要点

创建MediaCodec实例时需要特别注意的参数组合:

MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height); format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar); MediaCodec codec = MediaCodec.createEncoderByType(MIME_TYPE); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); codec.start();

关键参数说明:

  • BIT_RATE:建议值为分辨率宽×高×3~5
  • FRAME_RATE:需与摄像头输出帧率一致
  • I_FRAME_INTERVAL:关键帧间隔(秒)

3.2 编码流程详解

MediaCodec的典型编码流程可分为五个阶段:

  1. 获取输入缓冲区:dequeueInputBuffer
  2. 填充YUV数据:将处理后的NV12数据放入缓冲区
  3. 提交编码任务:queueInputBuffer
  4. 获取编码输出:dequeueOutputBuffer
  5. 处理编码数据:获取H.264 NAL单元
// 输入处理 int inputBufferIndex = codec.dequeueInputBuffer(TIMEOUT_US); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex); inputBuffer.clear(); inputBuffer.put(yuvData); codec.queueInputBuffer(inputBufferIndex, 0, yuvData.length, presentationTimeUs, 0); } // 输出处理 MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US); while (outputBufferIndex >= 0) { ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex); byte[] h264Data = new byte[bufferInfo.size]; outputBuffer.get(h264Data); // 处理h264Data... codec.releaseOutputBuffer(outputBufferIndex, false); outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, TIMEOUT_US); }

3.3 编码性能优化技巧

通过实践总结的三大优化方向:

  1. 缓冲区复用:避免频繁内存分配

    private ByteBuffer[] inputBuffers; private ByteBuffer[] outputBuffers; // 初始化时获取 inputBuffers = codec.getInputBuffers(); outputBuffers = codec.getOutputBuffers();
  2. 异步处理模式:使用Callback接口

    codec.setCallback(new MediaCodec.Callback() { @Override public void onInputBufferAvailable(MediaCodec mc, int index) { // 处理输入缓冲区... } @Override public void onOutputBufferAvailable(MediaCodec mc, int index, MediaCodec.BufferInfo info) { // 处理输出数据... } });
  3. 动态码率调整:根据网络状况调整

    Bundle params = new Bundle(); params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, newBitrate); codec.setParameters(params);

4. 完整实现与调试技巧

4.1 工程结构设计建议

合理的类职责划分可以大幅降低维护成本:

├── camera │ ├── CameraManager.java # 摄像头生命周期管理 │ └── CameraConfig.java # 分辨率等配置 ├── encoder │ ├── H264Encoder.java # 编码核心逻辑 │ └── FrameProcessor.java # YUV处理 └── output ├── FileWriter.java # 文件存储 └── NetworkSender.java # 网络传输

4.2 常见问题排查指南

开发过程中遇到的典型问题及解决方案:

问题1:画面绿屏或颜色异常

  • 检查YUV格式转换是否正确
  • 确认MediaCodec的COLOR_FORMAT配置
  • 验证数据旋转是否影响UV分量

问题2:编码延迟高

  • 降低预览分辨率(如1080p→720p)
  • 调整GOP结构(减小I帧间隔)
  • 使用硬件加速编码器

问题3:视频文件无法播放

  • 确保写入正确的SPS/PPS头
  • 检查H264 Annex B格式
  • 验证时间戳是否单调递增

4.3 调试工具推荐

  1. ADB命令实时监控

    adb shell dumpsys media.codec # 查看编解码器状态 adb logcat -s Camera2Client # 过滤摄像头日志
  2. 视频分析工具

    • FFmpeg:验证H264流完整性
    ffmpeg -i input.h264 -vf "split=2[a][b],[a]field=top[b]field=bottom, hstack" -f null -
    • CodecInfo:查看设备支持的编码格式
  3. 性能分析工具

    • Android Profiler:检测内存泄漏
    • Systrace:分析编码流水线瓶颈

5. 进阶方向与扩展思考

当基础功能实现后,可以考虑以下优化方向:

  1. 动态分辨率适配:根据网络状况调整编码参数
  2. 多路编码:同时生成不同质量的视频流
  3. 硬件加速:充分利用MediaCodec的硬件能力
  4. 低延迟优化:减少端到端延迟至200ms内

一个典型的动态参数调整实现:

public void adjustQuality(QualityLevel level) { switch (level) { case HIGH: setEncoderParams(1920, 1080, 4000000); break; case MEDIUM: setEncoderParams(1280, 720, 2500000); break; case LOW: setEncoderParams(854, 480, 1000000); break; } } private void setEncoderParams(int width, int height, int bitrate) { Bundle params = new Bundle(); params.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate); mediaCodec.setParameters(params); // 需要重新配置YUV处理逻辑 frameProcessor.updateFrameSize(width, height); }

在智能家居项目的实际应用中,我们发现夜间低光照条件下的视频质量明显下降。通过分析YUV直方图,最终采用动态调整对比度和降噪参数的方案,使夜间可辨识度提升40%。这种基于具体场景的优化,才是音视频开发真正的价值所在。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 21:59:56

终极安全指南:如何通过Khoj的魔法链接认证守护你的AI知识库

终极安全指南&#xff1a;如何通过Khoj的魔法链接认证守护你的AI知识库 【免费下载链接】khoj Your AI second brain. Self-hostable. Get answers from the web or your docs. Build custom agents, schedule automations, do deep research. Turn any online or local LLM in…

作者头像 李华
网站建设 2026/4/23 21:57:18

告别卡顿:Svelte 5中$derived与Map类型Store的终极响应式优化指南

告别卡顿&#xff1a;Svelte 5中$derived与Map类型Store的终极响应式优化指南 【免费下载链接】svelte web development for the rest of us 项目地址: https://gitcode.com/GitHub_Trending/sv/svelte Svelte 5作为一款革新性的前端框架&#xff0c;通过其独特的编译时…

作者头像 李华
网站建设 2026/4/23 21:54:26

终极指南:如何快速解决Slint UI框架中ComboBox点击崩溃问题

终极指南&#xff1a;如何快速解决Slint UI框架中ComboBox点击崩溃问题 【免费下载链接】slint Slint is an open-source declarative GUI toolkit to build native user interfaces for Rust, C, JavaScript, or Python apps. 项目地址: https://gitcode.com/GitHub_Trendin…

作者头像 李华
网站建设 2026/4/23 21:53:53

Kyoo扫描器工作原理:智能识别动漫名称与媒体文件

Kyoo扫描器工作原理&#xff1a;智能识别动漫名称与媒体文件 【免费下载链接】Kyoo A portable and vast media library solution. 项目地址: https://gitcode.com/gh_mirrors/ky/Kyoo Kyoo是一款功能强大的便携式媒体库解决方案&#xff0c;其核心组件之一——Kyoo扫描…

作者头像 李华