ffmpeg分析系列

hello,各位好,本人是一名嵌入式软件工程师,目前正使用ffmpeg开发一款嵌入式多媒体播放器,《ffmpeg分析》系列博文是本人在阅读ffmpeg源代码时所做的笔记,希望对各位有点帮助。分析过程结合下面的例程:
    http://dranger.com/ffmpeg/tutorial05.c
 
一. 调用av_register_all函数注册所有的格式和编码解码器.

1.1 先调用avcodec_register_all函数注册所有的编码解码器.

1. 下面列出与H264相关的注册:

    // 注册硬件加速器
    REGISTER_HWACCEL (H264_DXVA2, h264_dxva2);
    REGISTER_HWACCEL (H264_VAAPI, h264_vaapi);

    // 注册解码器
    REGISTER_DECODER (H264, h264);
    REGISTER_DECODER (H264_VDPAU, h264_vdpau);

    // 注册编码器
    REGISTER_ENCODER (LIBX264, libx264);

    // 注册分析器
    REGISTER_PARSER (H264, h264);

    // 注册位流分离器
    REGISTER_BSF (H264_MP4TOANNEXB, h264_mp4toannexb);


2. 下面列出注册宏:

#define REGISTER_HWACCEL(X,x){ \
          extern AVHWAccel x##_hwaccel; \
          if(CONFIG_##X##_HWACCEL) av_register_hwaccel(&x##_hwaccel);}

#define REGISTER_ENCODER(X,x){ \
          extern AVCodec x##_encoder; \
          if(CONFIG_##X##_ENCODER) avcodec_register(&x##_encoder);}

#define REGISTER_DECODER(X,x){ \
          extern AVCodec x##_decoder; \
          if(CONFIG_##X##_DECODER) avcodec_register(&x##_decoder);}

#define REGISTER_ENCDEC(X,x) REGISTER_ENCODER(X,x); REGISTER_DECODER(X,x)

#define REGISTER_PARSER(X,x){ \
          extern AVCodecParser x##_parser; \
          if(CONFIG_##X##_PARSER) av_register_codec_parser(&x##_parser);}

#define REGISTER_BSF(X,x){ \
          extern AVBitStreamFilter x##_bsf; \
          if(CONFIG_##X##_BSF) av_register_bitstream_filter(&x##_bsf);}


3. 分析一下注册函数, 以avcodec_register函数为例:

void avcodec_register(AVCodec*codec)
{
    AVCodec **p;
    avcodec_init();
    p = &first_avcodec;
    while (*p!= NULL) p =&(*p)->next;
    *p = codec;
    codec->next= NULL;
}


    可以看到avcodec_register函数把输入的AVCodec连成一个链表, 其它注册函数与之类似, 就不多言了.

4. 上面调用了avcodec_init函数:

void avcodec_init(void)
{
    static int initialized= 0;

     if (initialized!= 0)
        return;
    initialized = 1;

    dsputil_static_init ();
}


    这个函数只会真正执行一次.

5. 上面调用了dsputil_static_init函数:

av_coldvoid dsputil_static_init(void)
{
    int i;

     for(i=0;i<256;i++) ff_cropTbl[i + MAX_NEG_CROP]= i;
    for(i=0;i<MAX_NEG_CROP;i++){
        ff_cropTbl[i]= 0;
        ff_cropTbl[i + MAX_NEG_CROP + 256]= 255;
    }

     for(i=0;i<512;i++){
        ff_squareTbl[i]= (i - 256) *(i - 256);
    }

     for(i=0; i<64; i++) inv_zigzag_direct16[ff_zigzag_direct[i]]= i+1;
}

    
    可以看到, 它初始化了一些静态数据.

1.2 注册所有的格式和外部库及协议.

1. 下面列出与H264相关的注册:

    // 注册分离器和混合器
    REGISTER_MUXDEMUX (H264, h264);

    // 注册文件协议
    REGISTER_PROTOCOL (FILE,file);


2. 下面列出注册宏:

#define REGISTER_MUXER(X,x){ \
    extern AVOutputFormat x##_muxer; \
    if(CONFIG_##X##_MUXER) av_register_output_format(&x##_muxer);}

#define REGISTER_DEMUXER(X,x){ \
    extern AVInputFormat x##_demuxer; \
    if(CONFIG_##X##_DEMUXER) av_register_input_format(&x##_demuxer);}

#define REGISTER_MUXDEMUX(X,x) REGISTER_MUXER(X,x); REGISTER_DEMUXER(X,x)

#define REGISTER_PROTOCOL(X,x){ \
    extern URLProtocol x##_protocol; \
    if(CONFIG_##X##_PROTOCOL) av_register_protocol(&x##_protocol);}


    这些注册函数与avcodec_register函数类似, 就不多言了.



URL协议结构:

typedef struct URLProtocol {
    const char *name;
    int (*url_open)(URLContext *h, const char *url, int flags);
    int (*url_read)(URLContext *h, unsigned char *buf, int size);
    int (*url_write)(URLContext *h, unsigned char *buf, int size);
    int64_t (*url_seek)(URLContext *h, int64_t pos, int whence);
    int (*url_close)(URLContext *h);
    struct URLProtocol *next;
    int (*url_read_pause)(URLContext *h, int pause);
    int64_t (*url_read_seek)(URLContext *h, int stream_index,
                             int64_t timestamp, int flags);
    int (*url_get_file_handle)(URLContext *h);
} URLProtocol;



libavformat/file.c文件的file协议:

staticint file_open(URLContext*h, const char *filename, int flags)
{
    int access;
    int fd;

    av_strstart(filename,"file:", &filename);

    if (flags& URL_RDWR){
        access = O_CREAT | O_TRUNC | O_RDWR;
    } else if (flags & URL_WRONLY){
        access = O_CREAT | O_TRUNC | O_WRONLY;
    } else {
        access = O_RDONLY;
    }
#ifdef O_BINARY
    access |= O_BINARY;
#endif
    fd = open(filename, access, 0666);
    if (fd == -1)
        return AVERROR(errno);
    h->priv_data= (void*) (intptr_t) fd;
    return 0;
}

static int file_read(URLContext*h, unsigned char *buf, int size)
{
    int fd =(intptr_t) h->priv_data;
    return read(fd, buf, size);
}

static int file_write(URLContext*h, unsigned char *buf, int size)
{
    int fd =(intptr_t) h->priv_data;
    return write(fd, buf, size);
}

/* XXX: use llseek */
static int64_t file_seek(URLContext*h, int64_t pos, int whence)
{
    int fd =(intptr_t) h->priv_data;
    if (whence== AVSEEK_SIZE){
        struct stat st;
        int ret = fstat(fd,&st);
        return ret < 0 ? AVERROR(errno): st.st_size;
    }
    return lseek(fd, pos, whence);
}

static int file_close(URLContext*h)
{
    int fd =(intptr_t) h->priv_data;
    return close(fd);
}

static int file_get_handle(URLContext*h)
{
    return (intptr_t) h->priv_data;
}

URLProtocol file_protocol = {
    "file",
    file_open,
    file_read,
    file_write,
    file_seek,
    file_close,
    .url_get_file_handle = file_get_handle,
};


libavformat/allformats.c文件的av_register_all函数注册了file协议:

#define REGISTER_PROTOCOL(X,x){ \
    extern URLProtocol x##_protocol; \
    if(CONFIG_##X##_PROTOCOL) av_register_protocol(&x##_protocol);}


void av_register_all(void)
{
    /* 省略部分代码 */
    /* protocols */
    REGISTER_PROTOCOL (FILE,file);
    /* 省略部分代码 */
}


把注册协议函数也贴出来吧:

URLProtocol*first_protocol =NULL;


int av_register_protocol(URLProtocol*protocol)
{
    URLProtocol **p;
    p = &first_protocol;
    while (*p!= NULL) p =&(*p)->next;
    *p = protocol;
    protocol->next= NULL;
    return 0;

}

http://blogold.chinaunix.net/u3/104564/showart_2369209.html
探测数据结构:

/** This structure contains the data a format has to probe a file. */
typedef struct AVProbeData {
    const char *filename;
    unsigned char *buf; /**< Buffer must have AVPROBE_PADDING_SIZE of extra allocated bytes filled with zero. */
    int buf_size; /**< Size of buf except extra allocated bytes */
} AVProbeData;


h264的探测函数:

staticint h264_probe(AVProbeData*p)
{
    uint32_t code=-1;
    int sps=0, pps=0, idr=0, res=0, sli=0;
    int i;

    for(i=0; i<p->buf_size; i++){
        code = (code<<8)+ p->buf[i];
        if ((code& 0xffffff00)== 0x100){
            int ref_idc=(code>>5)&3;
            int type = code & 0x1F;
            static const int8_t ref_zero[32]={
                2, 0, 0, 0, 0,-1, 1,-1,
               -1, 1, 1, 1, 1,-1, 2, 2,
                2, 2, 2, 0, 2, 2, 2, 2,
                2, 2, 2, 2, 2, 2, 2, 2
            };

            if(code& 0x80) //forbidden bit

                return 0;

            if(ref_zero[type]== 1 && ref_idc)
                return 0;
            if(ref_zero[type]==-1&& !ref_idc)
                return 0;
            if(ref_zero[type]== 2)
                res++;

            switch(type){
            case 1: sli++;break;
            case 5: idr++;break;
            case 7:
                if(p->buf[i+2]&0x0F)
                    return 0;
                sps++;
                break;
            case 8: pps++;break;
            }
        }
    }
    if(sps && pps &&(idr||sli>3)&& res<(sps+pps+idr))
        return AVPROBE_SCORE_MAX/2+1;// +1 for .mpg

    return 0;
}


视频读首部函数:

staticint video_read_header(AVFormatContext*s,
                             AVFormatParameters *ap)
{
    AVStream *st;

    st = av_new_stream(s, 0);
    if (!st)
        return AVERROR(ENOMEM);

    st->codec->codec_type= AVMEDIA_TYPE_VIDEO;
    st->codec->codec_id= s->iformat->value;
    st->need_parsing= AVSTREAM_PARSE_FULL;

    /* for MJPEG, specify frame rate */
    /* for MPEG-4 specify it, too (most MPEG-4 streams do not have the fixed_vop_rate set ...)*/
    if (ap->time_base.num){
        st->codec->time_base= ap->time_base;
    } else if ( st->codec->codec_id== CODEC_ID_MJPEG||
                st->codec->codec_id== CODEC_ID_MPEG4||
                st->codec->codec_id== CODEC_ID_DIRAC||
                st->codec->codec_id== CODEC_ID_DNXHD||
                st->codec->codec_id== CODEC_ID_H264){
        st->codec->time_base=(AVRational){1,25};
    }
    av_set_pts_info(st, 64, 1, 1200000);

    return 0;
}


原始地读实际的包函数:

int ff_raw_read_partial_packet(AVFormatContext*s, AVPacket*pkt)
{
    int ret, size;

    size = RAW_PACKET_SIZE;

    if (av_new_packet(pkt, size)< 0)
        return AVERROR(ENOMEM);

    pkt->pos= url_ftell(s->pb);
    pkt->stream_index= 0;
    ret = get_partial_buffer(s->pb, pkt->data, size);
    if (ret< 0) {
        av_free_packet(pkt);
        return ret;
    }
    pkt->size= ret;
    return ret;
}


原始地写包函数:

static int raw_write_packet(struct AVFormatContext *s, AVPacket *pkt)
{
    put_buffer(s->pb, pkt->data, pkt->size);
    put_flush_packet(s->pb);
    return 0;
}


h264混合器:

AVOutputFormat h264_muxer = {
    "h264",
    NULL_IF_CONFIG_SMALL("raw H.264 video format"),
    NULL,
    "h264",
    0,
    CODEC_ID_NONE,
    CODEC_ID_H264,
    NULL,
    raw_write_packet,
    .flags= AVFMT_NOTIMESTAMPS,
};



h264分离器:

AVInputFormat h264_demuxer= {
    "h264",
    NULL_IF_CONFIG_SMALL("raw H.264 video format"),
    0,
    h264_probe,
    video_read_header,
    ff_raw_read_partial_packet,
    .flags= AVFMT_GENERIC_INDEX,
    .extensions ="h26l,h264,264",//FIXME remove after writing mpeg4_probe
    .value = CODEC_ID_H264,
}


libavformat/allformats.c文件的av_register_all函数注册了h264分离器和混合器:

#define REGISTER_MUXER(X,x){ \
    extern AVOutputFormat x##_muxer; \
    if(CONFIG_##X##_MUXER) av_register_output_format(&x##_muxer);}

#define REGISTER_DEMUXER(X,x){ \
    extern AVInputFormat x##_demuxer; \
    if(CONFIG_##X##_DEMUXER) av_register_input_format(&x##_demuxer);}

#define REGISTER_MUXDEMUX(X,x) REGISTER_MUXER(X,x); REGISTER_DEMUXER(X,x)


void av_register_all(void)
{
    /* 省略部分代码 */
    /* protocols */
    REGISTER_MUXDEMUX (H264, h264);
    /* 省略部分代码 */
}


把注册格式函数也贴出来吧:

/** head of registered input format linked list */
AVInputFormat *first_iformat = NULL;
/** head of registered output format linked list */
AVOutputFormat *first_oformat = NULL;


void av_register_input_format(AVInputFormat*format)
{
    AVInputFormat **p;
    p = &first_iformat;
    while (*p!= NULL) p =&(*p)->next;
    *p = format;
    format->next= NULL;
}

void av_register_output_format(AVOutputFormat*format)
{
    AVOutputFormat **p;
    p = &first_oformat;
    while (*p!= NULL) p =&(*p)->next;
    *p = format;
    format->next= NULL;
}


http://blogold.chinaunix.net/u3/104564/showart_2369231.html


调用av_open_input_file(&pFormatCtx, is->filename,NULL, 0,NULL)函数打开输入的文件.

1. 分析一下函数原型:
int av_open_input_file(AVFormatContext**ic_ptr,// 输出参数: 格式上下文
                       const char *filename,// 文件名
                       AVInputFormat *fmt,// 输入的格式, 为NULL, 即未知
                       int buf_size,// 缓冲的大小, 为0
                       AVFormatParameters *ap);// 格式的参数, 为NULL


2. 初始化探测数据:
    AVProbeData probe_data, *pd = &probe_data;

    pd->filename= "";
    if (filename)
        pd->filename= filename;
    pd->buf= NULL;
    pd->buf_size= 0;

3. 探测输入的格式:
    if (!fmt){ // fmt == NULL, 成立
        fmt = av_probe_input_format(pd, 0);
    }

    进入av_probe_input_format函数:
AVInputFormat *av_probe_input_format(AVProbeData*pd, int is_opened) {
    int score=0;
    return av_probe_input_format2(pd, is_opened,&score);
}

    进入av_probe_input_format2函数:
AVInputFormat *av_probe_input_format2(AVProbeData*pd, int is_opened, int *score_max)
{
    AVInputFormat *fmt1,*fmt;
    int score;

    fmt = NULL;
    for(fmt1= first_iformat; fmt1!= NULL; fmt1 = fmt1->next){
        if (!is_opened== !(fmt1->flags& AVFMT_NOFILE))// is_opened == 0, fmt1->flags 没有设置 AVFMT_NOFILE 标志时成立
            continue;
    /* 省略部分代码 */
}

    见libavformat/raw.c文件:
AVInputFormat h264_demuxer = {
    "h264",
    NULL_IF_CONFIG_SMALL("raw H.264 video format"),
    0,
    h264_probe,
    video_read_header,
    ff_raw_read_partial_packet,
    .flags= AVFMT_GENERIC_INDEX,
    .extensions ="h26l,h264,264",//FIXME remove after writing mpeg4_probe
    .value = CODEC_ID_H264,
};
    由于 h264_demuxer.flags == AVFMT_GENERIC_INDEX, 所以上面成立,continue, 返回的 AVInputFormat 指针为NULL, 探测不成功.

1. 打开文件:

if (!fmt || !(fmt->flags & AVFMT_NOFILE)) { 

因 fmt == NULL, 上面成立, 再看下面的代码:

        ByteIOContext*pb = NULL; // 字节IO上下文

        if ((err=url_fopen(&pb, filename, URL_RDONLY))< 0) { // 只读方式打开输入的文件
            goto fail;
        }
        if (buf_size> 0) { // 因 buf_size == 0, 不成立
            url_setbufsize(pb, buf_size);

        }


进入url_fopen函数:

int url_fopen(ByteIOContext**s,// 输出参数: 字节IO上下文
              const char *filename,// 文件名
              int flags)// 标志
{
    URLContext *h;// URL(统一资源定位)上下文
    int err;

    err = url_open(&h, filename, flags);// 打开URL
    if (err< 0)
        return err;
    err = url_fdopen(s, h);// 用URL上下文打开字节IO上下文
    if (err< 0) {
        url_close(h);
        return err;
    }
    return 0;
}


进入url_open函数:

int url_open(URLContext**puc, // 输出参数: URL上下文

constchar *filename, // 文件名

int flags) // 标志

{
    URLProtocol *up;
    const char*p;
    char proto_str[128],*q;

    // 提取协议
    p = filename;
    q = proto_str;
    while (*p!= '\0' &&*p !=':') { // 未结束, 并未遇到分隔符':'
        if (!isalpha(*p))// 如果不是英文字母
            goto file_proto;
        if ((q- proto_str)< sizeof(proto_str)- 1)
            *q++= *p;// 记录协议字符串
        p++;
    }

    if (*p== '\0' || is_dos_path(filename)){ // 如果上面是因为结束而跳出, 或且
文件名是DOS路径

    file_proto:
        strcpy(proto_str,"file");// 文件协议
    } else {
        *q ='\0'; // 追加结束符
    }

    up = first_protocol;
    while (up!= NULL) {
        if (!strcmp(proto_str, up->name))// 协议匹配
            return url_open_protocol (puc, up, filename, flags);// 用这个协议打开URL
        up = up->next;
    }
    *puc = NULL;
    return AVERROR(ENOENT);
}


进入url_open_protocol函数:

int url_open_protocol(URLContext **puc, // 输出参数: URL上下文

struct URLProtocol*up, // URL协议

constchar *filename, // 文件名

int flags// 标志
{
    URLContext *uc;
    int err;

    // 网络初始化
#if CONFIG_NETWORK
    if (!ff_network_init())
        return AVERROR(EIO);
#endif

    // 分配URL上下文并加上文件名的存储空间
    uc = av_mallocz(sizeof(URLContext)+ strlen(filename)+ 1);
    if (!uc){
        err = AVERROR(ENOMEM);
        goto fail;
    }

    // 初始化URL上下文
#if LIBAVFORMAT_VERSION_MAJOR>= 53
    uc->av_class= &urlcontext_class;
#endif

    // 记录文件名
    uc->filename= (char*) &uc[1];
    strcpy(uc->filename, filename);
 
    uc->prot= up
// URL协议

    uc->flags= flags// 标志
    uc->is_streamed= 0// 默认不是流, 可以在up->url_open函数里修改
    uc->max_packet_size= 0; // 
包最大多大, 默认为0,可以在up->url_open函数里修改
    // 打开URL
    err = up->url_open(uc, filename, flags);
    if (err< 0) {
        av_free(uc);
        goto fail;
    }

 

    if((flags & (URL_WRONLY | URL_RDWR)// 如果以可写方式打开

       ||!strcmp(up->name,"file")// 或且是文件协议

        // 如果不是流并且不可以url_seek

        if(!uc->is_streamed&& url_seek(uc, 0,SEEK_SET) < 0)
            uc->is_streamed= 1;// 强制为流

    // 输出
参数: URL上下文

    *puc= uc;
    return 0;
 fail:
    *puc = NULL;
#if CONFIG_NETWORK
    ff_network_close();
#endif
    return err;
}


先来看看url_get_max_packet_size函数

int url_get_max_packet_size(URLContext *h)
{
    return h->max_packet_size; // 包最大多大, 被上面初始化为0
}


进入url_fdopen函数:

int url_fdopen(

ByteIOContext**s,// 输出参数: 字节IO上下文

URLContext*h) // URL上下文
{
    uint8_t *buffer;
    int buffer_size, max_packet_size;

    max_packet_size = url_get_max_packet_size(h);
    if (max_packet_size){
        buffer_size = max_packet_size;
    } else {
        buffer_size = IO_BUFFER_SIZE;// 缓冲大小为IO_BUFFER_SIZE
    }
    buffer = av_malloc(buffer_size);// 分配缓冲
    if (!buffer)
        return AVERROR(ENOMEM);

    *s = av_mallocz(sizeof(ByteIOContext));// 分配字节IO上下文

    if(!*s){
        av_free(buffer);
        return AVERROR(ENOMEM);
    }

    if (init_put_byte(*s, buffer, buffer_size,
                      (h->flags& URL_WRONLY || h->flags& URL_RDWR), h,
                      url_read, url_write, url_seek)< 0) {
        av_free(buffer);
        av_freep(s);
        return AVERROR(EIO);
    }
    (*s)->is_streamed= h->is_streamed// 是否为流
    (*s)->max_packet_size= max_packet_size// 包最大多大
    if(h->prot){
        (*s)->read_pause= (int(*)(void*, int))h->prot->url_read_pause;// 读暂停函数
        (*s)->read_seek= (int64_t(*)(void*, int, int64_t,int))h->prot->url_read_seek;// 读seek函数
    }
    return 0;
}


进入init_put_byte函数:

int init_put_byte(ByteIOContext*s, // 字节IO上下文
                  unsigned char *buffer,// 缓冲
                  int buffer_size,// 缓冲的大小
                  int write_flag,// 写标志
                  void *opaque, // URL上下文
                  int (*read_packet)(void*opaque, uint8_t *buf,int buf_size),// 读包
                  int (*write_packet)(void*opaque, uint8_t *buf,int buf_size),// 写包
                  int64_t (*seek)(void*opaque, int64_t offset, int whence))// 调整文件指针
{
    s->buffer= buffer;
    s->buffer_size= buffer_size;
    s->buf_ptr= buffer;
    s->opaque= opaque;
    url_resetbuf(s, write_flag? URL_WRONLY : URL_RDONLY);
    s->write_packet= write_packet;
    s->read_packet= read_packet;
    s->seek= seek;
    s->pos = 0;
    s->must_flush= 0;
    s->eof_reached= 0;
    s->error= 0;
    s->is_streamed= 0;
    s->max_packet_size= 0;
    s->update_checksum=NULL;
    if(!read_packet&& !write_flag){
        s->pos= buffer_size;
        s->buf_end= s->buffer+ buffer_size;
    }
    s->read_pause= NULL;
    s->read_seek= NULL;
    return 0;
}

void*logctx= ap&& ap->prealloced_context? *ic_ptr : NULL;// 因为 ap == NULL, 所以 logctx 也 == NULL.

    if (!fmt&& (err = ff_probe_input_buffer(&pb,&fmt, filename, logctx, 0,
        logctx ? (*ic_ptr)->probesize: 0))< 0) {
        goto fail;
    }
    // fmt == NULL 时才执行 ff_probe_input_buffer 函数, 因为 fmt 就等于NULL, 成立.


ff_probe_input_buffer函数的原型:

int ff_probe_input_buffer(ByteIOContext**pb,// 字节IO上下文, 执行url_fopen得到的
                          AVInputFormat **fmt,// 输出参数: 输入的格式
                          const char *filename,// 文件名
                          void *logctx, // NULL
                          unsigned int offset, // 0
                          unsigned int max_probe_size
 // 0


关键的代码片断:

        /* 读待探测的数据 */
        buf = av_realloc(buf, probe_size+ AVPROBE_PADDING_SIZE);
        if ((ret= get_buffer(*pb, buf+ buf_offset, probe_size- buf_offset))< 0) {
            /* fail if error was not end of file, otherwise, lower score */
            if (ret!= AVERROR_EOF){
                av_free(buf);
                return ret;
            }
            score = 0;
            ret = 0;/* error was end of file, nothing read */
        }
        pd.buf_size += ret;
        pd.buf =&buf[offset];

        memset(pd.buf+ pd.buf_size, 0, AVPROBE_PADDING_SIZE);

        /* 猜测文件格式 */
        *fmt = av_probe_input_format2(&pd, 1,&score);


get_buffer函数, 有两处比较关键:

int get_buffer(ByteIOContext*s, unsigned char *buf, int size);
{

        /* 省略部分代码 */
        /* 读包 */
        if(s->read_packet)
           len = s->read_packet(s->opaque, buf, size);


        /* 省略部分代码 */

        /* 填充缓冲 */

        fill_buffer(s);

        /* 省略部分代码 */

}


fill_buffer函数, 有一处比较关键:

staticvoid fill_buffer(ByteIOContext*s)
{
        /* 省略部分代码 */
        /* 读包 */
        if(s->read_packet)
           len = s->read_packet(s->opaque, dst, len);

        /* 省略部分代码 */
}


好了, 到第二次探测输入格式的地方了:

*fmt= av_probe_input_format2(&pd, 1,&score);


进入av_probe_input_format2函数:

AVInputFormat*av_probe_input_format2(AVProbeData*pd, int is_opened, int *score_max)
{
    AVInputFormat *fmt1,*fmt;
    int score;

    fmt = NULL;
    for(fmt1= first_iformat; fmt1!= NULL; fmt1 = fmt1->next){
        if (!is_opened== !(fmt1->flags& AVFMT_NOFILE))
            continue;

/* 这次 is_opened == 1, fmt1->flags设置AVFMT_NOFILE标志才时成立 */

/* 由于 h264_demuxer.flags == AVFMT_GENERIC_INDEX, 所以上面不成立, 继续执行 */
        score = 0;
        if (fmt1->read_probe){
            score = fmt1->read_probe(pd)/* 调用h264_demuxer.h264_probe */
        } elseif (fmt1->extensions){
            if (av_match_ext(pd->filename, fmt1->extensions))/* 文件名和格式扩展名的匹配 */

/* h264_demuxer.extensions = "h26l,h264,264" */
                score = 50;
            }
        }
        if (score> *score_max){
            *score_max = score;
            fmt = fmt1;
        }elseif (score == *score_max)
            fmt = NULL;
    }
    return fmt;
}


av_match_ext函数:

int av_match_ext(constchar *filename,const char *extensions)
{
    const char*ext, *p;
    char ext1[32],*q;

    if(!filename)
        return 0;

    ext = strrchr(filename,'.');
    if (ext){
        ext++;
        p = extensions;
        for(;;){
            q = ext1;
            while (*p !='\0' &&*p !=',' && q-ext1<sizeof(ext1)-1)
                *q++= *p++;
            *q ='\0';
            if (!strcasecmp(ext1, ext))
                return 1;
            if (*p== '\0')
                break;
            p++;
        }
    }
    return 0;
}


总算探测到输入格式了.

err = av_open_input_stream(ic_ptr, pb, filename, fmt, ap);



int av_open_input_stream(

AVFormatContext**ic_ptr// 输出参数: 格式上下文
ByteIOContext *pb, 
// 字节IO上下文

constchar *filename// 文件名
AVInputFormat *fmt, 
// 输入的格式

AVFormatParameters*ap// 格式参数, 调用时为NULL
{
    int err;
    AVFormatContext *ic;
    AVFormatParameters default_ap;

     // 使用缺省的格式参数
    if(!ap){
        ap=&default_ap;
        memset(ap, 0,sizeof(default_ap));
    }

    if(!ap->prealloced_context)
        ic = avformat_alloc_context()// 分配格式上下文
    else
        ic = *ic_ptr;
    if (!ic){
        err = AVERROR(ENOMEM);
        goto fail;
    }

    // 初始化格式上下文
    ic->iformat= fmt// 格式
    ic->pb = pb// 字节IO上下文
    ic->duration= AV_NOPTS_VALUE;
    ic->start_time= AV_NOPTS_VALUE;
    av_strlcpy(ic->filename, filename,sizeof(ic->filename))// 文件名

    /* 分配私有数据 */
    if (fmt->priv_data_size> 0) {
        ic->priv_data= av_mallocz(fmt->priv_data_size);
        if (!ic->priv_data){
            err = AVERROR(ENOMEM);
            goto fail;
        }
    } else {
        ic->priv_data= NULL;
    }

    // 读首部
    if (ic->iformat->read_header){
        err = ic->iformat->read_header(ic, ap);
        if (err< 0)
            goto fail;
    }

    // 获得数据偏移
    if (pb && !ic->data_offset)
        ic->data_offset= url_ftell(ic->pb);

#if LIBAVFORMAT_VERSION_MAJOR< 53
    ff_metadata_demux_compat(ic);
#endif

    // 原始的包缓冲剩余的大小
    ic->raw_packet_buffer_remaining_size= RAW_PACKET_BUFFER_SIZE;

    // 输出参数: 格式上下文

    *ic_ptr= ic;
    return 0;

}


具体请参看
ffmpeg分析系列之三(输入输出格式)

格式上下文结构:

typedef struct AVFormatContext {
    const AVClass *av_class; /**< Set by avformat_alloc_context. */

    // 省略部分内容
}


AV类结构:

typedef struct {
    /**
     * The name of the class; usually it is the same name as the
     * context structure type to which the AVClass is associated.
     */

    const char* class_name;

    /**
     * A pointer to a function which returns the name of a context
     * instance ctx associated with the class.
     */

    const char* (*item_name)(void* ctx);

    /**
     * a pointer to the first option specified in the class if any or NULL
     *
     * @see av_set_default_options()
     */

    const struct AVOption *option;

    /**
     * LIBAVUTIL_VERSION with which this structure was created.
     * This is used to allow fields to be added without requiring major
     * version bumps everywhere.
     */


    int version;
} AVClass;



进入avformat_alloc_context函数, 分配格式上下文:

AVFormatContext*avformat_alloc_context(void)
{
    AVFormatContext *ic;
    ic = av_malloc(sizeof(AVFormatContext));
    if (!ic)return ic;
    avformat_get_context_defaults(ic);
    ic->av_class= &av_format_context_class;
    return ic;
}


staticconst AVClass av_format_context_class = { "AVFormatContext", format_to_name, options, LIBAVUTIL_VERSION_INT};


进入avformat_get_context_defaults函数, 格式获得缺省上下文:

staticvoid avformat_get_context_defaults(AVFormatContext*s)
{
    memset(s, 0,sizeof(AVFormatContext));
    s->av_class= &av_format_context_class;
    av_opt_set_defaults(s);
}


av_opt_set_defaults函数就不分析了. 

下面继续分析:

err = ic->iformat->read_header(ic, ap)


以输入格式为libavformat/raw.c下的h264_demuxer为例:

AVInputFormat h264_demuxer = {
    "h264",
    NULL_IF_CONFIG_SMALL("raw H.264 video format"),
    0,
    h264_probe,
    video_read_header,
    ff_raw_read_partial_packet,
    .flags= AVFMT_GENERIC_INDEX,
    .extensions = "h26l,h264,264", //FIXME remove after writing mpeg4_probe

    .value = CODEC_ID_H264,
};



会调用video_read_header函数:

staticint video_read_header(AVFormatContext*s,
                             AVFormatParameters *ap)
{
    AVStream *st;

    st = av_new_stream(s, 0)// 格式上下文增加一个流
    if (!st)
        return AVERROR(ENOMEM);


     // 初始化流
    st->codec->codec_type= AVMEDIA_TYPE_VIDEO// 编码编码器类型
    st->codec->codec_id= s->iformat->value// 为 CODEC_ID_H264
    st->need_parsing= AVSTREAM_PARSE_FULL// 需要全分析

    /* for MJPEG, specify frame rate */
    /* for MPEG-4 specify it, too (most MPEG-4 streams do not have the fixed_vop_rate set ...)*/
    if (ap->time_base.num){

        st->codec->time_base= ap->time_base;
    } else if ( st->codec->codec_id== CODEC_ID_MJPEG||
                st->codec->codec_id== CODEC_ID_MPEG4||
                st->codec->codec_id== CODEC_ID_DIRAC||
                st->codec->codec_id== CODEC_ID_DNXHD||
                st->codec->codec_id== CODEC_ID_H264){
        st->codec->time_base=(AVRational){1,25}// 设置时基
    }
    av_set_pts_info(st, 64, 1, 1200000)// 设置PTS(显示时间截)信息

    return 0;
}


进入av_new_stream函数:

AVStream*av_new_stream(AVFormatContext*s, int id)
{
    AVStream *st;
    int i;

    // 格式上下文不能太多流
    if (s->nb_streams>= MAX_STREAMS)
        return NULL;

    // 分配一个流
    st = av_mallocz(sizeof(AVStream));
    if (!st)
        return NULL;

    // 分配解码器上下文
    st->codec= avcodec_alloc_context();
    if (s->iformat){
        /* no default bitrate if decoding */
        st->codec->bit_rate= 0;
    }
    st->index= s->nb_streams// 流索引
    st->id = id// ID, 为0
    st->start_time= AV_NOPTS_VALUE// 开始时间
    st->duration= AV_NOPTS_VALUE;
        /* we set the current DTS to 0 so that formats without any timestamps
           but durations get some timestamps, formats with some unknown
           timestamps have their first few packets buffered and the
           timestamps corrected before they are returned to the user */

    st->cur_dts= 0// 当前的解码时间截
    st->first_dts= AV_NOPTS_VALUE// 起始的解码时间截
    st->probe_packets= MAX_PROBE_PACKETS// 探测的最大包数

    /* default pts setting is MPEG-like */

    av_set_pts_info(st, 33, 1, 90000)// 设置PTS显示时间截信息
    st->last_IP_pts= AV_NOPTS_VALUE
    for(i=0; i<MAX_REORDER_DELAY+1; i++)
        st->pts_buffer[i]= AV_NOPTS_VALUE;
    st->reference_dts= AV_NOPTS_VALUE;

    st->sample_aspect_ratio= (AVRational){0,1};

    s->streams[s->nb_streams++]= st// 记录流, 同时流数加一
    return st;
}


分配编码解码器上下文:
 

staticconst AVClass av_codec_context_class = { "AVCodecContext", context_to_name, options, LIBAVUTIL_VERSION_INT};


void avcodec_get_context_defaults2(AVCodecContext*s, enum AVMediaType codec_type){
    int flags=0;
    memset(s, 0,sizeof(AVCodecContext));

    s->av_class=&av_codec_context_class;

    s->codec_type= codec_type;
    if(codec_type== AVMEDIA_TYPE_AUDIO)
        flags= AV_OPT_FLAG_AUDIO_PARAM;
    else if(codec_type== AVMEDIA_TYPE_VIDEO)
        flags= AV_OPT_FLAG_VIDEO_PARAM;
    else if(codec_type== AVMEDIA_TYPE_SUBTITLE)
        flags= AV_OPT_FLAG_SUBTITLE_PARAM;
    av_opt_set_defaults2(s, flags, flags);

    s->time_base=(AVRational){0,1};
    s->get_buffer= avcodec_default_get_buffer;
    s->release_buffer= avcodec_default_release_buffer;
    s->get_format= avcodec_default_get_format;
    s->execute= avcodec_default_execute;
    s->execute2= avcodec_default_execute2;
    s->sample_aspect_ratio=(AVRational){0,1};
    s->pix_fmt= PIX_FMT_NONE;
    s->sample_fmt= SAMPLE_FMT_NONE;

    s->palctrl= NULL;
    s->reget_buffer= avcodec_default_reget_buffer;
    s->reordered_opaque= AV_NOPTS_VALUE;
}

AVCodecContext *avcodec_alloc_context2(enum AVMediaType codec_type){
    AVCodecContext *avctx= av_malloc(sizeof(AVCodecContext));

    if(avctx==NULL)return NULL;

    avcodec_get_context_defaults2(avctx, codec_type);

    return avctx;
}

void avcodec_get_context_defaults(AVCodecContext*s){
    avcodec_get_context_defaults2(s, AVMEDIA_TYPE_UNKNOWN);
}

AVCodecContext *avcodec_alloc_context(void){
    return avcodec_alloc_context2(AVMEDIA_TYPE_UNKNOWN);
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/454947.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux kernel的中断子系统之(二):IRQ Domain介绍

返回目录&#xff1a;《ARM-Linux中断系统》。 总结&#xff1a;一、二概述了软硬件不同角度的IRQ Number和HW Interrupt ID&#xff0c;这就需要他们之间架个桥梁。 三介绍了架设这种桥梁的几种方式&#xff1a;Linear、Radix Tree和no map。 四介绍了两种基础数据结构描述中断…

mysql返回yyyy mm dd_怎么把取出mysql数据库中的yyyy-MM-dd日期转成yyyy年MM月dd日格式...

您好&#xff0c;通过两个个步骤可以完成转换&#xff1a;第一步&#xff1a;日期处理可以在模板数据集中通过sql语句转换&#xff0c;转换方式方式如下&#xff1a;SELECT DATE_FORMAT(NOW(),%Y) YEAR输出结果&#xff1a;2018SELECT DATE_F…

关于JS的时间控制

关于JS的时间控制实现动态效果及实例操作 <script>BOM //Bowers Object Model 浏览器对象模型setTimeout() // 延迟执行一次setInterval() // 间隔执行var a 300;window.setTimeout(abc(a),3000); // 自定义函数赋值function abc(i){alert(i);}//setInterv…

感动一生的几句话

为什么80%的码农都做不了架构师&#xff1f;>>> 很多东西就掌握在我们手中&#xff1a; 比如快乐&#xff0c;你不快乐&#xff0c;谁会同情你的悲伤&#xff1b; 比如坚强&#xff0c;你不坚强&#xff0c;谁会怜悯你的懦弱&#xff1b; 比如努力&#xff0c;你不…

mysql5.6 memcached_MySQL 5.6 安装配置InnoDB memcached Plugin

准备工作, 安装libmemached包&#xff0c;提供一些memcat/cp/dump命令&#xff0c;方便测试。# yum install libmemcached.x86_64 -y1. Setup required tables.mysql> source MYSQL_HOME/share/innodb_memcached_config.sqlQuery OK, 1 row affected (0.00 sec)Database cha…

Java 监听器,国际化

1. 监听器 1.1 概述 监听器&#xff1a; 主要是用来监听特定对象的创建或销毁、属性的变化的&#xff01; 是一个实现特定接口的普通java类&#xff01; 对象&#xff1a; 自己创建自己用 (不用监听) 别人创建自己用 &#xff08;需要监听&#xff09; Servlet中哪些对象需要监…

patator mysql 字典_利用patator进行子域名爆破

前言:原来朋友写的一个子域名爆破工具挺好用,这前几天API接口关了.痛苦万分.自己也写了一个类似的但是不咋稳定.特地google找了下 找到一款patator.效果和速度还是不错的。knock的速度真心受不了啊patator是由Python写的 不用安装下载即可.下载地址&#xff1a;http://code.goo…

div 超出高度滚动条,超出宽度点点点

超出高度滚动条style"width:230px; height: 180px; overflow: auto;"超出宽度点点点style"width: 220px; overflow: hidden; white-space:nowrap; text-overflow:ellipsis;"转载于:https://www.cnblogs.com/thinkingthigh/p/7603703.html

mp4(H264容器)的详细文件格式分析

十六进制码流分析&#xff1a; ftyp Box 00 00 00 1C: size ,28,表示此BOX有28个字节&#xff0c;表示长度的四个字节也计算在内。以下同 66 74 79 70: type,表示BOX TYPE,此处为ftyp 6D 70 34 32: 可能是兼容的格式信息&#xff0c;/mp42 00 00 00…

hdu 5925 搜索

题意&#xff1a;一个图&#xff0c;n个障碍&#xff0c;求联通块 思路&#xff1a; 图很大&#xff0c;障碍物很少。把联通的障碍物块抠出来&#xff0c;然后暴力。 代码&#xff1a; #include<bits/stdc.h> using namespace std; #define MEM(a,b) memset(a,b,sizeof(a…

分析数据库CitusDB:提供弹性计算能力

本文讲的是分析数据库CitusDB&#xff1a;提供弹性计算能力,企业数据库市场很庞大&#xff0c;在这个领域既有Oracle这样行家&#xff0c;也有IBM(DB2)和微软(SQL Server)这样的跨界巨头。它们都与中小企业常用到的开源数据库MySQL一样&#xff0c;都属于传统关系型数据库。似乎…

mysql不能创建innodb类型表_MYSQL have_innodb DISABLED无法创建innodb类型的表

今天在一台MYSQL服务器上发现&#xff0c;明明用了engineinnodb创建的表&#xff0c;结果创建出来却成了myisam的表。再看show variables like %innodb%;have_innodb 成了DISABLED。经过一番试验&#xff0c;发现是我关闭数据库后&#xff0c;直接删除ibdata1文件造成的。删除该…

[bzoj1059]矩阵游戏

虽然是一道水难题&#xff0c;但是我这种蒟蒻还是要讲一讲的。 Description 小Q是一个非常聪明的孩子&#xff0c;除了国际象棋&#xff0c;他还很喜欢玩一个电脑益智游戏——矩阵游戏。矩阵游戏在一个N*N黑白方阵进行&#xff08;如同国际象棋一般&#xff0c;只是颜色是随意的…

H264 RTP头分析

h264 RTP头解析流程 结合NALDecoder.c分析 协议分析 &#xff1a;每一个RTP数据报都由头部&#xff08;Header&#xff09;和负载&#xff08;Payload&#xff09;两个部分组成&#xff0c;其中头部前 12 个字节的含义是固定的&#xff0c;而负载则可以是音频或者视频数据。 一…

golang mysql 插入_Mysql学习(一)添加一个新的用户并用golang操作Mysql

Mysql添加一个新的用户并赋予权限添加一个自己的用户到mysql首先我们需要先用root用户登录mysql&#xff0c;但是刚安装完没有密码&#xff0c;我们先跳过密码ailumiyanaailumiyana:~/Git_Project/Go_Test$ sudo mysqld_safe --skip-grant-tables2019-01-07T01:35:51.559420Z m…

云计算构建基石之Hyper-V:虚拟机管理

本文讲的是云计算构建基石之Hyper-V:虚拟机管理,作为云计算的重要基石&#xff0c;虚拟化技术的好坏起着关键作用。Hyper-V作为微软重要的虚拟化解决技术&#xff0c;在微软云计算构建解决方案中&#xff0c;更是关键至关键&#xff0c;基础之基础。在本系列文章中&#xff0c;…

Delphi语言最好的JSON代码库 mORMot学习笔记1

mORMot没有控件安装&#xff0c;直接添加到lib路径,工程中直接添加syncommons&#xff0c;syndb等到uses里 --------------------------------------------------------- 在进行网络编程中需要JSON对象的构建与解析&#xff0c;这个Delphi XE自带&#xff1a;{$IF CompilerVers…

3GP文件格式分析

1. 概述现在很多智能手机都支持多媒体功能&#xff0c;特别是音频和视频播放功能&#xff0c;而3GP文件格式是手机端普遍支持的视频文件格式。目前很多手机都支持h263视频编码格式的视频文件播放&#xff0c;还有些手机支持h264。音频文件格式普遍支持amr&#xff0c;有些手…

mysql group concat_MySQL 的 GROUP_CONCAT 函数详解

GROUP_CONCAT(expr) 函数会从 expr 中连接所有非 NULL 的字符串。如果没有非 NULL 的字符串&#xff0c;那么它就会返回 NULL。语法如下&#xff1a;GROUP_CONCAT 语法规则它在递归查询中用的比较多&#xff0c;但要使用好它并不容易。所以让我们一起来看看吧&#xff1a;假设有…

ORACLE数据库 常用命令和Sql常用语句

ORACLE 账号相关 如何获取表及权限 1.COPY表空间backup scottexp登录管理员账号system2.创建用户 create user han identified(认证) by mima default tablespace users&#xff08;默认的表空间&#xff09; quota&#xff08;配额&#xff09;10M on users;创建账号分配权限g…