Skip to content

Commit

Permalink
Account for NDI stride for odd sized windows
Browse files Browse the repository at this point in the history
  • Loading branch information
glenne committed May 14, 2024
1 parent 48d1baa commit 1f7ab85
Show file tree
Hide file tree
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
Expand Up @@ -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) {
Expand Down
58 changes: 31 additions & 27 deletions native/recorder/src/NdiReader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
}
Expand All @@ -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
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 3 additions & 4 deletions native/recorder/src/RecorderAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions native/recorder/src/VideoRecorder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
34 changes: 34 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 1f7ab85

Please sign in to comment.