mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 16:47:31 -05:00
#90 [WIP] merged metadata management for channel state, decoder states and audio metadata into a new metadata class. The new metadata class is maintained for each decoding audio channel, or in the future, a decoding TDMA channel. Audio playback, recording and streaming all use the same metadata object for the channel. The list of channel processing gui panels have been deleted and channel processing status will now reside in a table where each table row is a metadata instance from the processing channel. The channel processing manager is responsible for adding/removing the channel's metadata instance to/from the metadata table. This allows the channel processing table to be the controller for the messages, events and channel spectrum views, keyed to the selected table row metadata.
I still have to clean up the gui components and wire in the table selection listeners.
This commit is contained in:
@@ -1,29 +1,34 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.output;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import audio.AudioEvent;
|
||||
import audio.AudioPacket;
|
||||
import audio.AudioPacket.Type;
|
||||
import channel.metadata.Metadata;
|
||||
import controller.ThreadPoolManager;
|
||||
import controller.ThreadPoolManager.ThreadType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import source.mixer.MixerChannel;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.BooleanControl;
|
||||
@@ -35,418 +40,412 @@ import javax.sound.sampled.LineListener;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.Mixer;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import sample.Broadcaster;
|
||||
import sample.Listener;
|
||||
import source.mixer.MixerChannel;
|
||||
import audio.AudioEvent;
|
||||
import audio.AudioPacket;
|
||||
import audio.AudioPacket.Type;
|
||||
import audio.metadata.AudioMetadata;
|
||||
import controller.ThreadPoolManager;
|
||||
import controller.ThreadPoolManager.ThreadType;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public abstract class AudioOutput implements Listener<AudioPacket>, LineListener
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger( AudioOutput.class );
|
||||
|
||||
private LinkedTransferQueue<AudioPacket> mBuffer = new LinkedTransferQueue<>();
|
||||
private int mBufferStartThreshold;
|
||||
private int mBufferStopThreshold;
|
||||
private final static Logger mLog = LoggerFactory.getLogger(AudioOutput.class);
|
||||
|
||||
private Listener<AudioMetadata> mAudioMetadataListener;
|
||||
private Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
||||
private LinkedTransferQueue<AudioPacket> mBuffer = new LinkedTransferQueue<>();
|
||||
private int mBufferStartThreshold;
|
||||
private int mBufferStopThreshold;
|
||||
|
||||
private ThreadPoolManager mThreadPoolManager;
|
||||
private ScheduledFuture<?> mProcessorTask;
|
||||
private Listener<Metadata> mMetadataListener;
|
||||
private Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
||||
|
||||
private SourceDataLine mOutput;
|
||||
private Mixer mMixer;
|
||||
private MixerChannel mMixerChannel;
|
||||
private FloatControl mGainControl;
|
||||
private BooleanControl mMuteControl;
|
||||
private ThreadPoolManager mThreadPoolManager;
|
||||
private ScheduledFuture<?> mProcessorTask;
|
||||
|
||||
private AudioEvent mAudioStartEvent;
|
||||
private AudioEvent mAudioStopEvent;
|
||||
private SourceDataLine mOutput;
|
||||
private Mixer mMixer;
|
||||
private MixerChannel mMixerChannel;
|
||||
private FloatControl mGainControl;
|
||||
private BooleanControl mMuteControl;
|
||||
|
||||
private boolean mCanProcessAudio = false;
|
||||
private long mLastActivity = System.currentTimeMillis();
|
||||
private AudioEvent mAudioStartEvent;
|
||||
private AudioEvent mAudioStopEvent;
|
||||
|
||||
/**
|
||||
* Single audio channel playback with automatic starting and stopping of the
|
||||
* underlying sourcedataline specified by the mixer and mixer channel
|
||||
* arguments.
|
||||
*
|
||||
* Maintains an internal non-blocking audio packet queue and processes this
|
||||
* queue 25 times a second (every 40 ms).
|
||||
*
|
||||
* @param threadPoolManager for assigning buffer processing schedule task
|
||||
* @param mixer to obtain source data line
|
||||
* @param mixerChannel either mono or left/right stereo
|
||||
* @param audioFormat to use during playback
|
||||
* @param lineInfo to use when obtaining the source data line
|
||||
* @param requestedBufferSize of approximately 1 second of audio
|
||||
*/
|
||||
public AudioOutput( ThreadPoolManager threadPoolManager,
|
||||
Mixer mixer,
|
||||
MixerChannel mixerChannel,
|
||||
AudioFormat audioFormat,
|
||||
Line.Info lineInfo,
|
||||
int requestedBufferSize )
|
||||
{
|
||||
mThreadPoolManager = threadPoolManager;
|
||||
mMixer = mixer;
|
||||
mMixerChannel = mixerChannel;
|
||||
|
||||
try
|
||||
{
|
||||
mOutput = (SourceDataLine)mMixer.getLine( lineInfo );
|
||||
|
||||
if( mOutput != null )
|
||||
{
|
||||
mOutput.open( audioFormat, requestedBufferSize );
|
||||
|
||||
//Start threshold: buffer is full with 10% or less of capacity remaining
|
||||
mBufferStartThreshold = (int)( mOutput.getBufferSize() * 0.10 );
|
||||
private boolean mCanProcessAudio = false;
|
||||
private long mLastActivity = System.currentTimeMillis();
|
||||
|
||||
//Stop threshold: buffer is empty with 90% or more capacity available
|
||||
mBufferStopThreshold = (int)( mOutput.getBufferSize() * 0.90 );
|
||||
/**
|
||||
* Single audio channel playback with automatic starting and stopping of the
|
||||
* underlying sourcedataline specified by the mixer and mixer channel
|
||||
* arguments.
|
||||
*
|
||||
* Maintains an internal non-blocking audio packet queue and processes this
|
||||
* queue 25 times a second (every 40 ms).
|
||||
*
|
||||
* @param threadPoolManager for assigning buffer processing schedule task
|
||||
* @param mixer to obtain source data line
|
||||
* @param mixerChannel either mono or left/right stereo
|
||||
* @param audioFormat to use during playback
|
||||
* @param lineInfo to use when obtaining the source data line
|
||||
* @param requestedBufferSize of approximately 1 second of audio
|
||||
*/
|
||||
public AudioOutput(ThreadPoolManager threadPoolManager,
|
||||
Mixer mixer,
|
||||
MixerChannel mixerChannel,
|
||||
AudioFormat audioFormat,
|
||||
Line.Info lineInfo,
|
||||
int requestedBufferSize)
|
||||
{
|
||||
mThreadPoolManager = threadPoolManager;
|
||||
mMixer = mixer;
|
||||
mMixerChannel = mixerChannel;
|
||||
|
||||
try
|
||||
{
|
||||
mOutput = (SourceDataLine) mMixer.getLine(lineInfo);
|
||||
|
||||
if(mOutput != null)
|
||||
{
|
||||
mOutput.open(audioFormat, requestedBufferSize);
|
||||
|
||||
//Start threshold: buffer is full with 10% or less of capacity remaining
|
||||
mBufferStartThreshold = (int) (mOutput.getBufferSize() * 0.10);
|
||||
|
||||
//Stop threshold: buffer is empty with 90% or more capacity available
|
||||
mBufferStopThreshold = (int) (mOutput.getBufferSize() * 0.90);
|
||||
|
||||
mOutput.addLineListener(this);
|
||||
|
||||
if(mOutput != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Control gain = mOutput.getControl(FloatControl.Type.MASTER_GAIN);
|
||||
mGainControl = (FloatControl) gain;
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
mLog.warn("Couldn't obtain MASTER GAIN control for stereo line [" +
|
||||
mixer.getMixerInfo().getName() + " | " + getChannelName() + "]");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Control mute = mOutput.getControl(BooleanControl.Type.MUTE);
|
||||
mMuteControl = (BooleanControl) mute;
|
||||
}
|
||||
catch(IllegalArgumentException iae)
|
||||
{
|
||||
mLog.warn("Couldn't obtain MUTE control for stereo line [" +
|
||||
mixer.getMixerInfo().getName() + " | " + getChannelName() + "]");
|
||||
}
|
||||
|
||||
mOutput.addLineListener( this );
|
||||
|
||||
if( mOutput != null )
|
||||
{
|
||||
try
|
||||
{
|
||||
Control gain = mOutput.getControl( FloatControl.Type.MASTER_GAIN );
|
||||
mGainControl = (FloatControl)gain;
|
||||
}
|
||||
catch( IllegalArgumentException iae )
|
||||
{
|
||||
mLog.warn( "Couldn't obtain MASTER GAIN control for stereo line [" +
|
||||
mixer.getMixerInfo().getName() + " | " + getChannelName() + "]" );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Control mute = mOutput.getControl( BooleanControl.Type.MUTE );
|
||||
mMuteControl = (BooleanControl)mute;
|
||||
}
|
||||
catch( IllegalArgumentException iae )
|
||||
{
|
||||
mLog.warn( "Couldn't obtain MUTE control for stereo line [" +
|
||||
mixer.getMixerInfo().getName() + " | " + getChannelName() + "]" );
|
||||
}
|
||||
|
||||
/* Run the queue processor task every 40 milliseconds or 25 times a second */
|
||||
mProcessorTask = mThreadPoolManager.scheduleFixedRate(
|
||||
ThreadType.AUDIO_PROCESSING, new BufferProcessor(),
|
||||
40, TimeUnit.MILLISECONDS );
|
||||
}
|
||||
|
||||
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
|
||||
getChannelName() );
|
||||
mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
|
||||
getChannelName() );
|
||||
mProcessorTask = mThreadPoolManager.scheduleFixedRate(
|
||||
ThreadType.AUDIO_PROCESSING, new BufferProcessor(),
|
||||
40, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
mCanProcessAudio = true;
|
||||
}
|
||||
}
|
||||
catch ( LineUnavailableException e )
|
||||
{
|
||||
mLog.error( "Couldn't obtain audio source data line for "
|
||||
+ "audio output - mixer [" + mMixer.getMixerInfo().getName() + "]" );
|
||||
}
|
||||
}
|
||||
|
||||
public void reset()
|
||||
{
|
||||
broadcast( new AudioEvent( AudioEvent.Type.AUDIO_STOPPED, getChannelName() ) );
|
||||
}
|
||||
|
||||
public void dispose()
|
||||
{
|
||||
mCanProcessAudio = false;
|
||||
mAudioStartEvent = new AudioEvent(AudioEvent.Type.AUDIO_STARTED,
|
||||
getChannelName());
|
||||
mAudioStopEvent = new AudioEvent(AudioEvent.Type.AUDIO_STOPPED,
|
||||
getChannelName());
|
||||
|
||||
if( mProcessorTask != null && mThreadPoolManager != null )
|
||||
{
|
||||
mThreadPoolManager.cancel( mProcessorTask );
|
||||
}
|
||||
|
||||
mThreadPoolManager = null;
|
||||
mProcessorTask = null;
|
||||
mCanProcessAudio = true;
|
||||
}
|
||||
}
|
||||
catch(LineUnavailableException e)
|
||||
{
|
||||
mLog.error("Couldn't obtain audio source data line for "
|
||||
+ "audio output - mixer [" + mMixer.getMixerInfo().getName() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
mBuffer.clear();
|
||||
public void reset()
|
||||
{
|
||||
broadcast(new AudioEvent(AudioEvent.Type.AUDIO_STOPPED, getChannelName()));
|
||||
}
|
||||
|
||||
mAudioEventBroadcaster.dispose();
|
||||
mAudioEventBroadcaster = null;
|
||||
mAudioMetadataListener = null;
|
||||
|
||||
if( mOutput != null )
|
||||
{
|
||||
mOutput.close();
|
||||
}
|
||||
|
||||
mOutput = null;
|
||||
mGainControl = null;
|
||||
mMuteControl = null;
|
||||
}
|
||||
public void dispose()
|
||||
{
|
||||
mCanProcessAudio = false;
|
||||
|
||||
/**
|
||||
* Converts the audio packet data into a byte buffer format appropriate for
|
||||
* the underlying source data line.
|
||||
*/
|
||||
protected abstract ByteBuffer convert( AudioPacket packet );
|
||||
if(mProcessorTask != null && mThreadPoolManager != null)
|
||||
{
|
||||
mThreadPoolManager.cancel(mProcessorTask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Audio output channel name
|
||||
*/
|
||||
public String getChannelName()
|
||||
{
|
||||
return mMixerChannel.getLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixer Channel for this audio output
|
||||
*/
|
||||
protected MixerChannel getMixerChannel()
|
||||
{
|
||||
return mMixerChannel;
|
||||
}
|
||||
mThreadPoolManager = null;
|
||||
mProcessorTask = null;
|
||||
|
||||
/**
|
||||
* Registers a single listener to receive audio start and audio stop events
|
||||
*/
|
||||
public void addAudioEventListener( Listener<AudioEvent> listener )
|
||||
{
|
||||
mAudioEventBroadcaster.addListener( listener );
|
||||
}
|
||||
|
||||
public void removeAudioEventListener( Listener<AudioEvent> listener )
|
||||
{
|
||||
mAudioEventBroadcaster.removeListener( listener );
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts an audio event to the registered listener
|
||||
*/
|
||||
private void broadcast( AudioEvent audioEvent )
|
||||
{
|
||||
mAudioEventBroadcaster.broadcast( audioEvent );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a single listener to receive the audio metadata from each
|
||||
* audio packet
|
||||
*/
|
||||
public void setAudioMetadataListener( Listener<AudioMetadata> listener )
|
||||
{
|
||||
mAudioMetadataListener = listener;
|
||||
}
|
||||
|
||||
public void removeAudioMetadataListener()
|
||||
{
|
||||
mAudioMetadataListener = null;
|
||||
}
|
||||
mBuffer.clear();
|
||||
|
||||
/**
|
||||
* Broadcasts audio metadata to the registered listener
|
||||
*/
|
||||
private void broadcast( AudioMetadata metadata )
|
||||
{
|
||||
if( mAudioMetadataListener != null )
|
||||
{
|
||||
mAudioMetadataListener.receive( metadata );
|
||||
}
|
||||
}
|
||||
mAudioEventBroadcaster.dispose();
|
||||
mAudioEventBroadcaster = null;
|
||||
mMetadataListener = null;
|
||||
|
||||
/**
|
||||
* Timestamp of either last buffer received or last buffer processed
|
||||
*/
|
||||
public long getLastActivityTimestamp()
|
||||
{
|
||||
return mLastActivity;
|
||||
}
|
||||
if(mOutput != null)
|
||||
{
|
||||
mOutput.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last activity timestamp to current system time
|
||||
*/
|
||||
public void updateTimestamp()
|
||||
{
|
||||
mLastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive( AudioPacket packet )
|
||||
{
|
||||
if( mCanProcessAudio )
|
||||
{
|
||||
//Update the activity timestamp so that this audio output doesn't
|
||||
//get disconnected before it starts processing the audio stream
|
||||
updateTimestamp();
|
||||
|
||||
mBuffer.add( packet );
|
||||
}
|
||||
}
|
||||
|
||||
public class BufferProcessor implements Runnable
|
||||
{
|
||||
private AtomicBoolean mProcessing = new AtomicBoolean();
|
||||
|
||||
public BufferProcessor()
|
||||
{
|
||||
}
|
||||
mOutput = null;
|
||||
mGainControl = null;
|
||||
mMuteControl = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
/**
|
||||
* Converts the audio packet data into a byte buffer format appropriate for
|
||||
* the underlying source data line.
|
||||
*/
|
||||
protected abstract ByteBuffer convert(AudioPacket packet);
|
||||
|
||||
/**
|
||||
* Audio output channel name
|
||||
*/
|
||||
public String getChannelName()
|
||||
{
|
||||
return mMixerChannel.getLabel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixer Channel for this audio output
|
||||
*/
|
||||
protected MixerChannel getMixerChannel()
|
||||
{
|
||||
return mMixerChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a single listener to receive audio start and audio stop events
|
||||
*/
|
||||
public void addAudioEventListener(Listener<AudioEvent> listener)
|
||||
{
|
||||
mAudioEventBroadcaster.addListener(listener);
|
||||
}
|
||||
|
||||
public void removeAudioEventListener(Listener<AudioEvent> listener)
|
||||
{
|
||||
mAudioEventBroadcaster.removeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts an audio event to the registered listener
|
||||
*/
|
||||
private void broadcast(AudioEvent audioEvent)
|
||||
{
|
||||
mAudioEventBroadcaster.broadcast(audioEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a single listener to receive the audio metadata from each
|
||||
* audio packet
|
||||
*/
|
||||
public void setMetadataListener(Listener<Metadata> listener)
|
||||
{
|
||||
mMetadataListener = listener;
|
||||
}
|
||||
|
||||
public void removeAudioMetadataListener()
|
||||
{
|
||||
mMetadataListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts audio metadata to the registered listener
|
||||
*/
|
||||
private void broadcast(Metadata metadata)
|
||||
{
|
||||
if(mMetadataListener != null)
|
||||
{
|
||||
mMetadataListener.receive(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Timestamp of either last buffer received or last buffer processed
|
||||
*/
|
||||
public long getLastActivityTimestamp()
|
||||
{
|
||||
return mLastActivity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last activity timestamp to current system time
|
||||
*/
|
||||
public void updateTimestamp()
|
||||
{
|
||||
mLastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receive(AudioPacket packet)
|
||||
{
|
||||
if(mCanProcessAudio)
|
||||
{
|
||||
//Update the activity timestamp so that this audio output doesn't
|
||||
//get disconnected before it starts processing the audio stream
|
||||
updateTimestamp();
|
||||
|
||||
mBuffer.add(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public class BufferProcessor implements Runnable
|
||||
{
|
||||
private AtomicBoolean mProcessing = new AtomicBoolean();
|
||||
|
||||
public BufferProcessor()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
/* The processing flag ensures that only one instance of the
|
||||
* processor can run at any given time */
|
||||
if( mProcessing.compareAndSet( false, true ) )
|
||||
{
|
||||
List<AudioPacket> packets = new ArrayList<AudioPacket>();
|
||||
|
||||
mBuffer.drainTo( packets );
|
||||
|
||||
for( AudioPacket packet: packets )
|
||||
{
|
||||
if( packet.getType() == Type.AUDIO )
|
||||
{
|
||||
broadcast( packet.getAudioMetadata() );
|
||||
if(mProcessing.compareAndSet(false, true))
|
||||
{
|
||||
List<AudioPacket> packets = new ArrayList<AudioPacket>();
|
||||
|
||||
ByteBuffer buffer = convert( packet );
|
||||
|
||||
int wrote = 0;
|
||||
|
||||
if( !mOutput.isRunning() )
|
||||
{
|
||||
int toWrite = mOutput.available();
|
||||
|
||||
if( toWrite > buffer.array().length )
|
||||
{
|
||||
toWrite = buffer.array().length;
|
||||
}
|
||||
|
||||
//Top off the buffer and check if we can start it
|
||||
wrote += mOutput.write( buffer.array(), 0, toWrite );
|
||||
|
||||
checkStart();
|
||||
}
|
||||
mBuffer.drainTo(packets);
|
||||
|
||||
if( mOutput.isRunning() && wrote < buffer.array().length )
|
||||
{
|
||||
//Blocking write
|
||||
wrote += mOutput.write( buffer.array(), wrote,
|
||||
buffer.array().length - wrote );
|
||||
}
|
||||
|
||||
updateTimestamp();
|
||||
}
|
||||
}
|
||||
for(AudioPacket packet : packets)
|
||||
{
|
||||
if(packet.getType() == Type.AUDIO)
|
||||
{
|
||||
broadcast(packet.getMetadata());
|
||||
|
||||
checkStop();
|
||||
|
||||
mProcessing.set( false );
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
mLog.error( "Error while processing audio buffers", e );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts audio playback once audio buffer is almost full and remaining
|
||||
* capacity falls below the start threshold.
|
||||
*/
|
||||
private void checkStart()
|
||||
{
|
||||
if( mCanProcessAudio &&
|
||||
!mOutput.isRunning() &&
|
||||
mOutput.available() <= mBufferStartThreshold )
|
||||
{
|
||||
mOutput.start();
|
||||
}
|
||||
}
|
||||
ByteBuffer buffer = convert(packet);
|
||||
|
||||
/**
|
||||
* Stops audio playback and drains the audio buffer to empty when the
|
||||
* audio buffer is mostly empty and the available buffer capacity
|
||||
* exceeds the stop threshold
|
||||
*/
|
||||
private void checkStop()
|
||||
{
|
||||
if( mCanProcessAudio &&
|
||||
mOutput.isRunning() &&
|
||||
mOutput.available() >= mBufferStopThreshold )
|
||||
{
|
||||
mOutput.drain();
|
||||
mOutput.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mute state for this audio output channel
|
||||
*/
|
||||
public void setMuted( boolean muted )
|
||||
{
|
||||
if( mMuteControl != null )
|
||||
{
|
||||
mMuteControl.setValue( muted );
|
||||
|
||||
broadcast( new AudioEvent( muted ? AudioEvent.Type.AUDIO_MUTED :
|
||||
AudioEvent.Type.AUDIO_UNMUTED, getChannelName() ) );
|
||||
}
|
||||
}
|
||||
int wrote = 0;
|
||||
|
||||
/**
|
||||
* Current mute state for this audio output channel
|
||||
*/
|
||||
public boolean isMuted()
|
||||
{
|
||||
if( mMuteControl != null )
|
||||
{
|
||||
return mMuteControl.getValue();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
if(!mOutput.isRunning())
|
||||
{
|
||||
int toWrite = mOutput.available();
|
||||
|
||||
/**
|
||||
* Gain/volume control for this audio output channel, if one is available.
|
||||
*/
|
||||
public FloatControl getGainControl()
|
||||
{
|
||||
return mGainControl;
|
||||
}
|
||||
if(toWrite > buffer.array().length)
|
||||
{
|
||||
toWrite = buffer.array().length;
|
||||
}
|
||||
|
||||
public boolean hasGainControl()
|
||||
{
|
||||
return mGainControl != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors the source data line playback state and broadcasts audio events
|
||||
* to the registered listener as the state changes
|
||||
*/
|
||||
@Override
|
||||
public void update( LineEvent event )
|
||||
{
|
||||
LineEvent.Type type = event.getType();
|
||||
//Top off the buffer and check if we can start it
|
||||
wrote += mOutput.write(buffer.array(), 0, toWrite);
|
||||
|
||||
if( type == LineEvent.Type.START )
|
||||
{
|
||||
mAudioEventBroadcaster.broadcast( mAudioStartEvent );
|
||||
}
|
||||
else if( type == LineEvent.Type.STOP )
|
||||
{
|
||||
mAudioEventBroadcaster.broadcast( mAudioStopEvent );
|
||||
}
|
||||
}
|
||||
checkStart();
|
||||
}
|
||||
|
||||
if(mOutput.isRunning() && wrote < buffer.array().length)
|
||||
{
|
||||
//Blocking write
|
||||
wrote += mOutput.write(buffer.array(), wrote,
|
||||
buffer.array().length - wrote);
|
||||
}
|
||||
|
||||
updateTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
checkStop();
|
||||
|
||||
mProcessing.set(false);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error while processing audio buffers", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts audio playback once audio buffer is almost full and remaining
|
||||
* capacity falls below the start threshold.
|
||||
*/
|
||||
private void checkStart()
|
||||
{
|
||||
if(mCanProcessAudio &&
|
||||
!mOutput.isRunning() &&
|
||||
mOutput.available() <= mBufferStartThreshold)
|
||||
{
|
||||
mOutput.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops audio playback and drains the audio buffer to empty when the
|
||||
* audio buffer is mostly empty and the available buffer capacity
|
||||
* exceeds the stop threshold
|
||||
*/
|
||||
private void checkStop()
|
||||
{
|
||||
if(mCanProcessAudio &&
|
||||
mOutput.isRunning() &&
|
||||
mOutput.available() >= mBufferStopThreshold)
|
||||
{
|
||||
mOutput.drain();
|
||||
mOutput.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the mute state for this audio output channel
|
||||
*/
|
||||
public void setMuted(boolean muted)
|
||||
{
|
||||
if(mMuteControl != null)
|
||||
{
|
||||
mMuteControl.setValue(muted);
|
||||
|
||||
broadcast(new AudioEvent(muted ? AudioEvent.Type.AUDIO_MUTED :
|
||||
AudioEvent.Type.AUDIO_UNMUTED, getChannelName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current mute state for this audio output channel
|
||||
*/
|
||||
public boolean isMuted()
|
||||
{
|
||||
if(mMuteControl != null)
|
||||
{
|
||||
return mMuteControl.getValue();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gain/volume control for this audio output channel, if one is available.
|
||||
*/
|
||||
public FloatControl getGainControl()
|
||||
{
|
||||
return mGainControl;
|
||||
}
|
||||
|
||||
public boolean hasGainControl()
|
||||
{
|
||||
return mGainControl != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitors the source data line playback state and broadcasts audio events
|
||||
* to the registered listener as the state changes
|
||||
*/
|
||||
@Override
|
||||
public void update(LineEvent event)
|
||||
{
|
||||
LineEvent.Type type = event.getType();
|
||||
|
||||
if(type == LineEvent.Type.START)
|
||||
{
|
||||
mAudioEventBroadcaster.broadcast(mAudioStartEvent);
|
||||
}
|
||||
else if(type == LineEvent.Type.STOP)
|
||||
{
|
||||
mAudioEventBroadcaster.broadcast(mAudioStopEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user