mirror of https://github.com/opencv/opencv.git
Merge pull request #24592 from savuor:recorder_android
Android sample for VideoWriter #24592 This PR: * adds an Android sample for video recording with MediaNDK and built-in MJPEG. * adds a flag `--no_media_ndk` for `build_sdk.py` script to disable MediaNDK linkage. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMakepull/24696/head
parent
2e37a697b9
commit
500fd453a1
9 changed files with 475 additions and 2 deletions
@ -0,0 +1,12 @@ |
||||
set(sample example-video-recorder) |
||||
|
||||
if(BUILD_FAT_JAVA_LIB) |
||||
set(native_deps opencv_java) |
||||
else() |
||||
set(native_deps videoio) |
||||
endif() |
||||
|
||||
add_android_project(${sample} "${CMAKE_CURRENT_SOURCE_DIR}" LIBRARY_DEPS "${OPENCV_ANDROID_LIB_DIR}" SDK_TARGET 11 "${ANDROID_SDK_TARGET}" NATIVE_DEPS ${native_deps}) |
||||
if(TARGET ${sample}) |
||||
add_dependencies(opencv_android_examples ${sample}) |
||||
endif() |
@ -0,0 +1,36 @@ |
||||
apply plugin: 'com.android.application' |
||||
|
||||
android { |
||||
namespace 'org.opencv.samples.recorder' |
||||
compileSdkVersion @ANDROID_COMPILE_SDK_VERSION@ |
||||
defaultConfig { |
||||
applicationId "org.opencv.samples.recorder" |
||||
minSdkVersion @ANDROID_MIN_SDK_VERSION@ |
||||
targetSdkVersion @ANDROID_TARGET_SDK_VERSION@ |
||||
versionCode 301 |
||||
versionName "3.01" |
||||
} |
||||
buildTypes { |
||||
release { |
||||
minifyEnabled false |
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' |
||||
} |
||||
} |
||||
sourceSets { |
||||
main { |
||||
java.srcDirs = @ANDROID_SAMPLE_JAVA_PATH@ |
||||
aidl.srcDirs = @ANDROID_SAMPLE_JAVA_PATH@ |
||||
res.srcDirs = @ANDROID_SAMPLE_RES_PATH@ |
||||
manifest.srcFile '@ANDROID_SAMPLE_MANIFEST_PATH@' |
||||
} |
||||
} |
||||
} |
||||
|
||||
dependencies { |
||||
//implementation fileTree(dir: 'libs', include: ['*.jar']) |
||||
if (gradle.opencv_source == "sdk_path") { |
||||
implementation project(':opencv') |
||||
} else if (gradle.opencv_source == "maven_local" || gradle.opencv_source == "maven_cenral") { |
||||
implementation 'org.opencv:opencv:@OPENCV_VERSION_PLAIN@' |
||||
} |
||||
} |
@ -0,0 +1,38 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||
package="org.opencv.samples.recorder" |
||||
> |
||||
|
||||
<application |
||||
android:label="@string/app_name" |
||||
android:icon="@drawable/icon" |
||||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" > |
||||
|
||||
<activity |
||||
android:exported="true" |
||||
android:name="RecorderActivity" |
||||
android:label="@string/app_name" |
||||
android:screenOrientation="landscape" |
||||
android:configChanges="keyboardHidden|orientation"> |
||||
<intent-filter> |
||||
<action android:name="android.intent.action.MAIN" /> |
||||
<category android:name="android.intent.category.LAUNCHER" /> |
||||
</intent-filter> |
||||
</activity> |
||||
</application> |
||||
|
||||
<supports-screens android:resizeable="true" |
||||
android:smallScreens="true" |
||||
android:normalScreens="true" |
||||
android:largeScreens="true" |
||||
android:anyDensity="true" /> |
||||
|
||||
<uses-permission android:name="android.permission.CAMERA"/> |
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false"/> |
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/> |
||||
<uses-feature android:name="android.hardware.camera.front" android:required="false"/> |
||||
<uses-feature android:name="android.hardware.camera.front.autofocus" android:required="false"/> |
||||
|
||||
</manifest> |
After Width: | Height: | Size: 2.0 KiB |
@ -0,0 +1,38 @@ |
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:tools="http://schemas.android.com/tools" |
||||
xmlns:opencv="http://schemas.android.com/apk/res-auto" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" > |
||||
|
||||
<Button |
||||
android:id="@+id/btn1" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="50dp" |
||||
android:layout_margin="10dp" |
||||
android:text="Start Camera" /> |
||||
|
||||
<TextView |
||||
android:id="@+id/textview1" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_gravity="right" |
||||
android:layout_margin="10dp" |
||||
android:text="Status: Initialized" |
||||
android:textColor="#FF0000" /> |
||||
|
||||
<org.opencv.android.JavaCameraView |
||||
android:layout_width="fill_parent" |
||||
android:layout_height="fill_parent" |
||||
android:visibility="gone" |
||||
android:id="@+id/recorder_activity_java_surface_view" |
||||
opencv:show_fps="true" |
||||
opencv:camera_id="any" /> |
||||
|
||||
<ImageView |
||||
android:id="@+id/image_view" |
||||
android:layout_width="fill_parent" |
||||
android:layout_height="fill_parent" |
||||
android:visibility="gone" |
||||
/> |
||||
|
||||
</FrameLayout> |
@ -0,0 +1,4 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<resources> |
||||
<string name="app_name">OpenCV Video Recorder</string> |
||||
</resources> |
@ -0,0 +1,340 @@ |
||||
package org.opencv.samples.recorder; |
||||
|
||||
import org.opencv.android.CameraActivity; |
||||
import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame; |
||||
import org.opencv.android.OpenCVLoader; |
||||
import org.opencv.android.Utils; |
||||
import org.opencv.core.Mat; |
||||
import org.opencv.core.Size; |
||||
import org.opencv.imgproc.Imgproc; |
||||
import org.opencv.videoio.VideoCapture; |
||||
import org.opencv.videoio.VideoWriter; |
||||
import org.opencv.android.CameraBridgeViewBase; |
||||
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2; |
||||
import org.opencv.videoio.Videoio; |
||||
|
||||
import android.graphics.Bitmap; |
||||
import android.os.Bundle; |
||||
import android.os.Handler; |
||||
import android.util.Log; |
||||
import android.view.MenuItem; |
||||
import android.view.SurfaceView; |
||||
import android.view.WindowManager; |
||||
import android.view.View; |
||||
import android.widget.Button; |
||||
import android.widget.ImageView; |
||||
import android.widget.TextView; |
||||
import android.widget.Toast; |
||||
|
||||
import java.io.File; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
public class RecorderActivity extends CameraActivity implements CvCameraViewListener2, View.OnClickListener { |
||||
private static final String TAG = "OCVSample::Activity"; |
||||
private static final String FILENAME_MP4 = "sample_video1.mp4"; |
||||
private static final String FILENAME_AVI = "sample_video1.avi"; |
||||
|
||||
private static final int STATUS_FINISHED_PLAYBACK = 0; |
||||
private static final int STATUS_PREVIEW = 1; |
||||
private static final int STATUS_RECORDING = 2; |
||||
private static final int STATUS_PLAYING = 3; |
||||
private static final int STATUS_ERROR = 4; |
||||
|
||||
private String mVideoFilename; |
||||
private boolean mUseBuiltInMJPG = false; |
||||
|
||||
private int mStatus = STATUS_FINISHED_PLAYBACK; |
||||
private int mFPS = 30; |
||||
private int mWidth = 0, mHeight = 0; |
||||
|
||||
private CameraBridgeViewBase mOpenCvCameraView; |
||||
private ImageView mImageView; |
||||
private Button mTriggerButton; |
||||
private TextView mStatusTextView; |
||||
Runnable mPlayerThread; |
||||
|
||||
private VideoWriter mVideoWriter = null; |
||||
private VideoCapture mVideoCapture = null; |
||||
private Mat mVideoFrame; |
||||
private Mat mRenderFrame; |
||||
|
||||
public RecorderActivity() { |
||||
Log.i(TAG, "Instantiated new " + this.getClass()); |
||||
} |
||||
|
||||
/** Called when the activity is first created. */ |
||||
@Override |
||||
public void onCreate(Bundle savedInstanceState) { |
||||
Log.i(TAG, "called onCreate"); |
||||
super.onCreate(savedInstanceState); |
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); |
||||
|
||||
setContentView(R.layout.recorder_surface_view); |
||||
|
||||
mStatusTextView = (TextView) findViewById(R.id.textview1); |
||||
mStatusTextView.bringToFront(); |
||||
|
||||
if (OpenCVLoader.initLocal()) { |
||||
Log.i(TAG, "OpenCV loaded successfully"); |
||||
} else { |
||||
Log.e(TAG, "OpenCV initialization failed!"); |
||||
mStatus = STATUS_ERROR; |
||||
mStatusTextView.setText("Error: Can't initialize OpenCV"); |
||||
return; |
||||
} |
||||
|
||||
mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.recorder_activity_java_surface_view); |
||||
mOpenCvCameraView.setVisibility(SurfaceView.GONE); |
||||
mOpenCvCameraView.setCvCameraViewListener(this); |
||||
mOpenCvCameraView.disableView(); |
||||
|
||||
mImageView = (ImageView) findViewById(R.id.image_view); |
||||
|
||||
mTriggerButton = (Button) findViewById(R.id.btn1); |
||||
mTriggerButton.setOnClickListener(this); |
||||
mTriggerButton.bringToFront(); |
||||
|
||||
if (mUseBuiltInMJPG) |
||||
mVideoFilename = getFilesDir() + "/" + FILENAME_AVI; |
||||
else |
||||
mVideoFilename = getFilesDir() + "/" + FILENAME_MP4; |
||||
} |
||||
|
||||
@Override |
||||
public void onPause() |
||||
{ |
||||
Log.d(TAG, "Pause"); |
||||
super.onPause(); |
||||
if (mOpenCvCameraView != null) |
||||
mOpenCvCameraView.disableView(); |
||||
mImageView.setVisibility(SurfaceView.GONE); |
||||
if (mVideoWriter != null) { |
||||
mVideoWriter.release(); |
||||
mVideoWriter = null; |
||||
} |
||||
if (mVideoCapture != null) { |
||||
mVideoCapture.release(); |
||||
mVideoCapture = null; |
||||
} |
||||
mStatus = STATUS_FINISHED_PLAYBACK; |
||||
mStatusTextView.setText("Status: Finished playback"); |
||||
mTriggerButton.setText("Start Camera"); |
||||
|
||||
mVideoFrame.release(); |
||||
mRenderFrame.release(); |
||||
} |
||||
|
||||
@Override |
||||
public void onResume() |
||||
{ |
||||
Log.d(TAG, "onResume"); |
||||
super.onResume(); |
||||
|
||||
mVideoFrame = new Mat(); |
||||
mRenderFrame = new Mat(); |
||||
|
||||
changeStatus(); |
||||
} |
||||
|
||||
@Override |
||||
protected List<? extends CameraBridgeViewBase> getCameraViewList() { |
||||
return Collections.singletonList(mOpenCvCameraView); |
||||
} |
||||
|
||||
public void onDestroy() { |
||||
Log.d(TAG, "called onDestroy"); |
||||
super.onDestroy(); |
||||
if (mOpenCvCameraView != null) |
||||
mOpenCvCameraView.disableView(); |
||||
if (mVideoWriter != null) |
||||
mVideoWriter.release(); |
||||
if (mVideoCapture != null) |
||||
mVideoCapture.release(); |
||||
} |
||||
|
||||
public void onCameraViewStarted(int width, int height) { |
||||
Log.d(TAG, "Camera view started " + String.valueOf(width) + "x" + String.valueOf(height)); |
||||
mWidth = width; |
||||
mHeight = height; |
||||
} |
||||
|
||||
public void onCameraViewStopped() { |
||||
Log.d(TAG, "Camera view stopped"); |
||||
} |
||||
|
||||
public Mat onCameraFrame(CvCameraViewFrame inputFrame) |
||||
{ |
||||
Log.d(TAG, "Camera frame arrived"); |
||||
|
||||
Mat rgbMat = inputFrame.rgba(); |
||||
|
||||
Log.d(TAG, "Size: " + rgbMat.width() + "x" + rgbMat.height()); |
||||
|
||||
if (mVideoWriter != null && mVideoWriter.isOpened()) { |
||||
Imgproc.cvtColor(rgbMat, mVideoFrame, Imgproc.COLOR_RGBA2BGR); |
||||
mVideoWriter.write(mVideoFrame); |
||||
} |
||||
|
||||
return rgbMat; |
||||
} |
||||
|
||||
@Override |
||||
public void onClick(View view) { |
||||
Log.i(TAG,"onClick event"); |
||||
changeStatus(); |
||||
} |
||||
|
||||
public void changeStatus() { |
||||
switch(mStatus) { |
||||
case STATUS_ERROR: |
||||
Toast.makeText(this, "Error", Toast.LENGTH_LONG).show(); |
||||
break; |
||||
case STATUS_FINISHED_PLAYBACK: |
||||
if (!startPreview()) { |
||||
setErrorStatus(); |
||||
break; |
||||
} |
||||
mStatus = STATUS_PREVIEW; |
||||
mStatusTextView.setText("Status: Camera preview"); |
||||
mTriggerButton.setText("Start recording"); |
||||
break; |
||||
case STATUS_PREVIEW: |
||||
if (!startRecording()) { |
||||
setErrorStatus(); |
||||
break; |
||||
} |
||||
mStatus = STATUS_RECORDING; |
||||
mStatusTextView.setText("Status: recording video"); |
||||
mTriggerButton.setText(" Stop and play video"); |
||||
break; |
||||
case STATUS_RECORDING: |
||||
if (!stopRecording()) { |
||||
setErrorStatus(); |
||||
break; |
||||
} |
||||
if (!startPlayback()) { |
||||
setErrorStatus(); |
||||
break; |
||||
} |
||||
mStatus = STATUS_PLAYING; |
||||
mStatusTextView.setText("Status: Playing video"); |
||||
mTriggerButton.setText("Stop playback"); |
||||
break; |
||||
case STATUS_PLAYING: |
||||
if (!stopPlayback()) { |
||||
setErrorStatus(); |
||||
break; |
||||
} |
||||
mStatus = STATUS_FINISHED_PLAYBACK; |
||||
mStatusTextView.setText("Status: Finished playback"); |
||||
mTriggerButton.setText("Start Camera"); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
public void setErrorStatus() { |
||||
mStatus = STATUS_ERROR; |
||||
mStatusTextView.setText("Status: Error"); |
||||
} |
||||
|
||||
public boolean startPreview() { |
||||
mOpenCvCameraView.enableView(); |
||||
mOpenCvCameraView.setVisibility(View.VISIBLE); |
||||
return true; |
||||
} |
||||
|
||||
public boolean startRecording() { |
||||
Log.i(TAG,"Starting recording"); |
||||
|
||||
File file = new File(mVideoFilename); |
||||
file.delete(); |
||||
|
||||
mVideoWriter = new VideoWriter(); |
||||
if (!mUseBuiltInMJPG) { |
||||
mVideoWriter.open(mVideoFilename, Videoio.CAP_ANDROID, VideoWriter.fourcc('H', '2', '6', '4'), mFPS, new Size(mWidth, mHeight)); |
||||
if (!mVideoWriter.isOpened()) { |
||||
Log.i(TAG,"Can't record H264. Switching to MJPG"); |
||||
mUseBuiltInMJPG = true; |
||||
mVideoFilename = getFilesDir() + "/" + FILENAME_AVI; |
||||
} |
||||
} |
||||
|
||||
if (mUseBuiltInMJPG) { |
||||
mVideoWriter.open(mVideoFilename, VideoWriter.fourcc('M', 'J', 'P', 'G'), mFPS, new Size(mWidth, mHeight)); |
||||
} |
||||
|
||||
Log.d(TAG, "Size: " + String.valueOf(mWidth) + "x" + String.valueOf(mHeight)); |
||||
Log.d(TAG, "File: " + mVideoFilename); |
||||
|
||||
if (mVideoWriter.isOpened()) { |
||||
Toast.makeText(this, "Record started to file " + mVideoFilename, Toast.LENGTH_LONG).show(); |
||||
return true; |
||||
} else { |
||||
Toast.makeText(this, "Failed to start a record", Toast.LENGTH_LONG).show(); |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public boolean stopRecording() { |
||||
Log.i(TAG, "Finishing recording"); |
||||
mOpenCvCameraView.disableView(); |
||||
mOpenCvCameraView.setVisibility(SurfaceView.GONE); |
||||
mVideoWriter.release(); |
||||
mVideoWriter = null; |
||||
return true; |
||||
} |
||||
|
||||
public boolean startPlayback() { |
||||
mImageView.setVisibility(SurfaceView.VISIBLE); |
||||
|
||||
if (!mUseBuiltInMJPG){ |
||||
mVideoCapture = new VideoCapture(mVideoFilename, Videoio.CAP_ANDROID); |
||||
} else { |
||||
mVideoCapture = new VideoCapture(mVideoFilename, Videoio.CAP_OPENCV_MJPEG); |
||||
} |
||||
|
||||
if (!mVideoCapture.isOpened()) { |
||||
Log.e(TAG, "Can't open video"); |
||||
Toast.makeText(this, "Can't open file " + mVideoFilename, Toast.LENGTH_SHORT).show(); |
||||
return false; |
||||
} |
||||
|
||||
Toast.makeText(this, "Starting playback from file " + mVideoFilename, Toast.LENGTH_SHORT).show(); |
||||
|
||||
mPlayerThread = new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
if (mVideoCapture == null || !mVideoCapture.isOpened()) { |
||||
return; |
||||
} |
||||
mVideoCapture.read(mVideoFrame); |
||||
if (mVideoFrame.empty()) { |
||||
if (mStatus == STATUS_PLAYING) { |
||||
changeStatus(); |
||||
} |
||||
return; |
||||
} |
||||
// VideoCapture with CAP_ANDROID generates RGB frames instead of BGR
|
||||
// https://github.com/opencv/opencv/issues/24687
|
||||
Imgproc.cvtColor(mVideoFrame, mRenderFrame, mUseBuiltInMJPG ? Imgproc.COLOR_BGR2RGBA: Imgproc.COLOR_RGB2RGBA); |
||||
Bitmap bmp = Bitmap.createBitmap(mRenderFrame.cols(), mRenderFrame.rows(), Bitmap.Config.ARGB_8888); |
||||
Utils.matToBitmap(mRenderFrame, bmp); |
||||
mImageView.setImageBitmap(bmp); |
||||
Handler h = new Handler(); |
||||
h.postDelayed(this, 33); |
||||
} |
||||
}; |
||||
|
||||
mPlayerThread.run(); |
||||
return true; |
||||
} |
||||
|
||||
public boolean stopPlayback() { |
||||
mVideoCapture.release(); |
||||
mVideoCapture = null; |
||||
mImageView.setVisibility(SurfaceView.GONE); |
||||
return true; |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue