diff options
Diffstat (limited to 'src/Classes/UAudioCore_Portaudio.pas')
-rw-r--r-- | src/Classes/UAudioCore_Portaudio.pas | 257 |
1 files changed, 0 insertions, 257 deletions
diff --git a/src/Classes/UAudioCore_Portaudio.pas b/src/Classes/UAudioCore_Portaudio.pas deleted file mode 100644 index bcc8a001..00000000 --- a/src/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. |