rk3399视频显示_二

        本篇主要讲解怎么处理ffmpeg读取视频的packet包

视频显示的步骤,读流-解码-显示三部分。AVPacket我们并不能直接使用,需要进行解码后才可以。解码分为软解和硬解,前者兼容性强,但是耗cpu,后者性能高,有单独的硬件支持,不耗cpu性能,但专用性强。ffmpeg已经支持了市面上大多数的硬件解码,一般来说,我们直接调用ffmpeg自带的解码函数,如avcodec_decode_video2。在3399平台上亦是如此,ffmpeg也已经支持mpp的硬解,但是因为存在内部接口的封装等因素,效率并不是很高。所以,我们使用mpp原生的接口进行操作,解码速度相当快。

一、mpp解码

mpp模块封装了内部操作vpu的接口,简化了使用。mpp的使用方法与ffmpeg比较接近,学习起来也不会很复杂。mpp解码流程如下:

1、创建MppCtx以及MppApi

    ret = mpp_create(&m_ctx, &m_mpi);
    if (ret != MPP_OK)
    {
        errorf("mpp_create failed\n");
        destoryMpp();
        return false;
    }

mpp是可以多线程使用的,每个线程拥有自己独立的ctx及api

2、设置mpp解码模式

    MpiCmd mpi_cmd      = MPP_CMD_BASE;
    RK_U32 need_split   = 1;
    mpi_cmd = MPP_DEC_SET_PARSER_SPLIT_MODE;
    param = &need_split;
    ret = m_mpi->control(m_ctx, mpi_cmd, param);
    if (MPP_OK != ret)
    {
        errorf("m_mpi MPP_DEC_SET_PARSER_SPLIT_MODE  err\n");
        destoryMpp();
        return false;
    }

MPP_DEC_SET_PARSER_SPLIT_MODE,自动拼包模式,官方建议模式,该模式下会自动把读取的包拼接完整的帧进行解码。

 3、初始化mpp

    ret = mpp_init(m_ctx, MPP_CTX_DEC, MPP_VIDEO_CodingAVC);
    if (MPP_OK != ret)
    {
        errorf("mpp_init MPP_CTX_DEC  err\n");
        destoryMpp();
        return false;
    }

MPP_CTX_DEC 解码模式

MPP_CTX_ENC 编码模式

MPP_VIDEO_CodingAVC H264流类型

MPP_VIDEO_CodingHEVC H265/HEVC

更多类型见相关宏定义

至此mpp的初始化工作以及完成,接下来就可以进行解码操作了

在解码之前,先了解一下mpp包的基本概念

MppPacket 存储解码前的数据,与AVPacket意思一样,存储H264或者265的视频数据

MppFrame 存储解码后的数据,如YUV

解码的过程实际上,可以把mpp当成一个管道,一端输入packet,一端取frame。

mpp提供了塞数据取数据的接口,有同步异步之分,也有简易高阶接口之分,以适应不同用户群体。

我们选择简单的异步接口decode_put_packet/decode_get_frame

流程如下:

1、AVPacket转MppPacket

转换需要AVPacket中的data,size,pts,dts字段

    MPP_RET ret = MPP_OK;
    MppPacket packet;
    ret = mpp_packet_init(&packet, buffer, size);
    if (ret != MPP_OK)
    {
        mpp_packet_deinit(&packet);
        return false;
    }
    mpp_packet_set_pts(packet, pts);
    mpp_packet_set_dts(packet, dts);
    mpp_packet_set_length(packet, size + 5);

    if (NULL == buffer)
    {
        mpp_packet_set_eos(packet);
    }

2、喂packet

m_mpi->decode_put_packet(m_ctx, packet);

3、另一个线程获取frame

m_mpi->decode_get_frame(m_ctx, &frame);

以上为mpp解码全流程,各位可以根据实际情况组织代码,多路视频的情况下,要做好多线程并发处理。

二、rga处理

        rga是专门用于图形转换的硬件,用于对图片的格式转换,裁剪,缩小,旋转等操作。且速度极快,不占用cpu资源。

        下一部分会说到,因为我们适用的是qt播放视频,所以我们需要把mpp解码得到的yuv转成rgb再通过qt播放,同时如果配合npu计算棒计算AI的话,也需要将图像大小转成模型大小送入。rga适用起来也很简单,也是创建相应ctx,初始化,然后进行转换操作。实例流程代码如下:

1、初始化

    m_rga = RgaCreate();
    if(!m_rga)
    {
        errorf("rga create failed\n");
        return false;
    }
    m_rga->ops->initCtx(m_rga);

二、转换

    m_rga->ops->setSrcFormat(m_rga, m_mapType[rgaDataIn.type], rgaDataIn.width, rgaDataIn.height);
    m_rga->ops->setDstFormat(m_rga, m_mapType[rgaData.type], rgaData.width, rgaData.height);
    m_rga->ops->setSrcBufferPtr(m_rga, (unsigned char*)rgaDataIn.data);
    m_rga->ops->setDstBufferPtr(m_rga, (unsigned char*)m_mapData[url][i]);
    m_rga->ops->go(m_rga);

三、展示视频

        rga处理后,再将视频数据转成qt的pixmap即可展示,适用代码如下:

1、将裸数据封装成QPixmap

  int width = rgaData.width;
  int height = rgaData.height;
  /// 先转成img,RGB888的格式
  QImage img((uchar*)rgaData.data, width, height, QImage::Format_RGB888);
  /// 再转成pixmap
  QPixmap tmpMap = QPixmap::fromImage(img);
  /// 调用元对象函数,将显示丢给qt线程
  QMetaObject::invokeMethod(this, "slot_dispatchRealtimeVideo", Qt::QueuedConnection,
                            Q_ARG(std::string, input), Q_ARG(QPixmap, tmpMap));

2、label刷新pixmap即可显示视频

  label->setPixmap(pic);
  update();

这里有几点需要注意,不要生成pixmap后,直接调用label的setPixmap方法,qt以及一些ui框架,显示界面都是在自己的线程内完成,如果其它线程直接调用相应方法,会出现很多错误,比如花屏死机等。

另一个塞入qt线程,实例代码中是Qt::QueuedConnection,此时的接口是异步的,会将数据塞入队列中,qt内部会将队列中数据进行适当整合后在下次线程执行时,进行显示。此种方式,需要进行数据的缓存,否则可能在线程执行取队列数据时,数据已经失效了

还有一种方式是Qt::BlockingQueuedConnection,此时接口是同步的,一直阻塞到下次线程执行时取该数据展示为止。此方式不需要关注数据安全,不需要提前缓存,缺点是效率较低。当同时展示多路视频时,时间递增的

        看下显示效果吧

        

更多问题讨论或需求请联系微信号HardAndBetter,或者进qq群586166104讨论。 

你可能感兴趣的:(视频,ffmpeg,嵌入式,音视频,linux,c++)