当前位置: 首页 > 编程日记 > 正文

1小时学会:最简单的iOS直播推流(八)h264/aac 软编码

最简单的iOS 推流代码,视频捕获,软编码(faac,x264),硬编码(aac,h264),美颜,flv编码,rtmp协议,陆续更新代码解析,你想学的知识这里都有,愿意懂直播技术的同学快来看!!

源代码:https://github.com/hardman/AWLive

软编码包含3部分内容:
1. 将pcm/yuv数据编码成aac/h264格式
2. 将aac/h264数据封装成flv格式
3. 另外无论软编码还是硬编码,最后获得的flv格式数据,需要通过rtmp协议发送至服务器。

本篇将介绍第1部分内容。另外两部分内容将在后续文章中介绍。

根据上文介绍,软编码实现,对应音频/视频编码分别为:AWSWFaacEncoder 和 AWSWX264Encoder。

这两个类只是用OC封装的一个壳,实际上使用的是 libfaac 和 libx264 进行处理。

音频软编码

aw_faac.h和aw_faac.c这两个文件是对libfaac这个库使用方法的简单封装。
这两个文件预期功能是,封装出一个函数,将pcm数据,转成aac数据。

faac的使用步骤:
1. 使用 faacEncOpen 开启编码环境 配置编码属性。
2. 使用 faacEncEncode 函数编码。
3. 使用完毕后,调用 faacEncClose 关闭编码环境。

根据这个步骤,来看aw_faac.c文件。

faac封装第一步:开启编码环境

/*aw_faac_context 是自己创建的结构体,用于辅助aac编码,存储了faac库的必需的数据,及一些过程变量。它的创建及关闭请看demo中的代码,很简单,这里不需要解释。
*/
static void aw_open_faac_enc_handler(aw_faac_context *faac_ctx){// 开启faac// 参数依次为:// 输入 采样率(44100) 声道数(2)// 得到 最大输入样本数(1024) 最大输出字节数(2048)faac_ctx->faac_handler = faacEncOpen(faac_ctx->config.sample_rate, faac_ctx->config.channel_count, &faac_ctx->max_input_sample_count, &faac_ctx->max_output_byte_count);//根据最大输入样本数得到最大输入字节数faac_ctx->max_input_byte_count = faac_ctx->max_input_sample_count * faac_ctx->config.sample_size / 8;if(!faac_ctx->faac_handler){aw_log("[E] aac handler open failed");return;}//创建bufferfaac_ctx->aac_buffer = aw_alloc(faac_ctx->max_output_byte_count);//获取配置faacEncConfigurationPtr faac_config = faacEncGetCurrentConfiguration(faac_ctx->faac_handler);if (faac_ctx->config.sample_size == 16) {faac_config->inputFormat = FAAC_INPUT_16BIT;}else if (faac_ctx->config.sample_size == 24) {faac_config->inputFormat = FAAC_INPUT_24BIT;}else if (faac_ctx->config.sample_size == 32) {faac_config->inputFormat = FAAC_INPUT_32BIT;}else{faac_config->inputFormat = FAAC_INPUT_FLOAT;}//配置faac_config->aacObjectType = LOW;//aac对象类型: LOW Main LTPfaac_config->mpegVersion = MPEG4;//mpeg版本: MPEG2 MPEG4faac_config->useTns = 1;//抗噪faac_config->allowMidside = 0;// 是否使用mid/side编码if(faac_ctx->config.bitrate){//每秒钟每个通道的bitratefaac_config->bitRate = faac_ctx->config.bitrate / faac_ctx->config.channel_count;}faacEncSetConfiguration(faac_ctx->faac_handler, faac_config);//获取audio specific config,本系列文章中第六篇里面介绍了这个数据,它存储了aac格式的一些关键数据,//在rtmp协议中,必须将此数据在所有音频帧之前发送uint8_t *audio_specific_data = NULL;unsigned long audio_specific_data_len = 0;faacEncGetDecoderSpecificInfo(faac_ctx->faac_handler, &audio_specific_data, &audio_specific_data_len);//将获取的audio specific config data 存储到faac_ctx中if (audio_specific_data_len > 0) {faac_ctx->audio_specific_config_data = alloc_aw_data(0);memcpy_aw_data(&faac_ctx->audio_specific_config_data, audio_specific_data, (uint32_t)audio_specific_data_len);}}
//函数内具体参数配置,请参考:
//http://wenku.baidu.com/link?url=0E9GnSo7hZ-3WmB_eXz8EfnG8NqJJJtvjrVNW7hW-VEYWW-gYBMVM-CnFSicDE-veDl2tzfL-nu2FQ8msGcCOALuT8VW1l_NjQL9Gvw5V6_

faac封装第二步:开始编码

/*pcm_data 为 pcm格式的音频数据len 表示数据字节数
*/
extern void aw_encode_pcm_frame_2_aac(aw_faac_context *ctx, int8_t *pcm_data, long len){//判断输入参数if (!pcm_data || len <= 0) {aw_log("[E] aw_encode_pcm_frame_2_aac params error");return;}//清空encoded_aac_data,每次编码数据最终会存储到此字段中,所以首先清空。reset_aw_data(&ctx->encoded_aac_data);/*下列代码根据第一步"开启编码环境"函数中计算的最大输入子节数将pcm_data分割成合适的大小,使用faacEncEncode函数将pcm数据编码成aac数据。下列代码执行完成后,编码出的aac数据将会存储到encoded_aac_data字段中。*/long max_input_count = ctx->max_input_byte_count;long curr_read_count = 0;do{long remain_count = len - curr_read_count;if (remain_count <= 0) {break;}long read_count = 0;if (remain_count > max_input_count) {read_count = max_input_count;}else{read_count = remain_count;}long input_samples = read_count * 8 / ctx->config.sample_size;int write_count = faacEncEncode(ctx->faac_handler, (int32_t * )(pcm_data + curr_read_count), (uint32_t)input_samples, (uint8_t *)ctx->aac_buffer, (uint32_t)ctx->max_output_byte_count);if (write_count > 0) {data_writer.write_bytes(&ctx->encoded_aac_data, (const uint8_t *)ctx->aac_buffer, write_count);}curr_read_count += read_count;} while (curr_read_count + max_input_count < len);
}

faac封装第三步:关闭编码器:

extern void free_aw_faac_context(aw_faac_context **context_p){...//关闭faac编码器faacEncClose(context->faac_handler);...
}

上述代码仅仅作为faac编码器的封装,能够实现打开编码器。

真正实现编码过程的文件是:aw_sw_faac_encoder.h/aw_sw_faac_encoder.c文件

此文件的功能是:将传入的pcm数据通过aw_faac.c提供的功能,将数据转成aac数据格式,然后将aac数据格式转成flv格式,如何转成flv格式,会在后续文章介绍。

来看一下 aw_sw_faac_encoder.c文件的实现。
此文件逻辑也很清晰,它实现的功能有:
1. 开启编码器,创建一些过程变量。
2. 将audio specific config data 转成flv帧数据。
3. 将接收到的pcm数据,转成aac数据,然后将aac数据转成flv音频数据。
4. 关闭编码器。

可以看出,这种类似功能性代码,一般都是三部曲:打开-使用-关闭。

下面来看代码。

音频软编码器第一步:开启编码器

/*faac_config:需要由上层传入相关配置属性
*/
extern void aw_sw_encoder_open_faac_encoder(aw_faac_config *faac_config){//是否已经开启了,避免重复开启if (aw_sw_faac_encoder_is_valid()) {aw_log("[E] aw_sw_encoder_open_faac_encoder when encoder is already inited");return;}//创建配置int32_t faac_cfg_len = sizeof(aw_faac_config);if (!s_faac_config) {s_faac_config = aw_alloc(faac_cfg_len);}memcpy(s_faac_config, faac_config, faac_cfg_len);//开启faac软编码s_faac_ctx = alloc_aw_faac_context(*faac_config);
}

音频软编码第二步:将audio specific config data 转成flv帧数据。

extern aw_flv_audio_tag *aw_sw_encoder_create_faac_specific_config_tag(){//是否已打开编码器if(!aw_sw_faac_encoder_is_valid()){aw_log("[E] aw_sw_encoder_create_faac_specific_config_tag when audio encoder is not inited");return NULL;}//创建 audio specfic config recordaw_flv_audio_tag *aac_tag = aw_sw_encoder_create_flv_audio_tag(&s_faac_ctx->config);//根据flv协议:audio specific data对应的 aac_packet_type 固定为 aw_flv_a_aac_package_type_aac_sequence_header 值为0//普通的音频帧,此处值为1.aac_tag->aac_packet_type = aw_flv_a_aac_package_type_aac_sequence_header;aac_tag->config_record_data = copy_aw_data(s_faac_ctx->audio_specific_config_data);aac_tag->common_tag.timestamp = 0;aac_tag->common_tag.data_size = s_faac_ctx->audio_specific_config_data->size + 11 + aac_tag->common_tag.header_size;return aac_tag;
}

音频软编码器第三步:将接收到的pcm数据转成aac数据,然后将aac数据转成flv音频数据

/*pcm_data: 传入的pcm数据len: pcm数据长度timestamp:flv时间戳,rtmp协议要求发送的flv音视频帧的时间戳需为均匀增加,不允许 后发送的数据时间戳 比 先发送的数据的时间戳 还要小。aw_flv_audio_tag: 返回类型,生成的flv音频数据(flv中,每帧数据称为一个tag)。
*/
extern aw_flv_audio_tag *aw_sw_encoder_encode_faac_data(int8_t *pcm_data, long len, uint32_t timestamp){if (!aw_sw_faac_encoder_is_valid()) {aw_log("[E] aw_sw_encoder_encode_faac_data when encoder is not inited");return NULL;}//将pcm数据编码成aac数据aw_encode_pcm_frame_2_aac(s_faac_ctx, pcm_data, len);// 使用faac编码的数据会带有7个字节的adts头。rtmp不接受此值,在此去掉前7个字节。int adts_header_size = 7;//除去ADTS头的7字节if (s_faac_ctx->encoded_aac_data->size <= adts_header_size) {return NULL;}//将aac数据封装成flv音频帧。flv帧仅仅是将aac数据增加一些固定信息。并没有对aac数据进行编码操作。aw_flv_audio_tag *audio_tag = aw_encoder_create_audio_tag((int8_t *)s_faac_ctx->encoded_aac_data->data + adts_header_size, s_faac_ctx->encoded_aac_data->size - adts_header_size, timestamp, &s_faac_ctx->config);audio_count++;//返回结果return audio_tag;
}

音频软编码器第四步:关闭编码器

extern void aw_sw_encoder_close_faac_encoder(){//避免重复关闭if (!aw_sw_faac_encoder_is_valid()) {aw_log("[E] aw_sw_encoder_close_faac_encoder when encoder is not inited");return;}//是否aw_faac_context,也就关闭了faac编码环境。free_aw_faac_context(&s_faac_ctx);//释放配置数据if (s_faac_config) {aw_free(s_faac_config);s_faac_config = NULL;}
}

到此为止,音频软编码器就介绍完了。已经成功实现了将pcm数据转成flv音频帧。

下面介绍视频软编码。
套路同音频编码一致,对应的视频软编码是对x264这个库的封装。
文件在aw_x264.h/aw_x264.c中。

它实现的功能如下:
1. 初始化x264参数,打开编码环境
2. 进行编码
3. 关闭编码环境。

x264封装第一步:初始化x264参数,打开编码环境

/*config 表示配置数据aw_x264_context 是自定义结构体,用于存储x264编码重要属性及过程变量。
*/
extern aw_x264_context *alloc_aw_x264_context(aw_x264_config config){aw_x264_context *ctx = aw_alloc(sizeof(aw_x264_context));memset(ctx, 0, sizeof(aw_x264_context));//数据数据默认为 I420if (!config.input_data_format) {config.input_data_format = X264_CSP_I420;}//创建handlermemcpy(&ctx->config, &config, sizeof(aw_x264_config));x264_param_t *x264_param = NULL;//x264参数,具体请参考:http://blog.csdn.net/table/article/details/8085115aw_create_x264_param(ctx, &x264_param);//开启编码器aw_open_x264_handler(ctx, x264_param);aw_free(x264_param);//创建pic_in,x264内部用于存储输入图像数据的一段空间。x264_picture_t *pic_in = aw_alloc(sizeof(x264_picture_t));x264_picture_init(pic_in);//[注意有坑]//aw_stride是一个宏,用于将视频宽度转成16的倍数。如果不是16的倍数,有时候会编码失败(颜色缺失等)。int alloc_width = aw_stride(config.width);x264_picture_alloc(pic_in, config.input_data_format, alloc_width, config.height);pic_in->img.i_csp = config.input_data_format;//i_stride 表示换行步长,跟plane数及格式有关,x264内部用来判定读取多少数据需要换行。//关于yuv数据格式在第二章里面介绍过,这里再次回顾一下。if (config.input_data_format == X264_CSP_NV12) {//nv12数据包含2个plane,第一个plane存储了y数据大小为 width * height,//第二个plane存储uv数据,u和v隔位存储,数据大小为:width * (height / 2)pic_in->img.i_stride[0] = alloc_width;pic_in->img.i_stride[1] = alloc_width;pic_in->img.i_plane = 2;}else if(config.input_data_format == X264_CSP_BGR || config.input_data_format == X264_CSP_RGB){//rgb数据包含一个plane,数据长度为 width * 3 * height。pic_in->img.i_stride[0] = alloc_width * 3;pic_in->img.i_plane = 1;}else if(config.input_data_format == X264_CSP_BGRA){//bgra同rgb类似pic_in->img.i_stride[0] = alloc_width * 4;pic_in->img.i_plane = 1;}else{//YUV420//yuv420即I420格式。//包含3个plane,第一个plane存储y数据大小为width * height//第二个存储u数据,数据大小为 width * height / 4//第三个存储v数据,数据大小为 width * height / 4pic_in->img.i_stride[0] = alloc_width;pic_in->img.i_stride[1] = alloc_width / 2;pic_in->img.i_stride[2] = alloc_width / 2;pic_in->img.i_plane = 3;}//其他数据初始化,pic_in 用于存储输入数据(yuv/rgb等数据),pic_out用于存储输出数据(h264数据)ctx->pic_in = pic_in;ctx->pic_out = aw_alloc(sizeof(x264_picture_t));x264_picture_init(ctx->pic_out);//编码后数据变量ctx->encoded_h264_data = alloc_aw_data(0);ctx->sps_pps_data = alloc_aw_data(0);//获取sps pps// sps pps 数据是rtmp协议要求的必需在所有flv视频帧之前发送的一帧数据,存储了h264视频的一些关键属性。// 具体获取方法请看demo,很简单,这里就不解释了。aw_encode_x264_header(ctx);return ctx;
}

x264封装第二步:开始编码

//编码一帧数据
extern void aw_encode_yuv_frame_2_x264(aw_x264_context *aw_ctx, int8_t *yuv_frame, int len){if (len > 0 && yuv_frame) {//将视频数据填充到pic_in中,pic_in上面已经介绍过,x264需要这样处理。int actual_width = aw_stride(aw_ctx->config.width);//数据保存到pic_in中if (aw_ctx->config.input_data_format == X264_CSP_NV12) {aw_ctx->pic_in->img.plane[0] = (uint8_t *)yuv_frame;aw_ctx->pic_in->img.plane[1] = (uint8_t *)yuv_frame + actual_width * aw_ctx->config.height;}else if(aw_ctx->config.input_data_format == X264_CSP_BGR || aw_ctx->config.input_data_format == X264_CSP_RGB){aw_ctx->pic_in->img.plane[0] = (uint8_t *)yuv_frame;}else if(aw_ctx->config.input_data_format == X264_CSP_BGRA){aw_ctx->pic_in->img.plane[0] = (uint8_t *)yuv_frame;}else{//YUV420aw_ctx->pic_in->img.plane[0] = (uint8_t *)yuv_frame;aw_ctx->pic_in->img.plane[1] = (uint8_t *)yuv_frame + actual_width * aw_ctx->config.height;aw_ctx->pic_in->img.plane[2] = (uint8_t *)yuv_frame + actual_width * aw_ctx->config.height * 5 / 4;}//x264编码,编码后的数据存储在aw_ctx->nal中x264_encoder_encode(aw_ctx->x264_handler, &aw_ctx->nal, &aw_ctx->nal_count, aw_ctx->pic_in, aw_ctx->pic_out);aw_ctx->pic_in->i_pts++;}//将编码后的数据转存到encoded_h264_data中,这里面存储的就是编码好的h264视频帧了。reset_aw_data(&aw_ctx->encoded_h264_data);if (ctx->nal_count > 0) {int i = 0;for (; i < ctx->nal_count; i++) {data_writer.write_bytes(&ctx->encoded_h264_data, ctx->nal[i].p_payload, ctx->nal[i].i_payload);}}
}

x264封装第三步:关闭编码环境。

/*很简单,分别释放pic_in,pic_out,x264_handler即可
*/
extern void free_aw_x264_context(aw_x264_context **ctx_p){aw_x264_context *ctx = *ctx_p;if (ctx) {//释放pic_inif (ctx->pic_in) {x264_picture_clean(ctx->pic_in);aw_free(ctx->pic_in);ctx->pic_in = NULL;}//释放pic_outif (ctx->pic_out) {aw_free(ctx->pic_out);ctx->pic_out = NULL;}...//关闭handlerif (ctx->x264_handler) {x264_encoder_close(ctx->x264_handler);ctx->x264_handler = NULL;}...}
}

上面的代码只是对x264编码流程进行简单封装。
真正实现完整转码逻辑的是在 aw_sw_x264_encoder.h/aw_sw_x264_encoder.c 中。

它实现了如下功能:
1. 将收到的yuv数据编码成 h264格式。
2. 生成包含sps/pps数据的flv视频帧。
3. 将h264格式的数据转成flv视频数据。
4. 关闭编码器。

视频软编码器第一步:收到yuv数据,并编码成h264格式。

//打开编码器,就是在aw_x264基础上,封了一层。
extern void aw_sw_encoder_open_x264_encoder(aw_x264_config *x264_config){if (aw_sw_x264_encoder_is_valid()) {aw_log("[E] aw_sw_encoder_open_video_encoder when video encoder is not inited");return;}int32_t x264_cfg_len = sizeof(aw_x264_config);if (!s_x264_config) {s_x264_config = aw_alloc(x264_cfg_len);}memcpy(s_x264_config, x264_config, x264_cfg_len);s_x264_ctx = alloc_aw_x264_context(*x264_config);
}

视频软编码器第二步:生成包含sps/pps数据的flv视频帧

//根据flv/h264/aac协议创建video/audio首帧tag,flv 格式相关代码在 aw_encode_flv.h/aw_encode_flv.c 中
extern aw_flv_video_tag *aw_sw_encoder_create_x264_sps_pps_tag(){if(!aw_sw_x264_encoder_is_valid()){aw_log("[E] aw_sw_encoder_create_video_sps_pps_tag when video encoder is not inited");return NULL;}//创建 sps pps// 创建flv视频tagaw_flv_video_tag *sps_pps_tag = aw_sw_encoder_create_flv_video_tag();// 关键帧sps_pps_tag->frame_type = aw_flv_v_frame_type_key;// package type 为header,固定sps_pps_tag->h264_package_type = aw_flv_v_h264_packet_type_seq_header;// cts,项目内所有视频帧的cts 都为0sps_pps_tag->h264_composition_time = 0;// 将aw_x264中生成的sps/pps数据copy到tag中sps_pps_tag->config_record_data = copy_aw_data(s_x264_ctx->sps_pps_data);// 时间戳为0sps_pps_tag->common_tag.timestamp = 0;// flv tag长度为:header size + data header(11字节) + 数据长度(后续介绍)sps_pps_tag->common_tag.data_size = s_x264_ctx->sps_pps_data->size + 11 + sps_pps_tag->common_tag.header_size;return sps_pps_tag;
}

视频软编码器第三步:将h264格式的数据转成flv视频数据。

//将采集到的video yuv数据,编码为flv video tag
extern aw_flv_video_tag * aw_sw_encoder_encode_x264_data(int8_t *yuv_data, long len, uint32_t timeStamp){//是否已开启编码if (!aw_sw_x264_encoder_is_valid()) {aw_log("[E] aw_sw_encoder_encode_video_data when video encoder is not inited");return NULL;}//执行编码aw_encode_yuv_frame_2_x264(s_x264_ctx, yuv_data, (int32_t)len);//编码后是否能取到数据if (s_x264_ctx->encoded_h264_data->size <= 0) {return NULL;}//将h264数据转成flv tagx264_picture_t *pic_out = s_x264_ctx->pic_out;aw_flv_video_tag *video_tag = aw_encoder_create_video_tag((int8_t *)s_x264_ctx->encoded_h264_data->data, s_x264_ctx->encoded_h264_data->size, timeStamp, (uint32_t)((pic_out->i_pts - pic_out->i_dts) * 1000.0 / s_x264_ctx->config.fps), pic_out->b_keyframe);...return video_tag;
}

视频软编码器第四步:关闭编码器

//关闭编码器
extern void aw_sw_encoder_close_x264_encoder(){//避免重复关闭if (!aw_sw_x264_encoder_is_valid()) {aw_log("[E] aw_sw_encoder_close_video_encoder s_faac_ctx is NULL");return;}//释放配置if (s_x264_config) {aw_free(s_x264_config);s_x264_config = NULL;}//释放contextfree_aw_x264_context(&s_x264_ctx);
}

至此,软编码代码介绍完毕。
可以通过 AWSWFaacEncoder/AWSWX264Encoder 类调用上面的软编码器,给上层提供一致的接口。

总结,软编码器涉及的内容:
1. 第三方编码器:libfaac/libx264
2. 第三方编码器封装:aw_faac.h/aw_faac.c,aw_x264.h/aw_x264.c
3. 编码器(将原始数据转成最终数据)封装:aw_sw_faac_encoder.h/aw_sw_faac_encoder.c,aw_sw_x264_encoder.h/aw_sw_x264_encoder.c
4. 顶层抽象:AWSWFaacEncoder/AWSWX264Encoder

编码过程中需要注意的地方:
1. 注意 audio specific config 及 sps/pps数据的获取,不获取这两种数据,服务器没办法识别音视频帧的。
2. faac编码后注意去除adts头部。
3. x264编码器如果输入分辨率的宽度不是16的倍数,需要将其扩展成16的倍数,否则编码可能会出问题(颜色丢失,uv混乱)。

文章列表

  1. 1小时学会:最简单的iOS直播推流(一)项目介绍
  2. 1小时学会:最简单的iOS直播推流(二)代码架构概述
  3. 1小时学会:最简单的iOS直播推流(三)使用系统接口捕获音视频
  4. 1小时学会:最简单的iOS直播推流(四)如何使用GPUImage,如何美颜
  5. 1小时学会:最简单的iOS直播推流(五)yuv、pcm数据的介绍和获取
  6. 1小时学会:最简单的iOS直播推流(六)h264、aac、flv介绍
  7. 1小时学会:最简单的iOS直播推流(七)h264/aac 硬编码
  8. 1小时学会:最简单的iOS直播推流(八)h264/aac 软编码
  9. 1小时学会:最简单的iOS直播推流(九)flv 编码与音视频时间戳同步
  10. 1小时学会:最简单的iOS直播推流(十)librtmp使用介绍
  11. 1小时学会:最简单的iOS直播推流(十一)sps&pps和AudioSpecificConfig介绍(完结)

相关文章:

003小插曲之变量和字符串

变量&#xff1a;赋值&#xff08;名字值&#xff09;&#xff1b;变量名&#xff1a;字母分大小写/数字/下划线&#xff0c;不能以数字开头&#xff1b;拼接&#xff1b;原始字符串r&#xff1b; 专业优秀的名称&#xff1a;teacher/num/name/test/temp >>> teacher小…

mysql插入大量数据

创建实验表&#xff1a; CREATE TABLE a ( id int(11) NOT NULL AUTO_INCREMENT, name char(50) NOT NULL, type char(20) NOT NULL, PRIMARY KEY (id)) ENGINEInnoDB&#xff1b; 创建存储语句&#xff1a; delimiter // create procedure insertdata() begin declare i int …

十六进制190的2进制数_十六进制数系统解释

十六进制190的2进制数Hexadecimal numbers, often shortened to “hex numbers” or “hex”, are numbers represented in base 16 as opposed to base 10 that we use for everyday arithmetic and counting.十六进制数字(通常缩写为“十六进制数字”或“十六进制”)是以16为…

初学ssm框架的信息

ssm框架&#xff0c;就是Spring ,SpringMVC ,mybstis 的简称&#xff0c;我们是从mybstis 开始学起的&#xff0c;mybatis的作用作为一个连接数据库的框架&#xff0c;可以很好配置连接好数据库&#xff0c; 有mybatis,我们对数据库增删改查的操作更为简便了。SSM框架&#xff…

转:YUV RGB 常见视频格式解析

转&#xff1a; http://www.cnblogs.com/qinjunni/archive/2012/02/23/2364446.html YUV RGB 常见视频格式解析 I420是YUV格式的一种&#xff0c;而YUV有packed format和planar format两种&#xff0c;而I420属于planar format的一种。  同时I420表示了YUV的采样比例4:2:0。4…

1小时学会:最简单的iOS直播推流(十)librtmp使用介绍

最简单的iOS 推流代码&#xff0c;视频捕获&#xff0c;软编码(faac&#xff0c;x264)&#xff0c;硬编码&#xff08;aac&#xff0c;h264&#xff09;&#xff0c;美颜&#xff0c;flv编码&#xff0c;rtmp协议&#xff0c;陆续更新代码解析&#xff0c;你想学的知识这里都有…

导入语句 python_Python导入语句说明

导入语句 pythonWhile learning programming and reading some resources you’d have come across this word ‘abstraction’ which simply means to reduce and reuse the code as much as possible.在学习编程和阅读一些资源时&#xff0c;您会遇到“抽象”一词&#xff0c…

网页性能测试---webpagetest

http://www.webpagetest.org/转载于:https://www.cnblogs.com/cai-yu-candice/p/8194866.html

1小时学会:最简单的iOS直播推流(十一)spspps和AudioSpecificConfig介绍(完结)

最简单的iOS 推流代码&#xff0c;视频捕获&#xff0c;软编码(faac&#xff0c;x264)&#xff0c;硬编码&#xff08;aac&#xff0c;h264&#xff09;&#xff0c;美颜&#xff0c;flv编码&#xff0c;rtmp协议&#xff0c;陆续更新代码解析&#xff0c;你想学的知识这里都有…

ES5 数组方法forEach

ES6已经到了非学不可的地步了&#xff0c;对于ES5都不太熟的我决定是时候学习ES5了。 1. js 数组循环遍历。 数组循环变量&#xff0c;最先想到的就是 for(var i0;i<count;i)这样的方式了。 除此之外&#xff0c;也可以使用较简便的forEach 方式 2. forEach 函数。 使用如…

pytorch深度学习_了解如何使用PyTorch进行深度学习

pytorch深度学习PyTorch is an open source machine learning library for Python that facilitates building deep learning projects. Weve published a 10-hour course that will take you from being complete beginner in PyTorch to using it to code your own GANs (gen…

LwIP Application Developers Manual12---Configuring lwIP

1.前言 2.LwIP makefiles With minimal featuresC_SOURCES \ src/api/err.c \ src/core/init.c \ src/core/mem.c \ src/core/memp.c \ src/core/netif.c \ src/core/pbuf.c \ src/core/stats.c \ src/core/udp.c \ src/core/ipv4/icmp.c \ src/core/ipv4/inet.c \ src/core/i…

仿斗鱼聊天:基于CoreText的面向对象图文排版工具AWRichText

AWRichText 基于CoreText&#xff0c;面向对象&#xff0c;极简&#xff0c;易用&#xff0c;高效&#xff0c;支持精确点击&#xff0c;UIView混排&#xff0c;GIF动图&#xff0c;并不仅仅局限于图文混排的富文本排版神器。 代码地址&#xff1a;https://github.com/hardman/…

搭建nexus后,进入首页的时候出现warning: Could not connect to Nexus.错误

nexus出现这种问题&#xff0c;一般是版本太旧&#xff0c;换一个高版本的nexus就能解决了。 转载于:https://www.cnblogs.com/tietazhan/p/5459393.html

微软hackathon_武汉Hackathon的黑客之路–开发人员如何抗击COVID-19

微软hackathonThe Chinese New Year in 2020 was one of the saddest Chinese New Years in recent memory. After the sudden outbreak of the COVID-19 virus, the city pressed pause on all celebrations.2020年的农历新年是最近记忆中最可悲的农历新年之一。 在COVID-19病…

SVN版本控制系统使用

一.版本控制系统安装&#xff1a; 软件下载地址&#xff1a;https://www.visualsvn.com/downloads/ 二.安装版本控制系统以后&#xff0c;在window下&#xff0c;设置环境变量。 三.在命令提示符控制台查看服务器版本&#xff1a;svn --version 四.创建仓库&#xff1a;F:\DevR…

iOS的KVO实现剖析

KVO原理 对于KVO的原理&#xff0c;很多人都比较清楚了。大概是这样子的&#xff1a; 假定我们自己的类是Object和它的对象 obj&#xff0c; 当obj发送addObserverForKeypath:keypath消息后&#xff0c;系统会做3件事情&#xff1a; 动态创建一个Object的子类&#xff0c;名…

你真的以为了解java.io吗 呕心沥血 绝对干货 别把我移出首页了

文章结构1 flush的使用场景2 一个java字节流&#xff0c;inputstream 和 outputstream的简单例子3 分别测试了可能抛出java.io.FileNotFoundException&#xff0c;java.io.FileNotFoundException: test (拒绝访问。)&#xff0c;java.io.FileNotFoundException: test.txt (系统…

GitHub为所有人免费提供了所有核心功能-这就是您应该关心的原因

Just a couple of days ago, GitHub wrote a blog article stating that it is now free for teams. Heres the official blog article if youre interested. 就在几天前&#xff0c;GitHub写了一篇博客文章&#xff0c;指出它现在对团队免费。 如果您有兴趣&#xff0c;这是官…

什么是ObjCTypes?

先看一下消息转发流程: 在forwardInvocation这一步&#xff0c;你必须要实现一个方法: - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""); 该方法用于说明消息的返回值和参数类型。NSMethodSignature是方法签名&#x…

0基础JavaScript入门教程(一)认识代码

1. 环境&#xff1a; JavaScript简称js&#xff0c;后续我们将使用js来代替JavaScript。 认识代码前&#xff0c;需要安装js代码运行环境。 安装nodejs&#xff1a;在https://nodejs.org/zh-cn/ 下载LTS版本&#xff0c;然后安装安装visual studio code&#xff1a;https://…

junit、hamcrest、eclemma的安装与使用

1、junit的安装与使用 1.1 安装步骤 1&#xff09;从http://www.junit.org/ 下载junit相应的jar包&#xff1b; 2&#xff09; 在CLASSPATH中加入JAR包所在的路径&#xff0c;如E:\Java\jar\junit\junit-4.10.jar&#xff1b; 3&#xff09; 将junit-4.10.jar加入到项目的lib文…

如何撰写将赢得客户青睐的自由职业者提案和免费模板

Your prospective client asks you to provide them with a quote. So you just send them the quote, right?您的潜在客户要求您提供报价。 所以您只给他们发送报价吧&#xff1f; Wrong.错误。 If you did, you would be missing out on a massive opportunity here.如果这…

2. 把一幅图像进行平移。

实验二 #include "cv.h" #include<stdio.h> #include "highgui.h" IplImage *PingYi(IplImage *src, int h0, int w0); int main(int argc, char** argv) {IplImage* pImg; //声明IplImage指针IplImage* pImgAfterMove;pImg cvLoadImage("601…

后台的代理nginx部署方法

软件包如下&#xff1a;nginx-1.10.0.tar.gznginx-http-concat-master.zipngx_cache_purge-2.3.tar.gzopenssl-1.0.2h.tar.gzpcre-8.39.tar.gzzlib-1.2.8.tar.gz ngin部署方法&#xff1a;上面的安装包都存放在/apps/svr/soft目录下:cd /apps/svr/softtar -zxf nginx-1.10.0.ta…

iOS中你可能没有完全弄清楚的(一)synthesize

1. 什么是synthesize synthesize中文意思是合成&#xff0c;代码中我们经常这样用。 interface Test: NSObject property (nonatomic, unsafe_unretained) int i; endimplementation Test synthesize i; end 复制代码 使用synthesize的2个步骤&#xff1a; 首先你要有在类声…

framer x使用教程_如何使用Framer Motion将交互式动画和页面过渡添加到Next.js Web应用程序

framer x使用教程The web is vast and its full of static websites and apps. But just because those apps are static, it doesnt mean they have to be boring. 网络非常庞大&#xff0c;到处都是静态的网站和应用。 但是&#xff0c;仅仅因为这些应用程序是静态的&#xf…

POJ 2429

思路&#xff1a;a/n*b/nlcm/gcd 所以这道题就是分解ans.dfs枚举每种素数情况。套Miller_Rabin和pollard_rho模板 1 //#pragma comment(linker, "/STACK:167772160")//手动扩栈~~~~hdu 用c交2 #include<cstdio>3 #include<cstring>4 #include<cstdlib…

iOS中你可能没有完全弄清楚的(二)自己实现一个KVO源码及解析

前几天写了一篇blog&#xff08;点这里&#xff09;&#xff0c;分析了系统KVO可能的实现方式。并添加了简单代码验证。 既然系统KVO不好用&#xff0c;我们完全可以根据之前的思路&#xff0c;再造一个可以在项目中使用的KVO的轮子。 代码已经上传到github: https://github.…

js中的preventDefault与stopPropagation详解

1. preventDefault: 比如<a href"http://www.baidu.com">百度</a>,这是html中最基础的东西&#xff0c;起的作用就是点击百度链接到http://www.baidu.com,这是属于<a>标签的默认行为;preventDefault方法就是可以阻止它的默认行为的发生而发生其他…