unit UAudioPlayback_Portaudio;
interface
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
{$I switches.inc}
uses
Classes,
SysUtils,
UMusic;
implementation
uses
{$IFNDEF Win32}
libc,
{$ENDIF}
sdl,
portaudio,
ULog,
UIni,
UMain;
type
TPortaudioPlaybackStream = class(TAudioPlaybackStream)
private
Status: TStreamStatus;
Loop: boolean;
_volume: integer;
procedure Reset();
public
DecodeStream: TAudioDecodeStream;
constructor Create();
destructor Destroy(); override;
function SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
procedure Play(); override;
procedure Pause(); override;
procedure Stop(); override;
procedure Close(); override;
function GetLoop(): boolean; override;
procedure SetLoop(Enabled: boolean); override;
function GetLength(): real; override;
function GetStatus(): TStreamStatus; override;
function IsLoaded(): boolean;
function GetVolume(): integer; override;
procedure SetVolume(volume: integer); override;
// functions delegated to the decode stream
function GetPosition: real;
procedure SetPosition(Time: real);
function ReadData(Buffer: PChar; BufSize: integer): integer;
end;
type
TAudioMixerStream = class
private
activeStreams: TList;
mixerBuffer: PChar;
internalLock: PSDL_Mutex;
_volume: integer;
procedure Lock(); inline;
procedure Unlock(); inline;
function GetVolume(): integer;
procedure SetVolume(volume: integer);
public
constructor Create();
destructor Destroy(); override;
procedure AddStream(stream: TAudioPlaybackStream);
procedure RemoveStream(stream: TAudioPlaybackStream);
function ReadData(Buffer: PChar; BufSize: integer): integer;
property Volume: integer READ GetVolume WRITE SetVolume;
end;
type
TAudioPlayback_Portaudio = class( TInterfacedObject, IAudioPlayback )
private
MusicStream: TPortaudioPlaybackStream;
MixerStream: TAudioMixerStream;
paStream: PPaStream;
FrameSize: integer;
function InitializePortaudio(): boolean;
function StartPortaudioStream(): boolean;
function InitializeSDLAudio(): boolean;
function StartSDLAudioStream(): boolean;
procedure StopSDLAudioStream();
public
function GetName: String;
function InitializePlayback(): boolean;
destructor Destroy; override;
function Load(const Filename: String): TPortaudioPlaybackStream;
procedure SetVolume(Volume: integer);
procedure SetMusicVolume(Volume: integer);
procedure SetLoop(Enabled: boolean);
function Open(Filename: string): boolean; // true if succeed
procedure Rewind;
procedure SetPosition(Time: real);
procedure Play;
procedure Pause;
procedure Stop;
procedure Close;
function Finished: boolean;
function Length: real;
function GetPosition: real;
// Equalizer
procedure GetFFTData(var data: TFFTData);
// Interface for Visualizer
function GetPCMData(var data: TPCMData): Cardinal;
// Sounds
function OpenSound(const Filename: String): TAudioPlaybackStream;
procedure PlaySound(stream: TAudioPlaybackStream);
procedure StopSound(stream: TAudioPlaybackStream);
end;
function AudioCallback(input: Pointer; output: Pointer; frameCount: Longword;
timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
userData: Pointer): Integer; cdecl; forward;
var
singleton_AudioPlaybackPortaudio : IAudioPlayback;
{ TAudioMixerStream }
constructor TAudioMixerStream.Create();
begin
activeStreams := TList.Create;
internalLock := SDL_CreateMutex();
_volume := 100;
end;
destructor TAudioMixerStream.Destroy();
begin
if assigned(mixerBuffer) then
Freemem(mixerBuffer);
activeStreams.Free;
SDL_DestroyMutex(internalLock);
end;
procedure TAudioMixerStream.Lock();
begin
SDL_mutexP(internalLock);
end;
procedure TAudioMixerStream.Unlock();
begin
SDL_mutexV(internalLock);
end;
function TAudioMixerStream.GetVolume(): integer;
begin
Lock();
result := _volume;
Unlock();
end;
procedure TAudioMixerStream.SetVolume(volume: integer);
begin
Lock();
_volume := volume;
Unlock();
end;
procedure TAudioMixerStream.AddStream(stream: TAudioPlaybackStream);
begin
if not assigned(stream) then
Exit;
Lock();
// check if stream is already in list to avoid duplicates
if (activeStreams.IndexOf(Pointer(stream)) = -1) then
activeStreams.Add(Pointer(stream));
Unlock();
end;
procedure TAudioMixerStream.RemoveStream(stream: TAudioPlaybackStream);
begin
Lock();
activeStreams.Remove(Pointer(stream));
Unlock();
end;
function TAudioMixerStream.ReadData(Buffer: PChar; BufSize: integer): integer;
var
i: integer;
size: integer;
stream: TPortaudioPlaybackStream;
appVolume: single;
begin
result := BufSize;
// zero target-buffer (silence)
FillChar(Buffer^, BufSize, 0);
// resize mixer-buffer if necessary
ReallocMem(mixerBuffer, BufSize);
if not assigned(mixerBuffer) then
Exit;
Lock();
//writeln('Mix: ' + inttostr(activeStreams.Count));
// use _volume instead of Volume to prevent recursive locking
appVolume := _volume / 100 * SDL_MIX_MAXVOLUME;
for i := 0 to activeStreams.Count-1 do
begin
stream := TPortaudioPlaybackStream(activeStreams[i]);
if (stream.GetStatus() = ssPlaying) then
begin
// fetch data from current stream
size := stream.ReadData(mixerBuffer, BufSize);
if (size > 0) then
begin
SDL_MixAudio(PUInt8(Buffer), PUInt8(mixerBuffer), size,
Trunc(appVolume * stream.Volume / 100));
end;
end;
end;
Unlock();
end;
{ TPortaudioPlaybackStream }
constructor TPortaudioPlaybackStream.Create();
begin
inherited Create();
Reset();
end;
destructor TPortaudioPlaybackStream.Destroy();
begin
Close();
inherited Destroy();
end;
procedure TPortaudioPlaybackStream.Reset();
begin
Status := ssStopped;
Loop := false;
DecodeStream := nil;
_volume := 0;
end;
function TPortaudioPlaybackStream.SetDecodeStream(decodeStream: TAudioDecodeStream): boolean;
begin
result := false;
Reset();
if not assigned(decodeStream) then
Exit;
Self.DecodeStream := decodeStream;
_volume := 100;
result := true;
end;
procedure TPortaudioPlaybackStream.Close();
begin
Reset();
end;
procedure TPortaudioPlaybackStream.Play();
begin
if (status <> ssPaused) then
begin
// rewind
if assigned(DecodeStream) then
DecodeStream.Position := 0;
end;
status := ssPlaying;
//MixerStream.AddStream(Self);
end;
procedure TPortaudioPlaybackStream.Pause();
begin
status := ssPaused;
end;
procedure TPortaudioPlaybackStream.Stop();
begin
status := ssStopped;
end;
function TPortaudioPlaybackStream.IsLoaded(): boolean;
begin
result := assigned(DecodeStream);
end;
function TPortaudioPlaybackStream.GetLoop(): boolean;
begin
result := Loop;
end;
procedure TPortaudioPlaybackStream.SetLoop(Enabled: boolean);
begin
Loop := Enabled;
end;
function TPortaudioPlaybackStream.GetLength(): real;
begin
if assigned(DecodeStream) then
result := DecodeStream.Length
else
result := -1;
end;
function TPortaudioPlaybackStream.GetStatus(): TStreamStatus;
begin
result := status;
end;
function TPortaudioPlaybackStream.ReadData(Buffer: PChar; BufSize: integer): integer;
begin
if not assigned(DecodeStream) then
begin
result := -1;
Exit;
end;
result := DecodeStream.ReadData(Buffer, BufSize);
// end-of-file reached -> stop playback
if (DecodeStream.EOF) then
begin
status := ssStopped;
end;
end;
function TPortaudioPlaybackStream.GetPosition: real;
begin
if assigned(DecodeStream) then
result := DecodeStream.Position
else
result := -1;
end;
procedure TPortaudioPlaybackStream.SetPosition(Time: real);
begin
if assigned(DecodeStream) then
DecodeStream.Position := Time;
end;
function TPortaudioPlaybackStream.GetVolume(): integer;
begin
result := _volume;
end;
procedure TPortaudioPlaybackStream.SetVolume(volume: integer);
begin
// clamp volume
if (volume > 100) then
_volume := 100
else if (volume < 0) then
_volume := 0
else
_volume := volume;
end;
{ TAudioPlayback_Portaudio }
function AudioCallback(input: Pointer; output: Pointer; frameCount: Longword;
timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
userData: Pointer): Integer; cdecl;
var
playback: TAudioPlayback_Portaudio;
begin
playback := TAudioPlayback_Portaudio(userData);
with playback do
begin
MixerStream.ReadData(output, frameCount * FrameSize);
end;
result := paContinue;
end;
procedure SDLAudioCallback(userdata: Pointer; stream: PChar; len: integer); cdecl;
var
playback: TAudioPlayback_Portaudio;
begin
playback := TAudioPlayback_Portaudio(userdata);
with playback do
begin
MixerStream.ReadData(stream, len);
end;
end;
function TAudioPlayback_Portaudio.GetName: String;
begin
result := 'Portaudio_Playback';
end;
function TAudioPlayback_Portaudio.InitializePortaudio(): boolean;
var
paApi : TPaHostApiIndex;
paApiInfo : PPaHostApiInfo;
paOutParams : TPaStreamParameters;
paOutDevice : TPaDeviceIndex;
paOutDeviceInfo : PPaDeviceInfo;
err : TPaError;
begin
result := false;
Pa_Initialize();
// FIXME: determine automatically
{$IFDEF WIN32}
paApi := Pa_HostApiTypeIdToHostApiIndex(paDirectSound);
{$ELSE}
paApi := Pa_HostApiTypeIdToHostApiIndex(paALSA);
{$ENDIF}
if (paApi < 0) then
begin
Log.LogStatus('Pa_HostApiTypeIdToHostApiIndex: '+Pa_GetErrorText(paApi), 'UAudioPlayback_Portaudio');
exit;
end;
paApiInfo := Pa_GetHostApiInfo(paApi);
paOutDevice := paApiInfo^.defaultOutputDevice;
paOutDeviceInfo := Pa_GetDeviceInfo(paOutDevice);
with paOutParams do begin
device := paOutDevice;
channelCount := 2;
sampleFormat := paInt16;
suggestedLatency := paOutDeviceInfo^.defaultHighOutputLatency;
hostApiSpecificStreamInfo := nil;
end;
// set the size of one audio frame (2channel 16bit uint sample)
FrameSize := 2 * sizeof(Smallint);
err := Pa_OpenStream(paStream, nil, @paOutParams, 44100,
paFramesPerBufferUnspecified,
paNoFlag, @AudioCallback, Self);
if(err <> paNoError) then begin
Log.LogStatus('Pa_OpenStream: '+Pa_GetErrorText(err), 'UAudioPlayback_Portaudio');
exit;
end;
Log.LogStatus('Opened audio device', 'UAudioPlayback_Portaudio');
result := true;
end;
function TAudioPlayback_Portaudio.StartPortaudioStream(): boolean;
var
err: TPaError;
begin
result := false;
err := Pa_StartStream(paStream);
if(err <> paNoError) then
begin
Log.LogStatus('Pa_StartStream: '+Pa_GetErrorText(err), 'UAudioPlayback_Portaudio');
exit;
end;
result := true;
end;
function TAudioPlayback_Portaudio.InitializeSDLAudio(): boolean;
var
desiredAudioSpec, obtainedAudioSpec: TSDL_AudioSpec;
err: integer;
begin
result := false;
SDL_InitSubSystem(SDL_INIT_AUDIO);
FillChar(desiredAudioSpec, sizeof(desiredAudioSpec), 0);
with desiredAudioSpec do
begin
freq := 44100;
format := AUDIO_S16SYS;
channels := 2;
samples := 1024; // latency: 23 ms
callback := @SDLAudioCallback;
userdata := Self;
end;
// set the size of one audio frame (2channel 16bit uint sample)
FrameSize := 2 * sizeof(Smallint);
if(SDL_OpenAudio(@desiredAudioSpec, @obtainedAudioSpec) = -1) then
begin
Log.LogStatus('SDL_OpenAudio: ' + SDL_GetError(), 'UAudioPlayback_SDL');
exit;
end;
Log.LogStatus('Opened audio device', 'UAudioPlayback_SDL');
result := true;
end;
function TAudioPlayback_Portaudio.StartSDLAudioStream(): boolean;
begin
SDL_PauseAudio(0);
result := true;
end;
procedure TAudioPlayback_Portaudio.StopSDLAudioStream();
begin
SDL_CloseAudio();
end;
function TAudioPlayback_Portaudio.InitializePlayback: boolean;
begin
result := false;
//Log.LogStatus('InitializePlayback', 'UAudioPlayback_Portaudio');
//if(not InitializePortaudio()) then
if(not InitializeSDLAudio()) then
Exit;
MixerStream := TAudioMixerStream.Create;
//if(not StartPortaudioStream()) then;
if(not StartSDLAudioStream()) then
Exit;
result := true;
end;
destructor TAudioPlayback_Portaudio.Destroy;
begin
StopSDLAudioStream();
MixerStream.Free();
MusicStream.Free();
inherited Destroy();
end;
function TAudioPlayback_Portaudio.Load(const Filename: String): TPortaudioPlaybackStream;
var
decodeStream: TAudioDecodeStream;
playbackStream: TPortaudioPlaybackStream;
begin
Result := nil;
decodeStream := AudioDecoder.Open(Filename);
if not assigned(decodeStream) then
begin
Log.LogStatus('LoadSoundFromFile: Sound not found "' + Filename + '"', 'UAudioPlayback_Portaudio');
Exit;
end;
playbackStream := TPortaudioPlaybackStream.Create();
if (not playbackStream.SetDecodeStream(decodeStream)) then
Exit;
// FIXME: remove this line
MixerStream.AddStream(playbackStream);
result := playbackStream;
end;
procedure TAudioPlayback_Portaudio.SetVolume(Volume: integer);
begin
// sets volume only for this application
MixerStream.Volume := Volume;
end;
procedure TAudioPlayback_Portaudio.SetMusicVolume(Volume: Integer);
begin
if assigned(MusicStream) then
MusicStream.Volume := Volume;
end;
procedure TAudioPlayback_Portaudio.SetLoop(Enabled: boolean);
begin
if assigned(MusicStream) then
MusicStream.SetLoop(Enabled);
end;
function TAudioPlayback_Portaudio.Open(Filename: string): boolean;
var
decodeStream: TAudioDecodeStream;
begin
Result := false;
// free old MusicStream
MusicStream.Free();
MusicStream := Load(Filename);
if not assigned(MusicStream) then
Exit;
//Set Max Volume
SetMusicVolume(100);
Result := true;
end;
procedure TAudioPlayback_Portaudio.Rewind;
begin
SetPosition(0);
end;
procedure TAudioPlayback_Portaudio.SetPosition(Time: real);
begin
if assigned(MusicStream) then
MusicStream.SetPosition(Time);
end;
function TAudioPlayback_Portaudio.GetPosition: real;
begin
if assigned(MusicStream) then
Result := MusicStream.GetPosition()
else
Result := -1;
end;
function TAudioPlayback_Portaudio.Length: real;
begin
if assigned(MusicStream) then
Result := MusicStream.GetLength()
else
Result := -1;
end;
procedure TAudioPlayback_Portaudio.Play;
begin
if assigned(MusicStream) then
MusicStream.Play();
end;
procedure TAudioPlayback_Portaudio.Pause;
begin
if assigned(MusicStream) then
MusicStream.Pause();
end;
procedure TAudioPlayback_Portaudio.Stop;
begin
if assigned(MusicStream) then
MusicStream.Stop();
end;
procedure TAudioPlayback_Portaudio.Close;
begin
if assigned(MusicStream) then
begin
MixerStream.RemoveStream(MusicStream);
MusicStream.Close();
end;
end;
function TAudioPlayback_Portaudio.Finished: boolean;
begin
if assigned(MusicStream) then
Result := (MusicStream.GetStatus() = ssStopped)
else
Result := true;
end;
//Equalizer
procedure TAudioPlayback_Portaudio.GetFFTData(var data: TFFTData);
begin
//Get Channel Data Mono and 256 Values
// BASS_ChannelGetData(Bass, @Result, BASS_DATA_FFT512);
end;
// Interface for Visualizer
function TAudioPlayback_Portaudio.GetPCMData(var data: TPCMData): Cardinal;
begin
result := 0;
end;
function TAudioPlayback_Portaudio.OpenSound(const Filename: String): TAudioPlaybackStream;
begin
result := Load(Filename);
end;
procedure TAudioPlayback_Portaudio.PlaySound(stream: TAudioPlaybackStream);
begin
if assigned(stream) then
stream.Play();
end;
procedure TAudioPlayback_Portaudio.StopSound(stream: TAudioPlaybackStream);
begin
if assigned(stream) then
stream.Stop();
end;
initialization
singleton_AudioPlaybackPortaudio := TAudioPlayback_Portaudio.create();
AudioManager.add( singleton_AudioPlaybackPortaudio );
finalization
AudioManager.Remove( singleton_AudioPlaybackPortaudio );
end.