From 4c6d2f4cd49a4f6016026ef81db31c3656bb5e8c Mon Sep 17 00:00:00 2001 From: tobigun Date: Sat, 13 Sep 2008 08:27:50 +0000 Subject: Media modules moved from base to media git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1374 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioCore_Portaudio.pas | 257 +++++++++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 src/media/UAudioCore_Portaudio.pas (limited to 'src/media/UAudioCore_Portaudio.pas') diff --git a/src/media/UAudioCore_Portaudio.pas b/src/media/UAudioCore_Portaudio.pas new file mode 100644 index 00000000..cd279d99 --- /dev/null +++ b/src/media/UAudioCore_Portaudio.pas @@ -0,0 +1,257 @@ +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(DARWIN)} + array[0..0] of TPaHostApiTypeId = ( paDefaultApi ); // paCoreAudio +{$ELSEIF Defined(UNIX)} + // Note: Portmixer has no mixer support for JACK at the moment + array[0..2] of TPaHostApiTypeId = ( paALSA, paJACK, paOSS ); +{$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. -- cgit v1.2.3 From f16756422a5dbb24ce1b751bb9e2bb1de4f19713 Mon Sep 17 00:00:00 2001 From: tobigun Date: Tue, 23 Sep 2008 21:17:50 +0000 Subject: added file headers git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1404 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioCore_Portaudio.pas | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) (limited to 'src/media/UAudioCore_Portaudio.pas') diff --git a/src/media/UAudioCore_Portaudio.pas b/src/media/UAudioCore_Portaudio.pas index cd279d99..30773b07 100644 --- a/src/media/UAudioCore_Portaudio.pas +++ b/src/media/UAudioCore_Portaudio.pas @@ -1,3 +1,28 @@ +{* UltraStar Deluxe - Karaoke Game + * + * UltraStar Deluxe is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + *} + unit UAudioCore_Portaudio; interface -- cgit v1.2.3 From d397c0364763dbf0449adb2c8568377bba62d7fa Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Sat, 27 Sep 2008 23:28:57 +0000 Subject: remove some white space. no code change git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@1416 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioCore_Portaudio.pas | 1 - 1 file changed, 1 deletion(-) (limited to 'src/media/UAudioCore_Portaudio.pas') diff --git a/src/media/UAudioCore_Portaudio.pas b/src/media/UAudioCore_Portaudio.pas index 30773b07..25ceae3c 100644 --- a/src/media/UAudioCore_Portaudio.pas +++ b/src/media/UAudioCore_Portaudio.pas @@ -33,7 +33,6 @@ interface {$I ../switches.inc} - uses Classes, SysUtils, -- cgit v1.2.3 From d9a216be7770eb5003f03a3f0856920643425f54 Mon Sep 17 00:00:00 2001 From: k-m_schindler Date: Thu, 25 Feb 2010 11:28:04 +0000 Subject: cosmetics. no code change. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2156 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioCore_Portaudio.pas | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) (limited to 'src/media/UAudioCore_Portaudio.pas') diff --git a/src/media/UAudioCore_Portaudio.pas b/src/media/UAudioCore_Portaudio.pas index 25ceae3c..1c7e3ef5 100644 --- a/src/media/UAudioCore_Portaudio.pas +++ b/src/media/UAudioCore_Portaudio.pas @@ -44,7 +44,7 @@ type constructor Create(); class function GetInstance(): TAudioCore_Portaudio; function GetPreferredApiIndex(): TPaHostApiIndex; - function TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean; + function TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: double): boolean; end; implementation @@ -103,7 +103,7 @@ end; function TAudioCore_Portaudio.GetPreferredApiIndex(): TPaHostApiIndex; var - i: integer; + i: integer; apiIndex: TPaHostApiIndex; apiInfo: PPaHostApiInfo; begin @@ -112,11 +112,11 @@ begin // select preferred sound-API for i:= 0 to High(ApiPreferenceOrder) do begin - if(ApiPreferenceOrder[i] <> paDefaultApi) then + if (ApiPreferenceOrder[i] <> paDefaultApi) then begin // check if API is available apiIndex := Pa_HostApiTypeIdToHostApiIndex(ApiPreferenceOrder[i]); - if(apiIndex >= 0) then + 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 @@ -132,7 +132,7 @@ begin end; // None of the preferred APIs is available -> use default - if(result < 0) then + if (result < 0) then begin result := Pa_GetDefaultHostApi(); end; @@ -141,9 +141,9 @@ end; {* * Portaudio test callback used by TestDevice(). *} -function TestCallback(input: Pointer; output: Pointer; frameCount: Longword; +function TestCallback(input: pointer; output: pointer; frameCount: longword; timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags; - inputDevice: Pointer): Integer; cdecl; + inputDevice: pointer): integer; cdecl; begin // this callback is called only once result := paAbort; @@ -189,15 +189,15 @@ end; * 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; +function TAudioCore_Portaudio.TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: double): boolean; +const + altSampleRates: array[0..1] of double = (44100, 48000); // alternative sample-rates var - stream: PPaStream; - err: TPaError; + stream: PPaStream; + err: TPaError; cbWorks: boolean; cbPolls: integer; - i: integer; -const - altSampleRates: array[0..1] of Double = (44100, 48000); // alternative sample-rates + i: integer; begin Result := false; @@ -206,7 +206,7 @@ begin // check if device supports our input-format err := Pa_IsFormatSupported(inParams, outParams, sampleRate); - if(err <> paNoError) then + if (err <> paNoError) then begin // we cannot fix the error -> exit if (err <> paInvalidSampleRate) then @@ -244,14 +244,14 @@ begin err := Pa_OpenStream(stream, inParams, outParams, sampleRate, paFramesPerBufferUnspecified, paNoFlag, @TestCallback, nil); - if(err <> paNoError) then + if (err <> paNoError) then begin exit; end; // start the callback err := Pa_StartStream(stream); - if(err <> paNoError) then + if (err <> paNoError) then begin Pa_CloseStream(stream); exit; -- cgit v1.2.3 From c61600bfdda6608feb6a390d20e2e1200afe93f6 Mon Sep 17 00:00:00 2001 From: tobigun Date: Fri, 30 Apr 2010 13:49:10 +0000 Subject: portaudio playback (not used by default) segfault fixed git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2321 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/media/UAudioCore_Portaudio.pas | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) (limited to 'src/media/UAudioCore_Portaudio.pas') diff --git a/src/media/UAudioCore_Portaudio.pas b/src/media/UAudioCore_Portaudio.pas index 1c7e3ef5..c97b5d10 100644 --- a/src/media/UAudioCore_Portaudio.pas +++ b/src/media/UAudioCore_Portaudio.pas @@ -40,9 +40,13 @@ uses type TAudioCore_Portaudio = class + private + InitCount: integer; ///< keeps track of the number of Initialize/Terminate calls public constructor Create(); class function GetInstance(): TAudioCore_Portaudio; + function Initialize(): boolean; + function Terminate(): boolean; function GetPreferredApiIndex(): TPaHostApiIndex; function TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: double): boolean; end; @@ -92,6 +96,7 @@ var constructor TAudioCore_Portaudio.Create(); begin inherited; + InitCount := 0; end; class function TAudioCore_Portaudio.GetInstance(): TAudioCore_Portaudio; @@ -101,6 +106,57 @@ begin Result := Instance; end; +function TAudioCore_Portaudio.Initialize(): boolean; +var + Err: TPaError; +begin + // initialize only once + if (InitCount > 0) then + begin + Inc(InitCount); + Result := true; + Exit; + end; + + // init Portaudio + Err := Pa_Initialize(); + if (Err <> paNoError) then + begin + Log.LogError(Pa_GetErrorText(Err), 'TAudioCore_Portaudio.Initialize'); + Result := false; + Exit; + end; + + // only increment on success + Inc(InitCount); + Result := true; +end; + +function TAudioCore_Portaudio.Terminate(): boolean; +var + Err: TPaError; +begin + // decrement usage count + Dec(InitCount); + if (InitCount > 0) then + begin + // do not terminate yet + Result := true; + Exit; + end; + + // terminate if usage count is 0 + Err := Pa_Terminate(); + if (Err <> paNoError) then + begin + Log.LogError(Pa_GetErrorText(Err), 'TAudioCore_Portaudio.Terminate'); + Result := false; + Exit; + end; + + Result := true; +end; + function TAudioCore_Portaudio.GetPreferredApiIndex(): TPaHostApiIndex; var i: integer; -- cgit v1.2.3