SrsHls类实现Srs的hls功能
1. 
SrsHls类实例的构造
grep -Fnr "new SrsHls"
app/srs_app_hls.cpp:141:    writer = new SrsHlsCacheWriter(write_cache, write_file);
app/srs_app_hls.cpp:479:    current = new SrsHlsSegment(context, should_write_cache, should_write_file, default_acodec, default_vcodec);
app/srs_app_hls.cpp:1138:    muxer = new SrsHlsMuxer();
app/srs_app_hls.cpp:1139:    hls_cache = new SrsHlsCache();
app/srs_app_source.cpp:945:    hls = new SrsHls();

只在 SrsSource::SrsSource()里被调用,

SrsSource代表 rtmp living stream,
SrsSource包含SrsHls来实现hls分发
2. SrsHls的on_publish函数的调用
在 SrsSource::on_publish()里被调用,也就是在推流是调用
int SrsHls::on_publish(SrsRequest* req, bool fetch_sequence_header)
{
    int ret = ERROR_SUCCESS;
    
    srs_freep(_req);
    _req = req->copy();
    
    // update the hls time, for hls_dispose.
    last_update_time = srs_get_system_time_ms();
    
    // support multiple publish.
    if (hls_enabled) {
        return ret;
    }
    // 检查 vhost --> hls --> enabled
// enabled 不为 on,直接返回 std::
string vhost = req->vhost; if (!_srs_config->get_hls_enabled(vhost)) { return ret; } if ((ret = hls_cache->on_publish(muxer, req, stream_dts)) != ERROR_SUCCESS) { return ret; } // if enabled, open the muxer. hls_enabled = true; // ok, the hls can be dispose, or need to be dispose. hls_can_dispose = true; // when publish, don't need to fetch sequence header, which is old and maybe corrupt. // when reload, we must fetch the sequence header from source cache. if (fetch_sequence_header) { // notice the source to get the cached sequence header. // when reload to start hls, hls will never get the sequence header in stream, // use the SrsSource.on_hls_start to push the sequence header to HLS. if ((ret = source->on_hls_start()) != ERROR_SUCCESS) { srs_error("callback source hls start failed. ret=%d", ret); return ret; } } return ret; }

SrsHlsCache

app/srs_app_hls.hpp:358:    SrsHlsCache* hls_cache;
app/srs_app_hls.cpp:1139:    hls_cache = new SrsHlsCache(); // 在 SrsHls::SrsHls里
app/srs_app_hls.cpp:1153:    srs_freep(hls_cache);// 在 SrsHls::~SrsHls里
app/srs_app_hls.cpp:1234:    if ((ret = hls_cache->on_publish(muxer, req, stream_dts)) != ERROR_SUCCESS) {
app/srs_app_hls.cpp:1268:    if ((ret = hls_cache->on_unpublish(muxer)) != ERROR_SUCCESS) {
app/srs_app_hls.cpp:1334:        return hls_cache->on_sequence_header(muxer);
app/srs_app_hls.cpp:1349:    if ((ret = hls_cache->write_audio(codec, muxer, dts, sample)) != ERROR_SUCCESS) {
app/srs_app_hls.cpp:1398:        return hls_cache->on_sequence_header(muxer);
app/srs_app_hls.cpp:1409:    if ((ret = hls_cache->write_video(codec, muxer, dts, sample)) != ERROR_SUCCESS) {
int SrsHlsCache::on_publish(SrsHlsMuxer* muxer, SrsRequest* req, int64_t segment_start_dts)
{
    int ret = ERROR_SUCCESS;
// 推流命令: ffmpeg -re -i zhangwuji.mp4 -vcodec copy -acodec copy -f flv rtmp://192.168.151.151/live/marstv1

std::
string vhost = req->vhost;// __defaultVhost__ std::string stream = req->stream;// marstv1 std::string app = req->app;// live
// 配置文件 ./conf/hls.conf
double hls_fragment = _srs_config->get_hls_fragment(vhost);// 10秒
// the hls fragment in seconds, the duration of a piece of ts
// 一段ts文件的秒数
double hls_window = _srs_config->get_hls_window(vhost);// 60秒 // the hls window in seconds, the number of ts in m3u8
// m3u8文件包含的ts文件总的秒数,决定了m3u8中包含的ts文件的个数
// get the hls m3u8 ts list entry prefix config std::string entry_prefix = _srs_config->get_hls_entry_prefix(vhost);

# the hls entry prefix, which is base url of ts url.
# if specified, the ts path in m3u8 will be like:
# http://your-server/live/livestream-0.ts
# http://your-server/live/livestream-1.ts
# ...
# optional, default to empty string.
hls_entry_prefix http://your-server;

 

// get the hls path config
    std::string path = _srs_config->get_hls_path(vhost);
    std::string m3u8_file = _srs_config->get_hls_m3u8_file(vhost);
    std::string ts_file = _srs_config->get_hls_ts_file(vhost);
    bool cleanup = _srs_config->get_hls_cleanup(vhost);
bool wait_keyframe = _srs_config->get_hls_wait_keyframe(vhost); // the audio overflow, for pure audio to reap segment. double hls_aof_ratio = _srs_config->get_hls_aof_ratio(vhost); // whether use floor(timestamp/hls_fragment) for variable timestamp bool ts_floor = _srs_config->get_hls_ts_floor(vhost); // the seconds to dispose the hls. int hls_dispose = _srs_config->get_hls_dispose(vhost); // TODO: FIXME: support load exists m3u8, to continue publish stream. // for the HLS donot requires the EXT-X-MEDIA-SEQUENCE be monotonically increase. // open muxer
// mutex 类型为 SrsHlsMuxer
// 功能 muxer the HLS stream(m3u8 and ts files.)

  // 设置参数值
if ((ret = muxer->update_config(req, entry_prefix, path, m3u8_file, ts_file, hls_fragment, hls_window, ts_floor, hls_aof_ratio, cleanup, wait_keyframe)) != ERROR_SUCCESS ) { srs_error("m3u8 muxer update config failed. ret=%d", ret); return ret; } // if ((ret = muxer->segment_open(segment_start_dts)) != ERROR_SUCCESS) { srs_error("m3u8 muxer open segment failed. ret=%d", ret); return ret; } srs_trace("hls: win=%.2f, frag=%.2f, prefix=%s, path=%s, m3u8=%s, ts=%s, aof=%.2f, floor=%d, clean=%d, waitk=%d, dispose=%d", hls_window, hls_fragment, entry_prefix.c_str(), path.c_str(), m3u8_file.c_str(), ts_file.c_str(), hls_aof_ratio, ts_floor, cleanup, wait_keyframe, hls_dispose); return ret; }

推流过程中m3u8和ts文件的生成情况如下

 

marstv1.m3u8内容

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:YES
#EXT-X-MEDIA-SEQUENCE:9
#EXT-X-TARGETDURATION:18
#EXTINF:10.028, no desc
marstv1-9.ts
#EXTINF:15.368, no desc
marstv1-10.ts
#EXTINF:11.193, no desc
marstv1-11.ts
#EXTINF:12.400, no desc
marstv1-12.ts
#EXTINF:17.243, no desc
marstv1-13.ts

里面有5个ts文件,正在生成mars1-14.ts.tmp文件,

当它生成完成后会删除marstv1-9.ts,并更新m3u8文件

ts文件时长分别是

marstv1-9.ts 7s
marstv1-10.ts 10s
marstv1-11.ts 9s
marstv1-12.ts 5s
marstv1-13.ts 8s
最大10s,没超过hls_fragment设置的值10
hls_window 是60秒,所以m3u8里只有5个ts文件

将 hls_entry_prefix设为 http://neptunetv.com后
m3u8文件内容
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:YES
#EXT-X-MEDIA-SEQUENCE:5
#EXT-X-TARGETDURATION:16
#EXTINF:12.531, no desc
http://neptunetv.com/live/marstv1-5.ts
#EXTINF:13.151, no desc
http://neptunetv.com/live/marstv1-6.ts
#EXTINF:11.910, no desc
http://neptunetv.com/live/marstv1-7.ts
#EXTINF:15.439, no desc
http://neptunetv.com/live/marstv1-8.ts
#EXTINF:10.028, no desc
http://neptunetv.com/live/marstv1-9.ts

 

int SrsHlsMuxer::update_config(SrsRequest* r, string entry_prefix,
    string path, string m3u8_file, string ts_file, double fragment, double window,
    bool ts_floor, double aof_ratio, bool cleanup, bool wait_keyframe
) {
    int ret = ERROR_SUCCESS;
    
    srs_freep(req);
    req = r->copy();

    hls_entry_prefix = entry_prefix;
    hls_path = path;
    hls_ts_file = ts_file;
    hls_fragment = fragment;
    hls_aof_ratio = aof_ratio;
    hls_ts_floor = ts_floor;
    hls_cleanup = cleanup;
    hls_wait_keyframe = wait_keyframe;
    previous_floor_ts = 0;
    accept_floor_ts = 0;
    hls_window = window;
    deviation_ts = 0;
    
    // generate the m3u8 dir and path.
    m3u8_url = srs_path_build_stream(m3u8_file, req->vhost, req->app, req->stream);
    m3u8 = path + "/" + m3u8_url;

    // when update config, reset the history target duration.
    max_td = (int)(fragment * _srs_config->get_hls_td_ratio(r->vhost));
    
    // TODO: FIXME: refine better for SRS2 only support disk.
    should_write_cache = false;
    should_write_file = true;
    
    // create m3u8 dir once.
    m3u8_dir = srs_path_dirname(m3u8);
    if (should_write_file && (ret = srs_create_dir_recursively(m3u8_dir)) != ERROR_SUCCESS) {
        srs_error("create app dir %s failed. ret=%d", m3u8_dir.c_str(), ret);
        return ret;
    }
    srs_info("create m3u8 dir %s ok", m3u8_dir.c_str());
    
    return ret;
}
 
int SrsHlsMuxer::update_config(SrsRequest* r, string entry_prefix,
    string path, string m3u8_file, string ts_file, double fragment, double window,
    bool ts_floor, double aof_ratio, bool cleanup, bool wait_keyframe
) {
    int ret = ERROR_SUCCESS;
    // 配置文件为 ./conf/hls.conf
    srs_freep(req);
    req = r->copy();

    hls_entry_prefix = entry_prefix;// hls_entry_prefix=""
    hls_path = path;// hls_path="./objs/nginx/html"
    hls_ts_file = ts_file;//  "[app]/[stream]-[seq].ts"
    hls_fragment = fragment;// 10
    hls_aof_ratio = aof_ratio;// 2
    hls_ts_floor = ts_floor;// false
    hls_cleanup = cleanup;// true
    hls_wait_keyframe = wait_keyframe;// true
    previous_floor_ts = 0;
    accept_floor_ts = 0;
    hls_window = window;
    deviation_ts = 0;
    
    // generate the m3u8 dir and path.
    m3u8_url = srs_path_build_stream(m3u8_file, req->vhost, req->app, req->stream);
// "live/marstv1.m3u8" m3u8
= path + "/" + m3u8_url; // "./objs/nginx/html/live/marstv1.m3u8"

// when update config, reset the history target duration. max_td = (int)(fragment * _srs_config->get_hls_td_ratio(r->vhost)); // fragment default 10
  // _srs_config->get_hls_td_ratio(r->vhost) default 1.5
  // max_td default 15
// TODO: FIXME: refine better for SRS2 only support disk. should_write_cache = false; should_write_file = true; // create m3u8 dir once. m3u8_dir = srs_path_dirname(m3u8);
  // m3u8_dir --> "./objs/nginx/html/live"
if (should_write_file && (ret = srs_create_dir_recursively(m3u8_dir)) != ERROR_SUCCESS) { srs_error("create app dir %s failed. ret=%d", m3u8_dir.c_str(), ret); return ret; } srs_info("create m3u8 dir %s ok", m3u8_dir.c_str()); //创建m3u8文件所在的目录 return ret; }
int SrsHlsMuxer::segment_open(int64_t segment_start_dts)
{
  // 第一次调用到这里是,segment_start_dts 是 0
int ret = ERROR_SUCCESS; // current 类型,SrsHlsSegment为0 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.
// 从配置文件加载默认声音编码 hls_acodec
SrsCodecAudio default_acodec = SrsCodecAudioAAC; if (true) { 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.
  // 从配置文件加载默认声音编码 hls_vcodec
   SrsCodecVideo default_vcodec = SrsCodecVideoAVC;
    if (true) {
        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.
// SrsHlsSegment代表一个ts分段
current = new SrsHlsSegment(context, should_write_cache, should_write_file, default_acodec, default_vcodec); current->sequence_no = _sequence_no++;// 第一次运行到此 为0 current->segment_start_dts = segment_start_dts;// 第一次运行到此 为0 // generate filename. std::string ts_file = hls_ts_file;
// 执行前 "[app]/[stream]-[seq].ts" ts_file
= srs_path_build_stream(ts_file, req->vhost, req->app, req->stream);
// 执行后 "live/marstv1-[seq].ts"
// hls_ts_floor default off
//
if (hls_ts_floor) { // accept the floor ts for the first piece. int64_t current_floor_ts = (int64_t)(srs_update_system_time_ms() / (1000 * hls_fragment)); if (!accept_floor_ts) { accept_floor_ts = current_floor_ts - 1; } else { accept_floor_ts++; } // jump when deviation more than 10p if (accept_floor_ts - current_floor_ts > SRS_JUMP_WHEN_PIECE_DEVIATION) { srs_warn("hls: jmp for ts deviation, current=%"PRId64", accept=%"PRId64, current_floor_ts, accept_floor_ts); accept_floor_ts = current_floor_ts - 1; } // when reap ts, adjust the deviation. deviation_ts = (int)(accept_floor_ts - current_floor_ts); // dup/jmp detect for ts in floor mode. if (previous_floor_ts && previous_floor_ts != current_floor_ts - 1) { srs_warn("hls: dup/jmp ts, previous=%"PRId64", current=%"PRId64", accept=%"PRId64", deviation=%d", previous_floor_ts, current_floor_ts, accept_floor_ts, deviation_ts); } previous_floor_ts = current_floor_ts; // we always ensure the piece is increase one by one. std::stringstream ts_floor; ts_floor << accept_floor_ts; ts_file = srs_string_replace(ts_file, "[timestamp]", ts_floor.str()); // TODO: FIMXE: we must use the accept ts floor time to generate the hour variable. ts_file = srs_path_build_timestamp(ts_file); } else { ts_file = srs_path_build_timestamp(ts_file);//默认hls_ts_floor off 运行到此
// 对[timestamp]变量进行替换
}
if (true) {
// ts_file --> "live/marstv1-[seq].ts" std::stringstream ss; ss
<< current->sequence_no; ts_file = srs_string_replace(ts_file, "[seq]", ss.str());
// 替换 [seq]变量
// ts_file --> "live/marstv1-0.ts" } current
->full_path = hls_path + "/" + ts_file;
// "./objs/nginx/html/live/marstv1-0.ts" --> current->full_path srs_info(
"hls: generate ts path %s, tmpl=%s, floor=%d", ts_file.c_str(), hls_ts_file.c_str(), hls_ts_floor); // the ts url, relative or absolute url. std::string ts_url = current->full_path; if (srs_string_starts_with(ts_url, m3u8_dir)) { ts_url = ts_url.substr(m3u8_dir.length()); } while (srs_string_starts_with(ts_url, "/")) { ts_url = ts_url.substr(1); } 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 += ts_url; // create dir recursively for hls. 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; } srs_info("create ts dir %s ok", ts_dir.c_str()); // open temp ts file. 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; } srs_info("open HLS muxer success. path=%s, tmp=%s", current->full_path.c_str(), tmp_file.c_str()); // set the segment muxer audio codec. // TODO: FIXME: refine code, use event instead. if (acodec != SrsCodecAudioReserved1) { current->muxer->update_acodec(acodec); } return ret; }
SrsHlsSegment的构造函数
SrsHlsSegment::SrsHlsSegment(SrsTsContext* c, bool write_cache, bool write_file, SrsCodecAudio ac, SrsCodecVideo vc)
{
    duration = 0;
    sequence_no = 0;
    segment_start_dts = 0;
    is_sequence_header = false;
    writer = new SrsHlsCacheWriter(write_cache, write_file);
//
SrsHlsCacheWriter 继承 SrsFileWriter,用于写文件或缓存
// 参考 http://www.cnblogs.com/yan-shi-yi/p/6879605.html
  muxer = new SrsTSMuxer(writer, c, ac, vc);
}
SrsHlsCacheWriter::SrsHlsCacheWriter(bool write_cache, bool write_file)
{
    should_write_cache = write_cache;
    should_write_file = write_file;
}
SrsTSMuxer::SrsTSMuxer(SrsFileWriter* w, SrsTsContext* c, SrsCodecAudio ac, SrsCodecVideo vc)
{
    writer = w;
    context = c;

    acodec = ac;
    vcodec = vc;
}