安卓MediaCodec录像功能

以下是基于 Camera2 API + MediaCodec + MediaMuxer 实现摄像头画面编码到 MP4 文件的全链路代码及 Mermaid 流程图,结合最佳实践和关键注意事项。


📽️ ​全流程代码实现(Activity 级)​​

1. ​权限声明与初始化​

xml 复制代码
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

2. ​Activity 核心代码​

scss 复制代码
public class CameraRecorderActivity extends AppCompatActivity {
    private CameraDevice cameraDevice;
    private MediaCodec mediaCodec;
    private MediaMuxer mediaMuxer;
    private Surface encoderSurface;
    private int videoTrackIndex = -1;
    private boolean isMuxerStarted = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);
        // 1. 初始化 MediaCodec 和 MediaMuxer
        initMediaCodec(1280, 720); // 分辨率 720p
        // 2. 检查相机权限(代码略)
    }

    // 初始化编码器与混合器
    private void initMediaCodec(int width, int height) {
        try {
            // 配置 MediaCodec
            mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
            MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height);
            format.setInteger(MediaFormat.KEY_BIT_RATE, 4_000_000); // 4 Mbps
            format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, 
                             MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface); // Surface输入
            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); // 关键帧间隔1秒
            mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            encoderSurface = mediaCodec.createInputSurface(); // 获取编码器输入Surface
            mediaCodec.start();

            // 初始化 MediaMuxer
            String outputPath = getExternalFilesDir(null) + "/video.mp4";
            mediaMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
            
            // 设置异步回调
            mediaCodec.setCallback(new MediaCodecCallback());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 3. 启动 Camera2 会话
    private void startCameraSession() {
        CameraManager manager = (CameraManager) getSystemService(CAMERA_SERVICE);
        try {
            String cameraId = manager.getCameraIdList()[0];
            manager.openCamera(cameraId, new CameraDevice.StateCallback() {
                @Override
                public void onOpened(@NonNull CameraDevice camera) {
                    cameraDevice = camera;
                    // 创建预览 + 编码的 Surface 列表
                    List<Surface> surfaces = new ArrayList<>();
                    surfaces.add(previewSurface); // 预览Surface(TextureView)
                    surfaces.add(encoderSurface); // 编码器Surface
                    
                    // 创建捕获会话
                    cameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() {
                        @Override
                        public void onConfigured(@NonNull CameraCaptureSession session) {
                            // 开始连续预览请求
                            try {
                                CaptureRequest request = session.getDevice().createCaptureRequest(
                                    CameraDevice.TEMPLATE_PREVIEW);
                                request.addTarget(previewSurface);
                                request.addTarget(encoderSurface); // 同时输出到编码器
                                session.setRepeatingRequest(request.build(), null, null);
                            } catch (CameraAccessException e) {
                                e.printStackTrace();
                            }
                        }
                    }, null);
                }
            }, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    // 4. MediaCodec 异步回调处理编码数据
    private class MediaCodecCallback extends MediaCodec.Callback {
        @Override
        public void onInputBufferAvailable(MediaCodec codec, int index) {} // Surface模式无需实现

        @Override
        public void onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info) {
            ByteBuffer buffer = codec.getOutputBuffer(index);
            if (info.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                // 忽略编解码配置数据
                codec.releaseOutputBuffer(index, false);
                return;
            }

            if (buffer != null && info.size > 0) {
                buffer.position(info.offset);
                buffer.limit(info.offset + info.size);
                if (isMuxerStarted) {
                    mediaMuxer.writeSampleData(videoTrackIndex, buffer, info);
                }
            }
            codec.releaseOutputBuffer(index, false);
        }

        @Override
        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
            // 编码器格式变化时启动 Muxer
            videoTrackIndex = mediaMuxer.addTrack(format);
            mediaMuxer.start();
            isMuxerStarted = true;
        }
    }

    // 5. 释放资源
    private void releaseResources() {
        if (cameraDevice != null) {
            cameraDevice.close();
            cameraDevice = null;
        }
        if (mediaCodec != null) {
            mediaCodec.stop();
            mediaCodec.release();
        }
        if (mediaMuxer != null) {
            if (isMuxerStarted) {
                mediaMuxer.stop();
            }
            mediaMuxer.release();
        }
    }

    @Override
    protected void onDestroy() {
        releaseResources();
        super.onDestroy();
    }
}

📊 ​Mermaid 流程图​

flowchart TD A[启动Activity] --> B[初始化MediaCodec] B --> C[创建编码器输入Surface] C --> D[启动Camera2会话] D -->|"绑定预览Surface + 编码Surface"| E[摄像头数据流] E -->|"YUV帧→编码器"| F[MediaCodec编码H.264] F --> G{编码输出回调} G -->|"INFO_OUTPUT_FORMAT_CHANGED"| H[启动MediaMuxer] G -->|"有效数据帧"| I[写入MP4文件] H --> I I --> J[释放缓冲区] J --> G K[停止录制] --> L[释放Camera/Codec/Muxer]

⚠️ ​关键技术与注意事项​

  1. ​颜色格式匹配​

    • Camera2 默认输出 YUV_420_888,需通过 COLOR_FormatSurface 直接输入编码器,避免手动转换。
  2. ​MediaMuxer 启动时机​

    • 必须在 onOutputFormatChanged() 回调中启动 Muxer,否则写入失败。
  3. ​时间戳同步​

    • Camera2 自动生成帧时间戳(System.nanoTime()),无需手动计算。
  4. ​资源释放顺序​

    scss 复制代码
    cameraDevice.close() → mediaCodec.stop() → mediaMuxer.stop() → mediaCodec.release() → mediaMuxer.release()

    反向操作会导致崩溃或文件损坏。

  5. ​低端设备优化​

    • 降低分辨率(如 720p→480p)或使用 BITRATE_MODE_CQ(质量优先模式)。

💡 ​扩展场景方案​

  • 音视频同步录制 :
    添加 AudioRecord + MediaCodec 音频编码,在 Muxer 中同步写入音轨。
  • 屏幕录制 :
    替换 Camera2 为 MediaProjection,通过 VirtualDisplay 绑定到 encoderSurface。
  • 实时直播 :
    将 MediaMuxer 替换为 RTMP 封包库(如 libRtmp),直接推送编码数据。

完整可运行项目参考:Google Grafika 示例 或 AudioVideoRecordingSample。

相关推荐
Carson带你学Android9 小时前
Gemini 4 Argon 发布:对 Android 开发者意味着什么?
android·ai编程
事圆则缓10 小时前
Kotlin Flow、StateFlow、SharedFlow 全解析:冷流、热流与 Android 状态管理
android·kotlin·php
应用市场10 小时前
把旧 Pixel 变成相册备份中转站(上):Mac 到安卓的照片传输工具设计——流式上传、sha256 校验、adb forward 与 Bonjour
android·macos·adb·kotlin·swift
墨天梦11 小时前
B08_Fragment事务通信与双生命周期
android
Kapaseker11 小时前
Android 以后可能不会再有横竖屏适配了
android·kotlin
墨天梦11 小时前
B13_通知图片相机与影音
android·数码相机·kotlin
事圆则缓12 小时前
Android 一次性事件怎么设计:Channel、SharedFlow 还是 UI State?
android·ui
Android系统攻城狮1 天前
Linux Gstreamer深度解析之gst_audio_encoder_set_frame_max调用流程与实战(六十一)
android·linux·运维·音视频·gstreamer音视频·音视频进阶·gstreamer音视频进阶
传奇开心果编程1 天前
【Compose Multiplatform 跨端开发学与练】第7课 平台适配与互操作
android·windows·学习·ui·ios·kotlin·composer
mmsx1 天前
Android 地图数据链路:从 GDAL、KML 到多引擎适配
android·kotlin