iOS音視頻開發(fā)-視頻軟編碼(x264編碼H.264文件)

視頻軟編碼:

軟編碼主要是利用CPU編碼的過程,通常為FFmpeg+x264。

  • FFmpeg
    FFmpeg是一個非常強(qiáng)大的音視頻處理庫,包括視頻采集功能、視頻格式轉(zhuǎn)換、視頻抓圖、給視頻加水印等。
    FFmpeg在Linux平臺下開發(fā),但它同樣也可以在其它操作系統(tǒng)環(huán)境中編譯運行,包括Windows、Mac OS X等。
  • x264
    H.264是ITU制定的視頻編碼標(biāo)準(zhǔn)
    而x264是一個開源的H.264/MPEG-4 AVC視頻編碼函數(shù)庫,是最好的有損視頻編碼器,里面集成了非常多優(yōu)秀的算法用于視頻編碼.
    x264官網(wǎng)
    PS:FFmpeg本身并不包含編碼器,但存在強(qiáng)大的解碼器,而x264只提供了強(qiáng)大的編碼器,但是其獨立存在,社區(qū)提供了將x264編譯進(jìn)FFmpeg的方法,所以開發(fā)時使用的為FFmpeg+x264。這里記錄x264編碼器的使用方法,F(xiàn)Fmpeg+x264的使用后續(xù)記錄。
編譯x264

下載x264源碼:

下載gas-preprocessor文件:

下載x264編譯腳本文件:

修改權(quán)限、執(zhí)行腳本:

  • sudo chmod u+x build-x264.sh
  • sudo ./build-x264.sh
    當(dāng)腳本執(zhí)行過程中可能會出現(xiàn)警告導(dǎo)致不能編譯成功,一般為yasm版本過低或者nasm版本過低導(dǎo)致的(我遇到的)。
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

腳本執(zhí)行完畢生成的文件:


編譯好的x264文件夾.png

使用命令行工具查看編譯好的.a文件支持的架構(gòu):

命令:lipo -info libx264.a
結(jié)果:Architectures in the fat file: libx264.a are: armv7 armv7s i386 x86_64 arm64

當(dāng)然執(zhí)行腳本的時候你可以選擇想要的架構(gòu):

To build everything://支持所有架構(gòu)
./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://支持各架構(gòu)獨立庫文件
./build-x264.sh lipo
x264編碼實現(xiàn)

將編譯好的x264-iOS文件夾拖入工程即可。
x264編碼參數(shù)設(shè)置:

- (void)setupEncodeWithConfig:(BBVideoConfig *)config{
    
    _config = config;
    
    pX264Param = (x264_param_t *)malloc(sizeof(x264_param_t));
    assert(pX264Param);
    /* 配置參數(shù)預(yù)設(shè)置
     * 主要是zerolatency該參數(shù),即時編碼。
     * static const char * const x264_tune_names[] = { "film", "animation", "grain", "stillimage", "psnr", "ssim", "fastdecode", "zerolatency", 0 };
     */
    x264_param_default_preset(pX264Param, "veryfast", "zerolatency");
    
    /* 設(shè)置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; // 取空緩沖區(qū)繼續(xù)使用不死鎖的保證
    
    // 視頻寬高
    pX264Param->i_width   = config.videoSize.width; // 要編碼的圖像寬度.
    pX264Param->i_height  = config.videoSize.height; // 要編碼的圖像高度
    pX264Param->i_frame_total = 0; //編碼總幀數(shù),未知設(shè)置為0
    
    // 流參數(shù)
    pX264Param->b_cabac = 0; //支持利用基于上下文的自適應(yīng)的算術(shù)編碼 0為不支持
    pX264Param->i_bframe = 5;//兩個參考幀之間B幀的數(shù)量
    pX264Param->b_interlaced = 0;//隔行掃描
    pX264Param->rc.i_rc_method = X264_RC_ABR; // 碼率控制,CQP(恒定質(zhì)量),CRF(恒定碼率),ABR(平均碼率)
    pX264Param->i_level_idc = 30; // 編碼復(fù)雜度
    
    // 圖像質(zhì)量
    pX264Param->rc.f_rf_constant = 15; // rc.f_rf_constant是實際質(zhì)量,越大圖像越花,越小越清晰
    pX264Param->rc.f_rf_constant_max = 45; // param.rc.f_rf_constant_max ,圖像質(zhì)量的最大值。
    
    // 速率控制參數(shù) 通常為屏幕分辨率*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) ; // 平均碼率模式下,最大瞬時碼率,默認(rèn)0(與-B設(shè)置相同)
    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;//默認(rèn)0.9
    
    
    // 使用實時視頻傳輸時,需要實時發(fā)送sps,pps數(shù)據(jù)
    pX264Param->b_repeat_headers = 1;  // 重復(fù)SPS/PPS 放到關(guān)鍵幀前面。該參數(shù)設(shè)置是讓每個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
     * 一般為幀率的整數(shù)倍,通常設(shè)置2倍,即 GOP = 幀率 * 2;
     */
    pX264Param->b_intra_refresh = 1;
    pX264Param->b_annexb = 1;
    pX264Param->i_keyint_max = config.fps * 2;
    
    
    // Log參數(shù),打印編碼信息
    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得到設(shè)置給X264
    // 的參數(shù).通過x264_encoder_reconfig更新X264的參數(shù)
    pX264Handle = x264_encoder_open(pX264Param);
    assert(pX264Handle);
    
}

x264編碼主要實現(xiàn)代碼:

- (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);
    
    // 將編碼數(shù)據(jù)寫入文件
    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{
    // 清除圖像區(qū)域
    x264_picture_clean(pPicIn);
    // 關(guān)閉編碼器句柄
    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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容