mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-09-13 02:54:26 +08:00
perf(video): 新增 RKMPP DMA 采集编码通路并完善恢复逻辑
支持原生 HDMI 和 UVC 缓冲区导出,增加同步 RKMPP 编码及可选 MJPEG 硬件转码。 校验帧布局和缓冲区租约,在 DMA 不可用或编码失败时回退到复制通路。 保留自定义码率和 GOP 策略,重开采集时同步 HDMI 源帧率,并区分 UVC 超时状态。 验证:88 个视频测试通过(含 4 个新增回归测试);ARM64 cargo check --tests 通过。
This commit is contained in:
@@ -488,6 +488,7 @@ mod ffmpeg {
|
||||
}
|
||||
}
|
||||
builder.file(ffmpeg_hw_dir.join("ffmpeg_hw_mjpeg_h26x.cpp"));
|
||||
builder.file(ffmpeg_hw_dir.join("rkmpp_dmabuf.cpp"));
|
||||
} else {
|
||||
println!(
|
||||
"cargo:info=Skipping ffmpeg_hw_mjpeg_h26x.cpp (RKMPP) for arch {}",
|
||||
|
||||
38
libs/hwcodec/cpp/ffmpeg_hw/rkmpp_dma_jpeg.h
Normal file
38
libs/hwcodec/cpp/ffmpeg_hw/rkmpp_dma_jpeg.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Validate bounded JPEG headers before giving hardware a fixed-size output
|
||||
// buffer. Only baseline 8-bit JPEG is accepted; other streams use copy fallback.
|
||||
// No scan-data traversal or full-packet copy is needed.
|
||||
inline bool rkmpp_dma_jpeg_header(const uint8_t *data, size_t size, int width, int height) {
|
||||
if (!data || size < 4 || data[0] != 0xff || data[1] != 0xd8) return false;
|
||||
size_t pos = 2;
|
||||
bool sof = false;
|
||||
while (pos < size) {
|
||||
if (data[pos++] != 0xff) return false;
|
||||
while (pos < size && data[pos] == 0xff) ++pos;
|
||||
if (pos == size) return false;
|
||||
const unsigned marker = data[pos++];
|
||||
if (!marker || marker == 0xd8 || marker == 0xd9 || marker == 1 ||
|
||||
(marker >= 0xd0 && marker <= 0xd7)) return false;
|
||||
if (size - pos < 2) return false;
|
||||
const size_t length = (size_t(data[pos]) << 8) | data[pos + 1];
|
||||
if (length < 2 || length > size - pos) return false;
|
||||
if (marker == 0xc0) {
|
||||
if (sof || length < 8 || data[pos + 2] != 8) return false;
|
||||
const unsigned h = (unsigned(data[pos + 3]) << 8) | data[pos + 4];
|
||||
const unsigned w = (unsigned(data[pos + 5]) << 8) | data[pos + 6];
|
||||
const unsigned components = data[pos + 7];
|
||||
if (w != unsigned(width) || h != unsigned(height) ||
|
||||
(components != 1 && components != 3) || length != 8 + 3 * components) return false;
|
||||
sof = true;
|
||||
} else if (marker >= 0xc0 && marker <= 0xcf && marker != 0xc4 && marker != 0xcc) {
|
||||
return false; // Progressive, lossless, extended or differential SOF.
|
||||
} else if (marker == 0xda) {
|
||||
return sof && length >= 6 && size - pos > length;
|
||||
}
|
||||
pos += length;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
329
libs/hwcodec/cpp/ffmpeg_hw/rkmpp_dmabuf.cpp
Normal file
329
libs/hwcodec/cpp/ffmpeg_hw/rkmpp_dmabuf.cpp
Normal file
@@ -0,0 +1,329 @@
|
||||
#include "rkmpp_dmabuf_ffi.h"
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include "rkmpp_dma_jpeg.h"
|
||||
|
||||
// Native MPP is already linked by the ARM FFmpeg/RKMPP build. Keep this
|
||||
// optional for toolchains which only supply the FFmpeg headers.
|
||||
#if defined(__linux__) && __has_include(<rockchip/rk_mpi.h>)
|
||||
#define HAVE_MPP_DMA 1
|
||||
#include <cerrno>
|
||||
#include <linux/dma-buf.h>
|
||||
#include <sys/ioctl.h>
|
||||
extern "C" {
|
||||
#include <rockchip/rk_mpi.h>
|
||||
#include <rockchip/mpp_buffer.h>
|
||||
#include <rockchip/mpp_frame.h>
|
||||
#include <rockchip/mpp_packet.h>
|
||||
#include <rockchip/mpp_task.h>
|
||||
#include <rockchip/rk_venc_cfg.h>
|
||||
#include <rockchip/rk_venc_rc.h>
|
||||
}
|
||||
#endif
|
||||
|
||||
static thread_local char dma_error[192] = {};
|
||||
static int fail(const char *operation, int code) {
|
||||
std::snprintf(dma_error, sizeof(dma_error), "%s (ret=%d)", operation, code);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_MPP_DMA
|
||||
struct RkmppDmaEncoder {
|
||||
MppCtx ctx = nullptr;
|
||||
MppApi *api = nullptr;
|
||||
MppEncCfg cfg = nullptr;
|
||||
MppPacket packet = nullptr;
|
||||
std::array<MppBuffer, 16> buffers{};
|
||||
std::array<size_t, 16> capacities{};
|
||||
size_t count = 0;
|
||||
size_t minimum = 0;
|
||||
int width = 0, height = 0, stride = 0;
|
||||
MppFrameFormat format = MPP_FMT_YUV420SP;
|
||||
bool jpeg = false;
|
||||
MppCtx decoder = nullptr;
|
||||
MppApi *dec_api = nullptr;
|
||||
MppBufferGroup decoded_group = nullptr;
|
||||
MppBuffer decoded_buffer = nullptr;
|
||||
MppFrame decoded_frame = nullptr;
|
||||
MppPacket input_packet = nullptr;
|
||||
bool decoded_layout_set = false;
|
||||
int decoded_stride = 0, decoded_vstride = 0;
|
||||
|
||||
void close() {
|
||||
if (packet) mpp_packet_deinit(&packet);
|
||||
if (ctx) {
|
||||
// A timeout must not expose still-in-use input to V4L2 QBUF.
|
||||
api->reset(ctx);
|
||||
mpp_destroy(ctx);
|
||||
ctx = nullptr;
|
||||
}
|
||||
// On any decoder failure, end hardware access before releasing the
|
||||
// input packet, exported buffers, or allowing the caller's QBUF.
|
||||
if (decoder) {
|
||||
dec_api->reset(decoder);
|
||||
mpp_destroy(decoder);
|
||||
decoder = nullptr;
|
||||
}
|
||||
if (input_packet) mpp_packet_deinit(&input_packet);
|
||||
if (decoded_frame) mpp_frame_deinit(&decoded_frame);
|
||||
if (decoded_buffer) { mpp_buffer_put(decoded_buffer); decoded_buffer = nullptr; }
|
||||
if (decoded_group) { mpp_buffer_group_put(decoded_group); decoded_group = nullptr; }
|
||||
for (auto &buffer : buffers) {
|
||||
if (buffer) { mpp_buffer_put(buffer); buffer = nullptr; }
|
||||
}
|
||||
if (cfg) { mpp_enc_cfg_deinit(cfg); cfg = nullptr; }
|
||||
}
|
||||
~RkmppDmaEncoder() { close(); }
|
||||
};
|
||||
|
||||
static bool set_cfg(RkmppDmaEncoder *e, const char *key, int value) {
|
||||
int ret = mpp_enc_cfg_set_s32(e->cfg, key, value);
|
||||
if (ret) fail(key, ret);
|
||||
return ret == 0;
|
||||
}
|
||||
|
||||
extern "C" int rkmpp_dma_reconfigure(RkmppDmaEncoder *e, int kbps, int gop) {
|
||||
if (!e || !e->ctx || kbps <= 0 || kbps > 1000000 || gop <= 0)
|
||||
return fail("invalid DMA encoder configuration", -1);
|
||||
const int bps = kbps * 1000;
|
||||
if (!set_cfg(e, "rc:bps_target", bps) ||
|
||||
!set_cfg(e, "rc:bps_max", bps + bps / 16) ||
|
||||
!set_cfg(e, "rc:bps_min", bps - bps / 16) ||
|
||||
!set_cfg(e, "rc:gop", gop)) return -1;
|
||||
int ret = e->api->control(e->ctx, MPP_ENC_SET_CFG, e->cfg);
|
||||
return ret ? fail("MPP_ENC_SET_CFG", ret) : 0;
|
||||
}
|
||||
|
||||
extern "C" RkmppDmaEncoder *rkmpp_dma_new(
|
||||
int width, int height, int stride, int format, int codec, int fps,
|
||||
int kbps, int gop, const int *fds, const size_t *sizes, size_t count) {
|
||||
if (width <= 0 || height <= 0 || width > 8192 || height > 8192 ||
|
||||
(width & 1) || (height & 1) || format < 0 || format > 4 ||
|
||||
codec < 0 || codec > 1 || fps <= 0 || fps > 240 ||
|
||||
(format != 4 && stride < width * (format == 1 || format == 3 ? 3 : format == 2 ? 2 : 1)) ||
|
||||
(format == 2 && stride % 16 != 0) || !fds || !sizes || !count || count > 16) {
|
||||
fail("invalid DMA frame layout", -1); return nullptr;
|
||||
}
|
||||
auto *e = new (std::nothrow) RkmppDmaEncoder;
|
||||
if (!e) { fail("allocate DMA encoder", -1); return nullptr; }
|
||||
e->width = width; e->height = height; e->stride = stride; e->count = count;
|
||||
e->jpeg = format == 4;
|
||||
if (e->jpeg) e->stride = stride = (width + 15) & ~15;
|
||||
const int vstride = e->jpeg ? (height + 15) & ~15 : height;
|
||||
e->format = format == 3 ? MPP_FMT_RGB888 : format == 2 ? MPP_FMT_YUV422_YUYV : format == 1 ? MPP_FMT_BGR888 : MPP_FMT_YUV420SP;
|
||||
auto abort_init = [e](const char *op, int ret) -> RkmppDmaEncoder * {
|
||||
fail(op, ret); delete e; return nullptr;
|
||||
};
|
||||
int ret = mpp_create(&e->ctx, &e->api);
|
||||
if (ret) return abort_init("mpp_create", ret);
|
||||
RK_S64 timeout = 2000;
|
||||
ret = e->api->control(e->ctx, MPP_SET_OUTPUT_TIMEOUT, &timeout);
|
||||
if (ret) return abort_init("MPP_SET_OUTPUT_TIMEOUT", ret);
|
||||
ret = e->api->control(e->ctx, MPP_SET_INPUT_TIMEOUT, &timeout);
|
||||
if (ret) return abort_init("MPP_SET_INPUT_TIMEOUT", ret);
|
||||
ret = mpp_init(e->ctx, MPP_CTX_ENC, codec ? MPP_VIDEO_CodingHEVC : MPP_VIDEO_CodingAVC);
|
||||
if (ret) return abort_init("mpp_init", ret);
|
||||
ret = mpp_enc_cfg_init(&e->cfg);
|
||||
if (ret) return abort_init("mpp_enc_cfg_init", ret);
|
||||
ret = e->api->control(e->ctx, MPP_ENC_GET_CFG, e->cfg);
|
||||
if (ret) return abort_init("MPP_ENC_GET_CFG", ret);
|
||||
if (!set_cfg(e, "prep:width", width) || !set_cfg(e, "prep:height", height) ||
|
||||
!set_cfg(e, "prep:hor_stride", stride) || !set_cfg(e, "prep:ver_stride", vstride) ||
|
||||
!set_cfg(e, "prep:format", e->format) || !set_cfg(e, "rc:mode", MPP_ENC_RC_MODE_CBR) ||
|
||||
!set_cfg(e, "rc:fps_in_flex", 0) || !set_cfg(e, "rc:fps_in_num", fps) ||
|
||||
!set_cfg(e, "rc:fps_in_denorm", 1) || !set_cfg(e, "rc:fps_out_flex", 0) ||
|
||||
!set_cfg(e, "rc:fps_out_num", fps) || !set_cfg(e, "rc:fps_out_denorm", 1) ||
|
||||
!set_cfg(e, "codec:type", codec ? MPP_VIDEO_CodingHEVC : MPP_VIDEO_CodingAVC)) {
|
||||
delete e; return nullptr;
|
||||
}
|
||||
// Match the browser-friendly baseline profile used by the existing RKMPP
|
||||
// byte encoder, rather than inheriting MPP's High-profile default.
|
||||
const int level = int64_t(width) * height * fps <= int64_t(1920) * 1080 * 60 ? 42 : 52;
|
||||
if (!codec && (!set_cfg(e, "h264:profile", 66) || !set_cfg(e, "h264:level", level) ||
|
||||
!set_cfg(e, "h264:cabac_en", 0) || !set_cfg(e, "h264:trans8x8", 0))) {
|
||||
delete e; return nullptr;
|
||||
}
|
||||
if (rkmpp_dma_reconfigure(e, kbps, gop)) { delete e; return nullptr; }
|
||||
MppEncHeaderMode mode = MPP_ENC_HEADER_MODE_EACH_IDR;
|
||||
ret = e->api->control(e->ctx, MPP_ENC_SET_HEADER_MODE, &mode);
|
||||
if (ret) return abort_init("MPP_ENC_SET_HEADER_MODE", ret);
|
||||
// Reject arithmetic overflow even on 32-bit ARM; stride is supplied by a driver.
|
||||
if (size_t(stride) > std::numeric_limits<size_t>::max() / size_t(height))
|
||||
return abort_init("DMA buffer size overflow", -1);
|
||||
size_t minimum = size_t(stride) * height;
|
||||
if (format == 0) {
|
||||
if (minimum > std::numeric_limits<size_t>::max() / 3)
|
||||
return abort_init("DMA buffer size overflow", -1);
|
||||
minimum = minimum * 3 / 2;
|
||||
}
|
||||
if (e->jpeg) minimum = 68; // SOI/payload plus bounded hardware read headroom.
|
||||
e->minimum = minimum;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
if (fds[i] < 0 || sizes[i] < minimum) return abort_init("short DMA buffer", -1);
|
||||
MppBufferInfo info{};
|
||||
info.type = MPP_BUFFER_TYPE_EXT_DMA; info.fd = fds[i];
|
||||
info.size = sizes[i]; info.index = static_cast<int>(i);
|
||||
ret = mpp_buffer_import(&e->buffers[i], &info);
|
||||
if (ret) return abort_init("mpp_buffer_import", ret);
|
||||
e->capacities[i] = sizes[i];
|
||||
}
|
||||
if (e->jpeg) {
|
||||
ret = mpp_create(&e->decoder, &e->dec_api);
|
||||
if (ret) return abort_init("mpp_create JPEG decoder", ret);
|
||||
ret = mpp_init(e->decoder, MPP_CTX_DEC, MPP_VIDEO_CodingMJPEG);
|
||||
if (ret) return abort_init("mpp_init JPEG decoder", ret);
|
||||
MppFrameFormat output = MPP_FMT_YUV420SP;
|
||||
ret = e->dec_api->control(e->decoder, MPP_DEC_SET_OUTPUT_FORMAT, &output);
|
||||
if (ret) return abort_init("JPEG NV12 output", ret);
|
||||
ret = mpp_buffer_group_get_internal(&e->decoded_group, MPP_BUFFER_TYPE_DRM);
|
||||
if (ret) return abort_init("JPEG output buffer group", ret);
|
||||
// MPP JPEG requires aligned storage; reserve the conservative size used
|
||||
// by its advanced-task decoder demo. One output reused after encode.
|
||||
ret = mpp_buffer_get(e->decoded_group, &e->decoded_buffer, size_t(stride) * vstride * 4);
|
||||
if (ret) return abort_init("JPEG output buffer", ret);
|
||||
ret = mpp_frame_init(&e->decoded_frame);
|
||||
if (ret) return abort_init("JPEG output frame", ret);
|
||||
mpp_frame_set_buffer(e->decoded_frame, e->decoded_buffer);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
static int dma_read_sync(MppBuffer buffer, bool start) {
|
||||
dma_buf_sync sync{};
|
||||
sync.flags = DMA_BUF_SYNC_READ | (start ? DMA_BUF_SYNC_START : DMA_BUF_SYNC_END);
|
||||
int ret;
|
||||
do { ret = ioctl(mpp_buffer_get_fd(buffer), DMA_BUF_IOCTL_SYNC, &sync); }
|
||||
while (ret < 0 && errno == EINTR);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int decode_jpeg(RkmppDmaEncoder *e, size_t index, size_t bytes_used) {
|
||||
MppBuffer input = e->buffers[index];
|
||||
// The parser reads only header bytes with explicit DMA CPU-read ownership.
|
||||
if (dma_read_sync(input, true)) return fail("JPEG DMA read sync start", errno);
|
||||
const auto *data = static_cast<const uint8_t *>(mpp_buffer_get_ptr(input));
|
||||
const bool valid = rkmpp_dma_jpeg_header(data, bytes_used, e->width, e->height);
|
||||
if (dma_read_sync(input, false)) return fail("JPEG DMA read sync end", errno);
|
||||
if (!valid) return fail("unsupported/mismatched JPEG header", -1);
|
||||
|
||||
int ret = mpp_packet_init_with_buffer(&e->input_packet, input);
|
||||
if (ret) return fail("JPEG input packet", ret);
|
||||
mpp_packet_set_length(e->input_packet, bytes_used);
|
||||
MppTask task = nullptr;
|
||||
ret = e->dec_api->poll(e->decoder, MPP_PORT_INPUT, static_cast<MppPollType>(2000));
|
||||
if (ret) return fail("JPEG input poll", ret);
|
||||
ret = e->dec_api->dequeue(e->decoder, MPP_PORT_INPUT, &task);
|
||||
if (ret || !task) return fail("JPEG input task", ret);
|
||||
ret = mpp_task_meta_set_packet(task, KEY_INPUT_PACKET, e->input_packet);
|
||||
if (ret) return fail("JPEG input metadata", ret);
|
||||
ret = mpp_task_meta_set_frame(task, KEY_OUTPUT_FRAME, e->decoded_frame);
|
||||
if (ret) return fail("JPEG output metadata", ret);
|
||||
ret = e->dec_api->enqueue(e->decoder, MPP_PORT_INPUT, task);
|
||||
if (ret) return fail("JPEG submit", ret);
|
||||
task = nullptr;
|
||||
ret = e->dec_api->poll(e->decoder, MPP_PORT_OUTPUT, static_cast<MppPollType>(2000));
|
||||
if (ret) return fail("JPEG output poll", ret);
|
||||
ret = e->dec_api->dequeue(e->decoder, MPP_PORT_OUTPUT, &task);
|
||||
if (ret || !task) return fail("JPEG output task", ret);
|
||||
MppFrame result = nullptr;
|
||||
ret = mpp_task_meta_get_frame(task, KEY_OUTPUT_FRAME, &result);
|
||||
if (ret || result != e->decoded_frame) return fail("JPEG output frame mismatch", ret);
|
||||
ret = e->dec_api->enqueue(e->decoder, MPP_PORT_OUTPUT, task);
|
||||
if (ret) return fail("JPEG return output task", ret);
|
||||
ret = e->dec_api->poll(e->decoder, MPP_PORT_INPUT, static_cast<MppPollType>(2000));
|
||||
if (ret) return fail("JPEG input completion", ret);
|
||||
mpp_packet_deinit(&e->input_packet);
|
||||
if (mpp_frame_get_errinfo(result) || mpp_frame_get_discard(result) ||
|
||||
mpp_frame_get_info_change(result) ||
|
||||
mpp_frame_get_width(result) != unsigned(e->width) ||
|
||||
mpp_frame_get_height(result) != unsigned(e->height) ||
|
||||
mpp_frame_get_fmt(result) != MPP_FMT_YUV420SP ||
|
||||
mpp_frame_get_buffer(result) != e->decoded_buffer) {
|
||||
std::snprintf(dma_error, sizeof(dma_error),
|
||||
"invalid JPEG decoded frame: err=%u discard=%u info_change=%u size=%ux%u fmt=%x buffer_match=%d",
|
||||
mpp_frame_get_errinfo(result), mpp_frame_get_discard(result), mpp_frame_get_info_change(result),
|
||||
mpp_frame_get_width(result), mpp_frame_get_height(result), unsigned(mpp_frame_get_fmt(result)),
|
||||
int(mpp_frame_get_buffer(result) == e->decoded_buffer));
|
||||
return -1;
|
||||
}
|
||||
const int hs = mpp_frame_get_hor_stride(result), vs = mpp_frame_get_ver_stride(result);
|
||||
if (hs < e->width || vs < e->height || hs > 8192 || vs > 8192 || (hs & 15) || (vs & 15) ||
|
||||
size_t(hs) * vs * 3 / 2 > mpp_buffer_get_size(e->decoded_buffer))
|
||||
return fail("invalid JPEG decoded stride", -1);
|
||||
if (!e->decoded_layout_set) {
|
||||
if (!set_cfg(e, "prep:hor_stride", hs) || !set_cfg(e, "prep:ver_stride", vs)) return -1;
|
||||
ret = e->api->control(e->ctx, MPP_ENC_SET_CFG, e->cfg);
|
||||
if (ret) return fail("JPEG encoder layout", ret);
|
||||
e->decoded_stride = hs; e->decoded_vstride = vs; e->decoded_layout_set = true;
|
||||
} else if (hs != e->decoded_stride || vs != e->decoded_vstride) {
|
||||
return fail("JPEG decoded layout changed", -1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" int rkmpp_dma_encode(RkmppDmaEncoder *e, size_t index, size_t bytes_used, int fresh_fd, int64_t pts_us,
|
||||
int force_idr, const uint8_t **data, size_t *size) {
|
||||
if (!e || !e->ctx || index >= e->count || !data || !size)
|
||||
return fail("invalid DMA encode call", -1);
|
||||
*data = nullptr; *size = 0;
|
||||
if (e->packet) mpp_packet_deinit(&e->packet);
|
||||
auto abort_encode = [e](const char *op, int ret) {
|
||||
fail(op, ret); e->close(); return -1;
|
||||
};
|
||||
if (bytes_used > e->capacities[index] ||
|
||||
(e->jpeg ? (bytes_used < 4 || e->capacities[index] - bytes_used < 64) : bytes_used != e->minimum))
|
||||
return abort_encode("invalid DMA payload length", -1);
|
||||
if (fresh_fd >= 0) {
|
||||
MppBuffer replacement = nullptr;
|
||||
MppBufferInfo info{};
|
||||
info.type = MPP_BUFFER_TYPE_EXT_DMA; info.fd = fresh_fd;
|
||||
info.size = e->capacities[index]; info.index = static_cast<int>(index);
|
||||
const int ret = mpp_buffer_import(&replacement, &info);
|
||||
if (ret) return abort_encode("refresh USB DMA import", ret);
|
||||
if (e->buffers[index]) mpp_buffer_put(e->buffers[index]);
|
||||
e->buffers[index] = replacement;
|
||||
}
|
||||
if (e->jpeg && decode_jpeg(e, index, bytes_used)) {
|
||||
// Preserve the detailed decoder failure while ending BOTH engines.
|
||||
e->close(); return -1;
|
||||
}
|
||||
if (force_idr) {
|
||||
int ret = e->api->control(e->ctx, MPP_ENC_SET_IDR_FRAME, nullptr);
|
||||
if (ret) return abort_encode("MPP_ENC_SET_IDR_FRAME", ret);
|
||||
}
|
||||
MppFrame frame = e->jpeg ? e->decoded_frame : nullptr;
|
||||
int ret = 0;
|
||||
if (!e->jpeg) {
|
||||
ret = mpp_frame_init(&frame);
|
||||
if (ret) return abort_encode("mpp_frame_init", ret);
|
||||
mpp_frame_set_width(frame, e->width); mpp_frame_set_height(frame, e->height);
|
||||
mpp_frame_set_hor_stride(frame, e->stride); mpp_frame_set_ver_stride(frame, e->height);
|
||||
mpp_frame_set_fmt(frame, e->format);
|
||||
mpp_frame_set_buffer(frame, e->buffers[index]);
|
||||
}
|
||||
mpp_frame_set_pts(frame, pts_us);
|
||||
ret = e->api->encode_put_frame(e->ctx, frame);
|
||||
if (!e->jpeg) mpp_frame_deinit(&frame);
|
||||
if (ret) return abort_encode("encode_put_frame", ret);
|
||||
ret = e->api->encode_get_packet(e->ctx, &e->packet);
|
||||
if (ret || !e->packet) return abort_encode("encode_get_packet", ret);
|
||||
if (mpp_packet_is_partition(e->packet) || !mpp_packet_get_length(e->packet))
|
||||
return abort_encode("incomplete DMA encoder output", -1);
|
||||
// One synchronous input, no temporal scalability/reordering/split output.
|
||||
// A completed packet is the input-consumption barrier for this mode.
|
||||
*data = static_cast<const uint8_t *>(mpp_packet_get_pos(e->packet));
|
||||
*size = mpp_packet_get_length(e->packet);
|
||||
return 0;
|
||||
}
|
||||
extern "C" void rkmpp_dma_free(RkmppDmaEncoder *e) { delete e; }
|
||||
#else
|
||||
extern "C" RkmppDmaEncoder *rkmpp_dma_new(int,int,int,int,int,int,int,int,const int*,const size_t*,size_t) {
|
||||
fail("RKMPP DMA support not built", -1); return nullptr;
|
||||
}
|
||||
extern "C" int rkmpp_dma_encode(RkmppDmaEncoder*,size_t,size_t,int,int64_t,int,const uint8_t**,size_t*) { return -1; }
|
||||
extern "C" int rkmpp_dma_reconfigure(RkmppDmaEncoder*,int,int) { return -1; }
|
||||
extern "C" void rkmpp_dma_free(RkmppDmaEncoder*) {}
|
||||
#endif
|
||||
extern "C" const char *rkmpp_dma_error(void) { return dma_error; }
|
||||
31
libs/hwcodec/cpp/ffmpeg_hw/rkmpp_dmabuf_ffi.h
Normal file
31
libs/hwcodec/cpp/ffmpeg_hw/rkmpp_dmabuf_ffi.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct RkmppDmaEncoder RkmppDmaEncoder;
|
||||
// format: 0=NV12, 1=BGR24, 2=YUYV, 3=RGB24 (byte stride), 4=MJPEG (stride ignored).
|
||||
// codec: 0=H264, 1=HEVC. MJPEG is decoded to hardware NV12, then encoded.
|
||||
RkmppDmaEncoder *rkmpp_dma_new(int width, int height, int stride, int format,
|
||||
int codec, int fps, int kbps, int gop,
|
||||
const int *fds, const size_t *sizes, size_t count);
|
||||
// Synchronous input-completion boundary. On failure the encoder is destroyed
|
||||
// internally BEFORE returning, so it cannot keep reading the capture buffer.
|
||||
// Output is borrowed until the next call or destruction; copy before reusing it.
|
||||
// bytes_used must be the actual captured payload. MJPEG needs 64 bytes of
|
||||
// readable allocation headroom; never pass sizeimage as the compressed length.
|
||||
// fresh_fd=-1 reuses the original import (native HDMI). UVC supplies a new
|
||||
// export of this dequeued slot. Keep it open until replacement/free; the old
|
||||
// export can be closed AFTER this call, including on failure.
|
||||
int rkmpp_dma_encode(RkmppDmaEncoder *encoder, size_t index, size_t bytes_used, int fresh_fd, int64_t pts_us,
|
||||
int force_idr, const uint8_t **data, size_t *size);
|
||||
int rkmpp_dma_reconfigure(RkmppDmaEncoder *encoder, int kbps, int gop);
|
||||
void rkmpp_dma_free(RkmppDmaEncoder *encoder);
|
||||
const char *rkmpp_dma_error(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -5,6 +5,11 @@ pub mod ffmpeg;
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm", feature = "rkmpp"))]
|
||||
pub mod ffmpeg_hw;
|
||||
pub mod ffmpeg_ram;
|
||||
#[cfg(all(
|
||||
target_os = "linux",
|
||||
any(target_arch = "aarch64", target_arch = "arm", feature = "rkmpp")
|
||||
))]
|
||||
pub mod rkmpp_dmabuf;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn hwcodec_log(level: i32, message: *const std::os::raw::c_char) {
|
||||
|
||||
193
libs/hwcodec/src/rkmpp_dmabuf.rs
Normal file
193
libs/hwcodec/src/rkmpp_dmabuf.rs
Normal file
@@ -0,0 +1,193 @@
|
||||
//! Synchronous RKMPP encoder for pre-exported V4L2 DMA buffers.
|
||||
//! Unlike the byte-slice encoder this never reads raw pixels on the CPU.
|
||||
|
||||
use std::ffi::{c_char, c_int, c_void, CStr};
|
||||
use std::os::fd::{AsRawFd, OwnedFd};
|
||||
use std::ptr::NonNull;
|
||||
|
||||
unsafe extern "C" {
|
||||
fn rkmpp_dma_new(
|
||||
width: c_int,
|
||||
height: c_int,
|
||||
stride: c_int,
|
||||
format: c_int,
|
||||
codec: c_int,
|
||||
fps: c_int,
|
||||
kbps: c_int,
|
||||
gop: c_int,
|
||||
fds: *const c_int,
|
||||
sizes: *const usize,
|
||||
count: usize,
|
||||
) -> *mut c_void;
|
||||
fn rkmpp_dma_encode(
|
||||
encoder: *mut c_void,
|
||||
index: usize,
|
||||
bytes_used: usize,
|
||||
fresh_fd: c_int,
|
||||
pts_us: i64,
|
||||
force_idr: c_int,
|
||||
data: *mut *const u8,
|
||||
size: *mut usize,
|
||||
) -> c_int;
|
||||
fn rkmpp_dma_reconfigure(encoder: *mut c_void, kbps: c_int, gop: c_int) -> c_int;
|
||||
fn rkmpp_dma_free(encoder: *mut c_void);
|
||||
fn rkmpp_dma_error() -> *const c_char;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(i32)]
|
||||
pub enum DmaFormat {
|
||||
Nv12 = 0,
|
||||
Bgr24 = 1,
|
||||
Yuyv = 2,
|
||||
Rgb24 = 3,
|
||||
Mjpeg = 4,
|
||||
}
|
||||
|
||||
pub struct DmaEncoderConfig {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub stride: u32,
|
||||
pub format: DmaFormat,
|
||||
pub hevc: bool,
|
||||
pub fps: u32,
|
||||
pub bitrate_kbps: u32,
|
||||
pub gop: u32,
|
||||
}
|
||||
|
||||
pub struct DmaEncoder {
|
||||
ctx: NonNull<c_void>,
|
||||
// Export FDs remain open until AFTER mpp_destroy and imported buffer release.
|
||||
_buffers: Vec<(OwnedFd, usize)>,
|
||||
// Keep refreshed exports alive until native replacement/release has ended
|
||||
// all references to the previous import. At most one FD per capture slot.
|
||||
fresh_buffers: Vec<Option<OwnedFd>>,
|
||||
}
|
||||
|
||||
// Exclusive ownership: the context can move between threads but all calls are sequential.
|
||||
unsafe impl Send for DmaEncoder {}
|
||||
|
||||
fn last_error() -> String {
|
||||
unsafe {
|
||||
CStr::from_ptr(rkmpp_dma_error())
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl DmaEncoder {
|
||||
pub fn new(config: DmaEncoderConfig, buffers: Vec<(OwnedFd, usize)>) -> Result<Self, String> {
|
||||
let fds: Vec<_> = buffers.iter().map(|(fd, _)| fd.as_raw_fd()).collect();
|
||||
let sizes: Vec<_> = buffers.iter().map(|(_, size)| *size).collect();
|
||||
for value in [
|
||||
config.width,
|
||||
config.height,
|
||||
config.stride,
|
||||
config.fps,
|
||||
config.bitrate_kbps,
|
||||
config.gop,
|
||||
] {
|
||||
if value > c_int::MAX as u32 {
|
||||
return Err("DMA encoder parameter overflow".into());
|
||||
}
|
||||
}
|
||||
let ptr = unsafe {
|
||||
rkmpp_dma_new(
|
||||
config.width as _,
|
||||
config.height as _,
|
||||
config.stride as _,
|
||||
config.format as c_int,
|
||||
config.hevc as _,
|
||||
config.fps as _,
|
||||
config.bitrate_kbps as _,
|
||||
config.gop as _,
|
||||
fds.as_ptr(),
|
||||
sizes.as_ptr(),
|
||||
buffers.len(),
|
||||
)
|
||||
};
|
||||
Ok(Self {
|
||||
ctx: NonNull::new(ptr).ok_or_else(last_error)?,
|
||||
fresh_buffers: (0..buffers.len()).map(|_| None).collect(),
|
||||
_buffers: buffers,
|
||||
})
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// The indexed buffer must be dequeued and exclusively leased to this call.
|
||||
/// Do not requeue/write it until this function returns. On failure native MPP
|
||||
/// is synchronously destroyed before returning, ending all input access.
|
||||
/// `bytes_used` is the actual DQBUF payload length, not the buffer capacity.
|
||||
/// A refreshed FD, if supplied, must refer to the same leased capture slot
|
||||
/// with the capacity registered at construction. Ownership is retained here.
|
||||
pub unsafe fn encode(
|
||||
&mut self,
|
||||
index: usize,
|
||||
bytes_used: usize,
|
||||
fresh_fd: Option<OwnedFd>,
|
||||
pts_ms: i64,
|
||||
force_idr: bool,
|
||||
) -> Result<Vec<u8>, String> {
|
||||
let mut data = std::ptr::null();
|
||||
let mut size = 0;
|
||||
if index >= self.fresh_buffers.len() {
|
||||
return Err("Invalid DMA capture index".into());
|
||||
}
|
||||
let ret = unsafe {
|
||||
rkmpp_dma_encode(
|
||||
self.ctx.as_ptr(),
|
||||
index,
|
||||
bytes_used,
|
||||
fresh_fd.as_ref().map_or(-1, AsRawFd::as_raw_fd),
|
||||
pts_ms.saturating_mul(1000),
|
||||
force_idr as _,
|
||||
&mut data,
|
||||
&mut size,
|
||||
)
|
||||
};
|
||||
if fresh_fd.is_some() {
|
||||
// Native has now released the previous import, or destroyed both
|
||||
// hardware contexts on error. Only now may its old FD be closed.
|
||||
self.fresh_buffers[index] = fresh_fd;
|
||||
}
|
||||
if ret != 0 {
|
||||
return Err(last_error());
|
||||
}
|
||||
if data.is_null() || size == 0 {
|
||||
return Err("Empty RKMPP DMA packet".into());
|
||||
}
|
||||
// Copy only the compressed output, releasing the driver's packet promptly
|
||||
// regardless of how long a network subscriber retains its Bytes.
|
||||
Ok(unsafe { std::slice::from_raw_parts(data, size) }.to_vec())
|
||||
}
|
||||
|
||||
pub fn reconfigure(&mut self, kbps: u32, gop: u32) -> Result<(), String> {
|
||||
if kbps > c_int::MAX as u32 || gop > c_int::MAX as u32 {
|
||||
return Err("DMA encoder parameter overflow".into());
|
||||
}
|
||||
if unsafe { rkmpp_dma_reconfigure(self.ctx.as_ptr(), kbps as _, gop as _) } != 0 {
|
||||
return Err(last_error());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DmaEncoder {
|
||||
fn drop(&mut self) {
|
||||
unsafe { rkmpp_dma_free(self.ctx.as_ptr()) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn format_discriminants_match_native_abi() {
|
||||
assert_eq!(DmaFormat::Nv12 as c_int, 0);
|
||||
assert_eq!(DmaFormat::Bgr24 as c_int, 1);
|
||||
assert_eq!(DmaFormat::Yuyv as c_int, 2);
|
||||
assert_eq!(DmaFormat::Rgb24 as c_int, 3);
|
||||
assert_eq!(DmaFormat::Mjpeg as c_int, 4);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user