iOS 編碼流程和使用方法

這里只會涉及到編碼的代碼,有些東西可能是和rtmp推流是不適用的。
第一步:

注冊編碼器
av_register_all();

第二步:

初始化輸入碼流參數AVFormatContext,它包含的碼流參數比較多,主要含有一下部分:
struct AVInputFormat *iformat:輸入數據的封裝格式
AVIOContext *pb:輸入數據的緩存
unsigned int nb_streams:視音頻流的個數
AVStream **streams:視音頻流
char filename[1024]:文件名
int64_t duration:時長(單位:微秒us,轉換為秒需要除以1000000)
int bit_rate:比特率(單位bps,轉換為kbps需要除以1000)
AVDictionary *metadata:元數據

初始方法
pFormatConttext = avformat_alloc_context();

第三步:

初始化AVStream,AVStream是存儲每一個視頻/音頻流信息的結構體,它所帶參數有以下部分:
int index:標識該視頻/音頻流
AVCodecContext *codec:指向該視頻/音頻流的AVCodecContext(它們是一一對應的關系)
AVRational time_base:時基。通過該值可以把PTS,DTS轉化為真正的時間。FFMPEG其他結構體中也有這個字段,但是根據我的經驗,只有AVStream中的time_base是可用的。PTS*time_base=真正的時間
int64_t duration:該視頻/音頻流長度
AVDictionary *metadata:元數據信息
AVRational avg_frame_rate:幀率(注:對視頻來說,這個挺重要的)
AVPacket attached_pic:附帶的圖片。比如說一些MP3,AAC音頻文件附帶的專輯封面。

初始方法:
stream = avformat_new_stream(pFormatConttext, 0);

第四步:

初始化AVCodecContext,AVCodecContext是一個編碼信息設置體,編碼效果如何都是取決與對它的參數設置。

初始化,這里我是承接上一個參數設置的。
videoCodingContext = stream->codec

下面都是它設置的參數:
    //編碼器的ID號,這里我們自行指定為264編碼器,實際上也可以根據AVStream里的codecID 參數賦值
    videoCodingContext->codec_id = AV_CODEC_ID_H264;
    //編碼器編碼的數據類型
    videoCodingContext->codec_type = AVMEDIA_TYPE_VIDEO;
    //像素的格式,也就是說采用什么樣的色彩空間來表明一個像素點
    videoCodingContext->pix_fmt = AV_PIX_FMT_YUV420P;
    //編碼目標的視頻幀大小,以像素為單位
    videoCodingContext->width = encoder_h264_frame_width;
    videoCodingContext->height = encoder_h264_frame_height;
    //幀率的基本單位,我們用分數來表示,
    //用分數來表示的原因是,有很多視頻的幀率是帶小數的eg:NTSC 使用的幀率是29.97
    videoCodingContext->time_base.num = 1;
    videoCodingContext->time_base.den = 15;
    //目標的碼率,即采樣的碼率;顯然,采樣碼率越大,視頻大小越大
    videoCodingContext->bit_rate = 1500000;
    //每250幀插入1個I幀,I幀越少,視頻越小
    videoCodingContext->gop_size = 250;
    //最大和最小量化系數
    videoCodingContext->qmin = 10;
    videoCodingContext->qmax = 51;
    //兩個非B幀之間允許出現多少個B幀數
    //設置0表示不使用B幀
    //b 幀越多,圖片越小
    videoCodingContext->max_b_frames = 3;
    
    //新添加
    videoCodingContext->dct_algo = 0;
    videoCodingContext->me_pre_cmp = 2;//運動場景預判功能的力度,數值越大編碼時間越長
    videoCodingContext->qmin = 25;//最大和最小量化系數
    videoCodingContext->qmax = 40;
    videoCodingContext->max_qdiff = 3;
    videoCodingContext->gop_size = 250; //關鍵幀的最大間隔幀數/
    videoCodingContext->keyint_min = 10; //關鍵幀的最小間隔幀數,取值范圍10-51
    videoCodingContext->refs = 2;    //運動補償
    videoCodingContext->rc_max_rate = 200000;//最大碼流,x264中單位kbps,ffmpeg中單位bps
    videoCodingContext->rc_min_rate = 512000;//最小碼流
    //    pCodecCtx->rc_buffer_size = 2000000;
    //新增
    videoCodingContext->mb_decision = 1;
    videoCodingContext->keyint_min = 25;

    videoCodingContext->scenechange_threshold = 40;
    videoCodingContext->rc_strategy = 2;//碼率控制測率,宏定義,查API

第五步

初始化AVCodec,AVCodec是存儲編解碼器信息的結構體,其主要包含參數
const char *name:編解碼器的名字,比較短
const char *long_name:編解碼器的名字,全稱,比較長
enum AVMediaType type:指明了類型,是視頻,音頻,還是字幕
enum AVCodecID id:ID,不重復
const AVRational *supported_framerates:支持的幀率(僅視頻)
const enum AVPixelFormat *pix_fmts:支持的像素格式(僅視頻)
const int *supported_samplerates:支持的采樣率(僅音頻)
const enum AVSampleFormat *sample_fmts:支持的采樣格式(僅音頻)
const uint64_t *channel_layouts:支持的聲道數(僅音頻)
int priv_data_size:私有數據的大小
其初始化也是根據其邏輯來實現的,這里會根據videoCodingContext來進行初始化

初始化代碼
codec = avcodec_find_encoder(videoCodingContext->codec_id);
AVDictionary *param = 0;
//初始化編碼器
    if (avcodec_open2(videoCodingContext, codec, &param) < 0) {
        NSLog(@"編碼器初始化失敗");
        return NO;
    }

第六步

AVFrame, AVFrame是包含碼流參數較多的結構體,AVFrame就是用來保存幀數據的。應為我們編碼出的數據是以流的形式存在的所以我們有個流體。如果要做圖像識別這里能起到幫助的作用。
uint8_t *data[AV_NUM_DATA_POINTERS]:解碼后原始數據(對視頻來說是YUV,RGB,對音頻來說是PCM)
int linesize[AV_NUM_DATA_POINTERS]:data中“一行”數據的大小。注意:未必等于圖像的寬,一般大于圖像的寬。
int width, height:視頻幀寬和高(1920x1080,1280x720...)
int nb_samples:音頻的一個AVFrame中可能包含多個音頻幀,在此標記包含了幾個
int format:解碼后原始數據類型(YUV420,YUV422,RGB24...)
int key_frame:是否是關鍵幀
enum AVPictureType pict_type:幀類型(I,B,P...)
AVRational sample_aspect_ratio:寬高比(16:9,4:3...)
int64_t pts:顯示時間戳
int coded_picture_number:編碼幀序號
int display_picture_number:顯示幀序號
int8_t *qscale_table:QP表
uint8_t *mbskip_table:跳過宏塊表
int16_t (*motion_val[2])[2]:運動矢量表
uint32_t *mb_type:宏塊類型表
short *dct_coeff:DCT系數,這個沒有提取過
int8_t *ref_index[2]:運動估計參考幀列表(貌似H.264這種比較新的標準才會涉及到多參考幀)
int interlaced_frame:是否是隔行掃描
uint8_t motion_subsample_log2:一個宏塊中的運動矢量采樣個數,取log的

初始化
codingFrame = av_frame_alloc();

第七步

這里一塊主要做的設置我們保存區域有多大。avpicture_fill來把幀和我們新申請的內存來結合,這個函數的使用本質上是為已經分配的空間的結構體AVPicture掛上一段用于保存數據的空間,這個結構體中有一個指針數組data[4],掛在這個數組里。

代碼
avpicture_fill((AVPicture*)codingFrame, picture_buf, videoCodingContext->pix_fmt, videoCodingContext->width, videoCodingContext->height);

這里還要創建一個緩存區, AVPacket本身只是個容器,它data成員引用實際的數據緩沖區。這個緩沖區通常是由av_new_packet創建的,但也可能由 FFMPEG的API創建(如av_read_frame)。當某個AVPacket結構的數據緩沖區不再被使用時,要需要通過調用 av_free_packet釋放

代碼
av_new_packet(&pkt, picture_size);

以上的步驟都是一個編碼流程初始化,以下的步驟才是開始編碼。

第八步 編碼

這一塊主要是將攝像頭獲取的數據裝換為YUV數據。我這里通過攝像頭獲取的格式為kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,所以下面是我們對上面格式進行的yuv編碼。

/* 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++)
            {
                //                NSLog(@"這里的i是多少:%d pUV是什么:%s",i,pUV);
                *(pU++) = pUV[i<<1];
                *(pV++) = pUV[(i<<1) + 1];
            }
            pUV+=bytesrow1;
        }
        
        //Read raw YUV data
        picture_buf = yuv420_data;
        codingFrame->data[0] = picture_buf;              // Y
        codingFrame->data[1] = picture_buf+ y_size;      // U
        codingFrame->data[2] = picture_buf+ y_size*5/4;  // V

// PTS
        codingFrame->pts = framecnt;
        int got_picture = 0;
        
        // Encode
        codingFrame->width = encoder_h264_frame_width;
        codingFrame->height = encoder_h264_frame_height;
        codingFrame->format = AV_PIX_FMT_YUV420P;
        /*
         該函數用于編碼一幀視頻數據.
         該函數每個參數的含義在注釋里面已經寫的很清楚了,在這里用中文簡述一下:
         avctx:編碼器的AVCodecContext。
         avpkt:編碼輸出的AVPacket。
         frame:編碼輸入的AVFrame。
         got_packet_ptr:成功編碼一個AVPacket的時候設置為1。
         函數返回0代表編碼成功
         */
        int ret = avcodec_encode_video2(videoCodingContext, &pkt, codingFrame, &got_picture);
        if(ret < 0) {
            
            NSLog(@"編碼出錯了");
            
        }
        if (got_picture==1) {
            
            printf("編碼從這里開始Succeed to encode frame: %5d\tsize:%5d\n  data:%d", framecnt, pkt.size,pkt.buf);
            framecnt++;
            pkt.stream_index = stream->index;
           av_free_packet(&pkt);
}

以上八個步驟就是對攝像頭獲取的數據進行編碼,編成流,是可以讓我們的通過推流方式退出去的。
代碼:https://github.com/tangyi1234/TYSolutionOfCoding

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容