1. 程式人生 > >ios ffmpeg 實時視訊壓縮(主要是H264)

ios ffmpeg 實時視訊壓縮(主要是H264)

在xcode上實現 iphone 實時傳輸當前畫面的功能,本檔案針對 h264 編碼,如果需要其他編碼 ,可以把video_encode_frame_init()裡面的 編碼id替換掉

1、 ffmpeg 視訊流檔案

使用之前編譯好ffmpeg 庫,並且載入了x264的庫,然後將下面程式碼拷貝到一個空白的原始檔中,它再次封裝了視訊流處理的四個流程。

一、 註冊編碼器

void video_encode_register();

註冊所有編碼(當然前提是你的ffmpeg庫載入了所有的編碼器),放在程式啟動時執行,只需執行一次。

二、初始化
void video_encode_init();

初始化引數。

三、配置引數

void video_encode_frame_init(int gop_size,int bitrate,int frames_per_second_,int width,int height);

配置編碼引數。需要在抓屏開始之前設定

關鍵引數:

bit_rate:位元率,即每秒種播放視訊的位元數。

keyint_min:最小關鍵幀(I-frame)間隔

gop_size:關鍵幀間隔,也是最大間隔

max_b_frames:最大B-frame個數,與gop_size配合使用。等於0表示無B-frame

關鍵幀即key frame,也就是處於關鍵地位的。比如關鍵幀間隔是5,第1幀是關鍵幀,則第6幀也是關鍵幀,那麼第1、6幀如果單獨取出來儲存就是一張完整的圖片,而從第2~5幀只是儲存了與第1幀的差別,這樣減小了幀總體資料的大小。一般來說 關鍵幀越大,最終視訊的資料越小。但太大了很可能導致中間出現很多失真的畫面。

pix_fmt =AV_PIX_FMT_YUV420P; 幀格式,這裡只能是這個,因為h264格式只能出來 yuv420p 的圖片,所以如果RGBA的圖片,需要進行轉換。

width,height:幀的長寬。

time_base:即每秒播放多少幀。

四、進行編碼

<pre name="code" class="cpp">int  video_encode_process(AVPacket  *pkt,const uint8_t* imgbuf,int cw,int ch);

每隔一段時間抓屏一次,並將獲取的圖片資料儲存,應該是bmp 點陣圖,其他格式圖片存在檔案頭,向量圖資料儲存之後不是視訊

這時候cw,ch指的是圖片實際的長寬,而不是幀的長寬,但是最終會轉化成幀的長寬。不建議圖片實際長寬與幀長寬不一致,因為那樣圖片會失真。建議如果圖片長寬與實際長寬不一致,可以先轉換成長寬一致的圖片。比如用Qt裡的 scaleTo()等方法,而不是 用ffmpeg的方法。

例如:

AVPacket pkt;
char *imgbuf=captureScreen();//抓屏函式,返回的是image 的首地址,這裡不存在這個API,需要自己根據平臺自己完成
if(video_encode_process(&pkt,imgbuf,320,480)>0)
{
   save_file(pkt.data,pkt.len);//儲存資料到一個檔案中
   av_pkt_free(&pkt);//釋放pkt,因為data被申請了記憶體
}

五、編碼結束處理

video_encode_end(AVPacket *pkt);
//
//  h264video.c
//  AppLinkTester2
//
//  Created by on 15-5-20.
//
//

#include <stdio.h>
#include "h264video.h"


AVFrame *pframe;
AVCodecContext *contex;
FILE  *fhandle;
int frames_per_second;

void video_encode_register()
{
    av_register_all();
    avcodec_register_all();

    contex=NULL;
    pframe=NULL;
}

//31ms
void rgb32_to_yuv420(const uint8_t *rgb, AVFrame* frame,int cw,int ch)
{
    printf("rgb32_to_yuv420 start:\n");
    const uint8_t *rgb_src[3]= {rgb, NULL, NULL};
    int rgb_stride[3]={4*cw, 0, 0};
    
    struct SwsContext *yuvContext=sws_getContext(cw,ch,AV_PIX_FMT_RGBA,frame->width,frame->height,AV_PIX_FMT_YUV420P,SWS_BILINEAR, NULL, NULL, NULL);
    
    int oh=sws_scale(yuvContext,rgb_src,rgb_stride, 0, ch, frame->data, frame->linesize);
    if (oh!=frame->height) {
        printf("rgb32 to yuv420 error\n");
    }
    sws_freeContext(yuvContext);
    printf("rgb32_to_yuv420 end:\n");
}

//130ms  1
void video_encode_init()
{
    printf("video_encode_init start:\n");

        if (contex){
            avcodec_close(contex);
            av_free(contex);
        }
    if (pframe) {
        av_free(pframe);
    }
    contex=NULL;
    pframe=NULL;
    
    printf("video_encode_init end:\n");
}


int video_frame_get_width()
{
    return pframe->width;
}

int video_frame_get_height()
{
    return pframe->height;
}

//gop_size 關鍵幀間隔   biterate 位元率  單位:kb/s
void video_encode_frame_init(int gop_size,int bitrate,int frames_per_second_,int width,int height)
{
    AVCodec *codec=NULL;
    frames_per_second=frames_per_second_;
    
    codec=avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        return;
    }
    
    contex = avcodec_alloc_context3(codec);
    if (!contex) {
        fprintf(stderr, "Could not allocate video codec context\n");
        return;
    }
    
    contex->bit_rate = bitrate*1000;
    contex->keyint_min=0;
//    contex->scenechange_threshold
    contex->time_base=(AVRational){1,frames_per_second};
    contex->gop_size =gop_size;
    contex->max_b_frames = 0;
    contex->delay=0;
    contex->pix_fmt =AV_PIX_FMT_YUV420P;//
    
    av_opt_set(contex->priv_data, "preset", "superfast", 0);//superfast
    av_opt_set(contex->priv_data,"tune","zerolatency",0);
    
    contex->width = ((int)(width/2))*2;
    contex->height = ((int)(height/2))*2;
        
    if(avcodec_open2(contex, codec, NULL)){
        fprintf(stderr, "Could not open codec\n");
    }
    
    pframe = av_frame_alloc();
    if (!pframe) {
        fprintf(stderr, "Could not allocate video frame\n");
        return;
    }
//    pframe->pict_type=AV_PICTURE_TYPE_NONE;
//    pframe->key_frame=0;
    pframe->format=contex->pix_fmt;//
    pframe->width=contex->width;
    pframe->height=contex->height;
    
    int ret=av_image_alloc(pframe->data, pframe->linesize, contex->width, contex->height, contex->pix_fmt, 32);
    if(ret < 0){
        fprintf(stderr, "Couldn't alloc raw picture buffer\n");
        return;
    }
    pframe->pts=0;
}



int  video_encode_end(AVPacket *pkt)
{
//    uint8_t endcode[]={0,0,1,0xb7};
    //    FILE *f=fopen("/Users/patrick/git/png_to_video_h264/test01/test03.264", "wb+");

    av_init_packet(pkt);
    pkt->data = NULL;    // packet data will be allocated by the encoder
    pkt->size = 0;
    
    /* get the delayed frames */
    
    for (int got_output = 1; got_output; ) {
        fflush(stdout);
        
        int ret = avcodec_encode_video2(contex, pkt, NULL, &got_output);
        if (ret < 0) {
            fprintf(stderr, "Error encoding frame\n");
            return ret;
        }
        
        if (got_output) {
            pframe->pts++;
            printf("Write frame (size=%5d)\n", pkt->size);
//            fwrite(pkt.data, 1, pkt.size, fhandle);
            //            [self sendPacket:pkt.data  len:pkt.size];
//            av_free_packet(&pkt);
            
        }
    }
    
    
//    fwrite(endcode, 1, sizeof(endcode), fhandle);
    avcodec_close(contex);
    //
    //    [self sendPacket:endcode  len:sizeof(endcode)];
    printf("編碼完成,共%d張%d幀\n",(int)pframe->pts,(int)pframe->pts);
    printf("視訊時間應該是%f秒\n",(1.0*pframe->pts/frames_per_second));
    return pkt->size;
    
}

//6ms~ 300ms
int  video_encode_process(AVPacket  *pkt,const uint8_t* imgbuf,int cw,int ch)
{
    printf("video_encode_process start:\n");
    
    av_init_packet(pkt);
    pkt->data = NULL;    // packet data will be allocated by the encoder
    pkt->size = 0;
    
    int got_output=-1;
    if(contex == NULL||pframe == NULL){
        printf("encode contex is null\n");
        return -1;
    }
    if(contex->codec_id==AV_CODEC_ID_NONE){
        printf("contex is invalid");
        return -1;
    }
    
    rgb32_to_yuv420(imgbuf,pframe,cw,ch);
    
    fflush(stdout);
    
    /* encode the image */
    int ret = avcodec_encode_video2(contex, pkt, pframe, &got_output);
    
    if (ret < 0) {
        fprintf(stderr, "Error encoding frame\n");
        return ret;
    }
    
    if (got_output) {
        fflush(stdout);
        pframe->pts++;
        printf("Write frame %3d (size=%5d)\n", (int)pframe->pts, pkt->size);
        if (pkt->flags&AV_PKT_FLAG_KEY) {
            printf("key frame packet %d,pts=%ld",pframe->pts,pkt->pts);
        }
    }
    
    //free(&pFrame->data[0]);
    printf("video_encode_process end:\n");
    return pkt->size;
}