直播回源优化
该文档主要考虑的是针对需要回源时秒开的优化。
现状
当前 streamd server 在收到拉流请求时,若需要回源,在回源后 streamd 下发给 Player 数据时使用的是帧级别的下发,特别是在接收首个关键帧时,只有接收到完整的视频帧以后,方才开始下发给 Player,这样无形中增加了一定的数据接收+组帧的耗时,通过当前 superset 的数据分析,在需要回源的 case 中, streamd 等待接收首个视频关键帧的时长几乎全部超过 100ms 以上,这便为该次播放请求增加了固定时间的延迟,优化空间明显。
优化内容
针对当前数据转发的模式,将帧级别的转发模式优化为 Slice 级别的转发模式。
流程图

优点
- 加快秒开速度,减少回源阶段 Block 等待时间,初步估计对回源 case 的秒开优化在百毫秒以上。
- Streamd 下发数据时基于 Slice 进行下发,数据发送更加平滑,减少 Frame 级别的下发带来的网络拥塞,有益于卡顿率的降低。
缺点
Streamd 服务在各协议的封装性上需要结构性的调整,数据流程改动较大,需要考虑不同 Protocol/Muxer/Demuxer 之间的兼容性,可能难以实现所有 Protocol/Muxer/Demuxer 之间的分片转发,但依然有很大的优化场景需求。 最理想的情况为:源站提供 FLV 的数据拉流协议,而 Streamd 也是用 FLV 的回源拉流协议,Streamd 在接收端最多需要处理 FLV Header Tag 的部分信息,不阻塞 Slice 的下发。
FLV 格式

SRS 现状
那么问题来了, srs 是否已经考虑到这一点的优化空间呢?
对比查看 srs 是否使用的这种透明转发模式,还是帧转发模式? 这里记录的为使用 HTTP-FLV 回源协议的代码流程。
srs 需要回源时建议优先使用 Edge 模式,回源时,内部首先创建一个 SrsEdgeFlvUpstream 实例,用于向源站发起连接,并用于实时的接收、处理数据,代码文件:srs_app_edge.cpp。
通过 connect ==> do_connect 发起向源站的连接请求,判定 http response status code 非 404、非 302 后,开始接收、处理媒体数据。
事件轮询在 SrsEdgeIngester::ingest() 中,同步模式不断循环执行 SrsEdgeFlvUpstream::recv_message() 接收完整的数据,而后通过 SrsEdgeIngester::process_publish_message 将数据流转到后序处理的模块,比如 Source、Consumer 等,但在这之前重点是确认在 recv_message 中是否会 block 等待完整的一个 tag data。
srs_error_t SrsEdgeFlvUpstream::recv_message(SrsCommonMessage** pmsg)
{
srs_error_t err = srs_success;
char type;
int32_t size;
uint32_t time;
if ((err = decoder_->read_tag_header(&type, &size, &time)) != srs_success) {
return srs_error_wrap(err, "read tag header");
}
char* data = new char[size];
if ((err = decoder_->read_tag_data(data, size)) != srs_success) {
srs_freepa(data);
return srs_error_wrap(err, "read tag data");
}
char pps[4];
if ((err = decoder_->read_previous_tag_size(pps)) != srs_success) {
return srs_error_wrap(err, "read pts");
}
int stream_id = 1;
SrsCommonMessage* msg = NULL;
if ((err = srs_rtmp_create_msg(type, time, data, size, stream_id, &msg)) != srs_success) {
return srs_error_wrap(err, "create message");
}
*pmsg = msg;
return err;
}
可以看到首先读取 tag_header,然后从header 中获取 tag size 开始读取指定size 的tag data,SrsFlvDecoder::read_tag_data() ==» SrsHttpFileReader:read()
可以看到在 SrsHttpFileReader::read(void* buf, size_t count, ssize_t* pnread) 中其会持续的尝试去接收指定 size 大小的数据,直到接收完整,或者 io 报错,源码如下:
srs_error_t SrsHttpFileReader::read(void* buf, size_t count, ssize_t* pnread)
{
srs_error_t err = srs_success;
if (http->eof()) {
return srs_error_new(ERROR_HTTP_REQUEST_EOF, "EOF");
}
int total_read = 0;
while (total_read < (int)count) {
ssize_t nread = 0;
if ((err = http->read((char*)buf + total_read, (int)(count - total_read), &nread)) != srs_success) {
return srs_error_wrap(err, "read");
}
if (nread == 0) {
err = srs_error_new(ERROR_HTTP_REQUEST_EOF, "EOF");
break;
}
srs_assert(nread);
total_read += (int)nread;
}
if (pnread) {
*pnread = total_read;
}
return err;
}
结论: 当前 srs 使用的是帧级别的转发模式,没有实现分片的透明转发。
版权声明: 如无特别声明,本文版权归 Mr Chen 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:《 直播回源优化 》
本文链接:https://gbcpp.github.io/notes/optimize-back-to-source.html
本文最后一次更新为 天前,文章中的某些内容可能已过时!