2013-11-26 02:56:29 +00:00
|
|
|
/*
|
|
|
|
The MIT License (MIT)
|
|
|
|
|
2014-12-31 12:32:09 +00:00
|
|
|
Copyright (c) 2013-2015 winlin
|
2013-11-26 02:56:29 +00:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
|
2014-03-02 13:49:09 +00:00
|
|
|
#include <srs_app_hls.hpp>
|
2013-11-26 02:56:29 +00:00
|
|
|
|
2014-07-13 05:42:08 +00:00
|
|
|
/**
|
|
|
|
* the HLS section, only available when HLS enabled.
|
|
|
|
*/
|
2014-04-15 06:01:57 +00:00
|
|
|
#ifdef SRS_AUTO_HLS
|
2013-11-27 14:41:58 +00:00
|
|
|
|
2013-11-26 02:56:29 +00:00
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2013-12-02 03:46:39 +00:00
|
|
|
#include <algorithm>
|
2014-03-21 05:10:47 +00:00
|
|
|
using namespace std;
|
2013-12-02 03:46:39 +00:00
|
|
|
|
2014-03-01 02:30:16 +00:00
|
|
|
#include <srs_kernel_error.hpp>
|
2014-05-28 09:37:15 +00:00
|
|
|
#include <srs_kernel_codec.hpp>
|
2014-03-01 03:24:40 +00:00
|
|
|
#include <srs_protocol_amf0.hpp>
|
2014-07-12 00:47:47 +00:00
|
|
|
#include <srs_protocol_stack.hpp>
|
2014-03-02 13:49:09 +00:00
|
|
|
#include <srs_app_config.hpp>
|
|
|
|
#include <srs_app_source.hpp>
|
2013-11-26 03:48:18 +00:00
|
|
|
#include <srs_core_autofree.hpp>
|
2014-03-01 06:03:02 +00:00
|
|
|
#include <srs_protocol_rtmp.hpp>
|
2014-03-02 13:49:09 +00:00
|
|
|
#include <srs_app_pithy_print.hpp>
|
2014-06-08 05:03:03 +00:00
|
|
|
#include <srs_kernel_utility.hpp>
|
2015-01-22 09:08:38 +00:00
|
|
|
#include <srs_kernel_avc.hpp>
|
2014-07-04 23:33:18 +00:00
|
|
|
#include <srs_kernel_file.hpp>
|
2014-12-03 10:56:09 +00:00
|
|
|
#include <srs_protocol_buffer.hpp>
|
2013-11-26 02:56:29 +00:00
|
|
|
|
2013-12-02 07:55:10 +00:00
|
|
|
// max PES packets size to flush the video.
|
2014-04-15 06:01:57 +00:00
|
|
|
#define SRS_AUTO_HLS_AUDIO_CACHE_SIZE 1024 * 1024
|
2013-12-02 07:55:10 +00:00
|
|
|
|
2014-03-21 09:35:27 +00:00
|
|
|
// drop the segment when duration of ts too small.
|
2014-04-15 06:01:57 +00:00
|
|
|
#define SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS 100
|
2014-03-21 09:35:27 +00:00
|
|
|
|
2013-11-26 08:06:58 +00:00
|
|
|
// @see: NGX_RTMP_HLS_DELAY,
|
|
|
|
// 63000: 700ms, ts_tbn=90000
|
2014-04-15 06:01:57 +00:00
|
|
|
#define SRS_AUTO_HLS_DELAY 63000
|
2013-11-26 02:56:29 +00:00
|
|
|
|
|
|
|
// @see: ngx_rtmp_mpegts_header
|
|
|
|
u_int8_t mpegts_header[] = {
|
|
|
|
/* TS */
|
|
|
|
0x47, 0x40, 0x00, 0x10, 0x00,
|
|
|
|
/* PSI */
|
|
|
|
0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00,
|
|
|
|
/* PAT */
|
|
|
|
0x00, 0x01, 0xf0, 0x01,
|
|
|
|
/* CRC */
|
|
|
|
0x2e, 0x70, 0x19, 0x05,
|
|
|
|
/* stuffing 167 bytes */
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
|
|
|
|
/* TS */
|
|
|
|
0x47, 0x50, 0x01, 0x10, 0x00,
|
|
|
|
/* PSI */
|
|
|
|
0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00,
|
|
|
|
/* PMT */
|
|
|
|
0xe1, 0x00,
|
|
|
|
0xf0, 0x00,
|
2014-10-08 07:15:57 +00:00
|
|
|
// must generate header with/without video, @see:
|
|
|
|
// https://github.com/winlinvip/simple-rtmp-server/issues/40
|
2013-11-26 02:56:29 +00:00
|
|
|
0x1b, 0xe1, 0x00, 0xf0, 0x00, /* h264, pid=0x100=256 */
|
|
|
|
0x0f, 0xe1, 0x01, 0xf0, 0x00, /* aac, pid=0x101=257 */
|
|
|
|
/*0x03, 0xe1, 0x01, 0xf0, 0x00,*/ /* mp3 */
|
|
|
|
/* CRC */
|
|
|
|
0x2f, 0x44, 0xb9, 0x9b, /* crc for aac */
|
|
|
|
/*0x4e, 0x59, 0x3d, 0x1e,*/ /* crc for mp3 */
|
|
|
|
/* stuffing 157 bytes */
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
|
|
|
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
|
|
|
|
};
|
|
|
|
|
|
|
|
// @see: ngx_rtmp_mpegts.c
|
|
|
|
// TODO: support full mpegts feature in future.
|
|
|
|
class SrsMpegtsWriter
|
|
|
|
{
|
|
|
|
public:
|
2014-07-04 23:33:18 +00:00
|
|
|
static int write_header(SrsFileWriter* writer)
|
2014-03-18 03:32:58 +00:00
|
|
|
{
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
2014-07-04 23:33:18 +00:00
|
|
|
if ((ret = writer->write(mpegts_header, sizeof(mpegts_header), NULL)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
ret = ERROR_HLS_WRITE_FAILED;
|
|
|
|
srs_error("write ts file header failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2014-12-04 03:27:07 +00:00
|
|
|
static int write_frame(SrsFileWriter* writer, SrsMpegtsFrame* frame, SrsSimpleBuffer* buffer)
|
2014-03-18 03:32:58 +00:00
|
|
|
{
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
2014-07-15 02:44:06 +00:00
|
|
|
if (!buffer->bytes() || buffer->length() <= 0) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-07-15 02:44:06 +00:00
|
|
|
char* last = buffer->bytes() + buffer->length();
|
|
|
|
char* pos = buffer->bytes();
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
bool first = true;
|
|
|
|
while (pos < last) {
|
|
|
|
static char packet[188];
|
|
|
|
char* p = packet;
|
|
|
|
|
|
|
|
frame->cc++;
|
|
|
|
|
|
|
|
// sync_byte; //8bits
|
|
|
|
*p++ = 0x47;
|
|
|
|
// pid; //13bits
|
|
|
|
*p++ = (frame->pid >> 8) & 0x1f;
|
|
|
|
// payload_unit_start_indicator; //1bit
|
|
|
|
if (first) {
|
|
|
|
p[-1] |= 0x40;
|
|
|
|
}
|
|
|
|
*p++ = frame->pid;
|
|
|
|
|
|
|
|
// transport_scrambling_control; //2bits
|
|
|
|
// adaption_field_control; //2bits, 0x01: PayloadOnly
|
|
|
|
// continuity_counter; //4bits
|
|
|
|
*p++ = 0x10 | (frame->cc & 0x0f);
|
|
|
|
|
|
|
|
if (first) {
|
|
|
|
first = false;
|
|
|
|
if (frame->key) {
|
|
|
|
p[-1] |= 0x20; // Both Adaption and Payload
|
|
|
|
*p++ = 7; // size
|
|
|
|
*p++ = 0x50; // random access + PCR
|
2014-04-15 06:01:57 +00:00
|
|
|
p = write_pcr(p, frame->dts - SRS_AUTO_HLS_DELAY);
|
2014-03-18 03:32:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// PES header
|
|
|
|
// packet_start_code_prefix; //24bits, '00 00 01'
|
|
|
|
*p++ = 0x00;
|
|
|
|
*p++ = 0x00;
|
|
|
|
*p++ = 0x01;
|
|
|
|
//8bits
|
|
|
|
*p++ = frame->sid;
|
|
|
|
|
|
|
|
// pts(33bits) need 5bytes.
|
|
|
|
u_int8_t header_size = 5;
|
|
|
|
u_int8_t flags = 0x80; // pts
|
|
|
|
|
|
|
|
// dts(33bits) need 5bytes also
|
|
|
|
if (frame->dts != frame->pts) {
|
|
|
|
header_size += 5;
|
|
|
|
flags |= 0x40; // dts
|
|
|
|
}
|
|
|
|
|
|
|
|
// 3bytes: flag fields from PES_packet_length to PES_header_data_length
|
|
|
|
int pes_size = (last - pos) + header_size + 3;
|
|
|
|
if (pes_size > 0xffff) {
|
|
|
|
/**
|
|
|
|
* when actual packet length > 0xffff(65535),
|
|
|
|
* which exceed the max u_int16_t packet length,
|
|
|
|
* use 0 packet length, the next unit start indicates the end of packet.
|
|
|
|
*/
|
|
|
|
pes_size = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// PES_packet_length; //16bits
|
|
|
|
*p++ = (pes_size >> 8);
|
|
|
|
*p++ = pes_size;
|
|
|
|
|
|
|
|
// PES_scrambling_control; //2bits, '10'
|
|
|
|
// PES_priority; //1bit
|
|
|
|
// data_alignment_indicator; //1bit
|
|
|
|
// copyright; //1bit
|
|
|
|
// original_or_copy; //1bit
|
|
|
|
*p++ = 0x80; /* H222 */
|
|
|
|
|
|
|
|
// PTS_DTS_flags; //2bits
|
|
|
|
// ESCR_flag; //1bit
|
|
|
|
// ES_rate_flag; //1bit
|
|
|
|
// DSM_trick_mode_flag; //1bit
|
|
|
|
// additional_copy_info_flag; //1bit
|
|
|
|
// PES_CRC_flag; //1bit
|
|
|
|
// PES_extension_flag; //1bit
|
|
|
|
*p++ = flags;
|
|
|
|
|
|
|
|
// PES_header_data_length; //8bits
|
|
|
|
*p++ = header_size;
|
|
|
|
|
|
|
|
// pts; // 33bits
|
2014-04-15 06:01:57 +00:00
|
|
|
p = write_pts(p, flags >> 6, frame->pts + SRS_AUTO_HLS_DELAY);
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
// dts; // 33bits
|
|
|
|
if (frame->dts != frame->pts) {
|
2014-04-15 06:01:57 +00:00
|
|
|
p = write_pts(p, 1, frame->dts + SRS_AUTO_HLS_DELAY);
|
2014-03-18 03:32:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int body_size = sizeof(packet) - (p - packet);
|
|
|
|
int in_size = last - pos;
|
|
|
|
|
|
|
|
if (body_size <= in_size) {
|
|
|
|
memcpy(p, pos, body_size);
|
|
|
|
pos += body_size;
|
|
|
|
} else {
|
|
|
|
p = fill_stuff(p, packet, body_size, in_size);
|
|
|
|
memcpy(p, pos, in_size);
|
|
|
|
pos = last;
|
|
|
|
}
|
|
|
|
|
|
|
|
// write ts packet
|
2014-07-04 23:33:18 +00:00
|
|
|
if ((ret = writer->write(packet, sizeof(packet), NULL)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
ret = ERROR_HLS_WRITE_FAILED;
|
|
|
|
srs_error("write ts file failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2013-11-26 02:56:29 +00:00
|
|
|
private:
|
2014-03-18 03:32:58 +00:00
|
|
|
static char* fill_stuff(char* pes_body_end, char* packet, int body_size, int in_size)
|
|
|
|
{
|
|
|
|
char* p = pes_body_end;
|
|
|
|
|
|
|
|
// insert the stuff bytes before PES body
|
|
|
|
int stuff_size = (body_size - in_size);
|
|
|
|
|
|
|
|
// adaption_field_control; //2bits
|
|
|
|
if (packet[3] & 0x20) {
|
|
|
|
// has adaptation
|
|
|
|
// packet[4]: adaption_field_length
|
|
|
|
// packet[5]: adaption field data
|
|
|
|
// base: start of PES body
|
|
|
|
char* base = &packet[5] + packet[4];
|
|
|
|
int len = p - base;
|
|
|
|
p = (char*)memmove(base + stuff_size, base, len) + len;
|
|
|
|
// increase the adaption field size.
|
|
|
|
packet[4] += stuff_size;
|
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
// create adaption field.
|
|
|
|
// adaption_field_control; //2bits
|
|
|
|
packet[3] |= 0x20;
|
|
|
|
// base: start of PES body
|
|
|
|
char* base = &packet[4];
|
|
|
|
int len = p - base;
|
|
|
|
p = (char*)memmove(base + stuff_size, base, len) + len;
|
|
|
|
// adaption_field_length; //8bits
|
|
|
|
packet[4] = (stuff_size - 1);
|
|
|
|
if (stuff_size >= 2) {
|
|
|
|
// adaption field flags.
|
|
|
|
packet[5] = 0;
|
|
|
|
// adaption data.
|
|
|
|
if (stuff_size > 2) {
|
|
|
|
memset(&packet[6], 0xff, stuff_size - 2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
static char* write_pcr(char* p, int64_t pcr)
|
|
|
|
{
|
2014-12-29 00:38:29 +00:00
|
|
|
// the pcr=dts-delay
|
|
|
|
// and the pcr maybe negative
|
|
|
|
// @see https://github.com/winlinvip/simple-rtmp-server/issues/268
|
|
|
|
int64_t v = srs_max(0, pcr);
|
|
|
|
|
|
|
|
*p++ = (char) (v >> 25);
|
|
|
|
*p++ = (char) (v >> 17);
|
|
|
|
*p++ = (char) (v >> 9);
|
|
|
|
*p++ = (char) (v >> 1);
|
|
|
|
*p++ = (char) (v << 7 | 0x7e);
|
2014-03-18 03:32:58 +00:00
|
|
|
*p++ = 0;
|
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
static char* write_pts(char* p, u_int8_t fb, int64_t pts)
|
|
|
|
{
|
|
|
|
int32_t val;
|
|
|
|
|
|
|
|
val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1;
|
|
|
|
*p++ = val;
|
|
|
|
|
|
|
|
val = (((pts >> 15) & 0x7fff) << 1) | 1;
|
|
|
|
*p++ = (val >> 8);
|
|
|
|
*p++ = val;
|
|
|
|
|
|
|
|
val = (((pts) & 0x7fff) << 1) | 1;
|
|
|
|
*p++ = (val >> 8);
|
|
|
|
*p++ = val;
|
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
2013-11-26 02:56:29 +00:00
|
|
|
};
|
|
|
|
|
2013-12-02 06:24:09 +00:00
|
|
|
SrsTSMuxer::SrsTSMuxer()
|
|
|
|
{
|
2014-07-04 23:33:18 +00:00
|
|
|
writer = new SrsFileWriter();
|
2013-12-02 06:24:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SrsTSMuxer::~SrsTSMuxer()
|
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
close();
|
2014-07-04 23:33:18 +00:00
|
|
|
srs_freep(writer);
|
2013-12-02 06:24:09 +00:00
|
|
|
}
|
|
|
|
|
2014-03-21 05:10:47 +00:00
|
|
|
int SrsTSMuxer::open(string _path)
|
2013-12-02 06:24:09 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
path = _path;
|
|
|
|
|
|
|
|
close();
|
|
|
|
|
2014-07-04 23:33:18 +00:00
|
|
|
if ((ret = writer->open(path)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// write mpegts header
|
2014-07-04 23:33:18 +00:00
|
|
|
if ((ret = SrsMpegtsWriter::write_header(writer)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2013-12-02 06:24:09 +00:00
|
|
|
}
|
|
|
|
|
2014-12-04 03:27:07 +00:00
|
|
|
int SrsTSMuxer::write_audio(SrsMpegtsFrame* af, SrsSimpleBuffer* ab)
|
2013-12-02 06:24:09 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
2014-07-04 23:33:18 +00:00
|
|
|
if ((ret = SrsMpegtsWriter::write_frame(writer, af, ab)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2013-12-02 06:24:09 +00:00
|
|
|
}
|
|
|
|
|
2014-12-04 03:27:07 +00:00
|
|
|
int SrsTSMuxer::write_video(SrsMpegtsFrame* vf, SrsSimpleBuffer* vb)
|
2013-12-02 06:24:09 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
2014-07-04 23:33:18 +00:00
|
|
|
if ((ret = SrsMpegtsWriter::write_frame(writer, vf, vb)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2013-12-02 06:24:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SrsTSMuxer::close()
|
|
|
|
{
|
2014-07-04 23:33:18 +00:00
|
|
|
writer->close();
|
2013-12-02 06:24:09 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment::SrsHlsSegment()
|
2013-12-02 06:24:09 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
duration = 0;
|
|
|
|
sequence_no = 0;
|
|
|
|
muxer = new SrsTSMuxer();
|
|
|
|
segment_start_dts = 0;
|
2014-03-21 09:10:24 +00:00
|
|
|
is_sequence_header = false;
|
2013-12-02 06:24:09 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment::~SrsHlsSegment()
|
2013-11-26 02:56:29 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_freep(muxer);
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
2013-11-27 09:30:16 +00:00
|
|
|
|
2014-03-20 10:55:45 +00:00
|
|
|
void SrsHlsSegment::update_duration(int64_t current_frame_dts)
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2014-03-20 10:55:45 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
duration = (current_frame_dts - segment_start_dts) / 90000.0;
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_assert(duration >= 0);
|
|
|
|
|
2014-03-20 10:55:45 +00:00
|
|
|
return;
|
2013-11-26 02:56:29 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsMuxer::SrsHlsMuxer()
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
hls_fragment = hls_window = 0;
|
2014-05-04 02:45:13 +00:00
|
|
|
_sequence_no = 0;
|
2014-03-18 03:32:58 +00:00
|
|
|
current = NULL;
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
2013-11-26 08:06:58 +00:00
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsMuxer::~SrsHlsMuxer()
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2014-03-20 10:19:08 +00:00
|
|
|
std::vector<SrsHlsSegment*>::iterator it;
|
2014-03-18 03:32:58 +00:00
|
|
|
for (it = segments.begin(); it != segments.end(); ++it) {
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment* segment = *it;
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_freep(segment);
|
|
|
|
}
|
|
|
|
segments.clear();
|
|
|
|
|
|
|
|
srs_freep(current);
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
2014-05-04 02:45:13 +00:00
|
|
|
int SrsHlsMuxer::sequence_no()
|
|
|
|
{
|
|
|
|
return _sequence_no;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
int SrsHlsMuxer::update_config(
|
2014-03-21 05:10:47 +00:00
|
|
|
string _app, string _stream, string path, int fragment, int window
|
2013-12-02 07:55:10 +00:00
|
|
|
) {
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
app = _app;
|
|
|
|
stream = _stream;
|
|
|
|
hls_path = path;
|
|
|
|
hls_fragment = fragment;
|
|
|
|
hls_window = window;
|
|
|
|
|
|
|
|
return ret;
|
2013-11-26 02:56:29 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
int SrsHlsMuxer::segment_open(int64_t segment_start_dts)
|
2013-11-26 08:06:58 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
if (current) {
|
|
|
|
srs_warn("ignore the segment open, for segment is already open.");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: create all parents dirs.
|
|
|
|
// create dir for app.
|
|
|
|
if ((ret = create_dir()) != ERROR_SUCCESS) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// when segment open, the current segment must be NULL.
|
|
|
|
srs_assert(!current);
|
|
|
|
|
|
|
|
// new segment.
|
2014-03-20 10:19:08 +00:00
|
|
|
current = new SrsHlsSegment();
|
2014-05-04 02:45:13 +00:00
|
|
|
current->sequence_no = _sequence_no++;
|
2014-03-20 10:19:08 +00:00
|
|
|
current->segment_start_dts = segment_start_dts;
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
// generate filename.
|
|
|
|
char filename[128];
|
|
|
|
snprintf(filename, sizeof(filename),
|
|
|
|
"%s-%d.ts", stream.c_str(), current->sequence_no);
|
|
|
|
|
|
|
|
// TODO: use temp file and rename it.
|
|
|
|
current->full_path = hls_path;
|
|
|
|
current->full_path += "/";
|
|
|
|
current->full_path += app;
|
|
|
|
current->full_path += "/";
|
|
|
|
current->full_path += filename;
|
|
|
|
|
|
|
|
// TODO: support base url, and so on.
|
|
|
|
current->uri = filename;
|
|
|
|
|
2014-03-21 05:10:47 +00:00
|
|
|
std::string tmp_file = current->full_path + ".tmp";
|
|
|
|
if ((ret = current->muxer->open(tmp_file.c_str())) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("open hls muxer failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
2014-04-03 10:32:51 +00:00
|
|
|
srs_info("open HLS muxer success. path=%s, tmp=%s",
|
|
|
|
current->full_path.c_str(), tmp_file.c_str());
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
return ret;
|
2013-11-26 08:06:58 +00:00
|
|
|
}
|
|
|
|
|
2014-03-21 09:10:24 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
bool SrsHlsMuxer::is_segment_overflow()
|
|
|
|
{
|
|
|
|
srs_assert(current);
|
|
|
|
|
|
|
|
return current->duration >= hls_fragment;
|
|
|
|
}
|
|
|
|
|
2014-12-04 03:27:07 +00:00
|
|
|
int SrsHlsMuxer::flush_audio(SrsMpegtsFrame* af, SrsSimpleBuffer* ab)
|
2013-11-26 08:06:58 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2014-07-15 02:44:06 +00:00
|
|
|
if (ab->length() <= 0) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:55:45 +00:00
|
|
|
// update the duration of segment.
|
|
|
|
current->update_duration(af->pts);
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
if ((ret = current->muxer->write_audio(af, ab)) != ERROR_SUCCESS) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// write success, clear and free the buffer
|
2014-07-15 02:44:06 +00:00
|
|
|
ab->erase(ab->length());
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
return ret;
|
2013-11-26 08:06:58 +00:00
|
|
|
}
|
|
|
|
|
2014-12-04 03:27:07 +00:00
|
|
|
int SrsHlsMuxer::flush_video(SrsMpegtsFrame* /*af*/, SrsSimpleBuffer* /*ab*/, SrsMpegtsFrame* vf, SrsSimpleBuffer* vb)
|
2013-11-26 08:06:58 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
srs_assert(current);
|
|
|
|
|
|
|
|
// update the duration of segment.
|
2014-03-20 10:19:08 +00:00
|
|
|
current->update_duration(vf->dts);
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
if ((ret = current->muxer->write_video(vf, vb)) != ERROR_SUCCESS) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:55:45 +00:00
|
|
|
// write success, clear and free the buffer
|
2014-07-15 02:44:06 +00:00
|
|
|
vb->erase(vb->length());
|
2014-03-20 10:55:45 +00:00
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
2014-03-21 05:10:47 +00:00
|
|
|
int SrsHlsMuxer::segment_close(string log_desc)
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
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.
|
2014-03-20 10:19:08 +00:00
|
|
|
std::vector<SrsHlsSegment*>::iterator it;
|
2014-03-18 03:32:58 +00:00
|
|
|
it = std::find(segments.begin(), segments.end(), current);
|
|
|
|
srs_assert(it == segments.end());
|
|
|
|
|
2014-03-21 09:35:27 +00:00
|
|
|
// valid, add to segments if segment duration is ok
|
2014-04-15 06:01:57 +00:00
|
|
|
if (current->duration * 1000 >= SRS_AUTO_HLS_SEGMENT_MIN_DURATION_MS) {
|
2014-03-21 09:35:27 +00:00
|
|
|
segments.push_back(current);
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2014-05-04 02:45:13 +00:00
|
|
|
srs_info("%s reap ts segment, sequence_no=%d, uri=%s, duration=%.2f, start=%"PRId64"",
|
2014-03-21 09:35:27 +00:00
|
|
|
log_desc.c_str(), current->sequence_no, current->uri.c_str(), current->duration,
|
|
|
|
current->segment_start_dts);
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2014-03-21 09:35:27 +00:00
|
|
|
// close the muxer of finished segment.
|
|
|
|
srs_freep(current->muxer);
|
2014-03-26 10:13:33 +00:00
|
|
|
std::string full_path = current->full_path;
|
|
|
|
current = NULL;
|
|
|
|
|
2014-03-21 09:35:27 +00:00
|
|
|
// rename from tmp to real path
|
2014-03-26 10:13:33 +00:00
|
|
|
std::string tmp_file = full_path + ".tmp";
|
|
|
|
if (rename(tmp_file.c_str(), full_path.c_str()) < 0) {
|
2014-03-21 09:35:27 +00:00
|
|
|
ret = ERROR_HLS_WRITE_FAILED;
|
|
|
|
srs_error("rename ts file failed, %s => %s. ret=%d",
|
2014-03-26 10:13:33 +00:00
|
|
|
tmp_file.c_str(), full_path.c_str(), ret);
|
2014-03-21 09:35:27 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// reuse current segment index.
|
2014-05-04 02:45:13 +00:00
|
|
|
_sequence_no--;
|
2014-03-21 09:35:27 +00:00
|
|
|
|
|
|
|
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";
|
|
|
|
unlink(tmp_file.c_str());
|
|
|
|
|
|
|
|
srs_freep(current);
|
2014-03-21 05:10:47 +00:00
|
|
|
}
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
// the segments to remove
|
2014-03-20 10:19:08 +00:00
|
|
|
std::vector<SrsHlsSegment*> segment_to_remove;
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
// shrink the segments.
|
|
|
|
double duration = 0;
|
|
|
|
int remove_index = -1;
|
|
|
|
for (int i = segments.size() - 1; i >= 0; i--) {
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment* segment = segments[i];
|
2014-03-18 03:32:58 +00:00
|
|
|
duration += segment->duration;
|
|
|
|
|
|
|
|
if ((int)duration > hls_window) {
|
|
|
|
remove_index = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (int i = 0; i < remove_index && !segments.empty(); i++) {
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment* segment = *segments.begin();
|
2014-03-18 03:32:58 +00:00
|
|
|
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++) {
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment* segment = segment_to_remove[i];
|
2014-03-18 03:32:58 +00:00
|
|
|
unlink(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;
|
2013-11-26 08:06:58 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
int SrsHlsMuxer::refresh_m3u8()
|
2013-11-26 08:06:58 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
std::string m3u8_file = hls_path;
|
|
|
|
m3u8_file += "/";
|
|
|
|
m3u8_file += app;
|
|
|
|
m3u8_file += "/";
|
|
|
|
m3u8_file += stream;
|
|
|
|
m3u8_file += ".m3u8";
|
|
|
|
|
|
|
|
m3u8 = m3u8_file;
|
|
|
|
m3u8_file += ".temp";
|
|
|
|
|
|
|
|
int fd = -1;
|
|
|
|
ret = _refresh_m3u8(fd, m3u8_file);
|
|
|
|
if (fd >= 0) {
|
|
|
|
close(fd);
|
|
|
|
if (rename(m3u8_file.c_str(), m3u8.c_str()) < 0) {
|
|
|
|
ret = ERROR_HLS_WRITE_FAILED;
|
2014-03-21 05:10:47 +00:00
|
|
|
srs_error("rename m3u8 file failed. "
|
|
|
|
"%s => %s, ret=%d", m3u8_file.c_str(), m3u8.c_str(), ret);
|
2014-03-18 03:32:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove the temp file.
|
|
|
|
unlink(m3u8_file.c_str());
|
|
|
|
|
|
|
|
return ret;
|
2013-11-26 08:06:58 +00:00
|
|
|
}
|
|
|
|
|
2014-03-21 05:10:47 +00:00
|
|
|
int SrsHlsMuxer::_refresh_m3u8(int& fd, string m3u8_file)
|
2013-11-26 08:06:58 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
// no segments, return.
|
|
|
|
if (segments.size() == 0) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int flags = O_CREAT|O_WRONLY|O_TRUNC;
|
|
|
|
mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH;
|
|
|
|
if ((fd = ::open(m3u8_file.c_str(), flags, mode)) < 0) {
|
|
|
|
ret = ERROR_HLS_OPEN_FAILED;
|
|
|
|
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());
|
|
|
|
|
|
|
|
// #EXTM3U\n#EXT-X-VERSION:3\n
|
|
|
|
char header[] = {
|
|
|
|
// #EXTM3U\n
|
|
|
|
0x23, 0x45, 0x58, 0x54, 0x4d, 0x33, 0x55, 0xa,
|
|
|
|
// #EXT-X-VERSION:3\n
|
|
|
|
0x23, 0x45, 0x58, 0x54, 0x2d, 0x58, 0x2d, 0x56, 0x45, 0x52,
|
2014-07-04 01:53:50 +00:00
|
|
|
0x53, 0x49, 0x4f, 0x4e, 0x3a, 0x33, 0xa,
|
|
|
|
// #EXT-X-ALLOW-CACHE:NO
|
|
|
|
0x23, 0x45, 0x58, 0x54, 0x2d, 0x58, 0x2d, 0x41, 0x4c, 0x4c,
|
|
|
|
0x4f, 0x57, 0x2d, 0x43, 0x41, 0x43, 0x48, 0x45, 0x3a, 0x4e, 0x4f, 0x0a
|
2014-03-18 03:32:58 +00:00
|
|
|
};
|
|
|
|
if (::write(fd, header, sizeof(header)) != sizeof(header)) {
|
|
|
|
ret = ERROR_HLS_WRITE_FAILED;
|
|
|
|
srs_error("write m3u8 header failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
srs_verbose("write m3u8 header success.");
|
|
|
|
|
|
|
|
// #EXT-X-MEDIA-SEQUENCE:4294967295\n
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment* first = *segments.begin();
|
2014-03-18 03:32:58 +00:00
|
|
|
char sequence[34] = {};
|
|
|
|
int len = snprintf(sequence, sizeof(sequence), "#EXT-X-MEDIA-SEQUENCE:%d\n", first->sequence_no);
|
|
|
|
if (::write(fd, sequence, len) != len) {
|
|
|
|
ret = ERROR_HLS_WRITE_FAILED;
|
|
|
|
srs_error("write m3u8 sequence failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
srs_verbose("write m3u8 sequence success.");
|
|
|
|
|
|
|
|
// #EXT-X-TARGETDURATION:4294967295\n
|
|
|
|
int target_duration = 0;
|
2014-03-20 10:19:08 +00:00
|
|
|
std::vector<SrsHlsSegment*>::iterator it;
|
2014-03-18 03:32:58 +00:00
|
|
|
for (it = segments.begin(); it != segments.end(); ++it) {
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment* segment = *it;
|
2014-03-18 03:32:58 +00:00
|
|
|
target_duration = srs_max(target_duration, (int)segment->duration);
|
|
|
|
}
|
|
|
|
// TODO: maybe need to take an around value
|
|
|
|
target_duration += 1;
|
2014-03-21 09:10:24 +00:00
|
|
|
char duration[34]; // 23+10+1
|
2014-03-18 03:32:58 +00:00
|
|
|
len = snprintf(duration, sizeof(duration), "#EXT-X-TARGETDURATION:%d\n", target_duration);
|
|
|
|
if (::write(fd, duration, len) != len) {
|
|
|
|
ret = ERROR_HLS_WRITE_FAILED;
|
|
|
|
srs_error("write m3u8 duration failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
srs_verbose("write m3u8 duration success.");
|
|
|
|
|
|
|
|
// write all segments
|
|
|
|
for (it = segments.begin(); it != segments.end(); ++it) {
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsSegment* segment = *it;
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2014-03-21 09:10:24 +00:00
|
|
|
if (segment->is_sequence_header) {
|
|
|
|
// #EXT-X-DISCONTINUITY\n
|
|
|
|
char ext_discon[22]; // 21+1
|
|
|
|
len = snprintf(ext_discon, sizeof(ext_discon), "#EXT-X-DISCONTINUITY\n");
|
|
|
|
if (::write(fd, ext_discon, len) != len) {
|
|
|
|
ret = ERROR_HLS_WRITE_FAILED;
|
|
|
|
srs_error("write m3u8 segment discontinuity failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
srs_verbose("write m3u8 segment discontinuity success.");
|
|
|
|
}
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
// "#EXTINF:4294967295.208,\n"
|
2014-03-21 09:10:24 +00:00
|
|
|
char ext_info[25]; // 14+10+1
|
2014-03-18 03:32:58 +00:00
|
|
|
len = snprintf(ext_info, sizeof(ext_info), "#EXTINF:%.3f\n", segment->duration);
|
|
|
|
if (::write(fd, ext_info, len) != len) {
|
|
|
|
ret = ERROR_HLS_WRITE_FAILED;
|
2014-03-21 09:10:24 +00:00
|
|
|
srs_error("write m3u8 segment info failed. ret=%d", ret);
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2014-03-21 09:10:24 +00:00
|
|
|
srs_verbose("write m3u8 segment info success.");
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
// file name
|
|
|
|
std::string filename = segment->uri;
|
|
|
|
filename += "\n";
|
|
|
|
if (::write(fd, filename.c_str(), filename.length()) != (int)filename.length()) {
|
|
|
|
ret = ERROR_HLS_WRITE_FAILED;
|
|
|
|
srs_error("write m3u8 segment uri failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
srs_verbose("write m3u8 segment uri success.");
|
|
|
|
}
|
|
|
|
srs_info("write m3u8 %s success.", m3u8_file.c_str());
|
|
|
|
|
|
|
|
return ret;
|
2013-11-26 09:21:49 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
int SrsHlsMuxer::create_dir()
|
2013-11-26 09:21:49 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
std::string app_dir = hls_path;
|
|
|
|
app_dir += "/";
|
|
|
|
app_dir += app;
|
|
|
|
|
|
|
|
// TODO: cleanup the dir when startup.
|
2015-01-03 04:54:54 +00:00
|
|
|
|
|
|
|
if ((ret = srs_create_dir_recursively(app_dir)) != ERROR_SUCCESS) {
|
|
|
|
srs_error("create app dir %s failed. ret=%d", app_dir.c_str(), ret);
|
|
|
|
return ret;
|
2014-03-18 03:32:58 +00:00
|
|
|
}
|
2015-01-03 04:54:54 +00:00
|
|
|
srs_info("create app dir %s ok", app_dir.c_str());
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
return ret;
|
2013-11-26 08:06:58 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsCache::SrsHlsCache()
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2015-01-22 10:13:33 +00:00
|
|
|
cache = new SrsTsCache();
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
SrsHlsCache::~SrsHlsCache()
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2015-01-22 10:13:33 +00:00
|
|
|
srs_freep(cache);
|
2013-12-02 14:09:10 +00:00
|
|
|
}
|
2014-03-20 10:19:08 +00:00
|
|
|
|
|
|
|
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;
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2014-08-02 14:18:39 +00:00
|
|
|
int hls_fragment = (int)_srs_config->get_hls_fragment(vhost);
|
|
|
|
int hls_window = (int)_srs_config->get_hls_window(vhost);
|
2014-03-20 10:19:08 +00:00
|
|
|
|
|
|
|
// get the hls path config
|
|
|
|
std::string hls_path = _srs_config->get_hls_path(vhost);
|
|
|
|
|
2014-03-21 09:35:27 +00:00
|
|
|
// TODO: FIXME: support load exists m3u8, to continue publish stream.
|
|
|
|
// for the HLS donot requires the EXT-X-MEDIA-SEQUENCE be monotonically increase.
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
// open muxer
|
|
|
|
if ((ret = muxer->update_config(app, stream, hls_path, hls_fragment, hls_window)) != 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int SrsHlsCache::on_unpublish(SrsHlsMuxer* muxer)
|
|
|
|
{
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = muxer->flush_audio(cache->af, cache->ab)) != ERROR_SUCCESS) {
|
2014-03-20 10:19:08 +00:00
|
|
|
srs_error("m3u8 muxer flush audio failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-21 05:10:47 +00:00
|
|
|
if ((ret = muxer->segment_close("unpublish")) != ERROR_SUCCESS) {
|
2014-03-20 10:19:08 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2014-03-21 09:10:24 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
2014-03-20 10:19:08 +00:00
|
|
|
|
2014-06-08 14:36:17 +00:00
|
|
|
int SrsHlsCache::write_audio(SrsAvcAacCodec* codec, SrsHlsMuxer* muxer, int64_t pts, SrsCodecSample* sample)
|
2013-12-02 14:09:10 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
2015-01-22 10:13:33 +00:00
|
|
|
|
|
|
|
audio_buffer_start_pts = pts;
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
// write audio to cache.
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = cache->cache_audio(codec, pts, sample)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// flush if buffer exceed max size.
|
2015-01-22 10:13:33 +00:00
|
|
|
if (cache->ab->length() > SRS_AUTO_HLS_AUDIO_CACHE_SIZE) {
|
|
|
|
if ((ret = muxer->flush_audio(cache->af, cache->ab)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 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
|
|
|
|
if (pts - audio_buffer_start_pts > audio_delay * 90) {
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = muxer->flush_audio(cache->af, cache->ab)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-08 07:15:57 +00:00
|
|
|
// 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.
|
|
|
|
// @see https://github.com/winlinvip/simple-rtmp-server/issues/151
|
|
|
|
if (muxer->is_segment_overflow()) {
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = reap_segment("audio", muxer, cache->af->pts)) != ERROR_SUCCESS) {
|
2014-03-20 10:55:45 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
2013-12-02 14:09:10 +00:00
|
|
|
}
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2015-01-22 10:13:33 +00:00
|
|
|
int SrsHlsCache::write_video(SrsAvcAacCodec* codec, SrsHlsMuxer* muxer, int64_t dts, SrsCodecSample* sample)
|
2013-12-02 14:09:10 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
// write video to cache.
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = cache->cache_video(codec, dts, sample)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:55:45 +00:00
|
|
|
// new segment when:
|
|
|
|
// 1. base on gop.
|
|
|
|
// 2. some gops duration overflow.
|
2015-01-22 10:13:33 +00:00
|
|
|
if (cache->vf->key && muxer->is_segment_overflow()) {
|
|
|
|
if ((ret = reap_segment("video", muxer, cache->vf->dts)) != ERROR_SUCCESS) {
|
2014-03-20 10:19:08 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
// flush video when got one
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = muxer->flush_video(cache->af, cache->ab, cache->vf, cache->vb)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("m3u8 muxer flush video failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:55:45 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-21 05:10:47 +00:00
|
|
|
int SrsHlsCache::reap_segment(string log_desc, SrsHlsMuxer* muxer, int64_t segment_start_dts)
|
2014-03-20 10:55:45 +00:00
|
|
|
{
|
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
2014-03-21 05:10:47 +00:00
|
|
|
if ((ret = muxer->segment_close(log_desc)) != ERROR_SUCCESS) {
|
2014-03-20 10:55:45 +00:00
|
|
|
srs_error("m3u8 muxer close segment 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: flush audio before or after segment?
|
|
|
|
// segment open, flush the audio.
|
|
|
|
// @see: ngx_rtmp_hls_open_fragment
|
|
|
|
/* start fragment with audio to make iPhone happy */
|
2015-01-22 10:13:33 +00:00
|
|
|
if ((ret = muxer->flush_audio(cache->af, cache->ab)) != ERROR_SUCCESS) {
|
2014-03-20 10:55:45 +00:00
|
|
|
srs_error("m3u8 muxer flush audio failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
return ret;
|
2013-12-02 14:09:10 +00:00
|
|
|
}
|
|
|
|
|
2013-12-15 12:29:18 +00:00
|
|
|
SrsHls::SrsHls(SrsSource* _source)
|
2013-12-02 14:09:10 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
hls_enabled = false;
|
|
|
|
|
|
|
|
source = _source;
|
2014-06-08 14:36:17 +00:00
|
|
|
codec = new SrsAvcAacCodec();
|
2014-03-18 03:32:58 +00:00
|
|
|
sample = new SrsCodecSample();
|
|
|
|
jitter = new SrsRtmpJitter();
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
muxer = new SrsHlsMuxer();
|
|
|
|
hls_cache = new SrsHlsCache();
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2014-07-20 05:33:21 +00:00
|
|
|
pithy_print = new SrsPithyPrint(SRS_CONSTS_STAGE_HLS);
|
2014-03-20 10:19:08 +00:00
|
|
|
stream_dts = 0;
|
2013-12-02 14:09:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SrsHls::~SrsHls()
|
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_freep(codec);
|
|
|
|
srs_freep(sample);
|
|
|
|
srs_freep(jitter);
|
|
|
|
|
|
|
|
srs_freep(muxer);
|
2014-03-20 10:19:08 +00:00
|
|
|
srs_freep(hls_cache);
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
srs_freep(pithy_print);
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int SrsHls::on_publish(SrsRequest* req)
|
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
// support multiple publish.
|
|
|
|
if (hls_enabled) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
std::string vhost = req->vhost;
|
2014-03-18 03:32:58 +00:00
|
|
|
if (!_srs_config->get_hls_enabled(vhost)) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
if ((ret = hls_cache->on_publish(muxer, req, stream_dts)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-04-17 10:13:59 +00:00
|
|
|
// if enabled, open the muxer.
|
|
|
|
hls_enabled = true;
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
// notice the source to get the cached sequence header.
|
2014-04-16 03:11:53 +00:00
|
|
|
// 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.
|
2014-03-18 03:32:58 +00:00
|
|
|
if ((ret = source->on_hls_start()) != ERROR_SUCCESS) {
|
|
|
|
srs_error("callback source hls start failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SrsHls::on_unpublish()
|
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
// support multiple unpublish.
|
|
|
|
if (!hls_enabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
if ((ret = hls_cache->on_unpublish(muxer)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("ignore m3u8 muxer flush/close audio failed. ret=%d", ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
hls_enabled = false;
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
2013-12-15 12:29:18 +00:00
|
|
|
int SrsHls::on_meta_data(SrsAmf0Object* metadata)
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
if (!metadata) {
|
|
|
|
srs_trace("no metadata persent, hls ignored it.");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-07-16 01:37:27 +00:00
|
|
|
if (metadata->count() <= 0) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_trace("no metadata persent, hls ignored it.");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
2014-12-05 15:49:53 +00:00
|
|
|
int SrsHls::on_audio(SrsSharedPtrMessage* __audio)
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
if (!hls_enabled) {
|
|
|
|
return ret;
|
|
|
|
}
|
2014-12-05 15:49:53 +00:00
|
|
|
|
|
|
|
SrsSharedPtrMessage* audio = __audio->copy();
|
|
|
|
SrsAutoFree(SrsSharedPtrMessage, audio);
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
sample->clear();
|
|
|
|
if ((ret = codec->audio_aac_demux(audio->payload, audio->size, sample)) != ERROR_SUCCESS) {
|
2014-12-20 03:45:59 +00:00
|
|
|
srs_error("hls codec demux audio failed. ret=%d", ret);
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (codec->audio_codec_id != SrsCodecAudioAAC) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ignore sequence header
|
|
|
|
if (sample->aac_packet_type == SrsCodecAudioTypeSequenceHeader) {
|
2014-03-21 09:10:24 +00:00
|
|
|
return hls_cache->on_sequence_header(muxer);
|
2014-03-18 03:32:58 +00:00
|
|
|
}
|
|
|
|
|
2014-06-25 09:14:11 +00:00
|
|
|
if ((ret = jitter->correct(audio, 0, 0, SrsRtmpJitterAlgorithmFULL)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("rtmp jitter correct audio failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-01-22 09:35:07 +00:00
|
|
|
// the dts calc from rtmp/flv header.
|
|
|
|
int64_t dts = audio->timestamp * 90;
|
2014-03-18 03:32:58 +00:00
|
|
|
|
2014-03-20 10:19:08 +00:00
|
|
|
// for pure audio, we need to update the stream dts also.
|
2015-01-22 09:35:07 +00:00
|
|
|
stream_dts = dts;
|
2014-03-20 10:19:08 +00:00
|
|
|
|
2015-01-22 09:35:07 +00:00
|
|
|
if ((ret = hls_cache->write_audio(codec, muxer, dts, sample)) != ERROR_SUCCESS) {
|
2014-03-20 10:19:08 +00:00
|
|
|
srs_error("hls cache write audio failed. ret=%d", ret);
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
2013-12-02 07:55:10 +00:00
|
|
|
}
|
|
|
|
|
2014-12-05 15:49:53 +00:00
|
|
|
int SrsHls::on_video(SrsSharedPtrMessage* __video)
|
2013-12-02 07:55:10 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
int ret = ERROR_SUCCESS;
|
|
|
|
|
|
|
|
if (!hls_enabled) {
|
|
|
|
return ret;
|
|
|
|
}
|
2014-12-05 15:49:53 +00:00
|
|
|
|
|
|
|
SrsSharedPtrMessage* video = __video->copy();
|
|
|
|
SrsAutoFree(SrsSharedPtrMessage, video);
|
2014-03-18 03:32:58 +00:00
|
|
|
|
|
|
|
sample->clear();
|
|
|
|
if ((ret = codec->video_avc_demux(video->payload, video->size, sample)) != ERROR_SUCCESS) {
|
2014-12-20 03:45:59 +00:00
|
|
|
srs_error("hls codec demux video failed. ret=%d", ret);
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-01-14 03:32:19 +00:00
|
|
|
// ignore info frame,
|
|
|
|
// @see https://github.com/winlinvip/simple-rtmp-server/issues/288#issuecomment-69863909
|
|
|
|
if (sample->frame_type == SrsCodecVideoAVCFrameVideoInfoFrame) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-03-18 03:32:58 +00:00
|
|
|
if (codec->video_codec_id != SrsCodecVideoAVC) {
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ignore sequence header
|
|
|
|
if (sample->frame_type == SrsCodecVideoAVCFrameKeyFrame
|
|
|
|
&& sample->avc_packet_type == SrsCodecVideoAVCTypeSequenceHeader) {
|
2014-03-21 09:10:24 +00:00
|
|
|
return hls_cache->on_sequence_header(muxer);
|
2014-03-18 03:32:58 +00:00
|
|
|
}
|
|
|
|
|
2014-06-25 09:14:11 +00:00
|
|
|
if ((ret = jitter->correct(video, 0, 0, SrsRtmpJitterAlgorithmFULL)) != ERROR_SUCCESS) {
|
2014-03-18 03:32:58 +00:00
|
|
|
srs_error("rtmp jitter correct video failed. ret=%d", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2014-12-07 03:25:05 +00:00
|
|
|
int64_t dts = video->timestamp * 90;
|
2014-03-20 10:19:08 +00:00
|
|
|
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);
|
2014-03-18 03:32:58 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
hls_mux();
|
|
|
|
|
|
|
|
return ret;
|
2013-11-27 09:30:16 +00:00
|
|
|
}
|
|
|
|
|
2013-12-15 12:36:59 +00:00
|
|
|
void SrsHls::hls_mux()
|
2013-12-05 15:34:26 +00:00
|
|
|
{
|
2014-03-18 03:32:58 +00:00
|
|
|
// reportable
|
|
|
|
if (pithy_print->can_print()) {
|
2014-07-06 05:48:03 +00:00
|
|
|
// the run time is not equals to stream time,
|
|
|
|
// @see: https://github.com/winlinvip/simple-rtmp-server/issues/81#issuecomment-48100994
|
|
|
|
// it's ok.
|
2014-07-20 05:23:45 +00:00
|
|
|
srs_trace("-> "SRS_CONSTS_LOG_HLS
|
2014-07-06 05:48:03 +00:00
|
|
|
" time=%"PRId64", stream dts=%"PRId64"(%"PRId64"ms), sequence_no=%d",
|
2014-07-06 01:59:41 +00:00
|
|
|
pithy_print->age(), stream_dts, stream_dts / 90, muxer->sequence_no());
|
2014-03-18 03:32:58 +00:00
|
|
|
}
|
|
|
|
|
2014-04-26 13:41:18 +00:00
|
|
|
pithy_print->elapse();
|
2013-12-05 15:34:26 +00:00
|
|
|
}
|
|
|
|
|
2013-11-27 14:41:58 +00:00
|
|
|
#endif
|
|
|
|
|
2014-08-02 14:18:39 +00:00
|
|
|
|