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源码:

下载gas-preprocessor文件:

下载x264编译脚本文件:

修改权限、执行脚本:

  • 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

脚本执行完毕生成的文件:


编译好的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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容