ffmpeg学习日记506-源码-av_image_copy()函数分析及功能

实现文件

av_image_copy()实现在libavutil/imgutils.c中

函数原型

void av_image_copy(uint8_t *dst_data[4], int dst_linesizes[4],
                   const uint8_t *src_data[4], const int src_linesizes[4],
                   enum AVPixelFormat pix_fmt, int width, int height)

函数功能猜测

头文件中的注释说是图像拷贝,理解就是将原图像复制一份,然后在ffmpeg提供的例程中,使用该函数上面有一句注释,翻译过来是:“复制解码帧到目标缓存区:这是必须的,因为rawvideo期望非对齐的数据”,由此可见,该函数不仅仅是单一的图像拷贝,其中一些细节需要明确,我才能知道他具体干了什么事情

函数使用

该函数在例程中使用如下,解码视频,解码到frame数据之后,调用了该函数,然后将dst_data的0通道数据保存,就是YUV数据了

示例代码如下:

static int decode_packet(AVCodecContext *dec, const AVPacket *pkt)
{
    int ret = 0;

    // submit the packet to the decoder
    ret = avcodec_send_packet(dec, pkt);
    if (ret < 0) {
        fprintf(stderr, "Error submitting a packet for decoding (%s)\n", av_err2str(ret));
        return ret;
    }

    // get all the available frames from the decoder
    while (ret >= 0) {
        ret = avcodec_receive_frame(dec, frame);
        if (ret < 0) {
            // those two return values are special and mean there is no output
            // frame available, but there were no errors during decoding
            if (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
                return 0;

            fprintf(stderr, "Error during decoding (%s)\n", av_err2str(ret));
            return ret;
        }

        // write the frame data to output file
        if (dec->codec->type == AVMEDIA_TYPE_VIDEO)
            ret = output_video_frame(frame);
        else
            ret = output_audio_frame(frame);

        av_frame_unref(frame);
        if (ret < 0)
            return ret;
    }

    return 0;
}
static int output_video_frame(AVFrame *frame)
{
    if (frame->width != width || frame->height != height ||
        frame->format != pix_fmt) {
        /* To handle this change, one could call av_image_alloc again and
         * decode the following frames into another rawvideo file. */
        fprintf(stderr, "Error: Width, height and pixel format have to be "
                "constant in a rawvideo file, but the width, height or "
                "pixel format of the input video changed:\n"
                "old: width = %d, height = %d, format = %s\n"
                "new: width = %d, height = %d, format = %s\n",
                width, height, av_get_pix_fmt_name(pix_fmt),
                frame->width, frame->height,
                av_get_pix_fmt_name(frame->format));
        return -1;
    }

    printf("video_frame n:%d coded_n:%d\n",
           video_frame_count++, frame->coded_picture_number);

    /* copy decoded frame to destination buffer:
     * this is required since rawvideo expects non aligned data */
    av_image_copy(video_dst_data, video_dst_linesize,
                  (const uint8_t **)(frame->data), frame->linesize,
                  pix_fmt, width, height);

    /* write to rawvideo file */
    fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file);
    return 0;
}

下面来看看该函数的源代码实现,理一下他干了什么事情

函数分析

函数定义

void av_image_copy(uint8_t *dst_data[4], int dst_linesizes[4],
                   const uint8_t *src_data[4], const int src_linesizes[4],
                   enum AVPixelFormat pix_fmt, int width, int height)
{
    ptrdiff_t dst_linesizes1[4], src_linesizes1[4];
    int i;

//for循环创建数据大小参数副本
    for (i = 0; i < 4; i++) {
        dst_linesizes1[i] = dst_linesizes[i];
        src_linesizes1[i] = src_linesizes[i];
    }

    image_copy(dst_data, dst_linesizes1, src_data, src_linesizes1, pix_fmt,
               width, height, image_copy_plane);
}

dst_data是在调用之前就已经申请好的dst_linesizes大小的内存,注意这里是数组,不是变量。

image_copy()函数实现:

static void image_copy(uint8_t *dst_data[4], const ptrdiff_t dst_linesizes[4],
                       const uint8_t *src_data[4], const ptrdiff_t src_linesizes[4],
                       enum AVPixelFormat pix_fmt, int width, int height,
                       void (*copy_plane)(uint8_t *, ptrdiff_t, const uint8_t *,
                                          ptrdiff_t, ptrdiff_t, int))
{
//得到对应格式的一些具体信息描述,包括名称,通道表示,原理矩阵等
    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);

    if (!desc || desc->flags & AV_PIX_FMT_FLAG_HWACCEL)
        return;
//AV_PIX_FMT_FLAG_PAL的注释翻译为:像素格式在数据[1]中有一个调色板,值是这个调色板中的索引。没看懂什么意思
    if (desc->flags & AV_PIX_FMT_FLAG_PAL) {
	//如果格式相同,将数据拷贝到dst_data[0]中,调色板信息放到dst_data[1]中
        copy_plane(dst_data[0], dst_linesizes[0],
                   src_data[0], src_linesizes[0],
                   width, height);
        /* copy the palette */
        if ((desc->flags & AV_PIX_FMT_FLAG_PAL) || (dst_data[1] && src_data[1]))
            memcpy(dst_data[1], src_data[1], 4*256);
    } else {
        int i, planes_nb = 0;
//nb_components字段的解释:每个像素拥有的组件数量,(1-4)
//所以这里查找最大组件数量,我理解的这里翻译过来的组件数量就是通道数量
        for (i = 0; i < desc->nb_components; i++)
            planes_nb = FFMAX(planes_nb, desc->comp[i].plane + 1);

        for (i = 0; i < planes_nb; i++) {
            int h = height;
			//获取行数
            ptrdiff_t bwidth = av_image_get_linesize(pix_fmt, width, i);
            if (bwidth < 0) {
                av_log(NULL, AV_LOG_ERROR, "av_image_get_linesize failed\n");
                return;
            }
            if (i == 1 || i == 2) {
                h = AV_CEIL_RSHIFT(height, desc->log2_chroma_h);
            }
			//拷贝数据
            copy_plane(dst_data[i], dst_linesizes[i],
                       src_data[i], src_linesizes[i],
                       bwidth, h);
        }
    }
}

可以看到在else逻辑里面,也没有什么特别的,所以我要继续深究一下其中函数内部

av_image_get_linesize()函数实现:

//传入参数是格式,宽度,通道数
int av_image_get_linesize(enum AVPixelFormat pix_fmt, int width, int plane)
{
    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
    int max_step     [4];       /* max pixel step for each plane */
    int max_step_comp[4];       /* the component for each plane which has the max pixel step */

    if (!desc || desc->flags & AV_PIX_FMT_FLAG_HWACCEL)
        return AVERROR(EINVAL);
//填充最大的xxx,我不知道他这里填充了什么,但是填充了
    av_image_fill_max_pixsteps(max_step, max_step_comp, desc);
//返回填充之后的行大小
    return image_get_linesize(width, plane, max_step[plane], max_step_comp[plane], desc);
}

image_get_linesize()函数实现:

static inline
int image_get_linesize(int width, int plane,
                       int max_step, int max_step_comp,
                       const AVPixFmtDescriptor *desc)
{
    int s, shifted_w, linesize;

    if (!desc)
        return AVERROR(EINVAL);

    if (width < 0)
        return AVERROR(EINVAL);
		//判定通道数
    s = (max_step_comp == 1 || max_step_comp == 2) ? desc->log2_chroma_w : 0;
	//根据通道数,重新赋值width
    shifted_w = ((width + (1 << s) - 1)) >> s;
    if (shifted_w && max_step > INT_MAX / shifted_w)
        return AVERROR(EINVAL);
		//重新计算行大小
    linesize = max_step * shifted_w;

    if (desc->flags & AV_PIX_FMT_FLAG_BITSTREAM)
        linesize = (linesize + 7) >> 3;
    return linesize;
}

由此可见,在获取行大小的时候,数据内容发生了改变,被填充了。

函数功能猜测总结

到此,虽然我还是不理解“复制解码帧到目标缓存区:这是必须的,因为rawvideo期望非对齐的数据”是什么意思,但是数据发生了改变,说明要想得到YUV数据,这一步是必须的。

参考

-av_image_copy函数原型的深入探秘

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐