说来也奇怪,最近RK3588的用户扎堆,近期连续远程了七八个板子都是3588,有firefly的,有野火鲁班猫的,有正点原子的等,总体都大差不差,通过各种性能对比测试,鲁班猫性能更突出。在我们普通的PC机器电脑,播放一个8K,直接GPU干满,除非显卡强劲,普通的显卡一般就只能支撑1路265格式的8K播放,以为这就是极限了,没想到在小小的嵌入式板子上,播放8K也是这么的流畅,不愧是专有的RKMPP硬解方案,这还不是极限,开了4路8K,居然也行,绝对的震惊,颠覆了我的认知,这才几百的核心板,至于吗?这么凶残吗?
下面是一个使用纯 Qt 和 FFmpeg 实现 Rockchip MPP (RKMPP) 硬件解码的示例代码。这个示例展示了如何初始化 FFmpeg 并使用 RKMPP 进行硬件解码。
准备工作
- 确保已安装 FFmpeg 并启用了 RKMPP 支持
- 确保 Qt 开发环境已配置好
- 需要在 Rockchip 平台上运行(如 RK3399, RK3588 等)
完整代码实现
#include <QCoreApplication>
#include <QDebug>
#include <QImage>
#include <QFile>
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libavutil/hwcontext.h>
#include <libavutil/hwcontext_drm.h>
}
class RKMPPDecoder {
public:
RKMPPDecoder() : fmt_ctx(nullptr), video_dec_ctx(nullptr), hw_device_ctx(nullptr) {}
~RKMPPDecoder() { cleanup(); }
bool init(const QString &filePath) {
// 注册所有 FFmpeg 组件
avformat_network_init();
avdevice_register_all();
// 打开输入文件
if (avformat_open_input(&fmt_ctx, filePath.toUtf8().constData(), nullptr, nullptr) < 0) {
qWarning() << "Could not open source file" << filePath;
return false;
}
// 获取流信息
if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) {
qWarning() << "Could not find stream information";
return false;
}
// 查找视频流
int ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
if (ret < 0) {
qWarning() << "Could not find video stream in input file";
return false;
}
video_stream_idx = ret;
AVStream *video_stream = fmt_ctx->streams[video_stream_idx];
// 查找硬件解码器
const AVCodec *decoder = nullptr;
if (video_stream->codecpar->codec_id == AV_CODEC_ID_H264) {
decoder = avcodec_find_decoder_by_name("h264_rkmpp");
} else if (video_stream->codecpar->codec_id == AV_CODEC_ID_HEVC) {
decoder = avcodec_find_decoder_by_name("hevc_rkmpp");
} else if (video_stream->codecpar->codec_id == AV_CODEC_ID_VP9) {
decoder = avcodec_find_decoder_by_name("vp9_rkmpp");
}
if (!decoder) {
qWarning() << "Failed to find RKMPP decoder for codec" << avcodec_get_name(video_stream->codecpar->codec_id);
return false;
}
// 创建解码器上下文
video_dec_ctx = avcodec_alloc_context3(decoder);
if (!video_dec_ctx) {
qWarning() << "Failed to allocate decoder context";
return false;
}
// 复制流参数到解码器上下文
if (avcodec_parameters_to_context(video_dec_ctx, video_stream->codecpar) < 0) {
qWarning() << "Failed to copy codec parameters to decoder context";
return false;
}
// 设置硬件加速
if (initHardware() < 0) {
qWarning() << "Failed to initialize hardware decoder";
return false;
}
// 打开解码器
if (avcodec_open2(video_dec_ctx, decoder, nullptr) < 0) {
qWarning() << "Failed to open codec";
return false;
}
return true;
}
void decodeFrames() {
AVPacket *pkt = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
AVFrame *sw_frame = av_frame_alloc();
int ret = 0;
if (!pkt || !frame || !sw_frame) {
qWarning() << "Failed to allocate packet or frame";
goto end;
}
// 读取并解码帧
while (av_read_frame(fmt_ctx, pkt) >= 0) {
if (pkt->stream_index == video_stream_idx) {
ret = avcodec_send_packet(video_dec_ctx, pkt);
if (ret < 0) {
qWarning() << "Error sending packet to decoder";
continue;
}
while (ret >= 0) {
ret = avcodec_receive_frame(video_dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
qWarning() << "Error during decoding";
break;
}
// 处理硬件帧
if (frame->format == AV_PIX_FMT_DRM_PRIME) {
// 从硬件帧获取 DRM 描述符
AVDRMFrameDescriptor *drm_desc = (AVDRMFrameDescriptor *)frame->data[0];
// 这里可以添加处理 DRM 帧的代码
// 例如使用 Qt 的 EGL 或直接渲染到屏幕
qDebug() << "Got DRM PRIME frame, width:" << frame->width << "height:" << frame->height;
} else {
// 如果不是硬件帧,可能是软件回退
qDebug() << "Got software frame, format:" << frame->format;
}
av_frame_unref(frame);
}
}
av_packet_unref(pkt);
}
end:
av_packet_free(&pkt);
av_frame_free(&frame);
av_frame_free(&sw_frame);
}
private:
int initHardware() {
int ret = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_DRM, nullptr, nullptr, 0);
if (ret < 0) {
qWarning() << "Failed to create DRM HW device context";
return ret;
}
video_dec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
if (!video_dec_ctx->hw_device_ctx) {
qWarning() << "Failed to reference HW device context";
return -1;
}
// 设置像素格式为 DRM PRIME
video_dec_ctx->pix_fmt = AV_PIX_FMT_DRM_PRIME;
video_dec_ctx->get_format = get_hw_format;
return 0;
}
static enum AVPixelFormat get_hw_format(AVCodecContext *ctx, const enum AVPixelFormat *pix_fmts) {
const enum AVPixelFormat *p;
for (p = pix_fmts; *p != -1; p++) {
if (*p == AV_PIX_FMT_DRM_PRIME) {
return *p;
}
}
qWarning() << "Failed to get HW surface format";
return AV_PIX_FMT_NONE;
}
void cleanup() {
if (video_dec_ctx) {
avcodec_free_context(&video_dec_ctx);
video_dec_ctx = nullptr;
}
if (fmt_ctx) {
avformat_close_input(&fmt_ctx);
fmt_ctx = nullptr;
}
if (hw_device_ctx) {
av_buffer_unref(&hw_device_ctx);
hw_device_ctx = nullptr;
}
}
AVFormatContext *fmt_ctx;
AVCodecContext *video_dec_ctx;
AVBufferRef *hw_device_ctx;
int video_stream_idx;
};
int main(int argc, char *argv[]) {
QCoreApplication a(argc, argv);
if (argc < 2) {
qWarning() << "Usage: " << argv[0] << " <input file>";
return -1;
}
RKMPPDecoder decoder;
if (!decoder.init(argv[1])) {
return -1;
}
qDebug() << "Starting decoding...";
decoder.decodeFrames();
qDebug() << "Decoding finished";
return a.exec();
}
代码说明
-
初始化部分:
- 使用
avformat_open_input
打开输入文件 - 查找视频流并确定合适的 RKMPP 解码器 (h264_rkmpp, hevc_rkmpp 或 vp9_rkmpp)
- 初始化硬件解码上下文
- 使用
-
硬件加速设置:
- 使用
av_hwdevice_ctx_create
创建 DRM 硬件设备上下文 - 设置像素格式为
AV_PIX_FMT_DRM_PRIME
- 实现
get_hw_format
回调函数
- 使用
-
解码循环:
- 使用
av_read_frame
读取数据包 - 使用
avcodec_send_packet
和avcodec_receive_frame
进行解码 - 处理返回的 DRM PRIME 帧
- 使用
-
资源清理:
- 在析构函数中释放所有 FFmpeg 资源
编译说明
在 Rockchip 平台上编译此代码时,需要链接以下库:
- libavcodec
- libavformat
- libavutil
- libavdevice
典型的编译命令:
g++ -o rkmpp_decoder rkmpp_decoder.cpp \
-I/usr/include/qt \
-I/usr/include/qt/QtCore \
-lQt5Core \
-lavcodec -lavformat -lavutil -lavdevice
注意事项
- 此代码需要在 Rockchip 平台上运行,且系统已正确配置 RKMPP 驱动和 FFmpeg 支持
- 解码后的帧是 DRM PRIME 格式,需要额外的代码来渲染或转换为 Qt 可用的格式
- 实际应用中可能需要添加更多的错误处理和状态检查
如果需要将解码后的帧显示在 Qt 窗口中,可以考虑使用 Qt 的 EGL 或 OpenGL 集成来直接渲染 DRM PRIME 帧,或者将帧转换为 RGB 格式后使用 QImage/QPixmap 显示。