mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-09 23:27:24 -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" }
|
||||
}
|
||||
|
||||
version = '0.5.0-beta7'
|
||||
version = '0.5.0'
|
||||
sourceCompatibility = '17'
|
||||
|
||||
sourceSets {
|
||||
|
||||
@@ -25,19 +25,15 @@ import io.github.dsheirer.audio.convert.MP3FrameTools;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
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.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration> extends AbstractAudioBroadcaster<T>
|
||||
{
|
||||
@@ -255,7 +251,6 @@ public abstract class AudioStreamingBroadcaster<T extends BroadcastConfiguration
|
||||
public boolean canConnect()
|
||||
{
|
||||
BroadcastState state = getBroadcastState();
|
||||
|
||||
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.Listener;
|
||||
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.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
@@ -48,6 +41,12 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
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>
|
||||
{
|
||||
@@ -58,7 +57,7 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
|
||||
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_BROADCASTER_STATUS = 2;
|
||||
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 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 =
|
||||
FXCollections.observableArrayList(ConfiguredBroadcast.extractor());
|
||||
@@ -575,15 +574,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
{
|
||||
switch(columnIndex)
|
||||
{
|
||||
case COLUMN_SERVER_ICON:
|
||||
String iconPath = configuredBroadcast.getBroadcastConfiguration()
|
||||
.getBroadcastServerType().getIconPath();
|
||||
|
||||
if(iconPath != null && mIconModel != null)
|
||||
{
|
||||
return mIconModel.getScaledIcon(iconPath, 14);
|
||||
}
|
||||
break;
|
||||
case COLUMN_BROADCAST_SERVER_TYPE:
|
||||
return configuredBroadcast.getBroadcastServerType();
|
||||
case COLUMN_STREAM_NAME:
|
||||
return configuredBroadcast.getBroadcastConfiguration().getName();
|
||||
case COLUMN_BROADCASTER_STATUS:
|
||||
@@ -653,8 +645,8 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
case COLUMN_BROADCASTER_STREAMED_COUNT:
|
||||
case COLUMN_BROADCASTER_ERROR_COUNT:
|
||||
return Integer.class;
|
||||
case COLUMN_SERVER_ICON:
|
||||
return ImageIcon.class;
|
||||
case COLUMN_BROADCAST_SERVER_TYPE:
|
||||
return BroadcastServerType.class;
|
||||
case COLUMN_STREAM_NAME:
|
||||
default:
|
||||
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
|
||||
* 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
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
public enum BroadcastServerType
|
||||
@@ -23,17 +23,17 @@ public enum BroadcastServerType
|
||||
/**
|
||||
* 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_CALL("Broadcastify Call", "/images/broadcastify.png"),
|
||||
BROADCASTIFY_CALL("Broadcastify Call", "images/broadcastify.png"),
|
||||
|
||||
ICECAST_HTTP("Icecast 2 (v2.4+)", "/images/icecast.png"),
|
||||
ICECAST_TCP("Icecast (v2.3)", "/images/icecast.png"),
|
||||
SHOUTCAST_V1("Shoutcast v1.x", "/images/shoutcast.png"),
|
||||
SHOUTCAST_V2("Shoutcast v2.x", "/images/shoutcast.png"),
|
||||
ICECAST_HTTP("Icecast 2 (v2.4+)", "images/icecast.png"),
|
||||
ICECAST_TCP("Icecast (v2.3)", "images/icecast.png"),
|
||||
SHOUTCAST_V1("Shoutcast v1.x", "images/shoutcast.png"),
|
||||
SHOUTCAST_V2("Shoutcast v2.x", "images/shoutcast.png"),
|
||||
UNKNOWN("Unknown", null);
|
||||
|
||||
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
|
||||
* 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
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package io.github.dsheirer.audio.broadcast;
|
||||
|
||||
public enum BroadcastState
|
||||
@@ -68,7 +68,7 @@ public enum BroadcastState
|
||||
/**
|
||||
* 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
|
||||
@@ -103,10 +103,10 @@ public enum BroadcastState
|
||||
private String mLabel;
|
||||
private boolean mErrorState;
|
||||
|
||||
private BroadcastState(String label, boolean connected)
|
||||
BroadcastState(String label, boolean errorState)
|
||||
{
|
||||
mLabel = label;
|
||||
mErrorState = connected;
|
||||
mErrorState = errorState;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/*
|
||||
* ******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -15,23 +14,29 @@
|
||||
*
|
||||
* 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.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.swing.JTableColumnWidthMonitor;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
|
||||
/**
|
||||
* Table of broadcast streams and statuses.
|
||||
*/
|
||||
public class BroadcastStatusPanel extends JPanel
|
||||
{
|
||||
private JTable mTable;
|
||||
@@ -41,6 +46,12 @@ public class BroadcastStatusPanel extends JPanel
|
||||
private UserPreferences mUserPreferences;
|
||||
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)
|
||||
{
|
||||
mBroadcastModel = broadcastModel;
|
||||
@@ -65,6 +76,7 @@ public class BroadcastStatusPanel extends JPanel
|
||||
renderer.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
|
||||
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);
|
||||
|
||||
mScrollPane = new JScrollPane(mTable);
|
||||
@@ -72,6 +84,37 @@ public class BroadcastStatusPanel extends JPanel
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -20,13 +20,20 @@ package io.github.dsheirer.audio.broadcast.icecast;
|
||||
|
||||
import io.github.dsheirer.alias.AliasModel;
|
||||
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.MP3AudioConverter;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
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.buffer.IoBuffer;
|
||||
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.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
|
||||
{
|
||||
private static final Logger mLog = LoggerFactory.getLogger(IcecastHTTPAudioBroadcaster.class);
|
||||
@@ -143,8 +143,9 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
mSocketConnector = new NioSocketConnector();
|
||||
mSocketConnector.setConnectTimeoutCheckInterval(10000);
|
||||
|
||||
// mSocketConnector.getFilterChain().addLast("logger",
|
||||
// new LoggingFilter(IcecastHTTPAudioBroadcaster.class));
|
||||
// LoggingFilter loggingFilter = new LoggingFilter(IcecastHTTPAudioBroadcaster.class);
|
||||
// loggingFilter.setMessageReceivedLogLevel(LogLevel.DEBUG);
|
||||
// mSocketConnector.getFilterChain().addLast("logger", loggingFilter);
|
||||
|
||||
mSocketConnector.getFilterChain().addLast("codec", new HttpClientCodec());
|
||||
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
|
||||
* OK response and then play like everything is well and good.
|
||||
*/
|
||||
if(cause instanceof ArrayIndexOutOfBoundsException &&
|
||||
((ProtocolDecoderException)throwable).getHexdump().startsWith(HTTP_1_0_OK_HEX_DUMP))
|
||||
if(cause instanceof ArrayIndexOutOfBoundsException)
|
||||
{
|
||||
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
|
||||
{
|
||||
@@ -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.audio.broadcast.BroadcastState;
|
||||
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.MP3AudioConverter;
|
||||
import io.github.dsheirer.audio.convert.MP3Setting;
|
||||
import io.github.dsheirer.identifier.IdentifierCollection;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
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.future.ConnectFuture;
|
||||
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.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
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(IcecastTCPAudioBroadcaster.class);
|
||||
@@ -215,7 +213,13 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package io.github.dsheirer.audio.convert;
|
||||
|
||||
import io.github.dsheirer.audio.AudioFormats;
|
||||
import java.util.EnumSet;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
|
||||
@@ -29,8 +30,8 @@ import javax.sound.sampled.AudioFormat;
|
||||
public enum InputAudioFormat
|
||||
{
|
||||
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_22050(AudioFormats.PCM_SIGNED_22050_HZ_16_BIT_MONO, "16-Bit 22050 Hz (default)"),
|
||||
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"),
|
||||
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)"),
|
||||
@@ -41,6 +42,17 @@ public enum InputAudioFormat
|
||||
private AudioFormat mAudioFormat;
|
||||
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
|
||||
* @param audioFormat for the specified sample rate
|
||||
@@ -56,7 +68,7 @@ public enum InputAudioFormat
|
||||
*/
|
||||
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)
|
||||
{
|
||||
mInputAudioFormat = inputAudioFormat;
|
||||
|
||||
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;
|
||||
|
||||
//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
|
||||
if(inputAudioFormat != InputAudioFormat.SR_8000 && inputAudioFormat != InputAudioFormat.SR_32_8000)
|
||||
if(mInputAudioFormat != InputAudioFormat.SR_8000 && mInputAudioFormat != InputAudioFormat.SR_32_8000)
|
||||
{
|
||||
mResampler = LameFactory.getResampler(inputAudioFormat);
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ public class MP3Header
|
||||
*/
|
||||
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;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import net.sourceforge.lame.mp3.Lame;
|
||||
|
||||
/**
|
||||
@@ -79,6 +81,30 @@ public enum MP3Setting
|
||||
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
|
||||
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.service.radioreference.RadioReference;
|
||||
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.beans.binding.Bindings;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
@@ -55,12 +60,6 @@ import jiconfont.javafx.IconNode;
|
||||
import org.slf4j.Logger;
|
||||
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
|
||||
*/
|
||||
@@ -293,7 +292,13 @@ public class SystemEditor extends VBox
|
||||
List<EnrichedSite> enrichedSites = new ArrayList<>();
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import io.github.dsheirer.preference.UserPreferences;
|
||||
import io.github.dsheirer.preference.mp3.MP3Preference;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.CheckBox;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -99,12 +100,47 @@ public class MP3PreferenceEditor extends HBox
|
||||
mMP3SettingComboBox.getItems().addAll(MP3Setting.values());
|
||||
mMP3SettingComboBox.getSelectionModel().select(mMP3Preference.getMP3Setting());
|
||||
mMP3SettingComboBox.getSelectionModel().selectedItemProperty()
|
||||
.addListener((observable, oldValue, newValue) -> mMP3Preference.setMP3Setting(newValue));
|
||||
.addListener((observable, oldValue, newValue) -> {
|
||||
mMP3Preference.setMP3Setting(newValue);
|
||||
updateAudioSampleRateComboBox();
|
||||
});
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if(mAudioSampleRateComboBox == null)
|
||||
@@ -113,7 +149,12 @@ public class MP3PreferenceEditor extends HBox
|
||||
mAudioSampleRateComboBox.getItems().addAll(InputAudioFormat.values());
|
||||
mAudioSampleRateComboBox.getSelectionModel().select(mMP3Preference.getAudioSampleRate());
|
||||
mAudioSampleRateComboBox.getSelectionModel().selectedItemProperty()
|
||||
.addListener((observable, oldValue, newValue) -> mMP3Preference.setAudioSampleRate(newValue));
|
||||
.addListener((observable, oldValue, newValue) -> {
|
||||
if(newValue != null)
|
||||
{
|
||||
mMP3Preference.setAudioSampleRate(newValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return mAudioSampleRateComboBox;
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
/*
|
||||
* *********************************************************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
|
||||
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
* 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/>
|
||||
* *********************************************************************************************************************
|
||||
* 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.icon;
|
||||
|
||||
@@ -22,15 +24,6 @@ import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
import io.github.dsheirer.properties.SystemProperties;
|
||||
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.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -43,6 +36,15 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
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
|
||||
{
|
||||
@@ -241,7 +243,6 @@ public class IconModel
|
||||
if(path != null)
|
||||
{
|
||||
Icon icon = new Icon("", path);
|
||||
|
||||
return getScaledIcon(icon.getIcon(), height);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package 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.preference.PreferenceType;
|
||||
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.LoggerFactory;
|
||||
|
||||
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>
|
||||
{
|
||||
@@ -149,10 +146,12 @@ public class DecodeEventModel extends AbstractTableModel implements Listener<IDe
|
||||
*/
|
||||
public void receive(final IDecodeEvent event)
|
||||
{
|
||||
if (!mEventFilterSet.passes(event))
|
||||
{
|
||||
return;
|
||||
}
|
||||
//Disabled until #1368 decode event is fully implemented
|
||||
// if (!mEventFilterSet.passes(event))
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
if(!mEvents.contains(event))
|
||||
{
|
||||
mEvents.add(0, event);
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2014-2022 Dennis Sheirer
|
||||
*
|
||||
* * ******************************************************************************
|
||||
* * Copyright (C) 2014-2019 Dennis Sheirer
|
||||
* *
|
||||
* * This program is free software: you can redistribute it and/or modify
|
||||
* * it under the terms of the GNU General Public License as published by
|
||||
* * the Free Software Foundation, either version 3 of the License, or
|
||||
* * (at your option) any later version.
|
||||
* *
|
||||
* * This program is distributed in the hope that it will be useful,
|
||||
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* * GNU General Public License for more details.
|
||||
* *
|
||||
* * You should have received a copy of the GNU General Public License
|
||||
* * along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* * *****************************************************************************
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
package 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.swing.JTableColumnWidthMonitor;
|
||||
import io.github.dsheirer.sample.Listener;
|
||||
import net.miginfocom.swing.MigLayout;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import java.awt.*;
|
||||
import java.awt.Color;
|
||||
import java.awt.Component;
|
||||
import java.awt.EventQueue;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
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>
|
||||
{
|
||||
@@ -94,7 +97,8 @@ public class DecodeEventPanel extends JPanel implements Listener<ProcessingChain
|
||||
mTableColumnWidthMonitor = new JTableColumnWidthMonitor(mUserPreferences, mTable, TABLE_PREFERENCE_KEY);
|
||||
updateCellRenderers();
|
||||
|
||||
add(mEventManagementPanel, "span,growx");
|
||||
//Disabled until #1368 decode event is fully implemented
|
||||
// add(mEventManagementPanel, "span,growx");
|
||||
|
||||
mEmptyScroller = new JScrollPane(mTable);
|
||||
add(mEmptyScroller);
|
||||
|
||||
@@ -116,6 +116,24 @@ public class MP3Preference extends Preference
|
||||
{
|
||||
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;
|
||||
|
||||
@@ -24,11 +24,10 @@ import io.github.dsheirer.source.tuner.ITunerErrorListener;
|
||||
import io.github.dsheirer.source.tuner.Tuner;
|
||||
import io.github.dsheirer.source.tuner.TunerClass;
|
||||
import io.github.dsheirer.source.tuner.configuration.TunerConfiguration;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A discovered tuner that may be accessible for use.
|
||||
@@ -106,8 +105,8 @@ public abstract class DiscoveredTuner implements ITunerErrorListener
|
||||
|
||||
if(mEnabled)
|
||||
{
|
||||
setTunerStatus(TunerStatus.ENABLED);
|
||||
start();
|
||||
setTunerStatus(TunerStatus.ENABLED);
|
||||
}
|
||||
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.TunerType;
|
||||
import io.github.dsheirer.source.tuner.manager.TunerManager;
|
||||
import io.github.dsheirer.util.ThreadPool;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -267,9 +268,30 @@ public abstract class USBTunerController extends TunerController
|
||||
public final void stop()
|
||||
{
|
||||
mRunning = false;
|
||||
stopStreaming();
|
||||
mNativeBufferBroadcaster.clear();
|
||||
deviceStop();
|
||||
|
||||
//Spin the shutdown onto a new thread so that we can set a max wait threshold.
|
||||
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)
|
||||
{
|
||||
@@ -495,6 +517,9 @@ public abstract class USBTunerController extends TunerController
|
||||
private List<Transfer> mAvailableTransfers;
|
||||
private LinkedTransferQueue<Transfer> mInProgressTransfers = new LinkedTransferQueue<>();
|
||||
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
|
||||
@@ -559,34 +584,56 @@ public abstract class USBTunerController extends TunerController
|
||||
int status = LibUsb.submitTransfer(transfer);
|
||||
|
||||
if(status == LibUsb.SUCCESS)
|
||||
{
|
||||
mInProgressTransfers.add(transfer);
|
||||
}
|
||||
else if(status == LibUsb.ERROR_BUSY)
|
||||
{
|
||||
mInProgressTransfers.add(transfer);
|
||||
|
||||
//Weird issue/bug with libusb on Windows where libusb tells us the 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.
|
||||
mAnomalousTransfersDetected++;
|
||||
|
||||
if(mAnomalousTransfersDetected < USB_BULK_TRANSFER_BUFFER_POOL_SIZE)
|
||||
//Attempt to resubmit any previous transfers that failed on submit
|
||||
if(!mErrorTransfers.isEmpty())
|
||||
{
|
||||
mLog.error("USB transfer anomaly detected - continuing - previous status [" + previousStatus +
|
||||
" ] transferred [" + previousBytesTransferred + "] this has happened [" +
|
||||
mAnomalousTransfersDetected + "] times");
|
||||
Transfer toResubmit = mErrorTransfers.get(0);
|
||||
int resubmitStatus = LibUsb.submitTransfer(toResubmit);
|
||||
|
||||
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
|
||||
if(mAnomalousTransfersDetected == USB_BULK_TRANSFER_BUFFER_POOL_SIZE)
|
||||
//For ERROR_BUSY error, there is a weird issue/bug with libusb on Windows where libusb tells us the
|
||||
//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 " +
|
||||
"tranfers - sdrtrunk may no longer be streaming data from this tuner and may appear to " +
|
||||
"be locked up. If so, restart the application to resolve the issue and please send the " +
|
||||
"application log to the developer");
|
||||
mLog.error("Maximum USB I/O transfer submit errors reached - shutting down USB tuner");
|
||||
ThreadPool.CACHED.submit(() -> setErrorMessage("USB Error - Transfers"));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -610,6 +657,10 @@ public abstract class USBTunerController extends TunerController
|
||||
{
|
||||
LibUsb.cancelTransfer(transfer);
|
||||
}
|
||||
for(Transfer transfer: mErrorTransfers)
|
||||
{
|
||||
LibUsb.cancelTransfer(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -641,14 +692,15 @@ public abstract class USBTunerController extends TunerController
|
||||
transfer.buffer().rewind();
|
||||
break;
|
||||
default:
|
||||
//Unexpected transfer error - need to reset the bulk transfer interface
|
||||
//Unexpected transfer error - shutdown the tuner
|
||||
transfer.buffer().rewind();
|
||||
|
||||
//Only set an error if we're not shutting down
|
||||
if(mAutoResubmitTransfers)
|
||||
{
|
||||
setErrorMessage("LibUsb Transfer Error - stopping device - status [" + transfer.status() + "] - " +
|
||||
LibUsb.errorName(transfer.status()));
|
||||
//spin this off onto the thread pool, so it doesn't impact the usb processor thread.
|
||||
ThreadPool.CACHED.submit(() -> setErrorMessage("LibUsb Transfer Error - stopping device - " +
|
||||
"status [" + transfer.status() + "] - " + LibUsb.errorName(transfer.status())));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user