我正在尝试创建一个小应用程序,允许我使用Android MediaPlayer类流式传输在线广播电台。我已经阅读了有关shoutcast协议的信息,例如: here并花费数小时试图自行解决此问题。
由于正常的HttpURLConnection或URLConnection在Android 4.2.2下不再起作用,我提出了保持简单并使用普通套接字来完成工作的想法。 我面临的一个奇怪的问题是,当我连接到我的WiFi时,它工作得很好(只要我请求和处理元数据信息),但如果我连接到任何类型的移动网络,如LTE / 4G有很多故障,我不能再获取元数据信息,因为该方法返回一个不可读的字符串。
这是我的流媒体代码:
public class Streamer {
private static final String LOG_TAG = "Streamer";
/** The line feed character. */
private static final char LF = 10;
/** The carriage return character. */
private static final char CR = 13;
private static final CharSequence ICY_META = "icy-metaint:";
private static final String ICY_METADATA = "Icy-MetaData: 1";
/** The pattern to match ICY metadata in a stream, e.g. StreamTitle, StreamUrl. */
private static Pattern ICY_METADATA_PATTERN = Pattern.compile("StreamTitle='(.*)';");
private final StreamEventHandler notifier;
private final BlockingQueue<byte[]> mQueue;
private URL mUrl;
private int mKBytesPerSecond;
private boolean icy;
private static final String ICY_BITRATE = "icy-br:";
public Streamer(StreamEventHandler notifier, BlockingQueue<byte[]> queue) {
this.notifier = notifier;
this.mQueue = queue;
}
public void stream(URL url) throws IOException {
Log.d(LOG_TAG, "Streaming from: " + url);
mUrl = url;
BufferedWriter outToRadio = null;
InputStream inFromRadio = null;
Socket radio = null;
int metaInt = 0;
try {
try {
radio = new Socket(url.getHost(), 80);
outToRadio = new BufferedWriter(new PrintWriter(radio.getOutputStream()));
inFromRadio = radio.getInputStream();
} catch (IOException e1) {
Log.e(LOG_TAG, e1.getMessage());
notifier.onConnectionLost(url);
}
String header = "GET " + url.getFile() + " HTTP/1.1" + CR + LF + "Host: "
+ url.getHost() + CR + LF + ICY_METADATA + CR + LF + CR + LF;
Log.d(LOG_TAG, header);
try {
outToRadio.write(header);
outToRadio.flush();
} catch (IOException e) {
Log.e(LOG_TAG, e.getLocalizedMessage());
notifier.onConnectionLost(url);
}
BufferedReader bufReader = new BufferedReader(new InputStreamReader(inFromRadio));
String headerLine = null;
try {
headerLine = bufReader.readLine();
Log.d(LOG_TAG, headerLine);
if (!headerLine.contains("200 OK")) {
Log.e(LOG_TAG, "HTTP error. Didn't receive 200 OK");
radio.close();
return;
}
} catch (IOException e) {
Log.e(LOG_TAG, e.getMessage());
notifier.onConnectionLost(url);
}
while (true) {
try {
headerLine = bufReader.readLine();
Log.d(LOG_TAG, headerLine);
if (headerLine.contains(ICY_META)) {
metaInt = Integer.parseInt(headerLine.substring(ICY_META.length()).trim());
Log.d(LOG_TAG, "Icy Metaint = " + metaInt);
icy = true;
}
if (headerLine.contains(ICY_BITRATE)) {
int bitrate = Integer.parseInt(headerLine.substring(ICY_BITRATE.length())
.trim());
mKBytesPerSecond = bitrate * 1024 / 8;
Log.d(LOG_TAG, "Icy Bitrate = " + bitrate);
}
if (headerLine.equalsIgnoreCase("") || headerLine == null) {
break;
}
} catch (IOException e) {
Log.e(LOG_TAG, e.getMessage());
notifier.onConnectionLost(url);
}
}
Log.d(LOG_TAG, "HTTP request finished. Start streaming!");
playMp3Stream(inFromRadio, metaInt);
}
finally {
Log.d(LOG_TAG, "Disconnect");
if (radio != null) {
radio.close();
}
}
}
private void playMp3Stream(InputStream in, int metaint) throws IOException {
try {
if (icy) {
playIcyStream(in, metaint);
} else {
Log.d(LOG_TAG, "found mp3 stream , streaming");
playUnknownAudioStream(in);
}
} finally {
in.close();
}
}
private void playUnknownAudioStream(InputStream in) throws IOException {
byte[] buf = new byte[mKBytesPerSecond];
int read;
try {
while ((read = readFully(in, buf)) != -1 && !notifier.isCancelled()) {
notifier.bytesBuffered(read);
synchronized (StreamManagerService.bufGuard) {
mQueue.put(buf);
}
}
} catch (SocketTimeoutException ex) {
reconnect();
} catch (InterruptedException e) {
}
}
private void playIcyStream(InputStream in, int metaint) throws IOException {
Log.d(LOG_TAG, "Playing ICY stream with metaint: " + metaint);
byte[] buf = new byte[metaint];
ByteBuffer bytebuffer = ByteBuffer.wrap(new byte[mKBytesPerSecond]);
int read;
try {
while ((read = readFully(in, buf)) != -1 && !notifier.isCancelled()) {
// String rawStream = new String(buf, "UTF-8");
// Log.d("STREAM", rawStream);
skipIcyMetadata(in);
if (bytebuffer.remaining() > metaint) {
bytebuffer.put(buf);
}
else if (bytebuffer.remaining() == metaint) {
bytebuffer.put(buf);
putToAudioBuffer(bytebuffer);
}
else {
int splitPosition = bytebuffer.remaining();
bytebuffer.put(buf, 0, splitPosition);
putToAudioBuffer(bytebuffer);
int offset = splitPosition;
splitPosition = offset + mKBytesPerSecond >= metaint ? metaint - offset
: mKBytesPerSecond;
while (metaint - offset > bytebuffer.remaining()) {
bytebuffer.put(buf, offset, splitPosition);
putToAudioBuffer(bytebuffer);
offset += splitPosition;
splitPosition = offset + mKBytesPerSecond >= metaint ? metaint - offset
: mKBytesPerSecond;
}
bytebuffer.put(buf, offset, (metaint - offset));
}
}
} catch (SocketTimeoutException ex) {
Log.e(LOG_TAG, "reconnecting...");
reconnect();
} catch (SocketException ex2) {
Log.e(LOG_TAG, "reconnecting...");
reconnect();
} catch (InterruptedException e) {
Log.d(LOG_TAG, "writing to queue was interrupted");
}
}
private void putToAudioBuffer(ByteBuffer bytebuffer) throws InterruptedException {
bytebuffer.rewind();
byte[] oneSecond = new byte[mKBytesPerSecond];
bytebuffer.get(oneSecond);
bytebuffer.rewind();
synchronized (StreamManagerService.bufGuard) {
mQueue.put(oneSecond);
}
notifier.bytesBuffered(mQueue.size());
}
private void skipIcyMetadata(InputStream in) throws IOException {
int val = in.read();
if (val == 0) {
return;
}
Log.d(LOG_TAG, " - First metadata byte: " + val);
int len = val * 16;
if (len > 0) {
byte[] buf = new byte[len];
int read = readFully(in, buf);
Log.d(LOG_TAG, " - Metadata bytes: " + read);
String metadataRaw = new String(buf, "UTF-8");
Log.d(LOG_TAG, " - Metadata : " + metadataRaw);
// String metadata = parseIcyMetadata(metadataRaw);
// notifier.metadataReceived(metadata);
}
}
private int readFully(InputStream in, byte[] b) throws IOException {
int len = b.length;
int n = 0;
while (n < len) {
int count = in.read(b, n, len - n);
if (count < 0) {
if (n == 0) {
return -1;
} else {
return n;
}
}
n += count;
}
// String metadataRaw = new String(b, "UTF-8");
//
// Log.d(LOG_TAG, " - Stream : " + metadataRaw);
return n;
}
public void reconnect() {
int tries = 1;
ConnectivityManager cm = (ConnectivityManager) StreamManagerService.mContext
.getSystemService(Context.CONNECTIVITY_SERVICE);
while (cm.getActiveNetworkInfo() == null || !cm.getActiveNetworkInfo().isConnected()) {
Log.e(LOG_TAG, "No internet connection. Reconnection try # " + tries);
tries++;
if (tries > 120) {
this.notifier.onConnectionLost(mUrl);
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Log.i(LOG_TAG, "Internet reconnect established");
try {
stream(mUrl);
} catch (IOException e) {
Log.d(LOG_TAG, "Failed to reestablish connection to stream");
this.notifier.onConnectionLost(mUrl);
}
}
}
这是使用MediaPlayer的消费者
public class StreamConsumer {
private static final int INIT_MP_FEED_SIZE = 4;
private static final String LOG_TAG = "StreamConsumer";
private final IsCancelledNotifier mNotifier;
private ServerSocket mServerSocket;
private final BlockingQueue<byte[]> mQueue;
private MediaplayerRunnable mpRunnable;
private volatile boolean mNoError;
public StreamConsumer(IsCancelledNotifier notifier, BlockingQueue<byte[]> queue) {
mNotifier = notifier;
mQueue = queue;
mNoError = true;
try {
mServerSocket = new ServerSocket(0, 0, InetAddress.getByAddress(new byte[] { 127, 0, 0,
1 }));
} catch (IOException e) {
Log.e(LOG_TAG, "can't open ServerSocket");
}
}
public boolean streamDataToSocket() {
BufferedOutputStream outOnClient = null;
Socket client = null;
try {
if (mServerSocket == null) {
Log.d(LOG_TAG, "serversocket = null");
}
int currentPort = mServerSocket.getLocalPort();
Log.d(LOG_TAG, "Port: " + currentPort + " was assigned");
mpRunnable = new MediaplayerRunnable(currentPort);
new Thread(mpRunnable).start();
Log.d(LOG_TAG, "Server is waiting for client on Port: " + currentPort);
client = mServerSocket.accept();
Log.d(LOG_TAG, "client is ready");
outOnClient = new BufferedOutputStream(client.getOutputStream());
boolean initialMPFeed = true;
while (!mNotifier.isCancelled() && mNoError) {
if (initialMPFeed) {
for (int i = 0; i < INIT_MP_FEED_SIZE; i++) {
synchronized (StreamManagerService.bufGuard) {
byte[] temp = mQueue.remove();
Log.d(LOG_TAG, "Writing " + temp.length + " bytes");
outOnClient.write(temp);
}
// outOnClient.flush();
initialMPFeed = false;
}
}
if (mQueue.size() >= 1) {
Log.i(LOG_TAG, "in Buffer: " + mQueue.size() + " sec");
synchronized (StreamManagerService.bufGuard) {
byte[] temp = mQueue.take();
outOnClient.write(temp);
}
// outOnClient.flush();
}
Thread.sleep(500);
if (mNotifier.isCancelled()) {
break;
}
Thread.sleep(500);
}
} catch (NullPointerException e) {
mpRunnable.releaseMp();
Log.e(LOG_TAG, "serversocket couldn't be created, break up Consumer");
} catch (IOException e) {
mpRunnable.releaseMp();
Log.e(LOG_TAG, "IO: " + e.getMessage());
} catch (InterruptedException e) {
Log.e(LOG_TAG, "interrupted");
mpRunnable.releaseMp();
Log.d(LOG_TAG, "mediaplayer released");
} finally {
if (outOnClient != null) {
try {
outOnClient.close();
} catch (IOException e) {
Log.e(LOG_TAG, "cannt close outputstream");
}
}
if (client != null) {
try {
client.close();
} catch (IOException e) {
Log.e(LOG_TAG, "cannt close client socket");
}
}
}
Log.d(LOG_TAG, "end of the Consumer...");
mpRunnable.releaseMp();
return mNoError;
}
class MediaplayerRunnable implements Runnable {
private volatile MediaPlayer mp;
private final int mCurrentPort;
public MediaplayerRunnable(int port) {
this.mCurrentPort = port;
}
@Override
public void run() {
Log.d(LOG_TAG, "mediaplayer is starting...");
mp = new MediaPlayer();
mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
try {
mp.setDataSource("http://127.0.0.1:" + mCurrentPort);
mp.setOnPreparedListener(new OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mp.start();
int maxVolume = 100;
int currVolume = 25;
float log1 = (float) (Math.log(maxVolume - currVolume) / Math
.log(maxVolume));
mp.setVolume(0, 1 - log1);
Log.d(LOG_TAG, "mediaplayer started.");
}
});
mp.prepareAsync();
mp.setOnErrorListener(new OnErrorListener() {
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
mNoError = false;
Log.d(LOG_TAG, "Mediplayer error: " + what + "with extra" + extra);
return false;
}
});
} catch (Exception e) {
Log.e(LOG_TAG, "cannot set Datasource or Mediaplayer had onPrepare error");
}
}
public void releaseMp() {
mp.release();
Log.d(LOG_TAG, "mediaplayer released");
}
}
如果我将icy-metadata标头设置为0,我也会遇到毛刺和哔哔声。如果我将MediaPlayer的数据源设置为流式URL,则流式传输没有任何问题。奇怪的是,如果我通过我的套接字将从站点返回的HTTP标头发送到播放器,它将导致套接字异常失败发送(EPIPE:断开的管道或ECONNRESET)
我真的很困惑,因为我认为移动网络和WiFi之间没有区别。或者我做错了什么?有谁能告诉我我做错了什么或者请指出正确的解决方案吗?