SubtitleEdit/libse/TransportStream/TransportStreamParser.cs
2016-05-29 13:02:24 +02:00

462 lines
20 KiB
C#

using Nikse.SubtitleEdit.Core.BluRaySup;
using Nikse.SubtitleEdit.Core.VobSub;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Nikse.SubtitleEdit.Core.TransportStream
{
/// <summary>
/// MPEG transport stream parser
/// </summary>
public class TransportStreamParser
{
public delegate void LoadTransportStreamCallback(long position, long total);
public int NumberOfNullPackets { get; private set; }
public long TotalNumberOfPackets { get; private set; }
public long TotalNumberOfPrivateStream1 { get; private set; }
public long TotalNumberOfPrivateStream1Continuation0 { get; private set; }
public List<int> SubtitlePacketIds { get; private set; }
public List<Packet> SubtitlePackets { get; private set; }
// public List<Packet> ProgramAssociationTables { get; private set; }
private Dictionary<int, List<DvbSubPes>> SubtitlesLookup { get; set; }
private Dictionary<int, List<TransportStreamSubtitle>> DvbSubtitlesLookup { get; set; }
public bool IsM2TransportStream { get; private set; }
public ulong FirstVideoPts { get; private set; }
public void Parse(string fileName, LoadTransportStreamCallback callback)
{
using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
Parse(fs, callback);
}
}
/// <summary>
/// Can be used with e.g. MemoryStream or FileStream
/// </summary>
/// <param name="ms">Input stream</param>
/// <param name="callback">Optional callback event to follow progress</param>
public void Parse(Stream ms, LoadTransportStreamCallback callback)
{
bool firstVideoPtsFound = false;
IsM2TransportStream = false;
NumberOfNullPackets = 0;
TotalNumberOfPackets = 0;
TotalNumberOfPrivateStream1 = 0;
TotalNumberOfPrivateStream1Continuation0 = 0;
SubtitlePacketIds = new List<int>();
SubtitlePackets = new List<Packet>();
// ProgramAssociationTables = new List<Packet>();
ms.Position = 0;
const int packetLength = 188;
DetectFormat(ms);
var packetBuffer = new byte[packetLength];
var m2TsTimeCodeBuffer = new byte[4];
long position = 0;
// check for Topfield .rec file
ms.Seek(position, SeekOrigin.Begin);
ms.Read(m2TsTimeCodeBuffer, 0, 3);
if (m2TsTimeCodeBuffer[0] == 0x54 && m2TsTimeCodeBuffer[1] == 0x46 && m2TsTimeCodeBuffer[2] == 0x72)
position = 3760;
long transportStreamLength = ms.Length;
while (position < transportStreamLength)
{
ms.Seek(position, SeekOrigin.Begin);
if (IsM2TransportStream)
{
ms.Read(m2TsTimeCodeBuffer, 0, m2TsTimeCodeBuffer.Length);
//var tc = (m2tsTimeCodeBuffer[0]<< 24) | (m2tsTimeCodeBuffer[1] << 16) | (m2tsTimeCodeBuffer[2] << 8) | (m2tsTimeCodeBuffer[3]);
position += m2TsTimeCodeBuffer.Length;
}
ms.Read(packetBuffer, 0, packetLength);
byte syncByte = packetBuffer[0];
if (syncByte == Packet.SynchronizationByte)
{
var packet = new Packet(packetBuffer);
if (packet.IsNullPacket)
{
NumberOfNullPackets++;
}
else if (!firstVideoPtsFound && packet.IsVideoStream)
{
if (packet.Payload != null && packet.Payload.Length > 10)
{
int presentationTimestampDecodeTimestampFlags = packet.Payload[7] >> 6;
if (presentationTimestampDecodeTimestampFlags == Helper.B00000010 ||
presentationTimestampDecodeTimestampFlags == Helper.B00000011)
{
FirstVideoPts = (ulong)packet.Payload[9 + 4] >> 1;
FirstVideoPts += (ulong)packet.Payload[9 + 3] << 7;
FirstVideoPts += (ulong)(packet.Payload[9 + 2] & Helper.B11111110) << 14;
FirstVideoPts += (ulong)packet.Payload[9 + 1] << 22;
FirstVideoPts += (ulong)(packet.Payload[9 + 0] & Helper.B00001110) << 29;
firstVideoPtsFound = true;
}
}
}
else if (packet.IsProgramAssociationTable)
{
//var sb = new StringBuilder();
//sb.AppendLine("PacketNo: " + TotalNumberOfPackets + 1);
//sb.AppendLine("PacketId: " + packet.PacketId);
//sb.AppendLine();
//sb.AppendLine("TransportErrorIndicator: " + packet.TransportErrorIndicator);
//sb.AppendLine("PayloadUnitStartIndicator: " + packet.PayloadUnitStartIndicator);
//sb.AppendLine("TransportPriority: " + packet.TransportPriority);
//sb.AppendLine("ScramblingControl: " + packet.ScramblingControl);
//sb.AppendLine("AdaptationFieldExist: " + packet.AdaptationFieldControl);
//sb.AppendLine("ContinuityCounter: " + packet.ContinuityCounter);
//sb.AppendLine();
//if (packet.AdaptationField != null)
//{
//sb.AppendLine("AdaptationFieldLength: " + packet.AdaptationField.Length);
//sb.AppendLine("DiscontinuityIndicator: " + packet.AdaptationField.DiscontinuityIndicator);
//sb.AppendLine("RandomAccessIndicator: " + packet.AdaptationField.RandomAccessIndicator);
//sb.AppendLine("ElementaryStreamPriorityIndicator: " + packet.AdaptationField.ElementaryStreamPriorityIndicator);
//sb.AppendLine("PcrFlag: " + packet.AdaptationField.PcrFlag);
//sb.AppendLine("OpcrFlag: " + packet.AdaptationField.OpcrFlag);
//sb.AppendLine("SplicingPointFlag: " + packet.AdaptationField.SplicingPointFlag);
//sb.AppendLine("TransportPrivateDataFlag: " + packet.AdaptationField.TransportPrivateDataFlag);
//sb.AppendLine("AdaptationFieldExtensionFlag: " + packet.AdaptationField.AdaptationFieldExtensionFlag);
//sb.AppendLine();
//}
//sb.AppendLine("TableId: " + packet.ProgramAssociationTable.TableId);
//sb.AppendLine("SectionLength: " + packet.ProgramAssociationTable.SectionLength);
//sb.AppendLine("TransportStreamId: " + packet.ProgramAssociationTable.TransportStreamId);
//sb.AppendLine("VersionNumber: " + packet.ProgramAssociationTable.VersionNumber);
//sb.AppendLine("CurrentNextIndicator: " + packet.ProgramAssociationTable.CurrentNextIndicator);
//sb.AppendLine("SectionNumber: " + packet.ProgramAssociationTable.SectionNumber);
//sb.AppendLine("LastSectionNumber: " + packet.ProgramAssociationTable.LastSectionNumber);
//ProgramAssociationTables.Add(packet);
}
else if (packet.IsPrivateStream1 || SubtitlePacketIds.Contains(packet.PacketId))
{
TotalNumberOfPrivateStream1++;
SubtitlePackets.Add(packet);
if (!SubtitlePacketIds.Contains(packet.PacketId))
{
SubtitlePacketIds.Add(packet.PacketId);
}
if (packet.ContinuityCounter == 0)
{
TotalNumberOfPrivateStream1Continuation0++;
//int pesExtensionlength = 0;
//if (12 + packet.AdaptionFieldLength < packetBuffer.Length)
// pesExtensionlength = 0xFF & packetBuffer[12 + packet.AdaptionFieldLength];
//int pesOffset = 13 + packet.AdaptionFieldLength + pesExtensionlength;
//bool isTeletext = (pesExtensionlength == 0x24 && (0xFF & packetBuffer[pesOffset]) >> 4 == 1);
//// workaround uk freesat teletext
//if (!isTeletext)
// isTeletext = (pesExtensionlength == 0x24 && (0xFF & packetBuffer[pesOffset]) == 0x99);
//if (!isTeletext)
//{
//}
}
if (callback != null)
{
callback.Invoke(ms.Position, transportStreamLength);
}
}
TotalNumberOfPackets++;
position += packetLength;
}
else
{
position++;
}
}
if (IsM2TransportStream)
{
DvbSubtitlesLookup = new Dictionary<int, List<TransportStreamSubtitle>>();
SubtitlesLookup = new Dictionary<int, List<DvbSubPes>>();
foreach (int pid in SubtitlePacketIds)
{
var bdMs = new MemoryStream();
var list = MakeSubtitlePesPackets(pid, SubtitlePackets);
var startMsList = new List<ulong>();
var endMsList = new List<ulong>();
foreach (var item in list)
{
item.WriteToStream(bdMs);
if (item.DataIdentifier == 0x16)
{
if (startMsList.Count <= endMsList.Count)
startMsList.Add(item.PresentationTimestampToMilliseconds());
else
endMsList.Add(item.PresentationTimestampToMilliseconds());
}
//else if (item.DataBuffer[0] == 0x80)
//{ // TODO: Load bd sub after 0x80, so we can be sure to get correct time code???
// endMsList.Add(item.PresentationTimestampToMilliseconds() / 90);
//}
}
SubtitlesLookup.Add(pid, list);
bdMs.Position = 0;
var sb = new StringBuilder();
var bdList = BluRaySupParser.ParseBluRaySup(bdMs, sb, true);
if (bdList.Count > 0)
{
var subList = new List<TransportStreamSubtitle>();
for (int k = 0; k < bdList.Count; k++)
{
var bdSup = bdList[k];
ulong startMs = 0;
if (k < startMsList.Count)
startMs = startMsList[k];
ulong endMs = 0;
if (k < endMsList.Count)
endMs = endMsList[k];
subList.Add(new TransportStreamSubtitle(bdSup, startMs, endMs, (ulong)((FirstVideoPts + 45) / 90.0)));
}
DvbSubtitlesLookup.Add(pid, subList);
}
}
SubtitlePacketIds.Clear();
foreach (int key in DvbSubtitlesLookup.Keys)
SubtitlePacketIds.Add(key);
SubtitlePacketIds.Sort();
return;
}
// check for SubPictureStreamId = 32
SubtitlesLookup = new Dictionary<int, List<DvbSubPes>>();
foreach (int pid in SubtitlePacketIds)
{
var list = MakeSubtitlePesPackets(pid, SubtitlePackets);
bool hasImageSubtitles = false;
foreach (var item in list)
{
if (item.IsDvbSubpicture)
{
hasImageSubtitles = true;
break;
}
}
if (hasImageSubtitles)
{
SubtitlesLookup.Add(pid, list);
}
}
SubtitlePacketIds.Clear();
foreach (int key in SubtitlesLookup.Keys)
SubtitlePacketIds.Add(key);
SubtitlePacketIds.Sort();
// Merge packets and set start/end time
DvbSubtitlesLookup = new Dictionary<int, List<TransportStreamSubtitle>>();
var firstVideoMs = (ulong)((FirstVideoPts + 45) / 90.0);
foreach (int pid in SubtitlePacketIds)
{
var subtitles = new List<TransportStreamSubtitle>();
var list = ParseAndRemoveEmpty(GetSubtitlePesPackets(pid));
if (list != null)
{
for (int i = 0; i < list.Count; i++)
{
var pes = list[i];
pes.ParseSegments();
if (pes.ObjectDataList.Count > 0)
{
var sub = new TransportStreamSubtitle();
sub.StartMilliseconds = pes.PresentationTimestampToMilliseconds();
sub.Pes = pes;
if (i + 1 < list.Count && list[i + 1].PresentationTimestampToMilliseconds() > 25)
sub.EndMilliseconds = list[i + 1].PresentationTimestampToMilliseconds() - 25;
if (sub.EndMilliseconds < sub.StartMilliseconds || sub.EndMilliseconds - sub.StartMilliseconds > (ulong)Configuration.Settings.General.SubtitleMaximumDisplayMilliseconds)
sub.EndMilliseconds = sub.StartMilliseconds + 3500;
subtitles.Add(sub);
if (sub.StartMilliseconds < firstVideoMs)
firstVideoMs = sub.StartMilliseconds;
}
}
}
foreach (var s in subtitles)
{
s.OffsetMilliseconds = firstVideoMs;
}
DvbSubtitlesLookup.Add(pid, subtitles);
}
SubtitlePacketIds.Clear();
foreach (int key in DvbSubtitlesLookup.Keys)
{
if (DvbSubtitlesLookup[key].Count > 0)
SubtitlePacketIds.Add(key);
}
SubtitlePacketIds.Sort();
}
public List<TransportStreamSubtitle> GetDvbSubtitles(int packetId)
{
if (DvbSubtitlesLookup.ContainsKey(packetId))
return DvbSubtitlesLookup[packetId];
return null;
}
internal static List<DvbSubPes> MakeSubtitlePesPackets(int packetId, List<Packet> subtitlePackets)
{
var list = new List<DvbSubPes>();
int last = -1;
var packetList = new List<Packet>();
foreach (Packet packet in subtitlePackets)
{
if (packet.PacketId == packetId)
{
if (packet.PayloadUnitStartIndicator)
{
if (packetList.Count > 0)
AddPesPacket(list, packetList);
packetList = new List<Packet>();
}
if (packet.Payload != null && last != packet.ContinuityCounter)
packetList.Add(packet);
last = packet.ContinuityCounter;
}
}
if (packetList.Count > 0)
AddPesPacket(list, packetList);
return list;
}
public List<DvbSubPes> GetSubtitlePesPackets(int packetId)
{
if (SubtitlesLookup.ContainsKey(packetId))
return SubtitlesLookup[packetId];
return null;
}
private List<DvbSubPes> ParseAndRemoveEmpty(List<DvbSubPes> list)
{
var newList = new List<DvbSubPes>();
foreach (var pes in list)
{
pes.ParseSegments();
if (pes.ObjectDataList.Count > 0 || pes.PresentationTimestamp > 0)
{
newList.Add(pes);
}
}
return newList;
}
private static void AddPesPacket(List<DvbSubPes> list, List<Packet> packetList)
{
int bufferSize = 0;
foreach (Packet p in packetList)
bufferSize += p.Payload.Length;
var pesData = new byte[bufferSize];
int pesIndex = 0;
foreach (Packet p in packetList)
{
Buffer.BlockCopy(p.Payload, 0, pesData, pesIndex, p.Payload.Length);
pesIndex += p.Payload.Length;
}
DvbSubPes pes;
if (VobSubParser.IsMpeg2PackHeader(pesData))
{
pes = new DvbSubPes(pesData, Mpeg2Header.Length);
}
else if (VobSubParser.IsPrivateStream1(pesData, 0))
{
pes = new DvbSubPes(pesData, 0);
}
else
{
pes = new DvbSubPes(pesData, 0);
}
list.Add(pes);
}
internal void DetectFormat(Stream ms)
{
if (ms.Length > 192 + 192 + 5)
{
ms.Seek(0, SeekOrigin.Begin);
var buffer = new byte[192 + 192 + 5];
ms.Read(buffer, 0, buffer.Length);
if (buffer[0] == Packet.SynchronizationByte && buffer[188] == Packet.SynchronizationByte)
return;
if (buffer[4] == Packet.SynchronizationByte && buffer[192 + 4] == Packet.SynchronizationByte && buffer[192 + 192 + 4] == Packet.SynchronizationByte)
{
IsM2TransportStream = true;
}
}
}
public static bool IsDvbSup(string fileName)
{
try
{
byte[] pesData = File.ReadAllBytes(fileName);
if (pesData[0] != 0x20 || pesData[1] != 0 || pesData[2] != 0x0F)
return false;
var pes = new DvbSubPes(0, pesData);
return pes.SubtitleSegments.Count > 0;
}
catch
{
return false;
}
}
public static List<TransportStreamSubtitle> GetDvbSup(string fileName)
{
byte[] pesData = File.ReadAllBytes(fileName);
var list = new List<DvbSubPes>();
int index = 0;
while (index < pesData.Length - 10)
{
var pes = new DvbSubPes(index, pesData);
index = pes.Length + 1;
list.Add(pes);
}
var subtitles = new List<TransportStreamSubtitle>();
int seconds = 0;
for (int i = 0; i < list.Count; i++)
{
var pes = list[i];
pes.ParseSegments();
if (pes.ObjectDataList.Count > 0)
{
var sub = new TransportStreamSubtitle();
sub.StartMilliseconds = (ulong)seconds * 1000UL;
seconds += pes.PageCompositions[0].PageTimeOut;
if (pes.PageCompositions.Count > 0)
sub.EndMilliseconds = sub.StartMilliseconds + (ulong)pes.PageCompositions[0].PageTimeOut * 1000UL;
else
sub.EndMilliseconds = sub.StartMilliseconds + 2500;
sub.Pes = pes;
subtitles.Add(sub);
}
if (pes.PageCompositions.Count > 0)
{
seconds += pes.PageCompositions[0].PageTimeOut;
}
}
return subtitles;
}
}
}