安卓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

相关推荐
教程分享大师1 小时前
中兴B860AV5.2-U_S905L3SB安卓9.0系统带root权限当贝纯净版线刷包
android
_小马快跑_2 小时前
Android | LiveData 与 Flow 的异同点对比
android
whysqwhw5 小时前
安卓MediaCodec实现视频文件的转码压缩
android
沅霖8 小时前
下载Android studio
android·ide·android studio
xzkyd outpaper9 小时前
Kotlin 协程线程切换机制详解
android·开发语言·kotlin
Near_Li10 小时前
uniapp-使用mumu模拟器调试安卓APP
android·uni-app
zhangphil11 小时前
Android MediaMetadataRetriever取视频封面,Kotlin(1)
android·kotlin
onthewaying13 小时前
详解 Android GLSurfaceView 与 Renderer:开启你的 OpenGL ES 之旅
android·opengl
aqi0014 小时前
FFmpeg开发笔记(八十)使用百变魔音AiSound实现变声特效
android·ffmpeg·音视频·直播·流媒体