aboutsummaryrefslogtreecommitdiffstats
path: root/Game/Code/Classes
diff options
context:
space:
mode:
Diffstat (limited to 'Game/Code/Classes')
-rw-r--r--Game/Code/Classes/UAudioCore_Portaudio.pas141
-rw-r--r--Game/Code/Classes/UAudioInput_Bass.pas2
-rw-r--r--Game/Code/Classes/UAudioInput_Portaudio.pas66
-rw-r--r--Game/Code/Classes/UAudioPlayback_Portaudio.pas33
-rw-r--r--Game/Code/Classes/UAudioPlayback_SoftMixer.pas4
-rw-r--r--Game/Code/Classes/UMusic.pas31
-rw-r--r--Game/Code/Classes/UVideo.pas4
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);