mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
Resolves Audio issues (underwater/overlapping audio buffers).
Redesigned audio manager handling of audio outputs to ensure that only one channel's audio can be routed to an audio output at a time. Changed audio outputs to use the shared thread pool manager for scheduled tasks, instead of creating a thread pool for each audio output.
This commit is contained in:
@@ -2,7 +2,7 @@ package audio;
|
|||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -19,14 +19,11 @@ package audio;
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -53,7 +50,6 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
private static final Logger mLog = LoggerFactory.getLogger( AudioManager.class );
|
private static final Logger mLog = LoggerFactory.getLogger( AudioManager.class );
|
||||||
|
|
||||||
public static final int AUDIO_TIMEOUT = 2000; //2 seconds
|
public static final int AUDIO_TIMEOUT = 2000; //2 seconds
|
||||||
public static final int NO_SOURCE = 0;
|
|
||||||
|
|
||||||
public static final AudioEvent CONFIGURATION_CHANGE_STARTED =
|
public static final AudioEvent CONFIGURATION_CHANGE_STARTED =
|
||||||
new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null );
|
new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_STARTED, null );
|
||||||
@@ -62,19 +58,19 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_COMPLETE, null );
|
new AudioEvent( Type.AUDIO_CONFIGURATION_CHANGE_COMPLETE, null );
|
||||||
|
|
||||||
private LinkedTransferQueue<AudioPacket> mAudioPacketQueue = new LinkedTransferQueue<>();
|
private LinkedTransferQueue<AudioPacket> mAudioPacketQueue = new LinkedTransferQueue<>();
|
||||||
private Map<Integer,AudioChannelAssignment> mChannelAssignments = new HashMap<>();
|
private Map<Integer,AudioOutputConnection> mChannelConnectionMap = new HashMap<>();
|
||||||
private int mLowestPrioritySource;
|
private List<AudioOutputConnection> mAudioOutputConnections = new ArrayList<>();
|
||||||
|
private AudioOutputConnection mLowestPriorityConnection;
|
||||||
|
private int mAvailableConnectionCount;
|
||||||
|
|
||||||
private List<AudioOutput> mAvailableAudioOutputs = new ArrayList<>();
|
|
||||||
private Map<String,AudioOutput> mAudioOutputMap = new HashMap<>();
|
private Map<String,AudioOutput> mAudioOutputMap = new HashMap<>();
|
||||||
private List<String> mChannelNames = new ArrayList<>();
|
|
||||||
|
|
||||||
private Broadcaster<AudioEvent> mControllerBroadcaster = new Broadcaster<>();
|
private Broadcaster<AudioEvent> mControllerBroadcaster = new Broadcaster<>();
|
||||||
|
|
||||||
private ThreadPoolManager mThreadPoolManager;
|
private ThreadPoolManager mThreadPoolManager;
|
||||||
private ScheduledFuture<?> mProcessingTask;
|
private ScheduledFuture<?> mProcessingTask;
|
||||||
|
|
||||||
private MixerChannelConfiguration mMixerChannel;
|
private MixerChannelConfiguration mMixerChannelConfiguration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes all audio produced by the decoding channels and routes audio
|
* Processes all audio produced by the decoding channels and routes audio
|
||||||
@@ -92,11 +88,11 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
|
|
||||||
if( mixer != null )
|
if( mixer != null )
|
||||||
{
|
{
|
||||||
mMixerChannel = new MixerChannelConfiguration( mixer, MixerChannel.MONO );
|
mMixerChannelConfiguration = new MixerChannelConfiguration( mixer, MixerChannel.MONO );
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
setMixerChannelConfiguration( mMixerChannel );
|
setMixerChannelConfiguration( mMixerChannelConfiguration );
|
||||||
}
|
}
|
||||||
catch ( AudioException e )
|
catch ( AudioException e )
|
||||||
{
|
{
|
||||||
@@ -116,6 +112,15 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
|
|
||||||
mThreadPoolManager = null;
|
mThreadPoolManager = null;
|
||||||
mProcessingTask = null;
|
mProcessingTask = null;
|
||||||
|
|
||||||
|
mChannelConnectionMap.clear();
|
||||||
|
|
||||||
|
for( AudioOutputConnection connection: mAudioOutputConnections )
|
||||||
|
{
|
||||||
|
connection.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
mAudioOutputConnections.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -124,210 +129,47 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
mAudioPacketQueue.add( packet );
|
mAudioPacketQueue.add( packet );
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AudioPacketProcessor implements Runnable
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
removeCompletedChannelAssignments();
|
|
||||||
|
|
||||||
if( mAudioPacketQueue != null )
|
|
||||||
{
|
|
||||||
List<AudioPacket> packets = new ArrayList<AudioPacket>();
|
|
||||||
|
|
||||||
mAudioPacketQueue.drainTo( packets );
|
|
||||||
|
|
||||||
for( AudioPacket packet: packets )
|
|
||||||
{
|
|
||||||
/* Don't process any packet's marked as do not monitor */
|
|
||||||
if( !packet.getAudioMetadata().isDoNotMonitor() )
|
|
||||||
{
|
|
||||||
int source = packet.getAudioMetadata().getSource();
|
|
||||||
|
|
||||||
if( packet.getType() != AudioPacket.Type.END )
|
|
||||||
{
|
|
||||||
/* Existing channel assignment */
|
|
||||||
if( mChannelAssignments.containsKey( source ) )
|
|
||||||
{
|
|
||||||
mChannelAssignments.get( source ).getAudioOutput()
|
|
||||||
.receive( packet );
|
|
||||||
}
|
|
||||||
/* Start a new channel assignment */
|
|
||||||
else if( !mAvailableAudioOutputs.isEmpty() )
|
|
||||||
{
|
|
||||||
AudioOutput channel = mAvailableAudioOutputs.remove( 0 );
|
|
||||||
|
|
||||||
mChannelAssignments.put( packet.getAudioMetadata().getSource(),
|
|
||||||
new AudioChannelAssignment( channel, packet.getAudioMetadata().getPriority() ) );
|
|
||||||
|
|
||||||
updateLowestPriorityAssignment();
|
|
||||||
|
|
||||||
channel.receive( packet );
|
|
||||||
}
|
|
||||||
/* Override the lowest priority assignment */
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if( mLowestPrioritySource > -1 )
|
|
||||||
{
|
|
||||||
int priority = packet.getAudioMetadata().getPriority();
|
|
||||||
|
|
||||||
if( packet.getAudioMetadata().isSelected() ||
|
|
||||||
priority < mChannelAssignments
|
|
||||||
.get( mLowestPrioritySource ).getPriority() )
|
|
||||||
{
|
|
||||||
AudioChannelAssignment assignment =
|
|
||||||
mChannelAssignments.remove( mLowestPrioritySource );
|
|
||||||
|
|
||||||
if( assignment != null )
|
|
||||||
{
|
|
||||||
assignment.setPriority( priority );
|
|
||||||
|
|
||||||
int overrideSource = packet.getAudioMetadata().getSource();
|
|
||||||
|
|
||||||
mChannelAssignments.put( overrideSource, assignment );
|
|
||||||
|
|
||||||
mLowestPrioritySource = overrideSource;
|
|
||||||
|
|
||||||
assignment.getAudioOutput().receive( packet );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
mLowestPrioritySource = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks each audio channel assignment and removes any completed channels.
|
* Checks each audio channel assignment and disconnects any inactive connections
|
||||||
*/
|
*/
|
||||||
private void removeCompletedChannelAssignments()
|
private void disconnectInactiveChannelAssignments()
|
||||||
{
|
{
|
||||||
if( !mChannelAssignments.isEmpty() )
|
boolean changed = false;
|
||||||
|
|
||||||
|
for( AudioOutputConnection connection: mAudioOutputConnections )
|
||||||
{
|
{
|
||||||
Iterator<Entry<Integer,AudioChannelAssignment>> it =
|
if( connection.isConnected() && connection.isInactive() )
|
||||||
mChannelAssignments.entrySet().iterator();
|
|
||||||
|
|
||||||
Entry<Integer,AudioChannelAssignment> entry;
|
|
||||||
|
|
||||||
boolean removed = false;
|
|
||||||
|
|
||||||
while( it.hasNext() )
|
|
||||||
{
|
{
|
||||||
entry = it.next();
|
mChannelConnectionMap.remove( connection.getSource() );
|
||||||
|
connection.disconnect();
|
||||||
if( entry.getValue().isComplete() )
|
changed = true;
|
||||||
{
|
|
||||||
mAvailableAudioOutputs.add( entry.getValue().getAudioOutput() );
|
|
||||||
entry.getValue().dispose();
|
|
||||||
it.remove();
|
|
||||||
removed = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if( removed )
|
if( changed )
|
||||||
{
|
{
|
||||||
updateLowestPriorityAssignment();
|
updateLowestPriorityAssignment();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifies the lowest priority channel assignment ... the one that can
|
* Identifies the lowest priority channel connection
|
||||||
* be overridden with a higher priority call
|
|
||||||
*/
|
*/
|
||||||
private void updateLowestPriorityAssignment()
|
private void updateLowestPriorityAssignment()
|
||||||
{
|
{
|
||||||
int lowestPriority = -1;
|
mLowestPriorityConnection = null;
|
||||||
|
|
||||||
mLowestPrioritySource = -1;
|
for( AudioOutputConnection connection: mAudioOutputConnections )
|
||||||
|
|
||||||
for( int source: mChannelAssignments.keySet() )
|
|
||||||
{
|
{
|
||||||
AudioChannelAssignment assignment = mChannelAssignments.get( source );
|
if( connection.isConnected() &&
|
||||||
|
( mLowestPriorityConnection == null ||
|
||||||
if( assignment.getPriority() > lowestPriority )
|
mLowestPriorityConnection.getPriority() > connection.getPriority() ) )
|
||||||
{
|
{
|
||||||
lowestPriority = assignment.getPriority();
|
mLowestPriorityConnection = connection;
|
||||||
|
|
||||||
mLowestPrioritySource = source;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void endAllAudioAssignments()
|
|
||||||
{
|
|
||||||
for( AudioOutput output: mAudioOutputMap.values() )
|
|
||||||
{
|
|
||||||
output.receive( new AudioPacket( AudioPacket.Type.END, null ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AudioChannelAssignment implements Listener<AudioEvent>
|
|
||||||
{
|
|
||||||
private static final long CALL_END_DELAY = 2000; //2 seconds
|
|
||||||
|
|
||||||
private AudioOutput mAudioOutput;
|
|
||||||
private int mPriority;
|
|
||||||
private long mAudioStoppedTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
public AudioChannelAssignment( AudioOutput audioOutput, int priority )
|
|
||||||
{
|
|
||||||
mAudioOutput = audioOutput;
|
|
||||||
mPriority = priority;
|
|
||||||
|
|
||||||
mAudioOutput.addAudioEventListener( this );
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isComplete()
|
|
||||||
{
|
|
||||||
return mAudioStoppedTime != Long.MAX_VALUE &&
|
|
||||||
mAudioStoppedTime + CALL_END_DELAY < System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
mAudioOutput.removeAudioEventListener( this );
|
|
||||||
mAudioOutput.reset();
|
|
||||||
mAudioOutput = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AudioOutput getAudioOutput()
|
|
||||||
{
|
|
||||||
return mAudioOutput;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPriority()
|
|
||||||
{
|
|
||||||
return mPriority;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPriority( int priority )
|
|
||||||
{
|
|
||||||
mPriority = priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receive( AudioEvent event )
|
|
||||||
{
|
|
||||||
switch( event.getType() )
|
|
||||||
{
|
|
||||||
case AUDIO_STARTED:
|
|
||||||
case AUDIO_STOPPED:
|
|
||||||
case AUDIO_CONTINUATION:
|
|
||||||
mAudioStoppedTime = System.currentTimeMillis();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMixerChannelConfiguration( MixerChannelConfiguration entry ) throws AudioException
|
public void setMixerChannelConfiguration( MixerChannelConfiguration entry ) throws AudioException
|
||||||
@@ -343,40 +185,28 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
mThreadPoolManager.cancel( mProcessingTask );
|
mThreadPoolManager.cancel( mProcessingTask );
|
||||||
}
|
}
|
||||||
|
|
||||||
endAllAudioAssignments();
|
disposeCurrentConfiguration();
|
||||||
|
|
||||||
for( Entry<Integer,AudioChannelAssignment> e: mChannelAssignments.entrySet() )
|
|
||||||
{
|
|
||||||
e.getValue().getAudioOutput().dispose();
|
|
||||||
}
|
|
||||||
mChannelAssignments.clear();
|
|
||||||
|
|
||||||
for( AudioOutput output: mAvailableAudioOutputs )
|
|
||||||
{
|
|
||||||
output.dispose();
|
|
||||||
}
|
|
||||||
mAvailableAudioOutputs.clear();
|
|
||||||
|
|
||||||
mChannelNames.clear();
|
|
||||||
mAudioOutputMap.clear();
|
|
||||||
|
|
||||||
switch( entry.getMixerChannel() )
|
switch( entry.getMixerChannel() )
|
||||||
{
|
{
|
||||||
case MONO:
|
case MONO:
|
||||||
AudioOutput mono = new MonoAudioOutput( entry.getMixer() );
|
AudioOutput mono = new MonoAudioOutput( mThreadPoolManager,
|
||||||
mAvailableAudioOutputs.add( mono );
|
entry.getMixer() );
|
||||||
mChannelNames.add( mono.getChannelName() );
|
mAudioOutputConnections.add( new AudioOutputConnection( mono ) );
|
||||||
|
mAvailableConnectionCount++;
|
||||||
mAudioOutputMap.put( mono.getChannelName(), mono );
|
mAudioOutputMap.put( mono.getChannelName(), mono );
|
||||||
break;
|
break;
|
||||||
case STEREO:
|
case STEREO:
|
||||||
AudioOutput left = new StereoAudioOutput( entry.getMixer(), MixerChannel.LEFT );
|
AudioOutput left = new StereoAudioOutput( mThreadPoolManager,
|
||||||
mAvailableAudioOutputs.add( left );
|
entry.getMixer(), MixerChannel.LEFT );
|
||||||
mChannelNames.add( left.getChannelName() );
|
mAudioOutputConnections.add( new AudioOutputConnection( left ) );
|
||||||
|
mAvailableConnectionCount++;
|
||||||
mAudioOutputMap.put( left.getChannelName(), left );
|
mAudioOutputMap.put( left.getChannelName(), left );
|
||||||
|
|
||||||
AudioOutput right = new StereoAudioOutput( entry.getMixer(), MixerChannel.RIGHT );
|
AudioOutput right = new StereoAudioOutput( mThreadPoolManager,
|
||||||
mAvailableAudioOutputs.add( right );
|
entry.getMixer(), MixerChannel.RIGHT );
|
||||||
mChannelNames.add( right.getChannelName() );
|
mAudioOutputConnections.add( new AudioOutputConnection( right ) );
|
||||||
|
mAvailableConnectionCount++;
|
||||||
mAudioOutputMap.put( right.getChannelName(), right );
|
mAudioOutputMap.put( right.getChannelName(), right );
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -392,10 +222,33 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears all channel assignments and terminates all audio outputs in
|
||||||
|
* preparation for complete shutdown or change to another mixer/channel
|
||||||
|
* configuration
|
||||||
|
*/
|
||||||
|
private void disposeCurrentConfiguration()
|
||||||
|
{
|
||||||
|
mChannelConnectionMap.clear();
|
||||||
|
|
||||||
|
for( AudioOutputConnection connection : mAudioOutputConnections )
|
||||||
|
{
|
||||||
|
connection.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
mAvailableConnectionCount = 0;
|
||||||
|
|
||||||
|
mAudioOutputConnections.clear();
|
||||||
|
|
||||||
|
mAudioOutputMap.clear();
|
||||||
|
|
||||||
|
mLowestPriorityConnection = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
|
public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException
|
||||||
{
|
{
|
||||||
return mMixerChannel;
|
return mMixerChannelConfiguration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -426,4 +279,237 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
{
|
{
|
||||||
mControllerBroadcaster.removeListener( listener );
|
mControllerBroadcaster.removeListener( listener );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an audio output connection for the packet if one is
|
||||||
|
* available, or overrides an existing lower priority connection.
|
||||||
|
* Returns null if no connection is available for the audio packet.
|
||||||
|
*
|
||||||
|
* @param packet from a source
|
||||||
|
* @return an audio output connection or null
|
||||||
|
*/
|
||||||
|
private AudioOutputConnection getConnection( AudioPacket packet )
|
||||||
|
{
|
||||||
|
int source = packet.getAudioMetadata().getSource();
|
||||||
|
|
||||||
|
//Use an existing connection
|
||||||
|
if( mChannelConnectionMap.containsKey( source ) )
|
||||||
|
{
|
||||||
|
return mChannelConnectionMap.get( source );
|
||||||
|
}
|
||||||
|
|
||||||
|
//Connect to and use an available connection
|
||||||
|
if( mAvailableConnectionCount > 0 )
|
||||||
|
{
|
||||||
|
for( AudioOutputConnection connection: mAudioOutputConnections )
|
||||||
|
{
|
||||||
|
if( connection.isDisconnected() )
|
||||||
|
{
|
||||||
|
connection.connect( source );
|
||||||
|
mChannelConnectionMap.put( source, connection );
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Preempt an existing connection and connect it to this higher priority source
|
||||||
|
else //Check for a channel priority override
|
||||||
|
{
|
||||||
|
int priority = packet.getAudioMetadata().getPriority();
|
||||||
|
|
||||||
|
if( mLowestPriorityConnection != null &&
|
||||||
|
priority < mLowestPriorityConnection.getPriority() )
|
||||||
|
{
|
||||||
|
AudioOutputConnection connection = mLowestPriorityConnection;
|
||||||
|
|
||||||
|
mChannelConnectionMap.remove( connection.getSource() );
|
||||||
|
|
||||||
|
connection.connect( source );
|
||||||
|
|
||||||
|
mChannelConnectionMap.put( source, connection );
|
||||||
|
|
||||||
|
updateLowestPriorityAssignment();
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AudioPacketProcessor implements Runnable
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
disconnectInactiveChannelAssignments();
|
||||||
|
|
||||||
|
if( mAudioPacketQueue != null )
|
||||||
|
{
|
||||||
|
List<AudioPacket> packets = new ArrayList<AudioPacket>();
|
||||||
|
|
||||||
|
mAudioPacketQueue.drainTo( packets );
|
||||||
|
|
||||||
|
for( AudioPacket packet: packets )
|
||||||
|
{
|
||||||
|
/* Don't process any packet's marked as do not monitor */
|
||||||
|
if( !packet.getAudioMetadata().isDoNotMonitor() &&
|
||||||
|
packet.getType() == AudioPacket.Type.AUDIO )
|
||||||
|
{
|
||||||
|
AudioOutputConnection connection = getConnection( packet );
|
||||||
|
|
||||||
|
if( connection != null )
|
||||||
|
{
|
||||||
|
connection.receive( packet );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio output connection manages a connection between a soure and an audio
|
||||||
|
* output and maintains current state information about the audio activity
|
||||||
|
* received form the source.
|
||||||
|
*/
|
||||||
|
public class AudioOutputConnection implements Listener<AudioEvent>
|
||||||
|
{
|
||||||
|
private static final long CALL_END_DELAY = 2000; //2 seconds
|
||||||
|
private static final int DISCONNECTED = -1;
|
||||||
|
|
||||||
|
private AudioOutput mAudioOutput;
|
||||||
|
private int mPriority = 0;
|
||||||
|
private long mLastActivity = System.currentTimeMillis();
|
||||||
|
private int mSource = DISCONNECTED;
|
||||||
|
|
||||||
|
public AudioOutputConnection( AudioOutput audioOutput )
|
||||||
|
{
|
||||||
|
mAudioOutput = audioOutput;
|
||||||
|
mAudioOutput.addAudioEventListener( this );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void receive( AudioPacket packet )
|
||||||
|
{
|
||||||
|
if( packet.hasAudioMetadata() &&
|
||||||
|
packet.getAudioMetadata().getSource() == mSource )
|
||||||
|
{
|
||||||
|
int priority = packet.getAudioMetadata().getPriority();
|
||||||
|
|
||||||
|
if( mPriority != priority )
|
||||||
|
{
|
||||||
|
mPriority = priority;
|
||||||
|
updateLowestPriorityAssignment();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTimestamp();
|
||||||
|
|
||||||
|
if( mAudioOutput != null )
|
||||||
|
{
|
||||||
|
mAudioOutput.receive( packet );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if( packet.hasAudioMetadata() )
|
||||||
|
{
|
||||||
|
mLog.error( "Received audio packet from channel source [" +
|
||||||
|
packet.getAudioMetadata().getSource() +
|
||||||
|
"] however this assignment is currently connected "
|
||||||
|
+ "to channel source [" + mSource + "]" );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error( "Received audio packet with no metadata - "
|
||||||
|
+ "cannot route audio packet" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTimestamp()
|
||||||
|
{
|
||||||
|
mLastActivity = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminates the audio output and prepares this connection for disposal
|
||||||
|
*/
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
mAudioOutput.dispose();
|
||||||
|
mAudioOutput = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this assignment is currently disconnected from a channel source
|
||||||
|
*/
|
||||||
|
public boolean isDisconnected()
|
||||||
|
{
|
||||||
|
return mSource == DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if this assignment is currently connected to a channel source
|
||||||
|
*/
|
||||||
|
public boolean isConnected()
|
||||||
|
{
|
||||||
|
return !isDisconnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects this assignment to the indicated source so that audio
|
||||||
|
* packets from this source can be sent to the audio output
|
||||||
|
*/
|
||||||
|
public void connect( int source )
|
||||||
|
{
|
||||||
|
mSource = source;
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Currently connected source or -1 if disconnected
|
||||||
|
*/
|
||||||
|
public int getSource()
|
||||||
|
{
|
||||||
|
return mSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects this assignment from the source and prevents any audio
|
||||||
|
* from being routed to the audio output until another source is assigned
|
||||||
|
*/
|
||||||
|
public void disconnect()
|
||||||
|
{
|
||||||
|
mSource = DISCONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if audio output is current inactive, meaning that the
|
||||||
|
* audio output hasn't recently processed any audio packets.
|
||||||
|
*/
|
||||||
|
public boolean isInactive()
|
||||||
|
{
|
||||||
|
return mLastActivity != Long.MAX_VALUE &&
|
||||||
|
mLastActivity + CALL_END_DELAY < System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPriority()
|
||||||
|
{
|
||||||
|
return mPriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive( AudioEvent event )
|
||||||
|
{
|
||||||
|
switch( event.getType() )
|
||||||
|
{
|
||||||
|
case AUDIO_STARTED:
|
||||||
|
case AUDIO_STOPPED:
|
||||||
|
case AUDIO_CONTINUATION:
|
||||||
|
updateTimestamp();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package audio.output;
|
package audio.output;
|
||||||
|
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import javax.sound.sampled.FloatControl;
|
import javax.sound.sampled.FloatControl;
|
||||||
@@ -15,6 +14,7 @@ import audio.AudioEvent;
|
|||||||
import audio.AudioEvent.Type;
|
import audio.AudioEvent.Type;
|
||||||
import audio.AudioPacket;
|
import audio.AudioPacket;
|
||||||
import audio.metadata.AudioMetadata;
|
import audio.metadata.AudioMetadata;
|
||||||
|
import controller.ThreadPoolManager;
|
||||||
|
|
||||||
public abstract class AudioOutput implements Listener<AudioPacket>
|
public abstract class AudioOutput implements Listener<AudioPacket>
|
||||||
{
|
{
|
||||||
@@ -25,10 +25,15 @@ public abstract class AudioOutput implements Listener<AudioPacket>
|
|||||||
|
|
||||||
protected Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
protected Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
||||||
protected LinkedTransferQueue<AudioPacket> mBuffer = new LinkedTransferQueue<>();
|
protected LinkedTransferQueue<AudioPacket> mBuffer = new LinkedTransferQueue<>();
|
||||||
protected ScheduledExecutorService mExecutorService;
|
protected ThreadPoolManager mThreadPoolManager;
|
||||||
protected boolean mCanProcessAudio;
|
protected boolean mCanProcessAudio;
|
||||||
protected long mLastActivity;
|
protected long mLastActivity;
|
||||||
|
|
||||||
|
public AudioOutput( ThreadPoolManager threadPoolManager )
|
||||||
|
{
|
||||||
|
mThreadPoolManager = threadPoolManager;
|
||||||
|
}
|
||||||
|
|
||||||
public void reset()
|
public void reset()
|
||||||
{
|
{
|
||||||
broadcast( new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
|
broadcast( new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
|
||||||
@@ -41,14 +46,11 @@ public abstract class AudioOutput implements Listener<AudioPacket>
|
|||||||
|
|
||||||
mBuffer.clear();
|
mBuffer.clear();
|
||||||
|
|
||||||
if( mExecutorService != null )
|
|
||||||
{
|
|
||||||
mExecutorService.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
mAudioEventBroadcaster.dispose();
|
mAudioEventBroadcaster.dispose();
|
||||||
mAudioEventBroadcaster = null;
|
mAudioEventBroadcaster = null;
|
||||||
mAudioMetadataListener = null;
|
mAudioMetadataListener = null;
|
||||||
|
|
||||||
|
mThreadPoolManager = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract String getChannelName();
|
public abstract String getChannelName();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import java.nio.ByteOrder;
|
|||||||
import java.nio.ShortBuffer;
|
import java.nio.ShortBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
@@ -24,7 +24,8 @@ import audio.AudioEvent;
|
|||||||
import audio.AudioFormats;
|
import audio.AudioFormats;
|
||||||
import audio.AudioPacket;
|
import audio.AudioPacket;
|
||||||
import audio.AudioPacket.Type;
|
import audio.AudioPacket.Type;
|
||||||
import controller.NamingThreadFactory;
|
import controller.ThreadPoolManager;
|
||||||
|
import controller.ThreadPoolManager.ThreadType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mono Audio output with automatic flow control based on the availability of
|
* Mono Audio output with automatic flow control based on the availability of
|
||||||
@@ -54,9 +55,11 @@ public class MonoAudioOutput extends AudioOutput
|
|||||||
private AudioEvent mAudioStopEvent;
|
private AudioEvent mAudioStopEvent;
|
||||||
private AudioEvent mAudioContinuationEvent;
|
private AudioEvent mAudioContinuationEvent;
|
||||||
|
|
||||||
public MonoAudioOutput( Mixer mixer )
|
private ScheduledFuture<?> mProcessorTask;
|
||||||
|
|
||||||
|
public MonoAudioOutput( ThreadPoolManager threadPoolManager, Mixer mixer )
|
||||||
{
|
{
|
||||||
super();
|
super( threadPoolManager );
|
||||||
|
|
||||||
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
|
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
|
||||||
getChannelName() );
|
getChannelName() );
|
||||||
@@ -73,15 +76,7 @@ public class MonoAudioOutput extends AudioOutput
|
|||||||
if( mOutput != null )
|
if( mOutput != null )
|
||||||
{
|
{
|
||||||
mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO, BUFFER_SIZE );
|
mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO, BUFFER_SIZE );
|
||||||
|
|
||||||
mCanProcessAudio = true;
|
mCanProcessAudio = true;
|
||||||
|
|
||||||
mExecutorService = Executors.newSingleThreadScheduledExecutor(
|
|
||||||
new NamingThreadFactory( "audio (mono) output" ) );
|
|
||||||
|
|
||||||
/* Run the queue processor task every 40 milliseconds */
|
|
||||||
mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
|
|
||||||
0, 40, TimeUnit.MILLISECONDS );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch ( LineUnavailableException e )
|
catch ( LineUnavailableException e )
|
||||||
@@ -114,12 +109,10 @@ public class MonoAudioOutput extends AudioOutput
|
|||||||
mixer.getMixerInfo().getName() + "]" );
|
mixer.getMixerInfo().getName() + "]" );
|
||||||
}
|
}
|
||||||
|
|
||||||
mExecutorService = Executors.newSingleThreadScheduledExecutor(
|
|
||||||
new NamingThreadFactory( "audio (mono) output" ) );
|
|
||||||
|
|
||||||
/* Run the queue processor task every 40 milliseconds */
|
/* Run the queue processor task every 40 milliseconds */
|
||||||
mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
|
mProcessorTask = mThreadPoolManager.scheduleFixedRate(
|
||||||
0, 40, TimeUnit.MILLISECONDS );
|
ThreadType.AUDIO_PROCESSING, new QueueProcessor(),
|
||||||
|
40, TimeUnit.MILLISECONDS );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,12 +124,23 @@ public class MonoAudioOutput extends AudioOutput
|
|||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
|
if( mProcessorTask != null && mThreadPoolManager != null )
|
||||||
|
{
|
||||||
|
mThreadPoolManager.cancel( mProcessorTask );
|
||||||
|
}
|
||||||
|
|
||||||
|
mProcessorTask = null;
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
||||||
if( mOutput != null )
|
if( mOutput != null )
|
||||||
{
|
{
|
||||||
mOutput.close();
|
mOutput.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mOutput = null;
|
||||||
|
mGainControl = null;
|
||||||
|
mMuteControl = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class QueueProcessor implements Runnable
|
public class QueueProcessor implements Runnable
|
||||||
@@ -184,55 +188,10 @@ public class MonoAudioOutput extends AudioOutput
|
|||||||
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( mOutput.available() >= buffer.array().length )
|
//This is a blocking write
|
||||||
{
|
mOutput.write( buffer.array(), 0, buffer.array().length );
|
||||||
mOutput.write( buffer.array(), 0,
|
|
||||||
buffer.array().length );
|
|
||||||
|
|
||||||
checkStart();
|
checkStart();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int wrote = 0;
|
|
||||||
|
|
||||||
int toWrite = buffer.array().length;
|
|
||||||
|
|
||||||
while( toWrite > 0 )
|
|
||||||
{
|
|
||||||
int available = mOutput.available();
|
|
||||||
|
|
||||||
if( available < toWrite )
|
|
||||||
{
|
|
||||||
if( available > 0 )
|
|
||||||
{
|
|
||||||
wrote += mOutput.write( buffer.array(),
|
|
||||||
wrote, available );
|
|
||||||
|
|
||||||
checkStart();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Thread.sleep( 100 );
|
|
||||||
}
|
|
||||||
catch ( InterruptedException e )
|
|
||||||
{
|
|
||||||
mLog.debug( "Mono Audio Output - interrupted while sleeping awaiting buffer drain" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wrote += mOutput.write( buffer.array(), wrote,
|
|
||||||
toWrite );
|
|
||||||
|
|
||||||
checkStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
toWrite = buffer.array().length - wrote;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcast( packet.getAudioMetadata() );
|
broadcast( packet.getAudioMetadata() );
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import java.nio.ByteOrder;
|
|||||||
import java.nio.ShortBuffer;
|
import java.nio.ShortBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
@@ -24,7 +24,8 @@ import audio.AudioEvent;
|
|||||||
import audio.AudioFormats;
|
import audio.AudioFormats;
|
||||||
import audio.AudioPacket;
|
import audio.AudioPacket;
|
||||||
import audio.AudioPacket.Type;
|
import audio.AudioPacket.Type;
|
||||||
import controller.NamingThreadFactory;
|
import controller.ThreadPoolManager;
|
||||||
|
import controller.ThreadPoolManager.ThreadType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mono Audio output in stereo format with automatic flow control based on the
|
* Mono Audio output in stereo format with automatic flow control based on the
|
||||||
@@ -56,9 +57,12 @@ public class StereoAudioOutput extends AudioOutput
|
|||||||
|
|
||||||
private MixerChannel mMixerChannel;
|
private MixerChannel mMixerChannel;
|
||||||
|
|
||||||
public StereoAudioOutput( Mixer mixer, MixerChannel channel )
|
private ScheduledFuture<?> mProcessorTask;
|
||||||
|
|
||||||
|
public StereoAudioOutput( ThreadPoolManager threadPoolManager,
|
||||||
|
Mixer mixer, MixerChannel channel )
|
||||||
{
|
{
|
||||||
super();
|
super( threadPoolManager );
|
||||||
|
|
||||||
assert( channel == MixerChannel.LEFT || channel == MixerChannel.RIGHT );
|
assert( channel == MixerChannel.LEFT || channel == MixerChannel.RIGHT );
|
||||||
|
|
||||||
@@ -113,12 +117,10 @@ public class StereoAudioOutput extends AudioOutput
|
|||||||
mixer.getMixerInfo().getName() + " | " + channel.name() + "]" );
|
mixer.getMixerInfo().getName() + " | " + channel.name() + "]" );
|
||||||
}
|
}
|
||||||
|
|
||||||
mExecutorService = Executors.newSingleThreadScheduledExecutor(
|
|
||||||
new NamingThreadFactory( "audio (stereo) output" ) );
|
|
||||||
|
|
||||||
/* Run the queue processor task every 40 milliseconds */
|
/* Run the queue processor task every 40 milliseconds */
|
||||||
mExecutorService.scheduleAtFixedRate( new QueueProcessor(),
|
mProcessorTask = mThreadPoolManager.scheduleFixedRate(
|
||||||
0, 40, TimeUnit.MILLISECONDS );
|
ThreadType.AUDIO_PROCESSING, new QueueProcessor(),
|
||||||
|
40, TimeUnit.MILLISECONDS );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,12 +132,23 @@ public class StereoAudioOutput extends AudioOutput
|
|||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
|
if( mProcessorTask != null && mThreadPoolManager != null )
|
||||||
|
{
|
||||||
|
mThreadPoolManager.cancel( mProcessorTask );
|
||||||
|
}
|
||||||
|
|
||||||
|
mProcessorTask = null;
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
|
|
||||||
if( mOutput != null )
|
if( mOutput != null )
|
||||||
{
|
{
|
||||||
mOutput.close();
|
mOutput.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mOutput = null;
|
||||||
|
mGainControl = null;
|
||||||
|
mMuteControl = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ByteBuffer convert( float[] samples )
|
private ByteBuffer convert( float[] samples )
|
||||||
@@ -204,44 +217,10 @@ public class StereoAudioOutput extends AudioOutput
|
|||||||
ByteBuffer buffer = convert(
|
ByteBuffer buffer = convert(
|
||||||
packet.getAudioBuffer().getSamples() );
|
packet.getAudioBuffer().getSamples() );
|
||||||
|
|
||||||
if( mOutput.available() >= buffer.array().length )
|
|
||||||
{
|
|
||||||
mOutput.write( buffer.array(), 0,
|
mOutput.write( buffer.array(), 0,
|
||||||
buffer.array().length );
|
buffer.array().length );
|
||||||
|
|
||||||
checkStart();
|
checkStart();
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int wrote = 0;
|
|
||||||
|
|
||||||
int toWrite = buffer.array().length;
|
|
||||||
|
|
||||||
while( toWrite > 0 )
|
|
||||||
{
|
|
||||||
int available = mOutput.available();
|
|
||||||
|
|
||||||
if( available < toWrite )
|
|
||||||
{
|
|
||||||
if( available > 0 )
|
|
||||||
{
|
|
||||||
wrote += mOutput.write( buffer.array(),
|
|
||||||
wrote, available );
|
|
||||||
|
|
||||||
checkStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
wrote += mOutput.write( buffer.array(), wrote,
|
|
||||||
toWrite );
|
|
||||||
|
|
||||||
checkStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
toWrite = buffer.array().length - wrote;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
broadcast( packet.getAudioMetadata() );
|
broadcast( packet.getAudioMetadata() );
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ public class ThreadPoolManager
|
|||||||
private HashMap<ScheduledFuture<?>,ThreadType> mTasks =
|
private HashMap<ScheduledFuture<?>,ThreadType> mTasks =
|
||||||
new HashMap<ScheduledFuture<?>,ThreadType>();
|
new HashMap<ScheduledFuture<?>,ThreadType>();
|
||||||
|
|
||||||
private ScheduledExecutorService mExecutor;
|
private ScheduledExecutorService mExecutor =
|
||||||
|
Executors.newScheduledThreadPool( THREAD_POOL_SIZE,
|
||||||
|
new NamingThreadFactory( "sdrtrunk" ) );
|
||||||
|
|
||||||
public ThreadPoolManager()
|
public ThreadPoolManager()
|
||||||
{
|
{
|
||||||
@@ -48,12 +50,6 @@ public class ThreadPoolManager
|
|||||||
TimeUnit unit )
|
TimeUnit unit )
|
||||||
throws RejectedExecutionException
|
throws RejectedExecutionException
|
||||||
{
|
{
|
||||||
if( mExecutor == null )
|
|
||||||
{
|
|
||||||
mExecutor = Executors.newScheduledThreadPool( THREAD_POOL_SIZE,
|
|
||||||
new NamingThreadFactory( "sdrtrunk" ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
ScheduledFuture<?> task =
|
ScheduledFuture<?> task =
|
||||||
mExecutor.scheduleAtFixedRate( command, 0, period, unit );
|
mExecutor.scheduleAtFixedRate( command, 0, period, unit );
|
||||||
|
|
||||||
@@ -65,12 +61,6 @@ public class ThreadPoolManager
|
|||||||
public void scheduleOnce( Runnable command, long delay, TimeUnit unit )
|
public void scheduleOnce( Runnable command, long delay, TimeUnit unit )
|
||||||
throws RejectedExecutionException
|
throws RejectedExecutionException
|
||||||
{
|
{
|
||||||
if( mExecutor == null )
|
|
||||||
{
|
|
||||||
mExecutor = Executors.newScheduledThreadPool( THREAD_POOL_SIZE,
|
|
||||||
new NamingThreadFactory( "sdrtrunk" ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
mExecutor.schedule( command, delay, unit );
|
mExecutor.schedule( command, delay, unit );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user