ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

ffmpeg解码流程

2019-04-07 21:54:48  阅读:569  来源: 互联网

标签:ffmpeg int 解码 AV avformat av NULL 流程


1、ffmpeg解码流程

《1》、ffmpeg旧接口的解码流程

 

《2》、新接口解码流程

 

  注意在新接口流程中使用avcodec_parameters_to_context函数来初始解码器参数,在未加入该步骤之前解析avi封装的mpeg4视频没问题但是解析MP4封装的mpeg4视频会报如下错误

Picture size is 0x00

1加上该步骤后解决(解析wmv格式视频也必须加入这一步)

2、使用到的ffmpeg结构体及API说明

《1》、AVFormatContext结构体

  该结构体描述了一个媒体文件或媒体流的构成和基本信息。它是一个贯穿始终的数据结构,很多函数调用需要使用到它。它也是FFMPEG解封装(flv,avi,mp4)功能的结构体。

其主要的几个变量(主要考虑解码情况):

struct AVInputFormat *iformat;

//输入数据的封装格式。仅解封装用,由avformat_open_input()设置。

struct AVOutputFormat *oformat;

//输出数据的封装格式。仅封装用,调用者在avformat_write_header()之前设置。

AVIOContext *pb;// I/O上下文。

解封装:由用户在avformat_open_input()之前设置(然后用户必须手动关闭它)或通过avformat_open_input()设置。

封装:由用户在avformat_write_header()之前设置。 调用者必须注意关闭/释放IO上下文。

unsigned int nb_streams;//AVFormatContext.streams中元素的个数。

AVStream **streams;//文件中所有流的列表。char filename[1024];//输入输出文件名。

int64_t start_time;//第一帧的位置。

int64_t duration;//流的持续时间

int64_t bit_rate;//总流比特率(bit / s),如果不可用则为0。

int64_t probesize;

//从输入读取的用于确定输入容器格式的数据的最大大小。

仅封装用,由调用者在avformat_open_input()之前设置。

AVDictionary *metadata;//元数据

AVCodec *video_codec;//视频编解码器

AVCodec *audio_codec;//音频编解码器

AVCodec *subtitle_codec;//字母编解码器

AVCodec *data_codec;//数据编解码器

int (*io_open)(struct AVFormatContext *s, AVIOContext **pb, const char *url, int flags, AVDictionary **options);

//打开IO stream的回调函数。

void (*io_close)(struct AVFormatContext *s, AVIOContext *pb);

//关闭使用AVFormatContext.io_open()打开的流的回调函数。

使用时可以通过avformat_alloc_context分配后使用,也可以直接avformat_open_input。

//1、方法一

AVFormatContext *fmt_ctx = NULL;

string filename = "test.avi" ;

fmt_ctx = avformat_alloc_context();

avformat_open_input(&fmt_ctx, ilename.c_str(), NULL, NULL);

avformat_close_input(&fmt_ctx);

//2、方法二

AVFormatContext *fmt_ctx = NULL;

string filename = "test.avi" ;

int ret = avformat_open_input(&fmt_ctx, filename.c_str(), NULL, NULL);

avformat_close_input(&fmt_ctx);

推荐使用方法2,因为若传进avformat_open_input的fmt_ctx为NULL,该函数内部会调用avformat_alloc_context函数。相应的avformat_close_input内部会调用avformat_free_context。

《2》、AVCodec

  ffmpeg中的解码器及编码器都用AVCodec结构体保存一些编解码的配置信息。
  对解码来说可以按照下面方式使用

```

  //解码H264流 

   AVCodec*   Vcodec = NULL;

  Vcodec = avcodec_find_decoder(AV_CODEC_ID_H264); 

  //或者直接通过解码器名字找到解码器

  Vcodec = avcodec_find_decoder_by_name("h264_mediacodec");

  ```

AVCodecContext

  该结构体用于存储编解码器上下文的数据结构,包含了众多编解码需要的参数信息。这些信息参数需要进行初始化,使用avcodec_parameters_to_context进行初始化。不初始化解析一些格式的封装视频会导致编解码失败。该结构体内很多参数是编码时使用的,解码用不上。

几个主要的成员:

enum AVMediaType codec_type:编解码器的类型(视频,音频...)

struct AVCodec  *codec:采用的解码器AVCodec(H.264,MPEG2...)

int bit_rate:平均比特率

uint8_t *extradata; int extradata_size:针对特定编码器包含的附加信息(例如对于H.264解码器来说,存储SPS,PPS等)

AVRational time_base:根据该参数,可以把PTS转化为实际的时间(单位为秒s)

int width, height:如果是视频的话,代表宽和高

int refs:运动估计参考帧的个数(H.264的话会有多帧,MPEG2这类的一般就没有了)

int sample_rate:采样率(音频)

int channels:声道数(音频)

enum AVSampleFormat sample_fmt:采样格式

int profile:型(H.264里面就有,其他编码标准应该也有)

int level:级(和profile差不太多)

使用:

AVCodec*   Vcodec = NULL;

Vcodec = avcodec_find_decoder(AV_CODEC_ID_H264);

AVCodecContext*     AvContext = NULL;

AvContext = avcodec_alloc_context3(mVcodec);

avcodec_parameters_to_context(mAvContext,

fmt_ctx->streams[mVideoStreamIdx]->codecpar);

《4》、AVStream

  该结构体用于描述一个流媒体,该结构体中大部分值域可以由avformat_open_input函数根据文件头的信息确定,缺少的信息需要通过调用av_find_stream_info进一步获得。

av_find_stream_info函数读取一部分音视频来获取有关视频文件的一些信息,如编码宽高、视频时长等。对于一些没有头部信息的视频文件(如mpeg编码的文件)调用该函数是必须的。调用该函数可能会带了很大的延迟。

延迟优化方法参考。

主要的成员域:

index/id:index对应流的索引,这个数字是自动生成的,根据index可以从AVFormatContext::streams表中索引到该流;而id则是流的标识,依赖于具体的容器格式。比如对于MPEG TS格式,id就是pid。

time_base:流的时间基准,是一个实数,该流中媒体数据的pts和dts都将以这个时间基准为粒度。通常,使用av_rescale/av_rescale_q可以实现不同时间基准的转换。

start_time:流的起始时间,以流的时间基准为单位,通常是该流中第一个帧的pts。

duration:流的总时间,以流的时间基准为单位。

need_parsing:对该流parsing过程的控制域。

nb_frames:流内的帧数目。

avg_frame_rate:帧率相关。

codec:指向该流对应的AVCodecContext结构,调用avformat_open_input时生成。

parser:指向该流对应的AVCodecParserContext结构,调用av_find_stream_info时生成。

《5》、AVIOContext

  用于管理FFMPEG输入输出数据的结构体。

主要成员:

nsigned char *buffer:缓存开始位置

int buffer_size:缓存大小(默认32768)

unsigned char *buf_ptr:当前指针读取到的位置

unsigned char *buf_end:缓存结束的位置

void *opaque:URLContext结构体

在解码的情况下,buffer用于存储ffmpeg读入的数据。如打开一个视频文件时,先把数据从硬盘读入buffer,然后在送给解码器解码。
  opaque按照这篇博客说是指向URLContext,找源码没有找到相关的赋值操作但是在aviobuf.c的下面这个函数找到佐证。

*

typedef struct AVIOInternal {

    URLContext *h;

} AVIOInternal;

*/

static void *ff_avio_child_next(void *obj, void *prev)

{

    AVIOContext *s = obj;

    AVIOInternal *internal = s->opaque;

    return prev ? NULL : internal->h;

}

URLContext结构体中有一个URLProtocol。每种协议(rtp,rtmp,file,udp等)都有一个对应的URLProtocol。

《6》、AVPacket

  该结构体是ffmpeg中很重要的一个结构体,它保存了解码后或编码前的数据(仍然是压缩数据)和这些数据的一些附加信息,如显示时间戳(pts)、数据时长、所在媒体的索引等。

对于视频来说,一个AVPacket通常包含一帧压缩数据,而音频则有可能包含多个压缩的Frame。

重要的成员变量:

uint8_t *data:压缩编码的数据。

例如对于H.264来说。1个AVPacket的data通常对应一个NAL。

注意:在这里只是对应,而不是一模一样。他们之间有微小的差别:使用FFMPEG类库分离出多媒体文件中的H.264码流

因此在使用FFMPEG进行视音频处理的时候,常常可以将得到的AVPacket的data数据直接写成文件,从而得到视音频的码流文件。

int size:data的大小

int64_t pts:显示时间戳

int64_t dts:解码时间戳

int stream_index:标识该AVPacket所属的视频/音频流。

avpacket.h内有API说明,常用的几个API

av_packet_ref,av_packet_unref

av_new_packet, av_packet_alloc, av_init_packet, av_packet_unref,av_packet_free(free这个API为旧接口)

av_packet_clone:拷贝packet

《7》、AVFrame

  AVFrame结构体一般用于存储原始数据(非压缩的YUV,RGB数据等),此外还包含一些相关信息,比如解码的时候存储宏块类型表,QP表,运动矢量等数据。

AVFrame必须用av_frame_alloc分配,用av_frame_free释放。注意av_frame_alloc函数只创建实例但是该实例存储数据的buffer则需要通过另外的操作进行分配,如av_image_fill_arrays接口。

AVFrame内部几个常用的成员:

uint8_t *data[AV_NUM_DATA_POINTERS]:解码后原始数据(对视频来说是YUV,RGB,对音频来说是PCM)

int linesize[AV_NUM_DATA_POINTERS]:data中“一行”数据的大小。注意:未必等于图像的宽,一般大于图像的宽。

int width, height:视频帧宽和高(1920x1080,1280x720…)

int nb_samples:音频的一个AVFrame中可能包含多个音频帧,在此标记包含了几个

int format:解码后原始数据类型(YUV420,YUV422,RGB24…)

int key_frame:是否是关键帧

enum AVPictureType pict_type:帧类型(I,B,P…)

AVRational sample_aspect_ratio:宽高比(16:9,4:3…)

int64_t pts:显示时间戳

int coded_picture_number:编码帧序号

int display_picture_number:显示帧序号

int interlaced_frame:是否是隔行扫描

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

解码demo

AVCodecID strm_to_av_map_coding_type[STRM_CODEC_MAX] =

{

    AV_CODEC_ID_NONE,          //0

    AV_CODEC_ID_H264,          //1

    AV_CODEC_ID_H265,          //2

    AV_CODEC_ID_MPEG1VIDEO,    //3

    AV_CODEC_ID_MPEG2VIDEO,    //4

    AV_CODEC_ID_MJPEG,         //5

    AV_CODEC_ID_MPEG4          //6

 

}

AVPixelFormat strm_to_av_map_pixel_format[STRM_PIXFMT_MAX] =

{

    AV_PIX_FMT_NONE,

    AV_PIX_FMT_RGB24,

    AV_PIX_FMT_RGBA,

    AV_PIX_FMT_RGB565,

    AV_PIX_FMT_ARGB,

    AV_PIX_FMT_YUV420P,

    AV_PIX_FMT_YUV422P,

    AV_PIX_FMT_YUV444P,

    AV_PIX_FMT_GRAY8,

    AV_PIX_FMT_NV21,

    AV_PIX_FMT_UYVY422,

    AV_PIX_FMT_YUYV422

};

static AVFrame*    mVideoFrame = NULL;

static AVFrame*      mFrameYUV = NULL;

// 颜色转换上下文

static struct SwsContext*  mImgConvertCtx = NULL;

///< 解码器

static AVCodec*            mVcodec = NULL;

///< 解码上下文

static AVCodecContext*     mAvContext = NULL;

static AVFormatContext*    mFormatCtx = NULL;  // 用于读取文件  

static int mVideoStreamIdx = 0;

 

int test_decode(unsigned int mInputCodecType, unsigned int mOutputPixelFormat,

    string &filename, string &error)

{

    FILE* fp_YUV = fopen("decode_out.yuv", "wb+");

    if (!fp_YUV)

    {   perror("open out.yuv :");

        return -1;

    }

    av_register_all();                                        //1 、初始化

    mVideoFrame = av_frame_alloc();

    mFrameYUV   = av_frame_alloc();

    bool                mHasKeyFrame = false;

    AVPacket            mPktPacket;

    int ret = avformat_open_input(&mFormatCtx, filename.c_str(), NULL, NULL);     //2 、打开文件

    if (ret < 0)

    {

        printf("avformat_open_input fail \n");

        error = "avformat_open_input fail";

        av_log(NULL, AV_LOG_ERROR, "Cannot open file: %s.\n", filename.c_str());

        return false;

    }

    if (avformat_find_stream_info(mFormatCtx, NULL) < 0)               //3、找到视频流

    {

        return false;

    }

    av_dump_format(mFormatCtx, 0, filename.c_str(), 0);    

    //获取视频的编码信息  

    for (uint32_t i = 0; i < mFormatCtx->nb_streams; ++i)

    {

        if (mFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)

        {

            mVideoStreamIdx = i;

            break;

        }

    }

    // 寻找解码器

    mVcodec = avcodec_find_decoder(strm_to_av_map_coding_type[mInputCodecType]);  //4、根据视频流找到编码器

    mAvContext = avcodec_alloc_context3(mVcodec);                        //5、为编码器分配内存

    if (!mVcodec || !mAvContext)

    {

        return false;

    }

    ///不初始化解码器context会导致MP4封装的mpeg4码流解码失败        //6、完成数据的拷贝

    ret = avcodec_parameters_to_context(mAvContext, mFormatCtx->streams[mVideoStreamIdx]->codecpar);

    if (ret < 0)

    {

        av_log(NULL, AV_LOG_ERROR, "Error initializing the decoder context.\n");

        //exit_program(1);

    }

    // 打开解码器

    mAvContext->pix_fmt = strm_to_av_map_pixel_format[mOutputPixelFormat];   

    if (avcodec_open2(mAvContext, mVcodec, NULL) != 0)                        //7、打开编码器

    {

        //DecoderDeinit();

        return false;

    }

    //循环读入H264数据 

    AVPacket h264Pack;                                               //定义一个 AVPacket

    while (1)

    {

        av_init_packet(&h264Pack);                                   //初始化

        int ret = av_read_frame(mFormatCtx, &h264Pack);           //读取一帧放入  h264Pack 中

        if (ret != 0)

        {

            error = "Read a frame failed";

            av_packet_unref(&h264Pack);

            return false;

        }

        else if (h264Pack.stream_index != mVideoStreamIdx)

        {

            // not a video packet, skip it in this version

            av_packet_unref(&h264Pack);

            continue;

        }

        {

            // 初始化待解码包

            av_init_packet(&mPktPacket);

            mPktPacket.data = (uint8_t*)h264Pack.data;     // 用于解码的压缩视频帧数据

            mPktPacket.size = h264Pack.size;               //封装数据

            mPktPacket.pts = h264Pack.pts;

            mPktPacket.dts = h264Pack.dts;

            // 发送待解码包

            if (avcodec_send_packet(mAvContext, &mPktPacket))

            {

                error = "send packet failed";

                mHasKeyFrame = false;

                av_packet_unref(&mPktPacket);

                return false;

            }

 

            av_packet_unref(&mPktPacket);

 

            // 接收解码数据

            int ret = avcodec_receive_frame(mAvContext, mVideoFrame);

            if (ret != 0)

            {

                if (ret == AVERROR(EAGAIN))

                {

                    // 暂时没有输出,需要更多输入

                    error = "need more data";

                    //return false;

                    continue;

                }

            }

            DecodeFrameParams params;

            params.mInputCodecType = mInputCodecType;

            params.mOutputPixelFormat = mOutputPixelFormat;

            int frameSize = av_image_get_buffer_size(strm_to_av_map_pixel_format[params.mOutputPixelFormat],

mVideoFrame->width, mVideoFrame->height, 1);

 void* buf_ptr = NULL;

    buf_ptr = (void*)new(std::nothrow) char[frameSize];

    if (buf_ptr == NULL)

    {

        printf("decode memory allocation error! [buf_size = %d]\n", frameSize);

        return false;

    }

        av_image_fill_arrays(mFrameYUV->data, mFrameYUV->linesize, (uint8_t*)buf_ptr,

        strm_to_av_map_pixel_format[params.mOutputPixelFormat], mVideoFrame->width,

        mVideoFrame->height, 1);

            // 写文件保存视频数据

            fwrite(buf_ptr, frameSize, 1, fp_YUV);

            fflush(fp_YUV);

            if(buf_ptr)

            {

                delete [] buf_ptr;

                buf_ptr = NULL;

            }           

        }       

    }

    fclose(fp_YUV);

    avformat_close_input(&mFormatCtx);

    //printf("[strmsdk] decoder[%s] open successful.\n", mVcodec->name);

}

int main()

{

    //解码demo -> YUV

    string error;

    string filename;

    cout << "Input video: " << endl;

    cin >> filename;

 

    test_decode(6, 5, filename, error);

    cout << "error: " << error << endl;

 

}

标签:ffmpeg,int,解码,AV,avformat,av,NULL,流程
来源: https://blog.csdn.net/m0_37346206/article/details/89074969

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有