From 678cc132f942ff4d84a803550eedf96acc543bca Mon Sep 17 00:00:00 2001 From: tobigun Date: Sun, 23 May 2010 09:07:15 +0000 Subject: update to trunk rev. 2391 git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/branches/experimental@2401 b956fd51-792f-4845-bead-9b4dfca2ff2c --- cmake/src/media/UAudioInput_Portaudio.pas | 188 ++++++++++++++++++------------ 1 file changed, 115 insertions(+), 73 deletions(-) (limited to 'cmake/src/media/UAudioInput_Portaudio.pas') diff --git a/cmake/src/media/UAudioInput_Portaudio.pas b/cmake/src/media/UAudioInput_Portaudio.pas index 31d2882b..c7364eb4 100644 --- a/cmake/src/media/UAudioInput_Portaudio.pas +++ b/cmake/src/media/UAudioInput_Portaudio.pas @@ -45,11 +45,14 @@ uses portmixer, {$ENDIF} portaudio, + ctypes, UAudioCore_Portaudio, - URecord, + UUnicodeUtils, + UTextEncoding, UIni, ULog, - UMain; + UMain, + URecord; type TAudioInput_Portaudio = class(TAudioInputBase) @@ -57,7 +60,7 @@ type AudioCore: TAudioCore_Portaudio; function EnumDevices(): boolean; public - function GetName: String; override; + function GetName: string; override; function InitializeRecord: boolean; override; function FinalizeRecord: boolean; override; end; @@ -70,31 +73,76 @@ type {$ENDIF} PaDeviceIndex: TPaDeviceIndex; public - function Open(): boolean; + function Open(): boolean; function Close(): boolean; function Start(): boolean; override; - function Stop(): 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. + * If the string already is in UTF8 no conversion is performed, otherwise + * the local encoding is used. + *} +function ConvertPaStringToUTF8(const Str: RawByteString): UTF8String; +begin + if (IsUTF8String(Str)) then + Result := Str + else + Result := DecodeStringUTF8(Str, encLocale); +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; inputParams: TPaStreamParameters; deviceInfo: PPaDeviceInfo; + {$IFDEF UsePortmixer} + SourceIndex: integer; + {$ENDIF} begin Result := false; @@ -107,19 +155,19 @@ 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, AudioFormat.SampleRate, paFramesPerBufferUnspecified, paNoFlag, - @MicrophoneCallback, Pointer(Self)); - if(Error <> paNoError) then + @MicrophoneCallback, pointer(Self)); + if (Error <> paNoError) then begin Log.LogError('Error opening stream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Open'); Exit; @@ -155,7 +203,7 @@ end; function TPortaudioInputDevice.Start(): boolean; var - Error: TPaError; + Error: TPaError; begin Result := false; @@ -169,7 +217,7 @@ begin // start capture Error := Pa_StartStream(RecordStream); - if(Error <> paNoError) then + if (Error <> paNoError) then begin Log.LogError('Error starting stream: ' + Pa_GetErrorText(Error), 'TPortaudioInputDevice.Start'); Close(); @@ -268,34 +316,36 @@ end; function TAudioInput_Portaudio.EnumDevices(): boolean; var - i: integer; - paApiIndex: TPaHostApiIndex; - paApiInfo: PPaHostApiInfo; - deviceName: string; - deviceIndex: TPaDeviceIndex; - deviceInfo: PPaDeviceInfo; - channelCnt: integer; - SC: integer; // soundcard - err: TPaError; - errMsg: string; - paDevice: TPortaudioInputDevice; - inputParams: TPaStreamParameters; - stream: PPaStream; - streamInfo: PPaStreamInfo; - sampleRate: double; - latency: TPaTime; + i: integer; + deviceName: UTF8String; + paApiIndex: TPaHostApiIndex; + paApiInfo: PPaHostApiInfo; + paDeviceIndex:TPaDeviceIndex; + paDeviceInfo: PPaDeviceInfo; + channelCnt: integer; + deviceIndex: integer; + err: TPaError; + errMsg: string; + paDevice: TPortaudioInputDevice; + inputParams: TPaStreamParameters; + stream: PPaStream; + streamInfo: PPaStreamInfo; + sampleRate: double; + latency: TPaTime; {$IFDEF UsePortmixer} - mixer: PPxMixer; - sourceCnt: integer; - sourceIndex: integer; - sourceName: string; + mixer: PPxMixer; + sourceCnt: integer; + 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; // choose the best available Audio-API paApiIndex := AudioCore.GetPreferredApiIndex(); - if(paApiIndex = -1) then + if (paApiIndex = -1) then begin Log.LogError('No working Audio-API found', 'TAudioInput_Portaudio.EnumDevices'); Exit; @@ -303,17 +353,17 @@ begin paApiInfo := Pa_GetHostApiInfo(paApiIndex); - SC := 0; + deviceIndex := 0; // init array-size to max. input-devices count SetLength(AudioInputProcessor.DeviceList, paApiInfo^.deviceCount); for i:= 0 to High(AudioInputProcessor.DeviceList) do begin // convert API-specific device-index to global index - deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(paApiIndex, i); - deviceInfo := Pa_GetDeviceInfo(deviceIndex); + paDeviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(paApiIndex, i); + paDeviceInfo := Pa_GetDeviceInfo(paDeviceIndex); - channelCnt := deviceInfo^.maxInputChannels; + channelCnt := paDeviceInfo^.maxInputChannels; // current device is no input device -> skip if (channelCnt <= 0) then @@ -326,25 +376,25 @@ begin channelCnt := 2; paDevice := TPortaudioInputDevice.Create(); - AudioInputProcessor.DeviceList[SC] := paDevice; + AudioInputProcessor.DeviceList[deviceIndex] := paDevice; // retrieve device-name - deviceName := deviceInfo^.name; - paDevice.Name := deviceName; - paDevice.PaDeviceIndex := deviceIndex; + deviceName := ConvertPaStringToUTF8(paDeviceInfo^.name); + paDevice.Name := UnifyDeviceName(deviceName, deviceIndex); + paDevice.PaDeviceIndex := paDeviceIndex; - sampleRate := deviceInfo^.defaultSampleRate; + 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 := deviceInfo^.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 := deviceIndex; + device := paDeviceIndex; channelCount := channelCnt; sampleFormat := paInt16; suggestedLatency := latency; @@ -364,7 +414,7 @@ begin // open device for further info err := Pa_OpenStream(stream, @inputParams, nil, sampleRate, paFramesPerBufferUnspecified, paNoFlag, @MicrophoneTestCallback, nil); - if(err <> paNoError) then + if (err <> paNoError) then begin // unable to open device -> skip errMsg := Pa_GetErrorText(err); @@ -421,7 +471,7 @@ begin for sourceIndex := 1 to sourceCnt do begin sourceName := Px_GetInputSourceName(mixer, sourceIndex-1); - paDevice.Source[sourceIndex].Name := sourceName; + paDevice.Source[sourceIndex].Name := ConvertPaStringToUTF8(sourceName); end; Px_CloseMixer(mixer); @@ -430,48 +480,41 @@ begin // close test-stream Pa_CloseStream(stream); - Inc(SC); + Inc(deviceIndex); end; // adjust size to actual input-device count - SetLength(AudioInputProcessor.DeviceList, SC); + SetLength(AudioInputProcessor.DeviceList, deviceIndex); - Log.LogStatus('#Input-Devices: ' + inttostr(SC), 'Portaudio'); + Log.LogStatus('#Input-Devices: ' + inttostr(deviceIndex), 'Portaudio'); Result := true; end; function TAudioInput_Portaudio.InitializeRecord(): boolean; -var - err: TPaError; begin + Result := false; AudioCore := TAudioCore_Portaudio.GetInstance(); // initialize portaudio - err := Pa_Initialize(); - if(err <> paNoError) then - begin - Log.LogError(Pa_GetErrorText(err), 'TAudioInput_Portaudio.InitializeRecord'); - Result := false; - Exit; - end; - + if (not AudioCore.Initialize()) then + Exit; Result := EnumDevices(); end; function TAudioInput_Portaudio.FinalizeRecord: boolean; begin CaptureStop; - Pa_Terminate(); + AudioCore.Terminate(); Result := inherited FinalizeRecord(); 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; @@ -480,15 +523,14 @@ 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; end; - initialization MediaManager.add(TAudioInput_Portaudio.Create); -- cgit v1.2.3