Android开发:Camera2+MediaRecorder录制视频后上传到阿里云VOD

文章目录

  • 版权声明
  • 前言
    • 1.Camera1和Camera2的区别
    • 2.为什么选择Camera2?
  • 一、应用Camera2+MediaPlayer实现拍摄功能
    • 引入所需权限
    • 构建UI界面的XML
    • Activity中的代码部分
  • 二、在上述界面录制结束后点击跳转新的界面进行视频播放
    • 构建播放界面部分的XML
    • Activity的代码
        • 上述代码中的注释部分为上传功能留白。
  • 三.视频上传功能的实现
      • 0.集成Java上传SDK
      • 1.application.yml的配置
      • 2.Controller层
      • 3.Service
      • 4.Impl
      • 5.工具类
        • 在上述Android代码的留白部分加入Okhttp请求
  • 效果


版权声明

1.在视频录制阶段用到的Camera2+MediaRecorder技术借鉴了下方博主的文章,给出原文链接和版权声明:
在这里插入图片描述
原文链接
2.在视频播放阶段用到的MediaPlayer技术借鉴了下方博主的文章,给出原文连接:
在这里插入图片描述
原文链接

前言

1.Camera1和Camera2的区别

Camera1和Camera2在多个方面存在显著的区别。

首先,从功能角度来看,Camera1主要支持一次拍摄一张图片,而Camera2则支持一次拍摄多张图片,甚至可以是多张格式和尺寸不同的图片。例如,使用Camera2,你可以同时拍摄一张1440x1080的JPEG图片和一张全尺寸的RAW图片。

其次,Camera2提供了更多的手动设置选项,如曝光时间、ISO感光度、焦距等,这使得用户能够更精细地控制拍摄过程。此外,Camera2还支持RAW图像捕获和高速连拍模式等新功能,进一步丰富了其拍摄能力。

在性能优化方面,Camera2 API支持并行拍摄和预览,这使得在同时进行多个操作时表现更好。相比之下,Camera1可能在处理并行任务时表现稍显不足。

在检查相机信息方面,Camera2引入了CameraCharacteristics实例,它专门提供相机信息,使得用户可以在不开启相机的前提下检查几乎所有的相机信息。而Camera1则无法在开机相机之前检查详细的相机信息。

从实现逻辑上看,Camera1的逻辑是面向Camera对象的,所有的行为都是基于这个对象的方法的。如果Camera对象本身存在问题,是无法感知的,只能在调用方法时抛出异常。而Camera2的逻辑则是面向状态的,无论是设备还是会话(Session),任何状态的改变都可以做出相应的处理。这种逻辑的不同使得Camera2在处理各种情况时更为灵活和高效。

此外,Camera2还是现在的多摄像头管理框架,可以更好地管理和协调多个摄像头的工作。而Camera1主要是以前的前后摄像头的处理方式。

综上所述,Camera2在功能、性能、信息检查以及实现逻辑等方面相较于Camera1有明显的提升和优化,更适合现代摄影和多媒体应用的需求。然而,具体使用哪种API还需要根据具体的应用场景和需求来决定。

2.为什么选择Camera2?

Camera1(通常指的是较旧的相机API)在功能和性能上相较于Camera2(即新的相机API)确实有所不足。Camera2提供了更多的手动设置选项、支持并行拍摄和预览、允许在不开启相机的前提下检查相机信息等。此外,Camera2还是现在的多摄像头管理框架,可以更好地管理和协调多个摄像头的工作。
较新版本的API可能不再支持Camera1中的一些方法。随着技术的不断发展和更新,新的API版本往往会引入新的功能和改进,同时可能会淘汰或替换旧的方法。
较新版本的API不再支持Camera1中的方法可能会涉及多个方面,具体取决于API的更新内容和目标设备的兼容性。以下是一些常见的情况和可能不再支持的方法:

相机打开和关闭
openCamera(int):用于打开指定ID的相机。
release():用于释放相机资源。

相机参数设置
getParameters() 和 setParameters(Camera.Parameters):用于获取和设置相机的参数,如闪光灯模式、预览大小、图片格式等。
setDisplayOrientation(int):用于设置相机预览的显示方向。

预览和拍摄
setPreviewDisplay(SurfaceHolder):用于设置预览显示的Surface。
startPreview() 和 stopPreview():用于开始和停止预览。
takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback):用于拍摄照片并接收回调。

焦点和缩放
autoFocus(Camera.AutoFocusCallback):用于自动对焦。
startSmoothZoom(int) 和 stopSmoothZoom():用于平滑缩放。

事件监听
setPreviewCallback(Camera.PreviewCallback):用于接收预览帧的回调。
setErrorCallback(Camera.ErrorCallback):用于接收相机错误的回调。

其他
unlock() 和 reconnect():与媒体录制器配合使用,用于在录制视频时解锁和重新连接相机。
需要注意的是,随着Android版本的更新和Camera2 API的引入,许多Camera1的方法被新的API所替代或重新设计。Camera2 API提供了更灵活、更强大的相机功能,并支持更多的手动控制和高级特性。因此,在开发新应用或更新现有应用时,建议迁移到Camera2 API以利用最新的功能和性能优势。

一、应用Camera2+MediaPlayer实现拍摄功能

引入所需权限

 <uses-permission android:name="android.permission.INTERNET" /> <!-- 获取网络状态,根据网络状态切换进行数据请求网络转换 --><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><!-- 读取外置存储 --><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!-- 写外置存储 --><uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.CAMERA" /><uses-feature android:name="android.hardware.camera" /><uses-feature android:name="android.hardware.camera.autofocus" />

构建UI界面的XML

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".submit.Camera2"><TextureViewandroid:id="@+id/textureView"android:layout_width="match_parent"android:layout_height="match_parent"/><Buttonandroid:id="@+id/btn_finish"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="结束"app:layout_constraintTop_toTopOf="parent"app:layout_constraintRight_toRightOf="parent"/><Buttonandroid:id="@+id/btn_start"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="开始"app:layout_constraintTop_toTopOf="parent"app:layout_constraintLeft_toLeftOf="parent"/></androidx.constraintlayout.widget.ConstraintLayout>

Activity中的代码部分

package com.example.travelassistant.submit;import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureFailure;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.widget.Button;import com.example.travelassistant.R;import java.io.File;
import java.io.IOException;
import java.util.Arrays;public class Camera2 extends AppCompatActivity {private static final String TAG = Camera2.class.getSimpleName();private Button mBtnStatr,mBtnFinish;private TextureView mTextureView;private CameraManager mCameraManager;private CameraDevice mCameraDevice;private CameraCaptureSession mCameraCaptureSession;private CameraDevice.StateCallback mCameraDeviceStateCallback;private CameraCaptureSession.StateCallback mSessionStateCallback;private CameraCaptureSession.CaptureCallback mSessionCaptureCallback;private CaptureRequest.Builder mPreviewCaptureRequest;private CaptureRequest.Builder mRecorderCaptureRequest;private MediaRecorder mMediaRecorder;private String mCurrentSelectCamera;private Handler mChildHandler;@SuppressLint("MissingInflatedId")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_camera2);if(ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED){ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.RECORD_AUDIO}, 1);}if(ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED){ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.CAMERA}, 1);}if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);}mTextureView = findViewById(R.id.textureView);mBtnStatr = findViewById(R.id.btn_start);mBtnFinish = findViewById(R.id.btn_finish);initClickListener();initChildHandler();initTextureViewStateListener();initMediaRecorder();initCameraDeviceStateCallback();initSessionStateCallback();initSessionCaptureCallback();}private void initClickListener(){mBtnStatr.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {config();startRecorder();}});mBtnFinish.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {stopRecorder();}});}/*** 初始化TextureView的纹理生成监听,只有纹理生成准备好了。我们才能去进行摄像头的初始化工作让TextureView接收摄像头预览画面*/private void initTextureViewStateListener(){mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {@Overridepublic void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {//可以使用纹理initCameraManager();selectCamera();openCamera();}@Overridepublic void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {//纹理尺寸变化}@Overridepublic boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {//纹理被销毁return false;}@Overridepublic void onSurfaceTextureUpdated(SurfaceTexture surface) {//纹理更新}});}/*** 初始化子线程Handler,操作Camera2需要一个子线程的Handler*/private void initChildHandler(){HandlerThread handlerThread = new HandlerThread("Camera2Demo");handlerThread.start();mChildHandler = new Handler(handlerThread.getLooper());}/*** 初始化MediaRecorder*/private void initMediaRecorder(){mMediaRecorder = new MediaRecorder();}/*** 配置录制视频相关数据*/private void configMediaRecorder(){File file = new File(getExternalCacheDir(),"demo.mp4");if (file.exists()){file.delete();}mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);//设置音频来源mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);//设置视频来源mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);//设置输出格式mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);//设置音频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择AACmMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);//设置视频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择H264mMediaRecorder.setVideoEncodingBitRate(8*1024*1920);//设置比特率 一般是 1*分辨率 到 10*分辨率 之间波动。比特率越大视频越清晰但是视频文件也越大。mMediaRecorder.setVideoFrameRate(30);//设置帧数 选择 30即可, 过大帧数也会让视频文件更大当然也会更流畅,但是没有多少实际提升。人眼极限也就30帧了。Size size = getMatchingSize2();mMediaRecorder.setVideoSize(size.getWidth(),size.getHeight());mMediaRecorder.setOrientationHint(90);Surface surface = new Surface(mTextureView.getSurfaceTexture());mMediaRecorder.setPreviewDisplay(surface);mMediaRecorder.setOutputFile(file.getAbsolutePath());try {mMediaRecorder.prepare();} catch (IOException e) {e.printStackTrace();}}/*** 重新配置录制视频时的CameraCaptureSession*/private void config(){try {mCameraCaptureSession.stopRepeating();//停止预览,准备切换到录制视频mCameraCaptureSession.close();//关闭预览的会话,需要重新创建录制视频的会话mCameraCaptureSession = null;} catch (CameraAccessException e) {e.printStackTrace();}configMediaRecorder();Size cameraSize = getMatchingSize2();SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();surfaceTexture.setDefaultBufferSize(cameraSize.getWidth(),cameraSize.getHeight());Surface previewSurface = new Surface(surfaceTexture);Surface recorderSurface = mMediaRecorder.getSurface();//从获取录制视频需要的Surfacetry {mPreviewCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);mPreviewCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);mPreviewCaptureRequest.addTarget(previewSurface);mPreviewCaptureRequest.addTarget(recorderSurface);//请注意这里设置了Arrays.asList(previewSurface,recorderSurface) 2个Surface,很好理解录制视频也需要有画面预览,第一个是预览的Surface,第二个是录制视频使用的SurfacemCameraDevice.createCaptureSession(Arrays.asList(previewSurface,recorderSurface),mSessionStateCallback,mChildHandler);} catch (CameraAccessException e) {e.printStackTrace();}}/*** 开始录制视频*/private void startRecorder(){mMediaRecorder.start();}/*** 暂停录制视频(暂停后视频文件会自动保存)*/private void stopRecorder(){mMediaRecorder.stop();mMediaRecorder.reset();Intent intent = new Intent(Camera2.this, PlayVideoActivity.class);startActivity(intent);}/*** 初始化Camera2的相机管理,CameraManager用于获取摄像头分辨率,摄像头方向,摄像头id与打开摄像头的工作*/private void initCameraManager(){mCameraManager = (CameraManager)getSystemService(Context.CAMERA_SERVICE);}/*** 选择一颗我们需要使用的摄像头,主要是选择使用前摄还是后摄或者是外接摄像头*/private void selectCamera(){if (mCameraManager != null) {Log.e(TAG, "selectCamera: CameraManager is null");}try {String[] cameraIdList = mCameraManager.getCameraIdList();   //获取当前设备的全部摄像头id集合if (cameraIdList.length == 0){Log.e(TAG, "selectCamera: cameraIdList length is 0");}for (String cameraId : cameraIdList){ //遍历所有摄像头CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraId);//得到当前id的摄像头描述特征Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING); //获取摄像头的方向特征信息if (facing == CameraCharacteristics.LENS_FACING_BACK){ //这里选择了后摄像头mCurrentSelectCamera = cameraId;}}} catch (CameraAccessException e) {e.printStackTrace();}}private void initCameraDeviceStateCallback(){mCameraDeviceStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {//摄像头被打开try {mCameraDevice = camera;Size cameraSize = getMatchingSize2();//计算获取需要的摄像头分辨率SurfaceTexture surfaceTexture = mTextureView.getSurfaceTexture();//得到纹理surfaceTexture.setDefaultBufferSize(cameraSize.getWidth(),cameraSize.getHeight());Surface previewSurface = new Surface(surfaceTexture);mPreviewCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);mPreviewCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);mPreviewCaptureRequest.addTarget(previewSurface);mCameraDevice.createCaptureSession(Arrays.asList(previewSurface),mSessionStateCallback,mChildHandler);//创建数据捕获会话,用于摄像头画面预览,这里需要等待mSessionStateCallback回调} catch (CameraAccessException e) {e.printStackTrace();}}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {//摄像头断开}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {//异常}};}private void initSessionStateCallback(){mSessionStateCallback = new CameraCaptureSession.StateCallback() {@Overridepublic void onConfigured(@NonNull CameraCaptureSession session) {mCameraCaptureSession = session;try {//执行重复获取数据请求,等于一直获取数据呈现预览画面,mSessionCaptureCallback会返回此次操作的信息回调mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest.build(),mSessionCaptureCallback,mChildHandler);} catch (CameraAccessException e) {e.printStackTrace();}}@Overridepublic void onConfigureFailed(@NonNull CameraCaptureSession session) {}};}private void initSessionCaptureCallback(){mSessionCaptureCallback=new CameraCaptureSession.CaptureCallback() {@Overridepublic void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {super.onCaptureStarted(session, request, timestamp, frameNumber);}@Overridepublic void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {super.onCaptureProgressed(session, request, partialResult);}@Overridepublic void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {super.onCaptureCompleted(session, request, result);}@Overridepublic void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {super.onCaptureFailed(session, request, failure);}};}/*** 打开摄像头,这里打开摄像头后,我们需要等待mCameraDeviceStateCallback的回调*/@SuppressLint("MissingPermission")private void openCamera(){try {mCameraManager.openCamera(mCurrentSelectCamera,mCameraDeviceStateCallback,mChildHandler);} catch (CameraAccessException e) {e.printStackTrace();}}/*** 计算需要的使用的摄像头分辨率* @return*/private Size getMatchingSize2(){Size selectSize = null;try {CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCurrentSelectCamera);StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); //因为我这里是将预览铺满屏幕,所以直接获取屏幕分辨率int deviceWidth = displayMetrics.widthPixels; //屏幕分辨率宽int deviceHeigh = displayMetrics.heightPixels; //屏幕分辨率高Log.e(TAG, "getMatchingSize2: 屏幕密度宽度="+deviceWidth);Log.e(TAG, "getMatchingSize2: 屏幕密度高度="+deviceHeigh );/*** 循环40次,让宽度范围从最小逐步增加,找到最符合屏幕宽度的分辨率,* 你要是不放心那就增加循环,肯定会找到一个分辨率,不会出现此方法返回一个null的Size的情况* ,但是循环越大后获取的分辨率就越不匹配*/Integer s=sizes.length;selectSize=sizes[s-1];} catch (CameraAccessException e) {e.printStackTrace();}Log.e(TAG, "getMatchingSize2: 选择的分辨率宽度="+selectSize.getWidth());Log.e(TAG, "getMatchingSize2: 选择的分辨率高度="+selectSize.getHeight());return selectSize;}}

二、在上述界面录制结束后点击跳转新的界面进行视频播放

构建播放界面部分的XML

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".submit.PlayVideoActivity"><SurfaceViewandroid:id="@+id/surfaceView"android:layout_weight="10"android:layout_width="wrap_content"android:layout_height="wrap_content"android:rotation="90"/><!--水平线性布局--><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_weight="1"android:orientation="horizontal"><!--播放按钮--><Buttonandroid:id="@+id/play1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:text="播放"/><!--暂停按钮--><Buttonandroid:id="@+id/pause1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:text="取消" /><!--停止按钮--><Buttonandroid:id="@+id/stop1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:text="上传" /></LinearLayout></LinearLayout>

Activity的代码

package com.example.travelassistant.submit;import androidx.appcompat.app.AppCompatActivity;import android.media.AudioManager;
import android.media.MediaMetadataRetriever;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.Toast;import com.example.travelassistant.R;
import com.example.travelassistant.utils.AsyncResponse;
import com.example.travelassistant.utils.SendRequest;import java.io.File;
import java.io.IOException;public class PlayVideoActivity extends AppCompatActivity {private SurfaceView surfaceView;private Button play, pause, stop;private MediaPlayer mediaPlayer;private SurfaceHolder surfaceHolder;private boolean noPlay = true;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_play_video);getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);play = (Button) findViewById(R.id.play1);pause = (Button) findViewById(R.id.pause1);stop = (Button) findViewById(R.id.stop1);surfaceView = (SurfaceView) findViewById(R.id.surfaceView);surfaceHolder = surfaceView.getHolder();pause.setEnabled(false);stop.setEnabled(false);/*** 实现播放功能*/play.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (noPlay) {play();noPlay = false;} else {mediaPlayer.start();}}});/*** 实现取消功能*/pause.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {mediaPlayer.stop();mediaPlayer.release();//返回主页面if (mediaPlayer.isPlaying()) {mediaPlayer.pause();}}});/*** 实现上传功能*/stop.setOnClickListener(new View.OnClickListener(){@Overridepublic void onClick(View v) {if (mediaPlayer.isPlaying()) {mediaPlayer.stop();mediaPlayer.release();noPlay = true;pause.setEnabled(false);stop.setEnabled(false);}File file=new File(getExternalCacheDir()+"/demo.mp4");if (!file.exists()) {try {file.createNewFile();} catch (IOException e) {e.printStackTrace();}}//这个位置实现视频上传的功能@Overridepublic void processFailure() {}});}});}/*** 创建play()方法,在该方法中实现视频的播放功能*/public void play() {mediaPlayer = new MediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);mediaPlayer.setDisplay(surfaceHolder);try {MediaMetadataRetriever retriever = new MediaMetadataRetriever();retriever.setDataSource(getExternalCacheDir()+"/demo.mp4");String rotation = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);System.out.println(rotation);mediaPlayer.setDataSource(getExternalCacheDir()+"/demo.mp4");System.out.println(getExternalCacheDir()+"/demo.mp4");// mediaPlayer.setDataSource(Environment.getExternalStorageDirectory() + "/DCIM/Camera/video.mp4");mediaPlayer.prepare();} catch (Exception e) {e.printStackTrace();}mediaPlayer.start();pause.setEnabled(true);stop.setEnabled(true);// 为MediaPlayer对象添加完成事件监听器mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {@Overridepublic void onCompletion(MediaPlayer mp) {Toast.makeText(PlayVideoActivity.this, "视频播放完毕!", Toast.LENGTH_SHORT).show();}});}/*** 当前Activity销毁时,停止正在播放的视频,并释放MediaPlayer所占用的资源*/@Overrideprotected void onDestroy() {super.onDestroy();if (mediaPlayer != null) {if (mediaPlayer.isPlaying()) {mediaPlayer.stop();}// Activity销毁时停止播放,释放资源。不做这个操作,即使退出还是能听到视频播放的声音mediaPlayer.release();}}
}
上述代码中的注释部分为上传功能留白。

三.视频上传功能的实现

在实现视频上传到阿里云VOD功能时,有两种思路:
1.因为是在Android客户端上实现此功能,可以采取客户端的方式,可以参考阿里云VOD的开发文档
Android客户端上传文件
在这里插入图片描述
2.服务端开发一个上传的接口,Android应用Okhttp向服务端发送上传文件的请求
本文主要采取了第二种思路,接下来具体介绍如何实现

0.集成Java上传SDK

根据官方文档的提示,引入相关依赖
在这里插入图片描述
下载Java语言的SDK和Demo将其中的aliyun-java-vod-upload-1.4.15.jar放到模块的resources包下
如下图所示:
在这里插入图片描述
添加依赖

   <dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-core</artifactId><version>4.5.1</version></dependency><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.10.2</version></dependency><dependency><groupId>com.aliyun</groupId><artifactId>aliyun-java-sdk-vod</artifactId><version>2.16.11</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.68.noneautotype</version></dependency><dependency><groupId>org.json</groupId><artifactId>json</artifactId><version>20170516</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.2</version></dependency><dependency><groupId>com.aliyun.vod</groupId><artifactId>upload</artifactId><version>1.4.15</version><scope>system</scope><systemPath>${project.basedir}/src/main/resources/aliyun-java-vod-upload-1.4.15.jar</systemPath></dependency>

根据文档文档的提示:接下来实现视频文件的上传,并将视频中的某一帧作为封面图片

1.application.yml的配置

在这里插入图片描述
1和2中要从官网中获取 3表示上传文件 这里要设置的大一点
在这里插入图片描述

2.Controller层

//上传视频的同时将视频的某一帧作为封面上传@ApiOperation("上传视频并取某帧作为视频的封面")@PostMapping("upload")public Map<String,Object> uploadVideoAndImage(@RequestParam("file") MultipartFile multipartFile){String[] strings=vodService.uploadVideoAndImage(multipartFile);HashMap<String, Object> map = new HashMap<>();map.put("code","0");map.put("message","成功");map.put("data",strings);System.out.println(strings);return map;}

3.Service

public interface VodService {String[] uploadVideoAndImage(MultipartFile multipartFile);}

4.Impl

@Overridepublic String[] uploadVideoAndImage(MultipartFile multipartFile) {String[] strings = new String[3];try {InputStream inputStream = multipartFile.getInputStream();final int fifthFrame= 10;FFmpegFrameGrabber grabber;grabber = new FFmpegFrameGrabber(inputStream);grabber.start();// 视频总帧数int videoLength = grabber.getLengthInFrames();Frame frame = null;int i = 0;while (i < videoLength) {// 过滤前5帧,因为前5帧可能是全黑的frame = grabber.grabFrame();if ((i > fifthFrame) && (frame.image != null)) {break;}i++;}Java2DFrameConverter converter = new Java2DFrameConverter();// 绘制图片BufferedImage bi = converter.getBufferedImage(frame);BufferedImage rotatedImage = ImageRotation.rotateImage(bi, 90);grabber.stop();grabber.close();//将图片转换成输入流ByteArrayOutputStream os = new ByteArrayOutputStream();ImageIO.write(rotatedImage, "png", os);InputStream input = new ByteArrayInputStream(os.toByteArray());//绘制完图片后 直接以流的方式将封面图片上传到阿里云String imageType = "default";UploadImageRequest Imagerequest = new UploadImageRequest("LTAI5t6a4Bwa6d21Pd2NRAqo", "QLPSNZCYpe6EI70adnq8s9GRgvcYqo", imageType);Imagerequest.setInputStream(input);UploadImageImpl uploadImage = new UploadImageImpl();UploadImageResponse Imageresponse = uploadImage.upload(Imagerequest);if(Imageresponse.isSuccess()){String imageURL = Imageresponse.getImageURL();strings[0]=imageURL;String imageId = Imageresponse.getImageId();strings[1]=imageId;//上传结束后获得图片的url 将图片的Url作为视频的封面图片//获取文件名String originalFilename = multipartFile.getOriginalFilename();UploadStreamResponse response=null;//上传的视频的标题String title = originalFilename.substring(0, originalFilename.lastIndexOf("."));UploadStreamRequest request = new UploadStreamRequest(ConstantPropertiesUtils.KEY_ID, ConstantPropertiesUtils.KEY_SECRET, title, originalFilename, multipartFile.getInputStream());request.setCoverURL(imageURL);UploadVideoImpl uploader = new UploadVideoImpl();response = uploader.uploadStream(request);String videoId = response.getVideoId();System.out.println(videoId);strings[2]=videoId;}} catch (IOException e) {e.printStackTrace();}return strings;}

5.工具类

实现照片旋转

package com.ts.oss.util;import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;public class ImageRotation {public static BufferedImage rotateImage(BufferedImage image, double angle) {int w = image.getWidth();int h = image.getHeight();// 计算旋转后图像的新边界double sin = Math.sin(Math.toRadians(angle));double cos = Math.cos(Math.toRadians(angle));int newW = (int) Math.floor(Math.abs(w * cos) + Math.abs(h * sin));int newH = (int) Math.floor(Math.abs(h * cos) + Math.abs(w * sin));// 创建一个新的BufferedImage对象,用于存储旋转后的图像BufferedImage rotatedImage = new BufferedImage(newW, newH, image.getType());// 获取Graphics2D对象以进行绘制Graphics2D g2d = rotatedImage.createGraphics();// 设置渲染提示g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);// 旋转图像AffineTransform at = new AffineTransform();at.rotate(Math.toRadians(angle), newW / 2, newH / 2);g2d.setTransform(at);// 绘制旋转后的图像g2d.drawImage(image, (newW - w) / 2, (newH - h) / 2, null);// 释放Graphics2D对象的资源g2d.dispose();return rotatedImage;}
}

从配置文件中获取KEY和ID

package com.ts.oss.util;import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;@Component
public class ConstantPropertiesUtils implements InitializingBean {//读取配置文件中的内容@Value("${aliyun.oss.file.endpoint}")private String endpoint;@Value("${aliyun.oss.file.keyid}")private String keyId;@Value("${aliyun.oss.file.keysecret}")private String keySecret;@Value("${aliyun.oss.file.bucketname}")private String bucketName;//定义公开静态常量 供其他方法使用public static String END_POINT;public static String KEY_ID;public static String KEY_SECRET;public static String BUCKETNAME;@Overridepublic void afterPropertiesSet() throws Exception {END_POINT=endpoint;KEY_ID=keyId;KEY_SECRET=keySecret;BUCKETNAME=bucketName;}
}
在上述Android代码的留白部分加入Okhttp请求
File file=new File(getExternalCacheDir()+"/demo.mp4");
OkHttpClient client = new OkHttpClient();
String sendUrl="http://你的IP地址:你的端口号"+你的Controller请求路径;
MultipartBody.Builder requestBody=new MultipartBody.Builder().setType(MultipartBody.FORM);
RequestBody fileBody=RequestBody.create(MediaType.parse("video/*"),file);
requestBody.addFormDataPart("file", file.getName(),fileBody);Request request=new Request.Builder().url(sendUrl).post(requestBody.build()).build();//创建http请求client.newCall(request).enqueue(new Callback() {@Overridepublic void onFailure(@NonNull Call call, @NonNull IOException e) {e.printStackTrace();}@Overridepublic void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {String responseBody = response.body().string();// 在请求成功时调用回调函数}});

效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/313208.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一个基于单片机内存管理-开源模块

概述 此模块是一位大佬写的应用于单片机内存管理模块mem_malloc,这个mem_malloc的使用不会产生内存碎片,可以高效利用单片机ram空间。 源码仓库:GitHub - chenqy2018/mem_malloc mem_malloc介绍 一般单片机的内存都比较小,而且没有MMU,malloc 与free的使用容易造成内存碎…

Linux 添加启动服务--Service

1&#xff0c;服务配置service文件 Service 服务的实际作用是开启后自动启动服务&#xff0c;运行一些不须要登录的程序&#xff0c;任务。 实例1、上电自动连接WIFI热点 1.1 新建.service文件 /etc/systemd/system/wificonnect.service [Unit] DescriptionService [wifico…

react 项目路由配置(react-router-dom 版本 v6.3、v6.4)

根据 react-router-dom 的版本&#xff0c;有不同的方式 一、react-router-dom v6.3 用到的主要 api: BrowserRouteruseRoutesOutlet 下面是详细步骤&#xff1a; 1、index.js BrowserRouter 用来实现 单页的客户端路由使用 BrowserRouter 包裹 App放在 顶级 位置&#x…

【IoTDB 线上小课 02】开源增益的大厂研发岗面经

还有友友不知道我们的【IoTDB 视频小课】系列吗&#xff1f; 关于 IoTDB&#xff0c;关于物联网&#xff0c;关于时序数据库&#xff0c;关于开源...给我们 5 分钟&#xff0c;持续学习&#xff0c;干货满满~ 5分钟学会 大厂研发岗面试 之前的第一期小课&#xff0c;我们听了 I…

【leetcode面试经典150题】58. 两数相加(C++)

【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主&#xff0c;题解使用C语言。&#xff08;若有使用其他语言的同学也可了解题解思路&#xff0c;本质上语法内容一致&…

MySQL高级(性能分析-查看执行频次、慢查询日志)

目录 1、SQL性能分析 1.1、SQL执行频率 1.2、慢查询日志 1、SQL性能分析 1.1、SQL执行频率 MySQL 客户端连接成功后&#xff0c;通过 show [ session | global ] status 命令可以提供服务器状态信息。通过如下指令&#xff0c;可以查看当前数据库的 insert、update、delete、…

绿色自适应网址发布页源码

源码介绍 绿色自适应网址发布页源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器里面&#xff0c;重定向这个界面 效果截图 源码下载 绿色自适应网址…

Linux--进程间的通信-命名管道

前文&#xff1a; Linux–进程间的通信-匿名管道 Linux–进程间的通信–进程池 命名管道的概念 命名管道是一种进程间通信&#xff08;IPC&#xff09;机制&#xff0c;运行不同进程之间进行可靠的、单向或双向的数据通信。 特点和作用&#xff1a; 跨平台性&#xff1a;在W…

CST电磁仿真物体表面的Sheet结构和生成3D Model【基础教程】

由Sheet结构生成3D Model 使用Shell Solid and Thicken Sheet&#xff01; Modeling > Tools > Shape Tools > Shell Solid or Thicken Sheet Shell Solidor ThickenSheet会根据不同类型的模型提供两种完全不同的功能。 如033.由3D Model生成Cavity 所述&#xff…

数据结构(七)——B树和B+树

7.4.1_1 B树 5叉查找树 //5叉排序树的结点定义 struct Node {ElemType keys[4]; //最多4个关键字struct Node &child[5]; //最多5个孩子int num; //结点中有几个关键字 }; 如何保证查找效率&#xff1f; eg:对于5叉排序树&#xff0c;规定…

反射

目录 01、Java反射机制概述1.1、使用反射&#xff0c;实现同上的操作、调用私有属性 02、理解Class类并获取Class实例2.1、Class类的理解2.2、获取Class实例的4种方式2.3、Class实例对应的结构的说明 03、ClassLoader的理解3.1、ClassLoader的理解3.2、使用ClassLoader加载配置…

LabVIEW光学探测器板级检测系统

LabVIEW光学探测器板级检测系统 特种车辆乘员舱的灭火抑爆系统广泛采用光学探测技术来探测火情。光学探测器作为系统的关键部件&#xff0c;其探测灵敏度、响应速度和准确性直接关系到整个系统的运行效率和安全性。然而&#xff0c;光学探测器在长期使用过程中可能会因为灰尘污…

Android --- Activity

官方文档-activity Activity 提供窗口&#xff0c;供应在其中多个界面。此窗口通常会填满屏幕&#xff0c;但也可能小于屏幕并浮动在其他窗口之上。 大多数应用包含多个屏幕&#xff0c;这意味着它们包含多个 Activity。通常&#xff0c;应用中的一个 Activity 会被指定主 Ac…

【计算机考研】「软件工程」VS「电子信息」专硕有什么不同?

就今年的24国考来说&#xff0c;计算机技术&#xff08;085404&#xff09;能报的只是比计算机科学与技术少那么一点点&#xff08;因为“计算机类”它都可以报&#xff0c;只有写计算机科学与技术的报不了&#xff09;相对于其他天坑专业来说还是好很多的&#xff01; 本人双…

flask 应用程序

flask 程序示例 创建 hello.py 文件&#xff1a; # 导入 Flask 模块。Flask 类的一个对象是 wsgi 应用程序。 from flask import Flask# 创建app对象, Flask构造函数将当前模块的名称(__name__)作为参数。 app Flask(__name__)# route() 函数是一个装饰器&#xff0c;它告诉应…

redmibook 14 2020 安装 ubuntu

1. 参考博客 # Ubuntu20.10系统安装 -- 小米redmibook pro14 https://zhuanlan.zhihu.com/p/616543561# ubuntu18.04 wifi 问题 https://blog.csdn.net/u012748494/article/details/105421656/# 笔记本电脑安装了Ubuntu系统设置关盖/合盖不挂起/不睡眠 https://blog.csdn.net/…

Redis从入门到精通(十四)Redis分布式缓存(二)Redis哨兵集群的搭建和原理分析

文章目录 前言5.3 Redis哨兵5.3.1 哨兵原理5.3.1.1 集群的结构和作用5.3.1.2 集群监控原理5.3.1.3 集群故障恢复原理 5.3.2 搭建哨兵集群5.3.3 RedisTemplate5.3.3.1 搭建测试项目5.3.3.2 场景测试 前言 Redis分布式缓存系列文章&#xff1a; Redis从入门到精通(十三)Redis分…

VM虚拟机安装Linux系统Redhat7.4版本

1、打开VM软件创建一个新的虚拟机&#xff1a; 可选择经典安装&#xff0c;也可以选择自定义安装&#xff0c;本次选择自定义安装&#xff0c;然后选择下一步 2、直接默认选择下一步即可 3、选择稍后安装操作系统&#xff0c;选择下一步 4、之后选择呢需要安装客户机的操作系统…

[Algorithm][滑动窗口][长度最小的子数组] + 滑动窗口原理

目录 0.滑动窗口原理讲解1.长度最小的子数组1.题目链接2.算法原理讲解3.代码实现 0.滑动窗口原理讲解 滑动窗口&#xff1a;“同向双指针”滑动窗口可处理「⼀段连续的区间」问题如何使用&#xff1f; left 0, right 0进窗口判断 是否出窗口 更新结果 -> 视情况而定 可能…

ChatGPT4.5:能力大提升,全新体验

说明 ChatGPT4是2023年的5月份发布的&#xff0c;马上就发布一周年了。其他的大语言模型&#xff0c;比如Claude和开源的Lama也相继更新了最新版本。而根据目前国外发布的各种消息来看&#xff0c;ChatGPT4.5也即将发布。 GPT-4.5 Turbo 发布时间 最新消息显示&#xff0c;Op…