用FFmpeg获取视频流+音频流的信息(编码格式、分辨率、帧率、播放时长...)

我们经常需要知道一个媒体文件所包含的媒体流的信息,比如文件格式、播放时长、码率、视音频编码格式,视频分辨率,帧率,音频属性等信息。如何使用FFmpeg API获取这些信息呢?下面我会给出一个完善的类,这个类封装了FFmpeg读取文件信息的相关的API,读者只需要调类的方法就可以获得相关的信息。

这个类能够读取媒体文件的哪些信息呢?假如我们给出一个媒体文件(MP4,AVI,MKV。。。),里面至少要包含一个音频轨或视频轨,则调用该类的方法可以获得的信息如下:

媒体容器封装格式
文件播放时长
文件平均码率(视频+音频)
视频属性(编码器名称、视频分辨率、帧率、视频帧数、编码码率)
音频属性(编码器名称、采样率、声道数、编码码率)
 

 

下面先附上这个类(FFMediaInfoReader)的代码,后面再逐个部分讲解一下。

类头文件代码:

#ifndef _FFMediaInfoReader_H
#define _FFMediaInfoReader_H
 
#include <string>
using namespace std;
 
#ifdef __cplusplus
extern "C" {
#endif 
 
#ifdef HAVE_AV_CONFIG_H
#undef HAVE_AV_CONFIG_H
#endif
 
#include "./include/libavcodec/avcodec.h"
#include "./include/libavutil/mathematics.h"
#include "./include/libavutil/avutil.h"
#include "./include/libswscale/swscale.h"
#include "./include/libavutil/fifo.h"
#include "./include/libavformat/avformat.h"
#include "./include/libavutil/opt.h"
#include "./include/libavutil/error.h"
#include "./include/libavutil/pixfmt.h"
#include "./include/libswresample/swresample.h"  
#ifdef __cplusplus
}
#endif
 
#pragma comment( lib, "avcodec.lib")
#pragma comment( lib, "avutil.lib")
#pragma comment( lib, "avformat.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment( lib, "swscale.lib" )
 
#define PIX_FMT_BGR24    AV_PIX_FMT_BGR24
#define PIX_FMT_YUV420P  AV_PIX_FMT_YUV420P
 
#define nullptr NULL
 
class FFMediaInfoReader
{
public:
    FFMediaInfoReader();
    virtual ~FFMediaInfoReader();
 
    BOOL OpenFileStream(const char* szFilePath);
    void CloseFileStream();
 
	//获取视频分辨率
	void GetVideoSize(int & width, int & height) 
	{
		width  = m_width;
		height = m_height;
	}
 
	string GetMediaFormatName(); //获取媒体封装格式描述
 
	//获取媒体轨道数目
	int    GetTrackNum()
	{
		int n = 0;
 
		if(HasVideoTrack())
			n += 1;
		if(HasAudioTrack())
			n += 1;
		return n;
	}
 
	//是否有视频流
	bool    HasVideoTrack()
	{
		if( m_videoStreamIndex == -1)
			return false;
 
		if(m_width > 0 && m_height > 0)
			return true;
 
		return false;
	}
 
	//是否有音频流
	bool    HasAudioTrack()
	{
		if(m_audioStreamIndex == -1)
			return false;
 
		return true;
	}
 
	//获取视频帧数
	int  GetVideoFrameNumber()
	{
		return m_video_frame_count;
	}
 
	//获取文件总的码率
	int GetFormatBitrate()
	{
		if(m_inputAVFormatCxt == NULL)
			return -1;
 
		return m_inputAVFormatCxt->bit_rate;
	}
 
	//文件播放时长(单位:秒)
	int  GetFileDuration()
	{
		if(m_inputAVFormatCxt == NULL)
			return -1;
 
		return (m_inputAVFormatCxt->duration)/1000000; //以微秒为单位,转换为秒为单位
	}
 
	string GetVideoCodecName()
	{
		return m_vcodec_name;
	}
 
	string GetAudioCodecName()
	{
		return m_acodec_name;
	}
 
private:
 
    BOOL openMediaFile();
    void closeMediaFile();
 
private:
 
    string m_filePath;
 
    AVFormatContext* m_inputAVFormatCxt;
 
	int m_videoStreamIndex;
	int m_audioStreamIndex;
 
	string   m_vcodec_name;
	string   m_acodec_name;
 
    char m_tmpErrString[64];
    bool m_stop_status;
 
	BOOL   m_bInited;
 
	int m_width, m_height; //视频分辨率
    int   m_video_frame_count; //视频总帧数
	int   m_frame_rate; //视频帧率
 
	int  m_audio_samplerate;
	int  m_audio_channels;
   
};
 
#endif // _FFMediaInfoReader_H

 

 

源文件代码:

#include "stdafx.h"
#include "FFMediaInfoReader.h"
#include <sstream>
//#include <mmsystem.h>
 
string to_string(int n)
{
	std::ostringstream stm;
	string str;
	stm << n;
	str = stm.str();
	//std::cout << str << std::endl;
	return str;
}
 
 
//////////////////////////////////////////////////////////////
 
FFMediaInfoReader::FFMediaInfoReader()
{
    m_stop_status = false;
 
    m_inputAVFormatCxt = nullptr;
 
    m_videoStreamIndex = -1;
	m_audioStreamIndex = -1;
 
	m_bInited = FALSE;
    m_width = m_height = 0;
    m_frame_rate = 25;
    m_video_frame_count = 0;
	m_audio_samplerate = 0;
	m_audio_channels = 0;
 
	 /* register all codecs, demux and protocols */
    avcodec_register_all();
    av_register_all();
}
 
FFMediaInfoReader::~FFMediaInfoReader()
{
	CloseFileStream();
}
 
 
BOOL FFMediaInfoReader::OpenFileStream(const char* szFilePath)
{
    m_filePath   = szFilePath;
    m_video_frame_count = 0;
    m_videoStreamIndex = -1;
	m_audioStreamIndex = -1;
	m_vcodec_name = "";
	m_acodec_name = "";
	m_width = m_height = 0;
    m_audio_samplerate = 0;
	m_audio_channels = 0;
    m_video_frame_count = 0;
    m_bInited = FALSE;
 
	return openMediaFile();
}
 
 
void FFMediaInfoReader::CloseFileStream()
{
    m_stop_status = true;
 
    closeMediaFile();
    m_bInited = FALSE;
}
 
 
//打开输入文件
BOOL FFMediaInfoReader::openMediaFile()
{
    if (m_inputAVFormatCxt)
    {
        TRACE("already has input avformat \n");
		return FALSE;
    }
 
    int res = 0;
 
    if ((res = avformat_open_input(&m_inputAVFormatCxt, m_filePath.c_str(), 0, NULL)) < 0)
    {
  
    }
    
    if(res < 0)
    {
        string strError = "can not open file:" + m_filePath + ",errcode:" + to_string(res) + ",err msg:" + av_make_error_string(m_tmpErrString, AV_ERROR_MAX_STRING_SIZE, res);
		TRACE("--------------%s \n", strError.c_str());
		return FALSE;
    }
 
    if (avformat_find_stream_info(m_inputAVFormatCxt, 0) < 0)
    {
        TRACE("can not find stream info \n");
		return FALSE;
    }
 
	TRACE("filepath: %s, format: %s, Bitrate: %d Kbps \n",  m_filePath.c_str(), m_inputAVFormatCxt->iformat->name, m_inputAVFormatCxt->bit_rate/1000);
 
    av_dump_format(m_inputAVFormatCxt, 0, m_filePath.c_str(), 0);
    for (int i = 0; i < m_inputAVFormatCxt->nb_streams; i++)
    {
        AVStream *in_stream = m_inputAVFormatCxt->streams[i];
 
		if (in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			m_videoStreamIndex = i;
 
			m_width = in_stream->codec->width;
			m_height = in_stream->codec->height;
 
			if(in_stream->avg_frame_rate.den != 0 && in_stream->avg_frame_rate.num != 0)
			{
			  m_frame_rate = in_stream->avg_frame_rate.num/in_stream->avg_frame_rate.den;//每秒多少帧 
			}
 
			m_video_frame_count = in_stream->nb_frames; //视频帧数
 
			//m_vcodec_name = in_stream->codec->codec_name; //获取编码器名称。不行,这里获得的名字是空的,后面用别的方法
 
			TRACE("Video Track---stream index: %d, codec id: %d,  width: %d, height: %d, FrameRate: %d, Number of Frames: %d\n", 
				i, in_stream->codec->codec_id,  in_stream->codec->width, in_stream->codec->height, m_frame_rate, m_video_frame_count);
		}
		else if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			m_audioStreamIndex = i;
 
			m_audio_samplerate =  in_stream->codec->sample_rate;
	        m_audio_channels = in_stream->codec->channels;
 
			//m_acodec_name = in_stream->codec->codec_name; //获取编码器名称。不行,这里获得的名字是空的,后面用别的方法
 
			TRACE("Audio Track---stream index: %d, codec id: %d,  sample_rate: %d, channels: %d \n", 
				i, in_stream->codec->codec_id,  in_stream->codec->sample_rate, in_stream->codec->channels);
		}
    }
 
 
	if(m_videoStreamIndex != -1)
	{
		AVCodecContext *avctx;
		AVCodec *codec;
 
		do
		{
			avctx = m_inputAVFormatCxt->streams[m_videoStreamIndex]->codec;
			// 寻找视频解码器
			codec = avcodec_find_decoder(avctx->codec_id);
			if(codec == NULL)
				break;
 
			m_vcodec_name = codec->long_name; //视频编码器名称
 
            TRACE("video_codec name: %s \n", codec->long_name);
 
		}while(0);
	}
 
	
	if(m_audioStreamIndex != -1)
	{
		AVCodecContext *avctx;
		AVCodec *codec;
 
		do
		{
			avctx = m_inputAVFormatCxt->streams[m_audioStreamIndex]->codec;
			// 寻找音频解码器
			codec = avcodec_find_decoder(avctx->codec_id);
			if(codec == NULL)
				break;
 
			m_acodec_name = codec->long_name; //音频编码器名称
 
			TRACE("audio_codec_name: %s \n", codec->long_name);
 
		}while(0);
	}
 
#if 1
	int nVFrames = 0;
	bool bAudioDecoderInited = false;
	int ret;
 
	AVPacket pkt;
	av_init_packet(&pkt);
 
	//一帧一帧读取
	while (av_read_frame(m_inputAVFormatCxt, &pkt) >= 0)
	{
		if(pkt.stream_index == m_videoStreamIndex)
		{
			AVStream * pStream = m_inputAVFormatCxt->streams[m_videoStreamIndex];
 
			nVFrames++;
		}//Video
 
		else if(pkt.stream_index == m_audioStreamIndex)
		{
			AVStream * pStream = m_inputAVFormatCxt->streams[m_audioStreamIndex];
 
			if (!bAudioDecoderInited)
			{
				if (avcodec_open2(pStream->codec, avcodec_find_decoder(pStream->codec->codec_id), NULL) < 0)
				{
					TRACE("Could not open audio codec.(无法打开解码器)\n");
					break;
				}
 
				bAudioDecoderInited = true;
			}
 
 
			int dec_got_frame_a = 0;
		
 
			AVFrame *input_frame = av_frame_alloc();
			if (!input_frame)
			{
				ret = AVERROR(ENOMEM);
				break;
			}
			//解码为PCM音频
			if ((ret = avcodec_decode_audio4(pStream->codec, input_frame, &dec_got_frame_a, &pkt)) < 0)
			{
				TRACE("Could not decode audio frame.\n");
				
				av_frame_free(&input_frame);
				break;
			}
 
			if (dec_got_frame_a) //解码出一帧
			{
				//获得音频属性(音频输出格式、采样率、声道数)
				TRACE("audio_sample_format: %d, sample_rate: %d, channels: %d \n",  pStream->codec->sample_fmt, pStream->codec->sample_rate, pStream->codec->channels);
			}
 
			av_frame_free(&input_frame);
 
			//注意:这里根据你的需求情况决定是否屏蔽以下代码
			if(dec_got_frame_a) //获取到音频信息后是否跳出循环??
				break;
 
		} //Audio
 
		av_free_packet(&pkt);
	}
 
	if(m_video_frame_count == 0)
	{
		m_video_frame_count = nVFrames; //更新视频帧数
	}
 
#endif
 
	m_bInited = FALSE;
	return TRUE;
}
 
 
void FFMediaInfoReader::closeMediaFile()
{
    if (m_inputAVFormatCxt)
    {
        avformat_close_input(&m_inputAVFormatCxt);
		m_inputAVFormatCxt = NULL;
    }
}
 
 
string FFMediaInfoReader::GetMediaFormatName()
{
	if(m_inputAVFormatCxt == NULL)
		return "";
 
	//return m_inputAVFormatCxt->iformat->name;
	return m_inputAVFormatCxt->iformat->long_name;
}

 

 

知识点讲解

对代码中一些重点的部分讲解一下。

首先,要调用FFmpeg获取文件的相关信息,肯定要先打开一个文件,然后获取相关媒体流(视频流、音频流)对应的轨道。

 

1. 打开一个文件。

   int res = 0;
    if ((res = avformat_open_input(&m_inputAVFormatCxt, m_filePath.c_str(), 0, NULL)) < 0)
    {
 
    }
    if(res < 0)
    {
        string strError = "can not open file:" + m_filePath + ",errcode:" + std::to_string((int)res) + ",err msg:" + av_make_error_string(m_tmpErrString, AV_ERROR_MAX_STRING_SIZE, res);
        TRACE("%s \n", strError.c_str());
        return false;
    }
    if (avformat_find_stream_info(m_inputAVFormatCxt, 0) < 0)
    {
       TRACE("can not find stream info \n");
       return false;
    }
    av_dump_format(m_inputAVFormatCxt, 0, m_filePath.c_str(), 0);

 

2. 获取各个流的轨道(StreamIndex)

    for (int i = 0; i < m_inputAVFormatCxt->nb_streams; i++)
    {
        AVStream *in_stream = m_inputAVFormatCxt->streams[i];
 
		if (in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			m_videoStreamIndex = i;
 
		}
		else if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO)
		{
			m_audioStreamIndex = i;
		}
    }

 

3. 获取视频的信息(编码格式、视频宽高、帧率、帧数)

	AVStream *in_stream = m_inputAVFormatCxt->streams[i];
 
	if (in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO)
	{
		m_videoStreamIndex = i;
 
		m_width = in_stream->codec->width;
		m_height = in_stream->codec->height;
 
		if(in_stream->avg_frame_rate.den != 0 && in_stream->avg_frame_rate.num != 0)
		{
		  m_frame_rate = in_stream->avg_frame_rate.num/in_stream->avg_frame_rate.den;//每秒多少帧 
		}
 
		m_video_frame_count = in_stream->nb_frames; //视频帧数
 
		//m_vcodec_name = in_stream->codec->codec_name; //获取编码器名称。不行,这里获得的名字是空的,后面用别的方法
 
		TRACE("Video Track---stream index: %d, codec id: %d,  width: %d, height: %d, FrameRate: %d, Number of Frames: %d\n", 
			i, in_stream->codec->codec_id,  in_stream->codec->width, in_stream->codec->height, m_frame_rate, m_video_frame_count);
	}

 

上面代码中我们先取得AVStream类型的指针, in_stream->codec->codec_id获得表示视频(或音频)的CodecID,这个CodecID是一个枚举类型,表示一种编码格式,AV_CODEC_ID_H264表示H264格式。 in_stream->codec->width、 in_stream->codec->height是视频的宽高。获得帧数的方法是:in_stream->nb_frames; (这里要多谢一位网友的留言和提醒),但是这里要补充一下,获得帧数通过AVStream指针的nb_frames成员变量不一定能拿到实际的帧数,我测试过一些文件,发现读取出来的这个值是为0的,如果遇到这种情况,只能采用笨的方法:遍历整个文件的视频帧了。代码如下:
 

int nVFrames = 0;
AVPacket pkt;
av_init_packet(&pkt);
 
//一帧一帧读取
while (av_read_frame(m_inputAVFormatCxt, &pkt) >= 0)
{
	if(pkt.stream_index == m_videoStreamIndex)
	{
		nVFrames++;
	}
	av_free_packet(&pkt);
}
//读完要把文件指针移到文件开始位置
av_seek_frame(m_inputAVFormatCxt,m_videoStreamIndex, 0*1000, 0);

 

4. 获得音频的编码格式、采样率、声道数

音频的编码格式的获取方法跟视频的类似。但是这里也要补充一下:用这种方法获得某些文件的音频轨道的采样率和声道数值为0,要用另外一种方法(见下面最后一节)。

	AVStream *in_stream = m_inputAVFormatCxt->streams[i];
 
	if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO)
	{
		m_audioStreamIndex = i;
 
		m_audio_samplerate =  in_stream->codec->sample_rate;
		m_audio_channels = in_stream->codec->channels;
 
		//m_acodec_name = in_stream->codec->codec_name; //获取编码器名称。不行,这里获得的名字是空的,后面用别的方法
 
		TRACE("Audio Track---stream index: %d, codec id: %d,  sample_rate: %d, channels: %d \n", 
			i, in_stream->codec->codec_id,  in_stream->codec->sample_rate, in_stream->codec->channels);
	}

 

5. 获得整个文件的播放时长

读到的duration变量是以微妙为单位的,这个时间值转为秒要除以1000000.

if (m_inputAVFormatCxt->duration > 0)
{
	m_Duration = m_inputAVFormatCxt->duration / 1000000; //转换为秒
}
else
{
	m_Duration = 0;
}

 

6. 获取视频编码器、音频编码器名称

虽然我们可以用前面获取到的codec_id来判断流是什么编码格式的,但是如果要获得编码格式的一个字符串描述,最好还是通过获取这种格式(codec_id)对应的解码器的名称,获取代码如下:

if(m_videoStreamIndex != -1)
	{
		AVCodecContext *avctx;
		AVCodec *codec;
 
		do
		{
			avctx = m_inputAVFormatCxt->streams[m_videoStreamIndex]->codec;
			// 寻找视频解码器
			codec = avcodec_find_decoder(avctx->codec_id);
			if(codec == NULL)
				break;
 
			m_vcodec_name = codec->long_name; //视频编码器名称
 
            TRACE("video_codec name: %s \n", codec->long_name);
 
		}while(0);
	}
 
	
	if(m_audioStreamIndex != -1)
	{
		AVCodecContext *avctx;
		AVCodec *codec;
 
		do
		{
			avctx = m_inputAVFormatCxt->streams[m_audioStreamIndex]->codec;
			// 寻找音频解码器
			codec = avcodec_find_decoder(avctx->codec_id);
			if(codec == NULL)
				break;
 
			m_acodec_name = codec->long_name; //音频编码器名称
 
			TRACE("audio_codec_name: %s \n", codec->long_name);
 
		}while(0);
	}

 

7. 获取文件容器格式

我们要知道一个文件是MP4格式,AVI格式,还是MPEG-PS,MPEG-TS或其他,光看后缀名是不行的,FFmpeg能解析文件并获得文件封装用的格式,获得容器格式的函数如下:

string FFMediaInfoReader::GetMediaFormatName()
{
	if(m_inputAVFormatCxt == NULL)
		return "";
 
	//return m_inputAVFormatCxt->iformat->name;
	return m_inputAVFormatCxt->iformat->long_name;
}

注意:上面的m_inputAVFormatCxt->iformat->name和m_inputAVFormatCxt->iformat->long_name字符串值是不一样的,大家可以分别读取不同的值看有什么不同。

8. 获得文件总码率(平均码率)

m_inputAVFormatCxt->bit_rate/1000;//转为K为单位,Kbps

 

9. 获取轨道数(媒体流数目)

	//获取媒体轨道数目
	int    GetTrackNum()
	{
		int n = 0;
 
		if(HasVideoTrack())
			n += 1;
		if(HasAudioTrack())
			n += 1;
		return n;
	}
 
	//是否有视频流
	bool    HasVideoTrack()
	{
		if( m_videoStreamIndex == -1)
			return false;
 
		if(m_width > 0 && m_height > 0)
			return true;
 
		return false;
	}
 
	//是否有音频流
	bool    HasAudioTrack()
	{
		if(m_audioStreamIndex == -1)
			return false;
 
		return true;
	}

 

10. 如何有效获取音频的采样率、声道数

前面遗留了一个问题,现在就回答一下。要获取音频轨道的信息,直接打开文件获取是不可靠的,有些文件这时候还不能拿得到音频属性。这时候,我们就需要将音频帧读出来,解码之后,就能得到具体的音频属性了(注意:我们不需要全部帧解码,只解码一两个音频帧就能得到信息)。代码如下:
 

int nVFrames = 0;
	bool bAudioDecoderInited = false;
	int ret;
 
	AVPacket pkt;
	av_init_packet(&pkt);
 
	//一帧一帧读取
	while (av_read_frame(m_inputAVFormatCxt, &pkt) >= 0)
	{
		if(pkt.stream_index == m_videoStreamIndex)
		{
			AVStream * pStream = m_inputAVFormatCxt->streams[m_videoStreamIndex];
 
			nVFrames++;
		}//Video
 
		else if(pkt.stream_index == m_audioStreamIndex)
		{
			AVStream * pStream = m_inputAVFormatCxt->streams[m_audioStreamIndex];
 
			if (!bAudioDecoderInited)
			{
				if (avcodec_open2(pStream->codec, avcodec_find_decoder(pStream->codec->codec_id), NULL) < 0)
				{
					TRACE("Could not open audio codec.(无法打开解码器)\n");
					break;
				}
 
				bAudioDecoderInited = true;
			}
 
			int dec_got_frame_a = 0;
		
			AVFrame *input_frame = av_frame_alloc();
			if (!input_frame)
			{
				ret = AVERROR(ENOMEM);
				break;
			}
			//解码为PCM音频
			if ((ret = avcodec_decode_audio4(pStream->codec, input_frame, &dec_got_frame_a, &pkt)) < 0)
			{
				TRACE("Could not decode audio frame.\n");
				
				av_frame_free(&input_frame);
				break;
			}
 
			if (dec_got_frame_a) //解码出一帧
			{
				//获得音频属性(音频输出格式、采样率、声道数)
				TRACE("audio_sample_format: %d, sample_rate: %d, channels: %d \n",  pStream->codec->sample_fmt, pStream->codec->sample_rate, pStream->codec->channels);
			}
 
			av_frame_free(&input_frame);
 
			//注意:这里根据你的需求情况决定是否屏蔽以下代码
			if(dec_got_frame_a) //获取到音频信息后是否跳出循环??
				break;
 
		} //Audio
 
		av_free_packet(&pkt);
	}
 
	if(m_video_frame_count == 0)
	{
		m_video_frame_count = nVFrames; //更新视频帧数
	}
 
#endif

 

这个类会通过VC++的TRACE语句把读到的媒体信息打印出来,下面是在作者机器上读取一些媒体文件打印的信息:

filepath: G:\videos\钻石珠宝.mp4, format: mov,mp4,m4a,3gp,3g2,mj2, Bitrate: 22498 Kbps 
Video Track---stream index: 0, codec id: 28,  width: 3840, height: 2160, FrameRate: 29, Number of Frames: 1544
Audio Track---stream index: 1, codec id: 86018,  sample_rate: 44100, channels: 2 
video_codec name: H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 
audio_codec_name: AAC (Advanced Audio Coding) 
 
filepath: G:\videos(2)\xianjian.ts, format: mpegts, Bitrate: 1042 Kbps 
Video Track---stream index: 0, codec id: 2,  width: 640, height: 480, FrameRate: 25, Number of Frames: 0
Audio Track---stream index: 1, codec id: 86017,  sample_rate: 44100, channels: 2
video_codec name: MPEG-2 video
audio_codec_name: MP3 (MPEG audio layer 3)

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章