mirror of
https://github.com/claunia/cuetools.net.git
synced 2025-12-16 18:14:25 +00:00
Renamed CUETools.Codecs.BLDLPCM into CUETools.Codecs.MPEG
This commit is contained in:
740
CUETools.Codecs.MPEG/BDLPCM/AudioDecoder.cs
Normal file
740
CUETools.Codecs.MPEG/BDLPCM/AudioDecoder.cs
Normal file
@@ -0,0 +1,740 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
using CUETools.Codecs.MPEG;
|
||||
|
||||
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;
|
||||
_IO = IO != null ? IO : new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 0x10000);
|
||||
streams = new Dictionary<ushort, TsStream>();
|
||||
frameBuffer = new byte[192];
|
||||
demuxer_channel = 0;
|
||||
_samplePos = 0;
|
||||
_sampleLen = -1;
|
||||
demux_ts_packets(null, 0);
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
public IAudioDecoderSettings Settings => m_settings;
|
||||
|
||||
public void Close()
|
||||
{
|
||||
//if (_br != null)
|
||||
//{
|
||||
// _br.Close();
|
||||
// _br = null;
|
||||
//}
|
||||
_IO = null;
|
||||
}
|
||||
|
||||
public long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return _sampleLen;
|
||||
}
|
||||
}
|
||||
|
||||
public long Remaining
|
||||
{
|
||||
get
|
||||
{
|
||||
return _sampleLen - _samplePos;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe int StreamIds
|
||||
{
|
||||
get
|
||||
{
|
||||
int res = 0;
|
||||
foreach (var s in streams)
|
||||
if (s.Value.is_opened)
|
||||
res++;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public long Position
|
||||
{
|
||||
get
|
||||
{
|
||||
return _samplePos;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_samplePos == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe AudioPCMConfig PCM
|
||||
{
|
||||
get {
|
||||
if (chosenStream == null)
|
||||
{
|
||||
if (m_settings.StreamId.HasValue)
|
||||
{
|
||||
if (streams.ContainsKey((ushort)m_settings.StreamId.Value))
|
||||
{
|
||||
var s = streams[(ushort)m_settings.StreamId.Value];
|
||||
if (s.is_opened)
|
||||
{
|
||||
chosenStream = s;
|
||||
if (chosenStream.pcm == null)
|
||||
{
|
||||
demux_ts_packets(null, 0);
|
||||
}
|
||||
return chosenStream.pcm;
|
||||
}
|
||||
}
|
||||
throw new Exception("StreamId can be " +
|
||||
string.Join(", ", (new List<ushort>(streams.Keys)).FindAll(pid => streams[pid].is_opened).ConvertAll(pid => pid.ToString()).ToArray()));
|
||||
}
|
||||
if (m_settings.Stream.HasValue)
|
||||
{
|
||||
foreach (var s in streams)
|
||||
{
|
||||
if (s.Value.is_opened && s.Value.streamId == m_settings.Stream.Value)
|
||||
{
|
||||
chosenStream = s.Value;
|
||||
if (chosenStream.pcm == null)
|
||||
{
|
||||
demux_ts_packets(null, 0);
|
||||
}
|
||||
return chosenStream.pcm;
|
||||
}
|
||||
}
|
||||
throw new Exception("Stream can be " +
|
||||
string.Join(", ", (new List<TsStream>(streams.Values)).FindAll(s => s.is_opened).ConvertAll(s => s.streamId.ToString()).ToArray()));
|
||||
}
|
||||
|
||||
throw new Exception("multiple streams present, please specify StreamId or Stream");
|
||||
}
|
||||
return chosenStream.pcm;
|
||||
}
|
||||
}
|
||||
|
||||
public string Path { get { return _path; } }
|
||||
|
||||
public unsafe int Read(AudioBuffer buff, int maxLength)
|
||||
{
|
||||
buff.Prepare(this, maxLength);
|
||||
int sampleCount;
|
||||
fixed (int* dest = &buff.Samples[0,0])
|
||||
sampleCount = demux_ts_packets(dest, buff.Length);
|
||||
buff.Length = sampleCount;
|
||||
_samplePos += sampleCount;
|
||||
return sampleCount;
|
||||
}
|
||||
|
||||
unsafe int demux_ts_packets(int* dest, int maxSamples)
|
||||
{
|
||||
int samplesOffset = 0;
|
||||
while (true)
|
||||
{
|
||||
if (chosenStream != null && chosenStream.pcm != null)
|
||||
{
|
||||
int samplesInBuffer = chosenStream.savedBufferSize / chosenStream.pcm.BlockAlign;
|
||||
if (samplesInBuffer > 0)
|
||||
{
|
||||
int chunkSamples = Math.Min(samplesInBuffer, maxSamples - samplesOffset);
|
||||
int chunkLen = chunkSamples * chosenStream.pcm.BlockAlign;
|
||||
fixed (byte* psrc_start = &chosenStream.savedBuffer[0])
|
||||
BDBytesToFLACSamples(
|
||||
dest,
|
||||
psrc_start + chosenStream.savedBufferOffset,
|
||||
chunkSamples, chosenStream.pcm);
|
||||
samplesOffset += chunkSamples;
|
||||
dest += chunkSamples * chosenStream.pcm.ChannelCount;
|
||||
chosenStream.savedBufferOffset += chunkLen;
|
||||
chosenStream.savedBufferSize -= chunkLen;
|
||||
}
|
||||
|
||||
if (chosenStream.savedBufferSize > 0 && chosenStream.savedBufferOffset + chosenStream.pcm.BlockAlign > chosenStream.savedBuffer.Length)
|
||||
{
|
||||
Buffer.BlockCopy(chosenStream.savedBuffer, chosenStream.savedBufferOffset, chosenStream.savedBuffer, 0, chosenStream.savedBufferSize);
|
||||
chosenStream.savedBufferOffset = 0;
|
||||
}
|
||||
|
||||
if (samplesOffset >= maxSamples) return samplesOffset;
|
||||
}
|
||||
|
||||
int read = _IO.Read(frameBuffer, 0, 192);
|
||||
if (read != 192)
|
||||
break;
|
||||
//if (frameBuffer[0] == 0x47 && frameBuffer[4] != 0x47)
|
||||
// throw new FormatException("TS, not M2TS");
|
||||
//if (frameBuffer[0] == 0x47 || frameBuffer[4] != 0x47)
|
||||
// throw new FormatException("unknown stream type");
|
||||
|
||||
fixed (byte* ptr = &frameBuffer[0])
|
||||
{
|
||||
var fr = new FrameReader(ptr, ptr + 192);
|
||||
TsStream s;
|
||||
demux_ts_packet(fr, out s);
|
||||
int dataLen = (int) fr.Length;
|
||||
if (dataLen > 0 && s != null && (chosenStream == null || s == chosenStream))
|
||||
{
|
||||
int dataOffset = (int)(fr.Ptr - ptr);
|
||||
if (s.savedBufferSize > 0)
|
||||
{
|
||||
int blockLen = s.pcm.BlockAlign;
|
||||
int chunkLen = Math.Min(dataLen, blockLen - s.savedBufferSize);
|
||||
// fr.read_bytes(svptr + s.savedBufferOffset + s.savedBufferSize, chunkLen);
|
||||
Buffer.BlockCopy(frameBuffer, dataOffset, s.savedBuffer, s.savedBufferOffset + s.savedBufferSize, chunkLen);
|
||||
dataOffset += chunkLen;
|
||||
dataLen -= chunkLen;
|
||||
chosenStream.savedBufferSize += chunkLen;
|
||||
if (chosenStream.savedBufferSize == blockLen)
|
||||
{
|
||||
fixed (byte* psrc = &s.savedBuffer[s.savedBufferOffset])
|
||||
BDBytesToFLACSamples(dest, psrc, 1, s.pcm);
|
||||
samplesOffset += 1;
|
||||
dest += chosenStream.pcm.ChannelCount;
|
||||
chosenStream.savedBufferOffset = 0;
|
||||
chosenStream.savedBufferSize = 0;
|
||||
}
|
||||
}
|
||||
if (dataLen > 0)
|
||||
{
|
||||
var tmp = s.savedBuffer;
|
||||
s.savedBuffer = frameBuffer;
|
||||
s.savedBufferOffset = dataOffset;
|
||||
s.savedBufferSize = dataLen;
|
||||
frameBuffer = tmp;
|
||||
if (dest == null) return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return samplesOffset;
|
||||
}
|
||||
|
||||
unsafe static void BDBytesToFLACSamples_24bit_6ch(int* pdest, byte* psrc, int samples, int wastedBits)
|
||||
{
|
||||
for (int i = 0; i < samples; i++)
|
||||
{
|
||||
// front left
|
||||
*(pdest++) = ((((((int)*(psrc++) << 8) + (int)*(psrc++)) << 8) + (int)*(psrc++)) << 8) >> (8 + wastedBits);
|
||||
// front right
|
||||
*(pdest++) = ((((((int)*(psrc++) << 8) + (int)*(psrc++)) << 8) + (int)*(psrc++)) << 8) >> (8 + wastedBits);
|
||||
// front center
|
||||
*(pdest++) = ((((((int)*(psrc++) << 8) + (int)*(psrc++)) << 8) + (int)*(psrc++)) << 8) >> (8 + wastedBits);
|
||||
// surround left
|
||||
pdest[1] = ((((((int)*(psrc++) << 8) + (int)*(psrc++)) << 8) + (int)*(psrc++)) << 8) >> (8 + wastedBits);
|
||||
// surround right
|
||||
pdest[2] = ((((((int)*(psrc++) << 8) + (int)*(psrc++)) << 8) + (int)*(psrc++)) << 8) >> (8 + wastedBits);
|
||||
// LFE
|
||||
pdest[0] = ((((((int)*(psrc++) << 8) + (int)*(psrc++)) << 8) + (int)*(psrc++)) << 8) >> (8 + wastedBits);
|
||||
pdest += 3;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe static void BDBytesToFLACSamples_24bit_noremap(int* pdest, byte* psrc, int samples, int channels, int wastedBits)
|
||||
{
|
||||
for (int i = 0; i < samples * channels; i++)
|
||||
{
|
||||
*(pdest++) = ((((((int)*(psrc++) << 8) + (int)*(psrc++)) << 8) + (int)*(psrc++)) << 8) >> (8 + wastedBits);
|
||||
}
|
||||
// if (0 != (pcm.ChannelCount & 1)) channels are padded with one extra unused channel! is it the same for wav?
|
||||
}
|
||||
|
||||
unsafe static void BDBytesToFLACSamples_24bit(int* pdest, byte* psrc, int samples, int channels, int wastedBits)
|
||||
{
|
||||
switch (channels)
|
||||
{
|
||||
case 2:
|
||||
BDBytesToFLACSamples_24bit_noremap(pdest, psrc, samples, channels, wastedBits);
|
||||
break;
|
||||
case 4:
|
||||
BDBytesToFLACSamples_24bit_noremap(pdest, psrc, samples, channels, wastedBits);
|
||||
break;
|
||||
case 6:
|
||||
BDBytesToFLACSamples_24bit_6ch(pdest, psrc, samples, wastedBits);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe static void BDBytesToFLACSamples(int* pdest, byte* psrc, int samples, AudioPCMConfig pcm)
|
||||
{
|
||||
switch (pcm.BitsPerSample)
|
||||
{
|
||||
case 24:
|
||||
BDBytesToFLACSamples_24bit(pdest, psrc, samples, pcm.ChannelCount, 0);
|
||||
break;
|
||||
case 20:
|
||||
BDBytesToFLACSamples_24bit(pdest, psrc, samples, pcm.ChannelCount, 4);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe void process_psi_pat(TsStream s, FrameReader fr)
|
||||
{
|
||||
long len = fr.Length - 4;
|
||||
long n = len / 4;
|
||||
if (len < 0 || 0 != (len % 4))
|
||||
throw new NotSupportedException();
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
ushort channel = fr.read_ushort();
|
||||
ushort pid = fr.read_ushort();
|
||||
if ((pid & 0xe000) != 0xe000)
|
||||
throw new NotSupportedException();
|
||||
|
||||
pid &= 0x1fff;
|
||||
|
||||
if (demuxer_channel == 0 || demuxer_channel == channel)
|
||||
{
|
||||
if (!streams.ContainsKey(pid))
|
||||
streams.Add(pid, new TsStream());
|
||||
TsStream ss = streams[pid];
|
||||
ss.channel = channel;
|
||||
ss.type = 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe void process_psi_pmt(TsStream s, FrameReader fr)
|
||||
{
|
||||
// PMT (Program Map Table)
|
||||
ushort pcr_pid = fr.read_ushort();
|
||||
ushort info_len = (ushort)(fr.read_ushort() & 0x0fff);
|
||||
|
||||
fr.skip(info_len);
|
||||
|
||||
// CRC
|
||||
fr.Length -= 4;
|
||||
|
||||
while (fr.Length > 0)
|
||||
{
|
||||
byte type = fr.read_byte();
|
||||
ushort es_pid = fr.read_ushort();
|
||||
|
||||
if ((es_pid & 0xe000) != 0xe000)
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
es_pid &= 0x1fff;
|
||||
|
||||
info_len = (ushort)(fr.read_ushort() & 0x0fff);
|
||||
|
||||
while (info_len > 0)
|
||||
{
|
||||
byte tag = fr.read_byte();
|
||||
switch (tag)
|
||||
{
|
||||
case 0x05: // registration descriptor
|
||||
{
|
||||
byte len = fr.read_byte();
|
||||
uint rid = fr.read_uint();
|
||||
if (rid == 0x48444D56 /* "HDMV" */ && type == 0x80 /* LPCM */)
|
||||
{
|
||||
if (!streams.ContainsKey(es_pid))
|
||||
{
|
||||
streams.Add(es_pid, new TsStream());
|
||||
TsStream ss = streams[es_pid];
|
||||
if (ss.channel != s.channel || ss.type != type)
|
||||
{
|
||||
ss.channel = s.channel;
|
||||
ss.type = type;
|
||||
ss.streamId = s.streamId;
|
||||
ss.is_opened = true;
|
||||
s.streamId++;
|
||||
}
|
||||
}
|
||||
}
|
||||
fr.skip(len - 4);
|
||||
info_len -= (ushort)(len + 2);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
fr.skip(info_len - 1);
|
||||
info_len = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fr.Length > 0)
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
unsafe void process_psi(TsStream s, FrameReader fr, UInt16 pid, int table)
|
||||
{
|
||||
if (0 == pid)
|
||||
{
|
||||
process_psi_pat(s, fr); // Program Association Table
|
||||
return;
|
||||
}
|
||||
//if (1 == pid)
|
||||
//{
|
||||
// process_cat(s, ptr, end_ptr); // Conditional Access Table
|
||||
// return;
|
||||
//}
|
||||
//if (0x11 == pid)
|
||||
//{
|
||||
// process_sdt(s, ptr, end_ptr);
|
||||
// return;
|
||||
//}
|
||||
|
||||
if (table == 0x02)
|
||||
{
|
||||
process_psi_pmt(s, fr); // Program Map Table
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe void process_pes_header(TsStream s, FrameReader fr)
|
||||
{
|
||||
// Packet start code prefix
|
||||
if (fr.read_byte() != 0 || fr.read_byte() != 0 || fr.read_byte() != 1)
|
||||
throw new NotSupportedException();
|
||||
|
||||
s.ts_stream_id = fr.read_byte();
|
||||
|
||||
int pes_len = (int)fr.read_ushort();
|
||||
int pes_type = (fr.read_byte() >> 6) & 3;
|
||||
|
||||
// pes_type == 2; /* mpeg2 PES */
|
||||
|
||||
byte flags1 = fr.read_byte();
|
||||
s.frame_size = fr.read_byte();
|
||||
s.frame_num++;
|
||||
s.at_packet_header = true;
|
||||
|
||||
switch (flags1 & 0xc0)
|
||||
{
|
||||
case 0x80: // PTS only
|
||||
{
|
||||
ulong pts = fr.read_pts();
|
||||
|
||||
if (s.dts > 0 && pts > s.dts)
|
||||
s.frame_length = (uint)(pts - s.dts);
|
||||
s.dts = pts;
|
||||
|
||||
if (pts > s.last_pts)
|
||||
s.last_pts = pts;
|
||||
|
||||
if (s.first_pts == 0)
|
||||
s.first_pts = pts;
|
||||
}
|
||||
break;
|
||||
case 0xc0: // PTS,DTS
|
||||
{
|
||||
ulong pts = fr.read_pts();
|
||||
ulong dts = fr.read_pts();
|
||||
|
||||
if (s.dts > 0 && dts > s.dts)
|
||||
s.frame_length = (uint)(dts - s.dts);
|
||||
s.dts = dts;
|
||||
|
||||
if (pts > s.last_pts)
|
||||
s.last_pts = pts;
|
||||
|
||||
if (s.first_dts == 0)
|
||||
s.first_dts = dts;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe void BdHeader(TsStream s, FrameReader fr)
|
||||
{
|
||||
uint h = fr.read_uint();
|
||||
int pi_channels;
|
||||
int pi_channels_padding;
|
||||
int pi_bits;
|
||||
int pi_rate;
|
||||
|
||||
switch( ( h & 0xf000) >> 12 )
|
||||
{
|
||||
case 1:
|
||||
pi_channels = 1;
|
||||
break;
|
||||
case 3:
|
||||
pi_channels = 2;
|
||||
break;
|
||||
case 4:
|
||||
pi_channels = 3;
|
||||
//{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT,
|
||||
// AOUT_CHAN_CENTER, 0 };
|
||||
break;
|
||||
case 5:
|
||||
pi_channels = 3;
|
||||
//{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT,
|
||||
// AOUT_CHAN_REARCENTER, 0 };
|
||||
break;
|
||||
case 6:
|
||||
pi_channels = 4;
|
||||
//{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT,
|
||||
// AOUT_CHAN_CENTER, AOUT_CHAN_REARCENTER, 0 };
|
||||
break;
|
||||
case 7:
|
||||
pi_channels = 4;
|
||||
//{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT,
|
||||
// AOUT_CHAN_REARLEFT, AOUT_CHAN_REARRIGHT, 0 };
|
||||
break;
|
||||
case 8:
|
||||
pi_channels = 5;
|
||||
//{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT, AOUT_CHAN_CENTER,
|
||||
// AOUT_CHAN_MIDDLELEFT, AOUT_CHAN_MIDDLERIGHT, 0 };
|
||||
break;
|
||||
case 9:
|
||||
pi_channels = 6;
|
||||
//{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT, AOUT_CHAN_CENTER,
|
||||
// AOUT_CHAN_REARLEFT, AOUT_CHAN_REARRIGHT, AOUT_CHAN_LFE, 0 };
|
||||
break;
|
||||
case 10:
|
||||
pi_channels = 7;
|
||||
//{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT, AOUT_CHAN_CENTER,
|
||||
// AOUT_CHAN_MIDDLELEFT, AOUT_CHAN_REARLEFT, AOUT_CHAN_REARRIGHT,
|
||||
// AOUT_CHAN_MIDDLERIGHT, 0 };
|
||||
break;
|
||||
case 11:
|
||||
pi_channels = 8;
|
||||
//{ AOUT_CHAN_LEFT, AOUT_CHAN_RIGHT, AOUT_CHAN_CENTER,
|
||||
// AOUT_CHAN_MIDDLELEFT, AOUT_CHAN_REARLEFT, AOUT_CHAN_REARRIGHT,
|
||||
// AOUT_CHAN_MIDDLERIGHT, AOUT_CHAN_LFE, 0 };
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
pi_channels_padding = pi_channels & 1;
|
||||
|
||||
switch( (h >> 6) & 0x03 )
|
||||
{
|
||||
case 1:
|
||||
pi_bits = 16;
|
||||
break;
|
||||
case 2:
|
||||
pi_bits = 20;
|
||||
break;
|
||||
case 3:
|
||||
pi_bits = 24;
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
switch( (h >> 8) & 0x0f )
|
||||
{
|
||||
case 1:
|
||||
pi_rate = 48000;
|
||||
break;
|
||||
case 4:
|
||||
pi_rate = 96000;
|
||||
break;
|
||||
case 5:
|
||||
pi_rate = 192000;
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
if (s.pcm == null)
|
||||
s.pcm = new AudioPCMConfig(pi_bits, pi_channels, pi_rate);
|
||||
}
|
||||
|
||||
unsafe void demux_ts_packet(FrameReader fr, out TsStream dataStream)
|
||||
{
|
||||
dataStream = null;
|
||||
|
||||
uint timecode = fr.read_uint() & 0x3fffffff;
|
||||
|
||||
byte sync = fr.read_byte(); // ts sync byte
|
||||
if (sync != 0x47) throw new FormatException("invalid packet");
|
||||
|
||||
ushort pid = fr.read_ushort();
|
||||
bool transport_error = (pid & 0x8000) != 0;
|
||||
bool payload_unit_start_indicator = (pid & 0x4000) != 0;
|
||||
pid &= 0x1fff;
|
||||
if (transport_error)
|
||||
throw new FormatException("invalid packet ");
|
||||
|
||||
byte flags = fr.read_byte();
|
||||
bool adaptation_field_exist = (flags & 0x20) != 0;
|
||||
bool payload_data_exist = (flags & 0x10) != 0;
|
||||
byte continuity_counter = (byte)(flags & 0x0f);
|
||||
|
||||
if (pid == 0x1fff || !payload_data_exist)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// skip adaptation field
|
||||
if (adaptation_field_exist)
|
||||
{
|
||||
fr.skip(fr.read_byte());
|
||||
}
|
||||
|
||||
if (!streams.ContainsKey(pid))
|
||||
streams.Add(pid, new TsStream());
|
||||
TsStream s = streams[pid];
|
||||
|
||||
if (0 == pid || (s.channel != 0xffff && s.type == 0xff))
|
||||
{
|
||||
// PSI (Program Specific Information)
|
||||
if (payload_unit_start_indicator)
|
||||
{
|
||||
// begin of PSI table
|
||||
byte pointerLen = fr.read_byte();
|
||||
fr.skip(pointerLen);
|
||||
byte tableId = fr.read_byte();
|
||||
ushort sectionHeader = fr.read_ushort();
|
||||
bool syntaxFlag = ((sectionHeader >> 15) & 1) != 0;
|
||||
bool privateFlag = ((sectionHeader >> 14) & 1) != 0;
|
||||
int reservedBits = (sectionHeader >> 12) & 3;
|
||||
if (reservedBits != 3) throw new NotSupportedException();
|
||||
int l = sectionHeader & 0x0fff;
|
||||
if (syntaxFlag)
|
||||
{
|
||||
ushort extId = fr.read_ushort();
|
||||
byte tmp8 = fr.read_byte();
|
||||
int reserved3 = (tmp8 >> 6);
|
||||
int version = (tmp8 >> 1) & 31;
|
||||
bool current_flag = (tmp8 & 1) != 0;
|
||||
byte section_num = fr.read_byte();
|
||||
byte last_section_num = fr.read_byte();
|
||||
l -= 5;
|
||||
}
|
||||
|
||||
int len = (int)fr.Length;
|
||||
if (l <= len)
|
||||
{
|
||||
fr.Length = l;
|
||||
process_psi(s, fr, pid, tableId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (l > s.psi.Length)
|
||||
throw new FormatException("invalid packet ");
|
||||
fixed (byte* psip = &s.psi[0])
|
||||
fr.read_bytes(psip, len);
|
||||
s.psi_offset = len;
|
||||
s.psi_table = tableId;
|
||||
s.psi_len = l;
|
||||
return;
|
||||
}
|
||||
|
||||
// next part of PSI
|
||||
if (0 == s.psi_offset)
|
||||
throw new FormatException("invalid packet ");
|
||||
|
||||
{
|
||||
int len = (int)fr.Length;
|
||||
if (len > s.psi.Length - s.psi_offset)
|
||||
throw new FormatException("invalid packet ");
|
||||
fixed (byte* psip = &s.psi[s.psi_offset])
|
||||
fr.read_bytes(psip, len);
|
||||
s.psi_offset += len;
|
||||
}
|
||||
|
||||
if (s.psi_offset < s.psi_len)
|
||||
return;
|
||||
fixed (byte* psip = &s.psi[0])
|
||||
{
|
||||
var psiFr = new FrameReader(psip, psip + s.psi_len);
|
||||
process_psi(s, psiFr, pid, s.psi_table);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (s.type != 0xff)
|
||||
{
|
||||
// PES
|
||||
|
||||
if (payload_unit_start_indicator)
|
||||
{
|
||||
s.psi_offset = 0;
|
||||
s.psi_len = 9;
|
||||
}
|
||||
|
||||
while (s.psi_offset < s.psi_len)
|
||||
{
|
||||
int len = Math.Min((int)fr.Length, s.psi_len - s.psi_offset);
|
||||
if (len <= 0) return;
|
||||
fixed (byte* psip = &s.psi[s.psi_offset])
|
||||
fr.read_bytes(psip, len);
|
||||
s.psi_offset += len;
|
||||
if (s.psi_len == 9)
|
||||
s.psi_len += s.psi[8];
|
||||
}
|
||||
|
||||
if (s.psi_len != 0)
|
||||
{
|
||||
fixed (byte* psip = &s.psi[0])
|
||||
{
|
||||
var pesFr = new FrameReader(psip, psip + s.psi_len);
|
||||
process_pes_header(s, pesFr);
|
||||
}
|
||||
|
||||
s.psi_len = 0;
|
||||
s.psi_offset = 0;
|
||||
}
|
||||
|
||||
if (s.frame_num == 0)
|
||||
return;
|
||||
|
||||
//if(es_parse)
|
||||
//{
|
||||
// switch(s.type)
|
||||
// {
|
||||
// case 0x1b:
|
||||
// s.frame_num_h264.parse(fr);
|
||||
// break;
|
||||
// case 0x06:
|
||||
// case 0x81:
|
||||
// case 0x83:
|
||||
// s.frame_num_ac3.parse(fr);
|
||||
// break;
|
||||
// }
|
||||
//}
|
||||
|
||||
if (s.is_opened)
|
||||
{
|
||||
if (s.at_packet_header)
|
||||
{
|
||||
BdHeader(s, fr);
|
||||
s.at_packet_header = false;
|
||||
}
|
||||
|
||||
dataStream = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string _path;
|
||||
Stream _IO;
|
||||
Dictionary<UInt16, TsStream> streams;
|
||||
byte[] frameBuffer;
|
||||
|
||||
int demuxer_channel;
|
||||
TsStream chosenStream;
|
||||
long _samplePos, _sampleLen;
|
||||
DecoderSettings m_settings;
|
||||
}
|
||||
}
|
||||
45
CUETools.Codecs.MPEG/BDLPCM/DecoderSettings.cs
Normal file
45
CUETools.Codecs.MPEG/BDLPCM/DecoderSettings.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CUETools.Codecs.MPEG.BDLPCM
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
public class DecoderSettings : IAudioDecoderSettings
|
||||
{
|
||||
#region IAudioDecoderSettings implementation
|
||||
[Browsable(false)]
|
||||
public string Extension => "m2ts";
|
||||
|
||||
[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; }
|
||||
}
|
||||
}
|
||||
29
CUETools.Codecs.MPEG/CUETools.Codecs.MPEG.csproj
Normal file
29
CUETools.Codecs.MPEG/CUETools.Codecs.MPEG.csproj
Normal file
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net40;net20;netstandard2.0</TargetFrameworks>
|
||||
<Version>2.1.7.0</Version>
|
||||
<AssemblyName>CUETools.Codecs.MPEG</AssemblyName>
|
||||
<RootNamespace>CUETools.Codecs.MPEG</RootNamespace>
|
||||
<Product>CUETools</Product>
|
||||
<Description>A library for encoding and decoding BluRay Disc LPCM audio.</Description>
|
||||
<Copyright>Copyright (c) 2008-2018 Grigory Chudov</Copyright>
|
||||
<Authors>Grigory Chudov</Authors>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputPath>..\bin\$(Configuration)\plugins</OutputPath>
|
||||
<RepositoryUrl>https://github.com/gchudov/cuetools.net</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<Company />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemDefinitionGroup>
|
||||
<ProjectReference>
|
||||
<Private>False</Private>
|
||||
</ProjectReference>
|
||||
</ItemDefinitionGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\CUETools.Codecs\CUETools.Codecs.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
116
CUETools.Codecs.MPEG/FrameReader.cs
Normal file
116
CUETools.Codecs.MPEG/FrameReader.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CUETools.Codecs.MPEG
|
||||
{
|
||||
internal unsafe class FrameReader
|
||||
{
|
||||
internal FrameReader(byte* ptr, byte* end)
|
||||
{
|
||||
ptr_m = ptr;
|
||||
end_m = end;
|
||||
}
|
||||
|
||||
internal FrameReader(byte* ptr, long len)
|
||||
{
|
||||
ptr_m = ptr;
|
||||
end_m = ptr + len;
|
||||
}
|
||||
|
||||
internal FrameReader(FrameReader src, long len)
|
||||
{
|
||||
if (src.ptr_m + len > src.end_m) throw new IndexOutOfRangeException();
|
||||
ptr_m = src.ptr_m;
|
||||
end_m = src.ptr_m + len;
|
||||
}
|
||||
|
||||
internal void read_bytes(byte* dst, int len)
|
||||
{
|
||||
if (ptr_m + len > end_m) throw new IndexOutOfRangeException();
|
||||
AudioSamples.MemCpy(dst, ptr_m, len);
|
||||
ptr_m += len;
|
||||
}
|
||||
|
||||
internal byte[] read_bytes(int len)
|
||||
{
|
||||
var res = new byte[len];
|
||||
fixed (byte* ptr = &res[0])
|
||||
read_bytes(ptr, len);
|
||||
return res;
|
||||
}
|
||||
|
||||
internal string read_string(int len)
|
||||
{
|
||||
var res = new byte[len];
|
||||
fixed (byte* ptr = &res[0])
|
||||
read_bytes(ptr, len);
|
||||
return Encoding.UTF8.GetString(res, 0, res.Length);;
|
||||
}
|
||||
|
||||
internal byte read_byte()
|
||||
{
|
||||
if (ptr_m + 1 > end_m) throw new IndexOutOfRangeException();
|
||||
return *(ptr_m++);
|
||||
}
|
||||
|
||||
internal ushort read_ushort()
|
||||
{
|
||||
if (ptr_m + 2 > end_m) throw new IndexOutOfRangeException();
|
||||
ushort n = (ushort)(*(ptr_m++));
|
||||
n <<= 8; n += (ushort)(*(ptr_m++));
|
||||
return n;
|
||||
}
|
||||
|
||||
internal uint read_uint()
|
||||
{
|
||||
if (ptr_m + 4 > end_m) throw new IndexOutOfRangeException();
|
||||
uint n = (uint)(*(ptr_m++));
|
||||
n <<= 8; n += (uint)(*(ptr_m++));
|
||||
n <<= 8; n += (uint)(*(ptr_m++));
|
||||
n <<= 8; n += (uint)(*(ptr_m++));
|
||||
return n;
|
||||
}
|
||||
|
||||
internal ulong read_pts()
|
||||
{
|
||||
if (ptr_m + 5 > end_m) throw new IndexOutOfRangeException();
|
||||
ulong pts
|
||||
= ((ulong)(*(ptr_m++) & 0x0e) << 29);
|
||||
pts |= ((ulong)(*(ptr_m++) & 0xff) << 22);
|
||||
pts |= ((ulong)(*(ptr_m++) & 0xfe) << 14);
|
||||
pts |= ((ulong)(*(ptr_m++) & 0xff) << 7);
|
||||
pts |= ((ulong)(*(ptr_m++) & 0xfe) >> 1);
|
||||
return pts;
|
||||
}
|
||||
|
||||
internal void skip(long bytes)
|
||||
{
|
||||
if (ptr_m + bytes > end_m) throw new IndexOutOfRangeException();
|
||||
ptr_m += bytes;
|
||||
}
|
||||
|
||||
internal long Length
|
||||
{
|
||||
get
|
||||
{
|
||||
return end_m - ptr_m;
|
||||
}
|
||||
set
|
||||
{
|
||||
end_m = ptr_m + value;
|
||||
}
|
||||
}
|
||||
|
||||
internal byte* Ptr
|
||||
{
|
||||
get
|
||||
{
|
||||
return ptr_m;
|
||||
}
|
||||
}
|
||||
|
||||
byte* ptr_m;
|
||||
byte* end_m;
|
||||
}
|
||||
}
|
||||
661
CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs
Normal file
661
CUETools.Codecs.MPEG/MPLS/AudioDecoder.cs
Normal file
@@ -0,0 +1,661 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
||||
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;
|
||||
_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<BDLPCM.AudioDecoder>();
|
||||
var pids = new List<int>();
|
||||
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 entry = new BDLPCM.AudioDecoder(m2ts, null, chosenPid);
|
||||
readers.Add(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
currentReader = readers[0];
|
||||
pcm = currentReader.PCM;
|
||||
}
|
||||
|
||||
MPLSHeader parseHeader(FrameReader fr)
|
||||
{
|
||||
var hdr = new MPLSHeader();
|
||||
hdr.play_item = new List<MPLSPlaylistItem>();
|
||||
hdr.play_mark = new List<MPLSPlaylistMark>();
|
||||
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<MPLSStream>();
|
||||
item.audio = new List<MPLSStream>();
|
||||
item.pg = new List<MPLSStream>();
|
||||
|
||||
// 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<uint> Chapters
|
||||
{
|
||||
get
|
||||
{
|
||||
//settings.IgnoreShortItems
|
||||
var res = new List<uint>();
|
||||
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<BDLPCM.AudioDecoder> readers;
|
||||
BDLPCM.AudioDecoder 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<MPLSStream> video;
|
||||
public List<MPLSStream> audio;
|
||||
public List<MPLSStream> 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<MPLSPlaylistItem> play_item;
|
||||
public List<MPLSPlaylistMark> play_mark;
|
||||
}
|
||||
}
|
||||
45
CUETools.Codecs.MPEG/MPLS/DecoderSettings.cs
Normal file
45
CUETools.Codecs.MPEG/MPLS/DecoderSettings.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CUETools.Codecs.MPEG.MPLS
|
||||
{
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
public class DecoderSettings : IAudioDecoderSettings
|
||||
{
|
||||
#region IAudioDecoderSettings implementation
|
||||
[Browsable(false)]
|
||||
public string Extension => "mpls";
|
||||
|
||||
[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; }
|
||||
}
|
||||
}
|
||||
59
CUETools.Codecs.MPEG/TsStream.cs
Normal file
59
CUETools.Codecs.MPEG/TsStream.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace CUETools.Codecs.MPEG
|
||||
{
|
||||
internal class TsStream
|
||||
{
|
||||
internal UInt16 channel; // channel number (1,2 ...)
|
||||
internal int streamId; // stream number in channel
|
||||
internal byte type; // 0xff - not ES
|
||||
internal AudioPCMConfig pcm;
|
||||
internal byte[] savedBuffer;
|
||||
internal int savedBufferOffset;
|
||||
internal int savedBufferSize;
|
||||
|
||||
internal byte[] psi; // PAT,PMT cache (only for PSI streams)
|
||||
internal int psi_len;
|
||||
internal int psi_offset;
|
||||
internal int psi_table;
|
||||
internal bool at_packet_header;
|
||||
|
||||
internal byte ts_stream_id; // MPEG stream id
|
||||
|
||||
internal bool is_opened;
|
||||
|
||||
internal UInt64 dts; // current MPEG stream DTS (presentation time for audio, decode time for video)
|
||||
internal UInt64 first_dts;
|
||||
internal UInt64 first_pts;
|
||||
internal UInt64 last_pts;
|
||||
internal UInt32 frame_length; // frame length in ticks (90 ticks = 1 ms, 90000/frame_length=fps)
|
||||
internal UInt32 frame_size; // frame size in bytes
|
||||
internal UInt64 frame_num; // frame counter
|
||||
|
||||
internal TsStream()
|
||||
{
|
||||
is_opened = false;
|
||||
psi = new byte[512];
|
||||
psi_len = 0;
|
||||
psi_offset = 0;
|
||||
psi_table = 0;
|
||||
channel = 0xffff;
|
||||
streamId = 0;
|
||||
type = 0xff;
|
||||
ts_stream_id = 0;
|
||||
dts = 0;
|
||||
first_dts = 0;
|
||||
first_pts = 0;
|
||||
last_pts = 0;
|
||||
frame_length = 0;
|
||||
frame_size = 0;
|
||||
frame_num = 0;
|
||||
pcm = null;
|
||||
savedBuffer = new byte[192];
|
||||
savedBufferOffset = 0;
|
||||
savedBufferSize = 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user