From 4f9d62bb8f6e1aca1566ab704bc80027dfe5a864 Mon Sep 17 00:00:00 2001 From: Yannick de Jong Date: Fri, 23 Oct 2026 15:09:39 +0200 Subject: [PATCH 1/6] Mark the Teams speaker from the newest of its history Read the speaker news the meeting sends to several at once, take the front of the list, and never mark someone muted. --- src/backend/teams_call.rs | 17 +++++++++-- src/teams/calling/call.rs | 47 +++++++++++++++++------------- src/teams/calling/channel.rs | 55 +++++++++++++++++++++++++++++++----- 3 files changed, 91 insertions(+), 28 deletions(-) diff --git a/src/backend/teams_call.rs b/src/backend/teams_call.rs index 693003a..fa56e1f 100644 --- a/src/backend/teams_call.rs +++ b/src/backend/teams_call.rs @@ -413,7 +413,9 @@ fn roster(callee: &str, far: FarEnd) -> Roster { /// the interface knows people by, their names for any it does not), and /// you. The meeting mixes everyone's sound, so whoever it says spoke /// last (`speaker`, by MRI) is speaking while that sound is loud -/// (`far.speaking`); you as `far.me_speaking` says. +/// (`far.speaking`), unless muted since: the speaker is often only the +/// one from before we joined, and a muted one cannot be the sound; you +/// as `far.me_speaking` says. fn meeting_roster(people: &[Attendee], speaker: Option<&str>, far: FarEnd) -> Roster { let mut everyone: Vec = people .iter() @@ -425,6 +427,7 @@ fn meeting_roster(people: &[Attendee], speaker: Option<&str>, far: FarEnd) -> Ro me: false, muted: attendee.muted, speaking: far.speaking + && !attendee.muted && speaker.is_some_and(|s| s.eq_ignore_ascii_case(&attendee.mri)), name: attendee.name.clone(), waiting: attendee.waiting, @@ -998,7 +1001,7 @@ impl Shown<'_, T> { (self.tell)(Listen::Roster(self.roster())); } CallEvent::Speaker(mri) => { - self.speaker = Some(mri); + self.speaker = mri; (self.tell)(Listen::Roster(self.roster())); } CallEvent::Ended { result, .. } => self.ended = Some(result), @@ -1284,6 +1287,16 @@ mod tests { // No one named yet: no one. let roster = meeting_roster(&people, None, loud); assert!(roster.people.iter().all(|p| !p.speaking)); + // The last one named has muted since: someone else is the sound. + let people = [ + attendee("8:orgid:ana"), + Attendee { + muted: true, + ..attendee("8:orgid:bo") + }, + ]; + let roster = meeting_roster(&people, Some("8:orgid:bo"), loud); + assert!(roster.people.iter().all(|p| !p.speaking)); } #[test] diff --git a/src/teams/calling/call.rs b/src/teams/calling/call.rs index 2c45773..30f0f87 100644 --- a/src/teams/calling/call.rs +++ b/src/teams/calling/call.rs @@ -121,10 +121,10 @@ pub enum CallEvent { /// Whose camera the meeting is asked to send us on each camera line /// (by MRI), or no one's. Watching(Vec>), - /// Who in the meeting spoke last (by MRI), as its data channel says: - /// the meeting mixes everyone's sound, so this is who the bar marks - /// as speaking while it is loud. - Speaker(String), + /// Who in the meeting spoke last (by MRI), as its data channel says, + /// or no one we know: the meeting mixes everyone's sound, so this is + /// who the bar marks as speaking while it is loud. + Speaker(Option), /// An incoming call stopped ringing because another device of yours /// (or another delivery of the same call here) picked it up: not a /// missed call. @@ -565,8 +565,12 @@ struct InMeeting { /// Whose camera was last asked for on each camera line, by MRI and /// source id. watching: Vec>, - /// Who spoke last, by MRI, as the meeting says. + /// Who spoke last, by MRI, as the meeting says: whose camera is + /// kept in view. speaker: Option, + /// Who the bar was last told speaks: the newest speaker, or no one + /// when the meeting names a source not in the roster. + marked: Option, /// Whose screen share was last asked for, by MRI and source id; the /// outer `None` while none was ever asked for. watching_share: Option>, @@ -1598,8 +1602,11 @@ impl Call { } /// Who spoke, as a speaker history (`dsh`) from `way` says: the - /// newest last (seen: the first marked whoever spoke long ago); - /// ourselves (not in the list) aside. + /// newest first (recorded: each new speaker moves to the front, and + /// the meeting sends it again whenever one does). Only the newest + /// counts: one not in the roster (a phone, say) is marked as no one, + /// not as whoever spoke before, who may have muted since. The video + /// asked for follows the last speaker known. fn spoke( &mut self, history: &[i64], @@ -1610,23 +1617,25 @@ impl Call { let Some(meeting) = &mut self.meeting else { return; }; - let speaker = history.iter().rev().find_map(|&source| { - meeting - .people - .list() - .into_iter() + let people = meeting.people.list(); + let speaker = history.first().and_then(|&source| { + people + .iter() .find(|a| a.audio == Some(source)) - .map(|a| a.mri) + .map(|a| a.mri.clone()) }); log::debug!( - "Teams meeting: spoke, by audio source ({way}): {history:?}; marking {}", - speaker.as_deref().unwrap_or("no one") + "Teams meeting: spoke, by audio source ({way}): {history:?}; marking {} ({} of {} in the roster with an audio source)", + speaker.as_deref().unwrap_or("no one"), + people.iter().filter(|a| a.audio.is_some()).count(), + people.len() ); + if speaker != meeting.marked { + meeting.marked.clone_from(&speaker); + tell(CallEvent::Speaker(speaker.clone())); + } if speaker.is_some() && speaker != meeting.speaker { - meeting.speaker = speaker.clone(); - if let Some(mri) = speaker { - tell(CallEvent::Speaker(mri)); - } + meeting.speaker = speaker; self.ask_for_video(session, tell); } } diff --git a/src/teams/calling/channel.rs b/src/teams/calling/channel.rs index 6ace61f..67045f9 100644 --- a/src/teams/calling/channel.rs +++ b/src/teams/calling/channel.rs @@ -23,9 +23,9 @@ /// The channel's label. pub const LABEL: &str = "main-channel"; -/// The header's first bytes. +/// The header's first bytes, with one recipient (see [`decode`]). const MAGIC: [u8; 3] = [0x10, 0x0f, 0x92]; -/// The header's length. +/// The header's length, with one recipient. const HEADER: usize = 16; /// The meeting's media server's id. pub const SERVER: i32 = -4; @@ -116,9 +116,18 @@ pub fn encode(seq: u16, from: i32, to: i32, messages: &[serde_json::Value]) -> V out } -/// Reads one message; `None` for one not in this shape. +/// Reads one message; `None` for one not in this shape. The header's +/// second byte is its length less one, and the byte after the sender +/// counts the recipients, four bytes each: the server sends some news +/// (the speaker history, recorded) to several at once, in a longer +/// header. `to` is the first. pub fn decode(bytes: &[u8]) -> Option { - if bytes.len() < HEADER || bytes[..3] != MAGIC { + let header = usize::from(*bytes.get(1)?) + 1; + let recipients = usize::from(*bytes.get(10)?); + if bytes[0] != MAGIC[0] || bytes[2] != MAGIC[2] || recipients == 0 { + return None; + } + if header != 11 + 4 * recipients + 1 || bytes.len() < header { return None; } let id = |at: usize| -> Option { @@ -126,7 +135,7 @@ pub fn decode(bytes: &[u8]) -> Option { }; let seq = u16::from_be_bytes([bytes[3], bytes[4]]); let (from, to) = (id(6)?, id(11)?); - let messages = match serde_json::from_slice(&bytes[HEADER..]).ok()? { + let messages = match serde_json::from_slice(&bytes[header..]).ok()? { serde_json::Value::Array(messages) => messages, one => vec![one], }; @@ -145,7 +154,8 @@ pub enum Heard { Ready, /// A source request of ours was answered. SourceAnswered { sequence: u64, ok: bool }, - /// Who spoke, by their audio's source id: a history, the newest last. + /// Who spoke, by their audio's source id: a history, the newest first + /// (recorded: whoever starts talking moves to the front). Speakers(Vec), } @@ -179,7 +189,14 @@ impl Channel { /// Takes in what the server sent. pub fn heard(&mut self, bytes: &[u8]) -> Vec { let Some(frame) = decode(bytes) else { - log::debug!("meeting channel: a message in an unknown shape"); + if log::log_enabled!(log::Level::Debug) { + let start: String = bytes.iter().take(48).map(|b| format!("{b:02x}")).collect(); + log::debug!( + "meeting channel: a message in an unknown shape, {} bytes: {start}…; as text: {}", + bytes.len(), + String::from_utf8_lossy(&bytes[..bytes.len().min(300)]) + ); + } return Vec::new(); }; let mut heard = Vec::new(); @@ -348,6 +365,30 @@ mod tests { assert!(channel.heard(b"not a frame").is_empty()); } + #[test] + fn news_for_several_recipients_reads() { + // The server's speaker history to three of us at once: a 24-byte + // header naming three recipients (recorded shape). + let mut bytes = vec![ + 0x10, 0x17, 0x92, 0x2c, 0x1b, 0x00, 0xff, 0xff, 0xff, 0xfc, 0x03, + ]; + bytes.extend_from_slice(&[ + 0x00, 0x98, 0x9b, 0x8e, 0x00, 0x98, 0x9e, 0xda, 0x00, 0x98, 0x9f, 0xab, 0x01, + ]); + bytes.extend_from_slice(br#"[{"history":[4035,10001704,3183],"type":"dsh"}]"#); + let frame = decode(&bytes).expect("reads"); + assert_eq!(frame.from, SERVER); + assert_eq!(frame.to, 0x0098_9b8e); + let mut channel = Channel::default(); + assert_eq!( + channel.heard(&bytes), + [Heard::Speakers(vec![4035, 10_001_704, 3183])] + ); + // A length that does not match the recipients is not read. + bytes[1] = 0x0f; + assert!(decode(&bytes).is_none()); + } + #[test] fn the_servers_sequence_numbers_past_255_read() { // As recorded on a server whose count had run on: 0x086e. From 7b6b5f7a568293b913883617e22bdff6417d3a13 Mon Sep 17 00:00:00 2001 From: Yannick de Jong Date: Fri, 23 Oct 2026 15:09:45 +0200 Subject: [PATCH 2/6] Join Teams meetings muted As a huddle starts: the microphone stays closed and the meeting is told, until you unmute. --- src/backend/teams_call.rs | 23 ++++++++++++++++------- src/huddles/listen.rs | 4 ++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/backend/teams_call.rs b/src/backend/teams_call.rs index fa56e1f..df3f6b5 100644 --- a/src/backend/teams_call.rs +++ b/src/backend/teams_call.rs @@ -145,14 +145,23 @@ struct Wanted { /// A call's controls: for the caller to keep, and for the call's task /// (hang-up and mute for the call, and what its devices should do). +/// A call starts unmuted; a meeting (`muted`) muted, as a huddle does, +/// so joining one is never heard before you choose to speak. fn controls( chosen: &crate::devices::Chosen, + muted: bool, ) -> (Running, mpsc::UnboundedReceiver, Wanted) { let (control, controls) = mpsc::unbounded_channel(); let (devices, chosen) = watch::channel(chosen.clone()); - let (muted, microphone) = watch::channel(true); - // Unmuted from the start: the microphone task opens it on this. - let _ = muted.send(false); + let (mute, microphone) = watch::channel(true); + if muted { + // The call mutes its sound and tells the meeting as soon as it + // takes its controls, as when you mute. + let _ = control.send(Control::Mute(true)); + } else { + // Unmuted from the start: the microphone task opens it on this. + let _ = mute.send(false); + } // The camera starts off. #[cfg(feature = "huddle-camera")] let (camera_sender, camera) = watch::channel(false); @@ -165,7 +174,7 @@ fn controls( devices, call_id: None, control, - muted, + muted: mute, #[cfg(feature = "huddle-camera")] camera: camera_sender, #[cfg(feature = "huddle-share")] @@ -194,7 +203,7 @@ impl Caller { sink: Sink, ) { self.stop(); - let (mut running, controls, wanted) = controls(&self.chosen); + let (mut running, controls, wanted) = controls(&self.chosen, false); running.team.clone_from(&team); tokio::spawn(run( client, @@ -226,7 +235,7 @@ impl Caller { if let Some(old) = self.ringing.take() { let _ = old.decide.send(Decision::Decline); } - let (mut running, controls, wanted) = controls(&self.chosen); + let (mut running, controls, wanted) = controls(&self.chosen, false); running.team.clone_from(&team); let (decide, decisions) = oneshot::channel(); let call_id = call.call_id.clone(); @@ -242,7 +251,7 @@ impl Caller { /// ending the last call first. pub fn join_meeting(&mut self, client: TeamsClient, team: String, join: Join, sink: Sink) { self.stop(); - let (mut running, controls, wanted) = controls(&self.chosen); + let (mut running, controls, wanted) = controls(&self.chosen, true); running.team.clone_from(&team); tokio::spawn(run_meeting(client, team, join, controls, wanted, sink)); self.running = Some(running); diff --git a/src/huddles/listen.rs b/src/huddles/listen.rs index e0fa25c..3326afe 100644 --- a/src/huddles/listen.rs +++ b/src/huddles/listen.rs @@ -211,10 +211,10 @@ impl Listening { } } - /// Joining a Teams meeting in `team`: unmuted, as a call is. + /// Joining a Teams meeting in `team`: muted, as a huddle is (the + /// worker starts it so too). pub fn meeting(team: &str) -> Self { Self { - mic: crate::huddle_mic::Mic::Opening, meeting: true, ..Self::new(team, crate::meetings::MEETING_CHANNEL) } From d303978ee702110062cb5964f67b1735cf4f1361 Mon Sep 17 00:00:00 2001 From: Yannick de Jong Date: Fri, 23 Oct 2026 15:09:52 +0200 Subject: [PATCH 3/6] Tell Teams how its video reaches us Turn on transport-cc feedback, offer it on our video lines and answer it at the server's number, so the meeting stops sending us video at a crawl. --- src/teams/calling/media.rs | 13 +++++ src/teams/calling/mod.rs | 3 + src/teams/calling/sdp.rs | 110 ++++++++++++++++++++++++++++++++++--- src/teams/calling/video.rs | 9 ++- 4 files changed, 124 insertions(+), 11 deletions(-) diff --git a/src/teams/calling/media.rs b/src/teams/calling/media.rs index 972a1a6..1bbe41c 100644 --- a/src/teams/calling/media.rs +++ b/src/teams/calling/media.rs @@ -47,6 +47,7 @@ use str0m::crypto::Fingerprint; use str0m::format::{Codec, FormatParams}; use str0m::media::{Frequency, MediaKind, Mid, Pt}; use str0m::net::Protocol; +use str0m::rtp::Extension; use str0m::{ Candidate as IceCandidate, CandidateKind as IceKind, Event as RtcEvent, IceConnectionState, IceCreds, Rtc, RtcConfig, @@ -951,6 +952,11 @@ fn new_rtc(opus_pt: u8, video: &[(u8, Option)], controlling: bool, now: Inst if !video.is_empty() { config = config.enable_bwe(Some(str0m::bwe::Bitrate::kbps(BWE_START_KBPS))); } + // A meeting's media server, offering after the lobby, numbers + // transport-cc 5 where we number it 3 (recorded); str0m's map is set + // here, before its SDP comes, so the packets it sends us are read at + // either. Ours still go out at 3, the first. + config = config.set_extension(5, Extension::TransportSequenceNumber); config.codec_config().add_config( Pt::from(opus_pt), None, @@ -989,6 +995,11 @@ fn new_rtc(opus_pt: u8, video: &[(u8, Option)], controlling: bool, now: Inst } let mut rtc = config.build(now); rtc.direct_api().set_ice_controlling(controlling); + // What arrives is reported back (transport-cc feedback), as the + // offer's `rtcp-fb:* transport-cc` promises: without it the media + // server cannot tell what our link takes and sends us video slowly. + // str0m's direct API leaves it off. + rtc.direct_api().enable_twcc_feedback(); rtc } @@ -2301,6 +2312,7 @@ mod tests { }, ], video_streams: Vec::new(), + transport_cc: None, } } @@ -2590,6 +2602,7 @@ mod tests { share_video: None, lines: vec![audio_line(Direction::SendRecv)], video_streams: Vec::new(), + transport_cc: None, } } diff --git a/src/teams/calling/mod.rs b/src/teams/calling/mod.rs index 792f995..770806e 100644 --- a/src/teams/calling/mod.rs +++ b/src/teams/calling/mod.rs @@ -147,6 +147,9 @@ pub struct RemoteMedia { /// mid (`a=x-source-streamid`): where a source request asks for /// someone's video to come. pub video_streams: Vec<(String, u32)>, + /// The id the far end gives transport-cc (`a=extmap`), if it names + /// it: an answer to its offer must use its number. + pub transport_cc: Option, } impl RemoteMedia { diff --git a/src/teams/calling/sdp.rs b/src/teams/calling/sdp.rs index 65a248d..3b49a4a 100644 --- a/src/teams/calling/sdp.rs +++ b/src/teams/calling/sdp.rs @@ -42,6 +42,11 @@ pub enum SdpError { const ABS_SEND_TIME: &str = r"http:\\www.webrtc.org\experiments\rtp-hdrext\abs-send-time"; const TRANSPORT_CC: &str = r"http:\\www.ietf.org\id\draft-holmer-rmcat-transport-wide-cc-extensions-01"; +/// The header extensions our offer gives a video line, at str0m's own +/// numbers, as the web client gives every video line: transport-cc is +/// what the media server stamps the video it sends us with, and our +/// feedback on it is how it learns what our link can take. +const VIDEO_EXTENSIONS: [(u8, &str); 2] = [(2, ABS_SEND_TIME), (3, TRANSPORT_CC)]; /// One m-line as written, before it means anything. struct RawLine<'a> { @@ -193,6 +198,11 @@ pub fn read(sdp: &str) -> Result { .iter() .find(|l| l.media == "video" && l.port != 0 && l.attr("label") == Some(SHARE_LABEL)) .and_then(h264_of); + let transport_cc = raw + .iter() + .flat_map(|l| &l.attrs) + .filter(|(n, _)| *n == "extmap") + .find_map(|(_, v)| transport_cc_of(v)); let session_direction = direction_in(&session); let lines = raw @@ -230,9 +240,21 @@ pub fn read(sdp: &str) -> Result { share_video, lines, video_streams, + transport_cc, }) } +/// The id of an `a=extmap` value (`5 http:\\…` or `5/sendrecv …`) if it +/// names transport-cc, in Microsoft's backslashes or the usual slashes. +fn transport_cc_of(value: &str) -> Option { + let (id, uri) = value.split_once(char::is_whitespace)?; + let slashed = |uri: &str| uri.trim().replace('\\', "/"); + if slashed(uri) != slashed(TRANSPORT_CC) { + return None; + } + id.split('/').next()?.parse().ok() +} + /// The first H.264 at packetization mode 1 a video line lists, and its /// retransmission's payload type. fn h264_of(line: &RawLine<'_>) -> Option { @@ -505,7 +527,14 @@ pub fn offer(local: &LocalMedia) -> String { direction: local.video_direction, share: false, }; - video_line(&mut out, local, &line, setup_text(local.setup), false); + video_line( + &mut out, + local, + &line, + &VIDEO_EXTENSIONS, + setup_text(local.setup), + false, + ); continue; } // The share line carries our screen while we share; a meeting's @@ -529,7 +558,14 @@ pub fn offer(local: &LocalMedia) -> String { }, share: true, }; - video_line(&mut out, local, &line, setup_text(local.setup), false); + video_line( + &mut out, + local, + &line, + &VIDEO_EXTENSIONS, + setup_text(local.setup), + false, + ); continue; } push(&mut out, &format!("m=video {port} RTP/SAVP {h264} {rtx}")); @@ -567,7 +603,14 @@ pub fn offer(local: &LocalMedia) -> String { rtx: local.video_rtx, }; let line = more_camera(camera, codec); - video_line(&mut out, local, &line, setup_text(local.setup), false); + video_line( + &mut out, + local, + &line, + &VIDEO_EXTENSIONS, + setup_text(local.setup), + false, + ); } if let Some(ssrc) = local.data_ssrc { data_line(&mut out, local, "3", ssrc, setup_text(local.setup), false); @@ -601,9 +644,9 @@ fn more_camera(camera: &MoreCamera, codec: VideoCodec) -> VideoLine<'_> { /// while `local.data_ssrc` is set, which is kept as the web client keeps /// it. The bundle groups the lines we keep. /// -/// No header extensions are answered: an answer must use the offerer's -/// extension ids, which [`RemoteMedia`] does not carry, and audio works -/// without them. +/// Of the header extensions only transport-cc is answered, on the video +/// lines, at the offer's number: the media server's estimate of what +/// our link can take rests on our feedback on it. Audio works without. pub fn answer(local: &LocalMedia, remote: &RemoteMedia) -> String { lines_for(local, remote, Role::Answer) } @@ -652,6 +695,11 @@ fn lines_for(local: &LocalMedia, remote: &RemoteMedia, role: Role) -> String { // The share line's H.264: its own, or, re-enabling a line that was // dropped, the camera's (one codec, one number in a bundle). let share_codec = remote.share_video.or(remote.video); + let extensions: Vec<(u8, &str)> = remote + .transport_cc + .map(|id| (id, TRANSPORT_CC)) + .into_iter() + .collect(); let sends = |d: Direction| matches!(d, Direction::SendRecv | Direction::SendOnly); // What we want of the share line, and what it comes to. let share_direction = |theirs: Direction| match role { @@ -759,7 +807,7 @@ fn lines_for(local: &LocalMedia, remote: &RemoteMedia, role: Role) -> String { direction: share_direction(line.direction), share: true, }; - video_line(&mut out, local, &line_spec, setup, first); + video_line(&mut out, local, &line_spec, &extensions, setup, first); } } (LineKind::Video, _, _) if local.receive_cameras.iter().any(|c| c.mid == line.mid) => { @@ -771,7 +819,7 @@ fn lines_for(local: &LocalMedia, remote: &RemoteMedia, role: Role) -> String { }; let mut line_spec = more_camera(camera, codec); line_spec.direction = direction(Direction::SendRecv); - video_line(&mut out, local, &line_spec, setup, first); + video_line(&mut out, local, &line_spec, &extensions, setup, first); } } (LineKind::Video, _, _) => { @@ -789,7 +837,7 @@ fn lines_for(local: &LocalMedia, remote: &RemoteMedia, role: Role) -> String { direction: direction(local.video_direction), share: false, }; - video_line(&mut out, local, &line_spec, setup, first); + video_line(&mut out, local, &line_spec, &extensions, setup, first); } } // `accepted` keeps nothing else. @@ -893,6 +941,7 @@ fn video_line( out: &mut String, local: &LocalMedia, line: &VideoLine<'_>, + extensions: &[(u8, &str)], setup: &str, candidates: bool, ) { @@ -935,6 +984,9 @@ fn video_line( push(out, "a=rtcp-fb:* nack"); } push(out, "a=rtcp-fb:* nack pli"); + for (id, uri) in extensions { + push(out, &format!("a=extmap:{id} {uri}")); + } // Our resends' stream, paired with the line's, as the web client // writes it. if let (true, Some(_), Some(rtx_ssrc)) = (sending, rtx, rtx_ssrc) { @@ -1894,6 +1946,46 @@ mod tests { ); } + #[test] + fn video_lines_carry_transport_cc_at_the_number_agreed() { + // Our offer: at str0m's 3 on every video line, as the web client. + let local = LocalMedia { + video_ssrc: Some(77), + receive_cameras: vec![more("4", 401)], + ..local() + }; + let sdp = offer(&local); + let stamped = sdp + .split("m=") + .filter(|l| l.starts_with("video") && l.contains("a=sendrecv")) + .filter(|l| l.contains(&format!("a=extmap:3 {TRANSPORT_CC}\r\n"))) + .count(); + assert_eq!(stamped, 2, "{sdp}"); + assert_eq!(read(&sdp).expect("reads").transport_cc, Some(3)); + assert_eq!(read(MEETING_LOBBY).expect("reads").transport_cc, Some(3)); + // The media server's own offer numbers it 5: the answer keeps 5. + let remote = read(MEETING_RETARGET).expect("reads"); + assert_eq!(remote.transport_cc, Some(5)); + let ours = LocalMedia { + opus_pt: 102, + receive_cameras: vec![more("5", 501)], + ..local + }; + let answered = answer(&ours, &remote); + assert!( + answered.contains(&format!("a=extmap:5 {TRANSPORT_CC}\r\n")), + "{answered}" + ); + assert!(!answered.contains("a=extmap:3"), "{answered}"); + assert_eq!( + transport_cc_of( + "7/sendrecv http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" + ), + Some(7) + ); + assert_eq!(transport_cc_of(&format!("2 {ABS_SEND_TIME}")), None); + } + #[test] fn a_meetings_share_line_is_open_both_ways_from_the_start() { let local = LocalMedia { diff --git a/src/teams/calling/video.rs b/src/teams/calling/video.rs index e08b0b4..7c47cb5 100644 --- a/src/teams/calling/video.rs +++ b/src/teams/calling/video.rs @@ -610,9 +610,14 @@ impl CallVideo { self.pictures_in += 1; if self.pictures_in == 1 { log::info!( - "video: {}: the far end's first picture, {} bytes", + "video: {}: the far end's first picture, {} bytes; transport-cc {}", self.name(), - data.data.len() + data.data.len(), + if data.ext_vals.transport_cc.is_some() { + "stamped, so fed back" + } else { + "not stamped: the server learns nothing of our link" + } ); } self.last_picture = Some(now); From 441e21c39e1d9e5dffc2857a2fff0952f6df7161 Mon Sep 17 00:00:00 2001 From: Yannick de Jong Date: Fri, 23 Oct 2026 15:10:12 +0200 Subject: [PATCH 4/6] Play calls with opus-rs, through an adaptive jitter buffer and FEC The buffer grows after a stall and keeps the burst, conceals at most 60 ms, and plays a lost frame from the next one's copy; our microphone sends those copies too. --- Cargo.lock | 13 +- Cargo.toml | 21 +- TODO.md | 139 +- deny.toml | 6 +- src/huddle_audio.rs | 1 + src/huddle_audio/capture.rs | 159 + src/huddle_audio/encoder.rs | 70 +- src/huddle_audio/jitter.rs | 290 +- src/huddle_audio/media.rs | 6 +- src/huddle_audio/speaker.rs | 178 +- vendor/opus-decoder/Cargo.toml | 12 - vendor/opus-decoder/README.md | 229 -- vendor/opus-decoder/VENDORED.md | 22 - vendor/opus-decoder/src/celt/bands.rs | 2214 ----------- vendor/opus-decoder/src/celt/cwrs.rs | 120 - vendor/opus-decoder/src/celt/kiss_fft.rs | 191 - vendor/opus-decoder/src/celt/laplace.rs | 53 - vendor/opus-decoder/src/celt/mdct.rs | 528 --- vendor/opus-decoder/src/celt/mod.rs | 3484 ----------------- vendor/opus-decoder/src/celt/modes.rs | 238 -- vendor/opus-decoder/src/celt/quant_bands.rs | 286 -- vendor/opus-decoder/src/celt/rate.rs | 459 --- vendor/opus-decoder/src/celt/vq.rs | 179 - vendor/opus-decoder/src/compare.rs | 353 -- vendor/opus-decoder/src/entropy.rs | 411 -- vendor/opus-decoder/src/error.rs | 30 - vendor/opus-decoder/src/lib.rs | 884 ----- vendor/opus-decoder/src/multistream.rs | 452 --- vendor/opus-decoder/src/packet.rs | 434 -- vendor/opus-decoder/src/silk/decode_core.rs | 497 --- .../opus-decoder/src/silk/entropy_tables.rs | 289 -- vendor/opus-decoder/src/silk/gain.rs | 114 - vendor/opus-decoder/src/silk/lbrr.rs | 400 -- vendor/opus-decoder/src/silk/lpc.rs | 335 -- vendor/opus-decoder/src/silk/ltp.rs | 106 - vendor/opus-decoder/src/silk/mod.rs | 1349 ------- vendor/opus-decoder/src/silk/nlsf.rs | 182 - vendor/opus-decoder/src/silk/pitch.rs | 43 - vendor/opus-decoder/src/silk/plc.rs | 261 -- vendor/opus-decoder/src/silk/resampler.rs | 157 - .../src/silk/resampler_private.rs | 174 - vendor/opus-decoder/src/silk/resampler_rom.rs | 26 - vendor/opus-decoder/src/silk/stereo.rs | 176 - vendor/opus-decoder/src/silk/tables.rs | 380 -- 44 files changed, 633 insertions(+), 15318 deletions(-) create mode 100644 src/huddle_audio/capture.rs delete mode 100644 vendor/opus-decoder/Cargo.toml delete mode 100644 vendor/opus-decoder/README.md delete mode 100644 vendor/opus-decoder/VENDORED.md delete mode 100644 vendor/opus-decoder/src/celt/bands.rs delete mode 100644 vendor/opus-decoder/src/celt/cwrs.rs delete mode 100644 vendor/opus-decoder/src/celt/kiss_fft.rs delete mode 100644 vendor/opus-decoder/src/celt/laplace.rs delete mode 100644 vendor/opus-decoder/src/celt/mdct.rs delete mode 100644 vendor/opus-decoder/src/celt/mod.rs delete mode 100644 vendor/opus-decoder/src/celt/modes.rs delete mode 100644 vendor/opus-decoder/src/celt/quant_bands.rs delete mode 100644 vendor/opus-decoder/src/celt/rate.rs delete mode 100644 vendor/opus-decoder/src/celt/vq.rs delete mode 100644 vendor/opus-decoder/src/compare.rs delete mode 100644 vendor/opus-decoder/src/entropy.rs delete mode 100644 vendor/opus-decoder/src/error.rs delete mode 100644 vendor/opus-decoder/src/lib.rs delete mode 100644 vendor/opus-decoder/src/multistream.rs delete mode 100644 vendor/opus-decoder/src/packet.rs delete mode 100644 vendor/opus-decoder/src/silk/decode_core.rs delete mode 100644 vendor/opus-decoder/src/silk/entropy_tables.rs delete mode 100644 vendor/opus-decoder/src/silk/gain.rs delete mode 100644 vendor/opus-decoder/src/silk/lbrr.rs delete mode 100644 vendor/opus-decoder/src/silk/lpc.rs delete mode 100644 vendor/opus-decoder/src/silk/ltp.rs delete mode 100644 vendor/opus-decoder/src/silk/mod.rs delete mode 100644 vendor/opus-decoder/src/silk/nlsf.rs delete mode 100644 vendor/opus-decoder/src/silk/pitch.rs delete mode 100644 vendor/opus-decoder/src/silk/plc.rs delete mode 100644 vendor/opus-decoder/src/silk/resampler.rs delete mode 100644 vendor/opus-decoder/src/silk/resampler_private.rs delete mode 100644 vendor/opus-decoder/src/silk/resampler_rom.rs delete mode 100644 vendor/opus-decoder/src/silk/stereo.rs delete mode 100644 vendor/opus-decoder/src/silk/tables.rs diff --git a/Cargo.lock b/Cargo.lock index 4678e2d..11646d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3776,7 +3776,6 @@ dependencies = [ "notify-rust", "open", "openssl", - "opus-decoder", "opus-rs", "percent-encoding", "prost", @@ -4595,18 +4594,10 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "opus-decoder" -version = "0.1.1" -dependencies = [ - "thiserror 2.0.21", -] - [[package]] name = "opus-rs" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1880f411a21320dcb546c8f38f05ffb75643362ee0235437487999ed543e26ae" +version = "0.1.39" +source = "git+https://github.com/Yannicked/opus-rs?rev=58da80134013b7452e68b886b1fb028327ed2298#58da80134013b7452e68b886b1fb028327ed2298" [[package]] name = "orbclient" diff --git a/Cargo.toml b/Cargo.toml index 9ae2c32..2812fd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,8 +127,6 @@ str0m = { version = "0.24.1", default-features = false, features = ["openssl"] } # The certificate Chime's media server shows, read for the log only # (str0m-openssl's own OpenSSL, so no crate is added). openssl = "0.10.78" -# Opus in pure Rust, without unsafe code or a C library to find. -opus-decoder = "0.1.1" # The TURN client's long-term credential: HMAC-SHA1 (sha1 is above) keyed # with MD5. hmac = "0.13" @@ -141,14 +139,15 @@ rustls-native-certs = "0.8" tracing = { version = "0.1.44", default-features = false, features = ["log", "std"] } # Talking in a huddle: the microphone through cpal (rodio's), WebRTC's -# audio processing and an Opus encoder, all pure Rust. -# opus-rs: libopus 1.6 ported (BSD-3-Clause, no dependencies); its unsafe -# is SIMD and unchecked indexing. sonora: WebRTC's AEC3, noise +# audio processing and Opus, all pure Rust. +# opus-rs: libopus 1.6 ported (BSD-3-Clause, no dependencies), encoding +# what we send and decoding what we hear (0.1.39 plays a mono packet in a +# stereo decoder); its unsafe is SIMD and unchecked indexing. sonora: WebRTC's AEC3, noise # suppression, AGC2 and high-pass filter, M145, ported (BSD-3-Clause); # its unsafe is SIMD behind runtime CPU detection. sonora-agc2's RNN # voice detector decides what DTX leaves out; sonora-common-audio's sinc # resampler brings a 44.1 kHz microphone to 48. Both come with sonora. -opus-rs = "0.1.34" +opus-rs = "0.1.39" sonora = "0.2.0" sonora-agc2 = "0.2.0" sonora-common-audio = "0.2.0" @@ -369,11 +368,11 @@ opt-level = 2 debug = false [patch.crates-io] -# opus-decoder 0.1.1 with two fixes (vendor/opus-decoder/VENDORED.md): a -# hybrid packet naming more redundancy bytes than it holds panicked (in -# release too, which aborts the app), and frames with 16 short blocks -# overflowed a shift in debug builds. Drop once upstream has both. -opus-decoder = { path = "vendor/opus-decoder" } +# opus-rs 0.1.39 with in-band FEC decoding and libopus's packet-loss +# concealment (restsend/opus-rs PRs #67 and #68), and two encoder fixes: +# the AVX2 pulse search wrote past a silent band, and compute_theta wrote +# through a shared slice. The fork's `noslacking` branch; drop once released. +opus-rs = { git = "https://github.com/Yannicked/opus-rs", rev = "58da80134013b7452e68b886b1fb028327ed2298" } # crmne/egui apps-0.36, shared with ZapFast and Spotifast: egui 0.36.1 with # bidi shaping, egui's busy-loop fix and Wayland frame pacing. Every egui # crate comes from the same revision so they share one emath and epaint. diff --git a/TODO.md b/TODO.md index 3e46255..d6a6313 100644 --- a/TODO.md +++ b/TODO.md @@ -808,7 +808,9 @@ engineering, as for the rest of the session sign-in. decided it. - Opus: **`opus-decoder`** (pure Rust, `forbid(unsafe_code)`, no C, MIT/Apache, passes the 12 RFC 8251 vectors by its own account, - but young: 0.1, March 2026). libopus through the `opus` crate + but young: 0.1, March 2026). Since 2026-10-23 playback is `opus-rs` + instead, which also encodes: one dependency, and its SILK + concealment measured close to libopus where opus-decoder's was not. libopus through the `opus` crate builds its bundled C with CMake on Windows and macOS (or needs the system's library on Linux, which the Flatpak runtime has); it is the fallback if the decoder sounds wrong. It has not. @@ -865,19 +867,10 @@ engineering, as for the rest of the session sign-in. 440 Hz tone instead of the microphone, logging what was sent and Chime's RTCP receiver reports every 5 s. The loopback test has the pretend Chime decode our Opus and hear the mute. - - In-band FEC is off, an upstream `opus-rs` bug to report: with - 0.1.34, `OpusEncoder::new(48000, 1, Application::Voip)`, - `use_inband_fec = true` and `packet_loss_perc = 10`, one second of - a 0.3 sine at 440 Hz decodes to 3.9 times the energy put in (5.2 - times for a 120 Hz harmonic "vowel"; 2.7 to 3.3 with CBR; 1.8 to - 4.6 across 16–40 kbit/s), frame levels swinging from -1 to -50 - dBFS where -14 went in. `opus-decoder` and `opus-rs`'s own - decoder agree sample for sample, so the encoder's LBRR is at - fault. With FEC off: 0.95 to 0.98, as it should be; white noise - is fine either way. Turn it on when fixed; - `encoded_speech_decodes_back_with_both_decoders` shows it. - Playing stays on `opus-decoder`: `opus-rs`'s decoder conceals a - loss but cannot decode FEC. + - In-band FEC is on since `opus-rs` 0.1.36 fixed its LBRR (0.1.34 + decoded voiced sound at up to 5.2 times the energy put in); + `encoded_speech_decodes_back` guards it, and the speaker plays the + far end's copies (`a_lost_frame_plays_from_the_copy_in_the_next`). - Cost: `opus-rs` (no dependencies; its unsafe is SIMD and unchecked indexing), `sonora` and its six crates (unsafe only in SIMD behind runtime CPU detection), `derive_more`; all @@ -935,119 +928,11 @@ engineering, as for the rest of the session sign-in. devices instead. - [ ] Cameras of one model: `/dev/v4l/by-id/` ids from the helper would tell them apart across replugging. -- [ ] **File upstream: opus-decoder's collapse mask overflows.** A real - huddle stopped playing in a debug build: opus-decoder panicked on - the sound device's thread. Fixed in our patched copy - (`vendor/opus-decoder`, see its VENDORED.md; still unfixed on - Rusopus main `ecb22cf` as of 2026-10-07); drop the copy once a - fixed release is out. To file at - : - - > **`extract_collapse_mask` overflows its `u8` with 16 short blocks - > (attempt to shift left with overflow, celt/vq.rs:118)** - > - > opus-decoder 0.1.1, `src/celt/vq.rs`: - > - > ```rust - > fn extract_collapse_mask(iy: &[i32], n: usize, b: usize) -> u8 { - > ... - > let mut mask = 0u8; - > for i in 0..b { - > ... - > if nonzero != 0 { - > mask |= 1 << i; // line 118 - > ``` - > - > `b` can be 16: a transient 20 ms CELT frame has 8 short blocks, - > and a negative `tf_change` doubles them once more (the - > `time_divide` step before `quant_partition_mono`, undone later by - > `post_tf_collapse_mask`). With overflow checks on - > (any debug build) `1 << i` panics for `i >= 8`: - > - > ``` - > thread 'cpal_alsa_out' panicked at - > opus-decoder-0.1.1/src/celt/vq.rs:118:21: - > attempt to shift left with overflow - > ``` - > - > libopus keeps this mask in an `unsigned` (`celt/vq.c`, - > `static unsigned extract_collapse_mask(int *iy, int N, int B)`, - > and `alg_unquant` returns `unsigned`); the bits above 8 are folded - > down by `quant_band`'s `cm |= cm >> B` after the time-divide - > Haar steps, and only the final mask is stored as `unsigned char`. - > This crate's `post_tf_collapse_mask` already works on a `u32`, so - > the fix is to make `AlgUnquantResult::collapse_mask` and - > `extract_collapse_mask` `u32` (the `as u32` at bands.rs:1301 then - > goes). - > - > Reproduce: decode this 55-byte packet with - > `OpusDecoder::new(48_000, 2)` and `decode_float(.., false)` in a - > debug build: - > - > ``` - > f8 75 d5 48 6a 8c cf b8 7d b1 d2 3b 43 7d 6b 6b 17 c1 14 fe 7d - > a5 ae 93 56 58 c4 69 d1 30 da 3f 75 ab 8e ab 1c 2c 0e f2 e7 e0 - > 6b f5 08 84 7d 51 67 f9 16 30 90 1d 29 - > ``` - > - > About 1.4% of random 20 ms CELT packets hit it. In release builds - > the shift wraps to bit `i % 8`; after the fold that gives the same - > final mask, so the output matched a `u32` build bit for bit over - > 20,000 random packets. The panic is the only harm, but it takes - > down the audio thread of whatever is decoding. -- [ ] **File upstream: a malformed hybrid packet panics opus-decoder in - release builds too.** Fixed in our patched copy - (`vendor/opus-decoder`, libopus's `len*8 < ec_tell` check; still - unfixed on Rusopus main `ecb22cf`). Found by - fuzzing while fixing the above: about 1 in 80,000 random packets. - Our release builds abort on a panic, so any huddle participant (or - Chime) sending such a packet closes NoSlacking for everyone - listening; SRTP rules out corruption on the way, not a hostile - sender. Debug builds catch it now (`audio::guard`). Until upstream - fixes it we carry the copy. To file: - - > **Hybrid redundancy longer than the frame: `range start index - > out of range` (lib.rs:676 and :724)** - > - > In `OpusMode::Hybrid`, `redundancy_bytes = ec.dec_uint(256) + 2` - > is read from the packet and then used as - > `&frame[frame.len() - redundancy_bytes..]` without checking it - > against `frame.len()`. A frame shorter than the redundancy it - > claims underflows the subtraction and panics ("range start index - > 18446744073709551534 out of range for slice of length 76" in a - > release build). - > - > libopus (`src/opus_decoder.c`, `opus_decode_frame`) checks - > right after reading it: - > - > ```c - > len -= redundancy_bytes; - > /* This is a sanity check. It should never happen for a valid - > packet, so the exact behaviour is not normative. */ - > if (len*8 < ec_tell(&dec)) - > { - > len = 0; - > redundancy_bytes = 0; - > redundancy = 0; - > } - > ``` - > - > Reproduce with a fresh `OpusDecoder::new(48_000, 1)` and - > `decode_float(.., false)`, release or debug, on this 153-byte - > packet (TOC 0x69, two 76-byte hybrid frames): - > - > ``` - > 69 af 0e e6 85 96 57 1b b5 8c c1 a2 21 35 a5 94 3d 0d 19 d1 c8 e7 - > 7c d0 63 03 bd c4 31 df 4e 76 64 f8 27 93 e6 c1 2e 44 06 6c 2e a8 - > df a2 5d 8f b0 e1 a8 8f ce 1f d7 8a 47 af 68 f8 71 37 f5 9e 65 a3 - > 2a 18 28 26 82 e1 88 a7 b8 27 ad 60 fa 63 9d 18 42 b4 b7 92 e3 60 - > 34 5e 40 7e 7c ee 8b 98 8f 1c de 63 ad 44 ce 75 0b 2f 15 f7 be 4f - > 2d a3 9e 57 bb a8 dd bc f9 0a 3a 14 c2 73 f7 34 98 bb 54 28 c4 db - > fe 4d 7f 97 0a 58 92 0b 47 59 37 15 85 30 e0 b1 78 d8 9f 26 72 - > ``` - > - > A decoder fed by the network should turn this into an error (or - > libopus's behaviour), never a panic. +- [ ] **opus-rs from crates.io again.** Playing uses our fork + (Yannicked/opus-rs, pinned in `[patch.crates-io]`) for in-band FEC + decoding (restsend/opus-rs#67) and libopus's concealment (#68). Drop + the patch and the fork's line in deny.toml once a release has both. + The Rusopus PRs (#5–#7) stay open for opus-decoder's sake. ## Research notes diff --git a/deny.toml b/deny.toml index db33158..e88cbfe 100644 --- a/deny.toml +++ b/deny.toml @@ -69,9 +69,11 @@ allow-wildcard-paths = true unknown-registry = "deny" unknown-git = "deny" allow-registry = ["https://github.com/rust-lang/crates.io-index"] -# The shared fastframe crates and the egui and winit forks they build on -# (see [patch.crates-io] in Cargo.toml). Nothing else may come from git. +# The shared fastframe crates and the egui and winit forks they build on, +# and opus-rs with FEC and concealment until a release has them (see +# [patch.crates-io] in Cargo.toml). Nothing else may come from git. allow-git = [ + "https://github.com/Yannicked/opus-rs", "https://github.com/crmne/fastframe", "https://github.com/crmne/egui", "https://github.com/crmne/winit", diff --git a/src/huddle_audio.rs b/src/huddle_audio.rs index 7854174..e911d16 100644 --- a/src/huddle_audio.rs +++ b/src/huddle_audio.rs @@ -51,6 +51,7 @@ pub mod bitstream; #[cfg(feature = "huddle-camera")] pub mod camera_send; pub mod cameras; +pub mod capture; pub mod chime; #[cfg(feature = "huddle-video")] pub mod decode; diff --git a/src/huddle_audio/capture.rs b/src/huddle_audio/capture.rs new file mode 100644 index 0000000..cf54aea --- /dev/null +++ b/src/huddle_audio/capture.rs @@ -0,0 +1,159 @@ +//! A capture of what a call's speaker is given and plays, for finding +//! faults in the sound offline: opt in by naming a directory in +//! `NOSLACKING_AUDIO_CAPTURE`. Off otherwise, and never on by itself, +//! since it holds what was said. +//! +//! Three files go in the directory, written on a thread of their own so +//! the sound device's thread never waits on the disk: +//! +//! - `packets.bin`: each Opus packet as it came, as microseconds since +//! the capture began (u64), its RTP timestamp (u32) and its length +//! (u16), little-endian, then its bytes; +//! - `pulls.txt`: what the jitter buffer gave for each pull, by the +//! samples per channel played before it: `frame `, `lost` or +//! `silence`; +//! - `played.f32`: the sound as handed to the device, 48 kHz stereo, +//! interleaved little-endian `f32`. + +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::path::{Path, PathBuf}; +use std::sync::mpsc; +use std::time::Instant; + +use super::jitter::Pull; + +/// The variable naming the directory to capture into. +pub const VARIABLE: &str = "NOSLACKING_AUDIO_CAPTURE"; + +/// One thing to write. +enum Item { + Packet { + at: Instant, + timestamp: u32, + payload: Vec, + }, + Pull(String), + Played(Vec), +} + +/// The capture's end the speaker writes to. +#[derive(Debug)] +pub struct Capture { + items: mpsc::Sender, +} + +impl Capture { + /// A capture into the directory `NOSLACKING_AUDIO_CAPTURE` names, if + /// it names one that can be written to. + pub fn from_env() -> Option { + let dir = PathBuf::from(std::env::var_os(VARIABLE)?); + match Self::start(&dir) { + Ok(capture) => { + log::info!( + "huddle audio: capturing the far end's sound in {}", + dir.display() + ); + Some(capture) + } + Err(error) => { + log::warn!("huddle audio: no capture in {}: {error}", dir.display()); + None + } + } + } + + fn start(dir: &Path) -> std::io::Result { + std::fs::create_dir_all(dir)?; + let open = |name: &str| File::create(dir.join(name)).map(BufWriter::new); + let mut files = Files { + packets: open("packets.bin")?, + pulls: open("pulls.txt")?, + played: open("played.f32")?, + start: Instant::now(), + samples: 0, + }; + let (items, received) = mpsc::channel(); + std::thread::Builder::new() + .name("audio capture".into()) + .spawn(move || { + for item in received { + if let Err(error) = files.write(item) { + log::warn!("huddle audio: the capture stopped: {error}"); + return; + } + } + let _ = files.flush(); + })?; + Ok(Self { items }) + } + + /// A packet as it came. + pub fn packet(&self, timestamp: u32, payload: &[u8]) { + let _ = self.items.send(Item::Packet { + at: Instant::now(), + timestamp, + payload: payload.to_vec(), + }); + } + + /// What the jitter buffer gave. + pub fn pull(&self, pull: &Pull) { + let text = match pull { + Pull::Frame(payload) => format!("frame {}", payload.len()), + Pull::Lost => "lost".to_owned(), + Pull::Recover(next) => format!("recover {}", next.len()), + Pull::Silence => "silence".to_owned(), + }; + let _ = self.items.send(Item::Pull(text)); + } + + /// Sound as handed to the device, interleaved stereo. + pub fn played(&self, samples: &[f32]) { + let _ = self.items.send(Item::Played(samples.to_vec())); + } +} + +/// The files, on the capture's thread. +struct Files { + packets: BufWriter, + pulls: BufWriter, + played: BufWriter, + start: Instant, + /// Samples per channel played so far. + samples: u64, +} + +impl Files { + fn write(&mut self, item: Item) -> std::io::Result<()> { + match item { + Item::Packet { + at, + timestamp, + payload, + } => { + let micros = + u64::try_from(at.duration_since(self.start).as_micros()).unwrap_or(u64::MAX); + let length = u16::try_from(payload.len()).unwrap_or(u16::MAX); + self.packets.write_all(µs.to_le_bytes())?; + self.packets.write_all(×tamp.to_le_bytes())?; + self.packets.write_all(&length.to_le_bytes())?; + self.packets.write_all(&payload[..usize::from(length)])?; + } + Item::Pull(text) => writeln!(self.pulls, "{} {text}", self.samples)?, + Item::Played(samples) => { + self.samples += (samples.len() / 2) as u64; + for sample in samples { + self.played.write_all(&sample.to_le_bytes())?; + } + } + } + Ok(()) + } + + fn flush(&mut self) -> std::io::Result<()> { + self.packets.flush()?; + self.pulls.flush()?; + self.played.flush() + } +} diff --git a/src/huddle_audio/encoder.rs b/src/huddle_audio/encoder.rs index d4971ac..94a77ae 100644 --- a/src/huddle_audio/encoder.rs +++ b/src/huddle_audio/encoder.rs @@ -9,17 +9,15 @@ //! loss. `opus-rs` has no DTX of its own; [`super::uplink::Dtx`] leaves //! quiet frames out instead. //! -//! In-band FEC is off, where a browser has it on: with it, `opus-rs` -//! 0.1.34's VOIP packets at 48 kHz decode wrong whenever the sound is -//! voiced (a tone, a vowel), up to five times the energy put in, the same -//! in `opus-decoder` and in `opus-rs`'s own decoder, so the encoder's -//! low-bitrate copies are at fault. Noise came through; speech would not. -//! `encoded_speech_decodes_back_with_both_decoders` would catch it coming -//! back. Losses are concealed by the far end's decoder instead. +//! In-band FEC is on, as a browser has it: each packet carries a +//! low-bitrate copy of the one before, which the far end's decoder (libopus +//! in Chime and Teams) plays when that one is lost. `opus-rs` before 0.1.36 +//! got the copies wrong, so voiced sound decoded at up to five times the +//! energy put in; `encoded_speech_decodes_back` would catch that coming +//! back. //! -//! Playing stays with `opus-decoder` ([`super::speaker`]): `opus-rs`'s -//! decoder conceals a lost frame, but it cannot decode the FEC copy a -//! packet carries of the one before, which `opus-decoder` can. +//! `opus-rs` decodes what we hear too ([`super::speaker`]), and plays the +//! far end's FEC copies the same way. use super::jitter::CLOCK; use super::uplink::TWENTY_MS; @@ -51,14 +49,14 @@ impl std::fmt::Debug for OpusRs { } impl OpusRs { - /// The speech encoder: mono, VOIP, [`BITRATE`], no FEC (see above). + /// The speech encoder: mono, VOIP, [`BITRATE`], in-band FEC (see above). pub fn speech() -> Result { let rate = i32::try_from(CLOCK).map_err(|e| e.to_string())?; let mut inner = opus_rs::OpusEncoder::new(rate, 1, opus_rs::Application::Voip) .map_err(|e| format!("no Opus encoder: {e}"))?; inner.bitrate_bps = BITRATE; inner.use_cbr = false; - inner.use_inband_fec = false; + inner.use_inband_fec = true; inner.packet_loss_perc = EXPECTED_LOSS; Ok(Self { inner, @@ -116,6 +114,25 @@ mod tests { (pcm, packets) } + #[test] + fn packets_carry_a_copy_of_the_one_before() { + let (_, with) = encoded(440.0); + let mut encoder = OpusRs::speech().expect("an encoder"); + encoder.inner.use_inband_fec = false; + let mut tone = Tone::new(440.0, 0.3); + let mut without = 0; + for _ in 0..50 { + let mut frame = vec![0.0; TWENTY_MS]; + tone.fill(&mut frame); + without += encoder.encode(&frame).expect("encoded").len(); + } + let with: usize = with.iter().map(Vec::len).sum(); + assert!( + with > without + 50 * 3, + "{with} bytes with FEC, {without} without" + ); + } + #[test] fn packets_are_twenty_ms_and_near_the_bitrate() { let (_, packets) = encoded(440.0); @@ -132,32 +149,23 @@ mod tests { ); } - /// What `opus-rs` encodes, the decoder that plays huddles - /// (`opus-decoder`) and `opus-rs`'s own both decode to the tone. + /// What `opus-rs` encodes decodes back to the tone. #[test] - fn encoded_speech_decodes_back_with_both_decoders() { + fn encoded_speech_decodes_back() { let (pcm, packets) = encoded(440.0); - - let mut ours = opus_decoder::OpusDecoder::new(CLOCK, 1).expect("a decoder"); - let mut theirs = opus_rs::OpusDecoder::new(48_000, 1).expect("a decoder"); - let mut by_ours = Vec::new(); - let mut by_theirs = Vec::new(); + let mut decoder = opus_rs::OpusDecoder::new(48_000, 1).expect("a decoder"); + let mut decoded = Vec::new(); for packet in &packets { - let mut out = vec![0.0; ours.max_frame_size_per_channel()]; - let n = ours.decode_float(packet, &mut out, false).expect("decoded"); - by_ours.extend_from_slice(&out[..n]); let mut out = vec![0.0; TWENTY_MS]; - let n = theirs.decode(packet, TWENTY_MS, &mut out).expect("decoded"); - by_theirs.extend_from_slice(&out[..n]); + let n = decoder + .decode(packet, TWENTY_MS, &mut out) + .expect("decoded"); + decoded.extend_from_slice(&out[..n]); } - assert_eq!(by_ours.len(), pcm.len()); - assert_eq!(by_theirs.len(), pcm.len()); + assert_eq!(decoded.len(), pcm.len()); // Past the first 100 ms, where the encoder settles; the codec // delays by a few milliseconds. let settled = &pcm[4800..pcm.len() - 960]; - assert!(residual(settled, &by_ours[4800..], 960) < 0.1); - assert!(residual(settled, &by_theirs[4800..], 960) < 0.1); - // Both decoders agree with each other too. - assert!(residual(&by_ours[4800..40_000], &by_theirs[4800..], 64) < 0.05); + assert!(residual(settled, &decoded[4800..], 960) < 0.1); } } diff --git a/src/huddle_audio/jitter.rs b/src/huddle_audio/jitter.rs index 239ee9d..8fe402e 100644 --- a/src/huddle_audio/jitter.rs +++ b/src/huddle_audio/jitter.rs @@ -3,13 +3,28 @@ //! //! It holds back a little (60 ms by default) before it starts, so packets //! that come late or out of order still play in their place. A frame that -//! never came is played as a loss, which the decoder conceals; when the -//! buffer runs dry it conceals a little more ([`DRY_CONCEALED`]), which -//! bridges a short stall and otherwise fades the sound out, then waits to -//! fill again, playing silence; when it grows too long (the device's clock -//! slower than the sender's, or a burst after a stall) the oldest frames -//! go, so the delay stays short, with one concealed frame across the cut. -//! Each of those would otherwise be a step in the sound: a pop. +//! never came is played as a loss, which the decoder conceals, unless the +//! frame after it is here: that one carries a low-bitrate copy of it +//! (in-band FEC), which the decoder plays instead ([`Pull::Recover`]). +//! It never conceals more than [`DRY_CONCEALED`] in a row, as a guess +//! stretched further turns to buzzing noise. A longer gap (the sender pausing, or a stall +//! that ran the buffer dry) goes silent after that, and the sound after +//! it fades in. +//! +//! How much it holds follows the network, as WebRTC's NetEQ does: it +//! times each frame against the sound device's clock, and a frame later +//! than the fastest raises the target by that much at once, forgotten +//! over a minute or so. A network that stalls now and then is given room +//! for the next stall; a calm one keeps the delay short. +//! +//! The burst that follows a stall is kept, not cut: everything said is +//! heard, a little late. The delay then comes back down in the pauses: +//! a gap between frames (the sender sends nothing while its speaker is +//! quiet) is shortened while more is held than the target, which nobody +//! misses, as no frame is passed over. Only when far too much is held ([`Jitter::new`]'s limit: the device's clock slower than +//! the sender's for long, or a stall of seconds) do the oldest frames go +//! at once, with one concealed frame across the cut. Each of those would +//! otherwise be a step in the sound: a pop. use std::collections::BTreeMap; @@ -20,6 +35,14 @@ pub const FRAME: u32 = CLOCK / 50; /// How many frames are concealed when the buffer runs dry before it goes /// silent: 60 ms, enough to bridge a short stall. pub const DRY_CONCEALED: u32 = 3; +/// The silence played per pull while the buffer fills: 10 ms. +pub const SILENCE: u32 = CLOCK / 100; +/// How fast a late frame is forgotten: one sample of headroom for every +/// this many played, so 280 ms of it lasts a little over a minute. +const FORGET: u64 = 256; +/// How far over the target the buffer may be before a pause is +/// shortened: two frames, so it does not chase the network's ripples. +const SLACK: u64 = 2 * FRAME as u64; /// How many 48 kHz samples one Opus packet holds, from its TOC byte and /// frame count (RFC 6716 §3.1). `None` for an empty or broken packet. @@ -49,6 +72,9 @@ pub enum Pull { Frame(Vec), /// The frame due was lost: conceal one frame's length. Lost, + /// The frame due was lost but the next one is here: play the copy of + /// the lost one it carries (in-band FEC), one frame's length. + Recover(Vec), /// Nothing to play yet: silence while the buffer fills. Silence, } @@ -64,10 +90,16 @@ pub struct Counts { pub late: u64, /// Frames thrown away to keep the delay short. pub trimmed: u64, + /// Frames' worth of pauses cut short to bring the delay back down. + pub skipped: u64, /// Lost frames concealed. pub concealed: u64, + /// Lost frames played from the next one's copy (in-band FEC). + pub recovered: u64, /// Times the buffer ran dry while playing. pub underruns: u64, + /// How much the buffer means to hold now, in milliseconds. + pub target_ms: u64, } /// The buffer. @@ -79,9 +111,19 @@ pub struct Jitter { last: Option<(u32, u64)>, /// The timestamp due next, while playing. next: Option, - /// How much to hold before playing, and the most to hold, in samples. - target: u64, + /// The least to hold before playing, and the most to hold, in + /// samples. + base: u64, limit: u64, + /// Held on top of `base`, in samples: how late frames came lately, + /// the latest at once, forgotten slowly. + headroom: u64, + /// Samples played (silence too): the sound device's clock. + now: u64, + /// The least delay seen, `now` less a frame's timestamp: a frame on + /// time. It creeps up a little every pull, so a sender's clock + /// slower than the device's is not taken for frames ever later. + fastest: Option, /// The length of the last frame, for concealing a lost one. frame: u64, /// Frames concealed since the buffer ran dry. @@ -95,21 +137,25 @@ pub struct Jitter { impl Default for Jitter { fn default() -> Self { - Self::new(60, 300) + Self::new(60, 1000) } } impl Jitter { - /// A buffer that holds `target_ms` before it plays and at most - /// `limit_ms`. + /// A buffer that holds at least `target_ms` before it plays, more + /// while the network is uneven (up to half of `limit_ms`), and at + /// most `limit_ms`. pub fn new(target_ms: u32, limit_ms: u32) -> Self { let per_ms = u64::from(CLOCK / 1000); Self { frames: BTreeMap::new(), last: None, next: None, - target: u64::from(target_ms) * per_ms, + base: u64::from(target_ms) * per_ms, limit: u64::from(limit_ms.max(target_ms)) * per_ms, + headroom: 0, + now: 0, + fastest: None, frame: u64::from(FRAME), dry: 0, bridge: false, @@ -120,7 +166,18 @@ impl Jitter { /// What happened to the frames so far. pub fn counts(&self) -> Counts { - self.counts + Counts { + target_ms: self.target() / u64::from(CLOCK / 1000), + ..self.counts + } + } + + /// How much to hold, in samples: the base and the headroom, the two + /// together no more than half the limit. + pub fn target(&self) -> u64 { + (self.base + self.headroom) + .min(self.limit / 2) + .max(self.base) } /// Whether the frame just pulled is the first after silence, so the @@ -164,6 +221,7 @@ impl Jitter { } let at = self.unwrap(timestamp); self.counts.pushed += 1; + self.time(at); if let Some(next) = self.next.filter(|next| at < *next) { // Run dry, the buffer conceals ahead of frames that are only // slow; one of those plays from its own time, a little later @@ -179,9 +237,10 @@ impl Jitter { } } self.frames.insert(at, payload); - // Too much held: drop the oldest until back at the target. + // Far too much held: drop the oldest until back at the target. if self.held() > self.limit { - while self.held() > self.target && self.frames.len() > 1 { + let target = self.target(); + while self.held() > target && self.frames.len() > 1 { self.frames.pop_first(); self.counts.trimmed += 1; } @@ -192,10 +251,43 @@ impl Jitter { } } + /// Times a frame due at `at` against the device's clock: later than + /// the fastest frame, it raises the headroom by that much. + fn time(&mut self, at: u64) { + let delay = i128::from(self.now) - i128::from(at); + match self.fastest { + Some(fastest) if delay >= fastest => { + let late = u64::try_from(delay - fastest).unwrap_or(u64::MAX); + if late > self.limit { + // The sender's clock went back: time from here. + self.fastest = Some(delay); + } else { + self.headroom = self.headroom.max(late); + } + } + _ => self.fastest = Some(delay), + } + } + /// What to play next. pub fn pull(&mut self) -> Pull { + let pull = self.choose(); + let played = match &pull { + Pull::Frame(payload) => u64::from(opus_samples(payload).unwrap_or(FRAME)), + Pull::Lost | Pull::Recover(_) => self.frame, + Pull::Silence => u64::from(SILENCE), + }; + self.now += played; + self.headroom = self.headroom.saturating_sub(played / FORGET); + if let Some(fastest) = &mut self.fastest { + *fastest += 1; + } + pull + } + + fn choose(&mut self) -> Pull { let Some(next) = self.next else { - if self.held() < self.target { + if self.held() < self.target() { return Pull::Silence; } let Some((at, payload)) = self.frames.pop_first() else { @@ -227,16 +319,46 @@ impl Jitter { }; return self.play(at, payload); } - if first - next > self.limit { + let gap = first - next; + if gap > self.limit { // A jump (the sender restarted its clock): carry on from there. let Some((at, payload)) = self.frames.pop_first() else { return Pull::Silence; }; return self.play(at, payload); } - self.next = Some(next + self.frame); - self.counts.concealed += 1; - Pull::Lost + // A pause, not a lost frame or so, with more ahead than wanted: + // cut it short, as far as the excess goes. + let over = (self.held() + gap).saturating_sub(self.target()); + if gap >= 2 * self.frame && over > SLACK { + let cut = gap.min(over); + self.next = Some(next + cut); + self.counts.skipped += cut / self.frame.max(1); + self.resumed = true; + return self.choose(); + } + // One frame lost, and the one after it here: it carries a copy. + // Not once gone silent: then the sender paused, and sent none. + if gap == self.frame + && self.dry < DRY_CONCEALED + && let Some(after) = self.frames.get(&first) + { + self.dry = 0; + self.next = Some(first); + self.counts.recovered += 1; + return Pull::Recover(after.clone()); + } + if self.dry < DRY_CONCEALED { + self.dry += 1; + self.next = Some(next + self.frame); + self.counts.concealed += 1; + return Pull::Lost; + } + // Concealed as long as it sounds right: silence to the next frame, + // which fades in. + self.next = Some((next + u64::from(SILENCE)).min(first)); + self.resumed = true; + Pull::Silence } fn play(&mut self, at: u64, payload: Vec) -> Pull { @@ -266,6 +388,98 @@ mod tests { } } + /// A 20 ms CELT frame `len` bytes long, named `n`. + fn sized(n: u8, len: usize) -> Vec { + let mut payload = frame(n); + payload.resize(len, 0); + payload + } + + #[test] + fn a_stall_raises_the_target_and_the_burst_after_it_is_kept() { + let mut jitter = Jitter::new(60, 1000); + // Frames every 20 ms, but 50 to 63 held up and then all at once + // with 64: a 280 ms stall, as Teams' relay does now and then. + let arrives = |n: u64| if (50..64).contains(&n) { 64 } else { n }; + let mut sent = 0u64; + let mut heard = Vec::new(); + while jitter.now < 200 * u64::from(FRAME) { + while arrives(sent) * u64::from(FRAME) <= jitter.now { + let n = u8::try_from(sent).expect("under 256 frames"); + jitter.push(u32::from(n) * FRAME, sized(n, 40)); + sent += 1; + } + heard.extend(played(jitter.pull())); + } + let counts = jitter.counts(); + assert_eq!((counts.trimmed, counts.late, counts.skipped), (0, 0, 0)); + assert!(heard.windows(2).all(|w| w[1] == w[0] + 1), "{heard:?}"); + assert!((40..=64).all(|n| heard.contains(&n)), "the stall heard"); + assert!(counts.target_ms >= 300, "{counts:?}"); + // So the next one like it is ridden out without a sound missing. + let underruns = counts.underruns; + let concealed = counts.concealed; + let arrives = |n: u64| if (150..164).contains(&n) { 164 } else { n }; + while jitter.now < 400 * u64::from(FRAME) { + while arrives(sent) * u64::from(FRAME) <= jitter.now { + let n = u8::try_from(sent % 256).expect("modulo 256"); + jitter.push((sent * u64::from(FRAME)) as u32, sized(n, 40)); + sent += 1; + } + jitter.pull(); + } + assert_eq!(jitter.counts().underruns, underruns); + assert_eq!(jitter.counts().concealed, concealed); + } + + #[test] + fn the_delay_comes_back_down_in_the_pauses() { + let mut jitter = Jitter::new(20, 1000); + jitter.push(0, frame(0)); + assert_eq!(played(jitter.pull()), Some(0)); + // 140 ms of speech come at once, on time: 120 more than the + // target. Speech is never cut. + for n in 1..8 { + jitter.push(u32::from(n) * FRAME, frame(n)); + } + for n in 1..4 { + assert_eq!(played(jitter.pull()), Some(n)); + } + // Then a pause of 200 ms before frame 18: it is cut short, so + // frame 18 follows 7 sooner than its time, and fades in. + jitter.push(18 * FRAME, frame(18)); + for n in 4..8 { + assert_eq!(played(jitter.pull()), Some(n)); + } + assert_eq!(played(jitter.pull()), Some(18)); + assert!(jitter.take_resumed()); + assert!(jitter.counts().skipped >= 2, "{:?}", jitter.counts()); + assert_eq!(jitter.counts().concealed, 0); + } + + #[test] + fn a_long_gap_is_concealed_a_little_then_silent() { + // A deep buffer: the sender pauses 300 ms, and what follows is + // already here, held as long as wanted, no longer. + let mut jitter = Jitter::new(300, 1000); + jitter.push(0, frame(0)); + jitter.push(16 * FRAME, frame(16)); + assert_eq!(played(jitter.pull()), Some(0)); + let mut pulls = Vec::new(); + loop { + let pull = jitter.pull(); + if let Some(n) = played(pull.clone()) { + assert_eq!(n, 16); + break; + } + pulls.push(pull); + } + let lost = pulls.iter().filter(|p| **p == Pull::Lost).count(); + assert_eq!(lost, DRY_CONCEALED as usize, "{pulls:?}"); + assert!(pulls.iter().skip(lost).all(|p| *p == Pull::Silence)); + assert!(jitter.take_resumed(), "the sound after fades in"); + } + #[test] fn opus_packets_say_how_long_they_are() { assert_eq!(opus_samples(&[0xF8, 0xFF, 0xFE]), Some(960), "20 ms CELT"); @@ -285,12 +499,15 @@ mod tests { let mut jitter = Jitter::new(60, 300); jitter.push(1000, frame(1)); assert_eq!(jitter.pull(), Pull::Silence, "20 ms held, 60 wanted"); - // Out of order: 3 before 2. + // Out of order: 3 before 2. That is 2 late by a frame, so it + // wants a frame more before it plays. jitter.push(1000 + 2 * FRAME, frame(3)); jitter.push(1000 + FRAME, frame(2)); - assert_eq!(played(jitter.pull()), Some(1)); - assert_eq!(played(jitter.pull()), Some(2)); - assert_eq!(played(jitter.pull()), Some(3)); + assert_eq!(jitter.pull(), Pull::Silence, "60 ms held, 80 wanted"); + jitter.push(1000 + 3 * FRAME, frame(4)); + for n in 1..=4 { + assert_eq!(played(jitter.pull()), Some(n)); + } // Dry: concealed a while, then silence. for _ in 0..DRY_CONCEALED { assert_eq!(jitter.pull(), Pull::Lost); @@ -354,17 +571,34 @@ mod tests { } #[test] - fn a_missing_frame_is_concealed_and_a_late_one_dropped() { + fn a_missing_frame_is_recovered_from_the_next_and_a_late_one_dropped() { let mut jitter = Jitter::new(20, 300); jitter.push(0, frame(1)); assert_eq!(played(jitter.pull()), Some(1)); jitter.push(2 * FRAME, frame(3)); - assert_eq!(jitter.pull(), Pull::Lost); + // Frame 3 carries a copy of 2, then plays itself. + assert_eq!(jitter.pull(), Pull::Recover(frame(3))); assert_eq!(played(jitter.pull()), Some(3)); // Frame 2 shows up after its turn. jitter.push(FRAME, frame(2)); assert_eq!(jitter.counts().late, 1); - assert_eq!(jitter.counts().concealed, 1); + assert_eq!(jitter.counts().recovered, 1); + assert_eq!(jitter.counts().concealed, 0); + } + + #[test] + fn of_two_missing_frames_the_first_is_concealed_the_second_recovered() { + let mut jitter = Jitter::new(20, 300); + jitter.push(0, frame(1)); + assert_eq!(played(jitter.pull()), Some(1)); + jitter.push(3 * FRAME, frame(4)); + assert_eq!(jitter.pull(), Pull::Lost, "frame 2: no copy of it here"); + assert_eq!(jitter.pull(), Pull::Recover(frame(4)), "frame 3"); + assert_eq!(played(jitter.pull()), Some(4)); + assert_eq!( + (jitter.counts().concealed, jitter.counts().recovered), + (1, 1) + ); } #[test] diff --git a/src/huddle_audio/media.rs b/src/huddle_audio/media.rs index 2fc2425..6782676 100644 --- a/src/huddle_audio/media.rs +++ b/src/huddle_audio/media.rs @@ -2753,12 +2753,12 @@ mod tests { .iter() .all(|(time, _, _)| time % 960 == heard[0].0 % 960) ); - let mut decoder = opus_decoder::OpusDecoder::new(48_000, 1).expect("a decoder"); + let mut decoder = opus_rs::OpusDecoder::new(48_000, 1).expect("a decoder"); let mut loud = 0; for (_, level, payload) in &heard { - let mut pcm = vec![0.0; decoder.max_frame_size_per_channel()]; + let mut pcm = vec![0.0; 5760]; let n = decoder - .decode_float(payload, &mut pcm, false) + .decode(payload, 5760, &mut pcm) .expect("our Opus decodes"); assert_eq!(n, 960); if super::super::uplink::level(&pcm[..n]) < 30 { diff --git a/src/huddle_audio/speaker.rs b/src/huddle_audio/speaker.rs index 3eae708..1fb5a86 100644 --- a/src/huddle_audio/speaker.rs +++ b/src/huddle_audio/speaker.rs @@ -7,13 +7,12 @@ //! but a stream that never ends instead of a file. The level is left as //! decoded, so the system's volume is the one that counts. //! -//! Decoding is `opus-decoder`: pure Rust, no unsafe code, no C library to -//! find on Linux, macOS or Windows. It passes the RFC 8251 test vectors -//! by its own account; it is young, and libopus (the `opus` crate) is the -//! fallback if it ever decodes wrong. `opus-rs`, which encodes what we -//! send, decodes too, but not well enough to play with: it has no CELT -//! loss concealment, refuses a mono packet in a stereo decoder, has no -//! FEC, and reads the network's bytes with unchecked indexing. +//! Decoding is `opus-rs`, which also encodes what we send: libopus 1.6 +//! ported to Rust, no C library to find on Linux, macOS or Windows. A lost +//! frame plays from the copy the next one carries (in-band FEC) when that +//! one is here, and is concealed otherwise, both as libopus does (our fork +//! until a release has them; see Cargo.toml). libopus (the `opus` crate) +//! is the fallback if it ever decodes wrong. //! //! A panic in the decoder is caught ([`crate::audio::guard`]): that frame //! is lost and a fresh decoder takes the next. The session checks @@ -36,7 +35,7 @@ use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; -use super::jitter::{CLOCK, Counts, FRAME, Jitter, Pull, opus_samples}; +use super::jitter::{self, CLOCK, Counts, FRAME, Jitter, Pull, opus_samples}; use super::processing::RenderTap; use crate::audio::guard::{self, Health}; use crate::sync::lock_or_reset; @@ -45,7 +44,7 @@ use crate::sync::lock_or_reset; /// both. const CHANNELS: u16 = 2; /// Silence played while the buffer fills, per pull: 10 ms. -const SILENCE: usize = (CLOCK / 100) as usize * CHANNELS as usize; +const SILENCE: usize = jitter::SILENCE as usize * CHANNELS as usize; /// How long a device may go without asking for sound before it counts as /// stopped. It asks every few milliseconds while it plays. const STALL: Duration = Duration::from_secs(2); @@ -63,6 +62,9 @@ struct Shared { /// asked: its RMS, as `f32` bits (which order as the values do, for /// these never negative). loudest: AtomicU32, + /// Where what comes and what plays is captured, if asked for (see + /// [`super::capture`]). + capture: Option, } impl Shared { @@ -103,6 +105,9 @@ pub struct Feed { impl Feed { /// One Opus frame with its RTP timestamp. pub fn push(&self, timestamp: u32, payload: &[u8]) { + if let Some(capture) = &self.shared.capture { + capture.packet(timestamp, payload); + } self.shared.jitter().push(timestamp, payload.to_vec()); } @@ -122,8 +127,7 @@ impl Feed { } } -/// What decodes the huddle's frames: opus-decoder, or a stand-in in -/// tests. +/// What decodes the huddle's frames: opus-rs, or a stand-in in tests. trait Opus: Send { /// The most samples per channel one packet decodes to. fn room(&self) -> usize; @@ -131,15 +135,43 @@ trait Opus: Send { /// `out`; how many samples per channel came out, or `None` if it /// would not decode. fn decode(&mut self, packet: &[u8], out: &mut [f32]) -> Option; + /// Plays a lost frame from the copy `next`, the packet after it, + /// carries (in-band FEC), or conceals it where `next` has none. + fn recover(&mut self, next: &[u8], out: &mut [f32]) -> Option { + let _ = next; + self.decode(&[], out) + } +} + +/// The most one packet holds: 120 ms. +const ROOM: usize = CLOCK as usize / 1000 * 120; + +/// opus-rs's decoder, and how long the last packet was: a lost frame is +/// concealed for as long (opus-rs asks; libopus's API does too). +struct OpusRs { + inner: opus_rs::OpusDecoder, + last: usize, } -impl Opus for opus_decoder::OpusDecoder { +impl Opus for OpusRs { fn room(&self) -> usize { - self.max_frame_size_per_channel() + ROOM } fn decode(&mut self, packet: &[u8], out: &mut [f32]) -> Option { - self.decode_float(packet, out, false).ok() + let size = if packet.is_empty() { self.last } else { ROOM }; + let n = self.inner.decode(packet, size, out).ok()?; + if !packet.is_empty() { + self.last = n; + } + Some(n) + } + + fn recover(&mut self, next: &[u8], out: &mut [f32]) -> Option { + match self.inner.decode_fec(next, self.last, out) { + Ok(n) => Some(n), + Err(_) => self.decode(&[], out), + } } } @@ -162,9 +194,13 @@ fn rms(samples: &[f32]) -> f32 { type MakeOpus = fn() -> Result, String>; fn opus_decoder() -> Result, String> { - let decoder = opus_decoder::OpusDecoder::new(CLOCK, usize::from(CHANNELS)) + let rate = i32::try_from(CLOCK).map_err(|e| e.to_string())?; + let inner = opus_rs::OpusDecoder::new(rate, usize::from(CHANNELS)) .map_err(|e| format!("no Opus decoder: {e}"))?; - Ok(Box::new(decoder)) + Ok(Box::new(OpusRs { + inner, + last: FRAME as usize, + })) } /// Decodes and hands out samples as the device asks for them. @@ -210,6 +246,9 @@ impl Decoded { if let Some(tap) = &self.tap { tap.push(&self.samples, usize::from(CHANNELS)); } + if let Some(capture) = &self.shared.capture { + capture.played(&self.samples); + } } /// Decodes what comes next into `self.samples`. @@ -217,15 +256,19 @@ impl Decoded { let (pull, resumed) = { let mut jitter = self.shared.jitter(); let pull = jitter.pull(); + if let Some(capture) = &self.shared.capture { + capture.pull(&pull); + } let resumed = matches!(pull, Pull::Frame(_)) && jitter.take_resumed(); (pull, resumed) }; let room = self.decoder.room() * usize::from(CHANNELS); self.samples.resize(room, 0.0); self.at = 0; - let payload: &[u8] = match &pull { - Pull::Frame(payload) => payload, - Pull::Lost => &[], + let (payload, fec): (&[u8], bool) = match &pull { + Pull::Frame(payload) => (payload, false), + Pull::Lost => (&[], false), + Pull::Recover(next) => (next, true), Pull::Silence => { self.samples.clear(); self.samples.resize(SILENCE, 0.0); @@ -233,11 +276,13 @@ impl Decoded { } }; let (decoder, samples) = (&mut self.decoder, &mut self.samples); - let decoded = match self - .device - .health - .catch(|| decoder.decode(payload, samples)) - { + let decoded = match self.device.health.catch(|| { + if fec { + decoder.recover(payload, samples) + } else { + decoder.decode(payload, samples) + } + }) { Some(decoded) => decoded, None => { // Its state is suspect: a fresh one for the next frame. @@ -406,7 +451,10 @@ impl Speaker { tap: Option, choice: Option, ) -> Result<(Opened, Feed), String> { - let shared = Arc::new(Shared::default()); + let shared = Arc::new(Shared { + capture: super::capture::Capture::from_env(), + ..Shared::default() + }); let opened = Self::start(shared.clone(), tap, choice)?; Ok((opened, Feed { shared })) } @@ -504,9 +552,8 @@ mod tests { /// Opus's shortest silence: a 20 ms CELT frame with nothing in it. const SILENT_FRAME: [u8; 3] = [0xF8, 0xFF, 0xFE]; - /// A 20 ms CELT frame (found by feeding opus-decoder noise) whose - /// band has 16 short blocks: the one that overflowed opus-decoder's - /// u8 collapse mask on the device's thread in debug builds. + /// A 20 ms CELT frame (found by feeding a decoder noise) whose band has + /// 16 short blocks: one overflowed opus-decoder's collapse mask. const SIXTEEN_BLOCKS: [u8; 55] = [ 0xf8, 0x75, 0xd5, 0x48, 0x6a, 0x8c, 0xcf, 0xb8, 0x7d, 0xb1, 0xd2, 0x3b, 0x43, 0x7d, 0x6b, 0x6b, 0x17, 0xc1, 0x14, 0xfe, 0x7d, 0xa5, 0xae, 0x93, 0x56, 0x58, 0xc4, 0x69, 0xd1, 0x30, @@ -518,7 +565,7 @@ mod tests { const POISON: [u8; 2] = [0xF8, 0xDE]; /// Decodes every packet to 20 ms of a constant, panicking on - /// [`POISON`] as opus-decoder did on [`SIXTEEN_BLOCKS`]. + /// [`POISON`] as a decoder might on a malformed packet. struct Brittle; impl Opus for Brittle { @@ -547,12 +594,20 @@ mod tests { #[test] fn a_frame_with_sixteen_short_blocks_decodes() { - // Cargo.toml turns opus-decoder's overflow checks off for this; - // with them on, this panics in src/celt/vq.rs. - let mut decoder = opus_decoder::OpusDecoder::new(CLOCK, 2).expect("a decoder"); - let mut out = vec![0.0; decoder.max_frame_size_per_channel() * 2]; - let decoded = decoder.decode_float(&SIXTEEN_BLOCKS, &mut out, false); - assert_eq!(decoded.ok(), Some(960)); + let mut decoder = opus_decoder().expect("a decoder"); + let mut out = vec![0.0; decoder.room() * usize::from(CHANNELS)]; + assert_eq!(decoder.decode(&SIXTEEN_BLOCKS, &mut out), Some(960)); + } + + #[test] + fn a_mono_packet_plays_on_both_channels() { + // Teams sends mono SILK; the decoder is stereo. + const QUIET: [u8; 10] = [0x48, 0x09, 0x9e, 0xee, 0x99, 0xb6, 0xe5, 0x73, 0xb3, 0xad]; + let mut decoder = opus_decoder().expect("a decoder"); + let mut out = vec![0.0; decoder.room() * usize::from(CHANNELS)]; + assert_eq!(decoder.decode(&QUIET, &mut out), Some(960)); + // A loss conceals as long as the last packet. + assert_eq!(decoder.decode(&[], &mut out), Some(960)); } #[test] @@ -630,11 +685,60 @@ mod tests { assert!(!device.health.thread_gone()); } + #[test] + fn concealing_after_silence_stays_silent() { + // A SILK wideband packet of random bytes that decodes to near + // silence, as Teams' talk spurts end before it sends nothing: + // opus-decoder's concealment once played white noise after it. + const QUIET: [u8; 10] = [0x48, 0x09, 0x9e, 0xee, 0x99, 0xb6, 0xe5, 0x73, 0xb3, 0xad]; + let mut decoder = opus_decoder().expect("a decoder"); + let mut out = vec![0.0; decoder.room() * usize::from(CHANNELS)]; + let mut loudest = 0.0f32; + for _ in 0..3 { + let n = decoder.decode(&QUIET, &mut out).expect("decodes"); + loudest = loudest.max(rms(&out[..n * usize::from(CHANNELS)])); + } + assert!(loudest < 0.01, "near silence: {loudest}"); + for lost in 0..3 { + let n = decoder.decode(&[], &mut out).expect("conceals"); + let concealed = rms(&out[..n * usize::from(CHANNELS)]); + assert!( + concealed <= loudest + 0.001, + "lost frame {lost}: {concealed}" + ); + } + } + + #[test] + fn a_lost_frame_plays_from_the_copy_in_the_next() { + use super::super::encoder::{Encoder, OpusRs}; + use super::super::uplink::{TWENTY_MS, Tone}; + let mut encoder = OpusRs::speech().expect("an encoder"); + let mut tone = Tone::new(440.0, 0.3); + let (feed, _, mut decoded) = decoded(None, opus_decoder); + for n in 0..20u32 { + let mut frame = vec![0.0; TWENTY_MS]; + tone.fill(&mut frame); + let packet = encoder.encode(&frame).expect("encoded"); + // Frame 12 is lost on the way. + if n != 12 { + feed.push(n * FRAME, &packet); + } + } + let samples: Vec = decoded.by_ref().take(20 * 960 * 2).collect(); + let played = feed.played(); + assert_eq!((played.jitter.recovered, played.jitter.concealed), (1, 0)); + // Its 20 ms are sound, no louder than the tone (0.3 peak: 0.21 + // RMS): a low-bitrate copy, 0.128 RMS here, as libopus decodes it. + let at = 12 * 960 * 2; + let level = rms(&samples[at..at + 960 * 2]); + assert!((0.08..0.22).contains(&level), "{level}"); + } + #[test] fn a_hybrid_frame_claiming_too_much_redundancy_plays() { // Two 76-byte hybrid frames claiming more redundancy than they - // hold: opus-decoder 0.1.1 sliced out of range on it; the patched - // copy (vendor/opus-decoder) drops the redundancy as libopus does. + // hold: opus-decoder 0.1.1 sliced out of range on it. const BAD_HYBRID: [u8; 153] = [ 0x69, 0xaf, 0x0e, 0xe6, 0x85, 0x96, 0x57, 0x1b, 0xb5, 0x8c, 0xc1, 0xa2, 0x21, 0x35, 0xa5, 0x94, 0x3d, 0x0d, 0x19, 0xd1, 0xc8, 0xe7, 0x7c, 0xd0, 0x63, 0x03, 0xbd, 0xc4, diff --git a/vendor/opus-decoder/Cargo.toml b/vendor/opus-decoder/Cargo.toml deleted file mode 100644 index 1d21b2e..0000000 --- a/vendor/opus-decoder/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "opus-decoder" -version = "0.1.1" -edition = "2024" -rust-version = "1.85" -description = "Pure-Rust Opus decoder — RFC 8251 conformant, no unsafe, no FFI" -license = "MIT OR Apache-2.0" -repository = "https://github.com/TadeuszWolfGang/Rusopus" -publish = false - -[dependencies] -thiserror = "2.0.18" diff --git a/vendor/opus-decoder/README.md b/vendor/opus-decoder/README.md deleted file mode 100644 index 70aef54..0000000 --- a/vendor/opus-decoder/README.md +++ /dev/null @@ -1,229 +0,0 @@ -# opus-decoder - -[![Crates.io](https://img.shields.io/crates/v/opus-decoder.svg)](https://crates.io/crates/opus-decoder) -[![Docs.rs](https://docs.rs/opus-decoder/badge.svg)](https://docs.rs/opus-decoder) -[![CI](https://github.com/tadeuszwojcik/rasopus/actions/workflows/ci.yml/badge.svg)](https://github.com/tadeuszwojcik/rasopus/actions) -[![RFC 8251](https://img.shields.io/badge/RFC%208251-12%2F12%20conformant-brightgreen)](https://www.rfc-editor.org/rfc/rfc8251) -[![License](https://img.shields.io/crates/l/opus-decoder.svg)](LICENSE-APACHE) - -A pure-Rust [Opus](https://opus-codec.org/) decoder with no unsafe code and no -FFI. Passes all 12 RFC 8251 conformance test vectors. - -## Features - -- **RFC 8251 conformant** — all 12 official test vectors pass `opus_compare` -- **All Opus modes** — CELT, SILK, and Hybrid (SWB/FB) -- **All output sample rates** — 8, 12, 16, 24, 48 kHz -- **Mono and stereo** — including up/downmix -- **Packet Loss Concealment (PLC)** — CELT fade-out, SILK pitch extrapolation -- **Multistream** — `OpusMultistreamDecoder` for surround (5.1, 7.1) and call recording -- **`#![forbid(unsafe_code)]`** — throughout the correctness implementation -- **No FFI, no C dependencies** — pure Rust - -## Quick Start - -Add to `Cargo.toml`: - -```toml -[dependencies] -opus-decoder = "0.1" -``` - -### Decode a single packet - -```rust -use opus_decoder::{OpusDecoder, OpusError}; - -fn main() -> Result<(), OpusError> { - let mut decoder = OpusDecoder::new(48_000, 2)?; // 48 kHz, stereo - - let packet: &[u8] = /* your Opus packet bytes */; - let mut pcm = vec![0i16; 960 * 2]; // 20 ms at 48 kHz, stereo interleaved - - let samples = decoder.decode(packet, &mut pcm, false)?; - println!("Decoded {} samples per channel", samples); - Ok(()) -} -``` - -### Packet Loss Concealment - -```rust -// Pass an empty slice to trigger PLC -let mut pcm = vec![0i16; 960 * 2]; -decoder.decode(&[], &mut pcm, false)?; // generates concealment frame -``` - -### Float output - -```rust -let mut pcm_f32 = vec![0f32; 960 * 2]; -decoder.decode_float(packet, &mut pcm_f32, false)?; -``` - -### Multistream — call recording (2 speakers → stereo) - -```rust -use opus_decoder::OpusMultistreamDecoder; - -// Two independent mono streams → stereo output -// Left channel: caller (Stream A), Right channel: callee (Stream B) -let mut decoder = OpusMultistreamDecoder::new( - 48_000, // sample rate - 2, // output channels - 2, // nb_streams - 0, // nb_coupled_streams (both mono) - &[0, 1], // mapping: stream 0 → L, stream 1 → R -)?; - -let mut pcm = vec![0i16; 960 * 2]; -decoder.decode(&multistream_packet, &mut pcm, false)?; -``` - -### Multistream — 5.1 surround - -```rust -let mut decoder = OpusMultistreamDecoder::new( - 48_000, - 6, // 5.1 output channels - 4, // 4 streams total - 2, // 2 coupled (stereo) streams - &[0, 1, 2, 3, 4, 255], // mapping (255 = silence) -)?; -``` - -## API Reference - -### `OpusDecoder` - -```rust -impl OpusDecoder { - pub fn new(sample_rate: u32, channels: usize) -> Result; - pub fn decode(&mut self, packet: &[u8], pcm: &mut [i16], fec: bool) -> Result; - pub fn decode_float(&mut self, packet: &[u8], pcm: &mut [f32], fec: bool) -> Result; - pub fn reset(&mut self); -} -``` - -`sample_rate` must be one of: `8000`, `12000`, `16000`, `24000`, `48000`. -`channels` must be `1` or `2`. -`decode` returns the number of samples decoded **per channel**. -An empty `packet` or `fec: true` triggers Packet Loss Concealment (PLC). - -### `OpusMultistreamDecoder` - -```rust -impl OpusMultistreamDecoder { - pub fn new(sample_rate: u32, nb_channels: usize, nb_streams: usize, - nb_coupled_streams: usize, mapping: &[u8]) -> Result; - pub fn decode(&mut self, packet: &[u8], pcm: &mut [i16], fec: bool) -> Result; - pub fn decode_float(&mut self, packet: &[u8], pcm: &mut [f32], fec: bool) -> Result; - pub fn reset(&mut self); -} -``` - -`mapping[i] = 255` silences output channel `i`. -The first `nb_coupled_streams` streams are stereo-coupled; the rest are mono. - -### `OpusError` - -```rust -pub enum OpusError { - InvalidPacket, - InternalError, - BufferTooSmall, - InvalidArgument(&'static str), -} -``` - -## Conformance - -All 12 RFC 8251 test vectors pass the `opus_compare` quality metric: - -| Vector | Description | Result | -|--------|-------------|--------| -| tv01 | CELT stereo FB | ✅ PASS | -| tv02 | SILK NB 8 kHz mono | ✅ PASS | -| tv03 | SILK MB 12 kHz mono | ✅ PASS | -| tv04 | SILK WB 16 kHz mono | ✅ PASS | -| tv05 | Hybrid SWB transitions | ✅ PASS | -| tv06 | Hybrid FB transitions | ✅ PASS | -| tv07 | CELT mono FB | ✅ PASS | -| tv08 | SILK stereo + CELT NB stereo | ✅ PASS | -| tv09 | SILK→CELT transitions | ✅ PASS | -| tv10 | CeltOnly + Hybrid mix | ✅ PASS | -| tv11 | CELT stereo FB | ✅ PASS | -| tv12 | SILK bandwidth transitions | ✅ PASS | - -Run conformance tests locally: - -```bash -# Download test vectors -cargo xtask fetch-vectors - -# Run all 12 vectors -for v in 01 02 03 04 05 06 07 08 09 10 11 12; do - OPUS_TESTVECTORS_DIR="testdata/opus_testvectors" \ - OPUS_VECTOR=testvector${v} \ - cargo test --release -p opus-decoder rfc_conformance -- --ignored --nocapture \ - 2>&1 | grep -E "PASS|FAIL" -done -``` - -## Minimum Supported Rust Version (MSRV) - -**Rust 1.85** — enforced in CI. - -Uses: `std`, `thiserror`. No nightly features. - -## Out of Scope - -The following are intentionally **not** implemented in this crate: - -- **Encoder** — decoder only -- **Container parsing** — Ogg/WebM demux belongs in a separate crate -- **SIMD optimizations** — correctness first; performance is a future phase - -## Implementation Notes - -This crate is a pure Rust port of libopus based on RFC 6716 and RFC 8251, -implemented function-by-function and verified against the reference libopus implementation. -The floating-point decode path is used throughout (matching libopus float semantics). - -Internal architecture: - -``` -lib.rs — public API, mode routing, mode transitions -├── celt/ — CELT decoder (MDCT, PVQ, band decode, anti-collapse, PLC) -├── silk/ — SILK decoder (NLSF→LPC, excitation, IIR/FIR resampler, PLC) -├── multistream.rs — OpusMultistreamDecoder -├── entropy.rs — Range coder (EcDec) -└── compare.rs — opus_compare quality metric port -``` - -## Known Limitations - -- Hybrid mode PLC uses SILK-only concealment (no CELT highband PLC) -- No fuzz harness yet (planned) - -## License - -Licensed under either of: - -- [Apache License, Version 2.0](LICENSE-APACHE) -- [MIT License](LICENSE-MIT) - -at your option. - -This crate ports algorithms from [libopus](https://gitlab.xiph.org/xiph/opus), -which is licensed under the [BSD 3-Clause License](https://gitlab.xiph.org/xiph/opus/-/blob/master/COPYING). - -## Contributing - -Conformance is the top priority. Any change must pass all 12 RFC 8251 test vectors -before merging. Run: - -```bash -cargo test --release -p opus-decoder -cargo clippy -p opus-decoder --all-targets -- -D warnings -``` diff --git a/vendor/opus-decoder/VENDORED.md b/vendor/opus-decoder/VENDORED.md deleted file mode 100644 index d8c7e1b..0000000 --- a/vendor/opus-decoder/VENDORED.md +++ /dev/null @@ -1,22 +0,0 @@ -# opus-decoder 0.1.1, patched - -A copy of [`opus-decoder`](https://crates.io/crates/opus-decoder) 0.1.1 -(, MIT OR Apache-2.0, as its -manifest says), used through `[patch.crates-io]` in NoSlacking's -`Cargo.toml` for huddle audio. Only `src/`, the README and a standalone -manifest are copied; the upstream tests and benches are left out. - -Two changes, each marked `NoSlacking:` in the code: - -1. `src/lib.rs`, hybrid mode: the redundancy length read from the packet - is checked against the frame as libopus does (`opus_decoder.c`, - `if (len*8 < ec_tell(&dec))`). Without it a malformed packet sliced - out of range and panicked, which in a release build (`panic = "abort"`) - closes the app; one participant could close everyone's NoSlacking. -2. `src/celt/vq.rs`, `extract_collapse_mask`: a transient frame can have - 16 short blocks, so `1 << i` on a `u8` overflowed (a panic in debug - builds). `wrapping_shl` keeps what release builds always computed, - which the fold that follows turns into libopus's mask. - -Both are reported upstream (see TODO.md); drop this copy once a release -has them. diff --git a/vendor/opus-decoder/src/celt/bands.rs b/vendor/opus-decoder/src/celt/bands.rs deleted file mode 100644 index 0d41f10..0000000 --- a/vendor/opus-decoder/src/celt/bands.rs +++ /dev/null @@ -1,2214 +0,0 @@ -//! CELT band decode (`bands.c`) for mono decode-only vertical slice. -#![allow( - unused_variables, - unused_assignments, - dead_code, - clippy::too_many_arguments, - clippy::needless_option_as_deref, - clippy::needless_range_loop, - clippy::get_first -)] - -use crate::celt::modes::CeltMode; -use crate::celt::quant_bands; -use crate::celt::rate; -use crate::celt::vq; -use crate::entropy::EcDec; - -const BITRES: i32 = 3; -const QTHETA_OFFSET: i32 = 4; -const QTHETA_OFFSET_TWOPHASE: i32 = 16; -const SPREAD_AGGRESSIVE: i32 = 3; -const BIT_INTERLEAVE_TABLE: [u32; 16] = [0, 1, 1, 1, 2, 3, 3, 3, 2, 3, 3, 3, 2, 3, 3, 3]; -const BIT_DEINTERLEAVE_TABLE: [u8; 16] = [ - 0x00, 0x03, 0x0C, 0x0F, 0x30, 0x33, 0x3C, 0x3F, 0xC0, 0xC3, 0xCC, 0xCF, 0xF0, 0xF3, 0xFC, 0xFF, -]; -const EXP2_TABLE8: [i32; 8] = [16384, 17866, 19483, 21247, 23170, 25267, 27554, 30048]; -const ORDERY_TABLE: [usize; 30] = [ - 1, 0, 3, 0, 2, 1, 7, 0, 4, 3, 6, 1, 5, 2, 15, 0, 8, 7, 12, 3, 11, 4, 14, 1, 9, 6, 13, 2, 10, 5, -]; -/// CELT linear congruential generator used for folded/noise fill. -/// -/// Params: current seed. -/// Returns: next pseudo-random seed. -pub(crate) fn celt_lcg_rand(seed: u32) -> u32 { - seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223) -} - -/// Append one NDJSON debug entry for anti-collapse probes. -/// -/// Params: run metadata plus `data_json` object payload. -/// Returns: nothing. -fn append_anti_collapse_debug_log( - run_id: &str, - hypothesis_id: &str, - location: &str, - message: &str, - data_json: &str, -) { - let _ = (run_id, hypothesis_id, location, message, data_json); -} - -/// Prevent transient energy collapse by injecting low-level noise. -/// -/// Params: mode, normalized spectra (`x`/optional `y`), per-band collapse masks, -/// band/frame context (`lm`, `coded_channels`, `start`, `end`), energy history -/// (`log_e`, `prev1_log_e`, `prev2_log_e`), pulse allocation and RNG seed. -/// Returns: nothing; vectors are modified in-place. -#[allow(clippy::too_many_arguments)] -pub(crate) fn anti_collapse( - mode: &CeltMode, - x: &mut [f32], - mut y: Option<&mut [f32]>, - collapse_masks: &[u8], - lm: usize, - coded_channels: usize, - start: usize, - end: usize, - log_e: &[f32], - prev1_log_e: &[f32], - prev2_log_e: &[f32], - pulses: &[i32], - seed: u32, - trace_this_packet: bool, - packet_idx: usize, -) { - let nb = mode.nb_ebands; - if coded_channels == 0 || lm > 3 { - return; - } - let run_id = "run-anti-collapse-v1"; - let probe_packet = packet_idx <= 8 || trace_this_packet; - let mut seed_local = seed; - let blocks = 1usize << lm; - let block_mask_limit = if blocks >= 8 { - 0xFFu8 - } else { - ((1u16 << blocks) - 1) as u8 - }; - if probe_packet { - let data = format!( - "{{\"packet_idx\":{},\"lm\":{},\"coded_channels\":{},\"start\":{},\"end\":{},\"seed_in\":{},\"blocks\":{},\"collapse_masks_len\":{},\"block_mask_limit\":{}}}", - packet_idx, - lm, - coded_channels, - start, - end, - seed, - blocks, - collapse_masks.len(), - block_mask_limit - ); - // #region agent log - append_anti_collapse_debug_log( - run_id, - "H1", - "crates/opus-decoder/src/celt/bands.rs:anti_collapse", - "anti_collapse_entry", - &data, - ); - // #endregion - } - - for i in start..end { - let n0 = (mode.e_bands[i + 1] - mode.e_bands[i]) as usize; - if n0 == 0 || i >= pulses.len() { - continue; - } - let depth = (((1 + pulses[i].max(0)) as usize) / n0) >> lm; - let thresh = 0.5 * (2.0f32).powf(-0.125 * depth as f32); - let sqrt_1 = 1.0 / ((n0 << lm) as f32).sqrt(); - let band_start = (mode.e_bands[i] as usize) << lm; - let band_len = n0 << lm; - - for c in 0..coded_channels.min(2) { - let chan = if c == 0 { - &mut x[..] - } else if let Some(yv) = y.as_deref_mut() { - yv - } else { - continue; - }; - if band_start + band_len > chan.len() { - continue; - } - let mut prev1 = prev1_log_e[c * nb + i]; - let mut prev2 = prev2_log_e[c * nb + i]; - if coded_channels == 1 { - prev1 = prev1.max(prev1_log_e[nb + i]); - prev2 = prev2.max(prev2_log_e[nb + i]); - } - let ediff = (log_e[c * nb + i] - prev1.min(prev2)).max(0.0); - let mut r = 2.0 * (2.0f32).powf(-ediff); - if lm == 3 { - r *= std::f32::consts::SQRT_2; - } - r = r.min(thresh) * sqrt_1; - - let mask_idx = i * coded_channels + c; - if mask_idx >= collapse_masks.len() { - continue; - } - let collapse_mask = collapse_masks[mask_idx] & block_mask_limit; - let mut injected_blocks = 0usize; - let band = &mut chan[band_start..band_start + band_len]; - let band_pre_abs = if probe_packet { - band.iter().map(|v| v.abs()).sum::() - } else { - 0.0 - }; - for k in 0..blocks { - if (collapse_mask & (1u8 << k)) == 0 { - for j in 0..n0 { - seed_local = celt_lcg_rand(seed_local); - band[(j << lm) + k] = if (seed_local & 0x8000) != 0 { r } else { -r }; - } - injected_blocks += 1; - } - } - if injected_blocks > 0 { - vq::renormalise_vector(band, 1.0); - } - let band_post_abs = if probe_packet { - band.iter().map(|v| v.abs()).sum::() - } else { - 0.0 - }; - if probe_packet && (collapse_mask != block_mask_limit || injected_blocks > 0) { - let data = format!( - "{{\"packet_idx\":{},\"band\":{},\"channel\":{},\"depth\":{},\"ediff\":{},\"r\":{},\"collapse_mask\":{},\"block_mask_limit\":{},\"injected_blocks\":{},\"band_pre_abs\":{},\"band_post_abs\":{}}}", - packet_idx, - i, - c, - depth, - ediff, - r, - collapse_mask, - block_mask_limit, - injected_blocks, - band_pre_abs, - band_post_abs - ); - let hypothesis = if injected_blocks > 0 { "H3" } else { "H2" }; - // #region agent log - append_anti_collapse_debug_log( - run_id, - hypothesis, - "crates/opus-decoder/src/celt/bands.rs:anti_collapse", - "anti_collapse_band_probe", - &data, - ); - // #endregion - } - if trace_this_packet && (injected_blocks > 0 || i >= 18) { - // #region agent log - debug_trace!( - "R pkt{} anti_collapse band={} ch={} depth={} ediff={:.6} r={:.6} mask=0x{:x} injected_blocks={}", - packet_idx, - i, - c, - depth, - ediff, - r, - collapse_mask, - injected_blocks - ); - // #endregion - } - } - } -} - -/// Integer square-root for non-negative 32-bit values. -/// -/// Params: input integer value. -/// Returns: floor(sqrt(value)). -fn isqrt32(v: u32) -> u32 { - if v == 0 { - return 0; - } - let mut x = v; - let mut y = (x + 1) >> 1; - while y < x { - x = y; - y = (x + v / x) >> 1; - } - x -} - -/// Integer ilog helper equivalent to CELT `EC_ILOG`. -/// -/// Params: positive integer value. -/// Returns: floor(log2(v)) + 1. -fn ec_ilog(v: i32) -> i32 { - debug_assert!(v > 0); - 32 - (v as u32).leading_zeros() as i32 -} - -/// Bit-exact fractional multiply equivalent to `FRAC_MUL16`. -/// -/// Params: signed 16-bit fixed point multiplicands. -/// Returns: rounded product shifted by 15. -fn frac_mul16(a: i32, b: i32) -> i32 { - (16384 + ((a as i16 as i32) * (b as i16 as i32))) >> 15 -} - -/// Bit-exact cosine approximation from libopus `bands.c`. -/// -/// Params: angle in Q14 domain [0, 16384]. -/// Returns: cosine approximation in Q15-ish integer domain. -fn bitexact_cos(x: i32) -> i32 { - let tmp = (4096 + x * x) >> 13; - let x2 = tmp; - 1 + (32767 - x2) + frac_mul16(x2, -7651 + frac_mul16(x2, 8277 + frac_mul16(-626, x2))) -} - -/// Bit-exact log2(tan()) approximation for split bit rebalancing. -/// -/// Params: side and mid cosine terms. -/// Returns: fixed-point log2(tan()) approximation. -fn bitexact_log2tan(isin: i32, icos: i32) -> i32 { - let lc = ec_ilog(icos.max(1)); - let ls = ec_ilog(isin.max(1)); - let icos_n = icos << (15 - lc); - let isin_n = isin << (15 - ls); - (ls - lc) * (1 << 11) + frac_mul16(isin_n, frac_mul16(isin_n, -2597) + 7932) - - frac_mul16(icos_n, frac_mul16(icos_n, -2597) + 7932) -} - -/// One-stage Haar transform used by CELT TF reordering. -/// -/// Params: mutable vector, base size and stride. -/// Returns: nothing; vector is updated in-place. -fn haar1(x: &mut [f32], n0: usize, stride: usize) { - let half = n0 >> 1; - for i in 0..stride { - for j in 0..half { - let idx0 = stride * 2 * j + i; - let idx1 = stride * (2 * j + 1) + i; - let tmp1 = std::f32::consts::FRAC_1_SQRT_2 * x[idx0]; - let tmp2 = std::f32::consts::FRAC_1_SQRT_2 * x[idx1]; - x[idx0] = tmp1 + tmp2; - x[idx1] = tmp1 - tmp2; - } - } -} - -/// Convert natural to ordery Hadamard index order. -/// -/// Params: vector, segment size, stride and hadamard mode. -/// Returns: nothing; vector is updated in-place. -fn deinterleave_hadamard(x: &mut [f32], n0: usize, stride: usize, hadamard: bool) { - let n = n0 * stride; - let mut tmp = vec![0.0f32; n]; - if hadamard { - let ordery = &ORDERY_TABLE[(stride - 2)..]; - for i in 0..stride { - for j in 0..n0 { - tmp[ordery[i] * n0 + j] = x[j * stride + i]; - } - } - } else { - for i in 0..stride { - for j in 0..n0 { - tmp[i * n0 + j] = x[j * stride + i]; - } - } - } - x[..n].copy_from_slice(&tmp); -} - -/// Convert ordery Hadamard back to natural order. -/// -/// Params: vector, segment size, stride and hadamard mode. -/// Returns: nothing; vector is updated in-place. -fn interleave_hadamard(x: &mut [f32], n0: usize, stride: usize, hadamard: bool) { - let n = n0 * stride; - let mut tmp = vec![0.0f32; n]; - if hadamard { - let ordery = &ORDERY_TABLE[(stride - 2)..]; - for i in 0..stride { - for j in 0..n0 { - tmp[j * stride + i] = x[ordery[i] * n0 + j]; - } - } - } else { - for i in 0..stride { - for j in 0..n0 { - tmp[j * stride + i] = x[i * n0 + j]; - } - } - } - x[..n].copy_from_slice(&tmp); -} - -/// Apply quant_band decode-time TF/frequency post-processing. -/// -/// Params: decoded band vector and TF transform context. -/// Returns: nothing; vector is updated in-place. -fn apply_quant_band_post_tf( - x: &mut [f32], - n: usize, - b0: usize, - recombine: usize, - time_divide: usize, - long_blocks: bool, -) { - let mut n_b = n / b0.max(1); - if b0 > 1 { - interleave_hadamard(x, n_b >> recombine, b0 << recombine, long_blocks); - } - let mut b = b0; - for _ in 0..time_divide { - b >>= 1; - n_b <<= 1; - haar1(x, n_b, b); - } - for k in 0..recombine { - haar1(x, n >> k, 1 << k); - } -} - -/// Apply quant-band collapse-mask post-processing after TF transforms. -/// -/// Params: raw collapse mask from `quant_partition`, transformed block count, -/// recombine/time-divide context. -/// Returns: collapse mask mapped back to frame block layout. -fn post_tf_collapse_mask(mut cm: u32, b0: usize, recombine: usize, time_divide: usize) -> u32 { - let mut b = b0.max(1); - for _ in 0..time_divide { - b >>= 1; - if b > 0 { - cm |= cm >> b; - } - } - for _ in 0..recombine { - cm = BIT_DEINTERLEAVE_TABLE[(cm & 0x0F) as usize] as u32; - } - let final_b = b << recombine; - if final_b >= 32 { - cm - } else { - cm & ((1u32 << final_b) - 1) - } -} - -/// Transform fill mask through TF recombine/time-divide steps. -/// -/// Params: pre-TF fill mask, original block count, and TF context. -/// Returns: fill mask mapped to quant-partition block layout. -fn transform_fill_for_quant( - mut fill: u32, - mut blocks_orig: usize, - recombine: usize, - time_divide: usize, -) -> u32 { - for _ in 0..recombine { - let lo = (fill & 0x0F) as usize; - let hi = ((fill >> 4) & 0x0F) as usize; - fill = BIT_INTERLEAVE_TABLE[lo] | (BIT_INTERLEAVE_TABLE[hi] << 2); - } - for _ in 0..time_divide { - if blocks_orig == 0 { - break; - } - fill |= fill << blocks_orig; - blocks_orig <<= 1; - } - fill -} - -/// Prepare lowband reference for decode-time folding under TF transforms. -/// -/// Params: lowband slice and TF transform context. -/// Returns: transformed lowband scratch data. -fn transform_lowband_for_decode( - lowband: &[f32], - n: usize, - frame_blocks: usize, - recombine: usize, - tf_change: i32, - long_blocks: bool, -) -> Vec { - if lowband.len() != n { - return lowband.to_vec(); - } - let mut out = lowband.to_vec(); - let mut n_b = n / frame_blocks.max(1); - for k in 0..recombine { - haar1(&mut out, n >> k, 1 << k); - } - let mut b = frame_blocks >> recombine; - n_b <<= recombine; - let mut tf = tf_change; - while (n_b & 1) == 0 && tf < 0 { - haar1(&mut out, n_b, b); - b <<= 1; - n_b >>= 1; - tf += 1; - } - if b > 1 { - deinterleave_hadamard(&mut out, n_b >> recombine, b << recombine, long_blocks); - } - out -} - -/// Duplicate first-band folding history for hybrid transition, like libopus. -/// -/// Params: mode, normalized history buffers (starting at `norm_offset`), -/// CELT start band, block multiplier `m`, and dual-stereo flag. -/// Returns: nothing. -fn special_hybrid_folding_decode( - mode: &CeltMode, - norm_x: &mut [f32], - norm_y: &mut [f32], - start: usize, - m: usize, - dual_stereo_on: bool, -) { - if start + 2 >= mode.e_bands.len() { - return; - } - let n1 = m * (mode.e_bands[start + 1] as usize - mode.e_bands[start] as usize); - let n2 = m * (mode.e_bands[start + 2] as usize - mode.e_bands[start + 1] as usize); - if n2 <= n1 || n1 < (n2 - n1) { - return; - } - let len = n2 - n1; - let src_start = 2 * n1 - n2; - let src_end = src_start + len; - let dst_start = n1; - let dst_end = dst_start + len; - if src_end <= norm_x.len() && dst_end <= norm_x.len() { - norm_x.copy_within(src_start..src_end, dst_start); - } - if dual_stereo_on && src_end <= norm_y.len() && dst_end <= norm_y.len() { - norm_y.copy_within(src_start..src_end, dst_start); - } -} - -/// Merge decoded stereo mid/side vectors back to left/right. -/// -/// Matches libopus `stereo_merge()`: `x` is unscaled mid, `y` is already side-scaled. -/// Params: mutable mid/side vectors and decoded mid gain. -/// Returns: nothing; `x` and `y` become left/right channels. -fn stereo_merge_float(x: &mut [f32], y: &mut [f32], mid: f32) { - let xp = x.iter().zip(y.iter()).map(|(a, b)| a * b).sum::() * mid; - let side_energy = y.iter().map(|v| v * v).sum::(); - let el = mid * mid + side_energy - 2.0 * xp; - let er = mid * mid + side_energy + 2.0 * xp; - if er < 6.0e-4 || el < 6.0e-4 { - y.copy_from_slice(x); - return; - } - let lgain = 1.0 / el.sqrt(); - let rgain = 1.0 / er.sqrt(); - for i in 0..x.len() { - let l = mid * x[i] - y[i]; - let r = mid * x[i] + y[i]; - x[i] = lgain * l; - y[i] = rgain * r; - } -} - -/// Compute theta quantization resolution for recursive split. -/// -/// Params: partition size `n`, bit budget `b`, split `offset`, pulse cap and stereo flag. -/// Returns: theta resolution value `qn`. -fn compute_qn(n: usize, b: i32, offset: i32, pulse_cap: i32, stereo: bool) -> i32 { - let mut n2 = 2 * n as i32 - 1; - if stereo && n == 2 { - n2 -= 1; - } - let mut qb = (b + n2 * offset) / n2; - qb = qb.min(b - pulse_cap - (4 << BITRES)); - qb = qb.min(8 << BITRES); - if qb < (1 << BITRES >> 1) { - 1 - } else { - let mut qn = EXP2_TABLE8[(qb & 0x7) as usize] >> (14 - (qb >> BITRES)); - qn = ((qn + 1) >> 1) << 1; - qn.min(256) - } -} - -/// Decode split theta for mono recursive partition. -/// -/// Params: mode, band index and split context (`n`, `b`, `blocks`, `fill`). -/// Returns: `(delta, qalloc, itheta, imid, iside)` for recursive split. -#[allow(clippy::too_many_arguments)] -fn decode_theta_mono( - mode: &CeltMode, - band: usize, - dec: &mut EcDec<'_>, - n: usize, - b: &mut i32, - blocks: usize, - blocks0: usize, - lm: i32, - fill: &mut u32, -) -> (i32, i32, i32, i32, i32) { - let pulse_cap = mode.log_n[band] as i32 + lm * (1 << BITRES); - let offset = (pulse_cap >> 1) - QTHETA_OFFSET; - let qn = compute_qn(n, *b, offset, pulse_cap, false); - let tell_before = dec.tell_frac() as i32; - let mut itheta = 0i32; - if qn != 1 { - if blocks0 > 1 { - itheta = dec.dec_uint((qn + 1) as u32) as i32; - } else { - let ft = ((qn >> 1) + 1) * ((qn >> 1) + 1); - let fm = dec.decode(ft as u32) as i32; - let (fl, fs, ith) = if fm < ((qn >> 1) * ((qn >> 1) + 1) >> 1) { - let ith = ((isqrt32((8 * fm + 1) as u32) as i32) - 1) >> 1; - let fs = ith + 1; - let fl = ith * (ith + 1) >> 1; - (fl, fs, ith) - } else { - let ith = (2 * (qn + 1) - (isqrt32((8 * (ft - fm - 1) + 1) as u32) as i32)) >> 1; - let fs = qn + 1 - ith; - let fl = ft - ((qn + 1 - ith) * (qn + 2 - ith) >> 1); - (fl, fs, ith) - }; - dec.update(fl as u32, (fl + fs) as u32, ft as u32); - itheta = ith; - } - itheta = (itheta * 16384) / qn; - } - let qalloc = dec.tell_frac() as i32 - tell_before; - *b -= qalloc; - - if itheta == 0 { - *fill &= (1u32 << blocks) - 1; - return (-16384, qalloc, itheta, 32767, 0); - } - if itheta == 16384 { - *fill &= ((1u32 << blocks) - 1) << blocks; - return (16384, qalloc, itheta, 0, 32767); - } - let imid = bitexact_cos(itheta); - let iside = bitexact_cos(16384 - itheta); - let delta = frac_mul16((n as i32 - 1) << 7, bitexact_log2tan(iside, imid)); - (delta, qalloc, itheta, imid, iside) -} - -/// Decode stereo theta split and return side allocation context. -/// -/// Params: mode, band, split size/blocks, bit budget, intensity limit and decoder state. -/// Returns: `(inv, delta, qalloc, itheta, mid_gain, side_gain)` for stereo split coding. -#[allow(clippy::too_many_arguments)] -fn decode_theta_stereo( - mode: &CeltMode, - band: usize, - dec: &mut EcDec<'_>, - n: usize, - b: &mut i32, - blocks: usize, - _blocks0: usize, - lm: i32, - fill: &mut u32, - intensity: i32, - remaining_bits: i32, - disable_inv: bool, -) -> (bool, i32, i32, i32, f32, f32) { - let pulse_cap = mode.log_n[band] as i32 + lm * (1 << BITRES); - let offset = (pulse_cap >> 1) - - if n == 2 { - QTHETA_OFFSET_TWOPHASE - } else { - QTHETA_OFFSET - }; - let mut qn = compute_qn(n, *b, offset, pulse_cap, true); - if band as i32 >= intensity { - qn = 1; - } - let tell_before = dec.tell_frac() as i32; - let mut itheta = 0i32; - let mut inv = false; - if qn != 1 { - if n > 2 { - let p0 = 3i32; - let x0 = qn >> 1; - let ft = p0 * (x0 + 1) + x0; - let fs = dec.decode(ft as u32) as i32; - let x = if fs < (x0 + 1) * p0 { - fs / p0 - } else { - x0 + 1 + (fs - (x0 + 1) * p0) - }; - let (fl, fh) = if x <= x0 { - (p0 * x, p0 * (x + 1)) - } else { - ((x - 1 - x0) + (x0 + 1) * p0, (x - x0) + (x0 + 1) * p0) - }; - dec.update(fl as u32, fh as u32, ft as u32); - itheta = x; - } else { - itheta = dec.dec_uint((qn + 1) as u32) as i32; - } - itheta = (itheta * 16384) / qn; - } else { - if *b > (2 << BITRES) && remaining_bits > (2 << BITRES) { - inv = dec.dec_bit_logp(2); - } - if disable_inv { - inv = false; - } - itheta = 0; - } - let qalloc = dec.tell_frac() as i32 - tell_before; - *b -= qalloc; - if itheta == 0 { - *fill &= (1u32 << blocks) - 1; - return (inv, -16384, qalloc, itheta, 1.0, 0.0); - } - if itheta == 16384 { - *fill &= ((1u32 << blocks) - 1) << blocks; - return (inv, 16384, qalloc, itheta, 0.0, 1.0); - } - let imid = bitexact_cos(itheta); - let iside = bitexact_cos(16384 - itheta); - let delta = frac_mul16((n as i32 - 1) << 7, bitexact_log2tan(iside, imid)); - ( - inv, - delta, - qalloc, - itheta, - (imid as f32) * (1.0 / 32768.0), - (iside as f32) * (1.0 / 32768.0), - ) -} - -/// Decode one stereo band partition (decode-only path). -/// -/// Params: stereo vectors, split context, folding source and per-band allocation state. -/// Returns: collapse mask for this stereo band. -#[allow(clippy::too_many_arguments)] -fn quant_band_stereo_decode( - mode: &CeltMode, - x: &mut [f32], - y: &mut [f32], - band_idx: usize, - mut b: i32, - blocks_quant: usize, - blocks_orig: usize, - lowband_x: Option<&[f32]>, - lm: i32, - mut fill: u32, - spread: i32, - disable_inv: bool, - dec: &mut EcDec<'_>, - seed: &mut u32, - remaining_bits: &mut i32, - intensity: i32, - recombine: usize, - time_divide: usize, - long_blocks: bool, - mut mid_history_out: Option<&mut [f32]>, - trace_this_packet: bool, - packet_idx: usize, -) -> u32 { - let n = x.len(); - let orig_fill = fill; - if n == 1 { - let mut decode_sign = |v: &mut f32| { - if *remaining_bits >= (1 << BITRES) { - let sign = dec.dec_bits(1); - *remaining_bits -= 1 << BITRES; - *v = if sign != 0 { -1.0 } else { 1.0 }; - } else { - *v = 1.0; - } - }; - decode_sign(&mut x[0]); - decode_sign(&mut y[0]); - if let Some(dst) = mid_history_out.as_deref_mut() { - if !dst.is_empty() { - // Match libopus quant_band_n1(): preserve X[0] as lowband history. - dst[0] = x[0]; - } - } - return 1; - } - - let (inv, delta, qalloc, itheta, mid_gain, side_gain) = decode_theta_stereo( - mode, - band_idx, - dec, - n, - &mut b, - blocks_orig, - blocks_orig, - lm, - &mut fill, - intensity, - *remaining_bits, - disable_inv, - ); - if trace_this_packet && (band_idx == 12 || band_idx >= 18) { - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"bea564\",\"runId\":\"run-stereo-probe\",\"hypothesisId\":\"H7\",\"location\":\"crates/opus-decoder/src/celt/bands.rs:260\",\"message\":\"stereo_theta_decode\",\"data\":{{\"packet_idx\":{},\"band\":{},\"itheta\":{},\"qalloc\":{},\"delta\":{},\"inv\":{}}},\"timestamp\":{}}}\n", - packet_idx, band_idx, itheta, qalloc, delta, inv, ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - if trace_this_packet && band_idx >= 9 { - // #region agent log - debug_trace!( - "R pkt{} stereo_theta band {} itheta={} qalloc={} delta={} inv={} b_after_theta={} fill=0x{:x} mid_gain={:.6} side_gain={:.6}", - packet_idx, - band_idx, - itheta, - qalloc, - delta, - inv, - b, - fill, - mid_gain, - side_gain - ); - // #endregion - } - let fill_mid = transform_fill_for_quant(fill, blocks_orig, recombine, time_divide); - let fill_side_seed = if blocks_orig >= 32 { - 0 - } else { - fill >> blocks_orig - }; - let fill_side = transform_fill_for_quant(fill_side_seed, blocks_orig, recombine, time_divide); - let orig_fill_mid = transform_fill_for_quant(orig_fill, blocks_orig, recombine, time_divide); - - if n == 2 { - let mut mbits = b; - let mut sbits = 0; - if itheta != 0 && itheta != 16384 { - sbits = 1 << BITRES; - } - mbits -= sbits; - *remaining_bits -= qalloc + sbits; - let mut sign = 0i32; - if sbits != 0 { - sign = dec.dec_bits(1) as i32; - } - let sign = 1 - 2 * sign; - if itheta > 8192 { - let cm = quant_partition_mono( - mode, - y, - band_idx, - mbits, - blocks_quant, - lowband_x, - lm, - 1.0, - orig_fill_mid, - spread, - dec, - seed, - remaining_bits, - trace_this_packet, - packet_idx, - ); - apply_quant_band_post_tf(y, n, blocks_quant, recombine, time_divide, long_blocks); - x[0] = -(sign as f32) * y[1]; - x[1] = (sign as f32) * y[0]; - if let Some(dst) = mid_history_out.as_deref_mut() { - let scale = (n as f32).sqrt(); - for (d, s) in dst.iter_mut().zip(y.iter()) { - *d = *s * scale; - } - } - let xl0 = mid_gain * x[0]; - let xl1 = mid_gain * x[1]; - let yr0 = side_gain * y[0]; - let yr1 = side_gain * y[1]; - x[0] = xl0 - yr0; - y[0] = xl0 + yr0; - x[1] = xl1 - yr1; - y[1] = xl1 + yr1; - if inv { - y[0] = -y[0]; - y[1] = -y[1]; - } - return post_tf_collapse_mask(cm, blocks_quant, recombine, time_divide); - } - let cm = quant_partition_mono( - mode, - x, - band_idx, - mbits, - blocks_quant, - lowband_x, - lm, - 1.0, - orig_fill_mid, - spread, - dec, - seed, - remaining_bits, - trace_this_packet, - packet_idx, - ); - apply_quant_band_post_tf(x, n, blocks_quant, recombine, time_divide, long_blocks); - if let Some(dst) = mid_history_out.as_deref_mut() { - let scale = (n as f32).sqrt(); - for (d, s) in dst.iter_mut().zip(x.iter()) { - *d = *s * scale; - } - } - y[0] = -(sign as f32) * x[1]; - y[1] = (sign as f32) * x[0]; - let xl0 = mid_gain * x[0]; - let xl1 = mid_gain * x[1]; - let yr0 = side_gain * y[0]; - let yr1 = side_gain * y[1]; - x[0] = xl0 - yr0; - y[0] = xl0 + yr0; - x[1] = xl1 - yr1; - y[1] = xl1 + yr1; - if inv { - y[0] = -y[0]; - y[1] = -y[1]; - } - return post_tf_collapse_mask(cm, blocks_quant, recombine, time_divide); - } - - let mut mbits = 0.max(b.min((b - delta) / 2)); - let mut sbits = b - mbits; - if trace_this_packet && band_idx >= 9 { - // #region agent log - debug_trace!( - "R pkt{} stereo_split band {} mbits={} sbits={} rem_bits={} fill=0x{:x}", - packet_idx, - band_idx, - mbits, - sbits, - *remaining_bits, - fill - ); - // #endregion - } - *remaining_bits -= qalloc; - let mut rebalance = *remaining_bits; - let cm = if mbits >= sbits { - let mut cm = quant_partition_mono( - mode, - x, - band_idx, - mbits, - blocks_quant, - lowband_x, - lm, - 1.0, - fill_mid, - spread, - dec, - seed, - remaining_bits, - trace_this_packet, - packet_idx, - ); - rebalance = mbits - (rebalance - *remaining_bits); - if rebalance > (3 << BITRES) && itheta != 0 { - sbits += rebalance - (3 << BITRES); - } - cm |= quant_partition_mono( - mode, - y, - band_idx, - sbits, - blocks_quant, - None, - lm, - side_gain, - fill_side, - spread, - dec, - seed, - remaining_bits, - trace_this_packet, - packet_idx, - ); - apply_quant_band_post_tf(x, n, blocks_quant, recombine, time_divide, long_blocks); - apply_quant_band_post_tf(y, n, blocks_quant, recombine, time_divide, long_blocks); - cm - } else { - let mut cm = quant_partition_mono( - mode, - y, - band_idx, - sbits, - blocks_quant, - None, - lm, - side_gain, - fill_side, - spread, - dec, - seed, - remaining_bits, - trace_this_packet, - packet_idx, - ); - rebalance = sbits - (rebalance - *remaining_bits); - if rebalance > (3 << BITRES) && itheta != 16384 { - mbits += rebalance - (3 << BITRES); - } - cm |= quant_partition_mono( - mode, - x, - band_idx, - mbits, - blocks_quant, - lowband_x, - lm, - 1.0, - fill_mid, - spread, - dec, - seed, - remaining_bits, - trace_this_packet, - packet_idx, - ); - apply_quant_band_post_tf(x, n, blocks_quant, recombine, time_divide, long_blocks); - apply_quant_band_post_tf(y, n, blocks_quant, recombine, time_divide, long_blocks); - cm - }; - if let Some(dst) = mid_history_out.as_deref_mut() { - let scale = (n as f32).sqrt(); - for (d, s) in dst.iter_mut().zip(x.iter()) { - *d = *s * scale; - } - } - stereo_merge_float(x, y, mid_gain); - if inv { - for v in y.iter_mut() { - *v = -*v; - } - } - post_tf_collapse_mask(cm, blocks_quant, recombine, time_divide) -} - -/// Decode one mono partition with recursive split. -/// -/// Params: mode, target partition, band index and recursive coding context. -/// Returns: collapse mask for this partition. -#[allow(clippy::too_many_arguments)] -fn quant_partition_mono( - mode: &CeltMode, - x: &mut [f32], - band_idx: usize, - mut b: i32, - mut blocks: usize, - lowband: Option<&[f32]>, - mut lm: i32, - gain: f32, - mut fill: u32, - spread: i32, - dec: &mut EcDec<'_>, - seed: &mut u32, - remaining_bits: &mut i32, - trace_this_packet: bool, - packet_idx: usize, -) -> u32 { - let n0 = x.len(); - let blocks0 = blocks; - if n0 == 1 { - if *remaining_bits >= (1 << BITRES) { - let sign = dec.dec_bits(1); - *remaining_bits -= 1 << BITRES; - x[0] = if sign != 0 { -1.0 } else { 1.0 }; - } else { - x[0] = 1.0; - } - if trace_this_packet && band_idx == 0 { - // #region agent log - debug_trace!( - "R pkt{} qpart n1: rem_bits={} tell={} x0={}", - packet_idx, - *remaining_bits, - dec.tell_frac(), - x[0] - ); - // #endregion - } - return 1; - } - if trace_this_packet && (band_idx == 12 || band_idx >= 18) { - // #region agent log - debug_trace!( - "R pkt{} qpart enter: N={} b={} B={} lm={} rem_bits={} tell={}", - packet_idx, - n0, - b, - blocks, - lm, - *remaining_bits, - dec.tell_frac() - ); - // #endregion - } - let cache_row = ((lm + 1) as usize) - .saturating_mul(mode.nb_ebands) - .saturating_add(band_idx); - let do_split = if lm != -1 && n0 > 2 && cache_row < mode.cache.index.len() { - let base = mode.cache.index[cache_row]; - if base >= 0 { - let cache = &mode.cache.bits[base as usize..]; - b > cache[cache[0] as usize] as i32 + 12 - } else { - false - } - } else { - false - }; - if packet_idx == 6 && band_idx == 8 { - let data = format!( - "{{\"packet_idx\":{},\"band\":{},\"stage\":\"entry\",\"n\":{},\"b\":{},\"blocks\":{},\"lm\":{},\"do_split\":{},\"remaining_bits\":{},\"tell\":{}}}", - packet_idx, - band_idx, - n0, - b, - blocks, - lm, - do_split, - *remaining_bits, - dec.tell_frac() - ); - // #region agent log - append_anti_collapse_debug_log( - "run-pkt6-quant-decision-v1", - "H89", - "crates/opus-decoder/src/celt/bands.rs:quant_partition_mono", - "rust_pkt6_band8_qpart_entry", - &data, - ); - // #endregion - } - if do_split { - let n = n0 >> 1; - let (x_lo, x_hi) = x.split_at_mut(n); - lm -= 1; - if blocks == 1 { - fill = (fill & 1) | (fill << 1); - } - blocks = (blocks + 1) >> 1; - let (mut delta, qalloc, itheta, imid, iside) = decode_theta_mono( - mode, band_idx, dec, n, &mut b, blocks, blocks0, lm, &mut fill, - ); - let mid = (imid as f32) * (1.0 / 32768.0); - let side = (iside as f32) * (1.0 / 32768.0); - if blocks0 > 1 && (itheta & 0x3fff) != 0 { - if itheta > 8192 { - delta -= delta >> (4 - lm); - } else { - delta = 0.min(delta + (((n as i32) << BITRES) >> (5 - lm))); - } - } - let mut mbits = 0.max(b.min((b - delta) / 2)); - let mut sbits = b - mbits; - *remaining_bits -= qalloc; - if packet_idx == 6 && band_idx == 8 { - let data = format!( - "{{\"packet_idx\":{},\"band\":{},\"stage\":\"split\",\"n\":{},\"mbits\":{},\"sbits\":{},\"delta\":{},\"qalloc\":{},\"itheta\":{},\"blocks\":{},\"lm\":{},\"remaining_bits\":{},\"tell\":{}}}", - packet_idx, - band_idx, - n0, - mbits, - sbits, - delta, - qalloc, - itheta, - blocks, - lm, - *remaining_bits, - dec.tell_frac() - ); - // #region agent log - append_anti_collapse_debug_log( - "run-pkt6-quant-decision-v1", - "H89", - "crates/opus-decoder/src/celt/bands.rs:quant_partition_mono", - "rust_pkt6_band8_qpart_split", - &data, - ); - // #endregion - } - if trace_this_packet && (band_idx == 12 || band_idx >= 18) { - // #region agent log - debug_trace!( - "R pkt{} qpart split: N={} n={} qalloc={} itheta={} delta={} mbits={} sbits={} rem_bits={} tell={}", - packet_idx, - n0, - n, - qalloc, - itheta, - delta, - mbits, - sbits, - *remaining_bits, - dec.tell_frac() - ); - // #endregion - } - let next_lowband2 = lowband.map(|lb| if lb.len() > n { &lb[n..] } else { &lb[0..0] }); - let mut rebalance = *remaining_bits; - if mbits >= sbits { - let mut cm = quant_partition_mono( - mode, - x_lo, - band_idx, - mbits, - blocks, - lowband.map(|lb| &lb[..lb.len().min(n)]), - lm, - gain * mid, - fill, - spread, - dec, - seed, - remaining_bits, - trace_this_packet, - packet_idx, - ); - rebalance = mbits - (rebalance - *remaining_bits); - if rebalance > (3 << BITRES) && itheta != 0 { - sbits += rebalance - (3 << BITRES); - } - cm |= quant_partition_mono( - mode, - x_hi, - band_idx, - sbits, - blocks, - next_lowband2, - lm, - gain * side, - fill >> blocks, - spread, - dec, - seed, - remaining_bits, - trace_this_packet, - packet_idx, - ) << (blocks0 >> 1); - return cm; - } - - let mut cm = quant_partition_mono( - mode, - x_hi, - band_idx, - sbits, - blocks, - next_lowband2, - lm, - gain * side, - fill >> blocks, - spread, - dec, - seed, - remaining_bits, - trace_this_packet, - packet_idx, - ) << (blocks0 >> 1); - rebalance = sbits - (rebalance - *remaining_bits); - if rebalance > (3 << BITRES) && itheta != 16384 { - mbits += rebalance - (3 << BITRES); - } - cm |= quant_partition_mono( - mode, - x_lo, - band_idx, - mbits, - blocks, - lowband.map(|lb| &lb[..lb.len().min(n)]), - lm, - gain * mid, - fill, - spread, - dec, - seed, - remaining_bits, - trace_this_packet, - packet_idx, - ); - return cm; - } - - let mut q = rate::bits2pulses(mode, band_idx, lm, b.max(0)); - let mut curr_bits = rate::pulses2bits(mode, band_idx, lm, q); - *remaining_bits -= curr_bits; - while *remaining_bits < 0 && q > 0 { - *remaining_bits += curr_bits; - q -= 1; - curr_bits = rate::pulses2bits(mode, band_idx, lm, q); - *remaining_bits -= curr_bits; - } - if trace_this_packet && (band_idx == 12 || band_idx >= 18) { - // #region agent log - debug_trace!( - "R pkt{} qpart nosplit: N={} b={} q={} curr_bits={} rem_bits={} tell={}", - packet_idx, - n0, - b, - q, - curr_bits, - *remaining_bits, - dec.tell_frac() - ); - // #endregion - } - if q != 0 { - let k = rate::get_pulses(q); - if packet_idx == 6 && band_idx == 8 { - let data = format!( - "{{\"packet_idx\":{},\"band\":{},\"stage\":\"nosplit_q\",\"n\":{},\"q\":{},\"k\":{},\"curr_bits\":{},\"blocks\":{},\"lm\":{},\"remaining_bits\":{},\"tell\":{}}}", - packet_idx, - band_idx, - n0, - q, - k, - curr_bits, - blocks, - lm, - *remaining_bits, - dec.tell_frac() - ); - // #region agent log - append_anti_collapse_debug_log( - "run-pkt6-quant-decision-v1", - "H89", - "crates/opus-decoder/src/celt/bands.rs:quant_partition_mono", - "rust_pkt6_band8_qpart_nosplit_q", - &data, - ); - // #endregion - } - return vq::alg_unquant(x, k, spread, blocks, band_idx, false, dec, gain).collapse_mask - as u32; - } - - let cm_mask = if blocks >= 32 { - u32::MAX - } else { - (1u32 << blocks) - 1 - }; - let fill_masked = fill & cm_mask; - if trace_this_packet && (band_idx == 12 || band_idx >= 18) { - let (has_lowband, lowband_abs, lowband_len) = if let Some(lb) = lowband { - (true, lb.iter().map(|v| v.abs()).sum::(), lb.len()) - } else { - (false, -1.0, 0usize) - }; - // #region agent log - debug_trace!( - "R pkt{} qpart q0: N={} b={} B={} fill=0x{:x} has_lowband={} lowband_len={} lowband_abs={:.6} gain={:.6}", - packet_idx, - n0, - b, - blocks, - fill_masked, - has_lowband, - lowband_len, - lowband_abs, - gain - ); - // #endregion - } - if fill_masked == 0 { - x.fill(0.0); - return 0; - } - if let Some(lb) = lowband { - if lb.is_empty() { - for v in x.iter_mut() { - *seed = celt_lcg_rand(*seed); - *v = ((*seed as i32) >> 20) as f32; - } - vq::renormalise_vector(x, gain); - return cm_mask; - } - for (j, v) in x.iter_mut().enumerate() { - *seed = celt_lcg_rand(*seed); - let noise = if (*seed & 0x8000) != 0 { - 1.0 / 256.0 - } else { - -1.0 / 256.0 - }; - *v = lb[j % lb.len()] + noise; - } - vq::renormalise_vector(x, gain); - if blocks < 32 && fill_masked != cm_mask { - let n_per_block = n0 / blocks.max(1); - for b in 0..blocks { - if (fill_masked & (1u32 << b)) == 0 { - let start = b * n_per_block; - let end = (start + n_per_block).min(n0); - x[start..end].fill(0.0); - } - } - } - return fill_masked; - } - for v in x.iter_mut() { - *seed = celt_lcg_rand(*seed); - *v = ((*seed as i32) >> 20) as f32; - } - vq::renormalise_vector(x, gain); - cm_mask -} - -/// Decode all CELT bands in mono decode-only mode. -/// -/// Params: CELT mode, active band range, output normalized spectrum `x`, per-band -/// pulse allocation, transient flag, spread mode, TF flags, total bit budget, -/// running balance, coded band limit, decoder state and random seed. -/// Returns: collapse mask per band. -#[allow(clippy::too_many_arguments)] -pub(crate) fn quant_all_bands_mono( - mode: &CeltMode, - start: usize, - end: usize, - x: &mut [f32], - pulses: &[i32], - short_blocks: bool, - spread: i32, - tf_res: &[i32], - total_bits_q: i32, - mut balance: i32, - coded_bands: usize, - lm: usize, - dec: &mut EcDec<'_>, - seed: &mut u32, - packet_idx: usize, - frame_call_idx: usize, -) -> Vec { - let trace_target = None; - let trace_this_packet = trace_target == Some(packet_idx); - let m = 1usize << lm; - let mut masks = vec![0u8; mode.nb_ebands]; - let mut lowband_offset = 0usize; - let mut update_lowband = true; - let norm_offset = m * mode.e_bands[start] as usize; - let mut norm_hist = vec![0.0f32; x.len()]; - for i in start..end { - let tell = dec.tell_frac() as i32; - if i != start { - balance -= tell; - } - let remaining_bits = total_bits_q - tell - 1; - let b = if i < coded_bands { - let curr_balance = balance / ((coded_bands - i).min(3) as i32); - (pulses[i] + curr_balance) - .clamp(0, 16_383) - .min(remaining_bits + 1) - } else { - 0 - }; - let band_start = m * mode.e_bands[i] as usize; - let band_end = m * mode.e_bands[i + 1] as usize; - let n = band_end - band_start; - if i != start && (update_lowband || lowband_offset == 0) { - lowband_offset = i; - } - let frame_blocks = if short_blocks { m } else { 1 }; - let long_blocks = frame_blocks == 1; - let mut b_blocks = frame_blocks; - let spread_eff = spread.clamp(0, 3); - let tf_change = tf_res[i]; - let mut n_b = n / b_blocks.max(1); - let n_b_initial = n_b; - let mut effective_lowband = None::; - let mut x_cm = if b_blocks >= 32 { - u32::MAX - } else { - (1u32 << b_blocks) - 1 - }; - if lowband_offset != 0 && (spread != SPREAD_AGGRESSIVE || frame_blocks > 1 || tf_change < 0) - { - let lowband_start = m * mode.e_bands[lowband_offset] as usize; - let effective = lowband_start.saturating_sub(norm_offset + n); - let fold_base = effective + norm_offset; - let fold_limit = fold_base + n; - let mut fold_start = lowband_offset; - while fold_start > 0 { - fold_start -= 1; - if (m * mode.e_bands[fold_start] as usize) <= fold_base { - break; - } - } - let mut fold_end = lowband_offset; - while fold_end < i && (m * mode.e_bands[fold_end] as usize) < fold_limit { - fold_end += 1; - } - x_cm = 0; - for fold_i in fold_start..fold_end { - x_cm |= masks[fold_i] as u32; - } - effective_lowband = Some(effective); - } - let mut fill = x_cm; - let recombine = tf_change.max(0) as usize; - for _ in 0..recombine { - let lo = (fill & 0xF) as usize; - let hi = ((fill >> 4) & 0xF) as usize; - fill = BIT_INTERLEAVE_TABLE[lo] | (BIT_INTERLEAVE_TABLE[hi] << 2); - } - b_blocks >>= recombine; - n_b <<= recombine; - let mut tf_for_blocks = tf_change; - let mut time_divide = 0usize; - while (n_b & 1) == 0 && tf_for_blocks < 0 { - if b_blocks < 32 { - fill |= fill << b_blocks; - } else { - fill = u32::MAX; - } - b_blocks <<= 1; - n_b >>= 1; - tf_for_blocks += 1; - time_divide += 1; - } - if trace_this_packet { - debug_trace!( - "R pkt{} frame_call_idx={} band {} params: b={} N={} B={} fill=0x{:x} lowband_offset={} update_lowband={} spread_eff={} tf_change={}", - packet_idx, - frame_call_idx, - i, - b, - n, - b_blocks, - fill, - lowband_offset, - update_lowband, - spread_eff, - tf_change - ); - } - let mut remaining_band_bits = remaining_bits; - let lowband_range = effective_lowband.map(|eff| { - let src_start = norm_offset + eff; - (src_start, src_start + n) - }); - if trace_this_packet { - if let Some((src_start, src_end)) = lowband_range { - debug_trace!( - "R pkt{} band {} fold: lowband_start={} lowband_end={} B={} fill_in=0x{:x}", - packet_idx, - i, - src_start, - src_end, - b_blocks, - fill - ); - } else { - debug_trace!( - "R pkt{} band {} fold: lowband_start=-1 lowband_end=-1 B={} fill_in=0x{:x}", - packet_idx, - i, - b_blocks, - fill - ); - } - } - let (_x_before, x_after) = x.split_at_mut(band_start); - let lowband = - lowband_range.and_then(|(src_start, src_end)| norm_hist.get(src_start..src_end)); - let mut lowband_scratch = None; - let lowband_for_quant = if let Some(lb) = lowband { - if recombine > 0 || ((n_b_initial & 1) == 0 && tf_change < 0) || frame_blocks > 1 { - lowband_scratch = Some(transform_lowband_for_decode( - lb, - n, - frame_blocks, - recombine, - tf_change, - long_blocks, - )); - lowband_scratch.as_deref() - } else { - Some(lb) - } - } else { - None - }; - if packet_idx == 6 && i == 8 { - let (raw_abs, raw_first8) = if let Some(lb) = lowband { - let abs: f32 = lb.iter().map(|v| v.abs()).sum(); - ( - abs, - [ - lb.get(0).copied().unwrap_or(0.0), - lb.get(1).copied().unwrap_or(0.0), - lb.get(2).copied().unwrap_or(0.0), - lb.get(3).copied().unwrap_or(0.0), - lb.get(4).copied().unwrap_or(0.0), - lb.get(5).copied().unwrap_or(0.0), - lb.get(6).copied().unwrap_or(0.0), - lb.get(7).copied().unwrap_or(0.0), - ], - ) - } else { - (0.0, [0.0; 8]) - }; - let (prep_abs, prep_first8) = if let Some(lb) = lowband_for_quant { - let abs: f32 = lb.iter().map(|v| v.abs()).sum(); - ( - abs, - [ - lb.get(0).copied().unwrap_or(0.0), - lb.get(1).copied().unwrap_or(0.0), - lb.get(2).copied().unwrap_or(0.0), - lb.get(3).copied().unwrap_or(0.0), - lb.get(4).copied().unwrap_or(0.0), - lb.get(5).copied().unwrap_or(0.0), - lb.get(6).copied().unwrap_or(0.0), - lb.get(7).copied().unwrap_or(0.0), - ], - ) - } else { - (0.0, [0.0; 8]) - }; - let data = format!( - "{{\"packet_idx\":{},\"band\":{},\"raw_lowband_abs\":{},\"raw_lowband_first8\":[{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9}],\"prepared_lowband_abs\":{},\"prepared_lowband_first8\":[{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9}],\"recombine\":{},\"tf_change\":{},\"frame_blocks\":{}}}", - packet_idx, - i, - raw_abs, - raw_first8[0], - raw_first8[1], - raw_first8[2], - raw_first8[3], - raw_first8[4], - raw_first8[5], - raw_first8[6], - raw_first8[7], - prep_abs, - prep_first8[0], - prep_first8[1], - prep_first8[2], - prep_first8[3], - prep_first8[4], - prep_first8[5], - prep_first8[6], - prep_first8[7], - recombine, - tf_change, - frame_blocks - ); - // #region agent log - append_anti_collapse_debug_log( - "run-pkt6-lowband-v1", - "H86", - "crates/opus-decoder/src/celt/bands.rs:quant_all_bands_mono", - "rust_pkt6_band8_lowband", - &data, - ); - // #endregion - } - let tell_before = dec.tell_frac(); - let mut cm = quant_partition_mono( - mode, - &mut x_after[..n], - i, - b, - b_blocks, - lowband_for_quant, - lm as i32, - 1.0, - fill, - spread_eff, - dec, - seed, - &mut remaining_band_bits, - trace_this_packet, - packet_idx, - ); - let cm_raw = cm; - if packet_idx == 6 && i == 8 { - let pre_post_tf_abs: f32 = x_after[..n].iter().map(|v| v.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"band\":{},\"stage\":\"after_quant_partition_before_post_tf\",\"abs_sum\":{},\"first8\":[{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9}],\"n\":{},\"b_blocks\":{},\"recombine\":{},\"time_divide\":{}}}", - packet_idx, - i, - pre_post_tf_abs, - x_after.get(0).copied().unwrap_or(0.0), - x_after.get(1).copied().unwrap_or(0.0), - x_after.get(2).copied().unwrap_or(0.0), - x_after.get(3).copied().unwrap_or(0.0), - x_after.get(4).copied().unwrap_or(0.0), - x_after.get(5).copied().unwrap_or(0.0), - x_after.get(6).copied().unwrap_or(0.0), - x_after.get(7).copied().unwrap_or(0.0), - n, - b_blocks, - recombine, - time_divide - ); - // #region agent log - append_anti_collapse_debug_log( - "run-pkt6-quant-stage-v1", - "H88", - "crates/opus-decoder/src/celt/bands.rs:quant_all_bands_mono", - "rust_pkt6_band8_pre_post_tf", - &data, - ); - // #endregion - } - apply_quant_band_post_tf( - &mut x_after[..n], - n, - b_blocks, - recombine, - time_divide, - long_blocks, - ); - cm = post_tf_collapse_mask(cm, b_blocks, recombine, time_divide); - if packet_idx <= 8 || trace_this_packet { - let data = format!( - "{{\"packet_idx\":{},\"band\":{},\"frame_blocks\":{},\"b_blocks\":{},\"recombine\":{},\"time_divide\":{},\"cm_raw\":{},\"cm_post\":{},\"cm_used\":{}}}", - packet_idx, - i, - frame_blocks, - b_blocks, - recombine, - time_divide, - cm_raw, - post_tf_collapse_mask(cm_raw, b_blocks, recombine, time_divide), - cm - ); - // #region agent log - append_anti_collapse_debug_log( - "run-collapse-mask-ab-v1", - "H8", - "crates/opus-decoder/src/celt/bands.rs:quant_all_bands_mono", - "collapse_mask_post_tf_mapping", - &data, - ); - // #endregion - } - if packet_idx == 6 { - let band_abs_sum: f32 = x_after[..n].iter().map(|v| v.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"band\":{},\"n\":{},\"b\":{},\"frame_blocks\":{},\"b_blocks\":{},\"tf_change\":{},\"spread_eff\":{},\"recombine\":{},\"time_divide\":{},\"fill\":{},\"cm\":{},\"band_abs_sum\":{},\"band_first4\":[{:.9},{:.9},{:.9},{:.9}],\"band_last4\":[{:.9},{:.9},{:.9},{:.9}],\"lowband_offset\":{},\"lowband_present\":{}}}", - packet_idx, - i, - n, - b, - frame_blocks, - b_blocks, - tf_change, - spread_eff, - recombine, - time_divide, - fill, - cm, - band_abs_sum, - x_after.get(0).copied().unwrap_or(0.0), - x_after.get(1).copied().unwrap_or(0.0), - x_after.get(2).copied().unwrap_or(0.0), - x_after.get(3).copied().unwrap_or(0.0), - x_after.get(n.saturating_sub(4)).copied().unwrap_or(0.0), - x_after.get(n.saturating_sub(3)).copied().unwrap_or(0.0), - x_after.get(n.saturating_sub(2)).copied().unwrap_or(0.0), - x_after.get(n.saturating_sub(1)).copied().unwrap_or(0.0), - lowband_offset, - lowband_for_quant.is_some() - ); - // #region agent log - append_anti_collapse_debug_log( - "run-pkt6-quant-band-v1", - "H85", - "crates/opus-decoder/src/celt/bands.rs:quant_all_bands_mono", - "rust_pkt6_quant_band_after", - &data, - ); - // #endregion - } - if i + 1 < end { - let scale = (n as f32).sqrt(); - for j in 0..n { - norm_hist[band_start + j] = x_after[j] * scale; - } - } - let tell_after = dec.tell_frac(); - if trace_this_packet { - debug_trace!( - "pkt{} frame_call_idx={} band {} tell: {}->{} ({}bits)", - packet_idx, - frame_call_idx, - i, - tell_before, - tell_after, - tell_after as i32 - tell_before as i32 - ); - debug_trace!( - "R pkt{} frame_call_idx={} band {} cm_out=0x{:x}", - packet_idx, - frame_call_idx, - i, - cm - ); - } - masks[i] = (cm & 0xFF) as u8; - balance += pulses[i] + tell; - update_lowband = b > ((n as i32) << BITRES); - } - masks -} - -/// Decode all CELT bands in stereo decode-only mode. -/// -/// Params: mode, band range, stereo normalized spectra, allocation/TF flags and coder state. -/// Returns: collapse mask per channel-band slot (`2 * nb_ebands`). -#[allow(clippy::too_many_arguments)] -pub(crate) fn quant_all_bands_stereo( - mode: &CeltMode, - start: usize, - end: usize, - x: &mut [f32], - y: &mut [f32], - pulses: &[i32], - short_blocks: bool, - spread: i32, - tf_res: &[i32], - total_bits_q: i32, - mut balance: i32, - coded_bands: usize, - lm: usize, - dec: &mut EcDec<'_>, - seed: &mut u32, - dual_stereo: i32, - intensity: i32, - disable_inv: bool, - packet_idx: usize, - frame_call_idx: usize, -) -> Vec { - let trace_target = None; - let trace_this_packet = trace_target == Some(packet_idx); - let m = 1usize << lm; - let mut masks = vec![0u8; mode.nb_ebands * 2]; - let mut lowband_offset = 0usize; - let mut update_lowband = true; - let norm_offset = m * mode.e_bands[start] as usize; - let mut dual_stereo_on = dual_stereo != 0; - let mut norm_hist_x = vec![0.0f32; x.len()]; - let mut norm_hist_y = vec![0.0f32; y.len()]; - - for i in start..end { - let tell = dec.tell_frac() as i32; - if i != start { - balance -= tell; - } - let remaining_bits = total_bits_q - tell - 1; - let b = if i < coded_bands { - let curr_balance = balance / ((coded_bands - i).min(3) as i32); - (pulses[i] + curr_balance) - .clamp(0, 16_383) - .min(remaining_bits + 1) - } else { - 0 - }; - let band_start = m * mode.e_bands[i] as usize; - let band_end = m * mode.e_bands[i + 1] as usize; - let n = band_end - band_start; - if i != start && (update_lowband || lowband_offset == 0) { - lowband_offset = i; - } - - let frame_blocks = if short_blocks { m } else { 1 }; - let long_blocks = frame_blocks == 1; - let mut b_blocks = frame_blocks; - let spread_eff = spread.clamp(0, 3); - let tf_change = tf_res[i]; - let mut n_b = n / b_blocks.max(1); - let n_b_initial = n_b; - - if i == start + 1 { - special_hybrid_folding_decode( - mode, - &mut norm_hist_x[norm_offset..], - &mut norm_hist_y[norm_offset..], - start, - m, - dual_stereo_on, - ); - } - - let mut effective_lowband = None::; - let mut x_cm = if b_blocks >= 32 { - u32::MAX - } else { - (1u32 << b_blocks) - 1 - }; - let mut y_cm = x_cm; - if lowband_offset != 0 && (spread != SPREAD_AGGRESSIVE || frame_blocks > 1 || tf_change < 0) - { - let lowband_start = m * mode.e_bands[lowband_offset] as usize; - let effective = lowband_start.saturating_sub(norm_offset + n); - let fold_base = effective + norm_offset; - let fold_limit = fold_base + n; - let mut fold_start = lowband_offset; - while fold_start > 0 { - fold_start -= 1; - if (m * mode.e_bands[fold_start] as usize) <= fold_base { - break; - } - } - let mut fold_end = lowband_offset; - while fold_end < i && (m * mode.e_bands[fold_end] as usize) < fold_limit { - fold_end += 1; - } - x_cm = 0; - y_cm = 0; - for fold_i in fold_start..fold_end { - x_cm |= masks[fold_i * 2] as u32; - y_cm |= masks[fold_i * 2 + 1] as u32; - } - effective_lowband = Some(effective); - } - - if dual_stereo_on && i == intensity as usize { - dual_stereo_on = false; - for j in norm_offset..band_start { - norm_hist_x[j] = 0.5 * (norm_hist_x[j] + norm_hist_y[j]); - } - } - - let fill_theta = x_cm | y_cm; - let mut fill = fill_theta; - let mut fill_x = x_cm; - let mut fill_y = y_cm; - let recombine = tf_change.max(0) as usize; - for _ in 0..recombine { - let lo = (fill & 0xF) as usize; - let hi = ((fill >> 4) & 0xF) as usize; - fill = BIT_INTERLEAVE_TABLE[lo] | (BIT_INTERLEAVE_TABLE[hi] << 2); - - let lox = (fill_x & 0xF) as usize; - let hix = ((fill_x >> 4) & 0xF) as usize; - fill_x = BIT_INTERLEAVE_TABLE[lox] | (BIT_INTERLEAVE_TABLE[hix] << 2); - - let loy = (fill_y & 0xF) as usize; - let hiy = ((fill_y >> 4) & 0xF) as usize; - fill_y = BIT_INTERLEAVE_TABLE[loy] | (BIT_INTERLEAVE_TABLE[hiy] << 2); - } - - b_blocks >>= recombine; - n_b <<= recombine; - let mut tf_for_blocks = tf_change; - let mut time_divide = 0usize; - while (n_b & 1) == 0 && tf_for_blocks < 0 { - if b_blocks < 32 { - fill |= fill << b_blocks; - fill_x |= fill_x << b_blocks; - fill_y |= fill_y << b_blocks; - } else { - fill = u32::MAX; - fill_x = u32::MAX; - fill_y = u32::MAX; - } - b_blocks <<= 1; - n_b >>= 1; - tf_for_blocks += 1; - time_divide += 1; - } - - let lowband_range = effective_lowband.map(|eff| { - let src_start = norm_offset + eff; - (src_start, src_start + n) - }); - if trace_this_packet { - // #region agent log - debug_trace!( - "R pkt{} frame_call_idx={} band {} params: b={} N={} lowband_offset={} update_lowband={} spread_eff={} tf_change={} effective_lowband={} x_cm=0x{:x} y_cm=0x{:x} B={}", - packet_idx, - frame_call_idx, - i, - b, - n, - lowband_offset, - update_lowband, - spread_eff, - tf_change, - effective_lowband.map(|v| v as i32).unwrap_or(-1), - x_cm, - y_cm, - b_blocks - ); - // #endregion - } - let (_x_before, x_after) = x.split_at_mut(band_start); - let (_y_before, y_after) = y.split_at_mut(band_start); - - let lowband_x = - lowband_range.and_then(|(src_start, src_end)| norm_hist_x.get(src_start..src_end)); - let lowband_y = - lowband_range.and_then(|(src_start, src_end)| norm_hist_y.get(src_start..src_end)); - - let mut lowband_x_scratch = None; - let mut lowband_y_scratch = None; - let lowband_x_for_quant = if let Some(lb) = lowband_x { - if recombine > 0 || ((n_b_initial & 1) == 0 && tf_change < 0) || frame_blocks > 1 { - lowband_x_scratch = Some(transform_lowband_for_decode( - lb, - n, - frame_blocks, - recombine, - tf_change, - long_blocks, - )); - lowband_x_scratch.as_deref() - } else { - Some(lb) - } - } else { - None - }; - let lowband_y_for_quant = if let Some(lb) = lowband_y { - if recombine > 0 || ((n_b_initial & 1) == 0 && tf_change < 0) || frame_blocks > 1 { - lowband_y_scratch = Some(transform_lowband_for_decode( - lb, - n, - frame_blocks, - recombine, - tf_change, - long_blocks, - )); - lowband_y_scratch.as_deref() - } else { - Some(lb) - } - } else { - None - }; - - let mut remaining_band_bits = remaining_bits; - let tell_before = dec.tell_frac(); - let (cm_l, cm_r) = if dual_stereo_on { - let cmx_raw = quant_partition_mono( - mode, - &mut x_after[..n], - i, - b / 2, - b_blocks, - lowband_x_for_quant, - lm as i32, - 1.0, - fill_x, - spread_eff, - dec, - seed, - &mut remaining_band_bits, - trace_this_packet, - packet_idx, - ); - let cmy_raw = quant_partition_mono( - mode, - &mut y_after[..n], - i, - b / 2, - b_blocks, - lowband_y_for_quant, - lm as i32, - 1.0, - fill_y, - spread_eff, - dec, - seed, - &mut remaining_band_bits, - trace_this_packet, - packet_idx, - ); - apply_quant_band_post_tf( - &mut x_after[..n], - n, - b_blocks, - recombine, - time_divide, - long_blocks, - ); - apply_quant_band_post_tf( - &mut y_after[..n], - n, - b_blocks, - recombine, - time_divide, - long_blocks, - ); - let cmx = post_tf_collapse_mask(cmx_raw, b_blocks, recombine, time_divide); - let cmy = post_tf_collapse_mask(cmy_raw, b_blocks, recombine, time_divide); - if i + 1 < end { - let scale = (n as f32).sqrt(); - for j in 0..n { - norm_hist_x[band_start + j] = x_after[j] * scale; - norm_hist_y[band_start + j] = y_after[j] * scale; - } - } - (cmx, cmy) - } else { - let mut mid_hist = vec![0.0f32; n]; - if trace_this_packet && (i == 12 || i >= 18) { - // #region agent log - let log_path = std::env::current_dir() - .unwrap_or_else(|_| std::path::PathBuf::from(".")) - .join(".cursor") - .join("debug-bea564.log"); - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(log_path) - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"bea564\",\"runId\":\"run-tv01-tv10-debug1\",\"hypothesisId\":\"H2\",\"location\":\"crates/opus-decoder/src/celt/bands.rs:quant_all_bands_stereo\",\"message\":\"stereo_band_entry\",\"data\":{{\"packet_idx\":{},\"band\":{},\"n\":{},\"b\":{},\"b_blocks\":{},\"frame_blocks\":{},\"remaining_band_bits\":{},\"intensity\":{},\"dual_stereo_on\":{},\"tf_change\":{},\"recombine\":{},\"time_divide\":{},\"fill_theta\":{},\"fill_tf\":{}}},\"timestamp\":{}}}\n", - packet_idx, - i, - n, - b, - b_blocks, - frame_blocks, - remaining_band_bits, - intensity, - dual_stereo_on, - tf_change, - recombine, - time_divide, - fill_theta, - fill, - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - let cm = quant_band_stereo_decode( - mode, - &mut x_after[..n], - &mut y_after[..n], - i, - b, - b_blocks, - frame_blocks, - lowband_x_for_quant.as_deref(), - lm as i32, - fill_theta, - spread_eff, - disable_inv, - dec, - seed, - &mut remaining_band_bits, - intensity, - recombine, - time_divide, - long_blocks, - Some(&mut mid_hist), - trace_this_packet, - packet_idx, - ); - if trace_this_packet && (i == 12 || i >= 18) { - // #region agent log - let log_path = std::env::current_dir() - .unwrap_or_else(|_| std::path::PathBuf::from(".")) - .join(".cursor") - .join("debug-bea564.log"); - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(log_path) - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"bea564\",\"runId\":\"run-tv01-tv10-debug1\",\"hypothesisId\":\"H3\",\"location\":\"crates/opus-decoder/src/celt/bands.rs:quant_all_bands_stereo\",\"message\":\"stereo_band_exit\",\"data\":{{\"packet_idx\":{},\"band\":{},\"cm\":{},\"remaining_band_bits\":{}}},\"timestamp\":{}}}\n", - packet_idx, i, cm, remaining_band_bits, ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - if i + 1 < end { - norm_hist_x[band_start..band_start + n].copy_from_slice(&mid_hist); - } - (cm, cm) - }; - - let tell_after = dec.tell_frac(); - if trace_this_packet { - debug_trace!( - "pkt{} frame_call_idx={} stereo band {} tell: {}->{} ({}bits) dual_stereo={}", - packet_idx, - frame_call_idx, - i, - tell_before, - tell_after, - tell_after as i32 - tell_before as i32, - dual_stereo_on - ); - // #region agent log - debug_trace!( - "R pkt{} frame_call_idx={} band {} cm_out: x=0x{:x} y=0x{:x}", - packet_idx, - frame_call_idx, - i, - cm_l, - cm_r - ); - // #endregion - } - masks[i * 2] = (cm_l & 0xFF) as u8; - masks[i * 2 + 1] = (cm_r & 0xFF) as u8; - balance += pulses[i] + tell; - update_lowband = b > ((n as i32) << BITRES); - } - masks -} - -/// De-normalize normalized bands to MDCT bins using decoded energies. -/// -/// Params: mode, normalized `x`, output `freq`, decoded `band_loge`, band range, -/// and `silence` flag forcing zero spectrum output. -/// Returns: nothing. -pub(crate) fn denormalise_bands( - mode: &CeltMode, - x: &[f32], - freq: &mut [f32], - band_loge: &[f32], - start: usize, - end: usize, - lm: usize, - silence: bool, -) { - if silence { - freq.fill(0.0); - return; - } - let m = 1usize << lm; - for i in start..end { - let j0 = m * mode.e_bands[i] as usize; - let j1 = m * mode.e_bands[i + 1] as usize; - let lg = band_loge[i] + quant_bands::e_means()[i]; - let g = 2.0f32.powf(lg.max(-32.0)); - for j in j0..j1 { - freq[j] = x[j] * g; - } - } -} diff --git a/vendor/opus-decoder/src/celt/cwrs.rs b/vendor/opus-decoder/src/celt/cwrs.rs deleted file mode 100644 index 7a36366..0000000 --- a/vendor/opus-decoder/src/celt/cwrs.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! CELT combinatorial PVQ pulse decoding (`cwrs.c`) for decode path. - -use crate::entropy::EcDec; - -/// Result of decoding one CWRS PVQ codeword. -#[derive(Debug, Clone)] -pub(crate) struct PulseDecode { - pub yy: u32, - pub pulses: Vec, - pub nc: u32, - pub index: u32, -} - -/// Compute U(n, k) row up to `k+1` and return V(n, k). -/// -/// Params: `n` dimensions, `k` pulses. -/// Returns: `(u_row, v_nk)` where `u_row[i] = U(n, i)`. -fn ncwrs_urow(n: usize, k: usize) -> (Vec, u32) { - let len = k + 2; - let mut u = vec![0u32; len]; - u[0] = 0; - u[1] = 1; - for (idx, item) in u.iter_mut().enumerate().skip(2) { - *item = ((idx as u32) << 1) - 1; - } - for _ in 2..n { - unext(&mut u[1..], 1); - } - let v = u[k].wrapping_add(u[k + 1]); - (u, v) -} - -/// Advance one recurrence row for U-table generation. -/// -/// Params: mutable row slice and first base value. -/// Returns: nothing. -fn unext(u: &mut [u32], mut u0: u32) { - if u.len() < 2 { - return; - } - for j in 1..u.len() { - let u1 = u[j].wrapping_add(u[j - 1]).wrapping_add(u0); - u[j - 1] = u0; - u0 = u1; - } - let last = u.len() - 1; - u[last] = u0; -} - -/// Step recurrence backwards for one dimension removal. -/// -/// Params: mutable row slice and first base value. -/// Returns: nothing. -fn uprev(u: &mut [u32], mut u0: u32) { - if u.len() < 2 { - return; - } - for j in 1..u.len() { - let u1 = u[j].wrapping_sub(u[j - 1]).wrapping_sub(u0); - u[j - 1] = u0; - u0 = u1; - } - let last = u.len() - 1; - u[last] = u0; -} - -/// Decode CWRS index into signed pulse vector. -/// -/// Params: `n` dimensions, `k` pulses, index `idx`, and mutable U-row scratch. -/// Returns: `(sum_of_squares, decoded_vector)`. -fn cwrsi(n: usize, mut k: usize, mut idx: u32, u: &mut [u32]) -> (u32, Vec) { - let mut yy = 0u32; - let mut y = vec![0i32; n]; - for yj in y.iter_mut().take(n) { - let p = u[k + 1]; - let neg = idx >= p; - if neg { - idx = idx.wrapping_sub(p); - } - let yk = k; - let mut cur = u[k]; - while cur > idx { - k -= 1; - cur = u[k]; - } - idx = idx.wrapping_sub(cur); - let mut val = (yk - k) as i32; - if neg { - val = -val; - } - *yj = val; - yy = yy.wrapping_add((val * val) as u32); - uprev(&mut u[..=k + 1], 0); - } - (yy, y) -} - -/// Decode PVQ pulses from range coder. -/// -/// Params: range decoder, `n` dimensions and `k` pulses. -/// Returns: `(sum_of_squares, decoded_signed_pulses)`. -pub(crate) fn decode_pulses(dec: &mut EcDec<'_>, n: usize, k: usize) -> PulseDecode { - if n == 0 || k == 0 { - return PulseDecode { - yy: 0, - pulses: vec![0; n], - nc: 0, - index: 0, - }; - } - let (mut u, nc) = ncwrs_urow(n, k); - let idx = dec.dec_uint(nc.max(2)); - let (yy, pulses) = cwrsi(n, k, idx, &mut u); - PulseDecode { - yy, - pulses, - nc, - index: idx, - } -} diff --git a/vendor/opus-decoder/src/celt/kiss_fft.rs b/vendor/opus-decoder/src/celt/kiss_fft.rs deleted file mode 100644 index aba3486..0000000 --- a/vendor/opus-decoder/src/celt/kiss_fft.rs +++ /dev/null @@ -1,191 +0,0 @@ -#![allow(dead_code)] - -//! Minimal CELT FFT primitives for decoder path. -//! -//! This module intentionally implements only the inverse transform behavior -//! needed by the decoder. It follows the libopus scaling convention where the -//! inverse FFT is unscaled and the forward path applies `1/N`. - -use core::f32::consts::PI; - -/// Complex number used by CELT FFT/MDCT primitives. -#[derive(Debug, Clone, Copy, PartialEq)] -pub(crate) struct Complex32 { - /// Real component. - pub re: f32, - /// Imaginary component. - pub im: f32, -} - -impl Complex32 { - /// Create a complex value from real and imaginary parts. - /// - /// Params: `re` is the real component, `im` is the imaginary component. - /// Returns: a new `Complex32`. - pub const fn new(re: f32, im: f32) -> Self { - Self { re, im } - } -} - -/// Inverse-only FFT plan for CELT decoder usage. -#[derive(Debug, Clone)] -pub(crate) struct KissFft { - nfft: usize, - inv_twiddles: Vec, -} - -impl KissFft { - /// Build an inverse FFT plan with runtime twiddle generation. - /// - /// Params: `nfft` is the FFT size. - /// Returns: a plan containing precomputed inverse twiddles. - pub fn new(nfft: usize) -> Self { - let mut inv_twiddles = Vec::with_capacity(nfft); - for k in 0..nfft { - let phase = 2.0 * PI * (k as f32) / (nfft as f32); - inv_twiddles.push(Complex32::new(phase.cos(), phase.sin())); - } - Self { nfft, inv_twiddles } - } - - /// Return FFT size configured in this plan. - /// - /// Params: none. - /// Returns: number of points in the transform. - pub fn len(&self) -> usize { - self.nfft - } - - /// Compute unscaled inverse FFT. - /// - /// Params: `input` is frequency-domain complex spectrum, `output` is - /// destination time-domain buffer and must have length `self.len()`. - /// Returns: `Ok(())` on success, otherwise a static validation error. - pub fn ifft(&self, input: &[Complex32], output: &mut [Complex32]) -> Result<(), &'static str> { - if input.len() != self.nfft || output.len() != self.nfft { - return Err("kiss_fft length mismatch"); - } - for out in output.iter_mut() { - *out = Complex32::new(0.0, 0.0); - } - for (n, out) in output.iter_mut().enumerate() { - let mut acc_re = 0.0f32; - let mut acc_im = 0.0f32; - for (k, xk) in input.iter().enumerate() { - let tw = self.inv_twiddles[(k * n) % self.nfft]; - acc_re += xk.re * tw.re - xk.im * tw.im; - acc_im += xk.re * tw.im + xk.im * tw.re; - } - *out = Complex32::new(acc_re, acc_im); - } - Ok(()) - } - - /// Compute unscaled forward FFT. - /// - /// Params: `input` is time-domain complex vector, `output` is destination - /// frequency-domain buffer and must have length `self.len()`. - /// Returns: `Ok(())` on success, otherwise a static validation error. - pub fn fft(&self, input: &[Complex32], output: &mut [Complex32]) -> Result<(), &'static str> { - if input.len() != self.nfft || output.len() != self.nfft { - return Err("kiss_fft length mismatch"); - } - for out in output.iter_mut() { - *out = Complex32::new(0.0, 0.0); - } - for (n, out) in output.iter_mut().enumerate() { - let mut acc_re = 0.0f32; - let mut acc_im = 0.0f32; - for (k, xk) in input.iter().enumerate() { - let tw = self.inv_twiddles[(k * n) % self.nfft]; - // Conjugate inverse twiddle gives forward phase. - acc_re += xk.re * tw.re + xk.im * tw.im; - acc_im += -xk.re * tw.im + xk.im * tw.re; - } - *out = Complex32::new(acc_re, acc_im); - } - Ok(()) - } -} - -/// Compute forward DFT on flat interleaved complex buffers. -/// -/// Params: `input`/`output` are `[re0, im0, re1, im1, ...]` and `n` is the -/// number of complex samples. -/// Returns: nothing; writes the forward transform to `output`. -pub(crate) fn flat_fft_forward(input: &[f32], output: &mut [f32], n: usize) { - assert!(input.len() >= 2 * n && output.len() >= 2 * n); - for k in 0..n { - let mut sum_re = 0.0f64; - let mut sum_im = 0.0f64; - for j in 0..n { - let angle = -2.0 * PI as f64 * (k as f64) * (j as f64) / (n as f64); - let (sin_a, cos_a) = angle.sin_cos(); - let re = input[2 * j] as f64; - let im = input[2 * j + 1] as f64; - sum_re += re * cos_a - im * sin_a; - sum_im += re * sin_a + im * cos_a; - } - output[2 * k] = sum_re as f32; - output[2 * k + 1] = sum_im as f32; - } -} - -#[cfg(test)] -mod tests { - use super::{Complex32, KissFft}; - use core::f32::consts::PI; - - /// Compute libopus-style forward DFT with `1/N` scaling for tests. - /// - /// Params: `input` is time-domain complex vector. - /// Returns: scaled frequency-domain vector. - fn forward_scaled(input: &[Complex32]) -> Vec { - let n = input.len(); - let mut out = vec![Complex32::new(0.0, 0.0); n]; - for (k, yk) in out.iter_mut().enumerate() { - let mut acc_re = 0.0f32; - let mut acc_im = 0.0f32; - for (n_idx, xn) in input.iter().enumerate() { - let phase = -2.0 * PI * (k as f32) * (n_idx as f32) / (n as f32); - let c = phase.cos(); - let s = phase.sin(); - acc_re += xn.re * c - xn.im * s; - acc_im += xn.re * s + xn.im * c; - } - *yk = Complex32::new(acc_re / (n as f32), acc_im / (n as f32)); - } - out - } - - #[test] - fn ifft_roundtrip_matches_input() { - let n = 60usize; - let fft = KissFft::new(n); - let mut input = Vec::with_capacity(n); - for i in 0..n { - let t = i as f32 / n as f32; - input.push(Complex32::new( - (2.0 * PI * 3.0 * t).sin(), - (2.0 * PI * 5.0 * t).cos(), - )); - } - let freq = forward_scaled(&input); - let mut recon = vec![Complex32::new(0.0, 0.0); n]; - fft.ifft(&freq, &mut recon).expect("ifft must succeed"); - for (a, b) in input.iter().zip(recon.iter()) { - assert!( - (a.re - b.re).abs() < 2e-5, - "re mismatch: {} vs {}", - a.re, - b.re - ); - assert!( - (a.im - b.im).abs() < 2e-5, - "im mismatch: {} vs {}", - a.im, - b.im - ); - } - } -} diff --git a/vendor/opus-decoder/src/celt/laplace.rs b/vendor/opus-decoder/src/celt/laplace.rs deleted file mode 100644 index 750d72f..0000000 --- a/vendor/opus-decoder/src/celt/laplace.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! Laplace-distributed symbol decoding used by CELT energy quantization. - -use crate::entropy::EcDec; - -const LAPLACE_LOG_MINP: u32 = 0; -const LAPLACE_MINP: u32 = 1 << LAPLACE_LOG_MINP; -const LAPLACE_NMIN: u32 = 16; - -/// Compute the first decay bucket frequency for Laplace model. -/// -/// Params: `fs0` is probability mass of zero symbol, `decay` is model decay. -/// Returns: frequency for first non-zero bucket. -fn laplace_get_freq1(fs0: u32, decay: u32) -> u32 { - let ft = 32_768u32 - LAPLACE_MINP * (2 * LAPLACE_NMIN) - fs0; - ((ft as u64 * (16_384u32.saturating_sub(decay)) as u64) >> 15) as u32 -} - -/// Decode one integer symbol from CELT Laplace-like distribution. -/// -/// Params: `dec` is range decoder state, `fs` is zero-probability frequency, -/// `decay` is decay parameter. -/// Returns: decoded signed integer delta. -pub(crate) fn ec_laplace_decode(dec: &mut EcDec<'_>, fs: u32, decay: u32) -> i32 { - let mut val = 0i32; - let fm = dec.decode_bin(15); - let mut fl = 0u32; - let mut fs_cur = fs; - if fm >= fs_cur { - val += 1; - fl = fs_cur; - fs_cur = laplace_get_freq1(fs_cur, decay) + LAPLACE_MINP; - while fs_cur > LAPLACE_MINP && fm >= fl + 2 * fs_cur { - fs_cur *= 2; - fl += fs_cur; - fs_cur = (((fs_cur - 2 * LAPLACE_MINP) as u64 * decay as u64) >> 15) as u32; - fs_cur += LAPLACE_MINP; - val += 1; - } - if fs_cur <= LAPLACE_MINP { - let di = ((fm - fl) >> (LAPLACE_LOG_MINP + 1)) as i32; - val += di; - fl += (2 * di as u32) * LAPLACE_MINP; - } - if fm < fl + fs_cur { - val = -val; - } else { - fl += fs_cur; - } - } - let fh = (fl + fs_cur).min(32_768); - dec.update(fl, fh, 32_768); - val -} diff --git a/vendor/opus-decoder/src/celt/mdct.rs b/vendor/opus-decoder/src/celt/mdct.rs deleted file mode 100644 index 73fb335..0000000 --- a/vendor/opus-decoder/src/celt/mdct.rs +++ /dev/null @@ -1,528 +0,0 @@ -#![allow(dead_code, unused_variables, unused_assignments)] - -//! CELT inverse MDCT primitives. -//! -//! Current scope is a decoder-oriented path for 48 kHz / 20 ms (`LM=3`), with -//! explicit overlap/window processing kept close to libopus flow. - -use crate::celt::kiss_fft::{KissFft, flat_fft_forward}; - -/// Backward MDCT helper for CELT decode path. -#[derive(Debug, Clone)] -pub(crate) struct MdctBackward { - n: usize, - overlap: usize, - fft: KissFft, - trig: Vec, -} - -impl MdctBackward { - /// Create backward MDCT state for fixed frame size. - /// - /// Params: `n` is MDCT size (e.g. 960), `overlap` is overlap length - /// (e.g. 120). - /// Returns: initialized state with runtime trig tables. - pub fn new(n: usize, overlap: usize) -> Self { - let n2 = n / 2; - let mut trig = Vec::with_capacity(n2); - for j in 0..n2 { - let phase = 2.0_f64 * core::f64::consts::PI * (j as f64 + 0.125_f64) / n as f64; - trig.push(phase.cos() as f32); - } - Self { - n, - overlap, - fft: KissFft::new(n / 4), - trig, - } - } - - /// Return configured MDCT size. - /// - /// Params: none. - /// Returns: MDCT size `N` for this instance. - pub fn n(&self) -> usize { - self.n - } - - /// Return internal FFT size used by MDCT. - /// - /// Params: none. - /// Returns: complex FFT size `N/4`. - pub fn fft_len(&self) -> usize { - self.fft.len() - } - - /// Run `clt_mdct_backward`-style transform. - /// - /// Params: `input` contains `N/2` MDCT bins, `window` contains `overlap` - /// samples, `out` has length at least `N/2 + overlap`. - /// Returns: `Ok(())` on success, otherwise a static validation error. - pub fn backward( - &self, - input: &[f32], - window: &[f32], - out: &mut [f32], - ) -> Result<(), &'static str> { - let n = self.n; - let n2 = n >> 1; - let n4 = n >> 2; - let overlap = self.overlap; - let required = n2 + overlap; - if input.len() != n2 || out.len() < required { - return Err("mdct backward length mismatch"); - } - if window.len() != overlap { - return Err("mdct backward window length mismatch"); - } - - // #region agent log - let lm0_trace = false; - if lm0_trace { - let old_sum: f32 = out[..overlap.min(out.len())].iter().map(|x| x.abs()).sum(); - let first8: String = (0..8.min(out.len())) - .map(|i| format!("{:.6}", out[i])) - .collect::>() - .join(","); - let line = format!( - r#"{{"sessionId":"bea564","runId":"lm0_imdct","hypothesisId":"H2","location":"mdct.rs:backward_entry","message":"lm0_old_overlap","data":{{"n":{},"n2":{},"overlap":{},"old_sum":{},"old_first8":[{}]}},"timestamp":{}}}"#, - n, - n2, - overlap, - old_sum, - first8, - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_millis() - ); - let log_path = std::env::current_dir() - .unwrap_or_else(|_| std::path::PathBuf::from(".")) - .join(".cursor") - .join("debug-bea564.log"); - let _ = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(&log_path) - .and_then(|mut f| std::io::Write::write_all(&mut f, (line + "\n").as_bytes())); - } - // #endregion - - let trace_fft = false; - if trace_fft { - debug_trace!( - "mdct input[0]={:.6} input[{}]={:.6} trig[0]={:.6} trig[{}]={:.6}", - input[0], - n2 - 1, - input[n2 - 1], - self.trig[0], - n4, - self.trig[n4] - ); - debug_trace!("backward: self.n={} n4={}", self.n, n4); - if self.n == 1920 && self.trig.len() >= 960 { - debug_trace!( - "trig[0]={:.8} trig[1]={:.8} trig[479]={:.8}", - self.trig[0], - self.trig[1], - self.trig[479] - ); - debug_trace!( - "trig[480]={:.8} trig[481]={:.8} trig[959]={:.8}", - self.trig[480], - self.trig[481], - self.trig[959] - ); - debug_trace!( - "TRIG CHECK: [1]={:.8} [239]={:.8} [240]={:.8} [479]={:.8}", - self.trig[1], - self.trig[239], - self.trig[240], - self.trig[479] - ); - } - } - // Flat interleaved buffer: [re0, im0, re1, im1, ...] - let mut f2 = vec![0.0f32; n2]; - let t = &self.trig; - let impulse_mode = - input.first().copied() == Some(1.0) && input.iter().skip(1).all(|&v| v == 0.0); - for i in 0..n4 { - let xp1 = input[2 * i]; - let xp2 = input[n2 - 1 - 2 * i]; - // Match libopus clt_mdct_backward pre-rotation for float path. - let yr = xp2 * t[i] + xp1 * t[n4 + i]; - let yi = xp1 * t[i] - xp2 * t[n4 + i]; - // Swap slots like libopus FFT-vs-IFFT trick. - f2[2 * i] = yi; - f2[2 * i + 1] = yr; - } - if impulse_mode && f2.len() >= 8 { - debug_trace!( - "IMPULSE f2[0..8]: {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6}", - f2[0], - f2[1], - f2[2], - f2[3], - f2[4], - f2[5], - f2[6], - f2[7] - ); - if self.n == 1920 { - debug_trace!( - "R PRE-FFT[0..8]: {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6}", - f2[0], - f2[1], - f2[2], - f2[3], - f2[4], - f2[5], - f2[6], - f2[7] - ); - } - } - if trace_fft && self.n == 1920 && f2.len() >= 4 { - debug_trace!( - "f2[0..4]: {:.6} {:.6} {:.6} {:.6}", - f2[0], - f2[1], - f2[2], - f2[3] - ); - } - if n == 1920 && n4 >= 2 { - let mut sum_re = 0.0f32; - let mut sum_im = 0.0f32; - for i in 0..n4 { - sum_re += f2[2 * i]; - sum_im += f2[2 * i + 1]; - } - debug_trace!( - "MDCT1920 fft_in[0..2]: ({:.6},{:.6}) ({:.6},{:.6})", - f2[0], - f2[1], - f2[2], - f2[3] - ); - debug_trace!("MDCT1920 fft_in sum: ({:.6},{:.6})", sum_re, sum_im); - } - - let mut f2_out = vec![0.0f32; n2]; - flat_fft_forward(&f2, &mut f2_out, n4); - if impulse_mode && f2_out.len() >= 8 { - debug_trace!( - "IMPULSE f2_out[0..8]: {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6}", - f2_out[0], - f2_out[1], - f2_out[2], - f2_out[3], - f2_out[4], - f2_out[5], - f2_out[6], - f2_out[7] - ); - } - if trace_fft && self.n == 1920 && f2_out.len() >= 4 { - debug_trace!( - "f2_out[0..4]: {:.6} {:.6} {:.6} {:.6}", - f2_out[0], - f2_out[1], - f2_out[2], - f2_out[3] - ); - debug_trace!("f2_out[0..2]: {:.6} {:.6}", f2_out[0], f2_out[1]); - debug_trace!("f2_out[958..960]: {:.6} {:.6}", f2_out[958], f2_out[959]); - debug_trace!( - "post i=0: re={:.6} im={:.6} t0={:.6} t480={:.6}", - f2_out[0], - f2_out[959], - self.trig[0], - self.trig[480] - ); - let sum_re: f64 = (0..n4).map(|j| f2[2 * j] as f64).sum(); - let sum_im: f64 = (0..n4).map(|j| f2[2 * j + 1] as f64).sum(); - debug_trace!( - "VERIFY FFT: input_sum=({:.6},{:.6}) output[0]=({:.6},{:.6})", - sum_re, - sum_im, - f2_out[0], - f2_out[1] - ); - debug_trace!( - "f2_out[last2]: ({:.6},{:.6})", - f2_out[n2 - 2], - f2_out[n2 - 1] - ); - } - if n == 1920 && n4 >= 2 { - debug_trace!( - "MDCT1920 fft_out[0..2]: ({:.6},{:.6}) ({:.6},{:.6})", - f2_out[0], - f2_out[1], - f2_out[2], - f2_out[3] - ); - } - - let ov2 = overlap >> 1; - out[ov2..ov2 + n2].copy_from_slice(&f2_out[..n2]); - // #region agent log - if lm0_trace { - let after_sum: f32 = out[ov2..ov2 + n2.min(out.len().saturating_sub(ov2))] - .iter() - .map(|x| x.abs()) - .sum(); - let after_first8: String = (ov2..(ov2 + 8).min(out.len())) - .map(|i| format!("{:.6}", out[i])) - .collect::>() - .join(","); - let line = format!( - r#"{{"sessionId":"bea564","runId":"lm0_imdct","hypothesisId":"H3","location":"mdct.rs:after_copy","message":"after_copy_from_slice","data":{{"ov2":{},"after_sum":{},"out_60_68":[{}]}},"timestamp":{}}}"#, - ov2, - after_sum, - after_first8, - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_millis() - ); - let log_path = std::env::current_dir() - .unwrap_or_else(|_| std::path::PathBuf::from(".")) - .join(".cursor") - .join("debug-bea564.log"); - let _ = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(&log_path) - .and_then(|mut f| std::io::Write::write_all(&mut f, (line + "\n").as_bytes())); - } - // #endregion - let mut p0 = ov2; - let mut p1 = ov2 + n2 - 2; - for i in 0..((n4 + 1) >> 1) { - if trace_fft && self.n == 1920 && i == 0 { - debug_trace!("[RUST] POST i=0: p0={} p1={}", p0, p1); - debug_trace!( - "[RUST] read: out[{}]={:.6} out[{}]={:.6}", - p0, - out[p0], - p0 + 1, - out[p0 + 1] - ); - debug_trace!( - "[RUST] read: out[{}]={:.6} out[{}]={:.6}", - p1, - out[p1], - p1 + 1, - out[p1 + 1] - ); - debug_trace!( - "[RUST] trig: t[0]={:.8} t[480]={:.8} t[479]={:.8} t[959]={:.8}", - self.trig[0], - self.trig[480], - self.trig[479], - self.trig[959] - ); - } - // We swap real/imag in reads because we use FFT instead of IFFT. - let re = out[p0 + 1]; - let im = out[p0]; - let t0 = t[i]; - let t1 = t[n4 + i]; - let yr = re * t0 + im * t1; - let yi = re * t1 - im * t0; - - // Read yp1 from the original buffer state, like libopus. - let re2 = out[p1 + 1]; - let im2 = out[p1]; - out[p0] = yr; - out[p1 + 1] = yi; - - let t2 = t[n4 - i - 1]; - let t3 = t[n2 - i - 1]; - let yr2 = re2 * t2 + im2 * t3; - let yi2 = re2 * t3 - im2 * t2; - out[p1] = yr2; - out[p0 + 1] = yi2; - if trace_fft && self.n == 1920 && i == 0 { - debug_trace!( - "[RUST] write: out[60]={:.6} out[1019]={:.6} out[1018]={:.6} out[61]={:.6}", - out[60], - out[1019], - out[1018], - out[61] - ); - } - - p0 += 2; - p1 -= 2; - } - if trace_fft && self.n == 1920 && out.len() >= 64 { - debug_trace!( - "post-rot out[60..64]: {:.6} {:.6} {:.6} {:.6}", - out[60], - out[61], - out[62], - out[63] - ); - } - // #region agent log - if lm0_trace { - let post_rot_left: f32 = out[..ov2.min(out.len())].iter().map(|x| x.abs()).sum(); - let post_rot_right: f32 = out[ov2..overlap.min(out.len())] - .iter() - .map(|x| x.abs()) - .sum(); - let right_first8: String = (ov2..(ov2 + 8).min(out.len())) - .map(|i| format!("{:.6}", out[i])) - .collect::>() - .join(","); - let line = format!( - r#"{{"sessionId":"bea564","runId":"lm0_imdct","hypothesisId":"H7","location":"mdct.rs:after_post_rot","message":"before_window","data":{{"post_rot_left_sum":{},"post_rot_right_sum":{},"right_first8":[{}]}},"timestamp":{}}}"#, - post_rot_left, - post_rot_right, - right_first8, - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_millis() - ); - let log_path = std::env::current_dir() - .unwrap_or_else(|_| std::path::PathBuf::from(".")) - .join(".cursor") - .join("debug-bea564.log"); - let _ = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(&log_path) - .and_then(|mut f| std::io::Write::write_all(&mut f, (line + "\n").as_bytes())); - } - // #endregion - - for i in 0..(overlap / 2) { - let x1 = out[overlap - 1 - i]; - let x2 = out[i]; - out[i] = x2 * window[overlap - 1 - i] - x1 * window[i]; - out[overlap - 1 - i] = x2 * window[i] + x1 * window[overlap - 1 - i]; - // #region agent log - if lm0_trace && (i == 0 || i == overlap / 2 - 1) { - let line = format!( - r#"{{"sessionId":"bea564","runId":"lm0_imdct","hypothesisId":"H4","location":"mdct.rs:window_loop","message":"window_iter","data":{{"i":{},"x1":{},"x2":{},"out_i":{},"out_119_i":{}}},"timestamp":{}}}"#, - i, - x1, - x2, - out[i], - out[overlap - 1 - i], - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_millis() - ); - let log_path = std::env::current_dir() - .unwrap_or_else(|_| std::path::PathBuf::from(".")) - .join(".cursor") - .join("debug-bea564.log"); - let _ = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(&log_path) - .and_then(|mut f| std::io::Write::write_all(&mut f, (line + "\n").as_bytes())); - } - // #endregion - } - // #region agent log - if lm0_trace { - let frame_sum: f32 = out[..overlap.min(out.len())].iter().map(|x| x.abs()).sum(); - let frame_first8: String = (0..8.min(out.len())) - .map(|i| format!("{:.6}", out[i])) - .collect::>() - .join(","); - let line = format!( - r#"{{"sessionId":"bea564","runId":"lm0_imdct","hypothesisId":"H5","location":"mdct.rs:backward_exit","message":"lm0_after_window","data":{{"frame_sum":{},"frame_first8":[{}]}},"timestamp":{}}}"#, - frame_sum, - frame_first8, - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_millis() - ); - let log_path = std::env::current_dir() - .unwrap_or_else(|_| std::path::PathBuf::from(".")) - .join(".cursor") - .join("debug-bea564.log"); - let _ = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open(&log_path) - .and_then(|mut f| std::io::Write::write_all(&mut f, (line + "\n").as_bytes())); - } - // #endregion - Ok(()) - } - - /// Run direct inverse MDCT that returns raw IMDCT output. - /// - /// Params: `input` contains `N/2` MDCT bins, `out` has length `N`. - /// Returns: `Ok(())` on success, otherwise a static validation error. - pub fn backward_direct( - &self, - input: &[f32], - _window: &[f32], - out: &mut [f32], - ) -> Result<(), &'static str> { - let n = self.n; - let n2 = n / 2; - if input.len() != n2 || out.len() != n { - return Err("mdct backward length mismatch"); - } - - for (i, out_i) in out.iter_mut().enumerate() { - let mut sum = 0.0f64; - for (k, &xk) in input.iter().enumerate() { - let phase = core::f64::consts::PI / (n2 as f64) - * (i as f64 + 0.5 + n2 as f64 / 2.0) - * (k as f64 + 0.5); - sum += xk as f64 * phase.cos(); - } - *out_i = sum as f32; - } - if self.n == 1920 { - let rms = (out.iter().map(|x| x * x).sum::() / out.len() as f32).sqrt(); - debug_trace!( - "raw imdct[0..4]: {:.6} {:.6} {:.6} {:.6}", - out[0], - out[1], - out[2], - out[3] - ); - debug_trace!( - "raw imdct[958..962]: {:.6} {:.6} {:.6} {:.6}", - out[958], - out[959], - out[960], - out[961] - ); - debug_trace!("MDCT1920 raw rms: {:.6}", rms); - } - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::MdctBackward; - - #[test] - fn mdct_backward_validates_lengths() { - let mdct = MdctBackward::new(960, 120); - let input = vec![0.0f32; 480]; - let window = vec![1.0f32; 120]; - let mut out = vec![0.0f32; 960]; - assert!(mdct.backward(&input, &window, &mut out).is_ok()); - assert!(mdct.backward(&input[..479], &window, &mut out).is_err()); - assert!(mdct.backward(&input, &window[..119], &mut out).is_err()); - } -} diff --git a/vendor/opus-decoder/src/celt/mod.rs b/vendor/opus-decoder/src/celt/mod.rs deleted file mode 100644 index b4f02bb..0000000 --- a/vendor/opus-decoder/src/celt/mod.rs +++ /dev/null @@ -1,3484 +0,0 @@ -//! CELT decoder for Opus CELT-only and hybrid packets. -//! -//! The implementation stays structurally close to libopus to preserve -//! conformance while keeping the decode path self-contained in Rust. -#![allow( - unused_variables, - unused_assignments, - dead_code, - clippy::too_many_arguments, - clippy::needless_borrow, - clippy::get_first, - clippy::needless_range_loop, - clippy::unnecessary_cast -)] - -use crate::Error; -use crate::entropy::EcDec; -use core::sync::atomic::{AtomicUsize, Ordering}; - -mod bands; -mod cwrs; -mod kiss_fft; -mod laplace; -mod mdct; -mod modes; -mod quant_bands; -mod rate; -mod vq; - -const OVERLAP_48K_20MS: usize = 120; -const NBANDS_48K_20MS: usize = 21; -const LOG_ENERGY_FLOOR_DB: f32 = -28.0; -const BITRES: i32 = 3; -const COMBFILTER_MINPERIOD: usize = 15; -const DECODE_BUFFER_SIZE: usize = 2048; -const COMB_FILTER_GAINS: [[f32; 3]; 3] = [ - [0.306_640_62, 0.217_041_02, 0.129_638_67], - [0.463_867_2, 0.268_066_4, 0.0], - [0.799_804_7, 0.100_097_66, 0.0], -]; -const TRIM_ICDF: [u8; 11] = [126, 124, 119, 109, 87, 41, 19, 9, 4, 2, 0]; -const SPREAD_ICDF: [u8; 4] = [25, 23, 2, 0]; -const TAPSET_ICDF: [u8; 3] = [2, 1, 0]; -const TF_SELECT_TABLE: [[i8; 8]; 4] = [ - [0, -1, 0, -1, 0, -1, 0, -1], - [0, -1, 0, -2, 1, 0, 1, -1], - [0, -2, 0, -3, 2, 0, 1, -1], - [0, -2, 0, -3, 3, 0, 1, -1], -]; -static TRACE_PACKET_CALL_IDX: AtomicUsize = AtomicUsize::new(0); - -/// Result of decoding one CELT frame. -#[derive(Debug, Clone, Copy)] -pub(crate) struct CeltFrameDecode { - /// Number of decoded samples per channel. - pub samples_per_channel: usize, - /// Indicates transient frame path usage in CELT. - pub is_transient: bool, -} - -#[derive(Debug, Clone)] -pub(crate) struct CeltDecoder { - fs_hz: u32, - channels: u8, - start_band: usize, - mode: &'static modes::CeltMode, - mdct: mdct::MdctBackward, - mdct_480: mdct::MdctBackward, - mdct_240: mdct::MdctBackward, - mdct_short: mdct::MdctBackward, - decode_mem: Vec, - decode_mem_right: Vec, - prev_energy: Vec, - old_log_energy: Vec, - old_log_energy2: Vec, - background_log_energy: Vec, - deemph_mem: Vec, - end_band_override: Option, - postfilter_period: i32, - postfilter_period_old: i32, - postfilter_gain: f32, - postfilter_gain_old: f32, - postfilter_tapset: usize, - postfilter_tapset_old: usize, - final_range: u32, - rng_seed: u32, - last_split_count: usize, - loss_count: u32, -} - -impl CeltDecoder { - /// Create CELT decoder state. - /// - /// Params: `fs_hz` is output sample rate and `channels` is output channels. - /// Returns: initialized CELT decoder state. - pub fn new(fs_hz: u32, channels: u8) -> Self { - let mode = modes::mode48000_960_120(); - let state_ch = 2usize; - Self { - fs_hz, - channels, - start_band: 0, - mode, - mdct: mdct::MdctBackward::new(1920, mode.overlap), - mdct_480: mdct::MdctBackward::new(960, mode.overlap), - mdct_240: mdct::MdctBackward::new(480, mode.overlap), - mdct_short: mdct::MdctBackward::new(240, mode.overlap), - decode_mem: vec![0.0; DECODE_BUFFER_SIZE + mode.overlap], - decode_mem_right: vec![0.0; DECODE_BUFFER_SIZE + mode.overlap], - prev_energy: vec![0.0; NBANDS_48K_20MS * state_ch], - old_log_energy: vec![LOG_ENERGY_FLOOR_DB; NBANDS_48K_20MS * state_ch], - old_log_energy2: vec![LOG_ENERGY_FLOOR_DB; NBANDS_48K_20MS * state_ch], - background_log_energy: vec![0.0; NBANDS_48K_20MS * state_ch], - deemph_mem: vec![0.0; state_ch], - end_band_override: None, - postfilter_period: 0, - postfilter_period_old: 0, - postfilter_gain: 0.0, - postfilter_gain_old: 0.0, - postfilter_tapset: 0, - postfilter_tapset_old: 0, - final_range: 0, - rng_seed: 0, - last_split_count: 0, - loss_count: 0, - } - } - - /// Reset decoder state between streams. - /// - /// Params: none. - /// Returns: nothing. - pub fn reset(&mut self) { - self.decode_mem.fill(0.0); - self.decode_mem_right.fill(0.0); - self.prev_energy.fill(0.0); - self.old_log_energy.fill(LOG_ENERGY_FLOOR_DB); - self.old_log_energy2.fill(LOG_ENERGY_FLOOR_DB); - self.background_log_energy.fill(0.0); - self.deemph_mem.fill(0.0); - self.end_band_override = None; - self.postfilter_period = 0; - self.postfilter_period_old = 0; - self.postfilter_gain = 0.0; - self.postfilter_gain_old = 0.0; - self.postfilter_tapset = 0; - self.postfilter_tapset_old = 0; - self.final_range = 0; - self.rng_seed = 0; - self.last_split_count = 0; - self.start_band = 0; - self.loss_count = 0; - } - - /// Clear CELT PLC loss history before a good decode. - /// - /// Params: none. - /// Returns: nothing. - pub fn reset_loss_count(&mut self) { - self.loss_count = 0; - } - - /// Return the CELT output decimation factor for the configured sample rate. - /// - /// Params: none. - /// Returns: integer 48 kHz downsample factor for the public output rate. - fn downsample_factor(&self) -> Result { - match self.fs_hz { - 48_000 => Ok(1), - 24_000 => Ok(2), - 16_000 => Ok(3), - 12_000 => Ok(4), - 8_000 => Ok(6), - _ => Err(Error::NotImplemented), - } - } - - /// Convert a 48 kHz CELT frame size to output samples per channel. - /// - /// Params: internal `frame_size_48k`. - /// Returns: per-channel sample count at the configured public output rate. - fn output_frame_size(&self, frame_size_48k: usize) -> Result { - let downsample = self.downsample_factor()?; - if frame_size_48k % downsample != 0 { - return Err(Error::NotImplemented); - } - Ok(frame_size_48k / downsample) - } - - /// Write one deemphasized CELT channel into the interleaved i16 output buffer. - /// - /// Params: mutable interleaved `out`, per-channel synthesized `ch_synth`, - /// channel index `ch`, and additive flag `accum`. - /// Returns: `Ok(())` on success or `Error::NotImplemented` for unsupported - /// output rates. - fn write_output_channel_i16( - &self, - out: &mut [i16], - ch_synth: &[f32], - ch: usize, - accum: bool, - ) -> Result<(), Error> { - let downsample = self.downsample_factor()?; - let output_samples = self.output_frame_size(ch_synth.len())?; - for i in 0..output_samples { - let sample = Self::float_to_i16(ch_synth[i * downsample]); - let out_idx = i * self.channels as usize + ch; - out[out_idx] = if accum { - out[out_idx].saturating_add(sample) - } else { - sample - }; - } - Ok(()) - } - - /// Derive the highest active CELT band from saved energy history. - /// - /// Params: active output `channels`. - /// Returns: exclusive upper band index for PLC shaping. - fn plc_end_band(&self, channels: usize) -> usize { - let active_channels = channels.clamp(1, 2); - let nb_ebands = self.mode.nb_ebands; - for band in (0..nb_ebands).rev() { - for ch in 0..active_channels { - if self.prev_energy[ch * nb_ebands + band] > LOG_ENERGY_FLOOR_DB { - return band + 1; - } - } - } - 0 - } - - /// Called when a CELT packet is lost (packet=[] or fec path). - /// - /// Params: per-channel `frame_size` and output `channels`. - /// Returns: interleaved concealed floating PCM. - pub fn decode_lost(&mut self, frame_size: usize, channels: usize) -> Vec { - let downsample = match self.downsample_factor() { - Ok(value) => value, - Err(_) => return Vec::new(), - }; - let active_channels = channels.clamp(1, self.channels as usize).min(2); - let mut pcm = vec![0.0f32; frame_size * active_channels]; - let Some(frame_size_48k) = frame_size.checked_mul(downsample) else { - return pcm; - }; - let lm = match frame_size_48k { - 120 => 0, - 240 => 1, - 480 => 2, - 960 => 3, - _ => return pcm, - }; - - let nb_ebands = self.mode.nb_ebands; - let end = self.plc_end_band(active_channels); - if end == 0 { - self.loss_count = self.loss_count.saturating_add(1); - return pcm; - } - - let mut left_energy = self.prev_energy[..nb_ebands].to_vec(); - let mut right_energy = if active_channels == 2 { - self.prev_energy[nb_ebands..2 * nb_ebands].to_vec() - } else { - left_energy.clone() - }; - if self.loss_count > 0 { - for band in 0..end { - left_energy[band] = (left_energy[band] - 6.0).max(self.background_log_energy[band]); - if active_channels == 2 { - right_energy[band] = (right_energy[band] - 6.0) - .max(self.background_log_energy[nb_ebands + band]); - } - } - } - for band in end..nb_ebands { - left_energy[band] = LOG_ENERGY_FLOOR_DB; - right_energy[band] = LOG_ENERGY_FLOOR_DB; - } - - let mut mdct_left = vec![0.0f32; frame_size_48k]; - for band in 0..end { - let start = (self.mode.e_bands[band] as usize) << lm; - let band_len = - (self.mode.e_bands[band + 1] as usize - self.mode.e_bands[band] as usize) << lm; - for value in &mut mdct_left[start..start + band_len] { - self.rng_seed = bands::celt_lcg_rand(self.rng_seed); - *value = ((self.rng_seed >> 16) as i16) as f32; - } - vq::renormalise_vector(&mut mdct_left[start..start + band_len], 1.0); - } - let mut denorm_left = vec![0.0f32; frame_size_48k]; - bands::denormalise_bands( - self.mode, - &mdct_left, - &mut denorm_left, - &left_energy, - 0, - end, - lm, - false, - ); - if Self::synthesise_channel_to_mem( - &denorm_left, - frame_size_48k, - lm, - false, - self.mode.window, - self.mode.overlap, - &self.mdct, - &self.mdct_480, - &self.mdct_240, - &self.mdct_short, - &mut self.decode_mem, - ) - .is_err() - { - return pcm; - } - - if active_channels == 2 { - let mut mdct_right = vec![0.0f32; frame_size_48k]; - for band in 0..end { - let start = (self.mode.e_bands[band] as usize) << lm; - let band_len = - (self.mode.e_bands[band + 1] as usize - self.mode.e_bands[band] as usize) << lm; - for value in &mut mdct_right[start..start + band_len] { - self.rng_seed = bands::celt_lcg_rand(self.rng_seed); - *value = ((self.rng_seed >> 16) as i16) as f32; - } - vq::renormalise_vector(&mut mdct_right[start..start + band_len], 1.0); - } - let mut denorm_right = vec![0.0f32; frame_size_48k]; - bands::denormalise_bands( - self.mode, - &mdct_right, - &mut denorm_right, - &right_energy, - 0, - end, - lm, - false, - ); - if Self::synthesise_channel_to_mem( - &denorm_right, - frame_size_48k, - lm, - false, - self.mode.window, - self.mode.overlap, - &self.mdct, - &self.mdct_480, - &self.mdct_240, - &self.mdct_short, - &mut self.decode_mem_right, - ) - .is_err() - { - return pcm; - } - } - - self.postfilter_period = 0; - self.postfilter_period_old = 0; - self.postfilter_gain = 0.0; - self.postfilter_gain_old = 0.0; - self.postfilter_tapset = 0; - self.postfilter_tapset_old = 0; - self.old_log_energy2[..nb_ebands].copy_from_slice(&self.old_log_energy[..nb_ebands]); - self.old_log_energy[..nb_ebands].copy_from_slice(&left_energy); - self.prev_energy[..nb_ebands].copy_from_slice(&left_energy); - self.old_log_energy2[nb_ebands..2 * nb_ebands] - .copy_from_slice(&self.old_log_energy[nb_ebands..2 * nb_ebands]); - if active_channels == 2 { - self.old_log_energy[nb_ebands..2 * nb_ebands].copy_from_slice(&right_energy); - self.prev_energy[nb_ebands..2 * nb_ebands].copy_from_slice(&right_energy); - } else { - self.old_log_energy[nb_ebands..2 * nb_ebands].copy_from_slice(&left_energy); - self.prev_energy[nb_ebands..2 * nb_ebands].copy_from_slice(&left_energy); - } - - let out_start = Self::out_start(frame_size_48k); - let mut left = self.decode_mem[out_start..out_start + frame_size_48k].to_vec(); - self.apply_deemph(0, &mut left); - for i in 0..frame_size { - pcm[i * active_channels] = left[i * downsample]; - } - if active_channels == 2 { - let mut right = self.decode_mem_right[out_start..out_start + frame_size_48k].to_vec(); - self.apply_deemph(1, &mut right); - for i in 0..frame_size { - pcm[i * 2 + 1] = right[i * downsample]; - } - } - - self.final_range = 0; - self.last_split_count = 0; - self.loss_count = self.loss_count.saturating_add(1); - pcm - } - - /// Return CELT range coder final state. - /// - /// Params: none. - /// Returns: range coder final value for last decoded frame. - pub fn final_range(&self) -> u32 { - self.final_range - } - - /// Return the MDCT overlap window used for transition fades. - /// - /// Params: none. - /// Returns: immutable CELT synthesis window coefficients. - pub(crate) fn window(&self) -> &[f32] { - self.mode.window - } - - /// Return deemphasis memory for the selected channel. - /// - /// Params: `ch` channel index. - /// Returns: deemphasis memory value for that channel. - pub fn deemph_mem(&self, ch: usize) -> f32 { - self.deemph_mem[ch] - } - - /// Return absolute sum of the left decode memory. - /// - /// Params: none. - /// Returns: sum of absolute sample values in the left decode buffer. - pub fn decode_mem_abs_sum(&self) -> f32 { - self.decode_mem.iter().map(|x| x.abs()).sum() - } - - /// Return absolute sum of the right decode memory. - /// - /// Params: none. - /// Returns: sum of absolute sample values in the right decode buffer. - pub fn decode_mem_right_abs_sum(&self) -> f32 { - self.decode_mem_right.iter().map(|x| x.abs()).sum() - } - - /// Return leading CELT band energies for debug comparisons. - /// - /// Params: `count` is the number of entries to copy. - /// Returns: copied prefix of `prev_energy` with at most `count` values. - pub fn prev_energy_head(&self, count: usize) -> Vec { - self.prev_energy[..self.prev_energy.len().min(count)].to_vec() - } - - /// Return split count captured for last decoded frame. - /// - /// Params: none. - /// Returns: recursive split count. - pub fn last_split_count(&self) -> usize { - self.last_split_count - } - - /// Override the lowest coded band for the next CELT decode. - /// - /// Params: `band` lower band index to decode from. - /// Returns: nothing; the index is clamped to the valid mode range. - pub fn set_start_band(&mut self, band: usize) { - self.start_band = band.min(self.mode.nb_ebands.saturating_sub(1)); - } - - /// Override the highest coded band for the next CELT decode. - /// - /// Params: `band` exclusive upper band index. - /// Returns: nothing; the index is clamped to the valid mode range. - pub fn set_end_band(&mut self, band: usize) { - self.end_band_override = Some(band.clamp(1, self.mode.nb_ebands)); - } - - /// Clear any explicit CELT end-band override. - /// - /// Params: none. - /// Returns: nothing; future decodes use config-derived band end. - pub fn clear_end_band(&mut self) { - self.end_band_override = None; - } - - /// Return the currently configured CELT start band. - /// - /// Params: none. - /// Returns: active lower coded band index. - pub(crate) fn debug_start_band(&self) -> usize { - self.start_band - } - - /// Return the CELT end band implied by a TOC config. - /// - /// Params: packet `config`. - /// Returns: upper coded band index for the current mode. - pub(crate) fn debug_end_band(&self, config: u8) -> usize { - self.end_band_override - .unwrap_or_else(|| bandwidth_end(config)) - } - - /// Check whether packet should emit targeted debug logs. - /// - /// Params: `packet_idx` absolute packet index in current test run. - /// Returns: true when packet is in onset/hotspot probe set. - fn should_trace_debug_packet(packet_idx: usize) -> bool { - let _ = packet_idx; - false - } - - /// Append one NDJSON debug entry to the session log. - /// - /// Params: `run_id` run label, `hypothesis_id` tested hypothesis, `location` - /// source marker, `message` event name, and `data_json` object string. - /// Returns: nothing. - fn append_debug_log( - run_id: &str, - hypothesis_id: &str, - location: &str, - message: &str, - data_json: &str, - ) { - let _ = (run_id, hypothesis_id, location, message, data_json); - } - - /// Append one NDJSON debug entry to the active runtime debug session. - /// - /// Params: run metadata and JSON payload string. - /// Returns: nothing. - fn append_runtime_debug_log( - run_id: &str, - hypothesis_id: &str, - location: &str, - message: &str, - data_json: &str, - ) { - let _ = (run_id, hypothesis_id, location, message, data_json); - } - - /// Apply first-order deemphasis to one channel buffer in-place. - /// - /// Params: `ch` is channel index, `samples` are per-channel samples. - /// Returns: nothing. - fn apply_deemph(&mut self, ch: usize, samples: &mut [f32]) { - let coef = self.mode.preemph[0]; - let mut mem = self.deemph_mem[ch]; - for x in samples.iter_mut() { - // libopus recurrence: - // tmp = x + m; m = coef * tmp; output = tmp - let tmp = *x + mem; - mem = coef * tmp; - *x = tmp; - } - self.deemph_mem[ch] = mem; - } - - /// Apply CELT pitch comb postfilter in-place on decoder memory. - /// - /// Params: `buf` is decode memory, `start` is frame start in `buf`, `t0`/`t1` - /// are old/new periods, `n` is frame segment length, `g0`/`g1` are old/new - /// gains, `tapset0`/`tapset1` select tap weights, `window` is overlap window, - /// `overlap` is overlap length. - /// Returns: nothing. - fn comb_filter_in_place( - buf: &mut [f32], - start: usize, - t0: usize, - t1: usize, - n: usize, - g0: f32, - g1: f32, - tapset0: usize, - tapset1: usize, - window: &[f32], - overlap: usize, - ) { - if n == 0 || (g0 == 0.0 && g1 == 0.0) { - return; - } - let period0 = t0.max(COMBFILTER_MINPERIOD); - let period1 = t1.max(COMBFILTER_MINPERIOD); - let ts0 = tapset0.min(2); - let ts1 = tapset1.min(2); - let g00 = g0 * COMB_FILTER_GAINS[ts0][0]; - let g01 = g0 * COMB_FILTER_GAINS[ts0][1]; - let g02 = g0 * COMB_FILTER_GAINS[ts0][2]; - let g10 = g1 * COMB_FILTER_GAINS[ts1][0]; - let g11 = g1 * COMB_FILTER_GAINS[ts1][1]; - let g12 = g1 * COMB_FILTER_GAINS[ts1][2]; - let mut overlap_len = overlap.min(n); - if g0 == g1 && period0 == period1 && ts0 == ts1 { - overlap_len = 0; - } - - let mut x1 = buf[start - period1 + 1]; - let mut x2 = buf[start - period1]; - let mut x3 = buf[start - period1 - 1]; - let mut x4 = buf[start - period1 - 2]; - - for i in 0..overlap_len { - let x0 = buf[start + i - period1 + 2]; - let f = window[i] * window[i]; - let one_minus_f = 1.0 - f; - let mut y = buf[start + i]; - y += (one_minus_f * g00) * buf[start + i - period0]; - y += - (one_minus_f * g01) * (buf[start + i - period0 + 1] + buf[start + i - period0 - 1]); - y += - (one_minus_f * g02) * (buf[start + i - period0 + 2] + buf[start + i - period0 - 2]); - y += (f * g10) * x2; - y += (f * g11) * (x1 + x3); - y += (f * g12) * (x0 + x4); - buf[start + i] = y; - x4 = x3; - x3 = x2; - x2 = x1; - x1 = x0; - } - - if g1 == 0.0 { - return; - } - for i in overlap_len..n { - let x0 = buf[start + i - period1 + 2]; - let y = buf[start + i] + g10 * x2 + g11 * (x1 + x3) + g12 * (x0 + x4); - buf[start + i] = y; - x4 = x3; - x3 = x2; - x2 = x1; - x1 = x0; - } - } - - /// Convert one floating sample to i16 with saturation. - /// - /// Params: `x` is floating PCM sample. - /// Returns: saturated i16 PCM. - fn float_to_i16(x: f32) -> i16 { - let v = x.round().clamp(i16::MIN as f32, i16::MAX as f32); - v as i16 - } - - /// Return frame output start offset inside `decode_mem`. - /// - /// Params: `frame_size_48k` is frame size in 48 kHz samples. - /// Returns: start index for current synthesis output in `decode_mem`. - fn out_start(frame_size_48k: usize) -> usize { - DECODE_BUFFER_SIZE - frame_size_48k - } - - /// Return overlap tail slice stored in unified decode memory. - /// - /// Params: none. - /// Returns: immutable overlap tail `[DECODE_BUFFER_SIZE..DECODE_BUFFER_SIZE+overlap)`. - fn overlap_tail(&self) -> &[f32] { - let start = DECODE_BUFFER_SIZE; - let end = start + self.mode.overlap; - &self.decode_mem[start..end] - } - - /// Run CELT synthesis for one channel into the provided decode-state buffer. - /// - /// Params: denormalized spectrum, frame shape context and mutable channel state. - /// Returns: `Ok(())` on success, `Error::NotImplemented` for unsupported sizes. - fn synthesise_channel_to_mem( - denorm: &[f32], - frame_size_48k: usize, - lm: usize, - is_transient: bool, - window: &[f32], - overlap: usize, - mdct: &mdct::MdctBackward, - mdct_480: &mdct::MdctBackward, - mdct_240: &mdct::MdctBackward, - mdct_short: &mdct::MdctBackward, - decode_mem: &mut [f32], - ) -> Result<(), Error> { - let decode_len = DECODE_BUFFER_SIZE + overlap; - decode_mem.copy_within(frame_size_48k..decode_len, 0); - let out_start = Self::out_start(frame_size_48k); - if is_transient { - let m_blocks = 1usize << lm; - let short_len = frame_size_48k / m_blocks; - let mut short_coeffs = vec![0.0f32; short_len]; - for b in 0..m_blocks { - let out_offset = out_start + b * short_len; - for j in 0..short_len { - short_coeffs[j] = denorm[j * m_blocks + b]; - } - mdct_short - .backward( - &short_coeffs, - window, - &mut decode_mem[out_offset..out_offset + short_len + overlap], - ) - .map_err(|_| Error::NotImplemented)?; - } - } else { - let mdct_impl = match frame_size_48k { - 120 => mdct_short, - 240 => mdct_240, - 480 => mdct_480, - 960 => mdct, - _ => return Err(Error::NotImplemented), - }; - mdct_impl - .backward( - denorm, - window, - &mut decode_mem[out_start..out_start + frame_size_48k + overlap], - ) - .map_err(|_| Error::NotImplemented)?; - } - Ok(()) - } - - /// Decode a single CELT frame. - /// - /// `frame_size_48k` is samples-per-channel at 48 kHz (120/240/480/960). - pub fn decode_frame( - &mut self, - frame: &[u8], - frame_size_48k: usize, - config: u8, - packet_channels: u8, - parent_packet_idx: usize, - out: &mut [i16], - ) -> Result { - let mut ec = EcDec::new(frame); - self.decode_frame_with_ec( - frame, - &mut ec, - frame_size_48k, - config, - packet_channels, - parent_packet_idx, - out, - false, - ) - } - - /// Decode a single CELT frame using a shared entropy decoder. - /// - /// Params: source `frame`, shared mutable `ec`, `frame_size_48k`, TOC `config`, - /// coded `packet_channels`, parent `packet_idx`, mutable `out`, and `accum` - /// which adds decoded PCM on top of existing samples for hybrid mode. - /// Returns: decoded CELT frame metadata. - pub(crate) fn decode_frame_with_ec( - &mut self, - frame: &[u8], - mut ec: &mut EcDec<'_>, - frame_size_48k: usize, - config: u8, - packet_channels: u8, - parent_packet_idx: usize, - out: &mut [i16], - accum: bool, - ) -> Result { - let frame_call_idx = TRACE_PACKET_CALL_IDX.fetch_add(1, Ordering::SeqCst); - let _ = parent_packet_idx; - let packet_idx = usize::MAX; - let output_samples = self.output_frame_size(frame_size_48k)?; - if !matches!(self.channels, 1 | 2) { - return Err(Error::NotImplemented); - } - if !matches!(frame_size_48k, 120 | 240 | 480 | 960) { - return Err(Error::NotImplemented); - } - let needed = output_samples * self.channels as usize; - if out.len() < needed { - return Err(Error::OutputTooSmall { - needed, - got: out.len(), - }); - } - - let trace_this_packet = false; - let debug_packet = false; - let coded_channels = packet_channels.clamp(1, 2) as usize; - if debug_packet { - let overlap_abs: f32 = self.overlap_tail().iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"frame_size_48k\":{},\"config\":{},\"coded_channels\":{},\"overlap_abs_in\":{},\"deemph_mem0_in\":{},\"pf_old_period\":{},\"pf_old_gain\":{},\"pf_old_tapset\":{},\"pf_cur_period\":{},\"pf_cur_gain\":{},\"pf_cur_tapset\":{}}}", - packet_idx, - frame_size_48k, - config, - coded_channels, - overlap_abs, - self.deemph_mem[0], - self.postfilter_period_old, - self.postfilter_gain_old, - self.postfilter_tapset_old, - self.postfilter_period, - self.postfilter_gain, - self.postfilter_tapset - ); - // #region agent log - Self::append_debug_log( - "run-onset-map-v1", - "H1", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "state_in", - &data, - ); - // #endregion - } - if packet_idx == 6 { - let overlap_tail = self.overlap_tail(); - let ovl_abs_sum: f32 = overlap_tail.iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"entry\",\"overlap_abs_sum\":{},\"overlap_first4\":[{:.9},{:.9},{:.9},{:.9}],\"pf_old_period\":{},\"pf_old_gain\":{},\"pf_old_tapset\":{},\"pf_cur_period\":{},\"pf_cur_gain\":{},\"pf_cur_tapset\":{}}}", - packet_idx, - ovl_abs_sum, - overlap_tail.first().copied().unwrap_or(0.0), - overlap_tail.get(1).copied().unwrap_or(0.0), - overlap_tail.get(2).copied().unwrap_or(0.0), - overlap_tail.get(3).copied().unwrap_or(0.0), - self.postfilter_period_old, - self.postfilter_gain_old, - self.postfilter_tapset_old, - self.postfilter_period, - self.postfilter_gain, - self.postfilter_tapset - ); - // #region agent log - Self::append_runtime_debug_log( - "run-pkt6-overlap-entry-v1", - "H61", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_pkt6_overlap_entry", - &data, - ); - // #endregion - } - if coded_channels == 1 { - let nb = self.mode.nb_ebands; - for i in 0..nb { - self.prev_energy[i] = self.prev_energy[i].max(self.prev_energy[nb + i]); - } - } - let start = self.start_band.min(self.mode.nb_ebands.saturating_sub(1)); - let end = self - .end_band_override - .unwrap_or_else(|| bandwidth_end(config)); - let active_len = ec.storage(); - if trace_this_packet { - debug_trace!( - "R pkt{} config={} end={} coded_channels={} output_channels={} frame_bytes={} total_bits={}", - packet_idx, - config, - end, - coded_channels, - self.channels, - active_len, - active_len as i32 * 8 - ); - debug_trace!("R pkt{} frame_call_idx={}", packet_idx, frame_call_idx); - if self.channels == 2 { - debug_trace!( - "R pkt{} deemph_mem_in: ch0={:.6} ch1={:.6}", - packet_idx, - self.deemph_mem[0], - self.deemph_mem[1] - ); - } - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-stereo-probe\",\"hypothesisId\":\"H4\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:184\",\"message\":\"target_pkt_channels_and_config\",\"data\":{{\"packet_idx\":{},\"config\":{},\"end\":{},\"coded_channels\":{},\"output_channels\":{}}},\"timestamp\":{}}}\n", - packet_idx, config, end, coded_channels, self.channels, ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - let total_bits = (active_len * 8) as i32; - let trace_pkt38_stage = |_stage: &str, _ec: &EcDec<'_>| {}; - trace_pkt38_stage("entry", &ec); - let mut tell = ec.tell(); - let silence_flag = if tell >= total_bits { - true - } else if tell == 1 { - ec.dec_bit_logp(15) - } else { - false - }; - if silence_flag { - tell = total_bits; - } - if trace_this_packet { - debug_trace!( - "R pkt{} [after_silence_flag] tell={}", - packet_idx, - ec.tell_frac() - ); - } - trace_pkt38_stage("silence", &ec); - - let mut postfilter_pitch = 0i32; - let mut postfilter_qg = -1i32; - let mut postfilter_tapset = 0i32; - let mut postfilter_gain = 0.0f32; - if start == 0 && tell + 16 <= total_bits && ec.dec_bit_logp(1) { - let octave = ec.dec_uint(6) as i32; - postfilter_pitch = ((16 << octave) + ec.dec_bits((4 + octave) as u32) as i32) - 1; - postfilter_qg = ec.dec_bits(3) as i32; - if ec.tell() + 2 <= total_bits { - postfilter_tapset = ec.dec_icdf(&TAPSET_ICDF, 2) as i32; - } - postfilter_gain = 0.09375 * (postfilter_qg + 1) as f32; - } - if trace_this_packet { - debug_trace!( - "R pkt{} postfilter_active={} pitch={} qg={} tapset={}", - packet_idx, - postfilter_pitch > 0, - postfilter_pitch, - postfilter_qg, - postfilter_tapset - ); - } - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-postfilter-overlap-scan\",\"hypothesisId\":\"H10\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:227\",\"message\":\"postfilter_decoded\",\"data\":{{\"packet_idx\":{},\"coded_channels\":{},\"postfilter_active\":{},\"postfilter_pitch\":{},\"postfilter_qg\":{},\"postfilter_tapset\":{}}},\"timestamp\":{}}}\n", - packet_idx, - coded_channels, - postfilter_pitch > 0, - postfilter_pitch, - postfilter_qg, - postfilter_tapset, - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - if postfilter_pitch > 0 { - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-postfilter-overlap-scan\",\"hypothesisId\":\"H11\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:249\",\"message\":\"postfilter_active_but_not_applied_in_rust\",\"data\":{{\"packet_idx\":{},\"postfilter_pitch\":{},\"postfilter_qg\":{},\"postfilter_tapset\":{},\"comb_filter_applied\":false}},\"timestamp\":{}}}\n", - packet_idx, postfilter_pitch, postfilter_qg, postfilter_tapset, ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - tell = ec.tell(); - if trace_this_packet { - debug_trace!( - "R pkt{} [after_postfilter] tell={}", - packet_idx, - ec.tell_frac() - ); - } - trace_pkt38_stage("postfilt", &ec); - let lm = match frame_size_48k { - 120 => 0, - 240 => 1, - 480 => 2, - 960 => 3, - _ => unreachable!(), - }; - let is_transient = if lm > 0 && tell + 3 <= total_bits { - ec.dec_bit_logp(3) - } else { - false - }; - if packet_idx == 905 { - debug_trace!( - "R pkt905 frame_size_48k={} lm={} is_transient={}", - frame_size_48k, - lm, - is_transient - ); - } - if trace_this_packet { - debug_trace!( - "R pkt{} [after_transient] tell={}", - packet_idx, - ec.tell_frac() - ); - } - trace_pkt38_stage("transient", &ec); - tell = ec.tell(); - let intra_ener = tell + 3 <= total_bits && ec.dec_bit_logp(3); - if trace_this_packet { - debug_trace!( - "R pkt{} [after_intra_ener] tell={}", - packet_idx, - ec.tell_frac() - ); - } - trace_pkt38_stage("intra", &ec); - if trace_this_packet { - debug_trace!( - "R pkt{} prev_energy_all: {:?}", - packet_idx, - &self.prev_energy[..self.mode.nb_ebands] - ); - debug_trace!( - "R pkt{} old_log_energy_all: {:?}", - packet_idx, - &self.old_log_energy[..self.mode.nb_ebands] - ); - debug_trace!( - "R pkt{} old_log_energy2_all: {:?}", - packet_idx, - &self.old_log_energy2[..self.mode.nb_ebands] - ); - } - - quant_bands::unquant_coarse_energy( - self.mode, - start, - end, - &mut self.prev_energy, - intra_ener, - &mut ec, - coded_channels, - lm, - total_bits, - ); - if trace_this_packet { - debug_trace!( - "R pkt{} [after_coarse_energy] tell={}", - packet_idx, - ec.tell_frac() - ); - } - trace_pkt38_stage("coarse", &ec); - let mut tf_res = vec![0i32; self.mode.nb_ebands]; - tf_decode( - start, - end, - is_transient, - &mut tf_res, - lm, - total_bits, - &mut ec, - ); - if trace_this_packet { - debug_trace!("R pkt{} [after_tf] tell={}", packet_idx, ec.tell_frac()); - // #region agent log - let tf_csv = tf_res[start..end] - .iter() - .map(std::string::ToString::to_string) - .collect::>() - .join(","); - debug_trace!( - "R pkt{} frame_call_idx={} tf_res[{}..{}]={}", - packet_idx, - frame_call_idx, - start, - end, - tf_csv - ); - // #endregion - } - trace_pkt38_stage("tf", &ec); - let spread_decision = if ec.tell() + 4 <= total_bits { - ec.dec_icdf(&SPREAD_ICDF, 5) - } else { - 0 - }; - if trace_this_packet { - debug_trace!("R pkt{} [after_spread] tell={}", packet_idx, ec.tell_frac()); - // #region agent log - debug_trace!( - "R pkt{} frame_call_idx={} spread_decision={}", - packet_idx, - frame_call_idx, - spread_decision - ); - // #endregion - } - trace_pkt38_stage("spread", &ec); - let cap = rate::init_caps(self.mode, lm, coded_channels); - let mut offsets = vec![0i32; self.mode.nb_ebands]; - let mut dynalloc_logp = 6i32; - let mut total_bits_q = total_bits << BITRES; - tell = ec.tell_frac() as i32; - if trace_this_packet { - debug_trace!( - "R pkt{} [after_dynalloc_start] tell={}", - packet_idx, - ec.tell_frac() - ); - } - for i in start..end { - let width = (coded_channels as i32) - * (((self.mode.e_bands[i + 1] - self.mode.e_bands[i]) as i32) << lm); - let quanta = ((width << BITRES).min((6 << BITRES).max(width))).max(0); - let mut dynalloc_loop_logp = dynalloc_logp; - let mut boost = 0i32; - let tell_before_dyn_band = tell; - while tell + (dynalloc_loop_logp << BITRES) < total_bits_q && boost < cap[i] { - let flag = ec.dec_bit_logp(dynalloc_loop_logp as u32); - tell = ec.tell_frac() as i32; - if !flag { - break; - } - boost += quanta; - total_bits_q -= quanta; - dynalloc_loop_logp = 1; - } - offsets[i] = boost; - if boost > 0 { - dynalloc_logp = (dynalloc_logp - 1).max(2); - } - if trace_this_packet { - // #region agent log - debug_trace!( - "R pkt{} dynalloc band={} width={} quanta={} cap={} boost={} tell:{}->{} total_bits_q={} dynalloc_logp={}", - packet_idx, - i, - width, - quanta, - cap[i], - boost, - tell_before_dyn_band, - tell, - total_bits_q, - dynalloc_logp - ); - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-stereo-probe\",\"hypothesisId\":\"H8\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:318\",\"message\":\"dynalloc_band_state\",\"data\":{{\"packet_idx\":{},\"band\":{},\"width\":{},\"quanta\":{},\"cap\":{},\"boost\":{},\"tell_before\":{},\"tell_after\":{},\"total_bits_q\":{},\"dynalloc_logp\":{}}},\"timestamp\":{}}}\n", - packet_idx, - i, - width, - quanta, - cap[i], - boost, - tell_before_dyn_band, - tell, - total_bits_q, - dynalloc_logp, - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - } - if trace_this_packet { - debug_trace!( - "R pkt{} [after_dynalloc_end] tell={}", - packet_idx, - ec.tell_frac() - ); - } - trace_pkt38_stage("dynalloc", &ec); - let alloc_trim = if tell + (6 << BITRES) <= total_bits_q { - ec.dec_icdf(&TRIM_ICDF, 7) - } else { - 5 - }; - if trace_this_packet { - debug_trace!("R pkt{} [after_trim] tell={}", packet_idx, ec.tell_frac()); - // #region agent log - debug_trace!("R pkt{} alloc_trim={}", packet_idx, alloc_trim); - // #endregion - } - trace_pkt38_stage("trim", &ec); - let mut bits = ((active_len as i32 * 8) << BITRES) - ec.tell_frac() as i32 - 1; - let anti_collapse_rsv = if is_transient && lm >= 2 && bits >= ((lm as i32 + 2) << BITRES) { - 1 << BITRES - } else { - 0 - }; - if packet_idx < 5 || trace_this_packet { - debug_trace!( - "pkt{} is_transient={} anti_collapse_rsv={}", - packet_idx, - is_transient, - anti_collapse_rsv - ); - } - if (4..=8).contains(&packet_idx) { - let overlap_abs: f32 = self.overlap_tail().iter().map(|x| x.abs()).sum(); - let deemph = self.deemph_mem[0]; - debug_trace!( - "R pkt{} STATE_IN overlap_abs={:.4} deemph={:.6} frame_size={} config={} end={} is_transient={} lm={} postfilter_pitch={} anti_collapse_rsv={}", - packet_idx, - overlap_abs, - deemph, - frame_size_48k, - config, - end, - is_transient, - lm, - postfilter_pitch, - anti_collapse_rsv - ); - } - if debug_packet || intra_ener { - let data = format!( - "{{\"packet_idx\":{},\"config\":{},\"end\":{},\"lm\":{},\"is_transient\":{},\"intra_ener\":{},\"postfilter_active\":{},\"postfilter_pitch\":{},\"postfilter_qg\":{},\"postfilter_tapset\":{},\"anti_collapse_rsv\":{}}}", - packet_idx, - config, - end, - lm, - is_transient, - intra_ener, - postfilter_pitch > 0, - postfilter_pitch, - postfilter_qg, - postfilter_tapset, - anti_collapse_rsv - ); - // #region agent log - Self::append_debug_log( - "run-onset-map-v1", - "H4", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "packet_flags", - &data, - ); - // #endregion - } - bits -= anti_collapse_rsv; - let alloc = rate::clt_compute_allocation( - self.mode, - start, - end, - &offsets, - &cap, - alloc_trim, - bits, - coded_channels, - lm, - &mut ec, - packet_idx, - ); - if trace_this_packet { - debug_trace!("R pkt{} [after_alloc] tell={}", packet_idx, ec.tell_frac()); - debug_trace!( - "R pkt{} alloc_meta: bits_in={} balance_out={} coded_bands={} intensity={} dual_stereo={}", - packet_idx, - bits, - alloc.balance, - alloc.coded_bands, - alloc.intensity, - alloc.dual_stereo - ); - for i in start..end { - debug_trace!( - "R pkt{} alloc band={} pulse={} fine={} prio={}", - packet_idx, - i, - alloc.pulses[i], - alloc.fine_quant[i], - alloc.fine_priority[i] - ); - } - } - trace_pkt38_stage("alloc", &ec); - if trace_this_packet { - debug_trace!( - "pkt{}: is_transient={} anti_collapse_rsv={} coded_bands={}", - packet_idx, - is_transient, - anti_collapse_rsv, - alloc.coded_bands - ); - debug_trace!( - "pkt{}: tell_before_bands={} total_bits_q={}", - packet_idx, - ec.tell_frac(), - (active_len as i32 * 8) << 3 - ); - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-stereo-probe\",\"hypothesisId\":\"H2\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:368\",\"message\":\"tell_before_fine_with_alloc\",\"data\":{{\"packet_idx\":{},\"tell\":{},\"total_bits_q\":{},\"channels\":{},\"intensity\":{},\"dual_stereo\":{},\"coded_bands\":{}}},\"timestamp\":{}}}\n", - packet_idx, - ec.tell_frac(), - (active_len as i32 * 8) << 3, - coded_channels, - alloc.intensity, - alloc.dual_stereo, - alloc.coded_bands, - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - quant_bands::unquant_fine_energy( - self.mode, - start, - end, - &mut self.prev_energy, - &alloc.fine_quant, - &mut ec, - coded_channels, - packet_idx, - ); - trace_pkt38_stage("fine", &ec); - if trace_this_packet { - debug_trace!( - "R pkt{} [after_fine_energy] tell={}", - packet_idx, - ec.tell_frac() - ); - debug_trace!( - "R pkt{} tell_before_bands_actual={}", - packet_idx, - ec.tell_frac() - ); - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-stereo-probe\",\"hypothesisId\":\"H3\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:383\",\"message\":\"tell_before_bands_actual\",\"data\":{{\"packet_idx\":{},\"tell\":{},\"channels\":{}}},\"timestamp\":{}}}\n", - packet_idx, - ec.tell_frac(), - coded_channels, - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - let mut mdct_in = vec![0.0f32; frame_size_48k]; - let tell_before_bands_decode = ec.tell_frac(); - let mut mdct_side = if coded_channels == 2 { - Some(vec![0.0f32; frame_size_48k]) - } else { - None - }; - let band_decode_path = if coded_channels == 2 { - "quant_all_bands_stereo" - } else { - "quant_all_bands_mono" - }; - if trace_this_packet { - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-stereo-probe\",\"hypothesisId\":\"H5\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:434\",\"message\":\"band_decode_path_selected\",\"data\":{{\"packet_idx\":{},\"coded_channels\":{},\"path\":\"{}\",\"intensity\":{},\"dual_stereo\":{},\"tell_before\":{}}},\"timestamp\":{}}}\n", - packet_idx, - coded_channels, - band_decode_path, - alloc.intensity, - alloc.dual_stereo, - tell_before_bands_decode, - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - let collapse_masks = if let Some(ref mut side) = mdct_side { - bands::quant_all_bands_stereo( - self.mode, - start, - end, - &mut mdct_in, - side, - &alloc.pulses, - is_transient, - spread_decision, - &tf_res, - (active_len as i32 * 8 << BITRES) - anti_collapse_rsv, - alloc.balance, - alloc.coded_bands, - lm, - &mut ec, - &mut self.rng_seed, - alloc.dual_stereo, - alloc.intensity, - coded_channels == 2 && self.channels == 1, - packet_idx, - frame_call_idx, - ) - } else { - bands::quant_all_bands_mono( - self.mode, - start, - end, - &mut mdct_in, - &alloc.pulses, - is_transient, - spread_decision, - &tf_res, - (active_len as i32 * 8 << BITRES) - anti_collapse_rsv, - alloc.balance, - alloc.coded_bands, - lm, - &mut ec, - &mut self.rng_seed, - packet_idx, - frame_call_idx, - ) - }; - let anti_probe_packet = packet_idx <= 8 || trace_this_packet; - let collapse_blocks = 1usize << lm; - let collapse_mask_limit = if collapse_blocks >= 8 { - 0xFFu8 - } else { - ((1u16 << collapse_blocks) - 1) as u8 - }; - let collapse_nonfull_count = collapse_masks - .iter() - .filter(|&&m| (m & collapse_mask_limit) != collapse_mask_limit) - .count(); - let collapse_empty_count = collapse_masks - .iter() - .filter(|&&m| (m & collapse_mask_limit) == 0) - .count(); - if anti_probe_packet { - let data = format!( - "{{\"packet_idx\":{},\"lm\":{},\"coded_channels\":{},\"collapse_masks_len\":{},\"collapse_mask_limit\":{},\"collapse_nonfull_count\":{},\"collapse_empty_count\":{},\"seed_after_quant\":{},\"ec_rng_after_quant\":{},\"anti_collapse_rsv\":{},\"is_transient\":{}}}", - packet_idx, - lm, - coded_channels, - collapse_masks.len(), - collapse_mask_limit, - collapse_nonfull_count, - collapse_empty_count, - self.rng_seed, - ec.rng(), - anti_collapse_rsv, - is_transient - ); - // #region agent log - Self::append_runtime_debug_log( - "run-anti-collapse-v1", - "H2", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "anti_collapse_post_quant_state", - &data, - ); - // #endregion - } - if packet_idx <= 10 || coded_channels == 2 { - let data = format!( - "{{\"packet_idx\":{},\"coded_channels\":{},\"output_channels\":{},\"is_transient\":{},\"lm\":{},\"anti_collapse_rsv\":{},\"collapse_nonfull_count\":{}}}", - packet_idx, - coded_channels, - self.channels, - is_transient, - lm, - anti_collapse_rsv, - collapse_nonfull_count - ); - // #region agent log - Self::append_runtime_debug_log( - "run-coded-channels-scan-v1", - "H9", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "coded_channels_state", - &data, - ); - // #endregion - } - if trace_this_packet { - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let tell_after = ec.tell_frac(); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-stereo-probe\",\"hypothesisId\":\"H6\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:450\",\"message\":\"band_decode_consumed_bits\",\"data\":{{\"packet_idx\":{},\"coded_channels\":{},\"tell_before\":{},\"tell_after\":{},\"consumed\":{}}},\"timestamp\":{}}}\n", - packet_idx, - coded_channels, - tell_before_bands_decode, - tell_after, - tell_after as i32 - tell_before_bands_decode as i32, - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - if trace_this_packet && coded_channels == 2 { - let mid_abs = mdct_in.iter().map(|v| v.abs()).sum::(); - let side_abs = mdct_side - .as_ref() - .map(|v| v.iter().map(|x| x.abs()).sum::()) - .unwrap_or(0.0); - let output_strategy = if self.channels == 1 { - "freq_downmix_after_denorm" - } else { - "multi_channel_output" - }; - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-stereo-probe\",\"hypothesisId\":\"H9\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:486\",\"message\":\"stereo_vectors_before_denorm\",\"data\":{{\"packet_idx\":{},\"coded_channels\":{},\"mid_abs\":{},\"side_abs\":{},\"output_strategy\":\"{}\"}},\"timestamp\":{}}}\n", - packet_idx, coded_channels, mid_abs, side_abs, output_strategy, ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - self.last_split_count = 0; - if packet_idx == 0 { - let m = 1usize << lm; - let mut band_abs = String::new(); - for bi in start..end { - let bs = m * self.mode.e_bands[bi] as usize; - let be = m * self.mode.e_bands[bi + 1] as usize; - let acc: f32 = mdct_in[bs..be].iter().map(|v| v.abs()).sum(); - if !band_abs.is_empty() { - band_abs.push(','); - } - band_abs.push_str(&format!("{:.6}", acc)); - } - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-norm-band-scan\",\"hypothesisId\":\"H21\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:676\",\"message\":\"norm_band_abs_snapshot\",\"data\":{{\"packet_idx\":{},\"coded_channels\":{},\"start\":{},\"end\":{},\"lm\":{},\"band_abs_csv\":\"{}\"}},\"timestamp\":{}}}\n", - packet_idx, coded_channels, start, end, lm, band_abs, ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - if (packet_idx == 0 || (2128..=2129).contains(&packet_idx)) && is_transient { - let m_blocks = 1usize << lm; - let short_len = frame_size_48k / m_blocks.max(1); - // #region agent log - if m_blocks == 8 && short_len > 0 { - let mut block_abs = [0.0f32; 8]; - for b in 0..8 { - let mut acc = 0.0f32; - for j in 0..short_len { - acc += mdct_in[j * m_blocks + b].abs(); - } - block_abs[b] = acc; - } - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-norm-block-scan\",\"hypothesisId\":\"H20\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:684\",\"message\":\"norm_block_abs_snapshot\",\"data\":{{\"packet_idx\":{},\"coded_channels\":{},\"blocks\":{},\"short_len\":{},\"block_abs\":[{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6}]}},\"timestamp\":{}}}\n", - packet_idx, - coded_channels, - m_blocks, - short_len, - block_abs[0], - block_abs[1], - block_abs[2], - block_abs[3], - block_abs[4], - block_abs[5], - block_abs[6], - block_abs[7], - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - } - // #endregion - } - // Anti-collapse bit (consumed from range coder for transient frames). - let mut anti_collapse_on = 0u32; - if anti_collapse_rsv > 0 { - anti_collapse_on = ec.dec_bits(1); - if trace_this_packet { - debug_trace!( - "pkt{} anti_collapse_rsv={} anti_collapse_on={} is_transient={} lm={}", - packet_idx, - anti_collapse_rsv, - anti_collapse_on, - is_transient, - lm - ); - } - if debug_packet { - let data = format!( - "{{\"packet_idx\":{},\"anti_collapse_rsv\":{},\"anti_collapse_on\":{},\"is_transient\":{},\"lm\":{}}}", - packet_idx, anti_collapse_rsv, anti_collapse_on, is_transient, lm - ); - // #region agent log - Self::append_debug_log( - "run-onset-map-v1", - "H5", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "anti_collapse_flag", - &data, - ); - // #endregion - } - } else if trace_this_packet { - debug_trace!( - "pkt{} anti_collapse_rsv=0 anti_collapse_on=0 is_transient={} lm={}", - packet_idx, - is_transient, - lm - ); - if debug_packet { - let data = format!( - "{{\"packet_idx\":{},\"anti_collapse_rsv\":0,\"anti_collapse_on\":0,\"is_transient\":{},\"lm\":{}}}", - packet_idx, is_transient, lm - ); - // #region agent log - Self::append_debug_log( - "run-onset-map-v1", - "H5", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "anti_collapse_flag", - &data, - ); - // #endregion - } - } else if debug_packet { - let data = format!( - "{{\"packet_idx\":{},\"anti_collapse_rsv\":0,\"anti_collapse_on\":0,\"is_transient\":{},\"lm\":{}}}", - packet_idx, is_transient, lm - ); - // #region agent log - Self::append_debug_log( - "run-onset-map-v1", - "H5", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "anti_collapse_flag", - &data, - ); - // #endregion - } - if anti_probe_packet { - let data = format!( - "{{\"packet_idx\":{},\"anti_collapse_rsv\":{},\"anti_collapse_on\":{},\"seed_before_apply\":{},\"ec_rng_before_apply\":{},\"is_transient\":{},\"lm\":{},\"collapse_nonfull_count\":{}}}", - packet_idx, - anti_collapse_rsv, - anti_collapse_on, - self.rng_seed, - ec.rng(), - is_transient, - lm, - collapse_nonfull_count - ); - // #region agent log - Self::append_runtime_debug_log( - "run-anti-collapse-v1", - "H1", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "anti_collapse_gate_state", - &data, - ); - // #endregion - } - let anti_pre_main_abs = if anti_probe_packet { - mdct_in.iter().map(|v| v.abs()).sum::() - } else { - 0.0 - }; - let anti_pre_side_abs = if anti_probe_packet { - mdct_side - .as_ref() - .map(|side| side.iter().map(|v| v.abs()).sum::()) - .unwrap_or(0.0) - } else { - 0.0 - }; - quant_bands::unquant_energy_finalise( - self.mode, - start, - end, - Some(&mut self.prev_energy), - &alloc.fine_quant, - &alloc.fine_priority, - active_len as i32 * 8 - ec.tell(), - &mut ec, - coded_channels, - ); - if anti_collapse_on != 0 { - if trace_this_packet { - debug_trace!( - "R pkt{} applying anti_collapse lm={} coded_channels={} seed={}", - packet_idx, - lm, - coded_channels, - self.rng_seed - ); - } - if let Some(side) = mdct_side.as_mut() { - bands::anti_collapse( - self.mode, - &mut mdct_in, - Some(side), - &collapse_masks, - lm, - coded_channels, - start, - end, - &self.prev_energy, - &self.old_log_energy, - &self.old_log_energy2, - &alloc.pulses, - self.rng_seed, - trace_this_packet, - packet_idx, - ); - } else { - bands::anti_collapse( - self.mode, - &mut mdct_in, - None, - &collapse_masks, - lm, - coded_channels, - start, - end, - &self.prev_energy, - &self.old_log_energy, - &self.old_log_energy2, - &alloc.pulses, - self.rng_seed, - trace_this_packet, - packet_idx, - ); - } - } - if anti_probe_packet { - let anti_post_main_abs = mdct_in.iter().map(|v| v.abs()).sum::(); - let anti_post_side_abs = mdct_side - .as_ref() - .map(|side| side.iter().map(|v| v.abs()).sum::()) - .unwrap_or(0.0); - let data = format!( - "{{\"packet_idx\":{},\"anti_collapse_on\":{},\"main_abs_before\":{},\"main_abs_after\":{},\"side_abs_before\":{},\"side_abs_after\":{},\"main_abs_delta\":{},\"side_abs_delta\":{},\"seed_after_apply\":{},\"ec_rng_after_apply\":{}}}", - packet_idx, - anti_collapse_on, - anti_pre_main_abs, - anti_post_main_abs, - anti_pre_side_abs, - anti_post_side_abs, - anti_post_main_abs - anti_pre_main_abs, - anti_post_side_abs - anti_pre_side_abs, - self.rng_seed, - ec.rng() - ); - // #region agent log - Self::append_runtime_debug_log( - "run-anti-collapse-v1", - "H5", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "anti_collapse_apply_effect", - &data, - ); - // #endregion - } - if packet_idx == 6 { - let m = 1usize << lm; - let bound = m * self.mode.e_bands[end] as usize; - let x_abs_sum_active: f32 = mdct_in[..bound].iter().map(|x| x.abs()).sum(); - let x_abs_sum_tail: f32 = mdct_in[bound..].iter().map(|x| x.abs()).sum(); - let len = mdct_in.len(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"before_denormalise\",\"x_abs_sum_active\":{},\"x_abs_sum_tail\":{},\"bound\":{},\"x_first8\":[{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9}],\"x_active_last8\":[{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9}],\"x_tail_first4\":[{:.9},{:.9},{:.9},{:.9}]}}", - packet_idx, - x_abs_sum_active, - x_abs_sum_tail, - bound, - mdct_in.get(0).copied().unwrap_or(0.0), - mdct_in.get(1).copied().unwrap_or(0.0), - mdct_in.get(2).copied().unwrap_or(0.0), - mdct_in.get(3).copied().unwrap_or(0.0), - mdct_in.get(4).copied().unwrap_or(0.0), - mdct_in.get(5).copied().unwrap_or(0.0), - mdct_in.get(6).copied().unwrap_or(0.0), - mdct_in.get(7).copied().unwrap_or(0.0), - mdct_in.get(bound.saturating_sub(8)).copied().unwrap_or(0.0), - mdct_in.get(bound.saturating_sub(7)).copied().unwrap_or(0.0), - mdct_in.get(bound.saturating_sub(6)).copied().unwrap_or(0.0), - mdct_in.get(bound.saturating_sub(5)).copied().unwrap_or(0.0), - mdct_in.get(bound.saturating_sub(4)).copied().unwrap_or(0.0), - mdct_in.get(bound.saturating_sub(3)).copied().unwrap_or(0.0), - mdct_in.get(bound.saturating_sub(2)).copied().unwrap_or(0.0), - mdct_in.get(bound.saturating_sub(1)).copied().unwrap_or(0.0), - mdct_in.get(bound).copied().unwrap_or(0.0), - mdct_in - .get((bound + 1).min(len.saturating_sub(1))) - .copied() - .unwrap_or(0.0), - mdct_in - .get((bound + 2).min(len.saturating_sub(1))) - .copied() - .unwrap_or(0.0), - mdct_in - .get((bound + 3).min(len.saturating_sub(1))) - .copied() - .unwrap_or(0.0) - ); - // #region agent log - Self::append_runtime_debug_log( - "run-pkt6-mdctin-v1", - "H84", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_pkt6_mdct_in_before_denorm", - &data, - ); - // #endregion - let e = &self.prev_energy[..self.mode.nb_ebands]; - let e_abs_sum: f32 = e.iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"before_denormalise\",\"energy_abs_sum\":{},\"energy_first12\":[{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9}]}}", - packet_idx, - e_abs_sum, - e.get(0).copied().unwrap_or(0.0), - e.get(1).copied().unwrap_or(0.0), - e.get(2).copied().unwrap_or(0.0), - e.get(3).copied().unwrap_or(0.0), - e.get(4).copied().unwrap_or(0.0), - e.get(5).copied().unwrap_or(0.0), - e.get(6).copied().unwrap_or(0.0), - e.get(7).copied().unwrap_or(0.0), - e.get(8).copied().unwrap_or(0.0), - e.get(9).copied().unwrap_or(0.0), - e.get(10).copied().unwrap_or(0.0), - e.get(11).copied().unwrap_or(0.0) - ); - // #region agent log - Self::append_runtime_debug_log( - "run-pkt6-energy-v1", - "H83", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_pkt6_oldbande_before_denorm", - &data, - ); - // #endregion - } - if silence_flag { - let state_channels = coded_channels.min(2); - for c in 0..state_channels { - let base = c * self.mode.nb_ebands; - for i in 0..self.mode.nb_ebands { - self.prev_energy[base + i] = LOG_ENERGY_FLOOR_DB; - } - } - } - let mut denorm = vec![0.0f32; frame_size_48k]; - bands::denormalise_bands( - self.mode, - &mdct_in, - &mut denorm, - &self.prev_energy[..self.mode.nb_ebands], - start, - end, - lm, - silence_flag, - ); - if packet_idx == 905 { - let denorm_abs_sum: f32 = denorm.iter().map(|x| x.abs()).sum(); - let first_n = denorm.len().min(16); - debug_trace!( - "R pkt905 denorm abs_sum={:.6} first{}={:?}", - denorm_abs_sum, - first_n, - &denorm[..first_n] - ); - let m = 1usize << lm; - for i in 0..end { - let bs = m * self.mode.e_bands[i] as usize; - let be = m * self.mode.e_bands[i + 1] as usize; - let band_abs: f32 = denorm[bs..be].iter().map(|x| x.abs()).sum(); - debug_trace!("R pkt905 denorm band {} abs_sum={:.6}", i, band_abs); - } - let active_end = m * self.mode.e_bands[end] as usize; - let beyond_end_abs: f32 = denorm[active_end..].iter().map(|x| x.abs()).sum(); - debug_trace!( - "R pkt905 denorm beyond_end abs_sum={:.6} active_end={}", - beyond_end_abs, - active_end - ); - } - let mut denorm_right_for_stereo: Option> = None; - if coded_channels == 2 { - if let Some(side) = mdct_side.as_ref() { - let mut denorm_side = vec![0.0f32; frame_size_48k]; - bands::denormalise_bands( - self.mode, - side, - &mut denorm_side, - &self.prev_energy[self.mode.nb_ebands..self.mode.nb_ebands * 2], - start, - end, - lm, - silence_flag, - ); - if self.channels == 1 { - let sum_l_before: f32 = denorm.iter().map(|x| x.abs()).sum(); - let sum_r_before: f32 = denorm_side.iter().map(|x| x.abs()).sum(); - if trace_this_packet { - // #region agent log - debug_trace!( - "R pkt{} denorm abs_sum: L={:.6} R={:.6}", - packet_idx, - sum_l_before, - sum_r_before - ); - // #endregion - } - if packet_idx == 2135 { - debug_trace!( - "R pkt2135 denorm abs_sum: L={:.6} R={:.6}", - sum_l_before, - sum_r_before - ); - } - if packet_idx == 2128 { - let m = 1usize << lm; - for i in 0..end { - let s = self.mode.e_bands[i] as usize * m; - let e = self.mode.e_bands[i + 1] as usize * m; - let l_sum: f32 = denorm[s..e].iter().sum(); - let r_sum: f32 = denorm_side[s..e].iter().sum(); - let mono_sum: f32 = (l_sum + r_sum) * 0.5; - debug_trace!( - "R pkt2128 band{} L={:.6} R={:.6} mono={:.6}", - i, - l_sum, - r_sum, - mono_sum - ); - } - } - if debug_packet { - let data = format!( - "{{\"packet_idx\":{},\"sum_l_before\":{},\"sum_r_before\":{},\"frame_size_48k\":{},\"lm\":{}}}", - packet_idx, sum_l_before, sum_r_before, frame_size_48k, lm - ); - // #region agent log - Self::append_debug_log( - "run-onset-map-v1", - "H2", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "stereo_denorm_before_downmix", - &data, - ); - // #endregion - } - for i in 0..frame_size_48k { - denorm[i] = 0.5 * (denorm[i] + denorm_side[i]); - } - let sum_mono_after: f32 = denorm.iter().map(|x| x.abs()).sum(); - if trace_this_packet { - // #region agent log - debug_trace!( - "R pkt{} mono abs_sum after downmix: {:.6}", - packet_idx, - sum_mono_after - ); - // #endregion - } - if packet_idx == 2135 { - debug_trace!( - "R pkt2135 mono abs_sum after downmix: {:.6}", - sum_mono_after - ); - } - if debug_packet { - let data = format!( - "{{\"packet_idx\":{},\"sum_mono_after\":{},\"frame_size_48k\":{},\"lm\":{}}}", - packet_idx, sum_mono_after, frame_size_48k, lm - ); - // #region agent log - Self::append_debug_log( - "run-onset-map-v1", - "H2", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "stereo_denorm_after_downmix", - &data, - ); - // #endregion - } - } else if self.channels == 2 { - denorm_right_for_stereo = Some(denorm_side); - } - } - } else if self.channels == 2 { - // Mono-coded packet routed to stereo output: identical spectrum on both channels. - denorm_right_for_stereo = Some(denorm.clone()); - } - if packet_idx == 0 && is_transient { - let m_blocks = 1usize << lm; - let mut b0_csv = String::new(); - for bi in start..end { - let bs = m_blocks * self.mode.e_bands[bi] as usize; - let be = m_blocks * self.mode.e_bands[bi + 1] as usize; - let mut acc = 0.0f32; - let mut j = bs; - while j < be { - acc += denorm[j].abs(); - j += m_blocks; - } - if !b0_csv.is_empty() { - b0_csv.push(','); - } - b0_csv.push_str(&format!("{:.6}", acc)); - } - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-band-block0-scan\",\"hypothesisId\":\"H25\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:734\",\"message\":\"denorm_band_block0_abs\",\"data\":{{\"packet_idx\":{},\"start\":{},\"end\":{},\"lm\":{},\"band_block0_abs_csv\":\"{}\"}},\"timestamp\":{}}}\n", - packet_idx, start, end, lm, b0_csv, ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - - if packet_idx == 0 || (2127..=2129).contains(&packet_idx) { - let overlap_tail = self.overlap_tail(); - let ovl_sum: f32 = overlap_tail.iter().map(|x| x.abs()).sum(); - let ovl_n = overlap_tail.len().min(8); - debug_trace!( - "R pkt{} overlap_in len={} abs_sum={:.6} first8: {:?}", - packet_idx, - overlap_tail.len(), - ovl_sum, - &overlap_tail[..ovl_n] - ); - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-postfilter-overlap-scan\",\"hypothesisId\":\"H12\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:734\",\"message\":\"overlap_in_snapshot\",\"data\":{{\"packet_idx\":{},\"coded_channels\":{},\"overlap_len\":{},\"overlap_abs_sum\":{},\"overlap_first8\":[{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6}]}} ,\"timestamp\":{}}}\n", - packet_idx, - coded_channels, - overlap_tail.len(), - ovl_sum, - overlap_tail.first().copied().unwrap_or(0.0), - overlap_tail.get(1).copied().unwrap_or(0.0), - overlap_tail.get(2).copied().unwrap_or(0.0), - overlap_tail.get(3).copied().unwrap_or(0.0), - overlap_tail.get(4).copied().unwrap_or(0.0), - overlap_tail.get(5).copied().unwrap_or(0.0), - overlap_tail.get(6).copied().unwrap_or(0.0), - overlap_tail.get(7).copied().unwrap_or(0.0), - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - - // Match libopus buffer flow: shift history before new synthesis. - let decode_len = DECODE_BUFFER_SIZE + self.mode.overlap; - self.decode_mem.copy_within(frame_size_48k..decode_len, 0); - let out_start = Self::out_start(frame_size_48k); - if packet_idx == 6 { - let overlap_src_end = out_start + self.mode.overlap; - let ovl = &self.decode_mem[out_start..overlap_src_end]; - let ovl_abs_sum: f32 = ovl.iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"post_shift_overlap_source\",\"overlap_abs_sum\":{},\"overlap_first4\":[{:.9},{:.9},{:.9},{:.9}]}}", - packet_idx, - ovl_abs_sum, - ovl.first().copied().unwrap_or(0.0), - ovl.get(1).copied().unwrap_or(0.0), - ovl.get(2).copied().unwrap_or(0.0), - ovl.get(3).copied().unwrap_or(0.0) - ); - // #region agent log - Self::append_runtime_debug_log( - "run-pkt6-overlap-entry-v1", - "H62", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_pkt6_post_shift_overlap_source", - &data, - ); - // #endregion - } - if packet_idx == 5 { - let overlap_tail = self.overlap_tail(); - let ovl_abs_sum: f32 = overlap_tail.iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"post_shift\",\"overlap_tail_abs_sum\":{},\"overlap_tail_first4\":[{:.9},{:.9},{:.9},{:.9}],\"shift_applied\":true}}", - packet_idx, - ovl_abs_sum, - overlap_tail.first().copied().unwrap_or(0.0), - overlap_tail.get(1).copied().unwrap_or(0.0), - overlap_tail.get(2).copied().unwrap_or(0.0), - overlap_tail.get(3).copied().unwrap_or(0.0) - ); - // #region agent log - Self::append_runtime_debug_log( - "run-post-imdct-state-pkt5-v1", - "H34", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_post_shift_overlap_tail", - &data, - ); - // #endregion - } - - let mut base_synth = vec![0.0f32; frame_size_48k]; - let trace_transient_pkt5 = - packet_idx == 5 && is_transient && lm == 3 && frame_size_48k == 960; - let trace_long_pkt6 = packet_idx == 6 && !is_transient && lm == 3 && frame_size_48k == 960; - if packet_idx == 0 || (2127..=2129).contains(&packet_idx) { - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let denorm_abs: f32 = denorm.iter().map(|x| x.abs()).sum(); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-synthesis-path-scan\",\"hypothesisId\":\"H15\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:769\",\"message\":\"synthesis_path_decision\",\"data\":{{\"packet_idx\":{},\"coded_channels\":{},\"is_transient\":{},\"lm\":{},\"frame_size_48k\":{},\"postfilter_active\":{},\"postfilter_pitch\":{},\"denorm_abs_sum\":{}}},\"timestamp\":{}}}\n", - packet_idx, - coded_channels, - is_transient, - lm, - frame_size_48k, - postfilter_pitch > 0, - postfilter_pitch, - denorm_abs, - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - if trace_transient_pkt5 { - let denorm_abs: f32 = denorm.iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"after_denormalise_bands\",\"lm\":{},\"is_transient\":{},\"frame_size\":{},\"denorm_abs_sum\":{},\"denorm_first8\":[{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9}]}}", - packet_idx, - lm, - is_transient, - frame_size_48k, - denorm_abs, - denorm.get(0).copied().unwrap_or(0.0), - denorm.get(1).copied().unwrap_or(0.0), - denorm.get(2).copied().unwrap_or(0.0), - denorm.get(3).copied().unwrap_or(0.0), - denorm.get(4).copied().unwrap_or(0.0), - denorm.get(5).copied().unwrap_or(0.0), - denorm.get(6).copied().unwrap_or(0.0), - denorm.get(7).copied().unwrap_or(0.0) - ); - // #region agent log - Self::append_runtime_debug_log( - "run-transient-imdct-pkt5-v1", - "H30", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_denorm_after_denormalise", - &data, - ); - // #endregion - } - if trace_long_pkt6 { - let denorm_abs: f32 = denorm.iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"after_denormalise_bands\",\"lm\":{},\"is_transient\":{},\"frame_size\":{},\"denorm_abs_sum\":{},\"denorm_first8\":[{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9}]}}", - packet_idx, - lm, - is_transient, - frame_size_48k, - denorm_abs, - denorm.get(0).copied().unwrap_or(0.0), - denorm.get(1).copied().unwrap_or(0.0), - denorm.get(2).copied().unwrap_or(0.0), - denorm.get(3).copied().unwrap_or(0.0), - denorm.get(4).copied().unwrap_or(0.0), - denorm.get(5).copied().unwrap_or(0.0), - denorm.get(6).copied().unwrap_or(0.0), - denorm.get(7).copied().unwrap_or(0.0) - ); - // #region agent log - Self::append_runtime_debug_log( - "run-pkt6-long-imdct-v1", - "H81", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_pkt6_denorm_after_denormalise", - &data, - ); - // #endregion - } - if is_transient { - // Transient CELT frames are composed of 120-sample short blocks. - let m_blocks = 1usize << lm; - let short_len = frame_size_48k / m_blocks; - let mut short_coeffs = vec![0.0f32; short_len]; - - for b in 0..m_blocks { - let out_offset = out_start + b * short_len; - let overlap_in_abs: f32 = self.decode_mem - [out_offset..out_offset + self.mode.overlap] - .iter() - .map(|x| x.abs()) - .sum(); - for j in 0..short_len { - short_coeffs[j] = denorm[j * m_blocks + b]; - } - if trace_transient_pkt5 { - let coeff_abs_sum: f32 = short_coeffs.iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"after_deinterleave_before_imdct\",\"block\":{},\"blocks_total\":{},\"short_len\":{},\"coeff_abs_sum\":{},\"coeff_first5\":[{:.9},{:.9},{:.9},{:.9},{:.9}]}}", - packet_idx, - b, - m_blocks, - short_len, - coeff_abs_sum, - short_coeffs.get(0).copied().unwrap_or(0.0), - short_coeffs.get(1).copied().unwrap_or(0.0), - short_coeffs.get(2).copied().unwrap_or(0.0), - short_coeffs.get(3).copied().unwrap_or(0.0), - short_coeffs.get(4).copied().unwrap_or(0.0) - ); - // #region agent log - Self::append_runtime_debug_log( - "run-transient-imdct-pkt5-v1", - "H31", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_short_block_deinterleaved", - &data, - ); - // #endregion - } - let short_coeff_abs: f32 = short_coeffs.iter().map(|x| x.abs()).sum(); - let short_coeff_abs_contig: f32 = (0..short_len) - .map(|j| denorm[b * short_len + j].abs()) - .sum(); - self.mdct_short - .backward( - &short_coeffs, - self.mode.window, - &mut self.decode_mem - [out_offset..out_offset + short_len + self.mode.overlap], - ) - .map_err(|_| Error::NotImplemented)?; - if trace_transient_pkt5 { - let block_out = - &self.decode_mem[out_offset..out_offset + short_len + self.mode.overlap]; - let block_abs_sum: f32 = block_out.iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"after_short_imdct_before_overlap_chain\",\"block\":{},\"blocks_total\":{},\"block_out_len\":{},\"block_out_abs_sum\":{},\"block_out_first5\":[{:.9},{:.9},{:.9},{:.9},{:.9}]}}", - packet_idx, - b, - m_blocks, - block_out.len(), - block_abs_sum, - block_out.get(0).copied().unwrap_or(0.0), - block_out.get(1).copied().unwrap_or(0.0), - block_out.get(2).copied().unwrap_or(0.0), - block_out.get(3).copied().unwrap_or(0.0), - block_out.get(4).copied().unwrap_or(0.0) - ); - // #region agent log - Self::append_runtime_debug_log( - "run-transient-imdct-pkt5-v1", - "H32", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_short_block_imdct_out", - &data, - ); - // #endregion - } - if (packet_idx == 0 || (2127..=2129).contains(&packet_idx)) - && (packet_idx == 0 || b == 0 || b + 1 == m_blocks) - { - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let head_abs: f32 = self.decode_mem - [out_offset..out_offset + self.mode.overlap] - .iter() - .map(|x| x.abs()) - .sum(); - let tail_abs: f32 = self.decode_mem - [out_offset + short_len..out_offset + short_len + self.mode.overlap] - .iter() - .map(|x| x.abs()) - .sum(); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-synthesis-path-scan\",\"hypothesisId\":\"H16\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:804\",\"message\":\"short_mdct_block_snapshot\",\"data\":{{\"packet_idx\":{},\"block\":{},\"blocks_total\":{},\"short_len\":{},\"overlap\":{},\"overlap_in_abs_sum\":{},\"short_coeff_abs_sum\":{},\"short_coeff_abs_contig\":{},\"short_tmp_head_abs_sum\":{},\"short_tmp_tail_abs_sum\":{}}},\"timestamp\":{}}}\n", - packet_idx, - b, - m_blocks, - short_len, - self.mode.overlap, - overlap_in_abs, - short_coeff_abs, - short_coeff_abs_contig, - head_abs, - tail_abs, - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - } - base_synth.copy_from_slice(&self.decode_mem[out_start..out_start + frame_size_48k]); - if trace_transient_pkt5 { - let overlap_tail = self.overlap_tail(); - let base_abs_sum: f32 = base_synth.iter().map(|x| x.abs()).sum(); - let overlap_abs_sum: f32 = overlap_tail.iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"after_overlap_add_all_short_blocks\",\"result_abs_sum\":{},\"result_first8\":[{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9}],\"new_overlap_abs_sum\":{},\"new_overlap_first8\":[{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9}]}}", - packet_idx, - base_abs_sum, - base_synth.get(0).copied().unwrap_or(0.0), - base_synth.get(1).copied().unwrap_or(0.0), - base_synth.get(2).copied().unwrap_or(0.0), - base_synth.get(3).copied().unwrap_or(0.0), - base_synth.get(4).copied().unwrap_or(0.0), - base_synth.get(5).copied().unwrap_or(0.0), - base_synth.get(6).copied().unwrap_or(0.0), - base_synth.get(7).copied().unwrap_or(0.0), - overlap_abs_sum, - overlap_tail.first().copied().unwrap_or(0.0), - overlap_tail.get(1).copied().unwrap_or(0.0), - overlap_tail.get(2).copied().unwrap_or(0.0), - overlap_tail.get(3).copied().unwrap_or(0.0), - overlap_tail.get(4).copied().unwrap_or(0.0), - overlap_tail.get(5).copied().unwrap_or(0.0), - overlap_tail.get(6).copied().unwrap_or(0.0), - overlap_tail.get(7).copied().unwrap_or(0.0) - ); - // #region agent log - Self::append_runtime_debug_log( - "run-transient-imdct-pkt5-v1", - "H33", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_transient_overlap_result", - &data, - ); - // #endregion - } - } else { - // Long block path for all supported frame sizes. - let mdct_impl = match frame_size_48k { - 120 => &self.mdct_short, - 240 => &self.mdct_240, - 480 => &self.mdct_480, - 960 => &self.mdct, - _ => return Err(Error::NotImplemented), - }; - mdct_impl - .backward( - &denorm, - self.mode.window, - &mut self.decode_mem[out_start..out_start + frame_size_48k + self.mode.overlap], - ) - .map_err(|_| Error::NotImplemented)?; - if trace_long_pkt6 { - let out_abs_sum: f32 = self.decode_mem[out_start..out_start + frame_size_48k] - .iter() - .map(|x| x.abs()) - .sum(); - let ovl_abs_sum: f32 = self.decode_mem - [out_start + frame_size_48k..out_start + frame_size_48k + self.mode.overlap] - .iter() - .map(|x| x.abs()) - .sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"after_long_imdct\",\"out_abs_sum\":{},\"out_first8\":[{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9}],\"new_overlap_abs_sum\":{},\"new_overlap_first8\":[{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9}]}}", - packet_idx, - out_abs_sum, - self.decode_mem.get(out_start).copied().unwrap_or(0.0), - self.decode_mem.get(out_start + 1).copied().unwrap_or(0.0), - self.decode_mem.get(out_start + 2).copied().unwrap_or(0.0), - self.decode_mem.get(out_start + 3).copied().unwrap_or(0.0), - self.decode_mem.get(out_start + 4).copied().unwrap_or(0.0), - self.decode_mem.get(out_start + 5).copied().unwrap_or(0.0), - self.decode_mem.get(out_start + 6).copied().unwrap_or(0.0), - self.decode_mem.get(out_start + 7).copied().unwrap_or(0.0), - ovl_abs_sum, - self.decode_mem - .get(out_start + frame_size_48k) - .copied() - .unwrap_or(0.0), - self.decode_mem - .get(out_start + frame_size_48k + 1) - .copied() - .unwrap_or(0.0), - self.decode_mem - .get(out_start + frame_size_48k + 2) - .copied() - .unwrap_or(0.0), - self.decode_mem - .get(out_start + frame_size_48k + 3) - .copied() - .unwrap_or(0.0), - self.decode_mem - .get(out_start + frame_size_48k + 4) - .copied() - .unwrap_or(0.0), - self.decode_mem - .get(out_start + frame_size_48k + 5) - .copied() - .unwrap_or(0.0), - self.decode_mem - .get(out_start + frame_size_48k + 6) - .copied() - .unwrap_or(0.0), - self.decode_mem - .get(out_start + frame_size_48k + 7) - .copied() - .unwrap_or(0.0) - ); - // #region agent log - Self::append_runtime_debug_log( - "run-pkt6-long-imdct-v1", - "H82", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_pkt6_long_imdct_out", - &data, - ); - // #endregion - } - if packet_idx == 0 || (2127..=2129).contains(&packet_idx) { - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let head_abs: f32 = self.decode_mem[out_start..out_start + self.mode.overlap] - .iter() - .map(|x| x.abs()) - .sum(); - let tail_abs: f32 = self.decode_mem[out_start + frame_size_48k - ..out_start + frame_size_48k + self.mode.overlap] - .iter() - .map(|x| x.abs()) - .sum(); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-synthesis-path-scan\",\"hypothesisId\":\"H17\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:854\",\"message\":\"long_mdct_snapshot\",\"data\":{{\"packet_idx\":{},\"frame_size_48k\":{},\"overlap\":{},\"mdct_head_abs_sum\":{},\"mdct_tail_abs_sum\":{}}},\"timestamp\":{}}}\n", - packet_idx, frame_size_48k, self.mode.overlap, head_abs, tail_abs, ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - base_synth.copy_from_slice(&self.decode_mem[out_start..out_start + frame_size_48k]); - } - if packet_idx == 0 || (2127..=2129).contains(&packet_idx) { - let overlap_tail = self.overlap_tail(); - let ovl_sum: f32 = overlap_tail.iter().map(|x| x.abs()).sum(); - let ovl_n = overlap_tail.len().min(8); - debug_trace!( - "R pkt{} overlap_out len={} abs_sum={:.6} first8: {:?}", - packet_idx, - overlap_tail.len(), - ovl_sum, - &overlap_tail[..ovl_n] - ); - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-postfilter-overlap-scan\",\"hypothesisId\":\"H13\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:793\",\"message\":\"overlap_out_snapshot\",\"data\":{{\"packet_idx\":{},\"coded_channels\":{},\"overlap_len\":{},\"overlap_abs_sum\":{},\"overlap_first8\":[{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6}]}} ,\"timestamp\":{}}}\n", - packet_idx, - coded_channels, - overlap_tail.len(), - ovl_sum, - overlap_tail.first().copied().unwrap_or(0.0), - overlap_tail.get(1).copied().unwrap_or(0.0), - overlap_tail.get(2).copied().unwrap_or(0.0), - overlap_tail.get(3).copied().unwrap_or(0.0), - overlap_tail.get(4).copied().unwrap_or(0.0), - overlap_tail.get(5).copied().unwrap_or(0.0), - overlap_tail.get(6).copied().unwrap_or(0.0), - overlap_tail.get(7).copied().unwrap_or(0.0), - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - if packet_idx == 905 { - let post_imdct_abs: f32 = base_synth.iter().map(|x| x.abs()).sum(); - let first_n = base_synth.len().min(8); - debug_trace!( - "R pkt905 post_imdct abs_sum={:.6} first{}={:?}", - post_imdct_abs, - first_n, - &base_synth[..first_n] - ); - } - if debug_packet { - let post_imdct_abs: f32 = base_synth.iter().map(|x| x.abs()).sum(); - let overlap_abs_out: f32 = self.overlap_tail().iter().map(|x| x.abs()).sum(); - let mut first8_csv = String::new(); - for x in base_synth.iter().take(8) { - if !first8_csv.is_empty() { - first8_csv.push(';'); - } - first8_csv.push_str(&format!("{:.6}", x)); - } - let data = format!( - "{{\"packet_idx\":{},\"is_transient\":{},\"lm\":{},\"post_imdct_abs\":{},\"overlap_abs_out\":{},\"base_first8_csv\":\"{}\"}}", - packet_idx, is_transient, lm, post_imdct_abs, overlap_abs_out, first8_csv - ); - // #region agent log - Self::append_debug_log( - "run-onset-map-v1", - "H2", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "post_imdct_overlap_out", - &data, - ); - // #endregion - } - if trace_this_packet { - let post_imdct_abs: f32 = base_synth.iter().map(|x| x.abs()).sum(); - // #region agent log - debug_trace!( - "R pkt{} comb_pre c=0 abs_sum={:.6}", - packet_idx, - post_imdct_abs - ); - debug_trace!( - "R pkt{} comb_pre_first8 c=0: {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6}", - packet_idx, - base_synth.first().copied().unwrap_or(0.0), - base_synth.get(1).copied().unwrap_or(0.0), - base_synth.get(2).copied().unwrap_or(0.0), - base_synth.get(3).copied().unwrap_or(0.0), - base_synth.get(4).copied().unwrap_or(0.0), - base_synth.get(5).copied().unwrap_or(0.0), - base_synth.get(6).copied().unwrap_or(0.0), - base_synth.get(7).copied().unwrap_or(0.0), - ); - // #endregion - } - if trace_this_packet && self.channels == 2 { - let right_pre = &self.decode_mem_right[out_start..out_start + frame_size_48k]; - let right_pre_abs: f32 = right_pre.iter().map(|x| x.abs()).sum(); - // #region agent log - debug_trace!( - "R pkt{} comb_pre c=1 abs_sum={:.6}", - packet_idx, - right_pre_abs - ); - debug_trace!( - "R pkt{} comb_pre_first8 c=1: {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6}", - packet_idx, - right_pre.first().copied().unwrap_or(0.0), - right_pre.get(1).copied().unwrap_or(0.0), - right_pre.get(2).copied().unwrap_or(0.0), - right_pre.get(3).copied().unwrap_or(0.0), - right_pre.get(4).copied().unwrap_or(0.0), - right_pre.get(5).copied().unwrap_or(0.0), - right_pre.get(6).copied().unwrap_or(0.0), - right_pre.get(7).copied().unwrap_or(0.0), - ); - // #endregion - } - if self.channels == 2 { - let window = self.mode.window; - let overlap = self.mode.overlap; - if let Some(denorm_right) = denorm_right_for_stereo.as_deref() { - Self::synthesise_channel_to_mem( - denorm_right, - frame_size_48k, - lm, - is_transient, - window, - overlap, - &self.mdct, - &self.mdct_480, - &self.mdct_240, - &self.mdct_short, - &mut self.decode_mem_right, - )?; - } else { - Self::synthesise_channel_to_mem( - &denorm, - frame_size_48k, - lm, - is_transient, - window, - overlap, - &self.mdct, - &self.mdct_480, - &self.mdct_240, - &self.mdct_short, - &mut self.decode_mem_right, - )?; - } - } - - if packet_idx == 5 { - let overlap_tail = self.overlap_tail(); - let ovl_abs_sum: f32 = overlap_tail.iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"post_imdct\",\"overlap_tail_abs_sum\":{},\"overlap_tail_first4\":[{:.9},{:.9},{:.9},{:.9}]}}", - packet_idx, - ovl_abs_sum, - overlap_tail.first().copied().unwrap_or(0.0), - overlap_tail.get(1).copied().unwrap_or(0.0), - overlap_tail.get(2).copied().unwrap_or(0.0), - overlap_tail.get(3).copied().unwrap_or(0.0) - ); - // #region agent log - Self::append_runtime_debug_log( - "run-post-imdct-state-pkt5-v1", - "H34", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_post_imdct_overlap_tail", - &data, - ); - // #endregion - } - - let postfilter_before_abs: f32 = self.decode_mem[out_start..out_start + frame_size_48k] - .iter() - .map(|x| x.abs()) - .sum(); - let pre_s163 = if frame_size_48k > 163 { - self.decode_mem[out_start + 163] - } else { - 0.0 - }; - if packet_idx == 0 && frame_size_48k >= self.mode.short_mdct_size { - let chunk = self.mode.short_mdct_size; - let chunks = frame_size_48k / chunk; - if chunks == 8 { - let mut chunk_abs = [0.0f32; 8]; - for b in 0..8 { - let s = b * chunk; - let e = s + chunk; - chunk_abs[b] = self.decode_mem[out_start + s..out_start + e] - .iter() - .map(|x| x.abs()) - .sum(); - } - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-precomb-chunk-scan\",\"hypothesisId\":\"H23\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:1018\",\"message\":\"precomb_chunk_abs\",\"data\":{{\"packet_idx\":{},\"chunk_size\":{},\"chunk_abs\":[{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6},{:.6}],\"total_abs\":{}}},\"timestamp\":{}}}\n", - packet_idx, - chunk, - chunk_abs[0], - chunk_abs[1], - chunk_abs[2], - chunk_abs[3], - chunk_abs[4], - chunk_abs[5], - chunk_abs[6], - chunk_abs[7], - postfilter_before_abs, - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - } - let pf_period = self.postfilter_period.max(COMBFILTER_MINPERIOD as i32) as usize; - let pf_period_old = self.postfilter_period_old.max(COMBFILTER_MINPERIOD as i32) as usize; - let decoded_tapset = postfilter_tapset.clamp(0, 2) as usize; - let short_n = self.mode.short_mdct_size.min(frame_size_48k); - let disable_comb_filter = false; - if !disable_comb_filter { - Self::comb_filter_in_place( - &mut self.decode_mem, - out_start, - pf_period_old, - pf_period, - short_n, - self.postfilter_gain_old, - self.postfilter_gain, - self.postfilter_tapset_old, - self.postfilter_tapset, - self.mode.window, - self.mode.overlap, - ); - if lm != 0 && frame_size_48k > short_n { - Self::comb_filter_in_place( - &mut self.decode_mem, - out_start + short_n, - pf_period, - postfilter_pitch.max(0) as usize, - frame_size_48k - short_n, - self.postfilter_gain, - postfilter_gain, - self.postfilter_tapset, - decoded_tapset, - self.mode.window, - self.mode.overlap, - ); - } - if self.channels == 2 { - Self::comb_filter_in_place( - &mut self.decode_mem_right, - out_start, - pf_period_old, - pf_period, - short_n, - self.postfilter_gain_old, - self.postfilter_gain, - self.postfilter_tapset_old, - self.postfilter_tapset, - self.mode.window, - self.mode.overlap, - ); - if lm != 0 && frame_size_48k > short_n { - Self::comb_filter_in_place( - &mut self.decode_mem_right, - out_start + short_n, - pf_period, - postfilter_pitch.max(0) as usize, - frame_size_48k - short_n, - self.postfilter_gain, - postfilter_gain, - self.postfilter_tapset, - decoded_tapset, - self.mode.window, - self.mode.overlap, - ); - } - } - } - if packet_idx == 5 { - let overlap_start = DECODE_BUFFER_SIZE; - let overlap_end = overlap_start + self.mode.overlap; - let ovl = &self.decode_mem[overlap_start..overlap_end]; - let ovl_abs_sum: f32 = ovl.iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"post_comb\",\"overlap_tail_abs_sum\":{},\"overlap_tail_first4\":[{:.9},{:.9},{:.9},{:.9}],\"comb_filter_disabled\":{}}}", - packet_idx, - ovl_abs_sum, - ovl.get(0).copied().unwrap_or(0.0), - ovl.get(1).copied().unwrap_or(0.0), - ovl.get(2).copied().unwrap_or(0.0), - ovl.get(3).copied().unwrap_or(0.0), - disable_comb_filter - ); - // #region agent log - Self::append_runtime_debug_log( - "run-post-imdct-state-pkt5-v1", - "H35", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_post_comb_overlap_tail", - &data, - ); - // #endregion - } - self.postfilter_period_old = self.postfilter_period; - self.postfilter_gain_old = self.postfilter_gain; - self.postfilter_tapset_old = self.postfilter_tapset; - self.postfilter_period = postfilter_pitch; - self.postfilter_gain = postfilter_gain; - self.postfilter_tapset = decoded_tapset; - if lm != 0 { - self.postfilter_period_old = self.postfilter_period; - self.postfilter_gain_old = self.postfilter_gain; - self.postfilter_tapset_old = self.postfilter_tapset; - } - if packet_idx <= 2 || (2127..=2129).contains(&packet_idx) { - let postfilter_after_abs: f32 = self.decode_mem[out_start..out_start + frame_size_48k] - .iter() - .map(|x| x.abs()) - .sum(); - let post_s163 = if frame_size_48k > 163 { - self.decode_mem[out_start + 163] - } else { - 0.0 - }; - let mut post_win_csv = String::new(); - if frame_size_48k > 170 { - for i in 160..=170 { - if !post_win_csv.is_empty() { - post_win_csv.push(';'); - } - post_win_csv.push_str(&format!("{i}:{:.6}", self.decode_mem[out_start + i])); - } - } - let mut post_win2_csv = String::new(); - if frame_size_48k > 220 { - for i in 200..=220 { - if !post_win2_csv.is_empty() { - post_win2_csv.push(';'); - } - post_win2_csv.push_str(&format!("{i}:{:.6}", self.decode_mem[out_start + i])); - } - } - let mut post_prefix_csv = String::new(); - if packet_idx == 0 && frame_size_48k > 170 { - for i in 0..=170 { - if !post_prefix_csv.is_empty() { - post_prefix_csv.push(';'); - } - post_prefix_csv.push_str(&format!("{i}:{:.6}", self.decode_mem[out_start + i])); - } - } - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-postfilter-apply\",\"hypothesisId\":\"H19\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:980\",\"message\":\"comb_filter_applied\",\"data\":{{\"packet_idx\":{},\"lm\":{},\"postfilter_pitch\":{},\"postfilter_gain\":{},\"postfilter_tapset\":{},\"state_pitch\":{},\"state_gain\":{},\"state_tapset\":{},\"pre_abs_sum\":{},\"post_abs_sum\":{},\"pre_s163\":{},\"post_s163\":{},\"post_win_csv\":\"{}\",\"post_win2_csv\":\"{}\",\"post_prefix_csv\":\"{}\"}},\"timestamp\":{}}}\n", - packet_idx, - lm, - postfilter_pitch, - postfilter_gain, - decoded_tapset, - pf_period, - self.postfilter_gain, - self.postfilter_tapset, - postfilter_before_abs, - postfilter_after_abs, - pre_s163, - post_s163, - post_win_csv, - post_win2_csv, - post_prefix_csv, - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - if packet_idx == 905 { - let post_comb = &self.decode_mem[out_start..out_start + frame_size_48k]; - let post_comb_abs: f32 = post_comb.iter().map(|x| x.abs()).sum(); - let first_n = post_comb.len().min(8); - debug_trace!( - "R pkt905 post_comb abs_sum={:.6} first{}={:?}", - post_comb_abs, - first_n, - &post_comb[..first_n] - ); - } - if debug_packet { - let post_comb = &self.decode_mem[out_start..out_start + frame_size_48k]; - let post_comb_abs: f32 = post_comb.iter().map(|x| x.abs()).sum(); - let mut first8_csv = String::new(); - for x in post_comb.iter().take(8) { - if !first8_csv.is_empty() { - first8_csv.push(';'); - } - first8_csv.push_str(&format!("{:.6}", x)); - } - let post_s163 = if frame_size_48k > 163 { - post_comb[163] - } else { - 0.0 - }; - let mut post_win_160_170_csv = String::new(); - if frame_size_48k > 170 { - for i in 160..=170 { - if !post_win_160_170_csv.is_empty() { - post_win_160_170_csv.push(';'); - } - post_win_160_170_csv.push_str(&format!("{}:{:.6}", i, post_comb[i])); - } - } - let data = format!( - "{{\"packet_idx\":{},\"disable_comb_filter\":{},\"post_comb_abs\":{},\"post_comb_first8_csv\":\"{}\",\"post_comb_s163\":{},\"post_comb_160_170_csv\":\"{}\",\"pf_period_old_in\":{},\"pf_period_in\":{},\"pf_period_old_out\":{},\"pf_period_out\":{},\"pf_gain_old_out\":{},\"pf_gain_out\":{},\"pf_tapset_old_out\":{},\"pf_tapset_out\":{}}}", - packet_idx, - disable_comb_filter, - post_comb_abs, - first8_csv, - post_s163, - post_win_160_170_csv, - pf_period_old, - pf_period, - self.postfilter_period_old, - self.postfilter_period, - self.postfilter_gain_old, - self.postfilter_gain, - self.postfilter_tapset_old, - self.postfilter_tapset - ); - // #region agent log - Self::append_debug_log( - "run-onset-map-v2", - "H2", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "post_comb_and_pf_state", - &data, - ); - // #endregion - } - if trace_this_packet { - let post_comb = &self.decode_mem[out_start..out_start + frame_size_48k]; - let post_comb_abs: f32 = post_comb.iter().map(|x| x.abs()).sum(); - // #region agent log - debug_trace!( - "R pkt{} comb_post c=0 abs_sum={:.6} first16: {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6}", - packet_idx, - post_comb_abs, - post_comb.first().copied().unwrap_or(0.0), - post_comb.get(1).copied().unwrap_or(0.0), - post_comb.get(2).copied().unwrap_or(0.0), - post_comb.get(3).copied().unwrap_or(0.0), - post_comb.get(4).copied().unwrap_or(0.0), - post_comb.get(5).copied().unwrap_or(0.0), - post_comb.get(6).copied().unwrap_or(0.0), - post_comb.get(7).copied().unwrap_or(0.0), - post_comb.get(8).copied().unwrap_or(0.0), - post_comb.get(9).copied().unwrap_or(0.0), - post_comb.get(10).copied().unwrap_or(0.0), - post_comb.get(11).copied().unwrap_or(0.0), - post_comb.get(12).copied().unwrap_or(0.0), - post_comb.get(13).copied().unwrap_or(0.0), - post_comb.get(14).copied().unwrap_or(0.0), - post_comb.get(15).copied().unwrap_or(0.0), - ); - // #endregion - } - if trace_this_packet && self.channels == 2 { - let post_comb_r = &self.decode_mem_right[out_start..out_start + frame_size_48k]; - let post_comb_r_abs: f32 = post_comb_r.iter().map(|x| x.abs()).sum(); - // #region agent log - debug_trace!( - "R pkt{} comb_post c=1 abs_sum={:.6} first16: {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6}", - packet_idx, - post_comb_r_abs, - post_comb_r.first().copied().unwrap_or(0.0), - post_comb_r.get(1).copied().unwrap_or(0.0), - post_comb_r.get(2).copied().unwrap_or(0.0), - post_comb_r.get(3).copied().unwrap_or(0.0), - post_comb_r.get(4).copied().unwrap_or(0.0), - post_comb_r.get(5).copied().unwrap_or(0.0), - post_comb_r.get(6).copied().unwrap_or(0.0), - post_comb_r.get(7).copied().unwrap_or(0.0), - post_comb_r.get(8).copied().unwrap_or(0.0), - post_comb_r.get(9).copied().unwrap_or(0.0), - post_comb_r.get(10).copied().unwrap_or(0.0), - post_comb_r.get(11).copied().unwrap_or(0.0), - post_comb_r.get(12).copied().unwrap_or(0.0), - post_comb_r.get(13).copied().unwrap_or(0.0), - post_comb_r.get(14).copied().unwrap_or(0.0), - post_comb_r.get(15).copied().unwrap_or(0.0), - ); - if frame_size_48k > 750 { - debug_trace!( - "R pkt{} comb_post_740_750 c=1: {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6} {:.6}", - packet_idx, - post_comb_r[740], - post_comb_r[741], - post_comb_r[742], - post_comb_r[743], - post_comb_r[744], - post_comb_r[745], - post_comb_r[746], - post_comb_r[747], - post_comb_r[748], - post_comb_r[749], - post_comb_r[750], - ); - } - // #endregion - } - if packet_idx == 5 { - let overlap_tail = self.overlap_tail(); - let ovl_abs_sum: f32 = overlap_tail.iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"final_state\",\"overlap_abs_sum\":{},\"overlap_first4\":[{:.9},{:.9},{:.9},{:.9}],\"separate_overlap_buffer\":false}}", - packet_idx, - ovl_abs_sum, - overlap_tail.first().copied().unwrap_or(0.0), - overlap_tail.get(1).copied().unwrap_or(0.0), - overlap_tail.get(2).copied().unwrap_or(0.0), - overlap_tail.get(3).copied().unwrap_or(0.0) - ); - // #region agent log - Self::append_runtime_debug_log( - "run-post-imdct-state-pkt5-v1", - "H36", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_final_state_overlap", - &data, - ); - // #endregion - } - - let nb_ebands = self.mode.nb_ebands; - let state_len = 2 * nb_ebands; - if coded_channels == 1 { - let (left, right) = self.prev_energy.split_at_mut(nb_ebands); - right.copy_from_slice(left); - } - if !is_transient { - self.old_log_energy2[..state_len].copy_from_slice(&self.old_log_energy[..state_len]); - self.old_log_energy[..state_len].copy_from_slice(&self.prev_energy[..state_len]); - } else { - for i in 0..state_len { - self.old_log_energy[i] = self.old_log_energy[i].min(self.prev_energy[i]); - } - } - let m = 1usize << lm; - let max_background_increase = (m.min(160) as f32) * 0.001; - for i in 0..state_len { - self.background_log_energy[i] = - (self.background_log_energy[i] + max_background_increase).min(self.prev_energy[i]); - } - for c in 0..2usize { - let base = c * nb_ebands; - for i in 0..start.min(nb_ebands) { - self.prev_energy[base + i] = 0.0; - self.old_log_energy[base + i] = LOG_ENERGY_FLOOR_DB; - self.old_log_energy2[base + i] = LOG_ENERGY_FLOOR_DB; - } - for i in end.min(nb_ebands)..nb_ebands { - self.prev_energy[base + i] = 0.0; - self.old_log_energy[base + i] = LOG_ENERGY_FLOOR_DB; - self.old_log_energy2[base + i] = LOG_ENERGY_FLOOR_DB; - } - } - if trace_this_packet { - debug_trace!( - "R pkt{} energy_state_post prev={:?} old1={:?} old2={:?}", - packet_idx, - &self.prev_energy[..nb_ebands], - &self.old_log_energy[..nb_ebands], - &self.old_log_energy2[..nb_ebands] - ); - } - - if packet_idx == 6 { - let pre = &self.decode_mem[out_start..out_start + frame_size_48k]; - let pre_abs_sum: f32 = pre.iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"pre_deemph\",\"pre_abs_sum\":{},\"pre_first8\":[{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9}],\"deemph_mem0_in\":{}}}", - packet_idx, - pre_abs_sum, - pre.get(0).copied().unwrap_or(0.0), - pre.get(1).copied().unwrap_or(0.0), - pre.get(2).copied().unwrap_or(0.0), - pre.get(3).copied().unwrap_or(0.0), - pre.get(4).copied().unwrap_or(0.0), - pre.get(5).copied().unwrap_or(0.0), - pre.get(6).copied().unwrap_or(0.0), - pre.get(7).copied().unwrap_or(0.0), - self.deemph_mem[0] - ); - // #region agent log - Self::append_runtime_debug_log( - "run-pkt6-deemph-v1", - "H71", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_pkt6_pre_deemph", - &data, - ); - // #endregion - } - for ch in 0..self.channels as usize { - let decode_mem_ch = if ch == 0 { - &self.decode_mem - } else { - &self.decode_mem_right - }; - let mut ch_synth = decode_mem_ch[out_start..out_start + frame_size_48k].to_vec(); - self.apply_deemph(ch, &mut ch_synth); - if packet_idx == 6 && ch == 0 { - let post_abs_sum: f32 = ch_synth.iter().map(|x| x.abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"post_deemph\",\"post_abs_sum\":{},\"post_first8\":[{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9},{:.9}],\"deemph_mem0_out\":{}}}", - packet_idx, - post_abs_sum, - ch_synth.get(0).copied().unwrap_or(0.0), - ch_synth.get(1).copied().unwrap_or(0.0), - ch_synth.get(2).copied().unwrap_or(0.0), - ch_synth.get(3).copied().unwrap_or(0.0), - ch_synth.get(4).copied().unwrap_or(0.0), - ch_synth.get(5).copied().unwrap_or(0.0), - ch_synth.get(6).copied().unwrap_or(0.0), - ch_synth.get(7).copied().unwrap_or(0.0), - self.deemph_mem[ch] - ); - // #region agent log - Self::append_runtime_debug_log( - "run-pkt6-deemph-v1", - "H72", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_pkt6_post_deemph", - &data, - ); - // #endregion - } - if packet_idx == 905 && ch == 0 { - let post_deemph_abs: f32 = ch_synth.iter().map(|x| x.abs()).sum(); - let first_n = ch_synth.len().min(8); - debug_trace!( - "R pkt905 post_deemph abs_sum={:.6} first{}={:?}", - post_deemph_abs, - first_n, - &ch_synth[..first_n] - ); - } - if debug_packet && ch == 0 { - let post_deemph_abs: f32 = ch_synth.iter().map(|x| x.abs()).sum(); - let mut first8_csv = String::new(); - for x in ch_synth.iter().take(8) { - if !first8_csv.is_empty() { - first8_csv.push(';'); - } - first8_csv.push_str(&format!("{:.6}", x)); - } - let data = format!( - "{{\"packet_idx\":{},\"post_deemph_abs\":{},\"deemph_mem0_out\":{},\"post_deemph_first8_csv\":\"{}\"}}", - packet_idx, post_deemph_abs, self.deemph_mem[ch], first8_csv - ); - // #region agent log - Self::append_debug_log( - "run-onset-map-v1", - "H1", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "post_deemph", - &data, - ); - // #endregion - } - if trace_this_packet && ch == 0 { - let post_deemph_abs: f32 = ch_synth.iter().map(|x| x.abs()).sum(); - // #region agent log - debug_trace!( - "R pkt{} post_deemph_abs_sum={:.6}", - packet_idx, - post_deemph_abs - ); - debug_trace!( - "R pkt{} pcm[0..8]: {} {} {} {} {} {} {} {}", - packet_idx, - Self::float_to_i16(ch_synth.first().copied().unwrap_or(0.0)), - Self::float_to_i16(ch_synth.get(1).copied().unwrap_or(0.0)), - Self::float_to_i16(ch_synth.get(2).copied().unwrap_or(0.0)), - Self::float_to_i16(ch_synth.get(3).copied().unwrap_or(0.0)), - Self::float_to_i16(ch_synth.get(4).copied().unwrap_or(0.0)), - Self::float_to_i16(ch_synth.get(5).copied().unwrap_or(0.0)), - Self::float_to_i16(ch_synth.get(6).copied().unwrap_or(0.0)), - Self::float_to_i16(ch_synth.get(7).copied().unwrap_or(0.0)) - ); - // #endregion - } - if trace_this_packet && ch == 1 { - let post_deemph_abs: f32 = ch_synth.iter().map(|x| x.abs()).sum(); - // #region agent log - debug_trace!( - "R pkt{} post_deemph_abs_sum_ch1={:.6}", - packet_idx, - post_deemph_abs - ); - debug_trace!( - "R pkt{} pcm_ch1[0..8]: {} {} {} {} {} {} {} {}", - packet_idx, - Self::float_to_i16(ch_synth.first().copied().unwrap_or(0.0)), - Self::float_to_i16(ch_synth.get(1).copied().unwrap_or(0.0)), - Self::float_to_i16(ch_synth.get(2).copied().unwrap_or(0.0)), - Self::float_to_i16(ch_synth.get(3).copied().unwrap_or(0.0)), - Self::float_to_i16(ch_synth.get(4).copied().unwrap_or(0.0)), - Self::float_to_i16(ch_synth.get(5).copied().unwrap_or(0.0)), - Self::float_to_i16(ch_synth.get(6).copied().unwrap_or(0.0)), - Self::float_to_i16(ch_synth.get(7).copied().unwrap_or(0.0)) - ); - if frame_size_48k > 750 { - debug_trace!( - "R pkt{} pcm_ch1[740..750]: {} {} {} {} {} {} {} {} {} {} {}", - packet_idx, - Self::float_to_i16(ch_synth[740]), - Self::float_to_i16(ch_synth[741]), - Self::float_to_i16(ch_synth[742]), - Self::float_to_i16(ch_synth[743]), - Self::float_to_i16(ch_synth[744]), - Self::float_to_i16(ch_synth[745]), - Self::float_to_i16(ch_synth[746]), - Self::float_to_i16(ch_synth[747]), - Self::float_to_i16(ch_synth[748]), - Self::float_to_i16(ch_synth[749]), - Self::float_to_i16(ch_synth[750]), - ); - } - debug_trace!( - "R pkt{} deemph_mem_ch1_out={:.6}", - packet_idx, - self.deemph_mem[1] - ); - // #endregion - } - if packet_idx == 0 && ch == 0 && frame_size_48k > 170 { - let mut win_csv = String::new(); - for i in 160..=170 { - if !win_csv.is_empty() { - win_csv.push(';'); - } - let si = Self::float_to_i16(ch_synth[i]); - win_csv.push_str(&format!("{i}:{:.6}:{si}", ch_synth[i])); - } - let mut win2_csv = String::new(); - if frame_size_48k > 220 { - for i in 200..=220 { - if !win2_csv.is_empty() { - win2_csv.push(';'); - } - let si = Self::float_to_i16(ch_synth[i]); - win2_csv.push_str(&format!("{i}:{:.6}:{si}", ch_synth[i])); - } - } - // #region agent log - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-bea564.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"4200c5\",\"runId\":\"run-deemph-scan\",\"hypothesisId\":\"H26\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:1110\",\"message\":\"deemph_window_packet0\",\"data\":{{\"packet_idx\":{},\"channel\":{},\"window_csv\":\"{}\",\"window2_csv\":\"{}\"}},\"timestamp\":{}}}\n", - packet_idx, ch, win_csv, win2_csv, ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - // #endregion - } - self.write_output_channel_i16(out, &ch_synth, ch, accum)?; - if packet_idx == 6 && ch == 0 { - let out_len = output_samples * self.channels as usize; - let out_slice = &out[..out_len]; - let out_abs_sum: i64 = out_slice.iter().map(|&x| (x as i64).abs()).sum(); - let data = format!( - "{{\"packet_idx\":{},\"stage\":\"post_quant_i16\",\"out_abs_sum\":{},\"out_first8\":[{},{},{},{},{},{},{},{}]}}", - packet_idx, - out_abs_sum, - out_slice.get(0).copied().unwrap_or(0), - out_slice.get(1).copied().unwrap_or(0), - out_slice.get(2).copied().unwrap_or(0), - out_slice.get(3).copied().unwrap_or(0), - out_slice.get(4).copied().unwrap_or(0), - out_slice.get(5).copied().unwrap_or(0), - out_slice.get(6).copied().unwrap_or(0), - out_slice.get(7).copied().unwrap_or(0) - ); - // #region agent log - Self::append_runtime_debug_log( - "run-pkt6-deemph-v1", - "H73", - "crates/opus-decoder/src/celt/mod.rs:decode_frame", - "rust_pkt6_post_quant_i16", - &data, - ); - // #endregion - } - } - - if packet_idx == 38 { - // #region agent log H103 - if let Ok(mut f) = std::fs::OpenOptions::new() - .create(true) - .append(true) - .open("/Users/tadeusz/Opus/Rasopus/.cursor/debug-9a4e1c.log") - { - let ts = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .map(|d| d.as_millis()) - .unwrap_or(0); - let line = format!( - "{{\"sessionId\":\"9a4e1c\",\"runId\":\"tv09-pkt38-celt-stage\",\"hypothesisId\":\"H103\",\"location\":\"crates/opus-decoder/src/celt/mod.rs:3300\",\"message\":\"pkt38_before_final_range\",\"data\":{{\"tell\":{},\"tell_frac\":{},\"rng\":{},\"seed\":{},\"is_transient\":{},\"anti_collapse_on\":{},\"lm\":{}}},\"timestamp\":{}}}\n", - ec.tell(), - ec.tell_frac(), - ec.rng(), - self.rng_seed, - is_transient, - anti_collapse_on, - lm, - ts - ); - let _ = std::io::Write::write_all(&mut f, line.as_bytes()); - } - } - self.final_range = ec.final_range(); - if packet_idx < 5 { - debug_trace!( - "pkt{} final_range: got={:08x}", - packet_idx, - self.final_range - ); - } - self.rng_seed = ec.rng(); - let _ = ( - OVERLAP_48K_20MS, - self.mode.preemph[0], - self.mode.overlap, - self.prev_energy.len(), - alloc.intensity, - alloc.dual_stereo, - quant_bands::e_means()[0], - postfilter_pitch, - rate::pulses2bits(self.mode, 0, lm as i32, 0), - ); - Ok(CeltFrameDecode { - samples_per_channel: output_samples, - is_transient, - }) - } -} - -/// Map CELT or Hybrid Opus config to active end band. -/// -/// Params: TOC config value `(toc >> 3) & 0x1f`. -/// Returns: exclusive end band index. -fn bandwidth_end(config: u8) -> usize { - match config { - 12 | 13 => 19, - 14 | 15 => 21, - 16..=19 => 13, - 20..=23 => 17, - 24..=27 => 19, - 28..=31 => 21, - _ => 21, - } -} - -/// Decode CELT TF flags for active bands. -/// -/// Params: band range, transient flag, mutable tf array, LM, frame bit budget and range decoder. -/// Returns: nothing; `tf_res` updated in-place. -fn tf_decode( - start: usize, - end: usize, - is_transient: bool, - tf_res: &mut [i32], - lm: usize, - total_bits: i32, - dec: &mut EcDec<'_>, -) { - let mut budget = total_bits.max(0); - let mut tell = dec.tell(); - let mut logp = if is_transient { 2 } else { 4 }; - let tf_select_rsv = lm > 0 && tell + logp + 1 <= budget; - if tf_select_rsv { - budget = budget.saturating_sub(1); - } - let mut tf_changed = 0i32; - let mut curr = 0i32; - for i in start..end { - if tell + logp <= budget { - curr ^= i32::from(dec.dec_bit_logp(logp as u32)); - tell = dec.tell(); - tf_changed |= curr; - } - tf_res[i] = curr; - logp = if is_transient { 4 } else { 5 }; - } - let mut tf_select = 0i32; - let idx0 = 4 * usize::from(is_transient) + tf_changed as usize; - let idx1 = 4 * usize::from(is_transient) + 2 + tf_changed as usize; - if tf_select_rsv && TF_SELECT_TABLE[lm][idx0] != TF_SELECT_TABLE[lm][idx1] { - tf_select = i32::from(dec.dec_bit_logp(1)); - } - for t in tf_res.iter_mut().take(end).skip(start) { - let idx = 4 * usize::from(is_transient) + 2 * tf_select as usize + *t as usize; - *t = TF_SELECT_TABLE[lm][idx] as i32; - } -} diff --git a/vendor/opus-decoder/src/celt/modes.rs b/vendor/opus-decoder/src/celt/modes.rs deleted file mode 100644 index a7cc2ed..0000000 --- a/vendor/opus-decoder/src/celt/modes.rs +++ /dev/null @@ -1,238 +0,0 @@ -#![allow(clippy::excessive_precision)] - -//! CELT static mode data (48 kHz / 960 / 120 overlap). -//! -//! The values are ported from libopus `modes.c` + `static_modes_float.h`. - -/// CELT pulse cache tables used by allocation/rate logic. -#[derive(Debug, Clone, Copy)] -pub(crate) struct PulseCache { - pub index: &'static [i16], - pub bits: &'static [u8], - pub caps: &'static [u8], -} - -/// Flattened CELT mode definition for decoder use. -#[derive(Debug, Clone, Copy)] -pub(crate) struct CeltMode { - pub overlap: usize, - pub nb_ebands: usize, - pub preemph: [f32; 4], - pub e_bands: &'static [i16], - pub short_mdct_size: usize, - pub nb_alloc_vectors: usize, - pub alloc_vectors: &'static [u8], - pub log_n: &'static [i16], - pub window: &'static [f32], - pub cache: PulseCache, -} - -/// Return the static CELT mode for 48 kHz, 20 ms frames, 2.5 ms overlap. -pub(crate) fn mode48000_960_120() -> &'static CeltMode { - &MODE_48000_960_120 -} - -const EBAND_5MS: [i16; 22] = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 34, 40, 48, 60, 78, 100, -]; - -const LOG_N_400: [i16; 21] = [ - 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 16, 16, 16, 21, 21, 24, 29, 34, 36, -]; - -const WINDOW_120: [f32; 120] = [ - 6.7286966e-05f32, - 0.00060551348, - 0.0016815970, - 0.0032947962, - 0.0054439943, - 0.0081276923, - 0.011344001, - 0.015090633, - 0.019364886, - 0.024163635, - 0.029483315, - 0.035319905, - 0.041668911, - 0.048525347, - 0.055883718, - 0.063737999, - 0.072081616, - 0.080907428, - 0.090207705, - 0.099974111, - 0.11019769, - 0.12086883, - 0.13197729, - 0.14351214, - 0.15546177, - 0.16781389, - 0.18055550, - 0.19367290, - 0.20715171, - 0.22097682, - 0.23513243, - 0.24960208, - 0.26436860, - 0.27941419, - 0.29472040, - 0.31026818, - 0.32603788, - 0.34200931, - 0.35816177, - 0.37447407, - 0.39092462, - 0.40749142, - 0.42415215, - 0.44088423, - 0.45766484, - 0.47447104, - 0.49127978, - 0.50806798, - 0.52481261, - 0.54149077, - 0.55807973, - 0.57455701, - 0.59090049, - 0.60708841, - 0.62309951, - 0.63891306, - 0.65450896, - 0.66986776, - 0.68497077, - 0.69980010, - 0.71433873, - 0.72857055, - 0.74248043, - 0.75605425, - 0.76927895, - 0.78214257, - 0.79463430, - 0.80674445, - 0.81846456, - 0.82978733, - 0.84070669, - 0.85121779, - 0.86131698, - 0.87100183, - 0.88027111, - 0.88912479, - 0.89756398, - 0.90559094, - 0.91320904, - 0.92042270, - 0.92723738, - 0.93365955, - 0.93969656, - 0.94535671, - 0.95064907, - 0.95558353, - 0.96017067, - 0.96442171, - 0.96834849, - 0.97196334, - 0.97527906, - 0.97830883, - 0.98106616, - 0.98356480, - 0.98581869, - 0.98784191, - 0.98964856, - 0.99125274, - 0.99266849, - 0.99390969, - 0.99499004, - 0.99592297, - 0.99672162, - 0.99739874, - 0.99796667, - 0.99843728, - 0.99882195, - 0.99913147, - 0.99937606, - 0.99956527, - 0.99970802, - 0.99981248, - 0.99988613, - 0.99993565, - 0.99996697, - 0.99998518, - 0.99999457, - 0.99999859, - 0.99999982, - 1.0000000, -]; - -const BAND_ALLOCATION: [u8; 231] = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 90, 80, 75, 69, 63, 56, 49, 40, - 34, 29, 20, 18, 10, 0, 0, 0, 0, 0, 0, 0, 0, 110, 100, 90, 84, 78, 71, 65, 58, 51, 45, 39, 32, - 26, 20, 12, 0, 0, 0, 0, 0, 0, 118, 110, 103, 93, 86, 80, 75, 70, 65, 59, 53, 47, 40, 31, 23, - 15, 4, 0, 0, 0, 0, 126, 119, 112, 104, 95, 89, 83, 78, 72, 66, 60, 54, 47, 39, 32, 25, 17, 12, - 1, 0, 0, 134, 127, 120, 114, 103, 97, 91, 85, 78, 72, 66, 60, 54, 47, 41, 35, 29, 23, 16, 10, - 1, 144, 137, 130, 124, 113, 107, 101, 95, 88, 82, 76, 70, 64, 57, 51, 45, 39, 33, 26, 15, 1, - 152, 145, 138, 132, 123, 117, 111, 105, 98, 92, 86, 80, 74, 67, 61, 55, 49, 43, 36, 20, 1, 162, - 155, 148, 142, 133, 127, 121, 115, 108, 102, 96, 90, 84, 77, 71, 65, 59, 53, 46, 30, 1, 172, - 165, 158, 152, 143, 137, 131, 125, 118, 112, 106, 100, 94, 87, 81, 75, 69, 63, 56, 45, 20, 200, - 200, 200, 200, 200, 200, 200, 200, 198, 193, 188, 183, 178, 173, 168, 163, 158, 153, 148, 129, - 104, -]; - -const CACHE_INDEX_50: [i16; 105] = [ - -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 41, 41, 41, 82, 82, 123, 164, 200, 222, 0, 0, 0, 0, - 0, 0, 0, 0, 41, 41, 41, 41, 123, 123, 123, 164, 164, 240, 266, 283, 295, 41, 41, 41, 41, 41, - 41, 41, 41, 123, 123, 123, 123, 240, 240, 240, 266, 266, 305, 318, 328, 336, 123, 123, 123, - 123, 123, 123, 123, 123, 240, 240, 240, 240, 305, 305, 305, 318, 318, 343, 351, 358, 364, 240, - 240, 240, 240, 240, 240, 240, 240, 305, 305, 305, 305, 343, 343, 343, 351, 351, 370, 376, 382, - 387, -]; - -const CACHE_BITS_50: [u8; 392] = [ - 40, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 40, 15, 23, 28, 31, 34, 36, 38, 39, 41, 42, 43, 44, 45, 46, 47, - 47, 49, 50, 51, 52, 53, 54, 55, 55, 57, 58, 59, 60, 61, 62, 63, 63, 65, 66, 67, 68, 69, 70, 71, - 71, 40, 20, 33, 41, 48, 53, 57, 61, 64, 66, 69, 71, 73, 75, 76, 78, 80, 82, 85, 87, 89, 91, 92, - 94, 96, 98, 101, 103, 105, 107, 108, 110, 112, 114, 117, 119, 121, 123, 124, 126, 128, 40, 23, - 39, 51, 60, 67, 73, 79, 83, 87, 91, 94, 97, 100, 102, 105, 107, 111, 115, 118, 121, 124, 126, - 129, 131, 135, 139, 142, 145, 148, 150, 153, 155, 159, 163, 166, 169, 172, 174, 177, 179, 35, - 28, 49, 65, 78, 89, 99, 107, 114, 120, 126, 132, 136, 141, 145, 149, 153, 159, 165, 171, 176, - 180, 185, 189, 192, 199, 205, 211, 216, 220, 225, 229, 232, 239, 245, 251, 21, 33, 58, 79, 97, - 112, 125, 137, 148, 157, 166, 174, 182, 189, 195, 201, 207, 217, 227, 235, 243, 251, 17, 35, - 63, 86, 106, 123, 139, 152, 165, 177, 187, 197, 206, 214, 222, 230, 237, 250, 25, 31, 55, 75, - 91, 105, 117, 128, 138, 146, 154, 161, 168, 174, 180, 185, 190, 200, 208, 215, 222, 229, 235, - 240, 245, 255, 16, 36, 65, 89, 110, 128, 144, 159, 173, 185, 196, 207, 217, 226, 234, 242, 250, - 11, 41, 74, 103, 128, 151, 172, 191, 209, 225, 241, 255, 9, 43, 79, 110, 138, 163, 186, 207, - 227, 246, 12, 39, 71, 99, 123, 144, 164, 182, 198, 214, 228, 241, 253, 9, 44, 81, 113, 142, - 168, 192, 214, 235, 255, 7, 49, 90, 127, 160, 191, 220, 247, 6, 51, 95, 134, 170, 203, 234, 7, - 47, 87, 123, 155, 184, 212, 237, 6, 52, 97, 137, 174, 208, 240, 5, 57, 106, 151, 192, 231, 5, - 59, 111, 158, 202, 243, 5, 55, 103, 147, 187, 224, 5, 60, 113, 161, 206, 248, 4, 65, 122, 175, - 224, 4, 67, 127, 182, 234, -]; - -const CACHE_CAPS_50: [u8; 168] = [ - 224, 224, 224, 224, 224, 224, 224, 224, 160, 160, 160, 160, 185, 185, 185, 178, 178, 168, 134, - 61, 37, 224, 224, 224, 224, 224, 224, 224, 224, 240, 240, 240, 240, 207, 207, 207, 198, 198, - 183, 144, 66, 40, 160, 160, 160, 160, 160, 160, 160, 160, 185, 185, 185, 185, 193, 193, 193, - 183, 183, 172, 138, 64, 38, 240, 240, 240, 240, 240, 240, 240, 240, 207, 207, 207, 207, 204, - 204, 204, 193, 193, 180, 143, 66, 40, 185, 185, 185, 185, 185, 185, 185, 185, 193, 193, 193, - 193, 193, 193, 193, 183, 183, 172, 138, 65, 39, 207, 207, 207, 207, 207, 207, 207, 207, 204, - 204, 204, 204, 201, 201, 201, 188, 188, 176, 141, 66, 40, 193, 193, 193, 193, 193, 193, 193, - 193, 193, 193, 193, 193, 194, 194, 194, 184, 184, 173, 139, 65, 39, 204, 204, 204, 204, 204, - 204, 204, 204, 201, 201, 201, 201, 198, 198, 198, 187, 187, 175, 140, 66, 40, -]; - -const MODE_48000_960_120: CeltMode = CeltMode { - overlap: 120, - nb_ebands: 21, - preemph: [0.85000610, 0.0, 1.0, 1.0], - e_bands: &EBAND_5MS, - short_mdct_size: 120, - nb_alloc_vectors: 11, - alloc_vectors: &BAND_ALLOCATION, - log_n: &LOG_N_400, - window: &WINDOW_120, - cache: PulseCache { - index: &CACHE_INDEX_50, - bits: &CACHE_BITS_50, - caps: &CACHE_CAPS_50, - }, -}; diff --git a/vendor/opus-decoder/src/celt/quant_bands.rs b/vendor/opus-decoder/src/celt/quant_bands.rs deleted file mode 100644 index a5c9f41..0000000 --- a/vendor/opus-decoder/src/celt/quant_bands.rs +++ /dev/null @@ -1,286 +0,0 @@ -//! CELT energy dequantization (`quant_bands.c`) for decoder path. -#![allow( - unused_variables, - unused_assignments, - dead_code, - clippy::too_many_arguments, - clippy::needless_range_loop, - clippy::unnecessary_cast -)] - -use crate::celt::laplace::ec_laplace_decode; -use crate::celt::modes::CeltMode; -use crate::entropy::EcDec; -use std::sync::atomic::{AtomicUsize, Ordering}; - -const MAX_FINE_BITS: i32 = 8; -const E_MEANS: [f32; 25] = [ - 6.4375, 6.25, 5.75, 5.3125, 5.0625, 4.8125, 4.5, 4.375, 4.875, 4.6875, 4.5625, 4.4375, 4.875, - 4.625, 4.3125, 4.5, 4.375, 4.625, 4.75, 4.4375, 3.75, 3.75, 3.75, 3.75, 3.75, -]; -const PRED_COEF: [f32; 4] = [ - 29440.0 / 32768.0, - 26112.0 / 32768.0, - 21248.0 / 32768.0, - 16384.0 / 32768.0, -]; -const BETA_COEF: [f32; 4] = [ - 30147.0 / 32768.0, - 22282.0 / 32768.0, - 12124.0 / 32768.0, - 6554.0 / 32768.0, -]; -const BETA_INTRA: f32 = 4915.0 / 32768.0; -const SMALL_ENERGY_ICDF: [u8; 3] = [2, 1, 0]; -static TRACE_COARSE_CALL_IDX: AtomicUsize = AtomicUsize::new(0); -static TRACE_FINE_CALL_IDX: AtomicUsize = AtomicUsize::new(0); -static TRACE_FINALISE_CALL_IDX: AtomicUsize = AtomicUsize::new(0); - -const E_PROB_MODEL: [[[u8; 42]; 2]; 4] = [ - [ - [ - 72, 127, 65, 129, 66, 128, 65, 128, 64, 128, 62, 128, 64, 128, 64, 128, 92, 78, 92, 79, - 92, 78, 90, 79, 116, 41, 115, 40, 114, 40, 132, 26, 132, 26, 145, 17, 161, 12, 176, 10, - 177, 11, - ], - [ - 24, 179, 48, 138, 54, 135, 54, 132, 53, 134, 56, 133, 55, 132, 55, 132, 61, 114, 70, - 96, 74, 88, 75, 88, 87, 74, 89, 66, 91, 67, 100, 59, 108, 50, 120, 40, 122, 37, 97, 43, - 78, 50, - ], - ], - [ - [ - 83, 78, 84, 81, 88, 75, 86, 74, 87, 71, 90, 73, 93, 74, 93, 74, 109, 40, 114, 36, 117, - 34, 117, 34, 143, 17, 145, 18, 146, 19, 162, 12, 165, 10, 178, 7, 189, 6, 190, 8, 177, - 9, - ], - [ - 23, 178, 54, 115, 63, 102, 66, 98, 69, 99, 74, 89, 71, 91, 73, 91, 78, 89, 86, 80, 92, - 66, 93, 64, 102, 59, 103, 60, 104, 60, 117, 52, 123, 44, 138, 35, 133, 31, 97, 38, 77, - 45, - ], - ], - [ - [ - 61, 90, 93, 60, 105, 42, 107, 41, 110, 45, 116, 38, 113, 38, 112, 38, 124, 26, 132, 27, - 136, 19, 140, 20, 155, 14, 159, 16, 158, 18, 170, 13, 177, 10, 187, 8, 192, 6, 175, 9, - 159, 10, - ], - [ - 21, 178, 59, 110, 71, 86, 75, 85, 84, 83, 91, 66, 88, 73, 87, 72, 92, 75, 98, 72, 105, - 58, 107, 54, 115, 52, 114, 55, 112, 56, 129, 51, 132, 40, 150, 33, 140, 29, 98, 35, 77, - 42, - ], - ], - [ - [ - 42, 121, 96, 66, 108, 43, 111, 40, 117, 44, 123, 32, 120, 36, 119, 33, 127, 33, 134, - 34, 139, 21, 147, 23, 152, 20, 158, 25, 154, 26, 166, 21, 173, 16, 184, 13, 184, 10, - 150, 13, 139, 15, - ], - [ - 22, 178, 63, 114, 74, 82, 84, 83, 92, 82, 103, 62, 96, 72, 96, 67, 101, 73, 107, 72, - 113, 55, 118, 52, 125, 52, 118, 52, 117, 55, 135, 49, 137, 39, 157, 32, 145, 29, 97, - 33, 77, 40, - ], - ], -]; - -/// Decode coarse per-band energies. -/// -/// Params: mode/band range, mutable `old_ebands`, `intra` flag, decoder state, -/// channel count, LM and total frame bits. -/// Returns: nothing; `old_ebands` updated in-place. -pub(crate) fn unquant_coarse_energy( - mode: &CeltMode, - start: usize, - end: usize, - old_ebands: &mut [f32], - intra: bool, - dec: &mut EcDec<'_>, - channels: usize, - lm: usize, - total_bits: i32, -) { - let target_idx = None; - let coarse_call_idx = TRACE_COARSE_CALL_IDX.fetch_add(1, Ordering::SeqCst); - let trace_this_call = target_idx == Some(coarse_call_idx); - if trace_this_call { - debug_trace!( - "R coarse_call_idx={} start tell_frac={}", - coarse_call_idx, - dec.tell_frac() - ); - } - let prob_model = &E_PROB_MODEL[lm][usize::from(intra)]; - let mut prev = [0.0f32; 2]; - let (coef, beta) = if intra { - (0.0, BETA_INTRA) - } else { - (PRED_COEF[lm], BETA_COEF[lm]) - }; - let budget = (dec.tell() + (mode.window.len() as i32 * 0)).max(0); // keep branchless placeholder - let _ = budget; - - for i in start..end { - for c in 0..channels { - let tell = dec.tell(); - let tell_frac_before = dec.tell_frac(); - let qi = if total_bits - tell >= 15 { - let pi = 2 * i.min(20); - ec_laplace_decode( - dec, - (prob_model[pi] as u32) << 7, - (prob_model[pi + 1] as u32) << 6, - ) - } else if total_bits - tell >= 2 { - let q = dec.dec_icdf(&SMALL_ENERGY_ICDF, 2); - (q >> 1) ^ -((q & 1) as i32) - } else if total_bits - tell >= 1 { - -i32::from(dec.dec_bit_logp(1)) - } else { - -1 - }; - if trace_this_call { - debug_trace!( - "R pkt{} coarse band={} c={} qi={} tell_frac={}", - coarse_call_idx, - i, - c, - qi, - dec.tell_frac() - ); - } - let q = qi as f32; - let idx = i + c * mode.nb_ebands; - old_ebands[idx] = old_ebands[idx].max(-9.0); - let tmp = coef * old_ebands[idx] + prev[c] + q; - if target_idx == Some(0) && coarse_call_idx == 0 && i == 0 && c == 0 { - debug_trace!( - "[RUST] pkt0 band0: qi={} q={:.6} coef={:.6} old_before={:.6} prev={:.6} tmp={:.6}", - qi, - q, - coef, - old_ebands[idx], - prev[c], - tmp - ); - } - old_ebands[idx] = tmp.clamp(-28.0, 28.0); - prev[c] = prev[c] + q - beta * q; - } - } -} - -/// Decode fine per-band energies. -/// -/// Params: mode/band range, mutable `old_ebands`, per-band extra quant bits, -/// decoder and channel count. -/// Returns: nothing; `old_ebands` updated in-place. -pub(crate) fn unquant_fine_energy( - mode: &CeltMode, - start: usize, - end: usize, - old_ebands: &mut [f32], - extra_quant: &[i32], - dec: &mut EcDec<'_>, - channels: usize, - packet_idx: usize, -) { - let target_idx = None; - let fine_call_idx = TRACE_FINE_CALL_IDX.fetch_add(1, Ordering::SeqCst); - for i in start..end { - let extra = extra_quant[i]; - if extra <= 0 { - continue; - } - let tell_before_band = dec.tell_frac(); - let guard_ok = dec.tell() + (channels as i32) * extra <= (dec.storage() as i32) * 8; - for c in 0..channels { - let nbits_before = dec.nbits_total(); - let tell_before = dec.tell(); - let q2 = dec.dec_bits(extra as u32) as f32; - let offset = - (q2 + 0.5) * ((1u32 << (14 - extra as u32)) as f32) * (1.0 / 16384.0) - 0.5; - old_ebands[i + c * mode.nb_ebands] += offset; - } - if target_idx == Some(fine_call_idx) { - debug_trace!( - "R pkt{} fine band={} extra={} tell:{}->{}", - fine_call_idx, - i, - extra, - tell_before_band, - dec.tell_frac() - ); - } - } -} - -/// Spend remaining bits on final 1-bit fine energy refinement. -/// -/// Params: mode/band range, optional mutable energies, fine quant/priority -/// arrays, remaining whole bits, decoder and channel count. -/// Returns: nothing. -pub(crate) fn unquant_energy_finalise( - mode: &CeltMode, - start: usize, - end: usize, - old_ebands: Option<&mut [f32]>, - fine_quant: &[i32], - fine_priority: &[i32], - bits_left: i32, - dec: &mut EcDec<'_>, - channels: usize, -) { - let target_idx = None; - let finalise_call_idx = TRACE_FINALISE_CALL_IDX.fetch_add(1, Ordering::SeqCst); - let mut bits_left_mut = bits_left; - let mut old_opt = old_ebands; - for prio in 0..2 { - for i in start..end { - if bits_left_mut < channels as i32 { - return; - } - if fine_quant[i] >= MAX_FINE_BITS || fine_priority[i] != prio { - continue; - } - for c in 0..channels { - if target_idx == Some(0) && finalise_call_idx == 0 && i == 0 && c == 0 { - debug_trace!( - "[RUST] finalise band0 pre-read: tell={} end_offs={} nb_end_bits={}", - dec.tell(), - dec.end_offs(), - dec.nend_bits() - ); - } - let q2 = dec.dec_bits(1) as f32; - let offset = (q2 - 0.5) - * ((1u32 << (14 - fine_quant[i] as u32 - 1)) as f32) - * (1.0 / 16384.0); - if target_idx == Some(0) && finalise_call_idx == 0 && i == 0 && c == 0 { - debug_trace!( - "[RUST] finalise band0: fine_quant={} fine_priority={} q2={} offset={:.6}", - fine_quant[0], - fine_priority[0], - q2 as i32, - offset - ); - } - if let Some(ref mut old) = old_opt { - old[i + c * mode.nb_ebands] += offset; - } - bits_left_mut -= 1; - } - } - } -} - -/// Return CELT eMeans table used by energy logic. -/// -/// Params: none. -/// Returns: static per-band mean energy table. -pub(crate) fn e_means() -> &'static [f32; 25] { - &E_MEANS -} diff --git a/vendor/opus-decoder/src/celt/rate.rs b/vendor/opus-decoder/src/celt/rate.rs deleted file mode 100644 index cb1af66..0000000 --- a/vendor/opus-decoder/src/celt/rate.rs +++ /dev/null @@ -1,459 +0,0 @@ -//! CELT bit allocation logic (`rate.c`) for decoder path. -#![allow(clippy::too_many_arguments)] - -use crate::celt::modes::CeltMode; -use crate::entropy::EcDec; - -const BITRES: i32 = 3; -const FINE_OFFSET: i32 = 21; -const MAX_FINE_BITS: i32 = 8; -const ALLOC_STEPS: i32 = 6; -const LOG2_FRAC_TABLE: [u8; 24] = [ - 0, 8, 13, 16, 19, 21, 23, 24, 26, 27, 28, 29, 30, 31, 32, 32, 33, 34, 34, 35, 36, 36, 37, 37, -]; -const LOG_MAX_PSEUDO: usize = 6; - -/// Convert pseudo-pulse index to real pulse count. -/// -/// Params: `i` is pseudo index from CELT pulse cache. -/// Returns: actual pulse count for PVQ decode. -pub(crate) fn get_pulses(i: i32) -> i32 { - if i < 8 { - i - } else { - (8 + (i & 7)) << ((i >> 3) - 1) - } -} - -/// Convert allocated bits to pseudo pulse index. -/// -/// Params: `mode` CELT mode, `band` index, `lm` frame size log2, `bits` in BITRES units. -/// Returns: pseudo pulse index used by `get_pulses`. -pub(crate) fn bits2pulses(mode: &CeltMode, band: usize, lm: i32, bits: i32) -> i32 { - let lm1 = (lm + 1).max(0) as usize; - let cache_base = mode.cache.index[lm1 * mode.nb_ebands + band] as usize; - let cache = &mode.cache.bits[cache_base..]; - let mut lo = 0i32; - let mut hi = cache[0] as i32; - let target = bits - 1; - for _ in 0..LOG_MAX_PSEUDO { - let mid = (lo + hi + 1) >> 1; - if cache[mid as usize] as i32 >= target { - hi = mid; - } else { - lo = mid; - } - } - let lo_bits = if lo == 0 { - -1 - } else { - cache[lo as usize] as i32 - }; - if target - lo_bits <= cache[hi as usize] as i32 - target { - lo - } else { - hi - } -} - -/// Convert pseudo pulse index back to spent bits. -/// -/// Params: `mode` CELT mode, `band` index, `lm` frame size log2, `pulses` pseudo pulse index. -/// Returns: number of bits in BITRES units spent by this pulse count. -pub(crate) fn pulses2bits(mode: &CeltMode, band: usize, lm: i32, pulses: i32) -> i32 { - if pulses == 0 { - return 0; - } - let lm1 = (lm + 1).max(0) as usize; - let cache_base = mode.cache.index[lm1 * mode.nb_ebands + band] as usize; - let cache = &mode.cache.bits[cache_base..]; - cache[pulses as usize] as i32 + 1 -} - -/// Allocation result arrays produced by CELT `clt_compute_allocation`. -#[derive(Debug, Clone)] -pub(crate) struct AllocationResult { - pub coded_bands: usize, - pub intensity: i32, - pub dual_stereo: i32, - pub balance: i32, - pub pulses: Vec, - pub fine_quant: Vec, - pub fine_priority: Vec, -} - -/// Initialize CELT per-band bit caps for given `LM` and channel count. -/// -/// Params: `mode` is CELT mode table, `lm` is frame size log ratio, `channels` -/// is decode channel count. -/// Returns: per-band cap values in BITRES units. -pub(crate) fn init_caps(mode: &CeltMode, lm: usize, channels: usize) -> Vec { - let mut cap = vec![0i32; mode.nb_ebands]; - for (i, cap_i) in cap.iter_mut().enumerate() { - let n = ((mode.e_bands[i + 1] - mode.e_bands[i]) as usize) << lm; - let idx = mode.nb_ebands * (2 * lm + channels - 1) + i; - *cap_i = (((mode.cache.caps[idx] as i32) + 64) * channels as i32 * n as i32) >> 2; - } - cap -} - -/// Compute CELT allocation for one frame (decode path). -/// -/// Params: `mode` CELT mode, `start..end` active bands, `offsets` dynamic -/// allocation boosts, `cap` max bits per band, `alloc_trim` trim symbol, -/// `total` available bits in BITRES units, `channels`, `lm`, and `dec`. -/// Returns: full allocation result (coded bands, pulses, fine bits, balance). -pub(crate) fn clt_compute_allocation( - mode: &CeltMode, - start: usize, - end: usize, - offsets: &[i32], - cap: &[i32], - alloc_trim: i32, - total: i32, - channels: usize, - lm: usize, - dec: &mut EcDec<'_>, - _packet_idx: usize, -) -> AllocationResult { - let mut total_bits = total.max(0); - let skip_rsv = if total_bits >= (1 << BITRES) { - 1 << BITRES - } else { - 0 - }; - total_bits -= skip_rsv; - - let mut intensity_rsv = 0i32; - let mut dual_stereo_rsv = 0i32; - if channels == 2 { - intensity_rsv = LOG2_FRAC_TABLE[end - start] as i32; - if intensity_rsv > total_bits { - intensity_rsv = 0; - } else { - total_bits -= intensity_rsv; - dual_stereo_rsv = if total_bits >= (1 << BITRES) { - 1 << BITRES - } else { - 0 - }; - total_bits -= dual_stereo_rsv; - } - } - - let len = mode.nb_ebands; - let mut bits1 = vec![0i32; len]; - let mut bits2 = vec![0i32; len]; - let mut thresh = vec![0i32; len]; - let mut trim_offset = vec![0i32; len]; - let c = channels as i32; - for j in start..end { - let band_n = (mode.e_bands[j + 1] - mode.e_bands[j]) as i32; - thresh[j] = (c << BITRES).max((3 * band_n << lm << BITRES) >> 4); - trim_offset[j] = c - * band_n - * (alloc_trim - 5 - lm as i32) - * (end - j - 1) as i32 - * (1 << (lm as i32 + BITRES)) - >> 6; - if (band_n << lm) == 1 { - trim_offset[j] -= c << BITRES; - } - } - let mut lo = 1i32; - let mut hi = mode.nb_alloc_vectors as i32 - 1; - while lo <= hi { - let mut done = false; - let mut psum = 0i32; - let mid = (lo + hi) >> 1; - for j in (start..end).rev() { - let n = (mode.e_bands[j + 1] - mode.e_bands[j]) as i32; - let mut bits_j = - c * n * ((mode.alloc_vectors[mid as usize * len + j] as i32) << lm) >> 2; - if bits_j > 0 { - bits_j = (bits_j + trim_offset[j]).max(0); - } - bits_j += offsets[j]; - if bits_j >= thresh[j] || done { - done = true; - psum += bits_j.min(cap[j]); - } else if bits_j >= c << BITRES { - psum += c << BITRES; - } - } - if psum > total_bits { - hi = mid - 1; - } else { - lo = mid + 1; - } - } - hi = lo; - lo -= 1; - - let mut skip_start = start; - for j in start..end { - let n = (mode.e_bands[j + 1] - mode.e_bands[j]) as i32; - let mut bits1j = c * n * ((mode.alloc_vectors[lo as usize * len + j] as i32) << lm) >> 2; - let mut bits2j = if hi as usize >= mode.nb_alloc_vectors { - cap[j] - } else { - c * n * ((mode.alloc_vectors[hi as usize * len + j] as i32) << lm) >> 2 - }; - if bits1j > 0 { - bits1j = (bits1j + trim_offset[j]).max(0); - } - if bits2j > 0 { - bits2j = (bits2j + trim_offset[j]).max(0); - } - if lo > 0 { - bits1j += offsets[j]; - } - bits2j += offsets[j]; - if offsets[j] > 0 { - skip_start = j; - } - bits2j = (bits2j - bits1j).max(0); - bits1[j] = bits1j; - bits2[j] = bits2j; - } - - let mut pulses = vec![0i32; len]; - let mut fine_quant = vec![0i32; len]; - let mut fine_priority = vec![0i32; len]; - let mut intensity = 0i32; - let mut dual_stereo = 0i32; - let (coded_bands, balance) = interp_bits2pulses( - mode, - start, - end, - skip_start, - &bits1, - &bits2, - &thresh, - cap, - total_bits, - skip_rsv, - &mut intensity, - intensity_rsv, - &mut dual_stereo, - dual_stereo_rsv, - &mut pulses, - &mut fine_quant, - &mut fine_priority, - channels, - lm, - dec, - _packet_idx, - ); - - AllocationResult { - coded_bands, - intensity, - dual_stereo, - balance, - pulses, - fine_quant, - fine_priority, - } -} - -/// Interpolate allocation vectors and derive pulse/fine bit splits. -/// -/// Params: direct port of libopus `interp_bits2pulses` decoder flow. -/// Returns: number of coded bands. -#[allow(clippy::too_many_arguments)] -fn interp_bits2pulses( - mode: &CeltMode, - start: usize, - end: usize, - skip_start: usize, - bits1: &[i32], - bits2: &[i32], - thresh: &[i32], - cap: &[i32], - total: i32, - skip_rsv: i32, - intensity: &mut i32, - mut intensity_rsv: i32, - dual_stereo: &mut i32, - mut dual_stereo_rsv: i32, - bits: &mut [i32], - ebits: &mut [i32], - fine_priority: &mut [i32], - channels: usize, - lm: usize, - dec: &mut EcDec<'_>, - _packet_idx: usize, -) -> (usize, i32) { - let trace_packet0 = false; - let c = channels as i32; - let stereo = channels > 1; - let alloc_floor = c << BITRES; - let log_m = (lm as i32) << BITRES; - - let mut lo = 0i32; - let mut hi = 1 << ALLOC_STEPS; - for _ in 0..ALLOC_STEPS { - let mid = (lo + hi) >> 1; - let mut psum = 0i32; - let mut done = false; - for j in (start..end).rev() { - let tmp = bits1[j] + ((mid * bits2[j]) >> ALLOC_STEPS); - if tmp >= thresh[j] || done { - done = true; - psum += tmp.min(cap[j]); - } else if tmp >= alloc_floor { - psum += alloc_floor; - } - } - if psum > total { - hi = mid; - } else { - lo = mid; - } - } - - let mut psum = 0i32; - let mut done = false; - for j in (start..end).rev() { - let mut tmp = bits1[j] + ((lo * bits2[j]) >> ALLOC_STEPS); - if tmp < thresh[j] && !done { - tmp = if tmp >= alloc_floor { alloc_floor } else { 0 }; - } else { - done = true; - } - tmp = tmp.min(cap[j]); - bits[j] = tmp; - psum += tmp; - } - - let mut coded_bands = end; - let mut total_adj = total; - loop { - let j = coded_bands - 1; - if j <= skip_start { - // Match libopus: give back reserved skip bit only when skipping loop ends naturally. - total_adj += skip_rsv; - break; - } - let left = total - psum; - let denom = (mode.e_bands[coded_bands] - mode.e_bands[start]) as i32; - let percoeff = if denom > 0 { left / denom } else { 0 }; - let left_rem = left - denom * percoeff; - let rem = (left_rem - (mode.e_bands[j] - mode.e_bands[start]) as i32).max(0); - let band_width = (mode.e_bands[coded_bands] - mode.e_bands[j]) as i32; - let mut band_bits = bits[j] + percoeff * band_width + rem; - if band_bits >= thresh[j].max(alloc_floor + (1 << BITRES)) { - if dec.dec_bit_logp(1) { - break; - } - psum += 1 << BITRES; - band_bits -= 1 << BITRES; - } - psum -= bits[j] + intensity_rsv; - if intensity_rsv > 0 { - intensity_rsv = LOG2_FRAC_TABLE[j - start] as i32; - } - psum += intensity_rsv; - bits[j] = if band_bits >= alloc_floor { - alloc_floor - } else { - 0 - }; - psum += bits[j]; - coded_bands -= 1; - } - coded_bands = coded_bands.max(start + 1); - - if intensity_rsv > 0 { - *intensity = start as i32 + dec.dec_uint((coded_bands + 1 - start) as u32) as i32; - } else { - *intensity = 0; - } - if *intensity <= start as i32 { - total_adj += dual_stereo_rsv; - dual_stereo_rsv = 0; - } - if dual_stereo_rsv > 0 { - *dual_stereo = i32::from(dec.dec_bit_logp(1)); - } else { - *dual_stereo = 0; - } - if trace_packet0 { - debug_trace!( - "[RUST] alloc: coded_bands={} psum={} total_adj={}", - coded_bands, - psum, - total_adj - ); - } - - let left = total_adj - psum; - let denom = (mode.e_bands[coded_bands] - mode.e_bands[start]) as i32; - let percoeff = if denom > 0 { left / denom } else { 0 }; - let mut left_rem = left - denom * percoeff; - for (j, bits_j) in bits.iter_mut().enumerate().take(coded_bands).skip(start) { - *bits_j += percoeff * (mode.e_bands[j + 1] - mode.e_bands[j]) as i32; - let tmp = left_rem.min((mode.e_bands[j + 1] - mode.e_bands[j]) as i32); - *bits_j += tmp; - left_rem -= tmp; - } - - let mut balance = 0i32; - let stereo_shift = if stereo { 1 } else { 0 }; - for j in start..coded_bands { - let n0 = (mode.e_bands[j + 1] - mode.e_bands[j]) as i32; - let n = n0 << lm; - let bit = bits[j] + balance; - let mut excess; - if n > 1 { - excess = (bit - cap[j]).max(0); - bits[j] = bit - excess; - let den = c * n - + if channels == 2 && n > 2 && *dual_stereo == 0 && j < *intensity as usize { - 1 - } else { - 0 - }; - let nclogn = den * (mode.log_n[j] as i32 + log_m); - let mut offset = (nclogn >> 1) - den * FINE_OFFSET; - if n == 2 { - offset += den << BITRES >> 2; - } - if bits[j] + offset < den * 2 << BITRES { - offset += nclogn >> 2; - } else if bits[j] + offset < den * 3 << BITRES { - offset += nclogn >> 3; - } - let mut e = (bits[j] + offset + (den << (BITRES - 1))).max(0); - e = (e / den) >> BITRES; - if c * e > (bits[j] >> BITRES) { - e = (bits[j] >> stereo_shift) >> BITRES; - } - e = e.min(MAX_FINE_BITS); - ebits[j] = e; - fine_priority[j] = i32::from(ebits[j] * (den << BITRES) >= bits[j] + offset); - bits[j] -= c * ebits[j] << BITRES; - } else { - excess = (bit - (c << BITRES)).max(0); - bits[j] = bit - excess; - ebits[j] = 0; - fine_priority[j] = 1; - } - if excess > 0 { - let extra_fine = - ((excess >> (stereo_shift + BITRES)).max(0)).min(MAX_FINE_BITS - ebits[j]); - ebits[j] += extra_fine; - let extra_bits = (extra_fine * c) << BITRES; - fine_priority[j] = i32::from(extra_bits >= excess - balance); - excess -= extra_bits; - } - balance = excess; - } - for j in coded_bands..end { - ebits[j] = bits[j] >> if stereo { 1 } else { 0 } >> BITRES; - bits[j] = 0; - fine_priority[j] = i32::from(ebits[j] < 1); - } - (coded_bands, balance) -} diff --git a/vendor/opus-decoder/src/celt/vq.rs b/vendor/opus-decoder/src/celt/vq.rs deleted file mode 100644 index f91f78a..0000000 --- a/vendor/opus-decoder/src/celt/vq.rs +++ /dev/null @@ -1,179 +0,0 @@ -//! CELT PVQ vector decode helpers (`vq.c`) for decoder path. -#![allow(clippy::too_many_arguments)] - -use crate::celt::cwrs; -use crate::entropy::EcDec; - -/// CELT spreading mode: none. -pub(crate) const SPREAD_NONE: i32 = 0; - -/// PVQ band decode diagnostic information. -#[derive(Debug, Clone, Copy)] -pub(crate) struct PvqDecodeInfo { - pub collapse_mask: u8, - pub nc: u32, - pub index: u32, -} - -/// Run one exponential rotation stage. -/// -/// Params: mutable band vector, stage stride and sin/cos factors. -/// Returns: nothing. -fn exp_rotation1(x: &mut [f32], stride: usize, c: f32, s: f32) { - let len = x.len(); - if len <= stride { - return; - } - for i in 0..(len - stride) { - let x1 = x[i]; - let x2 = x[i + stride]; - x[i + stride] = c * x2 + s * x1; - x[i] = c * x1 - s * x2; - } - if len <= 2 * stride { - return; - } - for i in (0..=(len - 2 * stride - 1)).rev() { - let x1 = x[i]; - let x2 = x[i + stride]; - x[i + stride] = c * x2 + s * x1; - x[i] = c * x1 - s * x2; - } -} - -/// Apply CELT spreading rotation. -/// -/// Params: mutable band vector, direction, stride, pulses and spread mode. -/// Returns: nothing. -fn exp_rotation(x: &mut [f32], dir: i32, stride: usize, k: i32, spread: i32) { - const SPREAD_FACTOR: [i32; 3] = [15, 10, 5]; - if 2 * k >= x.len() as i32 || spread == SPREAD_NONE { - return; - } - let factor = SPREAD_FACTOR[(spread - 1) as usize]; - let gain = x.len() as f32 / (x.len() as f32 + factor as f32 * k as f32); - let theta = 0.5 * gain * gain; - let angle = core::f32::consts::FRAC_PI_2 * theta; - let c = angle.cos(); - let s = angle.sin(); - let mut stride2 = 0usize; - if x.len() >= 8 * stride { - stride2 = 1; - while (stride2 * stride2 + stride2) * stride + (stride >> 2) < x.len() { - stride2 += 1; - } - } - let len = x.len() / stride; - for i in 0..stride { - let xs = &mut x[i * len..(i + 1) * len]; - if dir < 0 { - if stride2 > 0 { - exp_rotation1(xs, stride2, s, c); - } - exp_rotation1(xs, 1, c, s); - } else { - exp_rotation1(xs, 1, c, -s); - if stride2 > 0 { - exp_rotation1(xs, stride2, s, -c); - } - } - } -} - -/// Normalize decoded integer pulses into unit-energy vector. -/// -/// Params: signed pulses, output vector and synthesis gain. -/// Returns: sum of squared pulses. -fn normalise_residual(iy: &[i32], x: &mut [f32], gain: f32) -> u32 { - let ryy = iy - .iter() - .fold(0u32, |acc, &v| acc.wrapping_add((v * v) as u32)); - let scale = if ryy == 0 { - 0.0 - } else { - gain / (ryy as f32).sqrt() - }; - for (dst, &src) in x.iter_mut().zip(iy.iter()) { - *dst = src as f32 * scale; - } - ryy -} - -/// Build collapse mask for transient anti-collapse handling. -/// -/// Params: decoded pulse vector, band length and block count. -/// Returns: bit mask with one bit per short block. -fn extract_collapse_mask(iy: &[i32], n: usize, b: usize) -> u8 { - if b <= 1 { - return 1; - } - let n0 = n / b; - let mut mask = 0u8; - for i in 0..b { - let mut nonzero = 0i32; - for j in 0..n0 { - nonzero |= iy[i * n0 + j]; - } - if nonzero != 0 { - // NoSlacking: up to 16 short blocks; libopus keeps an unsigned - // here. Wrapping onto bit `i % 8` is what release builds always - // did, and the fold after gives libopus's mask. - mask |= 1u8.wrapping_shl(i as u32); - } - } - mask -} - -/// Decode one PVQ normalized band. -/// -/// Params: output vector, pulses `k`, spread mode, blocks `b`, decoder and gain. -/// Returns: collapse mask and CWRS decode metadata for this band. -pub(crate) fn alg_unquant( - x: &mut [f32], - k: i32, - spread: i32, - b: usize, - band: usize, - trace_packet: bool, - dec: &mut EcDec<'_>, - gain: f32, -) -> PvqDecodeInfo { - let decoded = cwrs::decode_pulses(dec, x.len(), k as usize); - normalise_residual(&decoded.pulses, x, gain); - if trace_packet && band == 6 && x.len() >= 4 { - debug_trace!( - "band6 pre_rot: {:.6} {:.6} {:.6} {:.6}", - x[0], - x[1], - x[2], - x[3] - ); - } - exp_rotation(x, -1, b.max(1), k, spread); - if trace_packet && band == 6 && x.len() >= 4 { - debug_trace!( - "band6 post_rot: {:.6} {:.6} {:.6} {:.6}", - x[0], - x[1], - x[2], - x[3] - ); - } - PvqDecodeInfo { - collapse_mask: extract_collapse_mask(&decoded.pulses, x.len(), b.max(1)), - nc: decoded.nc, - index: decoded.index, - } -} - -/// Renormalize vector to desired gain. -/// -/// Params: mutable vector and target gain. -/// Returns: nothing. -pub(crate) fn renormalise_vector(x: &mut [f32], gain: f32) { - let e = x.iter().map(|v| v * v).sum::().max(1e-12); - let g = gain / e.sqrt(); - for v in x.iter_mut() { - *v *= g; - } -} diff --git a/vendor/opus-decoder/src/compare.rs b/vendor/opus-decoder/src/compare.rs deleted file mode 100644 index e1fbb68..0000000 --- a/vendor/opus-decoder/src/compare.rs +++ /dev/null @@ -1,353 +0,0 @@ -//! Conformance comparison helper. -//! -//! This ports libopus' `src/opus_compare.c` quality metric, used by the -//! reference `tests/run_vectors.sh` script when validating RFC test vectors. -//! -//! NOTE: This is intentionally *not* a bit-exact PCM comparator. Minor output -//! differences can happen between builds/platforms (especially in CELT), while -//! still being considered acceptable by the reference test harness. - -#![allow( - dead_code, - clippy::too_many_arguments, - clippy::needless_range_loop, - clippy::identity_op, - clippy::erasing_op -)] - -use core::f32; - -const NBANDS: usize = 21; -const NFREQS: usize = 240; - -// Bark-derived CELT bands used by opus_compare.c. -const BANDS: [usize; NBANDS + 1] = [ - 0, 2, 4, 6, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 68, 80, 96, 120, 156, 200, -]; - -const TEST_WIN_SIZE: usize = 480; -const TEST_WIN_STEP: usize = 120; - -#[derive(Debug, Clone, Copy)] -pub struct QualityResult { - pub quality_percent: f32, - pub internal_weighted_error: f64, -} - -impl QualityResult { - pub fn passes_vectors(&self) -> bool { - self.quality_percent >= 0.0 - } -} - -pub fn compare_quality( - reference_pcm: &[i16], - candidate_pcm: &[i16], - reference_channels: usize, - candidate_channels: usize, - rate_hz: u32, -) -> Result { - if reference_channels != 1 && reference_channels != 2 { - return Err(format!( - "invalid reference channel count: {reference_channels}" - )); - } - if candidate_channels != 1 && candidate_channels != 2 { - return Err(format!( - "invalid candidate channel count: {candidate_channels}" - )); - } - if !matches!(rate_hz, 8000 | 12000 | 16000 | 24000 | 48000) { - return Err(format!("invalid rate_hz: {rate_hz}")); - } - - if reference_pcm.len() % reference_channels != 0 { - return Err("reference pcm is not properly interleaved".to_string()); - } - if candidate_pcm.len() % candidate_channels != 0 { - return Err("candidate pcm is not properly interleaved".to_string()); - } - - let downsample = (48_000u32 / rate_hz) as usize; - if downsample == 0 || 48_000u32 % rate_hz != 0 { - return Err("rate_hz must divide 48000".to_string()); - } - - let xlength = reference_pcm.len() / reference_channels; - let ylength = candidate_pcm.len() / candidate_channels; - if xlength != ylength.saturating_mul(downsample) { - return Err(format!( - "sample counts do not match (ref frames {xlength} != cand frames {ylength} * downsample {downsample})" - )); - } - if xlength < TEST_WIN_SIZE { - return Err(format!( - "insufficient sample data (ref frames {xlength} < {TEST_WIN_SIZE})" - )); - } - - // Convert reference to f32 (mono or stereo per reference_channels). - let mut x: Vec = Vec::with_capacity(xlength * candidate_channels); - if reference_channels == 1 { - for i in 0..xlength { - x.push(reference_pcm[i] as f32); - } - } else if candidate_channels == 1 { - for i in 0..xlength { - let l = reference_pcm[2 * i] as f32; - let r = reference_pcm[2 * i + 1] as f32; - x.push(0.5 * (l + r)); - } - } else { - for &s in reference_pcm { - x.push(s as f32); - } - } - - let y: Vec = candidate_pcm.iter().map(|&s| s as f32).collect(); - - // Equivalent of: nframes=(xlength-TEST_WIN_SIZE+TEST_WIN_STEP)/TEST_WIN_STEP; - let nframes = (xlength - TEST_WIN_SIZE + TEST_WIN_STEP) / TEST_WIN_STEP; - if nframes == 0 { - return Err("nframes == 0".to_string()); - } - - let (ybands, yfreqs) = match rate_hz { - 48000 => (NBANDS, NFREQS), - 24000 => (19usize, NFREQS / downsample), - 16000 => (17usize, NFREQS / downsample), - 12000 => (15usize, NFREQS / downsample), - 8000 => (13usize, NFREQS / downsample), - _ => return Err("unreachable rate_hz".to_string()), - }; - - let nch = candidate_channels; - - // xb: per-band masking energy - let mut xb = vec![0f32; nframes * NBANDS * nch]; - let mut x_ps = vec![0f32; nframes * NFREQS * nch]; - let mut y_ps = vec![0f32; nframes * yfreqs * nch]; - - band_energy( - Some(&mut xb), - &mut x_ps, - &x, - nch, - nframes, - TEST_WIN_SIZE, - TEST_WIN_STEP, - 1, - NBANDS, - NFREQS, - ); - - band_energy( - None, - &mut y_ps, - &y, - nch, - nframes, - TEST_WIN_SIZE / downsample, - TEST_WIN_STEP / downsample, - downsample, - ybands, - yfreqs, - ); - - // Apply masking and smoothing like opus_compare.c. - for xi in 0..nframes { - // Frequency masking (low -> high): 10 dB/Bark slope. - for bi in 1..NBANDS { - for ci in 0..nch { - let cur = (xi * NBANDS + bi) * nch + ci; - let prev = (xi * NBANDS + (bi - 1)) * nch + ci; - xb[cur] += 0.1 * xb[prev]; - } - } - - // Frequency masking (high -> low): 15 dB/Bark slope. - for bi in (0..NBANDS - 1).rev() { - for ci in 0..nch { - let cur = (xi * NBANDS + bi) * nch + ci; - let next = (xi * NBANDS + (bi + 1)) * nch + ci; - xb[cur] += 0.03 * xb[next]; - } - } - - // Temporal masking: -3 dB/2.5 ms slope. - if xi > 0 { - for bi in 0..NBANDS { - for ci in 0..nch { - let cur = (xi * NBANDS + bi) * nch + ci; - let prev = ((xi - 1) * NBANDS + bi) * nch + ci; - xb[cur] += 0.5 * xb[prev]; - } - } - } - - // Allowing some cross-talk. - if nch == 2 { - for bi in 0..NBANDS { - let l = xb[(xi * NBANDS + bi) * nch + 0]; - let r = xb[(xi * NBANDS + bi) * nch + 1]; - xb[(xi * NBANDS + bi) * nch + 0] += 0.01 * r; - xb[(xi * NBANDS + bi) * nch + 1] += 0.01 * l; - } - } - - // Apply masking to spectral energies. - for bi in 0..ybands { - for xj in BANDS[bi]..BANDS[bi + 1] { - for ci in 0..nch { - x_ps[(xi * NFREQS + xj) * nch + ci] += 0.1 * xb[(xi * NBANDS + bi) * nch + ci]; - y_ps[(xi * yfreqs + xj) * nch + ci] += 0.1 * xb[(xi * NBANDS + bi) * nch + ci]; - } - } - } - } - - // Average consecutive frames (adds previous frame). - for bi in 0..ybands { - for xj in BANDS[bi]..BANDS[bi + 1] { - for ci in 0..nch { - let mut xtmp = x_ps[(0 * NFREQS + xj) * nch + ci]; - let mut ytmp = y_ps[(0 * yfreqs + xj) * nch + ci]; - for xi in 1..nframes { - let xtmp2 = x_ps[(xi * NFREQS + xj) * nch + ci]; - let ytmp2 = y_ps[(xi * yfreqs + xj) * nch + ci]; - x_ps[(xi * NFREQS + xj) * nch + ci] += xtmp; - y_ps[(xi * yfreqs + xj) * nch + ci] += ytmp; - xtmp = xtmp2; - ytmp = ytmp2; - } - } - } - } - - // Max frequency bin to compare (see opus_compare.c). - let max_compare = if rate_hz == 48_000 { - BANDS[NBANDS] - } else if rate_hz == 12_000 { - BANDS[ybands] - } else { - BANDS[ybands].saturating_sub(3) - }; - - // Error integration. - let mut err: f64 = 0.0; - for xi in 0..nframes { - let mut ef: f64 = 0.0; - for bi in 0..ybands { - let mut eb: f64 = 0.0; - for xj in BANDS[bi]..BANDS[bi + 1] { - if xj >= max_compare { - break; - } - for ci in 0..nch { - let re = - y_ps[(xi * yfreqs + xj) * nch + ci] / x_ps[(xi * NFREQS + xj) * nch + ci]; - let mut im = re - re.ln() - 1.0; - if (79..=81).contains(&xj) { - im *= 0.1; - } - if xj == 80 { - im *= 0.1; - } - eb += im as f64; - } - } - eb /= ((BANDS[bi + 1] - BANDS[bi]) * nch) as f64; - ef += eb * eb; - } - ef /= NBANDS as f64; - ef *= ef; - err += ef * ef; - } - - err = (err / nframes as f64).powf(1.0 / 16.0); - let q = 100.0 * (1.0 - 0.5 * (1.0 + err).ln() / (1.13f64).ln()); - - Ok(QualityResult { - quality_percent: q as f32, - internal_weighted_error: err, - }) -} - -fn band_energy( - mut out_bands: Option<&mut [f32]>, - ps: &mut [f32], - input: &[f32], - nchannels: usize, - nframes: usize, - window_sz: usize, - step: usize, - downsample: usize, - nbands: usize, - ps_freqs: usize, -) { - let mut window = vec![0f32; window_sz]; - let mut c = vec![0f32; window_sz]; - let mut s = vec![0f32; window_sz]; - let mut x = vec![0f32; nchannels * window_sz]; - - for xj in 0..window_sz { - window[xj] = - 0.5 - 0.5 * (2.0 * f32::consts::PI / (window_sz as f32 - 1.0) * xj as f32).cos(); - } - for xj in 0..window_sz { - c[xj] = (2.0 * f32::consts::PI / window_sz as f32 * xj as f32).cos(); - s[xj] = (2.0 * f32::consts::PI / window_sz as f32 * xj as f32).sin(); - } - - let ps_sz = window_sz / 2; - debug_assert_eq!(ps_freqs, ps_sz); - - for xi in 0..nframes { - for ci in 0..nchannels { - for xk in 0..window_sz { - x[ci * window_sz + xk] = window[xk] * input[(xi * step + xk) * nchannels + ci]; - } - } - - let mut xj = 0usize; - for bi in 0..nbands { - let mut p0 = 0f32; - let mut p1 = 0f32; - while xj < BANDS[bi + 1] { - for ci in 0..nchannels { - let mut ti = 0usize; - let mut re = 0f32; - let mut im = 0f32; - for xk in 0..window_sz { - re += c[ti] * x[ci * window_sz + xk]; - im -= s[ti] * x[ci * window_sz + xk]; - ti += xj; - if ti >= window_sz { - ti -= window_sz; - } - } - let ds = downsample as f32; - re *= ds; - im *= ds; - - let idx = (xi * ps_sz + xj) * nchannels + ci; - ps[idx] = re * re + im * im + 100_000.0; - if ci == 0 { - p0 += ps[idx]; - } else { - p1 += ps[idx]; - } - } - xj += 1; - } - - if let Some(out_bands) = out_bands.as_deref_mut() { - let denom = (BANDS[bi + 1] - BANDS[bi]) as f32; - out_bands[(xi * nbands + bi) * nchannels + 0] = p0 / denom; - if nchannels == 2 { - out_bands[(xi * nbands + bi) * nchannels + 1] = p1 / denom; - } - } - } - } -} diff --git a/vendor/opus-decoder/src/entropy.rs b/vendor/opus-decoder/src/entropy.rs deleted file mode 100644 index b7ad1b1..0000000 --- a/vendor/opus-decoder/src/entropy.rs +++ /dev/null @@ -1,411 +0,0 @@ -//! Entropy (range) decoder used by Opus (CELT + SILK). -//! -//! Port of libopus CELT range decoder (`celt/entdec.c`, `celt/entcode.c`). -//! Decoder-only; we keep the structure/semantics close to the reference to -//! minimize bit-exactness risk. - -#![allow(dead_code)] - -use core::cmp; - -// Constants from `celt/mfrngcod.h`. -const EC_SYM_BITS: i32 = 8; -const EC_SYM_MAX: u32 = (1u32 << EC_SYM_BITS) - 1; -const EC_CODE_BITS: i32 = 32; -const EC_CODE_TOP: u32 = 1u32 << (EC_CODE_BITS - 1); -const EC_CODE_BOT: u32 = EC_CODE_TOP >> EC_SYM_BITS; -const EC_CODE_EXTRA: i32 = ((EC_CODE_BITS - 2) % EC_SYM_BITS) + 1; - -// Constants from `celt/entcode.h`. -const EC_UINT_BITS: i32 = 8; -const BITRES: i32 = 3; - -// `ec_window` in libopus is `opus_uint32` by default. -type EcWindow = u32; -const EC_WINDOW_SIZE: i32 = (core::mem::size_of::() as i32) * 8; - -#[inline] -fn ec_ilog(v: u32) -> i32 { - // Matches `EC_ILOG()` semantics: returns 0 when v==0, else floor(log2(v))+1. - if v == 0 { - 0 - } else { - 32 - v.leading_zeros() as i32 - } -} - -#[inline] -fn imul32(a: u32, b: u32) -> u32 { - a.wrapping_mul(b) -} - -#[inline] -fn celt_udiv(n: u32, d: u32) -> u32 { - // libopus has an optional div table optimization; `n/d` is bit-exact. - n / d -} - -#[derive(Debug, Clone)] -pub(crate) struct EcDec<'a> { - buf: &'a [u8], - storage: usize, - - // Raw bits live at the end of the buffer, read LSB-first. - end_offs: usize, - end_window: EcWindow, - nend_bits: i32, - - // Total whole bits "used" so far (range coder + raw bits). - nbits_total: i32, - - // Range-coded bytes are read from the start. - offs: usize, - - // Range decoder state. - rng: u32, - // Difference between top of current range and input value, minus 1. - val: u32, - // Saved normalization factor from `decode()`. - ext: u32, - // Buffered symbol awaiting normalization. - rem: i32, - - error: bool, -} - -impl<'a> EcDec<'a> { - pub fn new(buf: &'a [u8]) -> Self { - let storage = buf.len(); - - // Mirrors `ec_dec_init()`. - let mut st = Self { - buf, - storage, - end_offs: 0, - end_window: 0, - nend_bits: 0, - nbits_total: EC_CODE_BITS + 1 - - ((EC_CODE_BITS - EC_CODE_EXTRA) / EC_SYM_BITS) * EC_SYM_BITS, - offs: 0, - rng: 1u32 << EC_CODE_EXTRA, - val: 0, - ext: 0, - rem: 0, - error: false, - }; - - st.rem = st.read_byte() as i32; - st.val = st.rng - 1 - ((st.rem as u32) >> (EC_SYM_BITS - EC_CODE_EXTRA)); - st.normalize(); - - st - } - - #[inline] - pub fn tell(&self) -> i32 { - // Matches `ec_tell()`: slightly over-estimates. - self.nbits_total - ec_ilog(self.rng) - } - - #[inline] - pub fn tell_frac(&self) -> u32 { - // Port of fast `ec_tell_frac()` from `celt/entcode.c`. - const CORRECTION: [u32; 8] = [35733, 38967, 42495, 46340, 50535, 55109, 60097, 65535]; - - let nbits = (self.nbits_total as u32) << BITRES; - let mut l = ec_ilog(self.rng); - let r = self.rng >> (l - 16); - let mut b = ((r >> 12) - 8) as usize; - b += (r > CORRECTION[b]) as usize; - l = (l << 3) + b as i32; - nbits - (l as u32) - } - - #[inline] - pub fn final_range(&self) -> u32 { - // libopus exposes this via OPUS_GET_FINAL_RANGE. - self.rng - } - - /// Return active entropy storage in bytes. - /// - /// Params: none. - /// Returns: currently visible storage after any shrinking for raw bits. - #[inline] - pub fn storage(&self) -> usize { - self.storage - } - - /// Return the whole-bit counter tracked by the entropy decoder. - /// - /// Params: none. - /// Returns: `nbits_total`, excluding partial range-coder bits. - #[inline] - pub fn nbits_total(&self) -> i32 { - self.nbits_total - } - - /// Return current entropy decoder range state. - /// - /// Params: none. - /// Returns: internal `rng` value used for CELT noise/folding seed chaining. - pub fn rng(&self) -> u32 { - self.rng - } - - /// Return current entropy decoder `val` state. - /// - /// Params: none. - /// Returns: internal `val` register for debug comparisons. - pub fn debug_val(&self) -> u32 { - self.val - } - - #[inline] - pub fn is_error(&self) -> bool { - self.error - } - - /// Shrink visible decoder storage to hide trailing side data. - /// - /// Params: `bytes` number of bytes reserved at packet end. - /// Returns: nothing; the active storage window is clamped in place. - pub fn shrink_storage(&mut self, bytes: usize) { - self.storage = self.storage.saturating_sub(bytes); - self.end_offs = self.end_offs.min(self.storage); - if self.offs > self.storage { - self.offs = self.storage; - self.error = true; - } - } - - /// Debug accessor: number of raw-bit bytes consumed from packet end. - /// - /// Params: none. - /// Returns: `end_offs` value from entropy decoder state. - pub(crate) fn end_offs(&self) -> usize { - self.end_offs - } - - /// Debug accessor: number of currently buffered raw bits. - /// - /// Params: none. - /// Returns: `nend_bits` value from entropy decoder state. - pub(crate) fn nend_bits(&self) -> i32 { - self.nend_bits - } - - #[inline] - fn read_byte(&mut self) -> u8 { - if self.offs < self.storage { - let b = self.buf[self.offs]; - self.offs += 1; - b - } else { - 0 - } - } - - #[inline] - fn read_byte_from_end(&mut self) -> u8 { - if self.end_offs < self.storage { - self.end_offs += 1; - self.buf[self.storage - self.end_offs] - } else { - 0 - } - } - - #[inline] - fn normalize(&mut self) { - while self.rng <= EC_CODE_BOT { - self.nbits_total += EC_SYM_BITS; - self.rng <<= EC_SYM_BITS; - - // Use up remaining bits from last symbol. - let mut sym = self.rem as u32; - - // Read next symbol. - self.rem = self.read_byte() as i32; - - // Take the rest of the bits we need from this new symbol. - sym = (sym << EC_SYM_BITS | (self.rem as u32)) >> (EC_SYM_BITS - EC_CODE_EXTRA); - - // Subtract from val, capped to < EC_CODE_TOP. - self.val = ((self.val << EC_SYM_BITS) + (EC_SYM_MAX & !sym)) & (EC_CODE_TOP - 1); - } - } - - /// Equivalent of `ec_decode()`. - pub fn decode(&mut self, ft: u32) -> u32 { - self.ext = celt_udiv(self.rng, ft); - let s = self.val / self.ext; - ft - cmp::min(s + 1, ft) - } - - /// Equivalent of `ec_decode_bin()` with `ft == 1< u32 { - self.ext = self.rng >> bits; - let s = self.val / self.ext; - (1u32 << bits) - cmp::min(s + 1, 1u32 << bits) - } - - /// Equivalent of `ec_dec_update()`. - pub fn update(&mut self, fl: u32, fh: u32, ft: u32) { - let s = imul32(self.ext, ft - fh); - self.val = self.val.wrapping_sub(s); - self.rng = if fl > 0 { - imul32(self.ext, fh - fl) - } else { - self.rng.wrapping_sub(s) - }; - self.normalize(); - } - - /// Decode a bit that has a 1/(1< bool { - let r = self.rng; - let d = self.val; - let s = r >> logp; - let ret = d < s; - if !ret { - self.val = d - s; - } - self.rng = if ret { s } else { r - s }; - self.normalize(); - ret - } - - /// Decodes a symbol from an "inverse CDF" table (`ec_dec_icdf`). - pub fn dec_icdf(&mut self, icdf: &[u8], ftb: u32) -> i32 { - let s0 = self.rng; - let d = self.val; - let r = s0 >> ftb; - let mut ret: i32 = -1; - let mut s = s0; - let mut t; - loop { - t = s; - ret += 1; - let idx = ret as usize; - if idx >= icdf.len() { - self.error = true; - return icdf.len() as i32 - 1; - } - s = imul32(r, icdf[idx] as u32); - if d >= s { - break; - } - } - self.val = d - s; - self.rng = t - s; - self.normalize(); - ret - } - - /// Decodes a symbol from an "inverse CDF" table (`ec_dec_icdf16`). - pub fn dec_icdf16(&mut self, icdf: &[u16], ftb: u32) -> i32 { - let s0 = self.rng; - let d = self.val; - let r = s0 >> ftb; - let mut ret: i32 = -1; - let mut s = s0; - let mut t; - loop { - t = s; - ret += 1; - let idx = ret as usize; - if idx >= icdf.len() { - self.error = true; - return icdf.len() as i32 - 1; - } - s = imul32(r, icdf[idx] as u32); - if d >= s { - break; - } - } - self.val = d - s; - self.rng = t - s; - self.normalize(); - ret - } - - /// Extract a raw unsigned integer with a non-power-of-2 range (`ec_dec_uint`). - pub fn dec_uint(&mut self, ft_in: u32) -> u32 { - // Match libopus behavior: undefined for ft<=1; we treat it as error. - if ft_in <= 1 { - self.error = true; - return 0; - } - - let mut ftm1 = ft_in - 1; - let mut ftb = ec_ilog(ftm1); - if ftb > EC_UINT_BITS { - ftb -= EC_UINT_BITS; - let ft = (ftm1 >> ftb) as u32 + 1; - let s = self.decode(ft); - self.update(s, s + 1, ft); - let t = (s << ftb) | self.dec_bits(ftb as u32); - if t <= ftm1 { - t - } else { - self.error = true; - ftm1 - } - } else { - ftm1 += 1; - let s = self.decode(ftm1); - self.update(s, s + 1, ftm1); - s - } - } - - /// Extract a sequence of raw bits from the stream (`ec_dec_bits`). - pub fn dec_bits(&mut self, bits: u32) -> u32 { - debug_assert!(bits <= 25); - - let mut window = self.end_window; - let mut available = self.nend_bits; - if available < bits as i32 { - loop { - window |= (self.read_byte_from_end() as EcWindow) << available; - available += EC_SYM_BITS; - if available > EC_WINDOW_SIZE - EC_SYM_BITS { - break; - } - } - } - let ret = window & ((1u32 << bits) - 1); - window >>= bits; - available -= bits as i32; - self.end_window = window; - self.nend_bits = available; - self.nbits_total += bits as i32; - ret - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn ilog_matches_expected() { - assert_eq!(ec_ilog(0), 0); - assert_eq!(ec_ilog(1), 1); - assert_eq!(ec_ilog(2), 2); - assert_eq!(ec_ilog(3), 2); - assert_eq!(ec_ilog(4), 3); - assert_eq!(ec_ilog(0xFFFF_FFFF), 32); - } - - #[test] - fn tell_frac_monotonicish() { - // Sanity: consuming bits should increase the reported usage. - let mut d = EcDec::new(&[0u8; 64]); - let a = d.tell_frac(); - let _ = d.dec_bits(8); - let b = d.tell_frac(); - assert!(b >= a); - } -} diff --git a/vendor/opus-decoder/src/error.rs b/vendor/opus-decoder/src/error.rs deleted file mode 100644 index 34fd811..0000000 --- a/vendor/opus-decoder/src/error.rs +++ /dev/null @@ -1,30 +0,0 @@ -use core::fmt; - -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Error { - InvalidSampleRate(u32), - InvalidChannels(u8), - PacketTooLarge { max: usize, got: usize }, - OutputTooSmall { needed: usize, got: usize }, - BadPacket, - NotImplemented, -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Error::InvalidSampleRate(fs) => write!(f, "invalid Opus output sample rate: {fs}"), - Error::InvalidChannels(ch) => write!(f, "invalid Opus channel count: {ch}"), - Error::PacketTooLarge { max, got } => { - write!(f, "Opus packet too large (max {max}, got {got})") - } - Error::OutputTooSmall { needed, got } => { - write!(f, "output buffer too small: needed {needed}, got {got}") - } - Error::BadPacket => write!(f, "invalid Opus packet"), - Error::NotImplemented => write!(f, "not implemented"), - } - } -} - -impl std::error::Error for Error {} diff --git a/vendor/opus-decoder/src/lib.rs b/vendor/opus-decoder/src/lib.rs deleted file mode 100644 index 904e453..0000000 --- a/vendor/opus-decoder/src/lib.rs +++ /dev/null @@ -1,884 +0,0 @@ -//! Public API for the pure Rust Opus decoder port. -//! -//! The crate keeps the CELT and SILK internals close to libopus while exposing -//! stable packet-to-PCM decoding entry points. - -#![forbid(unsafe_code)] -#![allow( - clippy::erasing_op, - clippy::identity_op, - clippy::precedence, - clippy::int_plus_one -)] - -macro_rules! debug_trace { - ($($arg:tt)*) => {}; -} - -mod celt; -pub(crate) mod compare; -mod entropy; -mod error; -mod multistream; -mod packet; -mod silk; -use crate::entropy::EcDec; -use core::sync::atomic::{AtomicUsize, Ordering}; - -pub(crate) use error::Error; -pub use multistream::OpusMultistreamDecoder; - -static TRACE_DECODE_PACKET_IDX: AtomicUsize = AtomicUsize::new(0); - -/// High-level single-stream Opus decoder. -/// -/// This wrapper exposes the stable public API for packet-to-PCM decoding while -/// keeping CELT and SILK state management internal to the crate. -pub struct OpusDecoder { - decoder: Decoder, - float_scratch: Vec, - loss_count: u32, - last_packet_duration: usize, -} - -impl OpusDecoder { - /// Maximum decoded frame size per channel at 48 kHz. - pub const MAX_FRAME_SIZE_48K: usize = Decoder::MAX_FRAME_SIZE_48K; - - /// Create a new decoder. - /// - /// `sample_rate` must be `8000`, `12000`, `16000`, `24000`, or `48000`, - /// and `channels` must be `1` or `2`. - /// - /// # Examples - /// - /// ```rust - /// use opus_decoder::OpusDecoder; - /// - /// let decoder = OpusDecoder::new(48_000, 2)?; - /// # let _ = decoder; - /// # Ok::<(), opus_decoder::OpusError>(()) - /// ``` - pub fn new(sample_rate: u32, channels: usize) -> Result { - let channels = - u8::try_from(channels).map_err(|_| OpusError::InvalidArgument("channels"))?; - let decoder = Decoder::new(sample_rate, channels).map_err(OpusError::from)?; - - Ok(Self { - decoder, - float_scratch: Vec::new(), - loss_count: 0, - last_packet_duration: 0, - }) - } - - /// Return the maximum decoded frame size per channel for this output rate. - pub fn max_frame_size_per_channel(&self) -> usize { - self.decoder.max_frame_size_per_channel() - } - - /// Mirror internal PLC bookkeeping into the public wrapper. - /// - /// Parameters: none. - /// Returns: nothing. - fn sync_state_from_decoder(&mut self) { - self.loss_count = self.decoder.loss_count; - self.last_packet_duration = self.decoder.last_packet_duration; - } - - /// Decode a packet into 16-bit PCM samples (interleaved if stereo). - /// - /// Empty `packet` input triggers packet loss concealment using the previous - /// decoder state. Returns the number of decoded samples per channel. - /// - `fec`: reserved for future in-band FEC support. Currently treated as - /// packet loss concealment (PLC) when `true`. Pass `false` for normal decode. - /// - /// # Examples - /// - /// ```rust,no_run - /// use opus_decoder::OpusDecoder; - /// - /// let mut decoder = OpusDecoder::new(48_000, 2)?; - /// let packet = std::fs::read("frame.opus")?; - /// let mut pcm = vec![0i16; 960 * 2]; - /// let samples = decoder.decode(&packet, &mut pcm, false)?; - /// # let _ = samples; - /// # Ok::<(), Box>(()) - /// ``` - pub fn decode( - &mut self, - packet: &[u8], - pcm: &mut [i16], - fec: bool, - ) -> Result { - let samples_per_channel = if packet.is_empty() || fec { - self.decoder.decode(None, pcm).map_err(OpusError::from)? - } else { - self.decoder - .decode(Some(packet), pcm) - .map_err(OpusError::from)? - }; - self.sync_state_from_decoder(); - Ok(samples_per_channel) - } - - /// Decode a packet into f32 PCM samples (interleaved if stereo). - /// - /// Empty `packet` input triggers packet loss concealment using the previous - /// decoder state. Returns the number of decoded samples per channel. - /// - `fec`: reserved for future in-band FEC support. Currently treated as - /// packet loss concealment (PLC) when `true`. Pass `false` for normal decode. - /// - /// # Examples - /// - /// ```rust,no_run - /// use opus_decoder::OpusDecoder; - /// - /// let mut decoder = OpusDecoder::new(48_000, 1)?; - /// let packet = std::fs::read("frame.opus")?; - /// let mut pcm = vec![0.0f32; 960]; - /// let samples = decoder.decode_float(&packet, &mut pcm, false)?; - /// # let _ = samples; - /// # Ok::<(), Box>(()) - /// ``` - pub fn decode_float( - &mut self, - packet: &[u8], - pcm: &mut [f32], - fec: bool, - ) -> Result { - let samples_per_channel_hint = if packet.is_empty() || fec { - self.decoder.last_packet_duration - } else { - packet::parse_packet(packet) - .map_err(OpusError::from)? - .samples_per_channel(self.decoder.fs_hz()) - }; - let needed = samples_per_channel_hint * self.decoder.channels() as usize; - if pcm.len() < needed { - return Err(OpusError::BufferTooSmall); - } - - if self.float_scratch.len() < needed { - self.float_scratch.resize(needed, 0); - } - - let samples_per_channel = self - .decoder - .decode( - if packet.is_empty() || fec { - None - } else { - Some(packet) - }, - &mut self.float_scratch[..needed], - ) - .map_err(OpusError::from)?; - self.sync_state_from_decoder(); - let written = samples_per_channel * self.decoder.channels() as usize; - for (dst, src) in pcm.iter_mut().zip(self.float_scratch[..written].iter()) { - *dst = f32::from(*src) / 32768.0; - } - - Ok(samples_per_channel) - } - - /// Reset decoder state (e.g. after packet loss or seek). - /// - /// Parameters: none. - /// Returns: nothing. - pub fn reset(&mut self) { - self.decoder.reset(); - self.loss_count = 0; - self.last_packet_duration = 0; - } - - /// Return the last range-coder final state observed by the decoder. - pub fn final_range(&self) -> u32 { - self.decoder.final_range() - } - - /// Return the last CELT recursive split count. - #[doc(hidden)] - pub fn last_split_count(&self) -> usize { - self.decoder.last_split_count() - } - - /// Return the current deemphasis memory for channel 0. - #[doc(hidden)] - pub fn deemph_mem(&self) -> f32 { - self.decoder.deemph_mem() - } - - /// Return whether the last CELT frame used the transient path. - #[doc(hidden)] - pub fn last_is_transient(&self) -> bool { - self.decoder.last_is_transient() - } - - /// Return whether the last decoded packet carried SILK redundancy. - #[doc(hidden)] - pub fn last_had_redundancy(&self) -> bool { - self.decoder.last_had_redundancy() - } - - /// Return whether the last decoded SILK redundancy was CELT-to-SILK. - #[doc(hidden)] - pub fn last_celt_to_silk(&self) -> bool { - self.decoder.last_celt_to_silk() - } -} - -/// Errors returned by the public Opus decoding API. -#[derive(Debug, thiserror::Error)] -pub enum OpusError { - /// The provided Opus packet is malformed or internally inconsistent. - #[error("invalid packet")] - InvalidPacket, - /// The decoder hit an internal unsupported or unexpected state. - #[error("internal error")] - InternalError, - /// The output PCM buffer is too small for the decoded frame. - #[error("buffer too small")] - BufferTooSmall, - /// One of the public API arguments is invalid. - #[error("invalid argument: {0}")] - InvalidArgument(&'static str), -} - -impl From for OpusError { - fn from(value: Error) -> Self { - match value { - Error::InvalidSampleRate(_) => Self::InvalidArgument("sample_rate"), - Error::InvalidChannels(_) => Self::InvalidArgument("channels"), - Error::PacketTooLarge { .. } | Error::BadPacket => Self::InvalidPacket, - Error::OutputTooSmall { .. } => Self::BufferTooSmall, - Error::NotImplemented => Self::InternalError, - } - } -} - -#[derive(Debug, Clone)] -pub(crate) struct Decoder { - fs_hz: u32, - channels: u8, - celt: celt::CeltDecoder, - silk: silk::SilkDecoder, - last_packet_split_count: usize, - last_final_range: u32, - last_had_redundancy: bool, - last_celt_to_silk: bool, - prev_mode: Option, - prev_redundancy: bool, - loss_count: u32, - last_packet_duration: usize, - last_output: Vec, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) enum OpusMode { - SilkOnly, - Hybrid, - CeltOnly, -} - -impl Decoder { - pub(crate) const MAX_FRAME_SIZE_48K: usize = 5760; // 120 ms @ 48 kHz - - pub(crate) fn new(fs_hz: u32, channels: u8) -> Result { - if !matches!(fs_hz, 8000 | 12000 | 16000 | 24000 | 48000) { - return Err(Error::InvalidSampleRate(fs_hz)); - } - if !matches!(channels, 1 | 2) { - return Err(Error::InvalidChannels(channels)); - } - Ok(Self { - fs_hz, - channels, - celt: celt::CeltDecoder::new(fs_hz, channels), - silk: silk::SilkDecoder::new(fs_hz, channels), - last_packet_split_count: 0, - last_final_range: 0, - last_had_redundancy: false, - last_celt_to_silk: false, - prev_mode: None, - prev_redundancy: false, - loss_count: 0, - last_packet_duration: 0, - last_output: Vec::new(), - }) - } - - pub(crate) fn fs_hz(&self) -> u32 { - self.fs_hz - } - - pub(crate) fn channels(&self) -> u8 { - self.channels - } - - pub(crate) fn max_frame_size_per_channel(&self) -> usize { - // Opus internally decodes 48 kHz and can output other Fs by deterministic - // downsampling/decimation (libopus behavior). - match self.fs_hz { - 8000 => 960, - 12000 => 1440, - 16000 => 1920, - 24000 => 2880, - 48000 => Self::MAX_FRAME_SIZE_48K, - _ => 0, // guarded in new() - } - } - - /// Conceal a lost non-CELT frame by fading the previous PCM. - /// - /// Params: per-channel `frame_size`, consecutive `loss_count`, and mutable `out`. - /// Returns: nothing; `out` receives interleaved concealed PCM. - fn conceal_with_fade(&self, frame_size: usize, loss_count: u32, out: &mut [i16]) { - let channels = self.channels as usize; - let needed = frame_size * channels; - if self.last_output.len() < needed { - out[..needed].fill(0); - return; - } - - let fade = 0.9f32.powi((loss_count.min(10) + 1) as i32); - for (dst, src) in out[..needed] - .iter_mut() - .zip(self.last_output[..needed].iter()) - { - let sample = f32::from(*src) * fade; - *dst = sample.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16; - } - } - - /// Persist the most recent decoded interleaved PCM frame. - /// - /// Params: decoded `out` buffer and `samples_per_channel` written into it. - /// Returns: nothing; the decoder keeps a copy for future PLC fallback. - fn store_last_output(&mut self, out: &[i16], samples_per_channel: usize) { - let written = samples_per_channel * self.channels as usize; - self.last_output.clear(); - self.last_output.extend_from_slice(&out[..written]); - } - - /// Decode a lost packet using CELT PLC or a safe fade fallback. - /// - /// Params: mutable interleaved `out` buffer. - /// Returns: concealed sample count per channel. - fn decode_lost_packet(&mut self, out: &mut [i16]) -> Result { - let samples_per_channel = self.last_packet_duration; - if samples_per_channel == 0 { - self.last_final_range = 0; - return Ok(0); - } - - let needed = samples_per_channel * self.channels as usize; - if out.len() < needed { - return Err(Error::OutputTooSmall { - needed, - got: out.len(), - }); - } - - out[..needed].fill(0); - match self.prev_mode { - Some(OpusMode::CeltOnly) => { - let concealed = self - .celt - .decode_lost(samples_per_channel, self.channels as usize); - for (dst, src) in out[..needed].iter_mut().zip(concealed.iter()) { - *dst = src.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16; - } - } - Some(OpusMode::SilkOnly | OpusMode::Hybrid) => { - self.silk - .decode_lost(samples_per_channel, out, self.loss_count)?; - } - _ => { - self.conceal_with_fade(samples_per_channel, self.loss_count, out); - } - } - - self.loss_count = self.loss_count.saturating_add(1); - self.last_packet_split_count = 0; - self.last_final_range = 0; - self.last_had_redundancy = false; - self.last_celt_to_silk = false; - self.prev_redundancy = false; - self.store_last_output(out, samples_per_channel); - Ok(samples_per_channel) - } - - pub(crate) fn reset(&mut self) { - // Placeholder. The real implementation will reset SILK/CELT state. - self.celt.reset(); - self.silk.reset(); - self.last_packet_split_count = 0; - self.last_final_range = 0; - self.last_had_redundancy = false; - self.last_celt_to_silk = false; - self.prev_mode = None; - self.prev_redundancy = false; - self.loss_count = 0; - self.last_packet_duration = 0; - self.last_output.clear(); - } - - /// Return range coder final state for conformance comparison. - pub(crate) fn final_range(&self) -> u32 { - self.last_final_range - } - - /// Return recursive split count from last decoded frame. - /// - /// This is a debug metric used for CELT split-path diagnostics. - pub(crate) fn last_split_count(&self) -> usize { - self.last_packet_split_count - } - - /// Return deemphasis memory for channel 0 (debug metric). - /// - /// This is used for CELT synthesis diagnostics in mono vectors. - pub(crate) fn deemph_mem(&self) -> f32 { - self.celt.deemph_mem(0) - } - - /// Return whether the last CELT frame was transient. - /// - /// This is a compatibility helper for the local conformance harness. - pub(crate) fn last_is_transient(&self) -> bool { - false - } - - /// Return whether the last decoded packet carried SILK redundancy. - /// - /// This follows the top-level transition bookkeeping used by the decoder. - pub(crate) fn last_had_redundancy(&self) -> bool { - self.last_had_redundancy - } - - /// Return whether the last decoded SILK redundancy was CELT-to-SILK. - /// - /// This is a debug helper for transition tracing in the conformance harness. - pub(crate) fn last_celt_to_silk(&self) -> bool { - self.last_celt_to_silk - } - - /// Decode one Opus packet to interleaved i16 PCM. - /// - /// - `packet=None` triggers PLC (packet loss concealment). - /// - `out` must be large enough for the maximum frame size (120 ms). - /// - Returns the number of samples per channel written. - pub(crate) fn decode( - &mut self, - packet: Option<&[u8]>, - out: &mut [i16], - ) -> Result { - let packet_idx = TRACE_DECODE_PACKET_IDX.fetch_add(1, Ordering::SeqCst); - let (toc, samples_per_channel_needed) = match packet { - Some(packet) => { - let pp = packet::parse_packet(packet)?; - (Some(pp.toc), pp.samples_per_channel(self.fs_hz)) - } - None => (None, self.last_packet_duration), - }; - - let needed = samples_per_channel_needed * self.channels as usize; - if out.len() < needed { - return Err(Error::OutputTooSmall { - needed, - got: out.len(), - }); - } - - let Some(toc) = toc else { - return self.decode_lost_packet(out); - }; - - self.celt.reset_loss_count(); - let config = (toc >> 3) & 0x1f; - let mode = match config { - 0..=11 => OpusMode::SilkOnly, - 12..=15 => OpusMode::Hybrid, - 16..=31 => OpusMode::CeltOnly, - _ => unreachable!(), - }; - // Parse again to get per-frame slices. - let pp = packet::parse_packet(packet.unwrap())?; - self.last_packet_split_count = 0; - self.last_final_range = 0; - - let transition = self.prev_mode.is_some() - && ((mode == OpusMode::CeltOnly - && self.prev_mode != Some(OpusMode::CeltOnly) - && !self.prev_redundancy) - || (mode != OpusMode::CeltOnly && self.prev_mode == Some(OpusMode::CeltOnly))); - let transition_samples = (self.fs_hz as usize) / 200; - let transition_overlap = (self.fs_hz as usize) / 400; - let channels = self.channels as usize; - let celt_transition = if transition && mode == OpusMode::CeltOnly { - vec![0i16; transition_samples * channels] - } else { - Vec::new() - }; - let mut apply_celt_transition = transition && mode == OpusMode::CeltOnly; - let mut reset_silk = transition && self.prev_mode == Some(OpusMode::CeltOnly); - let mut had_redundancy = false; - let mut last_celt_to_silk = false; - let mut written_per_channel = 0usize; - for &frame in pp.frames().iter() { - let frame_samples_48k = pp.samples_per_frame_48k; - let out_frame = &mut out[written_per_channel * self.channels as usize..]; - match mode { - OpusMode::CeltOnly => { - if apply_celt_transition { - self.celt.reset(); - } - // CELT frame sizes are specified in 48 kHz samples; this is enough - // to drive the CELT-side LM selection. - let mut ec = EcDec::new(frame); - let celt_frame = self.celt.decode_frame_with_ec( - frame, - &mut ec, - frame_samples_48k, - config, - pp.packet_channels, - packet_idx, - out_frame, - false, - )?; - if apply_celt_transition { - apply_transition_fade_i16( - &celt_transition, - &mut out_frame[..celt_frame.samples_per_channel * channels], - transition_overlap.min(celt_frame.samples_per_channel / 2), - channels, - self.celt.window(), - self.fs_hz, - ); - apply_celt_transition = false; - } - self.last_packet_split_count += self.celt.last_split_count(); - self.last_final_range = self.celt.final_range(); - written_per_channel += celt_frame.samples_per_channel; - } - OpusMode::SilkOnly => { - if reset_silk { - self.silk.reset(); - reset_silk = false; - } - let packet_frame = frame; - let silk_frame = self.silk.decode_frame( - packet_frame, - frame_samples_48k, - config, - pp.packet_channels, - packet_idx, - out_frame, - )?; - last_celt_to_silk = silk_frame.celt_to_silk; - let mut redundancy_rng = 0u32; - if silk_frame.consumed_redundancy { - let redundancy_data = - &packet_frame[packet_frame.len() - silk_frame.redundancy_bytes..]; - let redundancy_frame_size_48k = 240usize; - let redundancy_samples = (self.fs_hz as usize) / 200; - let redundancy_end_band = silk_redundancy_end_band(config); - let mut redundancy_out = - vec![0i16; redundancy_samples * self.channels as usize]; - if !silk_frame.celt_to_silk { - self.celt.reset(); - } - self.celt.set_start_band(0); - self.celt.set_end_band(redundancy_end_band); - let redundancy_frame = self.celt.decode_frame( - redundancy_data, - redundancy_frame_size_48k, - config, - pp.packet_channels, - packet_idx, - &mut redundancy_out, - )?; - self.celt.clear_end_band(); - redundancy_rng = self.celt.final_range(); - if !silk_frame.celt_to_silk { - let overlap = (self.fs_hz as usize) / 400; - let channels = self.channels as usize; - let silk_tail_start = - (silk_frame.samples_per_channel - overlap) * channels; - let silk_tail_end = silk_frame.samples_per_channel * channels; - let redundancy_start = overlap * channels; - let redundancy_end = redundancy_start + overlap * channels; - let silk_tail = out_frame[silk_tail_start..silk_tail_end].to_vec(); - smooth_fade_i16( - &silk_tail, - &redundancy_out[redundancy_start..redundancy_end], - &mut out_frame[silk_tail_start..silk_tail_end], - overlap, - channels, - self.celt.window(), - self.fs_hz, - ); - } else { - let overlap = (self.fs_hz as usize) / 400; - let channels = self.channels as usize; - let frame_len = silk_frame.samples_per_channel * channels; - apply_transition_fade_i16( - &redundancy_out, - &mut out_frame[..frame_len], - overlap.min(redundancy_frame.samples_per_channel / 2), - channels, - self.celt.window(), - self.fs_hz, - ); - } - } - had_redundancy = silk_frame.consumed_redundancy && !silk_frame.celt_to_silk; - self.last_final_range = self.silk.final_range() ^ redundancy_rng; - written_per_channel += silk_frame.samples_per_channel; - } - OpusMode::Hybrid => { - let mut ec = EcDec::new(frame); - let silk_frame = self.silk.decode_frame_with_ec( - frame, - &mut ec, - frame_samples_48k, - config, - pp.packet_channels, - true, - packet_idx, - out_frame, - )?; - let mut redundancy = if ec.tell() + 17 + 20 <= (frame.len() as i32) * 8 { - ec.dec_bit_logp(12) - } else { - false - }; - let mut celt_to_silk = false; - let mut redundancy_bytes = 0usize; - if redundancy { - celt_to_silk = ec.dec_bit_logp(1); - redundancy_bytes = ec.dec_uint(256) as usize + 2; - // NoSlacking: libopus's sanity check (opus_decoder.c, - // "len*8 < ec_tell"): a length the packet can't hold - // means no redundancy, never a slice out of range. - let left = frame.len().checked_sub(redundancy_bytes); - if left.is_none_or(|left| (left as i64) * 8 < i64::from(ec.tell())) { - redundancy = false; - celt_to_silk = false; - redundancy_bytes = 0; - } else { - ec.shrink_storage(redundancy_bytes); - } - } - let mut celt_to_silk_audio = Vec::new(); - let mut celt_to_silk_samples = 0usize; - let mut redundant_rng = 0u32; - let apply_celt_to_silk_audio = redundancy - && celt_to_silk - && (self.prev_mode != Some(OpusMode::SilkOnly) || self.prev_redundancy); - let reset_main_celt = self.prev_mode.is_some() - && self.prev_mode != Some(mode) - && !self.prev_redundancy; - if redundancy && celt_to_silk { - let redundancy_samples = (self.fs_hz as usize) / 200; - let redundancy_data = &frame[frame.len() - redundancy_bytes..]; - self.celt.set_start_band(0); - celt_to_silk_audio = vec![0i16; redundancy_samples * channels]; - let redundancy_frame = self.celt.decode_frame( - redundancy_data, - 240, - config, - pp.packet_channels, - packet_idx, - &mut celt_to_silk_audio, - )?; - self.last_packet_split_count += self.celt.last_split_count(); - celt_to_silk_samples = redundancy_frame.samples_per_channel; - redundant_rng = self.celt.final_range(); - } - if reset_main_celt { - self.celt.reset(); - } - self.celt.set_start_band(17); - let celt_frame = match self.celt.decode_frame_with_ec( - frame, - &mut ec, - frame_samples_48k, - config, - pp.packet_channels, - packet_idx, - out_frame, - true, - ) { - Ok(frame) => frame, - Err(err) => { - self.celt.set_start_band(0); - return Err(err); - } - }; - self.celt.set_start_band(0); - debug_assert_eq!( - silk_frame.samples_per_channel, - celt_frame.samples_per_channel - ); - self.last_packet_split_count += self.celt.last_split_count(); - let main_celt_rng = self.celt.final_range(); - self.last_final_range = main_celt_rng; - if redundancy && !celt_to_silk { - let channels = self.channels as usize; - let redundancy_samples = (self.fs_hz as usize) / 200; - let overlap = (self.fs_hz as usize) / 400; - let frame_len = celt_frame.samples_per_channel * channels; - let redundancy_data = &frame[frame.len() - redundancy_bytes..]; - let mut redundancy_out = vec![0i16; redundancy_samples * channels]; - self.celt.reset(); - self.celt.set_start_band(0); - let redundancy_frame = self.celt.decode_frame( - redundancy_data, - 240, - config, - pp.packet_channels, - packet_idx, - &mut redundancy_out, - )?; - self.last_packet_split_count += self.celt.last_split_count(); - let redundant_rng = self.celt.final_range(); - let fade_len = overlap * channels; - let tail_start = frame_len.saturating_sub(fade_len); - let tail_end = tail_start + fade_len; - let redundancy_start = fade_len; - let redundancy_end = redundancy_start + fade_len; - if tail_end <= out_frame.len() - && redundancy_end <= redundancy_out.len() - && redundancy_frame.samples_per_channel == redundancy_samples - { - let celt_tail = out_frame[tail_start..tail_end].to_vec(); - smooth_fade_i16( - &celt_tail, - &redundancy_out[redundancy_start..redundancy_end], - &mut out_frame[tail_start..tail_end], - overlap, - channels, - self.celt.window(), - self.fs_hz, - ); - } - self.last_final_range ^= redundant_rng; - } - if redundancy && celt_to_silk { - if apply_celt_to_silk_audio { - let overlap = (self.fs_hz as usize) / 400; - let frame_len = celt_frame.samples_per_channel * channels; - apply_transition_fade_i16( - &celt_to_silk_audio, - &mut out_frame[..frame_len], - overlap.min(celt_to_silk_samples / 2), - channels, - self.celt.window(), - self.fs_hz, - ); - } - self.last_final_range ^= redundant_rng; - } - last_celt_to_silk = celt_to_silk; - had_redundancy = redundancy && !celt_to_silk; - written_per_channel += silk_frame.samples_per_channel; - } - } - } - - self.prev_mode = Some(mode); - self.last_had_redundancy = had_redundancy; - self.last_celt_to_silk = last_celt_to_silk; - self.prev_redundancy = had_redundancy; - self.loss_count = 0; - self.last_packet_duration = written_per_channel; - self.store_last_output(out, written_per_channel); - Ok(written_per_channel) - } -} - -/// Crossfade SILK PCM with redundant CELT PCM using the CELT overlap window. -/// -/// Params: previous `in1`, incoming `in2`, mutable `out`, overlap length, -/// interleaved `channels`, CELT `window`, and output sampling rate `fs_hz`. -/// Returns: nothing; `out` is updated in-place. -fn smooth_fade_i16( - in1: &[i16], - in2: &[i16], - out: &mut [i16], - overlap: usize, - channels: usize, - window: &[f32], - fs_hz: u32, -) { - let inc = (48_000 / fs_hz) as usize; - for c in 0..channels { - for i in 0..overlap { - let w = window[i * inc] * window[i * inc]; - let idx = i * channels + c; - let mixed = w * in2[idx] as f32 + (1.0 - w) * in1[idx] as f32; - out[idx] = mixed.round().clamp(i16::MIN as f32, i16::MAX as f32) as i16; - } - } -} - -/// Apply the SILK-to-CELT transition prefix and crossfade. -/// -/// Params: previous-mode `transition` PCM, mutable decoded `pcm`, fade length -/// `overlap`, interleaved `channels`, CELT `window`, and output rate `fs_hz`. -/// Returns: nothing; `pcm` is updated in-place. -fn apply_transition_fade_i16( - transition: &[i16], - pcm: &mut [i16], - overlap: usize, - channels: usize, - window: &[f32], - fs_hz: u32, -) { - if overlap == 0 || channels == 0 { - return; - } - - let prefix_len = overlap * channels; - let copy_len = prefix_len.min(transition.len()).min(pcm.len()); - pcm[..copy_len].copy_from_slice(&transition[..copy_len]); - - let fade_available = (transition.len().saturating_sub(prefix_len)) - .min(pcm.len().saturating_sub(prefix_len)) - / channels; - if fade_available == 0 { - return; - } - - let fade_samples = fade_available.min(overlap); - let fade_len = fade_samples * channels; - let fade_start = prefix_len; - let fade_end = fade_start + fade_len; - let incoming = pcm[fade_start..fade_end].to_vec(); - smooth_fade_i16( - &transition[fade_start..fade_end], - &incoming, - &mut pcm[fade_start..fade_end], - fade_samples, - channels, - window, - fs_hz, - ); -} - -/// Map SILK packet config to CELT redundancy end band. -/// -/// Params: Opus TOC `config`. -/// Returns: exclusive CELT end band matching libopus packet bandwidth. -fn silk_redundancy_end_band(config: u8) -> usize { - match config { - 0..=3 => 13, - 4..=11 => 17, - 12..=13 => 19, - 14..=15 => 21, - _ => 21, - } -} diff --git a/vendor/opus-decoder/src/multistream.rs b/vendor/opus-decoder/src/multistream.rs deleted file mode 100644 index 4c196dc..0000000 --- a/vendor/opus-decoder/src/multistream.rs +++ /dev/null @@ -1,452 +0,0 @@ -//! Opus multistream decoder — wraps multiple `OpusDecoder` instances -//! and demultiplexes channel mapping per RFC 7845 §5.1. -//! -//! Mirrors the wrapper logic from libopus `opus_multistream_decoder.c`. - -use crate::packet; -use crate::{OpusDecoder, OpusError}; - -/// Opus multistream decoder. -/// -/// Holds one `OpusDecoder` per stream. Coupled streams use stereo decoders and -/// remaining streams use mono decoders. -pub struct OpusMultistreamDecoder { - /// One decoder per stream. Coupled streams are stereo (2ch), rest mono. - decoders: Vec, - /// Total output channels (1–255). - nb_channels: usize, - /// Total streams in packet (coupled + mono). - nb_streams: usize, - /// Streams that are stereo-coupled (always the first `nb_coupled_streams`). - nb_coupled_streams: usize, - /// `mapping[output_channel] = index into interleaved decoder outputs`. - /// `255` means "silence this output channel". - mapping: Vec, - /// Output sample rate (same for all streams). - sample_rate: u32, -} - -impl OpusMultistreamDecoder { - /// Create a new multistream decoder. - /// - /// Parameters: `sample_rate`, total output `nb_channels`, `nb_streams`, - /// `nb_coupled_streams`, and per-output-channel `mapping`. - /// Returns: initialized multistream decoder on success. - /// - /// # Examples - /// - /// ```rust - /// use opus_decoder::OpusMultistreamDecoder; - /// - /// let decoder = OpusMultistreamDecoder::new(48_000, 2, 2, 0, &[0, 1])?; - /// # let _ = decoder; - /// # Ok::<(), opus_decoder::OpusError>(()) - /// ``` - pub fn new( - sample_rate: u32, - nb_channels: usize, - nb_streams: usize, - nb_coupled_streams: usize, - mapping: &[u8], - ) -> Result { - if nb_channels == 0 || nb_channels > 255 { - return Err(OpusError::InvalidArgument("nb_channels")); - } - if nb_streams == 0 { - return Err(OpusError::InvalidArgument("nb_streams")); - } - if nb_coupled_streams > nb_streams { - return Err(OpusError::InvalidArgument("nb_coupled_streams")); - } - if nb_streams > 255usize.saturating_sub(nb_coupled_streams) { - return Err(OpusError::InvalidArgument("nb_streams")); - } - if mapping.len() != nb_channels { - return Err(OpusError::InvalidArgument("mapping")); - } - - let total_decoded_channels = (2 * nb_coupled_streams) + (nb_streams - nb_coupled_streams); - for &slot in mapping { - if slot != 255 && usize::from(slot) >= total_decoded_channels { - return Err(OpusError::InvalidArgument("mapping")); - } - } - - let mut decoders = Vec::with_capacity(nb_streams); - for stream_idx in 0..nb_streams { - let channels = if stream_idx < nb_coupled_streams { - 2 - } else { - 1 - }; - decoders.push(OpusDecoder::new(sample_rate, channels)?); - } - - Ok(Self { - decoders, - nb_channels, - nb_streams, - nb_coupled_streams, - mapping: mapping.to_vec(), - sample_rate, - }) - } - - /// Decode a multistream packet into interleaved i16 PCM. - /// - /// Parameters: multistream `packet`, writable interleaved `pcm`, and `fec`. - /// - `fec`: reserved for future in-band FEC support. Currently treated as - /// packet loss concealment (PLC) when `true`. Pass `false` for normal decode. - /// - /// Returns: decoded samples per output channel. - /// - /// # Examples - /// - /// ```rust,no_run - /// use opus_decoder::OpusMultistreamDecoder; - /// - /// let mut decoder = OpusMultistreamDecoder::new(48_000, 2, 2, 0, &[0, 1])?; - /// let packet = std::fs::read("multistream-frame.opus")?; - /// let mut pcm = vec![0i16; 960 * 2]; - /// let samples = decoder.decode(&packet, &mut pcm, false)?; - /// # let _ = samples; - /// # Ok::<(), Box>(()) - /// ``` - pub fn decode( - &mut self, - packet: &[u8], - pcm: &mut [i16], - fec: bool, - ) -> Result { - let sub_packets = if packet.is_empty() || fec { - vec![&[][..]; self.nb_streams] - } else { - split_multistream_packet(packet, self.nb_streams)? - }; - let frame_size = self.expected_frame_size(&sub_packets, fec)?; - let needed = frame_size * self.nb_channels; - if pcm.len() < needed { - return Err(OpusError::BufferTooSmall); - } - if frame_size == 0 { - return Ok(0); - } - - let mut stream_pcm = Vec::with_capacity(self.nb_streams); - for (stream_idx, sub_packet) in sub_packets.iter().enumerate() { - let channels = self.stream_channels(stream_idx); - let mut buf = vec![0i16; frame_size * channels]; - let written = self.decoders[stream_idx].decode(sub_packet, &mut buf, fec)?; - if written != frame_size { - return Err(OpusError::InvalidPacket); - } - stream_pcm.push(buf); - } - - for (channel_idx, &slot) in self.mapping.iter().enumerate() { - if slot == 255 { - zero_output_channel_i16(pcm, self.nb_channels, channel_idx, frame_size); - continue; - } - - let (stream_idx, source_channel) = self.slot_to_stream_channel(usize::from(slot)); - copy_output_channel_i16( - pcm, - self.nb_channels, - channel_idx, - &stream_pcm[stream_idx], - self.stream_channels(stream_idx), - source_channel, - frame_size, - ); - } - - Ok(frame_size) - } - - /// Decode a multistream packet into interleaved f32 PCM. - /// - /// Parameters: multistream `packet`, writable interleaved `pcm`, and `fec`. - /// - `fec`: reserved for future in-band FEC support. Currently treated as - /// packet loss concealment (PLC) when `true`. Pass `false` for normal decode. - /// - /// Returns: decoded samples per output channel. - /// - /// # Examples - /// - /// ```rust,no_run - /// use opus_decoder::OpusMultistreamDecoder; - /// - /// let mut decoder = OpusMultistreamDecoder::new(48_000, 2, 2, 0, &[0, 1])?; - /// let packet = std::fs::read("multistream-frame.opus")?; - /// let mut pcm = vec![0.0f32; 960 * 2]; - /// let samples = decoder.decode_float(&packet, &mut pcm, false)?; - /// # let _ = samples; - /// # Ok::<(), Box>(()) - /// ``` - pub fn decode_float( - &mut self, - packet: &[u8], - pcm: &mut [f32], - fec: bool, - ) -> Result { - let sub_packets = if packet.is_empty() || fec { - vec![&[][..]; self.nb_streams] - } else { - split_multistream_packet(packet, self.nb_streams)? - }; - let frame_size = self.expected_frame_size(&sub_packets, fec)?; - let needed = frame_size * self.nb_channels; - if pcm.len() < needed { - return Err(OpusError::BufferTooSmall); - } - if frame_size == 0 { - return Ok(0); - } - - let mut stream_pcm = Vec::with_capacity(self.nb_streams); - for (stream_idx, sub_packet) in sub_packets.iter().enumerate() { - let channels = self.stream_channels(stream_idx); - let mut buf = vec![0.0f32; frame_size * channels]; - let written = self.decoders[stream_idx].decode_float(sub_packet, &mut buf, fec)?; - if written != frame_size { - return Err(OpusError::InvalidPacket); - } - stream_pcm.push(buf); - } - - for (channel_idx, &slot) in self.mapping.iter().enumerate() { - if slot == 255 { - zero_output_channel_f32(pcm, self.nb_channels, channel_idx, frame_size); - continue; - } - - let (stream_idx, source_channel) = self.slot_to_stream_channel(usize::from(slot)); - copy_output_channel_f32( - pcm, - self.nb_channels, - channel_idx, - &stream_pcm[stream_idx], - self.stream_channels(stream_idx), - source_channel, - frame_size, - ); - } - - Ok(frame_size) - } - - /// Reset all internal decoders. - /// - /// Parameters: none. - /// Returns: nothing. - pub fn reset(&mut self) { - for decoder in &mut self.decoders { - decoder.reset(); - } - } - - /// Compute the expected frame size for one multistream decode call. - /// - /// Parameters: per-stream `sub_packets` and FEC flag `fec`. - /// Returns: decoded samples per output channel. - fn expected_frame_size(&self, sub_packets: &[&[u8]], fec: bool) -> Result { - if fec || sub_packets.iter().all(|packet| packet.is_empty()) { - return Ok(self - .decoders - .first() - .map(|decoder| decoder.last_packet_duration) - .unwrap_or(0)); - } - - let mut frame_size = None; - for sub_packet in sub_packets.iter().filter(|packet| !packet.is_empty()) { - let samples = packet::parse_packet(sub_packet) - .map_err(OpusError::from)? - .samples_per_channel(self.sample_rate); - if let Some(expected) = frame_size { - if expected != samples { - return Err(OpusError::InvalidPacket); - } - } else { - frame_size = Some(samples); - } - } - - Ok(frame_size.unwrap_or(0)) - } - - /// Return the decoded channel count for one elementary stream. - /// - /// Parameters: `stream_idx` elementary stream index. - /// Returns: `2` for coupled streams and `1` for mono streams. - fn stream_channels(&self, stream_idx: usize) -> usize { - if stream_idx < self.nb_coupled_streams { - 2 - } else { - 1 - } - } - - /// Map one decoded channel slot to `(stream_idx, channel_idx)`. - /// - /// Parameters: flattened decoded `slot`. - /// Returns: elementary stream index and channel index within that stream. - fn slot_to_stream_channel(&self, slot: usize) -> (usize, usize) { - let coupled_slots = 2 * self.nb_coupled_streams; - if slot < coupled_slots { - (slot / 2, slot % 2) - } else { - (self.nb_coupled_streams + (slot - coupled_slots), 0) - } - } -} - -/// Split a multistream packet into per-stream sub-packets. -/// -/// Parameters: full multistream `packet` and total `nb_streams`. -/// Returns: borrowed sub-packet slices in stream order. -fn split_multistream_packet(packet: &[u8], nb_streams: usize) -> Result, OpusError> { - if nb_streams == 0 { - return Err(OpusError::InvalidArgument("nb_streams")); - } - if packet.is_empty() { - return Ok(vec![packet; nb_streams]); - } - - let mut out = Vec::with_capacity(nb_streams); - let mut offset = 0usize; - for _stream_idx in 0..nb_streams.saturating_sub(1) { - let (packet_len, used) = parse_self_delimited_size(&packet[offset..])?; - offset += used; - if offset + packet_len > packet.len() { - return Err(OpusError::InvalidPacket); - } - out.push(&packet[offset..offset + packet_len]); - offset += packet_len; - } - - if offset > packet.len() { - return Err(OpusError::InvalidPacket); - } - out.push(&packet[offset..]); - Ok(out) -} - -/// Parse one self-delimited multistream sub-packet length. -/// -/// Parameters: encoded length `data`. -/// Returns: tuple `(packet_len, bytes_used)`. -fn parse_self_delimited_size(data: &[u8]) -> Result<(usize, usize), OpusError> { - if data.is_empty() { - return Err(OpusError::InvalidPacket); - } - - let first = usize::from(data[0]); - if first < 252 { - Ok((first, 1)) - } else { - if data.len() < 2 { - return Err(OpusError::InvalidPacket); - } - Ok((first + (4 * usize::from(data[1])), 2)) - } -} - -/// Copy one decoded i16 channel into the interleaved multistream output buffer. -/// -/// Parameters: mutable destination `pcm`, destination `dst_stride`, output -/// channel index `dst_channel`, source `src`, source `src_stride`, source -/// channel index `src_channel`, and `frame_size`. -/// Returns: nothing; destination channel is overwritten. -fn copy_output_channel_i16( - pcm: &mut [i16], - dst_stride: usize, - dst_channel: usize, - src: &[i16], - src_stride: usize, - src_channel: usize, - frame_size: usize, -) { - for sample_idx in 0..frame_size { - pcm[sample_idx * dst_stride + dst_channel] = src[sample_idx * src_stride + src_channel]; - } -} - -/// Zero one interleaved i16 output channel. -/// -/// Parameters: mutable destination `pcm`, destination `dst_stride`, output -/// channel index `dst_channel`, and `frame_size`. -/// Returns: nothing; destination channel is zero-filled. -fn zero_output_channel_i16( - pcm: &mut [i16], - dst_stride: usize, - dst_channel: usize, - frame_size: usize, -) { - for sample_idx in 0..frame_size { - pcm[sample_idx * dst_stride + dst_channel] = 0; - } -} - -/// Copy one decoded f32 channel into the interleaved multistream output buffer. -/// -/// Parameters: mutable destination `pcm`, destination `dst_stride`, output -/// channel index `dst_channel`, source `src`, source `src_stride`, source -/// channel index `src_channel`, and `frame_size`. -/// Returns: nothing; destination channel is overwritten. -fn copy_output_channel_f32( - pcm: &mut [f32], - dst_stride: usize, - dst_channel: usize, - src: &[f32], - src_stride: usize, - src_channel: usize, - frame_size: usize, -) { - for sample_idx in 0..frame_size { - pcm[sample_idx * dst_stride + dst_channel] = src[sample_idx * src_stride + src_channel]; - } -} - -/// Zero one interleaved f32 output channel. -/// -/// Parameters: mutable destination `pcm`, destination `dst_stride`, output -/// channel index `dst_channel`, and `frame_size`. -/// Returns: nothing; destination channel is zero-filled. -fn zero_output_channel_f32( - pcm: &mut [f32], - dst_stride: usize, - dst_channel: usize, - frame_size: usize, -) { - for sample_idx in 0..frame_size { - pcm[sample_idx * dst_stride + dst_channel] = 0.0; - } -} - -#[cfg(test)] -mod tests { - use super::*; - - /// Split one two-stream packet with a one-byte length prefix. - /// - /// Parameters: none. - /// Returns: nothing; panics on mismatch. - #[test] - fn split_two_stream_packet_with_short_prefix() { - let packet = [3u8, 10, 11, 12, 20, 21]; - let sub_packets = split_multistream_packet(&packet, 2).unwrap(); - assert_eq!(sub_packets, vec![&[10, 11, 12][..], &[20, 21][..]]); - } - - /// Split one empty packet into PLC sub-packets for all streams. - /// - /// Parameters: none. - /// Returns: nothing; panics on mismatch. - #[test] - fn split_empty_packet_for_plc() { - let sub_packets = split_multistream_packet(&[], 3).unwrap(); - assert_eq!(sub_packets, vec![&[][..], &[][..], &[][..]]); - } -} diff --git a/vendor/opus-decoder/src/packet.rs b/vendor/opus-decoder/src/packet.rs deleted file mode 100644 index 8a6036a..0000000 --- a/vendor/opus-decoder/src/packet.rs +++ /dev/null @@ -1,434 +0,0 @@ -use crate::Error; - -pub(crate) const MAX_FRAMES: usize = 48; -pub(crate) const MAX_PACKET_SIZE: usize = 1500; -pub(crate) const MAX_DURATION_SAMPLES_48K: usize = 5760; // 120 ms @ 48 kHz - -#[derive(Debug, Clone, Copy)] -pub(crate) struct ParsedPacket<'a> { - // Used by CELT/SILK mode selection and for debugging/conformance. - #[allow(dead_code)] - pub toc: u8, - #[allow(dead_code)] - pub packet_channels: u8, - pub frame_count: usize, - // Used by the actual decoder to feed individual frames into CELT/SILK. - #[allow(dead_code)] - pub frames: [&'a [u8]; MAX_FRAMES], - pub samples_per_frame_48k: usize, -} - -impl<'a> ParsedPacket<'a> { - #[allow(dead_code)] - pub fn frames(&self) -> &[&'a [u8]] { - &self.frames[..self.frame_count] - } - - pub fn samples_per_channel_48k(&self) -> usize { - self.samples_per_frame_48k * self.frame_count - } - - pub fn samples_per_channel(&self, fs_hz: u32) -> usize { - (self.samples_per_channel_48k() * fs_hz as usize) / 48_000 - } -} - -pub(crate) fn parse_packet(packet: &[u8]) -> Result, Error> { - if packet.is_empty() { - return Err(Error::BadPacket); - } - if packet.len() > MAX_PACKET_SIZE { - return Err(Error::PacketTooLarge { - max: MAX_PACKET_SIZE, - got: packet.len(), - }); - } - - let toc = packet[0]; - let packet_channels = if (toc & 0x04) != 0 { 2 } else { 1 }; - let samples_per_frame_48k = samples_per_frame_48k(toc); - - let mut frames: [&[u8]; MAX_FRAMES] = [&[]; MAX_FRAMES]; - - let code = toc & 0x03; - let frame_count = match code { - 0 => { - frames[0] = &packet[1..]; - 1 - } - 1 => { - let payload = &packet[1..]; - if payload.len() < 2 { - return Err(Error::BadPacket); - } - if payload.len() % 2 != 0 { - return Err(Error::BadPacket); - } - let sz0 = payload.len() / 2; - frames[0] = &payload[..sz0]; - frames[1] = &payload[sz0..]; - 2 - } - 2 => { - let payload = &packet[1..]; - let (sz0, used) = parse_size(payload)?; - let payload = &payload[used..]; - if sz0 > payload.len() { - return Err(Error::BadPacket); - } - frames[0] = &payload[..sz0]; - frames[1] = &payload[sz0..]; - 2 - } - 3 => { - let payload = &packet[1..]; - if payload.is_empty() { - return Err(Error::BadPacket); - } - - let ch = payload[0]; - let frame_count = (ch & 0x3f) as usize; - if !(1..=MAX_FRAMES).contains(&frame_count) { - return Err(Error::BadPacket); - } - - // RFC 6716: in the code-3 "frame count" byte, bit 6 indicates padding - // and bit 7 indicates VBR. - let has_padding = (ch & 0x40) != 0; - let vbr = (ch & 0x80) != 0; - - let mut idx = 1usize; // into payload - let mut data_end = payload.len(); // exclusive, relative to payload - - if has_padding { - let (pad_len, used) = parse_padding_len(&payload[idx..])?; - idx += used; - if pad_len > payload.len().saturating_sub(idx) { - return Err(Error::BadPacket); - } - data_end = payload.len() - pad_len; - if idx > data_end { - return Err(Error::BadPacket); - } - } - - let mut sizes = [0usize; MAX_FRAMES]; - if vbr { - let mut sum = 0usize; - for s in sizes.iter_mut().take(frame_count - 1) { - let (sz, used) = parse_size(&payload[idx..data_end])?; - idx += used; - *s = sz; - sum = sum.saturating_add(sz); - } - let remaining = data_end.saturating_sub(idx); - if sum > remaining { - return Err(Error::BadPacket); - } - sizes[frame_count - 1] = remaining - sum; - } else { - let remaining = data_end.saturating_sub(idx); - if remaining % frame_count != 0 { - return Err(Error::BadPacket); - } - let sz = remaining / frame_count; - for s in &mut sizes[..frame_count] { - *s = sz; - } - } - - // Now slice out each frame from the payload. - let mut off = idx; - for i in 0..frame_count { - let sz = sizes[i]; - if off + sz > data_end { - return Err(Error::BadPacket); - } - frames[i] = &payload[off..off + sz]; - off += sz; - } - if off != data_end { - return Err(Error::BadPacket); - } - - frame_count - } - _ => return Err(Error::BadPacket), - }; - - let total_samples_48k = samples_per_frame_48k.saturating_mul(frame_count); - if total_samples_48k > MAX_DURATION_SAMPLES_48K { - return Err(Error::BadPacket); - } - - Ok(ParsedPacket { - toc, - packet_channels, - frame_count, - frames, - samples_per_frame_48k, - }) -} - -fn parse_size(data: &[u8]) -> Result<(usize, usize), Error> { - if data.is_empty() { - return Err(Error::BadPacket); - } - let b0 = data[0] as usize; - if b0 < 252 { - Ok((b0, 1)) - } else { - if data.len() < 2 { - return Err(Error::BadPacket); - } - let b1 = data[1] as usize; - Ok(((b1 << 2) + b0, 2)) - } -} - -fn parse_padding_len(data: &[u8]) -> Result<(usize, usize), Error> { - let mut pad = 0usize; - let mut used = 0usize; - loop { - if used >= data.len() { - return Err(Error::BadPacket); - } - let p = data[used] as usize; - used += 1; - // RFC 6716 code-3 padding: each 255 byte contributes 254 and continues. - let add = if p == 255 { 254 } else { p }; - pad = pad.saturating_add(add); - if p != 255 { - break; - } - } - Ok((pad, used)) -} - -fn samples_per_frame_48k(toc: u8) -> usize { - // Port of libopus' `opus_packet_get_samples_per_frame()`, specialized to Fs=48000. - if (toc & 0x80) != 0 { - let audiosize = ((toc >> 3) & 0x03) as usize; - (48_000usize << audiosize) / 400 - } else if (toc & 0x60) == 0x60 { - if (toc & 0x08) != 0 { - 48_000usize / 50 - } else { - 48_000usize / 100 - } - } else { - let audiosize = ((toc >> 3) & 0x03) as usize; - if audiosize == 3 { - (48_000usize * 60) / 1000 - } else { - (48_000usize << audiosize) / 100 - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use std::io::Read; - use std::path::{Path, PathBuf}; - - #[test] - fn parse_code0_single_frame() { - let packet = [0b0000_0000u8, 1, 2, 3, 4]; - let pp = parse_packet(&packet).unwrap(); - assert_eq!(pp.frame_count, 1); - assert_eq!(pp.frames()[0], &[1, 2, 3, 4]); - } - - #[test] - fn parse_code1_two_frames_cbr() { - let packet = [0b0000_0001u8, 1, 2, 3, 4]; - let pp = parse_packet(&packet).unwrap(); - assert_eq!(pp.frame_count, 2); - assert_eq!(pp.frames()[0], &[1, 2]); - assert_eq!(pp.frames()[1], &[3, 4]); - } - - #[test] - fn parse_code1_odd_payload_is_rejected() { - let packet = [0b0000_0001u8, 1, 2, 3]; - let err = parse_packet(&packet).unwrap_err(); - assert!(matches!(err, Error::BadPacket)); - } - - #[test] - fn parse_code2_two_frames_vbr_short_size() { - // size field <252 => one byte. - let packet = [0b0000_0010u8, 2, 0xaa, 0xbb, 0xcc]; - let pp = parse_packet(&packet).unwrap(); - assert_eq!(pp.frame_count, 2); - assert_eq!(pp.frames()[0], &[0xaa, 0xbb]); - assert_eq!(pp.frames()[1], &[0xcc]); - } - - #[test] - fn parse_code2_two_frames_vbr_long_size() { - // size field >=252 => two bytes; size = 4*b1 + b0. - // Pick b0=252, b1=1 => size=256. - let mut packet = Vec::::new(); - packet.push(0b0000_0010u8); - packet.push(252); - packet.push(1); - packet.extend(std::iter::repeat_n(0x11, 256)); - packet.extend(std::iter::repeat_n(0x22, 10)); - - let pp = parse_packet(&packet).unwrap(); - assert_eq!(pp.frame_count, 2); - assert_eq!(pp.frames()[0].len(), 256); - assert_eq!(pp.frames()[1].len(), 10); - assert!(pp.frames()[0].iter().all(|&b| b == 0x11)); - assert!(pp.frames()[1].iter().all(|&b| b == 0x22)); - } - - #[test] - fn parse_code3_cbr_n_frames() { - // code=3, count=3, CBR, no padding - let mut packet = Vec::::new(); - packet.push(0b0000_0011u8); - packet.push(0b0000_0011u8); // 3 frames - packet.extend([1, 2, 3, 4, 5, 6]); // 6 bytes => 2 bytes per frame - - let pp = parse_packet(&packet).unwrap(); - assert_eq!(pp.frame_count, 3); - assert_eq!(pp.frames()[0], &[1, 2]); - assert_eq!(pp.frames()[1], &[3, 4]); - assert_eq!(pp.frames()[2], &[5, 6]); - } - - #[test] - fn parse_code3_vbr_n_frames() { - // code=3, count=3, VBR, no padding - // sizes: 1,2, last is remainder (3) - let mut packet = vec![ - 0b0000_0011u8, - 0b1000_0011u8, // VBR + 3 frames - 1, // size0 - 2, // size1 - ]; - packet.extend([0xa0]); // frame0 (1) - packet.extend([0xb0, 0xb1]); // frame1 (2) - packet.extend([0xc0, 0xc1, 0xc2]); // frame2 (3) - - let pp = parse_packet(&packet).unwrap(); - assert_eq!(pp.frame_count, 3); - assert_eq!(pp.frames()[0], &[0xa0]); - assert_eq!(pp.frames()[1], &[0xb0, 0xb1]); - assert_eq!(pp.frames()[2], &[0xc0, 0xc1, 0xc2]); - } - - #[test] - fn parse_code3_padding() { - // code=3, count=1, CBR, padding present with pad_len=2. - let mut packet = Vec::::new(); - packet.push(0b0000_0011u8); - packet.push(0b0100_0001u8); // padding + 1 frame - packet.push(2); // pad len - packet.extend([0x42, 0x43, 0x44]); // frame data (3 bytes) - packet.extend([0, 0]); // padding bytes - - let pp = parse_packet(&packet).unwrap(); - assert_eq!(pp.frame_count, 1); - assert_eq!(pp.frames()[0], &[0x42, 0x43, 0x44]); - } - - #[test] - fn samples_per_frame_uses_config_low_bits() { - // CELT-only config 19 (0b10011) is 20 ms => 960 samples at 48 kHz. - // This validates we use config low bits (toc>>3)&0x03, not toc bits 5..6. - let toc = 19u8 << 3; - assert_eq!(samples_per_frame_48k(toc), 960); - } - - #[test] - #[ignore = "requires OPUS_TESTVECTORS_DIR (or testdata/opus_testvectors)"] - fn parse_official_vectors_smoke() { - let dir = vectors_dir(); - let entries = fs::read_dir(&dir).unwrap_or_else(|e| { - panic!("failed to read vectors dir {}: {e}", dir.to_string_lossy()) - }); - - let mut bit_files = Vec::::new(); - for ent in entries { - let ent = ent.unwrap(); - let p = ent.path(); - if p.extension().and_then(|s| s.to_str()) == Some("bit") { - bit_files.push(p); - } - } - bit_files.sort(); - assert!(!bit_files.is_empty(), "no .bit files in {}", dir.display()); - - for bit in bit_files { - let packets = read_opus_demo_stream(&bit).unwrap_or_else(|e| { - panic!("failed to read {}: {e}", bit.display()); - }); - for p in packets.into_iter().flatten() { - parse_packet(&p).unwrap_or_else(|e| { - panic!( - "parse failed for {} packet len {}: {e}", - bit.display(), - p.len() - ); - }); - } - } - } - - fn vectors_dir() -> PathBuf { - if let Ok(p) = std::env::var("OPUS_TESTVECTORS_DIR") { - return PathBuf::from(p); - } - PathBuf::from("testdata/opus_testvectors") - } - - fn read_opus_demo_stream(path: &Path) -> std::io::Result>>> { - let mut f = fs::File::open(path)?; - let mut bytes = Vec::new(); - f.read_to_end(&mut bytes)?; - - let mut i = 0usize; - let mut out = Vec::new(); - while i < bytes.len() { - if i + 4 > bytes.len() { - return Err(std::io::Error::new( - std::io::ErrorKind::UnexpectedEof, - "truncated packet_len", - )); - } - let packet_len = - u32::from_be_bytes([bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]]) as usize; - i += 4; - - if i + 4 > bytes.len() { - return Err(std::io::Error::new( - std::io::ErrorKind::UnexpectedEof, - "truncated final_range", - )); - } - i += 4; - - if i + packet_len > bytes.len() { - return Err(std::io::Error::new( - std::io::ErrorKind::UnexpectedEof, - "truncated packet", - )); - } - let packet = if packet_len == 0 { - None - } else { - Some(bytes[i..i + packet_len].to_vec()) - }; - i += packet_len; - - out.push(packet); - } - - Ok(out) - } -} diff --git a/vendor/opus-decoder/src/silk/decode_core.rs b/vendor/opus-decoder/src/silk/decode_core.rs deleted file mode 100644 index af5c400..0000000 --- a/vendor/opus-decoder/src/silk/decode_core.rs +++ /dev/null @@ -1,497 +0,0 @@ -//! SILK excitation reconstruction and LPC synthesis. -#![allow(clippy::needless_range_loop)] - -use super::gain; -use super::ltp; -use super::pitch; -use super::plc::SilkPlcState; -use super::tables::{ - LTP_MEM_LENGTH_MS, LTP_ORDER, MAX_FRAME_LENGTH, MAX_LPC_ORDER, MAX_NB_SUBFR, - MAX_SUB_FRAME_LENGTH, QUANT_LEVEL_ADJUST_Q10, QUANTIZATION_OFFSETS_Q10, -}; - -const TYPE_VOICED: i32 = 2; -const RAND_MULTIPLIER: i32 = 196_314_165; -const RAND_INCREMENT: i32 = 907_633_515; - -/// Decoded SILK parameters needed by the synthesis core. -#[derive(Debug, Clone)] -pub(super) struct SilkFrameParams { - /// Signal type for the internal frame. - pub signal_type: i32, - /// Quantizer offset type. - pub quant_offset_type: i32, - /// Gain indices per subframe. - pub gain_indices: [i8; MAX_NB_SUBFR], - /// Absolute lag index for voiced frames. - pub lag_index: i16, - /// Contour table index for voiced frames. - pub contour_index: i8, - /// LTP periodicity index. - pub per_index: i8, - /// LTP codebook index per subframe. - pub ltp_indices: [i8; MAX_NB_SUBFR], - /// LTP scale selector. - pub ltp_scale_index: i8, - /// Excitation randomization seed. - pub seed: i8, - /// Signed excitation pulses. - pub pulses: Vec, - /// Conditional gain decoding flag. - pub conditional: bool, - /// Internal frame length in samples. - pub frame_length: usize, - /// Number of active subframes. - pub nb_subfr: usize, - /// Internal sampling rate in kHz. - pub fs_khz: u32, - /// LPC order for the current bandwidth. - pub lpc_order: usize, -} - -/// Persistent SILK synthesis state for one coded channel. -#[derive(Debug, Clone)] -pub(super) struct SilkChannelState { - /// Previous linear gain in Q16. - pub prev_gain_q16: i32, - /// Previous gain entropy index. - pub last_gain_index: i8, - /// Previous decoded signal type. - pub prev_signal_type: i32, - /// Previous pitch lag. - pub lag_prev: i32, - /// Persistent LPC synthesis state in Q14. - pub s_lpc_q14_buf: [i32; MAX_LPC_ORDER], - /// Output buffer kept for future voiced re-whitening. - pub out_buf: [i16; MAX_FRAME_LENGTH + 2 * MAX_SUB_FRAME_LENGTH], - /// PLC state refreshed after every successful decode. - pub plc: SilkPlcState, -} - -impl Default for SilkChannelState { - /// Create zeroed decoder state with libopus-compatible gain defaults. - /// - /// Params: none. - /// Returns: initialized per-channel SILK synthesis state. - fn default() -> Self { - Self { - prev_gain_q16: 65_536, - last_gain_index: 10, - prev_signal_type: 0, - lag_prev: 100, - s_lpc_q14_buf: [0; MAX_LPC_ORDER], - out_buf: [0; MAX_FRAME_LENGTH + 2 * MAX_SUB_FRAME_LENGTH], - plc: SilkPlcState::default(), - } - } -} - -/// Short debug snapshot from the SILK core. -#[derive(Debug, Clone, Copy, Default)] -pub(super) struct CoreTrace { - /// First eight excitation samples in Q14. - pub exc_q14_head: [i32; 8], - /// First eight synthesized LPC-state samples in Q14. - pub s_lpc_q14_head: [i32; 8], - /// First eight PCM samples at internal rate. - pub pcm_head: [i16; 8], -} - -/// Decode one SILK internal frame to PCM at the internal sample rate. -/// -/// Params: frame `params`, per-half `lpc_coeffs_q12`, mutable `output_pcm`, and channel `state`. -/// Returns: trace snapshot for packet-0 comparisons. -pub(super) fn decode_core( - params: &SilkFrameParams, - lpc_coeffs_q12: &[[i16; MAX_LPC_ORDER]; 2], - output_pcm: &mut [i16], - state: &mut SilkChannelState, - packet_idx: usize, - frame_idx: usize, -) -> CoreTrace { - let subfr_length = params.frame_length / params.nb_subfr; - let ltp_mem_length = params.fs_khz as usize * LTP_MEM_LENGTH_MS; - let nlsf_interpolation_flag = - lpc_coeffs_q12[0][..params.lpc_order] != lpc_coeffs_q12[1][..params.lpc_order]; - let gains_q16 = gain::decode_gains( - ¶ms.gain_indices[..params.nb_subfr], - &mut state.last_gain_index, - params.conditional, - params.nb_subfr, - ); - let pitch_lags = if params.signal_type == TYPE_VOICED { - pitch::decode_pitch_lags( - params.lag_index as i32, - params.contour_index as i32, - params.fs_khz as i32, - params.nb_subfr, - ) - } else { - [0; MAX_NB_SUBFR] - }; - let (ltp_coeffs_q14, ltp_scale_q14) = if params.signal_type == TYPE_VOICED { - ltp::decode_ltp_coeffs( - params.per_index, - ¶ms.ltp_indices, - params.nb_subfr, - params.ltp_scale_index, - ) - } else { - ([[0; super::tables::LTP_ORDER]; MAX_NB_SUBFR], 0) - }; - - let mut trace = CoreTrace::default(); - let offset_q10 = QUANTIZATION_OFFSETS_Q10[(params.signal_type >> 1) as usize] - [params.quant_offset_type as usize]; - let mut rand_seed = params.seed as i32; - let mut exc_q14 = [0i32; MAX_FRAME_LENGTH]; - for i in 0..params.frame_length { - rand_seed = silk_rand(rand_seed); - let mut sample = (params.pulses[i] as i32) << 14; - if sample > 0 { - sample -= QUANT_LEVEL_ADJUST_Q10 << 4; - } else if sample < 0 { - sample += QUANT_LEVEL_ADJUST_Q10 << 4; - } - sample += offset_q10 << 4; - if rand_seed < 0 { - sample = -sample; - } - rand_seed = rand_seed.wrapping_add(params.pulses[i] as i32); - exc_q14[i] = sample; - if i < 8 { - trace.exc_q14_head[i] = sample; - } - } - - let mut s_lpc_q14 = [0i32; MAX_LPC_ORDER + MAX_SUB_FRAME_LENGTH]; - s_lpc_q14[..MAX_LPC_ORDER].copy_from_slice(&state.s_lpc_q14_buf); - let mut s_ltp = [0i16; MAX_FRAME_LENGTH]; - let mut ltp_history_q15 = [0i32; MAX_FRAME_LENGTH * 2]; - let mut ltp_buf_idx = ltp_mem_length; - - for k in 0..params.nb_subfr { - let a_q12 = &lpc_coeffs_q12[k >> 1]; - let gain_q16 = gains_q16[k]; - let gain_q10 = gains_q16[k] >> 6; - let pitch_lag = pitch_lags[k]; - let mut inv_gain_q31 = inverse32_var_q(gain_q16, 47); - let gain_adj_q16 = if gain_q16 != state.prev_gain_q16 { - div32_var_q(state.prev_gain_q16, gain_q16, 16) - } else { - 1 << 16 - }; - - if gain_q16 != state.prev_gain_q16 { - for sample in s_lpc_q14.iter_mut().take(MAX_LPC_ORDER) { - *sample = smulww(gain_adj_q16, *sample); - } - } - state.prev_gain_q16 = gain_q16; - - if params.signal_type == TYPE_VOICED { - if k == 0 || (k == 2 && nlsf_interpolation_flag) { - let start_idx = - ltp_mem_length - pitch_lag as usize - params.lpc_order - (LTP_ORDER / 2); - if k == 2 { - let copy_len = 2 * subfr_length; - state.out_buf[ltp_mem_length..ltp_mem_length + copy_len] - .copy_from_slice(&output_pcm[..copy_len]); - } - lpc_analysis_filter( - &mut s_ltp[start_idx..ltp_mem_length], - &state.out_buf[start_idx + k * subfr_length - ..start_idx + k * subfr_length + (ltp_mem_length - start_idx)], - a_q12, - ltp_mem_length - start_idx, - params.lpc_order, - ); - if k == 0 { - inv_gain_q31 = (smulwb(inv_gain_q31, ltp_scale_q14) << 2) as i32; - } - for i in 0..(pitch_lag as usize + LTP_ORDER / 2) { - ltp_history_q15[ltp_buf_idx - i - 1] = - smulwb(inv_gain_q31, s_ltp[ltp_mem_length - i - 1] as i32); - } - } else if gain_adj_q16 != (1 << 16) { - for i in 0..(pitch_lag as usize + LTP_ORDER / 2) { - ltp_history_q15[ltp_buf_idx - i - 1] = - smulww(gain_adj_q16, ltp_history_q15[ltp_buf_idx - i - 1]); - } - } - } - - let subframe_range = k * subfr_length..(k + 1) * subfr_length; - let exc_subframe = &exc_q14[subframe_range.clone()]; - let mut pres_q14 = [0i32; MAX_SUB_FRAME_LENGTH]; - pres_q14[..subfr_length].copy_from_slice(exc_subframe); - if params.signal_type == TYPE_VOICED && pitch_lags[k] > 0 { - ltp::ltp_filter( - &mut pres_q14[..subfr_length], - exc_subframe, - &mut ltp_history_q15, - &mut ltp_buf_idx, - pitch_lags[k] as usize, - subfr_length, - <p_coeffs_q14[k], - packet_idx, - frame_idx, - k, - ); - } - - for i in 0..subfr_length { - let mut lpc_pred_q10 = (params.lpc_order as i32) >> 1; - for j in 0..params.lpc_order { - lpc_pred_q10 = smlawb( - lpc_pred_q10, - s_lpc_q14[MAX_LPC_ORDER + i - 1 - j], - a_q12[j] as i32, - ); - } - let current_q14 = add_sat32(pres_q14[i], lpc_pred_q10 << 4); - s_lpc_q14[MAX_LPC_ORDER + i] = current_q14; - - let pcm = sat16(rshift_round(smulww(current_q14, gain_q10), 8)); - output_pcm[k * subfr_length + i] = pcm; - - if k == 0 && i < 8 { - trace.s_lpc_q14_head[i] = current_q14; - trace.pcm_head[i] = pcm; - } - } - s_lpc_q14.copy_within(subfr_length..subfr_length + MAX_LPC_ORDER, 0); - } - - state - .s_lpc_q14_buf - .copy_from_slice(&s_lpc_q14[..MAX_LPC_ORDER]); - state.prev_signal_type = params.signal_type; - if params.signal_type == TYPE_VOICED && pitch_lags[params.nb_subfr - 1] > 0 { - state.lag_prev = pitch_lags[params.nb_subfr - 1]; - } - let mv_len = ltp_mem_length.saturating_sub(params.frame_length); - if mv_len > 0 { - state - .out_buf - .copy_within(params.frame_length..params.frame_length + mv_len, 0); - } - state.out_buf[mv_len..mv_len + params.frame_length] - .copy_from_slice(&output_pcm[..params.frame_length]); - trace -} - -/// Compute SILK RNG state update with wraparound. -/// -/// Params: signed `seed`. -/// Returns: updated pseudo-random state. -fn silk_rand(seed: i32) -> i32 { - seed.wrapping_mul(RAND_MULTIPLIER) - .wrapping_add(RAND_INCREMENT) -} - -/// Approximate signed `((a32 * b32) >> 16)`. -/// -/// Params: signed `a32` and `b32`. -/// Returns: signed Q16-scaled product. -fn smulww(a32: i32, b32: i32) -> i32 { - ((a32 as i64 * b32 as i64) >> 16) as i32 -} - -/// Approximate `a32 + ((b32 * (i16)c32) >> 16)`. -/// -/// Params: accumulator `a32`, multiplicand `b32`, and signed `c32`. -/// Returns: accumulated high-word product. -fn smlawb(a32: i32, b32: i32, c32: i32) -> i32 { - a32.wrapping_add(smulwb(b32, c32)) -} - -/// Saturating 32-bit addition. -/// -/// Params: signed `a` and `b`. -/// Returns: saturated sum. -fn add_sat32(a: i32, b: i32) -> i32 { - a.saturating_add(b) -} - -/// Saturate to `i16`. -/// -/// Params: signed `value`. -/// Returns: clamped 16-bit PCM sample. -fn sat16(value: i32) -> i16 { - value.clamp(i16::MIN as i32, i16::MAX as i32) as i16 -} - -/// Round a signed arithmetic right shift. -/// -/// Params: signed `value` and positive `shift`. -/// Returns: rounded shifted value. -fn rshift_round(value: i32, shift: usize) -> i32 { - if shift == 1 { - (value >> 1) + (value & 1) - } else { - ((value >> (shift - 1)) + 1) >> 1 - } -} - -/// Compute a saturating absolute value for normalization. -/// -/// Params: signed `value`. -/// Returns: non-negative magnitude, saturating `i32::MIN`. -fn abs32_for_clz(value: i32) -> i32 { - if value == i32::MIN { - i32::MAX - } else { - value.abs() - } -} - -/// Compute the signed high word of a 64-bit product. -/// -/// Params: signed `a32` and `b32`. -/// Returns: arithmetic `(a32 * b32) >> 32`. -fn smmul(a32: i32, b32: i32) -> i32 { - ((a32 as i64 * b32 as i64) >> 32) as i32 -} - -/// Left-shift with wraparound semantics. -/// -/// Params: signed `value` and non-negative `shift`. -/// Returns: wrapped left-shifted value. -fn lshift_ovflw(value: i32, shift: usize) -> i32 { - ((value as u32) << shift) as i32 -} - -/// Subtract with wraparound semantics. -/// -/// Params: signed `a` and `b`. -/// Returns: wrapped difference. -fn sub32_ovflw(a: i32, b: i32) -> i32 { - a.wrapping_sub(b) -} - -/// Left-shift with saturation. -/// -/// Params: signed `value` and non-negative `shift`. -/// Returns: left-shifted value clamped to `i32`. -fn lshift_sat32(value: i32, shift: usize) -> i32 { - if shift >= 31 { - if value > 0 { - i32::MAX - } else if value < 0 { - i32::MIN - } else { - 0 - } - } else { - value - .clamp(i32::MIN >> shift, i32::MAX >> shift) - .wrapping_shl(shift as u32) - } -} - -/// Approximate `(a32 << q_res) / b32` using SILK's reciprocal refinement. -/// -/// Params: numerator `a32`, denominator `b32`, and output Q-domain `q_res`. -/// Returns: scaled signed quotient. -fn div32_var_q(a32: i32, b32: i32, q_res: usize) -> i32 { - debug_assert!(b32 != 0); - if a32 == 0 { - return 0; - } - - let a_headrm = abs32_for_clz(a32).leading_zeros() as usize - 1; - let mut a32_nrm = a32 << a_headrm; - let b_headrm = abs32_for_clz(b32).leading_zeros() as usize - 1; - let b32_nrm = b32 << b_headrm; - - let b32_inv = (i32::MAX >> 2) / (b32_nrm >> 16); - let mut result = smulwb(a32_nrm, b32_inv); - a32_nrm = sub32_ovflw(a32_nrm, lshift_ovflw(smmul(b32_nrm, result), 3)); - result = smlawb(result, a32_nrm, b32_inv); - - let lshift = 29isize + a_headrm as isize - b_headrm as isize - q_res as isize; - if lshift < 0 { - lshift_sat32(result, (-lshift) as usize) - } else if lshift < 32 { - result >> lshift - } else { - 0 - } -} - -/// Approximate `a32 + ((b32 * c32) >> 16)` with SILK wraparound semantics. -/// -/// Params: accumulator `a32` and signed multiplicands `b32` and `c32`. -/// Returns: accumulated high-word product. -fn smlaww(a32: i32, b32: i32, c32: i32) -> i32 { - a32.wrapping_add(smulww(b32, c32)) -} - -/// Approximate `(1 << q_res) / value` using SILK's reciprocal refinement. -/// -/// Params: signed non-zero `value` and target result Q-domain `q_res`. -/// Returns: reciprocal approximation in fixed-point. -fn inverse32_var_q(value: i32, q_res: usize) -> i32 { - debug_assert!(value != 0); - debug_assert!(q_res > 0); - - let b_headrm = abs32_for_clz(value).leading_zeros() as usize - 1; - let b32_nrm = value << b_headrm; - let b32_inv = (i32::MAX >> 2) / (b32_nrm >> 16); - let mut result = b32_inv << 16; - let err_q32 = ((1i32 << 29) - smulwb(b32_nrm, b32_inv)) << 3; - result = smlaww(result, err_q32, b32_inv); - - let lshift = 61isize - b_headrm as isize - q_res as isize; - if lshift <= 0 { - lshift_sat32(result, (-lshift) as usize) - } else if lshift < 32 { - result >> lshift - } else { - 0 - } -} - -/// Port of `silk_LPC_analysis_filter` used for voiced re-whitening. -/// -/// Params: mutable `output`, past decoded `input`, LPC `a_q12`, signal `len`, and LPC `order`. -/// Returns: nothing; `output[..len]` receives Q0 re-whitened samples. -fn lpc_analysis_filter(output: &mut [i16], input: &[i16], a_q12: &[i16], len: usize, order: usize) { - debug_assert!(order <= len); - output[..order].fill(0); - for ix in order..len { - let mut out32_q12 = smulbb(input[ix - 1] as i32, a_q12[0] as i32); - out32_q12 = out32_q12.wrapping_add(smulbb(input[ix - 2] as i32, a_q12[1] as i32)); - out32_q12 = out32_q12.wrapping_add(smulbb(input[ix - 3] as i32, a_q12[2] as i32)); - out32_q12 = out32_q12.wrapping_add(smulbb(input[ix - 4] as i32, a_q12[3] as i32)); - out32_q12 = out32_q12.wrapping_add(smulbb(input[ix - 5] as i32, a_q12[4] as i32)); - out32_q12 = out32_q12.wrapping_add(smulbb(input[ix - 6] as i32, a_q12[5] as i32)); - for j in (6..order).step_by(2) { - out32_q12 = out32_q12.wrapping_add(smulbb(input[ix - 1 - j] as i32, a_q12[j] as i32)); - out32_q12 = - out32_q12.wrapping_add(smulbb(input[ix - 2 - j] as i32, a_q12[j + 1] as i32)); - } - let out32_q12 = ((input[ix] as i32) << 12).wrapping_sub(out32_q12); - output[ix] = sat16(rshift_round(out32_q12, 12)); - } -} - -/// Multiply signed low 16-bit halves. -/// -/// Params: signed `a32` and `b32`. -/// Returns: signed 32-bit product. -fn smulbb(a32: i32, b32: i32) -> i32 { - (a32 as i16 as i32) * (b32 as i16 as i32) -} - -/// Multiply by the signed low 16-bit half of `b32` and keep high word. -/// -/// Params: signed `a32` and signed `b32`. -/// Returns: high-word signed product. -fn smulwb(a32: i32, b32: i32) -> i32 { - let b16 = b32 as i16 as i32; - let high = (a32 >> 16) * b16; - let low = ((a32 & 0xFFFF) * b16) >> 16; - high.wrapping_add(low) -} diff --git a/vendor/opus-decoder/src/silk/entropy_tables.rs b/vendor/opus-decoder/src/silk/entropy_tables.rs deleted file mode 100644 index 52e3b02..0000000 --- a/vendor/opus-decoder/src/silk/entropy_tables.rs +++ /dev/null @@ -1,289 +0,0 @@ -//! SILK entropy decoding tables used by Phase 3a LBRR payload consumption. - -/// Number of SILK rate levels. -pub(super) const N_RATE_LEVELS: usize = 10; -/// Maximum pulse count symbol before LSB extension (`SILK_MAX_PULSES`). -pub(super) const SILK_MAX_PULSES: i32 = 16; -/// Shell frame length in samples. -pub(super) const SHELL_CODEC_FRAME_LENGTH: usize = 16; -/// Maximum NLSF quant amplitude used by entropy decode. -pub(super) const NLSF_QUANT_MAX_AMPLITUDE: i32 = 4; - -/// VAD-aware signal type + quant-offset iCDF. -pub(super) const TYPE_OFFSET_VAD_ICDF: [u8; 4] = [232, 158, 10, 0]; -/// No-VAD signal type + quant-offset iCDF. -pub(super) const TYPE_OFFSET_NO_VAD_ICDF: [u8; 2] = [230, 0]; - -/// NLSF interpolation-factor iCDF. -pub(super) const NLSF_INTERP_FACTOR_ICDF: [u8; 5] = [243, 221, 192, 181, 0]; -/// NLSF extension iCDF used for tails. -pub(super) const NLSF_EXT_ICDF: [u8; 7] = [100, 40, 16, 7, 3, 1, 0]; - -/// Uniform entropy tables used by SILK side information decode. -pub(super) const UNIFORM4_ICDF: [u8; 4] = [192, 128, 64, 0]; -pub(super) const UNIFORM6_ICDF: [u8; 6] = [213, 171, 128, 85, 43, 0]; -pub(super) const UNIFORM8_ICDF: [u8; 8] = [224, 192, 160, 128, 96, 64, 32, 0]; - -/// LSB-pulse extension iCDF. -pub(super) const LSB_ICDF: [u8; 2] = [120, 0]; - -/// Gain coding tables. -pub(super) const GAIN_ICDF: [[u8; 8]; 3] = [ - [224, 112, 44, 15, 3, 2, 1, 0], - [254, 237, 192, 132, 70, 23, 4, 0], - [255, 252, 226, 155, 61, 11, 2, 0], -]; -pub(super) const DELTA_GAIN_ICDF: [u8; 41] = [ - 250, 245, 234, 203, 71, 50, 42, 38, 35, 33, 31, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, - 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -]; - -/// Pitch decoding tables. -pub(super) const PITCH_LAG_ICDF: [u8; 32] = [ - 253, 250, 244, 233, 212, 182, 150, 131, 120, 110, 98, 85, 72, 60, 49, 40, 32, 25, 19, 15, 13, - 11, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -]; -pub(super) const PITCH_DELTA_ICDF: [u8; 21] = [ - 210, 208, 206, 203, 199, 193, 183, 168, 142, 104, 74, 52, 37, 27, 20, 14, 10, 6, 4, 2, 0, -]; -pub(super) const PITCH_CONTOUR_ICDF: [u8; 34] = [ - 223, 201, 183, 167, 152, 138, 124, 111, 98, 88, 79, 70, 62, 56, 50, 44, 39, 35, 31, 27, 24, 21, - 18, 16, 14, 12, 10, 8, 6, 4, 3, 2, 1, 0, -]; -pub(super) const PITCH_CONTOUR_NB_ICDF: [u8; 11] = [188, 176, 155, 138, 119, 97, 67, 43, 26, 10, 0]; -pub(super) const PITCH_CONTOUR_10MS_ICDF: [u8; 12] = - [165, 119, 80, 61, 47, 35, 27, 20, 14, 9, 4, 0]; -pub(super) const PITCH_CONTOUR_10MS_NB_ICDF: [u8; 3] = [113, 63, 0]; - -/// LTP coding tables. -pub(super) const LTP_PER_INDEX_ICDF: [u8; 3] = [179, 99, 0]; -pub(super) const LTP_GAIN_ICDF_0: [u8; 8] = [71, 56, 43, 30, 21, 12, 6, 0]; -pub(super) const LTP_GAIN_ICDF_1: [u8; 16] = [ - 199, 165, 144, 124, 109, 96, 84, 71, 61, 51, 42, 32, 23, 15, 8, 0, -]; -pub(super) const LTP_GAIN_ICDF_2: [u8; 32] = [ - 241, 225, 211, 199, 187, 175, 164, 153, 142, 132, 123, 114, 105, 96, 88, 80, 72, 64, 57, 50, - 44, 38, 33, 29, 24, 20, 16, 12, 9, 5, 2, 0, -]; -pub(super) const LTPSCALE_ICDF: [u8; 3] = [128, 64, 0]; - -/// Pulse coding tables. -pub(super) const RATE_LEVELS_ICDF: [[u8; 9]; 2] = [ - [241, 190, 178, 132, 87, 74, 41, 14, 0], - [223, 193, 157, 140, 106, 57, 39, 18, 0], -]; -pub(super) const PULSES_PER_BLOCK_ICDF: [[u8; 18]; 10] = [ - [ - 125, 51, 26, 18, 15, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, - ], - [ - 198, 105, 45, 22, 15, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, - ], - [ - 213, 162, 116, 83, 59, 43, 32, 24, 18, 15, 12, 9, 7, 6, 5, 3, 2, 0, - ], - [ - 239, 187, 116, 59, 28, 16, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, - ], - [ - 250, 229, 188, 135, 86, 51, 30, 19, 13, 10, 8, 6, 5, 4, 3, 2, 1, 0, - ], - [ - 249, 235, 213, 185, 156, 128, 103, 83, 66, 53, 42, 33, 26, 21, 17, 13, 10, 0, - ], - [ - 254, 249, 235, 206, 164, 118, 77, 46, 27, 16, 10, 7, 5, 4, 3, 2, 1, 0, - ], - [ - 255, 253, 249, 239, 220, 191, 156, 119, 85, 57, 37, 23, 15, 10, 6, 4, 2, 0, - ], - [ - 255, 253, 251, 246, 237, 223, 203, 179, 152, 124, 98, 75, 55, 40, 29, 21, 15, 0, - ], - [ - 255, 254, 253, 247, 220, 162, 106, 67, 42, 28, 18, 12, 9, 6, 4, 3, 2, 0, - ], -]; -pub(super) const SHELL_CODE_TABLE0: [u8; 152] = [ - 128, 0, 214, 42, 0, 235, 128, 21, 0, 244, 184, 72, 11, 0, 248, 214, 128, 42, 7, 0, 248, 225, - 170, 80, 25, 5, 0, 251, 236, 198, 126, 54, 18, 3, 0, 250, 238, 211, 159, 82, 35, 15, 5, 0, 250, - 231, 203, 168, 128, 88, 53, 25, 6, 0, 252, 238, 216, 185, 148, 108, 71, 40, 18, 4, 0, 253, 243, - 225, 199, 166, 128, 90, 57, 31, 13, 3, 0, 254, 246, 233, 212, 183, 147, 109, 73, 44, 23, 10, 2, - 0, 255, 250, 240, 223, 198, 166, 128, 90, 58, 33, 16, 6, 1, 0, 255, 251, 244, 231, 210, 181, - 146, 110, 75, 46, 25, 12, 5, 1, 0, 255, 253, 248, 238, 221, 196, 164, 128, 92, 60, 35, 18, 8, - 3, 1, 0, 255, 253, 249, 242, 229, 208, 180, 146, 110, 76, 48, 27, 14, 7, 3, 1, 0, -]; -pub(super) const SHELL_CODE_TABLE1: [u8; 152] = [ - 129, 0, 207, 50, 0, 236, 129, 20, 0, 245, 185, 72, 10, 0, 249, 213, 129, 42, 6, 0, 250, 226, - 169, 87, 27, 4, 0, 251, 233, 194, 130, 62, 20, 4, 0, 250, 236, 207, 160, 99, 47, 17, 3, 0, 255, - 240, 217, 182, 131, 81, 41, 11, 1, 0, 255, 254, 233, 201, 159, 107, 61, 20, 2, 1, 0, 255, 249, - 233, 206, 170, 128, 86, 50, 23, 7, 1, 0, 255, 250, 238, 217, 186, 148, 108, 70, 39, 18, 6, 1, - 0, 255, 252, 243, 226, 200, 166, 128, 90, 56, 30, 13, 4, 1, 0, 255, 252, 245, 231, 209, 180, - 146, 110, 76, 47, 25, 11, 4, 1, 0, 255, 253, 248, 237, 219, 194, 163, 128, 93, 62, 37, 19, 8, - 3, 1, 0, 255, 254, 250, 241, 226, 205, 177, 145, 111, 79, 51, 30, 15, 6, 2, 1, 0, -]; -pub(super) const SHELL_CODE_TABLE2: [u8; 152] = [ - 129, 0, 203, 54, 0, 234, 129, 23, 0, 245, 184, 73, 10, 0, 250, 215, 129, 41, 5, 0, 252, 232, - 173, 86, 24, 3, 0, 253, 240, 200, 129, 56, 15, 2, 0, 253, 244, 217, 164, 94, 38, 10, 1, 0, 253, - 245, 226, 189, 132, 71, 27, 7, 1, 0, 253, 246, 231, 203, 159, 105, 56, 23, 6, 1, 0, 255, 248, - 235, 213, 179, 133, 85, 47, 19, 5, 1, 0, 255, 254, 243, 221, 194, 159, 117, 70, 37, 12, 2, 1, - 0, 255, 254, 248, 234, 208, 171, 128, 85, 48, 22, 8, 2, 1, 0, 255, 254, 250, 240, 220, 189, - 149, 107, 67, 36, 16, 6, 2, 1, 0, 255, 254, 251, 243, 227, 201, 166, 128, 90, 55, 29, 13, 5, 2, - 1, 0, 255, 254, 252, 246, 234, 213, 183, 147, 109, 73, 43, 22, 10, 4, 2, 1, 0, -]; -pub(super) const SHELL_CODE_TABLE3: [u8; 152] = [ - 130, 0, 200, 58, 0, 231, 130, 26, 0, 244, 184, 76, 12, 0, 249, 214, 130, 43, 6, 0, 252, 232, - 173, 87, 24, 3, 0, 253, 241, 203, 131, 56, 14, 2, 0, 254, 246, 221, 167, 94, 35, 8, 1, 0, 254, - 249, 232, 193, 130, 65, 23, 5, 1, 0, 255, 251, 239, 211, 162, 99, 45, 15, 4, 1, 0, 255, 251, - 243, 223, 186, 131, 74, 33, 11, 3, 1, 0, 255, 252, 245, 230, 202, 158, 105, 57, 24, 8, 2, 1, 0, - 255, 253, 247, 235, 214, 179, 132, 84, 44, 19, 7, 2, 1, 0, 255, 254, 250, 240, 223, 196, 159, - 112, 69, 36, 15, 6, 2, 1, 0, 255, 254, 253, 245, 231, 209, 176, 136, 93, 55, 27, 11, 3, 2, 1, - 0, 255, 254, 253, 252, 239, 221, 194, 158, 117, 76, 42, 18, 4, 3, 2, 1, 0, -]; -pub(super) const SHELL_CODE_TABLE_OFFSETS: [u8; 17] = [ - 0, 0, 2, 5, 9, 14, 20, 27, 35, 44, 54, 65, 77, 90, 104, 119, 135, -]; -pub(super) const SIGN_ICDF: [u8; 42] = [ - 254, 49, 67, 77, 82, 93, 99, 198, 11, 18, 24, 31, 36, 45, 255, 46, 66, 78, 87, 94, 104, 208, - 14, 21, 32, 42, 51, 66, 255, 94, 104, 109, 112, 115, 118, 248, 53, 69, 80, 88, 95, 102, -]; - -/// Minimal NLSF entropy codebook fields needed by decode_indices. -#[derive(Debug, Clone, Copy)] -pub(super) struct NlsfEntropyCodebook { - /// NLSF order. - pub order: usize, - /// Number of stage-1 vectors. - pub n_vectors: usize, - /// Stage-1 iCDF table. - pub cb1_icdf: &'static [u8], - /// Packed selector map (`ec_sel`) for per-dimension stage-2 tables. - pub ec_sel: &'static [u8], - /// Stage-2 iCDF table bank (`ec_iCDF`). - pub ec_icdf: &'static [u8], -} - -/// NB/MB codebook stage-1 iCDF. -pub(super) const NLSF_CB1_ICDF_NB_MB: [u8; 64] = [ - 212, 178, 148, 129, 108, 96, 85, 82, 79, 77, 61, 59, 57, 56, 51, 49, 48, 45, 42, 41, 40, 38, - 36, 34, 31, 30, 21, 12, 10, 3, 1, 0, 255, 245, 244, 236, 233, 225, 217, 203, 190, 176, 175, - 161, 149, 136, 125, 114, 102, 91, 81, 71, 60, 52, 43, 35, 28, 20, 19, 18, 12, 11, 5, 0, -]; -/// NB/MB codebook selector map. -pub(super) const NLSF_CB2_SELECT_NB_MB: [u8; 160] = [ - 16, 0, 0, 0, 0, 99, 66, 36, 36, 34, 36, 34, 34, 34, 34, 83, 69, 36, 52, 34, 116, 102, 70, 68, - 68, 176, 102, 68, 68, 34, 65, 85, 68, 84, 36, 116, 141, 152, 139, 170, 132, 187, 184, 216, 137, - 132, 249, 168, 185, 139, 104, 102, 100, 68, 68, 178, 218, 185, 185, 170, 244, 216, 187, 187, - 170, 244, 187, 187, 219, 138, 103, 155, 184, 185, 137, 116, 183, 155, 152, 136, 132, 217, 184, - 184, 170, 164, 217, 171, 155, 139, 244, 169, 184, 185, 170, 164, 216, 223, 218, 138, 214, 143, - 188, 218, 168, 244, 141, 136, 155, 170, 168, 138, 220, 219, 139, 164, 219, 202, 216, 137, 168, - 186, 246, 185, 139, 116, 185, 219, 185, 138, 100, 100, 134, 100, 102, 34, 68, 68, 100, 68, 168, - 203, 221, 218, 168, 167, 154, 136, 104, 70, 164, 246, 171, 137, 139, 137, 155, 218, 219, 139, -]; -/// NB/MB codebook stage-2 iCDF table bank. -pub(super) const NLSF_CB2_ICDF_NB_MB: [u8; 72] = [ - 255, 254, 253, 238, 14, 3, 2, 1, 0, 255, 254, 252, 218, 35, 3, 2, 1, 0, 255, 254, 250, 208, 59, - 4, 2, 1, 0, 255, 254, 246, 194, 71, 10, 2, 1, 0, 255, 252, 236, 183, 82, 8, 2, 1, 0, 255, 252, - 235, 180, 90, 17, 2, 1, 0, 255, 248, 224, 171, 97, 30, 4, 1, 0, 255, 254, 236, 173, 95, 37, 7, - 1, 0, -]; - -/// WB codebook stage-1 iCDF. -pub(super) const NLSF_CB1_ICDF_WB: [u8; 64] = [ - 225, 204, 201, 184, 183, 175, 158, 154, 153, 135, 119, 115, 113, 110, 109, 99, 98, 95, 79, 68, - 52, 50, 48, 45, 43, 32, 31, 27, 18, 10, 3, 0, 255, 251, 235, 230, 212, 201, 196, 182, 167, 166, - 163, 151, 138, 124, 110, 104, 90, 78, 76, 70, 69, 57, 45, 34, 24, 21, 11, 6, 5, 4, 3, 0, -]; -/// WB codebook selector map. -pub(super) const NLSF_CB2_SELECT_WB: [u8; 256] = [ - 0, 0, 0, 0, 0, 0, 0, 1, 100, 102, 102, 68, 68, 36, 34, 96, 164, 107, 158, 185, 180, 185, 139, - 102, 64, 66, 36, 34, 34, 0, 1, 32, 208, 139, 141, 191, 152, 185, 155, 104, 96, 171, 104, 166, - 102, 102, 102, 132, 1, 0, 0, 0, 0, 16, 16, 0, 80, 109, 78, 107, 185, 139, 103, 101, 208, 212, - 141, 139, 173, 153, 123, 103, 36, 0, 0, 0, 0, 0, 0, 1, 48, 0, 0, 0, 0, 0, 0, 32, 68, 135, 123, - 119, 119, 103, 69, 98, 68, 103, 120, 118, 118, 102, 71, 98, 134, 136, 157, 184, 182, 153, 139, - 134, 208, 168, 248, 75, 189, 143, 121, 107, 32, 49, 34, 34, 34, 0, 17, 2, 210, 235, 139, 123, - 185, 137, 105, 134, 98, 135, 104, 182, 100, 183, 171, 134, 100, 70, 68, 70, 66, 66, 34, 131, - 64, 166, 102, 68, 36, 2, 1, 0, 134, 166, 102, 68, 34, 34, 66, 132, 212, 246, 158, 139, 107, - 107, 87, 102, 100, 219, 125, 122, 137, 118, 103, 132, 114, 135, 137, 105, 171, 106, 50, 34, - 164, 214, 141, 143, 185, 151, 121, 103, 192, 34, 0, 0, 0, 0, 0, 1, 208, 109, 74, 187, 134, 249, - 159, 137, 102, 110, 154, 118, 87, 101, 119, 101, 0, 2, 0, 36, 36, 66, 68, 35, 96, 164, 102, - 100, 36, 0, 2, 33, 167, 138, 174, 102, 100, 84, 2, 2, 100, 107, 120, 119, 36, 197, 24, 0, -]; -/// WB codebook stage-2 iCDF table bank. -pub(super) const NLSF_CB2_ICDF_WB: [u8; 72] = [ - 255, 254, 253, 244, 12, 3, 2, 1, 0, 255, 254, 252, 224, 38, 3, 2, 1, 0, 255, 254, 251, 209, 57, - 4, 2, 1, 0, 255, 254, 244, 195, 69, 4, 2, 1, 0, 255, 251, 232, 184, 84, 7, 2, 1, 0, 255, 254, - 240, 186, 86, 14, 2, 1, 0, 255, 254, 239, 178, 91, 30, 5, 1, 0, 255, 248, 227, 177, 100, 19, 2, - 1, 0, -]; - -const NLSF_CODEBOOK_NB_MB: NlsfEntropyCodebook = NlsfEntropyCodebook { - order: 10, - n_vectors: 32, - cb1_icdf: &NLSF_CB1_ICDF_NB_MB, - ec_sel: &NLSF_CB2_SELECT_NB_MB, - ec_icdf: &NLSF_CB2_ICDF_NB_MB, -}; - -const NLSF_CODEBOOK_WB: NlsfEntropyCodebook = NlsfEntropyCodebook { - order: 16, - n_vectors: 32, - cb1_icdf: &NLSF_CB1_ICDF_WB, - ec_sel: &NLSF_CB2_SELECT_WB, - ec_icdf: &NLSF_CB2_ICDF_WB, -}; - -/// Pick NLSF entropy codebook for internal SILK rate. -/// -/// Params: `fs_khz` internal SILK sample rate (8/12/16). -/// Returns: NB/MB codebook for 8/12 and WB codebook for 16 kHz. -pub(super) fn nlsf_codebook(fs_khz: u32) -> &'static NlsfEntropyCodebook { - if fs_khz == 16 { - &NLSF_CODEBOOK_WB - } else { - &NLSF_CODEBOOK_NB_MB - } -} - -/// Pick pitch low-bits iCDF for given internal SILK rate. -/// -/// Params: `fs_khz` internal SILK sample rate (8/12/16). -/// Returns: corresponding uniform iCDF table. -pub(super) fn pitch_lag_low_bits_icdf(fs_khz: u32) -> &'static [u8] { - match fs_khz { - 16 => &UNIFORM8_ICDF, - 12 => &UNIFORM6_ICDF, - _ => &UNIFORM4_ICDF, - } -} - -/// Pick pitch contour iCDF by bandwidth and subframe count. -/// -/// Params: `fs_khz` internal SILK sample rate and `nb_subfr` (2 or 4). -/// Returns: contour iCDF table used by decode_indices. -pub(super) fn pitch_contour_icdf(fs_khz: u32, nb_subfr: usize) -> &'static [u8] { - if fs_khz == 8 { - if nb_subfr == 4 { - &PITCH_CONTOUR_NB_ICDF - } else { - &PITCH_CONTOUR_10MS_NB_ICDF - } - } else if nb_subfr == 4 { - &PITCH_CONTOUR_ICDF - } else { - &PITCH_CONTOUR_10MS_ICDF - } -} - -/// Pick LTP gain iCDF table by PER index. -/// -/// Params: `per_index` decoded LTP periodicity class. -/// Returns: iCDF table for that class, or `None` when out of range. -pub(super) fn ltp_gain_icdf(per_index: usize) -> Option<&'static [u8]> { - match per_index { - 0 => Some(<P_GAIN_ICDF_0), - 1 => Some(<P_GAIN_ICDF_1), - 2 => Some(<P_GAIN_ICDF_2), - _ => None, - } -} diff --git a/vendor/opus-decoder/src/silk/gain.rs b/vendor/opus-decoder/src/silk/gain.rs deleted file mode 100644 index e7dda86..0000000 --- a/vendor/opus-decoder/src/silk/gain.rs +++ /dev/null @@ -1,114 +0,0 @@ -//! SILK gain dequantization helpers. - -use super::tables::{MAX_DELTA_GAIN_QUANT, MIN_DELTA_GAIN_QUANT, N_LEVELS_QGAIN}; - -const MIN_QGAIN_DB: i32 = 2; -const MAX_QGAIN_DB: i32 = 88; -const OFFSET: i32 = ((MIN_QGAIN_DB * 128) / 6) + 16 * 128; -const INV_SCALE_Q16: i32 = - (65_536 * (((MAX_QGAIN_DB - MIN_QGAIN_DB) * 128) / 6)) / (N_LEVELS_QGAIN - 1); - -/// Decode SILK gain indices to linear Q16 gains. -/// -/// Params: `gain_indices` decoded entropy indices, mutable `prev_index` state, -/// `conditional` first-subframe coding mode, and active `nb_subfr`. -/// Returns: decoded per-subframe gains in Q16. -pub(super) fn decode_gains( - gain_indices: &[i8], - prev_index: &mut i8, - conditional: bool, - nb_subfr: usize, -) -> [i32; super::tables::MAX_NB_SUBFR] { - let mut gains_q16 = [0i32; super::tables::MAX_NB_SUBFR]; - let mut prev = *prev_index as i32; - - for k in 0..nb_subfr { - if k == 0 && !conditional { - prev = (gain_indices[k] as i32).max(prev - 16); - } else { - let ind_tmp = gain_indices[k] as i32 + MIN_DELTA_GAIN_QUANT; - let double_step_size_threshold = 2 * MAX_DELTA_GAIN_QUANT - N_LEVELS_QGAIN + prev; - if ind_tmp > double_step_size_threshold { - prev += (ind_tmp << 1) - double_step_size_threshold; - } else { - prev += ind_tmp; - } - } - - prev = prev.clamp(0, N_LEVELS_QGAIN - 1); - gains_q16[k] = log2lin(smulwb(INV_SCALE_Q16, prev) + OFFSET); - } - - *prev_index = prev as i8; - gains_q16 -} - -/// Convert SILK log-domain gain to linear Q16. -/// -/// Params: `in_log_q7` log-domain value in Q7. -/// Returns: linear gain in Q16-compatible integer form. -fn log2lin(in_log_q7: i32) -> i32 { - if in_log_q7 < 0 { - return 0; - } - if in_log_q7 >= 3967 { - return i32::MAX; - } - - let mut out = 1i32 << (in_log_q7 >> 7); - let frac_q7 = in_log_q7 & 0x7F; - let curve = smlawb(frac_q7, smulbb(frac_q7, 128 - frac_q7), -174); - if in_log_q7 < 2048 { - out = add_rshift32(out, out.saturating_mul(curve), 7); - } else { - out = out.saturating_add((out >> 7).saturating_mul(curve)); - } - out -} - -/// Approximate `(a32 * (i16)b32) >> 16`. -/// -/// Params: signed `a32` and signed `b32`. -/// Returns: high-word signed product. -fn smulwb(a32: i32, b32: i32) -> i32 { - let b16 = b32 as i16 as i32; - let high = (a32 >> 16) * b16; - let low = ((a32 & 0xFFFF) * b16) >> 16; - high.wrapping_add(low) -} - -/// Approximate `a32 + ((b32 * (i16)c32) >> 16)`. -/// -/// Params: accumulator `a32`, multiplicand `b32`, and signed `c32`. -/// Returns: accumulated high-word product. -fn smlawb(a32: i32, b32: i32, c32: i32) -> i32 { - a32.wrapping_add(smulwb(b32, c32)) -} - -/// Multiply lower 16-bit halves of two signed values. -/// -/// Params: signed `a32` and `b32`. -/// Returns: signed 32-bit product. -fn smulbb(a32: i32, b32: i32) -> i32 { - (a32 as i16 as i32) * (b32 as i16 as i32) -} - -/// Add a rounded right-shifted value. -/// -/// Params: base `a`, addend `b`, and arithmetic `shift`. -/// Returns: `a + round(b >> shift)`. -fn add_rshift32(a: i32, b: i32, shift: usize) -> i32 { - a.saturating_add(rshift_round(b, shift)) -} - -/// Round a signed arithmetic right shift. -/// -/// Params: signed `value` and positive `shift`. -/// Returns: rounded shifted value. -fn rshift_round(value: i32, shift: usize) -> i32 { - if shift == 1 { - (value >> 1) + (value & 1) - } else { - ((value >> (shift - 1)) + 1) >> 1 - } -} diff --git a/vendor/opus-decoder/src/silk/lbrr.rs b/vendor/opus-decoder/src/silk/lbrr.rs deleted file mode 100644 index eee6478..0000000 --- a/vendor/opus-decoder/src/silk/lbrr.rs +++ /dev/null @@ -1,400 +0,0 @@ -//! SILK LBRR payload bit-consumption helpers (Phase 3a). - -use crate::Error; -use crate::entropy::EcDec; - -use super::entropy_tables; -use super::tables; - -const MAX_INTERNAL_FRAMES: usize = 3; -const MAX_NB_SUBFR: usize = 4; -const MAX_NB_SHELL_BLOCKS: usize = 20; -const TYPE_VOICED: i32 = 2; -const NLSF_STAGE2_ROW: usize = (2 * entropy_tables::NLSF_QUANT_MAX_AMPLITUDE + 1) as usize; - -/// Entropy-coding condition mode for SILK index parsing. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(super) enum CondCoding { - /// Independent coding (`CODE_INDEPENDENTLY`). - Independently, - /// Independent coding without LTP scaling (`CODE_INDEPENDENTLY_NO_LTP_SCALING`). - IndependentlyNoLtpScaling, - /// Conditional coding (`CODE_CONDITIONALLY`). - Conditionally, -} - -/// Channel-local entropy state needed by SILK index decoding. -#[derive(Debug, Clone, Copy, Default)] -pub(super) struct ChannelState { - /// Internal SILK sample rate in kHz. - pub fs_khz: u32, - /// Number of subframes in current internal frame (2 for 10 ms, 4 for 20 ms). - pub nb_subfr: usize, - /// Previous decoded signal type for entropy context. - pub ec_prev_signal_type: i32, - /// Previous decoded lag index for delta-lag entropy path. - pub ec_prev_lag_index: i32, -} - -/// Minimal decoded side information needed to continue entropy parsing. -#[derive(Debug, Clone, Copy)] -pub(super) struct DecodedSideInfo { - /// Signal type (no-voice, unvoiced, voiced). - pub signal_type: i32, - /// Quantizer offset type. - pub quant_offset_type: i32, - /// Decoded NLSF quantization path for Phase 3b. - pub nlsf: DecodedNlsfIndices, - /// Decoded gain indices for all subframes. - pub gain_indices: [i8; MAX_NB_SUBFR], - /// Absolute pitch lag index for voiced frames. - pub lag_index: i16, - /// Pitch contour index for voiced frames. - pub contour_index: i8, - /// LTP periodicity index. - pub per_index: i8, - /// LTP codebook index per subframe. - pub ltp_indices: [i8; MAX_NB_SUBFR], - /// LTP scaling index. - pub ltp_scale_index: i8, - /// Excitation randomization seed. - pub seed: i8, -} - -/// Stored SILK NLSF indices (stage1 plus stage2 residuals). -#[derive(Debug, Clone, Copy)] -pub(super) struct DecodedNlsfIndices { - /// Packed indices as `[I1, res0, ..., res(order-1)]`. - pub values: [i8; tables::MAX_LPC_ORDER + 1], - /// Decoded interpolation factor in Q2. - pub interp_coef_q2: i8, -} - -/// Configure channel-state decode context for current SILK frame shape. -/// -/// Params: mutable `state`, internal sampling rate `fs_khz`, and `nb_subfr`. -/// Returns: nothing. -pub(super) fn configure_channel_state(state: &mut ChannelState, fs_khz: u32, nb_subfr: usize) { - state.fs_khz = fs_khz; - state.nb_subfr = nb_subfr; -} - -/// Decode SILK side indices (`silk_decode_indices`) for entropy consumption. -/// -/// Params: mutable channel `state`, range decoder `dec`, `frame_vad` for current -/// internal frame, `decode_lbrr` flag, and entropy `cond_coding` mode. -/// Returns: decoded `signal_type` and `quant_offset_type`. -pub(super) fn decode_indices( - state: &mut ChannelState, - dec: &mut EcDec<'_>, - frame_vad: bool, - decode_lbrr: bool, - cond_coding: CondCoding, -) -> Result { - let ix = if decode_lbrr || frame_vad { - dec.dec_icdf(&entropy_tables::TYPE_OFFSET_VAD_ICDF, 8) + 2 - } else { - dec.dec_icdf(&entropy_tables::TYPE_OFFSET_NO_VAD_ICDF, 8) - }; - let signal_type = ix >> 1; - let quant_offset_type = ix & 1; - let mut gain_indices = [0i8; MAX_NB_SUBFR]; - let mut lag_index = 0i16; - let mut contour_index = 0i8; - let mut per_index = 0i8; - let mut ltp_indices = [0i8; MAX_NB_SUBFR]; - let mut ltp_scale_index = 0i8; - - if cond_coding == CondCoding::Conditionally { - gain_indices[0] = dec.dec_icdf(&entropy_tables::DELTA_GAIN_ICDF, 8) as i8; - } else { - gain_indices[0] = - (dec.dec_icdf(&entropy_tables::GAIN_ICDF[signal_type as usize], 8) << 3) as i8; - gain_indices[0] += dec.dec_icdf(&entropy_tables::UNIFORM8_ICDF, 8) as i8; - } - for gain in gain_indices.iter_mut().take(state.nb_subfr).skip(1) { - *gain = dec.dec_icdf(&entropy_tables::DELTA_GAIN_ICDF, 8) as i8; - } - - let cb = entropy_tables::nlsf_codebook(state.fs_khz); - let cb1_offset = ((signal_type >> 1) as usize) * cb.n_vectors; - let cb1_index = dec.dec_icdf(&cb.cb1_icdf[cb1_offset..], 8) as usize; - if cb1_index >= cb.n_vectors { - return Err(Error::BadPacket); - } - let mut nlsf_indices = [0i8; tables::MAX_LPC_ORDER + 1]; - nlsf_indices[0] = cb1_index as i8; - - let mut ec_ix = [0usize; 16]; - unpack_nlsf_ec_ix(&mut ec_ix, cb, cb1_index)?; - for (i, &row) in ec_ix.iter().take(cb.order).enumerate() { - let mut stage2 = dec.dec_icdf(&cb.ec_icdf[row..], 8); - if stage2 == 0 { - stage2 -= dec.dec_icdf(&entropy_tables::NLSF_EXT_ICDF, 8); - } else if stage2 == 2 * entropy_tables::NLSF_QUANT_MAX_AMPLITUDE { - stage2 += dec.dec_icdf(&entropy_tables::NLSF_EXT_ICDF, 8); - } - nlsf_indices[i + 1] = (stage2 - entropy_tables::NLSF_QUANT_MAX_AMPLITUDE) as i8; - } - - let mut interp_coef_q2 = 4i8; - if state.nb_subfr == MAX_NB_SUBFR { - interp_coef_q2 = dec.dec_icdf(&entropy_tables::NLSF_INTERP_FACTOR_ICDF, 8) as i8; - } - - if signal_type == TYPE_VOICED { - let mut decode_absolute_lag = true; - if cond_coding == CondCoding::Conditionally && state.ec_prev_signal_type == TYPE_VOICED { - let mut delta_lag = dec.dec_icdf(&entropy_tables::PITCH_DELTA_ICDF, 8); - if delta_lag > 0 { - delta_lag -= 9; - state.ec_prev_lag_index += delta_lag; - decode_absolute_lag = false; - } - } - if decode_absolute_lag { - state.ec_prev_lag_index = dec.dec_icdf(&entropy_tables::PITCH_LAG_ICDF, 8) - * ((state.fs_khz as i32) >> 1) - + dec.dec_icdf(entropy_tables::pitch_lag_low_bits_icdf(state.fs_khz), 8); - } - lag_index = state.ec_prev_lag_index as i16; - - contour_index = dec.dec_icdf( - entropy_tables::pitch_contour_icdf(state.fs_khz, state.nb_subfr), - 8, - ) as i8; - per_index = dec.dec_icdf(&entropy_tables::LTP_PER_INDEX_ICDF, 8) as i8; - let ltp_gain_icdf = - entropy_tables::ltp_gain_icdf(per_index as usize).ok_or(Error::BadPacket)?; - for ltp_index in ltp_indices.iter_mut().take(state.nb_subfr) { - *ltp_index = dec.dec_icdf(ltp_gain_icdf, 8) as i8; - } - if cond_coding == CondCoding::Independently { - ltp_scale_index = dec.dec_icdf(&entropy_tables::LTPSCALE_ICDF, 8) as i8; - } - } - - state.ec_prev_signal_type = signal_type; - let seed = dec.dec_icdf(&entropy_tables::UNIFORM4_ICDF, 8) as i8; - debug_trace!("silk lbrr side info consumed marker={}", 0); - - Ok(DecodedSideInfo { - signal_type, - quant_offset_type, - nlsf: DecodedNlsfIndices { - values: nlsf_indices, - interp_coef_q2, - }, - gain_indices, - lag_index, - contour_index, - per_index, - ltp_indices, - ltp_scale_index, - seed, - }) -} - -/// Decode SILK pulse payload (`silk_decode_pulses`) for entropy consumption. -/// -/// Params: range decoder `dec`, decoded `signal_type`, `quant_offset_type`, -/// and internal `frame_length` in samples. -/// Returns: decoded pulse magnitudes with signs, truncated to `frame_length`. -pub(super) fn decode_pulses( - dec: &mut EcDec<'_>, - signal_type: i32, - quant_offset_type: i32, - frame_length: usize, -) -> Result, Error> { - let rate_level = dec.dec_icdf( - &entropy_tables::RATE_LEVELS_ICDF[(signal_type >> 1) as usize], - 8, - ) as usize; - if rate_level >= entropy_tables::N_RATE_LEVELS { - return Err(Error::BadPacket); - } - - let mut iter = frame_length / entropy_tables::SHELL_CODEC_FRAME_LENGTH; - if iter * entropy_tables::SHELL_CODEC_FRAME_LENGTH < frame_length { - iter += 1; - } - if iter > MAX_NB_SHELL_BLOCKS { - return Err(Error::BadPacket); - } - - let mut sum_pulses = [0i32; MAX_NB_SHELL_BLOCKS]; - let mut n_lshifts = [0i32; MAX_NB_SHELL_BLOCKS]; - for i in 0..iter { - sum_pulses[i] = dec.dec_icdf(&entropy_tables::PULSES_PER_BLOCK_ICDF[rate_level], 8); - while sum_pulses[i] == entropy_tables::SILK_MAX_PULSES + 1 { - n_lshifts[i] += 1; - let row = entropy_tables::N_RATE_LEVELS - 1; - let offset = if n_lshifts[i] == 10 { 1 } else { 0 }; - sum_pulses[i] = dec.dec_icdf(&entropy_tables::PULSES_PER_BLOCK_ICDF[row][offset..], 8); - } - } - - let mut pulses = vec![0i16; iter * entropy_tables::SHELL_CODEC_FRAME_LENGTH]; - for i in 0..iter { - let block = &mut pulses[i * entropy_tables::SHELL_CODEC_FRAME_LENGTH - ..(i + 1) * entropy_tables::SHELL_CODEC_FRAME_LENGTH]; - if sum_pulses[i] > 0 { - shell_decoder(block, dec, sum_pulses[i])?; - } else { - block.fill(0); - } - } - - for i in 0..iter { - if n_lshifts[i] > 0 { - let block = &mut pulses[i * entropy_tables::SHELL_CODEC_FRAME_LENGTH - ..(i + 1) * entropy_tables::SHELL_CODEC_FRAME_LENGTH]; - for sample in block.iter_mut() { - let mut abs_q = *sample as i32; - for _ in 0..n_lshifts[i] { - abs_q = (abs_q << 1) + dec.dec_icdf(&entropy_tables::LSB_ICDF, 8); - } - *sample = abs_q as i16; - } - sum_pulses[i] |= n_lshifts[i] << 5; - } - } - - decode_signs( - dec, - &mut pulses, - signal_type, - quant_offset_type, - &sum_pulses, - iter, - )?; - pulses.truncate(frame_length); - Ok(pulses) -} - -/// Expand NLSF selector entries to stage-2 row offsets. -/// -/// Params: mutable `out_rows`, codebook descriptor `cb`, and `cb1_index`. -/// Returns: `Ok(())` on success, `BadPacket` on malformed selector bounds. -fn unpack_nlsf_ec_ix( - out_rows: &mut [usize; 16], - cb: &entropy_tables::NlsfEntropyCodebook, - cb1_index: usize, -) -> Result<(), Error> { - let sel_base = cb1_index * cb.order / 2; - if sel_base + cb.order / 2 > cb.ec_sel.len() { - return Err(Error::BadPacket); - } - for i in (0..cb.order).step_by(2) { - let entry = cb.ec_sel[sel_base + i / 2]; - out_rows[i] = (((entry >> 1) & 7) as usize) * NLSF_STAGE2_ROW; - out_rows[i + 1] = (((entry >> 5) & 7) as usize) * NLSF_STAGE2_ROW; - } - Ok(()) -} - -/// Decode one split in shell-tree coding. -/// -/// Params: mutable output split tuple, range decoder `dec`, parent pulse count `p`, -/// and shell table slice. -/// Returns: decoded child pulse counts. -fn decode_split(dec: &mut EcDec<'_>, p: i32, shell_table: &[u8]) -> Result<(i16, i16), Error> { - if p <= 0 { - return Ok((0, 0)); - } - let p_usize = p as usize; - if p_usize >= entropy_tables::SHELL_CODE_TABLE_OFFSETS.len() { - return Err(Error::BadPacket); - } - let offset = entropy_tables::SHELL_CODE_TABLE_OFFSETS[p_usize] as usize; - if offset >= shell_table.len() { - return Err(Error::BadPacket); - } - let child1 = dec.dec_icdf(&shell_table[offset..], 8) as i16; - let child2 = (p as i16).saturating_sub(child1); - Ok((child1, child2)) -} - -/// Decode one 16-sample shell block (`silk_shell_decoder`). -/// -/// Params: mutable `out_block`, range decoder `dec`, and total pulses `pulses4`. -/// Returns: nothing. -fn shell_decoder(out_block: &mut [i16], dec: &mut EcDec<'_>, pulses4: i32) -> Result<(), Error> { - if out_block.len() != entropy_tables::SHELL_CODEC_FRAME_LENGTH { - return Err(Error::BadPacket); - } - let (p30, p31) = decode_split(dec, pulses4, &entropy_tables::SHELL_CODE_TABLE3)?; - let (p20, p21) = decode_split(dec, p30 as i32, &entropy_tables::SHELL_CODE_TABLE2)?; - let (p10, p11) = decode_split(dec, p20 as i32, &entropy_tables::SHELL_CODE_TABLE1)?; - let (p00, p01) = decode_split(dec, p10 as i32, &entropy_tables::SHELL_CODE_TABLE0)?; - let (p02, p03) = decode_split(dec, p11 as i32, &entropy_tables::SHELL_CODE_TABLE0)?; - let (p12, p13) = decode_split(dec, p21 as i32, &entropy_tables::SHELL_CODE_TABLE1)?; - let (p04, p05) = decode_split(dec, p12 as i32, &entropy_tables::SHELL_CODE_TABLE0)?; - let (p06, p07) = decode_split(dec, p13 as i32, &entropy_tables::SHELL_CODE_TABLE0)?; - let (p22, p23) = decode_split(dec, p31 as i32, &entropy_tables::SHELL_CODE_TABLE2)?; - let (p14, p15) = decode_split(dec, p22 as i32, &entropy_tables::SHELL_CODE_TABLE1)?; - let (p08, p09) = decode_split(dec, p14 as i32, &entropy_tables::SHELL_CODE_TABLE0)?; - let (p10b, p11b) = decode_split(dec, p15 as i32, &entropy_tables::SHELL_CODE_TABLE0)?; - let (p16, p17) = decode_split(dec, p23 as i32, &entropy_tables::SHELL_CODE_TABLE1)?; - let (p12b, p13b) = decode_split(dec, p16 as i32, &entropy_tables::SHELL_CODE_TABLE0)?; - let (p14b, p15b) = decode_split(dec, p17 as i32, &entropy_tables::SHELL_CODE_TABLE0)?; - out_block.copy_from_slice(&[ - p00, p01, p02, p03, p04, p05, p06, p07, p08, p09, p10b, p11b, p12b, p13b, p14b, p15b, - ]); - Ok(()) -} - -/// Decode pulse signs (`silk_decode_signs`) for already decoded magnitudes. -/// -/// Params: range decoder `dec`, mutable `pulses` blocks, `signal_type`, -/// `quant_offset_type`, per-block `sum_pulses`, and active `iter` block count. -/// Returns: nothing. -fn decode_signs( - dec: &mut EcDec<'_>, - pulses: &mut [i16], - signal_type: i32, - quant_offset_type: i32, - sum_pulses: &[i32; MAX_NB_SHELL_BLOCKS], - iter: usize, -) -> Result<(), Error> { - if pulses.len() < iter * entropy_tables::SHELL_CODEC_FRAME_LENGTH { - return Err(Error::BadPacket); - } - let base = 7 * (quant_offset_type + (signal_type << 1)) as usize; - if base + 6 >= entropy_tables::SIGN_ICDF.len() { - return Err(Error::BadPacket); - } - for (i, &p) in sum_pulses.iter().take(iter).enumerate() { - if p <= 0 { - continue; - } - let idx = (p & 0x1F).min(6) as usize; - let mut icdf = [0u8; 2]; - icdf[0] = entropy_tables::SIGN_ICDF[base + idx]; - let block = &mut pulses[i * entropy_tables::SHELL_CODEC_FRAME_LENGTH - ..(i + 1) * entropy_tables::SHELL_CODEC_FRAME_LENGTH]; - for sample in block.iter_mut() { - if *sample > 0 { - let sign = dec.dec_icdf(&icdf, 8); - if sign == 0 { - *sample = -*sample; - } - } - } - } - Ok(()) -} - -/// Check whether parsed packet has any LBRR frame flagged. -/// -/// Params: per-channel per-frame LBRR flag matrix and coded channel count. -/// Returns: true when at least one LBRR frame is present. -pub(super) fn any_lbrr( - lbrr_flags: &[[bool; MAX_INTERNAL_FRAMES]; 2], - packet_channels: usize, -) -> bool { - lbrr_flags - .iter() - .take(packet_channels) - .any(|ch| ch.iter().any(|&flag| flag)) -} diff --git a/vendor/opus-decoder/src/silk/lpc.rs b/vendor/opus-decoder/src/silk/lpc.rs deleted file mode 100644 index 94150a9..0000000 --- a/vendor/opus-decoder/src/silk/lpc.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! SILK fixed-point LPC helpers. - -use super::tables::{LSF_COS_TAB, MAX_LPC_ORDER}; - -const QA: usize = 16; -const INV_PRED_GAIN_QA: usize = 24; -const MAX_LPC_STABILIZE_ITERATIONS: usize = 16; -const A_LIMIT_QA24: i32 = 16_773_022; -const MAX_PREDICTION_POWER_GAIN_INV_Q30: i32 = 107_374; - -/// Convert NLSF values in Q15 to LPC coefficients in Q12. -/// -/// Params: mutable `lpc_q12`, stabilized `nlsf_q15`, and LPC `order` (10 or 16). -/// Returns: nothing; `lpc_q12[..order]` is overwritten. -pub(super) fn nlsf2a(lpc_q12: &mut [i16], nlsf_q15: &[i16], order: usize) { - let ordering = match order { - 10 => &[0usize, 9, 6, 3, 4, 5, 8, 1, 2, 7][..], - 16 => &[0usize, 15, 8, 7, 4, 11, 12, 3, 2, 13, 10, 5, 6, 9, 14, 1][..], - _ => panic!("unsupported LPC order {order}"), - }; - - let mut cos_lsf_qa = [0i32; MAX_LPC_ORDER]; - for k in 0..order { - let f_int = (nlsf_q15[k] as i32 >> 8) as usize; - let f_frac = nlsf_q15[k] as i32 - ((f_int as i32) << 8); - let cos_val = LSF_COS_TAB[f_int] as i32; - let delta = LSF_COS_TAB[f_int + 1] as i32 - cos_val; - let interp = (cos_val << 8) + delta * f_frac; - cos_lsf_qa[ordering[k]] = rshift_round(interp, 20 - QA); - } - - let dd = order / 2; - let mut p = [0i32; MAX_LPC_ORDER / 2 + 1]; - let mut q = [0i32; MAX_LPC_ORDER / 2 + 1]; - nlsf2a_find_poly(&mut p, &cos_lsf_qa[0..], dd); - nlsf2a_find_poly(&mut q, &cos_lsf_qa[1..], dd); - - let mut a32_qa1 = [0i32; MAX_LPC_ORDER]; - for k in 0..dd { - let ptmp = p[k + 1] + p[k]; - let qtmp = q[k + 1] - q[k]; - a32_qa1[k] = -qtmp - ptmp; - a32_qa1[order - k - 1] = qtmp - ptmp; - } - - lpc_fit(lpc_q12, &mut a32_qa1, 12, QA + 1, order); - for i in 0..MAX_LPC_STABILIZE_ITERATIONS { - let inv_gain = lpc_inv_pred_gain(&lpc_q12[..order], order); - if inv_gain != 0 { - break; - } - bw_expand_32(&mut a32_qa1[..order], order, 65_536 - (2 << i)); - for k in 0..order { - lpc_q12[k] = rshift_round(a32_qa1[k], QA + 1 - 12) as i16; - } - } -} - -/// Compute inverse prediction gain and reject unstable LPC filters. -/// -/// Params: `lpc_q12` LPC coefficients in Q12 and LPC `order`. -/// Returns: inverse prediction gain in Q30, or `0` if unstable. -pub(super) fn lpc_inv_pred_gain(lpc_q12: &[i16], order: usize) -> i32 { - let mut a_tmp_qa = [0i32; MAX_LPC_ORDER]; - let mut dc_resp = 0i32; - for k in 0..order { - dc_resp += lpc_q12[k] as i32; - a_tmp_qa[k] = (lpc_q12[k] as i32) << (INV_PRED_GAIN_QA - 12); - } - if dc_resp >= 4096 { - return 0; - } - lpc_inverse_pred_gain_qa(&mut a_tmp_qa[..order], order) -} - -/// Apply Q16 bandwidth expansion to Q12 LPC coefficients. -/// -/// Params: mutable `lpc_q12`, LPC `order`, and `chirp_q16` factor. -/// Returns: nothing; coefficients are scaled in place. -#[allow(dead_code)] -pub(super) fn bw_expand(lpc_q12: &mut [i16], order: usize, chirp_q16: i32) { - let mut chirp_q16_local = chirp_q16; - let chirp_minus_one_q16 = chirp_q16_local - 65_536; - for coeff in lpc_q12.iter_mut().take(order.saturating_sub(1)) { - *coeff = rshift_round(((chirp_q16_local as i64) * (*coeff as i64)) as i32, 16) as i16; - chirp_q16_local += rshift_round( - ((chirp_q16_local as i64) * (chirp_minus_one_q16 as i64)) as i32, - 16, - ); - } - lpc_q12[order - 1] = rshift_round( - ((chirp_q16_local as i64) * (lpc_q12[order - 1] as i64)) as i32, - 16, - ) as i16; -} - -/// Build the even or odd Chebyshev polynomial used by `nlsf2a`. -/// -/// Params: mutable `out`, interleaved `c_lsf_qa`, and half-order `dd`. -/// Returns: nothing; `out[..=dd]` is written. -fn nlsf2a_find_poly(out: &mut [i32; MAX_LPC_ORDER / 2 + 1], c_lsf_qa: &[i32], dd: usize) { - out[0] = 1 << QA; - out[1] = -c_lsf_qa[0]; - for k in 1..dd { - let ftmp = c_lsf_qa[2 * k]; - out[k + 1] = (out[k - 1] << 1) - rshift_round64((ftmp as i64) * out[k] as i64, QA); - for n in (2..=k).rev() { - out[n] += out[n - 2] - rshift_round64((ftmp as i64) * out[n - 1] as i64, QA); - } - out[1] -= ftmp; - } -} - -/// Fit wide intermediate LPC coefficients into `i16` Q12 range. -/// -/// Params: mutable `lpc_q12`, mutable `a_qin`, output Q `qout`, input Q `qin`, and LPC `order`. -/// Returns: nothing; `lpc_q12[..order]` receives clipped or chirped coefficients. -fn lpc_fit( - lpc_q12: &mut [i16], - a_qin: &mut [i32; MAX_LPC_ORDER], - qout: usize, - qin: usize, - order: usize, -) { - let mut peak_index = 0usize; - for _ in 0..10 { - let mut max_abs = 0i32; - for (idx, &value) in a_qin.iter().take(order).enumerate() { - let abs_value = value.saturating_abs(); - if abs_value > max_abs { - max_abs = abs_value; - peak_index = idx; - } - } - let shifted = rshift_round(max_abs, qin - qout); - if shifted <= i16::MAX as i32 { - for k in 0..order { - lpc_q12[k] = rshift_round(a_qin[k], qin - qout) as i16; - } - return; - } - - let capped = shifted.min(163_838); - let numerator = ((capped - i16::MAX as i32) as i64) << 14; - let denominator = ((capped as i64) * (peak_index as i64 + 1)) >> 2; - let chirp_q16 = 65_470 - (numerator / denominator) as i32; - bw_expand_32(&mut a_qin[..order], order, chirp_q16); - } - - for k in 0..order { - let rounded = rshift_round(a_qin[k], qin - qout); - let clipped = rounded.clamp(i16::MIN as i32, i16::MAX as i32) as i16; - lpc_q12[k] = clipped; - a_qin[k] = (clipped as i32) << (qin - qout); - } -} - -/// Apply bandwidth expansion directly to 32-bit LPC intermediates. -/// -/// Params: mutable `ar_qa1`, LPC `order`, and `chirp_q16` factor. -/// Returns: nothing; coefficients are chirped in place. -fn bw_expand_32(ar_qa1: &mut [i32], order: usize, chirp_q16: i32) { - let mut chirp_q16_local = chirp_q16; - let chirp_minus_one_q16 = chirp_q16_local - 65_536; - for coeff in ar_qa1.iter_mut().take(order.saturating_sub(1)) { - *coeff = ((*coeff as i64 * chirp_q16_local as i64) >> 16) as i32; - chirp_q16_local += rshift_round( - ((chirp_q16_local as i64) * (chirp_minus_one_q16 as i64)) as i32, - 16, - ); - } - ar_qa1[order - 1] = ((ar_qa1[order - 1] as i64 * chirp_q16_local as i64) >> 16) as i32; -} - -/// Port of `LPC_inverse_pred_gain_QA_c` for Q24 LPC coefficients. -/// -/// Params: mutable `a_qa` scratch LPC vector and LPC `order`. -/// Returns: inverse prediction gain in Q30, or `0` when unstable. -fn lpc_inverse_pred_gain_qa(a_qa: &mut [i32], order: usize) -> i32 { - let mut inv_gain_q30 = 1 << 30; - for k in (1..order).rev() { - let coeff = a_qa[k]; - if !(-A_LIMIT_QA24..=A_LIMIT_QA24).contains(&coeff) { - return 0; - } - - let rc_q31 = -(coeff << (31 - INV_PRED_GAIN_QA)); - let rc_mult1_q30 = (1 << 30) - smmul(rc_q31, rc_q31); - inv_gain_q30 = smmul(inv_gain_q30, rc_mult1_q30) << 2; - if inv_gain_q30 < MAX_PREDICTION_POWER_GAIN_INV_Q30 { - return 0; - } - - let mult2_q = 32 - (rc_mult1_q30.unsigned_abs().leading_zeros() as usize); - let rc_mult2 = inverse32_var_q(rc_mult1_q30, mult2_q + 30); - for n in 0..((k + 1) >> 1) { - let tmp1 = a_qa[n]; - let tmp2 = a_qa[k - n - 1]; - let lhs = tmp1.saturating_sub(mul32_frac_q(tmp2, rc_q31, 31)); - let rhs = tmp2.saturating_sub(mul32_frac_q(tmp1, rc_q31, 31)); - let next0 = rshift_round64((lhs as i64) * rc_mult2 as i64, mult2_q); - let next1 = rshift_round64((rhs as i64) * rc_mult2 as i64, mult2_q); - a_qa[n] = next0; - a_qa[k - n - 1] = next1; - } - } - - if !(-A_LIMIT_QA24..=A_LIMIT_QA24).contains(&a_qa[0]) { - return 0; - } - let rc_q31 = -(a_qa[0] << (31 - INV_PRED_GAIN_QA)); - let rc_mult1_q30 = (1 << 30) - smmul(rc_q31, rc_q31); - inv_gain_q30 = smmul(inv_gain_q30, rc_mult1_q30) << 2; - if inv_gain_q30 < MAX_PREDICTION_POWER_GAIN_INV_Q30 { - 0 - } else { - inv_gain_q30 - } -} - -/// Multiply two signed 32-bit values and keep the high 32 bits. -/// -/// Params: signed `a` and `b`. -/// Returns: top-word signed product. -fn smmul(a: i32, b: i32) -> i32 { - (((a as i64) * (b as i64)) >> 32) as i32 -} - -/// Multiply by a Q-domain fractional value. -/// -/// Params: signed `a`, signed `b`, and fractional `q`. -/// Returns: rounded signed product in the original Q-domain. -fn mul32_frac_q(a: i32, b: i32, q: usize) -> i32 { - rshift_round64((a as i64) * (b as i64), q) -} - -/// Compute a saturating absolute value for normalization. -/// -/// Params: signed `value`. -/// Returns: non-negative magnitude, saturating `i32::MIN`. -fn abs32_for_clz(value: i32) -> i32 { - if value == i32::MIN { - i32::MAX - } else { - value.abs() - } -} - -/// Multiply by the signed low 16-bit half of `b32` and keep high word. -/// -/// Params: signed `a32` and signed `b32`. -/// Returns: high-word signed product. -fn smulwb(a32: i32, b32: i32) -> i32 { - let b16 = b32 as i16 as i32; - let high = (a32 >> 16) * b16; - let low = ((a32 & 0xFFFF) * b16) >> 16; - high.wrapping_add(low) -} - -/// Approximate `a32 + ((b32 * c32) >> 16)` with wraparound semantics. -/// -/// Params: accumulator `a32` and signed multiplicands `b32` and `c32`. -/// Returns: accumulated high-word product. -fn smlaww(a32: i32, b32: i32, c32: i32) -> i32 { - a32.wrapping_add(((b32 as i64 * c32 as i64) >> 16) as i32) -} - -/// Left-shift with saturation. -/// -/// Params: signed `value` and non-negative `shift`. -/// Returns: left-shifted value clamped to `i32`. -fn lshift_sat32(value: i32, shift: usize) -> i32 { - if shift >= 31 { - if value > 0 { - i32::MAX - } else if value < 0 { - i32::MIN - } else { - 0 - } - } else { - value - .clamp(i32::MIN >> shift, i32::MAX >> shift) - .wrapping_shl(shift as u32) - } -} - -/// Approximate `(1 << q_res) / value` in fixed-point. -/// -/// Params: signed non-zero `value` and target result Q `q_res`. -/// Returns: fixed-point inverse approximation. -fn inverse32_var_q(value: i32, q_res: usize) -> i32 { - debug_assert!(value != 0); - debug_assert!(q_res > 0); - - let b_headrm = abs32_for_clz(value).leading_zeros() as usize - 1; - let b32_nrm = value << b_headrm; - let b32_inv = (i32::MAX >> 2) / (b32_nrm >> 16); - let mut result = b32_inv << 16; - let err_q32 = ((1i32 << 29) - smulwb(b32_nrm, b32_inv)) << 3; - result = smlaww(result, err_q32, b32_inv); - - let lshift = 61isize - b_headrm as isize - q_res as isize; - if lshift <= 0 { - lshift_sat32(result, (-lshift) as usize) - } else if lshift < 32 { - result >> lshift - } else { - 0 - } -} - -/// Round a signed 32-bit arithmetic right shift. -/// -/// Params: signed `value` and positive `shift`. -/// Returns: rounded arithmetic shift result. -fn rshift_round(value: i32, shift: usize) -> i32 { - if shift == 1 { - (value >> 1) + (value & 1) - } else { - ((value >> (shift - 1)) + 1) >> 1 - } -} - -/// Round a signed 64-bit arithmetic right shift. -/// -/// Params: signed `value` and positive `shift`. -/// Returns: rounded arithmetic shift result. -fn rshift_round64(value: i64, shift: usize) -> i32 { - if shift == 1 { - ((value >> 1) + (value & 1)) as i32 - } else { - (((value >> (shift - 1)) + 1) >> 1) as i32 - } -} diff --git a/vendor/opus-decoder/src/silk/ltp.rs b/vendor/opus-decoder/src/silk/ltp.rs deleted file mode 100644 index d6a247a..0000000 --- a/vendor/opus-decoder/src/silk/ltp.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! SILK long-term prediction helpers. -#![allow(clippy::too_many_arguments)] - -use super::tables::{ - LTP_ORDER, LTP_SCALES_Q14, LTP_VQ_0_Q7, LTP_VQ_1_Q7, LTP_VQ_2_Q7, MAX_NB_SUBFR, -}; - -/// Decode LTP codebook entries to Q14 taps for each active subframe. -/// -/// Params: `per_index` codebook selector, per-subframe `ltp_indices`, active `nb_subfr`, -/// and mutable `ltp_scale_q14` output. -/// Returns: decoded per-subframe LTP taps in Q14. -pub(super) fn decode_ltp_coeffs( - per_index: i8, - ltp_indices: &[i8; MAX_NB_SUBFR], - nb_subfr: usize, - ltp_scale_index: i8, -) -> ([[i16; LTP_ORDER]; MAX_NB_SUBFR], i32) { - let mut coeffs_q14 = [[0i16; LTP_ORDER]; MAX_NB_SUBFR]; - let ltp_scale_q14 = if per_index >= 0 { - LTP_SCALES_Q14[ltp_scale_index as usize] as i32 - } else { - 0 - }; - - for k in 0..nb_subfr { - let row = codebook_row(per_index, ltp_indices[k] as usize); - for i in 0..LTP_ORDER { - coeffs_q14[k][i] = (row[i] as i16) << 7; - } - } - - (coeffs_q14, ltp_scale_q14) -} - -/// Apply the 5-tap SILK LTP filter to a subframe. -/// -/// Params: mutable `pres_q14`, Q14 `exc_q14`, Q14 `ltp_q15` history buffer, write `buf_idx`, -/// decoded `pitch_lag`, active `subfr_length`, and subframe `ltp_coefs_q14`. -/// Returns: nothing; `pres_q14` and `ltp_q15` are updated in place. -pub(super) fn ltp_filter( - pres_q14: &mut [i32], - exc_q14: &[i32], - ltp_q15: &mut [i32], - buf_idx: &mut usize, - pitch_lag: usize, - subfr_length: usize, - ltp_coefs_q14: &[i16; LTP_ORDER], - _packet_idx: usize, - _frame_idx: usize, - _subframe_idx: usize, -) { - let start = *buf_idx - pitch_lag + (LTP_ORDER / 2); - for i in 0..subfr_length { - let pred_base = start + i; - let mut ltp_pred_q13 = 2i32; - ltp_pred_q13 = smlawb(ltp_pred_q13, ltp_q15[pred_base], ltp_coefs_q14[0] as i32); - ltp_pred_q13 = smlawb( - ltp_pred_q13, - ltp_q15[pred_base - 1], - ltp_coefs_q14[1] as i32, - ); - ltp_pred_q13 = smlawb( - ltp_pred_q13, - ltp_q15[pred_base - 2], - ltp_coefs_q14[2] as i32, - ); - ltp_pred_q13 = smlawb( - ltp_pred_q13, - ltp_q15[pred_base - 3], - ltp_coefs_q14[3] as i32, - ); - ltp_pred_q13 = smlawb( - ltp_pred_q13, - ltp_q15[pred_base - 4], - ltp_coefs_q14[4] as i32, - ); - pres_q14[i] = exc_q14[i].saturating_add(ltp_pred_q13 << 1); - ltp_q15[*buf_idx] = pres_q14[i] << 1; - *buf_idx += 1; - } -} - -/// Select one LTP VQ row from the periodicity codebook. -/// -/// Params: `per_index` codebook selector and row `index`. -/// Returns: reference to the codebook row in Q7. -fn codebook_row(per_index: i8, index: usize) -> &'static [i8; LTP_ORDER] { - match per_index { - 0 => <P_VQ_0_Q7[index], - 1 => <P_VQ_1_Q7[index], - 2 => <P_VQ_2_Q7[index], - _ => panic!("unsupported LTP periodicity index {per_index}"), - } -} - -/// Approximate `a32 + ((b32 * (i16)c32) >> 16)`. -/// -/// Params: accumulator `a32`, multiplicand `b32`, and signed `c32`. -/// Returns: accumulated high-word product. -fn smlawb(a32: i32, b32: i32, c32: i32) -> i32 { - let c16 = c32 as i16 as i32; - let high = (b32 >> 16) * c16; - let low = ((b32 & 0xFFFF) * c16) >> 16; - a32.wrapping_add(high).wrapping_add(low) -} diff --git a/vendor/opus-decoder/src/silk/mod.rs b/vendor/opus-decoder/src/silk/mod.rs deleted file mode 100644 index 7db04e2..0000000 --- a/vendor/opus-decoder/src/silk/mod.rs +++ /dev/null @@ -1,1349 +0,0 @@ -//! SILK decoder scaffolding (Phase 3a). -//! -//! This module decodes SILK packet headers, side information, and synthesis -//! state while keeping the implementation close to the reference decoder. -#![allow(clippy::too_many_arguments, clippy::needless_borrow)] - -use crate::Error; -use crate::entropy::EcDec; - -mod decode_core; -mod entropy_tables; -mod gain; -mod lbrr; -mod lpc; -mod ltp; -mod nlsf; -mod pitch; -mod plc; -mod resampler; -mod resampler_private; -mod resampler_rom; -mod stereo; -mod tables; - -const MAX_INTERNAL_FRAMES: usize = 3; -const MAX_LPC_HALVES: usize = 2; -const MAX_API_FRAME_SAMPLES_48K: usize = 960 * MAX_INTERNAL_FRAMES; -const TYPE_VOICED: i32 = 2; - -/// Result of decoding one SILK Opus frame. -#[derive(Debug, Clone, Copy)] -pub(crate) struct SilkFrameDecode { - /// Number of decoded samples per output channel. - pub samples_per_channel: usize, - /// Whether a top-level SILK redundancy trailer was consumed. - pub consumed_redundancy: bool, - /// Number of trailing redundancy bytes reserved for CELT. - pub redundancy_bytes: usize, - /// Transition direction flag from the top-level redundancy trailer. - pub celt_to_silk: bool, -} - -#[derive(Debug, Clone, Copy, Default)] -struct ChannelHeader { - vad_flags: [bool; MAX_INTERNAL_FRAMES], - lbrr_flags: [bool; MAX_INTERNAL_FRAMES], -} - -#[derive(Debug, Clone, Copy, Default)] -struct ParsedHeader { - channels: [ChannelHeader; 2], -} - -/// Stateful SILK decoder shell used by top-level Opus decoder. -#[derive(Debug, Clone)] -pub(crate) struct SilkDecoder { - fs_hz: u32, - channels: u8, - final_range: u32, - lbrr_state: [lbrr::ChannelState; 2], - core_state: [decode_core::SilkChannelState; 2], - resampler_state: [Option; 2], - stereo_state: stereo::StereoState, - prev_nlsf_q15: [[i16; tables::MAX_LPC_ORDER]; 2], - pred_coef_q12: [[[[i16; tables::MAX_LPC_ORDER]; MAX_LPC_HALVES]; MAX_INTERNAL_FRAMES]; 2], - first_frame_after_reset: [bool; 2], - prev_decode_only_middle: bool, - prev_packet_channels: u8, -} - -impl SilkDecoder { - /// Create SILK decoder state. - /// - /// Params: `fs_hz` is Opus API output sample rate, `channels` is output channels. - /// Returns: initialized SILK decoder state with cleared range state. - pub fn new(fs_hz: u32, channels: u8) -> Self { - Self { - fs_hz, - channels, - final_range: 0, - lbrr_state: [lbrr::ChannelState::default(), lbrr::ChannelState::default()], - core_state: [ - decode_core::SilkChannelState::default(), - decode_core::SilkChannelState::default(), - ], - resampler_state: [None, None], - stereo_state: stereo::StereoState::default(), - prev_nlsf_q15: [[0; tables::MAX_LPC_ORDER]; 2], - pred_coef_q12: [[[[0; tables::MAX_LPC_ORDER]; MAX_LPC_HALVES]; MAX_INTERNAL_FRAMES]; 2], - first_frame_after_reset: [true; 2], - prev_decode_only_middle: false, - prev_packet_channels: 1, - } - } - - /// Reset SILK decoder persistent state. - /// - /// Params: none. - /// Returns: nothing. - pub fn reset(&mut self) { - self.final_range = 0; - self.lbrr_state = [lbrr::ChannelState::default(), lbrr::ChannelState::default()]; - self.core_state = [ - decode_core::SilkChannelState::default(), - decode_core::SilkChannelState::default(), - ]; - self.resampler_state = [None, None]; - self.stereo_state = stereo::StereoState::default(); - self.prev_nlsf_q15 = [[0; tables::MAX_LPC_ORDER]; 2]; - self.pred_coef_q12 = - [[[[0; tables::MAX_LPC_ORDER]; MAX_LPC_HALVES]; MAX_INTERNAL_FRAMES]; 2]; - self.first_frame_after_reset = [true; 2]; - self.prev_decode_only_middle = false; - self.prev_packet_channels = 1; - } - - /// Return SILK range coder final state from last decoded frame. - /// - /// Params: none. - /// Returns: `final_range` value for conformance checks. - pub fn final_range(&self) -> u32 { - self.final_range - } - - /// Update the per-channel rewhitening history after PLC synthesis. - /// - /// Params: mutable channel `state`, concealed internal `frame`, and internal `fs_khz`. - /// Returns: nothing; the history buffers are refreshed in place. - fn refresh_conceal_history( - out_buf: &mut [i16], - lag_prev: &mut i32, - pitch_lag: i32, - frame: &[i16], - fs_khz: u32, - ) { - let ltp_mem_length = fs_khz as usize * tables::LTP_MEM_LENGTH_MS; - let frame_len = frame.len(); - let mv_len = ltp_mem_length.saturating_sub(frame_len); - if mv_len > 0 { - out_buf.copy_within(frame_len..frame_len + mv_len, 0); - } - out_buf[mv_len..mv_len + frame_len].copy_from_slice(frame); - if pitch_lag > 0 { - *lag_prev = pitch_lag; - } - } - - /// Conceal one lost SILK or Hybrid packet using the stored PLC state. - /// - /// Params: target `samples_per_channel`, mutable interleaved `out`, and packet `loss_count`. - /// Returns: concealed sample count per output channel. - pub(crate) fn decode_lost( - &mut self, - samples_per_channel: usize, - out: &mut [i16], - loss_count: u32, - ) -> Result { - let packet_channels = self.prev_packet_channels.clamp(1, 2) as usize; - let channels = self.channels as usize; - let needed = samples_per_channel * channels; - if out.len() < needed { - return Err(Error::OutputTooSmall { - needed, - got: out.len(), - }); - } - - let plc0 = &self.core_state[0].plc; - let internal_frame_samples = plc0.subfr_length.saturating_mul(plc0.nb_subfr); - if internal_frame_samples == 0 || plc0.fs_khz <= 0 { - out[..needed].fill(0); - return Ok(samples_per_channel); - } - - let frame_output_samples = if self.fs_hz == 48_000 { - self.resampler_state[0] - .as_ref() - .map(|resampler| resampler.output_len(internal_frame_samples)) - .unwrap_or(0) - } else { - internal_frame_samples - }; - if frame_output_samples == 0 { - out[..needed].fill(0); - return Ok(samples_per_channel); - } - - out[..needed].fill(0); - let conceal_frames = samples_per_channel.div_ceil(frame_output_samples); - let mut mono_resampler_history = self.stereo_state.s_mid; - let mut written_total = 0usize; - - for _ in 0..conceal_frames { - let mut internal_pcm = [[0i16; tables::MAX_FRAME_LENGTH]; 2]; - for (ch, frame_pcm) in internal_pcm.iter_mut().enumerate().take(packet_channels) { - let ch_state = &mut self.core_state[ch]; - let frame_pcm = &mut frame_pcm[..internal_frame_samples]; - let (plc_state, lpc_state) = (&mut ch_state.plc, &mut ch_state.s_lpc_q14_buf[..]); - plc::plc_conceal(plc_state, lpc_state, frame_pcm, loss_count); - let fs_khz = plc_state.fs_khz as u32; - let pitch_lag = plc_state.pitch_lag; - Self::refresh_conceal_history( - &mut ch_state.out_buf, - &mut ch_state.lag_prev, - pitch_lag, - frame_pcm, - fs_khz, - ); - } - - if packet_channels == 1 { - self.stereo_state.s_mid.copy_from_slice( - &internal_pcm[0][internal_frame_samples - 2..internal_frame_samples], - ); - } - - let frame_write = - frame_output_samples.min(samples_per_channel.saturating_sub(written_total)); - if self.fs_hz == 48_000 { - let mut resampled_pcm = [[0i16; MAX_API_FRAME_SAMPLES_48K]; 2]; - for ch in 0..packet_channels { - if let Some(resampler) = self.resampler_state[ch].as_mut() { - let frame_pcm = &internal_pcm[ch][..internal_frame_samples]; - let expected = resampler.output_len(internal_frame_samples); - let written = if packet_channels == 2 { - resampler.process(frame_pcm, &mut resampled_pcm[ch][..expected]) - } else { - let mut resamp_input = vec![0i16; internal_frame_samples + 2]; - resamp_input[..2].copy_from_slice(&mono_resampler_history); - resamp_input[2..2 + internal_frame_samples].copy_from_slice(frame_pcm); - let written = resampler.process( - &resamp_input[1..1 + internal_frame_samples], - &mut resampled_pcm[ch][..expected], - ); - mono_resampler_history.copy_from_slice( - &frame_pcm[internal_frame_samples - 2..internal_frame_samples], - ); - written - }; - debug_assert_eq!(written, expected); - } - } - - for n in 0..frame_write { - let left = resampled_pcm[0][n]; - match channels { - 1 => out[written_total + n] = left, - 2 => { - let base = 2 * (written_total + n); - out[base] = left; - out[base + 1] = if packet_channels == 2 { - resampled_pcm[1][n] - } else { - left - }; - } - _ => unreachable!(), - } - } - } else { - let copy_len = internal_frame_samples.min(frame_write); - for n in 0..copy_len { - let left = internal_pcm[0][n]; - match channels { - 1 => out[written_total + n] = left, - 2 => { - let base = 2 * (written_total + n); - out[base] = left; - out[base + 1] = if packet_channels == 2 { - internal_pcm[1][n] - } else { - left - }; - } - _ => unreachable!(), - } - } - } - - written_total += frame_write; - if written_total >= samples_per_channel { - break; - } - } - - Ok(samples_per_channel) - } - - /// Decode one SILK Opus frame payload. - /// - /// Params: `frame` payload bytes, `frame_samples_48k` frame duration in 48 kHz samples, - /// `config` TOC config, `packet_channels` coded channels, `_packet_idx` debug packet index, - /// and `out` interleaved output slice. - /// Returns: decoded sample count per output channel at the API output rate. - pub fn decode_frame( - &mut self, - frame: &[u8], - frame_samples_48k: usize, - config: u8, - packet_channels: u8, - packet_idx: usize, - out: &mut [i16], - ) -> Result { - let mut dec = EcDec::new(frame); - self.decode_frame_with_ec( - frame, - &mut dec, - frame_samples_48k, - config, - packet_channels, - false, - packet_idx, - out, - ) - } - - /// Decode one SILK Opus frame payload using a shared entropy decoder. - /// - /// Params: original `frame`, shared mutable `dec`, `frame_samples_48k`, TOC `config`, - /// coded `packet_channels`, `is_hybrid` mode flag, debug `packet_idx`, and interleaved `out`. - /// Returns: decoded sample count per output channel at the API output rate. - pub(crate) fn decode_frame_with_ec( - &mut self, - frame: &[u8], - mut dec: &mut EcDec<'_>, - frame_samples_48k: usize, - config: u8, - packet_channels: u8, - is_hybrid: bool, - packet_idx: usize, - out: &mut [i16], - ) -> Result { - if !matches!(packet_channels, 1 | 2) { - return Err(Error::BadPacket); - } - - let internal_fs_hz = silk_internal_fs_hz(config)?; - let (internal_frames, nb_subfr, frame_length) = - internal_frame_shape(frame_samples_48k, internal_fs_hz)?; - let mut mono_resampler_history = self.stereo_state.s_mid; - if packet_idx == 0 && (12..=15).contains(&config) { - append_silk_debug_log( - "silk-hybrid-entry", - "H65", - &format!( - "{{\"packet_idx\":{},\"config\":{},\"internal_fs_khz\":{},\"nb_subfr\":{},\"internal_frames\":{},\"frame_length\":{},\"tell\":{},\"tell_frac\":{}}}", - packet_idx, - config, - internal_fs_hz / 1000, - nb_subfr, - internal_frames, - frame_length, - dec.tell(), - dec.tell_frac() - ), - ); - // #endregion - } - let samples_per_channel = (frame_samples_48k * self.fs_hz as usize) / 48_000; - let needed = samples_per_channel * self.channels as usize; - if out.len() < needed { - return Err(Error::OutputTooSmall { - needed, - got: out.len(), - }); - } - - let mut internal_pcm = [[0i16; MAX_INTERNAL_FRAMES * tables::MAX_FRAME_LENGTH]; 2]; - let mut resampled_pcm = [[0i16; MAX_API_FRAME_SAMPLES_48K]; 2]; - let trace_pkt0 = packet_idx == 0 && silk_trace_enabled(); - if trace_pkt0 { - trace_checkpoint(&dec, packet_idx, "init"); - } - for ch in 0..packet_channels as usize { - let prev_fs_khz = self.lbrr_state[ch].fs_khz; - let new_fs_khz = internal_fs_hz / 1000; - if prev_fs_khz != 0 && prev_fs_khz != new_fs_khz { - self.core_state[ch] = decode_core::SilkChannelState::default(); - self.first_frame_after_reset[ch] = true; - if (1116..=1120).contains(&packet_idx) { - append_silk_debug_log( - "tv12-silk-fs-reset", - "H112", - &format!( - "{{\"packet_idx\":{},\"channel\":{},\"prev_fs_khz\":{},\"new_fs_khz\":{}}}", - packet_idx, ch, prev_fs_khz, new_fs_khz - ), - ); - // #endregion - } - } - lbrr::configure_channel_state(&mut self.lbrr_state[ch], new_fs_khz, nb_subfr); - } - if self.fs_hz == 48_000 { - for ch in 0..packet_channels as usize { - let needs_reset = self.resampler_state[ch] - .as_ref() - .is_none_or(|state| !state.matches(internal_fs_hz, self.fs_hz)); - if needs_reset { - self.resampler_state[ch] = - Some(resampler::SilkResampler::new(internal_fs_hz, self.fs_hz)?); - } - } - if self.channels == 2 && packet_channels == 2 && self.prev_packet_channels == 1 { - self.stereo_state.pred_prev_q13 = [0; 2]; - self.stereo_state.s_side = [0; 2]; - if let Some(left_resampler) = self.resampler_state[0].clone() { - self.resampler_state[1] = Some(left_resampler); - } - } - } - let parsed = match parse_header( - &mut dec, - packet_channels as usize, - internal_frames, - packet_idx, - trace_pkt0, - ) { - Ok(parsed) => parsed, - Err(err) => { - if packet_idx == 0 && (12..=15).contains(&config) { - // #region agent log H66 - append_silk_debug_log( - "silk-hybrid-parse-header-error", - "H66", - &format!( - "{{\"packet_idx\":{},\"error\":\"{}\",\"tell\":{},\"tell_frac\":{}}}", - packet_idx, - err, - dec.tell(), - dec.tell_frac() - ), - ); - // #endregion - } - return Err(err); - } - }; - if packet_idx == 1118 { - let ch = &self.core_state[0]; - let tail_start = ch.out_buf.len().saturating_sub(8); - append_silk_debug_log( - "tv12-pkt1118-silk-entry", - "H110", - &format!( - "{{\"packet_idx\":1118,\"out_buf_tail\":{:?},\"s_lpc_head4\":{:?},\"lag_prev\":{},\"prev_gain_index\":{}}}", - &ch.out_buf[tail_start..], - &ch.s_lpc_q14_buf[..4], - ch.lag_prev, - ch.last_gain_index - ), - ); - // #endregion - } - if let Err(err) = consume_lbrr_payload( - &mut dec, - &parsed, - packet_channels as usize, - internal_frames, - &mut self.lbrr_state, - frame_length, - packet_idx, - trace_pkt0, - ) { - if packet_idx == 0 && (12..=15).contains(&config) { - // #region agent log H67 - append_silk_debug_log( - "silk-hybrid-lbrr-error", - "H67", - &format!( - "{{\"packet_idx\":{},\"error\":\"{}\",\"tell\":{},\"tell_frac\":{}}}", - packet_idx, - err, - dec.tell(), - dec.tell_frac() - ), - ); - // #endregion - } - return Err(err); - } - if trace_pkt0 { - trace_checkpoint(&dec, packet_idx, "after_lbrr_consume"); - } - if let Err(err) = consume_main_payload( - &mut dec, - &parsed, - packet_channels as usize, - internal_frames, - &mut self.lbrr_state, - frame_length, - &mut self.prev_nlsf_q15, - &mut self.pred_coef_q12, - &mut self.first_frame_after_reset, - &mut self.core_state, - &mut internal_pcm, - &mut self.stereo_state, - &mut self.prev_decode_only_middle, - packet_idx, - trace_pkt0, - ) { - if packet_idx == 0 && (12..=15).contains(&config) { - // #region agent log H68 - append_silk_debug_log( - "silk-hybrid-main-payload-error", - "H68", - &format!( - "{{\"packet_idx\":{},\"error\":\"{}\",\"tell\":{},\"tell_frac\":{}}}", - packet_idx, - err, - dec.tell(), - dec.tell_frac() - ), - ); - // #endregion - } - return Err(err); - } - let mut consumed_redundancy = false; - let mut redundancy_bytes = 0usize; - let mut celt_to_silk = false; - if !is_hybrid && dec.tell() + 17 <= (8 * frame.len() as i32) { - celt_to_silk = dec.dec_bit_logp(1); - redundancy_bytes = frame - .len() - .saturating_sub(((dec.tell() + 7).max(0) as usize) >> 3); - consumed_redundancy = redundancy_bytes > 0; - } - if trace_pkt0 { - trace_checkpoint(&dec, packet_idx, "after_main_decode"); - } - if packet_idx == 1117 { - let ch = &self.core_state[0]; - let tail_start = ch.out_buf.len().saturating_sub(8); - append_silk_debug_log( - "tv12-post-pkt1117-silk-state", - "H111", - &format!( - "{{\"packet_idx\":1117,\"out_buf_tail\":{:?},\"s_lpc_head4\":{:?},\"lag_prev\":{},\"prev_gain_index\":{}}}", - &ch.out_buf[tail_start..], - &ch.s_lpc_q14_buf[..4], - ch.lag_prev, - ch.last_gain_index - ), - ); - // #endregion - } - out[..needed].fill(0); - let internal_samples_per_channel = internal_frames * frame_length; - if self.fs_hz == 48_000 { - for ch in 0..packet_channels as usize { - if let Some(resampler) = self.resampler_state[ch].as_mut() { - let mut written_total = 0usize; - for iframe_idx in 0..internal_frames { - let pcm_range = iframe_idx * frame_length..(iframe_idx + 1) * frame_length; - let frame_pcm = &internal_pcm[ch][pcm_range]; - let expected = resampler.output_len(frame_length); - let written = if packet_channels == 2 { - resampler.process( - frame_pcm, - &mut resampled_pcm[ch][written_total..written_total + expected], - ) - } else { - let mut resamp_input = vec![0i16; frame_length + 2]; - resamp_input[..2].copy_from_slice(&mono_resampler_history); - resamp_input[2..2 + frame_length].copy_from_slice(frame_pcm); - let written = resampler.process( - &resamp_input[1..1 + frame_length], - &mut resampled_pcm[ch][written_total..written_total + expected], - ); - mono_resampler_history - .copy_from_slice(&frame_pcm[frame_length - 2..frame_length]); - written - }; - debug_assert_eq!(written, expected); - written_total += written; - } - debug_assert_eq!(written_total, samples_per_channel); - } - } - } - if self.fs_hz == 48_000 { - for n in 0..samples_per_channel { - let left = resampled_pcm[0][n]; - match self.channels { - 1 => out[n] = left, - 2 => { - out[2 * n] = left; - out[2 * n + 1] = if packet_channels == 2 { - resampled_pcm[1][n] - } else { - left - }; - } - _ => unreachable!(), - } - } - } else { - for n in 0..internal_samples_per_channel.min(samples_per_channel) { - let left = internal_pcm[0][n]; - match self.channels { - 1 => out[n] = left, - 2 => { - out[2 * n] = left; - out[2 * n + 1] = if packet_channels == 2 { - internal_pcm[1][n] - } else { - left - }; - } - _ => unreachable!(), - } - } - } - - self.prev_packet_channels = packet_channels; - self.final_range = dec.final_range(); - if trace_pkt0 { - trace_checkpoint(&dec, packet_idx, "after_decode"); - } - if dec.is_error() { - return Err(Error::BadPacket); - } - - if lbrr::any_lbrr( - &[parsed.channels[0].lbrr_flags, parsed.channels[1].lbrr_flags], - packet_channels as usize, - ) { - debug_trace!("silk: consumed LBRR side payload"); - } - - Ok(SilkFrameDecode { - samples_per_channel, - consumed_redundancy, - redundancy_bytes, - celt_to_silk, - }) - } -} - -/// Parse SILK packet header signaling used before core frame decode. -/// -/// Params: `dec` shared entropy decoder, `packet_channels` coded channels, and -/// `internal_frames` count of 20 ms SILK-internal frames (1/2/3). -/// Returns: parsed VAD/LBRR signaling snapshot for the frame. -fn parse_header( - dec: &mut EcDec<'_>, - packet_channels: usize, - internal_frames: usize, - packet_idx: usize, - trace_pkt0: bool, -) -> Result { - let mut parsed = ParsedHeader::default(); - - for ch in 0..packet_channels { - if packet_idx == 0 { - append_silk_debug_log( - "silk-header-before-vad", - "H69", - &format!( - "{{\"packet_idx\":{},\"channel\":{},\"tell\":{},\"tell_frac\":{},\"internal_frames\":{}}}", - packet_idx, - ch, - dec.tell(), - dec.tell_frac(), - internal_frames - ), - ); - // #endregion - } - for i in 0..internal_frames { - parsed.channels[ch].vad_flags[i] = dec.dec_bit_logp(1); - } - if packet_idx == 0 { - append_silk_debug_log( - "silk-header-after-vad", - "H70", - &format!( - "{{\"packet_idx\":{},\"channel\":{},\"tell\":{},\"tell_frac\":{},\"vad_flags\":\"{:?}\"}}", - packet_idx, - ch, - dec.tell(), - dec.tell_frac(), - &parsed.channels[ch].vad_flags[..internal_frames] - ), - ); - // #endregion - } - if trace_pkt0 { - trace_checkpoint(dec, packet_idx, &format!("after_vad_flags_ch{}", ch)); - } - - let has_lbrr = dec.dec_bit_logp(1); - parsed.channels[ch].lbrr_flags = unpack_lbrr_flags(dec, has_lbrr, internal_frames)?; - if packet_idx == 0 { - append_silk_debug_log( - "silk-header-after-lbrr", - "H71", - &format!( - "{{\"packet_idx\":{},\"channel\":{},\"tell\":{},\"tell_frac\":{},\"has_lbrr\":{},\"lbrr_flags\":\"{:?}\"}}", - packet_idx, - ch, - dec.tell(), - dec.tell_frac(), - has_lbrr, - &parsed.channels[ch].lbrr_flags[..internal_frames] - ), - ); - // #endregion - } - if trace_pkt0 { - trace_checkpoint(dec, packet_idx, &format!("after_lbrr_flag_ch{}", ch)); - } - } - - Ok(parsed) -} - -/// Consume LBRR payload side information for all flagged internal frames. -/// -/// Params: entropy decoder `dec`, parsed `header`, coded channel count, -/// `internal_frames`, mutable per-channel `states`, and internal `frame_length`. -/// Returns: nothing on success, `BadPacket` on malformed entropy payload. -fn consume_lbrr_payload( - dec: &mut EcDec<'_>, - header: &ParsedHeader, - packet_channels: usize, - internal_frames: usize, - states: &mut [lbrr::ChannelState; 2], - frame_length: usize, - packet_idx: usize, - trace_pkt0: bool, -) -> Result<(), Error> { - for i in 0..internal_frames { - for (ch, state) in states.iter_mut().enumerate().take(packet_channels) { - if !header.channels[ch].lbrr_flags[i] { - continue; - } - if packet_channels == 2 && ch == 0 { - let _ = stereo::decode_stereo_pred(dec); - if !header.channels[1].lbrr_flags[i] { - let _ = stereo::decode_mid_only(dec); - } - } - let cond = if i > 0 && header.channels[ch].lbrr_flags[i - 1] { - lbrr::CondCoding::Conditionally - } else { - lbrr::CondCoding::Independently - }; - let side = - lbrr::decode_indices(state, dec, header.channels[ch].vad_flags[i], true, cond)?; - let _ = - lbrr::decode_pulses(dec, side.signal_type, side.quant_offset_type, frame_length)?; - if trace_pkt0 { - trace_checkpoint(dec, packet_idx, &format!("after_lbrr_i{}_ch{}", i, ch)); - } - } - } - Ok(()) -} - -/// Consume main SILK frame side information and pulses for all internal frames. -/// -/// Params: entropy decoder `dec`, parsed packet `header`, coded channel count, -/// `internal_frames`, mutable per-channel `states`, internal `frame_length`, -/// packet-level `prev_decode_only_middle`, `packet_idx`, and tracing flag. -/// Returns: nothing on success, `BadPacket` on malformed entropy payload. -fn consume_main_payload( - dec: &mut EcDec<'_>, - header: &ParsedHeader, - packet_channels: usize, - internal_frames: usize, - states: &mut [lbrr::ChannelState; 2], - frame_length: usize, - prev_nlsf_q15: &mut [[i16; tables::MAX_LPC_ORDER]; 2], - pred_coef_q12: &mut [[[[i16; tables::MAX_LPC_ORDER]; MAX_LPC_HALVES]; MAX_INTERNAL_FRAMES]; 2], - first_frame_after_reset: &mut [bool; 2], - core_state: &mut [decode_core::SilkChannelState; 2], - internal_pcm: &mut [[i16; MAX_INTERNAL_FRAMES * tables::MAX_FRAME_LENGTH]; 2], - stereo_state: &mut stereo::StereoState, - prev_decode_only_middle: &mut bool, - packet_idx: usize, - trace_pkt0: bool, -) -> Result<(), Error> { - let mut signal_types = [[-1i32; MAX_INTERNAL_FRAMES]; 2]; - let mut mid_only_flags = [false; MAX_INTERNAL_FRAMES]; - for i in 0..internal_frames { - let mut decode_only_middle = false; - let mut pred_q13 = [0i32; 2]; - let mut decoded_frames: [Option; 2] = [None, None]; - let mut plc_lpc_q12 = [[0i32; tables::MAX_LPC_ORDER]; 2]; - let mut plc_nb_coefs = [0usize; 2]; - if packet_channels == 2 { - pred_q13 = stereo::decode_stereo_pred(dec); - decode_only_middle = !header.channels[1].vad_flags[i] && stereo::decode_mid_only(dec); - mid_only_flags[i] = decode_only_middle; - if trace_pkt0 { - trace_checkpoint(dec, packet_idx, &format!("after_stereo_i{}", i)); - } - debug_trace!( - "silk: stereo signaling i={} pred0={} pred1={} mid_only={}", - i, - pred_q13[0], - pred_q13[1], - decode_only_middle - ); - } - - for ch in 0..packet_channels { - if packet_idx == 0 && i == 0 && ch == 0 { - append_silk_debug_log( - "silk-main-after-header", - "H72", - &format!( - "{{\"packet_idx\":{},\"iframe\":{},\"channel\":{},\"tell\":{},\"tell_frac\":{},\"rng\":{}}}", - packet_idx, - i, - ch, - dec.tell(), - dec.tell_frac(), - dec.rng() - ), - ); - // #endregion - } - if ch == 1 && decode_only_middle { - continue; - } - let cond = if i == 0 { - if ch > 0 && *prev_decode_only_middle { - lbrr::CondCoding::IndependentlyNoLtpScaling - } else { - lbrr::CondCoding::Independently - } - } else if ch > 0 && *prev_decode_only_middle { - lbrr::CondCoding::IndependentlyNoLtpScaling - } else { - lbrr::CondCoding::Conditionally - }; - let side = lbrr::decode_indices( - &mut states[ch], - dec, - header.channels[ch].vad_flags[i], - false, - cond, - )?; - if packet_idx == 0 && i == 0 && ch == 0 { - append_silk_debug_log( - "silk-main-after-indices", - "H73", - &format!( - "{{\"packet_idx\":{},\"iframe\":{},\"channel\":{},\"tell\":{},\"tell_frac\":{},\"rng\":{},\"signal_type\":{},\"quant_offset_type\":{}}}", - packet_idx, - i, - ch, - dec.tell(), - dec.tell_frac(), - dec.rng(), - side.signal_type, - side.quant_offset_type - ), - ); - // #endregion - } - update_lpc_from_nlsf( - states[ch].fs_khz, - &side.nlsf, - &mut prev_nlsf_q15[ch], - &mut pred_coef_q12[ch][i], - &mut first_frame_after_reset[ch], - packet_idx, - i, - ch, - ); - if trace_pkt0 && packet_idx == 0 && i == 0 && ch == 0 { - trace_lpc( - packet_idx, - ch, - i, - "pred0", - &pred_coef_q12[ch][i][0], - states[ch].fs_khz, - ); - trace_lpc( - packet_idx, - ch, - i, - "pred1", - &pred_coef_q12[ch][i][1], - states[ch].fs_khz, - ); - } - let pulses = - lbrr::decode_pulses(dec, side.signal_type, side.quant_offset_type, frame_length)?; - if packet_idx == 0 && i == 0 && ch == 0 { - append_silk_debug_log( - "silk-main-after-pulses", - "H74", - &format!( - "{{\"packet_idx\":{},\"iframe\":{},\"channel\":{},\"tell\":{},\"tell_frac\":{},\"rng\":{}}}", - packet_idx, - i, - ch, - dec.tell(), - dec.tell_frac(), - dec.rng() - ), - ); - // #endregion - } - let frame_params = decode_core::SilkFrameParams { - signal_type: side.signal_type, - quant_offset_type: side.quant_offset_type, - gain_indices: side.gain_indices, - lag_index: side.lag_index, - contour_index: side.contour_index, - per_index: side.per_index, - ltp_indices: side.ltp_indices, - ltp_scale_index: side.ltp_scale_index, - seed: side.seed, - pulses, - conditional: matches!(cond, lbrr::CondCoding::Conditionally), - frame_length, - nb_subfr: states[ch].nb_subfr, - fs_khz: states[ch].fs_khz, - lpc_order: tables::nlsf_codebook(states[ch].fs_khz).order, - }; - signal_types[ch][i] = frame_params.signal_type as i32; - let pcm_range = i * frame_length..(i + 1) * frame_length; - let trace = decode_core::decode_core( - &frame_params, - &pred_coef_q12[ch][i], - &mut internal_pcm[ch][pcm_range], - &mut core_state[ch], - packet_idx, - i, - ); - let mut ltp_gains = [0i32; tables::MAX_NB_SUBFR]; - let lag = if frame_params.signal_type == TYPE_VOICED { - let pitch_lags = pitch::decode_pitch_lags( - frame_params.lag_index as i32, - frame_params.contour_index as i32, - frame_params.fs_khz as i32, - frame_params.nb_subfr, - ); - let (ltp_coeffs_q14, _) = ltp::decode_ltp_coeffs( - frame_params.per_index, - &frame_params.ltp_indices, - frame_params.nb_subfr, - frame_params.ltp_scale_index, - ); - for (subframe_idx, gain) in - ltp_gains.iter_mut().enumerate().take(frame_params.nb_subfr) - { - let summed_q14 = ltp_coeffs_q14[subframe_idx] - .iter() - .map(|coef| i32::from(*coef)) - .sum::() - .max(0); - *gain = summed_q14.clamp(0, 1 << 16); - } - pitch_lags[frame_params.nb_subfr - 1] - } else { - 0 - }; - for (dst, src) in plc_lpc_q12[ch] - .iter_mut() - .zip(pred_coef_q12[ch][i][1].iter()) - .take(frame_params.lpc_order) - { - *dst = i32::from(*src); - } - plc_nb_coefs[ch] = frame_params.lpc_order; - decoded_frames[ch] = Some(plc::SilkDecodedFrame { - lag, - ltp_gains, - output: Vec::new(), - }); - if trace_pkt0 && packet_idx == 0 && i == 0 && ch == 0 { - trace_core(packet_idx, "exc_Q14", &trace.exc_q14_head); - trace_core(packet_idx, "sLPC_Q14", &trace.s_lpc_q14_head); - } - if trace_pkt0 { - trace_checkpoint(dec, packet_idx, &format!("after_main_i{}_ch{}", i, ch)); - } - } - - if packet_channels == 2 { - let pcm_range = i * frame_length..(i + 1) * frame_length; - let (left_channels, right_channels) = internal_pcm.split_at_mut(1); - stereo::ms_to_lr( - stereo_state, - &mut left_channels[0][pcm_range.clone()], - &mut right_channels[0][pcm_range.clone()], - pred_q13, - states[0].fs_khz, - packet_idx, - ); - } else { - let pcm_range = i * frame_length..(i + 1) * frame_length; - let frame = &internal_pcm[0][pcm_range.clone()]; - stereo_state - .s_mid - .copy_from_slice(&frame[frame_length - 2..frame_length]); - } - - let pcm_range = i * frame_length..(i + 1) * frame_length; - for ch in 0..packet_channels { - let Some(mut decoded_frame) = decoded_frames[ch].take() else { - continue; - }; - decoded_frame - .output - .extend_from_slice(&internal_pcm[ch][pcm_range.clone()]); - plc::plc_update( - &mut core_state[ch].plc, - &decoded_frame, - &plc_lpc_q12[ch][..plc_nb_coefs[ch]], - states[ch].nb_subfr, - frame_length / states[ch].nb_subfr, - states[ch].fs_khz as i32, - plc_nb_coefs[ch], - ); - } - - *prev_decode_only_middle = decode_only_middle; - } - if packet_idx == 1118 { - let internal_samples_per_channel = internal_frames * frame_length; - let head_end = 8.min(internal_samples_per_channel); - let tail_start = internal_samples_per_channel.saturating_sub(8); - append_silk_debug_log( - "tv12-pkt1118-internal-pcm", - "H109A", - &format!( - "{{\"packet_idx\":1118,\"internal_fs_khz\":{},\"signal_type\":{},\"packet_channels\":{},\"internal_frames\":{},\"frame_length\":{},\"head\":{:?},\"tail\":{:?}}}", - states[0].fs_khz, - signal_types[0][0], - packet_channels, - internal_frames, - frame_length, - &internal_pcm[0][..head_end], - &internal_pcm[0][tail_start..internal_samples_per_channel] - ), - ); - } - Ok(()) -} - -/// Check whether SILK entropy tracing is enabled. -/// -/// Params: none. -/// Returns: `false`; runtime tracing is disabled in cleanup builds. -fn silk_trace_enabled() -> bool { - false -} - -/// Append one NDJSON debug line for SILK hybrid tracing. -/// -/// Params: event `message`, `hypothesis_id`, and raw JSON `data`. -/// Returns: nothing; runtime debug logging is disabled in cleanup builds. -fn append_silk_debug_log(message: &str, hypothesis_id: &str, data: &str) { - let _ = (message, hypothesis_id, data); -} - -/// Emit packet-0 SILK entropy checkpoint with tell/tell_frac/rng. -/// -/// Params: immutable entropy decoder `dec`, `packet_idx`, and textual `label`. -/// Returns: nothing. -fn trace_checkpoint(dec: &EcDec<'_>, packet_idx: usize, label: &str) { - let _ = (dec, packet_idx, label); -} - -/// Decode, optionally interpolate, and convert one NLSF vector to LPC halves. -/// -/// Params: internal `fs_khz`, decoded `nlsf_indices`, mutable `prev_nlsf_q15`, -/// mutable `pred_coef_q12`, and per-channel `first_frame_after_reset`. -/// Returns: nothing; previous NLSF and both LPC halves are updated in place. -fn update_lpc_from_nlsf( - fs_khz: u32, - nlsf_indices: &lbrr::DecodedNlsfIndices, - prev_nlsf_q15: &mut [i16; tables::MAX_LPC_ORDER], - pred_coef_q12: &mut [[i16; tables::MAX_LPC_ORDER]; MAX_LPC_HALVES], - first_frame_after_reset: &mut bool, - _packet_idx: usize, - _frame_idx: usize, - _channel_idx: usize, -) { - let codebook = tables::nlsf_codebook(fs_khz); - let order = codebook.order; - let mut curr_nlsf_q15 = [0i16; tables::MAX_LPC_ORDER]; - nlsf::nlsf_decode( - &mut curr_nlsf_q15[..order], - &nlsf_indices.values[..order + 1], - codebook, - ); - lpc::nlsf2a( - &mut pred_coef_q12[1][..order], - &curr_nlsf_q15[..order], - order, - ); - - let interp_coef_q2 = if *first_frame_after_reset { - 4 - } else { - nlsf_indices.interp_coef_q2 - }; - if interp_coef_q2 < 4 { - let mut interp_nlsf_q15 = [0i16; tables::MAX_LPC_ORDER]; - for i in 0..order { - let delta = curr_nlsf_q15[i] as i32 - prev_nlsf_q15[i] as i32; - interp_nlsf_q15[i] = - (prev_nlsf_q15[i] as i32 + ((interp_coef_q2 as i32 * delta) >> 2)) as i16; - } - lpc::nlsf2a( - &mut pred_coef_q12[0][..order], - &interp_nlsf_q15[..order], - order, - ); - } else { - let pred1 = pred_coef_q12[1]; - pred_coef_q12[0][..order].copy_from_slice(&pred1[..order]); - } - - prev_nlsf_q15[..order].copy_from_slice(&curr_nlsf_q15[..order]); - *first_frame_after_reset = false; -} - -/// Emit packet-0 LPC trace for one channel/frame half. -/// -/// Params: `packet_idx`, coded `channel`, internal frame `frame_idx`, `label`, -/// LPC slice `lpc_q12`, and internal `fs_khz`. -/// Returns: nothing. -fn trace_lpc( - packet_idx: usize, - channel: usize, - frame_idx: usize, - label: &str, - lpc_q12: &[i16], - fs_khz: u32, -) { - let _ = (packet_idx, channel, frame_idx, label, lpc_q12, fs_khz); -} - -/// Emit packet-0 SILK core trace for excitation or synthesis state. -/// -/// Params: `packet_idx`, textual `label`, and signed sample `values`. -/// Returns: nothing. -fn trace_core(packet_idx: usize, label: &str, values: &[i32; 8]) { - let _ = (packet_idx, label, values); -} - -/// Decode and expand LBRR per-internal-frame flags for one coded channel. -/// -/// Params: `dec` entropy decoder, `has_lbrr` packet-level LBRR present bit, and -/// `internal_frames` number of SILK-internal frames. -/// Returns: fixed-size LBRR flag array with active entries in `[0..internal_frames)`. -fn unpack_lbrr_flags( - dec: &mut EcDec<'_>, - has_lbrr: bool, - internal_frames: usize, -) -> Result<[bool; MAX_INTERNAL_FRAMES], Error> { - if !has_lbrr { - return Ok([false; MAX_INTERNAL_FRAMES]); - } - - if internal_frames == 1 { - return Ok([true, false, false]); - } - - let icdf = tables::lbrr_flags_icdf(internal_frames).ok_or(Error::BadPacket)?; - let symbol = dec.dec_icdf(icdf, 8) as u32 + 1; - Ok(unpack_lbrr_symbol(symbol, internal_frames)) -} - -/// Expand packed LBRR symbol bits to boolean frame flags. -/// -/// Params: `symbol` packed bit mask and `internal_frames` active frame count. -/// Returns: fixed-size LBRR flag array with decoded bits in low indices. -fn unpack_lbrr_symbol(symbol: u32, internal_frames: usize) -> [bool; MAX_INTERNAL_FRAMES] { - let mut out = [false; MAX_INTERNAL_FRAMES]; - for (i, flag) in out.iter_mut().take(internal_frames).enumerate() { - *flag = ((symbol >> i) & 1) != 0; - } - out -} - -/// Map Opus TOC config (SILK-only) to SILK internal decode sample rate. -/// -/// Params: `config` is TOC config value `(toc >> 3) & 0x1f`. -/// Returns: internal SILK sample rate in Hz (8000/12000/16000). -fn silk_internal_fs_hz(config: u8) -> Result { - match config >> 2 { - 0 => Ok(8_000), - 1 => Ok(12_000), - 2 | 3 => Ok(16_000), - _ => Err(Error::BadPacket), - } -} - -/// Derive SILK internal frame shape from Opus frame duration. -/// -/// Params: `frame_samples_48k` Opus frame size at 48 kHz and `internal_fs_hz`. -/// Returns: tuple `(internal_frames, nb_subfr, frame_length_samples)`. -fn internal_frame_shape( - frame_samples_48k: usize, - internal_fs_hz: u32, -) -> Result<(usize, usize, usize), Error> { - let fs_khz = (internal_fs_hz / 1000) as usize; - match frame_samples_48k { - 480 => Ok((1, 2, 10 * fs_khz)), - 960 => Ok((1, 4, 20 * fs_khz)), - 1_920 => Ok((2, 4, 20 * fs_khz)), - 2_880 => Ok((3, 4, 20 * fs_khz)), - _ => Err(Error::BadPacket), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::Decoder; - use std::fs; - use std::io::Read; - use std::path::{Path, PathBuf}; - - const TV02_PKT0_LPC_Q12: [i16; 10] = [5041, -1246, 1017, -614, 431, -560, 131, -357, 284, -46]; - - #[test] - fn maps_internal_frame_counts() { - assert_eq!(internal_frame_shape(480, 8_000).unwrap(), (1, 2, 80)); - assert_eq!(internal_frame_shape(960, 8_000).unwrap(), (1, 4, 160)); - assert_eq!(internal_frame_shape(1_920, 12_000).unwrap(), (2, 4, 240)); - assert_eq!(internal_frame_shape(2_880, 16_000).unwrap(), (3, 4, 320)); - assert!(internal_frame_shape(2_400, 12_000).is_err()); - } - - #[test] - fn unpacks_lbrr_bitfield() { - assert_eq!(unpack_lbrr_symbol(0b001, 3), [true, false, false]); - assert_eq!(unpack_lbrr_symbol(0b011, 3), [true, true, false]); - assert_eq!(unpack_lbrr_symbol(0b101, 3), [true, false, true]); - } - - #[ignore = "requires OPUS_TESTVECTORS_DIR"] - #[test] - fn testvector02_pkt0_nlsf_to_lpc_matches_cref() { - let packet = read_first_opus_demo_packet(&vectors_dir().join("testvector02.bit")); - let mut decoder = Decoder::new(48_000, 2).unwrap(); - let mut out = vec![0i16; decoder.max_frame_size_per_channel() * 2]; - let samples = decoder.decode(Some(&packet), &mut out).unwrap(); - assert_eq!(samples, 2880); - assert_eq!(decoder.final_range(), 0x5037_3c71); - assert_eq!( - &decoder.silk.pred_coef_q12[0][0][0][..10], - &TV02_PKT0_LPC_Q12 - ); - assert_eq!( - &decoder.silk.pred_coef_q12[0][0][1][..10], - &TV02_PKT0_LPC_Q12 - ); - } - - #[ignore = "requires OPUS_TESTVECTORS_DIR"] - #[test] - fn testvector02_final_range_matches_reference_for_all_packets() { - let packets = read_opus_demo_stream(&vectors_dir().join("testvector02.bit")); - let mut decoder = Decoder::new(48_000, 2).unwrap(); - let mut out = vec![0i16; decoder.max_frame_size_per_channel() * 2]; - for (packet_idx, packet) in packets.iter().enumerate() { - let _samples = decoder.decode(Some(&packet.payload), &mut out).unwrap(); - assert_eq!( - decoder.final_range(), - packet.expected_final_range, - "packet {packet_idx} final_range" - ); - } - } - - /// Resolve the local RFC vector directory used by decoder tests. - /// - /// Params: none. - /// Returns: absolute path to `testdata/opus_testvectors`. - fn vectors_dir() -> PathBuf { - if let Ok(path) = std::env::var("OPUS_TESTVECTORS_DIR") { - return PathBuf::from(path); - } - PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../../testdata/opus_testvectors") - .canonicalize() - .unwrap() - } - - /// Read the first packet payload from an `opus_demo` `.bit` stream. - /// - /// Params: absolute `path` to the vector stream file. - /// Returns: first packet payload bytes. - fn read_first_opus_demo_packet(path: &Path) -> Vec { - let mut file = fs::File::open(path).unwrap(); - let mut bytes = Vec::new(); - file.read_to_end(&mut bytes).unwrap(); - let packet_len = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize; - bytes[8..8 + packet_len].to_vec() - } - - /// Read an `opus_demo` `.bit` stream into packet payloads plus expected ranges. - /// - /// Params: absolute `path` to the `.bit` vector file. - /// Returns: ordered packet list with payloads and reference `final_range`. - fn read_opus_demo_stream(path: &Path) -> Vec { - let mut file = fs::File::open(path).unwrap(); - let mut bytes = Vec::new(); - file.read_to_end(&mut bytes).unwrap(); - let mut packets = Vec::new(); - let mut index = 0usize; - while index < bytes.len() { - let packet_len = u32::from_be_bytes([ - bytes[index], - bytes[index + 1], - bytes[index + 2], - bytes[index + 3], - ]) as usize; - index += 4; - let expected_final_range = u32::from_be_bytes([ - bytes[index], - bytes[index + 1], - bytes[index + 2], - bytes[index + 3], - ]); - index += 4; - let payload = bytes[index..index + packet_len].to_vec(); - index += packet_len; - packets.push(DemoPacket { - payload, - expected_final_range, - }); - } - packets - } - - /// One `opus_demo` packet plus its reference range coder state. - #[derive(Debug, Clone)] - struct DemoPacket { - /// Encoded Opus payload bytes. - payload: Vec, - /// Reference decoder final range after decoding the packet. - expected_final_range: u32, - } -} diff --git a/vendor/opus-decoder/src/silk/nlsf.rs b/vendor/opus-decoder/src/silk/nlsf.rs deleted file mode 100644 index fdec9c3..0000000 --- a/vendor/opus-decoder/src/silk/nlsf.rs +++ /dev/null @@ -1,182 +0,0 @@ -//! SILK NLSF reconstruction helpers. - -use super::tables::{self, MAX_LPC_ORDER, NlsfCodebook}; - -const MAX_LOOPS: usize = 20; -const NLSF_QUANT_LEVEL_ADJ_Q10: i32 = 102; - -/// Two-stage VQ decode: stage1 codebook plus predictive residuals. -/// -/// Params: `nlsf_q15` output slice, `nlsf_indices` packed stage1+stage2 indices, -/// and `codebook` selecting NB/MB or WB tables. -/// Returns: nothing; `nlsf_q15[..order]` is filled with stabilized Q15 NLSFs. -pub(super) fn nlsf_decode(nlsf_q15: &mut [i16], nlsf_indices: &[i8], codebook: &NlsfCodebook) { - let order = codebook.order; - debug_assert!(nlsf_q15.len() >= order); - debug_assert!(nlsf_indices.len() > order); - - let cb1_index = nlsf_indices[0] as usize; - let mut pred_q8 = [0u8; MAX_LPC_ORDER]; - let mut res_q10 = [0i16; MAX_LPC_ORDER]; - unpack_predictors(&mut pred_q8, codebook, cb1_index); - residual_dequant( - &mut res_q10, - &nlsf_indices[1..=order], - &pred_q8, - codebook.quant_step_size_q16, - order, - ); - - let cb_base = cb1_index * order; - for i in 0..order { - let weighted_q15 = ((res_q10[i] as i32) << 14) / codebook.cb1_wght_q9[cb_base + i] as i32; - let centroid_q15 = (codebook.cb1_nlsf_q8[cb_base + i] as i32) << 7; - nlsf_q15[i] = (weighted_q15 + centroid_q15).clamp(0, 32_767) as i16; - } - - nlsf_stabilize(nlsf_q15, order); -} - -/// Stabilize NLSFs with the canonical SILK minimum-delta constraints. -/// -/// Params: mutable `nlsf_q15` vector and LPC `order` (10 or 16). -/// Returns: nothing; the slice is modified in place. -pub(super) fn nlsf_stabilize(nlsf_q15: &mut [i16], order: usize) { - let delta_min_q15 = delta_min_for_order(order); - debug_assert!(nlsf_q15.len() >= order); - - for _ in 0..MAX_LOOPS { - let mut min_diff_q15 = nlsf_q15[0] as i32 - delta_min_q15[0] as i32; - let mut split = 0usize; - - for i in 1..order { - let diff_q15 = nlsf_q15[i] as i32 - (nlsf_q15[i - 1] as i32 + delta_min_q15[i] as i32); - if diff_q15 < min_diff_q15 { - min_diff_q15 = diff_q15; - split = i; - } - } - - let tail_diff_q15 = (1 << 15) - (nlsf_q15[order - 1] as i32 + delta_min_q15[order] as i32); - if tail_diff_q15 < min_diff_q15 { - min_diff_q15 = tail_diff_q15; - split = order; - } - - if min_diff_q15 >= 0 { - return; - } - - if split == 0 { - nlsf_q15[0] = delta_min_q15[0]; - } else if split == order { - nlsf_q15[order - 1] = ((1 << 15) - delta_min_q15[order] as i32) as i16; - } else { - let mut min_center_q15 = 0i32; - for &delta in &delta_min_q15[..split] { - min_center_q15 += delta as i32; - } - min_center_q15 += (delta_min_q15[split] as i32) >> 1; - - let mut max_center_q15 = 1 << 15; - for &delta in &delta_min_q15[split + 1..=order] { - max_center_q15 -= delta as i32; - } - max_center_q15 -= (delta_min_q15[split] as i32) >> 1; - - let center_q15 = limit32( - rshift_round(nlsf_q15[split - 1] as i32 + nlsf_q15[split] as i32, 1), - min_center_q15, - max_center_q15, - ); - nlsf_q15[split - 1] = (center_q15 - ((delta_min_q15[split] as i32) >> 1)) as i16; - nlsf_q15[split] = (nlsf_q15[split - 1] as i32 + delta_min_q15[split] as i32) as i16; - } - } - - nlsf_q15[..order].sort_unstable(); - nlsf_q15[0] = nlsf_q15[0].max(delta_min_q15[0]); - for i in 1..order { - let min_allowed = nlsf_q15[i - 1].saturating_add(delta_min_q15[i]); - nlsf_q15[i] = nlsf_q15[i].max(min_allowed); - } - let max_last = ((1 << 15) - delta_min_q15[order] as i32) as i16; - nlsf_q15[order - 1] = nlsf_q15[order - 1].min(max_last); - for i in (0..order - 1).rev() { - let max_allowed = nlsf_q15[i + 1] - delta_min_q15[i + 1]; - nlsf_q15[i] = nlsf_q15[i].min(max_allowed); - } -} - -/// Unpack predictor choices from the packed `ec_sel` map. -/// -/// Params: mutable `pred_q8`, source `codebook`, and stage-1 `cb1_index`. -/// Returns: nothing; predictor coefficients are written into `pred_q8[..order]`. -fn unpack_predictors(pred_q8: &mut [u8; MAX_LPC_ORDER], codebook: &NlsfCodebook, cb1_index: usize) { - let order = codebook.order; - let base = cb1_index * order / 2; - let entries = &codebook.ec_sel[base..base + order / 2]; - for i in (0..order).step_by(2) { - let entry = entries[i / 2]; - pred_q8[i] = codebook.pred_q8[i + ((entry & 1) as usize) * (order - 1)]; - pred_q8[i + 1] = codebook.pred_q8[i + (((entry >> 4) & 1) as usize) * (order - 1) + 1]; - } -} - -/// Reconstruct predictive stage-2 residuals in Q10. -/// -/// Params: mutable `res_q10`, signed stage-2 `indices`, predictor `pred_q8`, -/// residual step `quant_step_size_q16`, and LPC `order`. -/// Returns: nothing; `res_q10[..order]` is filled in reverse predictive order. -fn residual_dequant( - res_q10: &mut [i16; MAX_LPC_ORDER], - indices: &[i8], - pred_q8: &[u8; MAX_LPC_ORDER], - quant_step_size_q16: i32, - order: usize, -) { - let mut out_q10 = 0i32; - for i in (0..order).rev() { - let pred_q10 = (out_q10 * pred_q8[i] as i32) >> 8; - out_q10 = (indices[i] as i32) << 10; - if out_q10 > 0 { - out_q10 -= NLSF_QUANT_LEVEL_ADJ_Q10; - } else if out_q10 < 0 { - out_q10 += NLSF_QUANT_LEVEL_ADJ_Q10; - } - out_q10 = pred_q10 + (((out_q10 as i64) * quant_step_size_q16 as i64) >> 16) as i32; - res_q10[i] = out_q10 as i16; - } -} - -/// Return the canonical `deltaMin_Q15` table for a given LPC order. -/// -/// Params: `order` LPC order. -/// Returns: matching delta-min table slice. -fn delta_min_for_order(order: usize) -> &'static [i16] { - match order { - 10 => tables::NLSF_CB_NB_MB.delta_min_q15, - 16 => tables::NLSF_CB_WB.delta_min_q15, - _ => panic!("unsupported LPC order {order}"), - } -} - -/// Round an arithmetic right shift like `silk_RSHIFT_ROUND`. -/// -/// Params: signed `value` and positive `shift`. -/// Returns: rounded arithmetic right-shift result. -fn rshift_round(value: i32, shift: usize) -> i32 { - if shift == 1 { - (value >> 1) + (value & 1) - } else { - ((value >> (shift - 1)) + 1) >> 1 - } -} - -/// Clamp a value between two inclusive bounds. -/// -/// Params: signed `value`, `min_value`, and `max_value`. -/// Returns: bounded value. -fn limit32(value: i32, min_value: i32, max_value: i32) -> i32 { - value.clamp(min_value, max_value) -} diff --git a/vendor/opus-decoder/src/silk/pitch.rs b/vendor/opus-decoder/src/silk/pitch.rs deleted file mode 100644 index 8180ca4..0000000 --- a/vendor/opus-decoder/src/silk/pitch.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! SILK pitch lag decoding helpers. - -use super::tables::{ - CB_LAGS_STAGE2_10MS_NB, CB_LAGS_STAGE2_NB, CB_LAGS_STAGE3_10MS_WB, CB_LAGS_STAGE3_WB, - MAX_NB_SUBFR, PITCH_EST_MAX_LAG_MS, PITCH_EST_MIN_LAG_MS, -}; - -/// Decode absolute pitch lags for each active subframe. -/// -/// Params: absolute `lag_index`, `contour_index`, internal `fs_khz`, and active `nb_subfr`. -/// Returns: decoded pitch lag per subframe in samples. -pub(super) fn decode_pitch_lags( - lag_index: i32, - contour_index: i32, - fs_khz: i32, - nb_subfr: usize, -) -> [i32; MAX_NB_SUBFR] { - let min_lag = PITCH_EST_MIN_LAG_MS * fs_khz; - let max_lag = PITCH_EST_MAX_LAG_MS * fs_khz; - let lag = min_lag + lag_index; - let mut pitch_lags = [0i32; MAX_NB_SUBFR]; - - for (k, pitch_lag) in pitch_lags.iter_mut().enumerate().take(nb_subfr) { - let contour = contour_value(fs_khz, nb_subfr, k, contour_index as usize) as i32; - *pitch_lag = (lag + contour).clamp(min_lag, max_lag); - } - - pitch_lags -} - -/// Return one contour entry from the SILK pitch codebook. -/// -/// Params: internal `fs_khz`, active `nb_subfr`, `subframe` row, and `contour_index` column. -/// Returns: contour delta in samples. -fn contour_value(fs_khz: i32, nb_subfr: usize, subframe: usize, contour_index: usize) -> i8 { - match (fs_khz, nb_subfr) { - (8, 4) => CB_LAGS_STAGE2_NB[subframe][contour_index], - (8, 2) => CB_LAGS_STAGE2_10MS_NB[subframe][contour_index], - (_, 4) => CB_LAGS_STAGE3_WB[subframe][contour_index], - (_, 2) => CB_LAGS_STAGE3_10MS_WB[subframe][contour_index], - _ => panic!("unsupported pitch shape fs_khz={fs_khz} nb_subfr={nb_subfr}"), - } -} diff --git a/vendor/opus-decoder/src/silk/plc.rs b/vendor/opus-decoder/src/silk/plc.rs deleted file mode 100644 index 17823ec..0000000 --- a/vendor/opus-decoder/src/silk/plc.rs +++ /dev/null @@ -1,261 +0,0 @@ -//! SILK packet loss concealment helpers. -//! -//! This module stores the subset of libopus PLC state needed by the current -//! Rust decoder and synthesizes one concealed SILK internal frame at a time. - -use super::tables::{MAX_FRAME_LENGTH, MAX_LPC_ORDER, MAX_NB_SUBFR}; - -/// Metadata captured from one decoded SILK internal frame for PLC refresh. -#[derive(Debug, Clone)] -pub(super) struct SilkDecodedFrame { - /// Pitch lag from the last voiced subframe, or zero for unvoiced frames. - pub lag: i32, - /// Collapsed per-subframe pitch gains in Q16. - pub ltp_gains: [i32; MAX_NB_SUBFR], - /// Synthesized internal-rate PCM for the decoded frame. - pub output: Vec, -} - -/// Mirrors the libopus PLC state needed by the Rust SILK concealment path. -#[derive(Debug, Clone)] -pub(super) struct SilkPlcState { - /// Pitch lag in internal-rate samples from the last good voiced frame. - pub pitch_lag: i32, - /// Pitch gain per subframe in Q16. - pub pitch_gain: [i32; MAX_NB_SUBFR], - /// Saved LPC coefficients in Q12. - pub lpc_coeffs: [i32; MAX_LPC_ORDER], - /// Noise-shaping gain in Q14. - pub shape_gain: i32, - /// PLC pseudo-random seed. - pub rand_seed: i32, - /// Last synthesized internal-rate frame for pitch repetition. - pub last_frame_out: Vec, - /// Number of active subframes in one concealed internal frame. - pub nb_subfr: usize, - /// Samples per subframe at the internal SILK rate. - pub subfr_length: usize, - /// Internal sampling rate in kHz. - pub fs_khz: i32, - /// Active LPC order. - pub nb_coefs: usize, -} - -impl Default for SilkPlcState { - /// Create PLC state with stable defaults for the first concealment frame. - /// - /// Parameters: none. - /// Returns: initialized PLC state. - fn default() -> Self { - Self { - pitch_lag: 0, - pitch_gain: [0; MAX_NB_SUBFR], - lpc_coeffs: [0; MAX_LPC_ORDER], - shape_gain: 1 << 14, - rand_seed: 1, - last_frame_out: Vec::new(), - nb_subfr: 0, - subfr_length: 0, - fs_khz: 0, - nb_coefs: 0, - } - } -} - -/// Save decoded SILK state needed for future PLC synthesis. -/// -/// Parameters: mutable `plc` state, decoded `frame` metadata, saved `lpc_q12`, -/// frame layout (`nb_subfr`, `subfr_length`), `fs_khz`, and active `nb_coefs`. -/// Returns: nothing; `plc` is refreshed from the good frame. -pub(super) fn plc_update( - plc: &mut SilkPlcState, - frame: &SilkDecodedFrame, - lpc_q12: &[i32], - nb_subfr: usize, - subfr_length: usize, - fs_khz: i32, - nb_coefs: usize, -) { - plc.pitch_lag = frame.lag.max(0); - plc.pitch_gain.fill(0); - for (dst, src) in plc - .pitch_gain - .iter_mut() - .zip(frame.ltp_gains.iter()) - .take(nb_subfr.min(MAX_NB_SUBFR)) - { - *dst = (*src).clamp(0, 1 << 16); - } - - plc.lpc_coeffs.fill(0); - for (dst, src) in plc - .lpc_coeffs - .iter_mut() - .zip(lpc_q12.iter()) - .take(nb_coefs.min(MAX_LPC_ORDER)) - { - *dst = *src; - } - - plc.nb_subfr = nb_subfr; - plc.subfr_length = subfr_length; - plc.fs_khz = fs_khz; - plc.nb_coefs = nb_coefs.min(MAX_LPC_ORDER); - plc.last_frame_out.clear(); - plc.last_frame_out.extend_from_slice(&frame.output); -} - -/// Generate one concealed SILK internal frame. -/// -/// Parameters: mutable `plc` state, mutable LPC synthesis `lpc_state`, mutable -/// PCM `out`, and packet-level consecutive `loss_count`. -/// Returns: nothing; `out` contains concealed PCM and state is updated in place. -pub(super) fn plc_conceal( - plc: &mut SilkPlcState, - lpc_state: &mut [i32], - out: &mut [i16], - loss_count: u32, -) { - let frame_len = plc.subfr_length.saturating_mul(plc.nb_subfr); - let nb_coefs = plc.nb_coefs.min(MAX_LPC_ORDER).min(lpc_state.len()); - if frame_len == 0 || out.len() != frame_len || nb_coefs == 0 { - out.fill(0); - return; - } - - let attenuate_q16 = if loss_count == 0 { - 1 << 16 - } else { - ((1 << 16) * (10 - loss_count.min(9)) as i32) / 10 - }; - let base_rand_scale_q16 = (plc.fs_khz.max(1) * 3) << 10; - let noise_gain_q16 = mul_q14_q16(plc.shape_gain, base_rand_scale_q16); - let mut synth_q14 = [0i32; MAX_LPC_ORDER + MAX_FRAME_LENGTH]; - synth_q14[..nb_coefs].copy_from_slice(&lpc_state[..nb_coefs]); - - for subframe_idx in 0..plc.nb_subfr { - let gain_q16 = plc.pitch_gain[subframe_idx.min(MAX_NB_SUBFR - 1)]; - let pitch_offset = subframe_idx * plc.subfr_length; - for sample_idx in 0..plc.subfr_length { - let frame_idx = pitch_offset + sample_idx; - let pitch_sample = plc_pitch_sample(plc, frame_idx); - let mut excitation = mul_q16(pitch_sample, gain_q16); - - plc.rand_seed = plc - .rand_seed - .wrapping_mul(196_314_165) - .wrapping_add(907_633_515); - let noise = (plc.rand_seed >> 16) as i16 as i32; - excitation = excitation.wrapping_add(mul_q16(noise, noise_gain_q16)); - - let hist_idx = nb_coefs + frame_idx; - let mut lpc_pred_q10 = (nb_coefs as i32) >> 1; - for coef_idx in 0..nb_coefs { - lpc_pred_q10 = smlawb( - lpc_pred_q10, - synth_q14[hist_idx - 1 - coef_idx], - plc.lpc_coeffs[coef_idx], - ); - } - - let current_q14 = mul_q16( - add_sat32(lshift_sat32(excitation, 14), lshift_sat32(lpc_pred_q10, 4)), - attenuate_q16, - ); - synth_q14[hist_idx] = current_q14; - out[frame_idx] = sat16(rshift_round(current_q14, 14)); - } - } - - lpc_state[..nb_coefs].copy_from_slice(&synth_q14[frame_len..frame_len + nb_coefs]); - plc.last_frame_out.clear(); - plc.last_frame_out.extend_from_slice(out); - plc.shape_gain = ((plc.shape_gain as i64 * 16_220) >> 14) as i32; -} - -/// Read one pitch source sample from the saved PLC history. -/// -/// Parameters: immutable `plc` state and linear `frame_idx` inside the concealed frame. -/// Returns: previous PCM sample used for pitch repetition, or zero when unavailable. -fn plc_pitch_sample(plc: &SilkPlcState, frame_idx: usize) -> i32 { - if plc.pitch_lag <= 0 || plc.last_frame_out.is_empty() { - return 0; - } - - let lag = plc.pitch_lag as usize; - let history_len = plc.last_frame_out.len(); - let lag_idx = frame_idx.wrapping_sub(lag) % history_len; - plc.last_frame_out[lag_idx] as i32 -} - -/// Multiply a signed sample by a Q16 gain. -/// -/// Parameters: signed `value` and `gain_q16`. -/// Returns: scaled signed sample. -fn mul_q16(value: i32, gain_q16: i32) -> i32 { - ((value as i64 * gain_q16 as i64) >> 16) as i32 -} - -/// Multiply a Q14 value with a Q16 value and keep Q16 scaling. -/// -/// Parameters: Q14 `value_q14` and Q16 `value_q16`. -/// Returns: Q16-scaled product. -fn mul_q14_q16(value_q14: i32, value_q16: i32) -> i32 { - ((value_q14 as i64 * value_q16 as i64) >> 14) as i32 -} - -/// Approximate `a32 + ((b32 * (i16)c32) >> 16)`. -/// -/// Parameters: accumulator `a32`, multiplicand `b32`, and signed `c32`. -/// Returns: accumulated high-word product. -fn smlawb(a32: i32, b32: i32, c32: i32) -> i32 { - a32.wrapping_add(smulwb(b32, c32)) -} - -/// Multiply by the signed low 16-bit half of `b32` and keep high word. -/// -/// Parameters: signed `a32` and signed `b32`. -/// Returns: high-word signed product. -fn smulwb(a32: i32, b32: i32) -> i32 { - let b16 = b32 as i16 as i32; - let high = (a32 >> 16) * b16; - let low = ((a32 & 0xFFFF) * b16) >> 16; - high.wrapping_add(low) -} - -/// Saturating 32-bit addition. -/// -/// Parameters: signed `a` and `b`. -/// Returns: saturated sum. -fn add_sat32(a: i32, b: i32) -> i32 { - a.saturating_add(b) -} - -/// Left-shift with saturation. -/// -/// Parameters: signed `value` and non-negative `shift`. -/// Returns: saturated left-shifted value. -fn lshift_sat32(value: i32, shift: usize) -> i32 { - let widened = (value as i64) << shift; - widened.clamp(i64::from(i32::MIN), i64::from(i32::MAX)) as i32 -} - -/// Round a signed arithmetic right shift. -/// -/// Parameters: signed `value` and positive `shift`. -/// Returns: rounded shifted value. -fn rshift_round(value: i32, shift: usize) -> i32 { - if shift == 1 { - (value >> 1) + (value & 1) - } else { - ((value >> (shift - 1)) + 1) >> 1 - } -} - -/// Saturate a signed value to `i16`. -/// -/// Parameters: signed `value`. -/// Returns: clamped PCM sample. -fn sat16(value: i32) -> i16 { - value.clamp(i16::MIN as i32, i16::MAX as i32) as i16 -} diff --git a/vendor/opus-decoder/src/silk/resampler.rs b/vendor/opus-decoder/src/silk/resampler.rs deleted file mode 100644 index 36ccf50..0000000 --- a/vendor/opus-decoder/src/silk/resampler.rs +++ /dev/null @@ -1,157 +0,0 @@ -//! SILK decoder-side resampler for internal-rate PCM to 48 kHz. - -use crate::Error; - -use super::resampler_private; - -const RESAMPLER_MAX_BATCH_SIZE_MS: usize = 10; -const SILK_RESAMPLER_MAX_IIR_ORDER: usize = 6; -const SILK_RESAMPLER_MAX_DELAY: usize = 96; - -/// Decoder-side SILK resampler state. -#[derive(Debug, Clone)] -pub(super) struct SilkResampler { - /// Input sampling rate in Hz. - fs_in_hz: u32, - /// Output sampling rate in Hz. - fs_out_hz: u32, - /// Input rate in kHz. - fs_in_khz: usize, - /// One-block input batch size. - batch_size: usize, - /// Ratio step used by the FIR interpolator. - inv_ratio_q16: i32, - /// Decoder-mode delay compensation in samples. - input_delay: usize, - /// IIR/allpass state. - s_iir: [i32; SILK_RESAMPLER_MAX_IIR_ORDER], - /// FIR/history state. - s_fir: [i16; resampler_private::fir_history_len()], - /// Delay buffer used by the top-level dispatcher. - delay_buf: [i16; SILK_RESAMPLER_MAX_DELAY], -} - -impl SilkResampler { - /// Create a decoder-mode SILK resampler. - /// - /// Params: `fs_in_hz` internal SILK rate and `fs_out_hz` API output rate. - /// Returns: initialized resampler state for supported decoder paths. - pub(super) fn new(fs_in_hz: u32, fs_out_hz: u32) -> Result { - let input_delay = match (fs_in_hz, fs_out_hz) { - (8_000, 48_000) => 0, - (12_000, 48_000) => 4, - (16_000, 48_000) => 7, - _ => return Err(Error::NotImplemented), - }; - let fs_in_khz = (fs_in_hz / 1000) as usize; - let batch_size = fs_in_khz * RESAMPLER_MAX_BATCH_SIZE_MS; - let mut inv_ratio_q16 = ((((fs_in_hz as i64) << 15) / fs_out_hz as i64) << 2) as i32; - while smulww(inv_ratio_q16, fs_out_hz as i32) < ((fs_in_hz as i32) << 1) { - inv_ratio_q16 += 1; - } - - Ok(Self { - fs_in_hz, - fs_out_hz, - fs_in_khz, - batch_size, - inv_ratio_q16, - input_delay, - s_iir: [0; SILK_RESAMPLER_MAX_IIR_ORDER], - s_fir: [0; resampler_private::fir_history_len()], - delay_buf: [0; SILK_RESAMPLER_MAX_DELAY], - }) - } - - /// Check whether the current state matches a rate pair. - /// - /// Params: `fs_in_hz` and `fs_out_hz`. - /// Returns: true when the resampler can be reused as-is. - pub(super) fn matches(&self, fs_in_hz: u32, fs_out_hz: u32) -> bool { - self.fs_in_hz == fs_in_hz && self.fs_out_hz == fs_out_hz - } - - /// Resample internal-rate PCM to API-rate PCM. - /// - /// Params: immutable `input` PCM at internal rate and mutable `output` PCM at API rate. - /// Returns: number of output samples written. - pub(super) fn process(&mut self, input: &[i16], output: &mut [i16]) -> usize { - debug_assert!(input.len() >= self.fs_in_khz); - - let n_samples = self.fs_in_khz - self.input_delay; - self.delay_buf[self.input_delay..self.fs_in_khz].copy_from_slice(&input[..n_samples]); - - let mut written = 0usize; - let first_block = self.delay_buf[..self.fs_in_khz].to_vec(); - written += self.process_iir_fir(&first_block, &mut output[written..]); - let second_len = input.len().saturating_sub(self.fs_in_khz); - written += self.process_iir_fir( - &input[n_samples..n_samples + second_len], - &mut output[written..], - ); - - if self.input_delay > 0 { - let start = input.len() - self.input_delay; - self.delay_buf[..self.input_delay].copy_from_slice(&input[start..]); - } - written - } - - /// Run the IIR+FIR upsampling core on one contiguous input slice. - /// - /// Params: immutable `input` block and mutable `output` buffer. - /// Returns: number of output samples written. - fn process_iir_fir(&mut self, input: &[i16], output: &mut [i16]) -> usize { - if input.is_empty() { - return 0; - } - - let history = resampler_private::fir_history_len(); - let mut total_written = 0usize; - let mut in_offset = 0usize; - - while in_offset < input.len() { - let n_samples_in = (input.len() - in_offset).min(self.batch_size); - let mut buf = vec![0i16; history + (2 * n_samples_in) + history]; - buf[..history].copy_from_slice(&self.s_fir); - resampler_private::up2_hq( - &mut self.s_iir, - &mut buf[history..history + 2 * n_samples_in], - &input[in_offset..in_offset + n_samples_in], - n_samples_in, - ); - - let max_index_q16 = (n_samples_in as i32) << 17; - let written = resampler_private::iir_fir_interpol( - &mut output[total_written..], - &buf, - max_index_q16, - self.inv_ratio_q16, - ); - total_written += written; - - let src_start = 2 * n_samples_in; - self.s_fir - .copy_from_slice(&buf[src_start..src_start + history]); - in_offset += n_samples_in; - } - - total_written - } - - /// Return the number of output samples for a given input length. - /// - /// Params: input length in samples. - /// Returns: exact output sample count for the configured ratio. - pub(super) fn output_len(&self, input_len: usize) -> usize { - ((input_len as u64) * self.fs_out_hz as u64 / self.fs_in_hz as u64) as usize - } -} - -/// Approximate signed `((a32 * b32) >> 16)`. -/// -/// Params: signed `a32` and `b32`. -/// Returns: signed Q16-scaled product. -fn smulww(a32: i32, b32: i32) -> i32 { - ((a32 as i64 * b32 as i64) >> 16) as i32 -} diff --git a/vendor/opus-decoder/src/silk/resampler_private.rs b/vendor/opus-decoder/src/silk/resampler_private.rs deleted file mode 100644 index 7e38ac5..0000000 --- a/vendor/opus-decoder/src/silk/resampler_private.rs +++ /dev/null @@ -1,174 +0,0 @@ -//! Private SILK resampler building blocks. - -use super::resampler_rom::{ - RESAMPLER_FRAC_FIR_12, RESAMPLER_ORDER_FIR_12, RESAMPLER_UP2_HQ_0, RESAMPLER_UP2_HQ_1, -}; - -/// Upsample by 2 using the SILK high-quality allpass structure. -/// -/// Params: mutable `state_q10`, mutable `output`, immutable `input`, and active `len`. -/// Returns: nothing; `output[..2*len]` receives 2x upsampled PCM. -pub(super) fn up2_hq(state_q10: &mut [i32; 6], output: &mut [i16], input: &[i16], len: usize) { - for k in 0..len { - let in32 = (input[k] as i32) << 10; - - let mut y = in32 - state_q10[0]; - let mut x = smulwb(y, RESAMPLER_UP2_HQ_0[0] as i32); - let mut out32_1 = state_q10[0] + x; - state_q10[0] = in32 + x; - - y = out32_1 - state_q10[1]; - x = smulwb(y, RESAMPLER_UP2_HQ_0[1] as i32); - let out32_2 = state_q10[1] + x; - state_q10[1] = out32_1 + x; - - y = out32_2 - state_q10[2]; - x = smlawb(y, y, RESAMPLER_UP2_HQ_0[2] as i32); - out32_1 = state_q10[2] + x; - state_q10[2] = out32_2 + x; - output[2 * k] = sat16(rshift_round(out32_1, 10)); - - y = in32 - state_q10[3]; - x = smulwb(y, RESAMPLER_UP2_HQ_1[0] as i32); - out32_1 = state_q10[3] + x; - state_q10[3] = in32 + x; - - y = out32_1 - state_q10[4]; - x = smulwb(y, RESAMPLER_UP2_HQ_1[1] as i32); - let out32_2 = state_q10[4] + x; - state_q10[4] = out32_1 + x; - - y = out32_2 - state_q10[5]; - x = smlawb(y, y, RESAMPLER_UP2_HQ_1[2] as i32); - out32_1 = state_q10[5] + x; - state_q10[5] = out32_2 + x; - output[2 * k + 1] = sat16(rshift_round(out32_1, 10)); - } -} - -/// Interpolate a 2x-upsampled temporary buffer to the target rate. -/// -/// Params: mutable `output`, FIR/history `buffer`, maximum source `max_index_q16`, -/// and output-step `index_increment_q16`. -/// Returns: number of samples written to `output`. -pub(super) fn iir_fir_interpol( - output: &mut [i16], - buffer: &[i16], - max_index_q16: i32, - index_increment_q16: i32, -) -> usize { - let mut out_idx = 0usize; - let mut index_q16 = 0i32; - while index_q16 < max_index_q16 { - let table_index = smulwb(index_q16 & 0xFFFF, 12) as usize; - let buf_index = (index_q16 >> 16) as usize; - let buf_ptr = &buffer[buf_index..]; - - let mut res_q15 = smulbb( - buf_ptr[0] as i32, - RESAMPLER_FRAC_FIR_12[table_index][0] as i32, - ); - res_q15 = smlabb( - res_q15, - buf_ptr[1] as i32, - RESAMPLER_FRAC_FIR_12[table_index][1] as i32, - ); - res_q15 = smlabb( - res_q15, - buf_ptr[2] as i32, - RESAMPLER_FRAC_FIR_12[table_index][2] as i32, - ); - res_q15 = smlabb( - res_q15, - buf_ptr[3] as i32, - RESAMPLER_FRAC_FIR_12[table_index][3] as i32, - ); - res_q15 = smlabb( - res_q15, - buf_ptr[4] as i32, - RESAMPLER_FRAC_FIR_12[11 - table_index][3] as i32, - ); - res_q15 = smlabb( - res_q15, - buf_ptr[5] as i32, - RESAMPLER_FRAC_FIR_12[11 - table_index][2] as i32, - ); - res_q15 = smlabb( - res_q15, - buf_ptr[6] as i32, - RESAMPLER_FRAC_FIR_12[11 - table_index][1] as i32, - ); - res_q15 = smlabb( - res_q15, - buf_ptr[7] as i32, - RESAMPLER_FRAC_FIR_12[11 - table_index][0] as i32, - ); - output[out_idx] = sat16(rshift_round(res_q15, 15)); - out_idx += 1; - index_q16 += index_increment_q16; - } - out_idx -} - -/// Return FIR history length for the upsampling interpolator. -/// -/// Params: none. -/// Returns: FIR history length in samples. -pub(super) const fn fir_history_len() -> usize { - RESAMPLER_ORDER_FIR_12 -} - -/// Multiply a 32-bit value by the signed low half of another value. -/// -/// Params: signed `a32` and signed `b32`. -/// Returns: high-word product. -fn smulwb(a32: i32, b32: i32) -> i32 { - let b16 = b32 as i16 as i32; - let high = (a32 >> 16) * b16; - let low = ((a32 & 0xFFFF) * b16) >> 16; - high.wrapping_add(low) -} - -/// Multiply signed low halves and accumulate. -/// -/// Params: accumulator `a32`, signed `b32`, and signed `c32`. -/// Returns: accumulated product. -fn smlabb(a32: i32, b32: i32, c32: i32) -> i32 { - a32 + (b32 as i16 as i32) * (c32 as i16 as i32) -} - -/// Multiply signed low half and accumulate with high-word scaling. -/// -/// Params: accumulator `a32`, multiplicand `b32`, and signed `c32`. -/// Returns: accumulated high-word product. -fn smlawb(a32: i32, b32: i32, c32: i32) -> i32 { - a32.wrapping_add(smulwb(b32, c32)) -} - -/// Multiply signed low halves. -/// -/// Params: signed `a32` and `b32`. -/// Returns: signed 32-bit product. -fn smulbb(a32: i32, b32: i32) -> i32 { - (a32 as i16 as i32) * (b32 as i16 as i32) -} - -/// Saturate to `i16`. -/// -/// Params: signed `value`. -/// Returns: clamped PCM sample. -fn sat16(value: i32) -> i16 { - value.clamp(i16::MIN as i32, i16::MAX as i32) as i16 -} - -/// Round a signed arithmetic right shift. -/// -/// Params: signed `value` and positive `shift`. -/// Returns: rounded shifted value. -fn rshift_round(value: i32, shift: usize) -> i32 { - if shift == 1 { - (value >> 1) + (value & 1) - } else { - ((value >> (shift - 1)) + 1) >> 1 - } -} diff --git a/vendor/opus-decoder/src/silk/resampler_rom.rs b/vendor/opus-decoder/src/silk/resampler_rom.rs deleted file mode 100644 index e0464ad..0000000 --- a/vendor/opus-decoder/src/silk/resampler_rom.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! SILK resampler ROM tables. - -/// FIR interpolation order for upsampling paths. -pub(super) const RESAMPLER_ORDER_FIR_12: usize = 8; - -/// High-quality 2x upsampler allpass coefficients, branch 0. -pub(super) const RESAMPLER_UP2_HQ_0: [i16; 3] = [1746, 14_986, -26_453]; - -/// High-quality 2x upsampler allpass coefficients, branch 1. -pub(super) const RESAMPLER_UP2_HQ_1: [i16; 3] = [6854, 25_769, -9994]; - -/// Fractional FIR interpolation table for the upsampling resampler. -pub(super) const RESAMPLER_FRAC_FIR_12: [[i16; RESAMPLER_ORDER_FIR_12 / 2]; 12] = [ - [189, -600, 617, 30_567], - [117, -159, -1070, 29_704], - [52, 221, -2392, 28_276], - [-4, 529, -3350, 26_341], - [-48, 758, -3956, 23_973], - [-80, 905, -4235, 21_254], - [-99, 972, -4222, 18_278], - [-107, 967, -3957, 15_143], - [-103, 896, -3487, 11_950], - [-91, 773, -2865, 8798], - [-71, 611, -2143, 5784], - [-46, 425, -1375, 2996], -]; diff --git a/vendor/opus-decoder/src/silk/stereo.rs b/vendor/opus-decoder/src/silk/stereo.rs deleted file mode 100644 index fb942ca..0000000 --- a/vendor/opus-decoder/src/silk/stereo.rs +++ /dev/null @@ -1,176 +0,0 @@ -//! SILK stereo signaling parser helpers for Phase 3a. - -use crate::entropy::EcDec; - -use super::tables; - -const STEREO_STEP_Q16: i32 = 6_554; // SILK_FIX_CONST(0.5 / 5, 16) -const STEREO_INTERP_LEN_MS: usize = 8; - -/// Decoder-side SILK stereo state. -/// -/// Params: none. -/// Returns: persistent predictor and sample history for MS->LR conversion. -#[derive(Debug, Clone, Default)] -pub(super) struct StereoState { - pub pred_prev_q13: [i16; 2], - pub s_mid: [i16; 2], - pub s_side: [i16; 2], -} - -/// Decode SILK stereo predictor signaling (`silk_stereo_decode_pred`). -/// -/// Params: `dec` is the shared Opus range decoder over SILK frame payload. -/// Returns: two MS predictor values in Q13. -pub(super) fn decode_stereo_pred(dec: &mut EcDec<'_>) -> [i32; 2] { - let mut ix = [[0i32; 3]; 2]; - let joint = dec.dec_icdf(&tables::STEREO_PRED_JOINT_ICDF, 8); - ix[0][2] = joint / 5; - ix[1][2] = joint - 5 * ix[0][2]; - - for side in &mut ix { - side[0] = dec.dec_icdf(&tables::UNIFORM3_ICDF, 8); - side[1] = dec.dec_icdf(&tables::UNIFORM5_ICDF, 8); - } - - let mut pred_q13 = [0i32; 2]; - for (n, side) in ix.iter_mut().enumerate() { - side[0] += 3 * side[2]; - let low_q13 = tables::STEREO_PRED_QUANT_Q13[side[0] as usize]; - let high_q13 = tables::STEREO_PRED_QUANT_Q13[side[0] as usize + 1]; - let step_q13 = mul_wb(high_q13 - low_q13, STEREO_STEP_Q16); - pred_q13[n] = low_q13 + step_q13 * (2 * side[1] + 1); - } - - pred_q13[0] -= pred_q13[1]; - pred_q13 -} - -/// Decode SILK "mid-only coded" stereo flag (`silk_stereo_decode_mid_only`). -/// -/// Params: `dec` is the shared Opus range decoder over SILK frame payload. -/// Returns: true when side channel is omitted for this frame. -pub(super) fn decode_mid_only(dec: &mut EcDec<'_>) -> bool { - dec.dec_icdf(&tables::STEREO_ONLY_CODE_MID_ICDF, 8) != 0 -} - -/// Multiply helper matching SILK `silk_SMULWB` behavior. -/// -/// Params: `a` and `b` are signed fixed-point values. -/// Returns: high-word product `(a*b)>>16`. -fn mul_wb(a: i32, b: i32) -> i32 { - ((a as i64 * b as i64) >> 16) as i32 -} - -/// Convert one SILK internal frame from adaptive mid/side to left/right. -/// -/// Params: mutable stereo `state`, mutable `mid` and `side` frame buffers, -/// current `pred_q13`, and internal sampling rate `fs_khz`. -/// Returns: nothing; `mid` becomes left and `side` becomes right in place. -pub(super) fn ms_to_lr( - state: &mut StereoState, - mid: &mut [i16], - side: &mut [i16], - pred_q13: [i32; 2], - fs_khz: u32, - _packet_idx: usize, -) { - let frame_length = mid.len(); - debug_assert_eq!(side.len(), frame_length); - - let mut x1 = vec![0i16; frame_length + 2]; - let mut x2 = vec![0i16; frame_length + 2]; - x1[..2].copy_from_slice(&state.s_mid); - x2[..2].copy_from_slice(&state.s_side); - x1[2..].copy_from_slice(mid); - x2[2..].copy_from_slice(side); - state - .s_mid - .copy_from_slice(&x1[frame_length..frame_length + 2]); - state - .s_side - .copy_from_slice(&x2[frame_length..frame_length + 2]); - - let mut pred0_q13 = state.pred_prev_q13[0] as i32; - let mut pred1_q13 = state.pred_prev_q13[1] as i32; - let denom_q16 = (1 << 16) / (STEREO_INTERP_LEN_MS as i32 * fs_khz as i32); - let delta0_q13 = rshift_round(smulbb(pred_q13[0] - pred0_q13, denom_q16), 16); - let delta1_q13 = rshift_round(smulbb(pred_q13[1] - pred1_q13, denom_q16), 16); - let interp_len = (STEREO_INTERP_LEN_MS * fs_khz as usize).min(frame_length); - - for n in 0..interp_len { - pred0_q13 += delta0_q13; - pred1_q13 += delta1_q13; - let mut sum = (x1[n] as i32 + x1[n + 2] as i32 + ((x1[n + 1] as i32) << 1)) << 9; - let sum_after_pred0 = smlawb((x2[n + 1] as i32) << 8, sum, pred0_q13); - let sum_after_pred1 = smlawb(sum_after_pred0, (x1[n + 1] as i32) << 11, pred1_q13); - sum = sum_after_pred1; - x2[n + 1] = sat16(rshift_round(sum, 8)); - } - - pred0_q13 = pred_q13[0]; - pred1_q13 = pred_q13[1]; - for n in interp_len..frame_length { - let mut sum = (x1[n] as i32 + x1[n + 2] as i32 + ((x1[n + 1] as i32) << 1)) << 9; - sum = smlawb((x2[n + 1] as i32) << 8, sum, pred0_q13); - sum = smlawb(sum, (x1[n + 1] as i32) << 11, pred1_q13); - x2[n + 1] = sat16(rshift_round(sum, 8)); - } - state.pred_prev_q13 = [pred_q13[0] as i16, pred_q13[1] as i16]; - - for n in 0..frame_length { - let sum = x1[n + 1] as i32 + x2[n + 1] as i32; - let diff = x1[n + 1] as i32 - x2[n + 1] as i32; - x1[n + 1] = sat16(sum); - x2[n + 1] = sat16(diff); - } - mid.copy_from_slice(&x1[1..1 + frame_length]); - side.copy_from_slice(&x2[1..1 + frame_length]); -} - -/// Multiply signed low 16-bit halves. -/// -/// Params: signed `a32` and `b32`. -/// Returns: signed 32-bit product. -fn smulbb(a32: i32, b32: i32) -> i32 { - (a32 as i16 as i32) * (b32 as i16 as i32) -} - -/// Multiply by the signed low 16-bit half of `b32` and keep high word. -/// -/// Params: signed `a32` and signed `b32`. -/// Returns: high-word signed product. -fn smulwb(a32: i32, b32: i32) -> i32 { - let b16 = b32 as i16 as i32; - let high = (a32 >> 16) * b16; - let low = ((a32 & 0xFFFF) * b16) >> 16; - high.wrapping_add(low) -} - -/// Approximate `a32 + ((b32 * (i16)c32) >> 16)`. -/// -/// Params: accumulator `a32`, multiplicand `b32`, and signed `c32`. -/// Returns: accumulated high-word product. -fn smlawb(a32: i32, b32: i32, c32: i32) -> i32 { - a32.wrapping_add(smulwb(b32, c32)) -} - -/// Saturate to `i16`. -/// -/// Params: signed `value`. -/// Returns: clamped 16-bit PCM sample. -fn sat16(value: i32) -> i16 { - value.clamp(i16::MIN as i32, i16::MAX as i32) as i16 -} - -/// Round a signed arithmetic right shift. -/// -/// Params: signed `value` and positive `shift`. -/// Returns: rounded shifted value. -fn rshift_round(value: i32, shift: usize) -> i32 { - if shift == 1 { - (value >> 1) + (value & 1) - } else { - ((value >> (shift - 1)) + 1) >> 1 - } -} diff --git a/vendor/opus-decoder/src/silk/tables.rs b/vendor/opus-decoder/src/silk/tables.rs deleted file mode 100644 index 0fd08bd..0000000 --- a/vendor/opus-decoder/src/silk/tables.rs +++ /dev/null @@ -1,380 +0,0 @@ -//! SILK tables used by stereo signaling and NLSF/LPC decode. - -/// Maximum SILK LPC order. -pub(super) const MAX_LPC_ORDER: usize = 16; -/// Maximum number of SILK subframes. -pub(super) const MAX_NB_SUBFR: usize = 4; -/// Maximum SILK subframe length in samples. -pub(super) const MAX_SUB_FRAME_LENGTH: usize = 80; -/// Maximum SILK frame length in samples. -pub(super) const MAX_FRAME_LENGTH: usize = 320; -/// LTP filter order. -pub(super) const LTP_ORDER: usize = 5; -/// LTP memory in milliseconds. -pub(super) const LTP_MEM_LENGTH_MS: usize = 20; -/// Minimum pitch lag in milliseconds. -pub(super) const PITCH_EST_MIN_LAG_MS: i32 = 2; -/// Maximum pitch lag in milliseconds. -pub(super) const PITCH_EST_MAX_LAG_MS: i32 = 18; -/// Maximum number of stage-2 contour codebooks. -pub(super) const PE_NB_CBKS_STAGE2_EXT: usize = 11; -/// Maximum number of stage-3 contour codebooks. -pub(super) const PE_NB_CBKS_STAGE3_MAX: usize = 34; -/// Number of stage-3 contour codebooks for 10 ms. -pub(super) const PE_NB_CBKS_STAGE3_10MS: usize = 12; -/// Number of stage-2 contour codebooks for 10 ms. -pub(super) const PE_NB_CBKS_STAGE2_10MS: usize = 3; -/// Quantization level adjustment in Q10. -pub(super) const QUANT_LEVEL_ADJUST_Q10: i32 = 80; -/// Unvoiced low quantization offset in Q10. -pub(super) const OFFSET_UVL_Q10: i32 = 100; -/// Unvoiced high quantization offset in Q10. -pub(super) const OFFSET_UVH_Q10: i32 = 240; -/// Voiced low quantization offset in Q10. -pub(super) const OFFSET_VL_Q10: i32 = 32; -/// Voiced high quantization offset in Q10. -pub(super) const OFFSET_VH_Q10: i32 = 100; -/// Number of quantized gain levels. -pub(super) const N_LEVELS_QGAIN: i32 = 64; -/// Minimum gain delta index. -pub(super) const MIN_DELTA_GAIN_QUANT: i32 = -4; -/// Maximum gain delta index. -pub(super) const MAX_DELTA_GAIN_QUANT: i32 = 36; - -/// Full SILK NLSF codebook descriptor used by NLSF reconstruction. -#[derive(Debug, Clone, Copy)] -pub(super) struct NlsfCodebook { - /// LPC order for the codebook. - pub order: usize, - /// Residual quantization step size in Q16. - pub quant_step_size_q16: i32, - /// Stage-1 centroids in Q8. - pub cb1_nlsf_q8: &'static [u8], - /// Stage-1 weights in Q9. - pub cb1_wght_q9: &'static [i16], - /// Predictive residual coefficients in Q8. - pub pred_q8: &'static [u8], - /// Packed predictor/entropy selector map. - pub ec_sel: &'static [u8], - /// Minimum deltas used during stabilization in Q15. - pub delta_min_q15: &'static [i16], -} - -/// Joint iCDF for stereo predictor coarse index (`silk_stereo_pred_joint_iCDF`). -pub(super) const STEREO_PRED_JOINT_ICDF: [u8; 25] = [ - 249, 247, 246, 245, 244, 234, 210, 202, 201, 200, 197, 174, 82, 59, 56, 55, 54, 46, 22, 12, 11, - 10, 9, 7, 0, -]; - -/// Uniform 3-way iCDF (`silk_uniform3_iCDF`). -pub(super) const UNIFORM3_ICDF: [u8; 3] = [171, 85, 0]; - -/// Uniform 5-way iCDF (`silk_uniform5_iCDF`). -pub(super) const UNIFORM5_ICDF: [u8; 5] = [205, 154, 102, 51, 0]; - -/// Mid-only stereo flag iCDF (`silk_stereo_only_code_mid_iCDF`). -pub(super) const STEREO_ONLY_CODE_MID_ICDF: [u8; 2] = [64, 0]; - -/// Stereo predictor dequantization table (`silk_stereo_pred_quant_Q13`). -pub(super) const STEREO_PRED_QUANT_Q13: [i32; 16] = [ - -13_732, -10_050, -8_266, -7_526, -6_500, -5_000, -2_950, -820, 820, 2_950, 5_000, 6_500, - 7_526, 8_266, 10_050, 13_732, -]; - -/// LBRR-flag iCDF for 2 internal SILK frames (`silk_LBRR_flags_2_iCDF`). -pub(super) const LBRR_FLAGS_2_ICDF: [u8; 3] = [203, 150, 0]; - -/// LBRR-flag iCDF for 3 internal SILK frames (`silk_LBRR_flags_3_iCDF`). -pub(super) const LBRR_FLAGS_3_ICDF: [u8; 7] = [215, 195, 166, 125, 110, 82, 0]; - -/// Return LBRR iCDF table for given internal frame count. -/// -/// Params: `internal_frames` is number of 20 ms SILK-internal frames in one Opus frame. -/// Returns: table slice for 2/3-frame packets, or `None` for unsupported counts. -pub(super) fn lbrr_flags_icdf(internal_frames: usize) -> Option<&'static [u8]> { - match internal_frames { - 2 => Some(&LBRR_FLAGS_2_ICDF), - 3 => Some(&LBRR_FLAGS_3_ICDF), - _ => None, - } -} - -/// Piecewise-linear cosine approximation table in Q12. -pub(super) const LSF_COS_TAB: [i16; 129] = [ - 8192, 8190, 8182, 8170, 8152, 8130, 8104, 8072, 8034, 7994, 7946, 7896, 7840, 7778, 7714, 7644, - 7568, 7490, 7406, 7318, 7226, 7128, 7026, 6922, 6812, 6698, 6580, 6458, 6332, 6204, 6070, 5934, - 5792, 5648, 5502, 5352, 5198, 5040, 4880, 4718, 4552, 4382, 4212, 4038, 3862, 3684, 3502, 3320, - 3136, 2948, 2760, 2570, 2378, 2186, 1990, 1794, 1598, 1400, 1202, 1002, 802, 602, 402, 202, 0, - -202, -402, -602, -802, -1002, -1202, -1400, -1598, -1794, -1990, -2186, -2378, -2570, -2760, - -2948, -3136, -3320, -3502, -3684, -3862, -4038, -4212, -4382, -4552, -4718, -4880, -5040, - -5198, -5352, -5502, -5648, -5792, -5934, -6070, -6204, -6332, -6458, -6580, -6698, -6812, - -6922, -7026, -7128, -7226, -7318, -7406, -7490, -7568, -7644, -7714, -7778, -7840, -7896, - -7946, -7994, -8034, -8072, -8104, -8130, -8152, -8170, -8182, -8190, -8192, -]; - -const NLSF_CB1_NB_MB_Q8: [u8; 320] = [ - 12, 35, 60, 83, 108, 132, 157, 180, 206, 228, 15, 32, 55, 77, 101, 125, 151, 175, 201, 225, 19, - 42, 66, 89, 114, 137, 162, 184, 209, 230, 12, 25, 50, 72, 97, 120, 147, 172, 200, 223, 26, 44, - 69, 90, 114, 135, 159, 180, 205, 225, 13, 22, 53, 80, 106, 130, 156, 180, 205, 228, 15, 25, 44, - 64, 90, 115, 142, 168, 196, 222, 19, 24, 62, 82, 100, 120, 145, 168, 190, 214, 22, 31, 50, 79, - 103, 120, 151, 170, 203, 227, 21, 29, 45, 65, 106, 124, 150, 171, 196, 224, 30, 49, 75, 97, - 121, 142, 165, 186, 209, 229, 19, 25, 52, 70, 93, 116, 143, 166, 192, 219, 26, 34, 62, 75, 97, - 118, 145, 167, 194, 217, 25, 33, 56, 70, 91, 113, 143, 165, 196, 223, 21, 34, 51, 72, 97, 117, - 145, 171, 196, 222, 20, 29, 50, 67, 90, 117, 144, 168, 197, 221, 22, 31, 48, 66, 95, 117, 146, - 168, 196, 222, 24, 33, 51, 77, 116, 134, 158, 180, 200, 224, 21, 28, 70, 87, 106, 124, 149, - 170, 194, 217, 26, 33, 53, 64, 83, 117, 152, 173, 204, 225, 27, 34, 65, 95, 108, 129, 155, 174, - 210, 225, 20, 26, 72, 99, 113, 131, 154, 176, 200, 219, 34, 43, 61, 78, 93, 114, 155, 177, 205, - 229, 23, 29, 54, 97, 124, 138, 163, 179, 209, 229, 30, 38, 56, 89, 118, 129, 158, 178, 200, - 231, 21, 29, 49, 63, 85, 111, 142, 163, 193, 222, 27, 48, 77, 103, 133, 158, 179, 196, 215, - 232, 29, 47, 74, 99, 124, 151, 176, 198, 220, 237, 33, 42, 61, 76, 93, 121, 155, 174, 207, 225, - 29, 53, 87, 112, 136, 154, 170, 188, 208, 227, 24, 30, 52, 84, 131, 150, 166, 186, 203, 229, - 37, 48, 64, 84, 104, 118, 156, 177, 201, 230, -]; - -const NLSF_CB1_WGHT_NB_MB_Q9: [i16; 320] = [ - 2897, 2314, 2314, 2314, 2287, 2287, 2314, 2300, 2327, 2287, 2888, 2580, 2394, 2367, 2314, 2274, - 2274, 2274, 2274, 2194, 2487, 2340, 2340, 2314, 2314, 2314, 2340, 2340, 2367, 2354, 3216, 2766, - 2340, 2340, 2314, 2274, 2221, 2207, 2261, 2194, 2460, 2474, 2367, 2394, 2394, 2394, 2394, 2367, - 2407, 2314, 3479, 3056, 2127, 2207, 2274, 2274, 2274, 2287, 2314, 2261, 3282, 3141, 2580, 2394, - 2247, 2221, 2207, 2194, 2194, 2114, 4096, 3845, 2221, 2620, 2620, 2407, 2314, 2394, 2367, 2074, - 3178, 3244, 2367, 2221, 2553, 2434, 2340, 2314, 2167, 2221, 3338, 3488, 2726, 2194, 2261, 2460, - 2354, 2367, 2207, 2101, 2354, 2420, 2327, 2367, 2394, 2420, 2420, 2420, 2460, 2367, 3779, 3629, - 2434, 2527, 2367, 2274, 2274, 2300, 2207, 2048, 3254, 3225, 2713, 2846, 2447, 2327, 2300, 2300, - 2274, 2127, 3263, 3300, 2753, 2806, 2447, 2261, 2261, 2247, 2127, 2101, 2873, 2981, 2633, 2367, - 2407, 2354, 2194, 2247, 2247, 2114, 3225, 3197, 2633, 2580, 2274, 2181, 2247, 2221, 2221, 2141, - 3178, 3310, 2740, 2407, 2274, 2274, 2274, 2287, 2194, 2114, 3141, 3272, 2460, 2061, 2287, 2500, - 2367, 2487, 2434, 2181, 3507, 3282, 2314, 2700, 2647, 2474, 2367, 2394, 2340, 2127, 3423, 3535, - 3038, 3056, 2300, 1950, 2221, 2274, 2274, 2274, 3404, 3366, 2087, 2687, 2873, 2354, 2420, 2274, - 2474, 2540, 3760, 3488, 1950, 2660, 2897, 2527, 2394, 2367, 2460, 2261, 3028, 3272, 2740, 2888, - 2740, 2154, 2127, 2287, 2234, 2247, 3695, 3657, 2025, 1969, 2660, 2700, 2580, 2500, 2327, 2367, - 3207, 3413, 2354, 2074, 2888, 2888, 2340, 2487, 2247, 2167, 3338, 3366, 2846, 2780, 2327, 2154, - 2274, 2287, 2114, 2061, 2327, 2300, 2181, 2167, 2181, 2367, 2633, 2700, 2700, 2553, 2407, 2434, - 2221, 2261, 2221, 2221, 2340, 2420, 2607, 2700, 3038, 3244, 2806, 2888, 2474, 2074, 2300, 2314, - 2354, 2380, 2221, 2154, 2127, 2287, 2500, 2793, 2793, 2620, 2580, 2367, 3676, 3713, 2234, 1838, - 2181, 2753, 2726, 2673, 2513, 2207, 2793, 3160, 2726, 2553, 2846, 2513, 2181, 2394, 2221, 2181, -]; - -const NLSF_PRED_NB_MB_Q8: [u8; 18] = [ - 179, 138, 140, 148, 151, 149, 153, 151, 163, 116, 67, 82, 59, 92, 72, 100, 89, 92, -]; - -const NLSF_DELTA_MIN_NB_MB_Q15: [i16; 11] = [250, 3, 6, 3, 3, 3, 4, 3, 3, 3, 461]; - -const NLSF_CB1_WB_Q8: [u8; 512] = [ - 7, 23, 38, 54, 69, 85, 100, 116, 131, 147, 162, 178, 193, 208, 223, 239, 13, 25, 41, 55, 69, - 83, 98, 112, 127, 142, 157, 171, 187, 203, 220, 236, 15, 21, 34, 51, 61, 78, 92, 106, 126, 136, - 152, 167, 185, 205, 225, 240, 10, 21, 36, 50, 63, 79, 95, 110, 126, 141, 157, 173, 189, 205, - 221, 237, 17, 20, 37, 51, 59, 78, 89, 107, 123, 134, 150, 164, 184, 205, 224, 240, 10, 15, 32, - 51, 67, 81, 96, 112, 129, 142, 158, 173, 189, 204, 220, 236, 8, 21, 37, 51, 65, 79, 98, 113, - 126, 138, 155, 168, 179, 192, 209, 218, 12, 15, 34, 55, 63, 78, 87, 108, 118, 131, 148, 167, - 185, 203, 219, 236, 16, 19, 32, 36, 56, 79, 91, 108, 118, 136, 154, 171, 186, 204, 220, 237, - 11, 28, 43, 58, 74, 89, 105, 120, 135, 150, 165, 180, 196, 211, 226, 241, 6, 16, 33, 46, 60, - 75, 92, 107, 123, 137, 156, 169, 185, 199, 214, 225, 11, 19, 30, 44, 57, 74, 89, 105, 121, 135, - 152, 169, 186, 202, 218, 234, 12, 19, 29, 46, 57, 71, 88, 100, 120, 132, 148, 165, 182, 199, - 216, 233, 17, 23, 35, 46, 56, 77, 92, 106, 123, 134, 152, 167, 185, 204, 222, 237, 14, 17, 45, - 53, 63, 75, 89, 107, 115, 132, 151, 171, 188, 206, 221, 240, 9, 16, 29, 40, 56, 71, 88, 103, - 119, 137, 154, 171, 189, 205, 222, 237, 16, 19, 36, 48, 57, 76, 87, 105, 118, 132, 150, 167, - 185, 202, 218, 236, 12, 17, 29, 54, 71, 81, 94, 104, 126, 136, 149, 164, 182, 201, 221, 237, - 15, 28, 47, 62, 79, 97, 115, 129, 142, 155, 168, 180, 194, 208, 223, 238, 8, 14, 30, 45, 62, - 78, 94, 111, 127, 143, 159, 175, 192, 207, 223, 239, 17, 30, 49, 62, 79, 92, 107, 119, 132, - 145, 160, 174, 190, 204, 220, 235, 14, 19, 36, 45, 61, 76, 91, 108, 121, 138, 154, 172, 189, - 205, 222, 238, 12, 18, 31, 45, 60, 76, 91, 107, 123, 138, 154, 171, 187, 204, 221, 236, 13, 17, - 31, 43, 53, 70, 83, 103, 114, 131, 149, 167, 185, 203, 220, 237, 17, 22, 35, 42, 58, 78, 93, - 110, 125, 139, 155, 170, 188, 206, 224, 240, 8, 15, 34, 50, 67, 83, 99, 115, 131, 146, 162, - 178, 193, 209, 224, 239, 13, 16, 41, 66, 73, 86, 95, 111, 128, 137, 150, 163, 183, 206, 225, - 241, 17, 25, 37, 52, 63, 75, 92, 102, 119, 132, 144, 160, 175, 191, 212, 231, 19, 31, 49, 65, - 83, 100, 117, 133, 147, 161, 174, 187, 200, 213, 227, 242, 18, 31, 52, 68, 88, 103, 117, 126, - 138, 149, 163, 177, 192, 207, 223, 239, 16, 29, 47, 61, 76, 90, 106, 119, 133, 147, 161, 176, - 193, 209, 224, 240, 15, 21, 35, 50, 61, 73, 86, 97, 110, 119, 129, 141, 175, 198, 218, 237, -]; - -const NLSF_CB1_WGHT_WB_Q9: [i16; 512] = [ - 3657, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2925, 2963, 2963, 2925, 2846, - 3216, 3085, 2972, 3056, 3056, 3010, 3010, 3010, 2963, 2963, 3010, 2972, 2888, 2846, 2846, 2726, - 3920, 4014, 2981, 3207, 3207, 2934, 3056, 2846, 3122, 3244, 2925, 2846, 2620, 2553, 2780, 2925, - 3516, 3197, 3010, 3103, 3019, 2888, 2925, 2925, 2925, 2925, 2888, 2888, 2888, 2888, 2888, 2753, - 5054, 5054, 2934, 3573, 3385, 3056, 3085, 2793, 3160, 3160, 2972, 2846, 2513, 2540, 2753, 2888, - 4428, 4149, 2700, 2753, 2972, 3010, 2925, 2846, 2981, 3019, 2925, 2925, 2925, 2925, 2888, 2726, - 3620, 3019, 2972, 3056, 3056, 2873, 2806, 3056, 3216, 3047, 2981, 3291, 3291, 2981, 3310, 2991, - 5227, 5014, 2540, 3338, 3526, 3385, 3197, 3094, 3376, 2981, 2700, 2647, 2687, 2793, 2846, 2673, - 5081, 5174, 4615, 4428, 2460, 2897, 3047, 3207, 3169, 2687, 2740, 2888, 2846, 2793, 2846, 2700, - 3122, 2888, 2963, 2925, 2925, 2925, 2925, 2963, 2963, 2963, 2963, 2925, 2925, 2963, 2963, 2963, - 4202, 3207, 2981, 3103, 3010, 2888, 2888, 2925, 2972, 2873, 2916, 3019, 2972, 3010, 3197, 2873, - 3760, 3760, 3244, 3103, 2981, 2888, 2925, 2888, 2972, 2934, 2793, 2793, 2846, 2888, 2888, 2660, - 3854, 4014, 3207, 3122, 3244, 2934, 3047, 2963, 2963, 3085, 2846, 2793, 2793, 2793, 2793, 2580, - 3845, 4080, 3357, 3516, 3094, 2740, 3010, 2934, 3122, 3085, 2846, 2846, 2647, 2647, 2846, 2806, - 5147, 4894, 3225, 3845, 3441, 3169, 2897, 3413, 3451, 2700, 2580, 2673, 2740, 2846, 2806, 2753, - 4109, 3789, 3291, 3160, 2925, 2888, 2888, 2925, 2793, 2740, 2793, 2740, 2793, 2846, 2888, 2806, - 5081, 5054, 3047, 3545, 3244, 3056, 3085, 2944, 3103, 2897, 2740, 2740, 2740, 2846, 2793, 2620, - 4309, 4309, 2860, 2527, 3207, 3376, 3376, 3075, 3075, 3376, 3056, 2846, 2647, 2580, 2726, 2753, - 3056, 2916, 2806, 2888, 2740, 2687, 2897, 3103, 3150, 3150, 3216, 3169, 3056, 3010, 2963, 2846, - 4375, 3882, 2925, 2888, 2846, 2888, 2846, 2846, 2888, 2888, 2888, 2846, 2888, 2925, 2888, 2846, - 2981, 2916, 2916, 2981, 2981, 3056, 3122, 3216, 3150, 3056, 3010, 2972, 2972, 2972, 2925, 2740, - 4229, 4149, 3310, 3347, 2925, 2963, 2888, 2981, 2981, 2846, 2793, 2740, 2846, 2846, 2846, 2793, - 4080, 4014, 3103, 3010, 2925, 2925, 2925, 2888, 2925, 2925, 2846, 2846, 2846, 2793, 2888, 2780, - 4615, 4575, 3169, 3441, 3207, 2981, 2897, 3038, 3122, 2740, 2687, 2687, 2687, 2740, 2793, 2700, - 4149, 4269, 3789, 3657, 2726, 2780, 2888, 2888, 3010, 2972, 2925, 2846, 2687, 2687, 2793, 2888, - 4215, 3554, 2753, 2846, 2846, 2888, 2888, 2888, 2925, 2925, 2888, 2925, 2925, 2925, 2963, 2888, - 5174, 4921, 2261, 3432, 3789, 3479, 3347, 2846, 3310, 3479, 3150, 2897, 2460, 2487, 2753, 2925, - 3451, 3685, 3122, 3197, 3357, 3047, 3207, 3207, 2981, 3216, 3085, 2925, 2925, 2687, 2540, 2434, - 2981, 3010, 2793, 2793, 2740, 2793, 2846, 2972, 3056, 3103, 3150, 3150, 3150, 3103, 3010, 3010, - 2944, 2873, 2687, 2726, 2780, 3010, 3432, 3545, 3357, 3244, 3056, 3010, 2963, 2925, 2888, 2846, - 3019, 2944, 2897, 3010, 3010, 2972, 3019, 3103, 3056, 3056, 3010, 2888, 2846, 2925, 2925, 2888, - 3920, 3967, 3010, 3197, 3357, 3216, 3291, 3291, 3479, 3704, 3441, 2726, 2181, 2460, 2580, 2607, -]; - -const NLSF_PRED_WB_Q8: [u8; 30] = [ - 175, 148, 160, 176, 178, 173, 174, 164, 177, 174, 196, 182, 198, 192, 182, 68, 62, 66, 60, 72, - 117, 85, 90, 118, 136, 151, 142, 160, 142, 155, -]; - -const NLSF_DELTA_MIN_WB_Q15: [i16; 17] = - [100, 3, 40, 3, 3, 3, 5, 14, 14, 10, 11, 3, 8, 9, 7, 3, 347]; - -/// Narrowband/mediumband NLSF codebook. -pub(super) const NLSF_CB_NB_MB: NlsfCodebook = NlsfCodebook { - order: 10, - quant_step_size_q16: 11_796, - cb1_nlsf_q8: &NLSF_CB1_NB_MB_Q8, - cb1_wght_q9: &NLSF_CB1_WGHT_NB_MB_Q9, - pred_q8: &NLSF_PRED_NB_MB_Q8, - ec_sel: &super::entropy_tables::NLSF_CB2_SELECT_NB_MB, - delta_min_q15: &NLSF_DELTA_MIN_NB_MB_Q15, -}; - -/// Wideband NLSF codebook. -pub(super) const NLSF_CB_WB: NlsfCodebook = NlsfCodebook { - order: 16, - quant_step_size_q16: 9_830, - cb1_nlsf_q8: &NLSF_CB1_WB_Q8, - cb1_wght_q9: &NLSF_CB1_WGHT_WB_Q9, - pred_q8: &NLSF_PRED_WB_Q8, - ec_sel: &super::entropy_tables::NLSF_CB2_SELECT_WB, - delta_min_q15: &NLSF_DELTA_MIN_WB_Q15, -}; - -/// Quantization offsets in Q10 indexed by `[signal_type >> 1][quant_offset_type]`. -pub(super) const QUANTIZATION_OFFSETS_Q10: [[i32; 2]; 2] = [ - [OFFSET_UVL_Q10, OFFSET_UVH_Q10], - [OFFSET_VL_Q10, OFFSET_VH_Q10], -]; - -/// LTP scaling factors in Q14 indexed by `ltp_scale_index`. -pub(super) const LTP_SCALES_Q14: [i16; 3] = [15_565, 12_288, 8_192]; - -/// 20 ms pitch contour table for NB stage-2. -pub(super) const CB_LAGS_STAGE2_NB: [[i8; PE_NB_CBKS_STAGE2_EXT]; MAX_NB_SUBFR] = [ - [0, 2, -1, -1, -1, 0, 0, 1, 1, 0, 1], - [0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0], - [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0], - [0, -1, 2, 1, 0, 1, 1, 0, 0, -1, -1], -]; - -/// 20 ms pitch contour table for MB/WB stage-3. -pub(super) const CB_LAGS_STAGE3_WB: [[i8; PE_NB_CBKS_STAGE3_MAX]; MAX_NB_SUBFR] = [ - [ - 0, 0, 1, -1, 0, 1, -1, 0, -1, 1, -2, 2, -2, -2, 2, -3, 2, 3, -3, -4, 3, -4, 4, 4, -5, 5, - -6, -5, 6, -7, 6, 5, 8, -9, - ], - [ - 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, -1, 1, 0, 0, 1, -1, 0, 1, -1, -1, 1, -1, 2, 1, -1, 2, -2, -2, - 2, -2, 2, 2, 3, -3, - ], - [ - 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, -1, 1, 0, 0, 2, 1, -1, 2, -1, -1, 2, -1, 2, 2, - -1, 3, -2, -2, -2, 3, - ], - [ - 0, 1, 0, 0, 1, 0, 1, -1, 2, -1, 2, -1, 2, 3, -2, 3, -2, -2, 4, 4, -3, 5, -3, -4, 6, -4, 6, - 5, -5, 8, -6, -5, -7, 9, - ], -]; - -/// 10 ms pitch contour table for NB stage-2. -pub(super) const CB_LAGS_STAGE2_10MS_NB: [[i8; PE_NB_CBKS_STAGE2_10MS]; 2] = [[0, 1, 0], [0, 0, 1]]; - -/// 10 ms pitch contour table for MB/WB stage-3. -pub(super) const CB_LAGS_STAGE3_10MS_WB: [[i8; PE_NB_CBKS_STAGE3_10MS]; 2] = [ - [0, 0, 1, -1, 1, -1, 2, -2, 2, -2, 3, -3], - [0, 1, 0, 1, -1, 2, -1, 2, -2, 3, -2, 3], -]; - -/// LTP VQ codebook 0 in Q7. -pub(super) const LTP_VQ_0_Q7: [[i8; LTP_ORDER]; 8] = [ - [4, 6, 24, 7, 5], - [0, 0, 2, 0, 0], - [12, 28, 41, 13, -4], - [-9, 15, 42, 25, 14], - [1, -2, 62, 41, -9], - [-10, 37, 65, -4, 3], - [-6, 4, 66, 7, -8], - [16, 14, 38, -3, 33], -]; - -/// LTP VQ codebook 1 in Q7. -pub(super) const LTP_VQ_1_Q7: [[i8; LTP_ORDER]; 16] = [ - [13, 22, 39, 23, 12], - [-1, 36, 64, 27, -6], - [-7, 10, 55, 43, 17], - [1, 1, 8, 1, 1], - [6, -11, 74, 53, -9], - [-12, 55, 76, -12, 8], - [-3, 3, 93, 27, -4], - [26, 39, 59, 3, -8], - [2, 0, 77, 11, 9], - [-8, 22, 44, -6, 7], - [40, 9, 26, 3, 9], - [-7, 20, 101, -7, 4], - [3, -8, 42, 26, 0], - [-15, 33, 68, 2, 23], - [-2, 55, 46, -2, 15], - [3, -1, 21, 16, 41], -]; - -/// LTP VQ codebook 2 in Q7. -pub(super) const LTP_VQ_2_Q7: [[i8; LTP_ORDER]; 32] = [ - [-6, 27, 61, 39, 5], - [-11, 42, 88, 4, 1], - [-2, 60, 65, 6, -4], - [-1, -5, 73, 56, 1], - [-9, 19, 94, 29, -9], - [0, 12, 99, 6, 4], - [8, -19, 102, 46, -13], - [3, 2, 13, 3, 2], - [9, -21, 84, 72, -18], - [-11, 46, 104, -22, 8], - [18, 38, 48, 23, 0], - [-16, 70, 83, -21, 11], - [5, -11, 117, 22, -8], - [-6, 23, 117, -12, 3], - [3, -8, 95, 28, 4], - [-10, 15, 77, 60, -15], - [-1, 4, 124, 2, -4], - [3, 38, 84, 24, -25], - [2, 13, 42, 13, 31], - [21, -4, 56, 46, -1], - [-1, 35, 79, -13, 19], - [-7, 65, 88, -9, -14], - [20, 4, 81, 49, -29], - [20, 0, 75, 3, -17], - [5, -9, 44, 92, -8], - [1, -3, 22, 69, 31], - [-6, 95, 41, -12, 5], - [39, 67, 16, -4, 1], - [0, -6, 120, 55, -36], - [-13, 44, 122, 4, -24], - [81, 5, 11, 3, 7], - [2, 0, 9, 10, 88], -]; - -/// Select the NLSF codebook for the current internal SILK rate. -/// -/// Params: `fs_khz` is the internal SILK sampling rate in kHz. -/// Returns: NB/MB codebook for 8/12 kHz or WB codebook for 16 kHz. -pub(super) fn nlsf_codebook(fs_khz: u32) -> &'static NlsfCodebook { - if fs_khz == 16 { - &NLSF_CB_WB - } else { - &NLSF_CB_NB_MB - } -} From 34193b88d7e0330edd3407bc7b513549df9d540c Mon Sep 17 00:00:00 2001 From: Yannick de Jong Date: Mon, 26 Oct 2026 10:34:56 +0100 Subject: [PATCH 5/6] Move to opus-rs 0.1.40 It has the FEC, concealment and encoder fixes we pinned from the fork. --- Cargo.lock | 5 +++-- Cargo.toml | 10 +++------- deny.toml | 6 ++---- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 11646d1..be54f81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4596,8 +4596,9 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "opus-rs" -version = "0.1.39" -source = "git+https://github.com/Yannicked/opus-rs?rev=58da80134013b7452e68b886b1fb028327ed2298#58da80134013b7452e68b886b1fb028327ed2298" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d664b01dda81206fd3c80582eafd8dfef0e936eea1c2ac90ed408823bbb410c" [[package]] name = "orbclient" diff --git a/Cargo.toml b/Cargo.toml index 2812fd7..5b67846 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,12 +142,13 @@ tracing = { version = "0.1.44", default-features = false, features = ["log", "st # audio processing and Opus, all pure Rust. # opus-rs: libopus 1.6 ported (BSD-3-Clause, no dependencies), encoding # what we send and decoding what we hear (0.1.39 plays a mono packet in a -# stereo decoder); its unsafe is SIMD and unchecked indexing. sonora: WebRTC's AEC3, noise +# stereo decoder; 0.1.40 decodes in-band FEC and conceals loss like +# libopus); its unsafe is SIMD and unchecked indexing. sonora: WebRTC's AEC3, noise # suppression, AGC2 and high-pass filter, M145, ported (BSD-3-Clause); # its unsafe is SIMD behind runtime CPU detection. sonora-agc2's RNN # voice detector decides what DTX leaves out; sonora-common-audio's sinc # resampler brings a 44.1 kHz microphone to 48. Both come with sonora. -opus-rs = "0.1.39" +opus-rs = "0.1.40" sonora = "0.2.0" sonora-agc2 = "0.2.0" sonora-common-audio = "0.2.0" @@ -368,11 +369,6 @@ opt-level = 2 debug = false [patch.crates-io] -# opus-rs 0.1.39 with in-band FEC decoding and libopus's packet-loss -# concealment (restsend/opus-rs PRs #67 and #68), and two encoder fixes: -# the AVX2 pulse search wrote past a silent band, and compute_theta wrote -# through a shared slice. The fork's `noslacking` branch; drop once released. -opus-rs = { git = "https://github.com/Yannicked/opus-rs", rev = "58da80134013b7452e68b886b1fb028327ed2298" } # crmne/egui apps-0.36, shared with ZapFast and Spotifast: egui 0.36.1 with # bidi shaping, egui's busy-loop fix and Wayland frame pacing. Every egui # crate comes from the same revision so they share one emath and epaint. diff --git a/deny.toml b/deny.toml index e88cbfe..3a01570 100644 --- a/deny.toml +++ b/deny.toml @@ -69,11 +69,9 @@ allow-wildcard-paths = true unknown-registry = "deny" unknown-git = "deny" allow-registry = ["https://github.com/rust-lang/crates.io-index"] -# The shared fastframe crates and the egui and winit forks they build on, -# and opus-rs with FEC and concealment until a release has them (see -# [patch.crates-io] in Cargo.toml). Nothing else may come from git. +# The shared fastframe crates and the egui and winit forks they build on. +# Nothing else may come from git. allow-git = [ - "https://github.com/Yannicked/opus-rs", "https://github.com/crmne/fastframe", "https://github.com/crmne/egui", "https://github.com/crmne/winit", From 700f073cfc6481d1106dce2d67faca92734cf41d Mon Sep 17 00:00:00 2001 From: Yannick de Jong Date: Mon, 26 Oct 2026 11:34:10 +0100 Subject: [PATCH 6/6] Update dependencies to resolve cargo-deny advisories --- Cargo.lock | 433 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 267 insertions(+), 166 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be54f81..fd22834 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -293,9 +293,9 @@ dependencies = [ [[package]] name = "arboard" -version = "3.6.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +checksum = "aeb9553e62312c8756a5979254fc6d7da8bbeb5986a1e17037fab932f7037917" dependencies = [ "clipboard-win", "image", @@ -307,11 +307,20 @@ dependencies = [ "objc2-foundation 0.3.2", "parking_lot", "percent-encoding", - "windows-sys 0.60.2", + "windows-sys 0.61.2", "wl-clipboard-rs", "x11rb", ] +[[package]] +name = "arc-swap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380962b3d93b99642e50b9febf72c071e97e8b32d86eae9cf4ef582bb960ae6e" +dependencies = [ + "rustversion", +] + [[package]] name = "arrayref" version = "0.3.9" @@ -370,9 +379,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.50" +version = "0.4.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee19bd99b43e3691acbad4e840420a4881cea6c0b66a208125a824f8fd53f5a1" +checksum = "9d5755640120b24961f8231e05892e4a4f7e0d04e8aeca161508c9c397946970" dependencies = [ "compression-codecs", "compression-core", @@ -449,7 +458,7 @@ checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -484,7 +493,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -714,7 +723,7 @@ checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -766,18 +775,42 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "calloop" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b32313e937c8721c9d17ee9bf60699a6402b4b7c2cb6ac1bee0ffcaa4b4e5e" +dependencies = [ + "bitflags 2.13.2", + "polling", + "rustix 1.1.5", + "tracing", +] + [[package]] name = "calloop-wayland-source" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ - "calloop", + "calloop 0.13.0", "rustix 0.38.44", "wayland-backend", "wayland-client", ] +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop 0.14.5", + "rustix 1.1.5", + "wayland-backend", + "wayland-client", +] + [[package]] name = "cbc" version = "0.2.1" @@ -789,9 +822,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.6.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630" +checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0" dependencies = [ "find-msvc-tools", "jobserver", @@ -899,9 +932,9 @@ dependencies = [ [[package]] name = "clipboard-win" -version = "5.4.1" +version = "5.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +checksum = "052af7878466feccda12ba7a6a67d019d712f296809559b47c12c8cc196d3a9a" dependencies = [ "error-code", ] @@ -981,9 +1014,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "combine" -version = "4.6.8" +version = "4.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92" dependencies = [ "bytes", "memchr", @@ -991,9 +1024,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.45" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98fc98460ba0ad5317075d3632b8dfc45d0be8c4a49347c2a38272019717614a" +checksum = "cae36683101b2899f48bd31f8db9839437356188e3b1ff23096e73dab09ba8fe" dependencies = [ "compression-core", "flate2", @@ -1002,9 +1035,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" +checksum = "2ebdda89edc534bf8d5dd2857562633b6495557e5846552ade994b99ed13c36a" [[package]] name = "concat-string" @@ -1229,9 +1262,9 @@ checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc32fast" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055" dependencies = [ "cfg-if 1.0.5", ] @@ -1254,6 +1287,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.23" @@ -1298,9 +1340,9 @@ dependencies = [ [[package]] name = "ctutils" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03bb0e1cc970d482d121d9a1744999169b69a07470b3d644a7894e53fcaf4574" +checksum = "fe3e9d21d39e6f39ff46e03d5b621424f88ee23d8a1672c6bfc9ccc7f2da4f6a" dependencies = [ "cmov", ] @@ -1337,9 +1379,9 @@ checksum = "7eed2c4702fa172d1ce21078faa7c5203e69f5394d48cc436d25928394a867a2" [[package]] name = "defmt" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +checksum = "e4a0795ac740cb54d7b3775c9c17345f6aaecf884c01733b39d6017738e0e3e1" dependencies = [ "bitflags 1.3.2", "defmt-macros", @@ -1347,30 +1389,30 @@ dependencies = [ [[package]] name = "defmt-macros" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +checksum = "840bb056bd99c495c9188198a258ab787c79f306d864db3e6e8451442d6d8366" dependencies = [ "defmt-parser", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.9", ] [[package]] name = "defmt-parser" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +checksum = "71958f69fb2de856c5dd51ac9377e986ca1aa5919f931bf5395a807277f889a0" dependencies = [ "thiserror 2.0.21", ] [[package]] name = "deranged" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +checksum = "9e9de72ce2ad1f90dc62fa25f0f430ef85eb4b0d8fa0be4f30373bc40a21d28e" [[package]] name = "derive_more" @@ -1408,9 +1450,9 @@ dependencies = [ [[package]] name = "dimpl" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b8184e6c079d7c35d10649505f6788117dae289585e3ae86764bf48dc9c970" +checksum = "0e769119713c7cf11efb6dd594258095c4a5959fa2bb8d4a7f89978efbd0c08b" dependencies = [ "arrayvec", "log", @@ -1468,7 +1510,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -1713,9 +1755,9 @@ dependencies = [ [[package]] name = "emojis" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1514ced566c94991ed9563258ecf61e311fd773bf0a3f20606830386bf66e3" +checksum = "589871e639330783bc20ad89d32ac1b049e02c273d96c907ca27478c8bc674a2" dependencies = [ "phf 0.13.1", ] @@ -1848,9 +1890,9 @@ source = "git+https://github.com/crmne/egui?rev=ba6790fe7cf46e58e8d27ce1524cbfde [[package]] name = "equivalent" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" [[package]] name = "errno" @@ -2060,9 +2102,9 @@ checksum = "b97b65636e5b9ef369943878ac74335ba1c55c1cb6adbf1e2c293c624248d693" [[package]] name = "find-msvc-tools" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" [[package]] name = "fixedbitset" @@ -2119,9 +2161,9 @@ checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" [[package]] name = "font-types" -version = "0.12.6" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c92d9081c7ab3bd83ef1eecdfad090f3d966e3ce74106779664114cfe1a6274" +checksum = "835ae9d781464a79026cde387f9cbc650c9a4909d7cf7e658b127e3c35d2f35d" dependencies = [ "bytemuck", "serde", @@ -2154,7 +2196,7 @@ checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -2178,15 +2220,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - [[package]] name = "futures-channel" version = "0.3.34" @@ -2229,7 +2262,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -2700,9 +2733,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +checksum = "fe090a42a0d3140fed8c63263ed1fecc02422cb2f4bfe55cd8de2de1b8e105dd" dependencies = [ "displaydoc", "potential_utf", @@ -2714,9 +2747,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +checksum = "25d29b57c17c6528d0dc4c2141cfa2d08200465acb537a3909c8b9b8a4a984c0" dependencies = [ "displaydoc", "litemap", @@ -2727,9 +2760,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +checksum = "404a3b8c486f205c87fe7f23ab6894d84a4ad76d6e4671ae1981ba7def87c599" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -2741,15 +2774,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" +checksum = "20f0088bfb7441f7a37fa0c9e20cb1e9cba1cac6b9ce8cd2a6dc44294f8e8cbc" [[package]] name = "icu_properties" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +checksum = "2ea83d991c56158f7cf898bf86436c1dfe7c07ee82fe88951f2695c9f37a379f" dependencies = [ "displaydoc", "icu_collections", @@ -2762,15 +2795,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" +checksum = "b98f29af82d02b163fe50161388350e743bb3107cc8e8c2f7314cb505faddfcf" [[package]] name = "icu_provider" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +checksum = "413099315636c588ee8f011190b329b971a09a8fae9d157e01dcae8379468514" dependencies = [ "displaydoc", "icu_locale_core", @@ -2881,9 +2914,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +checksum = "3b5cf4372d022092674a83b8febf5865f7a573cc0c5baec5e49c879b6a20978f" dependencies = [ "block-padding", "hybrid-array", @@ -3127,9 +3160,9 @@ dependencies = [ [[package]] name = "keyring-core" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb1e621458ca9c51aa110bd0339d4751a056b9576bf1253aee1aa560dda0fc9d" +checksum = "29fbf4b551298c67925f38cf341a1ada797a75f1837bdf8ae1fc8e2ebc6efb59" dependencies = [ "log", ] @@ -3172,10 +3205,12 @@ dependencies = [ [[package]] name = "ksni" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "814b44c24cd2cb236c3b8a41c7f08237b452a8e76ecaa81f1cec40b5b678215b" +checksum = "1bcc1cb43797140f74b711f1c4a49f48a7a7429f211c5fb78264e7bcd1e99caf" dependencies = [ + "arc-swap", + "crossbeam-queue", "futures-util", "pastey", "serde", @@ -3220,9 +3255,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.190" +version = "0.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" [[package]] name = "libloading" @@ -3249,7 +3284,7 @@ dependencies = [ "bitflags 2.13.2", "libc", "plain", - "redox_syscall 0.9.4", + "redox_syscall 0.9.5", ] [[package]] @@ -3341,9 +3376,9 @@ checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "litrs" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +checksum = "4744e383959f0db86ede514b809b1c53251889093803c05267acc7d4e7030d70" [[package]] name = "lock_api" @@ -3416,9 +3451,9 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memmap2" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +checksum = "6b0ca1155a9ecc25aa12e8313a119f32edf0255fea2f44839c4ac0a12b2ac157" dependencies = [ "libc", ] @@ -3843,20 +3878,21 @@ version = "0.1.0" [[package]] name = "notify" -version = "8.2.0" +version = "8.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" +checksum = "82f812702fbc75d35416d3f884e04925be7e9498f4eceea2e24ba34559284674" dependencies = [ "bitflags 2.13.2", - "fsevent-sys", "inotify", "kqueue", "libc", "log", "mio", "notify-types", + "objc2-core-foundation", + "objc2-core-services", "walkdir", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4273,6 +4309,16 @@ dependencies = [ "objc2-core-video", ] +[[package]] +name = "objc2-core-services" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583300ad934cba24ff5292aee751ecc070f7ca6b39a574cc21b7b5e588e06a0b" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "objc2-core-text" version = "0.3.2" @@ -4527,9 +4573,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "open" -version = "5.4.4" +version = "5.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc" +checksum = "8d8c8b8e725ad26404d6896eb2f064294a0a7b4f1d9120210d85544684c4ca95" dependencies = [ "is-wsl", "libc", @@ -5260,9 +5306,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e" +checksum = "580c875845b41ecb1564ad1ae4703044cef774145e3d7a94dc2b3fa7c97868fd" dependencies = [ "bitflags 2.13.2", ] @@ -5515,9 +5561,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -5537,9 +5583,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.15" +version = "0.103.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +checksum = "4443b1b6d39d7c663139942cc4558262562d2c84123bb6282ef177009ca8b281" dependencies = [ "ring", "rustls-pki-types", @@ -5554,15 +5600,15 @@ checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "rusty_h264-accel" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18110c8f692668ccf004393bee04e369d4f0f173dd0ea4b515080258e6f9c599" +checksum = "ecb2772a6bc2a2ca811ba4bff42cc0e10f25b411bc4ca1793c90f3265566f609" [[package]] name = "rusty_h264-common" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf982ae23ecf8f13015ec1c5576fe2935f4e4296b097ebc4233b5872623af2a" +checksum = "5b9ab840d02dd582db8dc0a2f5e68fa75e222246ba378af567b578098c2b4cfc" dependencies = [ "bytemuck", "once_cell", @@ -5573,18 +5619,18 @@ dependencies = [ [[package]] name = "rusty_h264-decoder" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4deb1a3a5e928cbaa1dff7db6bc46262c850aedeb75cb5aeab890494c18a5" +checksum = "f08d16e3c51b0b190f731c8a214ade76a1ac30b94ba3933f93b740dd909f8155" dependencies = [ "rusty_h264-common", ] [[package]] name = "rusty_h264-encoder" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e037f6341a02f54021f7077735bd12e2fd61a749e1ee7248eee00466ef5fa4ba" +checksum = "99277b8dcb73a794d7084ee17fa4953ac9845b568d350ceede534fa2984a631e" dependencies = [ "rusty_h264-accel", "rusty_h264-common", @@ -5616,9 +5662,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +checksum = "9cb23c424d8ee841bb4d813677ca237e2a610067d594fec130f87d6093495ab5" dependencies = [ "windows-sys 0.61.2", ] @@ -5637,9 +5683,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sctp-proto" -version = "0.10.5" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "250132c5814c3d4f01269ee7a261450bc38dd5119280262d3c13a40062cfde55" +checksum = "bba218fe5119005820ab5aa74ffae0f1267763ae242541f5787ef42427cff262" dependencies = [ "bytes", "crc", @@ -5731,14 +5777,14 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] name = "serde_json" -version = "1.0.151" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ "itoa", "memchr", @@ -5755,7 +5801,7 @@ checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -5792,9 +5838,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +checksum = "47d7069beb7d6ac7b9acd1039986e73443f24234f41074da099d6f994ac9ad19" dependencies = [ "cfg-if 1.0.5", "cpufeatures", @@ -5887,9 +5933,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.16.2" +version = "1.16.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" dependencies = [ "serde", ] @@ -5901,8 +5947,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ "bitflags 2.13.2", - "calloop", - "calloop-wayland-source", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", "cursor-icon", "libc", "log", @@ -5919,14 +5965,41 @@ dependencies = [ "xkeysym", ] +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.13.2", + "calloop 0.14.5", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.1.5", + "thiserror 2.0.21", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + [[package]] name = "smithay-clipboard" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" dependencies = [ "libc", - "smithay-client-toolkit", + "smithay-client-toolkit 0.20.0", "wayland-backend", ] @@ -6287,9 +6360,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.6" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" dependencies = [ "proc-macro2", "quote", @@ -6313,7 +6386,7 @@ checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -6426,7 +6499,7 @@ checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -6501,9 +6574,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +checksum = "abc840c238154d58f9e86b2792261edfde565a17696e1cda1a449de27979801a" dependencies = [ "displaydoc", "zerovec", @@ -6517,9 +6590,9 @@ checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" [[package]] name = "tokio" -version = "1.53.2" +version = "1.54.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" +checksum = "b52fd3255258d26fac7264ab5a76b53c13367136dc011d3d6ad4f8ed070396a3" dependencies = [ "bytes", "libc", @@ -6540,7 +6613,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] @@ -6571,9 +6644,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" dependencies = [ "bytes", "futures-core", @@ -6787,9 +6860,9 @@ dependencies = [ [[package]] name = "typed-path" -version = "0.12.3" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" +checksum = "9ce898823d8cd0f028662d2f349694b1af0a5cc9be83f1d213a00d3147445e1e" [[package]] name = "typeid" @@ -6834,9 +6907,9 @@ checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" [[package]] name = "unicode-ident" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" [[package]] name = "unicode-segmentation" @@ -6920,9 +6993,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.27.0" +version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" +checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -7072,7 +7145,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "wasm-bindgen-shared", ] @@ -7158,6 +7231,32 @@ dependencies = [ "wayland-scanner", ] +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + [[package]] name = "wayland-protocols-plasma" version = "0.3.12" @@ -7788,7 +7887,7 @@ dependencies = [ "bitflags 2.13.2", "block2 0.5.1", "bytemuck", - "calloop", + "calloop 0.13.0", "cfg_aliases", "concurrent-queue", "core-foundation 0.9.4", @@ -7809,7 +7908,7 @@ dependencies = [ "raw-window-handle", "redox_syscall 0.4.1", "rustix 0.38.44", - "smithay-client-toolkit", + "smithay-client-toolkit 0.19.2", "smol_str", "tracing", "unicode-segmentation", @@ -7838,9 +7937,9 @@ dependencies = [ [[package]] name = "winresource" -version = "0.1.31" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" +checksum = "a2bc6d36480b829272a5bdabf08e6dca8004761c1da7e35efef800540c66221b" dependencies = [ "version_check", ] @@ -7988,9 +8087,9 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" [[package]] name = "yoke" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +checksum = "417ef3fcf6c300b7dbd25c85531a746931c02ad536f3cf4786ed1e88223340b3" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -8005,7 +8104,7 @@ checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "synstructure", ] @@ -8098,7 +8197,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "zbus_names", "zvariant", "zvariant_utils", @@ -8106,9 +8205,9 @@ dependencies = [ [[package]] name = "zbus_names" -version = "4.3.4" +version = "4.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +checksum = "812a12be4ead7a59e18ea8f047d08add0066b73bc62fc1467066a2472ae088dd" dependencies = [ "serde", "winnow", @@ -8138,18 +8237,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.62" +version = "0.8.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86502bf56ac7c77571a32e2647bb2a15894565e981fb2a48d7bde2d91c965a9d" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.62" +version = "0.8.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5457206954b06561e2608c7e19cf58b1926586d999c246eebe4502f7e2039d1a" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" dependencies = [ "proc-macro2", "quote", @@ -8173,7 +8272,7 @@ checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "synstructure", ] @@ -8196,9 +8295,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +checksum = "f58a2799cfe9a1670c7ea8edbd52ef8edb93cb37a7f83bf31ba9e35d9dae6a40" dependencies = [ "yoke", "zerofrom", @@ -8207,27 +8306,29 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +checksum = "c3743ddbf47738d929cd1ae2e199d40d0162979dfe31aef8ae646d2bf00bf97a" dependencies = [ "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", ] [[package]] name = "zip" -version = "9.0.5" +version = "9.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e04976da629d3b04204ba8105b396a193af8ad2583b2c79bfb3ab8af580a11e" +checksum = "ec2e3161de27091f18a831d9494e9c53378a34833af5c1dda35ad85849da18d6" dependencies = [ "crc32fast", "flate2", + "getrandom 0.4.3", "indexmap", "memchr", "nix", "typed-path", + "windows-sys 0.61.2", "zopfli", ] @@ -8245,9 +8346,9 @@ checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zopfli" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +checksum = "aaf7fc5d30c28483d93805c4a5e12b05bbb52407fa67c5f8bd552374cd01fb11" dependencies = [ "bumpalo", "crc32fast", @@ -8272,9 +8373,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.15.0" +version = "5.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" +checksum = "80bf6d6cd6a4b601acb64cfb8284cdc643fcfa75ef26ae3c46ec8aa4e8a1fbb7" dependencies = [ "endi", "enumflags2", @@ -8287,14 +8388,14 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.15.0" +version = "5.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" +checksum = "ef2672456ba1cb54b5eaec136e63135b3c7495fb938e53b28a62e633496d486d" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 3.0.6", + "syn 3.0.9", "zvariant_utils", ] @@ -8307,6 +8408,6 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 3.0.6", + "syn 3.0.9", "winnow", ]