Flex客户端设置speex编码时Red5对音频数据的处理以及将speex解码

§ 客户端设置音频编码格式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时,就表示这个TAGaudio

StreamID之后的数据就表示是AudioTagHeaderAudioTagHeader结构如下:

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个字节,也就是接跟着StreamID1个字节包含着音频类型、采样率等的基本信息.表里列的十分清楚.

 

 

有一点要注意的是,上面提到过的,当类型是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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值