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:
- The file references unnecessary stuff, preventing compilation, easy to fix.
- The encoding starts at the beginning of the wave-file, thereby encoding the header. This gives a noise at the beginning and can switch the stereo-channels. Fix: Offset the source into the data-section of the wave-file. Since this offset can vary, I have used the utility functions by Kambiz R. Khojasteh (http://www.delphiarea.com) to retrieve the necessary info using WinApi.MMSystem.
- Lame suggests writing a VBR-Header to the file, even though it's CBR, I have changed the routine accordingly. This way devices can e.g. figure out the duration of the mp3-audio more easily.
- Instead of file-handles I'm using TFileStream, that seems to speed up encoding considerably.
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.