aboutsummaryrefslogtreecommitdiffstats
path: root/test/TestPortAudioDevice.pas
diff options
context:
space:
mode:
Diffstat (limited to 'test/TestPortAudioDevice.pas')
-rw-r--r--test/TestPortAudioDevice.pas528
1 files changed, 528 insertions, 0 deletions
diff --git a/test/TestPortAudioDevice.pas b/test/TestPortAudioDevice.pas
new file mode 100644
index 00000000..ba394383
--- /dev/null
+++ b/test/TestPortAudioDevice.pas
@@ -0,0 +1,528 @@
+{* 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$
+ *}
+
+program TestPortAudioDevice;
+
+{* TestPortAudioDevice does some basic tests of the portaudio libs.
+ * If all works, it lists all audio input and output devices and their
+ * characteristics. Compile and run with simple commands.
+ *}
+
+{$IFDEF FPC}
+ {$MODE Delphi}
+{$ENDIF}
+
+uses
+ SysUtils,
+ ctypes,
+ crt,
+ math,
+ PortAudio in '../src/lib/portaudio/portaudio.pas';
+
+const
+ paDefaultApi = -1;
+
+ 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}
+
+ standardSampleRates: array[1..13] of cdouble =
+ ( 8000.0, 9600.0, 11025.0, 12000.0, 16000.0,
+ 22050.0, 24000.0, 32000.0, 44100.0, 48000.0,
+ 88200.0, 96000.0, 192000.0
+ );
+
+ SampleFormat: array[1..8] of culong =
+ (paFloat32, paInt32, paInt24, paInt16, paInt8, paUInt8,
+ paCustomFormat, paNonInterleaved
+ );
+ SampleFormatName: array[1..8] of string =
+ ('paFloat32', 'paInt32', 'paInt24', 'paInt16', 'paInt8', 'paUInt8',
+ 'paCustomFormat', 'paNonInterleaved'
+ );
+
+var
+ i, j: integer;
+ PaError: TPaError;
+ paApiIndex: TPaHostApiIndex;
+ paApiInfo: PPaHostApiInfo;
+ deviceIndex: TPaDeviceIndex;
+ deviceInfo: PPaDeviceInfo;
+ inputParameters: PPaStreamParameters;
+ outputParameters: PPaStreamParameters;
+ sampleRate: cdouble;
+ stream: PPaStream;
+ framesPerBuffer: culong;
+ streamFlags: TPaStreamFlags;
+ streamCallback: PPaStreamCallback;
+ callbackStartTime: TDateTime;
+ callbackWorks: boolean;
+ userData: Pointer;
+
+
+function 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;
+
+{
+type
+ TAudioSampleFormat = (
+ asfU8, asfS8, // unsigned/signed 8 bits
+ asfU16LSB, asfS16LSB, // unsigned/signed 16 bits (endianness: LSB)
+ asfU16MSB, asfS16MSB, // unsigned/signed 16 bits (endianness: MSB)
+ asfU16, asfS16, // unsigned/signed 16 bits (endianness: System)
+ asfS32, // signed 32 bits (endianness: System)
+ asfFloat, // float
+ asfDouble // double
+ );
+ TAudioFormatInfo = ;
+ TAudioInputDevice = record
+ AudioFormat: TAudioFormatInfo; // capture format info (e.g. 44.1kHz SInt16 stereo)
+ CaptureChannel: array of TCaptureBuffer; // sound-buffer references used for mono or stereo channel's capture data
+ end;
+
+procedure HandleMicrophoneData(Buffer: PByteArray; Size: integer; InputDevice: TAudioInputDevice);
+var
+ MultiChannelBuffer: PByteArray; // buffer handled as array of bytes (offset relative to channel)
+ SingleChannelBuffer: PByteArray; // temporary buffer for new samples per channel
+ SingleChannelBufferSize: integer;
+ ChannelIndex: integer;
+ CaptureChannel: TCaptureBuffer;
+ AudioFormat: TAudioFormatInfo;
+ SampleSize: integer;
+ SamplesPerChannel: integer;
+ i: integer;
+begin
+ AudioFormat := InputDevice.AudioFormat;
+ SampleSize := AudioSampleSize[AudioFormat.Format];
+ SamplesPerChannel := Size div AudioFormat.FrameSize;
+
+ SingleChannelBufferSize := SamplesPerChannel * SampleSize;
+ GetMem(SingleChannelBuffer, SingleChannelBufferSize);
+
+ // process channels
+ for ChannelIndex := 0 to High(InputDevice.CaptureChannel) do
+ begin
+ CaptureChannel := InputDevice.CaptureChannel[ChannelIndex];
+ // check if a capture buffer was assigned, otherwise there is nothing to do
+ if (CaptureChannel <> nil) then
+ begin
+ // set offset according to channel index
+ MultiChannelBuffer := @Buffer[ChannelIndex * SampleSize];
+ // separate channel-data from interleaved multi-channel (e.g. stereo) data
+ for i := 0 to SamplesPerChannel-1 do
+ begin
+ Move(MultiChannelBuffer[i*AudioFormat.FrameSize],
+ SingleChannelBuffer[i*SampleSize],
+ SampleSize);
+ end;
+ CaptureChannel.ProcessNewBuffer(SingleChannelBuffer, SingleChannelBufferSize);
+ end;
+ end;
+
+ FreeMem(SingleChannelBuffer);
+end;
+}
+
+procedure TestInitTerminate();
+begin
+ writeln ('*** Test of Pa_Initialize and Pa_Terminate ***');
+ PaError := Pa_Initialize;
+ if PaError = paNoError then
+ writeln ('Pa_Initialize: No error')
+ else
+ writeln ('Pa_Initialize: Error No ', PaError);
+
+ PaError := Pa_Terminate;
+ if PaError = paNoError then
+ writeln ('Pa_Terminate: No error')
+ else
+ writeln ('Pa_Terminate: Error No: ', PaError);
+ writeln;
+end;
+
+procedure TestErrorText();
+begin
+ writeln ('*** Test of Pa_GetErrorText ***');
+ PaError := Pa_Initialize;
+ writeln ('paNoError (0): ', Pa_GetErrorText(PaError));
+ writeln;
+ writeln ('Code Text');
+ writeln ('------------------------------------');
+ i := paNotInitialized;
+ repeat
+ writeln (i:6, ' ', Pa_GetErrorText(i));
+ i := succ(i);
+ until SameText(Pa_GetErrorText(i), 'Invalid error code') or (i = paNotInitialized + 100);
+ writeln (i:6, ' ', Pa_GetErrorText(i));
+ PaError := Pa_Terminate;
+ writeln;
+end;
+
+procedure TestVersion();
+begin
+ writeln ('*** Test of Pa_GetVersion and Pa_GetVersionText ***');
+ PaError := Pa_Initialize;
+ writeln ('Pa_GetVersion: ', Pa_GetVersion);
+ writeln ('Pa_GetVersionText: ', Pa_GetVersionText);
+ PaError := Pa_Terminate;
+ writeln;
+end;
+
+procedure TestApiInfo();
+begin
+ writeln ('*** Test of GetPreferredApiIndex ***');
+ PaError := Pa_Initialize;
+ paApiIndex := GetPreferredApiIndex();
+ if (paApiIndex = -1) then
+ writeln ('GetPreferredApiIndex: No working Audio-API found.')
+ else
+ writeln ('GetPreferredApiIndex: working Audio-API found. No: ', paApiIndex);
+ PaError := Pa_Terminate;
+ writeln;
+
+ writeln ('*** Test of Pa_GetHostApiInfo ***');
+ PaError := Pa_Initialize;
+ paApiIndex := GetPreferredApiIndex();
+ paApiInfo := Pa_GetHostApiInfo(paApiIndex);
+ writeln ('Pa_GetHostApiInfo:');
+ writeln ('paApiInfo.structVersion: ', paApiInfo.structVersion);
+ writeln ('paApiInfo._type: ', paApiInfo._type);
+ writeln ('paApiInfo.name: ', paApiInfo.name);
+ writeln ('paApiInfo.deviceCount: ', paApiInfo.deviceCount);
+ writeln ('paApiInfo.defaultInputDevice: ', paApiInfo.defaultInputDevice);
+ writeln ('paApiInfo.defaultOutputDevice: ', paApiInfo.defaultOutputDevice);
+ PaError := Pa_Terminate;
+ writeln;
+
+ writeln ('*** Test of Pa_HostApiDeviceIndexToDeviceIndex ***');
+ PaError := Pa_Initialize;
+ paApiIndex := GetPreferredApiIndex();
+ paApiInfo := Pa_GetHostApiInfo(paApiIndex);
+ for i:= 0 to paApiInfo^.deviceCount-1 do
+ begin
+ deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(paApiIndex, i);
+ writeln ('deviceIndex[', i, ']: ', deviceIndex);
+ end;
+ PaError := Pa_Terminate;
+ writeln;
+end;
+
+procedure TestDeviceInfo();
+begin
+ writeln ('*** Test of Pa_GetDeviceCount ***');
+ PaError := Pa_Initialize;
+ writeln ('Pa_GetDeviceCount: ', Pa_GetDeviceCount);
+ PaError := Pa_Terminate;
+ writeln;
+
+ writeln ('*** Test of Pa_GetDefaultInputDevice ***');
+ PaError := Pa_Initialize;
+ writeln ('Pa_GetDefaultInputDevice: ', Pa_GetDefaultInputDevice);
+ PaError := Pa_Terminate;
+ writeln;
+
+ writeln ('*** Test of Pa_GetDefaultOutputDevice ***');
+ PaError := Pa_Initialize;
+ writeln ('Pa_GetDefaultOutputDevice: ', Pa_GetDefaultOutputDevice);
+ PaError := Pa_Terminate;
+ writeln;
+
+ writeln ('*** Test of Pa_GetDeviceInfo ***');
+// Note: the fields of deviceInfo can also be used without the '^'.
+// deviceInfo.name works as well as deviceInfo^.name
+ PaError := Pa_Initialize;
+ paApiIndex := GetPreferredApiIndex();
+ paApiInfo := Pa_GetHostApiInfo(paApiIndex);
+ for i:= 0 to paApiInfo^.deviceCount - 1 do
+ begin
+ deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(paApiIndex, i);
+ deviceInfo := Pa_GetDeviceInfo(deviceIndex);
+ writeln ('deviceInfo[', i, '].name: ', deviceInfo^.name);
+ writeln ('deviceInfo[', i, '].structVersion: ', deviceInfo^.structVersion, ' (should be 2)');
+ writeln ('deviceInfo[', i, '].hostApi: ', deviceInfo^.hostApi);
+ writeln ('deviceInfo[', i, '].maxInputChannels: ', deviceInfo^.maxInputChannels);
+ writeln ('deviceInfo[', i, '].maxOutputChannels: ', deviceInfo^.maxOutputChannels);
+ writeln ('deviceInfo[', i, '].defaultLowInputLatency: ', deviceInfo^.defaultLowInputLatency:6:4);
+ writeln ('deviceInfo[', i, '].defaultLowOutputLatency: ', deviceInfo^.defaultLowOutputLatency:6:4);
+ writeln ('deviceInfo[', i, '].defaultHighInputLatency: ', deviceInfo^.defaultHighInputLatency:6:4);
+ writeln ('deviceInfo[', i, '].defaultHighOutputLatency: ', deviceInfo^.defaultHighOutputLatency:6:4);
+ writeln ('deviceInfo[', i, '].defaultSampleRate: ', deviceInfo^.defaultSampleRate:5:0);
+ writeln;
+ end;
+ PaError := Pa_Terminate;
+end;
+
+procedure TestFormatInfo();
+begin
+ writeln ('*** Test of Pa_IsFormatSupported ***');
+ PaError := Pa_Initialize;
+ paApiIndex := GetPreferredApiIndex();
+ paApiInfo := Pa_GetHostApiInfo(paApiIndex);
+ for i:= 0 to paApiInfo^.deviceCount - 1 do
+ begin
+ deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(paApiIndex, i);
+ deviceInfo := Pa_GetDeviceInfo(deviceIndex);
+ writeln ('Device[', i, '] ', deviceInfo^.name, ':');
+ New(inputParameters);
+ New(outputParameters);
+
+ if deviceInfo^.maxInputChannels > 0 then
+ begin
+ inputParameters^.device := deviceIndex;
+ inputParameters^.channelCount := deviceInfo^.maxInputChannels;
+ inputParameters^.sampleFormat := paInt16;
+ inputParameters^.suggestedLatency := 0;
+ inputParameters^.hostApiSpecificStreamInfo := nil;
+ outputParameters := nil;
+ end
+ else
+ begin
+ inputParameters := nil;
+ outputParameters^.device := deviceIndex;
+ outputParameters^.channelCount := deviceInfo^.maxOutputChannels;
+ outputParameters^.sampleFormat := paInt16;
+ outputParameters^.suggestedLatency := 0;
+ outputParameters^.hostApiSpecificStreamInfo := nil;
+ end;
+
+ sampleRate := deviceInfo^.defaultSampleRate;
+ PaError := Pa_IsFormatSupported(inputParameters, outputParameters, sampleRate);
+ if PaError = paFormatIsSupported then
+ writeln ('Sample rate: ', sampleRate:5:0, ' : supported')
+ else
+ writeln ('Sample rate: ', sampleRate:5:0, ' : Error: ', Pa_GetErrorText(PaError));
+
+ for j := low(standardSampleRates) to high(standardSampleRates) do
+ begin
+ sampleRate := standardSampleRates[j];
+ PaError := Pa_IsFormatSupported(inputParameters, outputParameters, sampleRate);
+ if PaError = paFormatIsSupported then
+ writeln ('Sample rate: ', sampleRate:5:0, ' : supported')
+ else
+ writeln ('Sample rate: ', sampleRate:5:0, ' : Error: ', PaError);
+ end;
+
+ writeln;
+ for j := low(SampleFormat) to high(SampleFormat) do
+ begin
+ if inputParameters <> nil then
+ inputParameters^.sampleFormat := SampleFormat[j]
+ else
+ outputParameters^.sampleFormat := SampleFormat[j];
+ PaError := Pa_IsFormatSupported(inputParameters, outputParameters, sampleRate);
+ if PaError = paFormatIsSupported then
+ writeln ('Sample Format ', SampleFormatName[j], ': supported')
+ else
+ writeln ('Sample Format ', SampleFormatName[j], ': ', Pa_GetErrorText(PaError));
+ end;
+
+ Dispose(inputParameters);
+ Dispose(outputParameters);
+ writeln;
+ end;
+ PaError := Pa_Terminate;
+end;
+
+function AudioCallback(input: pointer; output: pointer; frameCount: culong;
+ timeInfo: PPaStreamCallbackTimeInfo; statusFlags: TPaStreamCallbackFlags;
+ inputDevice: pointer): cint; cdecl;
+var
+ duration: real;
+begin
+ duration := (Now() - callbackStartTime) * 24 * 3600;
+ if (duration < 2.0) then
+ result := paContinue
+ else
+ begin
+ callbackWorks := true;
+ result := paComplete;
+ end;
+end;
+
+procedure TestStreams();
+begin
+ writeln ('*** Test of Pa_OpenStream and Pa_CloseStream ***');
+ PaError := Pa_Initialize;
+ paApiIndex := GetPreferredApiIndex();
+ paApiInfo := Pa_GetHostApiInfo(paApiIndex);
+ for i:= 0 to paApiInfo^.deviceCount - 1 do
+ begin
+ deviceIndex := Pa_HostApiDeviceIndexToDeviceIndex(paApiIndex, i);
+ deviceInfo := Pa_GetDeviceInfo(deviceIndex);
+ writeln ('Device[', i, '] ', deviceInfo^.name, ':');
+ New(inputParameters);
+ New(outputParameters);
+ if deviceInfo^.maxInputChannels > 0 then
+ begin
+ inputParameters^.device := deviceIndex;
+ inputParameters^.channelCount := deviceInfo^.maxInputChannels;
+ inputParameters^.sampleFormat := paInt16;
+ inputParameters^.suggestedLatency := deviceInfo.defaultHighInputLatency;
+ inputParameters^.hostApiSpecificStreamInfo := nil;
+ outputParameters := nil;
+ end
+ else
+ begin
+ inputParameters := nil;
+ outputParameters^.device := deviceIndex;
+ outputParameters^.channelCount := deviceInfo^.maxOutputChannels;
+ outputParameters^.sampleFormat := paInt16;
+ outputParameters^.suggestedLatency := deviceInfo.defaultLowOutputLatency;
+ outputParameters^.hostApiSpecificStreamInfo := nil;
+ end;
+
+ sampleRate := deviceInfo^.defaultSampleRate;
+ framesPerBuffer := paFramesPerBufferUnspecified;
+ streamFlags := paNoFlag;
+ streamCallback := @AudioCallback;
+ userData := nil;
+
+ PaError := Pa_OpenStream(
+ stream,
+ inputParameters,
+ outputParameters,
+ sampleRate,
+ framesPerBuffer,
+ streamFlags,
+ streamCallback,
+ userData
+ );
+ if (PaError = paNoError) and (stream <> nil) then
+ writeln ('Pa_OpenStream: success')
+ else
+ writeln ('Pa_OpenStream: ', Pa_GetErrorText(PaError));
+
+ if (PaError = paNoError) and (stream <> nil) then
+ begin
+ callbackStartTime := Now();
+
+ PaError := Pa_StartStream(stream);
+ if (PaError = paNoError) then
+ writeln ('Pa_StartStream: success')
+ else
+ writeln ('Pa_StartStream: ', Pa_GetErrorText(PaError));
+
+ callbackWorks := false;
+
+ // wait twice the time a successful callback would need for termination
+ writeln('Wait for callback');
+ delay(4000);
+
+ if (callbackWorks and (Pa_IsStreamStopped(stream) = 0)) then
+ begin
+ writeln ('Success: Device works');
+ PaError := Pa_StopStream(stream);
+ if (PaError = paNoError) then
+ writeln ('Pa_StopStream: success')
+ else
+ writeln ('Pa_StopStream: ', Pa_GetErrorText(PaError));
+ end
+ else
+ begin
+ writeln ('Error: Non working device');
+ PaError := Pa_AbortStream(stream);
+ if (PaError = paNoError) then
+ writeln ('Pa_AbortStream: success')
+ else
+ writeln ('Pa_AbortStream: ', Pa_GetErrorText(PaError));
+
+ end;
+ end;
+
+ PaError := Pa_CloseStream(stream);
+ if PaError = paNoError then
+ writeln ('Pa_CloseStream: success')
+ else
+ writeln ('Pa_CloseStream: ', Pa_GetErrorText(PaError));
+
+ Dispose(inputParameters);
+ Dispose(outputParameters);
+
+ writeln;
+ end;
+ PaError := Pa_Terminate;
+end;
+
+begin
+ // floating point exceptions are raised. Therefore, set the exception mask.
+ SetExceptionMask([exZeroDivide, exPrecision]);
+
+ writeln ('Start: Test of Portaudio libs');
+ writeln;
+
+ //TestInitTerminate();
+ //TestErrorText();
+ //TestVersion();
+ //TestApiInfo();
+ //TestDeviceInfo();
+ //TestFormatInfo();
+ TestStreams();
+
+ writeln ('End: Test of Portaudio libs');
+end. \ No newline at end of file