mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-15 01:57:39 -05:00
#30 - WIP Streaming audio support. Adds Shoutcast V1 handler.
This commit is contained in:
2
.idea/dictionaries/denny.xml
generated
2
.idea/dictionaries/denny.xml
generated
@@ -1,10 +1,12 @@
|
|||||||
<component name="ProjectDictionaryState">
|
<component name="ProjectDictionaryState">
|
||||||
<dictionary name="denny">
|
<dictionary name="denny">
|
||||||
<words>
|
<words>
|
||||||
|
<w>bahroo</w>
|
||||||
<w>esnid</w>
|
<w>esnid</w>
|
||||||
<w>fleetsync</w>
|
<w>fleetsync</w>
|
||||||
<w>ident</w>
|
<w>ident</w>
|
||||||
<w>polyphase</w>
|
<w>polyphase</w>
|
||||||
|
<w>shoutcast</w>
|
||||||
<w>talkgroup</w>
|
<w>talkgroup</w>
|
||||||
<w>tgid</w>
|
<w>tgid</w>
|
||||||
</words>
|
</words>
|
||||||
|
|||||||
3
.idea/libraries/imports.xml
generated
3
.idea/libraries/imports.xml
generated
@@ -21,9 +21,10 @@
|
|||||||
<root url="jar://$PROJECT_DIR$/imports/usb4java-javax-1.2.0.jar!/" />
|
<root url="jar://$PROJECT_DIR$/imports/usb4java-javax-1.2.0.jar!/" />
|
||||||
<root url="jar://$PROJECT_DIR$/imports/jsr80-1.0.1.jar!/" />
|
<root url="jar://$PROJECT_DIR$/imports/jsr80-1.0.1.jar!/" />
|
||||||
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-linux-arm.jar!/" />
|
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-linux-arm.jar!/" />
|
||||||
<root url="jar://$PROJECT_DIR$/imports/jmbe-interface-0.3.2.jar!/" />
|
|
||||||
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-osx-x86_64.jar!/" />
|
<root url="jar://$PROJECT_DIR$/imports/libusb4java-1.2.0-osx-x86_64.jar!/" />
|
||||||
<root url="jar://$PROJECT_DIR$/imports/JTransforms-3.0.jar!/" />
|
<root url="jar://$PROJECT_DIR$/imports/JTransforms-3.0.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/imports/jmbe-interface-0.3.3.jar!/" />
|
||||||
|
<root url="jar://$PROJECT_DIR$/imports/java-lame-3.98.4.jar!/" />
|
||||||
</CLASSES>
|
</CLASSES>
|
||||||
<JAVADOC />
|
<JAVADOC />
|
||||||
<SOURCES />
|
<SOURCES />
|
||||||
|
|||||||
9
.idea/libraries/jmbe_interface_0_3_3.xml
generated
9
.idea/libraries/jmbe_interface_0_3_3.xml
generated
@@ -1,9 +0,0 @@
|
|||||||
<component name="libraryTable">
|
|
||||||
<library name="jmbe-interface-0.3.3">
|
|
||||||
<CLASSES>
|
|
||||||
<root url="jar://$PROJECT_DIR$/imports/jmbe-interface-0.3.3.jar!/" />
|
|
||||||
</CLASSES>
|
|
||||||
<JAVADOC />
|
|
||||||
<SOURCES />
|
|
||||||
</library>
|
|
||||||
</component>
|
|
||||||
9
.idea/libraries/jump3r_1_0_3.xml
generated
9
.idea/libraries/jump3r_1_0_3.xml
generated
@@ -1,9 +0,0 @@
|
|||||||
<component name="libraryTable">
|
|
||||||
<library name="jump3r-1.0.3">
|
|
||||||
<CLASSES>
|
|
||||||
<root url="jar://$PROJECT_DIR$/imports/jump3r-1.0.3.jar!/" />
|
|
||||||
</CLASSES>
|
|
||||||
<JAVADOC />
|
|
||||||
<SOURCES />
|
|
||||||
</library>
|
|
||||||
</component>
|
|
||||||
BIN
imports/java-lame-3.98.4.jar
Normal file
BIN
imports/java-lame-3.98.4.jar
Normal file
Binary file not shown.
Binary file not shown.
@@ -20,7 +20,5 @@
|
|||||||
<SOURCES />
|
<SOURCES />
|
||||||
</library>
|
</library>
|
||||||
</orderEntry>
|
</orderEntry>
|
||||||
<orderEntry type="library" name="jmbe-interface-0.3.3" level="project" />
|
|
||||||
<orderEntry type="library" name="jump3r-1.0.3" level="project" />
|
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
58
src/audio/AudioUtils.java
Normal file
58
src/audio/AudioUtils.java
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 audio;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import record.wave.RealBufferWaveRecorder;
|
||||||
|
import sample.ConversionUtils;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class AudioUtils
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(AudioUtils.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the audio packets into a byte array of 16-bit, little-endian audio samples
|
||||||
|
*/
|
||||||
|
public static byte[] convertTo16BitSamples(List<AudioPacket> audioPackets)
|
||||||
|
{
|
||||||
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for(AudioPacket packet: audioPackets)
|
||||||
|
{
|
||||||
|
//Converting from 32-bit floats to signed 16-bit samples
|
||||||
|
ByteBuffer buffer = ConversionUtils.convertToSigned16BitSamples(packet.getAudioBuffer().getSamples());
|
||||||
|
stream.write(buffer.array());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
mLog.error("Error writing converted PCM bytes to output stream");
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,18 +16,17 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
*
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package audio.stream;
|
package audio.broadcast;
|
||||||
|
|
||||||
import audio.AudioPacket;
|
import audio.AudioPacket;
|
||||||
import audio.IAudioPacketListener;
|
|
||||||
import controller.ThreadPoolManager;
|
import controller.ThreadPoolManager;
|
||||||
import sample.Listener;
|
import sample.Listener;
|
||||||
|
|
||||||
public class StreamManager implements IAudioPacketListener, Listener<AudioPacket>
|
public class AudioBroadcastManager implements Listener<AudioPacket>
|
||||||
{
|
{
|
||||||
private ThreadPoolManager mThreadPoolManager;
|
private ThreadPoolManager mThreadPoolManager;
|
||||||
|
|
||||||
public StreamManager(ThreadPoolManager threadPoolManager)
|
public AudioBroadcastManager(ThreadPoolManager threadPoolManager)
|
||||||
{
|
{
|
||||||
mThreadPoolManager = threadPoolManager;
|
mThreadPoolManager = threadPoolManager;
|
||||||
}
|
}
|
||||||
@@ -37,10 +36,4 @@ public class StreamManager implements IAudioPacketListener, Listener<AudioPacket
|
|||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Listener<AudioPacket> getAudioPacketListener()
|
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -16,24 +16,26 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
*
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package audio.stream;
|
package audio.broadcast;
|
||||||
|
|
||||||
|
import audio.broadcast.configuration.BroadcastConfiguration;
|
||||||
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
import javax.swing.table.AbstractTableModel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class StreamModel extends AbstractTableModel
|
public class AudioBroadcastModel extends AbstractTableModel
|
||||||
{
|
{
|
||||||
private static final int COLUMN_CHANNEL_NAME = 0;
|
private static final int COLUMN_CHANNEL_NAME = 0;
|
||||||
private static final int COLUMN_CHANNEL_FORMAT = 1;
|
private static final int COLUMN_CHANNEL_FORMAT = 1;
|
||||||
private static final int COLUMN_SERVER_NAME = 2;
|
private static final int COLUMN_SERVER_NAME = 2;
|
||||||
|
|
||||||
private List<Stream> mStreams = new ArrayList<>();
|
private List<BroadcastConfiguration> mBroadcastConfigurations = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getRowCount()
|
public int getRowCount()
|
||||||
{
|
{
|
||||||
return mStreams.size();
|
return mBroadcastConfigurations.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -45,20 +47,20 @@ public class StreamModel extends AbstractTableModel
|
|||||||
@Override
|
@Override
|
||||||
public Object getValueAt(int rowIndex, int columnIndex)
|
public Object getValueAt(int rowIndex, int columnIndex)
|
||||||
{
|
{
|
||||||
if( rowIndex <= mStreams.size())
|
if( rowIndex <= mBroadcastConfigurations.size())
|
||||||
{
|
{
|
||||||
Stream stream = mStreams.get(rowIndex);
|
BroadcastConfiguration configuration = mBroadcastConfigurations.get(rowIndex);
|
||||||
|
|
||||||
if(stream != null)
|
if(configuration != null)
|
||||||
{
|
{
|
||||||
switch(columnIndex)
|
switch(columnIndex)
|
||||||
{
|
{
|
||||||
case COLUMN_CHANNEL_NAME:
|
case COLUMN_CHANNEL_NAME:
|
||||||
return stream.getChannel().getName();
|
return configuration.getAlias();
|
||||||
case COLUMN_CHANNEL_FORMAT:
|
case COLUMN_CHANNEL_FORMAT:
|
||||||
return stream.getChannel().getSourceFormat().name();
|
return configuration.getBroadcastFormat().name();
|
||||||
case COLUMN_SERVER_NAME:
|
case COLUMN_SERVER_NAME:
|
||||||
return stream.getServer().getName();
|
return configuration.getHost();
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
205
src/audio/broadcast/Broadcaster.java
Normal file
205
src/audio/broadcast/Broadcaster.java
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 audio.broadcast;
|
||||||
|
|
||||||
|
import audio.AudioPacket;
|
||||||
|
import audio.IAudioPacketListener;
|
||||||
|
import audio.broadcast.configuration.BroadcastConfiguration;
|
||||||
|
import audio.broadcast.handler.BroadcastHandler;
|
||||||
|
import audio.broadcast.handler.BroadcastState;
|
||||||
|
import controller.ThreadPoolManager;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sample.Listener;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class Broadcaster implements IAudioPacketListener, Listener<AudioPacket>
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger( Broadcaster.class );
|
||||||
|
|
||||||
|
private ThreadPoolManager mThreadPoolManager;
|
||||||
|
private ScheduledFuture mScheduledTask;
|
||||||
|
private AudioQueueProcessor mAudioQueueProcessor;
|
||||||
|
private LinkedTransferQueue<AudioPacket> mAudioQueue = new LinkedTransferQueue<>();
|
||||||
|
private BroadcastHandler mBroadcastHandler;
|
||||||
|
private boolean mEnabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcaster for sending broadcast audio to a remote broadcast server.
|
||||||
|
*/
|
||||||
|
public Broadcaster(ThreadPoolManager threadPoolManager, BroadcastHandler broadcastHandler)
|
||||||
|
{
|
||||||
|
mThreadPoolManager = threadPoolManager;
|
||||||
|
mBroadcastHandler = broadcastHandler;
|
||||||
|
mAudioQueueProcessor = new AudioQueueProcessor();
|
||||||
|
|
||||||
|
//Add broadcast state listener to stop processing if we have an error
|
||||||
|
mBroadcastHandler.addListener(new BroadcastStateMonitor());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast configuration used by this broadcaster
|
||||||
|
*/
|
||||||
|
public BroadcastConfiguration getBroadcastConfiguration()
|
||||||
|
{
|
||||||
|
return mBroadcastHandler.getBroadcastConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current state of the broadcaster's connection with the remote server
|
||||||
|
*/
|
||||||
|
public BroadcastState getBroadcastState()
|
||||||
|
{
|
||||||
|
return mBroadcastHandler.getBroadcastState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive broadcast state changes
|
||||||
|
*/
|
||||||
|
public void addListener(Listener<BroadcastState> listener)
|
||||||
|
{
|
||||||
|
mBroadcastHandler.addListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the listener from receiving broadcast state changes
|
||||||
|
*/
|
||||||
|
public void removeListener(Listener<BroadcastState> listener)
|
||||||
|
{
|
||||||
|
mBroadcastHandler.removeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IAudioPacketListener interface method
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Listener<AudioPacket> getAudioPacketListener()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects the broadcaster and halts all broadcasting.
|
||||||
|
*/
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
mEnabled = false;
|
||||||
|
stopProcessor();
|
||||||
|
mBroadcastHandler.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pauses the broadcaster.
|
||||||
|
* @param paused set to true to pause and false to continue
|
||||||
|
*/
|
||||||
|
public void setPaused(boolean paused)
|
||||||
|
{
|
||||||
|
if(mEnabled)
|
||||||
|
{
|
||||||
|
mBroadcastHandler.setPaused(paused);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary audio packet input method. Audio packets are queued and then processed by the underlying broadcast
|
||||||
|
* handler for broadcast to the remote server.
|
||||||
|
*/
|
||||||
|
public void receive(AudioPacket packet)
|
||||||
|
{
|
||||||
|
if(mEnabled)
|
||||||
|
{
|
||||||
|
startProcessor();
|
||||||
|
mAudioQueue.add(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the audio queue processor. The audio packet queue is serviced every 100 milliseconds. Subsequent calls
|
||||||
|
* to this method once running are ignored.
|
||||||
|
*/
|
||||||
|
private void startProcessor()
|
||||||
|
{
|
||||||
|
if(mEnabled && mScheduledTask == null)
|
||||||
|
{
|
||||||
|
if(mThreadPoolManager != null)
|
||||||
|
{
|
||||||
|
mScheduledTask = mThreadPoolManager.scheduleFixedRate(ThreadPoolManager.ThreadType.AUDIO_PROCESSING,
|
||||||
|
mAudioQueueProcessor, 100, TimeUnit.MILLISECONDS );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the audio queue processor
|
||||||
|
*/
|
||||||
|
private void stopProcessor()
|
||||||
|
{
|
||||||
|
if(mThreadPoolManager != null && mScheduledTask != null)
|
||||||
|
{
|
||||||
|
mThreadPoolManager.cancel(mScheduledTask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Monitors the broadcast handler for error state and halts audio broadcasting
|
||||||
|
*/
|
||||||
|
public class BroadcastStateMonitor implements Listener<BroadcastState>
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void receive(BroadcastState broadcastState)
|
||||||
|
{
|
||||||
|
if(broadcastState.isErrorState())
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes audio packets from the audio packet queue and broadcasts the audio
|
||||||
|
*/
|
||||||
|
public class AudioQueueProcessor implements Runnable
|
||||||
|
{
|
||||||
|
private AtomicBoolean mProcessing = new AtomicBoolean();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
if(mProcessing.compareAndSet(false, true))
|
||||||
|
{
|
||||||
|
List<AudioPacket> audioPackets = new ArrayList<>();
|
||||||
|
|
||||||
|
mAudioQueue.drainTo(audioPackets);
|
||||||
|
|
||||||
|
if(!audioPackets.isEmpty())
|
||||||
|
{
|
||||||
|
mBroadcastHandler.broadcast(audioPackets);
|
||||||
|
}
|
||||||
|
|
||||||
|
mProcessing.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
80
src/audio/broadcast/BroadcasterFactory.java
Normal file
80
src/audio/broadcast/BroadcasterFactory.java
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 audio.broadcast;
|
||||||
|
|
||||||
|
import audio.broadcast.configuration.BroadcastConfiguration;
|
||||||
|
import audio.broadcast.configuration.BroadcastFormat;
|
||||||
|
import audio.broadcast.configuration.ShoutcastV1Configuration;
|
||||||
|
import audio.broadcast.handler.ShoutcastV1Handler;
|
||||||
|
import audio.convert.IAudioConverter;
|
||||||
|
import audio.convert.MP3AudioConverter;
|
||||||
|
import controller.ThreadPoolManager;
|
||||||
|
|
||||||
|
public class BroadcasterFactory
|
||||||
|
{
|
||||||
|
public static final int MP3_MONO_16_KHZ_BITRATE = 16;
|
||||||
|
public static final boolean MP3_NO_VARIABLE_BITRATE = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an audio streaming broadcaster for the configuration
|
||||||
|
* @param threadPoolManager for creating an audio broadcasting connection thread
|
||||||
|
* @param configuration describing the server and audio types
|
||||||
|
* @return configured broadcaster or null
|
||||||
|
*/
|
||||||
|
public static Broadcaster getBroadcaster(ThreadPoolManager threadPoolManager,
|
||||||
|
BroadcastConfiguration configuration)
|
||||||
|
{
|
||||||
|
IAudioConverter converter = getAudioConverter(configuration.getBroadcastFormat());
|
||||||
|
|
||||||
|
if(converter != null)
|
||||||
|
{
|
||||||
|
switch(configuration.getBroadcastServerType())
|
||||||
|
{
|
||||||
|
case BROADCASTIFY:
|
||||||
|
break;
|
||||||
|
case ICECAST:
|
||||||
|
break;
|
||||||
|
case SHOUTCAST_V1:
|
||||||
|
return new Broadcaster(threadPoolManager,
|
||||||
|
new ShoutcastV1Handler((ShoutcastV1Configuration)configuration, converter));
|
||||||
|
case UNKNOWN:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an audio convert to convert from 8 kHz PCM audio to the specified format
|
||||||
|
* @param format to convert to
|
||||||
|
* @return audio convert or null
|
||||||
|
*/
|
||||||
|
public static IAudioConverter getAudioConverter(BroadcastFormat format)
|
||||||
|
{
|
||||||
|
switch(format)
|
||||||
|
{
|
||||||
|
case MP3:
|
||||||
|
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_NO_VARIABLE_BITRATE);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
134
src/audio/broadcast/configuration/BroadcastConfiguration.java
Normal file
134
src/audio/broadcast/configuration/BroadcastConfiguration.java
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 audio.broadcast.configuration;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
|
||||||
|
public abstract class BroadcastConfiguration
|
||||||
|
{
|
||||||
|
private BroadcastFormat mBroadcastFormat;
|
||||||
|
private String mAlias;
|
||||||
|
private String mHost;
|
||||||
|
private int mPort;
|
||||||
|
private String mPassword;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast audio streaming configuration. Describes the streaming server, broadcast user authentication and
|
||||||
|
* audio broadcast details to use when connecting to a streaming server and broadcasting audio.
|
||||||
|
*/
|
||||||
|
public BroadcastConfiguration(BroadcastFormat format)
|
||||||
|
{
|
||||||
|
mBroadcastFormat = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract BroadcastServerType getBroadcastServerType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias identifying this broadcast configuration.
|
||||||
|
*/
|
||||||
|
public String getAlias()
|
||||||
|
{
|
||||||
|
return mAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the alias name that identifies/describes this configuration
|
||||||
|
* @param alias
|
||||||
|
*/
|
||||||
|
public void setAlias(String alias)
|
||||||
|
{
|
||||||
|
mAlias = alias;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Streaming server host name.
|
||||||
|
*/
|
||||||
|
public String getHost()
|
||||||
|
{
|
||||||
|
return mHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Host name or IP address of the streaming server
|
||||||
|
* @param host
|
||||||
|
*/
|
||||||
|
public void setHost(String host)
|
||||||
|
{
|
||||||
|
mHost = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Streaming server port number;
|
||||||
|
*/
|
||||||
|
public int getPort()
|
||||||
|
{
|
||||||
|
return mPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Port number of the streaming server to where the broadcast will be directed.
|
||||||
|
* @param port
|
||||||
|
*/
|
||||||
|
public void setPort(int port)
|
||||||
|
{
|
||||||
|
mPort = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketAddress getAddress()
|
||||||
|
{
|
||||||
|
return new InetSocketAddress(getHost(), getPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Password to authenticate with the streaming server.
|
||||||
|
*/
|
||||||
|
public String getPassword()
|
||||||
|
{
|
||||||
|
return mPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the password used to authenticate the broadcast broadcast user to the streaming server
|
||||||
|
* @param password
|
||||||
|
*/
|
||||||
|
public void setPassword(String password)
|
||||||
|
{
|
||||||
|
mPassword = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio broadcast content type (e.g. mp3)
|
||||||
|
*/
|
||||||
|
public BroadcastFormat getBroadcastFormat()
|
||||||
|
{
|
||||||
|
return mBroadcastFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio broadcast content type (e.g. mp3)
|
||||||
|
* @param broadcastFormat
|
||||||
|
*/
|
||||||
|
public void setBroadcastFormat(BroadcastFormat broadcastFormat)
|
||||||
|
{
|
||||||
|
mBroadcastFormat = broadcastFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,19 +16,24 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
*
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package audio.stream.shout.source;
|
package audio.broadcast.configuration;
|
||||||
|
|
||||||
public abstract class ShoutSource implements IShoutSource
|
public class BroadcastConfigurationFactory
|
||||||
{
|
{
|
||||||
private ShoutSourceFormat mFormat;
|
public static BroadcastConfiguration getConfiguration(BroadcastServerType serverType, BroadcastFormat format)
|
||||||
|
{
|
||||||
|
switch(serverType)
|
||||||
|
{
|
||||||
|
case BROADCASTIFY:
|
||||||
|
break;
|
||||||
|
case ICECAST:
|
||||||
|
break;
|
||||||
|
case SHOUTCAST_V1:
|
||||||
|
return new ShoutcastV1Configuration(format);
|
||||||
|
case UNKNOWN:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
public ShoutSource(ShoutSourceFormat format)
|
return null;
|
||||||
{
|
|
||||||
mFormat = format;
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public ShoutSourceFormat getFormat()
|
|
||||||
{
|
|
||||||
return mFormat;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,20 +16,21 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
*
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package audio.stream.shout.server;
|
package audio.broadcast.configuration;
|
||||||
|
|
||||||
public abstract class ShoutServer implements IShoutServer
|
public enum BroadcastFormat
|
||||||
{
|
{
|
||||||
private String mName;
|
MP3("audio/mpeg");
|
||||||
|
|
||||||
public ShoutServer(String name)
|
private String mValue;
|
||||||
|
|
||||||
|
private BroadcastFormat(String value)
|
||||||
{
|
{
|
||||||
mName = name;
|
mValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public String getValue()
|
||||||
public String getName()
|
|
||||||
{
|
{
|
||||||
return mName;
|
return mValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
93
src/audio/broadcast/configuration/BroadcastMetadata.java
Normal file
93
src/audio/broadcast/configuration/BroadcastMetadata.java
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 audio.broadcast.configuration;
|
||||||
|
|
||||||
|
public enum BroadcastMetadata
|
||||||
|
{
|
||||||
|
AIM("icy-aim:"),
|
||||||
|
AUDIO_BIT_RATE("icy-br:"),
|
||||||
|
CHANNELS("icy-channels:"),
|
||||||
|
CONTENT_TYPE("Content-Type:"),
|
||||||
|
DESCRIPTION("icy-description:"),
|
||||||
|
GENRE("icy-genre:"),
|
||||||
|
ICE_AUDIO_INFO("ice-audio-info:"),
|
||||||
|
ICQ("icy-icq:"),
|
||||||
|
INTERNET_RELAY_CHAT("icy-irc:"),
|
||||||
|
METADATA_INTERVAL("icy-metaint:"),
|
||||||
|
METADATA_REQUEST("icy-metadata:"),
|
||||||
|
PRE_BUFFER("icy-prebuffer:"),
|
||||||
|
PUBLIC("icy-pub:"),
|
||||||
|
RESET("icy-reset:"),
|
||||||
|
SERVER("server:"),
|
||||||
|
STREAM_NAME("icy-name:"),
|
||||||
|
SONG_TITLE("icy-title:"),
|
||||||
|
URL("icy-url:"),
|
||||||
|
USER_AGENT("User-Agent:"),
|
||||||
|
UNKNOWN("Unknown");
|
||||||
|
|
||||||
|
private String mTag;
|
||||||
|
|
||||||
|
private BroadcastMetadata(String tag)
|
||||||
|
{
|
||||||
|
mTag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String COMMAND_TERMINATOR = "\n";
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encodes the metadata tag and value with an end-of-line character, appropriate for sending over the network.
|
||||||
|
*/
|
||||||
|
public String encode(String value)
|
||||||
|
{
|
||||||
|
if(value != null && !value.isEmpty())
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(mTag).append(value).append(COMMAND_TERMINATOR);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encode(boolean value)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(mTag).append(value ? "1" : "0").append(COMMAND_TERMINATOR);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encode(BroadcastFormat broadcastFormat)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(mTag).append(broadcastFormat.getValue()).append(COMMAND_TERMINATOR);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encode(int value)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(mTag).append(String.valueOf(value)).append(COMMAND_TERMINATOR);
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,9 +16,13 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
*
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package audio.stream.shout.source;
|
package audio.broadcast.configuration;
|
||||||
|
|
||||||
public interface IShoutSource
|
public enum BroadcastServerType
|
||||||
{
|
{
|
||||||
public ShoutSourceFormat getFormat();
|
BROADCASTIFY,
|
||||||
|
ICECAST,
|
||||||
|
SHOUTCAST_V1,
|
||||||
|
SHOUTCAST_V2,
|
||||||
|
UNKNOWN;
|
||||||
}
|
}
|
||||||
141
src/audio/broadcast/configuration/ShoutcastV1Configuration.java
Normal file
141
src/audio/broadcast/configuration/ShoutcastV1Configuration.java
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 audio.broadcast.configuration;
|
||||||
|
|
||||||
|
public class ShoutcastV1Configuration extends BroadcastConfiguration
|
||||||
|
{
|
||||||
|
private String mStreamName;
|
||||||
|
private String mGenre;
|
||||||
|
private boolean mPublic;
|
||||||
|
private int mChannels;
|
||||||
|
private int mBitRate;
|
||||||
|
private String mURL;
|
||||||
|
|
||||||
|
public ShoutcastV1Configuration(BroadcastFormat format)
|
||||||
|
{
|
||||||
|
super(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BroadcastServerType getBroadcastServerType()
|
||||||
|
{
|
||||||
|
return BroadcastServerType.SHOUTCAST_V1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream name
|
||||||
|
*/
|
||||||
|
public String getStreamName()
|
||||||
|
{
|
||||||
|
return mStreamName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the stream name
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
public void setStreamName(String name)
|
||||||
|
{
|
||||||
|
mStreamName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream genre
|
||||||
|
*/
|
||||||
|
public String getGenre()
|
||||||
|
{
|
||||||
|
return mGenre;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream genre
|
||||||
|
* @param genre
|
||||||
|
*/
|
||||||
|
public void setGenre(String genre)
|
||||||
|
{
|
||||||
|
mGenre = genre;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public visibility of the broadcast
|
||||||
|
*/
|
||||||
|
public boolean isPublic()
|
||||||
|
{
|
||||||
|
return mPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets public visibility of the broadcast
|
||||||
|
* @param isPublic indicates if the broadcast should be visible to the public
|
||||||
|
*/
|
||||||
|
public void setPublic(boolean isPublic)
|
||||||
|
{
|
||||||
|
mPublic = isPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of audio channels in the broadcast
|
||||||
|
*/
|
||||||
|
public int getChannels()
|
||||||
|
{
|
||||||
|
return mChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the number of audio channels in the broadcast
|
||||||
|
*/
|
||||||
|
public void setChannels(int channels)
|
||||||
|
{
|
||||||
|
mChannels = channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bit rate in bits per second
|
||||||
|
*/
|
||||||
|
public int getBitRate()
|
||||||
|
{
|
||||||
|
return mBitRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bit rate
|
||||||
|
* @param bitRate in samples per second
|
||||||
|
*/
|
||||||
|
public void setBitRate(int bitRate)
|
||||||
|
{
|
||||||
|
mBitRate = bitRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL associated with the broadcast where users can find additional details.
|
||||||
|
*/
|
||||||
|
public String getURL()
|
||||||
|
{
|
||||||
|
return mURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL associated with the broadcast where users can find additional details.
|
||||||
|
* @param url
|
||||||
|
*/
|
||||||
|
public void setURL(String url)
|
||||||
|
{
|
||||||
|
mURL = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
179
src/audio/broadcast/configuration/ShoutcastV2Configuration.java
Normal file
179
src/audio/broadcast/configuration/ShoutcastV2Configuration.java
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 audio.broadcast.configuration;
|
||||||
|
|
||||||
|
public class ShoutcastV2Configuration extends BroadcastConfiguration
|
||||||
|
{
|
||||||
|
private String mMountPoint;
|
||||||
|
private String mStreamName;
|
||||||
|
private String mSongTitle;
|
||||||
|
private String mGenre;
|
||||||
|
private boolean mPublic;
|
||||||
|
private int mChannels;
|
||||||
|
private int mBitRate;
|
||||||
|
private String mURL;
|
||||||
|
|
||||||
|
|
||||||
|
public ShoutcastV2Configuration(BroadcastFormat format)
|
||||||
|
{
|
||||||
|
super(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BroadcastServerType getBroadcastServerType()
|
||||||
|
{
|
||||||
|
return BroadcastServerType.SHOUTCAST_V1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMountPoint()
|
||||||
|
{
|
||||||
|
return mMountPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the optional mount point. Shoutcast v1.x servers don't require a mount point, but v2.x servers do require
|
||||||
|
* the mount point.
|
||||||
|
*
|
||||||
|
* Note: if the supplied mount point contains a leading slash (/) it will be removed.
|
||||||
|
*
|
||||||
|
* @param mountPoint
|
||||||
|
*/
|
||||||
|
public void setMountPoint(String mountPoint)
|
||||||
|
{
|
||||||
|
//Strip the leading slash from the mount point if the user supplies it
|
||||||
|
mMountPoint = mountPoint.replace("/", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream name
|
||||||
|
*/
|
||||||
|
public String getStreamName()
|
||||||
|
{
|
||||||
|
return mStreamName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the stream name
|
||||||
|
* @param name
|
||||||
|
*/
|
||||||
|
public void setStreamName(String name)
|
||||||
|
{
|
||||||
|
mStreamName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Song title
|
||||||
|
*/
|
||||||
|
public String getSongTitle()
|
||||||
|
{
|
||||||
|
return mSongTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the song title
|
||||||
|
*/
|
||||||
|
public void setSongTitle(String title)
|
||||||
|
{
|
||||||
|
mSongTitle = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream genre
|
||||||
|
*/
|
||||||
|
public String getGenre()
|
||||||
|
{
|
||||||
|
return mGenre;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream genre
|
||||||
|
* @param genre
|
||||||
|
*/
|
||||||
|
public void setGenre(String genre)
|
||||||
|
{
|
||||||
|
mGenre = genre;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public visibility of the broadcast
|
||||||
|
*/
|
||||||
|
public boolean isPublic()
|
||||||
|
{
|
||||||
|
return mPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets public visibility of the broadcast
|
||||||
|
* @param isPublic indicates if the broadcast should be visible to the public
|
||||||
|
*/
|
||||||
|
public void setPublic(boolean isPublic)
|
||||||
|
{
|
||||||
|
mPublic = isPublic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of audio channels in the broadcast
|
||||||
|
*/
|
||||||
|
public int getChannels()
|
||||||
|
{
|
||||||
|
return mChannels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the number of audio channels in the broadcast
|
||||||
|
*/
|
||||||
|
public void setChannels(int channels)
|
||||||
|
{
|
||||||
|
mChannels = channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bit rate in bits per second
|
||||||
|
*/
|
||||||
|
public int getBitRate()
|
||||||
|
{
|
||||||
|
return mBitRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bit rate
|
||||||
|
* @param bitRate in samples per second
|
||||||
|
*/
|
||||||
|
public void setBitRate(int bitRate)
|
||||||
|
{
|
||||||
|
mBitRate = bitRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL associated with the broadcast where users can find additional details.
|
||||||
|
*/
|
||||||
|
public String getURL()
|
||||||
|
{
|
||||||
|
return mURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL associated with the broadcast where users can find additional details.
|
||||||
|
* @param url
|
||||||
|
*/
|
||||||
|
public void setURL(String url)
|
||||||
|
{
|
||||||
|
mURL = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
140
src/audio/broadcast/handler/BroadcastHandler.java
Normal file
140
src/audio/broadcast/handler/BroadcastHandler.java
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 audio.broadcast.handler;
|
||||||
|
|
||||||
|
import audio.AudioPacket;
|
||||||
|
import audio.broadcast.configuration.BroadcastConfiguration;
|
||||||
|
import audio.convert.IAudioConverter;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sample.Listener;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
|
public abstract class BroadcastHandler
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger( BroadcastHandler.class );
|
||||||
|
|
||||||
|
protected BroadcastConfiguration mBroadcastConfiguration;
|
||||||
|
protected IAudioConverter mAudioConverter;
|
||||||
|
private List<Listener<BroadcastState>> mBroadcastStateListeners = new CopyOnWriteArrayList<>();
|
||||||
|
private BroadcastState mBroadcastState = BroadcastState.READY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract audio broadcast handler. Protocol-specific implementations will manage a connection with the remote
|
||||||
|
* broadcast audio server, convert audio using the audio converter argument and dispatch the converted audio to
|
||||||
|
* the remote server.
|
||||||
|
*
|
||||||
|
* @param broadcastConfiguration for configuring the handler
|
||||||
|
* @param audioConverter to convert audio packets to the desired output format
|
||||||
|
*/
|
||||||
|
public BroadcastHandler(BroadcastConfiguration broadcastConfiguration, IAudioConverter audioConverter)
|
||||||
|
{
|
||||||
|
mBroadcastConfiguration = broadcastConfiguration;
|
||||||
|
mAudioConverter = audioConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BroadcastConfiguration getBroadcastConfiguration()
|
||||||
|
{
|
||||||
|
return mBroadcastConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio converter
|
||||||
|
*/
|
||||||
|
protected IAudioConverter getAudioConverter()
|
||||||
|
{
|
||||||
|
return mAudioConverter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes and broadcasts the audio packets
|
||||||
|
*/
|
||||||
|
public abstract void broadcast(List<AudioPacket> audioPackets);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnects from the remote server and disables the broadcaster.
|
||||||
|
*/
|
||||||
|
public abstract void disconnect();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pauses or unpauses the broadcast
|
||||||
|
*/
|
||||||
|
public void setPaused(boolean paused)
|
||||||
|
{
|
||||||
|
if(!getBroadcastState().isErrorState())
|
||||||
|
{
|
||||||
|
if(paused)
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.PAUSED);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.READY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the listener to receive broadcast state changes
|
||||||
|
*/
|
||||||
|
public void addListener(Listener<BroadcastState> listener)
|
||||||
|
{
|
||||||
|
mBroadcastStateListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the listener from receiving broadcast state changes
|
||||||
|
*/
|
||||||
|
public void removeListener(Listener<BroadcastState> listener)
|
||||||
|
{
|
||||||
|
mBroadcastStateListeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the state of the broadcast connection
|
||||||
|
*/
|
||||||
|
protected void setBroadcastState(BroadcastState state)
|
||||||
|
{
|
||||||
|
if(mBroadcastState != state)
|
||||||
|
{
|
||||||
|
mLog.debug("Changing State to: " + state);
|
||||||
|
mBroadcastState = state;
|
||||||
|
|
||||||
|
for(Listener<BroadcastState> listener: mBroadcastStateListeners)
|
||||||
|
{
|
||||||
|
listener.receive(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current state of the broadcast connection
|
||||||
|
*/
|
||||||
|
public BroadcastState getBroadcastState()
|
||||||
|
{
|
||||||
|
return mBroadcastState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean connected()
|
||||||
|
{
|
||||||
|
return getBroadcastState() == BroadcastState.CONNECTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
91
src/audio/broadcast/handler/BroadcastState.java
Normal file
91
src/audio/broadcast/handler/BroadcastState.java
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 audio.broadcast.handler;
|
||||||
|
|
||||||
|
public enum BroadcastState
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Error while broadcasting stream data. Temporary error state to allow connection to be reset.
|
||||||
|
*/
|
||||||
|
BROADCAST_ERROR("Broadcast Error", false),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connected to the broadcast server and capable of streaming
|
||||||
|
*/
|
||||||
|
CONNECTED("Connected", false),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connection interrupted, attempting to reconnect.
|
||||||
|
*/
|
||||||
|
CONNECTING("Connecting", false),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnected from the broadcast server
|
||||||
|
*/
|
||||||
|
DISCONNECTED("Disconnected", true),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalid password
|
||||||
|
*/
|
||||||
|
INVALID_PASSWORD("Invalid Password", true),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server is not known or reachable
|
||||||
|
*/
|
||||||
|
NO_SERVER("No Server", false),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connected but paused. No audio is streaming.
|
||||||
|
*/
|
||||||
|
PAUSED("Paused", false),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial state with no connection attempted.
|
||||||
|
*/
|
||||||
|
READY("Ready", false),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server host name or port is invalid
|
||||||
|
*/
|
||||||
|
UNKNOWN_HOST("Unknown Host or Invalid Port", true),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unspecified error
|
||||||
|
*/
|
||||||
|
ERROR("Error", true);
|
||||||
|
|
||||||
|
private String mLabel;
|
||||||
|
private boolean mErrorState;
|
||||||
|
|
||||||
|
private BroadcastState(String label, boolean connected)
|
||||||
|
{
|
||||||
|
mLabel = label;
|
||||||
|
mErrorState = connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return mLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isErrorState()
|
||||||
|
{
|
||||||
|
return mErrorState;
|
||||||
|
}
|
||||||
|
}
|
||||||
379
src/audio/broadcast/handler/ShoutcastV1Handler.java
Normal file
379
src/audio/broadcast/handler/ShoutcastV1Handler.java
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 audio.broadcast.handler;
|
||||||
|
|
||||||
|
import audio.AudioPacket;
|
||||||
|
import audio.broadcast.Broadcaster;
|
||||||
|
import audio.broadcast.BroadcasterFactory;
|
||||||
|
import audio.broadcast.configuration.BroadcastFormat;
|
||||||
|
import audio.broadcast.configuration.BroadcastMetadata;
|
||||||
|
import audio.broadcast.configuration.ShoutcastV1Configuration;
|
||||||
|
import audio.convert.IAudioConverter;
|
||||||
|
import controller.ThreadPoolManager;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import record.wave.AudioPacketMonoWaveReader;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ShoutcastV1Handler extends BroadcastHandler
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV1Handler.class );
|
||||||
|
|
||||||
|
private static final long RECONNECT_INTERVAL_MILLISECONDS = 15000; //15 seconds
|
||||||
|
private long mLastConnectionAttempt = 0;
|
||||||
|
|
||||||
|
private Socket mSocket;
|
||||||
|
private DataOutputStream mOutputStream;
|
||||||
|
private DataInputStream mInputStream;
|
||||||
|
|
||||||
|
private static final int mBufferSize = 2000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Shoutcast Version 1 broadcast handler.
|
||||||
|
* @param configuration
|
||||||
|
* @param audioConverter
|
||||||
|
*/
|
||||||
|
public ShoutcastV1Handler(ShoutcastV1Configuration configuration, IAudioConverter audioConverter)
|
||||||
|
{
|
||||||
|
super(configuration, audioConverter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shoutcast V1 Configuration information
|
||||||
|
*/
|
||||||
|
private ShoutcastV1Configuration getShoutcastConfiguration()
|
||||||
|
{
|
||||||
|
return (ShoutcastV1Configuration)getBroadcastConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process audio packets using Shoutcast V1 Protocol
|
||||||
|
* @param audioPackets
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void broadcast(List<AudioPacket> audioPackets)
|
||||||
|
{
|
||||||
|
//If we're connected, send the audio, otherwise discard it
|
||||||
|
if(connect())
|
||||||
|
{
|
||||||
|
byte[] convertedAudio = mAudioConverter.convert(audioPackets);
|
||||||
|
|
||||||
|
if(convertedAudio != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
send(convertedAudio);
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
mLog.error("Error while dispatching audio", e);
|
||||||
|
setBroadcastState(BroadcastState.BROADCAST_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the audio handler is currently connected to the remote server and capable of streaming audio.
|
||||||
|
*
|
||||||
|
* This method will attempt to establish a connection or reconnect to the streaming server.
|
||||||
|
* @return true if the audio handler can stream audio
|
||||||
|
*/
|
||||||
|
private boolean connect()
|
||||||
|
{
|
||||||
|
if(!connected())
|
||||||
|
{
|
||||||
|
createConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
return connected();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from the remote server
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void disconnect()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a connnection to the remote server using the shoutcast configuration information. Once disconnected
|
||||||
|
* following a successful connection, attempts to reestablish a connection on a set interval
|
||||||
|
*/
|
||||||
|
private void createConnection()
|
||||||
|
{
|
||||||
|
if(!connected() && System.currentTimeMillis() - mLastConnectionAttempt >= RECONNECT_INTERVAL_MILLISECONDS)
|
||||||
|
{
|
||||||
|
createSocket();
|
||||||
|
|
||||||
|
if(mSocket.isConnected())
|
||||||
|
{
|
||||||
|
mLog.debug("Sending credentials");
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
//Password
|
||||||
|
sb.append(getShoutcastConfiguration().getPassword()).append(BroadcastMetadata.COMMAND_TERMINATOR);
|
||||||
|
|
||||||
|
//Metadata
|
||||||
|
sb.append(BroadcastMetadata.STREAM_NAME.encode(getShoutcastConfiguration().getStreamName()));
|
||||||
|
sb.append(BroadcastMetadata.URL.encode(getShoutcastConfiguration().getURL()));
|
||||||
|
sb.append(BroadcastMetadata.PUBLIC.encode(getShoutcastConfiguration().isPublic()));
|
||||||
|
sb.append(BroadcastMetadata.GENRE.encode(getShoutcastConfiguration().getGenre()));
|
||||||
|
sb.append(BroadcastMetadata.AUDIO_BIT_RATE.encode(getShoutcastConfiguration().getBitRate()));
|
||||||
|
|
||||||
|
//End of connection string
|
||||||
|
sb.append(BroadcastMetadata.COMMAND_TERMINATOR);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
send(sb.toString());
|
||||||
|
|
||||||
|
if(isInvalidResponse())
|
||||||
|
{
|
||||||
|
mLog.error("Invalid response after sending metadata");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
mLog.debug("Error while connecting to server", ioe);
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.debug("Connection established");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the string data to the remote server
|
||||||
|
* @param data to send
|
||||||
|
* @throws IOException if there is an error communicating with the remote server
|
||||||
|
*/
|
||||||
|
private void send(String data) throws IOException
|
||||||
|
{
|
||||||
|
mLog.debug("Sending [" + data + "]");
|
||||||
|
if(data != null && !data.isEmpty() && mOutputStream != null)
|
||||||
|
{
|
||||||
|
mOutputStream.writeBytes(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the byte data to the remote server
|
||||||
|
* @param data to send
|
||||||
|
* @throws IOException if there is an error communicating with the remote server
|
||||||
|
*/
|
||||||
|
private void send(byte[] data) throws IOException
|
||||||
|
{
|
||||||
|
mLog.debug("Sending bytes:" + data.length);
|
||||||
|
if(connected())
|
||||||
|
{
|
||||||
|
int sent = 0;
|
||||||
|
|
||||||
|
while(sent < data.length)
|
||||||
|
{
|
||||||
|
int available = data.length - sent;
|
||||||
|
|
||||||
|
int sending = available <mBufferSize ? available : mBufferSize;
|
||||||
|
|
||||||
|
mOutputStream.write(data, sent, sending);
|
||||||
|
|
||||||
|
sent += sending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getResponse() throws IOException
|
||||||
|
{
|
||||||
|
if(mInputStream != null)
|
||||||
|
{
|
||||||
|
int bytesAvailable = mInputStream.available();
|
||||||
|
|
||||||
|
if(bytesAvailable > 0)
|
||||||
|
{
|
||||||
|
byte[] responseBuffer = new byte[bytesAvailable];
|
||||||
|
|
||||||
|
int bytesRead = 0;
|
||||||
|
|
||||||
|
while(bytesRead < bytesAvailable)
|
||||||
|
{
|
||||||
|
bytesRead += mInputStream.read(responseBuffer, bytesRead, bytesAvailable - bytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(responseBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInvalidResponse()
|
||||||
|
{
|
||||||
|
String response;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response = getResponse();
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
mLog.error("Error while retrieving server response message", e);
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(response != null && !response.isEmpty())
|
||||||
|
{
|
||||||
|
if(response.startsWith("OK"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if(response.startsWith("Invalid Password"))
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.INVALID_PASSWORD);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.debug("Unrecognized server response:" + response);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a socket connection to the remote server and sets the state to CONNECTING.
|
||||||
|
*/
|
||||||
|
private void createSocket()
|
||||||
|
{
|
||||||
|
if(mSocket == null)
|
||||||
|
{
|
||||||
|
mLog.debug("Creating socket");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mSocket = new Socket(getShoutcastConfiguration().getHost(),
|
||||||
|
getShoutcastConfiguration().getPort());
|
||||||
|
mOutputStream = new DataOutputStream(mSocket.getOutputStream());
|
||||||
|
mInputStream = new DataInputStream(mSocket.getInputStream());
|
||||||
|
}
|
||||||
|
catch(UnknownHostException uhe)
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.UNKNOWN_HOST);
|
||||||
|
mLog.error("Unknown host or port. Unable to create connection to streaming server host[" +
|
||||||
|
getShoutcastConfiguration().getHost() + "] and port[" +
|
||||||
|
getShoutcastConfiguration().getPort() + "] - will reattempt connection periodically");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch(IOException ioe)
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
|
||||||
|
mLog.error("Error connecting to streaming server host[" +
|
||||||
|
getShoutcastConfiguration().getHost() + "] and port[" +
|
||||||
|
getShoutcastConfiguration().getPort() + "]", ioe);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mSocket.isConnected())
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.CONNECTING);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SocketAddress address = getShoutcastConfiguration().getAddress();
|
||||||
|
mSocket.connect(address);
|
||||||
|
setBroadcastState(BroadcastState.CONNECTING);
|
||||||
|
}
|
||||||
|
catch(UnknownHostException uhe)
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.UNKNOWN_HOST);
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
setBroadcastState(BroadcastState.ERROR);
|
||||||
|
|
||||||
|
mLog.error("Error connecting to streaming server host[" +
|
||||||
|
getShoutcastConfiguration().getHost() + "] and port[" +
|
||||||
|
getShoutcastConfiguration().getPort() + "]", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
ShoutcastV1Configuration config = new ShoutcastV1Configuration(BroadcastFormat.MP3);
|
||||||
|
config.setAlias("Test Configuration");
|
||||||
|
config.setHost("localhost");
|
||||||
|
config.setPort(8000);
|
||||||
|
config.setPassword("denny3");
|
||||||
|
config.setStreamName("Denny's Audio Broadcast Test");
|
||||||
|
config.setGenre("Scanner Audio");
|
||||||
|
config.setPublic(true);
|
||||||
|
config.setURL("http://localhost:8000");
|
||||||
|
config.setBitRate(16);
|
||||||
|
|
||||||
|
ThreadPoolManager threadPoolManager = new ThreadPoolManager();
|
||||||
|
|
||||||
|
final Broadcaster broadcaster = BroadcasterFactory.getBroadcaster(threadPoolManager,config);
|
||||||
|
|
||||||
|
Path path = Paths.get("/home/denny/Music/PCM.wav");
|
||||||
|
mLog.debug("Opening: " + path.toString());
|
||||||
|
|
||||||
|
mLog.debug("Registering and starting audio playback");
|
||||||
|
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
mLog.debug("Playback started [" + path.toString() + "]");
|
||||||
|
|
||||||
|
try (AudioPacketMonoWaveReader reader = new AudioPacketMonoWaveReader(path, true))
|
||||||
|
{
|
||||||
|
reader.setListener(broadcaster);
|
||||||
|
reader.read();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
mLog.error("Error", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.debug("Playback ended [" + path.toString() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,9 +16,13 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
*
|
*
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package audio.stream.shout.source;
|
package audio.convert;
|
||||||
|
|
||||||
public enum ShoutSourceFormat
|
import audio.AudioPacket;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface IAudioConverter
|
||||||
{
|
{
|
||||||
MP3;
|
public byte[] convert(List<AudioPacket> audioPackets);
|
||||||
}
|
}
|
||||||
77
src/audio/convert/MP3AudioConverter.java
Normal file
77
src/audio/convert/MP3AudioConverter.java
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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 audio.convert;
|
||||||
|
|
||||||
|
import audio.AudioFormats;
|
||||||
|
import audio.AudioPacket;
|
||||||
|
import audio.AudioUtils;
|
||||||
|
import net.sourceforge.lame.lowlevel.LameEncoder;
|
||||||
|
import net.sourceforge.lame.mp3.Lame;
|
||||||
|
import net.sourceforge.lame.mp3.MPEGMode;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MP3AudioConverter implements IAudioConverter
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger( MP3AudioConverter.class );
|
||||||
|
|
||||||
|
private LameEncoder mEncoder;
|
||||||
|
private ByteArrayOutputStream mMP3Stream = new ByteArrayOutputStream();
|
||||||
|
private byte[] mMP3Buffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts PCM 8kHz 16-bit Little Endian audio packets to Mono, Low Quality MP3 compressed audio.
|
||||||
|
*
|
||||||
|
* @param bitRate for converted MP3 audio
|
||||||
|
* @param variableBitRate (VBR) true or false for constant bit rate (CBR)
|
||||||
|
*/
|
||||||
|
public MP3AudioConverter(int bitRate, boolean variableBitRate)
|
||||||
|
{
|
||||||
|
mEncoder = new LameEncoder(AudioFormats.PCM_SIGNED_8KHZ_16BITS_MONO,
|
||||||
|
bitRate, MPEGMode.MONO, Lame.QUALITY_LOW, variableBitRate);
|
||||||
|
|
||||||
|
mMP3Buffer = new byte[mEncoder.getPCMBufferSize()];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] convert(List<AudioPacket> audioPackets)
|
||||||
|
{
|
||||||
|
mMP3Stream.reset();
|
||||||
|
|
||||||
|
byte[] pcmBytes = AudioUtils.convertTo16BitSamples(audioPackets);
|
||||||
|
|
||||||
|
int pcmBufferSize = Math.min(mMP3Buffer.length, pcmBytes.length);
|
||||||
|
|
||||||
|
int mp3BufferSize = 0;
|
||||||
|
|
||||||
|
int pcmBytesPosition = 0;
|
||||||
|
|
||||||
|
while (0 < (mp3BufferSize = mEncoder.encodeBuffer(pcmBytes, pcmBytesPosition, pcmBufferSize, mMP3Buffer)))
|
||||||
|
{
|
||||||
|
pcmBytesPosition += pcmBufferSize;
|
||||||
|
pcmBufferSize = Math.min(mMP3Buffer.length, pcmBytes.length - pcmBytesPosition);
|
||||||
|
mMP3Stream.write(mMP3Buffer, 0, mp3BufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mMP3Stream.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package audio.inverted;
|
package audio.invert;
|
||||||
|
|
||||||
import dsp.filter.FilterFactory;
|
import dsp.filter.FilterFactory;
|
||||||
import dsp.filter.FloatFIRFilter;
|
import dsp.filter.FloatFIRFilter;
|
||||||
@@ -23,7 +23,7 @@ import dsp.filter.Window.WindowType;
|
|||||||
import dsp.mixer.Oscillator;
|
import dsp.mixer.Oscillator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Based on the AudioType applied, produces normal, muted, or audio inverted
|
* Based on the AudioType applied, produces normal, muted, or audio invert
|
||||||
* audio output.
|
* audio output.
|
||||||
*/
|
*/
|
||||||
public class AudioAdapter
|
public class AudioAdapter
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package audio.inverted;
|
package audio.invert;
|
||||||
|
|
||||||
import audio.InversionFrequency;
|
import audio.InversionFrequency;
|
||||||
import sample.real.RealSampleListener;
|
import sample.real.RealSampleListener;
|
||||||
@@ -24,7 +24,7 @@ import dsp.filter.FloatFIRFilter;
|
|||||||
import dsp.mixer.Oscillator;
|
import dsp.mixer.Oscillator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies audio inversion (or un-inversion) to a stream of float audio samples
|
* Applies audio inversion (or un-inversion) to a broadcast of float audio samples
|
||||||
* by multiplying each successive sample by either a 1 or a -1 value.
|
* by multiplying each successive sample by either a 1 or a -1 value.
|
||||||
*/
|
*/
|
||||||
public class AudioInverter implements RealSampleListener
|
public class AudioInverter implements RealSampleListener
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package audio.inverted;
|
package audio.invert;
|
||||||
|
|
||||||
public enum AudioType
|
public enum AudioType
|
||||||
{
|
{
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package audio.inverted;
|
package audio.invert;
|
||||||
|
|
||||||
|
|
||||||
public interface IAudioTypeListener
|
public interface IAudioTypeListener
|
||||||
@@ -1,55 +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 audio.stream;
|
|
||||||
|
|
||||||
import audio.AudioPacket;
|
|
||||||
import audio.stream.shout.channel.IShoutChannel;
|
|
||||||
import audio.stream.shout.server.IShoutServer;
|
|
||||||
import sample.Listener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Streaming channel encapsulates communication with a specific server and channel.
|
|
||||||
*/
|
|
||||||
public class Stream implements Listener<AudioPacket>
|
|
||||||
{
|
|
||||||
private IShoutServer mServer;
|
|
||||||
private IShoutChannel mChannel;
|
|
||||||
|
|
||||||
public Stream(IShoutServer server, IShoutChannel channel)
|
|
||||||
{
|
|
||||||
mServer = server;
|
|
||||||
mChannel = channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IShoutServer getServer()
|
|
||||||
{
|
|
||||||
return mServer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IShoutChannel getChannel()
|
|
||||||
{
|
|
||||||
return mChannel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void receive(AudioPacket audioPacket)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +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 audio.stream.shout.channel;
|
|
||||||
|
|
||||||
import audio.stream.shout.source.ShoutSourceFormat;
|
|
||||||
|
|
||||||
public interface IShoutChannel
|
|
||||||
{
|
|
||||||
public String getName();
|
|
||||||
public ShoutSourceFormat getSourceFormat();
|
|
||||||
}
|
|
||||||
@@ -1,24 +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 audio.stream.shout.server;
|
|
||||||
|
|
||||||
public interface IShoutServer
|
|
||||||
{
|
|
||||||
public String getName();
|
|
||||||
}
|
|
||||||
@@ -1,27 +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 audio.stream.shout.source;
|
|
||||||
|
|
||||||
public class MP3ShoutSource extends ShoutSource
|
|
||||||
{
|
|
||||||
public MP3ShoutSource()
|
|
||||||
{
|
|
||||||
super(ShoutSourceFormat.MP3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -43,6 +43,7 @@ import javax.swing.JOptionPane;
|
|||||||
import javax.swing.JSeparator;
|
import javax.swing.JSeparator;
|
||||||
import javax.swing.KeyStroke;
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
|
import audio.broadcast.AudioBroadcastManager;
|
||||||
import map.IconManager;
|
import map.IconManager;
|
||||||
import map.MapService;
|
import map.MapService;
|
||||||
import module.log.EventLogManager;
|
import module.log.EventLogManager;
|
||||||
@@ -152,6 +153,9 @@ public class SDRTrunk implements Listener<TunerEvent>
|
|||||||
AudioManager audioManager = new AudioManager( threadPoolManager, sourceManager.getMixerManager() );
|
AudioManager audioManager = new AudioManager( threadPoolManager, sourceManager.getMixerManager() );
|
||||||
channelProcessingManager.addAudioPacketListener( audioManager );
|
channelProcessingManager.addAudioPacketListener( audioManager );
|
||||||
|
|
||||||
|
AudioBroadcastManager audioBroadcastManager = new AudioBroadcastManager(threadPoolManager);
|
||||||
|
channelProcessingManager.addAudioPacketListener(audioBroadcastManager);
|
||||||
|
|
||||||
MapService mapService = new MapService( mSettingsManager );
|
MapService mapService = new MapService( mSettingsManager );
|
||||||
channelProcessingManager.addMessageListener( mapService );
|
channelProcessingManager.addMessageListener( mapService );
|
||||||
|
|
||||||
|
|||||||
18
src/org/jma/encoder/audio/IAudioEncoder.java
Normal file
18
src/org/jma/encoder/audio/IAudioEncoder.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package org.jma.encoder.audio;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieved from: http://livertmpjavapublisher.blogspot.com/2011/06/orgtritonuslowlevellameapiiaudioencoder.html
|
||||||
|
* on 5 Sep 2016
|
||||||
|
*/
|
||||||
|
public interface IAudioEncoder
|
||||||
|
{
|
||||||
|
public int encodeBuffer(byte[] input, int offset, int encodedCount, byte[] output);
|
||||||
|
|
||||||
|
public int encodeFinish(byte[] input);
|
||||||
|
|
||||||
|
public int getInputBufferSize();
|
||||||
|
|
||||||
|
public int getOutputBufferSize();
|
||||||
|
|
||||||
|
public void close();
|
||||||
|
}
|
||||||
319
src/record/mp3/MP3Recorder.java
Normal file
319
src/record/mp3/MP3Recorder.java
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.AudioPacket;
|
||||||
|
import audio.IAudioPacketListener;
|
||||||
|
import audio.convert.MP3AudioConverter;
|
||||||
|
import module.Module;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import record.wave.AudioPacketMonoWaveReader;
|
||||||
|
import sample.Listener;
|
||||||
|
import util.TimeStamp;
|
||||||
|
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MP3 recorder for converting 8 kHz PCM audio packets to MP3 and writing to .mp3 file.
|
||||||
|
*/
|
||||||
|
public class MP3Recorder extends Module implements Listener<AudioPacket>, IAudioPacketListener
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger(MP3Recorder.class);
|
||||||
|
|
||||||
|
private MP3AudioConverter mMP3Converter;
|
||||||
|
private FileOutputStream mFileOutputStream;
|
||||||
|
private String mFilePrefix;
|
||||||
|
private Path mPath;
|
||||||
|
|
||||||
|
private BufferProcessor mBufferProcessor;
|
||||||
|
private ScheduledFuture<?> mProcessorHandle;
|
||||||
|
private LinkedBlockingQueue<AudioPacket> mAudioPacketQueue = new LinkedBlockingQueue<>(500);
|
||||||
|
private long mLastBufferReceived;
|
||||||
|
|
||||||
|
private AtomicBoolean mRunning = new AtomicBoolean();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MP3 audio recorder module for converting audio packets to 16 kHz constant bit rate MP3 format and
|
||||||
|
* recording to a file.
|
||||||
|
*/
|
||||||
|
public MP3Recorder(String filePrefix)
|
||||||
|
{
|
||||||
|
mFilePrefix = filePrefix;
|
||||||
|
|
||||||
|
int mp3BitRate = 16; //16 kHz
|
||||||
|
boolean variableBitRate = false; //CBR
|
||||||
|
mMP3Converter = new MP3AudioConverter(mp3BitRate, variableBitRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp of when the latest buffer was received by this recorder
|
||||||
|
*/
|
||||||
|
public long getLastBufferReceived()
|
||||||
|
{
|
||||||
|
return mLastBufferReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Path getPath()
|
||||||
|
{
|
||||||
|
return mPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts this recorder using as a scheduled thread broadcast running under the executor argument
|
||||||
|
*
|
||||||
|
* @param executor to use in scheduling MP3 conversion and file writes.
|
||||||
|
*/
|
||||||
|
public void start(ScheduledExecutorService executor)
|
||||||
|
{
|
||||||
|
if (mRunning.compareAndSet(false, true))
|
||||||
|
{
|
||||||
|
if (mBufferProcessor == null)
|
||||||
|
{
|
||||||
|
mBufferProcessor = new BufferProcessor();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append(mFilePrefix);
|
||||||
|
sb.append("_");
|
||||||
|
sb.append(TimeStamp.getLongTimeStamp("_"));
|
||||||
|
sb.append(".mp3");
|
||||||
|
|
||||||
|
mPath = Paths.get(sb.toString());
|
||||||
|
mFileOutputStream = new FileOutputStream(mPath.toFile());
|
||||||
|
|
||||||
|
/* Schedule the handler to run every second */
|
||||||
|
mProcessorHandle = executor.scheduleAtFixedRate(mBufferProcessor, 0, 200, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
catch (IOException io)
|
||||||
|
{
|
||||||
|
mLog.error("Error starting real buffer recorder", io);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the recorder.
|
||||||
|
*/
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
mRunning.set(false);
|
||||||
|
|
||||||
|
if (mProcessorHandle != null)
|
||||||
|
{
|
||||||
|
mProcessorHandle.cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
mProcessorHandle = null;
|
||||||
|
|
||||||
|
processAudioPacketQueue();
|
||||||
|
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the recording file.
|
||||||
|
*/
|
||||||
|
private void close()
|
||||||
|
{
|
||||||
|
if (mFileOutputStream != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mFileOutputStream.close();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
mLog.error("Error closing output stream", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary data insert method designed to accept a stream of audio packets and a final ending audio packet.
|
||||||
|
*
|
||||||
|
* Audio packets received before the recorder is started or after the recorder is stopped will be ignored.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void receive(AudioPacket audioPacket)
|
||||||
|
{
|
||||||
|
if (mRunning.get())
|
||||||
|
{
|
||||||
|
boolean success = mAudioPacketQueue.offer(audioPacket);
|
||||||
|
|
||||||
|
if (!success)
|
||||||
|
{
|
||||||
|
mLog.error("recorder buffer overflow - purging [" + mPath.toFile().getAbsolutePath() + "]");
|
||||||
|
|
||||||
|
mAudioPacketQueue.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
mLastBufferReceived = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IAudioPacketListener interface method.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Listener<AudioPacket> getAudioPacketListener()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disposes this audio recorder and prepares it for reclamation
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void dispose()
|
||||||
|
{
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Not implemented. Recorder modules are not appropriate for reset and reuse.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the audio packet queue.
|
||||||
|
*/
|
||||||
|
private void processAudioPacketQueue()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
List<AudioPacket> mPacketsToProcess = new ArrayList<>();
|
||||||
|
mAudioPacketQueue.drainTo(mPacketsToProcess);
|
||||||
|
|
||||||
|
if (!mPacketsToProcess.isEmpty())
|
||||||
|
{
|
||||||
|
boolean stopRequested = false;
|
||||||
|
int stopPacketIndex = 0;
|
||||||
|
|
||||||
|
//An END audio packet is a request to stop the recorder
|
||||||
|
for (AudioPacket packet : mPacketsToProcess)
|
||||||
|
{
|
||||||
|
if (packet.getType() == AudioPacket.Type.END)
|
||||||
|
{
|
||||||
|
stopRequested = true;
|
||||||
|
stopPacketIndex = mPacketsToProcess.indexOf(packet);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stopRequested)
|
||||||
|
{
|
||||||
|
mRunning.set(false);
|
||||||
|
mAudioPacketQueue.clear();
|
||||||
|
|
||||||
|
if (stopPacketIndex > 0)
|
||||||
|
{
|
||||||
|
List<AudioPacket> remainingPacketsToProcess = new ArrayList<>();
|
||||||
|
|
||||||
|
for (int x = 0; x < stopPacketIndex; x++)
|
||||||
|
{
|
||||||
|
remainingPacketsToProcess.add(mPacketsToProcess.get(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
write(remainingPacketsToProcess);
|
||||||
|
|
||||||
|
remainingPacketsToProcess.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write(mPacketsToProcess);
|
||||||
|
}
|
||||||
|
|
||||||
|
mPacketsToProcess.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException ioe)
|
||||||
|
{
|
||||||
|
mAudioPacketQueue.clear();
|
||||||
|
|
||||||
|
mLog.error("IOException while processing audio packet queue for MP3 conversion and storage", ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the PCM audio packets to MP3 format and writes the converted audio to the output stream.
|
||||||
|
*/
|
||||||
|
private void write(List<AudioPacket> audioPackets) throws IOException
|
||||||
|
{
|
||||||
|
byte[] mp3Audio = mMP3Converter.convert(audioPackets);
|
||||||
|
|
||||||
|
mFileOutputStream.write(mp3Audio);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BufferProcessor implements Runnable
|
||||||
|
{
|
||||||
|
private AtomicBoolean mProcessing = new AtomicBoolean();
|
||||||
|
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
if (mProcessing.compareAndSet(false, true))
|
||||||
|
{
|
||||||
|
processAudioPacketQueue();
|
||||||
|
|
||||||
|
mProcessing.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
Path path = Paths.get("/home/denny/Music/PCM.wav");
|
||||||
|
mLog.debug("Opening: " + path.toString());
|
||||||
|
|
||||||
|
final MP3Recorder recorder = new MP3Recorder("/home/denny/Music/denny_test");
|
||||||
|
recorder.start(Executors.newSingleThreadScheduledExecutor());
|
||||||
|
|
||||||
|
try (AudioPacketMonoWaveReader reader = new AudioPacketMonoWaveReader(path, true))
|
||||||
|
{
|
||||||
|
reader.setListener(recorder);
|
||||||
|
reader.read();
|
||||||
|
recorder.stop();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
mLog.error("Error", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.debug("Finished");
|
||||||
|
}
|
||||||
|
}
|
||||||
70
src/record/wave/AudioPacketMonoWaveReader.java
Normal file
70
src/record/wave/AudioPacketMonoWaveReader.java
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.wave;
|
||||||
|
|
||||||
|
import audio.AudioPacket;
|
||||||
|
import audio.IAudioPacketListener;
|
||||||
|
import audio.metadata.AudioMetadata;
|
||||||
|
import sample.Listener;
|
||||||
|
import sample.real.RealBuffer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class AudioPacketMonoWaveReader extends MonoWaveReader implements Listener<RealBuffer>
|
||||||
|
{
|
||||||
|
private AudioMetadata mMetadata = new AudioMetadata(0, false);
|
||||||
|
|
||||||
|
private IAudioPacketListener mListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads wav files and converts the contents to AudioPackets.
|
||||||
|
*
|
||||||
|
* @param path of file to read containing 8kHz, 16-bit signed litte endian PCM audio
|
||||||
|
* @param realTime causes the read() method to read and dispatch audio packets in real time. Note: when using
|
||||||
|
* real time playback, the thread calling the read() method will sleep as needed to effect real-time playback,
|
||||||
|
* therefore you should invoke read() on a separate thread if this will impact the calling thread.
|
||||||
|
*
|
||||||
|
* @throws IOException on any file IO errors
|
||||||
|
*/
|
||||||
|
public AudioPacketMonoWaveReader(Path path, boolean realTime) throws IOException
|
||||||
|
{
|
||||||
|
super(path, realTime);
|
||||||
|
setListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMetadata(AudioMetadata metadata)
|
||||||
|
{
|
||||||
|
mMetadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setListener(IAudioPacketListener listener)
|
||||||
|
{
|
||||||
|
mListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void receive(RealBuffer realBuffer)
|
||||||
|
{
|
||||||
|
if(mListener != null)
|
||||||
|
{
|
||||||
|
mListener.getAudioPacketListener().receive(new AudioPacket(realBuffer.getSamples(), mMetadata));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
257
src/record/wave/MonoWaveReader.java
Normal file
257
src/record/wave/MonoWaveReader.java
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* 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.wave;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sample.ConversionUtils;
|
||||||
|
import sample.Listener;
|
||||||
|
import sample.real.RealBuffer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.ShortBuffer;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class MonoWaveReader implements AutoCloseable
|
||||||
|
{
|
||||||
|
private final static Logger mLog = LoggerFactory.getLogger( MonoWaveReader.class);
|
||||||
|
|
||||||
|
private static final Pattern FILENAME_PATTERN = Pattern.compile( "(.*_)(\\d+)(\\.wav)" );
|
||||||
|
|
||||||
|
private Path mPath;
|
||||||
|
private boolean mRealTime;
|
||||||
|
private InputStream mInputStream;
|
||||||
|
private int mDataByteSize = 0;
|
||||||
|
private Listener<RealBuffer> mListener;
|
||||||
|
private byte[] mDataBuffer = new byte[8000]; //500 milliseconds of audio
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wave file reader for PCM 8kHz, 16-bit little-endian format.
|
||||||
|
*
|
||||||
|
* @param path of the wave file
|
||||||
|
* @param realTime to force replay to real-time. Note: you should run this reader on a separate thread if you
|
||||||
|
* choose real time since this reader will sleep the calling thread as needed to effect real-time playback.
|
||||||
|
*/
|
||||||
|
public MonoWaveReader(Path path, boolean realTime) throws IOException
|
||||||
|
{
|
||||||
|
assert( path != null );
|
||||||
|
mPath = path;
|
||||||
|
mRealTime = realTime;
|
||||||
|
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the file
|
||||||
|
*/
|
||||||
|
private void open() throws IOException
|
||||||
|
{
|
||||||
|
if(!Files.exists(mPath))
|
||||||
|
{
|
||||||
|
throw new IOException("File not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
mInputStream = Files.newInputStream(mPath, StandardOpenOption.READ);
|
||||||
|
|
||||||
|
//Check for RIFF header
|
||||||
|
byte[] buffer = new byte[4];
|
||||||
|
mInputStream.read(buffer);
|
||||||
|
|
||||||
|
if(!Arrays.equals(buffer, WaveUtils.RIFF_CHUNK))
|
||||||
|
{
|
||||||
|
throw new IOException("File is not .wav format - missing RIFF chunk");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get file size
|
||||||
|
mInputStream.read(buffer);
|
||||||
|
int fileSize = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
|
||||||
|
|
||||||
|
//Check for WAVE format
|
||||||
|
mInputStream.read(buffer);
|
||||||
|
|
||||||
|
if(!Arrays.equals(buffer, WaveUtils.WAV_FORMAT))
|
||||||
|
{
|
||||||
|
throw new IOException("File is not .wav format - missing WAVE format");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check for format chunk
|
||||||
|
mInputStream.read(buffer);
|
||||||
|
|
||||||
|
if(!Arrays.equals(buffer, WaveUtils.CHUNK_FORMAT))
|
||||||
|
{
|
||||||
|
throw new IOException("File is not .wav format - missing format chunk");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get chunk size
|
||||||
|
mInputStream.read(buffer);
|
||||||
|
int chunkSize = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
|
||||||
|
|
||||||
|
//Get format
|
||||||
|
mInputStream.read(buffer);
|
||||||
|
|
||||||
|
ShortBuffer shortBuffer = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
|
||||||
|
short format = shortBuffer.get();
|
||||||
|
if(format != WaveUtils.PCM_FORMAT)
|
||||||
|
{
|
||||||
|
throw new IOException("File format not supported - expecting PCM format");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get number of channels
|
||||||
|
short channels = shortBuffer.get();
|
||||||
|
if(channels != 1)
|
||||||
|
{
|
||||||
|
throw new IOException("Unsupported channel count - mono audio only");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get samples per second
|
||||||
|
mInputStream.read(buffer);
|
||||||
|
int sampleRate = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
|
||||||
|
|
||||||
|
//Get bytes per second
|
||||||
|
mInputStream.read(buffer);
|
||||||
|
int bytesPerSecond = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
|
||||||
|
|
||||||
|
mInputStream.read(buffer);
|
||||||
|
|
||||||
|
//Get frame size
|
||||||
|
shortBuffer = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
|
||||||
|
short frameSize = shortBuffer.get();
|
||||||
|
if(frameSize != 2)
|
||||||
|
{
|
||||||
|
throw new IOException("PCM frame size not supported - expecting 2 bytes per frame");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get bits per sample
|
||||||
|
short bitsPerSample = shortBuffer.get();
|
||||||
|
if(bitsPerSample != 16)
|
||||||
|
{
|
||||||
|
throw new IOException("PCM sample size not supported - expecting 16 bits per sample");
|
||||||
|
}
|
||||||
|
|
||||||
|
mInputStream.read(buffer);
|
||||||
|
|
||||||
|
if(!Arrays.equals(buffer, WaveUtils.CHUNK_DATA))
|
||||||
|
{
|
||||||
|
throw new IOException("Unexpected chunk - expecting data chunk");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get data chunk size
|
||||||
|
mInputStream.read(buffer);
|
||||||
|
mDataByteSize = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void read() throws IOException
|
||||||
|
{
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
long samplesRead = 0;
|
||||||
|
|
||||||
|
int totalBytesRead = 0;
|
||||||
|
|
||||||
|
while(totalBytesRead < mDataByteSize)
|
||||||
|
{
|
||||||
|
int bytesRead = mInputStream.read(mDataBuffer);
|
||||||
|
|
||||||
|
totalBytesRead += bytesRead;
|
||||||
|
|
||||||
|
float[] samples = ConversionUtils.convertFromSigned16BitSamples(mDataBuffer);
|
||||||
|
|
||||||
|
if(bytesRead != mDataBuffer.length)
|
||||||
|
{
|
||||||
|
samples = Arrays.copyOf(samples, bytesRead / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mRealTime)
|
||||||
|
{
|
||||||
|
samplesRead += samples.length;
|
||||||
|
|
||||||
|
long actualElapsed = System.currentTimeMillis() - start;
|
||||||
|
|
||||||
|
long audioElapsed = (long)(((double)samplesRead / 8000.0) * 1000.0);
|
||||||
|
|
||||||
|
if(audioElapsed > actualElapsed)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(audioElapsed - actualElapsed);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(mListener != null)
|
||||||
|
{
|
||||||
|
mListener.receive(new RealBuffer(samples));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setListener(Listener<RealBuffer> listener)
|
||||||
|
{
|
||||||
|
mListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the file
|
||||||
|
*/
|
||||||
|
public void close() throws IOException
|
||||||
|
{
|
||||||
|
if(mInputStream != null)
|
||||||
|
{
|
||||||
|
mInputStream.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
{
|
||||||
|
Path path = Paths.get("/home/denny/Music/PCM.wav");
|
||||||
|
mLog.debug("Opening: " + path.toString());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MonoWaveReader reader = new MonoWaveReader(path, true);
|
||||||
|
reader.setListener(new Listener<RealBuffer>()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void receive(RealBuffer realBuffer)
|
||||||
|
{
|
||||||
|
mLog.debug("Received buffer");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
reader.read();
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
mLog.error("Error", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLog.debug("Finished");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,14 +17,19 @@
|
|||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
package record.wave;
|
package record.wave;
|
||||||
|
|
||||||
|
import javax.sound.sampled.AudioFormat;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
import javax.sound.sampled.AudioFormat;
|
|
||||||
|
|
||||||
|
|
||||||
public class WaveUtils
|
public class WaveUtils
|
||||||
{
|
{
|
||||||
|
public static final byte[] RIFF_CHUNK = {(byte)0x52, (byte)0x49, (byte)0x46, (byte)0x46}; //RIFF
|
||||||
|
public static final byte[] WAV_FORMAT = {(byte)0x57, (byte)0x41, (byte)0x56, (byte)0x45}; //WAVE
|
||||||
|
public static final byte[] CHUNK_FORMAT = {(byte)0x66, (byte)0x6D, (byte)0x74, (byte)0x20}; //fmt space
|
||||||
|
public static final byte[] CHUNK_DATA = {(byte)0x64, (byte)0x61, (byte)0x74, (byte)0x61}; //data
|
||||||
|
public static final int PCM_FORMAT = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a wave file header with the RIFF chunk descriptor and the WAVE
|
* Creates a wave file header with the RIFF chunk descriptor and the WAVE
|
||||||
* format chunk 1 and chunk 2 header
|
* format chunk 1 and chunk 2 header
|
||||||
|
|||||||
Reference in New Issue
Block a user