前言
在学习ffmpeg 解码的时候, 经常需要用到av_image_fill_arrays这个函数, 其中有一个参数是const uint8_t *src
, 之后的逻辑中就没再用了, 我感到很疑惑, 学习了一下源码, 才恍然大悟, 特记录在此, 希望能帮到有同样疑惑的初学者。
逻辑上下文
- 申请了一段空间
- av_image_fill_arrays 调用。
- 申请了一个SwsContext
- 初始化SwsContext
- 给SwsContext赋值
- 把蓝色的frame(也就是ffmpeg得到的帧数据)变换
本文重点讲解第一步申请的空间哪去了
malloc 的空间哪去了?
malloc空间
/**
* Return the size in bytes of the amount of data required to store an
* image with the given parameters.
*
* @param pix_fmt the pixel format of the image
* @param width the width of the image in pixels
* @param height the height of the image in pixels
* @param align the assumed linesize alignment
* @return the buffer size in bytes, a negative error code in case of failure
*/
//int av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align);
outBuffer = static_cast<unsigned char *>(av_malloc(static_cast<size_t>(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, windowW, windowH, 1))));
通过av_malloc我们就得到了一个buffer.
瓜分分配的空间
av_image_fill_arrays
的作用简单一句话就是瓜分上一步分配到的buffer.
/**
* Setup the data pointers and linesizes based on the specified image
* parameters and the provided array.
*
* The fields of the given image are filled in by using the src
* address which points to the image data buffer. Depending on the
* specified pixel format, one or multiple image data pointers and
* line sizes will be set. If a planar format is specified, several
* pointers will be set pointing to the different picture planes and
* the line sizes of the different planes will be stored in the
* lines_sizes array. Call with src == NULL to get the required
* size for the src buffer.
*
* To allocate the buffer and fill in the dst_data and dst_linesize in
* one call, use av_image_alloc().
*
* @param dst_data data pointers to be filled in
* @param dst_linesize linesizes for the image in dst_data to be filled in
* @param src buffer which will contain or contains the actual image data, can be NULL
* @param pix_fmt the pixel format of the image
* @param width the width of the image in pixels
* @param height the height of the image in pixels
* @param align the value used in src for linesize alignment
* @return the size in bytes required for src, a negative error code
* in case of failure
*/
int av_image_fill_arrays(uint8_t *dst_data[4], int dst_linesize[4],
const uint8_t *src,
enum AVPixelFormat pix_fmt, int width, int height, int align);
av_image_fill_arrays
有好几个参数, 第一第二个参数就是一个frame对象里的两个成员,dst_data[4]是一个指针数组, 也就是一个字符矩阵。所以我们有必要先来了解一下frame以及它是怎么分配的。
typedef struct AVFrame {
#define AV_NUM_DATA_POINTERS 8
uint8_t *data[AV_NUM_DATA_POINTERS];
int linesize[AV_NUM_DATA_POINTERS];
......
}
AVFrame *av_frame_alloc(void)
{
AVFrame *frame = av_mallocz(sizeof(*frame));
if (!frame)
return NULL;
frame->extended_data = NULL;
get_frame_defaults(frame);
return frame;
}
void *av_mallocz(size_t size)
{
void *ptr = av_malloc(size);
if (ptr)
memset(ptr, 0, size);
return ptr;
}
分析上面的代码, 我们就发现了, 其实分配frame, 对data 只是分配了32个字节, 用来放指针的, 这样一堆破玩意儿怎么能保存像素数据呢, 是不是有点浅拷贝的意思。 可以想象一下, 应该是把src的真实的内存空间分配给data, 也就是说让data的指针指向 src, 这样就成了一个真的字符矩阵了。具体是怎么实现的呢
int av_image_fill_arrays(uint8_t *dst_data[4], int dst_linesize[4],
const uint8_t *src, enum AVPixelFormat pix_fmt,
int width, int height, int align)
{
int ret, i;
ret = av_image_check_size(width, height, 0, NULL);
if (ret < 0)
return ret;
ret = av_image_fill_linesizes(dst_linesize, pix_fmt, width);
if (ret < 0)
return ret;
for (i = 0; i < 4; i++)
dst_linesize[i] = FFALIGN(dst_linesize[i], align);
return av_image_fill_pointers(dst_data, pix_fmt, height, (uint8_t *)src, dst_linesize);
}
int av_image_fill_pointers(uint8_t *data[4], enum AVPixelFormat pix_fmt, int height,
uint8_t *ptr, const int linesizes[4])
{
int i, total_size, size[4] = { 0 }, has_plane[4] = { 0 };
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(pix_fmt);
memset(data , 0, sizeof(data[0])*4);
if (!desc || desc->flags & AV_PIX_FMT_FLAG_HWACCEL)
return AVERROR(EINVAL);
data[0] = ptr;
if (linesizes[0] > (INT_MAX - 1024) / height)
return AVERROR(EINVAL);
size[0] = linesizes[0] * height;
if (desc->flags & AV_PIX_FMT_FLAG_PAL ||
desc->flags & FF_PSEUDOPAL) {
data[1] = ptr + size[0]; /* palette is stored here as 256 32 bits words */
return size[0] + 256 * 4;
}
for (i = 0; i < 4; i++)
has_plane[desc->comp[i].plane] = 1;
total_size = size[0];
for (i = 1; i < 4 && has_plane[i]; i++) {
int h, s = (i == 1 || i == 2) ? desc->log2_chroma_h : 0;
data[i] = data[i-1] + size[i-1];
h = (height + (1 << s) - 1) >> s;
if (linesizes[i] > INT_MAX / h)
return AVERROR(EINVAL);
size[i] = h * linesizes[i];
if (total_size > INT_MAX - size[i])
return AVERROR(EINVAL);
total_size += size[i];
}
return total_size;
}
根据上面的分析就一目了然了, 最终来说是av_image_fill_pointers
来完成指针指向内存块的操作的。
总结
由av_image_fill_arrays
调用 av_image_fill_pointers
来实现了对分配内存的瓜分的。