(* 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_alsaaudio.pas,v $ Revision 1.6 2006/08/30 18:59:51 z0m3ie *** empty log message *** Revision 1.5 2006/07/04 17:12:45 z0m3ie ACS 2.4 alt wiederhergestellt (unterschiedliche Sampleformate ...) Revision 1.2 2006/01/01 18:46:40 z0m3ie *** empty log message *** Revision 1.1 2005/12/19 18:36:05 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.3 2005/08/28 20:31:17 z0m3ie linux restructuring for 2.4 Revision 1.2 2005/08/26 17:03:20 z0m3ie begon to make acs resourcestring aware more advanced tmixer for windows restructured tmixer its better handleable now 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.2 2005/08/22 20:17:01 z0m3ie changed Headers to log changed mail adress } {$ifdef mswindows}{$message error 'unit not supported'}{$endif} unit acs_alsaaudio; interface uses Classes, SysUtils, ACS_Types, ACS_Classes, baseunix, alsa, ACS_Strings,ACS_Audio; const BUF_SIZE = $4000; ALSAStateIdle = $ffffffff; // additional DriverState value; LATENCY = 60; type EALSABufferUnderrun = class(EACSException); EALSABufferOverrun = class(EACSException); { TALSAAudioIn } TALSAAudioIn = class(TACSBaseAudioIn) private FDevice : String; FPeriodSize, FPeriodNum : Integer; _audio_handle : Psnd_pcm_t; _hw_params : Psnd_pcm_hw_params_t; Busy : Boolean; FBPS, FChan, FFreq : Integer; BufStart, BufEnd : Integer; FOpened : Integer; FRecTime : Integer; FRecBytes : Integer; FLatency : Double; FSilentOnOverrun : Boolean; function GetDriverState: Integer; procedure OpenAudio; procedure CloseAudio; // function GetDriverState : Integer; protected function GetBPS : Integer; override; function GetCh : Integer; override; function GetSR : Integer; override; procedure SetDevice(Ch : Integer);override; function GetDeviceInfo : TACSDeviceInfo;override; function GetTotalTime : real; override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; function GetData(Buffer : Pointer; BufferSize : Integer): Integer; override; procedure Init; override; procedure Flush; override; property DriverState : Integer read GetDriverState; published property PeriodSize : Integer read FPeriodSize write FPeriodSize; property PeriodNum : Integer read FPeriodNum write FPeriodNum; property SilentOnOverrun : Boolean read FSilentOnOverrun write FSilentOnOverrun; end; { TALSAAudioOut } TALSAAudioOut = class(TACSBaseAudioOut) private FDevice : String; FPeriodSize, FPeriodNum : Integer; _audio_handle : Psnd_pcm_t; _hw_params : Psnd_pcm_hw_params_t; FBufferSize : Integer; FVolume : Byte; _audio_fd : Integer; Buffer : array [0..BUF_SIZE-1] of Byte; FLatency : Double; FSilentOnUnderrun : Boolean; function GetDriverState : Integer; protected procedure Done; override; function DoOutput(Abort : Boolean):Boolean; override; procedure Prepare; override; procedure SetDevice(Ch : Integer);override; function GetDeviceInfo : TACSDeviceInfo;override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; property DriverState : Integer read GetDriverState; property Latency : Double read FLatency; published property BufferSize : Integer read FBufferSize write FBufferSize; property Device : String read FDevice write FDevice stored True; property PeriodSize : Integer read FPeriodSize write FPeriodSize; property PeriodNum : Integer read FPeriodNum write FPeriodNum; property SilentOnUnderrun : Boolean read FSilentOnUnderrun write FSilentOnUnderrun; property Volume : Byte read FVolume write FVolume stored True; end; implementation constructor TALSAAudioIn.Create; begin inherited Create(AOwner); if not (csDesigning in ComponentState) then if not AsoundlibLoaded then raise EACSException.Create(Format(strcoudntloadLib,[asoundlib_path])); FBPS := 8; FChan := 1; FFreq := 8000; FSize := -1; FRecTime := 600; FDevice := 'default'; BufferSize := 32768; FSilentOnOverrun := True; end; destructor TALSAAudioIn.Destroy; begin inherited Destroy; CloseAudio; end; procedure TALSAAudioIn.OpenAudio; var Res : Integer; begin if FOpened = 0 then begin Res := snd_pcm_open(_audio_handle, @FDevice[1], SND_PCM_STREAM_CAPTURE, 0); if Res < 0 then raise EACSException.Create(Format(strcoudntopendevice,[FDevice])); // snd_pcm_reset(_audio_handle); end; Inc(FOpened); end; procedure TALSAAudioIn.CloseAudio; begin if FOpened = 1 then begin snd_pcm_drop(_audio_handle); snd_pcm_close(_audio_handle); end; if FOpened > 0 then Dec(FOpened); end; (* Note on the following three methods. These methods simply return the values passed by the user. As the actual input process begins, ALSAAudioIn may return a bit different value of the samplerate that is actually set by the ALSA drivers.*) function TALSAAudioIn.GetBPS : Integer; begin Result := FBPS; end; function TALSAAudioIn.GetCh : Integer; begin Result := FChan; end; function TALSAAudioIn.GetSR : Integer; begin Result := FFreq; end; procedure TALSAAudioIn.SetDevice(Ch: Integer); begin FDevice := IntToStr(ch); end; function TALSAAudioIn.GetDeviceInfo: TACSDeviceInfo; begin end; procedure TALSAAudioIn.Init; var aBufSize : Integer; begin if Busy then raise EACSException.Create(strBusy); BufEnd := 0; BufStart := 1; FPosition := 0; OpenAudio; snd_pcm_hw_params_malloc(_hw_params); snd_pcm_hw_params_any(_audio_handle, _hw_params); snd_pcm_hw_params_set_access(_audio_handle, _hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); if FBPS = 8 then snd_pcm_hw_params_set_format(_audio_handle, _hw_params, SND_PCM_FORMAT_U8) else snd_pcm_hw_params_set_format(_audio_handle, _hw_params, SND_PCM_FORMAT_S16_LE); Self.FFreq := snd_pcm_hw_params_set_rate_near(_audio_handle, _hw_params, FFreq, 0); snd_pcm_hw_params_set_channels(_audio_handle, _hw_params, FChan); if (FPeriodSize <> 0) and (FPeriodNum <> 0) then begin snd_pcm_hw_params_set_period_size_near(_audio_handle, _hw_params, FPeriodSize, 0); snd_pcm_hw_params_set_periods_near(_audio_handle, _hw_params, FPeriodNum, 0); aBufSize := (FPeriodSize * FPeriodNum) div (FChan * (FBPS shr 3)); end else aBufSize := Self.BufferSize div (FChan * (FBPS shr 3)); snd_pcm_hw_params_set_buffer_size_near(_audio_handle, _hw_params, aBufSize); snd_pcm_hw_params(_audio_handle, _hw_params); snd_pcm_hw_params_free(_hw_params); if snd_pcm_prepare(_audio_handle) < 0 then begin CloseAudio; raise EACSException.Create(strInputstartfailed); end; try FLatency := snd_pcm_hw_params_get_period_size(_audio_handle, 0) * snd_pcm_hw_params_get_periods(_audio_handle, 0)/(FFreq * FChan * (FBPS shr 3)); except end; FRecBytes := FRecTime * (GetBPS div 8) * GetCh * GetSR; Busy := True; FSize := FRecBytes; end; procedure TALSAAudioIn.Flush; begin snd_pcm_drain(_audio_handle); CloseAudio; Busy := False; end; function TALSAAudioIn.GetData(Buffer : Pointer; BufferSize : Integer): Integer; var l : Integer; begin if not Busy then raise EACSException.Create(strStreamnotopen); if FPosition >= FRecBytes then begin Result := 0; Exit; end; if BufStart > BufEnd then begin BufStart := 1; l := snd_pcm_readi(_audio_handle, @FBuffer[1], (BUF_SIZE div FChan) div (FBPS shr 3)); while l < 0 do begin snd_pcm_prepare(_audio_handle); if not FSilentOnOverrun then raise EALSABufferOverrun.Create(strBufferoverrun); l := snd_pcm_readi(_audio_handle, @FBuffer[1], (BUF_SIZE div FChan) div (FBPS shr 3)); end; if l <> (BUF_SIZE div FChan) div (FBPS shr 3) then begin Result := 0; Exit; end else BufEnd := l*FChan*(FBPS shr 3); end; if BufferSize < (BufEnd - BufStart + 1) then Result := BufferSize else Result := BufEnd - BufStart + 1; Move(FBuffer[BufStart-1], Buffer^, Result); Inc(BufStart, Result); Inc(FPosition, Result); end; function TALSAAudioIn.GetTotalTime : real; begin Result := FRecTime; end; function TALSAAudioIn.GetDriverState : Integer; begin if FOpened = 0 then Result := ALSAStateIdle else Result := snd_pcm_state(_audio_handle); end; constructor TALSAAudioOut.Create; begin inherited Create(AOwner); if not (csDesigning in ComponentState) then if not AsoundlibLoaded then raise EACSException.Create(Format(strCoudntloadLib,[asoundlib_path])); FVolume := 255; FDevice := 'default'; FBufferSize := 32768; FSilentOnUnderrun := True; end; destructor TALSAAudioOut.Destroy; begin if _audio_handle <> nil then snd_pcm_close(_audio_handle); inherited Destroy; end; procedure TALSAAudioOut.Prepare; var Res, aBufSize : Integer; begin FInput.Init; Res := snd_pcm_open(_audio_handle, @FDevice[1], SND_PCM_STREAM_PLAYBACK, 0); if Res < 0 then raise EACSException.Create(Format(strCoudntopendeviceOut,[FDevice])); //snd_pcm_reset(_audio_handle); snd_pcm_hw_params_malloc(_hw_params); snd_pcm_hw_params_any(_audio_handle, _hw_params); snd_pcm_hw_params_set_access(_audio_handle, _hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); if FInput.BitsPerSample = 8 then snd_pcm_hw_params_set_format(_audio_handle, _hw_params, SND_PCM_FORMAT_U8) else snd_pcm_hw_params_set_format(_audio_handle, _hw_params, SND_PCM_FORMAT_S16_LE); snd_pcm_hw_params_set_rate_near(_audio_handle, _hw_params, FInput.SampleRate, 0); snd_pcm_hw_params_set_channels(_audio_handle, _hw_params, FInput.Channels); if (FPeriodSize <> 0) and (FPeriodNum <> 0) then begin snd_pcm_hw_params_set_period_size_near(_audio_handle, _hw_params, FPeriodSize, 0); snd_pcm_hw_params_set_periods_near(_audio_handle, _hw_params, FPeriodNum, 0); aBufSize := (FPeriodSize * FPeriodNum) div (Finput.Channels * (Finput.BitsPerSample shr 3)); end else aBufSize := Self.FBufferSize div (Finput.Channels * (Finput.BitsPerSample shr 3)); snd_pcm_hw_params_set_buffer_size_near(_audio_handle, _hw_params, aBufSize); snd_pcm_hw_params(_audio_handle, _hw_params); snd_pcm_hw_params_free(_hw_params); if snd_pcm_prepare(_audio_handle) < 0 then begin raise EACSException.Create(strFailedtostartoutput); end; try FLatency := snd_pcm_hw_params_get_period_size(_audio_handle, 0) * snd_pcm_hw_params_get_periods(_audio_handle, 0)/(Finput.Channels * (Finput.BitsPerSample shr 3)); except end; end; procedure TALSAAudioOut.SetDevice(Ch: Integer); begin // FDevice := IntToStr(ch); end; function TALSAAudioOut.GetDeviceInfo: TACSDeviceInfo; begin end; procedure TALSAAudioOut.Done; begin snd_pcm_drain(_audio_handle); snd_pcm_close(_audio_handle); _audio_handle := 0; FInput.Flush; end; function TALSAAudioOut.DoOutput(Abort : Boolean):Boolean; var Len, i, VCoef, l : Integer; P : Pointer; P1 : PACSBuffer8; P2 : PACSBuffer16; begin // No exceptions Here Result := True; if not CanOutput then Exit; Len := 0; if Abort then begin snd_pcm_drain(_audio_handle); snd_pcm_close(_audio_handle); _audio_handle := 0; Result := False; Exit; end; try P := @Buffer[0]; while InputLock do; InputLock := True; Len := Finput.GetData(P, BUF_SIZE); InputLock := False; if Len = 0 then begin Result := False; Exit; end; if FVolume < 255 then begin VCoef := Round(FVolume/255); if FInput.BitsPerSample = 16 then begin P2 := @Buffer[0]; for i := 0 to (Len shr 1) -1 do P2[i] := P2[i]*VCoef; end else begin P1 := @Buffer[0]; for i := 0 to Len - 1 do P1[i] := P1[i]*VCoef; end; end; l := snd_pcm_writei(_audio_handle, P, (Len div Finput.Channels) div (FInput.BitsPerSample shr 3)); while l < 0 do begin snd_pcm_prepare(_audio_handle); if not FSilentOnUnderrun then raise EALSABufferUnderrun.Create(strBufferunderrun); l := snd_pcm_writei(_audio_handle, P, (Len div Finput.Channels) div (FInput.BitsPerSample shr 3)); end; if l = (Len div Finput.Channels) div (FInput.BitsPerSample shr 3) then Result := True else Result := False; except end; end; function TALSAAudioOut.GetDriverState : Integer; begin if not Busy then Result := ALSAStateIdle else Result := snd_pcm_state(_audio_handle); end; initialization if AsoundlibLoaded then begin RegisterAudioOut('Alsa',TAlsaAudioOut,LATENCY); RegisterAudioIn('Alsa',TAlsaAudioIn,LATENCY); end; end.