前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何使用MediaCodec解码音视频

如何使用MediaCodec解码音视频

作者头像
雪月清
发布2020-06-23 16:20:25
2.2K0
发布2020-06-23 16:20:25
举报
文章被收录于专栏:雪月清的随笔雪月清的随笔

播放一个音视频文件的时候,我们知道需要经过解协议->解封装->解码音频/视频->音频/视频同步->渲染播放这几个步骤,其中解码音频/视频是整个流程中最核心的一个环节.每个步骤的详细解释可以参考上篇文章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一起使用. 基本架构

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

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

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

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

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

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

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

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

代码语言:javascript
复制
val surface = surfaceView.holder.surface
videoCodec.configure(trackFormat, surface, null, 0)
代码语言:javascript
复制
启动解码器
代码语言:javascript
复制
videoCodec.start()
代码语言:javascript
复制
从MediaExtractor读取数据并填充到解码器的输入缓冲
代码语言:javascript
复制
// 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()
}
代码语言:javascript
复制
从解码器输出缓冲中拿到解码后的视频数据进行处理
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()
当不需要再使用解码器时,及时释放资源
代码语言:javascript
复制
videoCodec.release()
代码语言:javascript
复制
解码音频解码音频的步骤和解码视频的步骤是类似的,此处就不再赘述了,如果想要播放解码出来的音频PCM数据,可以使用AudioTrack,详情可以参考Demo代码

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

DEMO传送门:https://github.com/sifutang/Audio.git
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 雪月清的随笔 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档