android 音頻解碼之MediaCodec硬解碼及音頻重采樣


  最近做的一個項目,需要給硬件傳輸語音。因為硬件的種種限制問題,要求:

                                  1,音頻原生格式PCM。

                                  2.采樣率8000,單聲道,采樣值大小16Bit。

       我的音頻來源是接入了一個第三方的SDK,從中下載下來的音頻是AAC格式的。采樣率是44.1KHZ。雙聲道,16Bit。那么首先我需要把他轉成PCM。我用的是Android原生自帶的MediaCodec。具體可以看API文檔。有翻譯了的中文API(http://www.cnblogs.com/roger-yu/p/5635494.html);

public class AudioDecoder {
    private static final String TAG = "AudioDecoder ";
    private static final int TIMEOUT_US = 1000;
    private MediaExtractor mExtractor;
    private MediaCodec mDecoder;
    private String outputpath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/zzz.pcm";
    private FileOutputStream fos;
    private boolean eosReceived;
    private int mSampleRate = 0;
    int channel = 0;

    public void startPlay(String path) throws IOException {
        eosReceived = false;
        //創建MediaExtractor對象用來解AAC封裝
        mExtractor = new MediaExtractor();
        //設置需要MediaExtractor解析的文件的路徑
        try {
            mExtractor.setDataSource(path);
        } catch (Exception e) {
            Log.e(TAG, "設置文件路徑錯誤" + e.getMessage());
        }
        fos = new FileOutputStream(new File(outputpath));
        MediaFormat format = mExtractor.getTrackFormat(0);
        if (format == null) {
            Log.e(TAG, "format is null");
            return;
        }
        //   chunkPCMDataContainer = new ArrayList<>();

        //判斷當前幀的文件類型是否為audio
        String mime = format.getString(MediaFormat.KEY_MIME);
        if (mime.startsWith("audio/")) {
            Log.d(TAG, "format :" + format);
            //獲取當前音頻的采樣率
            mExtractor.selectTrack(0);
            mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
            //獲取當前幀的通道數
            channel = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
            //音頻文件的長度
            long duration = format.getLong(MediaFormat.KEY_DURATION);
            Log.d(TAG, "length:" + duration / 1000000);
        }

        //創建MedioCodec對象
        mDecoder = MediaCodec.createDecoderByType(mime);
        //配置MedioCodec
        mDecoder.configure(format, null, null, 0);

        if (mDecoder == null) {
            Log.e(TAG, "Can't find video info");
            return;
        }
        //啟動MedioCodec,等待傳入數據
        mDecoder.start();

        new Thread(AACDecoderAndPlayRunnable).start();
    }

    Runnable AACDecoderAndPlayRunnable = new Runnable() {
        @Override
        public void run() {
            AACDecoderAndPlay();
        }
    };

    private void AACDecoderAndPlay() {
        //MediaCodec在此ByteBuffer[]中獲取輸入數據
        ByteBuffer[] inputBuffers = mDecoder.getInputBuffers();
        //MediaCodec將解碼后的數據放到此ByteBuffer[]中 我們可以直接在這里面得到PCM數
        ByteBuffer[] outputBuffers = mDecoder.getOutputBuffers();

        //用於描述解碼得到的byte[]數據的相關信息
        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();

        //啟動AudioTrack,這是個播放器,可以播放PCM格式的數據。如果有需要可以用到。不需要播放的直接刪掉就可以了。
      /*  int buffsize = AudioTrack.getMinBufferSize(8000, AudioFormat.CHANNEL_OUT_STEREO,AudioFormat.ENCODING_PCM_16BIT);
        //創建AudioTrack對象
        AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                8000,
                AudioFormat.CHANNEL_OUT_STEREO,
                AudioFormat.ENCODING_PCM_16BIT,
                buffsize,
                AudioTrack.MODE_STREAM);

        //啟動AudioTrack
        audioTrack.play();*/

        while (!eosReceived) {
            //獲取可用的inputBuffer 網上很多填-1代表一直等待,0表示不等待 建議-1,避免丟幀。我這里隨便填了個1000,也沒有問題。具體我也不太清楚
            int inIndex = mDecoder.dequeueInputBuffer(TIMEOUT_US);
            if (inIndex >= 0) {
                ByteBuffer buffer = inputBuffers[inIndex];
                //從MediaExtractor中讀取一幀待解的數據
                int sampleSize = mExtractor.readSampleData(buffer, 0);
                //小於0 代表所有數據已讀取完成
                if (sampleSize < 0) {
                    Log.d(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM");
                    mDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                } else {
                    //插入一幀待解碼的數據
                    mDecoder.queueInputBuffer(inIndex, 0, sampleSize, mExtractor.getSampleTime(), 0);
                    //MediaExtractor移動到下一取樣處
                    mExtractor.advance();
                }
                //從mediadecoder隊列取出一幀解碼后的數據 參數BufferInfo上面已介紹 10000同樣為等待時間 同上-1代表一直等待,0代表不等待。此處單位為微秒
                //此處建議不要填-1 有些時候並沒有數據輸出,那么他就會一直卡在這 等待
                int outIndex = mDecoder.dequeueOutputBuffer(info, TIMEOUT_US);
                switch (outIndex) {
                    case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
                        outputBuffers = mDecoder.getOutputBuffers();
                        break;
                    case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
                        //  MediaFormat format = mDecoder.getOutputFormat();
                        //  audioTrack.setPlaybackRate(format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
                        break;
                    case MediaCodec.INFO_TRY_AGAIN_LATER:
                        break;
                    default:
                        ByteBuffer outBuffer = outputBuffers[outIndex];
                        //BufferInfo內定義了此數據塊的大小
                        final byte[] chunk = new byte[info.size];
                        //  createFileWithByte(chunk);
                        //將Buffer內的數據取出到字節數組中
                        outBuffer.get(chunk);
                        //數據取出后一定記得清空此Buffer MediaCodec是循環使用這些Buffer的,不清空下次會得到同樣的數據
                        outBuffer.clear();
                        //  putPCMData(chunk);
                        try {
                            //將解碼出來的PCM數據IO流存入本地文件。
                            fos.write(chunk);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        //  audioTrack.write(chunk,info.offset,info.offset+info.size);
                        //此操作一定要做,不然MediaCodec用完所有的Buffer后 將不能向外輸出數據
                        mDecoder.releaseOutputBuffer(outIndex, false);
                        break;
                }
                //所有幀都解碼完之后退出循環
                if (info.flags != 0) {
                    Log.i("AA", "轉碼成功++++++++++++++");
                    break;
                }

              /*
              所有幀都解碼完並播放完之后退出循環
              if((info.flags&MediaCodec.BUFFER_FLAG_END_OF_STREAM)!=0){
                    break;
                }*/
            }
        }

        Log.i("AA", "轉碼成功");
        //釋放MediaDecoder資源
        mDecoder.stop();
        mDecoder.release();
        mDecoder = null;

        //釋放MediaExtractor資源
        mExtractor.release();
        mExtractor = null;

      /*  //釋放AudioTrack資源
        audioTrack.stop();
        audioTrack.release();
        audioTrack = null;*/

        //關流
        try {
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void stop() {
        eosReceived = true;
    }

}

在Activityd里調用

    private static final String SAMPLE = Environment.getExternalStorageDirectory().getAbsolutePath()+"/需解碼的文件名";
protected static AudioDecoder mAudioDecoder;
 mAudioDecoder = new AudioDecoder();
        play.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    mAudioDecoder.startPlay(SAMPLE);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

好了,至此解碼部分已經搞定了。打你解出來文件一看。我擦嘞!一個1.6M的AAC文件。解出來有90M。差不多快擴大了80倍。不要問我什么,因為我也不知道。要么就是我解錯了,要么就是解出來就是這么大。

PCM的是解出來了。但是我的解出來的是44.1KHZ采樣率的。我的天,還要我重采樣。你要我這種小菜雞情何以堪。於是我在網上各種找。終於找到一個庫  JSSRC。

這里是github地址:https://github.com/hutm/JSSRC

里面的類也不是所有都需要用到,只需要SSRC,I0Bessel,SplitRadixFft這三個類就可以實現轉換采樣率的功能。於是我就把那三個類搬到我的工程里面來了。

然后直接用下面這個方法就可以把44.1KHZ重采樣成8K的了

 public void Resampling(){
        File BeforeSampleChangedFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/目標文件");
        File SampleChangedFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath()+"/目的文件");
        try {
            FileInputStream fis = new FileInputStream(BeforeSampleChangedFile);
            FileOutputStream fos = new FileOutputStream(SampleChangedFile);
        //同樣低采樣率轉高采樣率也是可以的,改下面參數就行。
new SSRC(fis,fos,44100,8000,2,2,1,Integer.MAX_VALUE,0,0,true); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }

基本就到此結束了。

-----------------Android世界里的一只小菜雞

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM