motioncam_decoder/
decoder.rs1use std::collections::HashMap;
2use std::path::Path;
3use memmap2::Mmap;
4use serde_json::Value;
5#[cfg(feature = "parallel")]
6use rayon::prelude::*;
7use crate::container::*;
8use crate::error::{McRawError, Result, read_le_bytes};
9use crate::raw;
10use crate::raw_legacy;
11
12pub struct AudioChunk {
14 pub timestamp_ns: i64,
16 pub samples: Vec<i16>,
18}
19
20pub struct Decoder {
25 mmap: Mmap,
26 metadata: Value,
27 frame_offsets: Vec<BufferOffset>,
28 frame_map: HashMap<i64, BufferOffset>,
29 audio_offsets: Vec<BufferOffset>,
30}
31
32impl Decoder {
33 pub fn from_path(path: impl AsRef<Path>) -> Result<Self> {
35 let file = std::fs::File::open(path)?;
36 let mmap = unsafe { Mmap::map(&file)? };
37 Self::from_mmap(mmap)
38 }
39
40 fn from_mmap(mmap: Mmap) -> Result<Self> {
41 let data = &mmap[..];
42 if data.len() < 8 {
43 return Err(McRawError::InvalidHeader);
44 }
45 if data[0..7] != CONTAINER_ID {
46 return Err(McRawError::InvalidHeader);
47 }
48 if data[7] != CONTAINER_VERSION {
49 return Err(McRawError::UnsupportedVersion(data[7]));
50 }
51
52 let mut cursor = 8;
53 let meta_item = parse_item(&data[cursor..])?;
54 cursor += 8;
55 if meta_item.item_type != ItemType::Metadata {
56 return Err(McRawError::InvalidItemType(meta_item.item_type as u32));
57 }
58 let meta_json = data
59 .get(cursor..cursor + meta_item.size as usize)
60 .ok_or(McRawError::TruncatedData)?;
61 let metadata: Value = serde_json::from_slice(meta_json)?;
62
63 let idx_size = std::mem::size_of::<BufferIndex>() + std::mem::size_of::<Item>();
64 if data.len() < idx_size {
65 return Err(McRawError::TruncatedData);
66 }
67 let end_cursor = data.len() - idx_size;
68 let idx_item = parse_item(&data[end_cursor..])?;
69 if idx_item.item_type != ItemType::BufferIndex {
70 return Err(McRawError::InvalidItemType(idx_item.item_type as u32));
71 }
72 let buf_idx = parse_buffer_index(&data[end_cursor + 8..])?;
73 if buf_idx.magic_number != INDEX_MAGIC_NUMBER {
74 return Err(McRawError::CorruptedIndex);
75 }
76
77 let mut frame_offsets = vec![BufferOffset { offset: 0, timestamp: 0 }; buf_idx.num_offsets as usize];
78 let mut off_cursor = buf_idx.index_data_offset as usize;
79 for bo in &mut frame_offsets {
80 *bo = parse_buffer_offset(data.get(off_cursor..).ok_or(McRawError::TruncatedData)?)?;
81 off_cursor += 16;
82 }
83
84 frame_offsets.sort_by_key(|b| b.timestamp);
85 let frame_map: HashMap<_, _> = frame_offsets.iter().map(|b| (b.timestamp, *b)).collect();
86
87 let mut audio_offsets = Vec::new();
88 if let Some(last) = frame_offsets.last() {
89 let mut cur = last.offset as usize;
90 while cur + 8 <= data.len() {
91 let Ok(item) = parse_item(&data[cur..]) else { break; };
92 cur += 8;
93 match item.item_type {
94 ItemType::Buffer | ItemType::Metadata | ItemType::AudioData | ItemType::AudioDataMetadata => {
95 cur += item.size as usize;
96 }
97 ItemType::AudioIndex => {
98 let aidx = parse_audio_index(data.get(cur..).ok_or(McRawError::TruncatedData)?)?;
99 cur += 16;
100 audio_offsets.reserve(aidx.num_offsets as usize);
101 for _ in 0..aidx.num_offsets {
102 audio_offsets.push(parse_buffer_offset(data.get(cur..).ok_or(McRawError::TruncatedData)?)?);
103 cur += 16;
104 }
105 }
106 _ => break,
107 }
108 }
109 }
110
111 Ok(Self { mmap, metadata, frame_offsets, frame_map, audio_offsets })
112 }
113
114 pub fn container_metadata(&self) -> &Value {
116 &self.metadata
117 }
118
119 #[cfg(unix)]
125 pub fn prefetch(&self, timestamp: i64) {
126 if let Some(bo) = self.frame_map.get(×tamp) {
127 let start = bo.offset.max(0) as usize;
128 let end = (start + 4 * 1024 * 1024).min(self.mmap.len());
129 if end > start {
130 unsafe {
131 libc::madvise(
132 self.mmap.as_ptr().add(start) as *mut _,
133 end - start,
134 libc::MADV_WILLNEED,
135 );
136 }
137 }
138 }
139 }
140
141 #[cfg(not(unix))]
143 pub fn prefetch(&self, _timestamp: i64) {}
144
145 pub fn frame_timestamps(&self) -> impl Iterator<Item = i64> + '_ {
147 self.frame_offsets.iter().map(|b| b.timestamp)
148 }
149
150 pub fn audio_sample_rate_hz(&self) -> i64 {
152 self.metadata["extraData"]["audioSampleRate"].as_i64().unwrap_or(0)
153 }
154
155 pub fn num_audio_channels(&self) -> i64 {
157 self.metadata["extraData"]["audioChannels"].as_i64().unwrap_or(0)
158 }
159
160 pub fn load_frame_metadata(&self, timestamp: i64) -> Result<Value> {
162 let offset = self.frame_map.get(×tamp).ok_or(McRawError::FrameNotFound(timestamp))?.offset as usize;
163 let data = &self.mmap[..];
164 let mut cur = offset;
165 let buf_item = parse_item(data.get(cur..).ok_or(McRawError::TruncatedData)?)?;
166 cur += 8 + buf_item.size as usize;
167 let meta_item = parse_item(data.get(cur..).ok_or(McRawError::TruncatedData)?)?;
168 cur += 8;
169 let json_bytes = data.get(cur..cur + meta_item.size as usize).ok_or(McRawError::TruncatedData)?;
170 Ok(serde_json::from_slice(json_bytes)?)
171 }
172
173 pub fn load_frame(&self, timestamp: i64) -> Result<(Vec<u16>, Value)> {
178 let offset = self.frame_map.get(×tamp).ok_or(McRawError::FrameNotFound(timestamp))?.offset as usize;
179 let data = &self.mmap[..];
180 let mut cur = offset;
181 let buf_item = parse_item(data.get(cur..).ok_or(McRawError::TruncatedData)?)?;
182 cur += 8;
183 let compressed = data.get(cur..cur + buf_item.size as usize).ok_or(McRawError::TruncatedData)?;
184 cur += buf_item.size as usize;
185 let meta_item = parse_item(data.get(cur..).ok_or(McRawError::TruncatedData)?)?;
186 cur += 8;
187 let json_bytes = data.get(cur..cur + meta_item.size as usize).ok_or(McRawError::TruncatedData)?;
188 let metadata: Value = serde_json::from_slice(json_bytes)?;
189 let width = metadata["width"].as_u64().ok_or_else(|| McRawError::DecompressionFailed("missing width".into()))? as usize;
190 let height = metadata["height"].as_u64().ok_or_else(|| McRawError::DecompressionFailed("missing height".into()))? as usize;
191 let comp_type = metadata["compressionType"].as_i64().unwrap_or(7);
192 let mut output = vec![0u16; width * height];
193 match comp_type {
194 7 => { raw::decode(&mut output, width, height, compressed)?; }
195 6 => { raw_legacy::decode_legacy(&mut output, width, height, compressed)?; }
196 other => return Err(McRawError::InvalidCompressionType(other as i32)),
197 }
198 Ok((output, metadata))
199 }
200
201 pub fn load_frame_into(&self, timestamp: i64, out: &mut [u16]) -> Result<[f32; 3]> {
207 let offset = self.frame_map.get(×tamp).ok_or(McRawError::FrameNotFound(timestamp))?.offset as usize;
208 let data = &self.mmap[..];
209 let mut cur = offset;
210 let buf_item = parse_item(data.get(cur..).ok_or(McRawError::TruncatedData)?)?;
211 cur += 8;
212 let compressed = data.get(cur..cur + buf_item.size as usize).ok_or(McRawError::TruncatedData)?;
213 cur += buf_item.size as usize;
214 let meta_item = parse_item(data.get(cur..).ok_or(McRawError::TruncatedData)?)?;
215 cur += 8;
216 let json_bytes = data.get(cur..cur + meta_item.size as usize).ok_or(McRawError::TruncatedData)?;
217 let light: FrameMetaLight = serde_json::from_slice(json_bytes)
218 .map_err(|e| McRawError::DecompressionFailed(format!("light meta: {}", e)))?;
219 let width = light.width.ok_or_else(|| McRawError::DecompressionFailed("missing width".into()))? as usize;
220 let height = light.height.ok_or_else(|| McRawError::DecompressionFailed("missing height".into()))? as usize;
221 let comp_type = light.compression_type.unwrap_or(7);
222 let pixel_count = width * height;
223 if out.len() < pixel_count {
224 return Err(McRawError::TruncatedData);
225 }
226 match comp_type {
227 7 => { raw::decode(&mut out[..pixel_count], width, height, compressed)?; }
228 6 => { raw_legacy::decode_legacy(&mut out[..pixel_count], width, height, compressed)?; }
229 other => return Err(McRawError::InvalidCompressionType(other as i32)),
230 }
231 let asn = light.as_shot_neutral.unwrap_or([1.0, 1.0, 1.0]);
232 Ok([asn[0] as f32, asn[1] as f32, asn[2] as f32])
233 }
234
235 pub fn load_all_frames_parallel(&self) -> Result<Vec<(i64, Vec<u16>, Value)>> {
239 self.frame_offsets
240 .par_iter()
241 .map(|bo| {
242 let (pixels, meta) = self.load_frame(bo.timestamp)?;
243 Ok((bo.timestamp, pixels, meta))
244 })
245 .collect()
246 }
247
248 pub fn load_audio(&self) -> Result<Vec<AudioChunk>> {
250 let data = &self.mmap[..];
251 let mut chunks = Vec::with_capacity(self.audio_offsets.len());
252 for off in &self.audio_offsets {
253 let mut cur = off.offset as usize;
254 if cur + 8 > data.len() {
255 continue;
256 }
257 let item = parse_item(data.get(cur..).ok_or(McRawError::TruncatedData)?)?;
258 cur += 8;
259 if item.item_type != ItemType::AudioData {
260 continue;
261 }
262 let samples_bytes = data.get(cur..cur + item.size as usize).ok_or(McRawError::TruncatedData)?;
263 cur += item.size as usize;
264 let samples: Vec<i16> = samples_bytes
265 .chunks_exact(2)
266 .map(|b| i16::from_le_bytes([b[0], b[1]]))
267 .collect();
268 let mut timestamp_ns = -1i64;
269 if cur + 8 <= data.len() {
270 if let Ok(meta_item) = parse_item(data.get(cur..).ok_or(McRawError::TruncatedData)?) {
271 if meta_item.item_type == ItemType::AudioDataMetadata && cur + 16 <= data.len() {
272 timestamp_ns = i64::from_le_bytes(read_le_bytes(data, cur + 8)?);
273 }
274 }
275 }
276 chunks.push(AudioChunk { timestamp_ns, samples });
277 }
278 Ok(chunks)
279 }
280}
281
282#[inline]
283fn parse_item(data: &[u8]) -> Result<Item> {
284 if data.len() < 8 {
285 return Err(McRawError::TruncatedData);
286 }
287 Ok(Item {
288 item_type: ItemType::try_from(u32::from_le_bytes(read_le_bytes(data, 0)?))?,
289 size: u32::from_le_bytes(read_le_bytes(data, 4)?),
290 })
291}
292
293#[inline]
294fn parse_buffer_offset(data: &[u8]) -> Result<BufferOffset> {
295 Ok(BufferOffset {
296 offset: i64::from_le_bytes(read_le_bytes(data, 0)?),
297 timestamp: i64::from_le_bytes(read_le_bytes(data, 8)?),
298 })
299}
300
301#[inline]
302fn parse_buffer_index(data: &[u8]) -> Result<BufferIndex> {
303 Ok(BufferIndex {
304 magic_number: u32::from_le_bytes(read_le_bytes(data, 0)?),
305 num_offsets: i32::from_le_bytes(read_le_bytes(data, 4)?),
306 index_data_offset: i64::from_le_bytes(read_le_bytes(data, 8)?),
307 })
308}
309
310#[inline]
311fn parse_audio_index(data: &[u8]) -> Result<AudioIndex> {
312 Ok(AudioIndex {
313 num_offsets: i64::from_le_bytes(read_le_bytes(data, 0)?),
314 start_timestamp_ms: i64::from_le_bytes(read_le_bytes(data, 8)?),
315 })
316}
317
318#[derive(serde::Deserialize)]
323struct FrameMetaLight {
324 width: Option<u64>,
325 height: Option<u64>,
326 #[serde(rename = "compressionType")]
327 compression_type: Option<i64>,
328 #[serde(rename = "asShotNeutral")]
329 as_shot_neutral: Option<[f64; 3]>,
330}