RK3568具备强大的编解码能力,其编解码器最大支持 4096x2304@60fps 的解码和 1920x1080@100fps的编码能力。RK平台统一使用MPP库作为其编解码器的开发工具。因此,可以实现一次开发,多个平台之间互相移植。
如果使用RK Debian系统,则可以使用板上自带的编译环境来进行代码开发编译。也可以使用传统的交叉编译方式。下面分别介绍这两种方式。
1、下载源代码:在 YY3568 开发板的Debian10系统
输入如下命令
git clone https://github.com/rockchip-linux/mpp.git
2、安装编译工具:
sudo apt-get update
sudo apt-get install gcc g++ cmake -y
3、配置编译工具
cd mpp/build/linux/aarch64
vim arm.linux.cross.cmake
##################################################
将下面内容
SET(CMAKE_C_COMPILER "aarch64-linux-gnu-gcc")
SET(CMAKE_CXX_COMPILER "aarch64-linux-gnu-g++")
修改为
SET(CMAKE_C_COMPILER "gcc")
SET(CMAKE_CXX_COMPILER "g++")
##################################################
4、编译和安装
./make-Makefiles.bash
make
sudo make install
1、使用demo, 在 mpp/build/linux/aarch64/test
下有编译好的程序
cd test
./mpp_info_test
2、查看输出:新版mpp程序把打印接口改为系统log
,需要输入下面命令才能查看mpp输出
tail -10f /var/log/messages
如果有rk356x的完整版linux sdk,则mpp的源码位于external/mpp目录下,如果没有,则可以通过命令
git clone https://github.com/rockchip-linux/mpp.git
拉取一个下来,建议使用release分支。拉取之后进入此目录。下文所述的所有路径都是相对于这个mpp工程的相对路径。
目录介绍。其中头文件在inc目录下,包括这些
测试用的demo源码在test目录下
然后mpp目录就是用于编译出动态库的mpp源码。这个库一般会预置到文件系统里面。在YYT-3568的debian里面,可以在/lib里面找到这个库。
测试用的demo源码在test目录下
然后mpp目录就是用于编译出动态库的mpp源码。这个库一般会预置到文件系统里面。在YYT-3568的debian里面,可以在/lib里面找到这个库。
编译之后,动态库在build/linux/aarch64/mpp下
注意到其链接关系,最终使用的动态库是librockchip_mpp.so.1文件。
然后测试的demo编译出的可执行程序在build/linux/aarch64/test下。
下面使用demo中的mpi_enc_test进行演示,这个是用来编码的。所谓的编码,就是把原始的RAW图像数据,比如YUV422 YUV420 NV12 NV16这些数据格式的,进行压缩,压缩方式通常有H264 H265等,压缩之后体积减小,便于存放,传输。
使用如下命令可以抓取10帧摄像头数据(摄像头使用的是mipi-csi2,经过rkisp)。摄像头数据就是RAW数据,ISP使用的是NV16格式
v4l2-ctl -d /dev/video0 --set-fmt-video=width=1632,height=1224,pixelformat=NV12 --stream-mmap=3 --stream-skip=3 --stream-to=/tmp/cif.out --stream-count=10 --stream-poll
抓取之后使用ls -ll命令可以看到其大小。可以看出这种数据格式的图像非常占空间
然后使用如下命令进行编码
./mpi_enc_test -w 1632 -h 1224 -t 7 -i cif.out -o cif.h264 -n 10 -f 0
其中-w为宽度 -h为高 -t指定输出格式,7代表H264,-i -o分别是输入输出文件名,-n是帧数,-f指定输入格式,0代表YUV420SP, NV12
编码之后输出文件为cif.h264,可以看出其占用空间大幅度下降。
mpp的相关输出信息可以查看syslog信息
less /var/log/syslog
将cif.h264和cif.out文件复制到PC端的虚拟机ubuntu里面,然后使用命令
W=1632; H=1224; mplayer cif.out -loop 0 -demuxer rawvideo -fps 30 -rawvideo w=${W}:h=${H}:size=$((${W}*${H}*3/2)):format=NV12
可以播放cif.out
mplayer cif.h264 -loop 0
可以播放cif.h264
可以看出,虽然占用空间大幅度下降,其画面质量差不多。而这个就是使用板端编码器的意义。
下面以demo中的mpi_enc_test源码为例,介绍一下mpp动态库的使用方法。
此源文件位置在mpp工程的test目录下
首先看下main函数,它首先使用mpi_enc_test_cmd_update_by_args解析出了所有的参数,然后通过enc_test_multi函数执行编码
int main(int argc, char **argv)
{
RK_S32 ret = MPP_NOK;
MpiEncTestArgs* cmd = mpi_enc_test_cmd_get();
// parse the cmd option
ret = mpi_enc_test_cmd_update_by_args(cmd, argc, argv);
if (ret)
goto DONE;
mpi_enc_test_cmd_show_opt(cmd);
ret = enc_test_multi(cmd, argv[0]);
DONE:
mpi_enc_test_cmd_put(cmd);
return ret;
}
mpi_enc_test_cmd_update_by_args函数就是逐个参数解析,并检查参数,然后把不全的参数补齐。
其中
MPP_RET mpp_opt_parse(MppOpt opt, int argc, char **argv);
的作用就是把执行mpi_enc_test命令时候的参数解析出来,并放到MppOpt opt里面。
MPP_RET mpi_enc_test_cmd_update_by_args(MpiEncTestArgs* cmd, int argc, char **argv)
{
MppOpt opts = NULL;
RK_S32 ret = -1;
RK_U32 i;
...
/* should change node count when option increases */
mpp_opt_setup(opts, cmd, 67, enc_opt_cnt);
for (i = 0; i < enc_opt_cnt; i++)
mpp_opt_add(opts, &enc_opts[i]);
/* mark option end */
mpp_opt_add(opts, NULL);
ret = mpp_opt_parse(opts, argc, argv);
...
if (!cmd->hor_stride)
cmd->hor_stride = mpi_enc_width_default_stride(cmd->width, cmd->format);
if (!cmd->ver_stride)
cmd->ver_stride = cmd->height;
...
if (cmd->rc_mode == MPP_ENC_RC_MODE_FIXQP) {
if (!cmd->qp_init) {
if (cmd->type == MPP_VIDEO_CodingAVC ||
cmd->type == MPP_VIDEO_CodingHEVC)
cmd->qp_init = 26;
}
}
if (cmd->trace_fps) {
fps_calc_init(&cmd->fps);
mpp_assert(cmd->fps);
fps_calc_set_cb(cmd->fps, show_enc_fps);
}
...
return ret;
}
之后main函数调用
int enc_test_multi(MpiEncTestArgs* cmd, const char *name);
此函数创建了一个线程,并执行
void *enc_test(void *arg);
函数,此函数就是循环执行编码操作
void *enc_test(void *arg)
{
...
mpp_log_q(quiet, "%s start\n", info->name);
...
ret = test_ctx_init(info);
...
ret = mpp_buffer_group_get_internal(&p->buf_grp, MPP_BUFFER_TYPE_DRM);
...
ret = mpp_buffer_get(p->buf_grp, &p->frm_buf, p->frame_size + p->header_size);
...
ret = mpp_buffer_get(p->buf_grp, &p->pkt_buf, p->frame_size);
...
ret = mpp_buffer_get(p->buf_grp, &p->md_info, p->mdinfo_size);
...
// encoder demo
ret = mpp_create(&p->ctx, &p->mpi);
...
mpp_log_q(quiet, "%p encoder test start w %d h %d type %d\n",
p->ctx, p->width, p->height, p->type);
ret = p->mpi->control(p->ctx, MPP_SET_OUTPUT_TIMEOUT, &timeout);
...
ret = mpp_init(p->ctx, MPP_CTX_ENC, p->type);
...
ret = mpp_enc_cfg_init(&p->cfg);
...
ret = test_mpp_enc_cfg_setup(info);
...
t_s = mpp_time();
ret = test_mpp_run(info);
t_e = mpp_time();
ret = p->mpi->reset(p->ctx);
...
return NULL;
}
此函数先初始化一个buffer,然后将输入文件的info,data等信息放到这个buffer里面,再调用
mpp_create
实例化一个MppCtx
然后使用
mpp_init
初始化它,再使用
mpp_enc_cfg_init
设置一下,最后在
test_mpp_run
中调用
mpi->encode_put_frame(ctx, frame);
mpi->encode_get_packet(ctx, &packet);
将一个frame的原始数据发送给mpp,并从mpp获取一个packet的编码后数据
将多个packet循环执行此操作,即得到最终的编码后文件
具体的MPP 的API可以参考MPP说明文档,在docs/Linux/Multimedia/Rockchip_Developer_Guide_MPP_CN.pdf里面。