Skip to content

Commit

Permalink
Reformat source. Add backlog indicator.
Browse files Browse the repository at this point in the history
  • Loading branch information
glenne committed Jan 14, 2025
1 parent 0dfe97f commit 12d939a
Show file tree
Hide file tree
Showing 19 changed files with 561 additions and 604 deletions.
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@
"__errc": "cpp",
"cstdarg": "cpp",
"semaphore": "cpp",
"regex": "cpp"
"regex": "cpp",
"__availability": "cpp",
"valarray": "cpp"
},

"eslint.validate": [
Expand Down
1 change: 1 addition & 0 deletions native/recorder/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
prebuilds/
*node_modules/
lib-build
build/
18 changes: 15 additions & 3 deletions native/recorder/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
cmake_minimum_required(VERSION 3.5) # Set minimum CMake version
cmake_minimum_required(VERSION 3.15) # Set minimum CMake version
project(ctrecorder VERSION 1.0 LANGUAGES CXX)

# Add the path to the directory containing CMake modules
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")

# Set a default build type if none was specified
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message(STATUS "Setting build type to 'Release' as none was specified. Use cmake -DCMAKE_BUILD_TYPE=Debug for Debug.")
Expand Down Expand Up @@ -35,10 +38,17 @@ set(SOURCE_FILES

# prefer static libs
if(APPLE)
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".dylib")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dylib" ".a")
set(EXTRA_LIBS z bz2 lzma pthread)
set(FFMPEG_FOLDER "/Users/glenne/ffmpeg-built-mac")
# set(FFMPEG_FOLDER "./lib-build/ffmpeg-static-mac-arm64")
set(FFMPEG_FOLDER "/opt/homebrew")
# set(USE_OPENCV OFF CACHE BOOL "Use OPENCV features" FORCE)
find_package(NDI REQUIRED)
if(NDI_FOUND)
message(STATUS "NDI found: Library=${NDI_LIBRARIES}, Include=${NDI_INCLUDE_DIR}")
else()
message(FATAL_ERROR "NDI not found!")
endif()
find_library(NDI_LIBRARIES ndi REQUIRED)
elseif(WIN32)
message(STATUS "Win32 section")
Expand Down Expand Up @@ -98,6 +108,7 @@ if (USE_OPENCV)
endif()

if (USE_FFMPEG)
message(STATUS "Adding ffmpeg libraries")
add_compile_definitions(USE_FFMPEG)
list(APPEND SOURCE_FILES
src/FFRecorder.cpp
Expand All @@ -116,6 +127,7 @@ if (USE_FFMPEG)
find_library(AVUTIL_LIBRARY ${LIB_PREFIX}avutil REQUIRED)
find_library(SWSCALE_LIBRARY ${LIB_PREFIX}swscale REQUIRED)
find_library(SWSRESAMPLE_LIBRARY ${LIB_PREFIX}swresample REQUIRED)

set(FFMPEG_LIBS ${AVFORMAT_LIBRARY} ${AVCODEC_LIBRARY} ${AVUTIL_LIBRARY} ${SWSCALE_LIBRARY} ${SWSRESAMPLE_LIBRARY})

message(STATUS "avlibs found at ${FFMPEG_LIBS}")
Expand Down
27 changes: 27 additions & 0 deletions native/recorder/cmake/FindNDI.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Custom FindNDI.cmake file to locate NDI SDK on macOS
set(NDI_SDK_PATH "/Library/NDI SDK for Apple")

# Locate the NDI library
find_library(NDI_LIBRARIES
NAMES ndi
PATHS ${NDI_SDK_PATH}/lib/macOS
REQUIRED
)

# Locate the NDI headers
find_path(NDI_INCLUDE_DIR
NAMES Processing.NDI.Lib.h
PATHS ${NDI_SDK_PATH}/include
REQUIRED
)

# Export the found paths for external use
if(NDI_LIBRARIES AND NDI_INCLUDE_DIR)
set(NDI_FOUND TRUE)
set(NDI_LIBRARIES ${NDI_LIBRARIES})
set(NDI_INCLUDE_DIR ${NDI_INCLUDE_DIR})
else()
set(NDI_FOUND FALSE)
endif()

mark_as_advanced(NDI_LIBRARIES NDI_INCLUDE_DIR)
9 changes: 4 additions & 5 deletions native/recorder/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "crewtimer_video_recorder",
"version": "1.0.6-module",
"version": "1.0.7-module",
"description": "A node electron utility to write mp4 files for CrewTimer Video Review",
"main": "index.js",
"types": "index.d.ts",
Expand All @@ -14,14 +14,13 @@
"upload": "prebuild --tag module --verbose --runtime napi",
"build": "node-gyp rebuild",
"testrebuild": "echo 'Building recorder' && mkdir -p ./release/Build &&cp ../../../../native/recorder/prebuilds/build/Release/crewtimer_video_recorder.node ./release/Build",

"build:mac-x64": "prebuild --prerelease --napi --napi-version 6 --runtime napi --arch x64 --platform darwin",
"build:mac-arm": "prebuild --prerelease --napi --napi-version 6 --runtime napi --arch arm64 --platform darwin",
"build:mac" : "yarn build:mac-x64 && yarn build:mac-arm",
"build:mac": "yarn build:mac-x64 && yarn build:mac-arm",
"build:win": "prebuild --napi --napi-version 6 --runtime napi --arch=x64 --platform win32",
"build:ffmpeg": "bash ./scripts/build-ffmpeg.sh",

"clean": "rm -rf node_modules yarn.lock prebuilds build"
"clean": "rm -rf node_modules yarn.lock prebuilds build",
"cleanbuild": "yarn clean && yarn install && yarn prebuild"
},
"keywords": [],
"author": "Glenn Engel (glenne)",
Expand Down
102 changes: 68 additions & 34 deletions native/recorder/src/FrameProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
#include "VideoUtils.hpp"
using namespace std::chrono;

static int16_t getTimezoneOffset() {
static int16_t getTimezoneOffset()
{
// Get the current time in the system's local timezone
auto now = std::chrono::system_clock::now();
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
Expand All @@ -35,21 +36,31 @@ FrameProcessor::FrameProcessor(const std::string directory,
: directory(directory), prefix(prefix), cropArea(cropArea),
pxCropArea(Rectangle(0, 0, 0, 0)), guide(guide),
videoRecorder(videoRecorder), durationSecs(durationSecs), running(true),
processThread(&FrameProcessor::processFrames, this) {
processThread(&FrameProcessor::processFrames, this)
{
errorMessage = "";
statusInfo.recording = false;
statusInfo.error = "";
statusInfo.filename = "";
statusInfo.width = 0;
statusInfo.height = 0;
statusInfo.fps = 0;
statusInfo.frameBacklog = 0;
tzOffset = getTimezoneOffset();
}

FrameProcessor::~FrameProcessor() { stop(); }

void FrameProcessor::stop() {
void FrameProcessor::stop()
{
errorMessage = "";
running = false;
frameAvailable.notify_all();

if (processThread.joinable())
processThread.join();
while (!frameQueue.empty()) {
while (!frameQueue.empty())
{
frameQueue.pop();
}

Expand All @@ -63,34 +74,41 @@ void FrameProcessor::stop() {
*/
void FrameProcessor::splitFile() { splitRequested = true; }

FrameProcessor::StatusInfo FrameProcessor::getStatus() {
FrameProcessor::StatusInfo FrameProcessor::getStatus()
{
statusInfo.recording = running;
statusInfo.error = errorMessage;
return statusInfo;
}

void FrameProcessor::addFrame(FramePtr video_frame) {
void FrameProcessor::addFrame(FramePtr video_frame)
{
std::unique_lock<std::mutex> lock(queueMutex);
if (!running) {
if (!running)
{
return;
}
lastFrame = video_frame;
frameQueue.push(video_frame);
frameAvailable.notify_one();
}

FramePtr FrameProcessor::getLastFrame() {
FramePtr FrameProcessor::getLastFrame()
{
std::unique_lock<std::mutex> lock(queueMutex);
return lastFrame;
}

void FrameProcessor::writeJsonSidecarFile() {
if (frameCount == 0) {
void FrameProcessor::writeJsonSidecarFile()
{
if (frameCount == 0)
{
return;
}

std::ofstream jsonFile(jsonFilename);
if (!jsonFile) {
if (!jsonFile)
{
errorMessage =
"Error: Could not open the file '" + jsonFilename + "' for writing.";
running = false;
Expand Down Expand Up @@ -118,7 +136,8 @@ void FrameProcessor::writeJsonSidecarFile() {
jsonFile.close();
}

void FrameProcessor::processFrames() {
void FrameProcessor::processFrames()
{
SystemEventQueue::push("fproc", "Starting frame processor");
int count = 0;
auto start = high_resolution_clock::now();
Expand All @@ -128,17 +147,23 @@ void FrameProcessor::processFrames() {
lastFPS = 0;
frameCount = 0;

while (running) {
while (running)
{
std::unique_lock<std::mutex> lock(queueMutex);
frameAvailable.wait(lock,
[this] { return !frameQueue.empty() || !running; });
while (running && !frameQueue.empty()) {
[this]
{ return !frameQueue.empty() || !running; });
while (running && !frameQueue.empty())
{
auto video_frame = frameQueue.front();
frameQueue.pop();

statusInfo.frameBacklog = frameQueue.size();
lock.unlock();
if (video_frame->xres == 0 || video_frame->yres == 0) {
std::cerr << "Null frame received" << std::endl;
continue;
if (video_frame->xres == 0 || video_frame->yres == 0)
{
std::cerr << "Null frame received" << std::endl;
continue;
}
const auto fps =
(float)video_frame->frame_rate_N / (float)video_frame->frame_rate_D;
Expand All @@ -156,9 +181,11 @@ void FrameProcessor::processFrames() {

if (propChange || count == 0 ||
(useEmbeddedTimestamp && video_frame->timestamp >= nextStartTime) ||
(okToSplit && splitRequested)) {
(okToSplit && splitRequested))
{
count++;
if (frameCount > 0) {
if (frameCount > 0)
{
writeJsonSidecarFile();
videoRecorder->stop();
}
Expand Down Expand Up @@ -194,12 +221,12 @@ void FrameProcessor::processFrames() {
statusInfo.height = video_frame->yres;

pxCropArea.x = std::round((cropArea.x * (video_frame->xres) / 4)) * 4;
int cropWidth = cropArea.width*video_frame->xres;
int cropWidth = cropArea.width * video_frame->xres;
cropWidth = std::min(video_frame->xres - pxCropArea.x, cropWidth);
pxCropArea.width = int(cropWidth / 4) * 4; // trunc to mult of 4

pxCropArea.y = std::round((cropArea.y * (video_frame->yres) / 4)) * 4;
int cropHeight = cropArea.height*video_frame->yres;
int cropHeight = cropArea.height * video_frame->yres;
cropHeight = std::min(video_frame->yres - pxCropArea.y, cropHeight);
pxCropArea.height = int(cropHeight / 4) * 4; // trunc to mult of 4

Expand All @@ -214,7 +241,8 @@ void FrameProcessor::processFrames() {
directory, filename,
pxCropArea.width ? pxCropArea.width : video_frame->xres,
pxCropArea.height ? pxCropArea.height : video_frame->yres, fps);
if (!err.empty()) {
if (!err.empty())
{
errorMessage = err;
running = false;
frameCount = 0;
Expand All @@ -232,27 +260,31 @@ void FrameProcessor::processFrames() {
// std::cerr << "vstride: " << std::dec << video_frame->stride
// << "ptr: " << std::hex << (void *)(video_frame->data) << std::dec
// << std::endl;
if (pxCropArea.width && pxCropArea.height) {
if (pxCropArea.width && pxCropArea.height)
{
// std::cout << "Cropping frame (" << pxCropArea.x << "," << pxCropArea.y
// << ")" << pxCropArea.width << "x" << pxCropArea.height
// << std::endl;
cropped = cropFrame(video_frame, pxCropArea.x, pxCropArea.y,
pxCropArea.width, pxCropArea.height);

if (!cropped) {
std::cout << "Crop (" << pxCropArea.x << "," << pxCropArea.y
<< ")" << pxCropArea.width << "x" << pxCropArea.height << " from frame=" << video_frame->xres << "x" << video_frame->yres
<< std::endl;
std::cerr << "Frame crop failed." << std::endl;
cropped = video_frame; // continue with full frame
if (!cropped)
{
std::cout << "Crop (" << pxCropArea.x << "," << pxCropArea.y
<< ")" << pxCropArea.width << "x" << pxCropArea.height << " from frame=" << video_frame->xres << "x" << video_frame->yres
<< std::endl;
std::cerr << "Frame crop failed." << std::endl;
cropped = video_frame; // continue with full frame
}
// std::cerr << " stride: " << std::dec << cropped->stride
// << "ptr: " << std::hex << (void *)(cropped->data) << std::dec
// << std::endl;
encodeTimestamp(cropped->data, cropped->stride, video_frame->timestamp);
}

encodeTimestamp(cropped->data, cropped->stride, video_frame->timestamp);
auto err = videoRecorder->writeVideoFrame(cropped);
if (!err.empty()) {
if (!err.empty())
{
errorMessage = err;
running = false;
frameCount = 0;
Expand All @@ -262,15 +294,17 @@ void FrameProcessor::processFrames() {
frameCount++;

lock.lock();
if (frameQueue.size() > 500) {
if (frameQueue.size() > 500)
{
SystemEventQueue::instance().push(
"fproc", "Frame queue overflow, discarding frames");
frameQueue = std::queue<FramePtr>();
}
}
}

if (frameCount > 0) {
if (frameCount > 0)
{
writeJsonSidecarFile();
videoRecorder->stop();
}
Expand Down
Loading

0 comments on commit 12d939a

Please sign in to comment.