diff options
Diffstat (limited to 'Game/Code/Classes')
-rw-r--r-- | Game/Code/Classes/UAudioCore_Portaudio.pas | 141 | ||||
-rw-r--r-- | Game/Code/Classes/UAudioInput_Bass.pas | 2 | ||||
-rw-r--r-- | Game/Code/Classes/UAudioInput_Portaudio.pas | 66 | ||||
-rw-r--r-- | Game/Code/Classes/UAudioPlayback_Portaudio.pas | 33 | ||||
-rw-r--r-- | Game/Code/Classes/UAudioPlayback_SoftMixer.pas | 4 | ||||
-rw-r--r-- | Game/Code/Classes/UMusic.pas | 31 | ||||
-rw-r--r-- | Game/Code/Classes/UVideo.pas | 4 |
7 files changed, 203 insertions, 78 deletions
diff --git a/Game/Code/Classes/UAudioCore_Portaudio.pas b/Game/Code/Classes/UAudioCore_Portaudio.pas index 952b211b..bb0635b3 100644 --- a/Game/Code/Classes/UAudioCore_Portaudio.pas +++ b/Game/Code/Classes/UAudioCore_Portaudio.pas @@ -18,6 +18,7 @@ type TAudioCore_Portaudio = class public class function GetPreferredApiIndex(): TPaHostApiIndex; + class function TestDevice(inParams, outParams: PPaStreamParameters; var sampleRate: Double): boolean; end; implementation @@ -96,4 +97,144 @@ begin 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. + *) +class 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. diff --git a/Game/Code/Classes/UAudioInput_Bass.pas b/Game/Code/Classes/UAudioInput_Bass.pas index cfb77038..a62ff22e 100644 --- a/Game/Code/Classes/UAudioInput_Bass.pas +++ b/Game/Code/Classes/UAudioInput_Bass.pas @@ -114,7 +114,7 @@ begin end; // start capturing - RecordStream := BASS_RecordStart(AudioFormat.SampleRate, AudioFormat.Channels, + RecordStream := BASS_RecordStart(Round(AudioFormat.SampleRate), AudioFormat.Channels, MakeLong(flags, latency), @MicrophoneCallback, DeviceIndex); if (RecordStream = 0) then diff --git a/Game/Code/Classes/UAudioInput_Portaudio.pas b/Game/Code/Classes/UAudioInput_Portaudio.pas index 2883ccb0..90ac41b1 100644 --- a/Game/Code/Classes/UAudioInput_Portaudio.pas +++ b/Game/Code/Classes/UAudioInput_Portaudio.pas @@ -145,7 +145,7 @@ var inputParams: TPaStreamParameters; stream: PPaStream; streamInfo: PPaStreamInfo; - sampleRate: integer; + sampleRate: double; latency: TPaTime; {$IFDEF UsePortmixer} sourceIndex: integer; @@ -206,10 +206,7 @@ begin paDevice.Description := deviceName; paDevice.PaDeviceIndex := deviceIndex; - if (deviceInfo^.defaultSampleRate > 0) then - sampleRate := Trunc(deviceInfo^.defaultSampleRate) - else - sampleRate := 44100; + sampleRate := deviceInfo^.defaultSampleRate; // on vista and xp the defaultLowInputLatency may be set to 0 but it works. // TODO: correct too low latencies (what is a too low latency, maybe < 10ms?) @@ -225,21 +222,17 @@ begin hostApiSpecificStreamInfo := nil; end; - // check if device supports our input-format - // TODO: retry with input-latency set to 20ms (defaultLowInputLatency might - // not be set correctly in OSS) - err := Pa_IsFormatSupported(@inputParams, nil, sampleRate); - if(err <> 0) then + // check souncard and adjust sample-rate + if (not TAudioCore_Portaudio.TestDevice(@inputParams, nil, sampleRate)) then begin - // format not supported -> skip - errMsg := Pa_GetErrorText(err); - Log.LogError('Device error: "'+ deviceName +'" ('+ errMsg +')', - 'TAudioInput_Portaudio.InitializeRecord'); + // ignore device if it does not work + Log.LogError('Device "'+paDevice.Description+'" does not work', + 'TAudioInput_Portaudio.EnumDevices'); paDevice.Free(); continue; end; - // check if the device really works + // open device for further info err := Pa_OpenStream(stream, @inputParams, nil, sampleRate, paFramesPerBufferUnspecified, paNoFlag, @MicrophoneTestCallback, nil); if(err <> paNoError) then @@ -247,40 +240,7 @@ begin // unable to open device -> skip errMsg := Pa_GetErrorText(err); Log.LogError('Device error: "'+ deviceName +'" ('+ errMsg +')', - 'TAudioInput_Portaudio.InitializeRecord'); - paDevice.Free(); - continue; - end; - - - // check if mic-callback works (might not be called on some devices) - - // start the callback - Pa_StartStream(stream); - - 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 (Note: Pa_StopStream might hang here) - Pa_AbortStream(stream); - - // ignore device if callback did not work - if (not cbWorks) then - begin - Log.LogError('Device "'+paDevice.Description+'" does not respond', - 'TAudioInput_Portaudio.InitializeRecord'); - Pa_CloseStream(stream); + 'TAudioInput_Portaudio.EnumDevices'); paDevice.Free(); continue; end; @@ -289,12 +249,12 @@ begin streamInfo := Pa_GetStreamInfo(stream); if (streamInfo <> nil) then begin - if (sampleRate <> Trunc(streamInfo^.sampleRate)) then + if (sampleRate <> streamInfo^.sampleRate) then begin - Log.LogStatus('Portaudio changed Samplerate from ' + IntToStr(sampleRate) + + Log.LogStatus('Portaudio changed Samplerate from ' + FloatToStr(sampleRate) + ' to ' + FloatToStr(streamInfo^.sampleRate), 'TAudioInput_Portaudio.InitializeRecord'); - sampleRate := Trunc(streamInfo^.sampleRate); + sampleRate := streamInfo^.sampleRate; end; end; @@ -308,7 +268,7 @@ begin Log.LogStatus('InputDevice "'+paDevice.Description+'"@' + IntToStr(paDevice.AudioFormat.Channels)+'x'+ - IntToStr(paDevice.AudioFormat.SampleRate)+'Hz ('+ + FloatToStr(paDevice.AudioFormat.SampleRate)+'Hz ('+ FloatTostr(inputParams.suggestedLatency)+'sec)' , 'Portaudio.InitializeRecord'); diff --git a/Game/Code/Classes/UAudioPlayback_Portaudio.pas b/Game/Code/Classes/UAudioPlayback_Portaudio.pas index 0f4eb7ae..431fbd43 100644 --- a/Game/Code/Classes/UAudioPlayback_Portaudio.pas +++ b/Game/Code/Classes/UAudioPlayback_Portaudio.pas @@ -66,27 +66,24 @@ var paOutDevice : TPaDeviceIndex;
paOutDeviceInfo : PPaDeviceInfo;
err : TPaError;
- sampleRate : integer;
+ sampleRate : double;
begin
result := false;
Pa_Initialize();
paApiIndex := TAudioCore_Portaudio.GetPreferredApiIndex();
- if(paApiIndex = -1) then - begin - Log.LogError('No working Audio-API found', 'TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine'); - Exit; - end; + if(paApiIndex = -1) then
+ begin
+ Log.LogError('No working Audio-API found', 'TAudioPlayback_Portaudio.InitializeAudioPlaybackEngine');
+ Exit;
+ end;
paApiInfo := Pa_GetHostApiInfo(paApiIndex);
paOutDevice := paApiInfo^.defaultOutputDevice;
paOutDeviceInfo := Pa_GetDeviceInfo(paOutDevice);
- if (paOutDeviceInfo^.defaultSampleRate > 0) then
- sampleRate := Trunc(paOutDeviceInfo^.defaultSampleRate)
- else
- sampleRate := 44100;
+ sampleRate := paOutDeviceInfo^.defaultSampleRate;
with paOutParams do begin
device := paOutDevice;
@@ -96,14 +93,24 @@ begin hostApiSpecificStreamInfo := nil;
end;
+ // check souncard and adjust sample-rate
+ if not TAudioCore_Portaudio.TestDevice(nil, @paOutParams, sampleRate) then
+ begin
+ Log.LogStatus('TestDevice failed!', 'TAudioPlayback_Portaudio.OpenDevice');
+ exit;
+ end;
+
+ // open output stream
err := Pa_OpenStream(paStream, nil, @paOutParams, sampleRate,
paFramesPerBufferUnspecified,
paNoFlag, @PortaudioAudioCallback, Self);
- if(err <> paNoError) then begin
- Log.LogStatus('Pa_OpenStream: '+Pa_GetErrorText(err), 'UAudioPlayback_Portaudio');
+ if(err <> paNoError) then
+ begin
+ Log.LogStatus(Pa_GetErrorText(err), 'TAudioPlayback_Portaudio.OpenDevice');
+ paStream := nil;
exit;
end;
-
+
FormatInfo := TAudioFormatInfo.Create(
paOutParams.channelCount,
sampleRate,
diff --git a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas index 7c7a09aa..f65b3d9a 100644 --- a/Game/Code/Classes/UAudioPlayback_SoftMixer.pas +++ b/Game/Code/Classes/UAudioPlayback_SoftMixer.pas @@ -363,8 +363,8 @@ begin end; if (SDL_BuildAudioCVT(@cvt, - srcFormat, srcFormatInfo.Channels, srcFormatInfo.SampleRate, - dstFormat, dstFormatInfo.Channels, dstFormatInfo.SampleRate) = -1) then + srcFormat, srcFormatInfo.Channels, Round(srcFormatInfo.SampleRate), + dstFormat, dstFormatInfo.Channels, Round(dstFormatInfo.SampleRate)) = -1) then begin Log.LogError(SDL_GetError(), 'TSoftMixerPlaybackStream.InitFormatConversion'); Exit; diff --git a/Game/Code/Classes/UMusic.pas b/Game/Code/Classes/UMusic.pas index c389574b..8efb8be7 100644 --- a/Game/Code/Classes/UMusic.pas +++ b/Game/Code/Classes/UMusic.pas @@ -117,11 +117,11 @@ type TAudioFormatInfo = class public Channels : byte; - SampleRate : integer; + SampleRate : double; Format : TAudioSampleFormat; FrameSize : integer; // calculated on construction - constructor Create(Channels: byte; SampleRate: integer; Format: TAudioSampleFormat); + constructor Create(Channels: byte; SampleRate: double; Format: TAudioSampleFormat); end; type @@ -273,6 +273,9 @@ type constructor Create(); destructor Destroy(); override; + + procedure LoadSounds(); + procedure UnloadSounds(); end; var // TODO : JB --- THESE SHOULD NOT BE GLOBAL @@ -302,6 +305,7 @@ uses sysutils, UMain, UCommandLine, + URecord, ULog; var @@ -314,7 +318,7 @@ var singleton_AudioManager : TInterfaceList = nil; -constructor TAudioFormatInfo.Create(Channels: byte; SampleRate: integer; Format: TAudioSampleFormat); +constructor TAudioFormatInfo.Create(Channels: byte; SampleRate: double; Format: TAudioSampleFormat); begin Self.Channels := Channels; Self.SampleRate := SampleRate; @@ -476,12 +480,26 @@ begin end; end; + +{ TSoundLibrary } + constructor TSoundLibrary.Create(); begin - //Log.LogStatus('Loading Sounds', 'Music Initialize'); + LoadSounds(); +end; + +destructor TSoundLibrary.Destroy(); +begin + UnloadSounds(); +end; +procedure TSoundLibrary.LoadSounds(); +begin + //Log.LogStatus('Loading Sounds', 'Music Initialize'); //Log.BenchmarkStart(4); + UnloadSounds(); + Start := AudioPlayback.OpenSound(SoundPath + 'Common start.mp3'); Back := AudioPlayback.OpenSound(SoundPath + 'Common back.mp3'); Swoosh := AudioPlayback.OpenSound(SoundPath + 'menu swoosh.mp3'); @@ -499,7 +517,7 @@ begin //Log.LogBenchmark('--> Loading Sounds', 4); end; -destructor TSoundLibrary.Destroy(); +procedure TSoundLibrary.UnloadSounds(); begin Start.Free; Back.Free; @@ -515,11 +533,10 @@ begin //Shuffle.Free; end; - initialization begin singleton_AudioManager := TInterfaceList.Create(); - + end; finalization diff --git a/Game/Code/Classes/UVideo.pas b/Game/Code/Classes/UVideo.pas index 5faf3a06..17eb38d3 100644 --- a/Game/Code/Classes/UVideo.pas +++ b/Game/Code/Classes/UVideo.pas @@ -618,9 +618,9 @@ begin TexX, TexY, integer(PIX_FMT_RGB24),
SWS_FAST_BILINEAR, nil, nil, nil);
if SoftwareScaleContext <> Nil then
- writeln('got swscale context')
+ debugwriteln('got swscale context')
else begin
- writeln('ERROR: didnīt get swscale context');
+ debugwriteln('ERROR: didn''t get swscale context');
av_free(AVFrameRGB);
av_free(AVFrame);
avcodec_close(VideoCodecContext);
|