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);