1use core::cell::{Cell, RefCell};
75use core::future::Future;
76
77use crate::dm::{ArrayAttributeRead, Cluster, Dataver, EndptId, InvokeContext, ReadContext};
78use crate::error::{Error, ErrorCode};
79use crate::tlv::{TLVArray, TLVBuilderParent, ToTLVArrayBuilder, ToTLVBuilder};
80use crate::utils::storage::Vec;
81use crate::utils::sync::blocking::Mutex;
82use crate::with;
83
84pub use crate::dm::clusters::decl::camera_av_stream_management::AudioCodecEnum;
85#[allow(unused_imports)]
86pub use crate::dm::clusters::decl::camera_av_stream_management::*;
87pub use crate::dm::clusters::decl::globals::StreamUsageEnum;
88
89use super::super::decl::camera_av_stream_management as decl;
90
91#[derive(Debug, Clone, Copy)]
95#[cfg_attr(feature = "defmt", derive(defmt::Format))]
96pub struct VideoSensorParams {
97 pub sensor_width: u16,
98 pub sensor_height: u16,
99 pub max_fps: u16,
100 pub max_hdrfps: Option<u16>,
101}
102
103#[derive(Debug, Clone, Copy)]
112#[cfg_attr(feature = "defmt", derive(defmt::Format))]
113pub struct RateDistortionPoint {
114 pub codec: VideoCodecEnum,
115 pub min_resolution: (u16, u16),
116 pub min_bit_rate: u32,
117}
118
119#[derive(Debug, Clone, Copy)]
121#[cfg_attr(feature = "defmt", derive(defmt::Format))]
122pub struct AudioStream {
123 pub audio_stream_id: u16,
124 pub stream_usage: StreamUsageEnum,
125 pub audio_codec: AudioCodecEnum,
126 pub channel_count: u8,
127 pub sample_rate: u32,
128 pub bit_rate: u32,
129 pub bit_depth: u8,
130 pub reference_count: u8,
131}
132
133#[derive(Debug, Clone, Copy)]
135pub struct AudioCapabilitiesConfig<'a> {
136 pub max_channels: u8,
137 pub supported_codecs: &'a [AudioCodecEnum],
138 pub supported_sample_rates: &'a [u32],
139 pub supported_bit_depths: &'a [u8],
140}
141
142#[derive(Debug, Clone, Copy)]
150#[cfg_attr(feature = "defmt", derive(defmt::Format))]
151pub struct VideoStream {
152 pub video_stream_id: u16,
153 pub stream_usage: StreamUsageEnum,
154 pub video_codec: VideoCodecEnum,
155 pub min_frame_rate: u16,
156 pub max_frame_rate: u16,
157 pub min_width: u16,
158 pub min_height: u16,
159 pub max_width: u16,
160 pub max_height: u16,
161 pub min_bit_rate: u32,
162 pub max_bit_rate: u32,
163 pub key_frame_interval: u16,
164 pub watermark_enabled: Option<bool>,
165 pub osd_enabled: Option<bool>,
166 pub reference_count: u8,
167}
168
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
172#[cfg_attr(feature = "defmt", derive(defmt::Format))]
173pub enum CamAvError {
174 ResourceExhausted,
176 DynamicConstraint,
180 NotFound,
182 Failure,
184}
185
186impl From<CamAvError> for Error {
187 fn from(e: CamAvError) -> Self {
188 match e {
189 CamAvError::ResourceExhausted => ErrorCode::ResourceExhausted.into(),
190 CamAvError::DynamicConstraint => ErrorCode::ConstraintError.into(),
191 CamAvError::NotFound => ErrorCode::NotFound.into(),
192 CamAvError::Failure => ErrorCode::Failure.into(),
193 }
194 }
195}
196
197pub trait CameraAvStreamHooks {
205 fn allocate_video(&self, stream: &VideoStream) -> impl Future<Output = Result<(), CamAvError>>;
214
215 fn modify_video(
223 &self,
224 video_stream_id: u16,
225 watermark_enabled: Option<bool>,
226 osd_enabled: Option<bool>,
227 ) -> impl Future<Output = Result<(), CamAvError>>;
228
229 fn deallocate_video(
232 &self,
233 video_stream_id: u16,
234 ) -> impl Future<Output = Result<(), CamAvError>>;
235}
236
237impl<T> CameraAvStreamHooks for &T
238where
239 T: CameraAvStreamHooks,
240{
241 fn allocate_video(&self, stream: &VideoStream) -> impl Future<Output = Result<(), CamAvError>> {
242 (*self).allocate_video(stream)
243 }
244
245 fn modify_video(
246 &self,
247 video_stream_id: u16,
248 watermark_enabled: Option<bool>,
249 osd_enabled: Option<bool>,
250 ) -> impl Future<Output = Result<(), CamAvError>> {
251 (*self).modify_video(video_stream_id, watermark_enabled, osd_enabled)
252 }
253
254 fn deallocate_video(
255 &self,
256 video_stream_id: u16,
257 ) -> impl Future<Output = Result<(), CamAvError>> {
258 (*self).deallocate_video(video_stream_id)
259 }
260}
261
262const MAX_STREAM_USAGES: usize = 8;
266
267#[derive(Debug, Clone, Copy)]
272pub struct CameraAvStreamConfig<'a> {
273 pub max_concurrent_encoders: u8,
274 pub max_encoded_pixel_rate: u32,
275 pub sensor: VideoSensorParams,
276 pub min_viewport: (u16, u16),
279 pub max_content_buffer_size: u32,
281 pub max_network_bandwidth: u32,
283 pub supported_stream_usages: &'a [StreamUsageEnum],
287 pub default_stream_usage_priorities: &'a [StreamUsageEnum],
290 pub rate_distortion_points: &'a [RateDistortionPoint],
293 pub mic_capabilities: Option<AudioCapabilitiesConfig<'a>>,
298}
299
300struct State<const NV: usize, const NA: usize> {
301 videos: Vec<VideoStream, NV>,
302 audios: Vec<AudioStream, NA>,
303 stream_usage_priorities: Vec<StreamUsageEnum, MAX_STREAM_USAGES>,
304}
305
306impl<const NV: usize, const NA: usize> State<NV, NA> {
307 const fn new() -> Self {
308 Self {
309 videos: Vec::new(),
310 audios: Vec::new(),
311 stream_usage_priorities: Vec::new(),
312 }
313 }
314
315 fn find_video_mut(&mut self, id: u16) -> Result<&mut VideoStream, Error> {
316 self.videos
317 .iter_mut()
318 .find(|s| s.video_stream_id == id)
319 .ok_or_else(|| ErrorCode::NotFound.into())
320 }
321}
322
323pub struct CameraAvStreamHandler<'a, H, const NV: usize, const NA: usize = 0>
327where
328 H: CameraAvStreamHooks,
329{
330 dataver: Dataver,
331 endpoint_id: EndptId,
332 config: CameraAvStreamConfig<'a>,
333 features: u32,
337 hooks: H,
338 state: Mutex<RefCell<State<NV, NA>>>,
339 next_id: Mutex<Cell<u16>>,
340 next_audio_id: Mutex<Cell<u16>>,
341}
342
343impl<'a, H, const NV: usize, const NA: usize> CameraAvStreamHandler<'a, H, NV, NA>
344where
345 H: CameraAvStreamHooks,
346{
347 pub const CLUSTER: Cluster<'static> = decl::FULL_CLUSTER
354 .with_revision(1)
355 .with_features(decl::Feature::VIDEO.bits())
356 .with_attrs(with!(
357 required;
358 AttributeId::MaxConcurrentEncoders
359 | AttributeId::MaxEncodedPixelRate
360 | AttributeId::VideoSensorParams
361 | AttributeId::MinViewportResolution
362 | AttributeId::RateDistortionTradeOffPoints
363 | AttributeId::AllocatedVideoStreams
364 ))
365 .with_cmds(with!(
366 decl::CommandId::VideoStreamAllocate
367 | decl::CommandId::VideoStreamModify
368 | decl::CommandId::VideoStreamDeallocate
369 | decl::CommandId::SetStreamPriorities
370 ));
371
372 pub const CLUSTER_VIDEO_AUDIO: Cluster<'static> = decl::FULL_CLUSTER
377 .with_revision(1)
378 .with_features(decl::Feature::VIDEO.bits() | decl::Feature::AUDIO.bits())
379 .with_attrs(with!(
380 required;
381 AttributeId::MaxConcurrentEncoders
382 | AttributeId::MaxEncodedPixelRate
383 | AttributeId::VideoSensorParams
384 | AttributeId::MinViewportResolution
385 | AttributeId::RateDistortionTradeOffPoints
386 | AttributeId::AllocatedVideoStreams
387 | AttributeId::MicrophoneCapabilities
388 | AttributeId::AllocatedAudioStreams
389 ))
390 .with_cmds(with!(
391 decl::CommandId::VideoStreamAllocate
392 | decl::CommandId::VideoStreamModify
393 | decl::CommandId::VideoStreamDeallocate
394 | decl::CommandId::SetStreamPriorities
395 ));
396
397 pub const fn new(
409 dataver: Dataver,
410 endpoint_id: EndptId,
411 config: CameraAvStreamConfig<'a>,
412 features: u32,
413 hooks: H,
414 ) -> Self {
415 core::assert!(
418 (features & decl::Feature::AUDIO.bits()) == 0 || config.mic_capabilities.is_some(),
419 "CameraAvStreamHandler: AUDIO feature requires `config.mic_capabilities` to be Some",
420 );
421 Self {
422 dataver,
423 endpoint_id,
424 config,
425 features,
426 hooks,
427 state: Mutex::new(RefCell::new(State::new())),
428 next_id: Mutex::new(Cell::new(1)),
429 next_audio_id: Mutex::new(Cell::new(1)),
430 }
431 }
432
433 pub const fn adapt(self) -> decl::HandlerAsyncAdaptor<Self> {
436 decl::HandlerAsyncAdaptor(self)
437 }
438
439 pub const fn endpoint_id(&self) -> EndptId {
441 self.endpoint_id
442 }
443
444 pub fn acquire_video(&self, video_stream_id: u16) -> Result<(), Error> {
451 let changed = self.state.lock(|cell| -> Result<bool, Error> {
452 let mut state = cell.borrow_mut();
453 let row = state.find_video_mut(video_stream_id)?;
454 row.reference_count = row.reference_count.saturating_add(1);
455 Ok(true)
456 })?;
457 if changed {
458 self.dataver.changed();
459 }
460 Ok(())
461 }
462
463 pub fn release_video(&self, video_stream_id: u16) {
468 let changed = self.state.lock(|cell| {
469 let mut state = cell.borrow_mut();
470 if let Some(row) = state
471 .videos
472 .iter_mut()
473 .find(|s| s.video_stream_id == video_stream_id)
474 {
475 row.reference_count = row.reference_count.saturating_sub(1);
476 true
477 } else {
478 false
479 }
480 });
481 if changed {
482 self.dataver.changed();
483 }
484 }
485
486 pub fn video_streams(&self) -> Vec<VideoStream, NV> {
490 self.state.lock(|cell| cell.borrow().videos.clone())
491 }
492
493 pub fn add_preallocated_video(&self, mut stream: VideoStream) -> Result<u16, Error> {
506 stream.reference_count = 0;
507 stream.video_stream_id = self.alloc_video_id();
508 let id = stream.video_stream_id;
509 let pushed = self.state.lock(|cell| {
510 let mut state = cell.borrow_mut();
511 state.videos.push(stream).is_ok()
512 });
513 if !pushed {
514 return Err(ErrorCode::ResourceExhausted.into());
515 }
516 self.dataver.changed();
517 Ok(id)
518 }
519
520 pub fn audio_streams(&self) -> Vec<AudioStream, NA> {
522 self.state.lock(|cell| cell.borrow().audios.clone())
523 }
524
525 pub fn add_preallocated_audio(&self, mut stream: AudioStream) -> Result<u16, Error> {
531 stream.reference_count = 0;
532 stream.audio_stream_id = self.alloc_audio_id();
533 let id = stream.audio_stream_id;
534 let pushed = self.state.lock(|cell| {
535 let mut state = cell.borrow_mut();
536 state.audios.push(stream).is_ok()
537 });
538 if !pushed {
539 return Err(ErrorCode::ResourceExhausted.into());
540 }
541 self.dataver.changed();
542 Ok(id)
543 }
544
545 fn alloc_video_id(&self) -> u16 {
551 self.next_id.lock(|cell| {
552 let mut id = cell.get();
553 if id == 0 {
554 id = 1;
555 }
556 cell.set(id.wrapping_add(1).max(1));
557 id
558 })
559 }
560
561 fn alloc_audio_id(&self) -> u16 {
562 self.next_audio_id.lock(|cell| {
563 let mut id = cell.get();
564 if id == 0 {
565 id = 1;
566 }
567 cell.set(id.wrapping_add(1).max(1));
568 id
569 })
570 }
571
572 fn ensure_priorities_seeded(&self) {
575 self.state.lock(|cell| {
576 let mut state = cell.borrow_mut();
577 if state.stream_usage_priorities.is_empty() {
578 for u in self.config.default_stream_usage_priorities {
579 let _ = state.stream_usage_priorities.push(*u);
580 }
581 }
582 });
583 }
584
585 fn has_feature(&self, bit: u32) -> bool {
586 self.features & bit != 0
587 }
588
589 fn validate_video_alloc(
593 &self,
594 request: &VideoStreamAllocateRequest<'_>,
595 ) -> Result<VideoStream, Error> {
596 let stream_usage = request.stream_usage()?;
597 let video_codec = request.video_codec()?;
598 let min_frame_rate = request.min_frame_rate()?;
599 let max_frame_rate = request.max_frame_rate()?;
600 let min_resolution = request.min_resolution()?;
601 let max_resolution = request.max_resolution()?;
602 let min_bit_rate = request.min_bit_rate()?;
603 let max_bit_rate = request.max_bit_rate()?;
604 let key_frame_interval = request.key_frame_interval()?;
605 let watermark = request.watermark_enabled()?;
606 let osd = request.osd_enabled()?;
607
608 let min_w = min_resolution.width()?;
609 let min_h = min_resolution.height()?;
610 let max_w = max_resolution.width()?;
611 let max_h = max_resolution.height()?;
612
613 if matches!(stream_usage, StreamUsageEnum::Internal) {
615 return Err(ErrorCode::ConstraintError.into());
616 }
617
618 if !self.config.supported_stream_usages.contains(&stream_usage) {
620 return Err(ErrorCode::ConstraintError.into());
621 }
622
623 let in_priorities = self.state.lock(|cell| {
626 cell.borrow()
627 .stream_usage_priorities
628 .contains(&stream_usage)
629 });
630 if !in_priorities {
631 return Err(ErrorCode::InvalidAction.into());
632 }
633
634 if !self
638 .config
639 .rate_distortion_points
640 .iter()
641 .any(|p| p.codec == video_codec)
642 {
643 return Err(ErrorCode::ConstraintError.into());
644 }
645
646 if min_frame_rate == 0
648 || min_frame_rate > max_frame_rate
649 || min_bit_rate > max_bit_rate
650 || min_w == 0
651 || min_h == 0
652 || min_w > max_w
653 || min_h > max_h
654 {
655 return Err(ErrorCode::ConstraintError.into());
656 }
657
658 if max_frame_rate > self.config.sensor.max_fps {
660 return Err(ErrorCode::ConstraintError.into());
661 }
662
663 if max_w > self.config.sensor.sensor_width
665 || max_h > self.config.sensor.sensor_height
666 || min_w < self.config.min_viewport.0
667 || min_h < self.config.min_viewport.1
668 {
669 return Err(ErrorCode::ConstraintError.into());
670 }
671
672 if watermark.is_some() && !self.has_feature(decl::Feature::WATERMARK.bits()) {
675 return Err(ErrorCode::ConstraintError.into());
676 }
677 if osd.is_some() && !self.has_feature(decl::Feature::ON_SCREEN_DISPLAY.bits()) {
678 return Err(ErrorCode::ConstraintError.into());
679 }
680
681 Ok(VideoStream {
682 video_stream_id: 0, stream_usage,
684 video_codec,
685 min_frame_rate,
686 max_frame_rate,
687 min_width: min_w,
688 min_height: min_h,
689 max_width: max_w,
690 max_height: max_h,
691 min_bit_rate,
692 max_bit_rate,
693 key_frame_interval,
694 watermark_enabled: watermark,
695 osd_enabled: osd,
696 reference_count: 0,
697 })
698 }
699
700 fn find_matching_existing(&self, candidate: &VideoStream) -> Option<u16> {
705 self.state.lock(|cell| {
706 cell.borrow().videos.iter().find_map(|s| {
707 if s.stream_usage == candidate.stream_usage
708 && s.video_codec == candidate.video_codec
709 && s.min_frame_rate == candidate.min_frame_rate
710 && s.max_frame_rate == candidate.max_frame_rate
711 && s.min_width == candidate.min_width
712 && s.min_height == candidate.min_height
713 && s.max_width == candidate.max_width
714 && s.max_height == candidate.max_height
715 && s.min_bit_rate == candidate.min_bit_rate
716 && s.max_bit_rate == candidate.max_bit_rate
717 && s.key_frame_interval == candidate.key_frame_interval
718 && s.watermark_enabled == candidate.watermark_enabled
719 && s.osd_enabled == candidate.osd_enabled
720 {
721 Some(s.video_stream_id)
722 } else {
723 None
724 }
725 })
726 })
727 }
728}
729
730impl<'a, H, const NV: usize, const NA: usize> ClusterAsyncHandler
731 for CameraAvStreamHandler<'a, H, NV, NA>
732where
733 H: CameraAvStreamHooks,
734{
735 const CLUSTER: Cluster<'static> = Self::CLUSTER;
736
737 fn dataver(&self) -> u32 {
738 self.dataver.get()
739 }
740
741 fn dataver_changed(&self) {
742 self.dataver.changed();
743 }
744
745 async fn max_content_buffer_size(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
746 Ok(self.config.max_content_buffer_size)
747 }
748
749 async fn max_network_bandwidth(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
750 Ok(self.config.max_network_bandwidth)
751 }
752
753 async fn max_concurrent_encoders(&self, _ctx: impl ReadContext) -> Result<u8, Error> {
754 Ok(self.config.max_concurrent_encoders)
755 }
756
757 async fn max_encoded_pixel_rate(&self, _ctx: impl ReadContext) -> Result<u32, Error> {
758 Ok(self.config.max_encoded_pixel_rate)
759 }
760
761 async fn video_sensor_params<P: TLVBuilderParent>(
762 &self,
763 _ctx: impl ReadContext,
764 builder: VideoSensorParamsStructBuilder<P>,
765 ) -> Result<P, Error> {
766 builder
767 .sensor_width(self.config.sensor.sensor_width)?
768 .sensor_height(self.config.sensor.sensor_height)?
769 .max_fps(self.config.sensor.max_fps)?
770 .max_hdrfps(self.config.sensor.max_hdrfps)?
771 .end()
772 }
773
774 async fn min_viewport_resolution<P: TLVBuilderParent>(
775 &self,
776 _ctx: impl ReadContext,
777 builder: VideoResolutionStructBuilder<P>,
778 ) -> Result<P, Error> {
779 builder
780 .width(self.config.min_viewport.0)?
781 .height(self.config.min_viewport.1)?
782 .end()
783 }
784
785 async fn rate_distortion_trade_off_points<P: TLVBuilderParent>(
786 &self,
787 _ctx: impl ReadContext,
788 builder: ArrayAttributeRead<
789 RateDistortionTradeOffPointsStructArrayBuilder<P>,
790 RateDistortionTradeOffPointsStructBuilder<P>,
791 >,
792 ) -> Result<P, Error> {
793 match builder {
794 ArrayAttributeRead::ReadAll(mut b) => {
795 for p in self.config.rate_distortion_points {
796 b = write_rate_distortion(b.push()?, p)?;
797 }
798 b.end()
799 }
800 ArrayAttributeRead::ReadOne(idx, b) => {
801 let Some(p) = self.config.rate_distortion_points.get(idx as usize) else {
802 return Err(ErrorCode::ConstraintError.into());
803 };
804 write_rate_distortion(b, p)
805 }
806 ArrayAttributeRead::ReadNone(b) => b.end(),
807 }
808 }
809
810 async fn supported_stream_usages<P: TLVBuilderParent>(
811 &self,
812 _ctx: impl ReadContext,
813 builder: ArrayAttributeRead<
814 ToTLVArrayBuilder<P, StreamUsageEnum>,
815 ToTLVBuilder<P, StreamUsageEnum>,
816 >,
817 ) -> Result<P, Error> {
818 read_enum_array(builder, self.config.supported_stream_usages)
819 }
820
821 async fn stream_usage_priorities<P: TLVBuilderParent>(
822 &self,
823 _ctx: impl ReadContext,
824 builder: ArrayAttributeRead<
825 ToTLVArrayBuilder<P, StreamUsageEnum>,
826 ToTLVBuilder<P, StreamUsageEnum>,
827 >,
828 ) -> Result<P, Error> {
829 self.ensure_priorities_seeded();
830 let snapshot = self.state.lock(|cell| {
831 let s = cell.borrow();
832 let mut out: Vec<StreamUsageEnum, MAX_STREAM_USAGES> = Vec::new();
834 for u in s.stream_usage_priorities.iter() {
835 let _ = out.push(*u);
836 }
837 out
838 });
839 read_enum_array(builder, &snapshot)
840 }
841
842 async fn allocated_video_streams<P: TLVBuilderParent>(
843 &self,
844 _ctx: impl ReadContext,
845 builder: ArrayAttributeRead<VideoStreamStructArrayBuilder<P>, VideoStreamStructBuilder<P>>,
846 ) -> Result<P, Error> {
847 let snapshot = self.state.lock(|cell| cell.borrow().videos.clone());
849 match builder {
850 ArrayAttributeRead::ReadAll(mut b) => {
851 for s in snapshot.iter() {
852 b = write_video_stream(b.push()?, s)?;
853 }
854 b.end()
855 }
856 ArrayAttributeRead::ReadOne(idx, b) => {
857 let Some(s) = snapshot.get(idx as usize) else {
858 return Err(ErrorCode::ConstraintError.into());
859 };
860 write_video_stream(b, s)
861 }
862 ArrayAttributeRead::ReadNone(b) => b.end(),
863 }
864 }
865
866 async fn handle_video_stream_allocate<P: TLVBuilderParent>(
869 &self,
870 ctx: impl InvokeContext,
871 request: VideoStreamAllocateRequest<'_>,
872 response: VideoStreamAllocateResponseBuilder<P>,
873 ) -> Result<P, Error> {
874 self.ensure_priorities_seeded();
875 let mut candidate = self.validate_video_alloc(&request)?;
876
877 if let Some(existing) = self.find_matching_existing(&candidate) {
879 return response.video_stream_id(existing)?.end();
880 }
881
882 let full = self.state.lock(|cell| cell.borrow().videos.len() >= NV);
884 if full {
885 return Err(ErrorCode::ResourceExhausted.into());
886 }
887
888 candidate.video_stream_id = self.alloc_video_id();
890 self.hooks.allocate_video(&candidate).await?;
891
892 let pushed = self.state.lock(|cell| {
896 let mut state = cell.borrow_mut();
897 state.videos.push(candidate).is_ok()
898 });
899 if !pushed {
900 let _ = self.hooks.deallocate_video(candidate.video_stream_id).await;
901 return Err(ErrorCode::ResourceExhausted.into());
902 }
903 ctx.notify_own_attr_changed(AttributeId::AllocatedVideoStreams as _);
904
905 response.video_stream_id(candidate.video_stream_id)?.end()
906 }
907
908 async fn handle_video_stream_modify(
909 &self,
910 ctx: impl InvokeContext,
911 request: VideoStreamModifyRequest<'_>,
912 ) -> Result<(), Error> {
913 let id = request.video_stream_id()?;
914 let watermark = request.watermark_enabled()?;
915 let osd = request.osd_enabled()?;
916
917 if watermark.is_some() && !self.has_feature(decl::Feature::WATERMARK.bits()) {
918 return Err(ErrorCode::ConstraintError.into());
919 }
920 if osd.is_some() && !self.has_feature(decl::Feature::ON_SCREEN_DISPLAY.bits()) {
921 return Err(ErrorCode::ConstraintError.into());
922 }
923
924 let exists = self
927 .state
928 .lock(|cell| cell.borrow().videos.iter().any(|s| s.video_stream_id == id));
929 if !exists {
930 return Err(ErrorCode::NotFound.into());
931 }
932
933 self.hooks.modify_video(id, watermark, osd).await?;
934
935 self.state.lock(|cell| {
936 let mut state = cell.borrow_mut();
937 if let Some(row) = state.videos.iter_mut().find(|s| s.video_stream_id == id) {
938 if let Some(w) = watermark {
939 row.watermark_enabled = Some(w);
940 }
941 if let Some(o) = osd {
942 row.osd_enabled = Some(o);
943 }
944 }
945 });
946 ctx.notify_own_attr_changed(AttributeId::AllocatedVideoStreams as _);
947 Ok(())
948 }
949
950 async fn handle_video_stream_deallocate(
951 &self,
952 ctx: impl InvokeContext,
953 request: VideoStreamDeallocateRequest<'_>,
954 ) -> Result<(), Error> {
955 let id = request.video_stream_id()?;
956
957 let status = self.state.lock(|cell| {
960 let state = cell.borrow();
961 match state.videos.iter().find(|s| s.video_stream_id == id) {
962 None => Err(ErrorCode::NotFound),
963 Some(s) if s.reference_count > 0 => Err(ErrorCode::InvalidAction),
964 Some(_) => Ok(()),
965 }
966 });
967 status.map_err(Error::from)?;
968
969 self.hooks.deallocate_video(id).await?;
970
971 self.state.lock(|cell| {
972 let mut state = cell.borrow_mut();
973 state.videos.retain(|s| s.video_stream_id != id);
974 });
975 ctx.notify_own_attr_changed(AttributeId::AllocatedVideoStreams as _);
976 Ok(())
977 }
978
979 async fn handle_set_stream_priorities(
980 &self,
981 ctx: impl InvokeContext,
982 request: SetStreamPrioritiesRequest<'_>,
983 ) -> Result<(), Error> {
984 let new_prio: TLVArray<'_, StreamUsageEnum> = request.stream_priorities()?;
985
986 let mut buffer: Vec<StreamUsageEnum, MAX_STREAM_USAGES> = Vec::new();
989 for entry in new_prio.iter() {
990 let usage = entry?;
991 if !self.config.supported_stream_usages.contains(&usage) {
992 return Err(ErrorCode::ConstraintError.into());
993 }
994 if buffer.contains(&usage) {
995 return Err(ErrorCode::ConstraintError.into());
996 }
997 buffer
998 .push(usage)
999 .map_err(|_| Error::from(ErrorCode::ResourceExhausted))?;
1000 }
1001
1002 self.state.lock(|cell| {
1003 let mut state = cell.borrow_mut();
1004 state.stream_usage_priorities.clear();
1005 for u in buffer.iter() {
1006 let _ = state.stream_usage_priorities.push(*u);
1007 }
1008 });
1009 ctx.notify_own_attr_changed(AttributeId::StreamUsagePriorities as _);
1010 Ok(())
1011 }
1012
1013 async fn microphone_capabilities<P: TLVBuilderParent>(
1014 &self,
1015 _ctx: impl ReadContext,
1016 builder: AudioCapabilitiesStructBuilder<P>,
1017 ) -> Result<P, Error> {
1018 let Some(cfg) = self.config.mic_capabilities else {
1019 return Err(ErrorCode::InvalidAction.into());
1020 };
1021 let b = builder.max_number_of_channels(cfg.max_channels)?;
1022 let mut codecs = b.supported_codecs()?;
1023 for codec in cfg.supported_codecs {
1024 codecs = codecs.push(codec)?;
1025 }
1026 let b = codecs.end()?;
1027 let mut rates = b.supported_sample_rates()?;
1028 for r in cfg.supported_sample_rates {
1029 rates = rates.push(r)?;
1030 }
1031 let b = rates.end()?;
1032 let mut depths = b.supported_bit_depths()?;
1033 for d in cfg.supported_bit_depths {
1034 depths = depths.push(d)?;
1035 }
1036 depths.end()?.end()
1037 }
1038
1039 async fn allocated_audio_streams<P: TLVBuilderParent>(
1040 &self,
1041 _ctx: impl ReadContext,
1042 builder: ArrayAttributeRead<AudioStreamStructArrayBuilder<P>, AudioStreamStructBuilder<P>>,
1043 ) -> Result<P, Error> {
1044 let snapshot = self.state.lock(|cell| cell.borrow().audios.clone());
1045 match builder {
1046 ArrayAttributeRead::ReadAll(mut b) => {
1047 for s in snapshot.iter() {
1048 b = write_audio_stream(b.push()?, s)?;
1049 }
1050 b.end()
1051 }
1052 ArrayAttributeRead::ReadOne(idx, b) => {
1053 let Some(s) = snapshot.get(idx as usize) else {
1054 return Err(ErrorCode::ConstraintError.into());
1055 };
1056 write_audio_stream(b, s)
1057 }
1058 ArrayAttributeRead::ReadNone(b) => b.end(),
1059 }
1060 }
1061
1062 async fn handle_audio_stream_allocate<P: TLVBuilderParent>(
1065 &self,
1066 _ctx: impl InvokeContext,
1067 _request: AudioStreamAllocateRequest<'_>,
1068 _response: AudioStreamAllocateResponseBuilder<P>,
1069 ) -> Result<P, Error> {
1070 Err(ErrorCode::CommandNotFound.into())
1071 }
1072
1073 async fn handle_audio_stream_deallocate(
1074 &self,
1075 _ctx: impl InvokeContext,
1076 _request: AudioStreamDeallocateRequest<'_>,
1077 ) -> Result<(), Error> {
1078 Err(ErrorCode::CommandNotFound.into())
1079 }
1080
1081 async fn handle_snapshot_stream_allocate<P: TLVBuilderParent>(
1082 &self,
1083 _ctx: impl InvokeContext,
1084 _request: SnapshotStreamAllocateRequest<'_>,
1085 _response: SnapshotStreamAllocateResponseBuilder<P>,
1086 ) -> Result<P, Error> {
1087 Err(ErrorCode::CommandNotFound.into())
1088 }
1089
1090 async fn handle_snapshot_stream_modify(
1091 &self,
1092 _ctx: impl InvokeContext,
1093 _request: SnapshotStreamModifyRequest<'_>,
1094 ) -> Result<(), Error> {
1095 Err(ErrorCode::CommandNotFound.into())
1096 }
1097
1098 async fn handle_snapshot_stream_deallocate(
1099 &self,
1100 _ctx: impl InvokeContext,
1101 _request: SnapshotStreamDeallocateRequest<'_>,
1102 ) -> Result<(), Error> {
1103 Err(ErrorCode::CommandNotFound.into())
1104 }
1105
1106 async fn handle_capture_snapshot<P: TLVBuilderParent>(
1107 &self,
1108 _ctx: impl InvokeContext,
1109 _request: CaptureSnapshotRequest<'_>,
1110 _response: CaptureSnapshotResponseBuilder<P>,
1111 ) -> Result<P, Error> {
1112 Err(ErrorCode::CommandNotFound.into())
1113 }
1114}
1115
1116fn read_enum_array<P: TLVBuilderParent>(
1121 builder: ArrayAttributeRead<
1122 ToTLVArrayBuilder<P, StreamUsageEnum>,
1123 ToTLVBuilder<P, StreamUsageEnum>,
1124 >,
1125 items: &[StreamUsageEnum],
1126) -> Result<P, Error> {
1127 match builder {
1128 ArrayAttributeRead::ReadAll(mut b) => {
1129 for item in items {
1130 b = b.push(item)?;
1131 }
1132 b.end()
1133 }
1134 ArrayAttributeRead::ReadOne(idx, b) => {
1135 let Some(item) = items.get(idx as usize) else {
1136 return Err(ErrorCode::ConstraintError.into());
1137 };
1138 b.set(item)
1139 }
1140 ArrayAttributeRead::ReadNone(b) => b.end(),
1141 }
1142}
1143
1144fn write_video_stream<P: TLVBuilderParent>(
1145 builder: VideoStreamStructBuilder<P>,
1146 s: &VideoStream,
1147) -> Result<P, Error> {
1148 let b = builder
1149 .video_stream_id(s.video_stream_id)?
1150 .stream_usage(s.stream_usage)?
1151 .video_codec(s.video_codec)?
1152 .min_frame_rate(s.min_frame_rate)?
1153 .max_frame_rate(s.max_frame_rate)?;
1154 let b = b
1155 .min_resolution()?
1156 .width(s.min_width)?
1157 .height(s.min_height)?
1158 .end()?;
1159 let b = b
1160 .max_resolution()?
1161 .width(s.max_width)?
1162 .height(s.max_height)?
1163 .end()?;
1164 b.min_bit_rate(s.min_bit_rate)?
1165 .max_bit_rate(s.max_bit_rate)?
1166 .key_frame_interval(s.key_frame_interval)?
1167 .watermark_enabled(s.watermark_enabled)?
1168 .osd_enabled(s.osd_enabled)?
1169 .reference_count(s.reference_count)?
1170 .end()
1171}
1172
1173fn write_audio_stream<P: TLVBuilderParent>(
1174 builder: AudioStreamStructBuilder<P>,
1175 s: &AudioStream,
1176) -> Result<P, Error> {
1177 builder
1178 .audio_stream_id(s.audio_stream_id)?
1179 .stream_usage(s.stream_usage)?
1180 .audio_codec(s.audio_codec)?
1181 .channel_count(s.channel_count)?
1182 .sample_rate(s.sample_rate)?
1183 .bit_rate(s.bit_rate)?
1184 .bit_depth(s.bit_depth)?
1185 .reference_count(s.reference_count)?
1186 .end()
1187}
1188
1189fn write_rate_distortion<P: TLVBuilderParent>(
1190 builder: RateDistortionTradeOffPointsStructBuilder<P>,
1191 p: &RateDistortionPoint,
1192) -> Result<P, Error> {
1193 let b = builder.codec(p.codec)?;
1194 let b = b
1195 .resolution()?
1196 .width(p.min_resolution.0)?
1197 .height(p.min_resolution.1)?
1198 .end()?;
1199 b.min_bit_rate(p.min_bit_rate)?.end()
1200}