Skip to content

Commit

Permalink
Allow camera selection from discovered cameras. Clean up UI.
Browse files Browse the repository at this point in the history
  • Loading branch information
glenne committed May 18, 2024
1 parent e3466b8 commit 3113681
Show file tree
Hide file tree
Showing 19 changed files with 502 additions and 177 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ npm-debug.log.*
*.sass.d.ts
*.scss.d.ts
*.mp4
query.json
2 changes: 1 addition & 1 deletion native/recorder/src/BaslerReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ class BaslerReader : public VideoReader, public CImageEventHandler {
PylonTerminate();
return 0;
}
virtual std::string start() override {
virtual std::string const std::string srcName) override {
keepRunning = true;
readerThread = std::thread([this]() { run(); });
readerThread.join();
Expand Down
106 changes: 63 additions & 43 deletions native/recorder/src/NdiReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,9 @@ class NdiReader : public VideoReader {
std::thread ndiThread;
std::atomic<bool> keepRunning;
std::shared_ptr<FrameProcessor> frameProcessor;
NDIlib_recv_instance_t pNDI_recv;
const std::string srcName;
NDIlib_recv_instance_t pNDI_recv = nullptr;
NDIlib_find_instance_t pNDI_find = nullptr;
std::string srcName;

class NdiFrame : public Frame {
NDIlib_recv_instance_t pNDI_recv;
Expand All @@ -222,64 +223,72 @@ class NdiReader : public VideoReader {
return "";
}

std::string connect() {
std::vector<CameraInfo> getCameraList() override {
std::vector<CameraInfo> list;
// Create a finder
NDIlib_find_instance_t pNDI_find = NDIlib_find_create_v2();
if (!pNDI_find)
return "NDIlib_find_create_v2() failed";
if (pNDI_find == nullptr) {
pNDI_find = NDIlib_find_create_v2();
}

// Wait until there is a source
if (!pNDI_find)
return list;

const NDIlib_source_t *p_sources = NULL;
uint32_t no_sources = 0;
while (!no_sources) {
// Wait until the sources on the network have changed
NDIlib_find_wait_for_sources(pNDI_find, 2000);
p_sources = NDIlib_find_get_current_sources(pNDI_find, &no_sources);
}

const NDIlib_source_t *p_source = NULL;
for (int src = 0; src < no_sources; src++) {
list.push_back(
CameraInfo(p_sources[src].p_ndi_name, p_sources[src].p_url_address));
// SystemEventQueue::push("NDI", std::string("Source Found: ") +
// p_sources[src].p_ndi_name + " at " +
// p_sources[src].p_ip_address);
}

// FIXME - allow passing in srcName
while (!p_source && keepRunning.load()) {
SystemEventQueue::push("NDI", "Looking for source " + srcName);
const NDIlib_source_t *p_sources = NULL;
uint32_t no_sources = 0;
while (!no_sources) {
// Wait until the sources on the network have changed
NDIlib_find_wait_for_sources(pNDI_find, 1000 /* One second */);
p_sources = NDIlib_find_get_current_sources(pNDI_find, &no_sources);
}
return list;
};

for (int src = 0; src < no_sources; src++) {
SystemEventQueue::push("NDI", std::string("Source Found: ") +
p_sources[src].p_ndi_name + " at " +
p_sources[src].p_ip_address);
if (std::string(p_sources[src].p_ndi_name).find(srcName) == 0) {
p_source = p_sources + src;
std::string connect() {
NDIlib_source_t p_source;
CameraInfo foundCamera;
std::vector<CameraInfo> cameras;
while (foundCamera.name == "" && keepRunning.load()) {
cameras = getCameraList();
for (auto camera : cameras) {
// std::cout << "Found camera: " << camera.name << std::endl;
if (camera.name.find(srcName) == 0) {
foundCamera = camera;
break;
}
}
}

if (!p_source) {
if (foundCamera.name == "") {
return ""; // stop received before ndi source found
}

NDIlib_recv_create_v3_t recv_create;

#ifdef NDI_BGRX
recv_create.color_format =
NDIlib_recv_color_format_BGRX_BGRA; // NDIlib_recv_color_format_RGBX_RGBA;
#else
recv_create.color_format = NDIlib_recv_color_format_UYVY_BGRA;
#endif
// We now have at least one source, so we create a receiver to look at it.
pNDI_recv = NDIlib_recv_create_v3(&recv_create);
if (!pNDI_recv)
return "NDIlib_recv_create_v3() failed";

// Connect to the source
SystemEventQueue::push("NDI", std::string("Connecting to ") +
p_source->p_ndi_name + " at " +
p_source->p_ip_address);
foundCamera.name + " at " +
foundCamera.address);
// Connect to our sources
NDIlib_recv_connect(pNDI_recv, p_source);

// Destroy the NDI finder. We needed to have access to the pointers to
// p_sources[0]
NDIlib_find_destroy(pNDI_find);
p_source.p_ndi_name = foundCamera.name.c_str();
p_source.p_url_address = foundCamera.address.c_str();
NDIlib_recv_connect(pNDI_recv, &p_source);
foundCamera.name = "";
cameras.clear();

return "";
}
Expand All @@ -305,7 +314,8 @@ class NdiReader : public VideoReader {
if (video_frame.xres && video_frame.yres) {
frameCount++;
if (frameCount == 1) {
break; // 1st frame often old frame cached from ndi sender. Ignore.
break; // 1st frame often old frame cached from ndi sender.
// Ignore.
}
if (video_frame.timestamp == NDIlib_recv_timestamp_undefined) {
std::cerr << "timestamp not supported" << std::endl;
Expand Down Expand Up @@ -378,8 +388,9 @@ class NdiReader : public VideoReader {
}

public:
NdiReader(const std::string srcName) : srcName(srcName) {}
std::string start() override {
NdiReader() {}
std::string start(const std::string srcName) override {
this->srcName = srcName;
keepRunning = true;
ndiThread = std::thread([this]() { run(); });
#ifndef _WIN32
Expand All @@ -389,11 +400,20 @@ class NdiReader : public VideoReader {
};
std::string stop() override {
keepRunning = false;
ndiThread.join();
if (ndiThread.joinable()) {
ndiThread.join();
}
return "";
}
virtual ~NdiReader() override {
stop();

if (pNDI_find != nullptr) {
NDIlib_find_destroy(pNDI_find);
}
};
};

std::shared_ptr<VideoReader> createNdiReader(std::string srcName) {
return std::shared_ptr<NdiReader>(new NdiReader(srcName));
std::shared_ptr<VideoReader> createNdiReader() {
return std::shared_ptr<NdiReader>(new NdiReader());
}
89 changes: 56 additions & 33 deletions native/recorder/src/RecorderAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ extern "C" {
#include "VideoController.hpp"

using json = nlohmann::json;
std::shared_ptr<VideoController> recorder;
std::shared_ptr<VideoController> recorder =
std::shared_ptr<VideoController>(new VideoController("ndi"));

// Utility function to clamp a value between 0 and 255
inline uint8_t clamp(int value) {
Expand Down Expand Up @@ -91,6 +92,33 @@ convertEventsToJS(const Napi::Env &env,
return jsArray;
}

// Helper function to convert nlohmann::json to Napi::Object
Napi::Object ConvertJsonToNapiObject(Napi::Env env, const json &j) {
Napi::Object obj = Napi::Object::New(env);
for (auto it = j.begin(); it != j.end(); ++it) {
if (it.value().is_string()) {
const std::string s = it.value();
obj.Set(it.key(), Napi::String::New(env, s));
} else if (it.value().is_number_integer()) {
obj.Set(it.key(), Napi::Number::New(env, it.value()));
} else if (it.value().is_number_float()) {
obj.Set(it.key(), Napi::Number::New(env, it.value()));
} else if (it.value().is_boolean()) {
obj.Set(it.key(), Napi::Boolean::New(env, it.value()));
} else if (it.value().is_object()) {
obj.Set(it.key(), ConvertJsonToNapiObject(env, it.value()));
} else if (it.value().is_array()) {
Napi::Array arr = Napi::Array::New(env, it.value().size());
size_t index = 0;
for (auto &el : it.value()) {
arr.Set(index++, ConvertJsonToNapiObject(env, el));
}
obj.Set(it.key(), arr);
}
}
return obj;
}

// Define a destructor to free uint8_t buffers
void FinalizeBuffer(Napi::Env env, void *data) {
// Clean up memory if necessary
Expand Down Expand Up @@ -135,11 +163,13 @@ Napi::Object nativeVideoRecorder(const Napi::CallbackInfo &info) {
}
auto folder = props.Get("recordingFolder").As<Napi::String>().Utf8Value();
auto prefix = props.Get("recordingPrefix").As<Napi::String>().Utf8Value();
auto networkCamera =
props.Get("networkCamera").As<Napi::String>().Utf8Value();
auto interval =
props.Get("recordingInterval").As<Napi::Number>().Uint32Value();
recorder = std::shared_ptr<VideoController>(
new VideoController("", "ffmpeg", folder, prefix, interval));
auto result = recorder->start();

auto result =
recorder->start(networkCamera, "ffmpeg", folder, prefix, interval);
if (!result.empty()) {
std::cerr << "Error: " << result << std::endl;
ret.Set("status", Napi::String::New(env, "Fail"));
Expand All @@ -153,7 +183,26 @@ Napi::Object nativeVideoRecorder(const Napi::CallbackInfo &info) {
if (recorder) {
auto err = recorder->stop();
std::cerr << "Recorder stoped with status: " << err << std::endl;
recorder = nullptr;
}
return ret;
} else if (op == "get-camera-list") {

if (recorder) {
auto cameras = recorder->getCameraList();
Napi::Array arr = Napi::Array::New(env, cameras.size());
size_t index = 0;
for (auto &camera : cameras) {
auto item = Napi::Object::New(env);
item.Set("name", Napi::String::New(env, camera.name));
item.Set("address", Napi::String::New(env, camera.address));
arr.Set(index++, item);
}

ret.Set("cameras", arr);
ret.Set("status", Napi::String::New(env, "OK"));
} else {
ret.Set("status", Napi::String::New(env, "Fail"));
ret.Set("error", Napi::String::New(env, "No recorder running"));
}
return ret;
} else if (op == "recording-status") {
Expand Down Expand Up @@ -184,8 +233,8 @@ Napi::Object nativeVideoRecorder(const Napi::CallbackInfo &info) {
}
return ret;
} else if (op == "recording-log") {
// TODO - perhaps avoid the copy and make friend class to access the list
// for serialization
// TODO - perhaps avoid the copy and make friend class to access the
// list for serialization
auto list = SystemEventQueue::getEventList();
ret.Set("list", convertEventsToJS(env, list));
return ret;
Expand Down Expand Up @@ -232,32 +281,6 @@ Napi::Object nativeVideoRecorder(const Napi::CallbackInfo &info) {
return ret;
}

// Helper function to convert nlohmann::json to Napi::Object
Napi::Object ConvertJsonToNapiObject(Napi::Env env, const json &j) {
Napi::Object obj = Napi::Object::New(env);
for (auto it = j.begin(); it != j.end(); ++it) {
if (it.value().is_string()) {
const std::string s = it.value();
obj.Set(it.key(), Napi::String::New(env, s));
} else if (it.value().is_number_integer()) {
obj.Set(it.key(), Napi::Number::New(env, it.value()));
} else if (it.value().is_number_float()) {
obj.Set(it.key(), Napi::Number::New(env, it.value()));
} else if (it.value().is_boolean()) {
obj.Set(it.key(), Napi::Boolean::New(env, it.value()));
} else if (it.value().is_object()) {
obj.Set(it.key(), ConvertJsonToNapiObject(env, it.value()));
} else if (it.value().is_array()) {
Napi::Array arr = Napi::Array::New(env, it.value().size());
size_t index = 0;
for (auto &el : it.value()) {
arr.Set(index++, ConvertJsonToNapiObject(env, el));
}
obj.Set(it.key(), arr);
}
}
return obj;
}
Napi::ThreadSafeFunction tsfn;

// Function to send message to Electron main process
Expand Down
Loading

0 comments on commit 3113681

Please sign in to comment.