av_pix_fmt_desc_get 函数是 FFmpeg 中用于获取像素格式描述信息的函数。它的作用是根据给定的像素格式(AVPixelFormat)返回对应的像素格式描述结构体(AVPixFmtDescriptor),该结构体包含了关于像素格式的详细信息,如分量数、每个分量的位深度、颜色空间等。
参数说明:
pix_fmt:要查询的像素格式,是一个枚举值,表示视频帧的像素格式,如 AV_PIX_FMT_YUV420P、AV_PIX_FMT_YUV422P 等。
返回值:
如果成功找到对应的像素格式描述结构体,则返回该结构体的指针;如果未找到,则返回 NULL。
static void draw_horiz_band(AVCodecContext *ctx, const AVFrame *fr, int offset[4],
int slice_position, int type, int height)
{
int i;
const AVPixFmtDescriptor *pix_fmt_desc;
int chroma_w, chroma_h;
int shift_slice_position;
int shift_height;
// 标记 draw_horiz_band 被调用
draw_horiz_band_called = 1;
// 获取像素格式描述信息
pix_fmt_desc = av_pix_fmt_desc_get(ctx->pix_fmt);
// 计算色度分量宽高
chroma_w = -((-ctx->width) >> pix_fmt_desc->log2_chroma_w);
chroma_h = -((-height) >> pix_fmt_desc->log2_chroma_h);
/*
ctx->width 表示视频帧的宽度,height 表示视频帧的高度,而 pix_fmt_desc->log2_chroma_w 和 pix_fmt_desc->log2_chroma_h 分别表示色度分量宽度和高度的对数值(即以2为底的对数)。
举个例子,假设视频帧的宽度 ctx->width 为 1920,高度 height 为 1080,而色度分量的采样率为 4:2:0(常见的视频编码格式之一),即色度分量的宽度和高度都是亮度分量的一半。
现在假设 pix_fmt_desc->log2_chroma_w 和 pix_fmt_desc->log2_chroma_h 都为 1(因为 2^1 = 2,所以色度分量的宽度和高度都是亮度分量的一半)。
根据这些假设,我们来看一下这两行代码的计算过程:
首先,将视频帧的宽度 ctx->width 取反,即 -1920。然后右移 pix_fmt_desc->log2_chroma_w 位,
即右移 1 位,得到 -960。再次取反,得到最终的 chroma_w 值为 960。
将视频帧的高度 height 取反,即 -1080。然后右移 pix_fmt_desc->log2_chroma_h 位,即右移 1 位,
得到 -540。再次取反,得到最终的 chroma_h 值为 540。
*/
// 计算偏移值
shift_slice_position = -((-slice_position) >> pix_fmt_desc->log2_chroma_h);
shift_height = -((-ctx->height) >> pix_fmt_desc->log2_chroma_h);
// 处理 Y 分量数据
for (i = 0; i < height; i++) {
// 拷贝 Y 分量数据到缓冲区
memcpy(slice_byte_buffer + ctx->width * slice_position + i * ctx->width,
fr->data[0] + offset[0] + i * fr->linesize[0], ctx->width);
}
// 处理 U 分量数据
for (i = 0; i < chroma_h; i++) {
// 拷贝 U 分量数据到缓冲区
memcpy(slice_byte_buffer + ctx->width * ctx->height + chroma_w * shift_slice_position + i * chroma_w,
fr->data[1] + offset[1] + i * fr->linesize[1], chroma_w);
}
// 处理 V 分量数据
for (i = 0; i < chroma_h; i++) {
// 拷贝 V 分量数据到缓冲区
memcpy(slice_byte_buffer + ctx->width * ctx->height + chroma_w * shift_height + chroma_w * shift_slice_position + i * chroma_w,
fr->data[2] + offset[2] + i * fr->linesize[2], chroma_w);
}
}
static int video_decode(const char *input_filename)
{
AVCodec *codec = NULL; // 视频编解码器
AVCodecContext *ctx = NULL; // 编解码器上下文
AVCodecParameters *origin_par = NULL; // 视频流参数
uint8_t *byte_buffer = NULL; // 存储解码后图像数据的缓冲区
AVFrame *fr = NULL; // 存储解码后图像帧的结构体
AVPacket pkt; // 存储视频数据的数据包
AVFormatContext *fmt_ctx = NULL; // 存储格式相关的上下文信息
int number_of_written_bytes; // 写入字节数
int video_stream; // 视频流索引
int got_frame = 0; // 是否解码到一帧图像
int byte_buffer_size; // 缓冲区大小
int result; // 操作结果
int end_of_stream = 0; // 是否到达视频流末尾
draw_horiz_band_called = 0; // 标志位,用于检查 draw_horiz_band 函数是否被调用过
// 打开输入文件并获取格式相关的上下文信息
result = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL);
if (result < 0) {
av_log(NULL, AV_LOG_ERROR, "Can't open file\n");
return result;
}
// 获取流信息
result = avformat_find_stream_info(fmt_ctx, NULL);
if (result < 0) {
av_log(NULL, AV_LOG_ERROR, "Can't get stream info\n");
return result;
}
// 找到视频流
video_stream = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (video_stream < 0) {
av_log(NULL, AV_LOG_ERROR, "Can't find video stream in input file\n");
return -1;
}
// 获取视频流的参数
origin_par = fmt_ctx->streams[video_stream]->codecpar;
// 查找视频编解码器
codec = avcodec_find_decoder(origin_par->codec_id);
if (!codec) {
av_log(NULL, AV_LOG_ERROR, "Can't find decoder\n");
return -1;
}
// 分配编解码器上下文
ctx = avcodec_alloc_context3(codec);
if (!ctx) {
av_log(NULL, AV_LOG_ERROR, "Can't allocate decoder context\n");
return AVERROR(ENOMEM);
}
// 将流参数拷贝到编解码器上下文
result = avcodec_parameters_to_context(ctx, origin_par);
if (result) {
av_log(NULL, AV_LOG_ERROR, "Can't copy decoder context\n");
return result;
}
// 设置 draw_horiz_band 函数
ctx->draw_horiz_band = draw_horiz_band;
ctx->thread_count = 1; // 设置线程数量为 1
// 打开编解码器
result = avcodec_open2(ctx, codec, NULL);
if (result < 0) {
av_log(ctx, AV_LOG_ERROR, "Can't open decoder\n");
return result;
}
// 分配图像帧结构体
fr = av_frame_alloc();
if (!fr) {
av_log(NULL, AV_LOG_ERROR, "Can't allocate frame\n");
return AVERROR(ENOMEM);
}
// 分配图像数据缓冲区
byte_buffer_size = av_image_get_buffer_size(ctx->pix_fmt, ctx->width, ctx->height, 32);
byte_buffer = av_malloc(byte_buffer_size);
if (!byte_buffer) {
av_log(NULL, AV_LOG_ERROR, "Can't allocate buffer\n");
return AVERROR(ENOMEM);
}
// 分配 slice_byte_buffer 存储 slice 数据
slice_byte_buffer = av_malloc(byte_buffer_size);
if (!slice_byte_buffer) {
av_log(NULL, AV_LOG_ERROR, "Can't allocate buffer\n");
return AVERROR(ENOMEM);
}
memset(slice_byte_buffer, 0, byte_buffer_size);
slice_byte_buffer_size = byte_buffer_size;
// 初始化数据包
av_init_packet(&pkt);
do {
// 读取数据包
if (!end_of_stream) {
if (av_read_frame(fmt_ctx, &pkt) < 0) {
end_of_stream = 1;
}
}
if (end_of_stream) {
pkt.data = NULL;
pkt.size = 0;
}
// 解码视频帧
if (pkt.stream_index == video_stream || end_of_stream) {
got_frame = 0;
result = avcodec_decode_video2(ctx, fr, &got_frame, &pkt);
if (result < 0) {
av_log(NULL, AV_LOG_ERROR, "Error decoding frame\n");
return result;
}
// 如果解码到一帧图像
if (got_frame) {
// 处理解码后的图像数据
process_frame(fr, byte_buffer, byte_buffer_size);
}
}
// 释放数据包
av_packet_unref(&pkt);
} while (!end_of_stream || got_frame);
// 释放资源
avcodec_free_context(&ctx);
avformat_close_input(&fmt_ctx);
av_frame_free(&fr);
av_free(byte_buffer);
av_free(slice_byte_buffer);
return 0;
}