Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 90 additions & 7 deletions HarmonyOS_NEXT/APIExample/entry/src/main/cpp/media_metadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,20 @@

#include "media_metadata.h"
#include <cassert>
#include <string>

MediaMetadata::MediaMetadata(uintptr_t rtcEngineHandler)
: rtcEngine_(reinterpret_cast<agora::rtc::IRtcEngine *>(rtcEngineHandler)), env_(nullptr), wrapper_(nullptr) {}

MediaMetadata::~MediaMetadata() { napi_delete_reference(env_, wrapper_); }
: rtcEngine_(reinterpret_cast<agora::rtc::IRtcEngine *>(rtcEngineHandler)), env_(nullptr), wrapper_(nullptr),
metadataCallback_(nullptr) {}

MediaMetadata::~MediaMetadata() {
ReleaseMetadataCallback();
delete[] pendingData_;
pendingData_ = nullptr;
if (env_ != nullptr && wrapper_ != nullptr) {
napi_delete_reference(env_, wrapper_);
}
}

bool MediaMetadata::onReadyToSendMetadata(Metadata &metadata, agora::rtc::VIDEO_SOURCE_TYPE source_type) {
if(pendingData_){
Expand All @@ -26,7 +35,51 @@ bool MediaMetadata::onReadyToSendMetadata(Metadata &metadata, agora::rtc::VIDEO_
}

void MediaMetadata::onMetadataReceived(const Metadata &metadata) {
AG_INFO("MediaMetadata::onMetadataReceived -- data=%{public}s", metadata.buffer);
if (metadata.buffer == nullptr || metadata.size <= 0) {
AG_ERROR("MediaMetadata::onMetadataReceived -- invalid metadata");
return;
}

auto *message = new std::string(reinterpret_cast<const char *>(metadata.buffer), metadata.size);
AG_INFO("MediaMetadata::onMetadataReceived -- data=%{public}s, size=%{public}d", message->c_str(), metadata.size);
napi_status status = napi_closing;
{
std::lock_guard<std::mutex> lock(callbackMutex_);
if (metadataCallback_ != nullptr) {
status = napi_call_threadsafe_function(metadataCallback_, message, napi_tsfn_nonblocking);
}
}
if (status != napi_ok) {
delete message;
}
}

void MediaMetadata::CallMetadataCallback(napi_env env, napi_value callback,
[[maybe_unused]] void *context, void *data) {
auto *message = static_cast<std::string *>(data);
if (message == nullptr) {
return;
}
if (env == nullptr || callback == nullptr) {
delete message;
return;
}

napi_value value;
if (napi_create_string_utf8(env, message->data(), message->size(), &value) == napi_ok) {
napi_value undefined;
napi_get_undefined(env, &undefined);
napi_call_function(env, undefined, callback, 1, &value, nullptr);
}
delete message;
}

void MediaMetadata::ReleaseMetadataCallback() {
std::lock_guard<std::mutex> lock(callbackMutex_);
if (metadataCallback_ != nullptr) {
napi_release_threadsafe_function(metadataCallback_, napi_tsfn_abort);
metadataCallback_ = nullptr;
}
}

void MediaMetadata::Destructor(napi_env env, void *nativeObject, [[maybe_unused]] void *finalize_hint) {
Expand Down Expand Up @@ -95,8 +148,8 @@ napi_value MediaMetadata::New(napi_env env, napi_callback_info info) {
napi_value MediaMetadata::Enable(napi_env env, napi_callback_info info) {
AG_INFO("MediaMetadata::Enable called");

size_t argc = 1;
napi_value args[1];
size_t argc = 2;
napi_value args[2];
napi_value jsThis;
napi_get_cb_info(env, info, &argc, args, &jsThis, nullptr);
bool enable;
Expand All @@ -109,9 +162,39 @@ napi_value MediaMetadata::Enable(napi_env env, napi_callback_info info) {
int ret = -1;

if (enable) {
ret = obj->rtcEngine_->registerMediaMetadataObserver(obj, IMetadataObserver::METADATA_TYPE::VIDEO_METADATA);
obj->ReleaseMetadataCallback();
if (argc >= 2) {
napi_valuetype callbackType = napi_undefined;
if (napi_typeof(env, args[1], &callbackType) != napi_ok || callbackType != napi_function) {
ret = -2;
} else {
napi_value resourceName;
napi_create_string_utf8(env, "MediaMetadataReceived", NAPI_AUTO_LENGTH, &resourceName);
napi_threadsafe_function callback = nullptr;
napi_status status = napi_create_threadsafe_function(
env, args[1], nullptr, resourceName, 0, 1, nullptr, nullptr, nullptr,
CallMetadataCallback, &callback);
if (status == napi_ok) {
{
std::lock_guard<std::mutex> lock(obj->callbackMutex_);
obj->metadataCallback_ = callback;
}
ret = obj->rtcEngine_->registerMediaMetadataObserver(
obj, IMetadataObserver::METADATA_TYPE::VIDEO_METADATA);
if (ret != 0) {
obj->ReleaseMetadataCallback();
}
} else {
ret = -3;
}
}
} else {
ret = obj->rtcEngine_->registerMediaMetadataObserver(
obj, IMetadataObserver::METADATA_TYPE::VIDEO_METADATA);
}
} else {
ret = obj->rtcEngine_->unregisterMediaMetadataObserver(obj, IMetadataObserver::METADATA_TYPE::VIDEO_METADATA);
obj->ReleaseMetadataCallback();
}

napi_value num;
Expand Down
5 changes: 5 additions & 0 deletions HarmonyOS_NEXT/APIExample/entry/src/main/cpp/media_metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "utils.h"
#include "napi/native_api.h"
#include "IAgoraRtcEngine.h"
#include <mutex>

static thread_local napi_ref g_media_metadata_ref = nullptr;

Expand All @@ -27,6 +28,8 @@ class MediaMetadata : public agora::rtc::IMetadataObserver {
static napi_value New(napi_env env, napi_callback_info info);
static napi_value Enable(napi_env env, napi_callback_info info);
static napi_value Send(napi_env env, napi_callback_info info);
static void CallMetadataCallback(napi_env env, napi_value callback, void *context, void *data);
void ReleaseMetadataCallback();

// IMetadataObserver
int getMaxMetadataSize() override { return DEFAULT_METADATA_SIZE_IN_BYTE; }
Expand All @@ -38,5 +41,7 @@ class MediaMetadata : public agora::rtc::IMetadataObserver {
napi_ref wrapper_;
unsigned char* pendingData_ = nullptr;
int pendingDataSize_ = 0;
std::mutex callbackMutex_;
napi_threadsafe_function metadataCallback_;
};
#endif // APIEXAMPLE_MEDIA_METADATA_H
163 changes: 140 additions & 23 deletions HarmonyOS_NEXT/APIExample/entry/src/main/cpp/origin_video_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,37 +6,131 @@

#include "origin_video_data.h"
#include <cassert>
#include <cstring>
#include <vector>

namespace {
struct SnapshotFrame {
std::vector<uint8_t> data;
int width;
int height;
int rotation;
int errorCode;
};

void SetInt32Property(napi_env env, napi_value object, const char *name, int value) {
napi_value property;
if (napi_create_int32(env, value, &property) == napi_ok) {
napi_set_named_property(env, object, name, property);
}
}
} // namespace

OriginVideoData::OriginVideoData(uintptr_t rtcEngineHandler)
: rtcEngine_(reinterpret_cast<agora::rtc::IRtcEngine *>(rtcEngineHandler)), env_(nullptr), wrapper_(nullptr),takeSnapshot_(false) {}

OriginVideoData::~OriginVideoData() { napi_delete_reference(env_, wrapper_); }
: rtcEngine_(reinterpret_cast<agora::rtc::IRtcEngine *>(rtcEngineHandler)), env_(nullptr), wrapper_(nullptr),
takeSnapshot_(false), snapshotCallback_(nullptr) {}

OriginVideoData::~OriginVideoData() {
takeSnapshot_.store(false);
{
std::lock_guard<std::mutex> lock(snapshotMutex_);
if (snapshotCallback_ != nullptr) {
napi_release_threadsafe_function(snapshotCallback_, napi_tsfn_abort);
snapshotCallback_ = nullptr;
}
}
if (env_ != nullptr && wrapper_ != nullptr) {
napi_delete_reference(env_, wrapper_);
}
}


bool OriginVideoData::onCaptureVideoFrame(agora::rtc::VIDEO_SOURCE_TYPE sourceType, VideoFrame &videoFrame) {
if (takeSnapshot_) {
uint8_t *buffer = videoFrame.yBuffer;
// real path: /data/app/el2/100/base/io.agora.hmos.apiexample/haps/entry/files/capture.i420
// play file: ffplay -f rawvideo -pixel_format yuv420p -video_size 1280x720 /Users/xcz/Downloads/capture.i420
SaveI420Buffer(buffer, videoFrame.width, videoFrame.height,
"/data/storage/el2/base/haps/entry/files/capture.i420");
takeSnapshot_ = false;
if (!takeSnapshot_.exchange(false)) {
return true;
}

auto *snapshot = new SnapshotFrame{{}, videoFrame.width, videoFrame.height, videoFrame.rotation, 0};
const bool invalidFrame = videoFrame.width <= 0 || videoFrame.height <= 0 ||
videoFrame.width % 2 != 0 || videoFrame.height % 2 != 0 ||
videoFrame.yBuffer == nullptr || videoFrame.uBuffer == nullptr ||
videoFrame.vBuffer == nullptr || videoFrame.yStride < videoFrame.width ||
videoFrame.uStride < videoFrame.width / 2 || videoFrame.vStride < videoFrame.width / 2;
if (invalidFrame) {
snapshot->errorCode = -1;
} else {
const int width = videoFrame.width;
const int height = videoFrame.height;
const int chromaWidth = width / 2;
const int chromaHeight = height / 2;
snapshot->data.resize(width * height + chromaWidth * chromaHeight * 2);

uint8_t *destination = snapshot->data.data();
for (int row = 0; row < height; ++row) {
std::memcpy(destination + row * width, videoFrame.yBuffer + row * videoFrame.yStride, width);
}

uint8_t *chromaDestination = destination + width * height;
for (int row = 0; row < chromaHeight; ++row) {
const uint8_t *uRow = videoFrame.uBuffer + row * videoFrame.uStride;
const uint8_t *vRow = videoFrame.vBuffer + row * videoFrame.vStride;
for (int column = 0; column < chromaWidth; ++column) {
chromaDestination[row * width + column * 2] = vRow[column];
chromaDestination[row * width + column * 2 + 1] = uRow[column];
}
}
}

napi_threadsafe_function callback = nullptr;
{
std::lock_guard<std::mutex> lock(snapshotMutex_);
callback = snapshotCallback_;
snapshotCallback_ = nullptr;
}
if (callback == nullptr) {
delete snapshot;
return true;
}

napi_status status = napi_call_threadsafe_function(callback, snapshot, napi_tsfn_nonblocking);
napi_release_threadsafe_function(callback, napi_tsfn_release);
if (status != napi_ok) {
delete snapshot;
}
return true;
}

void OriginVideoData::SaveI420Buffer(const uint8_t *buf, int width, int height, std::string filename) {
AG_INFO("OriginVideoData::SaveI420Buffer takeSnapshot -- width=%{public}d, height=%{public}d, filename=%{public}s", width, height,
filename.c_str());

FILE *dump_file_ = fopen(filename.c_str(), "w");
if (!dump_file_) {
void OriginVideoData::CallSnapshotCallback(napi_env env, napi_value callback,
[[maybe_unused]] void *context, void *data) {
auto *snapshot = static_cast<SnapshotFrame *>(data);
if (snapshot == nullptr) {
return;
}
int size = width * height * 3 / 2;
fwrite(buf, sizeof(uint8_t), size, dump_file_);
fflush(dump_file_);
fclose(dump_file_);
if (env == nullptr || callback == nullptr) {
delete snapshot;
return;
}

napi_value result;
napi_create_object(env, &result);
SetInt32Property(env, result, "width", snapshot->width);
SetInt32Property(env, result, "height", snapshot->height);
SetInt32Property(env, result, "rotation", snapshot->rotation);
SetInt32Property(env, result, "errorCode", snapshot->errorCode);

void *bufferData = nullptr;
napi_value buffer;
if (napi_create_arraybuffer(env, snapshot->data.size(), &bufferData, &buffer) == napi_ok) {
if (!snapshot->data.empty()) {
std::memcpy(bufferData, snapshot->data.data(), snapshot->data.size());
}
napi_set_named_property(env, result, "data", buffer);
}

napi_value undefined;
napi_get_undefined(env, &undefined);
napi_call_function(env, undefined, callback, 1, &result, nullptr);
delete snapshot;
}

OriginVideoData::VIDEO_FRAME_PROCESS_MODE OriginVideoData::getVideoFrameProcessMode() { return PROCESS_MODE_READ_ONLY; }
Expand Down Expand Up @@ -142,16 +236,39 @@ napi_value OriginVideoData::Enable(napi_env env, napi_callback_info info) {
}

napi_value OriginVideoData::TakeSnapshot(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
napi_value jsThis;
assert(napi_get_cb_info(env, info, nullptr, nullptr, &jsThis, nullptr) == napi_ok);
assert(napi_get_cb_info(env, info, &argc, args, &jsThis, nullptr) == napi_ok);

OriginVideoData *obj;
// 通过napi_unwrap将jsThis之前绑定的C++对象取出,并对其进行操作
assert(napi_unwrap(env, jsThis, reinterpret_cast<void **>(&obj)) == napi_ok);

obj->takeSnapshot_ = true;
int result = 0;
napi_valuetype callbackType = napi_undefined;
if (argc != 1 || napi_typeof(env, args[0], &callbackType) != napi_ok || callbackType != napi_function) {
result = -1;
} else {
std::lock_guard<std::mutex> lock(obj->snapshotMutex_);
if (obj->takeSnapshot_.load() || obj->snapshotCallback_ != nullptr) {
result = -2;
} else {
napi_value resourceName;
napi_create_string_utf8(env, "OriginVideoDataSnapshot", NAPI_AUTO_LENGTH, &resourceName);
napi_status status = napi_create_threadsafe_function(
env, args[0], nullptr, resourceName, 1, 1, nullptr, nullptr, nullptr,
CallSnapshotCallback, &obj->snapshotCallback_);
if (status == napi_ok) {
obj->takeSnapshot_.store(true);
} else {
obj->snapshotCallback_ = nullptr;
result = -3;
}
}
}

napi_value num;
assert(napi_create_int32(env, 0, &num) == napi_ok);
assert(napi_create_int32(env, result, &num) == napi_ok);
return num;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include "utils.h"
#include "napi/native_api.h"
#include "IAgoraRtcEngine.h"
#include <atomic>
#include <mutex>

static thread_local napi_ref g_origin_video_data_ref = nullptr;

Expand All @@ -25,11 +27,10 @@ class OriginVideoData : public agora::media::IVideoFrameObserver {
explicit OriginVideoData();
~OriginVideoData();

void SaveI420Buffer(const uint8_t *buf, int width, int height, std::string filename);

static napi_value New(napi_env env, napi_callback_info info);
static napi_value Enable(napi_env env, napi_callback_info info);
static napi_value TakeSnapshot(napi_env env, napi_callback_info info);
static void CallSnapshotCallback(napi_env env, napi_value callback, void *context, void *data);

// IVideoFrameObserver
bool onCaptureVideoFrame(agora::rtc::VIDEO_SOURCE_TYPE sourceType, VideoFrame &videoFrame);
Expand All @@ -50,7 +51,9 @@ class OriginVideoData : public agora::media::IVideoFrameObserver {
agora::rtc::IRtcEngine *rtcEngine_;
napi_env env_;
napi_ref wrapper_;
bool takeSnapshot_;
std::atomic<bool> takeSnapshot_;
std::mutex snapshotMutex_;
napi_threadsafe_function snapshotCallback_;
};

#endif // APIEXAMPLE_ORIGIN_VIDEO_DATA_H
Loading
Loading