Android MediaCodec 音频转码——硬编硬解
来源:互联网 发布:mac找不到图片文件夹 编辑:程序博客网 时间:2024/06/16 21:03
我本来是做Android的,但是来公司之后主要负责Android端的多媒体相关,很多有关音视频编解码的都没有接触过。刚开始有一个项目使用硬编硬解完成音频的转码,刚开始我连怎么用硬编硬解都不知道,所幸在百度上找到一篇文章android MediaCodec 音频编解码的实现——转码。这篇文章介绍的很好,介绍了硬编硬解的整个流程,也接触了MediaCodec这个用来硬编硬解的类,后来还找到一个很好的学习该类的使用方法的一个网站http://bigflake.com/mediacodec/。
我的需求是将原始的视频文件中的音频转码为amr格式的音频,原始音频主要是aac格式。android MediaCodec 音频编解码的实现——转码这篇文章中是MP3到aac的转换。
原理在上述博客中讲的很清楚了,这里不再重复。
一、初始化解码器
private void initDecoder(String srcPath) { long time = System.currentTimeMillis(); //private MediaExtractor mediaExtractor; mediaExtractor = new MediaExtractor(); try { mediaExtractor.setDataSource(srcPath); //遍历媒体轨道,然后选取音频轨道 for (int i = 0; i < mediaExtractor.getTrackCount(); i++) { MediaFormat format = mediaExtractor.getTrackFormat(i); //获取音频轨道 String mime = format.getString(MediaFormat.KEY_MIME); //public static final String AUDIO = "audio/"; if (mime.startsWith(AUDIO)) { LogUtils.d(TAG, format.toString()); //选择此音频轨道 mediaExtractor.selectTrack(i); mediaDecode = MediaCodec.createDecoderByType(mime); //第二个参数是surface,解码视频的时候需要,第三个是MediaCrypto, 是关于加密的,最后一个flag填0即可 //configure会使MediaCodec进入Configured state mediaDecode.configure(format, null, null, 0); break; } } } catch (IOException e) { e.printStackTrace(); } if (mediaDecode == null) { LogUtils.e(TAG, "create mediaDecode failed"); return; } //启动MediaCodec,等待传入数据 //调用此方法之后mediaCodec进入Executing state mediaDecode.start(); //MediaCodec在此ByteBuffer[]中获取输入数据 decodeInputBuffers = mediaDecode.getInputBuffers(); decodeOutputBuffers = mediaDecode.getOutputBuffers(); //用于描述解码得到的byte[]数据的相关信息 decodeBufferInfo = new MediaCodec.BufferInfo(); LogUtils.d(TAG, " initial time:" + (System.currentTimeMillis() - time) + " ms"); }
二、初始化编码器
private void initEncoder(String outPath) { long time = System.currentTimeMillis(); try { //参数对应-> mime type、采样率、声道数 //public static final String AUDIO_AMR = "audio/3gpp"; MediaFormat encodeFormat = MediaFormat.createAudioFormat(AUDIO_AMR, 8000, 1); //设置比特率,AMR一共有8中比特率 //public static final int MR795 = 7950; /* 7.95 kbps */ encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, BitRate.MR795); //设置nputBuffer的大小 encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 100 * 1024); mediaEncode = MediaCodec.createEncoderByType(AUDIO_AMR); //最后一个参数当使用编码器时设置 mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); } catch (Exception e) { e.printStackTrace(); } if (mediaEncode == null) { Log.e(TAG, "create mediaEncode failed"); return; } mediaEncode.start(); encodeInputBuffers = mediaEncode.getInputBuffers(); encodeOutputBuffers = mediaEncode.getOutputBuffers(); //用于描述解码得到的byte[]数据的相关信息 encodeBufferInfo = new MediaCodec.BufferInfo(); LogUtils.d(TAG, "format:" + mediaEncode.getOutputFormat()); try { fos = new FileOutputStream(new File(outPath)); bos = new BufferedOutputStream(fos, 10 * 1024); //AMR对应的文件头 byte[] header = new byte[]{0x23, 0x21, 0x41, 0x4D, 0x52, 0x0A}; bos.write(header); LogUtils.d(TAG, "Write head success"); bos.flush(); } catch (IOException e) { e.printStackTrace(); } LogUtils.d(TAG, " initial time:" + (System.currentTimeMillis() - time) + " ms"); }
其中,关于AMR文件头的格式以及AMR不同频率时的帧头可以参见这篇博客AMR文件格式分析
三、编解码的流程
//解码的实现private void srcAudioFormatToPCM() { long kTimeOutUs = 1000; long time = System.currentTimeMillis(); while (true) { //decodeInputBuffers.length一般为4,可以全部使用为了加速写入数据 for (int i = 0; i < decodeInputBuffers.length; i++) { //获取可用的inputBuffer -1代表一直等待,0表示不等待。以μs为单位 int inputIndex = mediaDecode.dequeueInputBuffer(kTimeOutUs); if (inputIndex < 0) { continue; } ByteBuffer inputBuffer = decodeInputBuffers[inputIndex]; // 清空之前传入的数据 inputBuffer.clear(); int sampleSize = mediaExtractor.readSampleData(inputBuffer, 0); if (sampleSize < 0) { codeOver = true; mediaDecode.queueInputBuffer(inputIndex, 0, 0, 0, BUFFER_FLAG_END_OF_STREAM); } else { // 通知mediaDecode解码刚刚传入的数据 //经测试presentationTimeUs不设置没有问题,但是我好像在stackoverflow上看见说如果不设置,会在部分手机上出现问题 presentationTimeUs = mediaExtractor.getSampleTime(); mediaDecode.queueInputBuffer(inputIndex, 0, sampleSize, 0, 0); // MediaExtractor移动到下一个Sample mediaExtractor.advance(); decodeSize += sampleSize; } } //获取解码得到的byte[]数据 参数BufferInfo上面已介绍 1000同样为等待时间 同上-1代表一直等待,0代表不等待。 //此处单位为微秒,此处建议不要填-1 有些时候并没有数据输出,那么他就会一直卡在这等待 //decodeBufferInfo = new MediaCodec.BufferInfo(); int outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 0); LogUtils.d(TAG, "firstOutputIndex: " + outputIndex); ByteBuffer outputBuffer; byte[] chunkPCM; //每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据 if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { // Subsequent data will conform to new format. decodeOutputBuffers = mediaDecode.getOutputBuffers(); } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { } while (outputIndex >= 0) { //拿到用于存放PCM数据的Buffer outputBuffer = decodeOutputBuffers[outputIndex]; //BufferInfo内定义了此数据块的大小 //LogUtils.d(TAG, "数据块大小: " + decodeBufferInfo.size); chunkPCM = new byte[decodeBufferInfo.size]; //将Buffer内的数据取出到字节数组中 outputBuffer.get(chunkPCM); //数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据 outputBuffer.clear(); putPCMData(chunkPCM);// //此操作一定要做,不然MediaCodec用完所有的Buffer后 将不能向外输出数据 mediaDecode.releaseOutputBuffer(outputIndex, false); //再次获取数据,如果没有数据输出则outputIndex=-1 循环结束 outputIndex = mediaDecode.dequeueOutputBuffer(decodeBufferInfo, 0); } if((decodeBufferInfo.flags & BUFFER_FLAG_END_OF_STREAM) != 0){ break; } } try { rawBos.flush(); } catch (IOException e) { e.printStackTrace(); } LogUtils.d(TAG, " decode time:" + (System.currentTimeMillis() - time) + " ms"); } /** * 编码的实现 */ private void encodeAudioFromPCM() { int inputIndex; ByteBuffer inputBuffer; int outputIndex; ByteBuffer outputBuffer; byte[] chunkAudio; int outBitSize; byte[] chunkPCM; long kTimeOutUs = 10000; int numBytesSubmitted = 0; boolean doneSubmittingInput = false; int numBytesDequeued = 0; boolean encodeDone = false; for (; ; ) { for (int i = 0; i < encodeInputBuffers.length; i++) { inputIndex = mediaEncode.dequeueInputBuffer(kTimeOutUs); if(inputIndex < 0){ continue; } chunkPCM = getPCMData(); //将PCM的数据填充给inputBuffer if(chunkPCM != null) { inputBuffer = encodeInputBuffers[inputIndex]; inputBuffer.clear(); if (chunkPCMDataContainer.size() == 0) { //如果输入结束,设置BUFFER_FLAG_END_OF_STREAM mediaEncode.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); break; } //将PCM的数据填充给inputBuffer inputBuffer.put(chunkPCM); //通知mediaEncode编码刚刚传入的数据 mediaEncode.queueInputBuffer(inputIndex, 0, chunkPCM.length, 0, 0); numBytesSubmitted += chunkPCM.length; } } outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 1000); if (outputIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { encodeOutputBuffers = mediaEncode.getOutputBuffers(); } else if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { // Subsequent data will conform to new format. MediaFormat format = mediaEncode.getOutputFormat(); } while (outputIndex >= 0) { outBitSize = encodeBufferInfo.size; outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出Buffer outputBuffer.position(encodeBufferInfo.offset); outputBuffer.limit(encodeBufferInfo.offset + outBitSize); chunkAudio = new byte[outBitSize]; outputBuffer.get(chunkAudio, 0, chunkAudio.length); try { bos.write(chunkAudio, 0, chunkAudio.length);//BufferOutputStream 将文件保存到内存卡中 *.amr numBytesDequeued += chunkAudio.length; } catch (IOException e) { e.printStackTrace(); } mediaEncode.releaseOutputBuffer(outputIndex, false); //encodeBufferInfo = new MediaCodec.BufferInfo(); outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 1000); } if (codeOver && (encodeBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { Log.d(TAG, "encode finish"); break; } } Log.d(TAG, "queued a total of " + numBytesSubmitted + "bytes, " + "dequeued " + numBytesDequeued + " bytes."); try { bos.flush(); } catch (IOException e1) { e1.printStackTrace(); } }
四、遇到的问题
在编写完代码之后,满怀兴喜的运行,但是在将aac文件转为amr文件之后,播放的时候却不对,是杂音。我刚开始以为是我的流程不对,但是如果将aac文件转为MP3文件,却可以转码成功。然后我上网查如何将aac转为amr文件,找到这篇文章,http://blog.csdn.net/honeybaby201314/article/details/50379040,发现使用上述文章的AmrInputStream和开源库opencore转出来的结果都不对。然后纳闷了好长时间,也找了很多资料,都没有找到。后来终于在stackoverflow上找到一个提问https://stackoverflow.com/questions/14929478/downsampling-pcm-wav-audio-from-22khz-to-8khz。原来使用的aac的采样率一般是44100Hz,但是amr的采样率一般设置为8000Hz,所以将aac转为amr时需要downSample,将采样率从44100 变为8000,这个不是线性的,自己实现起来比较麻烦。通过这篇文章找了一个库,http://blog.csdn.net/vertx/article/details/19078391?utm_source=tuicool
这个库的地址为
JSSRC
五、downSample
调用JSSRC的代码如下
private void downSample(){ File file = new File("aacdata.pcm"); FileInputStream fis = null; FileOutputStream fileOutputStream = null; try { fis = new FileInputStream(file); fileOutputStream = new FileOutputStream("aac8000.pcm"); //参数从左到右分别是原始采样率,输出采样率,每一帧所占字节,都是2个字节 //然后是声道数,长度,attenuation衰减,dither抖动相关吧(这个我也不知道),quite是否打印相关信息 new SSRC(fis, fileOutputStream, 44100, 8000, 2, 2, 1, (int) file.length(), 0, 0, false); } catch (IOException e) { e.printStackTrace(); } finally { CloseUtil.close(fis); CloseUtil.close(fileOutputStream); } }
六、后话
通过降低采样率之后终于得到了正常的AMR文件,整个过程中遇到了很多问题,但是最后总算是解决了。利用上述的方法进行AAC到AMR文件的转码很有代表性,代表了不同采样率之间的音频文件进行转码,还有一个问题也需要注意,就是声道数,这个也是需要注意的。当原始文件与转码之后文件的声道数不一致时,可以手动取某一个声道数,在此过程中注意字节序的问题。理解了整个过程之后,不同文件之间的互相转码也可以实现了。
- Android MediaCodec 音频转码——硬编硬解
- android MediaCodec 音频编解码的实现——转码
- android MediaCodec 音频编解码的实现——转码
- android MediaCodec 音频编解码的实现——转码
- android MediaCodec 音频编解码的实现——转码(好文)
- android音频采集,MediaCodec实时PCM转AAC
- android MediaCodec 音频编解码的实现
- android硬编码h264——MediaCodec
- Android MediaCodec
- Android MediaCodec
- android下MediaCodec硬编码(转)
- android MediaCodec 音频解码+讯飞语音 实现本地语音转文字功能
- Android MediaCodec硬解码AAC音频文件(实时AAC音频帧)并播放
- Android MediaCodec硬解码AAC音频文件(实时AAC音频帧)并播放
- 【Android 多媒体应用】使用MediaCodec解码使用AudioTrack播放音频数据
- android 使用MediaCodec(根据设备状况硬编解码)来转码音频(MP3 to aac),并同时裁剪音频
- 利用MediaCodec对音频编解码
- Android多媒体—音频技术
- IntelliJ Idea 项目(初始化/删除) Git
- 深入分析数据库存储引擎
- CentOS 7.0关闭默认防火墙启用iptables防火墙
- HDU 2602 Bone Collector【01背包】
- 11。17 set
- Android MediaCodec 音频转码——硬编硬解
- 如何原样输出html标签
- U-net翻译
- SSH整合报错:org.hibernate.hql.ast.QuerySyntaxException: userinfo is not mapped [from userinfo]
- python: 迭代器 (Iterator)
- 欢迎使用CSDN-markdown编辑器
- caffe之blob_demo.cpp正常运行大法
- c学习笔记-作业-冒泡升序
- Mybatis 分页查询数据(借用map来封装参数)