FFmpeg入门教程:常见API使用及C语言开发


因为项目原因接触使用ffmpeg,当时是使用c#调用ffmpeg,通过指令对视频进行转码。指令的方式比较容易上手,但是如果涉及到复杂点的音视频二次开发,如果没有对音视频相关概念有一定的了解的话,感觉很难理解代码的含义和逻辑。由于兴趣最近开始摸索学习ffmpeg API的相关使用。

相关概念理解

1、多媒体文件的基本概念

  • 多媒体文件就是一个容器
  • 在容器中有很多流(Stream/Track)
  • 每种流是由不同的编码器编码的
  • 从流中读出的数据称为包
  • 在一个包中包含着一个或多个帧

2、音频的量化编码

  • 模拟信号到数字信号的转换过程(连续 ->离散、不连续的过程才能被计算机使用
  • 模拟信号->采样->量化->编码->数字信号
  • 量化的基本概念: 采样大小:一个采样用多少个bit存放,常用的是16bit
  • 采样率:也就是采样频率(1秒采样次数),一般采样率有8kHz、16kHz、32kHz、44.1kHz、48kHz等,采样频率越高,声音的还原就越真实越自然,当然数据量就越大
  • 声道数:为了播放声音时能够还原真实的声场,在录制声音时在前后左右几个不同的方位同时获取声音,每个方位的声音就是一个声道。声道数是声音录制时的音源数量或回放时相应的扬声器数量,有单声道、双声道、多声道
  • 码率:也叫比特率,是指每秒传送的bit数。单位为 bps(Bit Per Second),比特率越高,每秒传送数据就越多,音质就越好。
码率计算公式:
码率 = 采样率 * 采样大小 * 声道数
比如采样率44.1kHz,采样大小为16bit,双声道PCM编码的WAV文件:
码率=44.1hHz*16bit*2=1411.2kbit/s。
录制1分钟的音乐的大小为(1411.2 * 1000 * 60) / 8 / 1024 / 1024 = 10.09M。

3、时间基

  • time_base是用来度量时间的,比如time_base = {1,40},它的意思是将1秒分成40段,那么每段就是1/40秒,在FFmpeg中函数av_q2d(time_base)就是用来计算一段的时间的,计算结果就是1/40秒。比如一个视频中某一帧的pts是800,也就是说有800段,那么它表示多少秒呢,pts av_q2d(time_base)=800(1/40)=20s,也就是说要在第20秒的时候播放这一帧 时间基的转换。不同格式时间基的不同。
  • PTS是渲染用的时间戳。DTS是解码时间戳。
    音频的PTS:以AAC音频为例,一个AAC原始帧包含一段时间内1024个采样及相关数据,也就是说一帧有1024个样本,如果采样率为44.1kHz(1秒采集44100个样本),所以aac音频1秒有44100/1024帧,每一帧的持续时间是1024/44100秒,由此可以计算出每一帧的pts。
  • 转换公式
timestamp() = pts * av_q2d(st->time_base)//计算该帧在视频音频中的位置
time() = st->duration * av_q2d(st->time_base)//计算视频音频中的长度
st  为AVStream流指针
时间基转换公式
timestamp(ffmpeg内部时间戳) = AV_TIME_BASE * time()
time() = AV_TIME_BASE_Q * timestamp(ffmpeg内部时间戳)//timestamp就算是PTS/DTS

环境配置

相关下载

进入官网 分别下载Dev和Shared压缩包。下载注意平台的选择对应。
将dev中include、lib文件分别解压到如下目录下。将shared中dll文件拷贝到项目Debug目录下,分则会出现报错。
在这里插入图片描述

环境配置

在VS中创建c/c++项目,右键项目属性
在这里插入图片描述
在这里插入图片描述
在其中添加如下dll文件

avcodec.lib; avformat.lib; avutil.lib; avdevice.lib; avfilter.lib; postproc.lib; swresample.lib; swscale.lib
libavcodec  提供一系列编码器的实现
libavformat 实现在流协议,容器格式及其IO访问
libavutil 包括了hash器、解码器和各种工具函数
libavfilter 提供了各种音视频过滤器
libavdevice 提供了访问捕获设备和回放设备的接口
libswresample 实现了混音和重采样
libswscale 实现了色彩转换和缩放功能

在这里插入图片描述

测试

本人是使用VS2017作为编辑器进行开发。

#include<stdio.h>
#include <iostream>
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
}
int main(int argc, char* argv[]) {
	printf(avcodec_configuration());
	system("pause");
	return 0;
}

在这里插入图片描述

开发案例

实现对两组视频的视频音频混搭,一个类似小咖秀的功能。

处理逻辑及使用API

  • API注册
  • 创建输入、输出上下文
  • 获取输入音频流、输入视频流
  • 创建输出音频流、输出视频流
  • 将输入流参数拷贝到输出流参数
  • 判断文件大小,确定输出文件长度
  • 写入头信息
  • 初始化包、分别读取音视频数据并写入文件
    在这里插入图片描述

相关代码


#include<stdio.h>
#include <iostream>
extern "C" {
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavformat/avio.h"
#include <libavutil/log.h>
#include <libavutil/timestamp.h>
}
#define ERROR_STR_SIZE 1024
int main(int argc, char const *argv[])
{
	int ret = -1;
	int err_code;
	char errors[ERROR_STR_SIZE];
	AVFormatContext *ifmt_ctx1 = NULL;
	AVFormatContext *ifmt_ctx2 = NULL;
	AVFormatContext *ofmt_ctx = NULL;
	AVOutputFormat *ofmt = NULL;
	AVStream *in_stream1 = NULL;
	AVStream *in_stream2 = NULL;
	AVStream *out_stream1 = NULL;
	AVStream *out_stream2 = NULL;
	int audio_stream_index = 0;
	int vedio_stream_indes = 0;
	// 文件最大时长,保证音频和视频数据长度一致
	double max_duration = 0;
	AVPacket pkt;
	int stream1 = 0, stream2 = 0;
	av_log_set_level(AV_LOG_DEBUG);
	//打开两个输入文件
	if ((err_code = avformat_open_input(&ifmt_ctx1, "C:\\Users\\haizhengzheng\\Desktop\\meta.mp4", 0, 0)) < 0) {
		av_strerror(err_code, errors, ERROR_STR_SIZE);
		av_log(NULL, AV_LOG_ERROR, "Could not open src file, %s, %d(%s)\n",
			"C:\\Users\\haizhengzheng\\Desktop\\meta.mp4", err_code, errors);
		goto END;
	}
	if ((err_code = avformat_open_input(&ifmt_ctx2, "C:\\Users\\haizhengzheng\\Desktop\\mercury.mp4", 0, 0)) < 0) {
		av_strerror(err_code, errors, ERROR_STR_SIZE);
		av_log(NULL, AV_LOG_ERROR,
			"Could not open the second src file, %s, %d(%s)\n",
			"C:\\Users\\haizhengzheng\\Desktop\\mercury.mp4", err_code, errors);
		goto END;
	}
	//创建输出上下文
	if ((err_code = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, "C:\\Users\\haizhengzheng\\Desktop\\amv.mp4")) < 0) {
		av_strerror(err_code, errors, ERROR_STR_SIZE);
		av_log(NULL, AV_LOG_ERROR, "Failed to create an context of outfile , %d(%s) \n",
			err_code, errors);
	}
	ofmt = ofmt_ctx->oformat;//获得输出文件的格式信息
	// 找到第一个参数里最好的音频流和第二个文件中的视频流下标
	audio_stream_index = av_find_best_stream(ifmt_ctx1, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);//获取音频流下标
	vedio_stream_indes = av_find_best_stream(ifmt_ctx2, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);//获取视频流下标
	// 获取第一个文件中的音频流
	in_stream1 = ifmt_ctx1->streams[audio_stream_index];
	stream1 = 0;
	// 创建音频输出流
	out_stream1 = avformat_new_stream(ofmt_ctx, NULL);
	if (!out_stream1) {
		av_log(NULL, AV_LOG_ERROR, "Failed to alloc out stream!\n");
		goto END;
	}
	// 拷贝流参数
	if ((err_code = avcodec_parameters_copy(out_stream1->codecpar, in_stream1->codecpar)) < 0) {
		av_strerror(err_code, errors, ERROR_STR_SIZE);
		av_log(NULL, AV_LOG_ERROR,
			"Failed to copy codec parameter, %d(%s)\n",
			err_code, errors);
	}
	out_stream1->codecpar->codec_tag = 0;
	// 获取第二个文件中的视频流
	in_stream2 = ifmt_ctx2->streams[vedio_stream_indes];
	stream2 = 1;

	// 创建视频输出流
	out_stream2 = avformat_new_stream(ofmt_ctx, NULL);
	if (!out_stream2) {
		av_log(NULL, AV_LOG_ERROR, "Failed to alloc out stream!\n");
		goto END;
	}
	// 拷贝流参数
	if ((err_code = avcodec_parameters_copy(out_stream2->codecpar, in_stream2->codecpar)) < 0) {
		av_strerror(err_code, errors, ERROR_STR_SIZE);
		av_log(NULL, AV_LOG_ERROR,
			"Failed to copy codec parameter, %d(%s)\n",
			err_code, errors);
		goto END;
	}
	out_stream2->codecpar->codec_tag = 0;
	//输出流信息
	av_dump_format(ofmt_ctx, 0, "C:\\Users\\haizhengzheng\\Desktop\\amv.mp4", 1);

	// 判断两个流的长度,确定最终文件的长度    time(秒) = st->duration * av_q2d(st->time_base)   duration 就是dts\pts     av_q2d()就是倒数
	if (in_stream1->duration * av_q2d(in_stream1->time_base) > in_stream2->duration * av_q2d(in_stream2->time_base)) {
		max_duration = in_stream2->duration * av_q2d(in_stream2->time_base);
	}
	else {
		max_duration = in_stream1->duration * av_q2d(in_stream1->time_base);
	}
	//打开输出文件
	if (!(ofmt->flags & AVFMT_NOFILE)) {
		if ((err_code = avio_open(&ofmt_ctx->pb, "C:\\Users\\haizhengzheng\\Desktop\\amv.mp4", AVIO_FLAG_WRITE)) < 0) {
			av_strerror(err_code, errors, ERROR_STR_SIZE);
			av_log(NULL, AV_LOG_ERROR,
				"Could not open output file, %s, %d(%s)\n",
				"C:\\Users\\haizhengzheng\\Desktop\\amv.mp4", err_code, errors);
			goto END;
		}
	}
	//写头信息
	avformat_write_header(ofmt_ctx, NULL);
	av_init_packet(&pkt);
	// 读取音频数据并写入输出文件中
	while (av_read_frame(ifmt_ctx1, &pkt) >= 0) {
		// 如果读取的时间超过了最长时间表示不需要该帧,跳过
		if (pkt.pts * av_q2d(in_stream1->time_base) > max_duration) {
			av_packet_unref(&pkt);
			continue;
		}
		// 如果是我们需要的音频流,转换时间基后写入文件  av_rescale_q_rnd()时间基转换函数
		if (pkt.stream_index == audio_stream_index) {
			pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream1->time_base, out_stream1->time_base,//获取包的PTS\DTS\duration
				(AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
			pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream1->time_base, out_stream1->time_base,
				(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
			pkt.duration = av_rescale_q(max_duration, in_stream1->time_base, out_stream1->time_base);
			pkt.pos = -1;
			pkt.stream_index = stream1;
			av_interleaved_write_frame(ofmt_ctx, &pkt);
			av_packet_unref(&pkt);
		}
	}


	// 读取视频数据并写入输出文件中
	while (av_read_frame(ifmt_ctx2, &pkt) >= 0) {

		// 如果读取的时间超过了最长时间表示不需要该帧,跳过
		if (pkt.pts * av_q2d(in_stream2->time_base) > max_duration) {
			av_packet_unref(&pkt);
			continue;
		}
		// 如果是我们需要的视频流,转换时间基后写入文件
		if (pkt.stream_index == vedio_stream_indes) {
			pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream2->time_base, out_stream2->time_base,
				(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
			pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream2->time_base, out_stream2->time_base,
				(AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
			pkt.duration = av_rescale_q(max_duration, in_stream2->time_base, out_stream2->time_base);
			pkt.pos = -1;
			pkt.stream_index = stream2;
			av_interleaved_write_frame(ofmt_ctx, &pkt);
			av_packet_unref(&pkt);
		}
	}
	//写尾信息
	av_write_trailer(ofmt_ctx);
		ret = 0;
END:
	// 释放内存
	if (ifmt_ctx1) {
		avformat_close_input(&ifmt_ctx1);
	}

	if (ifmt_ctx2) {
		avformat_close_input(&ifmt_ctx2);
	}

	if (ofmt_ctx) {
		if (!(ofmt->flags & AVFMT_NOFILE)) {
			avio_closep(&ofmt_ctx->pb);
		}
		avformat_free_context(ofmt_ctx);
	}
}

参考文章

音频基础知识
代码参考

  • 2
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值