diff --git a/trunk/research/librtmp/srs_ingest_mp4.c b/trunk/research/librtmp/srs_ingest_mp4.c index 6677b9cd7f..fa9f6fc681 100644 --- a/trunk/research/librtmp/srs_ingest_mp4.c +++ b/trunk/research/librtmp/srs_ingest_mp4.c @@ -236,6 +236,7 @@ int proxy(srs_mp4_t mp4, srs_rtmp_t ortmp) int ret = 0; if ((ret = srs_mp4_init_demuxer(mp4)) != 0) { + srs_human_trace("init demuxer failed. ret=%d", ret); return ret; } if ((ret = connect_oc(ortmp)) != 0) { diff --git a/trunk/src/kernel/srs_kernel_error.hpp b/trunk/src/kernel/srs_kernel_error.hpp index 7baefa53b3..a423681b5e 100644 --- a/trunk/src/kernel/srs_kernel_error.hpp +++ b/trunk/src/kernel/srs_kernel_error.hpp @@ -247,6 +247,8 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #define ERROR_MP4_BOX_ILLEGAL_TYPE 3071 #define ERROR_MP4_BOX_ILLEGAL_SCHEMA 3072 #define ERROR_MP4_BOX_STRING 3073 +#define ERROR_MP4_BOX_ILLEGAL_BRAND 3074 +#define ERROR_MP4_NOT_NON_SEEKABLE 3075 /////////////////////////////////////////////////////// // HTTP/StreamCaster/KAFKA protocol error. diff --git a/trunk/src/kernel/srs_kernel_mp4.cpp b/trunk/src/kernel/srs_kernel_mp4.cpp index 2b82c77d94..54bbc58b84 100644 --- a/trunk/src/kernel/srs_kernel_mp4.cpp +++ b/trunk/src/kernel/srs_kernel_mp4.cpp @@ -64,6 +64,18 @@ using namespace std; #define SRS_MP4_BOX_CO64 0x636f3634 // 'co64' #define SRS_MP4_BOX_STSZ 0x7374737a // 'stsz' #define SRS_MP4_BOX_STZ2 0x73747a32 // 'stz2' +#define SRS_MP4_BOX_AVC1 0x61766331 // 'avc1' +#define SRS_MP4_BOX_AVCC 0x61766343 // 'avcC' +#define SRS_MP4_BOX_MP4A 0x6d703461 // 'mp4a' +#define SRS_MP4_BOX_ESDS 0x65736473 // 'esds' + +#define SRS_MP4_BRAND_ISOM 0x69736f6d // 'isom' +#define SRS_MP4_BRAND_ISO2 0x69736f32 // 'iso2' +#define SRS_MP4_BRAND_AVC1 0x61766331 // 'avc1' +#define SRS_MP4_BRAND_MP41 0x6d703431 // 'mp41' + +#define SRS_MP4_HANDLER_VIDE 0x76696465 // 'vide' +#define SRS_MP4_HANDLER_SOUN 0x736f756e // 'soun' #define SRS_MP4_EOF_SIZE 0 #define SRS_MP4_USE_LARGE_SIZE 1 @@ -75,9 +87,12 @@ int srs_mp4_string_length(const string& v) void srs_mp4_string_write(SrsBuffer* buf, const string& v) { - if (!v.empty()) { - buf->write_bytes((char*)v.data(), (int)v.length()); + // Nothing for empty string. + if (v.empty()) { + return; } + + buf->write_bytes((char*)v.data(), (int)v.length()); buf->write_1bytes(0x00); } @@ -85,19 +100,22 @@ int srs_mp4_string_read(SrsBuffer* buf, string& v, int left) { int ret = ERROR_SUCCESS; - char* p = buf->data() + buf->pos(); + if (left == 0) { + return ret; + } - char* start = p; - while (p < start + left) { - if (*p == 0x00) { - v.append(start, p - start); - buf->skip((int)(p - start)); - return ret; - } + char* start = buf->data() + buf->pos(); + size_t len = strnlen(start, left); + + if (len == left) { + ret = ERROR_MP4_BOX_STRING; + srs_error("MP4 string corrupt, left=%d. ret=%d", left, ret); + return ret; } - ret = ERROR_MP4_BOX_STRING; - srs_error("MP4 string corrupt, left=%d. ret=%d", left, ret); + v.append(start, len); + buf->skip((int)len + 1); + return ret; } @@ -132,6 +150,21 @@ int SrsMp4Box::left_space(SrsBuffer* buf) return (int)sz() - (buf->pos() - start_pos); } +bool SrsMp4Box::is_ftyp() +{ + return type == SRS_MP4_BOX_FTYP; +} + +bool SrsMp4Box::is_moov() +{ + return type == SRS_MP4_BOX_MOOV; +} + +bool SrsMp4Box::is_mdat() +{ + return type == SRS_MP4_BOX_MDAT; +} + int SrsMp4Box::discovery(SrsBuffer* buf, SrsMp4Box** ppbox) { *ppbox = NULL; @@ -196,6 +229,9 @@ int SrsMp4Box::discovery(SrsBuffer* buf, SrsMp4Box** ppbox) case SRS_MP4_BOX_STCO: box = new SrsMp4ChunkOffsetBox(); break; case SRS_MP4_BOX_CO64: box = new SrsMp4ChunkLargeOffsetBox(); break; case SRS_MP4_BOX_STSZ: box = new SrsMp4SampleSizeBox(); break; + case SRS_MP4_BOX_AVC1: box = new SrsMp4VisualSampleEntry(); break; + case SRS_MP4_BOX_AVCC: box = new SrsMp4AvccBox(); break; + case SRS_MP4_BOX_MP4A: box = new SrsMp4AudioSampleEntry(); break; default: ret = ERROR_MP4_BOX_ILLEGAL_TYPE; srs_error("MP4 illegal box type=%d. ret=%d", type, ret); @@ -722,7 +758,7 @@ int SrsMp4MovieHeaderBox::decode_header(SrsBuffer* buf) } rate = buf->read_4bytes(); - volume = buf->read_4bytes(); + volume = buf->read_2bytes(); buf->skip(2); buf->skip(8); for (int i = 0; i < 9; i++) { @@ -862,53 +898,6 @@ SrsMp4ElstEntry::SrsMp4ElstEntry() media_rate_fraction = 0; } -int SrsMp4ElstEntry::nb_header(uint32_t version) -{ - int size = 0; - - if (version == 1) { - size += 8+8; - } else { - size += 4+4; - } - - size += 2+2; - - return size; -} - -int SrsMp4ElstEntry::encode_header(SrsBuffer* buf, uint32_t version) -{ - if (version == 1) { - buf->write_8bytes(segment_duration); - buf->write_8bytes(media_time); - } else { - buf->write_4bytes((uint32_t)segment_duration); - buf->write_4bytes((int32_t)media_time); - } - - buf->write_2bytes(media_rate_integer); - buf->write_2bytes(media_rate_fraction); - - return ERROR_SUCCESS; -} - -int SrsMp4ElstEntry::decode_header(SrsBuffer* buf, uint32_t version) -{ - if (version == 1) { - segment_duration = buf->read_8bytes(); - media_time = buf->read_8bytes(); - } else { - segment_duration = buf->read_4bytes(); - media_time = buf->read_4bytes(); - } - - media_rate_integer = buf->read_2bytes(); - media_rate_fraction = buf->read_2bytes(); - - return ERROR_SUCCESS; -} - SrsMp4EditListBox::SrsMp4EditListBox() { type = SRS_MP4_BOX_ELST; @@ -924,11 +913,12 @@ SrsMp4EditListBox::~SrsMp4EditListBox() int SrsMp4EditListBox::nb_header() { - int size = SrsMp4FullBox::nb_header(); + int size = SrsMp4FullBox::nb_header() + 4; - for (uint32_t i = 0; i < entry_count; i++) { - SrsMp4ElstEntry& entry = entries[i]; - size += entry.nb_header(version); + if (version == 1) { + size += entry_count * (2+2+8+8); + } else { + size += entry_count * (2+2+4+4); } return size; @@ -942,11 +932,20 @@ int SrsMp4EditListBox::encode_header(SrsBuffer* buf) return ret; } + buf->write_4bytes(entry_count); for (uint32_t i = 0; i < entry_count; i++) { SrsMp4ElstEntry& entry = entries[i]; - if ((ret = entry.encode_header(buf, version)) != ERROR_SUCCESS) { - return ret; + + if (version == 1) { + buf->write_8bytes(entry.segment_duration); + buf->write_8bytes(entry.media_time); + } else { + buf->write_4bytes((uint32_t)entry.segment_duration); + buf->write_4bytes((int32_t)entry.media_time); } + + buf->write_2bytes(entry.media_rate_integer); + buf->write_2bytes(entry.media_rate_fraction); } return ret; @@ -960,16 +959,23 @@ int SrsMp4EditListBox::decode_header(SrsBuffer* buf) return ret; } - int left = left_space(buf); - entry_count = left / SrsMp4ElstEntry().nb_header(version); + entry_count = buf->read_4bytes(); if (entry_count > 0) { entries = new SrsMp4ElstEntry[entry_count]; } for (int i = 0; i < entry_count; i++) { SrsMp4ElstEntry& entry = entries[i]; - if ((ret = entry.decode_header(buf, version)) != ERROR_SUCCESS) { - return ret; + + if (version == 1) { + entry.segment_duration = buf->read_8bytes(); + entry.media_time = buf->read_8bytes(); + } else { + entry.segment_duration = buf->read_4bytes(); + entry.media_time = buf->read_4bytes(); } + + entry.media_rate_integer = buf->read_2bytes(); + entry.media_rate_fraction = buf->read_2bytes(); } return ret; @@ -1104,6 +1110,16 @@ SrsMp4HandlerReferenceBox::~SrsMp4HandlerReferenceBox() { } +bool SrsMp4HandlerReferenceBox::is_video() +{ + return handler_type == SRS_MP4_HANDLER_VIDE; +} + +bool SrsMp4HandlerReferenceBox::is_audio() +{ + return handler_type == SRS_MP4_HANDLER_SOUN; +} + int SrsMp4HandlerReferenceBox::nb_header() { return SrsMp4FullBox::nb_header()+4+4+12+srs_mp4_string_length(name); @@ -1268,12 +1284,26 @@ SrsMp4DataEntryBox::~SrsMp4DataEntryBox() { } -int SrsMp4DataEntryBox::nb_header() +SrsMp4DataEntryUrlBox::SrsMp4DataEntryUrlBox() { + type = SRS_MP4_BOX_URL; +} + +SrsMp4DataEntryUrlBox::~SrsMp4DataEntryUrlBox() +{ +} + +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) { + return SrsMp4FullBox::nb_header(); + } return SrsMp4FullBox::nb_header()+srs_mp4_string_length(location); } -int SrsMp4DataEntryBox::encode_header(SrsBuffer* buf) +int SrsMp4DataEntryUrlBox::encode_header(SrsBuffer* buf) { int ret = ERROR_SUCCESS; @@ -1281,12 +1311,19 @@ int SrsMp4DataEntryBox::encode_header(SrsBuffer* buf) 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; + return ret; + } + srs_mp4_string_write(buf, location); return ret; } -int SrsMp4DataEntryBox::decode_header(SrsBuffer* buf) +int SrsMp4DataEntryUrlBox::decode_header(SrsBuffer* buf) { int ret = ERROR_SUCCESS; @@ -1294,23 +1331,20 @@ int SrsMp4DataEntryBox::decode_header(SrsBuffer* buf) 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 (flags == 0x01) { + return ret; + } + if ((ret = srs_mp4_string_read(buf, location, left_space(buf))) != ERROR_SUCCESS) { - srs_error("MP4 urx read string failed. ret=%d", ret); + srs_error("MP4 url read location failed. ret=%d", ret); return ret; } return ret; } -SrsMp4DataEntryUrlBox::SrsMp4DataEntryUrlBox() -{ - type = SRS_MP4_BOX_URL; -} - -SrsMp4DataEntryUrlBox::~SrsMp4DataEntryUrlBox() -{ -} - SrsMp4DataEntryUrnBox::SrsMp4DataEntryUrnBox() { type = SRS_MP4_BOX_URN; @@ -1322,7 +1356,7 @@ SrsMp4DataEntryUrnBox::~SrsMp4DataEntryUrnBox() int SrsMp4DataEntryUrnBox::nb_header() { - return SrsMp4DataEntryBox::nb_header()+srs_mp4_string_length(name); + return SrsMp4FullBox::nb_header()+srs_mp4_string_length(location)+srs_mp4_string_length(name); } int SrsMp4DataEntryUrnBox::encode_header(SrsBuffer* buf) @@ -1333,6 +1367,7 @@ int SrsMp4DataEntryUrnBox::encode_header(SrsBuffer* buf) return ret; } + srs_mp4_string_write(buf, location); srs_mp4_string_write(buf, name); return ret; @@ -1346,8 +1381,13 @@ int SrsMp4DataEntryUrnBox::decode_header(SrsBuffer* buf) return ret; } + if ((ret = srs_mp4_string_read(buf, location, left_space(buf))) != ERROR_SUCCESS) { + srs_error("MP4 urn read location failed. ret=%d", ret); + return ret; + } + if ((ret = srs_mp4_string_read(buf, name, left_space(buf))) != ERROR_SUCCESS) { - srs_error("MP4 urn read string failed. ret=%d", ret); + srs_error("MP4 urn read name failed. ret=%d", ret); return ret; } @@ -1578,6 +1618,55 @@ int SrsMp4VisualSampleEntry::decode_header(SrsBuffer* buf) return ret; } +SrsMp4AvccBox::SrsMp4AvccBox() +{ + type = SRS_MP4_BOX_AVCC; + nb_config = 0; + avc_config = NULL; +} + +SrsMp4AvccBox::~SrsMp4AvccBox() +{ + srs_freepa(avc_config); +} + +int SrsMp4AvccBox::nb_header() +{ + return SrsMp4Box::nb_header()+nb_config; +} + +int SrsMp4AvccBox::encode_header(SrsBuffer* buf) +{ + int ret = ERROR_SUCCESS; + + if ((ret = SrsMp4Box::encode_header(buf)) != ERROR_SUCCESS) { + return ret; + } + + if (nb_config) { + buf->write_bytes((char*)avc_config, nb_config); + } + + return ret; +} + +int SrsMp4AvccBox::decode_header(SrsBuffer* buf) +{ + int ret = ERROR_SUCCESS; + + if ((ret = SrsMp4Box::decode_header(buf)) != ERROR_SUCCESS) { + return ret; + } + + nb_config = left_space(buf); + if (nb_config) { + avc_config = new uint8_t[nb_config]; + buf->read_bytes((char*)avc_config, nb_config); + } + + return ret; +} + SrsMp4AudioSampleEntry::SrsMp4AudioSampleEntry() { reserved0 = 0; @@ -1905,8 +1994,7 @@ int SrsMp4SyncSampleBox::decode_header(SrsBuffer* buf) sample_numbers = new uint32_t[entry_count]; } for (uint32_t i = 0; i < entry_count; i++) { - uint32_t sample_number = sample_numbers[i]; - buf->write_4bytes(sample_number); + sample_numbers[i] = buf->read_4bytes(); } return ret; @@ -2143,16 +2231,18 @@ int SrsMp4SampleSizeBox::decode_header(SrsBuffer* buf) return ret; } +#define SRS_MP4_BUF_SIZE 4096 + SrsMp4Decoder::SrsMp4Decoder() { reader = NULL; - next = NULL; + buf = new char[SRS_MP4_BUF_SIZE]; stream = new SrsSimpleStream(); } SrsMp4Decoder::~SrsMp4Decoder() { - srs_freep(next); + srs_freepa(buf); srs_freep(stream); } @@ -2163,36 +2253,90 @@ int SrsMp4Decoder::initialize(ISrsReader* r) srs_assert(r); reader = r; - if ((ret = load_next_box(&next)) != ERROR_SUCCESS) { - return ret; + // File Type Box (ftyp) + if (true) { + SrsMp4Box* box = NULL; + SrsAutoFree(SrsMp4Box, box); + + if ((ret = load_next_box(&box, SRS_MP4_BOX_FTYP)) != ERROR_SUCCESS) { + return ret; + } + SrsMp4FileTypeBox* ftyp = dynamic_cast(box); + + bool legal_brand = false; + static uint32_t legal_brands[] = { + SRS_MP4_BRAND_ISOM, SRS_MP4_BRAND_ISO2, SRS_MP4_BRAND_AVC1, SRS_MP4_BRAND_MP41 + }; + for (int i = 0; i < sizeof(legal_brands)/sizeof(uint32_t); i++) { + if (ftyp->major_brand == legal_brands[i]) { + legal_brand = true; + break; + } + } + if (!legal_brand) { + ret = ERROR_MP4_BOX_ILLEGAL_BRAND; + srs_error("MP4 brand is illegal, brand=%d. ret=%d", ftyp->major_brand, ret); + return ret; + } + } + + // Media Data Box (mdat) or Movie Box (moov) + SrsMp4Box* box = NULL; + SrsAutoFree(SrsMp4Box, box); + while (true) { + if ((ret = load_next_box(&box, 0)) != ERROR_SUCCESS) { + return ret; + } + + if (!box->is_mdat() && !box->is_moov()) { + srs_freep(box); + continue; + } + break; } - if (next->type != SRS_MP4_BOX_FTYP) { - ret = ERROR_MP4_BOX_ILLEGAL_SCHEMA; - srs_error("MP4 first box must be FTYP, not %d. ret=%d", next->type, ret); + // Only support non-seek mp4, that is, mdat should never before moov. + // @see https://github.com/ossrs/srs/issues/738#issuecomment-276343669 + if (box->is_mdat()) { + ret = ERROR_MP4_NOT_NON_SEEKABLE; + srs_error("MP4 is not non-seekable. ret=%d", ret); return ret; } return ret; } -int SrsMp4Decoder::load_next_box(SrsMp4Box** ppbox) +int SrsMp4Decoder::load_next_box(SrsMp4Box** ppbox, uint32_t required_box_type) { int ret = ERROR_SUCCESS; - // Ignore for already loaded. - if (next) { - return ret; + while (true) { + SrsMp4Box* box = NULL; + if ((ret = do_load_next_box(&box, required_box_type)) != ERROR_SUCCESS) { + srs_freep(box); + return ret; + } + + if (!required_box_type || box->type == required_box_type) { + *ppbox = box; + break; + } + srs_freep(box); } - char* buf = new char[4096]; - SrsAutoFreeA(char, buf); + return ret; +} + +int SrsMp4Decoder::do_load_next_box(SrsMp4Box** ppbox, uint32_t required_box_type) +{ + int ret = ERROR_SUCCESS; + SrsMp4Box* box = NULL; while (true) { - uint64_t required = next? next->sz():4; + uint64_t required = box? box->sz():4; while (stream->length() < required) { ssize_t nread; - if ((ret = reader->read(buf, 4096, &nread)) != ERROR_SUCCESS) { + if ((ret = reader->read(buf, SRS_MP4_BUF_SIZE, &nread)) != ERROR_SUCCESS) { srs_error("MP4 load failed, nread=%d, required=%d. ret=%d", nread, required, ret); return ret; } @@ -2205,7 +2349,7 @@ int SrsMp4Decoder::load_next_box(SrsMp4Box** ppbox) SrsAutoFree(SrsBuffer, buffer); // Discovery the box with basic header. - if (!next && (ret = SrsMp4Box::discovery(buffer, ppbox)) != ERROR_SUCCESS) { + if (!box && (ret = SrsMp4Box::discovery(buffer, &box)) != ERROR_SUCCESS) { if (ret == ERROR_MP4_BOX_REQUIRE_SPACE) { continue; } @@ -2214,13 +2358,23 @@ int SrsMp4Decoder::load_next_box(SrsMp4Box** ppbox) } // Decode util we can demux the whole box. - if (!buffer->require((int)next->sz())) { + if (!buffer->require((int)box->sz())) { continue; } - ret = next->decode(buffer); + + if (!required_box_type || box->type == required_box_type) { + ret = box->decode(buffer); + } // Remove the consumed bytes. - stream->erase((int)next->sz()); + stream->erase((int)box->sz()); + + if (ret != ERROR_SUCCESS) { + srs_freep(box); + } else { + *ppbox = box; + } + break; } diff --git a/trunk/src/kernel/srs_kernel_mp4.hpp b/trunk/src/kernel/srs_kernel_mp4.hpp index cb51140b0a..03c3c50b90 100644 --- a/trunk/src/kernel/srs_kernel_mp4.hpp +++ b/trunk/src/kernel/srs_kernel_mp4.hpp @@ -72,6 +72,10 @@ class SrsMp4Box : public ISrsCodec virtual uint64_t sz(); // Get the left space of box, for decoder. virtual int left_space(SrsBuffer* buf); + // Box type helper. + virtual bool is_ftyp(); + virtual bool is_moov(); + virtual bool is_mdat(); /** * Discovery the box from buffer. * @param ppbox Output the discoveried box, which user must free it. @@ -353,10 +357,6 @@ struct SrsMp4ElstEntry int16_t media_rate_fraction; public: SrsMp4ElstEntry(); -public: - virtual int nb_header(uint32_t version); - virtual int encode_header(SrsBuffer* buf, uint32_t version); - virtual int decode_header(SrsBuffer* buf, uint32_t version); }; /** @@ -463,6 +463,9 @@ class SrsMp4HandlerReferenceBox : public SrsMp4FullBox public: SrsMp4HandlerReferenceBox(); virtual ~SrsMp4HandlerReferenceBox(); +public: + virtual bool is_video(); + virtual bool is_audio(); protected: virtual int nb_header(); virtual int encode_header(SrsBuffer* buf); @@ -542,6 +545,8 @@ class SrsMp4DataInformationBox : public SrsMp4Box /** * 8.7.2 Data Reference Box * ISO_IEC_14496-12-base-format-2012.pdf, page 56 + * 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. */ class SrsMp4DataEntryBox : public SrsMp4FullBox { @@ -550,10 +555,6 @@ class SrsMp4DataEntryBox : public SrsMp4FullBox public: SrsMp4DataEntryBox(); virtual ~SrsMp4DataEntryBox(); -protected: - virtual int nb_header(); - virtual int encode_header(SrsBuffer* buf); - virtual int decode_header(SrsBuffer* buf); }; /** @@ -565,6 +566,10 @@ class SrsMp4DataEntryUrlBox : public SrsMp4DataEntryBox public: SrsMp4DataEntryUrlBox(); virtual ~SrsMp4DataEntryUrlBox(); +protected: + virtual int nb_header(); + virtual int encode_header(SrsBuffer* buf); + virtual int decode_header(SrsBuffer* buf); }; /** @@ -679,6 +684,24 @@ class SrsMp4VisualSampleEntry : public SrsMp4SampleEntry virtual int decode_header(SrsBuffer* buf); }; +/** + * 5.3.4 AVC Video Stream Definition (avcC) + * ISO_IEC_14496-15-AVC-format-2012.pdf, page 19 + */ +class SrsMp4AvccBox : public SrsMp4Box +{ +public: + int nb_config; + uint8_t* avc_config; +public: + SrsMp4AvccBox(); + virtual ~SrsMp4AvccBox(); +protected: + virtual int nb_header(); + virtual int encode_header(SrsBuffer* buf); + virtual int decode_header(SrsBuffer* buf); +}; + /** * 8.5.2 Sample Description Box (mp4a) * ISO_IEC_14496-12-base-format-2012.pdf, page 45 @@ -957,8 +980,8 @@ class SrsMp4Decoder // The stream used to demux the boxes. // TODO: FIXME: refine for performance issue. SrsSimpleStream* stream; - // Always load next box. - SrsMp4Box* next; + // The temporary buffer to read from buffer. + char* buf; public: SrsMp4Decoder(); virtual ~SrsMp4Decoder(); @@ -970,7 +993,10 @@ class SrsMp4Decoder */ virtual int initialize(ISrsReader* r); private: - virtual int load_next_box(SrsMp4Box** ppbox); + // Load the next box from reader. + // @param required_box_type The box type required, 0 for any box. + virtual int load_next_box(SrsMp4Box** ppbox, uint32_t required_box_type); + virtual int do_load_next_box(SrsMp4Box** ppbox, uint32_t required_box_type); }; #endif