(* this file is a part of audio components suite v 2.3. copyright (c) 2002-2005 andrei borovsky. all rights reserved. see the license file for more details. you can contact me at mail@z0m3ie.de *) { $Log: acs_dxaudio.pas,v $ Revision 1.7 2006/08/31 20:10:56 z0m3ie *** empty log message *** Revision 1.6 2006/07/09 16:40:35 z0m3ie *** empty log message *** Revision 1.5 2006/07/04 17:12:45 z0m3ie ACS 2.4 alt wiederhergestellt (unterschiedliche Sampleformate ...) Revision 1.3 2006/01/01 18:46:40 z0m3ie *** empty log message *** Revision 1.2 2005/12/26 17:31:39 z0m3ie fixed some problems in acs_dsfiles fixed some problems in acs_vorbis reworked all buffers Revision 1.1 2005/12/19 18:36:05 z0m3ie *** empty log message *** Revision 1.9 2005/12/18 17:01:54 z0m3ie delphi compatibility Revision 1.8 2005/12/04 16:54:34 z0m3ie All classes are renamed, Style TACS... than T... to avoid conflicts with other components (eg TMixer is TACSMixer now) Revision 1.7 2005/10/02 16:51:31 z0m3ie *** empty log message *** Revision 1.6 2005/09/23 14:04:58 z0m3ie *** empty log message *** Revision 1.5 2005/09/18 19:28:59 z0m3ie more progress on driver handling Revision 1.4 2005/09/16 17:34:29 z0m3ie *** empty log message *** Revision 1.3 2005/09/15 20:59:38 z0m3ie start translate the documentation in the source for pasdoc Revision 1.2 2005/09/14 21:19:37 z0m3ie *** empty log message *** Revision 1.1 2005/09/13 21:53:45 z0m3ie maked seperat driver (not complete jet) Revision 1.1 2005/09/12 22:04:52 z0m3ie modified structure again, fileformats are now in an sperat folder. all File In/Out classes are capsulated from TFileIn and TFileOut Revision 1.1 2005/08/25 20:18:00 z0m3ie Version 2.4 restructure TCDPlayer removed (fits not in component structure) TMP3ToWavConverter removed (fits not in component structure) Revision 1.3 2005/08/22 20:17:02 z0m3ie changed Headers to log changed mail adress } {$ifdef linux}{$message error 'unit not supported'}{$endif linux} unit acs_dxaudio; interface uses ACS_Audio,SysUtils, Classes, Forms, ACS_Types, ACS_Classes, Windows,ACS_Strings; const LATENCY = 25; DS_POLLING_INTERVAL = 400; //milliseconds type TDSoundWrapper = record dsw_pDirectSound : Pointer; dsw_OutputBuffer : Pointer; dsw_WriteOffset : LongWord; dsw_OutputSize : Integer; dsw_BytesPerFrame : Integer; dsw_CounterTicksPerBuffer : Int64; dsw_LastPlayTime : Int64; dsw_LastPlayCursor : Int64; dsw_OutputUnderflows : Int64; dsw_OutputRunning : LongBool; dsw_FramesWritten : Double; dsw_FramesPlayed : Double; dsw_pDirectSoundCapture : Pointer; dsw_InputBuffer : Pointer; dsw_ReadOffset : LongWord; dsw_InputSize : LongWord; end; PDSoundWrapper = ^TDSoundWrapper; TDSW_DeviceInfo = record guid : TGUID; name : array[0..127] of char; end; TDSW_Devices = record devcount : Integer; dinfo : array [0..15] of TDSW_DeviceInfo; end; PDSW_Devices = ^TDSW_Devices; { TDXAudioOut } TDXAudioOut = class(TACSBaseAudioOut) private DSW : TDSoundWrapper; Devices : TDSW_Devices; Chan, SR, BPS : Integer; EndOfInput, StartInput : Boolean; FDeviceNumber : Integer; FDeviceCount : Integer; procedure SetDevice(Ch : Integer);override; function GetDeviceInfo : TACSDeviceInfo;override; function GetDeviceCount : Integer;override; protected procedure Done; override; function DoOutput(Abort : Boolean):Boolean; override; procedure Prepare; override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Pause;override; procedure Resume;override; end; TDXAudioIn = class(TACSBaseAudioIn) private DSW : TDSoundWrapper; Devices : TDSW_Devices; FDeviceNumber : Integer; FDeviceCount : Integer; FBPS, FChan, FFreq : Integer; FOpened : Integer; FBytesToRead : Integer; FRecTime : Integer; procedure SetDevice(i : Integer);override; function GetDeviceName(Number : Integer) : String; procedure OpenAudio; procedure CloseAudio; function GetBPS : Integer; override; function GetCh : Integer; override; function GetSR : Integer; override; function GetTotalTime : real; override; procedure SetRecTime(aRecTime : Integer); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; function GetData(Buffer : Pointer; BufferSize : Integer): Integer; override; procedure Init; override; procedure Flush; override; property DeviceCount : Integer read FDeviceCount; property DeviceName[Number : Integer] : String read GetDeviceName; end; implementation type DSW_Init_t = function(dsw: PDSoundWrapper) : HRESULT; cdecl; DSW_Term_t = procedure(dsw: PDSoundWrapper); cdecl; DSW_InitOutputDevice_t = function (dsw: PDSoundWrapper; const guid : TGUID) : HRESULT; cdecl; DSW_InitOutputBuffer_t = function (dsw: PDSoundWrapper; Wnd : HWND; bps, nFrameRate : LongWord; nChannels, bufSize : Integer): HRESULT; cdecl; DSW_StartOutput_t = function (dsw: PDSoundWrapper) : HRESULT; cdecl; DSW_StopOutput_t = function(dsw: PDSoundWrapper) : HRESULT; cdecl; DSW_RestartOutput_t = function(dsw: PDSoundWrapper) : HRESULT; cdecl; DSW_GetOutputStatus_t = function(dsw: PDSoundWrapper) : DWORD; cdecl; DSW_WriteBlock_t = function(dsw: PDSoundWrapper; buf : PByte; numBytes : Integer) : HRESULT; cdecl; DSW_ZeroEmptySpace_t = function(dsw: PDSoundWrapper) : HRESULT; cdecl; DSW_QueryOutputSpace_t = function(dsw: PDSoundWrapper; var bytesEmpty ) : HRESULT; cdecl; DSW_Enumerate_t = function(var devices : TDSW_Devices) : HRESULT; cdecl; DSW_InitInputBuffer_t = function(dsw: PDSoundWrapper; bps, nFrameRate, nChannels, bufSize : Integer) : HRESULT; cdecl; DSW_InitInputDevice_t = function(dsw: PDSoundWrapper; const GUID : TGUID) : HRESULT; cdecl; DSW_StartInput_t = function(dsw: PDSoundWrapper) : HRESULT; cdecl; DSW_StopInput_t = function(dsw: PDSoundWrapper) : HRESULT; cdecl; DSW_ReadBlock_t = function(dsw: PDSoundWrapper; buf : PByte; numBytes : Integer) : HRESULT; cdecl; DSW_QueryInputFilled_t = function(dsw: PDSoundWrapper; var bytesFilled : Integer) : HRESULT; cdecl; var LibdswLoaded : Boolean = False; DSW_Init : DSW_Init_t; DSW_Term : DSW_Term_t; DSW_InitOutputDevice : DSW_InitOutputDevice_t; DSW_InitOutputBuffer : DSW_InitOutputBuffer_t; DSW_StartOutput : DSW_StartOutput_t; DSW_StopOutput : DSW_StopOutput_t; DSW_RestartOutput : DSW_RestartOutput_t; DSW_GetOutputStatus : DSW_GetOutputStatus_t; DSW_WriteBlock : DSW_WriteBlock_t; DSW_ZeroEmptySpace : DSW_ZeroEmptySpace_t; DSW_QueryOutputSpace : DSW_QueryOutputSpace_t; DSW_Enumerate : DSW_Enumerate_t; DSW_InitInputBuffer : DSW_InitInputBuffer_t; DSW_InitInputDevice : DSW_InitInputDevice_t; DSW_StartInput : DSW_StartInput_t; DSW_StopInput : DSW_StopInput_t; DSW_ReadBlock : DSW_ReadBlock_t; DSW_QueryInputFilled : DSW_QueryInputFilled_t; Libhandle : HMODULE; procedure TDXAudioOut.Prepare; var Res : HResult; Wnd : HWND; Form : TForm; begin if (FDeviceNumber >= FDeviceCount) then raise EACSException.Create(Format(strChannelnotavailable,[FDeviceNumber])); FInput.Init; FBuffer := AllocMem(FBufferSize); Chan := FInput.Channels; SR := FInput.SampleRate; BPS := FInput.BitsPerSample; DSW_Init(@DSW); Res := DSW_InitOutputDevice(@DSW, Devices.dinfo[FDeviceNumber].guid); if Res <> 0 then raise EACSException.Create(strFailedtoCreateDSdev); { if Owner is TForm then begin Form := Owner as TForm; Wnd := Form.Handle; end else } Wnd := 0; Res := DSW_InitOutputBuffer(@DSW, Wnd, BPS, SR, Chan, FBufferSize); if Res <> 0 then raise EACSException.Create(strFailedtoCreateDSbuf); StartInput := True; EndOfInput := False; end; procedure TDXAudioOut.Done; begin Finput.Flush; DSW_Term(@DSW); FreeMem(FBuffer); end; function TDXAudioOut.DoOutput(Abort : Boolean):Boolean; var Len, offs, lb : Integer; Stat : LongWord; Res : HRESULT; PlayTime, CTime : LongWord; begin Result := True; if not Busy then Exit; if not CanOutput then begin Result := False; Exit; end; if Abort then begin DSW_StopOutput(@DSW); CanOutput := False; Result := False; Exit; end; if StartInput then begin Len := 0; while Len < FBufferSize do begin offs := FInput.GetData(@FBuffer^[Len], FBufferSize-Len); if offs = 0 then begin EndOfInput := True; Break; end; Inc(Len, offs); end; DSW_WriteBlock(@DSW, @FBuffer^, Len); DSW_StartOutput(@DSW); StartInput := False; end; if EndOfInput then begin CanOutput := False; PlayTime := Round(FBufferSize/(Chan*(BPS div 8)*SR))*1000; CTime := 0; while CTime < PlayTime do begin Sleep(100); DSW_ZeroEmptySpace(@DSW); Inc(CTime, 100); end; DSW_StopOutput(@DSW); Result := False; Exit; end; Sleep(DS_POLLING_INTERVAL); DSW_QueryOutputSpace(@DSW, lb); lb := lb - (lb mod 1024); Len := 0; while Len < lb do begin if FInput.Busy then begin try offs := Finput.GetData(@FBuffer^[Len], lb-Len); except DSW_StopOutput(@DSW); CanOutput := False; Result := False; Exit; end; end; if offs = 0 then Break; Inc(Len, offs); end; DSW_WriteBlock(@DSW, @Fbuffer^, Len); if offs = 0 then begin DSW_ZeroEmptySpace(@DSW); EndOfInput := True; end; end; constructor TDXAudioOut.Create; begin inherited Create(AOwner); FBufferSize := $40000; if not (csDesigning in ComponentState) then begin if not LibdswLoaded then raise EACSException.Create(Format(strCoudntloadlib,['dswrapper.dll'])); end; if LibdswLoaded then DSW_Enumerate(Devices); FDeviceCount := Devices.devcount; end; destructor TDXAudioOut.Destroy; begin if LibdswLoaded then DSW_Term(@DSW); end; procedure TDXAudioOut.Pause; begin if EndOfInput then Exit; DSW_StopOutput(@DSW); end; procedure TDXAudioOut.Resume; begin if EndOfInput then Exit; DSW_RestartOutput(@DSW); end; procedure TDXAudioOut.SetDevice(Ch: Integer); begin FBaseChannel := Ch; end; function TDXAudioOut.GetDeviceInfo: TACSDeviceInfo; begin if (FBaseChannel >= FDeviceCount) then exit; Result.DeviceName := PChar(@(Devices.dinfo[FBaseChannel].Name[0])); end; function TDXAudioOut.GetDeviceCount: Integer; begin Result := FDeviceCount; end; constructor TDXAudioIn.Create; begin inherited Create(AOwner); FBPS := 8; FChan := 1; FFreq := 8000; FSize := -1; BufferSize := $2000; if not (csDesigning in ComponentState) then begin if not LibdswLoaded then raise EACSException.Create(Format(strCoudntloadlib,['dswrapper.dll'])); end; if LibdswLoaded then DSW_Enumerate(Devices); FDeviceCount := Devices.devcount; end; destructor TDXAudioIn.Destroy; begin if LibdswLoaded then DSW_Term(@DSW); inherited Destroy; end; procedure TDXAudioIn.OpenAudio; var Res : HResult; BufSize : Integer; begin BufSize := BufferSize; if FOpened = 0 then begin DSW_Init(@DSW); if not Assigned(DSW_InitInputDevice) then raise EACSException.Create(Format(strChannelNotAvailable,[FDeviceNumber])); Res := DSW_InitInputDevice(@DSW, Devices.dinfo[FDeviceNumber].guid); if Res <> 0 then raise EACSException.Create(strFailedtoCreateDSdev); Res := DSW_InitInputBuffer(@DSW, FBPS, FFreq, FChan, BufSize); if Res <> 0 then raise EACSException.Create(strFailedtoCreateDSbuf); end; Inc(FOpened); end; procedure TDXAudioIn.CloseAudio; begin if FOpened = 1 then DSW_Term(@DSW); if FOpened > 0 then Dec(FOpened); end; function TDXAudioIn.GetBPS : Integer; begin Result := FBPS; end; function TDXAudioIn.GetCh : Integer; begin Result := FChan; end; function TDXAudioIn.GetSR : Integer; begin Result := FFreq; end; procedure TDXAudioIn.Init; begin if Busy then raise EACSException.Create(strBusy); if (FDeviceNumber >= FDeviceCount) then raise EACSException.Create(Format(strChannelnotavailable,[FDeviceNumber])); if FRecTime > 0 then FBytesToRead := FRecTime*FFreq*FChan*(FBPS div 8); BufEnd := 0; BufStart := 1; FPosition := 0; FBusy := True; FSize := FBytesToRead; OpenAudio; DSW_StartInput(@DSW); end; procedure TDXAudioIn.Flush; begin DSW_StopInput(@DSW); CloseAudio; FBusy := False; end; function TDXAudioIn.GetData(Buffer : Pointer; BufferSize : Integer): Integer; var l : Integer; begin if not Busy then raise EACSException.Create(strStreamnotopen); if (FBytesToRead >=0) and (FPosition >= FBytesToRead) then begin Result := 0; Exit; end; if BufStart >= BufEnd then begin BufStart := 0; Sleep(DS_POLLING_INTERVAL); DSW_QueryInputFilled(@DSW, l); if l > BufferSize then l := BufferSize; (* We have lost some data. Generally this shouldn't happen. *) l := l - (l mod 1024); DSW_ReadBlock(@DSW, @FBuffer, l); BufEnd := l; end; if BufferSize < (BufEnd - BufStart) then Result := BufferSize else Result := BufEnd - BufStart; Move(FBuffer[BufStart], Buffer^, Result); Inc(BufStart, Result); Inc(FPosition, Result); end; procedure TDXAudioIn.SetRecTime; begin FRecTime := aRecTime; if FRecTime > 0 then FBytesToRead := FRecTime*FFreq*FChan*(FBPS div 8) else FBytesToRead := -1; end; procedure TDXAudioIn.SetDevice(i : Integer); begin FDeviceNumber := i end; function TDXAudioIn.GetDeviceName(Number : Integer) : String; begin if (Number < FDeviceCount) then Result := PChar(@(Devices.dinfo[Number].Name[0])) else Result := ''; end; function TDXAudioIn.GetTotalTime : real; var BytesPerSec : Integer; begin BytesPerSec := FFreq*FChan*(FBPS div 8); Result := FBytesToRead/BytesPerSec; end; initialization Libhandle := LoadLibraryEx('dswrapper.dll', 0, 0); if Libhandle <> 0 then begin LibdswLoaded := True; DSW_Init := GetProcAddress(Libhandle, 'DSW_Init'); DSW_Term := GetProcAddress(Libhandle, 'DSW_Term'); DSW_InitOutputDevice := GetProcAddress(Libhandle, 'DSW_InitOutputDevice'); DSW_InitOutputBuffer := GetProcAddress(Libhandle, 'DSW_InitOutputBuffer'); DSW_StartOutput := GetProcAddress(Libhandle, 'DSW_StartOutput'); DSW_StopOutput := GetProcAddress(Libhandle, 'DSW_StopOutput'); DSW_RestartOutput := GetProcAddress(Libhandle, 'DSW_RestartOutput'); DSW_GetOutputStatus := GetProcAddress(Libhandle, 'DSW_GetOutputStatus'); DSW_WriteBlock := GetProcAddress(Libhandle, 'DSW_WriteBlock'); DSW_ZeroEmptySpace := GetProcAddress(Libhandle, 'DSW_ZeroEmptySpace'); DSW_QueryOutputSpace := GetProcAddress(Libhandle, 'DSW_QueryOutputSpace'); DSW_Enumerate := GetProcAddress(Libhandle, 'DSW_Enumerate'); DSW_InitInputDevice := GetProcAddress(Libhandle, 'DSW_InitInputDevice'); DSW_InitInputBuffer := GetProcAddress(Libhandle, 'DSW_InitInputBuffer'); DSW_StartInput := GetProcAddress(Libhandle, 'DSW_StartInput'); DSW_StopInput := GetProcAddress(Libhandle, 'DSW_StopInput'); DSW_ReadBlock := GetProcAddress(Libhandle, 'DSW_ReadBlock'); DSW_QueryInputFilled := GetProcAddress(Libhandle, 'DSW_QueryInputFilled'); end; RegisterAudioOut('DirectSound',TDXAudioOut,LATENCY); RegisterAudioIn('DirectSound',TDXAudioIn,LATENCY); finalization if Libhandle <> 0 then FreeLibrary(Libhandle); end.