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(media_source_id: u32, rtp_sequence: u16, key_phase: u32, opus: Vec<u8>) -> Self {
30        Self {
31            media_source_id,
32            rtp_sequence: rtp_sequence as u32,
33            rtp_timestamp: 960,
34            key_phase,
35            opus_frame: ByteBuf::from(opus),
36        }
37    }
38
39    /// Builds an Opus frame with an explicit `rtp_timestamp`.
40    /// Prefer [`GapPayload::opus_20ms`] for the common 48 kHz / 20 ms case.
41    pub fn with_timestamp(
42        media_source_id: u32,
43        rtp_sequence: u16,
44        rtp_timestamp: u64,
45        key_phase: u32,
46        opus: Vec<u8>,
47    ) -> Self {
48        Self {
49            media_source_id,
50            rtp_sequence: rtp_sequence as u32,
51            rtp_timestamp,
52            key_phase,
53            opus_frame: ByteBuf::from(opus),
54        }
55    }
56
57    /// CBOR-encodes the payload.
58    pub fn to_cbor(&self) -> Vec<u8> {
59        let mut buf = Vec::new();
60        ciborium::into_writer(self, &mut buf).expect("cbor encode");
61        buf
62    }
63
64    /// Decodes a CBOR-encoded payload.
65    pub fn from_cbor(data: &[u8]) -> Result<Self, CodecError> {
66        ciborium::from_reader(data).map_err(|e| CodecError::Decode(e.to_string()))
67    }
68}