feature: support HLS fmp4 segment.

pull/4159/head
Jacob Su 7 months ago
parent 15fbe45a9a
commit bf029ed564

@ -1787,6 +1787,13 @@ vhost hls.srs.com {
# default: off
enabled on;
# whether to use fmp4 as container
# The default value is off, then HLS use ts as container format,
# if on, HLS use fmp4 as container format.
# Overwrite by env SRS_VHOST_HLS_HLS_USE_FMP4 for all vhosts.
# default: off
hls_use_fmp4 on;
# the hls fragment in seconds, the duration of a piece of ts.
# Overwrite by env SRS_VHOST_HLS_HLS_FRAGMENT for all vhosts.
# default: 10
@ -1852,6 +1859,26 @@ vhost hls.srs.com {
# Overwrite by env SRS_VHOST_HLS_HLS_TS_FILE for all vhosts.
# default: [app]/[stream]-[seq].ts
hls_ts_file [app]/[stream]-[seq].ts;
# the hls fmp4 file name.
# we supports some variables to generate the filename.
# [vhost], the vhost of stream.
# [app], the app of stream.
# [stream], the stream name of stream.
# [2006], replace this const to current year.
# [01], replace this const to current month.
# [02], replace this const to current date.
# [15], replace this const to current hour.
# [04], replace this const to current minute.
# [05], replace this const to current second.p
# [999], replace this const to current millisecond.
# [timestamp],replace this const to current UNIX timestamp in ms.
# [seq], the sequence number of fmp4.
# [duration], replace this const to current ts duration.
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/dvr#custom-path
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/delivery-hls#hls-config
# Overwrite by env SRS_VHOST_HLS_HLS_FMP4_FILE for all vhosts.
# default: [app]/[stream]-[seq].m4s
hls_fmp4_file [app]/[stream]-[seq].m4s;
# the hls entry prefix, which is base url of ts url.
# for example, the prefix is:
# http://your-server/

@ -0,0 +1,22 @@
# the config for srs to delivery hls
# @see https://ossrs.net/lts/zh-cn/docs/v4/doc/sample-hls
# @see full.conf for detail config.
listen 1935;
max_connections 1000;
daemon off;
srs_log_tank console;
http_server {
enabled on;
listen 8080;
dir ./objs/nginx/html;
}
vhost __defaultVhost__ {
hls {
enabled on;
hls_use_fmp4 on;
hls_path ./objs/nginx/html;
hls_fragment 10;
hls_window 60;
}
}

@ -2683,7 +2683,7 @@ srs_error_t SrsConfig::check_normal_config()
&& m != "hls_storage" && m != "hls_mount" && m != "hls_td_ratio" && m != "hls_aof_ratio" && m != "hls_acodec" && m != "hls_vcodec"
&& m != "hls_m3u8_file" && m != "hls_ts_file" && m != "hls_ts_floor" && m != "hls_cleanup" && m != "hls_nb_notify"
&& m != "hls_wait_keyframe" && m != "hls_dispose" && m != "hls_keys" && m != "hls_fragments_per_key" && m != "hls_key_file"
&& m != "hls_key_file_path" && m != "hls_key_url" && m != "hls_dts_directly" && m != "hls_ctx" && m != "hls_ts_ctx") {
&& m != "hls_key_file_path" && m != "hls_key_url" && m != "hls_dts_directly" && m != "hls_ctx" && m != "hls_ts_ctx" && m != "hls_use_fmp4" && m != "hls_fmp4_file") {
return srs_error_new(ERROR_SYSTEM_CONFIG_INVALID, "illegal vhost.hls.%s of %s", m.c_str(), vhost->arg0().c_str());
}
@ -6936,6 +6936,31 @@ bool SrsConfig::get_hls_enabled(SrsConfDirective* vhost)
return SRS_CONF_PREFER_FALSE(conf->arg0());
}
bool SrsConfig::get_hls_use_fmp4(std::string vhost)
{
SRS_OVERWRITE_BY_ENV_BOOL("srs.vhost.hls.hls_use_fmp4"); // SRS_VHOST_HLS_HLS_USE_FMP4
static bool DEFAULT = false;
SrsConfDirective* conf = get_vhost(vhost);
if (!conf) {
return DEFAULT;
}
conf = conf->get("hls");
if (!conf) {
return DEFAULT;
}
conf = conf->get("hls_use_fmp4");
if (!conf || conf->arg0().empty()) {
return DEFAULT;
}
return SRS_CONF_PREFER_FALSE(conf->arg0());
}
string SrsConfig::get_hls_entry_prefix(string vhost)
{
SRS_OVERWRITE_BY_ENV_STRING("srs.vhost.hls.hls_entry_prefix"); // SRS_VHOST_HLS_HLS_ENTRY_PREFIX
@ -7012,6 +7037,25 @@ string SrsConfig::get_hls_ts_file(string vhost)
return conf->arg0();
}
string SrsConfig::get_hls_fmp4_file(std::string vhost)
{
SRS_OVERWRITE_BY_ENV_STRING("srs.vhost.hls.hls_fmp4_file"); // SRS_VHOST_HLS_HLS_FMP4_FILE
static string DEFAULT = "[app]/[stream]-[seq].m4s";
SrsConfDirective* conf = get_hls(vhost);
if (!conf) {
return DEFAULT;
}
conf = conf->get("hls_fmp4_file");
if (!conf || conf->arg0().empty()) {
return DEFAULT;
}
return conf->arg0();
}
bool SrsConfig::get_hls_ts_floor(string vhost)
{
SRS_OVERWRITE_BY_ENV_BOOL("srs.vhost.hls.hls_ts_floor"); // SRS_VHOST_HLS_HLS_TS_FLOOR

@ -933,6 +933,8 @@ public:
// Whether HLS is enabled.
virtual bool get_hls_enabled(std::string vhost);
virtual bool get_hls_enabled(SrsConfDirective* vhost);
// Whether HLS use fmp4 container format
virtual bool get_hls_use_fmp4(std::string vhost);
// Get the HLS m3u8 list ts segment entry prefix info.
virtual std::string get_hls_entry_prefix(std::string vhost);
// Get the HLS ts/m3u8 file store path.
@ -941,6 +943,8 @@ public:
virtual std::string get_hls_m3u8_file(std::string vhost);
// Get the HLS ts file path template.
virtual std::string get_hls_ts_file(std::string vhost);
// Get the HLS fmp4 file path template.
virtual std::string get_hls_fmp4_file(std::string vhost);
// Whether enable the floor(timestamp/hls_fragment) for variable timestamp.
virtual bool get_hls_ts_floor(std::string vhost);
// Get the hls fragment time, in srs_utime_t.

File diff suppressed because it is too large Load Diff

@ -32,11 +32,14 @@ class SrsTsAacJitter;
class SrsTsMessageCache;
class SrsHlsSegment;
class SrsTsContext;
class SrsMp4M2tsInitEncoder;
class SrsFmp4SegmentEncoder;
// The wrapper of m3u8 segment from specification:
//
// 3.3.2. EXTINF
// The EXTINF tag specifies the duration of a media segment.
// TODO: refactor this to support fmp4 segment.
class SrsHlsSegment : public SrsFragment
{
public:
@ -61,6 +64,40 @@ public:
virtual srs_error_t rename();
};
class SrsInitMp4Segment : public SrsFragment
{
private:
SrsFileWriter* fw_;
SrsMp4M2tsInitEncoder* init_;
public:
SrsInitMp4Segment();
virtual ~SrsInitMp4Segment();
public:
// Write the init mp4 file, with the v_tid(video track id) and a_tid (audio track id).
virtual srs_error_t write(SrsFormat* format, int v_tid, int a_tid);
};
// TODO: merge this code with SrsFragmentedMp4 in dash
class SrsHlsM4sSegment : public SrsFragment
{
private:
SrsFileWriter* fw_;
SrsFmp4SegmentEncoder* enc_;
public:
// sequence number in m3u8.
int sequence_no;
public:
SrsHlsM4sSegment();
virtual ~SrsHlsM4sSegment();
virtual srs_error_t initialize(int64_t time, uint32_t v_tid, uint32_t a_tid, int sequence_number, std::string m4s_path);
virtual srs_error_t write(SrsSharedPtrMessage* shared_msg, SrsFormat* format);
virtual srs_error_t reap(uint64_t& dts);
};
// The hls async call: on_hls
class SrsDvrAsyncCallOnHls : public ISrsAsyncCallTask
{
@ -217,6 +254,154 @@ private:
virtual srs_error_t _refresh_m3u8(std::string m3u8_file);
};
// Mux the HLS stream(m3u8 and m4s files).
// Generally, the m3u8 muxer only provides methods to open/close segments,
// to flush video/audio, without any mechenisms.
//
// That is, user must use HlsCache, which will control the methods of muxer,
// and provides HLS mechenisms.
class SrsHlsFmp4Muxer
{
private:
SrsRequest* req_;
private:
std::string hls_entry_prefix_;
std::string hls_path_;
std::string hls_m4s_file_;
bool hls_cleanup_;
bool hls_wait_keyframe_;
std::string m3u8_dir_;
double hls_aof_ratio_;
// TODO: FIXME: Use TBN 1000.
srs_utime_t hls_fragment_;
srs_utime_t hls_window_;
SrsAsyncCallWorker* async_;
private:
// Whether use floor algorithm for timestamp.
bool hls_ts_floor_;
// The deviation in piece to adjust the fragment to be more
// bigger or smaller.
int deviation_ts_;
// The previous reap floor timestamp,
// used to detect the dup or jmp or ts.
int64_t accept_floor_ts_;
int64_t previous_floor_ts_;
bool init_mp4_ready_;
private:
// Whether encrypted or not
bool hls_keys_;
int hls_fragments_per_key_;
// The key file name
std::string hls_key_file_;
// The key file path
std::string hls_key_file_path_;
// The key file url
std::string hls_key_url_;
// The key and iv.
unsigned char key_[16];
unsigned char iv_[16];
// The underlayer file writer.
SrsFileWriter* writer_;
private:
int sequence_no_;
srs_utime_t max_td_;
std::string m3u8_;
std::string m3u8_url_;
int video_track_id_;
int audio_track_id_;
uint64_t video_dts_;
private:
// The available cached segments in m3u8.
SrsFragmentWindow* segments_;
// The current writing segment.
SrsHlsM4sSegment* current_;
private:
// Latest audio codec, parsed from stream.
SrsAudioCodecId latest_acodec_;
// Latest audio codec, parsed from stream.
SrsVideoCodecId latest_vcodec_;
public:
SrsHlsFmp4Muxer();
virtual ~SrsHlsFmp4Muxer();
public:
virtual void dispose();
public:
virtual int sequence_no();
virtual std::string ts_url();
virtual srs_utime_t duration();
virtual int deviation();
public:
SrsAudioCodecId latest_acodec();
void set_latest_acodec(SrsAudioCodecId v);
SrsVideoCodecId latest_vcodec();
void set_latest_vcodec(SrsVideoCodecId v);
public:
// Initialize the hls muxer.
virtual srs_error_t initialize(int v_tid, int a_tid);
// When publish or unpublish stream.
virtual srs_error_t on_publish(SrsRequest* req);
virtual srs_error_t write_init_mp4(SrsFormat* format);
virtual srs_error_t write_audio(SrsSharedPtrMessage* shared_audio, SrsFormat* format);
virtual srs_error_t write_video(SrsSharedPtrMessage* shared_video, SrsFormat* format);
virtual srs_error_t on_unpublish();
// When publish, update the config for muxer.
virtual srs_error_t update_config(SrsRequest* r);
// Open a new segment(a new ts file)
virtual srs_error_t segment_open(srs_utime_t basetime);
virtual srs_error_t on_sequence_header();
// Whether segment overflow,
// that is whether the current segment duration>=(the segment in config)
virtual bool is_segment_overflow();
// Whether wait keyframe to reap the ts.
virtual bool wait_keyframe();
// Whether segment absolutely overflow, for pure audio to reap segment,
// that is whether the current segment duration>=2*(the segment in config)
virtual bool is_segment_absolutely_overflow();
public:
// Whether current hls muxer is pure audio mode.
// virtual bool pure_audio();
// virtual srs_error_t flush_audio(SrsTsMessageCache* cache);
// virtual srs_error_t flush_video(SrsTsMessageCache* cache);
// When flushing video or audio, we update the duration. But, we should also update the
// duration before closing the segment. Keep in mind that it's fine to update the duration
// several times using the same dts timestamp.
void update_duration(uint64_t dts);
// Close segment(ts).
virtual srs_error_t segment_close();
private:
virtual srs_error_t do_segment_close();
virtual srs_error_t write_hls_key();
virtual srs_error_t refresh_m3u8();
virtual srs_error_t _refresh_m3u8(std::string m3u8_file);
};
// The base class for HLS controller
class ISrsHlsController
{
public:
ISrsHlsController();
virtual ~ISrsHlsController();
public:
virtual srs_error_t initialize() = 0;
virtual void dispose() = 0;
// When publish or unpublish stream.
virtual srs_error_t on_publish(SrsRequest* req) = 0;
virtual srs_error_t on_unpublish() = 0;
virtual srs_error_t write_audio(SrsSharedPtrMessage* shared_audio, SrsFormat* format) = 0;
virtual srs_error_t write_video(SrsSharedPtrMessage* shared_video, SrsFormat* format) = 0;
virtual srs_error_t on_sequence_header(SrsSharedPtrMessage* msg, SrsFormat* format) = 0;
virtual int sequence_no() = 0;
virtual std::string ts_url() = 0;
virtual srs_utime_t duration() = 0;
virtual int deviation() = 0;
};
// The hls stream cache,
// use to cache hls stream and flush to hls muxer.
//
@ -232,14 +417,23 @@ private:
// when timestamp convert to flv tbn, it will loose precise,
// so we must gather audio frame together, and recalc the timestamp @see SrsTsAacJitter,
// we use a aac jitter to correct the audio pts.
class SrsHlsController
class SrsHlsController : public ISrsHlsController
{
private:
// The HLS muxer to reap ts and m3u8.
// The TS is cached to SrsTsMessageCache then flush to ts segment.
SrsHlsMuxer* muxer;
// The TS cache
// TODO: support both fmp4 and ts format
SrsTsMessageCache* tsmc;
// If the diff=dts-previous_audio_dts is about 23,
// that's the AAC samples is 1024, and we use the samples to calc the dts.
int64_t previous_audio_dts;
// The total aac samples.
uint64_t aac_samples;
// Whether directly turn FLV timestamp to TS DTS.
bool hls_dts_directly;
public:
SrsHlsController();
virtual ~SrsHlsController();
@ -258,11 +452,11 @@ public:
// must write a #EXT-X-DISCONTINUITY to m3u8.
// @see: hls-m3u8-draft-pantos-http-live-streaming-12.txt
// @see: 3.4.11. EXT-X-DISCONTINUITY
virtual srs_error_t on_sequence_header();
virtual srs_error_t on_sequence_header(SrsSharedPtrMessage* shared_audio, SrsFormat* format);
// write audio to cache, if need to flush, flush to muxer.
virtual srs_error_t write_audio(SrsAudioFrame* frame, int64_t pts);
virtual srs_error_t write_audio(SrsSharedPtrMessage* shared_audio, SrsFormat* format);
// write video to muxer.
virtual srs_error_t write_video(SrsVideoFrame* frame, int64_t dts);
virtual srs_error_t write_video(SrsSharedPtrMessage* shared_video, SrsFormat* format);
private:
// Reopen the muxer for a new hls segment,
// close current segment, open a new segment,
@ -271,12 +465,51 @@ private:
virtual srs_error_t reap_segment();
};
// Transmux RTMP stream to HLS(m3u8 and ts).
class SrsHlsMp4Controller : public ISrsHlsController
{
private:
bool has_video_sh_;
bool has_audio_sh_;
int video_track_id_;
int audio_track_id_;
// Current audio dts.
uint64_t audio_dts_;
// Current video dts.
uint64_t video_dts_;
SrsRequest* req_;
SrsHlsFmp4Muxer* muxer_;
public:
SrsHlsMp4Controller();
virtual ~SrsHlsMp4Controller();
public:
virtual srs_error_t initialize();
virtual void dispose();
// When publish or unpublish stream.
virtual srs_error_t on_publish(SrsRequest* req);
virtual srs_error_t on_unpublish();
virtual srs_error_t write_audio(SrsSharedPtrMessage* shared_audio, SrsFormat* format);
virtual srs_error_t write_video(SrsSharedPtrMessage* shared_video, SrsFormat* format);
virtual srs_error_t on_sequence_header(SrsSharedPtrMessage* shared_audio, SrsFormat* format);
virtual int sequence_no();
virtual std::string ts_url();
virtual srs_utime_t duration();
virtual int deviation();
};
// Transmux RTMP stream to HLS(m3u8 and ts,fmp4).
// TODO: FIXME: add utest for hls.
class SrsHls
{
private:
SrsHlsController* controller;
ISrsHlsController* controller;
private:
SrsRequest* req;
// Whether the HLS is enabled.
@ -290,14 +523,7 @@ private:
bool reloading_;
// To detect heartbeat and dispose it if configured.
srs_utime_t last_update_time;
private:
// If the diff=dts-previous_audio_dts is about 23,
// that's the AAC samples is 1024, and we use the samples to calc the dts.
int64_t previous_audio_dts;
// The total aac samples.
uint64_t aac_samples;
// Whether directly turn FLV timestamp to TS DTS.
bool hls_dts_directly;
private:
SrsOriginHub* hub;
SrsRtmpJitter* jitter;

@ -1361,6 +1361,7 @@ public:
public:
virtual bool is_aac_sequence_header();
virtual bool is_mp3_sequence_header();
// TODO: is avc|hevc|av1 sequence header
virtual bool is_avc_sequence_header();
private:
// Demux the video packet in H.264 codec.

@ -759,15 +759,8 @@ void SrsMp4MovieFragmentBox::set_mfhd(SrsMp4MovieFragmentHeaderBox* v)
boxes.push_back(v);
}
SrsMp4TrackFragmentBox* SrsMp4MovieFragmentBox::traf()
void SrsMp4MovieFragmentBox::add_traf(SrsMp4TrackFragmentBox* v)
{
SrsMp4Box* box = get(SrsMp4BoxTypeTRAF);
return dynamic_cast<SrsMp4TrackFragmentBox*>(box);
}
void SrsMp4MovieFragmentBox::set_traf(SrsMp4TrackFragmentBox* v)
{
remove(SrsMp4BoxTypeTRAF);
boxes.push_back(v);
}
@ -1647,15 +1640,8 @@ SrsMp4MovieExtendsBox::~SrsMp4MovieExtendsBox()
{
}
SrsMp4TrackExtendsBox* SrsMp4MovieExtendsBox::trex()
{
SrsMp4Box* box = get(SrsMp4BoxTypeTREX);
return dynamic_cast<SrsMp4TrackExtendsBox*>(box);
}
void SrsMp4MovieExtendsBox::set_trex(SrsMp4TrackExtendsBox* v)
void SrsMp4MovieExtendsBox::add_trex(SrsMp4TrackExtendsBox* v)
{
remove(SrsMp4BoxTypeTREX);
boxes.push_back(v);
}
@ -4989,13 +4975,11 @@ srs_error_t SrsMp4SampleManager::write(SrsMp4MovieBox* moov)
return err;
}
srs_error_t SrsMp4SampleManager::write(SrsMp4MovieFragmentBox* moof, uint64_t dts)
srs_error_t SrsMp4SampleManager::write(SrsMp4TrackFragmentBox* traf, uint64_t dts)
{
srs_error_t err = srs_success;
SrsMp4TrackFragmentBox* traf = moof->traf();
SrsMp4TrackFragmentRunBox* trun = traf->trun();
trun->flags = SrsMp4TrunFlagsDataOffset | SrsMp4TrunFlagsSampleDuration
| SrsMp4TrunFlagsSampleSize | SrsMp4TrunFlagsSampleFlag | SrsMp4TrunFlagsSampleCtsOffset;
@ -6327,7 +6311,7 @@ srs_error_t SrsMp4M2tsInitEncoder::write(SrsFormat* format, bool video, int tid)
moov->set_mvex(mvex);
SrsMp4TrackExtendsBox* trex = new SrsMp4TrackExtendsBox();
mvex->set_trex(trex);
mvex->add_trex(trex);
trex->track_ID = tid;
trex->default_sample_description_index = 1;
@ -6428,7 +6412,7 @@ srs_error_t SrsMp4M2tsInitEncoder::write(SrsFormat* format, bool video, int tid)
moov->set_mvex(mvex);
SrsMp4TrackExtendsBox* trex = new SrsMp4TrackExtendsBox();
mvex->set_trex(trex);
mvex->add_trex(trex);
trex->track_ID = tid;
trex->default_sample_description_index = 1;
@ -6442,6 +6426,248 @@ srs_error_t SrsMp4M2tsInitEncoder::write(SrsFormat* format, bool video, int tid)
return err;
}
srs_error_t SrsMp4M2tsInitEncoder::write(SrsFormat* format, int v_tid, int a_tid)
{
srs_error_t err = srs_success;
// Write ftyp box.
if (true) {
SrsUniquePtr<SrsMp4FileTypeBox> ftyp(new SrsMp4FileTypeBox());
ftyp->major_brand = SrsMp4BoxBrandMP42; // SrsMp4BoxBrandISO5;
ftyp->minor_version = 512;
ftyp->set_compatible_brands(SrsMp4BoxBrandISO6, SrsMp4BoxBrandMP41);
if ((err = srs_mp4_write_box(writer, ftyp.get())) != srs_success) {
return srs_error_wrap(err, "write ftyp");
}
}
// Write moov.
if (true) {
SrsUniquePtr<SrsMp4MovieBox> moov(new SrsMp4MovieBox());
SrsMp4MovieHeaderBox* mvhd = new SrsMp4MovieHeaderBox();
moov->set_mvhd(mvhd);
mvhd->timescale = 1000; // Use tbn ms.
mvhd->duration_in_tbn = 0;
mvhd->next_track_ID = 4294967295; // 2^32 - 1
// write video track
if (format->vcodec) {
SrsMp4TrackBox* trak = new SrsMp4TrackBox();
moov->add_trak(trak);
SrsMp4TrackHeaderBox* tkhd = new SrsMp4TrackHeaderBox();
trak->set_tkhd(tkhd);
tkhd->track_ID = v_tid;
tkhd->duration = 0;
tkhd->width = (format->vcodec->width << 16);
tkhd->height = (format->vcodec->height << 16);
SrsMp4MediaBox* mdia = new SrsMp4MediaBox();
trak->set_mdia(mdia);
SrsMp4MediaHeaderBox* mdhd = new SrsMp4MediaHeaderBox();
mdia->set_mdhd(mdhd);
mdhd->timescale = 1000;
mdhd->duration = 0;
mdhd->set_language0('u');
mdhd->set_language1('n');
mdhd->set_language2('d');
SrsMp4HandlerReferenceBox* hdlr = new SrsMp4HandlerReferenceBox();
mdia->set_hdlr(hdlr);
hdlr->handler_type = SrsMp4HandlerTypeVIDE;
hdlr->name = "VideoHandler";
SrsMp4MediaInformationBox* minf = new SrsMp4MediaInformationBox();
mdia->set_minf(minf);
SrsMp4VideoMeidaHeaderBox* vmhd = new SrsMp4VideoMeidaHeaderBox();
minf->set_vmhd(vmhd);
SrsMp4DataInformationBox* dinf = new SrsMp4DataInformationBox();
minf->set_dinf(dinf);
SrsMp4DataReferenceBox* dref = new SrsMp4DataReferenceBox();
dinf->set_dref(dref);
SrsMp4DataEntryBox* url = new SrsMp4DataEntryUrlBox();
dref->append(url);
SrsMp4SampleTableBox* stbl = new SrsMp4SampleTableBox();
minf->set_stbl(stbl);
SrsMp4SampleDescriptionBox* stsd = new SrsMp4SampleDescriptionBox();
stbl->set_stsd(stsd);
if (format->vcodec->id == SrsVideoCodecIdAVC) {
SrsMp4VisualSampleEntry* avc1 = new SrsMp4VisualSampleEntry(SrsMp4BoxTypeAVC1);
stsd->append(avc1);
avc1->width = format->vcodec->width;
avc1->height = format->vcodec->height;
avc1->data_reference_index = 1;
SrsMp4AvccBox* avcC = new SrsMp4AvccBox();
avc1->set_avcC(avcC);
avcC->avc_config = format->vcodec->avc_extra_data;
} else {
SrsMp4VisualSampleEntry* hev1 = new SrsMp4VisualSampleEntry(SrsMp4BoxTypeHEV1);
stsd->append(hev1);
hev1->width = format->vcodec->width;
hev1->height = format->vcodec->height;
hev1->data_reference_index = 1;
SrsMp4HvcCBox* hvcC = new SrsMp4HvcCBox();
hev1->set_hvcC(hvcC);
hvcC->hevc_config = format->vcodec->avc_extra_data;
}
SrsMp4DecodingTime2SampleBox* stts = new SrsMp4DecodingTime2SampleBox();
stbl->set_stts(stts);
SrsMp4Sample2ChunkBox* stsc = new SrsMp4Sample2ChunkBox();
stbl->set_stsc(stsc);
SrsMp4SampleSizeBox* stsz = new SrsMp4SampleSizeBox();
stbl->set_stsz(stsz);
// TODO: FIXME: need to check using stco or co64?
SrsMp4ChunkOffsetBox* stco = new SrsMp4ChunkOffsetBox();
stbl->set_stco(stco);
}
// write audio track
if (format->acodec) {
SrsMp4TrackBox* trak = new SrsMp4TrackBox();
moov->add_trak(trak);
SrsMp4TrackHeaderBox* tkhd = new SrsMp4TrackHeaderBox();
tkhd->volume = 0x0100;
trak->set_tkhd(tkhd);
tkhd->track_ID = a_tid;
tkhd->duration = 0;
SrsMp4MediaBox* mdia = new SrsMp4MediaBox();
trak->set_mdia(mdia);
SrsMp4MediaHeaderBox* mdhd = new SrsMp4MediaHeaderBox();
mdia->set_mdhd(mdhd);
mdhd->timescale = 1000;
mdhd->duration = 0;
mdhd->set_language0('u');
mdhd->set_language1('n');
mdhd->set_language2('d');
SrsMp4HandlerReferenceBox* hdlr = new SrsMp4HandlerReferenceBox();
mdia->set_hdlr(hdlr);
hdlr->handler_type = SrsMp4HandlerTypeSOUN;
hdlr->name = "SoundHandler";
SrsMp4MediaInformationBox* minf = new SrsMp4MediaInformationBox();
mdia->set_minf(minf);
SrsMp4SoundMeidaHeaderBox* smhd = new SrsMp4SoundMeidaHeaderBox();
minf->set_smhd(smhd);
SrsMp4DataInformationBox* dinf = new SrsMp4DataInformationBox();
minf->set_dinf(dinf);
SrsMp4DataReferenceBox* dref = new SrsMp4DataReferenceBox();
dinf->set_dref(dref);
SrsMp4DataEntryBox* url = new SrsMp4DataEntryUrlBox();
dref->append(url);
SrsMp4SampleTableBox* stbl = new SrsMp4SampleTableBox();
minf->set_stbl(stbl);
SrsMp4SampleDescriptionBox* stsd = new SrsMp4SampleDescriptionBox();
stbl->set_stsd(stsd);
SrsMp4AudioSampleEntry* mp4a = new SrsMp4AudioSampleEntry();
mp4a->data_reference_index = 1;
mp4a->samplerate = uint32_t(srs_flv_srates[format->acodec->sound_rate]) << 16;
if (format->acodec->sound_size == SrsAudioSampleBits16bit) {
mp4a->samplesize = 16;
} else {
mp4a->samplesize = 8;
}
if (format->acodec->sound_type == SrsAudioChannelsStereo) {
mp4a->channelcount = 2;
} else {
mp4a->channelcount = 1;
}
stsd->append(mp4a);
SrsMp4EsdsBox* esds = new SrsMp4EsdsBox();
mp4a->set_esds(esds);
SrsMp4ES_Descriptor* es = esds->es;
es->ES_ID = 0x02;
SrsMp4DecoderConfigDescriptor& desc = es->decConfigDescr;
desc.objectTypeIndication = SrsMp4ObjectTypeAac;
desc.streamType = SrsMp4StreamTypeAudioStream;
srs_freep(desc.decSpecificInfo);
SrsMp4DecoderSpecificInfo* asc = new SrsMp4DecoderSpecificInfo();
desc.decSpecificInfo = asc;
asc->asc = format->acodec->aac_extra_data;
SrsMp4DecodingTime2SampleBox* stts = new SrsMp4DecodingTime2SampleBox();
stbl->set_stts(stts);
SrsMp4Sample2ChunkBox* stsc = new SrsMp4Sample2ChunkBox();
stbl->set_stsc(stsc);
SrsMp4SampleSizeBox* stsz = new SrsMp4SampleSizeBox();
stbl->set_stsz(stsz);
// TODO: FIXME: need to check using stco or co64?
SrsMp4ChunkOffsetBox* stco = new SrsMp4ChunkOffsetBox();
stbl->set_stco(stco);
}
if (true) {
SrsMp4MovieExtendsBox* mvex = new SrsMp4MovieExtendsBox();
moov->set_mvex(mvex);
// video trex
SrsMp4TrackExtendsBox* v_trex = new SrsMp4TrackExtendsBox();
mvex->add_trex(v_trex);
v_trex->track_ID = v_tid;
v_trex->default_sample_description_index = 1;
// audio trex
SrsMp4TrackExtendsBox* a_trex = new SrsMp4TrackExtendsBox();
mvex->add_trex(a_trex);
a_trex->track_ID = a_tid;
a_trex->default_sample_description_index = 1;
}
if ((err = srs_mp4_write_box(writer, moov.get())) != srs_success) {
return srs_error_wrap(err, "write moov");
}
}
return err;
}
SrsMp4M2tsSegmentEncoder::SrsMp4M2tsSegmentEncoder()
{
writer = NULL;
@ -6568,7 +6794,7 @@ srs_error_t SrsMp4M2tsSegmentEncoder::flush(uint64_t& dts)
mfhd->sequence_number = sequence_number;
SrsMp4TrackFragmentBox* traf = new SrsMp4TrackFragmentBox();
moof->set_traf(traf);
moof->add_traf(traf);
SrsMp4TrackFragmentHeaderBox* tfhd = new SrsMp4TrackFragmentHeaderBox();
traf->set_tfhd(tfhd);
@ -6585,7 +6811,7 @@ srs_error_t SrsMp4M2tsSegmentEncoder::flush(uint64_t& dts)
SrsMp4TrackFragmentRunBox* trun = new SrsMp4TrackFragmentRunBox();
traf->set_trun(trun);
if ((err = samples->write(moof.get(), dts)) != srs_success) {
if ((err = samples->write(traf, dts)) != srs_success) {
return srs_error_wrap(err, "write samples");
}
@ -6635,3 +6861,207 @@ srs_error_t SrsMp4M2tsSegmentEncoder::flush(uint64_t& dts)
return err;
}
SrsFmp4SegmentEncoder::SrsFmp4SegmentEncoder()
{
writer_ = NULL;
sequence_number_ = 0;
decode_basetime_ = 0;
audio_track_id_ = 0;
video_track_id_ = 0;
nb_audios_ = 0;
nb_videos_ = 0;
styp_bytes_ = 0;
mdat_audio_bytes_ = 0;
mdat_video_bytes_ = 0;
audio_samples_ = new SrsMp4SampleManager();
video_samples_ = new SrsMp4SampleManager();
}
SrsFmp4SegmentEncoder::~SrsFmp4SegmentEncoder()
{
srs_freep(audio_samples_);
srs_freep(video_samples_);
}
srs_error_t SrsFmp4SegmentEncoder::initialize(ISrsWriter* w, uint32_t sequence, srs_utime_t basetime, uint32_t v_tid, uint32_t a_tid)
{
srs_error_t err = srs_success;
writer_ = w;
sequence_number_ = sequence;
decode_basetime_ = basetime;
video_track_id_ = v_tid;
audio_track_id_ = a_tid;
return err;
}
srs_error_t SrsFmp4SegmentEncoder::write_sample(SrsMp4HandlerType ht, uint16_t ft,
uint32_t dts, uint32_t pts, uint8_t* sample, uint32_t nb_sample)
{
srs_error_t err = srs_success;
SrsMp4Sample* ps = new SrsMp4Sample();
if (ht == SrsMp4HandlerTypeVIDE) {
ps->type = SrsFrameTypeVideo;
ps->frame_type = (SrsVideoAvcFrameType)ft;
ps->index = nb_videos_++;
video_samples_->append(ps);
mdat_video_bytes_ += nb_sample;
} else if (ht == SrsMp4HandlerTypeSOUN) {
ps->type = SrsFrameTypeAudio;
ps->index = nb_audios_++;
audio_samples_->append(ps);
mdat_audio_bytes_ += nb_sample;
} else {
srs_freep(ps);
return err;
}
ps->tbn = 1000;
ps->dts = dts;
ps->pts = pts;
// We should copy the sample data, which is shared ptr from video/audio message.
// Furthermore, we do free the data when freeing the sample.
ps->data = new uint8_t[nb_sample];
memcpy(ps->data, sample, nb_sample);
ps->nb_data = nb_sample;
return err;
}
srs_error_t SrsFmp4SegmentEncoder::flush(uint64_t& dts)
{
srs_error_t err = srs_success;
SrsMp4TrackFragmentRunBox* video_trun = NULL;
SrsMp4TrackFragmentRunBox* audio_trun = NULL;
if (nb_videos_ == 0 && nb_audios_ == 0) {
return srs_error_new(ERROR_MP4_ILLEGAL_MDAT, "empty samples");
}
// Create a mdat box.
// its payload will be writen by samples,
// and we will update its header(size) when flush.
SrsUniquePtr<SrsMp4MediaDataBox> mdat(new SrsMp4MediaDataBox());
SrsUniquePtr<SrsMp4MovieFragmentBox> moof(new SrsMp4MovieFragmentBox());
SrsMp4MovieFragmentHeaderBox* mfhd = new SrsMp4MovieFragmentHeaderBox();
moof->set_mfhd(mfhd);
mfhd->sequence_number = sequence_number_;
// write video traf
if (mdat_video_bytes_ > 0) {
// video traf
SrsMp4TrackFragmentBox* traf = new SrsMp4TrackFragmentBox();
moof->add_traf(traf);
SrsMp4TrackFragmentHeaderBox* tfhd = new SrsMp4TrackFragmentHeaderBox();
traf->set_tfhd(tfhd);
tfhd->track_id = video_track_id_;
tfhd->flags = SrsMp4TfhdFlagsDefaultBaseIsMoof;
SrsMp4TrackFragmentDecodeTimeBox* tfdt = new SrsMp4TrackFragmentDecodeTimeBox();
traf->set_tfdt(tfdt);
tfdt->version = 1;
tfdt->base_media_decode_time = srsu2ms(decode_basetime_);
SrsMp4TrackFragmentRunBox* trun = new SrsMp4TrackFragmentRunBox();
traf->set_trun(trun);
video_trun = trun;
if ((err = video_samples_->write(traf, dts)) != srs_success) {
return srs_error_wrap(err, "write samples");
}
}
// write audio traf
if (mdat_audio_bytes_ > 0) {
// audio traf
SrsMp4TrackFragmentBox* traf = new SrsMp4TrackFragmentBox();
moof->add_traf(traf);
SrsMp4TrackFragmentHeaderBox* tfhd = new SrsMp4TrackFragmentHeaderBox();
traf->set_tfhd(tfhd);
tfhd->track_id = audio_track_id_;
tfhd->flags = SrsMp4TfhdFlagsDefaultBaseIsMoof;
SrsMp4TrackFragmentDecodeTimeBox* tfdt = new SrsMp4TrackFragmentDecodeTimeBox();
traf->set_tfdt(tfdt);
tfdt->version = 1;
tfdt->base_media_decode_time = srsu2ms(decode_basetime_);
SrsMp4TrackFragmentRunBox* trun = new SrsMp4TrackFragmentRunBox();
traf->set_trun(trun);
audio_trun = trun;
if ((err = audio_samples_->write(traf, dts)) != srs_success) {
return srs_error_wrap(err, "write samples");
}
}
// @remark Remember the data_offset of turn is size(moof)+header(mdat)
int moof_bytes = moof->nb_bytes();
// rewrite video data_offset
if (video_trun != NULL) {
video_trun->data_offset = (int32_t)(moof_bytes + mdat->sz_header() + 0);
}
if (audio_trun != NULL) {
audio_trun->data_offset = (int32_t)(moof_bytes + mdat->sz_header() + mdat_video_bytes_);
}
// srs_trace("seq: %d, moof_bytes=%d, mdat->sz_header=%d", sequence_number_, moof->nb_bytes(), mdat->sz_header());
// srs_trace("mdat_video_bytes_ = %d, mdat_audio_bytes_ = %d", mdat_video_bytes_, mdat_audio_bytes_);
if ((err = srs_mp4_write_box(writer_, moof.get())) != srs_success) {
return srs_error_wrap(err, "write moof");
}
mdat->nb_data = mdat_video_bytes_ + mdat_audio_bytes_;
// Write mdat.
if (true) {
int nb_data = mdat->sz_header();
SrsUniquePtr<uint8_t[]> data(new uint8_t[nb_data]);
SrsUniquePtr<SrsBuffer> buffer(new SrsBuffer((char*)data.get(), nb_data));
if ((err = mdat->encode(buffer.get())) != srs_success) {
return srs_error_wrap(err, "encode mdat");
}
// TODO: FIXME: Ensure all bytes are writen.
if ((err = writer_->write(data.get(), nb_data, NULL)) != srs_success) {
return srs_error_wrap(err, "write mdat");
}
vector<SrsMp4Sample*>::iterator it;
// write video sample data
for (it = video_samples_->samples.begin(); it != video_samples_->samples.end(); ++it) {
SrsMp4Sample* sample = *it;
// TODO: FIXME: Ensure all bytes are writen.
if ((err = writer_->write(sample->data, sample->nb_data, NULL)) != srs_success) {
return srs_error_wrap(err, "write sample");
}
}
// write audio sample data
for (it = audio_samples_->samples.begin(); it != audio_samples_->samples.end(); ++it) {
SrsMp4Sample* sample = *it;
// TODO: FIXME: Ensure all bytes are writen.
if ((err = writer_->write(sample->data, sample->nb_data, NULL)) != srs_success) {
return srs_error_wrap(err, "write sample");
}
}
}
return err;
}

@ -317,9 +317,9 @@ public:
// Get the header of moof.
virtual SrsMp4MovieFragmentHeaderBox* mfhd();
virtual void set_mfhd(SrsMp4MovieFragmentHeaderBox* v);
// Get the traf.
virtual SrsMp4TrackFragmentBox* traf();
virtual void set_traf(SrsMp4TrackFragmentBox* v);
// Let moof support more than one traf
virtual void add_traf(SrsMp4TrackFragmentBox* v);
};
// 8.8.5 Movie Fragment Header Box (mfhd)
@ -710,8 +710,7 @@ public:
virtual ~SrsMp4MovieExtendsBox();
public:
// Get the track extends box.
virtual SrsMp4TrackExtendsBox* trex();
virtual void set_trex(SrsMp4TrackExtendsBox* v);
virtual void add_trex(SrsMp4TrackExtendsBox* v);
};
// 8.8.3 Track Extends Box(trex)
@ -1931,7 +1930,7 @@ public:
virtual srs_error_t write(SrsMp4MovieBox* moov);
// Write the samples info to moof.
// @param The dts is the dts of last segment.
virtual srs_error_t write(SrsMp4MovieFragmentBox* moof, uint64_t dts);
virtual srs_error_t write(SrsMp4TrackFragmentBox* traf, uint64_t dts);
private:
virtual srs_error_t write_track(SrsFrameType track,
SrsMp4DecodingTime2SampleBox* stts, SrsMp4SyncSampleBox* stss, SrsMp4CompositionTime2SampleBox* ctts,
@ -2111,6 +2110,7 @@ private:
};
// A fMP4 encoder, to write the init.mp4 with sequence header.
// TODO: What the M2ts short for?
class SrsMp4M2tsInitEncoder
{
private:
@ -2122,11 +2122,31 @@ public:
// Initialize the encoder with a writer w.
virtual srs_error_t initialize(ISrsWriter* w);
// Write the sequence header.
// TODO: merge this method to its sibling.
virtual srs_error_t write(SrsFormat* format, bool video, int tid);
/**
* The mp4 box format for init.mp4.
*
* |ftyp|
* |moov|
* | |mvhd|
* | |trak|
* | |trak|
* | |....|
* | |mvex|
* | | |trex|
* | | |trex|
* | | |....|
*
* Write the sequence header with both video and audio track.
*/
virtual srs_error_t write(SrsFormat* format, int v_tid, int a_tid);
};
// A fMP4 encoder, to cache segments then flush to disk, because the fMP4 should write
// trun box before mdat.
// TODO: fmp4 support package more than one tracks.
class SrsMp4M2tsSegmentEncoder
{
private:
@ -2160,6 +2180,47 @@ public:
virtual srs_error_t flush(uint64_t& dts);
};
// A fMP4 encoder, to cache segments then flush to disk, because the fMP4 should write
// trun box before mdat.
// TODO: fmp4 support package more than one tracks.
class SrsFmp4SegmentEncoder
{
private:
ISrsWriter* writer_;
uint32_t sequence_number_;
// TODO: audio, video may have different basetime.
srs_utime_t decode_basetime_;
uint32_t audio_track_id_;
uint32_t video_track_id_;
private:
uint32_t nb_audios_;
uint32_t nb_videos_;
uint32_t styp_bytes_;
uint64_t mdat_audio_bytes_;
uint64_t mdat_video_bytes_;
SrsMp4SampleManager* audio_samples_;
SrsMp4SampleManager* video_samples_;
public:
SrsFmp4SegmentEncoder();
virtual ~SrsFmp4SegmentEncoder();
public:
// Initialize the encoder with a writer w.
virtual srs_error_t initialize(ISrsWriter* w, uint32_t sequence, srs_utime_t basetime, uint32_t v_tid, uint32_t a_tid);
// Cache a sample.
// @param ht, The sample handler type, audio/soun or video/vide.
// @param ft, The frame type. For video, it's SrsVideoAvcFrameType.
// @param dts The output dts in milliseconds.
// @param pts The output pts in milliseconds.
// @param sample The output payload, user must free it.
// @param nb_sample The output size of payload.
// @remark All samples are RAW AAC/AVC data, because sequence header is writen to init.mp4.
virtual srs_error_t write_sample(SrsMp4HandlerType ht, uint16_t ft,
uint32_t dts, uint32_t pts, uint8_t* sample, uint32_t nb_sample);
// Flush the encoder, to write the moof and mdat.
virtual srs_error_t flush(uint64_t& dts);
};
// LCOV_EXCL_START
/////////////////////////////////////////////////////////////////////////////////
// MP4 dumps functions.

@ -3732,12 +3732,14 @@ VOID TEST(ConfigMainTest, CheckVhostConfig5)
if (true) {
MockSrsConfig conf;
HELPER_ASSERT_SUCCESS(conf.parse(_MIN_OK_CONF "vhost ossrs.net{hls{hls_keys on;hls_fragments_per_key 5;hls_key_file xxx;hls_key_file_path xxx2;hls_key_url xxx3;}}"));
HELPER_ASSERT_SUCCESS(conf.parse(_MIN_OK_CONF "vhost ossrs.net{hls{hls_keys on;hls_fragments_per_key 5;hls_key_file xxx;hls_key_file_path xxx2;hls_key_url xxx3;hls_use_fmp4 on;hls_fmp4_file xx.m4s;}}"));
EXPECT_TRUE(conf.get_hls_keys("ossrs.net"));
EXPECT_EQ(5, conf.get_hls_fragments_per_key("ossrs.net"));
EXPECT_STREQ("xxx", conf.get_hls_key_file("ossrs.net").c_str());
EXPECT_STREQ("xxx2", conf.get_hls_key_file_path("ossrs.net").c_str());
EXPECT_STREQ("xxx3", conf.get_hls_key_url("ossrs.net").c_str());
EXPECT_TRUE(conf.get_hls_use_fmp4("ossrs.net"));
EXPECT_STREQ("xx.m4s", conf.get_hls_fmp4_file("ossrs.net").c_str());
}
if (true) {
@ -5046,6 +5048,18 @@ VOID TEST(ConfigEnvTest, CheckEnvValuesHls)
SrsSetEnvConfig(hls_dts_directly, "SRS_VHOST_HLS_HLS_DTS_DIRECTLY", "off");
EXPECT_FALSE(conf.get_vhost_hls_dts_directly("__defaultVhost__"));
SrsSetEnvConfig(hls_use_fmp4_on, "SRS_VHOST_HLS_HLS_USE_FMP4", "on");
EXPECT_TRUE(conf.get_hls_use_fmp4("__defaultVhost__"));
SrsSetEnvConfig(hls_use_fmp4_off, "SRS_VHOST_HLS_HLS_USE_FMP4", "off");
EXPECT_FALSE(conf.get_hls_use_fmp4("__defaultVhost__"));
SrsSetEnvConfig(hls_use_fmp4_unexpected, "SRS_VHOST_HLS_HLS_USE_FMP4", "xx");
EXPECT_FALSE(conf.get_hls_use_fmp4("__defaultVhost__"));
SrsSetEnvConfig(hls_fmp4_file, "SRS_VHOST_HLS_HLS_FMP4_FILE", "xxx.m4s");
EXPECT_STREQ("xxx.m4s", conf.get_hls_fmp4_file("__defaultVhost__").c_str());
}
}

@ -898,11 +898,10 @@ VOID TEST(KernelMp4Test, TREXBox)
}
SrsMp4MovieExtendsBox box;
EXPECT_TRUE(NULL == box.trex());
SrsMp4TrackExtendsBox* trex = new SrsMp4TrackExtendsBox();
box.set_trex(trex);
EXPECT_TRUE(trex == box.trex());
box.add_trex(trex);
EXPECT_TRUE(trex == box.get(SrsMp4BoxTypeTREX));
}
VOID TEST(KernelMp4Test, TKHDBox)

Loading…
Cancel
Save