兩周前閒置時候編譯了opencore for iOS, 如何編譯的請參看這一篇文章。今天又有空,所以就試著去用了一下這個庫,我想把.amr的檔案decode為.wav格式的。在test目錄下有簡單的例子,教大家如何用這個庫,於是我就照著裡面的代碼,寫了一個for iOS在xcode裡跑,結果大失所望, 轉化出來的檔案只有4K大小。
首先我說說我的方法。
建立了一個iOS的工程,然後把編譯好的lib與include檔案拖到工程裡,然後修改wav.cpp尾碼為wav.mm,並修改它的內容如下:
#import <UIKit/UIkit.h>#include "wav.h"void WavWriter::writeString(const char *str) {fputc(str[0], wav);fputc(str[1], wav);fputc(str[2], wav);fputc(str[3], wav);}void WavWriter::writeInt32(int value) {fputc((value >> 0) & 0xff, wav);fputc((value >> 8) & 0xff, wav);fputc((value >> 16) & 0xff, wav);fputc((value >> 24) & 0xff, wav);}void WavWriter::writeInt16(int value) {fputc((value >> 0) & 0xff, wav);fputc((value >> 8) & 0xff, wav);}void WavWriter::writeHeader(int length) {writeString("RIFF");writeInt32(4 + 8 + 16 + 8 + length);writeString("WAVE");writeString("fmt ");writeInt32(16);int bytesPerFrame = bitsPerSample/8*channels;int bytesPerSec = bytesPerFrame*sampleRate;writeInt16(1); // FormatwriteInt16(channels); // ChannelswriteInt32(sampleRate); // SampleratewriteInt32(bytesPerSec); // Bytes per secwriteInt16(bytesPerFrame); // Bytes per framewriteInt16(bitsPerSample); // Bits per samplewriteString("data");writeInt32(length);}WavWriter::WavWriter(const char *filename, int sampleRate, int bitsPerSample, int channels) {NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);NSString *documentPath = [paths objectAtIndex:0];NSString *docFilePath = [documentPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%s", filename]];NSLog(@"documentPath=%@", documentPath);wav = fopen([docFilePath cStringUsingEncoding:NSASCIIStringEncoding], "wb");if (wav == NULL)return;dataLength = 0;this->sampleRate = sampleRate;this->bitsPerSample = bitsPerSample;this->channels = channels;writeHeader(dataLength);}WavWriter::~WavWriter() {if (wav == NULL)return;fseek(wav, 0, SEEK_SET);writeHeader(dataLength);fclose(wav);}void WavWriter::writeData(const unsigned char* data, int length) {if (wav == NULL)return;fwrite(data, length, 1, wav);dataLength += length;}
其實只修改了一處代碼,就是fopen時的第一個參數。
準備工程都做好了,現在開始decode了,於是我寫了一個方法:
const int sizes[] = { 12, 13, 15, 17, 19, 20, 26, 31, 5, 6, 5, 5, 0, 0, 0, 0 };- (IBAction)amrnbToWav:(id)sender{NSString * path = [[NSBundle mainBundle] pathForResource: @"test" ofType: @"amr"]; FILE* in = fopen([path cStringUsingEncoding:NSASCIIStringEncoding], "rb");if (!in) {NSLog(@"open file error");}char header[6];int n = fread(header, 1, 6, in);if (n != 6 || memcmp(header, "#!AMR\n", 6)) {NSLog(@"Bad header");}WavWriter wav("out.wav", 8000, 16, 1);void* amr = Decoder_Interface_init();while (true) {uint8_t buffer[500];/* Read the mode byte */n = fread(buffer, 1, 1, in);if (n <= 0)break;/* Find the packet size */int size = sizes[(buffer[0] >> 3) & 0x0f];if (size <= 0)break;n = fread(buffer + 1, 1, size, in);if (n != size)break;/* Decode the packet */int16_t outbuffer[160];Decoder_Interface_Decode(amr, buffer, outbuffer, 0);/* Convert to little endian and write to wav */uint8_t littleendian[320];uint8_t* ptr = littleendian;for (int i = 0; i < 160; i++) {*ptr++ = (outbuffer[i] >> 0) & 0xff;*ptr++ = (outbuffer[i] >> 8) & 0xff;}wav.writeData(littleendian, 320);}fclose(in);Decoder_Interface_exit(amr);}
注意要引入相應的標頭檔
#import "wav.h"#import "interf_dec.h"#import "dec_if.h"#import "interf_enc.h"
哎,以為會成功,結果轉化不成功啊。有沒有大俠知道原因???
最開始我以為是wav頭的問題,於是去研究了一下wav頭,參看這張圖,結果發現WavWriter這個類並沒有錯。後來,我以為是for iOS編譯的問題,於是我直接在mac上編譯了一份for mac的版本,然後建了一個mac工程測試,得到與ios上一樣的結果。 最後得出結論,可能是用法錯了。如何用呢,網上的資料太少了,有沒有大俠用過的呀?
我把WAV頭格式的圖拿了過來,好東西要聽魯訊的,拿來主義,呵呵。
歡迎大家一起討論。
我已把工程上傳網盤,有空的人幫著研究一下。
http://115.com/file/bhiqw3xd#
amrDemoForiOS.zip
在cocoachina上問了高人,叫我參看:http://www.cublog.cn/u3/112227/showart_2233739.html
照著http://www.cublog.cn/u3/112227/showart_2233739.html的代碼用了一下,這回正確了, 兩個不同之處就是計算frame大小的時候那個數組不同,官方的sample卻不能用,真是鬱悶呀,官方的那個數群組成員都小於20,有人知道為何官方那麼寫嗎?想不通,不過這回成功轉化為wav檔案了。呵呵,吃了午飯,回來比較了一下代碼,發現是因為我的amr檔案有錯誤幀造成的,所以在讀取的時候遇到錯誤幀的時候要丟棄錯誤幀,但不能結束處理,因為後面還有正確幀。所以我修改了一下轉化函數如下:
- (IBAction)amrnbToWav:(id)sender{NSString * path = [[NSBundle mainBundle] pathForResource: @"test" ofType: @"amr"]; FILE* in = fopen([path cStringUsingEncoding:NSASCIIStringEncoding], "rb");if (!in) {NSLog(@"open file error");}char header[6];int n = fread(header, 1, 6, in);if (n != 6 || memcmp(header, "#!AMR\n", 6)) {NSLog(@"Bad header");}WavWriter wav("out.wav", 8000, 16, 1);void* amr = Decoder_Interface_init();int frame = 0;unsigned char stdFrameHeader;while (true) {uint8_t buffer[500];/* Read the mode byte */unsigned char frameHeader;int size;int index;if (frame == 0) {n = fread(&stdFrameHeader, 1, sizeof(unsigned char), in);index = (stdFrameHeader >> 3) &0x0f;}else{while(1) //丟棄錯誤幀,處理正確幀{n = fread(&frameHeader, 1, sizeof(unsigned char), in);if (feof(in)) return;if (frameHeader == stdFrameHeader) break;}index = (frameHeader >> 3) & 0x0f;}if (n <= 0)break;/* Find the packet size */size = sizes[index];if (size <= 0)break;n = fread(buffer + 1, 1, size, in);if (n != size)break;frame++;/* Decode the packet */int16_t outbuffer[160];Decoder_Interface_Decode(amr, buffer, outbuffer, 0);/* Convert to little endian and write to wav */uint8_t littleendian[320];uint8_t* ptr = littleendian;for (int i = 0; i < 160; i++) {*ptr++ = (outbuffer[i] >> 0) & 0xff;*ptr++ = (outbuffer[i] >> 8) & 0xff;}wav.writeData(littleendian, 320);}NSLog(@"frame=%d", frame);fclose(in);Decoder_Interface_exit(amr);}
這下就可以正常轉amr為wav檔案了。官方的那個數組是最佳化過的!!
請下載Demo Project 。有網友向我反映該Demo第一個button的轉化不能成功,第二個button的轉化能成功。我運行了一下,的確有這個問題。第一個button的轉化我是參考opencore amr的sample寫的,當時沒有太在意,因為我測試第二button的轉化成功了,於是就沒有接著測第一個。今天研究了一下,發現主要原因是由對wav頭沒處理。標準的wav頭會有一些定節對齊的問題,只需要將wav.mm裡writeHeader(int
length)函數修改如下就行了。
void WavWriter::writeHeader(int length) {writeString("RIFF");writeInt32(4 + 8 + 20 + 8 + length); //將16改為20writeString("WAVE");writeString("fmt ");writeInt32(20);int bytesPerFrame = bitsPerSample/8*channels;int bytesPerSec = bytesPerFrame*sampleRate;writeInt16(1); // FormatwriteInt16(channels); // ChannelswriteInt32(sampleRate); // SampleratewriteInt32(bytesPerSec); // Bytes per secwriteInt16(bytesPerFrame); // Bytes per framewriteInt16(bitsPerSample); // Bits per samplewriteInt32(0); //這兒需要位元組對齊 nExSizewriteString("data");writeInt32(length);}
現在就可以成功轉化了,大功告成。wav頭FMT chunk標準是24位元組,這兒卻需要28位元組,不知道是不是位元組對齊的原因造成的。
請下載完整Demo project。