RK3568笔记十二:Zlmedia拉流显示测试

若该文为原创文章,转载请注明原文出处。

Zlmediakit功能很强大,测试一下拉流,在通过解码显示。

一、环境

1、平台:rk3568

2、开发板:ATK-RK3568正点原子板子

3、环境:buildroot

测试的代码在GitHub - airockchip/rknpu2

main_video.cc主要功能是通过Zlmedia拉取RTSP流,并解码,然后重新编码保存成视频,所以直接在例子上修改程序,增加DRM显示。

二、编译

1、修改交叉工具链

修改build-linux_RK3566_RK3568.sh,

2、增加DRM显示程序

screen_test.cc

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <dlfcn.h>
#include <vector>
#include <string>

#include "rga_func.h"
#include "rknn_api.h"


#include "RgaUtils.h"
#include "im2d.h"
#include "opencv2/core/core.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <opencv2/opencv.hpp>
#include "rga.h"
#include <xf86drm.h>
#include <xf86drmMode.h>
#include "dev.h"
#include "bo.h"

#include "screen_test.h"



static sp_dev *mDev;
#define OUTPUT_DEVICE_LCD 1

static drmModeConnectorPtr lcdConnectorPtr = nullptr;
static struct sp_crtc *lcdCRPtr;
static drmModeEncoderPtr lcdEncoderPtr = nullptr;
static uint32_t lcdCrtcId = 0;
static drmModeModeInfoPtr lcdModInfoPtr;


// 内部使用的函数原型声明
static void get_connector(uint8_t outpuDevice);
static void get_encoder(uint8_t outpuDevice);
static void get_crtc(void);
static int init_screens();



static void get_connector(uint8_t outpuDevice)
{
    int i, j = 0;
    int ret = 0;

    printf("mDev->num_connectors = %d\n", mDev->num_connectors);
    for (j = 0; j < mDev->num_connectors; j++)
    {
        // name 是分辨率信息
        printf("connector name:%d\n", j);
        printf("connector_type:%d\n", j);
        printf("connector_type_id:%d\n", j);
        printf("connector status:%d\n", j);
        // 对应不同的输出设备, 指定不同的connector跟encoder
        if (outpuDevice == OUTPUT_DEVICE_LCD)
        {
            if (mDev->connectors[j]->connector_type == DRM_MODE_CONNECTOR_DSI &&
                mDev->connectors[j]->connection == DRM_MODE_CONNECTED)
            {
                lcdConnectorPtr = mDev->connectors[j];
            }
        }
       
    }
}

static void get_encoder(uint8_t outpuDevice)
{
    int i;
    for (i = 0; i < mDev->num_encoders; i++)
    {
        if (outpuDevice == OUTPUT_DEVICE_LCD)
        {
            if (mDev->encoders[i]->encoder_type == DRM_MODE_ENCODER_DSI)
            {
                lcdEncoderPtr = mDev->encoders[i];
                lcdCrtcId = lcdEncoderPtr->crtc_id;
            }
        }
       
    }
}

static void get_crtc(void)
{
    int j;

    printf("lcd crtc id:%d\n", lcdCrtcId);

    for (j = 0; j < mDev->num_crtcs; j++)
    {

        printf("encoderPtr->crtc_id:%d\n", mDev->crtcs[j].crtc->crtc_id);
        printf("mode_valid:%d\n", mDev->crtcs[j].crtc->mode_valid);
        printf("mode_name:%s\n", mDev->crtcs[j].crtc->mode.name);
        if (mDev->crtcs[j].crtc->crtc_id == lcdCrtcId && mDev->crtcs[j].crtc->mode_valid)
        {
            lcdCRPtr = &mDev->crtcs[j];
        }
       
    }
}


static int init_screens()
{
    int ret = 0;
    
    // 获取lcd connector
    get_connector(OUTPUT_DEVICE_LCD);

    if (!lcdConnectorPtr)
    {
        printf("failed to get hdmi connector or encoder.\n");
        return -1;
    }

    printf("lcd connector id:%d\n", lcdConnectorPtr->connector_id);

    // 获取lcd encoder
    get_encoder(OUTPUT_DEVICE_LCD);


    if (!lcdEncoderPtr)
    {
        printf("failed to get encoder.\n");
        return -2;
    }

    printf("lcd encoder id:%d\n", lcdEncoderPtr->encoder_id);

    // 获取一下显示分辨率之类
    lcdModInfoPtr = &lcdConnectorPtr->modes[0];

    // 把connector的encoder id赋值为encoder的id
    lcdConnectorPtr->encoder_id = lcdEncoderPtr->encoder_id;

    // 获取lcd crtc
    get_crtc();
    if (!lcdCRPtr)
    {
        printf("failed to get crtc.\n");
        return -3;
    }

    if (lcdCRPtr->scanout)
    {
        printf("crtc already in use\n");
        return -4;
    }

    printf("lcd crtc id:%d\n", lcdCRPtr->crtc->crtc_id);

    // allset
    // 获取bo, 只需要输入分辨率即可.
    lcdCRPtr->scanout = create_sp_bo(mDev, lcdModInfoPtr->hdisplay, lcdModInfoPtr->vdisplay, 24, 32, DRM_FORMAT_XRGB8888, 0);
    if (!lcdCRPtr->scanout)
    {
        printf("failed to create new scanout bo\n");
        return -5;
    }


    printf("fill test color\n");

    fill_bo(lcdCRPtr->scanout, 0xff, 0xff, 0x0, 0x0);

    ret = drmModeSetCrtc(mDev->fd, lcdEncoderPtr->crtc_id, lcdCRPtr->scanout->fb_id, 0, 0, &lcdConnectorPtr->connector_id, 1, lcdModInfoPtr);
    if (ret)
    {
        printf("failed to set crtc mode ret=%d\n", ret);
        return -6;
    }
    lcdCRPtr->crtc = drmModeGetCrtc(mDev->fd, lcdCRPtr->crtc->crtc_id);
    memcpy(&lcdCRPtr->crtc->mode, lcdModInfoPtr, sizeof(*lcdModInfoPtr));

    return 0;
}



int drm_dis_init(void)
{
	 int ret = 0;
    int i = 0;
    printf("create sp dev\n");
    // 创建显示设备
    mDev = create_sp_dev();
    if (!mDev)
    {
        printf("failed to exec create_sp_dev.\n");
        return -10;
    }

    printf("init_screen\n");

    // 初始化屏幕
    ret = init_screens();
    if (ret != 0)
    {
        printf("failed to exec initialize_screens.\n");
        return -11;
    }
    return 0;
}

void draw_lcd_screen_rgb_960(uint8_t *data, uint32_t dataSize)
{
    uint32_t colIdx = 0;
    uint32_t rowIdx = 0;
    uint8_t *dataPtr = data;
    for (rowIdx = 0; rowIdx < 1280; rowIdx++)
    {
        uint8_t *rowPtr = (uint8_t *)lcdCRPtr->scanout->map_addr + rowIdx * lcdCRPtr->scanout->pitch;
        for (colIdx = 0; colIdx < 720; colIdx++)
        {
            uint8_t *pixel = rowPtr + colIdx * 4;   // bgr
            #if 1
            pixel[0] = *dataPtr;         
            dataPtr++;
            pixel[1] = *dataPtr;
            dataPtr++;
            pixel[2] = *dataPtr;
            dataPtr++;
            pixel[3] = 0xff;
            #else  // bgra
            pixel[0] = 0xff;   // B
            dataPtr++;
            pixel[1] = 0x00;   //G 
            dataPtr++;
            pixel[2] = 0x00;   //R
            dataPtr++;
            pixel[3] = 0xFF;
            #endif
        }
    }
}



void draw_lcd_screen_rgb_dynamic(uint8_t *data, uint32_t dataSize, uint8_t screenNum, uint8_t rows, uint8_t cols)
{
    if (rows == 0 || cols == 0 || screenNum >= rows * cols)
    {
        return; // 避免除零错误和数组越界
    }

    uint32_t startRowIdx, startColIdx;
    uint32_t screenWidth = LCD_SCREEN_WIDTH / cols;
    uint32_t screenHeight = LCD_SCREEN_HEIGHT / rows;

    // 计算起始行和列索引
    startRowIdx = (screenNum / cols) * screenHeight; // 根据屏幕编号计算起始行索引
    startColIdx = (screenNum % cols) * screenWidth;  // 根据屏幕编号计算起始列索引

    uint32_t rowIdx, colIdx;
    uint8_t *dataPtr = data;
    for (rowIdx = startRowIdx; rowIdx < startRowIdx + screenHeight; rowIdx++)
    {
        uint8_t *rowPtr = (uint8_t *)lcdCRPtr->scanout->map_addr + rowIdx * lcdCRPtr->scanout->pitch;
        for (colIdx = startColIdx; colIdx < startColIdx + screenWidth; colIdx++)
        {
            uint8_t *pixel = rowPtr + colIdx * 4;
            memcpy(pixel, dataPtr, 4);
            dataPtr += 4;
        }
    }
}


void draw_lcd_screen_rgb_nine(uint8_t *data, uint32_t dataSize, uint8_t part)
{
    uint32_t startRowIdx, startColIdx;
    uint32_t partWidth = LCD_SCREEN_WIDTH / 3;
    uint32_t partHeight = LCD_SCREEN_HEIGHT / 3;

    // 计算起始行和列索引
    startRowIdx = (part / 3) * partHeight; // 根据部分的行来计算起始行索引
    startColIdx = (part % 3) * partWidth;  // 根据部分的列来计算起始列索引

    uint32_t rowIdx, colIdx;
    uint8_t *dataPtr = data;
    for (rowIdx = startRowIdx; rowIdx < startRowIdx + partHeight; rowIdx++)
    {
        uint8_t *rowPtr = (uint8_t *)lcdCRPtr->scanout->map_addr + rowIdx * lcdCRPtr->scanout->pitch;
        for (colIdx = startColIdx; colIdx < startColIdx + partWidth; colIdx++)
        {
            uint8_t *pixel = rowPtr + colIdx * 4;
            memcpy(pixel, dataPtr, 4);
            dataPtr += 4;
        }
    }
}


void draw_lcd_screen_rgb_quarter(uint8_t *data, uint32_t dataSize, uint8_t quarter)
{
    uint32_t startRowIdx, startColIdx;
    switch (quarter)
    {
    case 0: // 左上角
        startRowIdx = 0;
        startColIdx = 0;
        break;
    case 1: // 右上角
        startRowIdx = 0;
        startColIdx = LCD_SCREEN_WIDTH / 2;
        break;
    case 2: // 左下角
        startRowIdx = LCD_SCREEN_HEIGHT / 2;
        startColIdx = 0;
        break;
    case 3: // 右下角
        startRowIdx = LCD_SCREEN_HEIGHT / 2;
        startColIdx = LCD_SCREEN_WIDTH / 2;
        break;
    default: // 默认为左上角
        startRowIdx = 0;
        startColIdx = 0;
        break;
    }

    uint32_t rowIdx, colIdx;
    uint8_t *dataPtr = data;
    for (rowIdx = startRowIdx; rowIdx < startRowIdx + LCD_SCREEN_HEIGHT / 2; rowIdx++)
    {
        uint8_t *rowPtr = (uint8_t *)lcdCRPtr->scanout->map_addr + rowIdx * lcdCRPtr->scanout->pitch;
        for (colIdx = startColIdx; colIdx < startColIdx + LCD_SCREEN_WIDTH / 2; colIdx++)
        {
            uint8_t *pixel = rowPtr + colIdx * 4;
            memcpy(pixel, dataPtr, 4);
            dataPtr += 4;
        }
    }
}

void draw_lcd_screen_rgb(uint8_t *data, uint32_t dataSize)
{

    uint32_t colIdx = 0;
    uint32_t rowIdx = 0;
    uint8_t *dataPtr = data;
    for (rowIdx = 0; rowIdx < LCD_SCREEN_WIDTH; rowIdx++)
    {
        uint8_t *rowPtr = (uint8_t *)lcdCRPtr->scanout->map_addr + rowIdx * lcdCRPtr->scanout->pitch;
        for (colIdx = 0; colIdx < LCD_SCREEN_HEIGHT; colIdx++)
        {
            uint8_t *pixel = rowPtr + colIdx * 4;
            memcpy(pixel, dataPtr, 4);
            dataPtr += 4;
#if 0
            uint8_t *pixel = rowPtr + colIdx * 4;
            pixel[0] = *dataPtr;
            dataPtr++;
            pixel[1] = *dataPtr;
            dataPtr++;
            pixel[2] = *dataPtr;
            dataPtr++;
            pixel[3] = 0xff;
#endif
        }
    }
}

3、格式转换显示

使用的是正点原子的5.5寸屏,在调试过程中,一直卡在显示部分,后面才发现,正点原子使用的是竖屏,横屏显示会出现问题。

修改mpp_decoder_frame_callback函数,解码后的格式是420_SP,需要转成BGRA8888格式才能显示,使用RGA转换格式

memset(&src_rect, 0, sizeof(src_rect));
  memset(&dst_rect, 0, sizeof(dst_rect));
  memset(&src1, 0, sizeof(src1));
  memset(&dst1, 0, sizeof(dst1));
  printf("resize with RGA!\n");
  resize_buf = (unsigned char *)malloc(1280 * 720 * 3);
  memset(resize_buf, 0, 1280 * 720 * 3);
  printf("=========> width_stride: %d, height_stride: %d\n", width_stride, height_stride);
  src1 = wrapbuffer_virtualaddr((unsigned char *)mpp_frame_addr, width_stride, height_stride, RK_FORMAT_YCbCr_420_SP, width_stride, height_stride);
  dst1 = wrapbuffer_virtualaddr((unsigned char*)resize_buf, 720, 1280, RK_FORMAT_BGR_888);
  ret = imcheck(src1, dst1, src_rect, dst_rect);
  if (IM_STATUS_NOERROR != ret) {
    printf("%d, check error! %s", __LINE__, imStrError((IM_STATUS)ret));
    free(resize_buf);
    return ;
  }
  IM_STATUS STATUS = imresize(src1, dst1);
	
	
  //MySaveBmp("out.bmp", (unsigned char *)resize_buf, 1280, 720);
  // 显示在屏目上。
  printf("draw_lcd_screen_rgb_960==============>\n");
  draw_lcd_screen_rgb_960((uint8_t *)resize_buf, 1280 * 720 * 3);
  printf("draw_lcd_screen_rgb_960 ok\n");
  free(resize_buf);

需要注意 dst1 = wrapbuffer_virtualaddr((unsigned char*)resize_buf, 720, 1280, RK_FORMAT_BGR_888);,原本图片是1280*720,需要对调才能正常显示。

三、推流

先准备一个1280*720的视频,使用的是FFMPEG方式推流,但直接推流是无法拉流的,所以先启动一个RTSP服务器。

服务器下载地址Releases · aler9/rtsp-simple-server · GitHub

  • 启动rtsp-simple-server

下载完成后解压缩然后执行里面的rtsp-simple-server.exe

ffmpeg推流直接使用命令

ffmpeg -re -stream_loop -1 -i test.mp4 -c copy -f rtsp rtsp://192.168.0.107:8554/stream

四、程序解析

// Copyright (c) 2023 by Rockchip Electronics Co., Ltd. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*-------------------------------------------
                Includes
-------------------------------------------*/
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>

#include "im2d.h"
#include "rga.h"
#include "RgaUtils.h"

#include "rknn_api.h"
#include "postprocess.h"

#include "utils/mpp_decoder.h"
#include "utils/mpp_encoder.h"
#include "utils/drawing.h"
#include "im2d_buffer.h"
#include "screen_test.h"
#if defined(BUILD_VIDEO_RTSP)
#include "mk_mediakit.h"

#endif

#define OUT_VIDEO_PATH "out.h264"

#define LOG_TAG "MPP_API"

static FILE *g_save_nv12;    // nv12 

typedef struct {
  rknn_context rknn_ctx;
  rknn_input_output_num io_num;
  rknn_tensor_attr* input_attrs;
  rknn_tensor_attr* output_attrs;
  int model_channel;
  int model_width;
  int model_height;
  FILE* out_fp;
  MppDecoder* decoder;
  MppEncoder* encoder;
} rknn_app_context_t;

typedef struct {
  int width;
  int height;
  int width_stride;
  int height_stride;
  int format;
  char* virt_addr;
  int fd;
} image_frame_t;

/*-------------------------------------------
                  Functions
-------------------------------------------*/

static void dump_tensor_attr(rknn_tensor_attr* attr)
{
  printf("  index=%d, name=%s, n_dims=%d, dims=[%d, %d, %d, %d], n_elems=%d, size=%d, fmt=%s, type=%s, qnt_type=%s, "
         "zp=%d, scale=%f\n",
         attr->index, attr->name, attr->n_dims, attr->dims[0], attr->dims[1], attr->dims[2], attr->dims[3],
         attr->n_elems, attr->size, get_format_string(attr->fmt), get_type_string(attr->type),
         get_qnt_type_string(attr->qnt_type), attr->zp, attr->scale);
}

double __get_us(struct timeval t) { return (t.tv_sec * 1000000 + t.tv_usec); }

static unsigned char* load_data(FILE* fp, size_t ofst, size_t sz)
{
  unsigned char* data;
  int ret;

  data = NULL;

  if (NULL == fp) {
    return NULL;
  }

  ret = fseek(fp, ofst, SEEK_SET);
  if (ret != 0) {
    printf("blob seek failure.\n");
    return NULL;
  }

  data = (unsigned char*)malloc(sz);
  if (data == NULL) {
    printf("buffer malloc failure.\n");
    return NULL;
  }
  ret = fread(data, 1, sz, fp);
  return data;
}

static unsigned char* read_file_data(const char* filename, int* model_size)
{
  FILE* fp;
  unsigned char* data;

  fp = fopen(filename, "rb");
  if (NULL == fp) {
    printf("Open file %s failed.\n", filename);
    return NULL;
  }

  fseek(fp, 0, SEEK_END);
  int size = ftell(fp);

  data = load_data(fp, 0, size);

  fclose(fp);

  *model_size = size;
  return data;
}

static int write_data_to_file(const char *path, char *data, unsigned int size) {
  FILE *fp;

  fp = fopen(path, "w");
  if(fp == NULL) {
    printf("open error: %s", path);
    return -1;
  }

  fwrite(data, 1, size, fp);
  fflush(fp);

  fclose(fp);
  return 0;
}

static int init_model(const char* model_path, rknn_app_context_t* app_ctx) {
  int ret;
  rknn_context ctx;

  /* Create the neural network */
  printf("Loading mode...\n");
  int model_data_size = 0;
  unsigned char* model_data = read_file_data(model_path, &model_data_size);
  if (model_data == NULL) {
    return -1;
  }

  ret = rknn_init(&ctx, model_data, model_data_size, 0, NULL);
  if (ret < 0) {
    printf("rknn_init error ret=%d\n", ret);
    return -1;
  }

  if (model_data) {
    free(model_data);
  }

  rknn_sdk_version version;
  ret = rknn_query(ctx, RKNN_QUERY_SDK_VERSION, &version, sizeof(rknn_sdk_version));
  if (ret < 0) {
    printf("rknn_query RKNN_QUERY_SDK_VERSION error ret=%d\n", ret);
    return -1;
  }
  printf("sdk version: %s driver version: %s\n", version.api_version, version.drv_version);

  ret = rknn_query(ctx, RKNN_QUERY_IN_OUT_NUM, &app_ctx->io_num, sizeof(rknn_input_output_num));
  if (ret < 0) {
    printf("rknn_query RKNN_QUERY_IN_OUT_NUM error ret=%d\n", ret);
    return -1;
  }
  printf("model input num: %d, output num: %d\n", app_ctx->io_num.n_input, app_ctx->io_num.n_output);

  rknn_tensor_attr* input_attrs = (rknn_tensor_attr*)malloc(app_ctx->io_num.n_input * sizeof(rknn_tensor_attr));
  memset(input_attrs, 0, sizeof(input_attrs));
  for (int i = 0; i < app_ctx->io_num.n_input; i++) {
    input_attrs[i].index = i;
    ret = rknn_query(ctx, RKNN_QUERY_INPUT_ATTR, &(input_attrs[i]), sizeof(rknn_tensor_attr));
    if (ret < 0) {
      printf("rknn_query RKNN_QUERY_INPUT_ATTR error ret=%d\n", ret);
      return -1;
    }
    dump_tensor_attr(&(input_attrs[i]));
  }

  rknn_tensor_attr* output_attrs = (rknn_tensor_attr*)malloc(app_ctx->io_num.n_output * sizeof(rknn_tensor_attr));
  memset(output_attrs, 0, sizeof(output_attrs));
  for (int i = 0; i < app_ctx->io_num.n_output; i++) {
    output_attrs[i].index = i;
    ret = rknn_query(ctx, RKNN_QUERY_OUTPUT_ATTR, &(output_attrs[i]), sizeof(rknn_tensor_attr));
    if (ret < 0) {
      printf("rknn_query RKNN_QUERY_OUTPUT_ATTR error ret=%d\n", ret);
      return -1;
    }
    dump_tensor_attr(&(output_attrs[i]));
  }

  app_ctx->input_attrs = input_attrs;
  app_ctx->output_attrs = output_attrs;
  app_ctx->rknn_ctx = ctx;

  if (input_attrs[0].fmt == RKNN_TENSOR_NCHW) {
    printf("model is NCHW input fmt\n");
    app_ctx->model_channel = input_attrs[0].dims[1];
    app_ctx->model_height  = input_attrs[0].dims[2];
    app_ctx->model_width   = input_attrs[0].dims[3];
  } else {
    printf("model is NHWC input fmt\n");
    app_ctx->model_height  = input_attrs[0].dims[1];
    app_ctx->model_width   = input_attrs[0].dims[2];
    app_ctx->model_channel = input_attrs[0].dims[3];
  }
  printf("model input height=%d, width=%d, channel=%d\n", app_ctx->model_height, app_ctx->model_width, app_ctx->model_channel);

  return 0;
}

static int release_model(rknn_app_context_t* app_ctx) {
  if (app_ctx->rknn_ctx != NULL) {
    rknn_destroy(app_ctx->rknn_ctx);
  }
  free(app_ctx->input_attrs);
  free(app_ctx->output_attrs);
  deinitPostProcess();
  return 0;
}

static int inference_model(rknn_app_context_t* app_ctx, image_frame_t* img, detect_result_group_t* detect_result) {
  int ret;
  rknn_context ctx = app_ctx->rknn_ctx;
  int model_width = app_ctx->model_width;
  int model_height = app_ctx->model_height;
  int model_channel = app_ctx->model_channel;

  struct timeval start_time, stop_time;
  const float    nms_threshold      = NMS_THRESH;
  const float    box_conf_threshold = BOX_THRESH;
  // You may not need resize when src resulotion equals to dst resulotion
  void* resize_buf = nullptr;
  // init rga context
  rga_buffer_t src;
  rga_buffer_t dst;
  im_rect      src_rect;
  im_rect      dst_rect;
  memset(&src_rect, 0, sizeof(src_rect));
  memset(&dst_rect, 0, sizeof(dst_rect));
  memset(&src, 0, sizeof(src));
  memset(&dst, 0, sizeof(dst));

  printf("input image %dx%d stride %dx%d format=%d\n", img->width, img->height, img->width_stride, img->height_stride, img->format);

  float scale_w = (float)model_width / img->width;
  float scale_h = (float)model_height / img->height;

  rknn_input inputs[1];
  memset(inputs, 0, sizeof(inputs));
  inputs[0].index        = 0;
  inputs[0].type         = RKNN_TENSOR_UINT8;
  inputs[0].size         = model_width * model_height * model_channel;
  inputs[0].fmt          = RKNN_TENSOR_NHWC;
  inputs[0].pass_through = 0;

  printf("resize with RGA!\n");
  resize_buf = malloc(model_width * model_height * model_channel);
  memset(resize_buf, 0, model_width * model_height * model_channel);

  src = wrapbuffer_virtualaddr((void*)img->virt_addr, img->width, img->height, img->format, img->width_stride, img->height_stride);
  dst = wrapbuffer_virtualaddr((void*)resize_buf, model_width, model_height, RK_FORMAT_RGB_888);
  ret = imcheck(src, dst, src_rect, dst_rect);
  if (IM_STATUS_NOERROR != ret) {
    printf("%d, check error! %s", __LINE__, imStrError((IM_STATUS)ret));
    return -1;
  }
  IM_STATUS STATUS = imresize(src, dst);

  inputs[0].buf = resize_buf;

  gettimeofday(&start_time, NULL);
  rknn_inputs_set(ctx, app_ctx->io_num.n_input, inputs);

  rknn_output outputs[app_ctx->io_num.n_output];
  memset(outputs, 0, sizeof(outputs));
  for (int i = 0; i < app_ctx->io_num.n_output; i++) {
    outputs[i].want_float = 0;
  }

  ret = rknn_run(ctx, NULL);
  ret = rknn_outputs_get(ctx, app_ctx->io_num.n_output, outputs, NULL);
  gettimeofday(&stop_time, NULL);
  printf("once run use %f ms\n", (__get_us(stop_time) - __get_us(start_time)) / 1000);

  printf("post process config: box_conf_threshold = %.2f, nms_threshold = %.2f\n", box_conf_threshold, nms_threshold);

  std::vector<float> out_scales;
  std::vector<int32_t> out_zps;
  for (int i = 0; i < app_ctx->io_num.n_output; ++i) {
    out_scales.push_back(app_ctx->output_attrs[i].scale);
    out_zps.push_back(app_ctx->output_attrs[i].zp);
  }

  post_process((int8_t*)outputs[0].buf, (int8_t*)outputs[1].buf, (int8_t*)outputs[2].buf, model_height, model_width,
               box_conf_threshold, nms_threshold, scale_w, scale_h, out_zps, out_scales, detect_result);
  ret = rknn_outputs_release(ctx, app_ctx->io_num.n_output, outputs);

  if (resize_buf) {
    free(resize_buf);
  }
  return 0;
}


typedef struct                       /**** BMP file info structure ****/  
{  
    unsigned int   biSize;           /* Size of info header */  
    int            biWidth;          /* Width of image */  
    int            biHeight;         /* Height of image */  
    unsigned short biPlanes;         /* Number of color planes */  
    unsigned short biBitCount;       /* Number of bits per pixel */  
    unsigned int   biCompression;    /* Type of compression to use */  
    unsigned int   biSizeImage;      /* Size of image data */  
    int            biXPelsPerMeter;  /* X pixels per meter */  
    int            biYPelsPerMeter;  /* Y pixels per meter */  
    unsigned int   biClrUsed;        /* Number of colors used */  
    unsigned int   biClrImportant;   /* Number of important colors */  
} BITMAPINFOHEADER;

typedef struct                       /**** BMP file header structure ****/  
{  
    unsigned int   bfSize;           /* Size of file */  
    unsigned short bfReserved1;      /* Reserved */  
    unsigned short bfReserved2;      /* ... */  
    unsigned int   bfOffBits;        /* Offset to bitmap data */  
} BITMAPFILEHEADER; 


void MySaveBmp(const char *filename,unsigned char *rgbbuf,int width,int height)  
{  
    BITMAPFILEHEADER bfh;  
    BITMAPINFOHEADER bih;  
    /* 
     * Magic number for file. It does not fit in the header structure due to 
     * alignment requirements, so put it outside 
     * 文件的魔术字,由于对齐的需要,没办法将魔术字作为BITMAPFILEHEADER的成员,所以
     * 这里将魔术字放在BITMAPFILEHEADER开头外面的位置。
     */  
    unsigned short bfType=0x4d42;    //'BM'             
    bfh.bfReserved1 = 0;  
    bfh.bfReserved2 = 0;  
    bfh.bfSize = 2/* 2B魔术字 */+sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)+width*height*3;  
    bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);  
  
    bih.biSize = sizeof(BITMAPINFOHEADER);  
    bih.biWidth = width;  
    bih.biHeight = height;  
    bih.biPlanes = 1;  
    bih.biBitCount = 24;  
    bih.biCompression = 0;  
    bih.biSizeImage = 0;  
    bih.biXPelsPerMeter = 5000;  
    bih.biYPelsPerMeter = 5000;  
    bih.biClrUsed = 0;  
    bih.biClrImportant = 0;  
  
    FILE *file = fopen(filename, "wb");  
    if (!file)  
    {  
        printf("Could not write file\n");  
        return;  
    }  
  
    /*Write headers*/  
    fwrite(&bfType,sizeof(bfType),1,file);  
    fwrite(&bfh,sizeof(bfh),1, file);  
    fwrite(&bih,sizeof(bih),1, file);  
  
    fwrite(rgbbuf,width*height*3,1,file);  
    fclose(file);  
}

// MPP解码回调函数
void mpp_decoder_frame_callback(void* userdata, int width_stride, int height_stride, int width, int height, int format, int fd, void* data) {

  rknn_app_context_t* ctx = (rknn_app_context_t*)userdata;

  int ret = 0;
  static int frame_index = 0;
  frame_index++;

  void* mpp_frame = NULL;
  int mpp_frame_fd = 0;
  void* mpp_frame_addr = NULL;
  int mpp_frame_size;
  int enc_data_size;

  rga_buffer_t origin;
  rga_buffer_t src;

  rga_buffer_t src1;
  rga_buffer_t dst1;
  unsigned char *resize_buf = nullptr;

  im_rect      src_rect;
  im_rect      dst_rect;

  // 编码器初始化
  if (ctx->encoder == NULL) {
    MppEncoder* mpp_encoder = new MppEncoder();
    MppEncoderParams enc_params;
    memset(&enc_params, 0, sizeof(MppEncoderParams));
    enc_params.width = width;
    enc_params.height = height;
    enc_params.hor_stride = width_stride;
    enc_params.ver_stride = height_stride;
    enc_params.fmt = MPP_FMT_YUV420SP;
    //enc_params.type = MPP_VIDEO_CodingHEVC;
    //Note: rk3562只能支持h264格式的视频流
    enc_params.type = MPP_VIDEO_CodingAVC;
    mpp_encoder->Init(enc_params, NULL);

    ctx->encoder = mpp_encoder;
  }

  int enc_buf_size = ctx->encoder->GetFrameSize();
  char* enc_data = (char*)malloc(enc_buf_size);

  // 图片格式
  image_frame_t img;
  img.width = width;
  img.height = height;
  img.width_stride = width_stride;
  img.height_stride = height_stride;
  img.fd = fd;
  img.virt_addr = (char*)data;
  img.format = RK_FORMAT_YCbCr_420_SP;
  detect_result_group_t detect_result;
  memset(&detect_result, 0, sizeof(detect_result_group_t));
  // RKNN推理
  ret = inference_model(ctx, &img, &detect_result);
  if (ret != 0) 
  {
    printf("inference model fail\n");

    if(enc_data)
      free(enc_data);

    return ;
  }

  mpp_frame = ctx->encoder->GetInputFrameBuffer();
  mpp_frame_fd = ctx->encoder->GetInputFrameBufferFd(mpp_frame);
  mpp_frame_addr = ctx->encoder->GetInputFrameBufferAddr(mpp_frame);
  mpp_frame_size = ctx->encoder->GetFrameSize();
  // 图片格式转换
  // Copy To another buffer avoid to modify mpp decoder buffer
  printf("wrapbuffer_fd==> width: %d, height: %d\n", width, height);
  origin = wrapbuffer_fd(fd, width, height, RK_FORMAT_YCbCr_420_SP, width_stride, height_stride);
  src = wrapbuffer_fd(mpp_frame_fd, width, height, RK_FORMAT_YCbCr_420_SP, width_stride, height_stride);
  imcopy(origin, src);

  // Draw objects
  for (int i = 0; i < detect_result.count; i++) {
    detect_result_t* det_result = &(detect_result.results[i]);
    printf("%s @ (%d %d %d %d) %f\n", det_result->name, det_result->box.left, det_result->box.top,
           det_result->box.right, det_result->box.bottom, det_result->prop);
    int x1 = det_result->box.left;
    int y1 = det_result->box.top;
    int x2 = det_result->box.right;
    int y2 = det_result->box.bottom;
    // 画框
    draw_rectangle_yuv420sp((unsigned char*)mpp_frame_addr, width_stride, height_stride, x1, y1, x2-x1+1, y2-y1+1, 0x00FF0000, 4);
  }
  
  //  保存原始视频流
  #if 0
        if (g_save_nv12) {
          fwrite((unsigned char*)mpp_frame_addr, 1, mpp_frame_size, g_save_nv12);
          printf("#Save len-%d to %s\n", mpp_frame_size, g_save_nv12);
        }
  #endif

  // 格式转换,420_SP转成BGR888
  memset(&src_rect, 0, sizeof(src_rect));
  memset(&dst_rect, 0, sizeof(dst_rect));
  memset(&src1, 0, sizeof(src1));
  memset(&dst1, 0, sizeof(dst1));
  printf("resize with RGA!\n");
  resize_buf = (unsigned char *)malloc(1280 * 720 * 3);
  memset(resize_buf, 0, 1280 * 720 * 3);
  printf("=========> width_stride: %d, height_stride: %d\n", width_stride, height_stride);
  src1 = wrapbuffer_virtualaddr((unsigned char *)mpp_frame_addr, width_stride, height_stride, RK_FORMAT_YCbCr_420_SP, width_stride, height_stride);
  dst1 = wrapbuffer_virtualaddr((unsigned char*)resize_buf, 720, 1280, RK_FORMAT_BGR_888);
  ret = imcheck(src1, dst1, src_rect, dst_rect);
  if (IM_STATUS_NOERROR != ret) {
    printf("%d, check error! %s", __LINE__, imStrError((IM_STATUS)ret));
    free(resize_buf);
    return ;
  }
  IM_STATUS STATUS = imresize(src1, dst1);
	
	
  //MySaveBmp("out.bmp", (unsigned char *)resize_buf, 1280, 720);
  // 显示在屏目上。
  printf("draw_lcd_screen_rgb_960==============>\n");
  draw_lcd_screen_rgb_960((uint8_t *)resize_buf, 1280 * 720 * 3);
  printf("draw_lcd_screen_rgb_960 ok\n");
  free(resize_buf);
 
  printf("waite get char to next step!\n");
  //getchar();

  printf("====>Encode\n");
  // Encode to file
  // Write header on first frame
  if (frame_index == 1) {
    enc_data_size = ctx->encoder->GetHeader(enc_data, enc_buf_size);
    fwrite(enc_data, 1, enc_data_size, ctx->out_fp);
  }
  printf("====>GetHeader ok\n");
  memset(enc_data, 0, enc_buf_size);
  enc_data_size = ctx->encoder->Encode(mpp_frame, enc_data, enc_buf_size);
  printf("====>Encode ok\n");
  fwrite(enc_data, 1, enc_data_size, ctx->out_fp);

  if (enc_data != nullptr) 
    {
      free(enc_data);
    }
  printf("fwrite ok\n");



}

int process_video_file(rknn_app_context_t* ctx, const char* path)
{
  int video_size;
  char* video_data = (char*)read_file_data(path, &video_size);
  char* video_data_end = video_data + video_size;
  printf("read video size=%d\n", video_size);

  const int SIZE = 8192;
  char* video_data_ptr = video_data;

  do {
      int pkt_eos = 0;
      int size = SIZE;
      if (video_data_ptr + size >= video_data_end) {
          pkt_eos = 1;
          size = video_data_end - video_data_ptr;
      }

      ctx->decoder->Decode((uint8_t*)video_data_ptr, size, pkt_eos);

      video_data_ptr += size;

      if (video_data_ptr >= video_data_end) {
          printf("reset decoder\n");
          break;
      }

      // LOGD("video_data_ptr=%p video_data_end=%p", video_data_ptr, video_data_end);
      // usleep(10*1000);
  } while (1);

  return 0;
}

#if defined(BUILD_VIDEO_RTSP)
void API_CALL on_track_frame_out(void *user_data, mk_frame frame) {
  rknn_app_context_t *ctx = (rknn_app_context_t *) user_data;
  printf("on_track_frame_out ctx=%p\n", ctx);
  const char* data = mk_frame_get_data(frame);
  size_t size = mk_frame_get_data_size(frame);
  printf("decoder=%p\n", ctx->decoder);
  ctx->decoder->Decode((uint8_t*)data, size, 0);
}

void API_CALL on_mk_play_event_func(void *user_data, int err_code, const char *err_msg, mk_track tracks[],
                                    int track_count) {
  rknn_app_context_t *ctx = (rknn_app_context_t *) user_data;
  if (err_code == 0) {
      //success
      printf("play success!");
      int i;
      for (i = 0; i < track_count; ++i) {
          if (mk_track_is_video(tracks[i])) {
              log_info("got video track: %s", mk_track_codec_name(tracks[i]));
              //监听track数据回调
              mk_track_add_delegate(tracks[i], on_track_frame_out, user_data);
          }
      }
  } else {
      printf("play failed: %d %s", err_code, err_msg);
  }
}

void API_CALL on_mk_shutdown_func(void *user_data, int err_code, const char *err_msg, mk_track tracks[], int track_count) {
  printf("play interrupted: %d %s", err_code, err_msg);
}

// 下面为Zlmeidia拉流处理
int process_video_rtsp(rknn_app_context_t* ctx, const char* url)
{
  mk_config config;
  memset(&config, 0, sizeof(mk_config));
  config.log_mask = LOG_CONSOLE;
  mk_env_init(&config);
  mk_player player = mk_player_create();
  mk_player_set_on_result(player, on_mk_play_event_func, ctx);
  mk_player_set_on_shutdown(player, on_mk_shutdown_func, ctx);
  mk_player_play(player, url);

  printf("enter any key to exit\n");
  getchar();

  if (player) {
      mk_player_release(player);
  }
  return 0;
}
#endif

/*-------------------------------------------
                  Main Functions
-------------------------------------------*/
int main(int argc, char** argv)
{
  int status = 0;
  int ret;

  if (argc != 4) {
    printf("Usage: %s <rknn_model> <video_path> <video_type 264/265> \n", argv[0]);
    return -1;
  }

  char* model_name = (char*)argv[1];
  char* video_name = argv[2];
  int video_type = atoi(argv[3]);

  rknn_app_context_t app_ctx;
  memset(&app_ctx, 0, sizeof(rknn_app_context_t));

  ret = init_model(model_name, &app_ctx);
  if (ret != 0) {
    printf("init model fail\n");
    return -1;
  }

  // MPP解码器
  if (app_ctx.decoder == NULL) {
    MppDecoder* decoder = new MppDecoder();
    decoder->Init(video_type, 30, &app_ctx);
    decoder->SetCallback(mpp_decoder_frame_callback);
    app_ctx.decoder = decoder;
  }

  // 视频保存
  if (app_ctx.out_fp == NULL) {
    FILE* fp = fopen(OUT_VIDEO_PATH, "w");
    if(fp == NULL) {
        printf("open %s error\n", OUT_VIDEO_PATH);
        return -1;
    }
    app_ctx.out_fp = fp;
  }
  
  // NV12保存
  g_save_nv12 = fopen("output.nv12", "w");
  if (!g_save_nv12)
    printf("#VENC TEST:: Open ./output.nv12 failed!\n");

  printf("app_ctx=%p decoder=%p\n", &app_ctx, app_ctx.decoder);

  // 判断是不是RTSP流还是文件
  if (strncmp(video_name, "rtsp", 4) == 0) {
#if defined(BUILD_VIDEO_RTSP)
        // DRM初始化
        drm_dis_init();
        // 拉流处理
        process_video_rtsp(&app_ctx, video_name);
#else
        printf("rtsp no support\n");
#endif
  } else {
    // 文件流处理
    process_video_file(&app_ctx, video_name);
  }

  printf("waiting finish\n");
  usleep(3*1000*1000);

  // release
  fflush(app_ctx.out_fp);
  fclose(app_ctx.out_fp);

  if (app_ctx.decoder != nullptr) {
    delete(app_ctx.decoder);
    app_ctx.decoder = nullptr;
  }
  if (app_ctx.encoder != nullptr) {
    delete(app_ctx.encoder);
    app_ctx.encoder = nullptr;
  }

  release_model(&app_ctx);

  return 0;
}

通过Zlmedia很方便的拉取RTSP流,并解码显示。使用DRM是为了多路显示。OPENCV只会显示一路,不知道怎么拼接流显示。

如有侵权,或需要完整代码,请及时联系博主。

相关推荐

  1. RK3568笔记三:Zlmedia测试

    2024-02-08 15:58:01       33 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-08 15:58:01       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-08 15:58:01       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-08 15:58:01       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-08 15:58:01       20 阅读

热门阅读

  1. 【pytorch常用方法汇总】张量的转换、生成篇

    2024-02-08 15:58:01       27 阅读
  2. 寒假作业2月5号

    2024-02-08 15:58:01       21 阅读
  3. 堆排及时间复杂度分析

    2024-02-08 15:58:01       29 阅读
  4. 企业微信会话存档:大文件拉取、加密、上传

    2024-02-08 15:58:01       51 阅读
  5. 每天一个数据分析题(一百五十七)

    2024-02-08 15:58:01       35 阅读
  6. 【力扣 - 时间复杂度和空间复杂度】

    2024-02-08 15:58:01       34 阅读
  7. 线程之间如何通信?

    2024-02-08 15:58:01       33 阅读
  8. NMEA GPS

    2024-02-08 15:58:01       24 阅读
  9. 系统架构22 - 软件架构设计(1)

    2024-02-08 15:58:01       33 阅读