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