iOS音视频开发-视频软编码(x264编码H.264文件)

视频软编码: 软编码主要是利用CPU编码的过程,通常为FFmpeg+x264。

  • FFmpeg
    FFmpeg是一个非常强大的音视频处理库,包括视频采集功能、视频格式转换、视频抓图、给视频加水印等。
    FFmpeg在Linux平台下开发,但它同样也可以在其它操作系统环境中编译运行,包括Windows、Mac OS X等。
  • x264
    H.264是ITU制定的视频编码标准
    而x264是一个开源的H.264/MPEG-4 AVC视频编码函数库,是最好的有损视频编码器,里面集成了非常多优秀的算法用于视频编码.
    x264官网
    PS:FFmpeg本身并不包含编码器,但存在强大的解码器,而x264只提供了强大的编码器,但是其独立存在,社区提供了将x264编译进FFmpeg的方法,所以开发时使用的为FFmpeg+x264。这里记录x264编码器的使用方法,FFmpeg+x264的使用后续记录。
编译x264 下载x264源码:
  • https://www.videolan.org/developers/x264.html
下载gas-preprocessor文件:
  • https://github.com/libav/gas-preprocessor。
  • 将下载的gas-preprocessor文件拷贝到/usr/local/bin目录下。
  • 修改文件权限:chmod 777 /usr/local/bin/gas-preprocessor.pl。
下载x264编译脚本文件:
  • https://github.com/kewlbear/x264-ios
  • 将脚本文件build-x264.sh 放在x264源码文件同级目录下,并不是x264文件夹里面。

    iOS音视频开发-视频软编码(x264编码H.264文件)
    文章图片
    同级目录.png
修改权限、执行脚本:
  • sudo chmod u+x build-x264.sh
  • sudo ./build-x264.sh
    当脚本执行过程中可能会出现警告导致不能编译成功,一般为yasm版本过低或者nasm版本过低导致的(我遇到的)。
Found yasm x.x.x.xxxx Minimum version is yasm-x.x.x If you really want to compile without asm, configure with --disable-asm.


Found nasm x.x.x.xxxx Minimum version is nasm-x.x.x If you really want to compile without asm, configure with --disable-asm.

解决办法:
下载Homebrew,利用Homebrew下载yasm/nasm。
Homebrew下载安装:
  • 地址:https://brew.sh/
  • Homebrew安装yasm命令:brew install yasm
  • Homebrew安装nasm命令:brew install nasm
脚本执行完毕生成的文件:

iOS音视频开发-视频软编码(x264编码H.264文件)
文章图片
编译好的x264文件夹.png
使用命令行工具查看编译好的.a文件支持的架构:
命令:lipo -info libx264.a 结果:Architectures in the fat file: libx264.a are: armv7 armv7s i386 x86_64 arm64

当然执行脚本的时候你可以选择想要的架构:
To build everything://支持所有架构 ./build-x264.shTo build for arm64://只支持arm64 ./build-x264.sh arm64To build fat library for armv7 and x86_64 (64-bit simulator)://只支持armv7和x86_64 ./build-x264.sh armv7 x86_64To build fat library from separately built thin libraries://支持各架构独立库文件 ./build-x264.sh lipo

x264编码实现 将编译好的x264-iOS文件夹拖入工程即可。
x264编码参数设置:
- (void)setupEncodeWithConfig:(BBVideoConfig *)config{_config = config; pX264Param = (x264_param_t *)malloc(sizeof(x264_param_t)); assert(pX264Param); /* 配置参数预设置 * 主要是zerolatency该参数,即时编码。 * static const char * const x264_tune_names[] = { "film", "animation", "grain", "stillimage", "psnr", "ssim", "fastdecode", "zerolatency", 0 }; */ x264_param_default_preset(pX264Param, "veryfast", "zerolatency"); /* 设置Profile.使用Baseline profile * static const char * const x264_profile_names[] = { "baseline", "main", "high", "high10", "high422", "high444", 0 }; */ x264_param_apply_profile(pX264Param, "baseline"); // cpuFlags pX264Param->i_threads = X264_SYNC_LOOKAHEAD_AUTO; // 取空缓冲区继续使用不死锁的保证// 视频宽高 pX264Param->i_width= config.videoSize.width; // 要编码的图像宽度. pX264Param->i_height= config.videoSize.height; // 要编码的图像高度 pX264Param->i_frame_total = 0; //编码总帧数,未知设置为0// 流参数 pX264Param->b_cabac = 0; //支持利用基于上下文的自适应的算术编码 0为不支持 pX264Param->i_bframe = 5; //两个参考帧之间B帧的数量 pX264Param->b_interlaced = 0; //隔行扫描 pX264Param->rc.i_rc_method = X264_RC_ABR; // 码率控制,CQP(恒定质量),CRF(恒定码率),ABR(平均码率) pX264Param->i_level_idc = 30; // 编码复杂度// 图像质量 pX264Param->rc.f_rf_constant = 15; // rc.f_rf_constant是实际质量,越大图像越花,越小越清晰 pX264Param->rc.f_rf_constant_max = 45; // param.rc.f_rf_constant_max ,图像质量的最大值。// 速率控制参数 通常为屏幕分辨率*3 (宽x高x3) pX264Param->rc.i_bitrate = config.bitrate / 1000; // 码率(比特率), x264使用的bitrate需要/1000。 // pX264Param->rc.i_vbv_max_bitrate=(int)((m_bitRate * 1.2) / 1000) ; // 平均码率模式下,最大瞬时码率,默认0(与-B设置相同) pX264Param->rc.i_vbv_buffer_size = pX264Param->rc.i_vbv_max_bitrate = (int)((config.bitrate * 1.2) / 1000); pX264Param->rc.f_vbv_buffer_init = 0.9; //默认0.9// 使用实时视频传输时,需要实时发送sps,pps数据 pX264Param->b_repeat_headers = 1; // 重复SPS/PPS 放到关键帧前面。该参数设置是让每个I帧都附带sps/pps。// 帧率 pX264Param->i_fps_num= config.fps; // 帧率分子 pX264Param->i_fps_den= 1; // 帧率分母 pX264Param->i_timebase_den = pX264Param->i_fps_num; pX264Param->i_timebase_num = pX264Param->i_fps_den; /* I帧间隔 GOP * 一般为帧率的整数倍,通常设置2倍,即 GOP = 帧率 * 2; */ pX264Param->b_intra_refresh = 1; pX264Param->b_annexb = 1; pX264Param->i_keyint_max = config.fps * 2; // Log参数,打印编码信息 pX264Param->i_log_level= X264_LOG_DEBUG; // 编码需要的辅助变量 iNal = 0; pNals = NULL; pPicIn = (x264_picture_t *)malloc(sizeof(x264_picture_t)); memset(pPicIn, 0, sizeof(x264_picture_t)); x264_picture_alloc(pPicIn, X264_CSP_I420, pX264Param->i_width, pX264Param->i_height); pPicIn->i_type = X264_TYPE_AUTO; pPicIn->img.i_plane = 3; pPicOut = (x264_picture_t *)malloc(sizeof(x264_picture_t)); memset(pPicOut, 0, sizeof(x264_picture_t)); x264_picture_init(pPicOut); // 打开编码器句柄,通过x264_encoder_parameters得到设置给X264 // 的参数.通过x264_encoder_reconfig更新X264的参数 pX264Handle = x264_encoder_open(pX264Param); assert(pX264Handle); }

x264编码主要实现代码:
- (void)encoderToH264:(CMSampleBufferRef)sampleBuffer{CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); CVPixelBufferLockBaseAddress(imageBuffer, 0); UInt8 *bufferPtr = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer,0); UInt8 *bufferPtr1 = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer,1); size_t width = CVPixelBufferGetWidth(imageBuffer); size_t height = CVPixelBufferGetHeight(imageBuffer); size_t bytesrow0 = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer,0); size_t bytesrow1= CVPixelBufferGetBytesPerRowOfPlane(imageBuffer,1); UInt8 *yuv420_data = https://www.it610.com/article/(UInt8 *)malloc(width * height *3/ 2); //buffer to store YUV with layout YYYYYYYYUUVV/* convert NV12 data to YUV420*/ UInt8 *pY = bufferPtr ; UInt8 *pUV = bufferPtr1; UInt8 *pU = yuv420_data + width * height; UInt8 *pV = pU + width * height / 4; for(int i = 0; i < height; i++) { memcpy(yuv420_data + i * width, pY + i * bytesrow0, width); } for(int j = 0; j < height/2; j++) { for(int i = 0; i < width/2; i++) { *(pU++) = pUV[i<<1]; *(pV++) = pUV[(i<<1) + 1]; } pUV += bytesrow1; }// yuv420_data <==> pInFrame pPicIn->img.plane[0] = yuv420_data; pPicIn->img.plane[1] = pPicIn->img.plane[0] + (int)_config.videoSize.width * (int)_config.videoSize.height; pPicIn->img.plane[2] = pPicIn->img.plane[1] + (int)(_config.videoSize.width * _config.videoSize.height / 4); pPicIn->img.i_stride[0] = _config.videoSize.width; pPicIn->img.i_stride[1] = _config.videoSize.width / 2; pPicIn->img.i_stride[2] = _config.videoSize.width / 2; // 编码 int frame_size = x264_encoder_encode(pX264Handle, &pNals, &iNal, pPicIn, pPicOut); // 将编码数据写入文件 if(frame_size > 0) {for (int i = 0; i < iNal; ++i) { fwrite(pNals[i].p_payload, 1, pNals[i].i_payload, pFile); }}CVPixelBufferUnlockBaseAddress(imageBuffer, 0); }

释放资源:
- (void)freeX264Resource{ // 清除图像区域 x264_picture_clean(pPicIn); // 关闭编码器句柄 x264_encoder_close(pX264Handle); pX264Handle = NULL; free(pPicIn); pPicIn = NULL; free(pPicOut); pPicOut = NULL; free(pX264Param); pX264Param = NULL; fclose(pFile); pFile = NULL; }

【iOS音视频开发-视频软编码(x264编码H.264文件)】代码地址:
参考链接:
https://www.cnblogs.com/fojian/archive/2012/09/01/2666627.html
https://web.archive.org/web/20150207075004/http://mewiki.project357.com/wiki/X264_Settings

    推荐阅读