前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >SRS:webrtc_to_rtmp详解

SRS:webrtc_to_rtmp详解

原创
作者头像
何其不顾四月天
修改于 2024-07-31 02:45:23
修改于 2024-07-31 02:45:23
47700
代码可运行
举报
运行总次数:0
代码可运行

SRS:webrtc_to_rtmp详解

前言

SRS(Simple Realtime Server),自我开始做音视频行业开始,就有人力推给我的一个开源库,虽然我到现在还是音频领域的入门出徘徊,但也积攒了一些对srs的使用经验。

目前,正在做协议之间的转换工作,也在学习与研究之中,srs不出意外的就拿出来学习研究了,主要研究的是 rtc协议转换为rtmp.也有一点小小的心得了,最近也恰巧有一些活动就一块参加了。

简介

srs

代码语言:wiki
AI代码解释
复制
SRS是一个开源的(MIT协议)简单高效的实时视频服务器,支持RTMP、WebRTC、HLS、HTTP-FLV、SRT、MPEG-DASH和GB28181等协议。 SRS媒体服务器和FFmpeg、OBS、VLC、 WebRTC等客户端配合使用,提供流的接收和分发的能力,是一个典型的发布 (推流)和订阅(播放)服务器模型。 SRS支持互联网广泛应用的音视频协议转换,比如可以将RTMP或SRT, 转成HLS或HTTP-FLV或WebRTC等协议

官网地址:SRS

SRS关于rtc-to-rtmp:srs:rtc_to_rtmp

srs编译下载以及运行:srs:源码编译以及运行

webrtc

WebRTC (Web Real-Time Communications) 是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。

关于直播以及协议转换,主要还是设计媒体传输层,webrtc协议媒体传输层使用rtp(Real-time Transport Protocol)。

git地址:webrtc

srs关于rtmp:srs:webrtc

webrtc传输协议:WebRTC学习 实时数据传输网络协议详解(浏览器协议栈、WebRTC传输协议分析)

RTP协议介绍:rtp

rtmp

RTMP(Real Time Messaging Protocol)实时消息传输协议是Adobe公司提出得一种媒体流传输协议,其提供了一个双向得通道消息服务,意图在通信端之间传递带有时间信息得视频、音频和数据消息流,其通过对不同类型得消息分配不同得优先级,进而在网传能力限制下确定各种消息得传输次序。RTMP最早是Adobe公司基于flash player播放器提出得一种音视频封装传输格式,在前期flash盛行时,得到了极其广泛得应用,当前flash基本被废弃,但是RTMP这种协议作为流媒体封装传输得方式,并没有预想中被冷落得情况,相反,在当下直播盛行得阶段,RTMP被经常用来向云端推流得流媒体协议. -- 来自于 流媒体协议之RTMP详解

关于rtmp-url的介绍:srs:rtmp-url

rtmpdump:RTMPDump

流程分析

启动

均采用默认端口以及httpx-static配置https代理。

  1. srs - 启动
代码语言:shell
AI代码解释
复制
./objs/srs -c conf/rtc.conf
#记得 rtc.conf -> rtc_to_rtmp on;
  1. signaling
代码语言:shell
AI代码解释
复制
./objs/signaling
  1. httpx-static
代码语言:shell
AI代码解释
复制
openssl genrsa -out server.key 2048
subj="/C=CN/ST=Beijing/L=Beijing/O=Me/OU=Me/CN=me.org"
openssl req -new -x509 -key server.key -out server.crt -days 365 -subj $subj
sudo ./objs/httpx-static -t 80  -s 443 -r /data/srs/trunk/3rdparty/signaling/www/ -ssk ./objs/server.key -ssc ./objs/server.crt -p http://127.0.0.1:1985/rtc -p http://127.0.0.1:1989/sig

主要流程介绍

在srs的关于rtc_to_rtmp,其实主要是数据的流向,怎么从rtc_server到了rtmp_server,数据格式是怎么转换的,如果从rtp到了flv,有了数据流向的通道,然后就只要梳理,在数据的流转过程中,在什么时间节点进行的数据格式转换,如何转换的。即协议的转换就梳理清楚了。

代码语言:shell
AI代码解释
复制
1.数据连接通道
2.数据格式转换

在srs中,协议的转换主要通过桥接器来进行连接,转换的。

代码语言:wiki
AI代码解释
复制
rtc_server->bridge->live_server(rtmp_server)

更详细的说, live_source 绑定了 brige,然后 rtc_server数据从birge到了live_source再到rtmp_server

代码语言:wiki
AI代码解释
复制
rtc_server->live_source(bridge)->live_server(rtmp_server)

从数据的角度来说,即为

代码语言:wiki
AI代码解释
复制
接受数据-> 读取数据 -> 解析数据 -> 数据格式转换 -> 数据发送

转换到srs中

代码语言:wiki
AI代码解释
复制
rtc_server(UDP:8000)_监听端口->rtc_server_接收数据->rtc_server_解析数据->rtc_server_转换数据->rtc_to_rtmp_brigre_发送数据->live_server->rtmp_client

就主要围绕这几个部分来讲了。

rtp_to_rtmp桥接器建立

代码语言:shell
AI代码解释
复制
srs_main_server.cpp => int main(int argc, char** argv, char** envp) //初始化入口函数
srs_main_server.cpp => err = do_main(argc, argv, envp) //主函数启动
srs_main_server.cpp => srs_error_t do_main(int argc, char** argv, char** envp) // 
srs_main_server.cpp => _srs_config->check_config() //配置文件读取以及检测配置
srs_main_server.cpp => run_directly_or_daemon() //进程是否守护以及后台运行
srs_main_server.cpp => run_in_thread_pool() // 运行线程池
srs_main_server.cpp => _srs_thread_pool->execute("hybrid", run_hybrid_server, (void*)NULL)) //  执行 run_hybrid_server 
srs_main_server.cpp => _srs_hybrid->register_server(new RtcServerAdapter()) //注册rtc服务
srs_main_server.cpp => _srs_hybrid->initialize() / server->initialize() //server初始化
srs_main_server.cpp => _srs_hybrid->run() / SrsHybridServer::run() //server run
srs_app_rtc_server.cpp => RtcServerAdapter::run(SrsWaitGroup* wg) //
srs_app_rtc_server.cpp => SrsRtcServer::listen_api() //进行
srs_app_rtc_conn.cpp => SrsGoApiRtcPublish::serve_http / server_->create_session(ruc, local_sdp, &session) // 进行rtc publish 会话创建
srs_app_rtc_conn.cpp => SrsGoApiRtcPlay::serve_http / server_->create_session(ruc, local_sdp, &session) // 进行rtc play 会话创建
srs_app_rtc_conn.cpp => do_create_session(ruc, local_sdp, session) // 去创建rtc 会话
srs_app_rtc_conn.cpp => SrsRtcConnection::add_publisher()/ session->add_publisher(ruc, local_sdp) //增加一个publisher
srs_app_rtc_conn.cpp => SrsRtcConnection::create_publisher(SrsRequest* req, SrsRtcSourceDescription* stream_desc) //rtc创建 Publisher
srs_app_rtc_conn.cpp => publisher->initialize(req, stream_desc) //publisher 初始化
srs_app_rtc_conn.cpp => SrsRtcPublishStream::initialize(SrsRequest* r, SrsRtcSourceDescription* stream_desc) //rtc 推流初始化
srs_app_rtc_conn.cpp => source_->set_bridge(bridge) //桥接器设置

这是rtc_to_rtmp 桥接器配置的代码:

代码语言:c
代码运行次数:0
运行
AI代码解释
复制
    // Bridge to rtmp
#if defined(SRS_RTC) && defined(SRS_FFMPEG_FIT)
	//读取配置文件看是否开启协议转换设置
    bool rtc_to_rtmp = _srs_config->get_rtc_to_rtmp(req_->vhost); 
	//如果开启协议转换
    if (rtc_to_rtmp) {
        //遍历检查是否有对应的 liveSource,如果没有则创建
        if ((err = _srs_sources->fetch_or_create(r, _srs_hybrid->srs()->instance(), live_source)) != srs_success) {
            return srs_error_wrap(err, "create source");
        }

        // Disable GOP cache for RTC2RTMP bridge, to keep the streams in sync,
        // especially for stream merging.
        live_source->set_cache(false);
		//创建新的桥
        SrsCompositeBridge* bridge = new SrsCompositeBridge();
        //添加 to_rtmp_brige
        bridge->append(new SrsFrameToRtmpBridge(live_source));
		//brige 初始化
        if ((err = bridge->initialize(r)) != srs_success) {
            srs_freep(bridge);
            return srs_error_wrap(err, "create bridge");
        }
		//liveSource与brige进行绑定
        source_->set_bridge(bridge);
    }
#endif

读取UDP数据

epoll模型监听端口,进行数据读取,也属于是传统流程了。

代码语言:txt
AI代码解释
复制
srs_app_listener.cpp => srs_error_t SrsUdpMuxListener::cycle() //监听函数
srs_app_listener.cpp => int nread = skt.recvfrom(SRS_UTIME_NO_TIMEOUT) //读取数据
srs_app_listener.cpp => int SrsUdpMuxSocket::recvfrom(srs_utime_t timeout) //读取数据函数
srs_app_listener.cpp => nread = srs_recvfrom(lfd, buf, nb_buf, (sockaddr*)&from, &fromlen, timeout) //读取数据到 buf[SRS_UDP_MAX_PACKET_SIZE] buf[65535]

解析UDP数据

rtp数据解析:解析流程还是相对复杂的,因为在rtp的传输过程中,包的方式有多种,单包,多个单包组成的一个包,一个大包的分片传输,而且rtp协议也相对复杂,还有rtcp的单独处理之类的。

代码语言:txt
AI代码解释
复制
srs_app_listener.cpp => err = handler->on_udp_packet(&skt); //解析UDP数据入口函数
srs_app_listener.hpp =>  ISrsUdpMuxHandler* handler; //上述handler定义
srs_app_listener.cpp => SrsUdpMuxListener::SrsUdpMuxListener(ISrsUdpMuxHandler* h, std::string i, int p) //handler赋值
srs_app_rtc_server.cpp => SrsUdpMuxListener* listener = new SrsUdpMuxListener(this, ip, port); //SrsUdpMuxListener 实例化,handler入参,值为 this 即 SrsRtcServer
srs_app_rtc_server.hpp => class SrsRtcServer : public ISrsUdpMuxHandler, public ISrsFastTimer, public ISrsReloadHandler //在定义SrsRtcServer的时候可以看到继承了公有类 ISrsUdpMuxHandler,即on_udp_packet()为 virtual srs_error_t on_udp_packet(SrsUdpMuxSocket* skt)
srs_app_rtc_server.hpp => virtual srs_error_t on_udp_packet(SrsUdpMuxSocket* skt) 声明了虚函数 on_udp_packet(),且在CPP做了实现
srs_app_rtc_server.cpp => SrsRtcServer::on_udp_packet(SrsUdpMuxSocket* skt) 
srs_app_rtc_server.cpp => char* data = skt->data(); int size = skt->size(); //获取了 data = buf[SRS_UDP_MAX_PACKET_SIZE] size= nread
srs_app_rtc_server.cpp => bool is_rtp_or_rtcp = srs_is_rtp_or_rtcp((uint8_t*)data, size); //判断是否是 rtp_or_rtcp 数据包
srs_app_rtc_server.cpp => bool is_rtcp = srs_is_rtcp((uint8_t*)data, size); //判断是否是 rtcp 数据包
srs_app_rtc_server.cpp => err = session->udp()->on_rtp(data, size); //如果是rtp的数据包,进行对应的处理
srs_app_rtc_network.cpp => srs_error_t SrsRtcUdpNetwork::on_rtp(char* data, int nb_data) //定位到 rtc_network udp on_rtp()函数
srs_app_rtc_network.cpp => conn_->on_rtp_plaintext(unprotected_buf, nb_unprotected_buf) //进行rtp上下文处理
srs_app_rtc_conn.cpp => publisher->on_rtp_plaintext(data, nb_data) //SrsRtcPublishStream 进行上下文处理
srs_app_rtc_conn.cpp => SrsRtcPublishStream::on_rtp_plaintext(char* plaintext, int nb_plaintext)
srs_app_rtc_conn.cpp => err = do_on_rtp_plaintext(pkt, &buf); //SrsRtcPublishStream 进一步进行 rtp上下文处理
srs_app_rtc_conn.cpp => pkt->decode(buf) //进行SrsRtpPacket数据包解码
srs_kernel_rtc_rtp.cpp => srs_error_t SrsRtpHeader::decode(SrsBuffer* buf) //关键rtp解码函数
srs_kernel_rtc_rtp.cpp => ssrc = buf->read_4bytes() //获取ssrc
srs_kernel_rtc_rtp.cpp => SrsRtcAudioRecvTrack* audio_track = get_audio_track(ssrc) //通过ssrc进行音频实例化
srs_kernel_rtc_rtp.cpp => SrsRtcVideoRecvTrack* video_track = get_video_track(ssrc) //通过ssrc进行视频实例化
srs_kernel_rtc_rtp.cpp => audio_track->on_rtp(source, pkt) //进行音频包处理
srs_kernel_rtc_rtp.cpp => video_track->on_rtp(source, pkt) //进行视频包处理
srs_app_rtc_source.cpp => source->on_rtp(pkt) //SrsRtcSource 进行 SrsRtpPacket* pkt 包处理
srs_app_rtc_source.cpp => frame_builder_->on_rtp(pkt)//SrsRtcFrameBuilder 进行 pkt包处理
srs_app_rtc_source.cpp => SrsRtcFrameBuilder::on_rtp(SrsRtpPacket *pkt)
srs_app_rtc_source.cpp => transcode_audio(pkt) //如果是音频包,进行转码处理,转码为AAC
srs_app_rtc_source.cpp => packet_video(pkt) //如果是视频包,进行视频包处理

音频包处理

处理音频包时,主要做了两部分工作,一部分是 opus转为aac,另一个部分是rtp转为flv,其中转换是使用的ffmpeg api,如果没有对应的了解,还是得需要看看的。

代码语言:txt
AI代码解释
复制
srs_app_rtc_source.cpp => SrsRtcFrameBuilder::transcode_audio(SrsRtpPacket *pkt)//入口函数
srs_app_rtc_source.cpp => if (is_first_audio_) //判断是否为首帧音频帧。进行首帧音频帧处理
    srs_app_rtc_source.cpp => codec_->aac_codec_header(&header, &header_len); //进行首帧header处理
    srs_app_rtc_source.cpp => SrsCommonMessage out_rtmp //定义rtmp msg消息
    srs_app_rtc_source.cpp => packet_aac(&out_rtmp, (char *)header, header_len, ts, is_first_audio_) //进行rtmp消息的格式化
    srs_app_rtc_source.cpp => SrsSharedPtrMessage msg; //定义通用发送的消息包
    srs_app_rtc_source.cpp => msg.create(&out_rtmp) //根据音频包,格式化通用消息包
    srs_app_rtc_source.cpp => bridge_->on_frame(&msg)) //桥接器进行消息消息包处理
srs_app_rtc_source.cpp => SrsRtpRawPayload *payload = dynamic_cast<SrsRtpRawPayload*>(pkt->payload()); //获取pkt内音频包消息
srs_app_rtc_source.cpp => codec_->transcode(&frame, out_pkts) //使用ffmpeg api 进行音频帧转码
srs_app_rtc_source.cpp => std::vector<SrsAudioFrame*>::iterator it = out_pkts.begin(); it != out_pkts.end(); ++it //进行转码后音频帧处理
srs_app_rtc_source.cpp => SrsCommonMessage out_rtmp //定义rtmp msg消息
srs_app_rtc_source.cpp => packet_aac(&out_rtmp, (char *)header, header_len, ts, is_first_audio_) //进行rtmp消息的格式化
srs_app_rtc_source.cpp => SrsSharedPtrMessage msg; //定义通用发送的消息包
srs_app_rtc_source.cpp => msg.create(&out_rtmp) //根据音频包,格式化通用消息包
srs_app_rtc_source.cpp => bridge_->on_frame(&msg)) //桥接器进行消息消息包处理

这一段代码为 srs_error_t SrsRtcFrameBuilder::transcode_audio(SrsRtpPacket *pkt)中比较关键的一部分,为opus转为aac的关键代码。

代码语言:c
代码运行次数:0
运行
AI代码解释
复制
    std::vector<SrsAudioFrame*> out_pkts;
    //获取rtp音频包
    SrsRtpRawPayload *payload = dynamic_cast<SrsRtpRawPayload*>(pkt->payload());
	//定义一个SrsAudioFrame变量
    SrsAudioFrame frame;
	//拷贝音频帧数据
    frame.add_sample(payload->payload, payload->nn_payload);
    frame.dts = ts;
    frame.cts = 0;
	//音频帧转码
    err = codec_->transcode(&frame, out_pkts);
    if (err != srs_success) {
        return err;
    }

//函数为aac 封装为 flv格式,其实主要是头处理。

代码语言:c
代码运行次数:0
运行
AI代码解释
复制
void SrsRtcFrameBuilder::packet_aac(SrsCommonMessage* audio, char* data, int len, uint32_t pts, bool is_header)
{
    //增加2个字节的tag header
    int rtmp_len = len + 2;
    audio->header.initialize_audio(rtmp_len, pts, 1);
    audio->create_payload(rtmp_len);
    SrsBuffer stream(audio->payload, rtmp_len);
    //AAC flag 属性设置
    uint8_t aac_flag = (SrsAudioCodecIdAAC << 4) | (SrsAudioSampleRate44100 << 2) | (SrsAudioSampleBits16bit << 1) | SrsAudioChannelsStereo;
    //写入AAC 详细属性
    stream.write_1bytes(aac_flag);
    //针对header处理
    if (is_header) {
        stream.write_1bytes(0);
    } else {
        stream.write_1bytes(1);
    }
    //写入flv数据
    stream.write_bytes(data, len);
    audio->size = rtmp_len;
}

视频包处理

在处理视频包时,还是相对复杂的,因为音频包单帧数据包比较大,有的是单包,有的是多包,还有的是分片包,所以处理流程也相对复杂。还有 从rtp中提取裸流数据转flv数据格式。要对比如h264的裸流格式有对应了解,如果你要在传输h264裸流是什么格式,在rtp中h264是什么格式,在flv中h264应该怎么处理。

代码语言:shell
AI代码解释
复制
srs_app_rtc_source.cpp => SrsRtcFrameBuilder::packet_video(SrsRtpPacket* src)//入口函数
srs_app_rtc_source.cpp => packet_video_key_frame(pkt) //如果为关键帧 - 进行关键帧处理
    srs_app_rtc_source.cpp => SrsSample* sps = stap_payload ? stap_payload->get_sps() : NULL; //获取关键帧 sps 信息
    srs_app_rtc_source.cpp => SrsSample* pps = stap_payload ? stap_payload->get_pps() : NULL; //获取关键帧 pps 信息
    srs_app_rtc_source.cpp => avc->mux_sequence_header(string(sps->bytes, sps->size), string(pps->bytes, pps->size), sh)) //生成rtmp:flv 关键帧 sps/pps 信息头
    srs_app_rtc_source.cpp => avc->mux_avc2flv(sh, SrsVideoAvcFrameTypeKeyFrame, SrsVideoAvcFrameTraitSequenceHeader, pkt->get_avsync_time(), pkt->get_avsync_time(), &flv, &nb_flv)) != srs_success) // 帧内容转 h264 raw换为flv - h264格式
    srs_app_rtc_source.cpp => SrsSharedPtrMessage msg; \  msg.create(&rtmp) // 生成通用消息包
    srs_app_rtc_source.cpp => bridge_->on_frame(&msg)  //桥接器进行消息处理
srs_app_rtc_source.cpp => packet_video_rtmp(const uint16_t start, const uint16_t end)//普通视频帧以及关键帧共同适用的帧内容处理
srs_app_rtc_source.cpp => nb_payload //传输数据包的大小,要记得是按照flv数据格式来进行计算的 rtp_body_payload + flv_header + flv_tag_header 来处理的
  1. 计算nb_payload大小
代码语言:c
代码运行次数:0
运行
AI代码解释
复制
    int nb_payload = 0;
    int16_t cnt = srs_rtp_seq_distance(start, end) + 1;
    srs_assert(cnt >= 1);

    for (uint16_t i = 0; i < (uint16_t)cnt; ++i) {
        uint16_t sn = start + i;
        uint16_t index = cache_index(sn);
        SrsRtpPacket* pkt = cache_video_pkts_[index].pkt;

        // fix crash when pkt->payload() if pkt is nullptr;
        if (!pkt) continue;

        // calculate nalu len
        SrsRtpFUAPayload2* fua_payload = dynamic_cast<SrsRtpFUAPayload2*>(pkt->payload());
        if (fua_payload && fua_payload->size > 0) {
            if (fua_payload->start) {
                // 在FUA分片传输中,第一片的大小及为包头怎么处理, 1个字节的格式为, 4个字节的整包大小
                nb_payload += 1 + 4;
            }
            nb_payload += fua_payload->size;
            continue;
        }

        SrsRtpSTAPPayload* stap_payload = dynamic_cast<SrsRtpSTAPPayload*>(pkt->payload());
        if (stap_payload) {
            for (int j = 0; j < (int)stap_payload->nalus.size(); ++j) {
                SrsSample* sample = stap_payload->nalus.at(j);
                if (sample->size > 0) {
                    // 单包的处理相对简单, 大小为 包的大小 + 包的数据大小
                    nb_payload += 4 + sample->size;
                }
            }
            continue;
        }

        SrsRtpRawPayload* raw_payload = dynamic_cast<SrsRtpRawPayload*>(pkt->payload());
        if (raw_payload && raw_payload->nn_payload > 0) {
            //多包,在FLV中,每一个子包都为单独的发送的,即每个子包都要 + 子包的大小
            nb_payload += 4 + raw_payload->nn_payload;
            continue;
        }
    }

    if (0 == nb_payload) {
        srs_warn("empty nalu");
        return err;
    }
   //type_codec1 + avc_type + composition time + nalu size + nalu
	//还需要加上TAG header 的大小
    nb_payload += 1 + 1 + 3;

2.合并rtmp包

代码语言:c
代码运行次数:0
运行
AI代码解释
复制
   SrsCommonMessage rtmp;
    SrsRtpPacket* pkt = cache_video_pkts_[cache_index(start)].pkt;
    rtmp.header.initialize_video(nb_payload, pkt->get_avsync_time(), 1);
    rtmp.create_payload(nb_payload);
    rtmp.size = nb_payload;
    SrsBuffer payload(rtmp.payload, rtmp.size);
 //写入包头
    if (pkt->is_keyframe()) {
        payload.write_1bytes(0x17); // type(4 bits): key frame; code(4bits): avc
        rtp_key_frame_ts_ = -1;
    } else {
        payload.write_1bytes(0x27); // type(4 bits): inter frame; code(4bits): avc
    }
    payload.write_1bytes(0x01); // avc_type: nalu
    payload.write_1bytes(0x0);  // composition time
    payload.write_1bytes(0x0);
    payload.write_1bytes(0x0);
	//一个包的大小
    int nalu_len = 0;
    for (uint16_t i = 0; i < (uint16_t)cnt; ++i) {
        uint16_t index = cache_index((start + i));
        SrsRtpPacket* pkt = cache_video_pkts_[index].pkt;

        // fix crash when pkt->payload() if pkt is nullptr;
        if (!pkt) continue;

        cache_video_pkts_[index].in_use = false;
        cache_video_pkts_[index].pkt = NULL;
        cache_video_pkts_[index].ts = 0;
        cache_video_pkts_[index].rtp_ts = 0;
        cache_video_pkts_[index].sn = 0;

        SrsRtpFUAPayload2* fua_payload = dynamic_cast<SrsRtpFUAPayload2*>(pkt->payload());
        if (fua_payload && fua_payload->size > 0) {
            //处理第一个分片包
            if (fua_payload->start) {
                //计算分片包第一个包的大小
                nalu_len = fua_payload->size + 1;
                //skip 4 bytes to write nalu_len future
                //跳过开始的4个字节,在整个包写入完成后,计算出整包大小,然后在写入
                payload.skip(4);
                //写入一个字节的格式位
                payload.write_1bytes(fua_payload->nri | fua_payload->nalu_type);
                //拷贝分片包
                payload.write_bytes(fua_payload->payload, fua_payload->size);
            } else {
                //中间包累加
                nalu_len += fua_payload->size;
                //拷贝中间包
                payload.write_bytes(fua_payload->payload, fua_payload->size);
                //处理最后一个包
                if (fua_payload->end) {
                    //write nalu_len back
                    //回写整包大小 - 指针跳到分片包开始位置
                    payload.skip(-(4 + nalu_len));
                    //写入整包大小
                    payload.write_4bytes(nalu_len);
                    //指针跳到包尾
                    payload.skip(nalu_len);
                }
            }
            srs_freep(pkt);
            continue;
        }

        SrsRtpSTAPPayload* stap_payload = dynamic_cast<SrsRtpSTAPPayload*>(pkt->payload());
        if (stap_payload) {
            for (int j = 0; j < (int)stap_payload->nalus.size(); ++j) {
                SrsSample* sample = stap_payload->nalus.at(j);
                if (sample->size > 0) {
                    //在每一个子包,包头写入子包大小
                    payload.write_4bytes(sample->size);
                    //写入子包数据
                    payload.write_bytes(sample->bytes, sample->size);
                }
            }
            srs_freep(pkt);
            continue;
        }

        SrsRtpRawPayload* raw_payload = dynamic_cast<SrsRtpRawPayload*>(pkt->payload());
        if (raw_payload && raw_payload->nn_payload > 0) {
            //写入单包大小
            payload.write_4bytes(raw_payload->nn_payload);
            //写入单包数据
            payload.write_bytes(raw_payload->payload, raw_payload->nn_payload);
            srs_freep(pkt);
            continue;
        }

        srs_freep(pkt);
    }
	//创建发送包
    SrsSharedPtrMessage msg;
    if ((err = msg.create(&rtmp)) != srs_success) {
        return srs_error_wrap(err, "create message");
    }
	//桥接触处理转换后的包
    if ((err = bridge_->on_frame(&msg)) != srs_success) {
        srs_warn("fail to pack video frame");
    }

桥接器处理

到了桥接器这理,其实处理相对简单一些了,也属于数据转换的最后的节点了,将数据包送到live_souce,并发送给对应的消费者

代码语言:txt
AI代码解释
复制
srs_app_stream_brige.cpp => srs_error_t SrsFrameToRtmpBridge::on_frame(SrsSharedPtrMessage* frame)//入口函数
srs_app_stream_brige.cpp => source_->on_frame(frame) // SrsLiveSource 进行消息包处理
srs_app_source.app => SrsLiveSource::on_frame(SrsSharedPtrMessage* msg) // liveSource 消息包处理入口函数
srs_app_source.app => mix_queue->push(msg->copy()) //将消息push到mix_queque队列
srs_app_source.app => SrsSharedPtrMessage* m = mix_queue->pop() //从队列中取出一个消息
srs_app_source.app => on_audio_imp(m) //如果消息是音频,进行音频消息处理
srs_app_source.app => on_video_imp(m) //如果消息是视频,进行视频消息处理
srs_app_source.app => consumer->enqueue(msg, atc, jitter_algorithm) //消费者消费一条对应的音视频包

其他步骤

发送数据到客户端,就不做说明了。

结束语

其实SRSrtc_to_rtmp的协议转换流程相对复杂一点,但是很具有代表性,我只讲述了一些关键部分,以及一两个关键的函数做了详细说明,但是整个流程链路还是相对齐全的。还是要对各种协议有一定的了解,以及C++的一些基础知识才能更好的理解。可能以后会对SRS的其他部分会继续做一些讲解的,如果精力以及时间允许,或者大家有对srs的哪一些模块有想了解的部分,也可以在评论区说明一下。如果有哪些不对的地方,请大家在评论区直接说明,会做对应的更改。

附录

以下实在学习过程中一些有帮助的资料

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
SRS学习笔记(1)-推拉流代码阅读
SRS是一个用C++开发的开源流媒体集群服务, 能够提供直播点播的功能. github链接: https://github.com/ossrs/srs, 官方架构图如下(3.0版本):
EndevChen
2020/05/24
2K0
SRS学习笔记(1)-推拉流代码阅读
性能优化:SRS为何能做到同类的三倍
性能无疑是服务器的核心能力,几乎每个开源服务器的介绍都是”高性能XXX服务器“。视频服务器由于业务的超复杂度,特别是WebRTC服务器,要做到高性能是非常有挑战的难点。 为何性能很重要?完备的功能需要用性能交换,安全性需要用性能交换,成本需要用性能交换,产品体验需要用性能交换,甚至系统弹性都需要性能交换。有了基础性能,就有了竞争力的资本;基础性能若有问题,举步维艰,想要干点啥都不容易,就像天生羸弱的身子板。 SRS虽然是单进程单线程模型,性能一直都很高,比如: •单进程能跑满千兆或万兆网卡,一般的场景完全能
Winlin
2022/03/18
2.4K0
重磅:SRS 5.0正式支持GB28181
支持GB28181是正确的事情,可能也是困难的事情,因为困难所以有趣。 Introduction 在非常多朋友的努力下,SRS的GB功能不少,详细可以参考srs-gb28181[1]。由于GB和摄像头的复杂性,问题也是不少的,特别是稳定性问题,这也是为什么GB一直迟迟没有进SRS 5.0分支的原因。 现在SRS 5.0已经临近功能封版了,我们增加了几个大的功能和改进,最后一个功能就是在考虑是否支持GB。鉴于GB目前的稳定性表现,肯定不能完全合并过来,是否能有稳定性更高的合并办法? 如果减少功能,当然稳定性就
Winlin
2022/10/09
4.3K0
重磅:SRS 5.0正式支持GB28181
如何在FreeSWITCH中对接SRS
SRS是一个简单、高效的优秀的开源实时音视频服务器,支持 RTMP/WebRTC/HLS/HTTP-FLV/SRT/MPEG-DASH/GB28181、Linux/Windows/macOS、X86_64/ARMv7/AARCH64/M1/RISCV/LOONGARCH/MIPS 等协议和技术。
杜金房
2023/09/03
1.6K1
如何在FreeSWITCH中对接SRS
基于FFmpeg进行RTMP推流(一)简介
这里的bin、include、lib就是我们刚才在FFmpeg下载的相关文件。 src是我们的项目源码目录。 新建Win32控制台应用程序、选择位置、项目名称。注意:去掉“为结局方案创建目录”的勾选
用户2929716
2018/08/23
14.2K0
基于FFmpeg进行RTMP推流(一)简介
视音频数据处理入门:UDP-RTP协议解析「建议收藏」
=====================================================
全栈程序员站长
2022/09/13
1.7K0
视音频数据处理入门:UDP-RTP协议解析「建议收藏」
SRS支持Haivision编码器,及解决HLS纯音频爆音
Haivision是另外一只野鸡编码器,黑爷要支持十万火急,所以看了下,Haivision的协议序列不是FMLE也不是FFMPEG也不是Flash,是自己的一个私有协议: 如果看不太明白,那么下面是个总结。 FFMPEG的消息序列,也就是推流的协议了: C/S: Handshake C: ConnectApp() tcUrl=xxx S: Ack Size 2500,000 S: Set Peer Bandwidth 2500,000 S: Set Chunk Size 60,000 C: Set Chu
Winlin
2022/03/18
6400
FFmpeg avformat_find_stream_info() 函数源码解析
先来看一下 avformat_find_stream_info() 的头文件里的注释对该函数的介绍,本文我们基于 FFmpeg n4.2 版本的源码分析。
JoeyBlue
2021/09/07
2.8K0
QT应用编程: 基于FFMPEG设计的流媒体播放器(播放rtmp视频流)
使用的FFMPEG库版本下载地址:https://download.csdn.net/download/xiaolong1126626497/12304729
DS小龙哥
2022/01/07
4.3K2
QT应用编程: 基于FFMPEG设计的流媒体播放器(播放rtmp视频流)
AVFormatContext封装层:理论与实战
AVFormatContext 是一个贯穿始终的数据结构,很多函数都用到它作为参数,是输入输出相关信息的一个容器,本文讲解 AVFormatContext 的封装层,主要包括两大数据结构:AVInputFormat,AVOutputFormat。
Gnep@97
2023/12/06
5580
AVFormatContext封装层:理论与实战
FFmpeg代码导读——HEVC在RTMP中的扩展
为推进HEVC视频编码格式在直播方案中的落地,经过CDN联盟讨论,并和主流云服务厂商达成一致,规范了HEVC在RTMP/FLV中的扩展,具体修改内容见下。
LiveVideoStack
2021/09/02
1.9K0
FFmpeg代码导读——HEVC在RTMP中的扩展
FLV文件格式官方规范详解
——如果要学习一个新的知识点,官方手册可能是最快的途径。查看网上其他人的总结也许入门更快,但是要准确,深入,完整,还是要看官方手册。 以下内容来自对官方文档Video File Format Specification Version 10的分析总结。过程中借助ffmpeg实际转换了一个flv文件用例研究。 一个FLV文件,每种类型的tag都属于一个流,也就是一个flv文件最多只有一个音频流,一个视频流,不存在多个独立的音视频流在一个文件的情况。(mp4好像是可以的) 另外,FLV文件格式所用的是大端序。
_gongluck
2018/03/08
3.5K0
FLV文件格式官方规范详解
FFMPEG音频视频开发:QT采集摄像头数据帧与声卡音频通过FFMPEG实时推流到RTMP服务器(v1.0)
如果已经完成FFMPEG录制视频保存到本地的功能,完成RTMP推流只需要修改几行代码即可完成。
DS小龙哥
2022/01/12
1.2K0
如何将PCM格式的原始音频采样数据编码为MP3格式或AAC格式的音频文件?
    音频采样格式可以分为packed和planar两类。以packed格式保存的采样数据,各声道间按照采样值交替存储;以planar格式保存的采样数据,各个采样值按照不同声道连续存储
故乡的樱花开了
2023/10/22
7660
SRS 5.0支持WebRTC over TCP
Written by Winlin, 李鹏 在很多网络条件下,WebRTC不适合使用UDP传输,因此支持TCP传输是极其重要的能力;而且SRS支持的是直接TCP传输的方式,避免使用TURN中转带来
Winlin
2022/09/07
2K1
SRS 5.0支持WebRTC over TCP
突破:SRS4支持WebRTC,迎来两位新作者
SRS4支持了WebRTC播放,John(志宏)大神实现了RTC框架,Bepartofyou(B神)实现了aac转opus,刘连响大神主持定义的协议保持RTMP、HLS、FLV、WebRTC的高度一致性,另外Native Demo正在路上,后续还有更多惊喜。 Scenarios SRS支持WebRTC后,将获得下面新的应用场景: 低延迟直播:RTMP延迟在3到5秒,WebRTC可以在1秒之内,可以基于云计算部署比较稳定的低延迟直播服务;也可以接入CDN厂商,目前阿里云和腾讯云CDN都支持了WebRTC直播
Winlin
2022/03/18
2.6K0
RTMP协议
与 HTTP(超文本传输协议)同样是一个基于 TCP 的 Real Time Messaging Protocol(实时消息传输协议)。由 Adobe Systems 公司为 Flash 播放器和服务器之间音频、视频和数据传输开发的一种开放协议 。在国内被广泛的应用于直播 领域。HTTP 默认端口为 80,RTMP 则为 1935。 我们通过阅读 Adobe 的协议规范,通过与服务器建立 TCP 通信,根据协议格式生成与解析数据即可使用 RTMP 进行 直播。当然我们也可以借助一些实现了 RTMP 协议的开源库来完成这一过程。
小木箱
2020/11/24
1.9K0
FFMPEG音视频开发指南(一)
FFmpeg是一款开源软件,用于生成处理多媒体数据的各类库和程序。FFmpeg可以转码、处理视频和图 片(调整视频、图片大小,去噪等)、打包、传输及播放视频。作为最受欢迎的视频和图像处理软件, 早已经被各行各业的不同公司所广泛使用。
DS小龙哥
2022/10/06
3.3K0
FFMPEG音视频开发指南(一)
ffplay播放器原理剖析
*****************************************************************************
全栈程序员站长
2022/09/07
8380
开源流媒体服务器SRS学习笔记(1) - 安装、推流、拉流
SRS(Simple RTMP Server) 是国人写的一款非常优秀的开源流媒体服务器软件,可用于直播/录播/视频客服等多种场景,其定位是运营级的互联网直播服务器集群。
菩提树下的杨过
2019/09/12
11.4K0
推荐阅读
相关推荐
SRS学习笔记(1)-推拉流代码阅读
更多 >
LV.2
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验