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
98 changes: 82 additions & 16 deletions src/frame_colorcorrect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@
*/

#include "frame_colorcorrect.h"
#include "mem_info.h"
#include "rockchip_bo.h"

#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <drm_fourcc.h>
#include <xf86drm.h>
#include <rga/im2d.h>
#include <rga/rga.h>
#include <spdlog/spdlog.h>
Expand Down Expand Up @@ -204,22 +211,73 @@ bool FrameColorCorrect::create_targets() {
// GBM_BO_USE_SCANOUT hints the allocator toward a scanout-optimized
// (potentially tiled/compressed) layout; GBM_BO_USE_LINEAR forces the
// layout RGA actually expects.
t.bo = gbm_bo_create(gbm_, width_, height_, GBM_FORMAT_ARGB8888,
GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR);
if (!t.bo) {
spdlog::error("FrameCC: gbm_bo_create failed for target {}", i);
return false;
//
// On >=4GB board variants, this buffer is one of the two RGA reads
// from/writes to during imcvtcolor() below, and GBM has no way to
// request the CMA-backed (<4GB) allocation RGA2's MMU requires (see
// mem_info.h). Bypass GBM there and allocate a plain KMS dumb buffer
// with the Rockchip CONTIG flag set directly; on the 1GB variant this
// can't matter (no memory exists above 4GB), so GBM is left as-is.
uint32_t stride_bytes = 0;
bool want_contig = platform_has_large_ram();
if (want_contig) {
struct drm_mode_create_dumb dmcd;
memset(&dmcd, 0, sizeof(dmcd));
dmcd.width = width_;
dmcd.height = height_;
dmcd.bpp = 32;
dmcd.flags = ROCKCHIP_BO_CONTIG;
int ret;
do {
ret = ioctl(drm_fd_, DRM_IOCTL_MODE_CREATE_DUMB, &dmcd);
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
if (ret == -1) {
spdlog::error("FrameCC: CONTIG dumb-buffer create failed for "
"target {} ({}), falling back to GBM", i, strerror(errno));
want_contig = false;
} else {
struct drm_prime_handle dph;
memset(&dph, 0, sizeof(dph));
dph.handle = dmcd.handle;
dph.flags = DRM_CLOEXEC | DRM_RDWR;
dph.fd = -1;
do {
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
ret = ioctl(drm_fd_, DRM_IOCTL_PRIME_HANDLE_TO_FD, &dph);
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
if (ret == -1) {
spdlog::error("FrameCC: prime export failed for CONTIG "
"target {} ({})", i, strerror(errno));
struct drm_mode_destroy_dumb dmd;
memset(&dmd, 0, sizeof(dmd));
dmd.handle = dmcd.handle;
ioctl(drm_fd_, DRM_IOCTL_MODE_DESTROY_DUMB, &dmd);
return false;
}
t.gem_handle = dmcd.handle;
t.prime_fd = dph.fd;
stride_bytes = dmcd.pitch;
spdlog::info("FrameCC: target {} CONTIG dumb bo stride={}", i, stride_bytes);
}
}
spdlog::info("FrameCC: target {} bo stride={} modifier=0x{:x}", i,
gbm_bo_get_stride(t.bo), gbm_bo_get_modifier(t.bo));

// Export once; keep fd open for RGA use in process()
t.prime_fd = gbm_bo_get_fd(t.bo);
if (t.prime_fd < 0) {
spdlog::error("FrameCC: gbm_bo_get_fd failed for target {}", i);
return false;
if (!want_contig) {
t.bo = gbm_bo_create(gbm_, width_, height_, GBM_FORMAT_ARGB8888,
GBM_BO_USE_RENDERING | GBM_BO_USE_LINEAR);
if (!t.bo) {
spdlog::error("FrameCC: gbm_bo_create failed for target {}", i);
return false;
}
stride_bytes = gbm_bo_get_stride(t.bo);
spdlog::info("FrameCC: target {} bo stride={} modifier=0x{:x}", i,
stride_bytes, gbm_bo_get_modifier(t.bo));

// Export once; keep fd open for RGA use in process()
t.prime_fd = gbm_bo_get_fd(t.bo);
if (t.prime_fd < 0) {
spdlog::error("FrameCC: gbm_bo_get_fd failed for target {}", i);
return false;
}
}
t.stride_px = gbm_bo_get_stride(t.bo) / 4; // bytes/row ÷ 4 bytes/px
t.stride_px = stride_bytes / 4; // bytes/row ÷ 4 bytes/px

// Import as EGLImage → bind as texture → attach as FBO
const EGLint img_attrs[] = {
Expand All @@ -228,7 +286,7 @@ bool FrameColorCorrect::create_targets() {
EGL_LINUX_DRM_FOURCC_EXT, (EGLint)DRM_FORMAT_ARGB8888,
EGL_DMA_BUF_PLANE0_FD_EXT, t.prime_fd,
EGL_DMA_BUF_PLANE0_OFFSET_EXT, 0,
EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)gbm_bo_get_stride(t.bo),
EGL_DMA_BUF_PLANE0_PITCH_EXT, (EGLint)stride_bytes,
EGL_NONE
};
t.img = eglCreateImageKHR_(dpy_, EGL_NO_CONTEXT,
Expand Down Expand Up @@ -451,7 +509,15 @@ void FrameColorCorrect::destroy_targets() {
eglDestroyImageKHR_(dpy_, t.img); t.img = EGL_NO_IMAGE_KHR;
}
if (t.prime_fd >= 0) { close(t.prime_fd); t.prime_fd = -1; }
if (t.bo) { gbm_bo_destroy(t.bo); t.bo = nullptr; }
if (t.bo) {
gbm_bo_destroy(t.bo); t.bo = nullptr;
} else if (t.gem_handle) {
struct drm_mode_destroy_dumb dmd;
memset(&dmd, 0, sizeof(dmd));
dmd.handle = t.gem_handle;
ioctl(drm_fd_, DRM_IOCTL_MODE_DESTROY_DUMB, &dmd);
t.gem_handle = 0;
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/frame_colorcorrect.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ class FrameColorCorrect {
static constexpr int kTargets = 2;
struct Target {
gbm_bo* bo{nullptr};
uint32_t gem_handle{0}; // set instead of bo on the CONTIG (raw
// dumb-buffer) allocation path -- see
// create_targets() in the .cpp
EGLImageKHR img{EGL_NO_IMAGE_KHR};
GLuint tex{0};
GLuint fbo{0};
Expand Down
86 changes: 84 additions & 2 deletions src/frame_processor.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
#include <pthread.h>
#include <time.h>
#include <cerrno>
#include <chrono>
#include <cstring>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>

#include "spdlog/spdlog.h"

#include <rga/im2d.h>
#include <rga/rga.h>
#include <xf86drm.h>

#include "dvr.h"
#include "frame_processor.h"
#include "mem_info.h"
#include "rockchip_bo.h"

// Map MPP pixel format to the corresponding RGA format for im2d DMA copies.
static int mpp_fmt_to_rga(MppFrameFormat fmt)
Expand All @@ -25,6 +32,67 @@ static inline uint32_t align_up(uint32_t v, uint32_t a) {
return (v + a - 1) & ~(a - 1);
}

bool FrameProcessor::alloc_contig_proc_copy(size_t size) {
struct drm_mode_create_dumb dmcd;
memset(&dmcd, 0, sizeof(dmcd));
// Opaque byte blob: width=size, height=1, bpp=8 gives exactly `size`
// bytes with pitch==size -- no image format semantics needed here,
// proc_copy_ is addressed by mpp_buffer_get_fd()/get_ptr() only.
dmcd.width = (uint32_t)size;
dmcd.height = 1;
dmcd.bpp = 8;
dmcd.flags = ROCKCHIP_BO_CONTIG;
int ret;
do {
ret = ioctl(drm_fd_, DRM_IOCTL_MODE_CREATE_DUMB, &dmcd);
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));
if (ret == -1) {
spdlog::error("FrameProcessor: CONTIG dumb-buffer create failed ({})", strerror(errno));
return false;
}

struct drm_prime_handle dph;
memset(&dph, 0, sizeof(dph));
dph.handle = dmcd.handle;
dph.flags = DRM_CLOEXEC | DRM_RDWR;
dph.fd = -1;
do {
ret = ioctl(drm_fd_, DRM_IOCTL_PRIME_HANDLE_TO_FD, &dph);
} while (ret == -1 && (errno == EINTR || errno == EAGAIN));

// The dma-buf fd (once exported) holds its own reference to the
// backing memory, so the GEM handle can be closed immediately --
// standard DRM/dma-buf pattern, same as done for the FrameColorCorrect
// render targets.
struct drm_mode_destroy_dumb dmd;
memset(&dmd, 0, sizeof(dmd));
dmd.handle = dmcd.handle;
ioctl(drm_fd_, DRM_IOCTL_MODE_DESTROY_DUMB, &dmd);

if (ret == -1) {
spdlog::error("FrameProcessor: CONTIG prime export failed ({})", strerror(errno));
return false;
}

MppBufferInfo info;
memset(&info, 0, sizeof(info));
info.type = MPP_BUFFER_TYPE_DRM;
info.size = size;
info.fd = dph.fd;
MPP_RET mret = mpp_buffer_import(&proc_copy_, &info);
// mpp_buffer_import() dups whatever fd it's handed rather than taking
// ownership of it -- close our copy unconditionally, on both success
// and failure, or a failed import leaks dph.fd (info.fd is only
// touched on the success path).
close(dph.fd);
if (mret != MPP_OK) {
spdlog::error("FrameProcessor: CONTIG buffer import failed ({})", (int)mret);
Comment thread
qodo-free-for-open-source-projects[bot] marked this conversation as resolved.
proc_copy_ = nullptr;
return false;
}
return true;
}

FrameProcessor::FrameProcessor(MppEncoder *enc, int fps, EncResolution res, int drm_fd,
std::function<void()> on_fatal_error)
: encoder(enc), interval_ns(1000000000L / fps), target_res_((int)res),
Expand Down Expand Up @@ -164,9 +232,20 @@ void FrameProcessor::process_loop() {

size_t dst_sz = (size_t)dst_hs * dst_vs * 3 / 2; // NV12

if (hold_grp && (!proc_copy_ || mpp_buffer_get_size(proc_copy_) < dst_sz)) {
if (!proc_copy_ || mpp_buffer_get_size(proc_copy_) < dst_sz) {
if (proc_copy_) { mpp_buffer_put(proc_copy_); proc_copy_ = nullptr; }
mpp_buffer_get(hold_grp, &proc_copy_, dst_sz);

if (platform_has_large_ram() && drm_fd_ >= 0) {
alloc_contig_proc_copy(dst_sz);
// On failure, deliberately do NOT fall back to hold_grp
// here: that would risk landing this buffer >=4GB again,
// exactly the corruption this workaround exists to
// prevent (see mem_info.h). proc_copy_ staying null is
// handled as fatal below, same as any other
// unrecoverable conversion failure.
} else if (hold_grp) {
mpp_buffer_get(hold_grp, &proc_copy_, dst_sz);
}
}
if (proc_copy_) {
// ── GL path: colour-correct + OSD in one GPU pass ───────
Expand Down Expand Up @@ -274,6 +353,9 @@ void FrameProcessor::process_loop() {
proc_meta_.fmt = fresh.fmt;
proc_meta_.buffer = nullptr;
}
} else {
spdlog::error("FrameProcessor: no reencode working buffer available, stopping DVR reencode");
fatal = true;
}
fresh.release(); // decoder buffer is free again
}
Expand Down
8 changes: 8 additions & 0 deletions src/frame_processor.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ class FrameProcessor {
void process_loop();
void timer_loop();

// Attempts CMA-backed (<4GB-guaranteed) allocation of proc_copy_ via a
// raw DRM dumb buffer, bypassing MPP's buffer-group CONTIG/DMA32 flags
// (see mem_info.h -- they don't reliably route to CMA on this
// platform). Only called on large-RAM boards. On failure, proc_copy_
// is left null; the caller does not fall back to hold_grp for this --
// see process_loop().
bool alloc_contig_proc_copy(size_t size);

MppEncoder *encoder;
std::atomic<long> interval_ns;
std::atomic<int> target_res_{1}; // 0=720p, 1=1080p
Expand Down
33 changes: 33 additions & 0 deletions src/mem_info.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once
/**
* mem_info.h
*
* Gate for the DVR-reencode CONTIG/CMA buffer workaround (see
* frame_processor.cpp and frame_colorcorrect.cpp): RGA2's MMU can only
* address physical memory below 4GB, and a kernel bug in the Rockchip GEM
* allocator (the __GFP_DMA32 safety flag is only ever applied under
* CONFIG_ARM_LPAE, a 32-bit-only Kconfig symbol never set on this arm64
* kernel) means a buffer can silently land above that boundary on board
* variants with >=4GB RAM. On the 1GB variant no buffer can ever
* physically be placed above 4GB, so forcing CMA allocation there would
* only add memory pressure for zero benefit -- gate on actual installed
* RAM rather than a compile-time board flag so one binary is correct on
* both variants.
*/

#include <cstdio>

inline bool platform_has_large_ram() {
FILE *f = fopen("/proc/meminfo", "r");
if (!f) return false;
char line[256];
unsigned long kb = 0;
bool found = false;
while (fgets(line, sizeof(line), f)) {
if (sscanf(line, "MemTotal: %lu kB", &kb) == 1) { found = true; break; }
}
fclose(f);
// Wide margin between the ~1GB and ~4GB board variants -- this only
// needs to land on the right side, not find a precise boundary.
return found && kb > 2ul * 1024 * 1024;
}
14 changes: 14 additions & 0 deletions src/rockchip_bo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#pragma once
/**
* rockchip_bo.h
*
* Rockchip vendor extension to struct drm_mode_create_dumb.flags (normally
* "must be zero"), from include/uapi/drm/rockchip_drm.h -- not shipped in
* this build's sysroot, so mirrored here. ROCKCHIP_BO_CONTIG requests
* CMA-backed allocation, guaranteed to sit below the 4GB physical boundary
* RGA2's MMU can address; see mem_info.h for why that matters.
*/

#include <cstdint>

static constexpr uint32_t ROCKCHIP_BO_CONTIG = 1u << 0;