Optimizes the playback of audio when using PortAudio (i.e. the Renderer and when playing back notification sounds). The main optimization is the reduced copies of the audio samples which used to be at least 3 in the best case and now there is 1 in the best case and 3 in the worst case. On my Ubuntu Karmic desktop, I see audio interruptions being brought down from often to rare. The binaries for 32- and 64-bit Linux and 32-bit Windows are kindly prepared by Damian Minkov. There is no binary for 64-bit Windows.

cusax-fix
Lyubomir Marinov 16 years ago
parent 173869a707
commit 32a1fc08a4

Binary file not shown.

Binary file not shown.

@ -104,10 +104,10 @@ JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_neomedia_portaudio_Po
/*
* Class: net_java_sip_communicator_impl_neomedia_portaudio_PortAudio
* Method: Pa_WriteStream
* Signature: (J[BJ)V
* Signature: (J[BIJI)V
*/
JNIEXPORT void JNICALL Java_net_java_sip_communicator_impl_neomedia_portaudio_PortAudio_Pa_1WriteStream
(JNIEnv *, jclass, jlong, jbyteArray, jlong);
(JNIEnv *, jclass, jlong, jbyteArray, jint, jlong, jint);
/*
* Class: net_java_sip_communicator_impl_neomedia_portaudio_PortAudio

@ -891,10 +891,10 @@ public void setEchoCancel(boolean enabled, boolean save)
{
try
{
PortAudioManager.getInstance().setEchoCancel(
enabled,
PortAudioManager.getInstance().getFrameSize(),
PortAudioManager.getInstance().getFilterLength());
PortAudioManager portAudioManager = PortAudioManager.getInstance();
portAudioManager
.setEchoCancel(enabled, portAudioManager.getFilterLength());
if(save)
NeomediaActivator.getConfigurationService()

@ -65,7 +65,7 @@ public class PortAudioAuto
// if PortAudio has a problem initializing like missing native
// components it will trow exception here and PortAudio rendering will
// not be inited.
PortAudioManager.getInstance();
PortAudioManager portAudioManager = PortAudioManager.getInstance();
// enable jmf logging, so we can track codec chains and formats
if(logger.isDebugEnabled())
@ -128,32 +128,32 @@ public class PortAudioAuto
boolean echoCancelEnabled =
config.getBoolean(
DeviceConfiguration.PROP_AUDIO_ECHOCANCEL_ENABLED,
PortAudioManager.getInstance().isEnabledEchoCancel());
portAudioManager.isEnabledEchoCancel());
if(echoCancelEnabled)
{
int echoCancelTail =
config.getInt(
DeviceConfiguration.PROP_AUDIO_ECHOCANCEL_TAIL,
PortAudioManager.getInstance().getFilterLength());
PortAudioManager.getInstance().setEchoCancel(
portAudioManager.getFilterLength());
portAudioManager.setEchoCancel(
echoCancelEnabled,
PortAudioManager.getInstance().getFrameSize(),
echoCancelTail);
}
boolean denoiseEnabled =
config.getBoolean(
DeviceConfiguration.PROP_AUDIO_DENOISE_ENABLED,
PortAudioManager.getInstance().isEnabledDeNoise());
PortAudioManager.getInstance().setDeNoise(denoiseEnabled);
portAudioManager.isEnabledDeNoise());
portAudioManager.setDeNoise(denoiseEnabled);
// suggested latency is saved in configuration as
// milliseconds but PortAudioManager use it as seconds
int defaultAudioLatency
= (int) (PortAudioManager.getSuggestedLatency()*1000);
int audioLatency = config.getInt(
DeviceConfiguration.PROP_AUDIO_LATENCY,
(int)(PortAudioManager.getSuggestedLatency()*1000));
if(audioLatency !=
(int)PortAudioManager.getSuggestedLatency()*1000)
defaultAudioLatency);
if(audioLatency != defaultAudioLatency)
PortAudioManager.setSuggestedLatency(
(double)audioLatency/1000d);
}

@ -6,9 +6,6 @@
*/
package net.java.sip.communicator.impl.neomedia.jmfext.media.protocol.portaudio;
import net.java.sip.communicator.impl.neomedia.portaudio.*;
import net.java.sip.communicator.impl.neomedia.portaudio.streams.*;
import java.io.*;
import javax.media.*;
@ -16,6 +13,8 @@
import javax.media.protocol.*;
import net.java.sip.communicator.impl.neomedia.control.*;
import net.java.sip.communicator.impl.neomedia.portaudio.*;
import net.java.sip.communicator.impl.neomedia.portaudio.streams.*;
/**
* The stream used by jmf, wraps our InputPortAudioStream, which wraps
@ -28,8 +27,8 @@ public class DSAudioStream
extends ControlsAdapter
implements PullBufferStream
{
private final static ContentDescriptor cd =
new ContentDescriptor(ContentDescriptor.RAW);
private static final ContentDescriptor cd
= new ContentDescriptor(ContentDescriptor.RAW);
private final int deviceIndex;
@ -55,8 +54,7 @@ void start()
{
if(stream == null)
{
AudioFormat audioFormat =
(AudioFormat)DataSource.getCaptureFormat();
AudioFormat audioFormat = DataSource.getCaptureFormat();
stream = PortAudioManager.getInstance().getInputStream(deviceIndex,
audioFormat.getSampleRate(), audioFormat.getChannels());
@ -106,7 +104,7 @@ public void read(Buffer buffer)
buffer.setData(bytebuff);
buffer.setLength(bytebuff.length);
buffer.setFlags(0);
buffer.setFlags(Buffer.FLAG_SYSTEM_TIME);
buffer.setFormat(getFormat());
buffer.setHeader(null);

@ -73,24 +73,24 @@ public class DataSource
/**
* The format of the media captured by the datasource.
*/
private static AudioFormat captureAudioFormat =
new AudioFormat(
private static AudioFormat captureAudioFormat
= new AudioFormat(
AudioFormat.LINEAR,
8000,
16,
1,
AudioFormat.LITTLE_ENDIAN,
AudioFormat.SIGNED,
16,
Format.NOT_SPECIFIED,
Format.byteArray);
8000,
16,
1,
AudioFormat.LITTLE_ENDIAN,
AudioFormat.SIGNED,
16,
Format.NOT_SPECIFIED,
Format.byteArray);
/**
* Return the formats supported by the datasource.
*
* @return the supported formats.
*/
public static Format getCaptureFormat()
public static AudioFormat getCaptureFormat()
{
return captureAudioFormat;
}

@ -127,26 +127,22 @@ public synchronized void stop()
/**
* Processes the data and renders it
* to the output device represented by this Renderer.
* @param inputBuffer the input data.
* @param buffer the input data.
* @return BUFFER_PROCESSED_OK if the processing is successful.
*/
public synchronized int process(Buffer inputBuffer)
public synchronized int process(Buffer buffer)
{
byte[] buff = new byte[inputBuffer.getLength()];
System.arraycopy(
(byte[])inputBuffer.getData(),
inputBuffer.getOffset(),
buff,
0,
buff.length);
try
{
stream.write(buff);
stream
.write(
(byte[]) buffer.getData(),
buffer.getOffset(),
buffer.getLength());
}
catch (PortAudioException e)
catch (PortAudioException paex)
{
logger.error("Error writing to device", e);
logger.error("Error writing to device", paex);
}
return BUFFER_PROCESSED_OK;

@ -37,7 +37,7 @@ public class PortAudioClipImpl
private final URL url;
private Object syncObject = new Object();
private final Object syncObject = new Object();
/**
* Creates the audio clip and initialize the listener used from the
@ -110,6 +110,7 @@ private class PlayThread
implements Runnable
{
private final byte[] buffer = new byte[1024];
private OutputPortAudioStream portAudioStream = null;
public void run()
@ -124,18 +125,22 @@ public void run()
if (portAudioStream == null)
{
int deviceIndex =
PortAudioUtils.getDeviceIndexFromLocator(
int deviceIndex
= PortAudioUtils.getDeviceIndexFromLocator(
audioNotifier.getDeviceConfiguration().
getAudioNotifyDevice().getLocator());
portAudioStream = PortAudioManager.getInstance().
getOutputStream(
deviceIndex,
audioStreamFormat.getSampleRate(),
audioStreamFormat.getChannels(),
PortAudioUtils.getPortAudioSampleFormat(
audioStreamFormat.getSampleSizeInBits()));
portAudioStream
= PortAudioManager
.getInstance()
.getOutputStream(
deviceIndex,
audioStreamFormat.getSampleRate(),
audioStreamFormat.getChannels(),
PortAudioUtils
.getPortAudioSampleFormat(
audioStreamFormat
.getSampleSizeInBits()));
portAudioStream.start();
}
@ -146,8 +151,12 @@ public void run()
return;
}
while(started && audioStream.read(buffer) != -1)
portAudioStream.write(buffer);
int bufferLength;
while(started
&& ((bufferLength = audioStream.read(buffer))
!= -1))
portAudioStream.write(buffer, 0, bufferLength);
if(!isLooping())
{
@ -158,12 +167,14 @@ public void run()
else
{
synchronized(syncObject) {
if (started) {
try {
if (started)
try
{
syncObject.wait(getLoopInterval());
} catch (InterruptedException e) {
}
}
catch (InterruptedException e)
{
}
}
}
}

@ -8,7 +8,9 @@
/**
* PortAudio functions.
*
* @author Lubomir Marinov
* @author Damian Minkov
*/
public final class PortAudio
{
@ -262,10 +264,39 @@ public static native void Pa_StopStream(long stream)
* @param frames The number of frames to be written from buffer.
* @throws PortAudioException
*/
public static void Pa_WriteStream(long stream, byte[] buffer, long frames)
throws PortAudioException
{
Pa_WriteStream(stream, buffer, 0, frames, 1);
}
/**
* Writes samples to an output stream. Does not return until the specified
* samples have been consumed - this may involve waiting for the operating
* system to consume the data.
* <p>
* Provides better efficiency than achieved through multiple consecutive
* calls to {@link #Pa_WriteStream(long, byte[], long)} with one and the
* same buffer because the JNI access to the bytes of the buffer which is
* likely to copy the whole buffer is only performed once.
* </p>
*
* @param stream the pointer to the PortAudio stream to write the samples to
* @param buffer the buffer containing the samples to be written
* @param offset the byte offset in <tt>buffer</tt> at which the samples to
* be written start
* @param frames the number of frames from <tt>buffer</tt> starting at
* <tt>offset</tt> are to be written with a single write
* @param numberOfWrites the number of writes each writing <tt>frames</tt>
* number of frames to be performed
* @throws PortAudioException if anything goes wrong while writing
*/
public static native void Pa_WriteStream(
long stream,
byte[] buffer,
long frames)
int offset,
long frames,
int numberOfWrites)
throws PortAudioException;
/**

@ -16,13 +16,17 @@
* them.
*
* @author Damian Minkov
* @author Lubomir Marinov
*/
public class PortAudioManager
{
/**
* 20ms in 8kHz is 160 samples.
* The number of frames to be read from or written to a native PortAudio
* stream in a single transfer of data. The current value is based on 20ms
* of audio with 8kHz frame rate which is equal to 160 frames.
*/
public static final int NUM_SAMPLES = 160;
private static final int FRAMES_PER_BUFFER = 160;
/**
* The static instance of portaudio manager.
@ -55,10 +59,11 @@ public class PortAudioManager
private boolean enabledDeNoise = true;
/**
* The default value for the frame size we use to read and write
* by portaudio. Currently 20 ms.
* The number of frames to be read from or written to a native PortAudio
* stream in a single transfer of data. The current value is based on 20ms
* of audio with 8kHz frame rate which is equal to 160 frames.
*/
private int frameSize = NUM_SAMPLES;
private final int framesPerBuffer = FRAMES_PER_BUFFER;
/**
* The default value for number of samples of echo to cancel.
@ -113,24 +118,32 @@ public InputPortAudioStream getInputStream(
throws PortAudioException
{
MasterPortAudioStream st = inputStreams.get(deviceIndex);
if(st == null)
{
st = new MasterPortAudioStream(deviceIndex, sampleRate, channels);
inputStreams.put(deviceIndex, st);
// if there is a output streams, get the latest one
// and connect them
// todo: we must link input to all outputs ???
if(isEnabledEchoCancel() && outputStreams.size() > 0)
{
OutputPortAudioStream out = outputStreams.get(
outputStreams.size() - 1);
st.setParams(out, isEnabledDeNoise(),
isEnabledEchoCancel(), getFrameSize(), getFilterLength());
}
/*
* If there are output streams, get the latest one and connect them.
*/
// TODO We must link input to all outputs???
boolean echoCancelIsEnabled = isEnabledEchoCancel();
int outputStreamCount;
OutputPortAudioStream out;
if(echoCancelIsEnabled
&& ((outputStreamCount = outputStreams.size()) > 0))
out = outputStreams.get(outputStreamCount - 1);
else
st.setParams(null, isEnabledDeNoise(),
isEnabledEchoCancel(), getFrameSize(), getFilterLength());
out = null;
st.setParams(
out,
isEnabledDeNoise(),
echoCancelIsEnabled,
getFramesPerBuffer(),
getFilterLength());
}
return new InputPortAudioStream(st);
@ -152,17 +165,26 @@ public OutputPortAudioStream getOutputStream(int deviceIndex,
{
OutputPortAudioStream out
= new OutputPortAudioStream(deviceIndex, sampleRate, channels);
outputStreams.add(out);
// if there are input streams created, get the first one
// and link it to this output
// TODO what to do with the others
if(isEnabledEchoCancel() && inputStreams.size() > 0)
/*
* If there are input streams created, get the first one and link it to
* this output.
*/
// TODO What to do with the others?
boolean echoCancelIsEnabled = isEnabledEchoCancel();
if (echoCancelIsEnabled && (inputStreams.size() > 0))
{
MasterPortAudioStream st = inputStreams.values().iterator().next();
st.setParams(out, isEnabledEchoCancel(),
isEnabledDeNoise(), getFrameSize(), getFilterLength());
st.setParams(
out,
echoCancelIsEnabled,
isEnabledDeNoise(),
getFramesPerBuffer(),
getFilterLength());
}
return out;
@ -206,16 +228,13 @@ public OutputPortAudioStream getOutputStream(
/**
* Enables or disables echo cancel.
* @param enabled should we enable or disable echo cancelation
* @param frameSize Number of samples to process at one time
* (should correspond to 10-20 ms)
* @param enabled should we enable or disable echo cancellation
* @param filterLength Number of samples of echo to cancel
* (should generally correspond to 100-500 ms)
*/
public void setEchoCancel(boolean enabled, int frameSize, int filterLength)
public void setEchoCancel(boolean enabled, int filterLength)
{
this.enabledEchoCancel = enabled;
this.frameSize = frameSize;
this.filterLength = filterLength;
}
@ -274,12 +293,14 @@ public boolean isEnabledDeNoise()
}
/**
* Number of samples to process at one time (should correspond to 10-20 ms).
* @return the frameSize.
* Gets the number of frames to process at a time (should correspond to
* 10-20ms).
*
* @return the number of frames to process at a time
*/
public int getFrameSize()
public int getFramesPerBuffer()
{
return frameSize;
return framesPerBuffer;
}
/**

@ -38,7 +38,19 @@ public class MasterPortAudioStream
/**
* The frame size we use.
*/
private int frameSize;
private final int frameSize;
/**
* The number of frames to read from a native PortAudio stream in a single
* invocation.
*/
private final int framesPerBuffer;
/**
* The number of bytes to read from a native PortAudio stream in a single
* invocation. Based on {@link #framesPerBuffer} and {@link #frameSize}.
*/
private final int bytesPerBuffer;
/**
* The sample rate for the current stream.
@ -85,6 +97,8 @@ public MasterPortAudioStream(
frameSize
= PortAudio.Pa_GetSampleSize(PortAudio.SAMPLE_FORMAT_INT16)
* channels;
framesPerBuffer = PortAudioManager.getInstance().getFramesPerBuffer();
bytesPerBuffer = frameSize * framesPerBuffer;
}
/**
@ -203,12 +217,11 @@ public synchronized byte[] read()
if(!started)
return new byte[0];
byte[] bytebuff = new byte[PortAudioManager.NUM_SAMPLES*frameSize];
byte[] bytebuff = new byte[bytesPerBuffer];
synchronized(connectedToStreamSync)
{
PortAudio.Pa_ReadStream(
stream, bytebuff, PortAudioManager.NUM_SAMPLES);
PortAudio.Pa_ReadStream(stream, bytebuff, framesPerBuffer);
}
for(InputPortAudioStream slave : slaves)

@ -39,22 +39,42 @@ public class OutputPortAudioStream
*/
private final int frameSize;
/**
* The number of frames to write to the native PortAudioStream represented
* by this instance with a single invocation.
*/
private final int framesPerBuffer;
/**
* The number of bytes to write to a native PortAudio stream with a single
* invocation. Based on {@link #framesPerBuffer} and {@link #frameSize}.
*/
private final int bytesPerBuffer;
/**
* The stream pointer we are using or 0 if stopped and not initialized.
*/
private long stream;
/**
* Whether this stream is started.
*/
private boolean started = false;
/**
* Buffer left for writing from previous write,
* as everything is split into parts of PortAudioManager.NUM_SAMPLES,
* this is what has left.
* The audio samples left unwritten by a previous call to
* {@link #write(byte[], int, int)}. As {@link #bytesPerBuffer} number of
* bytes are always written, the number of the unwritten audio samples is
* always less than that.
*/
private byte[] bufferLeft = null;
/**
* The number of bytes in {@link #bufferLeft} representing unwritten audio
* samples.
*/
private int bufferLeftLength = 0;
/**
* We use this object to sync input stream reads with this output stream
* closes, if this output stream is connected to input stream(when using
@ -86,7 +106,7 @@ public OutputPortAudioStream(
* @throws PortAudioException if stream fails to open.
*/
public OutputPortAudioStream(
int deviceIndex, double sampleRate, int channels, long sampleFormat)
int deviceIndex, double sampleRate, int channels, long sampleFormat)
throws PortAudioException
{
this.deviceIndex = deviceIndex;
@ -95,6 +115,9 @@ public OutputPortAudioStream(
this.sampleFormat = sampleFormat;
frameSize = PortAudio.Pa_GetSampleSize(sampleFormat)*channels;
framesPerBuffer = PortAudioManager.getInstance().getFramesPerBuffer();
bytesPerBuffer = frameSize * framesPerBuffer;
stream = createStream();
}
@ -149,77 +172,89 @@ private long createStream()
}
/**
* We will split everything into parts of PortAudioManager.NUM_SAMPLES.
* If something is left we will save it for next write and use it than.
* Writes a specific <tt>byte</tt> buffer of audio samples into the native
* PortAudio stream represented by this instance.
*<p>
* Splits the specified buffer and performs multiple writes with
* {@link PortAudioManager#getFramesPerBuffer()} number of frames at a time.
* If any bytes from the specified buffer remain unwritten, they are
* retained for the next write to be prepended to its buffer.
* </p>
*
* @param buffer the current buffer.
* @throws PortAudioException error while writing to device.
* @param buffer the <tt>byte</tt> buffer to the written into the native
* PortAudio stream represented by this instance
* @param offset the offset in <tt>buffer</tt> at which the audio samples to
* be written begin
* @param length the length of the audio samples in <tt>buffer</tt> to be
* written
* @throws PortAudioException if anything goes wrong while writing
*/
public synchronized void write(byte[] buffer)
public synchronized void write(byte[] buffer, int offset, int length)
throws PortAudioException
{
if((stream == 0) || !started)
return;
int numSamples = PortAudioManager.NUM_SAMPLES*frameSize;
/*
* If there are audio samples left unwritten from a previous write,
* prepend them to the specified buffer. If it's possible to write them
* now, do it.
*/
if ((bufferLeft != null) && (bufferLeftLength > 0))
{
int numberOfBytesInBufferLeftToBytesPerBuffer
= bytesPerBuffer - bufferLeftLength;
int numberOfBytesToCopyToBufferLeft
= (numberOfBytesInBufferLeftToBytesPerBuffer < length)
? numberOfBytesInBufferLeftToBytesPerBuffer
: length;
int currentIx = 0;
System
.arraycopy(
buffer,
offset,
bufferLeft,
bufferLeftLength,
numberOfBytesToCopyToBufferLeft);
offset += numberOfBytesToCopyToBufferLeft;
length -= numberOfBytesToCopyToBufferLeft;
bufferLeftLength += numberOfBytesToCopyToBufferLeft;
// if there are bytes from previous run
if(bufferLeft != null && bufferLeft.length > 0)
{
if(buffer.length + bufferLeft.length >= numSamples)
if (bufferLeftLength == bytesPerBuffer)
{
byte[] tmp = new byte[numSamples];
System.arraycopy(bufferLeft, 0, tmp, 0, bufferLeft.length);
System.arraycopy(buffer, currentIx, tmp,
bufferLeft.length, numSamples - bufferLeft.length);
currentIx += numSamples - bufferLeft.length;
bufferLeft = null;
PortAudio.Pa_WriteStream(
stream,tmp, tmp.length/frameSize);
}
else
{
// not enough bytes even with previous left
// so let store everything
byte[] tmp = new byte[numSamples];
System.arraycopy(bufferLeft, 0, tmp, 0, bufferLeft.length);
System.arraycopy(buffer, currentIx, tmp,
bufferLeft.length, numSamples - bufferLeft.length);
bufferLeft = null;
return;
PortAudio.Pa_WriteStream(stream, bufferLeft, framesPerBuffer);
bufferLeftLength = 0;
}
}
// now use all the current buffer
if(buffer.length > numSamples)
// Write the audio samples from the specified buffer.
int numberOfWrites = length / bytesPerBuffer;
if (numberOfWrites > 0)
{
while(currentIx <= buffer.length - numSamples)
{
byte[] tmp = new byte[numSamples];
System.arraycopy(buffer, currentIx, tmp, 0, numSamples);
PortAudio
.Pa_WriteStream(
stream,
buffer,
offset,
framesPerBuffer,
numberOfWrites);
PortAudio.Pa_WriteStream(
stream,tmp, tmp.length/frameSize);
currentIx += numSamples;
}
int bytesWritten = numberOfWrites * bytesPerBuffer;
if(currentIx < buffer.length)
{
bufferLeft = new byte[buffer.length - currentIx];
System.arraycopy(buffer, currentIx, bufferLeft, 0, bufferLeft.length);
}
}
else if(buffer.length < numSamples)
{
bufferLeft = buffer;
offset += bytesWritten;
length -= bytesWritten;
}
else
// If anything was left unwritten, remember it for next time.
if (length > 0)
{
PortAudio.Pa_WriteStream(
stream,buffer, buffer.length/frameSize);
if (bufferLeft == null)
{
bufferLeft = new byte[bytesPerBuffer];
}
System.arraycopy(buffer, offset, bufferLeft, 0, length);
bufferLeftLength = length;
}
}

Loading…
Cancel
Save