RK3588处理器平台使用MPP实现H264硬件编码

本文介绍了如何在MPP框架下,使用C++编程实现H.264编码,包括参数初始化、YUV420图像帧的处理以及编码成H264数据包的过程,展示了编码测试函数和关键步骤。
摘要由CSDN通过智能技术生成
#include <string.h>
#include <sys/time.h>

#include "utils.h"
#include "rk_mpi.h"
#include "mpp_env.h"
#include "mpp_mem.h"
#include "mpp_log.h"
#include "mpp_time.h"
#include "mpp_common.h"

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/opencv.hpp>

#include <signal.h>

using namespace std;
using namespace cv;

typedef struct
{
    MppCodingType   type;
    RK_U32          width;
    RK_U32          height;
    MppFrameFormat  format;

    RK_U32          num_frames;
} MpiEncTestCmd;

typedef struct
{
    //global flow control flag
    RK_U32 frm_eos;
    RK_U32 pkt_eos;
    RK_U32 frame_count;
    RK_U64 stream_size;

    //input ang output file
    FILE *fp_input;
    FILE *fp_output;

    //input and output
    MppBuffer frm_buf;
    MppEncSeiMode sei_mode;

    //base flow context
    MppCtx ctx;
    MppApi *mpi;
    MppEncPrepCfg prep_cfg;
    MppEncRcCfg rc_cfg;
    MppEncCodecCfg codec_cfg;

    //paramter for resource malloc
    RK_U32 width;
    RK_U32 height;
    RK_U32 hor_stride; //horizontal stride
    RK_U32 ver_stride; //vertical stride
    MppFrameFormat fmt;
    MppCodingType type;
    RK_U32 num_frames;

    //resources
    size_t frame_size;
    //NOTE: packet buffer may overflow
    size_t packet_size;

    //rate control runtime parameter
    RK_S32 gop;
    RK_S32 fps;
    RK_S32 bps;
} MpiEncTestData;
//------------------------------------------------------------------------------
MpiEncTestData encoder_params;
MpiEncTestData *encoder_params_ptr = &encoder_params;
//
MppApi *mpi;
MppCtx ctx;
//
bool first_frame_flg = true;
//------------------------------------------------------------------------------
//功能:MPP上下文初始化
//说明:根据MpiEncTestCmd参数设置MpiEncTestData参数
//------------------------------------------------------------------------------
MPP_RET test_ctx_init(MpiEncTestData **data, MpiEncTestCmd *cmd)
{
    MpiEncTestData *p = NULL;
    MPP_RET ret = MPP_OK;

    if (!data || !cmd)
    {
        mpp_err_f("invalid input data %p cmd %p\n", data, cmd);
        return MPP_ERR_NULL_PTR;
    }

    p = mpp_calloc(MpiEncTestData, 1);
    if (!p)
    {
        mpp_err_f("create MpiEncTestData failed\n");
        ret = MPP_ERR_MALLOC;
        goto RET;
    }

    //get paramter from cmd
    p->width        = cmd->width;
    p->height       = cmd->height;
    p->hor_stride   = MPP_ALIGN(cmd->width, 16);
    p->ver_stride   = MPP_ALIGN(cmd->height, 16);
    p->fmt          = cmd->format;
    p->type         = cmd->type;
    p->num_frames   = cmd->num_frames;
    p->frame_size   = p->hor_stride * p->ver_stride * 3 / 2;
    p->packet_size  = p->width * p->height;

RET:
    *data = p;
    return ret;
}
//------------------------------------------------------------------------------
//功能:设置MPP编码器参数
//说明:1-输入控制配置;2-码率控制配置;3-协议控制配置;4-SEI模式配置
//------------------------------------------------------------------------------
MPP_RET test_mpp_setup(MpiEncTestData *p)
{
    MPP_RET ret;
    MppApi *mpi;
    MppCtx ctx;
    MppEncCodecCfg *codec_cfg;
    MppEncPrepCfg *prep_cfg;
    MppEncRcCfg *rc_cfg;

    if (NULL == p)
    {
        return MPP_ERR_NULL_PTR;
    }

    mpi = p->mpi;
    ctx = p->ctx;
    codec_cfg = &p->codec_cfg;
    prep_cfg = &p->prep_cfg;
    rc_cfg = &p->rc_cfg;

    p->fps = 30;
    p->gop = 60;
    //p->bps = p->width * p->height / 5 * p->fps;
    p->bps = 4096*1024;

    //1--输入控制配置
    prep_cfg->change        = MPP_ENC_PREP_CFG_CHANGE_INPUT | MPP_ENC_PREP_CFG_CHANGE_ROTATION | MPP_ENC_PREP_CFG_CHANGE_FORMAT;
    prep_cfg->width         = p->width;
    prep_cfg->height        = p->height;
    prep_cfg->hor_stride    = p->hor_stride;
    prep_cfg->ver_stride    = p->ver_stride;
    prep_cfg->format        = p->fmt;
    prep_cfg->rotation      = MPP_ENC_ROT_0;
    ret = mpi->control(ctx, MPP_ENC_SET_PREP_CFG, prep_cfg);
    if (ret)
    {
        mpp_err("mpi control enc set prep cfg failed ret %d\n", ret);
        goto RET;
    }

    //2--码率控制配置
    rc_cfg->change  = MPP_ENC_RC_CFG_CHANGE_ALL;
    rc_cfg->rc_mode = MPP_ENC_RC_MODE_VBR;
    rc_cfg->quality = MPP_ENC_RC_QUALITY_CQP;

    if (rc_cfg->rc_mode == MPP_ENC_RC_MODE_CBR)
    {
        //constant bitrate has very small bps range of 1/16 bps
        rc_cfg->bps_target   = p->bps;
        rc_cfg->bps_max      = p->bps * 17 / 16;
        rc_cfg->bps_min      = p->bps * 15 / 16;
    }
    else if (rc_cfg->rc_mode ==  MPP_ENC_RC_MODE_VBR)
    {
        if (rc_cfg->quality == MPP_ENC_RC_QUALITY_CQP)
        {
            //constant QP does not have bps
            //rc_cfg->bps_target   = -1;
            //rc_cfg->bps_max      = -1;
            //rc_cfg->bps_min      = -1;
            rc_cfg->bps_target   = p->bps;
            rc_cfg->bps_max      = p->bps * 17 / 16;
            rc_cfg->bps_min      = p->bps * 1 / 16;
        }
        else
        {
            //variable bitrate has large bps range
            rc_cfg->bps_target   = p->bps;
            rc_cfg->bps_max      = p->bps * 17 / 16;
            rc_cfg->bps_min      = p->bps * 1 / 16;
        }
    }

    //fix input / output frame rate
    rc_cfg->fps_in_flex      = 0;
    rc_cfg->fps_in_num       = p->fps;
    rc_cfg->fps_in_denorm    = 1;
    rc_cfg->fps_out_flex     = 0;
    rc_cfg->fps_out_num      = p->fps;
    rc_cfg->fps_out_denorm   = 1;

    rc_cfg->gop              = p->gop;
    rc_cfg->skip_cnt         = 0;

    mpp_log("mpi_enc_test bps %d fps %d gop %d\n", rc_cfg->bps_target, rc_cfg->fps_out_num, rc_cfg->gop);
    ret = mpi->control(ctx, MPP_ENC_SET_RC_CFG, rc_cfg);
    if (ret)
    {
        mpp_err("mpi control enc set rc cfg failed ret %d\n", ret);
        goto RET;
    }

    //3--协议控制配置
    codec_cfg->coding = p->type;
    codec_cfg->h264.change = MPP_ENC_H264_CFG_CHANGE_PROFILE | MPP_ENC_H264_CFG_CHANGE_ENTROPY | MPP_ENC_H264_CFG_CHANGE_TRANS_8x8;

    //66  - Baseline profile
    //77  - Main profile
    //100 - High profile
    codec_cfg->h264.profile = 77;
    /*
    * H.264 level_idc parameter
    * 10 / 11 / 12 / 13    - qcif@15fps / cif@7.5fps / cif@15fps / cif@30fps
    * 20 / 21 / 22         - cif@30fps / half-D1@@25fps / D1@12.5fps
    * 30 / 31 / 32         - D1@25fps / 720p@30fps / 720p@60fps
    * 40 / 41 / 42         - 1080p@30fps / 1080p@30fps / 1080p@60fps
    * 50 / 51 / 52         - 4K@30fps
    */
    codec_cfg->h264.level    = 41;
    codec_cfg->h264.entropy_coding_mode  = 1;
    codec_cfg->h264.cabac_init_idc  = 0;
    //codec_cfg->h264.qp_min = 0;
    //codec_cfg->h264.qp_max = 50;
    //codec_cfg->h264.transform8x8_mode = 0;
    ret = mpi->control(ctx, MPP_ENC_SET_CODEC_CFG, codec_cfg);
    if (ret)
    {
        mpp_err("mpi control enc set codec cfg failed ret %d\n", ret);
        goto RET;
    }
    //4--SEI模式配置
    p->sei_mode = MPP_ENC_SEI_MODE_ONE_FRAME;
    ret = mpi->control(ctx, MPP_ENC_SET_SEI_CFG, &p->sei_mode);
    if (ret)
    {
        mpp_err("mpi control enc set sei cfg failed ret %d\n", ret);
        goto RET;
    }

RET:
    return ret;
}
//------------------------------------------------------------------------------
//功能:将YUV420视频帧数据填充到MPP buffer
//说明:使用16字节对齐,MPP可以实现零拷贝,提高效率
//------------------------------------------------------------------------------
void read_yuv_buffer(RK_U8 *buf, Mat &yuvImg, RK_U32 width, RK_U32 height)
{
    RK_U8 *buf_y = buf;
    RK_U8 *buf_u = buf + MPP_ALIGN(width, 16) * MPP_ALIGN(height, 16);
    RK_U8 *buf_v = buf_u + MPP_ALIGN(width, 16) * MPP_ALIGN(height, 16) / 4;
    //
    RK_U8 *yuvImg_y = yuvImg.data;
    RK_U8 *yuvImg_u = yuvImg_y + width * height;
    RK_U8 *yuvImg_v = yuvImg_u + width * height / 4;
    //
    memcpy(buf_y, yuvImg_y, width * height);
    memcpy(buf_u, yuvImg_u, width * height / 4);
    memcpy(buf_v, yuvImg_v, width * height / 4);
}

//------------------------------------------------------------------------------
//功能:MPP执行编码
//------------------------------------------------------------------------------
MPP_RET test_mpp_run_yuv(Mat yuvImg, MppApi *mpi, MppCtx &ctx, unsigned char * &H264_buf, int &length)
{
    MpiEncTestData *p = encoder_params_ptr;
    MPP_RET ret;

    MppFrame frame = NULL;
    MppPacket packet = NULL;
    void *buf = mpp_buffer_get_ptr(p->frm_buf);
    read_yuv_buffer((RK_U8*)buf, yuvImg, p->width, p->height);
    ret = mpp_frame_init(&frame);
    if (ret)
    {
        mpp_err_f("mpp_frame_init failed\n");
        goto RET;
    }
    //
    mpp_frame_set_width(frame, p->width);
    mpp_frame_set_height(frame, p->height);
    mpp_frame_set_hor_stride(frame, p->hor_stride);
    mpp_frame_set_ver_stride(frame, p->ver_stride);
    mpp_frame_set_fmt(frame, p->fmt);
    mpp_frame_set_buffer(frame, p->frm_buf);
    mpp_frame_set_eos(frame, p->frm_eos);

    ret = mpi->encode_put_frame(ctx, frame);
    if (ret)
    {
        mpp_err("mpp encode put frame failed\n");
        goto RET;
    }
    ret = mpi->encode_get_packet(ctx, &packet);
    if (ret)
    {
        mpp_err("mpp encode get packet failed\n");
        goto RET;
    }
    if (packet)
    {
        void *ptr   = mpp_packet_get_pos(packet);
        size_t len  = mpp_packet_get_length(packet);
        p->pkt_eos = mpp_packet_get_eos(packet);
        //
        H264_buf = new unsigned char[len];
        memcpy(H264_buf, ptr, len);
        length = len;
        mpp_packet_deinit(&packet);
        p->stream_size += len;
        p->frame_count++;
        if (p->pkt_eos)
        {
            mpp_log("found last packet\n");
            mpp_assert(p->frm_eos);
        }
    }
RET:
    return ret;
}

//------------------------------------------------------------------------------
//功能:初始化MPP编码器
//------------------------------------------------------------------------------
MpiEncTestData *test_mpp_run_yuv_init(MpiEncTestData *p, int width , int height, unsigned char * &SPS_buf, int &SPS_length)
{
    MPP_RET ret;
    //
    MpiEncTestCmd cmd;
    cmd.width = width;
    cmd.height = height;
    cmd.type = MPP_VIDEO_CodingAVC;
    cmd.format = MPP_FMT_YUV420P;
    cmd.num_frames = 0;
    ret = test_ctx_init(&p, &cmd);
    if (ret)
    {
        mpp_err_f("test data init failed ret %d\n", ret);
        goto MPP_TEST_OUT;
    }
    //
    mpp_log("p->frame_size = %d----------------\n", p->frame_size);
    ret = mpp_buffer_get(NULL, &p->frm_buf, p->frame_size);
    if (ret)
    {
        mpp_err_f("failed to get buffer for input frame ret %d\n", ret);
        goto MPP_TEST_OUT;
    }
    //
    mpp_log("mpi_enc_test encoder test start w %d h %d type %d\n",p->width, p->height, p->type);
    //encoder demo
    ret = mpp_create(&p->ctx, &p->mpi);
    if (ret)
    {
        mpp_err("mpp_create failed ret %d\n", ret);
        goto MPP_TEST_OUT;
    }
    //
    ret = mpp_init(p->ctx, MPP_CTX_ENC, p->type);
    if (ret)
    {
        mpp_err("mpp_init failed ret %d\n", ret);
        goto MPP_TEST_OUT;
    }
    //
    ret = test_mpp_setup(p);
    if (ret)
    {
        mpp_err_f("test mpp setup failed ret %d\n", ret);
        goto MPP_TEST_OUT;
    }

    mpi = p->mpi;
    ctx = p->ctx;
    //
    if (p->type == MPP_VIDEO_CodingHEVC)
    {
        MppPacket packet = NULL;
        ret = mpi->control(ctx, MPP_ENC_GET_EXTRA_INFO, &packet);
        if (ret)
        {
            mpp_err("mpi control enc get extra info failed\n");
        }
        //get and write sps/pps for H.264
        if (packet)
        {
            void *ptr    = mpp_packet_get_pos(packet);
            size_t len    = mpp_packet_get_length(packet);
            SPS_buf = new unsigned char[len];
            memcpy(SPS_buf, ptr, len);
            SPS_length = len;
            packet = NULL;
        }
    }
    return p;
MPP_TEST_OUT:
    return p;
}


//------------------------------------------------------------------------------
//功能:将YUV420格式图像帧编码为H264数据包
//------------------------------------------------------------------------------
void YuvtoH264(int width, int height, Mat yuv_frame, unsigned char* (&encode_buf), int &encode_length)
{
    unsigned char *H264_buf = NULL;
    int H264_buf_length = 0;
    unsigned char *SPS_buf = NULL;
    int SPS_length = 0;
    //
    if(first_frame_flg == true)
    {
        encoder_params_ptr = test_mpp_run_yuv_init(encoder_params_ptr, width, height, SPS_buf, SPS_length);
        //SPS数据,可以不使用
//        test_mpp_run_yuv(yuv_frame, mpi, ctx, H264_buf, length);
//        encode_buf = new unsigned char[SPS_length + length];
//        memcpy(encode_buf, SPS_buf, SPS_length);
//        memcpy(encode_buf + SPS_length, H264_buf, length);
//        encode_length = length + SPS_length;

        test_mpp_run_yuv(yuv_frame, mpi, ctx, H264_buf, H264_buf_length);
        encode_buf = new unsigned char[H264_buf_length];
        memcpy(encode_buf, H264_buf, H264_buf_length);
        encode_length = H264_buf_length;

        first_frame_flg = false;
        delete H264_buf;
        delete SPS_buf;
        printf("first_frame! \n");
        printf("SPS length: %d! \n", SPS_length);
        printf("frame length: %d! \n", H264_buf_length);
    }
    else
    {
        test_mpp_run_yuv(yuv_frame, mpi, ctx, H264_buf, H264_buf_length);
        encode_buf = new unsigned char[H264_buf_length];
        memcpy(encode_buf, H264_buf, H264_buf_length);
        encode_length = H264_buf_length;
        printf("frame length: %d! \n", H264_buf_length);
        delete H264_buf;
    }
}

int main(int argc, char **argv)
{
    signal(SIGPIPE, SIG_IGN);
    FILE* fp_output = NULL;
    //opencv读取视频帧
    cv::Mat frame;
    frame = cv::imread("../mpp_enc_test_rtsp_push/img_01.jpg");
    cv::resize(frame,frame,cv::Size(1920,1080));
    cv::cvtColor(frame, frame, cv::COLOR_BGR2YUV_I420);

    //创建Linux FIFO文件
    ::unlink("/home/monster/live/testProgs/test.264");
    ::mkfifo("/home/monster/live/testProgs/test.264", O_CREAT | O_EXCL | 777);
    fp_output = fopen("/home/monster/live/testProgs/test.264", "w+b");
    //创建本地文件
//    fp_output = fopen("test.264", "w+b");

    int i = 0;
    while(1)
    {
        i++;
//        if(i == 30 * 60)
//        {
//            break;
//        }
        printf("frame:%d\n", i);
        //在图像上绘制帧计数
        cv::Mat frame_yuv = frame.clone();
        std::string str = to_string(i);
        cv::putText(frame_yuv, str,cv::Point(100,100), cv::FONT_HERSHEY_PLAIN, 2.0f,cv::Scalar(0,0,255));
        //H264编码
        uchar *h264_buf = nullptr;
        int buf_len = 0;
        YuvtoH264(1920, 1080, frame_yuv, h264_buf, buf_len);
        fwrite(h264_buf, 1, buf_len, fp_output);
        delete h264_buf;
        h264_buf = nullptr;
    }
    fclose(fp_output);
}

  • 29
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值