SRS之SrsHlsCache::reap_segment详解

1. 是否可切片的检测

首先在调用 SrsHlsCache::reap_segment 函数进行切片时,针对音频或视频,都会有一个函数来进行检测当前片的时长是否符合所要求的时长。

对于音频,会调用 SrsHlsMuxer::is_segment_absolutely_overflow 函数进行检测,如下:

bool SrsHlsMuxer::is_segment_absolutely_overflow()
{
    srs_assert(current);

    /* 若当前片的时长小于 200 ms,则直接返回 false,即不允许切片 */
    /* to prevent very small segment. */
    if (current->duration * 1000 <
        2 * SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) {
        return false;
    }

    /* 若没有配置 hls_ts_floor,则默认为 0 */
    /* use N% deviation, to smoother. */
    double deviation = hls_ts_floor ?
        SRS_HLS_FLOOR_REAP_PERCENT * deviation_ts * hls_fragment : 0.0;

    /* hls_aof_ratio 默认为 2.0, 而 hls_fragment 默认为 10s,
     * 则需要当前片的时长大于 20s 时,才会进行切片 */
    return current->duration >= hls_aof_ratio * hls_fragment + deviation;
}

对于视频,则会调用 SrsHlsMuxer::is_segment_overflow 函数进行检测,如下:

bool SrsHlsMuxer::is_segment_overflow()
{
    srs_assert(current);

    /* 同样,若小于 200ms,直接不允许切片 */
    /* to prevent very small segment. */
    if (current->duration * 1000 <
        2 * SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) {
        return false;
    }

    /* use N% deviation, to smoother. */
    double deviation = hls_ts_floor?
        SRS_HLS_FLOOR_REAP_PERCENT * deviation_ts * hls_fragment : 0.0;

    /* 假设 hls_fragment 配置为 10s,因此若当前片的时长
     * 大于 10s 即可进行切片 */
    return current->duration >= hls_fragment + deviation;
}
  • 当调用以上两个函数,检测到满足其中一个时,则会调用 SrsHlsCache::reap_segment 函数对当前片进行切片。

2. SrsHlsCache::reap_segment

/*
 * reopen the muxer for a new hls segment,
 * close current segment, open a new segment,
 * then write the key frame to the new segment.
 * so, user must reap_segment then flush_video to hls muxer.
 */
int SrsHlsCache::reap_segment(string log_desc, SrsHlsMuxer* muxer,
    int64_t segment_start_dts)
{
    int ret = ERROR_SUCCESS;

    /* TODO: flush audio before or after segment?
     * TODO: fresh segment begin with audio or video? */

    /* close current ts. */
    /* 该函数会构造一个 M3u8 文件,将当前符合时长的所有 ts 文件
     * 更新到该文件的 Playlist 列表中,以便客户端可以进行播放
     * 同时会释放当前片(即 current)的所有资源 */
    if ((ret = muxer->segment_close(log_desc)) != ERROR_SUCCESS) {
        srs_error("m3u8 muxer close segment failed. ret=%d", ret);
        return ret;
    }

    /* open new ts. */
    if ((ret = muxer->segment_open(segment_start_dts)) != ERROR_SUCCESS) {
        srs_error("m3u8 muxer open segment failed. ret=%d", ret);
        return ret;
    }

    /* segment open, flush video first. */
    if ((ret = muxer->flush_video(cache)) != ERROR_SUCCESS) {
        srs_error("m3u8 muxer flush video failed. ret=%d", ret);
        return ret;
    }

    /* segment open, flush the audio.
     * @see: ngx_rtmp_hls_open_fragment
     * start fragment with audio to make iPhone happy */
    if ((ret = muxer->flush_audio(cache)) != ERROR_SUCCESS) {
        srs_error("m3u8 muxer flush audio failed. ret=%d", ret);
        return ret;
    }

    return ret;
}
  • 首先调用 SrsHlsMuxer::segment_close 函数关闭当前的 ts。

3. SrsHlsMuxer::segment_close

/**
 * close segment(ts).
 * @param log_desc the description for log.
 */
int SrsHlsMuxer::segment_close(string log_desc)
{
    int ret = ERROR_SUCCESS;

    if (!current) {
        srs_warn("ignore the segment close, for segment is not open.");
        return ret;
    }

    /* when close current segment, the current segment
     * must not be NULL. */
    srs_assert(current);

    /* assert segment duplicate. */
    std::vector<SrsHlsSegment*>::iterator it;
    it = std::find(segments.begin(), segments.end(), current);
    srs_assert(it == segments.end());

    /* valid, add to segments if segment duration is ok
     * when too small, it maybe not enough data to play.
     * when too large, it maybe timestamp corrupt.
     * make the segment more acceptable, when in [min, max_td * 2], it‘s ok */
    if (current->duration * 1000 >= SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS &&
        (int)current->duration <= max_td * 2) {
        /* 若当前片可以被切片时,将该片保存到 segments vector 容器中
         * segments: m3u8 segments */
        segments.push_back(current);

        /* 这里两个调用暂时忽略 */
        /* use async to call the http hooks, for it will cause thread switch. */
        if ((ret = async->execute(new SrsDvrAsyncCallOnHls(
            _srs_context->get_id(), req,
            current->full_path, current->uri, m3u8, m3u8_url,
            current->sequence_no, current->duration))) != ERROR_SUCCESS)
        {
            return ret;
        }

        /* use async to call the http hooks, for it will cause thread switch. */
        if ((ret = async->execute(new SrsDvrAsyncCallOnHlsNotify(_srs_context->get_id(),
            req, current->uri))) != ERROR_SUCCESS) {
            return ret;
        }

        /* close the muxer of finished segment. */
        srs_freep(current->muxer);
        /* full_path: ./objs/nginx/html/live/livestream-0.ts */
        std::string full_path = current->full_path;
        current = NULL;

        /* rename from tmp to real path */
        std::string tmp_file = full_path + ".tmp";
        /* 将 ./objs/nginx/html/live/livestream-0.ts.tmp 文件重命名为
         * ./objs/nginx/html/live/livestream-0.ts */
        if (should_write_file && rename(tmp_file.c_str(), full_path.c_str()) < 0) {
            ret = ERROR_HLS_WRITE_FAILED;
            srs_error("rename ts file failed, %s => %s. ret=%d",
                tmp_file.c_str(), full_path.c_str(), ret);
            return ret;
        }
    } else {
        /* reuse current segment index. */
        _sequence_no--;

        /* rename from tmp to real path */
        std::string tmp_file = current->full_path + ".tmp";
        if (should_write_file) {
            if (unlink(tmp_file.c_str()) < 0) {
                srs_warn("ignore unlink path failed, file=%s.", tmp_file.c_str());
            }
        }

        srs_freep(current);
    }

    /* the segments to remove */
    std::vector<SrsHlsSegment*> segment_to_remove;

    /* shrink the segments. */
    double duration  = 0;
    int remove_index = -1;
    for (int i = (int)segments.size() - 1; i >= 0; i--) {
        SrsHlsSegment* segment = segments[i];
        /* 统计 segmtns 容器保存的所有已经切片的片的总时长 */
        duration += segment->duration;

        /* 若所有片的总时长超过了 hls 窗口大小的限制,
         * 则丢弃第一个 m3u8 中的第一个切片,直到 ts 的总时长
         * 在这个配置项范围之内。 */
        if ((int)duration > hls_window) {
            /* 记录当前需要移除片的个数 */
            remove_index = i;
            break;
        }
    }
    /* 遍历 segments 前 remove_index 个片,逐一将 segments 容器中
     * 前 remove_index 个片移除 */
    for (int i = 0; i < remove_index && !segments.empty(); i++) {
        SrsHlsSegment* segment = *segments.begin();
        segments.erase(segments.begin());
        /* 将所有移除的片临时保存到该容器中 */
        segment_ro_remove.push_back(segment);
    }

    /* refresh the m3u8, donot contains the removed ts */
    ret = refresh_m3u8();

    /* remove the ts file. */
    for (int i = 0; i < (int)segment_to_remove.size(); i++) {
        SrsHlsSegment* segment = segment_to_remove[i];

        /* 从磁盘中删除该 ts 文件 */
        if (hls_cleanup && should_write_file) {
            if (unlink(segment->full_path.c_str()) < 0) {
                srs_warn("cleanup unlink path failed, file=%s.", segment->full_path.c_str());
            }
        }

        srs_freep(segment);
    }
    segment_to_remove.clear();

    /* check ret of refresh m3u8 */
    if (ret != ERROR_SUCCESS) {
        srs_error("refresh m3u8 failed. ret=%d", ret);
        return ret;
    }

    return ret;
}
  • 该函数首先将满足时长的片保存到 segments vector 容器中,接着检查当前 segments 中所有片的总时长是否已经超过 hls_window 的限制,默认为 60s。若超过了其限制,则丢弃第一个 m3u8 中的第一个切片,直到 ts 的总时长在这个配置项范围之内。然后调用 SrsHlsMuxer::refresh_m3u8 函数刷新 m3u8,并且不再包含刚才已经删除的片。

3.1 SrsHlsMuxer::refresh_m3u8

int SrsHlsMuxer::refresh_m3u8()
{
    int ret = ERROR_SUCCESS;

    /* no segments, also no m3u8, return. */
    if (segments.size() == 0) {
        return ret;
    }

    std::string temp_m3u8 = m3u8 + ".temp";
    if ((ret = _refresh_m3u8(temp_m3u8)) == ERROR_SUCCESS) {
        /* 将该临时文件 livestream.m3u8.temp 重命名为 livestream.m3u8 */
        if (should_write_file && rename(temp_m3u8.c_str(), m3u8.c_str()) < 0) {
            ret = ERROR_HLS_WRITE_FAILED;
            srs_error("rename m3u8 file failed. %s => %s, ret=%d", temp_m3u8.c_str(), m3u8.c_str(), ret);
        }
    }

    /* remove the temp file. */
    if (srs_path_exists(temp_m3u8)) {
        /* 若该临时文件存在则删除该临时文件 */
        if (unlink(temp_m3u8.c_str()) < 0) {
            srs_warn("ignore remove m3u8 failed, %s", temp_m3u8.c_str());
        }
    }

    return ret;
}
  • 该函数首先会生成一个 m3u8 文件的绝对路径,然后根据该路径调用 SrsHlsMuxer::_refresh_m3u8 函数。

3.2 SrsHlsMuxer::_refresh_m3u8

int SrsHlsMuxer::_refresh_m3u8(string m3u8_file)
{
    int ret = ERROR_SUCCESS;

    /* no segments, return. */
    if (segments.size() == 0) {
        return ret;
    }

    SrsHlsCacheWriter writer(should_write_cache, should_write_file);
    if ((ret = writer.open(m3u8_file)) != ERROR_SUCCESS) {
        srs_error("open m3u8 file %s failed. ret=%d", m3u8_file.c_str(), ret);
        return ret;
    }

    /* #EXTM3U\n
     * #EXT-X-VERSION:3\n
     * #EXT-X-ALLOW-CACHE:YES\n
     */
    std::stringstream ss;
    ss << "#EXTM3U" << SRS_CONSTS_LF
       << "#EXT-X-VERSION:3" << SRS_CONSTS_LF
       << "#EXT-X-ALLOW-CACHE:YES" << SRS_CONSTS_LF;

    /* #EXT-X-MEDIA-SEQUENCE:4294967295\n */
    SrsHlsSegment* first = *segments.begin();
    ss << "#EXT-X-MEDIA-SEQUENCE:" << first->sequence_no << SRS_CONSTS_LF;

    /* iterator shared for td generation and segments wrote. */
    std::vector<SrsHlsSegment*>::iterator it;

    /* #EXT-X-TARGETDURATION:4294967295\n */
    /*
     * @see hls-m3u8-draft-pantos-http-live-streaming-12.pdf, page 25
     * The Media Playlist file MUST contain an EXT-X-TARGETDURATION tag.
     * Its value MUST be equal to or greater than the EXTINF duration of any
     * media segment that appears or will appear in the Playlist file,
     * typical targer duration is 10 seconds.
     */
    /* @see https://github.com/ossrs/srs/issues/304#issuecomment-74000081 */
    int targer_duration = 0;
    for (it = segments.begin(); it != segments.end(); ++it) {
        SrsHlsSegment* segment = *it;
        target_duration = srs_max(target_duration, (int)ceil(segment->duration));
    }
    target_duration = srs_max(target_duration, max_td);

    ss << "#EXT-X-TARGETDURATION:" << target_duration << SRS_CONSTS_LF;

    /* write all segments */
    for (it = segments.begin(); it != segments.end(); ++it) {
        SrsHlsSegment* segment = *it;

        if (segment->is_sequence_header) {
            /* #EXT-X-DISCONTINUITY\n */
            ss << "#EXT-X-DISCONTINUITY" << SRS_CONSTS_LF;
            srs_verbose("write m3u8 segment discontinuity success.");
        }

        /* "#EXTINF:4294967295.208,\n" */
        ss.precision(3);
        ss.setf(std::ios::fixed, std::ios::floatfield);
        ss << "#EXTINF:" << segment->duration << ", no desc" << SRS_CONSTS_LF;

        /* {file name}\n */
        ss << segment->uri << SRS_CONSTS_LF;
    }

    /* write m3u8 to writer. */
    std::string m3u8 = ss.str();
    if ((ret = writer.write((char*)m3u8.c_str(), (int)m3u8.length(), NULL))
        != ERROR_SUCCESS) {
        srs_error("write m3u8 failed. ret=%d", ret);
        return ret;
    }
    srs_info("write m3u8 %s success.", m3u8_file.c_str());

    return ret;
}
  • 该函数根据 segments 构建一个 m3u8 的 Playlist,并将构建的数据写入到 m3u8 文件中。关于 m3u8 的相关知识可参考 HLS协议解析
  • 回到 SrsHlsCache::reap_segment 函数中,当调用 SrsHlsMuxer::segment_close 成功返回后,接着调用 SrsHlsMuxer::segment_open 函数打开一个新的 片,即 ts 文件。

4. SrsHlsMuxer::segment_open

/*
 * open a new segment(a new ts file),
 * @param segment_start_dts, use to calc the segment duration,
 *    use 0 for the first segment of HLS.
 */
int SrsHlsMuxer::segment_open(int64_t segment_start_dts)
{
    int ret = ERROR_SUCCESS;

    /* current: current writing segment. 初始化时为 NULL */
    if (current) {
        srs_warn("ignore the segment open, for segment is already open.");
        return ret;
    }

    /* when segment open, the current segment must be NULL. */
    srs_assert(!current);

    /* load the default acodec from config. */
    SrsCodecAudio default_acodec = SrsCodecAudioAAC;
    if (true) {
        /* hls 没有配置 hls_acodec,默认返回 "aac" */
        std::string default_acodec_str = _srs_config->get_hls_acodec(req->vhost);
        if (default_acodec_str == "mp3") {
            default_acodec = SrsCodecAudioMP3;
            srs_info("hls: use default mp3 acodec");
        } else if (default_acodec_str == "aac") {
            default_acodec = SrsCodecAudioAAC;
            srs_info("hls: use default aac acodec");
        } else if (default_acodec_str == "an") {
            default_acodec = SrsCodecAudioDisabled;
            srs_info("hls: use default an acodec for pure video");
        } else {
            srs_warn("hls: use aac for other codec=%s", default_acodec_str.c_str());
        }
    }

    /* load the default vcodec from config. */
    SrsCodecVideo default_vcodec = SrsCodecVideoAVC;
    if (true) {
        /* hls 中没有配置 hls_vcodec,默认返回 "h264" */
        std::string default_vcodec_str = _srs_config->get_hls_vcodec(req->vhost);
        if (default_vcodec_str == "h264") {
            default_vcodec = SrsCodecVideoAVC;
            srs_info("hls: use default h264 vcodec");
        } else if (default_vcodec_str == "vn") {
            default_vcodec = SrsCodecVideoDisabled;
            srs_info("hls: use default vn vcodec for pure audio");
        } else {
            srs_warn("hls: use h264 for other codec=%s", default_vcodec_str.c_str());
        }
    }

    /* new segment. */
    current = new SrsHlsSegment(context, should_write_cache, should_write_file,
                                default_acodec, default_vcodec);
    /* _sequence_no: sequence number in m3u8. */
    current->sequence_no = _sequence_no++;
    /* 若为 HLS 的 first segment,则 segment_start_dts 为 0,
     * 否则为当前接收到的音视频的 dts */
    current->segment_start_dts = segment_start_dts;

    /* generate filename. */
    /* ts_file: "[app]/[stream]-[seq].ts" */
    std::string ts_file = hls_ts_file;
    /* ts_file: "live/livestream-[seq].ts" */
    ts_file = srs_path_build_stream(ts_file, req->vhost, req->app, req->stream);
    /* SrsHlsMuxer 构造时,初始化该值为 false */
    if (hls_ts_floor) {
        ...

    } else {
        ts_file = srs_path_build_timestamp(ts_file);
    }
    if (true) {
        std::stringstream ss;
        ss << current->sequence_no;
        /* ts_file: live/livestream-x.ts */
        ts_file = srs_string_replace(ts_file, "[seq]", ss.str());
    }
    /* full_path: ./objs/nginx/html/live/livestream-x.ts */
    current->full_path = hls_path + "/" + ts_file;

    /* the ts url, relative or absolute url. */
    std::string ts_url = current->full_path;
    /* ts_url: ./objs/nginx/html/live/livestream-x.ts,
     * m3u8_dir: ./objs/nginx/html/live */
    if (srs_string_starts_with(ts_url, m3u8_dir)) {
        /* ts_url: /livestream-x.ts */
        ts_url = ts_url.substr(m3u8_dir.length());
    }
    while (srs_string_starts_with(ts_url, "/")) {
        /* ts_url: livestream-x.ts */
        ts_url = ts_url.substr(1);
    }
    /* current->uri: "" */
    current->uri += hls_entry_prefix;
    if (!hls_entry_prefix.empty() && !srs_string_ends_with(hls_entry_prefix, "/"))
    {
        current->uri += "/";

        /* add the http dir to uri. */
        string http_dir = srs_path_dirname(m3u8_url);
        if (!http_dir.empty()) {
            current->uri += http_dir + "/";
        }
    }
    /* current->uri: livestream-x.ts */
    current->uri += ts_url;

    /* create dir recursively for hls. */
    /* ts_dir: ./objs/nginx/html/live */
    std::string ts_dir = srs_path_dirname(current->full_path);
    if (should_write_file && (ret = srs_create_dir_recursively(ts_dir))
        != ERROR_SUCCESS) {
        srs_error("create app dir %s failed. ret=%d", ts_dir.c_str(), ret);
        return ret;
    }

    /* open temp ts file. */
    /* tmp_file: ./objs/nginx/html/live/livestream-x.ts.tmp */
    std::string tmp_file = current->full_path + ".tmp";
    if ((ret = current->muxer->open(tmp_file.c_str())) != ERROR_SUCCESS) {
        srs_error("open hls muxer failed. ret=%d", ret);
        return ret;
    }

    /* set the segment muxer audio codec. */
    if (acodec != SrsCodecAudioReserved1) {
        current->muxer->update_acodec(acodec);
    }

    return ret;
}
  • 该函数重新创建一个 SrsHlsSegment 类对象,并打开一个 ts 文件后。回到 SrsHlsCache::reap_segment 函数中,接着调用 SrsHlsMuxer::flush_video 和 SrsHlsMuxer::flush_audio 函数将 cache 中缓存的所有音视频封装为 ts,并写入到打开的 ts 文件中。这里两个函数的具体分析过程可参考 SRS之SrsHls::on_audio详解SRS之SrsHls::on_video详解

原文地址:https://www.cnblogs.com/jimodetiantang/p/9153156.html

时间: 2024-10-28 05:17:48

SRS之SrsHlsCache::reap_segment详解的相关文章

SRS之SrsHls::on_audio详解

1. SrsHls::on_audio 将音频数据封装到 ts 文件中. /* * mux the audio packet to ts. * @param shared_audio, directly ptr, copy it if need to save it. */ int SrsHls::on_audio(SrsSharedPtrMessage* shared_audio) { int ret = ERROR_SUCCESS; /* 检测是够使能了 hls */ if (!hls_en

SRS之SrsHls::on_video详解

1. SrsHls::on_video /* * mux the video packets to ts. * @param shared_video, directly ptr, copy it if need to save it. * @param is_sps_pps, whether the video is h.264 sps/pps. */ int SrsHls::on_video(SrsSharedPtrMessage* shared_video, bool is_sps_pps

SRS之SrsRtmpConn::service_cycle详解

1. SrsRtmpConn::service_cycle 当服务器在 conn 线程的开始调用 connect_app 函数接收并解析客户端发送的 connect 消息后,调用该 service_cycle 函数开始服务客户端的具体请求. /** * when valid and connected to vhost/app, service the client. */ int SrsRtmpConn::service_cycle() { int ret = ERROR_SUCCESS; /

Oracle 11g数据库详解(2015-1-18更新)

Oracle 11g数据库详解 整理者:高压锅 QQ:280604597 Email:[email protected] 大家有什么不明白的地方,或者想要详细了解的地方可以联系我,我会认真回复的 1   简介 数据库操作主要有以下几步: 1.  启动.停止数据库 2.  连接.断开数据库 3.  创建.修改.删除数据库用户 4.  表空间 5.  新建.修改.删除表 6.  查询.插入.修改.删除表数据 7.  新建.修改.删除视图 8.  新建.修改.删除存储过程 9.  新建.修改.删除触发

Oracle 11g数据库详解(2015-02-28更新)

Oracle 11g数据库详解 整理者:高压锅 QQ:280604597 Email:[email protected] 大家有什么不明白的地方,或者想要详细了解的地方可以联系我,我会认真回复的 1   简介 数据库操作主要有以下几步: 1.  启动.停止数据库 2.  连接.断开数据库 3.  创建.修改.删除数据库用户 4.  表空间 5.  新建.修改.删除表 6.  查询.插入.修改.删除表数据 7.  新建.修改.删除视图 8.  新建.修改.删除存储过程 9.  新建.修改.删除触发

linux查看端口及端口详解

今天现场查看了TCP端口的占用情况,如下图 红色部分是IP,现场那边问我是不是我的程序占用了tcp的链接,,我远程登陆现场查看了一下,这种类型的tcp链接占用了400多个,,后边查了一下资料,说ESTABLISHED状态 ESTABLISHED的意思是建立连接.表示两台机器正在通信.      之后查找  ncube-lm  发现ncube-lm是一个端口,是nCube License Manager (即ncube管理的一个许可证明),意思是被允许,被认证开放的意思,,, 之后查看端口号 是1

详解EBS接口开发之WIP模块接口

总体说明 文档目的 本文档针对WIP模块业务功能和接口进行分析和研究,对采用并发请求方式和调用API方式分别进行介绍 内容 WIP模块常用标准表简介 WIP事物处理组成 WIP相关业务流程 WIP相关API研究事例 (十)参考文档(七)采购相关的一些知识 (一)WIP模块常用标准表简介 1.1   常用标准表 如下表中列出了与WIP导入相关的表和说明: 表名 说明 其他信息 BOM_STRUCTURES_B BOM头信息 BOM_COMPONENTS_B BOM组件信息 BOM_OPERATIO

Spring事务管理(详解+实例)

写这篇博客之前我首先读了<Spring in action>,之后在网上看了一些关于Spring事务管理的文章,感觉都没有讲全,这里就将书上的和网上关于事务的知识总结一下,参考的文章如下: Spring事务机制详解 Spring事务配置的五种方式 Spring中的事务管理实例详解 1 初步理解 理解事务之前,先讲一个你日常生活中最常干的事:取钱. 比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱:然后ATM出1000元钱.这两个步骤必须是要么都执行要么都

转载:DenseNet算法详解

原文连接:http://blog.csdn.net/u014380165/article/details/75142664 参考连接:http://blog.csdn.net/u012938704/article/details/53468483 本文这里仅当学习笔记使用,具体细节建议前往原文细度. 论文:Densely Connected Convolutional Networks 论文链接:https://arxiv.org/pdf/1608.06993.pdf 代码的github链接:h