Merge pull request #255 from DSheirer/249-overflow-teardown-lockup

249 Channel TEARDOWN/OVERFLOW state lockup
This commit is contained in:
Denny Sheirer
2017-07-22 05:44:12 -04:00
committed by GitHub
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.ISquelchStateProvider;
import audio.squelch.SquelchState; import audio.squelch.SquelchState;
import channel.heartbeat.Heartbeat;
import channel.heartbeat.IHeartbeatListener;
import channel.metadata.Attribute; import channel.metadata.Attribute;
import channel.metadata.AttributeChangeRequest; import channel.metadata.AttributeChangeRequest;
import channel.metadata.IAttributeChangeRequestListener; import channel.metadata.IAttributeChangeRequestListener;
@@ -39,16 +41,13 @@ import sample.real.IOverflowListener;
import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeEvent;
import source.tuner.frequency.IFrequencyChangeListener; import source.tuner.frequency.IFrequencyChangeListener;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService; 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; import static source.tuner.frequency.FrequencyChangeEvent.Event.NOTIFICATION_FREQUENCY_CHANGE;
public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener, public class ChannelState extends Module implements ICallEventProvider, IDecoderStateEventListener,
IDecoderStateEventProvider, IFrequencyChangeListener, ISquelchStateProvider, IAttributeChangeRequestListener, IDecoderStateEventProvider, IFrequencyChangeListener, IHeartbeatListener, ISquelchStateProvider,
IOverflowListener IAttributeChangeRequestListener, IOverflowListener
{ {
private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class); 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<DecoderStateEvent> mDecoderStateListener;
private Listener<SquelchState> mSquelchStateListener; private Listener<SquelchState> mSquelchStateListener;
private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver(); private DecoderStateEventReceiver mDecoderStateEventReceiver = new DecoderStateEventReceiver();
private StateMonitor mStateMonitor = new StateMonitor(); private HeartbeatReceiver mHeartbeatReceiver = new HeartbeatReceiver();
private ScheduledFuture<?> mStateMonitorFuture;
private ChannelType mChannelType; private ChannelType mChannelType;
private TrafficChannelManager mTrafficChannelEndListener; private TrafficChannelManager mTrafficChannelEndListener;
private CallEvent mTrafficChannelCallEvent; 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 * Channel state tracks the overall state of all processing modules and decoders configured for the channel and
* configured for the channel and provides squelch control and decoder state * provides squelch control and decoder state reset events.
* reset events.
* *
* Uses a state enumeration that defines allowable channel state transitions in * Uses a state enumeration that defines allowable channel state transitions in order to track a call or data decode
* order to track a call or data decode event from start to finish. Uses a * event from start to finish. Uses a timer to monitor for inactivity and to provide a FADE period that indicates
* 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
* to the user that the activity has stopped while continuing to provide details * reset to IDLE.
* 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.
* *
* State Descriptions: * State Descriptions:
* IDLE Normal state. No voice or data call activity * IDLE: Normal state. No voice or data call activity
* CALL/DATA/ENCRYPTED/CONTROL * CALL/DATA/ENCRYPTED/CONTROL: Decoding states.
* Decoding states. * FADE: The phase after a voice or data call when either an explicit call end has been received, or when no new
* FADE The phase after a voice or data call when either an explicit * signalling updates have been received, and the fade timer has expired. This phase allows for gui updates to
* call end has been received, or when no new signalling updates * signal to the user that the call is ended, while continuing to display the call details for the user
* have been received, and the fade timer has expired. This phase * TEARDOWN: Indicates a traffic channel that will be torn down for reuse.
* 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) public ChannelState(ChannelType channelType)
{ {
@@ -137,23 +126,11 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
@Override @Override
public void start(ScheduledExecutorService executor) 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)); setState(State.CALL);
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);
}
} }
} }
@@ -162,16 +139,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
{ {
mMutableMetadata.resetAllAttributes(); mMutableMetadata.resetAllAttributes();
if(mStateMonitorFuture != null)
{
boolean success = mStateMonitorFuture.cancel(true);
if(!success)
{
mLog.error("Couldn't stop monitoring scheduled future");
}
}
mTrafficChannelEndListener = null; mTrafficChannelEndListener = null;
if(mTrafficChannelCallEvent != null) if(mTrafficChannelCallEvent != null)
@@ -182,8 +149,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mTrafficChannelCallEvent = null; mTrafficChannelCallEvent = null;
mStateMonitorFuture = null;
mSquelchLocked = false; mSquelchLocked = false;
} }
@@ -192,7 +157,6 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder
mCallEventListener = null; mCallEventListener = null;
mDecoderStateListener = null; mDecoderStateListener = null;
mSquelchStateListener = null; mSquelchStateListener = null;
mStateMonitor = null;
} }
@Override @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 * Sets the channel state to the specified state, or updates the timeout values so that the state monitor will not
* values so that the state monitor will not change state. Broadcasts a * change state. Broadcasts a squelch event when the state changes and the audio squelch state should change. Also
* squelch event when the state changes and the audio squelch state should * broadcasts changed attribute and decoder state events so that external processes can maintain sync with this
* change. Also broadcasts changed attribute and decoder state events so * channel state.
* that external processes can maintain sync with this channel state.
*/ */
protected void setState(State 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 teardown
case CONTROL: if(isStandardChannel())
//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:
broadcast(SquelchState.SQUELCH); broadcast(SquelchState.SQUELCH);
updateFadeTimeout(); updateFadeTimeout();
mState = state; mState = state;
break; }
case CALL: break;
broadcast(SquelchState.UNSQUELCH); case DATA:
updateFadeTimeout(); case ENCRYPTED:
mState = state; broadcast(SquelchState.SQUELCH);
break; updateFadeTimeout();
case FADE: mState = state;
processFadeState(); break;
break; case CALL:
case IDLE: broadcast(SquelchState.UNSQUELCH);
processIdleState(); updateFadeTimeout();
break; mState = state;
case TEARDOWN: break;
processTeardownState(); case FADE:
break; processFadeState();
case RESET: break;
mState = State.IDLE; case IDLE:
break; processIdleState();
default: break;
break; case TEARDOWN:
} processTeardownState();
break;
mMutableMetadata.receive(new AttributeChangeRequest<State>(Attribute.CHANNEL_STATE, case RESET:
mSourceOverflow ? State.OVERFLOW : mState)); 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, * 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. * 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 * Processes periodic heartbeats received from the processing chain to perform state monitoring and cleanup
* state to IDLE (standard channel) or to TEARDOWN (traffic channel) when * functions.
* decoding stops or the monitored channel returns to a no signal state.
* *
* Provides a FADE transition state to allow for momentary decoding dropouts * Monitors decoder state events to automatically transition the channel state to IDLE (standard channel) or to
* and to allow the user access to call details for a fade period upon * TEARDOWN (traffic channel) when decoding stops or the monitored channel returns to a no signal state.
* call end. *
* 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 @Override
public void run() public void receive(Heartbeat heartbeat)
{ {
try try
{ {
synchronized(mState) if(State.CALL_STATES.contains(mState) && mFadeTimeout <= System.currentTimeMillis())
{ {
if(State.CALL_STATES.contains(mState) && processFadeState();
mFadeTimeout <= System.currentTimeMillis()) }
else if(mState == State.FADE && mEndTimeout <= System.currentTimeMillis())
{
if(isTrafficChannel())
{ {
processFadeState(); processTeardownState();
} }
else if(mState == State.FADE && else
mEndTimeout <= System.currentTimeMillis())
{ {
if(isTrafficChannel()) processIdleState();
{
processTeardownState();
}
else
{
processIdleState();
}
} }
} }
} }

View File

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

View File

@@ -1713,33 +1713,41 @@ public class P25DecoderState extends DecoderState
.build()); .build());
break; break;
case SNDCP_RF_CONFIRMED_DATA: case SNDCP_RF_CONFIRMED_DATA:
SNDCPUserData sud = (SNDCPUserData)pduc; if(pduc instanceof SNDCPUserData)
StringBuilder sbFrom = new StringBuilder();
StringBuilder sbTo = new StringBuilder();
sbFrom.append(sud.getSourceIPAddress());
sbTo.append(sud.getDestinationIPAddress());
if(sud.getIPProtocol() == IPProtocol.UDP)
{ {
sbFrom.append(":"); SNDCPUserData sud = (SNDCPUserData)pduc;
sbFrom.append(sud.getUDPSourcePort());
sbTo.append(":"); StringBuilder sbFrom = new StringBuilder();
sbTo.append(sud.getUDPDestinationPort()); 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; break;
case SNDCP_RF_UNCONFIRMED_DATA: case SNDCP_RF_UNCONFIRMED_DATA:
break; break;

View File

@@ -17,6 +17,7 @@
******************************************************************************/ ******************************************************************************/
package source; package source;
import channel.heartbeat.IHeartbeatProvider;
import module.Module; import module.Module;
import sample.SampleType; import sample.SampleType;
import sample.real.IOverflowListener; 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. * 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 SampleType mSampleType;
protected IOverflowListener mOverflowListener; protected IOverflowListener mOverflowListener;

View File

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

View File

@@ -18,6 +18,8 @@
******************************************************************************/ ******************************************************************************/
package source.tuner; package source.tuner;
import channel.heartbeat.Heartbeat;
import channel.heartbeat.IHeartbeatProvider;
import dsp.filter.FilterFactory; import dsp.filter.FilterFactory;
import dsp.filter.Window.WindowType; import dsp.filter.Window.WindowType;
import dsp.filter.cic.ComplexPrimeCICDecimate; 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_RATE = 48000;
private static int CHANNEL_PASS_FREQUENCY = 12000; private static int CHANNEL_PASS_FREQUENCY = 12000;
private static final Heartbeat HEARTBEAT = new Heartbeat();
private OverflowableTransferQueue<ComplexBuffer> mBuffer; private OverflowableTransferQueue<ComplexBuffer> mBuffer;
@@ -66,6 +69,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
private Oscillator mMixer; private Oscillator mMixer;
private ComplexPrimeCICDecimate mDecimationFilter; private ComplexPrimeCICDecimate mDecimationFilter;
private Listener<ComplexBuffer> mListener; private Listener<ComplexBuffer> mListener;
private Listener<Heartbeat> mHeartbeatListener;
private IFrequencyChangeProcessor mFrequencyChangeProcessor; private IFrequencyChangeProcessor mFrequencyChangeProcessor;
private DownstreamProcessor mDownstreamFrequencyEventProcessor = new DownstreamProcessor(); private DownstreamProcessor mDownstreamFrequencyEventProcessor = new DownstreamProcessor();
private ScheduledFuture<?> mTaskHandle; private ScheduledFuture<?> mTaskHandle;
@@ -352,6 +356,25 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang
return mDownstreamFrequencyEventProcessor.getFrequencyChangeListener(); 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 * 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 * 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) 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); mBuffer.drainTo(mSampleBuffers, 20);
for(Buffer buffer : mSampleBuffers) for(Buffer buffer : mSampleBuffers)

View File

@@ -18,19 +18,9 @@
******************************************************************************/ ******************************************************************************/
package source.wave; package source.wave;
import java.io.File; import channel.heartbeat.Heartbeat;
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 org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sample.ConversionUtils; import sample.ConversionUtils;
import sample.Listener; import sample.Listener;
import sample.complex.ComplexBuffer; import sample.complex.ComplexBuffer;
@@ -39,22 +29,31 @@ import source.IControllableFileSource;
import source.IFrameLocationListener; import source.IFrameLocationListener;
import source.tuner.frequency.FrequencyChangeEvent; 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 public class ComplexWaveSource extends ComplexSource implements IControllableFileSource
{ {
private final static Logger mLog = private final static Logger mLog =
LoggerFactory.getLogger( ComplexWaveSource.class ); LoggerFactory.getLogger(ComplexWaveSource.class);
private IFrameLocationListener mFrameLocationListener; private IFrameLocationListener mFrameLocationListener;
private int mBytesPerFrame; private int mBytesPerFrame;
private int mFrameCounter = 0; private int mFrameCounter = 0;
private long mFrequency = 0; private long mFrequency = 0;
private Listener<ComplexBuffer> mListener; private Listener<ComplexBuffer> mListener;
private AudioInputStream mInputStream; private AudioInputStream mInputStream;
private File mFile; private File mFile;
public ComplexWaveSource( File file ) throws IOException public ComplexWaveSource(File file) throws IOException
{ {
mFile = file; mFile = file;
} }
@Override @Override
@@ -76,72 +75,88 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
return null; return null;
} }
/**
* Not implemented
*/
@Override @Override
public void reset() public void setHeartbeatListener(Listener<Heartbeat> listener)
{
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 * Not implemented
* the value can be set with setFrequency() method. */
*/ @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() public long getFrequency()
{ {
return mFrequency; return mFrequency;
} }
/** /**
* Changes the value returned from getFrequency() for this source. * Changes the value returned from getFrequency() for this source.
*/ */
public void setFrequency( long frequency ) public void setFrequency(long frequency)
{ {
mFrequency = frequency; mFrequency = frequency;
} }
/** /**
@@ -149,16 +164,16 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
*/ */
public void close() throws IOException public void close() throws IOException
{ {
if( mInputStream != null ) if(mInputStream != null)
{ {
mInputStream.close(); mInputStream.close();
} }
else else
{ {
throw new IOException( "Can't close wave source - was not opened" ); throw new IOException("Can't close wave source - was not opened");
} }
mInputStream = null; mInputStream = null;
} }
/** /**
@@ -166,69 +181,69 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
*/ */
public void open() throws IOException, UnsupportedAudioFileException public void open() throws IOException, UnsupportedAudioFileException
{ {
if( mInputStream == null ) if(mInputStream == null)
{ {
mInputStream = AudioSystem.getAudioInputStream( mFile ); mInputStream = AudioSystem.getAudioInputStream(mFile);
} }
else else
{ {
throw new IOException( "Can't open wave source - is already opened" ); throw new IOException("Can't open wave source - is already opened");
} }
AudioFormat format = mInputStream.getFormat(); AudioFormat format = mInputStream.getFormat();
mBytesPerFrame = format.getFrameSize(); 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 " + throw new IOException("Unsupported Wave Format - EXPECTED: 2 " +
"channels 16-bit samples FOUND: " + "channels 16-bit samples FOUND: " +
mInputStream.getFormat().getChannels() + " channels " + mInputStream.getFormat().getChannels() + " channels " +
mInputStream.getFormat().getSampleSizeInBits() + "-bit samples" ); mInputStream.getFormat().getSampleSizeInBits() + "-bit samples");
} }
/* Broadcast that we're at frame location 0 */ /* Broadcast that we're at frame location 0 */
broadcast( 0 ); broadcast(0);
} }
/** /**
* Reads the number of frames and sends a buffer to the listener * Reads the number of frames and sends a buffer to the listener
*/ */
@Override @Override
public void next( int frames ) throws IOException 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
{ {
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 */ /* Fill the buffer with samples from the file */
int samplesRead = mInputStream.read( buffer ); int samplesRead = mInputStream.read(buffer);
mFrameCounter += samplesRead; mFrameCounter += samplesRead;
broadcast( mFrameCounter ); broadcast(mFrameCounter);
if( broadcast && mListener != null ) if(broadcast && mListener != null)
{ {
if( samplesRead < buffer.length ) if(samplesRead < buffer.length)
{ {
buffer = Arrays.copyOf( buffer, samplesRead ); buffer = Arrays.copyOf(buffer, samplesRead);
} }
float[] samples = ConversionUtils float[] samples = ConversionUtils
.convertFromSigned16BitSamples( buffer ); .convertFromSigned16BitSamples(buffer);
mListener.receive( new ComplexBuffer( samples ) ); mListener.receive(new ComplexBuffer(samples));
} }
} }
} }
@@ -236,51 +251,51 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil
* 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 * the wave file
*/ */
@Override @Override
public void setListener( Listener<ComplexBuffer> listener ) public void setListener(Listener<ComplexBuffer> listener)
{
mListener = listener;
}
/**
* Unregisters the listener from receiving sample buffers
*/
public void removeListener( 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() public void dispose()
{ {
mListener = null; mListener = null;
} }
@Override @Override
public File getFile() public File getFile()
{ {
return mFile; return mFile;
} }
private void broadcast( int byteLocation ) private void broadcast(int byteLocation)
{ {
int frameLocation = (int)( byteLocation / mBytesPerFrame ); int frameLocation = (int)(byteLocation / mBytesPerFrame);
if( mFrameLocationListener != null ) if(mFrameLocationListener != null)
{ {
mFrameLocationListener.frameLocationUpdated( frameLocation ); mFrameLocationListener.frameLocationUpdated(frameLocation);
} }
} }
@Override @Override
public void setListener( IFrameLocationListener listener ) public void setListener(IFrameLocationListener listener)
{ {
mFrameLocationListener = listener; mFrameLocationListener = listener;
} }
@Override @Override
public void removeListener( IFrameLocationListener listener ) public void removeListener(IFrameLocationListener listener)
{ {
mFrameLocationListener = null; mFrameLocationListener = null;
} }
} }

View File

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