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 / FullBoxHeader | version + 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 等) |
Utf8String | null 終端 UTF-8 文字列 (new() は null を含む文字列を拒否) |
LanguageCode | 3 文字言語コード (from_ascii("eng") 等。0x60..=0x7F の範囲のみ) |
Mp4FileTime | MP4 時間 (1904/1/1 起点)。from_unix_time() で変換 |
TrackKind | Audio / Video / Subtitle |
SampleFlags | fMP4 のサンプルフラグ (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_offsetdata_offsetは file demuxer ではファイル先頭からの絶対位置、segment demuxer では入力バッファ先頭からの相対位置
マルチプレックス (mux)
Mp4FileMuxer - MP4 ファイルのマルチプレックス
複数トラックのサンプルを統合して MP4 ファイルを生成する。
基本的な使用フロー:
new()またはwith_options(Mp4FileMuxerOptions)でインスタンス生成initial_boxes_bytes()のバイト列をファイルに書き込む (ftyp + 予約領域 + mdat ヘッダー)- サンプルデータをファイルに追記し、
append_sample(&Sample)でメタデータを通知 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は「ファイル全体の絶対位置」ではなく「今回のセグメントのmdatpayload 領域の先頭からの相対位置」- 同一セグメント内の同一トラックのサンプルは
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)
| ボックス | 説明 |
|---|---|
MoovBox | movie ボックス (mvhd_box + trak_boxes + mvex_box: Option + unknown_boxes) |
MvhdBox | movie ヘッダー。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) |
MediaHeader | enum (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 用) |
EsdsBox | ES 記述子 (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>) |
TrunSample | duration / size / flags / composition_time_offset (すべて Option) |
SidxBox / SidxReference | セグメントインデックス (DASH 用) |
MfraBox / TfraBox / TfraEntry / MfroBox | ムービーフラグメントランダムアクセス |
TrunBox のフィールドに Option を渡すと、対応するフラグが立っていない場合はフィールドが省略される。
サンプルエントリー (boxes_sample_entry)
SampleEntry は以下の enum:
| variant | ボックス種別 | コーデック |
|---|---|---|
Avc1(Avc1Box) | avc1 | H.264 / AVC |
Hev1(Hev1Box) | hev1 | H.265 / HEVC (パラメータセット in-band) |
Hvc1(Hvc1Box) | hvc1 | H.265 / HEVC (パラメータセット out-of-band) |
Vp08(Vp08Box) | vp08 | VP8 |
Vp09(Vp09Box) | vp09 | VP9 |
Av01(Av01Box) | av01 | AV1 |
Opus(OpusBox) | Opus | Opus |
Mp4a(Mp4aBox) | mp4a | AAC |
Flac(FlacBox) | fLaC | FLAC |
Stpp(StppBox) | stpp | XML 字幕 (TTML / IMSC) |
Wvtt(WvttBox) | wvtt | WebVTT 字幕 |
Tx3g(Tx3gBox) | tx3g | 3GPP タイムドテキスト |
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
レビュー
まだレビューはありません。使ってみた感想をお寄せください。