如何为 Flutter(Android 和 iOS)本地编译 FFmpeg 二进制文件

FFmpeg Flutter Kit 软件包已停用,预编译的二进制文件也已移除。这意味着开发者现在需要在本地编译 FFmpeg 二进制文件才能在 Flutter 应用中使用它们。在本指南中,我们将逐步讲解为 Android 和 iOS 平台编译 FFmpeg 所需的步骤,确保您能够继续在 Flutter 项目中使用 FFmpeg。

1. 克隆 FFmpeg Kit 仓库

首先,从 GitHub 克隆 FFmpeg Kit 的源代码。打开终端并运行:

git clone https://github.com/arthenica/ffmpeg-kit.git

2. 通过 Homebrew 安装依赖项

在编译二进制文件之前,我们需要使用 Homebrew 安装一些必要的依赖项。这些软件包将有助于构建 Android 和 iOS 的二进制文件。

安装pkg-config(用于管理库编译和链接标志)、libtool(用于管理共享库)、NASM(用于构建汇编代码)、Automake(用于自动生成 Makefile 文件)和Xcode 命令行工具(如果尚未安装,请接受 Xcode 许可证)。

brew install pkg-config
brew install libtool  
brew install nasm 
brew install automake
brew install autoconf
xcode-select --install
sudo xcodebuild -license accept

这些依赖项对于在 macOS 上成功编译 FFmpeg 至关重要。

安装依赖项后,导航到克隆 FFmpeg Kit 存储库的目录:

cd ffmpeg-kit

3. 编译 FFmpeg for iOS

要编译 FFmpeg for iOS,需要运行 ios.sh 脚本,它能自动执行编译过程。您可以根据自己的具体需求(如启用某些编解码器或库)自定义该命令,只需让 Chat GPT 或任何 AI 代理根据您的需求构建命令即可,但不要忘了在末尾加上 -xcframework,以确保输出是跨平台的框架格式

./ios.sh --enable-gpl --enable-x264 --target=12.1 --xcframework

运行脚本后,编译将在 prebuilt/bundle-apple-framework-ios 目录中生成必要的框架文件。编译完成后,确保 build.log 文件中没有错误。

4. 在 iOS 项目中添加框架

接下来,将 prebuilt/bundle-apple-framework-ios 中的框架文件复制到 ffmpeg-kit/flutter/flutter/ios/Frameworks 目录中的 iOS 项目中。

mkdir -p flutter/flutter/ios/Frameworks
cp -r prebuilt/bundle-apple-xcframework-ios/* flutter/flutter/ios/Frameworks/

然后,需要将生成的框架添加到您的 iOS 项目中。打开 ffmpeg-kit/flutter/flutter/ios/ffmpeg_kit_flutter.podspec 文件,在 subspec 部分添加以下一行:

//将 https 改为 precompiled-gpl(或任何您想要的名称)
s.default_subspec = 'precompiled-gpl'

//然后,添加以下部分
s.subspec 'precompiled-gpl' do |ss|
    ss.vendored_frameworks = 'Frameworks/ffmpegkit.xcframework',
                             'Frameworks/libavcodec.xcframework',
                             'Frameworks/libavdevice.xcframework',
                             'Frameworks/libavfilter.xcframework',
                             'Frameworks/libavformat.xcframework',
                             'Frameworks/libavutil.xcframework',
                             'Frameworks/libswresample.xcframework',
                             'Frameworks/libswscale.xcframework'
end

此步骤确保iOS项目可以链接到已编译的FFmpeg库。

现在,我们已经完成了 IOS 部分。

5. 为Android编译FFmpeg

对于 Android,需要使用android.sh脚本编译 FFmpeg。首先,请确保您的 Android NDK 和 SDK 已正确设置。如果尚未全局设置,您可以在终端中手动定义路径:

//注意:较高的NDK版本可能会失败
export ANDROID_NDK_ROOT= "/Users/ibrahimeltayfe/Library/Android/sdk/ndk/25.1.8937393" 
export ANDROID_SDK_ROOT= "/Users/ibrahimeltayfe/Library/Android/sdk"

接下来,运行android.sh脚本为 Android 编译 FFmpeg:

./android.sh --enable-gpl --enable-x264 --api-level=24

这将生成一个.aar文件,它是包含已编译的 FFmpeg 二进制文件的 Android 库。

6. 将 FFmpeg .aar 集成到您的 Android 项目中

生成 .aar 文件后,将其移至 flutter/flutter/android/libs 目录。

mkdir -p flutter/flutter/android/libs
cp -r prebuilt/bundle-android-aar/ffmpeg-kit/ffmpeg-kit.aar flutter/flutter/android/libs/

您还需要加入其他必要的库:

1)下载以下 JAR 文件并将其放入 android/libs 目录:

  • smart-exception-java-0.2.1.jar
  • smart-exception-common-0.2.1.jar

您可以在这里找到它们:https://github.com/tanersener/smart-exception/releases/tag/v0.2.1

2)修改 build.gradle 文件,将 .aar 和 .jar 文件列为依赖项:

dependencies {
  //注释此行
  //implementation 'com.arthenica:ffmpeg-kit-https:6.0-2' 

  //添加此行
  implementation fileTree(dir: 'libs', include: ['*.jar']) //'*.aar'
}

但这里有一个问题,我们不能在实现中直接包含 *.aar,因为它会引发错误:

What went wrong:
Execution failed for task ':ffmpeg_kit_flutter:bundleDebugAar'.
> Error while evaluating property 'hasLocalAarDeps' of task ':ffmpeg_kit_flutter:bundleDebugAar'.
   > Direct local .aar file dependencies are not supported when building an AAR. The resulting AAR would be broken because the classes and Android resources from any local .aar file dependencies would not be packaged in the resulting AAR. Previous versions of the Android Gradle Plugin produce broken AARs in this case too (despite not throwing this error). The following direct local .aar file dependencies of the 

因此,我们应该将 .aar 文件上传到远程 maven 存储库,或者创建一个本地 maven 存储库,我遇到了一个 fork,作者在其中创建了一个本地 Maven 存储库,因此我借用了他的代码:)

https://github.com/hellohejinyu/ffmpeg_kit_flutter_full_gpl/blob/main/android/build.gradle

这就是我现在的 build.gradle:

import java.security.MessageDigest
import java.security.NoSuchAlgorithmException

buildscript {
    repositories {
        google()
        mavenCentral()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:8.1.0'
    }
}

String localMavenPath = project.mkdir("build").absolutePath

rootProject.allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url "file://$localMavenPath" }
    }
}

apply plugin: 'com.android.library'

android {
    // Conditional for compatibility with AGP <4.2.
    if (project.android.hasProperty("namespace")) {
        namespace 'com.arthenica.ffmpegkit.flutter'
    }

    compileSdkVersion 33

    defaultConfig {
        minSdkVersion 24
        targetSdkVersion 33
        versionCode 603
        versionName "6.0.3"
    }

    buildTypes {
        release {
            minifyEnabled false
        }
    }
    lintOptions {
        disable 'GradleCompatible'
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    task setupDependencies(type: Exec) {
        workingDir '.'
        commandLine 'sh', '-c', '../scripts/setup_android.sh'
        onlyIf { !file('libs/com.arthenica.ffmpegkit-flutter-6.0.3.aar').exists() }
    }
}

dependencies {
    implementation 'androidx.annotation:annotation:1.5.0'
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}


String aarPath = localMavenPath

task useAar {
    File file = project.file("libs")
    if (file.exists() && file.isDirectory()) {
        file.listFiles(new FileFilter() {
            @Override
            boolean accept(File pathname) {
                return pathname.name.endsWith(".aar")
            }
        }).each { item ->
            String aarName = item.name.substring(0, item.name.length() - 4)
            String[] aarInfo = aarName.split("-")
            String sha1 = getFileSha1(item)
            String md5 = getFileMD5(item)
            println("aar: " + aarInfo + " file sha1:" + sha1 + " md5:" + md5)
            String fromStr = item.path
            String intoStr = aarPath + "/" + aarInfo[0].replace(".", "/") + "/" + aarInfo[1] + "/" + aarInfo[2]
            String newName = aarInfo[1] + "-" + aarInfo[2] + ".aar"

            project.copy {
                from fromStr
                into intoStr
                rename(item.name, newName)
            }

            project.file(intoStr + "/" + newName + ".md5").write(md5)
            project.file(intoStr + "/" + newName + ".sha1").write(sha1)

            String pomPath = intoStr + "/" + newName.substring(0, newName.length() - 4) + ".pom"
            project.file(pomPath).write(createPomStr(aarInfo[0], aarInfo[1], aarInfo[2]))
            project.file(pomPath + ".md5").write(getFileMD5(project.file(pomPath)))
            project.file(pomPath + ".sha1").write(getFileSha1(project.file(pomPath)))

            String metadataPath = project.file(intoStr).getParentFile().path + "/maven-metadata.xml"
            project.file(metadataPath).write(createMetadataStr(aarInfo[0], aarInfo[1], aarInfo[2]))
            project.file(metadataPath + ".md5").write(getFileMD5(project.file(metadataPath)))
            project.file(metadataPath + ".sha1").write(getFileSha1(project.file(metadataPath)))
            dependencies {
                implementation "${aarInfo[0]}:${aarInfo[1]}:${aarInfo[2]}"
            }
        }
    }
}

public static String createMetadataStr(String groupId, String artifactId, String version) {
    return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
            "<metadata>\n" +
            "  <groupId>$groupId</groupId>\n" +
            "  <artifactId>$artifactId</artifactId>\n" +
            "  <versioning>\n" +
            "    <release>$version</release>\n" +
            "    <versions>\n" +
            "      <version>$version</version>\n" +
            "    </versions>\n" +
            "    <lastUpdated>${new Date().format('yyyyMMdd')}000000</lastUpdated>\n" +
            "  </versioning>\n" +
            "</metadata>\n"
}

public static String createPomStr(String groupId, String artifactId, String version) {
    return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
            "<project xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\" xmlns=\"http://maven.apache.org/POM/4.0.0\"\n" +
            "    xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
            "  <modelVersion>4.0.0</modelVersion>\n" +
            "  <groupId>$groupId</groupId>\n" +
            "  <artifactId>$artifactId</artifactId>\n" +
            "  <version>$version</version>\n" +
            "  <packaging>aar</packaging>\n" +
            "</project>\n"
}

public static String getFileSha1(File file) {
    FileInputStream input = null;
    try {
        input = new FileInputStream(file);
        MessageDigest digest = MessageDigest.getInstance("SHA-1");
        byte[] buffer = new byte[1024 * 1024 * 10];

        int len = 0;
        while ((len = input.read(buffer)) > 0) {
            digest.update(buffer, 0, len);
        }
        String sha1 = new BigInteger(1, digest.digest()).toString(16);
        int length = 40 - sha1.length();
        if (length > 0) {
            for (int i = 0; i < length; i++) {
                sha1 = "0" + sha1;
            }
        }
        return sha1;
    }
    catch (IOException e) {
        System.out.println(e);
    }
    catch (NoSuchAlgorithmException e) {
        System.out.println(e);
    }
    finally {
        try {
            if (input != null) {
                input.close();
            }
        }
        catch (IOException e) {
            System.out.println(e);
        }
    }
}

public static String getFileMD5(File file) {
    FileInputStream input = null;
    try {
        input = new FileInputStream(file);
        MessageDigest digest = MessageDigest.getInstance("MD5");
        byte[] buffer = new byte[1024 * 1024 * 10];

        int len = 0;
        while ((len = input.read(buffer)) > 0) {
            digest.update(buffer, 0, len);
        }
        String md5 = new BigInteger(1, digest.digest()).toString(16);
        int length = 32 - md5.length();
        if (length > 0) {
            for (int i = 0; i < length; i++) {
                md5 = "0" + md5;
            }
        }
        return md5;
    }
    catch (IOException e) {
        System.out.println(e);
    }
    catch (NoSuchAlgorithmException e) {
        System.out.println(e);
    }
    finally {
        try {
            if (input != null) {
                input.close();
            }
        }
        catch (IOException e) {
            System.out.println(e);
        }
    }
}

preBuild.dependsOn setupDependencies
useAar.dependsOn setupDependencies
preBuild.dependsOn useAar

不要忘记将 aar 文件的名称从libs/ffmpeg-kit.aar更改为libs/com.arthenica.ffmpegkit-flutter-6.0.3.aar

5. 解决 Flutter 和 FFmpeg Kit 与 Android 的兼容性问题

您会遇到的最后一个问题与 Flutter 在最新更新中取消了对 Embedded V1 的支持有关。

MSOB7YY 制作的 fork 修复了这一问题,只需将该 fork 中的更新代码替换 FFmpegKitFlutterPlugin.java 文件中的现有代码即可:

https://github.com/arthenica/ffmpeg-kit/blob/1d29b16bfca1baf03bf005cacebacdd0a225f731/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/FFmpegKitFlutterPlugin.java

您可以在FFmpegKitFlutterPlugin.java以下位置找到您的路径:
flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/FFmpegKitFlutterPlugin.java

就是这样!要将 ffmpeg-kit 集成到您的项目中,只需在 pubspec.yaml 中添加以下一行即可:

dependencies:
  ffmpeg_kit_flutter:
    path: path-to-your-ffmpegkit-package/flutter/flutter

此外,确保 Android minSdkVersion 设置为 24,iOS deployment target 设置为 12.1 或更高版本。

最后,跟踪和关注正在进行的修复

如果要想避免未来出现令人头疼的问题,那么关注社区的持续更新和修复是个好主意。有很多贡献者会创建分支并提供常见问题的解决方案。

您可以在此处跟踪这些贡献和正在进行的修复:GitHub 上的 FFmpeg Kit 问题

作者:Ibrahimeltayfe

本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/57555.html

(0)

相关推荐

发表回复

登录后才能评论