diff --git a/config/logback.xml b/config/logback.xml
index 42146503..9fe5f368 100644
--- a/config/logback.xml
+++ b/config/logback.xml
@@ -27,13 +27,7 @@
-
-
-
-
+
-
-
-
\ No newline at end of file
diff --git a/src/audio/broadcast/AudioBroadcaster.java b/src/audio/broadcast/AudioBroadcaster.java
index 8688b04f..2fd41747 100644
--- a/src/audio/broadcast/AudioBroadcaster.java
+++ b/src/audio/broadcast/AudioBroadcaster.java
@@ -20,6 +20,7 @@ package audio.broadcast;
import audio.AudioPacket;
import audio.IAudioPacketListener;
+import audio.convert.ISilenceGenerator;
import audio.metadata.AudioMetadata;
import controller.ThreadPoolManager;
import org.slf4j.Logger;
@@ -47,7 +48,7 @@ public abstract class AudioBroadcaster implements IAudioPacketListener
private RecordingQueueProcessor mRecordingQueueProcessor = new RecordingQueueProcessor();
private LinkedTransferQueue mAudioRecordingQueue = new LinkedTransferQueue<>();
- private byte[] mSilenceFrame;
+ private ISilenceGenerator mSilenceGenerator;
private Listener mBroadcastEventListener;
private BroadcastState mBroadcastState = BroadcastState.READY;
@@ -85,8 +86,7 @@ public abstract class AudioBroadcaster implements IAudioPacketListener
mBroadcastConfiguration = broadcastConfiguration;
mDelay = mBroadcastConfiguration.getDelay();
- //Create a 1 second silence frame - from 1200 millis of silence
- mSilenceFrame = BroadcastFactory.getSilenceFrame(getBroadcastConfiguration().getBroadcastFormat(), 1200);
+ mSilenceGenerator = BroadcastFactory.getSilenceGenerator(broadcastConfiguration.getBroadcastFormat());
mStreamManager = new StreamManager(threadPoolManager, this,
SystemProperties.getInstance().getApplicationFolder(BroadcastModel.TEMPORARY_STREAM_DIRECTORY));
@@ -326,7 +326,8 @@ public abstract class AudioBroadcaster implements IAudioPacketListener
{
private AtomicBoolean mProcessing = new AtomicBoolean();
private ByteArrayInputStream mInputStream;
- private int mChunkSize;
+ private int mAverageBytesPerInterval;
+ private long mFinalSilencePadding = 0;
@Override
public void run()
@@ -335,12 +336,18 @@ public abstract class AudioBroadcaster implements IAudioPacketListener
{
if(mInputStream == null || mInputStream.available() <= 0)
{
+ if(mFinalSilencePadding > 0)
+ {
+ broadcastAudio(mSilenceGenerator.generate(mFinalSilencePadding));
+ mFinalSilencePadding = 0;
+ }
+
nextRecording();
}
if(mInputStream != null)
{
- int length = Math.min(mChunkSize, mInputStream.available());
+ int length = Math.min(mAverageBytesPerInterval, mInputStream.available());
byte[] audio = new byte[length];
@@ -357,7 +364,7 @@ public abstract class AudioBroadcaster implements IAudioPacketListener
}
else
{
- broadcastAudio(mSilenceFrame);
+ broadcastAudio(mSilenceGenerator.generate(PROCESSOR_RUN_INTERVAL_MS));
}
mProcessing.set(false);
@@ -395,17 +402,21 @@ public abstract class AudioBroadcaster implements IAudioPacketListener
{
mInputStream = new ByteArrayInputStream(audio);
- int wholeIntervalChunks = (int)(recording.getRecordingLength() / PROCESSOR_RUN_INTERVAL_MS);
+ double intervals = (double)recording.getRecordingLength() / (double)PROCESSOR_RUN_INTERVAL_MS;
- //Check for divide by zero situation
- if(wholeIntervalChunks == 0)
+ mAverageBytesPerInterval = (int)((double)mInputStream.available() / intervals);
+
+ int wholeIntervals = (int)Math.floor(intervals) + 1;
+
+ //Silence padding to transmit at the end to keep stream length at multiples of run interval
+ mFinalSilencePadding = (wholeIntervals * PROCESSOR_RUN_INTERVAL_MS) -
+ recording.getRecordingLength();
+
+ if(connected())
{
- wholeIntervalChunks = 1;
+ broadcastMetadata(recording.getAudioMetadata());
}
- mChunkSize = (int)(mInputStream.available() / wholeIntervalChunks) + 1;
-
- broadcastMetadata(recording.getAudioMetadata());
metadataUpdateRequired = false;
}
}
@@ -423,7 +434,7 @@ public abstract class AudioBroadcaster implements IAudioPacketListener
}
//If we closed out a recording and don't have anything new, send an empty metadata update
- if(metadataUpdateRequired)
+ if(metadataUpdateRequired && connected())
{
broadcastMetadata(null);
}
diff --git a/src/audio/broadcast/BroadcastFactory.java b/src/audio/broadcast/BroadcastFactory.java
index b9bf1226..2677e9cd 100644
--- a/src/audio/broadcast/BroadcastFactory.java
+++ b/src/audio/broadcast/BroadcastFactory.java
@@ -33,7 +33,9 @@ import audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
import audio.broadcast.shoutcast.v2.ShoutcastV2AudioBroadcaster;
import audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
import audio.convert.IAudioConverter;
+import audio.convert.ISilenceGenerator;
import audio.convert.MP3AudioConverter;
+import audio.convert.MP3SilenceGenerator;
import controller.ThreadPoolManager;
import gui.editor.Editor;
import gui.editor.EmptyEditor;
@@ -53,6 +55,20 @@ public class BroadcastFactory
public static final int MP3_MONO_16_KHZ_BITRATE = 16;
public static final boolean MP3_CONSTANT_BITRATE = false;
+ private static MP3AudioConverter MP3_SILENT_FRAME_GENERATOR;
+
+ static
+ {
+ MP3_SILENT_FRAME_GENERATOR = new MP3AudioConverter(MP3Recorder.MP3_BIT_RATE, MP3Recorder.CONSTANT_BIT_RATE);
+
+ //We have to charge the converter pipeline with 1200 milliseconds to get 1 second of audio the first time
+ float[] silence = new float[9600]; //1200 milliseconds at 8000 kHz
+ AudioPacket silencePacket = new AudioPacket(silence, null);
+ List silencePackets = new ArrayList<>();
+ silencePackets.add(silencePacket);
+ MP3_SILENT_FRAME_GENERATOR.convert(silencePackets);
+ }
+
/**
* Creates an audio streaming broadcaster for the configuration
@@ -194,23 +210,15 @@ public class BroadcastFactory
}
}
- public static byte[] getSilenceFrame(BroadcastFormat format, long duration)
+ public static ISilenceGenerator getSilenceGenerator(BroadcastFormat format)
{
- int length = (int)(duration * 8); //8000 Hz sample rate
- float[] silence = new float[length];
- AudioPacket silencePacket = new AudioPacket(silence, null);
- List silencePackets = new ArrayList<>();
- silencePackets.add(silencePacket);
-
- switch(format)
+ switch (format)
{
case MP3:
- MP3AudioConverter converter = new MP3AudioConverter(MP3Recorder.MP3_BIT_RATE,
- MP3Recorder.CONSTANT_BIT_RATE);
-
- return converter.convert(silencePackets);
+ return new MP3SilenceGenerator();
default:
- throw new IllegalArgumentException("Unrecognized broadcast format: " + format.name());
+ throw new IllegalArgumentException("Unrecognized broadcast format [" + format +
+ "] can't create silence generator");
}
}
}
diff --git a/src/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java b/src/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java
index 4a06907e..d40f9e32 100644
--- a/src/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java
+++ b/src/audio/broadcast/icecast/IcecastHTTPAudioBroadcaster.java
@@ -80,16 +80,13 @@ public class IcecastHTTPAudioBroadcaster extends IcecastAudioBroadcaster
@Override
protected void broadcastAudio(byte[] audio)
{
- if(connect())
+ if(audio != null && audio.length > 0 && connect() && mStreamingSession != null && mStreamingSession.isConnected())
{
- if(mStreamingSession != null && mStreamingSession.isConnected())
- {
- IoBuffer buffer = IoBuffer.allocate(audio.length);
- buffer.put(audio);
- buffer.flip();
+ IoBuffer buffer = IoBuffer.allocate(audio.length);
+ buffer.put(audio);
+ buffer.flip();
- mStreamingSession.write(buffer);
- }
+ mStreamingSession.write(buffer);
}
}
diff --git a/src/audio/broadcast/icecast/IcecastMetadataUpdater.java b/src/audio/broadcast/icecast/IcecastMetadataUpdater.java
index c290fb33..002a2790 100644
--- a/src/audio/broadcast/icecast/IcecastMetadataUpdater.java
+++ b/src/audio/broadcast/icecast/IcecastMetadataUpdater.java
@@ -93,7 +93,8 @@ public class IcecastMetadataUpdater
{
//Single-use session - close it after we receive an error
session.closeNow();
- mLog.error("Metadata update failed", cause);
+
+ mLog.error("Metadata update failed");
}
@Override
diff --git a/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java b/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java
index c94cf7b9..c42cc11d 100644
--- a/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java
+++ b/src/audio/broadcast/icecast/IcecastTCPAudioBroadcaster.java
@@ -73,12 +73,9 @@ public class IcecastTCPAudioBroadcaster extends IcecastAudioBroadcaster
@Override
protected void broadcastAudio(byte[] audio)
{
- if(connect())
+ if(audio != null && audio.length > 0 && connect() && mStreamingSession != null && mStreamingSession.isConnected())
{
- if(mStreamingSession != null && mStreamingSession.isConnected())
- {
- mStreamingSession.write(audio);
- }
+ mStreamingSession.write(audio);
}
}
diff --git a/src/audio/convert/ISilenceGenerator.java b/src/audio/convert/ISilenceGenerator.java
new file mode 100644
index 00000000..ac2e1c5c
--- /dev/null
+++ b/src/audio/convert/ISilenceGenerator.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * 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
+ *
+ ******************************************************************************/
+package audio.convert;
+
+public interface ISilenceGenerator
+{
+ byte[] generate(long duration);
+}
diff --git a/src/audio/convert/MP3SilenceGenerator.java b/src/audio/convert/MP3SilenceGenerator.java
new file mode 100644
index 00000000..7b088506
--- /dev/null
+++ b/src/audio/convert/MP3SilenceGenerator.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * 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
+ *
+ ******************************************************************************/
+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.List;
+
+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);
+
+ /**
+ * Generates MP3 audio silence frames
+ */
+ public MP3SilenceGenerator()
+ {
+ }
+
+ public byte[] generate(long duration)
+ {
+ int length = (int)(duration * 8); //8000 Hz sample rate
+ float[] silence = new float[length];
+ AudioPacket silencePacket = new AudioPacket(silence, null);
+ List silencePackets = new ArrayList<>();
+ silencePackets.add(silencePacket);
+ return mGenerator.convert(silencePackets);
+ }
+
+ public static void main(String[] args)
+ {
+ mLog.debug("Starting ...");
+
+ MP3SilenceGenerator generator = new MP3SilenceGenerator();
+
+ for(long x = 0; x < 20; x ++)
+ {
+ byte[] silence = generator.generate(1000);
+ mLog.debug("Silence:" + x + " Length:" + silence.length);
+ }
+
+ mLog.debug("Finished");
+ }
+}