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文件)_第1张图片
    同级目录.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文件)_第2张图片
编译好的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.sh

To build for arm64://只支持arm64
./build-x264.sh arm64

To build fat library for armv7 and x86_64 (64-bit simulator)://只支持armv7和x86_64
./build-x264.sh armv7 x86_64

To 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 = (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;
}

代码地址:
参考链接:
https://www.cnblogs.com/fojian/archive/2012/09/01/2666627.html
https://web.archive.org/web/20150207075004/http://mewiki.project357.com/wiki/X264_Settings

你可能感兴趣的:(iOS音视频开发-视频软编码(x264编码H.264文件))