国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

ffmpeg.c(4.3.1)源碼剖析

這篇具有很好參考價(jià)值的文章主要介紹了ffmpeg.c(4.3.1)源碼剖析。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問(wèn)。


前言

本文對(duì) ffmpeg.c 源碼進(jìn)行學(xué)習(xí)及剖析。


一、FFmpeg 源碼結(jié)構(gòu)圖

ffmpeg.c(4.3.1)源碼剖析,音視頻開(kāi)發(fā),ffmpeg,c語(yǔ)言
鏈接:ffmpeg整體流程.jpg

下面對(duì)上述圖片進(jìn)行介紹:

  • 函數(shù)背景色
    • 函數(shù)在圖中以方框的形式表現(xiàn)出來(lái)。不同的背景色標(biāo)志了該函數(shù)不同的作用:
      • 粉紅色背景函數(shù):FFmpeg 的 API 函數(shù)。
      • 白色背景的函數(shù):FFmpeg 的內(nèi)部函數(shù)。
      • 黃色背景的函數(shù):URLProtocol 結(jié)構(gòu)體中的函數(shù),包含了讀寫(xiě)各種協(xié)議的功能。
      • 綠色背景的函數(shù):AVOutputFormat 結(jié)構(gòu)體中的函數(shù),包含了讀寫(xiě)各種封裝格式的功能。
      • 藍(lán)色背景的函數(shù):AVCodec 結(jié)構(gòu)體中的函數(shù),包含了編解碼的功能。
  • 區(qū)域
    • 整個(gè)關(guān)系圖可以分為以下幾個(gè)區(qū)域:
      • 左邊區(qū)域——架構(gòu)函數(shù)區(qū)域:這些函數(shù)并不針對(duì)某一特定的視頻格式。
      • 右上方黃色區(qū)域——協(xié)議處理函數(shù)區(qū)域:不同的協(xié)議(RTP,RTMP,F(xiàn)ILE) 會(huì)調(diào)用不同的協(xié)議處理函數(shù)。
      • 右邊中間綠色區(qū)域——封裝格式處理函數(shù)區(qū)域:不同的封裝格式(MKV,F(xiàn)LV,MPEG2TS,AVI)會(huì)調(diào)用不同的封裝格式處理函數(shù)。
      • 右邊下方藍(lán)色區(qū)域——編解碼函數(shù)區(qū)域:不同的編碼標(biāo)準(zhǔn)(HEVC,H.264,MPEG2)會(huì)調(diào)用不同的編解碼函數(shù)。
  • 箭頭線
    • 為了把調(diào)用關(guān)系表示的更明顯,圖中的箭頭線也使用了不同的顏色:
      • 紅色的箭頭線:標(biāo)志了編碼的流程。
      • 其他顏色的箭頭線:標(biāo)志了函數(shù)之間的調(diào)用關(guān)系。其中:
        • 調(diào)用 URLProtocol 結(jié)構(gòu)體中的函數(shù)用黃色箭頭線標(biāo)識(shí);
        • 調(diào)用 AVOutputFormat 結(jié)構(gòu)體中的函數(shù)用綠色箭頭線標(biāo)識(shí);
        • 調(diào)用 AVCodec 結(jié)構(gòu)體中的函數(shù)用藍(lán)色箭頭線標(biāo)識(shí)。
  • 函數(shù)所在的文件
    • 每個(gè)函數(shù)標(biāo)識(shí)了它所在的文件路徑。
      • 左邊區(qū)域(架構(gòu)函數(shù))
      • 右上區(qū)域(URLProtocol 協(xié)議處理函數(shù)),URLProtocol 結(jié)構(gòu)體包含如下協(xié)議處理函數(shù)指針:
        • url_open():打開(kāi)
        • url_read():讀取
        • url_write():寫(xiě)入
        • url_seek():調(diào)整進(jìn)度
        • url_close():關(guān)閉
          • 下面舉個(gè)例子,說(shuō)明不同的協(xié)議對(duì)應(yīng)著上述接口有不同的實(shí)現(xiàn)函數(shù):
            • File 協(xié)議(即文件)對(duì)應(yīng)的 URLProtocol 結(jié)構(gòu)體 ff_file_protocol:
              • url_open() -> file_open() -> open()
              • url_read() -> file_read() -> read()
              • url_write() -> file_write() -> write()
              • url_seek() -> file_seek() -> lseek()
              • url_close() -> file_close() -> close()
            • RTMP 協(xié)議(libRTMP)對(duì)應(yīng)的 URLProtocol 結(jié)構(gòu)體 ff_librtmp_protocol:
              • url_open() -> rtmp_open() -> RTMP_Init(),RTMP_SetupURL(),RTMP_Connect(),RTMP_ConnectStream()
              • url_read() -> rtmp_read() -> RTMP_Read()
              • url_write() -> rtmp_write() -> RTMP_Write()
              • url_seek() -> rtmp_read_seek() -> RTMP_SendSeek()
              • url_close() -> rtmp_close() -> RTMP_Close()
            • UDP 協(xié)議對(duì)應(yīng)的 URLProtocol 結(jié)構(gòu)體 ff_udp_protocol:
              • url_open() -> udp_open()
              • url_read() -> udp_read()
              • url_write() -> udp_write()
              • url_seek() -> udp_close()
              • url_close() -> udp_close()
      • 右中區(qū)域(AVOutputFormat 封裝格式處理函數(shù))
        • AVOutputFormat 包含如下封裝格式處理函數(shù)指針:
          • write_header():寫(xiě)文件頭
          • write_packet():寫(xiě)一幀數(shù)據(jù)
          • write_trailer():寫(xiě)文件尾
            • 下面舉個(gè)例子,說(shuō)明不同的封裝格式對(duì)應(yīng)著上述接口有不同的實(shí)現(xiàn)函數(shù):
              • FLV 封裝格式對(duì)應(yīng)的 AVOutputFormat 結(jié)構(gòu)體 ff_flv_muxer:
                • write_header() -> flv_write_header()
                • write_packet() – > flv_write_packet()
                • write_trailer() -> flv_write_trailer()
              • MKV 封裝格式對(duì)應(yīng)的 AVOutputFormat 結(jié)構(gòu)體 ff_matroska_muxer:
                • write_header() -> mkv_write_header()
                • write_packet() – > mkv_write_flush_packet()
                • write_trailer() -> mkv_write_trailer()
              • MPEG2TS 封裝格式對(duì)應(yīng)的 AVOutputFormat 結(jié)構(gòu)體 ff_mpegts_muxer:
                • write_header() -> mpegts_write_header()
                • write_packet() -> mpegts_write_packet()
                • write_trailer() -> mpegts_write_end()
              • AVI 封裝格式對(duì)應(yīng)的 AVOutputFormat 結(jié)構(gòu)體 ff_avi_muxer:
                • write_header() -> avi_write_header()
                • write_packet() -> avi_write_packet()
                • write_trailer() -> avi_write_trailer()
      • 右下區(qū)域(AVCodec 編解碼函數(shù))
        • AVCodec 包含如下編解碼函數(shù)指針:
          • init():初始化
          • encode2():編碼一幀數(shù)據(jù)
          • close():關(guān)閉
            • 下面舉個(gè)例子,說(shuō)明不同的編解碼器對(duì)應(yīng)著上述接口有不同的實(shí)現(xiàn)函數(shù):
              • HEVC 編碼器對(duì)應(yīng)的 AVCodec 結(jié)構(gòu)體 ff_libx265_encoder:
                • init() -> libx265_encode_init() -> x265_param_alloc(),x265_param_default_preset(),
                  x265_encoder_open()
                • encode2() -> libx265_encode_frame() -> x265_encoder_encode()
                • close() -> libx265_encode_close() -> x265_param_free(),x265_encoder_close()
              • H.264 編碼器對(duì)應(yīng)的 AVCodec 結(jié)構(gòu)體 ff_libx264_encoder:
                • init() -> X264_init() -> x264_param_default(),x264_encoder_open(),x264_encoder_headers()
                • encode2() -> X264_frame() -> x264_encoder_encode()
                • close() -> X264_close() -> x264_encoder_close()
              • VP8 編碼器(libVPX)對(duì)應(yīng)的 AVCodec 結(jié)構(gòu)體 ff_libvpx_vp8_encoder:
                • init() -> vpx_init() -> vpx_codec_enc_config_default()
                • encode2() -> vp8_encode() -> vpx_codec_enc_init(), vpx_codec_encode()
                • close() -> vp8_free() -> vpx_codec_destroy()

二、ffmpeg.h 頭文件詳解

ffmpeg.h 文件內(nèi)容如下所示:

/*
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#ifndef FFTOOLS_FFMPEG_H
#define FFTOOLS_FFMPEG_H

#include "config.h"

#include <stdint.h>
#include <stdio.h>
#include <signal.h>

#include "cmdutils.h"

#include "libavformat/avformat.h"
#include "libavformat/avio.h"

#include "libavcodec/avcodec.h"

#include "libavfilter/avfilter.h"

#include "libavutil/avutil.h"
#include "libavutil/dict.h"
#include "libavutil/eval.h"
#include "libavutil/fifo.h"
#include "libavutil/hwcontext.h"
#include "libavutil/pixfmt.h"
#include "libavutil/rational.h"
#include "libavutil/thread.h"
#include "libavutil/threadmessage.h"

#include "libswresample/swresample.h"

#define VSYNC_AUTO       -1
#define VSYNC_PASSTHROUGH 0
#define VSYNC_CFR         1
#define VSYNC_VFR         2
#define VSYNC_VSCFR       0xfe
#define VSYNC_DROP        0xff

#define MAX_STREAMS 1024    /* arbitrary sanity check value */

enum HWAccelID {
    HWACCEL_NONE = 0,
    HWACCEL_AUTO,
    HWACCEL_GENERIC,
    HWACCEL_VIDEOTOOLBOX,
    HWACCEL_QSV,
};

typedef struct HWAccel {
    const char *name;
    int (*init)(AVCodecContext *s);
    enum HWAccelID id;
    enum AVPixelFormat pix_fmt;
} HWAccel;

typedef struct HWDevice {
    const char *name;
    enum AVHWDeviceType type;
    AVBufferRef *device_ref;
} HWDevice;

/* select an input stream for an output stream */
typedef struct StreamMap {
    int disabled;           /* 1 is this mapping is disabled by a negative map */
    int file_index;
    int stream_index;
    int sync_file_index;
    int sync_stream_index;
    char *linklabel;       /* name of an output link, for mapping lavfi outputs */
} StreamMap;

typedef struct {
    int  file_idx,  stream_idx,  channel_idx; // input
    int ofile_idx, ostream_idx;               // output
} AudioChannelMap;

typedef struct OptionsContext {
    OptionGroup *g;

    /* input/output options */
    int64_t start_time;
    int64_t start_time_eof;
    int seek_timestamp;
    const char *format;

    SpecifierOpt *codec_names;
    int        nb_codec_names;
    SpecifierOpt *audio_channels;
    int        nb_audio_channels;
    SpecifierOpt *audio_sample_rate;
    int        nb_audio_sample_rate;
    SpecifierOpt *frame_rates;
    int        nb_frame_rates;
    SpecifierOpt *frame_sizes;
    int        nb_frame_sizes;
    SpecifierOpt *frame_pix_fmts;
    int        nb_frame_pix_fmts;

    /* input options */
    int64_t input_ts_offset;
    int loop;
    int rate_emu;
    int accurate_seek;
    int thread_queue_size;

    SpecifierOpt *ts_scale;
    int        nb_ts_scale;
    SpecifierOpt *dump_attachment;
    int        nb_dump_attachment;
    SpecifierOpt *hwaccels;
    int        nb_hwaccels;
    SpecifierOpt *hwaccel_devices;
    int        nb_hwaccel_devices;
    SpecifierOpt *hwaccel_output_formats;
    int        nb_hwaccel_output_formats;
    SpecifierOpt *autorotate;
    int        nb_autorotate;

    /* output options */
    StreamMap *stream_maps;
    int     nb_stream_maps;
    AudioChannelMap *audio_channel_maps; /* one info entry per -map_channel */
    int           nb_audio_channel_maps; /* number of (valid) -map_channel settings */
    int metadata_global_manual;
    int metadata_streams_manual;
    int metadata_chapters_manual;
    const char **attachments;
    int       nb_attachments;

    int chapters_input_file;

    int64_t recording_time;
    int64_t stop_time;
    uint64_t limit_filesize;
    float mux_preload;
    float mux_max_delay;
    int shortest;
    int bitexact;

    int video_disable;
    int audio_disable;
    int subtitle_disable;
    int data_disable;

    /* indexed by output file stream index */
    int   *streamid_map;
    int nb_streamid_map;

    SpecifierOpt *metadata;
    int        nb_metadata;
    SpecifierOpt *max_frames;
    int        nb_max_frames;
    SpecifierOpt *bitstream_filters;
    int        nb_bitstream_filters;
    SpecifierOpt *codec_tags;
    int        nb_codec_tags;
    SpecifierOpt *sample_fmts;
    int        nb_sample_fmts;
    SpecifierOpt *qscale;
    int        nb_qscale;
    SpecifierOpt *forced_key_frames;
    int        nb_forced_key_frames;
    SpecifierOpt *force_fps;
    int        nb_force_fps;
    SpecifierOpt *frame_aspect_ratios;
    int        nb_frame_aspect_ratios;
    SpecifierOpt *rc_overrides;
    int        nb_rc_overrides;
    SpecifierOpt *intra_matrices;
    int        nb_intra_matrices;
    SpecifierOpt *inter_matrices;
    int        nb_inter_matrices;
    SpecifierOpt *chroma_intra_matrices;
    int        nb_chroma_intra_matrices;
    SpecifierOpt *top_field_first;
    int        nb_top_field_first;
    SpecifierOpt *metadata_map;
    int        nb_metadata_map;
    SpecifierOpt *presets;
    int        nb_presets;
    SpecifierOpt *copy_initial_nonkeyframes;
    int        nb_copy_initial_nonkeyframes;
    SpecifierOpt *copy_prior_start;
    int        nb_copy_prior_start;
    SpecifierOpt *filters;
    int        nb_filters;
    SpecifierOpt *filter_scripts;
    int        nb_filter_scripts;
    SpecifierOpt *reinit_filters;
    int        nb_reinit_filters;
    SpecifierOpt *fix_sub_duration;
    int        nb_fix_sub_duration;
    SpecifierOpt *canvas_sizes;
    int        nb_canvas_sizes;
    SpecifierOpt *pass;
    int        nb_pass;
    SpecifierOpt *passlogfiles;
    int        nb_passlogfiles;
    SpecifierOpt *max_muxing_queue_size;
    int        nb_max_muxing_queue_size;
    SpecifierOpt *guess_layout_max;
    int        nb_guess_layout_max;
    SpecifierOpt *apad;
    int        nb_apad;
    SpecifierOpt *discard;
    int        nb_discard;
    SpecifierOpt *disposition;
    int        nb_disposition;
    SpecifierOpt *program;
    int        nb_program;
    SpecifierOpt *time_bases;
    int        nb_time_bases;
    SpecifierOpt *enc_time_bases;
    int        nb_enc_time_bases;
} OptionsContext;

typedef struct InputFilter {
    AVFilterContext    *filter;
    struct InputStream *ist;
    struct FilterGraph *graph;
    uint8_t            *name;
    enum AVMediaType    type;   // AVMEDIA_TYPE_SUBTITLE for sub2video

    AVFifoBuffer *frame_queue;

    // parameters configured for this input
    int format;

    int width, height;
    AVRational sample_aspect_ratio;

    int sample_rate;
    int channels;
    uint64_t channel_layout;

    AVBufferRef *hw_frames_ctx;

    int eof;
} InputFilter;

typedef struct OutputFilter {
    AVFilterContext     *filter;
    struct OutputStream *ost;
    struct FilterGraph  *graph;
    uint8_t             *name;

    /* temporary storage until stream maps are processed */
    AVFilterInOut       *out_tmp;
    enum AVMediaType     type;

    /* desired output stream properties */
    int width, height;
    AVRational frame_rate;
    int format;
    int sample_rate;
    uint64_t channel_layout;

    // those are only set if no format is specified and the encoder gives us multiple options
    int *formats;
    uint64_t *channel_layouts;
    int *sample_rates;
} OutputFilter;

typedef struct FilterGraph {
    int            index;
    const char    *graph_desc;

    AVFilterGraph *graph;
    int reconfiguration;

    InputFilter   **inputs;
    int          nb_inputs;
    OutputFilter **outputs;
    int         nb_outputs;
} FilterGraph;

typedef struct InputStream {
    int file_index;
    AVStream *st;
    int discard;             /* true if stream data should be discarded */
    int user_set_discard;
    int decoding_needed;     /* non zero if the packets must be decoded in 'raw_fifo', see DECODING_FOR_* */
#define DECODING_FOR_OST    1
#define DECODING_FOR_FILTER 2

    AVCodecContext *dec_ctx;
    AVCodec *dec;
    AVFrame *decoded_frame;
    AVFrame *filter_frame; /* a ref of decoded_frame, to be sent to filters */

    int64_t       start;     /* time when read started */
    /* predicted dts of the next packet read for this stream or (when there are
     * several frames in a packet) of the next frame in current packet (in AV_TIME_BASE units) */
    int64_t       next_dts;
    int64_t       dts;       ///< dts of the last packet read for this stream (in AV_TIME_BASE units)

    int64_t       next_pts;  ///< synthetic pts for the next decode frame (in AV_TIME_BASE units)
    int64_t       pts;       ///< current pts of the decoded frame  (in AV_TIME_BASE units)
    int           wrap_correction_done;

    int64_t filter_in_rescale_delta_last;

    int64_t min_pts; /* pts with the smallest value in a current stream */
    int64_t max_pts; /* pts with the higher value in a current stream */

    // when forcing constant input framerate through -r,
    // this contains the pts that will be given to the next decoded frame
    int64_t cfr_next_pts;

    int64_t nb_samples; /* number of samples in the last decoded audio frame before looping */

    double ts_scale;
    int saw_first_ts;
    AVDictionary *decoder_opts;
    AVRational framerate;               /* framerate forced with -r */
    int top_field_first;
    int guess_layout_max;

    int autorotate;

    int fix_sub_duration;
    struct { /* previous decoded subtitle and related variables */
        int got_output;
        int ret;
        AVSubtitle subtitle;
    } prev_sub;

    struct sub2video {
        int64_t last_pts;
        int64_t end_pts;
        AVFifoBuffer *sub_queue;    ///< queue of AVSubtitle* before filter init
        AVFrame *frame;
        int w, h;
        unsigned int initialize; ///< marks if sub2video_update should force an initialization
    } sub2video;

    int dr1;

    /* decoded data from this stream goes into all those filters
     * currently video and audio only */
    InputFilter **filters;
    int        nb_filters;

    int reinit_filters;

    /* hwaccel options */
    enum HWAccelID hwaccel_id;
    enum AVHWDeviceType hwaccel_device_type;
    char  *hwaccel_device;
    enum AVPixelFormat hwaccel_output_format;

    /* hwaccel context */
    void  *hwaccel_ctx;
    void (*hwaccel_uninit)(AVCodecContext *s);
    int  (*hwaccel_get_buffer)(AVCodecContext *s, AVFrame *frame, int flags);
    int  (*hwaccel_retrieve_data)(AVCodecContext *s, AVFrame *frame);
    enum AVPixelFormat hwaccel_pix_fmt;
    enum AVPixelFormat hwaccel_retrieved_pix_fmt;
    AVBufferRef *hw_frames_ctx;

    /* stats */
    // combined size of all the packets read
    uint64_t data_size;
    /* number of packets successfully read for this stream */
    uint64_t nb_packets;
    // number of frames/samples retrieved from the decoder
    uint64_t frames_decoded;
    uint64_t samples_decoded;

    int64_t *dts_buffer;
    int nb_dts_buffer;

    int got_output;
} InputStream;

typedef struct InputFile {
    AVFormatContext *ctx;
    int eof_reached;      /* true if eof reached */
    int eagain;           /* true if last read attempt returned EAGAIN */
    int ist_index;        /* index of first stream in input_streams */
    int loop;             /* set number of times input stream should be looped */
    int64_t duration;     /* actual duration of the longest stream in a file
                             at the moment when looping happens */
    AVRational time_base; /* time base of the duration */
    int64_t input_ts_offset;

    int64_t ts_offset;
    int64_t last_ts;
    int64_t start_time;   /* user-specified start time in AV_TIME_BASE or AV_NOPTS_VALUE */
    int seek_timestamp;
    int64_t recording_time;
    int nb_streams;       /* number of stream that ffmpeg is aware of; may be different
                             from ctx.nb_streams if new streams appear during av_read_frame() */
    int nb_streams_warn;  /* number of streams that the user was warned of */
    int rate_emu;
    int accurate_seek;

#if HAVE_THREADS
    AVThreadMessageQueue *in_thread_queue;
    pthread_t thread;           /* thread reading from this file */
    int non_blocking;           /* reading packets from the thread should not block */
    int joined;                 /* the thread has been joined */
    int thread_queue_size;      /* maximum number of queued packets */
#endif
} InputFile;

enum forced_keyframes_const {
    FKF_N,
    FKF_N_FORCED,
    FKF_PREV_FORCED_N,
    FKF_PREV_FORCED_T,
    FKF_T,
    FKF_NB
};

#define ABORT_ON_FLAG_EMPTY_OUTPUT        (1 <<  0)
#define ABORT_ON_FLAG_EMPTY_OUTPUT_STREAM (1 <<  1)

extern const char *const forced_keyframes_const_names[];

typedef enum {
    ENCODER_FINISHED = 1,
    MUXER_FINISHED = 2,
} OSTFinished ;

typedef struct OutputStream {
    int file_index;          /* file index */
    int index;               /* stream index in the output file */
    int source_index;        /* InputStream index */
    AVStream *st;            /* stream in the output file */
    int encoding_needed;     /* true if encoding needed for this stream */
    int frame_number;
    /* input pts and corresponding output pts
       for A/V sync */
    struct InputStream *sync_ist; /* input stream to sync against */
    int64_t sync_opts;       /* output frame counter, could be changed to some true timestamp */ // FIXME look at frame_number
    /* pts of the first frame encoded for this stream, used for limiting
     * recording time */
    int64_t first_pts;
    /* dts of the last packet sent to the muxer */
    int64_t last_mux_dts;
    // the timebase of the packets sent to the muxer
    AVRational mux_timebase;
    AVRational enc_timebase;

    AVBSFContext            *bsf_ctx;

    AVCodecContext *enc_ctx;
    AVCodecParameters *ref_par; /* associated input codec parameters with encoders options applied */
    AVCodec *enc;
    int64_t max_frames;
    AVFrame *filtered_frame;
    AVFrame *last_frame;
    int last_dropped;
    int last_nb0_frames[3];

    void  *hwaccel_ctx;

    /* video only */
    AVRational frame_rate;
    int is_cfr;
    int force_fps;
    int top_field_first;
    int rotate_overridden;
    double rotate_override_value;

    AVRational frame_aspect_ratio;

    /* forced key frames */
    int64_t forced_kf_ref_pts;
    int64_t *forced_kf_pts;
    int forced_kf_count;
    int forced_kf_index;
    char *forced_keyframes;
    AVExpr *forced_keyframes_pexpr;
    double forced_keyframes_expr_const_values[FKF_NB];

    /* audio only */
    int *audio_channels_map;             /* list of the channels id to pick from the source stream */
    int audio_channels_mapped;           /* number of channels in audio_channels_map */

    char *logfile_prefix;
    FILE *logfile;

    OutputFilter *filter;
    char *avfilter;
    char *filters;         ///< filtergraph associated to the -filter option
    char *filters_script;  ///< filtergraph script associated to the -filter_script option

    AVDictionary *encoder_opts;
    AVDictionary *sws_dict;
    AVDictionary *swr_opts;
    AVDictionary *resample_opts;
    char *apad;
    OSTFinished finished;        /* no more packets should be written for this stream */
    int unavailable;                     /* true if the steram is unavailable (possibly temporarily) */
    int stream_copy;

    // init_output_stream() has been called for this stream
    // The encoder and the bitstream filters have been initialized and the stream
    // parameters are set in the AVStream.
    int initialized;

    int inputs_done;

    const char *attachment_filename;
    int copy_initial_nonkeyframes;
    int copy_prior_start;
    char *disposition;

    int keep_pix_fmt;

    /* stats */
    // combined size of all the packets written
    uint64_t data_size;
    // number of packets send to the muxer
    uint64_t packets_written;
    // number of frames/samples sent to the encoder
    uint64_t frames_encoded;
    uint64_t samples_encoded;

    /* packet quality factor */
    int quality;

    int max_muxing_queue_size;

    /* the packets are buffered here until the muxer is ready to be initialized */
    AVFifoBuffer *muxing_queue;

    /* packet picture type */
    int pict_type;

    /* frame encode sum of squared error values */
    int64_t error[4];
} OutputStream;

typedef struct OutputFile {
    AVFormatContext *ctx;
    AVDictionary *opts;
    int ost_index;       /* index of the first stream in output_streams */
    int64_t recording_time;  ///< desired length of the resulting file in microseconds == AV_TIME_BASE units
    int64_t start_time;      ///< start time in microseconds == AV_TIME_BASE units
    uint64_t limit_filesize; /* filesize limit expressed in bytes */

    int shortest;

    int header_written;
} OutputFile;

extern InputStream **input_streams;
extern int        nb_input_streams;
extern InputFile   **input_files;
extern int        nb_input_files;

extern OutputStream **output_streams;
extern int         nb_output_streams;
extern OutputFile   **output_files;
extern int         nb_output_files;

extern FilterGraph **filtergraphs;
extern int        nb_filtergraphs;

extern char *vstats_filename;
extern char *sdp_filename;

extern float audio_drift_threshold;
extern float dts_delta_threshold;
extern float dts_error_threshold;

extern int audio_volume;
extern int audio_sync_method;
extern int video_sync_method;
extern float frame_drop_threshold;
extern int do_benchmark;
extern int do_benchmark_all;
extern int do_deinterlace;
extern int do_hex_dump;
extern int do_pkt_dump;
extern int copy_ts;
extern int start_at_zero;
extern int copy_tb;
extern int debug_ts;
extern int exit_on_error;
extern int abort_on_flags;
extern int print_stats;
extern int qp_hist;
extern int stdin_interaction;
extern int frame_bits_per_raw_sample;
extern AVIOContext *progress_avio;
extern float max_error_rate;
extern char *videotoolbox_pixfmt;

extern int filter_nbthreads;
extern int filter_complex_nbthreads;
extern int vstats_version;

extern const AVIOInterruptCB int_cb;

extern const OptionDef options[];
extern const HWAccel hwaccels[];
#if CONFIG_QSV
extern char *qsv_device;
#endif
extern HWDevice *filter_hw_device;


void term_init(void);
void term_exit(void);

void reset_options(OptionsContext *o, int is_input);
void show_usage(void);

void opt_output_file(void *optctx, const char *filename);

void remove_avoptions(AVDictionary **a, AVDictionary *b);
void assert_avoptions(AVDictionary *m);

int guess_input_channel_layout(InputStream *ist);

enum AVPixelFormat choose_pixel_fmt(AVStream *st, AVCodecContext *avctx, AVCodec *codec, enum AVPixelFormat target);
void choose_sample_fmt(AVStream *st, AVCodec *codec);

int configure_filtergraph(FilterGraph *fg);
int configure_output_filter(FilterGraph *fg, OutputFilter *ofilter, AVFilterInOut *out);
void check_filter_outputs(void);
int ist_in_filtergraph(FilterGraph *fg, InputStream *ist);
int filtergraph_is_simple(FilterGraph *fg);
int init_simple_filtergraph(InputStream *ist, OutputStream *ost);
int init_complex_filtergraph(FilterGraph *fg);

void sub2video_update(InputStream *ist, int64_t heartbeat_pts, AVSubtitle *sub);

int ifilter_parameters_from_frame(InputFilter *ifilter, const AVFrame *frame);

int ffmpeg_parse_options(int argc, char **argv);

int videotoolbox_init(AVCodecContext *s);
int qsv_init(AVCodecContext *s);

HWDevice *hw_device_get_by_name(const char *name);
int hw_device_init_from_string(const char *arg, HWDevice **dev);
void hw_device_free_all(void);

int hw_device_setup_for_decode(InputStream *ist);
int hw_device_setup_for_encode(OutputStream *ost);
int hw_device_setup_for_filter(FilterGraph *fg);

int hwaccel_decode_init(AVCodecContext *avctx);

int main_ffmpeg431(int argc, char** argv);

#endif /* FFTOOLS_FFMPEG_H */

全局變量與結(jié)構(gòu)體:

  • 輸入:

    • InputStream **input_streams = NULL;
    • int nb_input_streams = 0;
    • InputFile **input_files = NULL;
    • int nb_input_files = 0;
  • 輸出:

    • OutputStream **output_streams = NULL;
    • int nb_output_streams = 0;
    • OutputFile **output_files = NULL;
    • int nb_output_files = 0;

其中: input_streams 是輸入流的數(shù)組,nb_input_streams 是輸入流的個(gè)數(shù)。 input_files 是輸入文件(也可能是設(shè)備)的數(shù)組,nb_input_files 是輸入文件的個(gè)數(shù)。下面的輸出相關(guān)的變量們就不用解釋了。

可以看出,文件和流是分別保存的。于是,可以想象,結(jié)構(gòu) InputStream 中應(yīng)有其所屬的文件在 InputFile 中的序號(hào)(file_index) 。輸入流數(shù)組應(yīng)是這樣填充的:每當(dāng)在輸入文件中找到一個(gè)流時(shí),就把它添加到 input_streams 中,所以一個(gè)輸入文件對(duì)應(yīng)的輸入流在 input_streams 中是緊靠著的,于是 InputFile 結(jié)構(gòu)中應(yīng)有其第一個(gè)流在 input_streams 中的開(kāi)始序號(hào)(ist_index)和被放在 input_streams 中的流的總個(gè)數(shù)(nb_streams)。

在輸出流 output_streams 中,除了要保存其所在的輸出文件在 output_files 中的序號(hào)(index),還應(yīng)保存其對(duì)應(yīng)的輸入流在 input_streams 中的序號(hào)( source_index),也應(yīng)保存其在所屬輸出文件中的流序號(hào)(file_index)。而輸出文件中呢,只需保存它的第一個(gè)流在 output_streams 中的序號(hào)(ost_index)。

流和文件都準(zhǔn)備好了,下面就是轉(zhuǎn)換,那么轉(zhuǎn)換過(guò)程是怎樣的呢?
答:首先打開(kāi)輸入文件們,然后根據(jù)輸入流們準(zhǔn)備并打開(kāi)解碼器們,然后跟據(jù)輸出流們準(zhǔn)備并打開(kāi)編碼器們,然后創(chuàng)建輸出文件們,然后為所有輸出文件們寫(xiě)好頭部,然后就在循環(huán)中把輸入流轉(zhuǎn)換到輸出流并寫(xiě)入輸出文件中,轉(zhuǎn)換完后跳出循環(huán),然后寫(xiě)入文件尾,最后關(guān)閉所有的輸出文件

三、main 函數(shù)主要流程分析

main 函數(shù)如下:

int main_ffmpeg431(int argc, char **argv)
{
    int i, ret;
    BenchmarkTimeStamps ti;

    init_dynload();

    register_exit(ffmpeg_cleanup);

    setvbuf(stderr,NULL,_IONBF,0); /* win32 runtime needs this */

    av_log_set_flags(AV_LOG_SKIP_REPEATED);
    parse_loglevel(argc, argv, options);

    if(argc>1 && !strcmp(argv[1], "-d")){
        run_as_daemon=1;
        av_log_set_callback(log_callback_null);
        argc--;
        argv++;
    }

#if CONFIG_AVDEVICE
    avdevice_register_all();
#endif
    avformat_network_init();

    //show_banner(argc, argv, options);

    /* parse options and open all input/output files */
    ret = ffmpeg_parse_options(argc, argv);
    if (ret < 0)
        exit_program(1);

    if (nb_output_files <= 0 && nb_input_files == 0) {
        show_usage();
        av_log(NULL, AV_LOG_WARNING, "Use -h to get full help or, even better, run 'man %s'\n", program_name);
        exit_program(1);
    }

    /* file converter / grab */
    if (nb_output_files <= 0) {
        av_log(NULL, AV_LOG_FATAL, "At least one output file must be specified\n");
        exit_program(1);
    }

    for (i = 0; i < nb_output_files; i++) {
        if (strcmp(output_files[i]->ctx->oformat->name, "rtp"))
            want_sdp = 0;
    }

    current_time = ti = get_benchmark_time_stamps();
    if (transcode() < 0)
        exit_program(1);
    if (do_benchmark) {
        int64_t utime, stime, rtime;
        current_time = get_benchmark_time_stamps();
        utime = current_time.user_usec - ti.user_usec;
        stime = current_time.sys_usec  - ti.sys_usec;
        rtime = current_time.real_usec - ti.real_usec;
        av_log(NULL, AV_LOG_INFO,
               "bench: utime=%0.3fs stime=%0.3fs rtime=%0.3fs\n",
               utime / 1000000.0, stime / 1000000.0, rtime / 1000000.0);
    }
    av_log(NULL, AV_LOG_DEBUG, "%"PRIu64" frames successfully decoded, %"PRIu64" decoding errors\n",
           decode_error_stat[0], decode_error_stat[1]);
    if ((decode_error_stat[0] + decode_error_stat[1]) * max_error_rate < decode_error_stat[1])
        exit_program(69);

    exit_program(received_nb_signals ? 255 : main_return_code);
    return main_return_code;
}

總結(jié)起來(lái),分為以下幾個(gè)步驟:

  • 1、初始化工作
  • 2、解析命令行參數(shù)
  • 3、編碼
  • 4、收尾

四、ffmpeg_parse_options

下面是 ffmpeg_parse_options 的調(diào)用關(guān)系
ffmpeg.c(4.3.1)源碼剖析,音視頻開(kāi)發(fā),ffmpeg,c語(yǔ)言

1、命令行例子

ffmpeg -i abc.mp4 -i bbb.avi -vcodec libx264 -acodec aac -vf scale=640:480 -f flv -y abc.flv
  • 命令行包括三個(gè)部分:輸入?yún)?shù),輸出參數(shù),和全局選項(xiàng)。
  • -i /home/ron/music/avm.mp4 是輸入?yún)?shù),a.mp4 是輸出參數(shù)。輸入/輸出參數(shù)可以有專(zhuān)屬的選項(xiàng),這些選項(xiàng)應(yīng)該緊挨著放在輸入輸出參數(shù)前面。如 -vf “split [main][tmp]…[main][flip]” 就是輸出參數(shù) a.mp4 的選項(xiàng)。
  • 全局選項(xiàng)的位置不需要限定, 因?yàn)檫x項(xiàng)是以選項(xiàng)名字查找的。
  • 可以有多組輸入?yún)?shù)和多組輸出參數(shù)。

①、解析命令行 split_commandline()

split_commandline() 負(fù)責(zé)解析命令行。

/**
 * Split the commandline into an intermediate form convenient for further
 * processing.
 *
 * The commandline is assumed to be composed of options which either belong to a
 * group (those with OPT_SPEC, OPT_OFFSET or OPT_PERFILE) or are global
 * (everything else).
 *
 * A group (defined by an OptionGroupDef struct) is a sequence of options
 * terminated by either a group separator option (e.g. -i) or a parameter that
 * is not an option (doesn't start with -). A group without a separator option
 * must always be first in the supplied groups list.
 *
 * All options within the same group are stored in one OptionGroup struct in an
 * OptionGroupList, all groups with the same group definition are stored in one
 * OptionGroupList in OptionParseContext.groups. The order of group lists is the
 * same as the order of group definitions.
 */
int split_commandline(OptionParseContext *octx, int argc, char *argv[],
                      const OptionDef *options,
                      const OptionGroupDef *groups, int nb_groups);

解析的結(jié)果保存在 OptionParseContext 中。解析時(shí)需要參考 OptionDef 和 OptionGroupDef。OptonDef[] 是支持 ffmpeg 的選項(xiàng)列表,OptionGroupDef[] 是支持的組列表,包括輸入類(lèi)和輸出類(lèi),前者以 -i 開(kāi)頭,加上設(shè)備名。后者只有文件名。

下面的類(lèi)圖顯示了涉及的類(lèi):
ffmpeg.c(4.3.1)源碼剖析,音視頻開(kāi)發(fā),ffmpeg,c語(yǔ)言

  • OptionGroup 保存一個(gè)輸入(或輸出)和它的選項(xiàng)列表。Option 表示一個(gè)選項(xiàng)。
  • OptionParseContext 中包括多個(gè) OptionGroup。全局選項(xiàng)保存在 global_opts 中。所有輸入設(shè)備的選項(xiàng)保存在一個(gè) OptionGroupList 實(shí)例中,所有輸出設(shè)備的選項(xiàng)保存在另一個(gè)實(shí)例中。 兩者合起來(lái)組成數(shù)組 groups。

split_commandline() 在一個(gè)循環(huán)中解析命令行,主要涉及如下函數(shù)。

函數(shù) 功能
find_option() 查詢(xún)支持的 option 列表, 檢查當(dāng)前元素是否一個(gè)option
add_option() 將 option 加入一個(gè)臨時(shí)組。(因?yàn)?option 先于 group 出現(xiàn),還不知道應(yīng)該加入到哪個(gè)組。)
match_group_separator() 查詢(xún)支持的 group 列表,檢查當(dāng)前元素是否是一個(gè) Group
finish_group() 設(shè)置臨時(shí)組的參數(shù),并用它填充 OptionParseContext.groups(現(xiàn)在知道應(yīng)該加入哪個(gè)組了)

②、parse_optgroup()

parse_optgroup() 負(fù)責(zé)將 OptionGroup 轉(zhuǎn)換成 OptionsContext。

/**
 * Parse an options group and write results into optctx.
 *
 * @param optctx an app-specific options context. NULL for global options group
 */
int parse_optgroup(void *optctx, OptionGroup *g);
  • OptionGroup 保存的選項(xiàng)值是字符串,而 OptionsContext 保存的值是由 OptionDef 定義的實(shí)際類(lèi)型。 parse_optgroup() 的第一個(gè)參數(shù) optctx 實(shí)際上是 OptonsContext。

下面的類(lèi)圖顯示了涉及的類(lèi):
ffmpeg.c(4.3.1)源碼剖析,音視頻開(kāi)發(fā),ffmpeg,c語(yǔ)言

  • SpecifierOpt 保存實(shí)際類(lèi)型的選項(xiàng)。OptionsContext 有若干個(gè) SpecifierOpt 數(shù)組的成員。每個(gè) specfier 數(shù)組保存一類(lèi)選項(xiàng)。如 filters 保存 ”filter” 選項(xiàng)。但 filter 可以是 ”filter:v”,屬于 video,也可以是“filter:a”,屬于 audio。SpecifierOpt.specifier 成員就是用來(lái)標(biāo)記這個(gè)選項(xiàng)應(yīng)該屬于誰(shuí)的。對(duì)于”filter:v”,SpecifierOpt.specifier 就是”v”。
  • 這里順便提一下 AVDictionary。解析過(guò)程沒(méi)有用到它。用戶(hù)設(shè)置的選項(xiàng)可能不成功,而選項(xiàng)的最終值會(huì)保存在這里。用 av_dict_set()函數(shù)設(shè)置它。

parse_optgroup() 函數(shù)遍歷 OptonGroup 中的 Option,調(diào)用 write_option() 將其寫(xiě)入 OptionsContext。

  • 對(duì)于基本的選項(xiàng),它的 OptionDef 中定義了它在 OptionsContext 的偏移,所以將字符串轉(zhuǎn)化后,直接寫(xiě)入就好了。比如”filter:v”。
  • 有的選項(xiàng)可能是其他選項(xiàng)的別名。這時(shí)它的 OptionDef 指定了一個(gè)回調(diào)函數(shù)。這個(gè)函數(shù)會(huì)重定向到所指向的選項(xiàng)上去。如”vf”就是”filter:v”的別名,它的 OptionDef 指定了回調(diào)函數(shù) opt_video_filter()。這個(gè)函數(shù)會(huì)調(diào)用 parse_option() 和 find_option() 查找”filter:v”對(duì)應(yīng)的 OptionDef,并再次調(diào)用 write_option()。
  • 全局選項(xiàng)。它的 OptionDef 也定義了一個(gè)回調(diào)函數(shù)。這個(gè)函數(shù)直接設(shè)置全局變量。如 loglevel,它的 OptionDef 定義了 opt_loglevel()。這個(gè)函數(shù)調(diào)用 av_log_set_level() 設(shè)置日志輸出等級(jí)。

③、MATCH_PER_XXX_OPT()

宏 MATCH_PER_TYPE_OPT() 和 MATCH_PER_STREAM_OPT() 用于從 OptionsContext 讀值。

  • 前者指定參數(shù) mediatype,用它跟 OptionsContext.spcifier 比較,找出 option 并讀出。
  • 后者指定參數(shù) AVStream,調(diào)用 check_stream_specifier(),用 AVStream 的屬性與
    OptionContext.specifier 匹配,找出 option 并讀出。
#define MATCH_PER_STREAM_OPT(name, type, outvar, fmtctx, st)\
{\
    int i, ret, matches = 0;\
    SpecifierOpt *so = NULL;\
    for (i = 0; i < o->nb_ ## name; i++) {\
        char *spec = o->name[i].specifier;\
        if ((ret = check_stream_specifier(fmtctx, st, spec)) > 0) {\
            outvar = o->name[i].u.type;\
            so = &o->name[i];\
            matches++;\
        } else if (ret < 0)\
            exit_program(1);\
    }\
    if (matches > 1)\
       WARN_MULTIPLE_OPT_USAGE(name, type, so, st);\
}

#define MATCH_PER_TYPE_OPT(name, type, outvar, fmtctx, mediatype)\
{\
    int i;\
    for (i = 0; i < o->nb_ ## name; i++) {\
        char *spec = o->name[i].specifier;\
        if (!strcmp(spec, mediatype))\
            outvar = o->name[i].u.type;\
    }\
}

2、vf 選項(xiàng)解析

下圖是 avfilter_graph_parse2() 的函數(shù)調(diào)用關(guān)系。
ffmpeg.c(4.3.1)源碼剖析,音視頻開(kāi)發(fā),ffmpeg,c語(yǔ)言

①、filters

如下是 filters 的一個(gè)例子。 它來(lái)自 ffmpeg 的文檔:https://ffmpeg.org//ffmpeg-filters.html#Filtergraph-description

ffmpeg -i INPUT -vf “split [main][tmp]; [tmp] crop=iw:ih/2:0:0, vflip [flip]; [main][flip] overlay=0:H/2” OUTPUT

對(duì)應(yīng) FilterGraph 的結(jié)構(gòu)示意圖如下。矩形框內(nèi)是 vf 的內(nèi)容對(duì)應(yīng)的部分。其中 split 應(yīng)該導(dǎo)出到 inputs 中,overlay 應(yīng)該導(dǎo)出到 outputs 中。
ffmpeg.c(4.3.1)源碼剖析,音視頻開(kāi)發(fā),ffmpeg,c語(yǔ)言

②、vf 術(shù)語(yǔ)

描述 vf 的解析過(guò)程需要使用一些術(shù)語(yǔ)。其中一部分是關(guān)于 vf 語(yǔ)法的,另外一部分是關(guān)于生成的 FilterGraph 結(jié)構(gòu)的。

上圖標(biāo)出了 vf 語(yǔ)法的術(shù)語(yǔ)。

  • 過(guò)濾器。過(guò)濾器用紅色標(biāo)出,包括它的名字和參數(shù)。如”split”,只有名字。又如”overlay=0:H/2”,overlay 是名字, ”0:H/2”是參數(shù)。名字和參數(shù)用 = 連接。
  • 位置點(diǎn)。有兩類(lèi)位置點(diǎn),有名的和無(wú)名的。有名位置點(diǎn)用綠色標(biāo)出,名字用 [] 包住,如 main, flip, tmp。無(wú)名位置點(diǎn)不必標(biāo)出,如下圖所示。
  • 路徑。路徑是一條從位置點(diǎn)開(kāi)始,中間過(guò)濾器和位置點(diǎn)交錯(cuò),在位置點(diǎn)結(jié)束的處理流程。多條路徑組成整個(gè) filtergraph。中間的位置點(diǎn)都是無(wú)名的,開(kāi)始和結(jié)束的位置點(diǎn)應(yīng)該是有名的,除非這條路徑在 filtergraph 的開(kāi)始和結(jié)束位置。路徑之間用;隔開(kāi)。 如 [tmp] crop=iw:ih/2:0:0, vflip [flip]。以 tmp 開(kāi)始,中間包括 crop 和 vflip 和一個(gè)無(wú)名位置點(diǎn),在 flip 結(jié)束。有名位置點(diǎn)是該路徑與其他路徑的連接點(diǎn),所以需要有一個(gè)名字來(lái)標(biāo)記,而無(wú)名位置點(diǎn)只存在該路徑內(nèi)部的兩個(gè)過(guò)濾器之間,是隱含的,所以不需要名字。

下圖是 FilterGraph 的結(jié)構(gòu)圖。
ffmpeg.c(4.3.1)源碼剖析,音視頻開(kāi)發(fā),ffmpeg,c語(yǔ)言

  • FilterGraph 是由一系列的過(guò)濾器,Pad 和 Pad Link 構(gòu)成的。
  • 過(guò)濾器來(lái)自 FilterGraph 語(yǔ)法中的過(guò)濾器,它有一組 In Pad 和一組 OutPad,Pad 與語(yǔ)法中的位置點(diǎn)對(duì)應(yīng)。過(guò)濾器之間通過(guò) Pad 聯(lián)系,Pad Link 用來(lái)將一個(gè) In Pad 連接到一個(gè) OutPad。Pad Link 沒(méi)有對(duì)應(yīng)的語(yǔ)法元素。
  • Input/Output 用于解析過(guò)程,也用于保存整個(gè)解析的結(jié)果,以返回給調(diào)用者。open_inputs 標(biāo)記當(dāng)前還沒(méi)有解析(與其他 OutPad 連接)的 InPad,open_outputs 標(biāo)記當(dāng)前還沒(méi)有解析的 OutPad,curr_inputs 標(biāo)記當(dāng)前將要解析的 InPad。

③、avfilter_graph_parse2()

avfilter_graph_parse2() 負(fù)責(zé)解析 vf 選項(xiàng)內(nèi)容。

/**
 * Add a graph described by a string to a graph.
 *
 * @param[in]  graph   the filter graph where to link the parsed graph context
 * @param[in]  filters string to be parsed
 * @param[out] inputs  a linked list of all free (unlinked) inputs of the
 *                     parsed graph will be returned here. It is to be freed
 *                     by the caller using avfilter_inout_free().
 * @param[out] outputs a linked list of all free (unlinked) outputs of the
 *                     parsed graph will be returned here. It is to be freed by the
 *                     caller using avfilter_inout_free().
 * @return zero on success, a negative AVERROR code on error
 *
 * @note This function returns the inputs and outputs that are left
 * unlinked after parsing the graph and the caller then deals with
 * them.
 * @note This function makes no reference whatsoever to already
 * existing parts of the graph and the inputs parameter will on return
 * contain inputs of the newly parsed part of the graph.  Analogously
 * the outputs parameter will contain outputs of the newly created
 * filters.
 */
int avfilter_graph_parse2(AVFilterGraph *graph, const char *filters,
                          AVFilterInOut **inputs,
                          AVFilterInOut **outputs);

輸入?yún)?shù) filters 是 vf 選項(xiàng)內(nèi)容。輸出參數(shù) Inputs 是導(dǎo)出的輸入接口,outputs 是 filters 導(dǎo)出的輸出接口。

avfilter_graph_parse2() 主要調(diào)用四個(gè)函數(shù)進(jìn)行解析。

函數(shù) 功能
parse_input() 選取若干 open_outputs,以更新 curr_inputs
parse_filter() 解析過(guò)濾器
link_filter_inouts() 將新的過(guò)濾器連入當(dāng)前的 curr_inputs,并更新 curr_inputs
parse_output() 結(jié)束當(dāng)前的 curr_inputs,加入 open_outputs。

④、FilterGraph 類(lèi)

下面的類(lèi)圖顯示了 FilterGraph 各元素對(duì)應(yīng)的類(lèi)。
ffmpeg.c(4.3.1)源碼剖析,音視頻開(kāi)發(fā),ffmpeg,c語(yǔ)言

  • AVFilterContext 表示過(guò)濾器。AVFilter 是它的屬性類(lèi)。
  • AVFilterPad 是 Pad 類(lèi)。一個(gè) AVFilterContext 實(shí)例包括 AVFilterPad 的一組 In Pad 實(shí)例和一組 Out Pad 實(shí)例。 AVFilterLink 是 Pad Link 類(lèi),它連接兩個(gè) AVFilterPad 實(shí)例。
  • AVFilterLink 有一個(gè) FFFrameQueue,用于保存過(guò)濾的中間結(jié)果。這是一個(gè) frame 的數(shù)據(jù)通道。
  • AVFilterContext 有一個(gè)空間,用于保存該特定類(lèi)型 Filter 的私有信息,可以是 CropContext,SplitContext 或其他 filter 的一種。
  • AVFilterInOut 用于解析過(guò)程標(biāo)記 open_iputs, open_ouputs 和 curr_inputs。它沒(méi)有直接引用 AVFilterPad,而是引用 AVFilterContext,和用序號(hào)間接指向 AVFilterPad。

五、transcode 函數(shù)

transcode 用于實(shí)現(xiàn)媒體文件轉(zhuǎn)碼的函數(shù)之一。轉(zhuǎn)碼是指將一個(gè)媒體文件從一種編碼格式轉(zhuǎn)換為另一種編碼格式的過(guò)程。這可以包括視頻編解碼器、音頻編解碼器、容器格式或其他媒體屬性的更改。

其主要包括以下兩個(gè)核心函數(shù):

  • transcode_init()
    • 初始化,打開(kāi)所有輸出流的編碼器,打開(kāi)所有輸入流的解碼器,寫(xiě)入所有輸出文件的文件頭。
  • transcode_step()
    • 于實(shí)現(xiàn) FFmpeg 轉(zhuǎn)碼過(guò)程中的一個(gè)步驟的函數(shù)

1、transcode_init 函數(shù)

初始化工作:

  • AVFormatContext *oc;//輸出流的編解碼器結(jié)構(gòu)
  • OutputStream *ost;//輸出流
  • InputStream *ist; //輸入流
  • init_input_stream
  • init_output_stream
//transcode_init()函數(shù)是在轉(zhuǎn)換前做準(zhǔn)備工作的
static int transcode_init(void)
{
    int ret = 0, i, j, k;
    AVFormatContext *oc;//輸出流的編解碼器結(jié)構(gòu)
    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 */
	//初始化幀率仿真(轉(zhuǎn)換時(shí)是不按幀率來(lái)的,但如果要求幀率仿真,就可以做到)
    for (i = 0; i < nb_input_files; i++) {
        InputFile *ifile = input_files[i];
		//如果一個(gè)輸入文件被要求幀率仿真(指的是即使是轉(zhuǎn)換也像播放那樣按照幀率來(lái)進(jìn)行),則為這個(gè)文件中所有流記錄下開(kāi)始時(shí)間。
        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 */
	//什么也沒(méi)做,只是做了個(gè)判斷而已。
    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 */
	//輪循所有輸出流,打開(kāi)每個(gè)輸出流的編碼器
    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 */
	//打開(kāi)所有輸出文件,寫(xiě)入媒體文件頭
    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:\n");
    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, "\n");
            }
        }
    }

    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\n",
                   ost->attachment_filename, ost->file_index, ost->index);
            continue;
        }

		// 復(fù)雜過(guò)濾器
        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)\n", 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);
		//如果只是復(fù)制一個(gè)流(不用解碼后再編碼),則把輸入流的編碼參數(shù)直接賦值給輸出流  
		//此時(shí)是不需要解碼也不需要編碼,所以不需打開(kāi)解碼器和編碼器 
        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, "\n");
    }

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

    atomic_store(&transcode_init_done, 1);//初始化完成

    return 0;
}
static int init_input_stream(int ist_index, char *error, int error_len)
{
    int ret;
    InputStream *ist = input_streams[ist_index];

    if (ist->decoding_needed) {
        AVCodec *codec = ist->dec;
        if (!codec) {
            snprintf(error, error_len, "Decoder (codec %s) not found for input stream #%d:%d",
                    avcodec_get_name(ist->dec_ctx->codec_id), ist->file_index, ist->st->index);
            return AVERROR(EINVAL);
        }

        ist->dec_ctx->opaque                = ist;
        ist->dec_ctx->get_format            = get_format;
        ist->dec_ctx->get_buffer2           = get_buffer;
        ist->dec_ctx->thread_safe_callbacks = 1;

        av_opt_set_int(ist->dec_ctx, "refcounted_frames", 1, 0);
        if (ist->dec_ctx->codec_id == AV_CODEC_ID_DVB_SUBTITLE &&
           (ist->decoding_needed & DECODING_FOR_OST)) {
            av_dict_set(&ist->decoder_opts, "compute_edt", "1", AV_DICT_DONT_OVERWRITE);
            if (ist->decoding_needed & DECODING_FOR_FILTER)
                av_log(NULL, AV_LOG_WARNING, "Warning using DVB subtitles for filtering and output at the same time is not fully supported, also see -compute_edt [0|1]\n");
        }

        av_dict_set(&ist->decoder_opts, "sub_text_format", "ass", AV_DICT_DONT_OVERWRITE);

        /* Useful for subtitles retiming by lavf (FIXME), skipping samples in
         * audio, and video decoders such as cuvid or mediacodec */
        ist->dec_ctx->pkt_timebase = ist->st->time_base;

        if (!av_dict_get(ist->decoder_opts, "threads", NULL, 0))
            av_dict_set(&ist->decoder_opts, "threads", "auto", 0);
        /* Attached pics are sparse, therefore we would not want to delay their decoding till EOF. */
        if (ist->st->disposition & AV_DISPOSITION_ATTACHED_PIC)
            av_dict_set(&ist->decoder_opts, "threads", "1", 0);

        ret = hw_device_setup_for_decode(ist);
        if (ret < 0) {
            snprintf(error, error_len, "Device setup failed for "
                     "decoder on input stream #%d:%d : %s",
                     ist->file_index, ist->st->index, av_err2str(ret));
            return ret;
        }

		//打開(kāi)解碼器
        if ((ret = avcodec_open2(ist->dec_ctx, codec, &ist->decoder_opts)) < 0) {
            if (ret == AVERROR_EXPERIMENTAL)
                abort_codec_experimental(codec, 0);

            snprintf(error, error_len,
                     "Error while opening decoder for input stream "
                     "#%d:%d : %s",
                     ist->file_index, ist->st->index, av_err2str(ret));
            return ret;
        }
        assert_avoptions(ist->decoder_opts);
    }

    ist->next_pts = AV_NOPTS_VALUE;
    ist->next_dts = AV_NOPTS_VALUE;

    return 0;
}

2、transcode_step 函數(shù)

/**
 * Run a single step of transcoding.
 *
 * @return  0 for success, <0 for error
 */
 /*
 解碼流程是:
 process_input() -> output_packet() -> decode_audio()/decode_video()/transcode_subtitles()
 而decode_audio() 是調(diào)用 avcodec_decode_audio4() 來(lái)完成工作的。
 decode_video() 則是通過(guò)調(diào)用 avcodec_decode_video2() 來(lái)完成的。

 編碼流程是:
 reap_filters() -> do_video_out()或 do_audio_out() 
 -> avcodec_encode_video2() 或 avcodec_encode_audio2()。
 */
static int transcode_step(void)
{
    OutputStream *ost;
    InputStream  *ist = NULL;
    int ret;

	//選擇一個(gè)有效的輸出流進(jìn)行處理
    ost = choose_output();
    if (!ost) {
        if (got_eagain()) {
            reset_eagain();
            av_usleep(10000);
            return 0;
        }
        av_log(NULL, AV_LOG_VERBOSE, "No more inputs to read from, finishing.\n");
        return AVERROR_EOF;
    }

	//選擇一個(gè)輸入流
    if (ost->filter && !ost->filter->graph->graph) {
        if (ifilter_has_all_input_formats(ost->filter->graph)) {
            ret = configure_filtergraph(ost->filter->graph);
            if (ret < 0) {
                av_log(NULL, AV_LOG_ERROR, "Error reinitializing filters!\n");
                return ret;
            }
        }
    }

    if (ost->filter && ost->filter->graph->graph) {
        if (!ost->initialized) {
            char error[1024] = {0};
            ret = init_output_stream(ost, error, sizeof(error));
            if (ret < 0) {
                av_log(NULL, AV_LOG_ERROR, "Error initializing output stream %d:%d -- %s\n",
                       ost->file_index, ost->index, error);
                exit_program(1);
            }
        }
        if ((ret = transcode_from_filter(ost->filter->graph, &ist)) < 0)
            return ret;
        if (!ist)
            return 0;
    } else if (ost->filter) {
        int i;
        for (i = 0; i < ost->filter->graph->nb_inputs; i++) {
            InputFilter *ifilter = ost->filter->graph->inputs[i];
            if (!ifilter->ist->got_output && !input_files[ifilter->ist->file_index]->eof_reached) {
                ist = ifilter->ist;
                break;
            }
        }
        if (!ist) {
            ost->inputs_done = 1;
            return 0;
        }
    } else {
        av_assert0(ost->source_index >= 0);
        ist = input_streams[ost->source_index];
    }

	//讀取并處理每一個(gè)包
    ret = process_input(ist->file_index);
    if (ret == AVERROR(EAGAIN)) {
        if (input_files[ist->file_index]->eagain)
            ost->unavailable = 1;
        return 0;
    }

    if (ret < 0)
        return ret == AVERROR_EOF ? 0 : ret;

	//根據(jù)濾波器做濾波處理,并把處理完的音視頻輸出到輸出文件中
    return reap_filters(0);

}

我的qq:2442391036,歡迎交流!文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-777230.html


到了這里,關(guān)于ffmpeg.c(4.3.1)源碼剖析的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來(lái)自互聯(lián)網(wǎng)用戶(hù)投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 音視頻處理 ffmpeg中級(jí)開(kāi)發(fā) H264編碼

    音視頻處理 ffmpeg中級(jí)開(kāi)發(fā) H264編碼

    libavcodec/avcodec.h 常用的數(shù)據(jù)結(jié)構(gòu) AVCodec 編碼器結(jié)構(gòu)體 AVCodecContext 編碼器上下文 AVFrame 解碼后的幀 結(jié)構(gòu)體內(nèi)存的分配和釋放 av_frame_alloc 申請(qǐng) av_frame_free() 釋放 avcodec_alloc_context3() 創(chuàng)建編碼器上下文 avcodec_free_context() 釋放編碼器上下文 解碼步驟 avcodec_find_decoder 查找解碼器 avcod

    2024年02月01日
    瀏覽(109)
  • 音視頻開(kāi)發(fā)實(shí)戰(zhàn)03-FFmpeg命令行工具移植

    音視頻開(kāi)發(fā)實(shí)戰(zhàn)03-FFmpeg命令行工具移植

    作為一個(gè)音視頻開(kāi)發(fā)者,在日常工作中經(jīng)常會(huì)使用ffmpeg 命令來(lái)做很多事比如轉(zhuǎn)碼 ffmpeg -y -i test.mov -g 150 -s 1280x720 -codec libx265 -r 25 test_h265.mp4 ,水平翻轉(zhuǎn)視頻: ffmpeg -i src.mp4 -vf hflip -acodec copy -vcodec h264 -b 22000000 out.mp4 ,視頻截?。?ffmpeg -i input.wmv -ss 00:00:30.0 -c copy -t 00:00:10.0 ou

    2024年02月16日
    瀏覽(31)
  • FFMPEG開(kāi)發(fā)快速入坑——附錄一:音視頻同步

    FFMPEG開(kāi)發(fā)快速入坑——附錄一:音視頻同步

    本章節(jié)主要以本地音視頻播放為例,簡(jiǎn)要描述講解一個(gè)基本的播放器中,音視頻播放如何實(shí)現(xiàn)同步的。 通用媒體播放器框架 其中各個(gè)組件模塊: 1、Media Demux: 進(jìn)行媒體文件的解析,分別解析出音頻流數(shù)據(jù)包和視頻流數(shù)據(jù)包。主要使用? libavformat 庫(kù)中的函數(shù)。 2、Video Decoder:

    2024年01月19日
    瀏覽(27)
  • 音視頻開(kāi)發(fā)五:visual studio集成使用FFmpeg

    音視頻開(kāi)發(fā)五:visual studio集成使用FFmpeg

    ffmpeg 官網(wǎng) - download - 選擇Windows系統(tǒng) - 選擇gyan.dev版本- shared版本 在Windows系統(tǒng)上,Gyan.dev和BtbN都提供了FFmpeg的預(yù)編譯版本。Gyan.dev通常使用MSVC編譯器,而B(niǎo)tbN使用MinGW編譯器。因此,Gyan.dev的版本可能會(huì)更符合Windows標(biāo)準(zhǔn),而B(niǎo)tbN的版本可能會(huì)更加開(kāi)放和跨平臺(tái)。 選擇 shared版本 各

    2024年02月04日
    瀏覽(55)
  • Qt音視頻開(kāi)發(fā)40-ffmpeg采集桌面并錄制

    之前用ffmpeg打通了各種視頻文件和視頻流以及本地?cái)z像頭設(shè)備的采集,近期有個(gè)客戶(hù)需求要求將整個(gè)桌面屏幕采集下來(lái),并可以錄制保存成MP4文件,以前也遇到過(guò)類(lèi)似的需求,由于沒(méi)有搞過(guò),也沒(méi)有精力去摸索和測(cè)試,所以也就一直耽擱著,近期剛好這個(gè)需求又來(lái)了,定下心

    2023年04月25日
    瀏覽(21)
  • 音視頻開(kāi)發(fā)三:Windows環(huán)境下FFmpeg編譯安裝

    音視頻開(kāi)發(fā)三:Windows環(huán)境下FFmpeg編譯安裝

    FFmpeg是一套可以用來(lái)記錄、轉(zhuǎn)換數(shù)字音頻、視頻,并能將其轉(zhuǎn)化為流的 開(kāi)源計(jì)算機(jī)程序 。采用LGPL或GPL許可證 。它提供了錄制、轉(zhuǎn)換以及流化音視頻的完整解決方案。它包含了非常先進(jìn)的音頻/視頻編解碼庫(kù)libavcodec。 FFmpeg在Linux平臺(tái)下開(kāi)發(fā),但它同樣也可以在其它操作系統(tǒng)環(huán)

    2024年02月04日
    瀏覽(39)
  • 音視頻開(kāi)發(fā):ffplay使用ffmpeg濾鏡實(shí)現(xiàn)倍速播放

    曾經(jīng)為實(shí)現(xiàn)倍速播放使用過(guò)ffmpeg,對(duì)音頻使用atempo濾鏡即可實(shí)現(xiàn)變速不變調(diào)。但是當(dāng)時(shí)效果并不是特別好,和soundtouch相比處理后的音質(zhì)有明顯的區(qū)別。最近用新版本的ffmpeg濾鏡重新實(shí)現(xiàn)了倍速播放,發(fā)現(xiàn)效果變好,已經(jīng)達(dá)到可接受的程度,所以在此分享具體實(shí)現(xiàn)。 ffmpeg倍速

    2024年02月03日
    瀏覽(72)
  • opencv+ffmpeg+QOpenGLWidget開(kāi)發(fā)的音視頻播放器demo

    opencv+ffmpeg+QOpenGLWidget開(kāi)發(fā)的音視頻播放器demo

    ????本篇文檔的demo包含了 1.使用OpenCV對(duì)圖像進(jìn)行處理,對(duì)圖像進(jìn)行置灰,旋轉(zhuǎn),摳圖,高斯模糊,中值濾波,部分區(qū)域清除置黑,背景移除,邊緣檢測(cè)等操作;2.單純使用opencv播放顯示視頻;3.使用opencv和openGL播放顯示視頻;4.在ffmpeg解碼后,使用opencv顯示視頻,并支持對(duì)視

    2024年02月12日
    瀏覽(36)
  • 【音視頻開(kāi)發(fā)】FFmpeg轉(zhuǎn)換與封裝 I - MP4格式

    【音視頻開(kāi)發(fā)】FFmpeg轉(zhuǎn)換與封裝 I - MP4格式

    1 FFmpeg轉(zhuǎn)換與封裝 1.1 MP4格式轉(zhuǎn)換 1.1.1 MP4格式標(biāo)準(zhǔn) ????????FFmpeg支持的媒體封裝格式具有多樣性與全面性,與此, 我們還可以使用FFmpeg來(lái)對(duì)媒體格式進(jìn)行轉(zhuǎn)換與封裝 。 在互聯(lián)網(wǎng)常見(jiàn)的格式中,跨平臺(tái)最好的應(yīng)該是 MP4 文件,因?yàn)?MP4 文件既可以在PC 平臺(tái)的Flashplayer中播放,

    2024年02月08日
    瀏覽(35)
  • Qt/C++音視頻開(kāi)發(fā)50-不同ffmpeg版本之間的差異處理

    ffmpeg的版本眾多,從2010年開(kāi)始計(jì)算的項(xiàng)目的話(huà),基本上還在使用的有ffmpeg2/3/4/5/6,最近幾年版本彪的比較厲害,直接4/5/6,大版本之間接口有一些變化,特別是一些廢棄接口被徹底刪除了,而網(wǎng)絡(luò)上的各種文章幾乎都是ffmpeg3左右為主的,所以本人在寫(xiě)這個(gè)全功能播放組件的時(shí)

    2024年02月14日
    瀏覽(28)

覺(jué)得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包