So, I managed to create the functionality i wanted with the old camera the way I wanted it.
With mCamera.autoFocus(autoFocusCallback); i detect when I have focus and run the required code while in preview-mode.
Now I have a hard time grasping how to do the same in camera2 API.
My first idea was that i'd use
private void process(CaptureResult result) {
switch (mState) {
case STATE_PREVIEW: {
// We have nothing to do when the camera preview is working normally.
int afState = result.get(CaptureResult.CONTROL_AF_STATE);
//if (CaptureResult.CONTROL_AF_STATE == afState) {
Log.d("SOME KIND OF FOCUS", "WE HAVE");
//}
break;
}
}
but I fail to find some kind of state that tells me we have gotten focus. Does someone have any idea how this can be done with Camera2 API?
For those interested I ended up with a mix of this:
private CameraCaptureSession.CaptureCallback mCaptureCallback
= new CameraCaptureSession.CaptureCallback() {
private void process(CaptureResult result) {
switch (mState) {
case STATE_PREVIEW: {
int afState = result.get(CaptureResult.CONTROL_AF_STATE);
if (CaptureResult.CONTROL_AF_TRIGGER_START == afState) {
if (areWeFocused) {
//Run specific task here
}
}
if (CaptureResult.CONTROL_AF_STATE_PASSIVE_FOCUSED == afState) {
areWeFocused = true;
} else {
areWeFocused = false;
}
break;
}
}
}
#Override
public void onCaptureProgressed(CameraCaptureSession session, CaptureRequest request,
CaptureResult partialResult) {
process(partialResult);
}
#Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
TotalCaptureResult result) {
process(result);
}
};
It works good enough :)
You've basically got it. The list of states you can check for and their transitions can be found here.
It depends on what CONTROL_AF_MODE you are using, but generally you check for FOCUSED_LOCKED or perhaps PASSIVE_FOCUSED, though you may want to have cases for NOT_FOCUSED_LOCKED and PASSIVE_UNFOCUSED in case the camera just cannot focus on the scene.
Related
My app shows a preview and video recording starts with a button press.
What I'm trying to achieve is to automatically turn on flashlight (torch mode) as soon as the video recording starts.
However I couldn't find a way to do so. On Camera2 API we can use FLASH_MODE_AUTO which will use the flashlight when capturing photo when the scene is dark, but that doesn't work for video recording.
There's this FLASH_MODE_TORCH which I could use to turn on the flashlight just like I wanted, but there isn't a FLASH_MODE_TORCH_AUTO to automatically do so when the scene is dark..
There were some answers that uses Ambient light sensor (Sensor.TYPE_LIGHT) of the device to determine whether we are in a dark scene, however that uses the front ambient light sensor instead of the camera itself I think. This is not ideal as the ambient light can be low but the rear camera is able to adjust exposure level to achieve good enough image quality without using flash. So ideally if the camera says 'flash is required' then only the app activates FLASH_MODE_TORCH.
Since the app shows a preview the device already know whether flash is needed before the button press, is there a way to determine whether flash is required during preview?
Please try the below method where you need you can use it
below is for Camera API
public void switchFlashOnMode() {
Camera.Parameters p = getCamera().getParameters();
try {
//p.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
p.setFlashMode(Parameters.FLASH_MODE_AUTO);
getCamera().setParameters(p);
getCamera().startPreview();
isFlashTorch = true;
}catch (Exception e){
e.printStackTrace();
}
}
public void switchFlashOffMode() {
Camera.Parameters p = getCamera().getParameters();
try {
p.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
getCamera().setParameters(p);
Thread.sleep(200);
getCamera().stopPreview();
isFlashTorch = false;
}catch (Exception e){
e.printStackTrace();
}
}
below is for Camera2 API
void switchFlashMode() {
if (!flashSupport) return;
try {
if (isFlashTorch) {
isFlashTorch = false;
requestBuilder.set(CaptureRequest.FLASH_MODE, CameraMetadata.FLASH_MODE_OFF);
} else {
isFlashTorch = true;
//requestBuilder.set(CaptureRequest.FLASH_MODE,CameraMetadata.FLASH_MODE_TORCH);
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
}
cameraCaptureSession.setRepeatingRequest(requestBuilder.build(), null, null);
} catch (Exception e) {
e.printStackTrace();
}
}
hope it will help you
Finally figured this out, gpuser's answer used the right flag but it is not complete - still need to code the callback and turn on the torchlight when needed.
I also found that for video recording, we still use the same Camera2 API init and configuration steps, just that some of the callbacks will be fired multiple times, so I added a flag to perform the flash detection only once.
1)After camera started capturing, run this code
performAutoTorchDetectionOnce = true; // set this flag first, will be used later
captureRequestBuilder = camera.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); // CONTROL_AE_MODE_ON_AUTO_FLASH is important here, to enable flash detection
captureSession.setRepeatingRequest(captureRequestBuilder.build(), captureCallback, null);
2)And this is my captureCallback implementation, change it depending on your needs. The gist of it is that eventually the camera capture will fall into one of the two states, CONTROL_AE_STATE_CONVERGED or CONTROL_AE_STATE_FLASH_REQUIRED. These two states mean that auto exposure algorithm has finished running, if it is converged means no flash is needed whereas flash_required will mean that we have to turn on flash. In the latter we will then need to manually turn on the flash in the next step.
private CameraCaptureSession.CaptureCallback captureCallback =
new CameraCaptureSession.CaptureCallback() {
#Override
public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
long timestamp, long frameNumber) {
super.onCaptureStarted(session, request, timestamp, frameNumber);
}
#Override
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE);
if (aeState != null) {
if (performAutoTorchDetectionOnce) {
if (aeState == CameraMetadata.CONTROL_AE_STATE_CONVERGED // CONTROL_AE_STATE_CONVERGED means Auto-exposure has finished
|| aeState == CameraMetadata.CONTROL_AE_STATE_FLASH_REQUIRED) { // CONTROL_AE_STATE_FLASH_REQUIRED means Auto-exposure has finished, but flash is required
performAutoTorchDetectionOnce = false;
enableTorch(aeState == CameraMetadata.CONTROL_AE_STATE_FLASH_REQUIRED);
}
}
}
}
};
3)Here's the enableTorch implementation. I tried leaving CONTROL_AE_MODE as CONTROL_AE_MODE_ON_AUTO_FLASH but it didn't work, torch light does not turn on, so I have to change it to CONTROL_AE_MODE_ON.
public synchronized void enableTorch(boolean enable) {
Timber.d("enableTorch(" + enable + ") called");
try {
if (isCaptureStarted()) {
if (enable) {
captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_TORCH);
} else {
captureRequestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
}
captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
captureSession.setRepeatingRequest(captureRequestBuilder.build(), null, null);
}
} catch (CameraAccessException e) {
Timber.e(e, "enableTorch(" + enable + ") failed: ");
}
}
I am trying to convert my PC game code in unity to android and I am stuck on the controls change. Please help!
This is the code:
Getting the state of rocket.
enum State { Dying, Alive, Transcending }
State state = State.Alive;
// Update is called once per frame
void Update()
{
if (state == State.Alive)
{
RespondToThrustInput();
RespondToRotateInput();
}
}
When the rocket collides with anything it checks whether it's friendly or not before changing its state from alive to dead.
private void OnCollisionEnter(Collision collision)
{
if (state != State.Alive) { return; }
switch (collision.gameObject.tag)
{
case "Friendly":
break;
case "Finish":
state = State.Transcending;
audioSource.Stop();
audioSource.PlayOneShot(finishgame);
finishgameParticles.Play();
Invoke("LoadNextScene", levelloaddelay);
break;
default:
state = State.Dying;
audioSource.Stop();
audioSource.PlayOneShot(death);
deathParticles.Play();
Invoke("LoadFirstScene", levelloaddelay);
break;
}
}
private void LoadFirstScene()
{
SceneManager.LoadScene(9);
}
Loading next scene using build index.
private void LoadNextScene()
{
if (nextscenetoload > 7)
{
nextscenetoload = 0;
}
SceneManager.LoadScene(nextscenetoload);
}
Space for ignition or force and audio sources for playing sound effects.
private void RespondToThrustInput()
{
if (Input.GetKey(KeyCode.Space))
{
ApplyThrust();
}
else
{
audioSource.Stop();
mainengineParticles.Stop();
}
}
Apply thrust is the method I wrote with the logic of the rocket thrust.
private void ApplyThrust()
{
rigidbody.AddRelativeForce(Vector3.up * mainThrust * Time.deltaTime);
if (!audioSource.isPlaying)
audioSource.PlayOneShot(mainengine);
mainengineParticles.Play();
}
Rotation of the rocket or Left and right. Here I am trying to rotate the rocket using the A and D keys
void RespondToRotateInput()
{
float rotationThisFrame = rcsThrust * Time.deltaTime;
if (Input.GetKey(KeyCode.A))
{
rigidbody.freezeRotation = true;
transform.Rotate(Vector3.forward * rotationThisFrame);
rigidbody.freezeRotation = false;
}
else if (Input.GetKey(KeyCode.D))
{
rigidbody.freezeRotation = true;
transform.Rotate(-Vector3.forward * rotationThisFrame);
rigidbody.freezeRotation = false;
}
}
For PC games there is a keyboard to use to control, but for the android there are touches, you have to verify if there is a touch on the screen, like this:
if (Input.touchCount > 0)
{
Touch touch = Input.GetTouch(0);
//do something
}
also you need more work to determine where the touch is located and do you customizations... or in case you're not interested about this input handling, you can use some assets from Unity Assets Store to cover this part for you.
check this link for more information about touch control in Unity documentation:
https://docs.unity3d.com/ScriptReference/Input.GetTouch.html
I am trying to detect when camera has focused (or has stopped trying to) so I am calling result.get(CaptureResult.CONTROL_AF_STATE)in onCaptureCompleted method of callback.
It kind of works for mode AF_MODE_CONTINUOUS_PICTURE, camera reports CONTROL_AF_STATE 1 or 2 (CONTROL_AF_STATE_PASSIVE_SCAN or CONTROL_AF_STATE_PASSIVE_LOCKED), which is nice.
However when camera is switched to AF_MODE_MACRO, then reported CONTROL_AF_STATE is always 0 (INNACTIVE) no matter what happens. I was trying to refer to 1 but probably I do not get it right.
Further info: when changing modes between AF_MODE_MACRO and AF_MODE_CONTINUOUS_PICTURE I always start new capture session like this:
private void configCaptureSession(boolean macroModeNew) {
this.macroMode = macroModeNew;
try {
// Wanna macro?
if (macroMode) {
LOGGER.d( "MACRO ON","");
previewRequestBuilder.set(
CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_MACRO);
}
else {
// Continuous AF
LOGGER.d( "MACRO OFF","");
previewRequestBuilder.set(
CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
}
// Finally, we start displaying the camera preview.
previewRequest = previewRequestBuilder.build();
LOGGER.d( "SETTING NEW SESSION","");
captureSession.setRepeatingRequest(
previewRequest, captureCallback, backgroundHandler);
} catch (final CameraAccessException e) {
LOGGER.e(e, "Exception!");
}
}
captureCallback:
private final CameraCaptureSession.CaptureCallback captureCallback =
new CameraCaptureSession.CaptureCallback() {
#Override
public void onCaptureProgressed(
final CameraCaptureSession session,
final CaptureRequest request,
final CaptureResult partialResult) {}
#Override
public void onCaptureCompleted(
final CameraCaptureSession session,
final CaptureRequest request,
final TotalCaptureResult result) {
afState = result.get(CaptureResult.CONTROL_AF_STATE);
LOGGER.i("FOKKUS-MODE:"+result.get(CaptureResult.CONTROL_AF_MODE));
LOGGER.i("FOKKUS:"+result.get(CaptureResult.CONTROL_AF_STATE));
}
};
Does your device list support for AF_MODE_MACRO in https://developer.android.com/reference/android/hardware/camera2/CameraCharacteristics.html#CONTROL_AF_AVAILABLE_MODES ?
If not, then this is expected as you're trying to use a non-supported focusing mode.
If it is supported, the next issue is that I don't see you issuing an AF trigger command anywhere. Have you looked at the state transition tables for AF_STATE here:
https://developer.android.com/reference/android/hardware/camera2/CaptureResult.html#CONTROL_AF_STATE ?
For AF_AUTO and AF_MACRO, you have to trigger AF when you want a focus pass, and then wait for AF_STATE_FOCUSED_LOCKED or NOT_FOCUSED_LOCKED.
The continuous modes don't require a trigger to focus, which is why you're seeing something happening with them.
I am developing mobile app on Xamarin for android I try to use Camera2 class. Everything looks fine but this line occurs problem on convert type. It says (Java.Lang.Object -> Android.Hardware.Camera2.Params.Face[]) This line works on Android Studio but not in C#.
That's code I use on Xamarin. Other than face recognition, all builded requests works fine.
https://github.com/xamarin/monodroid-samples/tree/master/android5.0/Camera2Basic
Face[] faces = result.Get(CaptureResult.StatisticsFaces);
public class CameraCaptureListener : CameraCaptureSession.CaptureCallback
{
public FaceTrainActivityFragment Owner { get; set; }
public File File { get; set; }
public override void OnCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result)
{
Process(result);
}
public override void OnCaptureProgressed(CameraCaptureSession session, CaptureRequest request, CaptureResult partialResult)
{
Process(partialResult);
}
private void Process(CaptureResult result)
{
switch (Owner.mState)
{
case FaceTrainActivityFragment.STATE_PREVIEW:
{
if (result.Get(CaptureResult.StatisticsFaces) != null) {
//Face[] faces = result.Get(CaptureResult.StatisticsFaces);
//Face[] faces = (Face[])result.Get(CaptureResult.StatisticsFaces);
}
break;
}
}
}
}
it does not allowed me to compile even if I compile with using hard casting to (Face[]), it gives me same Java.Lang.Object error.
public void CreateCameraPreviewSession()
{
try
{
SurfaceTexture texture = mTextureView.SurfaceTexture;
if (texture == null)
{
throw new IllegalStateException("texture is null");
}
if (null == mCameraDevice) {
return;
}
// We configure the size of default buffer to be the size of camera preview we want.
texture.SetDefaultBufferSize(mPreviewSize.Width, mPreviewSize.Height);
// This is the output Surface we need to start preview.
Surface surface = new Surface(texture);
// We set up a CaptureRequest.Builder with the output Surface.
mPreviewRequestBuilder = mCameraDevice.CreateCaptureRequest(CameraTemplate.Preview);
mPreviewRequestBuilder.AddTarget(surface);
// Here, we create a CameraCaptureSession for camera preview.
List<Surface> surfaces = new List<Surface>();
surfaces.Add(surface);
//surfaces.Add(mImageReader.Surface);
setFaceDetect(mPreviewRequestBuilder, mFaceDetectMode);
mCameraDevice.CreateCaptureSession(surfaces, new CameraCaptureSessionCallback(this), null);
}
catch (CameraAccessException e)
{
e.PrintStackTrace();
}
and I am callling CreateCameraPreviewSession inside of Camera State Listener like that
public class CameraStateListener : CameraDevice.StateCallback
{
public FaceTrainActivityFragment owner;
public override void OnOpened(CameraDevice cameraDevice)
{
// This method is called when the camera is opened. We start camera preview here.
owner.mCameraOpenCloseLock.Release();
owner.mCameraDevice = cameraDevice;
owner.CreateCameraPreviewSession();
}
It says (Java.Lang.Object -> Android.Hardware.Camera.Params.Face[]) This line works on Android Studio but not in C#.
From the error you are getting, you are probably using the wrong namespace for Face. Instead of Android.Hardware.Camera.Params.Face, please use Android.Hardware.Camera2.Params.Face.
My problem is when I switch between different flashmodes and then want to capture an image, my captureBuilder won't set the chosen flashmode. It only works when i close and reopen the camera.
I took the https://github.com/googlesamples/android-Camera2Basic as a starting point.
my method:
private void captureStillPicture() {
try {
final Activity activity = (Activity) context;
if (null == activity || null == mCameraDevice) {
return;
}
// This is the CaptureRequest.Builder that we use to take a picture.
CaptureRequest.Builder captureBuilder =
mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
captureBuilder.addTarget(mImageReader.getSurface());
// Use the same AE and AF modes as the preview.
captureBuilder.set(CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
setCurrentFlash(captureBuilder);
// Orientation
int rotation = activity.getWindowManager()
.getDefaultDisplay()
.getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
CameraCaptureSession.CaptureCallback captureCallback =
new CameraCaptureSession.CaptureCallback() {
#Override
public void onCaptureCompleted(#NonNull CameraCaptureSession session,
#NonNull CaptureRequest request, #NonNull TotalCaptureResult result) {
super.onCaptureCompleted(session, request, result);
Toast.makeText(context, "image captured", Toast.LENGTH_SHORT)
.show();
unlockFocus();
}
};
mCaptureSession.stopRepeating();
mCaptureSession.capture(captureBuilder.build(), captureCallback, null);
} catch (CameraAccessException e) {
Log.e(this.getClass()
.getSimpleName(), e.getMessage(), e);
}
This is the setCurrentFlash method:
private void setCurrentFlash(CaptureRequest.Builder requestBuilder) {
if (mFlashSupported) {
switch (flashMode.name()) {
case FLASH_AUTO:
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
break;
case FLASH_ON:
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
break;
case FLASH_OFF:
requestBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
break;
default:
requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
break;
}
}
Any ideas why the builder is not setting the flash correctly before capturing?
***** EDIT *****
Solved issue by setting the flash to previewRequestBuilder when calling runPrecaptureSequence() like Eddy Talvala suggested
private void runPrecaptureSequence() {
try {
// This is how to tell the camera to trigger.
setCurrentFlash(previewRequestBuilder);
previewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
// Tell #mCaptureCallback to wait for the precapture sequence to be set.
state = STATE_WAITING_PRECAPTURE;
captureSession.capture(previewRequestBuilder.build(), mCaptureCallback, backgroundHandler);
} catch (CameraAccessException e) {
e.printStackTrace();
}
You want to update the flash mode in your preview request as well; generally the camera device wants to know your desired flash mode when you trigger the precapture sequence (with AE_PRECAPTURE_TRIGGER), so that it knows if it should turn on the precapture flash, which it needs to determine the final flash power.
The usual sequence of events is:
Set preview flash mode to desired mode
Wait for user to hit the shutter button
Issue single preview request with precapture trigger set (but keep preview request on repeat otherwise).
Wait for AE_STATE_PRECAPTURE to stop being the AE state in your capture results
Issue the final capture request (keep the same flash mode)
Get final JPEG in your ImageReader
(This ignores ensuring focus is good, which generally is done before/in parallel to starting the precapture sequence)