/* * Copyright (C) 2014 Fastboot Mobile, LLC. * * 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 com.fastbootmobile.encore.service; import android.content.Context; import android.util.Log; import com.fastbootmobile.encore.cast.WSStreamer; import org.java_websocket.WebSocketImpl; import org.java_websocket.server.DefaultSSLWebSocketServerFactory; import java.io.IOException; import java.io.InputStream; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManagerFactory; /** * Native Hub for audio sockets */ public class NativeHub { private static final String TAG = "NativeHub"; static { System.loadLibrary("c++_shared"); System.loadLibrary("protobuf"); System.loadLibrary("nativeplayerjni"); } private WSStreamer mStreamer; private WSStreamer mInsecureStreamer; private OnSampleWrittenListener mWrittenListener; // Used in native code private long mHandle; private byte[] mAudioMirrorBuffer; /** * Default constructor */ public NativeHub(Context context) { Log.i(TAG, "Initializing native hub"); WebSocketImpl.DEBUG = false; // Create the audio mirror buffer to stream audio to WebSocket, and the WS itself mStreamer = new WSStreamer(8887); try { String STORETYPE = "BKS"; // Yeah, that's plain text... We just need an SSL cert, // we don't really care if people can decrypt the audio // stream, that's not the expected behavior here :) String KEYSTORE = "key.bks"; String STOREPASSWORD = "encore"; String KEYPASSWORD = "encore"; KeyStore ks = KeyStore.getInstance(STORETYPE); InputStream is = context.getResources().getAssets().open(KEYSTORE); ks.load(is, STOREPASSWORD.toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(ks, KEYPASSWORD.toCharArray()); TrustManagerFactory tmf = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); tmf.init(ks); SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); mStreamer.setWebSocketFactory(new DefaultSSLWebSocketServerFactory(sslContext)); Log.d(TAG, "Web Socket Certificate Loaded"); } catch (NoSuchAlgorithmException e) { Log.e(TAG, "Unable to initialize WebSocket TLS security layer", e); } catch (KeyManagementException | KeyStoreException | IOException e) { Log.e(TAG, "Security error", e); } catch (CertificateException e) { Log.e(TAG, "Invalid SSL certificate", e); } catch (UnrecoverableKeyException e) { Log.e(TAG, "Key couldn't be read from keystore", e); } mInsecureStreamer = new WSStreamer(8886); nativeInitialize(); } /** * Called when the playback service starts */ public void onStart() { if (mAudioMirrorBuffer == null) { mAudioMirrorBuffer = new byte[262144]; } if (mStreamer != null) { mStreamer.start(); } if (mInsecureStreamer != null) { mInsecureStreamer.start(); } } /** * Called when the playback service stops */ public void onStop() { mAudioMirrorBuffer = null; try { mStreamer.stop(); mInsecureStreamer.stop(); } catch (IOException e) { Log.e(TAG, "IOException while stopping WS Streamer", e); } catch (InterruptedException e) { Log.e(TAG, "InterruptedException while stopping WS Streamer", e); } mStreamer = null; mInsecureStreamer = null; nativeShutdown(); } /** * Sets the currently active DSP chain to use to route the audio signal * @param chain The chain to set */ public void setDSPChain(String[] chain) { nativeSetDSPChain(chain); } /** * Creates an host socket with the provided name for a provider or DSP * @param name The name of the socket to create * @param isDsp Set this to true if the socket will be used by a DSP plugin * @return true if the socket has been successfully created, false otherwise */ public boolean createHostSocket(String name, boolean isDsp) { return nativeCreateHostSocket(name, isDsp); } /** * Releases an host socket and closes all bound connections * @param name The name of the socket to release */ public void releaseHostSocket(String name) { nativeReleaseHostSocket(name); } /** * Sets the native pointer of the active audio sink * @param handle The pointer */ public void setSinkPointer(long handle) { nativeSetSinkPointer(handle); } /** * Reduce the volume because some other priority operation is happening * @param duck True to reduce volume, false to restore */ public void setDucking(boolean duck) { nativeSetDucking(duck); } /** * Sets the listener that will be called when samples are written to the sink * @param listener The listener to use */ public void setOnAudioWrittenListener(OnSampleWrittenListener listener) { mWrittenListener = listener; } // Called from native code public void onAudioMirrorWritten(int len, int sampleRate, int channels) { if (mAudioMirrorBuffer != null) { mStreamer.write(mAudioMirrorBuffer, len); mInsecureStreamer.write(mAudioMirrorBuffer, len); // We use audio mirroring writing for tracking track elapsed time if (mWrittenListener != null) { mWrittenListener.onSampleWritten(mAudioMirrorBuffer, len, sampleRate, channels); } } } // Native methods private native boolean nativeInitialize(); private native void nativeShutdown(); private native void nativeSetDSPChain(String[] chain); private native boolean nativeCreateHostSocket(String name, boolean isDsp); private native void nativeReleaseHostSocket(String name); private native void nativeSetSinkPointer(long handle); private native void nativeSetDucking(boolean duck); public interface OnSampleWrittenListener { void onSampleWritten(byte[] bytes, int len, int sampleRate, int channels); } }