跳到主要内容

3.4.3 JDK API 说明(含 Python 封装)


目录


数据类型定义(data_type)

定义 API 中使用的基本枚举和结构体

枚举类型: media_type

设备媒体类型枚举:

含义
MEDIA_TYPE_CANT_STAT无法获取设备状态
MEDIA_TYPE_UNKNOWN未知
MEDIA_TYPE_VIDEO视频
MEDIA_TYPE_VBIVBI(垂直消隐)
MEDIA_TYPE_RADIO广播
MEDIA_TYPE_SDRSDR(软件定义无线电)
MEDIA_TYPE_TOUCH触摸输入
MEDIA_TYPE_SUBDEV子设备
MEDIA_TYPE_DVB_FRONTEND数字电视前端
MEDIA_TYPE_DVB_DEMUX数字电视解复用
MEDIA_TYPE_DVB_DVR数字电视录像
MEDIA_TYPE_DVB_NET数字电视网络
MEDIA_TYPE_DTV_CA数字电视条件访问
MEDIA_TYPE_MEDIA媒体设备

枚举类型: codec_type

表示当前设备或上下文是否为编码或解码:

含义
NOT_CODEC非编解码
CODEC_DEC解码
CODEC_ENC编码

结构体:v4l2_ctx

V4L2 捕获及编码上下文结构体定义:

struct v4l2_ctx {
int fd; // 设备文件句柄
unsigned int width; // 视频宽度
unsigned int height; // 视频高度
unsigned int pixelformat; // 输入像素格式
unsigned int out_pixelformat; // 输出像素格式
int nplanes; // 输入平面数
int out_nplanes; // 输出平面数
struct buffer* cap_buffers; // 捕获缓冲区数组
struct buffer* out_buffers; // 输出缓冲区数组
__u32 bytesperline[VIDEO_MAX_PLANES]; // 各输入平面行字节数
__u32 out_bytesperline[VIDEO_MAX_PLANES]; // 各输出平面行字节数
FILE* file[2]; // 输入/输出文件指针
int verbose; // 日志详细等级
enum codec_type ctype; // 编码/解码类型
};

核心接口(C++)

处理多媒体的主要类:包括帧、摄像头、解码/编码器、视频输出和图像处理。

JdkFrame: 图像帧封装类

class JdkFrame {
public:
JdkFrame(int dma_fd_, size_t size_, int w, int h);
~JdkFrame();

// 将 DMA 缓冲区映射到 CPU 内存并返回指针
unsigned char* toHost() const;
// 克隆返回数据副本
std::vector<unsigned char> Clone() const;
// 保存为 NV12 格式 .yuv 文件
bool saveToFile(const std::string& filename) const;
// 从文件加载数据(与 saveToFile 配对使用)
bool loadFromFile(const std::string& filename, size_t expected_size);

// 获取底层 DMA FD
int getDMAFd() const;
// 获取缓冲区大小
size_t getSize() const { return size_; }
// 获取分辨率
int getWidth() const { return width_; }
int getHeight() const { return height_; }

// 将原始 NALU 数据拷贝到内部 buffer(如编码后写入)
// offset:目标缓冲区偏移
int MemCopy(const uint8_t* nalu, int nalu_size, int offset = 0);

private:
size_t size_; // 缓冲区总大小
int width_;
int height_;
JdkDma dma_; // DMA 同步辅助
std::shared_ptr<JdkDmaBuffer> data; // 底层 DMA buffer
};

using JdkFramePtr = std::shared_ptr<JdkFrame>;

JdkDmaJdkDmaBuffer

class JdkDmaBuffer {
public:
// 构造并分配 DMA 缓冲区
explicit JdkDmaBuffer(size_t size);
~JdkDmaBuffer();

// 返回映射后的用户空间地址
void* data() const;
// 整块填充值
void fill(uint8_t val);

// 获取物理地址(需先调用 map_phys_addr)
void map_phys_addr();

// 公开字段(只读)
size_t m_size;
uint64_t m_phys;
};

class JdkDma {
public:
// 通过 DMA 引擎异步复制数据
int Asyn(const JdkDmaBuffer& dst, const JdkDmaBuffer& src, size_t size);
// FD 间的 DMA 复制
int Asyn(const int& dst_fd, const int& src_fd, size_t size);
};

JdkCamera

class JdkCamera {
public:
/**
* 创建并打开 V4L2 设备
* @param device 设备路径 (e.g. "/dev/video0")
* @param width 期望采集宽度
* @param height 期望采集高度
* @param pixfmt V4L2 像素格式 (e.g. V4L2_PIX_FMT_NV12)
* @param req_count 请求的缓冲区数量 (默认 4)
* @return 成功返回 JdkCameraPtr,否则返回 nullptr
*/
static std::shared_ptr<JdkCamera> create(const std::string& device,
int width,
int height,
__u32 pixfmt,
int req_count = 4);
/** 获取一帧图像(阻塞) */
JdkFramePtr getFrame();

~JdkCamera();

private:
explicit JdkCamera(const std::string& device);
class Impl;
std::unique_ptr<Impl> impl_;
};
using JdkCameraPtr = std::shared_ptr<JdkCamera>;

JdkDecoder

class JdkDecoder {
public:
/**
* 初始化硬件解码器
* @param width 输出分辨率宽度
* @param height 输出分辨率高度
* @param payload 输入码流类型 (见 MppCodingType)
* @param Format 输出像素格式 (默认 NV12)
*/
JdkDecoder(int width, int height,
MppCodingType payload,
MppPixelFormat Format = PIXEL_FORMAT_NV12);
~JdkDecoder();

/** 解码,从已封装帧中解码 */
std::shared_ptr<JdkFrame> Decode(std::shared_ptr<JdkFrame> frame);
/** 解码,从裸 NALU 数据解码 */
std::shared_ptr<JdkFrame> Decode(const uint8_t* nalu, int nalu_size);

private:
int width_;
int height_;
MppCodingType payload_;
int format_;
int channel_id_;
MppVdecCtx* pVdecCtx = nullptr;
};

JdkEncoder

class JdkEncoder {
public:
/**
* 初始化硬件编码器
* @param width 输入分辨率宽度
* @param height 输入分辨率高度
* @param payload 输出码流类型 (见 MppCodingType)
* @param Format 输入像素格式 (默认 NV12)
*/
JdkEncoder(int width, int height,
MppCodingType payload,
MppPixelFormat Format = PIXEL_FORMAT_NV12);
~JdkEncoder();

/** 编码,将原始帧编码为压缩码流 */
std::shared_ptr<JdkFrame> Encode(std::shared_ptr<JdkFrame> frame);

private:
int width_;
int height_;
MppCodingType payload_;
int format_;
int encoder_id_ = 0;
MppVencCtx* pVencCtx = nullptr;
};

JdkDrm

/** 支持的像素格式 */
enum class PixelFmt : uint32_t {
NV12 = DRM_FORMAT_NV12
};

class JdkDrm {
public:
/**
* 打开 DRM 设备并初始化
* @param width 显示宽度
* @param height 显示高度
* @param stride 行跨度 (bytes)
* @param fmt 像素格式
* @param device DRM 设备路径 (默认 "/dev/dri/card0")
*/
JdkDrm(int width, int height, int stride,
PixelFmt fmt = PixelFmt::NV12,
const char* device = "/dev/dri/card0");
~JdkDrm();

/** 发送一帧到 DRM 屏幕 */
int sendFrame(std::shared_ptr<JdkFrame> frame);
/** 销毁指定的 framebuffer */
void destroyFb(uint32_t fb, uint32_t handle);
/** 打开 DRM 设备 */
int openCard(const char* dev);
/** 自动选取合适的 connector/crtc/plane */
int pickConnectorCrtcPlane();
/** 导入 DMA FD 为 DRM framebuffer */
int importFb(int dma_fd, uint32_t& fb_id, uint32_t& handle);

private:
struct LastFB {
uint32_t fb_id;
uint32_t handle;
int dma_fd;
} last_;
};

JdkV2D

/** 支持的目标像素格式(枚举值请参考完整头文件) */
enum V2DFormat {
// 例如: V2D_NV12, V2D_RGB888, ……
};

/** 矩形区域 */
struct V2DRect {
int x, y, width, height;
};

class JdkV2D {
public:
JdkV2D() = default;
~JdkV2D() = default;

/** 格式转换 */
JdkFramePtr convert_format(const JdkFramePtr& input,
V2DFormat out_format);
/** 缩放 */
JdkFramePtr resize(const JdkFramePtr& input,
int out_width, int out_height);
/** 同时缩放并格式转换 */
JdkFramePtr resize_and_convert(const JdkFramePtr& input,
int out_width, int out_height,
V2DFormat out_format);
/** 填充矩形区域 */
bool fill_rect(const JdkFramePtr& image,
const V2DRect& rect,
uint32_t rgba_color);
/** 绘制矩形边框 */
bool draw_rect(const JdkFramePtr& image,
const V2DRect& rect,
uint32_t rgba_color,
int thickness = 2);
/** 绘制多矩形 */
bool draw_rects(const JdkFramePtr& image,
const std::vector<V2DRect>& rects,
uint32_t rgba_color,
int thickness = 2);
/** 图像融合(bottom 上叠加 top) */
JdkFramePtr blend(const JdkFramePtr& bottom,
const JdkFramePtr& top);
};

JdkVo

class JdkVo {
public:
/**
* 初始化 Vo 输出
* @param width 输出宽度
* @param height 输出高度
* @param Format 像素格式 (默认 NV12)
*/
JdkVo(int width, int height,
MppPixelFormat Format = PIXEL_FORMAT_NV12);
~JdkVo();

/** 发送一帧到 Vo 硬件输出 */
int sendFrame(std::shared_ptr<JdkFrame> frame);

private:
int width_;
int height_;
MppPixelFormat format_;
int channel_id_;
MppVoCtx* pVoCtx = nullptr;
};

Python 绑定(pyjdk)

模块导入

#下载安装
wget https://gitlab.dc.com:8443/bianbu/bianbu-linux/jdk/-/blob/main/pyjdk/pyjdk-0.1.0-cp312-cp312-linux_riscv64.whl

pip install pyjdk-0.1.0-cp312-cp312-linux_riscv64.whl
#导入
import pyjdk as jdk

枚举(Enums)

  • jdk.PixelFormat: NV12, MJPEG, JPEG(对应 V4L2 FourCC)
  • jdk.CodingType: H264, H265, JPEG, MJPEG
  • jdk.MppPixelFormat: NV12, NV21
  • jdk.V2DFormat: 常用如 RGB888(其余以实际编译产物为准)
  • jdk.DrmPixelFormat: NV12

jdk.V2DRect

r = jdk.V2DRect(x, y, w, h)
# 也支持 (x,y,w,h) tuple / list / dict 传入到绘制接口

jdk.Dma

dma = jdk.Dma()
dma.asyn(dst_fd: int, src_fd: int, size: int) -> int # 异步 DMA 拷贝(包装 JdkDma::Asyn)

jdk.Frame(原 JdkFrame)

f = jdk.Frame(dma_fd: int, size: int, width: int, height: int)

# 只读属性
f.dma_fd: int
f.size: int
f.width: int
f.height: int

# I/O 与视图
f.save(path: str) -> bool # 保存底层缓冲(NV12/raw/比特流)
f.load_from_file(path: str, expected_size: int) -> bool
f.to_numpy_nv12(copy: bool = False) -> (y, uv) # 零拷贝/深拷贝到 numpy(NV12 两平面)
f.to_bytes() -> bytes # 直接导出底层缓冲
f.mem_copy(src: bytes|bytearray|memoryview, offset: int = 0) -> int

# 资源管理
f.release() # 立即归还底层缓冲(QBUF)
# 支持 with 语法,退出时自动 release()
with f:
y, uv = f.to_numpy_nv12()

说明:to_numpy_nv12(copy=False) 返回的 y/uv 是对底层缓冲的零拷贝视图,生命周期与 numpy 对象绑定;若需独立副本,请设 copy=True


采集相机(MIPI / USB)

jdk.MipiCam

cam = jdk.MipiCam.create(device: str, width: int, height: int,
fourcc: jdk.PixelFormat = jdk.PixelFormat.NV12,
req_count: int = 4) -> jdk.MipiCam
frame = cam.get_frame() # 阻塞取帧,返回 jdk.Frame

# 也支持 for f in cam: 迭代获取帧;支持 with cam: 进入/退出管理

jdk.UsbCam

uc = jdk.UsbCam.create("/dev/video20", 1280, 720, jdk.PixelFormat.MJPEG)
f = uc.get_frame() # 返回 MJPEG 比特流帧(可配合 Decoder 解码)

对应示例:mipi_cam.pyusb_cam.py


编码器 jdk.Encoder

enc = jdk.Encoder(width: int, height: int,
coding: jdk.CodingType = jdk.CodingType.H264,
pixfmt: jdk.MppPixelFormat = jdk.MppPixelFormat.NV12)

pkt = enc.encode(frame: jdk.Frame) -> jdk.Frame # 返回“码流帧”(bitstream in Frame)

注意:源码仅导出了 encode(...) 方法;若你本地脚本里使用了 encode_frame(...),请改为 encode(...)encode_h264.py 示例里出现的 encode_frame 为旧名,建议更新)。


解码器 jdk.Decoder

dec = jdk.Decoder(width: int, height: int,
coding: jdk.CodingType = jdk.CodingType.JPEG,
pixfmt: jdk.MppPixelFormat = jdk.MppPixelFormat.NV12)

# 1) 从“码流帧”(Frame)解码
yuv = dec.decode(bitstream_frame: jdk.Frame) -> jdk.Frame

# 2) 直接从 bytes-like(bytes/bytearray/memoryview)解码
yuv = dec.decode(bitstream: bytes|bytearray|memoryview) -> jdk.Frame

对应示例:decode_jpeg.pyencode_decode.py


图像处理 jdk.V2D

v2d = jdk.V2D()
out1 = v2d.convert_format(input: jdk.Frame, out_format: jdk.V2DFormat) -> jdk.Frame
out2 = v2d.resize(input: jdk.Frame, out_width: int, out_height: int) -> jdk.Frame
out3 = v2d.resize_and_convert(input: jdk.Frame, out_width: int, out_height: int,
out_format: jdk.V2DFormat) -> jdk.Frame

v2d.fill_rect(image: jdk.Frame, rect: jdk.V2DRect, rgba_color: int) -> bool
v2d.draw_rect(image: jdk.Frame, rect: jdk.V2DRect, rgba_color: int, thickness: int = 2) -> bool
v2d.draw_rects(image: jdk.Frame, rects: list[jdk.V2DRect], rgba_color: int, thickness: int = 2) -> bool

mixed = v2d.blend(bottom: jdk.Frame, top: jdk.Frame) -> jdk.Frame

对应示例:v2d_demo.pyrgba_color 使用 0xAARRGGBB


显示输出

jdk.Vo

vo = jdk.Vo(width: int, height: int, pixfmt: jdk.MppPixelFormat = jdk.MppPixelFormat.NV12)
vo.send_frame(frame: jdk.Frame) -> int

jdk.Drm

drm = jdk.Drm(width: int, height: int,
stride: int = 0,
pixfmt: jdk.DrmPixelFormat = jdk.DrmPixelFormat.NV12,
card: str = "/dev/dri/card0")

drm.send_frame(frame: jdk.Frame) -> int

对应示例:jdk_vo.pyjdk_drm.py


端到端示例(与源码一致)

MIPI 采集 → 编码 → 解码(参考 encode_decode.py

cam = jdk.MipiCam.create("/dev/video50", 1920, 1080, jdk.PixelFormat.NV12)
enc = jdk.Encoder(1920, 1080, jdk.CodingType.H264, jdk.MppPixelFormat.NV12)
dec = jdk.Decoder(1920, 1080, jdk.CodingType.H264, jdk.MppPixelFormat.NV12)

for _ in range(60):
f = cam.get_frame()
pkt = enc.encode(f)
yuv = dec.decode(pkt)

从 bytes 解码 JPEG/MJPEG(参考 decode_jpeg.py

bs_frame = jdk.Frame(-1, size, 1920, 1080)
bs_frame.load_from_file("examples/data/1920x1080.jpg", expected_size=size)
dec = jdk.Decoder(1920,1080, jdk.CodingType.MJPEG, jdk.MppPixelFormat.NV12)
yuv = dec.decode(bs_frame)

NV12 → RGB888 + 画框(参考 v2d_demo.py

f = jdk.Frame(-1, w*h*3//2, w, h)
f.load_from_file("frame_1920x1080_nv12.yuv", expected_size=w*h*3//2)

v2d = jdk.V2D()
rgb = v2d.convert_format(f, jdk.V2DFormat.RGB888)
v2d.draw_rects(f, [jdk.V2DRect(30,20,100,80)], 0xFFFFFF00, 4)

错误处理与性能要点

  • 所有阻塞/重算路径(取帧、编解码、V2D)在 C++ 侧已释放 GIL,利于 Python 多线程吞吐。
  • Frame.to_numpy_nv12(copy=False) 为零拷贝视图;需谨慎管理生命周期并在用完后 f.release() 或退出 with f:
  • 解码器支持 两种输入形态Framebytes-like,便于网络流/文件流适配。