diff --git a/src/channel/heartbeat/Heartbeat.java b/src/channel/heartbeat/Heartbeat.java new file mode 100644 index 00000000..4799754e --- /dev/null +++ b/src/channel/heartbeat/Heartbeat.java @@ -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 + * + ******************************************************************************/ +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() + { + } +} diff --git a/src/channel/heartbeat/IHeartbeatListener.java b/src/channel/heartbeat/IHeartbeatListener.java new file mode 100644 index 00000000..904d0501 --- /dev/null +++ b/src/channel/heartbeat/IHeartbeatListener.java @@ -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 + * + ******************************************************************************/ +package channel.heartbeat; + +import sample.Listener; + +public interface IHeartbeatListener +{ + public Listener getHeartbeatListener(); +} diff --git a/src/channel/heartbeat/IHeartbeatProvider.java b/src/channel/heartbeat/IHeartbeatProvider.java new file mode 100644 index 00000000..3583cd42 --- /dev/null +++ b/src/channel/heartbeat/IHeartbeatProvider.java @@ -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 + * + ******************************************************************************/ +package channel.heartbeat; + +import sample.Listener; + +public interface IHeartbeatProvider +{ + public void setHeartbeatListener(Listener listener); + + public void removeHeartbeatListener(); +} diff --git a/src/channel/state/ChannelState.java b/src/channel/state/ChannelState.java index 1f116dd4..30d7d796 100644 --- a/src/channel/state/ChannelState.java +++ b/src/channel/state/ChannelState.java @@ -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 mDecoderStateListener; private Listener 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(Attribute.CHANNEL_STATE, mState)); + + if(mTrafficChannelEndListener != null) { - mMutableMetadata.receive(new AttributeChangeRequest(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(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(Attribute.CHANNEL_STATE, + mSourceOverflow ? State.OVERFLOW : mState)); } } + /** + * Receiver inner class that implements the IHeartbeatListener interface to receive heartbeat messages. + */ + @Override + public Listener 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 { @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(); } } } diff --git a/src/module/ProcessingChain.java b/src/module/ProcessingChain.java index 7c0bf5cd..5eaa4cbb 100644 --- a/src/module/ProcessingChain.java +++ b/src/module/ProcessingChain.java @@ -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 mComplexBufferBroadcaster = new Broadcaster<>(); private Broadcaster mDecoderStateEventBroadcaster = new Broadcaster<>(); private Broadcaster mFrequencyChangeEventBroadcaster = new Broadcaster<>(); + private Broadcaster mHeartbeatBroadcaster = new Broadcaster<>(); private Broadcaster mMessageBroadcaster = new Broadcaster<>(); private Broadcaster mFilteredRealBufferBroadcaster = new Broadcaster<>(); private Broadcaster 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); diff --git a/src/module/decode/p25/P25DecoderState.java b/src/module/decode/p25/P25DecoderState.java index 8dc31d68..5072bc31 100644 --- a/src/module/decode/p25/P25DecoderState.java +++ b/src/module/decode/p25/P25DecoderState.java @@ -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; diff --git a/src/source/Source.java b/src/source/Source.java index 932c2749..9b253fa5 100644 --- a/src/source/Source.java +++ b/src/source/Source.java @@ -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; diff --git a/src/source/mixer/RealMixerSource.java b/src/source/mixer/RealMixerSource.java index cfc96997..7f0dfba2 100644 --- a/src/source/mixer/RealMixerSource.java +++ b/src/source/mixer/RealMixerSource.java @@ -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 ******************************************************************************/ 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> mSampleListeners = new CopyOnWriteArrayList>(); - - /** - * 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 mHeartbeatListener; + private static final Heartbeat HEARTBEAT = new Heartbeat(); + + private CopyOnWriteArrayList> mSampleListeners = new CopyOnWriteArrayList>(); + + /** + * 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 listener) - { - //Not implemented - } - - @Override - public void removeFrequencyChangeListener() - { - //Not implemented - } - - @Override - public Listener getFrequencyChangeListener() - { - //Not implemented - return null; - } - - @Override - public void reset() - { - } - - @Override - public void start( ScheduledExecutorService executor ) - { - } - - @Override - public void stop() - { - } - - public void setListener( Listener listener ) + @Override + public void setFrequencyChangeListener(Listener 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 listener ) + @Override + public void removeFrequencyChangeListener() { - mSampleListeners.remove( listener ); + //Not implemented + } + + @Override + public Listener getFrequencyChangeListener() + { + //Not implemented + return null; + } + + /** + * Registers the listener to receive heartbeats from this source. + * + * @param listener to receive heartbeats + */ + @Override + public void setHeartbeatListener(Listener 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 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 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> it = mSampleListeners.iterator(); - - while( it.hasNext() ) - { - Listener next = it.next(); + Iterator> it = mSampleListeners.iterator(); + + while(it.hasNext()) + { + Listener 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(); + } } } diff --git a/src/source/tuner/TunerChannelSource.java b/src/source/tuner/TunerChannelSource.java index 7556e7b5..ba7900b8 100644 --- a/src/source/tuner/TunerChannelSource.java +++ b/src/source/tuner/TunerChannelSource.java @@ -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 mBuffer; @@ -66,6 +69,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang private Oscillator mMixer; private ComplexPrimeCICDecimate mDecimationFilter; private Listener mListener; + private Listener 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 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) diff --git a/src/source/wave/ComplexWaveSource.java b/src/source/wave/ComplexWaveSource.java index 8d80ce1d..39e6ef26 100644 --- a/src/source/wave/ComplexWaveSource.java +++ b/src/source/wave/ComplexWaveSource.java @@ -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 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 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 listener ) - { - mListener = listener; - } - - /** - * Unregisters the listener from receiving sample buffers - */ - public void removeListener( Listener listener ) + @Override + public void setListener(Listener listener) { - mListener = null; + mListener = listener; } - @Override + /** + * Unregisters the listener from receiving sample buffers + */ + public void removeListener(Listener 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; + } } diff --git a/src/source/wave/RealWaveSource.java b/src/source/wave/RealWaveSource.java index 4b9b120b..a17efb49 100644 --- a/src/source/wave/RealWaveSource.java +++ b/src/source/wave/RealWaveSource.java @@ -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 ******************************************************************************/ 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 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 listener) - { - //Not implemented - } + @Override + public void setFrequencyChangeListener(Listener listener) + { + //Not implemented + } - @Override - public void removeFrequencyChangeListener() - { - //Not implemented - } + @Override + public void removeFrequencyChangeListener() + { + //Not implemented + } - @Override - public Listener getFrequencyChangeListener() - { - //Not implemented - return null; - } + @Override + public Listener getFrequencyChangeListener() + { + //Not implemented + return null; + } + + /** + * Not implemented + */ + @Override + public void setHeartbeatListener(Listener 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 listener ) - { - mListener = listener; - } - - /** - * Unregisters the listener from receiving sample buffers - */ - public void removeListener( Listener listener ) + @Override + public void setListener(Listener listener) { - mListener = null; + mListener = listener; } - @Override + /** + * Unregisters the listener from receiving sample buffers + */ + public void removeListener(Listener 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; + } }