From 27b3e332076162b37a7a53f059004d23ad69f9d2 Mon Sep 17 00:00:00 2001 From: tobigun Date: Tue, 27 Apr 2010 19:09:40 +0000 Subject: - device input latency is now configurable via config.ini - latency[i] determines the latency for device i in milliseconds or -1 for autodetection (default) - this is necessary as mic capturing with portaudio (on linux) gets stuck if latency is too low. Either because portaudio's latency autodetection does not work or because the mic capture callback takes too long before it returns. In both cases the user should set the latency to a value of 100 (ms). - better input device test, it should not remove working devices anymore. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/trunk@2313 b956fd51-792f-4845-bead-9b4dfca2ff2c --- src/base/UIni.pas | 7 ++++ src/base/URecord.pas | 1 + src/media/UAudioInput_Portaudio.pas | 65 +++++++++++++++++++++++++++---------- 3 files changed, 56 insertions(+), 17 deletions(-) diff --git a/src/base/UIni.pas b/src/base/UIni.pas index 3ed3f3d5..ec229c54 100644 --- a/src/base/UIni.pas +++ b/src/base/UIni.pas @@ -63,9 +63,13 @@ type TInputDeviceConfig = record Name: string; Input: integer; + Latency: integer; //**< latency in ms, or LATENCY_AUTODETECT for default ChannelToPlayerMap: array of integer; end; +const + LATENCY_AUTODETECT = -1; + type //Options @@ -640,6 +644,7 @@ begin DeviceCfg := @InputDeviceConfig[High(InputDeviceConfig)]; DeviceCfg.Name := IniFile.ReadString('Record', Format('DeviceName[%d]', [DeviceIndex]), ''); DeviceCfg.Input := IniFile.ReadInteger('Record', Format('Input[%d]', [DeviceIndex]), 0); + DeviceCfg.Latency := IniFile.ReadInteger('Record', Format('Latency[%d]', [DeviceIndex]), LATENCY_AUTODETECT); // find the largest channel-number of the current device in the ini-file ChannelCount := GetMaxKeyIndex(RecordKeys, 'Channel', Format('[%d]', [DeviceIndex])); @@ -678,6 +683,8 @@ begin InputDeviceConfig[DeviceIndex].Name); IniFile.WriteInteger('Record', Format('Input[%d]', [DeviceIndex+1]), InputDeviceConfig[DeviceIndex].Input); + IniFile.WriteInteger('Record', Format('Latency[%d]', [DeviceIndex+1]), + InputDeviceConfig[DeviceIndex].Latency); // Channel-to-Player Mapping for ChannelIndex := 0 to High(InputDeviceConfig[DeviceIndex].ChannelToPlayerMap) do diff --git a/src/base/URecord.pas b/src/base/URecord.pas index 09ac92de..245d85a6 100644 --- a/src/base/URecord.pas +++ b/src/base/URecord.pas @@ -577,6 +577,7 @@ begin deviceCfg.Name := Trim(device.Name); deviceCfg.Input := 0; + deviceCfg.Latency := LATENCY_AUTODETECT; channelCount := device.AudioFormat.Channels; SetLength(deviceCfg.ChannelToPlayerMap, channelCount); diff --git a/src/media/UAudioInput_Portaudio.pas b/src/media/UAudioInput_Portaudio.pas index 26919d42..92e549ff 100644 --- a/src/media/UAudioInput_Portaudio.pas +++ b/src/media/UAudioInput_Portaudio.pas @@ -45,6 +45,7 @@ uses portmixer, {$ENDIF} portaudio, + ctypes, UAudioCore_Portaudio, UUnicodeUtils, UTextEncoding, @@ -77,18 +78,19 @@ type function Start(): boolean; override; function Stop(): boolean; override; + function DetermineInputLatency(Info: PPaDeviceInfo): TPaTime; + function GetVolume(): single; override; procedure SetVolume(Volume: single); override; end; -function MicrophoneCallback(input: pointer; output: pointer; frameCount: longword; +function MicrophoneCallback(input: pointer; output: pointer; frameCount: culong; timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags; - inputDevice: pointer): integer; cdecl; forward; + inputDevice: pointer): cint; cdecl; forward; -function MicrophoneTestCallback(input: pointer; output: pointer; frameCount: longword; +function MicrophoneTestCallback(input: pointer; output: pointer; frameCount: culong; timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags; - inputDevice: pointer): integer; cdecl; forward; - + inputDevice: pointer): cint; cdecl; forward; {** * Converts a string returned by Portaudio into UTF8. @@ -106,6 +108,33 @@ end; { TPortaudioInputDevice } +function TPortaudioInputDevice.DetermineInputLatency(Info: PPaDeviceInfo): TPaTime; +begin + if (Ini.InputDeviceConfig[CfgIndex].Latency <> -1) then + begin + // autodetection off -> set user latency + Result := Ini.InputDeviceConfig[CfgIndex].Latency / 1000 + end + else + begin + // 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?) + // TODO: retry with input-latency set to 20ms (defaultLowInputLatency might + // not be set correctly in OSS) + + // FIXME: according to the portaudio headers defaultHighInputLatency (approx. 40ms) is + // for robust non-interactive applications and defaultLowInputLatency (approx. 15ms) + // for interactive performance. + // We need defaultLowInputLatency here but this setting is far too buggy. If the callback + // does not return quickly the stream will be stuck after returning from the callback + // and the callback will not be called anymore and mic-capturing stops. + // Audacity (in AudioIO.cpp) uses defaultHighInputLatency if software playthrough is on + // and even higher latencies (100ms) without playthrough so this should be ok for now. + //Result := Info^.defaultLowInputLatency; + Result := Info^.defaultHighInputLatency; + end; +end; + function TPortaudioInputDevice.Open(): boolean; var Error: TPaError; @@ -126,12 +155,12 @@ begin device := PaDeviceIndex; channelCount := AudioFormat.Channels; sampleFormat := paInt16; - suggestedLatency := deviceInfo^.defaultLowInputLatency; + suggestedLatency := DetermineInputLatency(deviceInfo); hostApiSpecificStreamInfo := nil; end; - //Log.LogStatus(deviceInfo^.name, 'Portaudio'); - //Log.LogStatus(floattostr(deviceInfo^.defaultLowInputLatency), 'Portaudio'); + Log.LogStatus('Open ' + deviceInfo^.name, 'Portaudio'); + Log.LogStatus('Latency of ' + deviceInfo^.name + ': ' + floatToStr(inputParams.suggestedLatency), 'Portaudio'); // open input stream Error := Pa_OpenStream(RecordStream, @inputParams, nil, @@ -309,6 +338,8 @@ var sourceIndex: integer; sourceName: UTF8String; {$ENDIF} +const + MIN_TEST_LATENCY = 100 / 1000; // min. test latency of 100 ms to avoid removal of working devices begin Result := false; @@ -354,13 +385,13 @@ begin sampleRate := paDeviceInfo^.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?) - latency := paDeviceInfo^.defaultLowInputLatency; + // use a stable (high) latency so we do not remove working devices + if (paDeviceInfo^.defaultHighInputLatency > MIN_TEST_LATENCY) then + latency := paDeviceInfo^.defaultHighInputLatency + else + latency := MIN_TEST_LATENCY; // setup desired input parameters - // TODO: retry with input-latency set to 20ms (defaultLowInputLatency might - // not be set correctly in OSS) with inputParams do begin device := paDeviceIndex; @@ -488,9 +519,9 @@ end; {* * Portaudio input capture callback. *} -function MicrophoneCallback(input: pointer; output: pointer; frameCount: longword; +function MicrophoneCallback(input: pointer; output: pointer; frameCount: culong; timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags; - inputDevice: pointer): integer; cdecl; + inputDevice: pointer): cint; cdecl; begin AudioInputProcessor.HandleMicrophoneData(input, frameCount*4, inputDevice); result := paContinue; @@ -499,9 +530,9 @@ end; {* * Portaudio test capture callback. *} -function MicrophoneTestCallback(input: pointer; output: pointer; frameCount: longword; +function MicrophoneTestCallback(input: pointer; output: pointer; frameCount: culong; timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags; - inputDevice: pointer): integer; cdecl; + inputDevice: pointer): cint; cdecl; begin // this callback is called only once result := paAbort; -- cgit v1.2.3