Skip to main content

gap/
payload.rs

1//! GAP audio payload codec. Five CBOR keys.
2
3use gbp::CodecError;
4use serde::{Deserialize, Serialize};
5use serde_bytes::ByteBuf;
6
7/// Audio frame payload.
8#[derive(Clone, Debug, Serialize, Deserialize)]
9pub struct GapPayload {
10    /// Audio source identifier (microphone or device).
11    #[serde(rename = "msid")]
12    pub media_source_id: u32,
13    /// 16-bit `rtp_sequence` widened to `u32` for CBOR uint compatibility.
14    #[serde(rename = "seq")]
15    pub rtp_sequence: u32,
16    /// 48 kHz timestamp.
17    #[serde(rename = "ts")]
18    pub rtp_timestamp: u64,
19    /// Key phase (binds the payload to a specific MLS epoch).
20    #[serde(rename = "kp")]
21    pub key_phase: u32,
22    /// Opus frame bytes.
23    #[serde(rename = "opus")]
24    pub opus_frame: ByteBuf,
25}
26
27impl GapPayload {
28    /// Builds a 20 ms Opus frame at 48 kHz (960 samples).
29    pub fn opus_20ms(
30        media_source_id: u32,
31        rtp_sequence: u16,
32        key_phase: u32,
33        opus: Vec<u8>,
34    ) -> Self {
35        Self {
36            media_source_id,
37            rtp_sequence: rtp_sequence as u32,
38            rtp_timestamp: 960,
39            key_phase,
40            opus_frame: ByteBuf::from(opus),
41        }
42    }
43
44    /// Builds an Opus frame with an explicit `rtp_timestamp`.
45    /// Prefer [`GapPayload::opus_20ms`] for the common 48 kHz / 20 ms case.
46    pub fn with_timestamp(
47        media_source_id: u32,
48        rtp_sequence: u16,
49        rtp_timestamp: u64,
50        key_phase: u32,
51        opus: Vec<u8>,
52    ) -> Self {
53        Self {
54            media_source_id,
55            rtp_sequence: rtp_sequence as u32,
56            rtp_timestamp,
57            key_phase,
58            opus_frame: ByteBuf::from(opus),
59        }
60    }
61
62    /// CBOR-encodes the payload.
63    pub fn to_cbor(&self) -> Vec<u8> {
64        let mut buf = Vec::new();
65        ciborium::into_writer(self, &mut buf).expect("cbor encode");
66        buf
67    }
68
69    /// Decodes a CBOR-encoded payload.
70    pub fn from_cbor(data: &[u8]) -> Result<Self, CodecError> {
71        ciborium::from_reader(data).map_err(|e| CodecError::Decode(e.to_string()))
72    }
73}