上一节使用同步方式使用MediaCodec还是比较麻烦的,我们java中使用大量的回调来实现监听者模式,MediaCodec在sdk 19版本后也通过回调来告知使用者,input或者output已经准备好的情况,具体的api就是为MediaCodec设置CallBack,并实现其中的四个方法:
- decodeCodec.setCallback(new MediaCodec.Callback() {
- @Override
- public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
- Log.i(LOG_TAG, "input数据已准备好,当前index:" + index);
- }
-
- @Override
- public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
- Log.i(LOG_TAG, "outPut数据已准备好,当前index:" + index);
- }
-
- @Override
- public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
- Log.i(LOG_TAG, "编解码出错 onError" + e.toString());
- }
-
- @Override
- public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
- Log.i(LOG_TAG, "format发生更改,onOutputFormatChanged" + format.toString());
-
- }
- });
注意点:
先设置callBack,再设置config
callBack回调发生再主线程,需要自己手动切换
使用
具体的使用流程跟上一节其实是一样的,就是while循环中手动获取现在改为回调通知了,切换线程我是通过在子线程中构建Handler,收到回调之后再通过Handler把消息发出去,回调的信息通过一个队列去记录一下,具体的代码如下:
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
- public class DecodeAACAsyn extends Thread {
-
- private Context context;
- private MediaFormat audioFormat;
- private File pcmFile;
- private FileOutputStream fos = null;
-
- private MediaCodec decodeCodec = null;
- private Queue<byte[]> mOutDataQueue = new LinkedBlockingQueue<>();
- private Queue<Integer> mInputDataQueue = new LinkedBlockingQueue<>();
-
- private MediaExtractor audioExtractor = new MediaExtractor();
-
- private Handler mHandler;
-
- private Runnable outRunnable = () -> {
- try {
- Log.e(LOG_TAG, "outRunnable,当前线程: " + Thread.currentThread().getName());
- byte[] pcmData = mOutDataQueue.poll();
- if (pcmData == null) {
- return;
- }
- Log.e(LOG_TAG, "Handler回调收到,当前数据大小:" + pcmData.length);
- //装车
- fos.write(pcmData);//数据写入文件中
- fos.flush();
- } catch (Exception e) {
- e.printStackTrace();
- }
- };
-
- private Runnable inputRunnable = () -> {
- try {
- Log.e(LOG_TAG, "inputRunnable,当前线程: " + Thread.currentThread().getName());
-
- Integer index = mInputDataQueue.poll();
- if (index == null) {
- return;
- }
-
- ByteBuffer buffer;
- if (Build.VERSION.SDK_INT >= 21) {
- buffer = decodeCodec.getInputBuffer(index);
- } else {
- buffer = decodeCodec.getInputBuffers()[index];
- }
- int sampleSize = audioExtractor.readSampleData(buffer, 0);
- if (sampleSize < 0) {
- Log.i(LOG_TAG, "当前音频已经读取完了");
- decodeCodec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
- } else {
- Log.i(LOG_TAG, "读取到了音频数据,当前音频数据的数据长度为:" + sampleSize);
- long sampleTime = audioExtractor.getSampleTime();
- decodeCodec.queueInputBuffer(index, 0, sampleSize, sampleTime, 0);
- audioExtractor.advance();
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
-
- };
-
- public DecodeAACAsyn(Demo5Activity demo5Activity) {
- context = demo5Activity;
- pcmFile = new File(context.getExternalFilesDir(Environment.DIRECTORY_MUSIC), "demo5a.pcm");
- try {
- pcmFile.createNewFile();
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- }
-
- @RequiresApi(api = Build.VERSION_CODES.N)
- public void run() {
- super.run();
- Looper.prepare();
- mHandler = new Handler(Looper.myLooper()) {
- @Override
- public void handleMessage(@NonNull @NotNull Message msg) {
- super.handleMessage(msg);
- if (msg.what == 0) {
- destory();
- }
- }
- };
- Log.e(LOG_TAG, "Decode,当前线程: " + Thread.currentThread().getName());
- try {
- fos = new FileOutputStream(pcmFile.getAbsoluteFile());
- audioExtractor.setDataSource(context.getResources().openRawResourceFd(R.raw.demo5mp3));
- int count = audioExtractor.getTrackCount();
- for (int i = 0; i < count; i++) {
- audioFormat = audioExtractor.getTrackFormat(i);
- if (audioFormat.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
- audioExtractor.selectTrack(i);
- Log.i(LOG_TAG, "aac 找到了通道" + i);
- break;
- }
- }
-
- //初始化MiediaCodec
- decodeCodec = MediaCodec.createDecoderByType(audioFormat.getString(MediaFormat.KEY_MIME));
- /*
- * 通过回调方式来进行数据的编码,比刚才手动调用方式更合理,回调运行在主线程,记得切换线程
- */
- decodeCodec.setCallback(new MediaCodec.Callback() {
- @Override
- public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
- Log.i(LOG_TAG, "异步回调,onInputBufferAvailable,当前index:" + index);
- mInputDataQueue.offer(index);
- mHandler.post(inputRunnable);
-
- }
-
- @Override
- public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
- Log.i(LOG_TAG, "异步回调,onOutputBufferAvailable,当前index:" + index);
- Log.i(LOG_TAG, "获取到解码后的数据了,当前解析后的数据长度为:" + info.size);
- //拿到当前装满火腿肠的筐
- ByteBuffer outputBuffer;
- if (Build.VERSION.SDK_INT >= 21) {
- outputBuffer = codec.getOutputBuffer(index);
- } else {
- outputBuffer = codec.getOutputBuffers()[index];
- }
- //将火腿肠放到新的容器里,便于后期装车运走
- byte[] pcmData = new byte[info.size];
- outputBuffer.get(pcmData);//写入到字节数组中
- outputBuffer.clear();//清空当前筐
- //将装猪的数据放到队列里面,通过handler发送消息在子线程装入数据
- mOutDataQueue.offer(pcmData);
- mHandler.post(outRunnable);
- //把筐放回工厂里面
- codec.releaseOutputBuffer(index, false);
-
- if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
- mHandler.sendEmptyMessage(0);
- }
- }
-
- @Override
- public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
- Log.i(LOG_TAG, "异步回调,onError" + e.toString());
- }
-
- @Override
- public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
- Log.i(LOG_TAG, "异步回调,onOutputFormatChanged" + format.toString());
-
- }
- });
- //先配置callBack,再配置config;数据格式,surface用来渲染解析出来的数据;加密用的对象;标志 encode :1 decode:0
- decodeCodec.configure(audioFormat, null, null, 0);
-
- //启动解码
- decodeCodec.start();
-
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- Looper.loop();
- }
-
- public void destory() {
- Log.i(LOG_TAG, "销毁资源");
- if (audioExtractor != null) {
- audioExtractor.release();
- }
- if (decodeCodec != null) {
- decodeCodec.stop();
- decodeCodec.release();
- }
- if (fos != null) {
- try {
- fos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓