From afbc3443f33d55ae3340e03e93b62356fe4b6bd1 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 7 Feb 2017 21:56:20 +0800 Subject: [PATCH] fix #738, support DVR general mp4. 3.0.17 --- README.md | 2 + trunk/src/app/srs_app_dvr.cpp | 200 ++-- trunk/src/app/srs_app_dvr.hpp | 35 +- trunk/src/core/srs_core.hpp | 2 +- trunk/src/kernel/srs_kernel_error.hpp | 3 + trunk/src/kernel/srs_kernel_mp4.cpp | 984 ++++++++++++++++++-- trunk/src/kernel/srs_kernel_mp4.hpp | 176 +++- trunk/src/kernel/srs_kernel_utility.cpp | 37 + trunk/src/kernel/srs_kernel_utility.hpp | 9 + trunk/src/protocol/srs_protocol_utility.cpp | 25 - trunk/src/protocol/srs_protocol_utility.hpp | 12 +- trunk/src/protocol/srs_rtmp_handshake.cpp | 1 + 12 files changed, 1264 insertions(+), 222 deletions(-) diff --git a/README.md b/README.md index e9200a35a..43b806de1 100755 --- a/README.md +++ b/README.md @@ -185,6 +185,7 @@ Please select your language: ### V3 changes +* v3.0, 2017-02-07, fix [#738][bug #738] support DVR general mp4. 3.0.17 * v3.0, 2017-01-19, for [#742][bug #742] refine source, meta and origin hub. 3.0.16 * v3.0, 2017-01-17, for [#742][bug #742] refine source, timeout, live cycle. 3.0.15 * v3.0, 2017-01-11, fix [#735][bug #735] config transform refer_publish invalid. 3.0.14 @@ -1377,6 +1378,7 @@ Winlin [bug #735]: https://github.com/ossrs/srs/issues/735 [bug #742]: https://github.com/ossrs/srs/issues/742 +[bug #738]: https://github.com/ossrs/srs/issues/738 [bug #xxxxxxxxxxxxx]: https://github.com/ossrs/srs/issues/xxxxxxxxxxxxx [exo #828]: https://github.com/google/ExoPlayer/pull/828 diff --git a/trunk/src/app/srs_app_dvr.cpp b/trunk/src/app/srs_app_dvr.cpp index 35ee8bd7c..498ab1753 100644 --- a/trunk/src/app/srs_app_dvr.cpp +++ b/trunk/src/app/srs_app_dvr.cpp @@ -50,6 +50,7 @@ SrsDvrSegmenter::SrsDvrSegmenter() jitter = NULL; plan = NULL; duration = 0; + stream_previous_pkt_time = -1; path = ""; fs = new SrsFileWriter(); @@ -120,6 +121,9 @@ int SrsDvrSegmenter::open() jitter = new SrsRtmpJitter(); duration = 0; + // fresh stream starting. + stream_previous_pkt_time = -1; + // open file writer, in append or create mode. if ((ret = fs->open(tmp_dvr_file)) != ERROR_SUCCESS) { srs_error("open file stream for file %s failed. ret=%d", path.c_str(), ret); @@ -153,6 +157,10 @@ int SrsDvrSegmenter::write_audio(SrsSharedPtrMessage* shared_audio) return ret; } + if ((ret = on_update_duration(audio)) != ERROR_SUCCESS) { + return ret; + } + if ((ret = encode_audio(audio)) != ERROR_SUCCESS) { return ret; } @@ -171,13 +179,11 @@ int SrsDvrSegmenter::write_video(SrsSharedPtrMessage* shared_video) return ret; } - char* payload = video->payload; - int size = video->size; + if ((ret = encode_video(video)) != ERROR_SUCCESS) { + return ret; + } - bool is_sequence_header = SrsFlvCodec::video_is_sequence_header(payload, size); - bool is_key_frame = SrsFlvCodec::video_is_h264(payload, size) - && SrsFlvCodec::video_is_keyframe(payload, size) && !is_sequence_header; - if ((ret = encode_video(video, is_sequence_header, is_key_frame)) != ERROR_SUCCESS) { + if ((ret = on_update_duration(video)) != ERROR_SUCCESS) { return ret; } @@ -220,6 +226,24 @@ int SrsDvrSegmenter::close() return ret; } +int SrsDvrSegmenter::on_update_duration(SrsSharedPtrMessage* msg) +{ + int ret = ERROR_SUCCESS; + + // no previous packet or timestamp overflow. + if (stream_previous_pkt_time < 0 || stream_previous_pkt_time > msg->timestamp) { + stream_previous_pkt_time = msg->timestamp; + } + + // collect segment and stream duration, timestamp overflow is ok. + duration += msg->timestamp - stream_previous_pkt_time; + + // update previous packet time + stream_previous_pkt_time = msg->timestamp; + + return ret; +} + string SrsDvrSegmenter::generate_path() { // the path in config, for example, @@ -227,7 +251,7 @@ string SrsDvrSegmenter::generate_path() std::string path_config = _srs_config->get_dvr_path(req->vhost); // add [stream].[timestamp].flv as filename for dir - if (!srs_string_ends_with(path_config, ".flv")) { + if (!srs_string_ends_with(path_config, ".flv", ".mp4")) { path_config += "/[stream].[timestamp].flv"; } @@ -261,11 +285,6 @@ SrsDvrFlvSegmenter::SrsDvrFlvSegmenter() filesize_offset = 0; has_keyframe = false; - - starttime = -1; - stream_starttime = 0; - stream_previous_pkt_time = -1; - stream_duration = 0; } SrsDvrFlvSegmenter::~SrsDvrFlvSegmenter() @@ -335,16 +354,13 @@ int SrsDvrFlvSegmenter::open_encoder() has_keyframe = false; - // fresh stream starting. - starttime = -1; - stream_previous_pkt_time = -1; - stream_starttime = srs_update_system_time_ms(); - stream_duration = 0; - // update the duration and filesize offset. duration_offset = 0; filesize_offset = 0; + srs_freep(enc); + enc = new SrsFlvEncoder(); + if ((ret = enc->initialize(fs)) != ERROR_SUCCESS) { return ret; } @@ -431,17 +447,19 @@ int SrsDvrFlvSegmenter::encode_audio(SrsSharedPtrMessage* audio) return ret; } - if ((ret = on_update_duration(audio)) != ERROR_SUCCESS) { - return ret; - } - return ret; } -int SrsDvrFlvSegmenter::encode_video(SrsSharedPtrMessage* video, bool sh, bool keyframe) +int SrsDvrFlvSegmenter::encode_video(SrsSharedPtrMessage* video) { int ret = ERROR_SUCCESS; + char* payload = video->payload; + int size = video->size; + bool sh = SrsFlvCodec::video_is_sequence_header(payload, size); + bool keyframe = SrsFlvCodec::video_is_h264(payload, size) + && SrsFlvCodec::video_is_keyframe(payload, size) && !sh; + if (keyframe) { has_keyframe = true; } @@ -455,14 +473,6 @@ int SrsDvrFlvSegmenter::encode_video(SrsSharedPtrMessage* video, bool sh, bool k } } - // update segment duration, session plan just update the duration, - // the segment plan will reap segment if exceed, this video will write to next segment. - if ((ret = on_update_duration(video)) != ERROR_SUCCESS) { - return ret; - } - - char* payload = video->payload; - int size = video->size; if ((ret = enc->write_video(video->timestamp, payload, size)) != ERROR_SUCCESS) { return ret; } @@ -475,77 +485,133 @@ int SrsDvrFlvSegmenter::close_encoder() return refresh_metadata(); } -int SrsDvrFlvSegmenter::on_update_duration(SrsSharedPtrMessage* msg) -{ - int ret = ERROR_SUCCESS; - - // we must assumpt that the stream timestamp is monotonically increase, - // that is, always use time jitter to correct the timestamp. - // except the time jitter is disabled in config. - - // set the segment starttime at first time - if (starttime < 0) { - starttime = msg->timestamp; - } - - // no previous packet or timestamp overflow. - if (stream_previous_pkt_time < 0 || stream_previous_pkt_time > msg->timestamp) { - stream_previous_pkt_time = msg->timestamp; - } - - // collect segment and stream duration, timestamp overflow is ok. - duration += msg->timestamp - stream_previous_pkt_time; - stream_duration += msg->timestamp - stream_previous_pkt_time; - - // update previous packet time - stream_previous_pkt_time = msg->timestamp; - - return ret; -} - SrsDvrMp4Segmenter::SrsDvrMp4Segmenter() { enc = new SrsMp4Encoder(); + buffer = new SrsBuffer(); } SrsDvrMp4Segmenter::~SrsDvrMp4Segmenter() { srs_freep(enc); + srs_freep(buffer); } int SrsDvrMp4Segmenter::refresh_metadata() { - int ret = ERROR_SUCCESS; - return ret; + return ERROR_SUCCESS; } int SrsDvrMp4Segmenter::open_encoder() { int ret = ERROR_SUCCESS; + + srs_freep(enc); + enc = new SrsMp4Encoder(); + + if ((ret = enc->initialize(fs)) != ERROR_SUCCESS) { + return ret; + } + return ret; } -int SrsDvrMp4Segmenter::encode_metadata(SrsSharedPtrMessage* metadata) +int SrsDvrMp4Segmenter::encode_metadata(SrsSharedPtrMessage* /*metadata*/) { - int ret = ERROR_SUCCESS; - return ret; + return ERROR_SUCCESS; } int SrsDvrMp4Segmenter::encode_audio(SrsSharedPtrMessage* audio) { int ret = ERROR_SUCCESS; - return ret; + + if ((ret = buffer->initialize(audio->payload, audio->size)) != ERROR_SUCCESS) { + return ret; + } + + // E.4.2.1 AUDIODATA, flv_v10_1.pdf, page 3 + if (!buffer->require(1)) { + ret = ERROR_FLV_REQUIRE_SPACE; + srs_error("DVR require flva 1 byte space. ret=%d", ret); + return ret; + } + uint8_t v = buffer->read_1bytes(); + SrsCodecAudio sound_format = (SrsCodecAudio)((v >> 4) & 0x0f); + SrsCodecAudioSampleRate sound_rate = (SrsCodecAudioSampleRate)((v >> 2) & 0x03); + SrsCodecAudioSampleSize sound_size = (SrsCodecAudioSampleSize)((v >> 1) & 0x01); + SrsCodecAudioSoundType channels = (SrsCodecAudioSoundType)(v&0x01); + + uint16_t ct = 0x00; + if (sound_format == SrsCodecAudioAAC) { + if (!buffer->require(1)) { + ret = ERROR_FLV_REQUIRE_SPACE; + srs_error("DVR require flva 1 byte space, format=%d. ret=%d", sound_format, ret); + return ret; + } + v = buffer->read_1bytes(); + ct = (v == 0? SrsCodecAudioTypeSequenceHeader:SrsCodecAudioTypeRawData); + } + + if (ct == SrsCodecAudioTypeSequenceHeader) { + enc->acodec = sound_format; + enc->sample_rate = sound_rate; + enc->sound_bits = sound_size; + enc->channels = channels; + } + + uint8_t* sample = (uint8_t*)(buffer->data() + buffer->pos()); + uint32_t nb_sample = (uint32_t)(buffer->size() - buffer->pos()); + + uint32_t dts = (uint32_t)audio->timestamp; + return enc->write_sample(SrsMp4HandlerTypeSOUN, 0x00, ct, dts, dts, sample, nb_sample); } -int SrsDvrMp4Segmenter::encode_video(SrsSharedPtrMessage* video, bool sh, bool keyframe) +int SrsDvrMp4Segmenter::encode_video(SrsSharedPtrMessage* video) { int ret = ERROR_SUCCESS; - return ret; + + if ((ret = buffer->initialize(video->payload, video->size)) != ERROR_SUCCESS) { + return ret; + } + + // E.4.3.1 VIDEODATA, flv_v10_1.pdf, page 5 + if (!buffer->require(1)) { + ret = ERROR_FLV_REQUIRE_SPACE; + srs_error("DVR require flvv 1 byte space. ret=%d", ret); + return ret; + } + uint8_t v = buffer->read_1bytes(); + SrsCodecVideoAVCFrame frame_type = (SrsCodecVideoAVCFrame)((v>>4)&0x0f); + SrsCodecVideo codec_id = (SrsCodecVideo)(v&0x0f); + + if (!buffer->require(4)) { + ret = ERROR_FLV_REQUIRE_SPACE; + srs_error("DVR require flvv 4 bytes space, codec=%d. ret=%d", codec_id, ret); + return ret; + } + SrsCodecVideoAVCType ct = (SrsCodecVideoAVCType)buffer->read_1bytes(); + uint32_t cts = (uint32_t)buffer->read_3bytes(); + + if (ct == SrsCodecVideoAVCTypeSequenceHeader) { + enc->vcodec = codec_id; + } + + uint32_t dts = (uint32_t)video->timestamp; + uint32_t pts = dts + cts; + + uint8_t* sample = (uint8_t*)(buffer->data() + buffer->pos()); + uint32_t nb_sample = (uint32_t)(buffer->size() - buffer->pos()); + return enc->write_sample(SrsMp4HandlerTypeVIDE, frame_type, ct, dts, pts, sample, nb_sample); } int SrsDvrMp4Segmenter::close_encoder() { int ret = ERROR_SUCCESS; + + if ((ret = enc->flush()) != ERROR_SUCCESS) { + return ret; + } + return ret; } diff --git a/trunk/src/app/srs_app_dvr.hpp b/trunk/src/app/srs_app_dvr.hpp index eb47d13d4..c12c14086 100644 --- a/trunk/src/app/srs_app_dvr.hpp +++ b/trunk/src/app/srs_app_dvr.hpp @@ -60,10 +60,10 @@ class SrsDvrSegmenter : public ISrsReloadHandler protected: // The underlayer file object. SrsFileWriter* fs; - // The duration in ms of current segment. - int64_t duration; // Whether wait keyframe to reap segment. bool wait_keyframe; + // The duration in ms of current segment. + int64_t duration; private: // The path of current segment flv file path. std::string path; @@ -74,6 +74,11 @@ private: private: SrsRtmpJitter* jitter; SrsRtmpJitterAlgorithm jitter_algorithm; +private: + // The previous stream RTMP pkt time in ms, used to calc the duration. + // for the RTMP timestamp will overflow. + // TODO: FIXME: Use utility object to calc it. + int64_t stream_previous_pkt_time; public: SrsDvrSegmenter(); virtual ~SrsDvrSegmenter(); @@ -107,11 +112,13 @@ protected: virtual int open_encoder() = 0; virtual int encode_metadata(SrsSharedPtrMessage* metadata) = 0; virtual int encode_audio(SrsSharedPtrMessage* audio) = 0; - virtual int encode_video(SrsSharedPtrMessage* video, bool sh, bool keyframe) = 0; + virtual int encode_video(SrsSharedPtrMessage* video) = 0; virtual int close_encoder() = 0; private: // Generate the flv segment path. virtual std::string generate_path(); + // When update the duration of segment by rtmp msg. + virtual int on_update_duration(SrsSharedPtrMessage* msg); // interface ISrsReloadHandler public: virtual int on_reload_vhost_dvr(std::string vhost); @@ -134,19 +141,6 @@ private: int64_t filesize_offset; // Whether current segment has keyframe. bool has_keyframe; -private: - // The current segment starttime in ms, RTMP pkt time. - int64_t starttime; - // The stream start time in ms, to generate atc pts. abs time. - int64_t stream_starttime; - // The stream duration in ms, to generate atc segment. - int64_t stream_duration; - /** - * The previous stream RTMP pkt time in ms, used to calc the duration. - * for the RTMP timestamp will overflow. - */ - // TODO: FIXME: Use utility object to calc it. - int64_t stream_previous_pkt_time; public: SrsDvrFlvSegmenter(); virtual ~SrsDvrFlvSegmenter(); @@ -156,11 +150,8 @@ protected: virtual int open_encoder(); virtual int encode_metadata(SrsSharedPtrMessage* metadata); virtual int encode_audio(SrsSharedPtrMessage* audio); - virtual int encode_video(SrsSharedPtrMessage* video, bool sh, bool keyframe); + virtual int encode_video(SrsSharedPtrMessage* video); virtual int close_encoder(); -private: - // When update the duration of segment by rtmp msg. - virtual int on_update_duration(SrsSharedPtrMessage* msg); }; /** @@ -171,6 +162,8 @@ class SrsDvrMp4Segmenter : public SrsDvrSegmenter private: // The MP4 encoder, for MP4 target. SrsMp4Encoder* enc; + // The buffer to demux the packet to mp4 sample. + SrsBuffer* buffer; public: SrsDvrMp4Segmenter(); virtual ~SrsDvrMp4Segmenter(); @@ -180,7 +173,7 @@ protected: virtual int open_encoder(); virtual int encode_metadata(SrsSharedPtrMessage* metadata); virtual int encode_audio(SrsSharedPtrMessage* audio); - virtual int encode_video(SrsSharedPtrMessage* video, bool sh, bool keyframe); + virtual int encode_video(SrsSharedPtrMessage* video); virtual int close_encoder(); }; diff --git a/trunk/src/core/srs_core.hpp b/trunk/src/core/srs_core.hpp index efbc09fc3..7831f0769 100644 --- a/trunk/src/core/srs_core.hpp +++ b/trunk/src/core/srs_core.hpp @@ -31,7 +31,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // current release version #define VERSION_MAJOR 3 #define VERSION_MINOR 0 -#define VERSION_REVISION 16 +#define VERSION_REVISION 17 // generated by configure, only macros. #include diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index c26a5c256..03381af1f 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -257,6 +257,9 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define ERROR_MP4_ILLEGAL_TIMESTAMP 3081 #define ERROR_DVR_CANNOT_APPEND 3082 #define ERROR_DVR_ILLEGAL_PLAN 3083 +#define ERROR_FLV_REQUIRE_SPACE 3084 +#define ERROR_MP4_AVCC_CHANGE 3085 +#define ERROR_MP4_ASC_CHANGE 3086 /////////////////////////////////////////////////////// // HTTP/StreamCaster/KAFKA protocol error. diff --git a/trunk/src/kernel/srs_kernel_mp4.cpp b/trunk/src/kernel/srs_kernel_mp4.cpp index ea4c5cd7f..13ea4e59f 100644 --- a/trunk/src/kernel/srs_kernel_mp4.cpp +++ b/trunk/src/kernel/srs_kernel_mp4.cpp @@ -130,6 +130,7 @@ SrsMp4Box* SrsMp4Box::get(SrsMp4BoxType bt) vector::iterator it; for (it = boxes.begin(); it != boxes.end(); ++it) { SrsMp4Box* box = *it; + if (box->type == bt) { return box; } @@ -138,6 +139,24 @@ SrsMp4Box* SrsMp4Box::get(SrsMp4BoxType bt) return NULL; } +int SrsMp4Box::remove(SrsMp4BoxType bt) +{ + int nb_removed = 0; + + vector::iterator it; + for (it = boxes.begin(); it != boxes.end();) { + SrsMp4Box* box = *it; + + if (box->type == bt) { + it = boxes.erase(it); + } else { + ++it; + } + } + + return nb_removed; +} + int SrsMp4Box::discovery(SrsBuffer* buf, SrsMp4Box** ppbox) { *ppbox = NULL; @@ -240,6 +259,15 @@ int SrsMp4Box::encode(SrsBuffer* buf) { int ret = ERROR_SUCCESS; + uint64_t size = encode_actual_size(); + if (size > 0xffffffff) { + largesize = size; + } else { + smallsize = (uint32_t)size; + } + + start_pos = buf->pos(); + if ((ret = encode_header(buf)) != ERROR_SUCCESS) { srs_error("MP4 encode box header failed. ret=%d", ret); return ret; @@ -339,7 +367,7 @@ int SrsMp4Box::encode_header(SrsBuffer* buf) return ret; } - int size = nb_header(); + int size = SrsMp4Box::nb_header(); if (!buf->require(size)) { ret = ERROR_MP4_BOX_REQUIRE_SPACE; srs_error("MP4 box require %d bytes space. ret=%d", size, ret); @@ -356,6 +384,13 @@ int SrsMp4Box::encode_header(SrsBuffer* buf) buf->write_bytes((char*)usertype, 16); } + int lrsz = nb_header() - SrsMp4Box::nb_header(); + if (!buf->require(lrsz)) { + ret = ERROR_MP4_BOX_REQUIRE_SPACE; + srs_error("MP4 box require %d bytes space. ret=%d", lrsz, ret); + return ret; + } + return ret; } @@ -413,6 +448,11 @@ int SrsMp4Box::decode_header(SrsBuffer* buf) return ret; } +uint64_t SrsMp4Box::encode_actual_size() +{ + return nb_bytes(); +} + SrsMp4FullBox::SrsMp4FullBox() { version = 0; @@ -436,6 +476,12 @@ int SrsMp4FullBox::encode_header(SrsBuffer* buf) return ret; } + if (!buf->require(4)) { + ret = ERROR_MP4_BOX_REQUIRE_SPACE; + srs_error("MP4 full box requires 4 bytes space. ret=%d", ret); + return ret; + } + buf->write_1bytes(version); buf->write_3bytes(flags); @@ -486,6 +532,18 @@ SrsMp4FileTypeBox::~SrsMp4FileTypeBox() srs_freepa(compatible_brands); } +void SrsMp4FileTypeBox::set_compatible_brands(SrsMp4BoxBrand b0, SrsMp4BoxBrand b1, SrsMp4BoxBrand b2, SrsMp4BoxBrand b3) +{ + nb_compatible_brands = 4; + + srs_freepa(compatible_brands); + compatible_brands = new SrsMp4BoxBrand[4]; + compatible_brands[0] = b0; + compatible_brands[1] = b1; + compatible_brands[2] = b2; + compatible_brands[3] = b3; +} + int SrsMp4FileTypeBox::nb_header() { return SrsMp4Box::nb_header() + 8 + nb_compatible_brands * 4; @@ -547,39 +605,20 @@ SrsMp4MediaDataBox::~SrsMp4MediaDataBox() srs_freepa(data); } -int SrsMp4MediaDataBox::nb_header() +uint64_t SrsMp4MediaDataBox::encode_actual_size() { return SrsMp4Box::nb_header() + nb_data; } -int SrsMp4MediaDataBox::encode_header(SrsBuffer* buf) +int SrsMp4MediaDataBox::decode(SrsBuffer* buf) { int ret = ERROR_SUCCESS; - if ((ret = SrsMp4Box::encode_header(buf)) != ERROR_SUCCESS) { + if ((ret = SrsMp4Box::decode(buf)) != ERROR_SUCCESS) { return ret; } - if (nb_data) { - buf->write_bytes((char*)data, nb_data); - } - - return ret; -} - -int SrsMp4MediaDataBox::decode_header(SrsBuffer* buf) -{ - int ret = ERROR_SUCCESS; - - if ((ret = SrsMp4Box::decode_header(buf)) != ERROR_SUCCESS) { - return ret; - } - - int left = left_space(buf); - if (left) { - data = new uint8_t[left]; - buf->read_bytes((char*)data, left); - } + nb_data = left_space(buf); return ret; } @@ -648,6 +687,12 @@ SrsMp4MovieHeaderBox* SrsMp4MovieBox::mvhd() return dynamic_cast(box); } +void SrsMp4MovieBox::set_mvhd(SrsMp4MovieHeaderBox* v) +{ + remove(SrsMp4BoxTypeMVHD); + boxes.insert(boxes.begin(), v); +} + SrsMp4TrackBox* SrsMp4MovieBox::video() { for (int i = 0; i < boxes.size(); i++) { @@ -676,6 +721,11 @@ SrsMp4TrackBox* SrsMp4MovieBox::audio() return NULL; } +void SrsMp4MovieBox::add_trak(SrsMp4TrackBox* v) +{ + boxes.push_back(v); +} + int SrsMp4MovieBox::nb_vide_tracks() { int nb_tracks = 0; @@ -738,6 +788,8 @@ SrsMp4MovieHeaderBox::SrsMp4MovieHeaderBox() memcpy(matrix, v, 36); memset(pre_defined, 0, 24); + + next_track_ID = 0; } SrsMp4MovieHeaderBox::~SrsMp4MovieHeaderBox() @@ -853,6 +905,9 @@ SrsMp4TrackHeaderBox::SrsMp4TrackHeaderBox() int32_t v[] = {0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000}; memcpy(matrix, v, 36); + + width = height = 0; + flags = 0x03; } SrsMp4TrackHeaderBox::~SrsMp4TrackHeaderBox() @@ -875,6 +930,12 @@ SrsMp4TrackHeaderBox* SrsMp4TrackBox::tkhd() return dynamic_cast(box); } +void SrsMp4TrackBox::set_tkhd(SrsMp4TrackHeaderBox* v) +{ + remove(SrsMp4BoxTypeTKHD); + boxes.insert(boxes.begin(), v); +} + SrsMp4ChunkOffsetBox* SrsMp4TrackBox::stco() { SrsMp4SampleTableBox* box = stbl(); @@ -971,6 +1032,12 @@ SrsMp4MediaBox* SrsMp4TrackBox::mdia() return dynamic_cast(box); } +void SrsMp4TrackBox::set_mdia(SrsMp4MediaBox* v) +{ + remove(SrsMp4BoxTypeMDIA); + boxes.push_back(v); +} + SrsMp4MediaInformationBox* SrsMp4TrackBox::minf() { SrsMp4MediaBox* box = mdia(); @@ -1217,12 +1284,36 @@ SrsMp4MediaHeaderBox* SrsMp4MediaBox::mdhd() return dynamic_cast(box); } +void SrsMp4MediaBox::set_mdhd(SrsMp4MediaHeaderBox* v) +{ + remove(SrsMp4BoxTypeMDHD); + boxes.insert(boxes.begin(), v); +} + +SrsMp4HandlerReferenceBox* SrsMp4MediaBox::hdlr() +{ + SrsMp4Box* box = get(SrsMp4BoxTypeHDLR); + return dynamic_cast(box); +} + +void SrsMp4MediaBox::set_hdlr(SrsMp4HandlerReferenceBox* v) +{ + remove(SrsMp4BoxTypeHDLR); + boxes.push_back(v); +} + SrsMp4MediaInformationBox* SrsMp4MediaBox::minf() { SrsMp4Box* box = get(SrsMp4BoxTypeMINF); return dynamic_cast(box); } +void SrsMp4MediaBox::set_minf(SrsMp4MediaInformationBox* v) +{ + remove(SrsMp4BoxTypeMINF); + boxes.push_back(v); +} + SrsMp4MediaHeaderBox::SrsMp4MediaHeaderBox() { type = SrsMp4BoxTypeMDHD; @@ -1234,34 +1325,34 @@ SrsMp4MediaHeaderBox::~SrsMp4MediaHeaderBox() { } -uint8_t SrsMp4MediaHeaderBox::language0() +char SrsMp4MediaHeaderBox::language0() { - return (language >> 10) & 0x1f; + return (char)((language >> 10) & 0x1f + 0x60); } -void SrsMp4MediaHeaderBox::set_language0(uint8_t v) +void SrsMp4MediaHeaderBox::set_language0(char v) { - language |= uint16_t(v & 0x1f) << 10; + language |= uint16_t((uint8_t(v) - 0x60) & 0x1f) << 10; } -uint8_t SrsMp4MediaHeaderBox::language1() +char SrsMp4MediaHeaderBox::language1() { - return (language >> 5) & 0x1f; + return (char)((language >> 5) & 0x1f + 0x60); } -void SrsMp4MediaHeaderBox::set_language1(uint8_t v) +void SrsMp4MediaHeaderBox::set_language1(char v) { - language |= uint16_t(v & 0x1f) << 5; + language |= uint16_t((uint8_t(v) - 0x60) & 0x1f) << 5; } -uint8_t SrsMp4MediaHeaderBox::language2() +char SrsMp4MediaHeaderBox::language2() { - return language & 0x1f; + return (char)(language & 0x1f + 0x60); } -void SrsMp4MediaHeaderBox::set_language2(uint8_t v) +void SrsMp4MediaHeaderBox::set_language2(char v) { - language |= uint16_t(v & 0x1f); + language |= uint16_t((uint8_t(v) - 0x60) & 0x1f); } int SrsMp4MediaHeaderBox::nb_header() @@ -1405,12 +1496,54 @@ SrsMp4MediaInformationBox::~SrsMp4MediaInformationBox() { } +SrsMp4VideoMeidaHeaderBox* SrsMp4MediaInformationBox::vmhd() +{ + SrsMp4Box* box = get(SrsMp4BoxTypeVMHD); + return dynamic_cast(box); +} + +void SrsMp4MediaInformationBox::set_vmhd(SrsMp4VideoMeidaHeaderBox* v) +{ + remove(SrsMp4BoxTypeVMHD); + boxes.push_back(v); +} + +SrsMp4SoundMeidaHeaderBox* SrsMp4MediaInformationBox::smhd() +{ + SrsMp4Box* box = get(SrsMp4BoxTypeSMHD); + return dynamic_cast(box); +} + +void SrsMp4MediaInformationBox::set_smhd(SrsMp4SoundMeidaHeaderBox* v) +{ + remove(SrsMp4BoxTypeSMHD); + boxes.push_back(v); +} + +SrsMp4DataInformationBox* SrsMp4MediaInformationBox::dinf() +{ + SrsMp4Box* box = get(SrsMp4BoxTypeDINF); + return dynamic_cast(box); +} + +void SrsMp4MediaInformationBox::set_dinf(SrsMp4DataInformationBox* v) +{ + remove(SrsMp4BoxTypeDINF); + boxes.push_back(v); +} + SrsMp4SampleTableBox* SrsMp4MediaInformationBox::stbl() { SrsMp4Box* box = get(SrsMp4BoxTypeSTBL); return dynamic_cast(box); } +void SrsMp4MediaInformationBox::set_stbl(SrsMp4SampleTableBox* v) +{ + remove(SrsMp4BoxTypeSTBL); + boxes.push_back(v); +} + SrsMp4VideoMeidaHeaderBox::SrsMp4VideoMeidaHeaderBox() { type = SrsMp4BoxTypeVMHD; @@ -1515,6 +1648,18 @@ SrsMp4DataInformationBox::~SrsMp4DataInformationBox() { } +SrsMp4DataReferenceBox* SrsMp4DataInformationBox::dref() +{ + SrsMp4Box* box = get(SrsMp4BoxTypeDREF); + return dynamic_cast(box); +} + +void SrsMp4DataInformationBox::set_dref(SrsMp4DataReferenceBox* v) +{ + remove(SrsMp4BoxTypeDREF); + boxes.push_back(v); +} + SrsMp4DataEntryBox::SrsMp4DataEntryBox() { } @@ -1536,7 +1681,7 @@ int SrsMp4DataEntryUrlBox::nb_header() { // a 24-bit integer with flags; one flag is defined (x000001) which means that the media // data is in the same file as the Movie Box containing this data reference. - if (flags == 1) { + if (location.empty()) { return SrsMp4FullBox::nb_header(); } return SrsMp4FullBox::nb_header()+srs_mp4_string_length(location); @@ -1546,18 +1691,19 @@ int SrsMp4DataEntryUrlBox::encode_header(SrsBuffer* buf) { int ret = ERROR_SUCCESS; - if ((ret = SrsMp4FullBox::encode_header(buf)) != ERROR_SUCCESS) { - return ret; - } - // a 24-bit integer with flags; one flag is defined (x000001) which means that the media // data is in the same file as the Movie Box containing this data reference. if (location.empty()) { flags = 0x01; + } + + if ((ret = SrsMp4FullBox::encode_header(buf)) != ERROR_SUCCESS) { return ret; } - srs_mp4_string_write(buf, location); + if (!location.empty()) { + srs_mp4_string_write(buf, location); + } return ret; } @@ -1658,6 +1804,12 @@ SrsMp4DataEntryBox* SrsMp4DataReferenceBox::entry_at(int index) return entries.at(index); } +SrsMp4DataReferenceBox* SrsMp4DataReferenceBox::append(SrsMp4DataEntryBox* v) +{ + entries.push_back(v); + return this; +} + int SrsMp4DataReferenceBox::nb_header() { int size = SrsMp4FullBox::nb_header(); @@ -1746,42 +1898,99 @@ SrsMp4SampleDescriptionBox* SrsMp4SampleTableBox::stsd() return dynamic_cast(box); } +void SrsMp4SampleTableBox::set_stsd(SrsMp4SampleDescriptionBox* v) +{ + remove(SrsMp4BoxTypeSTSD); + boxes.push_back(v); +} + SrsMp4ChunkOffsetBox* SrsMp4SampleTableBox::stco() { SrsMp4Box* box = get(SrsMp4BoxTypeSTCO); return dynamic_cast(box); } +void SrsMp4SampleTableBox::set_stco(SrsMp4ChunkOffsetBox* v) +{ + remove(SrsMp4BoxTypeSTCO); + boxes.push_back(v); +} + SrsMp4SampleSizeBox* SrsMp4SampleTableBox::stsz() { SrsMp4Box* box = get(SrsMp4BoxTypeSTSZ); return dynamic_cast(box); } +void SrsMp4SampleTableBox::set_stsz(SrsMp4SampleSizeBox* v) +{ + remove(SrsMp4BoxTypeSTSZ); + boxes.push_back(v); +} + SrsMp4Sample2ChunkBox* SrsMp4SampleTableBox::stsc() { SrsMp4Box* box = get(SrsMp4BoxTypeSTSC); return dynamic_cast(box); } +void SrsMp4SampleTableBox::set_stsc(SrsMp4Sample2ChunkBox* v) +{ + remove(SrsMp4BoxTypeSTSC); + boxes.push_back(v); +} + SrsMp4DecodingTime2SampleBox* SrsMp4SampleTableBox::stts() { SrsMp4Box* box = get(SrsMp4BoxTypeSTTS); return dynamic_cast(box); } +void SrsMp4SampleTableBox::set_stts(SrsMp4DecodingTime2SampleBox* v) +{ + remove(SrsMp4BoxTypeSTTS); + boxes.push_back(v); +} + SrsMp4CompositionTime2SampleBox* SrsMp4SampleTableBox::ctts() { SrsMp4Box* box = get(SrsMp4BoxTypeCTTS); return dynamic_cast(box); } +void SrsMp4SampleTableBox::set_ctts(SrsMp4CompositionTime2SampleBox* v) +{ + remove(SrsMp4BoxTypeCTTS); + boxes.push_back(v); +} + SrsMp4SyncSampleBox* SrsMp4SampleTableBox::stss() { SrsMp4Box* box = get(SrsMp4BoxTypeSTSS); return dynamic_cast(box); } +void SrsMp4SampleTableBox::set_stss(SrsMp4SyncSampleBox* v) +{ + remove(SrsMp4BoxTypeSTSS); + boxes.push_back(v); +} + +int SrsMp4SampleTableBox::nb_header() +{ + return SrsMp4Box::nb_header(); +} + +int SrsMp4SampleTableBox::encode_header(SrsBuffer* buf) +{ + return SrsMp4Box::encode_header(buf); +} + +int SrsMp4SampleTableBox::decode_header(SrsBuffer* buf) +{ + return SrsMp4Box::decode_header(buf); +} + SrsMp4SampleEntry::SrsMp4SampleEntry() { memset(reserved, 0, 6); @@ -1828,6 +2037,8 @@ int SrsMp4SampleEntry::decode_header(SrsBuffer* buf) SrsMp4VisualSampleEntry::SrsMp4VisualSampleEntry() { + type = SrsMp4BoxTypeAVC1; + pre_defined0 = 0; reserved0 = 0; reserved1 = 0; @@ -1850,6 +2061,12 @@ SrsMp4AvccBox* SrsMp4VisualSampleEntry::avcC() return dynamic_cast(box); } +void SrsMp4VisualSampleEntry::set_avcC(SrsMp4AvccBox* v) +{ + remove(SrsMp4BoxTypeAVCC); + boxes.push_back(v); +} + int SrsMp4VisualSampleEntry::nb_header() { return SrsMp4SampleEntry::nb_header()+2+2+12+2+2+4+4+4+2+32+2+2; @@ -1956,6 +2173,8 @@ int SrsMp4AvccBox::decode_header(SrsBuffer* buf) SrsMp4AudioSampleEntry::SrsMp4AudioSampleEntry() { + type = SrsMp4BoxTypeMP4A; + reserved0 = 0; pre_defined0 = 0; reserved1 = 0; @@ -1973,6 +2192,12 @@ SrsMp4EsdsBox* SrsMp4AudioSampleEntry::esds() return dynamic_cast(box); } +void SrsMp4AudioSampleEntry::set_esds(SrsMp4EsdsBox* v) +{ + remove(SrsMp4BoxTypeESDS); + boxes.push_back(v); +} + SrsMp4DecoderSpecificInfo* SrsMp4AudioSampleEntry::asc() { SrsMp4EsdsBox* box = esds(); @@ -2194,7 +2419,7 @@ SrsMp4DecoderConfigDescriptor::~SrsMp4DecoderConfigDescriptor() int32_t SrsMp4DecoderConfigDescriptor::nb_payload() { - return 12 + (decSpecificInfo? decSpecificInfo->nb_bytes():0); + return 13 + (decSpecificInfo? decSpecificInfo->nb_bytes():0); } int SrsMp4DecoderConfigDescriptor::encode_payload(SrsBuffer* buf) @@ -2212,6 +2437,10 @@ int SrsMp4DecoderConfigDescriptor::encode_payload(SrsBuffer* buf) buf->write_4bytes(maxBitrate); buf->write_4bytes(avgBitrate); + if (decSpecificInfo && (ret = decSpecificInfo->encode(buf)) != ERROR_SUCCESS) { + return ret; + } + return ret; } @@ -2508,6 +2737,12 @@ SrsMp4SampleEntry* SrsMp4SampleDescriptionBox::entrie_at(int index) return entries.at(index); } +SrsMp4SampleDescriptionBox* SrsMp4SampleDescriptionBox::append(SrsMp4SampleEntry* v) +{ + entries.push_back(v); + return this; +} + int SrsMp4SampleDescriptionBox::nb_header() { int size = SrsMp4FullBox::nb_header(); @@ -3278,6 +3513,207 @@ SrsMp4Sample* SrsMp4SampleManager::at(uint32_t index) return samples.at(index); } +void SrsMp4SampleManager::append(SrsMp4Sample* sample) +{ + samples.push_back(sample); +} + +int SrsMp4SampleManager::write(SrsMp4MovieBox* moov) +{ + int ret = ERROR_SUCCESS; + + SrsMp4TrackBox* vide = moov->video(); + if (vide) { + bool has_cts = false; + vector::iterator it; + for (it = samples.begin(); it != samples.end(); ++it) { + SrsMp4Sample* sample = *it; + if (sample->dts != sample->pts) { + has_cts = true; + break; + } + } + + SrsMp4SampleTableBox* stbl = vide->stbl(); + + SrsMp4DecodingTime2SampleBox* stts = new SrsMp4DecodingTime2SampleBox(); + stbl->set_stts(stts); + + SrsMp4SyncSampleBox* stss = new SrsMp4SyncSampleBox(); + stbl->set_stss(stss); + + SrsMp4CompositionTime2SampleBox* ctts = NULL; + if (has_cts) { + ctts = new SrsMp4CompositionTime2SampleBox(); + stbl->set_ctts(ctts); + } + + SrsMp4Sample2ChunkBox* stsc = new SrsMp4Sample2ChunkBox(); + stbl->set_stsc(stsc); + + SrsMp4SampleSizeBox* stsz = new SrsMp4SampleSizeBox(); + stbl->set_stsz(stsz); + + SrsMp4ChunkOffsetBox* stco = new SrsMp4ChunkOffsetBox(); + stbl->set_stco(stco); + + if ((ret = write_track(SrsCodecFlvTagVideo, stts, stss, ctts, stsc, stsz, stco)) != ERROR_SUCCESS) { + return ret; + } + } + + SrsMp4TrackBox* soun = moov->audio(); + if (soun) { + SrsMp4SampleTableBox* stbl = soun->stbl(); + + SrsMp4DecodingTime2SampleBox* stts = new SrsMp4DecodingTime2SampleBox(); + stbl->set_stts(stts); + + SrsMp4SyncSampleBox* stss = NULL; + SrsMp4CompositionTime2SampleBox* ctts = NULL; + + SrsMp4Sample2ChunkBox* stsc = new SrsMp4Sample2ChunkBox(); + stbl->set_stsc(stsc); + + SrsMp4SampleSizeBox* stsz = new SrsMp4SampleSizeBox(); + stbl->set_stsz(stsz); + + SrsMp4ChunkOffsetBox* stco = new SrsMp4ChunkOffsetBox(); + stbl->set_stco(stco); + + if ((ret = write_track(SrsCodecFlvTagAudio, stts, stss, ctts, stsc, stsz, stco)) != ERROR_SUCCESS) { + return ret; + } + } + + return ret; +} + +int SrsMp4SampleManager::write_track(SrsCodecFlvTag track, + SrsMp4DecodingTime2SampleBox* stts, SrsMp4SyncSampleBox* stss, SrsMp4CompositionTime2SampleBox* ctts, + SrsMp4Sample2ChunkBox* stsc, SrsMp4SampleSizeBox* stsz, SrsMp4ChunkOffsetBox* stco) +{ + int ret = ERROR_SUCCESS; + + SrsMp4SttsEntry stts_entry; + vector stts_entries; + + SrsMp4CttsEntry ctts_entry; + vector ctts_entries; + + vector stsz_entries; + vector stco_entries; + vector stss_entries; + + SrsMp4Sample* previous = NULL; + vector::iterator it; + for (it = samples.begin(); it != samples.end(); ++it) { + SrsMp4Sample* sample = *it; + if (sample->type != track) { + continue; + } + + stsz_entries.push_back(sample->nb_data); + stco_entries.push_back((uint32_t)sample->offset); + + if (sample->frame_type == SrsCodecVideoAVCFrameKeyFrame) { + stss_entries.push_back(sample->index + 1); + } + + if (stts) { + if (previous) { + uint32_t delta = (uint32_t)(sample->dts - previous->dts); + if (stts_entry.sample_delta == 0 || stts_entry.sample_delta == delta) { + stts_entry.sample_delta = delta; + stts_entry.sample_count++; + } else { + stts_entries.push_back(stts_entry); + stts_entry.sample_count = 1; + stts_entry.sample_delta = delta; + } + } else { + // The first sample always in the STTS table. + stts_entry.sample_count++; + } + } + + if (ctts) { + int64_t offset = sample->pts - sample->dts; + if (offset < 0) { + ctts->version = 0x01; + } + if (ctts_entry.sample_count == 0 || ctts_entry.sample_offset == offset) { + ctts_entry.sample_count++; + } else { + ctts_entries.push_back(ctts_entry); + ctts_entry.sample_offset = offset; + ctts_entry.sample_count = 1; + } + } + + previous = sample; + } + + if (stts && stts_entry.sample_count) { + stts_entries.push_back(stts_entry); + } + + if (ctts && ctts_entry.sample_count) { + ctts_entries.push_back(ctts_entry); + } + + if (stts && !stts_entries.empty()) { + stts->entry_count = (uint32_t)stts_entries.size(); + stts->entries = new SrsMp4SttsEntry[stts->entry_count]; + for (int i = 0; i < (int)stts_entries.size(); i++) { + stts->entries[i] = stts_entries.at(i); + } + } + + if (ctts && !ctts_entries.empty()) { + ctts->entry_count = (uint32_t)ctts_entries.size(); + ctts->entries = new SrsMp4CttsEntry[ctts->entry_count]; + for (int i = 0; i < (int)ctts_entries.size(); i++) { + ctts->entries[i] = ctts_entries.at(i); + } + } + + if (stsc) { + stsc->entry_count = 1; + stsc->entries = new SrsMp4StscEntry[1]; + + SrsMp4StscEntry& v = stsc->entries[0]; + v.first_chunk = v.sample_description_index = v.samples_per_chunk = 1; + } + + if (stsz && !stsz_entries.empty()) { + stsz->sample_size = 0; + stsz->sample_count = (uint32_t)stsz_entries.size(); + stsz->entry_sizes = new uint32_t[stsz->sample_count]; + for (int i = 0; i < stsz->sample_count; i++) { + stsz->entry_sizes[i] = stsz_entries.at(i); + } + } + + if (stco && !stco_entries.empty()) { + stco->entry_count = (uint32_t)stco_entries.size(); + stco->entries = new uint32_t[stco->entry_count]; + for (int i = 0; i < stco->entry_count; i++) { + stco->entries[i] = stco_entries.at(i); + } + } + + if (stss && !stss_entries.empty()) { + stss->entry_count = (uint32_t)stss_entries.size(); + stss->sample_numbers = new uint32_t[stss->entry_count]; + for (int i = 0; i < stss->entry_count; i++) { + stss->sample_numbers[i] = stss_entries.at(i); + } + } + + return ret; +} + int SrsMp4SampleManager::do_load(map& tses, SrsMp4MovieBox* moov) { int ret = ERROR_SUCCESS; @@ -3741,32 +4177,31 @@ int SrsMp4Decoder::do_load_next_box(SrsMp4Box** ppbox, uint32_t required_box_typ return ret; } - // For mdat, skip the content. - if (box->is_mdat()) { - // Never load the mdat box content, instead we skip it, for it's too large. - // The demuxer use seeker to read sample one by one. - if (box->is_mdat()) { - int offset = (int)(box->sz() - stream->length()); - if (offset < 0) { - stream->erase(stream->length() + offset); - } else { - stream->erase(stream->length()); - } - if (offset > 0 && (ret = rsio->lseek(offset, SEEK_CUR, NULL)) != ERROR_SUCCESS) { - return ret; - } - } - } else { - // Util we can demux the whole box. + // Util we can demux the whole box. + // For mdat, only the header is required. + if (!box->is_mdat()) { if (!buffer->require((int)box->sz())) { continue; } - - // Decode the matched box or any box is matched. - if (!required_box_type || box->type == required_box_type) { - ret = box->decode(buffer); - } + } + // Decode the matched box or any box is matched. + if (!required_box_type || box->type == required_box_type || box->is_mdat()) { + ret = box->decode(buffer); + } + + // For mdat, always skip the content. + if (box->is_mdat()) { + int offset = (int)(box->sz() - stream->length()); + if (offset < 0) { + stream->erase(stream->length() + offset); + } else { + stream->erase(stream->length()); + } + if (offset > 0 && (ret = rsio->lseek(offset, SEEK_CUR, NULL)) != ERROR_SUCCESS) { + return ret; + } + } else { // Remove the consumed bytes. stream->erase((int)box->sz()); } @@ -3785,16 +4220,427 @@ int SrsMp4Decoder::do_load_next_box(SrsMp4Box** ppbox, uint32_t required_box_typ SrsMp4Encoder::SrsMp4Encoder() { - writer = NULL; + wsio = NULL; + mdat_bytes = 0; + mdat_offset = 0; + buffer = new SrsBuffer(); + nb_asc = nb_avcc = 0; + pasc = pavcc = NULL; + nb_audios = nb_videos = 0; + samples = new SrsMp4SampleManager(); + aduration = vduration = 0; + width = height = 0; + + acodec = SrsCodecAudioForbidden; + sample_rate = SrsCodecAudioSampleRateForbidden; + sound_bits = SrsCodecAudioSampleSizeForbidden; + channels = SrsCodecAudioSoundTypeForbidden; + vcodec = SrsCodecVideoForbidden; } SrsMp4Encoder::~SrsMp4Encoder() { + srs_freep(samples); + srs_freep(buffer); + srs_freepa(pasc); + srs_freepa(pavcc); } -int SrsMp4Encoder::initialize(ISrsWriter* w) +int SrsMp4Encoder::initialize(ISrsWriteSeeker* ws) { - writer = w; - return ERROR_SUCCESS; + int ret = ERROR_SUCCESS; + + wsio = ws; + + // Write ftyp box. + if (true) { + SrsMp4FileTypeBox* ftyp = new SrsMp4FileTypeBox(); + SrsAutoFree(SrsMp4FileTypeBox, ftyp); + + ftyp->major_brand = SrsMp4BoxBrandISOM; + ftyp->minor_version = 512; + ftyp->set_compatible_brands(SrsMp4BoxBrandISOM, SrsMp4BoxBrandISO2, SrsMp4BoxBrandAVC1, SrsMp4BoxBrandMP41); + + int nb_data = ftyp->nb_bytes(); + uint8_t* data = new uint8_t[nb_data]; + SrsAutoFreeA(uint8_t, data); + if ((ret = buffer->initialize((char*)data, nb_data)) != ERROR_SUCCESS) { + return ret; + } + if ((ret = ftyp->encode(buffer)) != ERROR_SUCCESS) { + return ret; + } + + // TODO: FIXME: Ensure write ok. + if ((ret = wsio->write(data, nb_data, NULL)) != ERROR_SUCCESS) { + return ret; + } + } + + // Write mdat box. + if (true) { + // Write empty mdat box, + // its payload will be writen by samples, + // and we will update its header(size) when flush. + SrsMp4MediaDataBox* mdat = new SrsMp4MediaDataBox(); + SrsAutoFree(SrsMp4MediaDataBox, mdat); + + // Update the mdat box from this offset. + if ((ret = wsio->lseek(0, SEEK_CUR, &mdat_offset)) != ERROR_SUCCESS) { + return ret; + } + + int nb_data = mdat->nb_bytes(); + uint8_t* data = new uint8_t[nb_data]; + SrsAutoFreeA(uint8_t, data); + if ((ret = buffer->initialize((char*)data, nb_data)) != ERROR_SUCCESS) { + return ret; + } + if ((ret = mdat->encode(buffer)) != ERROR_SUCCESS) { + return ret; + } + + // TODO: FIXME: Ensure all bytes are writen. + if ((ret = wsio->write(data, nb_data, NULL)) != ERROR_SUCCESS) { + return ret; + } + + mdat_bytes = 0; + } + + return ret; +} + +int SrsMp4Encoder::write_sample(SrsMp4HandlerType ht, + uint16_t ft, uint16_t ct, uint32_t dts, uint32_t pts, uint8_t* sample, uint32_t nb_sample) +{ + int ret = ERROR_SUCCESS; + + SrsMp4Sample* ps = new SrsMp4Sample(); + + // For SPS/PPS or ASC, copy it to moov. + bool vsh = (ht == SrsMp4HandlerTypeVIDE) && (ct == SrsCodecVideoAVCTypeSequenceHeader); + bool ash = (ht == SrsMp4HandlerTypeSOUN) && (ct == SrsCodecAudioTypeSequenceHeader); + if (vsh || ash) { + ret = copy_sequence_header(vsh, sample, nb_sample); + srs_freep(ps); + return ret; + } + + if (ht == SrsMp4HandlerTypeVIDE) { + ps->type = SrsCodecFlvTagVideo; + ps->frame_type = (SrsCodecVideoAVCFrame)ft; + ps->index = nb_videos++; + vduration = dts; + } else if (ht == SrsMp4HandlerTypeSOUN) { + ps->type = SrsCodecFlvTagAudio; + ps->index = nb_audios++; + aduration = dts; + } else { + srs_freep(ps); + return ret; + } + ps->tbn = 1000; + ps->dts = dts; + ps->pts = pts; + + if ((ret = do_write_sample(ps, sample, nb_sample)) != ERROR_SUCCESS) { + srs_freep(ps); + return ret; + } + + // Append to manager to build the moov. + samples->append(ps); + + return ret; +} + +int SrsMp4Encoder::flush() +{ + int ret = ERROR_SUCCESS; + + if (!nb_audios && !nb_videos) { + ret = ERROR_MP4_ILLEGAL_MOOV; + srs_error("MP4 missing audio and video track. ret=%d", ret); + return ret; + } + + // Write moov. + if (true) { + SrsMp4MovieBox* moov = new SrsMp4MovieBox(); + SrsAutoFree(SrsMp4MovieBox, moov); + + SrsMp4MovieHeaderBox* mvhd = new SrsMp4MovieHeaderBox(); + moov->set_mvhd(mvhd); + + mvhd->timescale = 1000; // Use tbn ms. + mvhd->duration_in_tbn = srs_max(vduration, aduration); + mvhd->next_track_ID++; + + if (nb_videos) { + SrsMp4TrackBox* trak = new SrsMp4TrackBox(); + moov->add_trak(trak); + + SrsMp4TrackHeaderBox* tkhd = new SrsMp4TrackHeaderBox(); + trak->set_tkhd(tkhd); + + tkhd->track_ID = mvhd->next_track_ID++; + tkhd->duration = vduration; + tkhd->width = (width << 16); + tkhd->height = (height << 16); + + SrsMp4MediaBox* mdia = new SrsMp4MediaBox(); + trak->set_mdia(mdia); + + SrsMp4MediaHeaderBox* mdhd = new SrsMp4MediaHeaderBox(); + mdia->set_mdhd(mdhd); + + mdhd->timescale = 1000; + mdhd->duration = vduration; + 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); + + SrsMp4VisualSampleEntry* avc1 = new SrsMp4VisualSampleEntry(); + stsd->append(avc1); + + avc1->width = width; + avc1->height = height; + + SrsMp4AvccBox* avcC = new SrsMp4AvccBox(); + avc1->set_avcC(avcC); + + avcC->nb_config = nb_avcc; + avcC->avc_config = new uint8_t[nb_avcc]; + memcpy(avcC->avc_config, pavcc, nb_avcc); + } + + if (nb_audios) { + SrsMp4TrackBox* trak = new SrsMp4TrackBox(); + moov->add_trak(trak); + + SrsMp4TrackHeaderBox* tkhd = new SrsMp4TrackHeaderBox(); + tkhd->volume = 0x0100; + trak->set_tkhd(tkhd); + + tkhd->track_ID = mvhd->next_track_ID++; + tkhd->duration = aduration; + + SrsMp4MediaBox* mdia = new SrsMp4MediaBox(); + trak->set_mdia(mdia); + + SrsMp4MediaHeaderBox* mdhd = new SrsMp4MediaHeaderBox(); + mdia->set_mdhd(mdhd); + + mdhd->timescale = 1000; + mdhd->duration = aduration; + 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->samplerate = uint32_t(flv_sample_rates[sample_rate]) << 16; + if (sound_bits == SrsCodecAudioSampleSize16bit) { + mp4a->samplesize = 16; + } else { + mp4a->samplesize = 8; + } + if (channels == SrsCodecAudioSoundTypeStereo) { + 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->nb_asc = nb_asc; + asc->asc = new uint8_t[nb_asc]; + memcpy(asc->asc, pasc, nb_asc); + } + + if ((ret = samples->write(moov)) != ERROR_SUCCESS) { + return ret; + } + + int nb_data = moov->nb_bytes(); + uint8_t* data = new uint8_t[nb_data]; + SrsAutoFreeA(uint8_t, data); + if ((ret = buffer->initialize((char*)data, nb_data)) != ERROR_SUCCESS) { + return ret; + } + + if ((ret = moov->encode(buffer)) != ERROR_SUCCESS) { + return ret; + } + + // TODO: FIXME: Ensure all bytes are writen. + if ((ret = wsio->write(data, nb_data, NULL)) != ERROR_SUCCESS) { + return ret; + } + } + + // Write mdat box. + if (true) { + // Update the mdat box header. + if ((ret = wsio->lseek(mdat_offset, SEEK_SET, NULL)) != ERROR_SUCCESS) { + return ret; + } + + // Write empty mdat box, + // its payload will be writen by samples, + // and we will update its header(size) when flush. + SrsMp4MediaDataBox* mdat = new SrsMp4MediaDataBox(); + SrsAutoFree(SrsMp4MediaDataBox, mdat); + + int nb_data = mdat->nb_bytes(); + uint8_t* data = new uint8_t[nb_data]; + SrsAutoFreeA(uint8_t, data); + if ((ret = buffer->initialize((char*)data, nb_data)) != ERROR_SUCCESS) { + return ret; + } + + // TODO: FIXME: Support 64bits size. + mdat->nb_data = (int)mdat_bytes; + if ((ret = mdat->encode(buffer)) != ERROR_SUCCESS) { + return ret; + } + + // TODO: FIXME: Ensure all bytes are writen. + if ((ret = wsio->write(data, nb_data, NULL)) != ERROR_SUCCESS) { + return ret; + } + } + + return ret; +} + +int SrsMp4Encoder::copy_sequence_header(bool vsh, uint8_t* sample, uint32_t nb_sample) +{ + int ret = ERROR_SUCCESS; + + if (vsh && pavcc) { + if (nb_sample == nb_avcc && srs_bytes_equals(sample, pavcc, nb_avcc)) { + return ret; + } + + ret = ERROR_MP4_AVCC_CHANGE; + srs_error("MP4 doesn't support avcc change. ret=%d", ret); + return ret; + } + + if (!vsh && pasc) { + if (nb_sample == nb_asc && srs_bytes_equals(sample, pasc, nb_asc)) { + return ret; + } + + ret = ERROR_MP4_ASC_CHANGE; + srs_error("MP4 doesn't support asc change. ret=%d", ret); + return ret; + } + + if (vsh) { + nb_avcc = nb_sample; + pavcc = new uint8_t[nb_avcc]; + memcpy(pavcc, sample, nb_sample); + + // TODO: FIXME: Parse the width and height. + } + + if (!vsh) { + nb_asc = nb_sample; + pasc = new uint8_t[nb_asc]; + memcpy(pasc, sample, nb_sample); + } + + return ret; +} + +int SrsMp4Encoder::do_write_sample(SrsMp4Sample* ps, uint8_t* sample, uint32_t nb_sample) +{ + int ret = ERROR_SUCCESS; + + ps->nb_data = nb_sample; + // Never copy data, for we already writen to writer. + ps->data = NULL; + + // Update the mdat box from this offset. + if ((ret = wsio->lseek(0, SEEK_CUR, &ps->offset)) != ERROR_SUCCESS) { + return ret; + } + + // TODO: FIXME: Ensure all bytes are writen. + if ((ret = wsio->write(sample, nb_sample, NULL)) != ERROR_SUCCESS) { + return ret; + } + + mdat_bytes += nb_sample; + + return ret; } diff --git a/trunk/src/kernel/srs_kernel_mp4.hpp b/trunk/src/kernel/srs_kernel_mp4.hpp index a61dabe26..0d65e3bfc 100644 --- a/trunk/src/kernel/srs_kernel_mp4.hpp +++ b/trunk/src/kernel/srs_kernel_mp4.hpp @@ -36,7 +36,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include -class ISrsWriter; +class ISrsWriteSeeker; class ISrsReadSeeker; class SrsMp4TrackBox; class SrsMp4MediaBox; @@ -59,6 +59,11 @@ class SrsMp4DecodingTime2SampleBox; class SrsMp4CompositionTime2SampleBox; class SrsMp4SyncSampleBox; class SrsMp4MediaHeaderBox; +class SrsMp4HandlerReferenceBox; +class SrsMp4VideoMeidaHeaderBox; +class SrsMp4DataInformationBox; +class SrsMp4DataReferenceBox; +class SrsMp4SoundMeidaHeaderBox; /** * 4.2 Object Structure @@ -173,6 +178,9 @@ public: // Get the contained box of specific type. // @return The first matched box. virtual SrsMp4Box* get(SrsMp4BoxType bt); + // Remove the contained box of specified type. + // @return The removed count. + virtual int remove(SrsMp4BoxType bt); /** * Discovery the box from buffer. * @param ppbox Output the discoveried box, which user must free it. @@ -196,6 +204,13 @@ protected: // It's not necessary to check the buffer, unless the box is not only determined by the verson. // Generally, it's not necessary, that is, all boxes is determinated by version. virtual int decode_header(SrsBuffer* buf); +protected: + // The actual size of this box, generally it must equal to nb_bytes, + // but for some special boxes, for instance mdat, the box encode actual size maybe large than + // the nb_bytes to write, because the data is written directly. + // That is, the actual size is used to encode the box size in header, + // while the nb_bytes is the bytes encoded the box. + virtual uint64_t encode_actual_size(); }; /** @@ -240,6 +255,8 @@ private: public: SrsMp4FileTypeBox(); virtual ~SrsMp4FileTypeBox(); +public: + virtual void set_compatible_brands(SrsMp4BoxBrand b0, SrsMp4BoxBrand b1, SrsMp4BoxBrand b2, SrsMp4BoxBrand b3); protected: virtual int nb_header(); virtual int encode_header(SrsBuffer* buf); @@ -256,17 +273,19 @@ protected: */ class SrsMp4MediaDataBox : public SrsMp4Box { -private: +public: // the contained media data + // TODO: FIXME: Support 64bits size. int nb_data; + // @remark User must alloc the data and codec it. uint8_t* data; public: SrsMp4MediaDataBox(); virtual ~SrsMp4MediaDataBox(); protected: - virtual int nb_header(); - virtual int encode_header(SrsBuffer* buf); - virtual int decode_header(SrsBuffer* buf); + virtual uint64_t encode_actual_size(); +public: + virtual int decode(SrsBuffer* buf); }; /** @@ -301,10 +320,13 @@ public: public: // Get the header of moov. virtual SrsMp4MovieHeaderBox* mvhd(); + virtual void set_mvhd(SrsMp4MovieHeaderBox* v); // Get the first video track. virtual SrsMp4TrackBox* video(); // Get the first audio track. virtual SrsMp4TrackBox* audio(); + // Add a new track. + virtual void add_trak(SrsMp4TrackBox* v); // Get the number of video tracks. virtual int nb_vide_tracks(); // Get the number of audio tracks. @@ -328,7 +350,7 @@ public: // an integer that declares the most recent time the presentation was modified (in // seconds since midnight, Jan. 1, 1904, in UTC time) uint64_t modification_time; -private: +public: // an integer that specifies the time-scale for the entire presentation; this is the number of // time units that pass in one second. For example, a time coordinate system that measures time in // sixtieths of a second has a time scale of 60. @@ -392,6 +414,7 @@ public: virtual SrsMp4TrackType track_type(); // Get the track header box. virtual SrsMp4TrackHeaderBox* tkhd(); + virtual void set_tkhd(SrsMp4TrackHeaderBox* v); public: // Get the chunk offset box. virtual SrsMp4ChunkOffsetBox* stco(); @@ -416,9 +439,10 @@ public: virtual SrsMp4AvccBox* avcc(); // For AAC codec, get the asc. virtual SrsMp4DecoderSpecificInfo* asc(); -private: +public: // Get the media box. virtual SrsMp4MediaBox* mdia(); + virtual void set_mdia(SrsMp4MediaBox* v); // Get the media info box. virtual SrsMp4MediaInformationBox* minf(); // Get the sample table box. @@ -568,8 +592,13 @@ public: virtual SrsMp4TrackType track_type(); // Get the media header box. virtual SrsMp4MediaHeaderBox* mdhd(); + virtual void set_mdhd(SrsMp4MediaHeaderBox* v); + // Get the hdlr box. + virtual SrsMp4HandlerReferenceBox* hdlr(); + virtual void set_hdlr(SrsMp4HandlerReferenceBox* v); // Get the media info box. virtual SrsMp4MediaInformationBox* minf(); + virtual void set_minf(SrsMp4MediaInformationBox* v); }; /** @@ -608,12 +637,15 @@ public: // the language code for this media. See ISO 639-2/T for the set of three character // codes. Each character is packed as the difference between its ASCII value and 0x60. Since the code // is confined to being three lower-case letters, these values are strictly positive. - virtual uint8_t language0(); - virtual void set_language0(uint8_t v); - virtual uint8_t language1(); - virtual void set_language1(uint8_t v); - virtual uint8_t language2(); - virtual void set_language2(uint8_t v); + // @param v The ASCII, for example, 'u'. + virtual char language0(); + virtual void set_language0(char v); + // @param v The ASCII, for example, 'n'. + virtual char language1(); + virtual void set_language1(char v); + // @param v The ASCII, for example, 'd'. + virtual char language2(); + virtual void set_language2(char v); protected: virtual int nb_header(); virtual int encode_header(SrsBuffer* buf); @@ -661,8 +693,18 @@ public: SrsMp4MediaInformationBox(); virtual ~SrsMp4MediaInformationBox(); public: + // Get the vmhd box. + virtual SrsMp4VideoMeidaHeaderBox* vmhd(); + virtual void set_vmhd(SrsMp4VideoMeidaHeaderBox* v); + // Get the smhd box. + virtual SrsMp4SoundMeidaHeaderBox* smhd(); + virtual void set_smhd(SrsMp4SoundMeidaHeaderBox* v); + // Get the dinf box. + virtual SrsMp4DataInformationBox* dinf(); + virtual void set_dinf(SrsMp4DataInformationBox* v); // Get the sample table box. virtual SrsMp4SampleTableBox* stbl(); + virtual void set_stbl(SrsMp4SampleTableBox* v); }; /** @@ -721,6 +763,10 @@ class SrsMp4DataInformationBox : public SrsMp4Box public: SrsMp4DataInformationBox(); virtual ~SrsMp4DataInformationBox(); +public: + // Get the dref box. + virtual SrsMp4DataReferenceBox* dref(); + virtual void set_dref(SrsMp4DataReferenceBox* v); }; /** @@ -787,6 +833,7 @@ public: public: virtual uint32_t entry_count(); virtual SrsMp4DataEntryBox* entry_at(int index); + virtual SrsMp4DataReferenceBox* append(SrsMp4DataEntryBox* v); protected: virtual int nb_header(); virtual int encode_header(SrsBuffer* buf); @@ -808,18 +855,29 @@ public: public: // Get the sample description box virtual SrsMp4SampleDescriptionBox* stsd(); + virtual void set_stsd(SrsMp4SampleDescriptionBox* v); // Get the chunk offset box. virtual SrsMp4ChunkOffsetBox* stco(); + virtual void set_stco(SrsMp4ChunkOffsetBox* v); // Get the sample size box. virtual SrsMp4SampleSizeBox* stsz(); + virtual void set_stsz(SrsMp4SampleSizeBox* v); // Get the sample to chunk box. virtual SrsMp4Sample2ChunkBox* stsc(); + virtual void set_stsc(SrsMp4Sample2ChunkBox* v); // Get the dts box. virtual SrsMp4DecodingTime2SampleBox* stts(); + virtual void set_stts(SrsMp4DecodingTime2SampleBox* v); // Get the cts/pts box. virtual SrsMp4CompositionTime2SampleBox* ctts(); + virtual void set_ctts(SrsMp4CompositionTime2SampleBox* v); // Get the sync dts box. virtual SrsMp4SyncSampleBox* stss(); + virtual void set_stss(SrsMp4SyncSampleBox* v); +protected: + virtual int nb_header(); + virtual int encode_header(SrsBuffer* buf); + virtual int decode_header(SrsBuffer* buf); }; /** @@ -877,6 +935,7 @@ public: public: // For avc1, get the avcc box. virtual SrsMp4AvccBox* avcC(); + virtual void set_avcC(SrsMp4AvccBox* v); protected: virtual int nb_header(); virtual int encode_header(SrsBuffer* buf); @@ -920,6 +979,7 @@ public: public: // For AAC codec, get the esds. virtual SrsMp4EsdsBox* esds(); + virtual void set_esds(SrsMp4EsdsBox* v); // For AAC codec, get the asc. virtual SrsMp4DecoderSpecificInfo* asc(); protected: @@ -1020,7 +1080,7 @@ class SrsMp4DecoderConfigDescriptor : public SrsMp4BaseDescriptor public: // an indication of the object or scene description type that needs to be supported // by the decoder for this elementary stream as per Table 5. - SrsMp4ObjectType objectTypeIndication; + SrsMp4ObjectType objectTypeIndication; // bit(8) SrsMp4StreamType streamType; // bit(6) uint8_t upStream; // bit(1) uint8_t reserved; // bit(1) @@ -1127,6 +1187,7 @@ public: public: virtual uint32_t entry_count(); virtual SrsMp4SampleEntry* entrie_at(int index); + virtual SrsMp4SampleDescriptionBox* append(SrsMp4SampleEntry* v); protected: virtual int nb_header(); virtual int encode_header(SrsBuffer* buf); @@ -1417,7 +1478,7 @@ public: // The type of sample, audio or video. SrsCodecFlvTag type; // The offset of sample in file. - uint64_t offset; + off_t offset; // The index of sample with a track, start from 0. uint32_t index; // The dts in tbn. @@ -1463,17 +1524,19 @@ public: SrsMp4SampleManager(); virtual ~SrsMp4SampleManager(); public: - /** - * Load the samples from moov. - * There must be atleast one track. - */ + // Load the samples from moov. There must be atleast one track. virtual int load(SrsMp4MovieBox* moov); - /** - * Get the sample at index position. - * @remark NULL if exceed the max index. - */ + // Get the sample at index position. + // @remark NULL if exceed the max index. virtual SrsMp4Sample* at(uint32_t index); + // Append the sample to the tail of manager. + virtual void append(SrsMp4Sample* sample); + // Write the samples info to moov. + virtual int write(SrsMp4MovieBox* moov); private: + virtual int write_track(SrsCodecFlvTag track, + SrsMp4DecodingTime2SampleBox* stts, SrsMp4SyncSampleBox* stss, SrsMp4CompositionTime2SampleBox* ctts, + SrsMp4Sample2ChunkBox* stsc, SrsMp4SampleSizeBox* stsz, SrsMp4ChunkOffsetBox* stco); virtual int do_load(std::map& tses, SrsMp4MovieBox* moov); private: // Load the samples of track from stco, stsz and stsc. @@ -1544,7 +1607,7 @@ public: virtual int initialize(ISrsReadSeeker* rs); /** * Read a sample from mp4. - * @param pht The sample type, audio/soun or video/vide. + * @param pht The sample hanler type, audio/soun or video/vide. * @param pft, The frame type. For video, it's SrsCodecVideoAVCFrame. * @param pct, The codec type. For video, it's SrsCodecVideoAVCType. For audio, it's SrsCodecAudioType. * @param pdts The output dts in milliseconds. @@ -1573,16 +1636,69 @@ private: class SrsMp4Encoder { private: - ISrsWriter* writer; + ISrsWriteSeeker* wsio; + SrsBuffer* buffer; + // The mdat offset at file, we must update the header when flush. + off_t mdat_offset; + // The mdat size in bytes, we must update it to the mdat box header. + uint64_t mdat_bytes; + // The samples build from moov. + SrsMp4SampleManager* samples; +public: + // The audio codec of first track, generally there is zero or one track. + // Forbidden if no audio stream. + SrsCodecAudio acodec; + // The audio sample rate. + SrsCodecAudioSampleRate sample_rate; + // The audio sound bits. + SrsCodecAudioSampleSize sound_bits; + // The audio sound type. + SrsCodecAudioSoundType channels; +private: + // For AAC, the asc in esds box. + int nb_asc; + uint8_t* pasc; + // The number of audio samples. + int nb_audios; + // The duration of audio stream. + uint64_t aduration; +public: + // The video codec of first track, generally there is zero or one track. + // Forbidden if no video stream. + SrsCodecVideo vcodec; +private: + // For H.264/AVC, the avcc contains the sps/pps. + int nb_avcc; + uint8_t* pavcc; + // The number of video samples. + int nb_videos; + // The duration of video stream. + uint64_t vduration; + // The size width/height of video. + uint32_t width; + uint32_t height; public: SrsMp4Encoder(); virtual ~SrsMp4Encoder(); public: - /** - * Initialize the encoder with a writer w. - * @param w The underlayer io writer, user must manage it. - */ - virtual int initialize(ISrsWriter* w); + // Initialize the encoder with a writer w. + // @param w The underlayer io writer, user must manage it. + virtual int initialize(ISrsWriteSeeker* ws); + // Write a sampel to mp4. + // @param ht, The sample handler type, audio/soun or video/vide. + // @param ft, The frame type. For video, it's SrsCodecVideoAVCFrame. + // @param ct, The codec type. For video, it's SrsCodecVideoAVCType. For audio, it's SrsCodecAudioType. + // @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. + virtual int write_sample(SrsMp4HandlerType ht, uint16_t ft, uint16_t ct, + uint32_t dts, uint32_t pts, uint8_t* sample, uint32_t nb_sample); + // Flush the encoder, to write the moov. + virtual int flush(); +private: + virtual int copy_sequence_header(bool vsh, uint8_t* sample, uint32_t nb_sample); + virtual int do_write_sample(SrsMp4Sample* ps, uint8_t* sample, uint32_t nb_sample); }; #endif diff --git a/trunk/src/kernel/srs_kernel_utility.cpp b/trunk/src/kernel/srs_kernel_utility.cpp index 9c554f6ed..80efdd0fc 100644 --- a/trunk/src/kernel/srs_kernel_utility.cpp +++ b/trunk/src/kernel/srs_kernel_utility.cpp @@ -320,6 +320,21 @@ bool srs_string_ends_with(string str, string flag) return str.rfind(flag) == str.length() - flag.length(); } +bool srs_string_ends_with(string str, string flag0, string flag1) +{ + return srs_string_ends_with(str, flag0) || srs_string_ends_with(str, flag1); +} + +bool srs_string_ends_with(string str, string flag0, string flag1, string flag2) +{ + return srs_string_ends_with(str, flag0) || srs_string_ends_with(str, flag1) || srs_string_ends_with(str, flag2); +} + +bool srs_string_ends_with(string str, string flag0, string flag1, string flag2, string flag3) +{ + return srs_string_ends_with(str, flag0) || srs_string_ends_with(str, flag1) || srs_string_ends_with(str, flag2) || srs_string_ends_with(str, flag3); +} + bool srs_string_starts_with(string str, string flag) { return str.find(flag) == 0; @@ -471,6 +486,28 @@ int srs_do_create_dir_recursively(string dir) return ret; } +bool srs_bytes_equals(void* pa, void* pb, int size) +{ + uint8_t* a = (uint8_t*)pa; + uint8_t* b = (uint8_t*)pb; + + if (!a && !b) { + return true; + } + + if (!a || !b) { + return false; + } + + for(int i = 0; i < size; i++){ + if(a[i] != b[i]){ + return false; + } + } + + return true; +} + int srs_create_dir_recursively(string dir) { int ret = ERROR_SUCCESS; diff --git a/trunk/src/kernel/srs_kernel_utility.hpp b/trunk/src/kernel/srs_kernel_utility.hpp index 17ba9d01f..ea3555634 100644 --- a/trunk/src/kernel/srs_kernel_utility.hpp +++ b/trunk/src/kernel/srs_kernel_utility.hpp @@ -81,6 +81,9 @@ extern std::string srs_string_trim_start(std::string str, std::string trim_chars extern std::string srs_string_remove(std::string str, std::string remove_chars); // whether string end with extern bool srs_string_ends_with(std::string str, std::string flag); +extern bool srs_string_ends_with(std::string str, std::string flag0, std::string flag1); +extern bool srs_string_ends_with(std::string str, std::string flag0, std::string flag1, std::string flag2); +extern bool srs_string_ends_with(std::string str, std::string flag0, std::string flag1, std::string flag2, std::string flag3); // whether string starts with extern bool srs_string_starts_with(std::string str, std::string flag); extern bool srs_string_starts_with(std::string str, std::string flag0, std::string flag1); @@ -96,6 +99,12 @@ extern std::string srs_string_min_match(std::string str, std::vector srs_string_split(std::string str, std::string flag); extern std::vector srs_string_split(std::string str, std::vector flags); +/** + * compare the memory in bytes. + * @return true if completely equal; otherwise, false. + */ +extern bool srs_bytes_equals(void* pa, void* pb, int size); + // create dir recursively extern int srs_create_dir_recursively(std::string dir); diff --git a/trunk/src/protocol/srs_protocol_utility.cpp b/trunk/src/protocol/srs_protocol_utility.cpp index 01c8c56b1..e2d4c6997 100644 --- a/trunk/src/protocol/srs_protocol_utility.cpp +++ b/trunk/src/protocol/srs_protocol_utility.cpp @@ -192,31 +192,6 @@ string srs_generate_vis_tc_url(string ip, string vhost, string app, int port) return "rtmp://" + ip + ":" + srs_int2str(port) + "/" + app; } -/** -* compare the memory in bytes. -*/ -bool srs_bytes_equals(void* pa, void* pb, int size) -{ - uint8_t* a = (uint8_t*)pa; - uint8_t* b = (uint8_t*)pb; - - if (!a && !b) { - return true; - } - - if (!a || !b) { - return false; - } - - for(int i = 0; i < size; i++){ - if(a[i] != b[i]){ - return false; - } - } - - return true; -} - template int srs_do_rtmp_create_msg(char type, uint32_t timestamp, char* data, int size, int stream_id, T** ppmsg) { diff --git a/trunk/src/protocol/srs_protocol_utility.hpp b/trunk/src/protocol/srs_protocol_utility.hpp index 46025dad1..912da9e64 100644 --- a/trunk/src/protocol/srs_protocol_utility.hpp +++ b/trunk/src/protocol/srs_protocol_utility.hpp @@ -89,25 +89,19 @@ extern std::string srs_generate_tc_url( * srs_detect_tools generate the normal tcUrl */ extern std::string srs_generate_normal_tc_url( - std::string ip, std::string vhost, std::string app, int port); + std::string ip, std::string vhost, std::string app, int port); /** * srs_detect_tools generate the normal tcUrl */ extern std::string srs_generate_via_tc_url( - std::string ip, std::string vhost, std::string app, int port); + std::string ip, std::string vhost, std::string app, int port); /** * srs_detect_tools generate the vis/vis2 tcUrl */ extern std::string srs_generate_vis_tc_url( - std::string ip, std::string vhost, std::string app, int port); - -/** -* compare the memory in bytes. -* @return true if completely equal; otherwise, false. -*/ -extern bool srs_bytes_equals(void* pa, void* pb, int size); + std::string ip, std::string vhost, std::string app, int port); /** * create shared ptr message from bytes. diff --git a/trunk/src/protocol/srs_rtmp_handshake.cpp b/trunk/src/protocol/srs_rtmp_handshake.cpp index cb6922b2d..bb0a81bf5 100644 --- a/trunk/src/protocol/srs_rtmp_handshake.cpp +++ b/trunk/src/protocol/srs_rtmp_handshake.cpp @@ -32,6 +32,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include #include #include +#include #ifdef SRS_AUTO_SSL