ffmpeg学习日记506-源码-av_image_copy()函数分析及功能
·
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数据,这一步是必须的。
参考
更多推荐
所有评论(0)