iOS FFmpeg+x264 編碼

本文介紹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文件:
gas-preprocessor
2态鳖、下載x264源碼:
3、下載x264編譯腳本文件:
4恶导、將源碼與腳本放在一起:
  • 新建一個(gè)文件夾浆竭,將編譯腳本build-x264.sh與x264源碼文件夾放入這個(gè)新建文件夾中,并將x264文件夾(x264-snapshot-xxxx)改名為"x264"


    x264源碼文件與x264編譯腳本
5惨寿、修改權(quán)限邦泄、執(zhí)行腳本:
  • sudo chmod u+x build-x264.sh
  • sudo ./build-x264.sh
  • 編譯過程中會(huì)生成scratch-x264文件夾與thin-x264文件夾
編譯中
  • 編譯完成最終會(huì)生成"x264-iOS"文件夾
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 yasmwhich 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=`pwd`/fat-x264
  • 將x264編譯出來的lib庫文件夾放入ffmpeg編譯腳本的文件夾中,并改名為"fat-x264"
fat-x264
3躲惰、編譯FFmpeg
  • ./build-ffmpeg.sh
    注:如果出現(xiàn)i386問題础拨,腳本中同樣將ARCHS中的i386去掉
    ARCHS="arm64 armv7 x86_64"
  • 編譯完成知举,在目錄下會(huì)生成"FFmpeg-iOS"文件夾
FFmpeg-iOS

三总寒、編碼實(shí)現(xiàn)
1圆到、將編譯好的FFmpeg-iOSx264-iOS導(dǎo)入工程中
lib
2刁赖、導(dǎo)入系統(tǒng)庫
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(&param, "preset", "slow", 0);
        av_dict_set(&param, "tune", "zerolatency", 0);
    }
    pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
    if (!pCodec) {
        NSLog(@"Can not find encoder!");
    }
    if (avcodec_open2(pCodecCtx, pCodec, &param) < 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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末边坤,一起剝皮案震驚了整個(gè)濱河市名扛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌茧痒,老刑警劉巖肮韧,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異旺订,居然都是意外死亡弄企,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門区拳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拘领,“玉大人,你說我怎么就攤上這事樱调≡妓兀” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵笆凌,是天一觀的道長(zhǎng)圣猎。 經(jīng)常有香客問我,道長(zhǎng)菩颖,這世上最難降的妖魔是什么样漆? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮晦闰,結(jié)果婚禮上放祟,老公的妹妹穿的比我還像新娘鳍怨。我一直安慰自己,他們只是感情好跪妥,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布鞋喇。 她就那樣靜靜地躺著,像睡著了一般眉撵。 火紅的嫁衣襯著肌膚如雪侦香。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天纽疟,我揣著相機(jī)與錄音罐韩,去河邊找鬼。 笑死污朽,一個(gè)胖子當(dāng)著我的面吹牛散吵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蟆肆,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼矾睦,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了炎功?” 一聲冷哼從身側(cè)響起枚冗,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛇损,沒想到半個(gè)月后赁温,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡州藕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年束世,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片床玻。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡毁涉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出锈死,到底是詐尸還是另有隱情贫堰,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布待牵,位于F島的核電站其屏,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏缨该。R本人自食惡果不足惜偎行,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蛤袒,春花似錦熄云、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至珍德,卻和暖如春练般,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锈候。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工薄料, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人泵琳。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓都办,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親虑稼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 視頻軟編碼: 軟編碼主要是利用CPU編碼的過程势木,通常為FFmpeg+x264蛛倦。 FFmpegFFmpeg是一個(gè)非常...
    ibabyblue閱讀 2,031評(píng)論 3 0
  • 1.FFmepg編譯環(huán)境及結(jié)構(gòu) 下載FFmepg FFmpeg配置選項(xiàng)介紹 下載gas-preprocessor....
    Jackey_song閱讀 2,196評(píng)論 2 2
  • 最近有時(shí)間整理一下自己之前學(xué)習(xí)FFmpeg的相關(guān)知識(shí)點(diǎn),特別是在編譯FFmpeg的過程中因?yàn)楦鞣N原因很容易出現(xiàn)各種...
    落葉隨風(fēng)_90e5閱讀 12,938評(píng)論 0 8
  • 1.編譯FFmpeg+x264 FFmpeg 編譯腳本:https://github.com/kewlbear/F...
    糖糖uzi閱讀 2,264評(píng)論 0 0
  • 今天把《窮查理寶典》看了一半啦桌,有些內(nèi)容有些看不懂溯壶,有些觀點(diǎn)我覺得很平凡,然后在網(wǎng)上搜了搜甫男,發(fā)現(xiàn)大家評(píng)價(jià)很高且改,因?yàn)檫@...
    玩家9564閱讀 67評(píng)論 0 0