摘要:本文通过阅读FFmpeg源码来理解FFmpeg中AVOption的实现原理和具体的使用方式。
  关键字:AVClss,AVOption,AVOptionRange
  版本:FFmpeg5.0

1 AVOption结构

  AVOption是FFmpeg中设置参数的一个基本抽象结构。因为FFmpeg是一个支持多种封装解封装器,编解码器的框架,而不同的外部库需要的参数各不相同,因此利用AVOption来封装一个基本的key-value结构来获取和设置对应模块的参数。AVOption本身就是一个key-value项,可以理解为C++中map中的项pair,而其中name就是keydefault_val就是value。而在实际使用中所有的参数是存储在AVClass中的AVOption数组中,而需要设置参数的模块会在Context结构体开头设置一个AVClass的指针来表示当前模块的参数,FFmpeg通过搜索该数组来获取和设置对应模块的参数。另外,并不是所有的参数都会存储在AVOption中,有些参数对应的Context本身就有对应的成员,因此AVOption只需要指定该成员相对应该Context首部地址的偏移即可进行设置和读取,也就是说通过AVOption设置和直接用Context->param = value的形式是一致的。

typedef struct AVOption {const char *name;           //参数名称const char *help;           //简短的帮助信息,因为是char因此无法支持中文等语言int offset;                 //选项相对于结构体首部的偏移量enum AVOptionType type;     //选项的类型union {int64_t i64;double dbl;const char *str;AVRational q;} default_val;              //选项的默认值double min;                 //选项可允许的最小值double max;                 //选项可允许的最大值int flags;                  //标志符const char *unit;           //当前选项所属的逻辑单元,可以为NULL
} AVOption;
  • default_value:当前选项的值是一个64bit的union,这基本能够表示所有的数值参数和字符串参数;

  • type:当前选项的类型,如下面的AVOptionType的定义,不仅仅输数据类型也涵盖了一些FFmpeg中用到的选项类型;

    enum AVOptionType{AV_OPT_TYPE_FLAGS,AV_OPT_TYPE_INT,AV_OPT_TYPE_INT64,AV_OPT_TYPE_DOUBLE,AV_OPT_TYPE_FLOAT,AV_OPT_TYPE_STRING,AV_OPT_TYPE_RATIONAL,AV_OPT_TYPE_BINARY,  ///< offset must point to a pointer immediately followed by an int for the lengthAV_OPT_TYPE_DICT,AV_OPT_TYPE_UINT64,AV_OPT_TYPE_CONST,AV_OPT_TYPE_IMAGE_SIZE, ///< offset must point to two consecutive integersAV_OPT_TYPE_PIXEL_FMT,AV_OPT_TYPE_SAMPLE_FMT,AV_OPT_TYPE_VIDEO_RATE, ///< offset must point to AVRationalAV_OPT_TYPE_DURATION,AV_OPT_TYPE_COLOR,AV_OPT_TYPE_CHANNEL_LAYOUT,AV_OPT_TYPE_BOOL,
    };
    
  • offset:如上面描述,这个offset是相对于包含AVClass的Context的首地址的偏移;

  • min,max:选项取值范围,需要注意的是这里声明的是double;

  • flagsflag描述当前选项的用处,如下定义;

    #define AV_OPT_FLAG_ENCODING_PARAM  1   ///< a generic parameter which can be set by the user for muxing or encoding
    #define AV_OPT_FLAG_DECODING_PARAM  2   ///< a generic parameter which can be set by the user for demuxing or decoding
    #define AV_OPT_FLAG_AUDIO_PARAM     8
    #define AV_OPT_FLAG_VIDEO_PARAM     16
    #define AV_OPT_FLAG_SUBTITLE_PARAM  32
    //The option is intended for exporting values to the caller.
    #define AV_OPT_FLAG_EXPORT          64
    //The option may not be set through the AVOptions API, only read. This flag only makes sense when AV_OPT_FLAG_EXPORT is also set.
    #define AV_OPT_FLAG_READONLY        128
    #define AV_OPT_FLAG_BSF_PARAM       (1<<8) ///< a generic parameter which can be set by the user for bit stream filtering
    #define AV_OPT_FLAG_RUNTIME_PARAM   (1<<15) ///< a generic parameter which can be set by the user at runtime
    #define AV_OPT_FLAG_FILTERING_PARAM (1<<16) ///< a generic parameter which can be set by the user for filtering
    #define AV_OPT_FLAG_DEPRECATED      (1<<17) ///< set if option is deprecated, users should refer to AVOption.help text for more information
    #define AV_OPT_FLAG_CHILD_CONSTS    (1<<18) ///< set if option constants can also reside in child objects
    

2 AVClass结构

  由于设置AVOption都是通过AVClass进行的,因此有必要了解下AVClass的结构。AVClass是FFmpeg中描述一个Context的抽象,FFmpeg中的Context的第一个成员就是一个AVClass的指针来描述当前Context的基本信息和相关的参数,设置参数也是通过这个指针进行的。比如AVFormatContext

typedef struct AVFormatContext {//A class for logging and @ref avoptions. Set by avformat_alloc_context(). Exports (de)muxer private options if they exist.const AVClass *av_class;//省略大部分代码
}AVFormatContext;
typedef struct AVClass {const char* class_name;                            //AVClass所属类的名称const char* (*item_name)(void* ctx);               //获取AVClass所属类名称的函数指针,有些实现会直接返回AVClass->class_nameconst struct AVOption *option;                     //当前类的参数,没有就置为NULLint version;                                       //当前字段创建的版本,可用于版本控制,This is used to allow fields to be added without requiring major version bumps everywhere.int log_level_offset_offset;                       //AVClass所属结构体中log_level_offset相对于其首地址的偏移,0表示没有该成员int parent_log_context_offset;                     //当前Context中存储parent context的偏移量void* (*child_next)(void *obj, void *prev);        //AVOptions中下一个可用的参数AVClassCategory category;                          //当前类的类别AVClassCategory (*get_category)(void* ctx);        //获取当前Context类别的函数指针      //查询对应选项的范围,虽然定义了但是FFmpeg源码中好像没有API用到int (*query_ranges)(struct AVOptionRanges **, void *obj, const char *key, int flags);/*** Iterate over the AVClasses corresponding to potential AVOptions-enabled children.* @param iter pointer to opaque iteration state. The caller must initialize *iter to NULL before the first call.* @return AVClass for the next AVOptions-enabled child or NULL if there are no more such children.* @note The difference between child_next and this is that child_next iterates over _already existing_ objects, while child_class_iterate iterates over _all possible_ children.const struct AVClass* (*child_class_iterate)(void **iter);
} AVClass;

  AVClassCategory定义如下:

typedef enum {AV_CLASS_CATEGORY_NA = 0,AV_CLASS_CATEGORY_INPUT,AV_CLASS_CATEGORY_OUTPUT,AV_CLASS_CATEGORY_MUXER,AV_CLASS_CATEGORY_DEMUXER,AV_CLASS_CATEGORY_ENCODER,AV_CLASS_CATEGORY_DECODER,AV_CLASS_CATEGORY_FILTER,AV_CLASS_CATEGORY_BITSTREAM_FILTER,AV_CLASS_CATEGORY_SWSCALER,AV_CLASS_CATEGORY_SWRESAMPLER,AV_CLASS_CATEGORY_DEVICE_VIDEO_OUTPUT = 40,AV_CLASS_CATEGORY_DEVICE_VIDEO_INPUT,AV_CLASS_CATEGORY_DEVICE_AUDIO_OUTPUT,AV_CLASS_CATEGORY_DEVICE_AUDIO_INPUT,AV_CLASS_CATEGORY_DEVICE_OUTPUT,AV_CLASS_CATEGORY_DEVICE_INPUT,AV_CLASS_CATEGORY_NB  ///< not part of ABI/API
}AVClassCategory;

  FFmpeg源码在libavutil/tests/opt.c中有示例程序TestContext,下面只截取其中一部分。从下面可以看到TestContext第一个成员便是AVClass的指针,然后下面定义了所有选项的数组并将该数组传递给AVClassoption成员。而其中选项不同类型初始化方式也不同有些是直接写值,有些是通过偏移写TestContext的成员变量的值:

typedef struct TestContext {const AVClass *class;int num;int flags;AVRational rational;enum AVPixelFormat pix_fmt;char *escape;
} TestContext;#define OFFSET(x) offsetof(TestContext, x)#define TEST_FLAG_COOL 01
#define TEST_FLAG_LAME 02
#define TEST_FLAG_MU   04static const AVOption test_options[]= {{"num",        "set num",            OFFSET(num),            AV_OPT_TYPE_INT,            { .i64 = 0 },                      0,       100, 1 },{"rational",   "set rational",       OFFSET(rational),       AV_OPT_TYPE_RATIONAL,       { .dbl = 1 },                      0,        10, 1 },{"escape",     "set escape str",     OFFSET(escape),         AV_OPT_TYPE_STRING,         { .str = "\\=," },          CHAR_MIN,  CHAR_MAX, 1 },{"flags",      "set flags",          OFFSET(flags),          AV_OPT_TYPE_FLAGS,          { .i64 = 1 },                      0,   INT_MAX, 1, "flags" },{"cool",       "set cool flag",      0,                      AV_OPT_TYPE_CONST,          { .i64 = TEST_FLAG_COOL },   INT_MIN,   INT_MAX, 1, "flags" },{"pix_fmt",    "set pixfmt",         OFFSET(pix_fmt),        AV_OPT_TYPE_PIXEL_FMT,      { .i64 = AV_PIX_FMT_0BGR },       -1,   INT_MAX, 1 },
};static const char *test_get_name(void *ctx)
{return "test";
}static const AVClass test_class = {.class_name = "TestContext",.item_name  = test_get_name,.option     = test_options,
};

3 AVOptionRange及相关的API

AVOption结构

typedef struct AVOptionRange {const char *str;                       //字符串描述double value_min, value_max;         //对于字符串表示最大最小长度。对于尺寸,这表示多组件情况下的最小/最大像素数或宽度/高度。double component_min, component_max;   //对于字符串,这表示字符的 unicode 范围,0-127 限制为 ASCII。int is_range;                           //是不是一个范围,0的话就是一个单纯的数值
} AVOptionRange;//List of AVOptionRange structs.
typedef struct AVOptionRanges {AVOptionRange **range;                   //二维数组,数组中元素的数目为nb_ranges * nb_componentsint nb_ranges;int nb_components;                        //每个component有nb_ranges个AVOptionRange
} AVOptionRanges;

  下面是FFmpeg代码中给出的示例:

 /* AV_OPT_TYPE_IMAGE_SIZE:* component index 0: range of pixel count (width * height).* component index 1: range of width.* component index 2: range of height.*/
int range_index, component_index;
AVOptionRanges *ranges;
AVOptionRange *range[3]; //may require more than 3 in the future.
av_opt_query_ranges(&ranges, obj, key, AV_OPT_MULTI_COMPONENT_RANGE);
for (range_index = 0; range_index < ranges->nb_ranges; range_index++) {for (component_index = 0; component_index < ranges->nb_components; component_index++)range[component_index] = ranges->range[ranges->nb_ranges * component_index + range_index];//do something with range here.
}
av_opt_freep_ranges(&ranges);

相关API描述

  • int av_opt_query_ranges_default(AVOptionRanges **, void *obj, const char *key, int flags):获取对应的key的取值范围,实现中只有一个AVOptionRange项:
    ranges->nb_ranges = 1;ranges->nb_components = 1;range->is_range = 1;
  • int av_opt_query_ranges(AVOptionRanges **ranges_arg, void *obj, const char *key, int flags):具体实现调用的是obj中的query_ranges,如果未定义则使用默认的av_opt_query_ranges_default
  • void av_opt_freep_ranges(AVOptionRanges **ranges):释放AVOptionRanges
  • **

4 AVOption相关API

  AVOption相关API的第一个参数都需要是一个结构体指针,该结构的第一个成员必须是AVClass的指针。

  • const AVOption *av_opt_next(const void *obj, const AVOption *prev):返回相对于prev的下一个AVOption。实现比较简单既然有了prevAVOption存储在数组中那么prev自增即可,只是多了一些边界检查;

    • obj:第一个成员为AVClass指针的结构体;
    • prev:需要寻找选项的上一个选项,如果为NULL则返回第一个;
    • 返回值:prev下一个选项;
const AVOption *av_opt_next(const void *obj, const AVOption *last){const AVClass *class;if (!obj)return NULL;class = *(const AVClass**)obj;if (!last && class && class->option && class->option[0].name)return class->option;if (last && last[1].name)return ++last;return NULL;
}
  • void *av_opt_child_next(void *obj, void *prev):使用AVClass中的child_next返回下一个可用的参数选型;
void *av_opt_child_next(void *obj, void *prev){const AVClass *c = *(AVClass **)obj;if (c->child_next)return c->child_next(obj, prev);return NULL;
}
  • const AVClass *av_opt_child_class_iterate(const AVClass *parent, void * *iter):搜寻子类的AVClass,c中没有子类的概念,这里的子类由AVClasschild_class_iterate定义,具体实现也是调用的child_class_iterate取搜索的;

    • iter:存储搜索标志位的地址;
  • const AVOption *av_opt_find2(void *obj, const char *name, const char *unit, int opt_flags, int search_flags, void * *target_obj):在obj中搜索目标参数:
    • 搜索成功的条件:(string(o->name) == string(name)) && ((o->flags & opt_flags) == opt_flags) && ((!unit && o->type != AV_OPT_TYPE_CONST) || (unit && o->type == AV_OPT_TYPE_CONST && o->unit && string(o->unit) == string(unit))))
    • obj:应该是一个第一个成员指针为AVClass的结构体;
    • name:希望搜索的参数的名称;
    • unit:搜索的参数所属的逻辑单元;
    • opt_flags:期望搜索的参数的标志位;
    • search_flags:搜索的目标的标志位,如果设置了AV_OPT_SEARCH_CHILDREN则搜索的是由child_class_iterate定义的子类;
    • target_obj:搜索的目标,如果未设置AV_OPT_SEARCH_CHILDREN则为NULL或者输入的obj,否则为对应的子类的指针或者NULL(是否为NULL根据search_flags是否设置了AV_OPT_SEARCH_FAKE_OBJ而定);
    • const AVOption *av_opt_find(void *obj, const char *name, const char *unit, int opt_flags, int search_flags):调用av_opt_find2实现,只不过无法搜索子类的选项;
const AVOption *av_opt_find2(void *obj, const char *name, const char *unit, int opt_flags, int search_flags, void **target_obj){//下面是简化的代码c= *(AVClass**)obj;if (search_flags & AV_OPT_SEARCH_CHILDREN) {if (search_flags & AV_OPT_SEARCH_FAKE_OBJ) {void *iter = NULL;const AVClass *child;while (child = av_opt_child_class_iterate(c, &iter))if (o = av_opt_find2(&child, name, unit, opt_flags, search_flags, NULL))return o;} else {void *child = NULL;while (child = av_opt_child_next(obj, child))if (o = av_opt_find2(child, name, unit, opt_flags, search_flags, target_obj))return o;}}while (o = av_opt_next(obj, o)) {if(符合条件 && target_obj)if (!(search_flags & AV_OPT_SEARCH_FAKE_OBJ))*target_obj = obj;else*target_obj = NULL;}return o;}}return NULL;
}
  • av_opt_set(void *obj, const char *name, const char *val, int search_flags):在obj中搜索name选项并设置对应的参数,如果对应的参数是只读参数或者搜索失败会返回错误;

    • obj:第一个成员为AVClass的结构体指针;
    • name:希望设置参数的选项的key;
    • val:key对应的值,以字符串的形式存储,具体的参数类型内部会根据AVOption的类型来进行转换;
    • search_flags:搜索目标选项的标志位,内部搜索通过av_opt_find2实现;
int av_opt_set(void *obj, const char *name, const char *val, int search_flags){int ret = 0;void *dst, *target_obj;const AVOption *o = av_opt_find2(obj, name, NULL, 0, search_flags, &target_obj);//然后将val根据类型转换成对应的值设置进o
}
  • void av_opt_free(void *obj):释放所有的AVOption,只有AV_OPT_TYPE_STRING|AV_OPT_TYPE_BINARY|AV_OPT_TYPE_DICT三中类型需要释放;
  • int av_opt_copy(void *dst, const void *src):拷贝一份;
  • 其他设置参数的API,下面这些API基本都是设置对应的类型的具体实现,和av_opt_set的实现类似,先搜索再设置,只不过有些需要通过偏移量写入到obj的成员变量里,具体的作用从API的名称也能看出来不再赘述:
    • av_opt_set_defaults(void *s):设置s中所有的AVOption值为默认值,s的以一个指针应该为AVClass
    • av_opt_set_defaults2(void *s, int mask, int flags):仅仅设置(opt->flags & mask) == flags的选项的默认值,av_opt_set_defaults也是调用av_opt_set_defaults2实现的;
    • av_set_options_string(void *ctx, const char *opts, const char *key_val_sep, const char *pairs_sep):设置一系列参数;
      • ctx:第一个成员为AVClass的结构体指针;
      • opts:多个键值对,键值对之间以paris_seq为分隔符,键值对内以key_val_sep为分隔符,比如width=30;height=40;bitrate=3000
      • key_val_sep:分隔key-val的字符串,比如key=value中分隔符为=
      • pairs_sep:分隔一对键值对的字符串,比如key1=val1;key2=val2中分隔符为;
    • int av_opt_set_from_string(void *ctx, const char *opts, const char *const *shorthand, const char *key_val_sep, const char *pairs_sep):基本参数和定义和av_set_options_string类似,不同的是多了一个shorthand参数:
      • shorthand:多个字符串的数组,如果opts中的前几个键值对未解析出key,则会使用shorthand中的值作为key。比如opts="5:hello:size=pal",shorthand={ "num", "string", NULL };则设置的结果是num=5,string=hello
    • int av_opt_set_dict(void *obj, struct AVDictionary * *options):遍历options内的值然后将值一一设置进obj;
    • int av_opt_set_dict2(void *obj, struct AVDictionary **options, int search_flags):类似av_opt_set_dictsearch_flags作用于av_opt_set
    • int av_opt_set_int (void *obj, const char *name, int64_t val, int search_flags)
    • int av_opt_set_double (void *obj, const char *name, double val, int search_flags)
    • int av_opt_set_q (void *obj, const char *name, AVRational val, int search_flags)
    • int av_opt_set_bin (void *obj, const char *name, const uint8_t *val, int size, int search_flags)
    • int av_opt_set_image_size(void *obj, const char *name, int w, int h, int search_flags)
    • int av_opt_set_pixel_fmt (void *obj, const char *name, enum AVPixelFormat fmt, int search_flags)
    • int av_opt_set_sample_fmt(void *obj, const char *name, enum AVSampleFormat fmt, int search_flags)
    • int av_opt_set_video_rate(void *obj, const char *name, AVRational val, int search_flags)
    • int av_opt_set_channel_layout(void *obj, const char *name, int64_t ch_layout, int search_flags)
    • int av_opt_set_dict_val(void *obj, const char *name, const AVDictionary *val, int search_flags)
  • int av_opt_get (void *obj, const char *name, int search_flags, uint8_t * *out_val):获取的实现就是通过avav_opt_find2搜索然后再将数据转成字符串传出;
  • 其他获取的API:
    • int av_opt_flag_is_set(void *obj, const char *field_name, const char *flag_name):获取某个flag是否设置,具体实现就是先搜索然后通过av_opt_get_int获取值;

      • obj:第一个成员为AVClass的结构体指针;
      • field_name:对应flag所属的标签;
      • flag_name:具体的flag的key;
    • int av_opt_get_key_value(const char * *ropts, const char *key_val_sep, const char *pairs_sep, unsigned flags, char * *rkey, char * *rval):从给定的字符串键值对中解析出第一个键值对并返回;
    • int av_opt_get_int (void *obj, const char *name, int search_flags, int64_t *out_val)
    • int av_opt_get_double (void *obj, const char *name, int search_flags, double *out_val)
    • int av_opt_get_q (void *obj, const char *name, int search_flags, AVRational *out_val)
    • int av_opt_get_image_size(void *obj, const char *name, int search_flags, int *w_out, int *h_out)
    • int av_opt_get_pixel_fmt (void *obj, const char *name, int search_flags, enum AVPixelFormat *out_fmt)
    • int av_opt_get_sample_fmt(void *obj, const char *name, int search_flags, enum AVSampleFormat *out_fmt)
    • int av_opt_get_video_rate(void *obj, const char *name, int search_flags, AVRational *out_val)
    • int av_opt_get_channel_layout(void *obj, const char *name, int search_flags, int64_t *ch_layout)
    • int av_opt_get_dict_val(void *obj, const char *name, int search_flags, AVDictionary * *out_val)
    • void *av_opt_ptr(const AVClass *class, void *obj, const char *name):返回AVClass所属Context中对应成员的地址,比如返回AVCodecContext->width的地址;
    • int av_opt_is_set_to_default_by_name(void *obj, const char *name, int search_flags):通过name搜索然后通过av_opt_is_set_to_default获取;
    • int av_opt_serialize(void *obj, int opt_flags, int flags, char * *buffer, const char key_val_sep, const char pairs_sep):将obj中的选项序列化为一个字符串,字符串中键值对间的间隔为pairs_sep,键值对内的间隔为key_val_sep
  • 其他API:
    • av_opt_show2(void *obj, void *av_log_obj, int req_flags, int rej_flags):通过av_log将当前context支持的选项输出,默认输出到控制台,可以通过FFmpeg内的av_log_callback重定向输出:

      • obj:是AVClass所属类的指针比如AVFormatContext
      • av_log_obj:一般情况置空,可以传入对应的Context的指针来显示更丰富的内容;
      • req_flagsrej_flags**:用来过滤需要显示的内容,req_flags是显示需要显示的,rej_flags是不显示对应的内容。
    • 下面几个API在头文件中有定义但是看不到实现,从注释看和av_opt_set类似,只不过会将结果写会最后一个指针来校验是否写正确:
      • int av_opt_eval_flags (void *obj, const AVOption *o, const char *val, int *flags_out)
      • av_opt_eval_int (void *obj, const AVOption *o, const char *val, int *int_out)
      • int av_opt_eval_int64 (void *obj, const AVOption *o, const char *val, int64_t *int64_out)
      • int av_opt_eval_float (void *obj, const AVOption *o, const char *val, float *float_out)
      • int av_opt_eval_double(void *obj, const AVOption *o, const char *val, double *double_out)
      • int av_opt_eval_q (void *obj, const AVOption *o, const char *val, AVRational *q_out)

FFmpeg5.0源码阅读之AVClass和AVOption相关推荐

  1. FFmpeg5.0源码阅读——AVFrame

      摘要:AVFrame是FFmpeg中表示裸数据的结构体,是FFmpeg最重要的结构体之一.本篇文章针对FFmpeg源码理解AVFrame的作用,相关的结构定义以及一些操作API的具体实现.   关 ...

  2. FFmpeg5.0源码阅读——mov文件格式解析

    摘要:之前在Mp4格式详解中详细描述了Mp4文件格式的具体布局方式.为了更加深入理解mp4文件格式,本文记录了ffmpeg中解封装mp4文件的基本实现. 关键字:mov.FFmpeg.mp4 1 简介 ...

  3. FFmpeg5.0源码阅读——avformat_open_input

      摘要:本文主要描述了FFmpeg中用于打开文件接口avformat_open_input的具体调用流程,详细描述了该接口被调用时所作的具体工作.   关键字:ffmpeg.avformat_ope ...

  4. FFmpeg5.0源码阅读—— av_read_frame

      摘要:本文主要描述了FFmpeg中用于打开编解码器接口av_read_frame的具体调用流程,详细描述了该接口被调用时所作的具体工作.   关键字:ffmpeg.av_read_frame    ...

  5. FFmpeg5.0源码阅读——av_interleaved_write_frame

      摘要:本文主要详细描述FFmpeg中封装时写packet到媒体文件的函数av_interleaved_write_frame的实现.   关键字:av_interleaved_write_fram ...

  6. FFmpeg5.0源码阅读—— avcodec_open2

      摘要:本文主要描述了FFmpeg中用于打开编解码器接口avcodec_open2大致流程的具体调用流程,详细描述了该接口被调用时所作的具体工作.   关键字:ffmpeg.avcodec_open ...

  7. Spring Boot 2.0系列文章(四):Spring Boot 2.0 源码阅读环境搭建

    前提 前几天面试的时候,被问过 Spring Boot 的自动配置源码怎么实现的,没看过源码的我只能投降��了. 这不,赶紧来补补了,所以才有了这篇文章的出现,Spring Boot 2. 0 源码阅 ...

  8. CloudSim 4.0源码阅读笔记(功耗实例)

    文章目录 一.NonPowerAware案例 1.1 基本概述 1.2 云任务/虚机/主机/功耗模型参数设置 1.3 初始化云任务(CloudletList)-如何载入自定义真实数据集中的CPU利用率 ...

  9. AFNetworking 3.0源码阅读 - AFURLResponseSerialization

    这次来说一下AFURLResponseSerialization这个HTTP响应类. 定义了一个协议,该协议返回序列化后的结果.后续的AFHTTPResponseSerializer以及他的子类都遵循 ...

最新文章

  1. 女生做软件测试需要学习什么技术?
  2. 海伦公式c语言编程funcd,c语言编程练习题及答案_0.doc
  3. 推荐一位零基础转 Python 的大佬
  4. android webview 获取网页内容,在WebView中获取网页中的内容
  5. extjs 提交表单给php,JavaScript_Extjs学习笔记之二 初识Extjs之Form,Extjs中的表单组件是Ext.form.Basic - phpStudy...
  6. 董明珠:我不是要把奥克斯整死 我希望它改邪归正
  7. ros发布节点信息python_ROS入门笔记(一): ROS简介
  8. 为什么程序必须得会C语言?
  9. linux+awk过滤端口,Awk简单过滤[1]
  10. debug命令_Python 必备 debug 神器:pdb
  11. C#信息采集系统,常见控件练习
  12. switch 大气层双系统 进入破解系统 及安装nsp nsz 格式教程
  13. 【theano-windows】学习笔记五——theano中张量部分函数
  14. 时间序列分析之GARCH模型介绍与应用
  15. symbian 串行通信
  16. Android 11 新特性和API兼容
  17. SAP中汇率固定配置和应用分析测试
  18. P02014158 马帅(信息论课程作业)
  19. 滤镜功能针的萌翻了!Snapchat为狗狗配戴眼镜
  20. JBPM工作流引擎内核设计思想及构架

热门文章

  1. puzzle(1036)数邻、多米诺骨牌
  2. 感谢时光让我在最美的年纪遇到你
  3. redis课程视频 黄建宏_求redis从入门到精通视频教程【50讲全】
  4. mysql数据库约束详解_深入理解mysql数据库的约束
  5. 计算机主机箱中最大的一块印刷电路板是,2计算机系统的硬件和软件
  6. Linux-网络命令
  7. 爱奇艺动态化框架 Qigsaw 正式开源!
  8. 苹果iPhone 12系列发布会:四款新机亮相均支持5G
  9. python将横转为竖_如何将视频从横屏转为竖屏?
  10. java网络编程---使用URL爬取歌曲