使用ffmpeg 的liavformat 封裝,使用libavcodec encodec,實現個編碼器,封裝yuv 文件爲flv/mp4等格式文件。數據結構
視頻編碼的過程是解碼的逆過程,編碼的流程,從數據結構上看就是AVFrame-> AVPacket->AVCodecContext->AVFormatContext。 生成AVPacket 爲封裝,生成AVFrame 爲encodec。其中,AVFormatContext:封裝格式上下文結構體,也是統領全局的結構體,保存了視頻文件 封裝 格式相關信息;AVCodecContext:編碼器上下文結構體,保存了視頻(音頻)編解碼相關信息;AVPacket:存儲一幀壓縮編碼數據;AVFrame:存儲一幀解碼後像素(採樣)數據。ide
編碼過程相對於解碼過程複雜點,由於解碼不須要關注各類參數,而編碼要設置各類複雜的參數,特使是時間戳的關係,這個搞不明白,就會出現幀率不對的狀況。下面是雷神總結的流程圖:編碼
他寫的代碼只有encode,並無封裝過程,本文完成了後面部分,並作了部分修改。源碼以下:.net
#include <stdio.h> #include <libavutil/opt.h> #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libavutil/imgutils.h> //test different codec #define TEST_H264 1 int main(int argc, char* argv[]) { AVCodec *pCodec; AVCodecContext *pCodecCtx= NULL; int i, ret, got_output; FILE *fp_in; FILE *fp_out; AVFrame *pFrame; AVPacket pkt; AVStream *video_st; int y_size; int framecnt=0; int framerate=25; char filename_in[] = "in_1280x720.yuv"; //輸入yuv文件, 必須註明分辨率 enum AVCodecID codec_id = AV_CODEC_ID_H264; // 輸出文件名 char * h264_out = "out.h264"; char * flv_out = "out.mp4"; // 輸出 format AVFormatContext *ofmt_ctx = NULL; int in_w=1280, in_h=720; //對應的分辨率 int out_w = 1280, out_h = 720; int framenum=10000; // 註冊全部編碼器 av_register_all(); // 初始化 out format avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, flv_out); if (!ofmt_ctx) { printf( "Could not create output context\n"); ret = AVERROR_UNKNOWN; return -1 ; } //Open output file if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) { ret = avio_open(&ofmt_ctx->pb, flv_out, AVIO_FLAG_WRITE);//打開輸出文件。 if (ret < 0) { printf( "Could not open output file '%s'", flv_out); return -1; } } video_st = avformat_new_stream(ofmt_ctx, 0); // 設置幀率,有的封裝格式,這個參數會被後面 avformat_write_header 覆蓋 // 輸出 codec 的參數,這裏的參數通常ffmpeg 能夠parse arg 獲得,這裏暫時寫死 pCodecCtx = video_st->codec; if (!pCodecCtx) { printf("Could not allocate video codec context\n"); return -1; } pCodecCtx->codec_id = codec_id; pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO; pCodecCtx->bit_rate = 5000; pCodecCtx->width = out_w; pCodecCtx->height = out_h; pCodecCtx->time_base.num=1; pCodecCtx->time_base.den=25; // h264重要參數,不設置會報錯 pCodecCtx->qmin = 10; pCodecCtx->qmax = 51; pCodecCtx->gop_size = 250; pCodecCtx->max_b_frames = 1; pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P; if (codec_id == AV_CODEC_ID_H264) av_opt_set(pCodecCtx->priv_data, "preset", "slow", 0); //打開 codec 編碼器 pCodec = avcodec_find_encoder(pCodecCtx->codec_id); if (!pCodec) { printf("Codec not found\n"); return -1; } if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { printf("Could not open codec\n"); return -1; } //Input raw data fp_in = fopen(filename_in, "rb"); if (!fp_in) { printf("Could not open %s\n", filename_in); return -1; } //h264 Output bitstream fp_out = fopen(h264_out, "wb"); if (!fp_out) { printf("Could not open %s\n", h264_out); return -1; } // AVDictionary* opt = NULL; // av_dict_set_int(&opt, "video_track_timescale", 25, 0); //Write file header if (avformat_write_header(ofmt_ctx, NULL) < 0) { printf( "Error occurred when opening output file\n"); return -1; } // init pFrame pFrame = av_frame_alloc(); if (!pFrame) { printf("Could not allocate video frame\n"); return -1; } pFrame->format = pCodecCtx->pix_fmt; pFrame->width = in_w; pFrame->height = in_h; ret = av_image_alloc(pFrame->data, pFrame->linesize, in_w, in_h, pCodecCtx->pix_fmt, 1); if (ret < 0) { printf("Could not allocate raw picture buffer\n"); return -1; } y_size = in_h * in_w; //Encode for (i; i< 5000; i++){ av_init_packet(&pkt); pkt.data = NULL; // packet data will be allocated by the encoder pkt.size = 0; //Read raw YUV data if (fread(pFrame->data[0],1,y_size,fp_in)<= 0|| // Y fread(pFrame->data[1],1,y_size/4,fp_in)<= 0|| // U fread(pFrame->data[2],1,y_size/4,fp_in)<= 0){ // V break; }else if(feof(fp_in)){ printf("read yuv file meet error\n"); break; } pFrame->pts = i; /* encode the pFrame */ ret = avcodec_encode_video2(pCodecCtx, &pkt, pFrame, &got_output); if (ret < 0) { printf("Error encoding frame\n"); return -1; } //因爲裸流裏原本就沒有可靠的pts和timebase等數據,pts的計算依靠設置的幀率 double calc_duration = (double)1/framerate; pkt.pts = (double)(framecnt*calc_duration) / (av_q2d(video_st->time_base)); pkt.dts = pkt.pts; pkt.duration = calc_duration / av_q2d(video_st->time_base); if (got_output) { printf("Succeed to encode frame: %5d\tsize:%5d\n",framecnt,pkt.size); framecnt++; pkt.stream_index = video_st->index; fwrite(pkt.data, 1, pkt.size, fp_out); av_interleaved_write_frame(ofmt_ctx, &pkt); //將AVPacket(存儲視頻壓縮碼流數據)寫入文件 } av_free_packet(&pkt); } av_write_trailer(ofmt_ctx); // write tailer // 釋放內存 fclose(fp_out); avcodec_close(video_st->codec); avcodec_close(pCodecCtx); av_free(pCodecCtx); av_freep(&pFrame->data[0]); av_frame_free(&pFrame); return 0; }
在寫編碼器的過程當中,碼率一直控制的不許,出現了幀率控制很差的狀況,有參考這個文章:https://blog.csdn.net/mj1523/article/details/50434977 ,可是後來發現,其實幀率控制不須要那麼作,由於是裸流,pkg 的pts 沒有設置引發的。而後加上pkg 生成部分的pts 設置,問題得以解決。code
下面大體介紹下,pts,dts 區別,爲何有這個區別,該怎麼取值等問題, timebase又是個啥玩意?orm
首先是爲何package 要有pts, dts 這兩個玩意?首先,視頻顯示是一幀一幀,可是編碼並非一幀的。 視頻分爲I P B 幀,須要在顯示B幀以前知道P幀中的信息,因此沒有B 幀的時候,pts,dts 編碼時間和顯示時間就是一致的,可是有的時候,編碼的時候P 幀須要提早。視頻
爲何取值的時候有timebase, ffmpeg 各個數據層之間的時間基準不同,不能直接相互使用,不然會出現溢出的狀況,好比mux/demux層的timebase,flv,MP4等通常是1:1000,ts通常是1:90*1000 。codec/decode層timebase,h264隨着幀率變化例如1:25 aac根據採樣率變化例如1:44100。c:Raw data 層的timebase有不少變化好比1:1000*1000 或1:1000等等。blog
最後是pts 怎麼取值的問題,相似轉碼,有穩定的輸入值參考,能夠直接使用輸入 AVStream 的pts 給輸出pkg 賦值,像本文這種狀況,是使用的裸流,就須要本身經過幀率和timebase 進行計算,計算過程就較爲簡單,查下對應公式便可。內存