iOS視頻軟編碼

  • FFmpeg編碼兩個(gè)重要的類
    • AVFormat
      保存的是解碼后和原始的音視頻信息
    • AVPacket
      解碼完成的數(shù)據(jù)及附加信息(解碼時(shí)間戳、顯示時(shí)間戳、時(shí)長等)
/*
 *  設(shè)置X264
 */
- (int)setX264ResourceWithVideoWidth:(int)width height:(int)height bitrate:(int)bitrate
{
    // 1.默認(rèn)從第0幀開始(記錄當(dāng)前的幀數(shù))
    framecnt = 0;

    // 2.記錄傳入的寬度&高度
    encoder_h264_frame_width = width;
    encoder_h264_frame_height = height;

    // 3.注冊FFmpeg所有編解碼器(無論編碼還是解碼都需要該步驟)
    av_register_all();

    // 4.初始化AVFormatContext: 用作之后寫入視頻幀并編碼成 h264,貫穿整個(gè)工程當(dāng)中(釋放資源時(shí)需要銷毀)
    pFormatCtx = avformat_alloc_context();

    // 5.設(shè)置輸出文件的路徑
    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");
        return -1;
    }

    // 7.創(chuàng)建新的輸出流, 用于寫入文件
    video_st = avformat_new_stream(pFormatCtx, 0);

    // 8.設(shè)置 20 幀每秒 ,也就是 fps 為 20
    video_st->time_base.num = 1;
    video_st->time_base.den = 25;

    if (video_st==NULL){
        return -1;
    }

    // 9.pCodecCtx 用戶存儲編碼所需的參數(shù)格式等等
    // 9.1.從媒體流中獲取到編碼結(jié)構(gòu)體悔叽,他們是一一對應(yīng)的關(guān)系,一個(gè) AVStream 對應(yīng)一個(gè)  AVCodecContext
    pCodecCtx = video_st->codec;

    // 9.2.設(shè)置編碼器的編碼格式(是一個(gè)id)爵嗅,每一個(gè)編碼器都對應(yīng)著自己的 id娇澎,例如 h264 的編碼 id 就是 AV_CODEC_ID_H264
    pCodecCtx->codec_id = fmt->video_codec;

    // 9.3.設(shè)置編碼類型為 視頻編碼
    pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;

    // 9.4.設(shè)置像素格式為 yuv 格式
    pCodecCtx->pix_fmt = PIX_FMT_YUV420P;

    // 9.5.設(shè)置視頻的寬高
    pCodecCtx->width = encoder_h264_frame_width;
    pCodecCtx->height = encoder_h264_frame_height;

    // 9.6.設(shè)置幀率
    pCodecCtx->time_base.num = 1;
    pCodecCtx->time_base.den = 15;

    // 9.7.設(shè)置碼率(比特率)
    pCodecCtx->bit_rate = bitrate;

    // 9.8.視頻質(zhì)量度量標(biāo)準(zhǔn)(常見qmin=10, qmax=51)
    pCodecCtx->qmin = 10;
    pCodecCtx->qmax = 51;

    // 9.9.設(shè)置圖像組層的大小(GOP-->兩個(gè)I幀之間的間隔)
    pCodecCtx->gop_size = 250;

    // 9.10.設(shè)置 B 幀最大的數(shù)量,B幀為視頻圖片空間的前后預(yù)測幀睹晒, B 幀相對于 I趟庄、P 幀來說,壓縮率比較大册招,也就是說相同碼率的情況下岔激,
    // 越多 B 幀的視頻,越清晰是掰,現(xiàn)在很多打視頻網(wǎng)站的高清視頻虑鼎,就是采用多編碼 B 幀去提高清晰度,
    // 但同時(shí)對于編解碼的復(fù)雜度比較高键痛,比較消耗性能與時(shí)間
    pCodecCtx->max_b_frames = 5;

    // 10.可選設(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);
    }

    // 11.輸出打印信息杉允,內(nèi)部是通過printf函數(shù)輸出(不需要輸出可以注釋掉該局)
    av_dump_format(pFormatCtx, 0, out_file, 1);

    // 12.通過 codec_id 找到對應(yīng)的編碼器
    pCodec = avcodec_find_encoder(pCodecCtx->codec_id);
    if (!pCodec) {
        printf("Can not find encoder! \n");
        return -1;
    }

    // 13.打開編碼器邑贴,并設(shè)置參數(shù) param
    if (avcodec_open2(pCodecCtx, pCodec,&param) < 0) {
        printf("Failed to open encoder! \n");
        return -1;
    }

    // 13.初始化原始數(shù)據(jù)對象: AVFrame
    pFrame = av_frame_alloc();

    // 14.通過像素格式(這里為 YUV)獲取圖片的真實(shí)大小,例如將 480 * 720 轉(zhuǎn)換成 int 類型
    avpicture_fill((AVPicture *)pFrame, picture_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height);

    // 15.h264 封裝格式的文件頭部叔磷,基本上每種編碼都有著自己的格式的頭部拢驾,想看具體實(shí)現(xiàn)的同學(xué)可以看看 h264 的具體實(shí)現(xiàn)
    avformat_write_header(pFormatCtx, NULL);

    // 16.創(chuàng)建編碼后的數(shù)據(jù) AVPacket 結(jié)構(gòu)體來存儲 AVFrame 編碼后生成的數(shù)據(jù)
    av_new_packet(&pkt, picture_size);

    // 17.設(shè)置 yuv 數(shù)據(jù)中 y 圖的寬高
    y_size = pCodecCtx->width * pCodecCtx->height;

    return 0;
}
  • 編碼每一幀數(shù)據(jù)
/*
 * 將CMSampleBufferRef格式的數(shù)據(jù)編碼成h264并寫入文件
 *
 */
- (void)encoderToH264:(CMSampleBufferRef)sampleBuffer
{
    // 1.通過CMSampleBufferRef對象獲取CVPixelBufferRef對象
    CVPixelBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);

    // 2.鎖定imageBuffer內(nèi)存地址開始進(jìn)行編碼
    if (CVPixelBufferLockBaseAddress(imageBuffer, 0) == kCVReturnSuccess) {
        // 3.從CVPixelBufferRef讀取YUV的值
        // NV12和NV21屬于YUV格式,是一種two-plane模式改基,即Y和UV分為兩個(gè)Plane繁疤,但是UV(CbCr)為交錯(cuò)存儲,而不是分為三個(gè)plane
        // 3.1.獲取Y分量的地址
        UInt8 *bufferPtr = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer,0);
        // 3.2.獲取UV分量的地址
        UInt8 *bufferPtr1 = (UInt8 *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer,1);

        // 3.3.根據(jù)像素獲取圖片的真實(shí)寬度&高度
        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);

        /* convert NV12 data to YUV420*/
        // 3.4.將NV12數(shù)據(jù)轉(zhuǎn)成YUV420數(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;
        int got_picture = 0;

        // 4.設(shè)置寬度高度以及YUV各式
        pFrame->width = encoder_h264_frame_width;
        pFrame->height = encoder_h264_frame_height;
        pFrame->format = PIX_FMT_YUV420P;

        // 5.對編碼前的原始數(shù)據(jù)(AVFormat)利用編碼器進(jìn)行編碼秕狰,將 pFrame 編碼后的數(shù)據(jù)傳入pkt 中
        int ret = avcodec_encode_video2(pCodecCtx, &pkt, pFrame, &got_picture);
        if(ret < 0) {
            printf("Failed to encode! \n");

        }

        // 6.編碼成功后寫入 AVPacket 到 輸入輸出數(shù)據(jù)操作著 pFormatCtx 中稠腊,當(dāng)然,記得釋放內(nèi)存
        if (got_picture==1) {
            framecnt++;
            pkt.stream_index = video_st->index;
            ret = av_write_frame(pFormatCtx, &pkt);
            av_free_packet(&pkt);
        }

        // 7.釋放yuv數(shù)據(jù)
        free(yuv420_data);
    }

    CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
}
  • 釋放資源
/*
 * 釋放資源
 */
- (void)freeX264Resource
{
    // 1.釋放AVFormatContext
    int ret = flush_encoder(pFormatCtx,0);
    if (ret < 0) {
        printf("Flushing encoder failed\n");
    }

    // 2.將還未輸出的AVPacket輸出出來
    av_write_trailer(pFormatCtx);

    // 3.關(guān)閉資源
    if (video_st){
        avcodec_close(video_st->codec);
        av_free(pFrame);
    }
    avio_close(pFormatCtx->pb);
    avformat_free_context(pFormatCtx);
}

int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index)
{
    int ret;
    int got_frame;
    AVPacket enc_pkt;
    if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &
          CODEC_CAP_DELAY))
        return 0;

    while (1) {
        enc_pkt.data = NULL;
        enc_pkt.size = 0;
        av_init_packet(&enc_pkt);
        ret = avcodec_encode_video2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,
                                     NULL, &got_frame);
        av_frame_free(NULL);
        if (ret < 0)
            break;
        if (!got_frame){
            ret=0;
            break;
        }
        ret = av_write_frame(fmt_ctx, &enc_pkt);
        if (ret < 0)
            break;
    }
    return ret;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸣哀,一起剝皮案震驚了整個(gè)濱河市架忌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌诺舔,老刑警劉巖鳖昌,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異低飒,居然都是意外死亡许昨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進(jìn)店門褥赊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糕档,“玉大人,你說我怎么就攤上這事拌喉∷倌牵” “怎么了?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵尿背,是天一觀的道長端仰。 經(jīng)常有香客問我,道長田藐,這世上最難降的妖魔是什么荔烧? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮汽久,結(jié)果婚禮上鹤竭,老公的妹妹穿的比我還像新娘。我一直安慰自己景醇,他們只是感情好臀稚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著三痰,像睡著了一般吧寺。 火紅的嫁衣襯著肌膚如雪窜管。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天稚机,我揣著相機(jī)與錄音微峰,去河邊找鬼。 笑死抒钱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的颜凯。 我是一名探鬼主播谋币,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼症概!你這毒婦竟也來了蕾额?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤彼城,失蹤者是張志新(化名)和其女友劉穎诅蝶,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體募壕,經(jīng)...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡调炬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了舱馅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缰泡。...
    茶點(diǎn)故事閱讀 39,764評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖代嗤,靈堂內(nèi)的尸體忽然破棺而出棘钞,到底是詐尸還是另有隱情,我是刑警寧澤干毅,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布宜猜,位于F島的核電站,受9級特大地震影響硝逢,放射性物質(zhì)發(fā)生泄漏姨拥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一趴捅、第九天 我趴在偏房一處隱蔽的房頂上張望垫毙。 院中可真熱鬧,春花似錦拱绑、人聲如沸综芥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽膀藐。三九已至屠阻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間额各,已是汗流浹背国觉。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留虾啦,地道東北人麻诀。 一個(gè)月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像傲醉,于是被迫代替她去往敵國和親蝇闭。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,665評論 2 354

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