mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
#30 - adds support for Shoutcast V2 (aka Ultravox 2.1) audio streams.
Resolves issue with MP3 framing where the remote server was disconnecting us due to bad MP3 sync. The LAME encoder was sending partial 20 byte MP3 frames initiall, then following with a 144 byte array which contained the remainder of the first frame and the header of the next frame. Adjusted the MP3SilenceGenerator to only emit complete MP3 frames. This should be the final commit for this feature, pending merge back into master branch.
This commit is contained in:
1
.idea/dictionaries/denny.xml
generated
1
.idea/dictionaries/denny.xml
generated
@@ -10,6 +10,7 @@
|
||||
<w>polyphase</w>
|
||||
<w>samplerate</w>
|
||||
<w>sdrtrunk</w>
|
||||
<w>sheirer</w>
|
||||
<w>shoutcast</w>
|
||||
<w>streamable</w>
|
||||
<w>talkgroup</w>
|
||||
|
||||
@@ -103,10 +103,23 @@ public abstract class AudioBroadcaster implements IAudioPacketListener
|
||||
protected abstract void broadcastAudio(byte[] audio);
|
||||
|
||||
/**
|
||||
* Broadcasts the next song's audio metadata prior to streaming the next song.
|
||||
* @param metadata
|
||||
* Protocol-specific metadata updater
|
||||
*/
|
||||
protected abstract void broadcastMetadata(AudioMetadata metadata);
|
||||
protected abstract IBroadcastMetadataUpdater getMetadataUpdater();
|
||||
|
||||
/**
|
||||
* Broadcasts the next song's audio metadata prior to streaming the next song.
|
||||
* @param metadata for the next recording that will be streamed
|
||||
*/
|
||||
protected void broadcastMetadata(AudioMetadata metadata)
|
||||
{
|
||||
IBroadcastMetadataUpdater metadataUpdater = getMetadataUpdater();
|
||||
|
||||
if(metadataUpdater != null)
|
||||
{
|
||||
metadataUpdater.update(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the broadcaster from the remote server for a reset or final stop.
|
||||
|
||||
@@ -32,6 +32,7 @@ import audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
|
||||
import audio.broadcast.shoutcast.v1.ShoutcastV1ConfigurationEditor;
|
||||
import audio.broadcast.shoutcast.v2.ShoutcastV2AudioBroadcaster;
|
||||
import audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
|
||||
import audio.broadcast.shoutcast.v2.ShoutcastV2ConfigurationEditor;
|
||||
import audio.convert.IAudioConverter;
|
||||
import audio.convert.ISilenceGenerator;
|
||||
import audio.convert.MP3AudioConverter;
|
||||
@@ -172,6 +173,9 @@ public class BroadcastFactory
|
||||
case SHOUTCAST_V1:
|
||||
editor = new ShoutcastV1ConfigurationEditor(broadcastModel, aliasModel, settingsManager);
|
||||
break;
|
||||
case SHOUTCAST_V2:
|
||||
editor = new ShoutcastV2ConfigurationEditor(broadcastModel, aliasModel, settingsManager);
|
||||
break;
|
||||
default:
|
||||
editor = new EmptyEditor<BroadcastConfiguration>();
|
||||
break;
|
||||
|
||||
@@ -488,60 +488,67 @@ public class BroadcastModel extends AbstractTableModel implements Listener<Audio
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex)
|
||||
{
|
||||
if( rowIndex <= mBroadcastConfigurations.size())
|
||||
try
|
||||
{
|
||||
BroadcastConfiguration configuration = mBroadcastConfigurations.get(rowIndex);
|
||||
|
||||
if(configuration != null)
|
||||
if( rowIndex <= mBroadcastConfigurations.size())
|
||||
{
|
||||
switch(columnIndex)
|
||||
BroadcastConfiguration configuration = mBroadcastConfigurations.get(rowIndex);
|
||||
|
||||
if(configuration != null)
|
||||
{
|
||||
case COLUMN_SERVER_ICON:
|
||||
String iconName = configuration.getBroadcastServerType().getIconName();
|
||||
switch(columnIndex)
|
||||
{
|
||||
case COLUMN_SERVER_ICON:
|
||||
String iconName = configuration.getBroadcastServerType().getIconName();
|
||||
|
||||
if(iconName != null && mSettingsManager != null)
|
||||
{
|
||||
return mSettingsManager.getImageIcon(iconName, 14);
|
||||
}
|
||||
break;
|
||||
case COLUMN_STREAM_NAME:
|
||||
return configuration.getName();
|
||||
case COLUMN_BROADCASTER_STATUS:
|
||||
AudioBroadcaster audioBroadcasterA = mBroadcasterMap.get(configuration.getName());
|
||||
if(iconName != null && mSettingsManager != null)
|
||||
{
|
||||
return mSettingsManager.getImageIcon(iconName, 14);
|
||||
}
|
||||
break;
|
||||
case COLUMN_STREAM_NAME:
|
||||
return configuration.getName();
|
||||
case COLUMN_BROADCASTER_STATUS:
|
||||
AudioBroadcaster audioBroadcasterA = mBroadcasterMap.get(configuration.getName());
|
||||
|
||||
if(audioBroadcasterA != null)
|
||||
{
|
||||
return audioBroadcasterA.getBroadcastState().toString();
|
||||
}
|
||||
else if(!configuration.isEnabled())
|
||||
{
|
||||
return "Disabled";
|
||||
}
|
||||
else if(!configuration.isValid())
|
||||
{
|
||||
return "Invalid Settings";
|
||||
}
|
||||
case COLUMN_BROADCASTER_QUEUE_SIZE:
|
||||
AudioBroadcaster audioBroadcasterB = mBroadcasterMap.get(configuration.getName());
|
||||
if(audioBroadcasterA != null)
|
||||
{
|
||||
return audioBroadcasterA.getBroadcastState().toString();
|
||||
}
|
||||
else if(!configuration.isEnabled())
|
||||
{
|
||||
return "Disabled";
|
||||
}
|
||||
else if(!configuration.isValid())
|
||||
{
|
||||
return "Invalid Settings";
|
||||
}
|
||||
case COLUMN_BROADCASTER_QUEUE_SIZE:
|
||||
AudioBroadcaster audioBroadcasterB = mBroadcasterMap.get(configuration.getName());
|
||||
|
||||
if(audioBroadcasterB != null)
|
||||
{
|
||||
return audioBroadcasterB.getQueueSize();
|
||||
}
|
||||
break;
|
||||
case COLUMN_BROADCASTER_STREAMED_COUNT:
|
||||
AudioBroadcaster audioBroadcasterC = mBroadcasterMap.get(configuration.getName());
|
||||
if(audioBroadcasterB != null)
|
||||
{
|
||||
return audioBroadcasterB.getQueueSize();
|
||||
}
|
||||
break;
|
||||
case COLUMN_BROADCASTER_STREAMED_COUNT:
|
||||
AudioBroadcaster audioBroadcasterC = mBroadcasterMap.get(configuration.getName());
|
||||
|
||||
if(audioBroadcasterC != null)
|
||||
{
|
||||
return audioBroadcasterC.getStreamedAudioCount();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if(audioBroadcasterC != null)
|
||||
{
|
||||
return audioBroadcasterC.getStreamedAudioCount();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
mLog.error("Error accessing data in broadcast model", e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ public class BroadcastPanel extends JPanel implements ActionListener, ListSelect
|
||||
menu.add(new AddBroadcastConfigurationItem(BroadcastServerType.ICECAST_TCP));
|
||||
menu.add(new AddBroadcastConfigurationItem(BroadcastServerType.ICECAST_HTTP));
|
||||
menu.add(new AddBroadcastConfigurationItem(BroadcastServerType.SHOUTCAST_V1));
|
||||
menu.add(new AddBroadcastConfigurationItem(BroadcastServerType.SHOUTCAST_V2));
|
||||
|
||||
menu.show(e.getComponent(), e.getX(), e.getY());
|
||||
}
|
||||
|
||||
@@ -45,6 +45,11 @@ public enum BroadcastState
|
||||
*/
|
||||
INVALID_CREDENTIALS("Invalid User Name/Password", true),
|
||||
|
||||
/**
|
||||
* Invalid mount point
|
||||
*/
|
||||
INVALID_MOUNT_POINT("Invalid Mount/Stream ID", true),
|
||||
|
||||
/**
|
||||
* Remote server max sources has been exceeded
|
||||
*/
|
||||
|
||||
31
src/audio/broadcast/IBroadcastMetadataUpdater.java
Normal file
31
src/audio/broadcast/IBroadcastMetadataUpdater.java
Normal file
@@ -0,0 +1,31 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.broadcast;
|
||||
|
||||
import audio.metadata.AudioMetadata;
|
||||
|
||||
public interface IBroadcastMetadataUpdater
|
||||
{
|
||||
/**
|
||||
* Updates broadcast audio metadata
|
||||
*
|
||||
* @param metadata containing metadata attributes
|
||||
*/
|
||||
void update(AudioMetadata metadata);
|
||||
}
|
||||
@@ -20,12 +20,12 @@ package audio.broadcast.icecast;
|
||||
|
||||
import audio.broadcast.AudioBroadcaster;
|
||||
import audio.broadcast.BroadcastConfiguration;
|
||||
import audio.metadata.AudioMetadata;
|
||||
import audio.broadcast.IBroadcastMetadataUpdater;
|
||||
import controller.ThreadPoolManager;
|
||||
|
||||
public abstract class IcecastAudioBroadcaster extends AudioBroadcaster
|
||||
{
|
||||
private IcecastMetadataUpdater mIcecastMetadataUpdater;
|
||||
private IBroadcastMetadataUpdater mMetadataUpdater;
|
||||
|
||||
public IcecastAudioBroadcaster(ThreadPoolManager threadPoolManager, BroadcastConfiguration broadcastConfiguration)
|
||||
{
|
||||
@@ -40,17 +40,14 @@ public abstract class IcecastAudioBroadcaster extends AudioBroadcaster
|
||||
return (IcecastConfiguration)getBroadcastConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts an audio metadata update
|
||||
*/
|
||||
@Override
|
||||
protected void broadcastMetadata(AudioMetadata metadata)
|
||||
protected IBroadcastMetadataUpdater getMetadataUpdater()
|
||||
{
|
||||
if(mIcecastMetadataUpdater == null)
|
||||
if(mMetadataUpdater == null)
|
||||
{
|
||||
mIcecastMetadataUpdater = new IcecastMetadataUpdater(getThreadPoolManager(), getConfiguration());
|
||||
mMetadataUpdater = new IcecastBroadcastMetadataUpdater(getThreadPoolManager(), getConfiguration());
|
||||
}
|
||||
|
||||
mIcecastMetadataUpdater.update(metadata);
|
||||
return mMetadataUpdater;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
******************************************************************************/
|
||||
package audio.broadcast.icecast;
|
||||
|
||||
import audio.broadcast.IBroadcastMetadataUpdater;
|
||||
import audio.metadata.AudioMetadata;
|
||||
import audio.metadata.Metadata;
|
||||
import audio.metadata.MetadataType;
|
||||
@@ -25,7 +26,6 @@ import controller.ThreadPoolManager;
|
||||
import org.apache.mina.core.future.ConnectFuture;
|
||||
import org.apache.mina.core.service.IoHandlerAdapter;
|
||||
import org.apache.mina.core.session.IoSession;
|
||||
import org.apache.mina.filter.logging.LoggingFilter;
|
||||
import org.apache.mina.http.HttpClientCodec;
|
||||
import org.apache.mina.http.HttpRequestImpl;
|
||||
import org.apache.mina.http.api.HttpMethod;
|
||||
@@ -40,7 +40,6 @@ import java.io.UnsupportedEncodingException;
|
||||
import java.net.ConnectException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
@@ -48,9 +47,9 @@ import java.util.concurrent.LinkedTransferQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class IcecastMetadataUpdater
|
||||
public class IcecastBroadcastMetadataUpdater implements IBroadcastMetadataUpdater
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(IcecastMetadataUpdater.class);
|
||||
private final static Logger mLog = LoggerFactory.getLogger(IcecastBroadcastMetadataUpdater.class);
|
||||
private final static String UTF8 = "UTF-8";
|
||||
|
||||
private ThreadPoolManager mThreadPoolManager;
|
||||
@@ -68,7 +67,7 @@ public class IcecastMetadataUpdater
|
||||
* broadcaster. When multiple metadata updates are received prior to completion of the current ongoing update
|
||||
* sequence, those updates will be queued and processed in the order received.
|
||||
*/
|
||||
public IcecastMetadataUpdater(ThreadPoolManager threadPoolManager, IcecastConfiguration icecastConfiguration)
|
||||
public IcecastBroadcastMetadataUpdater(ThreadPoolManager threadPoolManager, IcecastConfiguration icecastConfiguration)
|
||||
{
|
||||
mThreadPoolManager = threadPoolManager;
|
||||
mIcecastConfiguration = icecastConfiguration;
|
||||
@@ -82,7 +81,7 @@ public class IcecastMetadataUpdater
|
||||
if (mSocketConnector == null)
|
||||
{
|
||||
mSocketConnector = new NioSocketConnector();
|
||||
// mSocketConnector.getFilterChain().addLast("logger", new LoggingFilter(IcecastMetadataUpdater.class));
|
||||
// mSocketConnector.getFilterChain().addLast("logger", new LoggingFilter(IcecastBroadcastMetadataUpdater.class));
|
||||
mSocketConnector.getFilterChain().addLast("http_client_codec", new HttpClientCodec());
|
||||
|
||||
//Each metadata update session is single-use, so we'll shut it down upon success or failure
|
||||
@@ -58,7 +58,7 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
|
||||
* Note: use @see IcecastHTTPAudioBroadcaster for Icecast version 2.4.x and newer.
|
||||
*
|
||||
* This broadcaster uses the Apache Mina library for the streaming socket connection and for metadata updates. The
|
||||
* IcecastTCPIOHandler manages all interaction with the Icecast server and manages the overall broadcast state.
|
||||
* ShoutcastV2IOHandler manages all interaction with the Icecast server and manages the overall broadcast state.
|
||||
*
|
||||
* @param configuration for the Icecast stream
|
||||
*/
|
||||
|
||||
89
src/audio/convert/MP3FrameInspector.java
Normal file
89
src/audio/convert/MP3FrameInspector.java
Normal file
@@ -0,0 +1,89 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2017 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package audio.convert;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MP3FrameInspector
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MP3FrameInspector.class);
|
||||
|
||||
public static void inspect(byte[] frames)
|
||||
{
|
||||
if(frames == null || frames.length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(frames.length % 144 != 0)
|
||||
{
|
||||
log("Frame byte array not multiple of 144 byte frame size - length:" + frames.length, frames);
|
||||
return;
|
||||
}
|
||||
|
||||
//Check for erroneous frame sync
|
||||
for(int x = 0; x < frames.length - 1; x++)
|
||||
{
|
||||
if(frames[x] == (byte)0xFF && (frames[x + 1] & 0xE0) == 0xE0 && (x % 144) != 0)
|
||||
{
|
||||
log("Bad frame sync detected at byte " + x + " in frame " + (x / 144), frames);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Check MP3 headers
|
||||
|
||||
// log("Frames ...", frames);
|
||||
}
|
||||
|
||||
private static void log(String message, byte[] frames)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(message).append("\n");
|
||||
|
||||
int frameCounter = 0;
|
||||
sb.append(frameCounter++).append(" ");
|
||||
|
||||
for(int x = 0; x < frames.length; x++)
|
||||
{
|
||||
if(x > 0 && x % 144 == 0)
|
||||
{
|
||||
sb.append("\n");
|
||||
sb.append(frameCounter++).append(" ");
|
||||
}
|
||||
|
||||
sb.append(String.format("%02X ", frames[x]));
|
||||
}
|
||||
|
||||
mLog.info(sb.toString());
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
mLog.info("Starting ...");
|
||||
MP3SilenceGenerator gen = new MP3SilenceGenerator();
|
||||
|
||||
byte[] audio = gen.generate(173);
|
||||
|
||||
inspect(audio);
|
||||
|
||||
mLog.info("Finished");
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,12 @@
|
||||
package audio.convert;
|
||||
|
||||
import audio.AudioPacket;
|
||||
import audio.broadcast.AudioBroadcaster;
|
||||
import audio.convert.MP3AudioConverter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import record.mp3.MP3Recorder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MP3SilenceGenerator implements ISilenceGenerator
|
||||
@@ -33,14 +32,13 @@ public class MP3SilenceGenerator implements ISilenceGenerator
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MP3SilenceGenerator.class);
|
||||
|
||||
private MP3AudioConverter mGenerator = new MP3AudioConverter(MP3Recorder.MP3_BIT_RATE, MP3Recorder.CONSTANT_BIT_RATE);
|
||||
private byte[] mPreviousPartialFrameData;
|
||||
|
||||
/**
|
||||
* Generates MP3 audio silence frames
|
||||
*/
|
||||
public MP3SilenceGenerator()
|
||||
{
|
||||
//Prime the conversion buffer with 172 ms of audio to produce (and throw away) the first 20 byte buffer
|
||||
generate(172);
|
||||
}
|
||||
|
||||
public byte[] generate(long duration)
|
||||
@@ -50,34 +48,71 @@ public class MP3SilenceGenerator implements ISilenceGenerator
|
||||
AudioPacket silencePacket = new AudioPacket(silence, null);
|
||||
List<AudioPacket> silencePackets = new ArrayList<>();
|
||||
silencePackets.add(silencePacket);
|
||||
return mGenerator.convert(silencePackets);
|
||||
|
||||
byte[] frameData = mGenerator.convert(silencePackets);
|
||||
|
||||
frameData = merge(mPreviousPartialFrameData, frameData);
|
||||
|
||||
if(frameData != null && frameData.length > 0)
|
||||
{
|
||||
if(frameData.length < 144)
|
||||
{
|
||||
mPreviousPartialFrameData = frameData;
|
||||
return null;
|
||||
}
|
||||
else if((frameData.length % 144) == 0)
|
||||
{
|
||||
mPreviousPartialFrameData = null;
|
||||
return frameData;
|
||||
}
|
||||
else
|
||||
{
|
||||
int integralFrameLength = (int)(frameData.length / 144) * 144;
|
||||
mPreviousPartialFrameData = Arrays.copyOfRange(frameData, integralFrameLength, frameData.length);
|
||||
return Arrays.copyOf(frameData, integralFrameLength);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static byte[] merge(byte[] a, byte[] b)
|
||||
{
|
||||
if(a == null && b == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if(a == null)
|
||||
{
|
||||
return b;
|
||||
}
|
||||
else if(b == null)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
byte[] c = new byte[a.length + b.length];
|
||||
System.arraycopy(a,0,c,0,a.length);
|
||||
System.arraycopy(b,0,c,a.length,b.length);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
mLog.debug("Starting ...");
|
||||
|
||||
// MP3SilenceGenerator generator = new MP3SilenceGenerator();
|
||||
//
|
||||
// int sampleCount = 0;
|
||||
//
|
||||
// int block = 1;
|
||||
//
|
||||
// for(long x = 0; x < 5000; x ++)
|
||||
// {
|
||||
// byte[] silence = generator.generate(block);
|
||||
//
|
||||
// sampleCount += block;
|
||||
//
|
||||
// if(silence.length > 0)
|
||||
// {
|
||||
// mLog.debug("Silence:" + x + " Count:" + sampleCount + " Length:" + silence.length);
|
||||
// }
|
||||
// }
|
||||
|
||||
for(int x = 1; x <= 288; x++)
|
||||
for(long x = 243; x < 500; x ++)
|
||||
{
|
||||
mLog.debug(x + ": " + (x / 144));
|
||||
MP3SilenceGenerator generator = new MP3SilenceGenerator();
|
||||
|
||||
byte[] silence = generator.generate(x);
|
||||
|
||||
if(silence != null && silence.length > 0)
|
||||
{
|
||||
mLog.debug("Silence:" + x + " Length:" + silence.length);
|
||||
MP3FrameInspector.inspect(silence);
|
||||
}
|
||||
}
|
||||
|
||||
mLog.debug("Finished");
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* sdrtrunk
|
||||
* Copyright (C) 2014-2016 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
******************************************************************************/
|
||||
package record.mp3;
|
||||
|
||||
import audio.broadcast.BroadcastModel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import properties.SystemProperties;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class MP3FileInspector
|
||||
{
|
||||
private final static Logger mLog = LoggerFactory.getLogger(MP3FileInspector.class);
|
||||
|
||||
private Path mPath;
|
||||
|
||||
public MP3FileInspector(Path path)
|
||||
{
|
||||
mPath = path;
|
||||
}
|
||||
|
||||
public void inspect()
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] audio = Files.readAllBytes(mPath);
|
||||
|
||||
mLog.debug("File:" + mPath.toAbsolutePath().toString() + " Length:" + audio.length + " Residual:" + audio.length % 144);
|
||||
|
||||
// mLog.debug(format(Arrays.copyOfRange(audio, 0, 20)));
|
||||
// for(int x = 0; x < audio.length - 3; x++)
|
||||
// {
|
||||
// if(audio[x] == (byte)0xFF && ((audio[x + 1] & 0xE0) == 0xE0))
|
||||
// {
|
||||
// mLog.debug("\tFrame: " + x + "\t" + format(Arrays.copyOfRange(audio, x, x + 4)));
|
||||
//
|
||||
// x += 143;
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error reading temporary audio stream recording [" + mPath.toString() +
|
||||
"] - skipping recording");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args)
|
||||
{
|
||||
mLog.debug("Starting ...");
|
||||
|
||||
Path path = SystemProperties.getInstance().getApplicationFolder(BroadcastModel.TEMPORARY_STREAM_DIRECTORY);
|
||||
|
||||
if(path != null && Files.isDirectory(path))
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(BroadcastModel.TEMPORARY_STREAM_FILE_SUFFIX);
|
||||
sb.append("*.*");
|
||||
|
||||
try(DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path, sb.toString()))
|
||||
{
|
||||
directoryStream.forEach(new Consumer<Path>()
|
||||
{
|
||||
@Override
|
||||
public void accept(Path path)
|
||||
{
|
||||
MP3FileInspector inspector = new MP3FileInspector(path);
|
||||
inspector.inspect();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(IOException ioe)
|
||||
{
|
||||
mLog.error("Error discovering orphaned temporary stream recording files", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
mLog.debug("Finished");
|
||||
}
|
||||
|
||||
private String format(byte[] bytes)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
for(byte b: bytes)
|
||||
{
|
||||
sb.append(String.format("%02X", b));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user