我本来是做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文件的转码很有代表性,代表了不同采样率之间的音频文件进行转码,还有一个问题也需要注意,就是声道数,这个也是需要注意的。当原始文件与转码之后文件的声道数不一致时,可以手动取某一个声道数,在此过程中注意字节序的问题。理解了整个过程之后,不同文件之间的互相转码也可以实现了。

更多相关文章

  1. 一句话锁定MySQL数据占用元凶
  2. Android(安卓)4.0 CTS Debug
  3. Android7.1.2源码解析系列】Android编译系统翻译------Android_B
  4. android省市二级联动的实现
  5. Android(安卓)app中加载jar插件
  6. Android日志:Google官方下拉刷新控件
  7. Android直播开发之旅(16):使用FFmpeg保存网络流到本地文件
  8. Android新技术------Android(安卓)App Bundle之bundletool的使用
  9. Android中 的各种Adapter 整理

随机推荐

  1. android从网络获取图片
  2. Android NumberPickerDialog
  3. Android 拦截TextView中超链接点击事件
  4. 往Android Studio中导入项目时遇到Instal
  5. android 获取APP大小及其清理缓存内容
  6. 获取当前应用的版本号/应用名称
  7. android用听筒不断播放音乐
  8. android ViewPager加Fragment实现导航加
  9. android source code compile
  10. Android之Message机制的灵活应用