blob: 83c50f2c745ffd5b68ba8f352cb1768886833260 [file] [log] [blame]
/*
* Copyright (C) 2004-2020 Savoir-faire Linux Inc.
*
* Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com>
* Author: Adrien BĂ©raud <adrien.beraud@savoirfairelinux.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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package cx.ring.services;
import android.bluetooth.BluetoothHeadset;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Point;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.os.Build;
import android.text.TextUtils;
import android.view.SurfaceHolder;
import android.view.TextureView;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.media.AudioAttributesCompat;
import androidx.media.AudioFocusRequestCompat;
import androidx.media.AudioManagerCompat;
import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import cx.ring.daemon.IntVect;
import cx.ring.daemon.Ringservice;
import cx.ring.daemon.StringMap;
import cx.ring.daemon.UintVect;
import cx.ring.model.Conference;
import cx.ring.model.SipCall;
import cx.ring.model.SipCall.CallStatus;
import cx.ring.utils.BluetoothWrapper;
import cx.ring.utils.Log;
import cx.ring.utils.Ringer;
import cx.ring.utils.Tuple;
import io.reactivex.Completable;
import io.reactivex.Observable;
import static cx.ring.daemon.Ringservice.getCallMediaHandlerStatus;
import static cx.ring.daemon.RingserviceJNI.toggleCallMediaHandler;
public class HardwareServiceImpl extends HardwareService implements AudioManager.OnAudioFocusChangeListener, BluetoothWrapper.BluetoothChangeListener {
private static final Point VIDEO_SIZE_LOW = new Point(320, 240);
private static final Point VIDEO_SIZE_DEFAULT = new Point(720, 480);
private static final Point VIDEO_SIZE_HD = new Point(1280, 720);
private static final Point VIDEO_SIZE_FULL_HD = new Point(1920, 1080);
private static final Point VIDEO_SIZE_ULTRA_HD = new Point(3840, 2160);
private static final String TAG = HardwareServiceImpl.class.getSimpleName();
private static WeakReference<TextureView> mCameraPreviewSurface = new WeakReference<>(null);
private static WeakReference<Conference> mCameraPreviewCall = new WeakReference<>(null);
private static final Map<String, WeakReference<SurfaceHolder>> videoSurfaces = Collections.synchronizedMap(new HashMap<>());
private final Map<String, Shm> videoInputs = new HashMap<>();
private final Context mContext;
private final CameraService cameraService;
private final Ringer mRinger;
private final AudioManager mAudioManager;
private BluetoothWrapper mBluetoothWrapper;
private AudioFocusRequestCompat currentFocus = null;
private String mCapturingId = null;
private boolean mIsCapturing = false;
private boolean mIsScreenSharing = false;
private boolean mShouldCapture = false;
private boolean mShouldSpeakerphone = false;
private final boolean mHasSpeakerPhone;
private boolean mIsChoosePlugin = false;
private String mMediaHandlerId = "";
private String mPluginCallId = "";
public HardwareServiceImpl(Context context) {
mContext = context;
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mHasSpeakerPhone = hasSpeakerphone();
mRinger = new Ringer(mContext);
cameraService = new CameraService(mContext);
}
public Completable initVideo() {
Log.i(TAG, "initVideo()");
return cameraService.init();
}
public Observable<Tuple<Integer, Integer>> getMaxResolutions() {
return cameraService.getMaxResolutions();
}
public boolean isVideoAvailable() {
return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) || cameraService.hasCamera();
}
public boolean hasMicrophone() {
PackageManager pm = mContext.getPackageManager();
boolean hasMicrophone = pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE);
if (!hasMicrophone) {
MediaRecorder recorder = new MediaRecorder();
File testFile = new File(mContext.getCacheDir(), "MediaUtil#micAvailTestFile");
try {
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
recorder.setOutputFile(testFile.getAbsolutePath());
recorder.prepare();
recorder.start();
hasMicrophone = true;
} catch (IllegalStateException e) {
// Microphone is already in use
hasMicrophone = true;
} catch (Exception exception) {
hasMicrophone = false;
} finally {
recorder.release();
testFile.delete();
}
}
return hasMicrophone;
}
@Override
public boolean isSpeakerPhoneOn() {
return mAudioManager.isSpeakerphoneOn();
}
private final AudioFocusRequestCompat RINGTONE_REQUEST = new AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT)
.setAudioAttributes(new AudioAttributesCompat.Builder()
.setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributesCompat.USAGE_NOTIFICATION_RINGTONE)
.setLegacyStreamType(AudioManager.STREAM_RING)
.build())
.setOnAudioFocusChangeListener(this)
.build();
private final AudioFocusRequestCompat CALL_REQUEST = new AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT)
.setAudioAttributes(new AudioAttributesCompat.Builder()
.setContentType(AudioAttributesCompat.CONTENT_TYPE_SPEECH)
.setUsage(AudioAttributesCompat.USAGE_VOICE_COMMUNICATION)
.setLegacyStreamType(AudioManager.STREAM_VOICE_CALL)
.build())
.setOnAudioFocusChangeListener(this)
.build();
private void getFocus(AudioFocusRequestCompat request) {
if (currentFocus == request)
return;
if (currentFocus != null) {
AudioManagerCompat.abandonAudioFocusRequest(mAudioManager, currentFocus);
currentFocus = null;
}
if (request != null && AudioManagerCompat.requestAudioFocus(mAudioManager, request) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
currentFocus = request;
}
}
@Override
synchronized public void updateAudioState(final SipCall.CallStatus state, final boolean incomingCall, final boolean isOngoingVideo) {
Log.d(TAG, "updateAudioState: Call state updated to " + state + " Call is incoming: " + incomingCall + " Call is video: " + isOngoingVideo);
boolean callEnded = state.equals(CallStatus.HUNGUP) || state.equals(CallStatus.FAILURE) || state.equals(CallStatus.OVER);
try {
if (mBluetoothWrapper == null && !callEnded) {
mBluetoothWrapper = new BluetoothWrapper(mContext, this);
}
switch (state) {
case RINGING:
if (incomingCall)
startRinging();
getFocus(RINGTONE_REQUEST);
if (incomingCall) {
// ringtone for incoming calls
mAudioManager.setMode(AudioManager.MODE_RINGTONE);
setAudioRouting(true);
mShouldSpeakerphone = isOngoingVideo;
} else
setAudioRouting(isOngoingVideo);
break;
case CURRENT:
stopRinging();
getFocus(CALL_REQUEST);
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
setAudioRouting(isOngoingVideo);
break;
case HOLD:
case UNHOLD:
case INACTIVE:
break;
default:
closeAudioState();
break;
}
} catch (Exception e) {
Log.e(TAG, "Error updating audio state", e);
}
}
/*
This is required in the case where a call is incoming. If you have an incoming call, and no bluetooth device is connected, the ringer should always be played through the speaker.
However, this results in the call starting in a state where the speaker is always on and the UI is in an incorrect state.
If it is a bluetooth device, it takes priority and does not play on speaker regardless. Otherwise, it returns mShouldSpeakerphone which was updated in updateaudiostate.
*/
@Override
public boolean shouldPlaySpeaker() {
if(mBluetoothWrapper != null && mBluetoothWrapper.canBluetooth() && mBluetoothWrapper.isBTHeadsetConnected() )
return false;
else
return mShouldSpeakerphone;
}
@Override
synchronized public void closeAudioState() {
stopRinging();
abandonAudioFocus();
}
@Override
public void startRinging() {
mRinger.ring();
}
@Override
public void stopRinging() {
mRinger.stopRing();
}
@Override
public void onAudioFocusChange(int arg0) {
Log.i(TAG, "onAudioFocusChange " + arg0);
}
@Override
synchronized public void abandonAudioFocus() {
if (currentFocus != null) {
AudioManagerCompat.abandonAudioFocusRequest(mAudioManager, currentFocus);
currentFocus = null;
}
if (mAudioManager.isSpeakerphoneOn()) {
mAudioManager.setSpeakerphoneOn(false);
}
mAudioManager.setMode(AudioManager.MODE_NORMAL);
if (mBluetoothWrapper != null) {
mBluetoothWrapper.unregister();
mBluetoothWrapper.setBluetoothOn(false);
mBluetoothWrapper = null;
}
}
private void setAudioRouting(boolean requestSpeakerOn) {
mShouldSpeakerphone = requestSpeakerOn;
// prioritize bluetooth by checking for bluetooth device first
if (mBluetoothWrapper != null && mBluetoothWrapper.canBluetooth() && mBluetoothWrapper.isBTHeadsetConnected()) {
routeToBTHeadset();
} else if (!mAudioManager.isWiredHeadsetOn() && mHasSpeakerPhone && mShouldSpeakerphone) {
routeToSpeaker();
} else {
resetAudio();
}
}
private boolean hasSpeakerphone() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Check FEATURE_AUDIO_OUTPUT to guard against false positives.
PackageManager packageManager = mContext.getPackageManager();
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
return false;
}
AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
for (AudioDeviceInfo device : devices) {
if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
return true;
}
}
return false;
}
return true;
}
/**
* Routes audio to a bluetooth headset.
*/
private void routeToBTHeadset() {
Log.d(TAG, "routeToBTHeadset: Try to enable bluetooth");
int oldMode = mAudioManager.getMode();
mAudioManager.setMode(AudioManager.MODE_NORMAL);
mAudioManager.setSpeakerphoneOn(false);
mBluetoothWrapper.setBluetoothOn(true);
mAudioManager.setMode(oldMode);
audioStateSubject.onNext(new AudioState(AudioOutput.BLUETOOTH, mBluetoothWrapper.getDeviceName()));
}
/**
* Routes audio to the device's speaker and takes into account whether the transition is coming from bluetooth.
*/
private void routeToSpeaker() {
// if we are returning from bluetooth mode, switch to mode normal, otherwise, we switch to mode in communication
if (mAudioManager.isBluetoothScoOn()) {
int oldMode = mAudioManager.getMode();
mAudioManager.setMode(AudioManager.MODE_NORMAL);
mBluetoothWrapper.setBluetoothOn(false);
mAudioManager.setMode(oldMode);
}
mAudioManager.setSpeakerphoneOn(true);
audioStateSubject.onNext(STATE_SPEAKERS);
}
/**
* Returns to earpiece audio
*/
private void resetAudio() {
if (mBluetoothWrapper != null)
mBluetoothWrapper.setBluetoothOn(false);
mAudioManager.setSpeakerphoneOn(false);
audioStateSubject.onNext(STATE_INTERNAL);
}
@Override
synchronized public void toggleSpeakerphone(boolean checked) {
Ringservice.setAudioPlugin(Ringservice.getCurrentAudioOutputPlugin());
mShouldSpeakerphone = checked;
Log.w(TAG, "toggleSpeakerphone setSpeakerphoneOn " + checked);
if (mHasSpeakerPhone && checked) {
routeToSpeaker();
} else if (mBluetoothWrapper != null && mBluetoothWrapper.canBluetooth() && mBluetoothWrapper.isBTHeadsetConnected()) {
routeToBTHeadset();
} else {
resetAudio();
}
}
@Override
synchronized public void onBluetoothStateChanged(int status) {
Log.d(TAG, "bluetoothStateChanged to: " + status);
BluetoothEvent event = new BluetoothEvent();
if (status == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
Log.d(TAG, "BluetoothHeadset Connected");
event.connected = true;
} else if (status == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
Log.d(TAG, "BluetoothHeadset Disconnected");
event.connected = false;
if (mShouldSpeakerphone)
routeToSpeaker();
}
bluetoothEvents.onNext(event);
}
public void decodingStarted(String id, String shmPath, int width, int height, boolean isMixer) {
Log.i(TAG, "decodingStarted() " + id + " " + width + "x" + height);
Shm shm = new Shm();
shm.id = id;
shm.w = width;
shm.h = height;
videoInputs.put(id, shm);
WeakReference<SurfaceHolder> weakSurfaceHolder = videoSurfaces.get(id);
if (weakSurfaceHolder != null) {
SurfaceHolder holder = weakSurfaceHolder.get();
if (holder != null) {
shm.window = startVideo(id, holder.getSurface(), width, height);
if (shm.window == 0) {
Log.i(TAG, "DRingService.decodingStarted() no window !");
VideoEvent event = new VideoEvent();
event.start = true;
event.callId = shm.id;
videoEvents.onNext(event);
return;
}
VideoEvent event = new VideoEvent();
event.callId = shm.id;
event.started = true;
event.w = shm.w;
event.h = shm.h;
videoEvents.onNext(event);
}
}
}
@Override
public void decodingStopped(String id, String shmPath, boolean isMixer) {
Log.i(TAG, "decodingStopped() " + id);
Shm shm = videoInputs.remove(id);
if (shm == null) {
return;
}
if (shm.window != 0) {
try {
stopVideo(shm.id, shm.window);
} catch (Exception e) {
Log.e(TAG, "decodingStopped error" + e);
}
shm.window = 0;
}
}
@Override
public void getCameraInfo(String camId, IntVect formats, UintVect sizes, UintVect rates) {
// Use a larger resolution for Android 6.0+, 64 bits devices
final boolean useLargerSize = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && (Build.SUPPORTED_64_BIT_ABIS.length > 0 || mPreferenceService.isHardwareAccelerationEnabled());
//int MIN_WIDTH = useLargerSize ? (useHD ? VIDEO_WIDTH_HD : VIDEO_WIDTH) : VIDEO_WIDTH_MIN;
Point minVideoSize;
if (useLargerSize)
minVideoSize = parseResolution(mPreferenceService.getResolution());
else
minVideoSize = VIDEO_SIZE_LOW;
cameraService.getCameraInfo(camId, formats, sizes, rates, minVideoSize);
}
private Point parseResolution(int resolution) {
switch(resolution) {
case 480:
return VIDEO_SIZE_DEFAULT;
case 720:
return VIDEO_SIZE_HD;
case 1080:
return VIDEO_SIZE_FULL_HD;
case 2160:
return VIDEO_SIZE_ULTRA_HD;
default:
return VIDEO_SIZE_HD;
}
}
@Override
public void setParameters(String camId, int format, int width, int height, int rate) {
Log.d(TAG, "setParameters: " + camId + ", " + format + ", " + width + ", " + height + ", " + rate);
WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
cameraService.setParameters(camId, format, width, height, rate, windowManager.getDefaultDisplay().getRotation());
}
public boolean startScreenShare(Object projection) {
MediaProjection mediaProjection = (MediaProjection) projection;
if (mIsCapturing) {
endCapture();
}
if (!mIsScreenSharing && mediaProjection != null) {
mIsScreenSharing = true;
mediaProjection.registerCallback(new MediaProjection.Callback(){
@Override
public void onStop() {
stopScreenShare();
}
}, cameraService.getVideoHandler());
if (!cameraService.startScreenSharing(mediaProjection, mContext.getResources().getDisplayMetrics())) {
mIsScreenSharing = false;
mediaProjection.stop();
return false;
}
return true;
} else {
return false;
}
}
public void stopScreenShare() {
if (mIsScreenSharing) {
cameraService.stopScreenSharing();
mIsScreenSharing = false;
if (mShouldCapture)
startCapture(mCapturingId);
}
}
public void startMediaHandler(String mediaHandlerId) {
mIsChoosePlugin = true;
mMediaHandlerId = mediaHandlerId;
}
private void toggleMediaHandler(String callId) {
toggleCallMediaHandler(mMediaHandlerId, callId, true);
}
public void stopMediaHandler() {
mIsChoosePlugin = false;
mMediaHandlerId = "";
}
@Override
public void startCapture(@Nullable String camId) {
if (mIsScreenSharing) {
cameraService.stopScreenSharing();
mIsScreenSharing = false;
}
mShouldCapture = true;
if (mIsCapturing && mCapturingId != null && mCapturingId.equals(camId)) {
return;
}
if (camId == null) {
camId = mCapturingId != null ? mCapturingId : cameraService.switchInput(true);
}
CameraService.VideoParams videoParams = cameraService.getParams(camId);
if (videoParams == null) {
Log.w(TAG, "startCapture: no video parameters ");
return;
}
final TextureView surface = mCameraPreviewSurface.get();
if (surface == null) {
Log.w(TAG, "Can't start capture: no surface registered.");
cameraService.setPreviewParams(videoParams);
VideoEvent event = new VideoEvent();
event.start = true;
videoEvents.onNext(event);
return;
}
final Conference conf = mCameraPreviewCall.get();
boolean useHardwareCodec = mPreferenceService.isHardwareAccelerationEnabled() && (conf == null || !conf.isConference()) && !mIsChoosePlugin;
if (conf != null && useHardwareCodec) {
SipCall call = conf.getCall();
if (call != null) {
call.setDetails(Ringservice.getCallDetails(call.getDaemonIdString()).toNative());
videoParams.codec = call.getVideoCodec();
} else {
videoParams.codec = null;
}
}
Log.w(TAG, "startCapture: call " + camId + " " + videoParams.codec + " useHardwareCodec:" + useHardwareCodec + " bitrate:" + mPreferenceService.getBitrate());
mIsCapturing = true;
mCapturingId = videoParams.id;
Log.d(TAG, "startCapture: startCapture " + videoParams.id + " " + videoParams.width + "x" + videoParams.height + " rot" + videoParams.rotation);
mUiScheduler.scheduleDirect(() -> cameraService.openCamera(videoParams, surface,
new CameraService.CameraListener() {
@Override
public void onOpened() {
String currentCall = conf != null ? conf.getConfId() : null;
if (!TextUtils.isEmpty(mPluginCallId) && !mPluginCallId.equals(currentCall)) {
toggleCallMediaHandler("", conf.getId(), false);
mIsChoosePlugin = false;
mMediaHandlerId = "";
mPluginCallId = "";
}
else if (mIsChoosePlugin && !mMediaHandlerId.isEmpty())
mPluginCallId = conf != null ? conf.getConfId() : null;
toggleMediaHandler(conf.getId());
}
@Override
public void onError() {
stopCapture();
}
},
useHardwareCodec,
mPreferenceService.getResolution(),
mPreferenceService.getBitrate()));
cameraService.setPreviewParams(videoParams);
VideoEvent event = new VideoEvent();
event.started = true;
event.w = videoParams.width;
event.h = videoParams.height;
event.rot = videoParams.rotation;
videoEvents.onNext(event);
}
@Override
public void stopCapture() {
Log.d(TAG, "stopCapture: " + cameraService.isOpen());
mShouldCapture = false;
endCapture();
if (mIsScreenSharing) {
cameraService.stopScreenSharing();
mIsScreenSharing = false;
}
}
public void requestKeyFrame() {
cameraService.requestKeyFrame();
}
public void setBitrate(String device, int bitrate) {
cameraService.setBitrate(bitrate);
}
public void endCapture() {
if (cameraService.isOpen()) {
//final CameraService.VideoParams params = previewParams;
cameraService.closeCamera();
VideoEvent event = new VideoEvent();
event.started = false;
//event.w = params.width;
//event.h = params.height;
videoEvents.onNext(event);
}
mIsCapturing = false;
}
@Override
public void addVideoSurface(String id, Object holder) {
if (!(holder instanceof SurfaceHolder)) {
return;
}
Log.w(TAG, "addVideoSurface " + id);
Shm shm = videoInputs.get(id);
WeakReference<SurfaceHolder> surfaceHolder = new WeakReference<>((SurfaceHolder) holder);
videoSurfaces.put(id, surfaceHolder);
if (shm != null && shm.window == 0) {
shm.window = startVideo(shm.id, surfaceHolder.get().getSurface(), shm.w, shm.h);
}
if (shm == null || shm.window == 0) {
Log.i(TAG, "DRingService.addVideoSurface() no window !");
VideoEvent event = new VideoEvent();
event.start = true;
videoEvents.onNext(event);
return;
}
VideoEvent event = new VideoEvent();
event.callId = shm.id;
event.started = true;
event.w = shm.w;
event.h = shm.h;
videoEvents.onNext(event);
}
@Override
public void updateVideoSurfaceId(String currentId, String newId)
{
Log.w(TAG, "updateVideoSurfaceId " + currentId + " " + newId);
WeakReference<SurfaceHolder> surfaceHolder = videoSurfaces.get(currentId);
if (surfaceHolder == null) {
return;
}
SurfaceHolder surface = surfaceHolder.get();
Shm shm = videoInputs.get(currentId);
if (shm != null && shm.window != 0) {
try {
stopVideo(shm.id, shm.window);
} catch (Exception e) {
Log.e(TAG, "removeVideoSurface error" + e);
}
shm.window = 0;
}
videoSurfaces.remove(currentId);
if (surface != null) {
addVideoSurface(newId, surface);
}
}
@Override
public void addPreviewVideoSurface(Object oholder, Conference conference) {
if (!(oholder instanceof TextureView)) {
return;
}
TextureView holder = (TextureView) oholder;
Log.w(TAG, "addPreviewVideoSurface " + holder.hashCode() + " mCapturingId " + mCapturingId);
if (mCameraPreviewSurface.get() == oholder)
return;
mCameraPreviewSurface = new WeakReference<>(holder);
mCameraPreviewCall = new WeakReference<>(conference);
if (mShouldCapture && !mIsCapturing) {
startCapture(mCapturingId);
}
}
@Override
public void updatePreviewVideoSurface(Conference conference) {
Conference old = mCameraPreviewCall.get();
mCameraPreviewCall = new WeakReference<>(conference);
if (old != conference && mIsCapturing) {
String id = mCapturingId;
stopCapture();
startCapture(id);
}
}
@Override
public void removeVideoSurface(String id) {
Log.i(TAG, "removeVideoSurface " + id);
videoSurfaces.remove(id);
Shm shm = videoInputs.get(id);
if (shm == null) {
return;
}
if (shm.window != 0) {
try {
stopVideo(shm.id, shm.window);
} catch (Exception e) {
Log.e(TAG, "removeVideoSurface error" + e);
}
shm.window = 0;
}
VideoEvent event = new VideoEvent();
event.callId = shm.id;
event.started = false;
videoEvents.onNext(event);
}
@Override
public void removePreviewVideoSurface() {
Log.w(TAG, "removePreviewVideoSurface");
mCameraPreviewSurface.clear();
}
@Override
public void switchInput(String id, boolean setDefaultCamera) {
Log.w(TAG, "switchInput " + id);
mCapturingId = cameraService.switchInput(setDefaultCamera);
switchInput(id, "camera://" + mCapturingId);
}
@Override
public void setPreviewSettings() {
setPreviewSettings(cameraService.getPreviewSettings());
}
@Override
public int getCameraCount() {
return cameraService.getCameraCount();
}
@Override
public boolean hasCamera() {
return cameraService.hasCamera();
}
@Override
public boolean isPreviewFromFrontCamera() {
return cameraService.isPreviewFromFrontCamera();
}
@Override
public void setDeviceOrientation(int rotation) {
cameraService.setOrientation(rotation);
if (mCapturingId != null) {
CameraService.VideoParams videoParams = cameraService.getParams(mCapturingId);
VideoEvent event = new VideoEvent();
event.started = true;
event.w = videoParams.width;
event.h = videoParams.height;
event.rot = videoParams.rotation;
videoEvents.onNext(event);
}
}
@Override
protected List<String> getVideoDevices() {
return cameraService.getCameraIds();
}
private static class Shm {
String id;
int w, h;
long window = 0;
}
@Override
public void unregisterCameraDetectionCallback() {
cameraService.unregisterCameraDetectionCallback();
}
}