需求分析

需求很简单,在安卓手机上进行视频裁剪,只要裁短,不要求拼接,也不要求裁剪画面。编码形式直接复制原本的,分辨率码率帧率都直接照搬原本的。

找轮子

尽量不要重复造轮子,有现成的直接找现成的。这里找了一个ffmpeg实现的轮子来直接用,唯一问题是项目是5年前的,要做些适配。

分析轮子

5年前的轮子。。。试试在5年前的系统上跑一下。Android 9能正常运行,到了Android 10就报找不到文件了。在各个版本跑了一遍以后,发现以下问题等待解决:

  • 输入文件路径是写死的,需要改成手动选择
  • 没有权限申请相关的代码
  • 没有适配Android 10的新存储方式
  • Android 10以上无法用以前的方式在控制台运行ffmpeg

解决轮子的问题,适配Android 12

添加权限申请代码

我们需要读取外部存储里的媒体文件,其实读相册和下载是不需要权限的,但是保不齐用户的媒体文件会存在什么奇奇怪怪的地方,比如说微信和QQ就存在他们自己的公共目录里。所以这个读取权限还是有必要的。
在Manifest声明需要的权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

动态申请权限,写在第一个Activity里就好

private val REQUEST_CODE_PERMISSIONS = 10//数字随意private val REQUIRED_PERMISSIONS = arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE)//要申请更多的权限都在这里拼接//查询权限是否获取成功
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}//获取权限弹窗操作完成回调
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>,grantResults: IntArray) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)if (requestCode == REQUEST_CODE_PERMISSIONS) {if (allPermissionsGranted()) {selectFile()//去选择文件} else {Toast.makeText(this, "需要授予权限才能使用该功能", Toast.LENGTH_SHORT).show()//给用户提示}}
}

让用户自行选择文件

直接用intent唤起系统相册即可

private fun selectFile() {val i = Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {someActivityResultLauncher.launch(i)//Android 10以上的写法,targetAPI在31以上才这么写} else {startActivityForResult(i, REQUEST_SELECT_FILE)//Android 10以下的写法}
}override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)when (requestCode) {REQUEST_SELECT_FILE -> {if (resultCode == Activity.RESULT_OK && data != null && data.data != null) {toCrop(data.data!!)//选完文件以后跳转到裁剪界面}}}
}var someActivityResultLauncher = registerForActivityResult(StartActivityForResult(), ActivityResultCallback { result ->if (result.resultCode == Activity.RESULT_OK&& result.data != null && result.data!!.data != null) {toCrop(result.data!!.data!!)//选完文件以后跳转到裁剪界面}})/*** 跳转裁剪页面*/
private fun toCrop(data: Uri) {val filePathColumn = arrayOf(MediaStore.Video.Media.DATA)try {val cursor: Cursor? = contentResolver.query(data,filePathColumn, null, null, null)if (cursor != null) {cursor.moveToFirst()val columnIndex: Int = cursor.getColumnIndex(filePathColumn[0])val videoPath: String = cursor.getString(columnIndex)cursor.close()val intent = Intent(this, VideoCropPreActivity::class.java)intent.putExtra("src", videoPath)//在裁剪界面接收这个参数即可startActivity(intent)}} catch (e: Exception) {Log.e(TAG, "读取文件出错", e)Toast.makeText(this, resources.getString(R.string.readVideoFileError), Toast.LENGTH_LONG).show()}
}

注意,如果使用模拟器测试会触发一个闪退bug
模拟器镜像的谷歌相册是老版本,会抛出异常java.lang.IllegalArgumentException: Invalid column latitude这个是谷歌相册老版本的bug,更新相册或者使用别的相册APP选择图片即可解决。谷歌的锅我们不帮他修,不管这个问题。实机不会出现这个问题,我用Pixel 3 XL在Android 12实测过不会报错。

解决无法在控制台直接运行ffmpeg

从Android 10开始,不允许直接运行data/data/packagename/目录下的二进制文件。旧的写法正是把ffmpeg放到这里运行的,所以到了Android 10需要改变写法。
如果你直接用老的写法的话,会报文件不存在或者权限被拒绝,因为安卓10以上根本不会把你Assert里的二进制文件放到这个目录里。那么解决的思路就很简单了,我们把ffmpeg伪装成别的文件,骗系统把我们放进去不就可以了吗?

把ffmpeg放到jni的目录里

说干就干,伪装成so库文件。先把ffmpeg改名成ffmpeg.so,然后放进jniLibs里面去,你想支持哪些架构就放到哪些架构的目录里,反正这玩意不挑架构。

修改manifest

修改manifest,在application标签里加入这个参数

android:extractNativeLibs="true"

修改运行指令的目录

在原本的代码中搜索data/data,找到这一段

"/data/data/" + context.getPackageName() + "/ffmpeg"

把它改成

context.getApplicationInfo().nativeLibraryDir + "/ffmpeg.so"

有两处,都改掉以后就大功告成了~

性能优化

原本的轮子是有裁剪画面的功能的,但是我没有这个需求,所以要在最后的ffmpeg指令中把裁剪的指令干掉。因为不需要重新编码,直接复制视频流和音频流最快,所以加个-c copy
测试时发现一个小bug,如果文件名中有空格,会当成指令的分隔符,所以我们添加引号把文件名包起来,解决这个问题。

//-ss 开始时间(秒) -t 结束时间(秒)  时间裁切//-strict -2 -vf crop=500:500:0:100   尺寸裁切String cmd = context.getApplicationInfo().nativeLibraryDir + "/ffmpeg.so" + " -y -i "//-y是覆盖同名文件+ """ + srcVideoPath + """//加引号避免名字有空格无法识别+ " -ss " + start + " -t " + duration
//                + " -strict -2 -vf crop=" + width + ":" + height + ":" + x + ":" + y + " -preset fast "//裁剪尺寸+ " -c copy "//直接复制流,跳过编码节约时间+ """ + destPath + """;//加引号避免名字有空格无法识别task.execute(cmd);

原本的轮子渲染底部时间轴的图片速度很慢,通过修改MediaMetadataRetriever的参数可以大幅提升渲染速度,找到这一行

Bitmap bitmap = mmr.getFrameAtTime(i, MediaMetadataRetriever.OPTION_CLOSEST);

把它改成这样即可

Bitmap bitmap = mmr.getFrameAtTime(i, MediaMetadataRetriever.OPTION_CLOSEST_SYNC); 

已知问题

尽管加了引号包裹文件名,但在部分机型上还是存在带空格的文件路径和文件名找不到的情况,建议在选文件的阶段直接给用户提示。

文末

我总结了一些Android核心知识点,以及一些最新的大厂面试题、知识脑图和视频资料解析。

需要的小伙伴直接点击文末小卡片免费领取哦,以后的路也希望我们能一起走下去。(谢谢大家一直以来的支持,需要的自己领取)

Android学习PDF+架构视频+面试文档+源码笔记

部分资料一览:

  • 330页PDF Android学习核心笔记(内含8大板块)

  • Android学习的系统对应视频

  • Android进阶的系统对应学习资料

  • Android BAT大厂面试题(有解析)

领取地址:

【Android】FFmpeg实现视频剪辑,并兼容Android 12相关推荐

  1. Android FFmpeg 音视频开发教程

    LearnFFmpeg 项目地址:githubhaohao/LearnFFmpeg 简介: Android FFmpeg 音视频开发教程 更多:作者   提 Bug 标签: An Android FF ...

  2. android视频剪辑处理第三方,Android 中通过 FFmpeg 命令对音视频编辑处理

    以下文章来源于DevYk ,作者DevYK 音视频编辑器 前言 有时候我们想对音视频进行加工处理,比如视频编辑.添加字幕.裁剪等功能处理,虽然 Github 上开源了一些比较不错的项目,但是如果我们想 ...

  3. Android FFmpeg源码编译及在Android studio的集成

    准备工具: 1,ubuntu server 18.04.4(其他发行版服务器或桌面版都行,我这里以ubuntu为例,可以使用虚拟机,也可以使用公网的服务器) 2,ndk R17c linux版 (下载 ...

  4. android 相册选择视频和图片格式,Android获取文件类型是图片还是视频

    我们开发从相册分享图片到App的功能,开发完成后发现一个问题:我们设置的是用户可以选择多张图片到app,但是有一种情况是有的手机可以分享一张图片和一个视频,尴尬的是我们不支持视频分享.这就需要区分用户 ...

  5. Android FFmpeg视频播放器三 音频封装格式解码播放

    Android FFmpeg视频播放器一解封装 Android Android FFmpeg视频播放器二 视频封装格式解码播放 视频解封装之后就会得到音频流和视频流,解封状得到的数据是AVPackag ...

  6. Android视频编辑SDK免费版,Android视频编辑SDK

    android视频编辑sdk是一款视频编辑软件,用户可以看到视频配音配乐.添加字幕.添加滤镜.视频转场等各种功能,并可以快速的植入到软件中进行编辑,编辑的过程中支持用户进行智能的硬件解码,选择视频时不 ...

  7. Android FFmpeg系列——7 实现快进/快退功能

    Android FFmpeg系列--0 编译.so库 Android FFmpeg系列--1 播放视频 Android FFmpeg系列--2 播放音频 Android FFmpeg系列--3 C多线 ...

  8. 【Android FFMPEG 开发】Android 中使用 FFMPEG 对 MP3 文件进行混音操作

    文章目录 一.前置操作 ( 移植 FFMPEG ) 二.FFMPEG 混音命令 三.Android FFMPEG 混音源代码完整示例 四.博客源码 一.前置操作 ( 移植 FFMPEG ) 参考 [A ...

  9. Android 画中画(视频)

    Android 画中画(视频) 简介: Android 8.0(API 级别 26)允许以画中画 (PIP) 模式启动 Activity.画中画是一种特殊类型的多窗口模式,最常用于视频播放.使用该模式 ...

最新文章

  1. Permission is only granted to system app
  2. Vim 下使用 Slimv(类似Slime) 轻松调试 Common Lisp
  3. 实战SSM_O2O商铺_22【商铺列表】Service层开发
  4. Spring RabbitMQ使用
  5. java基础语法以及进制的转换
  6. css经典布局——头尾固定高度中间高度自适应布局
  7. EL函数以及自定义标签的应用
  8. 学习笔记(51):Python实战编程-ListBox
  9. batocera游戏整合包_FIFAol3头像包整合
  10. 「ECharts」交互 API (echarts、echartsInstance)
  11. C++自学22:复制内存(memcpy)/设置内存(memset)
  12. hadoop集群安装配置
  13. linux和windows下TIME_WAIT过多的解决办法
  14. 指数函数以及对数函数的导数
  15. [转]被历史歪曲得最多的皇帝--隋炀帝杨广简介
  16. Unity_回合制战斗系统_01
  17. 各种进制换算成十进制
  18. 在 Azure ML 上用 .NET 跑机器学习
  19. [Work Summary] Python将PDF转换成Word文档
  20. 全能型Mac解压缩软件 MacZip2.0.1(41)中文版 原ezip

热门文章

  1. 有人参加过CSDN超级实习生计划吗?靠谱吗?
  2. 【转载】阿里技术专家详解DDD系列 第二讲 - 应用架构
  3. 《计算机体系结构》重要知识点
  4. [附源码]计算机毕业设计JAVA高考志愿填报系统
  5. RFSoC应用笔记 - RF数据转换器 -02- IP配置指南
  6. Mac上安装 Adobe Premiere系列软件提示 501 错误的解决方法
  7. 易特鞋店销售管理后台软件(5)
  8. 软件设计师考点笔记二
  9. docker pull dubbo-admin 报错,各位大神请多多指教啊。。。
  10. Outlook登录企业邮箱入口,手机outlook如何登公司邮箱服务器?