一、流程

大致流程

原始音频/视频(存储在一定的封装格式(例如MP4、AVI等)中)

——提取——>码流数据h.264(封装数据)

——解码——>像素数据YUV

——>转换RGB格式的数据

——>保存成图片/直接用于显示

  • 码流:使用压缩算法压缩后的视频/音频数据
  • 像素数据:可以直接使用显示器播放的数据

具体实现流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fErSpw1n-1609872639250)(C:\Users\asus\Desktop\20200612193613.png)]

二、步骤

引入头文件:

extern "C"
{#include <libavcodec/avcodec.h>#include <libavformat/avformat.h>#include <libswscale/swscale.h>#include <libavutil/pixfmt.h>
}

1.注册组件

av_register_all();

2.打开视频流

AVFormatContext *fmatC;
fmatC=avformat_alloc_context();//初始化AVFormatContext结构体
avformat_open_input(&fmatC,fileName.toStdString().c_str(),NULL,NULL);//打开输入视频文件

相关结构体:封装格式上下文结构体AVFormatContext,也是统领全局的结构体,保存了视频文件封装格式相关信息

3.查看是否有流媒体信息

int res=avformat_find_stream_info(fmatC,NULL);//查看是否有流媒体信息
if(res<0)//未查找到流媒体信息
{qDebug()<<"Not find info!";return 1;
}

4.找到视频流

int videoIndex=-1;
for(int i=0;i<fmatC->nb_streams;i++)//2 streams info:picture and video
{if(fmatC->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)//找到视频编码器对应下标{videoIndex=i;break;}
}
if(videoIndex==-1)//未查找到视频流信息
{qDebug()<<"Not find video info!";return 1;
}

5.查找解码器

AVCodecContext *codecContext=fmatC->streams[videoIndex]->codec;
//查找解码器
AVCodec *codec=avcodec_find_decoder(codecContext->codec_id);//codec_id为编码器id
if(codec==NULL)//未找到
{qDebug()<<"Not find decode info!";return 1;
}

为什么要使用AVCodecContext上下文对象,而不是直接在AVFormatContextAVStream里存好AVCodec

因为要缩小AVFormatContext的所占内存空间,让AVCodecContext里只要保存编码器的id,等到要使用的时候再进行查找。

6.打开解码器

//打开解码器
res=avcodec_open2(codecContext,codec,NULL);
if(res!=0)
{qDebug()<<"Open code failed!";return 1;
}

7.循环读入一帧数据

AVPacket *acPkt;//定义一个存放提取后的码流数据h.264的AVPacket结构体
acPkt=av_packet_alloc();//初始化AVPacket结构体
int size=codecContext->width*codecContext->height;//帧大小
av_new_packet(acPkt,size);//初始化AVPacket中data
//因为date是一个指针(uint8_t *data),要单独再初始化分配内存int got_picture_ptr=-1;
AVFrame *picture;
picture=av_frame_alloc();//av_read_frame:从输入文件读取一帧压缩数据
//@return 0 if OK, < 0 on error or end of file
while(av_read_frame(fmatC,acPkt)==0)
{if(acPkt->stream_index==videoIndex)//此帧所属的AVStream是视频流{//[具体操作]}av_packet_unref(acPkt);//只是将AVPacket->data=NULL,AVPacket->size=0
}

相关结构体:

  • AVPacket存储一帧压缩编码数据

  • AVFrame存储一帧解码后像素(采样)数据

8.具体操作

while循环外定义保存地址,根据自己的情况自定义,我的是:

FILE *fileH265=fopen("/mnt/hgfs/share_ubuntu16.04/savh264.h264","wb+");
FILE *fileYUV=fopen("/mnt/hgfs/share_ubuntu16.04/savYUV.yuv","wb+");
QString imgPath="/mnt/hgfs/share_ubuntu16.04/temp.jpg";

8.1保存为h264文件

while循环中[具体操作]为:

//提取完就是h264文件了,所以直接写入保存就行
fwrite(acPkt->data,1,acPkt->size,fileH265);

8.2保存为YUV文件

解码后YUV格式的视频像素数据保存在AVFrame的data[0]、data[1]、data[2]中。但是这些像素值并不是连续存储的,每行有效像素之后存储了一些无效像素 。 以亮度 Y 数据为例 , data[0] 中一共包含了linesize[0]* height个数据。但是出于优化等方面的考虑,linesize[0]实际上并不等于宽度width,而是一个比宽度大一些的值。因此需要使用sws_scale()进行转换。转换后去除了无效数据,width和linesize[0]取值相等。

去除了无效数据的实现

在while循环前加上:

AVFrame *dstFrame;//定义一个存放解码后的像素数据YUV的AVPacket结构体
dstFrame=av_frame_alloc();//AVFrame中的data是指针,需要另外为其分配空间
//data定义:uint8_t *data[AV_NUM_DATA_POINTERS];
//以下是为data指针分配空间的过程://avpicture_get_size函数的第一个参数为enum AVPixelFormat pix_fmt,意为像素格式
//这里我设置为AV_PIX_FMT_YUVA420P
int numByte=avpicture_get_size(AV_PIX_FMT_YUV420P,codec->width,codec->height);//YUV帧的大小
uint8_t *buffer=(uint8_t *)av_malloc(numByte*sizeof(uint8_t));
//使存放目标dstFrame的data指针指向buffer
avpicture_fill((AVPicture *)dstFrame,buffer,AV_PIX_FMT_YUV420P,codecContext->width,codecContext->height);//定义像素数据转换的规则
SwsContext *sws_context=sws_getContext(codecContext->width,codecContext->height,codecContext->pix_fmt,codecContext->width,codecContext->height,AV_PIX_FMT_YUVA420P,SWS_BICUBIC,NULL,NULL,NULL);

其中,sws_getContext()的定义如下:

struct SwsContext *sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,int dstW, int dstH, enum AVPixelFormat dstFormat,int flags, SwsFilter *srcFilter,SwsFilter *dstFilter, const double *param);
/*** Allocate and return an SwsContext. You need it to perform* scaling/conversion operations using sws_scale().** @param srcW the width of the source image 源图片宽* @param srcH the height of the source image 源图片高* @param srcFormat the source image format 源图片像素数据格式* @param dstW the width of the destination image 目标图片宽* @param dstH the height of the destination image 目标图片高* @param dstFormat the destination image format 目标图片像素数据格式* @param flags specify which algorithm and options to use for rescaling 指定缩放算法* @param param extra parameters to tune the used scaler*              For SWS_BICUBIC param[0] and [1] tune the shape of the basis*              function, param[0] tunes f(1) and param[1] f´(1)*              For SWS_GAUSS param[0] tunes the exponent and thus cutoff*              frequency*              For SWS_LANCZOS param[0] tunes the width of the window function* @return a pointer to an allocated context, or NULL in case of error* @note this function is to be removed after a saner alternative is*       written*/

while循环中[具体操作]为:

//解码一帧提取数据
avcodec_decode_video2(codecContext,picture,&got_picture_ptr,acPkt);
//got_picture_ptr参数说明:ot_picture_ptr Zero if no frame could be ecompressed
if(got_picture_ptr)//帧数据可提取
{//缩放图片,去除无效数据sws_scale(sws_context,picture->data,picture->linesize,0,picture->height,dstFrame->data,dstFrame->linesize);//写入保存为YUV文件//YUV格式中,Y只包含亮度信息,而UV只包含色度信息。 fwrite(dstFrame->data[0],1,size,fileYUV);//Yfwrite(dstFrame->data[1],1,size/4,fileYUV);//U,占全部信息的1/4fwrite(dstFrame->data[2],1,size/4,fileYUV);//V,占全部信息的1/4
}

其中,sws_scale()的定义如下:

int sws_scale(struct SwsContext *c, const uint8_t *const srcSlice[],const int srcStride[], int srcSliceY, int srcSliceH,uint8_t *const dst[], const int dstStride[]);
/*** Scale the image slice in srcSlice and put the resulting scaled* slice in the image in dst. A slice is a sequence of consecutive* rows in an image. * 缩放图像切片并把结果放进目标dst里。切片是图像中连续行的序列。** Slices have to be provided in sequential order, either in* top-bottom or bottom-top order. If slices are provided in* non-sequential order the behavior of the function is undefined.* 切片必须按顺序提供,可以是上下顺序,也可以是下上顺序。* 如果切片是按非顺序提供的,则函数的行为是未定义的。** @param c         the scaling context previously created with*                  sws_getContext() 之前用sws_getContext创建的SwsContext对象* @param srcSlice  the array containing the pointers to the planes of*                  the source slice 解码后的图像像素数据(音频采样数据)* @param srcStride the array containing the strides for each plane of*                  the source image 对视频来说是图像中一行像素的大小;对音频帧的大小。* @param srcSliceY the position in the source image of the slice to*                  process, that is the number (counted starting from*                  zero) in the image of the first row of the slice 源图片处理的起始位置* @param srcSliceH the height of the source slice, that is the number*                  of rows in the slice 处理多少行* @param dst       the array containing the pointers to the planes of*                  the destination imagev 目标数据* @param dstStride the array containing the strides for each plane of*                  the destination image* @return          the height of the output slice*/

参数int srcSliceY, int srcSliceH,定义在输入图像上处理区域,srcSliceY是起始位置,srcSliceH是处理多少行。如果srcSliceY=0,srcSliceH=height,表示一次性处理完整个图像。这种设置是为了多线程并行,例如可以创建两个线程,第一个线程处理 [0, h/2-1]行,第二个线程处理 [h/2, h-1]行。并行处理加快速度。

8.3保存为一帧一帧的图片

与上同理,先去除了无效数据,在while循环前加上:

//像素数据格式为AV_PIX_FMT_RGB32
int numByte=avpicture_get_size(AV_PIX_FMT_RGB32,codecContext->width,codecContext->height);
uint8_t *buffer=(uint8_t *)av_malloc(numByte*sizeof(uint8_t));
avpicture_fill((AVPicture *)dstFrame,buffer,AV_PIX_FMT_RGB32,codecContext->width,codecContext->height);
SwsContext *sws_context=sws_getContext(codecContext->width,codecContext->height,codecContext->pix_fmt,codecContext->width,codecContext->height,AV_PIX_FMT_RGB32,SWS_BICUBIC,NULL,NULL,NULL);

while循环中[具体操作]为:

avcodec_decode_video2(codecContext,picture,&got_picture_ptr,acPkt);
if(got_picture_ptr)
{sws_scale(sws_context,picture->data,picture->linesize,0,picture->height,dstFrame->data,dstFrame->linesize);QImage img((uchar *)buffer,codecContext->width,codecContext->height,QImage::Format_RGB32);img.save(imgPath);//保存此帧图片
}

9.释放空间

好习惯:记得把之前分配的空间释放掉

fflush(fileH265);
fflush(fileYUV);
fclose(fileH265);
fclose(fileYUV);
av_free(buffer);
av_frame_unref(dstFrame);
av_frame_unref(picture);
avcodec_close(codecContext);
avformat_close_input(&fmatC);
av_free_packet(acPkt);

ht,QImage::Format_RGB32);
img.save(imgPath);//保存此帧图片
}


## 9.释放空间> 好习惯:记得把之前分配的空间释放掉```c
fflush(fileH265);
fflush(fileYUV);
fclose(fileH265);
fclose(fileYUV);
av_free(buffer);
av_frame_unref(dstFrame);
av_frame_unref(picture);
avcodec_close(codecContext);
avformat_close_input(&fmatC);
av_free_packet(acPkt);

FFMPEG框架学习——(2)视频的提取和解码相关推荐

  1. ffmpeg教程 如何从视频中提取音频文件?

    今天用ffmpeg实现第一个功能,从视频文件中提取音频,生成mp3文件.代码很简单,只要拼接好正确的参数就行了. 传送门 ffmpeg教程 如何从视频中提取音频文件?

  2. 基于 ffmpeg + Webassembly 实现前端视频帧提取

    作者:jordiwang  https://juejin.im/post/6854573219454844935 现有的前端视频帧提取主要是基于 canvas + video 标签的方式,在用户本地选 ...

  3. 【音视频】利用ffmpeg实现:音视频的提取,rtmp推流等

    目录 可列出电脑的设备 音频+桌面视频,存mp4 录声音  推流到服务器 音频+桌面视频,推流到服务器 音频+笔记本摄像头,推流到服务器 音频+笔记本摄像头,推流到服务器:通过wvp-pro代+AAC ...

  4. 使用FFMPEG——4.2.2版本实现提取视频编码解码文件,ffmpeg基础学习。

    FFMPEG基础学习 视频解码,并且输出到文件. 我通过雷霄骅的博客学习FFMPEG,在学习过程中发现"雷神"的代码由于版本的问题,很多代码已经无法在FFMPEG--4.2.2版本 ...

  5. 【FFMPEG】各种音视频编解码学习详解 h264 ,mpeg4 ,aac 等所有音视频格式

    目录(?)[-] 编解码学习笔记二codec类型 编解码学习笔记三Mpeg系列Mpeg 1和Mpeg 2 编解码学习笔记四Mpeg系列Mpeg 4 编解码学习笔记五Mpeg系列AAC音频 编解码学习笔 ...

  6. FFmpeg 使用命令整理 – 提取音频或视频、提取图片、格式转换等

    提取声音的软件网上搜索有不少,不过最有名最专业的似乎是一个命令行工具:ffmpeg.这个工具功能十分丰富且强大,我所需要的从视频中提取 mp3 的功能只是其中包含的一个.感觉使用起来很复杂因为是命令行 ...

  7. 【FFmpeg 命令】提取音频或视频、提取图片、格式转换等

    文章目录 1. FFmpeg 简介 2. 组件组成 3. 命令参数 3.1 主要参数 3.2 影像参数 4. 实战使用 4.1 格式转换 (将file.avi 转换成output.flv) 4.2 合 ...

  8. 一种基于深度学习的目标检测提取视频图像关键帧的方法

    摘要:针对传统的关键帧提取方法误差率高.实时性差等问题,提出了一种基于深度学习的目标检测提取视频图像关键帧的方法,分类提取列车头部.尾部及车身所在关键帧.在关键帧提取过程中,重点研究了基于SIFT特征 ...

  9. ffmpeg获取设备支持的分辨率_短视频字幕提取合成超简单,掌握ffmpeg这个小技巧

    引言 现在的短视频非常流行.大多数情况下我们会开着音量或者戴着耳机收看视频.但有些时候不是太方便,如果视频能有一个字幕就好了. 好消息是,字幕制作的软件很专业很好用,而且上手超级简单. 坏消息是,如何 ...

最新文章

  1. 计算机专业好的广东二本学校排名2015,2015年广东省二本大学排名名单
  2. C语言之 scanf() 函数的用法
  3. python表头写进csv文件_Python读取CSV文件列并在CSV-fi中写入文件名和列名
  4. linux中,项目生成的文件的权限为-rw-r-----
  5. 【初码干货】关于.NET玩爬虫这些事
  6. 韦根w34是多少位_韦根接口读卡器说明书
  7. Spring Bean 定义继承
  8. java重写面试题_Java面试题:重写了equals方法,为什么还要重写hashCode方法?
  9. python骗局-代购被骗?Python带你征服骗子!直接干掉他的网站!亲身经历!
  10. mysql错误Table ‘./mysql/proc’ is marked as crashed and should be repaired
  11. 并发编程 06—— CompletionService :Executor 和 BlockingQueue
  12. matlab工作区显示的是什么,matlab工作区介绍
  13. 屏幕录像专家 EXE视频转MP4
  14. python求解一元二次方程考虑复数_Python学习笔记:求解一元二次方程
  15. 渲染101渲染农场完美支持火凤凰插件Phoenix FD
  16. 小龙秋招【面试笔记】正式发布,速来围观!(已有40+同学斩获大厂offer)
  17. GVRP和VTP的比较与区别
  18. Proactol什么是脂肪燃烧
  19. 生成式模型与辨别式模型
  20. 阿里云ACP认证(SLB专项)

热门文章

  1. 世界上最简单的会计书(资产负债表)
  2. 计算机房摆放布局,数据中心机房位置及设备布置的几点要求
  3. 计算机管理员账户默认密码,win10默认管理员密码,win10管理员初始密码
  4. 基于springboot的校园食堂订餐系统
  5. echarts设置图标图例legend为圆,长方形,扇形等
  6. 怎么把PDF页面删除?教你两种方法
  7. 《SysML精粹》学习记录--第十一章
  8. 【码农说码】手撕锟斤拷,彻底搞懂GB2312,GBK,Big5,ASCII,UTF-8,UTF-32的前世今生
  9. 【笔记本电脑连接真无线 jbl flash x耳机】pin 是 000000
  10. 优酷android 离线 导出来,手机优酷缓存的视频如何导出 缓存视频导出到电脑方法...