#30 - WIP on the shoutcast v2 protocol

This commit is contained in:
Denny
2016-10-21 05:30:36 -04:00
parent a43ae9ce53
commit ac23aea602
16 changed files with 1016 additions and 38 deletions

View File

@@ -4,6 +4,7 @@
<w>bahroo</w>
<w>esnid</w>
<w>fleetsync</w>
<w>icecast</w>
<w>ident</w>
<w>polyphase</w>
<w>shoutcast</w>

View File

@@ -25,8 +25,45 @@
<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!/" />
<root url="jar://$PROJECT_DIR$/imports/commons-codec-1.9.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/commons-logging-1.2.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/httpclient-4.5.2.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/httpcore-4.4.5.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/httpasyncclient-4.1.2.jar!/" />
<root url="jar://$PROJECT_DIR$/imports/httpcore-nio-4.4.5.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
<JAVADOC>
<root url="https://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/index.html" />
</JAVADOC>
<SOURCES>
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/fluent-hc/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/fluent-hc/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/fluent-hc/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-cache/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-cache/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-cache/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-osgi/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-osgi/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-win/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-win/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient-win/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpmime/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-asyncclient-4.1.2/httpasyncclient/src/test/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/examples" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/main/java-deprecated" />
<root url="file://$USER_HOME$/Downloads/httpcomponents-client-4.5.2/httpclient/src/test/java" />
</SOURCES>
</library>
</component>

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
imports/httpcore-4.4.5.jar Normal file

Binary file not shown.

View File

@@ -45,6 +45,16 @@ public enum BroadcastState
*/
INVALID_PASSWORD("Invalid Password", true),
/**
* Remote server max sources has been exceeded
*/
MAX_SOURCES_EXCEEDED("Max Sources Exceeded", true),
/**
* Specified mount point is already in use
*/
MOUNT_POINT_IN_USE("Mount Point In Use", true),
/**
* Server is not known or reachable
*/
@@ -60,6 +70,11 @@ public enum BroadcastState
*/
READY("Ready", false),
/**
* Error on the remote server
*/
REMOTE_SERVER_ERROR("Remote Server Error", true),
/**
* Server host name or port is invalid
*/

View File

@@ -18,6 +18,8 @@
******************************************************************************/
package audio.broadcast;
import audio.broadcast.icecast.IcecastConfiguration;
import audio.broadcast.icecast.IcecastHandler;
import audio.broadcast.shoutcast.v1.ShoutcastV1Configuration;
import audio.broadcast.shoutcast.v1.ShoutcastV1Handler;
import audio.broadcast.shoutcast.v2.ShoutcastV2Configuration;
@@ -29,7 +31,8 @@ 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;
public static final boolean MP3_CONSTANT_BITRATE = false;
public static final boolean MP3_VARIABLE_BITRATE = true;
/**
* Creates an audio streaming broadcaster for the configuration
@@ -49,7 +52,8 @@ public class BroadcasterFactory
case BROADCASTIFY:
break;
case ICECAST:
break;
return new Broadcaster(threadPoolManager,
new IcecastHandler((IcecastConfiguration)configuration, converter));
case SHOUTCAST_V1:
return new Broadcaster(threadPoolManager,
new ShoutcastV1Handler((ShoutcastV1Configuration)configuration, converter));
@@ -74,7 +78,7 @@ public class BroadcasterFactory
switch(format)
{
case MP3:
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_NO_VARIABLE_BITRATE);
return new MP3AudioConverter(MP3_MONO_16_KHZ_BITRATE, MP3_CONSTANT_BITRATE);
}
return null;

View File

@@ -0,0 +1,181 @@
/*******************************************************************************
* 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.convert.IAudioConverter;
import org.apache.http.HttpEntity;
import org.apache.http.HttpVersion;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.entity.BasicHttpEntity;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.nio.ContentEncoder;
import org.apache.http.nio.IOControl;
import org.apache.http.nio.entity.HttpAsyncContentProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sample.Listener;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
public class HttpAsyncAudioStreamer extends BasicHttpEntityEnclosingRequest
implements IAudioPacketListener, Listener<AudioPacket>, HttpAsyncContentProducer
{
private final static Logger mLog = LoggerFactory.getLogger( HttpAsyncAudioStreamer.class );
private LinkedBlockingQueue<AudioPacket> mAudioQueue;
private IAudioConverter mIAudioConverter;
private IOControl mIOControl;
private AtomicBoolean mSuspended = new AtomicBoolean();
public HttpAsyncAudioStreamer(String method, String uri, IAudioConverter audioConverter, int queueSize)
{
super(method, uri, HttpVersion.HTTP_1_1);
mIAudioConverter = audioConverter;
mAudioQueue = new LinkedBlockingQueue<>(queueSize);
setEntity(new MyHttpEntity());
}
/**
* Queues the audio packet for conversion and streaming. Audio packets are assumed to arrive in sequence.
*/
@Override
public void receive(AudioPacket audioPacket)
{
//Insert the packet in the queue. If the queue is currently full, remove the head element and attempt
//to reinsert the packet.
if(!mAudioQueue.offer(audioPacket))
{
mLog.debug("Streamer - making room to add an audio packet");
mAudioQueue.poll();
mAudioQueue.offer(audioPacket);
}
if(mIOControl != null && mSuspended.compareAndSet(true, false))
{
mLog.debug("Streamer - content available, resuming streaming");
mIOControl.requestOutput();
}
}
@Override
public Listener<AudioPacket> getAudioPacketListener()
{
return this;
}
@Override
public void produceContent(ContentEncoder contentEncoder, IOControl ioControl) throws IOException
{
//IOControl is thread safe, so we'll keep a reference to it so that we can do flow control
if(mIOControl == null)
{
mLog.debug("IOControl class is: " + ioControl.getClass());
mIOControl = ioControl;
}
AudioPacket packet = mAudioQueue.poll();
List<AudioPacket> packets = new ArrayList<>();
packets.add(packet);
byte[] converted = mIAudioConverter.convert(packets);
mLog.debug("Produce Content - writing converted audio: " + converted.length + " to:" + contentEncoder.getClass());
int wrote = contentEncoder.write(ByteBuffer.wrap(converted));
mLog.debug("We wrote [" + wrote + "] bytes to the content encoder");
if(mAudioQueue.isEmpty() && mSuspended.compareAndSet(false, true))
{
mLog.debug("Produce Content - suspending output");
mIOControl.suspendOutput();
}
// List<AudioPacket> packets = new ArrayList<>();
// mAudioQueue.drainTo(packets);
//
// if(packets.isEmpty() && mSuspended.compareAndSet(false, true))
// {
// mLog.debug("Produce Content - no audio - suspending output");
// mIOControl.suspendOutput();
// }
// else
// {
// byte[] converted = mIAudioConverter.convert(packets);
// mLog.debug("Produce Content - writing converted audio: " + converted.length);
// contentEncoder.write(ByteBuffer.wrap(converted));
// }
}
@Override
public boolean isRepeatable()
{
return false;
}
@Override
public void close() throws IOException
{
mLog.debug("We were closed!");
}
public class MyHttpEntity extends AbstractHttpEntity
{
public MyHttpEntity()
{
}
@Override
public boolean isRepeatable()
{
return false;
}
@Override
public long getContentLength()
{
return -1;
}
@Override
public InputStream getContent() throws IOException, UnsupportedOperationException
{
return null;
}
@Override
public void writeTo(OutputStream outputStream) throws IOException
{
}
@Override
public boolean isStreaming()
{
return true;
}
}
}

View File

@@ -0,0 +1,202 @@
/*******************************************************************************
* 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.icecast;
import audio.broadcast.BroadcastConfiguration;
import audio.broadcast.BroadcastFormat;
import audio.broadcast.BroadcastServerType;
import java.util.Base64;
public class IcecastConfiguration extends BroadcastConfiguration
{
private String mUserName;
private String mMountPoint;
private String mStreamName;
private String mDescription;
private String mGenre;
private boolean mPublic;
private int mChannels;
private int mBitRate;
private String mURL;
public IcecastConfiguration(BroadcastFormat format)
{
super(format);
}
@Override
public BroadcastServerType getBroadcastServerType()
{
return BroadcastServerType.ICECAST;
}
/**
* Base64 encoded version of the username and password with prepended 'Basic ' tag.
*/
public String getAuthorization()
{
StringBuilder sb = new StringBuilder();
sb.append(getUserName()).append(":").append(getPassword());
String base64 = Base64.getEncoder().encodeToString(sb.toString().getBytes());
return "Basic " + base64;
}
public String getUserName()
{
return mUserName;
}
public void setUserName(String userName)
{
mUserName = userName;
}
/**
* Mount point or path to the stream
* @return mount point
*/
public String getMountPoint()
{
return mMountPoint;
}
/**
* Sets the mount point (path) for the stream
* @param mountPoint
*/
public void setMountPoint(String mountPoint)
{
mMountPoint = mountPoint;
}
/**
* Stream name
*/
public String getStreamName()
{
return mStreamName;
}
/**
* Sets the stream name
* @param name
*/
public void setStreamName(String name)
{
mStreamName = name;
}
public String getDescription()
{
return mDescription;
}
public void setDescription(String description)
{
mDescription = description;
}
/**
* 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;
}
}

View File

@@ -0,0 +1,381 @@
/*******************************************************************************
* 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.icecast;
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.HttpAsyncAudioStreamer;
import audio.convert.IAudioConverter;
import controller.ThreadPoolManager;
import org.apache.http.HttpException;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.nio.IOControl;
import org.apache.http.nio.client.methods.AsyncCharConsumer;
import org.apache.http.nio.client.methods.HttpAsyncMethods;
import org.apache.http.nio.protocol.BasicAsyncRequestProducer;
import org.apache.http.nio.protocol.HttpAsyncRequestProducer;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import properties.SystemProperties;
import record.wave.AudioPacketMonoWaveReader;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.CharBuffer;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.Future;
public class IcecastHandler extends BroadcastHandler
{
private final static Logger mLog = LoggerFactory.getLogger( IcecastHandler.class );
private static final String HTTP_100_CONTINUE = "100-continue";
private static final long RECONNECT_INTERVAL_MILLISECONDS = 15000; //15 seconds
private long mLastConnectionAttempt = 0;
private CloseableHttpAsyncClient mHttpClient;
private HttpAsyncAudioStreamer mAudioStreamer;
private URI mURI;
private int mCounter = 0;
/**
* Creates an Icecast broadcast handler.
* @param configuration
* @param audioConverter
*/
public IcecastHandler(IcecastConfiguration configuration, IAudioConverter audioConverter)
{
super(configuration, audioConverter);
mAudioStreamer = new HttpAsyncAudioStreamer(HttpPut.METHOD_NAME, getConfiguration().getMountPoint(),
getAudioConverter(), 20);
}
/**
* Configuration information
*/
private IcecastConfiguration getConfiguration()
{
return (IcecastConfiguration)getBroadcastConfiguration();
}
private URI getURI() throws URISyntaxException
{
if(mURI == null)
{
mURI = new URIBuilder()
.setScheme("http")
.setHost(getConfiguration().getHost())
.setPort(getConfiguration().getPort())
.setPath(getConfiguration().getMountPoint())
.build();
}
return mURI;
}
/**
* Process audio packets using Shoutcast V1 Protocol
* @param audioPackets
*/
@Override
public void broadcast(List<AudioPacket> audioPackets)
{
if(mCounter > 10)
{
mLog.debug("Connecting ...");
connect();
}
for(AudioPacket audioPacket: audioPackets)
{
mAudioStreamer.receive(audioPacket);
mCounter++;
}
}
/**
* 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)
{
mLog.debug("Creating a connection to icecast server");
mLastConnectionAttempt = System.currentTimeMillis();
if(mHttpClient == null)
{
mHttpClient = HttpAsyncClients.createDefault();
mHttpClient.start();
}
// try
// {
HttpHost httpHost = new HttpHost(getConfiguration().getHost(), getConfiguration().getPort());
// mAudioStreamer.addHeader(IcecastHeader.EXPECT.getValue(), HTTP_100_CONTINUE);
mAudioStreamer.addHeader(IcecastHeader.AUTHORIZATION.getValue(), getConfiguration().getAuthorization());
mAudioStreamer.addHeader(HttpHeaders.ACCEPT, "*/*");
mAudioStreamer.addHeader(HttpHeaders.CONTENT_TYPE, getConfiguration().getBroadcastFormat().getValue());
mAudioStreamer.addHeader(IcecastHeader.PUBLIC.getValue(), getConfiguration().isPublic() ? "1" : "0");
mAudioStreamer.addHeader(IcecastHeader.NAME.getValue(), getConfiguration().getStreamName());
mAudioStreamer.addHeader(IcecastHeader.DESCRIPTION.getValue(), getConfiguration().getHost());
mAudioStreamer.addHeader(IcecastHeader.URL.getValue(), getConfiguration().getURL());
mAudioStreamer.addHeader(IcecastHeader.GENRE.getValue(), getConfiguration().getGenre());
mAudioStreamer.addHeader(IcecastHeader.USER_AGENT.getValue(), SystemProperties.getInstance().getApplicationName());
// BasicAsyncRequestProducer producer = new BasicAsyncRequestProducer(httpHost, mAudioStreamer);
BasicAsyncRequestProducer producer = new MyProducer(httpHost, mAudioStreamer, mAudioStreamer);
AsyncCharConsumer<HttpResponse> consumer = new AsyncCharConsumer<HttpResponse>()
{
@Override
protected void onCharReceived(CharBuffer charBuffer, IOControl ioControl) throws IOException
{
mLog.debug("Received consumer characters!");
}
@Override
protected void onResponseReceived(HttpResponse response) throws HttpException, IOException
{
mLog.debug("Received consumer response:\n" + response.toString());
switch(response.getStatusLine().getStatusCode())
{
case HttpStatus.SC_OK: //200
case HttpStatus.SC_CONTINUE: //100
mLog.debug("Connected to icecast server");
setBroadcastState(BroadcastState.CONNECTED);
break;
case HttpStatus.SC_UNAUTHORIZED: //401
setBroadcastState(BroadcastState.INVALID_PASSWORD);
mLog.error("Couldn't connect to Icecast2 server using login credentials");
break;
case HttpStatus.SC_FORBIDDEN: //403
String reason = response.getStatusLine().getReasonPhrase();
if(reason.contains("Content-Type"))
{
setBroadcastState(BroadcastState.UNSUPPORTED_AUDIO_FORMAT);
mLog.error("Icecast2 server does not support audio format");
}
else if(reason.startsWith("internal format"))
{
setBroadcastState(BroadcastState.REMOTE_SERVER_ERROR);
mLog.error("Icecast2 server has an error");
}
else if(reason.startsWith("too many sources"))
{
setBroadcastState(BroadcastState.MAX_SOURCES_EXCEEDED);
mLog.error("Icecast2 server maximum number of sources exceeded");
}
else if(reason.startsWith("Mountpoint"))
{
setBroadcastState(BroadcastState.MOUNT_POINT_IN_USE);
mLog.error("Icecast2 server mount point is already in use");
}
case HttpStatus.SC_INTERNAL_SERVER_ERROR: //500
setBroadcastState(BroadcastState.INVALID_PASSWORD);
mLog.error("Couldn't connect to Icecast2 server using login credentials");
break;
default:
setBroadcastState(BroadcastState.ERROR);
mLog.error("Error connecting to remote server. HTTP Status Code:" +
response.getStatusLine().getStatusCode() + " Reason:" +
response.getStatusLine().getReasonPhrase());
}
}
@Override
protected HttpResponse buildResult(HttpContext httpContext) throws Exception
{
mLog.debug("Received consumer request for response!!!!!!");
return null;
}
};
mLog.debug("Sending connection request");
Future<HttpResponse> response = mHttpClient.execute(producer, consumer, new FutureCallback<HttpResponse>()
{
@Override
public void completed(HttpResponse httpResponse)
{
mLog.debug("Received final response:\n" + httpResponse.toString());
}
@Override
public void failed(Exception e)
{
mLog.debug("Received final response error", e);
}
@Override
public void cancelled()
{
mLog.debug("Received final response - cancelled");
}
});
mLog.debug("Create connection finished ... processing");
}
}
public static void main(String[] args)
{
boolean test = false;
IcecastConfiguration config = new IcecastConfiguration(BroadcastFormat.MP3);
config.setAlias("Test Configuration");
config.setHost("localhost");
config.setPort(8000);
config.setMountPoint("/dennyTestStream");
config.setUserName("source");
config.setPassword("denny");
config.setStreamName("Denny's Audio Broadcast Test");
config.setDescription("Description");
config.setGenre("Scanner Audio");
config.setPublic(true);
config.setURL("http://localhost:8000");
config.setBitRate(16);
if(test)
{
CloseableHttpClient client = HttpClients.createDefault();
URI uri;
try
{
uri = new URIBuilder()
.setScheme("http")
.setHost(config.getHost())
.setPort(config.getPort())
.setPath(config.getMountPoint())
.build();
HttpPut httpPut = new HttpPut(uri);
httpPut.setProtocolVersion(HttpVersion.HTTP_1_1);
httpPut.addHeader(IcecastHeader.AUTHORIZATION.getValue(), config.getAuthorization());
httpPut.addHeader(IcecastHeader.HOST.getValue(), config.getHost());
httpPut.addHeader(IcecastHeader.ACCEPT.getValue(), "*/*");
httpPut.addHeader(IcecastHeader.CONTENT_TYPE.getValue(), config.getBroadcastFormat().getValue());
httpPut.addHeader(IcecastHeader.PUBLIC.getValue(), config.isPublic() ? "1" : "0");
httpPut.addHeader(IcecastHeader.NAME.getValue(), config.getStreamName());
httpPut.addHeader(IcecastHeader.DESCRIPTION.getValue(), config.getHost());
httpPut.addHeader(IcecastHeader.URL.getValue(), config.getURL());
httpPut.addHeader(IcecastHeader.GENRE.getValue(), config.getGenre());
httpPut.addHeader(IcecastHeader.USER_AGENT.getValue(), SystemProperties.getInstance().getApplicationName());
httpPut.addHeader(IcecastHeader.EXPECT.getValue(), HTTP_100_CONTINUE);
CloseableHttpResponse response = client.execute(httpPut);
mLog.debug("Response:" + response.toString());
}
catch(URISyntaxException e)
{
mLog.error("URI syntax exception", e);
}
catch(IOException ioe)
{
mLog.error("IO Exception", ioe);
}
}
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() + "]");
}
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>
*
******************************************************************************/
package audio.broadcast.icecast;
public enum IcecastHeader
{
ACCEPT("Accept"),
AUTHORIZATION("Authorization"),
CONTENT_TYPE("Content-Type"),
DESCRIPTION("ice-description"),
EXPECT("Expect"),
GENRE("ice-genre"),
HOST("Host"),
NAME("ice-name"),
PUBLIC("ice-public"),
URL("ice-url"),
USER_AGENT("User-Agent"),
UNKNOWN("unknown");
private String mValue;
private IcecastHeader(String value)
{
mValue = value;
}
public String getValue()
{
return mValue;
}
}

View File

@@ -0,0 +1,103 @@
/*******************************************************************************
* 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.icecast;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.nio.ContentEncoder;
import org.apache.http.nio.IOControl;
import org.apache.http.nio.entity.HttpAsyncContentProducer;
import org.apache.http.nio.protocol.BasicAsyncRequestProducer;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class MyProducer extends BasicAsyncRequestProducer
{
private final static Logger mLog = LoggerFactory.getLogger( MyProducer.class );
public MyProducer(HttpHost target, HttpEntityEnclosingRequest request, HttpAsyncContentProducer producer)
{
super(target, request, producer);
}
@Override
public void produceContent(ContentEncoder encoder, IOControl ioctrl) throws IOException
{
// mLog.debug("############### Producing Content");
super.produceContent(encoder, ioctrl);
}
@Override
public HttpRequest generateRequest()
{
mLog.debug("############### Generating a request");
HttpRequest request = super.generateRequest();
mLog.debug("############### Our request class is:" + request.getClass() );
return request;
}
@Override
public HttpHost getTarget()
{
mLog.debug("############### Getting our target");
return super.getTarget();
}
@Override
public void requestCompleted(HttpContext context)
{
mLog.debug("############### requesting completed");
super.requestCompleted(context);
}
@Override
public void failed(Exception ex)
{
mLog.debug("############### Fail!");
super.failed(ex);
}
@Override
public boolean isRepeatable()
{
mLog.debug("############### Indicating if we're repeatable");
return super.isRepeatable();
}
@Override
public void resetRequest() throws IOException
{
mLog.debug("############### resetting request");
super.resetRequest();
}
@Override
public void close() throws IOException
{
mLog.debug("############### Closing");
super.close();
}
}

View File

@@ -44,6 +44,7 @@ import audio.convert.IAudioConverter;
import controller.ThreadPoolManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import properties.SystemProperties;
import record.wave.AudioPacketMonoWaveReader;
import java.io.DataInputStream;
@@ -402,7 +403,8 @@ public class ShoutcastV2Handler extends BroadcastHandler
(response.getError() != null ? response.getError() : "no error message"));
}
metadata.add(UltravoxMetadata.BROADCAST_CLIENT_APPLICATION.asXML("sdrtrunk"));
metadata.add(UltravoxMetadata.BROADCAST_CLIENT_APPLICATION
.asXML(SystemProperties.getInstance().getApplicationName()));
mLog.debug("Fetching metadata messages from metadata...");
List<UltravoxMessage> messages = getMetadataMessages(metadata);

View File

@@ -229,7 +229,7 @@ public class SDRTrunk implements Listener<TunerEvent>
/**
* Setup main JFrame window
*/
mTitle = getTitle();
mTitle = SystemProperties.getInstance().getApplicationName();
mMainGui.setTitle( mTitle );
mMainGui.setBounds( 100, 100, 1280, 800 );
mMainGui.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
@@ -448,32 +448,6 @@ public class SDRTrunk implements Listener<TunerEvent>
return homePath;
}
private String getTitle()
{
StringBuilder sb = new StringBuilder();
sb.append( "sdrtrunk" );
try( BufferedReader reader = new BufferedReader(
new InputStreamReader( this.getClass()
.getResourceAsStream( "/sdrtrunk-version" ) ) ) )
{
String version = reader.readLine();
if( version != null )
{
sb.append( " V" );
sb.append( version );
}
}
catch( Exception e )
{
mLog.error( "Couldn't read sdrtrunk version from application jar file" );
}
return sb.toString();
}
@Override
public void receive( TunerEvent event )
{

View File

@@ -17,11 +17,13 @@
******************************************************************************/
package properties;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -47,7 +49,7 @@ public class SystemProperties
private static SystemProperties mInstance;
private static Properties mProperties;
private Path mPropertiesPath;
private String mApplicationName;
private SystemProperties()
{
@@ -67,7 +69,7 @@ public class SystemProperties
return mInstance;
}
/**
* Saves any currently changed settings to the application properties file
*/
@@ -86,9 +88,6 @@ public class SystemProperties
"SDRTrunk - SDR Trunking Decoder Application Settings";
mProperties.store( out, comments );
// mLog.info( "SystemProperties - saved current properties [" +
// propsPath.toString() + "]" );
}
catch( Exception e )
{
@@ -216,6 +215,37 @@ public class SystemProperties
propertiesPath.toString() + "]" );
}
public String getApplicationName()
{
if(mApplicationName == null)
{
StringBuilder sb = new StringBuilder();
sb.append( "sdrtrunk" );
try(BufferedReader reader = new BufferedReader(
new InputStreamReader( this.getClass()
.getResourceAsStream( "/sdrtrunk-version" ) ) ) )
{
String version = reader.readLine();
if( version != null )
{
sb.append( " V" );
sb.append( version );
}
}
catch( Exception e )
{
mLog.error( "Couldn't read sdrtrunk version from application jar file" );
}
mApplicationName = sb.toString();
}
return mApplicationName;
}
/**
* Returns the value of the property, or null if the property doesn't exist
*/