aboutsummaryrefslogtreecommitdiffstats
path: root/Game/Code/Classes/UAudioCore_Portaudio.pas
diff options
context:
space:
mode:
Diffstat (limited to 'Game/Code/Classes/UAudioCore_Portaudio.pas')
-rw-r--r--Game/Code/Classes/UAudioCore_Portaudio.pas257
1 files changed, 0 insertions, 257 deletions
diff --git a/Game/Code/Classes/UAudioCore_Portaudio.pas b/Game/Code/Classes/UAudioCore_Portaudio.pas
deleted file mode 100644
index bcc8a001..00000000
--- a/Game/Code/Classes/UAudioCore_Portaudio.pas
+++ /dev/null
@@ -1,257 +0,0 @@
-unit UAudioCore_Portaudio;
-
-interface
-
-{$IFDEF FPC}
- {$MODE Delphi}
-{$ENDIF}
-
-{$I ../switches.inc}
-
-
-uses
- Classes,
- SysUtils,
- portaudio;
-
-type
- TAudioCore_Portaudio = class
- public
- constructor Create();
- class function GetInstance(): TAudioCore_Portaudio;
- function GetPreferredApiIndex(): TPaHostApiIndex;
- function TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean;
- end;
-
-implementation
-
-uses
- ULog;
-
-{*
- * The default API used by Portaudio is the least common denominator
- * and might lack efficiency. In addition it might not even work.
- * We use an array named ApiPreferenceOrder with which we define the order of
- * preferred APIs to use. The first API-type in the list is tried first.
- * If it is not available the next one is tried and so on ...
- * If none of the preferred APIs was found the default API (detected by
- * portaudio) is used.
- *
- * Pascal does not permit zero-length static arrays, so you must use paDefaultApi
- * as an array's only member if you do not have any preferences.
- * You can also append paDefaultApi to a non-zero length preferences array but
- * this is optional because the default API is always used as a fallback.
- *}
-const
- paDefaultApi = -1;
-const
- ApiPreferenceOrder:
-{$IF Defined(MSWINDOWS)}
- // Note1: Portmixer has no mixer support for paASIO and paWASAPI at the moment
- // Note2: Windows Default-API is MME, but DirectSound is faster
- array[0..0] of TPaHostApiTypeId = ( paDirectSound );
-{$ELSEIF Defined(LINUX)}
- // Note: Portmixer has no mixer support for JACK at the moment
- array[0..2] of TPaHostApiTypeId = ( paALSA, paJACK, paOSS );
-{$ELSEIF Defined(DARWIN)}
- array[0..0] of TPaHostApiTypeId = ( paDefaultApi ); // paCoreAudio
-{$ELSE}
- array[0..0] of TPaHostApiTypeId = ( paDefaultApi );
-{$IFEND}
-
-
-{ TAudioInput_Portaudio }
-
-var
- Instance: TAudioCore_Portaudio;
-
-constructor TAudioCore_Portaudio.Create();
-begin
- inherited;
-end;
-
-class function TAudioCore_Portaudio.GetInstance(): TAudioCore_Portaudio;
-begin
- if not assigned(Instance) then
- Instance := TAudioCore_Portaudio.Create();
- Result := Instance;
-end;
-
-function TAudioCore_Portaudio.GetPreferredApiIndex(): TPaHostApiIndex;
-var
- i: integer;
- apiIndex: TPaHostApiIndex;
- apiInfo: PPaHostApiInfo;
-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
- apiIndex := Pa_HostApiTypeIdToHostApiIndex(ApiPreferenceOrder[i]);
- if(apiIndex >= 0) then
- begin
- // we found an API but we must check if it works
- // (on linux portaudio might detect OSS but does not provide
- // any devices if ALSA is enabled)
- apiInfo := Pa_GetHostApiInfo(apiIndex);
- if (apiInfo^.deviceCount > 0) then
- begin
- Result := apiIndex;
- break;
- end;
- end;
- end;
- end;
-
- // None of the preferred APIs is available -> use default
- if(result < 0) then
- begin
- result := Pa_GetDefaultHostApi();
- end;
-end;
-
-{*
- * Portaudio test callback used by TestDevice().
- *}
-function TestCallback(input: Pointer; output: Pointer; frameCount: Longword;
- timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
- inputDevice: Pointer): Integer; cdecl;
-begin
- // this callback is called only once
- result := paAbort;
-end;
-
-(*
- * Tests if the callback works. Some devices can be opened without
- * an error but the callback is never called. Calling Pa_StopStream() on such
- * a stream freezes USDX then. Probably because the callback-thread is deadlocked
- * due to some bug in portaudio. The blocking Pa_ReadStream() and Pa_WriteStream()
- * block forever too and though can't be used for testing.
- *
- * To avoid freezing Pa_AbortStream (or Pa_CloseStream which calls Pa_AbortStream)
- * can be used to force the stream to stop. But for some reason this stops debugging
- * in gdb with a "no process found" message.
- *
- * Because freezing devices are non-working devices we test the devices here to
- * be able to exclude them from the device-selection list.
- *
- * Portaudio does not provide any test to check this error case (probably because
- * it should not even occur). So we have to open the device, start the stream and
- * check if the callback is called (the stream is stopped if the callback is called
- * for the first time, so we can poll until the stream is stopped).
- *
- * Another error that occurs is that some devices (even the default device) might
- * work at the beginning but stop after a few calls (maybe 50) of the callback.
- * For me this problem occurs with the default output-device. The "dmix" or "front"
- * device must be selected instead. Another problem is that (due to a bug in
- * portaudio or ALSA) the "front" device is not detected every time portaudio
- * is started. Sometimes it needs two or more restarts.
- *
- * There is no reasonable way to test for these errors. For the first error-case
- * we could test if the callback is called 50 times but this can take a second
- * for each device and it can fail in the 51st or even 100th callback call then.
- *
- * The second error-case cannot be tested at all. How should we now that one
- * device is missing if portaudio is not even able to detect it.
- * We could start and terminate Portaudio for several times and see if the device
- * count changes but this is ugly.
- *
- * Conclusion: We are not able to autodetect a working device with
- * portaudio (at least not with the newest v19_20071207) at the moment.
- * So we have to provide the possibility to manually select an output device
- * in the UltraStar options if we want to use portaudio instead of SDL.
- *)
-function TAudioCore_Portaudio.TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean;
-var
- stream: PPaStream;
- err: TPaError;
- cbWorks: boolean;
- cbPolls: integer;
- i: integer;
-const
- altSampleRates: array[0..1] of Double = (44100, 48000); // alternative sample-rates
-begin
- Result := false;
-
- if (sampleRate <= 0) then
- sampleRate := 44100;
-
- // check if device supports our input-format
- err := Pa_IsFormatSupported(inParams, outParams, sampleRate);
- if(err <> paNoError) then
- begin
- // we cannot fix the error -> exit
- if (err <> paInvalidSampleRate) then
- Exit;
-
- // try alternative sample-rates to the detected one
- sampleRate := 0;
- for i := 0 to High(altSampleRates) do
- begin
- // do not check the detected sample-rate twice
- if (altSampleRates[i] = sampleRate) then
- continue;
- // check alternative
- err := Pa_IsFormatSupported(inParams, outParams, altSampleRates[i]);
- if (err = paNoError) then
- begin
- // sample-rate works
- sampleRate := altSampleRates[i];
- break;
- end;
- end;
- // no working sample-rate found
- if (sampleRate = 0) then
- Exit;
- end;
-
- // FIXME: for some reason gdb stops after a call of Pa_AbortStream()
- // which is implicitely called by Pa_CloseStream().
- // gdb's stops with the message: "ptrace: no process found".
- // Probably because the callback-thread is killed what confuses gdb.
- {$IF Defined(Debug) and Defined(Linux)}
- cbWorks := true;
- {$ELSE}
- // open device for testing
- err := Pa_OpenStream(stream, inParams, outParams, sampleRate,
- paFramesPerBufferUnspecified,
- paNoFlag, @TestCallback, nil);
- if(err <> paNoError) then
- begin
- exit;
- end;
-
- // start the callback
- err := Pa_StartStream(stream);
- if(err <> paNoError) then
- begin
- Pa_CloseStream(stream);
- exit;
- end;
-
- cbWorks := false;
- // check if the callback was called (poll for max. 200ms)
- for cbPolls := 1 to 20 do
- begin
- // if the test-callback was called it should be aborted now
- if (Pa_IsStreamActive(stream) = 0) then
- begin
- cbWorks := true;
- break;
- end;
- // not yet aborted, wait and try (poll) again
- Pa_Sleep(10);
- end;
-
- // finally abort the stream
- Pa_CloseStream(stream);
- {$IFEND}
-
- Result := cbWorks;
-end;
-
-end.