本文へ移動
cccskills
無料GitHub で公開

shiguredo-mp4

時雨堂の Sans I/O MP4 ライブラリ shiguredo_mp4 の機能・API リファレンス。MP4 / fMP4 ファイルの mux/demux、ボックス群のエンコード・デコード、コーデック別サンプルエントリー、ISO/IEC 14496 系規格準拠に関する質問時に使用。

インストール方法を見る

含まれるファイル(1)

  • SKILL.md23.7 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

shiguredo_mp4

Sans I/O 設計に基づく MP4 (ISO Base Media File Format) の mux/demux ライブラリ。

特徴

  • 依存なし: 外部依存ゼロ (core / alloc のみ)
  • no_std 対応: std 非依存 (alloc クレートは必要)
  • Sans I/O: I/O を完全に分離した設計。ファイル読み書きは利用側の責務
  • 高レベル API: Mp4FileMuxer / Mp4FileDemuxer / Fmp4SegmentMuxer / Fmp4SegmentDemuxer による mux/demux
  • ボックス単位の API: 全ボックスが Encode / Decode を実装し、直接エンコード/デコード可能
  • C API / WebAssembly API: crates/c-api / crates/wasm で提供

バージョン情報

  • crate 名: shiguredo_mp4
  • バージョン: 2026.5.0
  • Rust Edition: 2024
  • 最小 Rust バージョン: 1.93
  • ライセンス: Apache-2.0

基本概念

MP4 ファイルは ftyp + 各種ボックス(moov / mdat / moof 等)で構成される。 すべてのボックスは BaseBox トレイトを実装し、Encode / Decode トレイトでバイト列と相互変換できる。

型説明
BaseBox全ボックスの共通トレイト (box_type() / children())
BoxHeaderボックスヘッダー (box_type + box_size)。decode_header_and_payload() でヘッダーとペイロードに分解
BoxSizeボックスのサイズ表現。U32(u32) / U64(u64)。VARIABLE_SIZE (U32(0)) は「ファイル末尾まで」を意味する
BoxTypeボックス種別。Normal([u8; 4]) / Uuid([u8; 16])
FullBox / FullBoxHeaderversion + flags を持つボックス用トレイト/ヘッダー
Mp4File<B>ファイル全体 (ftyp_box + boxes: Vec<B>) の表現。RootBox がデフォルト
Either<A, B>stco/co64 のように 2 種のボックスのどちらかを持つ場合に使う
Uint<T, BITS, OFFSET>任意ビット幅の整数 (Uint<u8, 2, 6> 等)
FixedPointNumber<I, F>固定小数点数 (16.16 等)
Utf8Stringnull 終端 UTF-8 文字列 (new() は null を含む文字列を拒否)
LanguageCode3 文字言語コード (from_ascii("eng") 等。0x60..=0x7F の範囲のみ)
Mp4FileTimeMP4 時間 (1904/1/1 起点)。from_unix_time() で変換
TrackKindAudio / Video / Subtitle
SampleFlagsfMP4 のサンプルフラグ (trun 等で使用)。from_fields() / sample_is_non_sync_sample() 等

Encode / Decode トレイト

use shiguredo_mp4::{Decode, Encode};

// エンコード: buf に書き込み、書き込んだバイト数を返す
let n = box.encode(&mut buf)?;

// バイト列に変換
let bytes = box.encode_to_vec()?;

// デコード: (値, 消費バイト数) を返す
let (decoded, size) = BoxType::decode(buf)?;

// オフセットを自動で進めるデコード
let value = u32::decode_at(buf, &mut offset)?;

エラー型

  • Error: エンコード/デコード時のエラー。kind: ErrorKind (InvalidInput / InvalidData / InsufficientBuffer / Unsupported) と reason / location / box_type を持つ。Display は発生箇所 (at src/xxx.rs:NNN) 付きで表示される
  • DemuxError: デマルチプレックス時のエラー。DecodeError / SampleTableError / InvalidState / InputRequired(RequiredInput) (Sans I/O の入力要求)
  • MuxError: マルチプレックス時のエラー。EncodeError / EmptyTracks / EmptySamples / PositionMismatch / MissingSampleEntry / AlreadyFinalized / TimescaleMismatch / MixedSampleEntries / NoSyncSamples / Overflow

デマルチプレックス (demux)

Mp4FileDemuxer - MP4 ファイルのデマルチプレックス

通常の MP4 ファイルからトラック情報とサンプルを時系列順に取得する。

メソッド説明
new()インスタンス生成
required_input()次に必要な入力範囲 Option<RequiredInput> を返す (初期化済みなら None)
handle_input(Input)ファイルデータを供給する。required_input() が要求した範囲を包含するデータを一度に渡すこと。部分的なデータ消費は行わない
tracks()&[TrackInfo] を返す。I/O が必要な場合は InputRequired を返す
next_sample()全トラックから最も早いタイムスタンプのサンプルを返す。無ければ None
prev_sample()現在位置より前で最も遅いタイムスタンプのサンプルを返す
seek(Duration)指定時刻にシーク。次回 next_sample() は指定時刻を含むサンプルから開始

Input は { position: u64, data: &[u8] }、RequiredInput は { position: u64, size: Option<usize> }。 required_input() で要求された範囲を供給し、DemuxError::InputRequired が返るまで繰り返す。 ストリーミング用途は想定していない(大きな mdat は要求されないが、moov は全体を渡す必要がある)。

use shiguredo_mp4::demux::{Input, Mp4FileDemuxer};

let mut demuxer = Mp4FileDemuxer::new();
while let Some(required) = demuxer.required_input() {
    // required.position と required.size に基づいてデータを読み込む
    let data: &[u8] = read_data(required.position, required.size);
    demuxer.handle_input(Input { position: required.position, data });
}
let tracks = demuxer.tracks()?;
while let Ok(Some(sample)) = demuxer.next_sample() {
    // sample.track, sample.timestamp, sample.data_offset, sample.data_size
}

Fmp4SegmentDemuxer - fMP4 セグメントのデマルチプレックス

初期化セグメント (ftyp + moov) とメディアセグメント (moof + mdat) を個別に処理する。

メソッド説明
new()インスタンス生成
handle_init_segment(&[u8])初期化セグメントを処理する (2 回目以降は InvalidState)
tracks()初期化済みトラック情報 &[TrackInfo] を返す
handle_media_segment(&[u8])メディアセグメントを処理し Vec<Sample> を返す。moof より前、moof と mdat の間、mdat の後ろのトップレベルボックス (styp / sidx / ssix / prft / free 等) は読み飛ばす。1 回の呼び出しで 1 つの moof + mdat ペアのみ (moof の後ろで mdat より先に別の moof が出る場合や、mdat の後ろの moof はエラー)。エラー時は内部状態が変わらない (同じインスタンスを使い続けられる)

Sample.data_offset は handle_media_segment() に渡したバッファ先頭からの相対位置。

use shiguredo_mp4::demux::Fmp4SegmentDemuxer;

let mut demuxer = Fmp4SegmentDemuxer::new();
demuxer.handle_init_segment(&init_segment)?;
let tracks = demuxer.tracks()?;
let samples = demuxer.handle_media_segment(&media_segment)?;

Fmp4FileDemuxer - fMP4 ファイルのインクリメンタルデマルチプレックス

1 つのファイル内に並んだ複数セグメントを順番に処理する。Mp4FileDemuxer と同様に required_input() / handle_input() の Sans I/O ループで使う。

Mp4FileDemuxer と同様に、要求された範囲より多いデータ (ファイル全体など) を渡してもよい。 ただし Mp4FileDemuxer と異なり、ファイル全体を渡す場合も required_input() が Some を返す間は handle_input() を繰り返し呼ぶ必要がある。 入力が要求された範囲の終端より手前で終わっている場合は、入力の終端をファイルの終端とみなす。

メソッド説明
new()インスタンス生成
required_input()次に必要な入力範囲を返す
handle_input(Input)ファイルデータを供給する
tracks()トラック情報を返す
next_sample()次のサンプルを返す (全トラック時系列順)

制限: tfhd の base_data_offset にファイル先頭からの絶対オフセットを記録した形式には非対応。

Mp4FileKindDetector - MP4 / fMP4 の種別判定

moov 内の mvex ボックスの有無で MP4 か fragmented MP4 かを incremental に判定する。

use shiguredo_mp4::demux::{Mp4FileKind, Mp4FileKindDetector};

let mut detector = Mp4FileKindDetector::new();
while let Some(required) = detector.required_input() {
    // データを読み込んで供給
    detector.handle_input(Input { position: required.position, data });
    if let Some(kind) = detector.file_kind()? {
        match kind {
            Mp4FileKind::Mp4 => { /* 通常 MP4 */ }
            Mp4FileKind::FragmentedMp4 => { /* fMP4 */ }
        }
        break;
    }
}

demux の共通型

  • TrackInfo: { track_id: u32, kind: TrackKind, duration: u64, timescale: NonZeroU32 }
    • duration / timestamp はタイムスケール単位。秒は timescale で割る
    • fMP4 では duration は init segment 由来で 0 になることが多い
  • Sample<'a>: { track: &'a TrackInfo, sample_entry: Option<&'a SampleEntry>, keyframe: bool, timestamp: u64, duration: u32, data_offset: u64, data_size: usize, composition_time_offset: Option<i64> }
    • sample_entry は前のサンプルから変更がない場合 None (最初のサンプルは常に Some)
    • timestamp は DTS。PTS は timestamp + composition_time_offset
    • data_offset は file demuxer ではファイル先頭からの絶対位置、segment demuxer では入力バッファ先頭からの相対位置

マルチプレックス (mux)

Mp4FileMuxer - MP4 ファイルのマルチプレックス

複数トラックのサンプルを統合して MP4 ファイルを生成する。

基本的な使用フロー:

  1. new() または with_options(Mp4FileMuxerOptions) でインスタンス生成
  2. initial_boxes_bytes() のバイト列をファイルに書き込む (ftyp + 予約領域 + mdat ヘッダー)
  3. サンプルデータをファイルに追記し、append_sample(&Sample) でメタデータを通知
  4. finalize() で完了し、FinalizedBoxes::offset_and_bytes_pairs() の内容をファイルの該当オフセットに書き込む
メソッド説明
new() / with_options(options)インスタンス生成
initial_boxes_bytes()初期ボックス群のバイト列 (ファイル先頭に書く)
append_sample(&Sample)サンプルを追加。エラー時は内部状態が変わらない (再呼び出し可能)
advance_position(u64)サンプルデータ以外のバイト列 (moof / mdat ヘッダ等) 分だけ書き込み位置を進める。OBS の Hybrid MP4 用
finalize()マルチプレックス完了。&FinalizedBoxes を返す
finalized_boxes()ファイナライズ結果を後から取得 (finalize() 前は None)

Sample のフィールド (mux::Sample):

フィールド説明
track_kind: TrackKindトラック種別
sample_entry: Option<SampleEntry>コーデック情報。最初のサンプルでは必須、以降は省略可 (前のサンプルを引き継ぐ)
keyframe: boolキーフレームか。stss ボックスの生成に使われる
timescale: NonZeroU32タイムスケール。同一トラック内で統一必須 (不一致は TimescaleMismatch)
duration: u32サンプルの尺 (タイムスケール単位)
composition_time_offset: Option<i64>PTS - DTS。負値は i32::MIN..=-1、非負値は 0..=u32::MAX の範囲のみ
data_offset: u64ファイル内でのサンプルデータの開始位置。直前の append_sample() の位置と一致必須 (PositionMismatch)
data_size: usizeサンプルデータのサイズ

注意点:

  • MP4 はサンプルのタイムスタンプを直接指定できず、累積尺で表現する。タイムスタンプのギャップは利用側で補完する (映像: 黒画像や尺調整 / 音声: 無音補完)
  • 映像トラックの全サンプルが keyframe = false だと finalize() が NoSyncSamples を返す
  • composition_time_offset は ctts ボックスに書き出される
  • 詳細な制御が必要な場合は muxer を使わずボックスを直接構築する
use shiguredo_mp4::mux::{Mp4FileMuxer, Sample};

let mut muxer = Mp4FileMuxer::new()?;
let initial_bytes = muxer.initial_boxes_bytes();
// file.write_all(initial_bytes)?;

let sample = Sample {
    track_kind: TrackKind::Video,
    sample_entry: Some(sample_entry),
    keyframe: true,
    timescale: NonZeroU32::MIN.saturating_add(30 - 1),
    duration: 1,
    composition_time_offset: None,
    data_offset: initial_bytes.len() as u64,
    data_size: 1024,
};
muxer.append_sample(&sample)?;

let finalized = muxer.finalize()?;
for (offset, bytes) in finalized.offset_and_bytes_pairs() {
    // file.seek(SeekFrom::Start(offset))?;
    // file.write_all(bytes)?;
}

faststart 対応

Mp4FileMuxerOptions::reserved_moov_box_size に予約サイズを指定すると、moov をファイル先頭付近に配置する faststart 形式になる (FinalizedBoxes::is_faststart_enabled() で判定)。 estimate_maximum_moov_box_size(&[sample_count_per_track]) で概算サイズを計算できる。 予約サイズより moov が大きい場合は moov はファイル末尾に配置され、faststart は無効になる。

Mp4FileMuxerOptions のフィールド:

  • reserved_moov_box_size: usize - faststart 用の moov 予約サイズ (デフォルト 0 = 無効)
  • creation_timestamp: Duration - 作成時刻 (デフォルト UNIX エポック)
  • audio_track / video_track / subtitle_track: TrackMetadata - mdhd.language / hdlr.name のメタデータ

TrackMetadata は { language: LanguageCode, name: Utf8String }。

Fmp4SegmentMuxer - fMP4 セグメントのマルチプレックス

初期化セグメント (ftyp + moov) とメディアセグメント (moof + mdat) を生成する。

メソッド説明
new() / with_options(SegmentMuxerOptions)インスタンス生成
create_media_segment_metadata(&[Sample])メディアセグメントの先頭メタデータ (moof + mdat ヘッダー) を生成。mdat ペイロードは含まない。サンプルの track 情報と sample entry を内部に蓄積する
create_media_segment_metadata_with_sidx(&[Sample])先頭に sidx ボックスを付加したメディアセグメントを生成 (DASH 等向け)
init_segment_bytes()その時点までに観測した内容を反映した初期化セグメントを返す。未観測なら EmptyTracks
mfra_bytes()ランダムアクセスインデックス (mfra) のバイト列を生成。ファイル末尾に付加

注意点:

  • Sample.data_offset は「ファイル全体の絶対位置」ではなく「今回のセグメントの mdat payload 領域の先頭からの相対位置」
  • 同一セグメント内の同一トラックのサンプルは data_offset 昇順で連続配置する必要がある (1 track = 1 traf = 1 trun 前提)
  • composition_time_offset は trun に書くため i32::MIN..=i32::MAX の範囲に限られる。負値と > i32::MAX の値の混在はエラー
  • 映像トラックの全サンプルが keyframe = false でも NoSyncSamples にはならない (trun の SampleFlags と sidx の SAP 判定に使われる)
  • mfra_bytes() は、実際にファイル先頭に配置する init segment を確定させた後で呼ぶこと (tfra の moof_offset は init segment のサイズ基準で計算される)
use shiguredo_mp4::mux::{Fmp4SegmentMuxer, Sample};

let mut muxer = Fmp4SegmentMuxer::new()?;
let segment = muxer.create_media_segment_metadata(&samples)?;
let init_segment = muxer.init_segment_bytes()?;
let mfra = muxer.mfra_bytes()?;

ボックス群 (boxes)

shiguredo_mp4::boxes モジュールに全ボックスが定義されている。 各ボックスは Encode / Decode / BaseBox を実装し、フィールドがすべて pub なので直接構築・参照できる。

トップレベルボックス

ボックス説明
RootBoxトップレベルボックスの enum (Free / Mdat / Moov / Moof / Mfra / Sidx / Unknown)
FtypBoxファイル種別 (major_brand / minor_version / compatible_brands)
MdatBoxメディアデータ本体 (payload: Vec<u8>)
FreeBox空き領域 (payload: Vec<u8>)
UnknownBox未知のボックスの受け皿
Brandブランド定数 (ISOM / ISO2 / MP41 / AVC1 / HEV1 / HVC1 / AV01 等)

moov ツリー (boxes_moov_tree)

ボックス説明
MoovBoxmovie ボックス (mvhd_box + trak_boxes + mvex_box: Option + unknown_boxes)
MvhdBoxmovie ヘッダー。DEFAULT_RATE / DEFAULT_VOLUME / DEFAULT_MATRIX 定数あり
TrakBoxトラック (tkhd_box + edts_box: Option + mdia_box)
TkhdBoxトラックヘッダー (track_id / duration / width / height 等)
EdtsBox / ElstBox / ElstEntry編集リスト
MdiaBoxメディア情報 (mdhd_box + hdlr_box + minf_box)
MdhdBoxメディアヘッダー (timescale / duration / language / creation_time)
HdlrBoxハンドラー (HANDLER_TYPE_VIDE / HANDLER_TYPE_SOUN / HANDLER_TYPE_SUBT / HANDLER_TYPE_TEXT 定数あり)
MinfBoxメディア情報ボックス (media_header: Option<MediaHeader> + dinf_box + stbl_box)
MediaHeaderenum (Smhd / Vmhd / Sthd / Nmhd)
DinfBox / DrefBox / UrlBoxデータ参照 (DinfBox::LOCAL_FILE 定数あり)
StblBoxサンプルテーブル
StsdBoxサンプル説明 (entries: Vec<SampleEntry>)
SttsBox / SttsEntryデコード時間テーブル (from_sample_deltas() で構築可能)
CttsBox / CttsEntryコンポジション時間オフセット
CslgBoxコンポジション時間シフト
SdtpBox / SdtpSampleFlags独立/再利用可能サンプル
StscBox / StscEntryサンプル-チャンクマッピング
StszBoxサンプルサイズ。Fixed { sample_size } / Variable { entry_sizes } の enum
StcoBox / Co64Boxチャンクオフセット (32bit / 64bit)。Either::A / Either::B で使い分ける
StssBox同期サンプル (不在 = 全サンプル同期)
MvexBox / MehdBox / TrexBoxムービー拡張 (fMP4 用)
EsdsBoxES 記述子 (AAC 用)

fMP4 ボックス (boxes_fmp4)

ボックス説明
MoofBoxムービーフラグメント (mfhd_box + traf_boxes)
MfhdBoxフラグメントヘッダー (sequence_number)
TrafBoxトラックフラグメント (tfhd_box + tfdt_box: Option + trun_boxes)
TfhdBoxトラックフラグメントヘッダー。FLAG_BASE_DATA_OFFSET_PRESENT 等のフラグ定数あり。default_base_is_moof / base_data_offset / default_sample_* フィールド
TfdtBoxフラグメントデコード時間 (base_media_decode_time)
TrunBoxトラックフラグメントラン (data_offset: Option<i32> + samples: Vec<TrunSample>)
TrunSampleduration / size / flags / composition_time_offset (すべて Option)
SidxBox / SidxReferenceセグメントインデックス (DASH 用)
MfraBox / TfraBox / TfraEntry / MfroBoxムービーフラグメントランダムアクセス

TrunBox のフィールドに Option を渡すと、対応するフラグが立っていない場合はフィールドが省略される。

サンプルエントリー (boxes_sample_entry)

SampleEntry は以下の enum:

variantボックス種別コーデック
Avc1(Avc1Box)avc1H.264 / AVC
Hev1(Hev1Box)hev1H.265 / HEVC (パラメータセット in-band)
Hvc1(Hvc1Box)hvc1H.265 / HEVC (パラメータセット out-of-band)
Vp08(Vp08Box)vp08VP8
Vp09(Vp09Box)vp09VP9
Av01(Av01Box)av01AV1
Opus(OpusBox)OpusOpus
Mp4a(Mp4aBox)mp4aAAC
Flac(FlacBox)fLaCFLAC
Stpp(StppBox)stppXML 字幕 (TTML / IMSC)
Wvtt(WvttBox)wvttWebVTT 字幕
Tx3g(Tx3gBox)tx3g3GPP タイムドテキスト
Unknown(UnknownBox)-未知のサンプルエントリー

SampleEntry のヘルパー:

  • audio_channel_count() / audio_sample_rate() / audio_sample_size() - 音声のチャンネル数 / サンプリングレート / ビット深度
  • video_resolution() - (幅, 高さ) を返す

コーデック設定ボックス (avcC / hvcC / vpcC / av1C / dOps / dfLa 等) も同モジュールで提供される。 Avc1Box 等の映像系は VisualSampleEntryFields (幅・高さ・解像度等)、音声系は AudioSampleEntryFields を持つ。

補助モジュール

aux (サンプルテーブルアクセサ)

StblBox をラップしてサンプル情報を簡単に取り出すための構造体。

  • SampleTableAccessor<T>: new(stbl_box) / sample_count() / chunk_count() / get_sample(NonZeroU32) / get_sample_by_timestamp(u64) / get_chunk(NonZeroU32) / samples() / chunks() / stbl_box()
  • SampleAccessor<'a, T>: index() / duration() / timestamp() / data_size() / data_offset() / is_sync_sample() / sync_sample() / composition_time_offset() / chunk()
  • ChunkAccessor<'a, T>: index() / offset() / sample_entry() / sample_entry_index() / sample_count() / samples()

SampleTableAccessor::new() は stts / stsz / stsc / stco(または co64) / stss / ctts の整合性を検証し、 不整合 (InconsistentSampleCount / FirstChunkIndexIsNotOne / ChunkIndicesNotMonotonicallyIncreasing 等) を SampleTableAccessorError で報告する。

use shiguredo_mp4::aux::{SampleTableAccessor, SampleAccessor};

let table = SampleTableAccessor::new(&stbl_box)?;
for sample in table.samples() {
    println!(
        "sample {}: ts={}, duration={}, offset={}, size={}",
        sample.index(),
        sample.timestamp(),
        sample.duration(),
        sample.data_offset(),
        sample.data_size(),
    );
}

descriptors (ES 記述子)

EsDescriptor / DecoderConfigDescriptor / DecoderSpecificInfo / SlConfigDescriptor を提供。 OBJECT_TYPE_INDICATION_AUDIO_ISO_IEC_14496_3 (0x40) 等の定数あり。

docs

  • docs::hybrid_mp4 - OBS の Hybrid MP4 の取り扱い
  • docs::subtitle - 字幕トラックの取り扱い

対応コーデック

  • 音声: AAC (mp4a) / Opus (Opus) / FLAC (fLaC)
  • 映像: VP8 (vp08) / VP9 (vp09) / AV1 (av01) / H.264 (avc1) / H.265 (hev1, hvc1)
  • 字幕: stpp / wvtt / tx3g

規格書

  • ISO/IEC 14496-1 (MPEG-4 システム)
  • ISO/IEC 14496-12 (ISO Base Media File Format)
  • ISO/IEC 14496-14 (MP4 ファイル形式)
  • ISO/IEC 14496-15 (AVC / HEVC のファイル形式)
  • ISO/IEC 14496-30 (Timed Text 系)
  • 3GPP TS 26.245 (タイムドテキスト)
  • VP Codec ISO Media File Format Binding
  • RFC 6386: VP8 Data Format and Decoding Guide
  • VP9 Bitstream and Decoding Process Specification
  • AV1 Codec ISO Media File Format Binding
  • AV1 Bitstream & Decoding Process Specification
  • Encapsulation of Opus in ISO Base Media File Format
  • Encapsulation of FLAC in ISO Base Media File Format

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

このスキルの問題を報告する