RtmpSession::onCmd_publish分析
格式 | commandName(str) | transactionID(num) | commandObject(obj) | publishName(str) | publishType(str) |
---|
例如 | “publish” | 5 | null | “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());
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>();
_media_info.parse(_tc_url + "/" + getStreamId(dec.load<std::string>()));
_media_info._schema = RTMP_SCHEMA;
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.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()){
on_res("rtmp推流url非法", false, false);
return;
}
Broadcast::PublishAuthInvoker invoker = [weak_self, on_res, pToken](const string &err, bool enableHls, bool enableMP4) {
auto strongSelf = weak_self.lock();
if (!strongSelf) {
return;
}
strongSelf->async([weak_self, on_res, err, pToken, enableHls, enableMP4]() {
auto strongSelf = weak_self.lock();
if (!strongSelf) {
return;
}
on_res(err, enableHls, enableMP4);
});
};
auto flag = NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastMediaPublish, _media_info, invoker, static_cast<SockInfo &>(*this));
InfoL<<"flag "<<flag;
if(!flag){
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("", toHls, toMP4);
return;
}
InfoL<<"hook publish "<<hook_publish<<" to_Hls "<<toHls<<" to_MP4 "<<toMP4;
auto body = make_json(args);
body["ip"] = sender.get_peer_ip();
body["port"] = sender.get_peer_port();
body["id"] = sender.getIdentifier();
do_http_hook(hook_publish,body,[invoker](const Value &obj,const string &err){
if(err.empty()){
bool enableHls = toHls;
bool enableMP4 = toMP4;
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.