From deb3448a5572cbe350836efda9ac49a084624e47 Mon Sep 17 00:00:00 2001 From: Grigory Chudov Date: Sat, 7 Apr 2018 23:02:01 -0400 Subject: [PATCH] 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);