ZLMediaKit中RTMP消息解析
-
在接收到完整的Chunck之后,Chunck的data部分使用下述函数进行解析,一般的消息会在当前函数进行解析完成,比如
MSG_SET_CHUNK
,否则会进入onRtmpChunk
进一步解析 -
从目前的测试情况来看,ZLMediaKit中的server端接收的消息顺序是
MSG_SET_CHUNCK connect releaseStream(不响应) FCPublish(不响应) createStream _checkbw(不响应) publish 其中上述消息就 MSG_SET_CHUNK 是在handle_chunk中处理的,其他的都是在onRtmpChunk中处理的
void RtmpProtocol::handle_chunk(RtmpPacket::Ptr packet) {
auto &chunk_data = *packet;
//std::cout<< "chunk_data.type_id " << chunk_data.type_id;
switch (chunk_data.type_id) {
case MSG_ACK: {
if (chunk_data.buffer.size() < 4) {
throw std::runtime_error("MSG_ACK: Not enough data");
}
//auto bytePeerRecv = load_be32(&chunk_data.buffer[0]);
//TraceL << "MSG_ACK:" << bytePeerRecv;
break;
}
case MSG_SET_CHUNK: {
if (chunk_data.buffer.size() < 4) {
throw std::runtime_error("MSG_SET_CHUNK :Not enough data");
}
_chunk_size_in = load_be32(&chunk_data.buffer[0]);
TraceL << "MSG_SET_CHUNK:" << _chunk_size_in;
break;
}
case MSG_USER_CONTROL: {
//user control message
if (chunk_data.buffer.size() < 2) {
throw std::runtime_error("MSG_USER_CONTROL: Not enough data.");
}
uint16_t event_type = load_be16(&chunk_data.buffer[0]);
chunk_data.buffer.erase(0, 2);
switch (event_type) {
case CONTROL_PING_REQUEST: {
if (chunk_data.buffer.size() < 4) {
throw std::runtime_error("CONTROL_PING_REQUEST: Not enough data.");
}
uint32_t timeStamp = load_be32(&chunk_data.buffer[0]);
//TraceL << "CONTROL_PING_REQUEST:" << time_stamp;
sendUserControl(CONTROL_PING_RESPONSE, timeStamp);
break;
}
case CONTROL_PING_RESPONSE: {
if (chunk_data.buffer.size() < 4) {
throw std::runtime_error("CONTROL_PING_RESPONSE: Not enough data.");
}
//uint32_t time_stamp = load_be32(&chunk_data.buffer[0]);
//TraceL << "CONTROL_PING_RESPONSE:" << time_stamp;
break;
}
case CONTROL_STREAM_BEGIN: {
//开始播放
if (chunk_data.buffer.size() < 4) {
throw std::runtime_error("CONTROL_STREAM_BEGIN: Not enough data.");
}
uint32_t stream_index = load_be32(&chunk_data.buffer[0]);
onStreamBegin(stream_index);
TraceL << "CONTROL_STREAM_BEGIN:" << stream_index;
break;
}
case CONTROL_STREAM_EOF: {
//暂停
if (chunk_data.buffer.size() < 4) {
throw std::runtime_error("CONTROL_STREAM_EOF: Not enough data.");
}
uint32_t stream_index = load_be32(&chunk_data.buffer[0]);
onStreamEof(stream_index);
TraceL << "CONTROL_STREAM_EOF:" << stream_index;
break;
}
case CONTROL_STREAM_DRY: {
//停止播放
if (chunk_data.buffer.size() < 4) {
throw std::runtime_error("CONTROL_STREAM_DRY: Not enough data.");
}
uint32_t stream_index = load_be32(&chunk_data.buffer[0]);
onStreamDry(stream_index);
TraceL << "CONTROL_STREAM_DRY:" << stream_index;
break;
}
default: /*WarnL << "unhandled user control:" << event_type; */ break;
}
break;
}
case MSG_WIN_SIZE: {
_windows_size = load_be32(&chunk_data.buffer[0]);
TraceL << "MSG_WIN_SIZE:" << _windows_size;
break;
}
case MSG_SET_PEER_BW: {
_bandwidth = load_be32(&chunk_data.buffer[0]);
_band_limit_type = chunk_data.buffer[4];
TraceL << "MSG_SET_PEER_BW:" << _windows_size;
break;
}
case MSG_AGGREGATE: {
auto ptr = (uint8_t *) chunk_data.buffer.data();
auto ptr_tail = ptr + chunk_data.buffer.size();
while (ptr + 8 + 3 < ptr_tail) {
auto type = *ptr;
ptr += 1;
auto size = load_be24(ptr);
ptr += 3;
auto ts = load_be24(ptr);
ptr += 3;
ts |= (*ptr << 24);
ptr += 1;
ptr += 3;
//参考FFmpeg多拷贝了4个字节
size += 4;
if (ptr + size > ptr_tail) {
break;
}
auto sub_packet_ptr = RtmpPacket::create();
auto &sub_packet = *sub_packet_ptr;
sub_packet.buffer.assign((char *)ptr, size);
sub_packet.type_id = type;
sub_packet.body_size = size;
sub_packet.time_stamp = ts;
sub_packet.stream_index = chunk_data.stream_index;
sub_packet.chunk_id = chunk_data.chunk_id;
handle_chunk(std::move(sub_packet_ptr));
ptr += size;
}
break;
}
default: onRtmpChunk(std::move(packet)); break;
}
}
- 收到的
connect
消息的消息类型是MSG_CMD
,下述代码将消息内容packet
存入AMFDecoder
,方便后续解析使用,然后就进入onProcessCmd
函数调用onCmd_connect
- 关于
MSG_CMD
(就是AMF0
)消息,其主要的结构和 body 部分的具体解析如下图所示:
void RtmpSession::onRtmpChunk(RtmpPacket::Ptr packet) {
auto &chunk_data = *packet;
switch (chunk_data.type_id) {
case MSG_CMD:
case MSG_CMD3: {
//MSG_CMD AMF0 1 个 byte 的 amf type,2 个 bytes 的字符长度,和 N 个 bytes 的数据
InfoL<<"RtmpSession::onRtmpChunk cmd ";
AMFDecoder dec(chunk_data.buffer, chunk_data.type_id == MSG_CMD3 ? 3 : 0);
onProcessCmd(dec);
break;
}
case MSG_DATA:
case MSG_DATA3: {
InfoL<<"RtmpSession::onRtmpChunk msg ";
AMFDecoder dec(chunk_data.buffer, chunk_data.type_id == MSG_DATA3 ? 3 : 0);
std::string type = dec.load<std::string>();
if (type == "@setDataFrame") {
setMetaData(dec);
} else {
TraceP(this) << "unknown notify:" << type;
}
break;
}
case MSG_AUDIO:
case MSG_VIDEO: {
if (!_publisher_src) {
WarnL << "Not a rtmp publisher!";
return;
}
GET_CONFIG(bool, rtmp_modify_stamp, Rtmp::kModifyStamp);
if (rtmp_modify_stamp) {
int64_t dts_out;
_stamp[chunk_data.type_id % 2].revise(chunk_data.time_stamp, chunk_data.time_stamp, dts_out, dts_out, true);
chunk_data.time_stamp = (uint32_t)dts_out;
}
if (!_set_meta_data) {
_set_meta_data = true;
_publisher_src->setMetaData(_publisher_metadata ? _publisher_metadata : TitleMeta().getMetadata());
}
_publisher_src->onWrite(std::move(packet));
break;
}
default:
WarnP(this) << "unhandled message:" << (int) chunk_data.type_id << hexdump(chunk_data.buffer.data(), chunk_data.buffer.size());
break;
}
}
void RtmpSession::onProcessCmd(AMFDecoder &dec) {
//定义一个类中的函数指针
typedef void (RtmpSession::*cmd_function)(AMFDecoder &dec);
//定义一个以string为key,类函数指针的无序map,static 只初始化一次
static unordered_map<string, cmd_function> s_cmd_functions;
static onceToken token([]() {
s_cmd_functions.emplace("connect", &RtmpSession::onCmd_connect);
s_cmd_functions.emplace("createStream", &RtmpSession::onCmd_createStream);
s_cmd_functions.emplace("publish", &RtmpSession::onCmd_publish);
s_cmd_functions.emplace("deleteStream", &RtmpSession::onCmd_deleteStream);
s_cmd_functions.emplace("play", &RtmpSession::onCmd_play);
s_cmd_functions.emplace("play2", &RtmpSession::onCmd_play2);
s_cmd_functions.emplace("seek", &RtmpSession::onCmd_seek);
s_cmd_functions.emplace("pause", &RtmpSession::onCmd_pause);
});
std::string method = dec.load<std::string>();
auto it = s_cmd_functions.find(method);
if (it == s_cmd_functions.end()) {
TraceP(this) << "can not support cmd:" << method;
return;
}
_recv_req_id = dec.load<double>();
auto fun = it->second;
InfoL<<"RtmpSession::onProcessCmd "<<method;
(this->*fun)(dec);
}
- 最终调用的
onCmd_connect
会存入相关的信息(根据请求的body中的数据获得),然后回复
void RtmpSession::onCmd_connect(AMFDecoder &dec) {
auto params = dec.load<AMFValue>();
///set chunk size
sendChunkSize(60000);
window Acknowledgement size/
sendAcknowledgementSize(5000000);
///set peerBandwidth
sendPeerBandwidth(5000000);
_media_info._app = params["app"].as_string();
_tc_url = params["tcUrl"].as_string();
if(_tc_url.empty()){
//defaultVhost:默认vhost
_tc_url = string(RTMP_SCHEMA) + "://" + DEFAULT_VHOST + "/" + _media_info._app;
} else {
auto pos = _tc_url.rfind('?');
if (pos != string::npos) {
//tc_url 中可能包含?以及参数,参见issue: #692
_tc_url = _tc_url.substr(0, pos);
}
}
bool ok = true; //(app == APP_NAME);
AMFValue version(AMF_OBJECT);
version.set("fmsVer", "FMS/3,0,1,123");
version.set("capabilities", 31.0);
AMFValue status(AMF_OBJECT);
status.set("level", ok ? "status" : "error");
status.set("code", ok ? "NetConnection.Connect.Success" : "NetConnection.Connect.InvalidApp");
status.set("description", ok ? "Connection succeeded." : "InvalidApp.");
status.set("objectEncoding", params["objectEncoding"]);
sendReply(ok ? "_result" : "_error", version, status);
if (!ok) {
throw std::runtime_error("Unsupported application: " + _media_info._app);
}
AMFEncoder invoke;
invoke << "onBWDone" << 0.0 << nullptr;
sendResponse(MSG_CMD, invoke.data());
}