FFmpeg解封装(解复用)并获取本地媒体文件的信息

版权声明: https://blog.csdn.net/u014552102/article/details/81709929

  最近在学(玩)习(弄)FFmpeg,学到了使用FFmpeg对媒体文件解封装(解复用)的方法,于是写下了这篇博客作为笔记。

 | 版权声明:崔杰城,未经博主允许不得转载。  

  媒体播放器播放一个媒体文件,需要以下步骤:解协议,解封装,解码,视音频同步,如果播放本地文件则不需要解协议。其中解封装(解复用)的作用是将输入的封装格式(AVI、MP4、TS、FLV等封装格式)的数据分离成音频流压缩编码数据和视频压缩编码数据,把这些视音频以及字幕等基本流分离出来。在这个解封装的过程中,我们可以获取到媒体文件的一些基本信息。下面介绍在vs中通过调用FFmpeg的函数实现对本地媒体文件解封装并获取媒体信息的方法,开发环境是windows7 64位+vs2015,FFmpeg版本是ffmpeg-3.4.2。

  如下图所示,vs工程目录下有video1.mp4和audio1.mp3两个媒体文件,其中video1.mp4是视频文件,audio1.mp3是音频文件。

  下面我们通过代码分别对video1.mp4和audio1.mp3两个媒体文件进行解封装并进行对比。

一、对视频文件video1.mp4进行解封装

代码如下:

#include <stdio.h>

/*FFmpeg是用C语言编写的,所以如果主程序是用C++编写,则引用FFmpeg的头文件时必须加上extern "C",否则编译会报错:"无法解析的外部符号......"*/
extern "C" {
#include "libavformat/avformat.h"
}

/**
* @brief 将一个AVRational类型的分数转换为double类型的浮点数
* @param r:r为一个AVRational类型的结构体变量,成员num表示分子,成员den表示分母,r的值即为(double)r.num / (double)r.den。用这种方法表示可以最大程度地避免精度的损失
* @return 如果变量r的分母den为0,则返回0(为了避免除数为0导致程序死掉);其余情况返回(double)r.num / (double)r.den
*/
static double r2d(AVRational r)
{
	return r.den == 0 ? 0 : (double)r.num / (double)r.den;
}


int main()
{
	//const char *path = "E:\\QtProjects\\C++_ffmpeg_Qt_player\\Projects\\Test21_1A\\video1.mp4"; //需要读取的本地媒体文件绝对路径在E:\QtProjects\C++_ffmpeg_Qt_player\Projects\Test21_1A\video1.mp4下,这里由于程序在windows平台,所以字符串中的路径得用“\\”分隔
	const char *path = "video1.mp4"; //需要读取的本地媒体文件相对路径为video1.mp4,这里由于文件video1.mp4就在工程目录下,所以相对路径为video1.mp4
	//const char *path = "audio1.mp3";
	av_register_all(); //初始化所有组件,只有调用了该函数,才能使用复用器和编解码器。否则,调用函数avformat_open_input会失败,无法获取媒体文件的信息
	avformat_network_init(); //打开网络流。这里如果只需要读取本地媒体文件,不需要用到网络功能,可以不用加上这一句
	AVDictionary *opts = NULL;
	AVFormatContext *ic = NULL;    //AVFormatContext是描述一个媒体文件或媒体流的构成和基本信息的结构体                
	int re = avformat_open_input(&ic, path, NULL, &opts); //媒体打开函数,调用该函数可以获得路径为path的媒体文件的信息,并把这些信息保存到指针ic指向的空间中(调用该函数后会分配一个空间,让指针ic指向该空间)
	if (re != 0)  //如果打开媒体文件失败,打印失败原因。比如,如果上面没有调用函数av_register_all,则会打印“XXX failed!:Invaliddata found when processing input”
	{
		char buf[1024] = { 0 };
		av_strerror(re, buf, sizeof(buf) - 1);
		printf("open %s failed!:%s", path, buf);
	}
	else         //打开媒体文件成功
	{
		printf("打开媒体文件 %s 成功!\n", path);
		avformat_find_stream_info(ic, NULL); //调用该函数可以进一步读取一部分视音频数据并且获得一些相关的信息。调用avformat_open_input之后,我们无法获取到正确和所有的媒体参数,所以还得要调用avformat_find_stream_info进一步的去获取。
		printf("媒体文件名称:%s\n", ic->filename); //调用avformat_open_input读取到的媒体文件的路径/名字
		printf("视音频流的个数:%d\n", ic->nb_streams);//视音频流的个数,如果一个媒体文件既有音频,又有视频,则nb_streams的值为2。如果媒体文件只有音频,则值为1
		//printf("duration:%d\n", ic->duration);    
		printf("媒体文件的平均码率:%lldbps\n", ic->bit_rate);    //媒体文件的平均码率,单位为bps
		int tns, thh, tmm, tss;
		tns = (ic->duration) / AV_TIME_BASE;
		thh = tns / 3600;
		tmm = (tns % 3600) / 60;
		tss = (tns % 60);
		printf("媒体文件总时长:%d时%d分%d秒\n", thh, tmm, tss); //通过上述运算,可以得到媒体文件的总时长

		printf("\n");
		for (int i = 0; i < ic->nb_streams; i++) //通过遍历的方式读取媒体文件视频和音频的信息,新版本的FFmpeg新增加了函数av_find_best_stream,也可以取得同样的效果,但这里为了兼容旧版本还是用这种遍历的方式
		{
			AVStream *as = ic->streams[i];
			if (AVMEDIA_TYPE_AUDIO == as->codecpar->codec_type) //如果是音频流,则打印音频的信息
			{
				printf("音频信息:\n");
				printf("index:%d\n", as->index);  //如果一个媒体文件既有音频,又有视频,则音频index的值一般为1。但该值不一定准确,所以还是得通过as->codecpar->codec_type判断是视频还是音频
				printf("音频采样率:%dHz\n", as->codecpar->sample_rate); //音频编解码器的采样率,单位为Hz
				if (AV_SAMPLE_FMT_FLTP == as->codecpar->format)   //音频采样格式
				{
					printf("音频采样格式:AV_SAMPLE_FMT_FLTP\n");
				}
				else if (AV_SAMPLE_FMT_S16P == as->codecpar->format)
				{
					printf("音频采样格式:AV_SAMPLE_FMT_S16P\n");
				}
				printf("音频信道数目:%d\n", as->codecpar->channels); //音频信道数目
				if (AV_CODEC_ID_AAC == as->codecpar->codec_id)   //音频压缩编码格式
				{
					printf("音频压缩编码格式:AAC\n");       
				}
				else if (AV_CODEC_ID_MP3 == as->codecpar->codec_id)
				{
					printf("音频压缩编码格式:MP3\n");        
				}
				int DurationAudio = (as->duration) * r2d(as->time_base); //音频总时长,单位为秒。注意如果把单位放大为毫秒或者微妙,音频总时长跟视频总时长不一定相等的,所以得进行音视频同步,最后得到媒体文件的总时长
				printf("音频总时长:%d时%d分%d秒\n", DurationAudio / 3600, (DurationAudio % 3600) / 60, (DurationAudio % 60)); //将音频总时长转换为时分秒的格式打印到控制台上
				printf("\n");
			}
			else if (AVMEDIA_TYPE_VIDEO == as->codecpar->codec_type)  //如果是视频流,则打印视频的信息
			{
				printf("视频信息:\n");
				printf("index:%d\n", as->index);   //如果一个媒体文件既有音频,又有视频,则视频index的值一般为0。但该值不一定准确,所以还是得通过as->codecpar->codec_type判断是视频还是音频
				printf("视频帧率:%lffps\n", r2d(as->avg_frame_rate)); //视频帧率,单位为fps,表示每秒出现多少帧
				if (AV_CODEC_ID_MPEG4 == as->codecpar->codec_id) //视频压缩编码格式
				{
					printf("视频压缩编码格式:MPEG4\n");
				}
				printf("帧宽度:%d 帧高度:%d\n", as->codecpar->width, as->codecpar->height); //视频帧宽度和帧高度
				int DurationVideo = (as->duration) * r2d(as->time_base); //视频总时长,单位为秒。注意如果把单位放大为毫秒或者微妙,音频总时长跟视频总时长不一定相等的,所以得进行音视频同步,最后得到媒体文件的总时长
				printf("视频总时长:%d时%d分%d秒\n", DurationVideo / 3600, (DurationVideo % 3600) / 60, (DurationVideo % 60)); //将视频总时长转换为时分秒的格式打印到控制台上
				printf("\n");
			}
		}

		//av_dump_format(ic, 0, path, 0);
	}
	if (ic)
	{
		avformat_close_input(&ic); //关闭一个AVFormatContext,和函数avformat_open_input()成对使用
	}
	getchar(); //加上这一句,防止程序打印完信息就马上退出了
	return 0;
}

运行上述代码,可以得到控制台输出结果如下:

通过对比程序控制台输出和video1.mp4在windows7下获得的信息,如下图所示,我们发现得到的信息是基本一样的,证明了上述代码的正确性

二、对音频文件audio1.mp3进行解封装

如下图所示,把上述代码中的const char *path = "video1.mp4"这一句换成const char *path = "audio1.mp3"

重新编译,运行,得到控制台输出结果如下:

通过对比程序控制台输出和audio1.mp3在windows7下获得的信息,如下图所示,我们发现得到的信息也是基本一样的,证明了上述代码的正确性

三、总结

从上述两次解封装我们可以看出,MP4文件是视频文件,MP3文件是音频文件,所以MP4文件的视音频流的个数是2,既有视频信息,也有音频信息。MP3文件的视音频流的个数是1,只有音频信息。

上面演示的源码,整个vs工程还有演示用的媒体文件可以在https://download.csdn.net/download/u014552102/10607810下载,欢迎各位下载

猜你喜欢

转载自blog.csdn.net/u014552102/article/details/81709929
今日推荐