diff --git a/.idea/dictionaries/denny.xml b/.idea/dictionaries/denny.xml
index 39329f5c..d0051597 100644
--- a/.idea/dictionaries/denny.xml
+++ b/.idea/dictionaries/denny.xml
@@ -10,6 +10,7 @@
talkgroup
tgid
ultravox
+ xtea
\ No newline at end of file
diff --git a/src/audio/broadcast/BroadcastState.java b/src/audio/broadcast/BroadcastState.java
index 818b8553..7c04ae6e 100644
--- a/src/audio/broadcast/BroadcastState.java
+++ b/src/audio/broadcast/BroadcastState.java
@@ -65,6 +65,11 @@ public enum BroadcastState
*/
UNKNOWN_HOST("Unknown Host or Invalid Port", true),
+ /**
+ * Unsupported audio format
+ */
+ UNSUPPORTED_AUDIO_FORMAT("Unsupported Audio Type", true),
+
/**
* Unspecified error
*/
diff --git a/src/audio/broadcast/BroadcasterFactory.java b/src/audio/broadcast/BroadcasterFactory.java
index 91f1f260..cc416e1a 100644
--- a/src/audio/broadcast/BroadcasterFactory.java
+++ b/src/audio/broadcast/BroadcasterFactory.java
@@ -20,6 +20,8 @@ package audio.broadcast;
import audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
import audio.broadcast.shoutcast.v1.ShoutcastV1Handler;
+import audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
+import audio.broadcast.shoutcast.v2.ShoutcastV2Handler;
import audio.convert.IAudioConverter;
import audio.convert.MP3AudioConverter;
import controller.ThreadPoolManager;
@@ -51,6 +53,9 @@ public class BroadcasterFactory
case SHOUTCAST_V1:
return new Broadcaster(threadPoolManager,
new ShoutcastV1Handler((ShoutcastV1Configuration)configuration, converter));
+ case SHOUTCAST_V2:
+ return new Broadcaster(threadPoolManager,
+ new ShoutcastV2Handler((ShoutcastV2Configuration)configuration, converter));
case UNKNOWN:
break;
}
@@ -70,7 +75,6 @@ public class BroadcasterFactory
{
case MP3:
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_NO_VARIABLE_BITRATE);
-
}
return null;
diff --git a/src/audio/broadcast/shoutcast/v1/ShoutcastV1Handler.java b/src/audio/broadcast/shoutcast/v1/ShoutcastV1Handler.java
index 7e4d0dcc..b15d35ba 100644
--- a/src/audio/broadcast/shoutcast/v1/ShoutcastV1Handler.java
+++ b/src/audio/broadcast/shoutcast/v1/ShoutcastV1Handler.java
@@ -133,12 +133,12 @@ public class ShoutcastV1Handler extends BroadcastHandler
{
if(!connected() && System.currentTimeMillis() - mLastConnectionAttempt >= RECONNECT_INTERVAL_MILLISECONDS)
{
+ mLastConnectionAttempt = System.currentTimeMillis();
+
createSocket();
if(mSocket.isConnected())
{
- mLog.debug("Sending credentials");
-
StringBuilder sb = new StringBuilder();
//Password
@@ -186,7 +186,6 @@ public class ShoutcastV1Handler extends BroadcastHandler
*/
private void send(String data) throws IOException
{
- mLog.debug("Sending [" + data + "]");
if(data != null && !data.isEmpty() && mOutputStream != null)
{
mOutputStream.writeBytes(data);
@@ -200,7 +199,6 @@ public class ShoutcastV1Handler extends BroadcastHandler
*/
private void send(byte[] data) throws IOException
{
- mLog.debug("Sending bytes:" + data.length);
if(connected())
{
int sent = 0;
@@ -344,7 +342,7 @@ public class ShoutcastV1Handler extends BroadcastHandler
config.setAlias("Test Configuration");
config.setHost("localhost");
config.setPort(8000);
- config.setPassword("denny3");
+ config.setPassword("denny3:#1");
config.setStreamName("Denny's Audio Broadcast Test");
config.setGenre("Scanner Audio");
config.setPublic(true);
diff --git a/src/audio/broadcast/shoutcast/v2/ShoutcastV2Configuration.java b/src/audio/broadcast/shoutcast/v2/ShoutcastV2Configuration.java
index f42fd780..eb121759 100644
--- a/src/audio/broadcast/shoutcast/v2/ShoutcastV2Configuration.java
+++ b/src/audio/broadcast/shoutcast/v2/ShoutcastV2Configuration.java
@@ -24,14 +24,13 @@ import audio.broadcast.BroadcastServerType;
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 mStreamID;
+ private String mUserID;
private int mBitRate;
+ private String mStreamName;
+ private String mStreamGenre;
private String mURL;
+ private boolean mPublic = true;
public ShoutcastV2Configuration(BroadcastFormat format)
@@ -42,26 +41,60 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
@Override
public BroadcastServerType getBroadcastServerType()
{
- return BroadcastServerType.SHOUTCAST_V1;
- }
-
- public String getMountPoint()
- {
- return mMountPoint;
+ return BroadcastServerType.SHOUTCAST_V2;
}
/**
- * 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
+ * Stream ID
*/
- public void setMountPoint(String mountPoint)
+ public int getStreamID()
{
- //Strip the leading slash from the mount point if the user supplies it
- mMountPoint = mountPoint.replace("/", "");
+ return mStreamID;
+ }
+
+ /**
+ * Sets the stream ID
+ *
+ * @param streamID numeric stream identifier between 1 - 2147483647
+ */
+ public void setStreamID(int streamID)
+ {
+ assert(1 <= streamID && streamID <= Integer.MAX_VALUE);
+ mStreamID = streamID;
+ }
+
+ /**
+ * User ID
+ */
+ public String getUserID()
+ {
+ return mUserID;
+ }
+
+ /**
+ * Sets the user ID
+ * @param userID
+ */
+ public void setUserID(String userID)
+ {
+ mUserID = userID;
+ }
+
+ /**
+ * Bit rate in kilobits per second
+ */
+ public int getBitRate()
+ {
+ return mBitRate;
+ }
+
+ /**
+ * Bit rate
+ * @param bitRate in kilobits per second
+ */
+ public void setBitRate(int bitRate)
+ {
+ mBitRate = bitRate;
}
/**
@@ -81,37 +114,20 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
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()
+ public String getStreamGenre()
{
- return mGenre;
+ return mStreamGenre;
}
/**
- * Stream genre
- * @param genre
+ * Sets the genre tag for this stream
*/
- public void setGenre(String genre)
+ public void setStreamGenre(String genre)
{
- mGenre = genre;
+ mStreamGenre = genre;
}
/**
@@ -131,39 +147,6 @@ public class ShoutcastV2Configuration extends BroadcastConfiguration
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.
*/
diff --git a/src/audio/broadcast/shoutcast/v2/ShoutcastV2Handler.java b/src/audio/broadcast/shoutcast/v2/ShoutcastV2Handler.java
new file mode 100644
index 00000000..3144870c
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/ShoutcastV2Handler.java
@@ -0,0 +1,570 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2;
+
+import audio.AudioPacket;
+import audio.broadcast.BroadcastFormat;
+import audio.broadcast.BroadcastHandler;
+import audio.broadcast.BroadcastState;
+import audio.broadcast.Broadcaster;
+import audio.broadcast.BroadcasterFactory;
+import audio.broadcast.shoutcast.v2.message.AuthenticateBroadcast;
+import audio.broadcast.shoutcast.v2.message.ConfigureIcyGenre;
+import audio.broadcast.shoutcast.v2.message.ConfigureIcyName;
+import audio.broadcast.shoutcast.v2.message.ConfigureIcyPublic;
+import audio.broadcast.shoutcast.v2.message.ConfigureIcyURL;
+import audio.broadcast.shoutcast.v2.message.MP3Audio;
+import audio.broadcast.shoutcast.v2.message.NegotiateBufferSize;
+import audio.broadcast.shoutcast.v2.message.NegotiateMaxPayloadSize;
+import audio.broadcast.shoutcast.v2.message.RequestCipher;
+import audio.broadcast.shoutcast.v2.message.SetupBroadcast;
+import audio.broadcast.shoutcast.v2.message.Standby;
+import audio.broadcast.shoutcast.v2.message.UltravoxMessage;
+import audio.broadcast.shoutcast.v2.message.UltravoxMessageFactory;
+import audio.broadcast.shoutcast.v2.message.UltravoxMessageType;
+import audio.broadcast.shoutcast.v2.message.UltravoxMetadata;
+import audio.broadcast.shoutcast.v2.message.XMLMetadata;
+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.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class ShoutcastV2Handler extends BroadcastHandler
+{
+ private final static Logger mLog = LoggerFactory.getLogger( ShoutcastV2Handler.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 int mMaxPayloadSize = 16377;
+
+ /**
+ * Creates a Shoutcast Version 1 broadcast handler.
+ * @param configuration
+ * @param audioConverter
+ */
+ public ShoutcastV2Handler(ShoutcastV2Configuration configuration, IAudioConverter audioConverter)
+ {
+ super(configuration, audioConverter);
+ }
+
+ /**
+ * Shoutcast V2 Configuration information
+ */
+ private ShoutcastV2Configuration getShoutcastConfiguration()
+ {
+ return (ShoutcastV2Configuration)getBroadcastConfiguration();
+ }
+
+ /**
+ * Broadcast audio packets
+ *
+ * @param audioPackets
+ */
+ @Override
+ public void broadcast(List audioPackets)
+ {
+ //If we're connected, send the audio, otherwise discard it
+ if(connect())
+ {
+ byte[] convertedAudio = mAudioConverter.convert(audioPackets);
+
+ if(convertedAudio != null)
+ {
+ List audioMessages = getAudioMessages(convertedAudio);
+
+ for(UltravoxMessage audioMessage: audioMessages)
+ {
+ try
+ {
+ send(audioMessage, false);
+ }
+ catch(IOException e)
+ {
+ mLog.error("Error while dispatching audio", e);
+ setBroadcastState(BroadcastState.BROADCAST_ERROR);
+ return;
+ }
+ }
+ }
+ }
+ else
+ {
+ mLog.debug("Not connected");
+ }
+ }
+
+ /**
+ * 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()
+ {
+// TerminateBroadcast terminate = (TerminateBroadcast)UltravoxMessageFactory
+// .getMessage(UltravoxMessageType.TERMINATE_BROADCAST);
+// response = send(terminate, out, in);
+// mLog.debug("Terminate Response:" + (response.getPayload() != null ? response.getPayload() : ""));
+
+ }
+
+ /**
+ * Sends the message. When get response is true, reads from the input stream and converts the response to a
+ * message that is returned.
+ *
+ * @param message to send to the shoutcast/ultravox server
+ * @param getResponse true if the sent message will generate a response
+ * @return response message when getResponse is set to true
+ * @throws IOException if there is an issue while communicating with the server
+ */
+ private UltravoxMessage send(UltravoxMessage message, boolean getResponse) throws IOException
+ {
+ byte[] bytes = message.getMessage();
+
+ mOutputStream.write(bytes);
+
+ if(getResponse)
+ {
+ try
+ {
+ Thread.sleep(50);
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+
+ int available = mInputStream.available();
+
+ if(available > 0)
+ {
+ byte[] buffer = new byte[available];
+ int read = mInputStream.read(buffer);
+
+ return UltravoxMessageFactory.getMessage(buffer);
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Loads the encoded audio byte array into a list of ultravox messages ready for transmission
+ * @param audio to embed as message payload
+ * @return list of messages containing the audio payload
+ */
+ private List getAudioMessages(byte[] audio)
+ {
+ List messages = new ArrayList<>();
+
+ int pointer = 0;
+
+ while(pointer < audio.length)
+ {
+ int payloadSize = Math.min(mMaxPayloadSize, audio.length - pointer);
+
+ byte[] payload = new byte[payloadSize];
+
+ System.arraycopy(audio, pointer, payload, 0, payloadSize);
+
+ MP3Audio mp3Audio = (MP3Audio)UltravoxMessageFactory.getMessage(UltravoxMessageType.MP3_DATA);
+ mp3Audio.setPayload(payload);
+ messages.add(mp3Audio);
+
+ pointer += payloadSize;
+ }
+
+ return messages;
+ }
+
+ /**
+ * Encodes the list of metadata in one or more Ultravox XMLMetadata messages according to the maximum negotiated
+ * payload size for the current connection. Each entry in the list of metadata strings should be an xml encoded
+ * value: value
+ *
+ * See UltravoxMetadata.TAG.asXML(String value)
+ *
+ * @param xmlMetadata list of xml encoded values
+ * @return a sequence of XMLMetadata messages sufficient to carry the complete set of metadata
+ */
+ private List getMetadataMessages(List xmlMetadata)
+ {
+ List messages = new ArrayList<>();
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("\n\n");
+
+ for(String metadata: xmlMetadata)
+ {
+ sb.append(metadata);
+ }
+
+ sb.append("\n");
+
+ mLog.debug("Metadata:" + sb.toString());
+ byte[] xml = sb.toString().getBytes();
+
+ int pointer = 0;
+ int messageCounter = 1;
+ int messageCount = (int)Math.ceil((double)xml.length / (double)(mMaxPayloadSize - 6));
+
+ if(messageCount > 32)
+ {
+ messageCount = 32; //Max number of metadata messages in a sequence
+ }
+
+
+ while(pointer < xml.length && messageCounter <= messageCount)
+ {
+ int payloadSize = Math.min(mMaxPayloadSize - 6, xml.length - pointer);
+
+ byte[] payload = new byte[payloadSize + 6];
+
+ payload[1] = (byte)(0x01);
+ payload[3] = (byte)(messageCount & 0xFF);
+ payload[5] = (byte)(messageCounter & 0xFF);
+
+ System.arraycopy(xml, pointer, payload, 6, payloadSize);
+
+ XMLMetadata message = (XMLMetadata)UltravoxMessageFactory.getMessage(UltravoxMessageType.XML_METADATA);
+ message.setPayload(payload);
+ messages.add(message);
+
+ pointer += payloadSize;
+ }
+
+ return messages;
+ }
+
+
+ /**
+ * 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)
+ {
+ mLastConnectionAttempt = System.currentTimeMillis();
+
+ createSocket();
+
+ if (mSocket.isConnected())
+ {
+ RequestCipher requestCipher = (RequestCipher) UltravoxMessageFactory
+ .getMessage(UltravoxMessageType.REQUEST_CIPHER);
+
+ try
+ {
+ UltravoxMessage response = send(requestCipher, true);
+
+ if (response instanceof RequestCipher)
+ {
+ RequestCipher cipherResponse = (RequestCipher) response;
+
+ AuthenticateBroadcast authenticateBroadcast = (AuthenticateBroadcast) UltravoxMessageFactory
+ .getMessage(UltravoxMessageType.AUTHENTICATE_BROADCAST);
+ authenticateBroadcast.setCredentials(cipherResponse.getCipher(),
+ getShoutcastConfiguration().getStreamID(), getShoutcastConfiguration().getUserID(),
+ getShoutcastConfiguration().getPassword());
+ response = send(authenticateBroadcast, true);
+
+ if (response.isErrorResponse())
+ {
+ setBroadcastState(BroadcastState.INVALID_PASSWORD);
+ return;
+ }
+
+ UltravoxMessage streamMimeType = UltravoxMessageFactory
+ .getMessage(UltravoxMessageType.STREAM_MIME_TYPE);
+ streamMimeType.setPayload(getShoutcastConfiguration().getBroadcastFormat().getValue());
+ response = send(streamMimeType, true);
+
+ if (response.isErrorResponse())
+ {
+ setBroadcastState(BroadcastState.UNSUPPORTED_AUDIO_FORMAT);
+ mLog.error("Audio format [" + getShoutcastConfiguration().getBroadcastFormat() + "] not supported");
+ return;
+ }
+
+ SetupBroadcast setupBroadcast = (SetupBroadcast) UltravoxMessageFactory
+ .getMessage(UltravoxMessageType.SETUP_BROADCAST);
+ setupBroadcast.setBitRate(getShoutcastConfiguration().getBitRate(),
+ getShoutcastConfiguration().getBitRate());
+ response = send(setupBroadcast, true);
+
+ if (response.isErrorResponse())
+ {
+ setBroadcastState(BroadcastState.UNSUPPORTED_AUDIO_FORMAT);
+ mLog.error("Audio bit rate [" + getShoutcastConfiguration().getBitRate() + "] not supported");
+ return;
+ }
+
+ NegotiateMaxPayloadSize negotiateMaxPayloadSize = (NegotiateMaxPayloadSize) UltravoxMessageFactory
+ .getMessage(UltravoxMessageType.NEGOTIATE_MAX_PAYLOAD_SIZE);
+ negotiateMaxPayloadSize.setMaximumPayloadSize(mMaxPayloadSize, 14000);
+ response = send(negotiateMaxPayloadSize, true);
+ mMaxPayloadSize = ((NegotiateMaxPayloadSize) response).getMaximumPayloadSize();
+
+ List metadata = new ArrayList<>();
+
+ if (getShoutcastConfiguration().getStreamName() != null)
+ {
+ metadata.add(UltravoxMetadata.TITLE_1.asXML(getShoutcastConfiguration().getStreamName()));
+
+ ConfigureIcyName icyName = (ConfigureIcyName) UltravoxMessageFactory
+ .getMessage(UltravoxMessageType.CONFIGURE_ICY_NAME);
+ icyName.setName(getShoutcastConfiguration().getStreamName());
+ response = send(icyName, false);
+ }
+
+ if (getShoutcastConfiguration().getStreamGenre() != null)
+ {
+ metadata.add(UltravoxMetadata.GENRE.asXML(getShoutcastConfiguration().getStreamGenre()));
+
+ ConfigureIcyGenre icyGenre = (ConfigureIcyGenre) UltravoxMessageFactory
+ .getMessage(UltravoxMessageType.CONFIGURE_ICY_GENRE);
+ icyGenre.setGenre(getShoutcastConfiguration().getStreamGenre());
+ response = send(icyGenre, false);
+ }
+
+ if (getShoutcastConfiguration().getURL() != null)
+ {
+ ConfigureIcyURL icyURL = (ConfigureIcyURL) UltravoxMessageFactory
+ .getMessage(UltravoxMessageType.CONFIGURE_ICY_URL);
+ icyURL.setURL(getShoutcastConfiguration().getURL());
+ response = send(icyURL, false);
+ }
+
+ ConfigureIcyPublic icyPublic = (ConfigureIcyPublic) UltravoxMessageFactory
+ .getMessage(UltravoxMessageType.CONFIGURE_ICY_PUBLIC);
+ icyPublic.setPublic(getShoutcastConfiguration().isPublic());
+ response = send(icyPublic, false);
+
+ Standby standby = (Standby) UltravoxMessageFactory.getMessage(UltravoxMessageType.STANDBY);
+ response = send(standby, true);
+
+ if (response.isValidResponse())
+ {
+ setBroadcastState(BroadcastState.CONNECTED);
+ }
+ else
+ {
+ setBroadcastState(BroadcastState.DISCONNECTED);
+ mLog.error("Error message after configuration and sending standby:" +
+ (response.getError() != null ? response.getError() : "no error message"));
+ }
+
+ metadata.add(UltravoxMetadata.BROADCAST_CLIENT_APPLICATION.asXML("sdrtrunk"));
+
+ mLog.debug("Fetching metadata messages from metadata...");
+ List messages = getMetadataMessages(metadata);
+
+ for (UltravoxMessage message : messages)
+ {
+ mLog.debug("Sending metadata message");
+ send(message, true);
+ }
+
+ mLog.debug("We're connected!!!");
+ }
+ }
+ catch (IOException e)
+ {
+ setBroadcastState(BroadcastState.ERROR);
+ mLog.error("Error while creating connection to server", e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a socket connection to the remote server and sets the state to CONNECTING.
+ */
+ private void createSocket()
+ {
+ if(mSocket == null)
+ {
+ 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 UltravoxMessage send(UltravoxMessage message,
+ DataOutputStream out,
+ DataInputStream in,
+ int maxPayloadSize) throws IOException
+ {
+ byte[] bytes = message.getMessage();
+
+ out.write(bytes);
+
+ try
+ {
+ Thread.sleep(50);
+ }
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }
+
+ int available = in.available();
+
+ if(available > 0)
+ {
+ byte[] buffer = new byte[available];
+ int read = in.read(buffer);
+
+ return UltravoxMessageFactory.getMessage(buffer);
+ }
+
+ return null;
+ }
+
+
+ public static void main(String[] args)
+ {
+ boolean test = false;
+
+ ShoutcastV2Configuration config = new ShoutcastV2Configuration(BroadcastFormat.MP3);
+ config.setStreamID(1);
+ config.setAlias("Test Configuration");
+ config.setHost("localhost");
+ config.setPort(8000);
+ config.setPassword("denny3");
+ config.setStreamName("Denny's Audio Broadcast Test");
+ config.setStreamGenre("Public Safety");
+ config.setPublic(true);
+ config.setURL("http://localhost:8000");
+ config.setBitRate(16);
+
+ if(test)
+ {
+ }
+ else
+ {
+ 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() + "]");
+ }
+ }
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/AuthenticateBroadcast.java b/src/audio/broadcast/shoutcast/v2/message/AuthenticateBroadcast.java
new file mode 100644
index 00000000..a35de1b3
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/AuthenticateBroadcast.java
@@ -0,0 +1,102 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+import util.XTEA;
+
+import java.util.Arrays;
+
+public class AuthenticateBroadcast extends UltravoxMessage
+{
+ /**
+ * Client request to server to send authentication credentials
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ AuthenticateBroadcast()
+ {
+ super(UltravoxMessageType.AUTHENTICATE_BROADCAST);
+ }
+
+
+ /**
+ * Server response to client request
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ AuthenticateBroadcast(byte[] data)
+ {
+ super(data);
+ }
+
+ /**
+ * Set credential to authenticate the stream with the server
+ *
+ * @param encryptionKey received from the server in the RequestCipher message
+ * @param streamID that we're going to stream to
+ * @param userID for the stream. Note: this is optional and can be null. The server currently ignores this value.
+ * @param password for the stream.
+ */
+ public void setCredentials(String encryptionKey, int streamID, String userID, String password)
+ {
+ XTEA xtea = new XTEA(encryptionKey);
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(SHOUTCAST_VERSION);
+ sb.append(":");
+ sb.append(streamID);
+ sb.append(":");
+ sb.append(userID != null ? encrypt(xtea, userID) : "");
+ sb.append(":");
+ sb.append(password != null ? encrypt(xtea, password) : "");
+
+ setPayload(sb.toString());
+ }
+
+ /**
+ * Encrypts the value using the XTEA block cipher algorithm and returns the encrypted value as a 16 hex value
+ *
+ * @param xtea instance of the XTEA algorithm with a loaded encryption key
+ * @param value to encode
+ * @return encrypted password as 64-bit, 16 character hexadecimal value
+ */
+ private static String encrypt(XTEA xtea, String value)
+ {
+ byte[] valueBytes = value.getBytes();
+
+ if(valueBytes.length % 8 != 0 || valueBytes.length == 0)
+ {
+ int newLength = ((valueBytes.length / 8) + 1) * 8;
+ valueBytes = Arrays.copyOf(valueBytes, newLength);
+ }
+
+ byte[] encrypted = xtea.encrypt(valueBytes);
+
+ StringBuilder sb = new StringBuilder();
+
+ for(byte b: encrypted)
+ {
+ sb.append(String.format("%02x", b));
+ }
+
+ return sb.toString();
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/ConfigureIcyGenre.java b/src/audio/broadcast/shoutcast/v2/message/ConfigureIcyGenre.java
new file mode 100644
index 00000000..2c720844
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/ConfigureIcyGenre.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+public class ConfigureIcyGenre extends UltravoxMessage
+{
+ /**
+ * Client request to server to configure the ICY-GENRE metadata
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ ConfigureIcyGenre()
+ {
+ super(UltravoxMessageType.CONFIGURE_ICY_GENRE);
+ }
+
+ /**
+ * Server response to client request
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ ConfigureIcyGenre(byte[] data)
+ {
+ super(data);
+ }
+
+ public void setGenre(String genre)
+ {
+ setPayload(genre);
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/ConfigureIcyName.java b/src/audio/broadcast/shoutcast/v2/message/ConfigureIcyName.java
new file mode 100644
index 00000000..4a4490da
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/ConfigureIcyName.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+import util.XTEA;
+
+import java.util.Arrays;
+
+public class ConfigureIcyName extends UltravoxMessage
+{
+ /**
+ * Client request to server to configure the ICY-NAME metadata
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ ConfigureIcyName()
+ {
+ super(UltravoxMessageType.CONFIGURE_ICY_NAME);
+ }
+
+ /**
+ * Server response to client request
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ ConfigureIcyName(byte[] data)
+ {
+ super(data);
+ }
+
+ public void setName(String name)
+ {
+ setPayload(name);
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/ConfigureIcyPublic.java b/src/audio/broadcast/shoutcast/v2/message/ConfigureIcyPublic.java
new file mode 100644
index 00000000..0b53d977
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/ConfigureIcyPublic.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+public class ConfigureIcyPublic extends UltravoxMessage
+{
+ /**
+ * Client request to server to configure the ICY-PUBLIC metadata
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ ConfigureIcyPublic()
+ {
+ super(UltravoxMessageType.CONFIGURE_ICY_PUBLIC);
+ }
+
+ /**
+ * Server response to client request
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ ConfigureIcyPublic(byte[] data)
+ {
+ super(data);
+ }
+
+ public void setPublic(boolean publicStream)
+ {
+ setPayload(publicStream ? "1" : "0");
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/ConfigureIcyURL.java b/src/audio/broadcast/shoutcast/v2/message/ConfigureIcyURL.java
new file mode 100644
index 00000000..87c5f9cb
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/ConfigureIcyURL.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+public class ConfigureIcyURL extends UltravoxMessage
+{
+ /**
+ * Client request to server to configure the ICY-URL metadata
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ ConfigureIcyURL()
+ {
+ super(UltravoxMessageType.CONFIGURE_ICY_URL);
+ }
+
+ /**
+ * Server response to client request
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ ConfigureIcyURL(byte[] data)
+ {
+ super(data);
+ }
+
+ public void setURL(String url)
+ {
+ setPayload(url);
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/FlushCachedMetadata.java b/src/audio/broadcast/shoutcast/v2/message/FlushCachedMetadata.java
new file mode 100644
index 00000000..3defeaf3
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/FlushCachedMetadata.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+public class FlushCachedMetadata extends UltravoxMessage
+{
+ /**
+ * Client request to server to flush any currently cached stream metadata
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ FlushCachedMetadata()
+ {
+ super(UltravoxMessageType.FLUSH_CACHED_METADATA);
+ }
+
+ /**
+ * Server response to client request
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ FlushCachedMetadata(byte[] data)
+ {
+ super(data);
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/UltravoxMessageType.java b/src/audio/broadcast/shoutcast/v2/message/MP3Audio.java
similarity index 60%
rename from src/audio/broadcast/shoutcast/v2/UltravoxMessageType.java
rename to src/audio/broadcast/shoutcast/v2/message/MP3Audio.java
index 62f9151c..27247282 100644
--- a/src/audio/broadcast/shoutcast/v2/UltravoxMessageType.java
+++ b/src/audio/broadcast/shoutcast/v2/message/MP3Audio.java
@@ -16,34 +16,29 @@
* along with this program. If not, see
*
******************************************************************************/
-package audio.broadcast.shoutcast.v2;
+package audio.broadcast.shoutcast.v2.message;
-public enum UltravoxMessageType
+public class MP3Audio extends UltravoxMessage
{
- AUTHENTICATE_BROADCAST(0x1001),
-
- UNKNOWN(0x0000);
-
- private int mValue;
-
- private UltravoxMessageType(int value)
+ /**
+ * Client request to server to send authentication credentials
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ MP3Audio()
{
- mValue = value;
+ super(UltravoxMessageType.MP3_DATA);
}
- public int getValue()
- {
- return mValue;
- }
- public static UltravoxMessageType fromValue(int value)
+ /**
+ * Server response to client request
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ MP3Audio(byte[] data)
{
- switch(value)
- {
- case 0x1001:
- return AUTHENTICATE_BROADCAST;
- default:
- return UNKNOWN;
- }
+ super(data);
}
}
diff --git a/src/audio/broadcast/shoutcast/v2/message/NegotiateBufferSize.java b/src/audio/broadcast/shoutcast/v2/message/NegotiateBufferSize.java
new file mode 100644
index 00000000..9e7523da
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/NegotiateBufferSize.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+public class NegotiateBufferSize extends UltravoxMessage
+{
+ /**
+ * Client request to server to negotiate the server's buffer size
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ NegotiateBufferSize()
+ {
+ super(UltravoxMessageType.NEGOTIATE_BUFFER_SIZE);
+ }
+
+ /**
+ * Server response to client request
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ NegotiateBufferSize(byte[] data)
+ {
+ super(data);
+ }
+
+ /**
+ * Sets the desired and minimum requested buffer sizes to send to the server
+ * @param desired buffer size in kilobytes
+ * @param minimum buffer size in kilobytes
+ */
+ public void setBufferSize(int desired, int minimum)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(desired).append(":").append(minimum);
+
+ setPayload(sb.toString());
+ }
+
+ /**
+ * Negotiated buffer size from a server response message.
+ *
+ * @return server negotiated buffer size, or zero if this is not a server response message, or this is an error
+ * server response message, or the server response message buffer size cannot be determined from the message payload.
+ */
+ public int getBufferSize()
+ {
+ String payload = getPayload();
+
+ if(payload != null && payload.startsWith("ACK:"))
+ {
+ try
+ {
+ int bufferSize = Integer.parseInt(payload.substring(4));
+ return bufferSize;
+ }
+ catch(Exception e)
+ {
+ //Do nothing, we couldn't parse the value
+ }
+ }
+
+ return 0;
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/NegotiateMaxPayloadSize.java b/src/audio/broadcast/shoutcast/v2/message/NegotiateMaxPayloadSize.java
new file mode 100644
index 00000000..d06ab0c2
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/NegotiateMaxPayloadSize.java
@@ -0,0 +1,87 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+public class NegotiateMaxPayloadSize extends UltravoxMessage
+{
+ /**
+ * Client request to server to negotiate maximum payload size
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ NegotiateMaxPayloadSize()
+ {
+ super(UltravoxMessageType.NEGOTIATE_MAX_PAYLOAD_SIZE);
+ }
+
+ /**
+ * Server response to client request
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ NegotiateMaxPayloadSize(byte[] data)
+ {
+ super(data);
+ }
+
+ /**
+ * Sets the maximum payload size.
+ *
+ * @param desiredMaximum payload size (no larger than 16377)
+ * @param minimum acceptable payload size (no larger than 16377)
+ */
+ public void setMaximumPayloadSize(int desiredMaximum, int minimum)
+ {
+ assert(desiredMaximum <= 16377);
+ assert(minimum <= 16377);
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(desiredMaximum).append(":").append(minimum);
+
+ setPayload(sb.toString());
+ }
+
+ /**
+ * Negotiated maximum payload size from a server response message.
+ *
+ * @return server negotiated maximum payload size, or zero if this is not a server response message, or this is an
+ * error server response message, or the server response message maximum payload size cannot be determined from the
+ * message payload.
+ */
+ public int getMaximumPayloadSize()
+ {
+ String payload = getPayload();
+
+ if(payload != null && payload.startsWith("ACK:"))
+ {
+ try
+ {
+ int maximumPayloadSize = Integer.parseInt(payload.substring(4));
+ return maximumPayloadSize;
+ }
+ catch(Exception e)
+ {
+ //Do nothing, we couldn't parse the value
+ }
+ }
+
+ return 0;
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/RequestCipher.java b/src/audio/broadcast/shoutcast/v2/message/RequestCipher.java
new file mode 100644
index 00000000..3c241034
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/RequestCipher.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+public class RequestCipher extends UltravoxMessage
+{
+ /**
+ * Client request to server for encryption key
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ RequestCipher()
+ {
+ super(UltravoxMessageType.REQUEST_CIPHER);
+ setPayload(SHOUTCAST_VERSION);
+ }
+
+ /**
+ * Server response to client request
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ RequestCipher(byte[] data)
+ {
+ super(data);
+ }
+
+ /**
+ * Cipher key returned from the server to use for encrypting user id and password credentials
+ *
+ * @return cipher key or null
+ */
+ public String getCipher()
+ {
+ if(isValidResponse())
+ {
+ return getPayload().replace(VALID_RESPONSE_PAYLOAD_PREFIX, "");
+ }
+
+ return null;
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/SetupBroadcast.java b/src/audio/broadcast/shoutcast/v2/message/SetupBroadcast.java
new file mode 100644
index 00000000..e2cec80c
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/SetupBroadcast.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+public class SetupBroadcast extends UltravoxMessage
+{
+ /**
+ * Client request to server to specify the stream bit rate
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ SetupBroadcast()
+ {
+ super(UltravoxMessageType.SETUP_BROADCAST);
+ }
+
+ /**
+ * Server response to client request
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ SetupBroadcast(byte[] data)
+ {
+ super(data);
+ }
+
+ /**
+ * Sets the average and maximum bit rates. Set both to the same value for a Constant Bit Rate (CBR) stream.
+ *
+ * @param average bit rate of the stream in kbps
+ * @param maximum bit rate of the stream in kbps
+ */
+ public void setBitRate(int average, int maximum)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append(average).append(":").append(maximum);
+
+ setPayload(sb.toString());
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/Standby.java b/src/audio/broadcast/shoutcast/v2/message/Standby.java
new file mode 100644
index 00000000..0da92698
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/Standby.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+public class Standby extends UltravoxMessage
+{
+ /**
+ * Client request to server to notify that configuration is complete, prepare for streaming
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ Standby()
+ {
+ super(UltravoxMessageType.STANDBY);
+ }
+
+ /**
+ * Server response to client request
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ Standby(byte[] data)
+ {
+ super(data);
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/StreamMimeType.java b/src/audio/broadcast/shoutcast/v2/message/StreamMimeType.java
new file mode 100644
index 00000000..8ce0a6c8
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/StreamMimeType.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+import audio.broadcast.BroadcastFormat;
+
+public class StreamMimeType extends UltravoxMessage
+{
+ /**
+ * Client request to server to set stream MIME type
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ StreamMimeType()
+ {
+ super(UltravoxMessageType.STREAM_MIME_TYPE);
+ }
+
+ /**
+ * Server response to client request
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ StreamMimeType(byte[] data)
+ {
+ super(data);
+ }
+
+ /**
+ * Sets the broadcast format or MIME type
+ * @param format of the stream
+ */
+ public void setFormat(BroadcastFormat format)
+ {
+ setPayload(format.getValue());
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/TerminateBroadcast.java b/src/audio/broadcast/shoutcast/v2/message/TerminateBroadcast.java
new file mode 100644
index 00000000..125e8b13
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/TerminateBroadcast.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+public class TerminateBroadcast extends UltravoxMessage
+{
+ /**
+ * Client request to server to terminate the broadcast
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ TerminateBroadcast()
+ {
+ super(UltravoxMessageType.TERMINATE_BROADCAST);
+ }
+
+ /**
+ * Server response to client request
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ TerminateBroadcast(byte[] data)
+ {
+ super(data);
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/UltravoxMessage.java b/src/audio/broadcast/shoutcast/v2/message/UltravoxMessage.java
similarity index 63%
rename from src/audio/broadcast/shoutcast/v2/UltravoxMessage.java
rename to src/audio/broadcast/shoutcast/v2/message/UltravoxMessage.java
index f9c94f68..5c5b0282 100644
--- a/src/audio/broadcast/shoutcast/v2/UltravoxMessage.java
+++ b/src/audio/broadcast/shoutcast/v2/message/UltravoxMessage.java
@@ -16,13 +16,19 @@
* along with this program. If not, see
*
******************************************************************************/
-package audio.broadcast.shoutcast.v2;
+package audio.broadcast.shoutcast.v2.message;
import bits.BinaryMessage;
-import com.sun.org.apache.regexp.internal.RE;
+
+import java.util.Arrays;
public abstract class UltravoxMessage
{
+ public static final String SHOUTCAST_VERSION = "2.1";
+ public static final String VALID_RESPONSE_PREFIX = "ACK";
+ public static final String VALID_RESPONSE_PAYLOAD_PREFIX = "ACK:";
+ public static final String ERROR_RESPONSE_PREFIX = "NAK:";
+
public static final int[] SYNC = {0,1,2,3,4,5,6,7};
public static final int[] RESERVED = {8,9,10,11};
public static final int REQUIRED_DELIVERY = 12;
@@ -38,25 +44,45 @@ public abstract class UltravoxMessage
*/
public UltravoxMessage(byte[] data)
{
- mMessage = new BinaryMessage(data);
+ mMessage = new BinaryMessage(data.length * 8);
+
+ int pointer = 0;
+
+ for(byte b: data)
+ {
+ mMessage.setByte(pointer, b);
+ pointer += 8;
+ }
}
/**
* Constructs an empty message of the specified length
*
* @param ultravoxMessageType type of message
- * @param length of message in bits (64 minimum)
*/
- public UltravoxMessage(UltravoxMessageType ultravoxMessageType, int length)
+ public UltravoxMessage(UltravoxMessageType ultravoxMessageType)
{
- assert(length >= 64);
-
- mMessage = new BinaryMessage(length);
+ mMessage = new BinaryMessage(56);
setSync();
setMessageType(ultravoxMessageType);
}
+ /**
+ * Message byte array
+ *
+ * @return message bytes
+ */
+ public byte[] getMessage()
+ {
+ return mMessage.toByteArray();
+ }
+
+ public String toString()
+ {
+ return mMessage.toString();
+ }
+
/**
* Sets the message sync bits to the predefined sync pattern: 01011010 (0x5A)
*/
@@ -138,8 +164,25 @@ public abstract class UltravoxMessage
*/
public String getPayload()
{
- //TODO: parse payload field
- return null;
+ int payloadLength = mMessage.getInt(PAYLOAD_LENGTH);
+
+ byte[] payload = new byte[payloadLength];
+
+ int payloadPointer = 48;
+
+ for(int x = 0; x < payloadLength; x++)
+ {
+ payload[x] = mMessage.getByte(payloadPointer);
+ payloadPointer += 8;
+ }
+
+ //Strip the trailing 0x00 null value from a server response
+ if(payload[payload.length - 1] == 0)
+ {
+ return new String(Arrays.copyOf(payload, payload.length - 1));
+ }
+
+ return new String(payload);
}
/**
@@ -147,11 +190,50 @@ public abstract class UltravoxMessage
*/
public void setPayload(String payload)
{
- //TODO: implement payload, along with trailing 0x00 byte and update overall payload size field.
+ setPayload(payload.getBytes());
}
- private void setPayloadLength(int length)
+ public void setPayload(byte[] payload)
{
- //TODO: correctly size the message to payload + 1 byte and set the payload length field
+ int messageBitLength = (7 + payload.length) * 8;
+
+ mMessage.setSize(messageBitLength);
+ mMessage.setInt(payload.length, PAYLOAD_LENGTH);
+
+ int payloadPointer = 48;
+
+ for(byte payloadByte: payload)
+ {
+ mMessage.setByte(payloadPointer, payloadByte);
+ payloadPointer += 8;
+ }
+
+ mMessage.setByte(payloadPointer, (byte)0x00);
+ }
+
+ public boolean isValidResponse()
+ {
+ String payload = getPayload();
+
+ return payload != null && payload.startsWith(VALID_RESPONSE_PREFIX);
+ }
+
+ public boolean isErrorResponse()
+ {
+ String payload = getPayload();
+
+ return payload != null && payload.startsWith(ERROR_RESPONSE_PREFIX);
+ }
+
+ public String getError()
+ {
+ String payload = getPayload();
+
+ if(payload != null)
+ {
+ return payload.replace(ERROR_RESPONSE_PREFIX, "");
+ }
+
+ return null;
}
}
diff --git a/src/audio/broadcast/shoutcast/v2/UltravoxMessageClass.java b/src/audio/broadcast/shoutcast/v2/message/UltravoxMessageClass.java
similarity index 97%
rename from src/audio/broadcast/shoutcast/v2/UltravoxMessageClass.java
rename to src/audio/broadcast/shoutcast/v2/message/UltravoxMessageClass.java
index dd7d2572..d92c9f86 100644
--- a/src/audio/broadcast/shoutcast/v2/UltravoxMessageClass.java
+++ b/src/audio/broadcast/shoutcast/v2/message/UltravoxMessageClass.java
@@ -16,7 +16,7 @@
* along with this program. If not, see
*
******************************************************************************/
-package audio.broadcast.shoutcast.v2;
+package audio.broadcast.shoutcast.v2.message;
public enum UltravoxMessageClass
{
diff --git a/src/audio/broadcast/shoutcast/v2/message/UltravoxMessageFactory.java b/src/audio/broadcast/shoutcast/v2/message/UltravoxMessageFactory.java
new file mode 100644
index 00000000..cd3be9ab
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/UltravoxMessageFactory.java
@@ -0,0 +1,131 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+public class UltravoxMessageFactory
+{
+ /**
+ * Creates an empty message of the specified message type for sending to the server
+ */
+ public static UltravoxMessage getMessage(UltravoxMessageType type)
+ {
+ switch(type)
+ {
+ case AUTHENTICATE_BROADCAST:
+ return new AuthenticateBroadcast();
+ case CONFIGURE_ICY_GENRE:
+ return new ConfigureIcyGenre();
+ case CONFIGURE_ICY_NAME:
+ return new ConfigureIcyName();
+ case CONFIGURE_ICY_PUBLIC:
+ return new ConfigureIcyPublic();
+ case CONFIGURE_ICY_URL:
+ return new ConfigureIcyURL();
+ case FLUSH_CACHED_METADATA:
+ return new FlushCachedMetadata();
+ case MP3_DATA:
+ return new MP3Audio();
+ case NEGOTIATE_BUFFER_SIZE:
+ return new NegotiateBufferSize();
+ case NEGOTIATE_MAX_PAYLOAD_SIZE:
+ return new NegotiateMaxPayloadSize();
+ case REQUEST_CIPHER:
+ return new RequestCipher();
+ case SETUP_BROADCAST:
+ return new SetupBroadcast();
+ case STANDBY:
+ return new Standby();
+ case STREAM_MIME_TYPE:
+ return new StreamMimeType();
+ case TERMINATE_BROADCAST:
+ return new TerminateBroadcast();
+ case XML_METADATA:
+ return new XMLMetadata();
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Creates an ultravox message from the server response bytes.
+ *
+ * @param bytes in network order (big-endian).
+ *
+ * @return constructed message or null if the message type cannot be determined or is unrecognized
+ */
+ public static UltravoxMessage getMessage(byte[] bytes)
+ {
+ UltravoxMessageType type = getMessageType(bytes);
+
+ switch(type)
+ {
+ case AUTHENTICATE_BROADCAST:
+ return new AuthenticateBroadcast(bytes);
+ case CONFIGURE_ICY_GENRE:
+ return new ConfigureIcyGenre(bytes);
+ case CONFIGURE_ICY_NAME:
+ return new ConfigureIcyName(bytes);
+ case CONFIGURE_ICY_PUBLIC:
+ return new ConfigureIcyPublic(bytes);
+ case CONFIGURE_ICY_URL:
+ return new ConfigureIcyURL(bytes);
+ case FLUSH_CACHED_METADATA:
+ return new FlushCachedMetadata(bytes);
+ case MP3_DATA:
+ return new MP3Audio(bytes);
+ case NEGOTIATE_BUFFER_SIZE:
+ return new NegotiateBufferSize(bytes);
+ case NEGOTIATE_MAX_PAYLOAD_SIZE:
+ return new NegotiateMaxPayloadSize(bytes);
+ case REQUEST_CIPHER:
+ return new RequestCipher(bytes);
+ case SETUP_BROADCAST:
+ return new SetupBroadcast(bytes);
+ case STANDBY:
+ return new Standby(bytes);
+ case STREAM_MIME_TYPE:
+ return new StreamMimeType(bytes);
+ case TERMINATE_BROADCAST:
+ return new TerminateBroadcast(bytes);
+ case XML_METADATA:
+ return new XMLMetadata(bytes);
+ case UNKNOWN:
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Identifies the message type from the server response byte array
+ *
+ * @param data in little-endian format
+ * @return message type or UNKNOWN if the message type cannot be determined.
+ */
+ private static UltravoxMessageType getMessageType(byte[] data)
+ {
+ if(data != null && data.length > 4)
+ {
+ int value = (data[2] << 8) + data[3];
+
+ return UltravoxMessageType.fromValue(value);
+ }
+
+ return UltravoxMessageType.UNKNOWN;
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/UltravoxMessageType.java b/src/audio/broadcast/shoutcast/v2/message/UltravoxMessageType.java
new file mode 100644
index 00000000..656e83bd
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/UltravoxMessageType.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+public enum UltravoxMessageType
+{
+ AUTHENTICATE_BROADCAST(0x1001),
+ SETUP_BROADCAST(0x1002),
+ NEGOTIATE_BUFFER_SIZE(0x1003),
+ STANDBY(0x1004),
+ TERMINATE_BROADCAST(0x1005),
+ FLUSH_CACHED_METADATA(0x1006),
+ NEGOTIATE_MAX_PAYLOAD_SIZE(0x1008),
+ REQUEST_CIPHER(0x1009),
+ STREAM_MIME_TYPE(0x1040),
+ CONFIGURE_ICY_NAME(0x1100),
+ CONFIGURE_ICY_GENRE(0x1101),
+ CONFIGURE_ICY_URL(0x1102),
+ CONFIGURE_ICY_PUBLIC(0x1103),
+ XML_METADATA(0x3902),
+ MP3_DATA(0x7000),
+ UNKNOWN(0x0000);
+
+ private int mValue;
+
+ private UltravoxMessageType(int value)
+ {
+ mValue = value;
+ }
+
+ public int getValue()
+ {
+ return mValue;
+ }
+
+ public static UltravoxMessageType fromValue(int value)
+ {
+ switch(value)
+ {
+ case 0x1001:
+ return AUTHENTICATE_BROADCAST;
+ case 0x1002:
+ return SETUP_BROADCAST;
+ case 0x1003:
+ return NEGOTIATE_BUFFER_SIZE;
+ case 0x1004:
+ return STANDBY;
+ case 0x1005:
+ return TERMINATE_BROADCAST;
+ case 0x1006:
+ return FLUSH_CACHED_METADATA;
+ case 0x1008:
+ return NEGOTIATE_MAX_PAYLOAD_SIZE;
+ case 0x1009:
+ return REQUEST_CIPHER;
+ case 0x1040:
+ return STREAM_MIME_TYPE;
+ case 0x1100:
+ return CONFIGURE_ICY_NAME;
+ case 0x1101:
+ return CONFIGURE_ICY_GENRE;
+ case 0x1102:
+ return CONFIGURE_ICY_URL;
+ case 0x1103:
+ return CONFIGURE_ICY_PUBLIC;
+ case 0x3902:
+ return XML_METADATA;
+ case 0x7000:
+ return MP3_DATA;
+ default:
+ return UNKNOWN;
+ }
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/UltravoxMetadata.java b/src/audio/broadcast/shoutcast/v2/message/UltravoxMetadata.java
new file mode 100644
index 00000000..d32fd13e
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/UltravoxMetadata.java
@@ -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
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+public enum UltravoxMetadata
+{
+ ALBUM_TITLE("TALB"),
+ ARTIST("TPE1"),
+ AUDIO_SOURCE_WEBPAGE("WOAS"),
+ BROADCAST_CLIENT_APPLICATION("TENC"),
+ COMMENT("COMM"),
+ CUSTOM_TEXT_FIELD("TXXX"),
+ ENCODE_SETTINGS("TSSE"),
+ GENERAL_BINARY_OBJECT("GEOB"),
+ GENRE("TCON"),
+ OWNER("TOWN"),
+ RADIO_STATION("TRSN"),
+ RADIO_STATION_OWNER("TRSO"),
+ RADIO_STATION_WEBPAGE("WORS"),
+ RECORDING_TIME("TDRC"),
+ SOURCE_MEDIA_TYPE("TMED"),
+ TITLE_1("TIT1"),
+ TITLE_2("TIT2"),
+ TITLE_3("TIT3"),
+ URL("URL"),
+ UNKNOWN("UNKNOWN");
+
+ private String mXMLTag;
+
+ private UltravoxMetadata(String xmlTag)
+ {
+ mXMLTag = xmlTag;
+ }
+
+ public String getXMLTag()
+ {
+ return mXMLTag;
+ }
+
+ /**
+ * Encodes the value as an XML tag stream
+ * @param value to enclose in XML tags
+ * @return xml tag wrapped value
+ */
+ public String asXML(String value)
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<").append(getXMLTag()).append(">");
+ sb.append(value);
+ sb.append("").append(getXMLTag()).append(">");
+
+ return sb.toString();
+ }
+}
diff --git a/src/audio/broadcast/shoutcast/v2/message/XMLMetadata.java b/src/audio/broadcast/shoutcast/v2/message/XMLMetadata.java
new file mode 100644
index 00000000..e981d7b5
--- /dev/null
+++ b/src/audio/broadcast/shoutcast/v2/message/XMLMetadata.java
@@ -0,0 +1,106 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package audio.broadcast.shoutcast.v2.message;
+
+public class XMLMetadata extends UltravoxMessage
+{
+ /**
+ * Client request to server to send cacheable XML metadata
+ *
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ */
+ XMLMetadata()
+ {
+ super(UltravoxMessageType.XML_METADATA);
+ }
+
+
+ /**
+ * Server response to client request
+ * Package private constructor. Use the UltravoxMessageFactory for this constructor.
+ *
+ * @param data bytes received from the server
+ */
+ XMLMetadata(byte[] data)
+ {
+ super(data);
+ }
+
+// /**
+// * Adds custom metadata (TXXX) tagged value to the metadata set. The metadata value is identified by the
+// * id parameter.
+// *
+// * @param id tag to identify the type of metadata
+// * @param value for the id tag
+// */
+// public void addCustomMetadata(String id, String value)
+// {
+// StringBuilder sb = new StringBuilder();
+// sb.append("<").append(UltravoxMetadata.CUSTOM_TEXT_FIELD.getXMLTag());
+// sb.append(" id=\"").append(id).append("\">");
+// sb.append(value);
+// sb.append("").append(UltravoxMetadata.CUSTOM_TEXT_FIELD.getXMLTag()).append(">");
+//
+// mMetadata.add(sb.toString());
+// }
+//
+// /**
+// * Adds the binary object data to the metadata set.
+// * @param id tag to identify the binary object
+// * @param mime type
+// * @param filename associated with the data
+// * @param base64Data base-64 encoded binary data to embed in the metadata tag
+// */
+// public void addBinaryObject(String id, String mime, String filename, String base64Data)
+// {
+// StringBuilder sb = new StringBuilder();
+// sb.append("<").append(UltravoxMetadata.GENERAL_BINARY_OBJECT.getXMLTag());
+// sb.append(" mime=\"").append(mime).append("\"");
+// sb.append(" filename=\"").append(filename).append("\"");
+// sb.append(" id=\"").append(id).append("\"");
+// sb.append(">");
+// sb.append(base64Data);
+// sb.append("").append(UltravoxMetadata.GENERAL_BINARY_OBJECT.getXMLTag()).append(">");
+//
+// mMetadata.add(sb.toString());
+// }
+//
+// /**
+// * Adds a recording time (TDRC) tagged metadata timestamp
+// *
+// * @param timestamp for the recording time
+// */
+// public void addRecordingTime(long timestamp)
+// {
+// Calendar calendar = new GregorianCalendar();
+// calendar.setTimeInMillis(timestamp);
+//
+// StringBuilder sb = new StringBuilder();
+// sb.append("\n");
+// sb.append("").append(calendar.get(Calendar.YEAR)).append("\n");
+// sb.append("").append(calendar.get(Calendar.MONTH)).append("\n");
+// sb.append("").append(calendar.get(Calendar.DAY_OF_MONTH)).append("\n");
+// sb.append("").append(calendar.get(Calendar.HOUR_OF_DAY)).append("\n");
+// sb.append("").append(calendar.get(Calendar.MINUTE)).append("\n");
+// sb.append("").append(calendar.get(Calendar.ZONE_OFFSET)).append("\n");
+// sb.append("\n");
+//
+// mMetadata.add(sb.toString());
+// }
+}
diff --git a/src/bits/BinaryMessage.java b/src/bits/BinaryMessage.java
index b9ae1570..56e21a12 100644
--- a/src/bits/BinaryMessage.java
+++ b/src/bits/BinaryMessage.java
@@ -17,6 +17,7 @@
******************************************************************************/
package bits;
+import java.util.Arrays;
import java.util.BitSet;
import java.util.logging.LogManager;
@@ -283,6 +284,33 @@ public class BinaryMessage extends BitSet
return values;
}
+
+ /**
+ * Returns this message as a little endian byte array. Extra 0 bits will be padded to the end to make the overall
+ * length a multiple of 8.
+ * @return little endian message byte array
+ */
+ public byte[] toByteArray()
+ {
+ int length = size() / 8;
+
+ if(length * 8 < size())
+ {
+ length ++;
+ }
+
+ byte[] bytes = new byte[length];
+
+ int pointer = 0;
+
+ for(int x = 0; x < length; x++)
+ {
+ bytes[x] = getByte(pointer);
+ pointer += 8;
+ }
+
+ return bytes;
+ }
/**
* Returns this bitset as a reversed bit order array of integer ones and zeros
@@ -421,6 +449,57 @@ public class BinaryMessage extends BitSet
return (byte)( value & 0xFF );
}
+ /**
+ * Returns the byte value contained between index and index + 7 bit positions
+ * @param index specifying the start of the byte value
+ * @return byte value contained at index <> index + 7 bit positions
+ */
+ public byte getByte(int index)
+ {
+ assert((index + 7) <= size());
+
+ int value = 0;
+
+ for(int x = 0; x < 8; x++)
+ {
+ value = value << 1;
+
+ if(get(index + x))
+ {
+ value++;
+ }
+ }
+
+ return (byte)value;
+ }
+
+ /**
+ * Sets the byte value at index position through index + 7 position.
+ *
+ * @param index bit position where to write the MSB of the byte value
+ * @param value to write at the index through index + 7 bit positions
+ */
+ public void setByte(int index, byte value)
+ {
+ assert((index + 8) <= size());
+
+ int mask = 0x80;
+
+ for(int x = 0; x < 8; x++)
+ {
+ if((mask & value) == mask)
+ {
+ set(index + x);
+ }
+ else
+ {
+ clear(index + x);
+ }
+
+ mask = mask >> 1;
+ }
+ }
+
/**
* Returns the long value represented by the bit array
* @param bits - an array of bit positions that will be treated as if they
diff --git a/src/util/XTEA.java b/src/util/XTEA.java
new file mode 100644
index 00000000..575c92c8
--- /dev/null
+++ b/src/util/XTEA.java
@@ -0,0 +1,184 @@
+/*******************************************************************************
+ * sdrtrunk
+ * Copyright (C) 2014-2016 Dennis Sheirer
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
+ *
+ ******************************************************************************/
+package util;
+
+import java.util.Arrays;
+
+public class XTEA
+{
+ //Key Schedule = (sqrt(5) 1) * 2^31
+ private static final int K = 0x9E3779B9;
+
+ //Byte mask values
+ private static final int MASK0 = 0xFF;
+ private static final int MASK1 = 0xFF00;
+ private static final int MASK2 = 0xFF0000;
+ private static final int MASK3 = 0xFF000000;
+
+ //Pre-calculated sub keys derived from encryption key
+ private int mSubKeys1[] = new int[32];
+ private int mSubKeys2[] = new int[32];
+
+ /**
+ * XTEA block cipher encryption using 128 bit encryption key and 64 bit plain text block size with 32 Feistel rounds.
+ * Shorter keys and/or plaintext values can be zero padded to the specified lengths.
+ *
+ * Algorithm source code adapted from: https://people.rit.edu/rab3106/CryptoReport.pdf (as accessed Sep. 2016)
+ *
+ * The encryption key can be changed at any point by invoking the setKey() method.
+ *
+ * @param key for encryption
+ */
+ public XTEA(String key)
+ {
+ setKey(Arrays.copyOf(key.getBytes(), 16));
+ }
+
+ /**
+ * Set the key for this block cipher. Key must be 128 bits or 16 bytes. The key value can be zero padded to
+ * achieve the specified key length.
+ *
+ * @param key encryption key to use
+ */
+ public void setKey(byte[] key)
+ {
+ assert(key.length == 16);
+
+ int keyInts[] = new int[8];
+
+ //Convert the key to an array of integers
+ for (int i = 0; i < 16; i += 4)
+ {
+ keyInts[i / 4] = ((key[i + 3] << 0)) & MASK0 | ((key[i + 2]) << 8) & MASK1 |
+ ((key[i + 1]) << 16) & MASK2 | ((key[i + 0]) << 24) & MASK3;
+ }
+
+ //Calculate subkeys for each Feistel round
+ int sum, cycle;
+
+ for (sum = 0, cycle = 0; cycle < 32; cycle++)
+ {
+ //Calculate round 1 subkey
+ mSubKeys1[cycle] = keyInts[sum & 3];
+
+ //Add K to sum
+ sum += K;
+
+ //Calculate round 2 subkey
+ mSubKeys2[cycle] = keyInts[(sum >>> 11) & 3];
+ }
+ }
+
+ /**
+ * Encrypts the plaintext argument. Textual byte array must be 64 bits or 8 bytes in length and can be zero padded
+ * to achieve the specified length. Encrypted text byte array replaces the original plaintext byte array.
+ *
+ * @param plainText to encrypt
+ * @return cipher text
+ */
+ public byte[] encrypt(byte[] plainText)
+ {
+ assert(plainText.length == 8);
+
+ //Convert the 8 bytes of plaintext to 2 integers
+ int result0 = (plainText[3] << 0) & MASK0 | (plainText[2] << 8) & MASK1 |
+ (plainText[1] << 16) & MASK2 | (plainText[0] << 24) & MASK3;
+ int result1 = (plainText[7] << 0) & MASK0 | (plainText[6] << 8) & MASK1 |
+ (plainText[5] << 16) & MASK2 | (plainText[4] << 24) & MASK3;
+
+ //Unrolled loop for all Feistel rounds
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 0) + mSubKeys1[0];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 1) + mSubKeys2[0];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 1) + mSubKeys1[1];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 2) + mSubKeys2[1];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 2) + mSubKeys1[2];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 3) + mSubKeys2[2];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 3) + mSubKeys1[3];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 4) + mSubKeys2[3];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 4) + mSubKeys1[4];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 5) + mSubKeys2[4];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 5) + mSubKeys1[5];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 6) + mSubKeys2[5];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 6) + mSubKeys1[6];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 7) + mSubKeys2[6];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 7) + mSubKeys1[7];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 8) + mSubKeys2[7];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 8) + mSubKeys1[8];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 9) + mSubKeys2[8];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 9) + mSubKeys1[9];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 10) + mSubKeys2[9];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 10) + mSubKeys1[10];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 11) + mSubKeys2[10];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 11) + mSubKeys1[11];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 12) + mSubKeys2[11];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 12) + mSubKeys1[12];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 13) + mSubKeys2[12];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 13) + mSubKeys1[13];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 14) + mSubKeys2[13];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 14) + mSubKeys1[14];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 15) + mSubKeys2[14];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 15) + mSubKeys1[15];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 16) + mSubKeys2[15];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 16) + mSubKeys1[16];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 17) + mSubKeys2[16];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 17) + mSubKeys1[17];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 18) + mSubKeys2[17];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 18) + mSubKeys1[18];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 19) + mSubKeys2[18];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 19) + mSubKeys1[19];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 20) + mSubKeys2[19];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 20) + mSubKeys1[20];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 21) + mSubKeys2[20];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 21) + mSubKeys1[21];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 22) + mSubKeys2[21];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 22) + mSubKeys1[22];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 23) + mSubKeys2[22];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 23) + mSubKeys1[23];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 24) + mSubKeys2[23];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 24) + mSubKeys1[24];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 25) + mSubKeys2[24];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 25) + mSubKeys1[25];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 26) + mSubKeys2[25];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 26) + mSubKeys1[26];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 27) + mSubKeys2[26];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 27) + mSubKeys1[27];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 28) + mSubKeys2[27];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 28) + mSubKeys1[28];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 29) + mSubKeys2[28];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 29) + mSubKeys1[29];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 30) + mSubKeys2[29];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 30) + mSubKeys1[30];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 31) + mSubKeys2[30];
+ result0 += (result1 << 4 ^ result1 >>> 5) + result1 ^ (K * 31) + mSubKeys1[31];
+ result1 += (result0 << 4 ^ result0 >>> 5) + result0 ^ (K * 32) + mSubKeys2[31];
+
+ byte[] cipherText = new byte[8];
+
+ cipherText[0] = (byte)(result0 >>> 24);
+ cipherText[1] = (byte)(result0 >>> 16);
+ cipherText[2] = (byte)(result0 >>> 8);
+ cipherText[3] = (byte)(result0 >>> 0);
+ cipherText[4] = (byte)(result1 >>> 24);
+ cipherText[5] = (byte)(result1 >>> 16);
+ cipherText[6] = (byte)(result1 >>> 8);
+ cipherText[7] = (byte)(result1 >>> 0);
+
+ return cipherText;
+ }
+}