mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-13 09:07:27 -05:00
Compare commits
9 Commits
v0.5.0-bet
...
v0.5.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4a85a4ed7 | ||
|
|
f61a415b43 | ||
|
|
29ed3b4553 | ||
|
|
a87309e792 | ||
|
|
ddb5b5ef87 | ||
|
|
7a58bf85b6 | ||
|
|
8707eb2c88 | ||
|
|
08499ac73f | ||
|
|
70170ff70f |
@@ -28,7 +28,7 @@ repositories {
|
|||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
|
|
||||||
version = '0.5.0-beta7'
|
version = '0.5.0'
|
||||||
sourceCompatibility = '17'
|
sourceCompatibility = '17'
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
|||||||
@@ -25,19 +25,15 @@ import io.github.dsheirer.audio.convert.MP3FrameTools;
|
|||||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import org.apache.commons.math3.util.FastMath;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
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;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration> extends AbstractAudioBroadcaster<T>
|
public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration> extends AbstractAudioBroadcaster<T>
|
||||||
{
|
{
|
||||||
@@ -255,7 +251,6 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
|||||||
public boolean canConnect()
|
public boolean canConnect()
|
||||||
{
|
{
|
||||||
BroadcastState state = getBroadcastState();
|
BroadcastState state = getBroadcastState();
|
||||||
|
|
||||||
return state != BroadcastState.CONNECTED && !state.isErrorState();
|
return state != BroadcastState.CONNECTED && !state.isErrorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,13 +26,6 @@ import io.github.dsheirer.properties.SystemProperties;
|
|||||||
import io.github.dsheirer.sample.Broadcaster;
|
import io.github.dsheirer.sample.Broadcaster;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.DirectoryStream;
|
import java.nio.file.DirectoryStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -48,6 +41,12 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.table.AbstractTableModel;
|
||||||
|
|
||||||
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
|
public class BroadcastModel extends AbstractTableModel implements Listener<AudioRecording>
|
||||||
{
|
{
|
||||||
@@ -58,7 +57,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
|
|
||||||
private static final String UNIQUE_NAME_REGEX = "(.*)\\((\\d*)\\)";
|
private static final String UNIQUE_NAME_REGEX = "(.*)\\((\\d*)\\)";
|
||||||
|
|
||||||
public static final int COLUMN_SERVER_ICON = 0;
|
public static final int COLUMN_BROADCAST_SERVER_TYPE = 0;
|
||||||
public static final int COLUMN_STREAM_NAME = 1;
|
public static final int COLUMN_STREAM_NAME = 1;
|
||||||
public static final int COLUMN_BROADCASTER_STATUS = 2;
|
public static final int COLUMN_BROADCASTER_STATUS = 2;
|
||||||
public static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
|
public static final int COLUMN_BROADCASTER_QUEUE_SIZE = 3;
|
||||||
@@ -67,7 +66,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
public static final int COLUMN_BROADCASTER_ERROR_COUNT = 6;
|
public static final int COLUMN_BROADCASTER_ERROR_COUNT = 6;
|
||||||
|
|
||||||
public static final String[] COLUMN_NAMES = new String[]
|
public static final String[] COLUMN_NAMES = new String[]
|
||||||
{"Streaming", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
|
{"Stream Type", "Name", "Status", "Queued", "Streamed/Uploaded", "Aged Off", "Upload Error"};
|
||||||
|
|
||||||
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
|
private ObservableList<ConfiguredBroadcast> mConfiguredBroadcasts =
|
||||||
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
|
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
|
||||||
@@ -575,15 +574,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
{
|
{
|
||||||
switch(columnIndex)
|
switch(columnIndex)
|
||||||
{
|
{
|
||||||
case COLUMN_SERVER_ICON:
|
case COLUMN_BROADCAST_SERVER_TYPE:
|
||||||
String iconPath = configuredBroadcast.getBroadcastConfiguration()
|
return configuredBroadcast.getBroadcastServerType();
|
||||||
.getBroadcastServerType().getIconPath();
|
|
||||||
|
|
||||||
if(iconPath != null && mIconModel != null)
|
|
||||||
{
|
|
||||||
return mIconModel.getScaledIcon(iconPath, 14);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case COLUMN_STREAM_NAME:
|
case COLUMN_STREAM_NAME:
|
||||||
return configuredBroadcast.getBroadcastConfiguration().getName();
|
return configuredBroadcast.getBroadcastConfiguration().getName();
|
||||||
case COLUMN_BROADCASTER_STATUS:
|
case COLUMN_BROADCASTER_STATUS:
|
||||||
@@ -653,8 +645,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
|||||||
case COLUMN_BROADCASTER_STREAMED_COUNT:
|
case COLUMN_BROADCASTER_STREAMED_COUNT:
|
||||||
case COLUMN_BROADCASTER_ERROR_COUNT:
|
case COLUMN_BROADCASTER_ERROR_COUNT:
|
||||||
return Integer.class;
|
return Integer.class;
|
||||||
case COLUMN_SERVER_ICON:
|
case COLUMN_BROADCAST_SERVER_TYPE:
|
||||||
return ImageIcon.class;
|
return BroadcastServerType.class;
|
||||||
case COLUMN_STREAM_NAME:
|
case COLUMN_STREAM_NAME:
|
||||||
default:
|
default:
|
||||||
return String.class;
|
return String.class;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* sdrtrunk
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
* Copyright (C) 2014-2022 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
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
*
|
*
|
||||||
* 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 io.github.dsheirer.audio.broadcast;
|
package io.github.dsheirer.audio.broadcast;
|
||||||
|
|
||||||
public enum BroadcastServerType
|
public enum BroadcastServerType
|
||||||
@@ -23,17 +23,17 @@ public enum BroadcastServerType
|
|||||||
/**
|
/**
|
||||||
* Broadcastify feeds (ie streaming) service
|
* Broadcastify feeds (ie streaming) service
|
||||||
*/
|
*/
|
||||||
BROADCASTIFY("Broadcastify Feed", "/images/broadcastify.png"), //Icecast Server 2.3.2
|
BROADCASTIFY("Broadcastify Feed", "images/broadcastify.png"), //Icecast Server 2.3.2
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcastify calls - completed audio recording push service
|
* Broadcastify calls - completed audio recording push service
|
||||||
*/
|
*/
|
||||||
BROADCASTIFY_CALL("Broadcastify Call", "/images/broadcastify.png"),
|
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
|
||||||
|
|
||||||
ICECAST_HTTP("Icecast 2 (v2.4+)", "/images/icecast.png"),
|
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
|
||||||
ICECAST_TCP("Icecast (v2.3)", "/images/icecast.png"),
|
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
|
||||||
SHOUTCAST_V1("Shoutcast v1.x", "/images/shoutcast.png"),
|
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
|
||||||
SHOUTCAST_V2("Shoutcast v2.x", "/images/shoutcast.png"),
|
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
|
||||||
UNKNOWN("Unknown", null);
|
UNKNOWN("Unknown", null);
|
||||||
|
|
||||||
private String mLabel;
|
private String mLabel;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*******************************************************************************
|
/*
|
||||||
* sdrtrunk
|
* *****************************************************************************
|
||||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
* Copyright (C) 2014-2022 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
|
||||||
@@ -14,8 +14,8 @@
|
|||||||
*
|
*
|
||||||
* 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 io.github.dsheirer.audio.broadcast;
|
package io.github.dsheirer.audio.broadcast;
|
||||||
|
|
||||||
public enum BroadcastState
|
public enum BroadcastState
|
||||||
@@ -68,7 +68,7 @@ public enum BroadcastState
|
|||||||
/**
|
/**
|
||||||
* Specified mount point is already in use
|
* Specified mount point is already in use
|
||||||
*/
|
*/
|
||||||
MOUNT_POINT_IN_USE("Mount Point In Use", false),
|
MOUNT_POINT_IN_USE("Mount Point In Use", true),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Network is unavailable or the server address cannot be resolved
|
* Network is unavailable or the server address cannot be resolved
|
||||||
@@ -103,10 +103,10 @@ public enum BroadcastState
|
|||||||
private String mLabel;
|
private String mLabel;
|
||||||
private boolean mErrorState;
|
private boolean mErrorState;
|
||||||
|
|
||||||
private BroadcastState(String label, boolean connected)
|
BroadcastState(String label, boolean errorState)
|
||||||
{
|
{
|
||||||
mLabel = label;
|
mLabel = label;
|
||||||
mErrorState = connected;
|
mErrorState = errorState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString()
|
public String toString()
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* ******************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2019 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
|
||||||
@@ -15,23 +14,29 @@
|
|||||||
*
|
*
|
||||||
* 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 io.github.dsheirer.audio.broadcast;
|
package io.github.dsheirer.audio.broadcast;
|
||||||
|
|
||||||
|
import io.github.dsheirer.icon.Icon;
|
||||||
|
import io.github.dsheirer.icon.IconModel;
|
||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JScrollPane;
|
import javax.swing.JScrollPane;
|
||||||
import javax.swing.JTable;
|
import javax.swing.JTable;
|
||||||
import javax.swing.SwingConstants;
|
import javax.swing.SwingConstants;
|
||||||
import javax.swing.table.DefaultTableCellRenderer;
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Component;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table of broadcast streams and statuses.
|
||||||
|
*/
|
||||||
public class BroadcastStatusPanel extends JPanel
|
public class BroadcastStatusPanel extends JPanel
|
||||||
{
|
{
|
||||||
private JTable mTable;
|
private JTable mTable;
|
||||||
@@ -41,6 +46,12 @@ public class BroadcastStatusPanel extends JPanel
|
|||||||
private UserPreferences mUserPreferences;
|
private UserPreferences mUserPreferences;
|
||||||
private String mPreferenceKey;
|
private String mPreferenceKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance
|
||||||
|
* @param broadcastModel to access the streams
|
||||||
|
* @param userPreferences for configuring the panel
|
||||||
|
* @param preferenceKey to store column preferences for this panel.
|
||||||
|
*/
|
||||||
public BroadcastStatusPanel(BroadcastModel broadcastModel, UserPreferences userPreferences, String preferenceKey)
|
public BroadcastStatusPanel(BroadcastModel broadcastModel, UserPreferences userPreferences, String preferenceKey)
|
||||||
{
|
{
|
||||||
mBroadcastModel = broadcastModel;
|
mBroadcastModel = broadcastModel;
|
||||||
@@ -65,6 +76,7 @@ public class BroadcastStatusPanel extends JPanel
|
|||||||
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
|
||||||
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
|
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCASTER_STATUS).setCellRenderer(new StatusCellRenderer());
|
||||||
|
mTable.getColumnModel().getColumn(BroadcastModel.COLUMN_BROADCAST_SERVER_TYPE).setCellRenderer(new ServerTypeRenderer());
|
||||||
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, mPreferenceKey);
|
mColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, mPreferenceKey);
|
||||||
|
|
||||||
mScrollPane = new JScrollPane(mTable);
|
mScrollPane = new JScrollPane(mTable);
|
||||||
@@ -72,6 +84,37 @@ public class BroadcastStatusPanel extends JPanel
|
|||||||
add(mScrollPane);
|
add(mScrollPane);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ServerTypeRenderer extends DefaultTableCellRenderer
|
||||||
|
{
|
||||||
|
public ServerTypeRenderer()
|
||||||
|
{
|
||||||
|
setOpaque(true);
|
||||||
|
setHorizontalAlignment(SwingConstants.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
|
||||||
|
{
|
||||||
|
JLabel component = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||||
|
|
||||||
|
if(value instanceof BroadcastServerType broadcastServerType)
|
||||||
|
{
|
||||||
|
component.setText(broadcastServerType.toString());
|
||||||
|
Icon icon = new Icon("empty", broadcastServerType.getIconPath());
|
||||||
|
ImageIcon imageIcon = icon.getIcon();
|
||||||
|
ImageIcon scaledIcon = IconModel.getScaledIcon(imageIcon, 13);
|
||||||
|
component.setIcon(scaledIcon);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
component.setText(null);
|
||||||
|
component.setIcon(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom cell renderer for the broadcast state column.
|
* Custom cell renderer for the broadcast state column.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -20,13 +20,20 @@ package io.github.dsheirer.audio.broadcast.icecast;
|
|||||||
|
|
||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastMetadata;
|
|
||||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||||
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
||||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.properties.SystemProperties;
|
import io.github.dsheirer.properties.SystemProperties;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import org.apache.mina.core.RuntimeIoException;
|
import org.apache.mina.core.RuntimeIoException;
|
||||||
import org.apache.mina.core.buffer.IoBuffer;
|
import org.apache.mina.core.buffer.IoBuffer;
|
||||||
import org.apache.mina.core.future.ConnectFuture;
|
import org.apache.mina.core.future.ConnectFuture;
|
||||||
@@ -42,13 +49,6 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.net.ConnectException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||||
{
|
{
|
||||||
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
|
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
|
||||||
@@ -143,8 +143,9 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
mSocketConnector = new NioSocketConnector();
|
mSocketConnector = new NioSocketConnector();
|
||||||
mSocketConnector.setConnectTimeoutCheckInterval(10000);
|
mSocketConnector.setConnectTimeoutCheckInterval(10000);
|
||||||
|
|
||||||
// mSocketConnector.getFilterChain().addLast("logger",
|
// LoggingFilter loggingFilter = new LoggingFilter(IcecastHTTPAudioBroadcaster.class);
|
||||||
// new LoggingFilter(IcecastHTTPAudioBroadcaster.class));
|
// loggingFilter.setMessageReceivedLogLevel(LogLevel.DEBUG);
|
||||||
|
// mSocketConnector.getFilterChain().addLast("logger", loggingFilter);
|
||||||
|
|
||||||
mSocketConnector.getFilterChain().addLast("codec", new HttpClientCodec());
|
mSocketConnector.getFilterChain().addLast("codec", new HttpClientCodec());
|
||||||
mSocketConnector.setHandler(new IcecastHTTPIOHandler());
|
mSocketConnector.setHandler(new IcecastHTTPIOHandler());
|
||||||
@@ -315,10 +316,49 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
* intercept the out of bounds exception and then inspect the message hex dump to see if it's an
|
* intercept the out of bounds exception and then inspect the message hex dump to see if it's an
|
||||||
* OK response and then play like everything is well and good.
|
* OK response and then play like everything is well and good.
|
||||||
*/
|
*/
|
||||||
if(cause instanceof ArrayIndexOutOfBoundsException &&
|
if(cause instanceof ArrayIndexOutOfBoundsException)
|
||||||
((ProtocolDecoderException)throwable).getHexdump().startsWith(HTTP_1_0_OK_HEX_DUMP))
|
|
||||||
{
|
{
|
||||||
setBroadcastState(BroadcastState.CONNECTED);
|
String hexDump = ((ProtocolDecoderException)throwable).getHexdump();
|
||||||
|
|
||||||
|
if(hexDump.startsWith(HTTP_1_0_OK_HEX_DUMP))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HttpDumpMessage message = new HttpDumpMessage(hexDump);
|
||||||
|
|
||||||
|
if(message.hasHttpResponseCode())
|
||||||
|
{
|
||||||
|
switch(message.getHttpResponseCode())
|
||||||
|
{
|
||||||
|
case 403: //Forbidden
|
||||||
|
if(message.toString().contains("Mountpoint in use"))
|
||||||
|
{
|
||||||
|
mLog.error("Stream [" + getStreamName() + "] - unable to connect - mountpoint in use");
|
||||||
|
setBroadcastState(BroadcastState.MOUNT_POINT_IN_USE);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mLog.error("String [" + getStreamName() + "] - HTTP protocol decoder error - message:\n\n" + message);
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.error("HTTP protocol decoder error - message:\n\n" + message);
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -374,4 +414,97 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class for parsing HTTP response messages from Icecast 2.4.2+
|
||||||
|
*/
|
||||||
|
public class HttpDumpMessage
|
||||||
|
{
|
||||||
|
private String mHexDump;
|
||||||
|
private String mMessage;
|
||||||
|
private int mHttpResponseCode = -1;
|
||||||
|
|
||||||
|
public HttpDumpMessage(String hexDump)
|
||||||
|
{
|
||||||
|
mHexDump = hexDump;
|
||||||
|
|
||||||
|
String[] split = mHexDump.split(" ");
|
||||||
|
byte[] bytes = new byte[split.length];
|
||||||
|
|
||||||
|
int pointer = 0;
|
||||||
|
for(String a : split)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int value = Integer.parseInt(a, 16);
|
||||||
|
bytes[pointer++] = (byte) (value & 0xFF);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
pointer++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mMessage = new String(bytes);
|
||||||
|
|
||||||
|
Pattern pattern = Pattern.compile("HTTP/1.0 (\\d{3})");
|
||||||
|
Matcher m = pattern.matcher(mMessage);
|
||||||
|
|
||||||
|
if(m.find())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mHttpResponseCode = Integer.parseInt(m.group(1));
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
mLog.error("Unable to parse HTTP response code that was matched from message: " + m.group(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if an HTTP response code was parsed from the message
|
||||||
|
* @return true if it was parsed.
|
||||||
|
*/
|
||||||
|
public boolean hasHttpResponseCode()
|
||||||
|
{
|
||||||
|
return mHttpResponseCode != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response code parsed from the hexdump message
|
||||||
|
* @return value.
|
||||||
|
*/
|
||||||
|
public int getHttpResponseCode()
|
||||||
|
{
|
||||||
|
return mHttpResponseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
String text = "HTTP/1.0 403 Forbidden";
|
||||||
|
|
||||||
|
Pattern pattern = Pattern.compile("HTTP/1.0 (\\d{3})");
|
||||||
|
|
||||||
|
Matcher m = pattern.matcher(text);
|
||||||
|
|
||||||
|
if(m.find())
|
||||||
|
{
|
||||||
|
mLog.info("Matching Group Count: " + m.groupCount());
|
||||||
|
mLog.info("Match 0: " + m.group(0));
|
||||||
|
mLog.info("Match 1: " + m.group(1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mLog.info("No Match");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,19 @@ package io.github.dsheirer.audio.broadcast.icecast;
|
|||||||
import io.github.dsheirer.alias.AliasModel;
|
import io.github.dsheirer.alias.AliasModel;
|
||||||
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
import io.github.dsheirer.audio.broadcast.BroadcastState;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory;
|
import io.github.dsheirer.audio.broadcast.icecast.codec.IcecastCodecFactory;
|
||||||
import io.github.dsheirer.audio.broadcast.icecast.IcecastMetadata;
|
|
||||||
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
import io.github.dsheirer.audio.convert.InputAudioFormat;
|
||||||
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
import io.github.dsheirer.audio.convert.MP3AudioConverter;
|
||||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||||
import io.github.dsheirer.properties.SystemProperties;
|
import io.github.dsheirer.properties.SystemProperties;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.nio.channels.UnresolvedAddressException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import org.apache.mina.core.RuntimeIoException;
|
import org.apache.mina.core.RuntimeIoException;
|
||||||
import org.apache.mina.core.future.ConnectFuture;
|
import org.apache.mina.core.future.ConnectFuture;
|
||||||
import org.apache.mina.core.service.IoHandlerAdapter;
|
import org.apache.mina.core.service.IoHandlerAdapter;
|
||||||
@@ -37,14 +43,6 @@ import org.apache.mina.transport.socket.nio.NioSocketConnector;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.nio.channels.UnresolvedAddressException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||||
{
|
{
|
||||||
private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class);
|
private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class);
|
||||||
@@ -215,7 +213,13 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
setBroadcastState(BroadcastState.DISCONNECTED);
|
//Only set broadcast state to disconnected if it's not already in an error state, to prevent a restart. We
|
||||||
|
//want to preserve the error state that got us here, so the user can see it.
|
||||||
|
if(!getBroadcastState().isErrorState())
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.DISCONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
mLastConnectionAttempt = System.currentTimeMillis();
|
mLastConnectionAttempt = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
package io.github.dsheirer.audio.convert;
|
package io.github.dsheirer.audio.convert;
|
||||||
|
|
||||||
import io.github.dsheirer.audio.AudioFormats;
|
import io.github.dsheirer.audio.AudioFormats;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
import javax.sound.sampled.AudioFormat;
|
import javax.sound.sampled.AudioFormat;
|
||||||
|
|
||||||
@@ -29,8 +30,8 @@ import javax.sound.sampled.AudioFormat;
|
|||||||
public enum InputAudioFormat
|
public enum InputAudioFormat
|
||||||
{
|
{
|
||||||
SR_8000(AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO, "16-Bit 8000 Hz (no resample)"),
|
SR_8000(AudioFormats.PCM_SIGNED_8000_HZ_16_BIT_MONO, "16-Bit 8000 Hz (no resample)"),
|
||||||
SR_16000(AudioFormats.PCM_SIGNED_16000_HZ_16_BIT_MONO, "16-Bit 16000 Hz"),
|
SR_16000(AudioFormats.PCM_SIGNED_16000_HZ_16_BIT_MONO, "16-Bit 16000 Hz (default)"),
|
||||||
SR_22050(AudioFormats.PCM_SIGNED_22050_HZ_16_BIT_MONO, "16-Bit 22050 Hz (default)"),
|
SR_22050(AudioFormats.PCM_SIGNED_22050_HZ_16_BIT_MONO, "16-Bit 22050 Hz"),
|
||||||
SR_44100(AudioFormats.PCM_SIGNED_44100_HZ_16_BIT_MONO, "16-Bit 44100 Hz"),
|
SR_44100(AudioFormats.PCM_SIGNED_44100_HZ_16_BIT_MONO, "16-Bit 44100 Hz"),
|
||||||
|
|
||||||
SR_32_8000(AudioFormats.PCM_SIGNED_8000_HZ_32_BIT_MONO, "32-Bit 8000 Hz (no resample)"),
|
SR_32_8000(AudioFormats.PCM_SIGNED_8000_HZ_32_BIT_MONO, "32-Bit 8000 Hz (no resample)"),
|
||||||
@@ -41,6 +42,17 @@ public enum InputAudioFormat
|
|||||||
private AudioFormat mAudioFormat;
|
private AudioFormat mAudioFormat;
|
||||||
private String mLabel;
|
private String mLabel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set that includes sample rates of 8 or 16 kHz
|
||||||
|
*/
|
||||||
|
public static final EnumSet<InputAudioFormat> SAMPLE_RATES_8_16 = EnumSet.of(SR_8000, SR_32_8000, SR_16000, SR_32_16000);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set that includes sample rates of 8, 16, or 22 kHz
|
||||||
|
*/
|
||||||
|
public static final EnumSet<InputAudioFormat> SAMPLE_RATES_8_16_22 = EnumSet.of(SR_8000, SR_32_8000, SR_16000,
|
||||||
|
SR_32_16000, SR_22050, SR_32_22050);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance
|
* Constructs an instance
|
||||||
* @param audioFormat for the specified sample rate
|
* @param audioFormat for the specified sample rate
|
||||||
@@ -56,7 +68,7 @@ public enum InputAudioFormat
|
|||||||
*/
|
*/
|
||||||
public static InputAudioFormat getDefault()
|
public static InputAudioFormat getDefault()
|
||||||
{
|
{
|
||||||
return SR_22050;
|
return SR_16000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -57,19 +57,20 @@ public class MP3AudioConverter implements IAudioConverter
|
|||||||
public MP3AudioConverter(InputAudioFormat inputAudioFormat, MP3Setting setting, boolean normalizeAudio)
|
public MP3AudioConverter(InputAudioFormat inputAudioFormat, MP3Setting setting, boolean normalizeAudio)
|
||||||
{
|
{
|
||||||
mInputAudioFormat = inputAudioFormat;
|
mInputAudioFormat = inputAudioFormat;
|
||||||
|
|
||||||
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
|
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
|
||||||
if(inputAudioFormat.getSampleRate() != mEncoder.getEffectiveSampleRate())
|
|
||||||
{
|
|
||||||
mLog.warn("MP3 encoder setting '" + setting + "' doesn't support sample rate '" + inputAudioFormat + "', resampling disabled. Increase bitrate or decrease sample rate to fix.");
|
|
||||||
mInputAudioFormat = InputAudioFormat.SR_8000;
|
|
||||||
mEncoder = LameFactory.getLameEncoder(mInputAudioFormat, setting);
|
|
||||||
}
|
|
||||||
|
|
||||||
mNormalizeAudio = normalizeAudio;
|
mNormalizeAudio = normalizeAudio;
|
||||||
|
|
||||||
|
//Ensure input audio sample rate is supported for the MP3 setting and update as necessary - should never happen.
|
||||||
|
if((mInputAudioFormat.getSampleRate() - mEncoder.getEffectiveSampleRate()) > 1.0)
|
||||||
|
{
|
||||||
|
mInputAudioFormat = InputAudioFormat.getDefault();
|
||||||
|
mLog.warn("MP3 setting [" + setting + "] does not support requested input audio sample rate [" +
|
||||||
|
inputAudioFormat + "] - using default sample rate [" + mInputAudioFormat + "]");
|
||||||
|
mEncoder = LameFactory.getLameEncoder(inputAudioFormat, setting);
|
||||||
|
}
|
||||||
|
|
||||||
//Resampling is only required if desired input sample rate is not system default of 8kHz
|
//Resampling is only required if desired input sample rate is not system default of 8kHz
|
||||||
if(inputAudioFormat != InputAudioFormat.SR_8000 && inputAudioFormat != InputAudioFormat.SR_32_8000)
|
if(mInputAudioFormat != InputAudioFormat.SR_8000 && mInputAudioFormat != InputAudioFormat.SR_32_8000)
|
||||||
{
|
{
|
||||||
mResampler = LameFactory.getResampler(inputAudioFormat);
|
mResampler = LameFactory.getResampler(inputAudioFormat);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ public class MP3Header
|
|||||||
*/
|
*/
|
||||||
public static boolean isPadded(byte[] frame, int offset)
|
public static boolean isPadded(byte[] frame, int offset)
|
||||||
{
|
{
|
||||||
return (frame[offset + 2] & 0x01) == 1;
|
return (frame[offset + 2] & 0x02) == 0x02;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
package io.github.dsheirer.audio.convert;
|
package io.github.dsheirer.audio.convert;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import net.sourceforge.lame.mp3.Lame;
|
import net.sourceforge.lame.mp3.Lame;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,6 +81,30 @@ public enum MP3Setting
|
|||||||
return mSetting;
|
return mSetting;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Input audio formats that are supported by the MP3Setting value.
|
||||||
|
* @return list of supported input audio sample rates.
|
||||||
|
*/
|
||||||
|
public List<InputAudioFormat> getSupportedSampleRates()
|
||||||
|
{
|
||||||
|
switch(this)
|
||||||
|
{
|
||||||
|
case CBR_16:
|
||||||
|
{
|
||||||
|
return InputAudioFormat.SAMPLE_RATES_8_16.stream().toList();
|
||||||
|
}
|
||||||
|
case CBR_32:
|
||||||
|
case VBR_7:
|
||||||
|
{
|
||||||
|
return InputAudioFormat.SAMPLE_RATES_8_16_22.stream().toList();
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
return Arrays.stream(InputAudioFormat.values()).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ import io.github.dsheirer.rrapi.type.Type;
|
|||||||
import io.github.dsheirer.rrapi.type.Voice;
|
import io.github.dsheirer.rrapi.type.Voice;
|
||||||
import io.github.dsheirer.service.radioreference.RadioReference;
|
import io.github.dsheirer.service.radioreference.RadioReference;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.binding.Bindings;
|
import javafx.beans.binding.Bindings;
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
@@ -55,12 +60,6 @@ import jiconfont.javafx.IconNode;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Radio Reference editor for trunked radio systems
|
* Radio Reference editor for trunked radio systems
|
||||||
*/
|
*/
|
||||||
@@ -293,7 +292,13 @@ public class SystemEditor extends VBox
|
|||||||
List<EnrichedSite> enrichedSites = new ArrayList<>();
|
List<EnrichedSite> enrichedSites = new ArrayList<>();
|
||||||
for(Site site: sites)
|
for(Site site: sites)
|
||||||
{
|
{
|
||||||
CountyInfo countyInfo = mRadioReference.getService().getCountyInfo(site.getCountyId());
|
CountyInfo countyInfo = null;
|
||||||
|
|
||||||
|
if(site.getCountyId() > 0)
|
||||||
|
{
|
||||||
|
countyInfo = mRadioReference.getService().getCountyInfo(site.getCountyId());
|
||||||
|
}
|
||||||
|
|
||||||
enrichedSites.add(new EnrichedSite(site, countyInfo));
|
enrichedSites.add(new EnrichedSite(site, countyInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import io.github.dsheirer.preference.UserPreferences;
|
|||||||
import io.github.dsheirer.preference.mp3.MP3Preference;
|
import io.github.dsheirer.preference.mp3.MP3Preference;
|
||||||
import javafx.geometry.HPos;
|
import javafx.geometry.HPos;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
import javafx.scene.control.CheckBox;
|
import javafx.scene.control.CheckBox;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
@@ -99,12 +100,47 @@ public class MP3PreferenceEditor extends HBox
|
|||||||
mMP3SettingComboBox.getItems().addAll(MP3Setting.values());
|
mMP3SettingComboBox.getItems().addAll(MP3Setting.values());
|
||||||
mMP3SettingComboBox.getSelectionModel().select(mMP3Preference.getMP3Setting());
|
mMP3SettingComboBox.getSelectionModel().select(mMP3Preference.getMP3Setting());
|
||||||
mMP3SettingComboBox.getSelectionModel().selectedItemProperty()
|
mMP3SettingComboBox.getSelectionModel().selectedItemProperty()
|
||||||
.addListener((observable, oldValue, newValue) -> mMP3Preference.setMP3Setting(newValue));
|
.addListener((observable, oldValue, newValue) -> {
|
||||||
|
mMP3Preference.setMP3Setting(newValue);
|
||||||
|
updateAudioSampleRateComboBox();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return mMP3SettingComboBox;
|
return mMP3SettingComboBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the input sample rate combo box values and alerts the user whenever we auto-change an input sample rate
|
||||||
|
* due to a change in the MP3 setting value.
|
||||||
|
*/
|
||||||
|
private void updateAudioSampleRateComboBox()
|
||||||
|
{
|
||||||
|
InputAudioFormat currentSelection = getAudioSampleRateComboBox().getSelectionModel().getSelectedItem();
|
||||||
|
MP3Setting setting = getMP3SettingComboBox().getSelectionModel().getSelectedItem();
|
||||||
|
|
||||||
|
getAudioSampleRateComboBox().getItems().clear();
|
||||||
|
getAudioSampleRateComboBox().getItems().addAll(setting.getSupportedSampleRates());
|
||||||
|
|
||||||
|
if(currentSelection != null && getAudioSampleRateComboBox().getItems().contains(currentSelection))
|
||||||
|
{
|
||||||
|
getAudioSampleRateComboBox().getSelectionModel().select(currentSelection);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getAudioSampleRateComboBox().getSelectionModel().select(InputAudioFormat.getDefault());
|
||||||
|
|
||||||
|
Alert alert = new Alert(Alert.AlertType.INFORMATION);
|
||||||
|
alert.setTitle("Sample Rate Updated");
|
||||||
|
alert.setHeaderText("Input Sample Rate Updated");
|
||||||
|
|
||||||
|
Label wrappingLabel = new Label("Previous input sample rate [" + currentSelection +
|
||||||
|
"] is not supported with encoder setting [" + setting + "]. Sample rate updated to default.");
|
||||||
|
wrappingLabel.setWrapText(true);
|
||||||
|
alert.getDialogPane().setContent(wrappingLabel);
|
||||||
|
alert.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private ComboBox<InputAudioFormat> getAudioSampleRateComboBox()
|
private ComboBox<InputAudioFormat> getAudioSampleRateComboBox()
|
||||||
{
|
{
|
||||||
if(mAudioSampleRateComboBox == null)
|
if(mAudioSampleRateComboBox == null)
|
||||||
@@ -113,7 +149,12 @@ public class MP3PreferenceEditor extends HBox
|
|||||||
mAudioSampleRateComboBox.getItems().addAll(InputAudioFormat.values());
|
mAudioSampleRateComboBox.getItems().addAll(InputAudioFormat.values());
|
||||||
mAudioSampleRateComboBox.getSelectionModel().select(mMP3Preference.getAudioSampleRate());
|
mAudioSampleRateComboBox.getSelectionModel().select(mMP3Preference.getAudioSampleRate());
|
||||||
mAudioSampleRateComboBox.getSelectionModel().selectedItemProperty()
|
mAudioSampleRateComboBox.getSelectionModel().selectedItemProperty()
|
||||||
.addListener((observable, oldValue, newValue) -> mMP3Preference.setAudioSampleRate(newValue));
|
.addListener((observable, oldValue, newValue) -> {
|
||||||
|
if(newValue != null)
|
||||||
|
{
|
||||||
|
mMP3Preference.setAudioSampleRate(newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return mAudioSampleRateComboBox;
|
return mAudioSampleRateComboBox;
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* *********************************************************************************************************************
|
* *****************************************************************************
|
||||||
* sdrtrunk
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
* it under the terms of the GNU General Public License as published by
|
||||||
* later version.
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
*
|
*
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
* This program is distributed in the hope that it will be useful,
|
||||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License along with this program.
|
* You should have received a copy of the GNU General Public License
|
||||||
* If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
* *********************************************************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.icon;
|
package io.github.dsheirer.icon;
|
||||||
|
|
||||||
@@ -22,15 +24,6 @@ import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
|
|||||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||||
import io.github.dsheirer.properties.SystemProperties;
|
import io.github.dsheirer.properties.SystemProperties;
|
||||||
import io.github.dsheirer.util.ThreadPool;
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ListChangeListener;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.swing.ImageIcon;
|
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -43,6 +36,15 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringProperty;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
|
||||||
public class IconModel
|
public class IconModel
|
||||||
{
|
{
|
||||||
@@ -241,7 +243,6 @@ public class IconModel
|
|||||||
if(path != null)
|
if(path != null)
|
||||||
{
|
{
|
||||||
Icon icon = new Icon("", path);
|
Icon icon = new Icon("", path);
|
||||||
|
|
||||||
return getScaledIcon(icon.getIcon(), height);
|
return getScaledIcon(icon.getIcon(), height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.module.decode.event;
|
package io.github.dsheirer.module.decode.event;
|
||||||
|
|
||||||
@@ -30,13 +27,13 @@ import io.github.dsheirer.module.decode.event.filter.DecodeEventFilterSet;
|
|||||||
import io.github.dsheirer.module.decode.event.filter.EventFilterProvider;
|
import io.github.dsheirer.module.decode.event.filter.EventFilterProvider;
|
||||||
import io.github.dsheirer.preference.PreferenceType;
|
import io.github.dsheirer.preference.PreferenceType;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class DecodeEventModel extends AbstractTableModel implements Listener<IDecodeEvent>, EventFilterProvider<IDecodeEvent>
|
public class DecodeEventModel extends AbstractTableModel implements Listener<IDecodeEvent>, EventFilterProvider<IDecodeEvent>
|
||||||
{
|
{
|
||||||
@@ -149,10 +146,12 @@ public class DecodeEventModel extends AbstractTableModel implements Listener<IDe
|
|||||||
*/
|
*/
|
||||||
public void receive(final IDecodeEvent event)
|
public void receive(final IDecodeEvent event)
|
||||||
{
|
{
|
||||||
if (!mEventFilterSet.passes(event))
|
//Disabled until #1368 decode event is fully implemented
|
||||||
{
|
// if (!mEventFilterSet.passes(event))
|
||||||
return;
|
// {
|
||||||
}
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
if(!mEvents.contains(event))
|
if(!mEvents.contains(event))
|
||||||
{
|
{
|
||||||
mEvents.add(0, event);
|
mEvents.add(0, event);
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
|
* *****************************************************************************
|
||||||
|
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||||
*
|
*
|
||||||
* * ******************************************************************************
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
* it under the terms of the GNU General Public License as published by
|
||||||
* *
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
* * This program is free software: you can redistribute it and/or modify
|
* (at your option) any later version.
|
||||||
* * it under the terms of the GNU General Public License as published by
|
|
||||||
* * the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* * (at your option) any later version.
|
|
||||||
* *
|
|
||||||
* * This program is distributed in the hope that it will be useful,
|
|
||||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* * GNU General Public License for more details.
|
|
||||||
* *
|
|
||||||
* * You should have received a copy of the GNU General Public License
|
|
||||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
|
||||||
* * *****************************************************************************
|
|
||||||
*
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
*
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
package io.github.dsheirer.module.decode.event;
|
package io.github.dsheirer.module.decode.event;
|
||||||
|
|
||||||
@@ -44,18 +41,24 @@ import io.github.dsheirer.preference.PreferenceType;
|
|||||||
import io.github.dsheirer.preference.UserPreferences;
|
import io.github.dsheirer.preference.UserPreferences;
|
||||||
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
import io.github.dsheirer.preference.swing.JTableColumnWidthMonitor;
|
||||||
import io.github.dsheirer.sample.Listener;
|
import io.github.dsheirer.sample.Listener;
|
||||||
import net.miginfocom.swing.MigLayout;
|
import java.awt.Color;
|
||||||
import org.slf4j.Logger;
|
import java.awt.Component;
|
||||||
import org.slf4j.LoggerFactory;
|
import java.awt.EventQueue;
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import javax.swing.table.DefaultTableCellRenderer;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import net.miginfocom.swing.MigLayout;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
|
|
||||||
public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain>
|
public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain>
|
||||||
{
|
{
|
||||||
@@ -94,7 +97,8 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
|||||||
mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
|
mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
|
||||||
updateCellRenderers();
|
updateCellRenderers();
|
||||||
|
|
||||||
add(mEventManagementPanel, "span,growx");
|
//Disabled until #1368 decode event is fully implemented
|
||||||
|
// add(mEventManagementPanel, "span,growx");
|
||||||
|
|
||||||
mEmptyScroller = new JScrollPane(mTable);
|
mEmptyScroller = new JScrollPane(mTable);
|
||||||
add(mEmptyScroller);
|
add(mEmptyScroller);
|
||||||
|
|||||||
@@ -116,6 +116,24 @@ public class MP3Preference extends Preference
|
|||||||
{
|
{
|
||||||
mInputAudioFormat = InputAudioFormat.getDefault();
|
mInputAudioFormat = InputAudioFormat.getDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Ensure the input sample rate is supported by the MP3 setting, or change it to the first supported rate.
|
||||||
|
MP3Setting setting = getMP3Setting();
|
||||||
|
|
||||||
|
if(!setting.getSupportedSampleRates().contains(mInputAudioFormat))
|
||||||
|
{
|
||||||
|
InputAudioFormat supportedAudioFormat = InputAudioFormat.getDefault();
|
||||||
|
|
||||||
|
//Default should always be supported by all mp3 settings, but fall-back to a supported rate if not.
|
||||||
|
if(!setting.getSupportedSampleRates().contains(supportedAudioFormat))
|
||||||
|
{
|
||||||
|
supportedAudioFormat = setting.getSupportedSampleRates().get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.warn("MP3 Setting [" + setting + "] does not support input sample rate [" + getAudioSampleRate().name() +
|
||||||
|
"] - updating to supported sample rate [" + supportedAudioFormat.name() + "]");
|
||||||
|
setAudioSampleRate(supportedAudioFormat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mInputAudioFormat;
|
return mInputAudioFormat;
|
||||||
|
|||||||
@@ -24,11 +24,10 @@ import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
|||||||
import io.github.dsheirer.source.tuner.Tuner;
|
import io.github.dsheirer.source.tuner.Tuner;
|
||||||
import io.github.dsheirer.source.tuner.TunerClass;
|
import io.github.dsheirer.source.tuner.TunerClass;
|
||||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A discovered tuner that may be accessible for use.
|
* A discovered tuner that may be accessible for use.
|
||||||
@@ -106,8 +105,8 @@ public abstract class DiscoveredTuner implements ITunerErrorListener
|
|||||||
|
|
||||||
if(mEnabled)
|
if(mEnabled)
|
||||||
{
|
{
|
||||||
setTunerStatus(TunerStatus.ENABLED);
|
|
||||||
start();
|
start();
|
||||||
|
setTunerStatus(TunerStatus.ENABLED);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
|||||||
import io.github.dsheirer.source.tuner.TunerController;
|
import io.github.dsheirer.source.tuner.TunerController;
|
||||||
import io.github.dsheirer.source.tuner.TunerType;
|
import io.github.dsheirer.source.tuner.TunerType;
|
||||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||||
|
import io.github.dsheirer.util.ThreadPool;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -267,9 +268,30 @@ public abstract class USBTunerController extends TunerController
|
|||||||
public final void stop()
|
public final void stop()
|
||||||
{
|
{
|
||||||
mRunning = false;
|
mRunning = false;
|
||||||
stopStreaming();
|
|
||||||
mNativeBufferBroadcaster.clear();
|
//Spin the shutdown onto a new thread so that we can set a max wait threshold.
|
||||||
deviceStop();
|
Thread t = new Thread(() -> {
|
||||||
|
stopStreaming();
|
||||||
|
mNativeBufferBroadcaster.clear();
|
||||||
|
deviceStop();
|
||||||
|
});
|
||||||
|
|
||||||
|
t.start();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//Wait up to 500 milliseconds for the shutdown to complete ... otherwise interrupt it and finish shutdown
|
||||||
|
t.join(500);
|
||||||
|
|
||||||
|
if(t.isAlive())
|
||||||
|
{
|
||||||
|
mLog.info("Tuner shutdown exceeded 500ms - forcing shutdown");
|
||||||
|
t.interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(InterruptedException ie)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
if(mDeviceHandle != null)
|
if(mDeviceHandle != null)
|
||||||
{
|
{
|
||||||
@@ -495,6 +517,9 @@ public abstract class USBTunerController extends TunerController
|
|||||||
private List<Transfer> mAvailableTransfers;
|
private List<Transfer> mAvailableTransfers;
|
||||||
private LinkedTransferQueue<Transfer> mInProgressTransfers = new LinkedTransferQueue<>();
|
private LinkedTransferQueue<Transfer> mInProgressTransfers = new LinkedTransferQueue<>();
|
||||||
private boolean mAutoResubmitTransfers = false;
|
private boolean mAutoResubmitTransfers = false;
|
||||||
|
private int mTransferErrorCount = 0;
|
||||||
|
private List<Transfer> mErrorTransfers = new ArrayList<>();
|
||||||
|
private int mResubmitFailureLogCount = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates USB Transfers to carry the streaming sample data. Transfer buffers are backed by native memory
|
* Creates USB Transfers to carry the streaming sample data. Transfer buffers are backed by native memory
|
||||||
@@ -559,34 +584,56 @@ public abstract class USBTunerController extends TunerController
|
|||||||
int status = LibUsb.submitTransfer(transfer);
|
int status = LibUsb.submitTransfer(transfer);
|
||||||
|
|
||||||
if(status == LibUsb.SUCCESS)
|
if(status == LibUsb.SUCCESS)
|
||||||
{
|
|
||||||
mInProgressTransfers.add(transfer);
|
|
||||||
}
|
|
||||||
else if(status == LibUsb.ERROR_BUSY)
|
|
||||||
{
|
{
|
||||||
mInProgressTransfers.add(transfer);
|
mInProgressTransfers.add(transfer);
|
||||||
|
|
||||||
//Weird issue/bug with libusb on Windows where libusb tells us the transfer is complete and then tells
|
//Attempt to resubmit any previous transfers that failed on submit
|
||||||
//us that the transfer is already submitted when we attempt to re-submit the transfer. We track the
|
if(!mErrorTransfers.isEmpty())
|
||||||
//number of times this occurs and log when the anomaly count exceeds the number of transfer buffers
|
|
||||||
//in case the application gets to a state where all transfers are no longer usable ... so that we
|
|
||||||
//know post-mortem what happened to the application.
|
|
||||||
mAnomalousTransfersDetected++;
|
|
||||||
|
|
||||||
if(mAnomalousTransfersDetected < USB_BULK_TRANSFER_BUFFER_POOL_SIZE)
|
|
||||||
{
|
{
|
||||||
mLog.error("USB transfer anomaly detected - continuing - previous status [" + previousStatus +
|
Transfer toResubmit = mErrorTransfers.get(0);
|
||||||
" ] transferred [" + previousBytesTransferred + "] this has happened [" +
|
int resubmitStatus = LibUsb.submitTransfer(toResubmit);
|
||||||
mAnomalousTransfersDetected + "] times");
|
|
||||||
|
if(resubmitStatus == LibUsb.SUCCESS)
|
||||||
|
{
|
||||||
|
mInProgressTransfers.add(transfer);
|
||||||
|
mTransferErrorCount--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mErrorTransfers.add(transfer);
|
||||||
|
|
||||||
|
if(mResubmitFailureLogCount < mAvailableTransfers.size())
|
||||||
|
{
|
||||||
|
mResubmitFailureLogCount++;
|
||||||
|
mLog.error("Attempt to resubmit previously failed transfer unsuccessful - status: " +
|
||||||
|
LibUsb.errorName(resubmitStatus));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mErrorTransfers.size() >= mAvailableTransfers.size())
|
||||||
|
{
|
||||||
|
mLog.error("Maximum USB I/O transfer submit errors reached - shutting down USB tuner");
|
||||||
|
ThreadPool.CACHED.submit(() -> setErrorMessage("USB Error - Transfers"));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else if(status == LibUsb.ERROR_BUSY || status == LibUsb.ERROR_IO)
|
||||||
|
{
|
||||||
|
mErrorTransfers.add(transfer);
|
||||||
|
mTransferErrorCount++;
|
||||||
|
|
||||||
//This should only log once, when the anomaly count matches the transfer count
|
//For ERROR_BUSY error, there is a weird issue/bug with libusb on Windows where libusb tells us the
|
||||||
if(mAnomalousTransfersDetected == USB_BULK_TRANSFER_BUFFER_POOL_SIZE)
|
//transfer is complete and then tells us that the transfer is already submitted when we attempt to
|
||||||
|
//re-submit the transfer. We track the number of times this occurs and log when the anomaly count
|
||||||
|
//exceeds the number of transfer buffers in case the application gets to a state where all transfers
|
||||||
|
//are no longer usable ... so that we know post-mortem what happened to the application.
|
||||||
|
mLog.error("USB error while submitting transfer buffer [" + LibUsb.errorName(status) + "] - this happened [" +
|
||||||
|
mTransferErrorCount + "] times, but may be temporary. Will attempt to resubmit in future");
|
||||||
|
|
||||||
|
if(mErrorTransfers.size() >= mAvailableTransfers.size())
|
||||||
{
|
{
|
||||||
mLog.warn("USB transfer anomaly detection count exceeded the total number [8] of available " +
|
mLog.error("Maximum USB I/O transfer submit errors reached - shutting down USB tuner");
|
||||||
"tranfers - sdrtrunk may no longer be streaming data from this tuner and may appear to " +
|
ThreadPool.CACHED.submit(() -> setErrorMessage("USB Error - Transfers"));
|
||||||
"be locked up. If so, restart the application to resolve the issue and please send the " +
|
|
||||||
"application log to the developer");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -610,6 +657,10 @@ public abstract class USBTunerController extends TunerController
|
|||||||
{
|
{
|
||||||
LibUsb.cancelTransfer(transfer);
|
LibUsb.cancelTransfer(transfer);
|
||||||
}
|
}
|
||||||
|
for(Transfer transfer: mErrorTransfers)
|
||||||
|
{
|
||||||
|
LibUsb.cancelTransfer(transfer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -641,14 +692,15 @@ public abstract class USBTunerController extends TunerController
|
|||||||
transfer.buffer().rewind();
|
transfer.buffer().rewind();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
//Unexpected transfer error - need to reset the bulk transfer interface
|
//Unexpected transfer error - shutdown the tuner
|
||||||
transfer.buffer().rewind();
|
transfer.buffer().rewind();
|
||||||
|
|
||||||
//Only set an error if we're not shutting down
|
//Only set an error if we're not shutting down
|
||||||
if(mAutoResubmitTransfers)
|
if(mAutoResubmitTransfers)
|
||||||
{
|
{
|
||||||
setErrorMessage("LibUsb Transfer Error - stopping device - status [" + transfer.status() + "] - " +
|
//spin this off onto the thread pool, so it doesn't impact the usb processor thread.
|
||||||
LibUsb.errorName(transfer.status()));
|
ThreadPool.CACHED.submit(() -> setErrorMessage("LibUsb Transfer Error - stopping device - " +
|
||||||
|
"status [" + transfer.status() + "] - " + LibUsb.errorName(transfer.status())));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user