mirror of
https://github.com/openharmony/applications_sample_camera.git
synced 2026-07-21 00:25:59 -04:00
-20
@@ -1,20 +0,0 @@
|
||||
# Copyright (c) 2021 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.
|
||||
import("//build/lite/config/component/lite_component.gni")
|
||||
|
||||
lite_component("ai") {
|
||||
features = [
|
||||
"asr:asr",
|
||||
"cv:cv",
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
# Copyright (c) 2021 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.
|
||||
import("//build/lite/config/component/lite_component.gni")
|
||||
|
||||
lite_component("asr") {
|
||||
features = [ "keyword_spotting:kws" ]
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
# Copyright (c) 2021 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.
|
||||
import("//build/lite/config/component/lite_component.gni")
|
||||
|
||||
executable("kws_app") {
|
||||
sources = [
|
||||
"audio_cache.cpp",
|
||||
"kws_app.cpp",
|
||||
"kws_manager.cpp",
|
||||
]
|
||||
cflags = [ "-Wall" ]
|
||||
cflags_cc = cflags
|
||||
include_dirs = [
|
||||
"//foundation/ai/ai_engine/services/common/platform/os_wrapper/audio_loader/include",
|
||||
"//foundation/ai/ai_engine/services/common/platform/os_wrapper/audio_loader/include/codec",
|
||||
"//foundation/ai/ai_engine/interfaces/kits/asr/keyword_spotting",
|
||||
"//foundation/ai/ai_engine/interfaces/kits",
|
||||
"//foundation/multimedia/interfaces/kits/audio_lite",
|
||||
"//foundation/ai/ai_engine/services/common/utils",
|
||||
"//foundation/ai/ai_engine/services/common/platform/utils",
|
||||
"//foundation/ai/ai_engine/services/common/platform/os_wrapper/utils",
|
||||
"//third_party/bounds_checking_function/include",
|
||||
]
|
||||
ldflags = [
|
||||
"-lstdc++",
|
||||
"-lpthread",
|
||||
"-lcodec",
|
||||
]
|
||||
deps = [
|
||||
"//foundation/ai/ai_engine/services/client/algorithm_sdk/asr:asr",
|
||||
"//foundation/ai/ai_engine/services/common/platform/os_wrapper/audio_loader:audio_loader",
|
||||
"//foundation/multimedia/audio_lite/frameworks:audio_capturer_lite",
|
||||
"//foundation/multimedia/media_utils_lite:media_common",
|
||||
"//third_party/bounds_checking_function:libsec_static",
|
||||
]
|
||||
output_dir = "$root_out_dir/dev_tools"
|
||||
}
|
||||
|
||||
lite_component("kws") {
|
||||
features = [ ":kws_app" ]
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 "audio_cache.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "securec.h"
|
||||
|
||||
namespace KWS {
|
||||
const int32_t MAX_BUFFER_SIZE = 36000;
|
||||
const int32_t MIN_BUFFER_SIZE = 8000;
|
||||
|
||||
AudioCache::AudioCache()
|
||||
: maxBufferSize_(0), left_(0), right_(0), buffer_(nullptr), prepared_(false)
|
||||
{
|
||||
}
|
||||
|
||||
AudioCache::~AudioCache()
|
||||
{
|
||||
if (prepared_ && buffer_ != nullptr) {
|
||||
delete[] buffer_;
|
||||
buffer_ = nullptr;
|
||||
}
|
||||
prepared_ = false;
|
||||
maxBufferSize_ = 0;
|
||||
left_ = 0;
|
||||
right_ = 0;
|
||||
}
|
||||
|
||||
bool AudioCache::Init(int32_t maxSize)
|
||||
{
|
||||
if (prepared_) {
|
||||
printf("[AudioCache]Cache has ready init.\n");
|
||||
return false;
|
||||
}
|
||||
if (maxSize > MAX_BUFFER_SIZE || maxSize < MIN_BUFFER_SIZE) {
|
||||
printf("[AudioCache]maxSize out of range, init failed.\n");
|
||||
return false;
|
||||
}
|
||||
maxBufferSize_ = maxSize;
|
||||
buffer_ = new (std::nothrow) uint8_t[maxBufferSize_];
|
||||
if (buffer_ == nullptr) {
|
||||
printf("[AudioCache]Fail to allocate buffer for given size.\n");
|
||||
return false;
|
||||
}
|
||||
errno_t ret = memset_s(buffer_, maxBufferSize_, 0x00, maxBufferSize_);
|
||||
if (ret != EOK) {
|
||||
printf("[AudioCache]Cache buffer init failed.\n");
|
||||
return false;
|
||||
}
|
||||
prepared_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AudioCache::AppendBuffer(int32_t bufferSize, const uint8_t *buffer)
|
||||
{
|
||||
if (!prepared_) {
|
||||
return false;
|
||||
}
|
||||
if (bufferSize + right_ >= maxBufferSize_) {
|
||||
left_ = 0;
|
||||
right_ = 0;
|
||||
}
|
||||
size_t remainLength = maxBufferSize_ - right_;
|
||||
size_t length = (bufferSize > remainLength) ? remainLength : bufferSize;
|
||||
size_t copySize = sizeof(uint8_t) * length;
|
||||
errno_t ret = memcpy_s(&buffer_[right_], copySize, buffer, copySize);
|
||||
if (ret != EOK) {
|
||||
printf("[AudioCache]AppendBuffer failed.\n");
|
||||
return false;
|
||||
}
|
||||
right_ += length;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t AudioCache::GetCapturedBuffer(uintptr_t &samplePtr)
|
||||
{
|
||||
if (right_ - left_ <= 0 || buffer_ == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
samplePtr = reinterpret_cast<uintptr_t>(buffer_ + left_);
|
||||
size_t length = right_ - left_;
|
||||
left_ = right_;
|
||||
return length;
|
||||
}
|
||||
} // namespace KWS
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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.
|
||||
*/
|
||||
|
||||
#ifndef AUDIO_CACHE_H
|
||||
#define AUDIO_CACHE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
|
||||
namespace KWS {
|
||||
class AudioCache {
|
||||
public:
|
||||
AudioCache();
|
||||
virtual ~AudioCache();
|
||||
bool Init(int32_t maxSize);
|
||||
size_t GetCapturedBuffer(uintptr_t &samplePtr);
|
||||
bool AppendBuffer(int32_t bufferSize, const uint8_t *buffer);
|
||||
|
||||
private:
|
||||
int32_t maxBufferSize_;
|
||||
int32_t left_;
|
||||
int32_t right_;
|
||||
uint8_t *buffer_;
|
||||
bool prepared_;
|
||||
};
|
||||
} // namespace KWS
|
||||
#endif // AUDIO_CACHE_H
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 <cstdio>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "kws_manager.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace KWS;
|
||||
|
||||
static void SampleHelp()
|
||||
{
|
||||
printf("****************************************\n");
|
||||
printf("Select the behavior of KWS app.\n");
|
||||
printf("s: AudioCap (Press q to quit)\n");
|
||||
printf("q: quit the sample.\n");
|
||||
printf("****************************************\n");
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
printf("Keyword spotting started.\n");
|
||||
SampleHelp();
|
||||
|
||||
shared_ptr<KwsManager> kwsMgr = make_shared<KwsManager>(AUDIO_SAMPLE_RATE, AUDIO_CODEC_BITRATE);
|
||||
if (kwsMgr == nullptr) {
|
||||
printf("Keyword spotting failed to allocate KWSManager.\n");
|
||||
return -1;
|
||||
}
|
||||
if (!kwsMgr->Prepare()) {
|
||||
printf("Keyword spotting failed to prepare KWSManager.\n");
|
||||
return -1;
|
||||
}
|
||||
char input = ' ';
|
||||
while (cin >> input) {
|
||||
switch (input) {
|
||||
case 's':
|
||||
kwsMgr->Start();
|
||||
break;
|
||||
case 'q':
|
||||
kwsMgr->Stop();
|
||||
printf("Keyword spotting Terminated.\n");
|
||||
return 0;
|
||||
default:
|
||||
SampleHelp();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
#include "kws_manager.h"
|
||||
|
||||
using namespace OHOS::AI;
|
||||
using namespace OHOS::Audio;
|
||||
using namespace OHOS::Media;
|
||||
|
||||
namespace KWS {
|
||||
namespace {
|
||||
const int32_t CONFIDENCE = 2662;
|
||||
const int32_t MAX_CACHE_SIZE = 16000;
|
||||
const int32_t SUCCESS = 0;
|
||||
const int32_t CHANNEL_COUNT = 1;
|
||||
|
||||
using Capturer = std::shared_ptr<AudioCapturer>;
|
||||
using Cache = std::shared_ptr<AudioCache>;
|
||||
using Plugin = std::shared_ptr<KWSSdk>;
|
||||
|
||||
// Can not create KwsManager twice
|
||||
std::shared_ptr<std::thread> g_producer = nullptr;
|
||||
std::shared_ptr<std::thread> g_consumer = nullptr;
|
||||
const auto DELETE_ARRAY = [](uint8_t *array) {
|
||||
if (array != nullptr) {
|
||||
delete[] array;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class MyKwsCallback : public KWSCallback {
|
||||
public:
|
||||
MyKwsCallback() {}
|
||||
~MyKwsCallback() {}
|
||||
|
||||
void OnError(int32_t errorCode)
|
||||
{
|
||||
printf("Executing error, error code: %d\n", errorCode);
|
||||
}
|
||||
|
||||
void OnResult(const Array<int32_t> &result)
|
||||
{
|
||||
if (result.size != WORD_CONTENT.size()) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < result.size; ++i) {
|
||||
if (result.data[i] > CONFIDENCE) {
|
||||
printf("[%s]\n", WORD_CONTENT[i].c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void ThreadTask::AudioProducer(KwsManager *kwsManager)
|
||||
{
|
||||
kwsManager->ProduceSamples();
|
||||
}
|
||||
|
||||
void ThreadTask::AudioConsumer(KwsManager *kwsManager)
|
||||
{
|
||||
kwsManager->ConsumeSamples();
|
||||
}
|
||||
|
||||
KwsManager::KwsManager(int32_t sampleRate, int32_t bitRate)
|
||||
: sampleRate_(sampleRate), bitRate_(bitRate)
|
||||
{
|
||||
status_ = IDLE;
|
||||
}
|
||||
|
||||
KwsManager::~KwsManager()
|
||||
{
|
||||
OnStop();
|
||||
status_ = IDLE;
|
||||
}
|
||||
|
||||
void KwsManager::ProduceSamples()
|
||||
{
|
||||
printf("[KwsManager]ProduceSamples start\n");
|
||||
if (capturer_ == nullptr || cache_ == nullptr || aacHandler_ == -1) {
|
||||
printf("[KwsManager]Produce AudioSample failed for nullptr\n");
|
||||
return;
|
||||
}
|
||||
int32_t readLen = 0;
|
||||
int32_t retCode = SUCCESS;
|
||||
size_t frameSize = capturer_->GetFrameCount() * sizeof(uint16_t);
|
||||
if (frameSize <= 0) {
|
||||
printf("[KwsManager]Capturer get frame count failed.\n");
|
||||
return;
|
||||
}
|
||||
CoderStream aacStream = {
|
||||
.buffer = new (std::nothrow) uint8_t[frameSize],
|
||||
.size = frameSize
|
||||
};
|
||||
CoderStream pcmStream = {
|
||||
.buffer = new (std::nothrow) uint8_t[frameSize],
|
||||
.size = frameSize
|
||||
};
|
||||
if (aacStream.buffer == nullptr || pcmStream.buffer == nullptr) {
|
||||
printf("[KwsManager]Allocate buffer for aacStream and pcmStream failed.\n");
|
||||
DELETE_ARRAY(aacStream.buffer);
|
||||
DELETE_ARRAY(pcmStream.buffer);
|
||||
return;
|
||||
}
|
||||
while (status_ == RUNNING) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (capturer_ == nullptr) {
|
||||
break;
|
||||
}
|
||||
readLen = capturer_->Read(aacStream.buffer, frameSize, false);
|
||||
}
|
||||
if (readLen <= 0 || readLen > static_cast<int32_t>(frameSize)) {
|
||||
continue;
|
||||
}
|
||||
aacStream.size = readLen;
|
||||
retCode = AudioWrapper::GetInstance().Convert(aacHandler_, aacStream, pcmStream);
|
||||
if (retCode != SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (cache_ == nullptr) {
|
||||
break;
|
||||
}
|
||||
if (!cache_->AppendBuffer(pcmStream.size, pcmStream.buffer)) {
|
||||
printf("[KwsManager]Fail to append pcm into cache.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
DELETE_ARRAY(aacStream.buffer);
|
||||
DELETE_ARRAY(pcmStream.buffer);
|
||||
}
|
||||
|
||||
void KwsManager::ConsumeSamples()
|
||||
{
|
||||
uintptr_t sampleAddr = 0;
|
||||
size_t sampleSize = 0;
|
||||
int32_t retCode = SUCCESS;
|
||||
while (status_ == RUNNING) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (cache_ == nullptr) {
|
||||
printf("[KwsManager]cache_ is nullptr\n");
|
||||
break;
|
||||
}
|
||||
sampleSize = cache_->GetCapturedBuffer(sampleAddr);
|
||||
}
|
||||
if (sampleSize == 0 || sampleAddr == 0) {
|
||||
continue;
|
||||
}
|
||||
Array<int16_t> input = {
|
||||
.data = (int16_t *)sampleAddr,
|
||||
.size = sampleSize >> 1
|
||||
};
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (plugin_ == nullptr) {
|
||||
printf("[KwsManager]cache_ is nullptr\n");
|
||||
break;
|
||||
}
|
||||
if ((retCode = plugin_->SyncExecute(input)) != SUCCESS) {
|
||||
printf("[KwsManager]SyncExecute KWS failed with retcode = [%d]\n", retCode);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool KwsManager::Prepare()
|
||||
{
|
||||
printf("[KwsManager]Prepare.\n");
|
||||
if (status_ != IDLE) {
|
||||
printf("[KwsManager]Already prepared.\n");
|
||||
return false;
|
||||
}
|
||||
if (!PreparedAudioCapturer()) {
|
||||
printf("[KwsManager]Fail to prepare AudioCapturer!\n");
|
||||
OnStop();
|
||||
return false;
|
||||
}
|
||||
if (!PreparedAudioWrapper()) {
|
||||
printf("[KwsManager]Fail to prepare AudioWrapper!\n");
|
||||
OnStop();
|
||||
return false;
|
||||
}
|
||||
if (!PreparedInference()) {
|
||||
printf("[KwsManager]Fail to prepare Inference!\n");
|
||||
OnStop();
|
||||
return false;
|
||||
}
|
||||
status_ = PREPARED;
|
||||
return true;
|
||||
}
|
||||
|
||||
void KwsManager::Start()
|
||||
{
|
||||
printf("[KwsManager]Start.\n");
|
||||
if (status_ == RUNNING) {
|
||||
printf("[KwsManager]Already running.\n");
|
||||
return;
|
||||
}
|
||||
if (status_ != PREPARED && !Prepare()) {
|
||||
printf("[KwsManager]Fail to prepare KwsManager!\n");
|
||||
return;
|
||||
}
|
||||
OnStart();
|
||||
}
|
||||
|
||||
void KwsManager::Stop()
|
||||
{
|
||||
printf("[KwsManager]Stop.\n");
|
||||
if (status_ == IDLE) {
|
||||
printf("[KwsManager]Is already stopped.\n");
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
status_ = IDLE;
|
||||
OnStop();
|
||||
}
|
||||
|
||||
void KwsManager::OnStart()
|
||||
{
|
||||
if (!capturer_->Start()) {
|
||||
printf("[KwsManager]Fail to start audioCapturer\n");
|
||||
OnStop();
|
||||
return;
|
||||
}
|
||||
if (aacHandler_ == -1) {
|
||||
printf("[KwsManager]Fail to start producer for the illegal aac-to-pcm handler\n");
|
||||
OnStop();
|
||||
return;
|
||||
}
|
||||
if (cache_ == nullptr) {
|
||||
printf("[KwsManager]Fail to start producer for the nullptr cache\n");
|
||||
OnStop();
|
||||
return;
|
||||
}
|
||||
if (plugin_ == nullptr) {
|
||||
printf("[KwsManager]Fail to start producer for the nullptr plugin\n");
|
||||
OnStop();
|
||||
return;
|
||||
}
|
||||
status_ = RUNNING;
|
||||
g_producer = std::make_shared<std::thread>(ThreadTask::AudioProducer, this);
|
||||
g_consumer = std::make_shared<std::thread>(ThreadTask::AudioConsumer, this);
|
||||
}
|
||||
|
||||
void KwsManager::StopAudioCapturer()
|
||||
{
|
||||
printf("[KwsManager]StopAudioCapturer\n");
|
||||
if (capturer_ != nullptr) {
|
||||
capturer_->Stop();
|
||||
capturer_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void KwsManager::StopAudioWrapper()
|
||||
{
|
||||
printf("[KwsManager]StopAudioWrapper\n");
|
||||
AudioWrapper::GetInstance().Deinit(aacHandler_);
|
||||
}
|
||||
|
||||
void KwsManager::StopInference()
|
||||
{
|
||||
printf("[KwsManager]StopInference\n");
|
||||
if (plugin_ != nullptr) {
|
||||
int32_t ret = plugin_->Destroy();
|
||||
if (ret != SUCCESS) {
|
||||
printf("[KwsManager]plugin_ destroy failed.\n");
|
||||
}
|
||||
plugin_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void KwsManager::OnStop()
|
||||
{
|
||||
if (capturer_ != nullptr) {
|
||||
StopAudioCapturer();
|
||||
}
|
||||
StopAudioWrapper();
|
||||
if (plugin_ != nullptr) {
|
||||
StopInference();
|
||||
}
|
||||
if (g_producer != nullptr) {
|
||||
g_producer->join();
|
||||
}
|
||||
if (g_consumer != nullptr) {
|
||||
g_consumer->join();
|
||||
}
|
||||
}
|
||||
|
||||
bool KwsManager::PreparedAudioCapturer()
|
||||
{
|
||||
// Set audio wrapper mode before build audio capturer
|
||||
AudioWrapper::GetInstance().SetCodecMode(false);
|
||||
if (capturer_ != nullptr) {
|
||||
printf("[KwsManager]Stop created AudioCapturer at first\n");
|
||||
StopAudioCapturer();
|
||||
}
|
||||
capturer_ = std::make_shared<AudioCapturer>();
|
||||
if (capturer_ == nullptr || !ConfiguredAudioCapturer()) {
|
||||
printf("[KwsManager]Fail to create AudioCapturer.\n");
|
||||
OnStop();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KwsManager::PreparedAudioWrapper()
|
||||
{
|
||||
cache_ = std::make_shared<AudioCache>();
|
||||
if (cache_ == nullptr) {
|
||||
printf("[KwsManager]Failed to create AudioCache\n");
|
||||
return false;
|
||||
}
|
||||
if (!cache_->Init(MAX_CACHE_SIZE)) {
|
||||
printf("[KwsManager]Failed to init AudioCache\n");
|
||||
return false;
|
||||
}
|
||||
if (!ConfiguredAudioWrapper()) {
|
||||
printf("[KwsManager]Failed to prepared AudioWrapper.\n");
|
||||
OnStop();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KwsManager::PreparedInference()
|
||||
{
|
||||
if (capturer_ == nullptr) {
|
||||
printf("[KwsManager]Only load plugin after AudioCapturer ready.\n");
|
||||
return false;
|
||||
}
|
||||
if (plugin_ != nullptr) {
|
||||
printf("[KwsManager]Stop created Inference Plugin at first.\n");
|
||||
StopInference();
|
||||
}
|
||||
plugin_ = std::make_shared<KWSSdk>();
|
||||
if (plugin_ == nullptr) {
|
||||
printf("[KwsManager]Failed to create InferencePlugin.\n");
|
||||
return false;
|
||||
}
|
||||
if (plugin_->Create() != SUCCESS) {
|
||||
printf("[KwsManager]Failed to create KWSSDK.\n");
|
||||
return false;
|
||||
}
|
||||
std::shared_ptr<KWSCallback> callback = std::make_shared<MyKwsCallback>();
|
||||
if (callback == nullptr) {
|
||||
printf("[KwsManager]Create callback failed.\n");
|
||||
return false;
|
||||
}
|
||||
plugin_->SetCallback(callback);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KwsManager::ConfiguredAudioCapturer()
|
||||
{
|
||||
AudioCapturerInfo audioConfig;
|
||||
audioConfig.inputSource = AUDIO_MIC;
|
||||
audioConfig.audioFormat = AAC_LC;
|
||||
audioConfig.sampleRate = AUDIO_SAMPLE_RATE;
|
||||
audioConfig.channelCount = CHANNEL_COUNT;
|
||||
audioConfig.bitRate = AUDIO_CODEC_BITRATE;
|
||||
audioConfig.streamType = TYPE_MEDIA;
|
||||
audioConfig.bitWidth = BIT_WIDTH_16;
|
||||
int32_t ret = capturer_->SetCapturerInfo(audioConfig);
|
||||
if (ret != SUCCESS) {
|
||||
printf("[KwsManager]ConfiguredAudioCapturer fail with ret = 0x%.8x\n", ret);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KwsManager::ConfiguredAudioWrapper()
|
||||
{
|
||||
ConvertType typo = CONVERT_AAC_TO_PCM;
|
||||
CoderConfig codecConfig;
|
||||
codecConfig.audioFormat = AAC_LC;
|
||||
codecConfig.bitRate = bitRate_;
|
||||
codecConfig.sampleRate = sampleRate_;
|
||||
codecConfig.channelCount = CHANNEL_COUNT;
|
||||
codecConfig.bitWidth = BIT_WIDTH_16;
|
||||
if (AudioWrapper::GetInstance().Init(typo, aacHandler_) != SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
if (AudioWrapper::GetInstance().SetConfig(aacHandler_, codecConfig) != SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
if (AudioWrapper::GetInstance().StartCodec(aacHandler_) != SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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.
|
||||
*/
|
||||
|
||||
#ifndef KWS_MANAGER_H
|
||||
#define KWS_MANAGER_H
|
||||
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#include "ai_datatype.h"
|
||||
#include "audio_cache.h"
|
||||
#include "audio_capturer.h"
|
||||
#include "audio_wrapper.h"
|
||||
#include "kws_callback.h"
|
||||
#include "kws_sdk.h"
|
||||
#include "media_errors.h"
|
||||
#include "media_info.h"
|
||||
|
||||
namespace KWS {
|
||||
const int32_t AUDIO_SAMPLE_RATE = 16000; // 16kHz
|
||||
const int32_t AUDIO_CODEC_BITRATE = 32000; // 32kHz
|
||||
|
||||
const std::vector<std::string> WORD_CONTENT = {
|
||||
"Hi Xiaowen",
|
||||
"Nihao Wenwen",
|
||||
"Unknown"
|
||||
};
|
||||
|
||||
enum KwsStatus {
|
||||
IDLE = 1000,
|
||||
PREPARED,
|
||||
RUNNING,
|
||||
};
|
||||
|
||||
class KwsManager {
|
||||
public:
|
||||
KwsManager(int32_t sampleRate, int32_t bitRate);
|
||||
~KwsManager();
|
||||
void Start();
|
||||
void Stop();
|
||||
bool Prepare();
|
||||
void ProduceSamples();
|
||||
void ConsumeSamples();
|
||||
|
||||
private:
|
||||
void OnStart();
|
||||
void OnStop();
|
||||
bool PreparedAudioCapturer();
|
||||
bool PreparedAudioWrapper();
|
||||
bool PreparedInference();
|
||||
bool ConfiguredAudioCapturer();
|
||||
bool ConfiguredAudioWrapper();
|
||||
void StopAudioCapturer();
|
||||
void StopAudioWrapper();
|
||||
void StopInference();
|
||||
|
||||
private:
|
||||
std::shared_ptr<AudioCache> cache_ = nullptr;
|
||||
std::shared_ptr<OHOS::AI::KWSSdk> plugin_ = nullptr;
|
||||
std::shared_ptr<OHOS::Audio::AudioCapturer> capturer_ = nullptr;
|
||||
int32_t sampleRate_ = AUDIO_SAMPLE_RATE;
|
||||
int32_t bitRate_ = AUDIO_CODEC_BITRATE;
|
||||
KwsStatus status_ = IDLE;
|
||||
intptr_t aacHandler_ = -1;
|
||||
std::mutex mutex_;
|
||||
};
|
||||
|
||||
class ThreadTask {
|
||||
public:
|
||||
static void AudioProducer(KwsManager *kwsManager);
|
||||
static void AudioConsumer(KwsManager *kwsManager);
|
||||
};
|
||||
} // namespace KWS
|
||||
#endif
|
||||
@@ -1,17 +0,0 @@
|
||||
# Copyright (c) 2021 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.
|
||||
import("//build/lite/config/component/lite_component.gni")
|
||||
|
||||
lite_component("cv") {
|
||||
features = [ "image_classification:ic" ]
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
# Copyright (c) 2021 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.
|
||||
import("//build/lite/config/component/lite_component.gni")
|
||||
|
||||
copy("demo_image_for_test") {
|
||||
sources = [ "resources/image_classification_demo.jpg" ]
|
||||
outputs = [ "$root_out_dir/data/image_classification_demo.jpg" ]
|
||||
}
|
||||
|
||||
executable("ic_app") {
|
||||
sources = [
|
||||
"camera_manager.cpp",
|
||||
"image_classification_sample.cpp",
|
||||
"picture_utils.cpp",
|
||||
"resize_computer.cpp",
|
||||
]
|
||||
cflags = [ "-Wall" ]
|
||||
cflags_cc = cflags
|
||||
include_dirs = [
|
||||
"//third_party/libjpeg",
|
||||
"//foundation/ai/ai_engine/interfaces/kits",
|
||||
"//foundation/ai/ai_engine/interfaces/kits/cv/image_classification",
|
||||
"//foundation/ai/ai_engine/services/client/algorithm_sdk/cv/image_classification/include",
|
||||
"//foundation/multimedia/media_lite/interfaces/kits/recorder_lite",
|
||||
]
|
||||
|
||||
ldflags = [ "-lstdc++" ]
|
||||
ldflags += [ "-lpthread" ]
|
||||
ldflags += [ "-Wl,-rpath-link=$ohos_root_path/$root_out_dir" ]
|
||||
|
||||
deps = [
|
||||
"//foundation/ai/ai_engine/services/client/algorithm_sdk/cv/image_classification:image_classification_sdk",
|
||||
"//foundation/multimedia/camera_lite/frameworks:camera_lite",
|
||||
"//foundation/multimedia/media_lite/frameworks/recorder_lite:recorder_lite",
|
||||
"//third_party/libjpeg",
|
||||
]
|
||||
output_dir = "$root_out_dir/dev_tools"
|
||||
}
|
||||
|
||||
lite_component("ic") {
|
||||
features = [
|
||||
":ic_app",
|
||||
":demo_image_for_test",
|
||||
]
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 "camera_manager.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace OHOS;
|
||||
using namespace OHOS::Media;
|
||||
|
||||
namespace IC {
|
||||
const int QFACTOR = 90;
|
||||
std::mutex g_mutex;
|
||||
std::condition_variable g_cv;
|
||||
|
||||
static void SampleSaveCapture(const char *p, uint32_t size)
|
||||
{
|
||||
printf("Start saving picture.\n");
|
||||
ofstream pic(CAMERA_SAVE_PATH, ofstream::out | ofstream::trunc);
|
||||
if (pic.is_open()) {
|
||||
printf("Write %d bytes.\n", size);
|
||||
pic.write(p, size);
|
||||
pic.close();
|
||||
printf("Saving picture end.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void SampleSurfaceListener::SetSurface(Surface *surface)
|
||||
{
|
||||
surface_ = surface;
|
||||
}
|
||||
|
||||
void SampleFrameStateCallback::OnFrameFinished(Camera &camera, FrameConfig &fc, FrameResult &result)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(g_mutex);
|
||||
printf("Receive frame complete inform.\n");
|
||||
if (fc.GetFrameConfigType() == FRAME_CONFIG_CAPTURE) {
|
||||
printf("Capture frame received.\n");
|
||||
list<Surface *> surfaceList = fc.GetSurfaces();
|
||||
for (Surface *surface : surfaceList) {
|
||||
SurfaceBuffer *buffer = surface->AcquireBuffer();
|
||||
if (buffer != nullptr) {
|
||||
char *virtAddr = static_cast<char *>(buffer->GetVirAddr());
|
||||
if (virtAddr != nullptr) {
|
||||
SampleSaveCapture(virtAddr, buffer->GetSize());
|
||||
}
|
||||
surface->ReleaseBuffer(buffer);
|
||||
}
|
||||
delete surface;
|
||||
}
|
||||
} else if (fc.GetFrameConfigType() == FRAME_CONFIG_PREVIEW) {
|
||||
printf("Preview frame received.\n");
|
||||
list<Surface *> surfaceList = fc.GetSurfaces();
|
||||
for (Surface *surface : surfaceList) {
|
||||
printf("surface release.\n");
|
||||
delete surface;
|
||||
}
|
||||
}
|
||||
delete &fc;
|
||||
g_cv.notify_all();
|
||||
}
|
||||
|
||||
void SampleSurfaceListener::OnBufferAvailable()
|
||||
{
|
||||
if (surface_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
SurfaceBuffer *buffer = surface_->AcquireBuffer();
|
||||
if (buffer != nullptr) {
|
||||
surface_->ReleaseBuffer(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
SampleCameraStateMng::SampleCameraStateMng(EventHandler &eventHdlr) : eventHdlr_(eventHdlr)
|
||||
{
|
||||
}
|
||||
|
||||
SampleCameraStateMng::~SampleCameraStateMng()
|
||||
{
|
||||
}
|
||||
|
||||
void SampleCameraStateMng::OnCreated(Camera &c)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(g_mutex);
|
||||
printf("Sample recv OnCreate camera.\n");
|
||||
auto config = CameraConfig::CreateCameraConfig();
|
||||
if (config == nullptr) {
|
||||
cout << "New object failed." << endl;
|
||||
return;
|
||||
}
|
||||
config->SetFrameStateCallback(&fsCb_, &eventHdlr_);
|
||||
c.Configure(*config);
|
||||
cam_ = &c;
|
||||
g_cv.notify_all();
|
||||
}
|
||||
|
||||
void SampleCameraStateMng::OnCreateFailed(const string cameraId, int32_t errorCode)
|
||||
{
|
||||
printf("Sample camera create failed. Error code: %d.\n", errorCode);
|
||||
}
|
||||
|
||||
void SampleCameraStateMng::OnReleased(Camera &c)
|
||||
{
|
||||
printf("Sample camera OnReleased.\n");
|
||||
}
|
||||
|
||||
void SampleCameraStateMng::Capture()
|
||||
{
|
||||
if (cam_ == nullptr) {
|
||||
printf("Camera is not ready.\n");
|
||||
return;
|
||||
}
|
||||
FrameConfig *fc = new (std::nothrow) FrameConfig(FRAME_CONFIG_CAPTURE);
|
||||
if (fc == nullptr) {
|
||||
printf("Create FrameConfig failed.\n");
|
||||
return;
|
||||
}
|
||||
Surface *surface = Surface::CreateSurface();
|
||||
if (surface == nullptr) {
|
||||
delete fc;
|
||||
printf("CreateSurface failed.\n");
|
||||
return;
|
||||
}
|
||||
surface->SetWidthAndHeight(CAMERA_PIC_WIDTH, CAMERA_PIC_HEIGHT);
|
||||
surface->SetUsage(BUFFER_CONSUMER_USAGE_HARDWARE);
|
||||
fc->AddSurface(*surface);
|
||||
fc->SetParameter(PARAM_KEY_IMAGE_ENCODE_QFACTOR, QFACTOR);
|
||||
cam_->TriggerSingleCapture(*fc);
|
||||
}
|
||||
} // namespace IC
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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.
|
||||
*/
|
||||
|
||||
#ifndef CAMERA_MANAGER_H
|
||||
#define CAMERA_MANAGER_H
|
||||
|
||||
#include "camera_kit.h"
|
||||
#include "recorder.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <pthread.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <sys/io.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace IC {
|
||||
// Settings of camera
|
||||
const int32_t CAMERA_PIC_WIDTH = 1920;
|
||||
const int32_t CAMERA_PIC_HEIGHT = 1080;
|
||||
const std::string CAMERA_SAVE_PATH = "/storage/Capture.jpg";
|
||||
|
||||
// condition_variable for camera
|
||||
extern std::mutex g_mutex;
|
||||
extern std::condition_variable g_cv;
|
||||
|
||||
class SampleFrameStateCallback : public OHOS::Media::FrameStateCallback {
|
||||
public:
|
||||
void OnFrameFinished(OHOS::Media::Camera &camera,
|
||||
OHOS::Media::FrameConfig &fc,
|
||||
OHOS::Media::FrameResult &result) override;
|
||||
};
|
||||
|
||||
class SampleSurfaceListener : public OHOS::IBufferConsumerListener {
|
||||
public:
|
||||
void SetSurface(OHOS::Media::Surface *surface);
|
||||
void OnBufferAvailable() override;
|
||||
|
||||
private:
|
||||
OHOS::Media::Surface *surface_ = nullptr;
|
||||
};
|
||||
|
||||
class SampleCameraDeivceCallback : public OHOS::Media::CameraDeviceCallback {
|
||||
};
|
||||
|
||||
class SampleCameraStateMng : public OHOS::Media::CameraStateCallback {
|
||||
public:
|
||||
SampleCameraStateMng() = delete;
|
||||
explicit SampleCameraStateMng(OHOS::Media::EventHandler &eventHdlr);
|
||||
~SampleCameraStateMng();
|
||||
void OnCreated(OHOS::Media::Camera &c) override;
|
||||
void OnCreateFailed(const std::string cameraId, int32_t errorCode) override;
|
||||
void OnReleased(OHOS::Media::Camera &c) override;
|
||||
void Capture();
|
||||
private:
|
||||
OHOS::Media::EventHandler &eventHdlr_;
|
||||
OHOS::Media::Camera *cam_ = nullptr;
|
||||
SampleSurfaceListener listener_;
|
||||
SampleFrameStateCallback fsCb_;
|
||||
};
|
||||
} // namespace IC
|
||||
#endif
|
||||
@@ -1,229 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 "camera_manager.h"
|
||||
#include "ic_callback.h"
|
||||
#include "ic_constants.h"
|
||||
#include "ic_retcode.h"
|
||||
#include "ic_sdk.h"
|
||||
#include "picture_utils.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace IC;
|
||||
using namespace OHOS;
|
||||
using namespace OHOS::AI;
|
||||
using namespace OHOS::Media;
|
||||
|
||||
namespace {
|
||||
const auto DELETE_PIC_BUFFER = [](uint8_t *&buffer) {
|
||||
if (buffer != nullptr) {
|
||||
delete[] buffer;
|
||||
buffer = nullptr;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class MyIcCallback : public IcCallback {
|
||||
public:
|
||||
MyIcCallback() {}
|
||||
~MyIcCallback() {}
|
||||
|
||||
void OnError(const IcRetCode errorCode)
|
||||
{
|
||||
printf("Executing error, error code: %d\n", errorCode);
|
||||
}
|
||||
|
||||
void OnResult(const IcOutput &result)
|
||||
{
|
||||
if (result.size % 2 == 0) {
|
||||
int numClass = result.size / 2;
|
||||
for (size_t i = 0; i < numClass; ++i) {
|
||||
printf("class index [%d] score: %d\n", result.data[i], result.data[i + numClass]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static int CameraCapture()
|
||||
{
|
||||
printf("Camera sample begin.\n");
|
||||
CameraKit *camKit = CameraKit::GetInstance();
|
||||
if (camKit == nullptr) {
|
||||
printf("Can not get CameraKit instance\n");
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
list<string> camList = camKit->GetCameraIds();
|
||||
string camId;
|
||||
for (auto &cam : camList) {
|
||||
printf("camera name: %s\n", cam.c_str());
|
||||
const CameraAbility *ability = camKit->GetCameraAbility(cam);
|
||||
list<CameraPicSize> sizeList = ability->GetSupportedSizes(0);
|
||||
for (auto &pic : sizeList) {
|
||||
if (pic.width == CAMERA_PIC_WIDTH && pic.height == CAMERA_PIC_HEIGHT) {
|
||||
camId = cam;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!camId.empty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (camId.empty()) {
|
||||
printf("No available camera.(1080p wanted)\n");
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
|
||||
EventHandler eventHdlr;
|
||||
SampleCameraStateMng camStateMng(eventHdlr);
|
||||
camKit->CreateCamera(camId, camStateMng, eventHdlr);
|
||||
std::unique_lock<std::mutex> cameraCreateLock(g_mutex);
|
||||
g_cv.wait(cameraCreateLock);
|
||||
cameraCreateLock.unlock();
|
||||
camStateMng.Capture();
|
||||
std::unique_lock<std::mutex> captureLock(g_mutex);
|
||||
g_cv.wait(captureLock);
|
||||
captureLock.unlock();
|
||||
printf("Camera sample end.\n");
|
||||
return IC_RETCODE_SUCCESS;
|
||||
}
|
||||
|
||||
static int ImageClassificationExecute(const IcInput &picData)
|
||||
{
|
||||
if (picData.data == nullptr || picData.size == 0) {
|
||||
printf("ImageClassificationExecute: picData is empty.\n");
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
shared_ptr<IcSdk> detector = make_shared<IcSdk>();
|
||||
shared_ptr<MyIcCallback> callback = make_shared<MyIcCallback>();
|
||||
if (detector == nullptr || callback == nullptr) {
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
detector->SetCallback(callback);
|
||||
int retcode = detector->Create();
|
||||
if (retcode != IC_RETCODE_SUCCESS) {
|
||||
printf("ImageClassificationExecute: IcSdk Create failed.\n");
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
retcode = detector->SyncExecute(picData);
|
||||
if (retcode != IC_RETCODE_SUCCESS) {
|
||||
printf("ImageClassificationExecute: IcSdk SyncExecute failed.\n");
|
||||
}
|
||||
(void)detector->Destroy();
|
||||
return retcode;
|
||||
}
|
||||
|
||||
static int GetPictureData(const string &sourcePath, Array<uint8_t> &picData)
|
||||
{
|
||||
int srcWidth = 0;
|
||||
int srcHeight = 0;
|
||||
uint8_t *rawData = ReadJpegFile(sourcePath, srcWidth, srcHeight);
|
||||
if (rawData == nullptr) {
|
||||
printf("ReadJpegFile failed.\n");
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
uint8_t *srcData = Resize(WIDTH_DEST, HEIGHT_DEST, rawData, srcWidth, srcHeight);
|
||||
if (srcData == nullptr) {
|
||||
printf("Resize failed.\n");
|
||||
DELETE_PIC_BUFFER(rawData);
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
int srcDataSize = WIDTH_DEST * HEIGHT_DEST * NUM_CHANNELS;
|
||||
uint8_t *input = ConvertToCaffeInput(srcData, srcDataSize);
|
||||
if (input == nullptr) {
|
||||
printf("Convert to caffe input failed.\n");
|
||||
DELETE_PIC_BUFFER(rawData);
|
||||
DELETE_PIC_BUFFER(srcData);
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
picData.data = input;
|
||||
picData.size = srcDataSize;
|
||||
DELETE_PIC_BUFFER(rawData);
|
||||
DELETE_PIC_BUFFER(srcData);
|
||||
return IC_RETCODE_SUCCESS;
|
||||
}
|
||||
|
||||
static int GetPicFromCamera(IcInput &picData)
|
||||
{
|
||||
int retcode = CameraCapture();
|
||||
if (retcode != IC_RETCODE_SUCCESS) {
|
||||
printf("Capture picture failed.\n");
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
retcode = GetPictureData(CAMERA_SAVE_PATH, picData);
|
||||
if (retcode != IC_RETCODE_SUCCESS) {
|
||||
printf("GetPictureData failed.\n");
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
return IC_RETCODE_SUCCESS;
|
||||
}
|
||||
|
||||
static int GetPicFromLocal(IcInput &picData)
|
||||
{
|
||||
int retcode = GetPictureData(JPEG_SRC_PATH, picData);
|
||||
if (retcode != IC_RETCODE_SUCCESS) {
|
||||
printf("GetPictureData failed.\n");
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
return IC_RETCODE_SUCCESS;
|
||||
}
|
||||
|
||||
static void SampleHelp()
|
||||
{
|
||||
printf("****************************************\n");
|
||||
printf("Select the image source.\n");
|
||||
printf("1: Camera capture\n");
|
||||
printf("2: Local\n");
|
||||
printf("****************************************\n");
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
IcInput picData = {
|
||||
.data = nullptr,
|
||||
.size = 0
|
||||
};
|
||||
int retcode = IC_RETCODE_SUCCESS;
|
||||
char input = ' ';
|
||||
SampleHelp();
|
||||
cin >> input;
|
||||
switch (input) {
|
||||
case '1':
|
||||
retcode = GetPicFromCamera(picData);
|
||||
break;
|
||||
case '2':
|
||||
retcode = GetPicFromLocal(picData);
|
||||
break;
|
||||
default:
|
||||
SampleHelp();
|
||||
break;
|
||||
}
|
||||
if (retcode != IC_RETCODE_SUCCESS) {
|
||||
printf("GetPicture failed.\n");
|
||||
return -1;
|
||||
}
|
||||
if (retcode != IC_RETCODE_SUCCESS) {
|
||||
printf("WriteBgrFile failed.\n");
|
||||
DELETE_PIC_BUFFER(picData.data);
|
||||
return -1;
|
||||
}
|
||||
retcode = ImageClassificationExecute(picData);
|
||||
if (retcode != IC_RETCODE_SUCCESS) {
|
||||
printf("ImageClassification failed.\n");
|
||||
} else {
|
||||
printf("ImageClassification successed.\n");
|
||||
}
|
||||
DELETE_PIC_BUFFER(picData.data);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 "picture_utils.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "ic_retcode.h"
|
||||
#include "resize_computer.h"
|
||||
#include "securec.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace IC {
|
||||
int WriteJpegFile(const string &filename, int quality,
|
||||
uint8_t *srcBuffer, int srcWidth, int srcHeight)
|
||||
{
|
||||
struct jpeg_compress_struct cinfo;
|
||||
struct jpeg_error_mgr jerr;
|
||||
JSAMPROW rowPointer[1];
|
||||
int rowStride = 0;
|
||||
cinfo.err = jpeg_std_error(&jerr);
|
||||
jpeg_create_compress(&cinfo);
|
||||
if (srcBuffer == nullptr) {
|
||||
printf("WriteJpegFile: srcBuffer is nullptr\n");
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
FILE *outfile;
|
||||
if ((outfile = fopen(filename.c_str(), "wb")) == nullptr) {
|
||||
printf("WriteJpegFile: can't open %s\n", filename.c_str());
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
jpeg_stdio_dest(&cinfo, outfile);
|
||||
cinfo.image_width = srcWidth;
|
||||
cinfo.image_height = srcHeight;
|
||||
cinfo.input_components = NUM_CHANNELS;
|
||||
cinfo.in_color_space = JCS_RGB;
|
||||
jpeg_set_defaults(&cinfo);
|
||||
jpeg_set_quality(&cinfo, quality, TRUE);
|
||||
jpeg_start_compress(&cinfo, TRUE);
|
||||
rowStride = srcWidth * NUM_CHANNELS;
|
||||
|
||||
while (cinfo.next_scanline < cinfo.image_height) {
|
||||
rowPointer[0] = &srcBuffer[cinfo.next_scanline * rowStride];
|
||||
(void)jpeg_write_scanlines(&cinfo, rowPointer, 1);
|
||||
}
|
||||
jpeg_finish_compress(&cinfo);
|
||||
|
||||
fclose(outfile);
|
||||
jpeg_destroy_compress(&cinfo);
|
||||
return IC_RETCODE_SUCCESS;
|
||||
}
|
||||
|
||||
int WriteBgrFile(const string &filename, uint8_t *dataBuffer, int bufferSize)
|
||||
{
|
||||
if (dataBuffer == nullptr || bufferSize <= 0) {
|
||||
printf("WriteBgrFile: dataBuffer is nullptr.\n");
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
ofstream outfile(filename.c_str(), ofstream::out | ofstream::trunc);
|
||||
if (!outfile.is_open()) {
|
||||
printf("WriteBgrFile: Error writing file from BGR dataBuffer\n");
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
outfile.write((const char*)dataBuffer, bufferSize);
|
||||
outfile.close();
|
||||
return IC_RETCODE_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t *ConvertToCaffeInput(uint8_t *dataBuffer, int maxSize)
|
||||
{
|
||||
if (dataBuffer == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
if (maxSize % NUM_CHANNELS != 0) {
|
||||
return nullptr;
|
||||
}
|
||||
uint8_t *input = new (std::nothrow) uint8_t[maxSize];
|
||||
if (input == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
int numPreChannel = maxSize / NUM_CHANNELS;
|
||||
for (int i = 0; i < maxSize; i++) {
|
||||
input[BGR_RED * numPreChannel + i / NUM_CHANNELS] = dataBuffer[i + RGB_RED];
|
||||
input[BGR_BLUE * numPreChannel + i / NUM_CHANNELS] = dataBuffer[i + RGB_BLUE];
|
||||
input[BGR_GREEN * numPreChannel + i / NUM_CHANNELS] = dataBuffer[i + RGB_GREEN];
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
uint8_t *ReadJpegFile(const string &filename, int &srcWidth, int &srcHeight)
|
||||
{
|
||||
struct jpeg_decompress_struct cinfo;
|
||||
struct MyErrorMgr jerr;
|
||||
FILE *infile;
|
||||
if ((infile = fopen(filename.c_str(), "rb")) == nullptr) {
|
||||
printf("ReadJpegFile: can't open %s\n", filename.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cinfo.err = jpeg_std_error(&jerr.pub);
|
||||
jpeg_create_decompress(&cinfo);
|
||||
jpeg_stdio_src(&cinfo, infile);
|
||||
(void)jpeg_read_header(&cinfo, TRUE);
|
||||
(void)jpeg_start_decompress(&cinfo);
|
||||
srcHeight = cinfo.output_height;
|
||||
srcWidth = cinfo.output_width;
|
||||
int dataSize = srcHeight * srcWidth * cinfo.output_components;
|
||||
uint8_t *buffer = new (std::nothrow) uint8_t[dataSize];
|
||||
if (buffer == nullptr) {
|
||||
printf("ReadJpegFile: error to alloc buffer.\n");
|
||||
(void)jpeg_finish_decompress(&cinfo);
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
fclose(infile);
|
||||
return nullptr;
|
||||
}
|
||||
uint8_t *rowptr = nullptr;
|
||||
while (cinfo.output_scanline < srcHeight) {
|
||||
rowptr = buffer + cinfo.output_scanline * srcWidth * cinfo.output_components;
|
||||
(void)jpeg_read_scanlines(&cinfo, &rowptr, 1);
|
||||
}
|
||||
|
||||
(void)jpeg_finish_decompress(&cinfo);
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
fclose(infile);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
uint8_t *Resize(
|
||||
const int widthDest, const int heightDest, uint8_t *src, int widthSrc, int heightSrc)
|
||||
{
|
||||
if (src == nullptr) {
|
||||
printf("Resize: src is nullptr.\n");
|
||||
return nullptr;
|
||||
}
|
||||
if (widthDest <= 0 || heightDest <= 0 || widthSrc <= 0 || heightSrc <= 0) {
|
||||
printf("Resize: dimension below zero.\n");
|
||||
return nullptr;
|
||||
}
|
||||
int bufferSize = widthDest * heightDest * NUM_CHANNELS;
|
||||
uint8_t *pDest = new (std::nothrow) uint8_t[bufferSize];
|
||||
PicInfo picInfo = {
|
||||
.widthSrc = widthSrc,
|
||||
.heightSrc = heightSrc,
|
||||
.widthDest = widthDest,
|
||||
.heightDest = heightDest
|
||||
};
|
||||
ResizeComputer resizer(picInfo);
|
||||
if (pDest == nullptr) {
|
||||
printf("Resize: pDest alloc failed.\n");
|
||||
return nullptr;
|
||||
}
|
||||
if (widthDest == widthSrc && heightDest == heightSrc) {
|
||||
if (memcpy_s(pDest, bufferSize, src, bufferSize) != EOK) {
|
||||
printf("Resize: memcpy_s failed.\n");
|
||||
delete[] pDest;
|
||||
return nullptr;
|
||||
} else {
|
||||
return pDest;
|
||||
}
|
||||
}
|
||||
resizer.Compute(pDest, src, bufferSize, widthSrc * heightSrc * NUM_CHANNELS);
|
||||
return pDest;
|
||||
}
|
||||
} // namespace IC
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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.
|
||||
*/
|
||||
|
||||
#ifndef PICTURE_UTILS_H
|
||||
#define PICTURE_UTILS_H
|
||||
|
||||
#include <csetjmp>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "jpeglib.h"
|
||||
|
||||
namespace IC {
|
||||
struct MyErrorMgr {
|
||||
struct jpeg_error_mgr pub;
|
||||
jmp_buf setjmp_buffer;
|
||||
};
|
||||
|
||||
using MyErrorPtr = MyErrorMgr *;
|
||||
|
||||
// The index order of BGR format
|
||||
enum BgrIndex {
|
||||
BGR_BLUE = 0,
|
||||
BGR_GREEN,
|
||||
BGR_RED,
|
||||
};
|
||||
|
||||
// The index order of RGB format
|
||||
enum RgbIndex {
|
||||
RGB_RED = 0,
|
||||
RGB_GREEN,
|
||||
RGB_BLUE,
|
||||
};
|
||||
|
||||
// Information about picture
|
||||
struct PicInfo {
|
||||
int widthSrc;
|
||||
int heightSrc;
|
||||
int widthDest;
|
||||
int heightDest;
|
||||
};
|
||||
|
||||
// Change this to your own settings
|
||||
const std::string JPEG_SRC_PATH = "/storage/data/image_classification_demo.jpg";
|
||||
const int WIDTH_DEST = 224;
|
||||
const int HEIGHT_DEST = 224;
|
||||
const int NUM_CHANNELS = 3;
|
||||
|
||||
int WriteJpegFile(const std::string &filename, int quality, uint8_t *srcBuffer, int srcWidth, int srcHeight);
|
||||
int WriteBgrFile(const std::string &filename, uint8_t *dataBuffer, int bufferSize);
|
||||
uint8_t *ConvertToCaffeInput(uint8_t *dataBuffer, int maxSize);
|
||||
uint8_t *ReadJpegFile(const std::string &filename, int &srcWidth, int &srcHeight);
|
||||
uint8_t *Resize(const int widthDest, const int heightDest, uint8_t *src, int widthSrc, int heightSrc);
|
||||
} // namespace IC
|
||||
#endif // PICTURE_UTILS_H
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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 "resize_computer.h"
|
||||
#include "ic_retcode.h"
|
||||
|
||||
namespace IC {
|
||||
const int RESIZE_CONSTANT = 2;
|
||||
|
||||
ResizeComputer::ResizeComputer(const PicInfo &pInfo)
|
||||
{
|
||||
// Initialize parameter for resize computer
|
||||
sw_ = pInfo.widthSrc - 1;
|
||||
sh_ = pInfo.heightSrc - 1;
|
||||
dw_ = pInfo.widthDest - 1;
|
||||
dh_ = pInfo.heightDest - 1;
|
||||
B_ = 0;
|
||||
N_ = 0;
|
||||
x_ = 0;
|
||||
y_ = 0;
|
||||
pLinePrev_ = nullptr;
|
||||
pLineNext_ = nullptr;
|
||||
pA_ = nullptr;
|
||||
pB_ = nullptr;
|
||||
pC_ = nullptr;
|
||||
pD_ = nullptr;
|
||||
}
|
||||
|
||||
int ResizeComputer::Compute(uint8_t *pDest, uint8_t *src, int pDestSize, int srcSize)
|
||||
{
|
||||
if (pDest == nullptr || src == nullptr || pDestSize <= 0 || srcSize <= 0) {
|
||||
printf("ResizeComputer::Compute input is nullptr.\n");
|
||||
return IC_RETCODE_FAILURE;
|
||||
}
|
||||
uint8_t *tmp = nullptr;
|
||||
|
||||
// This is linear stretch for picture resize
|
||||
for (int i = 0; i <= dh_; ++i) {
|
||||
tmp = pDest + i * (dw_ + 1) * NUM_CHANNELS;
|
||||
y_ = i * sh_ / dh_;
|
||||
N_ = dh_ - i * sh_ % dh_;
|
||||
pLinePrev_ = src + y_ * (sw_ + 1) * NUM_CHANNELS;
|
||||
y_++;
|
||||
pLineNext_ = (N_ == dh_) ? pLinePrev_ : (src + y_ * (sw_ + 1) * NUM_CHANNELS);
|
||||
for (int j = 0; j <= dw_; ++j) {
|
||||
x_ = j * sw_ / dw_ * NUM_CHANNELS;
|
||||
B_ = dw_ - j * sw_ % dw_;
|
||||
pA_ = pLinePrev_ + x_;
|
||||
pB_ = pA_ + NUM_CHANNELS;
|
||||
pC_ = pLineNext_ + x_;
|
||||
pD_ = pC_ + NUM_CHANNELS;
|
||||
if (B_ == dw_) {
|
||||
pB_ = pA_;
|
||||
pD_ = pC_;
|
||||
}
|
||||
for (int k = 0; k < NUM_CHANNELS; ++k, ++tmp, ++pA_, ++pB_, ++pC_, ++pD_) {
|
||||
*tmp = static_cast<uint8_t>(
|
||||
(B_ * N_ * (*pA_ - *pB_ - *pC_ + *pD_) + dw_ * N_ * (*pB_) +
|
||||
dh_ * B_ * (*pC_) + (dw_ * dh_ - dh_ * B_ - dw_ * N_) * (*pD_) +
|
||||
dw_ * dh_ / RESIZE_CONSTANT) / (dw_ * dh_));
|
||||
}
|
||||
}
|
||||
}
|
||||
return IC_RETCODE_SUCCESS;
|
||||
}
|
||||
} // namespace IC
|
||||
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021 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.
|
||||
*/
|
||||
|
||||
#ifndef RESIZE_COMPUTER_H
|
||||
#define RESIZE_COMPUTER_H
|
||||
|
||||
#include "picture_utils.h"
|
||||
|
||||
namespace IC {
|
||||
class ResizeComputer {
|
||||
public:
|
||||
explicit ResizeComputer(const PicInfo &pInfo);
|
||||
~ResizeComputer() = default;
|
||||
int Compute(uint8_t *pDest, uint8_t *src, int pDestSize, int srcSize);
|
||||
|
||||
private:
|
||||
int sw_;
|
||||
int sh_;
|
||||
int dw_;
|
||||
int dh_;
|
||||
int B_;
|
||||
int N_;
|
||||
int x_;
|
||||
int y_;
|
||||
uint8_t *pLinePrev_;
|
||||
uint8_t *pLineNext_;
|
||||
uint8_t *pA_;
|
||||
uint8_t *pB_;
|
||||
uint8_t *pC_;
|
||||
uint8_t *pD_;
|
||||
};
|
||||
} // namespace IC
|
||||
#endif // RESIZE_COMPUTER_H
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user