Skip to main content

atmos/os_lib/
fmp4.rs

1// fmp4.rs - 断片化 MP4(fMP4、ISO/IEC 14496-12 の Movie Fragment)のデマルチプレクサ。純粋モジュール。
2//
3// 【2026-10-17】YouTube の DASH / MSE は映像・音声とも fMP4 のセグメント(初期化セグメント =
4// ftyp + moov、メディアセグメント = [styp] [sidx] moof + mdat)で届くため新設。`SourceBuffer.appendBuffer`
5// のように任意の位置で区切られたバイト列を `push` で受け取り、完結したトップレベルのボックスから順に
6// 処理して、時刻付きのサンプル(復号順)を返す。
7//
8// 対応: moov(trak の tkhd・mdhd・hdlr・stsd の avcC/esds、mvex/trex の既定値)、moof(mfhd・traf の
9// tfhd・tfdt・trun。default-base-is-moof・base-data-offset・既定値の継承)、mdat、64 ビット長のボックス。
10// 未対応: 暗号化(senc/saiz)、サンプルグループ、edit list による時刻のずらし。
11
12use super::mp4::{extract_asc_from_stsd, extract_avcc_from_stsd};
13use alloc::vec::Vec;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
16pub enum TrackKind {
17    Video,
18    Audio,
19    Other,
20}
21
22/// 初期化セグメント(moov)から分かるトラックの情報。
23#[derive(Clone, Debug)]
24pub struct TrackInfo {
25    pub track_id: u32,
26    pub kind: TrackKind,
27    /// mdhd の timescale(1 秒あたりの刻み)。
28    pub timescale: u32,
29    /// 映像の幅・高さ(ピクセル。音声トラックまたは不明なら 0)。
30    pub width: u32,
31    pub height: u32,
32    /// H.264 の avcC(映像)。
33    pub avcc: Option<Vec<u8>>,
34    /// AAC の AudioSpecificConfig(音声)。
35    pub asc: Option<Vec<u8>>,
36    default_duration: u32,
37    default_size: u32,
38    default_flags: u32,
39}
40
41/// 1 サンプル(映像なら 1 アクセスユニット、AAC なら 1 フレーム)。時刻は timescale 単位。
42#[derive(Clone, Debug)]
43pub struct FragSample {
44    pub track_id: u32,
45    /// 復号時刻。
46    pub dts: u64,
47    /// 表示時刻(dts + composition offset)。
48    pub pts: i64,
49    pub duration: u32,
50    pub keyframe: bool,
51    pub data: Vec<u8>,
52}
53
54/// trun から求めた、mdat の中のサンプルの位置(ストリーム先頭からの絶対位置)。
55struct PendingSample {
56    track_id: u32,
57    dts: u64,
58    pts: i64,
59    duration: u32,
60    keyframe: bool,
61    offset: u64,
62    size: u32,
63}
64
65/// fMP4 デマルチプレクサ。バイト列を順に `push` する。
66#[derive(Default)]
67pub struct Fmp4Demuxer {
68    pub tracks: Vec<TrackInfo>,
69    /// まだ完結していないボックスのバイト列(`buf_pos` はその先頭のストリーム上の位置)。
70    buf: Vec<u8>,
71    buf_pos: u64,
72    /// 直前の moof から求めたサンプル(次の mdat で中身を取り出す)。
73    pending: Vec<PendingSample>,
74    /// トラックごとの次の復号時刻(tfdt が無いフラグメントの続きに使う)。
75    next_dts: Vec<(u32, u64)>,
76}
77
78fn be32(d: &[u8], p: usize) -> Option<u32> {
79    d.get(p..p + 4).map(|b| u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
80}
81
82fn be64(d: &[u8], p: usize) -> Option<u64> {
83    d.get(p..p + 8).map(|b| u64::from_be_bytes([b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7]]))
84}
85
86/// ボックスの (ヘッダ長, 全体長, 種類)。全体長 0 は「末尾まで」。
87fn box_header(d: &[u8], p: usize) -> Option<(usize, u64, [u8; 4])> {
88    let size = be32(d, p)? as u64;
89    let t = d.get(p + 4..p + 8)?;
90    let typ = [t[0], t[1], t[2], t[3]];
91    if size == 1 {
92        Some((16, be64(d, p + 8)?, typ))
93    } else {
94        Some((8, size, typ))
95    }
96}
97
98/// `d` の子ボックスを (種類, 中身) で列挙する。
99fn children(d: &[u8]) -> Vec<([u8; 4], &[u8])> {
100    let mut out = Vec::new();
101    let mut p = 0;
102    while let Some((hdr, size, typ)) = box_header(d, p) {
103        let size = if size == 0 { (d.len() - p) as u64 } else { size };
104        if size < hdr as u64 || p as u64 + size > d.len() as u64 {
105            break;
106        }
107        out.push((typ, &d[p + hdr..p + size as usize]));
108        p += size as usize;
109    }
110    out
111}
112
113fn child<'a>(d: &'a [u8], typ: &[u8; 4]) -> Option<&'a [u8]> {
114    children(d).into_iter().find(|(t, _)| t == typ).map(|(_, c)| c)
115}
116
117impl Fmp4Demuxer {
118    pub fn new() -> Self {
119        Self::default()
120    }
121
122    pub fn track(&self, id: u32) -> Option<&TrackInfo> {
123        self.tracks.iter().find(|t| t.track_id == id)
124    }
125
126    /// バイト列を追加し、取り出せたサンプル(復号順)を返す。
127    pub fn push(&mut self, bytes: &[u8]) -> Result<Vec<FragSample>, &'static str> {
128        self.buf.extend_from_slice(bytes);
129        let mut out = Vec::new();
130        while let Some((hdr, size, typ)) = box_header(&self.buf, 0) {
131            if size == 0 {
132                // 「末尾まで」の mdat は区切りが分からないので、来た分を末尾とみなさず待つ。
133                break;
134            }
135            if size < hdr as u64 {
136                return Err("fMP4: bad box size");
137            }
138            if size > 512 * 1024 * 1024 {
139                return Err("fMP4: box too large");
140            }
141            if (self.buf.len() as u64) < size {
142                break;
143            }
144            let size = size as usize;
145            let start = self.buf_pos;
146            let body: Vec<u8> = self.buf[hdr..size].to_vec();
147            match &typ {
148                b"moov" => self.parse_moov(&body)?,
149                b"moof" => self.parse_moof(&body, start)?,
150                b"mdat" => self.take_mdat(&body, start + hdr as u64, &mut out),
151                _ => {} // ftyp / styp / sidx / free など
152            }
153            self.buf.drain(..size);
154            self.buf_pos += size as u64;
155        }
156        Ok(out)
157    }
158
159    fn parse_moov(&mut self, moov: &[u8]) -> Result<(), &'static str> {
160        self.tracks.clear();
161        for (t, trak) in children(moov) {
162            if &t != b"trak" {
163                continue;
164            }
165            let Some(tkhd) = child(trak, b"tkhd") else { continue };
166            let is_tkhd_v1 = tkhd.first() == Some(&1);
167            let track_id = if is_tkhd_v1 { be32(tkhd, 20) } else { be32(tkhd, 12) }.unwrap_or(0);
168            // tkhd 末尾の width / height(16.16 固定小数点、v0 は offset 76/80、v1 は 88/92)
169            let tkhd_w = be32(tkhd, if is_tkhd_v1 { 88 } else { 76 }).map_or(0, |v| v >> 16);
170            let tkhd_h = be32(tkhd, if is_tkhd_v1 { 92 } else { 80 }).map_or(0, |v| v >> 16);
171            let Some(mdia) = child(trak, b"mdia") else { continue };
172            let timescale = child(mdia, b"mdhd")
173                .and_then(|m| if m.first() == Some(&1) { be32(m, 20) } else { be32(m, 12) })
174                .unwrap_or(1000)
175                .max(1);
176            let kind = match child(mdia, b"hdlr").and_then(|h| h.get(8..12)) {
177                Some(b"vide") => TrackKind::Video,
178                Some(b"soun") => TrackKind::Audio,
179                _ => TrackKind::Other,
180            };
181            let stsd = child(mdia, b"minf").and_then(|m| child(m, b"stbl")).and_then(|s| child(s, b"stsd"));
182            // stsd 内の VisualSampleEntry (offset 8+16+16 = 40..44 に u16 width, u16 height)
183            let (stsd_w, stsd_h) = stsd
184                .and_then(|s| s.get(40..44))
185                .map(|b| (u16::from_be_bytes([b[0], b[1]]) as u32, u16::from_be_bytes([b[2], b[3]]) as u32))
186                .unwrap_or((0, 0));
187            let (width, height) = if kind == TrackKind::Video {
188                (if stsd_w > 0 { stsd_w } else { tkhd_w }, if stsd_h > 0 { stsd_h } else { tkhd_h })
189            } else {
190                (0, 0)
191            };
192            let (avcc, asc) = match (kind, stsd) {
193                (TrackKind::Video, Some(s)) => (extract_avcc_from_stsd(s), None),
194                (TrackKind::Audio, Some(s)) => (None, extract_asc_from_stsd(s)),
195                _ => (None, None),
196            };
197            self.tracks.push(TrackInfo {
198                track_id,
199                kind,
200                timescale,
201                width,
202                height,
203                avcc,
204                asc,
205                default_duration: 0,
206                default_size: 0,
207                default_flags: 0,
208            });
209        }
210        // mvex/trex(トラックごとの既定値)
211        if let Some(mvex) = child(moov, b"mvex") {
212            for (t, trex) in children(mvex) {
213                if &t != b"trex" {
214                    continue;
215                }
216                let id = be32(trex, 4).unwrap_or(0);
217                if let Some(tr) = self.tracks.iter_mut().find(|x| x.track_id == id) {
218                    tr.default_duration = be32(trex, 12).unwrap_or(0);
219                    tr.default_size = be32(trex, 16).unwrap_or(0);
220                    tr.default_flags = be32(trex, 20).unwrap_or(0);
221                }
222            }
223        }
224        if self.tracks.is_empty() {
225            return Err("fMP4: no tracks in moov");
226        }
227        Ok(())
228    }
229
230    /// moof(`moof_pos` はその先頭のストリーム上の位置)から各サンプルの位置と時刻を求める。
231    fn parse_moof(&mut self, moof: &[u8], moof_pos: u64) -> Result<(), &'static str> {
232        self.pending.clear();
233        // base-data-offset も default-base-is-moof も無いときの既定: 最初の traf は moof の先頭、
234        // 以降は直前の traf のデータの終わり(8.8.7.1)。
235        let mut implicit_base = moof_pos;
236        for (t, traf) in children(moof) {
237            if &t != b"traf" {
238                continue;
239            }
240            let tfhd = child(traf, b"tfhd").ok_or("fMP4: traf without tfhd")?;
241            let flags = be32(tfhd, 0).unwrap_or(0) & 0xff_ffff;
242            let track_id = be32(tfhd, 4).ok_or("fMP4: short tfhd")?;
243            let tr = self.track(track_id).cloned();
244            let (mut dd, mut ds, mut df) = tr.as_ref().map_or((0, 0, 0), |t| (t.default_duration, t.default_size, t.default_flags));
245            let mut p = 8;
246            let mut base = if flags & 0x02_0000 != 0 { moof_pos } else { implicit_base };
247            if flags & 0x01 != 0 {
248                base = be64(tfhd, p).ok_or("fMP4: short tfhd")?;
249                p += 8;
250            }
251            if flags & 0x02 != 0 {
252                p += 4; // sample_description_index
253            }
254            if flags & 0x08 != 0 {
255                dd = be32(tfhd, p).ok_or("fMP4: short tfhd")?;
256                p += 4;
257            }
258            if flags & 0x10 != 0 {
259                ds = be32(tfhd, p).ok_or("fMP4: short tfhd")?;
260                p += 4;
261            }
262            if flags & 0x20 != 0 {
263                df = be32(tfhd, p).ok_or("fMP4: short tfhd")?;
264            }
265            let mut dts = match child(traf, b"tfdt") {
266                Some(tfdt) if tfdt.first() == Some(&1) => be64(tfdt, 4).unwrap_or(0),
267                Some(tfdt) => be32(tfdt, 4).unwrap_or(0) as u64,
268                None => self.next_dts.iter().find(|(id, _)| *id == track_id).map_or(0, |x| x.1),
269            };
270            let mut data_end = base;
271            for (t2, trun) in children(traf) {
272                if &t2 != b"trun" {
273                    continue;
274                }
275                let version = trun.first().copied().unwrap_or(0);
276                let tf = be32(trun, 0).unwrap_or(0) & 0xff_ffff;
277                let count = be32(trun, 4).ok_or("fMP4: short trun")? as usize;
278                if count > 1_000_000 {
279                    return Err("fMP4: too many samples in trun");
280                }
281                let mut q = 8;
282                let mut off = data_end;
283                if tf & 0x01 != 0 {
284                    let d = be32(trun, q).ok_or("fMP4: short trun")? as i32;
285                    off = (base as i64 + d as i64).max(0) as u64;
286                    q += 4;
287                }
288                let mut first_flags = None;
289                if tf & 0x04 != 0 {
290                    first_flags = Some(be32(trun, q).ok_or("fMP4: short trun")?);
291                    q += 4;
292                }
293                for i in 0..count {
294                    let mut dur = dd;
295                    let mut size = ds;
296                    let mut sflags = if i == 0 { first_flags.unwrap_or(df) } else { df };
297                    let mut cto: i64 = 0;
298                    if tf & 0x100 != 0 {
299                        dur = be32(trun, q).ok_or("fMP4: short trun")?;
300                        q += 4;
301                    }
302                    if tf & 0x200 != 0 {
303                        size = be32(trun, q).ok_or("fMP4: short trun")?;
304                        q += 4;
305                    }
306                    if tf & 0x400 != 0 {
307                        let f = be32(trun, q).ok_or("fMP4: short trun")?;
308                        if !(i == 0 && first_flags.is_some()) {
309                            sflags = f;
310                        }
311                        q += 4;
312                    }
313                    if tf & 0x800 != 0 {
314                        let v = be32(trun, q).ok_or("fMP4: short trun")?;
315                        cto = if version == 0 { v as i64 } else { v as i32 as i64 };
316                        q += 4;
317                    }
318                    // sample_is_non_sync_sample(ビット 16)が 0 ならキーフレーム。
319                    let keyframe = sflags & 0x1_0000 == 0;
320                    self.pending.push(PendingSample {
321                        track_id,
322                        dts,
323                        pts: dts as i64 + cto,
324                        duration: dur,
325                        keyframe,
326                        offset: off,
327                        size,
328                    });
329                    dts += dur as u64;
330                    off += size as u64;
331                }
332                data_end = off;
333            }
334            implicit_base = data_end;
335            if let Some(e) = self.next_dts.iter_mut().find(|(id, _)| *id == track_id) {
336                e.1 = dts;
337            } else {
338                self.next_dts.push((track_id, dts));
339            }
340        }
341        Ok(())
342    }
343
344    /// mdat(中身の先頭が `data_pos`)から、直前の moof のサンプルを取り出す。
345    fn take_mdat(&mut self, mdat: &[u8], data_pos: u64, out: &mut Vec<FragSample>) {
346        let end = data_pos + mdat.len() as u64;
347        self.pending.retain(|s| {
348            if s.offset >= data_pos && s.offset + s.size as u64 <= end {
349                let a = (s.offset - data_pos) as usize;
350                out.push(FragSample {
351                    track_id: s.track_id,
352                    dts: s.dts,
353                    pts: s.pts,
354                    duration: s.duration,
355                    keyframe: s.keyframe,
356                    data: mdat[a..a + s.size as usize].to_vec(),
357                });
358                false
359            } else {
360                true
361            }
362        });
363    }
364}