zoukankan      html  css  js  c++  java
  • [SimplePlayer] 1. 从视频文件中提取图像

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

    image

    视频文件格式,统称为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
    ");
            return -1;
        }

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

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

    if(avformat_find_stream_info(pFormatCtx, NULL)<0){
            fprintf(stderr, "find stream info failed
    ");
            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
    ");
            return -1;
        }

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

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

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

        if(avcodec_open2(pCodecCtx, pCodec, NULL)<0){
            fprintf(stderr, "open codec failed
    ");
            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
    ");
            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
    ", 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);
  • 相关阅读:
    w3wp.exe占用CPU100%的解决办法
    Visual Studio 2005 查找和替换窗口 显示不了
    IIS:w3wp.exe进程占用cpu和内存过多的处理办法
    C# form ComboBox
    从尾到头打印链表,不允许逆置原链表
    [置顶] ATL窗口thunk机制的剖析与实现
    flex自定义用ArrayCollection做数据源的带checkbox的tree(功能强大的完美版^_^)
    oracle的PremaredStatement.executeBatch为什么返回2
    窗体Controls的OfType<>方法的使用
    HDU 1421 动态规划(DP) 搬寝室
  • 原文地址:https://www.cnblogs.com/TaigaCon/p/9603854.html
Copyright © 2011-2022 走看看