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,有兴趣的同学可以后面好好练习一下。

相关推荐
大树8814 小时前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠14 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质14 小时前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush414 小时前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行52014 小时前
Linux 11 动态监控指令top
linux
小宇宙Zz14 小时前
Maven依赖冲突
java·服务器·maven
Inhand陈工15 小时前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智16 小时前
ARP代理--工作原理
运维·网络·arp·arp代理
不会C语言的男孩16 小时前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言
shushangyun_16 小时前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化