MediaCodec原理及使用

article/2025/10/6 19:05:38

使用MediaCodec目的

MediaCodec是Android底层多媒体框架的一部分,通常与MediaExtractor、MediaMuxer、AudioTrack结合使用,可以编码H264、H265、AAC、3gp等常见的音视频格式

MediaCodec工作原理是处理输入数据以产生输出数据

MediaCodec工作流程

MediaCodec的数据流分为input和output流,并通过异步的方式处理两路数据流,直到手动释放output缓冲区,MediaCodec才将数据处理完毕

input流:客户端输入待解码或者待编码的数据

output流:客户端输出的已解码或者已编码的数据

官方示例图:

MediaCodec API说明

getInputBuffers:获取需要输入流队列,返回ByteBuffer数组

queueInputBuffer:输入流入队

dequeueInputBuffer: 从输入流队列中取数据进行编码操作

getOutputBuffers:获取已经编解码之后的数据输出流队列,返回ByteBuffer数组

dequeueOutputBuffer:从输出队列中取出已经编码操作之后的数据

releaseOutputBuffer: 处理完成,释放output缓冲区

基本流程

 

MediaCodec的基本使用遵循上图所示,它的生命周期如下所示:

Stoped:创建好MediaCodec,进行配置,或者出现错误

Uninitialized: 当创建了一个MediaCodec对象,此时MediaCodec处于Uninitialized,在任何状态调用reset()方法使MediaCodec返回到Uninitialized状态

Configured: 使用configure(…)方法对MediaCodec进行配置转为Configured状态

Error: 出现错误

Executing:可以在Executing状态的任何时候通过调用flush()方法返回到Flushed状态

Flushed:调用start()方法后MediaCodec立即进入Flushed状态

Running:调用dequeueInputBuffer后,MediaCodec就转入Running状态

End-of-Stream:编解码结束后,MediaCodec将转入End-of-Stream子状态

Released:当使用完MediaCodec后,必须调用release()方法释放其资源

基本使用

//解码器
val mVideoDecoder = MediaCodec.createDecoderByType("video/avc")
//编码器
val mVideoEncoder = MediaCodec.createEncoderByType("video/avc")

MediaCodec工具类


/**
* Created with Android Studio.
* Description:
* @author: Wangjianxian
* @CreateDate: 2021/1/19 0:54
*/
object MediaCodecUtil {// 音频源:音频输入-麦克风private const val AUDIO_INPUT = MediaRecorder.AudioSource.MIC// 采样率// 44100是目前的标准,但是某些设备仍然支持22050,16000,11025// 采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级private const val AUDIO_SAMPLE_RATE = 44100// 音频通道 单声道private const val AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_MONO// 音频通道 立体声:CHANNEL_OUT_STEREO或CHANNEL_IN_STEREOprivate const val AUDIO_CHANNEL2 = AudioFormat.CHANNEL_IN_STEREO// 音频格式:PCM编码private const val AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BITprivate var bufferSizeInBytes: Int = 0/*** 获取缓冲大小*/fun getBufferSizeInBytes(): Int {return bufferSizeInBytes}fun createVideoEncode(surfaceSize: Size): MediaCodec {//视频编码器val videoEncoder = MediaCodec.createEncoderByType("video/avc")// 创建视频MediaFormatval videoFormat = MediaFormat.createVideoFormat("video/avc", surfaceSize.width, surfaceSize.height)// 指定编码器颜色格式videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface)// 指定编码器码率videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, 0)// 指定编码器帧率videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30)// 指定编码器关键帧间隔videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5)// BITRATE_MODE_CBR输出码率恒定// BITRATE_MODE_CQ保证图像质量// BITRATE_MODE_VBR图像复杂则码率高,图像简单则码率低videoFormat.setInteger(MediaFormat.KEY_BITRATE_MODE,MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR)videoFormat.setInteger(MediaFormat.KEY_COMPLEXITY,MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR)videoEncoder.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)return videoEncoder}fun createAudioEncoder(): MediaCodec {//音频编码器val audioEncoder = MediaCodec.createEncoderByType("audio/mp4a-latm")// 创建音频MediaFormat,参数2:采样率,参数3:通道val audioFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", 44100, 1)// 仅编码器指定比特率audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 4 * 1024)var bufferSizeInBytes = getBufferSizeInBytes()if (bufferSizeInBytes == 0) {bufferSizeInBytes = AudioRecord.getMinBufferSize(AUDIO_SAMPLE_RATE ,CHANNEL_IN_STEREO,ENCODING_PCM_16BIT)}//可选的,输入数据缓冲区的最大大小audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSizeInBytes)audioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE,MediaCodecInfo.CodecProfileLevel.AACObjectLC)audioEncoder.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)return audioEncoder}/*** 默认获取单声道AudioRecord*/fun getSingleAudioRecord(channelConfig: Int = AUDIO_CHANNEL,audioSource: Int = AUDIO_INPUT,sampleRateInHz: Int = AUDIO_SAMPLE_RATE,audioFormat: Int = AUDIO_ENCODING): AudioRecord {//audioRecord能接受的最小的buffer大小bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)return AudioRecord(audioSource,sampleRateInHz,channelConfig,audioFormat,bufferSizeInBytes)}
}

录制音视频并编码 

// 基本使用
val videoEncoder = MediaCodecUtil.createVideoEncode(size)
// 设置buffer
videoEncoder.setInputSurface(surface)
videoEncoder.start()
//音频录制类
val audioRecord = MediaCodecUtil.getSingleAudioRecord(AudioFormat.CHANNEL_IN_STEREO)
//音频编码器
val audioEncoder = MediaCodecUtil.createAudioEncoder()
audioEncoder.start()GlobalScope.launch (Dispatchers.IO) {while (isActive) {val length = AudioRecordUtil.getBufferSizeInBytes()audioRecord.read(mAudioBuffer, 0, length)val inputIndex = audioEncoder.dequeueInputBuffer(0)if (inputIndex >= 0) {val byteBuffer = audioEncoder.getInputBuffer(inputIndex)if (byteBuffer != null) {byteBuffer.clear()byteBuffer.put(mAudioBuffer)byteBuffer.limit(length);// 设定上限值audioEncoder.queueInputBuffer(inputIndex,0,length,System.nanoTime(),0); // 第三个参数为时间戳,这里是使用当前}}val outputIndex = audioEncoder.dequeueOutputBuffer(mBufferInfo, 0)if (outputIndex >= 0) {val byteBuffer = audioEncoder.getOutputBuffer(outputIndex)if (byteBuffer != null) {val byte = byteBuffer.get(outputIndex)}audioEncoder.releaseOutputBuffer(outputIndex, false)}}
}

YUV打包成MP4 

图像数据格式简介

YUV格式:

planar:先连续存储所有像素点的Y,紧接着存储所有像素点的U,再存储所有像素点的V,

将Y、U、V的三个分量分别存放在不同的矩阵中

packed:将Y、U、V值存储成Macro Pixels数组,和RGB的存放方式类似

YUV存储:

主流的采样方式主要有:YUV444,YUV422,YUV420,只有正确的还原每个像素点的YUV值,才能通过YUV与RGB的转换公式提取出每个像素点的RGB值,然后显示出来

YUV 4:4:4表示完全取样,每一个Y对应一组UV分量,一个YUV占8+8+8 = 24bits 3个字节

YUV 4:2:2表示2:1的水平取样,垂直完全采样,每两个Y共用一组UV分量,一个YUV占8+4+4 = 16bits 2个字节

YUV 4:2:0表示2:1的水平取样,垂直2:1采样,每四个Y共用一组UV分量,一个YUV占8+2+2 = 12bits 1.5个字节

YUV4:1:1表示4:1的水平取样,垂直完全采样

获取图像数据帧并进行编码

  • 使用MediaCodec对onPreviewFrame获取返回的图像帧(格式默认为NV21)进行编码,并使用MediaMuxer进行保存

创建编码器并打包

class VideoEncoder(size: Camera.Size) : AppCompatActivity() {private var mSize: Camera.Sizeprivate var mTrackIndex: Int = 0init {mSize = size}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 初始化编码器val mediaFormat =MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mSize.width, mSize.height)mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 1048576)mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30)mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)val mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)val mp4Path = Environment.getExternalStorageDirectory().toString() + "wjx" + ".mp4"// 创建混合生成器MediaMuxerval mediaMuxer = MediaMuxer(mp4Path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)// 配置状态mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)mediaCodec.start()encodeVideo(mediaCodec, mediaMuxer)}/*** 通过getInputBuffers获取输入队列,然后调用dequeueInputBuffer获取输入队列空闲数组下标,* 注意dequeueOutputBuffer会有几个特殊的返回值表示当前编解码状态的变化,* 然后再通过queueInputBuffer把原始YUV数据送入编码器,* 而在输出队列端同样通过getOutputBuffers和dequeueOutputBuffer获取输出的h264流,* 处理完输出数据之后,需要通过releaseOutputBuffer把输出buffer还给系统,重新放到输出队列中。*/private fun encodeVideo(mediaCodec: MediaCodec, mediaMuxer: MediaMuxer) {Thread(object : Runnable {override fun run() {while (true) {try {val bufferInfo = MediaCodec.BufferInfo()val outputBufferId = mediaCodec.dequeueOutputBuffer(bufferInfo, 0)if (outputBufferId >= 0) {val outPutBuffer = mediaCodec.getOutputBuffer(outputBufferId)val h264: ByteArray = ByteArray(bufferInfo.size)val outputBuffer = mediaCodec.getOutputBuffer(0)outPutBuffer?.get(h264)outPutBuffer?.position(bufferInfo.offset)outPutBuffer?.limit(bufferInfo.offset + bufferInfo.size)mediaMuxer.writeSampleData(mTrackIndex, outputBuffer!!, bufferInfo)mediaCodec.releaseOutputBuffer(outputBufferId, false)} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {val mediaFormat = mediaCodec.outputFormatmTrackIndex = mediaMuxer.addTrack(mediaFormat)mediaMuxer.start()}} catch (e: InterruptedException) {e.printStackTrace()}}mediaCodec.stop()mediaCodec.release()mediaMuxer.stop()mediaMuxer.release()}}).start();}
}

原文链接:MediaCodec原理及使用 - 资料 - 我爱音视频网 - 构建全国最权威的音视频技术交流分享论坛 

本文福利, C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部↓↓ 


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

相关文章

Android MediaCodec

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

android P MediaCodec编解码流程分析

1.MediaCodec初始化流程分析 通过上面流程分析可知,MediaCodecList初始化是通过调用它的getLocalInstance函数,然后在里面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 播放音视频的简易流程: MediaCodec codec MediaCodec.createDecoderByType("video/avc"); MediaFormat …

mediacodec api

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

MediaCodec硬解流程

一 MediaCodec概述 MediaCodec 是Android 4.1(api 16)版本引入的低层编解码接口,同时支持音视频的编码和解码。通常与MediaExtractor、MediaMuxer、AudioTrack结合使用,能够编解码诸如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 播放音视频的简易流程: MediaCodec codec MediaCodec.createDecoderByType("video/avc"); MediaFormat…

mediacodec

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

Android MediaCodec 完全解析

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

Android MediaCodec解析

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

Android原生编解码接口MediaCodec详解

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

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

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

Android MediaCodec简单总结

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

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

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

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

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

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

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

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

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

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

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

【软件测试】面试中介绍项目你该这么说!

黑马程序员视频库 播妞微信号:heiniu526 传智播客旗下互联网资讯、学习资源免费分享平台 测试人员在找工作的过程中,通常有一个问题是很难绕开的。就是要如何向别人介绍自己之前做过的项目。下面我们就这个问题简单的做一些分析。 大体上可以分为如下几个…

软件测试面试自我介绍/项目介绍居然还有模板?我要是早点发现就好了

目录 1、自我介绍 2、项目介绍 2.1、最全电商项目介绍 2.2、电商项目介绍 2.3、在线教育项目介绍 2.4、互联网金融项目介绍 总结 1、自我介绍 以XXX简历来举例(参照下面的案例,编写你的自我介绍,框架就是:我是谁&#xff0…

软件测试面试,如何自我介绍?如何介绍项目?如何介绍个人技术?(提供面试话术)

前准备不足而导致面试失败那可就亏大了!为了提高面试成功率,帮助大家尽快拿到高薪offer,我们盘点了面试环节必问的三类问题,希望对即将参加软件测试面试的小伙伴们有所帮助。 01 如何自我介绍 面试过程中一定要放慢语速&#xf…