最近做IM项目,看到录音上传需要涉及wav转amr格式的,看到以下文章,于是借来记载下。欢迎大家加群交流156747694
两周前空闲的时候编译了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); // Format
- writeInt16(channels); // Channels
- writeInt32(sampleRate); // Samplerate
- writeInt32(bytesPerSec); // Bytes per sec
- writeInt16(bytesPerFrame); // Bytes per frame
- writeInt16(bitsPerSample); // Bits per sample
- writeString("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);
- }
请下载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改为20
- writeString("WAVE");
- writeString("fmt ");
- writeInt32(20);
- int bytesPerFrame = bitsPerSample/8*channels;
- int bytesPerSec = bytesPerFrame*sampleRate;
- writeInt16(1); // Format
- writeInt16(channels); // Channels
- writeInt32(sampleRate); // Samplerate
- writeInt32(bytesPerSec); // Bytes per sec
- writeInt16(bytesPerFrame); // Bytes per frame
- writeInt16(bitsPerSample); // Bits per sample
- writeInt32(0); //这儿需要字节对齐 nExSize
- writeString("data");
- writeInt32(length);
- }
现在就可以成功转化了,大功告成。wav头FMT chunk标准是24字节,这儿却需要28字节,不知道是不是字节对齐的原因造成的。
请下载完整Demo project。