From be881945aca2eaf09385b931fe7dcf2b3b3fda1f Mon Sep 17 00:00:00 2001 From: Grigory Chudov Date: Sat, 7 Apr 2018 21:18:07 -0400 Subject: [PATCH 1/4] Cleanup encoder names --- CUETools.Codecs.ffmpeg/DecoderSettings.cs | 2 +- CUETools.Codecs/CUEToolsCodecsConfig.cs | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CUETools.Codecs.ffmpeg/DecoderSettings.cs b/CUETools.Codecs.ffmpeg/DecoderSettings.cs index 23b0b6b..0bb43ad 100644 --- a/CUETools.Codecs.ffmpeg/DecoderSettings.cs +++ b/CUETools.Codecs.ffmpeg/DecoderSettings.cs @@ -13,7 +13,7 @@ namespace CUETools.Codecs.ffmpegdll #region IAudioDecoderSettings implementation [Browsable(false)] - public string Name => "ffmpeg"; + public string Name => "ffmpeg.dll"; [Browsable(false)] public Type DecoderType => typeof(AudioDecoder); diff --git a/CUETools.Codecs/CUEToolsCodecsConfig.cs b/CUETools.Codecs/CUEToolsCodecsConfig.cs index e2e883d..c237c15 100644 --- a/CUETools.Codecs/CUEToolsCodecsConfig.cs +++ b/CUETools.Codecs/CUEToolsCodecsConfig.cs @@ -52,18 +52,18 @@ namespace CUETools.Codecs if (Type.GetType("Mono.Runtime", false) == null) { - encoders.Add(new CommandLine.EncoderSettings("flake", "flac", true, "0 1 2 3 4 5 6 7 8 9 10 11 12", "8", "flake.exe", "-%M - -o %O -p %P")); - encoders.Add(new CommandLine.EncoderSettings("takc", "tak", true, "0 1 2 2e 2m 3 3e 3m 4 4e 4m", "2", "takc.exe", "-e -p%M -overwrite - %O")); - encoders.Add(new CommandLine.EncoderSettings("ffmpeg alac", "m4a", true, "", "", "ffmpeg.exe", "-i - -f ipod -acodec alac -y %O")); - encoders.Add(new CommandLine.EncoderSettings("VBR (lame.exe)", "mp3", false, "V9 V8 V7 V6 V5 V4 V3 V2 V1 V0", "V2", "lame.exe", "--vbr-new -%M - %O")); - encoders.Add(new CommandLine.EncoderSettings("CBR (lame.exe)", "mp3", false, "96 128 192 256 320", "256", "lame.exe", "-m s -q 0 -b %M --noreplaygain - %O")); - encoders.Add(new CommandLine.EncoderSettings("oggenc", "ogg", false, "-1 -0.5 0 0.5 1 1.5 2 2.5 3 3.5 4 4.5 5 5.5 6 6.5 7 7.5 8", "3", "oggenc.exe", "-q %M - -o %O")); - encoders.Add(new CommandLine.EncoderSettings("opusenc", "opus", false, "6 16 32 48 64 96 128 192 256", "128", "opusenc.exe", "--bitrate %M - %O")); - encoders.Add(new CommandLine.EncoderSettings("nero aac", "m4a", false, "0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9", "0.4", "neroAacEnc.exe", "-q %M -if - -of %O")); - encoders.Add(new CommandLine.EncoderSettings("qaac tvbr", "m4a", false, "10 20 30 40 50 60 70 80 90 100 110 127", "80", "qaac.exe", "-s -V %M -q 2 - -o %O")); + encoders.Add(new CommandLine.EncoderSettings("flake.exe", "flac", true, "0 1 2 3 4 5 6 7 8 9 10 11 12", "8", "flake.exe", "-%M - -o %O -p %P")); + encoders.Add(new CommandLine.EncoderSettings("takc.exe", "tak", true, "0 1 2 2e 2m 3 3e 3m 4 4e 4m", "2", "takc.exe", "-e -p%M -overwrite - %O")); + encoders.Add(new CommandLine.EncoderSettings("ffmpeg.exe", "m4a", true, "", "", "ffmpeg.exe", "-i - -f ipod -acodec alac -y %O")); + encoders.Add(new CommandLine.EncoderSettings("lame.exe (VBR)", "mp3", false, "V9 V8 V7 V6 V5 V4 V3 V2 V1 V0", "V2", "lame.exe", "--vbr-new -%M - %O")); + encoders.Add(new CommandLine.EncoderSettings("lame.exe (CBR)", "mp3", false, "96 128 192 256 320", "256", "lame.exe", "-m s -q 0 -b %M --noreplaygain - %O")); + encoders.Add(new CommandLine.EncoderSettings("oggenc.exe", "ogg", false, "-1 -0.5 0 0.5 1 1.5 2 2.5 3 3.5 4 4.5 5 5.5 6 6.5 7 7.5 8", "3", "oggenc.exe", "-q %M - -o %O")); + encoders.Add(new CommandLine.EncoderSettings("opusenc.exe", "opus", false, "6 16 32 48 64 96 128 192 256", "128", "opusenc.exe", "--bitrate %M - %O")); + encoders.Add(new CommandLine.EncoderSettings("neroAacEnc.exe", "m4a", false, "0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9", "0.4", "neroAacEnc.exe", "-q %M -if - -of %O")); + encoders.Add(new CommandLine.EncoderSettings("qaac.exe (tvbr)", "m4a", false, "10 20 30 40 50 60 70 80 90 100 110 127", "80", "qaac.exe", "-s -V %M -q 2 - -o %O")); - decoders.Add(new CommandLine.DecoderSettings("takc", "tak", "takc.exe", "-d %I -")); - decoders.Add(new CommandLine.DecoderSettings("ffmpeg alac", "m4a", "ffmpeg.exe", "-v 0 -i %I -f wav -")); + decoders.Add(new CommandLine.DecoderSettings("takc.exe", "tak", "takc.exe", "-d %I -")); + decoders.Add(new CommandLine.DecoderSettings("ffmpeg.exe", "m4a", "ffmpeg.exe", "-v 0 -i %I -f wav -")); } else { From deb3448a5572cbe350836efda9ac49a084624e47 Mon Sep 17 00:00:00 2001 From: Grigory Chudov Date: Sat, 7 Apr 2018 23:02:01 -0400 Subject: [PATCH 2/4] Implement IAudioSource.Duration property and IAudioDecoderSettings.Open extension method. --- CUETools.Codecs.ALAC/ALACDotNet.cs | 4 +- CUETools.Codecs.Flake/AudioDecoder.cs | 4 +- CUETools.Codecs.LossyWAV/LossyWAVReader.cs | 34 +- CUETools.Codecs.MACLib/AudioDecoder.cs | 2 + CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs | 657 ++++++++++++++++++ CUETools.Codecs.MPEG/ATSI/DecoderSettings.cs | 45 ++ CUETools.Codecs.MPEG/BDLPCM/AudioDecoder.cs | 7 +- CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs | 14 +- CUETools.Codecs.TTA/CUETools.Codecs.TTA.cpp | 5 + CUETools.Codecs.WMA/AudioDecoder.cs | 2 + CUETools.Codecs.ffmpeg/AudioDecoder.cs | 17 +- CUETools.Codecs.libFLAC/Reader.cs | 8 +- CUETools.Codecs.libwavpack/Reader.cs | 2 + CUETools.Codecs/AudioDecoderSettings.cs | 6 + CUETools.Codecs/AudioPipe.cs | 2 + CUETools.Codecs/CommandLine/AudioDecoder.cs | 9 + CUETools.Codecs/IAudioSource.cs | 7 +- CUETools.Codecs/NULL/AudioDecoder.cs | 6 +- CUETools.Codecs/WAV/AudioDecoder.cs | 2 + CUETools.Converter/Program.cs | 17 +- CUETools.DSP.Mixer/MixingSource.cs | 5 + CUETools.Processor/CUESheetAudio.cs | 2 + CUETools.Ripper.SCSI/SCSIDrive.cs | 4 +- .../NoiseAndErrorsGenerator.cs | 4 +- CUETools.eac3to/Program.cs | 20 +- CUETools.eac3ui/MainWindow.xaml.cs | 5 +- .../CUETools.TestCodecs/FlacWriterTest.cs | 2 +- 27 files changed, 817 insertions(+), 75 deletions(-) create mode 100644 CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs create mode 100644 CUETools.Codecs.MPEG/ATSI/DecoderSettings.cs diff --git a/CUETools.Codecs.ALAC/ALACDotNet.cs b/CUETools.Codecs.ALAC/ALACDotNet.cs index 00e6186..608edd3 100644 --- a/CUETools.Codecs.ALAC/ALACDotNet.cs +++ b/CUETools.Codecs.ALAC/ALACDotNet.cs @@ -165,7 +165,9 @@ namespace CUETools.Codecs.ALAC _IO.Close(); } - public long Length + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + + public long Length { get { diff --git a/CUETools.Codecs.Flake/AudioDecoder.cs b/CUETools.Codecs.Flake/AudioDecoder.cs index bc456fc..16428d6 100644 --- a/CUETools.Codecs.Flake/AudioDecoder.cs +++ b/CUETools.Codecs.Flake/AudioDecoder.cs @@ -127,7 +127,9 @@ namespace CUETools.Codecs.Flake _IO.Close(); } - public long Length + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + + public long Length { get { diff --git a/CUETools.Codecs.LossyWAV/LossyWAVReader.cs b/CUETools.Codecs.LossyWAV/LossyWAVReader.cs index 9929e1b..9eca674 100644 --- a/CUETools.Codecs.LossyWAV/LossyWAVReader.cs +++ b/CUETools.Codecs.LossyWAV/LossyWAVReader.cs @@ -12,13 +12,9 @@ namespace CUETools.Codecs.LossyWAV public IAudioDecoderSettings Settings => null; - public long Length - { - get - { - return _audioSource.Length; - } - } + public TimeSpan Duration => _audioSource.Duration; + + public long Length => _audioSource.Length; public long Position { @@ -33,29 +29,11 @@ namespace CUETools.Codecs.LossyWAV } } - public long Remaining - { - get - { - return _audioSource.Remaining; - } - } + public long Remaining => _audioSource.Remaining; - public AudioPCMConfig PCM - { - get - { - return _audioSource.PCM; - } - } + public AudioPCMConfig PCM => _audioSource.PCM; - public string Path - { - get - { - return _audioSource.Path; - } - } + public string Path => _audioSource.Path; public LossyWAVReader(IAudioSource audioSource, IAudioSource lwcdfSource) { diff --git a/CUETools.Codecs.MACLib/AudioDecoder.cs b/CUETools.Codecs.MACLib/AudioDecoder.cs index 6ca6f51..8f69879 100644 --- a/CUETools.Codecs.MACLib/AudioDecoder.cs +++ b/CUETools.Codecs.MACLib/AudioDecoder.cs @@ -74,6 +74,8 @@ namespace CUETools.Codecs.MACLib public string Path => _path; + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length => _sampleCount; internal long SamplesInBuffer => _bufferLength - _bufferOffset; diff --git a/CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs b/CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs new file mode 100644 index 0000000..7427667 --- /dev/null +++ b/CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs @@ -0,0 +1,657 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; + +namespace CUETools.Codecs.MPEG.ATSI +{ + public class AudioDecoder : IAudioSource + { + public unsafe AudioDecoder(DecoderSettings settings, string path, Stream IO) + { + m_settings = settings; + _path = path; + _IO = IO != null ? IO : new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000); + int length = (int)_IO.Length; + contents = new byte[length]; + if (_IO.Read(contents, 0, length) != length) throw new Exception(""); + fixed (byte* ptr = &contents[0]) + { + var fr = new FrameReader(ptr, length); + hdr_m = parseHeader(fr); + fr = new FrameReader(ptr + hdr_m.list_pos, length - hdr_m.list_pos); + parsePlaylist(fr); + fr = new FrameReader(ptr + hdr_m.mark_pos, length - hdr_m.mark_pos); + parsePlaylistMarks(fr); + } + } + + void openEntries() + { + readers = new List(); + var pids = new List(); + foreach (var item in hdr_m.play_item) + foreach (var audio in item.audio) + { + if (audio.coding_type != 0x80 /* LPCM */) continue; + pids.Add(audio.pid); + } + int chosenPid; + if (m_settings.StreamId.HasValue) + { + if (!pids.Contains(m_settings.StreamId.Value)) + throw new Exception("StreamId can be " + + string.Join(", ", pids.ConvertAll(pid => pid.ToString()).ToArray())); + chosenPid = m_settings.StreamId.Value; + } + else if (m_settings.Stream.HasValue) + { + if (m_settings.Stream.Value < 0 || m_settings.Stream.Value >= pids.Count) + throw new Exception("Stream can be 0.." + (pids.Count - 1).ToString()); + chosenPid = pids[m_settings.Stream.Value]; + } + else throw new Exception("multiple streams present, please specify StreamId or Stream"); + foreach (var item in hdr_m.play_item) + foreach (var audio in item.audio) + { + if (audio.coding_type != 0x80 /* LPCM */) continue; + if (m_settings.IgnoreShortItems && item.out_time - item.in_time < shortItemDuration) continue; + if (audio.pid == chosenPid) + { + var parent = Directory.GetParent(System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(_path))); + var m2ts = System.IO.Path.Combine( + System.IO.Path.Combine(parent.FullName, "STREAM"), + item.clip_id + ".m2ts"); + var settings = new BDLPCM.DecoderSettings() { StreamId = chosenPid }; + var entry = settings.Open(m2ts); + readers.Add(entry); + break; + } + } + currentReader = readers[0]; + pcm = currentReader.PCM; + } + + MPLSHeader parseHeader(FrameReader fr) + { + var hdr = new MPLSHeader(); + hdr.play_item = new List(); + hdr.play_mark = new List(); + long length = fr.Length; + hdr.type_indicator = fr.read_uint(); + hdr.type_indicator2 = fr.read_uint(); + hdr.list_pos = fr.read_uint(); + hdr.mark_pos = fr.read_uint(); + hdr.ext_pos = fr.read_uint(); + if (hdr.type_indicator != 0x4d504c53 /*MPLS*/) throw new NotSupportedException(); + if (hdr.type_indicator2 != 0x30313030 && hdr.type_indicator2 != 0x30323030) throw new NotSupportedException(); + if (hdr.list_pos > length || hdr.mark_pos > length || hdr.ext_pos > length) throw new NotSupportedException(); + return hdr; + } + + void parsePlaylist(FrameReader parentFr) + { + uint len = parentFr.read_uint(); + var fr = new FrameReader(parentFr, len); + parentFr.skip(len); + ushort reserved = fr.read_ushort(); + hdr_m.list_count = fr.read_ushort(); + hdr_m.sub_count = fr.read_ushort(); + for (int i = 0; i < hdr_m.list_count; i++) + hdr_m.play_item.Add(parsePlaylistItem(fr)); + } + + void parsePlaylistMarks(FrameReader parentFr) + { + uint len = parentFr.read_uint(); + var fr = new FrameReader(parentFr, len); + parentFr.skip(len); + hdr_m.mark_count = fr.read_ushort(); + for (int ii = 0; ii < hdr_m.mark_count; ii++) + hdr_m.play_mark.Add(parsePlaylistMark(fr)); + } + + MPLSPlaylistItem parsePlaylistItem(FrameReader parentFr) + { + var item = new MPLSPlaylistItem(); + item.video = new List(); + item.audio = new List(); + item.pg = new List(); + + // PlayItem Length + ushort len = parentFr.read_ushort(); + var fr = new FrameReader(parentFr, len); + parentFr.skip(len); + + // Primary Clip identifer + item.clip_id = fr.read_string(5); + + // skip the redundant "M2TS" CodecIdentifier + uint codecId = fr.read_uint(); + if (codecId != 0x4D325453) throw new NotSupportedException("Incorrect CodecIdentifier"); + + ushort flags = fr.read_ushort(); + bool is_multi_angle = ((flags >> 4) & 1) != 0; + item.connection_condition = (byte)(flags & 15); + if (item.connection_condition != 0x01 && + item.connection_condition != 0x05 && + item.connection_condition != 0x06) + throw new NotSupportedException("Unexpected connection condition"); + + item.stc_id = fr.read_byte(); + item.in_time = fr.read_uint(); + item.out_time = fr.read_uint(); + + // Skip UO_mask_table, random_access_flag, reserved, still_mode + // and still_time + fr.skip(12); + + if (is_multi_angle) + { + byte num_angles = fr.read_byte(); + // skip reserved, is_different_audio, is_seamless_angle_change + fr.skip(1); + for (int ii = 1; ii < num_angles; ii++) + { + // Drop clip_id, clip_codec_id, stc_id + fr.skip(10); + } + } + + // Skip STN len + fr.skip(2); + + // Skip 2 reserved bytes + fr.skip(2); + + item.num_video = fr.read_byte(); + item.num_audio = fr.read_byte(); + item.num_pg = fr.read_byte(); + item.num_ig = fr.read_byte(); + item.num_secondary_audio = fr.read_byte(); + item.num_secondary_video = fr.read_byte(); + item.num_pip_pg = fr.read_byte(); + + // 5 reserve bytes + fr.skip(5); + + for (int ii = 0; ii < item.num_video; ii++) + item.video.Add(parseStream(fr)); + for (int ii = 0; ii < item.num_audio; ii++) + item.audio.Add(parseStream(fr)); + for ( int ii = 0; ii < item.num_pg; ii++) + item.pg.Add(parseStream(fr)); + + return item; + } + + MPLSStream parseStream(FrameReader parentFr) + { + MPLSStream s = new MPLSStream(); + + byte len = parentFr.read_byte(); + var fr = new FrameReader(parentFr, len); + parentFr.skip(len); + + s.stream_type = fr.read_byte(); + switch (s.stream_type) + { + case 1: + s.pid = fr.read_ushort(); + break; + case 2: + case 4: + s.subpath_id = fr.read_byte(); + s.subclip_id = fr.read_byte(); + s.pid = fr.read_ushort(); + break; + case 3: + s.subpath_id = fr.read_byte(); + s.pid = fr.read_ushort(); + break; + default: + throw new Exception("unrecognized stream type"); + }; + + len = parentFr.read_byte(); + fr = new FrameReader(parentFr, len); + parentFr.skip(len); + + s.coding_type = fr.read_byte(); + if (s.coding_type == 0x01 + || s.coding_type == 0x02 + || s.coding_type == 0xea + || s.coding_type == 0x1b) + { + // Video + byte fmt = fr.read_byte(); + s.format = (byte)(fmt >> 4); + s.rate = (byte)(fmt & 15); + } + else if (s.coding_type == 0x03 + || s.coding_type == 0x04 + || s.coding_type == 0x80 + || s.coding_type == 0x81 + || s.coding_type == 0x82 + || s.coding_type == 0x83 + || s.coding_type == 0x84 + || s.coding_type == 0x85 + || s.coding_type == 0x86) + { + // Audio + byte fmt = fr.read_byte(); + s.format = (byte)(fmt >> 4); + s.rate = (byte)(fmt & 15); + s.lang = fr.read_string(3); + } + else if (s.coding_type == 0x90 + || s.coding_type == 0x91) + { + s.lang = fr.read_string(3); + } + else if (s.coding_type == 0x92) + { + s.char_code = fr.read_byte(); + s.lang = fr.read_string(3); + } + else throw new Exception("unrecognized stream type"); + + return s; + } + + MPLSPlaylistMark parsePlaylistMark(FrameReader fr) + { + var mark = new MPLSPlaylistMark(); + mark.mark_id = fr.read_byte(); + mark.mark_type = fr.read_byte(); + mark.play_item_ref = fr.read_ushort(); + mark.time = fr.read_uint(); + mark.entry_es_pid = fr.read_ushort(); + mark.duration = fr.read_uint(); + return mark; + } + + public IAudioDecoderSettings Settings => m_settings; + + public void Close() + { + if (readers != null) + foreach (var rdr in readers) + { + rdr.Close(); + } + readers = null; + currentReader = null; + _IO = null; + } + + public long Length + { + get + { + return -1; + } + } + + public TimeSpan Duration + { + get + { + uint totalLength = 0; + foreach (var item in hdr_m.play_item) + { + if (item.num_audio == 0) continue; + uint item_duration = item.out_time - item.in_time; + if (m_settings.IgnoreShortItems && item_duration < shortItemDuration) continue; + totalLength += item_duration; + } + + return TimeSpan.FromSeconds(totalLength / 45000.0); + } + } + + public long Remaining + { + get + { + return -1; + } + } + + public long Position + { + get + { + long res = 0; + foreach (var rdr in readers) + { + res += rdr.Position; + if (rdr == currentReader) break; + } + return res; + } + set + { + throw new NotSupportedException(); + } + } + + public unsafe AudioPCMConfig PCM + { + get { + if (readers == null) openEntries(); + return pcm; + } + } + + public string Path { get { return _path; } } + + public unsafe int Read(AudioBuffer buff, int maxLength) + { + if (readers == null) openEntries(); + int res = currentReader.Read(buff, maxLength); + if (res == 0) + { + bool nextOne = false; + foreach (var rdr in readers) + { + if (nextOne) + { + currentReader = rdr; + return currentReader.Read(buff, maxLength); + } + nextOne = (rdr == currentReader); + } + currentReader = null; + } + return res; + } + + public string FileName + { + get + { + return System.IO.Path.GetFileName(_path); + } + } + + public MPLSHeader MPLSHeader + { + get + { + return hdr_m; + } + } + + public List Chapters + { + get + { + //settings.IgnoreShortItems + var res = new List(); + if (hdr_m.play_mark.Count < 1) return res; + if (hdr_m.play_item.Count < 1) return res; + res.Add(0); + for (int i = 0; i < hdr_m.mark_count; i++) + { + ushort mark_item = hdr_m.play_mark[i].play_item_ref; + uint item_in_time = hdr_m.play_item[mark_item].in_time; + uint item_out_time = hdr_m.play_item[mark_item].out_time; + if (m_settings.IgnoreShortItems && item_out_time - item_in_time < shortItemDuration) continue; + uint item_offset = 0; + for (int j = 0; j < mark_item; j++) + { + if (hdr_m.play_item[j].num_audio == 0) continue; + uint item_duration = hdr_m.play_item[j].out_time - hdr_m.play_item[j].in_time; + if (m_settings.IgnoreShortItems && item_duration < shortItemDuration) continue; + item_offset += item_duration; + } + res.Add(hdr_m.play_mark[i].time - item_in_time + item_offset); + } + uint end_offset = 0; + for (int j = 0; j < hdr_m.play_item.Count; j++) + { + if (hdr_m.play_item[j].num_audio == 0) continue; + uint item_duration = hdr_m.play_item[j].out_time - hdr_m.play_item[j].in_time; + if (m_settings.IgnoreShortItems && item_duration < shortItemDuration) continue; + end_offset += hdr_m.play_item[j].out_time - hdr_m.play_item[j].in_time; + } + res.Add(end_offset); + while (res.Count > 1 && res[1] - res[0] < 45000) res.RemoveAt(1); + while (res.Count > 1 && res[res.Count - 1] - res[res.Count - 2] < 45000) res.RemoveAt(res.Count - 2); + return res; + } + } + + readonly static int shortItemDuration = 45000 * 30; + + string _path; + Stream _IO; + byte[] contents; + + AudioPCMConfig pcm; + List readers; + IAudioSource currentReader; + MPLSHeader hdr_m; + DecoderSettings m_settings; + } + + public struct MPLSPlaylistMark + { + public byte mark_id; + public byte mark_type; + public ushort play_item_ref; + public uint time; + public ushort entry_es_pid; + public uint duration; + } + + public struct MPLSStream + { + public byte stream_type; + public byte coding_type; + public ushort pid; + public byte subpath_id; + public byte subclip_id; + public byte format; + public byte rate; + public byte char_code; + public string lang; + + public string FormatString + { + get + { + if (coding_type == 0x01 + || coding_type == 0x02 + || coding_type == 0xea + || coding_type == 0x1b) + switch (format) + { + case 0: return "reserved0"; + case 1: return "480i"; + case 2: return "576i"; + case 3: return "480p"; + case 4: return "1080i"; + case 5: return "720p"; + case 6: return "1080p"; + case 7: return "576p"; + default: return format.ToString(); + } + switch (format) + { + case 0: return "reserved0"; + case 1: return "mono"; + case 2: return "reserved2"; + case 3: return "stereo"; + case 4: return "reserved4"; + case 5: return "reserved5"; + case 6: return "multi-channel"; + case 12: return "combo"; + default: return format.ToString(); + } + } + } + + public int FrameRate + { + get + { + switch (rate) + { + case 1: return 24; + case 2: return 24; + case 3: return 25; + case 4: return 30; + case 6: return 50; + case 7: return 60; + default: throw new NotSupportedException(); + } + } + } + + public bool Interlaced + { + get + { + return format == 1 || format == 2 || format == 4; + } + } + + public string RateString + { + get + { + if (coding_type == 0x01 + || coding_type == 0x02 + || coding_type == 0xea + || coding_type == 0x1b) + switch (rate) + { + case 0: return "reserved0"; + case 1: return "23.976"; + case 2: return "24"; + case 3: return "25"; + case 4: return "29.97"; + case 5: return "reserved5"; + case 6: return "50"; + case 7: return "59.94"; + default: return rate.ToString(); + } + switch (rate) + { + case 0: return "reserved0"; + case 1: return "48KHz"; + case 2: return "reserved2"; + case 3: return "reserved3"; + case 4: return "96KHz"; + case 5: return "192KHz"; + //case 12: return "48/192KHz"; (core/hd) + case 12: return "192KHz"; + //case 14: return "48/96KHz"; (core/hd) + case 14: return "96KHz"; + default: return rate.ToString(); + } + } + } + + public string CodecString + { + get + { + switch (coding_type) + { + case 0x01: return "MPEG-1 Video"; + case 0x02: return "MPEG-2 Video"; + case 0x03: return "MPEG-1 Audio"; + case 0x04: return "MPEG-2 Audio"; + //case 0x80: return "LPCM"; + case 0x80: return "RAW/PCM"; + case 0x81: return "AC-3"; + case 0x82: return "DTS"; + case 0x83: return "TrueHD"; + case 0x84: return "AC-3 Plus"; + case 0x85: return "DTS-HD"; + //case 0x86: return "DTS-HD Master"; + case 0x86: return "DTS Master Audio"; + case 0xea: return "VC-1"; + case 0x1b: return "h264/AVC"; + case 0x90: return "Presentation Graphics"; + case 0x91: return "Interactive Graphics"; + case 0x92: return "Text Subtitle"; + default: return coding_type.ToString(); + } + } + } + + public byte CodingType + { + get + { + return coding_type; + } + } + + public byte FormatType + { + get + { + return format; + } + } + + public string LanguageString + { + get + { + CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.AllCultures); + foreach (var culture in cultures) + { + // Exclude custom cultures. + if ((culture.CultureTypes & CultureTypes.UserCustomCulture) == CultureTypes.UserCustomCulture) + continue; + + if (culture.ThreeLetterISOLanguageName == lang) + return culture.EnglishName; + } + return lang; + } + } + } + + public struct MPLSPlaylistItem + { + public string clip_id; + public byte connection_condition; + public byte stc_id; + public uint in_time; + public uint out_time; + + public byte num_video; + public byte num_audio; + public byte num_pg; + public byte num_ig; + public byte num_secondary_audio; + public byte num_secondary_video; + public byte num_pip_pg; + public List video; + public List audio; + public List pg; + } + + public struct MPLSHeader + { + public uint type_indicator; + public uint type_indicator2; + public uint list_pos; + public uint mark_pos; + public uint ext_pos; + + public ushort list_count; + public ushort sub_count; + public ushort mark_count; + + public List play_item; + public List play_mark; + } +} diff --git a/CUETools.Codecs.MPEG/ATSI/DecoderSettings.cs b/CUETools.Codecs.MPEG/ATSI/DecoderSettings.cs new file mode 100644 index 0000000..0bf4597 --- /dev/null +++ b/CUETools.Codecs.MPEG/ATSI/DecoderSettings.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Text; +using Newtonsoft.Json; + +namespace CUETools.Codecs.MPEG.ATSI +{ + [JsonObject(MemberSerialization.OptIn)] + public class DecoderSettings : IAudioDecoderSettings + { + #region IAudioDecoderSettings implementation + [Browsable(false)] + public string Extension => "ifo"; + + [Browsable(false)] + public string Name => "cuetools"; + + [Browsable(false)] + public Type DecoderType => typeof(AudioDecoder); + + [Browsable(false)] + public int Priority => 2; + + public IAudioDecoderSettings Clone() + { + return MemberwiseClone() as IAudioDecoderSettings; + } + #endregion + + public DecoderSettings() + { + this.Init(); + } + + [DefaultValue(true)] + public bool IgnoreShortItems { get; set; } + + [Browsable(false)] + public int? Stream { get; set; } + + [Browsable(false)] + public int? StreamId { get; set; } + } +} diff --git a/CUETools.Codecs.MPEG/BDLPCM/AudioDecoder.cs b/CUETools.Codecs.MPEG/BDLPCM/AudioDecoder.cs index 09d1a03..720c095 100644 --- a/CUETools.Codecs.MPEG/BDLPCM/AudioDecoder.cs +++ b/CUETools.Codecs.MPEG/BDLPCM/AudioDecoder.cs @@ -8,11 +8,6 @@ namespace CUETools.Codecs.MPEG.BDLPCM { public class AudioDecoder : IAudioSource { - public unsafe AudioDecoder(string path, Stream IO, int pid) - : this(new DecoderSettings() { StreamId = pid }, path, IO) - { - } - public unsafe AudioDecoder(DecoderSettings settings, string path, Stream IO) { _path = path; @@ -38,6 +33,8 @@ namespace CUETools.Codecs.MPEG.BDLPCM _IO = null; } + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length { get diff --git a/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs b/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs index 6b8301c..d234aea 100644 --- a/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs +++ b/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs @@ -7,11 +7,6 @@ namespace CUETools.Codecs.MPEG.MPLS { public class AudioDecoder : IAudioSource { - public unsafe AudioDecoder(string path, Stream IO, ushort pid) - : this(new DecoderSettings() { StreamId = pid }, path, IO) - { - } - public unsafe AudioDecoder(DecoderSettings settings, string path, Stream IO) { m_settings = settings; @@ -33,7 +28,7 @@ namespace CUETools.Codecs.MPEG.MPLS void openEntries() { - readers = new List(); + readers = new List(); var pids = new List(); foreach (var item in hdr_m.play_item) foreach (var audio in item.audio) @@ -67,7 +62,8 @@ namespace CUETools.Codecs.MPEG.MPLS var m2ts = System.IO.Path.Combine( System.IO.Path.Combine(parent.FullName, "STREAM"), item.clip_id + ".m2ts"); - var entry = new BDLPCM.AudioDecoder(m2ts, null, chosenPid); + var settings = new BDLPCM.DecoderSettings() { StreamId = chosenPid }; + var entry = settings.Open(m2ts); readers.Add(entry); break; } @@ -434,8 +430,8 @@ namespace CUETools.Codecs.MPEG.MPLS byte[] contents; AudioPCMConfig pcm; - List readers; - BDLPCM.AudioDecoder currentReader; + List readers; + IAudioSource currentReader; MPLSHeader hdr_m; DecoderSettings m_settings; } diff --git a/CUETools.Codecs.TTA/CUETools.Codecs.TTA.cpp b/CUETools.Codecs.TTA/CUETools.Codecs.TTA.cpp index 3314117..25e2f62 100644 --- a/CUETools.Codecs.TTA/CUETools.Codecs.TTA.cpp +++ b/CUETools.Codecs.TTA/CUETools.Codecs.TTA.cpp @@ -127,6 +127,11 @@ namespace TTA { } } + virtual property TimeSpan Duration { + TimeSpan get() { + return Length < 0 ? TimeSpan::Zero : TimeSpan::FromSeconds((double)Length / PCM->SampleRate); + } + } virtual property Int64 Length { Int64 get() { return _sampleCount; diff --git a/CUETools.Codecs.WMA/AudioDecoder.cs b/CUETools.Codecs.WMA/AudioDecoder.cs index 7ce86e6..6482de1 100644 --- a/CUETools.Codecs.WMA/AudioDecoder.cs +++ b/CUETools.Codecs.WMA/AudioDecoder.cs @@ -252,6 +252,8 @@ namespace CUETools.Codecs.WMA m_syncReader = null; } + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length { get diff --git a/CUETools.Codecs.ffmpeg/AudioDecoder.cs b/CUETools.Codecs.ffmpeg/AudioDecoder.cs index 49d7055..0b6c868 100644 --- a/CUETools.Codecs.ffmpeg/AudioDecoder.cs +++ b/CUETools.Codecs.ffmpeg/AudioDecoder.cs @@ -191,7 +191,7 @@ namespace CUETools.Codecs.ffmpegdll //if (stream->duration > 0) // _sampleCount = stream->duration; //else - _sampleCount = -1; + _sampleCount = -1; int bps = stream->codecpar->bits_per_raw_sample != 0 ? stream->codecpar->bits_per_raw_sample : @@ -279,6 +279,21 @@ namespace CUETools.Codecs.ffmpegdll public string Path => _path; + public TimeSpan Duration + { + get + { + // Sadly, duration is unreliable for most codecs. + if (stream->codecpar->codec_id == AVCodecID.AV_CODEC_ID_MLP) + return TimeSpan.Zero; + if (stream->duration > 0) + return TimeSpan.FromSeconds((double)stream->duration / stream->codecpar->sample_rate); + if (fmt_ctx->duration > 0) + return TimeSpan.FromSeconds((double)fmt_ctx->duration / ffmpeg.AV_TIME_BASE); + return TimeSpan.Zero; + } + } + public long Length => _sampleCount; public long Position diff --git a/CUETools.Codecs.libFLAC/Reader.cs b/CUETools.Codecs.libFLAC/Reader.cs index e566eda..02596d1 100644 --- a/CUETools.Codecs.libFLAC/Reader.cs +++ b/CUETools.Codecs.libFLAC/Reader.cs @@ -20,7 +20,7 @@ namespace CUETools.Codecs.libFLAC public string Name => "libFLAC"; [Browsable(false)] - public Type DecoderType => typeof(Reader); + public Type DecoderType => typeof(AudioDecoder); [Browsable(false)] public int Priority => 1; @@ -37,9 +37,9 @@ namespace CUETools.Codecs.libFLAC } } - public unsafe class Reader : IAudioSource + public unsafe class AudioDecoder : IAudioSource { - public Reader(DecoderSettings settings, string path, Stream IO) + public AudioDecoder(DecoderSettings settings, string path, Stream IO) { m_settings = settings; @@ -306,6 +306,8 @@ namespace CUETools.Codecs.libFLAC public string Path => m_path; + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length => m_sampleCount; private int SamplesInBuffer => m_bufferLength - m_bufferOffset; diff --git a/CUETools.Codecs.libwavpack/Reader.cs b/CUETools.Codecs.libwavpack/Reader.cs index 3347f39..b29342f 100644 --- a/CUETools.Codecs.libwavpack/Reader.cs +++ b/CUETools.Codecs.libwavpack/Reader.cs @@ -99,6 +99,8 @@ namespace CUETools.Codecs.libwavpack public string Path => _path; + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length => _sampleCount; public long Position diff --git a/CUETools.Codecs/AudioDecoderSettings.cs b/CUETools.Codecs/AudioDecoderSettings.cs index 8b9189b..1f34ebb 100644 --- a/CUETools.Codecs/AudioDecoderSettings.cs +++ b/CUETools.Codecs/AudioDecoderSettings.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Xml.Serialization; using System.Text; using Newtonsoft.Json; +using System.IO; namespace CUETools.Codecs { @@ -44,5 +45,10 @@ namespace CUETools.Codecs foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(settings)) property.ResetValue(settings); } + + public static IAudioSource Open(this IAudioDecoderSettings settings, string path, Stream IO = null) + { + return Activator.CreateInstance(settings.DecoderType, settings, path, IO) as IAudioSource; + } } } diff --git a/CUETools.Codecs/AudioPipe.cs b/CUETools.Codecs/AudioPipe.cs index 1545b9d..46fa580 100644 --- a/CUETools.Codecs/AudioPipe.cs +++ b/CUETools.Codecs/AudioPipe.cs @@ -54,6 +54,8 @@ namespace CUETools.Codecs } } + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length { get diff --git a/CUETools.Codecs/CommandLine/AudioDecoder.cs b/CUETools.Codecs/CommandLine/AudioDecoder.cs index 40e63ca..91ea5c1 100644 --- a/CUETools.Codecs/CommandLine/AudioDecoder.cs +++ b/CUETools.Codecs/CommandLine/AudioDecoder.cs @@ -27,6 +27,15 @@ namespace CUETools.Codecs.CommandLine } } + public TimeSpan Duration + { + get + { + Initialize(); + return rdr.Duration; + } + } + public long Length { get diff --git a/CUETools.Codecs/IAudioSource.cs b/CUETools.Codecs/IAudioSource.cs index 3dbb2f0..324002f 100644 --- a/CUETools.Codecs/IAudioSource.cs +++ b/CUETools.Codecs/IAudioSource.cs @@ -1,4 +1,6 @@ -namespace CUETools.Codecs +using System; + +namespace CUETools.Codecs { public interface IAudioSource { @@ -7,7 +9,8 @@ AudioPCMConfig PCM { get; } string Path { get; } - long Length { get; } + TimeSpan Duration { get; } + long Length { get; } long Position { get; set; } long Remaining { get; } diff --git a/CUETools.Codecs/NULL/AudioDecoder.cs b/CUETools.Codecs/NULL/AudioDecoder.cs index b4b78ff..651db6a 100644 --- a/CUETools.Codecs/NULL/AudioDecoder.cs +++ b/CUETools.Codecs/NULL/AudioDecoder.cs @@ -1,4 +1,6 @@ -namespace CUETools.Codecs.NULL +using System; + +namespace CUETools.Codecs.NULL { public class AudioDecoder : IAudioSource { @@ -8,6 +10,8 @@ public IAudioDecoderSettings Settings => null; + public TimeSpan Duration => TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length { get { return _sampleCount; } diff --git a/CUETools.Codecs/WAV/AudioDecoder.cs b/CUETools.Codecs/WAV/AudioDecoder.cs index d3df5e7..3cd32d0 100644 --- a/CUETools.Codecs/WAV/AudioDecoder.cs +++ b/CUETools.Codecs/WAV/AudioDecoder.cs @@ -55,6 +55,8 @@ namespace CUETools.Codecs.WAV } } + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length { get diff --git a/CUETools.Converter/Program.cs b/CUETools.Converter/Program.cs index e46f914..ef0dc0a 100644 --- a/CUETools.Converter/Program.cs +++ b/CUETools.Converter/Program.cs @@ -162,7 +162,7 @@ namespace CUETools.Converter AudioBuffer buff = new AudioBuffer(audioSource, 0x10000); Console.Error.WriteLine("Filename : {0}", sourceFile); - Console.Error.WriteLine("File Info : {0}kHz; {1} channel; {2} bit; {3}", audioSource.PCM.SampleRate, audioSource.PCM.ChannelCount, audioSource.PCM.BitsPerSample, TimeSpan.FromSeconds(audioSource.Length * 1.0 / audioSource.PCM.SampleRate)); + Console.Error.WriteLine("File Info : {0}kHz; {1} channel; {2} bit; {3}", audioSource.PCM.SampleRate, audioSource.PCM.ChannelCount, audioSource.PCM.BitsPerSample, audioSource.Duration); CUEToolsFormat fmt; if (encoderFormat == null) @@ -226,15 +226,16 @@ namespace CUETools.Converter TimeSpan elapsed = DateTime.Now - start; if ((elapsed - lastPrint).TotalMilliseconds > 60) { - long length = audioSource.Length; - if (length < 0 && sourceInfo != null) length = (long)(sourceInfo.Properties.Duration.TotalMilliseconds * audioSource.PCM.SampleRate / 1000); - if (length < audioSource.Position) length = audioSource.Position; - if (length < 1) length = 1; + var duration = audioSource.Duration; + var position = TimeSpan.FromSeconds((double)audioSource.Position / audioSource.PCM.SampleRate); + if (duration == TimeSpan.Zero && sourceInfo != null) duration = sourceInfo.Properties.Duration; + if (duration < position) duration = position; + if (duration < TimeSpan.FromSeconds(1)) duration = TimeSpan.FromSeconds(1); Console.Error.Write("\rProgress : {0:00}%; {1:0.00}x; {2}/{3}", - 100.0 * audioSource.Position / length, - audioSource.Position / elapsed.TotalSeconds / audioSource.PCM.SampleRate, + 100.0 * position.TotalSeconds / duration.TotalSeconds, + position.TotalSeconds / elapsed.TotalSeconds, elapsed, - TimeSpan.FromMilliseconds(elapsed.TotalMilliseconds / audioSource.Position * length) + TimeSpan.FromSeconds(elapsed.TotalSeconds / position.TotalSeconds * duration.TotalSeconds) ); lastPrint = elapsed; } diff --git a/CUETools.DSP.Mixer/MixingSource.cs b/CUETools.DSP.Mixer/MixingSource.cs index 4ed6f7d..4ed63fc 100644 --- a/CUETools.DSP.Mixer/MixingSource.cs +++ b/CUETools.DSP.Mixer/MixingSource.cs @@ -29,6 +29,11 @@ namespace CUETools.DSP.Mixer set { throw new NotSupportedException(); } } + public TimeSpan Duration + { + get { throw new NotSupportedException(); } + } + public long Length { get { throw new NotSupportedException(); } diff --git a/CUETools.Processor/CUESheetAudio.cs b/CUETools.Processor/CUESheetAudio.cs index 2f9af32..f68ea23 100644 --- a/CUETools.Processor/CUESheetAudio.cs +++ b/CUETools.Processor/CUESheetAudio.cs @@ -13,6 +13,8 @@ namespace CUETools.Processor public IAudioDecoderSettings Settings => null; + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + public long Length { get { return _sampleLen; } diff --git a/CUETools.Ripper.SCSI/SCSIDrive.cs b/CUETools.Ripper.SCSI/SCSIDrive.cs index 3554829..671cc4b 100644 --- a/CUETools.Ripper.SCSI/SCSIDrive.cs +++ b/CUETools.Ripper.SCSI/SCSIDrive.cs @@ -1149,7 +1149,9 @@ namespace CUETools.Ripper.SCSI return buff.Length; } - public long Length + public TimeSpan Duration => TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + + public long Length { get { diff --git a/CUETools.TestHelpers/NoiseAndErrorsGenerator.cs b/CUETools.TestHelpers/NoiseAndErrorsGenerator.cs index da89c79..a4cab6e 100644 --- a/CUETools.TestHelpers/NoiseAndErrorsGenerator.cs +++ b/CUETools.TestHelpers/NoiseAndErrorsGenerator.cs @@ -56,7 +56,9 @@ namespace CUETools.TestHelpers { } - public long Length + public TimeSpan Duration => Length < 0 ? TimeSpan.Zero : TimeSpan.FromSeconds((double)Length / PCM.SampleRate); + + public long Length { get { diff --git a/CUETools.eac3to/Program.cs b/CUETools.eac3to/Program.cs index 9f34b5d..ce485f6 100644 --- a/CUETools.eac3to/Program.cs +++ b/CUETools.eac3to/Program.cs @@ -126,7 +126,6 @@ namespace CUETools.eac3to var videos = new List(); var audios = new List(); List chapters; - TimeSpan duration; TagLib.UserDefined.AdditionalFileTypes.Config = config; #if !DEBUG @@ -147,7 +146,7 @@ namespace CUETools.eac3to Console.Error.WriteLine("M2TS, {0} video track{1}, {2} audio track{3}, {4}, {5}{6}", videos.Count, videos.Count > 1 ? "s" : "", audios.Count, audios.Count > 1 ? "s" : "", - CDImageLayout.TimeToString(mpls.Duration, "{0:0}:{1:00}:{2:00}"), frameRate * (interlaced ? 2 : 1), interlaced ? "i" : "p"); + CDImageLayout.TimeToString(audioSource.Duration, "{0:0}:{1:00}:{2:00}"), frameRate * (interlaced ? 2 : 1), interlaced ? "i" : "p"); //foreach (var item in mpls.MPLSHeader.play_item) //Console.Error.WriteLine("{0}.m2ts", item.clip_id); { @@ -178,8 +177,6 @@ namespace CUETools.eac3to Console.Error.WriteLine("{0}, {1}, {2}, {3}", audio.CodecString, audio.LanguageString, audio.FormatString, audio.RateString); } } - - duration = mpls.Duration; } if (destFile == null) @@ -323,7 +320,7 @@ namespace CUETools.eac3to AudioBuffer buff = new AudioBuffer(audioSource, 0x10000); Console.Error.WriteLine("Filename : {0}", sourceFile); Console.Error.WriteLine("File Info : {0}kHz; {1} channel; {2} bit; {3}", audioSource.PCM.SampleRate, audioSource.PCM.ChannelCount, audioSource.PCM.BitsPerSample, - duration); + audioSource.Duration); CUEToolsFormat fmt; if (encoderFormat == null) @@ -397,14 +394,15 @@ namespace CUETools.eac3to TimeSpan elapsed = DateTime.Now - start; if ((elapsed - lastPrint).TotalMilliseconds > 60) { - long length = (long)(duration.TotalSeconds * audioSource.PCM.SampleRate); - if (length < audioSource.Position) length = audioSource.Position; - if (length < 1) length = 1; + var duration = audioSource.Duration; + var position = TimeSpan.FromSeconds((double)audioSource.Position / audioSource.PCM.SampleRate); + if (duration < position) duration = position; + if (duration < TimeSpan.FromSeconds(1)) duration = TimeSpan.FromSeconds(1); Console.Error.Write("\rProgress : {0:00}%; {1:0.00}x; {2}/{3}", - 100.0 * audioSource.Position / length, - audioSource.Position / elapsed.TotalSeconds / audioSource.PCM.SampleRate, + 100.0 * position.TotalSeconds / duration.TotalSeconds, + position.TotalSeconds / elapsed.TotalSeconds, elapsed, - TimeSpan.FromMilliseconds(elapsed.TotalMilliseconds / audioSource.Position * length) + TimeSpan.FromSeconds(elapsed.TotalSeconds / position.TotalSeconds * duration.TotalSeconds) ); lastPrint = elapsed; } diff --git a/CUETools.eac3ui/MainWindow.xaml.cs b/CUETools.eac3ui/MainWindow.xaml.cs index 26e1c38..213530b 100644 --- a/CUETools.eac3ui/MainWindow.xaml.cs +++ b/CUETools.eac3ui/MainWindow.xaml.cs @@ -228,10 +228,11 @@ namespace BluTools void workerExtract_DoWork(object sender, DoWorkEventArgs e) { - CUETools.Codecs.MPEG.MPLS.AudioDecoder reader = null; + IAudioSource reader = null; try { - reader = new CUETools.Codecs.MPEG.MPLS.AudioDecoder(chosenReader.Path, null, pid); + var decoderSettings = new CUETools.Codecs.MPEG.MPLS.DecoderSettings() { StreamId = pid }; + reader = decoderSettings.Open(chosenReader.Path); Directory.CreateDirectory(outputFolderPath); if (File.Exists(outputCuePath)) throw new Exception(string.Format("File \"{0}\" already exists", outputCuePath)); if (File.Exists(outputAudioPath)) throw new Exception(string.Format("File \"{0}\" already exists", outputAudioPath)); diff --git a/CUETools/CUETools.TestCodecs/FlacWriterTest.cs b/CUETools/CUETools.TestCodecs/FlacWriterTest.cs index 192a2df..de1899e 100644 --- a/CUETools/CUETools.TestCodecs/FlacWriterTest.cs +++ b/CUETools/CUETools.TestCodecs/FlacWriterTest.cs @@ -49,7 +49,7 @@ namespace CUETools.TestCodecs [TestMethod()] public void SeekTest() { - var r = new CUETools.Codecs.libFLAC.Reader(new Codecs.libFLAC.DecoderSettings(), "test.flac", null); + var r = new Codecs.libFLAC.DecoderSettings().Open("test.flac", null); var buff1 = new AudioBuffer(r, 16536); var buff2 = new AudioBuffer(r, 16536); Assert.AreEqual(0, r.Position); From b6c3256d320524e45e4f96363d1db7a01ce6dcfd Mon Sep 17 00:00:00 2001 From: Grigory Chudov Date: Sun, 29 Apr 2018 16:53:55 -0400 Subject: [PATCH 3/4] DVDAudio titleset (ATSI) parser --- CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs | 1336 ++++++++++++------ CUETools.Codecs.MPEG/ATSI/DecoderSettings.cs | 3 + CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs | 46 +- CUETools.Codecs.MPEG/TsStream.cs | 9 + CUETools.Codecs/AudioPCMConfig.cs | 57 +- CUETools.Codecs/IAudioSource.cs | 26 + CUETools.eac3to/Program.cs | 85 +- CUETools.eac3ui/MainWindow.xaml.cs | 2 +- 8 files changed, 1068 insertions(+), 496 deletions(-) diff --git a/CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs b/CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs index 7427667..c6adee5 100644 --- a/CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs +++ b/CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs @@ -2,10 +2,11 @@ using System.Collections.Generic; using System.Globalization; using System.IO; +using static CUETools.Codecs.AudioPCMConfig; namespace CUETools.Codecs.MPEG.ATSI { - public class AudioDecoder : IAudioSource + public class AudioDecoder : IAudioSource, IAudioContainer { public unsafe AudioDecoder(DecoderSettings settings, string path, Stream IO) { @@ -13,16 +14,15 @@ namespace CUETools.Codecs.MPEG.ATSI _path = path; _IO = IO != null ? IO : new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000); int length = (int)_IO.Length; + if (length > 0x100000) throw new Exception("File too big"); contents = new byte[length]; if (_IO.Read(contents, 0, length) != length) throw new Exception(""); fixed (byte* ptr = &contents[0]) { var fr = new FrameReader(ptr, length); hdr_m = parseHeader(fr); - fr = new FrameReader(ptr + hdr_m.list_pos, length - hdr_m.list_pos); - parsePlaylist(fr); - fr = new FrameReader(ptr + hdr_m.mark_pos, length - hdr_m.mark_pos); - parsePlaylistMarks(fr); + fr = new FrameReader(ptr, length); + parseTitles(fr); } } @@ -30,245 +30,133 @@ namespace CUETools.Codecs.MPEG.ATSI { readers = new List(); var pids = new List(); - foreach (var item in hdr_m.play_item) - foreach (var audio in item.audio) - { - if (audio.coding_type != 0x80 /* LPCM */) continue; - pids.Add(audio.pid); - } - int chosenPid; - if (m_settings.StreamId.HasValue) - { - if (!pids.Contains(m_settings.StreamId.Value)) - throw new Exception("StreamId can be " + - string.Join(", ", pids.ConvertAll(pid => pid.ToString()).ToArray())); - chosenPid = m_settings.StreamId.Value; - } - else if (m_settings.Stream.HasValue) - { - if (m_settings.Stream.Value < 0 || m_settings.Stream.Value >= pids.Count) - throw new Exception("Stream can be 0.." + (pids.Count - 1).ToString()); - chosenPid = pids[m_settings.Stream.Value]; - } - else throw new Exception("multiple streams present, please specify StreamId or Stream"); - foreach (var item in hdr_m.play_item) - foreach (var audio in item.audio) - { - if (audio.coding_type != 0x80 /* LPCM */) continue; - if (m_settings.IgnoreShortItems && item.out_time - item.in_time < shortItemDuration) continue; - if (audio.pid == chosenPid) - { - var parent = Directory.GetParent(System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(_path))); - var m2ts = System.IO.Path.Combine( - System.IO.Path.Combine(parent.FullName, "STREAM"), - item.clip_id + ".m2ts"); - var settings = new BDLPCM.DecoderSettings() { StreamId = chosenPid }; - var entry = settings.Open(m2ts); - readers.Add(entry); - break; - } - } + //foreach (var item in hdr_m.play_item) + // foreach (var audio in item.audio) + // { + // if (audio.coding_type != 0x80 /* LPCM */) continue; + // pids.Add(audio.pid); + // } + //int chosenPid; + //if (m_settings.StreamId.HasValue) + //{ + // if (!pids.Contains(m_settings.StreamId.Value)) + // throw new Exception("StreamId can be " + + // string.Join(", ", pids.ConvertAll(pid => pid.ToString()).ToArray())); + // chosenPid = m_settings.StreamId.Value; + //} + //else if (m_settings.Stream.HasValue) + //{ + // if (m_settings.Stream.Value < 0 || m_settings.Stream.Value >= pids.Count) + // throw new Exception("Stream can be 0.." + (pids.Count - 1).ToString()); + // chosenPid = pids[m_settings.Stream.Value]; + //} + //else throw new Exception("multiple streams present, please specify StreamId or Stream"); + //foreach (var item in hdr_m.play_item) + // foreach (var audio in item.audio) + // { + // if (audio.coding_type != 0x80 /* LPCM */) continue; + // if (m_settings.IgnoreShortItems && item.out_time - item.in_time < shortItemDuration) continue; + // if (audio.pid == chosenPid) + // { + // var parent = Directory.GetParent(System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(_path))); + // var m2ts = System.IO.Path.Combine( + // System.IO.Path.Combine(parent.FullName, "STREAM"), + // item.clip_id + ".m2ts"); + // var settings = new BDLPCM.DecoderSettings() { StreamId = chosenPid }; + // var entry = settings.Open(m2ts); + // readers.Add(entry); + // break; + // } + // } currentReader = readers[0]; pcm = currentReader.PCM; } - MPLSHeader parseHeader(FrameReader fr) + ATSIHeader parseHeader(FrameReader fr) { - var hdr = new MPLSHeader(); - hdr.play_item = new List(); - hdr.play_mark = new List(); - long length = fr.Length; - hdr.type_indicator = fr.read_uint(); - hdr.type_indicator2 = fr.read_uint(); - hdr.list_pos = fr.read_uint(); - hdr.mark_pos = fr.read_uint(); - hdr.ext_pos = fr.read_uint(); - if (hdr.type_indicator != 0x4d504c53 /*MPLS*/) throw new NotSupportedException(); - if (hdr.type_indicator2 != 0x30313030 && hdr.type_indicator2 != 0x30323030) throw new NotSupportedException(); - if (hdr.list_pos > length || hdr.mark_pos > length || hdr.ext_pos > length) throw new NotSupportedException(); + var hdr = new ATSIHeader(fr); + + uint aob_offset = 0; + for (int i = 0; i < 9; i++) + { + var aob = new DVDAAOBFile(); + aob.fileName = System.IO.Path.Combine( + System.IO.Path.GetDirectoryName(System.IO.Path.GetFullPath(_path)), + $"ATS_{System.IO.Path.GetFileNameWithoutExtension(_path).Substring(4, 2)}_{i + 1}.AOB"); + aob.first = aob_offset; + try + { + aob.atsFile = new FileStream(aob.fileName, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000); + aob.last = (uint)(aob.first + aob.atsFile.Length / DVDA.BLOCK_SIZE) - 1; + aob.isExist = true; + } + catch (FileNotFoundException) + { + aob.last = aob.first + (uint)((1024 * 1024 - 32) * 1024 / DVDA.BLOCK_SIZE - 1); + aob.isExist = false; + } + aob_offset = aob.last + 1; + hdr.aobs.Add(aob); + } + return hdr; } - void parsePlaylist(FrameReader parentFr) + unsafe void parseTitles(FrameReader parentFr) { - uint len = parentFr.read_uint(); - var fr = new FrameReader(parentFr, len); - parentFr.skip(len); - ushort reserved = fr.read_ushort(); - hdr_m.list_count = fr.read_ushort(); - hdr_m.sub_count = fr.read_ushort(); - for (int i = 0; i < hdr_m.list_count; i++) - hdr_m.play_item.Add(parsePlaylistItem(fr)); - } - - void parsePlaylistMarks(FrameReader parentFr) - { - uint len = parentFr.read_uint(); - var fr = new FrameReader(parentFr, len); - parentFr.skip(len); - hdr_m.mark_count = fr.read_ushort(); - for (int ii = 0; ii < hdr_m.mark_count; ii++) - hdr_m.play_mark.Add(parsePlaylistMark(fr)); - } - - MPLSPlaylistItem parsePlaylistItem(FrameReader parentFr) - { - var item = new MPLSPlaylistItem(); - item.video = new List(); - item.audio = new List(); - item.pg = new List(); - - // PlayItem Length - ushort len = parentFr.read_ushort(); - var fr = new FrameReader(parentFr, len); - parentFr.skip(len); - - // Primary Clip identifer - item.clip_id = fr.read_string(5); - - // skip the redundant "M2TS" CodecIdentifier - uint codecId = fr.read_uint(); - if (codecId != 0x4D325453) throw new NotSupportedException("Incorrect CodecIdentifier"); - - ushort flags = fr.read_ushort(); - bool is_multi_angle = ((flags >> 4) & 1) != 0; - item.connection_condition = (byte)(flags & 15); - if (item.connection_condition != 0x01 && - item.connection_condition != 0x05 && - item.connection_condition != 0x06) - throw new NotSupportedException("Unexpected connection condition"); - - item.stc_id = fr.read_byte(); - item.in_time = fr.read_uint(); - item.out_time = fr.read_uint(); - - // Skip UO_mask_table, random_access_flag, reserved, still_mode - // and still_time - fr.skip(12); - - if (is_multi_angle) + for (int i = 0; i < hdr_m.nr_of_titles; i++) { - byte num_angles = fr.read_byte(); - // skip reserved, is_different_audio, is_seamless_angle_change - fr.skip(1); - for (int ii = 1; ii < num_angles; ii++) + var fr = new FrameReader(parentFr.Ptr + 0x800 + hdr_m.ats_title_idx[i], parentFr.Length - 0x800 - hdr_m.ats_title_idx[i]); + hdr_m.titles.Add(parseTitle(fr)); + } + for (int i = 0; i < hdr_m.nr_of_titles; i++) + { + var fr = new FrameReader(parentFr.Ptr + 0x100 + i * 16, 16); + hdr_m.titles[i].codec = fr.read_ushort(); + hdr_m.titles[i].format = fr.read_uint(); + } + } + + ATSITitle parseTitle(FrameReader fr) + { + var titleFr = new FrameReader(fr, fr.Length); + var title = new ATSITitle(this, titleFr); + for (int i = 0; i < title.ntracks; i++) + title.track_timestamp.Add(new ATSITrackTimestamp(titleFr)); + fr.skip(title.track_sector_table_offset); + for (int i = 0; i < title.nindexes; i++) + { + var dvdaSectorPointer = new ATSITrackSector(fr); + title.track_sector.Add(dvdaSectorPointer); + for (int k = 0; k < title.ntracks; k++) { - // Drop clip_id, clip_codec_id, stc_id - fr.skip(10); + var track_curr_idx = title.track_timestamp[k].pg_id; + var track_next_idx = (k < title.ntracks - 1) ? title.track_timestamp[k + 1].pg_id : 0; + if (i + 1 >= track_curr_idx && (i + 1 < track_next_idx || track_next_idx == 0)) + { + title.track_timestamp[k].sector_pointers.Add(i); + } } + + var nblocks = Math.Min(SEGMENT_HEADER_BLOCKS, dvdaSectorPointer.last_sector - dvdaSectorPointer.first_sector + 1); + var head_buf = new byte[nblocks * DVDA.BLOCK_SIZE]; + for (int b = 0; b < nblocks; b++) + { + getBlock(dvdaSectorPointer.first_sector + b, head_buf, b * DVDA.BLOCK_SIZE); + } + dvdaSectorPointer.dvdaBlock = new DVDABlock(head_buf); } - - // Skip STN len - fr.skip(2); - - // Skip 2 reserved bytes - fr.skip(2); - - item.num_video = fr.read_byte(); - item.num_audio = fr.read_byte(); - item.num_pg = fr.read_byte(); - item.num_ig = fr.read_byte(); - item.num_secondary_audio = fr.read_byte(); - item.num_secondary_video = fr.read_byte(); - item.num_pip_pg = fr.read_byte(); - - // 5 reserve bytes - fr.skip(5); - - for (int ii = 0; ii < item.num_video; ii++) - item.video.Add(parseStream(fr)); - for (int ii = 0; ii < item.num_audio; ii++) - item.audio.Add(parseStream(fr)); - for ( int ii = 0; ii < item.num_pg; ii++) - item.pg.Add(parseStream(fr)); - - return item; + return title; } - MPLSStream parseStream(FrameReader parentFr) + void getBlock(long block_no, byte[] buf, int offset) { - MPLSStream s = new MPLSStream(); - - byte len = parentFr.read_byte(); - var fr = new FrameReader(parentFr, len); - parentFr.skip(len); - - s.stream_type = fr.read_byte(); - switch (s.stream_type) - { - case 1: - s.pid = fr.read_ushort(); - break; - case 2: - case 4: - s.subpath_id = fr.read_byte(); - s.subclip_id = fr.read_byte(); - s.pid = fr.read_ushort(); - break; - case 3: - s.subpath_id = fr.read_byte(); - s.pid = fr.read_ushort(); - break; - default: - throw new Exception("unrecognized stream type"); - }; - - len = parentFr.read_byte(); - fr = new FrameReader(parentFr, len); - parentFr.skip(len); - - s.coding_type = fr.read_byte(); - if (s.coding_type == 0x01 - || s.coding_type == 0x02 - || s.coding_type == 0xea - || s.coding_type == 0x1b) - { - // Video - byte fmt = fr.read_byte(); - s.format = (byte)(fmt >> 4); - s.rate = (byte)(fmt & 15); - } - else if (s.coding_type == 0x03 - || s.coding_type == 0x04 - || s.coding_type == 0x80 - || s.coding_type == 0x81 - || s.coding_type == 0x82 - || s.coding_type == 0x83 - || s.coding_type == 0x84 - || s.coding_type == 0x85 - || s.coding_type == 0x86) - { - // Audio - byte fmt = fr.read_byte(); - s.format = (byte)(fmt >> 4); - s.rate = (byte)(fmt & 15); - s.lang = fr.read_string(3); - } - else if (s.coding_type == 0x90 - || s.coding_type == 0x91) - { - s.lang = fr.read_string(3); - } - else if (s.coding_type == 0x92) - { - s.char_code = fr.read_byte(); - s.lang = fr.read_string(3); - } - else throw new Exception("unrecognized stream type"); - - return s; - } - - MPLSPlaylistMark parsePlaylistMark(FrameReader fr) - { - var mark = new MPLSPlaylistMark(); - mark.mark_id = fr.read_byte(); - mark.mark_type = fr.read_byte(); - mark.play_item_ref = fr.read_ushort(); - mark.time = fr.read_uint(); - mark.entry_es_pid = fr.read_ushort(); - mark.duration = fr.read_uint(); - return mark; + var aob = hdr_m.aobs.Find(a => a.isExist && block_no >= a.first && block_no <= a.last); + if (aob == null) return; + aob.atsFile.Seek((block_no - aob.first) * DVDA.BLOCK_SIZE, SeekOrigin.Begin); + if (aob.atsFile.Read(buf, offset, DVDA.BLOCK_SIZE) != DVDA.BLOCK_SIZE) + throw new Exception(); + // theZone->decryptBlock(buf_ptr); } public IAudioDecoderSettings Settings => m_settings; @@ -276,10 +164,10 @@ namespace CUETools.Codecs.MPEG.ATSI public void Close() { if (readers != null) - foreach (var rdr in readers) - { - rdr.Close(); - } + foreach (var rdr in readers) + { + rdr.Close(); + } readers = null; currentReader = null; _IO = null; @@ -297,16 +185,19 @@ namespace CUETools.Codecs.MPEG.ATSI { get { - uint totalLength = 0; - foreach (var item in hdr_m.play_item) + int title = 0; + if (!m_settings.Title.HasValue) { - if (item.num_audio == 0) continue; - uint item_duration = item.out_time - item.in_time; - if (m_settings.IgnoreShortItems && item_duration < shortItemDuration) continue; - totalLength += item_duration; + if (hdr_m.titles.Count > 1) throw new Exception("multiple titles present, please specify Title"); } - - return TimeSpan.FromSeconds(totalLength / 45000.0); + else + { + if (m_settings.Title.Value < 0 || m_settings.Title >= hdr_m.titles.Count) + throw new Exception($"Title can be 0..{hdr_m.titles.Count - 1}"); + title = m_settings.Title.Value; + } + var chapters = hdr_m.titles[title].Chapters; + return chapters[chapters.Count - 1]; } } @@ -375,7 +266,9 @@ namespace CUETools.Codecs.MPEG.ATSI } } - public MPLSHeader MPLSHeader + public List AudioTitles => hdr_m.titles.ConvertAll(x => x as IAudioTitle); + + public ATSIHeader ATSIHeader { get { @@ -383,47 +276,7 @@ namespace CUETools.Codecs.MPEG.ATSI } } - public List Chapters - { - get - { - //settings.IgnoreShortItems - var res = new List(); - if (hdr_m.play_mark.Count < 1) return res; - if (hdr_m.play_item.Count < 1) return res; - res.Add(0); - for (int i = 0; i < hdr_m.mark_count; i++) - { - ushort mark_item = hdr_m.play_mark[i].play_item_ref; - uint item_in_time = hdr_m.play_item[mark_item].in_time; - uint item_out_time = hdr_m.play_item[mark_item].out_time; - if (m_settings.IgnoreShortItems && item_out_time - item_in_time < shortItemDuration) continue; - uint item_offset = 0; - for (int j = 0; j < mark_item; j++) - { - if (hdr_m.play_item[j].num_audio == 0) continue; - uint item_duration = hdr_m.play_item[j].out_time - hdr_m.play_item[j].in_time; - if (m_settings.IgnoreShortItems && item_duration < shortItemDuration) continue; - item_offset += item_duration; - } - res.Add(hdr_m.play_mark[i].time - item_in_time + item_offset); - } - uint end_offset = 0; - for (int j = 0; j < hdr_m.play_item.Count; j++) - { - if (hdr_m.play_item[j].num_audio == 0) continue; - uint item_duration = hdr_m.play_item[j].out_time - hdr_m.play_item[j].in_time; - if (m_settings.IgnoreShortItems && item_duration < shortItemDuration) continue; - end_offset += hdr_m.play_item[j].out_time - hdr_m.play_item[j].in_time; - } - res.Add(end_offset); - while (res.Count > 1 && res[1] - res[0] < 45000) res.RemoveAt(1); - while (res.Count > 1 && res[res.Count - 1] - res[res.Count - 2] < 45000) res.RemoveAt(res.Count - 2); - return res; - } - } - - readonly static int shortItemDuration = 45000 * 30; + readonly static int SEGMENT_HEADER_BLOCKS = 16; string _path; Stream _IO; @@ -432,126 +285,60 @@ namespace CUETools.Codecs.MPEG.ATSI AudioPCMConfig pcm; List readers; IAudioSource currentReader; - MPLSHeader hdr_m; + ATSIHeader hdr_m; DecoderSettings m_settings; } - public struct MPLSPlaylistMark + public class ATSITitle : IAudioTitle { - public byte mark_id; - public byte mark_type; - public ushort play_item_ref; - public uint time; - public ushort entry_es_pid; - public uint duration; - } - - public struct MPLSStream - { - public byte stream_type; - public byte coding_type; - public ushort pid; - public byte subpath_id; - public byte subclip_id; - public byte format; - public byte rate; - public byte char_code; - public string lang; - - public string FormatString + internal ATSITitle(AudioDecoder atsi, FrameReader fr) { - get - { - if (coding_type == 0x01 - || coding_type == 0x02 - || coding_type == 0xea - || coding_type == 0x1b) - switch (format) - { - case 0: return "reserved0"; - case 1: return "480i"; - case 2: return "576i"; - case 3: return "480p"; - case 4: return "1080i"; - case 5: return "720p"; - case 6: return "1080p"; - case 7: return "576p"; - default: return format.ToString(); - } - switch (format) - { - case 0: return "reserved0"; - case 1: return "mono"; - case 2: return "reserved2"; - case 3: return "stereo"; - case 4: return "reserved4"; - case 5: return "reserved5"; - case 6: return "multi-channel"; - case 12: return "combo"; - default: return format.ToString(); - } - } + this.atsi = atsi; + + track_timestamp = new List(); + track_sector = new List(); + + unknown1 = fr.read_ushort(); + ntracks = fr.read_byte(); + nindexes = fr.read_byte(); + length_pts = fr.read_uint(); + unknown2 = fr.read_ushort(); + unknown3 = fr.read_ushort(); + track_sector_table_offset = fr.read_ushort(); + unknown4 = fr.read_ushort(); } - public int FrameRate - { - get - { - switch (rate) - { - case 1: return 24; - case 2: return 24; - case 3: return 25; - case 4: return 30; - case 6: return 50; - case 7: return 60; - default: throw new NotSupportedException(); - } - } - } + // ?Unknown (e.g. 0x0000) + public ushort unknown1; + // ???Number of tracks in title (repeated - e.g. 0x0303 for 3 tracks, 0x0b0b for 12 tracks) + public byte ntracks; + public byte nindexes; + // Length of track in PTS ticks + public uint length_pts; + // ?Unknown (e.g. 0x0000) + public ushort unknown2; + // ?Unknown (e.g. 0x0010) + public ushort unknown3; + // Byte pointer to start of sector pointers table (relative to the start of this title record) + public ushort track_sector_table_offset; + // ?Unknown (e.g. 0x0000) + public ushort unknown4; - public bool Interlaced - { - get - { - return format == 1 || format == 2 || format == 4; - } - } + public const int SIZE = 16; - public string RateString + AudioDecoder atsi; + + public ushort codec; + public uint format; + + public byte StreamId { get { - if (coding_type == 0x01 - || coding_type == 0x02 - || coding_type == 0xea - || coding_type == 0x1b) - switch (rate) - { - case 0: return "reserved0"; - case 1: return "23.976"; - case 2: return "24"; - case 3: return "25"; - case 4: return "29.97"; - case 5: return "reserved5"; - case 6: return "50"; - case 7: return "59.94"; - default: return rate.ToString(); - } - switch (rate) - { - case 0: return "reserved0"; - case 1: return "48KHz"; - case 2: return "reserved2"; - case 3: return "reserved3"; - case 4: return "96KHz"; - case 5: return "192KHz"; - //case 12: return "48/192KHz"; (core/hd) - case 12: return "192KHz"; - //case 14: return "48/96KHz"; (core/hd) - case 14: return "96KHz"; - default: return rate.ToString(); - } + if (track_sector.Count < 1) return 0; + if (track_sector[0].dvdaBlock == null) return 0; + if (track_sector[0].dvdaBlock.sh == null) return 0; + return track_sector[0].dvdaBlock.sh.stream_id; } } @@ -559,99 +346,722 @@ namespace CUETools.Codecs.MPEG.ATSI { get { - switch (coding_type) + switch (StreamId) { - case 0x01: return "MPEG-1 Video"; - case 0x02: return "MPEG-2 Video"; - case 0x03: return "MPEG-1 Audio"; - case 0x04: return "MPEG-2 Audio"; - //case 0x80: return "LPCM"; - case 0x80: return "RAW/PCM"; - case 0x81: return "AC-3"; - case 0x82: return "DTS"; - case 0x83: return "TrueHD"; - case 0x84: return "AC-3 Plus"; - case 0x85: return "DTS-HD"; - //case 0x86: return "DTS-HD Master"; - case 0x86: return "DTS Master Audio"; - case 0xea: return "VC-1"; - case 0x1b: return "h264/AVC"; - case 0x90: return "Presentation Graphics"; - case 0x91: return "Interactive Graphics"; - case 0x92: return "Text Subtitle"; - default: return coding_type.ToString(); + case DVDA.PCM_STREAM_ID: return "RAW/PCM"; + case DVDA.MLP_STREAM_ID: return "MLP"; + default: return StreamId.ToString(); + } + //switch (codec) + //{ + // case 0x000: return "RAW/PCM"; + // case 0x100: return "MLP"; + // default: return codec.ToString(); + //} + } + } + + public AudioPCMConfig PCM + { + get + { + if (track_sector.Count < 1) return null; + if (track_sector[0].dvdaBlock == null) return null; + return new AudioPCMConfig( + track_sector[0].dvdaBlock.gr1_bits, + track_sector[0].dvdaBlock.channels, + track_sector[0].dvdaBlock.gr1_frequency); + } + } + + public string RateString + { + get + { + var sr = PCM.SampleRate; + if (sr % 1000 == 0) return $"{sr / 1000}KHz"; + if (sr % 100 == 0) return $"{sr / 100}.{(sr / 100) % 10}KHz"; + return $"{sr}Hz"; + } + } + + public string FormatString + { + get + { + if (track_sector.Count < 1) return "?"; + if (track_sector[0].dvdaBlock == null) return "?"; + switch (track_sector[0].dvdaBlock.ch_assignment) + { + case 0: return "mono"; + case 1: return "stereo"; + default: return "multi-channel"; } } } - public byte CodingType + public List Chapters { get { - return coding_type; - } - } - - public byte FormatType - { - get - { - return format; - } - } - - public string LanguageString - { - get - { - CultureInfo[] cultures = CultureInfo.GetCultures(CultureTypes.AllCultures); - foreach (var culture in cultures) + //settings.IgnoreShortItems + var res = new List(); + double time_base = 90000.0; + var durations = track_timestamp.ConvertAll(track => ((long)track.len_in_pts)); + bool ignoreShortItems = true; // m_settings.IgnoreShortItems + const int shortItemDuration = 90000 * 10; + for (int i = 0; i < track_timestamp.Count; i++) { - // Exclude custom cultures. - if ((culture.CultureTypes & CultureTypes.UserCustomCulture) == CultureTypes.UserCustomCulture) - continue; - - if (culture.ThreeLetterISOLanguageName == lang) - return culture.EnglishName; + if (ignoreShortItems && durations[i] < shortItemDuration) continue; + long item_offset = 0; + for (int j = 0; j < i; j++) + { + var item_duration = durations[j]; + if (ignoreShortItems && item_duration < shortItemDuration) continue; + item_offset += item_duration; + } + res.Add(TimeSpan.FromSeconds((uint)item_offset / time_base)); } - return lang; + long end_offset = 0; + for (int j = 0; j < track_timestamp.Count; j++) + { + var item_duration = durations[j]; + if (ignoreShortItems && item_duration < shortItemDuration) continue; + end_offset += item_duration; + } + res.Add(TimeSpan.FromSeconds((uint)end_offset / time_base)); + //while (res.Count > 1 && res[1] - res[0] < 45000) res.RemoveAt(1); + //while (res.Count > 1 && res[res.Count - 1] - res[res.Count - 2] < 45000) res.RemoveAt(res.Count - 2); + return res; } } + + public List track_timestamp; + public List track_sector; } - public struct MPLSPlaylistItem + public class ATSITrackTimestamp { - public string clip_id; - public byte connection_condition; - public byte stc_id; - public uint in_time; - public uint out_time; + internal ATSITrackTimestamp(FrameReader fr) + { + sector_pointers = new List(); + track_nr = fr.read_ushort(); + unknown2 = fr.read_ushort(); + pg_id = fr.read_byte(); + unknown3 = fr.read_byte(); + first_pts = fr.read_uint(); + len_in_pts = fr.read_uint(); + padding1 = fr.read_ushort(); + padding2 = fr.read_uint(); + } - public byte num_video; - public byte num_audio; - public byte num_pg; - public byte num_ig; - public byte num_secondary_audio; - public byte num_secondary_video; - public byte num_pip_pg; - public List video; - public List audio; - public List pg; + // ?Unknown(e.g. 0xc010 for first track, and 0x0010 for subsequent) + public ushort track_nr; + // ?Unknown(e.g. 0x0000) + public ushort unknown2; + // Track number in title + public byte pg_id; + // ?Unknown(e.g. 0x00) + public byte unknown3; + // First PTS of track + public uint first_pts; + //Length of track in PTS ticks + public uint len_in_pts; + //Padding(zero) + public ushort padding1; + //Padding(zero) + public uint padding2; + + public const int SIZE = 20; + + public List sector_pointers; } - public struct MPLSHeader + public class DVDABlock { - public uint type_indicator; - public uint type_indicator2; - public uint list_pos; - public uint mark_pos; - public uint ext_pos; + public DVDABlock(byte[] p_blocks) + { + head_buf = new byte[p_blocks.Length]; + head_len = 0; + scrambled = false; + int n_blocks = p_blocks.Length / 2048; + for (int i = 0; i < n_blocks; i++) + getPS1Payload(p_blocks, i * DVDA.BLOCK_SIZE); + if (head_len > 0) + getPS1Params(head_buf, head_len); + } - public ushort list_count; - public ushort sub_count; - public ushort mark_count; + void getPS1Payload(byte[] p_block, int offset, bool ignore_scrambling = true) + { + int i_ps1_body; + int i_curr = offset; + bool scrambling_checked = false; + if (p_block[i_curr] != 0x00 || p_block[i_curr + 1] != 0x00 || p_block[i_curr + 2] != 0x01 || p_block[i_curr + 3] != 0xBA) + return; + // scrambled = false; + i_curr += 14 + (p_block[i_curr + 13] & 0x07); + while (i_curr < offset + DVDA.BLOCK_SIZE) + { + int pes_len = (p_block[i_curr + 4] << 8) + p_block[i_curr + 5]; + if (p_block[i_curr + 0] != 0x00 || p_block[i_curr + 1] != 0x00 || p_block[i_curr + 2] != 0x01) + break; + uint pes_sid = p_block[i_curr + 3]; + if (pes_sid == 0xbd) + { // check for private stream 1 + if (!scrambling_checked && (p_block[i_curr + 6] & 0x30) != 0) + scrambled = true; // check for pes_scrambling_control + scrambling_checked = true; + int i_ps1_header = i_curr + 9 + p_block[i_curr + 8]; + int i_ps1_end = i_curr + 6 + pes_len; + if (scrambled && !ignore_scrambling && (i_ps1_end - offset > 127)) + throw new Exception("Block scrambled"); + i_ps1_body = i_ps1_header + ((head_len > 0) ? getSubstreamHeaderLen(p_block, i_ps1_header, i_ps1_end - i_ps1_header) : 0); + int ps1_body_len = i_ps1_end - i_ps1_body; + if (ps1_body_len > 0) + { + Array.Copy(p_block, i_ps1_body, head_buf, head_len, ps1_body_len); + head_len += ps1_body_len; + } + } + i_curr += 6 + pes_len; + } + } - public List play_item; - public List play_mark; + unsafe void getPS1Params(byte[] ps1_header, int ps1_len) + { + ch_assignment = -1; + gr1_frequency = 0; + gr1_bits = 0; + gr2_frequency = 0; + gr2_bits = 0; + vbr = false; + peak_bitrate = 0; + substreams = 0; + cci = 0; + if (ps1_len == 0) + return; + fixed (byte* p_ps1_header = &ps1_header[0]) + { + var fr = new FrameReader(p_ps1_header, ps1_len); + sh = new SUB_HEADER(fr); + switch (sh.stream_id) + { + case DVDA.PCM_STREAM_ID: + { + if (sh.extra_len < PCM_EXTRAHEADER.SIZE) break; + PCM_EXTRAHEADER pcm_ehdr = new PCM_EXTRAHEADER(fr); + cci = pcm_ehdr.cci; + ch_assignment = pcm_ehdr.channel_assignment; + decode_grp1_bits(pcm_ehdr.group1_bits); + decode_grp2_bits(pcm_ehdr.group2_bits); + decode_grp1_freq(pcm_ehdr.group1_freq); + decode_grp2_freq(pcm_ehdr.group2_freq); + vbr = false; + peak_bitrate = gr1_channels * gr1_frequency * gr1_bits + gr2_channels * gr2_frequency * gr2_bits; + substreams = 1; + break; + } + + case DVDA.MLP_STREAM_ID: + { + if (sh.extra_len < MLP_EXTRAHEADER.SIZE) break; + MLP_EXTRAHEADER mlp_ehdr = new MLP_EXTRAHEADER(fr); + fr.skip(sh.extra_len - MLP_EXTRAHEADER.SIZE); + while (fr.Length > MLP_LINK.SIZE + MLP_SIGNATURE.SIZE) + { + FrameReader fr1 = new FrameReader(fr, MLP_LINK.SIZE + MLP_SIGNATURE.SIZE); + fr1.skip(MLP_LINK.SIZE); + MLP_SIGNATURE mlp_sign = new MLP_SIGNATURE(fr1); + if (mlp_sign.signature1 != 0xF8726FBB /*|| p_mlp_sign->signature2 != 0xB752*/) + { + fr.skip(1); + continue; + } + cci = mlp_ehdr.cci; + fr.skip(MLP_LINK.SIZE); + ch_assignment = mlp_sign.channel_assignment; + decode_grp1_bits(mlp_sign.group1_bits); + decode_grp2_bits(mlp_sign.group2_bits); + decode_grp1_freq(mlp_sign.group1_freq); + decode_grp2_freq(mlp_sign.group2_freq); + vbr = (mlp_sign.bitrate & 0x8000) != 0; + peak_bitrate = ((mlp_sign.bitrate & ~0x8000) * gr1_frequency + 8) >> 4; + substreams = mlp_sign.substreams; + break; + } + break; + } + } + } + } + + unsafe int getSubstreamHeaderLen(byte[] substream_buf, int substream_off, int substream_len) + { + if (substream_len <= 4) return 0; + fixed (byte* p = &substream_buf[substream_off]) + { + var fr = new FrameReader(p, substream_len); + var hdr = new SUB_HEADER(fr); + switch (hdr.stream_id) + { + case DVDA.PCM_STREAM_ID: + case DVDA.MLP_STREAM_ID: + return SUB_HEADER.SIZE + hdr.extra_len; + default: + return 0; + } + } + } + + public int gr1_channels => ChannelsInMask(DVDA.grp1_ch_table[ch_assignment]); + + public int gr2_channels => ChannelsInMask(DVDA.grp2_ch_table[ch_assignment]); + + public int channels => gr1_channels + gr2_channels; + + public void decode_grp1_bits(byte b) + { + switch (b) + { + case 0: + gr1_bits = 16; + break; + case 1: + gr1_bits = 20; + break; + case 2: + gr1_bits = 24; + break; + default: + gr1_bits = 0; + break; + } + } + + public void decode_grp2_bits(byte b) + { + switch (b) + { + case 0: + gr2_bits = 16; + break; + case 1: + gr2_bits = 20; + break; + case 2: + gr2_bits = 24; + break; + case 0x0f: + default: + gr2_bits = 0; + break; + } + } + + public void decode_grp1_freq(byte b) + { + switch (b) + { + case 0: + gr1_frequency = 48000; + break; + case 1: + gr1_frequency = 96000; + break; + case 2: + gr1_frequency = 192000; + break; + case 8: + gr1_frequency = 44100; + break; + case 9: + gr1_frequency = 88200; + break; + case 0x0A: + gr1_frequency = 176400; + break; + default: + gr1_frequency = 0; + break; + } + } + + public void decode_grp2_freq(byte b) + { + switch (b) + { + case 0: + gr2_frequency = 48000; + break; + case 1: + gr2_frequency = 96000; + break; + case 8: + gr2_frequency = 44100; + break; + case 9: + gr2_frequency = 88200; + break; + case 0x0F: + default: + gr2_frequency = 0; + break; + } + } + + //uint getChannelId(int channel) + //{ + // if (channel < gr1_channels + gr2_channels) + // { + // if (channel < gr1_channels) + // return DVDA.grp1_ch_table[ch_assignment].[channel]; + // else + // return DVDA.grp2_ch_table[ch_assignment].[channel - gr1_channels]; + // } + // return 0; + //} + + //int remapChannel(int channel) + //{ + // return ch_remap[ch_assignment][channel]; + //} + + public SUB_HEADER sh; + public int head_check_ofs; + public int tail_check_ofs; + public byte[] head_buf; + public int head_len; + public int ch_assignment; + public int gr1_frequency; + public int gr1_bits; + public int gr2_frequency; + public int gr2_bits; + public bool vbr; + public int peak_bitrate; + public int substreams; + public byte cci; + + + private bool scrambled; + } + + public class ATSITrackSector + { + internal ATSITrackSector(FrameReader fr) + { + unknown4 = fr.read_uint(); + first_sector = fr.read_uint(); + last_sector = fr.read_uint(); + } + + // ?? Unknown (e.g. 0x01000000) + public uint unknown4; + // Relative sector pointer to first sector of track (relative to the start of the first .AOB file) + public uint first_sector; + // Relative sector pointer to last sector of track(relative to the start of the first.AOB file) + public uint last_sector; + + public DVDABlock dvdaBlock; + } + + public class SUB_HEADER + { + internal SUB_HEADER(FrameReader fr) + { + stream_id = fr.read_byte(); + cyclic = fr.read_byte(); + padding1 = fr.read_byte(); + extra_len = fr.read_byte(); + } + public byte stream_id; + public byte cyclic; + public byte padding1; + public byte extra_len; + + public const int SIZE = 4; + }; + + public struct PCM_EXTRAHEADER + { + internal PCM_EXTRAHEADER(FrameReader fr) + { + byte tmp; + first_audio_frame = fr.read_ushort(); + padding1 = fr.read_byte(); + group1_bits = (byte)(((tmp = fr.read_byte()) >> 4) & 0xf); + group2_bits = (byte)(tmp & 0xf); + group1_freq = (byte)(((tmp = fr.read_byte()) >> 4) & 0xf); + group2_freq = (byte)(tmp & 0xf); + padding2 = fr.read_byte(); + channel_assignment = fr.read_byte(); + padding3 = fr.read_byte(); + cci = fr.read_byte(); + } + + public ushort first_audio_frame; + public byte padding1; + public byte group2_bits;// : 4; + public byte group1_bits;// : 4; + public byte group2_freq;// : 4; + public byte group1_freq;// : 4; + public byte padding2; + public byte channel_assignment; + public byte padding3; + public byte cci; + + public const int SIZE = 9; + }; + + public class MLP_EXTRAHEADER + { + internal MLP_EXTRAHEADER(FrameReader fr) + { + fr.read_uint(); + cci = fr.read_byte(); + } + + public byte padding1; + public byte padding2; + public byte padding3; + public byte padding4; + public byte cci; + + public const int SIZE = 5; + }; + + public struct MLP_LINK + { + public ushort block_length;// : 12; + public ushort padding; + + public const int SIZE = 4; + }; + + public struct MLP_SIGNATURE + { + internal MLP_SIGNATURE(FrameReader fr) + { + byte tmp; + signature1 = fr.read_uint(); + group1_bits = (byte)(((tmp = fr.read_byte()) >> 4) & 0xf); + group2_bits = (byte)(tmp & 0xf); + group1_freq = (byte)(((tmp = fr.read_byte()) >> 4) & 0xf); + group2_freq = (byte)(tmp & 0xf); + padding1 = fr.read_byte(); + channel_assignment = fr.read_byte(); + signature2 = fr.read_ushort(); + padding2 = fr.read_uint(); + bitrate = fr.read_ushort(); + substreams = (byte)(fr.read_byte() & 0xf); + } + + public uint signature1; + public byte group2_bits;// : 4; + public byte group1_bits;// : 4; + public byte group2_freq;// : 4; + public byte group1_freq;// : 4; + public byte padding1; + public byte channel_assignment; + public ushort signature2; + public uint padding2; + public ushort bitrate; + public byte substreams;// : 4; + + public const int SIZE = 17; + }; + + public class ATSAudioFormat + { + public ushort audio_type; + } + + public class DVDAAOBFile + { + public string fileName; + public Stream atsFile; + public uint first; + public uint last; + public bool isExist; + } + + // Audio Title Set Information Management Table. + public class ATSIMAT + { + internal ATSIMAT(FrameReader fr) + { + ats_audio_format = new ATSAudioFormat[8]; + ats_downmix_matrix = new ushort[16, 8]; + ats_identifier = fr.read_string(12); + if (ats_identifier != "DVDAUDIO-ATS") throw new NotSupportedException(); + ats_last_sector = fr.read_uint(); + atsi_last_sector = fr.read_uint(); + ats_category = fr.read_uint(); + atsi_last_byte = fr.read_uint(); + atsm_vobs = fr.read_uint(); + atstt_vobs = fr.read_uint(); + ats_ptt_srpt = fr.read_uint(); + ats_pgcit = fr.read_uint(); + atsm_pgci_ut = fr.read_uint(); + ats_tmapt = fr.read_uint(); + atsm_c_adt = fr.read_uint(); + atsm_vobu_admap = fr.read_uint(); + ats_c_adt = fr.read_uint(); + ats_vobu_admap = fr.read_uint(); + for (int i = 0; i < 8; i++) + { + ats_audio_format[i] = new ATSAudioFormat(); + ats_audio_format[i].audio_type = fr.read_ushort(); + } + for (int i = 0; i < 16; i++) + for (int j = 0; j < 8; j++) + ats_downmix_matrix[i, j] = fr.read_ushort(); + } + + public string ats_identifier; // [12]; + public uint ats_last_sector; + public uint atsi_last_sector; + public uint ats_category; + public uint atsi_last_byte; + public uint atsm_vobs; + public uint atstt_vobs; + public uint ats_ptt_srpt; + public uint ats_pgcit; + public uint atsm_pgci_ut; + public uint ats_tmapt; + public uint atsm_c_adt; + public uint atsm_vobu_admap; + public uint ats_c_adt; + public uint ats_vobu_admap; + public ATSAudioFormat[] ats_audio_format; + public ushort[,] ats_downmix_matrix; + } + + public class ATSIHeader + { + internal ATSIHeader(FrameReader fr) + { + titles = new List(); + aobs = new List(); + ats_title_idx = new List(); + + var frMat = new FrameReader(fr, fr.Length); + mat = new ATSIMAT(frMat); + + //if (mat.atsm_vobs == 0) + // dvdaTitlesetType = DVDTitlesetAudio; + //else + // dvdaTitlesetType = DVDTitlesetVideo; + // aobs_last_sector = mat.ats_last_sector - 2 * (mat.atsi_last_sector + 1); + + fr.skip(2048); + nr_of_titles = fr.read_ushort(); + padding = fr.read_ushort(); + last_byte = fr.read_uint(); + for (int i = 0; i < nr_of_titles; i++) + { + // ?? Unknown - e.g. 0x8100 for first title, 0x8200 for second etc etc + fr.skip(2); + // ??unknown (e.g. 0x0000 or 0x0100) + fr.skip(2); + // Byte offset to record in following table (relative to the start of this sector) + ats_title_idx.Add(fr.read_uint()); + } + } + + ATSIMAT mat; + + // audio_pgcit_t + // Number of titles in the ATS + public ushort nr_of_titles; + // Padding (zero) + public ushort padding; + // Address of last byte in this table + public uint last_byte; + + public List ats_title_idx; + public List titles; + public List aobs; + } + + public static class DVDA + { + public static SpeakerConfig[] grp1_ch_table = + { + SpeakerConfig.DVDAUDIO_GR1_0, + SpeakerConfig.DVDAUDIO_GR1_1, + SpeakerConfig.DVDAUDIO_GR1_2, + SpeakerConfig.DVDAUDIO_GR1_3, + SpeakerConfig.DVDAUDIO_GR1_4, + SpeakerConfig.DVDAUDIO_GR1_5, + SpeakerConfig.DVDAUDIO_GR1_6, + SpeakerConfig.DVDAUDIO_GR1_7, + SpeakerConfig.DVDAUDIO_GR1_8, + SpeakerConfig.DVDAUDIO_GR1_9, + SpeakerConfig.DVDAUDIO_GR1_10, + SpeakerConfig.DVDAUDIO_GR1_11, + SpeakerConfig.DVDAUDIO_GR1_12, + SpeakerConfig.DVDAUDIO_GR1_13, + SpeakerConfig.DVDAUDIO_GR1_14, + SpeakerConfig.DVDAUDIO_GR1_15, + SpeakerConfig.DVDAUDIO_GR1_16, + SpeakerConfig.DVDAUDIO_GR1_17, + SpeakerConfig.DVDAUDIO_GR1_18, + SpeakerConfig.DVDAUDIO_GR1_19, + SpeakerConfig.DVDAUDIO_GR1_20, + }; + + public static SpeakerConfig[] grp2_ch_table = + { + SpeakerConfig.DVDAUDIO_GR2_0, + SpeakerConfig.DVDAUDIO_GR2_1, + SpeakerConfig.DVDAUDIO_GR2_2, + SpeakerConfig.DVDAUDIO_GR2_3, + SpeakerConfig.DVDAUDIO_GR2_4, + SpeakerConfig.DVDAUDIO_GR2_5, + SpeakerConfig.DVDAUDIO_GR2_6, + SpeakerConfig.DVDAUDIO_GR2_7, + SpeakerConfig.DVDAUDIO_GR2_8, + SpeakerConfig.DVDAUDIO_GR2_9, + SpeakerConfig.DVDAUDIO_GR2_10, + SpeakerConfig.DVDAUDIO_GR2_11, + SpeakerConfig.DVDAUDIO_GR2_12, + SpeakerConfig.DVDAUDIO_GR2_13, + SpeakerConfig.DVDAUDIO_GR2_14, + SpeakerConfig.DVDAUDIO_GR2_15, + SpeakerConfig.DVDAUDIO_GR2_16, + SpeakerConfig.DVDAUDIO_GR2_17, + SpeakerConfig.DVDAUDIO_GR2_18, + SpeakerConfig.DVDAUDIO_GR2_19, + SpeakerConfig.DVDAUDIO_GR2_20, + }; + + //int ch_remap[][] = { + // // Canonical order: Lf Rf C LFE Ls Rs S + // /* 0 */ { 0, -1, -1, -1, -1, -1, -1}, + // /* 1 */ { 0, 1, -1, -1, -1, -1, -1}, + // /* 2 */ { 0, 1, 2, -1, -1, -1, -1}, + // /* 3 */ { 0, 1, 2, 3, -1, -1, -1}, + // /* 4 */ { 0, 1, 2, -1, -1, -1, -1}, + // /* 5 */ { 0, 1, 2, 3, -1, -1, -1}, + // /* 6 */ { 0, 1, 2, 3, 4, -1, -1}, + // /* 7 */ { 0, 1, 2, -1, -1, -1, -1}, + // /* 8 */ { 0, 1, 2, 3, -1, -1, -1}, + // /* 9 */ { 0, 1, 2, 3, 4, -1, -1}, + // /* 10 */ { 0, 1, 2, 3, -1, -1, -1}, + // /* 11 */ { 0, 1, 2, 3, 4, -1, -1}, + // /* 12 */ { 0, 1, 2, 3, 4, 5, -1}, + // /* 13 */ { 0, 1, 2, 3, -1, -1, -1}, + // /* 14 */ { 0, 1, 2, 3, 4, -1, -1}, + // /* 15 */ { 0, 1, 2, 3, -1, -1, -1}, + // /* 16 */ { 0, 1, 2, 3, 4, -1, -1}, + // /* 17 */ { 0, 1, 2, 3, 4, 5, -1}, + // /* 18 */ { 0, 1, 3, 4, 2, -1, -1}, + // /* 19 */ { 0, 1, 3, 4, 2, -1, -1}, + // /* 20 */ { 0, 1, 4, 5, 2, 3, -1}, + //}; + + public const int BLOCK_SIZE = 2048; + public const byte PCM_STREAM_ID = 0xa0; + public const byte MLP_STREAM_ID = 0xa1; } } diff --git a/CUETools.Codecs.MPEG/ATSI/DecoderSettings.cs b/CUETools.Codecs.MPEG/ATSI/DecoderSettings.cs index 0bf4597..445fe22 100644 --- a/CUETools.Codecs.MPEG/ATSI/DecoderSettings.cs +++ b/CUETools.Codecs.MPEG/ATSI/DecoderSettings.cs @@ -41,5 +41,8 @@ namespace CUETools.Codecs.MPEG.ATSI [Browsable(false)] public int? StreamId { get; set; } + + [Browsable(false)] + public int? Title { get; set; } } } diff --git a/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs b/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs index d234aea..07aec49 100644 --- a/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs +++ b/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs @@ -5,7 +5,7 @@ using System.IO; namespace CUETools.Codecs.MPEG.MPLS { - public class AudioDecoder : IAudioSource + public class AudioDecoder : IAudioSource, IAudioContainer { public unsafe AudioDecoder(DecoderSettings settings, string path, Stream IO) { @@ -13,6 +13,7 @@ namespace CUETools.Codecs.MPEG.MPLS _path = path; _IO = IO != null ? IO : new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000); int length = (int)_IO.Length; + if (length > 0x100000) throw new Exception("File too big"); contents = new byte[length]; if (_IO.Read(contents, 0, length) != length) throw new Exception(""); fixed (byte* ptr = &contents[0]) @@ -383,15 +384,30 @@ namespace CUETools.Codecs.MPEG.MPLS } } - public List Chapters + public List AudioTitles + { + get + { + var titles = new List(); + foreach (var item in hdr_m.play_item) + foreach (var audio in item.audio) + { + if (audio.coding_type != 0x80 /* LPCM */) continue; + titles.Add(new AudioTitle(this, audio.pid)); + } + return titles; + } + } + + public List Chapters { get { //settings.IgnoreShortItems - var res = new List(); + var res = new List(); if (hdr_m.play_mark.Count < 1) return res; if (hdr_m.play_item.Count < 1) return res; - res.Add(0); + res.Add(TimeSpan.Zero); for (int i = 0; i < hdr_m.mark_count; i++) { ushort mark_item = hdr_m.play_mark[i].play_item_ref; @@ -406,7 +422,7 @@ namespace CUETools.Codecs.MPEG.MPLS if (m_settings.IgnoreShortItems && item_duration < shortItemDuration) continue; item_offset += item_duration; } - res.Add(hdr_m.play_mark[i].time - item_in_time + item_offset); + res.Add(TimeSpan.FromSeconds((hdr_m.play_mark[i].time - item_in_time + item_offset) / 45000.0)); } uint end_offset = 0; for (int j = 0; j < hdr_m.play_item.Count; j++) @@ -416,9 +432,9 @@ namespace CUETools.Codecs.MPEG.MPLS if (m_settings.IgnoreShortItems && item_duration < shortItemDuration) continue; end_offset += hdr_m.play_item[j].out_time - hdr_m.play_item[j].in_time; } - res.Add(end_offset); - while (res.Count > 1 && res[1] - res[0] < 45000) res.RemoveAt(1); - while (res.Count > 1 && res[res.Count - 1] - res[res.Count - 2] < 45000) res.RemoveAt(res.Count - 2); + res.Add(TimeSpan.FromSeconds(end_offset / 45000.0)); + while (res.Count > 1 && res[1] - res[0] < TimeSpan.FromSeconds(1.0)) res.RemoveAt(1); + while (res.Count > 1 && res[res.Count - 1] - res[res.Count - 2] < TimeSpan.FromSeconds(1.0)) res.RemoveAt(res.Count - 2); return res; } } @@ -436,6 +452,20 @@ namespace CUETools.Codecs.MPEG.MPLS DecoderSettings m_settings; } + public class AudioTitle : IAudioTitle + { + public AudioTitle(AudioDecoder source, int pid) + { + this.source = source; + this.pid = pid; + } + + public List Chapters => source.Chapters; + + AudioDecoder source; + int pid; + } + public struct MPLSPlaylistMark { public byte mark_id; diff --git a/CUETools.Codecs.MPEG/TsStream.cs b/CUETools.Codecs.MPEG/TsStream.cs index 7c95f50..040f62b 100644 --- a/CUETools.Codecs.MPEG/TsStream.cs +++ b/CUETools.Codecs.MPEG/TsStream.cs @@ -56,4 +56,13 @@ namespace CUETools.Codecs.MPEG savedBufferSize = 0; } }; + + public class AudioDescription + { + public int StreamId; + public string CodecString; + public string LanguageString; + public string FormatString; + public string RateString; + }; } diff --git a/CUETools.Codecs/AudioPCMConfig.cs b/CUETools.Codecs/AudioPCMConfig.cs index 0b736f1..b574670 100644 --- a/CUETools.Codecs/AudioPCMConfig.cs +++ b/CUETools.Codecs/AudioPCMConfig.cs @@ -32,7 +32,51 @@ KSAUDIO_SPEAKER_5POINT1 = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT), KSAUDIO_SPEAKER_5POINT1_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT), KSAUDIO_SPEAKER_7POINT1 = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER), - KSAUDIO_SPEAKER_7POINT1_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT) + KSAUDIO_SPEAKER_7POINT1_SURROUND = (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT), + + DVDAUDIO_GR1_0 = SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_1 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_2 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_3 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_4 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_5 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_6 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_7 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_8 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_9 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_10 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_11 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_12 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, + DVDAUDIO_GR1_13 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_14 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_15 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_16 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_17 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER, + DVDAUDIO_GR1_18 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR1_19 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR1_20 = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + + DVDAUDIO_GR2_0 = 0, + DVDAUDIO_GR2_1 = 0, + DVDAUDIO_GR2_2 = SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_3 = SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_4 = SPEAKER_LOW_FREQUENCY, + DVDAUDIO_GR2_5 = SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_6 = SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_7 = SPEAKER_FRONT_CENTER, + DVDAUDIO_GR2_8 = SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_9 = SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_10 = SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY, + DVDAUDIO_GR2_11 = SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_12 = SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_13 = SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_14 = SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_15 = SPEAKER_LOW_FREQUENCY, + DVDAUDIO_GR2_16 = SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_CENTER, + DVDAUDIO_GR2_17 = SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + DVDAUDIO_GR2_18 = SPEAKER_LOW_FREQUENCY, + DVDAUDIO_GR2_19 = SPEAKER_FRONT_CENTER, + DVDAUDIO_GR2_20 = SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY, } private int _bitsPerSample; @@ -46,6 +90,17 @@ public int BlockAlign { get { return _channelCount * ((_bitsPerSample + 7) / 8); } } public SpeakerConfig ChannelMask { get { return _channelMask; } } public bool IsRedBook { get { return _bitsPerSample == 16 && _channelCount == 2 && _sampleRate == 44100; } } + public static int ChannelsInMask(SpeakerConfig mask) + { + int count = 0; + while (mask != 0) + { + count++; + mask &= (mask - 1); + } + return count; + } + public static SpeakerConfig GetDefaultChannelMask(int channelCount) { switch (channelCount) diff --git a/CUETools.Codecs/IAudioSource.cs b/CUETools.Codecs/IAudioSource.cs index 324002f..472e8f1 100644 --- a/CUETools.Codecs/IAudioSource.cs +++ b/CUETools.Codecs/IAudioSource.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; namespace CUETools.Codecs { @@ -17,4 +18,29 @@ namespace CUETools.Codecs int Read(AudioBuffer buffer, int maxLength); void Close(); } + + public interface IAudioTitle + { + List Chapters { get; } + //IAudioSource Open { get; } + } + + public interface IAudioContainer + { + List AudioTitles { get; } + } + + public class NoContainerAudioTitle : IAudioTitle + { + public NoContainerAudioTitle(IAudioSource source) { this.source = source; } + public List Chapters => new List { TimeSpan.Zero, source.Duration }; + IAudioSource source; + } + + public class NoContainer : IAudioContainer + { + public NoContainer(IAudioSource source) { this.source = source; } + public List AudioTitles => new List { new NoContainerAudioTitle(source) }; + IAudioSource source; + } } diff --git a/CUETools.eac3to/Program.cs b/CUETools.eac3to/Program.cs index ce485f6..e2478d9 100644 --- a/CUETools.eac3to/Program.cs +++ b/CUETools.eac3to/Program.cs @@ -4,6 +4,7 @@ using CUETools.CTDB; using CUETools.Processor; using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Text; @@ -49,6 +50,7 @@ namespace CUETools.eac3to string encoderName = null; string encoderFormat = null; AudioEncoderType audioEncoderType = AudioEncoderType.NoAudio; + var decoderOptions = new Dictionary(); bool queryMeta = false; for (int arg = 0; arg < args.Length; arg++) @@ -61,14 +63,20 @@ namespace CUETools.eac3to encoderFormat = args[arg]; else if ((args[arg] == "-p" || args[arg] == "--padding") && ++arg < args.Length) ok = int.TryParse(args[arg], out padding); - else if ((args[arg] == "-m" || args[arg] == "--mode") && ++arg < args.Length) - encoderMode = args[arg]; + else if ((args[arg] == "-m" || args[arg] == "--mode") && arg + 1 < args.Length) + encoderMode = args[++arg]; else if (args[arg] == "--lossy") audioEncoderType = AudioEncoderType.Lossy; else if (args[arg] == "--lossless") audioEncoderType = AudioEncoderType.Lossless; else if (args[arg] == "--ctdb") queryMeta = true; + else if (args[arg] == "--decoder-option" && arg + 2 < args.Length) + { + var optionName = args[++arg]; + var optionValue = args[++arg]; + decoderOptions.Add(optionName, optionValue); + } else if ((args[arg][0] != '-' || args[arg] == "-") && sourceFile == null) sourceFile = args[arg]; else if ((args[arg][0] != '-' || args[arg] == "-") && sourceFile != null && destFile == null) @@ -122,10 +130,11 @@ namespace CUETools.eac3to #endif { IAudioSource audioSource = null; + IAudioContainer audioContainer = null; IAudioDest audioDest = null; var videos = new List(); - var audios = new List(); - List chapters; + var audios = new List(); + List chapters = null; TagLib.UserDefined.AdditionalFileTypes.Config = config; #if !DEBUG @@ -134,19 +143,49 @@ namespace CUETools.eac3to { if (true) { - var mpls = new Codecs.MPEG.MPLS.AudioDecoder(new Codecs.MPEG.MPLS.DecoderSettings(), sourceFile, null); - audioSource = mpls; + IAudioDecoderSettings decoderSettings = null; + if (Path.GetExtension(sourceFile) == ".mpls") + { + decoderSettings = new Codecs.MPEG.MPLS.DecoderSettings(); + } else + { + decoderSettings = new Codecs.MPEG.ATSI.DecoderSettings(); + } + foreach (var decOpt in decoderOptions) + { + var property = TypeDescriptor.GetProperties(decoderSettings).Find(decOpt.Key, true); + if (property == null) + throw new Exception($"{decoderSettings.Name} {decoderSettings.Extension} decoder settings object (of type {decoderSettings.GetType().FullName}) doesn't have a property named {decOpt.Key}."); + property.SetValue(decoderSettings, + TypeDescriptor.GetConverter(property.PropertyType).ConvertFromString(decOpt.Value)); + } + audioSource = decoderSettings.Open(sourceFile); + audioContainer = audioSource as IAudioContainer; + if (audioContainer == null) audioContainer = new NoContainer(audioSource); Console.ForegroundColor = ConsoleColor.White; int frameRate = 0; bool interlaced = false; - chapters = mpls.Chapters; - mpls.MPLSHeader.play_item.ForEach(i => i.video.ForEach(v => { if (!videos.Exists(v1 => v1.pid == v.pid)) videos.Add(v); })); - mpls.MPLSHeader.play_item.ForEach(i => i.audio.ForEach(v => { if (!audios.Exists(v1 => v1.pid == v.pid)) audios.Add(v); })); + audioContainer.AudioTitles.ForEach(t => chapters = t.Chapters); + if (audioSource is Codecs.MPEG.MPLS.AudioDecoder) + { + var mpls = audioSource as Codecs.MPEG.MPLS.AudioDecoder; + mpls.MPLSHeader.play_item.ForEach(i => i.video.ForEach(v => { if (!videos.Exists(v1 => v1.pid == v.pid)) videos.Add(v); })); + mpls.MPLSHeader.play_item.ForEach(i => i.audio.ForEach(v => { if (!audios.Exists(v1 => v1.StreamId == v.pid)) audios.Add(new Codecs.MPEG.AudioDescription() { StreamId = v.pid, CodecString = v.CodecString, LanguageString = v.LanguageString, FormatString = v.FormatString, RateString = v.RateString }); })); + } + else + if (audioSource is Codecs.MPEG.ATSI.AudioDecoder) + { + var atsi = audioSource as Codecs.MPEG.ATSI.AudioDecoder; + atsi.ATSIHeader.titles.ForEach(t => audios.Add(new Codecs.MPEG.AudioDescription() { StreamId = 0/*!*/, CodecString = t.CodecString, FormatString = t.FormatString, RateString = t.RateString })); + } videos.ForEach(v => { frameRate = v.FrameRate; interlaced = v.Interlaced; }); - Console.Error.WriteLine("M2TS, {0} video track{1}, {2} audio track{3}, {4}, {5}{6}", - videos.Count, videos.Count > 1 ? "s" : "", - audios.Count, audios.Count > 1 ? "s" : "", - CDImageLayout.TimeToString(audioSource.Duration, "{0:0}:{1:00}:{2:00}"), frameRate * (interlaced ? 2 : 1), interlaced ? "i" : "p"); + Console.Error.Write($@"M2TS, { + videos.Count} video track{(videos.Count != 1 ? "s" : "")}, { + audios.Count} audio track{(audios.Count != 1 ? "s" : "")}, { + CDImageLayout.TimeToString(audioSource.Duration, "{0:0}:{1:00}:{2:00}")}, { + (frameRate * (interlaced ? 2 : 1))}{ + (interlaced ? "i" : "p")}"); + Console.Error.WriteLine(); //foreach (var item in mpls.MPLSHeader.play_item) //Console.Error.WriteLine("{0}.m2ts", item.clip_id); { @@ -184,7 +223,7 @@ namespace CUETools.eac3to string strtoc = ""; for (int i = 0; i < chapters.Count; i++) - strtoc += string.Format(" {0}", chapters[i] / 600); + strtoc += string.Format(" {0}", (int)Math.Round((chapters[i].TotalSeconds * 75))); strtoc = strtoc.Substring(1); CDImageLayout toc = new CDImageLayout(strtoc); CTDBResponseMeta meta = null; @@ -235,9 +274,9 @@ namespace CUETools.eac3to for (int i = 0; i < chapters.Count - 1; i++) { sw.WriteLine("CHAPTER{0:00}={1}", i + 1, - CDImageLayout.TimeToString(TimeSpan.FromSeconds(chapters[i] / 45000.0))); - if (meta != null && meta.track.Length >= toc[i+1].Number) - sw.WriteLine("CHAPTER{0:00}NAME={1}", i + 1, meta.track[(int)toc[i+1].Number - 1].name); + CDImageLayout.TimeToString(chapters[i])); + if (meta != null && meta.track.Length >= toc[i + 1].Number) + sw.WriteLine("CHAPTER{0:00}NAME={1}", i + 1, meta.track[(int)toc[i + 1].Number - 1].name); else sw.WriteLine("CHAPTER{0:00}NAME=", i + 1); } @@ -306,14 +345,14 @@ namespace CUETools.eac3to throw new Exception("Unknown encoder format: " + destFile); } + if (stream - chapterStreams <= videos.Count) + throw new Exception("Video extraction not supported."); + if (stream - chapterStreams - videos.Count > audios.Count) + throw new Exception(string.Format("The source file doesn't contain a track with the number {0}.", stream)); + int streamId = audios[stream - chapterStreams - videos.Count - 1].StreamId; if (audioSource is Codecs.MPEG.MPLS.AudioDecoder) { - if (stream - chapterStreams <= videos.Count) - throw new Exception("Video extraction not supported."); - if (stream - chapterStreams - videos.Count > audios.Count) - throw new Exception(string.Format("The source file doesn't contain a track with the number {0}.", stream)); - int pid = audios[stream - chapterStreams - videos.Count - 1].pid; - (audioSource.Settings as Codecs.MPEG.MPLS.DecoderSettings).StreamId = pid; + (audioSource.Settings as Codecs.MPEG.MPLS.DecoderSettings).StreamId = streamId; } } diff --git a/CUETools.eac3ui/MainWindow.xaml.cs b/CUETools.eac3ui/MainWindow.xaml.cs index 213530b..d093bc2 100644 --- a/CUETools.eac3ui/MainWindow.xaml.cs +++ b/CUETools.eac3ui/MainWindow.xaml.cs @@ -125,7 +125,7 @@ namespace BluTools { string strtoc = ""; for (int i = 0; i < chapters.Count; i++) - strtoc += string.Format(" {0}", chapters[i] / 600); + strtoc += string.Format(" {0}", (int) Math.Round((chapters[i].TotalSeconds * 75))); strtoc = strtoc.Substring(1); CDImageLayout toc = new CDImageLayout(strtoc); ctdb = new CUEToolsDB(toc, null); From 47d1b098146ebd8b184b93a675daed924a0b2f83 Mon Sep 17 00:00:00 2001 From: Grigory Chudov Date: Mon, 30 Apr 2018 22:32:49 -0400 Subject: [PATCH 4/4] IAudioTitleSet --- CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs | 96 +++++++++-------------- CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs | 44 ++++++++++- CUETools.Codecs.MPEG/TsStream.cs | 9 --- CUETools.Codecs/IAudioSource.cs | 48 ++++++++++-- CUETools.eac3to/Program.cs | 22 ++---- 5 files changed, 127 insertions(+), 92 deletions(-) diff --git a/CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs b/CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs index c6adee5..f559aa3 100644 --- a/CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs +++ b/CUETools.Codecs.MPEG/ATSI/AudioDecoder.cs @@ -6,7 +6,7 @@ using static CUETools.Codecs.AudioPCMConfig; namespace CUETools.Codecs.MPEG.ATSI { - public class AudioDecoder : IAudioSource, IAudioContainer + public class AudioDecoder : IAudioSource, IAudioTitleSet { public unsafe AudioDecoder(DecoderSettings settings, string path, Stream IO) { @@ -331,7 +331,7 @@ namespace CUETools.Codecs.MPEG.ATSI public ushort codec; public uint format; - public byte StreamId + public int StreamId { get { @@ -342,64 +342,6 @@ namespace CUETools.Codecs.MPEG.ATSI } } - public string CodecString - { - get - { - switch (StreamId) - { - case DVDA.PCM_STREAM_ID: return "RAW/PCM"; - case DVDA.MLP_STREAM_ID: return "MLP"; - default: return StreamId.ToString(); - } - //switch (codec) - //{ - // case 0x000: return "RAW/PCM"; - // case 0x100: return "MLP"; - // default: return codec.ToString(); - //} - } - } - - public AudioPCMConfig PCM - { - get - { - if (track_sector.Count < 1) return null; - if (track_sector[0].dvdaBlock == null) return null; - return new AudioPCMConfig( - track_sector[0].dvdaBlock.gr1_bits, - track_sector[0].dvdaBlock.channels, - track_sector[0].dvdaBlock.gr1_frequency); - } - } - - public string RateString - { - get - { - var sr = PCM.SampleRate; - if (sr % 1000 == 0) return $"{sr / 1000}KHz"; - if (sr % 100 == 0) return $"{sr / 100}.{(sr / 100) % 10}KHz"; - return $"{sr}Hz"; - } - } - - public string FormatString - { - get - { - if (track_sector.Count < 1) return "?"; - if (track_sector[0].dvdaBlock == null) return "?"; - switch (track_sector[0].dvdaBlock.ch_assignment) - { - case 0: return "mono"; - case 1: return "stereo"; - default: return "multi-channel"; - } - } - } - public List Chapters { get @@ -436,6 +378,40 @@ namespace CUETools.Codecs.MPEG.ATSI } } + public AudioPCMConfig PCM + { + get + { + if (track_sector.Count < 1) return null; + if (track_sector[0].dvdaBlock == null) return null; + return new AudioPCMConfig( + track_sector[0].dvdaBlock.gr1_bits, + track_sector[0].dvdaBlock.channels, + track_sector[0].dvdaBlock.gr1_frequency); + } + } + + public string Codec + { + get + { + switch (StreamId) + { + case DVDA.PCM_STREAM_ID: return "RAW/PCM"; + case DVDA.MLP_STREAM_ID: return "MLP"; + default: return StreamId.ToString(); + } + //switch (codec) + //{ + // case 0x000: return "RAW/PCM"; + // case 0x100: return "MLP"; + // default: return codec.ToString(); + //} + } + } + + public string Language => ""; + public List track_timestamp; public List track_sector; } diff --git a/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs b/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs index 07aec49..61798ab 100644 --- a/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs +++ b/CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs @@ -5,7 +5,7 @@ using System.IO; namespace CUETools.Codecs.MPEG.MPLS { - public class AudioDecoder : IAudioSource, IAudioContainer + public class AudioDecoder : IAudioSource, IAudioTitleSet { public unsafe AudioDecoder(DecoderSettings settings, string path, Stream IO) { @@ -392,7 +392,7 @@ namespace CUETools.Codecs.MPEG.MPLS foreach (var item in hdr_m.play_item) foreach (var audio in item.audio) { - if (audio.coding_type != 0x80 /* LPCM */) continue; + //if (audio.coding_type != 0x80 /* LPCM */) continue; titles.Add(new AudioTitle(this, audio.pid)); } return titles; @@ -461,6 +461,44 @@ namespace CUETools.Codecs.MPEG.MPLS } public List Chapters => source.Chapters; + public AudioPCMConfig PCM + { + get + { + var s = FirstStream; + int channelCount = s.format == 1 ? 1 : s.format == 3 ? 2 : s.format == 6 ? 5 : 0; + int sampleRate = s.rate == 1 ? 48000 : s.rate == 4 ? 96000 : s.rate == 5 ? 192000 : s.rate == 12 ? 192000 : s.rate == 14 ? 96000 : 0; + int bitsPerSample = 0; + return new AudioPCMConfig(bitsPerSample, channelCount, sampleRate); + } + } + public string Codec + { + get + { + var s = FirstStream; + return s != null ? s.CodecString : "?"; + } + } + public string Language + { + get + { + var s = FirstStream; + return s != null ? s.LanguageString : "?"; + } + } + public int StreamId => pid; + + MPLSStream FirstStream + { + get + { + MPLSStream result = null; + source.MPLSHeader.play_item.ForEach(i => i.audio.FindAll(a => a.pid == pid).ForEach(x => result = x)); + return result; + } + } AudioDecoder source; int pid; @@ -476,7 +514,7 @@ namespace CUETools.Codecs.MPEG.MPLS public uint duration; } - public struct MPLSStream + public class MPLSStream { public byte stream_type; public byte coding_type; diff --git a/CUETools.Codecs.MPEG/TsStream.cs b/CUETools.Codecs.MPEG/TsStream.cs index 040f62b..7c95f50 100644 --- a/CUETools.Codecs.MPEG/TsStream.cs +++ b/CUETools.Codecs.MPEG/TsStream.cs @@ -56,13 +56,4 @@ namespace CUETools.Codecs.MPEG savedBufferSize = 0; } }; - - public class AudioDescription - { - public int StreamId; - public string CodecString; - public string LanguageString; - public string FormatString; - public string RateString; - }; } diff --git a/CUETools.Codecs/IAudioSource.cs b/CUETools.Codecs/IAudioSource.cs index 472e8f1..50ecd40 100644 --- a/CUETools.Codecs/IAudioSource.cs +++ b/CUETools.Codecs/IAudioSource.cs @@ -22,25 +22,61 @@ namespace CUETools.Codecs public interface IAudioTitle { List Chapters { get; } + AudioPCMConfig PCM { get; } + string Codec { get; } + string Language { get; } + int StreamId { get; } //IAudioSource Open { get; } } - public interface IAudioContainer + public interface IAudioTitleSet { List AudioTitles { get; } } - public class NoContainerAudioTitle : IAudioTitle + public static class IAudioTitleExtensions { - public NoContainerAudioTitle(IAudioSource source) { this.source = source; } + public static TimeSpan GetDuration(this IAudioTitle title) + { + var chapters = title.Chapters; + return chapters[chapters.Count - 1]; + } + + + public static string GetRateString(this IAudioTitle title) + { + var sr = title.PCM.SampleRate; + if (sr % 1000 == 0) return $"{sr / 1000}KHz"; + if (sr % 100 == 0) return $"{sr / 100}.{(sr / 100) % 10}KHz"; + return $"{sr}Hz"; + } + + public static string GetFormatString(this IAudioTitle title) + { + switch (title.PCM.ChannelCount) + { + case 1: return "mono"; + case 2: return "stereo"; + default: return "multi-channel"; + } + } + } + + public class SingleAudioTitle : IAudioTitle + { + public SingleAudioTitle(IAudioSource source) { this.source = source; } public List Chapters => new List { TimeSpan.Zero, source.Duration }; + public AudioPCMConfig PCM => source.PCM; + public string Codec => source.Settings.Extension; + public string Language => ""; + public int StreamId => 0; IAudioSource source; } - public class NoContainer : IAudioContainer + public class SingleAudioTitleSet : IAudioTitleSet { - public NoContainer(IAudioSource source) { this.source = source; } - public List AudioTitles => new List { new NoContainerAudioTitle(source) }; + public SingleAudioTitleSet(IAudioSource source) { this.source = source; } + public List AudioTitles => new List { new SingleAudioTitle(source) }; IAudioSource source; } } diff --git a/CUETools.eac3to/Program.cs b/CUETools.eac3to/Program.cs index e2478d9..7d7f76b 100644 --- a/CUETools.eac3to/Program.cs +++ b/CUETools.eac3to/Program.cs @@ -130,10 +130,10 @@ namespace CUETools.eac3to #endif { IAudioSource audioSource = null; - IAudioContainer audioContainer = null; + IAudioTitleSet audioContainer = null; IAudioDest audioDest = null; var videos = new List(); - var audios = new List(); + List audios = null; List chapters = null; TagLib.UserDefined.AdditionalFileTypes.Config = config; @@ -160,29 +160,23 @@ namespace CUETools.eac3to TypeDescriptor.GetConverter(property.PropertyType).ConvertFromString(decOpt.Value)); } audioSource = decoderSettings.Open(sourceFile); - audioContainer = audioSource as IAudioContainer; - if (audioContainer == null) audioContainer = new NoContainer(audioSource); + audioContainer = audioSource as IAudioTitleSet; + if (audioContainer == null) audioContainer = new SingleAudioTitleSet(audioSource); Console.ForegroundColor = ConsoleColor.White; int frameRate = 0; bool interlaced = false; - audioContainer.AudioTitles.ForEach(t => chapters = t.Chapters); + audios = audioContainer.AudioTitles; + audios.ForEach(t => chapters = t.Chapters); if (audioSource is Codecs.MPEG.MPLS.AudioDecoder) { var mpls = audioSource as Codecs.MPEG.MPLS.AudioDecoder; mpls.MPLSHeader.play_item.ForEach(i => i.video.ForEach(v => { if (!videos.Exists(v1 => v1.pid == v.pid)) videos.Add(v); })); - mpls.MPLSHeader.play_item.ForEach(i => i.audio.ForEach(v => { if (!audios.Exists(v1 => v1.StreamId == v.pid)) audios.Add(new Codecs.MPEG.AudioDescription() { StreamId = v.pid, CodecString = v.CodecString, LanguageString = v.LanguageString, FormatString = v.FormatString, RateString = v.RateString }); })); - } - else - if (audioSource is Codecs.MPEG.ATSI.AudioDecoder) - { - var atsi = audioSource as Codecs.MPEG.ATSI.AudioDecoder; - atsi.ATSIHeader.titles.ForEach(t => audios.Add(new Codecs.MPEG.AudioDescription() { StreamId = 0/*!*/, CodecString = t.CodecString, FormatString = t.FormatString, RateString = t.RateString })); } videos.ForEach(v => { frameRate = v.FrameRate; interlaced = v.Interlaced; }); Console.Error.Write($@"M2TS, { videos.Count} video track{(videos.Count != 1 ? "s" : "")}, { audios.Count} audio track{(audios.Count != 1 ? "s" : "")}, { - CDImageLayout.TimeToString(audioSource.Duration, "{0:0}:{1:00}:{2:00}")}, { + CDImageLayout.TimeToString(audios[0].GetDuration(), "{0:0}:{1:00}:{2:00}")}, { (frameRate * (interlaced ? 2 : 1))}{ (interlaced ? "i" : "p")}"); Console.Error.WriteLine(); @@ -213,7 +207,7 @@ namespace CUETools.eac3to Console.Error.Write(id++); Console.Error.Write(": "); Console.ForegroundColor = ConsoleColor.Gray; - Console.Error.WriteLine("{0}, {1}, {2}, {3}", audio.CodecString, audio.LanguageString, audio.FormatString, audio.RateString); + Console.Error.WriteLine("{0}, {1}, {2}, {3}", audio.Codec, audio.Language, audio.GetFormatString(), audio.GetRateString()); } } }