音视频5.2——MediaCodec 异步方式完成AAC硬解成PCM

  • Post author:
  • Post category:其他


音视频开发路线:


Android 音视频开发入门指南_Jhuster的专栏的技术博客_51CTO博客_android 音视频开发入门

demo地址:


GitHub – wygsqsj/videoPath: 音视频学习路线demo

MediaCodec异步方式

上一节使用同步方式使用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把消息发出去,回调的信息通过一个队列去记录一下,具体的代码如下:

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();
            }
        }
    }
}



版权声明:本文为wygsqsj原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。