ffmpeg 学习: 003-关键函数介绍

背景

了解一些关键函数对于开发的帮助比较大。

avformat_open_input

FFMPEG 打开媒体的过程开始于 avformat_open_input,因此该函数的重要性不可忽视。

在该函数中,FFMPEG 完成了:
1.输入输出结构体 AVIOContext 的初始化;
2.输入数据的协议(例如 RTMP,或者 file)的识别(通过一套评分机制):

A 判断文件名的后缀
B 读取文件头的 数据进行比对;

使用获得最高分的文件协议对应的 URLProtocol,通过函数指针的方式,与 FFMPEG 连接(非专业用词);
剩下的就是调用该 URLProtocol 的函数进行 open,read 等操作了

以下是通过 eclipse+MinGW 调试 FFMPEG 源代码获得的函数调用关系图 :

可见最终都调用了 URLProtocol 结构体中的函数指针。
URLProtocol 结构是一大堆函数指针的集合(avio.h 文件),数据结构 URLProtocol 的功能就是完成各种输入协议的读写等操作。

但输入协议种类繁多,它是怎样做到“大一统”的呢?
原来,每个具体的输入协议都有自己对应的 URLProtocol。
比如 file 协议(FFMPEG 把文件也当做一种特殊的协议)(*file.c 文件)

URLProtocol ff_pipe_protocol = {
      .name                = "pipe",
      .url_open            = pipe_open,
      .url_read            = file_read,
      .url_write           = file_write,
      .url_get_file_handle = file_get_handle,
      .url_check           = file_check,   
}

或者 rtmp 协议(此处使用了 librtmp)( librtmp.c 文件)

URLProtocol ff_rtmp_protocol = {
      .name                = "rtmp",
      .url_open            = rtmp_open,
      .url_read            = rtmp_read,
      .url_write           = rtmp_write,
      .url_close           = rtmp_close,
      .url_read_pause      = rtmp_read_pause,
      .url_read_seek       = rtmp_read_seek,
      .url_get_file_handle = rtmp_get_file_handle,
      .priv_data_size      = sizeof(RTMP),
      .flags               = URL_PROTOCOL_FLAG_NETWORK,
}; 

可见它们把各自的函数指针都赋值给了 URLProtocol 结构体的函数指针。
因此 avformat_open_input 只需调用 url_open,url_read 这些函数就可以完成各种具体输入协议的 open,read 等操作了。

avcodec_register_all()

ffmpeg 注册复用器,编码器等的函数 av_register_all()。

该函数在所有基于 ffmpeg 的应用程序中几乎都是第一个被调用的。只有调用了该函数,才能使用复用器,编码器等。

void avcodec_register_all(void)
{
    static AVOnce control = AV_ONCE_INIT;

    ff_thread_once(&control, register_all);
}
...

static void register_all(void)
{
    /* hardware accelerators */
    REGISTER_HWACCEL(H263_VAAPI,        h263_vaapi);
    REGISTER_HWACCEL(H263_VIDEOTOOLBOX, h263_videotoolbox);
    REGISTER_HWACCEL(H264_CUVID,        h264_cuvid);
    REGISTER_HWACCEL(H264_D3D11VA,      h264_d3d11va);
    REGISTER_HWACCEL(H264_D3D11VA2,     h264_d3d11va2);
    REGISTER_HWACCEL(H264_DXVA2,        h264_dxva2);
    ...
}

可见解复用器注册都是用 REGISTER_DEMUXER (X,x)

例如: REGISTER_DEMUXER (AAC, aac)

可见复用器注册都是用 REGISTER_MUXER (X,x))

例如: REGISTER_MUXER (ADTS, adts)

既有解复用器又有复用器的话,可以用 REGISTER_MUXDEMUX (X,x));

例如: REGISTER_MUXDEMUX (AC3, ac3);

我们来看一下宏的定义,这里以解复用器为例:

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

注意:define 里面的##可能不太常见,它的含义就是拼接两个字符串,

比如 #define Conn(x,y) x##y 那么 int n = Conn(123,456); 结果就是 n=123456;

我们以 REGISTER_DEMUXER (AAC, aac)为例,则它等效于

extern AVInputFormat ff_aac_demuxer;    
if(CONFIG_AAC_DEMUXER) 
av_register_input_format(&ff_aac_demuxer);    

从上面这段代码我们可以看出,真正注册的函数是 av_register_input_format(&ff_aac_demuxer),查看一下 av_register_input_format()的代码:

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

// libavformat/allformats.c
void av_register_input_format(AVInputFormat *format)
{
    ff_thread_once(&av_format_next_init, av_format_init_next);
}

// libavutil/thread.h 根据不同的宏定义有不同的实现,但实际上都是为了让 routine 指针所指的函数只执行一次。
libavutil/thread.h:147:#define ff_thread_once(control, routine) pthread_once(control, routine)
libavutil/thread.h:162:static inline int ff_thread_once(char *control, void (*routine)(void))
  static inline int ff_thread_once(char *control, void (*routine)(void))
  {
      if (!*control) {
          routine();
          *control = 1;
      }
      return 0;
  }

实际上我并没有理解这段函数,因为这段函数其中没有使用到参数:format,我不知道ff_thread_once的内部到底执行了什么。但根据前人给出的结论:av_register_input_format()的含义,一句话概括就是:遍历链表并把当前的 Input Format 加到链 表的尾部,然后确定是不是已经初始化过了(initialized),如果没有,就调用 avcodec_register_all()注 册编解码器(这个先不分析),然后就是注册,注册,注册...直到完成所有注册。

av_read_frame()

ffmpeg 中的 av_read_frame()的作用是读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,需要先调用 av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码(例如 H.264 中 一帧压缩数据通常对应一个 NAL)。
通过 av_read_packet(***),读取一个包,需要说明的是此函数必须是包含整数帧的,不存在半帧的情况,以 ts 流为例,是读取一个完整的 PES 包(一个完整 pes 包包含若干视频或音频 es 包),读取完毕后,通过 av_parser_parse2(***) 分析出视频一帧(或音频若干帧),返回,下次进入循环的时候,如果上次的数据没有完全取完,则 st = s->cur_st;不会是 NULL,即再此进入 av_parser_parse2(***)流程,而不是下面的 av_read_packet(**)流程,这样就保证了,如果读取一次包含了 N 帧视频数据(以视频为例),则调用 av_read_frame(***)N 次都不会 去读数据,而是返回第一次读取的数据,直到全部解析完毕。

avcodec_decode_video2()

ffmpeg 中的 avcodec_decode_video2()的作用是解码一帧视频数据。输入一个压缩编码的结构体 AVPacket,输出一个 解码后的结构体 AVFrame。

transcode_init()

transcode_init()函数是在转换前做准备工作的.此处看一下它的真面目,不废话,看注释吧:

// 为转换过程做准备
static int transcode_init(void)
{
    int ret = 0, i, j, k;
    AVFormatContext *oc;
    OutputStream *ost;
    InputStream *ist;
    char error[1024] = {0};

    for (i = 0; i < nb_filtergraphs; i++) {
        FilterGraph *fg = filtergraphs[i];
        for (j = 0; j < fg->nb_outputs; j++) {
            OutputFilter *ofilter = fg->outputs[j];
            if (!ofilter->ost || ofilter->ost->source_index >= 0)
                continue;
            if (fg->nb_inputs != 1)
                continue;
            for (k = nb_input_streams-1; k >= 0 ; k--)
                if (fg->inputs[0]->ist == input_streams[k])
                    break;
            ofilter->ost->source_index = k;
        }
    }

    /* init framerate emulation */
    for (i = 0; i < nb_input_files; i++) {
        InputFile *ifile = input_files[i];
        if (ifile->rate_emu)
            for (j = 0; j < ifile->nb_streams; j++)
                input_streams[j + ifile->ist_index]->start = av_gettime_relative();
    }

    /* init input streams */
    for (i = 0; i < nb_input_streams; i++)
        if ((ret = init_input_stream(i, error, sizeof(error))) < 0) {
            for (i = 0; i < nb_output_streams; i++) {
                ost = output_streams[i];
                avcodec_close(ost->enc_ctx);
            }
            goto dump_format;
        }

    /* open each encoder */
    for (i = 0; i < nb_output_streams; i++) {
        // skip streams fed from filtergraphs until we have a frame for them
        if (output_streams[i]->filter)
            continue;

        ret = init_output_stream(output_streams[i], error, sizeof(error));
        if (ret < 0)
            goto dump_format;
    }

    /* discard unused programs */
    for (i = 0; i < nb_input_files; i++) {
        InputFile *ifile = input_files[i];
        for (j = 0; j < ifile->ctx->nb_programs; j++) {
            AVProgram *p = ifile->ctx->programs[j];
            int discard  = AVDISCARD_ALL;

            for (k = 0; k < p->nb_stream_indexes; k++)
                if (!input_streams[ifile->ist_index + p->stream_index[k]]->discard) {
                    discard = AVDISCARD_DEFAULT;
                    break;
                }
            p->discard = discard;
        }
    }

    /* write headers for files with no streams */
    for (i = 0; i < nb_output_files; i++) {
        oc = output_files[i]->ctx;
        if (oc->oformat->flags & AVFMT_NOSTREAMS && oc->nb_streams == 0) {
            ret = check_init_output_file(output_files[i], i);
            if (ret < 0)
                goto dump_format;
        }
    }

 dump_format:
    /* dump the stream mapping */
    av_log(NULL, AV_LOG_INFO, "Stream mapping:
");
    for (i = 0; i < nb_input_streams; i++) {
        ist = input_streams[i];

        for (j = 0; j < ist->nb_filters; j++) {
            if (!filtergraph_is_simple(ist->filters[j]->graph)) {
                av_log(NULL, AV_LOG_INFO, "  Stream #%d:%d (%s) -> %s",
                       ist->file_index, ist->st->index, ist->dec ? ist->dec->name : "?",
                       ist->filters[j]->name);
                if (nb_filtergraphs > 1)
                    av_log(NULL, AV_LOG_INFO, " (graph %d)", ist->filters[j]->graph->index);
                av_log(NULL, AV_LOG_INFO, "
");
            }
        }
    }

    for (i = 0; i < nb_output_streams; i++) {
        ost = output_streams[i];

        if (ost->attachment_filename) {
            /* an attached file */
            av_log(NULL, AV_LOG_INFO, "  File %s -> Stream #%d:%d
",
                   ost->attachment_filename, ost->file_index, ost->index);
            continue;
        }

        if (ost->filter && !filtergraph_is_simple(ost->filter->graph)) {
            /* output from a complex graph */
            av_log(NULL, AV_LOG_INFO, "  %s", ost->filter->name);
            if (nb_filtergraphs > 1)
                av_log(NULL, AV_LOG_INFO, " (graph %d)", ost->filter->graph->index);

            av_log(NULL, AV_LOG_INFO, " -> Stream #%d:%d (%s)
", ost->file_index,
                   ost->index, ost->enc ? ost->enc->name : "?");
            continue;
        }

        av_log(NULL, AV_LOG_INFO, "  Stream #%d:%d -> #%d:%d",
               input_streams[ost->source_index]->file_index,
               input_streams[ost->source_index]->st->index,
               ost->file_index,
               ost->index);
        if (ost->sync_ist != input_streams[ost->source_index])
            av_log(NULL, AV_LOG_INFO, " [sync #%d:%d]",
                   ost->sync_ist->file_index,
                   ost->sync_ist->st->index);
        if (ost->stream_copy)
            av_log(NULL, AV_LOG_INFO, " (copy)");
        else {
            const AVCodec *in_codec    = input_streams[ost->source_index]->dec;
            const AVCodec *out_codec   = ost->enc;
            const char *decoder_name   = "?";
            const char *in_codec_name  = "?";
            const char *encoder_name   = "?";
            const char *out_codec_name = "?";
            const AVCodecDescriptor *desc;

            if (in_codec) {
                decoder_name  = in_codec->name;
                desc = avcodec_descriptor_get(in_codec->id);
                if (desc)
                    in_codec_name = desc->name;
                if (!strcmp(decoder_name, in_codec_name))
                    decoder_name = "native";
            }

            if (out_codec) {
                encoder_name   = out_codec->name;
                desc = avcodec_descriptor_get(out_codec->id);
                if (desc)
                    out_codec_name = desc->name;
                if (!strcmp(encoder_name, out_codec_name))
                    encoder_name = "native";
            }

            av_log(NULL, AV_LOG_INFO, " (%s (%s) -> %s (%s))",
                   in_codec_name, decoder_name,
                   out_codec_name, encoder_name);
        }
        av_log(NULL, AV_LOG_INFO, "
");
    }

    if (ret) {
        av_log(NULL, AV_LOG_ERROR, "%s
", error);
        return ret;
    }

    atomic_store(&transcode_init_done, 1);

    return 0;
}

transcode()

static int transcode(
           OutputFile *output_files,//输出文件数组
           int nb_output_files,//输出文件的数量
           InputFile *input_files,//输入文件数组
           int nb_input_files)//输入文件的数量   
{
       int ret, i;
       AVFormatContext *is, *os;
       OutputStream *ost;
       InputStream *ist;
       uint8_t *no_packet; 
int no_packet_count = 0;       int64_t timer_start;       int key;          if (!(no_packet = av_mallocz(nb_input_files)))           exit_program(1);          //设置编码参数,打开所有输出流的编码器,打开所有输入流的解码器,写入所有输出文件的文件头,于是准备好了       ret = transcode_init(output_files, nb_output_files, input_files,nb_input_files);       if (ret < 0)           goto fail;          if (!using_stdin){           av_log(NULL, AV_LOG_INFO, "Press [q] to stop, [?] for help
");       }          timer_start = av_gettime();          //循环,直到收到系统信号才退出       for (; received_sigterm == 0;)       {           int file_index, ist_index;           AVPacket pkt;           int64_t ipts_min;           double opts_min;           int64_t cur_time = av_gettime();              ipts_min = INT64_MAX;           opts_min = 1e100;           /* if 'q' pressed, exits */           if (!using_stdin)           {               //先查看用户按下了什么键,跟据键做出相应的反应               static int64_t last_time;               if (received_nb_signals)                   break;               /* read_key() returns 0 on EOF */               if (cur_time - last_time >= 100000 && !run_as_daemon){                   key = read_key();                   last_time = cur_time;               }else{            }     

/* select the stream that we must read now by looking at the           smallest output pts */           //下面这个循环的目的是找一个最小的输出 pts(也就是离当前最近的)的输出流           file_index = -1;           for (i = 0; i < nb_output_streams; i++){               OutputFile *of;               int64_t ipts;               double opts;               ost = &output_streams[i];//循环每一个输出流               of = &output_files[ost->file_index];//输出流对应的输出文件               os = output_files[ost->file_index].ctx;//输出流对应的 FormatContext               ist = &input_streams[ost->source_index];//输出流对应的输入流                  if (ost->is_past_recording_time || //是否过了录制时间?(可能用户指定了一个录制时间段)                       no_packet[ist->file_index]|| //对应的输入流这个时间内没有数据?                       (os->pb && avio_tell(os->pb) >= of->limit_filesize))//是否超出了录制范围(也是用户指定的)                   continue;//是的,符合上面某一条,那么再看下一个输出流吧                  //判断当前输入流所在的文件是否可以使用(我也不很明白)               opts = ost->st->pts.val * av_q2d(ost->st->time_base);               ipts = ist->pts;               if (!input_files[ist->file_index].eof_reached)   {                   if (ipts < ipts_min){                       //每找到一个 pts 更小的输入流就记录下来,这样循环完所有的输出流时就找到了                       //pts 最小的输入流,及输入文件的序号                       ipts_min = ipts;                       if (input_sync)                           file_index = ist->file_index;                   }                   if (opts < opts_min){                       opts_min = opts;                       if (!input_sync)                           file_index = ist->file_index;                   }               }                  //难道下面这句话的意思是:如果当前的输出流已接收的帧数,超出用户指定的输出最大帧数时,               //则当前输出流所属的输出文件对应的所有输出流,都算超过了录像时间?               if (ost->frame_number >= ost->max_frames){                   int j;                   for (j = 0; j < of->ctx->nb_streams; j++)                       output_streams[of->ost_index + j].is_past_recording_time =   1;                   continue;   

     }           }           /* if none, if is finished */           if (file_index < 0)  {               //如果没有找到合适的输入文件               if (no_packet_count){                   //如果是因为有的输入文件暂时得不到数据,则还不算是结束                   no_packet_count = 0;                   memset(no_packet, 0, nb_input_files);                   usleep(10000);                   continue;               }               //全部转换完成了,跳出大循环               break;           }              //从找到的输入文件中读出一帧(可能是音频也可能是视频),并放到 fifo 队列中           is = input_files[file_index].ctx;           ret = av_read_frame(is, &pkt);           if (ret == AVERROR(EAGAIN)) {               //此时发生了暂时没数据的情况               no_packet[file_index] = 1;               no_packet_count++;               continue;           }              //下文判断是否有输入文件到最后了           if (ret < 0){               input_files[file_index].eof_reached = 1;               if (opt_shortest)                   break;               else                   continue;           }              no_packet_count = 0;           memset(no_packet, 0, nb_input_files);              if (do_pkt_dump){               av_pkt_dump_log2(NULL, AV_LOG_DEBUG, &pkt, do_hex_dump,                       is->streams[pkt.stream_index]);           }           /* the following test is needed in case new streams appear  

 dynamically in stream : we ignore them */           //如果在输入文件中遇到一个忽然冒出的流,那么我们不鸟它           if (pkt.stream_index >= input_files[file_index].nb_streams)               goto discard_packet;              //取得当前获得的帧对应的输入流           ist_index = input_files[file_index].ist_index + pkt.stream_index;           ist = &input_streams[ist_index];           if (ist->discard)               goto discard_packet;              //重新鼓捣一下帧的时间戳           if (pkt.dts != AV_NOPTS_VALUE)               pkt.dts += av_rescale_q(input_files[ist->file_index].ts_offset,                       AV_TIME_BASE_Q, ist->st->time_base);           if (pkt.pts != AV_NOPTS_VALUE)               pkt.pts += av_rescale_q(input_files[ist->file_index].ts_offset,                       AV_TIME_BASE_Q, ist->st->time_base);              if (pkt.pts != AV_NOPTS_VALUE)               pkt.pts *= ist->ts_scale;           if (pkt.dts != AV_NOPTS_VALUE)               pkt.dts *= ist->ts_scale;              if (pkt.dts != AV_NOPTS_VALUE && ist->next_pts != AV_NOPTS_VALUE                   && (is->iformat->flags & AVFMT_TS_DISCONT))           {               int64_t pkt_dts = av_rescale_q(pkt.dts, ist->st->time_base,                       AV_TIME_BASE_Q);               int64_t delta = pkt_dts - ist->next_pts;               if ((delta < -1LL * dts_delta_threshold * AV_TIME_BASE                       || (delta > 1LL * dts_delta_threshold * AV_TIME_BASE                               && ist->st->codec->codec_type                                       != AVMEDIA_TYPE_SUBTITLE)                       || pkt_dts + 1 < ist->pts) && !copy_ts)               {                   input_files[ist->file_index].ts_offset -= delta;                   av_log( NULL,   AV_LOG_DEBUG,                           "timestamp discontinuity %"PRId64", new offset= %"PRId64"
",                           delta, input_files[ist->file_index].ts_offset);                   pkt.dts -= av_rescale_q(delta, AV_TIME_BASE_Q,  ist->st->time_base);                   if (pkt.pts != AV_NOPTS_VALUE)                       pkt.pts -= av_rescale_q(delta, AV_TIME_BASE_Q,  ist->st->time_base);   


 }           }              //把这一帧转换并写入到输出文件中           if (output_packet(ist, output_streams, nb_output_streams, &pkt) < 0){               av_log(NULL, AV_LOG_ERROR,                       "Error while decoding stream #%d:%d
",                       ist->file_index, ist->st->index);               if (exit_on_error)                   exit_program(1);               av_free_packet(&pkt);               continue;           }      discard_packet:           av_free_packet(&pkt);              /* dump report by using the output first video and audio streams */           print_report(output_files, output_streams, nb_output_streams, 0,                   timer_start, cur_time);       }          //文件处理完了,把缓冲中剩余的数据写到输出文件中       for (i = 0; i < nb_input_streams; i++){           ist = &input_streams[i];           if (ist->decoding_needed){               output_packet(ist, output_streams, nb_output_streams, NULL);           }       }       flush_encoders(output_streams, nb_output_streams);          term_exit();          //为输出文件写文件尾(有的不需要).       for (i = 0; i < nb_output_files; i++){           os = output_files[i].ctx;           av_write_trailer(os);       }          /* dump report by using the first video and audio streams */       print_report(output_files, output_streams, nb_output_streams, 1,               timer_start, av_gettime()); 


 //关闭所有的编码器       for (i = 0; i < nb_output_streams; i++){           ost = &output_streams[i];           if (ost->encoding_needed){               av_freep(&ost->st->codec->stats_in);               avcodec_close(ost->st->codec);           }   #if CONFIG_AVFILTER           avfilter_graph_free(&ost->graph);   #endif       }          //关闭所有的解码器       for (i = 0; i < nb_input_streams; i++){           ist = &input_streams[i];           if (ist->decoding_needed){               avcodec_close(ist->st->codec);           }       }          /* finished ! */       ret = 0;          fail: av_freep(&bit_buffer);       av_freep(&no_packet);          if (output_streams) {           for (i = 0; i < nb_output_streams; i++)  {               ost = &output_streams[i];               if (ost)    {                   if (ost->stream_copy)                       av_freep(&ost->st->codec->extradata);                   if (ost->logfile){                       fclose(ost->logfile);                       ost->logfile = NULL;                   }                   av_fifo_free(ost->fifo); /* works even if fifo is not                   initialized but set to zero */                   av_freep(&ost->st->codec->subtitle_header);                   av_free(ost->resample_frame.data[0]);                   av_free(ost->forced_kf_pts);                   if (ost->video_resample)                       sws_freeContext(ost->img_resample_ctx);  

                swr_free(&ost->swr);                   av_dict_free(&ost->opts);               }           }       }       return ret;   }
原文地址:https://www.cnblogs.com/schips/p/12197290.html