欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

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

最编程 2024-04-22 17:31:11
...

文章目录

  • 版权声明
  • 前言
    • 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.creat