I spent some time to have Delphi interface correctly with the Lame-encoder-DLL, so I thought it a good idea to share the result, since I also could not find any good Delphi-code for this on the net.
The Lame-source comes with a rudimentary Delphi-header-file, but this has several issues, which I have tried to fix:
Usage: EncodeWavToMP3(WaveFile, MP3File, Bitrate)
WaveFile needs to be 16-bit Stereo, but that could be adjusted.
Bitrate is a constant bitrate, for example 128. Support for VBR could be added.
If you use it and find something wrong, I'd like to know 🙂
总结一下,其实就是lame_enc.dall v3.99.3 for audacity专用版,直接下载地址为:Lame, lame_enc.dll and FFmpeg libraries for Audacity - Free and Safe downloads - LAME Websites eurorack blog and max4live blog 2023 - DO NOT CLICK GREEN DOWNLOAD BUTTONS
Here is the unit:
- unit MP3ExportLame;
-
- interface
-
- Uses System.SysUtils, WinApi.Windows, System.Classes;
-
- type
- // type definitions
-
- PHBE_STREAM = ^THBE_STREAM;
- THBE_STREAM = LongWord;
- BE_ERR = LongWord;
-
- const
- // encoding formats
-
- BE_CONFIG_MP3 = 0;
- BE_CONFIG_LAME = 256;
-
- // error codes
-
- BE_ERR_SUCCESSFUL: LongWord = 0;
- BE_ERR_INVALID_FORMAT: LongWord = 1;
- BE_ERR_INVALID_FORMAT_PARAMETERS: LongWord = 2;
- BE_ERR_NO_MORE_HANDLES: LongWord = 3;
- BE_ERR_INVALID_HANDLE: LongWord = 4;
-
- // format specific variables
-
- BE_MP3_MODE_STEREO = 0;
- BE_MP3_MODE_DUALCHANNEL = 2;
- BE_MP3_MODE_MONO = 3;
-
- // other constants
-
- BE_MAX_HOMEPAGE = 256;
-
- type
-
- TMP3 = packed record
- dwSampleRate: LongWord;
- byMode: Byte;
- wBitRate: Word;
- bPrivate: LongWord;
- bCRC: LongWord;
- bCopyright: LongWord;
- bOriginal: LongWord;
- end;
-
- TLHV1 = packed record
- // STRUCTURE INFORMATION
- dwStructVersion: DWORD;
- dwStructSize: DWORD;
-
- // BASIC ENCODER SETTINGS
- dwSampleRate: DWORD; // ALLOWED SAMPLERATE VALUES DEPENDS ON dwMPEGVersion
- dwReSampleRate: DWORD; // DOWNSAMPLERATE, 0=ENCODER DECIDES
- nMode: Integer;
- // BE_MP3_MODE_STEREO, BE_MP3_MODE_DUALCHANNEL, BE_MP3_MODE_MONO
- dwBitrate: DWORD; // CBR bitrate, VBR min bitrate
- dwMaxBitrate: DWORD; // CBR ignored, VBR Max bitrate
- nQuality: Integer; // Quality setting (NORMAL,HIGH,LOW,VOICE)
- dwMpegVersion: DWORD; // MPEG-1 OR MPEG-2
- dwPsyModel: DWORD; // FUTURE USE, SET TO 0
- dwEmphasis: DWORD; // 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
- bWriteVBRHeader: LONGBOOL; // WRITE XING VBR HEADER (TRUE/FALSE)
- bEnableVBR: LONGBOOL; // USE VBR ENCODING (TRUE/FALSE)
- nVBRQuality: Integer; // VBR QUALITY 0..9
-
- btReserved: array [0 .. 255] of Byte; // FUTURE USE, SET TO 0
- end;
-
- TAAC = packed record
- dwSampleRate: LongWord;
- byMode: Byte;
- wBitRate: Word;
- byEncodingMethod: Byte;
- end;
-
- TFormat = packed record
- case Byte of
- 1:
- (mp3: TMP3);
- 2:
- (lhv1: TLHV1);
- 3:
- (aac: TAAC);
- end;
-
- TBE_Config = packed record
- dwConfig: LongWord;
- format: TFormat;
- end;
-
- PBE_Config = ^TBE_Config;
-
- TBE_Version = record
- byDLLMajorVersion: Byte;
- byDLLMinorVersion: Byte;
-
- byMajorVersion: Byte;
- byMinorVersion: Byte;
-
- byDay: Byte;
- byMonth: Byte;
- wYear: Word;
-
- zHomePage: Array [0 .. BE_MAX_HOMEPAGE + 1] of Char;
- end;
-
- PBE_Version = ^TBE_Version;
-
-
- //Headers for Lame_enc.dll (ver. 3.100)
-
- Function beInitStream(var pbeConfig: TBE_Config; var dwSample: LongWord;
- var dwBufferSize: LongWord; var phbeStream: THBE_STREAM): BE_ERR; cdecl;
- external 'Lame_enc.dll';
- Function beEncodeChunk(hbeStream: THBE_STREAM; nSamples: LongWord; var pSample;
- var pOutput; var pdwOutput: LongWord): BE_ERR; cdecl; external 'Lame_enc.dll';
- Function beDeinitStream(hbeStream: THBE_STREAM; var pOutput;
- var pdwOutput: LongWord): BE_ERR; cdecl; external 'Lame_enc.dll';
- Function beCloseStream(hbeStream: THBE_STREAM): BE_ERR; cdecl;
- external 'Lame_enc.dll';
- Procedure beVersion(var pbeVersion: TBE_Version); cdecl;
- external 'Lame_enc.dll';
- // Added header for beWriteVBRHeader
- Procedure beWriteVBRHeader(MP3FileName: pAnsiChar); cdecl;
- external 'Lame_enc.dll';
-
- Procedure EncodeWavToMP3(WaveFile, MP3File: string; BitRate: Integer);
- // BitRate 128 192 256 etc.
-
- implementation
-
- uses WinApi.MMSystem;
-
- { ---------------------------------------- }
-
- { The following functions retrieve the necessary info from the input-wave-file. }
- { Source: }
- { WaveUtils - Utility functions and data types }
- { by Kambiz R. Khojasteh }
- { }
- { kambiz@delphiarea.com }
- { http://www.delphiarea.com }
-
- function mmioStreamProc(lpmmIOInfo: PMMIOInfo; uMsg, lParam1, lParam2: DWORD)
- : LRESULT; stdcall;
- var
- Stream: TStream;
- begin
- if Assigned(lpmmIOInfo) and (lpmmIOInfo^.adwInfo[0] <> 0) then
- begin
- Stream := TStream(lpmmIOInfo^.adwInfo[0]);
- case uMsg of
- MMIOM_OPEN:
- begin
- if TObject(lpmmIOInfo^.adwInfo[0]) is TStream then
- begin
- Stream.Seek(0, SEEK_SET);
- lpmmIOInfo^.lDiskOffset := 0;
- Result := MMSYSERR_NOERROR;
- end
- else
- Result := -1;
- end;
- MMIOM_CLOSE:
- Result := MMSYSERR_NOERROR;
- MMIOM_SEEK:
- try
- if lParam2 = SEEK_CUR then
- Stream.Seek(lpmmIOInfo^.lDiskOffset, SEEK_SET);
- Result := Stream.Seek(lParam1, lParam2);
- lpmmIOInfo^.lDiskOffset := Result;
- except
- Result := -1;
- end;
- MMIOM_READ:
- try
- Stream.Seek(lpmmIOInfo^.lDiskOffset, SEEK_SET);
- Result := Stream.Read(Pointer(lParam1)^, lParam2);
- lpmmIOInfo^.lDiskOffset := Stream.Seek(0, SEEK_CUR);
- except
- Result := -1;
- end;
- MMIOM_WRITE, MMIOM_WRITEFLUSH:
- try
- Stream.Seek(lpmmIOInfo^.lDiskOffset, SEEK_SET);
- Result := Stream.Write(Pointer(lParam1)^, lParam2);
- lpmmIOInfo^.lDiskOffset := Stream.Seek(0, SEEK_CUR);
- except
- Result := -1;
- end
- else
- Result := MMSYSERR_NOERROR;
- end;
- end
- else
- Result := -1;
- end;
-
- function OpenStreamWaveAudio(Stream: TStream): HMMIO;
- var
- mmIOInfo: TMMIOINFO;
- begin
- FillChar(mmIOInfo, SizeOf(mmIOInfo), 0);
- mmIOInfo.pIOProc := @mmioStreamProc;
- mmIOInfo.adwInfo[0] := DWORD(Stream);
- Result := mmioOpen(nil, @mmIOInfo, MMIO_READWRITE);
- end;
-
- function GetWaveAudioInfo(mmIO: HMMIO; var pWaveFormat: PWaveFormatEx;
- var DataSize, DataOffset: DWORD): Boolean;
-
- function GetWaveFormat(const ckRIFF: TMMCKInfo): Boolean;
- var
- ckFormat: TMMCKInfo;
- begin
- Result := False;
- ckFormat.ckid := mmioStringToFOURCC('fmt', 0);
- if (mmioDescend(mmIO, @ckFormat, @ckRIFF, MMIO_FINDCHUNK)
- = MMSYSERR_NOERROR) and (ckFormat.cksize >= SizeOf(TWaveFormat)) then
- begin
- if ckFormat.cksize < SizeOf(TWaveFormatEx) then
- begin
- GetMem(pWaveFormat, SizeOf(TWaveFormatEx));
- FillChar(pWaveFormat^, SizeOf(TWaveFormatEx), 0);
- end
- else
- GetMem(pWaveFormat, ckFormat.cksize);
- Result := (mmioRead(mmIO, pAnsiChar(pWaveFormat), ckFormat.cksize)
- = Integer(ckFormat.cksize));
- end;
- end;
-
- function GetWaveData(const ckRIFF: TMMCKInfo): Boolean;
- var
- ckData: TMMCKInfo;
- begin
- Result := False;
- ckData.ckid := mmioStringToFOURCC('data', 0);
- if (mmioDescend(mmIO, @ckData, @ckRIFF, MMIO_FINDCHUNK) = MMSYSERR_NOERROR)
- then
- begin
- DataSize := ckData.cksize;
- DataOffset := ckData.dwDataOffset;
- Result := True;
- end;
- end;
-
- var
- ckRIFF: TMMCKInfo;
- OrgPos: Integer;
- begin
- Result := False;
- OrgPos := mmioSeek(mmIO, 0, SEEK_CUR);
- try
- mmioSeek(mmIO, 0, SEEK_SET);
- ckRIFF.fccType := mmioStringToFOURCC('WAVE', 0);
- if (mmioDescend(mmIO, @ckRIFF, nil, MMIO_FINDRIFF) = MMSYSERR_NOERROR) then
- begin
- pWaveFormat := nil;
- if GetWaveFormat(ckRIFF) and GetWaveData(ckRIFF) then
- Result := True
- else if Assigned(pWaveFormat) then
- ReallocMem(pWaveFormat, 0);
- end
- finally
- mmioSeek(mmIO, OrgPos, SEEK_SET);
- end;
- end;
-
- function GetStreamWaveAudioInfo(Stream: TStream; var pWaveFormat: PWaveFormatEx;
- var DataSize, DataOffset: DWORD): Boolean;
- var
- mmIO: HMMIO;
- begin
- Result := False;
- if Stream.Size <> 0 then
- begin
- mmIO := OpenStreamWaveAudio(Stream);
- if mmIO <> 0 then
- try
- Result := GetWaveAudioInfo(mmIO, pWaveFormat, DataSize, DataOffset);
- finally
- mmioClose(mmIO, MMIO_FHOPEN);
- end;
- end;
- end;
-
- Procedure EncodeWavToMP3(WaveFile, MP3File: string; BitRate: Integer);
- var
- beConfig: TBE_Config;
- dwSamples, dwSamplesMP3: LongWord;
- hbeStream: THBE_STREAM;
- error: BE_ERR;
- pBuffer: PSmallInt;
- pMP3Buffer: PByte;
-
- done: LongWord;
- dwWrite: LongWord;
- ToRead: LongWord;
- ToWrite: LongWord;
-
- // changed from THandle to TFileStream
- fs, ft: TFileStream;
- TotalSize: DWORD;
-
- // variables to hold the wave info necessary for encoding
- pWaveFormat: PWaveFormatEx;
- DataOffset, DataSize, InputSampleRate: DWORD;
-
- begin
- beConfig.dwConfig := BE_CONFIG_LAME;
- fs := TFileStream.Create(WaveFile, fmOpenRead or fmShareDenyWrite);
- ft := TFileStream.Create(MP3File, fmCreate or fmShareDenyWrite);
- try
- TotalSize := fs.Size;
-
- // obtain info from source wave file
- try
- if not GetStreamWaveAudioInfo(fs, pWaveFormat, DataSize, DataOffset) then
- raise Exception.Create
- ('Unable to obtain necessary info from wave file.');
- if (pWaveFormat.nChannels <> 2) or (pWaveFormat.wBitsPerSample <> 16) then
- raise Exception.Create('Wave format must be 16bit Stereo.');
- InputSampleRate := pWaveFormat.nSamplesPerSec;
- finally
- FreeMem(pWaveFormat);
- end;
-
- // Structure information
- beConfig.format.lhv1.dwStructVersion := 1;
- beConfig.format.lhv1.dwStructSize := SizeOf(beConfig);
- // Basic encoder setting
- beConfig.format.lhv1.dwSampleRate := InputSampleRate;
- beConfig.format.lhv1.dwReSampleRate := InputSampleRate;
- beConfig.format.lhv1.nMode := BE_MP3_MODE_STEREO;
- beConfig.format.lhv1.dwBitrate := BitRate;
- beConfig.format.lhv1.dwMaxBitrate := BitRate;
- beConfig.format.lhv1.nQuality := 4;
- beConfig.format.lhv1.dwMpegVersion := 1;
- // MPEG1
- beConfig.format.lhv1.dwPsyModel := 0;
- beConfig.format.lhv1.dwEmphasis := 0;
- // Bit Stream Settings
- beConfig.format.lhv1.bPrivate := False;
- beConfig.format.lhv1.bCRC := True;
- beConfig.format.lhv1.bCopyright := True;
- beConfig.format.lhv1.bOriginal := True;
- // VBR Stuff
- // Have it write a VBRHeader, as recommended by Lame, even though it's CBR
- beConfig.format.lhv1.bWriteVBRHeader := True;
- beConfig.format.lhv1.bEnableVBR := False;
- beConfig.format.lhv1.nVBRQuality := 0;
- error := beInitStream(beConfig, dwSamples, dwSamplesMP3, hbeStream);
- if error = BE_ERR_SUCCESSFUL then
- begin
- pBuffer := AllocMem(dwSamples * 2);
- pMP3Buffer := AllocMem(dwSamplesMP3);
- try
- // Position the source file stream at the beginning of the PCM-data:
- done := DataOffset;
- fs.Seek(DataOffset, soFromBeginning);
- While (done < TotalSize) do
- begin
- if (done + dwSamples * 2 < TotalSize) then
- ToRead := dwSamples * 2
- else
- begin
- ToRead := TotalSize - done;
- FillChar(pBuffer^, dwSamples * 2, 0);
- end;
- fs.Read(pBuffer^, ToRead);
- error := beEncodeChunk(hbeStream, ToRead div 2, pBuffer^,
- pMP3Buffer^, ToWrite);
- if error <> BE_ERR_SUCCESSFUL then
- begin
- beCloseStream(hbeStream);
- raise Exception.Create('Encoding Error');
- end;
- ft.Write(pMP3Buffer^, ToWrite);
- done := done + ToRead;
- end;
- error := beDeinitStream(hbeStream, pMP3Buffer^, dwWrite);
- if error <> BE_ERR_SUCCESSFUL then
- begin
- beCloseStream(hbeStream);
- raise Exception.Create('Close Error');
- end;
- if dwWrite <> 0 then
- begin
- ft.Write(pMP3Buffer^, dwWrite);
- end;
- error := beCloseStream(hbeStream);
- if error <> BE_ERR_SUCCESSFUL then
- begin
- raise Exception.Create('Close Error');
- end;
- finally
- FreeMem(pBuffer);
- FreeMem(pMP3Buffer);
- end;
- end
- else
- begin
- Raise Exception.Create('InitStream failure');
- end;
- finally
- fs.free;
- ft.free;
- end;
- beWriteVBRHeader(pAnsiChar(AnsiString(MP3File)));
- end;
- end.