Skip to content

Commit

Permalink
Account for NDI stride for odd sized windows
Browse files Browse the repository at this point in the history
glenne committed May 14, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent 48d1baa commit 1f7ab85
Showing 6 changed files with 72 additions and 32 deletions.
2 changes: 1 addition & 1 deletion native/recorder/src/FFRecorder.cpp
Original file line number Diff line number Diff line change
@@ -199,7 +199,7 @@ class FFVideoRecorder : public VideoRecorder {
}

std::string writeVideoFrame(FramePtr video_frame) {
int inLinesize[1] = {2 * video_frame->xres};
int inLinesize[1] = {video_frame->stride};
if (sws_ctx == nullptr) {
auto src_fmt = AV_PIX_FMT_UYVY422;
switch (video_frame->pixelFormat) {
58 changes: 31 additions & 27 deletions native/recorder/src/NdiReader.cpp
Original file line number Diff line number Diff line change
@@ -86,25 +86,25 @@ class Point {
}
};

void setDigitPixels(uint32_t *screen, int digit, Point &start, int xres,
void setDigitPixels(uint32_t *screen, int digit, Point &start, int stride,
uint32_t fg, uint32_t bg) {
std::vector<std::vector<int>> &digitPixels =
digits[digit]; // Get the pixel representation for the digit

const auto border = 4;
for (size_t y = 0; y < border; y++) {
for (size_t x = 0; x < digitPixels[0].size() * scale + border * 2; ++x) {
screen[(start.x + x) + (start.y + y) * xres / 2] = bg;
screen[(start.x + x) + (start.y + y) * stride / 4] = bg;
screen[(start.x + x) +
(start.y + y + digitPixels.size() * scale + border) * xres / 2] =
(start.y + y + digitPixels.size() * scale + border) * stride / 4] =
bg;
}
}
for (size_t y = 0; y < digitPixels.size() * scale + 2 * border; ++y) {
for (size_t x = 0; x < border; x++) {
screen[(start.x + x) + (start.y + y) * xres / 2] = bg;
screen[(start.x + x) + (start.y + y) * stride / 4] = bg;
screen[(start.x + x + border + digitPixels[0].size() * scale) +
(start.y + y) * xres / 2] = bg;
(start.y + y) * stride / 4] = bg;
}
}
const auto yOffset = border;
@@ -115,7 +115,7 @@ void setDigitPixels(uint32_t *screen, int digit, Point &start, int xres,
const auto pixel = digitPixels[y][x] ? fg : bg;
for (size_t xExpand = 0; xExpand < scale; xExpand++) {
screen[(xOffset + start.x + x * scale + xExpand) +
((yOffset + start.y) + y * scale + yExpand) * xres / 2] =
((yOffset + start.y) + y * scale + yExpand) * stride / 4] =
pixel;
}
}
@@ -124,35 +124,35 @@ void setDigitPixels(uint32_t *screen, int digit, Point &start, int xres,
start.x += int(digitPixels[0].size() * scale + border * 2 - 2);
}

void setArea(uint32_t *screen, int xres, int startX, int startY, int width,
void setArea(uint32_t *screen, int stride, int startX, int startY, int width,
int height, uint32_t color) {
for (int x = startX / 2; x < startX / 2 + width / 2; x++) {
for (int y = startY; y < startY + height; y++) {
screen[x + y * xres / 2] = color;
screen[x + y * stride / 4] = color;
}
}
}

void overlayDigits(uint32_t *screen, int xres, Point &point, uint16_t value,
void overlayDigits(uint32_t *screen, int stride, Point &point, uint16_t value,
int digits) {
// clearArea(screen, xres, point.x - scale * 2, point.y - scale * 4,
// clearArea(screen, stride, point.x - scale * 2, point.y - scale * 4,
// digits * (3 * scale + 2 * scale) + scale*4,
// 5 * scale + scale * 6);

if (digits >= 3) {
const auto hundreds = (value / 100) % 10;
setDigitPixels(screen, hundreds, point, xres, timeColor, black);
setDigitPixels(screen, hundreds, point, stride, timeColor, black);
}
if (digits >= 2) {
const auto tens = (value / 10) % 10;
setDigitPixels(screen, tens, point, xres, timeColor, black);
setDigitPixels(screen, tens, point, stride, timeColor, black);
}

const auto ones = value % 10;
setDigitPixels(screen, ones, point, xres, timeColor, black);
setDigitPixels(screen, ones, point, stride, timeColor, black);
}

void overlayTime(uint32_t *screen, int xres, uint64_t ts100ns) {
void overlayTime(uint32_t *screen, int stride, uint64_t ts100ns) {
const auto milli = (5000 + ts100ns) / 10000;

// Convert utc milliseconds to time_point of system clock
@@ -171,26 +171,26 @@ void overlayTime(uint32_t *screen, int xres, uint64_t ts100ns) {

Point point = Point(20, 40);

overlayDigits(screen, xres, point, local_hours, 2);
setDigitPixels(screen, 10, point, xres, timeColor, black);
overlayDigits(screen, xres, point, local_minutes, 2);
setDigitPixels(screen, 10, point, xres, timeColor, black);
overlayDigits(screen, xres, point, local_secs, 2);
setDigitPixels(screen, 11, point, xres, timeColor, black);
overlayDigits(screen, xres, point, milli % 1000, 3);
overlayDigits(screen, stride, point, local_hours, 2);
setDigitPixels(screen, 10, point, stride, timeColor, black);
overlayDigits(screen, stride, point, local_minutes, 2);
setDigitPixels(screen, 10, point, stride, timeColor, black);
overlayDigits(screen, stride, point, local_secs, 2);
setDigitPixels(screen, 11, point, stride, timeColor, black);
overlayDigits(screen, stride, point, milli % 1000, 3);

// std::cout << "Current time: " << local_hours << ":" << local_minutes << ":"
// << local_secs << std::endl;
// std::cout << "Calced time: " << (milli / (60 * 60 * 1000)) % 24 << ":"
// << (milli / (60 * 1000)) % 60 << ":" << (milli / (1000)) % 60
// << " m=" << milli << std::endl;

setArea(screen, xres, 0, 0, 128, 3, black);
setArea(screen, stride, 0, 0, 128, 3, black);
uint64_t mask = 0x8000000000000000L;
for (int bit = 0; bit < 64; bit++) {
const bool val = (ts100ns & mask) != 0;
mask >>= 1;
setArea(screen, xres, bit * 2, 1, 2, 1, val ? white : black);
setArea(screen, stride, bit * 2, 1, 2, 1, val ? white : black);
}
}

@@ -302,10 +302,13 @@ class NdiReader : public VideoReader {
// Video data
case NDIlib_frame_type_video: {
if (video_frame.xres && video_frame.yres) {
if (video_frame.timestamp == NDIlib_recv_timestamp_undefined) {
std::cerr << "timestamp not supported" << std::endl;
}
// std::cout << "Video data received (" << video_frame.xres << "x"
// << video_frame.yres << std::endl;
overlayTime((uint32_t *)video_frame.p_data, video_frame.xres,
video_frame.timestamp);
overlayTime((uint32_t *)video_frame.p_data,
video_frame.line_stride_in_bytes, video_frame.timestamp);
auto delta = video_frame.timestamp - lastTS;
if (delta == 0 || (lastTS != 0 && delta > 400000)) {
// Convert 100ns ticks since 1970 to a duration, then to
@@ -341,8 +344,9 @@ class NdiReader : public VideoReader {

lastTS = video_frame.timestamp;
auto txframe = std::make_shared<NdiFrame>(pNDI_recv, video_frame);
txframe->xres = video_frame.xres;
txframe->yres = video_frame.yres;
txframe->xres = video_frame.xres & ~1; // force even
txframe->yres = video_frame.yres & ~1;
txframe->stride = video_frame.line_stride_in_bytes;
txframe->timestamp = video_frame.timestamp;
txframe->data = video_frame.p_data;
txframe->frame_rate_N = video_frame.frame_rate_N;
7 changes: 3 additions & 4 deletions native/recorder/src/RecorderAPI.cpp
Original file line number Diff line number Diff line change
@@ -26,14 +26,13 @@ inline uint8_t clamp(int value) {

// Function to convert a UYVY422 buffer to a preallocated RGBA buffer
void uyvyToRgba(const uint8_t *uyvyBuffer, uint8_t *rgbaBuffer, int width,
int height) {
int height, int stride) {
// Initialize index for the RGBA buffer
int rgbaIndex = 0;

for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; x += 2) {
// Each UYVY pixel pair contains four bytes
int uyvyIndex = (y * width + x) * 2;
int uyvyIndex = y * stride + x * 2;
uint8_t u = uyvyBuffer[uyvyIndex];
uint8_t y0 = uyvyBuffer[uyvyIndex + 1];
uint8_t v = uyvyBuffer[uyvyIndex + 2];
@@ -203,7 +202,7 @@ Napi::Object nativeVideoRecorder(const Napi::CallbackInfo &info) {

auto bufferData = Napi::Buffer<uint8_t>::New(env, totalBytes);
uyvyToRgba(uyvy422Frame->data, bufferData.Data(), uyvy422Frame->xres,
uyvy422Frame->yres);
uyvy422Frame->yres, uyvy422Frame->stride);
// Napi::Buffer<uint8_t> napiBuffer = Napi::Buffer<uint8_t>::New(
// env, bufferData, totalBytes, FinalizeBuffer);
// ret.Set("data", napiBuffer);
1 change: 1 addition & 0 deletions native/recorder/src/VideoRecorder.hpp
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ class Frame {
enum PixelFormat { UYVY422 = 0, RGBX = 1, BGR = 2 };
int xres;
int yres;
int stride;
uint8_t *data;
uint64_t timestamp;
int frame_rate_N;
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -113,6 +113,7 @@
"react-dom": "^18.2.0",
"react-markdown": "^9.0.1",
"react-markdown-css": "^1.0.2",
"react-measure": "^2.5.2",
"react-router-dom": "^6.22.3",
"react-usedatum": "^1.0.7",
"rehype-raw": "^7.0.0",
@@ -130,6 +131,7 @@
"@types/node": "20.6.2",
"@types/react": "^18.2.21",
"@types/react-dom": "^18.2.7",
"@types/react-measure": "^2.0.12",
"@types/react-test-renderer": "^18.0.1",
"@types/terser-webpack-plugin": "^5.0.4",
"@types/webpack-bundle-analyzer": "^4.6.0",
34 changes: 34 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
@@ -1026,6 +1026,13 @@
dependencies:
regenerator-runtime "^0.14.0"

"@babel/runtime@^7.2.0":
version "7.24.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.5.tgz#230946857c053a36ccc66e1dd03b17dd0c4ed02c"
integrity sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==
dependencies:
regenerator-runtime "^0.14.0"

"@babel/template@^7.22.15", "@babel/template@^7.24.0", "@babel/template@^7.3.3":
version "7.24.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50"
@@ -2359,6 +2366,13 @@
dependencies:
"@types/react" "*"

"@types/react-measure@^2.0.12":
version "2.0.12"
resolved "https://registry.yarnpkg.com/@types/react-measure/-/react-measure-2.0.12.tgz#e8ba05057357b9529aa4115064fe7ea77549f54c"
integrity sha512-Y6V11CH6bU7RhqrIdENPwEUZlPXhfXNGylMNnGwq5TAEs2wDoBA3kSVVM/EQ8u72sz5r9ja+7W8M8PIVcS841Q==
dependencies:
"@types/react" "*"

"@types/react-test-renderer@^18.0.1":
version "18.0.7"
resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-18.0.7.tgz#2cfe657adb3688cdf543995eceb2e062b5a68728"
@@ -5832,6 +5846,11 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2, get-intrinsic@
has-symbols "^1.0.3"
hasown "^2.0.0"

get-node-dimensions@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/get-node-dimensions/-/get-node-dimensions-1.2.1.tgz#fb7b4bb57060fb4247dd51c9d690dfbec56b0823"
integrity sha512-2MSPMu7S1iOTL+BOa6K1S62hB2zUAYNF/lV0gSVlOaacd087lc6nR1H1r0e3B1CerTo+RceOmi1iJW+vp21xcQ==

get-package-type@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
@@ -9390,6 +9409,16 @@ react-markdown@^9.0.1:
unist-util-visit "^5.0.0"
vfile "^6.0.0"

react-measure@^2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/react-measure/-/react-measure-2.5.2.tgz#4ffc410e8b9cb836d9455a9ff18fc1f0fca67f89"
integrity sha512-M+rpbTLWJ3FD6FXvYV6YEGvQ5tMayQ3fGrZhRPHrE9bVlBYfDCLuDcgNttYfk8IqfOI03jz6cbpqMRTUclQnaA==
dependencies:
"@babel/runtime" "^7.2.0"
get-node-dimensions "^1.2.1"
prop-types "^15.6.2"
resize-observer-polyfill "^1.5.0"

react-refresh@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.14.0.tgz#4e02825378a5f227079554d4284889354e5f553e"
@@ -9667,6 +9696,11 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==

resize-observer-polyfill@^1.5.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==

resolve-alpn@^1.0.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"

0 comments on commit 1f7ab85

Please sign in to comment.