zl程序教程

您现在的位置是:首页 >  后端

当前栏目

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

异步 方式 完成 PCM aac
2023-09-14 09:05:28 时间

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

本文福利, 免费领取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↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓