ffmpeg 视频编码一

本文介绍如何在ffmpeg中进行视频编码,包括初始化编码器、处理帧编码与输出,以及通过libswscale进行视频宽高调整和格式转换的重要性和步骤。通过实际案例展示了编码过程中的注意事项,如宽高匹配的重要性,并预告了后续将探讨的libavfilter应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. ffmpeg 视频解码一
2. ffmpeg 视频解码二
3. ffmpeg 音频解码一
4. ffmpeg 音频解码二
5. ffmpeg 音视频解码
6. ffmpeg 视频编码一
7. ffmpeg 视频编码一(精简版)
8. ffmpeg 视频编码二(基于 libswscale 转换视频)
9. ffmpeg 过滤器libavfilter的使用
10. ffmpeg 视频编码三(基于 libavfilter 转换视频)

前言

前面已经说过了视频解码了,但我们只了解解码肯定不行,这篇文章在其之上(先解码,再编码),来阐述下视频编码。

流程图

首先我们还是看下流程图。
在这里插入图片描述
相对于解码来说,编解码的话就是多了两个环节而已,首先是我们需要初始化我们的编码器并打开,其次是在解码完一帧视频的时候,我们直接对这一帧视频做编码,然后输出到我们的输出文件里即可。

源代码


#pragma once
#define __STDC_CONSTANT_MACROS
#define _CRT_SECURE_NO_WARNINGS

extern "C"
{
#include <libavformat/avformat.h>
#include "libavcodec/avcodec.h"
}

using namespace std;

#define INPUT_FILE_NAME "lh_online.h264"
#define OUTPUT_FILE_NAME "lh_online.mp4"
//mp4
#define L_AVCODEID AV_CODEC_ID_MPEG4

 //带编码视频类型
#define ENC_AV_PIX_FMT_YUV AV_PIX_FMT_YUV420P 
//编码之后的视频宽度
#define ENC_VIDEO_WIDTH 512
//编码之后的视频高度
#define ENC_VIDEO_HEIGHT 288
#define ENC_VIDEO_BITRATE 400000
//time_base  AVRational{1,15}
//一秒多少张图片(原视频是15。)
#define ENC_TIME_BASE_DEN 15

AVFormatContext* i_fmt_ctx = NULL, * o_fmt_ctx = NULL;
AVCodecContext* dec_c = NULL, * enc_c = NULL;;

AVStream* in_stream;
AVStream* out_stream;
AVFrame* frame;
AVPacket* dec_pkt, * enc_pkt;
int ret;
int stream_index;
int i = 0;


static void encode()
{
	//发送待编码数据到编码器
	ret = avcodec_send_frame(enc_c, frame);
	if (ret < 0) {
		fprintf(stderr, "Error sending a frame for encoding\n");
		exit(1);
	}

	while (ret >= 0) {
		//编码一帧视频
		ret = avcodec_receive_packet(enc_c, enc_pkt);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			return;
		else if (ret < 0) {
			fprintf(stderr, "Error during encoding\n");
			exit(1);
		}
		//写入文件时流序号(这里只编码了视频流,而且也只实例化了一个输出流(avformat_new_stream),序号直接就是0)
		//多输出流的时候需要注意,后面音视频同时处理的时候,再说这个吧
		enc_pkt->stream_index = 0;
		//时间基转换(解码器->输出流)
		av_packet_rescale_ts(enc_pkt,
			enc_c->time_base,
			out_stream->time_base);
		printf("Write packet %d (size=%d)\n", enc_pkt->pts, enc_pkt->size);
		//写文件
		ret = av_interleaved_write_frame(o_fmt_ctx, enc_pkt);
		av_packet_unref(enc_pkt);
	}
}

static void decode()
{
	//时间基转换(输入流->解码器)
	av_packet_rescale_ts(dec_pkt,
		in_stream->time_base,
		dec_c->time_base);

	//发送待解码到解码器
	ret = avcodec_send_packet(dec_c, dec_pkt);
	if (ret < 0) {
		av_log(NULL, AV_LOG_ERROR, "Error sending a packet for decoding.\n");
		exit(1);
	}

	while (ret >= 0) {
		//获取解码数据
		ret = avcodec_receive_frame(dec_c, frame);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			return;
		else if (ret < 0) {
			av_log(NULL, AV_LOG_ERROR, "Error receive a frame for decoding.\n");
			exit(1);
		}
		//设置一个pts
		frame->pts = i++;
		//此时一帧视频已经保存到frame中了
		//打印输出的视频帧的帧数
		printf("decode frame %d \n", dec_c->frame_number);
		//编码
		encode();
		av_frame_unref(frame);
	}
}

static int open_input_file() {
	int ret;
	AVCodec* codec;

	//打开输入文件,并为fmt_ctx分配空间
	if ((ret = avformat_open_input(&i_fmt_ctx, INPUT_FILE_NAME, NULL, NULL)) < 0) {
		av_log(NULL, AV_LOG_ERROR, "Codec not open source file.\n");
		return ret;
	}

	//获取流信息
	if ((ret = avformat_find_stream_info(i_fmt_ctx, NULL)) < 0) {
		av_log(NULL, AV_LOG_ERROR, "Could not find stream information.\n");
		return ret;
	}

	//获取视频流序号(这里我们明确要解码的是视频,也只处理视频)
	stream_index = av_find_best_stream(i_fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
	if (stream_index < 0) {
		av_log(NULL, AV_LOG_ERROR, "Cannot find stream index\n");
		return AVERROR(ENOMEM);
	}

	in_stream = i_fmt_ctx->streams[stream_index];

	//获取解码器(这里不需要我们显示的指定了)
	codec = avcodec_find_decoder(in_stream->codecpar->codec_id);
	if (!codec) {
		av_log(NULL, AV_LOG_ERROR, "Codec not found.\n");
		return AVERROR_DECODER_NOT_FOUND;
	}

	//分配解析器上下文
	dec_c = avcodec_alloc_context3(codec);
	if (!dec_c) {
		av_log(NULL, AV_LOG_ERROR, "Could not allocate video codec context.\n");
		return AVERROR(ENOMEM);
	}

	//把输入流的编解码参数复制到我们的解码器上
	if ((ret = avcodec_parameters_to_context(dec_c, in_stream->codecpar)) < 0) {
		av_log(NULL, AV_LOG_ERROR, "Failed to copy %s codec parameters to decoder context\n");
		return ret;
	}

	//打开解码器
	if ((ret = avcodec_open2(dec_c, codec, NULL)) < 0) {
		av_log(NULL, AV_LOG_ERROR, "Could not open codec.\n");
		return ret;
	}
}

static int open_output_file()
{
	int ret;
	AVCodec* codec;
	o_fmt_ctx = avformat_alloc_context();
	avformat_alloc_output_context2(&o_fmt_ctx, NULL, NULL, OUTPUT_FILE_NAME);
	if (!o_fmt_ctx) {
		av_log(NULL, AV_LOG_ERROR, "Could not create output context\n");
		return AVERROR_UNKNOWN;
	}

	//获取编码器
	codec = avcodec_find_encoder(L_AVCODEID);
	if (!codec) {
		av_log(NULL, AV_LOG_FATAL, "encoder Codec not found\n");
		return AVERROR_INVALIDDATA;
	}

	enc_c = avcodec_alloc_context3(codec);
	if (!enc_c) {
		av_log(NULL, AV_LOG_FATAL, "Failed to allocate the encoder context\n");
		return AVERROR(ENOMEM);
	}
	//一些默认参数的设置
	enc_c->codec_id = codec->id;
	enc_c->pix_fmt = ENC_AV_PIX_FMT_YUV;  
	enc_c->bit_rate = ENC_VIDEO_BITRATE;
	enc_c->width = ENC_VIDEO_WIDTH; 
	enc_c->height = ENC_VIDEO_HEIGHT;
	enc_c->time_base.num = 1;
	enc_c->time_base.den = ENC_TIME_BASE_DEN; 
	enc_c->gop_size = 12;
	enc_c->max_b_frames = 4;

	if (o_fmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
		enc_c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

	//实例化输出流
	out_stream = avformat_new_stream(o_fmt_ctx, NULL);
	if (!out_stream) {
		av_log(NULL, AV_LOG_ERROR, "Failed allocating output stream\n");
		return AVERROR_UNKNOWN;
	}

	//复制编码器参数到输出流
	ret = avcodec_parameters_from_context(out_stream->codecpar, enc_c);
	if (0 != ret)
	{
		fprintf(stderr, "Failed to copy codec parameters\n");
		return -1;
	}

	out_stream->time_base = enc_c->time_base;

	//打开编码器
	ret = avcodec_open2(enc_c, codec, NULL);
	if (ret < 0) {
		av_log(NULL, AV_LOG_ERROR, "Cannot open video encoder for stream\n");
		return ret;
	}

	//打开输出文件
	if (!(o_fmt_ctx->oformat->flags & AVFMT_NOFILE)) {
		ret = avio_open(&o_fmt_ctx->pb, OUTPUT_FILE_NAME, AVIO_FLAG_WRITE);
		if (ret < 0) {
			av_log(NULL, AV_LOG_ERROR, "Could not open output file\n", OUTPUT_FILE_NAME);
			return ret;
		}
	}

	//写文件头
	ret = avformat_write_header(o_fmt_ctx, NULL);
	if (ret < 0) {
		av_log(NULL, AV_LOG_ERROR, "Error occurred when opening output file\n");
		return ret;
	}
	return 0;
}

int main(int argc, char* argv[])
{
	//输入处理
	if ((ret = open_input_file()) < 0) {
		av_log(NULL, AV_LOG_ERROR, "could not open input file \s.\n", INPUT_FILE_NAME);
		goto end;
	}
	//输出处理
	if ((ret = open_output_file()) < 0) {
		av_log(NULL, AV_LOG_ERROR, "could not open output file \s.\n", OUTPUT_FILE_NAME);
		goto end;
	}
	//分配AVPacket
	dec_pkt = av_packet_alloc();
	if (!dec_pkt) {
		goto end;
	}
	enc_pkt = av_packet_alloc();
	if (!enc_pkt) {
		goto end;
	}
	//分配AVFrame
	frame = av_frame_alloc();
	if (!frame) {
		goto end;
	}

	//从文件读取帧
	while (av_read_frame(i_fmt_ctx, dec_pkt) >= 0) {
		//只处理视频流
		if (dec_pkt->stream_index == stream_index) {
			decode();
		}
		av_packet_unref(dec_pkt);
	}

	//写文件尾
	av_write_trailer(o_fmt_ctx);
end:
	//资源释放
	avcodec_free_context(&dec_c);
	avcodec_free_context(&enc_c);
	av_frame_free(&frame);
	av_packet_free(&dec_pkt);
	av_packet_free(&enc_pkt);
	avformat_close_input(&i_fmt_ctx);
	avformat_free_context(o_fmt_ctx);
	return 0;
}

其中有几点需要注意的:

  1. 原始视频(lh_online.h264)是一个帧率(15),即时间基(1/15),宽 * 高=512 * 288的视频
  2. 流程是 h264 -解码-> y420 -编码-> MP4(编码器ID是:AV_CODEC_ID_MPEG4)
  3. 编码需要一些默认参数,主要是(宽:width,高:height,时间基:time_base)

接下来我们看下效果:
在这里插入图片描述
可以对比一下源文件和我们编码出来的文件基本毫无差异。

但是可能大家发现了,我们编码的时候,设置的宽高和帧率和原始视频是一样的,那么不一样会怎样呢。

帧率改变,影响的是视频的总时长和播放速度,这里就不演示了,感兴趣的可以调整上面的代码的宏ENC_TIME_BASE_DEN,看下生成的视频的变化,下面主要演示下比较严重的问题。

前面的视频都是原视频,后面的视频是改变之后的视频。

  1. 编码视频的宽 > 源视频宽
    在这里插入图片描述
    这里我把宽调整到800了,你可能发现问题了,视频图片重复了。

  2. 编码视频的宽 < 源视频宽
    在这里插入图片描述
    这里我把宽调整到300了,此时视频图片被截取了。

  3. 编码视频的高 > 源视频高

在这里插入图片描述
这里我把高度调整到500,你会发现高度288一下的视频图片是不正常的,和宽的重复还不一样。

  1. 编码视频的高 < 源视频高
    在这里插入图片描述
    这里我把高度调整到150,和宽变小一样,视频高超出150的部分被截取。

看了这几个演示,相信大家应该大概有个底了,我们的视频编码的时候,宽高和原视频必须一致,不然就会出现上面4种情况当中的问题。

那么,我们就不能解决了么,比如我们就想调整宽高,或者把原视频显示的视频图片等比例放大缩小(分辨率)啊之类的,有,当然有,就是我们可以对视频进行重采样(libswscale)或者使用过滤器(libavfilter),这两个API都能实现我们想要的需求。

下篇文章我们就基于libswscale这个API来实现调整我们视频宽高相关的功能吧,这个API不仅可以调整宽高哈,libswscale里面实现了各种图像像素格式的转换,例如YUV与RGB之间的转换;以及图像大小缩放(例如640x360拉伸为1280x720)功能。而且libswscale还做了相应指令集的优化,因此它的转换效率比自己写的C语言的转换效率高很多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lhuann_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值