| /* |
| * Copyright (c) 2025 Project CHIP Authors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include <lib/support/tests/ExtraPwTestMacros.h> |
| #include <pw_unit_test/framework.h> |
| |
| #include <app/AttributeValueDecoder.h> |
| #include <app/CommandHandler.h> |
| #include <app/DefaultSafeAttributePersistenceProvider.h> |
| #include <app/SafeAttributePersistenceProvider.h> |
| #include <app/clusters/camera-av-stream-management-server/CameraAVStreamManagementCluster.h> |
| #include <app/data-model-provider/MetadataTypes.h> |
| #include <app/data-model-provider/tests/TestConstants.h> |
| #include <app/data-model/Decode.h> |
| #include <app/server-cluster/DefaultServerCluster.h> |
| #include <app/server-cluster/testing/ClusterTester.h> |
| #include <app/server-cluster/testing/ValidateGlobalAttributes.h> |
| #include <clusters/CameraAvStreamManagement/Attributes.h> |
| #include <clusters/CameraAvStreamManagement/Commands.h> |
| #include <clusters/CameraAvStreamManagement/Enums.h> |
| #include <clusters/CameraAvStreamManagement/Metadata.h> |
| #include <lib/core/CHIPError.h> |
| #include <lib/core/DataModelTypes.h> |
| #include <lib/support/ReadOnlyBuffer.h> |
| #include <protocols/interaction_model/StatusCode.h> |
| |
| namespace { |
| |
| using namespace chip; |
| using namespace chip::app; |
| using namespace chip::app::Clusters; |
| using namespace chip::app::Clusters::CameraAvStreamManagement; |
| using namespace chip::Testing; |
| using namespace chip::Protocols::InteractionModel; |
| |
| static constexpr chip::EndpointId kTestEndpointId = 1; |
| |
| static AudioCapabilitiesStruct & GetAudioCapabilities() |
| { |
| static std::array<AudioCodecEnum, 2> audioCodecs = { AudioCodecEnum::kOpus, AudioCodecEnum::kAacLc }; |
| static std::array<uint32_t, 2> sampleRates = { 48000, 32000 }; // Sample rates in Hz |
| static std::array<uint8_t, 2> bitDepths = { 24, 32 }; |
| static AudioCapabilitiesStruct audioCapabilities = { 2, chip::Span<AudioCodecEnum>(audioCodecs), |
| chip::Span<uint32_t>(sampleRates), chip::Span<uint8_t>(bitDepths) }; |
| return audioCapabilities; |
| } |
| static VideoSensorParamsStruct & GetVideoSensorParams() |
| { |
| static VideoSensorParamsStruct videoSensorParams = { 1920, 1080, 120, |
| chip::Optional<uint16_t>(30) }; // Typical numbers for Pi camera. |
| return videoSensorParams; |
| } |
| |
| static std::vector<RateDistortionTradeOffStruct> & GetRateDistortionTradeOffPoints() |
| { |
| static std::vector<RateDistortionTradeOffStruct> rateDistTradeOffs = { |
| { VideoCodecEnum::kH264, { 640, 480 }, 10000 /* bitrate */ } |
| }; |
| return rateDistTradeOffs; |
| } |
| |
| static std::vector<SnapshotCapabilitiesStruct> & GetSnapshotCapabilities() |
| { |
| static std::vector<SnapshotCapabilitiesStruct> snapshotCapabilities = { |
| { { 640, 480 }, 30, ImageCodecEnum::kJpeg, false, chip::MakeOptional(false) }, |
| { { 1280, 720 }, 30, ImageCodecEnum::kJpeg, true, chip::MakeOptional(true) }, |
| }; |
| return snapshotCapabilities; |
| } |
| |
| static std::vector<StreamUsageEnum> & GetSupportedStreamUsages() |
| { |
| static std::vector<StreamUsageEnum> supportedStreamUsage = { StreamUsageEnum::kLiveView, StreamUsageEnum::kRecording }; |
| return supportedStreamUsage; |
| } |
| |
| // Mock delegate for testing CameraAVStreamManagement |
| class MockCameraAVStreamManagementDelegate : public CameraAVStreamManagementDelegate |
| { |
| public: |
| MockCameraAVStreamManagementDelegate(std::vector<VideoStreamStruct> * videoStreams, |
| std::vector<AudioStreamStruct> * audioStreams, |
| std::vector<SnapshotStreamStruct> * snapshotStreams) : |
| mAllocatedVideoStreams(videoStreams), |
| mAllocatedAudioStreams(audioStreams), mAllocatedSnapshotStreams(snapshotStreams), mAudioStreamCount(0), |
| mVideoStreamCount(0), mSnapshotStreamCount(0) |
| {} |
| |
| Protocols::InteractionModel::Status VideoStreamAllocate(const VideoStreamStruct & allocateArgs, uint16_t & outStreamID) override |
| { |
| if (!std::any_of(GetRateDistortionTradeOffPoints().begin(), GetRateDistortionTradeOffPoints().end(), |
| [&](const auto & tradeOffPoint) { return tradeOffPoint.codec == allocateArgs.videoCodec; })) |
| { |
| return Protocols::InteractionModel::Status::DynamicConstraintError; |
| } |
| |
| VerifyOrReturnError(mVideoStreamCount < 1, Protocols::InteractionModel::Status::ResourceExhausted); |
| |
| outStreamID = static_cast<uint16_t>(mAllocatedVideoStreams->size() + 1); |
| mAllocatedVideoStreams->push_back(allocateArgs); |
| mVideoStreamCount++; |
| return Protocols::InteractionModel::Status::Success; |
| } |
| |
| void OnVideoStreamAllocated(const VideoStreamStruct & allocatedStream, StreamAllocationAction action) override {} |
| |
| Protocols::InteractionModel::Status VideoStreamModify(const uint16_t streamID, const Optional<bool> waterMarkEnabled, |
| const Optional<bool> osdEnabled) override |
| { |
| return Protocols::InteractionModel::Status::Success; |
| } |
| |
| Protocols::InteractionModel::Status VideoStreamDeallocate(const uint16_t streamID) override |
| { |
| mVideoStreamCount--; |
| return Protocols::InteractionModel::Status::Success; |
| } |
| |
| Protocols::InteractionModel::Status AudioStreamAllocate(const AudioStreamStruct & allocateArgs, uint16_t & outStreamID) override |
| { |
| VerifyOrReturnError(mAudioStreamCount < 1, Protocols::InteractionModel::Status::ResourceExhausted); |
| |
| auto & audioCapabilities = GetAudioCapabilities(); |
| |
| if (!std::any_of(audioCapabilities.supportedCodecs.begin(), audioCapabilities.supportedCodecs.end(), |
| [&](const auto & codec) { return codec == allocateArgs.audioCodec; })) |
| { |
| return Protocols::InteractionModel::Status::DynamicConstraintError; |
| } |
| |
| if (!std::any_of(audioCapabilities.supportedSampleRates.begin(), audioCapabilities.supportedSampleRates.end(), |
| [&](const auto & sampleRate) { return sampleRate == allocateArgs.sampleRate; })) |
| { |
| return Protocols::InteractionModel::Status::DynamicConstraintError; |
| } |
| |
| if (!std::any_of(audioCapabilities.supportedBitDepths.begin(), audioCapabilities.supportedBitDepths.end(), |
| [&](const auto & bitDepth) { return bitDepth == allocateArgs.bitDepth; })) |
| { |
| return Protocols::InteractionModel::Status::DynamicConstraintError; |
| } |
| |
| outStreamID = static_cast<uint16_t>(mAllocatedAudioStreams->size() + 1); |
| mAllocatedAudioStreams->push_back(allocateArgs); |
| mAudioStreamCount++; |
| return Protocols::InteractionModel::Status::Success; |
| } |
| |
| Protocols::InteractionModel::Status AudioStreamDeallocate(const uint16_t streamID) override |
| { |
| mAudioStreamCount--; |
| return Protocols::InteractionModel::Status::Success; |
| } |
| |
| Protocols::InteractionModel::Status SnapshotStreamAllocate(const SnapshotStreamAllocateArgs & allocateArgs, |
| uint16_t & outStreamID) override |
| { |
| VerifyOrReturnError(mSnapshotStreamCount < 1, Protocols::InteractionModel::Status::ResourceExhausted); |
| |
| if (!std::any_of(GetSnapshotCapabilities().begin(), GetSnapshotCapabilities().end(), |
| [&](const auto & capability) { return capability.imageCodec == allocateArgs.imageCodec; })) |
| { |
| return Protocols::InteractionModel::Status::DynamicConstraintError; |
| } |
| |
| outStreamID = static_cast<uint16_t>(mAllocatedSnapshotStreams->size() + 1); |
| |
| SnapshotStreamStruct newStream; |
| newStream.snapshotStreamID = outStreamID; |
| newStream.imageCodec = allocateArgs.imageCodec; |
| newStream.frameRate = allocateArgs.maxFrameRate; |
| newStream.minResolution = allocateArgs.minResolution; |
| newStream.maxResolution = allocateArgs.maxResolution; |
| newStream.quality = allocateArgs.quality; |
| newStream.encodedPixels = allocateArgs.encodedPixels; |
| newStream.hardwareEncoder = allocateArgs.hardwareEncoder; |
| newStream.watermarkEnabled = allocateArgs.watermarkEnabled; |
| newStream.OSDEnabled = allocateArgs.OSDEnabled; |
| newStream.referenceCount = 1; |
| |
| mAllocatedSnapshotStreams->push_back(newStream); |
| mSnapshotStreamCount++; |
| return Protocols::InteractionModel::Status::Success; |
| } |
| |
| Protocols::InteractionModel::Status SnapshotStreamModify(const uint16_t streamID, const Optional<bool> waterMarkEnabled, |
| const Optional<bool> osdEnabled) override |
| { |
| return Protocols::InteractionModel::Status::Success; |
| } |
| |
| Protocols::InteractionModel::Status SnapshotStreamDeallocate(const uint16_t streamID) override |
| { |
| mSnapshotStreamCount--; |
| return Protocols::InteractionModel::Status::Success; |
| } |
| |
| void OnStreamUsagePrioritiesChanged() override {} |
| |
| void OnAttributeChanged(AttributeId attributeId) override {} |
| |
| Protocols::InteractionModel::Status CaptureSnapshot(const DataModel::Nullable<uint16_t> streamID, |
| const VideoResolutionStruct & resolution, |
| ImageSnapshot & outImageSnapshot) override |
| { |
| outImageSnapshot.imageCodec = ImageCodecEnum::kJpeg; |
| outImageSnapshot.imageRes = resolution; |
| const uint8_t dummyImageData[] = { 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00, 0x00, |
| 0x01, 0x00, 0x01, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, |
| 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, |
| 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, |
| 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x11, 0x08, |
| 0x00, 0x01, 0x00, 0x01, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xFF, |
| 0xC4, 0x00, 0x1F, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, |
| 0x0A, 0x0B, 0xFF, 0xDA, 0x00, 0x0C, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3F, |
| 0x00, 0xF2, 0x8A, 0x28, 0xFF, 0xD9 }; |
| outImageSnapshot.data.assign(dummyImageData, dummyImageData + sizeof(dummyImageData)); |
| return Protocols::InteractionModel::Status::Success; |
| } |
| |
| CHIP_ERROR PersistentAttributesLoadedCallback() override { return CHIP_NO_ERROR; } |
| |
| const std::vector<VideoStreamStruct> & GetAllocatedVideoStreams() const override { return *mAllocatedVideoStreams; } |
| |
| const std::vector<AudioStreamStruct> & GetAllocatedAudioStreams() const override { return *mAllocatedAudioStreams; } |
| |
| const std::vector<SnapshotStreamStruct> & GetAllocatedSnapshotStreams() const { return *mAllocatedSnapshotStreams; } |
| |
| private: |
| std::vector<VideoStreamStruct> * mAllocatedVideoStreams; |
| std::vector<AudioStreamStruct> * mAllocatedAudioStreams; |
| std::vector<SnapshotStreamStruct> * mAllocatedSnapshotStreams; |
| uint8_t mAudioStreamCount; |
| uint8_t mVideoStreamCount; |
| uint8_t mSnapshotStreamCount; |
| }; |
| |
| // initialize memory as ReadOnlyBufferBuilder may allocate |
| struct TestCameraAVStreamManagementCluster : public ::testing::Test |
| { |
| static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); } |
| static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); } |
| |
| static CHIP_ERROR InitializeCameraAVSMDefaults(CameraAvStreamManagement::CameraAVStreamManagementCluster & mServer) |
| { |
| ReturnErrorOnFailure(mServer.SetSoftRecordingPrivacyModeEnabled(true)); |
| ReturnErrorOnFailure(mServer.SetSoftLivestreamPrivacyModeEnabled(true)); |
| ReturnErrorOnFailure(mServer.SetHardPrivacyModeOn(true)); |
| ReturnErrorOnFailure(mServer.SetNightVision(TriStateAutoEnum::kAuto)); |
| ReturnErrorOnFailure(mServer.SetViewport({ 0, 0, 1920, 1080 })); |
| ReturnErrorOnFailure(mServer.SetSpeakerMuted(true)); |
| ReturnErrorOnFailure(mServer.SetSpeakerVolumeLevel(1)); |
| ReturnErrorOnFailure(mServer.SetSpeakerMinLevel(1)); |
| ReturnErrorOnFailure(mServer.SetSpeakerMaxLevel(254)); |
| ReturnErrorOnFailure(mServer.SetMicrophoneMuted(true)); |
| ReturnErrorOnFailure(mServer.SetMicrophoneVolumeLevel(1)); |
| ReturnErrorOnFailure(mServer.SetMicrophoneMinLevel(1)); |
| ReturnErrorOnFailure(mServer.SetMicrophoneMaxLevel(254)); |
| ReturnErrorOnFailure(mServer.SetMicrophoneAGCEnabled(true)); |
| ReturnErrorOnFailure(mServer.SetImageRotation(0)); |
| ReturnErrorOnFailure(mServer.SetImageFlipHorizontal(false)); |
| ReturnErrorOnFailure(mServer.SetImageFlipVertical(false)); |
| ReturnErrorOnFailure(mServer.SetStatusLightEnabled(true)); |
| ReturnErrorOnFailure(mServer.SetStatusLightBrightness(Globals::ThreeLevelAutoEnum::kMedium)); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| TestCameraAVStreamManagementCluster() : |
| mMockDelegate(&mVideoStreams, &mAudioStreams, &mSnapshotStreams), |
| mServer(CameraAvStreamManagement::CameraAVStreamManagementCluster::Context{ mPersistenceProvider }, mMockDelegate, |
| kTestEndpointId, |
| chip::BitFlags<CameraAvStreamManagement::Feature>( |
| CameraAvStreamManagement::Feature::kVideo, CameraAvStreamManagement::Feature::kAudio, |
| CameraAvStreamManagement::Feature::kSnapshot, CameraAvStreamManagement::Feature::kSpeaker, |
| CameraAvStreamManagement::Feature::kImageControl, CameraAvStreamManagement::Feature::kPrivacy, |
| CameraAvStreamManagement::Feature::kWatermark, CameraAvStreamManagement::Feature::kHighDynamicRange, |
| CameraAvStreamManagement::Feature::kNightVision), |
| chip::BitFlags<CameraAvStreamManagement::OptionalAttribute>( |
| CameraAvStreamManagement::OptionalAttribute::kHardPrivacyModeOn, |
| CameraAvStreamManagement::OptionalAttribute::kMicrophoneAGCEnabled, |
| CameraAvStreamManagement::OptionalAttribute::kImageRotation, |
| CameraAvStreamManagement::OptionalAttribute::kImageFlipHorizontal, |
| CameraAvStreamManagement::OptionalAttribute::kImageFlipVertical, |
| CameraAvStreamManagement::OptionalAttribute::kStatusLightEnabled, |
| CameraAvStreamManagement::OptionalAttribute::kStatusLightBrightness), |
| 1, 248832000 /*1920*1080*120 */, GetVideoSensorParams(), false, { 640, 480 }, GetRateDistortionTradeOffPoints(), |
| 4096, GetAudioCapabilities(), GetAudioCapabilities(), TwoWayTalkSupportTypeEnum::kFullDuplex, |
| GetSnapshotCapabilities(), 128000000, GetSupportedStreamUsages(), GetSupportedStreamUsages()), |
| mClusterTester(mServer) |
| {} |
| |
| void SetUp() override |
| { |
| VerifyOrDie(mPersistenceProvider.Init(&mClusterTester.GetServerClusterContext().storage) == CHIP_NO_ERROR); |
| EXPECT_EQ(mServer.Startup(mClusterTester.GetServerClusterContext()), CHIP_NO_ERROR); |
| EXPECT_EQ(InitializeCameraAVSMDefaults(mServer), CHIP_NO_ERROR); |
| EXPECT_EQ(mServer.Init(), CHIP_NO_ERROR); |
| } |
| |
| std::vector<VideoStreamStruct> mVideoStreams; |
| std::vector<AudioStreamStruct> mAudioStreams; |
| std::vector<SnapshotStreamStruct> mSnapshotStreams; |
| MockCameraAVStreamManagementDelegate mMockDelegate; |
| app::DefaultSafeAttributePersistenceProvider mPersistenceProvider; |
| CameraAvStreamManagement::CameraAVStreamManagementCluster mServer; |
| ClusterTester mClusterTester; |
| }; |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestAttributes) |
| { |
| ASSERT_TRUE( |
| Testing::IsAttributesListEqualTo(mServer, |
| { |
| CameraAvStreamManagement::Attributes::MaxConcurrentEncoders::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::MaxEncodedPixelRate::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::VideoSensorParams::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::NightVisionUsesInfrared::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::MinViewportResolution::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::RateDistortionTradeOffPoints::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::MaxContentBufferSize::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::MicrophoneCapabilities::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::SpeakerCapabilities::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::TwoWayTalkSupport::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::SnapshotCapabilities::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::MaxNetworkBandwidth::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::CurrentFrameRate::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::HDRModeEnabled::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::SupportedStreamUsages::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::AllocatedVideoStreams::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::AllocatedAudioStreams::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::AllocatedSnapshotStreams::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::StreamUsagePriorities::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::SoftRecordingPrivacyModeEnabled::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::SoftLivestreamPrivacyModeEnabled::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::HardPrivacyModeOn::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::NightVision::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::Viewport::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::SpeakerMuted::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::SpeakerVolumeLevel::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::SpeakerMaxLevel::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::SpeakerMinLevel::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::MicrophoneMuted::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::MicrophoneVolumeLevel::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::MicrophoneMaxLevel::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::MicrophoneMinLevel::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::MicrophoneAGCEnabled::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::ImageRotation::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::ImageFlipHorizontal::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::ImageFlipVertical::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::StatusLightEnabled::kMetadataEntry, |
| CameraAvStreamManagement::Attributes::StatusLightBrightness::kMetadataEntry, |
| })); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestAcceptedCommands) |
| { |
| ASSERT_TRUE( |
| Testing::IsAcceptedCommandsListEqualTo(mServer, |
| { |
| CameraAvStreamManagement::Commands::VideoStreamAllocate::kMetadataEntry, |
| CameraAvStreamManagement::Commands::VideoStreamDeallocate::kMetadataEntry, |
| CameraAvStreamManagement::Commands::VideoStreamModify::kMetadataEntry, |
| CameraAvStreamManagement::Commands::AudioStreamAllocate::kMetadataEntry, |
| CameraAvStreamManagement::Commands::AudioStreamDeallocate::kMetadataEntry, |
| CameraAvStreamManagement::Commands::SnapshotStreamAllocate::kMetadataEntry, |
| CameraAvStreamManagement::Commands::SnapshotStreamDeallocate::kMetadataEntry, |
| CameraAvStreamManagement::Commands::SnapshotStreamModify::kMetadataEntry, |
| CameraAvStreamManagement::Commands::SetStreamPriorities::kMetadataEntry, |
| CameraAvStreamManagement::Commands::CaptureSnapshot::kMetadataEntry, |
| |
| })); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadClusterRevisionAttribute) |
| { |
| // Create a mock attribute request for ClusterRevision |
| chip::app::ConcreteAttributePath path(kTestEndpointId, CameraAvStreamManagement::Id, |
| chip::app::Clusters::Globals::Attributes::ClusterRevision::Id); |
| chip::app::DataModel::ReadAttributeRequest request(path, chip::Testing::kAdminSubjectDescriptor); |
| |
| // Create a buffer for encoding |
| chip::Platform::ScopedMemoryBufferWithSize<uint8_t> buffer; |
| ASSERT_TRUE(buffer.Alloc(1024)); |
| |
| // Create AttributeReportIBs::Builder for the encoder |
| chip::app::AttributeReportIBs::Builder attributeReportIBsBuilder; |
| chip::TLV::TLVWriter reportWriter; |
| reportWriter.Init(buffer.Get(), buffer.AllocatedSize()); |
| CHIP_ERROR err = attributeReportIBsBuilder.Init(&reportWriter); |
| ASSERT_EQ(err, CHIP_NO_ERROR); |
| |
| chip::app::AttributeValueEncoder encoder(attributeReportIBsBuilder, chip::Testing::kAdminSubjectDescriptor, request.path, |
| 0 /* dataVersion */); |
| |
| // Test reading cluster revision |
| auto status = mServer.ReadAttribute(request, encoder); |
| EXPECT_TRUE(status.IsSuccess()); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadMaxConcurrentEncoders) |
| { |
| uint8_t maxConcurrentEncoders = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MaxConcurrentEncoders::Id, maxConcurrentEncoders), CHIP_NO_ERROR); |
| EXPECT_EQ(maxConcurrentEncoders, 1); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadMaxEncodedPixelRate) |
| { |
| uint32_t maxEncodedPixelRate = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MaxEncodedPixelRate::Id, maxEncodedPixelRate), CHIP_NO_ERROR); |
| EXPECT_EQ(maxEncodedPixelRate, static_cast<uint32_t>(248832000)); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadVideoSensorParams) |
| { |
| Attributes::VideoSensorParams::TypeInfo::DecodableType videoSensorParams; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::VideoSensorParams::Id, videoSensorParams), CHIP_NO_ERROR); |
| EXPECT_EQ(videoSensorParams.sensorWidth, 1920); |
| EXPECT_EQ(videoSensorParams.sensorHeight, 1080); |
| EXPECT_EQ(videoSensorParams.maxFPS, 120); |
| EXPECT_TRUE(videoSensorParams.maxHDRFPS.HasValue()); |
| EXPECT_EQ(videoSensorParams.maxHDRFPS.Value(), 30); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadNightVisionUsesInfrared) |
| { |
| bool usesInfrared = true; // Default Init is false |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::NightVisionUsesInfrared::Id, usesInfrared), CHIP_NO_ERROR); |
| EXPECT_FALSE(usesInfrared); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadMinViewportResolution) |
| { |
| Attributes::MinViewportResolution::TypeInfo::Type minViewportResolution; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MinViewportResolution::Id, minViewportResolution), CHIP_NO_ERROR); |
| EXPECT_EQ(minViewportResolution.width, 640); |
| EXPECT_EQ(minViewportResolution.height, 480); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadRateDistortionTradeOffPoints) |
| { |
| Attributes::RateDistortionTradeOffPoints::TypeInfo::DecodableType rateDistortionTradeOffPoints; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::RateDistortionTradeOffPoints::Id, rateDistortionTradeOffPoints), |
| CHIP_NO_ERROR); |
| auto it = rateDistortionTradeOffPoints.begin(); |
| uint8_t count = 0; |
| while (it.Next()) |
| { |
| ++count; |
| } |
| EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); |
| EXPECT_EQ(count, 1u); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadMaxContentBufferSize) |
| { |
| uint32_t maxContentBufferSize = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MaxContentBufferSize::Id, maxContentBufferSize), CHIP_NO_ERROR); |
| EXPECT_EQ(maxContentBufferSize, 4096u); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadMicrophoneCapabilities) |
| { |
| Attributes::MicrophoneCapabilities::TypeInfo::DecodableType microphoneCapabilities; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MicrophoneCapabilities::Id, microphoneCapabilities), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(microphoneCapabilities.maxNumberOfChannels, 2); |
| |
| auto it = microphoneCapabilities.supportedCodecs.begin(); |
| uint8_t count = 0; |
| while (it.Next()) |
| { |
| ++count; |
| } |
| EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); |
| EXPECT_EQ(count, 2u); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadSpeakerCapabilities) |
| { |
| Attributes::SpeakerCapabilities::TypeInfo::DecodableType speakerCapabilities; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SpeakerCapabilities::Id, speakerCapabilities), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(speakerCapabilities.maxNumberOfChannels, 2); |
| |
| auto it = speakerCapabilities.supportedCodecs.begin(); |
| uint8_t count = 0; |
| while (it.Next()) |
| { |
| ++count; |
| } |
| EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); |
| EXPECT_EQ(count, 2u); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadTwoWayTalkSupport) |
| { |
| Attributes::TwoWayTalkSupport::TypeInfo::Type twoWayTalkSupport = TwoWayTalkSupportTypeEnum::kNotSupported; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::TwoWayTalkSupport::Id, twoWayTalkSupport), CHIP_NO_ERROR); |
| EXPECT_EQ(twoWayTalkSupport, TwoWayTalkSupportTypeEnum::kFullDuplex); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadSnapshotCapabilities) |
| { |
| Attributes::SnapshotCapabilities::TypeInfo::DecodableType snapshotCapabilities; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SnapshotCapabilities::Id, snapshotCapabilities), CHIP_NO_ERROR); |
| auto it = snapshotCapabilities.begin(); |
| uint8_t count = 0; |
| while (it.Next()) |
| { |
| ++count; |
| } |
| EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); |
| EXPECT_EQ(count, 2u); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadMaxNetworkBandwidth) |
| { |
| uint32_t maxNetworkBandwidth = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MaxNetworkBandwidth::Id, maxNetworkBandwidth), CHIP_NO_ERROR); |
| EXPECT_EQ(maxNetworkBandwidth, 128000000u); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadCurrentFrameRate) |
| { |
| Attributes::CurrentFrameRate::TypeInfo::Type currentFrameRate = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::CurrentFrameRate::Id, currentFrameRate), CHIP_NO_ERROR); |
| EXPECT_EQ(currentFrameRate, 0); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadWriteHDRModeEnabled) |
| { |
| bool hdrModeEnabled = false; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::HDRModeEnabled::Id, hdrModeEnabled), CHIP_NO_ERROR); |
| EXPECT_FALSE(hdrModeEnabled); // Default should be false |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::HDRModeEnabled::Id, true), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::HDRModeEnabled::Id, hdrModeEnabled), CHIP_NO_ERROR); |
| EXPECT_TRUE(hdrModeEnabled); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::HDRModeEnabled::Id, false), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::HDRModeEnabled::Id, hdrModeEnabled), CHIP_NO_ERROR); |
| EXPECT_FALSE(hdrModeEnabled); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadSupportedStreamUsages) |
| { |
| Attributes::SupportedStreamUsages::TypeInfo::DecodableType supportedStreamUsages; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SupportedStreamUsages::Id, supportedStreamUsages), CHIP_NO_ERROR); |
| auto it = supportedStreamUsages.begin(); |
| uint8_t count = 0; |
| while (it.Next()) |
| { |
| ++count; |
| } |
| EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); |
| EXPECT_EQ(count, 2u); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadAllocatedVideoStreams) |
| { |
| Attributes::AllocatedVideoStreams::TypeInfo::DecodableType allocatedVideoStreams; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedVideoStreams::Id, allocatedVideoStreams), CHIP_NO_ERROR); |
| auto it = allocatedVideoStreams.begin(); |
| uint8_t count = 0; |
| while (it.Next()) |
| { |
| ++count; |
| } |
| EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); |
| EXPECT_EQ(count, 0u); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadAllocatedAudioStreams) |
| { |
| Attributes::AllocatedAudioStreams::TypeInfo::DecodableType allocatedAudioStreams; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedAudioStreams::Id, allocatedAudioStreams), CHIP_NO_ERROR); |
| auto it = allocatedAudioStreams.begin(); |
| uint8_t count = 0; |
| while (it.Next()) |
| { |
| ++count; |
| } |
| EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); |
| EXPECT_EQ(count, 0u); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadAllocatedSnapshotStreams) |
| { |
| Attributes::AllocatedSnapshotStreams::TypeInfo::DecodableType allocatedSnapshotStreams; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedSnapshotStreams::Id, allocatedSnapshotStreams), CHIP_NO_ERROR); |
| auto it = allocatedSnapshotStreams.begin(); |
| uint8_t count = 0; |
| while (it.Next()) |
| { |
| ++count; |
| } |
| EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); |
| EXPECT_EQ(count, 0u); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadStreamUsagePriorities) |
| { |
| Attributes::StreamUsagePriorities::TypeInfo::DecodableType streamUsagePriorities; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::StreamUsagePriorities::Id, streamUsagePriorities), CHIP_NO_ERROR); |
| auto it = streamUsagePriorities.begin(); |
| uint8_t count = 0; |
| while (it.Next()) |
| { |
| ++count; |
| } |
| EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); |
| EXPECT_EQ(count, 2u); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadWriteSoftRecordingPrivacyModeEnabled) |
| { |
| bool softRecordingPrivacyModeEnabled = false; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SoftRecordingPrivacyModeEnabled::Id, softRecordingPrivacyModeEnabled), |
| CHIP_NO_ERROR); |
| EXPECT_TRUE(softRecordingPrivacyModeEnabled); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::SoftRecordingPrivacyModeEnabled::Id, false), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SoftRecordingPrivacyModeEnabled::Id, softRecordingPrivacyModeEnabled), |
| CHIP_NO_ERROR); |
| EXPECT_FALSE(softRecordingPrivacyModeEnabled); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::SoftRecordingPrivacyModeEnabled::Id, true), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SoftRecordingPrivacyModeEnabled::Id, softRecordingPrivacyModeEnabled), |
| CHIP_NO_ERROR); |
| EXPECT_TRUE(softRecordingPrivacyModeEnabled); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadWriteSoftLivestreamPrivacyModeEnabled) |
| { |
| bool softLivestreamPrivacyModeEnabled = false; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SoftLivestreamPrivacyModeEnabled::Id, softLivestreamPrivacyModeEnabled), |
| CHIP_NO_ERROR); |
| EXPECT_TRUE(softLivestreamPrivacyModeEnabled); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::SoftLivestreamPrivacyModeEnabled::Id, false), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SoftLivestreamPrivacyModeEnabled::Id, softLivestreamPrivacyModeEnabled), |
| CHIP_NO_ERROR); |
| EXPECT_FALSE(softLivestreamPrivacyModeEnabled); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::SoftLivestreamPrivacyModeEnabled::Id, true), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SoftLivestreamPrivacyModeEnabled::Id, softLivestreamPrivacyModeEnabled), |
| CHIP_NO_ERROR); |
| EXPECT_TRUE(softLivestreamPrivacyModeEnabled); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadHardPrivacyModeOn) |
| { |
| bool hardPrivacyModeOn = false; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::HardPrivacyModeOn::Id, hardPrivacyModeOn), CHIP_NO_ERROR); |
| EXPECT_TRUE(hardPrivacyModeOn); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadWriteNightVision) |
| { |
| TriStateAutoEnum nightVision = TriStateAutoEnum::kOff; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::NightVision::Id, nightVision), CHIP_NO_ERROR); |
| EXPECT_EQ(nightVision, TriStateAutoEnum::kAuto); // Default should be Auto |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::NightVision::Id, TriStateAutoEnum::kOn), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::NightVision::Id, nightVision), CHIP_NO_ERROR); |
| EXPECT_EQ(nightVision, TriStateAutoEnum::kOn); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::NightVision::Id, TriStateAutoEnum::kOff), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::NightVision::Id, nightVision), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(nightVision, TriStateAutoEnum::kOff); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadWriteViewport) |
| { |
| Attributes::Viewport::TypeInfo::Type viewport = { 0, 0, 0, 0 }; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::Viewport::Id, viewport), CHIP_NO_ERROR); |
| EXPECT_EQ(viewport.x1, 0); |
| EXPECT_EQ(viewport.y1, 0); |
| EXPECT_EQ(viewport.x2, 1920); |
| EXPECT_EQ(viewport.y2, 1080); |
| |
| // Attempt to write an invalid value (too small) |
| Attributes::Viewport::TypeInfo::Type invalidViewport = { 10, 20, 640, 480 }; |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::Viewport::Id, invalidViewport), CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| |
| // Read again to verify no change |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::Viewport::Id, viewport), CHIP_NO_ERROR); |
| EXPECT_EQ(viewport.x1, 0); |
| EXPECT_EQ(viewport.y1, 0); |
| EXPECT_EQ(viewport.x2, 1920); |
| EXPECT_EQ(viewport.y2, 1080); |
| |
| // Write a valid new value |
| Attributes::Viewport::TypeInfo::Type newViewport = { 0, 0, 1280, 720 }; |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::Viewport::Id, newViewport), CHIP_NO_ERROR); |
| |
| // Read again to verify |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::Viewport::Id, viewport), CHIP_NO_ERROR); |
| EXPECT_EQ(viewport.x1, 0); |
| EXPECT_EQ(viewport.y1, 0); |
| EXPECT_EQ(viewport.x2, 1280); |
| EXPECT_EQ(viewport.y2, 720); |
| |
| // Write back the original value |
| Attributes::Viewport::TypeInfo::Type originalViewport = { 0, 0, 1920, 1080 }; |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::Viewport::Id, originalViewport), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::Viewport::Id, viewport), CHIP_NO_ERROR); |
| EXPECT_EQ(viewport.x1, 0); |
| EXPECT_EQ(viewport.y1, 0); |
| EXPECT_EQ(viewport.x2, 1920); |
| EXPECT_EQ(viewport.y2, 1080); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadWriteSpeakerMuted) |
| { |
| bool speakerMuted = false; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SpeakerMuted::Id, speakerMuted), CHIP_NO_ERROR); |
| EXPECT_TRUE(speakerMuted); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::SpeakerMuted::Id, false), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SpeakerMuted::Id, speakerMuted), CHIP_NO_ERROR); |
| EXPECT_FALSE(speakerMuted); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::SpeakerMuted::Id, true), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SpeakerMuted::Id, speakerMuted), CHIP_NO_ERROR); |
| EXPECT_TRUE(speakerMuted); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadWriteSpeakerVolumeLevel) |
| { |
| uint8_t speakerVolumeLevel = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SpeakerVolumeLevel::Id, speakerVolumeLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(speakerVolumeLevel, 1); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::SpeakerVolumeLevel::Id, (uint8_t) 100), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SpeakerVolumeLevel::Id, speakerVolumeLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(speakerVolumeLevel, 100); |
| |
| // Test boundaries |
| uint8_t minLevel = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SpeakerMinLevel::Id, minLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::SpeakerVolumeLevel::Id, minLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SpeakerVolumeLevel::Id, speakerVolumeLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(speakerVolumeLevel, minLevel); |
| |
| uint8_t maxLevel = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SpeakerMaxLevel::Id, maxLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::SpeakerVolumeLevel::Id, maxLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SpeakerVolumeLevel::Id, speakerVolumeLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(speakerVolumeLevel, maxLevel); |
| |
| // Test out of bounds |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::SpeakerVolumeLevel::Id, (uint8_t) (0)), |
| CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::SpeakerVolumeLevel::Id, (uint8_t) (255)), |
| CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::SpeakerVolumeLevel::Id, (uint8_t) 1), CHIP_NO_ERROR); // Restore default |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadSpeakerMaxLevel) |
| { |
| uint8_t speakerMaxLevel = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SpeakerMaxLevel::Id, speakerMaxLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(speakerMaxLevel, 254); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadSpeakerMinLevel) |
| { |
| uint8_t speakerMinLevel = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::SpeakerMinLevel::Id, speakerMinLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(speakerMinLevel, 1); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadWriteMicrophoneMuted) |
| { |
| bool microphoneMuted = false; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MicrophoneMuted::Id, microphoneMuted), CHIP_NO_ERROR); |
| EXPECT_TRUE(microphoneMuted); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::MicrophoneMuted::Id, false), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MicrophoneMuted::Id, microphoneMuted), CHIP_NO_ERROR); |
| EXPECT_FALSE(microphoneMuted); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::MicrophoneMuted::Id, true), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MicrophoneMuted::Id, microphoneMuted), CHIP_NO_ERROR); |
| EXPECT_TRUE(microphoneMuted); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadWriteMicrophoneVolumeLevel) |
| { |
| uint8_t microphoneVolumeLevel = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MicrophoneVolumeLevel::Id, microphoneVolumeLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(microphoneVolumeLevel, 1); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::MicrophoneVolumeLevel::Id, (uint8_t) 50), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MicrophoneVolumeLevel::Id, microphoneVolumeLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(microphoneVolumeLevel, 50); |
| |
| // Test boundaries |
| uint8_t minLevel = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MicrophoneMinLevel::Id, minLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::MicrophoneVolumeLevel::Id, minLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MicrophoneVolumeLevel::Id, microphoneVolumeLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(microphoneVolumeLevel, minLevel); |
| |
| uint8_t maxLevel = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MicrophoneMaxLevel::Id, maxLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::MicrophoneVolumeLevel::Id, maxLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MicrophoneVolumeLevel::Id, microphoneVolumeLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(microphoneVolumeLevel, maxLevel); |
| |
| // Test out of bounds |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::MicrophoneVolumeLevel::Id, (uint8_t) (0)), |
| CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::MicrophoneVolumeLevel::Id, (uint8_t) (255)), |
| CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::MicrophoneVolumeLevel::Id, (uint8_t) 1), CHIP_NO_ERROR); // Restore default |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadMicrophoneMaxLevel) |
| { |
| uint8_t microphoneMaxLevel = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MicrophoneMaxLevel::Id, microphoneMaxLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(microphoneMaxLevel, 254); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadMicrophoneMinLevel) |
| { |
| uint8_t microphoneMinLevel = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MicrophoneMinLevel::Id, microphoneMinLevel), CHIP_NO_ERROR); |
| EXPECT_EQ(microphoneMinLevel, 1); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadMicrophoneAGCEnabled) |
| { |
| bool microphoneAGCEnabled = false; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::MicrophoneAGCEnabled::Id, microphoneAGCEnabled), CHIP_NO_ERROR); |
| EXPECT_TRUE(microphoneAGCEnabled); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadWriteImageRotation) |
| { |
| uint16_t imageRotation = 1; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::ImageRotation::Id, imageRotation), CHIP_NO_ERROR); |
| EXPECT_EQ(imageRotation, 0); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::ImageRotation::Id, (uint16_t) 90), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::ImageRotation::Id, imageRotation), CHIP_NO_ERROR); |
| EXPECT_EQ(imageRotation, 90); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::ImageRotation::Id, (uint16_t) 180), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::ImageRotation::Id, imageRotation), CHIP_NO_ERROR); |
| EXPECT_EQ(imageRotation, 180); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::ImageRotation::Id, (uint16_t) 270), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::ImageRotation::Id, imageRotation), CHIP_NO_ERROR); |
| EXPECT_EQ(imageRotation, 270); |
| |
| // Test invalid value |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::ImageRotation::Id, (uint16_t) 360), CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::ImageRotation::Id, (uint16_t) 0), CHIP_NO_ERROR); // Restore default |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadWriteImageFlipHorizontal) |
| { |
| bool imageFlipHorizontal = true; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::ImageFlipHorizontal::Id, imageFlipHorizontal), CHIP_NO_ERROR); |
| EXPECT_FALSE(imageFlipHorizontal); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::ImageFlipHorizontal::Id, true), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::ImageFlipHorizontal::Id, imageFlipHorizontal), CHIP_NO_ERROR); |
| EXPECT_TRUE(imageFlipHorizontal); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::ImageFlipHorizontal::Id, false), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::ImageFlipHorizontal::Id, imageFlipHorizontal), CHIP_NO_ERROR); |
| EXPECT_FALSE(imageFlipHorizontal); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadWriteImageFlipVertical) |
| { |
| bool imageFlipVertical = true; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::ImageFlipVertical::Id, imageFlipVertical), CHIP_NO_ERROR); |
| EXPECT_FALSE(imageFlipVertical); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::ImageFlipVertical::Id, true), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::ImageFlipVertical::Id, imageFlipVertical), CHIP_NO_ERROR); |
| EXPECT_TRUE(imageFlipVertical); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::ImageFlipVertical::Id, false), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::ImageFlipVertical::Id, imageFlipVertical), CHIP_NO_ERROR); |
| EXPECT_FALSE(imageFlipVertical); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadWriteStatusLightEnabled) |
| { |
| bool statusLightEnabled = false; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::StatusLightEnabled::Id, statusLightEnabled), CHIP_NO_ERROR); |
| EXPECT_TRUE(statusLightEnabled); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::StatusLightEnabled::Id, false), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::StatusLightEnabled::Id, statusLightEnabled), CHIP_NO_ERROR); |
| EXPECT_FALSE(statusLightEnabled); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::StatusLightEnabled::Id, true), CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::StatusLightEnabled::Id, statusLightEnabled), CHIP_NO_ERROR); |
| EXPECT_TRUE(statusLightEnabled); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReadWriteStatusLightBrightness) |
| { |
| Attributes::StatusLightBrightness::TypeInfo::Type statusLightBrightness = Globals::ThreeLevelAutoEnum::kAuto; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::StatusLightBrightness::Id, statusLightBrightness), CHIP_NO_ERROR); |
| EXPECT_EQ(statusLightBrightness, Globals::ThreeLevelAutoEnum::kMedium); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::StatusLightBrightness::Id, Globals::ThreeLevelAutoEnum::kLow), |
| CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::StatusLightBrightness::Id, statusLightBrightness), CHIP_NO_ERROR); |
| EXPECT_EQ(statusLightBrightness, Globals::ThreeLevelAutoEnum::kLow); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::StatusLightBrightness::Id, Globals::ThreeLevelAutoEnum::kHigh), |
| CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::StatusLightBrightness::Id, statusLightBrightness), CHIP_NO_ERROR); |
| EXPECT_EQ(statusLightBrightness, Globals::ThreeLevelAutoEnum::kHigh); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::StatusLightBrightness::Id, Globals::ThreeLevelAutoEnum::kAuto), |
| CHIP_NO_ERROR); |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::StatusLightBrightness::Id, statusLightBrightness), CHIP_NO_ERROR); |
| EXPECT_EQ(statusLightBrightness, Globals::ThreeLevelAutoEnum::kAuto); |
| |
| EXPECT_EQ(mClusterTester.WriteAttribute(Attributes::StatusLightBrightness::Id, Globals::ThreeLevelAutoEnum::kMedium), |
| CHIP_NO_ERROR); // Restore default |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestAudioStreamAllocateCommand) |
| { |
| using Request = Commands::AudioStreamAllocate::Type; |
| using Response = Commands::AudioStreamAllocateResponse::DecodableType; |
| |
| mAudioStreams.clear(); |
| |
| Request request; |
| |
| // Happy path |
| request.streamUsage = StreamUsageEnum::kLiveView; |
| request.audioCodec = AudioCodecEnum::kOpus; |
| request.channelCount = 2; |
| request.sampleRate = 48000; |
| request.bitRate = 128000; |
| request.bitDepth = 24; |
| |
| auto result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_TRUE(result.IsSuccess()); |
| ASSERT_TRUE(result.response.has_value()); |
| EXPECT_EQ(result.response->audioStreamID, 1); |
| |
| // channelCount out of bounds |
| request.channelCount = 0; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| |
| request.channelCount = 9; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| request.channelCount = 2; // Restore |
| |
| // sampleRate 0 |
| request.sampleRate = 0; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| request.sampleRate = 48000; // Restore |
| |
| // bitRate 0 |
| request.bitRate = 0; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| request.bitRate = 128000; // Restore |
| |
| // Invalid bitDepth |
| request.bitDepth = 12; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| request.bitDepth = 24; // Restore |
| |
| // streamUsage not supported by test fixture priorities |
| request.streamUsage = StreamUsageEnum::kAnalysis; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::InvalidInState)); |
| request.streamUsage = StreamUsageEnum::kLiveView; // Restore |
| |
| // Attempt to allocate a second stream, expecting ResourceExhausted |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ResourceExhausted)); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestAudioStreamAllocateCommandUnsupportedBitDepth) |
| { |
| using Request = Commands::AudioStreamAllocate::Type; |
| using Response = Commands::AudioStreamAllocateResponse::DecodableType; |
| |
| mAudioStreams.clear(); |
| |
| Request request; |
| |
| // Unsupported bitDepth |
| request.streamUsage = StreamUsageEnum::kLiveView; |
| request.audioCodec = AudioCodecEnum::kOpus; |
| request.channelCount = 2; |
| request.sampleRate = 48000; |
| request.bitRate = 128000; |
| request.bitDepth = 8; // Unsupported bitDepth |
| |
| auto result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::DynamicConstraintError)); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestAudioStreamDeallocateCommand) |
| { |
| using AllocateRequest = Commands::AudioStreamAllocate::Type; |
| using AllocateResponse = Commands::AudioStreamAllocateResponse::DecodableType; |
| using DeallocateRequest = Commands::AudioStreamDeallocate::Type; |
| |
| mAudioStreams.clear(); |
| |
| // First, allocate a stream |
| AllocateRequest allocRequest; |
| allocRequest.streamUsage = StreamUsageEnum::kLiveView; |
| allocRequest.audioCodec = AudioCodecEnum::kOpus; |
| allocRequest.channelCount = 2; |
| allocRequest.sampleRate = 48000; |
| allocRequest.bitRate = 128000; |
| allocRequest.bitDepth = 24; |
| |
| auto allocResult = mClusterTester.Invoke<AllocateRequest, AllocateResponse>(allocRequest); |
| ASSERT_TRUE(allocResult.IsSuccess()); |
| ASSERT_TRUE(allocResult.response.has_value()); |
| uint16_t streamId = allocResult.response->audioStreamID; |
| EXPECT_EQ(mServer.GetAllocatedAudioStreams().size(), 1u); |
| |
| // Deallocate the stream |
| DeallocateRequest deallocRequest; |
| deallocRequest.audioStreamID = streamId; |
| auto deallocResult = mClusterTester.Invoke<DeallocateRequest, DataModel::NullObjectType>(deallocRequest); |
| EXPECT_TRUE(deallocResult.IsSuccess()); |
| EXPECT_EQ(mServer.GetAllocatedAudioStreams().size(), 0u); |
| |
| // Attempt to deallocate again, should fail |
| deallocResult = mClusterTester.Invoke<DeallocateRequest, DataModel::NullObjectType>(deallocRequest); |
| EXPECT_EQ(deallocResult.GetStatusCode(), ClusterStatusCode(Status::NotFound)); |
| |
| // Attempt to deallocate a non-existent ID |
| deallocRequest.audioStreamID = 999; |
| deallocResult = mClusterTester.Invoke<DeallocateRequest, DataModel::NullObjectType>(deallocRequest); |
| EXPECT_EQ(deallocResult.GetStatusCode(), ClusterStatusCode(Status::NotFound)); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestVideoStreamAllocateCommand) |
| { |
| using Request = Commands::VideoStreamAllocate::Type; |
| using Response = Commands::VideoStreamAllocateResponse::DecodableType; |
| |
| mVideoStreams.clear(); |
| |
| Request request; |
| |
| // Happy path |
| request.streamUsage = StreamUsageEnum::kLiveView; |
| request.videoCodec = VideoCodecEnum::kH264; |
| request.minFrameRate = 30; |
| request.maxFrameRate = GetVideoSensorParams().maxFPS; |
| request.minResolution = { 640, 480 }; |
| request.maxResolution = { 1280, 720 }; |
| request.minBitRate = 10000; |
| request.maxBitRate = 10000; |
| request.keyFrameInterval = 4; |
| request.watermarkEnabled = chip::MakeOptional(false); |
| |
| auto result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_TRUE(result.IsSuccess()); |
| ASSERT_TRUE(result.response.has_value()); |
| EXPECT_EQ(result.response->videoStreamID, 1); |
| EXPECT_EQ(mServer.GetAllocatedVideoStreams().size(), 1u); |
| |
| // Invalid streamUsage |
| request.streamUsage = StreamUsageEnum::kAnalysis; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::InvalidInState)); |
| request.streamUsage = StreamUsageEnum::kLiveView; // Restore |
| |
| // minFrameRate > maxFrameRate |
| request.minFrameRate = 121; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| request.minFrameRate = 30; // Restore |
| |
| // minResolution > maxResolution |
| request.minResolution = { 1281, 720 }; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| request.minResolution = { 640, 480 }; // Restore |
| |
| // minBitRate > maxBitRate |
| request.minBitRate = 10001; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| request.minBitRate = 10000; // Restore |
| |
| // keyFrameInterval 65501 |
| request.keyFrameInterval = 65501; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| request.keyFrameInterval = 4; // Restore |
| |
| // Unsupported videoCodec |
| request.videoCodec = VideoCodecEnum::kHevc; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::DynamicConstraintError)); |
| request.videoCodec = VideoCodecEnum::kH264; // Restore |
| |
| // Attempt to allocate a second stream, expecting ResourceExhausted |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ResourceExhausted)); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestVideoStreamDeallocateCommand) |
| { |
| using AllocateRequest = Commands::VideoStreamAllocate::Type; |
| using AllocateResponse = Commands::VideoStreamAllocateResponse::DecodableType; |
| using DeallocateRequest = Commands::VideoStreamDeallocate::Type; |
| |
| mVideoStreams.clear(); |
| |
| // First, allocate a stream |
| AllocateRequest allocRequest; |
| allocRequest.streamUsage = StreamUsageEnum::kLiveView; |
| allocRequest.videoCodec = VideoCodecEnum::kH264; |
| allocRequest.minFrameRate = 30; |
| allocRequest.maxFrameRate = 120; |
| allocRequest.minResolution = { 640, 480 }; |
| allocRequest.maxResolution = { 1280, 720 }; |
| allocRequest.minBitRate = 10000; |
| allocRequest.maxBitRate = 10000; |
| allocRequest.keyFrameInterval = 4; |
| allocRequest.watermarkEnabled = chip::MakeOptional(false); |
| |
| auto allocResult = mClusterTester.Invoke<AllocateRequest, AllocateResponse>(allocRequest); |
| ASSERT_TRUE(allocResult.IsSuccess()); |
| ASSERT_TRUE(allocResult.response.has_value()); |
| uint16_t streamId = allocResult.response->videoStreamID; |
| EXPECT_EQ(mServer.GetAllocatedVideoStreams().size(), 1u); |
| |
| // Deallocate the stream |
| DeallocateRequest deallocRequest; |
| deallocRequest.videoStreamID = streamId; |
| auto deallocResult = mClusterTester.Invoke<DeallocateRequest, DataModel::NullObjectType>(deallocRequest); |
| EXPECT_TRUE(deallocResult.IsSuccess()); |
| EXPECT_EQ(mServer.GetAllocatedVideoStreams().size(), 0u); |
| |
| // Attempt to deallocate again, should fail |
| deallocResult = mClusterTester.Invoke<DeallocateRequest, DataModel::NullObjectType>(deallocRequest); |
| EXPECT_EQ(deallocResult.GetStatusCode(), ClusterStatusCode(Status::NotFound)); |
| |
| // Attempt to deallocate a non-existent ID |
| deallocRequest.videoStreamID = 999; |
| deallocResult = mClusterTester.Invoke<DeallocateRequest, DataModel::NullObjectType>(deallocRequest); |
| EXPECT_EQ(deallocResult.GetStatusCode(), ClusterStatusCode(Status::NotFound)); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestVideoStreamModifyCommand) |
| { |
| using AllocateRequest = Commands::VideoStreamAllocate::Type; |
| using AllocateResponse = Commands::VideoStreamAllocateResponse::DecodableType; |
| using ModifyRequest = Commands::VideoStreamModify::Type; |
| |
| mVideoStreams.clear(); |
| |
| // First, allocate a stream |
| AllocateRequest allocRequest; |
| allocRequest.streamUsage = StreamUsageEnum::kLiveView; |
| allocRequest.videoCodec = VideoCodecEnum::kH264; |
| allocRequest.minFrameRate = 30; |
| allocRequest.maxFrameRate = 120; |
| allocRequest.minResolution = { 640, 480 }; |
| allocRequest.maxResolution = { 1280, 720 }; |
| allocRequest.minBitRate = 10000; |
| allocRequest.maxBitRate = 10000; |
| allocRequest.keyFrameInterval = 4; |
| allocRequest.watermarkEnabled = chip::MakeOptional(false); |
| |
| auto allocResult = mClusterTester.Invoke<AllocateRequest, AllocateResponse>(allocRequest); |
| ASSERT_TRUE(allocResult.IsSuccess()); |
| ASSERT_TRUE(allocResult.response.has_value()); |
| uint16_t streamId = allocResult.response->videoStreamID; |
| EXPECT_EQ(mServer.GetAllocatedVideoStreams().size(), 1u); |
| |
| // Modify the stream |
| ModifyRequest modifyRequest; |
| modifyRequest.videoStreamID = streamId; |
| modifyRequest.watermarkEnabled = chip::MakeOptional(true); |
| |
| auto modifyResult = mClusterTester.Invoke<ModifyRequest, DataModel::NullObjectType>(modifyRequest); |
| EXPECT_TRUE(modifyResult.IsSuccess()); |
| |
| // Verify the changes |
| Attributes::AllocatedVideoStreams::TypeInfo::DecodableType allocatedVideoStreams; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedVideoStreams::Id, allocatedVideoStreams), CHIP_NO_ERROR); |
| auto it = allocatedVideoStreams.begin(); |
| ASSERT_TRUE(it.Next()); |
| const auto & stream = it.GetValue(); |
| EXPECT_EQ(stream.videoStreamID, streamId); |
| EXPECT_TRUE(stream.watermarkEnabled.Value()); |
| EXPECT_FALSE(it.Next()); |
| EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); |
| |
| // Modify only watermark |
| modifyRequest.watermarkEnabled = chip::MakeOptional(false); |
| modifyRequest.OSDEnabled = chip::Optional<bool>::Missing(); |
| modifyResult = mClusterTester.Invoke<ModifyRequest, DataModel::NullObjectType>(modifyRequest); |
| EXPECT_TRUE(modifyResult.IsSuccess()); |
| |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedVideoStreams::Id, allocatedVideoStreams), CHIP_NO_ERROR); |
| it = allocatedVideoStreams.begin(); |
| ASSERT_TRUE(it.Next()); |
| const auto & stream2 = it.GetValue(); |
| EXPECT_EQ(stream2.videoStreamID, streamId); |
| EXPECT_FALSE(stream2.watermarkEnabled.Value()); |
| EXPECT_FALSE(it.Next()); |
| EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); |
| |
| // Attempt to modify a non-existent ID |
| modifyRequest.videoStreamID = 999; |
| modifyResult = mClusterTester.Invoke<ModifyRequest, DataModel::NullObjectType>(modifyRequest); |
| EXPECT_EQ(modifyResult.GetStatusCode(), ClusterStatusCode(Status::NotFound)); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestSnapshotStreamAllocateCommand) |
| { |
| using Request = Commands::SnapshotStreamAllocate::Type; |
| using Response = Commands::SnapshotStreamAllocateResponse::DecodableType; |
| |
| mSnapshotStreams.clear(); |
| |
| Request request; |
| |
| // Happy path |
| request.imageCodec = ImageCodecEnum::kJpeg; |
| request.maxFrameRate = 30; |
| request.minResolution = { 640, 480 }; |
| request.maxResolution = { 1280, 720 }; |
| request.quality = 80; |
| request.watermarkEnabled = chip::MakeOptional(false); |
| |
| auto result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_TRUE(result.IsSuccess()); |
| ASSERT_TRUE(result.response.has_value()); |
| EXPECT_EQ(result.response->snapshotStreamID, 1); |
| EXPECT_EQ(mServer.GetAllocatedSnapshotStreams().size(), 1u); |
| |
| // maxFrameRate 0 |
| request.maxFrameRate = 0; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| request.maxFrameRate = 30; // Restore |
| |
| // minResolution > maxResolution |
| request.minResolution = { 1281, 720 }; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| request.minResolution = { 640, 480 }; // Restore |
| |
| // quality 0 |
| request.quality = 0; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| request.quality = 80; // Restore |
| |
| // quality 101 |
| request.quality = 101; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ConstraintError)); |
| request.quality = 80; // Restore |
| |
| // Unsupported maxFrameRate |
| request.maxFrameRate = 200; |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::DynamicConstraintError)); |
| request.maxFrameRate = 30; // Restore |
| |
| // Attempt to allocate a second stream, expecting ResourceExhausted |
| result = mClusterTester.Invoke<Request, Response>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::ResourceExhausted)); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestSnapshotStreamDeallocateCommand) |
| { |
| using AllocateRequest = Commands::SnapshotStreamAllocate::Type; |
| using AllocateResponse = Commands::SnapshotStreamAllocateResponse::DecodableType; |
| using DeallocateRequest = Commands::SnapshotStreamDeallocate::Type; |
| |
| mSnapshotStreams.clear(); |
| |
| // First, allocate a stream |
| AllocateRequest allocRequest; |
| allocRequest.imageCodec = ImageCodecEnum::kJpeg; |
| allocRequest.maxFrameRate = 30; |
| allocRequest.minResolution = { 640, 480 }; |
| allocRequest.maxResolution = { 640, 480 }; |
| allocRequest.quality = 80; |
| allocRequest.watermarkEnabled = chip::MakeOptional(false); |
| |
| auto allocResult = mClusterTester.Invoke<AllocateRequest, AllocateResponse>(allocRequest); |
| ASSERT_TRUE(allocResult.IsSuccess()); |
| ASSERT_TRUE(allocResult.response.has_value()); |
| uint16_t streamId = allocResult.response->snapshotStreamID; |
| EXPECT_EQ(mServer.GetAllocatedSnapshotStreams().size(), 1u); |
| |
| // Deallocate the stream |
| DeallocateRequest deallocRequest; |
| deallocRequest.snapshotStreamID = streamId; |
| auto deallocResult = mClusterTester.Invoke<DeallocateRequest, DataModel::NullObjectType>(deallocRequest); |
| EXPECT_TRUE(deallocResult.IsSuccess()); |
| EXPECT_EQ(mServer.GetAllocatedSnapshotStreams().size(), 0u); |
| |
| // Attempt to deallocate again, should fail |
| deallocResult = mClusterTester.Invoke<DeallocateRequest, DataModel::NullObjectType>(deallocRequest); |
| EXPECT_EQ(deallocResult.GetStatusCode(), ClusterStatusCode(Status::NotFound)); |
| |
| // Attempt to deallocate a non-existent ID |
| deallocRequest.snapshotStreamID = 999; |
| deallocResult = mClusterTester.Invoke<DeallocateRequest, DataModel::NullObjectType>(deallocRequest); |
| EXPECT_EQ(deallocResult.GetStatusCode(), ClusterStatusCode(Status::NotFound)); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestSnapshotStreamModifyCommand) |
| { |
| using AllocateRequest = Commands::SnapshotStreamAllocate::Type; |
| using AllocateResponse = Commands::SnapshotStreamAllocateResponse::DecodableType; |
| using ModifyRequest = Commands::SnapshotStreamModify::Type; |
| |
| mSnapshotStreams.clear(); |
| |
| // First, allocate a stream |
| AllocateRequest allocRequest; |
| allocRequest.imageCodec = ImageCodecEnum::kJpeg; |
| allocRequest.maxFrameRate = 30; |
| allocRequest.minResolution = { 1280, 720 }; |
| allocRequest.maxResolution = { 1280, 720 }; |
| allocRequest.quality = 80; |
| allocRequest.watermarkEnabled = chip::MakeOptional(false); |
| |
| auto allocResult = mClusterTester.Invoke<AllocateRequest, AllocateResponse>(allocRequest); |
| ASSERT_TRUE(allocResult.IsSuccess()); |
| ASSERT_TRUE(allocResult.response.has_value()); |
| uint16_t streamId = allocResult.response->snapshotStreamID; |
| EXPECT_EQ(mServer.GetAllocatedSnapshotStreams().size(), 1u); |
| |
| // Modify the stream |
| ModifyRequest modifyRequest; |
| modifyRequest.snapshotStreamID = streamId; |
| modifyRequest.watermarkEnabled = chip::MakeOptional(true); |
| |
| auto modifyResult = mClusterTester.Invoke<ModifyRequest, DataModel::NullObjectType>(modifyRequest); |
| EXPECT_TRUE(modifyResult.IsSuccess()); |
| |
| // Verify the changes |
| Attributes::AllocatedSnapshotStreams::TypeInfo::DecodableType allocatedSnapshotStreams; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedSnapshotStreams::Id, allocatedSnapshotStreams), CHIP_NO_ERROR); |
| auto it = allocatedSnapshotStreams.begin(); |
| ASSERT_TRUE(it.Next()); |
| const auto & stream = it.GetValue(); |
| EXPECT_EQ(stream.snapshotStreamID, streamId); |
| EXPECT_TRUE(stream.watermarkEnabled.Value()); |
| EXPECT_FALSE(it.Next()); |
| EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); |
| |
| // Modify watermark |
| modifyRequest.watermarkEnabled = chip::MakeOptional(false); |
| modifyRequest.OSDEnabled = chip::Optional<bool>::Missing(); |
| modifyResult = mClusterTester.Invoke<ModifyRequest, DataModel::NullObjectType>(modifyRequest); |
| EXPECT_TRUE(modifyResult.IsSuccess()); |
| |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedSnapshotStreams::Id, allocatedSnapshotStreams), CHIP_NO_ERROR); |
| it = allocatedSnapshotStreams.begin(); |
| ASSERT_TRUE(it.Next()); |
| const auto & stream2 = it.GetValue(); |
| EXPECT_EQ(stream2.snapshotStreamID, streamId); |
| EXPECT_FALSE(stream2.watermarkEnabled.Value()); |
| EXPECT_FALSE(it.Next()); |
| EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); |
| |
| // Attempt to modify a non-existent ID |
| modifyRequest.snapshotStreamID = 999; |
| modifyResult = mClusterTester.Invoke<ModifyRequest, DataModel::NullObjectType>(modifyRequest); |
| EXPECT_EQ(modifyResult.GetStatusCode(), ClusterStatusCode(Status::NotFound)); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestSetStreamPrioritiesCommand) |
| { |
| using Request = Commands::SetStreamPriorities::Type; |
| |
| mVideoStreams.clear(); |
| mAudioStreams.clear(); |
| mSnapshotStreams.clear(); |
| |
| Request request; |
| std::vector<StreamUsageEnum> priorities; |
| |
| // Happy path |
| priorities.push_back(StreamUsageEnum::kRecording); |
| priorities.push_back(StreamUsageEnum::kLiveView); |
| request.streamPriorities = DataModel::List<const StreamUsageEnum>(priorities.data(), priorities.size()); |
| auto result = mClusterTester.Invoke<Request, DataModel::NullObjectType>(request); |
| EXPECT_TRUE(result.IsSuccess()); |
| |
| Attributes::StreamUsagePriorities::TypeInfo::DecodableType readPriorities; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::StreamUsagePriorities::Id, readPriorities), CHIP_NO_ERROR); |
| auto it = readPriorities.begin(); |
| ASSERT_TRUE(it.Next()); |
| EXPECT_EQ(it.GetValue(), StreamUsageEnum::kRecording); |
| ASSERT_TRUE(it.Next()); |
| EXPECT_EQ(it.GetValue(), StreamUsageEnum::kLiveView); |
| EXPECT_FALSE(it.Next()); |
| EXPECT_EQ(it.GetStatus(), CHIP_NO_ERROR); |
| |
| // Invalid enum value |
| priorities.clear(); |
| priorities.push_back(static_cast<StreamUsageEnum>(0xFF)); |
| request.streamPriorities = DataModel::List<const StreamUsageEnum>(priorities.data(), priorities.size()); |
| result = mClusterTester.Invoke<Request, DataModel::NullObjectType>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::InvalidCommand)); |
| |
| // Duplicate enum value |
| priorities.clear(); |
| priorities.push_back(StreamUsageEnum::kLiveView); |
| priorities.push_back(StreamUsageEnum::kLiveView); |
| request.streamPriorities = DataModel::List<const StreamUsageEnum>(priorities.data(), priorities.size()); |
| result = mClusterTester.Invoke<Request, DataModel::NullObjectType>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::AlreadyExists)); |
| |
| // Unsupported enum value |
| priorities.clear(); |
| priorities.push_back(StreamUsageEnum::kAnalysis); |
| request.streamPriorities = DataModel::List<const StreamUsageEnum>(priorities.data(), priorities.size()); |
| result = mClusterTester.Invoke<Request, DataModel::NullObjectType>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::DynamicConstraintError)); |
| |
| // Invalid state - stream allocated |
| { |
| using AllocateRequest = Commands::VideoStreamAllocate::Type; |
| using AllocateResponse = Commands::VideoStreamAllocateResponse::DecodableType; |
| |
| mVideoStreams.clear(); |
| |
| AllocateRequest allocRequest; |
| allocRequest.streamUsage = StreamUsageEnum::kLiveView; |
| allocRequest.videoCodec = VideoCodecEnum::kH264; |
| allocRequest.minFrameRate = 30; |
| allocRequest.maxFrameRate = 120; |
| allocRequest.minResolution = { 640, 480 }; |
| allocRequest.maxResolution = { 1280, 720 }; |
| allocRequest.minBitRate = 10000; |
| allocRequest.maxBitRate = 10000; |
| allocRequest.keyFrameInterval = 4; |
| allocRequest.watermarkEnabled = chip::MakeOptional(false); |
| |
| auto allocResult = mClusterTester.Invoke<AllocateRequest, AllocateResponse>(allocRequest); |
| ASSERT_TRUE(allocResult.IsSuccess()); |
| EXPECT_EQ(mServer.GetAllocatedVideoStreams().size(), 1u); |
| } |
| |
| priorities.clear(); |
| priorities.push_back(StreamUsageEnum::kLiveView); |
| request.streamPriorities = DataModel::List<const StreamUsageEnum>(priorities.data(), priorities.size()); |
| result = mClusterTester.Invoke<Request, DataModel::NullObjectType>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::InvalidInState)); |
| |
| // Clean up |
| { |
| using DeallocateRequest = Commands::VideoStreamDeallocate::Type; |
| DeallocateRequest deallocRequest; |
| deallocRequest.videoStreamID = 1; |
| auto deallocResult = mClusterTester.Invoke<DeallocateRequest, DataModel::NullObjectType>(deallocRequest); |
| EXPECT_TRUE(deallocResult.IsSuccess()); |
| EXPECT_EQ(mServer.GetAllocatedVideoStreams().size(), 0u); |
| |
| mVideoStreams.clear(); |
| } |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestCaptureSnapshotCommand) |
| { |
| using AllocateRequest = Commands::SnapshotStreamAllocate::Type; |
| using AllocateResponse = Commands::SnapshotStreamAllocateResponse::DecodableType; |
| using CaptureRequest = Commands::CaptureSnapshot::Type; |
| using CaptureResponse = Commands::CaptureSnapshotResponse::DecodableType; |
| |
| mSnapshotStreams.clear(); |
| |
| // First, allocate a stream |
| AllocateRequest allocRequest; |
| allocRequest.imageCodec = ImageCodecEnum::kJpeg; |
| allocRequest.maxFrameRate = 30; |
| allocRequest.minResolution = { 640, 480 }; |
| allocRequest.maxResolution = { 1280, 720 }; |
| allocRequest.quality = 80; |
| allocRequest.watermarkEnabled = chip::MakeOptional(false); |
| |
| auto allocResult = mClusterTester.Invoke<AllocateRequest, AllocateResponse>(allocRequest); |
| ASSERT_TRUE(allocResult.IsSuccess()); |
| ASSERT_TRUE(allocResult.response.has_value()); |
| uint16_t streamId = allocResult.response->snapshotStreamID; |
| EXPECT_EQ(mServer.GetAllocatedSnapshotStreams().size(), 1u); |
| |
| // Disable privacy modes to allow capture |
| EXPECT_EQ(mServer.SetSoftLivestreamPrivacyModeEnabled(false), CHIP_NO_ERROR); |
| EXPECT_EQ(mServer.SetHardPrivacyModeOn(false), CHIP_NO_ERROR); |
| |
| CaptureRequest request; |
| request.snapshotStreamID.SetNonNull(streamId); |
| request.requestedResolution = { 640, 480 }; |
| |
| auto result = mClusterTester.Invoke<CaptureRequest, CaptureResponse>(request); |
| EXPECT_TRUE(result.IsSuccess()); |
| ASSERT_TRUE(result.response.has_value()); |
| EXPECT_EQ(result.response->imageCodec, ImageCodecEnum::kJpeg); |
| EXPECT_EQ(result.response->resolution.width, 640); |
| EXPECT_EQ(result.response->resolution.height, 480); |
| EXPECT_GT(result.response->data.size(), 0u); |
| EXPECT_EQ(result.response->data.data()[0], 0xFF); // Basic check on dummy data |
| EXPECT_EQ(result.response->data.data()[1], 0xD8); // Basic check on dummy data |
| |
| // Restore privacy modes |
| EXPECT_EQ(mServer.SetSoftLivestreamPrivacyModeEnabled(true), CHIP_NO_ERROR); |
| EXPECT_EQ(mServer.SetHardPrivacyModeOn(true), CHIP_NO_ERROR); |
| |
| // Check for InvalidInState error |
| result = mClusterTester.Invoke<CaptureRequest, CaptureResponse>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::InvalidInState)); |
| |
| // Check for NotFound error with invalid stream ID |
| request.snapshotStreamID.SetNonNull(999); |
| result = mClusterTester.Invoke<CaptureRequest, CaptureResponse>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::NotFound)); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestCaptureSnapshotCommand_NoStreamAllocated) |
| { |
| using CaptureRequest = Commands::CaptureSnapshot::Type; |
| using CaptureResponse = Commands::CaptureSnapshotResponse::DecodableType; |
| |
| mSnapshotStreams.clear(); |
| |
| // Disable privacy modes to allow capture |
| EXPECT_EQ(mServer.SetSoftLivestreamPrivacyModeEnabled(false), CHIP_NO_ERROR); |
| EXPECT_EQ(mServer.SetHardPrivacyModeOn(false), CHIP_NO_ERROR); |
| |
| CaptureRequest request; |
| request.snapshotStreamID.SetNull(); |
| request.requestedResolution = { 640, 480 }; |
| |
| auto result = mClusterTester.Invoke<CaptureRequest, CaptureResponse>(request); |
| EXPECT_EQ(result.GetStatusCode(), ClusterStatusCode(Status::NotFound)); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestUpdateVideoStreamRefCount) |
| { |
| using AllocateRequest = Commands::VideoStreamAllocate::Type; |
| using AllocateResponse = Commands::VideoStreamAllocateResponse::DecodableType; |
| |
| mVideoStreams.clear(); |
| |
| // First, allocate a stream |
| AllocateRequest allocRequest; |
| allocRequest.streamUsage = StreamUsageEnum::kLiveView; |
| allocRequest.videoCodec = VideoCodecEnum::kH264; |
| allocRequest.minFrameRate = 30; |
| allocRequest.maxFrameRate = 120; |
| allocRequest.minResolution = { 640, 480 }; |
| allocRequest.maxResolution = { 1280, 720 }; |
| allocRequest.minBitRate = 10000; |
| allocRequest.maxBitRate = 10000; |
| allocRequest.keyFrameInterval = 4; |
| allocRequest.watermarkEnabled = chip::MakeOptional(false); |
| |
| auto allocResult = mClusterTester.Invoke<AllocateRequest, AllocateResponse>(allocRequest); |
| ASSERT_TRUE(allocResult.IsSuccess()); |
| ASSERT_TRUE(allocResult.response.has_value()); |
| uint16_t streamId = allocResult.response->videoStreamID; |
| EXPECT_EQ(mServer.GetAllocatedVideoStreams().size(), 1u); |
| |
| // Read initial referenceCount |
| Attributes::AllocatedVideoStreams::TypeInfo::DecodableType allocatedVideoStreams; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedVideoStreams::Id, allocatedVideoStreams), CHIP_NO_ERROR); |
| auto it = allocatedVideoStreams.begin(); |
| ASSERT_TRUE(it.Next()); |
| uint8_t initialRefCount = it.GetValue().referenceCount; |
| |
| // Increment |
| EXPECT_EQ(mServer.UpdateVideoStreamRefCount(streamId, true), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedVideoStreams::Id, allocatedVideoStreams), CHIP_NO_ERROR); |
| it = allocatedVideoStreams.begin(); |
| ASSERT_TRUE(it.Next()); |
| EXPECT_EQ(it.GetValue().referenceCount, static_cast<uint8_t>(initialRefCount + 1)); |
| |
| // Decrement back |
| EXPECT_EQ(mServer.UpdateVideoStreamRefCount(streamId, false), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedVideoStreams::Id, allocatedVideoStreams), CHIP_NO_ERROR); |
| it = allocatedVideoStreams.begin(); |
| ASSERT_TRUE(it.Next()); |
| EXPECT_EQ(it.GetValue().referenceCount, initialRefCount); |
| |
| // Non-existent stream ID |
| EXPECT_EQ(mServer.UpdateVideoStreamRefCount(999, true), CHIP_ERROR_NOT_FOUND); |
| |
| // Already back at zero after the round-trip, so decrement should fail |
| EXPECT_EQ(mServer.UpdateVideoStreamRefCount(streamId, false), CHIP_ERROR_INVALID_INTEGER_VALUE); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestUpdateAudioStreamRefCount) |
| { |
| using AllocateRequest = Commands::AudioStreamAllocate::Type; |
| using AllocateResponse = Commands::AudioStreamAllocateResponse::DecodableType; |
| |
| mAudioStreams.clear(); |
| |
| AllocateRequest allocRequest; |
| allocRequest.streamUsage = StreamUsageEnum::kLiveView; |
| allocRequest.audioCodec = AudioCodecEnum::kOpus; |
| allocRequest.channelCount = 2; |
| allocRequest.sampleRate = 48000; |
| allocRequest.bitRate = 128000; |
| allocRequest.bitDepth = 24; |
| |
| auto allocResult = mClusterTester.Invoke<AllocateRequest, AllocateResponse>(allocRequest); |
| ASSERT_TRUE(allocResult.IsSuccess()); |
| ASSERT_TRUE(allocResult.response.has_value()); |
| uint16_t streamId = allocResult.response->audioStreamID; |
| EXPECT_EQ(mServer.GetAllocatedAudioStreams().size(), 1u); |
| |
| Attributes::AllocatedAudioStreams::TypeInfo::DecodableType allocatedAudioStreams; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedAudioStreams::Id, allocatedAudioStreams), CHIP_NO_ERROR); |
| auto it = allocatedAudioStreams.begin(); |
| ASSERT_TRUE(it.Next()); |
| uint8_t initialRefCount = it.GetValue().referenceCount; |
| |
| // Increment and verify |
| EXPECT_EQ(mServer.UpdateAudioStreamRefCount(streamId, true), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedAudioStreams::Id, allocatedAudioStreams), CHIP_NO_ERROR); |
| it = allocatedAudioStreams.begin(); |
| ASSERT_TRUE(it.Next()); |
| EXPECT_EQ(it.GetValue().referenceCount, static_cast<uint8_t>(initialRefCount + 1)); |
| |
| // Decrement and verify |
| EXPECT_EQ(mServer.UpdateAudioStreamRefCount(streamId, false), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedAudioStreams::Id, allocatedAudioStreams), CHIP_NO_ERROR); |
| it = allocatedAudioStreams.begin(); |
| ASSERT_TRUE(it.Next()); |
| EXPECT_EQ(it.GetValue().referenceCount, initialRefCount); |
| |
| // Non-existent stream |
| EXPECT_EQ(mServer.UpdateAudioStreamRefCount(999, true), CHIP_ERROR_NOT_FOUND); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestUpdateSnapshotStreamRefCount) |
| { |
| using AllocateRequest = Commands::SnapshotStreamAllocate::Type; |
| using AllocateResponse = Commands::SnapshotStreamAllocateResponse::DecodableType; |
| |
| mSnapshotStreams.clear(); |
| |
| AllocateRequest allocRequest; |
| allocRequest.imageCodec = ImageCodecEnum::kJpeg; |
| allocRequest.maxFrameRate = 30; |
| allocRequest.minResolution = { 640, 480 }; |
| allocRequest.maxResolution = { 1280, 720 }; |
| allocRequest.quality = 80; |
| allocRequest.watermarkEnabled = chip::MakeOptional(false); |
| |
| auto allocResult = mClusterTester.Invoke<AllocateRequest, AllocateResponse>(allocRequest); |
| ASSERT_TRUE(allocResult.IsSuccess()); |
| ASSERT_TRUE(allocResult.response.has_value()); |
| uint16_t streamId = allocResult.response->snapshotStreamID; |
| EXPECT_EQ(mServer.GetAllocatedSnapshotStreams().size(), 1u); |
| |
| Attributes::AllocatedSnapshotStreams::TypeInfo::DecodableType allocatedSnapshotStreams; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedSnapshotStreams::Id, allocatedSnapshotStreams), CHIP_NO_ERROR); |
| auto it = allocatedSnapshotStreams.begin(); |
| ASSERT_TRUE(it.Next()); |
| uint8_t initialRefCount = it.GetValue().referenceCount; |
| |
| EXPECT_EQ(mServer.UpdateSnapshotStreamRefCount(streamId, true), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedSnapshotStreams::Id, allocatedSnapshotStreams), CHIP_NO_ERROR); |
| it = allocatedSnapshotStreams.begin(); |
| ASSERT_TRUE(it.Next()); |
| EXPECT_EQ(it.GetValue().referenceCount, static_cast<uint8_t>(initialRefCount + 1)); |
| |
| EXPECT_EQ(mServer.UpdateSnapshotStreamRefCount(streamId, false), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedSnapshotStreams::Id, allocatedSnapshotStreams), CHIP_NO_ERROR); |
| it = allocatedSnapshotStreams.begin(); |
| ASSERT_TRUE(it.Next()); |
| EXPECT_EQ(it.GetValue().referenceCount, initialRefCount); |
| |
| // Non-existent ID |
| EXPECT_EQ(mServer.UpdateSnapshotStreamRefCount(999, true), CHIP_ERROR_NOT_FOUND); |
| } |
| |
| TEST_F(TestCameraAVStreamManagementCluster, TestReferenceCountResetOnBoot) |
| { |
| // 1. Prepare data with non-zero referenceCount |
| VideoStreamStruct stream{}; |
| stream.videoStreamID = 1; |
| stream.referenceCount = 5; // Non-zero |
| |
| uint8_t buffer[kMaxAllocatedVideoStreamsSerializedSize]; |
| TLV::TLVWriter writer; |
| writer.Init(buffer); |
| |
| TLV::TLVType arrayType; |
| ASSERT_EQ(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, arrayType), CHIP_NO_ERROR); |
| ASSERT_EQ(DataModel::Encode(writer, TLV::AnonymousTag(), stream), CHIP_NO_ERROR); |
| ASSERT_EQ(writer.EndContainer(arrayType), CHIP_NO_ERROR); |
| |
| size_t len = writer.GetLengthWritten(); |
| |
| // 2. Write to persistence |
| ConcreteAttributePath path(kTestEndpointId, CameraAvStreamManagement::Id, Attributes::AllocatedVideoStreams::Id); |
| ASSERT_EQ(mPersistenceProvider.SafeWriteValue(path, ByteSpan(buffer, len)), CHIP_NO_ERROR); |
| |
| // 3. Trigger Init to load from persistence |
| ASSERT_EQ(mServer.Init(), CHIP_NO_ERROR); |
| |
| // 4. Verify in-memory state has reset refCount |
| Attributes::AllocatedVideoStreams::TypeInfo::DecodableType allocatedVideoStreams; |
| ASSERT_EQ(mClusterTester.ReadAttribute(Attributes::AllocatedVideoStreams::Id, allocatedVideoStreams), CHIP_NO_ERROR); |
| |
| auto it = allocatedVideoStreams.begin(); |
| ASSERT_TRUE(it.Next()); |
| EXPECT_EQ(it.GetValue().referenceCount, 0); |
| } |
| |
| } // namespace |