基于opencore-amr实现amr-nb编码和解码,在Android上完成wav文件与amr文件格式的相互转换。wav和amr文件读写部分主要参考了opencore中的test文件夹下的例子,以及 IOS音频格式之AMR和WAV互转(更新支持amrv7s)。
1、opencore-amr的下载和编译
参考链接:Using OpenCORE AMR NB and WB Codecs(linux下的编译), GitHub代码,SourgeForge代码。
编译之后我们可以得到interf_enc.h、interf_dec.h、wrapper.cpp这些与接口有关的文件,通过这些文件相对容易写出JNI接口函数。
在linux下的编译可以得到so文件,但是是基于X86的,想要得到Arm 架构下的 so文件,需要用到ndk编译。
test文件夹下的编解码例子可以自己尝试一下,对后面编写java代码很有帮助。
2、Eclipse下Android工程建立
主要有两点:一是JAVA本地函数通过JNI调用opencore的代码(so封装、调用),二是转码相关的代码。首先需要将opencore编解码的cpp文件放在工程jni目录下,或者其他地方,然后需要编写Android.mk文件,将需要的代码包含进来.
A. JNI 接口cpp文件编写
解码和编码部分分开写,也可以合在一起写,重点是在JNI cpp文件中完成了数据类型的映射和接口函数的重写。写完之后也要包含到Android.mk文件中去。执行ndk-build就可以在lib文件夹下生成so文件。
解码部分JNI cpp代码:
1 #include <jni.h> 2 #include <interf_dec.h> 3 4 #ifndef _Included_com_example_amrcodec_decode_AmrDecInterface 5 #define _Included_com_example_amrcodec_decode_AmrDecInterface 6 #ifdef __cplusplus 7 extern "C" { 8 #endif 9 /* 10 * Class: com_example_amr_dec_decode_AmrDecInterface 11 * Method: initamr 12 * Signature: ()V 13 */ 14 JNIEXPORT int JNICALL Java_com_example_amrcodec_decode_AmrDecInterface_initDecamr 15 (JNIEnv *env, jobject ccc) { 16 17 return (jint) Decoder_Interface_init(); 18 } 19 20 /* 21 * Class: com_example_amr_dec_decode_AmrDecInterface 22 * Method: exitamr 23 * Signature: ()V 24 */ 25 JNIEXPORT int JNICALL Java_com_example_amrcodec_decode_AmrDecInterface_exitDecamr 26 (JNIEnv *env, jobject lll,jint* nativePointer) { 27 28 Decoder_Interface_exit(nativePointer); 29 30 } 31 32 /* 33 * Class: com_example_amr_dec_decode_AmrDecInterface 34 * Method: Decodeamr 35 * Signature: ([B[B)V 36 */ 37 JNIEXPORT void JNICALL Java_com_example_amrcodec_decode_AmrDecInterface_Decodeamr 38 (JNIEnv *env, jobject obj, jint* nativePointer,jbyteArray in, jshortArray out, jint bfi) 39 { 40 jsize inLen = env->GetArrayLength(in); //这里主要就是数据类型的映射 41 jbyte inBuf[inLen]; 42 env->GetByteArrayRegion(in, 0, inLen, inBuf); 43 44 jsize outLen = env->GetArrayLength(out); 45 short outBuf[outLen]; 46 47 Decoder_Interface_Decode(nativePointer, (const unsigned char*) inBuf, (short*) outBuf, bfi); 48 49 // env->ReleaseByteArrayElements(in, inBuf, JNI_ABORT); // no need - GetByteArrayRegion handles this 50 env->SetShortArrayRegion(out, 0, outLen, outBuf); //释放 51 52 } 53 54 #ifdef __cplusplus 55 } 56 #endif 57 #endif
编码部分JNI cpp代码:(多出来的枚举型很讨厌)
1 /* DO NOT EDIT THIS FILE - it is machine generated */ 2 #include <jni.h> 3 #include<interf_enc.h> 4 #include<string.h> 5 /* Header for class com_example_amr_dec_encode_AmrEncInterface */ 6 7 #ifndef _Included_com_example_amrcodec_encode_AmrEncInterface 8 #define _Included_com_example_amrcodec_encode_AmrEncInterface 9 #ifdef __cplusplus 10 extern "C" { 11 #endif 12 /* 13 * Class: com_example_amr_dec_encode_AmrEncInterface 14 * Method: initEncamr 15 * Signature: (I)I 16 */ 17 JNIEXPORT jint JNICALL Java_com_example_amrcodec_encode_AmrEncInterface_initEncamr( 18 JNIEnv *env, jobject job, jint dtxState) { 19 20 return (jint) Encoder_Interface_init(dtxState); 21 } 22 /* 23 * Class: com_example_amr_dec_encode_AmrEncInterface 24 * Method: exitEncamr 25 * Signature: (I)V 26 */ 27 JNIEXPORT void JNICALL Java_com_example_amrcodec_encode_AmrEncInterface_exitEncamr 28 (JNIEnv *env, jobject job1, jint* nativePointer) { 29 30 Encoder_Interface_exit(nativePointer); 31 32 } 33 34 /* 35 * Class: com_example_amr_dec_encode_AmrEncInterface 36 * Method: Encodeamr 37 * Signature: (ILcom/example/amr_dec/encode/AmrEncInterface/Mode;[S[BI)I 38 */ 39 JNIEXPORT jint JNICALL Java_com_example_amrcodec_encode_AmrEncInterface_Encodeamr( 40 JNIEnv *env, jobject job2, jint* nativePointer, jobject mode, 41 jshortArray speech, jbyteArray out, jint forceSpeech) { 42 43 jsize inLen = env->GetArrayLength(speech); 44 jshort inBuf[inLen]; 45 env->GetShortArrayRegion(speech, 0, inLen, inBuf); 46 jclass jcz = env->GetObjectClass(mode); 47 jmethodID getNameMethod = env->GetMethodID(jcz, "name", 48 "()Ljava/lang/String;"); 49 jstring modevalue = (jstring) env->CallObjectMethod(mode, getNameMethod); 50 const char * valueNative = env->GetStringUTFChars(modevalue, 0); 51 /*为了使用C++中定义的模式,需要做以下的映射*/ 52 Mode cmode ; 53 if(((strcmp(valueNative, "MR475") == 0))) 54 cmode = MR475; 55 if(((strcmp(valueNative, "MR515") == 0))) 56 cmode = MR515; 57 if(((strcmp(valueNative, "MR59") == 0))) 58 cmode = MR59; 59 if(((strcmp(valueNative, "MR67") == 0))) 60 cmode = MR67; 61 if(((strcmp(valueNative, "MR74") == 0))) 62 cmode = MR74; 63 if(((strcmp(valueNative, "MR795") == 0))) 64 cmode = MR795; 65 if(((strcmp(valueNative, "MR102") == 0))) 66 cmode = MR102; 67 if(((strcmp(valueNative, "MR122") == 0))) 68 cmode = MR122; 69 70 jsize outLen = env->GetArrayLength(out); 71 jbyte outBuf[outLen]; 72 int encodeLength; 73 74 encodeLength = Encoder_Interface_Encode(nativePointer, cmode, (const short*) inBuf, 75 (unsigned char*) outBuf, forceSpeech); 76 77 // env->ReleaseByteArrayElements(in, inBuf, JNI_ABORT); // no need - GetByteArrayRegion handles this 78 env->SetByteArrayRegion(out, 0, outLen, outBuf); 79 return encodeLength; 80 81 } 82 83 #ifdef __cplusplus 84 } 85 #endif 86 #endif
JAVA 本地函数部分代码(可以合在一起写,也可以放到其他文件里面去,不一定要单独写一个类)
1 package com.example.amrcodec.encode; 2 3 4 public class AmrEncInterface { 5 //本地函数的声明 6 7 public enum Mode{ 8 MR475 ,/* 4.75 kbps */ 9 MR515, /* 5.15 kbps */ 10 MR59, /* 5.90 kbps */ 11 MR67, /* 6.70 kbps */ 12 MR74, /* 7.40 kbps */ 13 MR795, /* 7.95 kbps */ 14 MR102, /* 10.2 kbps */ 15 MR122, /* 12.2 kbps */ 16 MRDTX, /* DTX */ 17 N_MODES /* Not Used */ 18 } 19 20 21 public static native int initEncamr(int ini); 22 public static native void exitEncamr(int encode); 23 public static native int Encodeamr(int gae, Mode mode, short[] in, byte[] outbuffer, int froceSpeech); 24 static{ 25 26 System.loadLibrary("amr_dec"); 27 28 } 29 30 31 }
1 package com.example.amrcodec.decode; 2 3 //本地函数的声明 4 public class AmrDecInterface{ 5 6 public static native int initDecamr(); 7 public static native void exitDecamr(int decode); 8 public static native int Decodeamr(int gae, byte[] in, short[] outbuffer, int unused); 9 static{ 10 11 System.loadLibrary("amr_dec"); 12 13 } 14 15 16 }
B.Wav文件的读写
关于wav和amr文件转换的c或者c++的代码有很多,别人做的java的相关函数也有一些,通过参考这些文件同时搞清楚amr文件和wav文件的存储结构,就可以自己编写出来java编写的转换代码。下图是wav文件头的格式:
WAV文件头,44个字节
这里还是贴一下读写wav文件的代码(也是参考网上的代码,点此链接,做一点修改就可以了)。
WaveReader.java
1 package com.example.amrcodec; 2 3 import java.io.BufferedInputStream; 4 import java.io.File; 5 import java.io.FileInputStream; 6 import java.io.FileNotFoundException; 7 import java.io.IOException; 8 9 import com.example.amrcodec.InvalidWaveException; 10 11 public class WaveReader { 12 private static final int WAV_HEADER_CHUNK_ID = 0x52494646; // "RIFF" 13 private static final int WAV_FORMAT = 0x57415645; // "WAVE" 14 private static final int WAV_FORMAT_CHUNK_ID = 0x666d7420; // "fmt " 15 private static final int WAV_DATA_CHUNK_ID = 0x64617461; // "data" 16 private static final int STREAM_BUFFER_SIZE = 4096; 17 18 private File mInFile; 19 private BufferedInputStream mInStream; 20 21 private int mSampleRate; 22 private int mChannels; 23 private int mSampleBits; 24 private int mFileSize; 25 private int mDataSize; 26 27 28 /** 29 * Constructor; initializes WaveReader to read from given file 30 * 31 * @param path path to input file 32 * @param name name of input file 33 */ 34 public WaveReader(String path, String name) { 35 this.mInFile = new File(path + File.separator + name); 36 } 37 38 /** 39 * Constructor; initializes WaveReader to read from given file 40 * 41 * @param file handle to input file 42 */ 43 public WaveReader(File file) { 44 this.mInFile = file; 45 } 46 47 /** 48 * Open WAV file for reading 49 * 50 * @throws FileNotFoundException if input file does not exist 51 * @throws InvalidWaveException if input file is not a valid WAVE file 52 * @throws IOException if I/O error occurred during file read 53 */ 54 public void openWave() throws FileNotFoundException, InvalidWaveException, IOException { 55 FileInputStream fileStream = new FileInputStream(mInFile); 56 mInStream = new BufferedInputStream(fileStream, STREAM_BUFFER_SIZE); 57 58 int headerId = readUnsignedInt(mInStream); // should be "RIFF" 59 if (headerId != WAV_HEADER_CHUNK_ID) { 60 throw new InvalidWaveException(String.format("Invalid WAVE header chunk ID: %d", headerId)); 61 } 62 mFileSize = readUnsignedIntLE(mInStream); // length of header 63 int format = readUnsignedInt(mInStream); // should be "WAVE" 64 if (format != WAV_FORMAT) { 65 throw new InvalidWaveException("Invalid WAVE format"); 66 } 67 68 int formatId = readUnsignedInt(mInStream); // should be "fmt " 69 if (formatId != WAV_FORMAT_CHUNK_ID) { 70 throw new InvalidWaveException("Invalid WAVE format chunk ID"); 71 } 72 int formatSize = readUnsignedIntLE(mInStream); 73 if (formatSize != 16) { 74 75 } 76 int audioFormat = readUnsignedShortLE(mInStream); 77 if (audioFormat != 1) { 78 throw new InvalidWaveException("Not PCM WAVE format"); 79 } 80 mChannels = readUnsignedShortLE(mInStream); 81 mSampleRate = readUnsignedIntLE(mInStream); 82 int byteRate = readUnsignedIntLE(mInStream); 83 int blockAlign = readUnsignedShortLE(mInStream); 84 mSampleBits = readUnsignedShortLE(mInStream); 85 86 int dataId = readUnsignedInt(mInStream); 87 if (dataId != WAV_DATA_CHUNK_ID) { 88 throw new InvalidWaveException("Invalid WAVE data chunk ID"); 89 } 90 mDataSize = readUnsignedIntLE(mInStream); 91 } 92 93 /** 94 * Get sample rate 95 * 96 * @return input file\'s sample rate 97 */ 98 public int getSampleRate() { 99 return mSampleRate; 100 } 101 102 /** 103 * Get number of channels 104 * 105 * @return number of channels in input file 106 */ 107 public int getChannels() { 108 return mChannels; 109 } 110 111 /** 112 * Get PCM format, S16LE or S8LE 113 * 114 * @return number of bits per sample 115 */ 116 public int getPcmFormat() { 117 return mSampleBits; 118 } 119 120 /** 121 * Get file size 122 * 123 * @return total input file size in bytes 124 */ 125 public int getFileSize() { 126 return mFileSize + 8; 127 } 128 129 /** 130 * Get input file\'s audio data size 131 * Basically file size without headers included 132 * 133 * @return audio data size in bytes 134 */ 135 public int getDataSize() { 136 return mDataSize; 137 } 138 139 /** 140 * Get input file length 141 * 142 * @return length of file in seconds 143 */ 144 public int getLength() { 145 if (mSampleRate == 0 || mChannels == 0 || (mSampleBits + 7) / 8 == 0) { 146 return 0; 147 } else { 148 return mDataSize / (mSampleRate * mChannels * ((mSampleBits + 7) / 8)); 149 } 150 } 151 152 /** 153 * Read audio data from input file (mono) 154 * 155 * @param dst mono audio data output buffer 156 * @param numSamples number of samples to read 157 * 158 * @return number of samples read 159 * 160 * @throws IOException if file I/O error occurs 161 */ 162 public int read(short[] dst, int numSamples) throws IOException { 163 if (mChannels != 1) { 164 return -1; 165 } 166 167 byte[] buf = new byte[numSamples * 2]; 168 int index = 0; 169 int bytesRead = mInStream.read(buf, 0, numSamples * 2); 170 171 for (int i = 0; i < bytesRead; i+=2) { 172 dst[index] = byteToShortLE(buf[i], buf[i+1]); 173 index++; 174 } 175 176 return index; 177 } 178 179 /** 180 * Read audio data from input file (stereo) 181 * 182 * @param left left channel audio output buffer 183 * @param right right channel audio output buffer 184 * @param numSamples number of samples to read 185 * 186 * @return number of samples read 187 * 188 * @throws IOException if file I/O error occurs 189 */ 190 public int read(short[] left, short[] right, int numSamples) throws IOException { 191 if (mChannels != 2) { 192 return -1; 193 } 194 byte[] buf = new byte[numSamples * 4]; 195 int index = 0; 196 int bytesRead = mInStream.read(buf, 0, numSamples * 4); 197 198 for (int i = 0; i < bytesRead; i+=2) { 199 short val = byteToShortLE(buf[0], buf[i+1]); 200 if (i % 4 == 0) { 201 left[index] = val; 202 } else { 203 right[index] = val; 204 index++; 205 } 206 } 207 208 return index; 209 } 210 211 /** 212 * Close WAV file. WaveReader object cannot be used again following this call. 213 * 214 * @throws IOException if I/O error occurred closing filestream 215 */ 216 public void closeWaveFile() throws IOException { 217 if (mInStream != null) { 218 mInStream.close(); 219 } 220 } 221 222 private static short byteToShortLE(byte b1, byte b2) { 223 return (short) (b1 & 0xFF | ((b2 & 0xFF) << 8)); 224 } 225 226 private static int readUnsignedInt(BufferedInputStream in) throws IOException { 227 int ret; 228 byte[] buf = new byte[4]; 229 ret = in.read(buf); 230 if (ret == -1) { 231 return -1; 232 } else { 233 return (((buf[0] & 0xFF) << 24) 234 | ((buf[1] & 0xFF) << 16) 235 | ((buf[2] & 0xFF) << 8) 236 | (buf[3] & 0xFF)); 237 } 238 } 239 240 private static int readUnsignedIntLE(BufferedInputStream in) throws IOException { 241 int ret; 242 byte[] buf = new byte[4]; 243 ret = in.read(buf); 244 if (ret == -1) { 245 return -1; 246 } else { 247 return (buf[0] & 0xFF 248 | ((buf[1] & 0xFF) << 8) 249 | ((buf[2] & 0xFF) << 16) 250 | ((buf[3] & 0xFF) << 24)); 251 } 252 } 253 254 private static short readUnsignedShortLE(BufferedInputStream in) throws IOException { 255 int ret; 256 byte[] buf = new byte[2]; 257 ret = in.read(buf, 0, 2); 258 if (ret == -1) { 259 return -1; 260 } else { 261 return byteToShortLE(buf[0], buf[1]); 262 } 263 } 264 }
1 package com.example.amrcodec; 2 3 4 import java.io.BufferedOutputStream; 5 import java.io.File; 6 import java.io.FileOutputStream; 7 import java.io.IOException; 8 import java.io.RandomAccessFile; 9 10 public class WaveWriter 11 { 12 private static final int OUTPUT_STREAM_BUFFER = 16384; 13 14 private File mOutFile; 15 private BufferedOutputStream mOutStream; 16 17 private int mSampleRate; 18 private int mChannels; 19 private int mSampleBits; 20 21 int mBytesWritten; 22 23 24 /** 25 * Constructor; initializes WaveWriter with file name and path 26 * 27 * @param path output file path 28 * @param name output file name 29 * @param sampleRate output sample rate 30 * @param channels number of channels 31 * @param sampleBits number of bits per sample (S8LE, S16LE) 32 */ 33 public WaveWriter(String path, String name, int sampleRate, int channels, 34 int sampleBits) { 35 this.mOutFile = new File(path + File.separator + name); 36 37 this.mSampleRate = sampleRate; 38 this.mChannels = channels; 39 this.mSampleBits = sampleBits; 40 41 this.mBytesWritten = 0; 42 } 43 44 /** 45 * Constructor; initializes WaveWriter with file name and path 46 * 47 * @param file output file handle 48 * @param sampleRate output sample rate 49 * @param channels number of channels 50 * @param sampleBits number of bits per sample (S8LE, S16LE) 51 */ 52 public WaveWriter(File file, int sampleRate, int channels, int sampleBits) { 53 this.mOutFile = file; 54 55 this.mSampleRate = sampleRate; 56 this.mChannels = channels; 57 this.mSampleBits = sampleBits; 58 59 this.mBytesWritten = 0; 60 } 61 62 /** 63 * Create output WAV file 64 * 65 * @return whether file creation succeeded 66 * 67 * @throws IOException if file I/O error occurs allocating header 68 */ 69 public boolean createWaveFile() throws IOException { 70 if (mOutFile.exists()) { 71 mOutFile.delete(); 72 73 } 74 75 System.out.println("marker1!"); 76 if (mOutFile.createNewFile()) { 77 78 FileOutputStream fileStream = new FileOutputStream(mOutFile); 79 mOutStream = new BufferedOutputStream(fileStream, OUTPUT_STREAM_BUFFER); 80 // write 44 bytes of space for the header 81 mOutStream.write(new byte[44]); //只是把头空出来,在文件流关闭之前写入相关数据即可。 82 return true; 83 } 84 85 return false; 86 } 87 88 /** 89 * Write audio data to output file (mono). Does 90 * nothing if output file is not mono channel. 91 * 92 * @param littleendian mono audio data input buffer 93 * @param offset offset into src buffer 94 * @param length buffer size in number of samples 95 * 96 * @throws IOException if file I/O error occurs 97 */ 98 public void write(byte[] littleendian, int offset, int length) throws IOException { 99 if (mChannels != 1) { 100 return; 101 } 102 if (offset > length) { 103 throw new IndexOutOfBoundsException(String.format("offset %d is greater than length %d", offset, length)); 104 } 105 for (int i = offset; i < length; i++) { 106 writeUnsignedBYTELE(mOutStream, littleendian[i]); 107 mBytesWritten += 1; 108 } 109 } 110 111 /** 112 * Write audio data to output file (stereo). Does 113 * nothing if output file is not stereo channel. 114 * 115 * @param left left channel audio data buffer 116 * @param right right channel audio data buffer 117 * @param offset offset into left/right buffers 118 * @param length buffer size in number of samples 119 * 120 * @throws IOException if file I/O error occurs 121 */ 122 public void write(short[] left, short[] right, int offset, int length) throws IOException { 123 if (mChannels != 2) { 124 return; 125 } 126 if (offset > length) { 127 throw new IndexOutOfBoundsException(String.format("offset %d is greater than length %d", offset, length)); 128 } 129 for (int i = offset; i < length; i++) { 130 writeUnsignedShortLE(mOutStream, left[i]); 131 writeUnsignedShortLE(mOutStream, right[i]); 132 mBytesWritten += 4; 133 } 134 } 135 136 /** 137 * Close output WAV file and write WAV header. WaveWriter 138 * cannot be used again following this call. 139 * 140 * @throws IOException if file I/O error occurs writing WAV header 141 */ 142 public void closeWaveFile() throws IOException { 143 if (mOutStream != null) { 144 this.mOutStream.flush(); 145 this.mOutStream.close(); 146 } 147 writeWaveHeader(); 148 } 149 150 private void writeWaveHeader() throws IOException { 151 // rewind to beginning of the file 152 RandomAccessFile file = new RandomAccessFile(this.mOutFile, "rw"); 153 file.seek(0); 154 155 int bytesPerSec = (mSampleBits + 7) / 8; 156 157 file.writeBytes("RIFF"); // WAV chunk header 158 file.writeInt(Integer.reverseBytes(mBytesWritten + 36)); // WAV chunk size 159 file.writeBytes("WAVE"); // WAV format 160 161 file.writeBytes("fmt "); // format subchunk header 162 file.writeInt(Integer.reverseBytes(16)); // format subchunk size 163 file.writeShort(Short.reverseBytes((short) 1)); // audio format 164 file.writeShort(Short.reverseBytes((short) mChannels)); // number of channels 165 file.writeInt(Integer.reverseBytes(mSampleRate)); // sample rate 166 file.writeInt(Integer.reverseBytes(mSampleRate * mChannels * bytesPerSec)); // byte rate 167 file.writeShort(Short.reverseBytes((short) (mChannels * bytesPerSec))); // block align 168 file.writeShort(Short.reverseBytes((short) mSampleBits)); // bits per sample 169 170 file.writeBytes("data"); // data subchunk header 171 file.writeInt(Integer.reverseBytes(mBytesWritten)); // data subchunk size 172 System.out.println("写入数据长度为:"+ mBytesWritten); 173 file.close(); 174 // file = null; 175 } 176 177 private static void writeUnsignedShortLE(BufferedOutputStream stream,short sample) 178 throws IOException { 179 // write already writes the lower order byte of this short 180 stream.write(sample); 181 //stream.write((sample >> 8)); 182 } 183 /*写一个字节的数据到输出流,原来的writeUnsignedShortLE是写两个字节,是有问题的,双声道的未作测试*/ 184 private static void writeUnsignedBYTELE(BufferedOutputStream stream,byte sample) 185 throws IOException { 186 // write already writes the lower order byte of this short 187 stream.write(sample); 188 } 189 }
C.amr编解码部分和图形界面部分
编解码部分的代码主要就是参考test文件夹下的c代码,照着写成JAVA的代码就可以了,难点就是JAVA文件流的读写了。
对单声道的amr文件来说,Header是"#AMR!/n",双声道的是不一样的,具体查阅RFC 4867 RTP Payload Format for AMR and AMR-WB。
AMR文件存储结构
arm2wav
1 package com.example.amrcodec; 2 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.IOException; 6 7 import com.example.amrcodec.decode.AmrDecInterface; 8 9 public class amr2wav { 10 /* From WmfDecBytesPerFrame in dec_input_format_tab.cpp */ 11 int sizes[] = { 12, 13, 15, 17, 19, 20, 26, 31, 5, 6, 5, 5, 0, 0, 0, 0 }; 12 private static int mNativeAmrDecoder = 0; // the pointer to the native 13 // amr-nb decoder 14 15 16 17 // 读取amr文件头的6个字节 18 19 20 public fileInfor[] convertamr(File input, File output) { 21 22 byte[] header = new byte[6]; 23 int fileSize = (int) input.length(); 24 System.out.println(fileSize); 25 fileInfor[] finfor = new fileInfor[2]; 26 /* amr文件输入信息,这里我们测试的是单声道的文件,文件头开始是“#AMR!/n” */ 27 28 finfor[0] = new fileInfor(); 29 finfor[1] = new fileInfor(); 30 finfor[0].fileType = "amr"; 31 finfor[0].fileSize = fileSize; 32 finfor[0].sampleRate = 8000; 33 finfor[0].bitsPerSample = 16; 34 finfor[0].channels = 1; 35 36 FileInputStream in = null; 37 int count = 0; 38 try { 39 in = new FileInputStream(input); 40 count = in.read(header, 0, 6); 41 } catch (Exception e) { 42 43 e.printStackTrace(); 44 System.out.println("读入文件错误!"); 45 return finfor; 46 } 47 System.out.println("开始创建文件!"); 48 if (count != 6 || header[0] != \'#\' || header[1] != \'!\' 49 || header[2] != \'A\' || header[3] != \'M\' || header[4] != \'R\' 50 || header[5] != \'\n\') { 51 System.out.println("BAD HEADER"); // 检查文件头是否是由#!AMR/n开始的 52 } 53 mNativeAmrDecoder = AmrDecInterface.initDecamr(); 54 System.out.println("开始创建文件1!"); 55 try { 56 // 创建WaveWriter对象 57 58 59 finfor[1].fileType = "wav"; 60 finfor[1].sampleRate = 8000; 61 finfor[1].bitsPerSample = 16; 62 finfor[1].channels = 1; 63 64 WaveWriter wav = new WaveWriter(output, finfor[1].sampleRate, 65 finfor[1].channels, finfor[1].bitsPerSample); 66 67 boolean flag = wav.createWaveFile(); 68 if (!flag) { 69 System.out.println("Failed to createWaveFile."); 70 in.close(); 71 return finfor; 72 } 73 int counter = 0; 74 75 while (true) { 76 byte[] buffer = new byte[500]; 77 78 if (in == null) { 79 break; 80 } 81 // 读入模式字节 82 int n = in.read(buffer, 0, 1); 83 if (n != 1) 84 break; 85 // 按照模式字节显示的数据包的大小来读数据 86 int size = sizes[(buffer[0] >> 3) & 0x0f]; 87 if (size <= 0) 88 break; 89 n = in.read(buffer, 1, 1 * size); 90 if (n != size) 91 break; 92 93 short[] outbuffer = new short[160]; 94 counter++; 95 System.out.println(counter); 96 97 // System.out.println("开始写入wav文件!"); 98 AmrDecInterface.Decodeamr(mNativeAmrDecoder, buffer, outbuffer, 99 0); 100 101 byte littleendian[] = new byte[320]; 102 int j = 0; 103 for (int i = 0; i < 160; i++) { 104 littleendian[j] = (byte) (outbuffer[i] >> 0 & 0xff); 105 littleendian[j + 1] = (byte) (outbuffer[i] >> 8 & 0xff); 106 j = j + 2; 107 } 108 109 wav.write(littleendian, 0, 320); 110 } 111 finfor[1].fileSize = wav.mBytesWritten+44; //wav文件大小 112 113 wav.closeWaveFile(); 114 in.close(); 115 System.out.println("wav文件写完!"); 116 AmrDecInterface.exitDecamr(mNativeAmrDecoder); 117 118 } catch (IOException e) { 119 120 } 121 return finfor; 122 123 } 124 125 }
wav2amr
1 package com.example.amrcodec; 2 3 import java.io.BufferedOutputStream; 4 import java.io.File; 5 import java.io.FileNotFoundException; 6 import java.io.FileOutputStream; 7 import java.io.IOException; 8 9 import com.example.amrcodec.encode.AmrEncInterface; 10 import com.example.amrcodec.encode.AmrEncInterface.Mode; 11 12 public class wav2amr { 13 14 Mode req_mode = Mode.MR475; // 先指定编码速率,后面再改 15 int dtx = 0; 16 int PCM_FRAME_SIZE = 160; // 8khz 8000*0.02=160 17 byte[] header = new byte[] { \'#\', \'!\', \'A\', \'M\', \'R\', \'\n\' }; // header,magic 18 // words! 19 20 private static int mNativeAmrEncoder = 0; // the pointer to the native 21 // amr-nb encoder 22 23 public fileInfor[] convertwav(File input, File output) 24 throws FileNotFoundException, InvalidWaveException, IOException { 25 26 fileInfor[] finfor = new fileInfor[2]; 27 finfor[0] = new fileInfor(); 28 finfor[1] = new fileInfor(); 29 30 int fileSize = (int) input.length(); 31 System.out.println(fileSize); 32 finfor[0].fileType = "wav"; 33 finfor[0].fileSize = fileSize; 34 int OUTPUT_STREAM_BUFFER = 16384; 35 /* File output stream */ 36 BufferedOutputStream mOutStream; 37 FileOutputStream fileStream = new FileOutputStream(output); 38 mOutStream = new BufferedOutputStream(fileStream, OUTPUT_STREAM_BUFFER); 39 40 WaveReader wav = new WaveReader(input); 41 wav.openWave(); 42 finfor[0].sampleRate = wav.getSampleRate(); 43 finfor[0].bitsPerSample = wav.getPcmFormat(); 44 finfor[0].channels = wav.getChannels(); 45 46 finfor[1].fileType = "amr"; 47 finfor[1].sampleRate = 8000; 48 finfor[1].bitsPerSample = 16; 49 finfor[1].channels = 1; 50 mNativeAmrEncoder = AmrEncInterface.initEncamr(dtx); 51 if (header.length != 6) { 52 System.out.println("BAD HEADER!"); 53 mOutStream.close(); 54 return finfor; 55 56 } 57 mOutStream.write(header, 0, 6); // write the header 58 int counter = 0; 59 int bytecount = 0; 60 while (true) { 61 counter++; 62 System.out.println(counter); 63 64 short[] speech = new short[160]; 65 byte[] outbuf = new byte[500]; 66 int readCount; 67 int channels = wav.getChannels();// 这里我们只测试单声道的,也先不考虑PCM每样点编码比特数 68 // int mSampleBits = wav.getPcmFormat();//获取PCM帧每样点比特数 69 int inputSize = channels * PCM_FRAME_SIZE; // 每次读取的大小160(单声道) 70 readCount = wav.read(speech, inputSize); 71 if (readCount != 160) { // 跳过有问题的帧 72 System.out.println("READ FILE ERROR!"); 73 break; 74 } 75 int outLength = AmrEncInterface.Encodeamr(mNativeAmrEncoder, 76 req_mode, speech, outbuf, 0); 77 // System.out.println(outLength); //这个长度是固定的32byte帧长 78 mOutStream.write(outbuf, 0, outLength); 79 bytecount += outLength; 80 81 } 82 finfor[1].fileSize = bytecount + 6; 83 wav.closeWaveFile(); 84 mOutStream.close(); 85 AmrEncInterface.exitEncamr(mNativeAmrEncoder); 86 87 return finfor; 88 89 } 90 91 }
activity这部分的内容写的比较简单就两个按钮,几个TextView 布局、美化也没怎么做。
Mainactivity
1 package com.example.amrcodec; 2 3 import java.io.File; 4 import java.io.FileNotFoundException; 5 import java.io.IOException; 6 7 import android.os.Bundle; 8 import android.os.Environment; 9 import android.app.Activity; 10 import android.view.Menu; 11 import android.view.View; 12 import android.widget.*; 13 14 public class MainActivity extends Activity { 15 16 private Button mybutton; 17 //private TextView mytextView; 18 private Button mybutton2; 19 //private TextView mytextView2; 20 private TextView mytextView3; 21 private TextView mytextView4; 22 private TextView mytextView5; 23 private TextView mytextView6; 24 private File sdCardDir = Environment.getExternalStorageDirectory(); //SD 卡的路径 25 26 @Override 27 protected void onCreate(Bundle savedInstanceState) { 28 super.onCreate(savedInstanceState); 29 setContentView(R.layout.activity_main); 30 31 mybutton = (Button) findViewById(R.id.mybutton1); 32 //mytextView = (TextView) findViewById(R.id.mytextview1); 33 mybutton2 = (Button) findViewById(R.id.mybutton2); 34 //mytextView2 = (TextView) findViewById(R.id.mytextview2); 35 mytextView3 = (TextView) findViewById(R.id.mytextview3); 36 mytextView4 = (TextView) findViewById(R.id.mytextview4); 37 mytextView5 = (TextView) findViewById(R.id.mytextview5); 38 mytextView6 = (TextView) findViewById(R.id.mytextview6); 39 mytextView3.setText(" "+\'\n\'+" "+\'\n\'+" "+\'\n\'+" "+\'\n\'+" "+\'\n\'); 40 mytextView4.setText(" "+\'\n\'+" "+\'\n\'+" "+\'\n\'+" "+\'\n\'+" "+\'\n\'); 41 mytextView5.setText(" "+\'\n\'+" "+\'\n\'+" "+\'\n\'+" "+\'\n\'+" "+\'\n\'); 42 mytextView6.setText(" "+\'\n\'+" "+\'\n\'+" "+\'\n\'+" "+\'\n\'+" "+\'\n\'); 43 mybutton.setOnClickListener(new Button.OnClickListener() { 44 45 @Override 46 public void onClick(View v) { 47 // TODO Auto-generated method stub 48 49 50 amr2wav aa = new amr2wav(); 51 System.out.println("创建对象"); 52 fileInfor[] displayInfor = new fileInfor[2]; 53 File amrFile = new File(sdCardDir,"test.amr"); //待转换的文件 54 55 56 File output11 = new File(sdCardDir,"testout.wav"); //输出的转换文件 57 58 displayInfor = aa.convertamr(amrFile,output11); 59 mytextView3.setText("输入:"+\'\n\'+"文件格式:"+displayInfor[0].fileType+\'\n\'+"文件大小:"+displayInfor[0].fileSize+\'\n\'+"采样频率:"+displayInfor[0].sampleRate+\'\n\'+"编码比特:"+displayInfor[0].bitsPerSample+\'\n\'+"声道数:"+displayInfor[0].channels); 60 mytextView4.setText("输出:"+\'\n\'+"文件格式:"+displayInfor[1].fileType+\'\n\'+"文件大小:"+displayInfor[1].fileSize+\'\n\'+"采样频率:"+displayInfor[1].sampleRate+\'\n\'+"编码比特:"+displayInfor[1].bitsPerSample+\'\n\'+"声道数:"+displayInfor[1].channels); 61 62 } 63 64 }); 65 66 mybutton2.setOnClickListener(new Button.OnClickListener() { 67 68 @Override 69 public void onClick(View v) { 70 // TODO Auto-generated method stub 71 72 wav2amr bb = new wav2amr(); 73 System.out.println("创建对象"); 74 File wavFile = new File(sdCardDir,"test.wav"); //待转换的文件 75 76 77 File output12 = new File(sdCardDir,"testout.amr"); //输出的转换文件 78 fileInfor[] displayInfor = new fileInfor[2]; 79 try { 80 displayInfor = bb.convertwav(wavFile,output12); 81 mytextView5.setText("输入:"+\'\n\'+"文件格式:"+displayInfor[0].fileType+\'\n\'+"文件大小:"+displayInfor[0].fileSize+\'\n\'+"采样频率:"+displayInfor[0].sampleRate+\'\n\'+"编码比特:"+displayInfor[0].bitsPerSample+\'\n\'+"声道数:"+displayInfor[0].channels); 82 mytextView6.setText("输出:"+\'\n\'+"文件格式:"+displayInfor[1].fileType+\'\n\'+"文件大小:"+displayInfor[1].fileSize+\'\n\'+"采样频率:"+displayInfor[1].sampleRate+\'\n\'+"编码比特:"+displayInfor[1].bitsPerSample+\'\n\'+"声道数:"+displayInfor[1].channels); 83 84 } catch (FileNotFoundException e) { 85 // TODO Auto-generated catch block 86 e.printStackTrace(); 87 } catch (InvalidWaveException e) { 88 // TODO Auto-generated catch block 89 e.printStackTrace(); 90 } catch (IOException e) { 91 // TODO Auto-generated catch block 92 e.printStackTrace(); 93 } 94 95 } 96 97 }); 98 99 100 } 101 102 103 @Override 104 public boolean onCreateOptionsMenu(Menu menu) { 105 // Inflate the menu; this adds items to the action bar if it is present. 106 getMenuInflater().inflate(R.menu.main, menu); 107 return true; 108 } 109 110 }
layout(全部采用线性布局)
1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:background="#FFFFFF" 6 android:orientation="vertical" 7 tools:context=".MainActivity" > 8 9 <LinearLayout 10 android:layout_width="match_parent" 11 android:layout_height="wrap_content" 12 android:orientation="vertical" > 13 14 <Button 15 android:id="@+id/mybutton1" 16 android:layout_width="match_parent" 17 android:layout_height="0dp" 18 android:layout_weight="1" 19 android:text="@string/str1" 20 android:textColor="#0033ff" /> 21 </LinearLayout> 22 23 <LinearLayout 24 android:layout_width="match_parent" 25 android:layout_height="wrap_content" 26 android:orientation="horizontal" > 27 28 <TextView 29 android:id="@+id/mytextview3" 30 android:layout_width="match_parent" 31 android:layout_height="wrap_content" 32 android:layout_gravity="left" 33 android:layout_weight="1" 34 android:textColor="#ff6600" 35 android:textSize="15sp" /> 36 37 <TextView 38 android:id="@+id/mytextview4" 39 android:layout_width="match_parent" 40 android:layout_height="wrap_content" 41 android:layout_gravity="left" 42 android:layout_weight="1" 43 android:textColor="#ff6600" 44 android:textSize="15sp" /> 45 </LinearLayout> 46 47 <LinearLayout 48 android:layout_width="match_parent" 49 android:layout_height="wrap_content" 50 android:orientation="vertical" > 51 52 <Button 53 android:id="@+id/mybutton2" 54 android:layout_width="match_parent" 55 android:layout_height="0dp" 56 android:layout_weight="1" 57 android:text="@string/str2" 58 android:textColor="#0033ff" /> 59 </LinearLayout> 60 61 <LinearLayout 62 android:layout_width="match_parent" 63 android:layout_height="wrap_content" 64 android:orientation="horizontal" > 65 66 <TextView 67 android:id="@+id/mytextview5" 68 android:layout_width="match_parent" 69 android:layout_height="wrap_content" 70 android:layout_gravity="left" 71 android:layout_weight="1" 72 android:textColor="#ff6600" 73 android:textSize="15sp" /> 74 75 <TextView 76 android:id="@+id/mytextview6" 77 android:layout_width="match_parent" 78 android:layout_height="wrap_content" 79 android:layout_gravity="left" 80 android:layout_weight="1" 81 android:textColor="#ff6600" 82 android:textSize="15sp" /> 83 </LinearLayout> 84 85 </LinearLayout>
AndroidManifest.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 3 package="com.example.amrcodec" 4 android:versionCode="1" 5 android:versionName="1.0" > 6 7 <uses-sdk 8 android:minSdkVersion="8" 9 android:targetSdkVersion="18" /> 10 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 11 <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> 12 <application 13 android:allowBackup="true" 14 android:icon="@drawable/ic_launcher" 15 android:label="@string/app_name" 16 android:theme="@style/AppTheme" > 17 <activity 18 android:name="com.example.amrcodec.MainActivity" 19 android:label="@string/app_name" > 20 <intent-filter> 21 <action android:name="android.intent.action.MAIN" /> 22 23 <category android:name="android.intent.category.LAUNCHER" /> 24 </intent-filter> 25 </activity> 26 </application> 27 28 </manifest>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
增加这两行是为了读写SD卡目录的文件。很关键,不然在Android虚拟机中是没有权限读写SD卡的。
3、Android apk demo的生成
做这个demo只是为了验证接口可行、so文件可调用,所以尽量做了简化。为了测试,需要在SD卡目录下放置需要转码的amr 和 wav文件,测试文件可以从这里下载AMR Test Files 、WAV Test Files,选择单声道(mono)某一码率的文件,输出的文件也在SD卡目 录下。demo apk转码的界面 如下图所示,显示了输入输出文件的一些基本信息。