T-CREATOR

FFmpeg 「Non-monotonous DTS」エラー徹底対策:muxer 選定と再エンコード条件

FFmpeg 「Non-monotonous DTS」エラー徹底対策:muxer 選定と再エンコード条件

動画変換の作業中に突然「Non-monotonous DTS in output stream」というエラーに遭遇したことはありませんか?このエラーは FFmpeg を使った動画処理で頻繁に発生するトラブルの一つです。特に複数の動画を結合したり、配信用にフォーマット変換を行う際によく現れます。本記事では、このエラーの本質を理解し、適切な muxer 選定や再エンコード条件の設定方法まで、実践的な対策を詳しく解説していきます。

背景

DTS と PTS の役割

動画ファイルには、フレームの表示順序と処理順序を管理する 2 つの重要なタイムスタンプが存在します。

typescript// タイムスタンプの概念を表す型定義
type Timestamp = {
  pts: number; // Presentation Time Stamp(表示時刻)
  dts: number; // Decoding Time Stamp(デコード時刻)
};

PTS(Presentation Time Stamp) は、そのフレームをいつ画面に表示するかを示す時刻です。 一方、DTS(Decoding Time Stamp) は、そのフレームをいつデコードすべきかを示します。

typescript// フレームデータの構造例
interface VideoFrame {
  data: Buffer; // フレームの実データ
  pts: number; // 表示タイムスタンプ
  dts: number; // デコードタイムスタンプ
  keyframe: boolean; // キーフレームかどうか
  frameType: 'I' | 'P' | 'B'; // フレームタイプ
}

なぜ DTS と PTS が異なるのか

動画圧縮では、I フレーム(キーフレーム)、P フレーム(前方予測フレーム)、B フレーム(双方向予測フレーム)という 3 種類のフレームを使います。

mermaidflowchart LR
  i1["I フレーム<br/>(基準)"] --> p1["P フレーム<br/>(I から予測)"]
  i1 --> b1["B フレーム<br/>(I と P から予測)"]
  p1 --> b1
  p1 --> p2["P フレーム<br/>(P から予測)"]
  b1 -.表示順序.-> i1
  b1 -.表示順序.-> p1

上図は、フレーム間の依存関係を示しています。B フレームは前後のフレームを参照するため、デコード順序と表示順序が異なるのです。

B フレームは前後両方のフレームを参照するため、表示順序とデコード順序が一致しません。

typescript// フレームの表示順序とデコード順序の違い
const displayOrder = ['I', 'B', 'B', 'P', 'B', 'B', 'P']; // 表示順
const decodeOrder = ['I', 'P', 'B', 'B', 'P', 'B', 'B']; // デコード順

// 実際のタイムスタンプ例
const frames = [
  { type: 'I', pts: 0, dts: 0 }, // 最初は一致
  { type: 'P', pts: 400, dts: 100 }, // デコードは早く
  { type: 'B', pts: 100, dts: 200 }, // 表示は P より前
  { type: 'B', pts: 200, dts: 300 },
];

このように、DTS は必ず単調増加(常に増え続ける)する必要がありますが、PTS は前後することがあります。

コンテナフォーマットと muxer の関係

FFmpeg における muxer(マルチプレクサ)は、エンコードされた動画・音声データをコンテナフォーマットに格納する役割を担います。

mermaidflowchart TB
  video["動画ストリーム<br/>(H.264/H.265)"] --> muxer["Muxer<br/>(mp4/mov/mkv)"]
  audio["音声ストリーム<br/>(AAC/MP3)"] --> muxer
  subtitle["字幕ストリーム"] --> muxer
  metadata["メタデータ"] --> muxer
  muxer --> container["コンテナファイル<br/>(*.mp4/*.mkv)"]

各コンテナフォーマットには、タイムスタンプの扱いに関して異なる仕様や制約があります。

typescript// 主要な muxer とその特徴
interface MuxerConfig {
  name: string;
  extension: string;
  timestampPrecision: number; // タイムスタンプ精度
  supportsBFrames: boolean; // B フレームのサポート
  strictDTS: boolean; // DTS の厳格性
}

const muxers: MuxerConfig[] = [
  {
    name: 'mp4',
    extension: '.mp4',
    timestampPrecision: 1000, // ミリ秒単位
    supportsBFrames: true,
    strictDTS: true, // DTS の単調性を厳格にチェック
  },
  {
    name: 'matroska',
    extension: '.mkv',
    timestampPrecision: 1000000, // マイクロ秒単位
    supportsBFrames: true,
    strictDTS: false, // より柔軟
  },
];

MP4 コンテナは DTS の単調性を厳格にチェックするため、「Non-monotonous DTS」エラーが発生しやすいのです。

課題

エラーが発生する典型的なケース

「Non-monotonous DTS in output stream」エラーは、DTS が単調増加していない状態で muxer がデータを受け取ったときに発生します。

ケース 1:動画の結合

複数の動画ファイルを concat フィルタで結合する際、各ファイルのタイムスタンプがリセットされず、DTS が逆転することがあります。

bash# エラーが発生しやすいコマンド例
ffmpeg -i video1.mp4 -i video2.mp4 \
  -filter_complex "[0:v][1:v]concat=n=2:v=1[v]" \
  -map "[v]" output.mp4

上記のコマンドでは、video1 の最後のフレームと video2 の最初のフレームの DTS が連続しないため、エラーが発生する可能性が高まります。

typescript// 結合時のタイムスタンプの問題
interface ConcatIssue {
  video1LastFrame: { pts: 10000; dts: 9800 };
  video2FirstFrame: { pts: 0; dts: 0 }; // DTS が逆転!
  // 期待値: { pts: 10100, dts: 9900 }
}

ケース 2:ストリームコピー時のフォーマット変換

-c copy オプションで再エンコードせずにコンテナだけを変換する場合、元のタイムスタンプがそのまま使われます。

bash# MKV から MP4 へのストリームコピー
ffmpeg -i input.mkv -c copy output.mp4

MKV ファイルが緩い DTS 管理で作られていた場合、MP4 の厳格な要件を満たせずエラーになります。

typescript// フォーマット変換時の互換性問題
interface FormatConversionIssue {
  sourceFormat: {
    container: 'mkv';
    dtsCheck: 'loose'; // 緩いチェック
    timestamps: [0, 100, 90, 200]; // 90 が逆転
  };
  targetFormat: {
    container: 'mp4';
    dtsCheck: 'strict'; // 厳格なチェック
    result: 'ERROR'; // エラー発生
  };
}

ケース 3:可変フレームレート(VFR)動画の処理

画面録画ソフトやゲーム録画で生成される VFR 動画は、フレーム間隔が不均一なため、タイムスタンプの計算が複雑になります。

typescript// VFR 動画のフレーム間隔例
const vfrFrames = [
  { frameNumber: 0, pts: 0, dts: 0 },
  { frameNumber: 1, pts: 33, dts: 33 }, // 通常の間隔
  { frameNumber: 2, pts: 66, dts: 66 },
  { frameNumber: 3, pts: 150, dts: 140 }, // 長い間隔
  { frameNumber: 4, pts: 180, dts: 170 }, // 短い間隔
  { frameNumber: 5, pts: 200, dts: 199 },
  { frameNumber: 6, pts: 210, dts: 198 }, // DTS が逆転!
];

エラーメッセージの詳細解析

実際に表示されるエラーメッセージを見てみましょう。

bash# 典型的なエラー出力
[mp4 @ 0x7f8b4c004000] Non-monotonous DTS in output stream 0:0;
previous: 12345, current: 12340 offset = 1234567890

このエラーメッセージから読み取れる情報は以下の通りです。

typescript// エラーメッセージの構造
interface DTSError {
  muxer: 'mp4'; // 使用している muxer
  stream: '0:0'; // 対象ストリーム(ファイル:ストリーム番号)
  previousDTS: 12345; // 直前のフレームの DTS
  currentDTS: 12340; // 現在のフレームの DTS(より小さい)
  offset: 1234567890; // 内部的なオフセット値
  problem: 'DTS decreased'; // 問題:DTS が減少している
}
#項目説明重要度
1muxer 名エラーを検出したコンテナ形式★★★
2stream問題が発生したストリーム番号★★☆
3previous DTS直前の DTS 値★★★
4current DTS現在の DTS 値(小さい)★★★
5offsetタイムベースのオフセット★☆☆

このエラーの本質は、「12345 → 12340」という DTS の減少にあります。

解決策

解決アプローチの全体像

「Non-monotonous DTS」エラーへの対策は、大きく分けて 3 つのアプローチがあります。

mermaidflowchart TD
  error["Non-monotonous DTS<br/>エラー発生"] --> check{"エラーの<br/>原因は?"}
  check -->|"結合・編集"| approach1["アプローチ 1<br/>タイムスタンプの<br/>再計算"]
  check -->|"フォーマット変換"| approach2["アプローチ 2<br/>寛容な muxer<br/>への変更"]
  check -->|"破損データ"| approach3["アプローチ 3<br/>完全な<br/>再エンコード"]

  approach1 --> solution1["fpsfilter, setpts<br/>使用"]
  approach2 --> solution2["MKV, MOV<br/>への変更"]
  approach3 --> solution3["libx264/libx265<br/>で再エンコード"]

上図のように、原因に応じて適切な対策を選択することで、効率的にエラーを解決できます。

それぞれの方法について、具体的なコマンドと設定を見ていきましょう。

アプローチ 1:タイムスタンプの再計算

fpsfilter による固定フレームレート化

VFR 動画を CFR(固定フレームレート)に変換することで、タイムスタンプの一貫性を保ちます。

bash# fps フィルタで 30fps に固定
ffmpeg -i input.mp4 \
  -vf "fps=30" \
  -c:v libx264 -preset medium \
  -c:a copy \
  output.mp4

このコマンドは以下の処理を実行します。

typescript// fps フィルタの動作イメージ
interface FpsFilterProcess {
  input: {
    frameRate: 'variable';
    frames: [
      { time: 0.0; pts: 0 },
      { time: 0.033; pts: 33 },
      { time: 0.15; pts: 150 } // 長い間隔
    ];
  };
  output: {
    frameRate: 30; // 固定
    frameInterval: 33.33; // 1/30秒 = 33.33ms
    frames: [
      { time: 0.0; pts: 0 },
      { time: 0.033; pts: 1000 },
      { time: 0.067; pts: 2000 }, // 均等間隔
      { time: 0.1; pts: 3000 }
    ];
  };
}

setpts による PTS の再設定

setpts フィルタを使うと、PTS を明示的に再計算できます。

bash# PTS をフレーム番号から再計算
ffmpeg -i input.mp4 \
  -vf "setpts=N/(30*TB)" \
  -c:v libx264 -preset medium \
  output.mp4

setpts のパラメータ説明です。

typescript// setpts の式の意味
interface SetptsFormula {
  N: 'フレーム番号(0から開始)';
  TB: 'タイムベース(通常は 1/fps)';
  formula: 'N/(30*TB)';
  example: {
    frame0: { N: 0; result: '0/(30*TB) = 0' };
    frame1: { N: 1; result: '1/(30*TB) = 0.0333...' };
    frame2: { N: 2; result: '2/(30*TB) = 0.0666...' };
  };
}
#オプション説明効果
1N現在のフレーム番号0, 1, 2, 3...
2TBタイムベース1/フレームレート
3N​/​(30*TB)30fps での PTS均等な間隔

動画結合時のタイムスタンプ調整

複数動画を結合する際は、concat demuxer を使うとタイムスタンプが自動調整されます。

bash# concat demuxer を使った安全な結合
# 1. 結合リストファイルを作成
echo "file 'video1.mp4'" > concat_list.txt
echo "file 'video2.mp4'" >> concat_list.txt
echo "file 'video3.mp4'" >> concat_list.txt

# 2. concat demuxer で結合
ffmpeg -f concat -safe 0 -i concat_list.txt \
  -c copy output.mp4

もし上記でエラーが出る場合は、再エンコードを組み合わせます。

bash# タイムスタンプを再計算しながら結合
ffmpeg -f concat -safe 0 -i concat_list.txt \
  -vf "fps=30,setpts=N/(30*TB)" \
  -c:v libx264 -preset medium \
  -c:a aac -b:a 128k \
  output.mp4
typescript// concat demuxer の動作
interface ConcatDemuxerProcess {
  input: [
    { file: 'video1.mp4'; duration: 10.0; lastDTS: 9800 },
    { file: 'video2.mp4'; duration: 15.0; lastDTS: 14700 }
  ];
  process: {
    step1: 'video1 のタイムスタンプをそのまま使用';
    step2: 'video2 の開始 DTS を video1 の終了 DTS + 100 に調整';
    adjustment: {
      video2OriginalStart: 0;
      video2NewStart: 9900; // 9800 + 100
    };
  };
  output: {
    continuousDTS: true; // DTS が連続
    duration: 25.0;
  };
}

アプローチ 2:寛容な muxer の選択

Matroska(MKV)形式の利用

MKV コンテナは DTS のチェックが MP4 より寛容なため、エラーを回避できることがあります。

bash# MP4 の代わりに MKV を使用
ffmpeg -i input.mp4 \
  -c copy \
  output.mkv

各フォーマットの DTS チェックの違いを比較してみましょう。

#フォーマット拡張子DTS チェックB フレーム推奨用途
1MP4.mp4★★★ 厳格Web 配信
2Matroska.mkv★☆☆ 寛容編集作業
3MOV.mov★★☆ 中程度Apple 環境
4AVI.avi★☆☆ 寛容レガシー
typescript// フォーマットごとの特性
interface ContainerComparison {
  mp4: {
    standard: 'ISO/IEC 14496-14';
    dtsValidation: 'strict';
    errorOnNonMonotonous: true;
    webSupport: 'excellent';
    useCase: ['Web配信', 'モバイル', 'ストリーミング'];
  };
  mkv: {
    standard: 'Matroska';
    dtsValidation: 'lenient';
    errorOnNonMonotonous: false; // 寛容
    webSupport: 'limited';
    useCase: ['編集作業', 'アーカイブ', '高品質保存'];
  };
}

MOV 形式の活用

Apple の QuickTime コンテナである MOV も、比較的柔軟にタイムスタンプを扱えます。

bash# MOV 形式で出力
ffmpeg -i input.mp4 \
  -c:v libx264 \
  -c:a aac \
  -movflags +faststart \
  output.mov

movflags +faststart オプションは、メタデータをファイルの先頭に配置し、Web でのストリーミング再生を可能にします。

typescript// MOV のメタデータ配置
interface MovFlagsEffect {
  withoutFaststart: {
    structure: [
      '動画データ',
      '音声データ',
      'メタデータ(moov atom)'
    ];
    streamingStart: '全ダウンロード後'; // 遅い
  };
  withFaststart: {
    structure: [
      'メタデータ(moov atom)',
      '動画データ',
      '音声データ'
    ];
    streamingStart: '即座に開始可能'; // 速い
  };
}

アプローチ 3:完全な再エンコード

GOP 構造の最適化

GOP(Group of Pictures)の構造を調整することで、タイムスタンプの問題を根本から解決できます。

bash# GOP サイズを指定して再エンコード
ffmpeg -i input.mp4 \
  -c:v libx264 \
  -g 60 \
  -keyint_min 60 \
  -sc_threshold 0 \
  -bf 2 \
  -c:a aac -b:a 128k \
  output.mp4

各オプションの意味を解説します。

typescript// GOP 関連オプションの詳細
interface GopSettings {
  options: {
    g: {
      name: '-g';
      value: 60;
      meaning: 'GOP サイズ(キーフレーム間隔)';
      unit: 'フレーム数';
      effect: '60 フレームごとに I フレームを挿入';
    };
    keyint_min: {
      name: '-keyint_min';
      value: 60;
      meaning: '最小キーフレーム間隔';
      effect: 'シーンチェンジでも 60 フレーム以下では I フレームを作らない';
    };
    sc_threshold: {
      name: '-sc_threshold';
      value: 0;
      meaning: 'シーンチェンジ検出の閾値';
      effect: '0 = シーンチェンジ検出を無効化';
    };
    bf: {
      name: '-bf';
      value: 2;
      meaning: 'B フレームの最大連続数';
      effect: 'B フレームを最大 2 枚まで連続使用';
    };
  };
}
#オプション効果DTS への影響
1-g60GOP サイズ固定★★★ 高
2-keyint_min60最小間隔固定★★☆ 中
3-sc_threshold0シーン検出無効★★★ 高
4-bf2B フレーム制限★★☆ 中

B フレームを使わない設定

B フレームが原因でタイムスタンプの問題が起きている場合は、B フレームを無効化します。

bash# B フレームなしで再エンコード
ffmpeg -i input.mp4 \
  -c:v libx264 \
  -bf 0 \
  -g 30 \
  -preset medium \
  -c:a copy \
  output.mp4

B フレームの有無による違いを見てみましょう。

typescript// B フレームあり/なしの比較
interface BframeComparison {
  withBframes: {
    gopPattern: ['I', 'B', 'B', 'P', 'B', 'B', 'P'];
    compression: 'excellent'; // 圧縮率高い
    fileSize: '100MB';
    dtsComplexity: 'high'; // DTS 計算複雑
    displayOrder: [0, 3, 1, 2, 6, 4, 5]; // 表示順序
    decodeOrder: [0, 1, 2, 3, 4, 5, 6]; // デコード順序
    timestamps: {
      frame0: { type: 'I'; pts: 0; dts: 0 };
      frame1: { type: 'B'; pts: 100; dts: 300 }; // DTS と PTS が大きく異なる
      frame2: { type: 'B'; pts: 200; dts: 400 };
      frame3: { type: 'P'; pts: 300; dts: 100 };
    };
  };
  withoutBframes: {
    gopPattern: ['I', 'P', 'P', 'P', 'P', 'P'];
    compression: 'good'; // 圧縮率やや低い
    fileSize: '130MB';
    dtsComplexity: 'low'; // DTS 計算シンプル
    displayOrder: [0, 1, 2, 3, 4, 5]; // 表示順序
    decodeOrder: [0, 1, 2, 3, 4, 5]; // デコード順序(同じ)
    timestamps: {
      frame0: { type: 'I'; pts: 0; dts: 0 };
      frame1: { type: 'P'; pts: 100; dts: 100 }; // DTS と PTS が一致
      frame2: { type: 'P'; pts: 200; dts: 200 };
      frame3: { type: 'P'; pts: 300; dts: 300 };
    };
  };
}

B フレームを無効化すると、ファイルサイズは増えますが、DTS と PTS の関係がシンプルになり、エラーが起きにくくなります。

ツーパスエンコーディングによる品質維持

再エンコードで品質を維持したい場合は、ツーパスエンコードを使用しましょう。

bash# 1 パス目:解析
ffmpeg -i input.mp4 \
  -c:v libx264 \
  -b:v 2M \
  -pass 1 \
  -g 60 -bf 2 \
  -an \
  -f null /dev/null

1 パス目では、動画の特性を解析してログファイルを生成します。

bash# 2 パス目:本エンコード
ffmpeg -i input.mp4 \
  -c:v libx264 \
  -b:v 2M \
  -pass 2 \
  -g 60 -bf 2 \
  -c:a aac -b:a 128k \
  output.mp4

2 パス目では、1 パス目の解析結果を使って最適なビットレート配分を行います。

typescript// ツーパスエンコードの仕組み
interface TwoPassEncoding {
  pass1: {
    purpose: '動画の複雑さを解析';
    output: 'ffmpeg2pass-0.log'; // ログファイル
    processedData: {
      sceneComplexity: [10, 50, 90, 30, 20]; // シーンごとの複雑度
      motionVectors: 'analyzed';
      optimalBitrate: 'calculated';
    };
    encoding: false; // 実際のエンコードはしない
  };
  pass2: {
    purpose: '最適なビットレート配分でエンコード';
    input: 'ffmpeg2pass-0.log'; // 1 パス目の結果
    processedData: {
      bitrateAllocation: {
        simpleScene: '1.5Mbps'; // 簡単なシーンは低ビットレート
        complexScene: '3.0Mbps'; // 複雑なシーンは高ビットレート
      };
    };
    encoding: true; // 実際にエンコード
    output: 'output.mp4';
  };
}

アプローチ 4:エラーの無視(最終手段)

**警告:この方法は推奨されません。**データの破損や再生不良の原因となる可能性があります。

bash# -fflags +igndts でエラーを無視(非推奨)
ffmpeg -fflags +igndts -i input.mp4 \
  -c copy \
  output.mp4

このオプションは、DTS のエラーを無視して処理を続行しますが、出力ファイルに問題が残る可能性があります。

typescript// エラー無視のリスク
interface IgnoreDTSRisks {
  option: '-fflags +igndts';
  behavior: 'DTS エラーを無視して処理継続';
  risks: [
    '再生時にフレームスキップが発生',
    '音声と映像の同期ずれ',
    '一部プレイヤーで再生不可',
    'シークが正常に動作しない',
    '動画編集ソフトで読み込めない'
  ];
  useCase: [
    '一時的な確認用',
    '他の方法が全て失敗した場合のみ'
  ];
  recommendation: '他の解決策を優先すべき';
}

具体例

実例 1:複数動画の結合で発生するエラー

問題の状況

3 つの動画ファイルを結合しようとした際に、以下のエラーが発生しました。

bash# エラーが発生したコマンド
ffmpeg -i video1.mp4 -i video2.mp4 -i video3.mp4 \
  -filter_complex "[0:v][1:v][2:v]concat=n=3:v=1[outv]" \
  -map "[outv]" output.mp4

# エラーメッセージ
[mp4 @ 0x7f9a8c004000] Non-monotonous DTS in output stream 0:0;
previous: 337000, current: 0 offset = 0

各ファイルの情報を確認してみます。

bash# ファイル情報の確認
ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 video1.mp4
# 出力: 11.233

ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 video2.mp4
# 出力: 8.967

ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 video3.mp4
# 出力: 15.442
typescript// 問題の分析
interface ConcatProblemAnalysis {
  video1: {
    duration: 11.233;
    lastFramePTS: 337000; // 最後のフレームの PTS
    lastFrameDTS: 336000; // 最後のフレームの DTS
  };
  video2: {
    duration: 8.967;
    firstFramePTS: 0; // リセットされている
    firstFrameDTS: 0; // ここで DTS が逆転
  };
  problem: {
    dtsJump: '336000 → 0';
    reason: 'concat フィルタがタイムスタンプを調整しない';
  };
}

解決方法:concat demuxer の使用

concat demuxer を使って、タイムスタンプを自動調整します。

bash# 手順 1:結合リストファイルの作成
cat > concat_list.txt << EOF
file 'video1.mp4'
file 'video2.mp4'
file 'video3.mp4'
EOF
bash# 手順 2:concat demuxer で結合
ffmpeg -f concat -safe 0 -i concat_list.txt \
  -c copy \
  output.mp4

もしストリームコピーでエラーが出る場合は、再エンコードします。

bash# 再エンコード版
ffmpeg -f concat -safe 0 -i concat_list.txt \
  -vf "fps=30,setpts=N/(30*TB)" \
  -c:v libx264 -preset medium -crf 23 \
  -c:a aac -b:a 128k \
  output.mp4
typescript// 解決後のタイムスタンプ
interface ConcatSolution {
  video1: {
    startDTS: 0;
    endDTS: 336000;
    duration: 11.233;
  };
  video2: {
    startDTS: 337000; // 自動調整された
    endDTS: 606000;
    duration: 8.967;
    adjustment: '+337000'; // video1 の終了 DTS + α
  };
  video3: {
    startDTS: 607000; // さらに調整
    endDTS: 1070000;
    duration: 15.442;
    adjustment: '+607000';
  };
  result: {
    continuousDTS: true; // DTS が連続
    totalDuration: 35.642;
    error: null;
  };
}

実例 2:MKV から MP4 への変換エラー

問題の状況

アニメやゲーム実況動画など、MKV 形式で配布されている動画を MP4 に変換する際によく発生します。

bash# エラーが発生したコマンド
ffmpeg -i anime_episode01.mkv -c copy output.mp4

# エラーメッセージ
[mp4 @ 0x7f8a9c004000] Non-monotonous DTS in output stream 0:0;
previous: 450120, current: 449880 offset = 0

元ファイルの詳細を確認します。

bash# ストリーム情報の確認
ffprobe -v error -select_streams v:0 \
  -show_entries stream=codec_name,r_frame_rate,avg_frame_rate \
  -of default=noprint_wrappers=1 anime_episode01.mkv

# 出力例
codec_name=h264
r_frame_rate=24000/1001  # 23.976fps
avg_frame_rate=24000/1001
typescript// 問題の原因分析
interface MKVtoMP4Problem {
  sourceFile: {
    container: 'mkv';
    videoCodec: 'h264';
    frameRate: '23.976fps (VFR の可能性)';
    dtsManagement: 'lenient'; // 緩い管理
    bFrames: true;
    timestamps: [
      { pts: 449880; dts: 449500 },
      { pts: 450000; dts: 449880 }, // MKV では許容
      { pts: 450120; dts: 450120 }
    ];
  };
  targetContainer: {
    container: 'mp4';
    dtsRequirement: 'strict'; // 厳格な要件
    result: 'ERROR: DTS not monotonous';
  };
}

解決方法:MKV のまま使用 or 再エンコード

方法 1:MKV のまま使用する(推奨)

Web 配信でなければ、MKV のまま使用するのが最もシンプルです。

bash# 変換不要
# MKV をそのまま利用

MKV の互換性について確認しておきましょう。

#用途MKVMP4推奨
1ローカル再生MKV
2Web ブラウザ再生MP4
3スマートフォンMP4
4動画編集どちらも
5アーカイブ保存MKV

方法 2:MOV に変換する

MP4 より寛容な MOV を使います。

bash# MOV へストリームコピー
ffmpeg -i anime_episode01.mkv -c copy output.mov
typescript// MOV 変換の利点
interface MOVConversion {
  advantages: [
    'MP4 より DTS チェックが緩い',
    'ストリームコピーで高速',
    '品質劣化なし'
  ];
  disadvantages: [
    'Web ブラウザでの再生サポートが限定的',
    'ファイルサイズが若干大きい'
  ];
  useCase: 'ローカル編集や確認用';
}

方法 3:完全再エンコード

どうしても MP4 が必要な場合は、再エンコードします。

bash# 固定 GOP で再エンコード
ffmpeg -i anime_episode01.mkv \
  -c:v libx264 \
  -preset slow \
  -crf 18 \
  -g 48 \
  -keyint_min 48 \
  -sc_threshold 0 \
  -bf 3 \
  -c:a aac -b:a 192k \
  output.mp4

各パラメータの意味です。

typescript// アニメに最適なエンコード設定
interface AnimeEncodingSettings {
  preset: {
    name: '-preset slow';
    meaning: 'エンコード速度と品質のバランス';
    options: [
      'ultrafast',
      'fast',
      'medium',
      'slow',
      'veryslow'
    ];
    recommended: 'slow';
    reason: 'アニメは flat な領域が多いため、slow で効率的に圧縮';
  };
  crf: {
    name: '-crf 18';
    meaning: '品質設定(低いほど高品質)';
    range: '0-51';
    recommended: '18-23';
    value: 18;
    reason: 'アニメの線画をクリアに保つため低めに設定';
  };
  gopSize: {
    name: '-g 48';
    meaning: 'GOP サイズ(23.976fps × 2秒)';
    calculation: '24 * 2 = 48';
    reason: 'シーンチェンジが多いアニメに適した間隔';
  };
  bframes: {
    name: '-bf 3';
    meaning: 'B フレーム最大数';
    value: 3;
    reason: 'flat な領域が多いアニメは B フレームで効率圧縮';
  };
}

実例 3:画面録画動画の VFR 問題

問題の状況

OBS Studio などで録画したゲーム実況動画を編集しようとすると、DTS エラーが頻発します。

bash# エラーが発生したコマンド
ffmpeg -i gameplay_recording.mp4 -c copy edited.mp4

# エラーメッセージ(複数回発生)
[mp4 @ 0x7f9a8c004000] Non-monotonous DTS in output stream 0:0;
previous: 123456, current: 123450 offset = 0
[mp4 @ 0x7f9a8c004000] Non-monotonous DTS in output stream 0:0;
previous: 234567, current: 234560 offset = 0

VFR の状態を確認します。

bash# フレームレート情報の詳細確認
ffprobe -v error -select_streams v:0 \
  -show_entries stream=r_frame_rate,avg_frame_rate,time_base \
  -show_entries packet=pts_time,dts_time,duration_time \
  -of csv=p=0 gameplay_recording.mp4 | head -20
typescript// VFR 動画の特徴
interface VFRCharacteristics {
  frameRates: {
    declared: '60fps'; // 宣言されているフレームレート
    actual: 'variable'; // 実際は可変
    samples: [
      { frameNumber: 0; interval: 16.67; fps: 60 },
      { frameNumber: 1; interval: 16.67; fps: 60 },
      { frameNumber: 2; interval: 50.0; fps: 20 }, // 処理落ち
      { frameNumber: 3; interval: 16.67; fps: 60 },
      { frameNumber: 4; interval: 33.33; fps: 30 } // 処理落ち
    ];
  };
  cause: {
    recording: 'ゲームの FPS 変動を忠実に記録';
    benefit: 'ファイルサイズ削減';
    problem: 'タイムスタンプが不規則になる';
  };
}

解決方法:CFR への変換

VFR を CFR(固定フレームレート)に変換します。

bash# fps フィルタで 60fps に固定
ffmpeg -i gameplay_recording.mp4 \
  -vf "fps=60" \
  -c:v libx264 \
  -preset medium \
  -crf 23 \
  -c:a copy \
  output_cfr.mp4

より詳細な制御が必要な場合は、setpts と組み合わせます。

bash# setpts でタイムスタンプを完全にリセット
ffmpeg -i gameplay_recording.mp4 \
  -vf "fps=60,setpts=N/(60*TB)" \
  -c:v libx264 \
  -preset medium \
  -crf 23 \
  -g 120 \
  -keyint_min 120 \
  -sc_threshold 0 \
  -c:a aac -b:a 192k \
  output_fixed.mp4
typescript// CFR 変換の効果
interface CFRConversionEffect {
  before: {
    type: 'VFR';
    frameIntervals: [16.67, 16.67, 50.0, 16.67, 33.33]; // ms
    dtsSequence: [0, 1000, 1500, 3500, 5000, 5500]; // 不規則
    problems: ['DTS 逆転', 'シーク不安定', '編集困難'];
  };
  after: {
    type: 'CFR';
    frameIntervals: [16.67, 16.67, 16.67, 16.67, 16.67]; // 固定
    dtsSequence: [0, 1000, 2000, 3000, 4000, 5000]; // 規則的
    benefits: ['DTS 単調増加', 'シーク正確', '編集可能'];
  };
  tradeoff: {
    fileSize: '+10-20%'; // ファイルサイズ増加
    quality: '±0%'; // 品質は維持
    compatibility: '大幅改善';
  };
}

実例 4:ライブ配信アーカイブの処理

問題の状況

YouTube や Twitch からダウンロードした配信アーカイブを編集する際に発生します。

bash# エラーが発生したコマンド
ffmpeg -ss 00:10:30 -to 00:25:45 -i stream_archive.mp4 \
  -c copy highlight.mp4

# エラーメッセージ
[mp4 @ 0x7f8a9c004000] Non-monotonous DTS in output stream 0:0;
previous: 1890000, current: 1889800 offset = 630000

ライブ配信アーカイブ特有の問題を分析します。

typescript// ライブ配信の特性
interface LiveStreamCharacteristics {
  encoding: {
    method: 'リアルタイムエンコード';
    priority: 'レイテンシ優先(品質より速度)';
    timestamps: {
      source: 'エンコーダのシステムクロック';
      accuracy: 'やや不正確';
      adjustments: 'リアルタイムで補正';
    };
  };
  issues: [
    'ネットワーク遅延によるタイムスタンプずれ',
    'エンコーダの処理落ちによる DTS の乱れ',
    'シーンの切り替わりでの GOP 構造の変化',
    '音声との同期を優先した調整'
  ];
  specificProblem: {
    seeking: '-ss オプションで途中から切り出すと DTS がずれる';
    reason: 'キーフレームの位置とタイムスタンプの不一致';
  };
}

解決方法:再エンコードによる安定化

シーク位置から再エンコードすることで、タイムスタンプを正規化します。

bash# 正確な切り出しと再エンコード
ffmpeg -ss 00:10:30 -to 00:25:45 -i stream_archive.mp4 \
  -vf "fps=30,setpts=N/(30*TB)" \
  -c:v libx264 \
  -preset medium \
  -crf 23 \
  -g 60 \
  -keyint_min 60 \
  -sc_threshold 0 \
  -c:a aac -b:a 128k \
  highlight.mp4

もっと高速に処理したい場合は、ツーステップで行います。

bash# ステップ 1:キーフレームで大まかに切り出し(高速)
ffmpeg -ss 00:10:25 -to 00:25:50 -i stream_archive.mp4 \
  -c copy temp.mp4

# ステップ 2:正確な位置で再エンコード
ffmpeg -ss 00:00:05 -to 00:15:20 -i temp.mp4 \
  -vf "fps=30,setpts=N/(30*TB)" \
  -c:v libx264 \
  -preset medium \
  -crf 23 \
  -g 60 \
  -c:a aac -b:a 128k \
  highlight.mp4
typescript// ツーステップ処理の利点
interface TwoStepProcessing {
  step1: {
    operation: 'キーフレームで大まかに切り出し';
    method: 'ストリームコピー';
    speed: '非常に高速(リアルタイムの 10-20 倍)';
    accuracy: '±数秒のずれ';
    purpose: '処理対象を大幅に削減';
  };
  step2: {
    operation: '正確な位置で再エンコード';
    method: 'フルエンコード';
    speed: '通常速度';
    accuracy: '完全に正確';
    duration: 'step1 で削減された部分のみ処理';
  };
  totalTime: {
    oneStep: '15 分の動画を 15 分で処理';
    twoStep: 'step1: 10 秒 + step2: 2 分 = 2 分 10 秒';
    improvement: '約 7 倍高速';
  };
}

まとめ

「Non-monotonous DTS」エラーは、動画処理において頻繁に遭遇するトラブルですが、原因を正しく理解すれば適切に対処できます。

対策の選択フロー

エラーに遭遇したときは、以下の順序で対策を検討してください。

mermaidflowchart TD
  start["Non-monotonous DTS<br/>エラー発生"] --> question1{"Web 配信が<br/>必須?"}

  question1 -->|"いいえ"| solution1["MKV/MOV 形式に変更<br/>(ストリームコピー)"]
  question1 -->|"はい"| question2{"動画の結合<br/>または編集?"}

  question2 -->|"はい"| solution2["concat demuxer<br/>または setpts 使用"]
  question2 -->|"いいえ"| question3{"VFR 動画?"}

  question3 -->|"はい"| solution3["fps フィルタで<br/>CFR に変換"]
  question3 -->|"いいえ"| solution4["GOP 固定で<br/>完全再エンコード"]

  solution1 --> check["動作確認"]
  solution2 --> check
  solution3 --> check
  solution4 --> check

  check -->|"成功"| end_success["完了"]
  check -->|"失敗"| fallback["より厳格な<br/>再エンコード"]
  fallback --> end_success

重要ポイントの再確認

本記事で解説した重要なポイントをまとめます。

#ポイント内容重要度
1DTS の性質DTS は必ず単調増加する必要がある★★★
2muxer の選択MP4 は厳格、MKV/MOV は寛容★★★
3VFR vs CFRVFR 動画は CFR に変換すると安定★★☆
4GOP 設定固定 GOP でタイムスタンプが安定★★☆
5concat 方法demuxer を使うとタイムスタンプ自動調整★★★
6B フレーム無効化すると DTS がシンプルに★☆☆

推奨ワークフロー

動画処理の目的別に、推奨するワークフローをまとめました。

typescript// 目的別の推奨設定
interface RecommendedWorkflows {
  webDelivery: {
    purpose: 'Web ブラウザでの配信',
    format: 'MP4',
    workflow: [
      '1. fps フィルタで CFR 化',
      '2. setpts でタイムスタンプリセット',
      '3. 固定 GOP で libx264 エンコード',
      '4. movflags +faststart 付与'
    ],
    command: `
ffmpeg -i input.mp4 \\
  -vf "fps=30,setpts=N/(30*TB)" \\
  -c:v libx264 -preset medium -crf 23 \\
  -g 60 -keyint_min 60 -sc_threshold 0 \\
  -c:a aac -b:a 128k \\
  -movflags +faststart \\
  output.mp4
    `.trim()
  };

  localArchive: {
    purpose: 'ローカル保存・アーカイブ',
    format: 'MKV',
    workflow: [
      '1. ストリームコピーで MKV に変換',
      '2. エラーが出なければ完了',
      '3. エラーが出たら最小限の再エンコード'
    ],
    command: `
ffmpeg -i input.mp4 -c copy output.mkv
    `.trim()
  };

  editing: {
    purpose: '動画編集用の中間ファイル',
    format: 'MOV (ProRes)',
    workflow: [
      '1. ProRes コーデックで再エンコード',
      '2. I フレームのみ(全フレームがキーフレーム)',
      '3. タイムスタンプは自動的に正規化'
    ],
    command: `
ffmpeg -i input.mp4 \\
  -c:v prores_ks -profile:v 2 \\
  -c:a pcm_s16le \\
  output.mov
    `.trim()
  };

  concatenation: {
    purpose: '複数動画の結合',
    format: 'MP4',
    workflow: [
      '1. concat_list.txt を作成',
      '2. concat demuxer で結合',
      '3. エラーが出たら fps + setpts で再エンコード'
    ],
    command: `
# まずはストリームコピーを試す
ffmpeg -f concat -safe 0 -i concat_list.txt -c copy output.mp4

# エラーが出たら再エンコード
ffmpeg -f concat -safe 0 -i concat_list.txt \\
  -vf "fps=30,setpts=N/(30*TB)" \\
  -c:v libx264 -preset medium -crf 23 \\
  -g 60 -c:a aac -b:a 128k \\
  output.mp4
    `.trim()
  };
}

トラブルシューティングチェックリスト

エラーが解決しない場合は、以下のチェックリストを確認してください。

  1. 元ファイルの確認

    • ffprobe で元ファイルのストリーム情報を確認しましたか?
    • フレームレートは CFR ですか、VFR ですか?
    • B フレームは使われていますか?
  2. コマンドの確認

    • ストリームコピー(-c copy)を使っていませんか?
    • タイムスタンプのリセット(setpts)を試しましたか?
    • GOP 設定(-g, -keyint_min, -sc_threshold)を指定しましたか?
  3. 出力フォーマットの確認

    • MP4 以外のフォーマット(MKV, MOV)を試しましたか?
    • Web 配信が本当に必須ですか?
  4. エンコード設定の確認

    • B フレームを無効化(-bf 0)してみましたか?
    • プリセットを変更(-preset slow)してみましたか?
typescript// トラブルシューティングの順序
interface TroubleshootingOrder {
  level1: {
    difficulty: '簡単';
    time: '数秒';
    actions: [
      'MKV 形式に変更してストリームコピー',
      'MOV 形式に変更してストリームコピー'
    ];
  };
  level2: {
    difficulty: '中程度';
    time: '数分';
    actions: [
      'fps フィルタで CFR 化',
      'setpts でタイムスタンプリセット',
      'concat demuxer の使用'
    ];
  };
  level3: {
    difficulty: '高度';
    time: '十数分';
    actions: [
      'GOP 固定での完全再エンコード',
      'B フレーム無効化',
      'ツーパスエンコード'
    ];
  };
}

このエラーに遭遇したときは、焦らずに原因を特定し、状況に応じた適切な対策を選択することが大切です。本記事で紹介した方法を組み合わせることで、ほとんどのケースで解決できるはずです。

動画処理は試行錯誤が必要な場面も多いですが、タイムスタンプの仕組みを理解しておけば、効率的にトラブルシューティングできるようになります。ぜひ本記事を参考にして、快適な動画処理環境を構築してください。

関連リンク