从零开始学习音视频编程技术(五) 使用FFMPEG解码视频之保存成图片

从零开始学习音视频编程技术(五) 使用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”宏定义,这样代码就能够通过编译了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容