這里只會涉及到編碼的代碼,有些東西可能是和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, ¶m) < 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