Android多媒体之 wav和amr的互转

时间:2021-08-14 19:40:59

1、wav和amr文件都有头文件,AudioRecord录制出来的文件是raw格式的就不能播放,加上wav头文件就变成wav文件就可以播放。

给raw文件添加wav头文件

/** 
* 这里提供一个头信息。插入这些信息就可以得到可以播放的文件。
* 为我为啥插入这44个字节,这个还真没深入研究,不过你随便打开一个wav
* 音频的文件,可以发现前面的头文件可以说基本一样哦。每种格式的文件都有
* 自己特有的头文件。
*/
private void WriteWaveFileHeader(FileOutputStream out, long totalAudioLen, long totalDataLen, long longSampleRate, int channels, long byteRate) throws IOException {
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) (2 * 16 / 8); // block align
header[33] = 0;
header[34] = 16; // 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);
}
amr头文件

final private static byte[] header = new byte[]{0x23, 0x21, 0x41, 0x4D, 0x52, 0x0A};

2、wav或raw转amr

     2.1 通过Android系统自带的AmrInputStream类,因为它被隐藏了,只有通过反射来操作。

public class AmrInputStream extends InputStream
{
static {
System.loadLibrary("media");
}

private final static String TAG = "AmrInputStream";

// frame is 20 msec at 8.000 khz
private final static int SAMPLES_PER_FRAME = 8000 * 20 / 1000;

// pcm input stream
private InputStream mInputStream;

// native handle
private int mGae;

// result amr stream
private final byte[] mBuf = new byte[SAMPLES_PER_FRAME * 2];
private int mBufIn = 0;
private int mBufOut = 0;

// helper for bytewise read()
private byte[] mOneByte = new byte[1];

/**
* Create a new AmrInputStream, which converts 16 bit PCM to AMR
* @param inputStream InputStream containing 16 bit PCM.
*/
public AmrInputStream(InputStream inputStream) {
mInputStream = inputStream;
mGae = GsmAmrEncoderNew();
GsmAmrEncoderInitialize(mGae);
}

@Override
public int read() throws IOException {
int rtn = read(mOneByte, 0, 1);
return rtn == 1 ? (0xff & mOneByte[0]) : -1;
}

@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}

@Override
public int read(byte[] b, int offset, int length) throws IOException {
if (mGae == 0) throw new IllegalStateException("not open");

// local buffer of amr encoded audio empty
if (mBufOut >= mBufIn) {
// reset the buffer
mBufOut = 0;
mBufIn = 0;

// fetch a 20 msec frame of pcm
for (int i = 0; i < SAMPLES_PER_FRAME * 2; ) {
int n = mInputStream.read(mBuf, i, SAMPLES_PER_FRAME * 2 - i);
if (n == -1) return -1;
i += n;
}

// encode it
mBufIn = GsmAmrEncoderEncode(mGae, mBuf, 0, mBuf, 0);
}

// return encoded audio to user
if (length > mBufIn - mBufOut) length = mBufIn - mBufOut;
System.arraycopy(mBuf, mBufOut, b, offset, length);
mBufOut += length;

return length;
}

@Override
public void close() throws IOException {
try {
if (mInputStream != null) mInputStream.close();
} finally {
mInputStream = null;
try {
if (mGae != 0) GsmAmrEncoderCleanup(mGae);
} finally {
try {
if (mGae != 0) GsmAmrEncoderDelete(mGae);
} finally {
mGae = 0;
}
}
}
}

@Override
protected void finalize() throws Throwable {
if (mGae != 0) {
close();
throw new IllegalStateException("someone forgot to close AmrInputStream");
}
}

//
// AudioRecord JNI interface
//
public static native int GsmAmrEncoderNew();
public static native void GsmAmrEncoderInitialize(int gae);
public static native int GsmAmrEncoderEncode(int gae,
byte[] pcm, int pcmOffset, byte[] amr, int amrOffset) throws IOException;
public static native void GsmAmrEncoderCleanup(int gae);
public static native void GsmAmrEncoderDelete(int gae);

}

JNI位于源代码下的frameworks\base\media\jni目录,对应的libmedia_jni.so文件文件在system/lib,打开File Exploer就可以看到。

下面是通过反射进行文件转换

 /**
* 通过反射调用android系统自身AmrInputStream类进行转换
* @param inPath 源文件
* @param outPath 目标文件
*/
public void systemWav2Amr(String inPath,String outPath){
try {
FileInputStream fileInputStream = new FileInputStream(inPath);
FileOutputStream fileoutputStream = new FileOutputStream(outPath);
// 获得Class
Class<?> cls = Class.forName("android.media.AmrInputStream");
// 通过Class获得所对应对象的方法
Method[] methods = cls.getMethods();
// 输出每个方法名
fileoutputStream.write(header);
Constructor<?> con = cls.getConstructor(InputStream.class);
Object obj = con.newInstance(fileInputStream);
for (Method method : methods) {
Class<?>[] parameterTypes = method.getParameterTypes();
if ("read".equals(method.getName())
&& parameterTypes.length == 3) {
byte[] buf = new byte[1024];
int len = 0;
while ((len = (int) method.invoke(obj, buf, 0, 1024)) > 0) {
fileoutputStream.write(buf, 0, len);
}
break;
}
}
for (Method method : methods) {
if ("close".equals(method.getName())) {
method.invoke(obj);
break;
}
}
fileoutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}

2.2 通过开源库opencore进行转换,下面是jni部分

public class AmrEncoder {

public enum Mode {
MR475,/* 4.75 kbps */
MR515, /* 5.15 kbps */
MR59, /* 5.90 kbps */
MR67, /* 6.70 kbps */
MR74, /* 7.40 kbps */
MR795, /* 7.95 kbps */
MR102, /* 10.2 kbps */
MR122, /* 12.2 kbps */
MRDTX, /* DTX */
N_MODES /* Not Used */
}

public static native void init(int dtx);

public static native int encode(int mode, short[] in, byte[] out);

public static native void reset();

public static native void exit();

static {
System.loadLibrary("amr-codec");
}
}

转换操作,里面也有解码的部分

private void wav2amr(final String inpath,final String outpath){
//Random random = new Random();
//File file = new File(root + "/RawAudio.raw");
// file.getAbsolutePath(),root + "/test" + random.nextInt(120) + ".amr"
new Thread(new Runnable() {
@Override
public void run() {
convertAMR(inpath,outpath);
}
}).start();
}
/**
* 将wav或raw文件转换成amr
* @param inpath 源文件
* @param outpath 目标文件
*/
private void convertAMR(String inpath,String outpath){
try {
AmrEncoder.init(0);
File inFile = new File(inpath);
List<short[]> armsList = new ArrayList<short[]>();
FileInputStream inputStream = new FileInputStream(inFile);
FileOutputStream outStream = new FileOutputStream(outpath);
//写入Amr头文件
outStream.write(header);

int byteSize = 320;
byte[] buff = new byte[byteSize];
int rc = 0;
while ((rc = inputStream.read(buff, 0, byteSize)) > 0) {
short[] shortTemp = new short[160];
//将byte[]转换成short[]
ByteBuffer.wrap(buff).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shortTemp);
//将short[]转换成byte[]
//ByteBuffer.wrap(buff).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().put(shortTemp);
armsList.add(shortTemp);
}

for (int i = 0; i < armsList.size(); i++) {
int size = armsList.get(i).length;
byte[] encodedData = new byte[size*2];
int len = AmrEncoder.encode(AmrEncoder.Mode.MR122.ordinal(), armsList.get(i), encodedData);
if (len>0) {
byte[] tempBuf = new byte[len];
System.arraycopy(encodedData, 0, tempBuf, 0, len);
outStream.write(tempBuf, 0, len);
}
}
AmrEncoder.reset();
AmrEncoder.exit();

outStream.close();
inputStream.close();
System.out.println("convert success ... "+outpath);
} catch (Exception e) {
e.printStackTrace();
}
}

3、参考资料

Android音频实时传输与播放(三):AMR硬编码与硬解码

https://github.com/kevinho/opencore-amr-android


源码下载