From 2e2b400274f784314bd64f62efa31f24c39dcb44 Mon Sep 17 00:00:00 2001 From: Bastian Kohler Date: Fri, 16 Jun 2023 00:42:27 +0200 Subject: [PATCH] Video recorder based on Camerax working. But problems with simultanious VideoDetector --- .../{ => Detection}/DetectionReport.java | 2 +- .../{ => Detection}/Detector.java | 2 +- .../VideoDetector.java | 53 ++++-- .../DetectionRecorder.java | 147 +++++++++++++++++ .../ueberwachungssystem/MainActivity.java | 22 ++- .../example/ueberwachungssystem/Recorder.java | 114 ------------- .../ueberwachungssystem/RecorderX.java | 153 ------------------ app/src/main/res/layout/activity_main.xml | 4 - 8 files changed, 200 insertions(+), 297 deletions(-) rename app/src/main/java/com/example/ueberwachungssystem/{ => Detection}/DetectionReport.java (95%) rename app/src/main/java/com/example/ueberwachungssystem/{ => Detection}/Detector.java (96%) rename app/src/main/java/com/example/ueberwachungssystem/{VideoDetection => Detection}/VideoDetector.java (81%) create mode 100644 app/src/main/java/com/example/ueberwachungssystem/DetectionRecorder.java delete mode 100644 app/src/main/java/com/example/ueberwachungssystem/Recorder.java delete mode 100644 app/src/main/java/com/example/ueberwachungssystem/RecorderX.java diff --git a/app/src/main/java/com/example/ueberwachungssystem/DetectionReport.java b/app/src/main/java/com/example/ueberwachungssystem/Detection/DetectionReport.java similarity index 95% rename from app/src/main/java/com/example/ueberwachungssystem/DetectionReport.java rename to app/src/main/java/com/example/ueberwachungssystem/Detection/DetectionReport.java index bfcb52c..2dc8cba 100644 --- a/app/src/main/java/com/example/ueberwachungssystem/DetectionReport.java +++ b/app/src/main/java/com/example/ueberwachungssystem/Detection/DetectionReport.java @@ -1,4 +1,4 @@ -package com.example.ueberwachungssystem; +package com.example.ueberwachungssystem.Detection; import android.util.Log; diff --git a/app/src/main/java/com/example/ueberwachungssystem/Detector.java b/app/src/main/java/com/example/ueberwachungssystem/Detection/Detector.java similarity index 96% rename from app/src/main/java/com/example/ueberwachungssystem/Detector.java rename to app/src/main/java/com/example/ueberwachungssystem/Detection/Detector.java index 7f00656..0b726a7 100644 --- a/app/src/main/java/com/example/ueberwachungssystem/Detector.java +++ b/app/src/main/java/com/example/ueberwachungssystem/Detection/Detector.java @@ -1,4 +1,4 @@ -package com.example.ueberwachungssystem; +package com.example.ueberwachungssystem.Detection; import androidx.annotation.NonNull; diff --git a/app/src/main/java/com/example/ueberwachungssystem/VideoDetection/VideoDetector.java b/app/src/main/java/com/example/ueberwachungssystem/Detection/VideoDetector.java similarity index 81% rename from app/src/main/java/com/example/ueberwachungssystem/VideoDetection/VideoDetector.java rename to app/src/main/java/com/example/ueberwachungssystem/Detection/VideoDetector.java index babb677..bfec02c 100644 --- a/app/src/main/java/com/example/ueberwachungssystem/VideoDetection/VideoDetector.java +++ b/app/src/main/java/com/example/ueberwachungssystem/Detection/VideoDetector.java @@ -1,4 +1,4 @@ -package com.example.ueberwachungssystem.VideoDetection; +package com.example.ueberwachungssystem.Detection; import android.content.Context; import android.graphics.ImageFormat; @@ -15,12 +15,27 @@ import androidx.camera.view.PreviewView; import androidx.core.content.ContextCompat; import androidx.lifecycle.LifecycleOwner; -import com.example.ueberwachungssystem.Detector; import com.google.common.util.concurrent.ListenableFuture; import java.nio.ByteBuffer; import java.util.concurrent.ExecutionException; + +/** + * Video Detector inherits some methods from abstract Detector class (more info there) + * + * + * USE FROM MAIN ACTIVITY: + * + * VideoDetector vd = new VideoDetector(this); + * vd.setPreview(previewView); //THIS IS OPTIONAL! + * vd.setOnDetectionListener(new Detector.OnDetectionListener{...}); + * vd.startDetection(); + * vd.stopDetection + * + * */ + + @ExperimentalGetImage public class VideoDetector extends Detector { @@ -33,19 +48,24 @@ public class VideoDetector extends Detector { private PreviewView previewView = null; // Detect Violation private static final float PIXEL_THRESHOLD = 30f; // Luminosity (brightness channel of YUV_420_888) - private static final float ALARM_THRESHOLD = 1f; // Percent of pixels changed + private static final float ALARM_THRESHOLD = 0.2f; // Percent of pixels changed private ByteBuffer previousBuffer = null; - /** Constructor */ + /** + * Constructor + * @param context: the context of calling activity (usually "this") + * */ public VideoDetector(Context context) { super(); this.context = context; } - /** Starts Video Detection */ + /** + * Starts the Video Detection + * */ @Override public void startDetection() { if (isDetectionRunning) @@ -56,7 +76,7 @@ public class VideoDetector extends Detector { cameraProviderFuture.addListener(() -> { try { cameraProvider = cameraProviderFuture.get(); - bindLuminosityAnalysis(cameraProvider); + bindAnalysis(cameraProvider); isDetectionRunning = true; previousBuffer = null; } catch (ExecutionException | InterruptedException e) { @@ -66,7 +86,9 @@ public class VideoDetector extends Detector { } - /** Stops Video Detection */ + /** + * Stops the Video Detection + * */ @Override public void stopDetection() { if (!isDetectionRunning) @@ -76,14 +98,20 @@ public class VideoDetector extends Detector { } - /** Set PreviewView to show Image */ + /** + * Set PreviewView to show video feed while detecting + * this is optional and does not need to be called + * */ public void setPreviewView(PreviewView previewView) { this.previewView = previewView; } - /** Binds the Luminosity Analyzer (configure and run Analysis) */ - private void bindLuminosityAnalysis(@NonNull ProcessCameraProvider cameraProvider) { + /** + * Binds the Luminosity Analyzer (configure and run Analysis) + * @param cameraProvider: CameraProvider of Context passed by Constructor + * */ + private void bindAnalysis(@NonNull ProcessCameraProvider cameraProvider) { // Configure and create Image Analysis ImageAnalysis.Builder builder = new ImageAnalysis.Builder(); builder.setTargetResolution(new Size(640, 480)); @@ -122,7 +150,10 @@ public class VideoDetector extends Detector { - /** Calculate Amount of Pixels changed */ + /** + * Calculate Amount of Pixels changed using a threshold + * @param image + * */ private int getChangedPixelCount(Image image) { Image.Plane[] planes = image.getPlanes(); ByteBuffer buffer = planes[0].getBuffer(); diff --git a/app/src/main/java/com/example/ueberwachungssystem/DetectionRecorder.java b/app/src/main/java/com/example/ueberwachungssystem/DetectionRecorder.java new file mode 100644 index 0000000..ba958b4 --- /dev/null +++ b/app/src/main/java/com/example/ueberwachungssystem/DetectionRecorder.java @@ -0,0 +1,147 @@ +package com.example.ueberwachungssystem; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.pm.PackageManager; +import android.media.MediaPlayer; +import android.view.Surface; +import android.widget.Toast; +import android.widget.VideoView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.camera.core.CameraSelector; +import androidx.camera.core.Preview; +import androidx.camera.core.VideoCapture; +import androidx.camera.lifecycle.ProcessCameraProvider; +import androidx.camera.view.PreviewView; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.LifecycleOwner; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.io.File; +import java.util.concurrent.ExecutionException; + +public class DetectionRecorder { + + private final Context context; + private ProcessCameraProvider cameraProvider; + private VideoCapture videoCapture = null; + private PreviewView previewView = null; + private boolean isRunning = false; + private boolean isVideoPlaying = false; + + + // Constructor + public DetectionRecorder(Context context) { + this.context = context; + } + + + public void start() { + if (isRunning) + return; + isRunning = true; + + // Request Camera Provider + final ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(context); + //Check for Camera availability + cameraProviderFuture.addListener(() -> { + try { + cameraProvider = cameraProviderFuture.get(); + startCameraX(cameraProvider); + } catch (ExecutionException | InterruptedException e) { + // No errors need to be handled for this Future. This should never be reached. + } + }, ContextCompat.getMainExecutor(context)); + } + + + @SuppressLint("RestrictedApi") + public void stop() { + if (!isRunning) + return; + + videoCapture.stopRecording(); + cameraProvider.unbind(videoCapture); + isRunning = false; + } + + @SuppressLint("RestrictedApi") + private void startCameraX(ProcessCameraProvider cameraProvider) { + //cameraProvider.unbindAll(); + + CameraSelector cameraSelector = new CameraSelector.Builder() + .requireLensFacing(CameraSelector.LENS_FACING_BACK) + .build(); + + // Preview Use Case + Preview preview = new Preview.Builder().build(); + if (previewView != null) + preview.setSurfaceProvider(previewView.getSurfaceProvider()); + + // Video Capture Use Case + videoCapture = new VideoCapture.Builder() + .setVideoFrameRate(30) + .setTargetRotation(Surface.ROTATION_0) + .build(); + + cameraProvider.bindToLifecycle((LifecycleOwner) context, cameraSelector, preview, videoCapture); + + + File dir = context.getFilesDir(); + String filename = "recording"; + String vidFilePath = dir.getAbsolutePath() + "/" + filename + ".mp4"; + File vidFile = new File(vidFilePath); + + if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + // TODO: Consider calling + // ActivityCompat#requestPermissions + // here to request the missing permissions, and then overriding + // public void onRequestPermissionsResult(int requestCode, String[] permissions, + // int[] grantResults) + // to handle the case where the user grants the permission. See the documentation + // for ActivityCompat#requestPermissions for more details. + return; + } + videoCapture.startRecording( + new VideoCapture.OutputFileOptions.Builder(vidFile).build(), + context.getMainExecutor(), + new VideoCapture.OnVideoSavedCallback() { + @Override + public void onVideoSaved(@NonNull VideoCapture.OutputFileResults outputFileResults) { + Toast.makeText(context, "recording saved", Toast.LENGTH_SHORT).show(); + } + @Override + public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) { + Toast.makeText(context, "recording failed", Toast.LENGTH_SHORT).show(); + } + } + ); + } + + /** Set PreviewView to show Image */ + public void setPreviewView(PreviewView previewView) { + this.previewView = previewView; + } + + private void playAudio() { + MediaPlayer mp = new MediaPlayer(); + try { + mp.setDataSource(context.getFilesDir() + "/audio.gpp"); + mp.prepare(); + mp.start(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void playVideo(VideoView videoView) { + if (isVideoPlaying) + return; + videoView.setVideoPath(context.getFilesDir() + "/recording.mp4"); + videoView.start(); + } +} diff --git a/app/src/main/java/com/example/ueberwachungssystem/MainActivity.java b/app/src/main/java/com/example/ueberwachungssystem/MainActivity.java index 8719e42..97cc034 100644 --- a/app/src/main/java/com/example/ueberwachungssystem/MainActivity.java +++ b/app/src/main/java/com/example/ueberwachungssystem/MainActivity.java @@ -10,16 +10,15 @@ import androidx.camera.core.ExperimentalGetImage; import android.Manifest; import android.content.pm.PackageManager; import android.os.Bundle; -import android.view.SurfaceView; import android.view.View; import android.widget.TextView; import android.widget.Toast; import android.widget.ToggleButton; import android.widget.VideoView; +import com.example.ueberwachungssystem.Detection.VideoDetector; + import java.io.File; -import java.net.URISyntaxException; -import java.util.Arrays; @ExperimentalGetImage public class MainActivity extends AppCompatActivity { @@ -33,7 +32,6 @@ public class MainActivity extends AppCompatActivity { setContentView(R.layout.activity_main); TextView textView = findViewById(R.id.textView); - SurfaceView surfaceView = findViewById(R.id.surfaceView); VideoView videoView = findViewById(R.id.videoView); PreviewView previewView = findViewById(R.id.previewView); @@ -41,8 +39,9 @@ public class MainActivity extends AppCompatActivity { - RecorderX recorderX = new RecorderX(this); - //recorderX.setPreviewView(previewView); + + DetectionRecorder detectionRecorder = new DetectionRecorder(this); + detectionRecorder.setPreviewView(previewView); ToggleButton toggleButton = findViewById(R.id.previewButton); toggleButton.setOnClickListener(new View.OnClickListener() { @@ -52,16 +51,13 @@ public class MainActivity extends AppCompatActivity { if (isRecordVideoAllowed() && toggleButton.isChecked()) { - try { - recorderX.startRecording(); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } + detectionRecorder.playVideo(videoView); + //detectionRecorder.start(); } else { - recorderX.stopRecording(); + detectionRecorder.stop(); File[] files = directory.listFiles(); - textView.setText(Arrays.toString(files)); + //textView.setText(Arrays.toString(files)); } } }); diff --git a/app/src/main/java/com/example/ueberwachungssystem/Recorder.java b/app/src/main/java/com/example/ueberwachungssystem/Recorder.java deleted file mode 100644 index beb59c4..0000000 --- a/app/src/main/java/com/example/ueberwachungssystem/Recorder.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.example.ueberwachungssystem; - -import android.app.Activity; -import android.content.Context; -import android.hardware.Camera; -import android.media.CamcorderProfile; -import android.media.MediaPlayer; -import android.media.MediaRecorder; -import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.WindowManager; -import android.widget.VideoView; -import java.io.IOException; - -public class Recorder { - - private final Context context; - private Camera camera; - private MediaRecorder mediaRecorder = null; - private SurfaceView surfaceView; - - private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90; - private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270; - - public Recorder (Context context) { - this.context = context; - this.surfaceView = new SurfaceView(context); - } - - - public void startRecording() { - try { - // Open the camera - camera = Camera.open(); - camera.setPreviewDisplay(surfaceView.getHolder()); - camera.startPreview(); - - camera.unlock(); - - // Create a new MediaRecorder instance - mediaRecorder = new MediaRecorder(); - - // Set the camera as the video source - mediaRecorder.setCamera(camera); - - // Set the audio and video source - mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); - mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); - - - CamcorderProfile camcorderProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH); - mediaRecorder.setProfile(camcorderProfile); - - // Set the output file path - mediaRecorder.setOutputFile(context.getFilesDir() + "/video.mp4"); - - - - mediaRecorder.prepare(); - mediaRecorder.start(); - - } catch (IOException e) { - e.printStackTrace(); - } - } - - - public void stopRecording() { - if (mediaRecorder != null) { - try { - mediaRecorder.stop(); - mediaRecorder.reset(); - mediaRecorder.release(); - mediaRecorder = null; - } catch (RuntimeException e) { - // RuntimeException may be thrown if the MediaRecorder is in an invalid state - e.printStackTrace(); - } - - if (camera != null) { - try { - camera.reconnect(); - camera.stopPreview(); - camera.lock(); - camera.release(); - camera = null; - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } - - public void setSurfaceView(SurfaceView surfaceView) { - this.surfaceView = surfaceView; - } - - - private void playAudio() { - MediaPlayer mp = new MediaPlayer(); - try { - mp.setDataSource(context.getFilesDir() + "/audio.gpp"); - mp.prepare(); - mp.start(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void playVideo(VideoView videoView) { - videoView.setVideoPath(context.getFilesDir() + "/video.mp4"); - videoView.start(); - } -} diff --git a/app/src/main/java/com/example/ueberwachungssystem/RecorderX.java b/app/src/main/java/com/example/ueberwachungssystem/RecorderX.java deleted file mode 100644 index 4a73ad2..0000000 --- a/app/src/main/java/com/example/ueberwachungssystem/RecorderX.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.example.ueberwachungssystem; - -import android.annotation.SuppressLint; -import android.content.ContentValues; -import android.content.Context; -import android.media.MediaPlayer; -import android.net.Uri; -import android.provider.MediaStore; -import android.util.Log; -import android.widget.Toast; -import android.widget.VideoView; - -import androidx.camera.core.Camera; -import androidx.camera.core.CameraSelector; -import androidx.camera.core.Preview; -import androidx.camera.lifecycle.ProcessCameraProvider; -import androidx.camera.video.FallbackStrategy; -import androidx.camera.video.MediaStoreOutputOptions; -import androidx.camera.video.Quality; -import androidx.camera.video.QualitySelector; -import androidx.camera.video.Recorder; -import androidx.camera.video.Recording; -import androidx.camera.video.VideoCapture; -import androidx.camera.video.VideoRecordEvent; -import androidx.camera.view.PreviewView; -import androidx.core.content.ContextCompat; -import androidx.core.util.Consumer; -import androidx.lifecycle.LifecycleOwner; - -import com.google.common.util.concurrent.ListenableFuture; - -import java.net.URISyntaxException; -import java.util.concurrent.ExecutionException; - -public class RecorderX { - - private final Context context; - private PreviewView previewView = null; - private ProcessCameraProvider cameraProvider; - - private Recording recording = null; - - public RecorderX(Context context) { - this.context = context; - } - - - @SuppressLint("MissingPermission") - public void startRecording() throws URISyntaxException { - - final ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(context); - try { - cameraProvider = cameraProviderFuture.get(); - } catch (ExecutionException | InterruptedException ignored) { - } - - - QualitySelector qualitySelector = QualitySelector - .from(Quality.HD, FallbackStrategy.higherQualityOrLowerThan(Quality.SD)); - - - Recorder.Builder recorderBuilder = new Recorder.Builder(); - recorderBuilder.setQualitySelector(qualitySelector); - Recorder recorder = recorderBuilder.build(); - - VideoCapture videoCapture = VideoCapture.withOutput(recorder); - - - // Create Preview - Preview preview = new Preview.Builder().build(); - // Specify which Camera to use - CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build(); - // Update PreviewView if set - if (previewView != null) - preview.setSurfaceProvider(previewView.getSurfaceProvider()); - - try { - Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner) context, cameraSelector, videoCapture, preview); - } catch (Exception e) { - Log.e("Error", "Use case binding failed", e); - } - - String name = "recording.mp4"; - Uri filesDirURI = Uri.fromFile(context.getFilesDir()); - ContentValues contentValues = new ContentValues(); - contentValues.put(MediaStore.Video.Media.DISPLAY_NAME, name); - - MediaStoreOutputOptions mediaStoreOutput = new MediaStoreOutputOptions - .Builder(context.getContentResolver(), filesDirURI) - .setContentValues(contentValues) - .build(); - - - - Consumer recordingListener = new Consumer() { - @Override - public void accept(VideoRecordEvent videoRecordEvent) { - if (videoRecordEvent instanceof VideoRecordEvent.Start) { - Toast.makeText(context, "Carture started", Toast.LENGTH_SHORT).show(); - } - else if (videoRecordEvent instanceof VideoRecordEvent.Finalize) { - VideoRecordEvent.Finalize finalize = (VideoRecordEvent.Finalize) videoRecordEvent; - - if (!finalize.hasError()) { - Toast.makeText(context, "Carture succeeded", Toast.LENGTH_SHORT).show(); - } - else { - recording.close(); - recording = null; - Toast.makeText(context, "Carture failed", Toast.LENGTH_SHORT).show(); - } - } - } - }; - - - recording = videoCapture.getOutput() - .prepareRecording(context, mediaStoreOutput) - .withAudioEnabled() - .start(ContextCompat.getMainExecutor(context), recordingListener); - - } - - - - - public void stopRecording() { - recording.stop(); - } - - - - /** Set PreviewView to show Image */ - public void setPreviewView(PreviewView previewView) { - this.previewView = previewView; - } - - private void playAudio() { - MediaPlayer mp = new MediaPlayer(); - try { - mp.setDataSource(context.getFilesDir() + "/audio.gpp"); - mp.prepare(); - mp.start(); - } catch (Exception e) { - e.printStackTrace(); - } - } - - public void playVideo(VideoView videoView) { - videoView.setVideoPath(context.getFilesDir() + "/video.mp4"); - videoView.start(); - } -} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 6d8ba14..2a66b2d 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -27,10 +27,6 @@ android:layout_height="wrap_content" android:text="ToggleButton" /> -