Android Tutorial Slow Motion video recording using CamEX2

Here is the example of SlowMo video capture using Camera2 API and CamEX2 class. This is the code for recording slow-motion video which you can find at the bottom of CamEX2 Class of attached example,
B4X:
#Region HighSpeed/SlowMo

Public Sub getHighSpeedVideoSizesAndFPS As List
    Dim scMap As JavaObject = GetFromCameraCharacteristic("SCALER_STREAM_CONFIGURATION_MAP")
    Dim sizelist() As Object = scMap.RunMethod("getHighSpeedVideoSizes",Null)
    Dim FPSRanges() As Object
    Dim vList As List
    vList.Initialize
    For Each size As JavaObject In sizelist
        FPSRanges = scMap.RunMethodJO("getHighSpeedVideoFpsRangesFor",Array(size))
        Dim fpsVal As Int
        Dim fpsR As Object
        For Each fps As JavaObject In FPSRanges
            fpsVal = fps.RunMethod("getUpper",Null)
            fpsR = fps
        Next
        vList.Add(CreateMap("sizeObject":size, "FPSRange":fpsR, "FPS":fpsVal, "quality":size.RunMethod("getHeight",Null)&"p"))
    Next
    Return vList
End Sub

'Prepares the surface for high speed video capture.
'Need to add this line to the manifest editor:
'AddPermission(android.permission.RECORD_AUDIO)
Public Sub PrepareSurfaceForHighSpeedVideo (MyTaskIndex As Int, Dir As String, FileName As String, FPS As Int, Size As Object) As ResumableSub
    If getHighSpeedVideoSizesAndFPS.Size > 0 Then

        HighSpeedVideo = False
        If MyTaskIndex <> TaskIndex Then Return False
        CloseSession
        Wait For (CreateSurface) Complete (Result As Boolean)
        If MyTaskIndex <> TaskIndex Then Return False
        File.Delete(Dir, FileName)
        MediaRecorder = Camera.CreateMediaRecorder(PreviewSize, Dir, FileName)
        MediaRecorder.RunMethod("setOrientationHint", Array(GetHintOrientation))
        MediaRecorder.RunMethod("setVideoFrameRate", Array(30))
        Dim dFps As Double = FPS
        MediaRecorder.RunMethod("setCaptureRate", Array(dFps))
        Dim ii As Int = (FPS/30)*1000000
        MediaRecorder.RunMethod("setVideoEncodingBitRate", Array(ii))
     
        Dim jo As JavaObject = Me
        jo.RunMethod("StartHighSpeedSession",Array(Camera,tv, Size))
        Wait For Camera_SessionConfigured (Success As Boolean)
        If MyTaskIndex <> TaskIndex Then Return False
        HighSpeedVideo = True
        Return Success
    Else
        Return False
    End If
End Sub


#End Region

#if Java
import android.view.TextureView;
import android.view.Surface;
import android.util.Size;
import android.util.Log;
import android.util.Range;
import java.util.ArrayList;
import java.util.List;
import java.io.IOException;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
import android.media.MediaCodec;
import android.os.Handler;

public void StartHighSpeedSession(final anywheresoftware.b4a.objects.Camera2 _cam,final TextureView Surface, final Size PreviewSize) throws CameraAccessException, IllegalStateException, IOException {
     
    final ArrayList<Surface> targets = new ArrayList<Surface>();
    if (Surface != null) {
        _cam.previewSurface = new Surface(Surface.getSurfaceTexture());
        Surface.getSurfaceTexture().setDefaultBufferSize(PreviewSize.getWidth(), PreviewSize.getHeight());
        targets.add(_cam.previewSurface);
    }
  
    _cam.persistentSurface = MediaCodec.createPersistentInputSurface();
    _cam.mediaRecorder.setInputSurface(_cam.persistentSurface);
    _cam.mediaRecorder.prepare();
    targets.add(_cam.persistentSurface);
  
    _cam.cameraDevice.createConstrainedHighSpeedCaptureSession((java.util.List)targets, (CameraCaptureSession.StateCallback)new CameraCaptureSession.StateCallback() {
        public void onConfigureFailed(final CameraCaptureSession session) {
            ba.raiseEventFromUI(null, "camera_sessionconfigured", new Object[] { false });
        }
     
        public void onConfigured(final CameraCaptureSession session) {
            _cam.captureSession = session;
            ba.raiseEventFromUI(null, "camera_sessionconfigured", new Object[] { true });
        }
     
        public void onClosed(final CameraCaptureSession session) {
            if (_cam.captureSession != null && _cam.captureSession.equals(session)) {
                _cam.captureSession = null;
            }
        }
    }, (Handler)null);
}

public Object SetRepeatingBurst(final anywheresoftware.b4a.objects.Camera2 _cam,final Object Builder, final Range<Integer> fpsRange) throws CameraAccessException {

    final CaptureRequest.Builder builder = ((CaptureRequest.Builder)Builder);
    final CaptureRequest req = builder.build();
  
    CameraConstrainedHighSpeedCaptureSession mPreviewSessionHighSpeed = (CameraConstrainedHighSpeedCaptureSession) _cam.captureSession;
  
    List<CaptureRequest> mPreviewBuilderBurst = mPreviewSessionHighSpeed.createHighSpeedRequestList(req);
    mPreviewSessionHighSpeed.setRepeatingBurst(mPreviewBuilderBurst, null, (Handler)null);
    return req;
}

#End If

Usage:
  1. Check if the device supports High FPS video recording using getHighSpeedVideoSizesAndFPS.
    1. This will return a list of High FPS profile.
    2. Each Profile will contain a capture size, fps range, max fps of that profile and a quality string (eg. 720p) for information
    3. If the list size is ZERO then the device does not support High FPS recording
  2. Initialize Camera Preview (check example attached line 84, 85)
  3. At last call, PrepareSurfaceForHighSpeedVideo to prepare the hardware to High FPS mode. (check example attached line 87)
  4. Now you can call StartVideoRecording and StopVideoRecording to record a slow-motion video.
I have modified some codes of the CamEX2 class to make those compatible with these new functions. This attached example is a modified version of the original Camex2 example. If you need that example project click here.

Note: This is not a complete camera example but just a tutorial of the above functions. So the preview panel may look stretched but the recorded video file will not.
 

Attachments

  • CamEx2_SlowMo_Video.zip
    16.9 KB · Views: 559
Last edited:

ElliotHC

Active Member
Licensed User
Hi, I've tried to run this but unsuccessfully.

upload_2019-8-9_9-45-1.png
 

Brandsum

Well-Known Member
Licensed User
Run in debug mode and check the line number. Also try to run the Camex2 original example and check if that works
 

Cableguy

Expert
Licensed User
Longtime User
Hummmm.... I'm pretty sure that "Slow Motion video RECORDING" is impossible... Not to say against nature laws!
You can, however, do "Slow Motion Video PLAYBACK"
 
Last edited:

ElliotHC

Active Member
Licensed User
Hummmm.... Com pretty sure that "Slow Motion video RECORDING" is impossible... Not to say against nature laws!
You can, however, do "Slow Motion Video PLAYBACK"
I need a 250ms loop at 960fps recording at 720p, a further 250ms taken after the trigger and added to the clip.. Then played back at 30fps.

Maybe I'll just use the default app, rename and upload the files when they appear.
 

Brandsum

Well-Known Member
Licensed User
Hummmm.... Com pretty sure that "Slow Motion video RECORDING" is impossible... Not to say against nature laws!
You can, however, do "Slow Motion Video PLAYBACK"
That is partially true. Actually, the hardware is recording at a higher frame rate but it is being written to disk at a lower frame rate. So if you capture for 5 seconds at 480 fps, then your video file duration will be greater than 1minute 20sec.

https://developer.android.com/reference/android/media/CamcorderProfile#QUALITY_HIGH_SPEED_LOW
 
Last edited:

Brandsum

Well-Known Member
Licensed User
960fps recording at 720p
If your device supports then you will get this profile from getHighSpeedVideoSizesAndFPS and then set previewsize and start video capture. You will get your slow-motion video.
 

Brandsum

Well-Known Member
Licensed User
Log window will show an arrow (->) sign before the error text. Click on that to goto line number.
 

emexes

Expert
Licensed User
I was running in debug mode, it didn't give me a line number :(
Log window will show an arrow (->) sign before the error text. Click on that to goto line number.
Maybe the B4A debug line numbers are not generated within inline Java.

Presumably could work around by manually coding your own trace mechanism, eg strategically-placed calls to a B4A Sub that writes to the log.
 

ElliotHC

Active Member
Licensed User
As I've pretty much run out of time on this, I'm going to use the un-built app and run an app in the background to upload the file. I'll come back to this next week to see if I can get it working.
 

Brandsum

Well-Known Member
Licensed User
That error was occurring from starter service. Maybe you need to give storage write access to your app.
 

ElliotHC

Active Member
Licensed User
I'm stuck between two options..

1. Try and get the SD card reading working so that I can move the video file out when it sees a new video produced by the internal camera app.
2. Try and get this app working, filming at 960fps constantly for 500ms looping, then on trigger take a further 500ms and save in a location I can easily read and write to.

In your opinion, which of these two options is going to be the easiest to implement?
 

Brandsum

Well-Known Member
Licensed User
Both options are almost similar. Assuming that you can read and write to external storage and can monitor for a new file,
  1. You need to find a way to trigger the internal camera app.
  2. You need to modify this example to record further 500ms after trigger.
 

Miguel Antonio

New Member
Hi!

Thanks a lot, great code!

A little question, when i run the code attached this error appear:

Error: (NullPointerException) java.lang.NullPointerException: Attempt to invoke virtual method 'java.util.List android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession.createHighSpeedRequestList(android.hardware.camera2.CaptureRequest)' on a null object reference

The device supports High FPS video, it´s a Xiaomi Redmi Note 8.

getHighSpeedVideoSizesAndFPS from Xiaomi log = (ArrayList) [{sizeObject=1280x720, FPSRange=[240, 240], FPS=240, quality=720p}, {sizeObject=1280x480, FPSRange=[240, 240], FPS=240, quality=480p}, {sizeObject=1280x400, FPSRange=[240, 240], FPS=240, quality=400p}, {sizeObject=800x480, FPSRange=[240, 240], FPS=240, quality=480p}, {sizeObject=720x480, FPSRange=[240, 240], FPS=240, quality=480p}, {sizeObject=640x480, FPSRange=[240, 240], FPS=240, quality=480p}, {sizeObject=480x320, FPSRange=[240, 240], FPS=240, quality=320p}, {sizeObject=320x240, FPSRange=[240, 240], FPS=240, quality=240p}, {sizeObject=1920x1080, FPSRange=[120, 120], FPS=120, quality=1080p}, {sizeObject=480x360, FPSRange=[240, 240], FPS=240, quality=360p}]

I appreciate any help,

Thanks a lot!
 
Top