6、SRS4.0源代码分析之Origin模式下RTMP协议解封装处理

目标:

RTMP是Adobe 公司为 Flash 播放器和服务器之间音视频数据传输开发的私有协议,因为出现的比较早,所以RTMP协议已经成为国内直播领域尤其是CDN之间推流的标准协议。

Adobe在2017年宣布到2020年底将不再支持Flash,所以很多系统平台的浏览器也都不再支持RTMP协议,如果流媒体服务器只支持RTMP协议,则最新的浏览器就无法通过无插件的方式从服务器获取媒体流,所以SRS服务器有个很重要的工作就是针对音视频数据的转码。

例如,HLS(HTTP Live Streaming)是一个被各种流媒体播放客户端广泛支持的标准协议,SRS流媒体服务器将RTMP推流端发送的音视频数据,转换为满足HLS协议要求的m3u8文件和ts文件,最终,浏览器通过HTTP协议从服务器获取m3u8文件和ts文件并实现本地播放。

本章将深入学习SRS4.0源代码中Origin模式下RTMP协议的解封装处理逻辑,为理解后续的各种转码逻辑打下基础。

内容:

SRS如果工作在Edge模式下,则属于纯转发,此时不会做HLS(ts、m3u8文件)转码处理,只有工作在Origin模式下的SRS服务器才会执行HLS(ts、m3u8文件)转码处理。所以从Origin模式的入口函数开始分析:

srs_error_t SrsOriginHub::on_video(SrsSharedPtrMessage* shared_video, bool is_sequence_header)

srs_error_t SrsOriginHub::on_audio(SrsSharedPtrMessage* shared_audio)
  • 1、对于RTMP协议,视频数据总是以FLV格式封装成Message中的payload部分,所以,首先分析SRS代码中针对视频FLV格式报文的解析处理:
srs_error_t SrsOriginHub::on_video(SrsSharedPtrMessage* shared_video, bool is_sequence_header)
{
    // 因为RTMP客户端发送的SPS+PPS报文,可能存在格式不统一,导致SRS解析失败
    // 实际上SPS+PPS是编码器生成用于指导解码器解码的信息,对于流媒体服务器其实意义不大
    // 更多的时候是服务器缓存后直接转发给播放器客户端
    // 所以,SRS作者在配置文件中增加了一个配置项,用于禁用SPS解析,防止不必要的失败导致无法推流
    if (is_sequence_header) {
        format->avc_parse_sps = _srs_config->get_parse_sps(req->vhost);
    }
    
    // 我们知道,对于RTMP协议,视频数据总是以FLV格式封装成Message中的payload部分
    // 所以SrsFormat::on_video()函数其实就是对FLV封装格式的H264报文进行格式化解析,后面详细分析
    if ((err = format->on_video(msg)) != srs_success) {
        return srs_error_wrap(err, "format consume video");
    }
    
    // 这里是为了过滤掉不支持的视频格式,例如H263
    if (!format->vcodec) {  return err; } 
    
    if (format->is_avc_sequence_header()) {
        // 这里主要是为了把视频帧的SPS+PPS信息打印出来
    }
    
    // 这里的判断,是为了保证先收到SPS+PPS后,再处理普通视频帧
    if (format->vcodec && !format->vcodec->is_avc_codec_ok()) { return err; }
    
    // HLS协议转码及其错误处理
    if ((err = hls->on_video(msg, format)) != srs_success) {
        ......
    }
    
    // DASH、DVR、HDS、FORWARDER处理,转码处理大同小异,暂不分析
    dash->on_video(msg, format);
    dvr->on_video(msg, format);
    hds->on_video(msg);
    
    forwarder->on_video(msg); // copy to all forwarders.
}

上面的函数基本包括了SRS源站在进行HLS转码之前针对Message的逻辑处理,其中的难点主要是SrsFormat::on_video()函数针对Message的解析处理。其实,如果知道了在RTMP协议中,音视频数据总是以FLV格式经行封装,就不难理解此函数内部的处理逻辑。

这里我们先回顾一下H264视频帧的FLV封装格式:

   FLV VideoTagHeader        后续数据类型描述符    是SPS+PPS时,这里3字节为全零
+---------------------+     +---------------+     +------------------+
|Frame Type | CodecID |  +  | AVCPacketType |  +  |  CompositionTime |
+---------------------+     +---------------+     +------------------+
   4 bits     4 bits            1 byte                   3 byte
  视频帧类型   编码器ID        0:SPS+PPS;1:NALU     cts字段,用于计算pts和dts

所以,SrsFormat::on_video()处理逻辑是:

  1. 读取一个字节的FLV videoTag,并根据高4bit判断帧类型,低4bit判断是否是H264编码类型
  2. 创建SrsVideoCodecConfig对象用于保存处理SPS+PPS
  3. 使用SrsFormat::video_avc_demux()处理H264视频帧
srs_error_t SrsFormat::on_video(int64_t timestamp, char* data, int size)
{
    SrsBuffer* buffer = new SrsBuffer(data, size);
    
    // 读取一个字节的FLV videoTag,并根据低4bit判断是否是H264编码类型,不是则直接返回
    int8_t frame_type = buffer->read_1bytes();
    SrsVideoCodecId codec_id = (SrsVideoCodecId)(frame_type & 0x0f);
    if (codec_id != SrsVideoCodecIdAVC) {  return err;  }

    // 创建处理SPS+PPS的SrsVideoCodecConfig对象,创建SrsVideoFrame对象
    if (!vcodec) {  vcodec = new SrsVideoCodecConfig();  }
    if (!video) {  video = new SrsVideoFrame(); }

    // 初始化SrsVideoCodecConfig对象
    if ((err = video->initialize(vcodec)) != srs_success) {
        return srs_error_wrap(err, "init video");
    }

    // buffer回退一个字节,再调用video_avc_demux()函数重新解析FLV报文
    buffer->skip(-1 * buffer->pos()); 
    return video_avc_demux(buffer, timestamp);
}

上面的函数只是对报文做简单的H264类型判断,接下来SrsFormat::video_avc_demux()函数解析FLV封装信息

srs_error_t SrsFormat::video_avc_demux(SrsBuffer* stream, int64_t timestamp)
{
    // 读取一个字节的FLV videoTag,并判断低4bit的编码类型和高4bit帧类型,不满足预期则直接返回
    int8_t frame_type = stream->read_1bytes();
    SrsVideoCodecId codec_id = (SrsVideoCodecId)(frame_type & 0x0f);
    frame_type = (frame_type >> 4) & 0x0f;
    
    // 如果帧类型是信息帧则直接丢弃不处理
    video->frame_type = (SrsVideoAvcFrameType)frame_type; 
    if (video->frame_type == SrsVideoAvcFrameTypeVideoInfoFrame) { return err; }
    if (codec_id != SrsVideoCodecIdAVC) {  return err;  }

    // FLV videoTag后面,必须还有4个字节(AVCPacketType+CompositionTime),否则先返回,暂不处理
    if (!stream->require(4)) {  return err;  }

    // 获取1字节AVCPacketType和3字节CompositionTime(即cts)
    int8_t avc_packet_type = stream->read_1bytes();
    int32_t composition_time = stream->read_3bytes();

    // 所谓cts其实是用于计算pts的,即 pts = dts + cts
    // avc_packet_type=0表示报文是SPS+PPS,avc_packet_type=1表示报文是普通NALU
    video->dts = timestamp;
    video->cts = composition_time;
    video->avc_packet_type = (SrsVideoAvcFrameTrait)avc_packet_type;
    
    // 解析完FLV封装后,使用类成员 raw 和 nb_raw 记录纯数据和纯数据的长度
    raw = stream->data() + stream->pos();
    nb_raw = stream->size() - stream->pos();

    // 使用avc_demux_sps_pps()函数处理SPS+PPS报文,video_nalu_demux()函数处理普通NALU报文
    if (avc_packet_type == SrsVideoAvcFrameTraitSequenceHeader) {
        avc_demux_sps_pps(stream);
    } else if (avc_packet_type == SrsVideoAvcFrameTraitNALU){
        video_nalu_demux(stream);
    }
}

因为SPS+PPS是编码器自动生成用于指导解码器正确解码的信息,对于流媒体服务器更多的是存储和转发,所以暂时可以不用深入了解SrsFormat::avc_demux_sps_pps()函数的处理细节。

SrsFormat::video_nalu_demux()函数用于处理普通NALU报文,此函数的重点是,根据AnnexB或IBMF格式解析NALU

srs_error_t SrsFormat::video_nalu_demux(SrsBuffer* stream)
{
    // 第一个报文进来,先进入此分支猜测NALU报文封装格式
    if (vcodec->payload_format == SrsAvcPayloadFormatGuess) 
    {
        if ((err = avc_demux_annexb_format(stream)) != srs_success) {
            ......
            vcodec->payload_format = SrsAvcPayloadFormatIbmf;
        } else {
            vcodec->payload_format = SrsAvcPayloadFormatAnnexb;
        }
    }
    else if (vcodec->payload_format == SrsAvcPayloadFormatIbmf) 
    {
        if ((err = avc_demux_ibmf_format(stream)) != srs_success) {
            ......
        }
    }
    else {
        if ((err = avc_demux_annexb_format(stream)) != srs_success) {
            ......
        }
    }

    return err;
}

如下,不管是哪种封装格式的NALU,最终都是调用SrsVideoFrame::add_sample()函数保存视频数据

srs_error_t SrsFormat::avc_demux_annexb_format(SrsBuffer* stream)
{
    while (!stream->empty()) {
        srs_avc_startswith_annexb(stream, &nb_start_code);
        ......
        video->add_sample(p, (int)(pp - p));
    }
}

srs_error_t SrsFormat::avc_demux_ibmf_format(SrsBuffer* stream)
{
    for (int i = 0; i < PictureLength;) {
        ......
        video->add_sample(stream->data() + stream->pos(), NALUnitLength);
    }
}
  • 2、接下来分析SRS针对RTMP音频报文FLV封装格式的解析处理:
srs_error_t SrsOriginHub::on_audio(SrsSharedPtrMessage* shared_audio)
{
    // 使用SrsFormat::on_audio()解析音频报文,如果报文为无效类型,则直接返回
    if ((err = format->on_audio(msg)) != srs_success) {  }
    if (!format->acodec) { return err; }

    if (format->is_aac_sequence_header()) {
        // 这里主要是为了将aac sequence header信息打印出来
    }
    
    // HLS协议转码及其错误处理
    if ((err = hls->on_video(msg, format)) != srs_success) {

    }
    
    // DASH、DVR、HDS、FORWARDER处理,本章暂不分析
}

在了解SrsFormat::on_audio()函数之前,我们先回顾一下AAC音频帧的FLV封装格式:

              FLV AudioTagHeader
++++++++++++++++++++++++++++++++++++++++++++++++++         +++++++++++++++
|SoundFormat | SoundRate | SoundSize | SoundType |   OR +  |AACPacketType|
++++++++++++++++++++++++++++++++++++++++++++++++++         +++++++++++++++
   4bits        2bits        1bit        1bit                  1 byte
  编码格式       采样率    采样精度8或16位  声道数(单/双)    0:AAC sequence header;1:AAC音频数据

所以,SrsFormat::on_audio()处理逻辑是:

  1. 读取一个字节的FLV audioTag,并根据高4bit判断音频编码类型,暂时只支持AAC和MP3
  2. 创建SrsAudioCodecConfig对象用于保存处理AAC sequence header
  3. 根据音频编码类型,调用audio_aac_demux()或audio_mp3_demux()处理实际的音频数据。
srs_error_t SrsFormat::on_audio(int64_t timestamp, char* data, int size)
{
    
    SrsBuffer* buffer = new SrsBuffer(data, size);
    
    // 读取一个字节的FLV audioTag,并根据低4bit判断是否是AAC或MP3编码类型,不是则直接返回
    uint8_t v = buffer->read_1bytes();
    SrsAudioCodecId codec = (SrsAudioCodecId)((v >> 4) & 0x0f);
    
    if (codec != SrsAudioCodecIdMP3 && codec != SrsAudioCodecIdAAC) { return err; }
    
    // 创建处理AAC sequence header的SrsAudioCodecConfig对象,创建SrsAudioFrame对象
    if (!acodec) { acodec = new SrsAudioCodecConfig(); }
    if (!audio) { audio = new SrsAudioFrame(); }
    
    if ((err = audio->initialize(acodec)) != srs_success) {
        return srs_error_wrap(err, "init audio");
    }
    
    // buffer回退一个字节,再重新解析FLV报文
    // 如果是MP3报文,调用audio_mp3_demux()函数,如果是AAC,则调用audio_aac_demux()函数
    buffer->skip(-1 * buffer->pos());
    if (codec == SrsAudioCodecIdMP3) { return audio_mp3_demux(buffer, timestamp); }
    
    return audio_aac_demux(buffer, timestamp);
}

AAC音频处理:

srs_error_t SrsFormat::audio_aac_demux(SrsBuffer* stream, int64_t timestamp)
{
    audio->cts = 0;
    audio->dts = timestamp;
    
    // 读取一个字节的FLV AudioTagHeader,并解析,
    // 并用解析的结果初始化SrsFormat::SrsAudioCodecConfig对象
    int8_t sound_format = stream->read_1bytes();
    int8_t sound_type = sound_format & 0x01;
    int8_t sound_size = (sound_format >> 1) & 0x01;
    int8_t sound_rate = (sound_format >> 2) & 0x03;
    
    sound_format = (sound_format >> 4) & 0x0f;
    SrsAudioCodecId codec_id = (SrsAudioCodecId)sound_format;
    acodec->id = codec_id;
    acodec->sound_type = (SrsAudioChannels)sound_type;
    acodec->sound_rate = (SrsAudioSampleRate)sound_rate;
    acodec->sound_size = (SrsAudioSampleBits)sound_size;
 
    // 再读取一个字节的AACPacketType
    SrsAudioAacFrameTrait aac_packet_type = (SrsAudioAacFrameTrait)stream->read_1bytes();
    audio->aac_packet_type = (SrsAudioAacFrameTrait)aac_packet_type;

    raw = stream->data() + stream->pos();
    nb_raw = stream->size() - stream->pos();
    
    // 根据报文类型分别处理AAC sequence header 和 AAC纯数据
    if (aac_packet_type == SrsAudioAacFrameTraitSequenceHeader) 
    {
        acodec->aac_extra_data = std::vector<char>(copy_stream_from, copy_stream_from + aac_extra_size);
        audio_aac_sequence_header_demux(&acodec->aac_extra_data[0], aac_extra_size);
    } 
    else if (aac_packet_type == SrsAudioAacFrameTraitRawData) 
    {
       if (!acodec->is_aac_codec_ok()) { return err; }
       
       audio->add_sample(stream->data() + stream->pos(), stream->size() - stream->pos())
    }
}

MP3音频处理:

srs_error_t SrsFormat::audio_mp3_demux(SrsBuffer* stream, int64_t timestamp)
{
    // 处理逻辑大同小异,不再单独分析

    // mp3 payload.
    if ((err = audio->add_sample(data, size)) != srs_success) {
        return srs_error_wrap(err, "add audio frame");
    }
}

画重点: 上面所有针对音视频RTMP报文的解析处理都是为了去除FLV封装,得到纯H264和AAC数据后,最终,通过SrsFrame::add_sample()对纯的音视频数据进行保存,具体代码如下:

srs_error_t SrsFrame::add_sample(char* bytes, int size)
{
    srs_error_t err = srs_success;
    
    if (nb_samples >= SrsMaxNbSamples) {
        return srs_error_new(ERROR_HLS_DECODE_ERROR, "Frame samples overflow");
    }
    
    SrsSample* sample = &samples[nb_samples++];
    sample->bytes = bytes;
    sample->size = size;
    sample->bframe = false;
    
    return err;
}

所以,每帧音视频数据,最终都是通过SrsFrame::add_sample()临时保存在SrsFrame对象中。

  • 3、Origin模式下的SRS服务器收到推流客户端publish和unpublish消息时,通知各种协议对象开始或结束转码逻辑
srs_error_t SrsOriginHub::on_publish()
{
    create_forwarders(); // 创建forwarder转发对象
    encoder->on_publish(req); // 外部ffmpeg编码工具
    hls->on_publish(); // 启动HLS协议转码
    dash->on_publish(); // 启动DASH
    dvr->on_publish(req)// 启动DVR
    hds->on_publish(req)// 启动HDS
    
    ng_exec->on_publish(req); // 启动外部工具进程
}

void SrsOriginHub::on_unpublish()
{
    is_active = false;

    destroy_forwarders();    // 释放forwarder转发对象
    encoder->on_unpublish(); // 
    hls->on_unpublish();     // 
    dash->on_unpublish();    // 
    dvr->on_unpublish();     // 
    hds->on_unpublish();     // 
    
    ng_exec->on_unpublish();
}
srs_error_t SrsHls::on_publish()
{
    // 如果配置文件中HLS未使能,则直接返回
    if (!_srs_config->get_hls_enabled(req->vhost)) { return err; }
    
    controller->on_publish(req)// 通知SrsHlsController为HLS转码做准备
    
    // 如果配置文件为true,则后续处理中直接将FLV时间戳转换为TS DTS
    hls_dts_directly = _srs_config->get_vhost_hls_dts_directly(req->vhost);
}

srs_error_t SrsHlsController::on_publish(SrsRequest* req)
{
    muxer->on_publish(req); // 在SrsHlsMuxer内部启动一个异步协程,排队处理一些耗时、阻塞的事情
    
    // 为后续生成ts和m3u8文件准备一些基本配置信息,包括:文件名、文件路径、文件时长
    // 具体参数信息:
    // win=60000ms, frag=10000ms, prefix=, path=./objs/nginx/html, m3u8=[app]/[stream].m3u8, ts=[app]/[stream]-[seq].ts, 
    // aof=2.00, floor=0, clean=1, waitk=1, dispose=0ms, dts_directly=1
    muxer->update_config(req, entry_prefix, path, m3u8_file, ts_file, hls_fragment,
                         hls_window, ts_floor, hls_aof_ratio, cleanup, 
                         wait_keyframe,hls_keys,hls_fragments_per_key,
                         hls_key_file, hls_key_file_path, hls_key_url);
                         
    muxer->segment_open();   
}

总结:

上面分析了RTMP协议针中FLV封装格式的音视频报文的解析逻辑,下一章将分析HLS转码的处理逻辑。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值