• MediaCodec 同步方式完成AAC硬解成PCM


    MediaCodec

    使用MediaCodec编解码实际是通过底层的硬件来对我们的音视频数据进行处理的,俗称硬编硬解,ffmpeg编解码是软解,效率不如MediaCodec,MediaCodec的主要实现是通过Native层去访问dsp芯片,让dsp芯片去编/解码流,整个流程按我的理解就是类似一个火腿肠加工厂,我给他一车猪,他拿走两头,一顿加工输入一筐火腿肠,我把这筐火腿肠取走,工厂收回筐,再抓两头猪进行加工。

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

    API说明

    构建MediaCodec

    MediaCodec.createDecoderByType("要解码的类型")构建解码器;

    MediaCodec.createEncoderByType("要编码码的类型")构建编码码器;配置config

    配置config

    1. configure(
    2. @Nullable MediaFormat format//media格式控制 ;参数3 ;
    3. @Nullable Surface surface,//surface用于渲染编解码后的视频
    4. @Nullable MediaCrypto crypto,//加密用的对象,可根据自身需要定制
    5. @ConfigureFlag int flags)//标志位,解码为0,编码为1

    start开启编解码

    dequeueInputBuffer() 可以理解为往工厂运猪的小推车,返回-1代表没有车子,返回大于-1代表有车子,获取到序号后就可以通过getInputBuffers()[序号]获取到小车子,再把猪放到车子里就可以了

    queueInputBuffer 理解成把小推车里的猪运进工厂

    1. void queueInputBuffer(
    2. int index,//小推车序号
    3. int offset, //偏移量,一般为0,表示从猪的那个地方开始加工
    4. int size, //猪的大小
    5. long presentationTimeUs, //时间戳,理解当前猪在一车猪中的顺序
    6. int flags//屠宰的标志,0进行加工 4就是没猪了
    7. )

    dequeueOutputBuffer(“延时获取的微秒值”) 返回已处理好的数据buffer序号,理解成装火腿肠的筐序号,通过getOutputBuffers()[序号]获取装载数据的Buffer; 他除了序号的作用,还可以用作标志位,大于等于0表示序号,小于0表示当前codec返回的一些标志:

    MediaCodec.INFO_OUTPUT_FORMAT_CHANGED 输出的format已更改

    MediaCodec.INFO_TRY_AGAIN_LATER 超时,没获取到

    MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED 输出缓冲区已更改

    releaseOutputBuffer(筐的序号, 标志位) 表示把筐还给工厂,第二个参数为true时将数据放到我们配置的surface里面

    stop() release()释放资源

    总之同步的方式就是配置完MediaCodec后开启while循环不断的dequeueInputBuffer -> queueInputBuffer填充数据 -> dequeueOutputBuffer -> releaseOutputBuffer

    使用

    • input数据
    1. //所有的猪都运进猪厂后不再添加
    2. if (hasAudio) {
    3. //从猪肉工厂获取装猪的小推车,填充数据后发送到猪肉工厂进行处理
    4. ByteBuffer[] inputBuffers = decodeCodec.getInputBuffers();//所有的小推车
    5. int inputIndex = decodeCodec.dequeueInputBuffer(0);//返回当前可用的小推车标号
    6. if (inputIndex != -1) {
    7. Log.i(LOG_TAG, "找到了input 小推车" + inputIndex);
    8. //将MediaCodec数据取出来放到这个缓冲区里
    9. ByteBuffer inputBuffer = inputBuffers[inputIndex];//拿到小推车
    10. inputBuffer.clear();//扔出去里面旧的东西
    11. //将audioExtractor里面的猪装载到小推车里面
    12. int readSize = audioExtractor.readSampleData(inputBuffer, 0);
    13. //audioExtractor没猪了,也要告知一下
    14. if (readSize < 0) {
    15. Log.i(LOG_TAG, "当前音频已经读取完了");
    16. decodeCodec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
    17. hasAudio = false;
    18. } else {//拿到猪
    19. Log.i(LOG_TAG, "读取到了音频数据,当前音频数据的数据长度为:" + readSize);
    20. //告诉工厂这头猪的小推车序号、猪的大小、猪在这群猪里的排行、屠宰的标志
    21. decodeCodec.queueInputBuffer(inputIndex, 0, readSize, audioExtractor.getSampleTime(), 0);
    22. //读取音频的下一帧
    23. audioExtractor.advance();
    24. }
    25. } else {
    26. Log.i(LOG_TAG, "没有可用的input 小推车");
    27. }
    28. }

    output

    1. int outputIndex = decodeCodec.dequeueOutputBuffer(decodeBufferInfo, 0);//返回当前筐的标记
    2. switch (outputIndex) {
    3. case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
    4. Log.i(LOG_TAG, "输出的format已更改" + decodeCodec.getOutputFormat());
    5. break;
    6. case MediaCodec.INFO_TRY_AGAIN_LATER:
    7. Log.i(LOG_TAG, "超时,没获取到");
    8. break;
    9. case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
    10. Log.i(LOG_TAG, "输出缓冲区已更改");
    11. break;
    12. default:
    13. Log.i(LOG_TAG, "获取到解码后的数据了,当前解析后的数据长度为:" + decodeBufferInfo.size);
    14. //获取所有的筐
    15. ByteBuffer[] outputBuffers = decodeCodec.getOutputBuffers();
    16. //拿到当前装满火腿肠的筐
    17. ByteBuffer outputBuffer;
    18. if (Build.VERSION.SDK_INT >= 21) {
    19. outputBuffer = decodeCodec.getOutputBuffer(outputIndex);
    20. } else {
    21. outputBuffer = outputBuffers[outputIndex];
    22. }
    23. //将火腿肠放到新的容器里,便于后期装车运走
    24. byte[] pcmData = new byte[decodeBufferInfo.size];
    25. outputBuffer.get(pcmData);//写入到字节数组中
    26. outputBuffer.clear();//清空当前筐
    27. //装车
    28. fos.write(pcmData);//数据写入文件中
    29. fos.flush();
    30. //把筐放回工厂里面
    31. decodeCodec.releaseOutputBuffer(outputIndex, false);
    32. break;
    33. }

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

     完整代码

    1. package com.wish.videopath.demo5;
    2. import android.content.Context;
    3. import android.media.MediaCodec;
    4. import android.media.MediaExtractor;
    5. import android.media.MediaFormat;
    6. import android.os.Build;
    7. import android.os.Environment;
    8. import android.util.Log;
    9. import androidx.annotation.NonNull;
    10. import androidx.annotation.RequiresApi;
    11. import com.wish.videopath.R;
    12. import java.io.File;
    13. import java.io.FileOutputStream;
    14. import java.io.IOException;
    15. import java.nio.ByteBuffer;
    16. import static com.wish.videopath.MainActivity.LOG_TAG;
    17. /**
    18. * 类名称:EncodeAACThread
    19. * 类描述:将AAC通过MediaCodec接码成PCM文件
    20. */
    21. class DecodeAACThread extends Thread {
    22. private Context context;
    23. private MediaFormat audioFormat;
    24. private File pcmFile;
    25. private boolean hasAudio = true;
    26. private FileOutputStream fos = null;
    27. public DecodeAACThread(Demo5Activity demo5Activity) {
    28. context = demo5Activity;
    29. pcmFile = new File(context.getExternalFilesDir(Environment.DIRECTORY_MUSIC), "demo5.pcm");
    30. try {
    31. pcmFile.createNewFile();
    32. } catch (IOException e) {
    33. e.printStackTrace();
    34. }
    35. }
    36. @RequiresApi(api = Build.VERSION_CODES.N)
    37. @Override
    38. public void run() {
    39. super.run();
    40. //通过MediaExtractor获取音频通道
    41. MediaExtractor audioExtractor = new MediaExtractor();
    42. MediaCodec decodeCodec = null;
    43. //pcm文件输出
    44. // FileOutputStream fos = null;
    45. try {
    46. fos = new FileOutputStream(pcmFile.getAbsoluteFile());
    47. audioExtractor.setDataSource(context.getResources().openRawResourceFd(R.raw.demo5));
    48. int count = audioExtractor.getTrackCount();
    49. for (int i = 0; i < count; i++) {
    50. audioFormat = audioExtractor.getTrackFormat(i);
    51. if (audioFormat.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
    52. audioExtractor.selectTrack(i);
    53. Log.i(LOG_TAG, "aac 找到了通道" + i);
    54. break;
    55. }
    56. }
    57. //初始化MiediaCodec
    58. decodeCodec = MediaCodec.createDecoderByType(audioFormat.getString(MediaFormat.KEY_MIME));
    59. //数据格式,surface用来渲染解析出来的数据;加密用的对象;标志 encode :1 decode:0
    60. decodeCodec.configure(audioFormat, null, null, 0);
    61. MediaCodec.BufferInfo decodeBufferInfo = new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息
    62. //启动解码
    63. decodeCodec.start();
    64. /*
    65. * 同步方式,流程是在while中
    66. * dequeueInputBuffer -> queueInputBuffer填充数据 -> dequeueOutputBuffer -> releaseOutputBuffer显示画面
    67. */
    68. boolean hasAudio = true;
    69. while (true) {
    70. //所有的猪都运进猪厂后不再添加
    71. if (hasAudio) {
    72. //从猪肉工厂获取装猪的小推车,填充数据后发送到猪肉工厂进行处理
    73. ByteBuffer[] inputBuffers = decodeCodec.getInputBuffers();//所有的小推车
    74. int inputIndex = decodeCodec.dequeueInputBuffer(0);//返回当前可用的小推车标号
    75. if (inputIndex != -1) {
    76. Log.i(LOG_TAG, "找到了input 小推车" + inputIndex);
    77. //将MediaCodec数据取出来放到这个缓冲区里
    78. ByteBuffer inputBuffer = inputBuffers[inputIndex];//拿到小推车
    79. inputBuffer.clear();//扔出去里面旧的东西
    80. //将audioExtractor里面的猪装载到小推车里面
    81. int readSize = audioExtractor.readSampleData(inputBuffer, 0);
    82. //audioExtractor没猪了,也要告知一下
    83. if (readSize < 0) {
    84. Log.i(LOG_TAG, "当前音频已经读取完了");
    85. decodeCodec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
    86. hasAudio = false;
    87. } else {//拿到猪
    88. Log.i(LOG_TAG, "读取到了音频数据,当前音频数据的数据长度为:" + readSize);
    89. //告诉工厂这头猪的小推车序号、猪的大小、猪在这群猪里的排行、屠宰的标志
    90. decodeCodec.queueInputBuffer(inputIndex, 0, readSize, audioExtractor.getSampleTime(), 0);
    91. //读取音频的下一帧
    92. audioExtractor.advance();
    93. }
    94. } else {
    95. Log.i(LOG_TAG, "没有可用的input 小推车");
    96. }
    97. }
    98. //工厂已经把猪运进去了,但是是否加工成火腿肠还是未知的,我们要通过装火腿肠的筐来判断是否已经加工完了
    99. int outputIndex = decodeCodec.dequeueOutputBuffer(decodeBufferInfo, 0);//返回当前筐的标记
    100. switch (outputIndex) {
    101. case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
    102. Log.i(LOG_TAG, "输出的format已更改" + decodeCodec.getOutputFormat());
    103. break;
    104. case MediaCodec.INFO_TRY_AGAIN_LATER:
    105. Log.i(LOG_TAG, "超时,没获取到");
    106. break;
    107. case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
    108. Log.i(LOG_TAG, "输出缓冲区已更改");
    109. break;
    110. default:
    111. Log.i(LOG_TAG, "获取到解码后的数据了,当前解析后的数据长度为:" + decodeBufferInfo.size);
    112. //获取所有的筐
    113. ByteBuffer[] outputBuffers = decodeCodec.getOutputBuffers();
    114. //拿到当前装满火腿肠的筐
    115. ByteBuffer outputBuffer;
    116. if (Build.VERSION.SDK_INT >= 21) {
    117. outputBuffer = decodeCodec.getOutputBuffer(outputIndex);
    118. } else {
    119. outputBuffer = outputBuffers[outputIndex];
    120. }
    121. //将火腿肠放到新的容器里,便于后期装车运走
    122. byte[] pcmData = new byte[decodeBufferInfo.size];
    123. outputBuffer.get(pcmData);//写入到字节数组中
    124. outputBuffer.clear();//清空当前筐
    125. //装车
    126. fos.write(pcmData);//数据写入文件中
    127. fos.flush();
    128. //把筐放回工厂里面
    129. decodeCodec.releaseOutputBuffer(outputIndex, false);
    130. break;
    131. }
    132. if ((decodeBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
    133. Log.i(LOG_TAG, "表示当前编解码已经完事了");
    134. break;
    135. }
    136. }
    137. } catch (IOException e) {
    138. e.printStackTrace();
    139. } finally {
    140. if (audioExtractor != null) {
    141. audioExtractor.release();
    142. }
    143. if (decodeCodec != null) {
    144. decodeCodec.stop();
    145. decodeCodec.release();
    146. }
    147. if (fos != null) {
    148. try {
    149. fos.close();
    150. } catch (IOException e) {
    151. e.printStackTrace();
    152. }
    153. }
    154. }
    155. }
    156. }

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

  • 相关阅读:
    猿创征文|JAVA 实现《俄罗斯方块升级版》游戏
    浅谈Kubernetes集群架构
    【保姆级教程】Vue项目调试技巧
    14天学习训练营导师课程-Pygame学习笔记-Part1(环境准备)
    【算法题】素数伴侣-用匈牙利算法解决二分图匹配问题
    系统软件开发平台的介绍
    推荐高效的电脑磁盘备份解决方案!
    K-Core, K-Shell & K-Crust傻傻分不清楚
    聪明的红绿灯,已经学会主动给你开路了
    【React】使用 react-pdf 将数据渲染为pdf并提供下载
  • 原文地址:https://blog.csdn.net/m0_60259116/article/details/126874097