| /* |
| * Copyright (c) 2025 Project CHIP Authors |
| * All rights reserved. |
| * |
| * 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 "AVStreamManagement.h" |
| #include "DeviceManager.h" |
| |
| using namespace ::chip; |
| |
| namespace { |
| |
| constexpr uint16_t kMinFrameRate = 30; |
| constexpr uint16_t kMaxFrameRate = 120; |
| constexpr uint32_t kDefaultBitRate = 10000; // bits per second |
| constexpr uint16_t kKeyFrameInterval = 4000; |
| constexpr uint16_t kMinWidth = 640; |
| constexpr uint16_t kMinHeight = 360; |
| constexpr uint16_t kMaxWidth = 1920; |
| constexpr uint16_t kMaxHeight = 1080; |
| |
| } // namespace |
| |
| namespace camera { |
| |
| void AVStreamManagement::Init(Controller::DeviceCommissioner * commissioner) |
| { |
| // Ensure that mCommissioner is not already initialized |
| VerifyOrDie(mCommissioner == nullptr); |
| |
| ChipLogProgress(Camera, "Initilize CommissionerControl"); |
| mCommissioner = commissioner; |
| } |
| |
| CHIP_ERROR AVStreamManagement::AllocateVideoStream(NodeId nodeId, EndpointId endpointId, uint8_t streamUsage, |
| Optional<uint16_t> minResWidth, Optional<uint16_t> minResHeight, |
| Optional<uint16_t> minFrameRate, Optional<uint32_t> minBitRate) |
| { |
| VerifyOrReturnError(mCommissioner != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| |
| ChipLogProgress(Camera, "Sending VideoStreamAllocate to (node=0x" ChipLogFormatX64 ", ep=%u, usage=%u)", |
| ChipLogValueX64(nodeId), endpointId, streamUsage); |
| |
| // Clear any stale data from previous requests. |
| mVideoStreamAllocate = {}; |
| |
| mVideoStreamAllocate.streamUsage = static_cast<app::Clusters::Globals::StreamUsageEnum>(streamUsage); |
| mVideoStreamAllocate.videoCodec = |
| app::Clusters::CameraAvStreamManagement::VideoCodecEnum::kH264; // Default to H.264; adjust as needed. |
| |
| // Handle frame rate configuration with validation |
| uint16_t requestedMinFrameRate = minFrameRate.ValueOr(kMinFrameRate); |
| if (requestedMinFrameRate > kMaxFrameRate) |
| { |
| ChipLogProgress(Camera, "Requested min frame rate (%u) exceeds max frame rate (%u), clamping to max", requestedMinFrameRate, |
| kMaxFrameRate); |
| requestedMinFrameRate = kMaxFrameRate; |
| } |
| |
| mVideoStreamAllocate.minFrameRate = requestedMinFrameRate; |
| mVideoStreamAllocate.maxFrameRate = kMaxFrameRate; |
| |
| // Handle resolution configuration with validation |
| uint16_t requestedMinWidth = minResWidth.ValueOr(kMinWidth); |
| uint16_t requestedMinHeight = minResHeight.ValueOr(kMinHeight); |
| |
| // Ensure min values don't exceed max values |
| if (requestedMinWidth > kMaxWidth) |
| { |
| ChipLogProgress(Camera, "Requested min width (%u) exceeds max width (%u), clamping to max", requestedMinWidth, kMaxWidth); |
| requestedMinWidth = kMaxWidth; |
| } |
| |
| if (requestedMinHeight > kMaxHeight) |
| { |
| ChipLogProgress(Camera, "Requested min height (%u) exceeds max height (%u), clamping to max", requestedMinHeight, |
| kMaxHeight); |
| requestedMinHeight = kMaxHeight; |
| } |
| |
| mVideoStreamAllocate.minResolution = { .width = requestedMinWidth, .height = requestedMinHeight }; |
| mVideoStreamAllocate.maxResolution = { .width = kMaxWidth, .height = kMaxHeight }; |
| |
| // Handle bit rate configuration with validation |
| uint32_t requestedMinBitRate = minBitRate.ValueOr(kDefaultBitRate); |
| if (requestedMinBitRate > kDefaultBitRate) |
| { |
| // If requested min bit rate is higher than default, use it as both min and max |
| mVideoStreamAllocate.minBitRate = requestedMinBitRate; |
| mVideoStreamAllocate.maxBitRate = requestedMinBitRate; |
| ChipLogProgress(Camera, "Using custom bit rate: %u (both min and max)", requestedMinBitRate); |
| } |
| else |
| { |
| // Use requested min bit rate and default max bit rate |
| mVideoStreamAllocate.minBitRate = requestedMinBitRate; |
| mVideoStreamAllocate.maxBitRate = kDefaultBitRate; |
| } |
| |
| mVideoStreamAllocate.keyFrameInterval = kKeyFrameInterval; |
| |
| mVideoStreamAllocate.watermarkEnabled.SetValue(false); |
| mVideoStreamAllocate.OSDEnabled.SetValue(false); |
| |
| mEndpointId = endpointId; |
| mCommandType = CommandType::kVideoStreamAllocate; |
| return mCommissioner->GetConnectedDevice(nodeId, &mOnConnectedCallback, &mOnConnectionFailureCallback); |
| } |
| |
| CHIP_ERROR AVStreamManagement::DeallocateVideoStream(NodeId nodeId, EndpointId endpointId, uint16_t videoStreamID) |
| { |
| VerifyOrReturnError(mCommissioner != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| |
| ChipLogProgress(Camera, "Sending VideoStreamDeallocate to (node=0x" ChipLogFormatX64 ", ep=%u, videoStreamID=%u)", |
| ChipLogValueX64(nodeId), endpointId, videoStreamID); |
| |
| mVideoStreamDeallocate.videoStreamID = videoStreamID; |
| |
| mEndpointId = endpointId; |
| mCommandType = CommandType::kVideoStreamDeallocate; |
| return mCommissioner->GetConnectedDevice(nodeId, &mOnConnectedCallback, &mOnConnectionFailureCallback); |
| } |
| |
| void AVStreamManagement::OnResponse(app::CommandSender * client, const app::ConcreteCommandPath & path, |
| const app::StatusIB & status, TLV::TLVReader * data) |
| { |
| ChipLogProgress(Camera, "AVStreamManagement: OnResponse."); |
| |
| CHIP_ERROR error = status.ToChipError(); |
| if (CHIP_NO_ERROR != error) |
| { |
| ChipLogError(Camera, "Response Failure: %s", ErrorStr(error)); |
| return; |
| } |
| |
| if (data != nullptr) |
| { |
| DeviceManager::Instance().HandleCommandResponse(path, *data); |
| } |
| } |
| |
| void AVStreamManagement::OnError(const app::CommandSender * client, CHIP_ERROR error) |
| { |
| // Handle the error, then reset mCommandSender |
| ChipLogError(Camera, "AVStreamManagement: OnError. Error: %" CHIP_ERROR_FORMAT, error.Format()); |
| } |
| |
| void AVStreamManagement::OnDone(app::CommandSender * client) |
| { |
| ChipLogProgress(Camera, "AVStreamManagement: OnDone."); |
| |
| switch (mCommandType) |
| { |
| case CommandType::kVideoStreamAllocate: |
| ChipLogProgress(Camera, "AVStreamManagement: Command VideoStreamAllocate has been successfully processed."); |
| break; |
| |
| case CommandType::kVideoStreamDeallocate: |
| ChipLogProgress(Camera, "AVStreamManagement: Command VideoStreamDeallocate has been successfully processed."); |
| break; |
| |
| default: |
| ChipLogError(Camera, "AVStreamManagement: Unknown or unhandled command type in OnDone."); |
| break; |
| } |
| |
| // Reset command type to undefined after processing is done |
| mCommandType = CommandType::kUndefined; |
| |
| // Ensure that mCommandSender is cleaned up after it is done |
| mCommandSender.reset(); |
| } |
| |
| CHIP_ERROR AVStreamManagement::SendCommandForType(CommandType commandType, DeviceProxy * device) |
| { |
| ChipLogProgress(AppServer, "Sending command with Endpoint ID: %d, Command Type: %d", mEndpointId, |
| static_cast<int>(commandType)); |
| |
| switch (commandType) |
| { |
| case CommandType::kVideoStreamAllocate: |
| return SendCommand(device, mEndpointId, app::Clusters::CameraAvStreamManagement::Id, |
| app::Clusters::CameraAvStreamManagement::Commands::VideoStreamAllocate::Id, mVideoStreamAllocate); |
| case CommandType::kVideoStreamDeallocate: |
| return SendCommand(device, mEndpointId, app::Clusters::CameraAvStreamManagement::Id, |
| app::Clusters::CameraAvStreamManagement::Commands::VideoStreamDeallocate::Id, mVideoStreamDeallocate); |
| default: |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| } |
| |
| void AVStreamManagement::OnDeviceConnectedFn(void * context, Messaging::ExchangeManager & exchangeMgr, |
| const SessionHandle & sessionHandle) |
| { |
| AVStreamManagement * self = reinterpret_cast<AVStreamManagement *>(context); |
| VerifyOrReturn(self != nullptr, ChipLogError(Camera, "OnDeviceConnectedFn: context is null")); |
| |
| OperationalDeviceProxy device(&exchangeMgr, sessionHandle); |
| |
| CHIP_ERROR err = self->SendCommandForType(self->mCommandType, &device); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Camera, "Failed to send AVStreamManagement command."); |
| self->OnDone(nullptr); |
| } |
| } |
| |
| void AVStreamManagement::OnDeviceConnectionFailureFn(void * context, const ScopedNodeId & peerId, CHIP_ERROR err) |
| { |
| LogErrorOnFailure(err); |
| AVStreamManagement * self = reinterpret_cast<AVStreamManagement *>(context); |
| VerifyOrReturn(self != nullptr, ChipLogError(Camera, "OnDeviceConnectedFn: context is null")); |
| self->OnDone(nullptr); |
| } |
| |
| } // namespace camera |