libRTMP 库从 RTMP 直播服务器不断地获取 RTMP 包,从RTMP包中获取音频和视频数据。RTMP只是一个应用层协议,传输的数据格式都是基于FLV格式的。

我们在推送音、视频包之前,会首先向服务器推送一个音、视频同步包,该包包含了 AAC 音频帧以及 H264 码流的解码信息。因此当我们首次向服务器请求 RTMP 包之后,服务器会下发给我们对应的音、视频同步包,这将决定我们如何解析并重组音、视频。

一、首先要了解FLV格式。

flv文件主要由两部分组成:header和body。

1.header

header部分记录了flv的类型、版本等信息,是flv的开头,一般都差不多,占9bytes。具体格式如下:

文件类型

3 bytes

“FLV”

版本

1 byte

一般为0x01

流信息

1 byte

倒数第一位是1表示有视频,倒数第三位是1表示有音频,倒数第二、四位必须为0

header长度

4 bytes

整个header的长度,一般为9;大于9表示下面还有扩展信息

2.body

body部分由一个个Tag组成,每个Tag的下面有一块4bytes的空间,用来记录这个tag的长度,这个后置用于逆向读取处理,他们的关系如下图:

2.1.Tag

每个Tag由也是由两部分组成的:Tag Header和Tag Data。Tag Header里存放的是当前Tag的类型、数据区(Tag Data)长度等信息,具体如下:

名称

长度

介绍

Tag类型

1 bytes

8:音频
9:视频
18:脚本
其他:保留

数据区长度

3 bytes

在数据区的长度

时间戳

3 bytes

整数,单位是毫秒。对于脚本型的tag总是0

时间戳扩展

1 bytes

将时间戳扩展为4bytes,代表高8位。很少用到

StreamsID

3 bytes

总是0

数据区(data)

由数据区长度决定

数据实体

2.2.Tag Data

数据区根据Tag类型的不同可分为三种,音频数据、视频数据和脚本数据。

2.2.1.音频数据

音频数据包括音频同步包和音频raw数据。

音频同步包格式

名称

二进制值

Hex

介绍

音频格式

4 bits

[AAC]1010

0xA

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

采样率

2 bits

[44kHZ]11

0 = 5.5-kHz
1 = 11-kHz
2 = 22-kHz
3 = 44-kHz
对于AAC总是3

采样的长度

1 bit

0 = snd8Bit
1 = snd16Bit
压缩过的音频都是16bit

音频类型

1 bit

0 = sndMono,单声道
1 = sndStereo,立体声
对于AAC总是1

ACCPacketType

8bit

00000000

只有SoundFormat == 10时才有此8bi的字段

0x00,表示音频同步包;0x01,表示音频raw数据。

AudioObjectType

5bits

[AAC LC]00010

SampleRateIndex

4bits

[44100]0100

ChannelConfig

4bits     [Stereo]0010

FrameLengthFlag

1bit

0

dependOnCoreCoder

1bit

0

extensionFlag

1bit

0

共4个字节(2bytes AACDecoderSpecificInfo和2bytesAudioSpecificConfig)

注:speex数据

每个音频包(相当于FLV中的audiotag)中,第一个字节的前四位表示编码格式,等于11说明为speex编码。后4个字节分别表示编码采样率、单声道or立体声、每个sample大小8位or16位。但采用speex编码时,它们是固定不变的,协议中的为无效数据。编码采样率恒为16khz,单声道,16bit/sample。 剩余的数据为音频帧数据。

AACsequence header存放的是AudioSpecificConfig结构,该结构则在“ISO-14496-3Audio”中描述。AudioSpecificConfig结构的描述非常复杂,这里我做一下简化,事先设定要将要编码的音频格式,其中,选择"AAC-LC"为音频编码,音频采样率为44100,于是AudioSpecificConfig简化为下表:

音频raw数据格式

名称

二进制值

Hex

介绍

音频格式

4 bits

[AAC]1010

0xA

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

采样率

2 bits

[44kHZ]11

0 = 5.5-kHz
1 = 11-kHz
2 = 22-kHz
3 = 44-kHz
对于AAC总是3

采样的长度

1 bit

0 = snd8Bit
1 = snd16Bit
压缩过的音频都是16bit

音频类型

1 bit

0 = sndMono,单声道
1 = sndStereo,立体声
对于AAC总是1

ACCPacketType

8bit

只有SoundFormat == 10时才有此8bi的字段

0x00,表示音频同步包;0x01,表示音频raw数据。

Raw data

音频raw数据

AudioSpecificConfig部分参数含义见:

https://wiki.multimedia.cx/index.php?title=MPEG-4_Audio

2.2.2.视频数据

视频同步包

Field

Type

Comment

Frame Type

UB [4]

Type of video frame. The following values are defined:
1 = key frame (for AVC, a seekable frame)
2 = inter frame (for AVC, a non-seekable frame)
3 = disposable inter frame (H.263 only)
4 = generated key frame (reserved for server use only)
5 = video info/command frame

CodecID

UB [4]

Codec Identifier. The following values are defined:
2 = Sorenson H.263
3 = Screen video
4 = On2 VP6
5 = On2 VP6 with alpha channel
6 = Screen video version 2
7 = AVC

AVCPacketType

IF CodecID == 7
UI8

The following values are defined:
0 = AVC sequence header
1 = AVC NALU
2 = AVC end of sequence (lower level NALU sequence ender is not required or supported)

CompositionTime

IF CodecID == 7
SI24

IF AVCPacketType == 1
Composition time offset
ELSE
0
See ISO 14496-12, 8.15.3 for an explanation of composition
times. The offset in an FLV file is always in milliseconds.

VideoTagHeader的头1个字节,也就是接跟着StreamID的1个字节包含着视频帧类型及视频CodecID最基本信息.表里列的十分清楚.

VideoTagHeader之后跟着的就是VIDEODATA数据了,也就是video payload.当然就像音频AAC一样,这里也有特例就是如果视频的格式是AVC(H.264)的话,VideoTagHeader会多出4个字节的信息.

AVCPacketType和 CompositionTime。AVCPacketType表示接下来 VIDEODATA(AVCVIDEOPACKET)的内容:

IF AVCPacketType == 0 AVCDecoderConfigurationRecord(AVC sequence header)
IF AVCPacketType == 1 One or more NALUs (Full frames are required)

AVCDecoderConfigurationRecord.包含着是H.264解码相关比较重要的sps和pps信息,再给AVC解码器送数据流之前一定要把sps和pps信息送出,否则的话解码器不能正常解码。而且在解码器stop之后再次start之前,如seek、快进快退状态切换等,都需要重新送一遍sps和pps的信息.AVCDecoderConfigurationRecord在FLV文件中一般情况也是出现1次,也就是第一个video tag.

AVC sequence header就是AVCDecoderConfigurationRecord结构,该结构在标准文档“ISO-14496-15AVC file format”中有详细说明。

2.2.3脚本数据

脚本Tag一般只有一个,是flv的第一个Tag,用于存放flv的信息,比如duration、audiodatarate、creator、width等。

首先介绍下脚本的数据类型。所有数据都是以数据类型+(数据长度)+数据的格式出现的,数据类型占1byte,数据长度看数据类型是否存在,后面才是数据。
其中数据类型的种类有:

  • 0 = Number type
  • 1 = Boolean type
  • 2 = String type
  • 3 = Object type
  • 4 = MovieClip type
  • 5 = Null type
  • 6 = Undefined type
  • 7 = Reference type
  • 8 = ECMA array type
  • 10 = Strict array type
  • 11 = Date type
  • 12 = Long string type

如果类型为String,后面的2bytes为字符串的长度(LongString是4bytes),再后面才是字符串数据;如果是Number类型,后面的8bytes为Double类型的数据;Boolean类型,后面1byte为Bool类型。

知道了这些后再来看看flv中的脚本,一般开头是0x02,表示String类型,后面的2bytes为字符串长度,一般是0x000a(“onMetaData”的长度),再后面就是字符串“onMetaData”。好像flv格式的文件都有onMetaData标记,在运行ActionScript的时候会用到它。后面跟的是0x08,表示ECMA Array类型,这个和Map比较相似,一个键跟着一个值。键都是String类型的,所以开头的0x02被省略了,直接跟着的是字符串的长度,然后是字符串,再是值的类型,也就是上面介绍的那些了。

第一个AMF包:第1个字节表示AMF包类型。

第二个AMF包:

第1个字节表示AMF包类型,一般总是0x08,表示数组。第2-5个字节为UI32类型值,表示数组元素的个数。后面即为各数组元素的封装,数组元素为元素名称和值组成的对。常见的数组元素如下表:

一、 通过RTMP_ReadPacket获取一包数据,在里面获取视频、音频、scripttag等。

视频H264获取过程:
1、获取第一视频同步包之后,解析到sps、pps数据。视频头增加H264边界分割符(00 00 00 01 或000001分隔符)。
2、其他视频包,解析出nalu数据。视频头增加H264边界分割符。

音频AAC获取过程:
1、获取第一音频同步包之后,解析到AACDecoderSpecificInfo和AudioSpecificConfig。AudioSpecificConfig用于生成ADST。
2、其他音频包,解析出原始音频数据(即es)。
一般的AAC解码器都需要把AAC的ES流打包成ADTS的格式,一般是在AAC ES流前添加7个字节的ADTSheader。也就是说你可以吧ADTS这个头看作是AAC的frameheader。

ADTS AAC

ADTS_header

AAC ES

ADTS_header

AAC ES

...

ADTS_header

AAC ES

ADTS内容及结构

ADTS 头中相对有用的信息 采样率、声道数、帧长度。想想也是,我要是解码器的话,你给我一堆得AAC音频ES流我也解不出来。每一个带ADTS头信息的AAC流会清晰的告送解码器他需要的这些信息。

一般情况下ADTS的头信息都是7个字节,分为2部分:

adts_fixed_header();

adts_variable_header();

syncword :同步头总是0xFFF,all bits must be 1,代表着一个ADTS帧的开始

ID:MPEGVersion: 0 for MPEG-4, 1 for MPEG-2

Layer:always: '00'

profile:表示使用哪个级别的AAC,有些芯片只支持AAC LC 。在MPEG-2AAC中定义了3种:

sampling_frequency_index:表示使用的采样率下标,通过这个下标在 Sampling Frequencies[ ]数组中查找得知采样率的值。

Thereare 13 supported frequencies:

  • 0: 96000 Hz
  • 1: 88200 Hz
  • 2: 64000 Hz
  • 3: 48000 Hz
  • 4: 44100 Hz
  • 5: 32000 Hz
  • 6: 24000 Hz
  • 7: 22050 Hz
  • 8: 16000 Hz
  • 9: 12000 Hz
  • 10: 11025 Hz
  • 11: 8000 Hz
  • 12: 7350 Hz
  • 13: Reserved
  • 14: Reserved
  • 15: frequency is written explictly

channel_configuration: 表示声道数

  • 0: Defined in AOT Specifc Config
  • 1: 1 channel: front-center
  • 2: 2 channels: front-left, front-right
  • 3: 3 channels: front-center, front-left, front-right
  • 4: 4 channels: front-center, front-left, front-right, back-center
  • 5: 5 channels: front-center, front-left, front-right, back-left, back-right
  • 6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel
  • 7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel
  • 8-15: Reserved

frame_length :一个ADTS帧的长度包括ADTS头和AAC原始流。

adts_buffer_fullness:0x7FF说明是码率可变的码流。

代码

/*** 本程序使用RTMP_ReadPacket与RTMP_ClientPacket方式读取流数据,接收RTMP流媒体并存储视频h264和音频aac文件。
*/
#include <stdio.h>
#include "librtmp/rtmp_sys.h"
#include "librtmp/log.h"//ACC音频ADTS
typedef struct tagAACDecoderSpecific
{unsigned char nAudioFortmatType;   //音频编码类型(0:Liner PCM platform endian,1:PCM,2:mp3,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,14:MP3 8-Khz,15:Device-specific sound)unsigned char nAudioSampleType; //音频采样率(0:5.5kHz,1:11KHz,2:22 kHz,3:44 kHz)unsigned char nAudioSizeType;    //音频采样精度(0:8bits,1:16bits)unsigned char nAudioStereo;//是否立体声(0:sndMono,1:sndStereo)unsigned char nAccPacketType;
}AACDecoderSpecific;typedef struct tagAudioSpecificConfig
{unsigned char nAudioObjectType;unsigned char nSampleFrequencyIndex;unsigned char nChannels;unsigned char nFrameLengthFlag;unsigned char nDependOnCoreCoder;unsigned char nExtensionFlag;
}AudioSpecificConfig;int InitSockets()
{
#ifdef WIN32WORD version;WSADATA wsaData;version = MAKEWORD(1, 1);return (WSAStartup(version, &wsaData) == 0);
#endif
}void CleanupSockets()
{
#ifdef WIN32WSACleanup();
#endif
}//解析ScriptTag
void ParseScriptTag(char *pBuffer, int nBufferLength, int &nVideoCodecId, int &nVideoWidth, int &nVideoHeight, int &nFrameRate, int &nAudioCodecId, int &nAudioSampleRate, int &nAudioSampleSize, bool &bStereo, int &nFileSize)
{AMFObject obj;AVal val;AMFObjectProperty * property;AMFObject subObject;int nRes = AMF_Decode(&obj, pBuffer, nBufferLength, FALSE);if (nRes < 0){//RTMP_Log(RTMP_LOGERROR, "%s, error decoding invoke packet", __FUNCTION__);return;}AMF_Dump(&obj);for (int n = 0; n < obj.o_num; n++){property = AMF_GetProp(&obj, NULL, n);if (property != NULL){if (property->p_type == AMF_OBJECT){AMFProp_GetObject(property, &subObject);for (int m = 0; m < subObject.o_num; m++){property = AMF_GetProp(&subObject, NULL, m);if (property != NULL){if (property->p_type == AMF_OBJECT){}else if (property->p_type == AMF_BOOLEAN){int bVal = AMFProp_GetBoolean(property);if (strncmp("stereo", property->p_name.av_val, property->p_name.av_len) == 0){bStereo = bVal > 0 ? true : false;}}else if (property->p_type == AMF_NUMBER){double dVal = AMFProp_GetNumber(property);if (strncmp("width", property->p_name.av_val, property->p_name.av_len) == 0){nVideoWidth = (int)dVal;}else if (stricmp("height", property->p_name.av_val) == 0){nVideoHeight = (int)dVal;}else if (stricmp("framerate", property->p_name.av_val) == 0){nFrameRate = (int)dVal;}else if (stricmp("videocodecid", property->p_name.av_val) == 0){nVideoCodecId = (int)dVal;}else if (stricmp("audiosamplerate", property->p_name.av_val) == 0){nAudioSampleRate = (int)dVal;}else if (stricmp("audiosamplesize", property->p_name.av_val) == 0){nAudioSampleSize = (int)dVal;}else if (stricmp("audiocodecid", property->p_name.av_val) == 0){nAudioCodecId = (int)dVal;}else if (stricmp("filesize", property->p_name.av_val) == 0){nFileSize = (int)dVal;}}else if (property->p_type == AMF_STRING){AMFProp_GetString(property, &val);}}}}else{AMFProp_GetString(property, &val);}}}
}//生成ADTS
int CreateADTS(AudioSpecificConfig ascAudioSpecificConfig, unsigned short nAudioFrameLength, char *pADTS)
{if (pADTS == NULL)return -1;unsigned char adts[7] = { 0 };int chanCfg = ascAudioSpecificConfig.nChannels;// fill in ADTS dataadts[0] = (byte)0xFF;adts[1] = (byte)0xF9;adts[2] = (byte)(((ascAudioSpecificConfig.nAudioObjectType - 1) << 6) + (ascAudioSpecificConfig.nSampleFrequencyIndex << 2) + (chanCfg >> 2));adts[3] = (byte)(((chanCfg & 3) << 6) + (nAudioFrameLength >> 11));adts[4] = (byte)((nAudioFrameLength & 0x7FF) >> 3);adts[5] = (byte)(((nAudioFrameLength & 7) << 5) + 0x1F);adts[6] = (byte)0xFC;/*int i = 0;adts[i++] = 0xFF; //AAAAAAAAadts[i++] = 0xF9;  //AAAABCCD//calc EEFFFFGH 8 bitsadts[i] = ((ascAudioSpecificConfig.nAudioObjectType - 1) << 6) & 0xC0;adts[i++] = (ascAudioSpecificConfig.nSampleFrequencyIndex << 2) & 0x3C;adts[i] = (ascAudioSpecificConfig.nChannels << 6) & 0xFF;adts[i++] &= 0xC0;unsigned short frameLength = nAudioFrameLength;          //一个ADTS帧的长度包括ADTS头和AAC原始流adts[i++] = ((frameLength << 5) & 0xFF00) >> 8;adts[i] = (frameLength & 0x07) << 5;adts[i++] = 0x1F;adts[6] = 0xFC;*/if (pADTS != NULL){memcpy(pADTS, adts, sizeof(adts));}return 0;
}int main(int argc, char* argv[])
{InitSockets();double duration=-1;int nRead;//is live stream ?bool bLiveStream=true;              int bufsize=1024*1024*10;          char *buf=(char*)malloc(bufsize);memset(buf,0,bufsize);long countbufsize=0;//点播if (0 == 1){FILE *fp = fopen("receive.flv", "wb");if (!fp){RTMP_LogPrintf("Open File Error.\n");CleanupSockets();return -1;}/* set log level *///RTMP_LogLevel loglvl=RTMP_LOGDEBUG;//RTMP_LogSetLevel(loglvl);RTMP *rtmp = RTMP_Alloc();RTMP_Init(rtmp);//set connection timeout,default 30srtmp->Link.timeout = 30;// HKS's live URLif(!RTMP_SetupURL(rtmp,"rtmp://live.hkstv.hk.lxdns.com/live/hks"))//if(!RTMP_SetupURL(rtmp,"rtmp://localhost/live/hks"))//if (!RTMP_SetupURL(rtmp, "rtmp://localhost:1935/flvplayback/flv:1.flv"))//rtmp://localhost:1935/flvplayback/mp4:2.mp4//if (!RTMP_SetupURL(rtmp, "rtmp://localhost:1935/flvplayback/mp4:2.mp4")){RTMP_Log(RTMP_LOGERROR, "SetupURL Err\n");RTMP_Free(rtmp);CleanupSockets();return -1;}//1hourRTMP_SetBufferMS(rtmp, 3600 * 1000);if (!RTMP_Connect(rtmp, NULL)){RTMP_Log(RTMP_LOGERROR, "Connect Err\n");RTMP_Free(rtmp);CleanupSockets();return -1;}if (!RTMP_ConnectStream(rtmp, 0)){RTMP_Log(RTMP_LOGERROR, "ConnectStream Err\n");RTMP_Close(rtmp);RTMP_Free(rtmp);CleanupSockets();return -1;}//它直接输出的就是FLV文件,包括FLV头,可对流按照flv格式解析就可提取音频,视频数据while(nRead=RTMP_Read(rtmp,buf,bufsize)){fwrite(buf,1,nRead,fp);countbufsize+=nRead;RTMP_LogPrintf("Receive: %5dByte, Total: %5.2fkB\n",nRead,countbufsize*1.0/1024);}if (fp)fclose(fp);if (buf){free(buf);}if (rtmp){RTMP_Close(rtmp);RTMP_Free(rtmp);CleanupSockets();rtmp = NULL;}return 0;}//直播if (0 == 0){FILE *fp = fopen("receive.h264", "wb");if (!fp){RTMP_LogPrintf("Open File Error.\n");CleanupSockets();return -1;}FILE *fpAAC = fopen("receive.m4a", "wb");if(!fpAAC){RTMP_LogPrintf("Open File Error.\n");CleanupSockets();return -1;}/* set log level *///RTMP_LogLevel loglvl=RTMP_LOGDEBUG;//RTMP_LogSetLevel(loglvl);RTMP *rtmp = RTMP_Alloc();RTMP_Init(rtmp);//set connection timeout,default 30srtmp->Link.timeout = 30;// HKS's live URLif(!RTMP_SetupURL(rtmp,"rtmp://live.hkstv.hk.lxdns.com/live/hks"))//if(!RTMP_SetupURL(rtmp,"rtmp://localhost/live/hks"))//if (!RTMP_SetupURL(rtmp, "rtmp://localhost:1935/flvplayback/mp4:2.mp4"))//rtmp://localhost:1935/flvplayback/mp4:2.mp4{RTMP_Log(RTMP_LOGERROR, "SetupURL Err\n");RTMP_Free(rtmp);CleanupSockets();return -1;}if (bLiveStream){rtmp->Link.lFlags |= RTMP_LF_LIVE;}//1hourRTMP_SetBufferMS(rtmp, 3600 * 1000);if (!RTMP_Connect(rtmp, NULL)){RTMP_Log(RTMP_LOGERROR, "Connect Err\n");RTMP_Free(rtmp);CleanupSockets();return -1;}if (!RTMP_ConnectStream(rtmp, 0)){RTMP_Log(RTMP_LOGERROR, "ConnectStream Err\n");RTMP_Close(rtmp);RTMP_Free(rtmp);CleanupSockets();return -1;}RTMPPacket pc = { 0 };// ps = { 0 };bool bVideoFirst = true;bool bAudioFirst = true;unsigned int nFrameType = 0;unsigned char result = 0;char *data = NULL;//FLV文件头static const char flvHeader[] = { 'F', 'L', 'V', 0x01,0x00,             /* 0x04代表有音频, 0x01代表有视频 */0x00, 0x00, 0x00, 0x09,0x00, 0x00, 0x00, 0x00};//fwrite(flvHeader, sizeof(flvHeader), 1, fp);int jjj = 0;AudioSpecificConfig ascAudioSpecificConfig = { 0 };while (RTMP_ReadPacket(rtmp, &pc)){if (RTMPPacket_IsReady(&pc)) //是否读取完毕。((a)->m_nBytesRead == (a)->m_nBodySize)  {if (!pc.m_nBodySize)continue;if (pc.m_packetType == RTMP_PACKET_TYPE_VIDEO && RTMP_ClientPacket(rtmp, &pc)){jjj++;if (jjj > 1000)break;result = pc.m_body[0];data = pc.m_body;bool bIsKeyFrame = false;if (result == 0x17)//I frame{bIsKeyFrame = true;}else if (result == 0x27){bIsKeyFrame = false;}static unsigned char const start_code[4] = { 0x00, 0x00, 0x00, 0x01 };//fwrite(start_code, 1, 4, fp);//int ret = fwrite(pc.m_body + 9, 1, pc.m_nBodySize-9, pf);if (bVideoFirst){fwrite(start_code, 1, 4, fp);//AVCsequence header//ioBuffer.put(foredata);//Access to SPSint spsnum = data[10] & 0x1f;int number_sps = 11;int count_sps = 1;while (count_sps <= spsnum){int spslen = (data[number_sps] & 0x000000FF) << 8 | (data[number_sps + 1] & 0x000000FF);number_sps += 2;fwrite(data + number_sps, 1, spslen, fp);fwrite(start_code, 1, 4, fp);number_sps += spslen;count_sps++;}//Get PPSint ppsnum = data[number_sps] & 0x1f;int number_pps = number_sps + 1;int count_pps = 1;while (count_pps <= ppsnum){int ppslen = (data[number_pps] & 0x000000FF) << 8 | data[number_pps + 1] & 0x000000FF;number_pps += 2;fwrite(data + number_pps, 1, ppslen, fp);fwrite(start_code, 1, 4, fp);number_pps += ppslen;count_pps++;}bVideoFirst = false;}else{//AVCNALUint len = 0;int num = 5;while (num<pc.m_nBodySize){len = (data[num] & 0x000000FF) << 24 | (data[num + 1] & 0x000000FF) << 16 | (data[num + 2] & 0x000000FF) << 8 | data[num + 3] & 0x000000FF;num = num + 4;fwrite(data + num, 1, len, fp);fwrite(start_code, 1, 4, fp);num = num + len;}}}else if (pc.m_packetType == RTMP_PACKET_TYPE_AUDIO && RTMP_ClientPacket(rtmp, &pc)){data = pc.m_body;if (bAudioFirst){//ACC音频同步包(1bytes+3bytes AccAudioData) (4bytes 包含了AACDecoderSpecific和AudioSpecificConfig)AACDecoderSpecific adsAACDecoderSpecific = { 0 };adsAACDecoderSpecific.nAudioFortmatType = (data[0] & 0xf0) >> 4; //音频编码类型(0:Liner PCM platform endian,1:PCM,2:mp3,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,14:MP3 8-Khz,15:Device-specific sound)adsAACDecoderSpecific.nAudioSampleType = (data[0] & 0x0c) >> 2;  //音频采样率(0:5.5kHz,1:11KHz,2:22 kHz,3:44 kHz)adsAACDecoderSpecific.nAudioSizeType = (data[0] & 0x02) >> 1; //音频采样精度(0:8bits,1:16bits)adsAACDecoderSpecific.nAudioStereo = data[0] & 0x01;//是否立体声(0:sndMono,1:sndStereo)if(adsAACDecoderSpecific.nAudioFortmatType == 10){//The following values are defined://0 = AAC sequence header//1 = AAC rawadsAACDecoderSpecific.nAccPacketType = data[1];unsigned short audioSpecificConfig = 0;audioSpecificConfig = (data[2] & 0xff) << 8;audioSpecificConfig += 0x00ff & data[3];ascAudioSpecificConfig.nAudioObjectType = (audioSpecificConfig & 0xF800) >> 11;ascAudioSpecificConfig.nSampleFrequencyIndex = (audioSpecificConfig & 0x0780) >> 7;ascAudioSpecificConfig.nChannels = (audioSpecificConfig & 0x78) >> 3;ascAudioSpecificConfig.nFrameLengthFlag = (audioSpecificConfig & 0x04) >> 2;ascAudioSpecificConfig.nDependOnCoreCoder = (audioSpecificConfig & 0x02) >> 1;ascAudioSpecificConfig.nExtensionFlag = audioSpecificConfig & 0x01;}else if(adsAACDecoderSpecific.nAudioFortmatType == 11){//speex类型数据时,后面的4位数据不起作用,固定的是16KHZ,单声道,16bit/sampleadsAACDecoderSpecific.nAudioStereo = 0;adsAACDecoderSpecific.nAudioSizeType = 1;adsAACDecoderSpecific.nAudioSampleType = 4;}bAudioFirst = false;}else{if(pc.m_nBodySize > 2){if(data[1] == 1){//raw data 获取 audio payload//写ADTS数据到文件char szADTSTemp[8] = { 0 };CreateADTS(ascAudioSpecificConfig, pc.m_nBodySize - 2 + 7, szADTSTemp);fwrite(szADTSTemp, 7, 1, fpAAC);//写raw数据到文件fwrite(pc.m_body + 2, pc.m_nBodySize - 2, 1, fpAAC);}else{int kkkkk = 0;}}else{int kkkk = 0;}}//The actual audio content for the pkt-> m_body+1, size is pkt-> m_nBodySize-1. Here is the voice of Speex code.}else if (pc.m_packetType == RTMP_PACKET_TYPE_INFO && RTMP_ClientPacket(rtmp, &pc)){int nVideoCodecId = 0;int nVideoWidth = 255;int nVideoHeight = 188;int nVideoFrameRate = 25;int nAudioCodecId = 0;int nAudioSampleRate = 0;int nAudioSampleSize = 0;bool bStereo = false;          //立体声int nFileSize = 0;ParseScriptTag(pc.m_body, pc.m_nBodySize, nVideoCodecId, nVideoWidth, nVideoHeight, nVideoFrameRate, nAudioCodecId, nAudioSampleRate, nAudioSampleSize, bStereo, nFileSize);int k = 0;}RTMPPacket_Free(&pc);}}if (fp)fclose(fp);if (fpAAC)fclose(fpAAC);if (buf){free(buf);}if (rtmp){RTMP_Close(rtmp);RTMP_Free(rtmp);CleanupSockets();rtmp = NULL;}}return 0;
}

注:H.264视频分析软件(Elecard.Streameye.Tools)分析H264数据,使用暴风影音播放m4a文件中的aac音频。

代码下载:点击链接

参考资料:
http://blog.csdn.net/leixiaohua1020/article/details/42104893
http://blog.chinaunix.net/uid-15063109-id-4273162.html
http://blog.csdn.net/bsplover/article/details/7426511
http://blog.csdn.net/zqf_office/article/details/50868520

librtmp获取视频流和音频流1相关推荐

  1. SpringBoot 视频流和音频流(输出到浏览器)

    记录一次处理视频输出到浏览器,就像输出图片到浏览器一样效果. 如需想要图片输出到浏览器请查阅: https://blog.csdn.net/qq_16771097/article/details/12 ...

  2. FFmpeg中一个线程获取视频流一个线程执行scale测试代码

    在https://blog.csdn.net/fengbingchun/article/details/94712986 中介绍过如果usb视频流编码类型为rawvideo则无需进行解码,可直接通过a ...

  3. safari 获取视频流_如何在Safari中将RSS feed和社交媒体合并为一个流

    safari 获取视频流 Safari allows you to subscribe to RSS feeds and add your social media accounts so you c ...

  4. java 人脸识别jar包_java版天网人脸识别系统,获取视频流人脸识识别推送服务器展示...

    java版天网人脸识别系统,获取视频流 进行人脸识别后推送到流媒体服务器实时展示 获取视频流 进行人脸识别后推送到red5服务器(人脸识别技术由虹软®提供) 整个系统共有两个项目组成 red5_hls ...

  5. WebRTC实践获取视频流

    技术前言 通过上次课程"WebRTC简介"我们知道了WebRtc技术主要是为了实现网页之间的实时通讯,本次课程我们将用一个简单实例展开讲解WebRTC调取本地摄像头的具体方法及原理 ...

  6. java读取视频_【转载】Java 后端读取视频文件获取视频流后 前端进行播放/下载...

    /** * 获取视频流 * @param response * @param videoId 视频存放信息索引 * @return * @author xWang * @Date 2020-05-20 ...

  7. 前端获取视频流并播放

    2017-09-05 前端获取视频流并播放  JavaScript html5出来后,前端播放视频瞬间变得方便容易多了,一个<video>标签就可以搞定,只要给src属性正确的文件路径 ...

  8. cv2.VideoCapture从摄像头获取视频流并处理但是处理速度慢

    载请附上原文出处链接及本声明. 原文链接:https://blog.csdn.net/weixin_40802676/article/details/107012916 cv2.VideoCaptur ...

  9. 流媒体服务器EasyNVR怎样获取视频流的播放地址?

    很多用户要求在视频直播的过程中,直播画面或者链接是可以进行分享的,以便更多的人能够看到,这一项要求被运用在很多监管平台当中. 在我们的流媒体服务器EasyNVR中可以通过多种方式获得播放地址,本文就为 ...

最新文章

  1. 如何高效推进ML模型开发和部署?Uber机器学习平台Michelangelo实践
  2. 108. Leetcode 188. 买卖股票的最佳时机 IV (动态规划-股票交易)
  3. Django实现省市县级联菜单
  4. 带你走进和声搜索算法(Harmony search )的世界!
  5. mysql查询语句 查询方式
  6. PAT 乙级 1005. 继续(3n+1)猜想 (25) Java版
  7. 计算机缺失esul.dll,msedgeupdateres_es.dll
  8. 【OpenCV学习笔记】【教程翻译】三( 车牌检测之区域分割)
  9. Origin绘制热重TG和微分热重DTG曲线
  10. html展示微信昵称特殊字符,微信昵称加标签一直弹特殊符号
  11. 进qq空间显示服务器失败,QQ空间找不到服务器-进空间找不到服务器的解决办法...
  12. flex+java项目创建_创建Flex 4和Java Web应用程序
  13. 计算机一级一分钟要打多少字,打字速度分几个等级,一分钟打多少个字算较快?...
  14. mysql中端口的概念_端口的概念,端口的分类
  15. 读《做人的最高境界是厚道》有感
  16. 对 matplotlib.cm.RdYlBu() 的理解
  17. 图像检索中一些特征索引技术
  18. python将图像变成没有颜色_python将图片设置背景颜色修改为透明色
  19. PyG基于Node2Vec实现节点分类及其可视化
  20. http无状态还是web应用无状态

热门文章

  1. 虹科分享 | IOTA网络性能监控 | 如何有效分析VoIP问题
  2. 为什么面试官对“我不会我可以学”不买账?
  3. TCP 可靠传输机制
  4. 炎炎夏日,谈一谈低功耗云终端的重要性
  5. 徐州php溪谷_ThinkPHP溪谷H5游戏平台系统V3.0完整版源码源码下载
  6. phpstudy端口冲突怎么解决
  7. 所谓曝光凯福德金业涉诈骗是何居心
  8. MGRE网络的chap认证--ppp认证--tunnel 隧道综合实验
  9. Sketch Mac入门——初识Sketch矢量绘图软件
  10. python 高级技巧