ffmpeg视频编码

一、视频编码流程

使用ffmpeg解码视频帧主要可分为两大步骤:初始化编码器和编码视频帧,以下代码以h264为例

1. 初始化编码器

初始化编码器包含以下步骤:

(1)查找编码器

videoCodec = avcodec_find_encoder_by_name(videoCodecName);

if (!videoCodec) {

release();

return false;

}

(2)设置编码器上下文参数

pCodecCtx = avcodec_alloc_context3(videoCodec);

pCodecCtx->time_base = { 1, m_fps }; // 设置编码器上下文的时间基准

pCodecCtx->framerate = { m_fps, 1 };

pCodecCtx->bit_rate = videoBitrate;

pCodecCtx->gop_size = 25;//影响视频的压缩效率、随机访问性能以及编码复杂度,对视频质量、文件大小和编码/解码性能也有影响

pCodecCtx->width = in_w;

pCodecCtx->height = in_h;

pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;// AV_PIX_FMT_NV12,AV_PIX_FMT_YUV420P

pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;

av_opt_set(pCodecCtx->priv_data, "preset", "superfast", 0); // 设置编码速度

// 以下为根据对应的编码格式设置相关的参数

if (pCodecCtx->codec_id == AV_CODEC_ID_H264)

{

pCodecCtx->keyint_min = m_fps;

av_opt_set(pCodecCtx->priv_data, "profile", "main", 0);

av_opt_set(pCodecCtx->priv_data, "b-pyramid", "none", 0);

av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);

}

else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)

pCodecCtx->max_b_frames = 2;

else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)

pCodecCtx->mb_decision = 2;

(3)打开编码器

if (avcodec_open2(pCodecCtx, videoCodec, NULL) < 0) // 将编码器上下文和编码器进行关联

{

release();

return false;

}

(4)设置图像帧参数

// 初始化编码帧

in_frame = av_frame_alloc();

if (!in_frame) {

return false;

}

// 设置 AVFrame 的其他属性

in_frame->width = pCodecCtx->width;

in_frame->height = pCodecCtx->height;

in_frame->format = pCodecCtx->pix_fmt;

err = av_frame_get_buffer(in_frame, 0);

if (err < 0) {

return false;

}

(5)创建格式转换上下文

ffmpeg的libx264编码器只支持输入格式为YUV420P,如果输入格式不是YUV420P,则需要转换

// 创建缩放上下文,图像缩放和格式转换上下文

if (videoSrcFormat != AV_PIX_FMT_YUV420P) {

sws_ctx = sws_getContext(in_w, in_h, videoSrcFormat,

in_w, in_h, AV_PIX_FMT_YUV420P,

SWS_BILINEAR, NULL, NULL, NULL);

if (!sws_ctx) {

release();

return false;

}

}

(6)封装设置和打开文件

如果要将编码后数据进行封装(如封装成mp4文件),则需要使用AVFormatContext,并设置视频流

// 打开格式上下文

if (avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, mp4_file) < 0) {

release();

return false;

}

// 初始化视频码流

video_st = avformat_new_stream(pFormatCtx, pCodecCtx->codec);

if (!video_st) {

std::cout << "avformat_new_stream error" << endl;

return false;

}

fmt = pFormatCtx->oformat;

video_st->id = pFormatCtx->nb_streams - 1;

avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);

pFormatCtx->video_codec_id = pFormatCtx->oformat->video_codec;

// 打开文件

if (avio_open(&pFormatCtx->pb, mp4_file, AVIO_FLAG_READ_WRITE) < 0) {

//std::cout << "output file open fail!" << endl;

swprintf(buf, 1024, L"avio_open error");

logger.logg(buf);

return false;

}

// 输出格式信息

av_dump_format(pFormatCtx, 0, mp4_file, 1);

// 写入文件头

int ret = avformat_write_header(pFormatCtx, NULL);

if (ret < 0) {

std::cout << "Error occurred when opening output file" << std::endl;

return false;

}

2. 编码视频帧

(1)将编码数据送往解码器

// data为需要编码的数据地址,为uint8_t类型的指针,size为输入帧的大小

// 如果输入数据格式不是YUV420P则需要转化

if (videoSrcFormat != AV_PIX_FMT_YUV420P) {

if (sws_scale(sws_ctx, (const uint8_t* const*)&data, mVideoSrcStride, 0, in_h, in_frame->data, in_frame->linesize) < 0) {

return false;

}

}

else {

in_frame->data[0] = data;

in_frame->data[1] = data + in_frame->linesize[0] * in_frame->height;

in_frame->data[2] = data + in_frame->linesize[0] * in_frame->height + in_frame->linesize[1] * in_frame->height / 2;

}

in_frame->pts = videoPktCount; // videoPktCount为输入帧的序号

// 编码

int ret = avcodec_send_frame(pCodecCtx, in_frame);//avcodec_send_frame() 函数用于将解码器处理的视频帧发送给编码器

if (ret < 0) {

return false;

}

(2)接收编码数据

AVPacket* vicdeo_pkt = av_packet_alloc();

while (ret >= 0) {

ret = avcodec_receive_packet(pCodecCtx, &vicdeo_pkt);//用于从编码器接收编码后的数据包

if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)

break;

else if (ret < 0) {

//cout << "Error during encoding" << endl;

return false;

}

// Prepare packet for muxing

pkt.stream_index = video_st->index;

av_packet_rescale_ts(&pkt, pCodecCtx->time_base, video_st->time_base);//用于将 AVPacket 中的时间戳(PTS和DTS)从一个时间基转换到另一个时间基

pkt.pos = -1;//在某些情况下,如果数据包的原始位置是未知的,或者该数据包不是从文件中读取的,而是由其他方式生成的,那么可能会将 pos 设置为 -1。

ret = av_interleaved_write_frame(pFormatCtx, &pkt); //用于将一个 AVPacket 写入到一个输出媒体文件中

if (ret < 0) {

return false;

}

// Free the packet

av_packet_unref(&pkt);

}

++videoPktCount; // 每编码完成一帧,videoPktCount加一

av_packet_free(&vicdeo_pkt);

videoPkt = nullptr;

二、使用ffmpeg实现对内存中的视频帧数据编码

以下代码实现了ffmpeg对视频流数据进行编码的主要过程,可分为初始化编码器(InitEncoder)和编码视频帧(DecodeVideoFrame)

extern "C" {

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

}

bool mHasVideo = false;

char* mp4_file = "test.mp4";

int in_w = 1920;

int in_h = 1080;

int m_fps = 30;

int64_t videoBitrate = 6000000;

int mVideoSrcChannel = 0;

int mVideoSrcStride[1] = { 0 };

int64_t videoPktCount = 0;

AVPixelFormat videoSrcFormat = AV_PIX_FMT_YUV420P; // 输入的像素格式

AVStream* video_st = nullptr;

const AVCodec* videoCodec = nullptr; // 视频编码器

AVCodecContext* pCodecCtx = nullptr; // 视频编码器上下文

uint8_t* in_frame_buf = nullptr;

AVFrame* in_frame = nullptr;

SwsContext* sws_ctx = nullptr;

// 初始化编码器

bool InitEncoder() {

/**** 编码器设置 ****/

// 查找编码器

videoCodec = avcodec_find_encoder_by_name(videoCodecName);

if (!videoCodec) {

release();

return false;

}

pCodecCtx = avcodec_alloc_context3(videoCodec);

pCodecCtx->time_base = { 1, m_fps }; // 设置编码器上下文的时间基准

pCodecCtx->framerate = { m_fps, 1 };

pCodecCtx->bit_rate = videoBitrate;

pCodecCtx->gop_size = 25;//影响视频的压缩效率、随机访问性能以及编码复杂度,对视频质量、文件大小和编码/解码性能也有影响

pCodecCtx->width = in_w;

pCodecCtx->height = in_h;

pCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;// AV_PIX_FMT_NV12,AV_PIX_FMT_YUV420P

pCodecCtx->codec_type = AVMEDIA_TYPE_VIDEO;

av_opt_set(pCodecCtx->priv_data, "preset", "superfast", 0);

// 以下为根据对应的编码格式设置相关的参数

if (pCodecCtx->codec_id == AV_CODEC_ID_H264)

{

pCodecCtx->keyint_min = m_fps;

av_opt_set(pCodecCtx->priv_data, "profile", "main", 0);

av_opt_set(pCodecCtx->priv_data, "b-pyramid", "none", 0);

av_opt_set(pCodecCtx->priv_data, "tune", "zerolatency", 0);

}

else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)

pCodecCtx->max_b_frames = 2;

else if (pCodecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)

pCodecCtx->mb_decision = 2;

if (avcodec_open2(pCodecCtx, videoCodec, NULL) < 0) // 将编码器上下文和编码器进行关联

{

release();

return false;

}

/**** 封装设置 ****/

// if (avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, mp4_file) < 0) {

// // swprintf(buf, 1024, L"avformat_alloc_output_context2 error");

// // logger.logg(buf);

// std::cout << "avformat_alloc_output_context2 error" << endl;

// return false;

// }

// // 初始化视频码流

// video_st = avformat_new_stream(pFormatCtx, pCodecCtx->codec);

// if (!video_st) {

// std::cout << "avformat_new_stream error" << endl;

// return false;

// }

// video_st->id = pFormatCtx->nb_streams - 1;

// avcodec_parameters_from_context(video_st->codecpar, pCodecCtx);

// pFormatCtx->video_codec_id = pFormatCtx->oformat->video_codec;

// // 打开文件

// if (avio_open(&pFormatCtx->pb, mp4_file, AVIO_FLAG_READ_WRITE) < 0) {

// release();

// return false;

// }

// 输出格式信息

// av_dump_format(pFormatCtx, 0, mp4_file, 1);

/****** 输入参数 *********/

// 初始化编码帧

in_frame = av_frame_alloc();

if (!in_frame) {

return false;

}

// 设置 AVFrame 的其他属性

in_frame->width = pCodecCtx->width;

in_frame->height = pCodecCtx->height;

in_frame->format = pCodecCtx->pix_fmt;

err = av_frame_get_buffer(in_frame, 0);

if (err < 0) {

release();

return false;

}

// 创建缩放上下文,图像缩放和格式转换上下文

if (videoSrcFormat != AV_PIX_FMT_YUV420P) {

sws_ctx = sws_getContext(in_w, in_h, videoSrcFormat,

in_w, in_h, AV_PIX_FMT_YUV420P,

SWS_BILINEAR, NULL, NULL, NULL);

if (!sws_ctx) {

release();

return false;

}

}

av_image_fill_linesizes(mVideoSrcStride, videoSrcFormat, in_w); // 计算图像的行大小,格式转换时会用上

videoPktCount = 0;

}

bool EncodeVideoFrame(uint8_t* data, int size) {

// data为需要编码的数据地址,为uint8_t类型的指针,size为输入帧大小

// 如果输入数据格式不是YUV420P则需要转化

if (videoSrcFormat != AV_PIX_FMT_YUV420P) {

if (sws_scale(sws_ctx, (const uint8_t* const*)&data, mVideoSrcStride, 0, in_h, in_frame->data, in_frame->linesize) < 0) {

return false;

}

}

else {

// 输入数据为yuv420p格式时,需分三个平面存储

in_frame->data[0] = data;

in_frame->data[1] = data + in_frame->linesize[0] * in_frame->height;

in_frame->data[2] = data + in_frame->linesize[0] * in_frame->height + in_frame->linesize[1] * in_frame->height / 2;

}

in_frame->pts = videoPktCount; // videoPktCount为输入帧的序号

// 编码

int ret = avcodec_send_frame(pCodecCtx, in_frame);//avcodec_send_frame() 函数用于将解码器处理的视频帧发送给编码器

if (ret < 0) {

return false;

}

AVPacket* vicdeo_pkt = av_packet_alloc();

while (ret >= 0) {

ret = avcodec_receive_packet(pCodecCtx, &vicdeo_pkt);//用于从编码器接收编码后的数据包

if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)

break;

else if (ret < 0) {

return false;

}

// Prepare packet for muxing

vicdeo_pkt.stream_index = video_st->index;

// av_packet_rescale_ts(&pkt, pCodecCtx->time_base, video_st->time_base);//用于将 AVPacket 中的时间戳(PTS和DTS)从一个时间基转换到另一个时间基

vicdeo_pkt.pos = -1;//在某些情况下,如果数据包的原始位置是未知的,或者该数据包不是从文件中读取的,而是由其他方式生成的,那么可能会将 pos 设置为 -1。

// ret = av_interleaved_write_frame(pFormatCtx, vicdeo_pkt); //用于将一个 AVPacket 写入到一个输出媒体文件中

// if (ret < 0) {

// return false;

// }

// Free the packet

av_packet_unref(vicdeo_pkt);

}

av_packet_free(&vicdeo_pkt);

videoPkt = nullptr;

++videoPktCount; // 每编码完成一帧,videoPktCount加一

}

void release() {

if (in_frame_buf) {

av_free(in_frame_buf);

in_frame_buf = nullptr;

}

if (in_frame) {

av_frame_free(&in_frame);

in_frame = nullptr;

}

if (pCodecCtx) {

avcodec_close(pCodecCtx);

avcodec_free_context(&pCodecCtx);

pCodecCtx = nullptr;

}

if (sws_ctx) {

sws_freeContext(sws_ctx);

sws_ctx = nullptr;

}

// if (pFormatCtx) {

// avio_close(pFormatCtx->pb);

// avformat_free_context(pFormatCtx);

// pFormatCtx = nullptr;

// }

}

曹操皮肤选择大揭秘:幽灵船长与夜都魔契谁更胜一筹?
只狼商人在哪 全商人位置一览