Add Muninn media frame document schemas
This commit is contained in:
parent
a4b29ea83e
commit
bf55a7243c
2 changed files with 262 additions and 0 deletions
|
|
@ -34,6 +34,9 @@ pub const IDUNN_RUDP_HEALTH_INGRESS_SCHEMA: &str = "idunn.rudp_health_ingress.v1
|
|||
pub const MUNINN_TELEMETRY_SURFACE_SCHEMA: &str = "muninn.telemetry_surface.v1";
|
||||
pub const MUNINN_CAPTURE_STREAM_SCHEMA: &str = "muninn.capture_stream.v1";
|
||||
pub const MUNINN_CAPTURE_STREAM_COMMAND_SCHEMA: &str = "muninn.capture_stream_command.v1";
|
||||
pub const MUNINN_MEDIA_VIDEO_ACCESS_UNIT_SCHEMA: &str = "muninn.media_video_access_unit.v1";
|
||||
pub const MUNINN_MEDIA_AUDIO_PACKET_SCHEMA: &str = "muninn.media_audio_packet.v1";
|
||||
pub const MUNINN_MEDIA_RECEIVER_FEEDBACK_SCHEMA: &str = "muninn.media_receiver_feedback.v1";
|
||||
pub const MUNINN_OBS_STREAM_CATALOG_SCHEMA: &str = "muninn.obs_stream_catalog.v1";
|
||||
pub const MUNINN_MOVE_MARKER_CANDIDATE_SCHEMA: &str = "muninn.move_marker_candidate.v1";
|
||||
pub const MUNINN_MOVE_CONTROLLER_STATE_SCHEMA: &str = "muninn.move_controller_state.v1";
|
||||
|
|
@ -744,6 +747,98 @@ pub struct MuninnCaptureStreamCommandRecord {
|
|||
pub updated_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "muninn.media_video_access_unit",
|
||||
schema = "muninn.media_video_access_unit.v1"
|
||||
)]
|
||||
pub struct MuninnMediaVideoAccessUnitRecord {
|
||||
#[cultcache(key = 0)]
|
||||
pub stream_id: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub session_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub frame_id: u64,
|
||||
#[cultcache(key = 3)]
|
||||
pub codec: String,
|
||||
#[cultcache(key = 4)]
|
||||
pub pts_ticks: i64,
|
||||
#[cultcache(key = 5)]
|
||||
pub duration_ticks: u32,
|
||||
#[cultcache(key = 6)]
|
||||
pub timebase_num: u32,
|
||||
#[cultcache(key = 7)]
|
||||
pub timebase_den: u32,
|
||||
#[cultcache(key = 8)]
|
||||
pub keyframe: bool,
|
||||
#[cultcache(key = 9)]
|
||||
pub dependency_frame_id: Option<u64>,
|
||||
#[cultcache(key = 10)]
|
||||
pub deadline_ticks: i64,
|
||||
#[cultcache(key = 11)]
|
||||
pub chunk_index: u16,
|
||||
#[cultcache(key = 12)]
|
||||
pub chunk_count: u16,
|
||||
#[cultcache(key = 13)]
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "muninn.media_audio_packet",
|
||||
schema = "muninn.media_audio_packet.v1"
|
||||
)]
|
||||
pub struct MuninnMediaAudioPacketRecord {
|
||||
#[cultcache(key = 0)]
|
||||
pub stream_id: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub session_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub packet_id: u64,
|
||||
#[cultcache(key = 3)]
|
||||
pub codec: String,
|
||||
#[cultcache(key = 4)]
|
||||
pub pts_ticks: i64,
|
||||
#[cultcache(key = 5)]
|
||||
pub duration_ticks: u32,
|
||||
#[cultcache(key = 6)]
|
||||
pub timebase_num: u32,
|
||||
#[cultcache(key = 7)]
|
||||
pub timebase_den: u32,
|
||||
#[cultcache(key = 8)]
|
||||
pub deadline_ticks: i64,
|
||||
#[cultcache(key = 9)]
|
||||
pub payload: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "muninn.media_receiver_feedback",
|
||||
schema = "muninn.media_receiver_feedback.v1"
|
||||
)]
|
||||
pub struct MuninnMediaReceiverFeedbackRecord {
|
||||
#[cultcache(key = 0)]
|
||||
pub stream_id: String,
|
||||
#[cultcache(key = 1)]
|
||||
pub session_id: String,
|
||||
#[cultcache(key = 2)]
|
||||
pub receiver_id: String,
|
||||
#[cultcache(key = 3)]
|
||||
pub highest_decodable_frame_id: Option<u64>,
|
||||
#[cultcache(key = 4)]
|
||||
pub missing_frame_ids: Vec<u64>,
|
||||
#[cultcache(key = 5)]
|
||||
pub late_frame_ids: Vec<u64>,
|
||||
#[cultcache(key = 6)]
|
||||
pub requested_keyframe: bool,
|
||||
#[cultcache(key = 7)]
|
||||
pub jitter_us: i64,
|
||||
#[cultcache(key = 8)]
|
||||
pub decode_queue_us: i64,
|
||||
#[cultcache(key = 9)]
|
||||
pub observed_at: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(
|
||||
type = "muninn.telemetry_surface",
|
||||
|
|
@ -1004,6 +1099,9 @@ cultmesh_rs::cultmesh_documents!(OdinDocuments {
|
|||
MuninnTelemetrySurfaceRecord => MUNINN_TELEMETRY_SURFACE_SCHEMA,
|
||||
MuninnCaptureStreamRecord => MUNINN_CAPTURE_STREAM_SCHEMA,
|
||||
MuninnCaptureStreamCommandRecord => MUNINN_CAPTURE_STREAM_COMMAND_SCHEMA,
|
||||
MuninnMediaVideoAccessUnitRecord => MUNINN_MEDIA_VIDEO_ACCESS_UNIT_SCHEMA,
|
||||
MuninnMediaAudioPacketRecord => MUNINN_MEDIA_AUDIO_PACKET_SCHEMA,
|
||||
MuninnMediaReceiverFeedbackRecord => MUNINN_MEDIA_RECEIVER_FEEDBACK_SCHEMA,
|
||||
MuninnObsStreamCatalogRecord => MUNINN_OBS_STREAM_CATALOG_SCHEMA,
|
||||
MuninnMoveMarkerCandidateRecord => MUNINN_MOVE_MARKER_CANDIDATE_SCHEMA,
|
||||
MuninnMoveControllerStateRecord => MUNINN_MOVE_CONTROLLER_STATE_SCHEMA,
|
||||
|
|
@ -1023,3 +1121,118 @@ pub struct OdinRecords {
|
|||
pub observation_streams: Vec<OdinObservationStreamRecord>,
|
||||
pub translation_routes: Vec<OdinTranslationRouteRecord>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use anyhow::Result;
|
||||
use cultmesh_rs::{CultMesh, CultMeshNodeOptions};
|
||||
|
||||
#[test]
|
||||
fn muninn_media_documents_round_trip_through_cultmesh() -> Result<()> {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let store_path = temp.path().join("muninn-media.cc");
|
||||
let mut node = CultMesh::create_node(
|
||||
&store_path,
|
||||
OdinDocuments,
|
||||
CultMeshNodeOptions {
|
||||
runtime_id: "muninn-media-test".to_string(),
|
||||
pull_on_start: true,
|
||||
},
|
||||
)?;
|
||||
|
||||
let video = MuninnMediaVideoAccessUnitRecord {
|
||||
stream_id: "muninn.raven.av.rudp".to_string(),
|
||||
session_id: "session-1".to_string(),
|
||||
frame_id: 42,
|
||||
codec: "h264".to_string(),
|
||||
pts_ticks: 126_000,
|
||||
duration_ticks: 3_000,
|
||||
timebase_num: 1,
|
||||
timebase_den: 90_000,
|
||||
keyframe: true,
|
||||
dependency_frame_id: None,
|
||||
deadline_ticks: 127_800,
|
||||
chunk_index: 0,
|
||||
chunk_count: 1,
|
||||
payload: vec![0, 0, 0, 1, 0x65],
|
||||
};
|
||||
let audio = MuninnMediaAudioPacketRecord {
|
||||
stream_id: video.stream_id.clone(),
|
||||
session_id: video.session_id.clone(),
|
||||
packet_id: 7,
|
||||
codec: "opus".to_string(),
|
||||
pts_ticks: 67_200,
|
||||
duration_ticks: 960,
|
||||
timebase_num: 1,
|
||||
timebase_den: 48_000,
|
||||
deadline_ticks: 68_160,
|
||||
payload: vec![0xf8, 0xff, 0xfe],
|
||||
};
|
||||
let feedback = MuninnMediaReceiverFeedbackRecord {
|
||||
stream_id: video.stream_id.clone(),
|
||||
session_id: video.session_id.clone(),
|
||||
receiver_id: "starfire.obs".to_string(),
|
||||
highest_decodable_frame_id: Some(41),
|
||||
missing_frame_ids: vec![42],
|
||||
late_frame_ids: vec![40],
|
||||
requested_keyframe: true,
|
||||
jitter_us: 750,
|
||||
decode_queue_us: 2_000,
|
||||
observed_at: "2026-06-18T00:00:00Z".to_string(),
|
||||
};
|
||||
|
||||
node.put("video:42:0", &video)?;
|
||||
node.put("audio:7", &audio)?;
|
||||
node.put("feedback:starfire.obs", &feedback)?;
|
||||
|
||||
assert_eq!(
|
||||
node.documents()
|
||||
.binding("muninn.media_video_access_unit")
|
||||
.and_then(|binding| binding.payload_schema_version.clone())
|
||||
.as_deref(),
|
||||
Some(MUNINN_MEDIA_VIDEO_ACCESS_UNIT_SCHEMA)
|
||||
);
|
||||
assert_eq!(
|
||||
node.documents()
|
||||
.binding("muninn.media_audio_packet")
|
||||
.and_then(|binding| binding.payload_schema_version.clone())
|
||||
.as_deref(),
|
||||
Some(MUNINN_MEDIA_AUDIO_PACKET_SCHEMA)
|
||||
);
|
||||
assert_eq!(
|
||||
node.documents()
|
||||
.binding("muninn.media_receiver_feedback")
|
||||
.and_then(|binding| binding.payload_schema_version.clone())
|
||||
.as_deref(),
|
||||
Some(MUNINN_MEDIA_RECEIVER_FEEDBACK_SCHEMA)
|
||||
);
|
||||
|
||||
let reloaded = CultMesh::create_node(
|
||||
&store_path,
|
||||
OdinDocuments,
|
||||
CultMeshNodeOptions {
|
||||
runtime_id: "muninn-media-test-reloaded".to_string(),
|
||||
pull_on_start: true,
|
||||
},
|
||||
)?;
|
||||
assert_eq!(
|
||||
reloaded
|
||||
.get_required::<MuninnMediaVideoAccessUnitRecord>("video:42:0")?
|
||||
.payload,
|
||||
vec![0, 0, 0, 1, 0x65]
|
||||
);
|
||||
assert_eq!(
|
||||
reloaded
|
||||
.get_required::<MuninnMediaAudioPacketRecord>("audio:7")?
|
||||
.codec,
|
||||
"opus"
|
||||
);
|
||||
assert!(
|
||||
reloaded
|
||||
.get_required::<MuninnMediaReceiverFeedbackRecord>("feedback:starfire.obs")?
|
||||
.requested_keyframe
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ function defineOdinDocuments(defineDocumentType) {
|
|||
muninnCaptureStreamCommandDefinition: null,
|
||||
muninnCaptureStreamDefinition: null,
|
||||
muninnCommandBoundaryDefinition: null,
|
||||
muninnMediaAudioPacketDefinition: null,
|
||||
muninnMediaReceiverFeedbackDefinition: null,
|
||||
muninnMediaVideoAccessUnitDefinition: null,
|
||||
muninnMoveControllerStateDefinition: null,
|
||||
muninnMoveIdentityDefinition: null,
|
||||
muninnMoveLightCommandDefinition: null,
|
||||
|
|
@ -106,6 +109,49 @@ function defineOdinDocuments(defineDocumentType) {
|
|||
name: (value) => value?.boundary_id || value?.boundaryId || value?.daemon_id || value?.daemonId || "muninn",
|
||||
schema: parseObjectDocument("Muninn command boundary"),
|
||||
});
|
||||
const muninnMediaVideoAccessUnitDefinition = defineDocumentType({
|
||||
type: "muninn.media_video_access_unit",
|
||||
schemaName: "muninn.media_video_access_unit",
|
||||
schemaId: "muninn.media_video_access_unit.v1",
|
||||
schemaVersion: "muninn.media_video_access_unit.v1",
|
||||
global: false,
|
||||
name: (value) => {
|
||||
const streamId = value?.stream_id || value?.streamId || "stream";
|
||||
const sessionId = value?.session_id || value?.sessionId || "session";
|
||||
const frameId = value?.frame_id ?? value?.frameId ?? "frame";
|
||||
const chunkIndex = value?.chunk_index ?? value?.chunkIndex ?? "chunk";
|
||||
return `${streamId}:${sessionId}:video:${frameId}:${chunkIndex}`;
|
||||
},
|
||||
schema: parseObjectDocument("Muninn media video access unit"),
|
||||
});
|
||||
const muninnMediaAudioPacketDefinition = defineDocumentType({
|
||||
type: "muninn.media_audio_packet",
|
||||
schemaName: "muninn.media_audio_packet",
|
||||
schemaId: "muninn.media_audio_packet.v1",
|
||||
schemaVersion: "muninn.media_audio_packet.v1",
|
||||
global: false,
|
||||
name: (value) => {
|
||||
const streamId = value?.stream_id || value?.streamId || "stream";
|
||||
const sessionId = value?.session_id || value?.sessionId || "session";
|
||||
const packetId = value?.packet_id ?? value?.packetId ?? "packet";
|
||||
return `${streamId}:${sessionId}:audio:${packetId}`;
|
||||
},
|
||||
schema: parseObjectDocument("Muninn media audio packet"),
|
||||
});
|
||||
const muninnMediaReceiverFeedbackDefinition = defineDocumentType({
|
||||
type: "muninn.media_receiver_feedback",
|
||||
schemaName: "muninn.media_receiver_feedback",
|
||||
schemaId: "muninn.media_receiver_feedback.v1",
|
||||
schemaVersion: "muninn.media_receiver_feedback.v1",
|
||||
global: false,
|
||||
name: (value) => {
|
||||
const streamId = value?.stream_id || value?.streamId || "stream";
|
||||
const sessionId = value?.session_id || value?.sessionId || "session";
|
||||
const receiverId = value?.receiver_id || value?.receiverId || "receiver";
|
||||
return `${streamId}:${sessionId}:feedback:${receiverId}`;
|
||||
},
|
||||
schema: parseObjectDocument("Muninn media receiver feedback"),
|
||||
});
|
||||
const muninnMoveControllerStateDefinition = defineDocumentType({
|
||||
type: "muninn.move_controller_state",
|
||||
schemaName: "muninn.move_controller_state",
|
||||
|
|
@ -300,6 +346,9 @@ function defineOdinDocuments(defineDocumentType) {
|
|||
muninnCaptureStreamCommandDefinition,
|
||||
muninnCaptureStreamDefinition,
|
||||
muninnCommandBoundaryDefinition,
|
||||
muninnMediaAudioPacketDefinition,
|
||||
muninnMediaReceiverFeedbackDefinition,
|
||||
muninnMediaVideoAccessUnitDefinition,
|
||||
muninnMoveControllerStateDefinition,
|
||||
muninnMoveIdentityDefinition,
|
||||
muninnMoveLightCommandDefinition,
|
||||
|
|
|
|||
Loading…
Reference in a new issue