const char* SRC_FILE = "1.mkv";
const char* OUT_FILE = "outfile.h264";
const char* OUT_FMT_FILE = "outfmtfile.mp4";
int main()
{
av_register_all();
AVFormatContext* pFormat = NULL; // 輸入文件的Format
if (avformat_open_input(&pFormat, SRC_FILE, NULL, NULL) < 0)
{
return 0;
}
AVCodecContext* video_dec_ctx = NULL; // 解碼器上下文
AVCodec* video_dec = NULL; // 視頻解碼器
if (avformat_find_stream_info(pFormat, NULL) < 0)
{
return 0;
}
av_dump_format(pFormat, 0, SRC_FILE, 0);
video_dec_ctx = pFormat->streams[0]->codec;
video_dec = avcodec_find_decoder(video_dec_ctx->codec_id);
if (avcodec_open2(video_dec_ctx, video_dec, NULL) < 0)
{
return 0;
}
AVFormatContext* pOFormat = NULL;
AVOutputFormat* ofmt = NULL;
if (avformat_alloc_output_context2(&pOFormat, NULL, NULL, OUT_FILE) < 0) // 下面有解析
{
return 0;
}
ofmt = pOFormat->oformat;
if (avio_open(&(pOFormat->pb), OUT_FILE, AVIO_FLAG_READ_WRITE) < 0)
{
return 0;
}
AVCodecContext *video_enc_ctx = NULL; // 視頻編碼器上下文
AVCodec *video_enc = NULL; // 視頻編碼器
video_enc = avcodec_find_encoder(AV_CODEC_ID_H264); // 指定編碼器
AVStream *video_st = avformat_new_stream(pOFormat, video_enc); // 創建流
if (!video_st)
return 0;
// 設置了編碼器的相關信息
video_enc_ctx = video_st->codec;
video_enc_ctx->width = video_dec_ctx->width;
video_enc_ctx->height = video_dec_ctx->height;
video_enc_ctx->pix_fmt = PIX_FMT_YUV420P;
video_enc_ctx->time_base.num = 1;
video_enc_ctx->time_base.den = 25;
video_enc_ctx->bit_rate = video_dec_ctx->bit_rate;
video_enc_ctx->gop_size = 250;
video_enc_ctx->max_b_frames = 10;
//H264
//pCodecCtx->me_range = 16;
//pCodecCtx->max_qdiff = 4;
video_enc_ctx->qmin = 10;
video_enc_ctx->qmax = 51;
// 打開編碼器
if (avcodec_open2(video_enc_ctx, video_enc, NULL) < 0)
{
printf("編碼器打開失敗!\n");
return 0;
}
printf("Output264video Information====================\n");
av_dump_format(pOFormat, 0, OUT_FILE, 1);
printf("Output264video Information====================\n");
//mp4 file
AVFormatContext* pMp4Format = NULL; // FormatContext - 格式上下文
AVOutputFormat* pMp4OFormat = NULL; // 輸出格式
//可以初始化一個用於輸出的AVFormatContext結構體,第二個參數如設成NULL格式有後面兩個猜出
// 第三個參數爲format_name 第四個參數爲fileName
if (avformat_alloc_output_context2(&pMp4Format, NULL, NULL, OUT_FMT_FILE) < 0)
{
return 0;
}
// 從輸出格式上下文中知道確切的輸出格式
pMp4OFormat = pMp4Format->oformat;
// 創建並初始化一個AVIOConetxt爲接收給定路徑文件資源的接收
if (avio_open(&(pMp4Format->pb), OUT_FMT_FILE, AVIO_FLAG_READ_WRITE) < 0)
{
return 0;
}
// 循環看輸入format中的流們,根據這個流創建輸出流
for (int i = 0; i < pFormat->nb_streams; i++) {
// 根據輸入流創建輸出流
AVStream *in_stream = pFormat->streams[i];
AVStream *out_stream = avformat_new_stream(pMp4Format, in_stream->codec->codec);
if (!out_stream) {
return 0;
}
// 拷貝輸入的avcodecConText 給輸出的AVCodecContext
int ret = 0;
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
if (ret < 0) {
fprintf(stderr, "Failed to copy context from input to output stream codec context\n");
return 0;
}
// 這是幹啥?
out_stream->codec->codec_tag = 0;
if (pMp4Format->oformat->flags & AVFMT_GLOBALHEADER)
out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
//Print detailed information about the input or output format, such as
//uration, bitrate, streams, container, programs, metadata, side data,
//codec and time base.
av_dump_format(pMp4Format, 0, OUT_FMT_FILE, 1);
// 寫視頻文件頭
if (avformat_write_header(pMp4Format, NULL) < 0)
{
return 0;
}
////
// 設置AVOption(選項設置-根據字符串設置結構體的屬性值)
av_opt_set(video_enc_ctx->priv_data, "preset", "superfast", 0);
av_opt_set(video_enc_ctx->priv_data, "tune", "zerolatency", 0);
avformat_write_header(pOFormat, NULL); // 再次寫頭?
// AVPacket是存儲壓縮編碼數據相關信息的結構體。 保存的是解碼前的數據,也就是壓縮後的數據。
// 該結構體不直接包含數據,其中有一個執行數據域的指針,ffmpeg中的很多數據結構都是用這種方法來管理
// 它保存瞭解複用之後,解碼之前的數據(仍然是壓縮後的數據)和關於這些數據的一些附加信息,如
// 顯示時間戳(pts)、解碼時間戳(dts)、數據時長,所在媒體流的索引等
AVPacket *pkt = new AVPacket();
av_init_packet(pkt);
AVFrame *pFrame = avcodec_alloc_frame(); //幀
int ts = 0;
while (1)
{
// 作用是讀取碼流中的音頻若干幀或者視頻一幀 - 返回流的下一幀
if (av_read_frame(pFormat, pkt) < 0)
{
avio_close(pOFormat->pb); // 關閉流 - 輸入的流哦
av_write_trailer(pMp4Format); // 給輸出MP4文件 寫文件件尾
avio_close(pMp4Format->pb); // 關閉流 - 輸出的MP4文件的流
delete pkt; // 銷燬pkt - 包
return 0;
}
// 根據pkt包中的流的索引來搞 0-對應的是視頻流, 1 - 對應的是音頻流
// stream_index 標識當前AVPacket所從屬的碼流
// 在前面的某個地方搞過 了 AVMEDIA_TYPE_VIDEO = 0,AVMEDIA_TYPE_AUDIO = 1;
if (pkt->stream_index == 0)
{
int got_picture = 0, ret = 0;
// 作用是解碼一幀視頻數據。輸入一個壓縮編碼的結構體AVPacket,輸出一個解碼後的結構體AVFrame
// video_dec_ctx:編解碼上下文環境,定義了編解碼操作的一些細節;
// got_picture_ptr:該值爲0表明沒有圖像可以解碼,否則表明有圖像可以解碼;
ret = avcodec_decode_video2(video_dec_ctx, pFrame, &got_picture, pkt); // - 解碼,將那個H264的文件解碼了
if (ret < 0)
{
delete pkt;
return 0;
}
//DTS(decoding time stamp) 和PTS(Presentation Time stamp) 前者是解碼的時間,後者是顯示的時間。 兩種時間戳
pFrame->pts = pFrame->pkt_pts;//ts++;
// 若爲真,則證明有圖像可以解碼。
if (got_picture)
{
AVPacket *tmppkt = new AVPacket;
av_init_packet(tmppkt);
int size = video_enc_ctx->width*video_enc_ctx->height * 3 / 2;
char* buf = new char[size];
memset(buf, 0, size);
tmppkt->data = (uint8_t*)buf; // 數據域指針-搞它這個存放幀數據的地方的管理方式。
tmppkt->size = size;
// 編碼一幀視頻數據
// video_enc_ctx:編碼器上下文
// tmppkt: output AVPacket. (輸出pkt)
// pFrame: AVFrame containing the raw video data to be encoded.
// got_picture: 如果輸出packt不爲空 它被設爲1,否則爲0 ,若函數返回值爲err,這個指針就不要用了。
ret = avcodec_encode_video2(video_enc_ctx, tmppkt, pFrame, &got_picture);
if (ret < 0)
{
avio_close(pOFormat->pb); // 關了這個流吧
delete buf;
return 0;
}
// 如果tmppkt不爲空
if (got_picture)
{
//ret = av_interleaved_write_frame(pOFormat, tmppkt);
AVStream *in_stream = pFormat->streams[pkt->stream_index]; // 輸入流
AVStream *out_stream = pMp4Format->streams[pkt->stream_index]; // 輸出流
// 對新的pkt的時間戳們進行
// 它的作用是計算 "a * b / c" 的值並分五種方式來取整. - "時鐘基c" 表示的 數值a 轉換成以 "時鐘基b" 來表示。
tmppkt->pts = av_rescale_q_rnd(tmppkt->pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
tmppkt->dts = av_rescale_q_rnd(tmppkt->dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
tmppkt->duration = av_rescale_q(tmppkt->duration, in_stream->time_base, out_stream->time_base);
tmppkt->pos = -1;
// Write a packet to an output media file ensuring correct interleaving.
ret = av_interleaved_write_frame(pMp4Format, tmppkt);
if (ret < 0)
return 0;
delete tmppkt; // 刪除臨時的pkt
delete buf; // 刪除buf
}
}
//avcodec_free_frame(&pFrame);
}
else if (pkt->stream_index == 1) // 音頻的,我不太理解
{
AVStream *in_stream = pFormat->streams[pkt->stream_index];
AVStream *out_stream = pMp4Format->streams[pkt->stream_index];
pkt->pts = av_rescale_q_rnd(pkt->pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
pkt->dts = av_rescale_q_rnd(pkt->dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF);
pkt->duration = av_rescale_q(pkt->duration, in_stream->time_base, out_stream->time_base);
pkt->pos = -1;
if (av_interleaved_write_frame(pMp4Format, pkt) < 0)
return 0;
}
}
avcodec_free_frame(&pFrame);
return 0;
}