# 帳票設計書 14-動画MJPEG AVI

## 概要

本ドキュメントは、Godot Engineにおける動画出力機能のうち、Motion JPEG（MJPEG）形式のAVIファイル出力に関する設計書である。ゲームプレイ録画を単一のAVIファイルとして出力し、広く互換性のある動画形式で保存する。

### 本帳票の処理概要

本機能は、Godot Engineの MovieWriter システムを使用して、ゲーム画面をフレームごとにJPEG圧縮し、AVI（Audio Video Interleave）コンテナ形式で映像・音声を多重化して出力する。MJPEGはフレーム間圧縮を行わないため、編集時のシーク性能が良好で、幅広いプレイヤー・編集ソフトで再生可能である。

**業務上の目的・背景**：ゲーム開発において、PNG連番よりもファイル管理が容易な単一ファイル形式での録画が求められることがある。MJPEG AVIは古くから使われている形式で互換性が高く、ほとんどの動画編集ソフトウェアで読み込み可能。また、フレーム単位でのアクセスが容易なため、動画編集ワークフローに適している。圧縮によりPNG連番より大幅にファイルサイズを削減できる。

**帳票の利用シーン**：ゲームプレイ動画の録画、デモ動画の作成、QAテストの記録、チュートリアル動画の収録、SNS用クリップの作成など。

**主要な出力内容**：
1. AVI形式の動画ファイル（.avi）
2. MJPEG圧縮された映像ストリーム（品質調整可能）
3. PCMオーディオストリーム（16ビットまたは32ビット）
4. インデックスチャンク（idx1）による高速シーク対応

**帳票の出力タイミング**：エディタのムービー録画機能を有効にしてゲームを実行した際、またはコマンドラインから `--write-movie` オプションで .avi ファイルを指定して実行した際に出力される。

**帳票の利用者**：ゲーム開発者、マーケティング担当者、動画制作者、QAエンジニア

## 帳票種別

動画出力 / AVIコンテナファイル出力

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | エディタ | Project > Movie Maker | ムービー録画設定・実行 |
| - | コマンドライン | --write-movie path.avi | コマンドライン引数指定 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | AVI（.avi） |
| 用紙サイズ | N/A（動画出力） |
| 向き | N/A |
| ファイル名 | 任意（呼び出し元で指定） |
| 出力方法 | ファイル保存 |
| 文字コード | N/A（バイナリ） |

### 映像設定

| 項目 | 内容 |
|-----|------|
| コーデック | MJPEG (Motion JPEG) |
| 品質 | project設定 editor/movie_writer/video_quality（デフォルト0.75） |
| ビット深度 | 24ビット（RGB） |
| フレームレート | ゲーム設定のFPSに依存 |

### 音声設定

| 項目 | 内容 |
|-----|------|
| フォーマット | PCM（非圧縮） |
| サンプリングレート | project設定 editor/movie_writer/mix_rate |
| ビット深度 | project設定 editor/movie_writer/audio_bit_depth（16または32） |
| チャンネル数 | 2〜8（スピーカーモード依存） |

## 帳票レイアウト

### レイアウト概要

AVI形式はRIFFコンテナ構造に従い、ヘッダー、データ、インデックスで構成される。

```
┌─────────────────────────────────────┐
│     RIFF Header ("RIFF____AVI ")    │
├─────────────────────────────────────┤
│     LIST "hdrl" (Header List)       │
│     ├── avih (Main AVI Header)      │
│     ├── LIST "strl" (Video Stream)  │
│     │   ├── strh (Stream Header)    │
│     │   └── strf (Stream Format)    │
│     ├── LIST "strl" (Audio Stream)  │
│     │   ├── strh (Stream Header)    │
│     │   └── strf (Stream Format)    │
│     └── LIST "odml" (Extended)      │
├─────────────────────────────────────┤
│     LIST "movi" (Movie Data)        │
│     ├── 00db (Video Frame 0)        │
│     ├── 01wb (Audio Block 0)        │
│     ├── 00db (Video Frame 1)        │
│     ├── 01wb (Audio Block 1)        │
│     │   ...                         │
├─────────────────────────────────────┤
│     idx1 (Index Chunk)              │
└─────────────────────────────────────┘
```

### avih（メインヘッダー）詳細

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | dwMicroSecPerFrame | フレーム間隔（マイクロ秒） | 1000000 / fps | 32bit LE |
| 2 | dwMaxBytesPerSec | 最大バイト/秒 | 7000（固定） | 32bit LE |
| 3 | dwFlags | フラグ | 16 | 32bit LE |
| 4 | dwTotalFrames | 総フレーム数 | frame_count | 32bit LE |
| 5 | dwStreams | ストリーム数 | 1（映像のみ） | 32bit LE |
| 6 | dwWidth | 映像幅 | movie_size.width | 32bit LE |
| 7 | dwHeight | 映像高さ | movie_size.height | 32bit LE |

### 映像ストリームヘッダー（strh vids）

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | fccType | ストリーム種別 | "vids" | 4バイト |
| 2 | fccHandler | コーデック | "MJPG" | 4バイト |
| 3 | dwScale | スケール | 1 | 32bit LE |
| 4 | dwRate | レート（FPS） | fps | 32bit LE |
| 5 | dwLength | フレーム数 | frame_count | 32bit LE |

### 音声ストリームヘッダー（strh auds）

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | fccType | ストリーム種別 | "auds" | 4バイト |
| 2 | dwScale | ブロックアライン | blockalign | 32bit LE |
| 3 | dwRate | バイト/秒 | mix_rate * blockalign | 32bit LE |
| 4 | dwLength | サンプル数 | frame_count * mix_rate / fps | 32bit LE |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| 出力パス | .avi 拡張子で終わるパス | Yes |
| 映像サイズ | movie_size が有効な値 | Yes |
| FPS | 0より大きい値 | Yes |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | フレーム番号 | 昇順（時系列） |

### 改ページ条件

N/A（動画出力）

## データベース参照仕様

### プロジェクト設定参照

| 設定キー | 用途 | デフォルト値 |
|---------|------|-------------|
| editor/movie_writer/mix_rate | オーディオサンプルレート | 48000 |
| editor/movie_writer/speaker_mode | スピーカーモード | STEREO |
| editor/movie_writer/video_quality | JPEG品質 | 0.75 |
| editor/movie_writer/audio_bit_depth | オーディオビット深度 | 16 |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| マイクロ秒/フレーム | 1000000 / fps | 切り捨て | avihヘッダー用 |
| ブロックアライン | bit_depth / 8 * channels | - | 音声用 |
| オーディオブロックサイズ | (mix_rate / fps) * blockalign | - | 1フレームあたり |
| SizeImage | ((width * 24 / 8 + 3) & 0xFFFFFFFC) * height | 4バイトアライン | strf用 |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[write_begin 呼び出し] --> B[出力パス設定]
    B --> C[AVIファイル作成]
    C --> D[RIFFヘッダー書き込み]
    D --> E[LIST hdrl 書き込み]
    E --> F[avih チャンク書き込み]
    F --> G[映像 strl/strh/strf 書き込み]
    G --> H[odml チャンク書き込み]
    H --> I[音声 strl/strh/strf 書き込み]
    I --> J[LIST movi ヘッダー書き込み]
    J --> K{フレームループ}
    K --> L[write_frame 呼び出し]
    L --> M[JPEG圧縮]
    M --> N[00db チャンク書き込み]
    N --> O[01wb オーディオ書き込み]
    O --> P{次フレーム?}
    P -->|Yes| K
    P -->|No| Q[write_end 呼び出し]
    Q --> R[idx1 インデックス書き込み]
    R --> S[ヘッダーサイズフィールド更新]
    S --> T[終了]
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| ファイル作成失敗 | FileAccess::open 失敗 | ERR_FAIL_COND_V(f.is_null(), ERR_CANT_OPEN) | パスを確認 |
| 未設定状態での呼び出し | f が null | ERR_FAIL_COND_V(f.is_null(), ERR_UNCONFIGURED) | write_begin を先に呼ぶ |
| JPEG圧縮失敗 | save_jpg_to_buffer が空を返す | 暗黙的エラー | 画像データを確認 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定フレーム数 | 制限なし（ファイルサイズ上限まで） |
| 目標出力時間 | リアルタイム以下（フレームドロップなし） |
| 同時出力数上限 | 1（シングルインスタンス） |
| ファイルサイズ | 品質0.75で約50-200KB/フレーム（1080p） |

## セキュリティ考慮事項

- 出力パスはユーザー指定のため、パストラバーサル攻撃に注意が必要
- 長時間録画によるディスク枯渇攻撃の可能性
- ファイルサイズがAVIの4GB制限を超える場合の考慮が必要

## 備考

- AVIフォーマットはMicrosoft DirectShowのRIFF形式に基づく
- odml拡張ヘッダーにより大容量ファイル対応
- 16ビットオーディオの場合、32ビットから変換が行われる
- idx1インデックスにより効率的なシークが可能
- base_pathが相対パスの場合は "res://" が自動付加される

---

## コードリーディングガイド

本帳票を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

まず、MovieWriter 基底クラスとMJPEG実装の関係を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | movie_writer.h | `servers/movie_writer/movie_writer.h` | MovieWriter 抽象クラスの仮想関数定義 |
| 1-2 | movie_writer_mjpeg.h | `modules/jpg/movie_writer_mjpeg.h` | MovieWriterMJPEG クラスのメンバ変数定義 |

**読解のコツ**: AVIファイルの構造を理解することが重要。RIFF/LIST/チャンクの入れ子構造を把握する。

#### Step 2: エントリーポイントを理解する

処理の起点となるファイル・関数を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | movie_writer_mjpeg.cpp | `modules/jpg/movie_writer_mjpeg.cpp` | handles_file による .avi 拡張子マッチング（行42-44） |
| 2-2 | movie_writer_mjpeg.cpp | `modules/jpg/movie_writer_mjpeg.cpp` | コンストラクタでのproject設定読み込み（行271-276） |

**主要処理フロー**:
1. **行271-276**: コンストラクタで mix_rate、speaker_mode、quality、audio_bit_depth を取得
2. **行42-44**: handles_file で .avi 拡張子をチェック
3. **行50-192**: write_begin でAVIヘッダー全体を書き込み
4. **行194-227**: write_frame でMJPEG/オーディオ書き込み
5. **行229-268**: write_end でインデックス書き込みとサイズ更新

#### Step 3: AVIヘッダー書き込みを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | movie_writer_mjpeg.cpp | `modules/jpg/movie_writer_mjpeg.cpp` | write_begin の実装（行50-192） |

**主要処理フロー**:
- **行66-68**: RIFFヘッダー（"RIFF____AVI "）
- **行69-71**: LIST hdrl ヘッダー
- **行72-88**: avih メインヘッダー（総フレーム数は後で更新）
- **行89-123**: 映像ストリーム（strl/strh/strf）
- **行125-134**: odml 拡張ヘッダー
- **行136-184**: 音声ストリーム（strl/strh/strf）
- **行186-189**: LIST movi ヘッダー

#### Step 4: フレーム書き込み処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | movie_writer_mjpeg.cpp | `modules/jpg/movie_writer_mjpeg.cpp` | write_frame の実装（行194-227） |

**主要処理フロー**:
- **行197**: Image::save_jpg_to_buffer() でJPEG圧縮
- **行200-207**: 00db（映像チャンク）書き込み、パディング考慮
- **行209-222**: 01wb（音声チャンク）書き込み、16/32ビット変換

#### Step 5: 終了処理とインデックス作成を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | movie_writer_mjpeg.cpp | `modules/jpg/movie_writer_mjpeg.cpp` | write_end の実装（行229-268） |

**主要処理フロー**:
- **行232-251**: idx1 インデックスチャンク書き込み
- **行253-265**: ヘッダー内のサイズ・フレーム数フィールド更新

### プログラム呼び出し階層図

```
MovieWriter::begin(movie_size, fps, base_path)
    │
    └─ MovieWriterMJPEG::write_begin(movie_size, fps, base_path)
           │
           ├─ FileAccess::open() - AVIファイル作成
           │
           └─ AVIヘッダー書き込み
                  │
                  ├─ RIFF/AVI ヘッダー
                  ├─ LIST hdrl (avih + strl*2 + odml)
                  └─ LIST movi ヘッダー

MovieWriter::add_frame()
    │
    └─ MovieWriterMJPEG::write_frame(image, audio_data)
           │
           ├─ image->save_jpg_to_buffer(quality)
           │       │
           │       └─ JPEG圧縮処理
           │
           ├─ f->store_buffer("00db" + jpg_data)
           │
           └─ f->store_buffer("01wb" + audio_data)
                  │
                  └─ [16bit時] 32bit→16bit 変換

MovieWriter::end()
    │
    └─ MovieWriterMJPEG::write_end()
           │
           ├─ idx1 インデックス書き込み
           │
           └─ ヘッダーサイズ/フレーム数更新
                  │
                  ├─ f->seek(4) - RIFFサイズ
                  ├─ f->seek(total_frames_ofs)
                  ├─ f->seek(total_frames_ofs2)
                  ├─ f->seek(total_frames_ofs3)
                  └─ f->seek(movi_data_ofs)
```

### データフロー図

```
[入力]               [処理]                    [出力]

フレームバッファ
    │
    └─ Ref<Image> ──────▶ save_jpg_to_buffer(quality)
                                │
                                ▼
                          JPEG圧縮 ───────▶ 00db チャンク ─┐
                                                          │
AudioServer出力                                           │
    │                                                     ▼
    └─ int32_t[] ───────▶ [16bit変換] ──────▶ 01wb チャンク ──▶ .avi ファイル
                                                          │
project設定                                               │
    │                                                     │
    ├─ quality ─────────▶ JPEG品質                        │
    ├─ mix_rate ────────▶ 音声サンプルレート               │
    ├─ speaker_mode ────▶ チャンネル数                    │
    └─ audio_bit_depth ─▶ 16/32bit選択                    │
                                                          │
write_end時 ──────────────────────────────────────────────┘
    │
    └─ idx1 + ヘッダー更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| movie_writer_mjpeg.cpp | `modules/jpg/movie_writer_mjpeg.cpp` | ソース | MJPEG AVI出力の主実装 |
| movie_writer_mjpeg.h | `modules/jpg/movie_writer_mjpeg.h` | ヘッダー | クラス宣言 |
| movie_writer.h | `servers/movie_writer/movie_writer.h` | ヘッダー | MovieWriter基底クラス |
| movie_writer.cpp | `servers/movie_writer/movie_writer.cpp` | ソース | MovieWriter基底実装 |
| register_types.cpp | `modules/jpg/register_types.cpp` | ソース | MovieWriterMJPEG登録 |
| image.h | `core/io/image.h` | ヘッダー | Image::save_jpg_to_buffer |
| image_loader_libjpeg_turbo.cpp | `modules/jpg/image_loader_libjpeg_turbo.cpp` | ソース | JPEG圧縮実装 |
| file_access.h | `core/io/file_access.h` | ヘッダー | ファイル書き込みAPI |
| project_settings.h | `core/config/project_settings.h` | ヘッダー | project設定取得 |
