diff --git a/src/qtmp4_atoms.h b/src/qtmp4_atoms.h index ee5a17d68..fc49d691d 100644 --- a/src/qtmp4_atoms.h +++ b/src/qtmp4_atoms.h @@ -141,4 +141,42 @@ typedef struct { video_stsd_atom_t id; } qt_image_description_t; +// one byte tag identifiers +#define MP4DT_O 0x01 +#define MP4DT_IO 0x02 +#define MP4DT_ES 0x03 +#define MP4DT_DEC_CONFIG 0x04 +#define MP4DT_DEC_SPECIFIC 0x05 +#define MP4DT_SL_CONFIG 0x06 +#define MP4DT_CONTENT_ID 0x07 +#define MP4DT_SUPPL_CONTENT_ID 0x08 +#define MP4DT_IP_PTR 0x09 +#define MP4DT_IPMP_PTR 0x0A +#define MP4DT_IPMP 0x0B +#define MP4DT_REGISTRATION 0x0D +#define MP4DT_ESID_INC 0x0E +#define MP4DT_ESID_REF 0x0F +#define MP4DT_FILE_IO 0x10 +#define MP4DT_FILE_O 0x11 +#define MP4DT_EXT_PROFILE_LEVEL 0x13 +#define MP4DT_TAGS_START 0x80 +#define MP4DT_TAGS_END 0xFE + +// MPEG4 esds structure +typedef struct { + uint8_t version; + uint32_t flags; + uint16_t esid; + uint8_t stream_priority; + uint8_t object_type_id; + uint8_t stream_type; + uint32_t buffer_size_db; + uint32_t max_bitrate; + uint32_t avg_bitrate; + uint8_t decoder_config_len; + unsigned char *decoder_config; + uint8_t sl_config_len; + unsigned char *sl_config; +} esds_t; + #endif // __QTMP4_ATOMS_H diff --git a/src/r_mp4.cpp b/src/r_mp4.cpp index 638e1f163..746b17942 100644 --- a/src/r_mp4.cpp +++ b/src/r_mp4.cpp @@ -26,6 +26,7 @@ #include "common.h" #include "matroska.h" #include "mkvmerge.h" +#include "p_aac.h" #include "p_passthrough.h" #include "p_video.h" #include "r_mp4.h" @@ -35,8 +36,12 @@ using namespace libmatroska; #define PFX "Quicktime/MP4 reader: " +#define BE2STR(a) ((char *)&a)[3], ((char *)&a)[2], ((char *)&a)[1], \ + ((char *)&a)[0] + int qtmp4_reader_c::probe_file(mm_io_c *in, int64_t size) { - uint32_t atom_size, object; + uint32_t atom; + uint64_t atom_size; if (size < 20) return 0; @@ -44,10 +49,14 @@ int qtmp4_reader_c::probe_file(mm_io_c *in, int64_t size) { in->setFilePointer(0, seek_beginning); atom_size = in->read_uint32_be(); - object = in->read_uint32_be(); + if (atom_size == 1) + atom_size = in->read_uint64_be(); + atom = in->read_uint32_be(); - if ((object == FOURCC('m', 'o', 'o', 'v')) || - (object == FOURCC('f', 't', 'y', 'p'))) + mxverb(2, PFX "Atom: '%c%c%c%c'; size: %llu\n", BE2STR(atom), atom_size); + + if ((atom == FOURCC('m', 'o', 'o', 'v')) || + (atom == FOURCC('f', 't', 'y', 'p'))) return 1; } catch (exception &ex) { @@ -105,6 +114,8 @@ void qtmp4_reader_c::free_demuxer(qtmp4_demuxer_t *dmx) { safefree(dmx->editlist_table); safefree(dmx->v_desc); safefree(dmx->a_priv); + safefree(dmx->a_esds.decoder_config); + safefree(dmx->a_esds.sl_config); } void qtmp4_reader_c::read_atom(uint32_t &atom, uint64_t &size, uint64_t &pos, @@ -122,8 +133,6 @@ void qtmp4_reader_c::read_atom(uint32_t &atom, uint64_t &size, uint64_t &pos, atom = io->read_uint32_be(); } -#define BE2STR(a) ((char *)&a)[3], ((char *)&a)[2], ((char *)&a)[1], \ - ((char *)&a)[0] #define skip_atom() io->setFilePointer(atom_pos + atom_size) void qtmp4_reader_c::parse_headers() { @@ -183,10 +192,19 @@ void qtmp4_reader_c::parse_headers() { strncasecmp(dmx->fourcc, "svq", 3) && strncasecmp(dmx->fourcc, "cvid", 4)) || ((dmx->type == 'a') && - strncasecmp(dmx->fourcc, "QDM", 3))) { + strncasecmp(dmx->fourcc, "QDM", 3) && + strncasecmp(dmx->fourcc, "MP4A", 4))) { mxwarn(PFX "Unknown/unsupported FourCC '%.4s' for track %u.\n", &dmx->fourcc, dmx->id); - free_demuxer(dmx); + + continue; + } + + if ((dmx->type == 'a') && + !strncasecmp(dmx->fourcc, "MP4A", 4) && + (dmx->a_esds.decoder_config == NULL)) { + mxwarn(PFX "AAC track %u is missing the esds atom/the decoder config.\n", + dmx->id); continue; } @@ -325,10 +343,9 @@ void qtmp4_reader_c::handle_header_atoms(uint32_t parent, int64_t parent_size, (new_dmx->fourcc[2] == 0) && (new_dmx->fourcc[3] == 0))) { free_demuxer(new_dmx); safefree(new_dmx); - } else { + } else demuxers.push_back(new_dmx); - new_dmx = NULL; - } + new_dmx = NULL; } @@ -480,15 +497,44 @@ void qtmp4_reader_c::handle_header_atoms(uint32_t parent, int64_t parent_size, if ((get_uint16_be(&sv1_stsd.v0.version) == 1) && (size > sizeof(sound_v1_stsd_atom_t))) { io->setFilePointer(pos + sizeof(sound_v1_stsd_atom_t) + 4); - new_dmx->a_priv_size = io->read_uint32_be(); - io->skip(4); - new_dmx->a_priv = - (unsigned char *)safemalloc(new_dmx->a_priv_size); - if (io->read(new_dmx->a_priv, new_dmx->a_priv_size) != - new_dmx->a_priv_size) - throw exception(); - mxverb(2, PFX "%*sAudio private data size %u\n", (level + 1) * 2, - "", new_dmx->a_priv_size); + while (io->getFilePointer() < (pos + size)) { + uint32_t add_atom, add_atom_size; + + add_atom_size = io->read_uint32_be(); + add_atom = io->read_uint32(); + add_atom_size -= 8; + mxverb(2, PFX "%*sAudio private data size: %u, type: '%.4s'\n", + (level + 1) * 2, "", add_atom_size + 8, &add_atom); + if (new_dmx->a_priv == NULL) { + mm_mem_io_c *memio; + uint32_t patom_size, patom; + + new_dmx->a_priv_size = add_atom_size; + new_dmx->a_priv = + (unsigned char *)safemalloc(new_dmx->a_priv_size); + if (io->read(new_dmx->a_priv, new_dmx->a_priv_size) != + new_dmx->a_priv_size) + throw exception(); + + memio = new mm_mem_io_c(new_dmx->a_priv, + new_dmx->a_priv_size); + while (!memio->eof()) { + patom_size = memio->read_uint32_be(); + patom = memio->read_uint32_be(); + mxverb(2, PFX "%*sAtom size: %u, atom: '%c%c%c%c'\n", + (level + 2) * 2, "", patom_size, BE2STR(patom)); + if ((patom == FOURCC('e', 's', 'd', 's')) && + !new_dmx->a_esds_parsed) { + memio->save_pos(); + new_dmx->a_esds_parsed = parse_esds_atom(memio, new_dmx); + memio->restore_pos(); + } + memio->skip(patom_size - 8); + } + delete memio; + } else + io->skip(add_atom_size); + } } memcpy(&new_dmx->a_stsd, &sv1_stsd, sizeof(sound_v1_stsd_atom_t)); @@ -825,6 +871,95 @@ int qtmp4_reader_c::read(generic_packetizer_c *ptzr) { return 0; } +uint32_t qtmp4_reader_c::read_esds_descr_len(mm_mem_io_c *memio) { + uint32_t len, num_bytes; + uint8_t byte; + + len = 0; + num_bytes = 0; + do { + byte = memio->read_uint8(); + num_bytes++; + len = (len << 7) | (byte & 0x7f); + } while (((byte & 0x80) == 0x80) && (num_bytes < 4)); + + return len; +} + +bool qtmp4_reader_c::parse_esds_atom(mm_mem_io_c *memio, + qtmp4_demuxer_t *dmx) { + uint32_t len; + uint8_t tag; + esds_t *e; + + e = &dmx->a_esds; + e->version = memio->read_uint8(); + e->flags = memio->read_uint24_be(); + mxverb(2, PFX "esds: version: %u, flags: %u\n", e->version, e->flags); + tag = memio->read_uint8(); + if (tag == MP4DT_ES) { + len = read_esds_descr_len(memio); + e->esid = memio->read_uint16_be(); + e->stream_priority = memio->read_uint8(); + mxverb(2, PFX "esds: id: %u, stream priority: %u, len: %u\n", e->esid, + (uint32_t)e->stream_priority, len); + } else { + e->esid = memio->read_uint16_be(); + mxverb(2, PFX "esds: id: %u\n", e->esid); + } + + tag = memio->read_uint8(); + if (tag != MP4DT_DEC_CONFIG) { + mxverb(2, PFX "tag is not DEC_CONFIG (0x%02x) but 0x%02x.\n", + MP4DT_DEC_CONFIG, (uint32_t)tag); + return false; + } + + len = read_esds_descr_len(memio); + + e->object_type_id = memio->read_uint8(); + e->stream_type = memio->read_uint8(); + e->buffer_size_db = memio->read_uint24_be(); + e->max_bitrate = memio->read_uint32_be(); + e->avg_bitrate = memio->read_uint32_be(); + mxverb(2, PFX "esds: decoder config descriptor, len: %u, object_type_id: " + "%u, stream_type: 0x%2x, buffer_size_db: %u, max_bitrate: %.3fkbit/s" + ", avg_bitrate: %.3fkbit/s\n", len, e->object_type_id, e->stream_type, + e->buffer_size_db, e->max_bitrate / 1000.0, e->avg_bitrate / 1000.0); + + e->decoder_config_len = 0; + + tag = memio->read_uint8(); + if (tag != MP4DT_DEC_SPECIFIC) { + mxverb(2, PFX "tag is not DEC_SPECIFIC (0x%02x) but 0x%02x.\n", + MP4DT_DEC_SPECIFIC, (uint32_t)tag); + return false; + } + + len = read_esds_descr_len(memio); + e->decoder_config_len = len; + e->decoder_config = (uint8_t *)safemalloc(len); + if (memio->read(e->decoder_config, len) != len) + throw exception(); + mxverb(2, PFX "esds: decoder specific descriptor, len: %u\n", len); + + tag = memio->read_uint8(); + if (tag != MP4DT_SL_CONFIG) { + mxverb(2, PFX "tag is not SL_CONFIG (0x%02x) but 0x%02x.\n", + MP4DT_SL_CONFIG, (uint32_t)tag); + return false; + } + + len = read_esds_descr_len(memio); + e->sl_config_len = len; + e->sl_config = (uint8_t *)safemalloc(len); + if (memio->read(e->sl_config, len) != len) + throw exception(); + mxverb(2, PFX "esds: sync layer config descriptor, len: %u\n", len); + + return true; +} + void qtmp4_reader_c::create_packetizers() { uint32_t i; qtmp4_demuxer_t *dmx; @@ -850,7 +985,8 @@ void qtmp4_reader_c::create_packetizers() { dmx->packetizer->duplicate_data_on_add(false); - mxinfo("+-> Using the video packetizer for track %u.\n", dmx->id); + mxinfo("+-> Using the video packetizer for track %u (FourCC: %.4s).\n", + dmx->id, dmx->fourcc); } else { if (!strncasecmp(dmx->fourcc, "QDMC", 4) || @@ -869,6 +1005,28 @@ void qtmp4_reader_c::create_packetizers() { mxinfo("+-> Using generic audio output module for stream " "%u (FourCC: %.4s).\n", dmx->id, dmx->fourcc); + } else if (!strncasecmp(dmx->fourcc, "MP4A", 4)) { + int profile, sample_rate_idx, channels; + + if (dmx->a_esds.decoder_config_len == 2) { + profile = (dmx->a_esds.decoder_config[0] >> 3) - 1; + sample_rate_idx = ((dmx->a_esds.decoder_config[0] & 0x07) << 1) | + (dmx->a_esds.decoder_config[1] >> 7); + channels = (dmx->a_esds.decoder_config[1] & 0x7f) >> 3; + + mxverb(2, PFX "AAC: profile: %d, sample_rate_idx: %d, channels: " + "%d\n", profile, sample_rate_idx, channels); + + dmx->packetizer = new aac_packetizer_c(this, AAC_ID_MPEG4, profile, + (uint32_t)dmx->a_samplerate, + channels, ti, false, true); + if (verbose) + mxinfo("+-> Using AAC output module for stream %u.\n", dmx->id, + dmx->fourcc); + } else + mxerror(PFX "AAC found, but decoder config data has length %u.\n", + dmx->a_esds.decoder_config_len); + } else die(PFX "Should not have happened #1."); } diff --git a/src/r_mp4.h b/src/r_mp4.h index 22a6c5619..6c83f7eed 100644 --- a/src/r_mp4.h +++ b/src/r_mp4.h @@ -102,6 +102,8 @@ typedef struct { unsigned char *a_priv; uint32_t a_priv_size; sound_v1_stsd_atom_t a_stsd; + esds_t a_esds; + bool a_esds_parsed; bool warning_printed; @@ -137,8 +139,8 @@ protected: virtual void read_atom(uint32_t &atom, uint64_t &size, uint64_t &pos, uint32_t &hsize); virtual void free_demuxer(qtmp4_demuxer_t *dmx); -// virtual real_demuxer_t *find_demuxer(int id); -// virtual int finish(); + virtual bool parse_esds_atom(mm_mem_io_c *memio, qtmp4_demuxer_t *dmx); + virtual uint32_t read_esds_descr_len(mm_mem_io_c *memio); }; #endif // __R_MP4_H