当前位置: 首页 > news >正文

【Android】录制视频

在这里插入图片描述
三三要成为安卓糕手

零:总体代码

1:UI设计

<?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:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".CameraVideoRecordActivity"><androidx.camera.view.PreviewViewandroid:id="@+id/preview_view"android:layout_width="match_parent"android:layout_height="match_parent"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /><ImageViewandroid:id="@+id/iv_record"android:layout_width="66dp"android:layout_height="66dp"android:src="@mipmap/icon_record"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.95" /><ImageViewandroid:id="@+id/iv_switch"android:layout_width="44dp"android:layout_height="44dp"android:layout_margin="30dp"android:src="@mipmap/icon_switch_camera"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2:Activity代码

public class CameraVideoRecordActivity extends AppCompatActivity {private static final String TAG = "CameraVideoRecordActivity";private PreviewView previewView;private ImageView ivRecord;private ImageView ivSwitch;private boolean isRecording;private VideoCapture<Recorder> videoCapture;private ExecutorService executorService;private Recording recording;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_camera_video_record);previewView = findViewById(R.id.preview_view);ivRecord = findViewById(R.id.iv_record);ivSwitch = findViewById(R.id.iv_switch);ivRecord.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (isRecording) {stopRecording();} else {startRecording();}}});startCamera();//创建一个子线程,用于处理录制回调事件,避免阻塞主线程的进行executorService = Executors.newSingleThreadExecutor();requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},100);}private void startCamera() {ListenableFuture<ProcessCameraProvider> providerListenableFuture = ProcessCameraProvider.getInstance(this);providerListenableFuture.addListener(new Runnable() {@Overridepublic void run() {try {ProcessCameraProvider cameraProvide = providerListenableFuture.get();//后置摄像头CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA;//创建预览实例,并把预览实例与previewView绑定Preview preview = new Preview.Builder().build();preview.setSurfaceProvider(previewView.getSurfaceProvider());//视频录制的实例Recorder recorder = new Recorder.Builder().setQualitySelector(QualitySelector.from(Quality.HD)).build();videoCapture = VideoCapture.withOutput(recorder);//相机绑定生命周期cameraProvide.unbindAll();cameraProvide.bindToLifecycle(CameraVideoRecordActivity.this,cameraSelector, preview,videoCapture);} catch (ExecutionException e) {throw new RuntimeException(e);} catch (InterruptedException e) {throw new RuntimeException(e);}}}, ContextCompat.getMainExecutor(this));}/*** 开始录制*/private void startRecording() {if(isRecording){Toast.makeText(this,"正在录制中",Toast.LENGTH_SHORT).show();return;}//在contentValues中指定文件名和类型ContentValues contentValues = new ContentValues();contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME,"video_" + System.currentTimeMillis() + ".mp4");contentValues.put(MediaStore.MediaColumns.MIME_TYPE,"video/mp4");//指定文件输出位置MediaStoreOutputOptions.Builder builder =new MediaStoreOutputOptions.Builder(getContentResolver(), MediaStore.Video.Media.EXTERNAL_CONTENT_URI).setContentValues(contentValues);MediaStoreOutputOptions build = builder.build();//检查录音权限申请if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {Toast.makeText(this, "清先获取录音的权限", Toast.LENGTH_SHORT).show();return;}PendingRecording recording = videoCapture.getOutput().prepareRecording(this, build).withAudioEnabled();//启动录制this.recording = recording.start(executorService, new Consumer<VideoRecordEvent>() {@Overridepublic void accept(VideoRecordEvent videoRecordEvent) {if (videoRecordEvent instanceof VideoRecordEvent.Start) {Log.i(TAG, "accept: 开始录制");isRecording = true;ivRecord.setImageResource(R.mipmap.icon_record);} else if(videoRecordEvent instanceof VideoRecordEvent.Finalize){Log.i(TAG, "accept: 录制结束");isRecording = false;ivRecord.setImageResource(R.mipmap.icon_stop_record);}}});}private void stopRecording() {if(recording != null && isRecording){recording.stop();//停止recording = null;//释放资源}}@Overrideprotected void onDestroy() {super.onDestroy();executorService.shutdown();}
}

一:startCamera启动相机

1:创建视频录制实例

这里是跟拍照功能那有一些不一样的地方;这里使用构建设计模式,设置默认录制视频的清晰度

                    //视频录制的实例Recorder recorder = new Recorder.Builder().setQualitySelector(QualitySelector.from(Quality.HD)).build();videoCapture = VideoCapture.withOutput(recorder);

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

二:开始录制功能代码分析

1:视频文件的输出配置

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这段代码的主要目的是创建一个视频文件的输出配置,告诉系统视频文件应该存储在哪里、以什么信息保存(文件名、格式等),以便后续视频录制完成后能正确写入到系统中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(1)MediaStoreOutputOptions.Builder构建器
MediaStoreOutputOptions.Builder builder =new MediaStoreOutputOptions.Builder(getContentResolver(), MediaStore.Video.Media.EXTERNAL_CONTENT_URI).setContentValues(contentValues);
  • 构造参数 1:getContentResolver()
    获取内容解析器(ContentResolver),用于访问 Android 系统的媒体库(如照片、视频库)。

  • 构造参数 2:MediaStore.Video.Media.EXTERNAL_CONTENT_URI
    指定视频文件的存储位置 ——外部存储的系统视频媒体库(即系统默认的 “视频” 文件夹),之前用过的

    • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
  • .setContentValues(contentValues)
    将之前创建的contentValues(包含文件名、MIME 类型等信息)设置到构建器中,这些信息会被用于创建视频文件。

(2)MediaStoreOutputOptions

  • MediaStoreOutputOptions:CameraX 库中用于配置媒体文件(视频 / 图片)输出位置和属性的类,确保文件能正确写入系统媒体库。
  • 为什么用这种方式存储?
    直接通过MediaStore存储视频,会让文件自动出现在系统相册 / 视频库中,无需手动刷新媒体库;同时符合 Android 10 + 的存储权限规范(无需申请WRITE_EXTERNAL_STORAGE权限)。

2:录音权限检查

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在启动带音频的视频录制前,检查应用是否已获得录音权限RECORD_AUDIO),这是 Android 权限管理的强制要求(属于 “危险权限”,必须明确申请)。

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {Toast.makeText(this, "请先获取录音的权限", Toast.LENGTH_SHORT).show();return;
}

3:创建 “待启动” 的录制对象。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • videoCapture.getOutput()
    videoCapture 是 CameraX 库中负责视频捕获的核心对象,在startCamera方法中已经创建好了,并且提取了成员变量,getOutput() 用于获取其输出控制器,后续通过它配置录制参数。

  • .prepareRecording(this, build)
    准备录制会话,参数说明:

    • build:前面创建的MediaStoreOutputOptions对象(包含视频存储路径、文件名等配置)
      这一步会根据配置初始化录制环境,确定视频文件的存储位置和格式。
  • .withAudioEnabled()
    启用音频录制功能(默认可能只录视频不含声音),使最终生成的视频包含音频轨道;所以这里需要检查录音权限

  • PendingRecording recording
    接收返回的PendingRecording对象,它表示 “已准备好但尚未启动” 的录制任务,后续通过它的start()方法真正开始录制。

4:开始录制

通过调用start()方法正式启动视频录制,并设置一个回调监听器,实时接收录制过程中的各种状态事件(如开始、结束等),进而更新应用的状态标记和 UI 显示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

(1)启动录制并绑定回调

  • start(...):用于正式启动录制,需要传入两个参数:

    • executorService:一个线程池(ExecutorService),指定回调事件的处理线程(避免在主线程处理耗时操作)
    • Consumer<VideoRecordEvent>接口实现,用于接收录制过程中的各种事件(回调函数)。
  • this.recording:将启动后的录制对象保存到成员变量中,方便后续控制资源释放(如调用stop()停止录制)。

(2)回调接口Consumer<VideoRecordEvent>

  • VideoRecordEvent:CameraX 定义的录制事件基类,包含多种子类,分别表示不同的录制状态(如开始、暂停、结束等)。
        //回调中的videoRecordEvent会有下面几种状态://VideoRecordEvent.Start:录制开始。//VideoRecordEvent.Pause:录制暂停。//VideoRecordEvent.Resume:录制恢复。//VideoRecordEvent.Finalize:录制完成(停止或失败)。//VideoRecordEvent.Status:录制状态更新(持续获取统计信息)。
  • accept()方法:当录制状态发生变化时,系统会自动调用该方法,并传入对应的事件对象recordEvent

(3)关键技术点

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

线程切换executorService指定了回调的执行线程,通常会在回调中通过runOnUiThread()切换到主线程更新 UI,setImageResource()属于 UI 操作,需确保在主线程执行

异步回调机制: 录制过程是异步的(在后台执行),通过回调通知 UI 线程状态变化,避免阻塞主线程。

状态管理isRecording变量用于全局跟踪录制状态,防止重复操作(如多次点击开始录制)。

(4)可能的扩展

  • 可以增加对其他事件的处理,如VideoRecordEvent.Pause(暂停)、VideoRecordEvent.Resume(恢复)等。
  • 可在Finalize事件中添加视频保存成功 / 失败的判断(通过recordEvent.getError()检查是否有错误)。
  • 可以在回调中实时更新录制时长(通过recordEvent.getRecordingStats().getRecordedDurationMs()获取已录制时长)。

5:效果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

http://www.dtcms.com/a/395676.html

相关文章:

  • RK3576-Android15_Usb白名单功能实现篇二
  • Spring中使用Apache Http客户端调第三方系统接口临时查看请求体参数
  • Linux系统-debian系的软件包管理
  • PCB工艺中的深微孔
  • 关于Pycharm中在运行出现语法错误:Non-UTF-8 code starting with
  • 构建AI智能体:四十一、大模型思维链提示工程:技术原理与行业应用案例分析
  • 鸿蒙系统中音视频的采集与播放
  • HTTPS 双向认证抓包实战,原理、难点、工具与可操作的排查流程
  • 开源跨平台文件管理工具,告别杂乱无章的数据世界
  • Node.js事件循环机制
  • Linux---文件系统
  • 循环语句效率与规范的原理及示例解析
  • Three.js 开发实战教程(四):相机系统全解析与多视角控制
  • 介绍一下SQLite的基本语法和常用命令
  • 台式电脑如何恢复出厂设置?Win10 强制重置详细教程
  • 李宏毅2023机器学习作业 HW02实操
  • 【C++实战㉜】深入C++动态内存分配:从理论到实战的进阶之路
  • 小鼠抗新冠病毒N蛋白IgG亚型抗体ELISA检测试剂盒
  • 安防监控中常见的报警类型有哪些?国标GB28181平台EasyGBS的报警能力解析
  • C++ 中 size_t 的用(用于跨平台编译)
  • C++ 拷贝构造函数调用时机
  • 手机镜头参数介绍
  • 区块链技术之《(1)—概述》
  • 复盘与导出工具最新版V31.0版本更新---彻底修复卡死闪退bug,盘中实时丝滑
  • 深入理解JVM类加载与垃圾回收机制
  • Ethernet/IP转ProfiNet网关选型指南:欧姆龙PLC对接研祥工控机最佳实践
  • Java 面试高频手撕题清单
  • 【论文阅读】Long-VLA:释放视觉语言动作模型在机器人操作中的长时程能力
  • Python poplib 库全解析:POP3 邮件收取的完整指南
  • DanceTrack数据集介绍