【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱: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,有兴趣的同学可以后面好好练习一下。