从零开始学习音视频编程技术(五) 使用FFMPEG解码视频之保存成图片-视频播放器开发-音视频技术文章-音视频编程技术 (yundiantech.com)
5.现在根据视频流 打开一个解码器来解码:
解释下列代码
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (pCodec == NULL) {
printf("Codec not found.");
return -1;
}
///打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Could not open codec.");
return -1;
}
该代码段是使用FFmpeg库中的函数,对一个视频文件进行解码。
首先,它使用视频文件格式上下文(pFormatCtx)访问视频流(videoStream)的解码器上下文(pCodecCtx)。
然后它使用avcodec_find_decoder函数在FFmpeg库中查找对应的视频解码器(pCodec)。如果无法找到解码器,则输出错误信息并退出程序。
最后,它使用avcodec_open2函数打开解码器并将解码器上下文与解码器相关联,以准备解码视频帧。如果无法打开解码器,则输出错误信息并退出程序。
6.现在开始读取视频了:
解释下列代码
int y_size = pCodecCtx->width * pCodecCtx->height;
AVPacket *packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
av_new_packet(packet, y_size); //分配packet的数据
if (av_read_frame(pFormatCtx, packet) < 0)
{
break; //这里认为视频读取完了
}
该代码段是在一个循环中使用FFmpeg库函数读取视频帧。
首先,它使用解码器上下文(pCodecCtx)的宽(width)和高(height)计算一帧视频的大小(y_size)。这是为了分配一个足够大的包(packet)来存储视频帧数据。
然后,它使用malloc函数为packet分配内存,并使用av_new_packet函数初始化packet的数据缓冲区大小以存储刚计算出的y_size大小的数据。
接下来,它使用av_read_frame函数读取视频文件中的一帧,如果读取完毕则跳出循环。
在这个过程中,packet用来存储读取到的帧数据,可供后续的解码器使用。
7.前面我们说过 视频里面的数据是经过编码压缩的,因此这里我们需要将其解码:
{
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);
if (ret < 0) {
printf("decode error.");
return -1;
}
}
该代码段是对读取的视频帧数据进行解码的过程。
首先,它检查刚才读取的packet中数据流(stream_index)是否对应于视频流(videoStream)。如果对应,说明读取到的数据是视频数据,需要进行解码。如果不是视频数据,则忽略该packet。
然后,它使用avcodec_decode_video2函数将packet中的数据解码到pFrame缓冲区中,并返回一个状态值(ret)表示解码是否成功。
最后,它检查解码返回值(ret)是否小于0,如果是,则说明解码过程出现了错误。此时输出错误信息并返回-1。
需要注意的是,这个过程是在循环中完成的,因此在每次迭代中都会读取下一帧视频数据并进行解码,直到视频结束
函数avcodec_decode_video2用于将视频包的数据解码为一帧有损压缩的或无损压缩的视频图像,返回解码状态。
函数原型为:
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *got_picture_ptr, const AVPacket *avpkt);
参数说明:
- avctx: 指向使用的解码器上下文。
- picture: 解码后的视频帧将被写入该AVFrame结构中。可以为NULL。
- got_picture_ptr: 用于标识是否已成功获得视频帧。为非零值时成功,为0时失败/没有组成一个完整的视频帧。可以为NULL,表示不关心是否成功。
- avpkt: 视频帧数据包。函数不对该包进行反引用或复制,所以调用者不能在解码过程中释放该包。
返回值说明: - 成功解码并组成完整视频帧时返回值大于等于零,表示成功解码的字节数。
- 出现解码错误时,返回一个负的错误代码。例如,AVERROR(EINVAL)表示传递了无效参数。
需要注意的是,此函数并未分配AVFrame结构,所以调用前必须进行分配。此外,由于该函数采用了运行缓存,请确保在话头中设置AVCodecContext以便正确使用该缓存。
在解码后的视频帧的AVFrame结构中,视频帧的像素数据存储在该结构的data[0]、data[1]、data[2]等元素中,而各个像素的尺寸和布局则通过该结构的width、height、format元素描述。
8.基本上所有解码器解码之后得到的图像数据都是YUV420的格式,而这里我们需要将其保存成图片文件,因此需要将得到的YUV420数据转换成RGB格式,转换格式也是直接使用FFMPEG来完成:
详细解析下列代码 if (got_picture) {
sws_scale(img_convert_ctx,
(uint8_t const * const *) pFrame->data,
pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
pFrameRGB->linesize);
}
该代码段用于将解码得到的视频帧转换成RGB格式。
如果解码成功,并且包含了完整的一帧视频帧,got_picture的值为非0,进入if语句块中。
在if语句中,它使用FFmpeg库函数sws_scale进行颜色空间转换和大小调整。sws_scale可以将原始视频帧中的像素数据转换为指定的目标像素格式,这里是将视频帧的像素数据转换为RGB格式。
具体地,它将源视频帧pFrame中的像素数据指针(data)和每行像素数据的大小(linesize)作为输入,同时将转换后的结果写入目标RGB帧pFrameRGB的像素数据指针(data)和每行像素数据的大小(linesize)。
这里需要注意的是,sws_scale不会自动为输出RGB帧分配缓冲区,因此要在使用该函数之前确保输出RGB帧的像素数据指针已分配好存储空间。
img_convert_ctx是使用sws_getContext函数创建的SwsContext上下文,用于保存转换器的配置参数,包括源像素格式、目标像素格式、调整大小等参数。
最终,经过这个过程,解码器就可以输出RGB格式的视频帧供后续操作使用了。
9.得到RGB数据之后就是直接写入文件了:
SaveFrame(pFrameRGB, pCodecCtx->width,pCodecCtx->height,index++); //保存图片 if (index > 50) return 0; //这里我们就保存50张图片
该代码片段中,使用 SaveFrame 函数保存了转换后的 RGB 格式的视频帧,函数的原型及参数说明如下:
void SaveFrame(AVFrame* pFrame, int width, int height, int index) {
FILE* pFile;
char szFilename[32];
int y;
// 打开输出文件
sprintf(szFilename, "frame%d.ppm", index);
pFile = fopen(szFilename, "wb");
if (pFile == NULL)
return;
// 写入头信息
fprintf(pFile, "P6\n%d %d\n255\n", width, height);
// 写入像素数据
for (y = 0; y < height; y++) {
fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);
}
// 关闭输出文件
fclose(pFile);
}
具体而言,这段代码将转换后的 RGB 格式的视频帧保存为 PPM 格式的文件。PPM 是常用的一种图像格式,通常使用 P6 格式表示,其中第一行为 "P6",然后是图像的宽度和高度,最后是像素实际的颜色值。
在 SaveFrame 函数中,首先通过 sprintf 函数构造了要保存的 PPM 文件名,index 可以用于区分不同帧的文件名。然后通过 fopen 函数打开文件,如果打开失败,函数就提前返回。
接下来,函数先写入 PPM 文件的头信息,即 "P6\n"、宽度、高度、颜色值最大值等元信息。然后,函数使用 fwrite 函数将转换后的视频帧数据写入文件,覆盖了原有的颜色信息。这里需要注意,一个像素占用 3 个字节空间,对于每一行,像素数据以行为单位排列,每行按从左到右、从上到下的顺序写入字节流中。具体来说,y 表示当前处理的行数,对于每一行,都通过 fwrite 函数将当前行的像素数据写入文件中。
最后,函数通过 fclose 函数关闭文件句柄,并结束函数的执行。如果需要保存多张文件,可以用一个计数器(如 index 变量),实现保存指定数量的文件,如这里的 50 张视频帧对应了 50 个文件。在保存完指定数量的图片后,代码通过条件语句执行了一个简单的退出程序操作:返回 0,结束了对该视频的处理。
ubuntu中的QT中报错:‘AV_PIX_FMTRGB24’ was not declared in this scope
如果仅仅是缺乏某些宏定义而导致编译错误,可以在代码文件或编译选项中手动添加这些宏定义,如:
#define AV_PIX_FMT_RGB24 AV_PIX_FMT_BGR24
这里将已废弃的“AV_PIX_FMT_RGB24”宏定义重定向到了未废弃的“AV_PIX_FMT BGR24”宏定义,这样代码就能够通过编译了。