视频编码案例
視頻編碼案例
目錄
1. FFmpeg視頻編碼流程
1. 函數(shù)說(shuō)明
2. av_image_get_buffer_size
int av_image_get_buffer_size(enum AVPixelFormat pix_fmt, int width, int height, int align);3. av_image_alloc
4. av_image_fill_arrays
int av_image_fill_arrays(uint8_t *dst_data[4], int dst_linesize[4], const uint8_t *src, enum AVPixelFormat pix_fmt, int width, int height, int align);2. H.264碼率設(shè)置
1. 什么是視頻碼率
2. 設(shè)置視頻碼率的必要性
| 480P | 720X480 | 1800Kbps |
| 720P | 1280X720 | 3500Kbps |
| 1080P | 1920X1080 | 8500Kbps |
3. ?機(jī)設(shè)置碼率建議
| 極低碼率 | (寬X?X3)/4 | 30kbps | 60kbps | 120kbps | 250kbps | 500kbps | 1000kbps |
| 低碼率 | (寬X?X3)/2 | 60kbps | 120kbps | 250kbps | 500kbps | 1000kbps | 2000kbps |
| 中碼率 | (寬X?X3) | 120kbps | 250kbps | 500kbps | 1000kbps | 2000kbps | 4000kbps |
| ?碼率 | (寬X?X3)X2 | 250kbps | 500kbps | 1000kbps | 2000kbps | 4000kbps | 8000kbps |
| 極?碼率 | (寬X?X3)X4 | 500kbps | 1000kbps | 2000kbps | 4000kbps | 8000kbps | 16000kbps |
3. FFmpeg與H264編碼指南
鑒于x264的參數(shù)眾多,各種參數(shù)的配合復(fù)雜,為了使?者?便,x264建議如?特別需要可使?preset和tune設(shè)置。這套開發(fā)者推薦的參數(shù)較為合理,可在此基礎(chǔ)上在調(diào)整?些具體參數(shù)以符合??需要,?動(dòng)設(shè)定的參數(shù)會(huì)覆蓋preset和tune?的參數(shù)。
使? ffmpeg -h encoder=libx264 命令查詢相關(guān)?持的參數(shù)
英?地址:https://trac.ffmpeg.org/wiki/Encode/H.264。內(nèi)容有?定出?,但是可以借鑒學(xué)習(xí)。
x264是?個(gè) H.264/MPEG4 AVC 編碼器,對(duì)于普通?戶通常有兩種碼率控制模式:CRF(Constant Rate Factor)和Two pass ABR。碼率控制是?種決定為每?個(gè)視頻幀分配多少?特?cái)?shù)的?法,它將決定?件的??和質(zhì)量的分配。
如果你在編譯和安裝libx264 ??需要幫助,請(qǐng)查看ffmpeg和x264編譯指南:
http://ffmpeg.org/trac/ffmpeg/wiki/CompilationGuide
參考:h264編碼參數(shù)
1. CRF(Constant Rate Factor):
1. 選擇?個(gè)CRF值
2. 選擇?個(gè)preset和tune
1. preset
2. tune
tune是x264中重要性僅次于preset的選項(xiàng),它是視覺(jué)優(yōu)化的參數(shù),tune可以理解為視頻偏好(或者視頻類型),tune不是?個(gè)單?的參數(shù),?是由?組參數(shù)構(gòu)成-tune來(lái)改變參數(shù)設(shè)置。當(dāng)前的 tune包括:
如果你不確定使?哪個(gè)選項(xiàng)或者說(shuō)你的輸?與所有的tune皆不匹配,你可以忽略–tune 選項(xiàng)。
你可以使?-tune來(lái)查看tune列表,也可以通過(guò)x264 --fullhelp來(lái)查看tune所采?的參數(shù)配置
3. profile
另外?個(gè)可選的參數(shù)是-profile:v,它可以將你的輸出限制到?個(gè)特定的 H.264 profile。?些?常?的或者要被淘汰的設(shè)備僅?持有限的選項(xiàng),?如只?持baseline或者main。
所有的profile 包括:
想要說(shuō)明H.264 high profile與H.264 main profile的區(qū)別就要講到H.264的技術(shù)發(fā)展了。JVT于2003年完成H.264基本部分標(biāo)準(zhǔn)制定?作,包含baseline profile、extended profile和main profile,分別包括不同的編碼?具。之后JVT?完成了H.264 FRExt(即:Fidelity Range Extensions)擴(kuò)展部分(Amendment)的制定?作,包括high profile(HP)、high 10 profile(Hi10P)、high 4:2:2 profile(Hi422P)、high 4:4:4 profile(Hi444P)4個(gè)profile。
H.264 baseline profile、extended profile和main profile都是針對(duì)8位樣本數(shù)據(jù)、4:2:0格式的視頻序列,FRExt將其擴(kuò)展到8~12位樣本數(shù)據(jù),視頻格式可以為4:2:0、4:2:2、4:4:4,設(shè)?了highprofile(HP)、high 10 profile(Hi10P)、high 4:2:2 profile(Hi422P)、high 4:4:4 profile(Hi444P) 4個(gè)profile,這4個(gè)profile都以main profile為基礎(chǔ)。
在相同配置情況下,High profile(HP)可以?Main profile(MP)節(jié)省10%的碼流量,?MPEG-2MP節(jié)省60%的碼流量,具有更好的編碼性能。根據(jù)應(yīng)?領(lǐng)域的不同:
擴(kuò)展閱讀:H264編碼系列之profile & level控制
如果你想讓你的視頻最?化的和?標(biāo)播放設(shè)備兼容(?如?版本的的ios或者所有的android 設(shè)備),那么你可以這做:
4. 問(wèn)題與解答:
不能,但它可以更加精確地控制?標(biāo)?件??。
與 veryslow相?,它以極?的編碼時(shí)間為代價(jià)換取了?概1%的視頻質(zhì)量提升,這是?種收益遞減準(zhǔn)則,veryslow 與 slower相?提升了3%;slower 與 slow相?提升了5%;slow 與 medium相?提升了5%~10%。
這是由于rgb->yuv的轉(zhuǎn)換,如果你轉(zhuǎn)換到y(tǒng)uv444,它依然是?損的。
不,x264沒(méi)有使?(?少現(xiàn)在沒(méi)有),有?些私有編碼器使?了GPU加快了編碼速度,但這并不意味著它們經(jīng)過(guò)良好的優(yōu)化。也有可能還不如x264,或許速度更慢。總的來(lái)說(shuō),ffmpeg到?前為?還不?持GPU。翻譯注釋:x264在2013版中已經(jīng)開始?持基于opencl的顯卡加速,?于幀類型的判定。
你需要使?-pix_fmt yuv420p來(lái)是你的輸出?持QT 播放器。這是因?yàn)閷?duì)于H.264視頻剪輯蘋果的Quicktime只?持 YUV420顏?空間。否則ffmpeg會(huì)根據(jù)你的視頻源輸出與Quick time 不兼容的視頻格式或者不是基于ffmpeg的視頻。
4. X264參數(shù)之zerolatency的分析
5. YUV編碼成H.264案例
/** * @projectName 08-02-encode_video * @brief 視頻編碼,從本地讀取YUV數(shù)據(jù)進(jìn)行H264編碼 */ #include <stdio.h> #include <stdlib.h> #include <string.h>#include <libavcodec/avcodec.h> #include <libavutil/time.h> #include <libavutil/opt.h> #include <libavutil/imgutils.h>int64_t get_time() {return av_gettime_relative() / 1000; // 換算成毫秒 }static int encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,FILE *outfile) {int ret;/* send the frame to the encoder */if (frame)printf("Send frame %3"PRId64"\n", frame->pts);/* 通過(guò)查閱代碼,使用x264進(jìn)行編碼時(shí),具體緩存幀是在x264源碼進(jìn)行,* 不會(huì)增加avframe對(duì)應(yīng)buffer的reference*/ret = avcodec_send_frame(enc_ctx, frame);if (ret < 0) {fprintf(stderr, "Error sending a frame for encoding\n");return -1;}while (ret >= 0) {ret = avcodec_receive_packet(enc_ctx, pkt);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return 0;} else if (ret < 0) {fprintf(stderr, "Error encoding audio frame\n");return -1;}if (pkt->flags & AV_PKT_FLAG_KEY)printf("Write packet flags:%d pts:%3"PRId64" dts:%3"PRId64" (size:%5d)\n",pkt->flags, pkt->pts, pkt->dts, pkt->size);if (!pkt->flags)printf("Write packet flags:%d pts:%3"PRId64" dts:%3"PRId64" (size:%5d)\n",pkt->flags, pkt->pts, pkt->dts, pkt->size);fwrite(pkt->data, 1, pkt->size, outfile);}return 0; }/*** @brief 提取測(cè)試文件:ffmpeg -i test_1280x720.flv -t 5 -r 25 -pix_fmt yuv420p yuv420p_1280x720.yuv* 參數(shù)輸入: yuv420p_1280x720.yuv yuv420p_1280x720.h264 libx264* @param argc* @param argv* @return*/ int main(int argc, char **argv) {char *in_yuv_file = NULL;char *out_h264_file = NULL;FILE *infile = NULL;FILE *outfile = NULL;const char *codec_name = NULL;const AVCodec *codec = NULL;AVCodecContext *codec_ctx = NULL;AVFrame *frame = NULL;AVPacket *pkt = NULL;int ret = 0;if (argc < 4) {fprintf(stderr, "Usage: %s <input_file out_file codec_name >, argc:%d\n",argv[0], argc);return 0;}in_yuv_file = argv[1]; // 輸入YUV文件out_h264_file = argv[2];codec_name = argv[3];/* 查找指定的編碼器 */codec = avcodec_find_encoder_by_name(codec_name);if (!codec) {fprintf(stderr, "Codec '%s' not found\n", codec_name);exit(1);}codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {fprintf(stderr, "Could not allocate video codec context\n");exit(1);}/* 設(shè)置分辨率*/codec_ctx->width = 1280;codec_ctx->height = 720;/* 設(shè)置time base */codec_ctx->time_base = (AVRational) {1, 25};codec_ctx->framerate = (AVRational) {25, 1};/* 設(shè)置I幀間隔* 如果frame->pict_type設(shè)置為AV_PICTURE_TYPE_I, 則忽略gop_size的設(shè)置,一直當(dāng)做I幀進(jìn)行編碼*/codec_ctx->gop_size = 25; // I幀間隔codec_ctx->max_b_frames = 2; // 如果不想包含B幀則設(shè)置為0codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;//if (codec->id == AV_CODEC_ID_H264) {// 相關(guān)的參數(shù)可以參考libx264.c的 AVOption options// ultrafast all encode time:2270ms// medium all encode time:5815ms// veryslow all encode time:19836msret = av_opt_set(codec_ctx->priv_data, "preset", "medium", 0);if (ret != 0) {printf("av_opt_set preset failed\n");}ret = av_opt_set(codec_ctx->priv_data, "profile", "main", 0); // 默認(rèn)是highif (ret != 0) {printf("av_opt_set profile failed\n");}ret = av_opt_set(codec_ctx->priv_data, "tune", "zerolatency", 0); // 直播是才使用該設(shè)置 // ret = av_opt_set(codec_ctx->priv_data, "tune","film",0); // 畫質(zhì)filmif (ret != 0) {printf("av_opt_set tune failed\n");}}/** 設(shè)置編碼器參數(shù)*//* 設(shè)置bitrate */codec_ctx->bit_rate = 3000000; // codec_ctx->rc_max_rate = 3000000; // codec_ctx->rc_min_rate = 3000000; // codec_ctx->rc_buffer_size = 2000000; // codec_ctx->thread_count = 4; // 開了多線程后也會(huì)導(dǎo)致幀輸出延遲, 需要緩存thread_count幀后再編程。 // codec_ctx->thread_type = FF_THREAD_FRAME; // 并 設(shè)置為FF_THREAD_FRAME/* 對(duì)于H264 AV_CODEC_FLAG_GLOBAL_HEADER 設(shè)置則只包含I幀,此時(shí)sps pps需要從codec_ctx->extradata讀取* 不設(shè)置則每個(gè)I幀都帶 sps pps sei*/ // codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; // 存本地文件時(shí)不要去設(shè)置/* 將codec_ctx和codec進(jìn)行綁定 */ret = avcodec_open2(codec_ctx, codec, NULL);if (ret < 0) {fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret));exit(1);}printf("thread_count: %d, thread_type:%d\n", codec_ctx->thread_count, codec_ctx->thread_type);// 打開輸入和輸出文件infile = fopen(in_yuv_file, "rb");if (!infile) {fprintf(stderr, "Could not open %s\n", in_yuv_file);exit(1);}outfile = fopen(out_h264_file, "wb");if (!outfile) {fprintf(stderr, "Could not open %s\n", out_h264_file);exit(1);}// 分配pkt和framepkt = av_packet_alloc();if (!pkt) {fprintf(stderr, "Could not allocate video frame\n");exit(1);}frame = av_frame_alloc();if (!frame) {fprintf(stderr, "Could not allocate video frame\n");exit(1);}// 為frame分配bufferframe->format = codec_ctx->pix_fmt;frame->width = codec_ctx->width;frame->height = codec_ctx->height;ret = av_frame_get_buffer(frame, 0);if (ret < 0) {fprintf(stderr, "Could not allocate the video frame data\n");exit(1);}// 計(jì)算出每一幀的數(shù)據(jù) 像素格式 * 寬 * 高// 1382400int frame_bytes = av_image_get_buffer_size(frame->format, frame->width,frame->height, 1);printf("frame_bytes %d\n", frame_bytes);uint8_t *yuv_buf = (uint8_t *) malloc(frame_bytes);if (!yuv_buf) {printf("yuv_buf malloc failed\n");return 1;}int64_t begin_time = get_time();int64_t end_time = begin_time;int64_t all_begin_time = get_time();int64_t all_end_time = all_begin_time;int64_t pts = 0;printf("start enode\n");for (;;) {memset(yuv_buf, 0, frame_bytes);size_t read_bytes = fread(yuv_buf, 1, frame_bytes, infile);if (read_bytes <= 0) {printf("read file finish\n");break;}/* 確保該frame可寫, 如果編碼器內(nèi)部保持了內(nèi)存參考計(jì)數(shù),則需要重新拷貝一個(gè)備份目的是新寫入的數(shù)據(jù)和編碼器保存的數(shù)據(jù)不能產(chǎn)生沖突*/int frame_is_writable = 1;if (av_frame_is_writable(frame) == 0) { // 這里只是用來(lái)測(cè)試printf("the frame can't write, buf:%p\n", frame->buf[0]);if (frame->buf && frame->buf[0]) // 打印referenc-counted,必須保證傳入的是有效指針printf("ref_count1(frame) = %d\n", av_buffer_get_ref_count(frame->buf[0]));frame_is_writable = 0;}ret = av_frame_make_writable(frame);if (frame_is_writable == 0) { // 這里只是用來(lái)測(cè)試printf("av_frame_make_writable, buf:%p\n", frame->buf[0]);if (frame->buf && frame->buf[0]) // 打印referenc-counted,必須保證傳入的是有效指針printf("ref_count2(frame) = %d\n", av_buffer_get_ref_count(frame->buf[0]));}if (ret != 0) {printf("av_frame_make_writable failed, ret = %d\n", ret);break;}int need_size = av_image_fill_arrays(frame->data, frame->linesize, yuv_buf,frame->format,frame->width, frame->height, 1);if (need_size != frame_bytes) {printf("av_image_fill_arrays failed, need_size:%d, frame_bytes:%d\n",need_size, frame_bytes);break;}pts += 40;// 設(shè)置ptsframe->pts = pts; // 使用采樣率作為pts的單位,具體換算成秒 pts*1/采樣率begin_time = get_time();ret = encode(codec_ctx, frame, pkt, outfile);end_time = get_time();printf("encode time:%lldms\n", end_time - begin_time);if (ret < 0) {printf("encode failed\n");break;}}/* 沖刷編碼器 */encode(codec_ctx, NULL, pkt, outfile);all_end_time = get_time();printf("all encode time:%lldms\n", all_end_time - all_begin_time);// 關(guān)閉文件fclose(infile);fclose(outfile);// 釋放內(nèi)存if (yuv_buf) {free(yuv_buf);}av_frame_free(&frame);av_packet_free(&pkt);avcodec_free_context(&codec_ctx);printf("main finish, please enter Enter and exit\n");getchar();return 0; }總結(jié)
- 上一篇: 音频编码案例
- 下一篇: H264和AAC合成FLV案例