aboutsummaryrefslogtreecommitdiffstats
path: root/Game/Code/Classes/UAudioInput_Portaudio.pas
diff options
context:
space:
mode:
Diffstat (limited to 'Game/Code/Classes/UAudioInput_Portaudio.pas')
-rw-r--r--Game/Code/Classes/UAudioInput_Portaudio.pas347
1 files changed, 347 insertions, 0 deletions
diff --git a/Game/Code/Classes/UAudioInput_Portaudio.pas b/Game/Code/Classes/UAudioInput_Portaudio.pas
new file mode 100644
index 00000000..753c69f6
--- /dev/null
+++ b/Game/Code/Classes/UAudioInput_Portaudio.pas
@@ -0,0 +1,347 @@
+unit UAudioInput_Portaudio;
+
+interface
+
+{$IFDEF FPC}
+ {$MODE Delphi}
+{$ENDIF}
+
+{$I ../switches.inc}
+
+
+uses
+ Classes,
+ SysUtils,
+ UMusic;
+
+implementation
+
+uses
+ URecord,
+ UIni,
+ ULog,
+ UMain,
+ {$IFDEF UsePortmixer}
+ portmixer,
+ {$ENDIF}
+ portaudio;
+
+type
+ TAudioInput_Portaudio = class(TAudioInputBase)
+ private
+ function GetPreferredApiIndex(): TPaHostApiIndex;
+ public
+ function GetName: String; override;
+ function InitializeRecord: boolean; override;
+ destructor Destroy; override;
+ end;
+
+ TPortaudioInputDevice = class(TAudioInputDevice)
+ public
+ RecordStream: PPaStream;
+ PaDeviceIndex: TPaDeviceIndex;
+
+ procedure Start(); override;
+ procedure Stop(); override;
+ end;
+
+function MicrophoneCallback(input: Pointer; output: Pointer; frameCount: Longword;
+ timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
+ inputDevice: Pointer): Integer; cdecl; forward;
+
+var
+ singleton_AudioInputPortaudio : IAudioInput;
+
+{* the default API used by Portaudio is the least common denominator
+ * and might lack efficiency. ApiPreferenceOrder defines the order of
+ * preferred APIs to use. The first API-type in the list is tried first. If it's
+ * not available the next is tried, ...
+ * If none of the preferred APIs was found the default API is used.
+ * Pascal doesn't permit zero-length static arrays, so you can use paDefaultApi
+ * as an array's only member if you do not have any preferences.
+ * paDefaultApi also terminates a preferences list but this is optional.
+ *}
+const
+ paDefaultApi = -1;
+var
+ ApiPreferenceOrder:
+{$IF Defined(WIN32)}
+ // Note1: Portmixer has no mixer support for paASIO and paWASAPI at the moment
+ // Note2: Windows Default-API is MME
+ //array[0..0] of TPaHostApiTypeId = ( paDirectSound, paMME );
+ array[0..0] of TPaHostApiTypeId = ( paDirectSound );
+{$ELSEIF Defined(LINUX)}
+ // Note1: Portmixer has no mixer support for paJACK at the moment
+ // Note2: Not tested, but ALSA might be better than OSS.
+ array[0..1] of TPaHostApiTypeId = ( paALSA, paOSS );
+{$ELSEIF Defined(DARWIN)}
+ // Note: Not tested.
+ //array[0..0] of TPaHostApiTypeId = ( paCoreAudio );
+ array[0..0] of TPaHostApiTypeId = ( paDefaultApi );
+{$ELSE}
+ array[0..0] of TPaHostApiTypeId = ( paDefaultApi );
+{$IFEND}
+
+
+{ TPortaudioInputDevice }
+
+procedure TPortaudioInputDevice.Start();
+var
+ Error: TPaError;
+ ErrorMsg: string;
+ inputParams: TPaStreamParameters;
+ deviceInfo: PPaDeviceInfo;
+begin
+ // get input latency info
+ deviceInfo := Pa_GetDeviceInfo(PaDeviceIndex);
+
+ // set input stream parameters
+ with inputParams do begin
+ device := PaDeviceIndex;
+ channelCount := 2;
+ sampleFormat := paInt16;
+ suggestedLatency := deviceInfo^.defaultLowInputLatency;
+ hostApiSpecificStreamInfo := nil;
+ end;
+
+ Log.LogStatus(inttostr(PaDeviceIndex), 'Portaudio');
+ Log.LogStatus(floattostr(deviceInfo^.defaultLowInputLatency), 'Portaudio');
+
+ // open input stream
+ Error := Pa_OpenStream(RecordStream, @inputParams, nil, SampleRate,
+ paFramesPerBufferUnspecified, paNoFlag,
+ @MicrophoneCallback, Pointer(Self));
+ if(Error <> paNoError) then begin
+ ErrorMsg := Pa_GetErrorText(Error);
+ Log.CriticalError('TPortaudioInputDevice.Start(): Error opening stream: ' + ErrorMsg);
+ //Halt;
+ end;
+
+ // start capture
+ Error := Pa_StartStream(RecordStream);
+ if(Error <> paNoError) then begin
+ Pa_CloseStream(RecordStream);
+ ErrorMsg := Pa_GetErrorText(Error);
+ Log.CriticalError('TPortaudioInputDevice.Start(): Error starting stream: ' + ErrorMsg);
+ //Halt;
+ end;
+end;
+
+procedure TPortaudioInputDevice.Stop();
+begin
+ if assigned(RecordStream) then begin
+ Pa_StopStream(RecordStream);
+ Pa_CloseStream(RecordStream);
+ end;
+end;
+
+
+{ TAudioInput_Portaudio }
+
+function TAudioInput_Portaudio.GetName: String;
+begin
+ result := 'Portaudio';
+end;
+
+function TAudioInput_Portaudio.GetPreferredApiIndex(): TPaHostApiIndex;
+var
+ i: integer;
+begin
+ result := -1;
+
+ // select preferred sound-API
+ for i:= 0 to High(ApiPreferenceOrder) do
+ begin
+ if(ApiPreferenceOrder[i] <> paDefaultApi) then begin
+ // check if API is available
+ result := Pa_HostApiTypeIdToHostApiIndex(ApiPreferenceOrder[i]);
+ if(result >= 0) then
+ break;
+ end;
+ end;
+
+ // None of the preferred APIs is available -> use default
+ if(result < 0) then begin
+ result := Pa_GetDefaultHostApi();
+ end;
+end;
+
+function TAudioInput_Portaudio.InitializeRecord(): boolean;
+var
+ i: integer;
+ apiIndex: TPaHostApiIndex;
+ apiInfo: PPaHostApiInfo;
+ deviceName: string;
+ deviceIndex: TPaDeviceIndex;
+ deviceInfo: PPaDeviceInfo;
+ sourceCnt: integer;
+ sourceName: string;
+ SC: integer; // soundcard
+ SCI: integer; // soundcard source
+ err: TPaError;
+ errMsg: string;
+ paDevice: TPortaudioInputDevice;
+ inputParams: TPaStreamParameters;
+ stream: PPaStream;
+ {$IFDEF UsePortmixer}
+ mixer: PPxMixer;
+ {$ENDIF}
+const
+ captureFreq = 44100;
+begin
+ result := false;
+
+ err := Pa_Initialize();
+ if(err <> paNoError) then begin
+ Log.LogError('Portaudio.InitializeRecord: ' + Pa_GetErrorText(err));
+ Exit;
+ end;
+ apiIndex := GetPreferredApiIndex();
+ apiInfo := Pa_GetHostApiInfo(apiIndex);
+
+ SC := 0;
+
+ // init array-size to max. input-devices count
+ SetLength(AudioInputProcessor.Device, apiInfo^.deviceCount);
+ for i:= 0 to High(AudioInputProcessor.Device) do
+ begin
+ // convert API-specific device-index to global index
+ deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(apiIndex, i);
+ deviceInfo := Pa_GetDeviceInfo(deviceIndex);
+
+ // current device is no input device -> skip
+ if(deviceInfo^.maxInputChannels <= 0) then
+ continue;
+
+ paDevice := TPortaudioInputDevice.Create();
+ AudioInputProcessor.Device[SC] := paDevice;
+
+ // retrieve device-name
+ deviceName := deviceInfo^.name;
+ paDevice.Description := deviceName;
+ paDevice.PaDeviceIndex := deviceIndex;
+
+ // setup desired input parameters
+ with inputParams do begin
+ device := deviceIndex;
+ channelCount := 2;
+ sampleFormat := paInt16;
+ suggestedLatency := deviceInfo^.defaultLowInputLatency;
+ hostApiSpecificStreamInfo := nil;
+ end;
+
+ paDevice.SampleRate := captureFreq;
+
+ // check if device supports our input-format
+ err := Pa_IsFormatSupported(@inputParams, nil, paDevice.SampleRate);
+ if(err <> 0) then begin
+ // format not supported -> skip
+ errMsg := Pa_GetErrorText(err);
+ Log.LogError('Portaudio.InitializeRecord, device: "'+ deviceName +'" '
+ + '('+ errMsg +')');
+ paDevice.Free();
+ continue;
+ end;
+
+ // TODO: retry with mono if stereo is not supported
+ // TODO: retry with input-latency set to 20ms (defaultLowInputLatency might
+ // not be set correctly in OSS)
+ // use TPaDeviceInfo.defaultSampleRate
+
+ err := Pa_OpenStream(stream, @inputParams, nil, paDevice.SampleRate,
+ paFramesPerBufferUnspecified, paNoFlag, @MicrophoneCallback, nil);
+ if(err <> paNoError) then begin
+ // unable to open device -> skip
+ errMsg := Pa_GetErrorText(err);
+ Log.LogError('Portaudio.InitializeRecord, device: "'+ deviceName +'" '
+ + '('+ errMsg +')');
+ paDevice.Free();
+ continue;
+ end;
+
+
+ {$IFDEF UsePortmixer}
+
+ // use default mixer
+ mixer := Px_OpenMixer(stream, 0);
+
+ // get input count
+ sourceCnt := Px_GetNumInputSources(mixer);
+ SetLength(paDevice.Source, sourceCnt);
+
+ // get input names
+ for SCI := 0 to sourceCnt-1 do
+ begin
+ sourceName := Px_GetInputSourceName(mixer, SCI);
+ paDevice.Source[SCI].Name := sourceName;
+ end;
+
+ Px_CloseMixer(mixer);
+
+ {$ELSE} // !UsePortmixer
+
+ //Pa_StartStream(stream);
+ // TODO: check if callback was called (this problem may occur on some devices)
+ //Pa_StopStream(stream);
+
+ // create a standard input source
+ SetLength(paDevice.Source, 1);
+ paDevice.Source[0].Name := 'Standard';
+
+ {$ENDIF}
+
+ // close test-stream
+ Pa_CloseStream(stream);
+
+ // use default input source
+ paDevice.SourceSelected := 0;
+
+ Inc(SC);
+ end;
+
+ // adjust size to actual input-device count
+ SetLength(AudioInputProcessor.Device, SC);
+
+ Log.LogStatus('#Soundcards: ' + inttostr(SC), 'Portaudio');
+
+ {
+ SoundCard[SC].InputSelected := Mic[Device];
+ }
+ result := true;
+end;
+
+destructor TAudioInput_Portaudio.Destroy;
+var
+ i: integer;
+ paSoundCard: TPortaudioInputDevice;
+begin
+ Pa_Terminate();
+ for i := 0 to High(AudioInputProcessor.Device) do
+ begin
+ AudioInputProcessor.Device[i].Free();
+ end;
+ AudioInputProcessor.Device := nil;
+
+ inherited Destroy;
+end;
+
+{*
+ * Portaudio input capture callback.
+ *}
+function MicrophoneCallback(input: Pointer; output: Pointer; frameCount: Longword;
+ timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
+ inputDevice: Pointer): Integer; cdecl;
+begin
+ AudioInputProcessor.HandleMicrophoneData(input, frameCount*4, inputDevice);
+ result := paContinue;
+end;
+
+
+initialization
+ singleton_AudioInputPortaudio := TAudioInput_Portaudio.create();
+ AudioManager.add( singleton_AudioInputPortaudio );
+
+finalization
+ AudioManager.Remove( singleton_AudioInputPortaudio );
+
+end.