• MediaCodec原理及使用


    使用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()方法释放其资源

    基本使用

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

    MediaCodec工具类

    1. /**
    2. * Created with Android Studio.
    3. * Description:
    4. * @author: Wangjianxian
    5. * @CreateDate: 2021/1/19 0:54
    6. */
    7. object MediaCodecUtil {
    8. // 音频源:音频输入-麦克风
    9. private const val AUDIO_INPUT = MediaRecorder.AudioSource.MIC
    10. // 采样率
    11. // 44100是目前的标准,但是某些设备仍然支持22050,16000,11025
    12. // 采样频率一般共分为22.05KHz、44.1KHz、48KHz三个等级
    13. private const val AUDIO_SAMPLE_RATE = 44100
    14. // 音频通道 单声道
    15. private const val AUDIO_CHANNEL = AudioFormat.CHANNEL_IN_MONO
    16. // 音频通道 立体声:CHANNEL_OUT_STEREO或CHANNEL_IN_STEREO
    17. private const val AUDIO_CHANNEL2 = AudioFormat.CHANNEL_IN_STEREO
    18. // 音频格式:PCM编码
    19. private const val AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT
    20. private var bufferSizeInBytes: Int = 0
    21. /**
    22. * 获取缓冲大小
    23. */
    24. fun getBufferSizeInBytes(): Int {
    25. return bufferSizeInBytes
    26. }
    27. fun createVideoEncode(surfaceSize: Size): MediaCodec {
    28. //视频编码器
    29. val videoEncoder = MediaCodec.createEncoderByType("video/avc")
    30. // 创建视频MediaFormat
    31. val videoFormat = MediaFormat.createVideoFormat(
    32. "video/avc", surfaceSize.width
    33. , surfaceSize.height
    34. )
    35. // 指定编码器颜色格式
    36. videoFormat.setInteger(
    37. MediaFormat.KEY_COLOR_FORMAT,
    38. MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface
    39. )
    40. // 指定编码器码率
    41. videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, 0)
    42. // 指定编码器帧率
    43. videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30)
    44. // 指定编码器关键帧间隔
    45. videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 5)
    46. // BITRATE_MODE_CBR输出码率恒定
    47. // BITRATE_MODE_CQ保证图像质量
    48. // BITRATE_MODE_VBR图像复杂则码率高,图像简单则码率低
    49. videoFormat.setInteger(
    50. MediaFormat.KEY_BITRATE_MODE,
    51. MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR
    52. )
    53. videoFormat.setInteger(
    54. MediaFormat.KEY_COMPLEXITY,
    55. MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR
    56. )
    57. videoEncoder.configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
    58. return videoEncoder
    59. }
    60. fun createAudioEncoder(): MediaCodec {
    61. //音频编码器
    62. val audioEncoder = MediaCodec.createEncoderByType("audio/mp4a-latm")
    63. // 创建音频MediaFormat,参数2:采样率,参数3:通道
    64. val audioFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", 44100, 1)
    65. // 仅编码器指定比特率
    66. audioFormat.setInteger(MediaFormat.KEY_BIT_RATE, 4 * 1024)
    67. var bufferSizeInBytes = getBufferSizeInBytes()
    68. if (bufferSizeInBytes == 0) {
    69. bufferSizeInBytes = AudioRecord.getMinBufferSize(
    70. AUDIO_SAMPLE_RATE ,
    71. CHANNEL_IN_STEREO,
    72. ENCODING_PCM_16BIT
    73. )
    74. }
    75. //可选的,输入数据缓冲区的最大大小
    76. audioFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, bufferSizeInBytes)
    77. audioFormat.setInteger(
    78. MediaFormat.KEY_AAC_PROFILE,
    79. MediaCodecInfo.CodecProfileLevel.AACObjectLC
    80. )
    81. audioEncoder.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
    82. return audioEncoder
    83. }
    84. /**
    85. * 默认获取单声道AudioRecord
    86. */
    87. fun getSingleAudioRecord(
    88. channelConfig: Int = AUDIO_CHANNEL,
    89. audioSource: Int = AUDIO_INPUT,
    90. sampleRateInHz: Int = AUDIO_SAMPLE_RATE,
    91. audioFormat: Int = AUDIO_ENCODING
    92. ): AudioRecord {
    93. //audioRecord能接受的最小的buffer大小
    94. bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat)
    95. return AudioRecord(
    96. audioSource,
    97. sampleRateInHz,
    98. channelConfig,
    99. audioFormat,
    100. bufferSizeInBytes
    101. )
    102. }
    103. }

    录制音视频并编码 

    1. // 基本使用
    2. val videoEncoder = MediaCodecUtil.createVideoEncode(size)
    3. // 设置buffer
    4. videoEncoder.setInputSurface(surface)
    5. videoEncoder.start()
    6. //音频录制类
    7. val audioRecord = MediaCodecUtil.getSingleAudioRecord(AudioFormat.CHANNEL_IN_STEREO)
    8. //音频编码器
    9. val audioEncoder = MediaCodecUtil.createAudioEncoder()
    10. audioEncoder.start()
    11. GlobalScope.launch (Dispatchers.IO) {
    12. while (isActive) {
    13. val length = AudioRecordUtil.getBufferSizeInBytes()
    14. audioRecord.read(mAudioBuffer, 0, length)
    15. val inputIndex = audioEncoder.dequeueInputBuffer(0)
    16. if (inputIndex >= 0) {
    17. val byteBuffer = audioEncoder.getInputBuffer(inputIndex)
    18. if (byteBuffer != null) {
    19. byteBuffer.clear()
    20. byteBuffer.put(mAudioBuffer)
    21. byteBuffer.limit(length);// 设定上限值
    22. audioEncoder.queueInputBuffer(
    23. inputIndex,
    24. 0,
    25. length,
    26. System.nanoTime(),
    27. 0
    28. ); // 第三个参数为时间戳,这里是使用当前
    29. }
    30. }
    31. val outputIndex = audioEncoder.dequeueOutputBuffer(mBufferInfo, 0)
    32. if (outputIndex >= 0) {
    33. val byteBuffer = audioEncoder.getOutputBuffer(outputIndex)
    34. if (byteBuffer != null) {
    35. val byte = byteBuffer.get(outputIndex)
    36. }
    37. audioEncoder.releaseOutputBuffer(outputIndex, false)
    38. }
    39. }
    40. }

    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进行保存

    创建编码器并打包

    1. class VideoEncoder(size: Camera.Size) : AppCompatActivity() {
    2. private var mSize: Camera.Size
    3. private var mTrackIndex: Int = 0
    4. init {
    5. mSize = size
    6. }
    7. override fun onCreate(savedInstanceState: Bundle?) {
    8. super.onCreate(savedInstanceState)
    9. // 初始化编码器
    10. val mediaFormat =
    11. MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, mSize.width, mSize.height)
    12. mediaFormat.setInteger(
    13. MediaFormat.KEY_COLOR_FORMAT,
    14. MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
    15. )
    16. mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 1048576)
    17. mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30)
    18. mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
    19. val mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
    20. val mp4Path = Environment.getExternalStorageDirectory().toString() + "wjx" + ".mp4"
    21. // 创建混合生成器MediaMuxer
    22. val mediaMuxer = MediaMuxer(mp4Path, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
    23. // 配置状态
    24. mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
    25. mediaCodec.start()
    26. encodeVideo(mediaCodec, mediaMuxer)
    27. }
    28. /**
    29. * 通过getInputBuffers获取输入队列,然后调用dequeueInputBuffer获取输入队列空闲数组下标,
    30. * 注意dequeueOutputBuffer会有几个特殊的返回值表示当前编解码状态的变化,
    31. * 然后再通过queueInputBuffer把原始YUV数据送入编码器,
    32. * 而在输出队列端同样通过getOutputBuffers和dequeueOutputBuffer获取输出的h264流,
    33. * 处理完输出数据之后,需要通过releaseOutputBuffer把输出buffer还给系统,重新放到输出队列中。
    34. */
    35. private fun encodeVideo(mediaCodec: MediaCodec, mediaMuxer: MediaMuxer) {
    36. Thread(object : Runnable {
    37. override fun run() {
    38. while (true) {
    39. try {
    40. val bufferInfo = MediaCodec.BufferInfo()
    41. val outputBufferId = mediaCodec.dequeueOutputBuffer(bufferInfo, 0)
    42. if (outputBufferId >= 0) {
    43. val outPutBuffer = mediaCodec.getOutputBuffer(outputBufferId)
    44. val h264: ByteArray = ByteArray(bufferInfo.size)
    45. val outputBuffer = mediaCodec.getOutputBuffer(0)
    46. outPutBuffer?.get(h264)
    47. outPutBuffer?.position(bufferInfo.offset)
    48. outPutBuffer?.limit(bufferInfo.offset + bufferInfo.size)
    49. mediaMuxer.writeSampleData(mTrackIndex, outputBuffer!!, bufferInfo)
    50. mediaCodec.releaseOutputBuffer(outputBufferId, false)
    51. } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
    52. val mediaFormat = mediaCodec.outputFormat
    53. mTrackIndex = mediaMuxer.addTrack(mediaFormat)
    54. mediaMuxer.start()
    55. }
    56. } catch (e: InterruptedException) {
    57. e.printStackTrace()
    58. }
    59. }
    60. mediaCodec.stop()
    61. mediaCodec.release()
    62. mediaMuxer.stop()
    63. mediaMuxer.release()
    64. }
    65. }).start();
    66. }
    67. }

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

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

  • 相关阅读:
    代码随想录算法训练营第三十九天 | 动态规划part02
    算法-简单-二叉树-翻转、对称
    图神经网络 | Pytorch图神经网络ST-GNN
    VB.Net 任务管理器相关操作
    JVM垃圾回收机制
    如何使用ArcGIS Pro提取河网水系
    【一知半解】AQS
    【C++】循环体内和循环体外定义变量的区别
    eNSP学习——连接RIP与OSPF网络、默认路由
    【EI会议征稿】第三届网络安全、人工智能与数字经济国际学术会议(CSAIDE 2024)
  • 原文地址:https://blog.csdn.net/m0_60259116/article/details/125915308