阅读基础
原因
为什么要讨论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 |
}; |
AAC Channel config通道数 | 4bit |
|
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) 1 NALU 长度字段的字节数减 1,计算 NALU 长度为 1 + (L & 3)
NSPS (Num Of Sequence Parameter Sets) 1 SPS 的个数,计算方法为 NSPS & 0x1F
SPSL (Sequence Parameter Set Length) 2 SPS 数据的长度 SPSNU (Sequence Parameter Set NAL Unit) 可变长度 存储实际的 SPS 数据,长度为 8 bit * SPSL
NPPS (Num Of Picture Parameter Sets) 1 PPS 的个数 PPSL (Picture Parameter Set Length) 2 PPS 数据的长度 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
学习资料分享