isobmff/boxes/track.rs
1//! Track structure boxes defined in ISO/IEC 14496-12 - 8.3
2
3use fixed::traits::ToFixed;
4use fixed::types::extra::{U8, U16};
5use fixed::{FixedI16, FixedU32};
6use scuffle_bytes_util::zero_copy::{Deserialize, DeserializeSeed, Serialize, U24Be};
7
8use super::{Brand, EditBox, MediaBox, MetaBox, UserDataBox};
9use crate::{BoxHeader, IsoBox, IsoSized, UnknownBox};
10
11/// Track box
12///
13/// ISO/IEC 14496-12 - 8.3.1
14#[derive(IsoBox, Debug, PartialEq, Eq)]
15#[iso_box(box_type = b"trak", crate_path = crate)]
16pub struct TrackBox<'a> {
17 /// The contained [`TrackHeaderBox`]. (mandatory)
18 #[iso_box(nested_box)]
19 pub tkhd: TrackHeaderBox,
20 /// The contained [`TrackReferenceBox`]. (optional)
21 #[iso_box(nested_box(collect))]
22 pub tref: Option<TrackReferenceBox<'a>>,
23 /// The contained [`TrackGroupBox`]. (optional)
24 #[iso_box(nested_box(collect))]
25 pub trgr: Option<TrackGroupBox<'a>>,
26 /// The contained [`EditBox`]. (optional)
27 #[iso_box(nested_box(collect))]
28 pub edts: Option<EditBox>,
29 /// The contained [`TrackTypeBox`]. (optional)
30 #[iso_box(nested_box(collect))]
31 pub ttyp: Option<TrackTypeBox>,
32 /// The contained [`MetaBox`]. (optional)
33 #[iso_box(nested_box(collect))]
34 pub meta: Option<MetaBox<'a>>,
35 /// The contained [`MediaBox`]. (mandatory)
36 #[iso_box(nested_box)]
37 pub mdia: MediaBox<'a>,
38 /// A list of unknown boxes that were not recognized during deserialization.
39 #[iso_box(nested_box(collect_unknown))]
40 pub unknown_boxes: Vec<UnknownBox<'a>>,
41 /// The contained [`UserDataBox`]. (optional)
42 #[iso_box(nested_box(collect))]
43 pub udta: Option<UserDataBox<'a>>,
44}
45
46impl<'a> TrackBox<'a> {
47 /// Creates a new [`TrackBox`] with the given `tkhd`, optional `edts`, and `mdia`.
48 pub fn new(tkhd: TrackHeaderBox, edts: Option<EditBox>, mdia: MediaBox<'a>) -> Self {
49 Self {
50 tkhd,
51 tref: None,
52 trgr: None,
53 edts,
54 ttyp: None,
55 meta: None,
56 mdia,
57 unknown_boxes: vec![],
58 udta: None,
59 }
60 }
61}
62
63bitflags::bitflags! {
64 /// Track header box flags
65 #[derive(Debug, PartialEq, Eq, Clone, Copy)]
66 pub struct TrackHeaderBoxFlags: u32 {
67 /// If this flag is set, it indicates that the track is enabled.
68 /// A disabled track (when this flag is not set) is treated as if it were not present.
69 const TrackEnabled = 0x000001;
70 /// If this flag is set, it indicates that the track, or one of its
71 /// alternatives (if any) forms a direct part of the presentation.
72 /// If this flag is unset, it indicates that the track does not represent a direct part of the presentation.
73 const TrackInMovie = 0x000002;
74 /// This flag currently has no assigned meaning, and the
75 /// value should be ignored by readers. In the absence of further guidance (e.g. from derived
76 /// specifications), the same value as for [`Self::TrackInMovie`] should be written.
77 const TrackInPreview = 0x000004;
78 /// If this flag is set, it indicates that the width and
79 /// height fields are not expressed in pixel units. The values have the same units but these units
80 /// are not specified. The values are only an indication of the desired aspect ratio. If the aspect
81 /// ratios of this track and other related tracks are not identical, then the respective positioning of
82 /// the tracks is undefined, possibly defined by external contexts.
83 const TrackSizeIsAspectRatio = 0x000008;
84 }
85}
86
87impl<'a> Deserialize<'a> for TrackHeaderBoxFlags {
88 fn deserialize<R>(reader: R) -> std::io::Result<Self>
89 where
90 R: scuffle_bytes_util::zero_copy::ZeroCopyReader<'a>,
91 {
92 let flags = U24Be::deserialize(reader)?;
93 Ok(Self::from_bits_truncate(*flags))
94 }
95}
96
97impl Serialize for TrackHeaderBoxFlags {
98 fn serialize<W>(&self, writer: W) -> std::io::Result<()>
99 where
100 W: std::io::Write,
101 {
102 U24Be(self.bits()).serialize(writer)
103 }
104}
105
106impl IsoSized for TrackHeaderBoxFlags {
107 fn size(&self) -> usize {
108 3
109 }
110}
111
112/// Track header box
113///
114/// ISO/IEC 14496-12 - 8.3.2
115#[derive(IsoBox, Debug, PartialEq, Eq)]
116#[iso_box(box_type = b"tkhd", skip_impl(deserialize_seed, serialize, sized), crate_path = crate)]
117pub struct TrackHeaderBox {
118 // full header:
119 /// An integer that specifies the version of this box.
120 ///
121 /// Part of full box header.
122 pub version: u8,
123 /// The flags for this box.
124 ///
125 /// Part of full box header.
126 pub flags: TrackHeaderBoxFlags,
127 // body:
128 /// An integer that declares the creation time of this track (in seconds since midnight,
129 /// Jan. 1, 1904, in UTC time).
130 pub creation_time: u64,
131 /// An integer that declares the most recent time the track was modified (in seconds
132 /// since midnight, Jan. 1, 1904, in UTC time).
133 pub modification_time: u64,
134 /// An integer that uniquely identifies this track over the entire life-time of this presentation;
135 /// `track_ID`s are never re-used and cannot be zero.
136 pub track_id: u32,
137 /// Reserved 32 bits, must be set to 0.
138 pub reserved1: u32,
139 /// An integer that indicates the duration of this track (in the timescale indicated in the
140 /// [`MovieHeaderBox`](super::MovieHeaderBox)) This duration field may be indefinite (all 1s) when either there is no edit list
141 /// and the [`MediaHeaderBox`](super::MediaHeaderBox) duration is indefinite (i.e. all 1s), or when an indefinitely repeated edit
142 /// list is desired (see subclause 8.6.6 for repeated edits). If there is no edit list and the duration is
143 /// not indefinite, then the duration shall be equal to the media duration given in the [`MediaHeaderBox`](super::MediaHeaderBox),
144 /// converted into the timescale in the [`MovieHeaderBox`](super::MovieHeaderBox). Otherwise the value of this field is equal to the
145 /// sum of the durations of all of the track’s edits (possibly including repetitions).
146 pub duration: u64,
147 /// Reserved 64 bits, must be set to 0.
148 pub reserved2: u64,
149 /// Specifies the front-to-back ordering of video tracks; tracks with lower numbers are closer to the
150 /// viewer. 0 is the normal value, and -1 would be in front of track 0, and so on
151 pub layer: i16,
152 /// An integer that specifies a group or collection of tracks. If this field is 0 there is
153 /// no information on possible relations to other tracks. If this field is not 0, it should be the same for
154 /// tracks that contain alternate data for one another and different for tracks belonging to different
155 /// such groups. Only one track within an alternate group should be played or streamed at any one
156 /// time, and shall be distinguishable from other tracks in the group via attributes such as bitrate,
157 /// codec, language, packet size etc. A group may have only one member.
158 pub alternate_group: i16,
159 /// Specifies the track's relative audio volume. Full volume is 1.0
160 /// and is the normal value. Its value is irrelevant for a purely visual track. Tracks may be composed
161 /// by combining them according to their volume, and then using the overall [`MovieHeaderBox`](super::MovieHeaderBox) volume
162 /// setting; or more complex audio composition (e.g. MPEG-4 BIFS) may be used.
163 pub volume: FixedI16<U8>,
164 /// Reserved 16 bits, must be set to 0.
165 pub reserved3: u16,
166 /// Provides a transformation matrix for the video; `(u,v,w)` are restricted here to `(0,0,1)`, hex
167 /// `(0,0,0x40000000)`.
168 pub matrix: [i32; 9],
169 /// [`width`](Self::width) and [`height`](Self::height) are track-dependent as follows:
170 ///
171 /// For text and subtitle tracks, they may, depending on the coding format, describe the suggested size of
172 /// the rendering area. For such tracks, the value `0x0` may also be used to indicate that the data may
173 /// be rendered at any size, that no preferred size has been indicated and that the actual size may be
174 /// determined by the external context or by reusing the width and height of another track. For those
175 /// tracks, the flag [`TrackSizeIsAspectRatio`](TrackHeaderBoxFlags::TrackSizeIsAspectRatio) may also be used.
176 ///
177 /// For non-visual tracks (e.g. audio), they should be set to zero.
178 ///
179 /// For all other tracks, they specify the track's visual presentation size. These need not be the same as the
180 /// pixel dimensions of the images, which is documented in the sample description(s); all images in the
181 /// sequence are scaled to this size, before any overall transformation of the track represented by the
182 /// matrix. The pixel dimensions of the images are the default values.
183 pub width: FixedU32<U16>,
184 /// See [`width`](Self::width).
185 pub height: FixedU32<U16>,
186}
187
188impl TrackHeaderBox {
189 /// Creates a new [`TrackHeaderBox`] with the specified parameters.
190 ///
191 /// All other fields are initialized to their default values.
192 pub fn new(
193 creation_time: u64,
194 modification_time: u64,
195 track_id: u32,
196 duration: u64,
197 dimensions: Option<(u32, u32)>,
198 ) -> Self {
199 let version = if creation_time > u32::MAX as u64 || modification_time > u32::MAX as u64 || duration > u32::MAX as u64
200 {
201 1
202 } else {
203 0
204 };
205
206 let (width, height) = dimensions.unwrap_or((0, 0));
207 let volume = if dimensions.is_some() { 0 } else { 1 };
208
209 Self {
210 version,
211 flags: TrackHeaderBoxFlags::TrackEnabled | TrackHeaderBoxFlags::TrackInMovie,
212 creation_time,
213 modification_time,
214 track_id,
215 reserved1: 0,
216 duration,
217 reserved2: 0,
218 layer: 0,
219 alternate_group: 0,
220 volume: volume.to_fixed(),
221 reserved3: 0,
222 matrix: [0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000],
223 width: width.to_fixed(),
224 height: height.to_fixed(),
225 }
226 }
227}
228
229impl<'a> DeserializeSeed<'a, BoxHeader> for TrackHeaderBox {
230 fn deserialize_seed<R>(mut reader: R, _seed: BoxHeader) -> std::io::Result<Self>
231 where
232 R: scuffle_bytes_util::zero_copy::ZeroCopyReader<'a>,
233 {
234 let version = u8::deserialize(&mut reader)?;
235 let flags = TrackHeaderBoxFlags::deserialize(&mut reader)?;
236
237 let creation_time = if version == 1 {
238 u64::deserialize(&mut reader)?
239 } else {
240 u32::deserialize(&mut reader)? as u64
241 };
242 let modification_time = if version == 1 {
243 u64::deserialize(&mut reader)?
244 } else {
245 u32::deserialize(&mut reader)? as u64
246 };
247 let track_id = u32::deserialize(&mut reader)?;
248 let reserved1 = u32::deserialize(&mut reader)?;
249 let duration = if version == 1 {
250 u64::deserialize(&mut reader)?
251 } else {
252 u32::deserialize(&mut reader)? as u64
253 };
254
255 let reserved2 = u64::deserialize(&mut reader)?;
256
257 let layer = i16::deserialize(&mut reader)?;
258 let alternate_group = i16::deserialize(&mut reader)?;
259 let volume = FixedI16::from_bits(i16::deserialize(&mut reader)?);
260
261 let reserved3 = u16::deserialize(&mut reader)?;
262
263 let mut matrix = [0; 9];
264 for m in &mut matrix {
265 *m = i32::deserialize(&mut reader)?;
266 }
267
268 let width = FixedU32::from_bits(u32::deserialize(&mut reader)?);
269 let height = FixedU32::from_bits(u32::deserialize(&mut reader)?);
270
271 Ok(Self {
272 version,
273 flags,
274 creation_time,
275 modification_time,
276 track_id,
277 reserved1,
278 duration,
279 reserved2,
280 layer,
281 alternate_group,
282 volume,
283 reserved3,
284 matrix,
285 width,
286 height,
287 })
288 }
289}
290
291impl Serialize for TrackHeaderBox {
292 fn serialize<W>(&self, mut writer: W) -> std::io::Result<()>
293 where
294 W: std::io::Write,
295 {
296 self.serialize_box_header(&mut writer)?;
297 self.version.serialize(&mut writer)?;
298 self.flags.serialize(&mut writer)?;
299
300 if self.version == 1 {
301 self.creation_time.serialize(&mut writer)?;
302 self.modification_time.serialize(&mut writer)?;
303 } else {
304 (self.creation_time as u32).serialize(&mut writer)?;
305 (self.modification_time as u32).serialize(&mut writer)?;
306 }
307 self.track_id.serialize(&mut writer)?;
308 self.reserved1.serialize(&mut writer)?;
309 if self.version == 1 {
310 self.duration.serialize(&mut writer)?;
311 } else {
312 (self.duration as u32).serialize(&mut writer)?;
313 }
314
315 self.reserved2.serialize(&mut writer)?;
316
317 self.layer.serialize(&mut writer)?;
318 self.alternate_group.serialize(&mut writer)?;
319 self.volume.to_bits().serialize(&mut writer)?;
320
321 self.reserved3.serialize(&mut writer)?;
322
323 self.matrix.serialize(&mut writer)?;
324
325 self.width.to_bits().serialize(&mut writer)?;
326 self.height.to_bits().serialize(&mut writer)?;
327
328 Ok(())
329 }
330}
331
332impl IsoSized for TrackHeaderBox {
333 fn size(&self) -> usize {
334 let mut size = self.version.size() + self.flags.size();
335 if self.version == 1 {
336 size += 8 + 8; // creation_time, modification_time
337 } else {
338 size += 4 + 4; // creation_time, modification_time
339 }
340 size += 4; // track_id
341 size += 4; // reserved1
342 if self.version == 1 {
343 size += 8; // duration
344 } else {
345 size += 4; // duration
346 }
347 size += 8; // reserved2
348 size += 2; // layer
349 size += 2; // alternate_group
350 size += 2; // volume
351 size += 2; // reserved3
352 size += self.matrix.size(); // matrix
353 size += 4; // width
354 size += 4; // height
355
356 Self::add_header_size(size)
357 }
358}
359
360/// Track reference box
361///
362/// ISO/IEC 14496-12 - 8.3.3
363#[derive(IsoBox, Debug, PartialEq, Eq)]
364#[iso_box(box_type = b"tref", crate_path = crate)]
365pub struct TrackReferenceBox<'a> {
366 /// Any unknown `TrackReferenceTypeBox`es that are contained in this box.
367 #[iso_box(nested_box(collect_unknown))]
368 pub track_reference_type: Vec<UnknownBox<'a>>,
369}
370
371/// Track group box
372///
373/// ISO/IEC 14496-12 - 8.3.4
374#[derive(IsoBox, Debug, PartialEq, Eq)]
375#[iso_box(box_type = b"trgr", crate_path = crate)]
376pub struct TrackGroupBox<'a> {
377 /// Any unknown `TrackGroupTypeBox`es that are contained in this box.
378 #[iso_box(nested_box(collect_unknown))]
379 pub track_group_type: Vec<UnknownBox<'a>>,
380}
381
382/// Track type box
383///
384/// ISO/IEC 14496-12 - 8.3.5
385#[derive(IsoBox, Debug, PartialEq, Eq)]
386#[iso_box(box_type = b"ttyp", crate_path = crate)]
387pub struct TrackTypeBox {
388 /// The "best use" brand of the file which will provide the greatest compatibility.
389 #[iso_box(from = "[u8; 4]")]
390 pub major_brand: Brand,
391 /// Minor version of the major brand.
392 pub minor_version: u32,
393 /// A list of compatible brands.
394 #[iso_box(repeated, from = "[u8; 4]")]
395 pub compatible_brands: Vec<Brand>,
396}