用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)

 

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