iOS音视频开发-视频软编码的实现-x264

视频软编码:
软编码主要是利用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文件夹里面。

同级目录.png
修改权限、执行脚本:

sudo chmod u+x build-x264.sh
sudo ./build-x264.sh
当脚本执行过程中可能会出现警告导致不能编译成功,一般为yasm版本过低或者nasm版本过低导致的(我遇到的)。

1
2
3
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.

或者

1
2
3
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文件支持的架构:

1
2
命令:lipo -info libx264.a
结果:Architectures in the fat file: libx264.a are: armv7 armv7s i386 x86_64 arm64

当然执行脚本的时候你可以选择想要的架构:

1
2
3
4
5
6
7
8
9
10
11
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编码参数设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
- (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编码主要实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
- (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