11 | 12 | 2017

Das Parsen

Die Informationen des Transport-Streams und das Auslesen dieser Informationen

Der allgemeine Aufbau eines Transport Streams

Bevor ich zur Lösungsimplementation komme, möchte ich Ihnen hier erläutern, wie ein solcher Transport Stream technisch aufgebaut ist.

Der Transportstream vereinfacht

Vereinfacht kann man sagen, handelt es sich hier um eine Datei, die aus lauter Paketen besteht. Jedes Paket ist genau 188 Bytes groß wobei die ersten 4 Byte für den Header (den Kopf) reserviert sind. Darin enthalten sind

  1. Das Sync Byte (0x47)
  2. Die Programm PID
  3. Der Coninuity Counter

Das Sync Byte 0x47 (0x steht für 47 im Hexadezimalsystem und die Schreibweise ist in C/C++ gebräuchlich) taucht alle 188 Bytes auf. Mit Hilfe des Sync Bytes bin ich in der Lage, Anfang und Ende eines Paketes zu erkennen. Sobald ich während des Parsens merke, dass nach den 188 Byte noch kein neues Sync-Byte auftaucht, ist während der Übertragung etwas schief gelaufen.

Die Programm PID benötige ich, um die verschiedenen Pakete zuzuordnen. In der Praxis folgen die Video-, Audio- und sonstigen Daten keinem gewöhnlichem Muster, sondern tauchen in unterschiedlicher Reihenfolge auf (na gut, ein bisschen System gibt es da schon).

Der Continuity Counter existiert wegen der Fehlererkennung. Hieran kann man erkennen, ob Pakete doppelt gesendet wurden, z.B. wenn ein Übertragungsfehler vom System erkannt wurde.

Ich möchte an dieser Stelle noch die ISO13818-1 erwähnen, in der man den ganz genauen Aufbau einer solchen Datei nocheinmal nachlesen kann. Zu beziehen ist diese Datei in meiner Download-Section. Für die weiteren Ausführungen wird sie nicht benötig, da ich auf die wichtigsten Details hier eingehen werde.

Das Öffnen eines Videos in DreamDVD

Mein Programm benutzt eine Hilfsbibliothek, die libdigitaltv (früher: libtsstream), eine Bibliothek die ich für den gesamten Backendprozess entwickelt habe. Diese Bibliothek enthält vordefinierte Klassen, die sich mit dem Parsen, Demultiplexen und Multiplexen beschäftigen.

Zunächst wird eine Instanz der Klasse CTSFile erstellt. Sie übernimmt die Verwaltung über die TS-Dateien. Das war notwendig, weil die Dreambox in der Lage ist, nach einer bestimmten Anzahl Gigabytes die Dateien zu splitten. Möchte man den gesamten Film schneiden, muß man genau wissen, wie diese einzelnen Dateien zusammengefügt werden.

Danach benötigt man eine Instanz der Klasse CTSParser. Diese Instanz übernimmt die Aufgabe des Parsens einer Datei. Später verwendet man auch diese Instanz zum extrahieren bestimmter Video- und Audiodaten.

Sobald die Methode parse() aufgerufen wird, beginnt der eigentliche Parsingprozess. Dieser Methode werden auch noch Parameter übergeben. Diese dienen lediglich der Informationsbereitstellung, damit man später in einem Informationsfenster nachvollziehen kann, an welcher Stelle das Programm gerade steht. Der folgende Quellcodeausschnitt zeigt den Anfang der Methode parse().

CIndexFile* idf = new CIndexFile(mTSFiles);
if (idf->fileExists())
{

    info += "TSParser: Index-Datei gefunden! Verwende diese ...\n";
    idf->createTracesFromIndexFile(mTraces);
    // MPEG2-Parser aktualisieren
    CTSVideoTrace* trace = this->GetVideoTrace();
    trace->setIndexFile(idf);
    return;
} 

Hier wird zunächst überprüft, ob es eventuell eine Indexdatei gibt. Diese Datei wird gleich im eigentlichen Parse-Vorgang angelegt, und enthält die Adressen der möglichen Schnittstellen. Der Parsevorgang wird eingeleitet mit:

int blength = 16384*1024;
uint8_t* buffer = (uint8_t*) malloc(blength + 1);
if (buffer)
{
    memset(buffer, '\0', blength + 1);
    mTSFiles->read(buffer, blength);
    int pstart = getPackStart(buffer, 0, blength);

    // Buffer parsen
    int offset = pstart;
    bool parseStop = false;
    while (offset < blength || !parseStop)
    {
    if (parseStop) break;
    this->parseTSPacket(buffer, offset, parseStop, blength, info);
    offset = offset + 188;
    }
    ...
}

Die Methode getPackStart(...) stellt immer sicher, dass wir wirklich am Beginn eines Transport Streams stehen. Dafür guckt sich diese Methode die aufeinanderfolgenden fünf Pakete an und prüft, ob diese jeweils mit einem Sync Byte beginnen. Bei erfolgreicher Ausführung liefert Sie die Adresse des nächstfolgenden Paketstarts zurück. Der Vorteil ist, ich kann an eine beliebige Stelle in Stream springen, ich bekomme immer den nächstmöglichen Paket-Start als Adresse zurückgeliefert.

Die nächste interessante Methode ist die Methode parseTSPacket(Puffer, Offset, parseStop, blength, info). Puffer enthält unsere aus der Datei geladenen Binärdaten, das Offset die Stelle an der ich gerade in der Datei stehe, parseStop einer booleschen Variable mit der ich den ganzen Vorgang stoppen kann und blength, einer Integerzahl welche die Größe des Puffers enthält.

Wie sie die Codezeilen lesen müssen, erfahren Sie in meinen Vorbemerkungen. Die Methode parseTSPacket(...) liest ermittelt jedenfalls die PID. Anschließend wird darin überprüft, ob schon die entsprechenden Video- und Audio-PIDs des aufgenommenen Senders identifiziert wurden. Wenn nicht, dann wird sobald ein Paket mit der PID 0 gefunden wird, die Methode parsePAT aufgerufen.

PAT steht für Program Association Table. In dieser Tabelle befinden sich die Informationen über die betreffenden PMTs (Program Map Tables). Und eben diese PMT's müssen wir auslesen, um zu wissen welche Audio- und welche Video-PID's existieren im Stream.

Die Methode parsePAT untersucht jetzt das betreffende Paket nach den verschiedenen PMT-PIDs. Alle gefundenen PMT's werden jetzt in einer Liste gespeichert. Im nächsten Schritt wird mit Hilfe der Methode identifyPMT, diese eben erstellte Liste ausgewertet und dir richtige hier gültige PMT-PID zugeordnet. Ich habe mich für diesen Weg entschieden, weil ich weis, dass in meinen Dreambox-TS-Dateien eigentlich nur eine PMT existiert. In der PAT stehen natürlich mehrere drin, weil die Dreambox diese bei der Aufnahme unbearbeitet übernimmt, obwohl sie nur ein Programm aufnimmt.