【android】Android多媒体之 wav和amr的互转

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

 

 


posted @ 2023-02-16 09:32  opensmarty  阅读(153)  评论(0编辑  收藏  举报