482 lines
13 KiB
ObjectPascal
482 lines
13 KiB
ObjectPascal
(*
|
|
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.
|