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

相关推荐
Y_Hungry2 小时前
Linux 怎么使用局域网内电脑的网络访问外部
linux·运维·服务器
10000hours2 小时前
【C语言编译】编译原理和详细过程
linux·c语言·笔记
小小不董3 小时前
Oracle OCP认证考试考点详解083系列06
linux·数据库·oracle·dba
网硕互联的小客服3 小时前
如何解决服务器文件丢失或损坏的问题
运维·服务器
Shanxun Liao3 小时前
如何在 PowerEdge 服务器上设置 NIC 分组
运维·服务器
一道秘制的小菜4 小时前
AimRT从入门到精通 - 03Channel发布者和订阅者
linux·服务器·c++·vim·aimrt
开开心心就好4 小时前
提升办公效率的PDF转图片实用工具
运维·服务器·网络·python·智能手机·pdf·ocr
茅坑的小石头4 小时前
linux tar命令详解。压缩格式对比
linux·运维·服务器
球求了4 小时前
Linux 入门:操作系统&&进程详解
linux·运维·服务器·开发语言·学习
李匠20244 小时前
C++负载均衡远程调用学习之负载均衡算法与实现
运维·c++·学习·负载均衡