mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 09:07:27 -05:00
Compare commits
34 Commits
v0.2.0-bet
...
v0.2.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
daf2b58cf2 | ||
|
|
cf2c0ca07d | ||
|
|
eead22ffad | ||
|
|
1550c62782 | ||
|
|
5f2d8c8fe6 | ||
|
|
22d3871d0b | ||
|
|
455e45269c | ||
|
|
ca8406bdc0 | ||
|
|
399d164bd0 | ||
|
|
6f2a7e89a9 | ||
|
|
b821588e54 | ||
|
|
62aca27e66 | ||
|
|
cdcc487095 | ||
|
|
d5b6589e36 | ||
|
|
be7ab871d1 | ||
|
|
294a830aaf | ||
|
|
a7da60cd27 | ||
|
|
894ebeb040 | ||
|
|
682fe0c779 | ||
|
|
88d3088ebc | ||
|
|
dc3390460a | ||
|
|
8db226cd7b | ||
|
|
b7a54598b1 | ||
|
|
dd22397f75 | ||
|
|
d8a20b5adb | ||
|
|
c670f1d855 | ||
|
|
1dc0125ef4 | ||
|
|
225e747d6e | ||
|
|
36f9953156 | ||
|
|
47c452e7e6 | ||
|
|
06c8bba97e | ||
|
|
a25bb83dea | ||
|
|
8e11cb4554 | ||
|
|
2892e4609e |
30
README.md
30
README.md
@@ -1,18 +1,22 @@
|
|||||||
# sdrtrunk
|
# sdrtrunk #
|
||||||
A cross-platform java application for decoding, monitoring, recording and streaming trunked mobile and related radio protocols using Software Defined Radios (SDR).
|
A cross-platform java application for decoding, monitoring and recording trunked
|
||||||
|
mobile and related radio protocols using Software Defined Radios (SDR).
|
||||||
|
|
||||||
|
# Documentation #
|
||||||
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted)
|
* [Getting Started](https://github.com/DSheirer/sdrtrunk/wiki/GettingStarted)
|
||||||
* [User's Manual](https://github.com/DSheirer/sdrtrunk/wiki/UserManual)
|
* [User's Manual (Version 0.2.0)](https://github.com/DSheirer/sdrtrunk/wiki/UserManual)
|
||||||
|
* [Wiki](https://github.com/DSheirer/sdrtrunk/wiki)
|
||||||
|
|
||||||
|
# Support Group and Error Reporting #
|
||||||
|
You can get help with setting up the sdrtrunk application or report any errors
|
||||||
|
that you encounter while running the application in the sdrtrunk Google Group at:
|
||||||
|
|
||||||
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
|
* [Support](https://groups.google.com/forum/#!forum/sdrtrunk)
|
||||||
|
|
||||||
|
# Updates #
|
||||||
|
Subscribe to this group to receive updates about new releases and the ongoing
|
||||||
|
application development effort.
|
||||||
|
|
||||||
|
* [Subscribe for Updates](https://groups.google.com/forum/#!forum/sdrtrunk)
|
||||||
|
|
||||||

|

|
||||||
<script>
|
|
||||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
|
||||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
|
||||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
|
||||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
|
||||||
|
|
||||||
ga('create', 'UA-67489234-1', 'auto');
|
|
||||||
ga('send', 'pageview');
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<property name="major" value="0"/>
|
<property name="major" value="0"/>
|
||||||
<property name="minor" value="2"/>
|
<property name="minor" value="2"/>
|
||||||
<property name="patch" value="0"/>
|
<property name="patch" value="0"/>
|
||||||
<property name="release" value="beta.4"/>
|
<property name="release" value="beta.8"/>
|
||||||
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}-${release}"/>
|
<property name="filename" value="sdrtrunk_${major}.${minor}.${patch}-${release}"/>
|
||||||
|
|
||||||
<!-- Build properties -->
|
<!-- Build properties -->
|
||||||
|
|||||||
2
scripts/run_sdrtrunk_linux.sh
Normal file → Executable file
2
scripts/run_sdrtrunk_linux.sh
Normal file → Executable file
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
java -XX:+UseG1GC -cp "*:libs/*" gui.SDRTrunk&
|
java -XX:+UseG1GC -cp "*:libs/*" gui.SDRTrunk
|
||||||
@@ -27,7 +27,6 @@ import audio.inverted.AudioType;
|
|||||||
public class TalkgroupID extends AliasID
|
public class TalkgroupID extends AliasID
|
||||||
{
|
{
|
||||||
private String mTalkgroup;
|
private String mTalkgroup;
|
||||||
private AudioType mAudioType;
|
|
||||||
|
|
||||||
public TalkgroupID()
|
public TalkgroupID()
|
||||||
{
|
{
|
||||||
@@ -44,20 +43,9 @@ public class TalkgroupID extends AliasID
|
|||||||
mTalkgroup = talkgroup;
|
mTalkgroup = talkgroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@XmlAttribute
|
|
||||||
public AudioType getAudioType()
|
|
||||||
{
|
|
||||||
return mAudioType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAudioType( AudioType type )
|
|
||||||
{
|
|
||||||
mAudioType = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasAudioType()
|
public boolean hasAudioType()
|
||||||
{
|
{
|
||||||
return mAudioType != null;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString()
|
public String toString()
|
||||||
@@ -66,13 +54,6 @@ public class TalkgroupID extends AliasID
|
|||||||
|
|
||||||
sb.append( "Talkgroup: " + mTalkgroup );
|
sb.append( "Talkgroup: " + mTalkgroup );
|
||||||
|
|
||||||
if( mAudioType != null )
|
|
||||||
{
|
|
||||||
sb.append( " [Audio " );
|
|
||||||
sb.append( mAudioType.getShortDisplayString() );
|
|
||||||
sb.append( "]" );
|
|
||||||
}
|
|
||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014 Dennis Sheirer
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -20,9 +20,7 @@ package alias.id.talkgroup;
|
|||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
import javax.swing.DefaultComboBoxModel;
|
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JComboBox;
|
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
@@ -30,7 +28,6 @@ import javax.swing.JTextArea;
|
|||||||
import javax.swing.JTextField;
|
import javax.swing.JTextField;
|
||||||
|
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
import audio.inverted.AudioType;
|
|
||||||
import controller.ConfigurableNode;
|
import controller.ConfigurableNode;
|
||||||
|
|
||||||
public class TalkgroupIDEditor extends JPanel implements ActionListener
|
public class TalkgroupIDEditor extends JPanel implements ActionListener
|
||||||
@@ -38,19 +35,15 @@ public class TalkgroupIDEditor extends JPanel implements ActionListener
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private TalkgroupIDNode mTalkgroupIDNode;
|
private TalkgroupIDNode mTalkgroupIDNode;
|
||||||
|
|
||||||
private JLabel mLabelName;
|
|
||||||
private JTextField mTextTalkgroupID;
|
private JTextField mTextTalkgroupID;
|
||||||
private JLabel mLabelAudio;
|
|
||||||
private JComboBox<AudioType> mComboAudioTypes;
|
|
||||||
|
|
||||||
private String mHelpText = "Enter a formatted talkgroup identifier.\n\n"
|
private String mHelpText = "Enter a formatted talkgroup identifier.\n\n"
|
||||||
|
+ "P25 Talkgroup Format: hexadecimal value, 4 characters for "
|
||||||
|
+ "talkgroup or 6 characters for a mobile radio\n\n"
|
||||||
+ "LTR Talkgroup Format: A-HH-TTT (A=Area, H=Home Repeater, "
|
+ "LTR Talkgroup Format: A-HH-TTT (A=Area, H=Home Repeater, "
|
||||||
+ "T=Talkgroup)\n\n"
|
+ "T=Talkgroup)\n\n"
|
||||||
+ "Passport Talkgroup Format: xxxxx (up to 5-digit number)\n\n"
|
+ "Passport Talkgroup Format: xxxxx (up to 5-digit number)\n\n"
|
||||||
+ "Wildcard: use one or more asterisks (*) for any talkgroup digits.\n\n"
|
+ "Wildcard: use one or more asterisks (*) for any talkgroup digits.";
|
||||||
+ "Audio: if the talkgroup uses simple frequency inversion, specify"
|
|
||||||
+ " the inversion frequency from the dropdown list to automatically"
|
|
||||||
+ " un-invert audio for this specific talkgroup";
|
|
||||||
|
|
||||||
public TalkgroupIDEditor( TalkgroupIDNode talkgroupIDNode )
|
public TalkgroupIDEditor( TalkgroupIDNode talkgroupIDNode )
|
||||||
{
|
{
|
||||||
@@ -72,22 +65,6 @@ public class TalkgroupIDEditor extends JPanel implements ActionListener
|
|||||||
|
|
||||||
add( mTextTalkgroupID, "growx,push" );
|
add( mTextTalkgroupID, "growx,push" );
|
||||||
|
|
||||||
add( new JLabel( "Audio:" ) );
|
|
||||||
|
|
||||||
mComboAudioTypes = new JComboBox<AudioType>();
|
|
||||||
|
|
||||||
mComboAudioTypes.setModel(
|
|
||||||
new DefaultComboBoxModel<AudioType>( AudioType.values() ) );
|
|
||||||
|
|
||||||
AudioType audioType = mTalkgroupIDNode.getTalkgroupID().getAudioType();
|
|
||||||
|
|
||||||
if( audioType != null )
|
|
||||||
{
|
|
||||||
mComboAudioTypes.setSelectedItem( audioType );
|
|
||||||
}
|
|
||||||
|
|
||||||
add( mComboAudioTypes, "growx,push" );
|
|
||||||
|
|
||||||
JButton btnSave = new JButton( "Save" );
|
JButton btnSave = new JButton( "Save" );
|
||||||
btnSave.addActionListener( TalkgroupIDEditor.this );
|
btnSave.addActionListener( TalkgroupIDEditor.this );
|
||||||
add( btnSave, "growx,push" );
|
add( btnSave, "growx,push" );
|
||||||
@@ -117,14 +94,6 @@ public class TalkgroupIDEditor extends JPanel implements ActionListener
|
|||||||
|
|
||||||
((ConfigurableNode)mTalkgroupIDNode.getParent()).sort();
|
((ConfigurableNode)mTalkgroupIDNode.getParent()).sort();
|
||||||
|
|
||||||
AudioType selected = mComboAudioTypes
|
|
||||||
.getItemAt( mComboAudioTypes.getSelectedIndex() );
|
|
||||||
|
|
||||||
if( selected != null )
|
|
||||||
{
|
|
||||||
mTalkgroupIDNode.getTalkgroupID().setAudioType( selected );
|
|
||||||
}
|
|
||||||
|
|
||||||
mTalkgroupIDNode.save();
|
mTalkgroupIDNode.save();
|
||||||
|
|
||||||
mTalkgroupIDNode.show();
|
mTalkgroupIDNode.show();
|
||||||
@@ -139,9 +108,6 @@ public class TalkgroupIDEditor extends JPanel implements ActionListener
|
|||||||
{
|
{
|
||||||
mTextTalkgroupID.setText(
|
mTextTalkgroupID.setText(
|
||||||
mTalkgroupIDNode.getTalkgroupID().getTalkgroup() );
|
mTalkgroupIDNode.getTalkgroupID().getTalkgroup() );
|
||||||
|
|
||||||
mComboAudioTypes.setSelectedItem(
|
|
||||||
mTalkgroupIDNode.getTalkgroupID().getAudioType() );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mTalkgroupIDNode.refresh();
|
mTalkgroupIDNode.refresh();
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
package audio;
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
@@ -17,6 +15,8 @@ package audio;
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
package audio;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@@ -50,7 +50,7 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger( AudioManager.class );
|
private static final Logger mLog = LoggerFactory.getLogger( AudioManager.class );
|
||||||
|
|
||||||
public static final int AUDIO_TIMEOUT = 2000; //2 seconds
|
public static final int AUDIO_TIMEOUT = 1000; //1 second
|
||||||
|
|
||||||
public static final String AUDIO_CHANNELS_PROPERTY = "audio.manager.channels";
|
public static final String AUDIO_CHANNELS_PROPERTY = "audio.manager.channels";
|
||||||
public static final String AUDIO_MIXER_PROPERTY = "audio.manager.mixer";
|
public static final String AUDIO_MIXER_PROPERTY = "audio.manager.mixer";
|
||||||
@@ -182,7 +182,8 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
|
|
||||||
for( AudioOutputConnection connection: mAudioOutputConnections )
|
for( AudioOutputConnection connection: mAudioOutputConnections )
|
||||||
{
|
{
|
||||||
if( connection.isConnected() && connection.isInactive() )
|
if( connection.isInactive() &&
|
||||||
|
mChannelConnectionMap.containsKey( connection.getSource() ) )
|
||||||
{
|
{
|
||||||
mChannelConnectionMap.remove( connection.getSource() );
|
mChannelConnectionMap.remove( connection.getSource() );
|
||||||
connection.disconnect();
|
connection.disconnect();
|
||||||
@@ -424,24 +425,21 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Audio output connection manages a connection between a soure and an audio
|
* Audio output connection manages a connection between a source and an audio
|
||||||
* output and maintains current state information about the audio activity
|
* output and maintains current state information about the audio activity
|
||||||
* received form the source.
|
* received form the source.
|
||||||
*/
|
*/
|
||||||
public class AudioOutputConnection implements Listener<AudioEvent>
|
public class AudioOutputConnection
|
||||||
{
|
{
|
||||||
private static final long CALL_END_DELAY = 2000; //2 seconds
|
|
||||||
private static final int DISCONNECTED = -1;
|
private static final int DISCONNECTED = -1;
|
||||||
|
|
||||||
private AudioOutput mAudioOutput;
|
private AudioOutput mAudioOutput;
|
||||||
private int mPriority = 0;
|
private int mPriority = 0;
|
||||||
private long mLastActivity = System.currentTimeMillis();
|
|
||||||
private int mSource = DISCONNECTED;
|
private int mSource = DISCONNECTED;
|
||||||
|
|
||||||
public AudioOutputConnection( AudioOutput audioOutput )
|
public AudioOutputConnection( AudioOutput audioOutput )
|
||||||
{
|
{
|
||||||
mAudioOutput = audioOutput;
|
mAudioOutput = audioOutput;
|
||||||
mAudioOutput.addAudioEventListener( this );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receive( AudioPacket packet )
|
public void receive( AudioPacket packet )
|
||||||
@@ -457,8 +455,6 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
updateLowestPriorityAssignment();
|
updateLowestPriorityAssignment();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTimestamp();
|
|
||||||
|
|
||||||
if( mAudioOutput != null )
|
if( mAudioOutput != null )
|
||||||
{
|
{
|
||||||
mAudioOutput.receive( packet );
|
mAudioOutput.receive( packet );
|
||||||
@@ -481,11 +477,6 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTimestamp()
|
|
||||||
{
|
|
||||||
mLastActivity = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Terminates the audio output and prepares this connection for disposal
|
* Terminates the audio output and prepares this connection for disposal
|
||||||
*/
|
*/
|
||||||
@@ -518,7 +509,8 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
public void connect( int source )
|
public void connect( int source )
|
||||||
{
|
{
|
||||||
mSource = source;
|
mSource = source;
|
||||||
updateTimestamp();
|
|
||||||
|
mAudioOutput.updateTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -536,6 +528,7 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
public void disconnect()
|
public void disconnect()
|
||||||
{
|
{
|
||||||
mSource = DISCONNECTED;
|
mSource = DISCONNECTED;
|
||||||
|
mPriority = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -544,28 +537,12 @@ public class AudioManager implements Listener<AudioPacket>, IAudioController
|
|||||||
*/
|
*/
|
||||||
public boolean isInactive()
|
public boolean isInactive()
|
||||||
{
|
{
|
||||||
return mLastActivity != Long.MAX_VALUE &&
|
return ( mAudioOutput.getLastActivityTimestamp() + AUDIO_TIMEOUT ) < System.currentTimeMillis();
|
||||||
mLastActivity + CALL_END_DELAY < System.currentTimeMillis();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPriority()
|
public int getPriority()
|
||||||
{
|
{
|
||||||
return mPriority;
|
return mPriority;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receive( AudioEvent event )
|
|
||||||
{
|
|
||||||
switch( event.getType() )
|
|
||||||
{
|
|
||||||
case AUDIO_STARTED:
|
|
||||||
case AUDIO_STOPPED:
|
|
||||||
case AUDIO_CONTINUATION:
|
|
||||||
updateTimestamp();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ public class AudioPacket
|
|||||||
return mAudioData;
|
return mAudioData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasAudioBuffer()
|
||||||
|
{
|
||||||
|
return mAudioData != null;
|
||||||
|
}
|
||||||
|
|
||||||
public enum Type
|
public enum Type
|
||||||
{
|
{
|
||||||
AUDIO,
|
AUDIO,
|
||||||
|
|||||||
@@ -1,19 +1,6 @@
|
|||||||
package audio.metadata;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import sample.Listener;
|
|
||||||
import alias.Alias;
|
|
||||||
import alias.priority.Priority;
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* SDR Trunk
|
* SDR Trunk
|
||||||
* Copyright (C) 2014,2015 Dennis Sheirer
|
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -28,15 +15,30 @@ import alias.priority.Priority;
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
package audio.metadata;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import sample.Listener;
|
||||||
|
import alias.Alias;
|
||||||
|
import alias.priority.Priority;
|
||||||
|
|
||||||
public class AudioMetadata implements Listener<Metadata>
|
public class AudioMetadata implements Listener<Metadata>
|
||||||
{
|
{
|
||||||
protected static final Logger mLog = LoggerFactory.getLogger( AudioMetadata.class );
|
protected static final Logger mLog = LoggerFactory.getLogger( AudioMetadata.class );
|
||||||
|
|
||||||
private int mSource;
|
private int mSource;
|
||||||
private List<Metadata> mMetadata = new ArrayList<>();
|
private Map<MetadataType,Metadata> mMetadata = new HashMap<>();
|
||||||
private boolean mUpdated = false;
|
private boolean mUpdated = false;
|
||||||
|
|
||||||
/* Channel Selectect == highest priority */
|
/* Channel Selected == highest priority */
|
||||||
private boolean mSelected = false;
|
private boolean mSelected = false;
|
||||||
|
|
||||||
/* Call Priority */
|
/* Call Priority */
|
||||||
@@ -92,7 +94,8 @@ public class AudioMetadata implements Listener<Metadata>
|
|||||||
copy.mPriority = mPriority;
|
copy.mPriority = mPriority;
|
||||||
copy.mSelected = mSelected;
|
copy.mSelected = mSelected;
|
||||||
copy.mRecordable = mRecordable;
|
copy.mRecordable = mRecordable;
|
||||||
copy.mMetadata.addAll( mMetadata );
|
|
||||||
|
copy.mMetadata.putAll( mMetadata );
|
||||||
copy.mUpdated = mUpdated;
|
copy.mUpdated = mUpdated;
|
||||||
copy.mIdentifier = new String( mIdentifier );
|
copy.mIdentifier = new String( mIdentifier );
|
||||||
|
|
||||||
@@ -106,25 +109,34 @@ public class AudioMetadata implements Listener<Metadata>
|
|||||||
*/
|
*/
|
||||||
public void reset()
|
public void reset()
|
||||||
{
|
{
|
||||||
Iterator<Metadata> it = mMetadata.iterator();
|
|
||||||
|
|
||||||
while( it.hasNext() )
|
|
||||||
{
|
|
||||||
if( it.next().isTemporal() )
|
|
||||||
{
|
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Reset recordable state, identifier and audio call priority levels
|
/* Reset recordable state, identifier and audio call priority levels
|
||||||
* and reprocess the metadata */
|
* and reprocess the metadata */
|
||||||
mRecordable = mSourceRecordable;
|
mRecordable = mSourceRecordable;
|
||||||
setIdentifier( mSource );
|
setIdentifier( mSource );
|
||||||
mPriority = Priority.DEFAULT_PRIORITY;
|
mPriority = Priority.DEFAULT_PRIORITY;
|
||||||
|
|
||||||
for( Metadata metadata: mMetadata )
|
List<MetadataType> toRemove = new ArrayList<>();
|
||||||
|
|
||||||
|
for( MetadataType type: mMetadata.keySet() )
|
||||||
{
|
{
|
||||||
processMetadata( metadata );
|
Metadata metadata = mMetadata.get( type );
|
||||||
|
|
||||||
|
if( metadata != null )
|
||||||
|
{
|
||||||
|
if( metadata.isTemporal() )
|
||||||
|
{
|
||||||
|
toRemove.add( type );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
processMetadata( metadata );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for( MetadataType type: toRemove )
|
||||||
|
{
|
||||||
|
mMetadata.remove( type );
|
||||||
}
|
}
|
||||||
|
|
||||||
mUpdated = true;
|
mUpdated = true;
|
||||||
@@ -185,8 +197,14 @@ public class AudioMetadata implements Listener<Metadata>
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mMetadata.add( metadata );
|
Metadata existing = mMetadata.get( metadata.getMetadataType() );
|
||||||
processMetadata( metadata );
|
|
||||||
|
if( existing == null || !existing.equals( metadata ) )
|
||||||
|
{
|
||||||
|
mMetadata.put( metadata.getMetadataType(), metadata );
|
||||||
|
processMetadata( metadata );
|
||||||
|
setUpdated( true );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mUpdated = true;
|
mUpdated = true;
|
||||||
@@ -195,26 +213,17 @@ public class AudioMetadata implements Listener<Metadata>
|
|||||||
/**
|
/**
|
||||||
* Returns the complete set of metadata
|
* Returns the complete set of metadata
|
||||||
*/
|
*/
|
||||||
public List<Metadata> getMetadata()
|
public Map<MetadataType,Metadata> getMetadata()
|
||||||
{
|
{
|
||||||
return mMetadata;
|
return mMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the first metadata object matching the type, or null if the
|
* Returns the metadata object matching the type or null
|
||||||
* metadata type is not found.
|
|
||||||
*/
|
*/
|
||||||
public Metadata getMetadata( MetadataType type )
|
public Metadata getMetadata( MetadataType type )
|
||||||
{
|
{
|
||||||
for( Metadata metadata: mMetadata )
|
return mMetadata.get( type );
|
||||||
{
|
|
||||||
if( metadata.getMetadataType() == type )
|
|
||||||
{
|
|
||||||
return metadata;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -296,11 +305,11 @@ public class AudioMetadata implements Listener<Metadata>
|
|||||||
sb.append( getPriority() );
|
sb.append( getPriority() );
|
||||||
sb.append( " updated:" + isUpdated() );
|
sb.append( " updated:" + isUpdated() );
|
||||||
sb.append( " " );
|
sb.append( " " );
|
||||||
for( Metadata m: getMetadata() )
|
for( Entry<MetadataType,Metadata> entry: getMetadata().entrySet() )
|
||||||
{
|
{
|
||||||
sb.append( m.getKey() );
|
sb.append( entry.getValue().getKey() );
|
||||||
sb.append( ":" );
|
sb.append( ":" );
|
||||||
sb.append( m.getValue() );
|
sb.append( entry.getValue().getValue() );
|
||||||
sb.append( " " );
|
sb.append( " " );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,4 +103,44 @@ public class Metadata
|
|||||||
|
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean equals( Metadata other )
|
||||||
|
{
|
||||||
|
if( mMetadataType != other.getMetadataType() )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mTemporal != other.isTemporal() )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mValue != null && other.getValue() != null &&
|
||||||
|
!mValue.contentEquals( other.getValue() ))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ( mValue == null && other.getValue() != null ) ||
|
||||||
|
( mValue != null && other.getValue() == null ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mValueAlias != null && other.getAlias() != null &&
|
||||||
|
!mValueAlias.getName().contentEquals( other.getAlias().getName() ))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( ( mValueAlias == null && other.getAlias() != null ) ||
|
||||||
|
( mValueAlias != null && other.getAlias() == null ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,148 @@
|
|||||||
package audio.output;
|
package audio.output;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import javax.sound.sampled.AudioFormat;
|
||||||
|
import javax.sound.sampled.BooleanControl;
|
||||||
|
import javax.sound.sampled.Control;
|
||||||
import javax.sound.sampled.FloatControl;
|
import javax.sound.sampled.FloatControl;
|
||||||
|
import javax.sound.sampled.Line;
|
||||||
|
import javax.sound.sampled.LineEvent;
|
||||||
|
import javax.sound.sampled.LineListener;
|
||||||
|
import javax.sound.sampled.LineUnavailableException;
|
||||||
|
import javax.sound.sampled.Mixer;
|
||||||
|
import javax.sound.sampled.SourceDataLine;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import sample.Broadcaster;
|
import sample.Broadcaster;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
|
import source.mixer.MixerChannel;
|
||||||
import audio.AudioEvent;
|
import audio.AudioEvent;
|
||||||
import audio.AudioEvent.Type;
|
|
||||||
import audio.AudioPacket;
|
import audio.AudioPacket;
|
||||||
|
import audio.AudioPacket.Type;
|
||||||
import audio.metadata.AudioMetadata;
|
import audio.metadata.AudioMetadata;
|
||||||
import controller.ThreadPoolManager;
|
import controller.ThreadPoolManager;
|
||||||
|
import controller.ThreadPoolManager.ThreadType;
|
||||||
|
|
||||||
public abstract class AudioOutput implements Listener<AudioPacket>
|
public abstract class AudioOutput implements Listener<AudioPacket>, LineListener
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger( AudioOutput.class );
|
private final static Logger mLog = LoggerFactory.getLogger( AudioOutput.class );
|
||||||
|
|
||||||
private AtomicBoolean mSquelched = new AtomicBoolean( false );
|
private LinkedTransferQueue<AudioPacket> mBuffer = new LinkedTransferQueue<>();
|
||||||
|
private int mBufferStartThreshold;
|
||||||
|
private int mBufferStopThreshold;
|
||||||
|
|
||||||
private Listener<AudioMetadata> mAudioMetadataListener;
|
private Listener<AudioMetadata> mAudioMetadataListener;
|
||||||
|
private Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
||||||
|
|
||||||
protected Broadcaster<AudioEvent> mAudioEventBroadcaster = new Broadcaster<>();
|
private ThreadPoolManager mThreadPoolManager;
|
||||||
protected LinkedTransferQueue<AudioPacket> mBuffer = new LinkedTransferQueue<>();
|
private ScheduledFuture<?> mProcessorTask;
|
||||||
protected ThreadPoolManager mThreadPoolManager;
|
|
||||||
protected boolean mCanProcessAudio;
|
|
||||||
protected long mLastActivity;
|
|
||||||
|
|
||||||
public AudioOutput( ThreadPoolManager threadPoolManager )
|
private SourceDataLine mOutput;
|
||||||
|
private Mixer mMixer;
|
||||||
|
private MixerChannel mMixerChannel;
|
||||||
|
private FloatControl mGainControl;
|
||||||
|
private BooleanControl mMuteControl;
|
||||||
|
|
||||||
|
private AudioEvent mAudioStartEvent;
|
||||||
|
private AudioEvent mAudioStopEvent;
|
||||||
|
|
||||||
|
private boolean mCanProcessAudio = false;
|
||||||
|
private long mLastActivity = System.currentTimeMillis();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single audio channel playback with automatic starting and stopping of the
|
||||||
|
* underlying sourcedataline specified by the mixer and mixer channel
|
||||||
|
* arguments.
|
||||||
|
*
|
||||||
|
* Maintains an internal non-blocking audio packet queue and processes this
|
||||||
|
* queue 25 times a second (every 40 ms).
|
||||||
|
*
|
||||||
|
* @param threadPoolManager for assigning buffer processing schedule task
|
||||||
|
* @param mixer to obtain source data line
|
||||||
|
* @param mixerChannel either mono or left/right stereo
|
||||||
|
* @param audioFormat to use during playback
|
||||||
|
* @param lineInfo to use when obtaining the source data line
|
||||||
|
* @param requestedBufferSize of approximately 1 second of audio
|
||||||
|
*/
|
||||||
|
public AudioOutput( ThreadPoolManager threadPoolManager,
|
||||||
|
Mixer mixer,
|
||||||
|
MixerChannel mixerChannel,
|
||||||
|
AudioFormat audioFormat,
|
||||||
|
Line.Info lineInfo,
|
||||||
|
int requestedBufferSize )
|
||||||
{
|
{
|
||||||
mThreadPoolManager = threadPoolManager;
|
mThreadPoolManager = threadPoolManager;
|
||||||
|
mMixer = mixer;
|
||||||
|
mMixerChannel = mixerChannel;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mOutput = (SourceDataLine)mMixer.getLine( lineInfo );
|
||||||
|
|
||||||
|
if( mOutput != null )
|
||||||
|
{
|
||||||
|
mOutput.open( audioFormat, requestedBufferSize );
|
||||||
|
|
||||||
|
//Start threshold: buffer is full with 10% or less of capacity remaining
|
||||||
|
mBufferStartThreshold = (int)( mOutput.getBufferSize() * 0.10 );
|
||||||
|
|
||||||
|
//Stop threshold: buffer is empty with 90% or more capacity available
|
||||||
|
mBufferStopThreshold = (int)( mOutput.getBufferSize() * 0.90 );
|
||||||
|
|
||||||
|
mOutput.addLineListener( this );
|
||||||
|
|
||||||
|
if( mOutput != null )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Control gain = mOutput.getControl( FloatControl.Type.MASTER_GAIN );
|
||||||
|
mGainControl = (FloatControl)gain;
|
||||||
|
}
|
||||||
|
catch( IllegalArgumentException iae )
|
||||||
|
{
|
||||||
|
mLog.warn( "Couldn't obtain MASTER GAIN control for stereo line [" +
|
||||||
|
mixer.getMixerInfo().getName() + " | " + getChannelName() + "]" );
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Control mute = mOutput.getControl( BooleanControl.Type.MUTE );
|
||||||
|
mMuteControl = (BooleanControl)mute;
|
||||||
|
}
|
||||||
|
catch( IllegalArgumentException iae )
|
||||||
|
{
|
||||||
|
mLog.warn( "Couldn't obtain MUTE control for stereo line [" +
|
||||||
|
mixer.getMixerInfo().getName() + " | " + getChannelName() + "]" );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Run the queue processor task every 40 milliseconds or 25 times a second */
|
||||||
|
mProcessorTask = mThreadPoolManager.scheduleFixedRate(
|
||||||
|
ThreadType.AUDIO_PROCESSING, new BufferProcessor(),
|
||||||
|
40, TimeUnit.MILLISECONDS );
|
||||||
|
}
|
||||||
|
|
||||||
|
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
|
||||||
|
getChannelName() );
|
||||||
|
mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
|
||||||
|
getChannelName() );
|
||||||
|
|
||||||
|
mCanProcessAudio = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch ( LineUnavailableException e )
|
||||||
|
{
|
||||||
|
mLog.error( "Couldn't obtain audio source data line for "
|
||||||
|
+ "audio output - mixer [" + mMixer.getMixerInfo().getName() + "]" );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset()
|
public void reset()
|
||||||
@@ -44,47 +155,50 @@ public abstract class AudioOutput implements Listener<AudioPacket>
|
|||||||
{
|
{
|
||||||
mCanProcessAudio = false;
|
mCanProcessAudio = false;
|
||||||
|
|
||||||
|
if( mProcessorTask != null && mThreadPoolManager != null )
|
||||||
|
{
|
||||||
|
mThreadPoolManager.cancel( mProcessorTask );
|
||||||
|
}
|
||||||
|
|
||||||
|
mThreadPoolManager = null;
|
||||||
|
mProcessorTask = null;
|
||||||
|
|
||||||
mBuffer.clear();
|
mBuffer.clear();
|
||||||
|
|
||||||
mAudioEventBroadcaster.dispose();
|
mAudioEventBroadcaster.dispose();
|
||||||
mAudioEventBroadcaster = null;
|
mAudioEventBroadcaster = null;
|
||||||
mAudioMetadataListener = null;
|
mAudioMetadataListener = null;
|
||||||
|
|
||||||
mThreadPoolManager = null;
|
if( mOutput != null )
|
||||||
}
|
|
||||||
|
|
||||||
public abstract String getChannelName();
|
|
||||||
|
|
||||||
public abstract void setMuted( boolean muted );
|
|
||||||
public abstract boolean isMuted();
|
|
||||||
|
|
||||||
public abstract FloatControl getGainControl();
|
|
||||||
public abstract boolean hasGainControl();
|
|
||||||
|
|
||||||
protected void broadcast( AudioMetadata metadata )
|
|
||||||
{
|
|
||||||
if( mAudioMetadataListener != null )
|
|
||||||
{
|
{
|
||||||
mAudioMetadataListener.receive( metadata );
|
mOutput.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mOutput = null;
|
||||||
|
mGainControl = null;
|
||||||
|
mMuteControl = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void broadcast( AudioEvent audioEvent )
|
/**
|
||||||
|
* Converts the audio packet data into a byte buffer format appropriate for
|
||||||
|
* the underlying source data line.
|
||||||
|
*/
|
||||||
|
protected abstract ByteBuffer convert( AudioPacket packet );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio output channel name
|
||||||
|
*/
|
||||||
|
public String getChannelName()
|
||||||
{
|
{
|
||||||
mAudioEventBroadcaster.broadcast( audioEvent );
|
return mMixerChannel.getLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSquelched( boolean squelched )
|
/**
|
||||||
|
* Mixer Channel for this audio output
|
||||||
|
*/
|
||||||
|
protected MixerChannel getMixerChannel()
|
||||||
{
|
{
|
||||||
mSquelched.set( squelched );
|
return mMixerChannel;
|
||||||
|
|
||||||
broadcast( new AudioEvent( squelched ? Type.AUDIO_SQUELCHED :
|
|
||||||
Type.AUDIO_UNSQUELCHED, getChannelName() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSquelched()
|
|
||||||
{
|
|
||||||
return mSquelched.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,6 +214,14 @@ public abstract class AudioOutput implements Listener<AudioPacket>
|
|||||||
mAudioEventBroadcaster.removeListener( listener );
|
mAudioEventBroadcaster.removeListener( listener );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcasts an audio event to the registered listener
|
||||||
|
*/
|
||||||
|
private void broadcast( AudioEvent audioEvent )
|
||||||
|
{
|
||||||
|
mAudioEventBroadcaster.broadcast( audioEvent );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a single listener to receive the audio metadata from each
|
* Registers a single listener to receive the audio metadata from each
|
||||||
* audio packet
|
* audio packet
|
||||||
@@ -115,21 +237,200 @@ public abstract class AudioOutput implements Listener<AudioPacket>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timestamp when most recent buffer was received
|
* Broadcasts audio metadata to the registered listener
|
||||||
*/
|
*/
|
||||||
public long getLastBufferReceived()
|
private void broadcast( AudioMetadata metadata )
|
||||||
|
{
|
||||||
|
if( mAudioMetadataListener != null )
|
||||||
|
{
|
||||||
|
mAudioMetadataListener.receive( metadata );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp of either last buffer received or last buffer processed
|
||||||
|
*/
|
||||||
|
public long getLastActivityTimestamp()
|
||||||
{
|
{
|
||||||
return mLastActivity;
|
return mLastActivity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the last activity timestamp to current system time
|
||||||
|
*/
|
||||||
|
public void updateTimestamp()
|
||||||
|
{
|
||||||
|
mLastActivity = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receive( AudioPacket packet )
|
public void receive( AudioPacket packet )
|
||||||
{
|
{
|
||||||
if( mCanProcessAudio )
|
if( mCanProcessAudio )
|
||||||
{
|
{
|
||||||
mLastActivity = System.currentTimeMillis();
|
//Update the activity timestamp so that this audio output doesn't
|
||||||
|
//get disconnected before it starts processing the audio stream
|
||||||
|
updateTimestamp();
|
||||||
|
|
||||||
mBuffer.add( packet );
|
mBuffer.add( packet );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class BufferProcessor implements Runnable
|
||||||
|
{
|
||||||
|
private AtomicBoolean mProcessing = new AtomicBoolean();
|
||||||
|
|
||||||
|
public BufferProcessor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
/* The processing flag ensures that only one instance of the
|
||||||
|
* processor can run at any given time */
|
||||||
|
if( mProcessing.compareAndSet( false, true ) )
|
||||||
|
{
|
||||||
|
List<AudioPacket> packets = new ArrayList<AudioPacket>();
|
||||||
|
|
||||||
|
mBuffer.drainTo( packets );
|
||||||
|
|
||||||
|
for( AudioPacket packet: packets )
|
||||||
|
{
|
||||||
|
if( packet.getType() == Type.AUDIO )
|
||||||
|
{
|
||||||
|
broadcast( packet.getAudioMetadata() );
|
||||||
|
|
||||||
|
ByteBuffer buffer = convert( packet );
|
||||||
|
|
||||||
|
int wrote = 0;
|
||||||
|
|
||||||
|
if( !mOutput.isRunning() )
|
||||||
|
{
|
||||||
|
int toWrite = mOutput.available();
|
||||||
|
|
||||||
|
if( toWrite > buffer.array().length )
|
||||||
|
{
|
||||||
|
toWrite = buffer.array().length;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Top off the buffer and check if we can start it
|
||||||
|
wrote += mOutput.write( buffer.array(), 0, toWrite );
|
||||||
|
|
||||||
|
checkStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mOutput.isRunning() && wrote < buffer.array().length )
|
||||||
|
{
|
||||||
|
//Blocking write
|
||||||
|
wrote += mOutput.write( buffer.array(), wrote,
|
||||||
|
buffer.array().length - wrote );
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkStop();
|
||||||
|
|
||||||
|
mProcessing.set( false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
mLog.error( "Error while processing audio buffers", e );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts audio playback once audio buffer is almost full and remaining
|
||||||
|
* capacity falls below the start threshold.
|
||||||
|
*/
|
||||||
|
private void checkStart()
|
||||||
|
{
|
||||||
|
if( mCanProcessAudio &&
|
||||||
|
!mOutput.isRunning() &&
|
||||||
|
mOutput.available() <= mBufferStartThreshold )
|
||||||
|
{
|
||||||
|
mOutput.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops audio playback and drains the audio buffer to empty when the
|
||||||
|
* audio buffer is mostly empty and the available buffer capacity
|
||||||
|
* exceeds the stop threshold
|
||||||
|
*/
|
||||||
|
private void checkStop()
|
||||||
|
{
|
||||||
|
if( mCanProcessAudio &&
|
||||||
|
mOutput.isRunning() &&
|
||||||
|
mOutput.available() >= mBufferStopThreshold )
|
||||||
|
{
|
||||||
|
mOutput.drain();
|
||||||
|
mOutput.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the mute state for this audio output channel
|
||||||
|
*/
|
||||||
|
public void setMuted( boolean muted )
|
||||||
|
{
|
||||||
|
if( mMuteControl != null )
|
||||||
|
{
|
||||||
|
mMuteControl.setValue( muted );
|
||||||
|
|
||||||
|
broadcast( new AudioEvent( muted ? AudioEvent.Type.AUDIO_MUTED :
|
||||||
|
AudioEvent.Type.AUDIO_UNMUTED, getChannelName() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current mute state for this audio output channel
|
||||||
|
*/
|
||||||
|
public boolean isMuted()
|
||||||
|
{
|
||||||
|
if( mMuteControl != null )
|
||||||
|
{
|
||||||
|
return mMuteControl.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gain/volume control for this audio output channel, if one is available.
|
||||||
|
*/
|
||||||
|
public FloatControl getGainControl()
|
||||||
|
{
|
||||||
|
return mGainControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasGainControl()
|
||||||
|
{
|
||||||
|
return mGainControl != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors the source data line playback state and broadcasts audio events
|
||||||
|
* to the registered listener as the state changes
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void update( LineEvent event )
|
||||||
|
{
|
||||||
|
LineEvent.Type type = event.getType();
|
||||||
|
|
||||||
|
if( type == LineEvent.Type.START )
|
||||||
|
{
|
||||||
|
mAudioEventBroadcaster.broadcast( mAudioStartEvent );
|
||||||
|
}
|
||||||
|
else if( type == LineEvent.Type.STOP )
|
||||||
|
{
|
||||||
|
mAudioEventBroadcaster.broadcast( mAudioStopEvent );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,273 +3,55 @@ package audio.output;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.ShortBuffer;
|
import java.nio.ShortBuffer;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import javax.sound.sampled.BooleanControl;
|
|
||||||
import javax.sound.sampled.Control;
|
|
||||||
import javax.sound.sampled.FloatControl;
|
|
||||||
import javax.sound.sampled.LineUnavailableException;
|
|
||||||
import javax.sound.sampled.Mixer;
|
import javax.sound.sampled.Mixer;
|
||||||
import javax.sound.sampled.SourceDataLine;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import source.mixer.MixerChannel;
|
import source.mixer.MixerChannel;
|
||||||
import audio.AudioEvent;
|
|
||||||
import audio.AudioFormats;
|
import audio.AudioFormats;
|
||||||
import audio.AudioPacket;
|
import audio.AudioPacket;
|
||||||
import audio.AudioPacket.Type;
|
|
||||||
import controller.ThreadPoolManager;
|
import controller.ThreadPoolManager;
|
||||||
import controller.ThreadPoolManager.ThreadType;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mono Audio output with automatic flow control based on the availability of
|
* Mono Audio output implementation
|
||||||
* audio data packets.
|
|
||||||
*/
|
*/
|
||||||
public class MonoAudioOutput extends AudioOutput
|
public class MonoAudioOutput extends AudioOutput
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger( MonoAudioOutput.class );
|
|
||||||
|
|
||||||
/* Output line buffer size is set at one half second of audio at 48 kHz and
|
|
||||||
* two bytes per sample */
|
|
||||||
private final static int BUFFER_SIZE = 48000;
|
private final static int BUFFER_SIZE = 48000;
|
||||||
|
|
||||||
/* The queue processor will run every 20 milliseconds checking for inbound
|
|
||||||
* audio and automatically starting or stopping audio playback. We set the
|
|
||||||
* data line available space threshold to start when we're 20 ms away from
|
|
||||||
* being full, and the stop threshold when 20 ms away from being empty */
|
|
||||||
private final static int SAMPLE_SIZE_40_MS = BUFFER_SIZE / 25;
|
|
||||||
private final static int START_THRESHOLD = SAMPLE_SIZE_40_MS;
|
|
||||||
private final static int STOP_THRESHOLD = BUFFER_SIZE - SAMPLE_SIZE_40_MS;
|
|
||||||
|
|
||||||
private SourceDataLine mOutput;
|
|
||||||
private FloatControl mGainControl;
|
|
||||||
private BooleanControl mMuteControl;
|
|
||||||
|
|
||||||
private AudioEvent mAudioStartEvent;
|
|
||||||
private AudioEvent mAudioStopEvent;
|
|
||||||
private AudioEvent mAudioContinuationEvent;
|
|
||||||
|
|
||||||
private ScheduledFuture<?> mProcessorTask;
|
|
||||||
|
|
||||||
public MonoAudioOutput( ThreadPoolManager threadPoolManager, Mixer mixer )
|
public MonoAudioOutput( ThreadPoolManager threadPoolManager, Mixer mixer )
|
||||||
{
|
{
|
||||||
super( threadPoolManager );
|
super( threadPoolManager,
|
||||||
|
mixer,
|
||||||
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
|
MixerChannel.MONO,
|
||||||
getChannelName() );
|
AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO,
|
||||||
mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
|
AudioFormats.MONO_SOURCE_DATALINE_INFO,
|
||||||
getChannelName() );
|
BUFFER_SIZE );
|
||||||
mAudioContinuationEvent = new AudioEvent( AudioEvent.Type.AUDIO_CONTINUATION,
|
|
||||||
getChannelName() );
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mOutput = (SourceDataLine)mixer.getLine(
|
|
||||||
AudioFormats.MONO_SOURCE_DATALINE_INFO );
|
|
||||||
|
|
||||||
if( mOutput != null )
|
|
||||||
{
|
|
||||||
mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_MONO, BUFFER_SIZE );
|
|
||||||
mCanProcessAudio = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch ( LineUnavailableException e )
|
|
||||||
{
|
|
||||||
mLog.error( "Couldn't obtain source data line for 48kHz PCM mono "
|
|
||||||
+ "audio output" );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( mOutput != null )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Control gain = mOutput.getControl( FloatControl.Type.MASTER_GAIN );
|
|
||||||
mGainControl = (FloatControl)gain;
|
|
||||||
}
|
|
||||||
catch( IllegalArgumentException iae )
|
|
||||||
{
|
|
||||||
mLog.warn( "Couldn't obtain MASTER GAIN control for mono line [" +
|
|
||||||
mixer.getMixerInfo().getName() + "]" );
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Control mute = mOutput.getControl( BooleanControl.Type.MUTE );
|
|
||||||
mMuteControl = (BooleanControl)mute;
|
|
||||||
}
|
|
||||||
catch( IllegalArgumentException iae )
|
|
||||||
{
|
|
||||||
mLog.warn( "Couldn't obtain MUTE control for mono line [" +
|
|
||||||
mixer.getMixerInfo().getName() + "]" );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Run the queue processor task every 40 milliseconds */
|
|
||||||
mProcessorTask = mThreadPoolManager.scheduleFixedRate(
|
|
||||||
ThreadType.AUDIO_PROCESSING, new QueueProcessor(),
|
|
||||||
40, TimeUnit.MILLISECONDS );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public String getChannelName()
|
* Converts the audio packet data into mono audio frames.
|
||||||
|
*/
|
||||||
|
protected ByteBuffer convert( AudioPacket packet )
|
||||||
{
|
{
|
||||||
return MixerChannel.MONO.getLabel();
|
if( packet.hasAudioBuffer() )
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
if( mProcessorTask != null && mThreadPoolManager != null )
|
|
||||||
{
|
{
|
||||||
mThreadPoolManager.cancel( mProcessorTask );
|
float[] samples = packet.getAudioBuffer().getSamples();
|
||||||
}
|
|
||||||
|
|
||||||
mProcessorTask = null;
|
/* Little-endian byte buffer */
|
||||||
|
ByteBuffer buffer =
|
||||||
|
ByteBuffer.allocate( samples.length * 2 )
|
||||||
|
.order( ByteOrder.LITTLE_ENDIAN );
|
||||||
|
|
||||||
super.dispose();
|
ShortBuffer shortBuffer = buffer.asShortBuffer();
|
||||||
|
|
||||||
if( mOutput != null )
|
for( float sample: samples )
|
||||||
{
|
|
||||||
mOutput.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
mOutput = null;
|
|
||||||
mGainControl = null;
|
|
||||||
mMuteControl = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class QueueProcessor implements Runnable
|
|
||||||
{
|
|
||||||
private AtomicBoolean mProcessing = new AtomicBoolean();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
/* The mProcessing flag ensures that only one instance of the
|
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
||||||
* processor can run at any given time */
|
|
||||||
if( mProcessing.compareAndSet( false, true ) )
|
|
||||||
{
|
|
||||||
List<AudioPacket> packets = new ArrayList<AudioPacket>();
|
|
||||||
|
|
||||||
mBuffer.drainTo( packets );
|
|
||||||
|
|
||||||
if( packets.size() > 0 )
|
|
||||||
{
|
|
||||||
broadcast( mAudioContinuationEvent );
|
|
||||||
|
|
||||||
for( AudioPacket packet: packets )
|
|
||||||
{
|
|
||||||
if( mCanProcessAudio )
|
|
||||||
{
|
|
||||||
if( isSquelched() )
|
|
||||||
{
|
|
||||||
checkStop();
|
|
||||||
}
|
|
||||||
else if( packet.getType() == Type.AUDIO )
|
|
||||||
{
|
|
||||||
float[] samples = packet.getAudioBuffer().getSamples();
|
|
||||||
|
|
||||||
/* Little-endian byte buffer */
|
|
||||||
ByteBuffer buffer =
|
|
||||||
ByteBuffer.allocate( samples.length * 2 )
|
|
||||||
.order( ByteOrder.LITTLE_ENDIAN );
|
|
||||||
|
|
||||||
ShortBuffer shortBuffer = buffer.asShortBuffer();
|
|
||||||
|
|
||||||
for( float sample: samples )
|
|
||||||
{
|
|
||||||
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
//This is a blocking write
|
|
||||||
mOutput.write( buffer.array(), 0, buffer.array().length );
|
|
||||||
|
|
||||||
checkStart();
|
|
||||||
|
|
||||||
broadcast( packet.getAudioMetadata() );
|
|
||||||
|
|
||||||
mLastActivity = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
checkStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
mProcessing.set( false );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch( Exception e )
|
|
||||||
{
|
|
||||||
mLog.error( "Error while processing audio buffers", e );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkStart()
|
return null;
|
||||||
{
|
|
||||||
if( mCanProcessAudio && !isSquelched() &&
|
|
||||||
!mOutput.isRunning() && mOutput.available() <= START_THRESHOLD )
|
|
||||||
{
|
|
||||||
mOutput.start();
|
|
||||||
|
|
||||||
mAudioEventBroadcaster.broadcast( mAudioStartEvent );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkStop()
|
|
||||||
{
|
|
||||||
if( mCanProcessAudio && mOutput.isRunning() &&
|
|
||||||
( mOutput.available() >= STOP_THRESHOLD || isSquelched() ) )
|
|
||||||
{
|
|
||||||
mOutput.drain();
|
|
||||||
mOutput.stop();
|
|
||||||
|
|
||||||
mAudioEventBroadcaster.broadcast( mAudioStopEvent );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMuted( boolean muted )
|
|
||||||
{
|
|
||||||
if( mMuteControl != null )
|
|
||||||
{
|
|
||||||
mMuteControl.setValue( muted );
|
|
||||||
|
|
||||||
broadcast( new AudioEvent( muted ? AudioEvent.Type.AUDIO_MUTED :
|
|
||||||
AudioEvent.Type.AUDIO_UNMUTED, getChannelName() ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isMuted()
|
|
||||||
{
|
|
||||||
if( mMuteControl != null )
|
|
||||||
{
|
|
||||||
return mMuteControl.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FloatControl getGainControl()
|
|
||||||
{
|
|
||||||
return mGainControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasGainControl()
|
|
||||||
{
|
|
||||||
return mGainControl != null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,302 +3,69 @@ package audio.output;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.ShortBuffer;
|
import java.nio.ShortBuffer;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
import javax.sound.sampled.BooleanControl;
|
|
||||||
import javax.sound.sampled.Control;
|
|
||||||
import javax.sound.sampled.FloatControl;
|
|
||||||
import javax.sound.sampled.LineUnavailableException;
|
|
||||||
import javax.sound.sampled.Mixer;
|
import javax.sound.sampled.Mixer;
|
||||||
import javax.sound.sampled.SourceDataLine;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import source.mixer.MixerChannel;
|
import source.mixer.MixerChannel;
|
||||||
import audio.AudioEvent;
|
|
||||||
import audio.AudioFormats;
|
import audio.AudioFormats;
|
||||||
import audio.AudioPacket;
|
import audio.AudioPacket;
|
||||||
import audio.AudioPacket.Type;
|
|
||||||
import controller.ThreadPoolManager;
|
import controller.ThreadPoolManager;
|
||||||
import controller.ThreadPoolManager.ThreadType;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mono Audio output in stereo format with automatic flow control based on the
|
* Stereo audio output implementation.
|
||||||
* availability of audio data packets.
|
|
||||||
*/
|
*/
|
||||||
public class StereoAudioOutput extends AudioOutput
|
public class StereoAudioOutput extends AudioOutput
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger( StereoAudioOutput.class );
|
|
||||||
|
|
||||||
/* Output line buffer size is set at one half second of audio at 48 kHz and
|
|
||||||
* two bytes per sample for two channels */
|
|
||||||
private final static int BUFFER_SIZE = 96000;
|
private final static int BUFFER_SIZE = 96000;
|
||||||
|
|
||||||
/* The queue processor will run every 20 milliseconds checking for inbound
|
|
||||||
* audio and automatically starting or stopping audio playback. We set the
|
|
||||||
* data line available space threshold to start when we're 20 ms away from
|
|
||||||
* being full, and the stop threshold when 20 ms away from being empty */
|
|
||||||
private final static int SAMPLE_SIZE_40_MS = BUFFER_SIZE / 25;
|
|
||||||
private final static int START_THRESHOLD = SAMPLE_SIZE_40_MS;
|
|
||||||
private final static int STOP_THRESHOLD = BUFFER_SIZE - SAMPLE_SIZE_40_MS;
|
|
||||||
|
|
||||||
private SourceDataLine mOutput;
|
|
||||||
private FloatControl mGainControl;
|
|
||||||
private BooleanControl mMuteControl;
|
|
||||||
|
|
||||||
private AudioEvent mAudioStartEvent;
|
|
||||||
private AudioEvent mAudioStopEvent;
|
|
||||||
private AudioEvent mAudioContinuationEvent;
|
|
||||||
|
|
||||||
private MixerChannel mMixerChannel;
|
|
||||||
|
|
||||||
private ScheduledFuture<?> mProcessorTask;
|
|
||||||
|
|
||||||
public StereoAudioOutput( ThreadPoolManager threadPoolManager,
|
public StereoAudioOutput( ThreadPoolManager threadPoolManager,
|
||||||
Mixer mixer, MixerChannel channel )
|
Mixer mixer, MixerChannel channel )
|
||||||
{
|
{
|
||||||
super( threadPoolManager );
|
super( threadPoolManager,
|
||||||
|
mixer,
|
||||||
assert( channel == MixerChannel.LEFT || channel == MixerChannel.RIGHT );
|
channel,
|
||||||
|
AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO,
|
||||||
mMixerChannel = channel;
|
AudioFormats.STEREO_SOURCE_DATALINE_INFO,
|
||||||
|
BUFFER_SIZE );
|
||||||
mAudioStartEvent = new AudioEvent( AudioEvent.Type.AUDIO_STARTED,
|
|
||||||
getChannelName() );
|
|
||||||
mAudioStopEvent = new AudioEvent( AudioEvent.Type.AUDIO_STOPPED,
|
|
||||||
getChannelName() );
|
|
||||||
mAudioContinuationEvent = new AudioEvent( AudioEvent.Type.AUDIO_CONTINUATION,
|
|
||||||
getChannelName() );
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
mOutput = (SourceDataLine)mixer.getLine(
|
|
||||||
AudioFormats.STEREO_SOURCE_DATALINE_INFO );
|
|
||||||
|
|
||||||
if( mOutput != null )
|
|
||||||
{
|
|
||||||
mOutput.open( AudioFormats.PCM_SIGNED_48KHZ_16BITS_STEREO, BUFFER_SIZE );
|
|
||||||
mCanProcessAudio = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch ( LineUnavailableException e )
|
|
||||||
{
|
|
||||||
mLog.error( "Couldn't obtain source data line for 48kHz PCM stereo "
|
|
||||||
+ "audio output - mixer [" + mixer.getMixerInfo().getName() +
|
|
||||||
"] channel [" + mMixerChannel.name() + "]" );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( mOutput != null )
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Control gain = mOutput.getControl( FloatControl.Type.MASTER_GAIN );
|
|
||||||
mGainControl = (FloatControl)gain;
|
|
||||||
}
|
|
||||||
catch( IllegalArgumentException iae )
|
|
||||||
{
|
|
||||||
mLog.warn( "Couldn't obtain MASTER GAIN control for stereo line [" +
|
|
||||||
mixer.getMixerInfo().getName() + " | " + channel.name() + "]" );
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Control mute = mOutput.getControl( BooleanControl.Type.MUTE );
|
|
||||||
mMuteControl = (BooleanControl)mute;
|
|
||||||
}
|
|
||||||
catch( IllegalArgumentException iae )
|
|
||||||
{
|
|
||||||
mLog.warn( "Couldn't obtain MUTE control for stereo line [" +
|
|
||||||
mixer.getMixerInfo().getName() + " | " + channel.name() + "]" );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Run the queue processor task every 40 milliseconds */
|
|
||||||
mProcessorTask = mThreadPoolManager.scheduleFixedRate(
|
|
||||||
ThreadType.AUDIO_PROCESSING, new QueueProcessor(),
|
|
||||||
40, TimeUnit.MILLISECONDS );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public String getChannelName()
|
* Converts the audio packet data into stereo audio frames with the mixer
|
||||||
|
* channel containing the audio and the other channel containing zero
|
||||||
|
* valued (silent) samples.
|
||||||
|
*/
|
||||||
|
protected ByteBuffer convert( AudioPacket packet )
|
||||||
{
|
{
|
||||||
return mMixerChannel.getLabel();
|
if( packet.hasAudioBuffer() )
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose()
|
|
||||||
{
|
|
||||||
if( mProcessorTask != null && mThreadPoolManager != null )
|
|
||||||
{
|
{
|
||||||
mThreadPoolManager.cancel( mProcessorTask );
|
float[] samples = packet.getAudioBuffer().getSamples();
|
||||||
}
|
|
||||||
|
|
||||||
mProcessorTask = null;
|
/* Little-endian byte buffer */
|
||||||
|
ByteBuffer buffer = ByteBuffer.allocate( samples.length * 4 )
|
||||||
|
.order( ByteOrder.LITTLE_ENDIAN );
|
||||||
|
|
||||||
super.dispose();
|
ShortBuffer shortBuffer = buffer.asShortBuffer();
|
||||||
|
|
||||||
if( mOutput != null )
|
if( getMixerChannel() == MixerChannel.LEFT )
|
||||||
{
|
|
||||||
mOutput.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
mOutput = null;
|
|
||||||
mGainControl = null;
|
|
||||||
mMuteControl = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ByteBuffer convert( float[] samples )
|
|
||||||
{
|
|
||||||
/* Little-endian byte buffer */
|
|
||||||
ByteBuffer buffer = ByteBuffer.allocate( samples.length * 4 )
|
|
||||||
.order( ByteOrder.LITTLE_ENDIAN );
|
|
||||||
|
|
||||||
ShortBuffer shortBuffer = buffer.asShortBuffer();
|
|
||||||
|
|
||||||
if( mMixerChannel == MixerChannel.LEFT )
|
|
||||||
{
|
|
||||||
for( float sample: samples )
|
|
||||||
{
|
{
|
||||||
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
for( float sample: samples )
|
||||||
shortBuffer.put( (short)0 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for( float sample: samples )
|
|
||||||
{
|
|
||||||
shortBuffer.put( (short)0 );
|
|
||||||
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class QueueProcessor implements Runnable
|
|
||||||
{
|
|
||||||
private AtomicBoolean mProcessing = new AtomicBoolean();
|
|
||||||
|
|
||||||
public QueueProcessor()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
/* The mProcessing flag ensures that only one instance of the
|
|
||||||
* processor can run at any given time */
|
|
||||||
if( mProcessing.compareAndSet( false, true ) )
|
|
||||||
{
|
{
|
||||||
List<AudioPacket> packets = new ArrayList<AudioPacket>();
|
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
||||||
|
shortBuffer.put( (short)0 );
|
||||||
mBuffer.drainTo( packets );
|
|
||||||
|
|
||||||
if( packets.size() > 0 )
|
|
||||||
{
|
|
||||||
broadcast( mAudioContinuationEvent );
|
|
||||||
|
|
||||||
for( AudioPacket packet: packets )
|
|
||||||
{
|
|
||||||
if( mCanProcessAudio )
|
|
||||||
{
|
|
||||||
if( isSquelched() )
|
|
||||||
{
|
|
||||||
checkStop();
|
|
||||||
}
|
|
||||||
else if( packet.getType() == Type.AUDIO )
|
|
||||||
{
|
|
||||||
ByteBuffer buffer = convert(
|
|
||||||
packet.getAudioBuffer().getSamples() );
|
|
||||||
|
|
||||||
mOutput.write( buffer.array(), 0,
|
|
||||||
buffer.array().length );
|
|
||||||
|
|
||||||
checkStart();
|
|
||||||
|
|
||||||
broadcast( packet.getAudioMetadata() );
|
|
||||||
|
|
||||||
mLastActivity = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
checkStop();
|
|
||||||
}
|
|
||||||
|
|
||||||
mProcessing.set( false );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
else
|
||||||
{
|
{
|
||||||
mLog.error( "Error while processing audio buffers", e );
|
for( float sample: samples )
|
||||||
|
{
|
||||||
|
shortBuffer.put( (short)0 );
|
||||||
|
shortBuffer.put( (short)( sample * Short.MAX_VALUE ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkStart()
|
return null;
|
||||||
{
|
|
||||||
if( mCanProcessAudio && !isSquelched() &&
|
|
||||||
!mOutput.isRunning() && mOutput.available() <= START_THRESHOLD )
|
|
||||||
{
|
|
||||||
mOutput.start();
|
|
||||||
|
|
||||||
mAudioEventBroadcaster.broadcast( mAudioStartEvent );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkStop()
|
|
||||||
{
|
|
||||||
if( mCanProcessAudio && mOutput.isRunning() &&
|
|
||||||
( mOutput.available() >= STOP_THRESHOLD || isSquelched() ) )
|
|
||||||
{
|
|
||||||
mOutput.drain();
|
|
||||||
mOutput.stop();
|
|
||||||
|
|
||||||
mAudioEventBroadcaster.broadcast( mAudioStopEvent );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setMuted( boolean muted )
|
|
||||||
{
|
|
||||||
if( mMuteControl != null )
|
|
||||||
{
|
|
||||||
mMuteControl.setValue( muted );
|
|
||||||
|
|
||||||
broadcast( new AudioEvent( muted ? AudioEvent.Type.AUDIO_MUTED :
|
|
||||||
AudioEvent.Type.AUDIO_UNMUTED, getChannelName() ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isMuted()
|
|
||||||
{
|
|
||||||
if( mMuteControl != null )
|
|
||||||
{
|
|
||||||
return mMuteControl.getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FloatControl getGainControl()
|
|
||||||
{
|
|
||||||
return mGainControl;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasGainControl()
|
|
||||||
{
|
|
||||||
return mGainControl != null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -297,6 +297,9 @@ public class Channel extends Configuration
|
|||||||
|
|
||||||
/* Broadcast channel deleted event */
|
/* Broadcast channel deleted event */
|
||||||
fireChannelEvent( Event.CHANNEL_DELETED );
|
fireChannelEvent( Event.CHANNEL_DELETED );
|
||||||
|
|
||||||
|
mProcessingChain.dispose();
|
||||||
|
mProcessingChain = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -809,7 +812,7 @@ public class Channel extends Configuration
|
|||||||
{
|
{
|
||||||
if( mProcessingChain == null )
|
if( mProcessingChain == null )
|
||||||
{
|
{
|
||||||
mProcessingChain = new ProcessingChain();
|
mProcessingChain = new ProcessingChain( getName() );
|
||||||
|
|
||||||
/* Get the optional alias list for the decode modules to use */
|
/* Get the optional alias list for the decode modules to use */
|
||||||
AliasList aliasList = mResourceManager.getPlaylistManager()
|
AliasList aliasList = mResourceManager.getPlaylistManager()
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ import source.Source;
|
|||||||
|
|
||||||
public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable
|
public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable
|
||||||
{
|
{
|
||||||
public InstrumentableProcessingChain()
|
public InstrumentableProcessingChain( String name )
|
||||||
{
|
{
|
||||||
|
super( name );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSource( Source source ) throws IllegalStateException
|
public void setSource( Source source ) throws IllegalStateException
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ public class DecoderViewFrame extends JInternalFrame
|
|||||||
|
|
||||||
mDecodeConfig = decodeConfig;
|
mDecodeConfig = decodeConfig;
|
||||||
|
|
||||||
mProcessingChain = new InstrumentableProcessingChain();
|
mProcessingChain = new InstrumentableProcessingChain( "instrumented" );
|
||||||
|
|
||||||
List<Module> modules = DecoderFactory
|
List<Module> modules = DecoderFactory
|
||||||
.getModules( ChannelType.STANDARD,
|
.getModules( ChannelType.STANDARD,
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ package module;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import message.IMessageListener;
|
import message.IMessageListener;
|
||||||
@@ -61,7 +62,7 @@ import audio.metadata.Metadata;
|
|||||||
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 controller.ThreadPoolManager;
|
import controller.NamingThreadFactory;
|
||||||
import controller.channel.ChannelEvent;
|
import controller.channel.ChannelEvent;
|
||||||
import controller.channel.IChannelEventListener;
|
import controller.channel.IChannelEventListener;
|
||||||
import controller.channel.IChannelEventProvider;
|
import controller.channel.IChannelEventProvider;
|
||||||
@@ -99,15 +100,17 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
private Broadcaster<RealBuffer> mRealBufferBroadcaster = new Broadcaster<>();
|
private Broadcaster<RealBuffer> mRealBufferBroadcaster = new Broadcaster<>();
|
||||||
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
|
private Broadcaster<SquelchState> mSquelchStateBroadcaster = new Broadcaster<>();
|
||||||
|
|
||||||
// private ThreadPoolManager mThreadPoolManager;
|
private ScheduledExecutorService mScheduledExecutorService;
|
||||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||||
|
|
||||||
protected Source mSource;
|
protected Source mSource;
|
||||||
private List<Module> mModules = new ArrayList<>();
|
private List<Module> mModules = new ArrayList<>();
|
||||||
private IFrequencyCorrectionController mFrequencyCorrectionController;
|
private IFrequencyCorrectionController mFrequencyCorrectionController;
|
||||||
|
|
||||||
public ProcessingChain()
|
public ProcessingChain( String name )
|
||||||
{
|
{
|
||||||
|
mScheduledExecutorService = Executors.newScheduledThreadPool( 1,
|
||||||
|
new NamingThreadFactory( "channel " + name ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispose()
|
public void dispose()
|
||||||
@@ -121,6 +124,9 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
|
|
||||||
mModules.clear();
|
mModules.clear();
|
||||||
|
|
||||||
|
mScheduledExecutorService.shutdownNow();
|
||||||
|
mScheduledExecutorService = null;
|
||||||
|
|
||||||
mFrequencyCorrectionController = null;
|
mFrequencyCorrectionController = null;
|
||||||
|
|
||||||
mAudioPacketBroadcaster.dispose();
|
mAudioPacketBroadcaster.dispose();
|
||||||
@@ -432,9 +438,11 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
* of the decoder state's */
|
* of the decoder state's */
|
||||||
if( mSource instanceof TunerChannelSource )
|
if( mSource instanceof TunerChannelSource )
|
||||||
{
|
{
|
||||||
|
TunerChannelSource tunerChannelSource = (TunerChannelSource)mSource;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
long frequency = ((TunerChannelSource)mSource).getFrequency();
|
long frequency = tunerChannelSource.getFrequency();
|
||||||
|
|
||||||
mDecoderStateEventBroadcaster.broadcast(
|
mDecoderStateEventBroadcaster.broadcast(
|
||||||
new DecoderStateEvent( this, Event.SOURCE_FREQUENCY,
|
new DecoderStateEvent( this, Event.SOURCE_FREQUENCY,
|
||||||
@@ -444,6 +452,8 @@ public class ProcessingChain implements IChannelEventListener
|
|||||||
{
|
{
|
||||||
mLog.error( "Error getting frequency from tuner channel source", e );
|
mLog.error( "Error getting frequency from tuner channel source", e );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tunerChannelSource.start( mScheduledExecutorService );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -153,7 +153,9 @@ public class LTRStandardDecoderState extends DecoderState
|
|||||||
case CA_ENDD:
|
case CA_ENDD:
|
||||||
CallEndMessage end = (CallEndMessage)message;
|
CallEndMessage end = (CallEndMessage)message;
|
||||||
|
|
||||||
int repeater = end.getHomeRepeater();
|
//Home channel is 31 for call end -- use the free channel
|
||||||
|
//as the call end channel
|
||||||
|
int repeater = end.getFree();
|
||||||
|
|
||||||
mLCNTracker.processCallChannel( repeater );
|
mLCNTracker.processCallChannel( repeater );
|
||||||
|
|
||||||
|
|||||||
@@ -289,6 +289,7 @@ public class P25MessageFramer implements Listener<Dibit>
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
mComplete = true;
|
mComplete = true;
|
||||||
|
dispatch( new P25Message( mMessage.copy(), duid, mAliasList ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ public class P25_C4FMDecoder extends P25Decoder implements IRealBufferListener
|
|||||||
/* Filter demodulated sample buffers */
|
/* Filter demodulated sample buffers */
|
||||||
float[] filter = FilterFactory.getLowPass( 48000, 2500, 4000, 80, WindowType.HANNING, true );
|
float[] filter = FilterFactory.getLowPass( 48000, 2500, 4000, 80, WindowType.HANNING, true );
|
||||||
|
|
||||||
mLog.debug( "Demod Filter tap count:" + filter.length + " coefficients:" + Arrays.toString( filter ) );
|
|
||||||
mC4FMPreFilter = new RealFIRFilter_RB_RB( filter, 1.0f );
|
mC4FMPreFilter = new RealFIRFilter_RB_RB( filter, 1.0f );
|
||||||
|
|
||||||
/* Issue tuned frequency correction commands, remotely controlled by the
|
/* Issue tuned frequency correction commands, remotely controlled by the
|
||||||
|
|||||||
@@ -18,4 +18,19 @@ public class TDUMessage extends P25Message
|
|||||||
mCRC = new CRC[ 1 ];
|
mCRC = new CRC[ 1 ];
|
||||||
mCRC[ 0 ] = CRC.PASSED;
|
mCRC[ 0 ] = CRC.PASSED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append( "NAC:" );
|
||||||
|
sb.append( getNAC() );
|
||||||
|
sb.append( " " );
|
||||||
|
sb.append( getDUID().getLabel() );
|
||||||
|
|
||||||
|
sb.append( " TERMINATOR DATA UNIT" );
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ import java.awt.Component;
|
|||||||
import java.awt.Point;
|
import java.awt.Point;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.awt.event.MouseListener;
|
import java.awt.event.MouseListener;
|
||||||
import java.util.HashMap;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import javax.swing.JMenu;
|
import javax.swing.JMenu;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
@@ -53,8 +54,8 @@ public class ChannelList extends JPanel implements ChannelEventListener
|
|||||||
|
|
||||||
private final static Logger mLog = LoggerFactory.getLogger( ChannelList.class );
|
private final static Logger mLog = LoggerFactory.getLogger( ChannelList.class );
|
||||||
|
|
||||||
private HashMap<Channel,ChannelCollectionPanel> mDisplayedPanels =
|
private Map<Channel,ChannelCollectionPanel> mDisplayedPanels =
|
||||||
new HashMap<Channel,ChannelCollectionPanel>();
|
new ConcurrentHashMap<Channel,ChannelCollectionPanel>();
|
||||||
|
|
||||||
private PlaylistManager mPlaylistManager;
|
private PlaylistManager mPlaylistManager;
|
||||||
private SettingsManager mSettingsManager;
|
private SettingsManager mSettingsManager;
|
||||||
@@ -253,20 +254,6 @@ public class ChannelList extends JPanel implements ChannelEventListener
|
|||||||
idleMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
|
idleMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
|
||||||
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_IDLE ) );
|
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_IDLE ) );
|
||||||
|
|
||||||
JMenu noTunerMenu = new JMenu( "No Tuner" );
|
|
||||||
channelsMenu.add( noTunerMenu );
|
|
||||||
|
|
||||||
noTunerMenu.add( new ColorSettingMenuItem( mSettingsManager,
|
|
||||||
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ) );
|
|
||||||
noTunerMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
|
|
||||||
ColorSettingName.CHANNEL_STATE_GRADIENT_MIDDLE_NO_TUNER ) );
|
|
||||||
noTunerMenu.add( new JSeparator() );
|
|
||||||
|
|
||||||
noTunerMenu.add( new ColorSettingMenuItem( mSettingsManager,
|
|
||||||
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ) );
|
|
||||||
noTunerMenu.add( new ColorSettingResetMenuItem( mSettingsManager,
|
|
||||||
ColorSettingName.CHANNEL_STATE_GRADIENT_TOP_NO_TUNER ) );
|
|
||||||
|
|
||||||
JMenu labelMenu = new JMenu( "Labels" );
|
JMenu labelMenu = new JMenu( "Labels" );
|
||||||
colorMenu.add( labelMenu );
|
colorMenu.add( labelMenu );
|
||||||
|
|
||||||
|
|||||||
@@ -144,11 +144,6 @@ public class ChannelState extends Module implements ICallEventProvider,
|
|||||||
mMonitoring.set( false );
|
mMonitoring.set( false );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new RuntimeException( "Channel state monitor's scheduled "
|
|
||||||
+ "future pointer was not null" );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,6 +383,8 @@ public class ChannelState extends Module implements ICallEventProvider,
|
|||||||
{
|
{
|
||||||
broadcast( SquelchState.SQUELCH );
|
broadcast( SquelchState.SQUELCH );
|
||||||
|
|
||||||
|
broadcast( new MetadataReset() );
|
||||||
|
|
||||||
mState = State.TEARDOWN;
|
mState = State.TEARDOWN;
|
||||||
|
|
||||||
broadcast( ChangedAttribute.CHANNEL_STATE );
|
broadcast( ChangedAttribute.CHANNEL_STATE );
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ public class ChannelStatePanel extends JPanel
|
|||||||
switch( changedAttribute )
|
switch( changedAttribute )
|
||||||
{
|
{
|
||||||
case CHANNEL_STATE:
|
case CHANNEL_STATE:
|
||||||
if( state.getState() != null )
|
if( state != null && state.getState() != null )
|
||||||
{
|
{
|
||||||
mStateLabel.setText( state.getState().getDisplayValue() );
|
mStateLabel.setText( state.getState().getDisplayValue() );
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,14 @@ public class TrafficChannelManager extends Module
|
|||||||
@Override
|
@Override
|
||||||
public void dispose()
|
public void dispose()
|
||||||
{
|
{
|
||||||
|
for( Channel trafficChannel: mTrafficChannelPool )
|
||||||
|
{
|
||||||
|
trafficChannel.setEnabled( false );
|
||||||
|
trafficChannel.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
mTrafficChannelPool.clear();
|
||||||
|
|
||||||
mCallEventListener = null;
|
mCallEventListener = null;
|
||||||
mResourceManager = null;
|
mResourceManager = null;
|
||||||
mDecodeConfiguration = null;
|
mDecodeConfiguration = null;
|
||||||
|
|||||||
@@ -557,11 +557,6 @@ public class SettingsManager
|
|||||||
ArrayList<TunerConfiguration> configs = getSettings()
|
ArrayList<TunerConfiguration> configs = getSettings()
|
||||||
.getTunerConfigurations( type );
|
.getTunerConfigurations( type );
|
||||||
|
|
||||||
if( configs.isEmpty() )
|
|
||||||
{
|
|
||||||
configs.add( addNewTunerConfiguration( type, "Default" ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return configs;
|
return configs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,9 +63,8 @@ public class Recording implements Comparable<Recording>,
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource getChannel( TunerChannel channel )
|
||||||
TunerChannel channel ) throws RejectedExecutionException,
|
throws RejectedExecutionException, SourceException
|
||||||
SourceException
|
|
||||||
{
|
{
|
||||||
// TODO Auto-generated method stub
|
// TODO Auto-generated method stub
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -71,9 +71,7 @@ public class RecordingSourceManager
|
|||||||
|
|
||||||
if( recording != null )
|
if( recording != null )
|
||||||
{
|
{
|
||||||
retVal = recording.getChannel(
|
retVal = recording.getChannel( tunerChannel );
|
||||||
mResourceManager.getThreadPoolManager(),
|
|
||||||
tunerChannel );
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,10 +30,9 @@ import sample.complex.ComplexBuffer;
|
|||||||
import source.SourceException;
|
import source.SourceException;
|
||||||
import source.tuner.frequency.FrequencyChangeBroadcaster;
|
import source.tuner.frequency.FrequencyChangeBroadcaster;
|
||||||
import source.tuner.frequency.FrequencyChangeEvent;
|
import source.tuner.frequency.FrequencyChangeEvent;
|
||||||
import source.tuner.frequency.FrequencyChangeListener;
|
|
||||||
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
||||||
|
import source.tuner.frequency.FrequencyChangeListener;
|
||||||
import controller.ResourceManager;
|
import controller.ResourceManager;
|
||||||
import controller.ThreadPoolManager;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tuner - provides tuner channel sources, representing a channel frequency
|
* Tuner - provides tuner channel sources, representing a channel frequency
|
||||||
@@ -147,8 +146,8 @@ public abstract class Tuner implements FrequencyChangeBroadcaster,
|
|||||||
*
|
*
|
||||||
* @return - source for 48k sample rate
|
* @return - source for 48k sample rate
|
||||||
*/
|
*/
|
||||||
public abstract TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public abstract TunerChannelSource getChannel( TunerChannel channel )
|
||||||
TunerChannel channel ) throws RejectedExecutionException, SourceException;
|
throws RejectedExecutionException, SourceException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Releases the tuned channel resources
|
* Releases the tuned channel resources
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ public interface TunerChannelProvider
|
|||||||
*
|
*
|
||||||
* @return - source for 48k sample rate
|
* @return - source for 48k sample rate
|
||||||
*/
|
*/
|
||||||
public abstract TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public abstract TunerChannelSource getChannel( TunerChannel channel )
|
||||||
TunerChannel channel ) throws RejectedExecutionException, SourceException;
|
throws RejectedExecutionException, SourceException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Releases the tuned channel resources
|
* Releases the tuned channel resources
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
@@ -37,8 +38,6 @@ import source.SourceException;
|
|||||||
import source.tuner.frequency.FrequencyChangeEvent;
|
import source.tuner.frequency.FrequencyChangeEvent;
|
||||||
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
||||||
import source.tuner.frequency.FrequencyChangeListener;
|
import source.tuner.frequency.FrequencyChangeListener;
|
||||||
import controller.ThreadPoolManager;
|
|
||||||
import controller.ThreadPoolManager.ThreadType;
|
|
||||||
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;
|
||||||
@@ -62,7 +61,6 @@ public class TunerChannelSource extends ComplexSource
|
|||||||
private ComplexPrimeCICDecimate mDecimationFilter;
|
private ComplexPrimeCICDecimate mDecimationFilter;
|
||||||
private Listener<ComplexBuffer> mListener;
|
private Listener<ComplexBuffer> mListener;
|
||||||
private FrequencyChangeListener mFrequencyChangeListener;
|
private FrequencyChangeListener mFrequencyChangeListener;
|
||||||
private ThreadPoolManager mThreadPoolManager;
|
|
||||||
private ScheduledFuture<?> mTaskHandle;
|
private ScheduledFuture<?> mTaskHandle;
|
||||||
|
|
||||||
private long mTunerFrequency = 0;
|
private long mTunerFrequency = 0;
|
||||||
@@ -71,15 +69,11 @@ public class TunerChannelSource extends ComplexSource
|
|||||||
|
|
||||||
private DecimationProcessor mDecimationProcessor = new DecimationProcessor();
|
private DecimationProcessor mDecimationProcessor = new DecimationProcessor();
|
||||||
|
|
||||||
public TunerChannelSource( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource( Tuner tuner, TunerChannel tunerChannel )
|
||||||
Tuner tuner,
|
throws RejectedExecutionException, SourceException
|
||||||
TunerChannel tunerChannel )
|
|
||||||
throws RejectedExecutionException, SourceException
|
|
||||||
{
|
{
|
||||||
super( "Tuner Channel Source" );
|
super( "Tuner Channel Source" );
|
||||||
|
|
||||||
mThreadPoolManager = threadPoolManager;
|
|
||||||
|
|
||||||
mTuner = tuner;
|
mTuner = tuner;
|
||||||
mTuner.addListener( (FrequencyChangeListener)this );
|
mTuner.addListener( (FrequencyChangeListener)this );
|
||||||
|
|
||||||
@@ -95,14 +89,17 @@ public class TunerChannelSource extends ComplexSource
|
|||||||
/* Fire a sample rate change event to setup the decimation chain */
|
/* Fire a sample rate change event to setup the decimation chain */
|
||||||
frequencyChanged( new FrequencyChangeEvent(
|
frequencyChanged( new FrequencyChangeEvent(
|
||||||
Event.SAMPLE_RATE_CHANGE_NOTIFICATION, mTuner.getSampleRate() ) );
|
Event.SAMPLE_RATE_CHANGE_NOTIFICATION, mTuner.getSampleRate() ) );
|
||||||
|
}
|
||||||
|
|
||||||
/* Schedule the decimation task to run every 20 ms (50 iterations/second) */
|
public void start( ScheduledExecutorService scheduledExecutorService )
|
||||||
mTaskHandle = mThreadPoolManager.scheduleFixedRate( ThreadType.DECIMATION,
|
{
|
||||||
mDecimationProcessor, 20, TimeUnit.MILLISECONDS );
|
/* Schedule the decimation task to run every 10 ms (100 iterations/second) */
|
||||||
|
mTaskHandle = scheduledExecutorService.scheduleAtFixedRate(
|
||||||
|
mDecimationProcessor, 0, 10, TimeUnit.MILLISECONDS );
|
||||||
|
|
||||||
/* Finally, register to receive samples from the tuner */
|
/* Finally, register to receive samples from the tuner */
|
||||||
mTuner.addListener( (Listener<ComplexBuffer>)this );
|
mTuner.addListener( (Listener<ComplexBuffer>)this );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose()
|
public void dispose()
|
||||||
@@ -129,14 +126,15 @@ public class TunerChannelSource extends ComplexSource
|
|||||||
{
|
{
|
||||||
if( mTaskHandle != null )
|
if( mTaskHandle != null )
|
||||||
{
|
{
|
||||||
mThreadPoolManager.cancel( mTaskHandle );
|
mTaskHandle.cancel( true );
|
||||||
mThreadPoolManager = null;
|
|
||||||
mTaskHandle = null;
|
mTaskHandle = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
synchronized( mBuffer )
|
||||||
mBuffer.clear();
|
{
|
||||||
mBuffer = null;
|
mBuffer.clear();
|
||||||
|
mBuffer = null;
|
||||||
|
}
|
||||||
|
|
||||||
mFrequencyChangeListener = null;
|
mFrequencyChangeListener = null;
|
||||||
mListener = null;
|
mListener = null;
|
||||||
@@ -160,7 +158,13 @@ public class TunerChannelSource extends ComplexSource
|
|||||||
@Override
|
@Override
|
||||||
public void receive( ComplexBuffer buffer )
|
public void receive( ComplexBuffer buffer )
|
||||||
{
|
{
|
||||||
mBuffer.add( buffer );
|
synchronized( mBuffer )
|
||||||
|
{
|
||||||
|
if( mBuffer != null )
|
||||||
|
{
|
||||||
|
mBuffer.add( buffer );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFrequencyChangeListener( FrequencyChangeListener listener )
|
public void setFrequencyChangeListener( FrequencyChangeListener listener )
|
||||||
@@ -290,7 +294,7 @@ public class TunerChannelSource extends ComplexSource
|
|||||||
{
|
{
|
||||||
if( mBuffer != null )
|
if( mBuffer != null )
|
||||||
{
|
{
|
||||||
mBuffer.drainTo( mSampleBuffers, 4 );
|
mBuffer.drainTo( mSampleBuffers, 8 );
|
||||||
|
|
||||||
for( Buffer buffer: mSampleBuffers )
|
for( Buffer buffer: mSampleBuffers )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -194,8 +194,8 @@ public abstract class TunerController implements Tunable
|
|||||||
*
|
*
|
||||||
* @throws RejectedExecutionException if the decimation processor has an error
|
* @throws RejectedExecutionException if the decimation processor has an error
|
||||||
*/
|
*/
|
||||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource getChannel( Tuner tuner, TunerChannel channel )
|
||||||
Tuner tuner, TunerChannel channel ) throws RejectedExecutionException
|
throws RejectedExecutionException
|
||||||
{
|
{
|
||||||
TunerChannelSource source = null;
|
TunerChannelSource source = null;
|
||||||
|
|
||||||
@@ -210,8 +210,7 @@ public abstract class TunerController implements Tunable
|
|||||||
updateLOFrequency();
|
updateLOFrequency();
|
||||||
}
|
}
|
||||||
|
|
||||||
source = new TunerChannelSource( threadPoolManager,
|
source = new TunerChannelSource( tuner, channel );
|
||||||
tuner, channel );
|
|
||||||
}
|
}
|
||||||
catch( SourceException se )
|
catch( SourceException se )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -100,8 +100,7 @@ public class TunerManager
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
retVal = tuner.getChannel(
|
retVal = tuner.getChannel( tunerChannel );
|
||||||
mResourceManager.getThreadPoolManager(), tunerChannel );
|
|
||||||
}
|
}
|
||||||
catch ( RejectedExecutionException ree )
|
catch ( RejectedExecutionException ree )
|
||||||
{
|
{
|
||||||
@@ -545,16 +544,14 @@ public class TunerManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
config = configs.get( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
//If we're still null at this point, create a default config
|
//If we're still null at this point, create a default config
|
||||||
if( config == null )
|
if( config == null )
|
||||||
{
|
{
|
||||||
|
String name = "Default-" + tuner.getUniqueID();
|
||||||
|
|
||||||
config = mResourceManager.getSettingsManager()
|
config = mResourceManager.getSettingsManager()
|
||||||
.addNewTunerConfiguration( tuner.getTunerType(), "Default" );
|
.addNewTunerConfiguration( tuner.getTunerType(), name );
|
||||||
}
|
}
|
||||||
|
|
||||||
//Persist the config as the selected tuner configuration. The method
|
//Persist the config as the selected tuner configuration. The method
|
||||||
|
|||||||
@@ -128,8 +128,8 @@ public class AirspyTuner extends Tuner
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource getChannel( TunerChannel channel )
|
||||||
TunerChannel channel ) throws RejectedExecutionException, SourceException
|
throws RejectedExecutionException, SourceException
|
||||||
{
|
{
|
||||||
//TODO: this channel has a decimated sample rate of:
|
//TODO: this channel has a decimated sample rate of:
|
||||||
// 10.0 MSps = 10,000,000 / 208 = 48076.923
|
// 10.0 MSps = 10,000,000 / 208 = 48076.923
|
||||||
@@ -138,7 +138,7 @@ public class AirspyTuner extends Tuner
|
|||||||
//Consider implementing a fractional resampler to get a correct 48 kHz
|
//Consider implementing a fractional resampler to get a correct 48 kHz
|
||||||
//output sample rate
|
//output sample rate
|
||||||
|
|
||||||
return mController.getChannel( threadPoolManager, this, channel );
|
return mController.getChannel( this, channel );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -262,7 +262,30 @@ public class AirspyTunerController extends TunerController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static String getTransferStatus( int status )
|
||||||
|
{
|
||||||
|
switch( status )
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return "TRANSFER COMPLETED (0)";
|
||||||
|
case 1:
|
||||||
|
return "TRANSFER ERROR (1)";
|
||||||
|
case 2:
|
||||||
|
return "TRANSFER TIMED OUT (2)";
|
||||||
|
case 3:
|
||||||
|
return "TRANSFER CANCELLED (3)";
|
||||||
|
case 4:
|
||||||
|
return "TRANSFER STALL (4)";
|
||||||
|
case 5:
|
||||||
|
return "TRANSFER NO DEVICE (5)";
|
||||||
|
case 6:
|
||||||
|
return "TRANSFER OVERFLOW (6)";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN TRANSFER STATUS (" + status + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void apply( TunerConfiguration config ) throws SourceException
|
public void apply( TunerConfiguration config ) throws SourceException
|
||||||
{
|
{
|
||||||
if( config instanceof AirspyTunerConfiguration )
|
if( config instanceof AirspyTunerConfiguration )
|
||||||
@@ -1192,18 +1215,21 @@ public class AirspyTunerController extends TunerController
|
|||||||
*/
|
*/
|
||||||
public void addListener( Listener<ComplexBuffer> listener )
|
public void addListener( Listener<ComplexBuffer> listener )
|
||||||
{
|
{
|
||||||
mComplexBufferBroadcaster.addListener( listener );
|
synchronized( mComplexBufferBroadcaster )
|
||||||
|
{
|
||||||
|
mComplexBufferBroadcaster.addListener( listener );
|
||||||
|
|
||||||
if( mBufferProcessor == null || !mBufferProcessor.isRunning() )
|
if( mBufferProcessor == null || !mBufferProcessor.isRunning() )
|
||||||
{
|
{
|
||||||
mBufferProcessor = new BufferProcessor();
|
mBufferProcessor = new BufferProcessor();
|
||||||
|
|
||||||
Thread thread = new Thread( mBufferProcessor );
|
Thread thread = new Thread( mBufferProcessor );
|
||||||
thread.setDaemon( true );
|
thread.setDaemon( true );
|
||||||
thread.setName( "Airspy Buffer Processor" );
|
thread.setName( "Airspy Buffer Processor" );
|
||||||
|
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1212,12 +1238,15 @@ public class AirspyTunerController extends TunerController
|
|||||||
*/
|
*/
|
||||||
public void removeListener( Listener<ComplexBuffer> listener )
|
public void removeListener( Listener<ComplexBuffer> listener )
|
||||||
{
|
{
|
||||||
mComplexBufferBroadcaster.removeListener( listener );
|
synchronized( mComplexBufferBroadcaster )
|
||||||
|
{
|
||||||
|
mComplexBufferBroadcaster.removeListener( listener );
|
||||||
|
|
||||||
if( !mComplexBufferBroadcaster.hasListeners() )
|
if( !mComplexBufferBroadcaster.hasListeners() )
|
||||||
{
|
{
|
||||||
mBufferProcessor.stop();
|
mBufferProcessor.stop();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1227,8 +1256,11 @@ public class AirspyTunerController extends TunerController
|
|||||||
public class BufferProcessor implements Runnable, TransferCallback
|
public class BufferProcessor implements Runnable, TransferCallback
|
||||||
{
|
{
|
||||||
private ScheduledFuture<?> mSampleDispatcherTask;
|
private ScheduledFuture<?> mSampleDispatcherTask;
|
||||||
private CopyOnWriteArrayList<Transfer> mTransfers;
|
private LinkedTransferQueue<Transfer> mAvailableTransfers;
|
||||||
|
private LinkedTransferQueue<Transfer> mTransfersInProgress = new LinkedTransferQueue<>();
|
||||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||||
|
private ByteBuffer mLibUsbHandlerStatus;
|
||||||
|
private boolean mCancel = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
@@ -1237,33 +1269,64 @@ public class AirspyTunerController extends TunerController
|
|||||||
{
|
{
|
||||||
prepareTransfers();
|
prepareTransfers();
|
||||||
|
|
||||||
for( Transfer transfer: mTransfers )
|
|
||||||
{
|
|
||||||
int result = LibUsb.submitTransfer( transfer );
|
|
||||||
|
|
||||||
if( result != LibUsb.SUCCESS )
|
|
||||||
{
|
|
||||||
mLog.error( "error submitting transfer [" +
|
|
||||||
LibUsb.errorName( result ) + "]" );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mSampleDispatcherTask = mThreadPoolManager.scheduleFixedRate(
|
mSampleDispatcherTask = mThreadPoolManager.scheduleFixedRate(
|
||||||
ThreadType.SOURCE_SAMPLE_PROCESSING, new BufferDispatcher(),
|
ThreadType.SOURCE_SAMPLE_PROCESSING, new BufferDispatcher(),
|
||||||
20, TimeUnit.MILLISECONDS );
|
20, TimeUnit.MILLISECONDS );
|
||||||
|
|
||||||
|
mLibUsbHandlerStatus = ByteBuffer.allocateDirect( 4 );
|
||||||
|
|
||||||
|
List<Transfer> transfers = new ArrayList<>();
|
||||||
|
|
||||||
|
mCancel = false;
|
||||||
|
|
||||||
while( mRunning.get() )
|
while( mRunning.get() )
|
||||||
{
|
{
|
||||||
ByteBuffer completed = ByteBuffer.allocateDirect( 4 );
|
mAvailableTransfers.drainTo( transfers );
|
||||||
|
|
||||||
|
for( Transfer transfer: transfers )
|
||||||
|
{
|
||||||
|
int result = LibUsb.submitTransfer( transfer );
|
||||||
|
|
||||||
|
if( result == LibUsb.SUCCESS )
|
||||||
|
{
|
||||||
|
mTransfersInProgress.add( transfer );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error( "error submitting transfer [" +
|
||||||
|
LibUsb.errorName( result ) + "]" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int result = LibUsb.handleEventsTimeoutCompleted(
|
int result = LibUsb.handleEventsTimeoutCompleted(
|
||||||
null, USB_TIMEOUT_MS, completed.asIntBuffer() );
|
null, USB_TIMEOUT_MS, mLibUsbHandlerStatus.asIntBuffer() );
|
||||||
|
|
||||||
if( result != LibUsb.SUCCESS )
|
if( result != LibUsb.SUCCESS )
|
||||||
{
|
{
|
||||||
mLog.error( "error handling events for libusb" );
|
mLog.error( "error handling events for libusb" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transfers.clear();
|
||||||
|
|
||||||
|
mLibUsbHandlerStatus.rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mCancel )
|
||||||
|
{
|
||||||
|
for( Transfer transfer: mTransfersInProgress )
|
||||||
|
{
|
||||||
|
LibUsb.cancelTransfer( transfer );
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = LibUsb.handleEventsTimeoutCompleted( null,
|
||||||
|
USB_TIMEOUT_MS, mLibUsbHandlerStatus.asIntBuffer() );
|
||||||
|
|
||||||
|
if( result != LibUsb.SUCCESS )
|
||||||
|
{
|
||||||
|
mLog.error( "error handling events for libusb" );
|
||||||
|
}
|
||||||
|
|
||||||
|
mLibUsbHandlerStatus.rewind();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1273,10 +1336,10 @@ public class AirspyTunerController extends TunerController
|
|||||||
*/
|
*/
|
||||||
public void stop()
|
public void stop()
|
||||||
{
|
{
|
||||||
|
mCancel = true;
|
||||||
|
|
||||||
if( mRunning.compareAndSet( true, false ) )
|
if( mRunning.compareAndSet( true, false ) )
|
||||||
{
|
{
|
||||||
cancel();
|
|
||||||
|
|
||||||
if( mSampleDispatcherTask != null )
|
if( mSampleDispatcherTask != null )
|
||||||
{
|
{
|
||||||
mSampleDispatcherTask.cancel( true );
|
mSampleDispatcherTask.cancel( true );
|
||||||
@@ -1296,97 +1359,68 @@ public class AirspyTunerController extends TunerController
|
|||||||
@Override
|
@Override
|
||||||
public void processTransfer( Transfer transfer )
|
public void processTransfer( Transfer transfer )
|
||||||
{
|
{
|
||||||
if( transfer.status() == LibUsb.TRANSFER_COMPLETED )
|
mTransfersInProgress.remove( transfer );
|
||||||
{
|
|
||||||
ByteBuffer buffer = transfer.buffer();
|
|
||||||
|
|
||||||
byte[] data = new byte[ transfer.actualLength() ];
|
|
||||||
|
|
||||||
buffer.get( data );
|
|
||||||
|
|
||||||
buffer.rewind();
|
|
||||||
|
|
||||||
mFilledBuffers.add( data );
|
|
||||||
|
|
||||||
if( !isRunning() )
|
|
||||||
{
|
|
||||||
LibUsb.cancelTransfer( transfer );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch( transfer.status() )
|
switch( transfer.status() )
|
||||||
{
|
{
|
||||||
case LibUsb.TRANSFER_COMPLETED:
|
case LibUsb.TRANSFER_COMPLETED:
|
||||||
/* resubmit the transfer */
|
case LibUsb.TRANSFER_STALL:
|
||||||
int result = LibUsb.submitTransfer( transfer );
|
if( transfer.actualLength() > 0 )
|
||||||
|
|
||||||
if( result != LibUsb.SUCCESS )
|
|
||||||
{
|
{
|
||||||
mLog.error( "couldn't resubmit buffer transfer to tuner" );
|
ByteBuffer buffer = transfer.buffer();
|
||||||
LibUsb.freeTransfer( transfer );
|
|
||||||
mTransfers.remove( transfer );
|
byte[] data = new byte[ transfer.actualLength() ];
|
||||||
|
|
||||||
|
buffer.get( data );
|
||||||
|
|
||||||
|
buffer.rewind();
|
||||||
|
|
||||||
|
if( isRunning() )
|
||||||
|
{
|
||||||
|
mFilledBuffers.add( data );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case LibUsb.TRANSFER_CANCELLED:
|
case LibUsb.TRANSFER_CANCELLED:
|
||||||
/* free the transfer and remove it */
|
|
||||||
LibUsb.freeTransfer( transfer );
|
|
||||||
mTransfers.remove( transfer );
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
/* unexpected error */
|
/* unexpected error */
|
||||||
mLog.error( "transfer error" );
|
mLog.error( "transfer error [" +
|
||||||
|
getTransferStatus( transfer.status() ) +
|
||||||
|
"] transferred actual: " + transfer.actualLength() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mAvailableTransfers.add( transfer );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancel()
|
|
||||||
{
|
|
||||||
for( Transfer transfer: mTransfers )
|
|
||||||
{
|
|
||||||
LibUsb.cancelTransfer( transfer );
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteBuffer completed = ByteBuffer.allocateDirect( 4 );
|
|
||||||
|
|
||||||
int result = LibUsb.handleEventsTimeoutCompleted(
|
|
||||||
null, USB_TIMEOUT_MS, completed.asIntBuffer() );
|
|
||||||
|
|
||||||
if( result != LibUsb.SUCCESS )
|
|
||||||
{
|
|
||||||
mLog.error( "error handling usb events during cancel [" +
|
|
||||||
LibUsb.errorName( result ) + "]" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares (allocates) a set of transfer buffers for use in
|
* Prepares (allocates) a set of transfer buffers for use in
|
||||||
* transferring data from the tuner via the bulk interface
|
* transferring data from the tuner via the bulk interface
|
||||||
*/
|
*/
|
||||||
private void prepareTransfers() throws LibUsbException
|
private void prepareTransfers() throws LibUsbException
|
||||||
{
|
{
|
||||||
mTransfers = new CopyOnWriteArrayList<Transfer>();
|
if( mAvailableTransfers == null )
|
||||||
|
|
||||||
for( int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++ )
|
|
||||||
{
|
{
|
||||||
Transfer transfer = LibUsb.allocTransfer();
|
mAvailableTransfers = new LinkedTransferQueue<>();
|
||||||
|
|
||||||
if( transfer == null )
|
for( int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++ )
|
||||||
{
|
{
|
||||||
throw new LibUsbException( "couldn't allocate transfer",
|
Transfer transfer = LibUsb.allocTransfer();
|
||||||
LibUsb.ERROR_NO_MEM );
|
|
||||||
}
|
|
||||||
|
|
||||||
final ByteBuffer buffer =
|
if( transfer == null )
|
||||||
ByteBuffer.allocateDirect( mBufferSize );
|
{
|
||||||
|
throw new LibUsbException( "couldn't allocate transfer",
|
||||||
|
LibUsb.ERROR_NO_MEM );
|
||||||
|
}
|
||||||
|
|
||||||
LibUsb.fillBulkTransfer( transfer,
|
final ByteBuffer buffer =
|
||||||
mDeviceHandle,
|
ByteBuffer.allocateDirect( mBufferSize );
|
||||||
USB_ENDPOINT,
|
|
||||||
buffer,
|
|
||||||
BufferProcessor.this,
|
|
||||||
"Buffer #" + x,
|
|
||||||
USB_TIMEOUT_MS );
|
|
||||||
|
|
||||||
mTransfers.add( transfer );
|
LibUsb.fillBulkTransfer( transfer, mDeviceHandle, USB_ENDPOINT,
|
||||||
|
buffer, BufferProcessor.this, "Buffer", USB_TIMEOUT_MS );
|
||||||
|
|
||||||
|
mAvailableTransfers.add( transfer );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ public class B100Tuner extends Tuner
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource getChannel( TunerChannel channel )
|
||||||
TunerChannel channel ) throws RejectedExecutionException, SourceException
|
throws RejectedExecutionException, SourceException
|
||||||
{
|
{
|
||||||
//TODO: pass this call to the tuner controller
|
//TODO: pass this call to the tuner controller
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -112,11 +112,10 @@ public class FCDTuner extends MixerTuner
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource getChannel( TunerChannel tunerChannel )
|
||||||
TunerChannel tunerChannel ) throws RejectedExecutionException,
|
throws RejectedExecutionException, SourceException
|
||||||
SourceException
|
|
||||||
{
|
{
|
||||||
return mController.getChannel( threadPoolManager, this, tunerChannel );
|
return mController.getChannel( this, tunerChannel );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -106,10 +106,10 @@ public class HackRFTuner extends Tuner
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource getChannel( TunerChannel channel )
|
||||||
TunerChannel channel ) throws RejectedExecutionException, SourceException
|
throws RejectedExecutionException, SourceException
|
||||||
{
|
{
|
||||||
return mController.getChannel( threadPoolManager, this, channel );
|
return mController.getChannel( this, channel );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
@@ -91,7 +92,7 @@ public class HackRFTunerController extends TunerController
|
|||||||
private final static Logger mLog =
|
private final static Logger mLog =
|
||||||
LoggerFactory.getLogger( HackRFTunerController.class );
|
LoggerFactory.getLogger( HackRFTunerController.class );
|
||||||
|
|
||||||
public final static long TIMEOUT_US = 1000000l; //uSeconds
|
public final static long USB_TIMEOUT_MS = 1000000l; //uSeconds
|
||||||
public static final byte USB_ENDPOINT = (byte)0x81;
|
public static final byte USB_ENDPOINT = (byte)0x81;
|
||||||
public static final byte USB_INTERFACE = (byte)0x0;
|
public static final byte USB_INTERFACE = (byte)0x0;
|
||||||
|
|
||||||
@@ -394,7 +395,7 @@ public class HackRFTunerController extends TunerController
|
|||||||
(short)value,
|
(short)value,
|
||||||
(short)index,
|
(short)index,
|
||||||
buffer,
|
buffer,
|
||||||
TIMEOUT_US );
|
USB_TIMEOUT_MS );
|
||||||
|
|
||||||
if( transferred < 0 )
|
if( transferred < 0 )
|
||||||
{
|
{
|
||||||
@@ -467,7 +468,7 @@ public class HackRFTunerController extends TunerController
|
|||||||
(short)value,
|
(short)value,
|
||||||
(short)index,
|
(short)index,
|
||||||
buffer,
|
buffer,
|
||||||
TIMEOUT_US );
|
USB_TIMEOUT_MS );
|
||||||
|
|
||||||
if( transferred < 0 )
|
if( transferred < 0 )
|
||||||
{
|
{
|
||||||
@@ -886,18 +887,21 @@ public class HackRFTunerController extends TunerController
|
|||||||
*/
|
*/
|
||||||
public void addListener( Listener<ComplexBuffer> listener )
|
public void addListener( Listener<ComplexBuffer> listener )
|
||||||
{
|
{
|
||||||
mComplexBufferBroadcaster.addListener( listener );
|
synchronized( mComplexBufferBroadcaster )
|
||||||
|
{
|
||||||
|
mComplexBufferBroadcaster.addListener( listener );
|
||||||
|
|
||||||
if( mBufferProcessor == null || !mBufferProcessor.isRunning() )
|
if( mBufferProcessor == null || !mBufferProcessor.isRunning() )
|
||||||
{
|
{
|
||||||
mBufferProcessor = new BufferProcessor();
|
mBufferProcessor = new BufferProcessor();
|
||||||
|
|
||||||
Thread thread = new Thread( mBufferProcessor );
|
Thread thread = new Thread( mBufferProcessor );
|
||||||
thread.setDaemon( true );
|
thread.setDaemon( true );
|
||||||
thread.setName( "HackRF Sample Processor" );
|
thread.setName( "HackRF Sample Processor" );
|
||||||
|
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -906,12 +910,15 @@ public class HackRFTunerController extends TunerController
|
|||||||
*/
|
*/
|
||||||
public void removeListener( Listener<ComplexBuffer> listener )
|
public void removeListener( Listener<ComplexBuffer> listener )
|
||||||
{
|
{
|
||||||
mComplexBufferBroadcaster.removeListener( listener );
|
synchronized( mComplexBufferBroadcaster )
|
||||||
|
{
|
||||||
|
mComplexBufferBroadcaster.removeListener( listener );
|
||||||
|
|
||||||
if( !mComplexBufferBroadcaster.hasListeners() )
|
if( !mComplexBufferBroadcaster.hasListeners() )
|
||||||
{
|
{
|
||||||
mBufferProcessor.stop();
|
mBufferProcessor.stop();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -921,45 +928,77 @@ public class HackRFTunerController extends TunerController
|
|||||||
public class BufferProcessor implements Runnable, TransferCallback
|
public class BufferProcessor implements Runnable, TransferCallback
|
||||||
{
|
{
|
||||||
private ScheduledFuture<?> mSampleDispatcherTask;
|
private ScheduledFuture<?> mSampleDispatcherTask;
|
||||||
private CopyOnWriteArrayList<Transfer> mTransfers;
|
private LinkedTransferQueue<Transfer> mAvailableTransfers;
|
||||||
|
private LinkedTransferQueue<Transfer> mTransfersInProgress = new LinkedTransferQueue<>();
|
||||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||||
|
private ByteBuffer mLibUsbHandlerStatus;
|
||||||
|
private boolean mCancel = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
if( mRunning.compareAndSet( false, true ) )
|
if( mRunning.compareAndSet( false, true ) )
|
||||||
{
|
{
|
||||||
mLog.info( "starting HackRF sample fetch thread" );
|
|
||||||
|
|
||||||
prepareTransfers();
|
prepareTransfers();
|
||||||
|
|
||||||
for( Transfer transfer: mTransfers )
|
|
||||||
{
|
|
||||||
int result = LibUsb.submitTransfer( transfer );
|
|
||||||
|
|
||||||
if( result != LibUsb.SUCCESS )
|
|
||||||
{
|
|
||||||
mLog.error( "error submitting transfer [" +
|
|
||||||
LibUsb.errorName( result ) + "]" );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mSampleDispatcherTask = mThreadPoolManager.scheduleFixedRate(
|
mSampleDispatcherTask = mThreadPoolManager.scheduleFixedRate(
|
||||||
ThreadType.SOURCE_SAMPLE_PROCESSING, new BufferDispatcher(),
|
ThreadType.SOURCE_SAMPLE_PROCESSING, new BufferDispatcher(),
|
||||||
20, TimeUnit.MILLISECONDS );
|
20, TimeUnit.MILLISECONDS );
|
||||||
|
|
||||||
|
mLibUsbHandlerStatus = ByteBuffer.allocateDirect( 4 );
|
||||||
|
|
||||||
|
List<Transfer> transfers = new ArrayList<>();
|
||||||
|
|
||||||
|
mCancel = false;
|
||||||
|
|
||||||
while( mRunning.get() )
|
while( mRunning.get() )
|
||||||
{
|
{
|
||||||
ByteBuffer completed = ByteBuffer.allocateDirect( 4 );
|
mAvailableTransfers.drainTo( transfers );
|
||||||
|
|
||||||
|
for( Transfer transfer: transfers )
|
||||||
|
{
|
||||||
|
int result = LibUsb.submitTransfer( transfer );
|
||||||
|
|
||||||
|
if( result == LibUsb.SUCCESS )
|
||||||
|
{
|
||||||
|
mTransfersInProgress.add( transfer );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error( "error submitting transfer [" +
|
||||||
|
LibUsb.errorName( result ) + "]" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int result = LibUsb.handleEventsTimeoutCompleted(
|
int result = LibUsb.handleEventsTimeoutCompleted(
|
||||||
null, TIMEOUT_US, completed.asIntBuffer() );
|
null, USB_TIMEOUT_MS, mLibUsbHandlerStatus.asIntBuffer() );
|
||||||
|
|
||||||
if( result != LibUsb.SUCCESS )
|
if( result != LibUsb.SUCCESS )
|
||||||
{
|
{
|
||||||
mLog.error( "error handling events for libusb" );
|
mLog.error( "error handling events for libusb" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transfers.clear();
|
||||||
|
|
||||||
|
mLibUsbHandlerStatus.rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
if( mCancel )
|
||||||
|
{
|
||||||
|
for( Transfer transfer: mTransfersInProgress )
|
||||||
|
{
|
||||||
|
LibUsb.cancelTransfer( transfer );
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = LibUsb.handleEventsTimeoutCompleted(
|
||||||
|
null, USB_TIMEOUT_MS, mLibUsbHandlerStatus.asIntBuffer() );
|
||||||
|
|
||||||
|
if( result != LibUsb.SUCCESS )
|
||||||
|
{
|
||||||
|
mLog.error( "error cancelling transfers in progress" );
|
||||||
|
}
|
||||||
|
|
||||||
|
mLibUsbHandlerStatus.rewind();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -969,12 +1008,10 @@ public class HackRFTunerController extends TunerController
|
|||||||
*/
|
*/
|
||||||
public void stop()
|
public void stop()
|
||||||
{
|
{
|
||||||
|
mCancel = true;
|
||||||
|
|
||||||
if( mRunning.compareAndSet( true, false ) )
|
if( mRunning.compareAndSet( true, false ) )
|
||||||
{
|
{
|
||||||
mLog.info( "stopping HackRF sample fetch thread" );
|
|
||||||
|
|
||||||
cancel();
|
|
||||||
|
|
||||||
if( mSampleDispatcherTask != null )
|
if( mSampleDispatcherTask != null )
|
||||||
{
|
{
|
||||||
mSampleDispatcherTask.cancel( true );
|
mSampleDispatcherTask.cancel( true );
|
||||||
@@ -994,41 +1031,29 @@ public class HackRFTunerController extends TunerController
|
|||||||
@Override
|
@Override
|
||||||
public void processTransfer( Transfer transfer )
|
public void processTransfer( Transfer transfer )
|
||||||
{
|
{
|
||||||
if( transfer.status() == LibUsb.TRANSFER_COMPLETED )
|
mTransfersInProgress.remove( transfer );
|
||||||
{
|
|
||||||
ByteBuffer buffer = transfer.buffer();
|
|
||||||
|
|
||||||
byte[] data = new byte[ transfer.actualLength() ];
|
|
||||||
|
|
||||||
buffer.get( data );
|
|
||||||
|
|
||||||
buffer.rewind();
|
|
||||||
|
|
||||||
mFilledBuffers.add( data );
|
|
||||||
|
|
||||||
if( !isRunning() )
|
|
||||||
{
|
|
||||||
LibUsb.cancelTransfer( transfer );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch( transfer.status() )
|
switch( transfer.status() )
|
||||||
{
|
{
|
||||||
case LibUsb.TRANSFER_COMPLETED:
|
case LibUsb.TRANSFER_COMPLETED:
|
||||||
/* resubmit the transfer */
|
case LibUsb.TRANSFER_STALL:
|
||||||
int result = LibUsb.submitTransfer( transfer );
|
if( transfer.actualLength() > 0 )
|
||||||
|
|
||||||
if( result != LibUsb.SUCCESS )
|
|
||||||
{
|
{
|
||||||
mLog.error( "couldn't resubmit buffer transfer to tuner" );
|
ByteBuffer buffer = transfer.buffer();
|
||||||
LibUsb.freeTransfer( transfer );
|
|
||||||
mTransfers.remove( transfer );
|
byte[] data = new byte[ transfer.actualLength() ];
|
||||||
|
|
||||||
|
buffer.get( data );
|
||||||
|
|
||||||
|
buffer.rewind();
|
||||||
|
|
||||||
|
if( isRunning() )
|
||||||
|
{
|
||||||
|
mFilledBuffers.add( data );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case LibUsb.TRANSFER_CANCELLED:
|
case LibUsb.TRANSFER_CANCELLED:
|
||||||
/* free the transfer and remove it */
|
|
||||||
LibUsb.freeTransfer( transfer );
|
|
||||||
mTransfers.remove( transfer );
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
/* unexpected error */
|
/* unexpected error */
|
||||||
@@ -1036,57 +1061,38 @@ public class HackRFTunerController extends TunerController
|
|||||||
getTransferStatus( transfer.status() ) +
|
getTransferStatus( transfer.status() ) +
|
||||||
"] transferred actual: " + transfer.actualLength() );
|
"] transferred actual: " + transfer.actualLength() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mAvailableTransfers.add( transfer );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancel()
|
|
||||||
{
|
|
||||||
for( Transfer transfer: mTransfers )
|
|
||||||
{
|
|
||||||
LibUsb.cancelTransfer( transfer );
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteBuffer completed = ByteBuffer.allocateDirect( 4 );
|
|
||||||
|
|
||||||
int result = LibUsb.handleEventsTimeoutCompleted(
|
|
||||||
null, TIMEOUT_US, completed.asIntBuffer() );
|
|
||||||
|
|
||||||
if( result != LibUsb.SUCCESS )
|
|
||||||
{
|
|
||||||
mLog.error( "error handling usb events during cancel [" +
|
|
||||||
LibUsb.errorName( result ) + "]" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares (allocates) a set of transfer buffers for use in
|
* Prepares (allocates) a set of transfer buffers for use in
|
||||||
* transferring data from the tuner via the bulk interface
|
* transferring data from the tuner via the bulk interface
|
||||||
*/
|
*/
|
||||||
private void prepareTransfers() throws LibUsbException
|
private void prepareTransfers() throws LibUsbException
|
||||||
{
|
{
|
||||||
mTransfers = new CopyOnWriteArrayList<Transfer>();
|
if( mAvailableTransfers == null )
|
||||||
|
|
||||||
for( int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++ )
|
|
||||||
{
|
{
|
||||||
Transfer transfer = LibUsb.allocTransfer();
|
mAvailableTransfers = new LinkedTransferQueue<>();
|
||||||
|
|
||||||
if( transfer == null )
|
for( int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++ )
|
||||||
{
|
{
|
||||||
throw new LibUsbException( "couldn't allocate transfer",
|
Transfer transfer = LibUsb.allocTransfer();
|
||||||
LibUsb.ERROR_NO_MEM );
|
|
||||||
}
|
|
||||||
|
|
||||||
final ByteBuffer buffer =
|
if( transfer == null )
|
||||||
ByteBuffer.allocateDirect( mBufferSize );
|
{
|
||||||
|
throw new LibUsbException( "couldn't allocate transfer",
|
||||||
|
LibUsb.ERROR_NO_MEM );
|
||||||
|
}
|
||||||
|
|
||||||
LibUsb.fillBulkTransfer( transfer,
|
final ByteBuffer buffer =
|
||||||
mDeviceHandle,
|
ByteBuffer.allocateDirect( mBufferSize );
|
||||||
USB_ENDPOINT,
|
|
||||||
buffer,
|
|
||||||
BufferProcessor.this,
|
|
||||||
"Buffer #" + x,
|
|
||||||
TIMEOUT_US );
|
|
||||||
|
|
||||||
mTransfers.add( transfer );
|
LibUsb.fillBulkTransfer( transfer, mDeviceHandle,USB_ENDPOINT,
|
||||||
|
buffer, BufferProcessor.this, "Buffer", USB_TIMEOUT_MS );
|
||||||
|
|
||||||
|
mAvailableTransfers.add( transfer );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import source.tuner.TunerType;
|
|||||||
import source.tuner.frequency.FrequencyChangeListener;
|
import source.tuner.frequency.FrequencyChangeListener;
|
||||||
import source.tuner.rtl.RTL2832TunerController.SampleRate;
|
import source.tuner.rtl.RTL2832TunerController.SampleRate;
|
||||||
import controller.ResourceManager;
|
import controller.ResourceManager;
|
||||||
import controller.ThreadPoolManager;
|
|
||||||
|
|
||||||
public class RTL2832Tuner extends Tuner
|
public class RTL2832Tuner extends Tuner
|
||||||
{
|
{
|
||||||
@@ -148,16 +147,14 @@ public class RTL2832Tuner extends Tuner
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager,
|
public TunerChannelSource getChannel( TunerChannel channel )
|
||||||
TunerChannel channel )
|
throws RejectedExecutionException, SourceException
|
||||||
throws RejectedExecutionException,
|
|
||||||
SourceException
|
|
||||||
{
|
{
|
||||||
TunerChannelSource source = null;
|
TunerChannelSource source = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
source = mController.getChannel( threadPoolManager, this, channel );
|
source = mController.getChannel( this, channel );
|
||||||
}
|
}
|
||||||
catch( Exception e )
|
catch( Exception e )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import java.nio.charset.Charset;
|
|||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.List;
|
||||||
import java.util.concurrent.LinkedTransferQueue;
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -55,8 +55,6 @@ import sample.complex.ComplexBuffer;
|
|||||||
import source.SourceException;
|
import source.SourceException;
|
||||||
import source.tuner.TunerController;
|
import source.tuner.TunerController;
|
||||||
import source.tuner.TunerType;
|
import source.tuner.TunerType;
|
||||||
import source.tuner.frequency.FrequencyChangeEvent;
|
|
||||||
import source.tuner.frequency.FrequencyChangeEvent.Event;
|
|
||||||
import buffer.FloatAveragingBuffer;
|
import buffer.FloatAveragingBuffer;
|
||||||
import controller.ResourceManager;
|
import controller.ResourceManager;
|
||||||
import controller.ThreadPoolManager;
|
import controller.ThreadPoolManager;
|
||||||
@@ -73,7 +71,7 @@ public abstract class RTL2832TunerController extends TunerController
|
|||||||
public final static int TWO_TO_22_POWER = 4194304;
|
public final static int TWO_TO_22_POWER = 4194304;
|
||||||
|
|
||||||
public final static byte USB_INTERFACE = (byte)0x0;
|
public final static byte USB_INTERFACE = (byte)0x0;
|
||||||
public final static byte BULK_ENDPOINT_IN = (byte)0x81;
|
public final static byte USB_BULK_ENDPOINT = (byte)0x81;
|
||||||
public final static byte CONTROL_ENDPOINT_IN =
|
public final static byte CONTROL_ENDPOINT_IN =
|
||||||
(byte)( LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR );
|
(byte)( LibUsb.ENDPOINT_IN | LibUsb.REQUEST_TYPE_VENDOR );
|
||||||
public final static byte CONTROL_ENDPOINT_OUT =
|
public final static byte CONTROL_ENDPOINT_OUT =
|
||||||
@@ -1342,21 +1340,24 @@ public abstract class RTL2832TunerController extends TunerController
|
|||||||
*/
|
*/
|
||||||
public void addListener( Listener<ComplexBuffer> listener )
|
public void addListener( Listener<ComplexBuffer> listener )
|
||||||
{
|
{
|
||||||
mComplexBufferBroadcaster.addListener( listener );
|
synchronized( mComplexBufferBroadcaster )
|
||||||
|
{
|
||||||
|
mComplexBufferBroadcaster.addListener( listener );
|
||||||
|
|
||||||
if( mBufferProcessor == null )
|
if( mBufferProcessor == null )
|
||||||
{
|
{
|
||||||
mBufferProcessor = new BufferProcessor( mThreadPoolManager );
|
mBufferProcessor = new BufferProcessor( mThreadPoolManager );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( !mBufferProcessor.isRunning() )
|
if( !mBufferProcessor.isRunning() )
|
||||||
{
|
{
|
||||||
Thread thread = new Thread( mBufferProcessor );
|
Thread thread = new Thread( mBufferProcessor );
|
||||||
thread.setDaemon( true );
|
thread.setDaemon( true );
|
||||||
thread.setName( "RTL2832 Sample Processor" );
|
thread.setName( "RTL2832 Sample Processor" );
|
||||||
|
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1365,12 +1366,15 @@ public abstract class RTL2832TunerController extends TunerController
|
|||||||
*/
|
*/
|
||||||
public void removeListener( Listener<ComplexBuffer> listener )
|
public void removeListener( Listener<ComplexBuffer> listener )
|
||||||
{
|
{
|
||||||
mComplexBufferBroadcaster.removeListener( listener );
|
synchronized( mComplexBufferBroadcaster )
|
||||||
|
{
|
||||||
|
mComplexBufferBroadcaster.removeListener( listener );
|
||||||
|
|
||||||
if( !mComplexBufferBroadcaster.hasListeners() )
|
if( !mComplexBufferBroadcaster.hasListeners() )
|
||||||
{
|
{
|
||||||
mBufferProcessor.stop();
|
mBufferProcessor.stop();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1380,9 +1384,12 @@ public abstract class RTL2832TunerController extends TunerController
|
|||||||
public class BufferProcessor implements Runnable, TransferCallback
|
public class BufferProcessor implements Runnable, TransferCallback
|
||||||
{
|
{
|
||||||
private ScheduledFuture<?> mSampleDispatcherTask;
|
private ScheduledFuture<?> mSampleDispatcherTask;
|
||||||
private CopyOnWriteArrayList<Transfer> mTransfers;
|
private LinkedTransferQueue<Transfer> mAvailableTransfers;
|
||||||
|
private LinkedTransferQueue<Transfer> mTransfersInProgress = new LinkedTransferQueue<>();
|
||||||
private AtomicBoolean mRunning = new AtomicBoolean();
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||||
private ThreadPoolManager mThreadPoolManager;
|
private ThreadPoolManager mThreadPoolManager;
|
||||||
|
private ByteBuffer mLibUsbHandlerStatus;
|
||||||
|
private boolean mCancel = false;
|
||||||
|
|
||||||
public BufferProcessor( ThreadPoolManager manager )
|
public BufferProcessor( ThreadPoolManager manager )
|
||||||
{
|
{
|
||||||
@@ -1409,18 +1416,6 @@ public abstract class RTL2832TunerController extends TunerController
|
|||||||
|
|
||||||
prepareTransfers();
|
prepareTransfers();
|
||||||
|
|
||||||
for( Transfer transfer: mTransfers )
|
|
||||||
{
|
|
||||||
int result = LibUsb.submitTransfer( transfer );
|
|
||||||
|
|
||||||
if( result != LibUsb.SUCCESS )
|
|
||||||
{
|
|
||||||
mLog.error( "error submitting transfer [" +
|
|
||||||
LibUsb.errorName( result ) + "]" );
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
mSampleDispatcherTask = mThreadPoolManager.scheduleFixedRate(
|
mSampleDispatcherTask = mThreadPoolManager.scheduleFixedRate(
|
||||||
@@ -1432,19 +1427,65 @@ public abstract class RTL2832TunerController extends TunerController
|
|||||||
mLog.error( "NPE! = tpm null: " + ( mThreadPoolManager == null ), npe );
|
mLog.error( "NPE! = tpm null: " + ( mThreadPoolManager == null ), npe );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mLibUsbHandlerStatus = ByteBuffer.allocateDirect( 4 );
|
||||||
|
|
||||||
while( mRunning.get() )
|
List<Transfer> transfers = new ArrayList<>();
|
||||||
|
|
||||||
|
mCancel = false;
|
||||||
|
|
||||||
|
while( mRunning.get() )
|
||||||
{
|
{
|
||||||
ByteBuffer completed = ByteBuffer.allocateDirect( 4 );
|
mAvailableTransfers.drainTo( transfers );
|
||||||
|
|
||||||
|
for( Transfer transfer: transfers )
|
||||||
|
{
|
||||||
|
int result = LibUsb.submitTransfer( transfer );
|
||||||
|
|
||||||
|
if( result == LibUsb.SUCCESS )
|
||||||
|
{
|
||||||
|
mTransfersInProgress.add( transfer );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error( "Error submitting transfer [" +
|
||||||
|
LibUsb.errorName( result ) + "]" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int result = LibUsb.handleEventsTimeoutCompleted(
|
int result = LibUsb.handleEventsTimeoutCompleted(
|
||||||
null, TIMEOUT_US, completed.asIntBuffer() );
|
null, TIMEOUT_US, mLibUsbHandlerStatus.asIntBuffer() );
|
||||||
|
|
||||||
if( result != LibUsb.SUCCESS )
|
if( result != LibUsb.SUCCESS )
|
||||||
{
|
{
|
||||||
mLog.error( "error handling events for libusb" );
|
mLog.error( "error handling events for libusb" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transfers.clear();
|
||||||
|
|
||||||
|
mLibUsbHandlerStatus.rewind();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( mCancel )
|
||||||
|
{
|
||||||
|
for( Transfer transfer: mTransfersInProgress )
|
||||||
|
{
|
||||||
|
LibUsb.cancelTransfer( transfer );
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = LibUsb.handleEventsTimeoutCompleted(
|
||||||
|
null, TIMEOUT_US, mLibUsbHandlerStatus.asIntBuffer() );
|
||||||
|
|
||||||
|
if( result != LibUsb.SUCCESS )
|
||||||
|
{
|
||||||
|
mLog.error( "error handling events for libusb" );
|
||||||
|
}
|
||||||
|
|
||||||
|
mLibUsbHandlerStatus.rewind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error( "Couldn't start buffer processor - already running!" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1453,10 +1494,10 @@ public abstract class RTL2832TunerController extends TunerController
|
|||||||
*/
|
*/
|
||||||
public void stop()
|
public void stop()
|
||||||
{
|
{
|
||||||
|
mCancel = true;
|
||||||
|
|
||||||
if( mRunning.compareAndSet( true, false ) )
|
if( mRunning.compareAndSet( true, false ) )
|
||||||
{
|
{
|
||||||
cancel();
|
|
||||||
|
|
||||||
if( mSampleDispatcherTask != null )
|
if( mSampleDispatcherTask != null )
|
||||||
{
|
{
|
||||||
mThreadPoolManager.cancel( mSampleDispatcherTask );
|
mThreadPoolManager.cancel( mSampleDispatcherTask );
|
||||||
@@ -1477,43 +1518,29 @@ public abstract class RTL2832TunerController extends TunerController
|
|||||||
@Override
|
@Override
|
||||||
public void processTransfer( Transfer transfer )
|
public void processTransfer( Transfer transfer )
|
||||||
{
|
{
|
||||||
if( transfer.status() == LibUsb.TRANSFER_COMPLETED )
|
mTransfersInProgress.remove( transfer );
|
||||||
{
|
|
||||||
ByteBuffer buffer = transfer.buffer();
|
|
||||||
|
|
||||||
byte[] data = new byte[ transfer.actualLength() ];
|
|
||||||
|
|
||||||
buffer.get( data );
|
|
||||||
|
|
||||||
buffer.rewind();
|
|
||||||
|
|
||||||
mFilledBuffers.add( data );
|
|
||||||
|
|
||||||
mSampleCounter.addAndGet( transfer.actualLength() );
|
|
||||||
|
|
||||||
if( !isRunning() )
|
|
||||||
{
|
|
||||||
LibUsb.cancelTransfer( transfer );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch( transfer.status() )
|
switch( transfer.status() )
|
||||||
{
|
{
|
||||||
case LibUsb.TRANSFER_COMPLETED:
|
case LibUsb.TRANSFER_COMPLETED:
|
||||||
/* resubmit the transfer */
|
case LibUsb.TRANSFER_STALL:
|
||||||
int result = LibUsb.submitTransfer( transfer );
|
if( transfer.actualLength() > 0 )
|
||||||
|
|
||||||
if( result != LibUsb.SUCCESS )
|
|
||||||
{
|
{
|
||||||
mLog.error( "couldn't resubmit buffer transfer to tuner" );
|
ByteBuffer buffer = transfer.buffer();
|
||||||
LibUsb.freeTransfer( transfer );
|
|
||||||
mTransfers.remove( transfer );
|
byte[] data = new byte[ transfer.actualLength() ];
|
||||||
|
|
||||||
|
buffer.get( data );
|
||||||
|
|
||||||
|
buffer.rewind();
|
||||||
|
|
||||||
|
if( isRunning() )
|
||||||
|
{
|
||||||
|
mFilledBuffers.add( data );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case LibUsb.TRANSFER_CANCELLED:
|
case LibUsb.TRANSFER_CANCELLED:
|
||||||
/* free the transfer and remove it */
|
|
||||||
LibUsb.freeTransfer( transfer );
|
|
||||||
mTransfers.remove( transfer );
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
/* unexpected error */
|
/* unexpected error */
|
||||||
@@ -1521,57 +1548,38 @@ public abstract class RTL2832TunerController extends TunerController
|
|||||||
getTransferStatus( transfer.status() ) +
|
getTransferStatus( transfer.status() ) +
|
||||||
"] transferred actual: " + transfer.actualLength() );
|
"] transferred actual: " + transfer.actualLength() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mAvailableTransfers.add( transfer );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancel()
|
|
||||||
{
|
|
||||||
for( Transfer transfer: mTransfers )
|
|
||||||
{
|
|
||||||
LibUsb.cancelTransfer( transfer );
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteBuffer completed = ByteBuffer.allocateDirect( 4 );
|
|
||||||
|
|
||||||
int result = LibUsb.handleEventsTimeoutCompleted(
|
|
||||||
null, TIMEOUT_US, completed.asIntBuffer() );
|
|
||||||
|
|
||||||
if( result != LibUsb.SUCCESS )
|
|
||||||
{
|
|
||||||
mLog.error( "error handling usb events during cancel [" +
|
|
||||||
LibUsb.errorName( result ) + "]" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares (allocates) a set of transfer buffers for use in
|
* Prepares (allocates) a set of transfer buffers for use in
|
||||||
* transferring data from the tuner via the bulk interface
|
* transferring data from the tuner via the bulk interface
|
||||||
*/
|
*/
|
||||||
private void prepareTransfers() throws LibUsbException
|
private void prepareTransfers() throws LibUsbException
|
||||||
{
|
{
|
||||||
mTransfers = new CopyOnWriteArrayList<Transfer>();
|
if( mAvailableTransfers == null )
|
||||||
|
|
||||||
for( int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++ )
|
|
||||||
{
|
{
|
||||||
Transfer transfer = LibUsb.allocTransfer();
|
mAvailableTransfers = new LinkedTransferQueue<>();
|
||||||
|
|
||||||
if( transfer == null )
|
for( int x = 0; x < TRANSFER_BUFFER_POOL_SIZE; x++ )
|
||||||
{
|
{
|
||||||
throw new LibUsbException( "couldn't allocate transfer",
|
Transfer transfer = LibUsb.allocTransfer();
|
||||||
LibUsb.ERROR_NO_MEM );
|
|
||||||
}
|
|
||||||
|
|
||||||
final ByteBuffer buffer =
|
if( transfer == null )
|
||||||
ByteBuffer.allocateDirect( mBufferSize );
|
{
|
||||||
|
throw new LibUsbException( "couldn't allocate transfer",
|
||||||
|
LibUsb.ERROR_NO_MEM );
|
||||||
|
}
|
||||||
|
|
||||||
LibUsb.fillBulkTransfer( transfer,
|
final ByteBuffer buffer =
|
||||||
mDeviceHandle,
|
ByteBuffer.allocateDirect( mBufferSize );
|
||||||
BULK_ENDPOINT_IN,
|
|
||||||
buffer,
|
|
||||||
BufferProcessor.this,
|
|
||||||
"Buffer #" + x,
|
|
||||||
TIMEOUT_US );
|
|
||||||
|
|
||||||
mTransfers.add( transfer );
|
LibUsb.fillBulkTransfer( transfer, mDeviceHandle, USB_BULK_ENDPOINT,
|
||||||
|
buffer, BufferProcessor.this, "Buffer", TIMEOUT_US );
|
||||||
|
|
||||||
|
mAvailableTransfers.add( transfer );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public class R820TTunerConfiguration extends TunerConfiguration
|
|||||||
private R820TLNAGain mLNAGain = R820TLNAGain.GAIN_222;
|
private R820TLNAGain mLNAGain = R820TLNAGain.GAIN_222;
|
||||||
private R820TVGAGain mVGAGain = R820TVGAGain.GAIN_210;
|
private R820TVGAGain mVGAGain = R820TVGAGain.GAIN_210;
|
||||||
private double mFrequencyCorrection = 0.0d;
|
private double mFrequencyCorrection = 0.0d;
|
||||||
private SampleRate mSampleRate = SampleRate.RATE_1_920MHZ;
|
private SampleRate mSampleRate = SampleRate.RATE_2_400MHZ;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default constructor for JAXB
|
* Default constructor for JAXB
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package spectrum;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
import java.util.concurrent.ArrayBlockingQueue;
|
||||||
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
@@ -56,8 +57,12 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
|
|||||||
private CopyOnWriteArrayList<DFTResultsConverter> mListeners =
|
private CopyOnWriteArrayList<DFTResultsConverter> mListeners =
|
||||||
new CopyOnWriteArrayList<DFTResultsConverter>();
|
new CopyOnWriteArrayList<DFTResultsConverter>();
|
||||||
|
|
||||||
private ArrayBlockingQueue<ComplexBuffer> mQueue =
|
//Fixed size sample queue. We set the size at 12 based on the highest
|
||||||
new ArrayBlockingQueue<ComplexBuffer>( 200 );
|
//sample rate tuner (10 MHz) producing 153 frames per second and the lowest
|
||||||
|
//DFT processing frame rate of 12 frames per second. This means that the
|
||||||
|
//maximum size needed is 153 / 12 = 11 frames per DFT processing iteration
|
||||||
|
//so we set it at 12.
|
||||||
|
private BlockingQueue<ComplexBuffer> mQueue = new ArrayBlockingQueue<>( 12 );
|
||||||
|
|
||||||
private ScheduledExecutorService mScheduler = Executors
|
private ScheduledExecutorService mScheduler = Executors
|
||||||
.newScheduledThreadPool( 1, new NamingThreadFactory( "spectrum dft" ) );
|
.newScheduledThreadPool( 1, new NamingThreadFactory( "spectrum dft" ) );
|
||||||
@@ -246,15 +251,7 @@ public class DFTProcessor implements Listener<ComplexBuffer>,
|
|||||||
@Override
|
@Override
|
||||||
public void receive( ComplexBuffer sampleBuffer )
|
public void receive( ComplexBuffer sampleBuffer )
|
||||||
{
|
{
|
||||||
if( !mQueue.offer( sampleBuffer ) )
|
mQueue.offer( sampleBuffer );
|
||||||
{
|
|
||||||
mLog.error( "DFTProcessor - [" + mSampleType.toString()
|
|
||||||
+ "] queue is full, purging queue, "
|
|
||||||
+ "samples[" + sampleBuffer + "]" );
|
|
||||||
|
|
||||||
mQueue.clear();
|
|
||||||
mQueue.offer( sampleBuffer );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getNextBuffer()
|
private void getNextBuffer()
|
||||||
|
|||||||
Reference in New Issue
Block a user