目錄
1. 概述 2. AMR編碼 3. AMR解碼 4. AMR幀讀取算法 5. 參考資料 1. 概述 現(xiàn)在很多智能手機都支持多媒體功能,特別是音頻和視頻播放功能,而AMR文件格式是手機端普遍支持的音頻文件格式。 AMR,全稱是:Adaptive Multi-Rate,自適應多速率,是一種音頻編碼文件格式,專用于有效地壓縮語音頻率。 AMR音頻主要用于移動設備的音頻壓縮,壓縮比非常高,但是音質(zhì)比較差,主要用于語音類的音頻壓縮,不適合對音質(zhì)要求較高的音樂類音頻的壓縮。 AMR的編解碼是基于“3GPP AMR Floating-point Speech Codec”來做的,3GPP還專門開放了基于ANSI-C實現(xiàn)的編解碼代碼,便于我們在各種平臺上進行移植。 #ifndef amrFileCodec_h #define amrFileCodec_h #define AMR_MAGIC_NUMBER "#!AMR\n" #define PCM_FRAME_SIZE 160 // 8khz 8000*0.02=160 #define MAX_AMR_FRAME_SIZE 32 #define AMR_FRAME_COUNT_PER_SECOND 50 //int amrEncodeMode[] = {4750, 5150, 5900, 6700, 7400, 7950, 10200, 12200}; // amr 編碼方式 typedef struct { char chChunkID[4]; int nChunkSize; }XCHUNKHEADER; typedef struct { short nFormatTag; short nChannels; int nSamplesPerSec; int nAvgBytesPerSec; short nBlockAlign; short nBitsPerSample; }WAVEFORMAT; typedef struct { short nFormatTag; short nChannels; int nSamplesPerSec; int nAvgBytesPerSec; short nBlockAlign; short nBitsPerSample; short nExSize; }WAVEFORMATX; typedef struct { char chRiffID[4]; int nRiffSize; char chRiffFormat[4]; }RIFFHEADER; typedef struct { char chFmtID[4]; int nFmtSize; WAVEFORMAT wf; }FMTBLOCK; // WAVE音頻采樣頻率是8khz // 音頻樣本單元數(shù) = 8000*0.02 = 160 (由采樣頻率決定) // 聲道數(shù) 1 : 160 // 2 : 160*2 = 320 // bps決定樣本(sample)大小 // bps = 8 --> 8位 unsigned char // 16 --> 16位 unsigned short int EncodeWAVEFileToAMRFile(const char* pchWAVEFilename, const char* pchAMRFileName, int nChannels, int nBitsPerSample); // 將AMR文件解碼成WAVE文件 int DecodeAMRFileToWAVEFile(const char* pchAMRFileName, const char* pchWAVEFilename); #endif 2. AMR編碼 3GPP提供了編碼代碼,并提供了一個encoder.c程序,該程序示范了如何對一個16位的單聲道PCM數(shù)據(jù)進行壓縮的。(采樣頻率必須是8khz) 我對該程序進行一定的拓展,數(shù)據(jù)位支持8位和16位,可以是單聲道和雙聲道。 l 對于8位PCM只需要將每個采樣的sample數(shù)據(jù)位擴展成16位,并左移7位。 l 對于雙聲道,可以只對左聲道數(shù)據(jù)進行處理,也可以只對右聲道數(shù)據(jù)進行處理,或者將左右聲道數(shù)據(jù)求平均值就可。 這樣兩個小處理,就可以將PCM規(guī)范成3PGG的編碼器需要的數(shù)據(jù)格式。 代碼在 amrFileEncoder.c 中。 #include "amrFileCodec.h" // 從WAVE文件中跳過WAVE文件頭,直接到PCM音頻數(shù)據(jù) void SkipToPCMAudioData(FILE* fpwave) { RIFFHEADER riff; FMTBLOCK fmt; XCHUNKHEADER chunk; WAVEFORMATX wfx; int bDataBlock = 0; // 1. 讀RIFF頭 fread(&riff, 1, sizeof(RIFFHEADER), fpwave); // 2. 讀FMT塊 - 如果 fmt.nFmtSize>16 說明需要還有一個附屬大小沒有讀 fread(&chunk, 1, sizeof(XCHUNKHEADER), fpwave); if ( chunk.nChunkSize>16 ) { fread(&wfx, 1, sizeof(WAVEFORMATX), fpwave); } else { memcpy(fmt.chFmtID, chunk.chChunkID, 4); fmt.nFmtSize = chunk.nChunkSize; fread(&fmt.wf, 1, sizeof(WAVEFORMAT), fpwave); } // 3.轉(zhuǎn)到data塊 - 有些還有fact塊等。 while(!bDataBlock) { fread(&chunk, 1, sizeof(XCHUNKHEADER), fpwave); if ( !memcmp(chunk.chChunkID, "data", 4) ) { bDataBlock = 1; break; } // 因為這個不是data塊,就跳過塊數(shù)據(jù) fseek(fpwave, chunk.nChunkSize, SEEK_CUR); } } // 從WAVE文件讀一個完整的PCM音頻幀 // 返回值: 0-錯誤 >0: 完整幀大小 int ReadPCMFrame(short speech[], FILE* fpwave, int nChannels, int nBitsPerSample) { int nRead = 0; int x = 0, y=0; unsigned short ush1=0, ush2=0, ush=0; // 原始PCM音頻幀數(shù)據(jù) unsigned char pcmFrame_8b1[PCM_FRAME_SIZE]; unsigned char pcmFrame_8b2[PCM_FRAME_SIZE<<1]; unsigned short pcmFrame_16b1[PCM_FRAME_SIZE]; unsigned short pcmFrame_16b2[PCM_FRAME_SIZE<<1]; if (nBitsPerSample==8 && nChannels==1) { nRead = fread(pcmFrame_8b1, (nBitsPerSample/8), PCM_FRAME_SIZE*nChannels, fpwave); for(x=0; x<PCM_FRAME_SIZE; x++) { speech[x] =(short)((short)pcmFrame_8b1[x] << 7); } } else if (nBitsPerSample==8 && nChannels==2) { nRead = fread(pcmFrame_8b2, (nBitsPerSample/8), PCM_FRAME_SIZE*nChannels, fpwave); for( x=0, y=0; y<PCM_FRAME_SIZE; y++,x+=2 ) { // 1 - 取兩個聲道之左聲道 speech[y] =(short)((short)pcmFrame_8b2[x+0] << 7); // 2 - 取兩個聲道之右聲道 //speech[y] =(short)((short)pcmFrame_8b2[x+1] << 7); // 3 - 取兩個聲道的平均值 //ush1 = (short)pcmFrame_8b2[x+0]; //ush2 = (short)pcmFrame_8b2[x+1]; //ush = (ush1 + ush2) >> 1; //speech[y] = (short)((short)ush << 7); } } else if (nBitsPerSample==16 && nChannels==1) { nRead = fread(pcmFrame_16b1, (nBitsPerSample/8), PCM_FRAME_SIZE*nChannels, fpwave); for(x=0; x<PCM_FRAME_SIZE; x++) { speech[x] = (short)pcmFrame_16b1[x+0]; } } else if (nBitsPerSample==16 && nChannels==2) { nRead = fread(pcmFrame_16b2, (nBitsPerSample/8), PCM_FRAME_SIZE*nChannels, fpwave); for( x=0, y=0; y<PCM_FRAME_SIZE; y++,x+=2 ) { //speech[y] = (short)pcmFrame_16b2[x+0]; speech[y] = (short)((int)((int)pcmFrame_16b2[x+0] + (int)pcmFrame_16b2[x+1])) >> 1; } } // 如果讀到的數(shù)據(jù)不是一個完整的PCM幀, 就返回0 if (nRead<PCM_FRAME_SIZE*nChannels) return 0; return nRead; } // WAVE音頻采樣頻率是8khz // 音頻樣本單元數(shù) = 8000*0.02 = 160 (由采樣頻率決定) // 聲道數(shù) 1 : 160 // 2 : 160*2 = 320 // bps決定樣本(sample)大小 // bps = 8 --> 8位 unsigned char // 16 --> 16位 unsigned short int EncodeWAVEFileToAMRFile(const char* pchWAVEFilename, const char* pchAMRFileName, int nChannels, int nBitsPerSample) { FILE* fpwave; FILE* fpamr; /* input speech vector */ short speech[160]; /* counters */ int byte_counter, frames = 0, bytes = 0; /* pointer to encoder state structure */ int *enstate; /* requested mode */ enum Mode req_mode = MR122; int dtx = 0; /* bitstream filetype */ unsigned char amrFrame[MAX_AMR_FRAME_SIZE]; fpwave = fopen(pchWAVEFilename, "rb"); if (fpwave == NULL) { return 0; } // 創(chuàng)建并初始化amr文件 fpamr = fopen(pchAMRFileName, "wb"); if (fpamr == NULL) { fclose(fpwave); return 0; } /* write magic number to indicate single channel AMR file storage format */ bytes = fwrite(AMR_MAGIC_NUMBER, sizeof(char), strlen(AMR_MAGIC_NUMBER), fpamr); /* skip to pcm audio data*/ SkipToPCMAudioData(fpwave); enstate = Encoder_Interface_init(dtx); while(1) { // read one pcm frame if (!ReadPCMFrame(speech, fpwave, nChannels, nBitsPerSample)) break; frames++; /* call encoder */ byte_counter = Encoder_Interface_Encode(enstate, req_mode, speech, amrFrame, 0); bytes += byte_counter; fwrite(amrFrame, sizeof (unsigned char), byte_counter, fpamr ); } Encoder_Interface_exit(enstate); fclose(fpamr); fclose(fpwave); return frames; } 3. AMR解碼 3GPP提供了解碼代碼,并提供了一個decoder.c程序,該程序示范了如何對amr音頻進行解碼。解碼成一個wave文件(8khz 16位單聲道)。 解碼是需要注意AMR壞幀的處理。在AMR讀幀算法中有說明。 文件解碼器代碼在 amrFileDecoder.c 中。 #include "amrFileCodec.h" void WriteWAVEFileHeader(FILE* fpwave, int nFrame) { char tag[10] = ""; // 1. 寫RIFF頭 strcpy(tag, "RIFF"); memcpy(riff.chRiffID, tag, 4); riff.nRiffSize = 4 // WAVE + sizeof(XCHUNKHEADER) // fmt + sizeof(WAVEFORMATX) // WAVEFORMATX + sizeof(XCHUNKHEADER) // DATA + nFrame*160*sizeof(short); // strcpy(tag, "WAVE"); memcpy(riff.chRiffFormat, tag, 4); fwrite(&riff, 1, sizeof(RIFFHEADER), fpwave); // 2. 寫FMT塊 strcpy(tag, "fmt "); memcpy(chunk.chChunkID, tag, 4); chunk.nChunkSize = sizeof(WAVEFORMATX); fwrite(&chunk, 1, sizeof(XCHUNKHEADER), fpwave); memset(&wfx, 0, sizeof(WAVEFORMATX)); wfx.nFormatTag = 1; wfx.nChannels = 1; // 單聲道 wfx.nSamplesPerSec = 8000; // 8khz wfx.nAvgBytesPerSec = 16000; wfx.nBlockAlign = 2; wfx.nBitsPerSample = 16; // 16位 fwrite(&wfx, 1, sizeof(WAVEFORMATX), fpwave); // 3. 寫data塊頭 strcpy(tag, "data"); memcpy(chunk.chChunkID, tag, 4); chunk.nChunkSize = nFrame*160*sizeof(short); fwrite(&chunk, 1, sizeof(XCHUNKHEADER), fpwave); } const int round(const double x) { return((int)(x+0.5)); } // 根據(jù)幀頭計算當前幀大小 int caclAMRFrameSize(unsigned char frameHeader) { int mode; int temp1 = 0; int temp2 = 0; int frameSize; temp1 = frameHeader; // 編碼方式編號 = 幀頭的3-6位 temp1 &= 0x78; // 0111-1000 temp1 >>= 3; mode = amrEncodeMode[temp1]; // 計算amr音頻數(shù)據(jù)幀大小 // 原理: amr 一幀對應20ms,那么一秒有50幀的音頻數(shù)據(jù) temp2 = round((double)(((double)mode / (double)AMR_FRAME_COUNT_PER_SECOND) / (double)8)); frameSize = round((double)temp2 + 0.5); return frameSize; } // 讀第一個幀 - (參考幀) // 返回值: 0-出錯; 1-正確 int ReadAMRFrameFirst(FILE* fpamr, unsigned char frameBuffer[], int* stdFrameSize, unsigned char* stdFrameHeader) { memset(frameBuffer, 0, sizeof(frameBuffer)); // 先讀幀頭 fread(stdFrameHeader, 1, sizeof(unsigned char), fpamr); if (feof(fpamr)) return 0; // 根據(jù)幀頭計算幀大小 *stdFrameSize = caclAMRFrameSize(*stdFrameHeader); // 讀首幀 frameBuffer[0] = *stdFrameHeader; fread(&(frameBuffer[1]), 1, (*stdFrameSize-1)*sizeof(unsigned char), fpamr); if (feof(fpamr)) return 0; return 1; } // 返回值: 0-出錯; 1-正確 int ReadAMRFrame(FILE* fpamr, unsigned char frameBuffer[], int stdFrameSize, unsigned char stdFrameHeader) { int bytes = 0; unsigned char frameHeader; // 幀頭 memset(frameBuffer, 0, sizeof(frameBuffer)); // 讀幀頭 // 如果是壞幀(不是標準幀頭),則繼續(xù)讀下一個字節(jié),直到讀到標準幀頭 while(1) { bytes = fread(&frameHeader, 1, sizeof(unsigned char), fpamr); if (feof(fpamr)) return 0; if (frameHeader == stdFrameHeader) break; } // 讀該幀的語音數(shù)據(jù)(幀頭已經(jīng)讀過) frameBuffer[0] = frameHeader; bytes = fread(&(frameBuffer[1]), 1, (stdFrameSize-1)*sizeof(unsigned char), fpamr); if (feof(fpamr)) return 0; return 1; } // 將AMR文件解碼成WAVE文件 int DecodeAMRFileToWAVEFile(const char* pchAMRFileName, const char* pchWAVEFilename) { FILE* fpamr = NULL; FILE* fpwave = NULL; char magic[8]; int * destate; int nFrameCount = 0; int stdFrameSize; unsigned char stdFrameHeader; unsigned char amrFrame[MAX_AMR_FRAME_SIZE]; short pcmFrame[PCM_FRAME_SIZE]; fpamr = fopen(pchAMRFileName, "rb"); if ( fpamr==NULL ) return 0; // 檢查amr文件頭 fread(magic, sizeof(char), strlen(AMR_MAGIC_NUMBER), fpamr); if (strncmp(magic, AMR_MAGIC_NUMBER, strlen(AMR_MAGIC_NUMBER))) { fclose(fpamr); return 0; } // 創(chuàng)建并初始化WAVE文件 fpwave = fopen(pchWAVEFilename, "wb"); WriteWAVEFileHeader(fpwave, nFrameCount); /* init decoder */ destate = Decoder_Interface_init(); // 讀第一幀 - 作為參考幀 memset(amrFrame, 0, sizeof(amrFrame)); memset(pcmFrame, 0, sizeof(pcmFrame)); ReadAMRFrameFirst(fpamr, amrFrame, &stdFrameSize, &stdFrameHeader); // 解碼一個AMR音頻幀成PCM數(shù)據(jù) Decoder_Interface_Decode(destate, amrFrame, pcmFrame, 0); nFrameCount++; fwrite(pcmFrame, sizeof(short), PCM_FRAME_SIZE, fpwave); // 逐幀解碼AMR并寫到WAVE文件里 while(1) { memset(amrFrame, 0, sizeof(amrFrame)); memset(pcmFrame, 0, sizeof(pcmFrame)); if (!ReadAMRFrame(fpamr, amrFrame, stdFrameSize, stdFrameHeader)) break; // 解碼一個AMR音頻幀成PCM數(shù)據(jù) (8k-16b-單聲道) Decoder_Interface_Decode(destate, amrFrame, pcmFrame, 0); nFrameCount++; fwrite(pcmFrame, sizeof(short), PCM_FRAME_SIZE, fpwave); } Decoder_Interface_exit(destate); fclose(fpwave); // 重寫WAVE文件頭 fpwave = fopen(pchWAVEFilename, "r+"); WriteWAVEFileHeader(fpwave, nFrameCount); fclose(fpwave); return nFrameCount; } 4. AMR幀讀取算法 因為可能存在異常幀,所以不一定所有的語音幀大小一致,對于跟正常幀大小不一致的,或者幀頭跟正常幀頭不一致的,就不交給解碼器,直接拋棄該壞幀。 讀取幀的算法,用C語言來編寫,readAMRFrame.c,JAVA可以用類似的方法。 下面是算法描述流程圖。 讀首幀(標準幀) ReadFirstAMRFrame 根據(jù)幀頭計算標準幀的大小 caclAMRFrameSize AMR音頻文件流 讀幀頭(字節(jié)) frameHeader 判斷是否為壞幀? Y N 讀本幀音頻數(shù)據(jù) 幀頭 + 音頻數(shù)據(jù) = 當前幀數(shù)據(jù) 5. 參考資料 l rfc3267 http://www./rfc/rfc3267.txt http://ietfreport./rfc/PDF/rfc3267.pdf l 3GPP TS 26.104 V 6.1.0 (2004-03) http://www./ftp/Specs/html-info/26104-CRs.htm l 3GPP AMR Floating-point Speech Codec http://www./ftp/Specs/html-info/26104.htm l “amr編程匯總” http://blog.csdn.net/windcao/archive/2006/01/04/570348.aspx l 關于AMR文件格式的解釋 http://www./blog/user1/11409/archives/2006/16832.html 本文來自CSDN博客,轉(zhuǎn)載請標明出處:http://blog.csdn.net/jinlking/archive/2009/06/10/4256311.aspx
|
|