/** * Stops and closes all components related to Bluetooth audio. */ public void stop() { ThreadUtils.checkIsOnMainThread(); Log.d(TAG, "stop: BT state=" + bluetoothState); if (bluetoothAdapter == null) { return; } // Stop BT SCO connection with remote device if needed. stopScoAudio(); // Close down remaining BT resources. if (bluetoothState == State.UNINITIALIZED) { return; } unregisterReceiver(bluetoothHeadsetReceiver); cancelTimer(); if (bluetoothHeadset != null) { bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset); bluetoothHeadset = null; } bluetoothAdapter = null; bluetoothDevice = null; bluetoothState = State.UNINITIALIZED; Log.d(TAG, "stop done: BT state=" + bluetoothState); }
/** * Changes default audio device. * TODO(henrika): add usage of this method in the AppRTCMobile client. */ public void setDefaultAudioDevice(AudioDevice defaultDevice) { ThreadUtils.checkIsOnMainThread(); switch (defaultDevice) { case SPEAKER_PHONE: defaultAudioDevice = defaultDevice; break; case EARPIECE: if (hasEarpiece()) { defaultAudioDevice = defaultDevice; } else { defaultAudioDevice = AudioDevice.SPEAKER_PHONE; } break; default: Log.e(TAG, "Invalid default audio device selection"); break; } Log.d(TAG, "setDefaultAudioDevice(device=" + defaultAudioDevice + ")"); updateAudioDeviceState(); }
/** * Initializes the TCPChannelClient. If IP is a local IP address, starts a listening server on * that IP. If not, instead connects to the IP. * * @param eventListener Listener that will receive events from the client. * @param ip IP address to listen on or connect to. * @param port Port to listen on or connect to. */ public TCPChannelClient( ExecutorService executor, TCPChannelEvents eventListener, String ip, int port) { this.executor = executor; executorThreadCheck = new ThreadUtils.ThreadChecker(); executorThreadCheck.detachThread(); this.eventListener = eventListener; InetAddress address; try { address = InetAddress.getByName(ip); } catch (UnknownHostException e) { reportError("Invalid IP address."); return; } if (address.isAnyLocalAddress()) { socket = new TCPSocketServer(address, port); } else { socket = new TCPSocketClient(address, port); } socket.start(); }
/** Stops and closes all components related to Bluetooth audio. */ public void stop() { ThreadUtils.checkIsOnMainThread(); Log.d(TAG, "stop: BT state=" + bluetoothState); if (bluetoothAdapter == null) { return; } // Stop BT SCO connection with remote device if needed. stopScoAudio(); // Close down remaining BT resources. if (bluetoothState == State.UNINITIALIZED) { return; } unregisterReceiver(bluetoothHeadsetReceiver); cancelTimer(); if (bluetoothHeadset != null) { bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset); bluetoothHeadset = null; } bluetoothAdapter = null; bluetoothDevice = null; bluetoothState = State.UNINITIALIZED; Log.d(TAG, "stop done: BT state=" + bluetoothState); }
private boolean stopPlayout() { Logging.d(TAG, "stopPlayout"); assertTrue(audioThread != null); logUnderrunCount(); audioThread.stopThread(); final Thread aThread = audioThread; audioThread = null; if (aThread != null) { Logging.d(TAG, "Stopping the AudioTrackThread..."); aThread.interrupt(); if (!ThreadUtils.joinUninterruptibly(aThread, AUDIO_TRACK_THREAD_JOIN_TIMEOUT_MS)) { Logging.e(TAG, "Join of AudioTrackThread timed out."); } Logging.d(TAG, "AudioTrackThread has now been stopped."); } releaseAudioResources(); return true; }
/** * Stops and closes all components related to Bluetooth audio. */ public void stop() { ThreadUtils.checkIsOnMainThread(); unregisterReceiver(bluetoothHeadsetReceiver); Log.d(TAG, "stop: BT state=" + bluetoothState); if (bluetoothAdapter != null) { // Stop BT SCO connection with remote device if needed. stopScoAudio(); // Close down remaining BT resources. if (bluetoothState != State.UNINITIALIZED) { cancelTimer(); if (bluetoothHeadset != null) { bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset); bluetoothHeadset = null; } bluetoothAdapter = null; bluetoothDevice = null; bluetoothState = State.UNINITIALIZED; } } Log.d(TAG, "stop done: BT state=" + bluetoothState); }
/** * Starts Bluetooth SCO connection with remote device. * Note that the phone application always has the priority on the usage of the SCO connection * for telephony. If this method is called while the phone is in call it will be ignored. * Similarly, if a call is received or sent while an application is using the SCO connection, * the connection will be lost for the application and NOT returned automatically when the call * ends. Also note that: up to and including API version JELLY_BEAN_MR1, this method initiates a * virtual voice call to the Bluetooth headset. After API version JELLY_BEAN_MR2 only a raw SCO * audio connection is established. * TODO(henrika): should we add support for virtual voice call to BT headset also for JBMR2 and * higher. It might be required to initiates a virtual voice call since many devices do not * accept SCO audio without a "call". */ public boolean startScoAudio() { ThreadUtils.checkIsOnMainThread(); Log.d(TAG, "startSco: BT state=" + bluetoothState + ", " + "attempts: " + scoConnectionAttempts + ", " + "SCO is on: " + isScoOn()); if (scoConnectionAttempts >= MAX_SCO_CONNECTION_ATTEMPTS) { Log.e(TAG, "BT SCO connection fails - no more attempts"); return false; } if (bluetoothState != State.HEADSET_AVAILABLE) { Log.e(TAG, "BT SCO connection fails - no headset available"); return false; } // Start BT SCO channel and wait for ACTION_AUDIO_STATE_CHANGED. Log.d(TAG, "Starting Bluetooth SCO and waits for ACTION_AUDIO_STATE_CHANGED..."); // The SCO connection establishment can take several seconds, hence we cannot rely on the // connection to be available when the method returns but instead register to receive the // intent ACTION_SCO_AUDIO_STATE_UPDATED and wait for the state to be SCO_AUDIO_STATE_CONNECTED. bluetoothState = State.SCO_CONNECTING; audioManager.startBluetoothSco(); scoConnectionAttempts++; startTimer(); Log.d(TAG, "startScoAudio done: BT state=" + bluetoothState); return true; }
private BluetoothManager(Context context, RTCAudioManager audioManager) { Log.d(TAG, "ctor"); ThreadUtils.checkIsOnMainThread(); apprtcContext = context; apprtcAudioManager = audioManager; this.audioManager = getAudioManager(context); bluetoothState = State.UNINITIALIZED; bluetoothServiceListener = new BluetoothServiceListener(); bluetoothHeadsetReceiver = new BluetoothHeadsetBroadcastReceiver(); handler = new Handler(Looper.getMainLooper()); }
/** * Starts Bluetooth SCO connection with remote device. * Note that the phone application always has the priority on the usage of the SCO connection * for telephony. If this method is called while the phone is in call it will be ignored. * Similarly, if a call is received or sent while an application is using the SCO connection, * the connection will be lost for the application and NOT returned automatically when the call * ends. Also note that: up to and including API version JELLY_BEAN_MR1, this method initiates a * virtual voice call to the Bluetooth headset. After API version JELLY_BEAN_MR2 only a raw SCO * audio connection is established. * TODO(henrika): should we add support for virtual voice call to BT headset also for JBMR2 and * higher. It might be required to initiates a virtual voice call since many devices do not * accept SCO audio without a "call". */ public boolean startScoAudio() { ThreadUtils.checkIsOnMainThread(); Log.d(TAG, "startSco: BT state=" + bluetoothState + ", " + "attempts: " + scoConnectionAttempts + ", " + "SCO is on: " + isScoOn()); if (scoConnectionAttempts >= MAX_SCO_CONNECTION_ATTEMPTS) { Log.e(TAG, "BT SCO connection fails - no more attempts"); return false; } if (bluetoothState != State.HEADSET_AVAILABLE) { Log.e(TAG, "BT SCO connection fails - no headset available"); return false; } // Start BT SCO channel and wait for ACTION_AUDIO_STATE_CHANGED. Log.d(TAG, "Starting Bluetooth SCO and waits for ACTION_AUDIO_STATE_CHANGED..."); // The SCO connection establishment can take several seconds, hence we cannot rely on the // connection to be available when the method returns but instead register to receive the // intent ACTION_SCO_AUDIO_STATE_UPDATED and wait for the state to be SCO_AUDIO_STATE_CONNECTED. bluetoothState = State.SCO_CONNECTING; audioManager.startBluetoothSco(); audioManager.setBluetoothScoOn(true); scoConnectionAttempts++; startTimer(); Log.d(TAG, "startScoAudio done: BT state=" + bluetoothState + ", " + "SCO is on: " + isScoOn()); return true; }
/** * Stops Bluetooth SCO connection with remote device. */ public void stopScoAudio() { ThreadUtils.checkIsOnMainThread(); Log.d(TAG, "stopScoAudio: BT state=" + bluetoothState + ", " + "SCO is on: " + isScoOn()); if (bluetoothState != State.SCO_CONNECTING && bluetoothState != State.SCO_CONNECTED) { return; } cancelTimer(); audioManager.stopBluetoothSco(); audioManager.setBluetoothScoOn(false); bluetoothState = State.SCO_DISCONNECTING; Log.d(TAG, "stopScoAudio done: BT state=" + bluetoothState + ", " + "SCO is on: " + isScoOn()); }
/** * Called when start of the BT SCO channel takes too long time. Usually * happens when the BT device has been turned on during an ongoing call. */ private void bluetoothTimeout() { ThreadUtils.checkIsOnMainThread(); if (bluetoothState == State.UNINITIALIZED || bluetoothHeadset == null) { return; } Log.d(TAG, "bluetoothTimeout: BT state=" + bluetoothState + ", " + "attempts: " + scoConnectionAttempts + ", " + "SCO is on: " + isScoOn()); if (bluetoothState != State.SCO_CONNECTING) { return; } // Bluetooth SCO should be connecting; check the latest result. boolean scoConnected = false; List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices(); if (devices.size() > 0) { bluetoothDevice = devices.get(0); if (bluetoothHeadset.isAudioConnected(bluetoothDevice)) { Log.d(TAG, "SCO connected with " + bluetoothDevice.getName()); scoConnected = true; } else { Log.d(TAG, "SCO is not connected with " + bluetoothDevice.getName()); } } if (scoConnected) { // We thought BT had timed out, but it's actually on; updating state. bluetoothState = State.SCO_CONNECTED; scoConnectionAttempts = 0; } else { // Give up and "cancel" our request by calling stopBluetoothSco(). Log.w(TAG, "BT failed to connect after timeout"); stopScoAudio(); } updateAudioDeviceState(); Log.d(TAG, "bluetoothTimeout done: BT state=" + bluetoothState); }
private RTCAudioManager(Context context) { Log.d(TAG, "ctor"); ThreadUtils.checkIsOnMainThread(); apprtcContext = context; audioManager = ((android.media.AudioManager) context.getSystemService(Context.AUDIO_SERVICE)); bluetoothManager = BluetoothManager.create(context, this); wiredHeadsetReceiver = new WiredHeadsetReceiver(); amState = AudioManagerState.UNINITIALIZED; SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); useSpeakerphone = sharedPreferences.getString("speakerphone_preference", "auto"); Log.d(TAG, "useSpeakerphone: " + useSpeakerphone); if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) { defaultAudioDevice = AudioDevice.EARPIECE; } else { defaultAudioDevice = AudioDevice.SPEAKER_PHONE; } // Create and initialize the proximity sensor. // Tablet devices (e.g. Nexus 7) does not support proximity sensors. // Note that, the sensor will not be active until start() has been called. proximitySensor = ProximitySensor.create(context, new Runnable() { // This method will be called each time a state change is detected. // Example: user holds his hand over the device (closer than ~5 cm), // or removes his hand from the device. public void run() { onProximitySensorChangedState(); } }); Log.d(TAG, "defaultAudioDevice: " + defaultAudioDevice); Utils.logDeviceInfo(TAG); }
public void stop() { Log.d(TAG, "stop"); ThreadUtils.checkIsOnMainThread(); if (amState != AudioManagerState.RUNNING) { Log.e(TAG, "Trying to stop AudioManager in incorrect state: " + amState); return; } amState = AudioManagerState.UNINITIALIZED; unregisterReceiver(wiredHeadsetReceiver); bluetoothManager.stop(); // Restore previously stored audio states. setSpeakerphoneOn(savedIsSpeakerPhoneOn); setMicrophoneMute(savedIsMicrophoneMute); audioManager.setMode(savedAudioMode); // Abandon audio focus. Gives the previous focus owner, if any, focus. audioManager.abandonAudioFocus(audioFocusChangeListener); audioFocusChangeListener = null; Log.d(TAG, "Abandoned audio focus for VOICE_CALL streams"); if (proximitySensor != null) { proximitySensor.stop(); proximitySensor = null; } audioManagerEvents = null; Log.d(TAG, "AudioManager stopped"); }
/** * Changes selection of the currently active audio device. */ public void selectAudioDevice(AudioDevice device) { ThreadUtils.checkIsOnMainThread(); if (!audioDevices.contains(device)) { Log.e(TAG, "Can not select " + device + " from available " + audioDevices); } userSelectedAudioDevice = device; updateAudioDeviceState(); }
protected AppRTCBluetoothManager(Context context, AppRTCAudioManager audioManager) { Log.d(TAG, "ctor"); ThreadUtils.checkIsOnMainThread(); apprtcContext = context; apprtcAudioManager = audioManager; this.audioManager = getAudioManager(context); bluetoothState = State.UNINITIALIZED; bluetoothServiceListener = new BluetoothServiceListener(); bluetoothHeadsetReceiver = new BluetoothHeadsetBroadcastReceiver(); handler = new Handler(Looper.getMainLooper()); }
/** Stops Bluetooth SCO connection with remote device. */ public void stopScoAudio() { ThreadUtils.checkIsOnMainThread(); Log.d(TAG, "stopScoAudio: BT state=" + bluetoothState + ", " + "SCO is on: " + isScoOn()); if (bluetoothState != State.SCO_CONNECTING && bluetoothState != State.SCO_CONNECTED) { return; } cancelTimer(); audioManager.stopBluetoothSco(); audioManager.setBluetoothScoOn(false); bluetoothState = State.SCO_DISCONNECTING; Log.d(TAG, "stopScoAudio done: BT state=" + bluetoothState + ", " + "SCO is on: " + isScoOn()); }
private AppRTCAudioManager(Context context) { Log.d(TAG, "ctor"); ThreadUtils.checkIsOnMainThread(); apprtcContext = context; audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE)); bluetoothManager = AppRTCBluetoothManager.create(context, this); wiredHeadsetReceiver = new WiredHeadsetReceiver(); amState = AudioManagerState.UNINITIALIZED; SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); useSpeakerphone = sharedPreferences.getString(context.getString(R.string.pref_speakerphone_key), context.getString(R.string.pref_speakerphone_default)); Log.d(TAG, "useSpeakerphone: " + useSpeakerphone); if (useSpeakerphone.equals(SPEAKERPHONE_FALSE)) { defaultAudioDevice = AudioDevice.EARPIECE; } else { defaultAudioDevice = AudioDevice.SPEAKER_PHONE; } // Create and initialize the proximity sensor. // Tablet devices (e.g. Nexus 7) does not support proximity sensors. // Note that, the sensor will not be active until start() has been called. proximitySensor = AppRTCProximitySensor.create(context, new Runnable() { // This method will be called each time a state change is detected. // Example: user holds his hand over the device (closer than ~5 cm), // or removes his hand from the device. public void run() { onProximitySensorChangedState(); } }); Log.d(TAG, "defaultAudioDevice: " + defaultAudioDevice); AppRTCUtils.logDeviceInfo(TAG); }
/** Changes selection of the currently active audio device. */ public void selectAudioDevice(AudioDevice device) { ThreadUtils.checkIsOnMainThread(); if (!audioDevices.contains(device)) { Log.e(TAG, "Can not select " + device + " from available " + audioDevices); } userSelectedAudioDevice = device; updateAudioDeviceState(); }
private boolean stopRecording() { threadChecker.checkIsOnValidThread(); Logging.d(TAG, "stopRecording"); assertTrue(audioThread != null); audioThread.stopThread(); if (!ThreadUtils.joinUninterruptibly(audioThread, AUDIO_RECORD_THREAD_JOIN_TIMEOUT_MS)) { Logging.e(TAG, "Join of AudioRecordJavaThread timed out"); } audioThread = null; if (effects != null) { effects.release(); } releaseAudioResources(); return true; }
/** * Stops Bluetooth SCO connection with remote device. */ public void stopScoAudio() { ThreadUtils.checkIsOnMainThread(); Log.d(TAG, "stopScoAudio: BT state=" + bluetoothState + ", " + "SCO is on: " + isScoOn()); if (bluetoothState != State.SCO_CONNECTING && bluetoothState != State.SCO_CONNECTED) { return; } cancelTimer(); audioManager.stopBluetoothSco(); bluetoothState = State.SCO_DISCONNECTING; Log.d(TAG, "stopScoAudio done: BT state=" + bluetoothState); }
private boolean stopRecording() { Logging.d(TAG, "stopRecording"); assertTrue(audioThread != null); audioThread.stopThread(); if (!ThreadUtils.joinUninterruptibly(audioThread, AUDIO_RECORD_THREAD_JOIN_TIMEOUT_MS)) { Logging.e(TAG, "Join of AudioRecordJavaThread timed out"); } audioThread = null; if (effects != null) { effects.release(); } releaseAudioResources(); return true; }
/** * Initialize this class, sharing resources with |sharedContext|. The custom |drawer| will be used * for drawing frames on the EGLSurface. This class is responsible for calling release() on * |drawer|. It is allowed to call init() to reinitialize the renderer after a previous * init()/release() cycle. */ public void init(final EglBase.Context sharedContext, RendererCommon.RendererEvents rendererEvents, final int[] configAttributes, RendererCommon.GlDrawer drawer) { ThreadUtils.checkIsOnMainThread(); this.rendererEvents = rendererEvents; synchronized (layoutLock) { isFirstFrameRendered = false; rotatedFrameWidth = 0; rotatedFrameHeight = 0; frameRotation = 0; } eglRenderer.init(sharedContext, configAttributes, drawer); }
@Override protected void onMeasure(int widthSpec, int heightSpec) { ThreadUtils.checkIsOnMainThread(); final Point size; synchronized (layoutLock) { size = videoLayoutMeasure.measure(widthSpec, heightSpec, rotatedFrameWidth, rotatedFrameHeight); } setMeasuredDimension(size.x, size.y); logD("onMeasure(). New size: " + size.x + "x" + size.y); }
@Override public void onSurfaceTextureAvailable(final SurfaceTexture surface, final int width, final int height) { logD("onSurfaceTextureAvailable: " + surface + " size: " + width + "x" + height); ThreadUtils.checkIsOnMainThread(); eglRenderer.createEglSurface(surface); }
@Override public boolean onSurfaceTextureDestroyed(final SurfaceTexture surface) { logD("onSurfaceTextureDestroyed: " + surface); ThreadUtils.checkIsOnMainThread(); final CountDownLatch completionLatch = new CountDownLatch(1); eglRenderer.releaseEglSurface(new Runnable() { @Override public void run() { completionLatch.countDown(); } }); ThreadUtils.awaitUninterruptibly(completionLatch); return true; }
/** * Block until any pending frame is returned and all GL resources released, even if an interrupt * occurs. If an interrupt occurs during release(), the interrupt flag will be set. This function * should be called before the Activity is destroyed and the EGLContext is still valid. If you * don't call this function, the GL resources might leak. */ public void release() { final CountDownLatch eglCleanupBarrier = new CountDownLatch(1); synchronized (handlerLock) { if (renderThreadHandler == null) { Logging.d(TAG, getResourceName() + "Already released"); return; } // Release EGL and GL resources on render thread. // TODO(magjed): This might not be necessary - all OpenGL resources are automatically deleted // when the EGL context is lost. It might be dangerous to delete them manually in // Activity.onDestroy(). renderThreadHandler.postAtFrontOfQueue(new Runnable() { @Override public void run() { drawer.release(); drawer = null; if (yuvTextures != null) { GLES20.glDeleteTextures(3, yuvTextures, 0); yuvTextures = null; } // Clear last rendered image to black. makeBlack(); eglBase.release(); eglBase = null; eglCleanupBarrier.countDown(); } }); // Don't accept any more frames or messages to the render thread. renderThreadHandler = null; } // Make sure the EGL/GL cleanup posted above is executed. ThreadUtils.awaitUninterruptibly(eglCleanupBarrier); renderThread.quit(); synchronized (frameLock) { if (pendingFrame != null) { VideoRenderer.renderFrameDone(pendingFrame); pendingFrame = null; } } // The |renderThread| cleanup is not safe to cancel and we need to wait until it's done. ThreadUtils.joinUninterruptibly(renderThread); renderThread = null; // Reset statistics and event reporting. synchronized (layoutLock) { frameWidth = 0; frameHeight = 0; frameRotation = 0; rendererEvents = null; } resetStatistics(); }
/** * Returns the internal state. */ public State getState() { ThreadUtils.checkIsOnMainThread(); return bluetoothState; }
/** * Activates components required to detect Bluetooth devices and to enable * BT SCO (audio is routed via BT SCO) for the headset profile. The end * state will be HEADSET_UNAVAILABLE but a state machine has started which * will start a state change sequence where the final outcome depends on * if/when the BT headset is enabled. * Example of state change sequence when start() is called while BT device * is connected and enabled: * UNINITIALIZED --> HEADSET_UNAVAILABLE --> HEADSET_AVAILABLE --> * SCO_CONNECTING --> SCO_CONNECTED <==> audio is now routed via BT SCO. * Note that the AppRTCAudioManager is also involved in driving this state * change. */ public void start() { ThreadUtils.checkIsOnMainThread(); Log.d(TAG, "start"); if (!hasPermission(apprtcContext, android.Manifest.permission.BLUETOOTH)) { Log.w(TAG, "Process (pid=" + Process.myPid() + ") lacks BLUETOOTH permission"); return; } if (bluetoothState != State.UNINITIALIZED) { Log.w(TAG, "Invalid BT state"); return; } bluetoothHeadset = null; bluetoothDevice = null; scoConnectionAttempts = 0; // Get a handle to the default local Bluetooth adapter. bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (bluetoothAdapter == null) { Log.w(TAG, "Device does not support Bluetooth"); return; } // Ensure that the device supports use of BT SCO audio for off call use cases. if (!audioManager.isBluetoothScoAvailableOffCall()) { Log.e(TAG, "Bluetooth SCO audio is not available off call"); return; } logBluetoothAdapterInfo(bluetoothAdapter); // Establish a connection to the HEADSET profile (includes both Bluetooth Headset and // Hands-Free) proxy object and install a listener. if (!getBluetoothProfileProxy( apprtcContext, bluetoothServiceListener, BluetoothProfile.HEADSET)) { Log.e(TAG, "BluetoothAdapter.getProfileProxy(HEADSET) failed"); return; } // Register receivers for BluetoothHeadset change notifications. IntentFilter bluetoothHeadsetFilter = new IntentFilter(); // Register receiver for change in connection state of the Headset profile. bluetoothHeadsetFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); // Register receiver for change in audio connection state of the Headset profile. bluetoothHeadsetFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED); registerReceiver(bluetoothHeadsetReceiver, bluetoothHeadsetFilter); Log.d(TAG, "HEADSET profile state: " + stateToString(bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET))); Log.d(TAG, "Bluetooth proxy for headset profile has started"); bluetoothState = State.HEADSET_UNAVAILABLE; Log.d(TAG, "start done: BT state=" + bluetoothState); }