Renate Schaaf 64 Posted May 19, 2022 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 🙂 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. 1 1 Share this post Link to post
David Heffernan 2354 Posted May 20, 2022 Did you submit a PR for these changes? Share this post Link to post
David Heffernan 2354 Posted May 20, 2022 Did you submit a PR for these changes? Share this post Link to post
Renate Schaaf 64 Posted May 20, 2022 29 minutes ago, David Heffernan said: Did you submit a PR for these changes? No. I'd like to find out if this really works in all cases, first. Then I'll have to teach myself how to do PRs :). Share this post Link to post
Fr0sT.Brutal 900 Posted May 20, 2022 Weird to see such issues. Usually DLL interface just translates all function declarations to some language. Do you mean these issues exist inside the DLL itself? Share this post Link to post
Renate Schaaf 64 Posted May 20, 2022 (edited) 1 hour ago, Fr0sT.Brutal said: Do you mean these issues exist inside the DLL itself? No. The functions have just not been used not quite correctly in the example procedure that encodes a file. I should have made that more clear in my post. Edited May 20, 2022 by Renate Schaaf Share this post Link to post