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


    MediaCodec异步方式

    上一节使用同步方式使用MediaCodec还是比较麻烦的,我们java中使用大量的回调来实现监听者模式,MediaCodec在sdk 19版本后也通过回调来告知使用者,input或者output已经准备好的情况,具体的api就是为MediaCodec设置CallBack,并实现其中的四个方法:

    1. decodeCodec.setCallback(new MediaCodec.Callback() {
    2. @Override
    3. public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
    4. Log.i(LOG_TAG, "input数据已准备好,当前index:" + index);
    5. }
    6. @Override
    7. public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
    8. Log.i(LOG_TAG, "outPut数据已准备好,当前index:" + index);
    9. }
    10. @Override
    11. public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
    12. Log.i(LOG_TAG, "编解码出错 onError" + e.toString());
    13. }
    14. @Override
    15. public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
    16. Log.i(LOG_TAG, "format发生更改,onOutputFormatChanged" + format.toString());
    17. }
    18. });

    注意点:

    先设置callBack,再设置config

    callBack回调发生再主线程,需要自己手动切换

    使用

    具体的使用流程跟上一节其实是一样的,就是while循环中手动获取现在改为回调通知了,切换线程我是通过在子线程中构建Handler,收到回调之后再通过Handler把消息发出去,回调的信息通过一个队列去记录一下,具体的代码如下:

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

    1. public class DecodeAACAsyn extends Thread {
    2. private Context context;
    3. private MediaFormat audioFormat;
    4. private File pcmFile;
    5. private FileOutputStream fos = null;
    6. private MediaCodec decodeCodec = null;
    7. private Queue<byte[]> mOutDataQueue = new LinkedBlockingQueue<>();
    8. private Queue<Integer> mInputDataQueue = new LinkedBlockingQueue<>();
    9. private MediaExtractor audioExtractor = new MediaExtractor();
    10. private Handler mHandler;
    11. private Runnable outRunnable = () -> {
    12. try {
    13. Log.e(LOG_TAG, "outRunnable,当前线程: " + Thread.currentThread().getName());
    14. byte[] pcmData = mOutDataQueue.poll();
    15. if (pcmData == null) {
    16. return;
    17. }
    18. Log.e(LOG_TAG, "Handler回调收到,当前数据大小:" + pcmData.length);
    19. //装车
    20. fos.write(pcmData);//数据写入文件中
    21. fos.flush();
    22. } catch (Exception e) {
    23. e.printStackTrace();
    24. }
    25. };
    26. private Runnable inputRunnable = () -> {
    27. try {
    28. Log.e(LOG_TAG, "inputRunnable,当前线程: " + Thread.currentThread().getName());
    29. Integer index = mInputDataQueue.poll();
    30. if (index == null) {
    31. return;
    32. }
    33. ByteBuffer buffer;
    34. if (Build.VERSION.SDK_INT >= 21) {
    35. buffer = decodeCodec.getInputBuffer(index);
    36. } else {
    37. buffer = decodeCodec.getInputBuffers()[index];
    38. }
    39. int sampleSize = audioExtractor.readSampleData(buffer, 0);
    40. if (sampleSize < 0) {
    41. Log.i(LOG_TAG, "当前音频已经读取完了");
    42. decodeCodec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
    43. } else {
    44. Log.i(LOG_TAG, "读取到了音频数据,当前音频数据的数据长度为:" + sampleSize);
    45. long sampleTime = audioExtractor.getSampleTime();
    46. decodeCodec.queueInputBuffer(index, 0, sampleSize, sampleTime, 0);
    47. audioExtractor.advance();
    48. }
    49. } catch (Exception e) {
    50. e.printStackTrace();
    51. }
    52. };
    53. public DecodeAACAsyn(Demo5Activity demo5Activity) {
    54. context = demo5Activity;
    55. pcmFile = new File(context.getExternalFilesDir(Environment.DIRECTORY_MUSIC), "demo5a.pcm");
    56. try {
    57. pcmFile.createNewFile();
    58. } catch (IOException e) {
    59. e.printStackTrace();
    60. }
    61. }
    62. @RequiresApi(api = Build.VERSION_CODES.N)
    63. public void run() {
    64. super.run();
    65. Looper.prepare();
    66. mHandler = new Handler(Looper.myLooper()) {
    67. @Override
    68. public void handleMessage(@NonNull @NotNull Message msg) {
    69. super.handleMessage(msg);
    70. if (msg.what == 0) {
    71. destory();
    72. }
    73. }
    74. };
    75. Log.e(LOG_TAG, "Decode,当前线程: " + Thread.currentThread().getName());
    76. try {
    77. fos = new FileOutputStream(pcmFile.getAbsoluteFile());
    78. audioExtractor.setDataSource(context.getResources().openRawResourceFd(R.raw.demo5mp3));
    79. int count = audioExtractor.getTrackCount();
    80. for (int i = 0; i < count; i++) {
    81. audioFormat = audioExtractor.getTrackFormat(i);
    82. if (audioFormat.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
    83. audioExtractor.selectTrack(i);
    84. Log.i(LOG_TAG, "aac 找到了通道" + i);
    85. break;
    86. }
    87. }
    88. //初始化MiediaCodec
    89. decodeCodec = MediaCodec.createDecoderByType(audioFormat.getString(MediaFormat.KEY_MIME));
    90. /*
    91. * 通过回调方式来进行数据的编码,比刚才手动调用方式更合理,回调运行在主线程,记得切换线程
    92. */
    93. decodeCodec.setCallback(new MediaCodec.Callback() {
    94. @Override
    95. public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
    96. Log.i(LOG_TAG, "异步回调,onInputBufferAvailable,当前index:" + index);
    97. mInputDataQueue.offer(index);
    98. mHandler.post(inputRunnable);
    99. }
    100. @Override
    101. public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
    102. Log.i(LOG_TAG, "异步回调,onOutputBufferAvailable,当前index:" + index);
    103. Log.i(LOG_TAG, "获取到解码后的数据了,当前解析后的数据长度为:" + info.size);
    104. //拿到当前装满火腿肠的筐
    105. ByteBuffer outputBuffer;
    106. if (Build.VERSION.SDK_INT >= 21) {
    107. outputBuffer = codec.getOutputBuffer(index);
    108. } else {
    109. outputBuffer = codec.getOutputBuffers()[index];
    110. }
    111. //将火腿肠放到新的容器里,便于后期装车运走
    112. byte[] pcmData = new byte[info.size];
    113. outputBuffer.get(pcmData);//写入到字节数组中
    114. outputBuffer.clear();//清空当前筐
    115. //将装猪的数据放到队列里面,通过handler发送消息在子线程装入数据
    116. mOutDataQueue.offer(pcmData);
    117. mHandler.post(outRunnable);
    118. //把筐放回工厂里面
    119. codec.releaseOutputBuffer(index, false);
    120. if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
    121. mHandler.sendEmptyMessage(0);
    122. }
    123. }
    124. @Override
    125. public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
    126. Log.i(LOG_TAG, "异步回调,onError" + e.toString());
    127. }
    128. @Override
    129. public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
    130. Log.i(LOG_TAG, "异步回调,onOutputFormatChanged" + format.toString());
    131. }
    132. });
    133. //先配置callBack,再配置config;数据格式,surface用来渲染解析出来的数据;加密用的对象;标志 encode :1 decode:0
    134. decodeCodec.configure(audioFormat, null, null, 0);
    135. //启动解码
    136. decodeCodec.start();
    137. } catch (IOException e) {
    138. e.printStackTrace();
    139. }
    140. Looper.loop();
    141. }
    142. public void destory() {
    143. Log.i(LOG_TAG, "销毁资源");
    144. if (audioExtractor != null) {
    145. audioExtractor.release();
    146. }
    147. if (decodeCodec != null) {
    148. decodeCodec.stop();
    149. decodeCodec.release();
    150. }
    151. if (fos != null) {
    152. try {
    153. fos.close();
    154. } catch (IOException e) {
    155. e.printStackTrace();
    156. }
    157. }
    158. }
    159. }

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

  • 相关阅读:
    Flink 流处理API-Window API
    python3网络爬虫--2323爬取B站视频弹幕 解so文件(附源码)
    go 项目打包部署到服务器
    MySQL数据库——日志管理、备份与恢复
    BruceR 包中介效应结果不一致?
    轻松安装vue脚手架的一个详细讲解
    GaussDB CN服务异常实例分析
    根据表单控件名称,获取对应指定字段名称
    Go测试学习
    微软真是活菩萨,面向初学者的机器学习、数据科学、AI、LLM课程统统免费
  • 原文地址:https://blog.csdn.net/m0_60259116/article/details/126874681