blob: 5a3b47225395cd5b9ab770683537f78a23363b4e [file] [log] [blame]
/*
* 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