MediaCodec是Android底层多媒体框架的一部分,通常与MediaExtractor、MediaMuxer、AudioTrack结合使用,可以编码H264、H265、AAC、3gp等常见的音视频格式
MediaCodec工作原理是处理输入数据以产生输出数据
MediaCodec的数据流分为input和output流,并通过异步的方式处理两路数据流,直到手动释放output缓冲区,MediaCodec才将数据处理完毕
input流:客户端输入待解码或者待编码的数据
output流:客户端输出的已解码或者已编码的数据
官方示例图:
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")
-
- /**
- * 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_STEREO
- private const val AUDIO_CHANNEL2 = AudioFormat.CHANNEL_IN_STEREO
-
- // 音频格式:PCM编码
- private const val AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT
-
- private var bufferSizeInBytes: Int = 0
-
- /**
- * 获取缓冲大小
- */
- fun getBufferSizeInBytes(): Int {
- return bufferSizeInBytes
- }
-
-
- fun createVideoEncode(surfaceSize: Size): MediaCodec {
- //视频编码器
- val videoEncoder = MediaCodec.createEncoderByType("video/avc")
- // 创建视频MediaFormat
- val 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格式:
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的水平取样,垂直完全采样
获取图像数据帧并进行编码
- class VideoEncoder(size: Camera.Size) : AppCompatActivity() {
- private var mSize: Camera.Size
- private var mTrackIndex: Int = 0
- init {
- 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"
- // 创建混合生成器MediaMuxer
- val 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.outputFormat
- mTrackIndex = 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)↓↓↓↓↓↓见下面↓↓文章底部↓↓