Resolves #249 - removes the state monitor runnable from the channel

state and replaces it with a processing chain heartbeat that is generated
by the sample source.

Previously, the state monitor was running on a separate thread and this
was causing multi-threading issues.  With this update, all channel state
operations (and all module operations) occur on the source's buffer
processing thread.
This commit is contained in:
Denny
2017-07-22 05:42:13 -04:00
parent b9be6f43e0
commit be62ae0459
11 changed files with 892 additions and 732 deletions

View File

@@ -0,0 +1,31 @@
/*******************************************************************************
* 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 channel.heartbeat;
public class Heartbeat
{
/**
* Periodic pulse that is broadcast to subscribing modules within the demodulating chain so that
* monitoring of state and other attributes can occur on the primary demodulation and decoding
* thread.
*/
public Heartbeat()
{
}
}

View File

@@ -0,0 +1,26 @@
/*******************************************************************************
* 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 channel.heartbeat;
import sample.Listener;
public interface IHeartbeatListener
{
public Listener<Heartbeat> getHeartbeatListener();
}

View File

@@ -0,0 +1,28 @@
/*******************************************************************************
* 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 channel.heartbeat;
import sample.Listener;
public interface IHeartbeatProvider
{
public void setHeartbeatListener(Listener<Heartbeat> listener);
public void removeHeartbeatListener();
}

View File

@@ -20,6 +20,8 @@ package channel.state;
import audio.squelch.ISquelchStateProvider;
import audio.squelch.SquelchState;
import channel.heartbeat.Heartbeat;
import channel.heartbeat.IHeartbeatListener;
import channel.metadata.Attribute;
import channel.metadata.AttributeChangeRequest;
import channel.metadata.IAttributeChangeRequestListener;
@@ -39,16 +41,13 @@ import sample.real.IOverflowListener;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.IFrequencyChangeListener;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import static source.tuner.frequency.FrequencyChangeEvent.Event.NOTIFICATION_FREQUENCY_CHANGE;
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
IDecoderStateEventProvider, IFrequencyChangeListener, ISquelchStateProvider, IAttributeChangeRequestListener,
IOverflowListener
IDecoderStateEventProvider, IFrequencyChangeListener, IHeartbeatListener, ISquelchStateProvider,
IAttributeChangeRequestListener, IOverflowListener
{
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class);
@@ -61,8 +60,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
private Listener<DecoderStateEvent> mDecoderStateListener;
private Listener<SquelchState> mSquelchStateListener;
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
private StateMonitor mStateMonitor = new StateMonitor();
private ScheduledFuture<?> mStateMonitorFuture;
private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
private ChannelType mChannelType;
private TrafficChannelManager mTrafficChannelEndListener;
private CallEvent mTrafficChannelCallEvent;
@@ -79,30 +77,21 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
/**
* Channel state tracks the overall state of all processing modules and decoders
* configured for the channel and provides squelch control and decoder state
* reset events.
* Channel state tracks the overall state of all processing modules and decoders configured for the channel and
* provides squelch control and decoder state reset events.
*
* Uses a state enumeration that defines allowable channel state transitions in
* order to track a call or data decode event from start to finish. Uses a
* timer to monitor for inactivity and to provide a FADE period that indicates
* to the user that the activity has stopped while continuing to provide details
* about the call, before the state is reset to IDLE.
*
* Since this class is multi-threaded between decoder events and the internal
* timer that monitors for inactivity, this class uses the mState object for
* thread synchronization.
* Uses a state enumeration that defines allowable channel state transitions in order to track a call or data decode
* event from start to finish. Uses a timer to monitor for inactivity and to provide a FADE period that indicates
* to the user that the activity has stopped while continuing to provide details about the call, before the state is
* reset to IDLE.
*
* State Descriptions:
* IDLE Normal state. No voice or data call activity
* CALL/DATA/ENCRYPTED/CONTROL
* Decoding states.
* FADE The phase after a voice or data call when either an explicit
* call end has been received, or when no new signalling updates
* have been received, and the fade timer has expired. This phase
* allows for gui updates to signal to the user that the call is
* ended, while continuing to display the call details for the user
* TEARDOWN Indicates a traffic channel that will be torn down for reuse.
* IDLE: Normal state. No voice or data call activity
* CALL/DATA/ENCRYPTED/CONTROL: Decoding states.
* FADE: The phase after a voice or data call when either an explicit call end has been received, or when no new
* signalling updates have been received, and the fade timer has expired. This phase allows for gui updates to
* signal to the user that the call is ended, while continuing to display the call details for the user
* TEARDOWN: Indicates a traffic channel that will be torn down for reuse.
*/
public ChannelState(ChannelType channelType)
{
@@ -137,23 +126,11 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
@Override
public void start(ScheduledExecutorService executor)
{
if(mStateMonitorFuture == null && mStateMonitor != null)
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
if(mTrafficChannelEndListener != null)
{
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, mState));
if(mTrafficChannelEndListener != null)
{
setState(State.CALL);
}
try
{
mStateMonitorFuture = executor.scheduleAtFixedRate(mStateMonitor, 0, 20, TimeUnit.MILLISECONDS);
}
catch(RejectedExecutionException ree)
{
mLog.error("state monitor scheduled task rejected", ree);
}
setState(State.CALL);
}
}
@@ -162,16 +139,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
{
mMutableMetadata.resetAllAttributes();
if(mStateMonitorFuture != null)
{
boolean success = mStateMonitorFuture.cancel(true);
if(!success)
{
mLog.error("Couldn't stop monitoring scheduled future");
}
}
mTrafficChannelEndListener = null;
if(mTrafficChannelCallEvent != null)
@@ -182,8 +149,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mTrafficChannelCallEvent = null;
mStateMonitorFuture = null;
mSquelchLocked = false;
}
@@ -192,7 +157,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mCallEventListener = null;
mDecoderStateListener = null;
mSquelchStateListener = null;
mStateMonitor = null;
}
@Override
@@ -312,70 +276,74 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
}
/**
* Sets the channel state to the specified state, or updates the timeout
* values so that the state monitor will not change state. Broadcasts a
* squelch event when the state changes and the audio squelch state should
* change. Also broadcasts changed attribute and decoder state events so
* that external processes can maintain sync with this channel state.
* Sets the channel state to the specified state, or updates the timeout values so that the state monitor will not
* change state. Broadcasts a squelch event when the state changes and the audio squelch state should change. Also
* broadcasts changed attribute and decoder state events so that external processes can maintain sync with this
* channel state.
*/
protected void setState(State state)
{
synchronized(mState)
if(state == mState)
{
if(state == mState)
if(State.CALL_STATES.contains(state))
{
if(State.CALL_STATES.contains(state))
{
updateFadeTimeout();
}
updateFadeTimeout();
}
else if(state != mState && mState.canChangeTo(state))
}
else if(mState.canChangeTo(state))
{
switch(state)
{
switch(state)
{
case CONTROL:
//Don't allow traffic channels to be control channels,
//otherwise they can't transition to call tear down
if(isStandardChannel())
{
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
}
break;
case DATA:
case ENCRYPTED:
case CONTROL:
//Don't allow traffic channels to be control channels, otherwise they can't transition to teardown
if(isStandardChannel())
{
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
break;
case CALL:
broadcast(SquelchState.UNSQUELCH);
updateFadeTimeout();
mState = state;
break;
case FADE:
processFadeState();
break;
case IDLE:
processIdleState();
break;
case TEARDOWN:
processTeardownState();
break;
case RESET:
mState = State.IDLE;
break;
default:
break;
}
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
mSourceOverflow ? State.OVERFLOW : mState));
}
break;
case DATA:
case ENCRYPTED:
broadcast(SquelchState.SQUELCH);
updateFadeTimeout();
mState = state;
break;
case CALL:
broadcast(SquelchState.UNSQUELCH);
updateFadeTimeout();
mState = state;
break;
case FADE:
processFadeState();
break;
case IDLE:
processIdleState();
break;
case TEARDOWN:
processTeardownState();
break;
case RESET:
mState = State.IDLE;
break;
default:
break;
}
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE,
mSourceOverflow ? State.OVERFLOW : mState));
}
}
/**
* Receiver inner class that implements the IHeartbeatListener interface to receive heartbeat messages.
*/
@Override
public Listener<Heartbeat> getHeartbeatListener()
{
return mHeartbeatReceiver;
}
/**
* This method is invoked if the source buffer provider goes into overflow state. Since this is an external state,
* we use the mSourceOverflow variable to override the internal state reported to external listeners.
@@ -625,39 +593,35 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
}
/**
* Monitors decoder state events to automatically transition the channel
* state to IDLE (standard channel) or to TEARDOWN (traffic channel) when
* decoding stops or the monitored channel returns to a no signal state.
* Processes periodic heartbeats received from the processing chain to perform state monitoring and cleanup
* functions.
*
* Provides a FADE transition state to allow for momentary decoding dropouts
* and to allow the user access to call details for a fade period upon
* call end.
* Monitors decoder state events to automatically transition the channel state to IDLE (standard channel) or to
* TEARDOWN (traffic channel) when decoding stops or the monitored channel returns to a no signal state.
*
* Provides a FADE transition state to allow for momentary decoding dropouts and to allow the user access to call
* details for a fade period upon call end.
*/
public class StateMonitor implements Runnable
public class HeartbeatReceiver implements Listener<Heartbeat>
{
@Override
public void run()
public void receive(Heartbeat heartbeat)
{
try
{
synchronized(mState)
if(State.CALL_STATES.contains(mState) && mFadeTimeout <= System.currentTimeMillis())
{
if(State.CALL_STATES.contains(mState) &&
mFadeTimeout <= System.currentTimeMillis())
processFadeState();
}
else if(mState == State.FADE && mEndTimeout <= System.currentTimeMillis())
{
if(isTrafficChannel())
{
processFadeState();
processTeardownState();
}
else if(mState == State.FADE &&
mEndTimeout <= System.currentTimeMillis())
else
{
if(isTrafficChannel())
{
processTeardownState();
}
else
{
processIdleState();
}
processIdleState();
}
}
}

View File

@@ -24,16 +24,17 @@ import audio.IAudioPacketProvider;
import audio.squelch.ISquelchStateListener;
import audio.squelch.ISquelchStateProvider;
import audio.squelch.SquelchState;
import channel.heartbeat.Heartbeat;
import channel.heartbeat.IHeartbeatListener;
import channel.heartbeat.IHeartbeatProvider;
import channel.metadata.AttributeChangeRequest;
import channel.metadata.IAttributeChangeRequestListener;
import channel.metadata.IAttributeChangeRequestProvider;
import channel.state.ChannelState;
import channel.state.DecoderState;
import channel.state.DecoderStateEvent;
import channel.state.DecoderStateEvent.Event;
import channel.state.IDecoderStateEventListener;
import channel.state.IDecoderStateEventProvider;
import channel.state.State;
import controller.channel.Channel.ChannelType;
import controller.channel.ChannelEvent;
import controller.channel.IChannelEventListener;
@@ -63,8 +64,6 @@ import sample.real.RealBuffer;
import source.ComplexSource;
import source.RealSource;
import source.Source;
import source.SourceException;
import source.tuner.TunerChannelSource;
import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.IFrequencyChangeListener;
import source.tuner.frequency.IFrequencyChangeProvider;
@@ -103,6 +102,7 @@ public class ProcessingChain implements IChannelEventListener
private Broadcaster<ComplexBuffer> mComplexBufferBroadcaster = new Broadcaster<>();
private Broadcaster<DecoderStateEvent> mDecoderStateEventBroadcaster = new Broadcaster<>();
private Broadcaster<FrequencyChangeEvent> mFrequencyChangeEventBroadcaster = new Broadcaster<>();
private Broadcaster<Heartbeat> mHeartbeatBroadcaster = new Broadcaster<>();
private Broadcaster<Message> mMessageBroadcaster = new Broadcaster<>();
private Broadcaster<RealBuffer> mFilteredRealBufferBroadcaster = new Broadcaster<>();
private Broadcaster<RealBuffer> mUnFilteredRealBufferBroadcaster = new Broadcaster<>();
@@ -329,6 +329,11 @@ public class ProcessingChain implements IChannelEventListener
}
}
if(module instanceof IHeartbeatListener)
{
mHeartbeatBroadcaster.addListener(((IHeartbeatListener)module).getHeartbeatListener());
}
if(module instanceof IMessageListener)
{
mMessageBroadcaster.addListener(((IMessageListener) module).getMessageListener());
@@ -391,6 +396,11 @@ public class ProcessingChain implements IChannelEventListener
mFrequencyChangeEventBroadcaster.removeListener(((IFrequencyChangeListener) module).getFrequencyChangeListener());
}
if(module instanceof IHeartbeatListener)
{
mHeartbeatBroadcaster.removeListener(((IHeartbeatListener)module).getHeartbeatListener());
}
if(module instanceof IMessageListener)
{
mMessageBroadcaster.removeListener(((IMessageListener) module).getMessageListener());
@@ -448,6 +458,11 @@ public class ProcessingChain implements IChannelEventListener
((IFrequencyChangeProvider) module).setFrequencyChangeListener(mFrequencyChangeEventBroadcaster);
}
if(module instanceof IHeartbeatProvider)
{
((IHeartbeatProvider)module).setHeartbeatListener(mHeartbeatBroadcaster);
}
if(module instanceof IMessageProvider)
{
((IMessageProvider) module).setMessageListener(mMessageBroadcaster);
@@ -505,6 +520,11 @@ public class ProcessingChain implements IChannelEventListener
((IFrequencyChangeProvider) module).setFrequencyChangeListener(null);
}
if(module instanceof IHeartbeatProvider)
{
((IHeartbeatProvider)module).removeHeartbeatListener();
}
if(module instanceof IMessageProvider)
{
((IMessageProvider) module).setMessageListener(null);

View File

@@ -1713,33 +1713,41 @@ public class P25DecoderState extends DecoderState
.build());
break;
case SNDCP_RF_CONFIRMED_DATA:
SNDCPUserData sud = (SNDCPUserData)pduc;
StringBuilder sbFrom = new StringBuilder();
StringBuilder sbTo = new StringBuilder();
sbFrom.append(sud.getSourceIPAddress());
sbTo.append(sud.getDestinationIPAddress());
if(sud.getIPProtocol() == IPProtocol.UDP)
if(pduc instanceof SNDCPUserData)
{
sbFrom.append(":");
sbFrom.append(sud.getUDPSourcePort());
sbTo.append(":");
sbTo.append(sud.getUDPDestinationPort());
SNDCPUserData sud = (SNDCPUserData)pduc;
StringBuilder sbFrom = new StringBuilder();
StringBuilder sbTo = new StringBuilder();
sbFrom.append(sud.getSourceIPAddress());
sbTo.append(sud.getDestinationIPAddress());
if(sud.getIPProtocol() == IPProtocol.UDP)
{
sbFrom.append(":");
sbFrom.append(sud.getUDPSourcePort());
sbTo.append(":");
sbTo.append(sud.getUDPDestinationPort());
}
broadcast(new DecoderStateEvent(this, Event.START, State.DATA));
broadcast(new P25CallEvent.Builder(CallEventType.DATA_CALL)
.aliasList(getAliasList())
.channel(mCurrentChannel)
.details("DATA: " + sud.getPayload() +
" RADIO IP:" + sbTo.toString())
.frequency(mCurrentChannelFrequency)
.from(sbFrom.toString())
.to(pduc.getLogicalLinkID())
.build());
}
else
{
mLog.error("Error - expected SNDCPUserData instance but class was: " + pduc.getClass() +
" and PDU Type:" + pduc.getPDUType().name());
}
broadcast(new DecoderStateEvent(this, Event.START, State.DATA));
broadcast(new P25CallEvent.Builder(CallEventType.DATA_CALL)
.aliasList(getAliasList())
.channel(mCurrentChannel)
.details("DATA: " + sud.getPayload() +
" RADIO IP:" + sbTo.toString())
.frequency(mCurrentChannelFrequency)
.from(sbFrom.toString())
.to(pduc.getLogicalLinkID())
.build());
break;
case SNDCP_RF_UNCONFIRMED_DATA:
break;

View File

@@ -17,6 +17,7 @@
******************************************************************************/
package source;
import channel.heartbeat.IHeartbeatProvider;
import module.Module;
import sample.SampleType;
import sample.real.IOverflowListener;
@@ -26,7 +27,8 @@ import source.tuner.frequency.IFrequencyChangeProvider;
/**
* Abstract class to define the minimum functionality of a sample data provider.
*/
public abstract class Source extends Module implements IFrequencyChangeListener, IFrequencyChangeProvider
public abstract class Source extends Module implements IFrequencyChangeListener, IFrequencyChangeProvider,
IHeartbeatProvider
{
protected SampleType mSampleType;
protected IOverflowListener mOverflowListener;

View File

@@ -1,174 +1,191 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014 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 source.mixer;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
import channel.heartbeat.Heartbeat;
import dsp.gain.AutomaticGainControl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import sample.adapter.ISampleAdapter;
import sample.real.RealBuffer;
import source.RealSource;
import source.SourceException;
import dsp.gain.AutomaticGainControl;
import source.tuner.frequency.FrequencyChangeEvent;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.TargetDataLine;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
public class RealMixerSource extends RealSource
{
private final static Logger mLog = LoggerFactory.getLogger( RealMixerSource.class );
private final static Logger mLog = LoggerFactory.getLogger(RealMixerSource.class);
private long mFrequency = 0;
private int mBufferSize = 16384;
private BufferReader mBufferReader = new BufferReader();
private TargetDataLine mTargetDataLine;
private AudioFormat mAudioFormat;
private int mBytesPerFrame = 0;
private ISampleAdapter mSampleAdapter;
private AutomaticGainControl mAGC = new AutomaticGainControl();
CopyOnWriteArrayList<Listener<RealBuffer>> mSampleListeners = new CopyOnWriteArrayList<Listener<RealBuffer>>();
/**
* Real Mixer Source - constructs a reader on the mixer/sound card target
* data line using the specified audio format (sample size, sample rate )
* and broadcasts real buffers (float arrays) to all registered listeners.
* Reads buffers sized to 10% of the sample rate specified in audio format.
*
* @param targetDataLine - mixer or sound card to be used
*
* @param format - audio format
*
* @param name - token name to use for this source
*
* @param sampleAdapter - adapter to convert byte array data read from the
* mixer into float array data. Can optionally invert the channel data if
* the left/right stereo channels are inverted.
*/
public RealMixerSource( TargetDataLine targetDataLine,
AudioFormat format,
ISampleAdapter sampleAdapter )
private long mFrequency = 0;
private int mBufferSize = 16384;
private BufferReader mBufferReader = new BufferReader();
private TargetDataLine mTargetDataLine;
private AudioFormat mAudioFormat;
private int mBytesPerFrame = 0;
private ISampleAdapter mSampleAdapter;
private AutomaticGainControl mAGC = new AutomaticGainControl();
private Listener<Heartbeat> mHeartbeatListener;
private static final Heartbeat HEARTBEAT = new Heartbeat();
private CopyOnWriteArrayList<Listener<RealBuffer>> mSampleListeners = new CopyOnWriteArrayList<Listener<RealBuffer>>();
/**
* Real Mixer Source - constructs a reader on the mixer/sound card target
* data line using the specified audio format (sample size, sample rate )
* and broadcasts real buffers (float arrays) to all registered listeners.
* Reads buffers sized to 10% of the sample rate specified in audio format.
*
* @param targetDataLine - mixer or sound card to be used
* @param format - audio format
* @param sampleAdapter - adapter to convert byte array data read from the
* mixer into float array data. Can optionally invert the channel data if
* the left/right stereo channels are inverted.
*/
public RealMixerSource(TargetDataLine targetDataLine,
AudioFormat format,
ISampleAdapter sampleAdapter)
{
mTargetDataLine = targetDataLine;
mTargetDataLine = targetDataLine;
mAudioFormat = format;
mSampleAdapter = sampleAdapter;
}
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
//Not implemented
}
@Override
public void removeFrequencyChangeListener()
{
//Not implemented
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
//Not implemented
return null;
}
@Override
public void reset()
{
}
@Override
public void start( ScheduledExecutorService executor )
{
}
@Override
public void stop()
{
}
public void setListener( Listener<RealBuffer> listener )
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
mSampleListeners.add( listener );
/* If this is the first listener, start the reader thread */
if( !mBufferReader.isRunning() )
{
Thread thread = new Thread( mBufferReader );
thread.setDaemon( true );
thread.setName( "Sample Reader" );
thread.start();
}
//Not implemented
}
public void removeListener( Listener<RealBuffer> listener )
@Override
public void removeFrequencyChangeListener()
{
mSampleListeners.remove( listener );
//Not implemented
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
//Not implemented
return null;
}
/**
* Registers the listener to receive heartbeats from this source.
*
* @param listener to receive heartbeats
*/
@Override
public void setHeartbeatListener(Listener<Heartbeat> listener)
{
mHeartbeatListener = listener;
}
/**
* Removes the currently registered heartbeat listener
*/
@Override
public void removeHeartbeatListener()
{
mHeartbeatListener = null;
}
@Override
public void reset()
{
}
@Override
public void start(ScheduledExecutorService executor)
{
}
@Override
public void stop()
{
}
public void setListener(Listener<RealBuffer> listener)
{
mSampleListeners.add(listener);
/* If this is the first listener, start the reader thread */
if(!mBufferReader.isRunning())
{
Thread thread = new Thread(mBufferReader);
thread.setDaemon(true);
thread.setName("Sample Reader");
thread.start();
}
}
public void removeListener(Listener<RealBuffer> listener)
{
mSampleListeners.remove(listener);
/* If this is the laster listener, stop the reader thread */
if( mSampleListeners.isEmpty() )
{
mBufferReader.stop();
}
if(mSampleListeners.isEmpty())
{
mBufferReader.stop();
}
}
public void broadcast( RealBuffer samples )
public void broadcast(RealBuffer samples)
{
Iterator<Listener<RealBuffer>> it = mSampleListeners.iterator();
while( it.hasNext() )
{
Listener<RealBuffer> next = it.next();
Iterator<Listener<RealBuffer>> it = mSampleListeners.iterator();
while(it.hasNext())
{
Listener<RealBuffer> next = it.next();
/* if this is the last (or only) listener, send him the original
* buffer, otherwise send him a copy of the buffer */
if( it.hasNext() )
{
next.receive( samples.copyOf() );
}
else
{
next.receive( samples );
}
}
if(it.hasNext())
{
next.receive(samples.copyOf());
}
else
{
next.receive(samples);
}
}
}
public int getSampleRate()
{
if( mTargetDataLine != null )
{
return (int)mTargetDataLine.getFormat().getSampleRate();
}
else
{
return 0;
}
if(mTargetDataLine != null)
{
return (int)mTargetDataLine.getFormat().getSampleRate();
}
else
{
return 0;
}
}
/**
@@ -176,7 +193,7 @@ public class RealMixerSource extends RealSource
*/
public long getFrequency() throws SourceException
{
return mFrequency;
return mFrequency;
}
/**
@@ -184,122 +201,128 @@ public class RealMixerSource extends RealSource
* be useful if you are streaming an external audio source in through the
* sound card and you want to specify a frequency for that source
*/
public void setFrequency( long frequency )
public void setFrequency(long frequency)
{
mFrequency = frequency;
mFrequency = frequency;
}
/**
* Reader thread. Performs blocking read against the mixer target data
* line, converts the samples to an array of floats using the specified
* adapter. Dispatches float arrays to all registered listeners.
*/
public class BufferReader implements Runnable
{
private AtomicBoolean mRunning = new AtomicBoolean();
@Override
/**
* Reader thread. Performs blocking read against the mixer target data
* line, converts the samples to an array of floats using the specified
* adapter. Dispatches float arrays to all registered listeners.
*/
public class BufferReader implements Runnable
{
private AtomicBoolean mRunning = new AtomicBoolean();
@Override
public void run()
{
if( mRunning.compareAndSet( false, true ) )
{
if( mTargetDataLine == null )
{
mRunning.set( false );
if(mRunning.compareAndSet(false, true))
{
//Send a heartbeat every time we run
if(mHeartbeatListener != null)
{
mHeartbeatListener.receive(HEARTBEAT);
}
mLog.error( "ComplexMixerSource - mixer target data line"
+ " is null" );
}
else
{
mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8;
if(mTargetDataLine == null)
{
mRunning.set(false);
if( mBytesPerFrame == AudioSystem.NOT_SPECIFIED )
{
mBytesPerFrame = 2;
}
mLog.error("ComplexMixerSource - mixer target data line"
+ " is null");
}
else
{
mBytesPerFrame = mAudioFormat.getSampleSizeInBits() / 8;
if(mBytesPerFrame == AudioSystem.NOT_SPECIFIED)
{
mBytesPerFrame = 2;
}
/* Set buffer size to 1/10 second of samples */
mBufferSize = (int)( mAudioFormat.getSampleRate()
* 0.05 ) * mBytesPerFrame;
mBufferSize = (int)(mAudioFormat.getSampleRate()
* 0.05) * mBytesPerFrame;
/* We'll reuse the same buffer for each read */
byte[] buffer = new byte[ mBufferSize ];
try
{
mTargetDataLine.open( mAudioFormat );
byte[] buffer = new byte[mBufferSize];
mTargetDataLine.start();
}
catch ( LineUnavailableException e )
try
{
mLog.error( "ComplexMixerSource - mixer target data line"
+ "not available to read data from", e );
mRunning.set( false );
mTargetDataLine.open(mAudioFormat);
mTargetDataLine.start();
}
while( mRunning.get() )
{
try
catch(LineUnavailableException e)
{
mLog.error("ComplexMixerSource - mixer target data line"
+ "not available to read data from", e);
mRunning.set(false);
}
while(mRunning.get())
{
try
{
/* Blocking read - waits until the buffer fills */
mTargetDataLine.read( buffer, 0, buffer.length );
mTargetDataLine.read(buffer, 0, buffer.length);
/* Convert samples to float array */
float[] samples =
mSampleAdapter.convert( buffer );
float[] samples =
mSampleAdapter.convert(buffer);
/* Dispatch samples to registered listeners */
broadcast( new RealBuffer( samples ) );
broadcast(new RealBuffer(samples));
}
catch ( Exception e )
catch(Exception e)
{
mLog.error( "ComplexMixerSource - error while reading"
+ "from the mixer target data line", e );
mRunning.set( false );
mLog.error("ComplexMixerSource - error while reading"
+ "from the mixer target data line", e);
mRunning.set(false);
}
}
}
}
}
/* Close the data line if it is still open */
if( mTargetDataLine != null && mTargetDataLine.isOpen() )
{
mTargetDataLine.close();
}
}
if(mTargetDataLine != null && mTargetDataLine.isOpen())
{
mTargetDataLine.close();
}
}
}
/**
* Stops the reader thread
*/
public void stop()
{
if( mRunning.compareAndSet( true, false ) )
{
mTargetDataLine.stop();
mTargetDataLine.close();
}
}
/**
* Stops the reader thread
*/
public void stop()
{
if(mRunning.compareAndSet(true, false))
{
mTargetDataLine.stop();
mTargetDataLine.close();
}
}
/**
* Indicates if the reader thread is running
*/
public boolean isRunning()
{
return mRunning.get();
}
}
/**
* Indicates if the reader thread is running
*/
public boolean isRunning()
{
return mRunning.get();
}
}
@Override
@Override
public void dispose()
{
if( mBufferReader != null )
{
mBufferReader.stop();
mSampleListeners.clear();
}
if(mBufferReader != null)
{
mBufferReader.stop();
mSampleListeners.clear();
}
}
}

View File

@@ -18,6 +18,8 @@
******************************************************************************/
package source.tuner;
import channel.heartbeat.Heartbeat;
import channel.heartbeat.IHeartbeatProvider;
import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType;
import dsp.filter.cic.ComplexPrimeCICDecimate;
@@ -58,6 +60,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
private static int CHANNEL_RATE = 48000;
private static int CHANNEL_PASS_FREQUENCY = 12000;
private static final Heartbeat HEARTBEAT = new Heartbeat();
private OverflowableTransferQueue<ComplexBuffer> mBuffer;
@@ -66,6 +69,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
private Oscillator mMixer;
private ComplexPrimeCICDecimate mDecimationFilter;
private Listener<ComplexBuffer> mListener;
private Listener<Heartbeat> mHeartbeatListener;
private IFrequencyChangeProcessor mFrequencyChangeProcessor;
private DownstreamProcessor mDownstreamFrequencyEventProcessor = new DownstreamProcessor();
private ScheduledFuture<?> mTaskHandle;
@@ -352,6 +356,25 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
return mDownstreamFrequencyEventProcessor.getFrequencyChangeListener();
}
/**
* Registers the listener to receive heartbeats from this source.
* @param listener to receive heartbeats
*/
@Override
public void setHeartbeatListener(Listener<Heartbeat> listener)
{
mHeartbeatListener = listener;
}
/**
* Removes the currently registered heartbeat listener
*/
@Override
public void removeHeartbeatListener()
{
mHeartbeatListener = null;
}
/**
* Managers frequency change requests and notifications from/to any downstream component. Downstream
* components are those that receive samples from this tuner channel source. These downstream components will be
@@ -477,6 +500,13 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
{
if(mProcessing)
{
//Send a heartbeat every time this runs to allow downstream components to perform periodic
//state monitoring functions on this thread
if(mHeartbeatListener != null)
{
mHeartbeatListener.receive(HEARTBEAT);
}
mBuffer.drainTo(mSampleBuffers, 20);
for(Buffer buffer : mSampleBuffers)

View File

@@ -18,19 +18,9 @@
******************************************************************************/
package source.wave;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.ScheduledExecutorService;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import channel.heartbeat.Heartbeat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.ConversionUtils;
import sample.Listener;
import sample.complex.ComplexBuffer;
@@ -39,22 +29,31 @@ import source.IControllableFileSource;
import source.IFrameLocationListener;
import source.tuner.frequency.FrequencyChangeEvent;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.ScheduledExecutorService;
public class ComplexWaveSource extends ComplexSource implements IControllableFileSource
{
private final static Logger mLog =
LoggerFactory.getLogger( ComplexWaveSource.class );
private final static Logger mLog =
LoggerFactory.getLogger(ComplexWaveSource.class);
private IFrameLocationListener mFrameLocationListener;
private int mBytesPerFrame;
private int mFrameCounter = 0;
private IFrameLocationListener mFrameLocationListener;
private int mBytesPerFrame;
private int mFrameCounter = 0;
private long mFrequency = 0;
private Listener<ComplexBuffer> mListener;
private AudioInputStream mInputStream;
private File mFile;
public ComplexWaveSource( File file ) throws IOException
public ComplexWaveSource(File file) throws IOException
{
mFile = file;
mFile = file;
}
@Override
@@ -76,89 +75,105 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
return null;
}
/**
* Not implemented
*/
@Override
public void reset()
{
stop();
start( null );
}
@Override
public void start( ScheduledExecutorService executor )
{
try
{
open();
}
catch ( IOException | UnsupportedAudioFileException e )
{
mLog.error( "Error starting complex wave source" );
}
}
@Override
public void stop()
{
try
{
close();
}
catch ( IOException e )
{
mLog.error( "Error stopping complex wave source" );
}
}
@Override
public long getFrameCount() throws IOException
{
// TODO Auto-generated method stub
return 0;
}
@Override
public int getSampleRate()
public void setHeartbeatListener(Listener<Heartbeat> listener)
{
if( mInputStream != null )
{
return (int)mInputStream.getFormat().getSampleRate();
}
return 0;
}
/**
* Returns the frequency set for this file. Normally returns zero, but
* the value can be set with setFrequency() method.
*/
/**
* Not implemented
*/
@Override
public void removeHeartbeatListener()
{
}
@Override
public void reset()
{
stop();
start(null);
}
@Override
public void start(ScheduledExecutorService executor)
{
try
{
open();
}
catch(IOException | UnsupportedAudioFileException e)
{
mLog.error("Error starting complex wave source");
}
}
@Override
public void stop()
{
try
{
close();
}
catch(IOException e)
{
mLog.error("Error stopping complex wave source");
}
}
@Override
public long getFrameCount() throws IOException
{
// TODO Auto-generated method stub
return 0;
}
@Override
public int getSampleRate()
{
if(mInputStream != null)
{
return (int)mInputStream.getFormat().getSampleRate();
}
return 0;
}
/**
* Returns the frequency set for this file. Normally returns zero, but
* the value can be set with setFrequency() method.
*/
public long getFrequency()
{
return mFrequency;
return mFrequency;
}
/**
* Changes the value returned from getFrequency() for this source.
*/
public void setFrequency( long frequency )
public void setFrequency(long frequency)
{
mFrequency = frequency;
mFrequency = frequency;
}
/**
* Closes the source file
*/
public void close() throws IOException
{
if( mInputStream != null )
{
mInputStream.close();
}
else
{
throw new IOException( "Can't close wave source - was not opened" );
}
mInputStream = null;
if(mInputStream != null)
{
mInputStream.close();
}
else
{
throw new IOException("Can't close wave source - was not opened");
}
mInputStream = null;
}
/**
@@ -166,121 +181,121 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
*/
public void open() throws IOException, UnsupportedAudioFileException
{
if( mInputStream == null )
{
mInputStream = AudioSystem.getAudioInputStream( mFile );
}
else
{
throw new IOException( "Can't open wave source - is already opened" );
}
if(mInputStream == null)
{
mInputStream = AudioSystem.getAudioInputStream(mFile);
}
else
{
throw new IOException("Can't open wave source - is already opened");
}
AudioFormat format = mInputStream.getFormat();
mBytesPerFrame = format.getFrameSize();
if( format.getChannels() != 2 || format.getSampleSizeInBits() != 16 )
if(format.getChannels() != 2 || format.getSampleSizeInBits() != 16)
{
throw new IOException( "Unsupported Wave Format - EXPECTED: 2 " +
"channels 16-bit samples FOUND: " +
mInputStream.getFormat().getChannels() + " channels " +
mInputStream.getFormat().getSampleSizeInBits() + "-bit samples" );
throw new IOException("Unsupported Wave Format - EXPECTED: 2 " +
"channels 16-bit samples FOUND: " +
mInputStream.getFormat().getChannels() + " channels " +
mInputStream.getFormat().getSampleSizeInBits() + "-bit samples");
}
/* Broadcast that we're at frame location 0 */
broadcast( 0 );
broadcast(0);
}
/**
* Reads the number of frames and sends a buffer to the listener
*/
@Override
public void next( int frames ) throws IOException
{
next( frames, true );
}
/**
* Reads the number of frames and optionally sends the buffer to the listener
*/
public void next( int frames, boolean broadcast ) throws IOException
@Override
public void next(int frames) throws IOException
{
if( mInputStream != null )
next(frames, true);
}
/**
* Reads the number of frames and optionally sends the buffer to the listener
*/
public void next(int frames, boolean broadcast) throws IOException
{
if(mInputStream != null)
{
byte[] buffer = new byte[ mBytesPerFrame * frames ];
byte[] buffer = new byte[mBytesPerFrame * frames];
/* Fill the buffer with samples from the file */
int samplesRead = mInputStream.read( buffer );
int samplesRead = mInputStream.read(buffer);
mFrameCounter += samplesRead;
broadcast( mFrameCounter );
if( broadcast && mListener != null )
{
if( samplesRead < buffer.length )
{
buffer = Arrays.copyOf( buffer, samplesRead );
}
mFrameCounter += samplesRead;
float[] samples = ConversionUtils
.convertFromSigned16BitSamples( buffer );
mListener.receive( new ComplexBuffer( samples ) );
}
broadcast(mFrameCounter);
if(broadcast && mListener != null)
{
if(samplesRead < buffer.length)
{
buffer = Arrays.copyOf(buffer, samplesRead);
}
float[] samples = ConversionUtils
.convertFromSigned16BitSamples(buffer);
mListener.receive(new ComplexBuffer(samples));
}
}
}
/**
* Registers the listener to receive sample buffers as they are read from
* Registers the listener to receive sample buffers as they are read from
* the wave file
*/
@Override
public void setListener( Listener<ComplexBuffer> listener )
{
mListener = listener;
}
/**
* Unregisters the listener from receiving sample buffers
*/
public void removeListener( Listener<ComplexBuffer> listener )
@Override
public void setListener(Listener<ComplexBuffer> listener)
{
mListener = null;
mListener = listener;
}
@Override
/**
* Unregisters the listener from receiving sample buffers
*/
public void removeListener(Listener<ComplexBuffer> listener)
{
mListener = null;
}
@Override
public void dispose()
{
mListener = null;
mListener = null;
}
@Override
public File getFile()
{
return mFile;
}
@Override
public File getFile()
{
return mFile;
}
private void broadcast( int byteLocation )
{
int frameLocation = (int)( byteLocation / mBytesPerFrame );
if( mFrameLocationListener != null )
{
mFrameLocationListener.frameLocationUpdated( frameLocation );
}
}
@Override
public void setListener( IFrameLocationListener listener )
{
mFrameLocationListener = listener;
}
private void broadcast(int byteLocation)
{
int frameLocation = (int)(byteLocation / mBytesPerFrame);
@Override
public void removeListener( IFrameLocationListener listener )
{
mFrameLocationListener = null;
}
if(mFrameLocationListener != null)
{
mFrameLocationListener.frameLocationUpdated(frameLocation);
}
}
@Override
public void setListener(IFrameLocationListener listener)
{
mFrameLocationListener = listener;
}
@Override
public void removeListener(IFrameLocationListener listener)
{
mFrameLocationListener = null;
}
}

View File

@@ -1,35 +1,25 @@
/*******************************************************************************
* SDR Trunk
* Copyright (C) 2014,2015 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 source.wave;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.ScheduledExecutorService;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import channel.heartbeat.Heartbeat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.ConversionUtils;
import sample.Listener;
import sample.real.RealBuffer;
@@ -38,131 +28,154 @@ import source.IFrameLocationListener;
import source.RealSource;
import source.tuner.frequency.FrequencyChangeEvent;
public class RealWaveSource extends RealSource
implements IControllableFileSource, AutoCloseable
{
private final static Logger mLog =
LoggerFactory.getLogger( RealWaveSource.class );
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.ScheduledExecutorService;
private IFrameLocationListener mFrameLocationListener;
private int mBytesPerFrame;
private int mFrameCounter = 0;
public class RealWaveSource extends RealSource implements IControllableFileSource, AutoCloseable
{
private final static Logger mLog = LoggerFactory.getLogger(RealWaveSource.class);
private IFrameLocationListener mFrameLocationListener;
private int mBytesPerFrame;
private int mFrameCounter = 0;
private long mFrequency = 0;
private Listener<RealBuffer> mListener;
private AudioInputStream mInputStream;
private File mFile;
public RealWaveSource( File file ) throws IOException
public RealWaveSource(File file) throws IOException
{
mFile = file;
mFile = file;
}
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
//Not implemented
}
@Override
public void setFrequencyChangeListener(Listener<FrequencyChangeEvent> listener)
{
//Not implemented
}
@Override
public void removeFrequencyChangeListener()
{
//Not implemented
}
@Override
public void removeFrequencyChangeListener()
{
//Not implemented
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
//Not implemented
return null;
}
@Override
public Listener<FrequencyChangeEvent> getFrequencyChangeListener()
{
//Not implemented
return null;
}
/**
* Not implemented
*/
@Override
public void setHeartbeatListener(Listener<Heartbeat> listener)
{
}
/**
* Not implemented
*/
@Override
public void removeHeartbeatListener()
{
}
@Override
public void reset()
{
stop();
start( null );
}
@Override
public void reset()
{
stop();
start(null);
}
@Override
public void start( ScheduledExecutorService executor )
{
try
{
open();
}
catch ( IOException | UnsupportedAudioFileException e )
{
mLog.error( "Error starting real wave source", e );
}
}
@Override
public void start(ScheduledExecutorService executor)
{
try
{
open();
}
catch(IOException | UnsupportedAudioFileException e)
{
mLog.error("Error starting real wave source", e);
}
}
@Override
public void stop()
{
try
{
close();
}
catch ( IOException e )
{
mLog.error( "Error stopping real wave source", e );
}
}
@Override
public void stop()
{
try
{
close();
}
catch(IOException e)
{
mLog.error("Error stopping real wave source", e);
}
}
@Override
public long getFrameCount() throws IOException
{
// TODO Auto-generated method stub
return 0;
}
@Override
public long getFrameCount() throws IOException
{
// TODO Auto-generated method stub
return 0;
}
@Override
@Override
public int getSampleRate()
{
if( mInputStream != null )
{
return (int)mInputStream.getFormat().getSampleRate();
}
return 0;
if(mInputStream != null)
{
return (int)mInputStream.getFormat().getSampleRate();
}
return 0;
}
/**
* Returns the frequency set for this file. Normally returns zero, but
* the value can be set with setFrequency() method.
*/
/**
* Returns the frequency set for this file. Normally returns zero, but
* the value can be set with setFrequency() method.
*/
public long getFrequency()
{
return mFrequency;
return mFrequency;
}
/**
* Changes the value returned from getFrequency() for this source.
*/
public void setFrequency( long frequency )
public void setFrequency(long frequency)
{
mFrequency = frequency;
mFrequency = frequency;
}
/**
* Closes the source file
*/
public void close() throws IOException
{
if( mInputStream != null )
{
mInputStream.close();
}
else
{
throw new IOException( "Can't close wave source - was not opened" );
}
mInputStream = null;
if(mInputStream != null)
{
mInputStream.close();
}
else
{
throw new IOException("Can't close wave source - was not opened");
}
mInputStream = null;
}
/**
@@ -170,126 +183,126 @@ public class RealWaveSource extends RealSource
*/
public void open() throws IOException, UnsupportedAudioFileException
{
if( mInputStream == null )
{
mInputStream = AudioSystem.getAudioInputStream( mFile );
}
else
{
throw new IOException( "Can't open wave source - is already opened" );
}
if(mInputStream == null)
{
mInputStream = AudioSystem.getAudioInputStream(mFile);
}
else
{
throw new IOException("Can't open wave source - is already opened");
}
AudioFormat format = mInputStream.getFormat();
mBytesPerFrame = format.getFrameSize();
if( format.getChannels() != 1 || format.getSampleSizeInBits() != 16 )
if(format.getChannels() != 1 || format.getSampleSizeInBits() != 16)
{
throw new IOException( "Unsupported Wave Format - EXPECTED: 1 " +
"channel 16-bit samples FOUND: " +
mInputStream.getFormat().getChannels() + " channels " +
mInputStream.getFormat().getSampleSizeInBits() + "-bit samples" );
throw new IOException("Unsupported Wave Format - EXPECTED: 1 " +
"channel 16-bit samples FOUND: " +
mInputStream.getFormat().getChannels() + " channels " +
mInputStream.getFormat().getSampleSizeInBits() + "-bit samples");
}
/* Broadcast that we're at frame location 0 */
broadcast( 0 );
broadcast(0);
}
/**
* Reads the number of frames and sends a buffer to the listener
*/
@Override
public void next( int frames ) throws IOException
{
next( frames, true );
}
/**
* Reads the number of frames and optionally sends the buffer to the listener
*/
public void next( int frames, boolean broadcast ) throws IOException
@Override
public void next(int frames) throws IOException
{
if( mInputStream != null )
{
byte[] buffer = new byte[ mBytesPerFrame * frames ];
/* Fill the buffer with samples from the file */
int samplesRead = mInputStream.read( buffer );
mFrameCounter += samplesRead;
broadcast( mFrameCounter );
if( broadcast && mListener != null )
{
if( samplesRead < buffer.length )
{
if( samplesRead == -1 )
{
throw new IOException( "End of recording" );
}
buffer = Arrays.copyOf( buffer, samplesRead );
}
next(frames, true);
}
float[] samples = ConversionUtils
.convertFromSigned16BitSamples( buffer );
mListener.receive( new RealBuffer( samples ) );
}
/**
* Reads the number of frames and optionally sends the buffer to the listener
*/
public void next(int frames, boolean broadcast) throws IOException
{
if(mInputStream != null)
{
byte[] buffer = new byte[mBytesPerFrame * frames];
/* Fill the buffer with samples from the file */
int samplesRead = mInputStream.read(buffer);
mFrameCounter += samplesRead;
broadcast(mFrameCounter);
if(broadcast && mListener != null)
{
if(samplesRead < buffer.length)
{
if(samplesRead == -1)
{
throw new IOException("End of recording");
}
buffer = Arrays.copyOf(buffer, samplesRead);
}
float[] samples = ConversionUtils
.convertFromSigned16BitSamples(buffer);
mListener.receive(new RealBuffer(samples));
}
}
}
/**
* Registers the listener to receive sample buffers as they are read from
* Registers the listener to receive sample buffers as they are read from
* the wave file
*/
@Override
public void setListener( Listener<RealBuffer> listener )
{
mListener = listener;
}
/**
* Unregisters the listener from receiving sample buffers
*/
public void removeListener( Listener<RealBuffer> listener )
@Override
public void setListener(Listener<RealBuffer> listener)
{
mListener = null;
mListener = listener;
}
@Override
/**
* Unregisters the listener from receiving sample buffers
*/
public void removeListener(Listener<RealBuffer> listener)
{
mListener = null;
}
@Override
public void dispose()
{
mListener = null;
mListener = null;
}
@Override
public File getFile()
{
return mFile;
}
private void broadcast( int byteLocation )
{
int frameLocation = (int)( byteLocation / mBytesPerFrame );
if( mFrameLocationListener != null )
{
mFrameLocationListener.frameLocationUpdated( frameLocation );
}
}
@Override
public void setListener( IFrameLocationListener listener )
{
mFrameLocationListener = listener;
}
@Override
public File getFile()
{
return mFile;
}
@Override
public void removeListener( IFrameLocationListener listener )
{
mFrameLocationListener = null;
}
private void broadcast(int byteLocation)
{
int frameLocation = (int)(byteLocation / mBytesPerFrame);
if(mFrameLocationListener != null)
{
mFrameLocationListener.frameLocationUpdated(frameLocation);
}
}
@Override
public void setListener(IFrameLocationListener listener)
{
mFrameLocationListener = listener;
}
@Override
public void removeListener(IFrameLocationListener listener)
{
mFrameLocationListener = null;
}
}