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



    版权声明

    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" />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    构建UI界面的XML

    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391

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

    构建播放界面部分的XML

    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    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();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    上述代码中的注释部分为上传功能留白。

    三.视频上传功能的实现

    在实现视频上传到阿里云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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

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

    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3.Service

    public interface VodService {
       
    
        String[] uploadVideoAndImage(MultipartFile multipartFile);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    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;
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    从配置文件中获取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;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    在上述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();
                    // 在请求成功时调用回调函数
                   
                }
            });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    效果

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

  • 相关阅读:
    构建用户身份基础设施,推动新能源汽车高质量发展
    【SSM】Spring系列——IoC 控制反转
    【小航的算法日记】贪心算法
    SpringBoot实战:轻松实现接口数据脱敏
    springboot中如何同时操作同一功能
    【Linux】网络高级IO
    Unity URP简单烘焙场景步骤
    搭建远端存储,深度解读SPDK NVMe-oF target
    Information Bottleneck【信息瓶颈IB】
    教大家怎么看monaco-editor的官方文档
  • 原文地址:https://blog.csdn.net/qq_43646281/article/details/137710114