SSD Beispielprogramm 1 - Pascal

Copyright 2007 by Thomas Becker, D-49080 Osnabrück.
All rights reserved.

Inhalt

Dieses Beipiel zeigt, wie der Inhalt einer kompletten TAK-Datei sequentiell dekodiert und in eine neu erstellte Wavedatei geschrieben wird.

Wichtiger Hinweis

Das Beispiel soll vornehmlich den Einsatz der relevanten SDK-Funktionen illustrieren. Deshalb wurde der Code auf das nötigste reduziert. Er enthält weder eine ordentliche Fehlerbehandlung noch einen anständigen Resourcenschutz. Ferner sind einige Hilfsfunktionen (z.B. das Schreiben in die Wavedatei) nur Platzhalter ohne Implementierung.

Hilfsfunktionen

Teilweise Platzhalterfunktionen ohne Implementierung.

procedure Proto (const ALine : String);

Schreibt die Textzeile ALine auf den Bildschirm oder in eine Datei.

procedure Error (const AWhat : String);

begin
  Proto (AWhat);
  HALT;  // Programmausführung beenden.
end;

Gibt die Fehlermeldung AWhat aus und beendet die Programmausführung.

procedure DecoError (ADecoder : TtakSeekableStreamDecoder);

var
  ErrorMsg   : Array[0..tak_ErrorStringSizeMax - 1] of Char;

begin
  tak_SSD_GetErrorString (tak_SSD_State (ADecoder), ErrorMsg, tak_ErrorStringSizeMax);
  Error (ErrorMsg);
end;

Wird bei jedem Fehler mit dem betroffenen Decoderobjekt als Parameter aufgerufen und tut folgendes:

procedure WriteToWave (ABuf     : Pointer;
                       AByteNum : TtakInt32);

Schreibt AByteNum Bytes aus ABuf in die Wavedatei.

procedure CreateWaveHeader (const AStreamInfo : Ttak_str_StreamInfo);

Erzeugt einen Waveheader gemäß dem Audioformat und der Sampleanzahl, die AStreamInfo spezifiziert, und schreibt ihn in die Wavedatei.

type
  PByte_Arr = ^TByte_Arr;				      
  TByte_Arr = Array[0..$7FFFFFFF] of Byte;

Wird für den Zugriff auf den Inhalt dynamisch erzeugter Puffer benötigt.

Rahmenprogramm

Anmerkung: Die folgende Prozedur ruft einige Funktionen auf, die in den anschließenden Abschnitten detailiert besprochen werden.

procedure DecodeFile;

var
  Options      : TtakSSDOptions;
  Decoder      : TtakSeekableStreamDecoder;
  StreamInfo   : Ttak_str_StreamInfo;
  WaveMetaDesc : Ttak_str_SimpleWaveDataHeader;
  WaveMetaBuf  : PByte_Arr;

begin
  Options.Cpu   := tak_Cpu_Any;
  Options.Flags := tak_ssd_opt_SequentialRead;

  Decoder := tak_SSD_Create_FromFile ('d:\YourFilePath.tak', Options, Nil, Nil);
  if Decoder <> Nil then begin
    if tak_SSD_Valid (Decoder) = tak_True then begin
      if tak_SSD_GetStreamInfo (ADecoder, StreamInfo) <> tak_res_Ok then
        DecoError (Decoder);
          { Kann eigentlich nie passieren, wenn der Decoder nach der
            Erzeugung gültig war...
          }
      ReadWaveMetaData (Decoder, WaveMetaDesc, WaveMetaBuf);
      if WaveMetaBuf <> Nil then
        WriteWaveMetaDataHead (WaveMetaDesc, WaveMetaBuf)
          { Wenn WaveMetaBuf <> Nil, dann wurden die Wavemetaten
            erfolgreich gelesen. 
          }
      else
        CreateWaveHeader (StreamInfo);
          { Keine Wavemetadaten verfügbar, wir müssen selbst einen passenden
            Waveheader erzeugen. 
          }
      DecodeAudioData (StreamInfo);
      if WaveMetaBuf <> Nil then begin
        WriteWaveMetaDataTail (WaveMetaDesc, WaveMetaBuf);
        DestroyWaveMetaData (WaveMetaDesc, WaveMetaBuf);
      end;
    end  
    else
      DecoError (Decoder);
    tak_SSD_Destroy (Decoder);
  end
  else
    Proto ('System error: Unable to create Decoder!'); 
end;

Zunächste werden die Dekoderoptionen festgelegt. Das Feld Cpu sollte immer mit tak_Cpu_Any belegt werden, damit der Decoder alle verfügbaren Cpu-Features zur Optimierung einsetzt. Nicht verfügbare Features (wie z.B. SSE) werden automatisch deaktiviert.

Das Feld Flags enthält eine Kombination von Optionskonstanten. tak_ssd_opt_SequentialRead bietet maximale Performance, wenn die Datei ausschließlich sequentiell gelesen wird.

Standardmäßig werden die Audiodaten beschädigter Frames durch Stille ersetzt. Sollen sie stattdessen ersatzlos entfernt werden, ist zusätzlich das Flag tak_ssd_opt_SkipDamagedFrames anzugeben:

  Options.Flags := tak_ssd_opt_SequentialRead or tak_ssd_opt_SkipDamagedFrames;

Es wird empfohlen, dem Anwender der Software eine entsprechende Option anzubieten.

Wichtige Hinweise:

Das Ergebnis des Decoderkonstructors tak_SSD_Create_FromFile ist nur dann Nil, wenn ein gravierender Systemfehler aufgetreten ist (z.B. ein OutOfMemory). Nach erfolgreicher Konstruktion ist deshalb unbedingt zusätzlich mittels tak_SSD_Valid zu prüfen, ob ein Decoderfehler vorliegt, z.B. ein Fehler beim Öffnen der zu dekodierenden Ddatei.

tak_SSD_GetStreamInfo liest die StreamInfo, die eine Reihe nützlicher Informationen enthält, die später noch benötigt werden.

Nach Abschluß der Dekodierung zerstört tak_SSD_Destroy den Dekoder.

Wavemetadaten lesen

Standardmäßig enthalten TAK-Dateien im Metadatenbereich den Header der ursprünglichen Wavedatei und ggfs. an die Audiodaten angehängte Metadaten. Beides soll nun gelesen werden.

procedure DestroyWaveMetaData (const ADesc : Ttak_str_SimpleWaveDataHeader;
                               var   ABuf  : PByte_Arr);
  { Wenn ABuf <> Nil ist, wird der von ihm belegte Speicher
    entsprechend den Größenangaben in ADesc freigegeben und ABuf
    dann auf Nil gesetzt.
  }
  
begin
  if ABuf <> Nil then begin
    FreeMem (ABuf, ADesc.HeadSize + ADesc.TailSize);
    ABuf := Nil;
  end;
end;


procedure ReadWaveMetaData (    ADecoder : TtakSeekableStreamDecoder;
                            var ADesc    : Ttak_str_SimpleWaveDataHeader;
                            var ABuf     : PByte_Arr);
  { Liest die Wavemetadatenbeschreibung in ADesc und die Metadaten
    selbst in ABuf. Bei Erfolg ist ABuf nach der Rückkehr <> Nil.
	
    ABuf muß ggfs. später durch Aufruf von DestroyWaveMetaData
    freigegeben werden. 
  }

begin   
  ABuf := Nil;	  
  if tak_SSD_GetSimpleWaveDataDesc (ADecoder, ADesc) = tak_res_Ok then begin
    { Die Beschreibung der Wavemetadaten wurde erfolgreich gelesen.
    } 
    GetMem (ABuf, ADesc.HeadSize + ADesc.TailSize);
      { Speicher für die Metadaten reservieren.
      }
    if tak_SSD_ReadSimpleWaveData (ADecoder, ABuf,
                                   ADesc.HeadSize + ADesc.TailSize)
       <> tak_res_Ok
    then begin
      { Das Lesen der Metadaten schlug fehl.
      } 
      if tak_SSD_Valid (ADecoder) <> tak_True then 
         DecoError (ADecoder)
           { Fataler Fehler!
           } 
      else
        DestroyWaveMetaData (ADesc, ABuf);
          { Wir können weitermachen, müssen aber später unseren eigenen Header
            erzeugen.
          }
    end;	
  end	
  else
  begin
    { Die Beschreibung der Wavemetadaten konnte nicht gelesen werden.
    } 
    if tak_SSD_Valid (ADecoder) <> tak_True then 
      DecoError (ADecoder);
        { Ursache war ein fataler Fehler.
        }
  end;
end

tak_SSD_GetSimpleWaveDataDesc liest die Beschreibung der Wavemetadaten aus dem Stream. Da diese optional sind, muß ein Scheitern keinen Fehler bedeuten. Bei Bedarf gibt das Funktionsergebnis Aufschluss über die Ursache des Scheiterns.

Die Metadatenbeschreibung ADesc enthält die Größen für führende (Header) und abschließende Metadaten. Die Summe beider Werte ergibt die erforderliche Größe des Puffers ABuf, in den die Metadaten mittels tak_SSD_ReadSimpleWaveData eingelesen werden.

Wavemetadaten schreiben

Die folgenden zwei Funktionen schreiben den Waveheader und ggfs. abschließende Metadaten in die Zieldatei:

procedure WriteWaveMetaDataHead (const ADesc : Ttak_str_SimpleWaveDataHeader;
                                       ABuf  : PByte_Arr);
  { Schreibt, falls vorhanden, den Waveheader aus ABuf in die Wavedatei.
  }
  
begin
  if ABuf <> Nil then
    WriteToWave (ABuf, ADesc.HeadSize);
end;


procedure WriteWaveMetaDataTail (const ADesc : Ttak_str_SimpleWaveDataHeader;
                                       ABuf  : PByte_Arr);
  { Schreibt, falls vorhanden, abschließende Metadaten aus ABuf in die
    Wavedatei.
  }

begin
  if (ABuf <> Nil) and (ADesc.TailSize <> 0) then begin
    WriteToWave (@ABuf^[ADesc.HeadSize], ADesc.TailSize);
end;

Audiodaten dekodieren und ausgeben

Die Audiodaten werden sequentiell dekodiert und in die Wavedatei geschrieben.

procedure DecodeAudioData (const AStreamInfo : Ttak_str_StreamInfo);

var
  SamplesPerBuf : TtakInt32;
  SampleSize    : TtakInt32;
  BufSize       : TtakInt32;
  Audio         : PByte_Arr;
  ReadNum       : TtakInt32;
  OpResult      : TtakResult;

begin
  SamplesPerBuf := AStreamInfo.Sizes.FrameSizeInSamples;
  SampleSize    := AStreamInfo.Audio.BlockSize;
    { Frame- und Samplegröße.
    }
  BufSize := SamplesPerBuf * SampleSize;
    { Genug Platz um einen dekodierten Frame aufzunehemen.
    }
  GetMem (Audio, BufSize);
    { Für optimale Performance sollte der Speicher auf ein ganzzahliges
      Vielfaches von 4 oder besser noch 8 ausgerichtet sein.
    }

AStreamInfo enthält eine Reihe nützlicher Informationen, die zwecks einfacherer Handhabung in lokale Variablen kopiert werden:

Wird ein Stream sequentiell dekodiert, läßt sich die größte Geschwindigkeit erzielen, wenn die Audiodaten frameweise gelesen werden. Das Produkt aus der Framegröße SamplesPerBuf und der Samplegröße SampleSize ergibt die passende Größe BufSize des Pufferspeichers Audio für die Leseoperationen.

Bei jedem Durchlauf der folgenden Schleife wird ein Frame dekodiert und in die Wavedatei geschrieben:

  ReadNum := 1;
  while ReadNum > 0 do begin
    OpResult := tak_SSD_ReadAudio (Decoder, Audio, SamplesPerBuf, ReadNum);
    if  (OpResult <> tak_res_Ok)
    and (OpResult <> tak_res_ssd_FrameDamaged) then 
      DecoError (Decoder);
    if ReadNum > 0 then
      WriteToWave (Audio, ReadNum * SampleSize);
  end;
  
  FreeMem (Audio, BufSize);
end;

Mit tak_SSD_ReadAudio werden maximal SamplesPerBuf Samples in den Puffer Audio gelesen. ReadNum enthält die Anzahl tatsächlich gelesener Samples und kann nur beim Lesen des letzten Frames am Dateiende kleiner als SamplesPerBuf werden.

Das Leseergebnis tak_res_ssd_FrameDamaged zeigt an, daß beschädigte Audiodaten übersprungen bzw. durch Stille ersetzt wurden. Der Decoder kann nach einem solchen Fehler problemlos weiterarbeiten; es ist die Entscheidung des Programmierers, ob die Dekodierung beim Auftreten beschädigter Frames abgebrochen werden soll. Das Beispiel ignoriert sie.

Es ist nicht nötig, Meldungen über beschädigte Frames festzuhalten. Ein Aufruf von tak_SSD_GetStateInfo liefert jederzeit ausführliche Statusinformationen incl. Beschädigungen.