最近做的一个项目,需要给硬件传输语音。因为硬件的种种限制问题,要求:

                                  1,音频原生格式PCM。

                                  2.采样率8000,单声道,采样值大小16Bit。

       我的音频来源是接入了一个第三方的SDK,从中下载下来的音频是AAC格式的。采样率是44.1KHZ。双声道,16Bit。那么首先我需要把他转成PCM。我用的是Android原生自带的MediaCodec。具体可以看API文档。有翻译了的中文API();

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这三个类就可以实现转换采样率的功能。于是我就把那三个类搬到我的工程里面来了。

android media 硬件解码流程 android音频解码_数据

然后直接用下面这个方法就可以把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世界里的一只小菜鸡