当前位置: 首页 > article >正文

FFmepg入门:最简单的视频重编码工具

FFmepg入门:最简单的视频重编码工具

最近工作真的巨忙,上周给我忙晕了,好久没来更新FFmpeg的相关学习了,最近简单写一个视频的重编码工具;可以根据输入的宽高以及编码器输出到指定的文件中进行预览。接下来就进入正文吧。

整体思路

  1. 解析输入的参数,-i 后面是输入的文件,-vc 指定视频编码器, -w 指定视频宽,-h 指定视频高,-o 输出文件名
  2. 对输入的文件进行解析,参考流程图,读出format上下文,codec上下文,编码器,以及帧率等基本参数
  3. 根据输入参数更新 输出编码器,输出帧大小
  4. 逐帧读取 解码后进行编码;
  5. 写入文件
    在这里插入图片描述
    我们将上述过程拆为三个部分进行讲解,然后看看每个部分都做了哪些工作;

1. 输入流准备

第一步就是准备输入流,我们结合流程图和代码讲解。
首先指定需要使用的参数

char* 	input_file = "/Users/chenhuaiyi/workspace/ffmpeg/files/恋爱.mp4";
char* 	codec_name = "libx264";
int		width = 896;
int 	height = 414;
char*	output_file = "/Users/chenhuaiyi/workspace/ffmpeg/files/恋爱_重编码.mp4";

接下来初始化一些上下文参数(这里我就不赘述了,学过前面的视频播放的同学对固定的哪些实体都很熟系了吧)

avFormatContext

// 1. 打开视频文件,获取格式上下文
	if (avformat_open_input(&formatCtx, input_file, NULL, NULL) < 0) {
		printf("打开文件%s失败", input_file);
	}

video_stream

// 3. 找到对应的视频流
	video_stream = av_find_best_stream(formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
	if (video_stream == -1) {
		printf("该文件没有视频流");
	}

avCodecContext

// 4. 将视频流编码参数写入上下文
	codeCtx = avcodec_alloc_context3(codec);
	avcodec_parameters_to_context(codeCtx, formatCtx->streams[video_stream]->codecpar);

avcodec

// 5. 查找流的解码器
	codec = avcodec_find_decoder(codeCtx->codec_id);
	
	// 6. 打开流的解码器
	if (avcodec_open2(codeCtx, codec, NULL) < 0) {
		printf("打开解码器失败");
	}

2. 输出流准备

输出流可能相比较比较陌生,实际上和输入流准备是一个相反的过程。
输入formatContext ---- 输出formatContext
输入avcodecContext ---- 输出avcodecContext
输入stream ---- 输出stream
输入解码器avcodec — 输出编码器codec

另外,还要加一个设置AVIOContext的过程。

avcodec输出编码器

output_codec = avcodec_find_encoder_by_name(codec_name);
	if (!output_codec) {
		printf("输入的编码器有误");
	}

avcodecContext输出上下文
这里我说一下,这下面的参数一开始的时候我也不清楚需要设置哪些,也是看资料和尝试试出来的。

这下面几个应该是必须设置的

width和height:不必多说,这个是视频的大小
pix_fmt:帧格式
time_base:时间基,这个非常重要,关系到后续pts和dts
bit_rate:码率,没有码率就不知道怎么传输
framerate:帧率,帧率就是和视频播放速度挂钩,本质上和time_base就是一个东西

// 2. 输出codec上下文
	output_codeCtx = avcodec_alloc_context3(output_codec);
	
	// 3. 输出参数设置(宽,高,格式,帧率)
	output_codeCtx->width 		= 	width;
	output_codeCtx->height 		= 	height;
	output_codeCtx->pix_fmt 	= 	AV_PIX_FMT_YUV420P;
	output_codeCtx->time_base 	= 	av_inv_q(formatCtx->streams[video_stream]->avg_frame_rate);
	output_codeCtx->bit_rate	=  	400000;
	output_codeCtx->framerate	=   formatCtx->streams[video_stream]->r_frame_rate;;
	// output_codeCtx->max_b_frames=	1;
	// output_codeCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

打开编码器

// 4. 打开编码器
	if (avcodec_open2(output_codeCtx, output_codec, NULL) < 0){
		printf("打开编码器有误");
	}

输出 output avformatContext

// 5. 设置输出formatCtx
	avformat_alloc_output_context2(&output_formatCtx, NULL, NULL, output_file);

输出stream

// 5. 添加输出流到format codec上下文
	output_stream = avformat_new_stream(output_formatCtx, output_codec);
	
// 6. 设置流信息
	if (avcodec_parameters_from_context(output_stream->codecpar, output_codeCtx)) {
		printf("输出codec上下文拷贝失败");
	}

AVIOContext
这个实体有点陌生
看原码注解:大概就是在复用过程中,要执行一下,确保正确写入文件头

muxing: set by the user before avformat_write_header().
The caller must take care of closing / freeing the IO context.

	// 7. 设置IO
	if (!(output_formatCtx->oformat->flags & AVFMT_NOFILE)) {
		if (avio_open(&output_formatCtx->pb, output_file, AVIO_FLAG_WRITE) < 0) {
			fprintf(stderr, "Could not open output file '%s'\n", output_file);
		}
	}

写入文件头

// 8. 写入文件头
	if (avformat_write_header(output_formatCtx, NULL) < 0) {
		fprintf(stderr, "Error occurred when opening output file\n");
	}

3. 编解码转化

这个流程实际上非常简单。先明确两个事情

  • 编码后、解码前的数据为packet
  • 编码前、解码后的数据为frame

那么编解码转化就可以从下面五步组成了(详细见代码详情)

  1. 从input中取出packet: av_read_frame
  2. 将packet送入解码器: avcodec_send_packet
  3. 从解码器中获得解码后的frame: avcodec_receive_frame
  4. 格式转化:sws_scale
  5. 将frame送入编码器:avcodec_send_frame
  6. 从编码器中获取编码后的packet:avcodec_receive_packet
  7. 将packet写入output:av_interleaved_write_frame

4. 收尾工作

写入文件:

/**
	 写入文件
	 */
	av_write_trailer(output_formatCtx);

释放空间

源代码

//
//  main.c
//  decoder
//  音视频的编解码知识学习
//
//  Created by chenhuaiyi on 2025/3/13.
//

#include <stdio.h>
// ffmpeg
#include "libavcodec/avcodec.h"
#include "libswresample/swresample.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "libavutil/time.h"
#include "libavutil/channel_layout.h"
// SDL
#include "SDL.h"
#include "SDL_thread.h"


int main(int argc, const char * argv[]) {
	
	/**
	 功能:输入一个视频,并指定视频的编码的参数,以及输出的文件,最终生产新的文件
	 1. 解析输入的参数,-i 后面是输入的文件,-vc 指定视频编码器, -w 指定视频宽,-h 指定视频高,-o 输出文件名
	 2. 对输入的文件进行解析,参考流程图,读出format上下文,codec上下文,编码器,以及帧率等基本参数
	 3. 根据输入参数更新 输出编码器,输出帧大小
	 4. 逐帧读取 解码后进行编码;
	 5. 写入文件
	 */
	
	char* 	input_file = "/Users/chenhuaiyi/workspace/ffmpeg/files/恋爱.mp4";
	char* 	codec_name = "libx264";
	int		width = 896;
	int 	height = 414;
	char*	output_file = "/Users/chenhuaiyi/workspace/ffmpeg/files/恋爱_重编码.mp4";
	
	AVFormatContext*	formatCtx = avformat_alloc_context();
	AVCodecContext*		codeCtx;
	const AVCodec*		codec;
	int					video_stream = -1;
	
	const AVCodec*		output_codec;
	AVFormatContext*	output_formatCtx;
	AVCodecContext*		output_codeCtx;
	AVStream*			output_stream;
	
	struct SwsContext*	sws_ctx;
	
	
	/**  ------------------------   输入流准备工作      ---------------------- **/
	// 1. 打开视频文件,获取格式上下文
	if (avformat_open_input(&formatCtx, input_file, NULL, NULL) < 0) {
		printf("打开文件%s失败", input_file);
	}
	
	// 2. 对文件探测流信息
	if (avformat_find_stream_info(formatCtx, NULL) < 0) {
		printf("文件探测流信息失败");
	}
	
	// 3. 找到对应的视频流
	video_stream = av_find_best_stream(formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
	if (video_stream == -1) {
		printf("该文件没有视频流");
	}
	
	// 4. 将视频流编码参数写入上下文
	codeCtx = avcodec_alloc_context3(codec);
	avcodec_parameters_to_context(codeCtx, formatCtx->streams[video_stream]->codecpar);

	// 5. 查找流的解码器
	codec = avcodec_find_decoder(codeCtx->codec_id);
	
	// 6. 打开流的解码器
	if (avcodec_open2(codeCtx, codec, NULL) < 0) {
		printf("打开解码器失败");
	}
	
	av_dump_format(formatCtx, 0, input_file, 0);
	
	/**  ------------------------   输出流准备工作      ---------------------- **/
	
	// 1. 找到编码器
	output_codec = avcodec_find_encoder_by_name(codec_name);
	if (!output_codec) {
		printf("输入的编码器有误");
	}
	
	// 2. 输出codec上下文
	output_codeCtx = avcodec_alloc_context3(output_codec);
	
	// 3. 输出参数设置(宽,高,格式,帧率)
	output_codeCtx->width 		= 	width;
	output_codeCtx->height 		= 	height;
	output_codeCtx->pix_fmt 	= 	AV_PIX_FMT_YUV420P;
	output_codeCtx->time_base 	= 	av_inv_q(formatCtx->streams[video_stream]->avg_frame_rate);
	output_codeCtx->bit_rate	=  	400000;
	output_codeCtx->framerate	=   formatCtx->streams[video_stream]->r_frame_rate;;
//	output_codeCtx->max_b_frames=	1;
//	output_codeCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
	
	// 4. 打开编码器
	if (avcodec_open2(output_codeCtx, output_codec, NULL) < 0){
		printf("打开编码器有误");
	}
	
	// 5. 设置输出formatCtx
	avformat_alloc_output_context2(&output_formatCtx, NULL, NULL, output_file);
	
	// 5. 添加输出流到format codec上下文
	output_stream = avformat_new_stream(output_formatCtx, output_codec);
	
	// 6. 设置流信息
	if (avcodec_parameters_from_context(output_stream->codecpar, output_codeCtx)) {
		printf("输出codec上下文拷贝失败");
	}
	
	// 7. 设置IO
	if (!(output_formatCtx->oformat->flags & AVFMT_NOFILE)) {
		if (avio_open(&output_formatCtx->pb, output_file, AVIO_FLAG_WRITE) < 0) {
			fprintf(stderr, "Could not open output file '%s'\n", output_file);
		}
	}
	
	// 8. 写入文件头
	if (avformat_write_header(output_formatCtx, NULL) < 0) {
		fprintf(stderr, "Error occurred when opening output file\n");
	}
	
	av_dump_format(output_formatCtx, 0, output_file, 1);
	
	
	
	/**  ------------------------   编解码转化     ---------------------- **/
	AVPacket packet;
	AVFrame* frame = av_frame_alloc();
	AVFrame* output_frame = av_frame_alloc();
	output_frame->width	= width;
	output_frame->height = height;
	output_frame->format = AV_PIX_FMT_YUV420P;
	av_image_alloc(output_frame->data, output_frame->linesize, width, height, AV_PIX_FMT_YUV420P, 1);
	
	sws_ctx = sws_getContext(codeCtx->width,
							 codeCtx->height,
							 codeCtx->pix_fmt,
							 width, height,
							 AV_PIX_FMT_YUV420P,
							 SWS_BILINEAR,
							 NULL, NULL, NULL);
	// 开始工作
	while (av_read_frame(formatCtx, &packet) >= 0) {
		
		if (packet.stream_index == video_stream) {
			/**
			 送入解码器, 解码
			 packet->frame
			 */
			int ret = avcodec_send_packet(codeCtx, &packet);
			if (ret < 0) {
				printf("packet resolve error!");
				break;
			}
			
			while (!avcodec_receive_frame(codeCtx, frame)) {
				// 上下文转化
				sws_scale(sws_ctx,
						  (uint8_t const * const *)frame->data,
						  frame->linesize,
						  0,
						  frame->height,
						  output_frame->data,
						  output_frame->linesize);
				
				output_frame->pts = av_rescale_q(frame->pts, av_inv_q(formatCtx->streams[video_stream]->avg_frame_rate), output_codeCtx->time_base);
				
				/**
				 送入编码器,编码
				 frame->packet
				 */
				ret = avcodec_send_frame(output_codeCtx, output_frame);
				if (ret < 0) {
					printf("frame resolve error!\n");
					break;
				}
				
				while (!avcodec_receive_packet(output_codeCtx, &packet)) {
					packet.stream_index = output_stream->index;
					av_packet_rescale_ts(&packet, formatCtx->streams[video_stream]->time_base, output_formatCtx->streams[0]->time_base);
					av_interleaved_write_frame(output_formatCtx, &packet);
					av_packet_unref(&packet);
				}
				
			}
		}
		av_packet_unref(&packet);
	}
	
	/**
	 写入文件
	 */
	av_write_trailer(output_formatCtx);
	
	sws_freeContext(sws_ctx);
	av_frame_free(&frame);
	av_frame_free(&output_frame);
	avcodec_free_context(&codeCtx);
	avcodec_free_context(&output_codeCtx);
	avformat_close_input(&formatCtx);
	avio_closep(&output_formatCtx->pb);
	avformat_free_context(output_formatCtx);
	
	return 0;
}


http://www.kler.cn/a/599179.html

相关文章:

  • MyBatis基础一
  • 无人船 | 基于ROS的轻量级多无人艇自主导航仿真框架
  • Git 钩子:特定操作脚本
  • GithubPages+自定义域名+Cloudfare加速+浏览器收录(2025最新排坑)
  • unix网络编程
  • 【XPipe】一款好用的SSH工具
  • 丐版插入selectdb模拟
  • Debian,Ubuntu,设置/etc/vim/vimrc.tiny解决:上下左右变成ABCD,backspace退格键失效的问题
  • netplan是如何操控systemd-networkd的? 笔记250324
  • 常见框架漏洞攻略-ThinkPHP篇
  • 搜广推校招面经五十七
  • C语言入门教程100讲(40)文件定位
  • search_fields与filterset_fields的使用
  • 【参考资料 II】C 运算符大全:算术、关系、赋值、逻辑、条件、指针、符号、成员、按位、混合运算符
  • 多线程编程
  • 模糊数学 | 模型 / 集合 / 关系 / 矩阵
  • endnote相关资料记录
  • V8引擎源码编译踩坑实录
  • vue3 如何清空 let arr = reactive([])
  • React Native集成到现有原生Android应用