iOS音視頻開發(fā)-視頻軟編碼(FFmpeg+x264編碼H.264文件)

上篇記錄了利用x264編碼的實現(xiàn)幻枉,這里記錄一下FFmpeg+x264編碼為H.264的實現(xiàn)過程,為什么使用FFmpeg+x264弧呐,因為兩種框架互補組成了強大的編解碼器(FFmpeg解碼折晦,x246編碼)教沾。


編譯FFmpeg+x264

FFmpeg+x264編譯步驟:

  • 1、將編譯好的x264文件夾放置在FFmpeg腳本目錄下朱转,并將文件夾改名為fat-x264(因為腳本中定義的引用x264文件夾的名稱為fat-x264);
  • 2蟹地、執(zhí)行腳本文件:./build-ffmpeg.sh。
    這里需要注意的問題藤为,之前編譯并沒有遇到怪与,此次總結(jié)重新編譯了一次,遇到一些問題記錄一下凉蜂。
    • 1)最新的FFmpeg版本為:n3.4.2琼梆,腳本中使用版本號為:n3.4。執(zhí)行腳本的時候會出現(xiàn)諸如此類的錯誤:
    libavcodec/libx264.c: In function 'x264_init_static':
    libavcodec/libx264.c:892.9 error: 'x264_bit_depth' undeclared(first use in this     function) 
               if(x264_bit_depth== 8)  
    
    解決辦法:修改腳本文件中的版本號:FF_VERSION="3.4->FF_VERSION="3.4.2"窿吩。
    • 2)要將x264編譯進FFmpeg中茎杂,需要取消腳本中對該句代碼的注銷:
      #X264=`pwd`/fat-x264 ->X264=`pwd`/fat-x264
    • 3)關(guān)于bitcode,現(xiàn)階段在編譯庫文件的時候纫雁,支持bitcode還是有必要的(畢竟某個工程因為使用了此編譯庫文件放棄bitcode功能煌往,向上層開發(fā)者提供了功能,是不使用是他們的事了)轧邪,腳本中已經(jīng)實現(xiàn)了支持bitcode功能刽脖,不必修改。
    • 4)將編譯好的文件拖拽到工程中忌愚,需要添加的依賴庫為:libiconv.dylib/libz.dylib/libbz2.dylib/CoreMedia.framework/AVFoundation.framework/VideoToolbox.framework曲管,不然會報并未支持arm64......架構(gòu)的錯誤。

命令行命令:

//查看是否支持相應(yīng)的架構(gòu):arm64 i386....
lipo -info libxx.a
//查看是夠支持bitcode  >=0
otool -l libx264.a | grep __bitcode | wc -l

介紹:不錯的編譯過程文章

視頻捕獲和編碼配置代碼在前面已記錄硕糊,這里就不贅述了院水。FFmpeg+x264編碼實現(xiàn)代碼如下:

#import <Foundation/Foundation.h>
#import <CoreMedia/CoreMedia.h>
@class BBVideoConfig;
@interface BBH264SoftEncoder : NSObject
/*
 * 設(shè)置編碼后文件的保存路徑
 */
- (void)setFilePath:(NSString *)path;

/*
 * 初始化編碼配置
 */
- (void)setupEncodeWithConfig:(BBVideoConfig *)config;

/*
 * 將CMSampleBufferRef格式的數(shù)據(jù)編碼成h264并寫入文件
 */
- (void)encoderToH264:(CMSampleBufferRef)sampleBuffer;

/*
 * 釋放資源
 */
- (void)freeX264Resource;
@end

#import "BBH264SoftEncoder.h"
#import "BBVideoConfig.h"

#ifdef __cplusplus
extern "C" {
#endif
    
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
    
#ifdef __cplusplus
};
#endif

@implementation BBH264SoftEncoder
{
    AVFormatContext                     *pFormatCtx;
    AVOutputFormat                      *fmt;
    AVStream                            *video_st;
    AVCodecContext                      *pCodecCtx;
    AVCodec                             *pCodec;
    AVPacket                             pkt;
    uint8_t                             *picture_buf;
    AVFrame                             *pFrame;
    int                                  picture_size;
    int                                  y_size;
    int                                  framecnt;
    char                                *out_file;
    
    int                                  encoder_h264_frame_width; // 編碼的圖像寬度
    int                                  encoder_h264_frame_height; // 編碼的圖像高度
}

/*
 * 設(shè)置編碼后文件的文件名,保存路徑
 */
- (void)setFilePath:(NSString *)path;
{
    out_file = [self nsstring2char:path];
}

/*
 * 將路徑轉(zhuǎn)成C語言字符串(傳入路徑為C字符串)
 */
- (char*)nsstring2char:(NSString *)path
{
    
    NSUInteger len = [path length];
    char *filepath = (char*)malloc(sizeof(char) * (len + 1));
    
    [path getCString:filepath maxLength:len + 1 encoding:[NSString defaultCStringEncoding]];
    
    return filepath;
}


/*
 *  設(shè)置X264
 */
- (void)setupEncodeWithConfig:(BBVideoConfig *)config
{
    // 1.默認(rèn)從第0幀開始(記錄當(dāng)前的幀數(shù))
    framecnt = 0;
    
    // 2.記錄傳入的寬度&高度
    encoder_h264_frame_width = config.videoSize.width;
    encoder_h264_frame_height = config.videoSize.height;
    
    // 3.注冊FFmpeg所有編解碼器(無論編碼還是解碼都需要該步驟)
    av_register_all();
    
    // 4.初始化AVFormatContext: 用作之后寫入視頻幀并編碼成 h264简十,貫穿整個工程當(dāng)中(釋放資源時需要銷毀)
    pFormatCtx = avformat_alloc_context();
    
    // 5.設(shè)置輸出文件的路徑檬某,fmt初始化的時候根據(jù)傳入的參數(shù)猜出video_codec、mime_type螟蝙、extensions等等信息恢恼。
    fmt = av_guess_format(NULL, out_file, NULL);
    pFormatCtx->oformat = fmt;
    
    // 6.打開文件的緩沖區(qū)輸入輸出,flags 標(biāo)識為  AVIO_FLAG_READ_WRITE 胰默,可讀寫
    if (avio_open(&pFormatCtx->pb, out_file, AVIO_FLAG_READ_WRITE) < 0){
        printf("Failed to open output file! \n");
    }
    
    // 7.創(chuàng)建新的輸出流, 用于寫入文件
    video_st = avformat_new_stream(pFormatCtx, 0);
    
    if (video_st==NULL){ printf("Failed to setup stream! \n"); return; }
    
    // 8.pCodecCtx 用戶存儲編碼所需的參數(shù)格式等等
    // 8.1.從媒體流中獲取到編碼結(jié)構(gòu)體场斑,他們是一一對應(yīng)的關(guān)系漓踢,一個 AVStream 對應(yīng)一個  AVCodecContext
    AVCodec *codec = avcodec_find_encoder(pFormatCtx->oformat->video_codec);
    pCodecCtx = avcodec_alloc_context3(codec);
    
    // 8.2.設(shè)置編碼器的編碼格式(是一個id),每一個編碼器都對應(yīng)著自己的 id和簸,例如 h264 的編碼 id 就是 AV_CODEC_ID_H264
    pCodecCtx->codec_id = fmt->video_codec;
    
    // 8.3.設(shè)置編碼類型為 視頻編碼
    pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;
    
    // 8.4.設(shè)置像素格式為 yuv 格式
    pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
    
    // 8.5.設(shè)置視頻的寬高
    pCodecCtx->width = encoder_h264_frame_width;
    pCodecCtx->height = encoder_h264_frame_height;
    
    // 8.6.設(shè)置幀率
    pCodecCtx->time_base.num = 1;
    pCodecCtx->time_base.den = 25;
    
    // 8.7.設(shè)置碼率(比特率)
    pCodecCtx->bit_rate = config.bitrate;
    
    // 8.8.視頻質(zhì)量度量標(biāo)準(zhǔn)(常見qmin=10, qmax=51)
    pCodecCtx->qmin = 10;
    pCodecCtx->qmax = 51;
    
    // 8.9.設(shè)置圖像組層的大小(GOP-->兩個I幀之間的間隔)
    pCodecCtx->gop_size = 30;
    
    // 8.10.設(shè)置 B 幀最大的數(shù)量彭雾,B幀為視頻圖片空間的前后預(yù)測幀, B 幀相對于 I锁保、P 幀來說薯酝,壓縮率比較大,也就是說相同碼率的情況下爽柒,
    // 越多 B 幀的視頻吴菠,越清晰,現(xiàn)在很多打視頻網(wǎng)站的高清視頻浩村,就是采用多編碼 B 幀去提高清晰度做葵,
    // 但同時對于編解碼的復(fù)雜度比較高,比較消耗性能與時間
    pCodecCtx->max_b_frames = 5;
    
    // 9.可選設(shè)置
    AVDictionary *param = 0;
    // H.264
    if(pCodecCtx->codec_id == AV_CODEC_ID_H264) {
        // 通過--preset的參數(shù)調(diào)節(jié)編碼速度和質(zhì)量的平衡心墅。
        av_dict_set(&param, "preset", "slow", 0);
        
        // 通過--tune的參數(shù)值指定片子的類型酿矢,是和視覺優(yōu)化的參數(shù),或有特別的情況怎燥。
        // zerolatency: 零延遲瘫筐,用在需要非常低的延遲的情況下,比如視頻直播的編碼
        av_dict_set(&param, "tune", "zerolatency", 0);
    }
    
    // 10.輸出打印信息铐姚,內(nèi)部是通過printf函數(shù)輸出(不需要輸出可以注釋掉)
    av_dump_format(pFormatCtx, 0, out_file, 1);
    
    // 11.通過 codec_id 找到對應(yīng)的編碼器
    pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
    if (!pCodec) {
        printf("Can not find encoder! \n");
    }
    
    // 12.打開編碼器策肝,并設(shè)置參數(shù) param
    if (avcodec_open2(pCodecCtx, pCodec,&param) < 0) {
        printf("Failed to open encoder! \n");
    }
    
    // 13.將AVCodecContext的成員復(fù)制到AVCodecParameters結(jié)構(gòu)體
    avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);
    
    // 14.真實幀率
    AVRational rational = {1, 25};
    av_stream_set_r_frame_rate(video_st, rational);
    
    // 15.初始化原始數(shù)據(jù)對象: AVFrame
    pFrame = av_frame_alloc();
    
    // 16.通過像素格式(這里為 YUV)獲取圖片的真實大小,例如將 480 * 720 轉(zhuǎn)換成 int 類型
    av_image_fill_arrays(pFrame->data, pFrame->linesize, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
    
    // 17.h264 封裝格式的文件頭部隐绵,基本上每種編碼都有著自己的格式的頭部之众。
    if (avformat_write_header(pFormatCtx, NULL) < 0) { printf("Failed to write! \n"); return; }
    
    // 18.創(chuàng)建編碼后的數(shù)據(jù) AVPacket 結(jié)構(gòu)體來存儲 AVFrame 編碼后生成的數(shù)據(jù)
    av_new_packet(&pkt, picture_size);
    
    // 19.設(shè)置 yuv 數(shù)據(jù)中 y 圖的寬高
    y_size = pCodecCtx->width * pCodecCtx->height;
    
}

/*
 * 將CMSampleBufferRef格式的數(shù)據(jù)編碼成h264并寫入文件
 *
 */
- (void)encoderToH264:(CMSampleBufferRef)sampleBuffer
{
    // 1.通過CMSampleBufferRef對象獲取CVPixelBufferRef對象
    CVPixelBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    
    // 2.鎖定imageBuffer內(nèi)存地址開始進行編碼
    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.根據(jù)像素獲取圖片的真實寬度&高度
        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);
        
        // 3.4.將NV12數(shù)據(jù)轉(zhuǎn)成YUV420P(I420)數(shù)據(jù)
        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的數(shù)據(jù)
        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.設(shè)置當(dāng)前幀
        pFrame->pts = framecnt;
        
        // 4.設(shè)置寬度高度以及YUV格式
        pFrame->width = encoder_h264_frame_width;
        pFrame->height = encoder_h264_frame_height;
        pFrame->format = AV_PIX_FMT_YUV420P;
        
        // 5.對編碼前的原始數(shù)據(jù)(AVFormat)利用編碼器進行編碼峭跳,將 pFrame 編碼后的數(shù)據(jù)傳入pkt 中
        int ret = avcodec_send_frame(pCodecCtx, pFrame);
        if (ret != 0) {
            printf("Failed to encode! \n");
            return;
        }
        
        while (avcodec_receive_packet(pCodecCtx, &pkt) == 0) {
            framecnt++;
            pkt.stream_index = video_st->index;
            //也可以使用C語言函數(shù):fwrite()膘婶、fflush()寫文件和清空文件寫入緩沖區(qū)。
            ret = av_write_frame(pFormatCtx, &pkt);
            if (ret < 0) {
                printf("Failed write to file坦康!\n");
            }
            //釋放packet
            av_packet_unref(&pkt);
        }
        
        // 7.釋放yuv數(shù)據(jù)
        free(yuv420_data);
    }
    
    CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
}

/*
 * 釋放資源
 */
- (void)freeX264Resource
{
    
    // 1.將還未輸出的AVPacket輸出出來
    av_write_trailer(pFormatCtx);
    
    // 2.關(guān)閉資源
    avcodec_close(pCodecCtx);
    av_free(pFrame);
    
    avio_close(pFormatCtx->pb);
    avformat_free_context(pFormatCtx);
}
@end

編碼使用FFmpeg新版API實現(xiàn)竣付,網(wǎng)上很多都是較舊的API代碼诡延,但不影響滞欠,只是個別接口變更而已,整體的實現(xiàn)思路一致肆良。
學(xué)習(xí)過程中筛璧,查閱了大量的資料逸绎,收獲頗豐,非常感謝學(xué)習(xí)路上各位coder的無私分享夭谤,尤其coderWhy先森棺牧、七牛的深愛、雷霄驊(致敬)朗儒。
參考鏈接:
https://depthlove.github.io/2015/09/18/use-ffmpeg-and-x264-encode-iOS-camera-video-to-h264/
http://blog.csdn.net/leixiaohua1020/article/details/25430425

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颊乘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子醉锄,更是在濱河造成了極大的恐慌乏悄,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恳不,死亡現(xiàn)場離奇詭異檩小,居然都是意外死亡,警方通過查閱死者的電腦和手機烟勋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門规求,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卵惦,你說我怎么就攤上這事阻肿。” “怎么了鸵荠?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵冕茅,是天一觀的道長。 經(jīng)常有香客問我蛹找,道長姨伤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任庸疾,我火速辦了婚禮乍楚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘届慈。我一直安慰自己徒溪,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布金顿。 她就那樣靜靜地躺著臊泌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪揍拆。 梳的紋絲不亂的頭發(fā)上渠概,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音,去河邊找鬼播揪。 笑死贮喧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的猪狈。 我是一名探鬼主播箱沦,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼雇庙!你這毒婦竟也來了谓形?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤疆前,失蹤者是張志新(化名)和其女友劉穎套耕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體峡继,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡冯袍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了碾牌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片康愤。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖舶吗,靈堂內(nèi)的尸體忽然破棺而出征冷,到底是詐尸還是另有隱情,我是刑警寧澤誓琼,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布检激,位于F島的核電站,受9級特大地震影響腹侣,放射性物質(zhì)發(fā)生泄漏叔收。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一傲隶、第九天 我趴在偏房一處隱蔽的房頂上張望饺律。 院中可真熱鬧,春花似錦跺株、人聲如沸复濒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巧颈。三九已至,卻和暖如春袖扛,著一層夾襖步出監(jiān)牢的瞬間砸泛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留晾嘶,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓娶吞,卻偏偏與公主長得像垒迂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子妒蛇,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,435評論 2 359

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