安卓部署ffmpeg全平台so并实现命令行调用

news2024/12/25 8:53:27

安卓 FFmpeg系列

第一章 Ubuntu生成ffmpeg安卓全平台so
第二章 Windows生成ffmpeg安卓全平台so
第三章 生成支持x264的ffmpeg安卓全平台so
第四章 部署ffmpeg安卓全平台so并使用(本章)


文章目录

  • 安卓 FFmpeg系列
  • 前言
  • 一、添加so
    • 1、拷贝ffmpeg到项目
    • 2、build.gradle指定so目录
  • 二、调用命令行
    • 1、新建CMakeLists链接ffmpeg的so
    • 2、封装命令行方法
      • (1)、导入main符号
      • (2)、将字符串解析为argc、argv
      • (3)、注册log回调输出安卓日志
      • (4)、阻止exit退出
      • 完整代码
    • 3、dart ffi调用
    • 4、java jni调用
  • 三、完整代码
  • 四、使用示例
    • 1、flutter调用命令行rtsp拉流
  • 总结
  • 附录
    • 1、dart将字符串解析为argc、argv


前言

前面的章节实现了ffmpeg全平台so的生成,接下来的步骤就是部署以及使用了,部署so还是比较简单的,用gradle和cmake都可以部署,部署好了就可以直接使用了,如果需要c++进行封装一层,则需要链接,对cmake熟悉的话链接也是比较简单的。


一、添加so

1、拷贝ffmpeg到项目

ffmpeg的生成方法可以参考前面三章,或者使用第二章 生成好的包。将ffmpeg生成好的包拷贝到如下目录。
在这里插入图片描述

并将so放到对应abi的目录中。
在这里插入图片描述

2、build.gradle指定so目录

指定的目录为abi名称的上一级。
在这里插入图片描述

sourceSets {
     main {
         jniLibs.srcDirs = ['src/main/cpp/jniLibs/ffmpeg4.3.6/24']       
     }
 }

如果是jni或者flutter的ffi直接调用ffmpeg的符号,则到这一步就结束了。
通过jni或ffi直接调用命令行:可以使用第三章 生成的包,里面有个libffmpeg.so,包含了ffmpeg的main符号,可以通过ffi直接调用。但需要解决2个问题:1、字符串解析为argc、argv。(dart.可参考附录)2、中断ffmpeg的exit操作。
否则继续往下


二、调用命令行

此步骤依赖第三章 生成的包:libffmpeg.so,是ffmpeg可执行程序,笔者将其生成了so。

1、新建CMakeLists链接ffmpeg的so

在项目中新建一个CMakeLists.txt,用于生成c++代码。
在这里插入图片描述
在CMakeLists.txt中填入以下内容,会链接ffmpeg的所有so,以及包含头文件。

# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
#拼接ffmpeg目录
set(PREFIX "${CMAKE_SOURCE_DIR}/jniLibs/ffmpeg4.3.6/24/${ANDROID_ABI}")
#包含ffmpeg头文件目录
include_directories(${PREFIX}/include)
#添加链接目录
link_directories(${PREFIX})
# 搜索ffmpeg目录下的所有.so文件
file(GLOB SO_FILES  "${PREFIX}/*.so" )  
# 获取目录下所有库名
foreach(SO_FILE IN LISTS SO_FILES)
    # 获取不带路径的文件名
    get_filename_component(LIB_NAME ${SO_FILE} NAME)
    list(APPEND FFMPEG_LIBRARIES "${LIB_NAME}")
endforeach()

find_library( 
        log-lib
        log )

add_library( 
        ffmpeg_v4_native-lib
        SHARED
        native-lib.cpp
)

target_link_libraries(
        ffmpeg_v4_native-lib
        #ffmpeg链接到native-lib
        ${FFMPEG_LIBRARIES}
        ${log-lib}
        android
)

在build.gradle中关联CmakeLists.txt
在这里插入图片描述

externalNativeBuild {
    cmake {
        path "src/main/cpp/CMakeLists.txt"
    }
}

2、封装命令行方法

此步骤依赖第三章 生成的包:libffmpeg.so,是ffmpeg可执行程序,笔者将其生成了so。
新建一个cpp文件用于实现ffmpeg命令行的调用。
在这里插入图片描述

(1)、导入main符号

// 导入ffmpeg的main符号,直接jni或者ffi调用也行,但是需要解决一个问题:ffmpeg内部会调用exit退出进程,在安卓会导致Activity退出。
//目前本文件的解决方案是在exit过程中抛出c++异常并捕获,中断后续退出操作。
extern "C" int main(int argc, char **argv);

(2)、将字符串解析为argc、argv

参考C++ 将字符串解析为argc、argv

(3)、注册log回调输出安卓日志

输出安卓日志方便调试。

//  自定义的日志回调函数,输入安卓日志
av_log_set_callback([](void *avcl, int level, const char *fmt, va_list vl)
								{
	if (level >= 0)
		level &= 0xff;
	if (level > av_log_get_level())
		return;
	AVBPrint part;
	av_bprint_init(&part, 0, 65536);
	av_vbprintf(&part, fmt, vl);
	//打印安卓日志标签为ffmpeg
	__android_log_print(ANDROID_LOG_INFO, "ffmpeg", "%s", part.str);
	av_bprint_finalize(&part, NULL); });

(4)、阻止exit退出

ffmpeg中有大量异常流程会调用exit,会导致整个进程退出,所以需要阻止这种情况,我们可以注册atexit并抛出异常,中断exit操作。

atexit([](){ throw 0; });

try catch中捕获异常,避免程序终止。

try
{
	// 调用ffmpeg的main
	return main(argv.size(), argv.data());
}
catch (...)
{
//main里面调用了exit会走到这里。
}

完整代码

在native-lib.cpp中加入如下代码

#include <jni.h>
#include <dlfcn.h>
#include <android/log.h>
#include <mutex>
#include <iostream>
#include <sstream>
#include <vector>
#include <string>
extern "C"
{
#include <libavutil/log.h>
#include <libavutil/bprint.h>
}
// 导入ffmpeg的main符号,直接jni或者ffi调用也行,但是需要解决一个问题:ffmpeg内部会调用exit退出进程,在安卓会导致Activity退出。
//目前本文件的解决方案是在exit过程中抛出c++异常并捕获,中断后续退出操作。
extern "C" int main(int argc, char **argv);
static std::vector<std::string> split(const std::string &str, char delim);
static std::vector<std::string> parseArgv(const std::string &str);
static bool _isFFmpegInit = false;
static std::mutex _mtx;
// 调用ffmpeg命令行,参数是命令行,例如:ffmpeg --version
extern "C" int ffmpeg_exec(char *shell)
{
	if (!_isFFmpegInit)
	{
		std::unique_lock<std::mutex> lck(_mtx);
		if (!_isFFmpegInit)
		{
			//  自定义的日志回调函数
			av_log_set_callback([](void *avcl, int level, const char *fmt, va_list vl)
								{
	if (level >= 0)
		level &= 0xff;
	if (level > av_log_get_level())
		return;
	AVBPrint part;
	av_bprint_init(&part, 0, 65536);
	av_vbprintf(&part, fmt, vl);
	__android_log_print(ANDROID_LOG_INFO, "ffmpeg", "%s", part.str);
	av_bprint_finalize(&part, NULL); });
			// 注册退出回调,在回调触发异常,阻止ffmpeg调用exit进程退出。
			atexit([]()
				   { throw 0; });
			_isFFmpegInit = true;
		}
	}
	try
	{
		// 解析命令行
		auto strArgv = parseArgv(shell);
		std::vector<char *> argv;
		for (int i = 0; i < strArgv.size(); i++)
			argv.push_back((char *)strArgv[i].c_str());
		// 调用ffmpeg的main
		return main(argv.size(), argv.data());
	}
	catch (...)
	{
	}
	return -1;
}

static std::vector<std::string> split(const std::string &str, char delim)
{
	std::vector<std::string> tokens;
	std::istringstream iss(str);
	std::string token;
	while (std::getline(iss, token, delim))
		if (!token.empty())
			tokens.push_back(token);
	return tokens;
}
static std::vector<std::string> parseArgv(const std::string &str)
{
	std::vector<std::string> args;
	int n = 0;
	for (auto i : split(str, '"'))
	{
		if (n++ % 2 == 0)
			for (auto j : split(i, ' '))
				args.push_back(j);
		else
			args.push_back(i);
	}
	return args;
}

3、dart ffi调用

导入方法,上一步生成的库名称是libffmpeg_v4_native-lib.so,里面提供的符号是ffmpeg_exec。

import 'package:ffi/ffi.dart';
import 'dart:ffi';
final int Function(Pointer<Utf8> shell) _ffmpeg_exec =
    DynamicLibrary.open("libffmpeg_v4_native-lib.so")
        .lookup<NativeFunction<Int32 Function(Pointer<Utf8>)>>('ffmpeg_exec')
        .asFunction(isLeaf: true);

封装成dart方法

//执行ffmpeg命令行。
int ffmpegExec(String s) {
  final cmd = s.toNativeUtf8();
  final ret = _ffmpeg_exec(cmd);
  malloc.free(cmd);
  return ret;
}

4、java jni调用

查找android使用jna调用so的方法。按上述步骤生成的so名称为libffmpeg_v4_native-lib.so,符号对应java为int ffmpeg_exec(String s)


三、完整代码

flutter示例项目,已加入第二章生成好的包里。


四、使用示例

1、flutter调用命令行rtsp拉流

作为测试命令,-f以及输出为空

Future<void> main() async {
  
  ffmpegExec(
      "ffmpeg -rtsp_transport tcp -i  rtsp://rtspstream:a4388c5a3f8c06031368479b29087a09@zephyr.rtsp.stream/movie  -vcodec  copy  -f  null  _  ");
  runApp(const MyApp());
}

效果预览

在这里插入图片描述


总结

以上就是今天讲述的内容,ffmpeg的so部署还是比较容易的,但是命令行的调用会麻烦一些,尤其是要解决ffmpeg退出问题,在c++中比较好解决,java理论上也比较好实现,如果在dart中则会比较麻烦,因为dart的方法通常不能跨线程调用,多线程的情况会出问题。


附录

1、dart将字符串解析为argc、argv

//将字符串解析为argv
List<String> _stringToArgv(String input) {
  // 使用正则表达式分割字符串
  // 这里使用了简单的空白字符分割,但是不会处理引号内的空格
  var parts = input.split(RegExp(r'\s+'));
  // 处理引号内的文本
  var argv = <String>[];
  for (var part in parts) {
    if (part.startsWith('"') && part.endsWith('"')) {
      // 去除引号
      argv.add(part.substring(1, part.length - 1));
    } else if (part.isNotEmpty) {
      argv.add(part);
    }
  }
  return argv;
}

将字符串数组,转为native type的argc、argv。
下列代码资源释放略

final args = _stringToArgv(s);
final argc=args.length;
final argv =
     calloc.allocate<Pointer<Char>>(sizeOf<Pointer>() * (args.length));
 for (int i = 0; i < args.length; i++) {
   argv[i] = args[i].toNativeUtf8().cast();
 }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1690154.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为CE6851-48S6Q-HI升级设备版本及补丁

文章目录 升级前准备工作笔记本和交换机设备配置互联地址启用FTP设备访问FTP设备升级系统版本及补丁 升级前准备工作 使用MobaXterm远程工具连接设备&#xff0c;并作为FTP服务器准备升级所需的版本文件及补丁文件 笔记本和交换机设备配置互联地址 在交换机接口配置IP&#…

文件传输服务应用1——java集成smb2/3实现文件共享方案详细教程和windows共享服务使用配置

在实际项目开发过程中&#xff0c;读取网络资源或者局域网内主机的文件是必要的操作和需求。而FTP&#xff08;文件传输协议&#xff09;和SMB&#xff08;服务器消息块&#xff09;是两种最为常见的文件传输协议。它们各自在文件传输领域拥有独特的优势和特点&#xff0c;但同…

3DMax

先转换为可编辑多边形 按“1”选择为点&#xff0c;点击目标焊接&#xff08;CtrlShiftw&#xff09;&#xff0c;然后点击一个顶点拉到另一个定点上&#xff1b; 选择一个面&#xff0c;点击塌陷&#xff08;CtrlAltC&#xff09;&#xff0c;四点合并为一个点&#xff1b; …

Ai指令-公众号内训课:学会ai指令+公众号的底层逻辑(7节课)

课程目录 第一课:详解公众号的流量机制.mp4 第二课:快速搞定公众号号设化装修.mp4 第三课:一期学员直呼牛X的课程&#xff0c;正确的爆文利用姿势.mp4 第五课:颗粒度级别拆解AI公众号爆文.mp4 第六课:AI提示词进阶技巧课。提示词自动化.mp4 第七课.实操演示提示词撰写工作…

element-ui输入框和多行文字输入框字体不一样解决

element-ui的type"textarea"的字体样式与其他样式不同 <el-input type"textarea"></el-input> <el-input ></el-input>设置&#xff1a; .el-textarea__inner::placeholder {font-family: "Helvetica Neue", Helvetic…

GPIO模拟spi时序点亮数码管

目录 spi.h spi.c main.c 实验效果 spi.h #ifndef __SPI_H__ #define __SPI_H__#include "stm32mp1xx_gpio.h" #include "stm32mp1xx_rcc.h"//spi初始化 void spi_init(); //spi写入数据 void spi_write(unsigned char data);#endif spi.c #include…

C#编程-.NET Framework使用工具类简化对象之间的属性复制和操作

在C#编程中&#xff0c;对象之间的属性复制和操作是一个常见的需求。为此&#xff0c;.NET Framework提供了多种实用工具库&#xff0c;如AutoMapper、ValueInjecter和ExpressMapper。这些库通过简化代码&#xff0c;提高了开发效率。本文将介绍这些工具库&#xff0c;比较它们…

Html基础笔记

Html超文本标记语言 (HyperText Markup Language) 超文本 指的是网页中可以显示的内容(图片,超链接,视频,) 标记语言 标记–>标签(标注) 例如:买东西的时候—>商品具有标签,看到标签就知道商品的属性(价格,材质,型号等,) 标记语言就是提供了很多的标签,不同的标签…

Android 几个简单的自定义对话框介绍

Android 几个简单的自定义对话框介绍 文章目录 一、前言二、对话框相关内容1、效果2、对话框显示的调用代码&#xff08;1&#xff09;原生对话框代码&#xff1a;&#xff08;2&#xff09;自定义对话框代码&#xff1a; 3、对话框SweetAlertDialog 主要实现代码&#xff1a;4…

在DAYU200上实现OpenHarmony跳转拨号界面

一、简介 日常生活中&#xff0c;打电话是最常见的交流方式之一&#xff0c;那么如何在OpenAtom OpenHarmony&#xff08;简称“OpenHarmony”&#xff09;中进行电话服务相关的开发呢&#xff1f;今天我们可以一起来了解一下如何通过电话服务系统支持的API实现拨打电话的功能…

噱头还是风口?剖析AI短剧出海的未来

导语 |去年以来&#xff0c;低成本、高回报的土味短剧在国内爆火&#xff0c;而这股风也成功地刮到海外。2024 年&#xff0c;越来越多的人把目光投向了拥有庞大市场的国外“蓝海”&#xff0c;当人们还在高谈阔论 Sora 是否颠覆影视行业的时候&#xff0c;AI 已经在出海短剧中…

Linux系统搭建Tale个人博客网站并实现无公网IP访问本地博客

文章目录 前言1. Tale网站搭建1.1 检查本地环境1.2 部署Tale个人博客系统1.3 启动Tale服务1.4 访问博客地址 2. Linux安装Cpolar内网穿透3. 创建Tale博客公网地址4. 使用公网地址访问Tale 前言 今天给大家带来一款基于 Java 语言的轻量级博客开源项目——Tale&#xff0c;Tale…

【嵌入式芯片开发】不使用MicroLib的串口重定向万能预编译配置(适用于ARMCC、AC6等不同的编译器及版本)

【嵌入式芯片开发】不使用MicroLib的串口重定向万能预编译配置&#xff08;适用于ARMCC、AC6等不同的编译器及版本&#xff09; 文章目录 基本的串口重定向接收中断与scanf不能同时工作重定向卡死、低功耗一直唤醒 串口重定向万能预编译配置附录&#xff1a;Cortex-M架构的Sys…

【设计模式深度剖析】【5】【创建型】【原型模式】| 类比群发邮件,加深理解

&#x1f448;️上一篇:建造者模式 | 下一篇:创建型设计模式对比&#x1f449;️ 目录 原型模式(Prototype Pattern)概览定义英文原话直译 3个角色类图1. 抽象原型&#xff08;Prototype&#xff09;角色2. 具体原型&#xff08;Concrete Prototype&#xff09;角色3. 客户…

【cocos creator 】生成六边形地图

想要生成一个六边形组成的地图 完整代码示例 以下是完整的代码示例&#xff0c;包含了注释来解释每一步&#xff1a; cc.Class({extends: cc.Component,properties: {hexPrefab: {default: null,type: cc.Prefab},mapWidth: 10, // 网格的宽度&#xff08;六边形的数量&am…

基于Pytorch框架全连接神经网络对手势图片识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 手势识别是计算机视觉领域的一个重要研究方向&#xff0c;具有广泛的应用前景&#xff0c;如人…

react中怎么为props设置默认值

在React中&#xff0c;你可以使用ES6的类属性&#xff08;class properties&#xff09;或者函数组件中的默认参数&#xff08;default parameters&#xff09;来定义props的默认值。 1.类组件中定义默认props 对于类组件&#xff0c;你可以在组件内部使用defaultProps属性来…

基于Java的推箱子游戏设计与实现(论文 + 源码)

【免费】关于基于JAVA的推箱子游戏.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89325018 基于Java的推箱子游戏设计与实现 摘 要 社会在进步&#xff0c;人们生活质量也在日益提高。高强度的压力也接踵而来。社会中急需出现新的有效方式来缓解人们的压力。…

Java开发大厂面试第20讲:什么是分布式锁?Redi 怎样实现的分布式锁?

“锁”是我们实际工作和面试中无法避开的话题之一&#xff0c;正确使用锁可以保证高并发环境下程序的正确执行&#xff0c;也就是说只有使用锁才能保证多人同时访问时程序不会出现问题。 我们本课时的面试题是&#xff0c;什么是分布式锁&#xff1f;如何实现分布式锁&#xf…

秋招突击——算法打卡——5/24——无重复字符的最长字串

题目描述 实现代码 // 无重复字符的最长子串 int lengthOfLongestSubstring(string s) {int l 0,r 0;int res 0;unordered_map<char,int> temp;while(l < s.size()){temp[s.at(l)] l;for (r l 1; r < s.size() ; r) {if(temp.count(s.at(r))) break;else te…