【笔记】An ffmpeg and SDL Tutorial 01《视频帧保存到PPM文件》
来源:互联网 发布:淘宝衣服洗过退货技巧 编辑:程序博客网 时间:2024/06/07 20:37
概述
本节讲述了从一个视频文件中截取图片的过程。参考Git(https://git.oschina.net/MyFFMpeg/MyPlayer)下的《\src\An-ffmpeg-and-SDL-Tutorial\tutorial01》工程。该工程简单的封装了FFmpeg的数据结构,便于阅读。
媒体文件是由一定的格式组织而成,文件本身成为容器,文件包含了文件的类型,该类型标明了文件的组装形式和其他各种媒体相关的信息。除了文件信息以外其它大部分内容成为“流”,流一般分为音频流和视频流(还有字幕流等)。被切割成一块块的,这样的块被叫做“包”(Packet),通过FFmpeg,我们将流中的“包”读出来,然后通过编解码(CODEC)模块将“包”转换为“帧”(Frame)。最后我们对“帧”进行不同的操作达到我们想要的效果。本节我们将“帧”保存为PPM格式的图片,PPM格式介绍可以参考文章《
PPM文件格式详解
》。下面通过两张图片简单介绍下FFMpeg数据结构(其实这些结构也就说明了媒体文件的存储结构),如下图:
FFmpeg库的使用流程很固定,下面是其解码流程图:
初始化FFmpeg库
与很多库一样,FFmpeg需要进行初始化才能使用,整个程序初始化一次即可,对应代码如下:
void my::InitFFmpegLibrary(){av_register_all();avformat_network_init();}
打开文件
打开文件,主要执行了下面的操作:
- 初始化m_pFormatCtx
- 获得视音频索引
- 打开视频解码器
对应于下面代码中的函数:int CMyFFmpeg::Init(const char *pFileName)
class CMyFFmpeg{public:CMyFFmpeg();~CMyFFmpeg();// 初始化m_pFormatCtx、m_pCodecCtx等intInit(const char*pFileName);voidUninit();// 打印结构体信息voidLog_StructInfo();// 获取m_pCodecCtx中的宽高intGetWidth();intGetHeight();private:friend class CMyDecoder;std::stringm_strFileName;AVFormatContext*m_pFormatCtx;unsigned intm_nIndexVideo;unsigned intm_nIndexAudio;AVCodecContext*m_pCodecCtx;AVCodec*m_pCodec;// 准确的说,它只是m_pCodecCtx的一部分,并不是成员变量,只需要释放m_pCodecCtx就可以了};int CMyFFmpeg::Init(const char *pFileName){m_strFileName = pFileName;// 初始化m_pFormatCtx{m_pFormatCtx = avformat_alloc_context();if (m_pFormatCtx == NULL){return -1;}if (avformat_open_input(&m_pFormatCtx, pFileName, NULL, NULL) != 0){printf("Couldn't open input stream.\n");return -2;}if (avformat_find_stream_info(m_pFormatCtx, NULL) < 0){printf("Couldn't find stream information.\n");return -3;}}// 获得视音频索引{m_nIndexVideo = -1;m_nIndexAudio = -1;for (unsigned int i = 0; i < m_pFormatCtx->nb_streams; i++){if (m_pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){if (m_nIndexVideo == -1){m_nIndexVideo = i;}}else if (m_pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){if (m_nIndexAudio == -1){m_nIndexAudio = i;}}}if (m_nIndexVideo == -1){printf("Didn't find a video stream.\n");return -21;}}// 打开视频解码器{m_pCodecCtx = avcodec_alloc_context3(NULL);if (m_pCodecCtx == NULL){printf("Could not allocate AVCodecContext\n");return -31;}int nRet = avcodec_parameters_to_context(m_pCodecCtx, m_pFormatCtx->streams[m_nIndexVideo]->codecpar);// 新的API,替换avcodec_copy_contextif (nRet != 0){printf("Could not allocate AVCodecContext\n");return -32;}m_pCodec = avcodec_find_decoder(m_pCodecCtx->codec_id);//指向AVCodec的指针.查找解码器if (m_pCodec == NULL){printf("Codec not found.\n");return -33;}//打开解码器if (avcodec_open2(m_pCodecCtx, m_pCodec, NULL) < 0){printf("Could not open codec.\n");return -34;}}return 0;}void CMyFFmpeg::Uninit(){if (m_pCodecCtx){avcodec_close(m_pCodecCtx);m_pCodecCtx = NULL;// avcodec_free_context(&m_pCodecCtx);}if (m_pFormatCtx){avformat_close_input(&m_pFormatCtx);m_pFormatCtx = NULL;}}
读取数据AVFrame
函数AVFrame* CMyDecoder::GetFrame()对数据读取和解码进行了封装,代码如下:
class CMyDecoder{public:CMyDecoder();~CMyDecoder();public:intInit(CMyFFmpeg *pMy, AVPixelFormat fmt);intUninit();AVFrame*GetFrame();AVFrame*GetFrame2();// 使用新的API执行private:CMyFFmpeg*m_pMy;SwsContext*m_pSwsCtx;AVFrame*m_pFrame;// avcodec_decode_video2 解出来的数据AVFrame*m_pFrameSws;// Sws转换后的Frameuint8_t*m_pBuff4FrameSws;// m_pFrameSws对应的缓冲区AVPacketm_packet;};int CMyDecoder::Init(CMyFFmpeg *pMy, AVPixelFormat fmt){m_pMy = pMy;if (!m_pMy){return -1;}int width = m_pMy->m_pCodecCtx->width;int height = m_pMy->m_pCodecCtx->height;// 初始化用到的结构体m_pFrame = av_frame_alloc();m_pFrameSws = av_frame_alloc();m_pBuff4FrameSws = (uint8_t *)av_malloc(avpicture_get_size(fmt, width, height));avpicture_fill((AVPicture *)m_pFrameSws, m_pBuff4FrameSws, fmt, width, height);m_pSwsCtx = sws_getContext(width, height, m_pMy->m_pCodecCtx->pix_fmt,width, height, fmt, SWS_BICUBIC, NULL, NULL, NULL);if (!m_pFrame || !m_pFrameSws || !m_pBuff4FrameSws || !m_pSwsCtx){return -2;}return 0;}int CMyDecoder::Uninit(){m_pMy = NULL;// 释放 m_pFrameSws和m_pBuff4FrameSwsif (m_pBuff4FrameSws){av_free(m_pBuff4FrameSws);m_pBuff4FrameSws = NULL;}if (m_pFrameSws){av_frame_free(&m_pFrameSws);m_pFrameSws = NULL;}// 释放 m_pFrameif (m_pFrame){av_frame_free(&m_pFrame);m_pFrame = NULL;}return 0;}AVFrame* CMyDecoder::GetFrame(){bool hasGetFrame = false;if (av_read_frame(m_pMy->m_pFormatCtx, &m_packet) < 0){return NULL;}else{if (m_packet.stream_index == m_pMy->m_nIndexVideo){int got_picture = 0;int nRet = avcodec_decode_video2(m_pMy->m_pCodecCtx, m_pFrame, &got_picture, &m_packet);if (nRet < 0){hasGetFrame = false;}else if (got_picture == 0){hasGetFrame = false;}else{sws_scale(m_pSwsCtx, (const uint8_t* const*)m_pFrame->data, m_pFrame->linesize, 0,m_pMy->m_pCodecCtx->height, m_pFrameSws->data, m_pFrameSws->linesize);hasGetFrame = true;}}// av_read_frame读取成功后,每次都需要释放m_packet内部的数据结构av_free_packet(&m_packet);}if (hasGetFrame){return m_pFrameSws;}else{return NULL;}}
保存文件
根据AVFrame创建PPM文件,代码如下:
void my::File_WritePPM(AVFrame *pFrame, int width, int height, const std::string &strFileName){std::ofstream ofile;// 打开文件ofile.open(strFileName.data(), std::ios::binary);if (!ofile){return;}// 写文件头ofile << "P6\n"<< width << " " << height << "\n"<< "255\n";// 写像素信息int nSizePerLine = width * 3;// 每一个像素有3个字节,每行有width*3个字节for (int y = 0; y < height; y++){const char *pBuf = (const char *)(pFrame->data[0] + pFrame->linesize[0] * y);ofile.write(pBuf, nSizePerLine);}// 关闭文件ofile.close();}
0 0
- 【笔记】An ffmpeg and SDL Tutorial 01《视频帧保存到PPM文件》
- 【笔记】An ffmpeg and SDL Tutorial 02《视频播放》
- 【笔记】An ffmpeg and SDL Tutorial 00
- An ffmpeg and SDL Tutorial 01
- An ffmpeg and SDL Tutorial
- An ffmpeg and SDL Tutorial
- An ffmpeg and SDL Tutorial
- An ffmpeg and SDL Tutorial
- An ffmpeg and SDL Tutorial 学习笔记(一)
- An ffmpeg and SDL Tutorial 学习笔记(二)
- An ffmpeg and SDL Tutorial 学习笔记(三)
- An ffmpeg and SDL Tutorial 学习笔记(四)
- An ffmpeg and SDL Tutorial 学习笔记(五)
- An ffmpeg and SDL Tutorial 学习笔记(六)
- An ffmpeg and SDL Tutorial 学习笔记(七)
- An ffmpeg and SDL Tutorial 02
- An ffmpeg and SDL Tutorial 00
- An ffmpeg and SDL Tutorial 03
- win7 64位+vs2010+opencv2.4.10+cmake3.6编译报错的解决
- LinkedList模拟栈和队列
- DLUT C++上机作业(实验七)
- 构造函数与普通函数的区别
- Sublime Text3--插件安装
- 【笔记】An ffmpeg and SDL Tutorial 01《视频帧保存到PPM文件》
- 菜鸟说Less
- java学习(7)
- ArrayList集合
- (转)马云又在押宝智能投顾!
- redis设计与实现第一部分读书笔记(未完)
- 文章标题
- selenium webdriver 如何实现将浏览器滚动条移动到某个位置
- POJ 2154-Color(Polya定理-旋转 串项链)