* Copyright (c) 2025 Huawei Device Co., Ltd.
* 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 "video_processing_server.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <iservice_registry.h>
#include <system_ability_definition.h>
#include "algorithm_errors.h"
#include "surface_buffer.h"
#include "vpe_model_path.h"
#include "vpe_sa_constants.h"
using namespace OHOS::Media::VideoProcessingEngine;
using namespace OHOS;
using namespace std::placeholders;
using VpeAlgo = IVideoProcessingAlgorithm;
namespace {
const int VPE_INFO_FILE_MAX_LENGTH = 20485760;
const std::string UNLOAD_HANLDER = "unload_vpe_sa_handler";
const std::string UNLOAD_TASK_ID = "unload_vpe_sa";
constexpr int32_t DELAY_TIME = 180000;
REGISTER_SYSTEM_ABILITY_BY_ID(VideoProcessingServer, VIDEO_PROCESSING_SERVER_SA_ID, false);
}
VideoProcessingServer::VideoProcessingServer(int32_t saId, bool runOnCreate) : SystemAbility(saId, runOnCreate)
{
VPE_LOGD("VideoProcessingServer construct!");
}
VideoProcessingServer::~VideoProcessingServer()
{
VPE_LOGD("VideoProcessingServer destruction!");
}
ErrCode VideoProcessingServer::LoadInfo(int32_t key, SurfaceBufferInfo& bufferInfo)
{
if (key < 0 || key >= VPE_MODEL_KEY_NUM) {
VPE_LOGE("Input key %{public}d is invalid!", key);
UnloadVideoProcessingSA();
return ERR_INVALID_DATA;
}
std::string path = VPE_MODEL_PATHS[key];
VPE_LOGD("LoadInfoForVpe %{public}s", path.c_str());
bufferInfo.surfacebuffer = SurfaceBuffer::Create();
if (bufferInfo.surfacebuffer == nullptr) {
VPE_LOGE("Create surface buffer failed");
UnloadVideoProcessingSA();
return ERR_NULL_OBJECT;
}
std::unique_ptr<std::ifstream> fileStream = std::make_unique<std::ifstream>(path, std::ios::binary);
if (!fileStream->is_open()) {
VPE_LOGE("file is not open %{public}s", path.c_str());
UnloadVideoProcessingSA();
return ERR_NULL_OBJECT;
}
fileStream->seekg(0, std::ios::end);
int fileLength = fileStream->tellg();
fileStream->seekg(0, std::ios::beg);
if (fileLength < 0 || fileLength > VPE_INFO_FILE_MAX_LENGTH) {
VPE_LOGE("fileLength %{public}d is too short or too long!", fileLength);
UnloadVideoProcessingSA();
return ERR_INVALID_DATA;
}
BufferRequestConfig inputCfg;
inputCfg.width = fileLength;
inputCfg.height = 1;
VPE_LOGD("FileLength: %{public}d", fileLength);
inputCfg.strideAlignment = fileLength;
inputCfg.usage = BUFFER_USAGE_CPU_READ | BUFFER_USAGE_CPU_WRITE | BUFFER_USAGE_MEM_DMA;
inputCfg.format = GRAPHIC_PIXEL_FMT_YCBCR_420_SP;
inputCfg.timeout = 0;
GSError err = bufferInfo.surfacebuffer->Alloc(inputCfg);
if (err != GSERROR_OK) {
VPE_LOGE("Alloc surface buffer failed");
UnloadVideoProcessingSA();
return ERR_INVALID_DATA;
}
fileStream->read(reinterpret_cast<char*>(bufferInfo.surfacebuffer->GetVirAddr()), fileLength);
fileStream->close();
UnloadVideoProcessingSA();
return ERR_NONE;
}
ErrCode VideoProcessingServer::Create(const std::string& feature, const std::string& clientName, int32_t& clientID)
{
VPE_LOGD(">>> feature:%{public}s client:%{public}s", feature.c_str(), clientName.c_str());
uint32_t id;
std::lock_guard<std::mutex> lock(lock_);
auto ret = CreateLocked(feature, clientName, id);
if (ret == VPE_ALGO_ERR_OK) {
clientID = static_cast<int>(id);
VPE_LOGD("<<< feature:%{public}s client:%{public}s isWorking_:%{public}d {new ID:%{public}u}",
feature.c_str(), clientName.c_str(), isWorking_.load(), id);
}
DelayUnloadTaskLocked();
return ret;
}
ErrCode VideoProcessingServer::Destroy(int32_t clientID)
{
std::lock_guard<std::mutex> lock(lock_);
auto ret = DestroyLocked(static_cast<uint32_t>(clientID));
DelayUnloadTaskLocked();
return ret;
}
ErrCode VideoProcessingServer::SetParameter(int32_t clientID, int32_t tag, const std::vector<uint8_t>& parameter)
{
return Execute(clientID, std::bind(&VpeAlgo::SetParameter, _1, _2, tag, parameter), VPE_LOG_INFO);
}
ErrCode VideoProcessingServer::GetParameter(int32_t clientID, int32_t tag, std::vector<uint8_t>& parameter)
{
return Execute(clientID, std::bind(&VpeAlgo::GetParameter, _1, _2, tag, parameter), VPE_LOG_INFO);
}
ErrCode VideoProcessingServer::UpdateMetadata(int32_t clientID, SurfaceBufferInfo& image)
{
CHECK_AND_RETURN_RET_LOG(image.surfacebuffer != nullptr, VPE_ALGO_ERR_INVALID_PARAM,
"Invalid input: image is null!");
return Execute(clientID, std::bind(&VpeAlgo::UpdateMetadata, _1, _2, image), VPE_LOG_INFO);
}
ErrCode VideoProcessingServer::Process(int32_t clientID, const SurfaceBufferInfo& input, SurfaceBufferInfo& output)
{
CHECK_AND_RETURN_RET_LOG(input.surfacebuffer != nullptr && output.surfacebuffer != nullptr,
VPE_ALGO_ERR_INVALID_PARAM, "Invalid input: input or output is null!");
return Execute(clientID, std::bind(&VpeAlgo::Process, _1, _2, input, output), VPE_LOG_INFO);
}
ErrCode VideoProcessingServer::ComposeImage(int32_t clientID, const SurfaceBufferInfo& inputSdrImage,
const SurfaceBufferInfo& inputGainmap, SurfaceBufferInfo& outputHdrImage, bool legacy)
{
CHECK_AND_RETURN_RET_LOG(inputSdrImage.surfacebuffer != nullptr && inputGainmap.surfacebuffer != nullptr &&
outputHdrImage.surfacebuffer != nullptr, VPE_ALGO_ERR_INVALID_PARAM, "Invalid input: input or output is null!");
return Execute(clientID,
std::bind(&VpeAlgo::ComposeImage, _1, _2, inputSdrImage, inputGainmap, outputHdrImage, legacy), VPE_LOG_INFO);
}
ErrCode VideoProcessingServer::DecomposeImage(int32_t clientID, const SurfaceBufferInfo& inputImage,
SurfaceBufferInfo& outputSdrImage, SurfaceBufferInfo& outputGainmap)
{
CHECK_AND_RETURN_RET_LOG(inputImage.surfacebuffer != nullptr && outputSdrImage.surfacebuffer != nullptr &&
outputGainmap.surfacebuffer != nullptr, VPE_ALGO_ERR_INVALID_PARAM, "Invalid input: input or output is null!");
return Execute(clientID,
std::bind(&VpeAlgo::DecomposeImage, _1, _2, inputImage, outputSdrImage, outputGainmap), VPE_LOG_INFO);
}
void VideoProcessingServer::UnloadVideoProcessingSA()
{
if (CreateUnloadHandler()) {
VPE_LOGI("CreateUnloadHandler success!");
DelayUnloadTask();
} else {
return;
}
VPE_LOGD("Start/Update Delay Time Unload VPE SA!");
return;
}
void VideoProcessingServer::OnStart(const SystemAbilityOnDemandReason& startReason)
{
VPE_LOGD("Start VPE SA because %{public}s.", startReason.GetName().c_str());
if (CreateUnloadHandler()) {
VPE_LOGI("CreateUnloadHandler success!");
DelayUnloadTask();
}
CHECK_AND_RETURN_LOG(Publish(this), "Failed to publish SA!");
}
void VideoProcessingServer::OnStop(const SystemAbilityOnDemandReason& stopReason)
{
VPE_LOGD("Stop VPE SA because %{public}s.", stopReason.GetName().c_str());
DestroyUnloadHandler();
ClearAlgorithms();
}
ErrCode VideoProcessingServer::CreateLocked(const std::string& feature, const std::string& clientName, uint32_t& id)
{
AlgoPtr algo = nullptr;
bool isNew = false;
auto it = algorithms_.find(feature);
if (it == algorithms_.end() || it->second == nullptr) {
algo = factory_.Create(feature);
CHECK_AND_RETURN_RET_LOG(algo != nullptr, VPE_ALGO_ERR_NO_MEMORY,
"Failed to create '%{public}s' for '%{public}s'!", feature.c_str(), clientName.c_str());
CHECK_AND_RETURN_RET_LOG(algo->Initialize() == VPE_ALGO_ERR_OK, ERR_INVALID_STATE,
"Failed to initialize '%{public}s' for '%{public}s'!", feature.c_str(), clientName.c_str());
isNew = true;
} else {
algo = it->second;
}
CHECK_AND_RETURN_RET_LOG(algo->Add(clientName, id) == VPE_ALGO_ERR_OK, ERR_INVALID_DATA,
"Failed to add client to '%{public}s' for '%{public}s'!", feature.c_str(), clientName.c_str());
clients_[id] = feature;
if (isNew) {
algorithms_[feature] = algo;
}
isWorking_ = true;
return VPE_ALGO_ERR_OK;
}
ErrCode VideoProcessingServer::DestroyLocked(uint32_t id)
{
auto it = clients_.find(id);
if (it == clients_.end()) [[unlikely]] {
VPE_LOGE("Invalid input: no client for ID=%{public}d", id);
return VPE_ALGO_ERR_INVALID_CLIENT_ID;
}
std::string feature = it->second;
clients_.erase(it);
isWorking_ = !clients_.empty();
VPE_LOGD("isWorking_:%{public}d", isWorking_.load());
auto itAlgo = algorithms_.find(feature);
if (itAlgo == algorithms_.end()) [[unlikely]] {
VPE_LOGE("Invalid input: no '%{public}s' for ID=%{public}d", feature.c_str(), id);
return VPE_ALGO_ERR_INVALID_VAL;
}
if (itAlgo->second == nullptr) [[unlikely]] {
VPE_LOGE("Invalid input: null '%{public}s' for ID=%{public}d", feature.c_str(), id);
algorithms_.erase(itAlgo);
return VPE_ALGO_ERR_INVALID_VAL;
}
auto algo = itAlgo->second;
auto ret = algo->Del(id);
CHECK_AND_LOG(ret == VPE_ALGO_ERR_OK, "Failed to del(ID=%{public}d) of '%{public}s'", id, feature.c_str());
if (!algo->HasClient()) {
ret = algo->Deinitialize();
CHECK_AND_LOG(ret == VPE_ALGO_ERR_OK, "Failed to deinitialize of '%{public}s' for ID=%{public}d",
feature.c_str(), id);
algorithms_.erase(itAlgo);
}
return ret;
}
bool VideoProcessingServer::CreateUnloadHandler()
{
std::lock_guard<std::mutex> lock(lock_);
return CreateUnloadHandlerLocked();
}
bool VideoProcessingServer::CreateUnloadHandlerLocked()
{
if (unloadHandler_ != nullptr) {
return true;
}
unloadHandler_ = std::make_shared<AppExecFwk::EventHandler>(AppExecFwk::EventRunner::Create(UNLOAD_HANLDER));
return unloadHandler_ != nullptr;
}
void VideoProcessingServer::DestroyUnloadHandler()
{
std::lock_guard<std::mutex> lock(lock_);
if (unloadHandler_ != nullptr) {
unloadHandler_->RemoveAllEvents();
unloadHandler_->RemoveTask(UNLOAD_TASK_ID);
unloadHandler_ = nullptr;
}
}
void VideoProcessingServer::DelayUnloadTask()
{
std::lock_guard<std::mutex> lock(lock_);
DelayUnloadTaskLocked();
}
void VideoProcessingServer::DelayUnloadTaskLocked()
{
VPE_LOGD("delay unload task begin, isWorking_:%{public}d", isWorking_.load());
CHECK_AND_RETURN_LOG(CreateUnloadHandlerLocked(), "unloadHandler_ is NOT created!");
unloadHandler_->RemoveTask(UNLOAD_TASK_ID);
VPE_LOGD("delay unload task post task(wait %{public}dms)", DELAY_TIME);
auto task = [this]() {
VPE_LOGD("do unload task, isWorking_:%{public}d", isWorking_.load());
auto samgr = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
CHECK_AND_RETURN_LOG(samgr != nullptr, "Failed to GetSystemAbilityManager!");
CHECK_AND_RETURN_LOG(samgr->UnloadSystemAbility(VIDEO_PROCESSING_SERVER_SA_ID) == ERR_OK,
"Failed to unload VPE SA!");
VPE_LOGI("kill VPE service success!");
};
unloadHandler_->PostTask(task, UNLOAD_TASK_ID, DELAY_TIME);
}
void VideoProcessingServer::ClearAlgorithms()
{
std::lock_guard<std::mutex> lock(lock_);
for (auto& [feature, algo] : algorithms_) {
if (algo == nullptr) [[unlikely]] {
VPE_LOGE("algorithm is null of '%{public}s'!", feature.c_str());
continue;
}
if (algo->Deinitialize() != VPE_ALGO_ERR_OK) [[unlikely]] {
VPE_LOGE("Failed to deinitialize of '%{public}s'!", feature.c_str());
continue;
}
}
algorithms_.clear();
clients_.clear();
isWorking_ = false;
VPE_LOGD("isWorking_:%{public}d", isWorking_.load());
}
ErrCode VideoProcessingServer::Execute(int clientID, std::function<int(AlgoPtr&, uint32_t)>&& operation,
const LogInfo& logInfo)
{
uint32_t id = static_cast<uint32_t>(clientID);
AlgoPtr algorithm;
{
std::lock_guard<std::mutex> lock(lock_);
auto it = clients_.find(id);
if (it == clients_.end()) [[unlikely]] {
VPE_ORG_LOGE(logInfo, "Invalid input: no client for ID=%{public}d!", id);
DelayUnloadTaskLocked();
return VPE_ALGO_ERR_INVALID_CLIENT_ID;
}
auto itAlgo = algorithms_.find(it->second);
if (itAlgo == algorithms_.end() || itAlgo->second == nullptr) [[unlikely]] {
VPE_ORG_LOGE(logInfo, "Invalid input: no '%{public}s' for ID=%{public}d!", it->second.c_str(), id);
DelayUnloadTaskLocked();
return VPE_ALGO_ERR_INVALID_VAL;
}
algorithm = itAlgo->second;
}
auto err = operation(algorithm, id);
DelayUnloadTask();
return err;
}