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