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)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
相关文章
- curl 异步捉取数据类
- RabbitMQ使用交换机处理异步消息队列------发布与订阅
- 异步操作超出页面超时时间
- 充分发挥异步在 ASP.NET 中的强大优势
- jquery formValidate demo 采用struts 异步方式检验用户名是否存在
- iOS开发那些事-iOS网络编程异步GET方法请求编程
- CompletableFuture并行异步处理类使用示例
- 用手动创建新的script标签的方式,实现JavaScript脚本的异步加载
- 再谈IO的异步,同步,阻塞和非阻塞
- 用手动创建新的script标签的方式,实现JavaScript脚本的异步加载
- 〖Python语法进阶篇⑧〗- 异步关键字与gevent包
- 【Kotlin 协程】Flow 异步流 ② ( 使用 Flow 异步流持续获取不同返回值 | Flow 异步流获取返回值方式与其它方式对比 | 在 Android 中使用 Flow 异步流下载文件 )
- 15.2 asyncio--异步I/O事件库
- 异步请求库aiohttp的使用
- flask 实现异步非阻塞----gevent
- 异步