Flex客户端设置speex编码时Red5对音频数据的处理以及将speex解码
2013-01-05 17:27
831 查看
§ 客户端设置音频编码格式speex且包中只一个帧
§ red5接收帧并添加帧长
§ 解码时依据帧长获取帧数据解码
1.flex客户端的设置
因为flex只支持Nellymoser(默认的)和speex,我们决定使用speex编码方式,解码后转成AAC编码格式,我们看一下flex提供的音频接口,引用一篇博客的内容:
一、flex中提供的音频接口
用flex编写客户端,它提供的接口是封装过的,与speex标准编解码器之间的调用实际上相当于一个黑盒,它们之间的差异需要我们分析。
麦克风音频的接口由类Mircophone提供,大多都有中文注释,我就不一一赘述了,只挑出其中一些做自己的讲解。
speex编码有三种模式
二、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结构如下:
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://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Microphone.html#enableVAD
http://www.cnblogs.com/chef/archive/2012/08/17/2643464.html
http://blog.csdn.net/cssmhyl/article/details/8059420
§ 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: 0 = Linear PCM, platform endian 1 = ADPCM 2 = MP3 3 = Linear PCM, little endian 4 = Nellymoser 16 kHz mono 5 = Nellymoser 8 kHz mono 6 = Nellymoser 7 = G.711 A-law logarithmic PCM 8 = G.711 mu-law logarithmic PCM 9 = reserved 10 = AAC 11 = Speex 14 = MP3 8 kHz 15 = Device-specific sound Formats 7, 8, 14, and 15 are reserved. AAC is supported in Flash Player 9,0,115,0 and higher. Speex is supported in Flash Player 10 and higher. |
SoundRate | UB [2] | Sampling rate. The following values are defined: 0 = 5.5 kHz 1 = 11 kHz 2 = 22 kHz 3 = 44 kHz |
SoundSize | UB [1] | Size of each audio sample. This parameter only pertains to uncompressed formats. Compressed formats always decode to 16 bits internally. 0 = 8-bit samples 1 = 16-bit samples |
SoundType | UB [1] | Mono or stereo sound 0 = Mono sound 1 = Stereo sound |
AACPacketType | IF SoundFormat == 10 UI8 | The following values are defined: 0 = AAC sequence header 1 = AAC raw |
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://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Microphone.html#enableVAD
http://www.cnblogs.com/chef/archive/2012/08/17/2643464.html
http://blog.csdn.net/cssmhyl/article/details/8059420
相关文章推荐
- 【flex、Red5】Red5服务器+flex客户端中xml数据的压缩以及传输
- 一步一步教你如何做iOS推送客户端以及推送数据处理
- WebRTC音视频引擎研究(2)--VoiceEngine音频编解码器数据结构以及参数设置
- WebRTC音视频引擎研究(2)--VoiceEngine音频编解码器数据结构以及参数设置
- CASE函数 sql server——分组查询(方法和思想) ref和out 一般处理程序结合反射技术统一执行客户端请求 遍历查询结果集,update数据 HBuilder设置APP状态栏
- WebRTC音视频引擎研究(2)--VoiceEngine音频编解码器数据结构以及参数设置
- 使用 ffmpeg 进行网络推流:拉流->解封装->解码->处理原始数据(音频、视频)->编码->编码->推流
- hive的数据导入与数据导出:(本地,云hdfs,hbase),列分隔符的设置,以及hdfs上传给pig如何处理
- 在.NET中使用Speex -- 音频数据编解码
- hive的数据导入与数据导出:(本地,云hdfs,hbase),列分隔符的设置,以及hdfs上传给pig如何处理
- WebRTC音视频引擎研究(2)--VoiceEngine音频编解码器数据结构以及参数设置
- WebRTC音视频引擎研究(2)--VoiceEngine音频编解码器数据结构以及参数设置
- Miko Android自学之路 WifiDirect中文最强详解,如何传输数据,如何设置GroupOwener,如何设置客户端以及服务器端
- QT中用udp客户端发送消息,丢失数据的情况以及处理!
- WebRTC音视频引擎研究(2)--VoiceEngine音频编解码器数据结构以及参数设置
- Miko Android自学之路 WifiDirect中文最强详解,如何传输数据,如何设置GroupOwener,如何设置客户端以及服务器端
- Miko Android自学之路 WifiDirect中文最强详解,如何传输数据,如何设置GroupOwener,如何设置客户端以及服务器端
- WebRTC音视频引擎研究(2)--VoiceEngine音频编解码器数据结构以及参数设置 .
- WebRTC音视频引擎研究--VoiceEngine音频编解码器数据结构以及参数设置
- 在.NET中使用Speex -- 音频数据编解码