ZLMediaKit中 RtmpSession::onCmd_publish分析

7 篇文章 0 订阅
5 篇文章 0 订阅
本文详细介绍了RTMP推流过程中onCmd_publish命令的处理,包括消息格式、参数解析、鉴权逻辑以及如何响应客户端。在接收到publish消息后,服务器会进行一系列操作,如设置媒体信息、调用PublishAuthInvoker进行鉴权,并根据配置或外部hook API决定是否允许HLS和MP4转协议。同时,当接收到setDataFrame消息时,会解析onMetaData以获取流元数据。
摘要由CSDN通过智能技术生成

RtmpSession::onCmd_publish分析

  • RTMP推流消息中body的格式
格式commandName(str)transactionID(num)commandObject(obj)publishName(str)publishType(str)
例如“publish”5null“rff?123”live/record/append/record
void RtmpSession::onCmd_publish(AMFDecoder &dec) {
    std::shared_ptr<Ticker> ticker(new Ticker);
    weak_ptr<RtmpSession> weak_self = dynamic_pointer_cast<RtmpSession>(shared_from_this());
    //在结束的时候自动调用析构函数lambda
    std::shared_ptr<onceToken> pToken(new onceToken(nullptr,[ticker,weak_self](){
        auto strong_self = weak_self.lock();
        if(strong_self){
            DebugP(strong_self.get()) << "publish 回复时间:" << ticker->elapsedTime() << "ms";
        }
    }));
    dec.load<AMFValue>();/* NULL */
    _media_info.parse(_tc_url + "/" + getStreamId(dec.load<std::string>()));
    _media_info._schema = RTMP_SCHEMA;

    //  一个回复的lambda
    auto on_res = [this,pToken](const string &err, bool enableHls, bool enableMP4){
        auto src = dynamic_pointer_cast<RtmpMediaSource>(MediaSource::find(RTMP_SCHEMA,
                                                                           _media_info._vhost,
                                                                           _media_info._app,
                                                                           _media_info._streamid));

        bool auth_success = err.empty();
        bool ok = (!src && !_publisher_src && auth_success);

         InfoL<<"enter on_res rtmp "<<_media_info._vhost<<"  "<<_media_info._app<<"  "<<_media_info._streamid<<" auth "<<auth_success<<" ok "<<ok;
        AMFValue status(AMF_OBJECT);
        status.set("level", ok ? "status" : "error");
        status.set("code", ok ? "NetStream.Publish.Start" : (auth_success ? "NetStream.Publish.BadName" : "NetStream.Publish.BadAuth"));
        status.set("description", ok ? "Started publishing stream." : (auth_success ? "Already publishing." : err.data()));
        status.set("clientid", "0");
        sendReply("onStatus", nullptr, status);
        if (!ok) {
            string errMsg = StrPrinter << (auth_success ? "already publishing:" : err.data()) << " "
                                       << _media_info._vhost << " "
                                       << _media_info._app << " "
                                       << _media_info._streamid;
            shutdown(SockException(Err_shutdown,errMsg));
            return;
        }


        //在收到推流命令的时候建立_publisher_src
        _publisher_src.reset(new RtmpMediaSourceImp(_media_info._vhost, _media_info._app, _media_info._streamid));
       
        _publisher_src->setListener(dynamic_pointer_cast<MediaSourceEvent>(shared_from_this()));
        //设置转协议
        
        _publisher_src->setProtocolTranslation(enableHls, enableMP4);
       
        setSocketFlags();
        
    };

    if(_media_info._app.empty() || _media_info._streamid.empty()){
        //不允许莫名其妙的推流url
        on_res("rtmp推流url非法", false, false);
        return;
    }

    //invoker("", 1, 0);
    Broadcast::PublishAuthInvoker invoker = [weak_self, on_res, pToken](const string &err, bool enableHls, bool enableMP4) {
        auto strongSelf = weak_self.lock();
        if (!strongSelf) {
            return;
        }

        //将任务丢进线程中执行 任务就是下述函数参数lambda 实际就是回复
        strongSelf->async([weak_self, on_res, err, pToken, enableHls, enableMP4]() {
            auto strongSelf = weak_self.lock();
            if (!strongSelf) {
                return;
            }
            on_res(err, enableHls, enableMP4);
        });
    };

    //提交推流事件 其中事件的添加在函数installWebHook()中
    auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, _media_info, invoker, static_cast<SockInfo &>(*this));
    InfoL<<"flag "<<flag;

    if(!flag){
        //该事件无人监听,默认鉴权成功,
        //即在eventMap中没有添加该事件 例如事件 Broadcast::kBroadcastMediaPublish
        // NoticeCenter::Instance().addListener
        GET_CONFIG(bool,to_hls,General::kPublishToHls);
        GET_CONFIG(bool,to_mp4,General::kPublishToMP4);
        on_res("", to_hls, to_mp4);
    }
}
  • 上述提交事件之后,实际上会调用下述函数的第三个参数的lambda,上述参数_media_info, invoker, static_cast<SockInfo &>(*this)实际上是下述的BroadcastMediaPublishArgs,就是参数,下面的invoker函数就是上述函数中的 lambda 表达式 invoker
    NoticeCenter::Instance().addListener(nullptr,Broadcast::kBroadcastMediaPublish,[](BroadcastMediaPublishArgs){
        GET_CONFIG(string,hook_publish,Hook::kOnPublish);
        GET_CONFIG(bool,toHls,General::kPublishToHls);
        GET_CONFIG(bool,toMP4,General::kPublishToMP4);

        if(!hook_enable || args._param_strs == hook_adminparams || hook_publish.empty() || sender.get_peer_ip() == "127.0.0.1"){
             InfoL<<"enter "<<hook_enable<<" toHls "<<toHls<<" toMP4 "<<toMP4<<Broadcast::kBroadcastMediaPublish<<" return";
              //如果没有鉴权,执行invoker就返回
            invoker("", toHls, toMP4);
            
            return;
        }

        InfoL<<"hook publish "<<hook_publish<<" to_Hls "<<toHls<<" to_MP4 "<<toMP4;

        //异步执行该hook api,防止阻塞NoticeCenter
        auto body = make_json(args);
        body["ip"] = sender.get_peer_ip();
        body["port"] = sender.get_peer_port();
        body["id"] = sender.getIdentifier();
        //执行hook 鉴权
        do_http_hook(hook_publish,body,[invoker](const Value &obj,const string &err){
            if(err.empty()){
                //推流鉴权成功
                bool enableHls = toHls;
                bool enableMP4 = toMP4;

                //兼容用户不传递enableHls、enableMP4参数
                if (obj.isMember("enableHls")) {
                    enableHls = obj["enableHls"].asBool();
                }
                if (obj.isMember("enableMP4")) {
                    enableMP4 = obj["enableMP4"].asBool();
                }
                invoker(err, enableHls, enableMP4);
            } else {
                //推流鉴权失败
                invoker(err, false, false);
            }

        });
    });
  • 在收到客户端的publish消息之后,会再次收到客户端的setDataFrame的消息,其格式为
格式setDataFrame(AMF0)onMetaData(AMF0)property(ECMA Array)
例如“@setDataFrame”“onMetaData”数组
  • ECMA Arrya本身的类型为0x08,其后紧跟着的是数组元素的个数,此处为20,占用4个字节表示,在之后便是具体的数组中的每一个元素。而数组中的每一个元素的具体编码方式又是遵循AMF0编码标准的。此例中,共表示了20个属性。包含文件大小,视频宽度和高度,视频编码codec_id,帧率信息,比特率信息,音频的codec_id,音频采样率,channel数量等,最后还有一个encoder字段来表示编码器。
  • 在ZLMediaKit中其接收setDataFrame的消息为,其中主要的处理在setMetaData中,主要就是将数组解析出来
    在这里插入图片描述
  • 参考以下博客:
    链接: https://blog.csdn.net/mlfcjob/article/details/106221645.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值