rtmp封包分析之flv格式封包细节

阅读基础

flv格式分析与解复用-优快云博客

原因

为什么要讨论flv封包?因为rtmp基于flv进行传输数据,我们需要将data -> flv.data ->rtmp.data

在上述文章中,我们是对音视频flv格式解复用探讨,但是对于封包的操作细节并没有探讨,且没有对aac和h264的sequence header详细描述。sequence header在flv封包中起到开篇点题的作用,所以我们要搞清楚sequence header基本结构。

aac sequence header

sequence header解析

flv格式数据音频第一帧是配置信息数据包,所以我们对aac seq进行详细解析。

AudioSpecificConfig 4B
字段名称                    字节数        描述
audioObjectType            5 bits        AAC 音频编码类型
samplingFrequencyIndex     4 bits        采样率索引
channelConfiguration       4 bits        声道数
//可选字段
frameLengthFlag            1 bit         帧长度标志
dependsOnCoreCoder         1 bit         是否依赖核心编码器
extensionFlag              1 bit         扩展标志

AudioSpecificConfig() {
    audioObjectType;                 // 5 bits
    samplingFrequencyIndex;         // 4 bits
    if (samplingFrequencyIndex == 0xF)
        samplingFrequency;          // 24 bits
    channelConfiguration;           // 4 bits
    if (audioObjectType == 29)
        extensionAudioObjectType;   // 5 bits
    
    // 可能还有其他可选字段
}

AudioSpecificConfig包含着一些更加详细的音频信息,它的定义在ISO14496-3中1.6.2.1。

ps:下列字节是从1开始(以数组索引需-1)

字段占位含义
AAC Profile编码级别5 bits

编码级别:

AAC Main 0x01  

AAC LC 0x02  

AAC SSR 0x03

AAC Sample Frequence采样率

4bit


const int sampling_frequencies[] = {
    96000,  // 0x0
    88200,  // 0x1
    64000,  // 0x2
    48000,  // 0x3
    44100,  // 0x4
    32000,  // 0x5
    24000,  // 0x6
    22050,  // 0x7
    16000,  // 0x8
    12000,  // 0x9
    11025,  // 0xa
    8000,   // 0xb
    7350    // 0xc
    // 0xd e f是保留的

};

AAC Channel config通道数4bit

0x00 - 未定义

0x01 - 单声道 (center front speaker)

0x02 - 双声道 (left, right front speakers)

0x03 - 三声道 (center, left, right front speakers)

0x04 - 四声道 (center, left, right front speakers, rear surround speakers)

0x05 - 五声道 (center, left, right front speakers, left surround, right surround rear speakers)

0x06 - 5.1 声道 (center, left, right front speakers, left surround, right surround rear speakers, front low frequency effects speaker)

0x07 - 7.1 声道 (center, left, right center front speakers, left, right outside front speakers, left surround, right surround rear speakers, front low frequency effects speaker)

0x08 - 0x0F - 保留

frameLengthFlag                1 bit帧长度标志
dependsOnCoreCoder        1 bit   

是否依赖核心编码器
extensionFlag                 1 bit    扩展标志

 rtmp封包代码:

class AudioSpecMsg :public MsgBaseObj
{
public:
    AudioSpecMsg(uint8_t profile, uint8_t channel_num, uint32_t samplerate){
        profile_ = profile;
        channels_ = channel_num;
        sample_rate_ = samplerate;
    }

    virtual ~AudioSpecMsg(){}
    uint8_t profile_ = 2;   //2 : AAC LC(Low Complexity)
    uint8_t channels_ = 2;
    uint32_t sample_rate_ = 48000;
    int64_t pts_;
};

// 2. 检查是否需要发送音频配置信息 (flv -> AudioSpecMsg)
if(need_send_audio_spec_config)
{
    need_send_audio_spec_config = false;
    AudioSpecMsg *aud_spc_msg = new AudioSpecMsg(audio_encoder_->get_profile(),
                                                 audio_encoder_->get_channels(),
                                                 audio_encoder_->get_sample_rate());
    aud_spc_msg->pts_ = 0;
    rtmp_pusher->Post(RTMP_BODY_AUD_SPEC, aud_spc_msg);
}



 case RTMP_BODY_AUD_SPEC:// 2.4 处理音频配置消息
 {
     if(!is_first_audio_sequence_) {
         is_first_audio_sequence_ = true;
         LogInfo("%s:t%u", AVPublishTime::GetInstance()->getAacHeaderTag(),
                 AVPublishTime::GetInstance()->getCurrenTime());
     }
     //aac sequence header
     AudioSpecMsg* audio_spec = (AudioSpecMsg*)data;
    
     uint8_t aac_spec_[4];//data aac header
     aac_spec_[0] = 0xAF;    //1010 1111
     aac_spec_[1] = 0x0;     // 0 = aac sequence header

    //根据audio参数,转化成aac sequence header并写入
     AACRTMPPackager::GetAudioSpecificConfig(&aac_spec_[2], audio_spec->profile_,
             audio_spec->sample_rate_, audio_spec->channels_);
     SendAudioSpecificConfig((char *)aac_spec_, 4);
     break;
 }

 配合《flv格式分析与解复用》描述如下:

 小结

一定要分清楚,aac_data(看阅读基础)和aac_spec这两者音频参数。具体参数如下

MPEG-4 音频 - MultimediaWiki --- MPEG-4 Audio - MultimediaWiki

AVC sequence header

AVC 序列头部的基本信息,帮助解码器理解如何解析和播放视频流。

sequence header

AVC 配置数据,它包含了以下内容:

  • SPS: 包含有关视频流的编码信息,如分辨率、帧率、码率等。
  • PPS: 包含关于图像编码的参数,如量化参数、运动补偿等。
字段名称字节数描述
CV (Configuration Version)1固定为 0x01,表示版本号
PI (AVC Profile Indication)1表示视频编码所使用的 AVC profile 信息
PC (Profile Compatibility)1表示视频编码的 profile 兼容性信息
LI (AVC Level Indication)1表示 AVC level 信息,值越高表示编码复杂度越高
L (Length Size Minus One)1NALU 长度字段的字节数减 1,计算 NALU 长度为 1 + (L & 3)
NSPS (Num Of Sequence Parameter Sets)1SPS 的个数,计算方法为 NSPS & 0x1F
SPSL (Sequence Parameter Set Length)2SPS 数据的长度
SPSNU (Sequence Parameter Set NAL Unit)可变长度存储实际的 SPS 数据,长度为 8 bit * SPSL
NPPS (Num Of Picture Parameter Sets)1PPS 的个数
PPSL (Picture Parameter Set Length)2PPS 数据的长度
PPSNU (Picture Parameter Set NAL Unit)可变长度存储实际的 PPS 数据,长度为 8 bit * PPSL

 rtmp封包代码:

if(need_send_video_config)
{
    need_send_video_config = false;
	// 2.1 创建 VideoSequenceHeaderMsg 消息,包含 SPS 和 PPS 数据
    VideoSequenceHeaderMsg * vid_config_msg = new VideoSequenceHeaderMsg(
                video_encoder_->get_sps_data(),
                video_encoder_->get_sps_size(),
                video_encoder_->get_pps_data(),
                video_encoder_->get_pps_size()
                );
    vid_config_msg->nWidth = video_width_;
    vid_config_msg->nHeight = video_height_;
    vid_config_msg->nFrameRate = video_fps_;
    vid_config_msg->nVideoDataRate = video_bitrate_;
    vid_config_msg->pts_ = 0;

	// 2.2 通过 rtmp_pusher 发送视频配置信息
    rtmp_pusher->Post(RTMP_BODY_VID_CONFIG, vid_config_msg);

}





 case RTMP_BODY_VID_CONFIG:// 2.2 处理视频配置消息
 {
     if(!is_first_video_sequence_) {
         is_first_video_sequence_ = true;
         LogInfo("%s:t%u", AVPublishTime::GetInstance()->getAvcHeaderTag(),
                 AVPublishTime::GetInstance()->getCurrenTime());
     }
     VideoSequenceHeaderMsg *vid_cfg_msg = (VideoSequenceHeaderMsg*)data;
     if(!sendH264SequenceHeader(vid_cfg_msg))
     {
         LogError("sendH264SequenceHeader failed");
     }
     delete vid_cfg_msg;
     break;
 }


/// @brief 构建并发送 H.264 视频流的序列头部数据包。
/// @param seq_header 
/// @return 
bool RTMPPusher::sendH264SequenceHeader(VideoSequenceHeaderMsg *seq_header)
{
    if (seq_header == NULL)
    {
        return false;
    }
    uint8_t body[1024] = { 0 };

    //1. 构建 RTMP 视频数据包头部
    int i = 0;
    body[i++] = 0x17; // 1:keyframe  7:AVC
    body[i++] = 0x00; // AVC sequence header
    //2. AVCPacketType 保留字段,填充 0
    body[i++] = 0x00;
    body[i++] = 0x00;
    body[i++] = 0x00; // fill in 0;   0

    //3. 构建 AVC Decoder Configuration Record
    body[i++] = 0x01;               // configurationVersion
    body[i++] = seq_header->sps_[1]; // AVCProfileIndication
    body[i++] = seq_header->sps_[2]; // profile_compatibility
    body[i++] = seq_header->sps_[3]; // AVCLevelIndication
    body[i++] = 0xff;               // lengthSizeMinusOne

    // 写入 SPS(Sequence Parameter Set)数据
    body[i++] = 0xE1;           //&0x1f
    // sps data length
    body[i++] = (seq_header->sps_size_ >> 8) & 0xff;;
    body[i++] = seq_header->sps_size_ & 0xff;
    // sps data
    memcpy(&body[i], seq_header->sps_, seq_header->sps_size_);
    i = i + seq_header->sps_size_;

    // 写入 PPS(Picture Parameter Set)数据
    body[i++] = 0x01; //& 0x1f
    // pps data length
    body[i++] = (seq_header->pps_size_ >> 8) & 0xff;;
    body[i++] = seq_header->pps_size_ & 0xff;
    // sps data
    memcpy(&body[i], seq_header->pps_, seq_header->pps_size_);
    i = i + seq_header->pps_size_;

    time_ = TimesUtil::GetTimeMillisecond();
    //    time_ = Tim

    //rtmp封包发送
    return sendPacket(RTMP_PACKET_TYPE_VIDEO, (unsigned char*)body, i, 0);
}

 配合《flv格式分析与解复用》描述如下:

 

小结

 sequence header字段较多,主要是sps和pps信息,注意 L 用于计算 NALU 长度的方法非常关键。

tagData总体框架

aac Tag Data 

if(aac_size > 0)
{
	// 将编码后的 AAC 数据封装成 AudioRawMsg 消息,并通过 rtmp_pusher 发送出去
    AudioRawMsg *aud_raw_msg = new AudioRawMsg(aac_size + 2); //两个字节的头部信息
    // 打上时间戳
    aud_raw_msg->pts = AVPublishTime::GetInstance()->get_audio_pts();
	//flv ->aac raw header
    aud_raw_msg->data[0] = 0xaf; //1010 1111  音频格式4bit->10 aac      2bit 采样率xxx
    aud_raw_msg->data[1] = 0x01; // 1 =  raw data数据
    memcpy(&aud_raw_msg->data[2], aac_buf_, aac_size);
    rtmp_pusher->Post(RTMP_BODY_AUD_RAW, aud_raw_msg);
    LogDebug("PcmCallback Post");
}


 case RTMP_BODY_AUD_RAW: // 2.5 处理音频原始数据消息
 {
     if(!is_first_audio_frame_) { //首帧
         is_first_audio_frame_ = true;
         LogInfo("%s:t%u", AVPublishTime::GetInstance()->getAacDataTag(),
                 AVPublishTime::GetInstance()->getCurrenTime());
     }
     AudioRawMsg* audio_raw = (AudioRawMsg*)data;

    //RTMP发送
     if(sendPacket(RTMP_PACKET_TYPE_AUDIO, (unsigned char*)audio_raw->data,
                   audio_raw->size, audio_raw->pts))
     {

     }
     else
     {
         LogInfo("at handle send audio pack fail");
     }
     delete audio_raw;                       //注意要用new 会调用析构函数,释放内部空间
     break;
 }

 主要写入红框的信息 

avc Tag Data 


if(video_encoder_->Encode(yuv, 0, video_nalu_buf, video_nalu_size_) == 0)
{
    // 3.1 获取编码后的 NALU 数据
    NaluStruct *nalu = new NaluStruct(video_nalu_buf, video_nalu_size_);
    nalu->type = video_nalu_buf[0] & 0x1f;  // 0001 1111
    nalu->pts = AVPublishTime::GetInstance()->get_video_pts();

	// 3.2 通过 rtmp_pusher 发送编码后的视频数据
	rtmp_pusher->Post(RTMP_BODY_VID_RAW, nalu);
    LogDebug("YuvCallback Post");
}



case RTMP_BODY_VID_RAW: // 2.3 处理视频原始数据消息
{
    if(!is_first_video_frame_) {
        is_first_video_frame_ = true;
        LogInfo("%s:t%u", AVPublishTime::GetInstance()->getAvcFrameTag(),
                AVPublishTime::GetInstance()->getCurrenTime());
    }

    NaluStruct* nalu = (NaluStruct*)data;
    if(sendH264Packet((char*)nalu->data,nalu->size,(nalu->type == 0x05) ? true : false,
                      nalu->pts))
    {
        //LogInfo("send pack ok");
    }
    else
    {
        LogInfo("at handle send h264 pack fail");
    }
    delete nalu;                       //注意要用new 会调用析构函数,释放内部空间
    break;
}


bool RTMPPusher::sendH264Packet(char *data,int size, bool is_keyframe, unsigned int timestamp)
{
	// 1. 检查输入参数是否合法
    if (data == NULL && size<11)
    {
        return false;
    }
	//封装flv raw video类型头部 5B + nalu.length 4B + nalu
    unsigned char *body = new unsigned char[size + 9];
	
    int i = 0;
	//type id: 0001 0111 or  0010 0111   
	//0111->7 avc 	 1 or 10 
    if (is_keyframe)
    {
        body[i++] = 0x17;// 如果是关键帧, 设置帧类型标志为 0x17 (I-frame + AVC)
    }
    else
    {
        body[i++] = 0x27;// 如果是P帧, 设置帧类型标志为 0x27 (P-frame + AVC)
    }
    body[i++] = 0x01; // avpacket type == 1
    body[i++] = 0x00; // 合成时间偏移
    body[i++] = 0x00;
    body[i++] = 0x00;

    // NALU size
    body[i++] = size >> 24;
    body[i++] = size >> 16;
    body[i++] = size >> 8;
    body[i++] = size & 0xff;

    // NALU data
    memcpy(&body[i], data, size);
	//rtmp发送
    bool bRet = sendPacket(RTMP_PACKET_TYPE_VIDEO, body, i + size, timestamp);
    delete[] body;
    return bRet;
}

 主要写入红框的信息 

Script Tag Data

为了本文完整性,把matedata也一并进行分析

//5. RTMP -> FLV的格式去发送, metadata
FLVMetadataMsg *metadata = new FLVMetadataMsg();
// 设置视频相关
metadata->has_video = true;
metadata->width = video_encoder_->get_width();
metadata->height = video_encoder_->get_height();
metadata->framerate = video_encoder_->get_framerate();
metadata->videodatarate = video_encoder_->get_bit_rate();
// 设置音频相关
metadata->has_audio = true;
metadata->channles = audio_encoder_->get_channels();
metadata->audiosamplerate = audio_encoder_->get_sample_rate();
metadata->audiosamplesize = 16;
metadata->audiodatarate = 64;
metadata->pts = 0;
rtmp_pusher->Post(RTMP_BODY_METADATA, metadata, false);



case RTMP_BODY_METADATA: // 2.1 处理元数据消息
{
    if(!is_first_metadata_) {
        is_first_metadata_ = true;
        LogInfo("%s:t%u", AVPublishTime::GetInstance()->getMetadataTag(),
                AVPublishTime::GetInstance()->getCurrenTime());
    }

    FLVMetadataMsg *metadata = (FLVMetadataMsg*)data;
    if(!SendMetadata(metadata))
    {
        LogError("SendMetadata failed");
    }
    delete metadata;
    break;
}



bool RTMPPusher::SendMetadata(FLVMetadataMsg *metadata)
{
    if (metadata == NULL)
    {
        return false;
    }
    char body[1024] = { 0 };

    //开始构建 RTMP 元数据包的内容
    char * p = (char *)body;
    //设置第一个字段为 AMF 字符串
    p = put_byte(p, AMF_STRING);
    p = put_amf_string(p, "@setDataFrame"); //写入string.length 和 string.data
    //设置第二个字段为 AMF 字符串
    p = put_byte(p, AMF_STRING);
    p = put_amf_string(p, "onMetaData");
    //设置第三个字段为 AMF 对象, 并添加元数据信息
    p = put_byte(p, AMF_OBJECT);
    p = put_amf_string(p, "copyright");
    p = put_byte(p, AMF_STRING);
    p = put_amf_string(p, "firehood");

    if(metadata->has_video) //如果有视频数据, 添加视频相关属性
    {
        p = put_amf_string(p, "width");
        p = put_amf_double(p, metadata->width);

        p = put_amf_string(p, "height");
        p = put_amf_double(p, metadata->height);

        p = put_amf_string(p, "framerate");
        p = put_amf_double(p, metadata->framerate);

        p = put_amf_string(p, "videodatarate");
        p = put_amf_double(p, metadata->videodatarate);

        p = put_amf_string(p, "videocodecid");
        p = put_amf_double(p, FLV_CODECID_H264);
    }
    if(metadata->has_audio)//如果有音频数据, 添加音频相关属性
    {
        p = put_amf_string(p, "audiodatarate");
        p = put_amf_double(p, (double)metadata->audiodatarate);

        p = put_amf_string(p, "audiosamplerate");
        p = put_amf_double(p, (double)metadata->audiosamplerate);

        p = put_amf_string(p, "audiosamplesize");
        p = put_amf_double(p, (double)metadata->audiosamplesize);

        p = put_amf_string(p, "stereo");
        p = put_amf_double(p, (double)metadata->channles);

        p = put_amf_string(p, "audiocodecid");
        p = put_amf_double(p, (double)FLV_CODECID_AAC);
    }

    //最后在对象结尾添加 AMF 对象结束标记。
    p = put_amf_string(p, "");
    p = put_byte(p, AMF_OBJECT_END);

    //rtmp发送
    return sendPacket(RTMP_PACKET_TYPE_INFO, (unsigned char*)body, p - body, 0);
}

 主要遵循下列原则

总结

本文主要对flv sequence headern知识点进行补充,以及描述rtmp传输对flv封包流程。

查阅

【总结】FLV(AAC/AVC)学习笔记 | evan xia

学习资料分享

0voice · GitHub

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值