Skip to content
cccskills
FreePublished on GitHub

shiguredo-mp4

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

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md23.7 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

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

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

Report a problem with this skill