From 8618172f811bb6a7ddc811cc981212a5b18a15fc Mon Sep 17 00:00:00 2001 From: Martchus Date: Tue, 7 Jul 2015 03:01:48 +0200 Subject: [PATCH] improved detection of media formats in Matroska and MP4 files --- matroska/matroskatrack.cpp | 8 +- mediaformat.cpp | 106 ++++++- mediaformat.h | 84 +++++- mp4/mp4atom.cpp | 15 +- mp4/mp4ids.cpp | 46 ++- mp4/mp4ids.h | 48 ++- mp4/mp4track.cpp | 578 +++++++++++++++++++++---------------- mp4/mp4track.h | 11 + 8 files changed, 618 insertions(+), 278 deletions(-) diff --git a/matroska/matroskatrack.cpp b/matroska/matroskatrack.cpp index a020a87..dac3de0 100644 --- a/matroska/matroskatrack.cpp +++ b/matroska/matroskatrack.cpp @@ -61,14 +61,14 @@ MediaFormat MatroskaTrack::codecIdToMediaFormat(const string &codecId) fmt.general = GeneralMediaFormat::Mpeg4Video; if(part2 == "ISO") { if(part3 == "SP") { - fmt.sub = SubFormats::Mpeg4Sp; + fmt.sub = SubFormats::Mpeg4SimpleProfile1; } else if(part3 == "ASP") { - fmt.sub = SubFormats::Mpeg4Asp; + fmt.sub = SubFormats::Mpeg4AdvancedSimpleProfile1; } else if(part3 == "AVC") { - fmt.sub = SubFormats::Mpeg4Avc; + fmt.general = GeneralMediaFormat::Avc; } } else if(part2 == "MS" && part3 == "V3") { - fmt.sub = SubFormats::Mpeg4MsV3; + fmt.sub = SubFormats::Mpeg4SimpleProfile1; } } else if(part1 == "V_MPEG1") { fmt.general = GeneralMediaFormat::Mpeg1Video; diff --git a/mediaformat.cpp b/mediaformat.cpp index be1aadb..7009446 100644 --- a/mediaformat.cpp +++ b/mediaformat.cpp @@ -41,6 +41,20 @@ const char *MediaFormat::name() const case GeneralMediaFormat::AfxStream: return "AFX Stream"; case GeneralMediaFormat::Alac: return "Apple Lossless Audio Codec"; case GeneralMediaFormat::Als: return "ALS"; + case GeneralMediaFormat::Amr: return "Adaptive Multi-Rate audio codec"; + case GeneralMediaFormat::Avc: + switch(sub) { + case AvcBaselineProfile: return "Advanced Video Coding Basline Profile"; + case AvcMainProfile: return "Advanced Video Coding Main Profile"; + case AvcScalableBaselineProfile: return "Advanced Video Coding Scalable Basline Profile"; + case AvcScalableHighProfile: return "Advanced Video Coding Scalable High Profile"; + case AvcExtendedProfile: return "Advanced Video Coding Extended Profile"; + case AvcHighProfile: return "Advanced Video Coding High Profile"; + case AvcHigh10Profile: return "Advanced Video Coding High 10 Profile"; + case AvcHigh422Profile: return "Advanced Video Coding High 4:2:2 Profile"; + case AvcHigh444Profile: return "Advanced Video Coding High 4:4:4 Profile"; + default: return "Advanced Video Coding"; + } case GeneralMediaFormat::Bitmap: return "Windows Bitmap"; case GeneralMediaFormat::Dirac: return "Dirac"; case GeneralMediaFormat::Dts: return "DTS"; @@ -62,6 +76,7 @@ const char *MediaFormat::name() const case GeneralMediaFormat::FontDataStream: return "Font Data Stream"; case GeneralMediaFormat::Gif: return "GIF"; case GeneralMediaFormat::Gpp2Cmf: return "3GPP2 Compact Multimedia Format (CMF)"; + case GeneralMediaFormat::Hevc: return "High Efficiency Video Coding"; case GeneralMediaFormat::ImaadpcmAcm: return "IMAADPCM ACM"; case GeneralMediaFormat::ImageSubtitle: switch(sub) { @@ -73,6 +88,7 @@ const char *MediaFormat::name() const case GeneralMediaFormat::OggKate: return "Karaoke And Text Encapsulation"; case GeneralMediaFormat::MicrosoftAudioCodecManager: return "Microsoft Audio Codec Manager"; case GeneralMediaFormat::MicrosoftVideoCodecManager: return "Microsoft Video Codec Manager"; + case GeneralMediaFormat::DolbyMlp: return "Dolby TrueHD"; case GeneralMediaFormat::Mpeg1Audio: switch(sub) { case Mpeg1Layer1: return "MPEG-1 Layer 1"; @@ -100,11 +116,65 @@ const char *MediaFormat::name() const } case GeneralMediaFormat::Mpeg4Video: switch(sub) { - case Mpeg4Sp: return "MPEG-4 Simple Profile"; - case Mpeg4Asp: return "MPEG-4 Advanced Simple Profile"; - case Mpeg4Avc: return "MPEG-4 Advanced Video Coding"; - case Mpeg4AvcParams: return "Parameter for MPEG-4 Advanced Video Coding"; - case Mpeg4MsV3: return "MPEG-4 Microsoft V3"; + case Mpeg4SimpleProfile1: return "MPEG-4 Simple Profile L1"; + case Mpeg4SimpleProfile2: return "MPEG-4 Simple Profile L2"; + case Mpeg4SimpleProfile3: return "MPEG-4 Simple Profile L2"; + case Mpeg4SimpleProfile0: return "MPEG-4 Simple Profile"; + case Mpeg4SimpleScalableProfile0: return "MPEG-4 Simple Scalable Profile"; + case Mpeg4SimpleScalableProfile1: return "MPEG-4 Simple Scalable Profile L1"; + case Mpeg4SimpleScalableProfile2: return "MPEG-4 Simple Scalable Profile L2"; + case Mpeg4CoreProfile1: return "MPEG-4 Core Profile L1"; + case Mpeg4CoreProfiel2: return "MPEG-4 Core Profile L2"; + case Mpeg4MainProfile2: return "MPEG-4 Main Profile L2"; + case Mpeg4MainProfile3: return "MPEG-4 Main Profile L3"; + case Mpeg4MainProfile4: return "MPEG-4 Main Profile L4"; + case Mpeg4NBitPrifle2: return "MPEG-4 N-Bit Profile L2"; + case Mpeg4ScalableTextureProfile1: return "MPEG-4 Scalable Texture Profile L1"; + case Mpeg4SimpleFaceAnimationProfile1: return "MPEG-4 Simple Face Animation Profile L1"; + case Mpeg4SimpleFaceAnimationProfile2: return "MPEG-4 Simple Face Animation Profile L2"; + case Mpeg4SimpleFbaProfile1: return "MPEG-4 Simple FBA Profile L1"; + case Mpeg4SimpleFbaProfile2: return "MPEG-4 Simple FBA Profile L2"; + case Mpeg4BasicAnimatedTextureProfiel1: return "MPEG-4 Basic Animated Texture Profile L1"; + case Mpeg4BasicAnimatedTextureProfiel2: return "MPEG-4 Basic Animated Texture Profile L2"; + case Mpeg4AvcProfile: return "MPEG-4 Advanced Audio Coding Profile"; + case Mpeg4HybridProfile1: return "MPEG-4 Hybrid Profile L1"; + case Mpeg4HybridProfile2: return "MPEG-4 Hybrid Profile L2"; + case Mpeg4AdvancedRealTimeSimpleProfile1: return "MPEG-4 Basic Animated Texture Profile L1"; + case Mpeg4AdvancedRealTimeSimpleProfile2: return "MPEG-4 Basic Animated Texture Profile L2"; + case Mpeg4AdvancedRealTimeSimpleProfile3: return "MPEG-4 Basic Animated Texture Profile L3"; + case Mpeg4AdvancedRealTimeSimpleProfile4: return "MPEG-4 Basic Animated Texture Profile L4"; + case Mpeg4CoreScalableProfile1: return "MPEG-4 Core Scalable Profile L1"; + case Mpeg4CoreScalableProfile2: return "MPEG-4 Core Scalable Profile L2"; + case Mpeg4CoreScalableProfile3: return "MPEG-4 Core Scalable Profile L3"; + case Mpeg4AdvancedCodingEfficiencyProfile1: return "MPEG-4 Advanced Coding Efficiency Profile L1"; + case Mpeg4AdvancedCodingEfficiencyProfile2: return "MPEG-4 Advanced Coding Efficiency Profile L2"; + case Mpeg4AdvancedCodingEfficiencyProfile3: return "MPEG-4 Advanced Coding Efficiency Profile L3"; + case Mpeg4AdvancedCodingEfficiencyProfile4: return "MPEG-4 Advanced Coding Efficiency Profile L4"; + case Mpeg4AdvancedCoreProfile1: return "MPEG-4 Advanced Core Profile L1"; + case Mpeg4AdvancedCoreProfile2: return "MPEG-4 Advanced Core Profile L2"; + case Mpeg4AdvancedScalableTexture1: return "MPEG-4 Advanced Scalable Texture L1"; + case Mpeg4AdvancedScalableTexture2: return "MPEG-4 Advanced Scalable Texture L2"; + case Mpeg4SimpleStudioProfile1: return "MPEG-4 Simple Studio Profile L1"; + case Mpeg4SimpleStudioProfile2: return "MPEG-4 Simple Studio Profile L2"; + case Mpeg4SimpleStudioProfile3: return "MPEG-4 Simple Studio Profile L3"; + case Mpeg4SimpleStudioProfile4: return "MPEG-4 Simple Studio Profile L4"; + case Mpeg4CoreStudioProfile1: return "MPEG-4 Core Studio Profile L1"; + case Mpeg4CoreStudioProfile2: return "MPEG-4 Core Studio Profile L2"; + case Mpeg4CoreStudioProfile3: return "MPEG-4 Core Studio Profile L3"; + case Mpeg4CoreStudioProfile4: return "MPEG-4 Core Studio Profile L4"; + case Mpeg4AdvancedSimpleProfile0: return "MPEG-4 Advanced Simple Profile"; + case Mpeg4AdvancedSimpleProfile1: return "MPEG-4 Advanced Simple Profile L1"; + case Mpeg4AdvancedSimpleProfile2: return "MPEG-4 Advanced Simple Profile L2"; + case Mpeg4AdvancedSimpleProfile3: return "MPEG-4 Advanced Simple Profile L3"; + case Mpeg4AdvancedSimpleProfile4: return "MPEG-4 Advanced Simple Profile L4"; + case Mpeg4AdvancedSimpleProfile5: return "MPEG-4 Advanced Simple Profile L5"; + case Mpeg4AdvancedSimpleProfile3b: return "MPEG-4 Advanced Simple Profile L3b"; + case Mpeg4FineGranularityScalableProfile0: return "MPEG-4 Fine Granularity Scalable Profile"; + case Mpeg4FineGranularityScalableProfile1: return "MPEG-4 Fine Granularity Scalable Profile L1"; + case Mpeg4FineGranularityScalableProfile2: return "MPEG-4 Fine Granularity Scalable Profile L2"; + case Mpeg4FineGranularityScalableProfile3: return "MPEG-4 Fine Granularity Scalable Profile L3"; + case Mpeg4FineGranularityScalableProfile4: return "MPEG-4 Fine Granularity Scalable Profile L4"; + case Mpeg4FineGranularityScalableProfile5: return "MPEG-4 Fine Granularity Scalable Profile L5"; default: return "MPEG-4 Visual"; } case GeneralMediaFormat::Mpc: return "Musepack SV8"; @@ -146,6 +216,7 @@ const char *MediaFormat::name() const case GeneralMediaFormat::Vorbis: return "Vorbis"; case GeneralMediaFormat::Vp8: return "VP8"; case GeneralMediaFormat::WavPack: return "WavPack"; + case GeneralMediaFormat::WindowsMediaAudio: return "Windows Media Audio"; default: return "unknown"; } } @@ -182,6 +253,8 @@ const char *MediaFormat::abbreviation() const case GeneralMediaFormat::AfxStream: return "AFX"; case GeneralMediaFormat::Alac: return "ALAC"; case GeneralMediaFormat::Als: return "ALS"; + case GeneralMediaFormat::Amr: return "AMR"; + case GeneralMediaFormat::Avc: return "H.264"; case GeneralMediaFormat::Bitmap: return "BMP"; case GeneralMediaFormat::Dirac: return "Dirac"; case GeneralMediaFormat::Dts: return "DTS"; @@ -203,6 +276,7 @@ const char *MediaFormat::abbreviation() const case GeneralMediaFormat::FontDataStream: return "FDS"; case GeneralMediaFormat::Gif: return "GIF"; case GeneralMediaFormat::Gpp2Cmf: return "3GPP2 CMF"; + case GeneralMediaFormat::Hevc: return "H.265"; case GeneralMediaFormat::ImaadpcmAcm: return "IMAADPCM ACM"; case GeneralMediaFormat::ImageSubtitle: switch(sub) { @@ -214,6 +288,7 @@ const char *MediaFormat::abbreviation() const case GeneralMediaFormat::OggKate: return "OggKate"; case GeneralMediaFormat::MicrosoftAudioCodecManager: return "MS ACM"; case GeneralMediaFormat::MicrosoftVideoCodecManager: return "MS VCM"; + case GeneralMediaFormat::DolbyMlp: return "Dolby TrueHD"; case GeneralMediaFormat::Mpeg1Audio: switch(sub) { case Mpeg1Layer1: return "MP1"; @@ -241,11 +316,21 @@ const char *MediaFormat::abbreviation() const } case GeneralMediaFormat::Mpeg4Video: switch(sub) { - case Mpeg4Sp: return "MPEG-4 SP"; - case Mpeg4Asp: return "H.263"; - case Mpeg4Avc: return "H.264"; - case Mpeg4AvcParams: return "H.264 params"; - case Mpeg4MsV3: return "MPEG-4 MS V3"; + case Mpeg4SimpleProfile1: + case Mpeg4SimpleProfile2: + case Mpeg4SimpleProfile3: + case Mpeg4SimpleProfile0: + return "MPEG-4 SP"; + case Mpeg4AdvancedSimpleProfile0: + case Mpeg4AdvancedSimpleProfile1: + case Mpeg4AdvancedSimpleProfile2: + case Mpeg4AdvancedSimpleProfile3: + case Mpeg4AdvancedSimpleProfile4: + case Mpeg4AdvancedSimpleProfile5: + case Mpeg4AdvancedSimpleProfile3b: + return "H.263"; + case Mpeg4AvcProfile: + return "H.264"; default: return "MPEG-4 Visual"; } case GeneralMediaFormat::Mpc: return "MPC"; @@ -291,6 +376,7 @@ const char *MediaFormat::abbreviation() const case GeneralMediaFormat::Vorbis: return "Vorbis"; case GeneralMediaFormat::Vp8: return "VP8"; case GeneralMediaFormat::WavPack: return "WavPack"; + case GeneralMediaFormat::WindowsMediaAudio: return "WMA"; default: return ""; } } diff --git a/mediaformat.h b/mediaformat.h index 2f989c5..113df50 100644 --- a/mediaformat.h +++ b/mediaformat.h @@ -32,6 +32,8 @@ enum class GeneralMediaFormat AfxStream, /**< AFX Stream */ Alac, /**< Apple Lossless Audio Codec */ Als, /**< ALS */ + Amr, /** < AMR */ + Avc, /** < Advanced Video Coding */ Bitmap, /**< Windows Bitmap */ Dirac, /**< Dirac */ Dts, /**< DTS */ @@ -42,6 +44,7 @@ enum class GeneralMediaFormat FontDataStream, /**< Font Data Stream */ Gif, /**< GIF */ Gpp2Cmf, /**< 3GPP2 Compact Multimedia Format (CMF) */ + Hevc, /**< H.265/High Efficiency Video Coding */ ImaadpcmAcm, /**< IMAADPCM ACM */ ImageSubtitle, /**< Image subtitle */ InteractionStream, /**< Interaction Stream */ @@ -49,6 +52,7 @@ enum class GeneralMediaFormat OggKate, /**< Karaoke And Text Encapsulation */ MicrosoftAudioCodecManager, /**< Microsoft Audio Codec Manager (ACM) */ MicrosoftVideoCodecManager, /**< Microsoft Video Codec Manager (VCM) */ + DolbyMlp, /** < Dolby MLP */ Mpeg1Audio, /**< MPEG-1 Audio */ Mpeg1Video, /**< MPEG-1 Vudio */ Mpeg2Audio, /**< MPEG-2 Audio */ @@ -79,7 +83,8 @@ enum class GeneralMediaFormat VobSub, /**< VobSub */ Vorbis, /**< Vorbis */ Vp8, /** < VP8 */ - WavPack /**< WavPack */ + WavPack, /**< WavPack */ + WindowsMediaAudio /**< Windows Media Audio */ }; /*! @@ -126,11 +131,77 @@ enum Mpeg2VideoProfile : unsigned char { }; enum Mpeg4VideoProfile : unsigned char { - Mpeg4Sp = 1, - Mpeg4Asp, - Mpeg4Avc, - Mpeg4AvcParams, - Mpeg4MsV3 + Mpeg4SimpleProfile1 = 0x01, + Mpeg4SimpleProfile2 = 0x02, + Mpeg4SimpleProfile3 = 0x03, + Mpeg4SimpleProfile0 = 0x08, + Mpeg4SimpleScalableProfile0 = 0x10, + Mpeg4SimpleScalableProfile1 = 0x11, + Mpeg4SimpleScalableProfile2 = 0x12, + Mpeg4CoreProfile1 = 0x21, + Mpeg4CoreProfiel2 = 0x22, + Mpeg4MainProfile2 = 0x32, + Mpeg4MainProfile3 = 0x33, + Mpeg4MainProfile4 = 0x34, + Mpeg4NBitPrifle2 = 0x42, + Mpeg4ScalableTextureProfile1 = 0x51, + Mpeg4SimpleFaceAnimationProfile1 = 0x61, + Mpeg4SimpleFaceAnimationProfile2 = 0x62, + Mpeg4SimpleFbaProfile1 = 0x63, + Mpeg4SimpleFbaProfile2 = 0x64, + Mpeg4BasicAnimatedTextureProfiel1 = 0x71, + Mpeg4BasicAnimatedTextureProfiel2 = 0x72, + Mpeg4AvcProfile = 0x7F, + Mpeg4HybridProfile1 = 0x81, + Mpeg4HybridProfile2 = 0x82, + Mpeg4AdvancedRealTimeSimpleProfile1 = 0x91, + Mpeg4AdvancedRealTimeSimpleProfile2 = 0x92, + Mpeg4AdvancedRealTimeSimpleProfile3 = 0x93, + Mpeg4AdvancedRealTimeSimpleProfile4 = 0x94, + Mpeg4CoreScalableProfile1 = 0xA1, + Mpeg4CoreScalableProfile2 = 0xA2, + Mpeg4CoreScalableProfile3 = 0xA3, + Mpeg4AdvancedCodingEfficiencyProfile1 = 0xB1, + Mpeg4AdvancedCodingEfficiencyProfile2 = 0xB2, + Mpeg4AdvancedCodingEfficiencyProfile3 = 0xB3, + Mpeg4AdvancedCodingEfficiencyProfile4 = 0xB4, + Mpeg4AdvancedCoreProfile1 = 0xC1, + Mpeg4AdvancedCoreProfile2 = 0xC2, + Mpeg4AdvancedScalableTexture1 = 0xD1, + Mpeg4AdvancedScalableTexture2 = 0xD2, + Mpeg4SimpleStudioProfile1 = 0xE1, + Mpeg4SimpleStudioProfile2 = 0xE2, + Mpeg4SimpleStudioProfile3 = 0xE3, + Mpeg4SimpleStudioProfile4 = 0xE4, + Mpeg4CoreStudioProfile1 = 0xE5, + Mpeg4CoreStudioProfile2 = 0xE6, + Mpeg4CoreStudioProfile3 = 0xE7, + Mpeg4CoreStudioProfile4 = 0xE8, + Mpeg4AdvancedSimpleProfile0 = 0xF0, + Mpeg4AdvancedSimpleProfile1 = 0xF1, + Mpeg4AdvancedSimpleProfile2 = 0xF2, + Mpeg4AdvancedSimpleProfile3 = 0xF3, + Mpeg4AdvancedSimpleProfile4 = 0xF4, + Mpeg4AdvancedSimpleProfile5 = 0xF5, + Mpeg4AdvancedSimpleProfile3b = 0xF7, + Mpeg4FineGranularityScalableProfile0 = 0xF8, + Mpeg4FineGranularityScalableProfile1 = 0xF9, + Mpeg4FineGranularityScalableProfile2 = 0xFA, + Mpeg4FineGranularityScalableProfile3 = 0xFB, + Mpeg4FineGranularityScalableProfile4 = 0xFC, + Mpeg4FineGranularityScalableProfile5 = 0xFD +}; + +enum AvcProfile : unsigned char { + AvcBaselineProfile = 0x42, + AvcMainProfile = 0x4D, + AvcScalableBaselineProfile = 0x53, + AvcScalableHighProfile = 0x56, + AvcExtendedProfile = 0x58, + AvcHighProfile = 0x64, + AvcHigh10Profile = 0x6E, + AvcHigh422Profile = 0x7A, + AvcHigh444Profile = 0x90 }; enum DtsSpecifier : unsigned char { @@ -221,3 +292,4 @@ inline MediaFormat::operator bool() const } #endif // MEDIAFORMAT_H + diff --git a/mp4/mp4atom.cpp b/mp4/mp4atom.cpp index de92b61..0739513 100644 --- a/mp4/mp4atom.cpp +++ b/mp4/mp4atom.cpp @@ -151,7 +151,11 @@ bool Mp4Atom::isParent() const switch(id()) { case Movie: case Track: case Media: case MediaInformation: case DataInformation: case SampleTable: case UserData: case Meta: case ItunesList: case MovieFragment: - case TrackFragment: case MovieExtends: case DataReference: + case TrackFragment: case MovieExtends: case DataReference: case Mp4AtomIds::AvcConfiguration: + case FourccIds::Mpeg4Audio: case FourccIds::AmrNarrowband: case FourccIds::Amr: + case FourccIds::Drms: case FourccIds::Alac: case FourccIds::WindowsMediaAudio: + case FourccIds::Ac3: case FourccIds::EAc3: case FourccIds::DolbyMpl: + case FourccIds::Dts: case FourccIds::DtsH: case FourccIds::DtsE: return true; default: if(parent()) { @@ -188,7 +192,8 @@ bool Mp4Atom::isPadding() const * * \remarks This information is not read from the atom header. The offsets are known * for specific atoms. - * \remarks This method returns zero for non-parent atoms which have no childs. * + * \remarks This method returns zero for non-parent atoms which have no childs. + * \remarks Childs with variable offset such as the "esds"-atom must be denoted! */ uint64 Mp4Atom::firstChildOffset() const { @@ -196,15 +201,13 @@ uint64 Mp4Atom::firstChildOffset() const using namespace FourccIds; if(isParent()) { switch(id()) { - case Meta: return headerSize() + 0x4; - case DataReference: return headerSize() + 0x8; + case Meta: return headerSize() + 0x4u; + case DataReference: return headerSize() + 0x8u; default: return headerSize(); } } else { switch(id()) { case SampleDescription: return headerSize() + 0x08u; - case Avc1: return headerSize() + 0x4eu; - case Mpeg4Audio: return headerSize() + 0x1cu; default: return 0x00u; } } diff --git a/mp4/mp4ids.cpp b/mp4/mp4ids.cpp index 8e221ab..c974528 100644 --- a/mp4/mp4ids.cpp +++ b/mp4/mp4ids.cpp @@ -51,16 +51,21 @@ MediaFormat fourccToMediaFormat(uint32 fourccId) return GeneralMediaFormat::Mpeg2Video; case Mpeg4Video: return GeneralMediaFormat::Mpeg4Video; + case Hevc1: case Hevc2: + return MediaFormat(GeneralMediaFormat::Hevc); case Avc1: case Avc2: case Avc3: case Avc4: case H264Decoder1: case H264Decoder2: case H264Decoder3: case H264Decoder4: case H264Decoder5: case H264Decoder6: - return MediaFormat(GeneralMediaFormat::Mpeg4Video, SubFormats::Mpeg4Avc); - case H263: case XvidDecoder1: case XvidDecoder2: - case XvidDecoder3: case XvidDecoder4: case XvidDecoder5: - case Divx5Decoder: - return MediaFormat(GeneralMediaFormat::Mpeg4Video, SubFormats::Mpeg4Asp); + return MediaFormat(GeneralMediaFormat::Avc); case Divx4Decoder1: case Divx4Decoder2: case Divx4Decoder3: case Divx4Decoder4: case Divx4Decoder5: case Divx4Decoder6: case Divx4Decoder7: - return MediaFormat(GeneralMediaFormat::Mpeg4Video, SubFormats::Mpeg4Sp); + case H263Quicktime: case H2633GPP: case XvidDecoder1: case XvidDecoder2: + case XvidDecoder3: case XvidDecoder4: case XvidDecoder5: case Divx5Decoder: + return MediaFormat(GeneralMediaFormat::Mpeg4Video, SubFormats::Mpeg4AdvancedSimpleProfile0); + case Divx3Decoder1: case Divx3Decoder2: case Divx3Decoder3: case Divx3Decoder4: case Divx3Decoder5: + case Divx3Decoder6: case Divx3Decoder7: case Divx3Decoder8: case Divx3Decoder9: case Divx3Decoder10: + case Divx3Decoder11: case Divx3Decoder12: case Divx3Decoder13: case Divx3Decoder14: case Divx3Decoder15: + case Divx3Decoder16: case Divx3Decoder17: + return MediaFormat(GeneralMediaFormat::Mpeg4Video, SubFormats::Mpeg4SimpleProfile0); case Tiff: return GeneralMediaFormat::Tiff; case AppleTextAtsuiCodec: @@ -85,6 +90,10 @@ MediaFormat fourccToMediaFormat(uint32 fourccId) return GeneralMediaFormat::Alac; case Ac3: return GeneralMediaFormat::Ac3; + case EAc3: + return GeneralMediaFormat::EAc3; + case DolbyMpl: + return GeneralMediaFormat::DolbyMlp; case Ac4: return GeneralMediaFormat::Ac4; case Rv20: case Rv30: case Rv40: @@ -97,6 +106,15 @@ MediaFormat fourccToMediaFormat(uint32 fourccId) return MediaFormat(GeneralMediaFormat::Pcm, SubFormats::PcmIntLe); case FloatingPoint32Bit: case FloatingPoint64Bit: return MediaFormat(GeneralMediaFormat::Pcm, SubFormats::PcmFloatIeee); + case Amr: case AmrNarrowband: + return MediaFormat(GeneralMediaFormat::Amr); + case Dts: case DtsH: + return MediaFormat(GeneralMediaFormat::Dts); + case DtsE: + return MediaFormat(GeneralMediaFormat::Dts, SubFormats::DtsExpress); + case WindowsMediaAudio: case WindowsMediaAudio7: + case WindowsMediaAudio9Professional: case WindowsMediaAudio9Standard: + return MediaFormat(GeneralMediaFormat::WindowsMediaAudio); // TODO: map more FOURCCs default: return GeneralMediaFormat::Unknown; @@ -131,8 +149,8 @@ MediaFormat streamObjectTypeFormat(byte streamObjectTypeId) case SynthesizedTextureStream: return GeneralMediaFormat::SynthesizedTextureStream; case StreamingTextStream: return GeneralMediaFormat::StreamingTextStream; case Mpeg4Visual: return GeneralMediaFormat::Mpeg4Video; - case Avc: return MediaFormat(GeneralMediaFormat::Mpeg4Video, SubFormats::Mpeg4Avc); - case ParameterSetsForAvc: return MediaFormat(GeneralMediaFormat::Mpeg4Video, SubFormats::Mpeg4AvcParams); + case Avc: return GeneralMediaFormat::Avc; + case ParameterSetsForAvc: return GeneralMediaFormat::Avc; case Als: return GeneralMediaFormat::Als; case Sa0c: return GeneralMediaFormat::Sa0c; case Aac: return MediaFormat(GeneralMediaFormat::Aac, SubFormats::AacMpeg4LowComplexityProfile); @@ -262,4 +280,16 @@ LIB_EXPORT MediaFormat idToMediaFormat(byte mpeg4AudioObjectId, bool sbrPresent, } +/*! + * \brief Encapsulates MPEG-4 video (14496-2) codes. + */ +namespace Mpeg4VideoCodes { +} + +/*! + * \brief Encapsulates MPEG-2 video codes. + */ +namespace Mpeg2VideoCodes { +} + } diff --git a/mp4/mp4ids.h b/mp4/mp4ids.h index 7924b47..963d546 100644 --- a/mp4/mp4ids.h +++ b/mp4/mp4ids.h @@ -146,6 +146,7 @@ enum KnownValue : uint32 { Alaw21 = 0x616C6177, AlphaCompositor = 0x626C6E64, AlphaGain = 0x6761696E, + Amr = 0x73617762, AmrNarrowband = 0x73616D72, Animation = 0x726C6520, /**< Animation */ Appl1 = 0x6476690, @@ -252,11 +253,16 @@ enum KnownValue : uint32 { Divx4Decoder6 = 0x6D347332, Divx4Decoder7 = 0x6D703473, Divx5Decoder = 0x44583530, - Drm = 0x64726D73, + Drms = 0x64726D73, + Drmi = 0x64726D69, + Dts = 0x6474736C, + DtsH = 0x64747368, + DtsE = 0x64747365, Dvca = 0x64766361, DvcPro501 = 0x64763570, DvcPro502 = 0x6476356E, DvcProPal = 0x64767070, + EAc3 = 0x65632D33, EdgeDetection = 0x65646765, Emboss = 0x656D6273, Explode = 0x78706C6F, @@ -271,7 +277,8 @@ enum KnownValue : uint32 { Glass = 0x676C6173, GradientWipe = 0x6D617474, Graphics = 0x736D6320, /**< Graphics */ - H263 = 0x68323633, /**< H.263/MPEG-4 ASP video */ + H263Quicktime = 0x68323633, /**< H.263/MPEG-4 ASP video (Quicktime) */ + H2633GPP = 0x73323633, /**< H.263 (3GPP format) */ H264Decoder1 = 0x44415643, H264Decoder2 = 0x48323634, H264Decoder3 = 0x56535348, @@ -279,6 +286,8 @@ enum KnownValue : uint32 { H264Decoder5 = 0x68323634, H264Decoder6 = 0x78323634, Hdv3 = 0x68647633, + Hevc1 = 0x68766331, /**< H.265/High Efficiency Video Coding */ + Hevc2 = 0x68657631, /**< H.265/High Efficiency Video Coding */ HslBalance = 0x68736C62, Ima4 = 0x696D6134, Ima41 = 0x696D6134, @@ -303,6 +312,7 @@ enum KnownValue : uint32 { Mace31 = 0x4D414333, Mace61 = 0x4D414336, MatrixWipe = 0x736D7034, + DolbyMpl = 0x6D6C7061, MotionJpegA = 0x6D6A7061, /**< Motion-JPEG (format A) */ MotionJpegB = 0x6D6A7062, /**< Motion-JPEG (format B) */ Mp3 = 0x2e6d7033, /**< MPEG-1 Layer 3 */ @@ -366,6 +376,7 @@ enum KnownValue : uint32 { Ulaw21 = 0x756C6177, VcmImageCodec = 0x4D6A7067, Vdva = 0x76647661, + WindowsMediaAudio = 0x6F776D61, /**< ? */ WindowsMediaAudio7 = 0x574D4131, WindowsMediaAudio9Professional = 0x574D4133, WindowsMediaAudio9Standard = 0x574D4132, @@ -579,6 +590,39 @@ LIB_EXPORT MediaFormat idToMediaFormat(byte mpeg4AudioObjectId, bool sbrPresent } +namespace Mpeg4VideoCodes { +enum KnownValue : byte { + VideoObjectStart = 0x00, + VideoObjectLayerStart = 0x20, + VisualObjectSequenceStart = 0xB0, + VisualObjectSequendeEnd = 0xB1, + UserDataStart = 0xB2, + GroupOfVopStart = 0xB3, + VideoSessionError = 0xB4, + VisualObjectStart = 0xB5, + VopStart = 0xB6, + FbaObjectStart = 0xBA, + FbaObjectPlaneStart = 0xBB, + MeshObjectStart = 0xBC, + MeshObjectPlaneStart = 0xBD, + StillTextureObjectStart = 0xBE, + TextureSpatialLayerStart = 0xBF, + TextureSnrLayerStart = 0xC0, + TextureTitleStart = 0xC1, + TextureShapeLayerStart = 0xC2, + StuffingStart = 0xC3 +}; +} + +namespace Mpeg2VideoCodes { +enum KnownValue : byte { + Pic = 0x00, + Seq = 0xB3, + Ext = 0xB5, + Gop = 0xB8 +}; +} + /*! * \brief Specifies the tag type. */ diff --git a/mp4/mp4track.cpp b/mp4/mp4track.cpp index 37aebe8..63b4ec6 100644 --- a/mp4/mp4track.cpp +++ b/mp4/mp4track.cpp @@ -61,6 +61,9 @@ Mpeg4AudioSpecificConfig::Mpeg4AudioSpecificConfig() : epConfig(0) {} +Mpeg4VideoSpecificConfig::Mpeg4VideoSpecificConfig() +{} + /*! * \class Media::Mp4Track * \brief Implementation of Media::AbstractTrack for the MP4 container. @@ -160,140 +163,140 @@ vector Mp4Track::readChunkOffsets() } } // read sample offsets of fragments -// Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingById(moof, true); -// uint64 totalDuration = 0; -// while(moofAtom) { -// moofAtom->parse(); -// Mp4Atom *trafAtom = moofAtom->childById(traf); -// while(trafAtom) { -// trafAtom->parse(); -// Mp4Atom *tfhdAtom = trafAtom->childById(tfhd); -// while(tfhdAtom) { -// tfhdAtom->parse(); -// uint32 calculatedDataSize = 0; -// if(tfhdAtom->dataSize() < calculatedDataSize) { -// addNotification(NotificationType::Critical, "tfhd atom is truncated.", context); -// } else { -// m_stream->seekg(tfhdAtom->dataOffset() + 1); -// uint32 flags = reader.readUInt24(); -// if(m_id == reader.readUInt32()) { // check track ID -// if(flags & 0x000001) { // base-data-offset present -// calculatedDataSize += 8; -// } -// if(flags & 0x000002) { // sample-description-index present -// calculatedDataSize += 4; -// } -// if(flags & 0x000008) { // default-sample-duration present -// calculatedDataSize += 4; -// } -// if(flags & 0x000010) { // default-sample-size present -// calculatedDataSize += 4; -// } -// if(flags & 0x000020) { // default-sample-flags present -// calculatedDataSize += 4; -// } -// //uint64 baseDataOffset = moofAtom->startOffset(); -// //uint32 defaultSampleDescriptionIndex = 0; -// uint32 defaultSampleDuration = 0; -// uint32 defaultSampleSize = 0; -// uint32 defaultSampleFlags = 0; -// if(tfhdAtom->dataSize() < calculatedDataSize) { -// addNotification(NotificationType::Critical, "tfhd atom is truncated (presence of fields denoted).", context); -// } else { -// if(flags & 0x000001) { // base-data-offset present -// //baseDataOffset = reader.readUInt64(); -// m_stream->seekg(8, ios_base::cur); -// } -// if(flags & 0x000002) { // sample-description-index present -// //defaultSampleDescriptionIndex = reader.readUInt32(); -// m_stream->seekg(4, ios_base::cur); -// } -// if(flags & 0x000008) { // default-sample-duration present -// defaultSampleDuration = reader.readUInt32(); -// //m_stream->seekg(4, ios_base::cur); -// } -// if(flags & 0x000010) { // default-sample-size present -// defaultSampleSize = reader.readUInt32(); -// } -// if(flags & 0x000020) { // default-sample-flags present -// defaultSampleFlags = reader.readUInt32(); -// //m_stream->seekg(4, ios_base::cur); -// } -// } -// Mp4Atom *trunAtom = trafAtom->childById(trun); -// while(trunAtom) { -// uint32 calculatedDataSize = 8; -// if(trunAtom->dataSize() < calculatedDataSize) { -// addNotification(NotificationType::Critical, "trun atom is truncated.", context); -// } else { -// m_stream->seekg(trunAtom->dataOffset() + 1); -// uint32 flags = reader.readUInt24(); -// uint32 sampleCount = reader.readUInt32(); -// m_sampleCount += sampleCount; -// if(flags & 0x000001) { // data offset present -// calculatedDataSize += 4; -// } -// if(flags & 0x000004) { // first-sample-flags present -// calculatedDataSize += 4; -// } -// uint32 entrySize = 0; -// if(flags & 0x000100) { // sample-duration present -// entrySize += 4; -// } -// if(flags & 0x000200) { // sample-size present -// entrySize += 4; -// } -// if(flags & 0x000400) { // sample-flags present -// entrySize += 4; -// } -// if(flags & 0x000800) { // sample-composition-time-offsets present -// entrySize += 4; -// } -// calculatedDataSize += entrySize * sampleCount; -// if(trunAtom->dataSize() < calculatedDataSize) { -// addNotification(NotificationType::Critical, "trun atom is truncated (presence of fields denoted).", context); -// } else { -// if(flags & 0x000001) { // data offset present -// m_stream->seekg(4, ios_base::cur); -// //int32 dataOffset = reader.readInt32(); -// } -// if(flags & 0x000004) { // first-sample-flags present -// m_stream->seekg(4, ios_base::cur); -// } -// for(uint32 i = 0; i < sampleCount; ++i) { -// if(flags & 0x000100) { // sample-duration present -// totalDuration += reader.readUInt32(); -// } else { -// totalDuration += defaultSampleDuration; -// } -// if(flags & 0x000200) { // sample-size present -// m_sampleSizes.push_back(reader.readUInt32()); -// m_size += m_sampleSizes.back(); -// } else { -// m_size += defaultSampleSize; -// } -// if(flags & 0x000400) { // sample-flags present -// m_stream->seekg(4, ios_base::cur); -// } -// if(flags & 0x000800) { // sample-composition-time-offsets present -// m_stream->seekg(4, ios_base::cur); -// } -// } -// } -// } -// trunAtom = trunAtom->siblingById(trun, false); -// } -// if(m_sampleSizes.empty() && defaultSampleSize) { -// m_sampleSizes.push_back(defaultSampleSize); -// } -// } -// } -// tfhdAtom = tfhdAtom->siblingById(tfhd, false); -// } -// trafAtom = trafAtom->siblingById(traf, false); -// } -// moofAtom = moofAtom->siblingById(moof, false); -// } + // Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingById(moof, true); + // uint64 totalDuration = 0; + // while(moofAtom) { + // moofAtom->parse(); + // Mp4Atom *trafAtom = moofAtom->childById(traf); + // while(trafAtom) { + // trafAtom->parse(); + // Mp4Atom *tfhdAtom = trafAtom->childById(tfhd); + // while(tfhdAtom) { + // tfhdAtom->parse(); + // uint32 calculatedDataSize = 0; + // if(tfhdAtom->dataSize() < calculatedDataSize) { + // addNotification(NotificationType::Critical, "tfhd atom is truncated.", context); + // } else { + // m_stream->seekg(tfhdAtom->dataOffset() + 1); + // uint32 flags = reader.readUInt24(); + // if(m_id == reader.readUInt32()) { // check track ID + // if(flags & 0x000001) { // base-data-offset present + // calculatedDataSize += 8; + // } + // if(flags & 0x000002) { // sample-description-index present + // calculatedDataSize += 4; + // } + // if(flags & 0x000008) { // default-sample-duration present + // calculatedDataSize += 4; + // } + // if(flags & 0x000010) { // default-sample-size present + // calculatedDataSize += 4; + // } + // if(flags & 0x000020) { // default-sample-flags present + // calculatedDataSize += 4; + // } + // //uint64 baseDataOffset = moofAtom->startOffset(); + // //uint32 defaultSampleDescriptionIndex = 0; + // uint32 defaultSampleDuration = 0; + // uint32 defaultSampleSize = 0; + // uint32 defaultSampleFlags = 0; + // if(tfhdAtom->dataSize() < calculatedDataSize) { + // addNotification(NotificationType::Critical, "tfhd atom is truncated (presence of fields denoted).", context); + // } else { + // if(flags & 0x000001) { // base-data-offset present + // //baseDataOffset = reader.readUInt64(); + // m_stream->seekg(8, ios_base::cur); + // } + // if(flags & 0x000002) { // sample-description-index present + // //defaultSampleDescriptionIndex = reader.readUInt32(); + // m_stream->seekg(4, ios_base::cur); + // } + // if(flags & 0x000008) { // default-sample-duration present + // defaultSampleDuration = reader.readUInt32(); + // //m_stream->seekg(4, ios_base::cur); + // } + // if(flags & 0x000010) { // default-sample-size present + // defaultSampleSize = reader.readUInt32(); + // } + // if(flags & 0x000020) { // default-sample-flags present + // defaultSampleFlags = reader.readUInt32(); + // //m_stream->seekg(4, ios_base::cur); + // } + // } + // Mp4Atom *trunAtom = trafAtom->childById(trun); + // while(trunAtom) { + // uint32 calculatedDataSize = 8; + // if(trunAtom->dataSize() < calculatedDataSize) { + // addNotification(NotificationType::Critical, "trun atom is truncated.", context); + // } else { + // m_stream->seekg(trunAtom->dataOffset() + 1); + // uint32 flags = reader.readUInt24(); + // uint32 sampleCount = reader.readUInt32(); + // m_sampleCount += sampleCount; + // if(flags & 0x000001) { // data offset present + // calculatedDataSize += 4; + // } + // if(flags & 0x000004) { // first-sample-flags present + // calculatedDataSize += 4; + // } + // uint32 entrySize = 0; + // if(flags & 0x000100) { // sample-duration present + // entrySize += 4; + // } + // if(flags & 0x000200) { // sample-size present + // entrySize += 4; + // } + // if(flags & 0x000400) { // sample-flags present + // entrySize += 4; + // } + // if(flags & 0x000800) { // sample-composition-time-offsets present + // entrySize += 4; + // } + // calculatedDataSize += entrySize * sampleCount; + // if(trunAtom->dataSize() < calculatedDataSize) { + // addNotification(NotificationType::Critical, "trun atom is truncated (presence of fields denoted).", context); + // } else { + // if(flags & 0x000001) { // data offset present + // m_stream->seekg(4, ios_base::cur); + // //int32 dataOffset = reader.readInt32(); + // } + // if(flags & 0x000004) { // first-sample-flags present + // m_stream->seekg(4, ios_base::cur); + // } + // for(uint32 i = 0; i < sampleCount; ++i) { + // if(flags & 0x000100) { // sample-duration present + // totalDuration += reader.readUInt32(); + // } else { + // totalDuration += defaultSampleDuration; + // } + // if(flags & 0x000200) { // sample-size present + // m_sampleSizes.push_back(reader.readUInt32()); + // m_size += m_sampleSizes.back(); + // } else { + // m_size += defaultSampleSize; + // } + // if(flags & 0x000400) { // sample-flags present + // m_stream->seekg(4, ios_base::cur); + // } + // if(flags & 0x000800) { // sample-composition-time-offsets present + // m_stream->seekg(4, ios_base::cur); + // } + // } + // } + // } + // trunAtom = trunAtom->siblingById(trun, false); + // } + // if(m_sampleSizes.empty() && defaultSampleSize) { + // m_sampleSizes.push_back(defaultSampleSize); + // } + // } + // } + // tfhdAtom = tfhdAtom->siblingById(tfhd, false); + // } + // trafAtom = trafAtom->siblingById(traf, false); + // } + // moofAtom = moofAtom->siblingById(moof, false); + // } return offsets; } @@ -511,7 +514,7 @@ std::unique_ptr Mp4Track::parseMpeg4ElementaryStreamI // read extended descriptor Mpeg4Descriptor esDesc(esDescAtom->container(), m_istream->tellg(), esDescAtom->dataSize() - 4); try { - esDesc.parse(); + esDesc.parse(); // check ID if(esDesc.id() != Mpeg4DescriptorIds::ElementaryStreamDescr) { addNotification(NotificationType::Critical, "Invalid descriptor found.", context); @@ -551,8 +554,12 @@ std::unique_ptr Mp4Track::parseMpeg4ElementaryStreamI case Aac: case Mpeg2AacMainProfile: case Mpeg2AacLowComplexityProfile: case Mpeg2AacScaleableSamplingRateProfile: case Mpeg2Audio: case Mpeg1Audio: esInfo->audioSpecificConfig = parseAudioSpecificConfig(decCfgDescChild); + break; + case Mpeg4Visual: + esInfo->videoSpecificConfig = parseVideoSpecificConfig(decCfgDescChild); + break; default: - ; // TODO: covering remaining object types + ; // TODO: covering more object types } break; } @@ -573,7 +580,7 @@ std::unique_ptr Mp4Track::parseMpeg4ElementaryStreamI } /*! - * \brief Reads the audio specific configuration for the track. + * \brief Parses the audio specific configuration for the track. * \remarks * - Notifications might be added. * \sa mpeg4ElementaryStreamInfo() @@ -584,7 +591,7 @@ unique_ptr Mp4Track::parseAudioSpecificConfig(Mpeg4Des using namespace Mpeg4AudioObjectIds; // read config into buffer and construct BitReader for bitwise reading m_istream->seekg(decSpecInfoDesc->dataOffset()); - cout << "audio cfg @" << decSpecInfoDesc->dataOffset() << endl; + //cout << "audio cfg @" << decSpecInfoDesc->dataOffset() << endl; auto buff = make_unique(decSpecInfoDesc->dataSize()); m_istream->read(buff.get(), decSpecInfoDesc->dataSize()); BitReader bitReader(buff.get(), decSpecInfoDesc->dataSize()); @@ -670,6 +677,11 @@ unique_ptr Mp4Track::parseAudioSpecificConfig(Mpeg4Des case ErBsac: case ErAacLd: case ErCelp: case ErHvxc: case ErHiln: case ErParametric: case ErAacEld: switch(audioCfg->epConfig = bitReader.readBits(2)) { + case 2: + break; + case 3: + bitReader.skipBits(1); + break; default: throw NotImplementedException(); // TODO } @@ -690,21 +702,92 @@ unique_ptr Mp4Track::parseAudioSpecificConfig(Mpeg4Des } } } + } else if (syncExtensionType == 0x548) { + audioCfg->psPresent = bitReader.readBits(1); } } } catch(ios_base::failure &) { if(m_istream->fail()) { - throw; // IO error caused by input stream + // IO error caused by input stream + throw; + } else { + // IO error caused by bitReader + addNotification(NotificationType::Critical, "Audio specific configuration is truncated.", context); } - // IO error caused by bitReader - addNotification(NotificationType::Critical, "Audio specific configuration is truncated.", context); } catch(NotImplementedException &) { addNotification(NotificationType::Information, "Not implemented for the format of audio track.", context); } return audioCfg; } - /*! +/*! + * \brief Parses the video specific configuration for the track. + * \remarks + * - Notifications might be added. + * \sa mpeg4ElementaryStreamInfo() + */ +std::unique_ptr Mp4Track::parseVideoSpecificConfig(Mpeg4Descriptor *decSpecInfoDesc) +{ + static const string context("parsing MPEG-4 video specific config from elementary stream descriptor"); + using namespace Mpeg4AudioObjectIds; + auto videoCfg = make_unique(); + // seek to start + m_istream->seekg(decSpecInfoDesc->dataOffset()); + uint64 bytesRemaining = decSpecInfoDesc->dataSize(); + if(bytesRemaining > 3 && (m_reader.readUInt24BE() == 1)) { + bytesRemaining -= 3; + uint32 buff1; + while(bytesRemaining) { + --bytesRemaining; + switch(m_reader.readByte()) { // read start code + case Mpeg4VideoCodes::VisualObjectSequenceStart: + if(bytesRemaining) { + videoCfg->profile = m_reader.readByte(); + --bytesRemaining; + } + break; + case Mpeg4VideoCodes::VideoObjectLayerStart: + + break; + case Mpeg4VideoCodes::UserDataStart: + buff1 = 0; + while(bytesRemaining >= 3) { + if((buff1 = m_reader.readUInt24BE()) != 1) { + m_istream->seekg(-2, ios_base::cur); + videoCfg->userData.push_back(buff1 >> 16); + --bytesRemaining; + } else { + bytesRemaining -= 3; + break; + } + } + if(buff1 != 1 && bytesRemaining > 0) { + videoCfg->userData += m_reader.readString(bytesRemaining); + bytesRemaining = 0; + } + break; + default: + ; + } + // skip stuff we're not interested in to get the start of the + // next video object + while(bytesRemaining >= 3) { + if(m_reader.readUInt24BE() != 1) { + m_istream->seekg(-2, ios_base::cur); + --bytesRemaining; + } else { + bytesRemaining -= 3; + break; + } + } + } + } else { + addNotification(NotificationType::Critical, "\"Visual Object Sequence Header\" not found.", context); + } + return videoCfg; +} + +/*! * \brief Updates the chunk offsets of the track. This is necessary when the mdat atom (which contains * the actual chunk data) is moved. * \param oldMdatOffsets Specifies a vector holding the old offsets of the "mdat"-atoms. @@ -796,8 +879,9 @@ void Mp4Track::updateChunkOffset(uint32 chunkIndex, uint64 offset) } /*! - * \brief Makes the track entry (trak atom) for the track. The data is written to the assigned output stream + * \brief Makes the track entry ("trak"-atom) for the track. The data is written to the assigned output stream * at the current position. + * \remarks Currently the "trak"-atom from the source file is just copied to the output stream. */ void Mp4Track::makeTrack() { @@ -1053,49 +1137,49 @@ void Mp4Track::internalParseHeader() static const string context("parsing MP4 track"); using namespace Mp4AtomIds; if(!m_trakAtom) { - addNotification(NotificationType::Critical, "Trak atom is null.", context); + addNotification(NotificationType::Critical, "\"trak\"-atom is null.", context); throw InvalidDataException(); } // get atoms try { if(!(m_tkhdAtom = m_trakAtom->childById(TrackHeader))) { - addNotification(NotificationType::Critical, "No tkhd atom found.", context); + addNotification(NotificationType::Critical, "No \"tkhd\"-atom found.", context); throw InvalidDataException(); } if(!(m_mdiaAtom = m_trakAtom->childById(Media))) { - addNotification(NotificationType::Critical, "No mdia atom found.", context); + addNotification(NotificationType::Critical, "No \"mdia\"-atom found.", context); throw InvalidDataException(); } if(!(m_mdhdAtom = m_mdiaAtom->childById(MediaHeader))) { - addNotification(NotificationType::Critical, "No mdhd atom found.", context); + addNotification(NotificationType::Critical, "No \"mdhd\"-atom found.", context); throw InvalidDataException(); } if(!(m_hdlrAtom = m_mdiaAtom->childById(HandlerReference))) { - addNotification(NotificationType::Critical, "No hdlr atom found.", context); + addNotification(NotificationType::Critical, "No \"hdlr\"-atom found.", context); throw InvalidDataException(); } if(!(m_minfAtom = m_mdiaAtom->childById(MediaInformation))) { - addNotification(NotificationType::Critical, "No minf atom found.", context); + addNotification(NotificationType::Critical, "No \"minf\"-atom found.", context); throw InvalidDataException(); } if(!(m_stblAtom = m_minfAtom->childById(SampleTable))) { - addNotification(NotificationType::Critical, "No stbl atom found.", context); + addNotification(NotificationType::Critical, "No \"stbl\"-atom found.", context); throw InvalidDataException(); } if(!(m_stsdAtom = m_stblAtom->childById(SampleDescription))) { - addNotification(NotificationType::Critical, "No stsd atom found.", context); + addNotification(NotificationType::Critical, "No \"stsd\"-atom found.", context); throw InvalidDataException(); } if(!(m_stcoAtom = m_stblAtom->childById(ChunkOffset)) && !(m_stcoAtom = m_stblAtom->childById(ChunkOffset64))) { - addNotification(NotificationType::Critical, "No stco/co64 atom found.", context); + addNotification(NotificationType::Critical, "No \"stco\"/\"co64\"-atom found.", context); throw InvalidDataException(); } if(!(m_stscAtom = m_stblAtom->childById(SampleToChunk))) { - addNotification(NotificationType::Critical, "No stsc atom found.", context); + addNotification(NotificationType::Critical, "No \"stsc\"-atom found.", context); throw InvalidDataException(); } if(!(m_stszAtom = m_stblAtom->childById(SampleSize)) && !(m_stszAtom = m_stblAtom->childById(CompactSampleSize))) { - addNotification(NotificationType::Critical, "No stsz/stz2 atom found.", context); + addNotification(NotificationType::Critical, "No \"stsz\"/\"stz2\"-atom found.", context); throw InvalidDataException(); } } catch(Failure &) { @@ -1122,7 +1206,7 @@ void Mp4Track::internalParseHeader() m_id = reader.readUInt32BE(); break; default: - addNotification(NotificationType::Critical, "Version of tkhd atom not supported. It will be ignored. Track ID, creation time and modification time might not be be determined.", context); + addNotification(NotificationType::Critical, "Version of \"tkhd\"-atom not supported. It will be ignored. Track ID, creation time and modification time might not be be determined.", context); m_creationTime = DateTime(); m_modificationTime = DateTime(); m_id = 0; @@ -1145,7 +1229,7 @@ void Mp4Track::internalParseHeader() m_duration = TimeSpan::fromSeconds(static_cast(reader.readUInt64BE()) / static_cast(m_timeScale)); break; default: - addNotification(NotificationType::Warning, "Version of mdhd atom not supported. It will be ignored. Creation time, modification time, time scale and duration might not be determined.", context); + addNotification(NotificationType::Warning, "Version of \"mdhd\"-atom not supported. It will be ignored. Creation time, modification time, time scale and duration might not be determined.", context); m_timeScale = 0; m_duration = TimeSpan(); } @@ -1179,24 +1263,92 @@ void Mp4Track::internalParseHeader() m_istream->seekg(m_stcoAtom->dataOffset() + 4); m_chunkCount = reader.readUInt32BE(); // read stsd atom - m_istream->seekg(m_stsdAtom->startOffset() + 12); // seek to beg, skip size, name, version and flags + m_istream->seekg(m_stsdAtom->dataOffset() + 4); // seek to beg, skip size, name, version and flags uint32 entryCount = reader.readUInt32BE(); - Mp4Atom *codecConfigContainerAtom; - string::size_type firstZeroByte; + Mp4Atom *esDescParentAtom = nullptr; + uint16 tmp; if(entryCount > 0) { - // read only first entry - if((codecConfigContainerAtom = m_stsdAtom->firstChild())) { - try { + try { + for(Mp4Atom *codecConfigContainerAtom = m_stsdAtom->firstChild(); codecConfigContainerAtom; codecConfigContainerAtom = codecConfigContainerAtom->nextSibling()) { codecConfigContainerAtom->parse(); // parse FOURCC m_formatId = interpretIntegerAsString(codecConfigContainerAtom->id()); m_format = FourccIds::fourccToMediaFormat(codecConfigContainerAtom->id()); - // parse AVC configuration - //codecConfigContainerAtom->childById(Mp4AtomIds::AvcConfiguration); - // parse MPEG-4 elementary stream descriptor - Mp4Atom *esDescAtom = codecConfigContainerAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor); + // parse codecConfigContainerAtom + m_istream->seekg(codecConfigContainerAtom->dataOffset()); + switch(codecConfigContainerAtom->id()) { + case FourccIds::Mpeg4Audio: case FourccIds::AmrNarrowband: case FourccIds::Amr: + case FourccIds::Drms: case FourccIds::Alac: case FourccIds::WindowsMediaAudio: + case FourccIds::Ac3: case FourccIds::EAc3: case FourccIds::DolbyMpl: + case FourccIds::Dts: case FourccIds::DtsH: case FourccIds::DtsE: + m_istream->seekg(6 + 2, ios_base::cur); // skip reserved bytes, data reference index + tmp = reader.readUInt16BE(); // read sound version + m_istream->seekg(6, ios_base::cur); + m_channelCount = reader.readUInt16BE(); + m_bitsPerSample = reader.readUInt16BE(); + m_istream->seekg(4, ios_base::cur); // skip reserved bytes (again) + if(!m_sampleRate) { + m_sampleRate = reader.readUInt32BE() >> 16; + if(codecConfigContainerAtom->id() != FourccIds::DolbyMpl) { + m_sampleRate >>= 16; + } + } else { + m_istream->seekg(4, ios_base::cur); + } + if(codecConfigContainerAtom->id() != FourccIds::WindowsMediaAudio) { + switch(tmp) { + case 1: + codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 16); + break; + case 2: + codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28 + 32); + break; + default: + codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 28); + } + if(!esDescParentAtom) { + esDescParentAtom = codecConfigContainerAtom; + } + } + break; + case FourccIds::Mpeg4Video: case FourccIds::H263Quicktime: case FourccIds::H2633GPP: + case FourccIds::Avc1: case FourccIds::Avc2: case FourccIds::Avc3: case FourccIds::Avc4: + case FourccIds::Drmi: case FourccIds::Hevc1: case FourccIds::Hevc2: + m_istream->seekg(6 + 2 + 16, ios_base::cur); // skip reserved bytes, data reference index, and reserved bytes (again) + m_pixelSize.setWidth(reader.readUInt16BE()); + m_pixelSize.setHeight(reader.readUInt16BE()); + m_resolution.setWidth(static_cast(reader.readFixed16BE())); + m_resolution.setHeight(static_cast(reader.readFixed16BE())); + m_istream->seekg(4, ios_base::cur); // skip reserved bytes + m_framesPerSample = reader.readUInt16BE(); + tmp = reader.readByte(); + m_compressorName = reader.readString(31); + if(tmp == 0) { + m_compressorName.clear(); + } else if(tmp < 32) { + m_compressorName.resize(tmp); + } + m_depth = reader.readUInt16BE(); // 24: color without alpha + codecConfigContainerAtom->denoteFirstChild(codecConfigContainerAtom->headerSize() + 78); + if(!esDescParentAtom) { + esDescParentAtom = codecConfigContainerAtom; + } + break; + case Mp4AtomIds::PixalAspectRatio: + break; // TODO + case Mp4AtomIds::CleanAperature: + break; // TODO + default: + ; + } + } + // parse AVC configuration + //codecConfigContainerAtom->childById(Mp4AtomIds::AvcConfiguration); + // parse MPEG-4 elementary stream descriptor + if(esDescParentAtom) { + Mp4Atom *esDescAtom = esDescParentAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor); if(!esDescAtom) { - esDescAtom = codecConfigContainerAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor2); + esDescAtom = esDescParentAtom->childById(Mp4FormatExtensionIds::Mpeg4ElementaryStreamDescriptor2); } if(esDescAtom) { try { @@ -1222,6 +1374,15 @@ void Mp4Track::internalParseHeader() addNotification(NotificationType::Warning, "Audio specific config has invalid extension sample frequency index.", context); } } + if(m_esInfo->videoSpecificConfig) { + // check the video specific config for useful information + if(m_format.general == GeneralMediaFormat::Mpeg4Video && m_esInfo->videoSpecificConfig->profile) { + m_format.sub = m_esInfo->videoSpecificConfig->profile; + if(!m_esInfo->videoSpecificConfig->userData.empty()) { + m_formatId += " / " + m_esInfo->videoSpecificConfig->userData; + } + } + } // check the stream data for missing information switch(m_format.general) { case GeneralMediaFormat::Mpeg1Audio: case GeneralMediaFormat::Mpeg2Audio: { @@ -1238,73 +1399,14 @@ void Mp4Track::internalParseHeader() } catch(Failure &) { } } - // seek to start offset of additional atom and skip reserved bytes and data reference index - m_istream->seekg(codecConfigContainerAtom->startOffset() + 8 + 6 + 2); - switch(m_mediaType) { - case MediaType::Audio: - m_istream->seekg(8, ios_base::cur); // skip reserved bytes - m_channelCount = reader.readUInt16BE(); - m_bitsPerSample = reader.readUInt16BE(); - m_istream->seekg(4, ios_base::cur); // skip reserved bytes - if(!m_sampleRate) { - m_sampleRate = reader.readUInt32BE() >> 16; - } else { - m_istream->seekg(4, ios_base::cur); - } - break; - case MediaType::Video: - m_istream->seekg(16, ios_base::cur); // skip reserved bytes - m_pixelSize.setWidth(reader.readUInt16BE()); - m_pixelSize.setHeight(reader.readUInt16BE()); - m_resolution.setWidth(reader.readUInt32BE()); - m_resolution.setHeight(reader.readUInt32BE()); - m_istream->seekg(4, ios_base::cur); // skip reserved bytes - m_framesPerSample = reader.readUInt16BE(); - m_compressorName = reader.readString(30); - firstZeroByte = m_compressorName.find('\0'); - if(firstZeroByte == 0) { - m_compressorName.clear(); - } else if(firstZeroByte != string::npos) { - m_compressorName.resize(firstZeroByte - 1); - } - m_depth = reader.readUInt16BE(); - if(m_depth == 0x0018) { - // images are in color with no alpha - } else { - m_depth = 0; - } - codecConfigContainerAtom = codecConfigContainerAtom->nextSibling(); - if(codecConfigContainerAtom) { - while(codecConfigContainerAtom) { - codecConfigContainerAtom->parse(); - switch(codecConfigContainerAtom->id()) { - case Mp4AtomIds::PixalAspectRatio: - break; // todo - case Mp4AtomIds::CleanAperature: - break; // todo - default: - ; - } - codecConfigContainerAtom = codecConfigContainerAtom->nextSibling(); - } - codecConfigContainerAtom = codecConfigContainerAtom->siblingById(Mp4AtomIds::Drms, true); - if(codecConfigContainerAtom) { - m_encrypted = true; - } - } - break; - default: - ; - } - } catch(Failure &) { - addNotification(NotificationType::Warning, "Unable to parse child atoms of stsd atom correctly.", context); } + } catch (Failure &) { + addNotification(NotificationType::Critical, "Unable to parse child atoms of \"stsd\"-atom.", context); } } // read stsz atom which holds the sample size table m_sampleSizes.clear(); - m_size = 0; - m_sampleCount = 0; + m_size = m_sampleCount = 0; uint64 actualSampleSizeTableSize = m_stszAtom->dataSize(); if(actualSampleSizeTableSize < 12) { addNotification(NotificationType::Critical, "The stsz atom is truncated. There are no sample sizes present. The size of the track can not be determined.", context); @@ -1374,15 +1476,12 @@ void Mp4Track::internalParseHeader() } } // no sample sizes found, search for trun atoms - Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingById(MovieFragment, true); uint64 totalDuration = 0; - while(moofAtom) { + for(Mp4Atom *moofAtom = m_trakAtom->container().firstElement()->siblingById(MovieFragment, true); moofAtom; moofAtom = moofAtom->siblingById(MovieFragment, false)) { moofAtom->parse(); - Mp4Atom *trafAtom = moofAtom->childById(TrackFragment); - while(trafAtom) { + for(Mp4Atom *trafAtom = moofAtom->childById(TrackFragment); trafAtom; trafAtom = trafAtom->siblingById(TrackFragment, false)) { trafAtom->parse(); - Mp4Atom *tfhdAtom = trafAtom->childById(TrackFragmentHeader); - while(tfhdAtom) { + for(Mp4Atom *tfhdAtom = trafAtom->childById(TrackFragmentHeader); tfhdAtom; tfhdAtom = tfhdAtom->siblingById(TrackFragmentHeader, false)) { tfhdAtom->parse(); uint32 calculatedDataSize = 0; if(tfhdAtom->dataSize() < calculatedDataSize) { @@ -1434,8 +1533,7 @@ void Mp4Track::internalParseHeader() m_istream->seekg(4, ios_base::cur); } } - Mp4Atom *trunAtom = trafAtom->childById(TrackFragmentRun); - while(trunAtom) { + for(Mp4Atom *trunAtom = trafAtom->childById(TrackFragmentRun); trunAtom; trunAtom = trunAtom->siblingById(TrackFragmentRun, false)) { uint32 calculatedDataSize = 8; if(trunAtom->dataSize() < calculatedDataSize) { addNotification(NotificationType::Critical, "trun atom is truncated.", context); @@ -1495,18 +1593,14 @@ void Mp4Track::internalParseHeader() } } } - trunAtom = trunAtom->siblingById(TrackFragmentRun, false); } if(m_sampleSizes.empty() && defaultSampleSize) { m_sampleSizes.push_back(defaultSampleSize); } } } - tfhdAtom = tfhdAtom->siblingById(TrackFragmentHeader, false); } - trafAtom = trafAtom->siblingById(TrackFragment, false); } - moofAtom = moofAtom->siblingById(MovieFragment, false); } // set duration from "trun-information" if the duration has not been determined yet if(m_duration.isNull() && totalDuration) { @@ -1515,7 +1609,7 @@ void Mp4Track::internalParseHeader() timeScale = trakAtom().container().timeScale(); } if(timeScale) { - m_duration = TimeSpan::fromSeconds(static_cast(totalDuration) / static_cast(m_timeScale)); + m_duration = TimeSpan::fromSeconds(static_cast(totalDuration) / static_cast(timeScale)); } } // caluculate average bitrate diff --git a/mp4/mp4track.h b/mp4/mp4track.h index 0c2cea9..3fd0f28 100644 --- a/mp4/mp4track.h +++ b/mp4/mp4track.h @@ -40,6 +40,15 @@ public: byte epConfig; }; +class LIB_EXPORT Mpeg4VideoSpecificConfig +{ +public: + Mpeg4VideoSpecificConfig(); + + byte profile; + std::string userData; +}; + class LIB_EXPORT Mpeg4ElementaryStreamInfo { public: @@ -63,6 +72,7 @@ public: uint32 maxBitrate; uint32 averageBitrate; std::unique_ptr audioSpecificConfig; + std::unique_ptr videoSpecificConfig; }; inline Mpeg4ElementaryStreamInfo::Mpeg4ElementaryStreamInfo() : @@ -126,6 +136,7 @@ public: AvcConfiguration parseAvcConfiguration(Mp4Atom *avcConfigAtom); std::unique_ptr parseMpeg4ElementaryStreamInfo(Mp4Atom *esDescAtom); std::unique_ptr parseAudioSpecificConfig(Mpeg4Descriptor *decSpecInfoDesc); + std::unique_ptr parseVideoSpecificConfig(Mpeg4Descriptor *decSpecInfoDesc); // methods to read the "index" (chunk offsets and sizes) std::vector readChunkOffsets();