Vorbemerkungen

Notwendige Kentnisse im Umgang mit Binärdaten

Im Unterschied zu Textdateien lassen sich Binärdateien nicht ohne weiteres Parsen, in dem man Strings vergleicht. Binärdateien kann man nur unter Verwendung von bekannten Adressen auswerten. Die größte Schwierigkeit taucht im Umgang mit Binärdateien genau dann auf, wenn sich die gesuchten Informationen nicht in einem Byte, sondern einem der Bits verstecken.

Beispiel:

Ein Datenstrom der Folge 0111011101111001111111010101011011111111 wird beim auslesen Byteweise eingelesen, wie in folgender Abbildung dargestellt.

Byteweises auslesen einer Datei

Die folgende Abbildung wird vielleicht durch das Folgende Codefragment noch verständlicher: 

fread(buffer, size, 1, mFileP);

Das Beispiel zeigt, wie Binärdaten der Größe size aus einer Datei gelesen werden. Greife ich z.B. mit buffer[0] auf den Buffer buffer zu, dann enthält buffer[0] die obige Zeile 01110111 und buffer[2] enthält analog 11111101.

Möchte ich jetzt an eine Information herankommen, welche sich im 3. Byte an 2. Stelle befindet und 3 Bits lang ist, dann benötige ich die sogenannten Bitshift-Operatoren. Nehmen wir ferner an, an dieser besagten Stelle befindet sich eine Integer Zahl. Der Ermittlung dieser Zahl erfolgt mit:

int zahl = (buffer[2] & 0x38) >> 3;

Das sieht zugegebener Maßen sehr schwierig und kompliziert aus, ist aber nachvollziehbar. Zunächst wird der Ausdruck in der Klammer ausgewertet. Dort steht eine Und-Verknüpfung zwischen dem Buffer und der 0x38 drin. Bei der 0x38 handelt es sich um eine sogenannte Bitmaske (0x38 entspricht dem Binärcode 0011 1000). Die Verknüpfung die dort ausgeführt wird kann mit folgender Grafik verdeutlicht werden:

Die UND-Verknüpfung

Die & (UND)-Verknüpfung wird bitweise durchgeführt. Der Grund ist einfach. Wir wollen dabei alle unnötigen Informationen verlieren. Uns interessieren nur die Informationen ab dem 3. Bit. Wir sind jetzt aber noch nicht ganz fertig. Um den korrekten Wert zu erfahren, müssen wir noch diese Information drei Bits nach rechts verschieben. Das geschieht mit Hilfe der >> 3. Mathematisch gesehen ist das Shiften einer Zahl gleichbedeutend mit der binären Division durch 2. Für unsere zukünftige Vorstellung reicht es zu wissen, dass wir bei solchen Operationen die Informationen nach rechts, bzw. analog bei "<<" nach links verschieben. Den Grund entnimmt man dabei immer den entsprechenden Dokumenten wie z.B. der ISO13818-1. Dort ist immer genau definiert, an welchen Positionen der vielen Bits und Bytes, die wirklich wichtigen Infos stecken.

Ein letztes Beispiel zur Festigung:

Ziel ist die Ermittlung der PID in einem Transport-Stream Header. Dafür werfen wir ein Blick in die ISO-13818-1 hinein und finden folgenden Pseudo-Code:

Ermittlung der PID
Die Mnemonics (Abkürzungen) verraten uns, an welchen Stellen des Bitstroms wir uns "einhacken müssen". Dargestellt ist hier der typische Aufbau eines Transport-Stream-Paketes. Das Sync Byte belegt, wie der Name es bereits nahe legt, ein Byte (8 Bit). Danach folgen mit einem Bit der transport_error_indicator, der payload_unit_start_indicator und die transport_priority. Anschließend gibt es 13 Bits. Das Kürzel uimsbf bedeutet unsigned integer most significant bit first. Es handelt sich demnach um einen ganzzahlig positiven Integerwert, die größeren Potenzen stehen vorn (typische Network/Big Endian Byte Order).

Nehmen wir an, wir haben einen Puffer buffer, und an 0. Stelle steht der Headeranfang eines Transportstream-Paketes. Die Pid selbst wollen wir in einer int-Variable speichern. Wir definieren:

int pid = ((buffer[1] & 0x1F) << 8) | buffer[2];

Die Erklärung ist denke ich klar? Ganz einfach. Die ersten 3 Bits des Buffers buffer[1] sind für die PID uninteressant. Wir ignorieren diese in dem wir die Bitmaske 0x1F (0001 1111) benutzen. Bis dahin haben wir jedoch nur die ersten fünf Bits ausgelesen. Acht fehlen noch. Schaffen wir also Platz, in dem wir die bisherige Zahl acht Stellen nach links shiften. Anschließend wird einfach per ODER (|)-Verknüpfung der Rest des Buffers buffer[2] angefügt.

Fertig!

PS:

Alle Shiftoperationen im gesamten Projekt beruhen auf diesen Überlegungen. Auch in anderen Hardware nah programmierten Projekten finden sich diese Muster wieder.