From 9a194d87e7eff8d29e59f519e6c2b124fbd23041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Martinez?= Date: Fri, 3 Jun 2022 08:38:49 +0200 Subject: [PATCH] Silent channels detection --- Source/CLI/CLI_Help.cpp | 6 ++ Source/CLI/CLI_Main.cpp | 2 +- Source/CLI/CommandLine_Parser.cpp | 4 + Source/CLI/LeaveSD.rc | 9 +- Source/Common/Config.h | 2 +- Source/Common/Core.cpp | 125 ++++++++++++++++++++++------ Source/Common/Core.h | 3 + Source/Templates/LeaveSD_Encode.txt | 2 +- Source/Templates/LeaveSD_Probe.txt | 1 + 9 files changed, 119 insertions(+), 35 deletions(-) create mode 100644 Source/Templates/LeaveSD_Probe.txt diff --git a/Source/CLI/CLI_Help.cpp b/Source/CLI/CLI_Help.cpp index 261bc30..47d3df3 100644 --- a/Source/CLI/CLI_Help.cpp +++ b/Source/CLI/CLI_Help.cpp @@ -61,6 +61,12 @@ return_value Help(ostream& Out, const char* Name, bool Full) " Use the old AAC format without extensions.\n" " By default HE-AAC (AAC with SBR extension) is used.\n" "\n" + " --keep-silent\n" + " Ignore detetion of silent (also the one with a bit of noise) channels\n" + " and encode them.\n" + " By default silent channels are discarded if last 7 channels are detected\n" + " as having no relevant content\n" + "\n" << endl; return ReturnValue_OK; diff --git a/Source/CLI/CLI_Main.cpp b/Source/CLI/CLI_Main.cpp index c243ab8..ccccc69 100644 --- a/Source/CLI/CLI_Main.cpp +++ b/Source/CLI/CLI_Main.cpp @@ -17,7 +17,7 @@ int main(int argc, const char* argv[]) { // Environment - setlocale(LC_ALL, ""); + setlocale(LC_ALL, "C"); // Configure Core C; diff --git a/Source/CLI/CommandLine_Parser.cpp b/Source/CLI/CommandLine_Parser.cpp index ab42c4f..31f3900 100644 --- a/Source/CLI/CommandLine_Parser.cpp +++ b/Source/CLI/CommandLine_Parser.cpp @@ -45,6 +45,10 @@ return_value Parse(Core& C, int argc, const char* argv_ansi[], LPCWSTR argv[]) { C.KeepTemp = true; } + else if (strcmp(argv_ansi[i], "--keep-silent") == 0) + { + C.KeepSilent = true; + } else if (!strcmp(argv_ansi[i], "--legacy-aac")) { C.LegacyAac = true; diff --git a/Source/CLI/LeaveSD.rc b/Source/CLI/LeaveSD.rc index a2894ad..e18e2e3 100644 --- a/Source/CLI/LeaveSD.rc +++ b/Source/CLI/LeaveSD.rc @@ -1,8 +1,8 @@ #include VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,1,4,0 - PRODUCTVERSION 0,1,4,0 + FILEVERSION 0,1,5,0 + PRODUCTVERSION 0,1,5,0 FILEFLAGSMASK 0x17L #ifdef _DEBUG FILEFLAGS 0x1L @@ -18,13 +18,12 @@ BEGIN BLOCK "040904b0" BEGIN VALUE "CompanyName", "MediaArea.net" - VALUE "FileDescription", "" - VALUE "FileVersion", "0,1,4,0" + VALUE "FileVersion", "0.1.5.0" VALUE "InternalName", "LeaveSD" VALUE "LegalCopyright", "MediaArea.net" VALUE "OriginalFilename", "LeaveSD.exe" VALUE "ProductName", "LeaveSD" - VALUE "ProductVersion", "0,1,4,0" + VALUE "ProductVersion", "0.1.5.0" END END BLOCK "VarFileInfo" diff --git a/Source/Common/Config.h b/Source/Common/Config.h index 6f7131e..ae01fa5 100644 --- a/Source/Common/Config.h +++ b/Source/Common/Config.h @@ -17,5 +17,5 @@ enum return_value //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- -#define Program_Version "0.1.4" +#define Program_Version "0.1.5" //--------------------------------------------------------------------------- diff --git a/Source/Common/Core.cpp b/Source/Common/Core.cpp index f045494..df5058e 100644 --- a/Source/Common/Core.cpp +++ b/Source/Common/Core.cpp @@ -335,31 +335,31 @@ void Core::Convert(size_t ID, size_t FilePos, bool FullCheck) } else HasVideo = true; - bool HasAudio; + int SourceChannelCount; ThreadData.ChannelCount = MI.Get(Stream_Audio, 0, __T("Channel(s)")); if (!MI.Count_Get(Stream_Audio) || MI.Get(Stream_Audio, 0, __T("Format_Version")).empty()) { EraseBeginEnd.push_back({ __T(",\r\n\"--default-track-flag\","), __T("_7.aac\"") }); WarningMessages.push_back("no audio detected"); - HasAudio = false; + SourceChannelCount = 0; } else if (ThreadData.ChannelCount == __T("1")) { WarningMessages.push_back("1-ch audio detected"); - HasAudio = true; + SourceChannelCount = 1; } else if (ThreadData.ChannelCount.empty() || ThreadData.ChannelCount == __T("8")) { if (ThreadData.ChannelCount.empty()) ThreadData.ChannelCount = __T("8"); // In practice files we got have a channel_configuration of 0 and in practice they have 8 channels - HasAudio = true; + SourceChannelCount = 8; } else { Data.Finished(Dest, { "Audio channel count not supported" }, {}); return; } - if (HasAudio && MI.Get(Stream_Audio, 0, __T("Format")) != __T("AAC")) + if (SourceChannelCount && MI.Get(Stream_Audio, 0, __T("Format")) != __T("AAC")) { Data.Finished(Dest, { "Only AAC audio is supported" }, {}); return; @@ -439,7 +439,7 @@ void Core::Convert(size_t ID, size_t FilePos, bool FullCheck) }; // Decode audio - if (HasAudio) + if (SourceChannelCount) { system(AdaptTemplate(__T("LeaveSD_Decode.txt")).c_str()); Data.Delete(TempNamePrefix + __T(".aac")); @@ -460,15 +460,76 @@ void Core::Convert(size_t ID, size_t FilePos, bool FullCheck) } // Encode audio - if (HasAudio) + int DestChannelCount = SourceChannelCount; + if (SourceChannelCount) { + // Probing silence + if (SourceChannelCount > 1 && !KeepSilent) + { + system(AdaptTemplate(__T("LeaveSD_Probe.txt"), {}, {}, {}).c_str()); + File ProbeF; + ProbeF.Open(TempNamePrefix + __T("_log_probe.txt")); + char* ProbeC = new char[ProbeF.Size_Get()]; + ProbeF.Read((int8u*)ProbeC, ProbeF.Size_Get()); + string Probe(ProbeC, ProbeF.Size_Get()); + delete[] ProbeC; + vector Probe_Levels, Probe_Peaks; + size_t Pos1 = (size_t)-1; + for (;;) + { + Pos1 = Probe.find(" RMS level dB: ", Pos1 + 1); + if (Pos1 == string::npos) + break; + Probe_Levels.push_back((float)atof(Probe.c_str() + Pos1 + 15)); + } + size_t Pos2 = (size_t)-1; + for (;;) + { + Pos2 = Probe.find(" RMS peak dB: ", Pos2 + 1); + if (Pos2 == string::npos) + break; + Probe_Peaks.push_back((float)atof(Probe.c_str() + Pos2 + 14)); + } + if (Probe_Levels.size() == 9 && Probe_Peaks.size() == 9) + { + float Level = -100; + float Peak = -100; + for (size_t i = 1; i < 8; i++) // Channel 0 and summary excluded + { + if (Level < Probe_Levels[i]) + Level = Probe_Levels[i]; + if (Peak < Probe_Peaks[i]) + Peak = Probe_Peaks[i]; + } + if (Level < SilenceLevel && Peak < SilencePeak) + { + if (Probe_Levels[0] < SilenceLevel || Probe_Peaks[0] < SilencePeak) + { + WarningMessages.push_back("silence detected in all channels"); + } + else + { + WarningMessages.push_back("silence detected in all channels but the first one" + " (RMS level " + to_string((int)(Level - 0.5)) + + " RMS peak " + to_string((int)(Peak - 0.5)) + ")" + " so 1-ch audio encoded"); + DestChannelCount = 1; + } + } + } + } + + // Encode EraseBeginEnd.clear(); Replace.clear(); - if (MI.Get(Stream_Audio, 0, __T("Channel(s)")) == __T("1")) + if (SourceChannelCount == 1) { - EraseBeginEnd.push_back({ __T(" -map_channel 0.0.1"), __T("7.aac\"") }); Replace.push_back({ __T("-ac 8"), __T("-ac 2") }); } + if (DestChannelCount == 1) + { + EraseBeginEnd.push_back({ __T(" -map_channel 0.0.1"), __T("7.aac\"") }); + } if (LegacyAac) { Replace.push_back({ __T(" -profile:a aac_he"), String() }); @@ -555,11 +616,11 @@ void Core::Convert(size_t ID, size_t FilePos, bool FullCheck) { EraseBeginEnd.push_back({ __T(",\r\n\"--sync\","), __T(".avc\"") }); } - if (!HasAudio) + if (!DestChannelCount) { EraseBeginEnd.push_back({ __T(",\r\n\"--sync\",\r\n\"0:%DELAY_A%\",\r\n\"--language\",\r\n\"0:mul"), __T("_7.aac\"") }); } - if (MI.Get(Stream_Audio, 0, __T("Channel(s)")) == __T("1")) + if (DestChannelCount == 1) { EraseBeginEnd.push_back({ __T(",\r\n\"--sync\",\r\n\"0:%DELAY_A%\",\r\n\"--language\",\r\n\"0:ara"), __T("_7.aac\"") }); } @@ -636,16 +697,22 @@ void Core::Convert(size_t ID, size_t FilePos, bool FullCheck) // Check ThreadData.IsChecking = true; - uint64_t PacketCount[2]; - uint64_t PacketCheckingCount[2]; + uint64_t PacketCount[2][8]; + uint64_t PacketCheckingCount[2][8]; uint64_t Duration, CheckingDuration; Duration = Ztring(MI.Get(Stream_General, 0, __T("Duration"))).To_int64u(); - PacketCount[0] = Ztring(MI.Get(Stream_Video, 0, __T("FrameCount"))).To_int64u(); - PacketCount[1] = Ztring(MI.Get(Stream_Audio, 0, __T("FrameCount"))).To_int64u(); + PacketCount[0][0] = Ztring(MI.Get(Stream_Video, 0, __T("FrameCount"))).To_int64u(); + for (size_t i = 0; i < DestChannelCount; i++) + { + PacketCount[1][i] = Ztring(MI.Get(Stream_Audio, i, __T("FrameCount"))).To_int64u(); + } MI.Open(Dest); CheckingDuration = Ztring(MI.Get(Stream_General, 0, __T("Duration"))).To_int64u(); - PacketCheckingCount[0] = Ztring(MI.Get(Stream_Video, 0, __T("FrameCount"))).To_int64u(); - PacketCheckingCount[1] = Ztring(MI.Get(Stream_Audio, 0, __T("FrameCount"))).To_int64u(); + PacketCheckingCount[0][0] = Ztring(MI.Get(Stream_Video, 0, __T("FrameCount"))).To_int64u(); + for (size_t i = 0; i < DestChannelCount; i++) + { + PacketCheckingCount[1][i] = Ztring(MI.Get(Stream_Audio, i, __T("FrameCount"))).To_int64u(); + } if (Duration && CheckingDuration) { uint64_t Ratio = Duration < 20000 ? 10 : 1; @@ -673,27 +740,31 @@ void Core::Convert(size_t ID, size_t FilePos, bool FullCheck) return to_string(Count) + " (" + out.str() + "%)"; }; bool LaunchFullCheck = false; - if (CheckingDuration == 0 || PacketCheckingCount[0] + PacketCheckingCount[1] == 0) + if (CheckingDuration == 0 || PacketCheckingCount[0][0] + PacketCheckingCount[1][0] == 0) { Data.Finished(Dest, { "can not read output file" }, {}); return; } vector ErrorMessages; - if (PacketCount[0] != PacketCheckingCount[0]) - ErrorMessages.push_back(WithPercent((int64_t)(PacketCount[0] - PacketCheckingCount[0]), PacketCount[0]) + " missing video packets"); - if (PacketCount[1] / (LegacyAac ? 1 : 2) != PacketCheckingCount[1] && PacketCheckingCount[1] + 10 < PacketCount[1] / (LegacyAac ? 1 : 2)) // Temporary: there is some small issues in MediaInfo counting + if (PacketCount[0][0] != PacketCheckingCount[0][0]) + ErrorMessages.push_back(WithPercent((int64_t)(PacketCount[0][0] - PacketCheckingCount[0][0]), PacketCount[0][0]) + " missing video packets"); + for (size_t i = 0; i < DestChannelCount; i++) { - ErrorMessages.push_back(WithPercent((int64_t)(PacketCount[1] - PacketCheckingCount[1]), PacketCount[1]) + " missing audio packets"); - LaunchFullCheck = true; + if (PacketCount[1][i] / (LegacyAac ? 1 : 2) != PacketCheckingCount[1][i] && PacketCheckingCount[1][i] + 10 < PacketCount[1][i] / (LegacyAac ? 1 : 2)) // Temporary: there is some small issues in MediaInfo counting + { + ErrorMessages.push_back(WithPercent((int64_t)(PacketCount[1][i] - PacketCheckingCount[1][i]), PacketCount[1][i]) + " missing audio packets"); + LaunchFullCheck = true; + break; + } } - if (HasAudio) + if (DestChannelCount) { if (!ThreadData.Stats_InvalidAudioPackets.empty()) - WarningMessages.push_back(WithPercent(ThreadData.Stats_InvalidAudioPackets.size(), PacketCount[1]) + " invalid AAC syncs in audio packet (skipped)"); + WarningMessages.push_back(WithPercent(ThreadData.Stats_InvalidAudioPackets.size(), PacketCount[1][0]) + " invalid AAC syncs in audio packet (skipped)"); if (!ThreadData.Stats_InvalidAacPackets.empty()) - WarningMessages.push_back(WithPercent(ThreadData.Stats_InvalidAacPackets.size(), PacketCount[1]) + " invalid AAC packets (replaced by silent)"); + WarningMessages.push_back(WithPercent(ThreadData.Stats_InvalidAacPackets.size(), PacketCount[1][0]) + " invalid AAC packets (replaced by silent)"); if (ThreadData.Stats_AudioPacketInvalidSize) - WarningMessages.push_back(WithPercent(ThreadData.Stats_AudioPacketInvalidSize, PacketCount[1]) + " invalid audio packets (skipped)"); + WarningMessages.push_back(WithPercent(ThreadData.Stats_AudioPacketInvalidSize, PacketCount[1][0]) + " invalid audio packets (skipped)"); } if (ThreadData.Stats_JunkBytes) WarningMessages.push_back(to_string(ThreadData.Stats_JunkBytes) + " junk bytes"); diff --git a/Source/Common/Core.h b/Source/Common/Core.h index dafe142..c617c06 100644 --- a/Source/Common/Core.h +++ b/Source/Common/Core.h @@ -50,6 +50,9 @@ class Core bool ForceExistingFiles = false; bool SkipExistingFiles = false; bool LegacyAac = false; + bool KeepSilent = false; + float SilenceLevel = -70; + float SilencePeak = -50; bool Scan = false; diff --git a/Source/Templates/LeaveSD_Encode.txt b/Source/Templates/LeaveSD_Encode.txt index 6757ec3..7403ac4 100644 --- a/Source/Templates/LeaveSD_Encode.txt +++ b/Source/Templates/LeaveSD_Encode.txt @@ -1 +1 @@ -ffmpeg.exe -y -f s16le -ar 44.1k -ac 8 -i "%TEMPPATH%.aif" -map_channel 0.0.0 -b:a 48k -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_0.aac" -map_channel 0.0.1 -b:a 48k -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_1.aac" -map_channel 0.0.2 "%TEMPPATH%_2.aac" -map_channel 0.0.3 -b:a 48k -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_3.aac" -map_channel 0.0.4 -b:a 48k -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_4.aac" -map_channel 0.0.5 -b:a 48k -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_5.aac" -map_channel 0.0.6 -b:a 48k -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_6.aac" -map_channel 0.0.7 -b:a 48k -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_7.aac" >"%TEMPPATH%_log_encode.txt" 2>&1 \ No newline at end of file +ffmpeg.exe -y -f s16le -ar 44.1k -ac 8 -i "%TEMPPATH%.aif" -map_channel 0.0.0 -b:a 48k -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_0.aac" -map_channel 0.0.1 -b:a 48k -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_1.aac" -map_channel 0.0.2 -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_2.aac" -map_channel 0.0.3 -b:a 48k -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_3.aac" -map_channel 0.0.4 -b:a 48k -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_4.aac" -map_channel 0.0.5 -b:a 48k -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_5.aac" -map_channel 0.0.6 -b:a 48k -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_6.aac" -map_channel 0.0.7 -b:a 48k -c:a libfdk_aac -profile:a aac_he "%TEMPPATH%_7.aac" >"%TEMPPATH%_log_encode.txt" 2>&1 \ No newline at end of file diff --git a/Source/Templates/LeaveSD_Probe.txt b/Source/Templates/LeaveSD_Probe.txt new file mode 100644 index 0000000..7c88b93 --- /dev/null +++ b/Source/Templates/LeaveSD_Probe.txt @@ -0,0 +1 @@ +ffmpeg.exe -y -f s16le -ar 44.1k -ac 8 -i "%TEMPPATH%.aif" -af astats -f null - >"%TEMPPATH%_log_probe.txt" 2>&1 \ No newline at end of file