/* Copyright (c) MediaArea.net SARL. All Rights Reserved. * * Use of this source code is governed by a BSD-style license that can * be found in the License.html file in the root of the source tree. */ //--------------------------------------------------------------------------- // Pre-compilation #include "MediaInfo/PreComp.h" #ifdef __BORLANDC__ #pragma hdrstop #endif //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #include "MediaInfo/Setup.h" //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #if defined(MEDIAINFO_AAC_YES) || defined(MEDIAINFO_MPEGH3DA_YES) //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- #include "MediaInfo/Audio/File_Usac.h" #include "MediaInfo/Audio/File_Aac_GeneralAudio.h" #include "MediaInfo/Audio/File_Aac_GeneralAudio_Sbr.h" #include #include using namespace std; #define ARITH_ESCAPE 16 //--------------------------------------------------------------------------- namespace MediaInfoLib { //*************************************************************************** // Tables //*************************************************************************** struct stts_struct { int32u SampleCount; int32u SampleDuration; }; struct sgpd_prol_struct { int16s roll_distance; }; struct sbgp_struct { int64u FirstSample; int64u LastSample; int32u group_description_index; bool operator==(int64u SamplePos) const { return SamplePos>=FirstSample && SamplePos>= 2; if (Pos>240) { #if MEDIAINFO_CONFORMANCE Fill_Conformance("UsacCoreCoderData GeneralCompliance", "Issue detected while parsing hcod_sf"); #endif C.IFrameParsed=false; Element_End0(); return; } } Element_Info1(huffman_scl[Pos][0]-60); Element_End0(); } //---------------------------------------------------------------------------- int32u File_Usac::arith_decode(int16u& low, int16u& high, int16u& value, const int16u* cf, int32u cfl, size_t* TooMuch) { int32u range=(int32u)(high-low+1), value32=value; int32u cm=((((int)(value32-low+1))<<14)-((int)1))/range; const int16u* p=cf-1; do { const int16u* q=p+(cfl>>1); if (*q>cm) { p=q; cfl++; } cfl>>=1; } while (cfl>1); int32u symbol=(int32u)(p-cf+1); if (symbol) high=low+((range*cf[symbol-1])>>14)-1; low+=(range*cf[symbol])>>14; for (;;) { if (high&0x8000) { if (!(low&0x8000)) { if (low&0x4000 && !(high&0x4000)) { low-=0x4000; high-=0x4000; value32-=0x4000; } else break; } } low=low<<1; high=(high<<1) | 1; value32<<=1; if (Data_BS_Remain()) { bool bit; Get_SB (bit, "arith_data"); value32|=(int)bit; } else { (*TooMuch)++; } } value=(int16u)value32; return symbol; } //--------------------------------------------------------------------------- int16u File_Usac::sbr_huff_dec(const int8s(*Table)[2], const char* Name) { int8u bit; int8s index=0; Element_Begin1(Name); while (index>=0) { Get_S1(1, bit, "bit"); index=Table[index][bit]; } Element_End0(); return index+64; } //--------------------------------------------------------------------------- int16s File_Usac::huff_dec_1D(const int16s (*Table)[2], const char* Name) { Element_Begin1(Name); int8u bit; int16s index=0; do { Get_S1(1, bit, "bit"); index=Table[index][bit]; } while (index>0); Element_End0(); return index; } //--------------------------------------------------------------------------- bool File_Usac::huff_dec_2D(const int16s (*Table)[2], int8s (&aTmp)[2], const char* Name) { int16s index=huff_dec_1D(Table, Name); if (index==0) //Escape { aTmp[0]=0; aTmp[1]=1; return true; } index=-(index+1); aTmp[0]=index>>4; aTmp[1]=index&0xf; return false; } //*********************************************************************** // BS_Bookmark //*********************************************************************** //--------------------------------------------------------------------------- File_Usac::bs_bookmark File_Usac::BS_Bookmark(size_t NewSize) { bs_bookmark B; auto RemainingSize=Data_BS_Remain(); #if !MEDIAINFO_TRACE size_t BS_Size=(Element_Size-Element_Offset)*8; #endif //MEDIAINFO_TRACE auto AlreadyParsed=BS_Size-RemainingSize; if (NewSize>RemainingSize) NewSize=RemainingSize; B.Element_Offset=Element_Offset; B.Element_Size=Element_Size; B.Trusted=Trusted; B.UnTrusted=Element[Element_Level].UnTrusted; B.End=RemainingSize-NewSize; B.BitsNotIncluded=B.End%8; B.NewSize=NewSize+B.BitsNotIncluded; BS->Resize(B.NewSize); BS_Size=AlreadyParsed+B.NewSize; Element_Size=Element_Offset+(BS_Size+7)/8; #if MEDIAINFO_CONFORMANCE for (size_t Level=0; LevelB.BitsNotIncluded) { int8u LastByte; auto BitsRemaining=Data_BS_Remain()-B.BitsNotIncluded; if (BitsRemaining<8) { //Peek_S1((int8u)BitsRemaining, LastByte); //#if MEDIAINFO_CONFORMANCE // if (LastByte) // Fill_Conformance((ConformanceFieldName+" GeneralCompliance").c_str(), "Padding bits are not 0, the bitstream may be malformed", bitset8(), Warning); //#endif LastByte=0; } else { #if MEDIAINFO_CONFORMANCE bool IsZeroed=false; if (BitsRemaining<=32) //TODO: more than 32 bits { int32u Probe; Peek_S4(BitsRemaining, Probe); IsZeroed=!Probe; } if (IsZeroed) Fill_Conformance((ConformanceFieldName+" GeneralCompliance").c_str(), "Extra zero bytes after the end of the syntax was reached", bitset8(), Warning); else Fill_Conformance((ConformanceFieldName+" GeneralCompliance").c_str(), "Extra bytes after the end of the syntax was reached", bitset8(), Warning); #endif LastByte=1; } Skip_BS(BitsRemaining, LastByte?"Unknown":"Padding"); } else if (C.IFrameParsed && Data_BS_Remain()Resize(B.End); Element_Offset=B.Element_Offset; Element_Size=B.Element_Size; #if MEDIAINFO_TRACE BS_Size=(Element_Size-Element_Offset)*8; #endif //MEDIAINFO_TRACE Trusted=B.Trusted; Element[Element_Level].UnTrusted=B.UnTrusted; return IsNotValid; } //*********************************************************************** // Conformance //*********************************************************************** //--------------------------------------------------------------------------- #if MEDIAINFO_CONFORMANCE void File_Usac::Fill_Conformance(const char* Field, const char* Value, bitset8 Flags, conformance_level Level) { if (Level == Warning && Warning_Error) Level = Error; field_value FieldValue(Field, Value, Flags, (int64u)-1, IsParsingRaw>=2?(IsParsingRaw-2):(int64u)-1); auto& Conformance = ConformanceErrors[Level]; auto Current = find(Conformance.begin(), Conformance.end(), FieldValue); if (Current != Conformance.end()) return; Conformance.emplace_back(FieldValue); } #endif //--------------------------------------------------------------------------- #if MEDIAINFO_CONFORMANCE void File_Usac::Clear_Conformance() { for (size_t Level = 0; Level < ConformanceLevel_Max; Level++) { auto& Conformance = ConformanceErrors[Level]; Conformance.clear(); } } #endif //--------------------------------------------------------------------------- #if MEDIAINFO_CONFORMANCE void File_Usac::Merge_Conformance(bool FromConfig) { for (size_t Level = 0; Level < ConformanceLevel_Max; Level++) { auto& Conformance = ConformanceErrors[Level]; auto& Conformance_Total = ConformanceErrors_Total[Level]; for (const auto& FieldValue : Conformance) { auto Current = find(Conformance_Total.begin(), Conformance_Total.end(), FieldValue); if (Current != Conformance_Total.end()) { if (Current->FramePoss.size() < 8) { if (FromConfig) { if (Current->FramePoss.empty() || Current->FramePoss[0].Main != (int64u)-1) Current->FramePoss.insert(Current->FramePoss.begin(), {(int64u)-1, (int64u)-1}); } else Current->FramePoss.push_back({Frame_Count_NotParsedIncluded, FieldValue.FramePoss[0].Sub}); } else if (Current->FramePoss.size() == 8) Current->FramePoss.push_back({(int64u)-1, (int64u)-1}); //Indicating "..." continue; } if (!CheckIf(FieldValue.Flags)) continue; Conformance_Total.push_back(FieldValue); if (!FromConfig) Conformance_Total.back().FramePoss.front() = {Frame_Count_NotParsedIncluded, FieldValue.FramePoss[0].Sub}; } Conformance.clear(); } } #endif //--------------------------------------------------------------------------- #if MEDIAINFO_CONFORMANCE const profilelevel_struct& Mpeg4_Descriptors_ToProfileLevel(int8u AudioProfileLevelIndication); void File_Usac::SetProfileLevel(int8u AudioProfileLevelIndication) { ProfileLevel = Mpeg4_Descriptors_ToProfileLevel(AudioProfileLevelIndication); switch (ProfileLevel.profile) { case Baseline_USAC : ConformanceFlags.set(BaselineUsac); break; case Extended_HE_AAC : ConformanceFlags.set(xHEAAC); break; default:; } } #endif //--------------------------------------------------------------------------- #if MEDIAINFO_CONFORMANCE void File_Usac::Streams_Finish_Conformance_Profile(usac_config& CurrentConf) { if (ProfileLevel.profile == UnknownAudio) { auto AudioProfileLevelIndication = MediaInfoLib::Config.UsacProfile(); if (!AudioProfileLevelIndication) SetProfileLevel(AudioProfileLevelIndication); else if (!IsSub) ConformanceFlags.set(xHEAAC); // TODO: remove this check (initially done for LATM without the profile option) } if (ConformanceFlags[xHEAAC] && ProfileLevel.profile == Extended_HE_AAC && ProfileLevel.level > 1 && ProfileLevel.level <= 5) { if (CurrentConf.sampling_frequency && xHEAAC_Constraints[ProfileLevel.level].MaxSamplingRate) { int32u MaxSamplingRate = 24000 << (xHEAAC_Constraints[ProfileLevel.level].MaxSamplingRate - 1); if (CurrentConf.sampling_frequency > MaxSamplingRate) Fill_Conformance("Crosscheck InitialObjectDescriptor audioProfileLevelIndication", ("MP4 InitialObjectDescriptor audioProfileLevelIndication " + Mpeg4_Descriptors_AudioProfileLevelString(ProfileLevel) + " does not permit USAC UsacConfig usacSamplingFrequency " + to_string(CurrentConf.sampling_frequency) + ", max is " + to_string(MaxSamplingRate)).c_str()); } else if (CurrentConf.sampling_frequency > 48000) Fill_Conformance("Crosscheck InitialObjectDescriptor audioProfileLevelIndication", ("MP4 InitialObjectDescriptor audioProfileLevelIndication " + Mpeg4_Descriptors_AudioProfileLevelString(ProfileLevel) + " does not permit USAC UsacConfig usacSamplingFrequency " + to_string(CurrentConf.sampling_frequency) + ", max is 48000").c_str()); else if (CurrentConf.sampling_frequency_index < Aac_sampling_frequency_Size && Aac_sampling_frequency[CurrentConf.sampling_frequency_index] == CurrentConf.sampling_frequency && ((CurrentConf.sampling_frequency_index < 0x03 || CurrentConf.sampling_frequency_index > 0x0C) && (CurrentConf.sampling_frequency_index < 0x11 || CurrentConf.sampling_frequency_index > 0x1B))) Fill_Conformance("Crosscheck InitialObjectDescriptor audioProfileLevelIndication", ("MP4 InitialObjectDescriptor audioProfileLevelIndication " + Mpeg4_Descriptors_AudioProfileLevelString(ProfileLevel) + " does not permit USAC UsacConfig usacSamplingFrequency " + to_string(CurrentConf.sampling_frequency)).c_str()); if (!CurrentConf.channelConfigurationIndex) { if (CurrentConf.numOutChannels && CurrentConf.numOutChannels > xHEAAC_Constraints[ProfileLevel.level].MaxChannels) Fill_Conformance("Crosscheck InitialObjectDescriptor audioProfileLevelIndication", ("MP4 InitialObjectDescriptor audioProfileLevelIndication " + Mpeg4_Descriptors_AudioProfileLevelString(ProfileLevel) + " does not permit USAC UsacConfig numOutChannels " + to_string(CurrentConf.numOutChannels) + ", max is " + to_string(xHEAAC_Constraints[ProfileLevel.level].MaxChannels)).c_str()); } else { switch (CurrentConf.channelConfigurationIndex) { case 1 : case 2 : case 8 : break; default: Fill_Conformance("Crosscheck InitialObjectDescriptor audioProfileLevelIndication", ("MP4 InitialObjectDescriptor audioProfileLevelIndication " + Mpeg4_Descriptors_AudioProfileLevelString(ProfileLevel) + " does not permit USAC UsacConfig channelConfigurationIndex " + to_string(CurrentConf.channelConfigurationIndex)).c_str()); } } } if (IsCmaf && *IsCmaf && CurrentConf.channelConfigurationIndex != 1 && CurrentConf.channelConfigurationIndex != 2) Fill_Conformance("Crosscheck CMAF channelConfiguration", ("CMAF does not permit USAC UsacConfig channelConfigurationIndex " + to_string(CurrentConf.channelConfigurationIndex) + ", permitted values are 1 and 2").c_str()); } void File_Usac::Streams_Finish_Conformance() { Streams_Finish_Conformance_Profile(Conf); Merge_Conformance(true); for (size_t Level = 0; Level < ConformanceLevel_Max; Level++) { auto& Conformance_Total = ConformanceErrors_Total[Level]; if (Conformance_Total.empty()) continue; for (size_t i = Conformance_Total.size() - 1; i < Conformance_Total.size(); i--) { if (!CheckIf(Conformance_Total[i].Flags)) { Conformance_Total.erase(Conformance_Total.begin() + i); } } if (Conformance_Total.empty()) continue; string Conformance_String = "Conformance"; Conformance_String += ConformanceLevel_Strings[Level]; Fill(Stream_Audio, 0, Conformance_String.c_str(), Conformance_Total.size()); Conformance_String += ' '; for (const auto& ConformanceError : Conformance_Total) { size_t Space = 0; for (;;) { Space = ConformanceError.Field.find(' ', Space + 1); if (Space == string::npos) { break; } const auto Field = Conformance_String + ConformanceError.Field.substr(0, Space); const auto& Value = Retrieve_Const(StreamKind_Last, StreamPos_Last, Field.c_str()); if (Value.empty()) { Fill(StreamKind_Last, StreamPos_Last, Field.c_str(), "Yes"); } } auto Value = ConformanceError.Value; if (!ConformanceError.FramePoss.empty() && (ConformanceError.FramePoss.size() != 1 || ConformanceError.FramePoss[0].Main != (int64u)-1)) { auto HasConfError = ConformanceError.FramePoss[0].Main == (int64u)-1; Value += " ("; if (HasConfError) Value += "conf & "; Value += "frame"; if (ConformanceError.FramePoss.size() - HasConfError > 1) Value += 's'; Value += ' '; for (size_t i = HasConfError; i < ConformanceError.FramePoss.size(); i++) { auto FramePos = ConformanceError.FramePoss[i]; if (FramePos.Main == (int64u)-1) Value += "..."; else { Value += to_string(FramePos.Main); if (FramePos.Sub != (int64u)-1) { Value += '.'; Value += to_string(FramePos.Sub); } } Value += '+'; } Value.back() = ')'; } Fill(Stream_Audio, 0, (Conformance_String + ConformanceError.Field).c_str(), Value); } Conformance_Total.clear(); } } #endif //--------------------------------------------------------------------------- #if MEDIAINFO_CONFORMANCE void File_Usac::numPreRollFrames_Check(usac_config& CurrentConf, int32u numPreRollFrames, const string& numPreRollFramesConchString) { string FieldName = numPreRollFramesConchString.substr(numPreRollFramesConchString.rfind(' ') + 1); int numPreRollFrames_Max; if (CurrentConf.coreSbrFrameLengthIndex >= coreSbrFrameLengthIndex_Mapping_Size || coreSbrFrameLengthIndex_Mapping[CurrentConf.coreSbrFrameLengthIndex].sbrRatioIndex) { if (CurrentConf.harmonicSBR) numPreRollFrames_Max = 3; else numPreRollFrames_Max = 2; } else numPreRollFrames_Max = 1; if (numPreRollFrames != numPreRollFrames_Max) { auto Value = FieldName + " is " + to_string(numPreRollFrames) + " but "; if (numPreRollFrames > numPreRollFrames_Max) Value += "<= "; if (numPreRollFrames > 3) { Value += "3 is required"; } else { Value += to_string(numPreRollFrames_Max) + " is recommended"; if (CurrentConf.coreSbrFrameLengthIndex >= coreSbrFrameLengthIndex_Mapping_Size || coreSbrFrameLengthIndex_Mapping[CurrentConf.coreSbrFrameLengthIndex].sbrRatioIndex) { if (CurrentConf.harmonicSBR) { if (numPreRollFrames < numPreRollFrames_Max) Value += " due to SBR with harmonic patching"; } else Value += " due to SBR without harmonic patching"; } else Value += " due to no SBR"; } Fill_Conformance(numPreRollFramesConchString.c_str(), Value, bitset8(), numPreRollFrames > numPreRollFrames_Max ? Error : Warning); } } #endif //*************************************************************************** // Elements - USAC - Config //*************************************************************************** //--------------------------------------------------------------------------- void File_Usac::UsacConfig(size_t BitsNotIncluded) { // Init C = usac_config(); #if MEDIAINFO_CONFORMANCE C.Reset(); #endif Element_Begin1("UsacConfig"); bool usacConfigExtensionPresent; Get_S1 (5, C.sampling_frequency_index, "usacSamplingFrequencyIndex"); Param_Info1C(C.sampling_frequency_index bsOutChannelPos_List; #endif for (int32u i=0; i 1 && ProfileLevel.level <= 5) { if (C.numOutChannels == 2 && ActualOrder != "L R") Fill_Conformance("Crosscheck InitialObjectDescriptor audioProfileLevelIndication", ("MP4 InitialObjectDescriptor audioProfileLevelIndication " + Mpeg4_Descriptors_AudioProfileLevelString(ProfileLevel) + " implies a channel layout of L R, a channel layout of " + ActualOrder + " is not recommended").c_str(), bitset8(), Warning); } } if (!C.numOutChannels) Fill_Conformance("numOutChannels GeneralCompliance", "numOutChannels is 0"); else if (!ExpectedOrder.empty()) { if (ExpectedOrder != ActualOrder) Fill_Conformance("Crosscheck AudioSpecificConfig channelConfigurationIndex", ("MP4 AudioSpecificConfig channelConfigurationIndex " + to_string(channelConfiguration) + " (" + ExpectedOrder + ") does not match USAC UsacConfig channel mapping " + ActualOrder).c_str()); } #endif } else if (C.channelConfigurationIndex= coreSbrFrameLengthIndex_Mapping_Size) Fill_Conformance("UsacConfig coreSbrFrameLengthIndex", ("coreSbrFrameLengthIndex " + to_string(C.coreSbrFrameLengthIndex) + " is known as reserved in ISO/IEC 23003-3:2020, bitstream parsing is partial and may be wrong").c_str(), bitset8(), Info); #endif UsacDecoderConfig(); if (!C.IFrameParsed) { Element_End0(); return; } Get_SB (usacConfigExtensionPresent, "usacConfigExtensionPresent"); if (usacConfigExtensionPresent) UsacConfigExtension(); Element_End0(); if (BitsNotIncluded!=(size_t)-1) { if (Data_BS_Remain()>BitsNotIncluded) { int8u LastByte; auto BitsRemaining=Data_BS_Remain()-BitsNotIncluded; if (BitsRemaining<8) { //Peek_S1((int8u)BitsRemaining, LastByte); //#if MEDIAINFO_CONFORMANCE // if (LastByte) // Fill_Conformance((ConformanceFieldName+" GeneralCompliance").c_str(), "Padding bits are not 0, the bitstream may be malformed", bitset8(), Warning); //#endif LastByte=0; } else { #if MEDIAINFO_CONFORMANCE bool IsZeroed=false; if (BitsRemaining<=32) //TODO: more than 32 bits { int32u Probe; Peek_S4(BitsRemaining, Probe); IsZeroed=!Probe; } if (IsZeroed) Fill_Conformance("UsacConfig GeneralCompliance", "Extra zero bytes after the end of the syntax was reached", bitset8(), Warning); else Fill_Conformance("UsacConfig GeneralCompliance", "Extra bytes after the end of the syntax was reached", bitset8(), Warning); #endif LastByte=1; } Skip_BS(BitsRemaining, LastByte?"Unknown":"Padding"); } else if (C.IFrameParsed && Data_BS_Remain()