若该文为原创文章,未经容许不得转载
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/108573195
各位读者,常识无穷而人力有穷,要么改需要,要么找专业人士,要么本人钻研
红瘦子(红模拟)的博文大全:开发技术汇合(蕴含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬联合等等)继续更新中...(点击传送门)
FFmpeg开发专栏(点击传送门)
上一篇:《FFmpeg开发笔记(三):ffmpeg介绍、windows编译以及开发环境搭建》
下一篇:敬请期待
前言
ffmpeg波及了很多,循序渐进,本篇形容根本的解码流程。
Demo
ffmpeg解码流程
ffmpeg的解码和编码都遵循其根本的执行流程。
根本流程如下:
步骤一:注册:
应用ffmpeg对应的库,都须要进行注册,能够注册子项也能够注册全副。
步骤二:关上文件:
关上文件,依据文件名信息获取对应的ffmpeg全局上下文。
步骤三:探测流信息:
肯定要探测流信息,拿到流编码的编码格局,不探测流信息则其流编码器拿到的编码类型可能为空,后续进行数据转换的时候就无奈通晓原始格局,导致谬误。
步骤四:查找对应的解码器
根据流的格局查找解码器,软解码还是硬解码是在此处决定的,然而特地留神是否反对硬件,须要本人查找本地的硬件解码器对应的标识,并查问其是否反对。广泛操作是,枚举反对文件后缀解码的所有解码器进行查找,查找到了就是能够硬解了(此处,不做过多的探讨,对应硬解码后续会有文章进行进一步钻研)。
(留神:解码时查找解码器,编码时查找编码器,两者函数不同,不要弄错了,否则后续能关上然而数据是错的)
步骤五:关上解码器
关上获取到的解码器。
步骤六:申请缩放数据格式转换构造体
此处特地留神,基本上解码的数据都是yuv系列格局,然而咱们显示的数据是rgb等相干色彩空间的数据,所以此处转换构造体就是进行转换前到转换后的形容,给后续转换函数提供转码根据,是很要害并且十分罕用的构造体。
步骤七:申请缓存区
申请一个缓存区outBuffer,fill到咱们指标帧数据的data上,比方rgb数据,QAVFrame的data上存是有指定格局的数据,且存储有规定,而fill到outBuffer(本人申请的指标格局一帧缓存区),则是咱们须要的数据格式存储程序。
举个例子,解码转换后的数据为rgb888,理论间接用data数据是谬误的,然而用outBuffer就是对的,所以此处应该是ffmpeg的fill函数做了一些转换。
进入循环解码:
步骤八:获取一帧packet
拿取封装的一个packet,判断packet数据的类型进行解码拿到存储的编码数据
步骤九:数据转换
应用转换函数联合转换构造体对编码的数据进行转换,那拿到须要的指标宽度、高度和指定存储格局的原始数据。
步骤十:自行处理
拿到了原始数据自行处理。
一直循环,直到拿取pakcet函数胜利,然而无奈got一帧数据,则代表文件解码曾经实现。
帧率须要本人管制循环,此处只是循环拿取,可加提早等。
步骤十一:开释QAVPacket
此处要独自列出是因为,其实很多网上和开发者的代码:
在进入循环解码前进行了av_new_packet,循环中未av_free_packet,造成内存溢出;
在进入循环解码前进行了av_new_packet,循环中进行av_free_pakcet,那么一次new对应无数次free,在编码器上是不合乎前后一一对应标准的。
查看源代码,其实能够发现av_read_frame时,主动进行了av_new_packet(),那么其实对于packet,只须要进行一次av_packet_alloc()即可,解码完后av_free_packet。
执行完后,返回执行“步骤八:获取一帧packet”,一次循环完结。
步骤十二:开释转换构造体
全副解码实现后,装置申请程序,进行对应资源的开释。
步骤十三:敞开解码/编码器
敞开之前关上的解码/编码器。
步骤十四:敞开上下文
敞开文件上下文后,要对之前申请的变量依照申请的程序,顺次开释。
另附上实现的具体解码流程图:
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/108573195
ffmpeg解码相干变量
AVFormatContext
AVFormatContext形容了一个媒体文件或媒体流的形成和根本信息,位于avformat.h文件中。
AVInputFormat
AVInputFormat 是相似COM 接口的数据结构,示意输出文件容器格局,着重于性能函数,一种文件容器格局对应一个AVInputFormat 构造,在程序运行时有多个实例,位于avoformat.h文件中。
AVDictionary
AVDictionary 是一个字典汇合,键值对,用于配置相干信息。
AVCodecContext
AVCodecContext是一个形容编解码器上下文的数据结构,蕴含了泛滥编解码器须要的参数信息,位于avcodec.h文件中。
AVPacket
AVPacket是FFmpeg中很重要的一个数据结构,它保留理解复用(demuxer)之后,解码(decode)之前的数据(依然是压缩后的数据)和对于这些数据的一些附加的信息,如显示工夫戳(pts),解码工夫戳(dts),数据时长(duration),所在流媒体的索引(stream_index)等等。
应用前,应用av_packet_alloc()调配,
AVCodec
AVCodec是存储编解码器信息的构造体,位于avcodec.h文件中。
AVFrame
AVFrame中存储的是通过解码后的原始数据。在解码中,AVFrame是解码器的输入;在编码中,AVFrame是编码器的输出。
应用前,应用av_frame_alloc()进行调配。
struct SwsContext
应用前,应用sws_getContext()进行获取,次要用于视频图像的转换。
ffmpeg解码流程相干函数原型
av_register_all
void av_register_all(void);
初始化libavformat并注册所有muxer、demuxer和协定。如果不调用此函数,则能够抉择想要指定注册反对的哪种格局,通过av_register_input_format()、av_register_output_format()。
avformat_open_input
int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);
关上输出流并读取标头。编解码器未关上。流必须应用avformat_close_input()敞开,返回0-胜利,<0-失败错误码。
- 参数一:指向用户提供的AVFormatContext(由avformat_alloc_context调配)的指针。
- 参数二:要关上的流的url
- 参数三:fmt如果非空,则此参数强制应用特定的输出格局。否则将自动检测格局。
- 参数四:蕴含AVFormatContext和demuxer公有选项的字典。返回时,此参数将被销毁并替换为蕴含找不到的选项。都无效则返回为空。
avformat_find_stream_info
int avformat_find_stream_info(AVFormatContext ic, AVDictionary *options);
读取查看媒体文件的数据包以获取具体的流信息,如媒体存入的编码格局。
- 参数一:媒体文件上下文。
- 参数二:字典,一些配置选项。
avcodec_find_decoder
AVCodec *avcodec_find_decoder(enum AVCodecID id);
查找具备匹配编解码器ID的已注册解码器,解码时,曾经获取到了,注册的解码器能够通过枚举查看,枚举太多,略。
avcodec_open2
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
初始化AVCodeContext以应用给定的AVCodec。
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);
调配并返回一个SwsContext。须要它来执行sws_scale()进行缩放/转换操作。
avpicture_get_size
int avpicture_get_size(enum AVPixelFormat pix_fmt, int width, int height);
返回存储具备给定参数的图像的缓存区域大小。
- 参数一:图像的像素格局
- 参数二:图像的像素宽度
- 参数三:图像的像素高度
avpicture_fill
int avpicture_fill(AVPicture *picture, const uint8_t *ptr, enum AVPixelFormat pix_fmt, int width, int height);
依据指定的图像、提供的数组设置数据指针和线条大小参数。
- 参数一:输出AVFrame指针,强制转换为AVPciture即可。
- 参数二:映射到的缓存区,开发者本人申请的寄存图像数据的缓存区。
- 参数三:图像数据的编码格局。
- 参数四:图像像素宽度。
- 参数五:图像像素高度。
av_read_frame
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
返回流的下一帧。此函数返回存储在文件中的内容,不对无效的帧进行验证。获取存储在文件中的帧中,并为每个调用返回一个。不会的省略无效帧之间的有效数据,以便给解码器最大可用于解码的信息。
返回0是胜利,小于0则是谬误,大于0则是文件开端,所以大于等于0是返回胜利。
avcodec_decode_video2
int avcodec_decode_video2(AVCodecContext *avctx, AVFrame *picture, int *got_picture_ptr, const AVPacket *avpkt);
将大小为avpkt->size from avpkt->data的视频帧解码为图片。一些解码器能够反对单个avpkg包中的多个帧,解码器将只解码第一帧。出错时返回负值,否则返回字节数,如果没有帧能够解压缩,则为0。
- 参数一:编解码器上下文。
- 参数二:将解码视频帧存储在AVFrame中。
- 参数三:输出缓冲区的AVPacket。
- 参数四:如果没有帧能够解压,那么失去的图片是0,否则,它是非零的。
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[]);
在srcSlice中缩放图像切片并将后果缩放在dst中切片图像。切片是间断的序列图像中的行。
- 参数一:以前用创立的缩放上下文*sws_getContext()。
- 参数二:蕴含指向源片段,就是AVFrame的data。
- 参数三:蕴含每个立体的跨步的数组,其实就是AVFrame的linesize。
- 参数四:切片在源图像中的地位,从开始计数0对应切片第一行的图像,所以间接填0即可。
- 参数五:源切片的像素高度。
- 参数六:指标数据地址映像,是指标AVFrame的data。
- 参数七:指标每个立体的跨步的数组,就是linesize。
av_free_packet
void av_free_packet(AVPacket *pkt);
开释一个包。
avcodec_close
int avcodec_close(AVCodecContext *avctx);
敞开给定的avcodeContext并开释与之关联的所有数据(但不是AVCodecContext自身)。
avformat_close_input
void avformat_close_input(AVFormatContext **s);
敞开关上的输出AVFormatContext。开释它和它的所有内容并将*s设置为空。
Demo源码
void FFmpegManager::testDecode(){// QString fileName = "test/1.avi"; QString fileName = "test/1.mp4"; // ffmpeg相干变量事后定义与调配 AVFormatContext *pAVFormatContext = 0; // ffmpeg的全局上下文,所有ffmpeg操作都须要 AVInputFormat *pAVInputFormat = 0; // ffmpeg的输出格局构造体 AVDictionary *pAVDictionary = 0; // ffmpeg的字典option,各种参数给格局编解码配置参数的 AVCodecContext *pAVCodecContext = 0; // ffmpeg编码上下文 AVCodec *pAVCodec = 0; // ffmpeg编码器 AVPacket *pAVPacket = 0; // ffmpag单帧数据包 AVFrame *pAVFrame = 0; // ffmpeg单帧缓存 AVFrame *pAVFrameRGB32 = 0; // ffmpeg单帧缓存转换色彩空间后的缓存 struct SwsContext *pSwsContext = 0; // ffmpag编码数据格式转换 int ret = 0; // 函数执行后果 int videoIndex = -1; // 音频流所在的序号 int gotPicture = 0; // 解码时数据是否解码胜利 int numBytes = 0; // 解码后的数据长度 uchar *outBuffer = 0; // 解码后的数据寄存缓存区 pAVFormatContext = avformat_alloc_context(); // 调配 pAVPacket = av_packet_alloc(); // 调配 pAVFrame = av_frame_alloc(); // 调配 pAVFrameRGB32 = av_frame_alloc(); // 调配 if(!pAVFormatContext || !pAVPacket || !pAVFrame || !pAVFrameRGB32) { LOG << "Failed to alloc"; goto END; } // 步骤一:注册所有容器和编解码器(也能够只注册一类,如注册容器、注册编码器等) av_register_all(); // 步骤二:关上文件(ffmpeg胜利则返回0) LOG << "文件:" << fileName << ",是否存在:" << QFile::exists(fileName); ret = avformat_open_input(&pAVFormatContext, fileName.toUtf8().data(), pAVInputFormat, 0); if(ret) { LOG << "Failed"; goto END; } // 步骤三:探测流媒体信息 // Assertion desc failed at libswscale/swscale_internal.h:668 // 入坑:因为pix_fmt为空,须要对编码器上下文进一步探测 ret = avformat_find_stream_info(pAVFormatContext, 0); if(ret < 0) { LOG << "Failed to avformat_find_stream_info(pAVCodecContext, 0)"; goto END; } // 打印文件信息 LOG << "视频文件蕴含流信息的数量:" << pAVFormatContext->nb_streams; // 在Qt中av_dump_format不会进行命令行输入// av_dump_format(pAVFormatContext, 1, fileName.toUtf8().data(), 0); // 步骤三:提取流信息,提取视频信息 for(int index = 0; index < pAVFormatContext->nb_streams; index++) { pAVCodecContext = pAVFormatContext->streams[index]->codec; switch (pAVCodecContext->codec_type) { case AVMEDIA_TYPE_UNKNOWN: LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_UNKNOWN"; break; case AVMEDIA_TYPE_VIDEO: LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_VIDEO"; videoIndex = index; LOG; break; case AVMEDIA_TYPE_AUDIO: LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_AUDIO"; break; case AVMEDIA_TYPE_DATA: LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_DATA"; break; case AVMEDIA_TYPE_SUBTITLE: LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_SUBTITLE"; break; case AVMEDIA_TYPE_ATTACHMENT: LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_ATTACHMENT"; break; case AVMEDIA_TYPE_NB: LOG << "流序号:" << index << "类型为:" << "AVMEDIA_TYPE_NB"; break; default: break; } // 曾经找打视频品流 if(videoIndex != -1) { break; } } if(videoIndex == -1 || !pAVCodecContext) { LOG << "Failed to find video stream"; goto END; } // 步骤四:对找到的视频流寻解码器 pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id); if(!pAVCodec) { LOG << "Fialed to avcodec_find_decoder(pAVCodecContext->codec_id):" << pAVCodecContext->codec_id; goto END; } // 步骤五:关上解码器 ret = avcodec_open2(pAVCodecContext, pAVCodec, NULL); if(ret) { LOG << "Failed to avcodec_open2(pAVCodecContext, pAVCodec, pAVDictionary)"; goto END; } LOG << pAVCodecContext->width << "x" << pAVCodecContext->height; // 步骤六:对拿到的原始数据格式进行缩放转换为指定的格局高宽大小 // Assertion desc failed at libswscale/swscale_internal.h:668 // 入坑:因为pix_fmt为空,须要对编码器上下文进一步探测 pSwsContext = sws_getContext(pAVCodecContext->width, pAVCodecContext->height, pAVCodecContext->pix_fmt, pAVCodecContext->width, pAVCodecContext->height, AV_PIX_FMT_RGBA, SWS_FAST_BILINEAR, 0, 0, 0); numBytes = avpicture_get_size(AV_PIX_FMT_RGBA, pAVCodecContext->width, pAVCodecContext->height); outBuffer = (uchar *)av_malloc(numBytes); // pAVFrame32的data指针指向了outBuffer avpicture_fill((AVPicture *)pAVFrameRGB32, outBuffer, AV_PIX_FMT_RGBA, pAVCodecContext->width, pAVCodecContext->height); // 此处无需调配 // av_read_frame时他会调配,av_new_packet多此一举,正好解释了一次new和屡次free的问题// av_new_packet(pAVPacket, pAVCodecContext->width * pAVCodecContext->height); // 步骤七:读取一帧数据的数据包 while(av_read_frame(pAVFormatContext, pAVPacket) >= 0) { if(pAVPacket->stream_index == videoIndex) { // 步骤八:对读取的数据包进行解码 ret = avcodec_decode_video2(pAVCodecContext, pAVFrame, &gotPicture, pAVPacket); if(ret < 0) { LOG << "Failed to avcodec_decode_video2(pAVFormatContext, pAVFrame, &gotPicture, pAVPacket)"; break; } // 等于0代表拿到了解码的帧数据 if(!gotPicture) { LOG << "no data"; break; }else{ sws_scale(pSwsContext, (const uint8_t * const *)pAVFrame->data, pAVFrame->linesize, 0, pAVCodecContext->height, pAVFrameRGB32->data, pAVFrameRGB32->linesize); QImage imageTemp((uchar *)outBuffer, pAVCodecContext->width, pAVCodecContext->height, QImage::Format_RGBA8888); QImage image = imageTemp.copy(); LOG << image.save("1.jpg"); } av_free_packet(pAVPacket); } QThread::msleep(100); }END: LOG << "开释回收资源"; if(outBuffer) { av_free(outBuffer); outBuffer = 0; } if(pSwsContext) { sws_freeContext(pSwsContext); pSwsContext = 0; LOG << "sws_freeContext(pSwsContext)"; } if(pAVFrameRGB32) { av_frame_free(&pAVFrameRGB32); pAVFrame = 0; LOG << "av_frame_free(pAVFrameRGB888)"; } if(pAVFrame) { av_frame_free(&pAVFrame); pAVFrame = 0; LOG << "av_frame_free(pAVFrame)"; } if(pAVPacket) { av_free_packet(pAVPacket); pAVPacket = 0; LOG << "av_free_packet(pAVPacket)"; } if(pAVCodecContext) { avcodec_close(pAVCodecContext); pAVCodecContext = 0; LOG << "avcodec_close(pAVCodecContext);"; } if(pAVFormatContext) { avformat_free_context(pAVFormatContext); pAVFormatContext = 0; LOG << "avformat_free_context(pAVFormatContext)"; }}
工程模板v1.1.0
对应工程模板v1.1.0
上一篇:《FFmpeg开发笔记(三):ffmpeg介绍、windows编译以及开发环境搭建》
下一篇:敬请期待
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/108573195