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">
    <TextureView
        android:id="@+id/textureView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />


    <Button
        android: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"/>
    <Button
        android: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")
    @Override
    protected 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() {
            @Override
            public void onClick(View v) {
                config();
                startRecorder();

            }
        });
        mBtnFinish.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopRecorder();

            }
        });
    }

    /**
     * 初始化TextureView的纹理生成监听,只有纹理生成准备好了。我们才能去进行摄像头的初始化工作让TextureView接收摄像头预览画面
     */
    private void initTextureViewStateListener(){
        mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                //可以使用纹理
                initCameraManager();
                selectCamera();
                openCamera();

            }

            @Override
            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
                //纹理尺寸变化

            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                //纹理被销毁
                return false;
            }

            @Override
            public 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项目需要考虑兼容问题,应该选择AAC
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);//设置视频编码格式,请注意这里使用默认,实际app项目需要考虑兼容问题,应该选择H264
        mMediaRecorder.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();//从获取录制视频需要的Surface
        try {
            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,第二个是录制视频使用的Surface
            mCameraDevice.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() {
            @Override
            public 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();
                }

            }

            @Override
            public void onDisconnected(@NonNull CameraDevice camera) {
                //摄像头断开

            }

            @Override
            public void onError(@NonNull CameraDevice camera, int error) {
                //异常

            }
        };
    }

    private void initSessionStateCallback(){
        mSessionStateCallback = new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(@NonNull CameraCaptureSession session) {
                mCameraCaptureSession = session;
                try {
                    //执行重复获取数据请求,等于一直获取数据呈现预览画面,mSessionCaptureCallback会返回此次操作的信息回调
                    mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest.build(),mSessionCaptureCallback,mChildHandler);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }

            }

            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession session) {

            }
        };
    }

    private void initSessionCaptureCallback(){
        mSessionCaptureCallback=new CameraCaptureSession.CaptureCallback() {
            @Override
            public void onCaptureStarted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, long timestamp, long frameNumber) {
                super.onCaptureStarted(session, request, timestamp, frameNumber);
            }

            @Override
            public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
                super.onCaptureProgressed(session, request, partialResult);
            }

            @Override
            public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                super.onCaptureCompleted(session, request, result);
            }

            @Override
            public 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">
    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_weight="10"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:rotation="90"
        />
    <!--水平线性布局-->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="horizontal">
        <!--播放按钮-->
        <Button
            android:id="@+id/play1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="播放"/>
        <!--暂停按钮-->
        <Button
            android:id="@+id/pause1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="取消" />
        <!--停止按钮-->
        <Button
            android: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;

    @Override
    protected 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() {
            @Override
            public void onClick(View v) {
                if (noPlay) {
                    play();
                    noPlay = false;
                } else {
                    mediaPlayer.start();
                }
            }
        });
        /**
         * 实现取消功能
         */
        pause.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mediaPlayer.stop();
                mediaPlayer.release();
                //返回主页面
                if (mediaPlayer.isPlaying()) {
                    mediaPlayer.pause();
                }
            }
        });
        /**
         * 实现上传功能
         */
        stop.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public 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();
                    }
                }
                //这个位置实现视频上传的功能

                    @Override
                    public 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() {
            @Override
            public void onCompletion(MediaPlayer mp) {
                Toast.makeText(PlayVideoActivity.this, "视频播放完毕!", Toast.LENGTH_SHORT).show();
            }
        });
    }


    /**
     * 当前Activity销毁时,停止正在播放的视频,并释放MediaPlayer所占用的资源
     */
    @Override
    protected 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

@Override
    public 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;
    @Override
    public 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() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {


                e.printStackTrace();

            }

            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                String responseBody = response.body().string();
                // 在请求成功时调用回调函数
               
            }
        });

效果

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于Python的深度学习的中文情感分析系统(V2.0),附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

2024年MathorCup数学建模B题甲骨文智能识别中原始拓片单字自动分割与识别研究解题文档与程序

2024年第十四届MathorCup高校数学建模挑战赛 B题 甲骨文智能识别中原始拓片单字自动分割与识别研究 原题再现&#xff1a; 甲骨文是我国目前已知的最早成熟的文字系统&#xff0c;它是一种刻在龟甲或兽骨上的古老文字。甲骨文具有极其重要的研究价值&#xff0c;不仅对中国文…

【学习心得】神经网络知识中的符号解释②

我在上篇文章中初步介绍了一些神经网络中的符号&#xff0c;只有统一符号及其对应的含义才能使我自己在后续的深度学习中有着一脉相承的体系。如果对我之前的文章感兴趣可以点击链接看看哦&#xff1a; 【学习心得】神经网络知识中的符号解释①http://t.csdnimg.cn/f6PeJ 一、…

010、Python+fastapi,第一个后台管理项目走向第10步:ubutun 20.04下安装ngnix+mysql8+redis5环境

一、说明 先吐槽一下&#xff0c;ubuntu 界面还是不习惯&#xff0c;而且用的是云电脑&#xff0c;有些快捷键不好用&#xff0c;只能将就&#xff0c;谁叫我们穷呢&#xff1f; 正在思考怎么往后进行&#xff0c;突然发现没安装mysql 和redis&#xff0c;准备安装&#xff0…

组合预测 | Matlab实现ICEEMDAN-SMA-SVM基于改进完备集合经验模态分解-黏菌优化算法-支持向量机的时间序列预测

组合预测 | Matlab实现ICEEMDAN-SMA-SVM基于改进完备集合经验模态分解-黏菌优化算法-支持向量机的时间序列预测 目录 组合预测 | Matlab实现ICEEMDAN-SMA-SVM基于改进完备集合经验模态分解-黏菌优化算法-支持向量机的时间序列预测预测效果基本介绍程序设计参考资料预测效果 基本…

kali工具----域名IP及路由跟踪

域名IP及路由跟踪 测试网络范围内的IP地址或域名也是渗透测试的一个重要部分。通过测试网络范围内的IP地址或域名&#xff0c;确定是否有人入侵自己的网络中并损害系统。不少单位选择仅对局部IP基础架构进行渗透测试&#xff0c;但从现在的安全形势来看&#xff0c;只有对整个I…

未来计算机的发展趋势是什么?

未来计算机的发展趋势是多方面的,涵盖了硬件、软件、体系结构以及计算范式等多个层面。以下是一些预期的趋势: 1. 量子计算: 随着量子理论的不断成熟和技术的进步,量子计算机将可能解决传统计算机难以处理的问题,比如药物发现、材料科学、复杂系统模拟等领域。量子计算的…

UT单元测试

Tips&#xff1a;在使用时一定要注意版本适配性问题 一、Mockito 1.1 Mock的使用 Mock 的中文译为仿制的&#xff0c;模拟的&#xff0c;虚假的。对于测试框架来说&#xff0c;即构造出一个模拟/虚假的对象&#xff0c;使我们的测试能顺利进行下去。 Mock 测试就是在测试过程…

SQL语言自用(持续更新)+实验记录

课本:《数据库原理及其应用教程》&#xff08;第四版&#xff09; (主编)黄德才&(副主编)陆亿红 实验&#xff1a;学校实验课材料 其他&#xff1a; [ ]表示可以被删除&#xff0c;也表示可以被替换&#xff0c;请自行判断。如果有一些截图或照片&#xff0c;是暂时懒得整…

基于SpringBoot的“滴答拍摄影项目”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“滴答拍摄影项目”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 滴答拍摄影项目结构图 管理员登录首页界面图 用…

[lesson24]经典问题解析二

经典问题解析二 关于析构的疑问 当程序中存在多个对象的时候&#xff0c;如何确定这些对象的析构顺序&#xff1f;单个对象创建时构造函数的调用顺序 1.调用父类的构造过程2.调用成员变量的构造函数(调用顺序于声明顺序相同)调用类自身的构造函数 析构函数与对应的构造函数的…

车联网大数据与人工智能一体化:开启智慧出行新时代

随着物联网技术的快速发展&#xff0c;车联网已经成为了汽车行业的重要趋势之一。而在车联网的发展过程中&#xff0c;大数据和人工智能的应用也日益成为关键因素。本文将探讨如何将大数据与人工智能一体化应用于车联网&#xff0c;以实现智慧出行的目标。 尤其是近来国内的华为…

【算法刷题 | 回溯思想 03】4.13( 组合总和、组合总和|| )

文章目录 5.组合总和5.1题目5.2解法&#xff1a;回溯5.2.1回溯思路&#xff08;1&#xff09;函数返回值以及参数&#xff08;2&#xff09;终止条件&#xff08;3&#xff09;遍历过程 5.2.2代码实现 6.组合总和 ||6.1题目6.2解法&#xff1a;回溯6.2.1回溯思路&#xff08;1&…

Linux软件包管理器yum—5

一、Linux下软件安装的方式 ①源代码安装&#xff1a; ②rmp包安装&#xff1a; 本质是拷贝可执行程序到系统目录下。 ③yum一键下载&#xff0c;安装&#xff0c;卸载。相当于手机的应用商店。 二、yum 2.1查看yum已配置的源&#xff1a; ls /etc/yum.repos.d/ 2.2查看yum…

Nexus 启动异常

在迁移 Nexus 到新的服务器上&#xff0c;我们有下面的异常。 [rootdevops log]# /opt/nexus/bin/nexus start No suitable Java Virtual Machine could be found on your system. The version of the JVM must be 1.8. Please define INSTALL4J_JAVA_HOME to point to a suita…

springboot数字化智慧城市管理系统源码

目录 ​系统开发环境 系统功能模块 系统特点 1、智慧城管移动端 2、案件受理 3、AI视频智识别分析 系统应用价值 1、提升案件办理效率 2、提升监管效能 3、提升行政执法水平 4、推进行政执法创新 智慧城管综合执法办案系统功能 现场移动执法 一般程序案件的网上办…

5.3 mybatis之autoMappingUnknownColumnBehavior作用

文章目录 1. NONE2. WARNING3. FAILING autoMappingUnknownColumnBehavior是< settings >配置下的属性&#xff0c;该属性是指定发现自动映射目标未知列&#xff08;或未知属性类型&#xff09;的行为。就是说当数据库中的字段找不到映射java对象的属性或者与java对象对应…

MQ概览及Kafka详解

文章目录 概览MQ优点MQ缺点常见MQ对比JMS消息模型点对点模式发布订阅模式 kafka基础架构发布订阅工作流程生产者生产者文件存储生产者分区策略生产者数据可靠性保证生产者数据一致性保证生产者ack机制ExactlyOnce生产者发送消息流程 消费者消费者分区分配策略消费者消费数据问题…

MATLAB 自定义实现点云法向量和曲率计算(详细解读)(64)

MATLAB 自定义实现点云法向量和曲率计算(详细解读)(64) 一、算法介绍二、算法步骤三、算法实现1.代码 (完整,注释清晰,可直接用)2.结果一、算法介绍 首先说明: ------这里代码手动实现,不调用matlab提供的法向量计算接口,更有助于大家了解法向量和曲率的计算方法,…

Docker 学习笔记(八):Dockerfile实战篇,制作 Tomcat 镜像,发布镜像到 DockerHub 和阿里云

一、前言 记录时间 [2024-4-13] 系列文章简摘&#xff1a; Docker 学习笔记&#xff08;六&#xff09;&#xff1a;挑战容器数据卷技术一文通&#xff0c;实战多个 MySQL 数据同步&#xff0c;能懂会用&#xff0c;初学必备 Docker 学习笔记&#xff08;七&#xff09;&#x…