逆天Qt/在嵌入式板子上同时播放4路8K视频/硬解码GPU绘制/RK3588性能太凶残

说来也奇怪,最近RK3588的用户扎堆,近期连续远程了七八个板子都是3588,有firefly的,有野火鲁班猫的,有正点原子的等,总体都大差不差,通过各种性能对比测试,鲁班猫性能更突出。在我们普通的PC机器电脑,播放一个8K,直接GPU干满,除非显卡强劲,普通的显卡一般就只能支撑1路265格式的8K播放,以为这就是极限了,没想到在小小的嵌入式板子上,播放8K也是这么的流畅,不愧是专有的RKMPP硬解方案,这还不是极限,开了4路8K,居然也行,绝对的震惊,颠覆了我的认知,这才几百的核心板,至于吗?这么凶残吗?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

下面是一个使用纯 Qt 和 FFmpeg 实现 Rockchip MPP (RKMPP) 硬件解码的示例代码。这个示例展示了如何初始化 FFmpeg 并使用 RKMPP 进行硬件解码。

准备工作

  1. 确保已安装 FFmpeg 并启用了 RKMPP 支持
  2. 确保 Qt 开发环境已配置好
  3. 需要在 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();
}

代码说明

  1. 初始化部分:

    • 使用 avformat_open_input 打开输入文件
    • 查找视频流并确定合适的 RKMPP 解码器 (h264_rkmpp, hevc_rkmpp 或 vp9_rkmpp)
    • 初始化硬件解码上下文
  2. 硬件加速设置:

    • 使用 av_hwdevice_ctx_create 创建 DRM 硬件设备上下文
    • 设置像素格式为 AV_PIX_FMT_DRM_PRIME
    • 实现 get_hw_format 回调函数
  3. 解码循环:

    • 使用 av_read_frame 读取数据包
    • 使用 avcodec_send_packetavcodec_receive_frame 进行解码
    • 处理返回的 DRM PRIME 帧
  4. 资源清理:

    • 在析构函数中释放所有 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

注意事项

  1. 此代码需要在 Rockchip 平台上运行,且系统已正确配置 RKMPP 驱动和 FFmpeg 支持
  2. 解码后的帧是 DRM PRIME 格式,需要额外的代码来渲染或转换为 Qt 可用的格式
  3. 实际应用中可能需要添加更多的错误处理和状态检查

如果需要将解码后的帧显示在 Qt 窗口中,可以考虑使用 Qt 的 EGL 或 OpenGL 集成来直接渲染 DRM PRIME 帧,或者将帧转换为 RGB 格式后使用 QImage/QPixmap 显示。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值