-
Notifications
You must be signed in to change notification settings - Fork 75
feat(linux): VAAPI decode to DRM PRIME dma-buf #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| // VAAPI decode → DRM PRIME (dma-buf). Linux only. No RGB download. | ||
|
|
||
| extern "C" { | ||
| #include <libavcodec/avcodec.h> | ||
| #include <libavutil/hwcontext.h> | ||
| #include <libavutil/hwcontext_drm.h> | ||
| } | ||
|
|
||
| #include <new> | ||
| #include <string.h> | ||
| #include <unistd.h> | ||
|
|
||
| #include "ffmpeg_ram_ffi.h" | ||
|
|
||
| namespace { | ||
|
|
||
| struct VaapiPrimeDec { | ||
| AVCodecContext *c = nullptr; | ||
| AVBufferRef *hw_device_ctx = nullptr; | ||
| AVFrame *frame = nullptr; | ||
| AVFrame *mapped = nullptr; | ||
| AVPacket *pkt = nullptr; | ||
| }; | ||
|
|
||
| enum AVPixelFormat get_hw_format(AVCodecContext *ctx, | ||
| const enum AVPixelFormat *pix_fmts) { | ||
| (void)ctx; | ||
| for (const enum AVPixelFormat *p = pix_fmts; *p != AV_PIX_FMT_NONE; p++) { | ||
| if (*p == AV_PIX_FMT_VAAPI) { | ||
| return *p; | ||
| } | ||
| } | ||
| return AV_PIX_FMT_NONE; | ||
| } | ||
|
|
||
| void close_mapped(VaapiPrimeDec *d) { | ||
| if (d->mapped) { | ||
| av_frame_unref(d->mapped); | ||
| } | ||
| } | ||
|
|
||
| int fill_prime(AVFrame *mapped, FFmpegPrimeFrame *out) { | ||
| auto *desc = (AVDRMFrameDescriptor *)mapped->data[0]; | ||
| if (!desc || desc->nb_layers < 1 || desc->nb_objects < 1) { | ||
| return 0; | ||
| } | ||
| memset(out, 0, sizeof(*out)); | ||
| out->kind = FFMPEG_GPU_FRAME_PRIME; | ||
| if (desc->nb_objects > 4) { | ||
| return 0; | ||
| } | ||
| out->n_fds = desc->nb_objects; | ||
| for (int i = 0; i < out->n_fds; i++) { | ||
| int fd = dup(desc->objects[i].fd); | ||
| if (fd < 0) { | ||
| for (int j = 0; j < i; j++) { | ||
| close(out->fds[j]); | ||
| } | ||
| return 0; | ||
| } | ||
| out->fds[i] = fd; | ||
| if (i == 0) { | ||
| out->modifier = desc->objects[i].format_modifier; | ||
| } | ||
| } | ||
| out->width = mapped->width; | ||
| out->height = mapped->height; | ||
| int n = 0; | ||
| for (int L = 0; L < desc->nb_layers && n < 4; L++) { | ||
| AVDRMLayerDescriptor *layer = &desc->layers[L]; | ||
| for (int p = 0; p < layer->nb_planes && n < 4; p++) { | ||
| out->pitches[n] = (int)layer->planes[p].pitch; | ||
| out->offsets[n] = (int)layer->planes[p].offset; | ||
| out->obj_indices[n] = layer->planes[p].object_index; | ||
| n++; | ||
| } | ||
| } | ||
| out->n_planes = n; | ||
| if (desc->nb_layers == 1) { | ||
| out->fourcc = desc->layers[0].format; | ||
| } else { | ||
| out->fourcc = 0x3231564e; // DRM_FORMAT_NV12 | ||
| } | ||
|
Comment on lines
+79
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: sed -n '36,88p' cpp/ffmpeg_ram/ffmpeg_vaapi_prime.cpp
sed -n '1,120p' src/ffmpeg_ram/prime.rs
rg -n 'fourcc|n_planes|obj_indices|PrimeFrame|last' src cpp examplesRepository: rustdesk-org/hwcodec Length of output: 7674 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- native decoder setup and receive path ---'
cat -n cpp/ffmpeg_ram/ffmpeg_vaapi_prime.cpp | sed -n '1,230p'
printf '%s\n' '--- FFI and Rust module consumers ---'
cat -n cpp/ffmpeg_ram/ffmpeg_ram_ffi.h | sed -n '25,60p'
rg -n -C 5 'VaapiPrimeDecoder|PrimeFrame|GPU_FRAME_PRIME|fourcc|obj_indices|n_planes|ffmpeg_vaapi_prime' --glob '!target/**' --glob '!node_modules/**' src cpp examples Cargo.toml build.rs
printf '%s\n' '--- relevant project files ---'
rg -n -C 4 'VAAPI|PRIME|H264|HEVC|H\\.264|H\\.265|NV12|DRM_FORMAT' README.md docs cpp src build.rs Cargo.toml 2>/dev/null || trueRepository: rustdesk-org/hwcodec Length of output: 50376 🤖 get_repo_knowledge executed:
Length of output: 3399 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- native decoder setup and receive path ---'
cat -n cpp/ffmpeg_ram/ffmpeg_vaapi_prime.cpp | sed -n '1,230p'
printf '%s\n' '--- FFI and Rust module consumers ---'
cat -n cpp/ffmpeg_ram/ffmpeg_ram_ffi.h | sed -n '25,60p'
rg -n -C 5 'VaapiPrimeDecoder|PrimeFrame|GPU_FRAME_PRIME|fourcc|obj_indices|n_planes|ffmpeg_vaapi_prime' --glob '!target/**' --glob '!node_modules/**' --glob '!build/**' src cpp examples Cargo.toml build.rs
printf '%s\n' '--- relevant project files ---'
rg -n -C 4 'VAAPI|PRIME|H264|HEVC|H\\.264|H\\.265|NV12|DRM_FORMAT' README.md docs cpp src build.rs Cargo.toml 2>/dev/null || trueRepository: rustdesk-org/hwcodec Length of output: 50376 🌐 Web query:
💡 Result: <search_synthesis> <source_evidence> Citations:
Do not assume that every multi-layer descriptor is NV12. FFmpeg permits a multi-layer NV12 descriptor, such as Reject unsupported layouts, or preserve the descriptor’s overall format and layer grouping in 🤖 Prompt for AI Agents |
||
| return n >= 1; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| extern "C" void *ffmpeg_vaapi_prime_new(int hevc) { | ||
| auto *d = new (std::nothrow) VaapiPrimeDec(); | ||
| if (!d) { | ||
| return nullptr; | ||
| } | ||
| const AVCodec *codec = avcodec_find_decoder_by_name(hevc ? "hevc" : "h264"); | ||
| if (!codec) { | ||
| delete d; | ||
| return nullptr; | ||
| } | ||
| d->c = avcodec_alloc_context3(codec); | ||
| if (!d->c) { | ||
| delete d; | ||
| return nullptr; | ||
| } | ||
| d->c->flags |= AV_CODEC_FLAG_LOW_DELAY; | ||
| d->c->thread_count = 1; | ||
| d->c->get_format = get_hw_format; | ||
| d->c->extra_hw_frames = 8; | ||
| int ret = av_hwdevice_ctx_create(&d->hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, | ||
| nullptr, nullptr, 0); | ||
| if (ret < 0) { | ||
| ffmpeg_vaapi_prime_free(d); | ||
| return nullptr; | ||
| } | ||
| d->c->hw_device_ctx = av_buffer_ref(d->hw_device_ctx); | ||
| d->pkt = av_packet_alloc(); | ||
| d->frame = av_frame_alloc(); | ||
| d->mapped = av_frame_alloc(); | ||
| if (!d->pkt || !d->frame || !d->mapped) { | ||
| ffmpeg_vaapi_prime_free(d); | ||
| return nullptr; | ||
| } | ||
| if (avcodec_open2(d->c, codec, nullptr) != 0) { | ||
| ffmpeg_vaapi_prime_free(d); | ||
| return nullptr; | ||
| } | ||
| return d; | ||
| } | ||
|
|
||
| extern "C" void ffmpeg_vaapi_prime_free(void *decoder) { | ||
| auto *d = (VaapiPrimeDec *)decoder; | ||
| if (!d) { | ||
| return; | ||
| } | ||
| close_mapped(d); | ||
| if (d->mapped) | ||
| av_frame_free(&d->mapped); | ||
| if (d->frame) | ||
| av_frame_free(&d->frame); | ||
| if (d->pkt) | ||
| av_packet_free(&d->pkt); | ||
| if (d->c) | ||
| avcodec_free_context(&d->c); | ||
| if (d->hw_device_ctx) | ||
| av_buffer_unref(&d->hw_device_ctx); | ||
| delete d; | ||
| } | ||
|
|
||
| extern "C" int ffmpeg_vaapi_prime_decode(void *decoder, const uint8_t *data, | ||
| int length, FFmpegPrimeFrame *out) { | ||
| auto *d = (VaapiPrimeDec *)decoder; | ||
| if (!d || !data || length <= 0 || !out) { | ||
| return 0; | ||
| } | ||
| d->pkt->data = (uint8_t *)data; | ||
| d->pkt->size = length; | ||
| int ret = avcodec_send_packet(d->c, d->pkt); | ||
| av_packet_unref(d->pkt); | ||
| if (ret < 0) { | ||
| return 0; | ||
| } | ||
| int got = 0; | ||
| while (ret >= 0) { | ||
| ret = avcodec_receive_frame(d->c, d->frame); | ||
| if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { | ||
| break; | ||
| } | ||
| if (ret < 0) { | ||
| break; | ||
| } | ||
| close_mapped(d); | ||
| d->mapped->format = AV_PIX_FMT_DRM_PRIME; | ||
| if (av_hwframe_map(d->mapped, d->frame, AV_HWFRAME_MAP_READ) < 0) { | ||
| av_frame_unref(d->frame); | ||
| continue; | ||
| } | ||
| if (fill_prime(d->mapped, out)) { | ||
| got = 1; | ||
|
Comment on lines
+176
to
+177
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Close descriptors before overwriting a successful output. If one packet produces multiple frames, each successful Close the current output descriptors before another 🤖 Prompt for AI AgentsSource: Learnings |
||
| } else { | ||
| close_mapped(d); | ||
| } | ||
| av_frame_unref(d->frame); | ||
| } | ||
| return got; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| use super::{ffmpeg_vaapi_prime_decode, ffmpeg_vaapi_prime_free, ffmpeg_vaapi_prime_new}; | ||
| use std::os::raw::{c_int, c_void}; | ||
|
|
||
| pub const GPU_FRAME_PRIME: i32 = 2; | ||
|
|
||
| #[repr(C)] | ||
| #[derive(Clone, Copy)] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift Remove shallow These traits copy descriptor integers without calling Make 🤖 Prompt for AI AgentsSource: Learnings |
||
| pub struct PrimeFrame { | ||
| pub kind: i32, | ||
| pub n_fds: i32, | ||
| pub fds: [i32; 4], | ||
| pub fourcc: u32, | ||
| pub modifier: u64, | ||
| pub width: i32, | ||
| pub height: i32, | ||
| pub n_planes: i32, | ||
| pub pitches: [i32; 4], | ||
| pub offsets: [i32; 4], | ||
| pub obj_indices: [i32; 4], | ||
| } | ||
|
|
||
| impl Default for PrimeFrame { | ||
| fn default() -> Self { | ||
| Self { | ||
| kind: GPU_FRAME_PRIME, | ||
| n_fds: 0, | ||
| fds: [-1; 4], | ||
| fourcc: 0, | ||
| modifier: 0, | ||
| width: 0, | ||
| height: 0, | ||
| n_planes: 0, | ||
| pitches: [0; 4], | ||
| offsets: [0; 4], | ||
| obj_indices: [0; 4], | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl PrimeFrame { | ||
| pub fn close_fds(&mut self) { | ||
| for i in 0..self.n_fds.max(0) as usize { | ||
| let fd = self.fds[i]; | ||
| if fd >= 0 { | ||
| unsafe { | ||
| libc::close(fd); | ||
| } | ||
| self.fds[i] = -1; | ||
| } | ||
| } | ||
| self.n_fds = 0; | ||
| } | ||
| } | ||
|
|
||
| mod libc { | ||
| extern "C" { | ||
| pub fn close(fd: i32) -> i32; | ||
| } | ||
| } | ||
|
|
||
| pub struct VaapiPrimeDecoder { | ||
| ctx: *mut c_void, | ||
| pub last: PrimeFrame, | ||
| } | ||
|
|
||
| unsafe impl Send for VaapiPrimeDecoder {} | ||
|
|
||
| impl VaapiPrimeDecoder { | ||
| pub fn new(hevc: bool) -> Result<Self, ()> { | ||
| let ctx = unsafe { ffmpeg_vaapi_prime_new(if hevc { 1 } else { 0 }) }; | ||
| if ctx.is_null() { | ||
| return Err(()); | ||
| } | ||
| Ok(Self { | ||
| ctx, | ||
| last: PrimeFrame::default(), | ||
| }) | ||
| } | ||
|
|
||
| pub fn decode(&mut self, data: &[u8]) -> Result<bool, ()> { | ||
| self.last.close_fds(); | ||
| let mut out = PrimeFrame::default(); | ||
| let ok = unsafe { | ||
| ffmpeg_vaapi_prime_decode( | ||
| self.ctx, | ||
| data.as_ptr(), | ||
| data.len() as c_int, | ||
| &mut out as *mut PrimeFrame as *mut _, | ||
| ) | ||
| }; | ||
| if ok == 1 { | ||
| self.last = out; | ||
| Ok(true) | ||
| } else { | ||
| Ok(false) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl Drop for VaapiPrimeDecoder { | ||
| fn drop(&mut self) { | ||
| self.last.close_fds(); | ||
| if !self.ctx.is_null() { | ||
| unsafe { ffmpeg_vaapi_prime_free(self.ctx) }; | ||
| self.ctx = std::ptr::null_mut(); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🔎 Supported by static analysis
🏁 Script executed:
Repository: rustdesk-org/hwcodec
Length of output: 4837
🏁 Script executed:
Repository: rustdesk-org/hwcodec
Length of output: 16475
🤖 get_repo_knowledge executed:
get_repo_knowledge rustdesk-org/hwcodec /tmp/coderabbit-repo-knowledge/rustdesk-org-hwcodec-85616907/architectureLength of output: 2521
🏁 Script executed:
Repository: rustdesk-org/hwcodec
Length of output: 42273
🏁 Script executed:
Repository: rustdesk-org/hwcodec
Length of output: 14807
Make VAAPI PRIME optional in Linux builds.
build_ffmpeg_ramruns for every build. On Linux, it always compilesffmpeg_vaapi_prime.cppand emits-lvaand-lva-drm. The Rustprimemodule is also enabled for every Linux build. A Linux build without the VAAPI development libraries therefore fails at link time, even when only normal RAM decoding is used.Add a dedicated Cargo feature for PRIME. Gate the PRIME source, link directives, and Rust module with that feature. Keep the existing non-PRIME RAM sources unconditional. The repository has no other direct
vaorva-drmlink directives, and its README documents VAAPI codec support but does not require PRIME for Linux builds.🤖 Prompt for AI Agents