一、视频编码流程
使用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;
- // 计算所需缓冲区大小
- int size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
- in_frame_buf = (uint8_t*)av_malloc(size); // 分配图像帧内存空间
- // 填充 AVFrame
- av_image_fill_arrays(in_frame->data, in_frame->linesize, in_frame_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
复制代码 (5)创建格式转换上下文
ffmpeg的libx264编码器只支持输入格式为P,如果输入格式不是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);
复制代码 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 {
- memcpy(in_frame->data[0], data, size);
- }
- 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 <libavformat/avformat.h>
- #include <libavcodec/avcodec.h>
- #include <libavutil/avutil.h>
- #include <libavutil/opt.h>
- #include <libavutil/mathematics.h>
- #include <libavutil/timestamp.h>
- #include <libswresample/swresample.h>
- #include <libswscale/swscale.h>
- #include <libavutil/imgutils.h>
- #include <libavutil/error.h>
- }
- 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);
- 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;
- // 计算所需缓冲区大小
- int size = av_image_get_buffer_size(pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
- in_frame_buf = (uint8_t*)av_malloc(size);
- // 填充 AVFrame
- av_image_fill_arrays(in_frame->data, in_frame->linesize, in_frame_buf, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, 1);
- // 创建缩放上下文,图像缩放和格式转换上下文
- 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 DecodeVideoFrame(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 {
- memcpy(in_frame->data[0], data, size);
- }
- 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;
- }
- }
-
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |