米尔电子
直播中

jf_99374259

2年用户 48经验值
擅长:处理器/DSP
私信 关注
[技术]

【米尔-全志T113-i开发板试用】JPG硬件编码的实现、YUV转换neon加速和对比测试

MYC-YT113i核心板及开发板

真正的国产核心板,100%国产物料认证

  • 国产T113-i处理器配备2*Cortex-A7@1.2GHz ,RISC-V
  • 外置DDR3接口、支持视频编解码器、HiFi4 DSP
  • 接口丰富:视频采集接口、显示器接口、USB2.0 接口、CAN 接口、千兆以太网接口
  • 工业级:-40℃~+85℃、尺寸37mm*39mm
  • 邮票孔+LGA,140+50PIN

全志 T113-i 硬件编码支持情况

  • JPEG/MJPEG up to 1080p@60fps
  • Supports input picture scaler up/down

没了!

是的,你没看错!

全志T113-i只支持jpg/mjpg编码,并不支持mpeg4/h263/h264/h265/av1这些

也行吧,我们还可以拿这个加速JPG图片编码

图片.png

开发环境配置

基础开发环境搭建参考上上一篇

https://bbs.elecfans.com/jishu_2408808_1_1.html

全志的视频引擎库环境搭建参考上一篇

https://bbs.elecfans.com/jishu_2412192_1_1.html

编码用的so库不同,额外从开发板上提取

adb pull /usr/lib/libvencoder.so
adb pull /usr/lib/libvenc_common.so
adb pull /usr/lib/libvenc_base.so
adb pull /usr/lib/libvenc_codec.so
adb pull /usr/lib/libvenc_jpeg.so
adb pull /usr/lib/libvenc_h264.so
adb pull /usr/lib/libvenc_h265.so

咦!似乎系统带了 h264/h264 的编码库,其实是不能用的,调用他们会报错

ERROR  : cedarc <H264EncFrame:2721>: h264 encoder wait interrupt overtime

也难怪,硬件上本来就不支持 h264/h265 编码,但凡有一点点支持,全志应该也会写在pdf里(×

pic dir

基于opencv-mobile的jpg软件编码

https://github.com/nihui/opencv-mobile

opencv-mobile 通过调整编译参数,删减部分opencv源码,来最小化编译的 opencv 库

提供了 opencv 常用的功能,如读写图片,处理,矩阵操作等等 版本与上游同步,无第三方依赖

在绝大多数情况下,以 1/10 的体积无痛替换官方 opencv,尤其适合对体积有特殊要求的移动端和嵌入式环境

图片.png

参考上上一篇的配置,我们编写基于opencv-mobile的jpg软件编码程序,读取图片,然后保存为新的jpg

cv::Mat bgr = cv::imread("in.jpg", 1);

// encode jpg
cv::imwrite("out0.jpg", bgr);

基于cedarc的jpg硬件编码

  1. 创建 videoencoder 初始化,设置 jpg quality 参数
  2. 分配缓存区,存放YUV420数据
  3. RGB转YUV420
  4. 开始编码,获取返回的 bitstream 数据
  5. 将 bitstream 数据写入 out.jpg

其中步骤 1,2,4 是基于cedarc库完成,剩余步骤是软件实现

cedarc库的使用方法参考

https://github.com/MYIR-ALLWINNER/framework/tree/develop-yt113-framework/libcedarc/demo/vencoderDemo

vdecoder其实也自带了方便函数 AWJpecEnc,本文的实现代码也主要参考这个,精简代码去除不必要的 memcpy

https://github.com/MYIR-ALLWINNER/framework/blob/develop-yt113-framework/libcedarc/vencoder/vencoder.c#L743

硬件编码核心关键代码

#include <vencoder.h>

// 步骤1
VideoEncoder* venc = VideoEncCreate(VENC_CODEC_JPEG);

VideoEncSetParameter(venc, VENC_IndexParamJpegQuality, (void*)&quality);

const int aligned_width = (width + 15) / 16 * 16;
const int aligned_height = (height + 15) / 16 * 16;

{
    VencBaseConfig config;
    memset(&config, 0, sizeof(config));
    config.nInputWidth = width;
    config.nInputHeight = height;
    config.nDstWidth = width;
    config.nDstHeight = height;
    config.nStride = aligned_width;
    config.eInputFormat = VENC_PIXEL_YUV420SP;

    VideoEncInit(venc, &config);
}

// 步骤2
{
    VencAllocateBufferParam bufferParam;
    bufferParam.nSizeY = aligned_width * aligned_height;
    bufferParam.nSizeC = aligned_width * aligned_height / 2;
    bufferParam.nBufferNum = 1;

    AllocInputBuffer(venc, &bufferParam);
}

VencInputBuffer input_buffer;
memset(&input_buffer, 0, sizeof(input_buffer));
GetOneAllocInputBuffer(venc, &input_buffer);

// 步骤3
unsigned char* yptr = (unsigned char*)input_buffer.pAddrVirY;
unsigned char* uvptr = (unsigned char*)input_buffer.pAddrVirC;

bgr2yuv420sp(bgr.data, width, height, yptr, uvptr, aligned_width);

FlushCacheAllocInputBuffer(venc, &input_buffer);

// 步骤4
AddOneInputBuffer(venc, &input_buffer);

VideoEncodeOneFrame(venc);

AlreadyUsedInputBuffer(venc, &input_buffer);

ReturnOneAllocInputBuffer(venc, &input_buffer);

VencOutputBuffer output_buffer;
GetOneBitstreamFrame(venc, &output_buffer);

// 步骤5
FILE* fp = fopen(path, "wb");

fwrite(output_buffer.pData0, 1, output_buffer.nSize0, fp);
if (output_buffer.nSize1)
{
    fwrite(output_buffer.pData1, 1, output_buffer.nSize1, fp);
}

fclose(fp);

RGB转YUV420的neon加速

RGB转YUV的计算公式为

y =  0.29900 * r + 0.58700 * g + 0.11400 * b
u = -0.16874 * r - 0.33126 * g + 0.50000 * b  + 128
v =  0.50000 * r - 0.41869 * g - 0.08131 * b  + 128

这不是标准的 BT601 系数,而是JPG图片专用的系数,t113-i上的g2d硬件目前还没途径实现这样的颜色转换,因此只能使用CPU软件实现

Cortex-A7的浮点性能不强,先量化为纯整数实现

y = (  38 * r +  75 * g +  15 * b + 64) >> 7
u = ((-43 * r -  84 * g + 127 * b + 128) >> 8) + 128
v = ((127 * r - 107 * g -  20 * b + 128) >> 8) + 128

基于新公式,加入ARM neon指令集加速

  • vmull.u8 vmlal.u8 vmlsl.u8 等指令可以一条指令完成 8 个uchar与 8 个uchar乘法/累加/累减
  • vld3.u8 指令可以一条指令完成 8 个RGB uchar加载和deinterleave
  • vqrshrun.s16 指令可以一条指令完成 8 个short右移 + rounding + minmax(x,0,255)
// 初始化系数
uint8x8_t _v38 = vdup_n_u8(38);
uint8x8_t _v75 = vdup_n_u8(75);
uint8x8_t _v15 = vdup_n_u8(15);
uint8x8_t _v127 = vdup_n_u8(127);
uint8x8_t _v84 = vdup_n_u8(84);
uint8x8_t _v107 = vdup_n_u8(107);
uint8x8_t _v43 = vdup_n_u8(43);
uint8x8_t _v20 = vdup_n_u8(20);
uint16x8_t _v128 = vdupq_n_u16((128 << 8) + 128);

const unsigned char* bgr_ptr;

// 加载 8个 RGB
uint8x8x3_t _bgr = vld3_u8(bgr_ptr);
uint8x8_t _b = _bgr.val[0];
uint8x8_t _g = _bgr.val[1];
uint8x8_t _r = _bgr.val[2];

// 计算 8个 Y
uint16x8_t _y = vmull_u8(_b, _v15);
_y = vmlal_u8(_y, _g, _v75);
_y = vmlal_u8(_y, _r, _v38);
uint8x8_t _y_u8 = vqrshrun_n_s16(vreinterpretq_s16_u16(_y), 7);

// 计算 8个 U
uint16x8_t _u = vmlal_u8(_v128, _b, _v127);
_u = vmlsl_u8(_u, _g, _v84);
_u = vmlsl_u8(_u, _r, _v43);
uint8x8_t _u_u8 = vqshrn_n_u16(_u, 8);

// 计算 8个 V
uint16x8_t _v = vmlal_u8(_v128, _r, _v127);
_v = vmlsl_u8(_v, _g, _v107);
_v = vmlsl_u8(_v, _b, _v20);
uint8x8_t _v_u8 = vqshrn_n_u16(_v, 8);

1080p图片编码性能测试

对1080p图片,分别使用 opencv-mobile 软编码和 cedarc 硬件编码库+neon加速做解码测试,循环调用记录最低耗时

可以看到米尔-全志T113-i开发板的jpg硬件编码能有效加速大约 12 倍!
图片.png

更多回帖

发帖
×
20
完善资料,
赚取积分