专栏首页雪月清的随笔如何使用MediaCodec解码音视频

如何使用MediaCodec解码音视频

播放一个音视频文件的时候,我们知道需要经过解协议->解封装->解码音频/视频->音频/视频同步->渲染播放这几个步骤,其中解码音频/视频是整个流程中最核心的一个环节.每个步骤的详细解释可以参考上篇文章Android中如何使用OpenGL播放视频 Android平台下解码音视频可以采用软件解码如ffmpeg,或使用硬件解码如MediaCodec来实现软件解码:利用CPU进行解码处理,这种方式会加大CPU负担并增加功耗,它的优点则是具有更强的适配性;硬件解码:调用GPU的专门解码音视频的模块来处理,减少CPU运算,降低功耗.由于Android机型碎片化比较严重,硬件解码的实现又依赖于具体的厂商,所以硬件解码的适配性并不是那么友好一般而言,在Android设备支持硬解的情况下优先使用Android设备的硬件解码,减少CPU占用,降低功耗;在硬解不支持的情况下选择使用软解码,至少让音视频能正常播放. 软硬结合,才是王道->_-> 当然,本篇文章所描述的是使用硬件解码MediaCodec的方式来解码一个视频文件. MediaCodec简介 android.media.MediaCodec是从API16开始由Android提供的供开发者能更加灵活的处理音视频的编解码组件,与MediaPlayer/MediaRecorder等high-level组件相比,MediaCodec能让开发者直接处理具体的音视频数据,所以它是low-level API它通常与MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface和AudioTrack一起使用. 基本架构

MediaCodec拥有一组输入输出缓冲队列,采用异步的方式来处理数据.
处理流程:请求或接收到一个空的输入缓冲(input buffer);向输入缓冲中填充数据;将输入缓冲传递给编解码器处理;编解码器处理完这些数据并将处理结果输出至一个空的输出缓冲(output buffer)中;请求或接收到一个填充了结果数据的输出缓冲(output buffer);使用输出缓冲中的数据;将输出缓冲释放给编解码器再次使用.
生命周期
当使用工厂方法创建一个编解码器时,它处于未初始化状态(Uninitialized),调用configure方法对编解码器进行配置后,它处于配置状态(Configured),然后调用start方法让编解码器进入执行状态(Executing)
在执行状态时,我们就可以通过上面描述的流程步骤来处理数据了
执行状态(Executing)包含刷新(Flushed),运行(Running),流结束(End of Streaming)三个子状态

当调用编解码器的start方法后,它进入执行状态中的刷新(Flushed)子状态;

从第一个输入缓冲被移出队列的时候,它进入运行(Running)子状态,编解码器的大部分生命周期都处于这个状态;

当一个输入缓冲被标记为end-of-stream并入队时,它进入流结束(End of Streaming)子状态,此后编解码器将不再接受新的输入缓冲,但输出缓冲是能继续产生的,直到end-of-stream标记到达输出端;

可以调用stop方法结束执行状态,编解码器将回到未初始化状态(Uninitialized)

当不再使用编解码器时,必须调用release方法释放相关资源

MediaExtractor简介
在使用MediaCodec解码音频/视频的时候,首先需要获取编码后的音频/视频数据.
这里我们引入MediaExtractor,它可以分离mp4,flv生成视频h264/mpeg和音频mp3或aac(无adts头)
MediaExtractor的用法很简单,参考官方文档的举例即可
MediaCodec解码音视频
解码视频创建视频解码器
val trackFormat = mediaExtractor.getTrackFormat(videoTrackIndex)
val type = trackFormat.getString(MediaFormat.KEY_MIME)!!
val videoCodec = MediaCodec.createDecoderByType(type)

配置解码器format,并指定渲染输出的surface

val surface = surfaceView.holder.surface
videoCodec.configure(trackFormat, surface, null, 0)
启动解码器
videoCodec.start()
从MediaExtractor读取数据并填充到解码器的输入缓冲
// request input buffer
val inputIndex = videoCodec.dequeueInputBuffer(timeout)
if (inputIndex < 0) {
    break
}
val inputBuffer = videoCodec.getInputBuffer(inputIndex) ?: continue
// clear old data
inputBuffer.clear()
// read data to input buffer
val sampleSize = mediaExtractor.readSampleData(inputBuffer, 0)
if (sampleSize < 0) {
    // mark end-of-stream to input buffer
    videoCodec.queueInputBuffer(inputIndex,
                0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM)
} else {
    // queue input buffer
    val sampleTime = mediaExtractor.sampleTime
    videoCodec.queueInputBuffer(inputIndex, 0, sampleSize, sampleTime, 0)
    // advance to the next sample
    mediaExtractor.advance()
}
从解码器输出缓冲中拿到解码后的视频数据进行处理
val outputIndex = videoCodec.dequeueOutputBuffer(decodeBufferInfo, timeout)
if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
    break
} else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    // val newFormat = videoCodec.outputFormat
} else if (outputIndex < 0) {
    // do nothing
} else {
    val outputBuffer = videoCodec.getOutputBuffer(outputIndex) ?: continue
    // process decoded data
    // ....

    sync(decodeBufferInfo, startMs)
    videoCodec.releaseOutputBuffer(outputIndex, true)
    if ((decodeBufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
         Log.d(TAG, "codecVideoToSurface: -------------------finish")
         break
}
视频解码的时候,我们需要根据视频帧的时间戳来做同步,否则视频帧将很快的进行渲染播放
private fun sync(info: MediaCodec.BufferInfo, startMs: Long) {
    val timeDiff = info.presentationTimeUs / 1000 - (System.currentTimeMillis() - startMs)
    if (timeDiff > 0) {
        try {
            Thread.sleep(timeDiff)
        } catch (e: InterruptedException) {
            e.printStackTrace()
        }
    }
}
解码流程结束时
videoCodec.stop()
当不需要再使用解码器时,及时释放资源
videoCodec.release()
解码音频解码音频的步骤和解码视频的步骤是类似的,此处就不再赘述了,如果想要播放解码出来的音频PCM数据,可以使用AudioTrack,详情可以参考Demo代码

当我们通过MediaCodec解码出来了音频/视频数据后,可以做一些后期处理,比如多个音频的混音等

DEMO传送门:https://github.com/sifutang/Audio.git

本文分享自微信公众号 - 雪月清的随笔(gh_b3d66920f13b),作者:雪月青

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 如何使用OpenGL渲染YUV数据

    本篇文章主要描述如何使用OpenGL ES来渲染i420(YUV420P)和nv21(YUV420SP)

    雪月清
  • 滤镜之LUT

    滤镜基本是相机或者图像处理软件中的标配功能,它能对图像实现各种特殊效果,比如iPhone中的滤镜功能:

    雪月清
  • 数字图像处理领域中常见的几种色彩模式

    在数字图像处理过程中,常见的几种色彩模式有RGB, HSL\HSV和YCbCr RGB: 通过对红(R), 绿(G), 蓝(B)三个颜色通道的变化和叠加来得到其...

    雪月清
  • 无讼创始人蒋勇:法律数据的结构化特征让人工智能的应用有了更好的基础

    无讼创始人 蒋勇 企业经营的方方面面都和法律有关,法律能力必不可少。据统计,中国有4000万企业,其中只有不到1%的企业拥有法务,绝大多数企业,尤其是中小企业,...

    数据猿
  • API管理的正确姿势--API Gateway

    数字化生态,以创新客户体验为核心,所有我们身边能感知到的变化都来自于渐近的创新。这些创新需要试错,需要不断的升级,并且创新往往与我们熟知的功能分离开来分别呈现。...

    yuanyi928
  • 文本挖掘系列文章1

    自然语言处理是计算机科学领域与人工智能领域中的一个重要方向。它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。自然语言处理是一门融语言学、计算机...

    伏草惟存
  • API-First,Kubernetes上微服务的一种方法

    对那些曾经使用更传统方式构建应用的开发者来说,转向容器化微服务不是一个容易的转变。当开发者设计分布式应用时,微服务应用也正是分布式的,其中有许多新的概念和细节需...

    zqfan
  • 新型Power9处理器 针对AI和机器学习而开发

    目前全球需要越来越大的计算能力来处理像人工智能和机器学习这样的资源密集型工作负载,IBM公司以其最新一代Power芯片 - Power9进入了竞争。该公司打算向...

    企鹅号小编
  • c语言-转义序列

    landv
  • 如何开发QQ、微信第三方登录?

    互联网应用当中,我们的应用会使用多个第三方账号进行登录,比如:网易、微信、QQ等,我们把此称为多账户统一登陆。通过这篇文章, 我想阐释多账户登陆的技术方案细节,...

    闫小林

扫码关注云+社区

领取腾讯云代金券