public async Task ReadAsync(IContentSink sink, Stream stream, CancellationToken cancel_token)
{
var state = ReaderState.EBML;
var position = 0L;
var stream_index = -1;
var stream_origin = DateTime.Now;
var timecode_scale = 1000000.0;
var ebml = new EBML();
var clusters = new LinkedList <Cluster>();
var headers = new List <Element>();
var eos = false;
while (!eos)
{
try {
var elt = await Element.ReadHeaderAsync(stream, cancel_token).ConfigureAwait(false);
if (ebml.MaxIDLength < elt.ID.Length ||
ebml.MaxSizeLength < elt.Size.Length)
{
throw new BadDataException();
}
parse_retry:
switch (state)
{
case ReaderState.EBML:
if (elt.ID.BinaryEquals(Elements.EBML))
{
await elt.ReadBodyAsync(stream, cancel_token).ConfigureAwait(false);
headers.Clear();
headers.Add(elt);
ebml = new EBML(elt);
state = ReaderState.Segment;
}
else
{
throw new BadDataException();
}
break;
case ReaderState.Segment:
if (elt.ID.BinaryEquals(Elements.Segment))
{
headers.Add(elt);
state = ReaderState.EndOfHeader;
}
else if (elt.ID.BinaryEquals(Elements.EBML))
{
state = ReaderState.EBML;
goto parse_retry;
}
else if (elt.ID.BinaryEquals(Elements.Void) ||
elt.ID.BinaryEquals(Elements.CRC32))
{
await elt.ReadBodyAsync(stream, cancel_token).ConfigureAwait(false);
headers.Add(elt);
}
else
{
throw new BadDataException();
}
break;
case ReaderState.EndOfHeader:
if (elt.ID.BinaryEquals(Elements.Segment))
{
state = ReaderState.Segment;
goto parse_retry;
}
else if (elt.ID.BinaryEquals(Elements.EBML))
{
state = ReaderState.EBML;
goto parse_retry;
}
else if (elt.ID.BinaryEquals(Elements.Cluster))
{
clusters.Clear();
MemoryStream header;
using (header = new MemoryStream()) {
foreach (var c in headers)
{
c.Write(header);
}
}
headers.Clear();
stream_index = Channel.GenerateStreamID();
stream_origin = DateTime.Now;
position = 0;
sink.OnContentHeader(
new Content(stream_index, TimeSpan.Zero, 0, header.ToArray(), PCPChanPacketContinuation.None)
);
position += header.ToArray().LongLength;
var info = new AtomCollection();
if (ebml.DocType == "webm")
{
info.SetChanInfoType("WEBM");
info.SetChanInfoStreamType("video/webm");
info.SetChanInfoStreamExt(".webm");
}
else
{
info.SetChanInfoType("MKV");
info.SetChanInfoStreamType("video/x-matroska");
info.SetChanInfoStreamExt(".mkv");
}
sink.OnChannelInfo(new ChannelInfo(info));
state = ReaderState.Cluster;
goto parse_retry;
}
else if (elt.ID.BinaryEquals(Elements.Info))
{
await elt.ReadBodyAsync(stream, cancel_token).ConfigureAwait(false);
var s = new MemoryStream(elt.Data);
while (s.Position < s.Length)
{
var child = Element.ReadHeader(s);
if (child.ID.BinaryEquals(Elements.TimecodeScale))
{
timecode_scale = Element.ReadUInt(s, child.Size.Value) * 1.0;
}
else
{
child.ReadBody(s);
}
}
headers.Add(elt);
}
else
{
await elt.ReadBodyAsync(stream, cancel_token).ConfigureAwait(false);
headers.Add(elt);
}
break;
case ReaderState.Cluster:
if (elt.ID.BinaryEquals(Elements.Segment))
{
state = ReaderState.Segment;
goto parse_retry;
}
else if (elt.ID.BinaryEquals(Elements.EBML))
{
state = ReaderState.EBML;
goto parse_retry;
}
else if (elt.ID.BinaryEquals(Elements.Cluster))
{
if (clusters.Count > 0)
{
var timespan = clusters.Sum(c => c.Timespan);
if (timespan >= 30.0)
{
var sz = clusters.Sum(c => c.Timespan > 0 ? c.BlockSize : 0);
var kbps = (int)((sz * 8 / timespan + 900) / 1000.0);
var info = new AtomCollection();
info.SetChanInfoBitrate(kbps);
sink.OnChannelInfo(new ChannelInfo(info));
while (clusters.Count > 1)
{
clusters.RemoveFirst();
}
}
}
var cluster = new Cluster(elt);
clusters.AddLast(cluster);
sink.OnContent(
new Content(stream_index, DateTime.Now - stream_origin, position, elt.ToArray(), PCPChanPacketContinuation.None)
);
position += elt.ByteSize;
state = ReaderState.Timecode;
}
else if (elt.ID.BinaryEquals(Elements.Void) ||
elt.ID.BinaryEquals(Elements.CRC32))
{
await elt.ReadBodyAsync(stream, cancel_token).ConfigureAwait(false);
sink.OnContent(
new Content(stream_index, DateTime.Now - stream_origin, position, elt.ToArray(), PCPChanPacketContinuation.None)
);
position += elt.ByteSize;
}
else
{
throw new BadDataException();
}
break;
case ReaderState.Timecode:
if (elt.ID.BinaryEquals(Elements.Segment))
{
state = ReaderState.Segment;
goto parse_retry;
}
else if (elt.ID.BinaryEquals(Elements.EBML))
{
state = ReaderState.EBML;
goto parse_retry;
}
else if (elt.ID.BinaryEquals(Elements.Cluster))
{
state = ReaderState.Cluster;
goto parse_retry;
}
else if (elt.ID.BinaryEquals(Elements.SimpleBlock) ||
elt.ID.BinaryEquals(Elements.BlockGroup))
{
state = ReaderState.Block;
goto parse_retry;
}
else if (elt.ID.BinaryEquals(Elements.Timecode))
{
await elt.ReadBodyAsync(stream, cancel_token).ConfigureAwait(false);
if (clusters.Last != null)
{
clusters.Last.Value.Timecode =
Element.ReadUInt(new MemoryStream(elt.Data), elt.Data.Length) * (timecode_scale / 1000000000.0);
if (clusters.Count > 1)
{
clusters.Last.Previous.Value.Timespan = clusters.Last.Value.Timecode - clusters.Last.Previous.Value.Timecode;
}
}
sink.OnContent(
new Content(stream_index, DateTime.Now - stream_origin, position, elt.ToArray(), PCPChanPacketContinuation.None)
);
position += elt.ByteSize;
state = ReaderState.Block;
}
else
{
await elt.ReadBodyAsync(stream, cancel_token).ConfigureAwait(false);
sink.OnContent(
new Content(stream_index, DateTime.Now - stream_origin, position, elt.ToArray(), PCPChanPacketContinuation.None)
);
position += elt.ByteSize;
}
break;
case ReaderState.Block:
if (elt.ID.BinaryEquals(Elements.Segment))
{
state = ReaderState.Segment;
goto parse_retry;
}
else if (elt.ID.BinaryEquals(Elements.EBML))
{
state = ReaderState.EBML;
goto parse_retry;
}
else if (elt.ID.BinaryEquals(Elements.Cluster))
{
state = ReaderState.Cluster;
goto parse_retry;
}
else if ((elt.ID.BinaryEquals(Elements.SimpleBlock) ||
elt.ID.BinaryEquals(Elements.BlockGroup)) &&
(clusters.Last.Value.BlockID == null ||
elt.ID.BinaryEquals(clusters.Last.Value.BlockID)))
{
await elt.ReadBodyAsync(stream, cancel_token).ConfigureAwait(false);
clusters.Last.Value.BlockSize += elt.Size.Value;
clusters.Last.Value.BlockID = elt.ID.Binary;
sink.OnContent(
new Content(stream_index, DateTime.Now - stream_origin, position, elt.ToArray(), PCPChanPacketContinuation.None)
);
position += elt.ByteSize;
}
else if (clusters.Last.Value.BlockID == null)
{
await elt.ReadBodyAsync(stream, cancel_token).ConfigureAwait(false);
sink.OnContent(
new Content(stream_index, DateTime.Now - stream_origin, position, elt.ToArray(), PCPChanPacketContinuation.None)
);
position += elt.ByteSize;
}
else
{
state = ReaderState.Cluster;
goto parse_retry;
}
break;
}
}
catch (EndOfStreamException) {
eos = true;
}
catch (BadDataException) {
}
}
}