Blame view

trunk/src/app/srs_app_hls.cpp 39.1 KB
1 2 3
/*
The MIT License (MIT)
4
Copyright (c) 2013-2015 SRS(simple-rtmp-server)
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
24
#include <srs_app_hls.hpp>
25
26 27 28
/**
* the HLS section, only available when HLS enabled.
*/
29
#ifdef SRS_AUTO_HLS
30
31 32 33 34 35
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
36
#include <math.h>
37
38
#include <algorithm>
39
#include <sstream>
40
using namespace std;
41
42
#include <srs_kernel_error.hpp>
winlin authored
43
#include <srs_kernel_codec.hpp>
44 45
#include <srs_rtmp_amf0.hpp>
#include <srs_rtmp_stack.hpp>
46 47
#include <srs_app_config.hpp>
#include <srs_app_source.hpp>
48
#include <srs_core_autofree.hpp>
49
#include <srs_rtmp_sdk.hpp>
50
#include <srs_app_pithy_print.hpp>
51
#include <srs_kernel_utility.hpp>
52
#include <srs_kernel_codec.hpp>
53
#include <srs_kernel_file.hpp>
54
#include <srs_rtmp_buffer.hpp>
55
#include <srs_kernel_ts.hpp>
56
#include <srs_app_utility.hpp>
57
#include <srs_app_http_hooks.hpp>
58
59
// drop the segment when duration of ts too small.
60
#define SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS 100
61
62
// fragment plus the deviation percent.
63
#define SRS_HLS_FLOOR_REAP_PERCENT 0.3
64
// reset the piece id when deviation overflow this.
65
#define SRS_JUMP_WHEN_PIECE_DEVIATION 20
66
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
ISrsHlsHandler::ISrsHlsHandler()
{
}

ISrsHlsHandler::~ISrsHlsHandler()
{
}

SrsHlsCacheWriter::SrsHlsCacheWriter(bool write_cache, bool write_file)
{
    should_write_cache = write_cache;
    should_write_file = write_file;
}

SrsHlsCacheWriter::~SrsHlsCacheWriter()
{
}

int SrsHlsCacheWriter::open(string file)
{
    if (!should_write_file) {
        return ERROR_SUCCESS;
    }

    return impl.open(file);
}

void SrsHlsCacheWriter::close()
{
    if (!should_write_file) {
        return;
    }

    impl.close();
}

bool SrsHlsCacheWriter::is_open()
{
    if (!should_write_file) {
        return true;
    }

    return impl.is_open();
}

int64_t SrsHlsCacheWriter::tellg()
{
    if (!should_write_file) {
        return 0;
    }

    return impl.tellg();
}

int SrsHlsCacheWriter::write(void* buf, size_t count, ssize_t* pnwrite)
{
    if (should_write_cache) {
        if (count > 0) {
            data.append((char*)buf, count);
        }
    }

    if (should_write_file) {
        return impl.write(buf, count, pnwrite);
    }

    return ERROR_SUCCESS;
}

string SrsHlsCacheWriter::cache()
{
    return data;
}
141
SrsHlsSegment::SrsHlsSegment(SrsTsContext* c, bool write_cache, bool write_file, SrsCodecAudio ac, SrsCodecVideo vc)
142
{
143 144 145
    duration = 0;
    sequence_no = 0;
    segment_start_dts = 0;
winlin authored
146
    is_sequence_header = false;
147
    writer = new SrsHlsCacheWriter(write_cache, write_file);
148
    muxer = new SrsTSMuxer(writer, c, ac, vc);
149 150
}
151
SrsHlsSegment::~SrsHlsSegment()
152
{
153
    srs_freep(muxer);
154
    srs_freep(writer);
winlin authored
155
}
156
157
void SrsHlsSegment::update_duration(int64_t current_frame_dts)
winlin authored
158
{
159 160 161 162 163 164 165 166
    // we use video/audio to update segment duration,
    // so when reap segment, some previous audio frame will
    // update the segment duration, which is nagetive,
    // just ignore it.
    if (current_frame_dts < segment_start_dts) {
        return;
    }
    
167
    duration = (current_frame_dts - segment_start_dts) / 90000.0;
168 169
    srs_assert(duration >= 0);
    
170
    return;
171 172
}
173
SrsDvrAsyncCallOnHls::SrsDvrAsyncCallOnHls(SrsRequest* r, string p, string t, string m, string mu, int s, double d)
174
{
175
    req = r->copy();
176
    path = p;
177 178 179
    ts_url = t;
    m3u8 = m;
    m3u8_url = mu;
180
    seq_no = s;
181
    duration = d;
182 183 184 185
}

SrsDvrAsyncCallOnHls::~SrsDvrAsyncCallOnHls()
{
186
    srs_freep(req);
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
}

int SrsDvrAsyncCallOnHls::call()
{
    int ret = ERROR_SUCCESS;
    
#ifdef SRS_AUTO_HTTP_CALLBACK
    // http callback for on_hls in config.
    if (_srs_config->get_vhost_http_hooks_enabled(req->vhost)) {
        // HTTP: on_hls
        SrsConfDirective* on_hls = _srs_config->get_vhost_on_hls(req->vhost);
        if (!on_hls) {
            srs_info("ignore the empty http callback: on_hls");
            return ret;
        }
        
        std::string file = path;
        int sn = seq_no;
        for (int i = 0; i < (int)on_hls->args.size(); i++) {
            std::string url = on_hls->args.at(i);
207
            if ((ret = SrsHttpHooks::on_hls(url, req, file, ts_url, m3u8, m3u8_url, sn, duration)) != ERROR_SUCCESS) {
208 209 210 211 212 213 214 215 216 217 218 219
                srs_error("hook client on_hls failed. url=%s, ret=%d", url.c_str(), ret);
                return ret;
            }
        }
    }
#endif
    
    return ret;
}

string SrsDvrAsyncCallOnHls::to_string()
{
220 221 222 223 224
    return "on_hls: " + path;
}

SrsDvrAsyncCallOnHlsNotify::SrsDvrAsyncCallOnHlsNotify(SrsRequest* r, string u)
{
225
    req = r->copy();
226 227 228 229 230
    ts_url = u;
}

SrsDvrAsyncCallOnHlsNotify::~SrsDvrAsyncCallOnHlsNotify()
{
231
    srs_freep(req);
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
}

int SrsDvrAsyncCallOnHlsNotify::call()
{
    int ret = ERROR_SUCCESS;
    
#ifdef SRS_AUTO_HTTP_CALLBACK
    // http callback for on_hls_notify in config.
    if (_srs_config->get_vhost_http_hooks_enabled(req->vhost)) {
        // HTTP: on_hls
        SrsConfDirective* on_hls = _srs_config->get_vhost_on_hls_notify(req->vhost);
        if (!on_hls) {
            srs_info("ignore the empty http callback: on_hls_notify");
            return ret;
        }
        
248 249 250 251 252 253 254
        std::string url;
        if (true) {
            static u_int32_t nb_call = 0;
            int index = nb_call++ % on_hls->args.size();
            url = on_hls->args.at(index);
        }
        
255
        int nb_notify = _srs_config->get_vhost_hls_nb_notify(req->vhost);
256 257 258
        if ((ret = SrsHttpHooks::on_hls_notify(url, req, ts_url, nb_notify)) != ERROR_SUCCESS) {
            srs_error("hook client on_hls_notify failed. url=%s, ts=%s, ret=%d", url.c_str(), ts_url.c_str(), ret);
            return ret;
259 260 261 262 263 264 265 266 267 268
        }
    }
#endif
    
    return ret;
}

string SrsDvrAsyncCallOnHlsNotify::to_string()
{
    return "on_hls_notify: " + ts_url;
269 270
}
271
SrsHlsMuxer::SrsHlsMuxer()
winlin authored
272
{
273
    req = NULL;
274
    handler = NULL;
275
    hls_fragment = hls_window = 0;
276
    hls_aof_ratio = 1.0;
277
    deviation_ts = 0;
278
    hls_cleanup = true;
279
    hls_wait_keyframe = true;
280
    previous_floor_ts = 0;
winlin authored
281
    accept_floor_ts = 0;
282
    hls_ts_floor = false;
283
    target_duration = 0;
284
    _sequence_no = 0;
285
    current = NULL;
286
    acodec = SrsCodecAudioReserved1;
287 288
    should_write_cache = false;
    should_write_file = true;
289
    async = new SrsAsyncCallWorker();
290
    context = new SrsTsContext();
winlin authored
291
}
winlin authored
292
293
SrsHlsMuxer::~SrsHlsMuxer()
winlin authored
294
{
295
    std::vector<SrsHlsSegment*>::iterator it;
296
    for (it = segments.begin(); it != segments.end(); ++it) {
297
        SrsHlsSegment* segment = *it;
298 299 300 301 302
        srs_freep(segment);
    }
    segments.clear();
    
    srs_freep(current);
303
    srs_freep(req);
304
    srs_freep(async);
305
    srs_freep(context);
winlin authored
306 307
}
308 309 310 311 312
int SrsHlsMuxer::sequence_no()
{
    return _sequence_no;
}
313 314 315 316 317 318 319 320 321 322
string SrsHlsMuxer::ts_url()
{
    return current? current->uri:"";
}

double SrsHlsMuxer::duration()
{
    return current? current->duration:0;
}
323
int SrsHlsMuxer::deviation()
324
{
325 326 327 328 329
    // no floor, no deviation.
    if (!hls_ts_floor) {
        return 0;
    }
    
330
    return deviation_ts;
331 332
}
333 334 335 336 337 338 339 340 341 342 343 344 345
int SrsHlsMuxer::initialize(ISrsHlsHandler* h)
{
    int ret = ERROR_SUCCESS;
    
    handler = h;
    
    if ((ret = async->start()) != ERROR_SUCCESS) {
        return ret;
    }
    
    return ret;
}
346
int SrsHlsMuxer::update_config(SrsRequest* r, string entry_prefix,
347
    string path, string m3u8_file, string ts_file, double fragment, double window,
348
    bool ts_floor, double aof_ratio, bool cleanup, bool wait_keyframe
349
) {
350 351
    int ret = ERROR_SUCCESS;
    
352 353 354
    srs_freep(req);
    req = r->copy();
355
    hls_entry_prefix = entry_prefix;
356
    hls_path = path;
357
    hls_ts_file = ts_file;
358
    hls_fragment = fragment;
359
    hls_aof_ratio = aof_ratio;
360
    hls_ts_floor = ts_floor;
361
    hls_cleanup = cleanup;
362
    hls_wait_keyframe = wait_keyframe;
363
    previous_floor_ts = 0;
winlin authored
364
    accept_floor_ts = 0;
365
    hls_window = window;
366
    deviation_ts = 0;
367 368
    
    // generate the m3u8 dir and path.
369 370
    m3u8_url = srs_path_build_stream(m3u8_file, req->vhost, req->app, req->stream);
    m3u8 = path + "/" + m3u8_url;
371 372 373

    // we always keep the target duration increasing.
    int max_td = srs_max(target_duration, (int)(fragment * _srs_config->get_hls_td_ratio(r->vhost)));
374
    srs_info("hls update target duration %d=>%d, aof=%.2f", target_duration, max_td, aof_ratio);
375
    target_duration = max_td;
376 377 378 379 380 381 382 383 384 385 386 387 388

    std::string storage = _srs_config->get_hls_storage(r->vhost);
    if (storage == "ram") {
        should_write_cache = true;
        should_write_file = false;
    } else if (storage == "disk") {
        should_write_cache = false;
        should_write_file = true;
    } else {
        srs_assert(storage == "both");
        should_write_cache = true;
        should_write_file = true;
    }
389
    
winlin authored
390 391 392 393 394 395 396 397
    // 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());
    
398
    return ret;
399 400
}
401
int SrsHlsMuxer::segment_open(int64_t segment_start_dts)
winlin authored
402
{
403 404 405 406 407 408 409 410 411
    int ret = ERROR_SUCCESS;
    
    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);
412 413 414

    // load the default acodec from config.
    SrsCodecAudio default_acodec = SrsCodecAudioAAC;
415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
    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 {
            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) {
        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());
        }
441
    }
442 443
    
    // new segment.
444
    current = new SrsHlsSegment(context, should_write_cache, should_write_file, default_acodec, default_vcodec);
445
    current->sequence_no = _sequence_no++;
446
    current->segment_start_dts = segment_start_dts;
447 448
    
    // generate filename.
449 450
    std::string ts_file = hls_ts_file;
    ts_file = srs_path_build_stream(ts_file, req->vhost, req->app, req->stream);
451
    if (hls_ts_floor) {
winlin authored
452
        // accept the floor ts for the first piece.
453
        int64_t current_floor_ts = (int64_t)(srs_update_system_time_ms() / (1000 * hls_fragment));
winlin authored
454
        if (!accept_floor_ts) {
455
            accept_floor_ts = current_floor_ts - 1;
winlin authored
456 457 458 459
        } else {
            accept_floor_ts++;
        }
        
460 461 462 463 464 465 466 467 468
        // 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);
        
469
        // dup/jmp detect for ts in floor mode.
470
        if (previous_floor_ts && previous_floor_ts != current_floor_ts - 1) {
471
            srs_warn("hls: dup/jmp ts, previous=%"PRId64", current=%"PRId64", accept=%"PRId64", deviation=%d",
472
                     previous_floor_ts, current_floor_ts, accept_floor_ts, deviation_ts);
473
        }
474
        previous_floor_ts = current_floor_ts;
475 476 477 478 479 480 481 482 483 484
        
        // 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);
485
    }
486 487 488 489 490 491
    if (true) {
        std::stringstream ss;
        ss << current->sequence_no;
        ts_file = srs_string_replace(ts_file, "[seq]", ss.str());
    }
    current->full_path = hls_path + "/" + ts_file;
492
    srs_info("hls: generate ts path %s, tmpl=%s, floor=%d", ts_file.c_str(), hls_ts_file.c_str(), hls_ts_floor);
493
    
494 495 496 497 498 499 500 501
    // 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);
    }
502 503 504 505
    current->uri += hls_entry_prefix;
    if (!hls_entry_prefix.empty() && !srs_string_ends_with(hls_entry_prefix, "/")) {
        current->uri += "/";
    }
506 507 508
    current->uri += ts_url;
    
    // create dir recursively for hls.
winlin authored
509 510 511
    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);
512 513
        return ret;
    }
winlin authored
514
    srs_info("create ts dir %s ok", ts_dir.c_str());
515
    
516
    // open temp ts file.
517 518
    std::string tmp_file = current->full_path + ".tmp";
    if ((ret = current->muxer->open(tmp_file.c_str())) != ERROR_SUCCESS) {
519 520 521
        srs_error("open hls muxer failed. ret=%d", ret);
        return ret;
    }
522
    srs_info("open HLS muxer success. path=%s, tmp=%s",
523
        current->full_path.c_str(), tmp_file.c_str());
524 525 526 527 528

    // set the segment muxer audio codec.
    if (acodec != SrsCodecAudioReserved1) {
        current->muxer->update_acodec(acodec);
    }
529 530
    
    return ret;
winlin authored
531 532
}
winlin authored
533 534 535 536 537 538 539 540 541 542 543 544 545
int SrsHlsMuxer::on_sequence_header()
{
    int ret = ERROR_SUCCESS;
    
    srs_assert(current);
    
    // set the current segment to sequence header,
    // when close the segement, it will write a discontinuity to m3u8 file.
    current->is_sequence_header = true;
    
    return ret;
}
546 547 548
bool SrsHlsMuxer::is_segment_overflow()
{
    srs_assert(current);
549
    
550
    // to prevent very small segment.
551
    if (current->duration * 1000 < 2 * SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) {
552 553 554
        return false;
    }
    
555
    // use N% deviation, to smoother.
556
    double deviation = hls_ts_floor? SRS_HLS_FLOOR_REAP_PERCENT * deviation_ts * hls_fragment : 0.0;
557 558
    srs_info("hls: dur=%.2f, tar=%.2f, dev=%.2fms/%dp, frag=%.2f",
        current->duration, hls_fragment + deviation, deviation, deviation_ts, hls_fragment);
559 560
    
    return current->duration >= hls_fragment + deviation;
561 562
}
563 564 565 566 567
bool SrsHlsMuxer::wait_keyframe()
{
    return hls_wait_keyframe;
}
568 569
bool SrsHlsMuxer::is_segment_absolutely_overflow()
{
570
    // @see https://github.com/simple-rtmp-server/srs/issues/151#issuecomment-83553950
571
    srs_assert(current);
572
    
573
    // to prevent very small segment.
574
    if (current->duration * 1000 < 2 * SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) {
575 576 577
        return false;
    }
    
578 579 580 581 582 583
    // use N% deviation, to smoother.
    double deviation = hls_ts_floor? SRS_HLS_FLOOR_REAP_PERCENT * deviation_ts * hls_fragment : 0.0;
    srs_info("hls: dur=%.2f, tar=%.2f, dev=%.2fms/%dp, frag=%.2f",
             current->duration, hls_fragment + deviation, deviation, deviation_ts, hls_fragment);
    
    return current->duration >= hls_aof_ratio * hls_fragment + deviation;
584 585
}
586
int SrsHlsMuxer::update_acodec(SrsCodecAudio ac)
587 588 589
{
    srs_assert(current);
    srs_assert(current->muxer);
590 591
    acodec = ac;
    return current->muxer->update_acodec(ac);
592 593
}
594
int SrsHlsMuxer::flush_audio(SrsTsCache* cache)
winlin authored
595
{
596 597 598 599 600 601 602 603
    int ret = ERROR_SUCCESS;

    // if current is NULL, segment is not open, ignore the flush event.
    if (!current) {
        srs_warn("flush audio ignored, for segment is not open.");
        return ret;
    }
    
604
    if (!cache->audio || cache->audio->payload->length() <= 0) {
605 606 607
        return ret;
    }
    
608
    // update the duration of segment.
609
    current->update_duration(cache->audio->pts);
610
    
611
    if ((ret = current->muxer->write_audio(cache->audio)) != ERROR_SUCCESS) {
612 613 614
        return ret;
    }
    
615 616
    // write success, clear and free the msg
    srs_freep(cache->audio);
617 618

    return ret;
winlin authored
619 620
}
621
int SrsHlsMuxer::flush_video(SrsTsCache* cache)
winlin authored
622
{
623 624 625 626 627 628 629 630
    int ret = ERROR_SUCCESS;

    // if current is NULL, segment is not open, ignore the flush event.
    if (!current) {
        srs_warn("flush video ignored, for segment is not open.");
        return ret;
    }
    
631 632 633 634
    if (!cache->video || cache->video->payload->length() <= 0) {
        return ret;
    }
    
635 636 637
    srs_assert(current);
    
    // update the duration of segment.
638
    current->update_duration(cache->video->dts);
639
    
640
    if ((ret = current->muxer->write_video(cache->video)) != ERROR_SUCCESS) {
641 642 643
        return ret;
    }
    
644 645
    // write success, clear and free the msg
    srs_freep(cache->video);
646
    
647
    return ret;
winlin authored
648 649
}
650
int SrsHlsMuxer::segment_close(string log_desc)
winlin authored
651
{
652 653 654 655 656 657 658 659 660 661 662
    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.
663
    std::vector<SrsHlsSegment*>::iterator it;
664 665 666
    it = std::find(segments.begin(), segments.end(), current);
    srs_assert(it == segments.end());
667
    // valid, add to segments if segment duration is ok
668
    if (current->duration * 1000 >= SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) {
669
        segments.push_back(current);
670
        
671
        // use async to call the http hooks, for it will cause thread switch.
672
        if ((ret = async->execute(new SrsDvrAsyncCallOnHls(req,
673 674 675
            current->full_path, current->uri, m3u8, m3u8_url,
            current->sequence_no, current->duration))) != ERROR_SUCCESS)
        {
676 677
            return ret;
        }
678 679
        
        // use async to call the http hooks, for it will cause thread switch.
680
        if ((ret = async->execute(new SrsDvrAsyncCallOnHlsNotify(req, current->uri))) != ERROR_SUCCESS) {
681 682
            return ret;
        }
683
    
684
        srs_info("%s reap ts segment, sequence_no=%d, uri=%s, duration=%.2f, start=%"PRId64,
685
            log_desc.c_str(), current->sequence_no, current->uri.c_str(), current->duration, 
686
            current->segment_start_dts);
687 688 689 690 691 692 693
        
        // notify handler for update ts.
        srs_assert(current->writer);
        if (handler && (ret = handler->on_update_ts(req, current->uri, current->writer->cache())) != ERROR_SUCCESS) {
            srs_error("notify handler for update ts failed. ret=%d", ret);
            return ret;
        }
694
    
695 696
        // close the muxer of finished segment.
        srs_freep(current->muxer);
697 698 699
        std::string full_path = current->full_path;
        current = NULL;
        
700
        // rename from tmp to real path
701
        std::string tmp_file = full_path + ".tmp";
702
        if (should_write_file && rename(tmp_file.c_str(), full_path.c_str()) < 0) {
703 704
            ret = ERROR_HLS_WRITE_FAILED;
            srs_error("rename ts file failed, %s => %s. ret=%d", 
705
                tmp_file.c_str(), full_path.c_str(), ret);
706 707 708 709
            return ret;
        }
    } else {
        // reuse current segment index.
710
        _sequence_no--;
711 712 713 714 715 716 717
        
        srs_trace("%s drop ts segment, sequence_no=%d, uri=%s, duration=%.2f, start=%"PRId64"",
            log_desc.c_str(), current->sequence_no, current->uri.c_str(), current->duration, 
            current->segment_start_dts);
        
        // rename from tmp to real path
        std::string tmp_file = current->full_path + ".tmp";
718 719 720
        if (should_write_file) {
            unlink(tmp_file.c_str());
        }
721 722
        
        srs_freep(current);
723
    }
724 725
    
    // the segments to remove
726
    std::vector<SrsHlsSegment*> segment_to_remove;
727 728 729 730
    
    // shrink the segments.
    double duration = 0;
    int remove_index = -1;
731
    for (int i = (int)segments.size() - 1; i >= 0; i--) {
732
        SrsHlsSegment* segment = segments[i];
733 734 735 736 737 738 739 740
        duration += segment->duration;
        
        if ((int)duration > hls_window) {
            remove_index = i;
            break;
        }
    }
    for (int i = 0; i < remove_index && !segments.empty(); i++) {
741
        SrsHlsSegment* segment = *segments.begin();
742 743 744 745 746 747 748 749 750
        segments.erase(segments.begin());
        segment_to_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++) {
751
        SrsHlsSegment* segment = segment_to_remove[i];
752 753 754 755 756
        
        if (hls_cleanup) {
            unlink(segment->full_path.c_str());
        }
        
757 758 759 760 761 762 763 764 765 766 767
        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;
winlin authored
768 769
}
770
int SrsHlsMuxer::refresh_m3u8()
winlin authored
771
{
772 773
    int ret = ERROR_SUCCESS;
    
774 775 776
    std::string temp_m3u8 = m3u8 + ".temp";
    if ((ret = _refresh_m3u8(temp_m3u8)) == ERROR_SUCCESS) {
        if (should_write_file && rename(temp_m3u8.c_str(), m3u8.c_str()) < 0) {
777
            ret = ERROR_HLS_WRITE_FAILED;
778
            srs_error("rename m3u8 file failed. %s => %s, ret=%d", temp_m3u8.c_str(), m3u8.c_str(), ret);
779 780 781 782
        }
    }
    
    // remove the temp file.
783
    unlink(temp_m3u8.c_str());
784 785
    
    return ret;
winlin authored
786 787
}
788
int SrsHlsMuxer::_refresh_m3u8(string m3u8_file)
winlin authored
789
{
790 791 792 793 794 795
    int ret = ERROR_SUCCESS;
    
    // no segments, return.
    if (segments.size() == 0) {
        return ret;
    }
796 797 798

    SrsHlsCacheWriter writer(should_write_cache, should_write_file);
    if ((ret = writer.open(m3u8_file)) != ERROR_SUCCESS) {
799 800 801 802 803
        srs_error("open m3u8 file %s failed. ret=%d", m3u8_file.c_str(), ret);
        return ret;
    }
    srs_info("open m3u8 file %s success.", m3u8_file.c_str());
    
804 805
    // #EXTM3U\n
    // #EXT-X-VERSION:3\n
806
    // #EXT-X-ALLOW-CACHE:YES\n
807 808 809
    std::stringstream ss;
    ss << "#EXTM3U" << SRS_CONSTS_LF
        << "#EXT-X-VERSION:3" << SRS_CONSTS_LF
810
        << "#EXT-X-ALLOW-CACHE:YES" << SRS_CONSTS_LF;
811 812 813
    srs_verbose("write m3u8 header success.");
    
    // #EXT-X-MEDIA-SEQUENCE:4294967295\n
814
    SrsHlsSegment* first = *segments.begin();
815
    ss << "#EXT-X-MEDIA-SEQUENCE:" << first->sequence_no << SRS_CONSTS_LF;
816 817 818
    srs_verbose("write m3u8 sequence success.");
    
    // #EXT-X-TARGETDURATION:4294967295\n
819 820 821 822 823 824 825 826
    /**
    * @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,
    * rounded to the nearest integer. Its value MUST NOT change. A
    * typical target duration is 10 seconds.
    */
827
    // @see https://github.com/simple-rtmp-server/srs/issues/304#issuecomment-74000081
828
    std::vector<SrsHlsSegment*>::iterator it;
829
    for (it = segments.begin(); it != segments.end(); ++it) {
830
        SrsHlsSegment* segment = *it;
831
        target_duration = srs_max(target_duration, (int)ceil(segment->duration));
832
    }
833
    ss << "#EXT-X-TARGETDURATION:" << target_duration << SRS_CONSTS_LF;
834 835 836 837
    srs_verbose("write m3u8 duration success.");
    
    // write all segments
    for (it = segments.begin(); it != segments.end(); ++it) {
838
        SrsHlsSegment* segment = *it;
839
        
winlin authored
840 841
        if (segment->is_sequence_header) {
            // #EXT-X-DISCONTINUITY\n
842
            ss << "#EXT-X-DISCONTINUITY" << SRS_CONSTS_LF;
winlin authored
843 844 845
            srs_verbose("write m3u8 segment discontinuity success.");
        }
        
846
        // "#EXTINF:4294967295.208,\n"
847 848
        ss.precision(3);
        ss.setf(std::ios::fixed, std::ios::floatfield);
849
        ss << "#EXTINF:" << segment->duration << ", no desc" << SRS_CONSTS_LF;
winlin authored
850
        srs_verbose("write m3u8 segment info success.");
851
        
852
        // {file name}\n
853
        ss << segment->uri << SRS_CONSTS_LF;
854 855
        srs_verbose("write m3u8 segment uri success.");
    }
856 857 858 859 860 861 862

    // 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;
    }
863
    srs_info("write m3u8 %s success.", m3u8_file.c_str());
864 865 866 867 868 869

    // notify handler for update m3u8.
    if (handler && (ret = handler->on_update_m3u8(req, writer.cache())) != ERROR_SUCCESS) {
        srs_error("notify handler for update m3u8 failed. ret=%d", ret);
        return ret;
    }
870 871
    
    return ret;
872 873
}
874
SrsHlsCache::SrsHlsCache()
winlin authored
875
{
876
    cache = new SrsTsCache();
winlin authored
877 878
}
879
SrsHlsCache::~SrsHlsCache()
winlin authored
880
{
881
    srs_freep(cache);
882
}
883 884 885 886 887 888 889 890

int SrsHlsCache::on_publish(SrsHlsMuxer* muxer, SrsRequest* req, int64_t segment_start_dts)
{
    int ret = ERROR_SUCCESS;

    std::string vhost = req->vhost;
    std::string stream = req->stream;
    std::string app = req->app;
891
    
892 893
    double hls_fragment = _srs_config->get_hls_fragment(vhost);
    double hls_window = _srs_config->get_hls_window(vhost);
894
    
895 896
    // get the hls m3u8 ts list entry prefix config
    std::string entry_prefix = _srs_config->get_hls_entry_prefix(vhost);
897
    // get the hls path config
898 899 900
    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);
901
    bool cleanup = _srs_config->get_hls_cleanup(vhost);
902
    bool wait_keyframe = _srs_config->get_hls_wait_keyframe(vhost);
903 904
    // the audio overflow, for pure audio to reap segment.
    double hls_aof_ratio = _srs_config->get_hls_aof_ratio(vhost);
905 906
    // whether use floor(timestamp/hls_fragment) for variable timestamp
    bool ts_floor = _srs_config->get_hls_ts_floor(vhost);
907
    
908 909 910
    // TODO: FIXME: support load exists m3u8, to continue publish stream.
    // for the HLS donot requires the EXT-X-MEDIA-SEQUENCE be monotonically increase.
    
911
    // open muxer
912
    if ((ret = muxer->update_config(req, entry_prefix,
913
        path, m3u8_file, ts_file, hls_fragment, hls_window, ts_floor, hls_aof_ratio,
914
        cleanup, wait_keyframe)) != ERROR_SUCCESS
915
    ) {
916 917 918 919 920 921 922 923
        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;
    }
924
    srs_trace("hls: win=%.2f, frag=%.2f, prefix=%s, path=%s, m3u8=%s, ts=%s, aof=%.2f, floor=%d, clean=%d, waitk=%d",
925
        hls_window, hls_fragment, entry_prefix.c_str(), path.c_str(), m3u8_file.c_str(),
926
        ts_file.c_str(), hls_aof_ratio, ts_floor, cleanup, wait_keyframe);
927 928 929 930 931 932 933 934
    
    return ret;
}

int SrsHlsCache::on_unpublish(SrsHlsMuxer* muxer)
{
    int ret = ERROR_SUCCESS;
    
935
    if ((ret = muxer->flush_audio(cache)) != ERROR_SUCCESS) {
936 937 938 939
        srs_error("m3u8 muxer flush audio failed. ret=%d", ret);
        return ret;
    }
    
940
    if ((ret = muxer->segment_close("unpublish")) != ERROR_SUCCESS) {
941 942 943 944 945
        return ret;
    }
    
    return ret;
}
winlin authored
946 947 948 949 950 951 952 953 954 955 956

int SrsHlsCache::on_sequence_header(SrsHlsMuxer* muxer)
{
    // TODO: support discontinuity for the same stream
    // currently we reap and insert discontinity when encoder republish,
    // but actually, event when stream is not republish, the 
    // sequence header may change, for example,
    // ffmpeg ingest a external rtmp stream and push to srs,
    // when the sequence header changed, the stream is not republish.
    return muxer->on_sequence_header();
}
957
    
958
int SrsHlsCache::write_audio(SrsAvcAacCodec* codec, SrsHlsMuxer* muxer, int64_t pts, SrsCodecSample* sample)
959
{
960 961 962
    int ret = ERROR_SUCCESS;
    
    // write audio to cache.
963
    if ((ret = cache->cache_audio(codec, pts, sample)) != ERROR_SUCCESS) {
964 965 966 967
        return ret;
    }
    
    // flush if buffer exceed max size.
968 969
    if (cache->audio->payload->length() > SRS_AUTO_HLS_AUDIO_CACHE_SIZE) {
        if ((ret = muxer->flush_audio(cache)) != ERROR_SUCCESS) {
970 971 972
            return ret;
        }
    }
973
974 975 976 977
    // TODO: config it.
    // in ms, audio delay to flush the audios.
    int64_t audio_delay = SRS_CONF_DEFAULT_AAC_DELAY;
    // flush if audio delay exceed
978 979 980
    // cache->audio will be free in flush_audio
    // so we must check whether it's null ptr.
    if (cache->audio && pts - cache->audio->start_pts > audio_delay * 90) {
981
        if ((ret = muxer->flush_audio(cache)) != ERROR_SUCCESS) {
982 983 984 985
            return ret;
        }
    }
    
986 987 988 989 990
    // reap when current source is pure audio.
    // it maybe changed when stream info changed,
    // for example, pure audio when start, audio/video when publishing,
    // pure audio again for audio disabled.
    // so we reap event when the audio incoming when segment overflow.
991
    // @see https://github.com/simple-rtmp-server/srs/issues/151
992
    // we use absolutely overflow of segment to make jwplayer/ffplay happy
993
    // @see https://github.com/simple-rtmp-server/srs/issues/151#issuecomment-71155184
994
    if (cache->audio && muxer->is_segment_absolutely_overflow()) {
995
        srs_info("hls: absolute audio reap segment.");
996
        if ((ret = reap_segment("audio", muxer, cache->audio->pts)) != ERROR_SUCCESS) {
997 998 999 1000
            return ret;
        }
    }
    
1001
    return ret;
1002
}
1003
    
1004
int SrsHlsCache::write_video(SrsAvcAacCodec* codec, SrsHlsMuxer* muxer, int64_t dts, SrsCodecSample* sample)
1005
{
1006 1007 1008
    int ret = ERROR_SUCCESS;
    
    // write video to cache.
1009
    if ((ret = cache->cache_video(codec, dts, sample)) != ERROR_SUCCESS) {
1010 1011 1012
        return ret;
    }
    
1013 1014 1015 1016 1017
    // when segment overflow, reap if possible.
    if (muxer->is_segment_overflow()) {
        // do reap ts if any of:
        //      a. wait keyframe and got keyframe.
        //      b. always reap when not wait keyframe.
1018
        if (!muxer->wait_keyframe()|| sample->frame_type == SrsCodecVideoAVCFrameKeyFrame) {
1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029
            // when wait keyframe, there must exists idr frame in sample.
            if (!sample->has_idr && muxer->wait_keyframe()) {
                srs_warn("hls: ts starts without IDR, first nalu=%d, idr=%d", sample->first_nalu_type, sample->has_idr);
            }
            
            // reap the segment, which will also flush the video.
            if ((ret = reap_segment("video", muxer, cache->video->dts)) != ERROR_SUCCESS) {
                return ret;
            }
            
            // the video must be flushed, just return.
1030 1031 1032 1033
            return ret;
        }
    }
    
1034
    // flush video when got one
1035
    if ((ret = muxer->flush_video(cache)) != ERROR_SUCCESS) {
1036 1037 1038 1039
        srs_error("m3u8 muxer flush video failed. ret=%d", ret);
        return ret;
    }
    
1040 1041 1042
    return ret;
}
1043
int SrsHlsCache::reap_segment(string log_desc, SrsHlsMuxer* muxer, int64_t segment_start_dts)
1044 1045
{
    int ret = ERROR_SUCCESS;
winlin authored
1046 1047 1048
    
    // TODO: flush audio before or after segment?
    // TODO: fresh segment begin with audio or video?
1049
winlin authored
1050
    // close current ts.
1051
    if ((ret = muxer->segment_close(log_desc)) != ERROR_SUCCESS) {
1052 1053 1054 1055
        srs_error("m3u8 muxer close segment failed. ret=%d", ret);
        return ret;
    }
    
winlin authored
1056
    // open new ts.
1057 1058 1059 1060
    if ((ret = muxer->segment_open(segment_start_dts)) != ERROR_SUCCESS) {
        srs_error("m3u8 muxer open segment failed. ret=%d", ret);
        return ret;
    }
1061 1062
    
    // segment open, flush video first.
1063
    if ((ret = muxer->flush_video(cache)) != ERROR_SUCCESS) {
1064 1065 1066
        srs_error("m3u8 muxer flush video failed. ret=%d", ret);
        return ret;
    }
winlin authored
1067
    
1068 1069 1070
    // segment open, flush the audio.
    // @see: ngx_rtmp_hls_open_fragment
    /* start fragment with audio to make iPhone happy */
1071
    if ((ret = muxer->flush_audio(cache)) != ERROR_SUCCESS) {
1072 1073 1074
        srs_error("m3u8 muxer flush audio failed. ret=%d", ret);
        return ret;
    }
1075 1076
    
    return ret;
1077 1078
}
1079
SrsHls::SrsHls()
1080
{
1081 1082
    source = NULL;
    handler = NULL;
1083
    
1084 1085
    hls_enabled = false;
1086
    codec = new SrsAvcAacCodec();
1087 1088 1089
    sample = new SrsCodecSample();
    jitter = new SrsRtmpJitter();
    
1090
    muxer = new SrsHlsMuxer();
1091
    hls_cache = new SrsHlsCache();
1092
1093
    pprint = SrsPithyPrint::create_hls();
1094
    stream_dts = 0;
1095 1096 1097 1098
}

SrsHls::~SrsHls()
{
1099 1100 1101 1102 1103
    srs_freep(codec);
    srs_freep(sample);
    srs_freep(jitter);
    
    srs_freep(muxer);
1104
    srs_freep(hls_cache);
1105
    
1106
    srs_freep(pprint);
winlin authored
1107 1108
}
1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122
int SrsHls::initialize(SrsSource* s, ISrsHlsHandler* h)
{
    int ret = ERROR_SUCCESS;

    source = s;
    handler = h;

    if ((ret = muxer->initialize(h)) != ERROR_SUCCESS) {
        return ret;
    }

    return ret;
}
winlin authored
1123 1124
int SrsHls::on_publish(SrsRequest* req)
{
1125 1126 1127 1128 1129 1130 1131
    int ret = ERROR_SUCCESS;
    
    // support multiple publish.
    if (hls_enabled) {
        return ret;
    }
    
1132
    std::string vhost = req->vhost;
1133 1134 1135 1136
    if (!_srs_config->get_hls_enabled(vhost)) {
        return ret;
    }
    
1137
    if ((ret = hls_cache->on_publish(muxer, req, stream_dts)) != ERROR_SUCCESS) {
1138 1139 1140
        return ret;
    }
    
winlin authored
1141 1142 1143
    // if enabled, open the muxer.
    hls_enabled = true;
    
1144
    // notice the source to get the cached sequence header.
1145 1146
    // 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.
1147 1148 1149 1150 1151 1152
    if ((ret = source->on_hls_start()) != ERROR_SUCCESS) {
        srs_error("callback source hls start failed. ret=%d", ret);
        return ret;
    }

    return ret;
winlin authored
1153 1154 1155 1156
}

void SrsHls::on_unpublish()
{
1157 1158 1159 1160 1161 1162 1163
    int ret = ERROR_SUCCESS;
    
    // support multiple unpublish.
    if (!hls_enabled) {
        return;
    }
1164
    if ((ret = hls_cache->on_unpublish(muxer)) != ERROR_SUCCESS) {
1165 1166 1167 1168
        srs_error("ignore m3u8 muxer flush/close audio failed. ret=%d", ret);
    }
    
    hls_enabled = false;
winlin authored
1169 1170
}
1171
int SrsHls::on_meta_data(SrsAmf0Object* metadata)
winlin authored
1172
{
1173 1174 1175 1176 1177 1178 1179
    int ret = ERROR_SUCCESS;

    if (!metadata) {
        srs_trace("no metadata persent, hls ignored it.");
        return ret;
    }
    
1180
    if (metadata->count() <= 0) {
1181 1182 1183 1184 1185
        srs_trace("no metadata persent, hls ignored it.");
        return ret;
    }
    
    return ret;
winlin authored
1186 1187
}
1188
int SrsHls::on_audio(SrsSharedPtrMessage* shared_audio)
winlin authored
1189
{
1190 1191 1192 1193 1194
    int ret = ERROR_SUCCESS;
    
    if (!hls_enabled) {
        return ret;
    }
1195
1196
    SrsSharedPtrMessage* audio = shared_audio->copy();
1197
    SrsAutoFree(SrsSharedPtrMessage, audio);
1198 1199 1200
    
    sample->clear();
    if ((ret = codec->audio_aac_demux(audio->payload, audio->size, sample)) != ERROR_SUCCESS) {
1201 1202 1203 1204 1205 1206 1207 1208
        if (ret != ERROR_HLS_TRY_MP3) {
            srs_error("hls aac demux audio failed. ret=%d", ret);
            return ret;
        }
        if ((ret = codec->audio_mp3_demux(audio->payload, audio->size, sample)) != ERROR_SUCCESS) {
            srs_error("hls mp3 demux audio failed. ret=%d", ret);
            return ret;
        }
1209
    }
1210 1211
    srs_info("audio decoded, type=%d, codec=%d, cts=%d, size=%d, time=%"PRId64, 
        sample->frame_type, codec->audio_codec_id, sample->cts, audio->size, audio->timestamp);
1212
    SrsCodecAudio acodec = (SrsCodecAudio)codec->audio_codec_id;
1213
    
1214 1215 1216 1217 1218 1219 1220 1221
    // ts support audio codec: aac/mp3
    if (acodec != SrsCodecAudioAAC && acodec != SrsCodecAudioMP3) {
        return ret;
    }

    // when codec changed, write new header.
    if ((ret = muxer->update_acodec(acodec)) != ERROR_SUCCESS) {
        srs_error("http: ts audio write header failed. ret=%d", ret);
1222 1223 1224 1225
        return ret;
    }
    
    // ignore sequence header
1226
    if (acodec == SrsCodecAudioAAC && sample->aac_packet_type == SrsCodecAudioTypeSequenceHeader) {
winlin authored
1227
        return hls_cache->on_sequence_header(muxer);
1228 1229
    }
    
1230
    if ((ret = jitter->correct(audio, 0, 0, SrsRtmpJitterAlgorithmFULL)) != ERROR_SUCCESS) {
1231 1232 1233 1234
        srs_error("rtmp jitter correct audio failed. ret=%d", ret);
        return ret;
    }
    
1235 1236
    // the dts calc from rtmp/flv header.
    int64_t dts = audio->timestamp * 90;
1237
    
1238
    // for pure audio, we need to update the stream dts also.
1239
    stream_dts = dts;
1240
    
1241
    if ((ret = hls_cache->write_audio(codec, muxer, dts, sample)) != ERROR_SUCCESS) {
1242
        srs_error("hls cache write audio failed. ret=%d", ret);
1243 1244 1245 1246
        return ret;
    }
    
    return ret;
winlin authored
1247 1248
}
1249
int SrsHls::on_video(SrsSharedPtrMessage* shared_video)
winlin authored
1250
{
1251 1252 1253 1254 1255
    int ret = ERROR_SUCCESS;
    
    if (!hls_enabled) {
        return ret;
    }
1256
1257
    SrsSharedPtrMessage* video = shared_video->copy();
1258
    SrsAutoFree(SrsSharedPtrMessage, video);
1259 1260 1261
    
    sample->clear();
    if ((ret = codec->video_avc_demux(video->payload, video->size, sample)) != ERROR_SUCCESS) {
1262
        srs_error("hls codec demux video failed. ret=%d", ret);
1263 1264
        return ret;
    }
1265 1266
    srs_info("video decoded, type=%d, codec=%d, avc=%d, cts=%d, size=%d, time=%"PRId64, 
        sample->frame_type, codec->video_codec_id, sample->avc_packet_type, sample->cts, video->size, video->timestamp);
1267
    
1268
    // ignore info frame,
1269
    // @see https://github.com/simple-rtmp-server/srs/issues/288#issuecomment-69863909
1270 1271 1272 1273
    if (sample->frame_type == SrsCodecVideoAVCFrameVideoInfoFrame) {
        return ret;
    }
    
1274 1275 1276 1277 1278 1279 1280
    if (codec->video_codec_id != SrsCodecVideoAVC) {
        return ret;
    }
    
    // ignore sequence header
    if (sample->frame_type == SrsCodecVideoAVCFrameKeyFrame
         && sample->avc_packet_type == SrsCodecVideoAVCTypeSequenceHeader) {
winlin authored
1281
        return hls_cache->on_sequence_header(muxer);
1282 1283
    }
    
1284
    if ((ret = jitter->correct(video, 0, 0, SrsRtmpJitterAlgorithmFULL)) != ERROR_SUCCESS) {
1285 1286 1287 1288
        srs_error("rtmp jitter correct video failed. ret=%d", ret);
        return ret;
    }
    
1289
    int64_t dts = video->timestamp * 90;
1290 1291 1292
    stream_dts = dts;
    if ((ret = hls_cache->write_video(codec, muxer, dts, sample)) != ERROR_SUCCESS) {
        srs_error("hls cache write video failed. ret=%d", ret);
1293 1294 1295
        return ret;
    }
    
1296 1297
    // pithy print message.
    hls_show_mux_log();
1298 1299
    
    return ret;
1300 1301
}
1302
void SrsHls::hls_show_mux_log()
winlin authored
1303
{
1304 1305
    pprint->elapse();
1306
    // reportable
1307
    if (pprint->can_print()) {
1308
        // the run time is not equals to stream time,
1309
        // @see: https://github.com/simple-rtmp-server/srs/issues/81#issuecomment-48100994
1310
        // it's ok.
1311
        srs_trace("-> "SRS_CONSTS_LOG_HLS" time=%"PRId64", stream dts=%"PRId64"(%"PRId64"ms), sno=%d, ts=%s, dur=%.2f, dva=%dp",
1312
            pprint->age(), stream_dts, stream_dts / 90, muxer->sequence_no(), muxer->ts_url().c_str(),
1313
            muxer->duration(), muxer->deviation());
1314
    }
winlin authored
1315 1316
}
1317 1318
#endif
1319