Android Open Source - droidtv Stream Service






From Project

Back to project page droidtv.

License

The source code is released under:

GNU General Public License

If you think the Android project droidtv listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/******************************************************************************
 *  DroidTV, live TV on Android devices with host USB port and a DVB tuner    *
 *  Copyright (C) 2012  Christian Ulrich <chrulri@gmail.com>                  *
 *                                                                            *
 *  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/>.     *
 ******************************************************************************/
/* ww  w. j av  a 2 s .  co  m*/
package com.chrulri.droidtv;

import static com.chrulri.droidtv.Utils.NEWLINE;

import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

import com.chrulri.droidtv.Utils.ProcessUtils;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

/**
 * DVBlast wrapper service <br/>
 * <br/>
 * README: {@link http://git.videolan.org/?p=dvblast.git;a=blob;f=README;h=
 * cda01aa2e0cf0999478a7dcb1d60305e5c8a7a7f
 * ;hb=350557c669ce3670b7ea1e252b11f261c0610239}
 */
public class StreamService extends Service {
    public class LocalBinder extends Binder {
        public StreamService getService() {
            return StreamService.this;
        }
    }

    private static final String TAG = StreamService.class.getSimpleName();

    static final int DVBLAST = R.raw.dvblast_2_1_0;
    static final int DVBLASTCTL = R.raw.dvblastctl_2_1_0;

    public static final String EXTRA_CHANNELCONFIG = "channelconfig";

    public enum DvbType {
        ATSC, DVBT, DVBC, DVBS
    }

    public class FrontendStatus {

        public static final int HAS_SIGNAL = 0x001;
        public static final int HAS_CARRIER = 0x02;
        public static final int HAS_VITERBI = 0x04;
        public static final int HAS_SYNC = 0x08;
        public static final int HAS_LOCK = 0x0F;
        public static final int REINIT = 0x10;

        public int status;
        public int ber;
        public int signal;
        public int snr;

        @Override
        public String toString() {
            return String.format(
                    "FrontendStatus[status=%X, ber=%X, signal=%X, snr=%X]",
                    status, ber, signal, snr);
        }
    }

    static final String UDP_IP = "127.0.0.1";
    static final int UDP_PORT = 1555;

    static final String HTTP_IP = "0.0.0.0";// "127.0.0.1";
    static final int HTTP_PORT = 1666;
    static final String HTTP_HEADER = "HTTP/1.1 200 OK" + NEWLINE +
            "Content-Type: video/mp2t" + NEWLINE +
            "Connection: keep-alive" + NEWLINE + NEWLINE;

    public static final Uri SERVICE_URI = Uri.parse(String.format("http://127.0.0.1:%d/tv.ts",
            HTTP_PORT));

    /**
     * ip:port 1 serviceid
     */
    static final String DVBLAST_CONFIG_CONTENT = UDP_IP + ":" + UDP_PORT + " 1 %d";
    static final String DVBLAST_CONFIG_FILENAME = "dvblast.conf";
    static final String DVBLAST_SOCKET = "droidtv.socket";
    static final int DVBLAST_CHECKDELAY = 2500;

    private AsyncDvblastTask mDvblastTask;
    private AsyncDvblastCtlTask mDvblastCtlTask;
    private AsyncStreamTask mStreamTask;
    private DatagramSocket mUdpSocket;
    private ServerSocket mHttpSocket;

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate");
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy");
        stopStream();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return new LocalBinder();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return false;
    }

    public void stopStream() {
        Log.d(TAG, ">>> stopStream");
        ProcessUtils.finishTask(mDvblastCtlTask, false);
        ProcessUtils.finishTask(mStreamTask, true);
        ProcessUtils.finishTask(mDvblastTask, true);
        if (mUdpSocket != null) {
            mUdpSocket.close();
        }
        if (mHttpSocket != null) {
            try {
                mHttpSocket.close();
            } catch (IOException e) {
                // nop
            }
        }
        Log.d(TAG, "<<< stopStream");
    }

    public String startStream(String channelconfig) {
        try {
            Log.d(TAG, ">>> startStream(" + channelconfig + ")");
            try {
                // watchdog socket file
                new File(getCacheDir(), DVBLAST_SOCKET).delete();
                // config file
                File configFile = new File(getCacheDir(), DVBLAST_CONFIG_FILENAME);
                PrintWriter writer = new PrintWriter(configFile);
                // sNAME/iFREQ/iServiceID
                String[] params = channelconfig.split(":");
                // check config length
                if (params.length != 3) {
                    throw new IOException("invalid DVB params count[" + params.length + "]");
                }
                // parse config
                String name = params[0];
                int freq = tryParseInt(params[1], "frequency");
                int sid = tryParseInt(params[2], "service ID");
                // print config
                writer.println(String.format(DVBLAST_CONFIG_CONTENT, sid));
                writer.close();
                // run dvblast
                Log.d(TAG, "dvblast(" + configFile + "," + freq + ")");
                mUdpSocket = new DatagramSocket(UDP_PORT, InetAddress.getByName(null));
                mUdpSocket.setSoTimeout(100);
                mHttpSocket = new ServerSocket(HTTP_PORT, 0/* FIXME */);
                mStreamTask = new AsyncStreamTask();
                mStreamTask.execute();
                mDvblastTask = new AsyncDvblastTask(configFile, freq);
                mDvblastTask.execute();
                mDvblastCtlTask = new AsyncDvblastCtlTask();
                mDvblastCtlTask.execute();
                return name;
            } catch (IOException e) {
                Log.e(TAG, "starting stream failed", e);
                sendError(e);
                stopStream();
                return null;
            }
        } finally {
            Log.d(TAG, "<<< startStream");
        }
    }

    class AsyncStreamTask extends AsyncTask<Void, Void, Void> {

        final String TAG = StreamService.TAG + "." + AsyncStreamTask.class.getSimpleName();

        @Override
        protected void onCancelled() {
            sendUpdates(null);
        }

        @Override
        protected void onPostExecute(Void result) {
            sendUpdates(null);
        }

        @Override
        protected Void doInBackground(Void... params) {
            Log.d(TAG, ">>> AsyncStreamTask");
            byte[] data = new byte[4096];
            DatagramPacket dataPacket = new DatagramPacket(data, data.length);
            while (!isCancelled()) {
                Socket client;
                try {
                    client = mHttpSocket.accept();
                } catch (IOException e) {
                    continue;
                }
                // TODO parse HTTP request
                try {
                    OutputStream out = client.getOutputStream();
                    out.write(HTTP_HEADER.getBytes());
                    out.flush();
                    while (!isCancelled()) {
                        try {
                            mUdpSocket.receive(dataPacket);
                            out.write(dataPacket.getData(), dataPacket.getOffset(),
                                    dataPacket.getLength());
                        } catch (InterruptedIOException e) {
                            // nop
                        } catch (SocketException e) {
                            break;
                        }
                    }
                } catch (IOException e) {
                    Log.e(TAG, "STREAM", e);
                }
            }
            Log.d(TAG, "<<< AsyncStreamTask");
            return null;
        }
    }

    class AsyncDvblastCtlTask extends AsyncTask<Void, FrontendStatus, Void> {

        final String TAG = StreamService.TAG + "." + AsyncDvblastCtlTask.class.getSimpleName();

        @Override
        protected void onProgressUpdate(FrontendStatus... values) {
            FrontendStatus status = values[0];
            Log.d(TAG, "fe_status: " + status);
        }

        @Override
        protected Void doInBackground(Void... params) {
            Log.d(TAG, ">>> AsyncDvblastCtlTask");
            while (!isCancelled()) {
                try {
                    Thread.sleep(DVBLAST_CHECKDELAY);
                } catch (InterruptedException e) {
                    continue;
                }
                try {
                    Process dvblastctl = ProcessUtils.runBinary(StreamService.this, DVBLASTCTL,
                            "-r", DVBLAST_SOCKET, "-x", "xml", "fe_status");
                    int exitCode = dvblastctl.waitFor();
                    if (exitCode != 0) {
                        Log.w(TAG, "exited with " + exitCode);
                        continue;
                    }
                    Document dom = getDomElement(dvblastctl.getInputStream());
                    FrontendStatus status = new FrontendStatus();
                    NodeList statusList = dom.getElementsByTagName("STATUS");
                    for (int i = 0; i < statusList.getLength(); i++) {
                        Node node = statusList.item(i);
                        String statusName = node.getAttributes().getNamedItem("status")
                                .getNodeValue();
                        if ("HAS_SIGNAL".equals(statusName)) {
                            status.status |= FrontendStatus.HAS_SIGNAL;
                        } else if ("HAS_CARRIER".equals(statusName)) {
                            status.status |= FrontendStatus.HAS_CARRIER;
                        } else if ("HAS_VITERBI".equals(statusName)) {
                            status.status |= FrontendStatus.HAS_VITERBI;
                        } else if ("HAS_SYNC".equals(statusName)) {
                            status.status |= FrontendStatus.HAS_SYNC;
                        } else if ("HAS_LOCK".equals(statusName)) {
                            status.status |= FrontendStatus.HAS_LOCK;
                        } else if ("REINIT".equals(statusName)) {
                            status.status |= FrontendStatus.REINIT;
                        }
                    }
                    NodeList valueList = dom.getElementsByTagName("VALUE");
                    for (int i = 0; i < valueList.getLength(); i++) {
                        Node node = valueList.item(i);
                        Node valueNode = node.getAttributes().item(0);
                        String valueName = valueNode.getNodeName();
                        int value = Integer.parseInt(valueNode.getNodeValue());
                        if ("bit_error_rate".equalsIgnoreCase(valueName)) {
                            status.ber = value;
                        } else if ("signal_strength".equalsIgnoreCase(valueName)) {
                            status.signal = value;
                        } else if ("snr".equalsIgnoreCase(valueName)) {
                            status.snr = value;
                        }
                    }
                    publishProgress(status);
                } catch (Throwable t) {
                    Log.w(TAG, "dvblastctl", t);
                }
            }
            Log.d(TAG, "<<< AsyncDvblastCtlTask");
            return null;
        }

        private Document getDomElement(InputStream xmlStream) {
            Document doc = null;
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            try {
                DocumentBuilder db = dbf.newDocumentBuilder();
                InputSource is = new InputSource();
                is.setByteStream(xmlStream);
                doc = db.parse(is);
            } catch (ParserConfigurationException e) {
                Log.e(TAG, e.getMessage());
                return null;
            } catch (SAXException e) {
                Log.e("Error: ", e.getMessage());
                return null;
            } catch (IOException e) {
                Log.e("Error: ", e.getMessage());
                return null;
            }
            return doc;
        }

    }

    class AsyncDvblastTask extends AsyncTask<Void, CharSequence, Void> {

        final String TAG = StreamService.TAG + "." + AsyncDvblastTask.class.getSimpleName();

        private File mConfigFile;
        private int mFreq;

        public AsyncDvblastTask(File configFile, int freq) {
            mConfigFile = configFile;
            mFreq = freq;
        }

        @Override
        protected void onCancelled() {
            sendUpdates(null);
        }

        @Override
        protected void onProgressUpdate(CharSequence... values) {
            if (isCancelled())
                return;
            sendUpdates(values);
        }

        @Override
        protected void onPostExecute(Void result) {
            sendUpdates(null);
        }

        @Override
        protected Void doInBackground(Void... params) {
            Log.d(TAG, ">>> AsyncDvblastTask");
            try {
                Process dvblast = ProcessUtils.runBinary(StreamService.this, DVBLAST,
                        "-U", "-O", "5000", "-r", DVBLAST_SOCKET,
                        "-x", "xml", "-c", mConfigFile.getAbsolutePath(),
                        "-f", "" + mFreq, "-q");
                Reader input = new InputStreamReader(dvblast.getInputStream());
                BufferedReader reader = new BufferedReader(input);
                Integer exitCode = null;
                String line;
                while (!isCancelled() &&
                        (exitCode = ProcessUtils.checkExitCode(dvblast)) == null) {
                    try {
                        if (reader.ready()) {
                            line = reader.readLine();
                            if (line == null) {
                                Thread.sleep(250);
                            } else {
                                Log.d(TAG, "#" + line);
                                publishProgress(line);
                            }
                        } else {
                            Thread.sleep(250);
                        }
                    } catch (Throwable t) {
                        break;
                    }
                }
                if (exitCode == null) {
                    Log.d(TAG, "dvblast destroying");
                    ProcessUtils.terminate(dvblast);
                    Log.d(TAG, "dvblast destroyed");
                } else if (exitCode != 0) {
                    // FIXME localization
                    Log.e(TAG, "dvblast failed (" + exitCode + ")");
                    Log.d(TAG, ProcessUtils.readStdOut(dvblast));
                    Log.d(TAG, ProcessUtils.readErrOut(dvblast));
                    sendError("dvblast failed (" + exitCode + ")");
                }
            } catch (Throwable t) {
                Log.e(TAG, "dvblast", t);
                sendError("dvblast", t);
            }
            Log.d(TAG, "<<< AsyncDvblastTask");
            return null;
        }
    }

    private void sendError(String message) {
        Intent intent = new Intent(ChannelsActivity.ACTION_ERROR);
        intent.putExtra(ChannelsActivity.EXTRA_ERRORMSG, message);
        sendBroadcast(intent);
    }

    private void sendError(String message, Throwable t) {
        Intent intent = new Intent(ChannelsActivity.ACTION_ERROR);
        intent.putExtra(ChannelsActivity.EXTRA_ERRORMSG, message);
        intent.putExtra(ChannelsActivity.EXTRA_ERROR, t);
        sendBroadcast(intent);
    }

    private void sendError(Throwable t) {
        Intent intent = new Intent(ChannelsActivity.ACTION_ERROR);
        intent.putExtra(ChannelsActivity.EXTRA_ERROR, t);
        sendBroadcast(intent);
    }

    private void sendUpdates(CharSequence[] values) {
        Intent intent = new Intent(ChannelsActivity.ACTION_UPDATE);
        intent.putExtra(ChannelsActivity.EXTRA_UPDATES, values);
        sendBroadcast(intent);
    }

    private static int tryParseInt(String str, String paramName)
            throws IOException {
        try {
            return Integer.parseInt(str);
        } catch (NumberFormatException e) {
            throw new IOException(
                    "error while parsing " + paramName + " (" + str + ")");
        }
    }
}




Java Source Code List

com.chrulri.droidtv.ChannelsActivity.java
com.chrulri.droidtv.PreferencesActivity.java
com.chrulri.droidtv.ScanActivity.java
com.chrulri.droidtv.StreamService.java
com.chrulri.droidtv.Utils.java