安卓MediaCodec图片声音合成视频

以下是使用 Android MediaCodec 实现多张图片与短音频文件合成视频的完整代码流程,结合 Mermaid 流程图说明核心步骤:


📽️ ​完整代码流程 (Kotlin)​

scss 复制代码
// 1. 初始化视频编码器 & 音频编码器
val videoCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC).apply {
    val format = MediaFormat.createVideoFormat(...).apply {
        setInteger(MediaFormat.KEY_BIT_RATE, 5000000)
        setInteger(MediaFormat.KEY_FRAME_RATE, 30)
        setInteger(KEY_COLOR_FORMAT, COLOR_FormatSurface) // 使用 Surface 渲染
    }
    configure(format, null, null, CONFIGURE_FLAG_ENCODE)
    val surface = createInputSurface() // 获取渲染 Surface
    start()
}

val audioCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC).apply {
    val format = MediaFormat.createAudioFormat(...).apply {
        setInteger(KEY_BIT_RATE, 128000)
        setInteger(KEY_SAMPLE_RATE, 44100)
    }
    configure(format, null, null, CONFIGURE_FLAG_ENCODE)
    start()
}

// 2. 配置 MediaMuxer (封装视频+音频)
val muxer = MediaMuxer(outputPath, MUXER_OUTPUT_MPEG_4)
var videoTrackIndex = -1
var audioTrackIndex = -1

// 3. 处理视频帧 (图片→Surface)
imagePaths.forEachIndexed { index, path ->
    val bitmap = BitmapFactory.decodeFile(path)
    val canvas = surface.lockCanvas(null)
    canvas.drawBitmap(bitmap, null, Rect(0, 0, width, height), null)
    surface.unlockCanvasAndPost(canvas)

    // 设置时间戳 (单位微秒)
    val pts = index * 1_000_000L / frameRate 

    // 提取编码后的视频数据
    drainEncoder(videoCodec, muxer, videoTrackIndex, pts, isVideo = true)
}

// 4. 处理音频数据 (示例:短音频 PCM 输入)
val audioBuffer = readAudioFile(audioPath) // 读取音频 PCM 数据
val audioInfo = MediaCodec.BufferInfo().apply {
    presentationTimeUs = 0 // 音频起始时间戳
    size = audioBuffer.size
}
drainEncoder(audioCodec, muxer, audioTrackIndex, audioInfo, isVideo = false)

// 5. 资源释放
videoCodec.stop()
audioCodec.stop()
muxer.stop()
videoCodec.release()
audioCodec.release()
muxer.release()

// 辅助函数:提取编码数据并写入 Muxer
fun drainEncoder(
    codec: MediaCodec, 
    muxer: MediaMuxer, 
    trackIndex: Int, 
    info: BufferInfo, 
    isVideo: Boolean
) {
    while (true) {
        val outputIndex = codec.dequeueOutputBuffer(info, TIMEOUT_US)
        if (outputIndex >= 0) {
            val buffer = codec.getOutputBuffer(outputIndex)
            muxer.writeSampleData(trackIndex, buffer, info) // 写入封装器
            codec.releaseOutputBuffer(outputIndex, false)
        } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            // 首次获取轨道格式
            if (isVideo) videoTrackIndex = muxer.addTrack(codec.outputFormat)
            else audioTrackIndex = muxer.addTrack(codec.outputFormat)
            if (videoTrackIndex >= 0 && audioTrackIndex >= 0) muxer.start()
        }
    }
}

🔄 ​Mermaid 流程图

flowchart TD S((开始)) --> I["初始化编码器"] I --> V["配置视频 Surface"] I --> A["配置音频 PCM"] V --> R["图片渲染到 Surface"] R --> E1["编码视频帧"] A --> E2["编码音频数据"] E1 --> M["MediaMuxer 封装"] E2 --> M M --> D{"全部帧完成?"} D -->|是| C["释放资源"] D -->|否| R C --> F["输出视频文件"]

⚙️ ​关键实现细节

  1. 视频编码初始化

    • 必须使用 COLOR_FormatSurface 避免兼容性问题
    • 时间戳计算:pts = 帧序号 × (1_000_000 / 帧率)
  2. 音频处理

    • 音频需转换为 PCM 格式输入编码器
    • 音频时间戳从 0 开始,与视频同步需对齐起始点
  3. 同步机制

    • 视频同步:按帧率均匀分配时间戳
    • 音视频对齐:确保音频总时长 ≥ 视频时长,不足则循环填充
  4. MediaMuxer 启动时机

    • 需等待视频和音频轨道格式均获取后(INFO_OUTPUT_FORMAT_CHANGED)再启动

⚠️ ​注意事项

  1. 线程管理

    • 编码操作需在后台线程执行
    • 使用 HandlerThread 避免阻塞 UI
  2. 资源释放

    • 严格按顺序释放:先停止编码器 → 停止 Muxer → 释放资源
  3. 兼容性处理

    • 检测设备支持的编码格式:

      lua 复制代码
      MediaCodecList.findEncoderForFormat(format)
  4. 音频截断

    • 若音频短于视频,需在 drainEncoder 中补静音帧或循环音频
相关推荐
聚美智数11 分钟前
护照OCR识别-护照图像识别-护照OCR文字识别-护照识别OCR-护照信息识别-护照OCR
android·ocr
码农coding2 小时前
android12 点击桌面应用图标启动Activity流程
android
johnsong2 小时前
深度拆解:150M循环模型如何用循环推理击败大Transformer
android·深度学习·transformer
CedarQR3 小时前
云卓 G20 遥控器 Android 开发实录:从 USB 设备到读取摇杆/按键通道值
android·linux·无人机
pengyu4 小时前
【Kotlin 协程修仙录 · 炼虚境 · 初阶】 | 虚空造物:Channel 基础与协程间通信的管道艺术
android·前端·kotlin
小强闯江湖4 小时前
ViewCompose:让原生 Android View 进入声明式时代
android·开源·kotlin
摇滚侠6 小时前
《SpringBoot 3:入门与应用实战》第 6 章 Spring Boot 最佳实践 阅读笔记 12
android·spring boot·笔记
mmsx7 小时前
Android 宠物健康服务平台实战:从架构设计到主题换肤完整解析(源码和文档)
android·架构·sqlite·app·源码·宠物
、如果7 小时前
ima Skills 自进化实战:三层结构、反馈与版本验证
android·人工智能·ai编程
hunterandroid7 小时前
自定义 View 绘制与触摸冲突:从滑动卡顿到事件分发闭环
android·前端