§ 客户端设置音频编码格式speex且包中只一个帧
§ red5接收帧并添加帧长
§ 解码时依据帧长获取帧数据解码
1.flex客户端的设置
因为flex只支持Nellymoser(默认的)和speex,我们决定使用speex编码方式,解码后转成AAC编码格式,我们看一下flex提供的音频接口,引用一篇博客的内容:
一、flex中提供的音频接口
用flex编写客户端,它提供的接口是封装过的,与speex标准编解码器之间的调用实际上相当于一个黑盒,它们之间的差异需要我们分析。
麦克风音频的接口由类Mircophone提供,大多都有中文注释,我就不一一赘述了,只挑出其中一些做自己的讲解。
codec | 编码格式,只支持Nellymoser、speex两种格式, Nellymoser多用于游戏开发,而且商业使用限制较多 |
rate | 设置采样频率,注意是麦克风采样率,而非编码采样率 |
framesPerPacket | 一个音频包中包含的音频帧数量(后文会有更详细的说明) |
encodeQuality | 编码质量,在同等编码采样率下,质量越高,效果越好, 但每帧所包含的数据越多。当该值也确定下来时, 每帧数据的字节大小也就确定了 |
enableVAD | 是否开启Voice Activation Detection。它的作用自己google。 当开启时,静音状态下speex编码器将持续编码10字节大小的音频帧 |
speex编码有三种模式
模式 | 编码采样率 | 一帧数据 所表示的时间 | 编码一帧需要的 sample数量 |
narrow band(窄带) | 8khz | 20ms | 160 |
wide band(宽带) | 16khz | 20ms | 320 |
ultra-wide band(超宽带) | 32khz | 20ms | 640 |
二、RTMP中的speex数据
每个音频包(相当于FLV中的audiotag)中,第一个字节的前四位表示编码格式,等于11说明为speex编码。后4个字节分别表示编码采样率、单声道or立体声、每个sample大小8位or16位。但采用speex编码时,它们是固定不变的,协议中的为无效数据。编码采样率恒为16khz,单声道,16bit/sample。
剩余的数据为音频帧数据,可以为多帧的集合,取决于前文提到过的framesPerPacket。在flex中的默认值为2,故每个音频包中有两帧数据。注意,当前文所说的VAD功能开启时,两帧数据可以是两帧实际数据,也可以是两帧10字节数据,还可以各占一帧。
客户端麦克风属性framesPerPacket 表示一个音频包中包含的音频数量,
framesPerPacket = 1时, 一个音频包中只有一个帧
服务端接收两种长度数据
43=1+42 (1为audio data 的头)
11=1+10
其中43为客户端正常发声是数据,11为客户端不发生数据,
framesPerPacket = 2时,一个音频包中有两个帧
服务端接收三种长度数据
85 = 1+42+42
53= 1+42+10
21 = 1+10+10
最前面一个字节是头,后面跟的都是帧数据
为了简便起见,我们在客户端将framesPerPacket 设置为1,这样服务端收到长度为11和长度为43的帧数据,其中第一个字节是头,在这里我们收到的基本是B2一个字节
B2化成二进制是1011 0010,前四个字节是整数11,代表这个是speex的Audio data,我们看一下Audio data的解析:
我们在服务端收到的RTMP报中可以获得RTMP数据类型
如果TAG包中的TagType==8时,就表示这个TAG是audio。
StreamID之后的数据就表示是AudioTagHeader,AudioTagHeader结构如下:
Field | Type | Comment |
SoundFormat | UB [4] | Format of SoundData. The following values are defined: |
SoundRate | UB [2] | Sampling rate. The following values are defined: |
SoundSize | UB [1] | Size of each audio sample. This parameter only pertains to |
SoundType | UB [1] | Mono or stereo sound |
AACPacketType | IF SoundFormat == 10 | The following values are defined: |
AudioTagHeader的头1个字节,也就是接跟着StreamID的1个字节包含着音频类型、采样率等的基本信息.表里列的十分清楚.
有一点要注意的是,上面提到过的,当类型是11,也就是speex类型数据时,后面的4位数据不起作用,固定的是16KHZ,单声道,16bit/sample
这样我们可以分析数据了,第一位是头B2,后面紧跟的是帧数据,当客户端不发声的时候,我们就接受到11字节的数据,发声时接受的是43字节的数据
2.服务端red5对数据的处理
我们决定根据没真的长度来对speex数据解码,所以在服务端接收到数据时,得出数据的长度,用四个字节(或少点)来保存帧长度(这个长度是去掉头的长的,就是10或42),
然后将长度的四个字节保存下来,再保存帧数据这样我们保存下来的数据应该是
长度,帧数据,长度,帧数据。。。。
3.speex解码成pcm
我们使用speex库来解码,speex库依赖于ogg库,所以我们将这两个库编译后添加到工程里。
我使用了objective –c,解码后添加wav头,然后就可以播放了。
//按帧的字节数解码
NSData *decodeSpeexData(NSData *data){
if (data == nil){
NSLog(@"datais nil...");
return nil;
}
int nPos = 0;
char *buf = (char *)[databytes];
int maxLen = [data length];
nPos += sizeof(SpeexHeader);
if (nPos >= maxLen) {
return nil;
}
//这时取出来的是纯speex数据
buf += nPos;
//--------------------------------------
int frames = 0;
short pcmFrame[FRAME_SIZE];
spx_int16_t output[FRAME_SIZE];
int tmp = 1;
void *dec_state = speex_decoder_init(&speex_wb_mode);
speex_decoder_ctl(dec_state, SPEEX_SET_ENH,&tmp);
NSMutableData *PCMRawData = [[[NSMutableDataalloc]init]autorelease];
SpeexBits bits;
speex_bits_init(&bits);
for (; ; ) {
int nbBytes = 0;
if (data.length >4) {
Byte *len = (Byte *)[[datasubdataWithRange:NSMakeRange(0,4)]bytes];
Byte byte = len[3];
nbBytes = (int)byte;
NSData *subdata =[data subdataWithRange:NSMakeRange(4,nbBytes)];
buf = (char *)[subdata bytes];
data = [data subdataWithRange:NSMakeRange(nbBytes+4,data.length-nbBytes-4)];
}else{
break;
}
speex_bits_read_from(&bits,buf, nbBytes);
speex_decode_int(dec_state,&bits, output);
for (int i =0; i <FRAME_SIZE; i++) {
pcmFrame[i] = output[i];
}
[PCMRawData appendBytes:pcmFrame length:sizeof(short)*FRAME_SIZE];
// buf += nbBytes;
frames++;
}
speex_bits_destroy(&bits);
speex_decoder_destroy(dec_state);
NSMutableData *outData = [[[NSMutableDataalloc]init]autorelease];
WriteWAVEHeader(outData,frames);
[outData appendData:PCMRawData];
NSString *speexfilepath = [[NSHomeDirectory()stringByAppendingPathComponent:@"Documents"]stringByAppendingPathComponent:[NSStringstringWithFormat:@"%.0f.%@", [NSDatetimeIntervalSinceReferenceDate] *1000.0,@"pcm"]];
[PCMRawData writeToFile:speexfilepathatomically:YES];
return outData;
}
void WriteWAVEHeader(NSMutableData*fpwave,int nFrame)
{
char tag[10] ="";
//1. 写RIFF头
RIFFHEADER riff;
strcpy(tag, "RIFF");
memcpy(riff.chRiffID, tag,4);
riff.nRiffSize = 4 // WAVE
+ sizeof(XCHUNKHEADER) // fmt
+ sizeof(WAVEFORMATX) //WAVEFORMATX
+ sizeof(XCHUNKHEADER) // DATA
+ nFrame*320*sizeof(short); //
strcpy(tag, "WAVE");
memcpy(riff.chRiffFormat, tag,4);
//fwrite(&riff,1, sizeof(RIFFHEADER), fpwave);
[fpwave appendBytes:&riff length:sizeof(RIFFHEADER)];
// 2. 写FMT块
XCHUNKHEADER chunk;
WAVEFORMATX wfx;
strcpy(tag, "fmt");
memcpy(chunk.chChunkID, tag,4);
unsigned short m_pcmData;
chunk.nChunkSize = sizeof(WAVEFORMATX);
//fwrite(&chunk,1, sizeof(XCHUNKHEADER), fpwave);
[fpwave appendBytes:&chunk length:sizeof(XCHUNKHEADER)];
memset(&wfx, 0,sizeof(WAVEFORMATX));
wfx.nFormatTag = 1;
wfx.nChannels = 1; // 单声道
wfx.nSamplesPerSec = 16000; // 8khz
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec*sizeof(m_pcmData);
wfx.nBlockAlign = 2;
wfx.nBitsPerSample = 16; // 16位
//fwrite(&wfx, 1, sizeof(WAVEFORMATX), fpwave);
[fpwave appendBytes:&wfx length:sizeof(WAVEFORMATX)];
// 3. 写data块头
strcpy(tag, "data");
memcpy(chunk.chChunkID, tag,4);
chunk.nChunkSize = nFrame*320*sizeof(short);
//fwrite(&chunk,1, sizeof(XCHUNKHEADER), fpwave);
[fpwave appendBytes:&chunk length:sizeof(XCHUNKHEADER)];
}
头文件内容如下
#defineFRAME_SIZE 320// speex音频16khz*20ms-> 16000*0.02=320
#define MAX_NB_BYTES 200
#define SPEEX_SAMPLE_RATE 16000
typedef struct
{
char chChunkID[4];
int nChunkSize;
}XCHUNKHEADER;
typedef struct
{
short nFormatTag;
short nChannels;
intnSamplesPerSec;
intnAvgBytesPerSec;
shortnBlockAlign;
shortnBitsPerSample;
}WAVEFORMAT;
typedef struct
{
short nFormatTag;
short nChannels;
int nSamplesPerSec;
intnAvgBytesPerSec;
shortnBlockAlign;
shortnBitsPerSample;
short nExSize;
}WAVEFORMATX;
typedef struct
{
char chRiffID[4];
int nRiffSize;
charchRiffFormat[4];
}RIFFHEADER;
typedef struct
{
char chFmtID[4];
int nFmtSize;
WAVEFORMAT wf;
}FMTBLOCK;
@interface SpeexCodec: NSObject
int EncodeWAVEFileToSpeexFile(constchar*pchWAVEFilename,constchar*pchAMRFileName,int nChannels,intnBitsPerSample);
int DecodeSpeexFileToWAVEFile(constchar*pchAMRFileName,constchar* pchWAVEFilename);
NSData* DecodeSpeexToWAVE(NSData* data);
NSData* EncodeWAVEToSpeex(NSData* data,int nChannels,intnBitsPerSample);
NSData* addHeaderSpeexData(NSData *data);
// 根据帧头计算当前帧大小
float CalculatePlayTime(NSData *speexData,int frame_size);
void encodeToSpeexStream();
void decodeSpeexStream();
NSData *decodeSpeexData(NSData *data);
@end
备注: 一些相关文档的链接
http://www.cnblogs.com/chef/archive/2012/08/17/2643464.html