Implements the blocking PortAudio stream interface on top the non-blocking PortAudio stream interface in order to make it possible to use the DirectSound PortAudio implementation on Windows and to decrease the input/capture and output/playback latency.

cusax-fix
Lyubomir Marinov 15 years ago
parent 3f655c0fbf
commit 9fdfa04773

@ -12,6 +12,12 @@
#include <stdlib.h>
#include <string.h>
/**
* The minimum number of frames by which the playback is to be delayed before it
* is accessible to the echo cancellation/capture.
*/
#define MIN_PLAY_DELAY_IN_FRAMES 2
static void AudioQualityImprovement_cancelEchoFromPlay
(AudioQualityImprovement *aqi,
void *buffer, unsigned long length);
@ -27,6 +33,8 @@ static void AudioQualityImprovement_resampleInPlay
static void AudioQualityImprovement_retain(AudioQualityImprovement *aqi);
static void AudioQualityImprovement_setFrameSize
(AudioQualityImprovement *aqi, jint frameSize);
static void AudioQualityImprovement_setInputLatency
(AudioQualityImprovement *aqi, jlong inputLatency);
static void AudioQualityImprovement_setOutputLatency
(AudioQualityImprovement *aqi, jlong outputLatency);
@ -46,16 +54,8 @@ static void AudioQualityImprovement_updatePlayIsDelaying
static void AudioQualityImprovement_updatePreprocess
(AudioQualityImprovement *aqi);
#ifndef _WIN32
static pthread_mutex_t AudioQualityImprovement_sharedInstancesMutex
= PTHREAD_MUTEX_INITIALIZER;
#else /* #ifndef _WIN32 */
static CRITICAL_SECTION AudioQualityImprovement_sharedInstancesMutex
= {(void*)-1, -1, 0, 0, 0, 0};
#endif /* #ifndef _WIN32 */
static AudioQualityImprovement *AudioQualityImprovement_sharedInstances
= NULL;
static Mutex *AudioQualityImprovement_sharedInstancesMutex = NULL;
static AudioQualityImprovement *AudioQualityImprovement_sharedInstances = NULL;
/**
*
@ -104,8 +104,7 @@ static void
AudioQualityImprovement_free(AudioQualityImprovement *aqi)
{
/* mutex */
mutex_destroy(aqi->mutex);
free(aqi->mutex);
Mutex_free(aqi->mutex);
/* preprocess */
if (aqi->preprocess)
speex_preprocess_state_destroy(aqi->preprocess);
@ -132,7 +131,7 @@ AudioQualityImprovement_getSharedInstance(const char *stringID, jlong longID)
{
AudioQualityImprovement *theSharedInstance = NULL;
if (!mutex_lock(&AudioQualityImprovement_sharedInstancesMutex))
if (!Mutex_lock(AudioQualityImprovement_sharedInstancesMutex))
{
AudioQualityImprovement *aSharedInstance
= AudioQualityImprovement_sharedInstances;
@ -160,11 +159,18 @@ AudioQualityImprovement_getSharedInstance(const char *stringID, jlong longID)
if (theSharedInstance)
AudioQualityImprovement_sharedInstances = theSharedInstance;
}
mutex_unlock(&AudioQualityImprovement_sharedInstancesMutex);
Mutex_unlock(AudioQualityImprovement_sharedInstancesMutex);
}
return theSharedInstance;
}
/** Loads the <tt>AudioQualityImprovement</tt> class. */
void
AudioQualityImprovement_load()
{
AudioQualityImprovement_sharedInstancesMutex = Mutex_new(NULL);
}
static AudioQualityImprovement *
AudioQualityImprovement_new
(const char *stringID, jlong longID, AudioQualityImprovement *next)
@ -181,15 +187,17 @@ AudioQualityImprovement_new
return NULL;
}
/* mutex */
aqi->mutex = malloc(sizeof(Mutex));
if (!(aqi->mutex) || mutex_init(aqi->mutex, NULL))
aqi->mutex = Mutex_new(NULL);
if (!(aqi->mutex))
{
AudioQualityImprovement_free(aqi);
return NULL;
}
aqi->inputLatency = -1;
aqi->longID = longID;
aqi->next = next;
aqi->outputLatency = -1;
aqi->retainCount = 1;
}
return aqi;
@ -229,7 +237,7 @@ AudioQualityImprovement_process
jlong latency,
void *buffer, unsigned long length)
{
if ((sampleSizeInBits == 16) && (channels == 1) && !mutex_lock(aqi->mutex))
if ((sampleSizeInBits == 16) && (channels == 1) && !Mutex_lock(aqi->mutex))
{
switch (sampleOrigin)
{
@ -239,6 +247,7 @@ AudioQualityImprovement_process
AudioQualityImprovement_setFrameSize(aqi, length);
if (aqi->preprocess)
{
AudioQualityImprovement_setInputLatency(aqi, latency);
if (aqi->echo && aqi->play && aqi->playLength)
{
AudioQualityImprovement_cancelEchoFromPlay(
@ -260,16 +269,16 @@ AudioQualityImprovement_process
}
break;
}
mutex_unlock(aqi->mutex);
Mutex_unlock(aqi->mutex);
}
}
void
AudioQualityImprovement_release(AudioQualityImprovement *aqi)
{
if (!mutex_lock(&AudioQualityImprovement_sharedInstancesMutex))
if (!Mutex_lock(AudioQualityImprovement_sharedInstancesMutex))
{
if (!mutex_lock(aqi->mutex))
if (!Mutex_lock(aqi->mutex))
{
--(aqi->retainCount);
if (aqi->retainCount < 1)
@ -298,13 +307,13 @@ AudioQualityImprovement_release(AudioQualityImprovement *aqi)
}
}
mutex_unlock(aqi->mutex);
Mutex_unlock(aqi->mutex);
AudioQualityImprovement_free(aqi);
}
else
mutex_unlock(aqi->mutex);
Mutex_unlock(aqi->mutex);
}
mutex_unlock(&AudioQualityImprovement_sharedInstancesMutex);
Mutex_unlock(AudioQualityImprovement_sharedInstancesMutex);
}
}
@ -435,10 +444,10 @@ AudioQualityImprovement_resampleInPlay
static void
AudioQualityImprovement_retain(AudioQualityImprovement *aqi)
{
if (!mutex_lock(aqi->mutex))
if (!Mutex_lock(aqi->mutex))
{
++(aqi->retainCount);
mutex_unlock(aqi->mutex);
Mutex_unlock(aqi->mutex);
}
}
@ -456,14 +465,14 @@ void
AudioQualityImprovement_setDenoise
(AudioQualityImprovement *aqi, jboolean denoise)
{
if (!mutex_lock(aqi->mutex))
if (!Mutex_lock(aqi->mutex))
{
if (aqi->denoise != denoise)
{
aqi->denoise = denoise;
AudioQualityImprovement_updatePreprocess(aqi);
}
mutex_unlock(aqi->mutex);
Mutex_unlock(aqi->mutex);
}
}
@ -485,14 +494,14 @@ AudioQualityImprovement_setEchoFilterLengthInMillis
{
if (echoFilterLengthInMillis < 0)
echoFilterLengthInMillis = 0;
if (!mutex_lock(aqi->mutex))
if (!Mutex_lock(aqi->mutex))
{
if (aqi->echoFilterLengthInMillis != echoFilterLengthInMillis)
{
aqi->echoFilterLengthInMillis = echoFilterLengthInMillis;
AudioQualityImprovement_updatePreprocess(aqi);
}
mutex_unlock(aqi->mutex);
Mutex_unlock(aqi->mutex);
}
}
@ -507,6 +516,17 @@ AudioQualityImprovement_setFrameSize
}
}
static void
AudioQualityImprovement_setInputLatency
(AudioQualityImprovement *aqi, jlong inputLatency)
{
if (aqi->inputLatency != inputLatency)
{
aqi->inputLatency = inputLatency;
AudioQualityImprovement_updatePlayDelay(aqi);
}
}
static void
AudioQualityImprovement_setOutputLatency
(AudioQualityImprovement *aqi, jlong outputLatency)
@ -522,7 +542,7 @@ void
AudioQualityImprovement_setSampleRate
(AudioQualityImprovement *aqi, int sampleRate)
{
if (!mutex_lock(aqi->mutex))
if (!Mutex_lock(aqi->mutex))
{
if (aqi->sampleRate != sampleRate)
{
@ -530,7 +550,18 @@ AudioQualityImprovement_setSampleRate
AudioQualityImprovement_updatePlayDelay(aqi);
AudioQualityImprovement_updatePreprocess(aqi);
}
mutex_unlock(aqi->mutex);
Mutex_unlock(aqi->mutex);
}
}
/** Unloads the <tt>AudioQualityImprovement</tt> class. */
void
AudioQualityImprovement_unload()
{
if (AudioQualityImprovement_sharedInstancesMutex)
{
Mutex_free(AudioQualityImprovement_sharedInstancesMutex);
AudioQualityImprovement_sharedInstances = NULL;
}
}
@ -539,20 +570,21 @@ AudioQualityImprovement_updatePlayDelay(AudioQualityImprovement *aqi)
{
spx_uint32_t playDelay;
/*
* Apart from output latency, there is obviously input latency as well.
* Since the echo cancellation implementation will attempt to adapt to the
* delay between the far and the near ends anyway, don't take the input
* latency into account and don't increase the risk of reversing the delay.
*/
if (!(aqi->outputLatency) || !(aqi->frameSize) || !(aqi->sampleRate))
playDelay = 2;
if ((aqi->inputLatency < 0)
|| (aqi->outputLatency < 0)
|| !(aqi->frameSize)
|| !(aqi->sampleRate))
{
playDelay = MIN_PLAY_DELAY_IN_FRAMES;
}
else
{
playDelay
= aqi->outputLatency
= (aqi->inputLatency + aqi->outputLatency)
/ ((aqi->frameSize / sizeof(spx_int16_t))
/ (aqi->sampleRate / 1000));
if (playDelay < MIN_PLAY_DELAY_IN_FRAMES)
playDelay = MIN_PLAY_DELAY_IN_FRAMES;
}
if (aqi->playDelay != playDelay)
@ -637,15 +669,15 @@ AudioQualityImprovement_updatePreprocess(AudioQualityImprovement *aqi)
{
if (!(aqi->echo))
{
int echoFrameSize
= aqi->frameSize / (16 /* sampleSizeInBits */ / 8);
int echoFilterLength
= (int)
((aqi->sampleRate * aqi->echoFilterLengthInMillis)
/ 1000);
aqi->echo
= speex_echo_state_init(
aqi->frameSize / (16 /* sampleSizeInBits */ / 8),
echoFilterLength);
= speex_echo_state_init(echoFrameSize, echoFilterLength);
aqi->filterLengthOfEcho = echoFilterLength;
/*
* Since echo has just been (re)created, make sure that the

@ -14,63 +14,7 @@
typedef void *AudioQualityImprovement;
#else /* #ifndef AUDIO_QUALITY_IMPROVEMENT_IMPLEMENTATION */
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
typedef CRITICAL_SECTION Mutex;
static inline int mutex_init(Mutex* mutex, void* arg)
{
InitializeCriticalSection(mutex);
arg = NULL; /* unused */
return 0;
}
static inline int mutex_destroy(Mutex* mutex)
{
DeleteCriticalSection(mutex);
return 0;
}
static inline int mutex_lock(Mutex* mutex)
{
EnterCriticalSection(mutex);
return 0;
}
static inline int mutex_unlock(Mutex* mutex)
{
LeaveCriticalSection(mutex);
return 0;
}
#else /* #ifdef _WIN32 */
#include <pthread.h>
typedef pthread_mutex_t Mutex;
static inline int mutex_init(Mutex* mutex, void* arg)
{
return pthread_mutex_init(mutex, arg);
}
static inline int mutex_destroy(Mutex* mutex)
{
return pthread_mutex_destroy(mutex);
}
static inline int mutex_lock(Mutex* mutex)
{
return pthread_mutex_lock(mutex);
}
static inline int mutex_unlock(Mutex* mutex)
{
return pthread_mutex_unlock(mutex);
}
#endif /* #ifdef _WIN32 */
#include "Mutex.h"
#include <speex/speex_echo.h>
#include <speex/speex_preprocess.h>
#include <speex/speex_resampler.h>
@ -85,6 +29,9 @@ typedef struct _AudioQualityImprovement
int filterLengthOfEcho;
jint frameSize;
int frameSizeOfPreprocess;
/** The capture latency in milliseconds. */
jlong inputLatency;
jlong longID;
Mutex *mutex;
struct _AudioQualityImprovement *next;
@ -146,6 +93,9 @@ typedef enum
AudioQualityImprovement *AudioQualityImprovement_getSharedInstance
(const char *stringID, jlong longID);
/** Loads the <tt>AudioQualityImprovement</tt> class. */
void AudioQualityImprovement_load();
void AudioQualityImprovement_process
(AudioQualityImprovement *aqi,
AudioQualityImprovementSampleOrigin sampleOrigin,
@ -184,4 +134,7 @@ void AudioQualityImprovement_setEchoFilterLengthInMillis
void AudioQualityImprovement_setSampleRate
(AudioQualityImprovement *aqi, int sampleRate);
/** Unloads the <tt>AudioQualityImprovement</tt> class. */
void AudioQualityImprovement_unload();
#endif /* #ifndef _NET_JAVA_SIP_COMMUNICATOR_IMPL_NEOMEDIA_PORTAUDIO_AUDIOQUALITYIMPROVEMENT_H_ */

@ -0,0 +1,95 @@
/*
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
#ifndef _NET_JAVA_SIP_COMMUNICATOR_IMPL_NEOMEDIA_CONDITIONVARIABLE_H_
#define _NET_JAVA_SIP_COMMUNICATOR_IMPL_NEOMEDIA_CONDITIONVARIABLE_H_
#include "Mutex.h"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
typedef HANDLE ConditionVariable;
static inline void ConditionVariable_free(ConditionVariable *condVar)
{
if (CloseHandle(*condVar))
free(condVar);
}
static inline ConditionVariable *ConditionVariable_new(void *attr)
{
ConditionVariable *condVar = malloc(sizeof(ConditionVariable));
if (condVar)
{
HANDLE event = CreateEvent(NULL, FALSE, FALSE, NULL);
if (event)
*condVar = event;
else
{
free(condVar);
condVar = NULL;
}
}
return condVar;
}
static inline int ConditionVariable_notify(ConditionVariable *condVar)
{
return SetEvent(*condVar) ? 0 : GetLastError();
}
static inline int ConditionVariable_wait
(ConditionVariable *condVar, Mutex *mutex)
{
DWORD waitForSingleObject;
LeaveCriticalSection(mutex);
waitForSingleObject = WaitForSingleObject(*condVar, INFINITE);
EnterCriticalSection(mutex);
return waitForSingleObject;
}
#else /* #ifdef _WIN32 */
#include <pthread.h>
typedef pthread_cond_t ConditionVariable;
static inline void ConditionVariable_free(ConditionVariable *condVar)
{
if (!pthread_cond_destroy(condVar))
free(condVar);
}
static inline ConditionVariable *ConditionVariable_new(void *attr)
{
ConditionVariable *condVar = malloc(sizeof(ConditionVariable));
if (condVar && pthread_cond_init(condVar, attr))
{
free(condVar);
condVar = NULL;
}
return condVar;
}
static inline int ConditionVariable_notify(ConditionVariable *condVar)
{
return pthread_cond_signal(condVar);
}
static inline int ConditionVariable_wait
(ConditionVariable *condVar, Mutex *mutex)
{
return pthread_cond_wait(condVar, mutex);
}
#endif /* #ifdef _WIN32 */
#endif /* _NET_JAVA_SIP_COMMUNICATOR_IMPL_NEOMEDIA_CONDITIONVARIABLE_H_ */

@ -0,0 +1,82 @@
/*
* SIP Communicator, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
#ifndef _NET_JAVA_SIP_COMMUNICATOR_IMPL_NEOMEDIA_PORTAUDIO_MUTEX_H_
#define _NET_JAVA_SIP_COMMUNICATOR_IMPL_NEOMEDIA_PORTAUDIO_MUTEX_H_
#include <stdlib.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
typedef CRITICAL_SECTION Mutex;
static inline void Mutex_free(Mutex* mutex)
{
DeleteCriticalSection(mutex);
free(mutex);
}
static inline int Mutex_lock(Mutex* mutex)
{
EnterCriticalSection(mutex);
return 0;
}
static inline Mutex *Mutex_new(void* attr)
{
Mutex *mutex = malloc(sizeof(Mutex));
(void) attr;
if (mutex)
InitializeCriticalSection(mutex);
return mutex;
}
static inline int Mutex_unlock(Mutex* mutex)
{
LeaveCriticalSection(mutex);
return 0;
}
#else /* #ifdef _WIN32 */
#include <pthread.h>
typedef pthread_mutex_t Mutex;
static inline void Mutex_free(Mutex* mutex)
{
if (!pthread_mutex_destroy(mutex))
free(mutex);
}
static inline int Mutex_lock(Mutex* mutex)
{
return pthread_mutex_lock(mutex);
}
static inline Mutex *Mutex_new(void* attr)
{
Mutex *mutex = malloc(sizeof(Mutex));
if (mutex && pthread_mutex_init(mutex, attr))
{
free(mutex);
mutex = NULL;
}
return mutex;
}
static inline int Mutex_unlock(Mutex* mutex)
{
return pthread_mutex_unlock(mutex);
}
#endif /* #ifdef _WIN32 */
#endif /* #ifndef _NET_JAVA_SIP_COMMUNICATOR_IMPL_NEOMEDIA_PORTAUDIO_MUTEX_H_ */

@ -8,7 +8,10 @@
#include "net_java_sip_communicator_impl_neomedia_portaudio_PortAudio.h"
#include "AudioQualityImprovement.h"
#include "ConditionVariable.h"
#include "Mutex.h"
#include <portaudio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
@ -18,14 +21,40 @@ typedef struct
AudioQualityImprovement *audioQualityImprovement;
int channels;
JNIEnv *env;
jboolean finished;
/**
* The value specified as the <tt>framesPerBuffer</tt> argument to the
* <tt>Pa_OpenStream</tt> function call which has opened #stream.
*/
unsigned long framesPerBuffer;
void *input;
size_t inputCapacity;
ConditionVariable *inputCondVar;
long inputFrameSize;
/** The input latency of #stream. */
jlong inputLatency;
size_t inputLength;
Mutex *inputMutex;
Mutex *mutex;
void *output;
size_t outputCapacity;
ConditionVariable *outputCondVar;
long outputFrameSize;
/** The output latency of #stream. */
jlong outputLatency;
size_t outputLength;
Mutex *outputMutex;
/**
* The indicator which determines whether this <tt>PortAudioStream</tt>
* implements the blocking stream interface on top of the non-blocking
* stream interface.
*/
jboolean pseudoBlocking;
jlong retainCount;
double sampleRate;
int sampleSizeInBits;
PaStream *stream;
@ -45,17 +74,53 @@ static unsigned long PortAudio_getSampleSizeInBits
static void PortAudio_throwException(JNIEnv *env, PaError errorCode);
static jlong System_currentTimeMillis();
static int PortAudioStream_callback(
const void *input,
/**
* Allocates (and initializes) the memory and its associated variables for a
* specific buffer to be used by the pseudo-blocking stream interface
* implementation of a <tt>PortAudioStream</tt>.
*
* @param capacity the number of bytes to be allocated to the buffer
* @param bufferPtr a pointer which specifies where the location of the
* allocated buffer is to be stored
* @param bufferLengthPtr a pointer which specifies where the initial length
* (i.e. zero) is to be stored
* @param bufferCapacityPtr a pointer which specifies where the capacity of the
* allocated buffer is to be stored
* @param bufferMutexPtr a pointer which specifies where the <tt>Mute</tt> to
* synchronize the access to the allocated buffer is to be stored
* @param bufferCondVarPtr a pointer which specifies where the
* <tt>ConditionVariable</tt> to synchronize the access to the allocated buffer
* is to be stored
* @return the location of the allocated buffer upon success; otherwise,
* <tt>NULL</tt>
*/
static void *PortAudioStream_allocPseudoBlockingBuffer
(size_t capacity,
void **bufferPtr, size_t *bufferLengthPtr, size_t *bufferCapacityPtr,
Mutex **bufferMutexPtr, ConditionVariable **bufferCondVarPtr);
static void PortAudioStream_free(JNIEnv *env, PortAudioStream *stream);
static int PortAudioStream_javaCallback
(const void *input,
void *output,
unsigned long frameCount,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData);
static void PortAudioStream_finishedCallback(void *userData);
static void PortAudioStream_free(JNIEnv *env, PortAudioStream *stream);
static PortAudioStream * PortAudioStream_new(
JNIEnv *env, jobject streamCallback);
static void PortAudioStream_javaFinishedCallback(void *userData);
static PortAudioStream * PortAudioStream_new
(JNIEnv *env, jobject streamCallback);
static void PortAudioStream_popFromPseudoBlockingBuffer
(void *buffer, size_t length, size_t *bufferLengthPtr);
static int PortAudioStream_pseudoBlockingCallback
(const void *input,
void *output,
unsigned long frameCount,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData);
static void PortAudioStream_pseudoBlockingFinishedCallback(void *userData);
static void PortAudioStream_release(PortAudioStream *stream);
static void PortAudioStream_retain(PortAudioStream *stream);
static const char *AUDIO_QUALITY_IMPROVEMENT_STRING_ID = "portaudio";
#define LATENCY_HIGH net_java_sip_communicator_impl_neomedia_portaudio_PortAudio_LATENCY_HIGH
@ -87,6 +152,8 @@ Java_net_java_sip_communicator_impl_neomedia_portaudio_PortAudio_Pa_1CloseStream
if (paNoError != errorCode)
PortAudio_throwException(env, errorCode);
else if (portAudioStream->pseudoBlocking)
PortAudioStream_release(portAudioStream);
else
PortAudioStream_free(env, portAudioStream);
}
@ -186,6 +253,8 @@ Java_net_java_sip_communicator_impl_neomedia_portaudio_PortAudio_Pa_1OpenStream
jobject streamCallback)
{
PortAudioStream *stream = PortAudioStream_new(env, streamCallback);
PaStreamCallback *effectiveStreamCallback;
PaStreamFinishedCallback *effectiveStreamFinishedCallback;
PaError errorCode;
PaStreamParameters *inputStreamParameters
= (PaStreamParameters *) inputParameters;
@ -195,41 +264,137 @@ Java_net_java_sip_communicator_impl_neomedia_portaudio_PortAudio_Pa_1OpenStream
if (!stream)
return 0;
errorCode
= Pa_OpenStream(
&(stream->stream),
PortAudio_fixInputParametersSuggestedLatency(inputStreamParameters),
PortAudio_fixOutputParametersSuggestedLatency(
outputStreamParameters),
sampleRate,
framesPerBuffer,
streamFlags,
streamCallback ? PortAudioStream_callback : NULL,
stream);
if (streamCallback)
{
effectiveStreamCallback = PortAudioStream_javaCallback;
effectiveStreamFinishedCallback = PortAudioStream_javaFinishedCallback;
stream->pseudoBlocking = JNI_FALSE;
}
else
{
/*
* Some host APIs such as DirectSound don't really implement the
* blocking stream interface. If we're to ever be able to try them out,
* we'll have to implement the blocking stream interface on top of the
* non-blocking stream interface.
*/
effectiveStreamCallback = NULL;
effectiveStreamFinishedCallback = NULL;
stream->pseudoBlocking = JNI_FALSE;
/*
* TODO It should be possible to implement the blocking stream interface
* without a specific framesPerBuffer.
*/
if ((paFramesPerBufferUnspecified != framesPerBuffer)
&& (framesPerBuffer > 0))
{
PaDeviceIndex device;
if (inputStreamParameters)
device = inputStreamParameters->device;
else if (outputStreamParameters)
device = outputStreamParameters->device;
else
device = paNoDevice;
if (device != paNoDevice)
{
const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(device);
if (deviceInfo)
{
const PaHostApiInfo *hostApiInfo
= Pa_GetHostApiInfo(deviceInfo->hostApi);
if (hostApiInfo && (paDirectSound == hostApiInfo->type))
{
effectiveStreamCallback
= PortAudioStream_pseudoBlockingCallback;
effectiveStreamFinishedCallback
= PortAudioStream_pseudoBlockingFinishedCallback;
stream->pseudoBlocking = JNI_TRUE;
}
}
}
}
}
if (JNI_TRUE == stream->pseudoBlocking)
{
stream->mutex = Mutex_new(NULL);
errorCode = (stream->mutex) ? paNoError : paInsufficientMemory;
}
else
errorCode = paNoError;
if (paNoError == errorCode)
{
errorCode
= Pa_OpenStream(
&(stream->stream),
PortAudio_fixInputParametersSuggestedLatency(
inputStreamParameters),
PortAudio_fixOutputParametersSuggestedLatency(
outputStreamParameters),
sampleRate,
framesPerBuffer,
streamFlags,
effectiveStreamCallback,
stream);
}
if (paNoError == errorCode)
{
stream->framesPerBuffer = framesPerBuffer;
stream->inputFrameSize
= PortAudio_getFrameSize(inputStreamParameters);
stream->outputFrameSize
= PortAudio_getFrameSize(outputStreamParameters);
stream->sampleRate = sampleRate;
if (streamCallback)
if (effectiveStreamFinishedCallback)
{
errorCode
= Pa_SetStreamFinishedCallback(
stream->stream,
PortAudioStream_finishedCallback);
effectiveStreamFinishedCallback);
}
stream->audioQualityImprovement
= AudioQualityImprovement_getSharedInstance(
AUDIO_QUALITY_IMPROVEMENT_STRING_ID,
0);
stream->sampleRate = sampleRate;
if (inputStreamParameters)
{
stream->sampleSizeInBits
= PortAudio_getSampleSizeInBits(inputStreamParameters);
stream->channels = inputStreamParameters->channelCount;
/*
* Prepare whatever is necessary for the pseudo-blocking stream
* interface implementation. For example, allocate its memory early
* because doing it in the stream callback may introduce latency.
*/
if (stream->pseudoBlocking
&& !PortAudioStream_allocPseudoBlockingBuffer(
2 * framesPerBuffer * (stream->inputFrameSize),
&(stream->input),
&(stream->inputLength),
&(stream->inputCapacity),
&(stream->inputMutex),
&(stream->inputCondVar)))
{
Java_net_java_sip_communicator_impl_neomedia_portaudio_PortAudio_Pa_1CloseStream(
env, clazz,
(jlong) stream);
if (JNI_FALSE == (*env)->ExceptionCheck(env))
{
PortAudio_throwException(env, paInsufficientMemory);
return 0;
}
}
if (stream->audioQualityImprovement)
{
const PaStreamInfo *streamInfo;
@ -246,11 +411,31 @@ Java_net_java_sip_communicator_impl_neomedia_portaudio_PortAudio_Pa_1OpenStream
}
}
}
else if (outputStreamParameters)
if (outputStreamParameters)
{
stream->sampleSizeInBits
= PortAudio_getSampleSizeInBits(outputStreamParameters);
stream->channels = outputStreamParameters->channelCount;
if (stream->pseudoBlocking
&& !PortAudioStream_allocPseudoBlockingBuffer(
2 * framesPerBuffer * (stream->outputFrameSize),
&(stream->output),
&(stream->outputLength),
&(stream->outputCapacity),
&(stream->outputMutex),
&(stream->outputCondVar)))
{
Java_net_java_sip_communicator_impl_neomedia_portaudio_PortAudio_Pa_1CloseStream(
env, clazz,
(jlong) stream);
if (JNI_FALSE == (*env)->ExceptionCheck(env))
{
PortAudio_throwException(env, paInsufficientMemory);
return 0;
}
}
if (stream->audioQualityImprovement)
{
const PaStreamInfo *streamInfo;
@ -264,6 +449,9 @@ Java_net_java_sip_communicator_impl_neomedia_portaudio_PortAudio_Pa_1OpenStream
}
}
if (stream->pseudoBlocking)
PortAudioStream_retain(stream);
return (jlong) stream;
}
else
@ -285,25 +473,78 @@ Java_net_java_sip_communicator_impl_neomedia_portaudio_PortAudio_Pa_1ReadStream
PortAudioStream *portAudioStream = (PortAudioStream *) stream;
PaError errorCode;
errorCode = Pa_ReadStream(portAudioStream->stream, data, frames);
if ((paNoError == errorCode) || (paInputOverflowed == errorCode))
if (portAudioStream->pseudoBlocking)
{
if (portAudioStream->audioQualityImprovement)
PortAudioStream_retain(portAudioStream);
if (Mutex_lock(portAudioStream->inputMutex))
errorCode = paInternalError;
else
{
AudioQualityImprovement_process(
portAudioStream->audioQualityImprovement,
AUDIO_QUALITY_IMPROVEMENT_SAMPLE_ORIGIN_INPUT,
portAudioStream->sampleRate,
portAudioStream->sampleSizeInBits,
portAudioStream->channels,
portAudioStream->inputLatency,
data, frames * portAudioStream->inputFrameSize);
jlong bytesRead = 0;
jlong totalBytesToRead
= frames * portAudioStream->inputFrameSize;
errorCode = paNoError;
while (bytesRead < totalBytesToRead)
{
jlong bytesToRead;
if (JNI_TRUE == portAudioStream->finished)
{
errorCode = paStreamIsStopped;
break;
}
if (!(portAudioStream->inputLength))
{
ConditionVariable_wait(
portAudioStream->inputCondVar,
portAudioStream->inputMutex);
continue;
}
bytesToRead = totalBytesToRead - bytesRead;
if (bytesToRead > portAudioStream->inputLength)
bytesToRead = portAudioStream->inputLength;
memcpy(
data + bytesRead,
portAudioStream->input,
bytesToRead);
PortAudioStream_popFromPseudoBlockingBuffer(
portAudioStream->input,
bytesToRead,
&(portAudioStream->inputLength));
bytesRead += bytesToRead;
}
Mutex_unlock(portAudioStream->inputMutex);
}
(*env)->ReleaseByteArrayElements(env, buffer, data, 0);
PortAudioStream_release(portAudioStream);
}
else
{
errorCode = Pa_ReadStream(portAudioStream->stream, data, frames);
if ((paNoError == errorCode) || (paInputOverflowed == errorCode))
{
errorCode = paNoError;
if (portAudioStream->audioQualityImprovement)
{
AudioQualityImprovement_process(
portAudioStream->audioQualityImprovement,
AUDIO_QUALITY_IMPROVEMENT_SAMPLE_ORIGIN_INPUT,
portAudioStream->sampleRate,
portAudioStream->sampleSizeInBits,
portAudioStream->channels,
portAudioStream->inputLatency,
data, frames * portAudioStream->inputFrameSize);
}
}
}
if (paNoError == errorCode)
(*env)->ReleaseByteArrayElements(env, buffer, data, 0);
else
{
(*env)->ReleaseByteArrayElements(env, buffer, data, JNI_ABORT);
PortAudio_throwException(env, errorCode);
}
}
@ -313,8 +554,27 @@ JNIEXPORT void JNICALL
Java_net_java_sip_communicator_impl_neomedia_portaudio_PortAudio_Pa_1StartStream
(JNIEnv *env, jclass clazz, jlong stream)
{
PaError errorCode = Pa_StartStream(((PortAudioStream *) stream)->stream);
PortAudioStream *portAudioStream = (PortAudioStream *) stream;
PaError errorCode;
if (portAudioStream->pseudoBlocking)
{
PortAudioStream_retain(portAudioStream);
if (Mutex_lock(portAudioStream->mutex))
errorCode = paInternalError;
else
{
portAudioStream->finished = JNI_FALSE;
errorCode = Pa_StartStream(portAudioStream->stream);
if (paNoError != errorCode)
portAudioStream->finished = JNI_TRUE;
Mutex_unlock(portAudioStream->mutex);
}
if (paNoError != errorCode)
PortAudioStream_release(portAudioStream);
}
else
errorCode = Pa_StartStream(portAudioStream->stream);
if (paNoError != errorCode)
PortAudio_throwException(env, errorCode);
}
@ -339,15 +599,7 @@ Java_net_java_sip_communicator_impl_neomedia_portaudio_PortAudio_Pa_1WriteStream
jbyte *bufferBytes;
jbyte* data;
PortAudioStream *portAudioStream;
PaStream *paStream;
AudioQualityImprovement *audioQualityImprovement;
double sampleRate;
unsigned long sampleSizeInBits;
int channels;
jlong outputLatency;
long framesInBytes;
PaError errorCode;
jint i;
PaError errorCode = paNoError;
bufferBytes = (*env)->GetByteArrayElements(env, buffer, NULL);
if (!bufferBytes)
@ -355,37 +607,97 @@ Java_net_java_sip_communicator_impl_neomedia_portaudio_PortAudio_Pa_1WriteStream
data = bufferBytes + offset;
portAudioStream = (PortAudioStream *) stream;
paStream = portAudioStream->stream;
audioQualityImprovement = portAudioStream->audioQualityImprovement;
sampleRate = portAudioStream->sampleRate;
sampleSizeInBits = portAudioStream->sampleSizeInBits;
channels = portAudioStream->channels;
outputLatency = portAudioStream->outputLatency;
framesInBytes = frames * portAudioStream->outputFrameSize;
for (i = 0; i < numberOfWrites; i++)
if (portAudioStream->pseudoBlocking)
{
errorCode = Pa_WriteStream(paStream, data, frames);
if ((paNoError != errorCode) && (errorCode != paOutputUnderflowed))
break;
PortAudioStream_retain(portAudioStream);
if (Mutex_lock(portAudioStream->outputMutex))
errorCode = paInternalError;
else
{
if (audioQualityImprovement)
jlong bytesWritten = 0;
jlong totalBytesToWrite
= numberOfWrites * frames * portAudioStream->outputFrameSize;
errorCode = paNoError;
while (bytesWritten < totalBytesToWrite)
{
size_t outputCapacity
= portAudioStream->outputCapacity
- portAudioStream->outputLength;
jlong bytesToWrite;
if (JNI_TRUE == portAudioStream->finished)
{
errorCode = paStreamIsStopped;
break;
}
if (outputCapacity < 1)
{
ConditionVariable_wait(
portAudioStream->outputCondVar,
portAudioStream->outputMutex);
continue;
}
bytesToWrite = totalBytesToWrite - bytesWritten;
if (bytesToWrite > outputCapacity)
bytesToWrite = outputCapacity;
memcpy(
((jbyte *) portAudioStream->output)
+ portAudioStream->outputLength,
data + bytesWritten,
bytesToWrite);
portAudioStream->outputLength += bytesToWrite;
bytesWritten += bytesToWrite;
}
Mutex_unlock(portAudioStream->outputMutex);
}
PortAudioStream_release(portAudioStream);
}
else
{
PaStream *paStream;
AudioQualityImprovement *audioQualityImprovement;
double sampleRate;
unsigned long sampleSizeInBits;
int channels;
jlong outputLatency;
long framesInBytes;
jint i;
paStream = portAudioStream->stream;
audioQualityImprovement = portAudioStream->audioQualityImprovement;
sampleRate = portAudioStream->sampleRate;
sampleSizeInBits = portAudioStream->sampleSizeInBits;
channels = portAudioStream->channels;
outputLatency = portAudioStream->outputLatency;
framesInBytes = frames * portAudioStream->outputFrameSize;
for (i = 0; i < numberOfWrites; i++)
{
errorCode = Pa_WriteStream(paStream, data, frames);
if ((paNoError != errorCode) && (paOutputUnderflowed != errorCode))
break;
else
{
AudioQualityImprovement_process(
audioQualityImprovement,
AUDIO_QUALITY_IMPROVEMENT_SAMPLE_ORIGIN_OUTPUT,
sampleRate, sampleSizeInBits, channels,
outputLatency,
data, framesInBytes);
if (audioQualityImprovement)
{
AudioQualityImprovement_process(
audioQualityImprovement,
AUDIO_QUALITY_IMPROVEMENT_SAMPLE_ORIGIN_OUTPUT,
sampleRate, sampleSizeInBits, channels,
outputLatency,
data, framesInBytes);
}
data += framesInBytes;
}
data += framesInBytes;
}
}
(*env)->ReleaseByteArrayElements(env, buffer, bufferBytes, JNI_ABORT);
if ((paNoError != errorCode) && (errorCode != paOutputUnderflowed))
if ((paNoError != errorCode) && (paOutputUnderflowed != errorCode))
PortAudio_throwException(env, errorCode);
}
@ -556,6 +868,20 @@ Java_net_java_sip_communicator_impl_neomedia_portaudio_PortAudio_setEchoFilterLe
}
}
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved)
{
AudioQualityImprovement_load();
return JNI_VERSION_1_4;
}
JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *vm, void *reserved)
{
AudioQualityImprovement_unload();
}
static PaStreamParameters *
PortAudio_fixInputParametersSuggestedLatency
(PaStreamParameters *inputParameters)
@ -648,9 +974,105 @@ PortAudio_throwException(JNIEnv *env, PaError errorCode)
(*env)->ThrowNew(env, clazz, Pa_GetErrorText(errorCode));
}
/**
* Allocates (and initializes) the memory and its associated variables for a
* specific buffer to be used by the pseudo-blocking stream interface
* implementation of a <tt>PortAudioStream</tt>.
*
* @param capacity the number of bytes to be allocated to the buffer
* @param bufferPtr a pointer which specifies where the location of the
* allocated buffer is to be stored
* @param bufferLengthPtr a pointer which specifies where the initial length
* (i.e. zero) is to be stored
* @param bufferCapacityPtr a pointer which specifies where the capacity of the
* allocated buffer is to be stored
* @param bufferMutexPtr a pointer which specifies where the <tt>Mute</tt> to
* synchronize the access to the allocated buffer is to be stored
* @param bufferCondVarPtr a pointer which specifies where the
* <tt>ConditionVariable</tt> to synchronize the access to the allocated buffer
* is to be stored
* @return the location of the allocated buffer upon success; otherwise,
* <tt>NULL</tt>
*/
static void *
PortAudioStream_allocPseudoBlockingBuffer
(size_t capacity,
void **bufferPtr, size_t *bufferLengthPtr, size_t *bufferCapacityPtr,
Mutex **bufferMutexPtr, ConditionVariable **bufferCondVarPtr)
{
void *buffer = malloc(capacity);
if (buffer)
{
Mutex *mutex = Mutex_new(NULL);
if (mutex)
{
ConditionVariable *condVar = ConditionVariable_new(NULL);
if (condVar)
{
if (bufferPtr)
*bufferPtr = buffer;
if (bufferLengthPtr)
*bufferLengthPtr = 0;
if (bufferCapacityPtr)
*bufferCapacityPtr = capacity;
*bufferMutexPtr = mutex;
*bufferCondVarPtr = condVar;
}
else
{
Mutex_free(mutex);
free(buffer);
buffer = NULL;
}
}
else
{
free(buffer);
buffer = NULL;
}
}
return buffer;
}
static void
PortAudioStream_free(JNIEnv *env, PortAudioStream *stream)
{
if (stream->streamCallback)
(*env)->DeleteGlobalRef(env, stream->streamCallback);
if (stream->inputMutex && !Mutex_lock(stream->inputMutex))
{
if (stream->input)
free(stream->input);
ConditionVariable_free(stream->inputCondVar);
Mutex_unlock(stream->inputMutex);
Mutex_free(stream->inputMutex);
}
if (stream->outputMutex && !Mutex_lock(stream->outputMutex))
{
if (stream->output)
free(stream->output);
ConditionVariable_free(stream->outputCondVar);
Mutex_unlock(stream->outputMutex);
Mutex_free(stream->outputMutex);
}
if (stream->audioQualityImprovement)
AudioQualityImprovement_release(stream->audioQualityImprovement);
if (stream->mutex)
Mutex_free(stream->mutex);
free(stream);
}
static int
PortAudioStream_callback(
const void *input,
PortAudioStream_javaCallback
(const void *input,
void *output,
unsigned long frameCount,
const PaStreamCallbackTimeInfo *timeInfo,
@ -713,7 +1135,7 @@ PortAudioStream_callback(
}
static void
PortAudioStream_finishedCallback(void *userData)
PortAudioStream_javaFinishedCallback(void *userData)
{
PortAudioStream *stream = (PortAudioStream *) userData;
jobject streamCallback = stream->streamCallback;
@ -755,18 +1177,6 @@ PortAudioStream_finishedCallback(void *userData)
(*env)->CallVoidMethod(env, streamCallback, streamFinishedCallbackMethodID);
}
static void
PortAudioStream_free(JNIEnv *env, PortAudioStream *stream)
{
if (stream->streamCallback)
(*env)->DeleteGlobalRef(env, stream->streamCallback);
if (stream->audioQualityImprovement)
AudioQualityImprovement_release(stream->audioQualityImprovement);
free(stream);
}
static PortAudioStream *
PortAudioStream_new(JNIEnv *env, jobject streamCallback)
{
@ -799,6 +1209,152 @@ PortAudioStream_new(JNIEnv *env, jobject streamCallback)
return stream;
}
static void
PortAudioStream_popFromPseudoBlockingBuffer
(void *buffer, size_t length, size_t *bufferLengthPtr)
{
size_t i;
size_t newLength = *bufferLengthPtr - length;
jbyte *oldBuffer = (jbyte *) buffer;
jbyte *newBuffer = ((jbyte *) buffer) + length;
for (i = 0; i < newLength; i++)
*oldBuffer++ = *newBuffer++;
*bufferLengthPtr = newLength;
}
static int
PortAudioStream_pseudoBlockingCallback
(const void *input,
void *output,
unsigned long frameCount,
const PaStreamCallbackTimeInfo *timeInfo,
PaStreamCallbackFlags statusFlags,
void *userData)
{
PortAudioStream *stream = (PortAudioStream *) userData;
if (input && stream->inputMutex && !Mutex_lock(stream->inputMutex))
{
size_t inputLength = frameCount * stream->inputFrameSize;
size_t newInputLength;
void *inputInStream;
/*
* Remember the specified input so that it can be retrieved later on in
* our pseudo-blocking Pa_ReadStream().
*/
newInputLength = stream->inputLength + inputLength;
if (newInputLength > stream->inputCapacity)
{
PortAudioStream_popFromPseudoBlockingBuffer(
stream->input,
newInputLength - stream->inputCapacity,
&(stream->inputLength));
}
inputInStream = ((jbyte *) (stream->input)) + stream->inputLength;
memcpy(inputInStream, input, inputLength);
stream->inputLength += inputLength;
/* Improve the audio quality of the specified input if possible. */
if (stream->audioQualityImprovement)
{
AudioQualityImprovement_process(
stream->audioQualityImprovement,
AUDIO_QUALITY_IMPROVEMENT_SAMPLE_ORIGIN_INPUT,
stream->sampleRate, stream->sampleSizeInBits, stream->channels,
stream->inputLatency,
inputInStream, inputLength);
}
ConditionVariable_notify(stream->inputCondVar);
Mutex_unlock(stream->inputMutex);
}
if (output && stream->outputMutex && !Mutex_lock(stream->outputMutex))
{
size_t outputLength = frameCount * stream->outputFrameSize;
size_t availableOutputLength = outputLength;
if (availableOutputLength > stream->outputLength)
availableOutputLength = stream->outputLength;
memcpy(output, stream->output, availableOutputLength);
PortAudioStream_popFromPseudoBlockingBuffer(
stream->output,
availableOutputLength,
&(stream->outputLength));
if (availableOutputLength < outputLength)
{
memset(
((jbyte *) stream->output) + availableOutputLength,
0,
outputLength - availableOutputLength);
}
if (stream->audioQualityImprovement)
{
AudioQualityImprovement_process(
stream->audioQualityImprovement,
AUDIO_QUALITY_IMPROVEMENT_SAMPLE_ORIGIN_OUTPUT,
stream->sampleRate, stream->sampleSizeInBits, stream->channels,
stream->outputLatency,
output, outputLength);
}
ConditionVariable_notify(stream->outputCondVar);
Mutex_unlock(stream->outputMutex);
}
return paContinue;
}
static void
PortAudioStream_pseudoBlockingFinishedCallback(void *userData)
{
PortAudioStream *stream = (PortAudioStream *) userData;
if (!Mutex_lock(stream->mutex))
{
stream->finished = JNI_TRUE;
if (stream->inputMutex && !Mutex_lock(stream->inputMutex))
{
ConditionVariable_notify(stream->inputCondVar);
Mutex_unlock(stream->inputMutex);
}
if (stream->outputMutex && !Mutex_lock(stream->outputMutex))
{
ConditionVariable_notify(stream->outputCondVar);
Mutex_unlock(stream->outputMutex);
}
Mutex_unlock(stream->mutex);
}
PortAudioStream_release(stream);
}
static void
PortAudioStream_release(PortAudioStream *stream)
{
if (!Mutex_lock(stream->mutex))
{
--(stream->retainCount);
if (stream->retainCount < 1)
{
Mutex_unlock(stream->mutex);
PortAudioStream_free(NULL, stream);
}
else
Mutex_unlock(stream->mutex);
}
}
static void
PortAudioStream_retain(PortAudioStream *stream)
{
if (!Mutex_lock(stream->mutex))
{
++(stream->retainCount);
Mutex_unlock(stream->mutex);
}
}
/**
* Returns the current time in milliseconds (akin to
* <tt>java.lang.System#currentTimeMillis()</tt>).

Loading…
Cancel
Save