amr编程汇总
今年8月我负责研发amr语音的录制和格式转换,现在将经验于大家分享一下。
amr的特性:
a静音期间内的存储空间几乎为0,所以amr文件的长度并不与录音时间成正比。
b压缩比高,连续讲话60秒的录音文件大概20k左右,因此你可以用手机存储传输amr而不用太担心存储空间和gprs流量费。
一 首先说一下格式转换的问题:
提起格式转换建议看看http://xa.bi/mms/
.-----------.
-->| MID/MOD |
`-----+-----'
|
timidity
| | |
v v v
.---------. .-------------. .-------. .-----------. .-------.
| +--mp123->| | | +--encode->| +--ifs2amr->| |
| MP3 | | WAV |<-sox->| RAW | | AMR-IF2 | | AMR |
| |<--lame--+ | | |<-decode--| |<-amr2ifs--+ |
`----+----' `---------+---' `-------' `-----------' `---+---'
| ^ | |
v | v v
这里介绍了一个大致的转换思路,以及需要用到的相应工具。
主要思路是首先将amr通过一系列转换变成wav然后在转其他格式。
本文接下来就剖析一下amr转换成wav的过程。
1首先让我们来了解一下amr的文件格式。
http://www.ietf.org/rfc/rfc3267.txt定义了amr的文件格式。
amr是一种有损压缩,最初的版本只用来存储单声道的语音文件,比特率为4.75 kbit/s。
后续版本提供了双声道和更多的表现细节的能力,
主要是采样率的不断的提高一直到最后的比特率12.5 kbit/s,
最终使得amr可以存储高质量的音乐,当然也占用了大量的存储空间。
2AMR-IF2文件格式。定制AMR-IF2的目的就是用于格式转换的。AMR-IF2相当于一个接口文件格式。
这样在amr的版本不断变化的情况下,我们无需对先前的转换播放从程序做太多的改变。
3两者的区别和联系:
AMR-IF2是没有文件头的。而amr文件有着不同的文件头但所有的文件头有一个共同的特点,
都包括 {0x23,0x21,0x41,0x4D,0x52,0x 0A}(#!AMR+回车)
3.1两者都是分贞的每贞都有固定的长度。与比特率相关。
主要有两种贞,一种是用来记录一小段时间内的声音采样,
采用了小波变换进行提取压缩,压缩率视比特率而定。
另外还有一种特殊的贞叫silenc frame,顾名思义就是静音,表示静音的时间长度。
3.2两者的贞的存储格式是不同的需要进行转换。要了解更多关于amr-if2文件格式请参阅:
http://www.3gpp.org/ftp/tsg_sa/WG4_CODEC/Specs_update_after_SA16/26101-500.zip
Figure A.1: Frame structure for AMR IF2
其中frametype的订阅如下 0-15,注意只占用了4个bit,就是为了节省4个bit给我们的转换工作带来了许多麻烦。类型中的***SID是指该贞为silence贞。
Table
1a: Interpretation of Frame Type,
Mode Indication and Mode Request fields
Frame Type | Mode Indication | Mode Request | Frame content (AMR mode, comfort noise, or other) |
0 | 0 | 0 | AMR 4,75 kbit/s |
1 | 1 | 1 | AMR 5,15 kbit/s |
2 | 2 | 2 | AMR 5,90 kbit/s |
3 | 3 | 3 | AMR 6,70 kbit/s (PDC-EFR) |
4 | 4 | 4 | AMR 7,40 kbit/s (TDMA-EFR) |
5 | 5 | 5 | AMR 7,95 kbit/s |
6 | 6 | 6 | AMR 10,2 kbit/s |
7 | 7 | 7 | AMR 12,2 kbit/s (GSM-EFR) |
8 | - | - | AMR SID |
9 | - | - | GSM-EFR SID |
10 | - | - | TDMA-EFR SID |
11 | - | - | PDC-EFR SID |
12-14 | - | - | For future use |
15 | - | - | No Data (No transmission/No reception) |
那么每个贞到底是多长呢,这与比特率有关。如下:
Table 2: Number of bits in Classes A, B, and C for each AMR codec mode
Frame Type | AMR | Total number of bits | Class A | Class B | Class C |
0 | 4,75 | 95 | 42 | 53 | 0 |
1 | 5,15 | 103 | 49 | 54 | 0 |
2 | 5,90 | 118 | 55 | 63 | 0 |
3 | 6,70 | 134 | 58 | 76 | 0 |
4 | 7,40 | 148 | 61 | 87 | 0 |
5 | 7,95 | 159 | 75 | 84 | 0 |
6 | 10,2 | 204 | 65 | 99 | 40 |
7 | 12,2 | 244 | 81 | 103 | 60 |
拿amr475来说
前4个bit放frame type 接下来的95个bit放音频数据,其中classA占42个bit,classB占53个bit。如下所示:
由上面两表可见,if2的贞与amr文件的贞相比多了一个4bit的类型,相应的字节也都左移了。
由于去掉了类型,amr文件存储有时会比if2小一些。
http://xa.bi/files/amrconvert中使用了perl的字符串和正则处理,先将二进制bit转换成串
再去掉type,最后转换成二进制。
我们也可以用位操作来完成这两种贞的转换。
我们可以这样转换两种文件:
If2->amr:
1取出第一个字节,判断贞的类型。
2根据贞类型生成文件头并写入文件。
3根据贞类型确定贞长度。
4如果有则读取一贞,转换成amr贞执行5,否则结束。
5将amr贞写文件 执行4。
Amr->if2:
1读取文件头,判断文件类型。
2根据文件类型确定贞长度。
3如果有则读取一贞,转换成if2贞执行5,否则结束。
4将amr贞写文件 执行4。
26104-500描述了从raw到if2的转换
你可以参考里面的26104-500_ANSI-C_source_code
如果使用特殊的wav文件,从wav到raw的转换是非常简单的,所谓的raw是特定wav
(单声道,8KHz,16Bit)。如果你使用的文件是单声道16bit,8KHz的文件,只需要去掉文件头就可以了。
否则必须用Sox,或者用sox相同的方法。这是一个开源的软件,有兴趣的话你可以参考一下。
http://sox.sourceforge.net/
二接着谈谈amr的录制和播放
如果是pc的话建议录制成wav文件然后转换成amr,手机可不能这么搞,文件太大了转换也麻烦,所以应该直接录制成amr。
Java手机的录制和播放:J2ME (MIDP 2.0, CLDC 1.0)
http://www.hcilab.org/documents/tutorials/AudioTest/
录制:
Player player;
...
player=Manager.createPlayer(“capture://audio?encoding=amr”);
player.realize();
RecordControl rc = (RecordControl)player.getControl("RecordControl");
ByteArrayOutputStream output = new ByteArrayOutputStream();
rc.setRecordStream(output);
rc.startRecord();
player.start();
Thread.currentThread().sleep(5000);
rc.commit();
播放:
ByteArrayInputStream recordedInputStream = new ByteArrayInputStream(recordedSoundArray);
Player p2 = Manager.createPlayer(recordedInputStream,"audio/x-wav");
p2.prefetch();
p2.start();
Symbian手机的录制和播放:
主要是CMdaAudioRecorderUtility的使用方法的问题。
播放:
TFileName tFullFileName = GetFullFileName(aBarFileName);
iCurrentFileName=tFullFileName;
RLog::Log(aBarFileName);
RLog::Log(tFullFileName);
#ifdef __WINS__
TRAPD(err,
iMdaAudioRecorderUtility->OpenFileL(
tFullFileName,
KMMFExControllerUID,
KMMFExControllerUID,
KMMFExDesFormatUID,
KMMFFourCCCodeAMR
));
#else
TRAPD(err,
iMdaAudioRecorderUtility->OpenFileL(
tFullFileName
));
#endif
if(err)
{
RLog::Log(_L("OpenFile err"),err);
User::Leave(err);
}
CMdaAudioRecorderUtility* iMdaAudioRecorderUtility;
iMdaAudioRecorderUtility= new ...
iMdaAudioRecorderUtility->SetAudioDeviceMode(CMdaAudioRecorderUtility::ELocal);
// Set maximum volume for playback
iMdaAudioRecorderUtility->SetVolume(iMdaAudioRecorderUtility->MaxVolume());
// Set the playback position to the start of the file
iMdaAudioRecorderUtility->SetPosition(TTimeIntervalMicroSeconds(0));
TRAPD(errp,iMdaAudioRecorderUtility->PlayL());
if(errp)
{
RLog::Log(_L("Play"),errp);
}
录制:
if(iMdaAudioRecorderUtility->State()==CMdaAudioClipUtility::ERecording
||iMdaAudioRecorderUtility->State()==CMdaAudioClipUtility::EPlaying)
return;
RLog::Log(_L("RecordL 1"));
if(iMdaAudioRecorderUtility->State()==CMdaAudioClipUtility::EOpen)
iMdaAudioRecorderUtility->Close();
//TPtrC aFileName(KRecorderFile);
TRAPD(errc,this->CleanAmrFileL(aBarFileName));
if(errc)
{
RLog::Log(_L("CleanAmrFileL:"),errc);
User::Leave(errc);
}
else{
RLog::Log(_L("CleanAmrFileL:OK"));
}
TRAPD(erro,this->OpenRecordFileL(aBarFileName));
if(erro)
{
RLog::Log(_L("OpenRecordFile:"),erro);
User::Leave(erro);
}
else{
RLog::Log(_L("OpenRfileOK"));
}
// Record from the device microphone
iMdaAudioRecorderUtility->SetAudioDeviceMode(CMdaAudioRecorderUtility::ELocal);
// Set maximum gain for recording
iMdaAudioRecorderUtility->SetGain(iMdaAudioRecorderUtility->MaxGain());
// Delete current audio sample from beginning of file
iMdaAudioRecorderUtility->SetPosition(TTimeIntervalMicroSeconds(0));
//iMdaAudioRecorderUtility->CropL();
TRAPD(err,iMdaAudioRecorderUtility->RecordL());
if(err){
RLog::Log(_L("RecordL Err"),err);
}
//iEngineStatus=ERECORDING;
也用CMdaAudioOutputStream可以进行流播放
我用的是syimbian的stream带的sdk
iAudioStreamPlayer = CMdaAudioOutputStream::NewL(*this);
iSettings.iVolume = iAudioStreamPlayer -> MaxVolume()/iVolume;
iSettings.iChannels = TMdaAudioDataSettings::EChannelsMono;
iSettings.iSampleRate =(aMMF4CCode==KMMFFourCCCodeMP3)? TMdaAudioDataSettings::ESampleRate8000Hz: TMdaAudioDataSettings::ESampleRate32000Hz;
#ifndef __WINS__
this->iAudioStreamPlayer->SetDataTypeL(aMMF4CCode);
this->iAudioStreamPlayer->SetPriority(EPriorityNormal, EMdaPriorityPreferenceNone);
#endif
//this->iAudioStreamPlayer->SetDataTypeL(KMMFFourCCCodePCM16);
iAudioStreamPlayer -> Open(&iSettings);
流的播放:
有时候你只能在内存里面播放amr而不是从文件里面打开。这需要你完成转码和流的播放两步工作。
在SDK 1.2中你只能用 CAmrToPcmDecoder 在 amrcodec.h 里面。
如果SDK是 2.0 或者更高的版本中使用 CMMFCodec
未完待续