MediaCodec解析MP4视频

article/2025/10/6 17:45:04

MediaCodec讲解

MediaCodec是Android提供的用于对音视频进行编解码的类,它通过访问底层的codec来实现编解码的功能。是Android media基础框架的一部分,通常和 MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface和AudioTrack一起使用。

MediaCodec支持的数据类型

编解码器支持的数据类型:压缩的音视频数据,原始音频数据和原始视频数据

  • 数据通过ByteBuffers类来表示。

  • 可以设置Surface来获取/呈现原始的视频数据,Surface使用本地的视频buffer,不需要进行ByteBuffers拷贝。可以让编解码器的效率更高。

  • 通常在使用Surface的时候,无法访问原始的视频数据,但是可以使用ImageReader访问解码后的原始视频帧。在使用ByteBuffer的模式下,可以使用Image类和getInput/OutputImage(int)获取原始视频帧。

压缩的音视频数据
  • 对于视频类型,这通常是一个压缩视频帧。
  • 对于音频数据,这通常是单个访问单元(通常包含由格式类型的指定的几毫秒的音频段(通常包含几毫秒的音频),但是该要求略微放松,因为一个buffer可以包含多个编码的音频访问单元。
  • 在以上两种情况下,buffer都不在任意字节边界上启动或结束,而是在帧/访问单元边界上启动或结束,除非它们被BUFFER_FLAG_PARTIAL_FRAME标记。
原始音频数据

原始音频buffer包含PCM音频数据的整个帧,这是每个通道按通道顺序的一个样本。每个样本都是一个 AudioFormat#ENCODING_PCM_16BIT。

原始视频数据

在ByteBuffer模式下,视频buffer根据它们的MediaFormat#KEY_COLOR_FORMAT进行布局。可以从getCodecInfo(). MediaCodecInfo.getCapabilitiesForType.CodecCapability.colorFormats获取支持的颜色格式。视频编解码器可以支持三种颜色格式:

  • native raw video format: CodecCapabilities.COLOR_FormatSurface,可以与输入/输出的Surface一起使用。
  • flexible YUV buffers 例如CodecCapabilities.COLOR_FormatYUV420Flexible, 可以使用getInput/OutputImage(int)与输入/输出Surface一起使用,也可以在ByteBuffer模式下使用。
  • other, specific formats: 通常只支持ByteBuffer模式。有些颜色格式是厂商特有的,其他定义在CodecCapabilities。对于等价于flexible格式的颜色格式,可以使用getInput/OutputImage(int)。

从Build.VERSION_CODES.LOLLIPOP_MR1.开始,所有视频编解码器都支持flexible的YUV 4:2:0 buffer。

MediaCodec状态与生命周期

在这里插入图片描述

MediaCodec生命周期状态分为三种 Stopped、Executing和Released
其中Stopped包含三种子状态 Uninitialized(为初始化状态)、Configured(已配置状态)、Error(异常状态)
Executing也包含三个子状态 Flushed(刷新状态)、Running(运行状态)和EOS(流结束状态)

Stopped状态:
  • Uninitialized:当使用工厂方法创建了一个MediaCodec对象,此时处于Uninitialized状态。可以在任何状态调用reset()方法使MediaCodec返回到Uninitialized状态
  • Configured:使用configure(…)方法对MediaCodec进行配置转为Configured状态
  • Error:MediaCodec遇到错误时进入Error状态。错误可能是在队列操作时返回的错误或者异常导致的。
Executing状态:

当调用了mediaCodec.start()方法后,就由stopped到Executing状态了,在此状态下,可以通过上面描述的缓冲队列操作来处理数据

  • Flushed:在调用start()方法后MediaCodec立即进入Flushed子状态,此时MediaCodec会拥有所有的缓存。可以在Executing状态的任何时候通过调用flush()方法返回到Flushed子状态。
  • Running:一旦第一个输入缓存(input buffer)被移出队列,MediaCodec就转入Running子状态,这种状态占据了MediaCodec的大部分生命周期。通过调用stop()方法转移到Uninitialized状态。
  • EOS:将一个带有end-of-stream标记的输入buffer入队列时,MediaCodec将转入End-of-Stream子状态。在这种状态下,MediaCodec不再接收之后的输入buffer,但它仍然产生输出buffer直到end-of-stream标记输出。
Released状态

当使用完MediaCodec后,必须调用release()方法释放其资源。调用 release()方法进入最终的Released状态。

工作原理和基本流程

在这里插入图片描述

来看这个图片,这张图片就是MediaCodec的工作原理。简单讲一下就是。

  • 数据的生产方(左侧的Client)从input缓冲队列申请empty buffer,然后把要处理的数据,填充到这些empty buffer里面。就是上面的空方块(empty buffer),经过Client(数据生成方)之后,变成了红色实心的方块(有数据的buffer)

  • 这些数据经过Codec处理之后,然后处理后的数据(黄色的方块)放入到右侧output缓冲区队列

  • 消费方Client(右侧Client)从output缓冲区队列申请处理后的buffer,然后进一步处理,最后再将改buffer放回缓冲队列。

接下来,就以解析mp4的视频为例。讲解一下mediaCodec的解码视频轨道的过程。各个功能看文中的代码注释,一些异常处理,这里就暂时不考虑了。

这里大概讲一下mp4解码的过程(代码参考grafika)

  1. 创建MediaExtractor(),并设置数据源,MediaExtractor类,可以用来分离容器中的视频track和音频track。然后选中音频轨道
  2. 通过MediaCodec.createDecoderByType解码器
  3. 调用decoder的configure与start函数
  4. 调用decoder.dequeueInputBuffera得到inputBufIndex
  5. decoder.getInputBuffer(inputBufIndex)得到inputBuf
  6. 调用 extractor.readSampleData填充inputBuf
  7. 调用extractor.advance()移动到下一个样本
  8. 调用decoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC)得到decoderStatus,这里是消费者消费数据
  9. 调用decoder.releaseOutputBuffer(decoderStatus,doRender),释放输出的Buffer空间
package com.example.videodemoimport android.media.MediaCodec
import android.media.MediaExtractor
import android.media.MediaFormat
import android.os.Handler
import android.os.Message
import android.util.Log
import android.view.Surface
import java.io.File
import java.io.FileNotFoundException
import java.io.IOExceptionclass MediaCodecDemo constructor(val path: File, val outputSurface: Surface,val callback: SpeedControlCallback) {private val TAG: String = "MediaCodecDemo"var mVideoHeight = -1var mVideoWidth = -1private val mBufferInfo = MediaCodec.BufferInfo() //输出buffer的metadatavar mLoop = falseinit {var extractor: MediaExtractor? = nulltry {extractor = MediaExtractor()extractor.setDataSource(path.toString())val trackIndex = selectVideoTrack(extractor)if (trackIndex < 0) {throw RuntimeException("找不到视频轨道")}extractor.selectTrack(trackIndex)val format = extractor.getTrackFormat(trackIndex)mVideoHeight = format.getInteger(MediaFormat.KEY_HEIGHT)mVideoWidth = format.getInteger(MediaFormat.KEY_WIDTH)} finally {extractor?.release()}}//寻找视频轨道,并返回对应的indexprivate fun selectVideoTrack(extractor: MediaExtractor): Int {val numTracks = extractor.trackCountLog.d("hch","index $numTracks")for (index in 0 until numTracks) {val format = extractor.getTrackFormat(index)val mime = format.getString(MediaFormat.KEY_MIME)mime?.let {if (it.startsWith("video/")) {Log.d("hch","index $index")return index}}}return -1}private fun play() {var extractor: MediaExtractor? = nullvar decoder: MediaCodec? = nulltry {extractor = MediaExtractor()extractor.setDataSource(path.toString())//文件不存在,直接抛出异常if (!path.exists()) {throw FileNotFoundException("file donot exists")}val trackIndex = selectVideoTrack(extractor)if (trackIndex < 0) {throw RuntimeException("video track dont find")}extractor.selectTrack(trackIndex)val format = extractor.getTrackFormat(trackIndex)val mine = format.getString(MediaFormat.KEY_MIME)decoder = mine?.let { MediaCodec.createDecoderByType(it) }decoder?.let {decoder?.configure(format, outputSurface, null, 0)decoder?.start()doFrame(extractor!!, trackIndex, it)}} catch (e:IOException){}finally {decoder?.stop()decoder?.release()decoder = nullextractor?.release()extractor = null}}private fun doFrame(extractor: MediaExtractor, trackIndex: Int, decoder: MediaCodec) {val TIMEOUT_USEC = 10000Lvar inputChunk = 0var inputDone = falsevar outputDone = falsewhile (!outputDone) {//获取输入buff的空间if (!inputDone) {//得到inputBufIndexval inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC)if (inputBufIndex >= 0) {//得到对应的inputBufferval inputBuf = decoder.getInputBuffer(inputBufIndex)inputBuf?.let {//数据生成方填充对应的inputBufval chunkSize = extractor.readSampleData(it, 0)if (chunkSize < 0) {//读到文件末decoder.queueInputBuffer(inputBufIndex,0,0,0L,MediaCodec.BUFFER_FLAG_END_OF_STREAM)//推出循环inputDone = true} else {//val presentationTimeUs = extractor.sampleTimedecoder.queueInputBuffer(inputBufIndex,0,chunkSize,presentationTimeUs,0)inputChunk++//移动到下一个样本extractor.advance()}}} else {Log.d("hch", "input buffer not available")}}if (!outputDone) {//获取Codec的数据val decoderStatus = decoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC)when {decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER -> {Log.d("hch", "no output from decoder available")}decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED -> {val newFormat = decoder.outputFormatLog.d(TAG, "decoder output format changed: $newFormat")}decoderStatus < 0 -> {throw RuntimeException("unexpected result from decoder.dequeueOutputBuffer: $decoderStatus")}else -> {// decoderStatus >= 0var doLoop = falseif (mBufferInfo.flags.and(MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {if (mLoop) {doLoop = true} else {outputDone = true}}val doRender = (mBufferInfo.size != 0)//控制速率if (doRender){callback.preRender(mBufferInfo.presentationTimeUs)}//释放资源decoder.releaseOutputBuffer(decoderStatus,doRender)if (doRender ) {callback.postRender()}if (doLoop){Log.d(TAG,"")extractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC)inputDone = falsedecoder.flush()}}}}}}class PlayTask(player: MediaCodecDemo,) :Runnable {private val mPlayer: MediaCodecDemoprivate var mDoLoop = falseprivate var mThread: Thread? = nullprivate val mLocalHandler: LocalHandlerprivate val mStopLock = java.lang.Object()private var mStopped = falsefun setLoopMode(loopMode: Boolean) {mDoLoop = loopMode}fun execute() {mThread = Thread(this, "Movie Player")mThread?.start()}fun waitForStop() {synchronized(mStopLock) {while (!mStopped) {try {mStopLock.wait()} catch (ie: InterruptedException) {// discard}}}}override fun run() {try {mPlayer.play()} catch (ioe: IOException) {throw RuntimeException(ioe)} finally {// tell anybody waiting on us that we're donesynchronized(mStopLock) {mStopped = truemStopLock.notifyAll()}}}private class LocalHandler : Handler() {override fun handleMessage(msg: Message) {val what = msg.whatwhen (what) {MSG_PLAY_STOPPED -> {val fb: PlayerFeedback = msg.obj as PlayerFeedbackfb.playbackStopped()}else -> throw RuntimeException("Unknown msg $what")}}}companion object {private const val MSG_PLAY_STOPPED = 0}/*** Prepares new PlayTask.** @param player The player object, configured with control and output.* @param feedback UI feedback object.*/init {mPlayer = playermLocalHandler = LocalHandler()}}}interface PlayerFeedback {fun playbackStopped()
}
interface FrameCallback {fun preRender(presentationTimeUsec: Long)fun postRender()fun loopReset()
}
package com.example.videodemoimport android.util.Log
import com.example.videodemo.SpeedControlCallbackclass SpeedControlCallback : FrameCallback {private var mPrevPresentUsec: Long = 0private var mPrevMonoUsec: Long = 0private var mFixedFrameDurationUsec: Long = 0private var mLoopReset = falsefun setFixedPlaybackRate(fps: Int) {mFixedFrameDurationUsec = ONE_MILLION / fps}// runs on decode threadoverride fun preRender(presentationTimeUsec: Long) {if (mPrevMonoUsec == 0L) {mPrevMonoUsec = System.nanoTime() / 1000mPrevPresentUsec = presentationTimeUsec} else {// Compute the desired time delta between the previous frame and this frame.var frameDelta: Longif (mLoopReset) {mPrevPresentUsec = presentationTimeUsec - ONE_MILLION / 30mLoopReset = false}frameDelta = if (mFixedFrameDurationUsec != 0L) {mFixedFrameDurationUsec} else {presentationTimeUsec - mPrevPresentUsec}if (frameDelta < 0) {Log.w(TAG, "Weird, video times went backward")frameDelta = 0} else if (frameDelta == 0L) {Log.i(TAG, "Warning: current frame and previous frame had same timestamp")} else if (frameDelta > 10 * ONE_MILLION) {Log.i(TAG, "Inter-frame pause was " + frameDelta / ONE_MILLION +"sec, capping at 5 sec")frameDelta = 5 * ONE_MILLION}val desiredUsec = mPrevMonoUsec + frameDelta // when we want to wake upvar nowUsec = System.nanoTime() / 1000while (nowUsec < desiredUsec - 100 /*&& mState == RUNNING*/) {var sleepTimeUsec = desiredUsec - nowUsecif (sleepTimeUsec > 500000) {sleepTimeUsec = 500000}try {if (CHECK_SLEEP_TIME) {val startNsec = System.nanoTime()Thread.sleep(sleepTimeUsec / 1000, (sleepTimeUsec % 1000).toInt() * 1000)val actualSleepNsec = System.nanoTime() - startNsecLog.d(TAG, "sleep=" + sleepTimeUsec + " actual=" + actualSleepNsec / 1000 +" diff=" + Math.abs(actualSleepNsec / 1000 - sleepTimeUsec) +" (usec)")} else {Thread.sleep(sleepTimeUsec / 1000, (sleepTimeUsec % 1000).toInt() * 1000)}} catch (ie: InterruptedException) {}nowUsec = System.nanoTime() / 1000}mPrevMonoUsec += frameDeltamPrevPresentUsec += frameDelta}}override fun postRender() {}override fun loopReset() {mLoopReset = true}companion object {private const val TAG = "SpeedControlCallback"private const val CHECK_SLEEP_TIME = falseprivate const val ONE_MILLION = 1000000L}
}

以上就是mediaCodec的解码mp4视频轨道的代码了。


http://chatgpt.dhexx.cn/article/BXi78eqm.shtml

相关文章

Android视频编解码之MediaCodec简单入门

本篇只是简单入门&#xff0c;后面会继续写文章详细讲解&#xff1a; 由于MediaCodec涉及内容众多&#xff0c;原本想一篇文章把所有内容概括&#xff0c;但是后来发现不太可能&#xff0c;限于自己能力&#xff0c;想要考虑全面太难&#xff0c;我也是刚开始学习需要借助网上…

Android:MediaCodec基本原理

最近需要使用MediaCodec做一些工作&#xff0c;因此对MediaCodec做了些研究和代码编写&#xff0c;在此先对MediaCodec的一些基础原理、工作流程、常用API等做个初步总结&#xff0c;方便后续开发过程中查阅。 1.MediaCodec简介 1.1 MediaCodec来历 Android从最初的API 1.0版…

MediaCodec原理及使用

使用MediaCodec目的 MediaCodec是Android底层多媒体框架的一部分&#xff0c;通常与MediaExtractor、MediaMuxer、AudioTrack结合使用&#xff0c;可以编码H264、H265、AAC、3gp等常见的音视频格式 MediaCodec工作原理是处理输入数据以产生输出数据 MediaCodec工作流程 Medi…

Android MediaCodec

Android中可以使用MediaCodec来访问底层的媒体编解码器&#xff0c;可以对媒体进行编/解码。 MediaCodec可以处理的数据有以下三种类型&#xff1a;压缩数据、原始音频数据、原始视频数据。这三种类型的数据均可以利用ByteBuffers进行处理&#xff0c;但是对于原始视频数据应提…

android P MediaCodec编解码流程分析

1.MediaCodec初始化流程分析 通过上面流程分析可知&#xff0c;MediaCodecList初始化是通过调用它的getLocalInstance函数&#xff0c;然后在里面new的MediaCodecList对象。在MediaCodec的CreateByType函数中new了MediaCodec对象。 allocateNode调用流程是在ACodec.cpp中的onA…

MediaCodec_Analyze-3-start

MediaCodec Analyse – start Refrence: https://source.android.google.cn/devices/media 一 APK调用的核心API Android APK使用 MediaCodec API 播放音视频的简易流程&#xff1a; MediaCodec codec MediaCodec.createDecoderByType("video/avc"); MediaFormat …

mediacodec api

mediacodec api 官网&#xff1a; https://developer.android.com/reference/android/media/MediaCodec 中文&#xff1a; https://www.apiref.com/android-zh/android/media/MediaCodec.html 学习mediacodec api的使用说明&#xff0c;在android源码查看&#xff1a; andro…

MediaCodec硬解流程

一 MediaCodec概述 MediaCodec 是Android 4.1(api 16)版本引入的低层编解码接口&#xff0c;同时支持音视频的编码和解码。通常与MediaExtractor、MediaMuxer、AudioTrack结合使用&#xff0c;能够编解码诸如H.264、H.265、AAC、3gp等常见的音视频格式。MediaCodec在编解码的过…

MediaCodec_Analyze-1-create

MediaCodec Analyse – create Refrence: https://source.android.google.cn/devices/media 一 APK调用的核心API Android APK使用 MediaCodec API 播放音视频的简易流程&#xff1a; MediaCodec codec MediaCodec.createDecoderByType("video/avc"); MediaFormat…

mediacodec

MedaiCodec简介 MediaCodec是Android中提供的音视频编/解码工具。它主要是完成上层接口的封装&#xff0c;供给开发者使用&#xff0c;编解码功能实际是在native底层服务中完成的 MediaCodec工作流程 包括两个缓冲区队列 一个输入缓冲区队列&#xff0c;包含一组输入缓冲区(格…

Android MediaCodec 完全解析

MediaCodec是什么&#xff1f; MediaCodec类为开发者提供了能访问到Android底层媒体Codec&#xff08;Encoder/Decoder&#xff09;的能力&#xff0c;它是Android底层多媒体基础架构的一部分&#xff08;通常和MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、MediaDrm…

Android MediaCodec解析

Android MediaCodec解析 1 引言 MediaCodec是Android平台提供的一个底层的音视频编解码框架&#xff0c;它是安卓底层多媒体基础框架的重要组成部分。它经常和 MediaExtractor, MediaSync, MediaMuxer, MediaCrypto, MediaDrm, Image, Surface, AudioTrack一起使用。解码的作…

Android原生编解码接口MediaCodec详解

作者&#xff1a;躬行之 了解了音视频的相关知识&#xff0c;可以先阅读同系列文章&#xff1a; 音视频开发基础知识音频帧、视频帧及其同步Camera2、MediaCodec录制mp4 MediaCodec 是 Android 中的编解码器组件&#xff0c;用来访问底层提供的编解码器&#xff0c;通常与 Me…

MediaCodec视频解码流程详解及参考demo

一、MediaCodec简介 MediaCodec是Android自带的底层多媒体支持架构的一部分&#xff08;通常与 MediaExtractor&#xff0c;MediaSync&#xff0c;MediaMuxer&#xff0c;MediaCrypto&#xff0c;MediaDrm&#xff0c;Image&#xff0c;Surface 和 AudioTrack 一起使用&#xf…

Android MediaCodec简单总结

#.MedaiCodec简介 MediaCodec是Android中提供的音视频编码、解码工具。它主要是完成上层接口的封装&#xff0c;提供给开发者使用&#xff0c;编解码功能实际是在native底层服务中完成的。 #.MediaCodec工作的宏观流程&#xff1a; ##.包换两个缓冲区队列 一个输入缓冲区队列&a…

软件测试面试指导之自我介绍

面试自我介绍虽然人人都准备&#xff0c;但是做到让人印象深刻可不容易啊。 本篇就具体来聊聊人人都要经历的面试&#xff0c;怎么做自我介绍&#xff0c;才能让你在面试官的眼睛里像金子一样闪闪发光&#xff1f; 面试是什么&#xff1f; 它是个机会&#xff0c;让面试官更…

软件测试面试要注意的细节以及处理(自我介绍篇)

面试问题第一问&#xff0c;95%都会是&#xff1a; 请简单的做个自我介绍吧~ 分以下几点说明。 一、个人的基本信息&#xff0c;扬长避短 1、年纪太大与太小&#xff0c;都不需要主动去说明。 比如我年纪只有21岁 例子&#xff1a;面试官您好&#xff0c;我叫***&#xff…

【软件测试】企业测试面试题9道,从自我介绍到项目考察+回答......

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、自我介绍 您好…

软件测试面试怎样介绍自己的项目?会问到什么程度?

最近收到很多粉丝的私信说找不到工作&#xff0c;简历投了百十来份&#xff0c;邀约都没几个&#xff0c;更别说offer了&#xff0c;是不是软件测试要黄了&#xff1f; 说句实话&#xff0c;现在大环境确实不好&#xff0c;互联网大厂裁员这是摆在明面上的原因。时代的一粒沙&…

软件测试面试怎样介绍自己的测试项目?会问到什么程度?

想知道面试时该怎样介绍测试项目&#xff1f;会问到什么程度&#xff1f;那就需要换位思考&#xff0c;思考HR在这个环节想知道什么。 HR在该环节普遍想获得的情报主要是下面这2个方面&#xff1a; 1&#xff09;应聘者的具体经验和技术能力&#xff0c; 2&#xff09;应聘者的…