HarmonyOS鸿蒙最全【Android】FFmpeg兼容Android 12,可以实现视频剪辑(1),花了19998买的学习教程

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

分析轮子

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" 

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

的技术提升。**

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的Android视频直播推拉流的实现过程,需要用到FFmpeg6.0, SRS 6.0和nginx。 1. 配置FFmpeg和SRS 首先,需要下载并配置FFmpeg和SRS。可以参考FFmpeg和SRS的官方文档,或者在网上搜索相关的教程。 2. 编写Android客户端 接下来,需要编写Android客户端代码。可以使用FFmpeg的Java接口来实现视频编码和解码,使用SRS的Java客户端库来实现RTMP推流和拉流。 以下是一个简单的推流示例: ```java import io.ossrs.rtmp.ConnectCheckerRtmp; import io.ossrs.rtmp.Log; import io.ossrs.rtmp.RtmpPublisher; import io.ossrs.rtmp.SrsFlvMuxer; import io.ossrs.rtmp.SrsFrame; public class MainActivity extends AppCompatActivity implements ConnectCheckerRtmp { private static final String TAG = "MainActivity"; private static final String RTMP_URL = "rtmp://<your_server_ip>/live/stream"; private RtmpPublisher mRtmpPublisher; private SrsFlvMuxer mSrsFlvMuxer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSrsFlvMuxer = new SrsFlvMuxer(); mRtmpPublisher = new RtmpPublisher(mSrsFlvMuxer, this); // Connect to SRS server mRtmpPublisher.connect(RTMP_URL); } @Override public void onDestroy() { super.onDestroy(); // Disconnect from SRS server mRtmpPublisher.stop(); } // Called when connected to SRS server @Override public void onConnectionSuccess() { Log.d(TAG, "onConnectionSuccess"); } // Called when failed to connect to SRS server @Override public void onConnectionFailed(String reason) { Log.e(TAG, "onConnectionFailed: " + reason); } // Called when disconnected from SRS server @Override public void onDisconnected() { Log.d(TAG, "onDisconnected"); } // Called when received a video frame public void onVideoFrame(byte[] data, int width, int height, long timestamp) { SrsFrame frame = new SrsFrame(); frame.type = SrsFrame.Type.Video; frame.timeStamp = timestamp; frame.data = data; frame.width = width; frame.height = height; mSrsFlvMuxer.writeFrame(frame); } // Called when received an audio frame public void onAudioFrame(byte[] data, long timestamp) { SrsFrame frame = new SrsFrame(); frame.type = SrsFrame.Type.Audio; frame.timeStamp = timestamp; frame.data = data; mSrsFlvMuxer.writeFrame(frame); } } ``` 以上代码使用了SRS的Java客户端库来实现RTMP推流功能,可以通过`onVideoFrame`和`onAudioFrame`方法来推送视频和音频帧。 以下是一个简单的拉流示例: ```java import io.ossrs.rtmp.ConnectCheckerRtmp; import io.ossrs.rtmp.Log; import io.ossrs.rtmp.RtmpPlayer; import io.ossrs.rtmp.SrsFlvMuxer; import io.ossrs.rtmp.SrsFrame; public class MainActivity extends AppCompatActivity implements ConnectCheckerRtmp { private static final String TAG = "MainActivity"; private static final String RTMP_URL = "rtmp://<your_server_ip>/live/stream"; private RtmpPlayer mRtmpPlayer; private SrsFlvMuxer mSrsFlvMuxer; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSrsFlvMuxer = new SrsFlvMuxer(); mRtmpPlayer = new RtmpPlayer(mSrsFlvMuxer, this); // Connect to SRS server mRtmpPlayer.connect(RTMP_URL); } @Override public void onDestroy() { super.onDestroy(); // Disconnect from SRS server mRtmpPlayer.stop(); } // Called when connected to SRS server @Override public void onConnectionSuccess() { Log.d(TAG, "onConnectionSuccess"); // Start playing mRtmpPlayer.play(); } // Called when failed to connect to SRS server @Override public void onConnectionFailed(String reason) { Log.e(TAG, "onConnectionFailed: " + reason); } // Called when disconnected from SRS server @Override public void onDisconnected() { Log.d(TAG, "onDisconnected"); } // Called when received a video frame public void onVideoFrame(byte[] data, int width, int height, long timestamp) { // Handle video frame } // Called when received an audio frame public void onAudioFrame(byte[] data, long timestamp) { // Handle audio frame } } ``` 以上代码使用了SRS的Java客户端库来实现RTMP拉流功能,可以通过`onVideoFrame`和`onAudioFrame`方法来处理接收到的视频和音频帧。 3. 配置nginx 最后,需要配置nginx来提供HTTP-FLV流媒体服务。可以参考nginx的官方文档,或者在网上搜索相关的教程。 以上是一个简单的Android视频直播推拉流的实现过程。需要注意的是,这只是一个示例,实际的应用中还需要根据具体的需求进行修改和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值