本文介紹iOS下使用FFmpeg+x264進(jìn)行軟編碼轧简。
x264是一個(gè)開源的H.264/MPEG-4 AVC視頻編碼函數(shù)庫驰坊,我們可以直接使用x264的API進(jìn)行編碼,也可以將x264編譯到FFmpeg中哮独,使用FFmpeg提供的API進(jìn)行編碼庐橙。
一假勿、編譯x264
1、下載gas-preprocessor文件:
- https://github.com/libav/gas-preprocessor
- 將里面的gas-preprocessor.pl拷貝到/usr/local/bin目錄下
- 修改文件權(quán)限:chmod 777 /usr/local/bin/gas-preprocessor.pl
2态鳖、下載x264源碼:
3、下載x264編譯腳本文件:
4恶导、將源碼與腳本放在一起:
-
新建一個(gè)文件夾浆竭,將編譯腳本build-x264.sh與x264源碼文件夾放入這個(gè)新建文件夾中,并將x264文件夾(x264-snapshot-xxxx)改名為"x264"
5惨寿、修改權(quán)限邦泄、執(zhí)行腳本:
- sudo chmod u+x build-x264.sh
- sudo ./build-x264.sh
- 編譯過程中會(huì)生成scratch-x264文件夾與thin-x264文件夾
- 編譯完成最終會(huì)生成"x264-iOS"文件夾
6、編譯遇到的問題:
No working C compiler found.
可能是xcode路徑問題裂垦,終端輸入命令:
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer/Found no assembler,Minimum version is yasm-x.x.x
或Found no assembler,Minimum version is nasm-x.x.x
或Found yasm x.x.x.xxxx,Minimum version is yasm-x.x.x
或Found nasm x.x.x.xxxx,Minimum version is nasm-x.x.x
原因是沒有安裝yasm/nasm或yasm/nasm版本太低顺囊,需要重新安裝yasm/nasm。
安裝yasm/nasm可通過Homebrew安裝蕉拢,Homebrew下載地址:https://brew.sh/
Homebrew安裝yasm命令:brew install yasm
Homebrew安裝nasm命令:brew install nasm如果已經(jīng)安裝了yasm/nasm并且是最新版本特碳,仍然提示上面的問題晕换,那么可能是yasm/nasm安裝的路徑?jīng)]有識(shí)別到,
which yasm
或which nasm
查看下路徑蒸其,并將最新版本的yasm/nasm拷貝到此目錄下摸袁,如果使用"sudo"命令也沒有權(quán)限但惶,那么需要按照下面的步驟關(guān)閉rootless:
(1) 關(guān)機(jī)湿蛔、重啟進(jìn)入恢復(fù)模式
重啟系統(tǒng)阳啥。按住Command + R進(jìn)入恢復(fù)模式斩狱, 在菜單中打開Terminal
(2) 關(guān)閉rootless
輸入:csrutil disable,重啟設(shè)備
(3) 拷貝完成秕岛,重新打開rootless如果編譯i386遇到No working C compiler found
可以直接將i386略過編譯碌燕,即編譯腳本中ARCHS="arm64 x86_64 i386 armv7 armv7s"
將i386去掉重新編譯慈鸠,或終端輸入./build-x264.sh arm64 x86_64 armv7 armv7s
進(jìn)行編譯
二壶冒、編譯FFmpeg+x264
1、下載FFmpeg編譯腳本:
2记罚、x264修改
- 將build-ffmpeg.sh中的
#X264=`pwd`/fat-x264
注釋去掉桐智,即X264=`pwd`/fat-x264
- 將x264編譯出來的lib庫文件夾放入ffmpeg編譯腳本的文件夾中,并改名為"fat-x264"
3躲惰、編譯FFmpeg
- ./build-ffmpeg.sh
注:如果出現(xiàn)i386問題础拨,腳本中同樣將ARCHS中的i386去掉
ARCHS="arm64 armv7 x86_64"
- 編譯完成知举,在目錄下會(huì)生成"FFmpeg-iOS"文件夾
三总寒、編碼實(shí)現(xiàn)
1圆到、將編譯好的FFmpeg-iOS
與x264-iOS
導(dǎo)入工程中
2刁赖、導(dǎo)入系統(tǒng)庫
- 導(dǎo)入<VideoToolbox/VideoToolbox.h>、libz.1.2.5.tbd僚焦、libbz2.1.0.tbd、libiconv.2.4.0.tbd等系統(tǒng)庫
可參考:http://www.reibang.com/p/5d20e2a50faa
3曙痘、Code
- X264Encoder.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface X264Encoder : NSObject
@property (assign, nonatomic) CGSize videoSize;
@property (assign, nonatomic) CGFloat frameRate;
@property (assign, nonatomic) CGFloat maxKeyframeInterval;
@property (assign, nonatomic) CGFloat bitrate;
@property (strong, nonatomic) NSString *profileLevel;
+ (instancetype)defaultX264Encoder;
- (instancetype)initX264Encoder:(CGSize)videoSize
frameRate:(NSUInteger)frameRate
maxKeyframeInterval:(CGFloat)maxKeyframeInterval
bitrate:(NSUInteger)bitrate
profileLevel:(NSString *)profileLevel;
- (void)encoding:(CVPixelBufferRef)pixelBuffer timestamp:(CGFloat)timestamp;
- (void)teardown;
@end
NS_ASSUME_NONNULL_END
- X264Encoder.m
#import "X264Encoder.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#ifdef __cplusplus
};
#endif
@implementation X264Encoder
{
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
AVPacket packet;
AVFrame *pFrame;
int pictureSize;
int frameCounter;
int frameWidth;
int frameHeight;
}
+ (instancetype)defaultX264Encoder
{
X264Encoder *x264encoder = [[X264Encoder alloc] initX264Encoder:CGSizeMake(720, 1280) frameRate:30 maxKeyframeInterval:25 bitrate:1024*1000 profileLevel:@""];
return x264encoder;
}
- (instancetype)initX264Encoder:(CGSize)videoSize
frameRate:(NSUInteger)frameRate
maxKeyframeInterval:(CGFloat)maxKeyframeInterval
bitrate:(NSUInteger)bitrate
profileLevel:(NSString *)profileLevel
{
self = [super init];
if (self) {
_videoSize = videoSize;
_frameRate = frameRate;
_maxKeyframeInterval = maxKeyframeInterval;
_bitrate = bitrate;
_profileLevel = profileLevel;
[self setupEncoder];
}
return self;
}
- (void)setupEncoder
{
avcodec_register_all();
frameCounter = 0;
frameWidth = self.videoSize.width;
frameHeight = self.videoSize.height;
// Param that must set
pCodecCtx = avcodec_alloc_context3(pCodec);
pCodecCtx->codec_id = AV_CODEC_ID_H264;
pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
pCodecCtx->width = frameWidth;
pCodecCtx->height = frameHeight;
pCodecCtx->time_base.num = 1;
pCodecCtx->time_base.den = self.frameRate;
pCodecCtx->bit_rate = self.bitrate;
pCodecCtx->gop_size = self.maxKeyframeInterval;
pCodecCtx->qmin = 10;
pCodecCtx->qmax = 51;
AVDictionary *param = NULL;
if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {
av_dict_set(¶m, "preset", "slow", 0);
av_dict_set(¶m, "tune", "zerolatency", 0);
}
pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
if (!pCodec) {
NSLog(@"Can not find encoder!");
}
if (avcodec_open2(pCodecCtx, pCodec, ¶m) < 0) {
NSLog(@"Failed to open encoder!");
}
pFrame = av_frame_alloc();
pFrame->width = frameWidth;
pFrame->height = frameHeight;
pFrame->format = AV_PIX_FMT_YUV420P;
avpicture_fill((AVPicture *)pFrame, NULL, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
pictureSize = avpicture_get_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);
av_new_packet(&packet, pictureSize);
}
- (void)encoding:(CVPixelBufferRef)pixelBuffer timestamp:(CGFloat)timestamp
{
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
UInt8 *pY = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
UInt8 *pUV = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
size_t width = CVPixelBufferGetWidth(pixelBuffer);
size_t height = CVPixelBufferGetHeight(pixelBuffer);
size_t pYBytes = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
size_t pUVBytes = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
UInt8 *pYUV420P = (UInt8 *)malloc(width * height * 3 / 2);
UInt8 *pU = pYUV420P + (width * height);
UInt8 *pV = pU + (width * height / 4);
for(int i = 0; i < height; i++) {
memcpy(pYUV420P + i * width, pY + i * pYBytes, 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 += pUVBytes;
}
pFrame->data[0] = pYUV420P;
pFrame->data[1] = pFrame->data[0] + width * height;
pFrame->data[2] = pFrame->data[1] + (width * height) / 4;
pFrame->pts = frameCounter;
int got_picture = 0;
if (!pCodecCtx) {
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
return;
}
int ret = avcodec_encode_video2(pCodecCtx, &packet, pFrame, &got_picture);
if(ret < 0) {
NSLog(@"Failed to encode!");
}
if (got_picture == 1) {
NSLog(@"Succeed to encode frame: %5d\tsize:%5d", frameCounter, packet.size);
frameCounter++;
av_free_packet(&packet);
}
free(pYUV420P);
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
}
- (void)teardown
{
avcodec_close(pCodecCtx);
av_free(pFrame);
pCodecCtx = NULL;
pFrame = NULL;
}
@end
- use
- (void)initX264Encoder
{
dispatch_sync(encodeQueue, ^{
self->x264encoder = [X264Encoder defaultX264Encoder];
});
}
- (void)teardown
{
dispatch_sync(encodeQueue, ^{
[self->x264encoder teardown];
});
}
- (void)videoWithSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
dispatch_sync(encodeQueue, ^{
if (self->isRecording) {
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CMTime ptsTime = CMSampleBufferGetOutputPresentationTimeStamp(sampleBuffer);
CGFloat pts = CMTimeGetSeconds(ptsTime);
[self->x264encoder encoding:pixelBuffer timestamp:pts];
}
});
}
以上芳悲,則實(shí)現(xiàn)了iOS下使用FFmpeg+x264進(jìn)行軟編碼的整個(gè)流程。
demo:https://github.com/XuningZhai/VideoEncode_x264