最近做的一个项目,需要给硬件传输语音。因为硬件的种种限制问题,要求:
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这三个类就可以实现转换采样率的功能。于是我就把那三个类搬到我的工程里面来了。
然后直接用下面这个方法就可以把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世界里的一只小菜鸡