最近做的一个项目,需要给硬件传输语音。因为硬件的种种限制问题,要求:
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世界里的一只小菜鸡