Android多媒体之 wav和amr的互转

时间:2021-12-20 19:42:03

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


源码下载