Compare commits

...

12 Commits

Author SHA1 Message Date
Denny
c8994d5593 Updates build to version 0.3.0 Beta 13 2017-07-22 05:45:36 -04:00
Denny Sheirer
99ef57c90f Merge pull request #255 from DSheirer/249-overflow-teardown-lockup
249 Channel TEARDOWN/OVERFLOW state lockup
2017-07-22 05:44:12 -04:00
Denny
be62ae0459 Resolves #249 - removes the state monitor runnable from the channel
state and replaces it with a processing chain heartbeat that is generated
by the sample source.

Previously, the state monitor was running on a separate thread and this
was causing multi-threading issues.  With this update, all channel state
operations (and all module operations) occur on the source's buffer
processing thread.
2017-07-22 05:42:13 -04:00
Denny Sheirer
b9be6f43e0 Merge pull request #253 from DSheirer/252-usb-transfer-processor-bad-logging
252 USB transfer processor erroneous logging and performance enhancements
2017-07-09 12:29:21 +00:00
Denny
d5ad3f807d Resolves issue where the tuner's buffer processor is logging that an overflow state has been cleared, caused by a clearing of the buffer when the tuner is being shutdown because there are no more channel consumers registered on the tuner.
Tweaked buffer size limits and buffer handling to attempt to avoid overflow situations in the first place.

Resolved an issue where the tuner processor was not deregistering correctly from the LIBUSB tuner processor during tuner shutdown.
2017-07-09 08:21:07 -04:00
Denny Sheirer
d85ebd018d Merge pull request #251 from DSheirer/250-state-monitor-throwable
StateMonitor - try/catch for Throwables
2017-07-01 11:39:21 +00:00
Denny
ab4e31ae00 Ensures that any leftover cached LDU1 message is reset when the call ends. 2017-07-01 07:36:55 -04:00
Denny
5efd89632e Ensures that any leftover cached LDU1 message is reset when the call ends. 2017-05-28 07:30:05 -04:00
Denny
1dc3863489 Updates build to 0.3.0 Beta 12 2017-05-28 07:22:43 -04:00
Denny Sheirer
441286ba13 Merge pull request #247 from DSheirer/244-ldu1-encryption-flag
244 Prevent P25 Encrypted Audio Decoding/Playback
2017-05-28 11:20:20 +00:00
Denny
e4323df347 Resolves issue #244 - updates the P25 IMBE audio module to correctly
detect encrypted audio state so that encrypted audio is not (incorrectly)
decoded and played back to the user.

Processes HDU and LDU2 messages to identify encrypted call state.  Once
established, all subsequent LDU1/2 messages are decoded only if the call
is unencrypted.  If an LDU1 message is received out-of-sequence, meaning
that the initial HDU message was missed, then the LDU1 is cached until
we receive an LDU2 message and correctly determine the call encryption
state.
2017-05-28 07:17:09 -04:00
Denny
a872e0d664 Updates version to Beta 0.3.0 Beta 11 2017-05-20 07:24:27 -04:00
19 changed files with 1222 additions and 1006 deletions

View File

@@ -7,7 +7,7 @@
<property name="major" value="0"/> <property name="major" value="0"/>
<property name="minor" value="3"/> <property name="minor" value="3"/>
<property name="patch" value="0"/> <property name="patch" value="0"/>
<property name="release" value="-beta10"/> <property name="release" value="-beta13"/>
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/> <property name="filename" value="sdrtrunk_${major}.${minor}.${patch}${release}"/>
<!-- Build properties --> <!-- Build properties -->

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,45 +593,41 @@ 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();
}
} }
} }
} }
catch(Exception e) catch(Throwable e)
{ {
mLog.error("Exception thrown while state monitor is running " + mLog.error("An error occurred while state monitor was running " +
"- state [" + getState() + "- state [" + getState() +
"] current [" + System.currentTimeMillis() + "] current [" + System.currentTimeMillis() +
"] mResetTimeout [" + mEndTimeout + "] mResetTimeout [" + mEndTimeout +

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

@@ -421,7 +421,7 @@ public class P25DecoderState extends DecoderState
mCurrentCallEvent = new P25CallEvent.Builder(CallEventType.CALL) mCurrentCallEvent = new P25CallEvent.Builder(CallEventType.CALL)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(mCurrentChannel) .channel(mCurrentChannel)
.details((hdu.isEncrypted() ? "ENCRYPTED " : "")) .details((hdu.isEncryptedAudio() ? "ENCRYPTED " : ""))
.frequency(mCurrentChannelFrequency) .frequency(mCurrentChannelFrequency)
.to(to) .to(to)
.build(); .build();
@@ -1107,7 +1107,7 @@ public class P25DecoderState extends DecoderState
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(gvcu.getChannelA()) .channel(gvcu.getChannelA())
.details((gvcu.isEncrypted() ? "ENCRYPTED" : "")) .details((gvcu.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
.frequency(gvcu.getDownlinkFrequencyA()) .frequency(gvcu.getDownlinkFrequencyA())
.to(userA) .to(userA)
.build()); .build());
@@ -1122,7 +1122,7 @@ public class P25DecoderState extends DecoderState
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(gvcu.getChannelB()) .channel(gvcu.getChannelB())
.details((gvcu.isEncrypted() ? "ENCRYPTED" : "")) .details((gvcu.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
.frequency(gvcu.getDownlinkFrequencyB()) .frequency(gvcu.getDownlinkFrequencyB())
.to(userB) .to(userB)
.build()); .build());
@@ -1151,7 +1151,7 @@ public class P25DecoderState extends DecoderState
broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT) broadcast(new P25CallEvent.Builder(CallEventType.CALL_DETECT)
.aliasList(getAliasList()) .aliasList(getAliasList())
.channel(gvcue.getTransmitChannel()) .channel(gvcue.getTransmitChannel())
.details((gvcue.isEncrypted() ? "ENCRYPTED" : "")) .details((gvcue.isEncryptedLinkControlWord() ? "ENCRYPTED" : ""))
.frequency(gvcue.getDownlinkFrequency()) .frequency(gvcue.getDownlinkFrequency())
.to(group) .to(group)
.build()); .build());
@@ -1187,7 +1187,7 @@ public class P25DecoderState extends DecoderState
if(mCurrentCallEvent.getDetails() == null) if(mCurrentCallEvent.getDetails() == null)
{ {
mCurrentCallEvent.setDetails( mCurrentCallEvent.setDetails(
(gvcuser.isEncrypted() ? "ENCRYPTED " : "") + (gvcuser.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
(gvcuser.isEmergency() ? "EMERGENCY " : "")); (gvcuser.isEmergency() ? "EMERGENCY " : ""));
broadcast(mCurrentCallEvent); broadcast(mCurrentCallEvent);
@@ -1399,7 +1399,7 @@ public class P25DecoderState extends DecoderState
if(mCurrentCallEvent.getDetails() == null) if(mCurrentCallEvent.getDetails() == null)
{ {
mCurrentCallEvent.setDetails( mCurrentCallEvent.setDetails(
(tivcu.isEncrypted() ? "ENCRYPTED " : "") + (tivcu.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
(tivcu.isEmergency() ? "EMERGENCY " : "")); (tivcu.isEmergency() ? "EMERGENCY " : ""));
broadcast(mCurrentCallEvent); broadcast(mCurrentCallEvent);
@@ -1482,7 +1482,7 @@ public class P25DecoderState extends DecoderState
if(mCurrentCallEvent.getDetails() == null) if(mCurrentCallEvent.getDetails() == null)
{ {
mCurrentCallEvent.setDetails( mCurrentCallEvent.setDetails(
(uuvcu.isEncrypted() ? "ENCRYPTED " : "") + (uuvcu.isEncryptedLinkControlWord() ? "ENCRYPTED " : "") +
(uuvcu.isEmergency() ? "EMERGENCY " : "")); (uuvcu.isEmergency() ? "EMERGENCY " : ""));
broadcast(mCurrentCallEvent); broadcast(mCurrentCallEvent);
@@ -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

@@ -12,6 +12,9 @@ import jmbe.iface.AudioConverter;
import message.IMessageListener; import message.IMessageListener;
import message.Message; import message.Message;
import module.Module; import module.Module;
import module.decode.p25.message.hdu.HDUMessage;
import module.decode.p25.message.ldu.LDU1Message;
import module.decode.p25.message.ldu.LDU2Message;
import module.decode.p25.message.ldu.LDUMessage; import module.decode.p25.message.ldu.LDUMessage;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -25,14 +28,18 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
private final static Logger mLog = LoggerFactory.getLogger(P25AudioModule.class); private final static Logger mLog = LoggerFactory.getLogger(P25AudioModule.class);
private static final String IMBE_CODEC = "IMBE"; private static final String IMBE_CODEC = "IMBE";
private static boolean mLibraryLoadStatusLogged = false; private static boolean mLibraryLoadStatusLogged = false;
private boolean mCanConvertAudio = false; private boolean mCanConvertAudio = false;
private boolean mEncryptedCall = false;
private boolean mEncryptedCallStateEstablished = false;
private AudioConverter mAudioConverter; private AudioConverter mAudioConverter;
private Listener<AudioPacket> mAudioPacketListener; private Listener<AudioPacket> mAudioPacketListener;
private SquelchStateListener mSquelchStateListener = new SquelchStateListener(); private SquelchStateListener mSquelchStateListener = new SquelchStateListener();
private NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f); private NonClippingGain mGain = new NonClippingGain(5.0f, 0.95f);
private Metadata mMetadata; private Metadata mMetadata;
private LDU1Message mCachedLDU1Message = null;
public P25AudioModule(Metadata metadata) public P25AudioModule(Metadata metadata)
{ {
@@ -76,32 +83,73 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
} }
/** /**
* Primary processing method for p25 imbe audio frame messages. Each LDU * Processes call header (HDU) and voice frame (LDU1/LDU2) messages to decode audio and to determine the
* audio message contains 9 imbe voice frames. Each frame is converted to * encrypted audio status of a call event. Only the HDU and LDU2 messages convey encrypted call status. If an
* audio when the JMBE library is loaded, processed by auto-gain, and * LDU1 message is received without a preceding HDU message, then the LDU1 message is cached until the first
* broadcast to the audio packet listener. * LDU2 message is received and the encryption state can be determined. Both the LDU1 and the LDU2 message are
* then processed for audio if the call is unencrypted.
*/ */
public void receive(Message message) public void receive(Message message)
{ {
if (mCanConvertAudio && mAudioPacketListener != null) if(mCanConvertAudio && mAudioPacketListener != null)
{ {
if (message instanceof LDUMessage) if(mEncryptedCallStateEstablished)
{ {
LDUMessage ldu = (LDUMessage) message; if(message instanceof LDUMessage)
/* Only process unencrypted audio frames */
if (!(ldu.isValid() && ldu.isEncrypted()))
{ {
for (byte[] frame : ldu.getIMBEFrames()) processAudio((LDUMessage)message);
{
float[] audio = mAudioConverter.decode(frame);
audio = mGain.apply(audio);
mAudioPacketListener.receive(new AudioPacket(audio, mMetadata.copyOf()));
}
} }
} }
else
{
if(message instanceof HDUMessage)
{
mEncryptedCallStateEstablished = true;
mEncryptedCall = ((HDUMessage)message).isEncryptedAudio();
}
else if(message instanceof LDU1Message)
{
//When we receive an LDU1 message with first receiving the HDU message, cache the LDU1 Message
//until we can determine the encrypted call state from the next LDU2 message
mCachedLDU1Message = (LDU1Message)message;
}
else if(message instanceof LDU2Message)
{
mEncryptedCallStateEstablished = true;
LDU2Message ldu2 = (LDU2Message)message;
mEncryptedCall = ldu2.isEncryptedAudio();
if(mCachedLDU1Message != null)
{
processAudio(mCachedLDU1Message);
mCachedLDU1Message = null;
}
processAudio(ldu2);
}
}
}
}
/**
* Processes an audio packet by decoding the IMBE audio frames and rebroadcasting them as PCM audio packets.
*/
private void processAudio(LDUMessage ldu)
{
if(!mEncryptedCall)
{
for(byte[] frame : ldu.getIMBEFrames())
{
float[] audio = mAudioConverter.decode(frame);
audio = mGain.apply(audio);
mAudioPacketListener.receive(new AudioPacket(audio, mMetadata.copyOf()));
}
}
else
{
//Encrypted audio processing not implemented
} }
} }
@@ -119,18 +167,18 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
Class temp = Class.forName("jmbe.JMBEAudioLibrary"); Class temp = Class.forName("jmbe.JMBEAudioLibrary");
library = (AudioConversionLibrary) temp.newInstance(); library = (AudioConversionLibrary)temp.newInstance();
if ((library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 && if((library.getMajorVersion() == 0 && library.getMinorVersion() >= 3 &&
library.getBuildVersion() >= 3) || library.getMajorVersion() >= 1) library.getBuildVersion() >= 3) || library.getMajorVersion() >= 1)
{ {
mAudioConverter = library.getAudioConverter(IMBE_CODEC, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO); mAudioConverter = library.getAudioConverter(IMBE_CODEC, AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO);
if (mAudioConverter != null) if(mAudioConverter != null)
{ {
mCanConvertAudio = true; mCanConvertAudio = true;
if (!mLibraryLoadStatusLogged) if(!mLibraryLoadStatusLogged)
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("JMBE audio conversion library ["); sb.append("JMBE audio conversion library [");
@@ -144,7 +192,7 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
} }
else else
{ {
if (!mLibraryLoadStatusLogged) if(!mLibraryLoadStatusLogged)
{ {
mLog.info("JMBE audio conversion library NOT FOUND"); mLog.info("JMBE audio conversion library NOT FOUND");
mLibraryLoadStatusLogged = true; mLibraryLoadStatusLogged = true;
@@ -156,28 +204,28 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
mLog.warn("JMBE library version 0.3.3 or higher is required - found: " + library.getVersion()); mLog.warn("JMBE library version 0.3.3 or higher is required - found: " + library.getVersion());
} }
} }
catch (ClassNotFoundException e1) catch(ClassNotFoundException e1)
{ {
if (!mLibraryLoadStatusLogged) if(!mLibraryLoadStatusLogged)
{ {
mLog.error("Couldn't find/load JMBE audio conversion library"); mLog.error("Couldn't find/load JMBE audio conversion library");
mLibraryLoadStatusLogged = true; mLibraryLoadStatusLogged = true;
} }
} }
catch (InstantiationException e1) catch(InstantiationException e1)
{ {
if (!mLibraryLoadStatusLogged) if(!mLibraryLoadStatusLogged)
{ {
mLog.error("Couldn't instantiate JMBE audio conversion library class"); mLog.error("Couldn't instantiate JMBE audio conversion library class");
mLibraryLoadStatusLogged = true; mLibraryLoadStatusLogged = true;
} }
} }
catch (IllegalAccessException e1) catch(IllegalAccessException e1)
{ {
if (!mLibraryLoadStatusLogged) if(!mLibraryLoadStatusLogged)
{ {
mLog.error("Couldn't load JMBE audio conversion library due to " mLog.error("Couldn't load JMBE audio conversion library due to "
+ "security restrictions"); + "security restrictions");
mLibraryLoadStatusLogged = true; mLibraryLoadStatusLogged = true;
} }
} }
@@ -196,19 +244,25 @@ public class P25AudioModule extends Module implements Listener<Message>, IAudioP
} }
/** /**
* Wrapper for squelch state listener. Internally, the P25 audio module * Wrapper for squelch state to process end of call actions. At call end the encrypted call state established
* doesn't have a squelch state. If there are IMBE audio frames, we have * flag is reset so that the encrypted audio state for the next call can be properly detected and we send an
* audio. We use this listener to signal to the recorder manager that the * END audio packet so that downstream processors like the audio recorder can properly close out a call sequence.
* channel state is resetting and it should end the recording.
*/ */
public class SquelchStateListener implements Listener<SquelchState> public class SquelchStateListener implements Listener<SquelchState>
{ {
@Override @Override
public void receive(SquelchState state) public void receive(SquelchState state)
{ {
if (state == SquelchState.SQUELCH && mAudioPacketListener != null) if(state == SquelchState.SQUELCH)
{ {
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf())); if(mAudioPacketListener != null)
{
mAudioPacketListener.receive(new AudioPacket(AudioPacket.Type.END, mMetadata.copyOf()));
}
mEncryptedCallStateEstablished = false;
mEncryptedCall = false;
mCachedLDU1Message = null;
} }
} }
} }

View File

@@ -245,7 +245,7 @@ public class HDUMessage extends P25Message
return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) ); return Encryption.fromValue( mMessage.getInt( ALGORITHM_ID ) );
} }
public boolean isEncrypted() public boolean isEncryptedAudio()
{ {
return getEncryption() != Encryption.UNENCRYPTED; return getEncryption() != Encryption.UNENCRYPTED;
} }

View File

@@ -1,244 +1,246 @@
package module.decode.p25.message.ldu; package module.decode.p25.message.ldu;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Vendor;
import module.decode.p25.reference.VendorLinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import alias.AliasList; import alias.AliasList;
import bits.BinaryMessage; import bits.BinaryMessage;
import edac.CRC; import edac.CRC;
import edac.Hamming10; import edac.Hamming10;
import edac.ReedSolomon_63_47_17; import edac.ReedSolomon_63_47_17;
import module.decode.p25.reference.DataUnitID;
import module.decode.p25.reference.LinkControlOpcode;
import module.decode.p25.reference.Vendor;
import module.decode.p25.reference.VendorLinkControlOpcode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LDU1Message extends LDUMessage public class LDU1Message extends LDUMessage
{ {
private final static Logger mLog = LoggerFactory.getLogger( LDU1Message.class ); private final static Logger mLog = LoggerFactory.getLogger(LDU1Message.class);
public static final int ENCRYPTION_FLAG = 352; public static final int ENCRYPTED_LINK_CONTROL_WORD_FLAG = 352;
public static final int IMPLICIT_VENDOR_FLAG = 353; public static final int IMPLICIT_VENDOR_FLAG = 353;
public static final int[] OPCODE = { 354,355,356,357,362,363 }; public static final int[] OPCODE = {354, 355, 356, 357, 362, 363};
public static final int[] VENDOR = { 364,365,366,367,372,373,374,375 }; public static final int[] VENDOR = {364, 365, 366, 367, 372, 373, 374, 375};
public static final int[] GOLAY_WORD_STARTS = { public static final int[] GOLAY_WORD_STARTS = {
352,362,372,382,536,546,556,566,720,730,740,750,904,914,924,934,1088, 352, 362, 372, 382, 536, 546, 556, 566, 720, 730, 740, 750, 904, 914, 924, 934, 1088,
1098,1108,1118,1272,1282,1292,1302 }; 1098, 1108, 1118, 1272, 1282, 1292, 1302};
public static final int[] CW_HEX_0 = { 352,353,354,355,356,357 }; public static final int[] CW_HEX_0 = {352, 353, 354, 355, 356, 357};
public static final int[] CW_HEX_1 = { 362,363,364,365,366,367 }; public static final int[] CW_HEX_1 = {362, 363, 364, 365, 366, 367};
public static final int[] CW_HEX_2 = { 372,373,374,375,376,377 }; public static final int[] CW_HEX_2 = {372, 373, 374, 375, 376, 377};
public static final int[] CW_HEX_3 = { 382,383,384,385,386,387 }; public static final int[] CW_HEX_3 = {382, 383, 384, 385, 386, 387};
public static final int[] CW_HEX_4 = { 536,537,538,539,540,541 }; public static final int[] CW_HEX_4 = {536, 537, 538, 539, 540, 541};
public static final int[] CW_HEX_5 = { 546,547,548,549,550,551 }; public static final int[] CW_HEX_5 = {546, 547, 548, 549, 550, 551};
public static final int[] CW_HEX_6 = { 556,557,558,559,560,561 }; public static final int[] CW_HEX_6 = {556, 557, 558, 559, 560, 561};
public static final int[] CW_HEX_7 = { 566,567,568,569,570,571 }; public static final int[] CW_HEX_7 = {566, 567, 568, 569, 570, 571};
public static final int[] CW_HEX_8 = { 720,721,722,723,724,725 }; public static final int[] CW_HEX_8 = {720, 721, 722, 723, 724, 725};
public static final int[] CW_HEX_9 = { 730,731,732,733,734,735 }; public static final int[] CW_HEX_9 = {730, 731, 732, 733, 734, 735};
public static final int[] CW_HEX_10 = { 740,741,742,743,744,745 }; public static final int[] CW_HEX_10 = {740, 741, 742, 743, 744, 745};
public static final int[] CW_HEX_11 = { 750,751,752,753,754,755 }; public static final int[] CW_HEX_11 = {750, 751, 752, 753, 754, 755};
public static final int[] RS_HEX_0 = { 904,905,906,907,908,909 }; public static final int[] RS_HEX_0 = {904, 905, 906, 907, 908, 909};
public static final int[] RS_HEX_1 = { 914,915,916,917,918,919 }; public static final int[] RS_HEX_1 = {914, 915, 916, 917, 918, 919};
public static final int[] RS_HEX_2 = { 924,925,926,927,928,929 }; public static final int[] RS_HEX_2 = {924, 925, 926, 927, 928, 929};
public static final int[] RS_HEX_3 = { 934,935,936,937,938,939 }; public static final int[] RS_HEX_3 = {934, 935, 936, 937, 938, 939};
public static final int[] RS_HEX_4 = { 1088,1089,1090,1091,1092,1093 }; public static final int[] RS_HEX_4 = {1088, 1089, 1090, 1091, 1092, 1093};
public static final int[] RS_HEX_5 = { 1098,1099,1100,1101,1102,1103 }; public static final int[] RS_HEX_5 = {1098, 1099, 1100, 1101, 1102, 1103};
public static final int[] RS_HEX_6 = { 1108,1109,1110,1111,1112,1113 }; public static final int[] RS_HEX_6 = {1108, 1109, 1110, 1111, 1112, 1113};
public static final int[] RS_HEX_7 = { 1118,1119,1120,1121,1122,1123 }; public static final int[] RS_HEX_7 = {1118, 1119, 1120, 1121, 1122, 1123};
public static final int[] RS_HEX_8 = { 1272,1273,1274,1275,1276,1277 }; public static final int[] RS_HEX_8 = {1272, 1273, 1274, 1275, 1276, 1277};
public static final int[] RS_HEX_9 = { 1282,1283,1284,1285,1286,1287 }; public static final int[] RS_HEX_9 = {1282, 1283, 1284, 1285, 1286, 1287};
public static final int[] RS_HEX_10 = { 1292,1293,1294,1295,1296,1297 }; public static final int[] RS_HEX_10 = {1292, 1293, 1294, 1295, 1296, 1297};
public static final int[] RS_HEX_11 = { 1302,1303,1304,1305,1306,1307 }; public static final int[] RS_HEX_11 = {1302, 1303, 1304, 1305, 1306, 1307};
/* Reed-Solomon(24,12,13) code protects the link control word. Maximum /* Reed-Solomon(24,12,13) code protects the link control word. Maximum
* correctable errors are: Hamming Distance(13) / 2 = 6 */ * correctable errors are: Hamming Distance(13) / 2 = 6 */
public static final ReedSolomon_63_47_17 mReedSolomonDecoder = public static final ReedSolomon_63_47_17 mReedSolomonDecoder =
new ReedSolomon_63_47_17( 6 ); new ReedSolomon_63_47_17(6);
public LDU1Message( BinaryMessage message, DataUnitID duid, public LDU1Message(BinaryMessage message, DataUnitID duid,
AliasList aliasList ) AliasList aliasList)
{ {
super( message, duid, aliasList ); super(message, duid, aliasList);
/* NID CRC is checked in the message framer, thus a constructed message /* NID CRC is checked in the message framer, thus a constructed message
* means it passed the CRC */ * means it passed the CRC */
mCRC = new CRC[ 3 ]; mCRC = new CRC[3];
mCRC[ 0 ] = CRC.PASSED; mCRC[0] = CRC.PASSED;
checkCRC(); checkCRC();
} }
/** /**
* Subclass constructor from an existing LDU1Message * Subclass constructor from an existing LDU1Message
*/ */
protected LDU1Message( LDU1Message message ) protected LDU1Message(LDU1Message message)
{ {
super( message.getSourceMessage(), DataUnitID.LDU1, message.getAliasList() ); super(message.getSourceMessage(), DataUnitID.LDU1, message.getAliasList());
mCRC = message.mCRC; mCRC = message.mCRC;
} }
@Override /**
public boolean isEncrypted() * Indicates if the contents of the Link Control Word are encrypted.
{ *
return mMessage.get( ENCRYPTION_FLAG ); * @return true if the contents of the LCW are encrypted.
} */
public boolean isEncryptedLinkControlWord()
{
return mMessage.get(ENCRYPTED_LINK_CONTROL_WORD_FLAG);
}
private void checkCRC() private void checkCRC()
{ {
mCRC[ 1 ] = CRC.PASSED; mCRC[1] = CRC.PASSED;
/* Hamming( 10,6,3 ) error detection and correction */ /* Hamming( 10,6,3 ) error detection and correction */
for( int index: GOLAY_WORD_STARTS ) for(int index : GOLAY_WORD_STARTS)
{ {
int errors = Hamming10.checkAndCorrect( mMessage, index ); int errors = Hamming10.checkAndCorrect(mMessage, index);
if( errors > 1 ) if(errors > 1)
{ {
mCRC[ 1 ] = CRC.FAILED_CRC; mCRC[1] = CRC.FAILED_CRC;
} }
} }
/* Reed-Solomon( 24,16,9 ) error detection and correction /* Reed-Solomon( 24,16,9 ) error detection and correction
* Check the Reed-Solomon parity bits. The RS decoder expects the code * Check the Reed-Solomon parity bits. The RS decoder expects the code
* words and reed solomon parity hex codewords in reverse order. * words and reed solomon parity hex codewords in reverse order.
* *
* Since this is a truncated RS(63) codes, we pad the code with zeros */ * Since this is a truncated RS(63) codes, we pad the code with zeros */
int[] input = new int [63]; int[] input = new int[63];
int[] output = new int [63]; int[] output = new int[63];
input[ 0 ] = mMessage.getInt( RS_HEX_11 ); input[0] = mMessage.getInt(RS_HEX_11);
input[ 1 ] = mMessage.getInt( RS_HEX_10 ); input[1] = mMessage.getInt(RS_HEX_10);
input[ 2 ] = mMessage.getInt( RS_HEX_9 ); input[2] = mMessage.getInt(RS_HEX_9);
input[ 3 ] = mMessage.getInt( RS_HEX_8 ); input[3] = mMessage.getInt(RS_HEX_8);
input[ 4 ] = mMessage.getInt( RS_HEX_7 ); input[4] = mMessage.getInt(RS_HEX_7);
input[ 5 ] = mMessage.getInt( RS_HEX_6 ); input[5] = mMessage.getInt(RS_HEX_6);
input[ 6 ] = mMessage.getInt( RS_HEX_5 ); input[6] = mMessage.getInt(RS_HEX_5);
input[ 7 ] = mMessage.getInt( RS_HEX_4 ); input[7] = mMessage.getInt(RS_HEX_4);
input[ 8 ] = mMessage.getInt( RS_HEX_3 ); input[8] = mMessage.getInt(RS_HEX_3);
input[ 9 ] = mMessage.getInt( RS_HEX_2 ); input[9] = mMessage.getInt(RS_HEX_2);
input[ 10 ] = mMessage.getInt( RS_HEX_1 ); input[10] = mMessage.getInt(RS_HEX_1);
input[ 11 ] = mMessage.getInt( RS_HEX_0 ); input[11] = mMessage.getInt(RS_HEX_0);
input[ 12 ] = mMessage.getInt( CW_HEX_11 ); input[12] = mMessage.getInt(CW_HEX_11);
input[ 13 ] = mMessage.getInt( CW_HEX_10 ); input[13] = mMessage.getInt(CW_HEX_10);
input[ 14 ] = mMessage.getInt( CW_HEX_9 ); input[14] = mMessage.getInt(CW_HEX_9);
input[ 15 ] = mMessage.getInt( CW_HEX_8 ); input[15] = mMessage.getInt(CW_HEX_8);
input[ 16 ] = mMessage.getInt( CW_HEX_7 ); input[16] = mMessage.getInt(CW_HEX_7);
input[ 17 ] = mMessage.getInt( CW_HEX_6 ); input[17] = mMessage.getInt(CW_HEX_6);
input[ 18 ] = mMessage.getInt( CW_HEX_5 ); input[18] = mMessage.getInt(CW_HEX_5);
input[ 19 ] = mMessage.getInt( CW_HEX_4 ); input[19] = mMessage.getInt(CW_HEX_4);
input[ 20 ] = mMessage.getInt( CW_HEX_3 ); input[20] = mMessage.getInt(CW_HEX_3);
input[ 21 ] = mMessage.getInt( CW_HEX_2 ); input[21] = mMessage.getInt(CW_HEX_2);
input[ 22 ] = mMessage.getInt( CW_HEX_1 ); input[22] = mMessage.getInt(CW_HEX_1);
input[ 23 ] = mMessage.getInt( CW_HEX_0 ); input[23] = mMessage.getInt(CW_HEX_0);
/* indexes 24 - 62 are defaulted to zero */ /* indexes 24 - 62 are defaulted to zero */
boolean irrecoverableErrors = mReedSolomonDecoder.decode( input, output ); boolean irrecoverableErrors = mReedSolomonDecoder.decode(input, output);
if( irrecoverableErrors ) if(irrecoverableErrors)
{ {
mCRC[ 2 ] = CRC.FAILED_CRC; mCRC[2] = CRC.FAILED_CRC;
} }
else else
{ {
/* Since we've detected that we can correct the RS words, mark the /* Since we've detected that we can correct the RS words, mark the
* hamming crc as corrected as well, if it is currently failed */ * hamming crc as corrected as well, if it is currently failed */
if( mCRC[ 1 ] == CRC.FAILED_CRC ) if(mCRC[1] == CRC.FAILED_CRC)
{ {
mCRC[ 1 ] = CRC.CORRECTED; mCRC[1] = CRC.CORRECTED;
} }
mCRC[ 2 ] = CRC.PASSED; mCRC[2] = CRC.PASSED;
/* Only fix the codeword data hex codewords */ /* Only fix the codeword data hex codewords */
repairHexCodeword( input, output, 12, CW_HEX_11 ); repairHexCodeword(input, output, 12, CW_HEX_11);
repairHexCodeword( input, output, 13, CW_HEX_10 ); repairHexCodeword(input, output, 13, CW_HEX_10);
repairHexCodeword( input, output, 14, CW_HEX_9 ); repairHexCodeword(input, output, 14, CW_HEX_9);
repairHexCodeword( input, output, 15, CW_HEX_8 ); repairHexCodeword(input, output, 15, CW_HEX_8);
repairHexCodeword( input, output, 16, CW_HEX_7 ); repairHexCodeword(input, output, 16, CW_HEX_7);
repairHexCodeword( input, output, 17, CW_HEX_6 ); repairHexCodeword(input, output, 17, CW_HEX_6);
repairHexCodeword( input, output, 18, CW_HEX_5 ); repairHexCodeword(input, output, 18, CW_HEX_5);
repairHexCodeword( input, output, 19, CW_HEX_4 ); repairHexCodeword(input, output, 19, CW_HEX_4);
repairHexCodeword( input, output, 20, CW_HEX_3 ); repairHexCodeword(input, output, 20, CW_HEX_3);
repairHexCodeword( input, output, 21, CW_HEX_2 ); repairHexCodeword(input, output, 21, CW_HEX_2);
repairHexCodeword( input, output, 22, CW_HEX_1 ); repairHexCodeword(input, output, 22, CW_HEX_1);
repairHexCodeword( input, output, 23, CW_HEX_0 ); repairHexCodeword(input, output, 23, CW_HEX_0);
} }
} }
private void repairHexCodeword( int[] input, int[] output, int index, int[] indexSet ) private void repairHexCodeword(int[] input, int[] output, int index, int[] indexSet)
{ {
if( input[ index ] != output[ index ] ) if(input[index] != output[index])
{ {
mMessage.load( indexSet[ 0 ], 6, output[ index ] ); mMessage.load(indexSet[0], 6, output[index]);
mCRC[ 2 ] = CRC.CORRECTED; mCRC[2] = CRC.CORRECTED;
} }
} }
@Override @Override
public String getMessage() public String getMessage()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append( getMessageStub() ); sb.append(getMessageStub());
sb.append( " " ); sb.append(" ");
sb.append( mMessage.toString() ); sb.append(mMessage.toString());
return sb.toString(); return sb.toString();
} }
public String getMessageStub() public String getMessageStub()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append( super.getMessageStub() ); sb.append(super.getMessageStub());
if( isEncrypted() ) if(isEncryptedLinkControlWord())
{ {
sb.append( "ENCRYPTED" ); sb.append("ENCRYPTED");
} }
else else
{ {
if( isImplicitFormat() || getVendor() == Vendor.STANDARD ) if(isImplicitFormat() || getVendor() == Vendor.STANDARD)
{ {
sb.append( getOpcode().getLabel() ); sb.append(getOpcode().getLabel());
} }
else else
{ {
sb.append( "VENDOR:" + getVendor().getLabel() + " " ); sb.append("VENDOR:" + getVendor().getLabel() + " ");
sb.append( getVendorOpcode().getLabel() ); sb.append(getVendorOpcode().getLabel());
} }
} }
return sb.toString(); return sb.toString();
} }
public boolean isImplicitFormat() public boolean isImplicitFormat()
{ {
return mMessage.get( IMPLICIT_VENDOR_FLAG ); return mMessage.get(IMPLICIT_VENDOR_FLAG);
} }
public LinkControlOpcode getOpcode() public LinkControlOpcode getOpcode()
{ {
return LinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) ); return LinkControlOpcode.fromValue(mMessage.getInt(OPCODE));
} }
public VendorLinkControlOpcode getVendorOpcode() public VendorLinkControlOpcode getVendorOpcode()
{ {
return VendorLinkControlOpcode.fromValue( mMessage.getInt( OPCODE ) ); return VendorLinkControlOpcode.fromValue(mMessage.getInt(OPCODE));
} }
public Vendor getVendor() public Vendor getVendor()
{ {
if( isImplicitFormat() ) if(isImplicitFormat())
{ {
return Vendor.STANDARD; return Vendor.STANDARD;
} }
return Vendor.fromValue( mMessage.getInt( VENDOR ) ); return Vendor.fromValue(mMessage.getInt(VENDOR));
} }
} }

View File

@@ -73,8 +73,10 @@ public class LDU2Message extends LDUMessage
checkCRC(); checkCRC();
} }
@Override /**
public boolean isEncrypted() * Indicates if the audio stream is encrypted
*/
public boolean isEncryptedAudio()
{ {
Encryption encryption = getEncryption(); Encryption encryption = getEncryption();

View File

@@ -35,8 +35,6 @@ public abstract class LDUMessage extends P25Message
super( message, duid, aliasList ); super( message, duid, aliasList );
} }
public abstract boolean isEncrypted();
@Override @Override
public String getMessage() public String getMessage()
{ {

View File

@@ -124,10 +124,6 @@ public class OverflowableTransferQueue<E>
mQueue.clear(); mQueue.clear();
mCounter.set(0); mCounter.set(0);
mOverflow.set(false); mOverflow.set(false);
if(mOverflowListener != null)
{
mOverflowListener.sourceOverflow(false);
}
} }
} }
} }

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

@@ -54,7 +54,7 @@ public class USBTransferProcessor implements TransferCallback
private static final int TRANSFER_BUFFER_POOL_SIZE = 16; private static final int TRANSFER_BUFFER_POOL_SIZE = 16;
//Maximum number of filled buffers for the blocking queue //Maximum number of filled buffers for the blocking queue
private static final int FILLED_BUFFER_MAX_CAPACITY = 300; private static final int FILLED_BUFFER_MAX_CAPACITY = 500;
//Threshold for resetting buffer overflow condition //Threshold for resetting buffer overflow condition
private static final int FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD = 100; private static final int FILLED_BUFFER_OVERFLOW_RESET_THRESHOLD = 100;
@@ -200,7 +200,7 @@ public class USBTransferProcessor implements TransferCallback
//Unregister from LibUSB processor so that it auto-stops LibUSB processing //Unregister from LibUSB processor so that it auto-stops LibUSB processing
TunerManager.LIBUSB_TRANSFER_PROCESSOR.registerTransferProcessor(this); TunerManager.LIBUSB_TRANSFER_PROCESSOR.unregisterTransferProcessor(this);
//Directly invoke the timeout handler to ensure that our cancelled transfer buffers are flushed. //Directly invoke the timeout handler to ensure that our cancelled transfer buffers are flushed.
int result = LibUsb.handleEventsTimeoutCompleted(null, USB_TIMEOUT_MS, int result = LibUsb.handleEventsTimeoutCompleted(null, USB_TIMEOUT_MS,
@@ -415,23 +415,27 @@ public class USBTransferProcessor implements TransferCallback
@Override @Override
public void run() public void run()
{ {
try mFilledBuffers.drainTo(mBuffersToDispatch, 50);
{
mFilledBuffers.drainTo(mBuffersToDispatch, 15);
for(byte[] buffer : mBuffersToDispatch) while(!mBuffersToDispatch.isEmpty())
{
try
{ {
float[] complexSamples = mSampleAdapter.convert(buffer); for(byte[] buffer : mBuffersToDispatch)
{
float[] complexSamples = mSampleAdapter.convert(buffer);
mComplexBufferBroadcaster.broadcast(new ComplexBuffer(complexSamples)); mComplexBufferBroadcaster.broadcast(new ComplexBuffer(complexSamples));
}
}
catch(Exception e)
{
mLog.error(mDeviceName + " - error while dispatching complex IQ buffer samples", e);
} }
}
catch(Exception e)
{
mLog.error(mDeviceName + " - error while dispatching complex IQ buffer samples", e);
}
mBuffersToDispatch.clear(); mBuffersToDispatch.clear();
mFilledBuffers.drainTo(mBuffersToDispatch, 50);
}
} }
} }
} }

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;
} }
} }