mirror of
https://github.com/ossrs/srs.git
synced 2025-03-09 15:49:59 +00:00
feature: support HLS fmp4 segment.
This commit is contained in:
parent
15fbe45a9a
commit
bf029ed564
11 changed files with 1829 additions and 107 deletions
|
@ -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/
|
||||
|
|
22
trunk/conf/hls.mp4.conf
Normal file
22
trunk/conf/hls.mp4.conf
Normal file
|
@ -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()
|
||||
void SrsMp4MovieExtendsBox::add_trex(SrsMp4TrackExtendsBox* v)
|
||||
{
|
||||
SrsMp4Box* box = get(SrsMp4BoxTypeTREX);
|
||||
return dynamic_cast<SrsMp4TrackExtendsBox*>(box);
|
||||
}
|
||||
|
||||
void SrsMp4MovieExtendsBox::set_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…
Add table
Add a link
Reference in a new issue