Android上的Webradio - 通过移动网络发出的声音故障

时间:2014-10-13 14:15:24

标签: java android streaming android-mediaplayer shoutcast

我正在尝试创建一个小应用程序,允许我使用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之间没有区别。或者我做错了什么?有谁能告诉我我做错了什么或者请指出正确的解决方案吗?

0 个答案:

没有答案