1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-10-11 17:13:37 +02:00

Update portaudio to upstream r1885

This commit is contained in:
lllucius
2013-02-27 01:33:15 +00:00
parent a8c69337c2
commit 123ec235c9
233 changed files with 36854 additions and 12370 deletions

View File

@@ -0,0 +1,92 @@
README for PortAudio Loopback Test
Copyright (c) 1999-2010 Phil Burk and Ross Bencina
See complete license at end of file.
This folder contains code for a single executable that does a standalone test of PortAudio.
It does not require a human to listen to the result. Instead it listens to itself using
a loopback cable connected between the audio output and the audio input. Special pop detectors
and phase analysers can detect errors in the audio stream.
This test can be run from a script as part of a nightly build and test.
--- How to Build the Loopback Test ---
The loopback test is not normally built by the makefile.
To build the loopback test, enter:
./configure && make loopback
This will build the "bin/paloopback" executable.
--- How To Run Test ---
Connect stereo cables from one or more output audio devices to audio input devices.
The test will scan all the ports and find the cables.
Adjust the volume levels of the hardware so you get a decent signal that will not clip.
Run the test from the command line with the following options:
-i# Input device ID. Will scan for loopback if not specified.
-o# Output device ID. Will scan for loopback if not specified.
-r# Sample Rate in Hz. Will use multiple common rates if not specified.
-s# Size of callback buffer in frames, framesPerBuffer.
-w Save bad recordings in a WAV file.
-dDir Path for Directory for WAV files. Default is current directory.
-m Just test the DSP Math code and not the audio devices.
If the -w option is set then any tests that fail will save the recording of the broken
channel in a WAV file. The files will be numbered and shown in the report.
--- ToDo ---
* Add check for harmonic and enharmonic distortion.
* Measure min/max peak values.
* Detect DC bias.
* Test against matrix of devices/APIs and settings.
* Detect mono vs stereo loopback.
* More command line options
--quick
--latency
--duration
* Automated build and test script with cron job.
* Test on Windows.
/*
* PortAudio Portable Real-Time Audio Library
* Latest Version at: http://www.portaudio.com
*
* Copyright (c) 1999-2008 Phil Burk and Ross Bencina
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/

View File

@@ -0,0 +1,707 @@
/*
* PortAudio Portable Real-Time Audio Library
* Latest Version at: http://www.portaudio.com
*
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <math.h>
#include "qa_tools.h"
#include "audio_analyzer.h"
#include "write_wav.h"
#define PAQA_POP_THRESHOLD (0.04)
/*==========================================================================================*/
double PaQa_GetNthFrequency( double baseFrequency, int index )
{
// Use 13 tone equal tempered scale because it does not generate harmonic ratios.
return baseFrequency * pow( 2.0, index / 13.0 );
}
/*==========================================================================================*/
void PaQa_EraseBuffer( float *buffer, int numFrames, int samplesPerFrame )
{
int i;
int numSamples = numFrames * samplesPerFrame;
for( i=0; i<numSamples; i++ )
{
*buffer++ = 0.0;
}
}
/*==========================================================================================*/
void PaQa_SetupSineGenerator( PaQaSineGenerator *generator, double frequency, double amplitude, double frameRate )
{
generator->phase = 0.0;
generator->amplitude = amplitude;
generator->frequency = frequency;
generator->phaseIncrement = 2.0 * frequency * MATH_PI / frameRate;
}
/*==========================================================================================*/
void PaQa_MixSine( PaQaSineGenerator *generator, float *buffer, int numSamples, int stride )
{
int i;
for( i=0; i<numSamples; i++ )
{
float value = sinf( (float) generator->phase ) * generator->amplitude;
*buffer += value; // Mix with existing value.
buffer += stride;
// Advance phase and wrap around.
generator->phase += generator->phaseIncrement;
if (generator->phase > MATH_TWO_PI)
{
generator->phase -= MATH_TWO_PI;
}
}
}
/*==========================================================================================*/
void PaQa_GenerateCrackDISABLED( float *buffer, int numSamples, int stride )
{
int i;
int offset = numSamples/2;
for( i=0; i<numSamples; i++ )
{
float phase = (MATH_TWO_PI * 0.5 * (i - offset)) / numSamples;
float cosp = cosf( phase );
float cos2 = cosp * cosp;
// invert second half of signal
float value = (i < offset) ? cos2 : (0-cos2);
*buffer = value;
buffer += stride;
}
}
/*==========================================================================================*/
int PaQa_InitializeRecording( PaQaRecording *recording, int maxFrames, int frameRate )
{
int numBytes = maxFrames * sizeof(float);
recording->buffer = (float*)malloc(numBytes);
QA_ASSERT_TRUE( "Allocate recording buffer.", (recording->buffer != NULL) );
recording->maxFrames = maxFrames; recording->sampleRate = frameRate;
recording->numFrames = 0;
return 0;
error:
return 1;
}
/*==========================================================================================*/
void PaQa_TerminateRecording( PaQaRecording *recording )
{
if (recording->buffer != NULL)
{
free( recording->buffer );
recording->buffer = NULL;
}
recording->maxFrames = 0;
}
/*==========================================================================================*/
int PaQa_WriteRecording( PaQaRecording *recording, float *buffer, int numFrames, int stride )
{
int i;
int framesToWrite;
float *data = &recording->buffer[recording->numFrames];
framesToWrite = numFrames;
if ((framesToWrite + recording->numFrames) > recording->maxFrames)
{
framesToWrite = recording->maxFrames - recording->numFrames;
}
for( i=0; i<framesToWrite; i++ )
{
*data++ = *buffer;
buffer += stride;
}
recording->numFrames += framesToWrite;
return (recording->numFrames >= recording->maxFrames);
}
/*==========================================================================================*/
int PaQa_WriteSilence( PaQaRecording *recording, int numFrames )
{
int i;
int framesToRecord;
float *data = &recording->buffer[recording->numFrames];
framesToRecord = numFrames;
if ((framesToRecord + recording->numFrames) > recording->maxFrames)
{
framesToRecord = recording->maxFrames - recording->numFrames;
}
for( i=0; i<framesToRecord; i++ )
{
*data++ = 0.0f;
}
recording->numFrames += framesToRecord;
return (recording->numFrames >= recording->maxFrames);
}
/*==========================================================================================*/
int PaQa_RecordFreeze( PaQaRecording *recording, int numFrames )
{
int i;
int framesToRecord;
float *data = &recording->buffer[recording->numFrames];
framesToRecord = numFrames;
if ((framesToRecord + recording->numFrames) > recording->maxFrames)
{
framesToRecord = recording->maxFrames - recording->numFrames;
}
for( i=0; i<framesToRecord; i++ )
{
// Copy old value forward as if the signal had frozen.
data[i] = data[i-1];
}
recording->numFrames += framesToRecord;
return (recording->numFrames >= recording->maxFrames);
}
/*==========================================================================================*/
/**
* Write recording to WAV file.
*/
int PaQa_SaveRecordingToWaveFile( PaQaRecording *recording, const char *filename )
{
WAV_Writer writer;
int result = 0;
#define NUM_SAMPLES (200)
short data[NUM_SAMPLES];
const int samplesPerFrame = 1;
int numLeft = recording->numFrames;
float *buffer = &recording->buffer[0];
result = Audio_WAV_OpenWriter( &writer, filename, recording->sampleRate, samplesPerFrame );
if( result < 0 ) goto error;
while( numLeft > 0 )
{
int i;
int numToSave = (numLeft > NUM_SAMPLES) ? NUM_SAMPLES : numLeft;
// Convert double samples to shorts.
for( i=0; i<numToSave; i++ )
{
double fval = *buffer++;
// Convert float to int and clip to short range.
int ival = fval * 32768.0;
if( ival > 32767 ) ival = 32767;
else if( ival < -32768 ) ival = -32768;
data[i] = ival;
}
result = Audio_WAV_WriteShorts( &writer, data, numToSave );
if( result < 0 ) goto error;
numLeft -= numToSave;
}
result = Audio_WAV_CloseWriter( &writer );
if( result < 0 ) goto error;
return 0;
error:
printf("ERROR: result = %d\n", result );
return result;
#undef NUM_SAMPLES
}
/*==========================================================================================*/
double PaQa_MeasureCrossingSlope( float *buffer, int numFrames )
{
int i;
double slopeTotal = 0.0;
int slopeCount = 0;
float previous;
double averageSlope = 0.0;
previous = buffer[0];
for( i=1; i<numFrames; i++ )
{
float current = buffer[i];
if( (current > 0.0) && (previous < 0.0) )
{
double delta = current - previous;
slopeTotal += delta;
slopeCount += 1;
}
previous = current;
}
if( slopeCount > 0 )
{
averageSlope = slopeTotal / slopeCount;
}
return averageSlope;
}
/*==========================================================================================*/
/*
* We can't just measure the peaks cuz they may be clipped.
* But the zero crossing should be intact.
* The measured slope of a sine wave at zero should be:
*
* slope = sin( 2PI * frequency / sampleRate )
*
*/
double PaQa_MeasureSineAmplitudeBySlope( PaQaRecording *recording,
double frequency, double frameRate,
int startFrame, int numFrames )
{
float *buffer = &recording->buffer[startFrame];
double measuredSlope = PaQa_MeasureCrossingSlope( buffer, numFrames );
double unitySlope = sin( MATH_TWO_PI * frequency / frameRate );
double estimatedAmplitude = measuredSlope / unitySlope;
return estimatedAmplitude;
}
/*==========================================================================================*/
double PaQa_CorrelateSine( PaQaRecording *recording, double frequency, double frameRate,
int startFrame, int numFrames, double *phasePtr )
{
double magnitude = 0.0;
int numLeft = numFrames;
double phase = 0.0;
double phaseIncrement = 2.0 * MATH_PI * frequency / frameRate;
double sinAccumulator = 0.0;
double cosAccumulator = 0.0;
float *data = &recording->buffer[startFrame];
QA_ASSERT_TRUE( "startFrame out of bounds", (startFrame < recording->numFrames) );
QA_ASSERT_TRUE( "numFrames out of bounds", ((startFrame+numFrames) <= recording->numFrames) );
while( numLeft > 0 )
{
double sample = (double) *data++;
sinAccumulator += sample * sin( phase );
cosAccumulator += sample * cos( phase );
phase += phaseIncrement;
if (phase > MATH_TWO_PI)
{
phase -= MATH_TWO_PI;
}
numLeft -= 1;
}
sinAccumulator = sinAccumulator / numFrames;
cosAccumulator = cosAccumulator / numFrames;
// TODO Why do I have to multiply by 2.0? Need it to make result come out right.
magnitude = 2.0 * sqrt( (sinAccumulator * sinAccumulator) + (cosAccumulator * cosAccumulator ));
if( phasePtr != NULL )
{
double phase = atan2( cosAccumulator, sinAccumulator );
*phasePtr = phase;
}
return magnitude;
error:
return -1.0;
}
/*==========================================================================================*/
void PaQa_FilterRecording( PaQaRecording *input, PaQaRecording *output, BiquadFilter *filter )
{
int numToFilter = (input->numFrames > output->maxFrames) ? output->maxFrames : input->numFrames;
BiquadFilter_Filter( filter, &input->buffer[0], &output->buffer[0], numToFilter );
output->numFrames = numToFilter;
}
/*==========================================================================================*/
/** Scan until we get a correlation of a single that goes over the tolerance level,
* peaks then drops to half the peak.
* Look for inverse correlation as well.
*/
double PaQa_FindFirstMatch( PaQaRecording *recording, float *buffer, int numFrames, double threshold )
{
int ic,is;
// How many buffers will fit in the recording?
int maxCorrelations = recording->numFrames - numFrames;
double maxSum = 0.0;
int peakIndex = -1;
double inverseMaxSum = 0.0;
int inversePeakIndex = -1;
double location = -1.0;
QA_ASSERT_TRUE( "numFrames out of bounds", (numFrames < recording->numFrames) );
for( ic=0; ic<maxCorrelations; ic++ )
{
int pastPeak;
int inversePastPeak;
double sum = 0.0;
// Correlate buffer against the recording.
float *recorded = &recording->buffer[ ic ];
for( is=0; is<numFrames; is++ )
{
float s1 = buffer[is];
float s2 = *recorded++;
sum += s1 * s2;
}
if( (sum > maxSum) )
{
maxSum = sum;
peakIndex = ic;
}
if( ((-sum) > inverseMaxSum) )
{
inverseMaxSum = -sum;
inversePeakIndex = ic;
}
pastPeak = (maxSum > threshold) && (sum < 0.5*maxSum);
inversePastPeak = (inverseMaxSum > threshold) && ((-sum) < 0.5*inverseMaxSum);
//printf("PaQa_FindFirstMatch: ic = %4d, sum = %8f, maxSum = %8f, inverseMaxSum = %8f\n", ic, sum, maxSum, inverseMaxSum );
if( pastPeak && inversePastPeak )
{
if( maxSum > inverseMaxSum )
{
location = peakIndex;
}
else
{
location = inversePeakIndex;
}
break;
}
}
//printf("PaQa_FindFirstMatch: location = %4d\n", (int)location );
return location;
error:
return -1.0;
}
/*==========================================================================================*/
// Measure the area under the curve by summing absolute value of each value.
double PaQa_MeasureArea( float *buffer, int numFrames, int stride )
{
int is;
double area = 0.0;
for( is=0; is<numFrames; is++ )
{
area += fabs( *buffer );
buffer += stride;
}
return area;
}
/*==========================================================================================*/
// Measure the area under the curve by summing absolute value of each value.
double PaQa_MeasureRootMeanSquare( float *buffer, int numFrames )
{
int is;
double area = 0.0;
double root;
for( is=0; is<numFrames; is++ )
{
float value = *buffer++;
area += value * value;
}
root = sqrt( area );
return root / numFrames;
}
/*==========================================================================================*/
// Compare the amplitudes of these two signals.
// Return ratio of recorded signal over buffer signal.
double PaQa_CompareAmplitudes( PaQaRecording *recording, int startAt, float *buffer, int numFrames )
{
QA_ASSERT_TRUE( "startAt+numFrames out of bounds", ((startAt+numFrames) < recording->numFrames) );
{
double recordedArea = PaQa_MeasureArea( &recording->buffer[startAt], numFrames, 1 );
double bufferArea = PaQa_MeasureArea( buffer, numFrames, 1 );
if( bufferArea == 0.0 ) return 100000000.0;
return recordedArea / bufferArea;
}
error:
return -1.0;
}
/*==========================================================================================*/
double PaQa_ComputePhaseDifference( double phase1, double phase2 )
{
double delta = phase1 - phase2;
while( delta > MATH_PI )
{
delta -= MATH_TWO_PI;
}
while( delta < -MATH_PI )
{
delta += MATH_TWO_PI;
}
return delta;
}
/*==========================================================================================*/
int PaQa_MeasureLatency( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
{
double threshold;
PaQaSineGenerator generator;
#define MAX_BUFFER_SIZE 2048
float buffer[MAX_BUFFER_SIZE];
double period = testTone->sampleRate / testTone->frequency;
int cycleSize = (int) (period + 0.5);
//printf("PaQa_AnalyseRecording: frequency = %8f, frameRate = %8f, period = %8f, cycleSize = %8d\n",
// testTone->frequency, testTone->sampleRate, period, cycleSize );
analysisResult->latency = -1;
analysisResult->valid = (0);
// Set up generator to find matching first cycle.
QA_ASSERT_TRUE( "cycleSize out of bounds", (cycleSize < MAX_BUFFER_SIZE) );
PaQa_SetupSineGenerator( &generator, testTone->frequency, testTone->amplitude, testTone->sampleRate );
PaQa_EraseBuffer( buffer, cycleSize, testTone->samplesPerFrame );
PaQa_MixSine( &generator, buffer, cycleSize, testTone->samplesPerFrame );
threshold = cycleSize * 0.02;
analysisResult->latency = PaQa_FindFirstMatch( recording, buffer, cycleSize, threshold );
QA_ASSERT_TRUE( "Could not find the start of the signal.", (analysisResult->latency >= 0) );
analysisResult->amplitudeRatio = PaQa_CompareAmplitudes( recording, analysisResult->latency, buffer, cycleSize );
return 0;
error:
return -1;
}
/*==========================================================================================*/
// Apply cosine squared window.
void PaQa_FadeInRecording( PaQaRecording *recording, int startFrame, int count )
{
int is;
double phase = 0.5 * MATH_PI;
// Advance a quarter wave
double phaseIncrement = 0.25 * 2.0 * MATH_PI / count;
assert( startFrame >= 0 );
assert( count > 0 );
/* Zero out initial part of the recording. */
for( is=0; is<startFrame; is++ )
{
recording->buffer[ is ] = 0.0f;
}
/* Fade in where signal begins. */
for( is=0; is<count; is++ )
{
double c = cos( phase );
double w = c * c;
float x = recording->buffer[ is + startFrame ];
float y = x * w;
//printf("FADE %d : w=%f, x=%f, y=%f\n", is, w, x, y );
recording->buffer[ is + startFrame ] = y;
phase += phaseIncrement;
}
}
/*==========================================================================================*/
/** Apply notch filter and high pass filter then detect remaining energy.
*/
int PaQa_DetectPop( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
{
int result = 0;
int i;
double maxAmplitude;
int maxPosition;
PaQaRecording notchOutput = { 0 };
BiquadFilter notchFilter;
PaQaRecording hipassOutput = { 0 };
BiquadFilter hipassFilter;
int frameRate = (int) recording->sampleRate;
analysisResult->popPosition = -1;
analysisResult->popAmplitude = 0.0;
result = PaQa_InitializeRecording( &notchOutput, recording->numFrames, frameRate );
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
result = PaQa_InitializeRecording( &hipassOutput, recording->numFrames, frameRate );
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
// Use notch filter to remove test tone.
BiquadFilter_SetupNotch( &notchFilter, testTone->frequency / frameRate, 0.5 );
PaQa_FilterRecording( recording, &notchOutput, &notchFilter );
//result = PaQa_SaveRecordingToWaveFile( &notchOutput, "notch_output.wav" );
//QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result );
// Apply fade-in window.
PaQa_FadeInRecording( &notchOutput, (int) analysisResult->latency, 500 );
// Use high pass to accentuate the edges of a pop. At higher frequency!
BiquadFilter_SetupHighPass( &hipassFilter, 2.0 * testTone->frequency / frameRate, 0.5 );
PaQa_FilterRecording( &notchOutput, &hipassOutput, &hipassFilter );
//result = PaQa_SaveRecordingToWaveFile( &hipassOutput, "hipass_output.wav" );
//QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result );
// Scan remaining signal looking for peak.
maxAmplitude = 0.0;
maxPosition = -1;
for( i=(int) analysisResult->latency; i<hipassOutput.numFrames; i++ )
{
float x = hipassOutput.buffer[i];
float mag = fabs( x );
if( mag > maxAmplitude )
{
maxAmplitude = mag;
maxPosition = i;
}
}
if( maxAmplitude > PAQA_POP_THRESHOLD )
{
analysisResult->popPosition = maxPosition;
analysisResult->popAmplitude = maxAmplitude;
}
PaQa_TerminateRecording( &notchOutput );
PaQa_TerminateRecording( &hipassOutput );
return 0;
error:
PaQa_TerminateRecording( &notchOutput );
PaQa_TerminateRecording( &hipassOutput );
return -1;
}
/*==========================================================================================*/
int PaQa_DetectPhaseError( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
{
int i;
double period = testTone->sampleRate / testTone->frequency;
int cycleSize = (int) (period + 0.5);
double maxAddedFrames = 0.0;
double maxDroppedFrames = 0.0;
double previousPhase = 0.0;
double previousFrameError = 0;
int loopCount = 0;
int skip = cycleSize;
int windowSize = cycleSize;
// Scan recording starting with first cycle, looking for phase errors.
analysisResult->numDroppedFrames = 0.0;
analysisResult->numAddedFrames = 0.0;
analysisResult->droppedFramesPosition = -1.0;
analysisResult->addedFramesPosition = -1.0;
for( i=analysisResult->latency; i<(recording->numFrames - windowSize); i += skip )
{
double expectedPhase = previousPhase + (skip * MATH_TWO_PI / period);
double expectedPhaseIncrement = PaQa_ComputePhaseDifference( expectedPhase, previousPhase );
double phase = 666.0;
double mag = PaQa_CorrelateSine( recording, testTone->frequency, testTone->sampleRate, i, windowSize, &phase );
if( (loopCount > 1) && (mag > 0.0) )
{
double phaseDelta = PaQa_ComputePhaseDifference( phase, previousPhase );
double phaseError = PaQa_ComputePhaseDifference( phaseDelta, expectedPhaseIncrement );
// Convert phaseError to equivalent number of frames.
double frameError = period * phaseError / MATH_TWO_PI;
double consecutiveFrameError = frameError + previousFrameError;
// if( fabs(frameError) > 0.01 )
// {
// printf("FFFFFFFFFFFFF frameError = %f, at %d\n", frameError, i );
// }
if( consecutiveFrameError > 0.8 )
{
double droppedFrames = consecutiveFrameError;
if (droppedFrames > (maxDroppedFrames * 1.001))
{
analysisResult->numDroppedFrames = droppedFrames;
analysisResult->droppedFramesPosition = i + (windowSize/2);
maxDroppedFrames = droppedFrames;
}
}
else if( consecutiveFrameError < -0.8 )
{
double addedFrames = 0 - consecutiveFrameError;
if (addedFrames > (maxAddedFrames * 1.001))
{
analysisResult->numAddedFrames = addedFrames;
analysisResult->addedFramesPosition = i + (windowSize/2);
maxAddedFrames = addedFrames;
}
}
previousFrameError = frameError;
//if( i<8000 )
//{
// printf("%d: phase = %8f, expected = %8f, delta = %8f, frameError = %8f\n", i, phase, expectedPhaseIncrement, phaseDelta, frameError );
//}
}
previousPhase = phase;
loopCount += 1;
}
return 0;
}
/*==========================================================================================*/
int PaQa_AnalyseRecording( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
{
int result = 0;
memset( analysisResult, 0, sizeof(PaQaAnalysisResult) );
result = PaQa_MeasureLatency( recording, testTone, analysisResult );
QA_ASSERT_EQUALS( "latency measurement", 0, result );
if( (analysisResult->latency >= 0) && (analysisResult->amplitudeRatio > 0.1) )
{
analysisResult->valid = (1);
result = PaQa_DetectPop( recording, testTone, analysisResult );
QA_ASSERT_EQUALS( "detect pop", 0, result );
result = PaQa_DetectPhaseError( recording, testTone, analysisResult );
QA_ASSERT_EQUALS( "detect phase error", 0, result );
}
return 0;
error:
return -1;
}

View File

@@ -0,0 +1,187 @@
/*
* PortAudio Portable Real-Time Audio Library
* Latest Version at: http://www.portaudio.com
*
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
#ifndef _AUDIO_ANALYZER_H
#define _AUDIO_ANALYZER_H
#include "biquad_filter.h"
#define MATH_PI (3.141592653589793238462643)
#define MATH_TWO_PI (2.0 * MATH_PI)
typedef struct PaQaSineGenerator_s
{
double phase;
double phaseIncrement;
double frequency;
double amplitude;
} PaQaSineGenerator;
/** Container for a monophonic audio sample in memory. */
typedef struct PaQaRecording_s
{
/** Maximum number of frames that can fit in the allocated buffer. */
int maxFrames;
float *buffer;
/** Actual number of valid frames in the buffer. */
int numFrames;
int sampleRate;
} PaQaRecording;
typedef struct PaQaTestTone_s
{
int samplesPerFrame;
int startDelay;
double sampleRate;
double frequency;
double amplitude;
} PaQaTestTone;
typedef struct PaQaAnalysisResult_s
{
int valid;
/** Latency in samples from output to input. */
double latency;
double amplitudeRatio;
double popAmplitude;
double popPosition;
double numDroppedFrames;
double droppedFramesPosition;
double numAddedFrames;
double addedFramesPosition;
} PaQaAnalysisResult;
/*================================================================*/
/*================= General DSP Tools ============================*/
/*================================================================*/
/**
* Calculate Nth frequency of a series for use in testing multiple channels.
* Series should avoid harmonic overlap between channels.
*/
double PaQa_GetNthFrequency( double baseFrequency, int index );
void PaQa_EraseBuffer( float *buffer, int numFrames, int samplesPerFrame );
void PaQa_MixSine( PaQaSineGenerator *generator, float *buffer, int numSamples, int stride );
void PaQa_WriteSine( float *buffer, int numSamples, int stride,
double frequency, double amplitude );
/**
* Generate a signal with a sharp edge in the middle that can be recognized despite some phase shift.
*/
void PaQa_GenerateCrack( float *buffer, int numSamples, int stride );
double PaQa_ComputePhaseDifference( double phase1, double phase2 );
/**
* Measure the area under the curve by summing absolute value of each value.
*/
double PaQa_MeasureArea( float *buffer, int numFrames, int stride );
/**
* Measure slope of the positive zero crossings.
*/
double PaQa_MeasureCrossingSlope( float *buffer, int numFrames );
/**
* Prepare an oscillator that can generate a sine tone for testing.
*/
void PaQa_SetupSineGenerator( PaQaSineGenerator *generator, double frequency, double amplitude, double frameRate );
/*================================================================*/
/*================= Recordings ===================================*/
/*================================================================*/
/**
* Allocate memory for containg a mono audio signal. Set up recording for writing.
*/
int PaQa_InitializeRecording( PaQaRecording *recording, int maxSamples, int sampleRate );
/**
* Free memory allocated by PaQa_InitializeRecording.
*/
void PaQa_TerminateRecording( PaQaRecording *recording );
/**
* Apply a biquad filter to the audio from the input recording and write it to the output recording.
*/
void PaQa_FilterRecording( PaQaRecording *input, PaQaRecording *output, BiquadFilter *filter );
int PaQa_SaveRecordingToWaveFile( PaQaRecording *recording, const char *filename );
/**
* @param stride is the spacing of samples to skip in the input buffer. To use every samples pass 1. To use every other sample pass 2.
*/
int PaQa_WriteRecording( PaQaRecording *recording, float *buffer, int numSamples, int stride );
/** Write zeros into a recording. */
int PaQa_WriteSilence( PaQaRecording *recording, int numSamples );
int PaQa_RecordFreeze( PaQaRecording *recording, int numSamples );
double PaQa_CorrelateSine( PaQaRecording *recording, double frequency, double frameRate,
int startFrame, int numSamples, double *phasePtr );
double PaQa_FindFirstMatch( PaQaRecording *recording, float *buffer, int numSamples, double tolerance );
/**
* Estimate the original amplitude of a clipped sine wave by measuring
* its average slope at the zero crossings.
*/
double PaQa_MeasureSineAmplitudeBySlope( PaQaRecording *recording,
double frequency, double frameRate,
int startFrame, int numFrames );
double PaQa_MeasureRootMeanSquare( float *buffer, int numFrames );
/**
* Compare the amplitudes of these two signals.
* Return ratio of recorded signal over buffer signal.
*/
double PaQa_CompareAmplitudes( PaQaRecording *recording, int startAt, float *buffer, int numSamples );
/**
* Analyse a recording of a sine wave.
* Measure latency and look for dropped frames, etc.
*/
int PaQa_AnalyseRecording( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult );
#endif /* _AUDIO_ANALYZER_H */

View File

@@ -0,0 +1,122 @@
#include <math.h>
#include <string.h>
#include "biquad_filter.h"
/**
* Unit_BiquadFilter implements a second order IIR filter.
Here is the equation that we use for this filter:
y(n) = a0*x(n) + a1*x(n-1) + a2*x(n-2) - b1*y(n-1) - b2*y(n-2)
*
* @author (C) 2002 Phil Burk, SoftSynth.com, All Rights Reserved
*/
#define FILTER_PI (3.141592653589793238462643)
/***********************************************************
** Calculate coefficients common to many parametric biquad filters.
*/
static void BiquadFilter_CalculateCommon( BiquadFilter *filter, double ratio, double Q )
{
double omega;
memset( filter, 0, sizeof(BiquadFilter) );
/* Don't let frequency get too close to Nyquist or filter will blow up. */
if( ratio >= 0.499 ) ratio = 0.499;
omega = 2.0 * (double)FILTER_PI * ratio;
filter->cos_omega = (double) cos( omega );
filter->sin_omega = (double) sin( omega );
filter->alpha = filter->sin_omega / (2.0 * Q);
}
/*********************************************************************************
** Calculate coefficients for Highpass filter.
*/
void BiquadFilter_SetupHighPass( BiquadFilter *filter, double ratio, double Q )
{
double scalar, opc;
if( ratio < BIQUAD_MIN_RATIO ) ratio = BIQUAD_MIN_RATIO;
if( Q < BIQUAD_MIN_Q ) Q = BIQUAD_MIN_Q;
BiquadFilter_CalculateCommon( filter, ratio, Q );
scalar = 1.0 / (1.0 + filter->alpha);
opc = (1.0 + filter->cos_omega);
filter->a0 = opc * 0.5 * scalar;
filter->a1 = - opc * scalar;
filter->a2 = filter->a0;
filter->b1 = -2.0 * filter->cos_omega * scalar;
filter->b2 = (1.0 - filter->alpha) * scalar;
}
/*********************************************************************************
** Calculate coefficients for Notch filter.
*/
void BiquadFilter_SetupNotch( BiquadFilter *filter, double ratio, double Q )
{
double scalar, opc;
if( ratio < BIQUAD_MIN_RATIO ) ratio = BIQUAD_MIN_RATIO;
if( Q < BIQUAD_MIN_Q ) Q = BIQUAD_MIN_Q;
BiquadFilter_CalculateCommon( filter, ratio, Q );
scalar = 1.0 / (1.0 + filter->alpha);
opc = (1.0 + filter->cos_omega);
filter->a0 = scalar;
filter->a1 = -2.0 * filter->cos_omega * scalar;
filter->a2 = filter->a0;
filter->b1 = filter->a1;
filter->b2 = (1.0 - filter->alpha) * scalar;
}
/*****************************************************************
** Perform core IIR filter calculation without permutation.
*/
void BiquadFilter_Filter( BiquadFilter *filter, float *inputs, float *outputs, int numSamples )
{
int i;
double xn, yn;
// Pull values from structure to speed up the calculation.
double a0 = filter->a0;
double a1 = filter->a1;
double a2 = filter->a2;
double b1 = filter->b1;
double b2 = filter->b2;
double xn1 = filter->xn1;
double xn2 = filter->xn2;
double yn1 = filter->yn1;
double yn2 = filter->yn2;
for( i=0; i<numSamples; i++)
{
// Generate outputs by filtering inputs.
xn = inputs[i];
yn = (a0 * xn) + (a1 * xn1) + (a2 * xn2) - (b1 * yn1) - (b2 * yn2);
outputs[i] = yn;
// Delay input and output values.
xn2 = xn1;
xn1 = xn;
yn2 = yn1;
yn1 = yn;
if( (i & 7) == 0 )
{
// Apply a small bipolar impulse to filter to prevent arithmetic underflow.
// Underflows can cause the FPU to interrupt the CPU.
yn1 += (double) 1.0E-26;
yn2 -= (double) 1.0E-26;
}
}
filter->xn1 = xn1;
filter->xn2 = xn2;
filter->yn1 = yn1;
filter->yn2 = yn2;
}

View File

@@ -0,0 +1,38 @@
#ifndef _BIQUADFILTER_H
#define _BIQUADFILTER_H
/**
* Unit_BiquadFilter implements a second order IIR filter.
*
* @author (C) 2002 Phil Burk, SoftSynth.com, All Rights Reserved
*/
#define BIQUAD_MIN_RATIO (0.000001)
#define BIQUAD_MIN_Q (0.00001)
typedef struct BiquadFilter_s
{
double xn1; // storage for delayed signals
double xn2;
double yn1;
double yn2;
double a0; // coefficients
double a1;
double a2;
double b1;
double b2;
double cos_omega;
double sin_omega;
double alpha;
} BiquadFilter;
void BiquadFilter_SetupHighPass( BiquadFilter *filter, double ratio, double Q );
void BiquadFilter_SetupNotch( BiquadFilter *filter, double ratio, double Q );
void BiquadFilter_Filter( BiquadFilter *filter, float *inputs, float *outputs, int numSamples );
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,171 @@
/*
* PortAudio Portable Real-Time Audio Library
* Latest Version at: http://www.portaudio.com
*
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
#include "paqa_tools.h"
/*******************************************************************/
void PaQa_ListAudioDevices(void)
{
int i, numDevices;
const PaDeviceInfo *deviceInfo;
numDevices = Pa_GetDeviceCount();
for( i=0; i<numDevices; i++ )
{
deviceInfo = Pa_GetDeviceInfo( i );
printf( "#%d: ", i );
printf( "%2d in", deviceInfo->maxInputChannels );
printf( ", %2d out", deviceInfo->maxOutputChannels );
printf( ", %s", deviceInfo->name );
printf( ", on %s\n", Pa_GetHostApiInfo( deviceInfo->hostApi )->name );
}
}
/*******************************************************************/
void PaQa_ConvertToFloat( const void *input, int numSamples, PaSampleFormat inFormat, float *output )
{
int i;
switch( inFormat )
{
case paUInt8:
{
unsigned char *data = (unsigned char *)input;
for( i=0; i<numSamples; i++ )
{
int value = *data++;
value -= 128;
*output++ = value / 128.0f;
}
}
break;
case paInt8:
{
char *data = (char *)input;
for( i=0; i<numSamples; i++ )
{
int value = *data++;
*output++ = value / 128.0f;
}
}
break;
case paInt16:
{
short *data = (short *)input;
for( i=0; i<numSamples; i++ )
{
*output++ = *data++ / 32768.0f;
}
}
break;
case paInt32:
{
int *data = (int *)input;
for( i=0; i<numSamples; i++ )
{
int value = (*data++) >> 8;
float fval = (float) (value / ((double) 0x00800000));
*output++ = fval;
}
}
break;
}
}
/*******************************************************************/
void PaQa_ConvertFromFloat( const float *input, int numSamples, PaSampleFormat outFormat, void *output )
{
int i;
switch( outFormat )
{
case paUInt8:
{
unsigned char *data = (unsigned char *)output;
for( i=0; i<numSamples; i++ )
{
float value = *input++;
int byte = ((int) (value * 127)) + 128;
*data++ = (unsigned char) byte;
}
}
break;
case paInt8:
{
char *data = (char *)output;
for( i=0; i<numSamples; i++ )
{
float value = *input++;
int byte = (int) (value * 127);
*data++ = (char) byte;
}
}
break;
case paInt16:
{
short *data = (short *)output;
for( i=0; i<numSamples; i++ )
{
float value = *input++;
// Use assymmetric conversion to avoid clipping.
short sval = value * 32767.0;
*data++ = sval;
}
}
break;
case paInt32:
{
int *data = (int *)output;
for( i=0; i<numSamples; i++ )
{
float value = *input++;
// Use assymmetric conversion to avoid clipping.
int ival = value * ((double) 0x007FFFF0);
ival = ival << 8;
*data++ = ival;
}
}
break;
}
}

View File

@@ -0,0 +1,52 @@
/*
* PortAudio Portable Real-Time Audio Library
* Latest Version at: http://www.portaudio.com
*
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
#ifndef _PAQA_TOOLS_H
#define _PAQA_TOOLS_H
#include <stdio.h>
#include "portaudio.h"
void PaQa_ListAudioDevices(void);
void PaQa_ConvertToFloat( const void *input, int numSamples, PaSampleFormat inFormat, float *output );
void PaQa_ConvertFromFloat( const float *input, int numSamples, PaSampleFormat outFormat, void *output );
#endif /* _PAQA_TOOLS_H */

View File

@@ -0,0 +1,74 @@
/*
* PortAudio Portable Real-Time Audio Library
* Latest Version at: http://www.portaudio.com
*
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
#ifndef _QA_TOOLS_H
#define _QA_TOOLS_H
extern int g_testsPassed;
extern int g_testsFailed;
#define QA_ASSERT_TRUE( message, flag ) \
if( !(flag) ) \
{ \
printf( "%s:%d - ERROR - %s\n", __FILE__, __LINE__, message ); \
g_testsFailed++; \
goto error; \
} \
else g_testsPassed++;
#define QA_ASSERT_EQUALS( message, expected, actual ) \
if( ((expected) != (actual)) ) \
{ \
printf( "%s:%d - ERROR - %s, expected %d, got %d\n", __FILE__, __LINE__, message, expected, actual ); \
g_testsFailed++; \
goto error; \
} \
else g_testsPassed++;
#define QA_ASSERT_CLOSE( message, expected, actual, tolerance ) \
if (fabs((expected)-(actual))>(tolerance)) \
{ \
printf( "%s:%d - ERROR - %s, expected %f, got %f, tol=%f\n", __FILE__, __LINE__, message, ((double)(expected)), ((double)(actual)), ((double)(tolerance)) ); \
g_testsFailed++; \
goto error; \
} \
else g_testsPassed++;
#endif

View File

@@ -0,0 +1,718 @@
/*
* PortAudio Portable Real-Time Audio Library
* Latest Version at: http://www.portaudio.com
*
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "qa_tools.h"
#include "audio_analyzer.h"
#include "test_audio_analyzer.h"
#include "write_wav.h"
#include "biquad_filter.h"
#define FRAMES_PER_BLOCK (64)
#define PRINT_REPORTS 0
#define TEST_SAVED_WAVE (0)
/*==========================================================================================*/
/**
* Detect a single tone.
*/
static int TestSingleMonoTone( void )
{
int result = 0;
PaQaSineGenerator generator;
PaQaRecording recording;
float buffer[FRAMES_PER_BLOCK];
double sampleRate = 44100.0;
int maxFrames = ((int)sampleRate) * 1;
int samplesPerFrame = 1;
int stride = 1;
int done = 0;
double freq = 234.5;
double amp = 0.5;
double mag1, mag2;
// Setup a sine oscillator.
PaQa_SetupSineGenerator( &generator, freq, amp, sampleRate );
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
done = 0;
while (!done)
{
PaQa_EraseBuffer( buffer, FRAMES_PER_BLOCK, samplesPerFrame );
PaQa_MixSine( &generator, buffer, FRAMES_PER_BLOCK, stride );
done = PaQa_WriteRecording( &recording, buffer, FRAMES_PER_BLOCK, samplesPerFrame );
}
mag1 = PaQa_CorrelateSine( &recording, freq, sampleRate, 0, recording.numFrames, NULL );
QA_ASSERT_CLOSE( "exact frequency match", amp, mag1, 0.01 );
mag2 = PaQa_CorrelateSine( &recording, freq * 1.23, sampleRate, 0, recording.numFrames, NULL );
QA_ASSERT_CLOSE( "wrong frequency", 0.0, mag2, 0.01 );
PaQa_TerminateRecording( &recording );
return 0;
error:
PaQa_TerminateRecording( &recording);
return 1;
}
/*==========================================================================================*/
/**
* Mix multiple tones and then detect them.
*/
static int TestMixedMonoTones( void )
{
int i;
int result = 0;
#define NUM_TONES (5)
PaQaSineGenerator generators[NUM_TONES];
PaQaRecording recording;
float buffer[FRAMES_PER_BLOCK];
double sampleRate = 44100.0;
int maxFrames = ((int)sampleRate) * 1;
int samplesPerFrame = 1;
double baseFreq = 234.5;
double amp = 0.1;
double mag2;
int stride = samplesPerFrame;
int done = 0;
// Setup a sine oscillator.
for( i=0; i<NUM_TONES; i++ )
{
PaQa_SetupSineGenerator( &generators[i], PaQa_GetNthFrequency( baseFreq, i ), amp, sampleRate );
}
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
done = 0;
while (!done)
{
PaQa_EraseBuffer( buffer, FRAMES_PER_BLOCK, samplesPerFrame );
for( i=0; i<NUM_TONES; i++ )
{
PaQa_MixSine( &generators[i], buffer, FRAMES_PER_BLOCK, stride );
}
done = PaQa_WriteRecording( &recording, buffer, FRAMES_PER_BLOCK, samplesPerFrame );
}
for( i=0; i<NUM_TONES; i++ )
{
double mag = PaQa_CorrelateSine( &recording, PaQa_GetNthFrequency( baseFreq, i), sampleRate, 0, recording.numFrames, NULL );
QA_ASSERT_CLOSE( "exact frequency match", amp, mag, 0.01 );
}
mag2 = PaQa_CorrelateSine( &recording, baseFreq * 0.87, sampleRate, 0, recording.numFrames, NULL );
QA_ASSERT_CLOSE( "wrong frequency", 0.0, mag2, 0.01 );
PaQa_TerminateRecording( &recording );
return 0;
error:
PaQa_TerminateRecording( &recording);
return 1;
}
/*==========================================================================================*/
/**
* Generate a recording with added or dropped frames.
*/
static void MakeRecordingWithAddedFrames( PaQaRecording *recording, PaQaTestTone *testTone, int glitchPosition, int framesToAdd )
{
PaQaSineGenerator generator;
#define BUFFER_SIZE 512
float buffer[BUFFER_SIZE];
int frameCounter = testTone->startDelay;
int stride = 1;
// Record some initial silence.
int done = PaQa_WriteSilence( recording, testTone->startDelay );
// Setup a sine oscillator.
PaQa_SetupSineGenerator( &generator, testTone->frequency, testTone->amplitude, testTone->sampleRate );
while (!done)
{
int framesThisLoop = BUFFER_SIZE;
if( frameCounter == glitchPosition )
{
if( framesToAdd > 0 )
{
// Record some frozen data without advancing the sine generator.
done = PaQa_RecordFreeze( recording, framesToAdd );
frameCounter += framesToAdd;
}
else if( framesToAdd < 0 )
{
// Advance sine generator a few frames.
PaQa_MixSine( &generator, buffer, 0 - framesToAdd, stride );
}
}
else if( (frameCounter < glitchPosition) && ((frameCounter + framesThisLoop) > glitchPosition) )
{
// Go right up to the glitchPosition.
framesThisLoop = glitchPosition - frameCounter;
}
if( framesThisLoop > 0 )
{
PaQa_EraseBuffer( buffer, framesThisLoop, testTone->samplesPerFrame );
PaQa_MixSine( &generator, buffer, framesThisLoop, stride );
done = PaQa_WriteRecording( recording, buffer, framesThisLoop, testTone->samplesPerFrame );
}
frameCounter += framesThisLoop;
}
}
/*==========================================================================================*/
/**
* Generate a clean recording.
*/
static void MakeCleanRecording( PaQaRecording *recording, PaQaTestTone *testTone )
{
PaQaSineGenerator generator;
#define BUFFER_SIZE 512
float buffer[BUFFER_SIZE];
int stride = 1;
// Record some initial silence.
int done = PaQa_WriteSilence( recording, testTone->startDelay );
// Setup a sine oscillator.
PaQa_SetupSineGenerator( &generator, testTone->frequency, testTone->amplitude, testTone->sampleRate );
// Generate recording with good phase.
while (!done)
{
PaQa_EraseBuffer( buffer, BUFFER_SIZE, testTone->samplesPerFrame );
PaQa_MixSine( &generator, buffer, BUFFER_SIZE, stride );
done = PaQa_WriteRecording( recording, buffer, BUFFER_SIZE, testTone->samplesPerFrame );
}
}
/*==========================================================================================*/
/**
* Generate a recording with pop.
*/
static void MakeRecordingWithPop( PaQaRecording *recording, PaQaTestTone *testTone, int popPosition, int popWidth, double popAmplitude )
{
int i;
MakeCleanRecording( recording, testTone );
// Apply glitch to good recording.
if( (popPosition + popWidth) >= recording->numFrames )
{
popWidth = (recording->numFrames - popPosition) - 1;
}
for( i=0; i<popWidth; i++ )
{
float good = recording->buffer[i+popPosition];
float bad = (good > 0.0) ? (good - popAmplitude) : (good + popAmplitude);
recording->buffer[i+popPosition] = bad;
}
}
/*==========================================================================================*/
/**
* Detect one phase error in a recording.
*/
static int TestDetectSinglePhaseError( double sampleRate, int cycleSize, int latencyFrames, int glitchPosition, int framesAdded )
{
int result = 0;
PaQaRecording recording;
PaQaTestTone testTone;
PaQaAnalysisResult analysisResult = { 0.0 };
int framesDropped = 0;
int maxFrames = ((int)sampleRate) * 2;
testTone.samplesPerFrame = 1;
testTone.sampleRate = sampleRate;
testTone.frequency = sampleRate / cycleSize;
testTone.amplitude = 0.5;
testTone.startDelay = latencyFrames;
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
MakeRecordingWithAddedFrames( &recording, &testTone, glitchPosition, framesAdded );
PaQa_AnalyseRecording( &recording, &testTone, &analysisResult );
if( framesAdded < 0 )
{
framesDropped = -framesAdded;
framesAdded = 0;
}
#if PRINT_REPORTS
printf("\n=== Dropped Frame Analysis ===================\n");
printf(" expected actual\n");
printf(" latency: %10.3f %10.3f\n", (double)latencyFrames, analysisResult.latency );
printf(" num added frames: %10.3f %10.3f\n", (double)framesAdded, analysisResult.numAddedFrames );
printf(" added frames at: %10.3f %10.3f\n", (double)glitchPosition, analysisResult.addedFramesPosition );
printf(" num dropped frames: %10.3f %10.3f\n", (double)framesDropped, analysisResult.numDroppedFrames );
printf(" dropped frames at: %10.3f %10.3f\n", (double)glitchPosition, analysisResult.droppedFramesPosition );
#endif
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording latency", latencyFrames, analysisResult.latency, 0.5 );
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording framesAdded", framesAdded, analysisResult.numAddedFrames, 1.0 );
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording framesDropped", framesDropped, analysisResult.numDroppedFrames, 1.0 );
// QA_ASSERT_CLOSE( "PaQa_AnalyseRecording glitchPosition", glitchPosition, analysisResult.glitchPosition, cycleSize );
PaQa_TerminateRecording( &recording );
return 0;
error:
PaQa_TerminateRecording( &recording);
return 1;
}
/*==========================================================================================*/
/**
* Test various dropped sample scenarios.
*/
static int TestDetectPhaseErrors( void )
{
int result;
result = TestDetectSinglePhaseError( 44100, 200, 477, -1, 0 );
if( result < 0 ) return result;
/*
result = TestDetectSinglePhaseError( 44100, 200, 77, -1, 0 );
if( result < 0 ) return result;
result = TestDetectSinglePhaseError( 44100, 200, 83, 3712, 9 );
if( result < 0 ) return result;
result = TestDetectSinglePhaseError( 44100, 280, 83, 3712, 27 );
if( result < 0 ) return result;
result = TestDetectSinglePhaseError( 44100, 200, 234, 3712, -9 );
if( result < 0 ) return result;
result = TestDetectSinglePhaseError( 44100, 200, 2091, 8923, -2 );
if( result < 0 ) return result;
result = TestDetectSinglePhaseError( 44100, 120, 1782, 5772, -18 );
if( result < 0 ) return result;
// Note that if the frequency is too high then it is hard to detect single dropped frames.
result = TestDetectSinglePhaseError( 44100, 200, 500, 4251, -1 );
if( result < 0 ) return result;
*/
return 0;
}
/*==========================================================================================*/
/**
* Detect one pop in a recording.
*/
static int TestDetectSinglePop( double sampleRate, int cycleSize, int latencyFrames, int popPosition, int popWidth, double popAmplitude )
{
int result = 0;
PaQaRecording recording;
PaQaTestTone testTone;
PaQaAnalysisResult analysisResult = { 0.0 };
int maxFrames = ((int)sampleRate) * 2;
testTone.samplesPerFrame = 1;
testTone.sampleRate = sampleRate;
testTone.frequency = sampleRate / cycleSize;
testTone.amplitude = 0.5;
testTone.startDelay = latencyFrames;
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
MakeRecordingWithPop( &recording, &testTone, popPosition, popWidth, popAmplitude );
PaQa_AnalyseRecording( &recording, &testTone, &analysisResult );
#if PRINT_REPORTS
printf("\n=== Pop Analysis ===================\n");
printf(" expected actual\n");
printf(" latency: %10.3f %10.3f\n", (double)latencyFrames, analysisResult.latency );
printf(" popPosition: %10.3f %10.3f\n", (double)popPosition, analysisResult.popPosition );
printf(" popAmplitude: %10.3f %10.3f\n", popAmplitude, analysisResult.popAmplitude );
printf(" cycleSize: %6d\n", cycleSize );
printf(" num added frames: %10.3f\n", analysisResult.numAddedFrames );
printf(" added frames at: %10.3f\n", analysisResult.addedFramesPosition );
printf(" num dropped frames: %10.3f\n", analysisResult.numDroppedFrames );
printf(" dropped frames at: %10.3f\n", analysisResult.droppedFramesPosition );
#endif
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording latency", latencyFrames, analysisResult.latency, 0.5 );
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording popPosition", popPosition, analysisResult.popPosition, 10 );
if( popWidth > 0 )
{
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording popAmplitude", popAmplitude, analysisResult.popAmplitude, 0.1 * popAmplitude );
}
PaQa_TerminateRecording( &recording );
return 0;
error:
PaQa_SaveRecordingToWaveFile( &recording, "bad_recording.wav" );
PaQa_TerminateRecording( &recording);
return 1;
}
/*==========================================================================================*/
/**
* Analyse recording with a DC offset.
*/
static int TestSingleInitialSpike( double sampleRate, int stepPosition, int cycleSize, int latencyFrames, double stepAmplitude )
{
int i;
int result = 0;
// Account for highpass filter offset.
int expectedLatency = latencyFrames + 1;
PaQaRecording recording;
PaQaRecording hipassOutput = { 0 };
BiquadFilter hipassFilter;
PaQaTestTone testTone;
PaQaAnalysisResult analysisResult = { 0.0 };
int maxFrames = ((int)sampleRate) * 2;
testTone.samplesPerFrame = 1;
testTone.sampleRate = sampleRate;
testTone.frequency = sampleRate / cycleSize;
testTone.amplitude = -0.5;
testTone.startDelay = latencyFrames;
result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
result = PaQa_InitializeRecording( &hipassOutput, maxFrames, (int) sampleRate );
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
MakeCleanRecording( &recording, &testTone );
// Apply DC step.
for( i=stepPosition; i<recording.numFrames; i++ )
{
recording.buffer[i] += stepAmplitude;
}
// Use high pass as a DC blocker!
BiquadFilter_SetupHighPass( &hipassFilter, 10.0 / sampleRate, 0.5 );
PaQa_FilterRecording( &recording, &hipassOutput, &hipassFilter );
testTone.amplitude = 0.5;
PaQa_AnalyseRecording( &hipassOutput, &testTone, &analysisResult );
#if PRINT_REPORTS
printf("\n=== InitialSpike Analysis ===================\n");
printf(" expected actual\n");
printf(" latency: %10.3f %10.3f\n", (double)expectedLatency, analysisResult.latency );
printf(" popPosition: %10.3f\n", analysisResult.popPosition );
printf(" popAmplitude: %10.3f\n", analysisResult.popAmplitude );
printf(" amplitudeRatio: %10.3f\n", analysisResult.amplitudeRatio );
printf(" cycleSize: %6d\n", cycleSize );
printf(" num added frames: %10.3f\n", analysisResult.numAddedFrames );
printf(" added frames at: %10.3f\n", analysisResult.addedFramesPosition );
printf(" num dropped frames: %10.3f\n", analysisResult.numDroppedFrames );
printf(" dropped frames at: %10.3f\n", analysisResult.droppedFramesPosition );
#endif
QA_ASSERT_CLOSE( "PaQa_AnalyseRecording latency", expectedLatency, analysisResult.latency, 4.0 );
QA_ASSERT_EQUALS( "PaQa_AnalyseRecording no pop from step", -1, (int) analysisResult.popPosition );
PaQa_TerminateRecording( &recording );
PaQa_TerminateRecording( &hipassOutput );
return 0;
error:
PaQa_SaveRecordingToWaveFile( &recording, "bad_step_original.wav" );
PaQa_SaveRecordingToWaveFile( &hipassOutput, "bad_step_hipass.wav" );
PaQa_TerminateRecording( &recording);
PaQa_TerminateRecording( &hipassOutput );
return 1;
}
/*==========================================================================================*/
/**
* Test various dropped sample scenarios.
*/
static int TestDetectPops( void )
{
int result;
// No pop.
result = TestDetectSinglePop( 44100, 200, 477, -1, 0, 0.0 );
if( result < 0 ) return result;
// short pop
result = TestDetectSinglePop( 44100, 300, 810, 3987, 1, 0.5 );
if( result < 0 ) return result;
// medium long pop
result = TestDetectSinglePop( 44100, 300, 810, 9876, 5, 0.5 );
if( result < 0 ) return result;
// short tiny pop
result = TestDetectSinglePop( 44100, 250, 810, 5672, 1, 0.05 );
if( result < 0 ) return result;
return 0;
}
/*==========================================================================================*/
/**
* Test analysis when there is a DC offset step before the sine signal.
*/
static int TestInitialSpike( void )
{
int result;
//( double sampleRate, int stepPosition, int cycleSize, int latencyFrames, double stepAmplitude )
// No spike.
result = TestSingleInitialSpike( 44100, 32, 100, 537, 0.0 );
if( result < 0 ) return result;
// Small spike.
result = TestSingleInitialSpike( 44100, 32, 100, 537, 0.1 );
if( result < 0 ) return result;
// short pop like Ross's error.
result = TestSingleInitialSpike( 8000, 32, 42, 2000, 0.1 );
if( result < 0 ) return result;
// Medium spike.
result = TestSingleInitialSpike( 44100, 40, 190, 3000, 0.5 );
if( result < 0 ) return result;
// Spike near sine.
//result = TestSingleInitialSpike( 44100, 2900, 140, 3000, 0.1 );
if( result < 0 ) return result;
return 0;
}
#if TEST_SAVED_WAVE
/*==========================================================================================*/
/**
* Simple test that writes a sawtooth waveform to a file.
*/
static int TestSavedWave()
{
int i,j;
WAV_Writer writer;
int result = 0;
#define NUM_SAMPLES (200)
short data[NUM_SAMPLES];
short saw = 0;
result = Audio_WAV_OpenWriter( &writer, "test_sawtooth.wav", 44100, 1 );
if( result < 0 ) goto error;
for( i=0; i<15; i++ )
{
for( j=0; j<NUM_SAMPLES; j++ )
{
data[j] = saw;
saw += 293;
}
result = Audio_WAV_WriteShorts( &writer, data, NUM_SAMPLES );
if( result < 0 ) goto error;
}
result = Audio_WAV_CloseWriter( &writer );
if( result < 0 ) goto error;
return 0;
error:
printf("ERROR: result = %d\n", result );
return result;
}
#endif /* TEST_SAVED_WAVE */
/*==========================================================================================*/
/**
* Easy way to generate a sine tone recording.
*/
void PaQa_FillWithSine( PaQaRecording *recording, double sampleRate, double freq, double amp )
{
PaQaSineGenerator generator;
float buffer[FRAMES_PER_BLOCK];
int samplesPerFrame = 1;
int stride = 1;
int done = 0;
// Setup a sine oscillator.
PaQa_SetupSineGenerator( &generator, freq, amp, sampleRate );
done = 0;
while (!done)
{
PaQa_EraseBuffer( buffer, FRAMES_PER_BLOCK, samplesPerFrame );
PaQa_MixSine( &generator, buffer, FRAMES_PER_BLOCK, stride );
done = PaQa_WriteRecording( recording, buffer, FRAMES_PER_BLOCK, samplesPerFrame );
}
}
/*==========================================================================================*/
/**
* Generate a tone then knock it out using a filter.
* Also check using filter slightly off tune to see if some energy gets through.
*/
static int TestNotchFilter( void )
{
int result = 0;
PaQaRecording original = { 0 };
PaQaRecording filtered = { 0 };
BiquadFilter notchFilter;
double sampleRate = 44100.0;
int maxFrames = ((int)sampleRate) * 1;
double freq = 234.5;
double amp = 0.5;
double mag1, mag2, mag3;
result = PaQa_InitializeRecording( &original, maxFrames, (int) sampleRate );
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
PaQa_FillWithSine( &original, sampleRate, freq, amp );
//result = PaQa_SaveRecordingToWaveFile( &original, "original.wav" );
//QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result );
mag1 = PaQa_CorrelateSine( &original, freq, sampleRate, 0, original.numFrames, NULL );
QA_ASSERT_CLOSE( "exact frequency match", amp, mag1, 0.01 );
// Filter with exact frequency.
result = PaQa_InitializeRecording( &filtered, maxFrames, (int) sampleRate );
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
BiquadFilter_SetupNotch( &notchFilter, freq / sampleRate, 0.5 );
PaQa_FilterRecording( &original, &filtered, &notchFilter );
result = PaQa_SaveRecordingToWaveFile( &filtered, "filtered1.wav" );
QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result );
mag2 = PaQa_CorrelateSine( &filtered, freq, sampleRate, 0, filtered.numFrames, NULL );
QA_ASSERT_CLOSE( "should eliminate tone", 0.0, mag2, 0.01 );
// Filter with mismatched frequency.
BiquadFilter_SetupNotch( &notchFilter, 1.07 * freq / sampleRate, 2.0 );
PaQa_FilterRecording( &original, &filtered, &notchFilter );
//result = PaQa_SaveRecordingToWaveFile( &filtered, "badfiltered.wav" );
//QA_ASSERT_EQUALS( "PaQa_SaveRecordingToWaveFile failed", 0, result );
mag3 = PaQa_CorrelateSine( &filtered, freq, sampleRate, 0, filtered.numFrames, NULL );
QA_ASSERT_CLOSE( "should eliminate tone", amp*0.26, mag3, 0.01 );
PaQa_TerminateRecording( &original );
PaQa_TerminateRecording( &filtered );
return 0;
error:
PaQa_TerminateRecording( &original);
PaQa_TerminateRecording( &filtered );
return 1;
}
/*==========================================================================================*/
/**
*/
int PaQa_TestAnalyzer( void )
{
int result;
#if TEST_SAVED_WAVE
// Write a simple wave file.
if ((result = TestSavedWave()) != 0) return result;
#endif /* TEST_SAVED_WAVE */
// Generate single tone and verify presence.
if ((result = TestSingleMonoTone()) != 0) return result;
// Generate prime series of tones and verify presence.
if ((result = TestMixedMonoTones()) != 0) return result;
// Detect dropped or added samples in a sine wave recording.
if ((result = TestDetectPhaseErrors()) != 0) return result;
// Test to see if notch filter can knock out the test tone.
if ((result = TestNotchFilter()) != 0) return result;
// Detect pops that get back in phase.
if ((result = TestDetectPops()) != 0) return result;
// Test to see if the latency detector can be tricked like it was on Ross' Windows machine.
if ((result = TestInitialSpike()) != 0) return result;
return 0;
}

View File

@@ -0,0 +1,46 @@
/*
* PortAudio Portable Real-Time Audio Library
* Latest Version at: http://www.portaudio.com
*
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
#ifndef _TEST_AUDIO_ANALYZER_H
#define _TEST_AUDIO_ANALYZER_H
/** Test the audio analyzer by itself without any PortAudio calls. */
int PaQa_TestAnalyzer( void );
#endif /* _TEST_AUDIO_ANALYZER_H */

View File

@@ -0,0 +1,242 @@
/*
* PortAudio Portable Real-Time Audio Library
* Latest Version at: http://www.portaudio.com
*
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
/**
* Very simple WAV file writer for saving captured audio.
*/
#include <stdio.h>
#include <stdlib.h>
#include "write_wav.h"
/* Write long word data to a little endian format byte array. */
static void WriteLongLE( unsigned char **addrPtr, unsigned long data )
{
unsigned char *addr = *addrPtr;
*addr++ = (unsigned char) data;
*addr++ = (unsigned char) (data>>8);
*addr++ = (unsigned char) (data>>16);
*addr++ = (unsigned char) (data>>24);
*addrPtr = addr;
}
/* Write short word data to a little endian format byte array. */
static void WriteShortLE( unsigned char **addrPtr, unsigned short data )
{
unsigned char *addr = *addrPtr;
*addr++ = (unsigned char) data;
*addr++ = (unsigned char) (data>>8);
*addrPtr = addr;
}
/* Write IFF ChunkType data to a byte array. */
static void WriteChunkType( unsigned char **addrPtr, unsigned long cktyp )
{
unsigned char *addr = *addrPtr;
*addr++ = (unsigned char) (cktyp>>24);
*addr++ = (unsigned char) (cktyp>>16);
*addr++ = (unsigned char) (cktyp>>8);
*addr++ = (unsigned char) cktyp;
*addrPtr = addr;
}
#define WAV_HEADER_SIZE (4 + 4 + 4 + /* RIFF+size+WAVE */ \
4 + 4 + 16 + /* fmt chunk */ \
4 + 4 ) /* data chunk */
/*********************************************************************************
* Open named file and write WAV header to the file.
* The header includes the DATA chunk type and size.
* Returns number of bytes written to file or negative error code.
*/
long Audio_WAV_OpenWriter( WAV_Writer *writer, const char *fileName, int frameRate, int samplesPerFrame )
{
unsigned int bytesPerSecond;
unsigned char header[ WAV_HEADER_SIZE ];
unsigned char *addr = header;
int numWritten;
writer->dataSize = 0;
writer->dataSizeOffset = 0;
writer->fid = fopen( fileName, "wb" );
if( writer->fid == NULL )
{
return -1;
}
/* Write RIFF header. */
WriteChunkType( &addr, RIFF_ID );
/* Write RIFF size as zero for now. Will patch later. */
WriteLongLE( &addr, 0 );
/* Write WAVE form ID. */
WriteChunkType( &addr, WAVE_ID );
/* Write format chunk based on AudioSample structure. */
WriteChunkType( &addr, FMT_ID );
WriteLongLE( &addr, 16 );
WriteShortLE( &addr, WAVE_FORMAT_PCM );
bytesPerSecond = frameRate * samplesPerFrame * sizeof( short);
WriteShortLE( &addr, (short) samplesPerFrame );
WriteLongLE( &addr, frameRate );
WriteLongLE( &addr, bytesPerSecond );
WriteShortLE( &addr, (short) (samplesPerFrame * sizeof( short)) ); /* bytesPerBlock */
WriteShortLE( &addr, (short) 16 ); /* bits per sample */
/* Write ID and size for 'data' chunk. */
WriteChunkType( &addr, DATA_ID );
/* Save offset so we can patch it later. */
writer->dataSizeOffset = (int) (addr - header);
WriteLongLE( &addr, 0 );
numWritten = fwrite( header, 1, sizeof(header), writer->fid );
if( numWritten != sizeof(header) ) return -1;
return (int) numWritten;
}
/*********************************************************************************
* Write to the data chunk portion of a WAV file.
* Returns bytes written or negative error code.
*/
long Audio_WAV_WriteShorts( WAV_Writer *writer,
short *samples,
int numSamples
)
{
unsigned char buffer[2];
unsigned char *bufferPtr;
int i;
short *p = samples;
int numWritten;
int bytesWritten;
if( numSamples <= 0 )
{
return -1;
}
for( i=0; i<numSamples; i++ )
{
bufferPtr = buffer;
WriteShortLE( &bufferPtr, *p++ );
numWritten = fwrite( buffer, 1, sizeof( buffer), writer->fid );
if( numWritten != sizeof(buffer) ) return -1;
}
bytesWritten = numSamples * sizeof(short);
writer->dataSize += bytesWritten;
return (int) bytesWritten;
}
/*********************************************************************************
* Close WAV file.
* Update chunk sizes so it can be read by audio applications.
*/
long Audio_WAV_CloseWriter( WAV_Writer *writer )
{
unsigned char buffer[4];
unsigned char *bufferPtr;
int numWritten;
int riffSize;
/* Go back to beginning of file and update DATA size */
int result = fseek( writer->fid, writer->dataSizeOffset, SEEK_SET );
if( result < 0 ) return result;
bufferPtr = buffer;
WriteLongLE( &bufferPtr, writer->dataSize );
numWritten = fwrite( buffer, 1, sizeof( buffer), writer->fid );
if( numWritten != sizeof(buffer) ) return -1;
/* Update RIFF size */
result = fseek( writer->fid, 4, SEEK_SET );
if( result < 0 ) return result;
riffSize = writer->dataSize + (WAV_HEADER_SIZE - 8);
bufferPtr = buffer;
WriteLongLE( &bufferPtr, riffSize );
numWritten = fwrite( buffer, 1, sizeof( buffer), writer->fid );
if( numWritten != sizeof(buffer) ) return -1;
fclose( writer->fid );
writer->fid = NULL;
return writer->dataSize;
}
/*********************************************************************************
* Simple test that write a sawtooth waveform to a file.
*/
#if 0
int main( void )
{
int i;
WAV_Writer writer;
int result;
#define NUM_SAMPLES (200)
short data[NUM_SAMPLES];
short saw = 0;
for( i=0; i<NUM_SAMPLES; i++ )
{
data[i] = saw;
saw += 293;
}
result = Audio_WAV_OpenWriter( &writer, "rendered_midi.wav", 44100, 1 );
if( result < 0 ) goto error;
for( i=0; i<15; i++ )
{
result = Audio_WAV_WriteShorts( &writer, data, NUM_SAMPLES );
if( result < 0 ) goto error;
}
result = Audio_WAV_CloseWriter( &writer );
if( result < 0 ) goto error;
return 0;
error:
printf("ERROR: result = %d\n", result );
return result;
}
#endif

View File

@@ -0,0 +1,103 @@
/*
* PortAudio Portable Real-Time Audio Library
* Latest Version at: http://www.portaudio.com
*
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
#ifndef _WAV_WRITER_H
#define _WAV_WRITER_H
/*
* WAV file writer.
*
* Author: Phil Burk
*/
#ifdef __cplusplus
extern "C" {
#endif
/* Define WAV Chunk and FORM types as 4 byte integers. */
#define RIFF_ID (('R'<<24) | ('I'<<16) | ('F'<<8) | 'F')
#define WAVE_ID (('W'<<24) | ('A'<<16) | ('V'<<8) | 'E')
#define FMT_ID (('f'<<24) | ('m'<<16) | ('t'<<8) | ' ')
#define DATA_ID (('d'<<24) | ('a'<<16) | ('t'<<8) | 'a')
#define FACT_ID (('f'<<24) | ('a'<<16) | ('c'<<8) | 't')
/* Errors returned by Audio_ParseSampleImage_WAV */
#define WAV_ERR_CHUNK_SIZE (-1) /* Chunk size is illegal or past file size. */
#define WAV_ERR_FILE_TYPE (-2) /* Not a WAV file. */
#define WAV_ERR_ILLEGAL_VALUE (-3) /* Illegal or unsupported value. Eg. 927 bits/sample */
#define WAV_ERR_FORMAT_TYPE (-4) /* Unsupported format, eg. compressed. */
#define WAV_ERR_TRUNCATED (-5) /* End of file missing. */
/* WAV PCM data format ID */
#define WAVE_FORMAT_PCM (1)
#define WAVE_FORMAT_IMA_ADPCM (0x0011)
typedef struct WAV_Writer_s
{
FILE *fid;
/* Offset in file for data size. */
int dataSizeOffset;
int dataSize;
} WAV_Writer;
/*********************************************************************************
* Open named file and write WAV header to the file.
* The header includes the DATA chunk type and size.
* Returns number of bytes written to file or negative error code.
*/
long Audio_WAV_OpenWriter( WAV_Writer *writer, const char *fileName, int frameRate, int samplesPerFrame );
/*********************************************************************************
* Write to the data chunk portion of a WAV file.
* Returns bytes written or negative error code.
*/
long Audio_WAV_WriteShorts( WAV_Writer *writer,
short *samples,
int numSamples
);
/*********************************************************************************
* Close WAV file.
* Update chunk sizes so it can be read by audio applications.
*/
long Audio_WAV_CloseWriter( WAV_Writer *writer );
#ifdef __cplusplus
};
#endif
#endif /* _WAV_WRITER_H */

View File

@@ -0,0 +1,368 @@
/** @file paqa_devs.c
@ingroup qa_src
@brief Self Testing Quality Assurance app for PortAudio
Try to open each device and run through all the
possible configurations. This test does not verify
that the configuration works well. It just verifies
that it does not crash. It requires a human to listen to
the outputs.
@author Phil Burk http://www.softsynth.com
Pieter adapted to V19 API. Test now relies heavily on
Pa_IsFormatSupported(). Uses same 'standard' sample rates
as in test pa_devs.c.
*/
/*
* $Id: paqa_devs.c 1756 2011-09-08 06:09:29Z philburk $
*
* This program uses the PortAudio Portable Audio Library.
* For more information see: http://www.portaudio.com
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
#include <stdio.h>
#include <math.h>
#include "portaudio.h"
#include "pa_trace.h"
/****************************************** Definitions ***********/
#define MODE_INPUT (0)
#define MODE_OUTPUT (1)
typedef struct PaQaData
{
unsigned long framesLeft;
int numChannels;
int bytesPerSample;
int mode;
short sawPhase;
PaSampleFormat format;
}
PaQaData;
/****************************************** Prototypes ***********/
static void TestDevices( int mode );
static void TestFormats( int mode, PaDeviceIndex deviceID, double sampleRate,
int numChannels );
static int TestAdvance( int mode, PaDeviceIndex deviceID, double sampleRate,
int numChannels, PaSampleFormat format );
static int QaCallback( const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData );
/****************************************** Globals ***********/
static int gNumPassed = 0;
static int gNumFailed = 0;
/****************************************** Macros ***********/
/* Print ERROR if it fails. Tally success or failure. */
/* Odd do-while wrapper seems to be needed for some compilers. */
#define EXPECT(_exp) \
do \
{ \
if ((_exp)) {\
/* printf("SUCCESS for %s\n", #_exp ); */ \
gNumPassed++; \
} \
else { \
printf("ERROR - 0x%x - %s for %s\n", result, \
((result == 0) ? "-" : Pa_GetErrorText(result)), \
#_exp ); \
gNumFailed++; \
goto error; \
} \
} while(0)
/*******************************************************************/
/* This routine will be called by the PortAudio engine when audio is needed.
** It may be called at interrupt level on some machines so don't do anything
** that could mess up the system like calling malloc() or free().
*/
static int QaCallback( const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData )
{
unsigned long i;
short phase;
PaQaData *data = (PaQaData *) userData;
(void) inputBuffer;
/* Play simple sawtooth wave. */
if( data->mode == MODE_OUTPUT )
{
phase = data->sawPhase;
switch( data->format )
{
case paFloat32:
{
float *out = (float *) outputBuffer;
for( i=0; i<framesPerBuffer; i++ )
{
phase += 0x123;
*out++ = (float) (phase * (1.0 / 32768.0));
if( data->numChannels == 2 )
{
*out++ = (float) (phase * (1.0 / 32768.0));
}
}
}
break;
case paInt32:
{
int *out = (int *) outputBuffer;
for( i=0; i<framesPerBuffer; i++ )
{
phase += 0x123;
*out++ = ((int) phase ) << 16;
if( data->numChannels == 2 )
{
*out++ = ((int) phase ) << 16;
}
}
}
break;
case paInt16:
{
short *out = (short *) outputBuffer;
for( i=0; i<framesPerBuffer; i++ )
{
phase += 0x123;
*out++ = phase;
if( data->numChannels == 2 )
{
*out++ = phase;
}
}
}
break;
default:
{
unsigned char *out = (unsigned char *) outputBuffer;
unsigned long numBytes = framesPerBuffer * data->numChannels * data->bytesPerSample;
for( i=0; i<numBytes; i++ )
{
*out++ = 0;
}
}
break;
}
data->sawPhase = phase;
}
/* Are we through yet? */
if( data->framesLeft > framesPerBuffer )
{
PaUtil_AddTraceMessage("QaCallback: running. framesLeft", data->framesLeft );
data->framesLeft -= framesPerBuffer;
return 0;
}
else
{
PaUtil_AddTraceMessage("QaCallback: DONE! framesLeft", data->framesLeft );
data->framesLeft = 0;
return 1;
}
}
/*******************************************************************/
int main(void);
int main(void)
{
PaError result;
EXPECT( ((result=Pa_Initialize()) == 0) );
printf("Test OUTPUT ---------------\n");
TestDevices( MODE_OUTPUT );
printf("Test INPUT ---------------\n");
TestDevices( MODE_INPUT );
error:
Pa_Terminate();
printf("QA Report: %d passed, %d failed.\n", gNumPassed, gNumFailed );
return (gNumFailed > 0) ? 1 : 0;
}
/*******************************************************************
* Try each output device, through its full range of capabilities. */
static void TestDevices( int mode )
{
int id, jc, i;
int maxChannels;
const PaDeviceInfo *pdi;
static double standardSampleRates[] = { 8000.0, 9600.0, 11025.0, 12000.0,
16000.0, 22050.0, 24000.0,
32000.0, 44100.0, 48000.0,
88200.0, 96000.0,
-1.0 }; /* Negative terminated list. */
int numDevices = Pa_GetDeviceCount();
for( id=0; id<numDevices; id++ ) /* Iterate through all devices. */
{
pdi = Pa_GetDeviceInfo( id );
/* Try 1 to maxChannels on each device. */
maxChannels = (( mode == MODE_INPUT ) ? pdi->maxInputChannels : pdi->maxOutputChannels);
for( jc=1; jc<=maxChannels; jc++ )
{
printf("\n========================================================================\n");
printf(" Device = %s\n", pdi->name );
printf("========================================================================\n");
/* Try each standard sample rate. */
for( i=0; standardSampleRates[i] > 0; i++ )
{
TestFormats( mode, (PaDeviceIndex)id, standardSampleRates[i], jc );
}
}
}
}
/*******************************************************************/
static void TestFormats( int mode, PaDeviceIndex deviceID, double sampleRate,
int numChannels )
{
TestAdvance( mode, deviceID, sampleRate, numChannels, paFloat32 );
TestAdvance( mode, deviceID, sampleRate, numChannels, paInt16 );
TestAdvance( mode, deviceID, sampleRate, numChannels, paInt32 );
/* TestAdvance( mode, deviceID, sampleRate, numChannels, paInt24 ); */
}
/*******************************************************************/
static int TestAdvance( int mode, PaDeviceIndex deviceID, double sampleRate,
int numChannels, PaSampleFormat format )
{
PaStreamParameters inputParameters, outputParameters, *ipp, *opp;
PaStream *stream = NULL;
PaError result = paNoError;
PaQaData myData;
#define FRAMES_PER_BUFFER (64)
const int kNumSeconds = 100;
/* Setup data for synthesis thread. */
myData.framesLeft = (unsigned long) (sampleRate * kNumSeconds);
myData.numChannels = numChannels;
myData.mode = mode;
myData.format = format;
switch( format )
{
case paFloat32:
case paInt32:
case paInt24:
myData.bytesPerSample = 4;
break;
/* case paPackedInt24:
myData.bytesPerSample = 3;
break; */
default:
myData.bytesPerSample = 2;
break;
}
if( mode == MODE_INPUT )
{
inputParameters.device = deviceID;
inputParameters.channelCount = numChannels;
inputParameters.sampleFormat = format;
inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
inputParameters.hostApiSpecificStreamInfo = NULL;
ipp = &inputParameters;
}
else
{
ipp = NULL;
}
if( mode == MODE_OUTPUT )
{
outputParameters.device = deviceID;
outputParameters.channelCount = numChannels;
outputParameters.sampleFormat = format;
outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
outputParameters.hostApiSpecificStreamInfo = NULL;
opp = &outputParameters;
}
else
{
opp = NULL;
}
if(paFormatIsSupported == Pa_IsFormatSupported( ipp, opp, sampleRate ))
{
printf("------ TestAdvance: %s, device = %d, rate = %g, numChannels = %d, format = %lu -------\n",
( mode == MODE_INPUT ) ? "INPUT" : "OUTPUT",
deviceID, sampleRate, numChannels, (unsigned long)format);
EXPECT( ((result = Pa_OpenStream( &stream,
ipp,
opp,
sampleRate,
FRAMES_PER_BUFFER,
paClipOff, /* we won't output out of range samples so don't bother clipping them */
QaCallback,
&myData ) ) == 0) );
if( stream )
{
PaTime oldStamp, newStamp;
unsigned long oldFrames;
int minDelay = ( mode == MODE_INPUT ) ? 1000 : 400;
/* Was:
int minNumBuffers = Pa_GetMinNumBuffers( FRAMES_PER_BUFFER, sampleRate );
int msec = (int) ((minNumBuffers * 3 * 1000.0 * FRAMES_PER_BUFFER) / sampleRate);
*/
int msec = (int)( 3.0 *
(( mode == MODE_INPUT ) ? inputParameters.suggestedLatency : outputParameters.suggestedLatency ));
if( msec < minDelay ) msec = minDelay;
printf("msec = %d\n", msec); /**/
EXPECT( ((result=Pa_StartStream( stream )) == 0) );
/* Check to make sure PortAudio is advancing timeStamp. */
oldStamp = Pa_GetStreamTime(stream);
Pa_Sleep(msec);
newStamp = Pa_GetStreamTime(stream);
printf("oldStamp = %g, newStamp = %g\n", oldStamp, newStamp ); /**/
EXPECT( (oldStamp < newStamp) );
/* Check to make sure callback is decrementing framesLeft. */
oldFrames = myData.framesLeft;
Pa_Sleep(msec);
printf("oldFrames = %lu, myData.framesLeft = %lu\n", oldFrames, myData.framesLeft ); /**/
EXPECT( (oldFrames > myData.framesLeft) );
EXPECT( ((result=Pa_CloseStream( stream )) == 0) );
stream = NULL;
}
}
return 0;
error:
if( stream != NULL ) Pa_CloseStream( stream );
return -1;
}

View File

@@ -0,0 +1,403 @@
/** @file paqa_errs.c
@ingroup qa_src
@brief Self Testing Quality Assurance app for PortAudio
Do lots of bad things to test error reporting.
@author Phil Burk http://www.softsynth.com
Pieter Suurmond adapted to V19 API.
*/
/*
* $Id: paqa_errs.c 1756 2011-09-08 06:09:29Z philburk $
*
* This program uses the PortAudio Portable Audio Library.
* For more information see: http://www.portaudio.com
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
#include <stdio.h>
#include <math.h>
#include "portaudio.h"
/*--------- Definitions ---------*/
#define MODE_INPUT (0)
#define MODE_OUTPUT (1)
#define FRAMES_PER_BUFFER (64)
#define SAMPLE_RATE (44100.0)
typedef struct PaQaData
{
unsigned long framesLeft;
int numChannels;
int bytesPerSample;
int mode;
}
PaQaData;
static int gNumPassed = 0; /* Two globals */
static int gNumFailed = 0;
/*------------------- Macros ------------------------------*/
/* Print ERROR if it fails. Tally success or failure. Odd */
/* do-while wrapper seems to be needed for some compilers. */
#define EXPECT(_exp) \
do \
{ \
if ((_exp)) {\
gNumPassed++; \
} \
else { \
printf("\nERROR - 0x%x - %s for %s\n", result, Pa_GetErrorText(result), #_exp ); \
gNumFailed++; \
goto error; \
} \
} while(0)
#define HOPEFOR(_exp) \
do \
{ \
if ((_exp)) {\
gNumPassed++; \
} \
else { \
printf("\nERROR - 0x%x - %s for %s\n", result, Pa_GetErrorText(result), #_exp ); \
gNumFailed++; \
} \
} while(0)
/*-------------------------------------------------------------------------*/
/* This routine will be called by the PortAudio engine when audio is needed.
It may be called at interrupt level on some machines so don't do anything
that could mess up the system like calling malloc() or free().
*/
static int QaCallback( const void* inputBuffer,
void* outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void* userData )
{
unsigned long i;
unsigned char* out = (unsigned char *) outputBuffer;
PaQaData* data = (PaQaData *) userData;
(void)inputBuffer; /* Prevent "unused variable" warnings. */
/* Zero out buffer so we don't hear terrible noise. */
if( data->mode == MODE_OUTPUT )
{
unsigned long numBytes = framesPerBuffer * data->numChannels * data->bytesPerSample;
for( i=0; i<numBytes; i++ )
{
*out++ = 0;
}
}
/* Are we through yet? */
if( data->framesLeft > framesPerBuffer )
{
data->framesLeft -= framesPerBuffer;
return 0;
}
else
{
data->framesLeft = 0;
return 1;
}
}
static PaDeviceIndex FindInputOnlyDevice(void)
{
PaDeviceIndex result = Pa_GetDefaultInputDevice();
if( result != paNoDevice && Pa_GetDeviceInfo(result)->maxOutputChannels == 0 )
return result;
for( result = 0; result < Pa_GetDeviceCount(); ++result )
{
if( Pa_GetDeviceInfo(result)->maxOutputChannels == 0 )
return result;
}
return paNoDevice;
}
static PaDeviceIndex FindOutputOnlyDevice(void)
{
PaDeviceIndex result = Pa_GetDefaultOutputDevice();
if( result != paNoDevice && Pa_GetDeviceInfo(result)->maxInputChannels == 0 )
return result;
for( result = 0; result < Pa_GetDeviceCount(); ++result )
{
if( Pa_GetDeviceInfo(result)->maxInputChannels == 0 )
return result;
}
return paNoDevice;
}
/*-------------------------------------------------------------------------------------------------*/
static int TestBadOpens( void )
{
PaStream* stream = NULL;
PaError result;
PaQaData myData;
PaStreamParameters ipp, opp;
const PaDeviceInfo* info = NULL;
/* Setup data for synthesis thread. */
myData.framesLeft = (unsigned long) (SAMPLE_RATE * 100); /* 100 seconds */
myData.numChannels = 1;
myData.mode = MODE_OUTPUT;
/*----------------------------- No devices specified: */
ipp.device = opp.device = paNoDevice;
ipp.channelCount = opp.channelCount = 0; /* Also no channels. */
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
ipp.sampleFormat = opp.sampleFormat = paFloat32;
/* Take the low latency of the default device for all subsequent tests. */
info = Pa_GetDeviceInfo(Pa_GetDefaultInputDevice());
ipp.suggestedLatency = info ? info->defaultLowInputLatency : 0.100;
info = Pa_GetDeviceInfo(Pa_GetDefaultOutputDevice());
opp.suggestedLatency = info ? info->defaultLowOutputLatency : 0.100;
HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, &opp,
SAMPLE_RATE, FRAMES_PER_BUFFER,
paClipOff, QaCallback, &myData )) == paInvalidDevice));
/*----------------------------- No devices specified #2: */
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, NULL,
SAMPLE_RATE, FRAMES_PER_BUFFER,
paClipOff, QaCallback, &myData )) == paInvalidDevice));
/*----------------------------- Out of range input device specified: */
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
ipp.sampleFormat = opp.sampleFormat = paFloat32;
ipp.channelCount = 0; ipp.device = Pa_GetDeviceCount(); /* And no output device, and no channels. */
opp.channelCount = 0; opp.device = paNoDevice;
HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, NULL,
SAMPLE_RATE, FRAMES_PER_BUFFER,
paClipOff, QaCallback, &myData )) == paInvalidDevice));
/*----------------------------- Out of range output device specified: */
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
ipp.sampleFormat = opp.sampleFormat = paFloat32;
ipp.channelCount = 0; ipp.device = paNoDevice; /* And no input device, and no channels. */
opp.channelCount = 0; opp.device = Pa_GetDeviceCount();
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp,
SAMPLE_RATE, FRAMES_PER_BUFFER,
paClipOff, QaCallback, &myData )) == paInvalidDevice));
if (Pa_GetDefaultInputDevice() != paNoDevice) {
/*----------------------------- Zero input channels: */
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
ipp.sampleFormat = opp.sampleFormat = paFloat32;
ipp.channelCount = 0; ipp.device = Pa_GetDefaultInputDevice();
opp.channelCount = 0; opp.device = paNoDevice; /* And no output device, and no output channels. */
HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, NULL,
SAMPLE_RATE, FRAMES_PER_BUFFER,
paClipOff, QaCallback, &myData )) == paInvalidChannelCount));
}
if (Pa_GetDefaultOutputDevice() != paNoDevice) {
/*----------------------------- Zero output channels: */
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
ipp.sampleFormat = opp.sampleFormat = paFloat32;
ipp.channelCount = 0; ipp.device = paNoDevice; /* And no input device, and no input channels. */
opp.channelCount = 0; opp.device = Pa_GetDefaultOutputDevice();
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp,
SAMPLE_RATE, FRAMES_PER_BUFFER,
paClipOff, QaCallback, &myData )) == paInvalidChannelCount));
}
/*----------------------------- Nonzero input and output channels but no output device: */
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
ipp.sampleFormat = opp.sampleFormat = paFloat32;
ipp.channelCount = 2; ipp.device = Pa_GetDefaultInputDevice(); /* Both stereo. */
opp.channelCount = 2; opp.device = paNoDevice;
HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, &opp,
SAMPLE_RATE, FRAMES_PER_BUFFER,
paClipOff, QaCallback, &myData )) == paInvalidDevice));
/*----------------------------- Nonzero input and output channels but no input device: */
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
ipp.sampleFormat = opp.sampleFormat = paFloat32;
ipp.channelCount = 2; ipp.device = paNoDevice;
opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice();
HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, &opp,
SAMPLE_RATE, FRAMES_PER_BUFFER,
paClipOff, QaCallback, &myData )) == paInvalidDevice));
if (Pa_GetDefaultOutputDevice() != paNoDevice) {
/*----------------------------- NULL stream pointer: */
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
ipp.sampleFormat = opp.sampleFormat = paFloat32;
ipp.channelCount = 0; ipp.device = paNoDevice; /* Output is more likely than input. */
opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice(); /* Only 2 output channels. */
HOPEFOR(((result = Pa_OpenStream(NULL, &ipp, &opp,
SAMPLE_RATE, FRAMES_PER_BUFFER,
paClipOff, QaCallback, &myData )) == paBadStreamPtr));
/*----------------------------- Low sample rate: */
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
ipp.sampleFormat = opp.sampleFormat = paFloat32;
ipp.channelCount = 0; ipp.device = paNoDevice;
opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice();
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp,
1.0, FRAMES_PER_BUFFER, /* 1 cycle per second (1 Hz) is too low. */
paClipOff, QaCallback, &myData )) == paInvalidSampleRate));
/*----------------------------- High sample rate: */
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
ipp.sampleFormat = opp.sampleFormat = paFloat32;
ipp.channelCount = 0; ipp.device = paNoDevice;
opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice();
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp,
10000000.0, FRAMES_PER_BUFFER, /* 10^6 cycles per second (10 MHz) is too high. */
paClipOff, QaCallback, &myData )) == paInvalidSampleRate));
/*----------------------------- NULL callback: */
/* NULL callback is valid in V19 -- it means use blocking read/write stream
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
ipp.sampleFormat = opp.sampleFormat = paFloat32;
ipp.channelCount = 0; ipp.device = paNoDevice;
opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice();
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp,
SAMPLE_RATE, FRAMES_PER_BUFFER,
paClipOff,
NULL,
&myData )) == paNullCallback));
*/
/*----------------------------- Bad flag: */
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
ipp.sampleFormat = opp.sampleFormat = paFloat32;
ipp.channelCount = 0; ipp.device = paNoDevice;
opp.channelCount = 2; opp.device = Pa_GetDefaultOutputDevice();
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp,
SAMPLE_RATE, FRAMES_PER_BUFFER,
255, /* Is 8 maybe legal V19 API? */
QaCallback, &myData )) == paInvalidFlag));
}
/*----------------------------- using input device as output device: */
if( FindInputOnlyDevice() != paNoDevice )
{
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
ipp.sampleFormat = opp.sampleFormat = paFloat32;
ipp.channelCount = 0; ipp.device = paNoDevice; /* And no input device, and no channels. */
opp.channelCount = 2; opp.device = FindInputOnlyDevice();
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, &opp,
SAMPLE_RATE, FRAMES_PER_BUFFER,
paClipOff, QaCallback, &myData )) == paInvalidChannelCount));
}
/*----------------------------- using output device as input device: */
if( FindOutputOnlyDevice() != paNoDevice )
{
ipp.hostApiSpecificStreamInfo = opp.hostApiSpecificStreamInfo = NULL;
ipp.sampleFormat = opp.sampleFormat = paFloat32;
ipp.channelCount = 2; ipp.device = FindOutputOnlyDevice();
opp.channelCount = 0; opp.device = paNoDevice; /* And no output device, and no channels. */
HOPEFOR(((result = Pa_OpenStream(&stream, &ipp, NULL,
SAMPLE_RATE, FRAMES_PER_BUFFER,
paClipOff, QaCallback, &myData )) == paInvalidChannelCount));
}
if( stream != NULL ) Pa_CloseStream( stream );
return result;
}
/*-----------------------------------------------------------------------------------------*/
static int TestBadActions( void )
{
PaStream* stream = NULL;
const PaDeviceInfo* deviceInfo = NULL;
PaError result = 0;
PaQaData myData;
PaStreamParameters opp;
const PaDeviceInfo* info = NULL;
/* Setup data for synthesis thread. */
myData.framesLeft = (unsigned long)(SAMPLE_RATE * 100); /* 100 seconds */
myData.numChannels = 1;
myData.mode = MODE_OUTPUT;
opp.device = Pa_GetDefaultOutputDevice(); /* Default output. */
opp.channelCount = 2; /* Stereo output. */
opp.hostApiSpecificStreamInfo = NULL;
opp.sampleFormat = paFloat32;
info = Pa_GetDeviceInfo(opp.device);
opp.suggestedLatency = info ? info->defaultLowOutputLatency : 0.100;
if (opp.device != paNoDevice) {
HOPEFOR(((result = Pa_OpenStream(&stream, NULL, /* Take NULL as input parame- */
&opp, /* ters, meaning try only output. */
SAMPLE_RATE, FRAMES_PER_BUFFER,
paClipOff, QaCallback, &myData )) == paNoError));
}
HOPEFOR(((deviceInfo = Pa_GetDeviceInfo(paNoDevice)) == NULL));
HOPEFOR(((deviceInfo = Pa_GetDeviceInfo(87654)) == NULL));
HOPEFOR(((result = Pa_StartStream(NULL)) == paBadStreamPtr));
HOPEFOR(((result = Pa_StopStream(NULL)) == paBadStreamPtr));
HOPEFOR(((result = Pa_IsStreamStopped(NULL)) == paBadStreamPtr));
HOPEFOR(((result = Pa_IsStreamActive(NULL)) == paBadStreamPtr));
HOPEFOR(((result = Pa_CloseStream(NULL)) == paBadStreamPtr));
HOPEFOR(((result = Pa_SetStreamFinishedCallback(NULL, NULL)) == paBadStreamPtr));
HOPEFOR(((result = !Pa_GetStreamInfo(NULL))));
HOPEFOR(((result = Pa_GetStreamTime(NULL)) == 0.0));
HOPEFOR(((result = Pa_GetStreamCpuLoad(NULL)) == 0.0));
HOPEFOR(((result = Pa_ReadStream(NULL, NULL, 0)) == paBadStreamPtr));
HOPEFOR(((result = Pa_WriteStream(NULL, NULL, 0)) == paBadStreamPtr));
/** @todo test Pa_GetStreamReadAvailable and Pa_GetStreamWriteAvailable */
if (stream != NULL) Pa_CloseStream(stream);
return result;
}
/*---------------------------------------------------------------------*/
int main(void);
int main(void)
{
PaError result;
EXPECT(((result = Pa_Initialize()) == paNoError));
TestBadOpens();
TestBadActions();
error:
Pa_Terminate();
printf("QA Report: %d passed, %d failed.\n", gNumPassed, gNumFailed);
return 0;
}

View File

@@ -0,0 +1,482 @@
/** @file paqa_latency.c
@ingroup qa_src
@brief Test latency estimates.
@author Ross Bencina <rossb@audiomulch.com>
@author Phil Burk <philburk@softsynth.com>
*/
/*
* $Id: patest_sine.c 1368 2008-03-01 00:38:27Z rossb $
*
* This program uses the PortAudio Portable Audio Library.
* For more information see: http://www.portaudio.com/
* Copyright (c) 1999-2000 Ross Bencina and Phil Burk
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* The text above constitutes the entire PortAudio license; however,
* the PortAudio community also makes the following non-binding requests:
*
* Any person wishing to distribute modifications to the Software is
* requested to send the modifications to the original developer so that
* they can be incorporated into the canonical version. It is also
* requested that these non-binding requests be included along with the
* license above.
*/
#include <stdio.h>
#include <math.h>
#include "portaudio.h"
#include "loopback/src/qa_tools.h"
#define NUM_SECONDS (5)
#define SAMPLE_RATE (44100)
#define FRAMES_PER_BUFFER (64)
#ifndef M_PI
#define M_PI (3.14159265)
#endif
#define TABLE_SIZE (200)
typedef struct
{
float sine[TABLE_SIZE];
int left_phase;
int right_phase;
char message[20];
int minFramesPerBuffer;
int maxFramesPerBuffer;
int callbackCount;
PaTime minDeltaDacTime;
PaTime maxDeltaDacTime;
PaStreamCallbackTimeInfo previousTimeInfo;
}
paTestData;
/* Used to tally the results of the QA tests. */
int g_testsPassed = 0;
int g_testsFailed = 0;
/* This routine will be called by the PortAudio engine when audio is needed.
** It may called at interrupt level on some machines so don't do anything
** that could mess up the system like calling malloc() or free().
*/
static int patestCallback( const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData )
{
paTestData *data = (paTestData*)userData;
float *out = (float*)outputBuffer;
unsigned long i;
(void) timeInfo; /* Prevent unused variable warnings. */
(void) statusFlags;
(void) inputBuffer;
if( data->minFramesPerBuffer > framesPerBuffer )
{
data->minFramesPerBuffer = framesPerBuffer;
}
if( data->maxFramesPerBuffer < framesPerBuffer )
{
data->maxFramesPerBuffer = framesPerBuffer;
}
/* Measure min and max output time stamp delta. */
if( data->callbackCount > 0 )
{
PaTime delta = timeInfo->outputBufferDacTime - data->previousTimeInfo.outputBufferDacTime;
if( data->minDeltaDacTime > delta )
{
data->minDeltaDacTime = delta;
}
if( data->maxDeltaDacTime < delta )
{
data->maxDeltaDacTime = delta;
}
}
data->previousTimeInfo = *timeInfo;
for( i=0; i<framesPerBuffer; i++ )
{
*out++ = data->sine[data->left_phase]; /* left */
*out++ = data->sine[data->right_phase]; /* right */
data->left_phase += 1;
if( data->left_phase >= TABLE_SIZE ) data->left_phase -= TABLE_SIZE;
data->right_phase += 3; /* higher pitch so we can distinguish left and right. */
if( data->right_phase >= TABLE_SIZE ) data->right_phase -= TABLE_SIZE;
}
data->callbackCount += 1;
return paContinue;
}
PaError paqaCheckLatency( PaStreamParameters *outputParamsPtr,
paTestData *dataPtr, double sampleRate, unsigned long framesPerBuffer )
{
PaError err;
PaStream *stream;
const PaStreamInfo* streamInfo;
dataPtr->minFramesPerBuffer = 9999999;
dataPtr->maxFramesPerBuffer = 0;
dataPtr->minDeltaDacTime = 9999999.0;
dataPtr->maxDeltaDacTime = 0.0;
dataPtr->callbackCount = 0;
printf("Stream parameter: suggestedOutputLatency = %g\n", outputParamsPtr->suggestedLatency );
if( framesPerBuffer == paFramesPerBufferUnspecified ){
printf("Stream parameter: user framesPerBuffer = paFramesPerBufferUnspecified\n" );
}else{
printf("Stream parameter: user framesPerBuffer = %lu\n", framesPerBuffer );
}
err = Pa_OpenStream(
&stream,
NULL, /* no input */
outputParamsPtr,
sampleRate,
framesPerBuffer,
paClipOff, /* we won't output out of range samples so don't bother clipping them */
patestCallback,
dataPtr );
if( err != paNoError ) goto error1;
streamInfo = Pa_GetStreamInfo( stream );
printf("Stream info: inputLatency = %g\n", streamInfo->inputLatency );
printf("Stream info: outputLatency = %g\n", streamInfo->outputLatency );
err = Pa_StartStream( stream );
if( err != paNoError ) goto error2;
printf("Play for %d seconds.\n", NUM_SECONDS );
Pa_Sleep( NUM_SECONDS * 1000 );
printf(" minFramesPerBuffer = %4d\n", dataPtr->minFramesPerBuffer );
printf(" maxFramesPerBuffer = %4d\n", dataPtr->maxFramesPerBuffer );
printf(" minDeltaDacTime = %f\n", dataPtr->minDeltaDacTime );
printf(" maxDeltaDacTime = %f\n", dataPtr->maxDeltaDacTime );
err = Pa_StopStream( stream );
if( err != paNoError ) goto error2;
err = Pa_CloseStream( stream );
Pa_Sleep( 1 * 1000 );
printf("-------------------------------------\n");
return err;
error2:
Pa_CloseStream( stream );
error1:
printf("-------------------------------------\n");
return err;
}
/*******************************************************************/
static int paqaNoopCallback( const void *inputBuffer, void *outputBuffer,
unsigned long framesPerBuffer,
const PaStreamCallbackTimeInfo* timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData )
{
(void)inputBuffer;
(void)outputBuffer;
(void)framesPerBuffer;
(void)timeInfo;
(void)statusFlags;
(void)userData;
return paContinue;
}
/*******************************************************************/
static int paqaCheckMultipleSuggested( PaDeviceIndex deviceIndex, int isInput )
{
int i;
int numLoops = 10;
PaError err;
PaStream *stream;
PaStreamParameters streamParameters;
const PaStreamInfo* streamInfo;
double lowLatency;
double highLatency;
double finalLatency;
double sampleRate = SAMPLE_RATE;
const PaDeviceInfo *pdi = Pa_GetDeviceInfo( deviceIndex );
double previousLatency = 0.0;
int numChannels = 1;
float toleranceRatio = 1.0;
printf("------------------------ paqaCheckMultipleSuggested - %s\n",
(isInput ? "INPUT" : "OUTPUT") );
if( isInput )
{
lowLatency = pdi->defaultLowInputLatency;
highLatency = pdi->defaultHighInputLatency;
numChannels = (pdi->maxInputChannels < 2) ? 1 : 2;
}
else
{
lowLatency = pdi->defaultLowOutputLatency;
highLatency = pdi->defaultHighOutputLatency;
numChannels = (pdi->maxOutputChannels < 2) ? 1 : 2;
}
streamParameters.channelCount = numChannels;
streamParameters.device = deviceIndex;
streamParameters.hostApiSpecificStreamInfo = NULL;
streamParameters.sampleFormat = paFloat32;
sampleRate = pdi->defaultSampleRate;
printf(" lowLatency = %g\n", lowLatency );
printf(" highLatency = %g\n", highLatency );
printf(" numChannels = %d\n", numChannels );
printf(" sampleRate = %g\n", sampleRate );
if( (highLatency - lowLatency) < 0.001 )
{
numLoops = 1;
}
for( i=0; i<numLoops; i++ )
{
if( numLoops == 1 )
streamParameters.suggestedLatency = lowLatency;
else
streamParameters.suggestedLatency = lowLatency + ((highLatency - lowLatency) * i /(numLoops - 1));
printf(" suggestedLatency[%d] = %6.4f\n", i, streamParameters.suggestedLatency );
err = Pa_OpenStream(
&stream,
(isInput ? &streamParameters : NULL),
(isInput ? NULL : &streamParameters),
sampleRate,
paFramesPerBufferUnspecified,
paClipOff, /* we won't output out of range samples so don't bother clipping them */
paqaNoopCallback,
NULL );
if( err != paNoError ) goto error;
streamInfo = Pa_GetStreamInfo( stream );
err = Pa_CloseStream( stream );
if( isInput )
{
finalLatency = streamInfo->inputLatency;
}
else
{
finalLatency = streamInfo->outputLatency;
}
printf(" finalLatency = %6.4f\n", finalLatency );
/* For the default low & high latency values, expect quite close; for other requested
* values, at worst the next power-of-2 may result (eg 513 -> 1024) */
toleranceRatio = ( (i == 0) || (i == ( numLoops - 1 )) ) ? 0.1 : 1.0;
QA_ASSERT_CLOSE( "final latency should be close to suggested latency",
streamParameters.suggestedLatency, finalLatency, (streamParameters.suggestedLatency * toleranceRatio) );
if( i == 0 )
{
previousLatency = finalLatency;
}
}
if( numLoops > 1 )
{
QA_ASSERT_TRUE( " final latency should increase with suggested latency", (finalLatency > previousLatency) );
}
return 0;
error:
return -1;
}
/*******************************************************************/
static int paqaVerifySuggestedLatency( void )
{
PaDeviceIndex id;
int result = 0;
const PaDeviceInfo *pdi;
int numDevices = Pa_GetDeviceCount();
printf("\n ------------------------ paqaVerifySuggestedLatency\n");
for( id=0; id<numDevices; id++ ) /* Iterate through all devices. */
{
pdi = Pa_GetDeviceInfo( id );
printf("\nUsing device #%d: '%s' (%s)\n", id, pdi->name, Pa_GetHostApiInfo(pdi->hostApi)->name);
if( pdi->maxOutputChannels > 0 )
{
if( paqaCheckMultipleSuggested( id, 0 ) < 0 )
{
printf("OUTPUT CHECK FAILED !!! #%d: '%s'\n", id, pdi->name);
result -= 1;
}
}
if( pdi->maxInputChannels > 0 )
{
if( paqaCheckMultipleSuggested( id, 1 ) < 0 )
{
printf("INPUT CHECK FAILED !!! #%d: '%s'\n", id, pdi->name);
result -= 1;
}
}
}
return result;
}
/*******************************************************************/
static int paqaVerifyDeviceInfoLatency( void )
{
PaDeviceIndex id;
const PaDeviceInfo *pdi;
int numDevices = Pa_GetDeviceCount();
printf("\n ------------------------ paqaVerifyDeviceInfoLatency\n");
for( id=0; id<numDevices; id++ ) /* Iterate through all devices. */
{
pdi = Pa_GetDeviceInfo( id );
printf("Using device #%d: '%s' (%s)\n", id, pdi->name, Pa_GetHostApiInfo(pdi->hostApi)->name);
if( pdi->maxOutputChannels > 0 )
{
printf(" Output defaultLowOutputLatency = %f seconds\n", pdi->defaultLowOutputLatency);
printf(" Output defaultHighOutputLatency = %f seconds\n", pdi->defaultHighOutputLatency);
QA_ASSERT_TRUE( "defaultLowOutputLatency should be > 0", (pdi->defaultLowOutputLatency > 0.0) );
QA_ASSERT_TRUE( "defaultHighOutputLatency should be > 0", (pdi->defaultHighOutputLatency > 0.0) );
//QA_ASSERT_TRUE( "defaultHighOutputLatency should be > Low", (pdi->defaultHighOutputLatency > pdi->defaultLowOutputLatency) );
}
if( pdi->maxInputChannels > 0 )
{
printf(" Input defaultLowOutputLatency = %f seconds\n", pdi->defaultLowInputLatency);
printf(" Input defaultHighOutputLatency = %f seconds\n", pdi->defaultHighInputLatency);
QA_ASSERT_TRUE( "defaultLowOutputLatency should be > 0", (pdi->defaultLowInputLatency > 0.0) );
QA_ASSERT_TRUE( "defaultHighOutputLatency should be > 0", (pdi->defaultHighInputLatency > 0.0) );
//QA_ASSERT_TRUE( "defaultHighOutputLatency should be > Low", (pdi->defaultHighInputLatency > pdi->defaultLowInputLatency) );
}
}
return 0;
error:
return -1;
}
/*******************************************************************/
int main(void);
int main(void)
{
PaStreamParameters outputParameters;
PaError err;
paTestData data;
const PaDeviceInfo *deviceInfo;
int i;
int framesPerBuffer;
double sampleRate = SAMPLE_RATE;
printf("\nPortAudio QA: investigate output latency.\n");
/* initialise sinusoidal wavetable */
for( i=0; i<TABLE_SIZE; i++ )
{
data.sine[i] = (float) sin( ((double)i/(double)TABLE_SIZE) * M_PI * 2. );
}
data.left_phase = data.right_phase = 0;
err = Pa_Initialize();
if( err != paNoError ) goto error;
/* Run self tests. */
if( paqaVerifyDeviceInfoLatency() < 0 ) goto error;
if( paqaVerifySuggestedLatency() < 0 ) goto error;
outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
if (outputParameters.device == paNoDevice) {
fprintf(stderr,"Error: No default output device.\n");
goto error;
}
printf("\n\nNow running Audio Output Tests...\n");
printf("-------------------------------------\n");
outputParameters.channelCount = 2; /* stereo output */
outputParameters.sampleFormat = paFloat32; /* 32 bit floating point output */
deviceInfo = Pa_GetDeviceInfo( outputParameters.device );
printf("Using device #%d: '%s' (%s)\n", outputParameters.device, deviceInfo->name, Pa_GetHostApiInfo(deviceInfo->hostApi)->name);
printf("Device info: defaultLowOutputLatency = %f seconds\n", deviceInfo->defaultLowOutputLatency);
printf("Device info: defaultHighOutputLatency = %f seconds\n", deviceInfo->defaultHighOutputLatency);
sampleRate = deviceInfo->defaultSampleRate;
printf("Sample Rate for following tests: %g\n", sampleRate);
outputParameters.hostApiSpecificStreamInfo = NULL;
printf("-------------------------------------\n");
// Try to use a small buffer that is smaller than we think the device can handle.
// Try to force combining multiple user buffers into a host buffer.
printf("------------- Try a very small buffer.\n");
framesPerBuffer = 9;
outputParameters.suggestedLatency = deviceInfo->defaultLowOutputLatency;
err = paqaCheckLatency( &outputParameters, &data, sampleRate, framesPerBuffer );
if( err != paNoError ) goto error;
printf("------------- 64 frame buffer with 1.1 * defaultLow latency.\n");
framesPerBuffer = 64;
outputParameters.suggestedLatency = deviceInfo->defaultLowOutputLatency * 1.1;
err = paqaCheckLatency( &outputParameters, &data, sampleRate, framesPerBuffer );
if( err != paNoError ) goto error;
// Try to create a huge buffer that is bigger than the allowed device maximum.
printf("------------- Try a huge buffer.\n");
framesPerBuffer = 16*1024;
outputParameters.suggestedLatency = ((double)framesPerBuffer) / sampleRate; // approximate
err = paqaCheckLatency( &outputParameters, &data, sampleRate, framesPerBuffer );
if( err != paNoError ) goto error;
printf("------------- Try suggestedLatency = 0.0\n");
outputParameters.suggestedLatency = 0.0;
err = paqaCheckLatency( &outputParameters, &data, sampleRate, paFramesPerBufferUnspecified );
if( err != paNoError ) goto error;
printf("------------- Try suggestedLatency = defaultLowOutputLatency\n");
outputParameters.suggestedLatency = deviceInfo->defaultLowOutputLatency;
err = paqaCheckLatency( &outputParameters, &data, sampleRate, paFramesPerBufferUnspecified );
if( err != paNoError ) goto error;
printf("------------- Try suggestedLatency = defaultHighOutputLatency\n");
outputParameters.suggestedLatency = deviceInfo->defaultHighOutputLatency;
err = paqaCheckLatency( &outputParameters, &data, sampleRate, paFramesPerBufferUnspecified );
if( err != paNoError ) goto error;
printf("------------- Try suggestedLatency = defaultHighOutputLatency * 4\n");
outputParameters.suggestedLatency = deviceInfo->defaultHighOutputLatency * 4;
err = paqaCheckLatency( &outputParameters, &data, sampleRate, paFramesPerBufferUnspecified );
if( err != paNoError ) goto error;
Pa_Terminate();
printf("SUCCESS - test finished.\n");
return err;
error:
Pa_Terminate();
fprintf( stderr, "ERROR - test failed.\n" );
fprintf( stderr, "Error number: %d\n", err );
fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
return err;
}