aom2.0的工程中,aomenc是编码端工程,主要完成视频序列的编码,其入口是amenc.c中的main函数

main函数的主要流程如下:

  1. 初始化
  2. 解析命令行参数
    1. parse_global_config 解析全局编码配置文件参数
    2. parse_stream_params 解析视频流参数
  3. 进入编码过程循环 pass
    1. 打开输入文件open_input_file
    2. 设置视频流和编解码器配置
      1. set_stream_dimensions 设置视频流的维度
      2. validate_stream_config 验证视频流的配置
      3. show_stream_config 打印显示视频流的配置
    3. setup_pass 设置当前编码过程参数
    4. initialize_encoder 初始化编码器
    5. open_output_file 打开输出文件
    6. 循环读取视频中的每一帧
      1. read_frame 读取当前帧(如果没有限制读取的帧数或者当前读取的帧数小于限制读取的帧数)
      2. encode_frame 对当前帧进行编码(如果当前读取的帧大于需要跳过的前n帧,则对当前帧进行编码)
      3. update_quantizer_histogram 更新量化器
      4. get_cx_data 获取编码后的数据并将其写入码流中
    7. 打印相关信息(编码时间、PSNR等)
    8. 关闭输入、输出文件
  4. 释放内存
  5. 返回编码成功或者失败的标志

注意:

编码过程循环是由全局编码配置参数passes和pass控制的,其中passes指的是执行的总的编码过程次数(1/2),pass指的当前执行的编码过程(1/2),AV1的编码过程分类如下:

enum aom_enc_pass {AOM_RC_ONE_PASS,   /**< Single pass mode */AOM_RC_FIRST_PASS, /**< First pass of multi-pass mode */AOM_RC_LAST_PASS   /**< Final pass of multi-pass mode */
};

AV1默认是执行两次编码过程,First Pass主要是收集一些信息,用来加速Second Pass

main函数代码及注释如下:

int main(int argc, const char **argv_) {int pass;aom_image_t raw;//原始帧aom_image_t raw_shift;//用来处理高比特深度情况下的帧int allocated_raw_shift = 0;int use_16bit_internal = 0;int input_shift = 0;int frame_avail, got_data;struct AvxInputContext input;//打开输入文件的上下文struct AvxEncoderConfig global;//编码器的配置文件struct stream_state *streams = NULL;char **argv, **argi;uint64_t cx_time = 0;int stream_cnt = 0;int res = 0;int profile_updated = 0;memset(&input, 0, sizeof(input));exec_name = argv_[0];/* Setup default input stream settings */// 设置默认输入流设置input.framerate.numerator = 30;input.framerate.denominator = 1;input.only_i420 = 1;input.bit_depth = 0;/* First parse the global configuration values, because we want to apply* other parameters on top of the default configuration provided by the* codec.*/// 首先解析全局配置值,因为我们希望在编解码器提供的默认配置之上应用其他参数,所有流使用的编码器配置argv = argv_dup(argc - 1, argv_ + 1);parse_global_config(&global, &argv);if (argc < 2) usage_exit();//如果输入参数小于2,则退出switch (global.color_type) {//输入的文件类型case I420: input.fmt = AOM_IMG_FMT_I420; break;case I422: input.fmt = AOM_IMG_FMT_I422; break;case I444: input.fmt = AOM_IMG_FMT_I444; break;case YV12: input.fmt = AOM_IMG_FMT_YV12; break;}{/* Now parse each stream's parameters. Using a local scope here* due to the use of 'stream' as loop variable in FOREACH_STREAM* loops*/// 现在解析每个流的参数// 由于在FOREACH_stream循环中使用“stream”作为循环变量,因此在此处使用局部范围struct stream_state *stream = NULL;do {stream = new_stream(&global, stream);//新建一个stream流stream_cnt++;if (!streams) streams = stream;//streams指向第一个stream流} while (parse_stream_params(&global, stream, argv));//解析stream流}/* Check for unrecognized options */// 检查未识别的选项for (argi = argv; *argi; argi++)if (argi[0][0] == '-' && argi[0][1])die("Error: Unrecognized option %s\n", *argi);FOREACH_STREAM(stream, streams) { //遍历所有的stream流check_encoder_config(global.disable_warning_prompt, &global,&stream->config.cfg);// If large_scale_tile = 1, only support to output to ivf format.// 如果large_scale_tile=1,则只支持输出为ivf格式。if (stream->config.cfg.large_scale_tile && !stream->config.write_ivf)die("only support ivf output format while large-scale-tile=1\n");}/* Handle non-option arguments */// 处理没有命令行的情况input.filename = argv[0];if (!input.filename) {fprintf(stderr, "No input file specified!\n");usage_exit();}/* Decide if other chroma subsamplings than 4:2:0 are supported */// 决定是否支持4:2:0以外的其他色度子采样if (global.codec->fourcc == AV1_FOURCC) input.only_i420 = 0;/*************开始编码,默认编码过程分为两个PASS***************/for (pass = global.pass ? global.pass - 1 : 0; pass < global.passes; pass++) {int frames_in = 0, seen_frames = 0; //frame_in指的是读取的帧数,seen_frames表示显示的帧数int64_t estimated_time_left = -1;int64_t average_rate = -1;int64_t lagged_count = 0;//打开输入文件open_input_file(&input, global.csp);/* If the input file doesn't specify its w/h (raw files), try to get* the data from the first stream's configuration.*/// 如果输入文件没有指定其w/h(原始文件),请尝试从第一个流的配置中获取数据。if (!input.width || !input.height) {FOREACH_STREAM(stream, streams) {if (stream->config.cfg.g_w && stream->config.cfg.g_h) {input.width = stream->config.cfg.g_w;input.height = stream->config.cfg.g_h;break;}};}/* Update stream configurations from the input file's parameters */// 从输入文件的参数更新流配置if (!input.width || !input.height)fatal("Specify stream dimensions with --width (-w) "" and --height (-h)");/* If input file does not specify bit-depth but input-bit-depth parameter* exists, assume that to be the input bit-depth. However, if the* input-bit-depth paramter does not exist, assume the input bit-depth* to be the same as the codec bit-depth.*//** 如果输入文件未指定位深度,但存在输入位深度参数,* 则假定为输入位深度。但是,如果输入位深度参数不存在,* 则假设输入位深度与编解码器位深度相同。*/if (!input.bit_depth) {FOREACH_STREAM(stream, streams) {if (stream->config.cfg.g_input_bit_depth)input.bit_depth = stream->config.cfg.g_input_bit_depth;elseinput.bit_depth = stream->config.cfg.g_input_bit_depth =(int)stream->config.cfg.g_bit_depth;}if (input.bit_depth > 8) input.fmt |= AOM_IMG_FMT_HIGHBITDEPTH;} else {FOREACH_STREAM(stream, streams) {stream->config.cfg.g_input_bit_depth = input.bit_depth;}}// 遍历所有的视频流,设置视频流的配置文件和编解码器的配置文件FOREACH_STREAM(stream, streams) {if (input.fmt != AOM_IMG_FMT_I420 && input.fmt != AOM_IMG_FMT_I42016) {/* Automatically upgrade if input is non-4:2:0 but a 4:2:0 profilewas selected. */// 如果输入类型不是4:2:0,但选择了4:2:0配置文件,则自动升级。switch (stream->config.cfg.g_profile) {case 0:if (input.bit_depth < 12 && (input.fmt == AOM_IMG_FMT_I444 ||input.fmt == AOM_IMG_FMT_I44416)) {if (!stream->config.cfg.monochrome) {stream->config.cfg.g_profile = 1;profile_updated = 1;}} else if (input.bit_depth == 12 || input.fmt == AOM_IMG_FMT_I422 ||input.fmt == AOM_IMG_FMT_I42216) {stream->config.cfg.g_profile = 2;profile_updated = 1;}break;case 1:if (input.bit_depth == 12 || input.fmt == AOM_IMG_FMT_I422 ||input.fmt == AOM_IMG_FMT_I42216) {stream->config.cfg.g_profile = 2;profile_updated = 1;} else if (input.bit_depth < 12 &&(input.fmt == AOM_IMG_FMT_I420 ||input.fmt == AOM_IMG_FMT_I42016)) {stream->config.cfg.g_profile = 0;profile_updated = 1;}break;case 2:if (input.bit_depth < 12 && (input.fmt == AOM_IMG_FMT_I444 ||input.fmt == AOM_IMG_FMT_I44416)) {stream->config.cfg.g_profile = 1;profile_updated = 1;} else if (input.bit_depth < 12 &&(input.fmt == AOM_IMG_FMT_I420 ||input.fmt == AOM_IMG_FMT_I42016)) {stream->config.cfg.g_profile = 0;profile_updated = 1;} else if (input.bit_depth == 12 &&input.file_type == FILE_TYPE_Y4M) {// Note that here the input file values for chroma subsampling// are used instead of those from the command line.AOM_CODEC_CONTROL_TYPECHECKED(&stream->encoder,AV1E_SET_CHROMA_SUBSAMPLING_X,input.y4m.dst_c_dec_h >> 1);AOM_CODEC_CONTROL_TYPECHECKED(&stream->encoder,AV1E_SET_CHROMA_SUBSAMPLING_Y,input.y4m.dst_c_dec_v >> 1);} else if (input.bit_depth == 12 &&input.file_type == FILE_TYPE_RAW) {AOM_CODEC_CONTROL_TYPECHECKED(&stream->encoder,AV1E_SET_CHROMA_SUBSAMPLING_X,stream->chroma_subsampling_x);AOM_CODEC_CONTROL_TYPECHECKED(&stream->encoder,AV1E_SET_CHROMA_SUBSAMPLING_Y,stream->chroma_subsampling_y);}break;default: break;}}/* Automatically set the codec bit depth to match the input bit depth.* Upgrade the profile if required. */// 自动设置编解码器位深度以匹配输入位深度。如果需要,请升级配置文件。if (stream->config.cfg.g_input_bit_depth >(unsigned int)stream->config.cfg.g_bit_depth) {stream->config.cfg.g_bit_depth = stream->config.cfg.g_input_bit_depth;if (!global.quiet) {fprintf(stderr,"Warning: automatically updating bit depth to %d to ""match input format.\n",stream->config.cfg.g_input_bit_depth);}}if (stream->config.cfg.g_bit_depth > 10) {switch (stream->config.cfg.g_profile) {case 0:case 1:stream->config.cfg.g_profile = 2;profile_updated = 1;break;default: break;}}if (stream->config.cfg.g_bit_depth > 8) {stream->config.use_16bit_internal = 1;}if (profile_updated && !global.quiet) {fprintf(stderr,"Warning: automatically updating to profile %d to ""match input format.\n",stream->config.cfg.g_profile);}/* Set limit */stream->config.cfg.g_limit = global.limit;}//设置流的维度FOREACH_STREAM(stream, streams) {set_stream_dimensions(stream, input.width, input.height);}// 检验流的配置FOREACH_STREAM(stream, streams) { validate_stream_config(stream, &global); }/* Ensure that --passes and --pass are consistent. If --pass is set and* --passes=2, ensure --fpf was set.*/// 确保--passes和--pass一致。如果设置了--pass并且--passes=2,请确保设置了--fpf。if (global.pass && global.passes == 2) {FOREACH_STREAM(stream, streams) {if (!stream->config.stats_fn)die("Stream %d: Must specify --fpf when --pass=%d"" and --passes=2\n",stream->index, global.pass);}}#if !CONFIG_WEBM_IOFOREACH_STREAM(stream, streams) {if (stream->config.write_webm) {stream->config.write_webm = 0;stream->config.write_ivf = 0;warn("aomenc compiled w/o WebM support. Writing OBU stream.");}}
#endif/* Use the frame rate from the file only if none was specified* on the command-line.*/// 仅当在命令行上未指定时,才使用文件的帧速率。if (!global.have_framerate) {global.framerate.num = input.framerate.numerator;global.framerate.den = input.framerate.denominator;}FOREACH_STREAM(stream, streams) {stream->config.cfg.g_timebase.den = global.framerate.num;stream->config.cfg.g_timebase.num = global.framerate.den;}/* Show configuration */// 显示配置if (global.verbose && pass == 0) {FOREACH_STREAM(stream, streams) {show_stream_config(stream, &global, &input);}}if (pass == (global.pass ? global.pass - 1 : 0)) {if (input.file_type == FILE_TYPE_Y4M)/*The Y4M reader does its own allocation.Just initialize this here to avoid problems if we never read anyframes.*/memset(&raw, 0, sizeof(raw));elseaom_img_alloc(&raw, input.fmt, input.width, input.height, 32);FOREACH_STREAM(stream, streams) {stream->rate_hist =init_rate_histogram(&stream->config.cfg, &global.framerate);//初始化速率直方图}}FOREACH_STREAM(stream, streams) { setup_pass(stream, &global, pass); }//配置编码过程FOREACH_STREAM(stream, streams) { initialize_encoder(stream, &global); } //初始化编码器FOREACH_STREAM(stream, streams) {open_output_file(stream, &global, &input.pixel_aspect_ratio); //打开输出文件ivf}if (strcmp(global.codec->name, "av1") == 0 ||strcmp(global.codec->name, "av1") == 0) {// Check to see if at least one stream uses 16 bit internal.// Currently assume that the bit_depths for all streams using// highbitdepth are the same.// 检查是否至少有一个流使用16位内部流。// 目前假设所有使用高位深度的流的位_深度是相同的。FOREACH_STREAM(stream, streams) {if (stream->config.use_16bit_internal) {use_16bit_internal = 1;}input_shift = (int)stream->config.cfg.g_bit_depth -stream->config.cfg.g_input_bit_depth;};}frame_avail = 1;got_data = 0;while (frame_avail || got_data) { //循环读取视频帧struct aom_usec_timer timer;// 如果没有限制读取的帧数或者当前读取的帧数小于限制读取的帧数if (!global.limit || frames_in < global.limit) {frame_avail = read_frame(&input, &raw);//读取视频帧if (frame_avail) frames_in++;// 不显示跳过的前n帧seen_frames =frames_in > global.skip_frames ? frames_in - global.skip_frames : 0;// 打印编码进度if (!global.quiet) {// quiet表示"不打印编码进度"float fps = usec_to_fps(cx_time, seen_frames);fprintf(stderr, "\rPass %d/%d ", pass + 1, global.passes);if (stream_cnt == 1)fprintf(stderr, "frame %4d/%-4d %7" PRId64 "B ", frames_in,streams->frames_out, (int64_t)streams->nbytes);elsefprintf(stderr, "frame %4d ", frames_in);fprintf(stderr, "%7" PRId64 " %s %.2f %s ",cx_time > 9999999 ? cx_time / 1000 : cx_time,cx_time > 9999999 ? "ms" : "us", fps >= 1.0 ? fps : fps * 60,fps >= 1.0 ? "fps" : "fpm");print_time("ETA", estimated_time_left);}} else {frame_avail = 0;}//如果当前读取的帧大于跳过的前n帧,则对当前帧进行编码if (frames_in > global.skip_frames) {aom_image_t *frame_to_encode; //当前需要编码的帧if (input_shift || (use_16bit_internal && input.bit_depth == 8)){assert(use_16bit_internal);// Input bit depth and stream bit depth do not match, so up// shift frame to stream bit depth// 输入比特深度和流比特深度不匹配,因此将帧比特深度移位到流比特深度if (!allocated_raw_shift) {aom_img_alloc(&raw_shift, raw.fmt | AOM_IMG_FMT_HIGHBITDEPTH,input.width, input.height, 32);allocated_raw_shift = 1;}aom_img_upshift(&raw_shift, &raw, input_shift);frame_to_encode = &raw_shift;} else {frame_to_encode = &raw;}aom_usec_timer_start(&timer);if (use_16bit_internal) { //使用16bit深度assert(frame_to_encode->fmt & AOM_IMG_FMT_HIGHBITDEPTH);FOREACH_STREAM(stream, streams) {//遍历所有的流,并编码当前帧if (stream->config.use_16bit_internal)encode_frame(stream, &global,frame_avail ? frame_to_encode : NULL, frames_in);elseassert(0);};} else {assert((frame_to_encode->fmt & AOM_IMG_FMT_HIGHBITDEPTH) == 0);FOREACH_STREAM(stream, streams) {//遍历所有的流,并编码当前帧encode_frame(stream, &global, frame_avail ? frame_to_encode : NULL,frames_in);}}aom_usec_timer_mark(&timer);cx_time += aom_usec_timer_elapsed(&timer);//更新量化器FOREACH_STREAM(stream, streams) { update_quantizer_histogram(stream); }got_data = 0;FOREACH_STREAM(stream, streams) {get_cx_data(stream, &global, &got_data);}if (!got_data && input.length && streams != NULL &&!streams->frames_out) {lagged_count = global.limit ? seen_frames : ftello(input.file);} else if (input.length) {int64_t remaining;int64_t rate;if (global.limit) {const int64_t frame_in_lagged = (seen_frames - lagged_count) * 1000;rate = cx_time ? frame_in_lagged * (int64_t)1000000 / cx_time : 0;remaining = 1000 * (global.limit - global.skip_frames -seen_frames + lagged_count);} else {const int64_t input_pos = ftello(input.file);const int64_t input_pos_lagged = input_pos - lagged_count;const int64_t input_limit = input.length;rate = cx_time ? input_pos_lagged * (int64_t)1000000 / cx_time : 0;remaining = input_limit - input_pos + lagged_count;}average_rate = (average_rate <= 0) ? rate : (average_rate * 7 + rate) / 8;estimated_time_left = average_rate ? remaining / average_rate : -1;}if (got_data && global.test_decode != TEST_DECODE_OFF) {FOREACH_STREAM(stream, streams) {test_decode(stream, global.test_decode);}}}// frames_in > global.skip_framesfflush(stdout);if (!global.quiet) fprintf(stderr, "\033[K");} // //循环读取视频帧if (stream_cnt > 1) fprintf(stderr, "\n");if (!global.quiet) {FOREACH_STREAM(stream, streams) {const int64_t bpf =seen_frames ? (int64_t)(stream->nbytes * 8 / seen_frames) : 0;const int64_t bps = bpf * global.framerate.num / global.framerate.den;fprintf(stderr,"\rPass %d/%d frame %4d/%-4d %7" PRId64 "B %7" PRId64"b/f %7" PRId64"b/s"" %7" PRId64 " %s (%.2f fps)\033[K\n",pass + 1, global.passes, frames_in, stream->frames_out,(int64_t)stream->nbytes, bpf, bps,stream->cx_time > 9999999 ? stream->cx_time / 1000: stream->cx_time,stream->cx_time > 9999999 ? "ms" : "us",usec_to_fps(stream->cx_time, seen_frames));}}if (global.show_psnr) {if (global.codec->fourcc == AV1_FOURCC) {FOREACH_STREAM(stream, streams) {int64_t bps = 0;if (stream->psnr_count && seen_frames && global.framerate.den){bps = (int64_t)stream->nbytes * 8 * (int64_t)global.framerate.num / global.framerate.den / seen_frames;}// 打印PSNRshow_psnr(stream, (1 << stream->config.cfg.g_input_bit_depth) - 1,bps);//static void show_psnr(struct stream_state *stream, double peak, int64_t bps) }} else {FOREACH_STREAM(stream, streams) { show_psnr(stream, 255.0, 0); }}}//销毁编码器FOREACH_STREAM(stream, streams) { aom_codec_destroy(&stream->encoder); }if (global.test_decode != TEST_DECODE_OFF) {FOREACH_STREAM(stream, streams) { aom_codec_destroy(&stream->decoder); }}close_input_file(&input);if (global.test_decode == TEST_DECODE_FATAL) {FOREACH_STREAM(stream, streams) { res |= stream->mismatch_seen; }}FOREACH_STREAM(stream, streams) {close_output_file(stream, global.codec->fourcc);//关闭文件}FOREACH_STREAM(stream, streams) {stats_close(&stream->stats, global.passes - 1);}if (global.pass) break;} //Pass Endif (global.show_q_hist_buckets) {// 显示量化器直方图FOREACH_STREAM(stream, streams) {show_q_histogram(stream->counts, global.show_q_hist_buckets);}}if (global.show_rate_hist_buckets) { //显示速率直方图FOREACH_STREAM(stream, streams) {show_rate_histogram(stream->rate_hist, &stream->config.cfg,global.show_rate_hist_buckets);}}FOREACH_STREAM(stream, streams) { destroy_rate_histogram(stream->rate_hist); }#if CONFIG_INTERNAL_STATS/* TODO(jkoleszar): This doesn't belong in this executable. Do it for now,* to match some existing utilities.*/if (!(global.pass == 1 && global.passes == 2)) {FOREACH_STREAM(stream, streams) {FILE *f = fopen("opsnr.stt", "a");if (stream->mismatch_seen) {fprintf(f, "First mismatch occurred in frame %d\n",stream->mismatch_seen);} else {fprintf(f, "No mismatch detected in recon buffers\n");}fclose(f);}}
#endifif (allocated_raw_shift) aom_img_free(&raw_shift);aom_img_free(&raw);free(argv);free(streams);return res ? EXIT_FAILURE : EXIT_SUCCESS;
}

AV1代码学习:编码端aomenc.c的main函数相关推荐

  1. AV1代码学习:函数encode_frame和aom_codec_encode

    1.encode_frame函数 在编码端aomenc.c的main函数中,在进入编码过程循环后,循环读取视频的每一帧数据,然后通过encode_frame函数对每一帧进行编码. encode_fra ...

  2. 跟着论文代码学习编码第一天:main.py

    根据ESRT和LBNet的代码学习编码.首先看main.py. 1.  args模块  B站小侯学府的args讲解 需要三步,创建argparse.ArgumentParser解释器,添加add_ar ...

  3. 【C语言进阶深度学习记录】二十九 main函数与命令行参数

    文章目录 1 main函数的返回值 2 main函数的参数 2.1 main函数的参数的代码案例分析 3 main函数不一定是程序中第一个执行的函数 4 总结 1 main函数的返回值 main函数是 ...

  4. AV1代码学习6:tpl_model之一

    AV1的tpl_model是AV1的一个c文件,包含了一系列函数,其主要目的是为了利用lookahead design记录每个块的一些数据,包括失真等,在实际编码时利用这些数据建立模型,调整QP或者l ...

  5. AV1代码学习6:函数av1_encode和 av1_first_pass

    av1_encode没什么特别好说的,会把在av1_encode_strategy的参数(EncodeFrameInput和EncodeFrameParams)赋给结构体AV1_COMP和AV1_CO ...

  6. AV1代码学习:av1_foreach_transformed_block_in_plane函数

    在AV1中,进行预测变换都是基于Transform Block(变换块)进行的,变换块一共19种尺寸,并且其尺寸通常是小于或者等于编码块尺寸的,如下代码所示. enum {TX_4X4, // 4x4 ...

  7. AV1代码学习3:函数aom_codec_encode

    函数aom_codec_encode主要就是根据命令行参数--cpu-used来决定num_enc, 通常情况下,为了通测方便,--cpu-used都是设置为1. 提高--cpu-used的数值会加快 ...

  8. AV1代码学习6:tpl_model之二

    mode_estimation字面意思就是模式估计,实质上是对帧内和帧间的模式进行遍历.帧内预测选取了13种模式,主要是DC模式.角度模式和新加入的PAETH模式.帧间对7个参考帧进行遍历,寻找COS ...

  9. x264代码学习笔记(二):x264_encoder_encode函数

    encode()函数中循环调用encode_frame()函数进行逐帧编码: 调用x264_encoder_encode()函数完成一帧编码: 将编码后的码流载入码流文件中. static int e ...

  10. x264代码学习笔记(五):x264_slicetype_analyse函数

    x264_slicetype_decide函数(代码所在位置为"x264-snapshot-20171128-2245-stable\encoder\slicetype.c")的主 ...

最新文章

  1. PHP实现时间轴函数
  2. 每日一皮:公鸡说,你不会下蛋上去瞎扭啥嘛...
  3. C#获取电脑硬件信息(CPU ID、主板ID、硬盘ID、BIOS编号
  4. MathType中的一些精彩技巧
  5. 用python语言调试程序你用的平台是_Python 程序如何高效地调试?
  6. SparkSQL高并发:读取存储数据库
  7. Mac OS 文件、文件夹重命名的方法
  8. 暴力破解之NTscan+密码字典工具
  9. curl 命令测试post请求
  10. 【简单快速】启动后桌面正常下方任务栏无反应/鼠标一直转圈
  11. SCUT校赛130:对抗女巫的魔法碎片(思维)
  12. 小程序后台持续定位功能
  13. [论文阅读]中文版-AlexNet
  14. webservice接口和http接口(API接口)的区别
  15. 使用Eclipse创建最简单的JavaWeb网页项目
  16. 【实战】Windows 10 CodeSoft 6 条形码标签打印开发实战 【产品标签设计印刷】【Codesoft】
  17. ICT界的基建狂魔—上海季冠:借助闪星云平台一天改造150家品牌珠宝店
  18. vue框架 行内样式 添加背景图片
  19. 使用js实时显示北京时间
  20. 直播预告|SSL云,助力“智”时代,构筑新安全

热门文章

  1. codeforce Zebras(思维 + 模拟)
  2. java唯一订单号_java高并发下唯一订单号生成器【16位数字订单号】
  3. 中图分类法----O 数理科学和化学
  4. Access时间日期比较查询的方法总结
  5. MODIS计算NDVI注意事项_江仔91_新浪博客
  6. Web学习 第二天作业 做一个在线简历
  7. php phpunit selenium,PHPUnit和Selenium
  8. 技术管理进阶——团队合并、解散怎么办?
  9. cpu上干硅脂怎么清理_电脑清灰CPU怎么涂硅脂 导热硅脂涂抹方法教程
  10. PS实战-涂抹掉原图上的文字