【音视频】MediaCodec 视频处理

这个系列文章我们来介绍一位海外工程师如何探索安卓音视频基础技术,对于想要开始学习音视频技术的朋友,这些文章是份不错的入门资料,本篇介绍 MediaCodec 视频处理。

——来自公众号“关键帧Keyframe”的分享

MediaCodec 是 Android 的底层多媒体 API,提供对硬件加速视频编码和解码功能的访问。在本篇博文中,我们将探讨 AVSample 仓库如何实现 MediaCodec 视频处理,涵盖编码和解码工作流程,并提供实际示例。

MediaCodec 是 Android 多媒体管道的基础,提供对设备硬件编码器和解码器的直接访问。与 MediaRecorder 等高层 API 不同,MediaCodec 为开发者提供了对视频处理管道的精细控制,支持自定义工作流程、实时处理以及与 OpenGL ES 集成以实现高级效果。

AVSample 仓库展示了 MediaCodec 视频处理的两种主要方法:

  1. 使用包装类的简化 Kotlin 实现
  2. 展示相机集成和 OpenGL ES 处理的全面 Java 实现

注意:MediaCodec 异步运行,需要仔细管理输入和输出缓冲区。始终正确处理 BUFFER_FLAG_CODEC_CONFIG 和 BUFFER_FLAG_END_OF_STREAM 标志,以确保流正确初始化和终止。

1、使用 WriteH264 进行 MediaCodec 视频编码

该仓库通过扩展基础 H264Encoder 类的 WriteH264 类提供了 H.264 编码的简单实现。此实现处理核心编码工作流程,同时为处理后的数据提供回调机制。

1.1、基本编码设置

要开始编码视频,需要使用适当的参数配置编码器:

val encoder = WriteH264()
encoder.prepare(context, VideoConfiguration.createDefault())
encoder.setOnEncodeListener(this)
encoder.start()

VideoConfiguration 类封装了基本编码参数,如分辨率、比特率、帧率和编解码器格式。这种抽象简化了配置过程,同时保持了灵活性。

1.2、处理编码数据

onVideoEncode 回调是编码后的 H.264 数据可用的地方。此方法展示了如何识别 H.264 流中的不同 NAL 单元类型:

override fun onVideoEncode(bb: ByteBuffer?, bi: MediaCodec.BufferInfo) {
    val h264Arrays = ByteArray(bi.size)
    bb?.position(bi.offset)
    bb?.limit(bi.offset + bi.size)
    bb?.get(h264Arrays)
    val tag = h264Arrays[4].and(0x1f).toInt()
    if (tag == 0x07) { // SPS
        LogHelper.e(TAG, " SPS " + h264Arrays.size)
    } elseif (tag == 0x08) { // PPS
        LogHelper.e(TAG, " PPS ")
    } elseif (tag == 0x05) { // IDR frame
        LogHelper.e(TAG, " 关键帧 " + h264Arrays.size)
    } else { // Regular frame
        LogHelper.e(TAG, " 普通帧 " + h264Arrays.size)
    }
    mFileOutputStream?.write(h264Arrays)
}

此实现自动将编码后的 H.264 数据写入文件,同时记录每个 NAL 单元的类型,便于验证编码过程是否正常工作。

2、使用 H264Decoder 进行 MediaCodec 视频解码

H264Decoder 类为 H.264 视频解码提供了简化的接口。虽然仓库中的实现很简洁,但它扩展了可能包含核心解码逻辑的 BaseVideoDecoder 类。

2.1、基本解码器使用

解码器遵循与编码器类似的模式,具有配置、启动和停止方法:

val decoder = H264Decoder()
decoder.configure(videoConfiguration)
decoder.start()

实际解码过程涉及将编码后的 H.264 数据提供给解码器并处理解码后的输出,通常渲染到 Surface 进行显示。

3、完整的编码-解码工作流程

H264MediaCodecEDActivity 展示了完整的编码-解码管道,展示了如何在实际应用中连接编码器和解码器。此活动实现了 SurfaceHolder.Callback 和 OnVideoEncodeListener 接口来管理视频处理生命周期。

3.1、工作流程架构

编码-解码工作流程遵循以下序列:

  1. 初始化编码器和解码器组件
  2. 使用视频参数配置编码器
  3. 用户启动录制时开始编码
  4. 实时将编码数据提供给解码器
  5. 将解码后的视频渲染到 Surface
  6. 在解码器中处理编码器输出

连接编码器和解码器的关键是在 onVideoEncode 回调中正确处理编码器输出:

override fun onVideoEncode(data: ByteBuffer?, info: MediaCodec.BufferInfo?) {
    if (data == null || info == null) return
    
    if (mH264Buffer == null || mH264Buffer?.size!! < info.size) {
        mH264Buffer = ByteArray(info.size)
    }
    data.position(info.offset)
    data.limit(info.offset + info.size)
    data.get(mH264Buffer, 0, info.size)

    if (info.flags and MediaCodec.BUFFER_FLAG_CODEC_CONFIG == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
        // SPS, PPS - configure decoder with format data
        mH264Decoder?.configure(
            VideoConfiguration.Builder()
                .setSurface(surface.holder.surface)
                .setSpsPpsBuffer(ByteBuffer.wrap(mH264Buffer, 0, info.size))
                .setCodeType(VideoConfiguration.ICODEC.DECODE)
                .build()
        )
    } else {
        // Video frame data - send to decoder for rendering
        mH264Decoder?.enqueue(
            mH264Buffer!!,
            info.presentationTimeUs,
            info.flags
        )
    }
}

此实现正确地将编解码器配置数据(SPS/PPS)与实际视频帧分开,确保在处理视频数据之前正确初始化解码器。

4、高级相机到 MPEG 编码

CameraToMpegTest 类展示了更高级的用例,从相机捕获视频,使用 OpenGL ES 处理,并将其编码为 MP4 文件。此实现展示了 MediaCodec 与其他 Android 多媒体 API 结合使用时的全部功能。

4.1、关键组件

  • 相机集成:使用 Camera.setPreviewTexture() 直接访问相机预览
  • OpenGL ES 处理:使用自定义片段着色器进行实时视频处理
  • Surface 输入:使用 MediaCodec 的输入 Surface 进行高效编码
  • MediaMuxer:将编码后的视频与适当的 MP4 容器格式结合

4.2、编码器配置

编码器使用相机捕获的特定参数进行配置:

MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
        MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);

这里的 COLOR_FormatSurface 颜色格式至关重要,因为它允许编码器直接与 Surface 输入一起工作,实现高效的 GPU 到编码器数据传输。

4.3、处理管道

相机到 MPEG 管道遵循此工作流程:

  1. 相机设置:使用与编码器分辨率匹配的适当预览大小配置相机
  2. Surface 创建:使用 CodecInputSurface 为编码器创建输入 Surface
  3. 纹理设置:创建 SurfaceTexture 以接收相机帧
  4. 处理循环:通过 OpenGL ES 持续处理相机帧并发送到编码器
  5. 混合:将编码后的帧与 MediaMuxer 结合以创建 MP4 文件

4.4、实时视频处理

此实现的一个关键功能是能够使用 OpenGL ES 片段着色器应用实时视频效果。该示例演示了简单的颜色通道交换效果:

private static final String SWAPPED_FRAGMENT_SHADER =
        "#extension GL_OES_EGL_image_external : require\n" +
        "precision mediump float;\n" +
        "varying vec2 vTextureCoord;\n" +
        "uniform samplerExternalOES sTexture;\n" +
        "void main() {\n" +
        "  gl_FragColor = texture2D(sTexture, vTextureCoord).gbra;\n" +
        "}\n";

此着色器可以在编码过程中动态应用,展示了如何在编码管道中实现实时视频效果。

5、性能考虑

在实现 MediaCodec 视频处理时,几个性能考虑至关重要:

5.1、缓冲区管理

高效处理输入和输出缓冲区对于流畅性能至关重要。CameraToMpegTest 中的 drainEncoder 方法演示了正确的缓冲区处理:

private void drainEncoder(boolean endOfStream) {
    final int TIMEOUT_USEC = 10000;
    ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers();
    while (true) {
        int encoderStatus = mEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);
        // Handle various encoder states and process output data
    }
}

5.2、同步

相机、OpenGL ES 和编码器线程之间的正确同步至关重要。该实现使用 SurfaceTexture.OnFrameAvailableListener 来协调帧处理。

5.3、内存管理

避免不必要的缓冲区复制。实现尽可能在组件之间直接传递 ByteBuffer 引用。

5.4、分辨率匹配

确保相机预览分辨率与编码器分辨率匹配以避免不必要的缩放。choosePreviewSize 方法演示了此优化。

6、MediaCodec 视频处理的最佳实践

基于 AVSample 仓库中的实现,以下是 MediaCodec 视频处理的关键最佳实践:

6.1、 尽可能使用 Surface 输入

对于编码,优先使用 createInputSurface() 而非手动缓冲区输入。这种方法更高效,并且可以与 OpenGL ES 无缝协作。

6.2、 单独处理配置数据

始终检查 BUFFER_FLAG_CODEC_CONFIG 以将 SPS/PPS 数据与视频帧分开处理。

6.3、 实现适当的错误处理

MediaCodec 操作可能因各种原因失败。实现健壮的错误处理和恢复机制。

6.4、 仔细管理生命周期

正确启动、停止和释放 MediaCodec 实例以避免资源泄漏。H264MediaCodecEDActivity 演示了 onDestroy() 中的正确生命周期管理。

6.5、 使用适当的超时

当使缓冲区出队时,使用合理的超时来平衡响应性和电池寿命。

6.6、 考虑异步处理

对于复杂的管道,考虑使用异步回调或单独线程来防止阻塞 UI 线程。

重要提醒:始终在多个设备上测试 MediaCodec 实现。不同的 Android 设备可能具有不同的编解码器功能和行为。AVSample 仓库提供了良好的基础,但可能需要针对生产用途进行设备特定调整。

7、结论

AVSample 仓库提供了 MediaCodec 视频处理的全面示例,从简单的编码-解码工作流程到带有 OpenGL ES 处理的高级相机集成。关键要点是:

  • MediaCodec 提供了对视频编码和解码的强大底层控制
  • 正确的缓冲区管理和生命周期处理对于稳定实现至关重要
  • 与其他 Android 多媒体 API(Camera、OpenGL ES、MediaMuxer)的集成实现了高级视频处理工作流程
  • 简化的 Kotlin 实现提供了易于访问的入口点,而 Java 实现展示了生产就绪的技术

通过理解这些实现并遵循概述的最佳实践,开发者可以为 Android 创建高效、高性能的视频处理应用程序。

学习和提升音视频开发技术,推荐你加入我们的知识星球:【关键帧的音视频开发圈】

【音视频】MediaCodec 视频处理

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论