From 0fb0e2a77cc1d4483585622ec0b36a3384028759 Mon Sep 17 00:00:00 2001 From: BlueMatthew Date: Mon, 26 Feb 2024 10:24:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0OSD=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E7=9A=84=E4=BC=A0=E9=80=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/cpp/PhoneDevice.cpp | 39 +++++- .../com/xypower/mpapp/MicroPhotoService.java | 6 +- .../mpapp/video/AutoFitSurfaceView.java | 57 +++++++++ .../xypower/mpapp/video/VideoFragment.java | 112 +++++++++++++++++- .../main/res/layout-land/activity_main.xml | 14 +-- app/src/main/res/layout/activity_video.xml | 2 +- app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 2 +- 8 files changed, 217 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/com/xypower/mpapp/video/AutoFitSurfaceView.java diff --git a/app/src/main/cpp/PhoneDevice.cpp b/app/src/main/cpp/PhoneDevice.cpp index ad0d9086..4452ef9b 100644 --- a/app/src/main/cpp/PhoneDevice.cpp +++ b/app/src/main/cpp/PhoneDevice.cpp @@ -186,7 +186,7 @@ CPhoneDevice::CPhoneDevice(JavaVM* vm, jobject service, const std::string& appPa mRegisterHeartbeatMid = env->GetMethodID(classService, "registerHeartbeatTimer", "(I)V"); mUpdateTimeMid = env->GetMethodID(classService, "updateTime", "(J)Z"); mUpdateCaptureScheduleMid = env->GetMethodID(classService, "updateCaptureSchedule", "(J)Z"); - mStartRecordingMid = env->GetMethodID(classService, "startRecording", "(IJIIIII)V"); + mStartRecordingMid = env->GetMethodID(classService, "startRecording", "(IJIIIIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); mRequestWakelockMid = env->GetMethodID(classService, "requestWakelock", "(Ljava/lang/String;J)V"); mReleaseWakelockMid = env->GetMethodID(classService, "releaseWakelock", "(Ljava/lang/String;)V"); @@ -881,8 +881,43 @@ bool CPhoneDevice::TakePhoto(const IDevice::PHOTO_INFO& photoInfo, const vector< ALOGE("Failed to get JNI Env"); return false; } + + jstring leftTopOSD = NULL; + jstring rightTopOSD = NULL; + jstring rightBottomOSD = NULL; + jstring leftBottomOSD = NULL; + + for (vector::const_iterator it = mOsds.cbegin(); it != mOsds.cend(); ++it) + { + if (it->text.empty()) + { + continue; + } + switch (it->alignment) + { + case OSD_ALIGNMENT_TOP_LEFT: + leftTopOSD = env->NewStringUTF(it->text.c_str()); + break; + case OSD_ALIGNMENT_TOP_RIGHT: + rightTopOSD = env->NewStringUTF(it->text.c_str()); + break; + case OSD_ALIGNMENT_BOTTOM_RIGHT: + rightBottomOSD = env->NewStringUTF(it->text.c_str()); + break; + case OSD_ALIGNMENT_BOTTOM_LEFT: + leftBottomOSD = env->NewStringUTF(it->text.c_str()); + break; + } + } + int orientation = mPhotoInfo.orientation == 0 ? -1 : (mPhotoInfo.orientation - 1) * 90; - env->CallVoidMethod(m_javaService, mStartRecordingMid, mPhotoInfo.cameraId, (unsigned long)mPhotoInfo.photoId, mPhotoInfo.duration, mPhotoInfo.width, mPhotoInfo.height, mPhotoInfo.duration, orientation); + env->CallVoidMethod(m_javaService, mStartRecordingMid, mPhotoInfo.cameraId, (unsigned long)mPhotoInfo.photoId, mPhotoInfo.duration, mPhotoInfo.width, mPhotoInfo.height, + mPhotoInfo.duration, orientation, leftTopOSD, rightTopOSD, rightBottomOSD, leftBottomOSD); + + if (leftTopOSD) env->DeleteLocalRef(leftTopOSD); + if (rightTopOSD) env->DeleteLocalRef(rightTopOSD); + if (rightBottomOSD) env->DeleteLocalRef(rightBottomOSD); + if (leftBottomOSD) env->DeleteLocalRef(leftBottomOSD); if (didAttachThread) { diff --git a/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java b/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java index e8274505..132fe937 100644 --- a/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java +++ b/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java @@ -484,7 +484,7 @@ public class MicroPhotoService extends Service { registerPhotoTimer(getApplicationContext(), scheduleTime, scheduleTime, timeout, schedules); } - public void startRecording(int cameraId, long videoId, int duration, int width, int height, int quality, int orientation) { + public void startRecording(int cameraId, long videoId, int duration, int width, int height, int quality, int orientation, String leftTopOsd, String rightTopOsd, String rightBottomOsd, String leftBottomOsd) { Context context = getApplicationContext(); Intent intent = new Intent(this, VideoActivity.class); @@ -495,6 +495,10 @@ public class MicroPhotoService extends Service { intent.putExtra("height", height); intent.putExtra("quality", quality); intent.putExtra("orientation", orientation); + intent.putExtra("leftTopOsd", leftTopOsd); + intent.putExtra("rightTopOsd", rightTopOsd); + intent.putExtra("rightBottomOsd", rightBottomOsd); + intent.putExtra("leftBottomOsd", leftBottomOsd); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent); diff --git a/app/src/main/java/com/xypower/mpapp/video/AutoFitSurfaceView.java b/app/src/main/java/com/xypower/mpapp/video/AutoFitSurfaceView.java new file mode 100644 index 00000000..14e61e80 --- /dev/null +++ b/app/src/main/java/com/xypower/mpapp/video/AutoFitSurfaceView.java @@ -0,0 +1,57 @@ +package com.xypower.mpapp.video; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.SurfaceView; + +public class AutoFitSurfaceView extends SurfaceView { + + private float mAspectRatio; + + public AutoFitSurfaceView(Context context) { + super(context); + } + + public AutoFitSurfaceView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AutoFitSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setAspectRatio(int width, int height){ + mAspectRatio = (float)width / height; + getHolder().setFixedSize(width, height); + requestLayout(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + if (mAspectRatio == 0) { + setMeasuredDimension(width, height); + }else { + int newW,newH; + float actualRatio; + if (width > height) { + actualRatio = mAspectRatio; + }else { + actualRatio = 1 / mAspectRatio; + } + + if (width < height * actualRatio){ + newH = height; + newW = (int) (height * actualRatio); + }else { + newW = width; + newH = (int) (width / actualRatio); + } + setMeasuredDimension(newW, newH); + + } + } +} + diff --git a/app/src/main/java/com/xypower/mpapp/video/VideoFragment.java b/app/src/main/java/com/xypower/mpapp/video/VideoFragment.java index 4599c8f0..a0e48727 100644 --- a/app/src/main/java/com/xypower/mpapp/video/VideoFragment.java +++ b/app/src/main/java/com/xypower/mpapp/video/VideoFragment.java @@ -9,9 +9,16 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.Rect; import android.graphics.RectF; import android.graphics.SurfaceTexture; +import android.graphics.drawable.BitmapDrawable; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; @@ -36,11 +43,13 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; +import android.text.TextUtils; import android.util.Log; import android.util.Size; import android.util.SparseIntArray; import android.view.LayoutInflater; import android.view.Surface; +import android.view.SurfaceHolder; import android.view.TextureView; import android.view.View; import android.view.ViewGroup; @@ -52,10 +61,13 @@ import com.xypower.mpapp.R; import java.io.File; import java.io.IOException; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.Date; import java.util.List; +import java.util.Locale; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -97,10 +109,6 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Med INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0); } - private int mCameraId; - private long mVideoId = 0; - private int mDuration = 0; - /** * An {@link AutoFitTextureView} for camera preview. */ @@ -133,6 +141,7 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Med public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { openCamera(width, height); + mMainHandler.postDelayed(mWatermarkRunnable, 16); } @Override @@ -143,6 +152,7 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Med @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { + mMainHandler.removeCallbacks(mWatermarkRunnable); return true; } @@ -162,7 +172,22 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Med */ private Size mVideoSize; + private int mCameraId; + private long mVideoId = 0; + private int mDuration = 0; + private int mOrientation = -1; + private String mLeftTopOsd = null; + private String mRightTopOsd = null; + private String mRightBottomOsd = null; + private String mLeftBottomOsd = null; + + private Bitmap mWatermarkBitmap = null; + private Paint mWatermarkPaint = null; + + private Runnable mWatermarkRunnable = null; + + private SurfaceHolder mSurfaceHolder = null; /** * MediaRecorder @@ -357,9 +382,37 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Med int width = argument.getInt("width", 0); int height = argument.getInt("height", 0); mOrientation = argument.getInt("orientation", -1); + mLeftTopOsd = argument.getString("leftTopOsd", null); + mLeftBottomOsd = argument.getString("leftBottomOsd", null); + mRightBottomOsd = argument.getString("rightBottomOsd", null); + mLeftBottomOsd = argument.getString("leftBottomOsd", null); + if (width > 0 && height > 0) { mVideoSize = new Size(width, height); } + + if (!TextUtils.isEmpty(mLeftTopOsd) || !TextUtils.isEmpty(mLeftTopOsd) || !TextUtils.isEmpty(mLeftTopOsd) || !TextUtils.isEmpty(mLeftTopOsd)) { + mWatermarkRunnable = new Runnable() { + @Override + public void run() { + updateWatermark(); + + long ms = System.currentTimeMillis() % 1000; + if (ms == 0) { + ms = 1000; + } + mMainHandler.postDelayed(this, ms); + } + }; + + mWatermarkPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mWatermarkPaint.setColor(Color.WHITE); + mWatermarkPaint.setTextSize(32); + + mWatermarkBitmap = Bitmap.createBitmap(mVideoSize.getWidth(), mVideoSize.getHeight(), Bitmap.Config.ARGB_8888); + + // mMainHandler.post(mWatermarkRunnable); + } } Log.i(TAG, "Recv recording request CameraId=" + mCameraId + " videoId=" + Long.toString(mVideoId)); @@ -556,6 +609,7 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Med try { closePreviewSession(); SurfaceTexture texture = mTextureView.getSurfaceTexture(); + assert texture != null; texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); @@ -800,6 +854,56 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Med startPreview(); } + private void updateWatermark() { + Canvas canvas = new Canvas(mWatermarkBitmap); + canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); + + Date date = new Date(); + String ts = Long.toString(date.getTime() / 1000); + + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); + String dateStr = simpleDateFormat.format(date); + + String osd = null; + if (!TextUtils.isEmpty(mLeftTopOsd)) { + osd = mLeftTopOsd.replace("%%DATETIME%%", dateStr).replace("%%TS%%", ts); + canvas.drawText(osd, 20, 20, mWatermarkPaint); + } + + if (!TextUtils.isEmpty(mRightTopOsd)) { + osd = mRightTopOsd.replace("%%DATETIME%%", dateStr).replace("%%TS%%", ts); + Rect rect = new Rect(); + mWatermarkPaint.getTextBounds(osd, 0, osd.length(), rect); + + canvas.drawText(osd, mVideoSize.getWidth() - rect.width() - 20, 20, mWatermarkPaint); + } + + if (!TextUtils.isEmpty(mRightBottomOsd)) { + osd = mRightBottomOsd.replace("$$DATETIME%%", dateStr).replace("%%TS%%", ts); + Rect rect = new Rect(); + mWatermarkPaint.getTextBounds(osd, 0, osd.length(), rect); + + canvas.drawText(osd, mVideoSize.getWidth() - rect.width() - 20, mVideoSize.getHeight() - rect.height() - 20, mWatermarkPaint); + } + + if (!TextUtils.isEmpty(mLeftBottomOsd)) { + osd = mLeftBottomOsd.replace("$$DATETIME%%", dateStr).replace("%%TS%%", ts); + Rect rect = new Rect(); + mWatermarkPaint.getTextBounds(osd, 0, osd.length(), rect); + + canvas.drawText(osd, 20, mVideoSize.getHeight() - rect.height() - 20, mWatermarkPaint); + } + + // mTextureView.setForeground(new BitmapDrawable(mWatermarkBitmap)); + + boolean isAvailable = mTextureView.isAvailable(); + Canvas textureCanvas = mTextureView.lockCanvas(); + if (textureCanvas != null) { + textureCanvas.drawBitmap(mWatermarkBitmap, 0, 0, null); + } + mTextureView.unlockCanvasAndPost(textureCanvas); + } + /** * Compares two {@code Size}s based on their areas. */ diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 02e4696b..2f634c7c 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -22,14 +22,13 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/activity_horizontal_margin_small" - android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginTop="@dimen/activity_vertical_margin_small" android:ems="10" android:imeOptions="actionDone" android:inputType="text" android:maxLines="1" android:lines="1" android:singleLine="true" - android:text="XY-ANDROIDSIM-001" app:layout_constraintLeft_toRightOf="@+id/textViewCmdId" app:layout_constraintTop_toTopOf="parent" /> @@ -62,6 +61,7 @@ android:inputType="text" android:imeOptions="actionDone" android:hint="@string/main_server" + android:layout_marginTop="@dimen/activity_vertical_spacing_small" app:layout_constraintStart_toStartOf="@+id/cmdid" app:layout_constraintTop_toBottomOf="@+id/cmdid" /> @@ -101,7 +101,7 @@ android:layout_width="48dp" android:layout_height="wrap_content" android:layout_marginStart="@dimen/activity_horizontal_margin" - android:layout_marginTop="0dp" + android:layout_marginTop="@dimen/activity_vertical_spacing_small" android:ems="10" android:inputType="none|number" android:imeOptions="actionDone" @@ -151,7 +151,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/activity_horizontal_margin" - android:layout_marginTop="@dimen/activity_vertical_margin_small" + android:layout_marginTop="@dimen/activity_vertical_spacing_small" android:text="Start" android:minWidth="@dimen/activity_btn_min_width" android:minHeight="@dimen/activity_btn_min_height" @@ -199,7 +199,7 @@ android:id="@+id/btnTakePhoto" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginTop="@dimen/activity_vertical_spacing_small" android:layout_marginStart="@dimen/activity_horizontal_margin" android:minWidth="@dimen/activity_btn_min_width" android:minHeight="@dimen/activity_btn_min_height" @@ -245,7 +245,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/activity_horizontal_margin" - android:layout_marginTop="@dimen/activity_vertical_margin_small" + android:layout_marginTop="@dimen/activity_vertical_spacing_small" android:minWidth="@dimen/activity_btn_min_width" android:minHeight="@dimen/activity_btn_min_height" android:text="@string/btn_tv_ch1" @@ -291,7 +291,7 @@ android:layout_height="wrap_content" android:text="@string/main_send_hb" android:layout_marginStart="@dimen/activity_horizontal_margin" - android:layout_marginTop="@dimen/activity_vertical_margin_small" + android:layout_marginTop="@dimen/activity_vertical_spacing_small" android:minWidth="@dimen/activity_btn_min_width" android:minHeight="@dimen/activity_btn_min_height" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/activity_video.xml b/app/src/main/res/layout/activity_video.xml index 4fff021b..381d9b6d 100644 --- a/app/src/main/res/layout/activity_video.xml +++ b/app/src/main/res/layout/activity_video.xml @@ -5,4 +5,4 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="#000" - tools:context="com.example.android.camera2basic.CameraActivity" /> \ No newline at end of file + tools:context="com.xypower.mpapp.video.VideoActivity" /> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index dcd3a378..ac6c520a 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -6,6 +6,7 @@ 4dp 8dp 8dp + 2dp 72dp 28dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4d0baef7..27fd50bf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,7 +39,7 @@ 短视频高 压缩率(50-100) USB Camera - 短视频时长(分) + 短视频时长(秒) Hello blank fragment Record