iOS直播技术分享-视频编码(三)
x264是一种免费的、具有更优秀算法的符合H.264/MPEG-4 AVC视频压缩编码标准格式的编码库。它同xvid一样都是开源项目,但x264是采用H.264标准的,而xvid是采用MPEG-4早期标准的。由于H.264是2003年正式发布的最新的视频编码标准,因此,在通常情况下,x264压缩出的视频文件在相同质量下要比xvid压缩出的文件要小,或者也可以说,在相同体积下比xvid压缩出的文件质量要好。它符合GPL许可证。
iOS视频编码分为硬编码和软编码:硬编码就是利用手机专用的硬件进行编码,软编码是用CPU进行编码。由于苹果在iOS8开放的硬编码的API,故现在大多数的直播应用都是采用的硬编码。
iOS硬编码
从iOS8开始,苹果开放了硬解码和硬编码API,框架为 VideoToolbox.framework, 此框架需要在iOS8及以上的系统上才能使用。
此框架中的硬解码API是几个纯C函数,在任何OC或者 C++代码里都可以使用。使用的时候,首先,要把VideoToolbox.framework 添加到工程里,并且在要使用该API的文件中包含头文件#include <VideoToolbox/VideoToolbox.h>,然后,就可以畅快的高效的对视频流进行硬编码了。
直接上代码来说明,首先是定义了编码所需的变量
1 | @interface CLHardwareVideoEncoder (){ |
初始化编码session
1 | - (void)initCompressionSession{ |
编码输入
1 | - (void)encodeVideoData:(CVImageBufferRef)pixelBuffer timeStamp:(uint64_t)timeStamp{ |
回调
1 | static void VideoCompressonOutputCallback(void *VTref, void *VTFrameRef, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer) |
iOS软编码
软编码主要是利用CPU进行编码的过程, 具体的编码通常会用FFmpeg+x264,需要自己先编译FFmpeg(iOS)和X264。
- 将编译好的文件夹拖入到工程中
- 添加依赖库:
libiconv.dylib/libz.dylib/libbz2.dylib/CoreMedia.framework/AVFoundation.framework - FFmpeg编码两个重要的类
- AVFormat
保存的是解码后和原始的音视频信息 - AVPacket
解码完成的数据及附加信息(解码时间戳、显示时间戳、时长等)
1 | /* |
- 编码每一帧数据
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/*
* 将CMSampleBufferRef格式的数据编码成h264并写入文件
*
*/
- (void)encoderToH264:(CMSampleBufferRef)sampleBuffer
{
// 1.通过CMSampleBufferRef对象获取CVPixelBufferRef对象
CVPixelBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// 2.锁定imageBuffer内存地址开始进行编码
if (CVPixelBufferLockBaseAddress(imageBuffer, 0) == kCVReturnSuccess) {
// 3.从CVPixelBufferRef读取YUV的值
// NV12和NV21属于YUV格式,是一种two-plane模式,即Y和UV分为两个Plane,但是UV(CbCr)为交错存储,而不是分为三个plane
// 3.1.获取Y分量的地址
UInt8 *bufferPtr = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer,0);
// 3.2.获取UV分量的地址
UInt8 *bufferPtr1 = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer,1);
// 3.3.根据像素获取图片的真实宽度&高度
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
// 获取Y分量长度
size_t bytesrow0 = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer,0);
size_t bytesrow1 = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer,1);
UInt8 *yuv420_data = (UInt8 *)malloc(width * height *3/2);
/* convert NV12 data to YUV420*/
// 3.4.将NV12数据转成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;
}
// 3.5.分别读取YUV的数据
picture_buf = yuv420_data;
pFrame->data[0] = picture_buf; // Y
pFrame->data[1] = picture_buf+ y_size; // U
pFrame->data[2] = picture_buf+ y_size*5/4; // V
// 4.设置当前帧
pFrame->pts = framecnt;
int got_picture = 0;
// 4.设置宽度高度以及YUV各式
pFrame->width = encoder_h264_frame_width;
pFrame->height = encoder_h264_frame_height;
pFrame->format = PIX_FMT_YUV420P;
// 5.对编码前的原始数据(AVFormat)利用编码器进行编码,将 pFrame 编码后的数据传入pkt 中
int ret = avcodec_encode_video2(pCodecCtx, &pkt, pFrame, &got_picture);
if(ret < 0) {
printf("Failed to encode! \n");
}
// 6.编码成功后写入 AVPacket 到 输入输出数据操作着 pFormatCtx 中,当然,记得释放内存
if (got_picture==1) {
framecnt++;
pkt.stream_index = video_st->index;
ret = av_write_frame(pFormatCtx, &pkt);
av_free_packet(&pkt);
}
// 7.释放yuv数据
free(yuv420_data);
}
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
} - 释放资源
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/*
* 释放资源
*/
- (void)freeX264Resource
{
// 1.释放AVFormatContext
int ret = flush_encoder(pFormatCtx,0);
if (ret < 0) {
printf("Flushing encoder failed\n");
}
// 2.将还未输出的AVPacket输出出来
av_write_trailer(pFormatCtx);
// 3.关闭资源
if (video_st){
avcodec_close(video_st->codec);
av_free(pFrame);
}
avio_close(pFormatCtx->pb);
avformat_free_context(pFormatCtx);
}
int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index)
{
int ret;
int got_frame;
AVPacket enc_pkt;
if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
CODEC_CAP_DELAY))
return 0;
while (1) {
enc_pkt.data = NULL;
enc_pkt.size = 0;
av_init_packet(&enc_pkt);
ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
NULL, &got_frame);
av_frame_free(NULL);
if (ret < 0)
break;
if (!got_frame){
ret=0;
break;
}
ret = av_write_frame(fmt_ctx, &enc_pkt);
if (ret < 0)
break;
}
return ret;
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Clang's Blog!
评论