Lightningbeam/lightningbeam-ui/lightningbeam-core/tests/packed_video_stream.rs

82 lines
3.3 KiB
Rust

//! Integration test for the packed-video streaming path:
//! SQLite `MediaKind::Video` blob -> `BlobReader` -> `ffmpeg-blob-io` AVIO shim ->
//! ffmpeg `Input`. This is exactly what `video.rs`'s `VideoSource::Packed` does to
//! decode frames (and what `daw-backend` does for the embedded audio), minus the
//! thin wrapper. We use a hand-built WAV as the packed container — FFmpeg must
//! demux it through our blob reader, proving the production byte path end to end.
//!
//! (Decoding real video frames is left to user runtime verification; here we prove
//! the container streams from the blob and exposes its streams.)
use ffmpeg_blob_io::BlobInput;
use lightningbeam_core::beam_archive::{BeamArchive, MediaKind, MediaMeta, MediaStorage};
use std::sync::atomic::{AtomicU64, Ordering};
use uuid::Uuid;
fn temp_db_path(tag: &str) -> std::path::PathBuf {
static N: AtomicU64 = AtomicU64::new(0);
let n = N.fetch_add(1, Ordering::Relaxed);
let mut p = std::env::temp_dir();
p.push(format!("packed_video_it_{}_{}_{}.beam", std::process::id(), tag, n));
let _ = std::fs::remove_file(&p);
p
}
/// Minimal 16-bit PCM WAV (a real, demuxable container).
fn make_wav(sample_rate: u32, channels: u16, samples: &[i16]) -> Vec<u8> {
let bits: u16 = 16;
let block_align: u16 = channels * (bits / 8);
let byte_rate: u32 = sample_rate * block_align as u32;
let data_len: u32 = (samples.len() * 2) as u32;
let mut v = Vec::new();
v.extend_from_slice(b"RIFF");
v.extend_from_slice(&(36 + data_len).to_le_bytes());
v.extend_from_slice(b"WAVE");
v.extend_from_slice(b"fmt ");
v.extend_from_slice(&16u32.to_le_bytes());
v.extend_from_slice(&1u16.to_le_bytes());
v.extend_from_slice(&channels.to_le_bytes());
v.extend_from_slice(&sample_rate.to_le_bytes());
v.extend_from_slice(&byte_rate.to_le_bytes());
v.extend_from_slice(&block_align.to_le_bytes());
v.extend_from_slice(&bits.to_le_bytes());
v.extend_from_slice(b"data");
v.extend_from_slice(&data_len.to_le_bytes());
for s in samples {
v.extend_from_slice(&s.to_le_bytes());
}
v
}
#[test]
fn packed_media_streams_through_avio_to_ffmpeg() {
let path = temp_db_path("stream");
let id = Uuid::new_v4();
let samples: Vec<i16> = (0..4000).map(|i| ((i % 200) as i16 - 100) * 100).collect();
let container = make_wav(8000, 1, &samples);
// Pack as a Video media row (the save path does this for real videos).
let mut archive = BeamArchive::create(&path).unwrap();
archive
.put_media_packed(id, MediaKind::Video, "wav", &container, MediaMeta::default())
.unwrap();
let info = archive.media_info(id).unwrap().unwrap();
assert_eq!(info.kind, MediaKind::Video);
assert_eq!(info.storage, MediaStorage::Packed);
drop(archive);
// Reproduce VideoSource::Packed::open(): fresh read-only archive + blob reader.
let archive = BeamArchive::open(&path).unwrap();
let hint = archive.media_info(id).unwrap().map(|i| i.codec);
let reader = archive.open_blob_reader(&path, id).unwrap();
let input = BlobInput::open(Box::new(reader), hint.as_deref())
.expect("open the packed container by streaming from the SQLite blob");
assert!(
input.streams().count() >= 1,
"demuxer found streams via the blob-backed AVIO shim"
);
let _ = std::fs::remove_file(&path);
}