二、視頻編碼
1、將yuv文件編碼爲flv格式的視頻文件
解碼的過程,請看上一篇
本篇是對上一篇解碼出來的yuv文件,進行編碼
上一篇獲取的yuv爲320x320,fps調整爲15的yuv420p文件
#include <memory>
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
}
// 申請智能指針變量
#define NEW_PTR(T, P, V, Fn) T *P = V; std::shared_ptr<T> P##P(P, [&P](T *){if(P != nullptr){Fn;}})
// 打印異常信息,並退出main函數
#define FATAL(M, ...) printf(M, ##__VA_ARGS__); return -1
// 自定義變量
const char *src_media = "D:/2.yuv";
const char *dst_media = "D:/2.flv";
// yuv420p的參數
#define YUV_IN_WIDTH 320
#define YUV_IN_HEIGHT 320
#define YUV_IN_FORMAT AV_PIX_FMT_YUV420P
#define YUV_IN_FPS 15
int main(int argc, char **argv) {
// 申請一個輸出的上下文
NEW_PTR(AVFormatContext, fmt_ctx, nullptr, avio_closep(&fmt_ctx->pb); avformat_close_input(&fmt_ctx));
avformat_alloc_output_context2(&fmt_ctx, nullptr, nullptr, dst_media);
if (fmt_ctx == nullptr) {
FATAL("alloc output format context failed.");
}
// 查詢編碼器
AVCodec *video_enc;
if ((video_enc = avcodec_find_encoder(fmt_ctx->oformat->video_codec)) == nullptr) {
FATAL("find video encoder failed.");
}
AVStream *video_stream = avformat_new_stream(fmt_ctx, video_enc);
video_stream->id = fmt_ctx->nb_streams - 1;
// 爲編碼器申請上下文
NEW_PTR(AVCodecContext, video_enc_ctx, nullptr, avcodec_free_context(&video_enc_ctx));
if ((video_enc_ctx = avcodec_alloc_context3(video_enc)) == nullptr) {
FATAL("allocate video enc context failed.");
}
// 爲編碼器配置編碼參數
video_enc_ctx->pix_fmt = video_enc->pix_fmts ? video_enc->pix_fmts[0] : AV_PIX_FMT_YUV420P;
video_enc_ctx->bit_rate = 400000;
video_enc_ctx->width = YUV_IN_WIDTH * 2;
video_enc_ctx->height = YUV_IN_HEIGHT * 2;
video_enc_ctx->time_base = AVRational{1, YUV_IN_FPS};
video_enc_ctx->framerate = AVRational{YUV_IN_FPS, 1};
video_enc_ctx->gop_size = 10;
// video_enc_ctx->max_b_frames = 1; // flv格式的視頻,不支持該參數
video_enc_ctx->qmin = 10;
video_enc_ctx->qmax = 50;
if (fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER) {
video_enc_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
}
NEW_PTR(AVDictionary, dict, nullptr, av_dict_free(&dict));
av_dict_set(&dict, "preset", "slow", 0);
// 打開編碼器
if (avcodec_open2(video_enc_ctx, video_enc, &dict) < 0) {
FATAL("codec open failed.");
}
if (avcodec_parameters_from_context(video_stream->codecpar, video_enc_ctx) < 0) {
FATAL("copy params failed.");
}
if (avio_open(&fmt_ctx->pb, dst_media, AVIO_FLAG_WRITE) < 0) {
FATAL("open dst file failed.");
}
// 寫入頭信息
if (avformat_write_header(fmt_ctx, nullptr) < 0) {
FATAL("write header failed.");
}
// 申請一個視頻frame
NEW_PTR(AVFrame, video_frame, nullptr, av_frame_free(&video_frame));
if ((video_frame = av_frame_alloc()) == nullptr) {
FATAL("allocate frame failed.");
}
// 給frame的data和size分配空間
video_frame->format = video_enc_ctx->pix_fmt;
video_frame->width = video_enc_ctx->width;
video_frame->height = video_enc_ctx->height;
if (av_frame_get_buffer(video_frame, 32) < 0) {
FATAL("allocate frame data failed.");
}
// 申請一個轉換後的視頻幀
NEW_PTR(AVFrame, buf_frame, nullptr, av_frame_free(&buf_frame));
if ((buf_frame = av_frame_alloc()) == nullptr) {
FATAL("allocate buf frame failed.");
}
// 給frame的data和size分配空間
buf_frame->format = YUV_IN_FORMAT;
buf_frame->width = YUV_IN_WIDTH;
buf_frame->height = YUV_IN_HEIGHT;
if (av_frame_get_buffer(buf_frame, 32) < 0) {
FATAL("allocate buf frame data failed.");
}
// 申請一塊內存,用來讀取frame數據
int frameSize = av_image_get_buffer_size((AVPixelFormat)buf_frame->format, buf_frame->width, buf_frame->height, 1);
NEW_PTR(uint8_t, pFrameBuf, (uint8_t *) av_malloc((size_t) frameSize), av_freep(&pFrameBuf));
// 申請一個sws視頻轉換器
NEW_PTR(SwsContext, sws_ctx, nullptr, sws_freeContext(sws_ctx));
sws_ctx = sws_getContext(buf_frame->width, buf_frame->height, (AVPixelFormat)buf_frame->format,
video_frame->width, video_frame->height, (AVPixelFormat) video_frame->format,
SWS_BILINEAR, nullptr, nullptr, nullptr);
// 申請一個packet,並初始化
AVPacket pkt;
av_init_packet(&pkt);
pkt.data = nullptr;
pkt.size = 0;
NEW_PTR(FILE, input, nullptr, fclose(input));
if ((input = fopen(src_media, "rb")) == nullptr) {
FATAL("no readable file.");
}
// 循環讀取frame數據
int frame_count = 0;
while (true) {
// 聲明一個用來編碼的幀
AVFrame *encode_frame = nullptr;
if (fread(pFrameBuf, 1, (size_t) frameSize, input) < 0) {
FATAL("read input file failed.");
} else if (!feof(input)) {
// 文件沒有到結尾,則獲取編碼幀
av_image_fill_arrays(buf_frame->data, buf_frame->linesize, pFrameBuf, (AVPixelFormat)buf_frame->format, buf_frame->width, buf_frame->height, 1);
sws_scale(sws_ctx, (const uint8_t *const *) buf_frame->data,
buf_frame->linesize, 0, buf_frame->height, video_frame->data,
video_frame->linesize);
video_frame->pts = frame_count++;
encode_frame = video_frame;
} else {
// 文件結束了,則發送一個空指針的frame,用來清空緩衝區
encode_frame = nullptr;
}
// 發送一個frame
if (avcodec_send_frame(video_enc_ctx, encode_frame) < 0) {
FATAL("send frame exception.");
}
// 接受編碼完的內容
while (true) {
auto packet_ret = avcodec_receive_packet(video_enc_ctx, &pkt);
// 判斷是否完全接受了packet
if (packet_ret == AVERROR(EAGAIN) || packet_ret == AVERROR_EOF) {
break;
}
// 檢查是否接受異常
if (packet_ret < 0) {
FATAL("receive packet exception.");
}
av_packet_rescale_ts(&pkt, video_enc_ctx->time_base, video_stream->time_base);
pkt.stream_index = video_stream->index;
av_interleaved_write_frame(fmt_ctx, &pkt);
av_packet_unref(&pkt);
}
// 編碼幀爲空,則表示已經處理完所有的編碼,退出該循環
if (encode_frame == nullptr) break;
}
av_write_trailer(fmt_ctx);
return 0;
}