Projekte > Audio-CD (CDDA) > Tracks kopieren

Audio Tracks kopieren

  • Beschreibung
  • Beispiel
  • Track kopieren
  • Wave-Header schreiben
  • Sektoren lesen
  • Demo
  • Änderungen
  • Beschreibung

    Durch das Lesen der TOC sind die Startadressen der Tracks sowie des LeadOuts bekannt. Nun müssen die Sektoren der Tracks kopiert werden.

    Beispiel

    Dazu wieder das bekannte Beispiel:

    Track kopieren

    Die Spieldauer beziehungsweise die Länge der Tracks ergibt sich, wie bereits in den Demos gesehen, aus den Startadressen des betreffenden und des darauffolgenden Tracks beziehungsweise des LeadOuts. Die Pausen zwischen den Tracks werden nicht berücksichtigt.

    Um einen Track zu kopieren, werden der Routine die Eigenschaften mitgeteilt. Das heißt die Adresse als LBA, ab welcher gelesen werden soll, die Anzahl der zu lesenden Sektoren und der Name der Datei, in welcher die Daten gespeichert werden sollen.

    Zunächst wird die Datei angelegt und mit einem Header versehen. Danach werden die Sektoren in Stücken gelesen und in der Datei gespeichert. Abschließend wird der Header aktualisiert und die Datei geschlossen. Dies sieht ohne weitere Bedingungen so aus:

    type
      TTrack = record
        dwOffset   : Cardinal;
        dwLength   : Cardinal;
        stFilename : String;
      end;
     
    function ReadTrack(aTrack: TTrack; aDevice: THandle): Boolean;
    Const
      fdwSectors = 20;
    var
      dwReturned  : Cardinal;
      dwSectors   : Cardinal;
      aFileStream : TFileStream;
    begin
      {
      * Wave initialisieren.
      }
      aFileStream := TFileStream.Create(aTrack.stFileName, fmCreate);
      WriteWavHeader(aFileStream, 0);
      {
      *  Lesen.
      }
      dwReturned := 0;
      Result     := True;
      while (dwReturned < aTrack.dwLength) and Result
      do begin
        {
        *  Auf Rest prüfen.
        }
        if (dwReturned + fdwSectors) < aTrack.dwLength
        then dwSectors := fdwSectors
        else dwSectors := aTrack.dwLength - dwReturned;
        {
        *  Daten lesen.
        }
        Result := ReadCDDA(aTrack.dwOffset + dwReturned, dwSectors, aDevice,
                           aFileStream);
        inc(dwReturned, dwSectors);
      end;
      {
      *  Waveheader aktualisieren und Datei schließen.
      }
      WriteWavHeader(aFileStream, aFileStream.Size);
      aFileStream.Free;
    end;
    

    Wave-Header schreiben

    Für das Schreiben des WAVE-Header wird eine eigene Procedure verwendet. Die einzustellenden Werte sind für eine AudioCD standardisiert.

    const
      WAVE_FORMAT_PCM = $0001;
     
    type
      TID = Array[0..3] of AnsiChar;
     
    type
      TWaveHeader = record
        {  RiffHeader  }
        ChunkID       : TID;       //  'RIFF'
        ChunkSize     : Cardinal;  //  Die Anzahl der folgenden Daten bis Dateiende.
        Format        : TID;       //  'WAVE'
        {  SubChunk1  }
        SubChunk1ID   : TID;       //  'fmt '
        SubChunk1Size : Cardinal;  //  Länge des folgenden Headers - Bei PCM 16
        AudioFormat   : Word;      //  PCM = 1
        NumChannels   : Word;      //  Mono = 1, Stereo = 2
        SampleRate    : Cardinal;  //  Bei CD 44100
        ByteRate      : Cardinal;  //  = SampleRate * NumChannels * BitsPerSample / 8
        BlockAlign    : Word;      //  = NumChannels * BitsPerSample / 8
        BitsPerSample : Word;      //  8 bits = 8, 16 bits = 16
        {  SubChunk2  }
        SubChunk2ID   : TID;       //  'data'
        SubChunk2Size : Cardinal;  //  Die Anzahl der folgenden Daten bis Dateiende.
      end;
     
    procedure WriteWavHeader(aFS: TFileStream; aSize: Cardinal);
    var
      Wave: TWaveHeader;
    begin
      aFS.Seek(0, soFromBeginning);
      {  RiffHeader  }
      Wave.ChunkID       := 'RIFF';
      Wave.ChunkSize     := aSize - SizeOf(TWaveHeader) + SizeOf(Wave.ChunkID) +
                                                          SizeOf(Wave.ChunkSize);
      Wave.Format        := 'WAVE';
      {  SubChunk1  }
      Wave.SubChunk1ID   := 'fmt ';
      Wave.SubChunk1Size := 16;
      Wave.AudioFormat   := WAVE_FORMAT_PCM;
      Wave.NumChannels   := 2;
      Wave.SampleRate    := 44100;
      Wave.ByteRate      := 44100 * 2 * 2;
      Wave.BlockAlign    := 2 * 16 div 8;
      Wave.BitsPerSample := 16;
      {  SubChunk2  }
      Wave.SubChunk2ID   := 'data';
      Wave.SubChunk2Size := aSize - SizeOf(TWaveHeader);
      {  Header schreiben.  }
      aFS.Write(Wave, SizeOf(TWaveHeader));
    end;
    

    Der Header wird nach dem Schreiben der Datei neu geschrieben, weil die Werte für die ChunkSize und die SubChunk2Size von der Größe der Datei abhängig sind.

    Sektoren lesen

    Das Lesen der Sektoren erfolgt in einer eigenen Routine. Für die Funktion DeviceIOControl bietet es sich an, die Eingabestruktur RAW_READ_INFO und den Ausgabepuffer gleich als Pointer zu deklarieren und den dafür benötigten Speicher zu reservieren. Die Werte für die Eingabestruktur Raw_Read_Info sind nicht ganz logisch. Für den Offset muss die LBA mit der Größe der CDROM-Sektoren multipliziert werden, wobei dabei von logischen Datensektoren ausgegangen wird, welche 2048 Byte groß sind. Der SektorCount wird erwartungsgemäß mit der Anzahl der zu lesenden Sektoren angegeben. Beim TrackMode wird CDDA angegeben und damit die zu lesende Sektorgröße eingestellt. Bei der Größe des Ausgabepuffers wird natürlich die Größe der CDDA/Audiosektoren verwendet.

    const
      CB_CDROMSECTOR = 2048;
      CB_AUDIOSECTOR = 2352;
     
    type
      TTRACK_MODE_TYPE = (YellowMode2, XAForm2, CDDA, RawWithC2AndSubCode, RawWithC2,
                          RawWithSubCode);
     
    type
      TRAW_READ_INFO = record
        DiskOffset  : LARGE_INTEGER;
        SectorCount : ULONG;
        TrackMode   : TTRACK_MODE_TYPE;
      end;
      PTRAW_READ_INFO = ^TRAW_READ_INFO;
     
    const
      IOCTL_CDROM_RAW_READ = (IOCTL_CDROM_BASE shl 16) or (FILE_READ_ACCESS shl 14) or
                             ($000F shl 2) or METHOD_OUT_DIRECT;
     
    function ReadCDDA(startLBA, countSectors: Cardinal; aDevice: THandle;
                      aFS: TFileStream): Boolean;
    var
      pRawReadInfo : PTRAW_READ_INFO;
      pBuffer      : PAnsiChar;
      BufferSize   : Cardinal;
      dwReturned   : Cardinal;
    begin
      Result := False;
      {
      *  Vorbereitung Struktur RAW_READ_INFO
      }
      pRawReadInfo := AllocMem(SizeOf(TRAW_READ_INFO));
      pRawReadInfo^.DiskOffset.QuadPart := startLBA * CB_CDROMSECTOR;
      pRawReadInfo^.SectorCount         := countSectors;
      pRawReadInfo^.TrackMode           := CDDA;
      {
      *  Speicher holen
      }
      BufferSize := CB_AUDIOSECTOR * countSectors;
      pBuffer    := AllocMem(BufferSize);
      {
      *  Handle zum Laufwerk holen.
      }
      if (fhDevice <> INVALID_HANDLE_VALUE) and (pBuffer <> nil)
      then begin
        {
        *  Den Lesebefehl ausführen.
        }
        Result := DeviceIoControl(aDevice, IOCTL_CDROM_RAW_READ,
                                  pRawReadInfo, SizeOf(TRAW_READ_INFO),
                                  pBuffer, BufferSize, dwReturned, nil);
        {
        *  Speichern
        }
        if Result
        then aFS.Write(pBuffer^, dwReturned);
      end;
      {
      *  Speicher freigeben.
      }
      FreeMem(pBuffer);
      FreeMem(pRawReadInfo);
    end;
    

    Der Aufruf von ReadTrack erfolgt aus einer Routine, welche zunächst das Handle zum Laufwerk holt, die Daten für den Aufruf zusammenstellt und die Funktion aufruft. Es ist sinnvoll, den Code zum Beispiel mit einer Abbruchmöglichkeit und einer Fortschrittsanzeige zu erweitern. Des weiteren läßt sich die Kopiergeschwindigkeit durch Optimierung der Anzahl der auf einmal zu lesenden Sektoren erhöhen. Eventuell kann man auch eine Routine einbauen, welche die durch die TOC vorgegebenen Schnittmarken auf Richtigkeit überprüft.

    Demo

    Demo, welche sich die Infos von Music Brainz holt und die Tracks ohne Fortschrittsanzeige kopiert:

    CD Copy Step 2 (podCDCopyStep2.7z - 556 kb) MD5 (1 kb).
    Compiler: Turbo Delphi
    Stand: 13. Juli 2017

    Änderungen an der Demo

    13.07.2017Änderung: Reservierte Zeichen werden vor dem Speichern aus dem Dateinamen entfernt.
    29.07.2015Änderung: Austausch der Downloadroutine.
    Änderung: Anzeige der DiscID und der ReleaseID.
    Hinzu: Fortschrittsanzeige
    26.07.2015Hinzu: Splash Screen
    Änderung: Überarbeitung, damit es mit Turbo Delphi und Delphi XE5 läuft.
    10.06.2012Fehler: Bei den Helferlein zur Berechnung der Titellänge fehlte eine Klammer.
    09.06.2012Fehler: Auswahl des Zielordners korrigiert.
    Fehler: Länge des letzten Tracks beim Auslesen korrigiert.
    Änderung: Routine für SHA1-Hash ausgetauscht.