視頻軟編碼:
軟編碼主要是利用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文件:
- https://github.com/libav/gas-preprocessor。
- 將下載的gas-preprocessor文件拷貝到/usr/local/bin目錄下。
- 修改文件權(quán)限:chmod 777 /usr/local/bin/gas-preprocessor.pl。
下載x264編譯腳本文件:
- https://github.com/kewlbear/x264-ios
-
將腳本文件build-x264.sh 放在x264源碼文件同級目錄下,并不是x264文件夾里面。
同級目錄.png
修改權(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
