Projekte > Audio-CD (CDDA) > MP3 speichern

Blade DLL Interface für LAME

  • Beschreibung
  • MP3-Header-Dateien
  • Einstellungen
  • Puffer
  • Encoden
  • Demos
  • Änderungen an den Demos
  • Links
  • Beschreibung

    Blade war einer der ersten Encoder. Er ging wohl kaum über den Beispiel-Encoder des Fraunhofer-Instituts hinaus und bot keine gute Qualität. Die Lame-Encoder-DLL enthält ein kompatibles Interface, welches hier benutzt werden soll. Das Projekt bietet auf seiner Seite nur den Quelltext an. Kompilate der Lame_enc.DLL erhält man unter anderen auf RareWares.

    MP3-Header-Dateien

    Hier wird nun die seit Oktober 2017 verfügbare Version 3.100 verwendet. Das Archiv bei RareWares enthält neben der Lame.exe und der Lame_enc.DLL verschiedene HTML-Seiten des lame-Projektes. Im Quelltext der lame_enc.dll findet sich im Verzeichnis DLL die Headerdatei BladeMP3EncDLL.h und eine Unit MP3export.pas. Darin sind die meisten Deklaratonen und eine Routine zum Encoden enthalten. Ich verwende in den Demos eine eigene Unit, welche die lame_enc.dll anfangs staitsch einband. Mittlerweile wird die Dll dynamisch eingebunden und enthält mehr Funktionen.

    unit uBladeMP3EncDLL;
    {
    *  Übersetzung der BladeMP3EncDLL.h
    }
    interface
    {*
     * Blade Type of DLL Interface for Lame encoder
     *
     * Copyright (c) 1999-2002 A.L. Faber
     * Based on bladedll.h version 1.0 written by Jukka Poikolainen
     *
     * This library is free software; you can redistribute it and/or
     * modify it under the terms of the GNU Lesser General Public
     * License as published by the Free Software Foundation; either
     * version 2 of the License, or (at your option) any later version.
     *
     * This library is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     * Lesser General Public License for more details.
     *
     * You should have received a copy of the GNU Lesser General Public
     * License along with this library; if not, write to the
     * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
     * Boston, MA  02111-1307, USA.
     *}
    type
      {* type definitions *}
      HBE_STREAM  = Cardinal;
      PHBE_STREAM = ^HBE_STREAM;
      BE_ERR      = Integer;
    const
      {* encoding formats *}
      BE_CONFIG_MP3  = 0;
      BE_CONFIG_AAC  = 1;          // aus Bladedll.h
      BE_CONFIG_LAME = 256;
      {* error codes *}
      BE_ERR_SUCCESSFUL                = $00000000;
      BE_ERR_INVALID_FORMAT            = $00000001;
      BE_ERR_INVALID_FORMAT_PARAMETERS = $00000002;
      BE_ERR_NO_MORE_HANDLES           = $00000003;
      BE_ERR_INVALID_HANDLE            = $00000004;
      BE_ERR_BUFFER_TOO_SMALL          = $00000005;
      {* other constants *}}
      BE_MAX_HOMEPAGE = 128;
      {* format specific variables *}
      BE_MP3_MODE_STEREO      = 0;
      BE_MP3_MODE_JSTEREO     = 1;
      BE_MP3_MODE_DUALCHANNEL = 2;
      BE_MP3_MODE_MONO        = 3;
    type
      {* Methode für die variable Bitrate *}
      TVBRMETHOD = (
        VBR_METHOD_NONE    = -1,
        VBR_METHOD_DEFAULT =  0,
        VBR_METHOD_OLD     =  1,
        VBR_METHOD_NEW     =  2,
        VBR_METHOD_MTRH    =  3,
        VBR_METHOD_ABR     =  4
      );
      {* Qualitätseinstellungen *}
      TLAME_QUALITY_PRESET = (
        LQP_NOPRESET         = -1,
        // QUALITY PRESETS
        LQP_NORMAL_QUALITY   = 0,
        LQP_LOW_QUALITY      = 1,
        LQP_HIGH_QUALITY     = 2,
        LQP_VOICE_QUALITY    = 3,
        LQP_R3MIX            = 4,
        LQP_VERYHIGH_QUALITY = 5,
        LQP_STANDARD         = 6,
        LQP_FAST_STANDARD    = 7,
        LQP_EXTREME          = 8,
        LQP_FAST_EXTREME     = 9,
        LQP_INSANE           = 10,
        LQP_ABR              = 11,
        LQP_CBR              = 12,
        LQP_MEDIUM           = 13,
        LQP_FAST_MEDIUM      = 14,
        // NEW PRESET VALUES
        LQP_PHONE            = 1000,
        LQP_SW               = 2000,
        LQP_AM               = 3000,
        LQP_FM               = 4000,
        LQP_VOICE            = 5000,
        LQP_RADIO            = 6000,
        LQP_TAPE             = 7000,
        LQP_HIFI             = 8000,
        LQP_CD               = 9000,
        LQP_STUDIO           = 10000
      );
      {* BE_CONFIG_MP3 *}
      TBE_CONFIG_MP3 = packed record
        dwSampleRate : Cardinal;  // 48000, 44100 and 32000 allowed
        byMode       : Byte;      // BE_MP3_MODE_STEREO, BE_MP3_MODE_DUALCHANNEL, ...
        wBitrate     : Word;      // 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, ...
        bPrivate     : LongBool;
        bCRC         : LongBool;
        bCopyright   : LongBool;
        bOriginal    : LongBool;
      end;
      {* LAME HEADER VERSION 1 *}
      TBE_CONFIG_LHV1 = packed record
        // STRUCTURE INFORMATION
        dwStructVersion : Cardinal;
        dwStructSize    : Cardinal;
        // BASIC ENCODER SETTINGS
        dwSampleRate   : Cardinal;  // SAMPLERATE OF INPUT FILE
        dwReSampleRate : Cardinal;  // DOWNSAMPLERATE, 0=ENCODER DECIDES
        nMode          : Integer;   // BE_MP3_MODE_STEREO, BE_MP3_MODE_DUALCHANNEL, ...
        dwBitrate      : Cardinal;  // CBR bitrate, VBR min bitrate
        dwMaxBitrate   : Cardinal;  // CBR ignored, VBR Max bitrate
        nPreset        : Integer;   // Quality preset, use one of the settings of ...
        dwMpegVersion  : Cardinal;  // FUTURE USE, MPEG-1 OR MPEG-2
        dwPsyModel     : Cardinal;  // FUTURE USE, SET TO 0
        dwEmphasis     : Cardinal;  // FUTURE USE, SET TO 0
        // BIT STREAM SETTINGS
        bPrivate       : LongBool;  // Set Private Bit (TRUE/FALSE)
        bCRC           : LongBool;  // Insert CRC (TRUE/FALSE)
        bCopyright     : LongBool;  // Set Copyright Bit (TRUE/FALSE)
        bOriginal      : LongBool;  // Set Original Bit (TRUE/FALSE)
        // VBR STUFF
        WriteVBRHeader : LongBool;  // WRITE XING VBR HEADER (TRUE/FALSE)
        bEnableVBR     : LongBool;  // USE VBR ENCODING (TRUE/FALSE)
        nVBRQuality    : Integer;   // VBR QUALITY 0..9
        dwVbrAbr_bps   : Cardinal;  // Use ABR in stead of nVBRQuality
        nVbrMethod     : TVBRMETHOD;
        bNoRes         : LongBool;  // Disable Bit resorvoir (TRUE/FALSE)
        // MISC SETTINGS
        bStrictIso     : LongBool;  // Use strict ISO encoding rules (TRUE/FALSE)
        nQuality       : Word;      // Quality Setting, HIGH BYTE should be NOT LOW ...
        // FUTURE USE, SET TO 0, align strucutre to 331 bytes
        btReserved     : Array[0..255 - 4 * SizeOf(Cardinal) {- SizeOf(WORD)}] of Byte;
      end;
      {* AAC HEADER *}
      TBE_CONFIG_AAC = packed record
        dwSampleRate     : Cardinal;
        byMode           : Byte;
        wBitrate         : Word;
        byEncodingMethod : Byte;
      end;
      TFormat = packed record
      case dwConfig : Cardinal of // BE_CONFIG_XXXXX
        BE_CONFIG_MP3  : (mp3  : TBE_CONFIG_MP3);
        BE_CONFIG_LAME : (lhv1 : TBE_CONFIG_LHV1);
        BE_CONFIG_AAC  : (aac  : TBE_CONFIG_AAC);
      end;
      TBE_CONFIG = packed record
        {* Currently only BE_CONFIG_MP3 is supported *}
        Format : TFormat;
      end;
      PBE_CONFIG = ^TBE_CONFIG;
    const
      CURRENT_STRUCT_VERSION = 1;
      CURRENT_STRUCT_SIZE    = sizeof(TBE_CONFIG);        // is currently 331 bytes
    type
      TBE_VERSION = packed record
        // BladeEnc DLL Version number
        byDLLMajorVersion : Byte;
        byDLLMinorVersion : Byte;
        // BladeEnc Engine Version Number
        byMajorVersion : Byte;
        byMinorVersion : Byte;
        // DLL Release date
        byDay   : Byte;
        byMonth : Byte;
        wYear   : Word;
        // BladeEnc Homepage URL
        zHomepage : Array[0..BE_MAX_HOMEPAGE] of AnsiChar;
        byAlphaLevel : Byte;
        byBetaLevel  : Byte;
        byMMXEnabled : Byte;
        btReserved : Array[1..125] of Byte;
      end;
      PBE_VERSION = ^TBE_VERSION;
    {
    ********************************************************************************
    *  Anpassungen zur dynamischen Einbindung der DLL.
    *
    }
    const
      szNameDLL                 = 'lame_enc.dll';
      szBeCloseStream           = 'beCloseStream';
      szBeDeInitStream          = 'beDeinitStream';
      szBeEncodeChunk           = 'beEncodeChunk';
      szBeEncodeChunkFloatS16NI = 'beEncodeChunkFloatS16NI';
      szBeFlushNoGap            = 'beFlushNoGap';
      szBeInitStream            = 'beInitStream';
      szBeVersion               = 'beVersion';
      szBeWriteInfoTag          = 'beWriteInfoTag';
      szBeWriteVBRHeader        = 'beWriteVBRHeader';
    type
      TFNBeInitStream            = function(var pbeConfig: TBE_CONFIG;
                                            var dwSamples, dwBufferSize: Cardinal;
                                            var phbeStream: HBE_STREAM): BE_ERR; CDECL;
      TFNBeEncodeChunk           = function(hbeStream: HBE_STREAM; nSamples: Cardinal;
                                            pSamples: PShortInt; pOutput: PByte;
                                            var pdwOutput: Cardinal): BE_ERR; CDECL;
      TFNBeEncodeChunkFloatS16NI = function(hbeStream: HBE_STREAM; nSamples: Cardinal;
                                            buffer_l, buffer_r: PSingle; pOutput:
                                            varPByte; pdwOutput: Cardinal): BE_ERR; CDECL;
      TFNBeDeInitStream          = function(hbeStream: HBE_STREAM; pOutput: PByte;
                                            var pdwOutput: Cardinal): BE_ERR; CDECL;
      TFNBeCloseStream           = function(hbeStream: HBE_STREAM): BE_ERR; CDECL;
      TFNBeVersion               = procedure(var pbeVersion: TBE_VERSION); CDECL;
      TFNBeWriteVBRHeader        = function(lpszFileName: PAnsiChar): BE_ERR; CDECL;
      TFNBeFlushNoGap            = function(hbeStream: HBE_STREAM; pOutput: PByte;
                                            pdwOutput: Cardinal): BE_ERR; CDECL;
      TFNBeWriteInfoTag          = function(hbeStream: HBE_STREAM;
                                            lpszFileName: PAnsiChar): BE_ERR; CDECL;
    var
      hLibrary                : THandle;
      beLoaded                : Boolean;
      beInitStream            : TFNbeInitStream;
      beEncodeChunk           : TFNbeEncodeChunk;
      beEncodeChunkFloatS16NI : TFNBeEncodeChunkFloatS16NI;
      beDeInitStream          : TFNbeDeInitStream;
      beCloseStream           : TFNbeCloseStream;
      beVersion               : TFNBeVersion;
      beWriteVBRHeader        : TFNBeWriteVBRHeader;
      beFlushNoGap            : TFNBeFlushNoGap;
      beWriteInfoTag          : TFNBeWriteInfoTag;
     
    procedure LoadLameDLL;
    procedure CloseLameDLL;
     
    implementation
     
    uses
      Windows;
     
    procedure LoadLameDLL;
    {*******************************************************************************
    *  Library laden und die Einsprungspunkte holen.
    }
    begin
      {*  Init  *}
      beLoaded                 := False;
      @beInitStream            := nil;
      @beEncodeChunk           := nil;
      @beEncodeChunkFloatS16NI := nil;
      @beDeInitStream          := nil;
      @beCloseStream           := nil;
      @beVersion               := nil;
      @beWriteVBRHeader        := nil;
      @beFlushNoGap            := nil;
      @beWriteInfoTag          := nil;
      {*  Library laden.  *}
      hLibrary := LoadLibrary(szNameDLL);
      {*  Wenn geladen ...  *}
      if hLibrary > 0
      then begin
        {*  ... Einsprungpunkte holen.  *}
        @beInitStream            := GetProcAddress(hLibrary, szBeInitStream);
        @beEncodeChunk           := GetProcAddress(hLibrary, szBeEncodeChunk);
        @beEncodeChunkFloatS16NI := GetProcAddress(hLibrary, szBeEncodeChunkFloatS16NI);
        @beDeInitStream          := GetProcAddress(hLibrary, szBeDeInitStream);
        @beCloseStream           := GetProcAddress(hLibrary, szBeCloseStream);
        @beVersion               := GetProcAddress(hLibrary, szBeVersion);
        @beWriteVBRHeader        := GetProcAddress(hLibrary, szBeWriteVBRHeader);
        @beFlushNoGap            := GetProcAddress(hLibrary, szBeFlushNoGap);
        @beWriteInfoTag          := GetProcAddress(hLibrary, szBeWriteInfoTag);
        {*  Auswerten, ob alle Einsprungpunkte gefunden wurden.  *}
        beLoaded := Assigned(beInitStream) and
                    Assigned(beEncodeChunk) and
                    Assigned(beEncodeChunkFloatS16NI) and
                    Assigned(beDeInitStream) and
                    Assigned(beCloseStream) and
                    Assigned(beVersion) and
                    Assigned(beWriteVBRHeader) and
                    Assigned(beFlushNoGap) and
                    Assigned(beWriteInfoTag);
      end;
    end;
     
    procedure CloseLameDLL;
    {*******************************************************************************
    *  Library entladen.
    }
    begin
      {*  Handle schliessen.  *}
      CloseHandle(hLibrary);
    end;
     
    initialization
     
      LoadLameDLL;
     
    end.
    

    Die Struktur TBE_CONFIG nimmt die Einstellungen TLHV1 für das Encoding auf. Mit der Funktion beInitStream wird der Encoder initialisiert und ihm die Konfiguration mitgeteilt. Dafür erhält man die Anzahl der Sample, welche dem Encoder jeweils übergeben werden sollen, die Größe für den für die Antwort bereitzustellenden Buffer und das Handle für den Stream. Der Funktion beEncodeChunk wird das Handle, die Anzahl der zu encodenden Sample und der Pointer zu diesen Samples übergeben. Bei älteren Versionen der Lame_enc.DLL musste die Anzahl der Sample exakt stimmen. Nun dürfen es auch mehr sein. Zurück bekommt man den Pointer auf den Output und dessen Größe. Nach dem letzten Aufruf der Funktion beEncodeChunk wird die Funktion beDeInitStream aufgerufen. Ihr wird wieder das Handle übergeben und man erhält den Pointer auf den letzten Output sowie dessen Größe. Mit der Funktion beCloseStream schließt man den Stream.

    Einstellungen

    BE_CONFIG.dwConfig - Gibt an, welcher Header konfiguriert wird.

    BE_CONFIG.LHV1.dwStructVersion - Version der Struktur.

    BE_CONFIG.LHV1.dwStructSize - Größe der Struktur.

    BE_CONFIG.LHV1.dwSampleRate - Samplerate der Quelle, akzeptiert werden 32000, 44100 und 48000 Hz.

    BE_CONFIG.LHV1.dwReSampleRate - Samplerate des Zieles, akzeptiert werden 32000, 44100 und 48000 Hz. Wenn die Samplerate nicht geändert werden soll wird 0 angegeben.

    BE_CONFIG.LHV1.nMode - Mode der Audiokanäle: Stereo (0), Joint Stereo (1), Dualchannel (2) und Mono (3).

    BE_CONFIG.LHV1.dwBitrate - Bei konstanter Bitrate ist es die Bitrate, bei variabler Bitrate ist es die minimale Bitrate.

    BE_CONFIG.LHV1.dwMaxBitrate - Wird nur bei maximaler Bitrate angegeben.

    BE_CONFIG.LHV1.nPreset - Ein Wert aus der Aufzählung von TLAME_QUALITY_PRESET. Dabei ist zu beachten, dass manche Presets eigene Bitrateneinstellungen beinhalten und somit individuelle aussen vor bleiben.

    BE_CONFIG.LHV1.dwMpegVersion - MPEG-1, MPEG-2 oder MPEG-2.5.

    BE_CONFIG.LHV1.dwPsyModel und BE_CONFIG.LHV1.dwEmphasis sind noch reserviert.

    BE_CONFIG.LHV1.bPrivate - Gibt an, ob es ein privater Stream ist.

    BE_CONFIG.LHV1.bCRC - Gibt an, ob die Frames mit einer Fehlerprüfsumme versehen werden soll.

    BE_CONFIG.LHV1.bCopyright - Gibt an, ob der Stream kopiergeschützt ist. Könnte man aus dem CONTROL des Tracks auslesen.

    BE_CONFIG.LHV1.bOriginal - Gibt an, ob der Titel das Original ist.

    BE_CONFIG.LHV1.WriteVBRHeader - Gibt an, ob ein XING Header geschrieben werden soll.

    BE_CONFIG.LHV1.bEnableVBR - gibt an, ob mit variabler Bitrate encodet werden soll.

    BE_CONFIG.LHV1.nVBRQuality - Gibt die Qualität beim Encoding mit variabler Bitrate an. Die Werte reichen von 0 (Höchste Qualität - Langsam) bis 9 (Niedrigste Qulität, Schnell).

    BE_CONFIG.LHV1.dwVbrAbr_bps - Gibt die durchschnittliche Bitrate an.

    BE_CONFIG.LHV1.nVbrMethod - Gibt die Encodingmethode beim Encoding mit variabler Bitrate an. Die Werte sind in TVBRMETHOD aufgezählt.

    BE_CONFIG.LHV1.bNoRes, BE_CONFIG.LHV1.bStrictIso, BE_CONFIG.LHV1.nQuality und BE_CONFIG.LHV1.btReserved sind noch reserviert.

    Das könnte dann so aussehen:

    var
      ...
      pbeConfig : TBE_CONFIG;
     
    begin
      ...
      FillChar(pbeConfig, SizeOf(TBE_CONFIG), 0);
      with pbeConfig.format
      do begin
        dwConfig             := BE_CONFIG_LAME;
        lhv1.dwStructVersion := CURRENT_STRUCT_VERSION;
        lhv1.dwStructSize    := CURRENT_STRUCT_SIZE);
        lhv1.dwSampleRate    := 44100;
        lhv1.dwReSampleRate  := 0;
        lhv1.nMode           := 1;
        lhv1.dwBitrate       := 32;
        lhv1.dwMaxBitrate    := 192;
        lhv1.nPreset         := Integer(LQP_FAST_STANDARD);
        lhv1.dwMpegVersion   := MPEG1;
        lhv1.dwPsyModel      := 0;
        lhv1.dwEmphasis      := 0;
        lhv1.bPrivate        := False;
        lhv1.bCRC            := False;
        lhv1.bCopyright      := False;
        lhv1.bOriginal       := False;
        lhv1.WriteVBRHeader  := False;
        lhv1.bEnableVBR      := True;
        lhv1.nVBRQuality     := 5;
        lhv1.dwVbrAbr_bps    := 0;
        lhv1.nVbrMethod      := TVBRMETHOD(fMP3Settings.VBRMethod - 1);
        lhv1.bNoRes          := False;
        lhv1.bStrictIso      := False;
        lhv1.nQuality        := 0;
      end;
      ...
    end;
    ...
    

    Buffer

    Die Sektoren der CD werden in einen Buffer eingelesen. Der Encoder liest die Chunks aus einen Buffer und gibt die encodeten Frames in einen anderen Buffer aus.

    Jeder Sektor einer CD enthält 98 Frames mit je sechs Samples für beide Kanäle. Das heißt 588 Samples für jeden Kanal bzw. insgesamt 1176 Samples.

    Mit dem Aufruf der Funktion beInitStream erhält man die Anzahl der Samples, welche der Funktion beEncodeChunk übergeben werden sollen. Dies sind 2304. Dabei wird jedes Sample ohne Unterscheidung nach Mono oder Stereo, gezählt.

    Für die Ausgabe der Daten von der CD und die Eingabe zum Encoder sollte ein gemeinsamer Buffer benutzt werden. Dieser sollte in seiner Größe so bemessen sein, dass nicht großartig mit den Daten jongliert werden muss. Dazu wird das kleinste gemeinsame Vielfache ermittelt:

    kgV(1176, 2304) = 112896 (Siehe mathepower.com)

    Dies sind 96 Sektoren, 49 Chunks oder 225792 B bzw. etwa 220,5 kB. In den ersten Demos waren 96 * 6 = 576 Sektoren eingestellt. Das funktionierte mit dem Laufwerk in PC sehr gut. Im aktuellen Laptop jedoch nicht. Da musste die Einstellung bis auf 48 heruntergenommen werden.

    Encoden

    Als Grundlage dient das Programm aus dem vorigen Kapitel. Dieses muss ein wenig geändert und erweitert werden. So werden in die grafische Oberfläche einige Einstellmöglichkeiten für das MP3 eingefügt:

    In die Funktion READTrack werden die Trackinformationen und Encodingeinstellungen mit je einem Record übergeben.

    type
      TMP3Settings = record
        EnabledVBR  : Boolean;
        MinimalBR   : Byte;
        MaximalBR   : Byte;
        ChannelMode : Byte;
        Quality     : Byte;
        VBRQuality  : Byte;
        VBRMethod   : Byte;
      end;
     
    type
      TRipTrack = record
        nNumber    : Cardinal;
        dwOffset   : Cardinal;
        dwLength   : Cardinal;
        stFilename : String;
      end;
      TRipTraclList = Array of TRipTrack;
    

    Hier sind diesmal auch schon ein paar Überprüfungen enthalten:

    function ReadTrackMP3(aTrack: TRipTrack; aDevice: THandle): Boolean;
    const
      cnBR  : Array[0..13] of Integer = (32, 64, 96, 128, 160, 192, 224, 256,
                                         288, 320, 352, 384, 416, 448);
      cnLQP : Array[0..25] of Integer = (-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
                                         10, 11, 12, 13, 14, 1000, 2000, 3000,
                                         4000, 5000, 6000, 7000, 8000, 9000, 10000);
      SamplePerSector = 2 {Channels} * 6 {Samples per Frame} * 98 {Frames per Sector};
      fdwSectors      = 96;
    var
      dwReturned   : Cardinal;
      dwSectors    : Cardinal;
      aFileStream  : TFileStream;
      pbeConfig    : TBE_CONFIG;
      beError      : Cardinal;
      dwSamples    : Cardinal;
      dwBufferSize : Cardinal;
      hbeStream    : HBE_STREAM;
      pBufferPCM   : PSmallInt;
      pBufferMP3   : PByte;
      toWrite      : Cardinal;
    begin
      {*  Konfiguration eingeben.  *}
      FillChar(pbeConfig, SizeOf(TBE_CONFIG), 0);
      pbeConfig.format.dwConfig             := BE_CONFIG_LAME;
      pbeConfig.format.lhv1.dwStructVersion := CURRENT_STRUCT_VERSION;
      pbeConfig.format.lhv1.dwStructSize    := CURRENT_STRUCT_SIZE;
      pbeConfig.format.lhv1.dwSampleRate    := 44100;
      pbeConfig.format.lhv1.dwReSampleRate  := 0;
      pbeConfig.format.lhv1.nMode           := fMP3Settings.ChannelMode;
      pbeConfig.format.lhv1.dwBitrate       := cnBR[fMP3Settings.MinimalBR];
      pbeConfig.format.lhv1.nPreset         := cnLQP[fMP3Settings.Qualität];
      pbeConfig.format.lhv1.dwMpegVersion   := MPEG1;
      pbeConfig.format.lhv1.dwPsyModel      := 0;
      pbeConfig.format.lhv1.dwEmphasis      := 0;
      pbeConfig.format.lhv1.bPrivate        := False;
      pbeConfig.format.lhv1.bCRC            := False;
      pbeConfig.format.lhv1.bCopyright      := False;
      pbeConfig.format.lhv1.bOriginal       := False;
      pbeConfig.format.lhv1.WriteVBRHeader  := False;
      pbeConfig.format.lhv1.bEnableVBR      := fMP3Settings.EnabledVBR;
      pbeConfig.format.lhv1.dwVbrAbr_bps    := 0;
      pbeConfig.format.lhv1.bNoRes          := False;
      pbeConfig.format.lhv1.bStrictIso      := False;
      pbeConfig.format.lhv1.nQuality        := 0;
      if fMP3Settings.EnabledVBR
      then begin
        pbeConfig.format.lhv1.WriteVBRHeader := True;
        pbeConfig.format.lhv1.dwMaxBitrate   := cnBR[fMP3Settings.MaximalBR];
        pbeConfig.format.lhv1.nVBRQuality    := fMP3Settings.VBRQuality;
        pbeConfig.format.lhv1.nVbrMethod     := TVBRMETHOD(fMP3Settings.VBRMethod - 1);
      end;
      {*  Stream initialisieren.  *}
      beError := beInitStream(pbeConfig, dwSamples, dwBufferSize, hbeStream);
      if beError = BE_ERR_SUCCESSFUL
      then begin
        {*  Speicher reservieren.  *}
        dwBufferSize := CB_AUDIOSECTOR * fdwSectors;
        GetMem(pBufferPCM, dwBufferSize);
        GetMem(pBufferMP3, dwBufferSize);
        try
          aFileStream := TFileStream.Create(aTrack.stFilename, fmCreate or
                                            fmOpenWrite or fmShareExclusive);
          {*  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,
                               pBufferPCM);
            if not(Result)
            then begin
              beCloseStream(hbeStream);
              raise Exception.Create('Beim Lesen von CD ist ein Fehler aufgetreten.');
            end;
            {*  Encoden.  *}
            beError := beEncodeChunk(hbeStream, dwSectors * SamplePerSector,
                                     pBufferPCM, pBufferMP3, ToWrite);
            if beError <> BE_ERR_SUCCESSFUL
            then begin
              beCloseStream(hbeStream);
              raise Exception.Create('Beim Encoden ist ein Fehler aufgetreten.');
            end;
            {*  Datei schreiben.  *}
            if aFileStream.Write(pBufferMP3^, ToWrite) <> ToWrite
            then begin
              beCloseStream(hbeStream);
              raise Exception.Create('Beim Schreiben ist ein Fehler aufgetreten.');
            end;
            {*  Zähler erhöhen.  *}
            inc(dwReturned, dwSectors);
            Application.ProcessMessages;
          end;
          {*  Deinitialisieren und Rest schreiben.  *}
          beError := beDeInitStream(hbeStream, pBufferMP3, ToWrite);
          if (beError <> BE_ERR_SUCCESSFUL)
          or (aFileStream.Write(pBufferMP3, ToWrite) <> ToWrite)
          then begin
            beCloseStream(hbeStream);
            raise Exception.Create('Beim Schreiben ist ein Fehler aufgetreten.');
          end;
          {*  Datei schließen.  *}
          aFileStream.Destroy;
        finally
          {*  Speicher freigeben.  *}
          FreeMem(pBufferPCM);
          FreeMem(pBufferMP3);
        end;
        {*  Stream schließen.  *}
        beCloseStream(hbeStream);
        {
        *  Den VBRHeader füllen.
        }
        if fMP3Settings.EnabledVBR
        then beWriteVBRHeader(PChar(fRipTrackList[i].stFileName));
      end
      else raise Exception.Create('Beim Initialisieren ist ein Fehler aufgetreten.');
    end;
    

    Die Notation ist nicht konsistent, aber dies ist ja nur eine Demo. Wichtiger ist, dass das Encoden das Auslesen des Laufwerkes ausbremsen kann. Deshalb kann man der Konstante fdwSectors auch einen Faktor verpassen. Die Faktoren 4 bis 10 scheinen bei mir ganz gut zu gehen. Vermutlich sollte man einen Thread zum Encoden verwenden.

    Die Lame-Einstellungen enthalten eine Einstellung zum Schreiben eines VBRHeaders. Ist dieser eingeschaltet, werden der MP3-Datei 417 Byte vorangestellt. Die ersten vier Byte sind ein Header, der Rest ist leer. Mit der Funktion beWriteVBRHeader (siehe am Ende des vorstehenden Quelltextes) wird der leere Bereich mit einen Xingheader und einem Lame-Versionstring gefüllt.

    Demos

    Die Demos lesen die Tracks, lassen sie vom Lame Encoder encoden und speichern sie als MP3 in einem beliebigen Ordner. Die Demos binden die lame_enc.dll dynamisch ein.

    Demo, welche die Trackdaten aus der TOC und dem CD Text der CD ermittelt.
    Encode MP3 CD Text (pcddaEncodeMP3CDText.7z - 459 kb) inklusive Lame_enc.DLL 3.100 MD5 (1 kb).
    Compiler: Delphi 7 Personal
    Stand: 27. August 2018

    Demo, welche die TOC liest und die Daten, wenn verfügbar, von FreeDB holt.
    Encode MP3 FreeDB (pcddaEncodeMP3FDB.7z - 459 kb) inklusive Lame_enc.DLL 3.100 MD5 (1 kb).
    Compiler: Delphi 7 Personal
    Stand: 27. August 2018

    Demo, welche die TOC liest und die Daten, wenn verfügbar, von Music Brainz holt. Gegebenenfalls mit Coverbild.
    Encode MP3 Music Brainz (pcddaEncodeMP3MB.7z - 498 kb) inklusive Lame_enc.DLL MD5 (1 kb).
    Compiler: Turbo Delphi
    Stand: 27. Juli 2017

    Änderungen an den Demos

    27.08.2018Änderung: Das Sperrenn und Entsperren des Schublade entfernt.
    Änderung: Beim VBR Encoding wird das Erstellen des VBR Header aktiviert.
    18.08.2018Änderung: Neue Demos mit der Abfrage des CDTextes, von FreeDB und Muic Brainz.
    13.07.2017Änderung: Reservierte Zeichen werden vor dem Speichern aus dem Dateinamen entfernt.
    29.07.2015Änderung: Lesen der CD überarbeitet, Anzahl der auf einmal zu lesenden Sektoren herabgesetzt.
    Änderung: Austausch der Downloadroutine.
    Änderung: Anzeige der DiscID und der ReleaseID.
    Hinzu: Fortschrittsanzeige.
    10.06.2012Fehler: Bei den Helferlein zur Berechnung der Titellänge fehlte eine Klammer.