iOS 編碼流程和使用方法

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

注冊編碼器
av_register_all();

第二步:

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

初始方法
pFormatConttext = avformat_alloc_context();

第三步:

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

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

第四步:

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

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

下面都是它設置的參數(shù):
    //編碼器的ID號,這里我們自行指定為264編碼器,實際上也可以根據(jù)AVStream里的codecID 參數(shù)賦值
    videoCodingContext->codec_id = AV_CODEC_ID_H264;
    //編碼器編碼的數(shù)據(jù)類型
    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;
    //幀率的基本單位,我們用分數(shù)來表示,
    //用分數(shù)來表示的原因是,有很多視頻的幀率是帶小數(shù)的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;
    //最大和最小量化系數(shù)
    videoCodingContext->qmin = 10;
    videoCodingContext->qmax = 51;
    //兩個非B幀之間允許出現(xiàn)多少個B幀數(shù)
    //設置0表示不使用B幀
    //b 幀越多,圖片越小
    videoCodingContext->max_b_frames = 3;
    
    //新添加
    videoCodingContext->dct_algo = 0;
    videoCodingContext->me_pre_cmp = 2;//運動場景預判功能的力度,數(shù)值越大編碼時間越長
    videoCodingContext->qmin = 25;//最大和最小量化系數(shù)
    videoCodingContext->qmax = 40;
    videoCodingContext->max_qdiff = 3;
    videoCodingContext->gop_size = 250; //關(guān)鍵幀的最大間隔幀數(shù)/
    videoCodingContext->keyint_min = 10; //關(guān)鍵幀的最小間隔幀數(shù),取值范圍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是存儲編解碼器信息的結(jié)構(gòu)體,其主要包含參數(shù)
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:支持的聲道數(shù)(僅音頻)
int priv_data_size:私有數(shù)據(jù)的大小
其初始化也是根據(jù)其邏輯來實現(xiàn)的,這里會根據(jù)videoCodingContext來進行初始化

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

第六步

AVFrame, AVFrame是包含碼流參數(shù)較多的結(jié)構(gòu)體,AVFrame就是用來保存幀數(shù)據(jù)的。應為我們編碼出的數(shù)據(jù)是以流的形式存在的所以我們有個流體。如果要做圖像識別這里能起到幫助的作用。
uint8_t *data[AV_NUM_DATA_POINTERS]:解碼后原始數(shù)據(jù)(對視頻來說是YUV,RGB,對音頻來說是PCM)
int linesize[AV_NUM_DATA_POINTERS]:data中“一行”數(shù)據(jù)的大小。注意:未必等于圖像的寬,一般大于圖像的寬。
int width, height:視頻幀寬和高(1920x1080,1280x720...)
int nb_samples:音頻的一個AVFrame中可能包含多個音頻幀,在此標記包含了幾個
int format:解碼后原始數(shù)據(jù)類型(YUV420,YUV422,RGB24...)
int key_frame:是否是關(guān)鍵幀
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系數(shù),這個沒有提取過
int8_t *ref_index[2]:運動估計參考幀列表(貌似H.264這種比較新的標準才會涉及到多參考幀)
int interlaced_frame:是否是隔行掃描
uint8_t motion_subsample_log2:一個宏塊中的運動矢量采樣個數(shù),取log的

初始化
codingFrame = av_frame_alloc();

第七步

這里一塊主要做的設置我們保存區(qū)域有多大。avpicture_fill來把幀和我們新申請的內(nèi)存來結(jié)合,這個函數(shù)的使用本質(zhì)上是為已經(jīng)分配的空間的結(jié)構(gòu)體AVPicture掛上一段用于保存數(shù)據(jù)的空間,這個結(jié)構(gòu)體中有一個指針數(shù)組data[4],掛在這個數(shù)組里。

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

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

代碼
av_new_packet(&pkt, picture_size);

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

第八步 編碼

這一塊主要是將攝像頭獲取的數(shù)據(jù)裝換為YUV數(shù)據(jù)。我這里通過攝像頭獲取的格式為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;
        /*
         該函數(shù)用于編碼一幀視頻數(shù)據(jù).
         該函數(shù)每個參數(shù)的含義在注釋里面已經(jīng)寫的很清楚了,在這里用中文簡述一下:
         avctx:編碼器的AVCodecContext。
         avpkt:編碼輸出的AVPacket。
         frame:編碼輸入的AVFrame。
         got_packet_ptr:成功編碼一個AVPacket的時候設置為1。
         函數(shù)返回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);
}

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容