| 導購 | 订阅 | 在线投稿
分享
 
 
 

DShow中實現抓圖的幾種方法

來源:互聯網  2008-06-01 02:12:03  評論

1.加入Sample Grabber Filter

當我們加入Sample Grabber Filter的時候,我們可以直接調用其接口(interface)ISampleGrabber。該接口可以獲取經過該Filter的單獨的Media Samples。詳情請參見DXSDK。

1.1 派生出自己的Sample Grabber

從ISampleGrabberCB中派生出自己的類,然後實現其虛函數,詳情請參見SDK中的示例程序(DXSDK ROOT\Samples\C++\DirectShow\Editing\GrabBitmaps)。

1.2 直接調用Sample Grabber Filter的接口

假如我們在播放的過程中動態的加入Filter的話,操作和效率都不樂觀。所以我采用下面的方法:

該方法傳遞的是時間,不是在播放的時候動態加入Filter然後截圖,而是另外打開源文件進行操作。

A)申明以下接口:

#001 IGraphBuilder *pGraph = NULL; //for graph builder

#002 IMediaControl *pControl = NULL; //media control

#003 IMediaSeeking *pSeeking = NULL; //media seeking

#004 IMediaEventEx *pEvent = NULL; //media envent

#005 IBaseFilter *pNullFilter =NULL;//for holding the Sample grabber Filter

B)初始化接口:

#001 JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC,

#002 IID_IGraphBuilder, (void **)&pGraph));

#003

#004 JIF(CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC,

#005 IID_IBaseFilter, (void **)&pNullFilter));

#006 JIF(pGraph->QueryInterface(IID_IMediaControl,(void **)&pControl));

#007 JIF(pGraph->QueryInterface(IID_IMediaSeeking, (void **)&pSeeking));

#008 JIF(pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent));

C)創建Sample Grabber

#001 // Create the Sample Grabber.

#002 IBaseFilter *pGrabberF = NULL;

#003 JIF(CoCreateInstance(CLSID_SampleGrabber,NULL, CLSCTX_INPROC_SERVER,

#004 IID_IBaseFilter, (void**)&pGrabberF));

#005

#006 JIF(pGraph->AddFilter(pGrabberF, L"Sample Grabber"));

#007 JIF(pGraph->AddFilter(pNullFilter, L"Null Render Filter"));

#008

#009 ISampleGrabber *pGrabber;

#010 JIF(pGrabberF->QueryInterface(IID_ISampleGrabber, (void**)&pGrabber));

設置Sample Grabber的媒體格式:調用SetMediaType,該函數接受一個AM_MEDIA_TYPE的結構,主要是設置該結構中的majortype,和suBType域。

D)添加Source Filter:

#001 IBaseFilter *pSrc;

#002 JIF(pGraph->AddSourceFilter(T2W(m_szFile), L"Source", &pSrc));

E)連接Grabber 和 NullRender兩個Filter:

#001 IPin *pOutPin;

#002 hr = GetPin(pGrabberF, PINDIR_OUTPUT, &pOutPin);

#003

#004 IPin *pInPin;

#005 hr = GetPin(pNullFilter, PINDIR_INPUT, &pInPin);

#006

#007 pGraph->Connect(pOutPin, pInPin);

F)取得當前所連接媒體的類型

#001 AM_MEDIA_TYPE mt;

#002 hr = pGrabber->GetConnectedMediaType(&mt);

#003 // Examine the format block.

#004 VIDEOINFOHEADER *pVih;

#005 if ((mt.formattype == FORMAT_VideoInfo) &&

#006 (mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&

#007 (mt.pbFormat != NULL) )

#008 {

#009 pVih = (VIDEOINFOHEADER*)mt.pbFormat;

#010 }

#011 else

#012 {

#013 // Wrong format. Free the format block and return an error.

#014 FreeMediaType(mt);

#015 return VFW_E_INVALIDMEDIATYPE;

#016 }

#017

#018 // Do buffer the samples as they pass through

#019 //

#020 hr = pGrabber->SetBufferSamples(TRUE);

#021

#022 // Only grab one at a time, stop stream after

#023 // grabbing one sample

#024 //

#025 hr = pGrabber->SetOneShot( TRUE );

G)Seeking文件,使其到達要截圖的時間幀

#001 pSeeking->SetPositions(pCurrentPos,

#002 AM_SEEKING_AbsolutePositioning,

#003 NULL, AM_SEEKING_NoPositioning );

#004

#005 pControl->Run();

#006

#007 long EvCode = 0;

#008

#009 hr = pEvent->WaitForCompletion( INFINITE, &EvCode );

H)取得當前的buffer數據

#001 // Find the required buffer size.

#002 long cbBuffer = 0;

#003 hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);

#004 LONGLONG currentPos;

#005 pSeeking->GetCurrentPosition(&currentPos);

#006 BYTE *pBuffer = new BYTE[cbBuffer];

#007 if (!pBuffer)

#008 {

#009 // Out of memory. Return an error code.

#010 Msg("Out of Memory");

#011 }

#012 hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);

I)寫入文件

#001 // Create a file to hold the bitmap

#002 HANDLE hf = CreateFile(szFilename, GENERIC_WRITE, FILE_SHARE_READ,

#003 NULL, CREATE_ALWAYS, NULL, NULL );

#004

#005 if( hf == INVALID_HANDLE_VALUE )

#006 {

#007 // Failed to create file

#008 return 0;

#009 }

#010

#011 // Write out the file header

#012 //

#013 BITMAPFILEHEADER bfh;

#014 memset( &bfh, 0, sizeof( bfh ) );

#015 bfh.bfType = 'MB';

#016 bfh.bfSize = sizeof( bfh ) + cbBuffer + sizeof( BITMAPINFOHEADER );

#017 bfh.bfOffBits = sizeof(BITMAPINFOHEADER)+sizeof( BITMAPFILEHEADER );

#018

#019 DWord Written = 0;

#020 WriteFile( hf, &bfh, sizeof( bfh ), &Written, NULL );

#021

#022 // Write the bitmap format

#023 //

#024 BITMAPINFOHEADER bih;

#025 memset( &bih, 0, sizeof( bih ) );

#026 bih.biSize = sizeof( bih );

#027 bih.biWidth = pVih->bmiHeader.biWidth;

#028 bih.biHeight = pVih->bmiHeader.biHeight;

#029 bih.biPlanes = pVih->bmiHeader.biPlanes;

#030 bih.biBitCount = pVih->bmiHeader.biBitCount;

#031

#032 Written = 0;

#033

#034 WriteFile( hf, &bih, sizeof( bih ), &Written, NULL );

#035

#036 // Write the bitmap bits

#037 //

#038 Written = 0;

#039 WriteFile( hf, pBuffer, cbBuffer, &Written, NULL );

#040 FreeMediaType(mt);

#041 CloseHandle(hf);

J)釋放資源

#001 pControl->Stop();

#002 SAFE_RELEASE(pControl);

#003 SAFE_RELEASE(pSeeking);

#004 SAFE_RELEASE(pEvent);

#005 SAFE_RELEASE(pSrc);

#006 SAFE_RELEASE(pNullFilter);

#007 SAFE_RELEASE(pGrabber);

#008 SAFE_RELEASE(pGrabberF);

#009 SAFE_RELEASE(pGraph);

K)其實我們可以不用NullRender,而是用IVideoWindow接口來實現。假如是那樣的話,首先申明IVideoWindow *pVideo = NULL;將pVideo加入到Filter Graph中

#001 JIF(pGraph->QueryInterface(IID_IVideoWindow,(void**)&pVideo));

#002 hr = pGraph->Render(pOutPin);

#003 if (pVideo)

#004 {

#005 hr = pVideo->put_AutoShow(OAFALSE);

#006 }

通過IBasicVideo::GetCurrentImage接口

對于該接口,Video Renderer和Video Mixing Renderer(VMR)有不同的實現。

A)Video Renderer

假如該Renderer使用了DDraw加速的話,該調用會失敗。在調用該接口的時候,必須首先暫停Renderer(可以通過IMediaControl::Pause()暫停,假如不能確信該操作是否成功,應該調用IMediaControl::GetState()判定狀態)。

B)Video Mixing Renderer

對于VMR,該方法都會成功(不管是否運用了DDraw加速,也不管是否是暫停狀態),此時對于它所有的狀態(running, stopped, or paused)都適用。

函數Grabber代碼如下(調用該函數的時候應該先將媒體文件暫停,原因上面已經說了):

#001 bool Grabber(IBasicVideo mBasicVideo, TCHAR *szFilename)

#002 {

#003 if (mBasicVideo)

#004 {

#005 long bitmapSize = 0;

#006 if(SUCCEEDED(mBasicVideo->GetCurrentImage(&bitmapSize, 0)))

#007 {

#008 //if語句裏面的操作時取得buffer的size。

#009 //當我們在布確定image buffer的大小的情況下,我們給

#010 //GetCurrentImage的第二個參數傳遞0或者NULL,取得buffer的

#011 //大小供以後使用。

#012 bool pass = false;

#013 unsigned char * buffer = new unsigned char[bitmapSize];

#014 if(SUCCEEDED(mBasicVideo->GetCurrentImage(&bitmapSize,(long*)buffer)))

#015 {

#016 //此時已經用到剛才所取得的大小(分配空間)

#017 BITMAPFILEHEADER hdr; //Bitmap的頭信息

#018 LPBITMAPINFOHEADER lpbi; // Bitmap的文件信息(包括數據)

#019

#020 lpbi = (LPBITMAPINFOHEADER)buffer;

#021

#022 int nColors = 1 << lpbi->biBitCount;

#023 if (nColors > 256)

#024 nColors = 0;

#025

#026 hdr.bfType = ((WORD) ('M' << 8) 'B'); //always is "BM"

#027 hdr.bfSize = bitmapSize + sizeof( hdr );

#028 hdr.bfReserved1 = 0;

#029 hdr.bfReserved2 = 0;

#030 hdr.bfOffBits = (DWORD) (sizeof(BITMAPFILEHEADER) + lpbi->biSize

#031 CFile bitmapFile(outFile, CFile::modeReadWrite CFile::modeCreate CFile::typeBinary);

#032 bitmapFile.Write(&hdr, sizeof(BITMAPFILEHEADER));

#033 bitmapFile.Write(buffer, bitmapSize);

#034 bitmapFile.Close();

#035 pass = true;

#036 }

#037 delete [] buffer; //數據用過之後記得要釋放空間

#038 return true;

#039 }

#040 }

#041

#042 return false;

#043 }

IMediaDet接口

IMediaDet接口可以取得媒體文件的信息。SDK裏面的示例代碼(沒有寫入文件):

#001 long size;

#002 //取得圖像幀的大小,給GetBitmapBits的第三個參數傳遞0 or NULL

#003 hr = pDet->GetBitmapBits(0, &size, 0, width, height);

#004 if(SUCCEEDED(hr))

#005 {

#006 char *pBuffer = new char[size];

#007 if(!pBuffer)

#008 {

#009 return E_OUTOFMEMORY;

#010 }

#011

#012 try

#013 {

#014 hr = pDet->GetbitmapsBits(0, 0, pBuffer, width, height);

#015 }

#016 catch(...)

#017 {

#018 delete [] pBuffer;

#019 throw;

#020 }

#021

#022 if(SUCCEEDED(hr))

#023 {

#024 BITMAPINFOHEADER *bmih = (BITMAPINFOHEADER*)pBuffer;

#025 HDC hdcDest = GetDC(0);

#026

#027 //Find the address of the start of the image data

#028 void *pData = pBuffer + sizeof(BITMAPINFOHEADER);

#029

#030 //Note: In general a BITMAPINFOHEADER can include extra color

#031 //information at the end, so calculating the offset to the image

#032 //data i snot generally correct. However, the IMediaDet interface

#033 //always returns an RGB-24 image with no extra color information

#034

#035 BITMAPINFO bmi;

#036 ZeroMemory(&bmi, sizeof(BITMAPINFO));

#037 CopyMemory(&(bmi.bmiHeader), bmih, sizeof(BITMAPINFOHEADER));

#038 HBITMAP hBitmap = CreateDIBitmap(hdcDect, bmih, CBM_INIT,

#039 pData, &bmi, DIB_RGB_COLORS);

#040 }

#041

#042 delete [] pBuffer;

#043 }

該方法並沒有寫入bitmap,具體的寫入過程可以參加上面的幾種方法。

1.加入Sample Grabber Filter   當我們加入Sample Grabber Filter的時候,我們可以直接調用其接口(interface)ISampleGrabber。該接口可以獲取經過該Filter的單獨的Media Samples。詳情請參見DXSDK。   1.1 派生出自己的Sample Grabber   從ISampleGrabberCB中派生出自己的類,然後實現其虛函數,詳情請參見SDK中的示例程序(DXSDK ROOT\Samples\C++\DirectShow\Editing\GrabBitmaps)。   1.2 直接調用Sample Grabber Filter的接口   假如我們在播放的過程中動態的加入Filter的話,操作和效率都不樂觀。所以我采用下面的方法:   該方法傳遞的是時間,不是在播放的時候動態加入Filter然後截圖,而是另外打開源文件進行操作。   A)申明以下接口: #001 IGraphBuilder *pGraph = NULL; //for graph builder #002 IMediaControl *pControl = NULL; //media control #003 IMediaSeeking *pSeeking = NULL; //media seeking #004 IMediaEventEx *pEvent = NULL; //media envent #005 IBaseFilter *pNullFilter =NULL;//for holding the Sample grabber Filter   B)初始化接口: #001 JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, #002 IID_IGraphBuilder, (void **)&pGraph)); #003 #004 JIF(CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC, #005 IID_IBaseFilter, (void **)&pNullFilter)); #006 JIF(pGraph->QueryInterface(IID_IMediaControl,(void **)&pControl)); #007 JIF(pGraph->QueryInterface(IID_IMediaSeeking, (void **)&pSeeking)); #008 JIF(pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent));   C)創建Sample Grabber #001 // Create the Sample Grabber. #002 IBaseFilter *pGrabberF = NULL; #003 JIF(CoCreateInstance(CLSID_SampleGrabber,NULL, CLSCTX_INPROC_SERVER, #004 IID_IBaseFilter, (void**)&pGrabberF)); #005 #006 JIF(pGraph->AddFilter(pGrabberF, L"Sample Grabber")); #007 JIF(pGraph->AddFilter(pNullFilter, L"Null Render Filter")); #008 #009 ISampleGrabber *pGrabber; #010 JIF(pGrabberF->QueryInterface(IID_ISampleGrabber, (void**)&pGrabber));   設置Sample Grabber的媒體格式:調用SetMediaType,該函數接受一個AM_MEDIA_TYPE的結構,主要是設置該結構中的majortype,和suBType域。   D)添加Source Filter: #001 IBaseFilter *pSrc; #002 JIF(pGraph->AddSourceFilter(T2W(m_szFile), L"Source", &pSrc));   E)連接Grabber 和 NullRender兩個Filter: #001 IPin *pOutPin; #002 hr = GetPin(pGrabberF, PINDIR_OUTPUT, &pOutPin); #003 #004 IPin *pInPin; #005 hr = GetPin(pNullFilter, PINDIR_INPUT, &pInPin); #006 #007 pGraph->Connect(pOutPin, pInPin);   F)取得當前所連接媒體的類型 #001 AM_MEDIA_TYPE mt; #002 hr = pGrabber->GetConnectedMediaType(&mt); #003 // Examine the format block. #004 VIDEOINFOHEADER *pVih; #005 if ((mt.formattype == FORMAT_VideoInfo) && #006 (mt.cbFormat >= sizeof(VIDEOINFOHEADER)) && #007 (mt.pbFormat != NULL) ) #008 { #009 pVih = (VIDEOINFOHEADER*)mt.pbFormat; #010 } #011 else #012 { #013 // Wrong format. Free the format block and return an error. #014 FreeMediaType(mt); #015 return VFW_E_INVALIDMEDIATYPE; #016 } #017 #018 // Do buffer the samples as they pass through #019 // #020 hr = pGrabber->SetBufferSamples(TRUE); #021 #022 // Only grab one at a time, stop stream after #023 // grabbing one sample #024 // #025 hr = pGrabber->SetOneShot( TRUE );   G)Seeking文件,使其到達要截圖的時間幀 #001 pSeeking->SetPositions(pCurrentPos, #002 AM_SEEKING_AbsolutePositioning, #003 NULL, AM_SEEKING_NoPositioning ); #004 #005 pControl->Run(); #006 #007 long EvCode = 0; #008 #009 hr = pEvent->WaitForCompletion( INFINITE, &EvCode );   H)取得當前的buffer數據 #001 // Find the required buffer size. #002 long cbBuffer = 0; #003 hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL); #004 LONGLONG currentPos; #005 pSeeking->GetCurrentPosition(&currentPos); #006 BYTE *pBuffer = new BYTE[cbBuffer]; #007 if (!pBuffer) #008 { #009 // Out of memory. Return an error code. #010 Msg("Out of Memory"); #011 } #012 hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);   I)寫入文件 #001 // Create a file to hold the bitmap #002 HANDLE hf = CreateFile(szFilename, GENERIC_WRITE, FILE_SHARE_READ, #003 NULL, CREATE_ALWAYS, NULL, NULL ); #004 #005 if( hf == INVALID_HANDLE_VALUE ) #006 { #007 // Failed to create file #008 return 0; #009 } #010 #011 // Write out the file header #012 // #013 BITMAPFILEHEADER bfh; #014 memset( &bfh, 0, sizeof( bfh ) ); #015 bfh.bfType = 'MB'; #016 bfh.bfSize = sizeof( bfh ) + cbBuffer + sizeof( BITMAPINFOHEADER ); #017 bfh.bfOffBits = sizeof(BITMAPINFOHEADER)+sizeof( BITMAPFILEHEADER ); #018 #019 DWord Written = 0; #020 WriteFile( hf, &bfh, sizeof( bfh ), &Written, NULL ); #021 #022 // Write the bitmap format #023 // #024 BITMAPINFOHEADER bih; #025 memset( &bih, 0, sizeof( bih ) ); #026 bih.biSize = sizeof( bih ); #027 bih.biWidth = pVih->bmiHeader.biWidth; #028 bih.biHeight = pVih->bmiHeader.biHeight; #029 bih.biPlanes = pVih->bmiHeader.biPlanes; #030 bih.biBitCount = pVih->bmiHeader.biBitCount; #031 #032 Written = 0; #033 #034 WriteFile( hf, &bih, sizeof( bih ), &Written, NULL ); #035 #036 // Write the bitmap bits #037 // #038 Written = 0; #039 WriteFile( hf, pBuffer, cbBuffer, &Written, NULL ); #040 FreeMediaType(mt); #041 CloseHandle(hf);   J)釋放資源 #001 pControl->Stop(); #002 SAFE_RELEASE(pControl); #003 SAFE_RELEASE(pSeeking); #004 SAFE_RELEASE(pEvent); #005 SAFE_RELEASE(pSrc); #006 SAFE_RELEASE(pNullFilter); #007 SAFE_RELEASE(pGrabber); #008 SAFE_RELEASE(pGrabberF); #009 SAFE_RELEASE(pGraph);   K)其實我們可以不用NullRender,而是用IVideoWindow接口來實現。假如是那樣的話,首先申明IVideoWindow *pVideo = NULL;將pVideo加入到Filter Graph中 #001 JIF(pGraph->QueryInterface(IID_IVideoWindow,(void**)&pVideo)); #002 hr = pGraph->Render(pOutPin); #003 if (pVideo) #004 { #005 hr = pVideo->put_AutoShow(OAFALSE); #006 }   通過IBasicVideo::GetCurrentImage接口   對于該接口,Video Renderer和Video Mixing Renderer(VMR)有不同的實現。   A)Video Renderer   假如該Renderer使用了DDraw加速的話,該調用會失敗。在調用該接口的時候,必須首先暫停Renderer(可以通過IMediaControl::Pause()暫停,假如不能確信該操作是否成功,應該調用IMediaControl::GetState()判定狀態)。   B)Video Mixing Renderer   對于VMR,該方法都會成功(不管是否運用了DDraw加速,也不管是否是暫停狀態),此時對于它所有的狀態(running, stopped, or paused)都適用。   函數Grabber代碼如下(調用該函數的時候應該先將媒體文件暫停,原因上面已經說了): #001 bool Grabber(IBasicVideo mBasicVideo, TCHAR *szFilename) #002 { #003 if (mBasicVideo) #004 { #005 long bitmapSize = 0; #006 if(SUCCEEDED(mBasicVideo->GetCurrentImage(&bitmapSize, 0))) #007 { #008 //if語句裏面的操作時取得buffer的size。 #009 //當我們在布確定image buffer的大小的情況下,我們給 #010 //GetCurrentImage的第二個參數傳遞0或者NULL,取得buffer的 #011 //大小供以後使用。 #012 bool pass = false; #013 unsigned char * buffer = new unsigned char[bitmapSize]; #014 if(SUCCEEDED(mBasicVideo->GetCurrentImage(&bitmapSize,(long*)buffer))) #015 { #016 //此時已經用到剛才所取得的大小(分配空間) #017 BITMAPFILEHEADER hdr; //Bitmap的頭信息 #018 LPBITMAPINFOHEADER lpbi; // Bitmap的文件信息(包括數據) #019 #020 lpbi = (LPBITMAPINFOHEADER)buffer; #021 #022 int nColors = 1 << lpbi->biBitCount; #023 if (nColors > 256) #024 nColors = 0; #025 #026 hdr.bfType = ((WORD) ('M' << 8) 'B'); //always is "BM" #027 hdr.bfSize = bitmapSize + sizeof( hdr ); #028 hdr.bfReserved1 = 0; #029 hdr.bfReserved2 = 0; #030 hdr.bfOffBits = (DWORD) (sizeof(BITMAPFILEHEADER) + lpbi->biSize #031 CFile bitmapFile(outFile, CFile::modeReadWrite CFile::modeCreate CFile::typeBinary); #032 bitmapFile.Write(&hdr, sizeof(BITMAPFILEHEADER)); #033 bitmapFile.Write(buffer, bitmapSize); #034 bitmapFile.Close(); #035 pass = true; #036 } #037 delete [] buffer; //數據用過之後記得要釋放空間 #038 return true; #039 } #040 } #041 #042 return false; #043 }   IMediaDet接口   IMediaDet接口可以取得媒體文件的信息。SDK裏面的示例代碼(沒有寫入文件): #001 long size; #002 //取得圖像幀的大小,給GetBitmapBits的第三個參數傳遞0 or NULL #003 hr = pDet->GetBitmapBits(0, &size, 0, width, height); #004 if(SUCCEEDED(hr)) #005 { #006 char *pBuffer = new char[size]; #007 if(!pBuffer) #008 { #009 return E_OUTOFMEMORY; #010 } #011 #012 try #013 { #014 hr = pDet->GetbitmapsBits(0, 0, pBuffer, width, height); #015 } #016 catch(...) #017 { #018 delete [] pBuffer; #019 throw; #020 } #021 #022 if(SUCCEEDED(hr)) #023 { #024 BITMAPINFOHEADER *bmih = (BITMAPINFOHEADER*)pBuffer; #025 HDC hdcDest = GetDC(0); #026 #027 //Find the address of the start of the image data #028 void *pData = pBuffer + sizeof(BITMAPINFOHEADER); #029 #030 //Note: In general a BITMAPINFOHEADER can include extra color #031 //information at the end, so calculating the offset to the image #032 //data i snot generally correct. However, the IMediaDet interface #033 //always returns an RGB-24 image with no extra color information #034 #035 BITMAPINFO bmi; #036 ZeroMemory(&bmi, sizeof(BITMAPINFO)); #037 CopyMemory(&(bmi.bmiHeader), bmih, sizeof(BITMAPINFOHEADER)); #038 HBITMAP hBitmap = CreateDIBitmap(hdcDect, bmih, CBM_INIT, #039 pData, &bmi, DIB_RGB_COLORS); #040 } #041 #042 delete [] pBuffer; #043 }   該方法並沒有寫入bitmap,具體的寫入過程可以參加上面的幾種方法。
󰈣󰈤
王朝萬家燈火計劃
期待原創作者加盟
 
 
 
>>返回首頁<<
 
 
 
 
 熱帖排行
 
 
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有