UnityでMobile-FFmpegを使う

***※2021/4/19***
Mobile-FFmpegの更新は [FFmpegKit](https://github.com/arthenica/ffmpeg-kit) に移行されました。

## はじめに
Unityで開発中のアプリ内で動画ファイル・音声ファイルを加工する処理が必要になり、Mobile-FFmpegを利用することになった。導入して使えるようになるまでにやったことのメモ。

## Mobile-FFmpeg とは
<img src="https://raw.githubusercontent.com/tanersener/mobile-ffmpeg/master/docs/assets/mobile-ffmpeg-logo-v7.png" width="320">
FFmpegのAndroid/iOS対応版オープンソースライブラリ。内容はFFmpegおよびFFprobeの実装と外部ライブラリからなる。アップデートも活発に行われている。

コマンドライン版FFmpegと全く同じ感覚でオプションコマンド文字列を指定して呼び出すシンプルなAPIになっているのが特徴。
~~https://github.com/tanersener/mobile-ffmpeg~~
~~https://github.com/tanersener/ffmpeg-kit~~
[https://github.com/arthenica/ffmpeg-kit](https://github.com/arthenica/ffmpeg-kit)


## 注意
MobileFFmpegは40以上の外部依存ライブラリがあるため、まずMobileFFmpegをアプリのどこに使うのか予め使用目的を定め、それに必要な機能と必須のライブラリを明確にしておくと良いでしょう。

この記事はMobile-FFmpeg Version 4.4、Unity2018以降を対象に書かれています。

## ビルド済みバイナリ
自前でビルドできますがビルド済みバイナリも用意されているので今回はこれを使いました。
バイナリは内包する外部ライブラリの違いで8種類のバリエーションが用意されています。

**Packages**

* _`min`_ : 最小限のパッケージ
* _`min-gpl`_ : minにGPLライセンスライブラリを追加したもの
* _`https`_ : TLS関係のライブラリを追加したもの
* _`https-gpl`_ : httpsにGPLライセンスライブラリを追加したもの
* _`audio`_ : オーディオ関係のライブラリを追加したもの
* _`video`_ : ビデオ関係のライブラリを追加したもの
* _`full`_ : フルセット（GPLライセンスを含まない）
* _`full-gpl`_ : フルセット（GPLライセンスを含む）

用途に応じて上記の中からpackageを選択し、ファイルを[ダウンロード](https://github.com/tanersener/mobile-ffmpeg/releases)
Android用のライブラリはaarファイルをダウンロード
・(ファイル名) : `mobile-ffmpeg-[Package]-[Version].aar`
iOS用のライブラリはxcframework.zipファイルをダウンロードして解凍
・(ファイル名) : `mobile-ffmpeg-[Package]-[Version]-ios-xcframework.zip`

パッケージごとの詳細は以下を確認。
[2.1 Packages](https://github.com/tanersener/mobile-ffmpeg/blob/master/README.md#21-packages)

## Unityへのインポート

ダウンロードした
.aarファイルを
`/Assets/Plugins/Android`
zipを解凍してframeworkファイル群を
`/Assets/Plugins/iOS`
に配置。

## Unity側のC#から呼び出す
AndroidとiOSそれぞれ、Pluginモジュール呼び出し用ラッパーを実装します。基本的にはExecuteとCancelのAPI２つ呼び出せればOKです。

MobileFFmpegのGitHub、isshue#258のスレッドを参考にさせていただきました。
https://github.com/tanersener/mobile-ffmpeg/issues/258#issuecomment-663913978

### コード
ソースファイルは３つあります。.mmファイルは`Assets/Plugins/iOS`に置いてください。

```C#:FFmpegWrapper.cs
using UnityEngine;

public class FFmpegWrapper
{
    private static int Execute(string command)
    {
#if UNITY_ANDROID
        using (AndroidJavaClass configClass = new AndroidJavaClass("com.arthenica.mobileffmpeg.Config"))
        {
            AndroidJavaObject paramVal = new AndroidJavaClass("com.arthenica.mobileffmpeg.Signal").GetStatic<AndroidJavaObject>("SIGXCPU");
            configClass.CallStatic("ignoreSignal", new object[] { paramVal });

            using (AndroidJavaClass ffmpeg = new AndroidJavaClass("com.arthenica.mobileffmpeg.FFmpeg"))
            {
                int code = ffmpeg.CallStatic<int>("execute", new object[] { command });
                return code;
            }
        }
#elif UNITY_IOS
        return MobileFFmpegIOS.Execute(command);
#else
        return 0;
#endif
    }

    private static int Cancel()
    {
#if UNITY_ANDROID
        using (AndroidJavaClass configClass = new AndroidJavaClass("com.arthenica.mobileffmpeg.Config"))
        {
            using (AndroidJavaClass ffmpeg = new AndroidJavaClass("com.arthenica.mobileffmpeg.FFmpeg"))
            {
                int code = ffmpeg.CallStatic<int>("cancel");
                return code;
            }
        }
#elif UNITY_IOS
        return MobileFFmpegIOS.Cancel();
#else
        return 0;
#endif
    }
}
```
```C#:MobileFFmpegIOS.cs
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;


public class MobileFFmpegIOS : MonoBehaviour
{
#if UNITY_IOS
    [DllImport("__Internal")]
    private static extern int _execute(string command);

    [DllImport("__Internal")]
    private static extern void _cancel();
#endif

    /**
     * Synchronously executes FFmpeg command provided. Space character is used to split command
     * into arguments.
     *
     * @param command FFmpeg command
     * @return zero on successful execution, 255 on user cancel and non-zero on error
     */
    public static int Execute(string command)
    {
        int result = -1;
#if UNITY_IOS
        if (Application.platform == RuntimePlatform.IPhonePlayer)
        {
            result = _execute(command);
        }
#endif

        return result;
    }

    /**
     * Cancels an ongoing operation.
     *
     * This function does not wait for termination to complete and returns immediately.
     */
    public static void Cancel()
    {
#if UNITY_IOS
        if (Application.platform == RuntimePlatform.IPhonePlayer)
        {
            _cancel();
        }
#endif
    }
}
```

以下は`Assets/Plugins/iOS`に配置

```Objective-C:MobileFFmpeg.mm
//In unity, You'd place this file in your "Assets>plugins>ios" folder
//Objective-C Code
#import <mobileffmpeg/MobileFFmpeg.h>

extern "C"
{
    /**
    * Synchronously executes FFmpeg command provided. Space character is used to split command
    * into arguments.
    *
    * @param command FFmpeg command
    * @return zero on successful execution, 255 on user cancel and non-zero on error
    */
    int _execute(const char* command)
    {
        return [MobileFFmpeg execute: @(command)];
    }
    
    /**
    * Cancels an ongoing operation.
    *
    * This function does not wait for termination to complete and returns immediately.
    */
    void _cancel()
    {
        [MobileFFmpeg cancel];
    }
}
```

## Unityから呼び出す
Executeのパラメータにオプションコマンドを文字列で渡し呼び出します（"ffmpeg "は不要なので注意）。成功すれば0が返ります。

```C#:Example
private void Test()
{
    var input = Application.persistentDataPath + "/input.mov";
    var output = Application.persistentDataPath + "/output.mp4";
    int rc = FFmpegWrapper.Execute(string.Format("-i {0} {1}", input, output));
    Debug.Log("Return Code is " + rc);
}
```

非同期実行の`FFmpeg.executeAsync`関数も用意されていますが試してません。申し訳ございません。。。

## その他
### ○Xcodeでリンカーエラーが出たら
**[Link Binary with Libraries]**に以下のライブラリがなければ追加する。
 bzip2, iconv, libuuid, zlib, AudioToolbox, VideoToolbox, AVFoundation
(libz.tbd, libbz2.tbd, libiconv.tbd)

Armv7に対応してないっぽいので**Architectures**はArm64専用でビルドしました

### ○実行時エラー
・ファイル書き込み権限がない、Outputファイルと同名のファイルがすでに存在する場合失敗します
・コマンドに応じて必要なライブラリが決まるので、モジュールがない場合は失敗します

## 参考記事
AndroidでFFmpegを使って音声ファイルを解析・変換する
[https://qiita.com/tarumzu/items/a4d15957a144f520f842](https://qiita.com/tarumzu/items/a4d15957a144f520f842)
それFFmpegで出来るよ！
[https://qiita.com/cha84rakanal/items/e84fe4eb6fbe2ae13fd8](https://qiita.com/cha84rakanal/items/e84fe4eb6fbe2ae13fd8)
FFmpegで動画をGIFに変換
[https://qiita.com/wMETAw/items/fdb754022aec1da88e6e](https://qiita.com/wMETAw/items/fdb754022aec1da88e6e)