FFmpegを用いて音入り動画を作成できる無料アプリ

# 9VAeきゅうべえ Windows / Linux /Android 版の欠点

[フリーソフト9VAeきゅうべえ](https://qiita.com/danjiro/items/253e5a33a38599098274)は、下のような動画が簡単につくれるベクトルアニメーションアプリです。２Ｄグラフィックス作成機能があり、音も入れられます。

![(new-sample).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/3ed5638d-905e-f45c-d1a8-08ed24f25ea9.gif)

しかし音入りの MP4動画を作ることは、Windows / Linux /Android 版ではできませんでした。（Mac / iPhone / iPad版は可能。上はアニメGIF）

今回、[FFmpegで音入りアニメーション動画を作成する方法](https://dnjiro.hatenablog.com/entry/ffmpeg-9va)を見つけましたので紹介します。この方法をつかって、9VAeきゅうべえは、どのOSでも動画出力できるようになりました。

- [9VAe ダウンロードはこちら（Win/Mac/Linux/iOS/Android/Chromebook）](https://9vae.blogspot.com/p/9vae-download.html)

# 9VAe きゅうべえの音声の仕様

- 9VAeには、効果音、音楽の２トラックがあり、２つの音を同時に鳴らすことができます。
- 音が鳴っているトラックで別の音を鳴らすと前の音は停止します。
- 音の開始時間はキーフレームからわかります。
- 9VAeは音のファイルを指定時刻に鳴らすだけで、音を修正する機能はありません。

![音声トラック.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/6d026213-fedd-8c5a-3bb8-1a31f359376f.gif)

# FFmpeg の機能

- FFmpeg は連番画像から動画を作成する機能があります。
- 動画に音を入れる機能があります。
- 動画や音をカットしたり、つないだりする機能があります。
- [オプションの説明](http://www.ffmpeg.org/ffmpeg.html#Options)

ただ、その記述方法がわかりにくく、試行錯誤を繰り返しました。結局、理解したこと。

- 動画に音声を合成するときに、時間を指定して、そこに音を入れる機能はないみたい。途中から音を鳴らしたい場合は、音の前に無音をいれる。短い音をいれる場合、音の後ろに無音をいれなければならない。
- そのため、鳴らす音の長さを正確にはかる必要がある。
- 映像に２トラックの音をいれることもできるはずだが、うまくならなかった。結局、効果音トラック、音楽トラックの音を合成してひとつの音にした。

今回、FFmpegで使用した機能は以下です。いろんなOSで同じように動作します。

|機能|命令|
|:--|:--|
|連番画像から映像作成|-vcodec mpeg4 -pix_fmt yuv420p |
|指定した時間の無音作成|-f lavfi -i aevalsrc|
|指定した時間で音をカット|-t 秒|
|２つの音をつなぐ|-filter_complex concat|
|作成した音の時間を取得|関数を自作|
|２トラックの音を重ねる|-filter_complex|
|映像に音を追加する|-c:v copy -c:a copy|

これらの機能をつかって、効果音トラック、音楽トラックの音を無音をはさんで順番につなげていき、最後に、２つのトラックを合成して１トラックにし、映像に入れます。具体例を示します。

## 連番画像から映像作成

```sh
ffmpeg -y -r １秒のフレーム数 -i %04d.jpg -b 6000k -vcodec mpeg4 -pix_fmt yuv420p video.mp4 
```

%04d.jpg は連番画像（%04d は、0001,0002,0003...）9VAeから指定したフレームレート、画像サイズで出力します。なお、画像サイズを奇数にするとスマホで再生できないようです。
video.mp4 は出力ファイル名
-y は同名ファイルがあったときの上書き指定

## 指定した時間(秒)の無音作成

```sh
ffmpeg -f lavfi -i aevalsrc=0:d=秒 -ac 2 -strict -2  -c:a aac -ab 256k  out.m4a
```
秒は小数可能
-strict -2 がないとエラーになるときがある

## 指定した時間(秒)で音をカット

```sh
ffmpeg -i in.wav -t 秒 -c:a aac -ab 256k out.m4a
```

in.wav が 指定した時間より短い場合、out.m4a は、指定した時間より短い音になる。out.m4a の時間を調べ、必要なら、無音を追加しなければならない。
-ab 256k はビットレート指定で CD音質

## ２つの音をつなぎ、指定した時間(秒)で音をカット

```sh
ffmpeg -i in.m4a -i in.wav -t 秒 -filter_complex concat=n=2:v=0:a=1   -ab 256k out.m4a
```

in.m4a の後ろに in.wav をつなぐ

## ２トラックの音を重ねる

```sh
ffmpeg -y -i trk1.m4a -i trk2.m4a -filter_complex "[0:a][1:a]amix=inputs=2[a]" -map "[a]" -ab 256k out.m4a
```
２つの音、trk1.m4a  trk2.m4a を重ねて１トラックの音  out.m4a にする

## 映像に音をつける

```sh
ffmpeg -y -i video.mp4 -i add.m4a -c:v copy -c:a copy out.mp4
```
映像 video.mp4 に 音 add.m4a をつける

## 音 m4a の時間（msec）を取得

以下のようなプログラムを自作しました。エラーの場合、負の値が返ります。

```c
#include <stdio.h>

#define CHis(a,b,c,d) ((a)==hd[4] && (b)==hd[5] && (c)==hd[6] && (d)==hd[7])
#define Int3(a) *(a)*0x10000   + *((a)+1)*0x100   + *((a)+2)
#define Int4(a) *(a)*0x1000000 + *((a)+1)*0x10000 + *((a)+2)*0x100 + *((a)+3)
//m4a 形式の音の再生時間（msec）を取得する
long AcGetM4aMsec(char *fileName)
{
    FILE *fp; int bb,cc; long ret=-1;
    unsigned char hd[8],work[1024]; 

    // ファイルオープン
    fp = fopen(fileName, "rb");
    for(;fp;){
        // 先頭フレームのヘッダ8Byteを読み込む
        if(fread(hd, 1, 8, fp) != 8) break;
        bb = Int4(hd);//チャンクサイズ
        bb-=8;
        if(CHis('m','o','o','v')){//メタデータ発見
            for(;bb>0;){
                if(fread(hd, 1, 8, fp) != 8) break; 
                bb-=8;
                cc = Int4(hd);//チャンクサイズ
                bb-=cc;
                for(;cc>0;cc-=1024){
                    if(fread(work, 1, cc>1024?1024:cc, fp) < 1024) break;
                }
                if(CHis('m','v','h','d')){//時間情報が入っている
                    int version=work[0];
                    int flags = Int3(work+1);
                    int creatTime = Int4(work+4);
                    int modifTime = Int4(work+8);
                    int timeScale = Int4(work+12);//秒60 の場合60
                    int duration  = Int4(work+16);//duration/timeScale=秒
                    ret = (long)((double)duration*1000/timeScale);
                    goto exit;
                }
            }
            continue;
        }
        for(;bb>0;bb-=1024){//データ読み飛ばし
            if(fread(work, 1, bb>1024?1024:bb, fp) < 1024) break;
        }
    }
exit:
    if(fp) fclose(fp);
    return ret;
}

```