填坑记录
最近做语音项目,需要上传语音文件。因为需要iOS,Android两端都要能够在线播放,保存格式为aac,Android自带编码器MediaCodec,很轻松实现。
但是测试过程中遇到了问题,华为mate9不能播放,播放时解码失败,无奈啊,只能找别的办法,最后将音频转码成MP3再上传,解决了问题。
分享一下,用的是Lame实现的,实现过程,悉数奉上。
github地址 https://github.com/suntiago/lame4androiddemo
第一步需要将原始pcm流保存为wav文件。
只需要机上pcm协议头部,尾部即可。
切记需要修改,如果设置不正确,转码会失败。
//采样率
private static int frequency = 16000;
//CHANNEL_IN_STEREO;//双声道
private static int channelConfiguration = AudioFormat.CHANNEL_IN_MONO;
//采样声道
private static int channels = 1;
//音频数据格式:脉冲编码调制(PCM)每个样品16位
private static int EncodingBitRate = AudioFormat.ENCODING_PCM_16BIT;
private static final int RECORDER_BPP = 16;
//PCM 原始文件传wav
// 为wav文件添加头,尾
private void copyWaveFile(String inFilename, String outFilename) {
Log.d(TAG, "copyWaveFile");
FileInputStream in = null;
FileOutputStream out = null;
long totalAudioLen = 0;
long totalDataLen = totalAudioLen + 36;
long longSampleRate = frequency;
long byteRate = RECORDER_BPP * frequency * channels / 8;
byte[] data = new byte[1280];
try {
in = new FileInputStream(inFilename);
out = new FileOutputStream(outFilename);
totalAudioLen = in.getChannel().size();
totalDataLen = totalAudioLen + 36;
WriteWaveFileHeader(out, totalAudioLen, totalDataLen,
longSampleRate, channels, byteRate);
while (in.read(data) != -1) {
out.write(data);
}
in.close();
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void WriteWaveFileHeader(
FileOutputStream out, long totalAudioLen,
long totalDataLen, long longSampleRate, int channels,
long byteRate) throws IOException {
Log.d(TAG, "WriteWaveFileHeader");
byte[] header = new byte[44];
header[0] = 'R'; // RIFF/WAVE header
header[1] = 'I';
header[2] = 'F';
header[3] = 'F';
header[4] = (byte) (totalDataLen & 0xff);
header[5] = (byte) ((totalDataLen >> 8) & 0xff);
header[6] = (byte) ((totalDataLen >> 16) & 0xff);
header[7] = (byte) ((totalDataLen >> 24) & 0xff);
header[8] = 'W';
header[9] = 'A';
header[10] = 'V';
header[11] = 'E';
header[12] = 'f'; // 'fmt ' chunk
header[13] = 'm';
header[14] = 't';
header[15] = ' ';
header[16] = 16; // 4 bytes: size of 'fmt ' chunk
header[17] = 0;
header[18] = 0;
header[19] = 0;
header[20] = 1; // format = 1
header[21] = 0;
header[22] = (byte) channels;
header[23] = 0;
header[24] = (byte) (longSampleRate & 0xff);
header[25] = (byte) ((longSampleRate >> 8) & 0xff);
header[26] = (byte) ((longSampleRate >> 16) & 0xff);
header[27] = (byte) ((longSampleRate >> 24) & 0xff);
header[28] = (byte) (byteRate & 0xff);
header[29] = (byte) ((byteRate >> 8) & 0xff);
header[30] = (byte) ((byteRate >> 16) & 0xff);
header[31] = (byte) ((byteRate >> 24) & 0xff);
header[32] = (byte) (channels * 16 / 8); // block align
header[33] = 0;
header[34] = RECORDER_BPP; // bits per sample
header[35] = 0;
header[36] = 'd';
header[37] = 'a';
header[38] = 't';
header[39] = 'a';
header[40] = (byte) (totalAudioLen & 0xff);
header[41] = (byte) ((totalAudioLen >> 8) & 0xff);
header[42] = (byte) ((totalAudioLen >> 16) & 0xff);
header[43] = (byte) ((totalAudioLen >> 24) & 0xff);
out.write(header, 0, 44);
}
加载androidlame库,我的so库是在 https://github.com/flyingfishes/LameForAndroid基础之上编译的。
static {
System.loadLibrary("androidlame");
}
public AndroidLame() {
initializeDefault();
}
public AndroidLame(LameBuilder builder) {
initialize(builder);
}
private void initialize(LameBuilder builder) {
initialize(builder.inSampleRate, builder.outChannel, builder.outSampleRate,
builder.outBitrate, builder.scaleInput, getIntForMode(builder.mode), getIntForVbrMode(builder.vbrMode), builder.quality, builder.vbrQuality, builder.abrMeanBitrate,
builder.lowpassFreq, builder.highpassFreq, builder.id3tagTitle, builder.id3tagArtist,
builder.id3tagAlbum, builder.id3tagYear, builder.id3tagComment);
}
public int encode(short[] buffer_l, short[] buffer_r,
int samples, byte[] mp3buf) {
return lameEncode(buffer_l, buffer_r, samples, mp3buf);
}
public int encodeBufferInterLeaved(short[] pcm, int samples,
byte[] mp3buf) {
return encodeBufferInterleaved(pcm, samples, mp3buf);
}
public int flush(byte[] mp3buf) {
return lameFlush(mp3buf);
}
public void close() {
lameClose();
}
///////////NATIVE
private static native void initializeDefault();
private static native void initialize(int inSamplerate, int outChannel,
int outSamplerate, int outBitrate, float scaleInput, int mode, int vbrMode,
int quality, int vbrQuality, int abrMeanBitrate, int lowpassFreq, int highpassFreq, String id3tagTitle,
String id3tagArtist, String id3tagAlbum, String id3tagYear,
String id3tagComment);
private native static int lameEncode(short[] buffer_l, short[] buffer_r,
int samples, byte[] mp3buf);
private native static int encodeBufferInterleaved(short[] pcm, int samples,
byte[] mp3buf);
private native static int lameFlush(byte[] mp3buf);
private native static void lameClose();
////UTILS
private static int getIntForMode(LameBuilder.Mode mode) {
switch (mode) {
case STEREO:
return 0;
case JSTEREO:
return 1;
case MONO:
return 3;
case DEFAULT:
return 4;
}
return -1;
}
private static int getIntForVbrMode(LameBuilder.VbrMode mode) {
switch (mode) {
case VBR_OFF:
return 0;
case VBR_RH:
return 2;
case VBR_ABR:
return 3;
case VBR_MTRH:
return 4;
case VBR_DEFAUT:
return 6;
}
return -1;
}