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; }