本文爲做者原創,轉載請註明出處:http://www.javashuo.com/article/p-rnnbskcv-w.htmlhtml
FFmpeg編解碼處理系列筆記:
[0]. FFmpeg時間戳詳解
[1]. FFmpeg編解碼處理1-轉碼全流程簡介
[2]. FFmpeg編解碼處理2-編解碼API詳解
[3]. FFmpeg編解碼處理3-視頻編碼
[4]. FFmpeg編解碼處理4-音頻編碼git
基於 FFmpeg 4.1 版本。github
編碼使用 avcodec_send_frame() 和 avcodec_receive_packet() 兩個函數。ide
視頻編碼的步驟:
[1] 初始化打開輸出文件時構建編碼器上下文
[2] 視頻幀編碼
[2.1] 設置幀類型 "frame->pict_type=AV_PICTURE_TYPE_NONE",讓編碼器根據設定參數自行生成 I/B/P 幀類型
[2.2] 將原始幀送入編碼器,從編碼器取出編碼幀
[2.3] 更新編碼幀流索引
[2.4] 將幀中時間參數按輸出封裝格式的時間基進行轉換函數
完整源碼在 open_output_file() 函數中,下面摘出關鍵部分:佈局
// 3. 構建AVCodecContext if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO || dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) // 音頻流或視頻流 { // 3.1 查找編碼器AVCodec,本例使用與解碼器相同的編碼器 AVCodec *encoder = NULL; if ((dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) && (strcmp(v_enc_name, "copy") != 0)) { encoder = avcodec_find_encoder_by_name(v_enc_name); } else if ((dec_ctx->codec_type == AVMEDIA_TYPE_AUDIO) && (strcmp(a_enc_name, "copy") != 0)) { encoder = avcodec_find_encoder_by_name(a_enc_name); } else { encoder = avcodec_find_encoder(dec_ctx->codec_id); } if (!encoder) { av_log(NULL, AV_LOG_FATAL, "Necessary encoder not found\n"); return AVERROR_INVALIDDATA; } // 3.2 AVCodecContext初始化:分配結構體,使用AVCodec初始化AVCodecContext相應成員爲默認值 AVCodecContext *enc_ctx = avcodec_alloc_context3(encoder); if (!enc_ctx) { av_log(NULL, AV_LOG_FATAL, "Failed to allocate the encoder context\n"); return AVERROR(ENOMEM); } // 3.3 AVCodecContext初始化:配置圖像/聲音相關屬性 /* In this example, we transcode to same properties (picture size, * sample rate etc.). These properties can be changed for output * streams easily using filters */ if (dec_ctx->codec_type == AVMEDIA_TYPE_VIDEO) { enc_ctx->height = dec_ctx->height; // 圖像高 enc_ctx->width = dec_ctx->width; // 圖像寬 enc_ctx->sample_aspect_ratio = dec_ctx->sample_aspect_ratio; // 採樣寬高比:像素寬/像素高 /* take first format from list of supported formats */ if (encoder->pix_fmts) // 編碼器支持的像素格式列表 { enc_ctx->pix_fmt = encoder->pix_fmts[0]; // 編碼器採用所支持的第一種像素格式 } else { enc_ctx->pix_fmt = dec_ctx->pix_fmt; // 編碼器採用解碼器的像素格式 } /* video time_base can be set to whatever is handy and supported by encoder */ enc_ctx->time_base = av_inv_q(dec_ctx->framerate); // 時基:解碼器幀率取倒數 enc_ctx->framerate = dec_ctx->framerate; //enc_ctx->bit_rate = dec_ctx->bit_rate; /* emit one intra frame every ten frames * check frame pict_type before passing frame * to encoder, if frame->pict_type is AV_PICTURE_TYPE_I * then gop_size is ignored and the output of encoder * will always be I frame irrespective to gop_size */ //enc_ctx->gop_size = 10; //enc_ctx->max_b_frames = 1; } else { enc_ctx->sample_rate = dec_ctx->sample_rate; // 採樣率 enc_ctx->channel_layout = dec_ctx->channel_layout; // 聲道佈局 enc_ctx->channels = av_get_channel_layout_nb_channels(enc_ctx->channel_layout); // 聲道數量 /* take first format from list of supported formats */ enc_ctx->sample_fmt = encoder->sample_fmts[0]; // 編碼器採用所支持的第一種採樣格式 enc_ctx->time_base = (AVRational){1, enc_ctx->sample_rate}; // 時基:編碼器採樣率取倒數 // enc_ctx->codec->capabilities |= AV_CODEC_CAP_VARIABLE_FRAME_SIZE; // 只讀標誌 // 初始化一個FIFO用於存儲待編碼的音頻幀,初始化FIFO大小的1個採樣點 // av_audio_fifo_alloc()第二個參數是聲道數,第三個參數是單個聲道的採樣點數 // 採樣格式及聲道數在初始化FIFO時已設置,各處涉及FIFO大小的地方都是用的單個聲道的採樣點數 pp_audio_fifo[i] = av_audio_fifo_alloc(enc_ctx->sample_fmt, enc_ctx->channels, 1); if (pp_audio_fifo == NULL) { av_log(NULL, AV_LOG_ERROR, "Could not allocate FIFO\n"); return AVERROR(ENOMEM); } } if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) { enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } // 3.4 AVCodecContext初始化:使用AVCodec初始化AVCodecContext,初始化完成 /* Third parameter can be used to pass settings to encoder */ ret = avcodec_open2(enc_ctx, encoder, NULL); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for stream #%u\n", i); return ret; } // 3.5 設置輸出流codecpar ret = avcodec_parameters_from_context(out_stream->codecpar, enc_ctx); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Failed to copy encoder parameters to output stream #%u\n", i); return ret; } // 3.6 保存輸出流contex pp_enc_ctx[i] = enc_ctx; }
完整源碼在 transcode_video() 函數中,下面摘出關鍵部分:測試
// 2. 濾鏡處理 ret = filtering_frame(sctx->flt_ctx, frame_dec, frame_flt); if (ret == AVERROR_EOF) { av_log(NULL, AV_LOG_INFO, "filtering vframe EOF\n"); flt_finished = true; av_frame_free(&frame_flt); // flush encoder } else if (ret < 0) { av_log(NULL, AV_LOG_INFO, "filtering vframe error %d\n", ret); goto end; } flush_encoder: // 3. 編碼 if (frame_flt != NULL) { // 3.1 設置幀類型。若是不設置,則使用輸入流中的幀類型。 frame_flt->pict_type = AV_PICTURE_TYPE_NONE; } // 3.2 編碼 ret = av_encode_frame(sctx->o_codec_ctx, frame_flt, &opacket); if (ret == AVERROR(EAGAIN)) // 須要讀取新的packet餵給編碼器 { //av_log(NULL, AV_LOG_INFO, "encode vframe need more packet\n"); goto end; } else if (ret == AVERROR_EOF) { av_log(NULL, AV_LOG_INFO, "encode vframe EOF\n"); enc_finished = true; goto end; } else if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "encode vframe error %d\n", ret); goto end; } // 3.3 更新編碼幀中流序號,並進行時間基轉換 // AVPacket.pts和AVPacket.dts的單位是AVStream.time_base,不一樣的封裝格式其AVStream.time_base不一樣 // 因此輸出文件中,每一個packet須要根據輸出封裝格式從新計算pts和dts opacket.stream_index = sctx->stream_idx; av_packet_rescale_ts(&opacket, sctx->o_codec_ctx->time_base, sctx->o_stream->time_base); // 4. 將編碼後的packet寫入輸出媒體文件 ret = av_interleaved_write_frame(sctx->o_fmt_ctx, &opacket); av_packet_unref(&opacket);
作一個實驗,修改 5.1.2 節 frame_flt->pict_type 值和 5.1.1 節 enc_ctx->gop_size 和 enc_ctx->max_b_frames,將編碼後視頻幀 I/B/P 類型打印出來,觀察實驗結果。this
咱們選一個很短的視頻文件用於測試(右鍵另存爲):tnmil3.flv
編碼
tnmil3.flv 重命名爲 tnmil.flv 轉碼:tnmil.flv ==> tnmilo1.flv 命令:./transcode -i tnmil.flv -c:v copy -c:a copy tnmilo1.flv tnmil.flv ==> tnmilo1.flv 不修改frame IBP類型,不設置編碼器gop_size和max_b_frames IBPBPBPBPBPBBBPBBBPBBBPBBPBBBPBBBPBBPBBBPBBBPBBBPBPBPBBPBBBPBBBPBPBBBPBBBPBPBBPBBBPPIBBP IBPBPBPBPBPBBBPBBBPBBBPBBPBBBPBBBPBBPBBBPBBBPBBBPBPBPBBPBBBPBBBPBPBBBPBBBPBPBBPBBBPPIBBP tnmil.flv ==> tnmilo3.flv 將frame IBP類型設爲NONE,將編碼器gop_size設爲10,max_b_frames設爲1 IBPBPBPBPBPBBBPBBBPBBBPBBPBBBPBBBPBBPBBBPBBBPBBBPBPBPBBPBBBPBBBPBPBBBPBBBPBPBBPBBBPPIBBP IBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPPIBPP tnmilo3.flv ==> tnmilo4.flv 不修改frame IBP類型,不設置編碼器gop_size和max_b_frames IBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPPIBPP IBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPPIBPP tnmilo3.flv ==> tnmilo5.flv 將frame IBP類型設爲NONE,不設置編碼器gop_size(默認-1)和max_b_frames(默認0) IBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPBPBPBPPIBPPIBPP IBPBPBPBPBBBPBPBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBBBPBPBBBPPIBBP
實驗結論以下:將原始視頻幀 frame 送入視頻編碼器後生成編碼幀 packet,那麼
[1] 手工設置每一幀 frame 的幀類型爲 I/B/P,則編碼後的 packet 的幀類型和 frame 中的同樣。編碼器是否設置 gop_size 和 max_b_frames 兩個參數無影響。
[2] 將每一幀 frame 的幀類型設置爲 NONE,若是未設置編碼器的 gop_size(默認值 -1)和 max_b_frames (默認值 0)兩個參數,則編碼器自動選擇合適參數來進行編碼,生成幀類型。
[3] 將每一幀 frame 的幀類型設置爲 NONE,若是設置了編碼器的 gop_size 和 max_b_frames 兩個參數,則編碼器按照這兩個參數來進行編碼,生成幀類型。spa