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的使用后续记录。
- https://www.videolan.org/developers/x264.html
- https://github.com/libav/gas-preprocessor。
- 将下载的gas-preprocessor文件拷贝到/usr/local/bin目录下。
- 修改文件权限:chmod 777 /usr/local/bin/gas-preprocessor.pl。
- https://github.com/kewlbear/x264-ios
- 将脚本文件build-x264.sh 放在x264源码文件同级目录下,并不是x264文件夹里面。
文章图片
同级目录.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
文章图片
编译好的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
推荐阅读
- 深入理解Go之generate
- 2020-04-07vue中Axios的封装和API接口的管理
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- iOS中的Block
- 记录iOS生成分享图片的一些问题,根据UIView生成固定尺寸的分享图片
- 2019-08-29|2019-08-29 iOS13适配那点事
- Hacking|Hacking with iOS: SwiftUI Edition - SnowSeeker 项目(一)
- iOS面试题--基础
- 我的软件测试开发工程师书单
- echart|echart 双轴图开发