[SimplePlayer] 1. 从视频文件中提取图像

在开始之前,我们需要了解视频文件的格式。视频文件的格式众多,无法三言两语就能详细分析其结构,尽管如此,ffmpeg却很好地提取了各类视频文件的共同特性,并对其进行了抽象描述。

视频文件格式,统称为container。它包含一个描述视频信息的头部,以及内含实际的音视频编码数据的packets。当然,这里的头部以及packet部分只是个抽象描述,实际的视频格式的描述信息可能不是存放在视频文件的起始位置,可能是由分散于视频文件的各个位置的多个部分组成;数据包有可能是由头部以及尾部进行分割的传统数据包形式,也有可能是一大块数据区域,由索引进行各个数据包的分割。

视频文件中的packets最主要的就是视频以及音频packets,demux的过程就是解析container的header来获取视频信息,所得到的视频信息能帮助我们区分packet是音频或者视频。同样属性的packets会被称为stream。

packet中存储的数据就是音视频编码后的数据,通过解码器进行decode后就能得到视频图像或者音频帧。其中需要注意的一点是,一个packet不一定对应一帧,packet的顺序也不一定是实际的播放顺序,而通过ffmpeg解码出来的frame的顺序就是实际的播放顺序。

Demux

首先需要一个用于存储视频文件信息的结构体。

pFormatCtx = avformat_alloc_context();

读取视频文件,并对该文件进行demux,所得到的视频信息存储于刚刚所构建的结构体当中

    if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0){
        fprintf(stderr, "open input failed\n");
        return -1;
    }

如果pFormatCtx=NULL,那么avformat_open_input也能自动为pFormatCtx分配存储空间。

对于有些视频格式,单单通过demux并不能获得所有的视频信息,为了获得这些信息,还需要读取该视频几个最前端packets。所读取的这几个packets会被缓存以供后续处理。

if(avformat_find_stream_info(pFormatCtx, NULL)<0){
        fprintf(stderr, "find stream info failed\n");
        return -1;
    }

从所获得的信息当中得到video stream序号,后续可以通过stream序号来对packet进行筛选。

videoStream = av_find_best_stream(pFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);

Decode

创建一个用于存储以及维护解码信息结构体。

pCodecCtx = avcodec_alloc_context3(NULL);

把demux时所获得的视频相关信息传递到解码结构体中。

if(avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStream]->codecpar)<0){
        fprintf(stderr, "copy param from format context to codec context failed\n");
        return -1;
    }

根据解码器id来寻找对应的解码器

pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if(pCodec==NULL){
        fprintf(stderr, "Unsupported codec,codec id %d\n", pCodecCtx->codec_id);
        return -1;
    }else{
        fprintf(stdout, "codec id is %d\n", pCodecCtx->codec_id);
    }

打开该解码器,主要目的是对解码器进行初始化

    if(avcodec_open2(pCodecCtx, pCodec, NULL)<0){
        fprintf(stderr, "open codec failed\n");
        return -1;
    }

创建一个用于维护所读取的packet的结构体,一个用于维护解码所得的frame的结构体

    pPacket = av_packet_alloc();
    pFrame = av_frame_alloc();
    if(pFrame == NULL||pPacket == NULL){
        fprintf(stderr, "cannot get buffer of frame or packet\n");
        return -1;
    }

从视频文件中读取packet,如果所读取的packet是video,则进行解码,解码所得的帧由pFrame进行维护。当然,并不是每次调用avcodec_decode_video2都会返回一帧,因为也可能会有需要多个packet才能解码出一帧的情况,因此只有当指示一帧是否解码完成的frameFinished为1才能对这一帧进行后续处理。

    while(av_read_frame(pFormatCtx, pPacket)>=0){
        //Only deal with the video stream of the type "videoStream"
        if(pPacket->stream_index==videoStream){
            //Decode video frame
            avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, pPacket);
            //fprintf(stdout, "Frame : %d ,pts=%lld, timebase=%lf\n", i, pFrame->pts, av_q2d(pFormatCtx->streams[videoStream]->time_base));
            if(frameFinished){
                if(i>=START_FRAME && i<=END_FRAME){
                    SaveFrame2YUV(pFrame, pCodecCtx->width, pCodecCtx->height, i);
                    i++;
                }else{
                    i++;
                    continue;
                }
            }
        }
        av_packet_unref(pPacket);
    }

当一个packet被解码后就可以调用av_packet_unref来释放该packet所占用的空间了。

Store

视频文件解码出来后通常都是YUV格式,Y、U、V三路分量分别存储在AVFrame的data[0]、data[1]、data[2]所指向的内存区域。linesize[0]、linesize[1]、linesize[2]分别指示了Y、U、V一行所占用的字节数。下面把解码所得的帧保存为YUV Planar格式。

void SaveFrame2YUV(AVFrame *pFrame, int width, int height, int iFrame){
    static FILE *pFile;
    char szFilename[32];
    int y;

    //Open file
    if(iFrame==START_FRAME){
         sprintf(szFilename, "Video.yuv");
        pFile = fopen(szFilename, "wb");
        if(pFile==NULL)
            return;
    }

    //Write YUV Data, Only support YUV420
    //Y
    for(y=0; y<height; y++){
        fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, pFrame->linesize[0], pFile);
    }
    //U
    for(y=0; y<(height+1)/2; y++){
        fwrite(pFrame->data[1]+y*pFrame->linesize[1], 1, pFrame->linesize[1], pFile);
    }
    //V
    for(y=0; y<(height+1)/2; y++){
        fwrite(pFrame->data[2]+y*pFrame->linesize[2], 1, pFrame->linesize[2], pFile);
    }

    //Close FIle
    if(iFrame==END_FRAME){
        fclose(pFile);
    }
}

最后就是释放内存,关闭decoder,关闭demuxer

    av_free(pPacket);
    av_free(pFrame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

原文地址:https://www.cnblogs.com/TaigaCon/p/9603854.html

时间: 2024-09-29 05:48:05

[SimplePlayer] 1. 从视频文件中提取图像的相关文章

(转载)[FFmpeg]使用ffmpeg从各种视频文件中直接截取视频图片

你曾想过从一个视频文件中提取图片吗?在Linux下就可以,在这个教程中我将使用ffmpeg来从视频中获取图片. 什么是ffmpeg?What is ffmpeg? ffmpeg是一个非常有用的命令行程序,它可以用来转码媒体文件.它是领先的多媒体框架FFmpeg的一部分,其有很多功能,比如解码.编码.转码.混流.分离.转化为流.过滤以及播放几乎所有的由人和机器创建的媒体文件. 在这个框架中包含有各种工具,每一个用于完成特定的功能.例如,ffserver能够将多媒体文件转化为用于实时广播的流,ffp

从视频文件中读入数据--&gt;将数据转换为灰度图--&gt;对图像做candy边缘检测

//从视频文件中读入数据-->将数据转换为灰度图-->对图像做candy边缘检测 //作者:sandy //时间:2015-10-10 #include <cv.h> #include <highgui.h> int main(int argc, char *argv[]){ //预备工作 CvCapture* capture=cvCreateFileCapture("E:\\Videos\\xx.avi");//让capture变量指向视频文件 i

[转]【流媒體】H264—MP4格式及在MP4文件中提取H264的SPS、PPS及码流

[流媒體]H264—MP4格式及在MP4文件中提取H264的SPS.PPS及码流 SkySeraph Apr 1st 2012  Email:[email protected].com 一.MP4格式基本概念 MP4格式对应标准MPEG-4标准(ISO/IEC14496) 二.MP4封装格式核心概念 1  MP4封装格式对应标准为 ISO/IEC 14496-12(信息技术 视听对象编码的第12部分: ISO 基本媒体文件格式/Information technology Coding of a

C#实现通过ffmpeg从flv视频文件中截图的方法

本文实例讲述了C#实现通过ffmpeg从flv视频文件中截图的方法.分享给大家供大家参考.具体分析如下: 需要先下载ffmpeg,这是开源的,代码如下所示: 代码如下: using System; using System.Configuration; public class PublicMethod:System.Web.UI.Page { public PublicMethod() { } //文件路径 public static string ffmpegtool = "ffmpeg/f

SQLServer 从xml 文件中提取节点数据到数据库中

原文出处:http://blog.csdn.net/kk185800961/article/details/12350715  转载请注明出处 XML实例文件: <?xml version="1.0" encoding="utf-8"?> <!-- edited with XMLSpy v2010 (http://www.altova.com) by fengshuai (founder) --> <Root> <Frame

android studio从布局文件中提取style

写一个复杂的布局文件:当快写完时,发现已经快1000行代码啦,虽然有空格,但是布局也显得太庞大啦,无意间发现android studio从布局文件中提取style的方法,很是方便. 首先在布局文件中正常编写View的属性,然后点击右键打开菜单,依次选择Refactor -> Extract -> Style,如图所示: 其次,在弹出的提取style对话框中,选择所需要的属性,如图所示: 最后,为style命名,点击ok,然后就可以在style.xml文件中看到这个style了,如图所示: 就这

JSFinder:一个在js文件中提取URL和子域名的脚本

JSFinder介绍 JSFinder是一款用作快速在网站的js文件中提取URL,子域名的脚本工具. 支持用法 简单爬取 深度爬取 批量指定URL/指定JS 其他参数 以往我们子域名多数使用爆破或DNS中获得,这个脚本从JS文件中匹配出子域也算是添砖加瓦. 简单爬取示例 子域名清单 https://github.com/Threezh1/JSFinder 点个赞 (0) 原文地址:https://www.cnblogs.com/nul1/p/11140910.html

如何使用JMeter从文件中提取数据

在性能测试方面,重用响应数据至关重要.几乎(如果不是全部!)负载测试场景假设您: 从先前的响应中提取有趣的方面,并在下一个请求中重用它们(也称为相关) 确保实际响应符合预期(又称断言) 因此,如果您是性能测试工程师,那么了解如何实现此关联和断言逻辑非常重要.幸运的是,BlazeMeter的知识库和JMeter博客已经有一些关于如何做到这一点的精彩文章.请查看以下内容: 使用带有JMeter的RegEx(正则表达式提取器) - 使用Perl5样式的正则表达式解析响应 在JMeter中使用XPath

C#控制台基础 在博客备份xml文件中提取所有博文的标题 (正则,流读取)

镇场诗: 清心感悟智慧语,不着世间名与利.学水处下纳百川,舍尽贡高我慢意. 学有小成返哺根,愿铸一良心博客.诚心于此写经验,愿见文者得启发.------------------------------------------ introduction: 解析 博客园-博客备份 生成的XML文件,获得所有博文的标题. code: 1 using System; 2 using System.Collections.Generic; 3 using System.IO; 4 using System