Linux上位机开发实践(一个硬件算法加速的示例)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

音视频编、解码是linux平台的一个刚需应用。默认linux上面音视频开发,通常都是使用ffmpeg开发的。不过,ffmpeg一般都是基于软件来进行编解码处理的。如果是嵌入式平台,需要转换成硬件加速来实现编解码,不然实时性上面是没有办法做到的。今天看一个rk平台下面的ffmpeg是如何实现硬件加速的。

1、代码地址

代码本身位于github上,链接如下所示,

复制代码
https://github.com/jjm2473/ffmpeg-rk

2、rk编解码的目录

作者把rk编、解码提炼到开源包的最外面了,单独有一个目录,即libavrkmpp就是rk平台下面的主要编解码目录。目录下面有两个主要的文件,rkmppenc.c和rkmppdec.c。很明显,前者是编码文件,后者是解码文件。

3、分析rkmppenc.c

rkmppenc.c下面有一个编码注册函数,即avrkmpp_init_encoder。通过这个注册函数,可以找到libavcodec/rkmppenc.c这个文件。可以通过RKMPP_ENC结构体发现,正是在这里完成了编码器的注册。结构体中除了avrkmpp_init_encoder之外,还有两个函数比较重要,avrkmpp_close_encoder和avrkmpp_encode_frame。我们看下avrkmpp_encode_frame,

复制代码
int avrkmpp_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
                              const AVFrame *frame, int *got_packet)
{
    int ret;
    MppFrame mpp_frame = NULL;

    ret = rkmpp_send_frame(avctx, frame, &mpp_frame);
    if (ret)
        return ret;

    ret = rkmpp_receive_packet(avctx, pkt, &mpp_frame);
    av_assert0(mpp_frame == NULL);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
        *got_packet = 0;
    } else if (ret) {
        return ret;
    } else {
        *got_packet = 1;
    }

    return 0;
}

和软编码一样,硬编码中最重要的也是送数据和拿数据,这里送数据就是rkmpp_send_frame函数,拿数据就是rkmpp_receive_packet函数。

复制代码
static int rkmpp_receive_packet(AVCodecContext *avctx, AVPacket *pkt,
                                MppFrame *mpp_frame)
{
    int ret;
    RKMPPEncodeContext *rk_context = avctx->priv_data;
    RKMPPEncoder *encoder = (RKMPPEncoder *)rk_context->encoder_ref->data;
    MppCtx ctx = encoder->ctx;
    MppApi *mpi = encoder->mpi;
    MppTask task = NULL;
    MppPacket packet = NULL;

    ret = mpi->poll(ctx, MPP_PORT_OUTPUT, MPP_POLL_BLOCK);
    if (ret) {
        av_log(avctx, AV_LOG_ERROR, "Failed to poll task output (ret = %d)\n", ret);
        ret = AVERROR_UNKNOWN;
        goto fail;
    }

    /* ...... */
}

这里送数据比较好理解,拿数据其实也不难,这里的拿数据,其实等待硬件加速完成。从代码当中,我们发现了poll。有过网络编程经验的同学,应该对poll不陌生。**这里的poll明显就是等待的过程。也就是说等到硬件处理好了,这里的poll就会返回。**剩下来的就是数据dequeue的过程。

4、继续分析rkmppdec.c

有了rkmppenc.c的经验,我们就会比较容易找到avrkmpp_init_decoder函数。借助于这个函数,也就找到了libavcodec/rkmppdec.c。这里的RKMPP_DEC就是解码注册的地方。除了avrkmpp_init_decoder之外,另外两个比较重要的就是avrkmpp_close_decoder和rkmpp_receive_frame。当然,这里解码还多一个avrkmpp_decoder_flush函数,有兴趣的同学可以看下。

复制代码
int avrkmpp_receive_frame(AVCodecContext *avctx, AVFrame *frame, 
    int (*ff_decode_get_packet)(AVCodecContext *, AVPacket *))
{
    RKMPPDecodeContext *rk_context = avctx->priv_data;
    RKMPPDecoder *decoder = (RKMPPDecoder *)rk_context->decoder_ref->data;
    AVPacket *packet = &decoder->packet;
    int ret;

    // no more frames after EOS
    if (decoder->eos)
        return AVERROR_EOF;

    // draining remain frames
    if (decoder->draining)
        return rkmpp_get_frame(avctx, frame, MPP_TIMEOUT_BLOCK);

    while (1) {
        if (!packet->size) {
            ret = ff_decode_get_packet(avctx, packet);
            if (ret == AVERROR_EOF) {
                av_log(avctx, AV_LOG_DEBUG, "End of stream.\n");
                // send EOS and start draining
                rkmpp_send_eos(avctx);
                return rkmpp_get_frame(avctx, frame, MPP_TIMEOUT_BLOCK);
            } else if (ret == AVERROR(EAGAIN)) {
                // not blocking so that we can feed new data ASAP
                return rkmpp_get_frame(avctx, frame, MPP_TIMEOUT_NON_BLOCK);
            } else if (ret < 0) {
                av_log(avctx, AV_LOG_ERROR, "Failed to get packet (code = %d)\n", ret);
                return ret;
            }
        } else {
            // send pending data to decoder
            ret = rkmpp_send_packet(avctx, packet);
            if (ret == AVERROR(EAGAIN)) {
                // some streams might need more packets to start returning frames
                ret = rkmpp_get_frame(avctx, frame, 5);
                if (ret != AVERROR(EAGAIN))
                    return ret;
            } else if (ret < 0) {
                av_log(avctx, AV_LOG_ERROR, "Failed to send data (code = %d)\n", ret);
                return ret;
            } else {
                av_packet_unref(packet);
                packet->size = 0;

                // blocked waiting for decode result
                if (decoder->sync)
                    return rkmpp_get_frame(avctx, frame, MPP_TIMEOUT_BLOCK);
            }
        }
    }
}

整个解码还是比较简单的,就是一个rkmpp_send_packet和rkmpp_get_frame的过程。当然,rkmpp_send_packet比较好理解,就是把数据送过去。我们看下rkmpp_get_frame,

复制代码
static int rkmpp_get_frame(AVCodecContext *avctx, AVFrame *frame, int timeout)
{
    RKMPPDecodeContext *rk_context = avctx->priv_data;
    RKMPPDecoder *decoder = (RKMPPDecoder *)rk_context->decoder_ref->data;
    RKMPPFrameContext *framecontext = NULL;
    AVBufferRef *framecontextref = NULL;
    int ret;
    MppFrame mppframe = NULL;
    MppBuffer buffer = NULL;
    int mode;

    // should not provide any frame after EOS
    if (decoder->eos)
        return AVERROR_EOF;

    if (decoder->mjpeg) {
        ret = rkmpp_get_frame_mjpeg(decoder, timeout==MPP_TIMEOUT_BLOCK?200:timeout, &mppframe);
    } else {
        decoder->mpi->control(decoder->ctx, MPP_SET_OUTPUT_TIMEOUT, (MppParam)&timeout);

        ret = decoder->mpi->decode_get_frame(decoder->ctx, &mppframe);
    }

    if (ret != MPP_OK && ret != MPP_ERR_TIMEOUT) {
        av_log(avctx, AV_LOG_ERROR, "Failed to get frame (code = %d)\n", ret);
        return AVERROR_UNKNOWN;
    }

    if (!mppframe) {
        if (timeout != MPP_TIMEOUT_NON_BLOCK)
            av_log(avctx, AV_LOG_DEBUG, "Timeout getting decoded frame.\n");
        return AVERROR(EAGAIN);
    }

    /*......*/

}

代码的结构还是比较清晰,这里最重要的地方,就是在rkmpp_get_frame里面继续调用了decode_get_frame函数。这个函数如果返回了,基本解码结束了。当然,也有可能出现了timeout问题。在解码之前,还会调用一下control函数,即设置一下超时限制。

5、rkmpp库

**不管是rkmppenc.c,还是rkmppdec.c,里面有很多mpp开头的函数,或者是结构体,这些都是和rk soc关联的内容,也是属于rk特有的内容。**实际使用的时候,厂家一般也是以h+lib的形式提供给客户的,驱动代码和实现代码,一般不会提供。

6、总结

isp、视频编解码、音频编解码、crc、dma,这几个部分都是常见加速的地方。很多功能厂家都会直接提供,大家没有必要自己去研究和开发,只需要在使用的时候,积极去调用即可。rk属于目前用的比较多的soc,有兴趣的同学可以后面好好练习一下。

相关推荐
爱知菜1 小时前
Windows安装Docker Desktop(WSL2模式)和Docker Pull网络问题解决
运维·docker·容器
做测试的小薄2 小时前
Nginx 命令大全:Linux 与 Windows 系统的全面解析
linux·自动化测试·windows·nginx·环境部署
影龙帝皖3 小时前
Linux网络之局域网yum仓库与apt的实现
linux·服务器·网络
月下雨(Moonlit Rain)3 小时前
Docker
运维·docker·容器
碎忆3 小时前
在VMware中安装虚拟机Ubuntu
linux·ubuntu
农民小飞侠3 小时前
ubuntu 安装pyllama教程
linux·python·ubuntu
打工人你好3 小时前
UNIX域套接字(Unix Domain Sockets, UDS) 的两种接口
服务器·unix
技术小甜甜4 小时前
[Dify] 使用 Docker 本地部署 Dify 并集成 Ollama 模型的详细指南
运维·docker·容器·dify
AI云师兄4 小时前
MCP 实战系列(Day 2)- 动手搓个文件系统 MCP 服务器
服务器·人工智能·ai编程
学习中的程序媛~4 小时前
主服务器和子服务器之间通过NFS实现文件夹共享
运维·服务器