织梦CMS - 轻松建站从此开始!

罗索

利用VC++实现AVI文件的合成和分解

jackyhwei 发布于 2011-11-16 14:05 点击:次 
相信你肯定会对AVI的文件结构已经很清楚了,在介绍完了AVI文件结构后,我们就来看看如何对avi文件进行读写了,为了对avi进行读写,微软提供了一套API,总共50个函数,他们的用途主要有两类,一个是avi文件的操作,一类是数据流streams的操作。
TAG:

AVI是音频视频交错(Audio Video Interleaved)的英文缩写,它是Microsoft公司开发的一种符合RIFF文件规范的数字音频与视频文件格式,原先用于Microsoft Video for Windows (简称VFW)环境,现在已被Windows 95/98、OS/2等多数操作系统直接支持。AVI格式允许视频和音频交错在一起同步播放,支持256色和RLE压缩,但AVI文件并未限定压缩标准,因此,AVI文件格式只是作为控制界面上的标准,不具有兼容性,用不同压缩算法生成的AVI文件,必须使用相应的解压缩算法才能播放出来。常用的AVI播放驱动程序,主要是Microsoft Video for Windows或Windows 95/98中的Video 1,以及Intel公司的Indeo Video。

在介绍AVI文件前,我们要先来看看RIFF文件结构。AVI文件采用的是RIFF文件结构方式,RIFF(Resource Interchange File Format,资源互换文件格式)是微软公司定义的一种用于管理windows环境中多媒体数据的文件格式,波形音频wave,MIDI和数字视频AVI都采用这种格式存储。构造RIFF文件的基本单元叫做数据块(Chunk),每个数据块包含3个部分,

1、4字节的数据块标记(或者叫做数据块的ID)

2、数据块的大小

3、数据

整个RIFF文件可以看成一个数据块,其数据块ID为RIFF,称为RIFF块。一个RIFF文件中只允许存在一个RIFF块。RIFF块中包含一系列的子块,其中有一种字块的ID为"LIST",称为LIST,LIST块中可以再包含一系列的子块,但除了LIST块外的其他所有的子块都不能再包含子块。

RIFF和LIST块分别比普通的数据块多一个被称为形式类型(Form Type)和列表类型(List Type)的数据域,其组成如下:

1、4字节的数据块标记(Chunk ID)

2、数据块的大小

3、4字节的形式类型或者列表类型

4、数据

下面我们看看AVI文件的结构。AVI文件是目前使用的最复杂的RIFF文件,它能同时存储同步表现的音频视频数据。AVI的RIFF块的形式类型是AVI,它包含3个子块,如下所述:

1、信息块,一个ID为"hdrl"的LIST块,定义AVI文件的数据格式。

2、数据块,一个ID为 "movi"的LIST块,包含AVI的音视频序列数据。

3、索引块,ID为 "idxl"的子块,定义 "movi"LIST块的索引数据,是可选块。

AVI文件的结构如下图所示,下面将具体介绍AVI文件的各子块构造。

1、信息块,信息块包含两个子块,即一个ID为 avih 的子块和一个ID 为 strl 的LIST块。

"avih"子块的内容可由如下的结构定义:

  1. typedef struct 
  2. DWORD dwMicroSecPerFrame ; //显示每桢所需的时间ns,定义avi的显示速率 
  3. DWORD dwMaxBytesPerSec; // 最大的数据传输率 
  4. DWORD dwPaddingGranularity; //记录块的长度需为此值的倍数,通常是2048 
  5. DWORD dwFlages; //AVI文件的特殊属性,如是否包含索引块,音视频数据是否交叉存储 
  6. DWORD dwTotalFrame; //文件中的总桢数 
  7. DWORD dwInitialFrames; //说明在开始播放前需要多少桢 
  8. DWORD dwStreams; //文件中包含的数据流种类 
  9. DWORD dwSuggestedBufferSize; //建议使用的缓冲区的大小, 
  10. //通常为存储一桢图像以及同步声音所需要的数据之和 
  11. DWORD dwWidth; //图像宽 
  12. DWORD dwHeight; //图像高 
  13. DWORD dwReserved[4]; //保留值 
  14. }MainAVIHeader; 

"strl" LIST块用于记录AVI数据流,每一种数据流都在该LIST块中占有3个子块,他们的ID分别是"strh","strf", "strd";
"strh"子块由如下结构定义。

  1. typedef struct 
  2. FOURCC fccType; //4字节,表示数据流的种类 vids 表示视频数据流 
  3. //auds 音频数据流 
  4. FOURCC fccHandler;//4字节 ,表示数据流解压缩的驱动程序代号 
  5. DWORD dwFlags; //数据流属性 
  6. WORD wPriority; //此数据流的播放优先级 
  7. WORD wLanguage; //音频的语言代号 
  8. DWORD dwInitalFrames;//说明在开始播放前需要多少桢 
  9. DWORD dwScale; //数据量,视频每桢的大小或者音频的采样大小 
  10. DWORD dwRate; //dwScale /dwRate = 每秒的采样数 
  11. DWORD dwStart; //数据流开始播放的位置,以dwScale为单位 
  12. DWORD dwLength; //数据流的数据量,以dwScale为单位 
  13. DWORD dwSuggestedBufferSize; //建议缓冲区的大小 
  14. DWORD dwQuality; //解压缩质量参数,值越大,质量越好 
  15. DWORD dwSampleSize; //音频的采样大小 
  16. RECT rcFrame; //视频图像所占的矩形 
  17. }AVIStreamHeader; 

"strf"子块紧跟在"strh"子块之后,其结构视"strh"子块的类型而定,如下所述;如果 strh子块是视频数据流,则 strf子块的内容是一个与windows设备无关位图的BIMAPINFO结构,如下:

  1. typedef struct tagBITMAPINFO 
  2. BITMAPINFOHEADER bmiHeader; 
  3. RGBQUAD bmiColors[1]; //颜色表 
  4. }BITMAPINFO; 
  5.  
  6. typedef struct tagBITMAPINFOHEADER 
  7. DWORD biSize; 
  8. LONG biWidth; 
  9. LONG biHeight; 
  10. WORD biPlanes; 
  11. WORD biBitCount; 
  12. DWORD biCompression; 
  13. DWORD biSizeImage; 
  14. LONG biXPelsPerMeter; 
  15. LONG biYPelsPerMeter; 
  16. DWORD biClrUsed; 
  17. DWORD biClrImportant; 
  18. }BITMAPINFOHEADER; 

如果 strh子块是音频数据流,则strf子块的内容是一个WAVEFORMAT结构,如下:

  1. typedef struct 
  2. WORD wFormatTag; 
  3. WORD nChannels; //声道数 
  4. DWORD nSamplesPerSec; //采样率 
  5. DWORD nAvgBytesPerSec; //WAVE声音中每秒的数据量 
  6. WORD nBlockAlign; //数据块的对齐标志 
  7. WORD biSize; //此结构的大小 
  8. }WAVEFORMAT 

"strd"子块紧跟在strf子块后,存储供压缩驱动程序使用的参数,不一定存在,也没有固定的结构。

"strl" LIST块定义的AVI数据流依次将 "hdrl " LIST 块中的数据流头结构与"movi" LIST块中的数据联系在一起,第一个数据流头结构用于数据流0,第二个用于数据流1,依次类推。

数据块中存储视频和音频数据流,数据可直接存于 "movi" LIST块中。数据块中音视频数据按不同的字块存放,其结构如下所述,

音频字块
"##wb"
Wave 数据流
视频子块中存储DIB数据,又分为压缩或者未压缩DIB,
"##db"
RGB数据流
"##dc"
压缩的图像数据流

看到了吧,avi文件的图像数据可以是压缩的,和非压缩格式的。对于压缩格式来说,也可采用不同的编码,也许你曾经遇到有些avi没法识别,就是因为编码方式不一样,如果没有相应的解码,你就没法识别视频数据。AVI的编码方式有很多种,比较常见的有 mpeg2,mpeg4,divx等。

索引块,索引快包含数据块在文件中的位置索引,能提高avi文件的读写速度,其中存放着一组AVIINDEXENTRY结构数据。如下,这个块并不是必需的,也许不存在。

  1. typedef struct 
  2. DWORD ckid; //记录数据块中子块的标记 
  3. DWORD dwFlags; //表示chid所指子块的属性 
  4. DWORD dwChunkOffset; //子块的相对位置 
  5. DWORD dwChunkLength; //子块长度 
  6. }; 

现在我相信你肯定会对AVI的文件结构已经很清楚了,在介绍完了AVI文件结构后,我们就来看看如何对avi文件进行读写了,为了对avi进行读写,微软提供了一套API,总共50个函数,他们的用途主要有两类,一个是avi文件的操作,一类是数据流streams的操作。

1、打开和关闭文件

AVIFileOpen ,AVIFileAddRef, AVIFileRelease

2、从文件中读取文件信息

通过AVIFileInfo可以获取avi文件的一些信息,这个函数返回一个AVIFILEINFO结构,通过AVIFileReadData可以用来获取AVIFileInfo函数得不到的信息。这些信息也许不包含在文件的头部,比如拥有file的公司和个人的名称。

3、写入文件信息


可以通过AVIFileWriteData函数来写入文件的一些额外信息。

4、打开和关闭一个流

打开一个数据流就跟打开文件一样,你可以通过 AVIFileGetStream函数来打开一个数据流,这个函数创建了一个流的接口,然后在该接口中保存了一个句柄。

如果你想操作文件的某一个单独的流,你可以采用AVIStreamOpenFromFile函数,这个函数综合了AVIFileOpen和AVIFileGetStream函数。

如果你想操作文件中的多个数据流,你就要首先AVIFileOpen,然后AVIFileGetStream。

可以通过AVIStreamAddRef来增加stream接口的引用。

通过AVIStreamRelease函数来关闭数据流。这个函数用来减少streams的引用计数,当计数减少为0时,删除。

5、从流中读取数据和信息

AVIStreamInfo函数可以获取数据的一些信息,该函数返回一个AVISTREAMINFO结构,该结构包含了数据的类型压缩方法,建议的buffersize,回放的rate,以及一些description。

如果数据流还有一些其它的额外的信息,你可以通过AVIStreamReadData函数来获取。应用程序分配一个内存,传递给这个函数,然后这个函数会通过这个内存返回数据流的信息,额外的信息可能包括数据流的压缩和解压缩的方法,你可以通过AVIStreamDataSize宏来回去需要申请内存块的大小。

可以通过AVIStreamReadFormat函数获取数据流的格式信息。这个函数通过指定的内存返回数据流的格式信息,比如对于视频流,这个buffer包含了一个BIMAPINFO结构,对于音频流,内存块包含了WAVEFORMATEX或者PCMAVEFORMAT结构。你可以通过给AVIStreamReadFormat传递一个空buffer就可以获取buffer的大小。也可以通过AVIStreamFormatSize宏。

可以通过AVIStreamRead函数来返回多媒体的数据。这个函数将数据复制到应用程序提供的内存中,对于视频流,这个函数返回图像祯,对于音频流,这个函数返回音频的sample数据。可以通过给AVIStreamRead传递一个NULL的buffer来获取需要的buffer的大小。也可以通过AVIStreamSampleSize宏来获取buffer的大小。

有些AVI数据流句柄可能需要在启动数据流的前要做一下准备工作,此时,我们可以调用AVIStreamBeginStreaming函数来告知AVI数据流handle来申请分配它需要的一些资源。在完毕后,调用AVIStreamEndStreamming函数来释放资源。

6、操作压缩的视频数据

如果你要演示一祯或者几祯压缩视频图像时,你可以调用AVIStreamRead函数,将获取的数据传递给DrawDib函数来显示图像。这些函数可以显示压缩和未压缩的图像。

AVIFile也提供了一个函数AVIStreamGetFrameOpen,来获取未压缩的视频祯,这个函数创建了内存来获取未压缩的数据。也可以通过AVIStreamGetFrame函数来解压缩一个单独的视频祯。这个函数可以解压缩某一祯图像,然后将数据以一个BIMAPINFOHEADER结构返回。当你调用完AVIStreamGetFrame函数后,要调用AVIStreamGetFrameClose函数释放上一个函数申请的资源。

7、根据已存在的数据流创建文件

创建一个包含多个数据流的文件的方法就是整合多个数据流,将其写入一个新文件。这些数据流可以是内存中的数据,也可以是存在于另一个文件中。

我们可以用AVISave这个函数来build一个文件。这个函数可以创建一个文件,并且将指定的多个数据流按照指定的顺序写入文件,你也可以通过AVISaveV函数来创建一个新的文件,这个函数的功能和AVISave的功能一样,主要区别是AVISaveV采用的数据流数组,而AVISave是单个的数据流,多次保存。

我们可以调用AVISaveOptions函数来显示一个对话框,可以让用户来选择压缩方式。

我们可以在调用AVISave和AVISaveV函数时指定一个回调函数,用来显示avi文件的生成进度,可以让用户随时地取消生成avi文件。

我们可以调用GetSaveFileNamePreview函数来显示保存的对话框让用户选择保存的文件名。

通过AVIMakeFileFromStreams函数我们可以创建一个虚拟的文件句柄,其他的avi函数可以通过这个虚拟的文件句柄来操作文件中的数据流,操作完毕要记得调用AVIFileRelease释放。

8、向文件写入一个数据流

我们可以通过AVIFileCreateStream函数来在一个新文件或者已经存在的文件中创建一个数据流。这个函数根据AVISTREAMINFO结构定义了新的数据流,并为新的数据流创建一个接口,返回接口的指针。

在写入新的数据前,一定要指定流的格式信息,通过AVIStreamSetFormat函数,当设置一个视频流的时候,一定要使用BIMAPINFO结构来设置,音频就用WAVEFORMAT。

然后我们就可以通过AVIStreamWrite函数将我们的多媒体数据写入数据流了。这个函数将应用程序提供的内存数据复制到指定的流。缺省的avi handler将数据写入流的最后。

如果你有其他额外的信息需要写入流,你可以调用AVIFileWriteData或者AVIStreamWriteData,最后记得在完成数据写入后,要调用AVIStreamRelease。

9、数据流中的祯的位置

寻找起始祯:

可以通过AVIStreamStart函数来获取第一祯包含的sample number。也可以通过AVIStreamInfo函数来获取这个信息,这个函数的AVISTREAMINFO结构中包含了dwStart,可以通过AVIStreamStartTime宏来获取第一个sample。

可以通过AVIStreamLength函数来获取流的长度。这个函数返回流中的sample的数目。也可以通过AVIStreamInfo函数来获取这些信息,可以通过AVIStreamLengthTime宏来获取流的长度,毫秒。

在视频流中,一个sample对应着一祯图像,所以,有时这些sample中没有视频数据,如果你调用AVIStreamRead函数来数据,可能返回NULL,也可以通过AVIStreamFindSample通过指定FIND_ANY标志来查找指定的sample。

查找关键祯

通过AVIStreamFindSample函数查找符合要寻找的sample,然后可以通过下面的宏判断是否关键祯。

在time和sample间互相切换。

AVIStreamSampleToTime这个函数可以将smaple转换成毫秒。对于视频,这个值代表的是这个祯开始播放的时间。

在了解了上面的知识后,我们对avi的文件结构以及如何操作avi文件心里就明白了,下面我们可以开始我们的编程了。我们要做两件事情:

1、如何将一组静态的bmp位图合成一个avi的视频文件;

2、如何将一个未压缩的avi文件解析成一幅幅位图。

示例程序界面如下:(图略)

下面的函数演示了如何将一个文件夹下面的所有bmp文件都保存为一个avi文件,函数的第一个参数是要生成的AVI的文件名,第二个参数是存放bmp文件的文件夹名,这个函数会枚举该文件夹下的所有bmp文件,合成一个AVI文件。

  1. void Cbmp2aviDlg::AVItoBmp(CString strAVIFileName, CString strBmpDir) 
  2. // TODO: 在此添加控件通知处理程序代码 
  3. AVIFileInit(); 
  4. PAVIFILE avi; 
  5. int res=AVIFileOpen(&avi, strAVIFileName, OF_READ, NULL); 
  6. int n = GetLastError(); 
  7. if (res!=AVIERR_OK) 
  8. //an error occures 
  9. if (avi!=NULL) 
  10. AVIFileRelease(avi); 
  11. return ; 
  12. AVIFILEINFO avi_info; 
  13. AVIFileInfo(avi, &avi_info, sizeof(AVIFILEINFO)); 
  14. PAVISTREAM pStream; 
  15. res=AVIFileGetStream(avi, &pStream, streamtypeVIDEO /*video stream*/
  16. /*first stream*/); 
  17. if (res!=AVIERR_OK) 
  18. if (pStream!=NULL) 
  19. AVIStreamRelease(pStream); 
  20. AVIFileExit(); 
  21. return ; 
  22.  
  23. //do some task with the stream 
  24. int iNumFrames; 
  25. int iFirstFrame; 
  26. iFirstFrame=AVIStreamStart(pStream); 
  27. if (iFirstFrame==-1) 
  28. //Error getteing the frame inside the stream 
  29. if (pStream!=NULL) 
  30. AVIStreamRelease(pStream); 
  31. AVIFileExit(); 
  32. return ; 
  33. iNumFrames=AVIStreamLength(pStream); 
  34. if (iNumFrames==-1) 
  35. //Error getteing the number of frames inside the stream 
  36. if (pStream!=NULL) 
  37. AVIStreamRelease(pStream); 
  38. AVIFileExit(); 
  39. return ; 
  40.  
  41. //getting bitmap from frame 
  42. BITMAPINFOHEADER bih; 
  43. ZeroMemory(&bih, sizeof(BITMAPINFOHEADER)); 
  44.  
  45. bih.biBitCount=24; //24 bit per pixel 
  46. bih.biClrImportant=0; 
  47. bih.biClrUsed = 0; 
  48. bih.biCompression = BI_RGB; 
  49. bih.biPlanes = 1; 
  50. bih.biSize = 40; 
  51. bih.biXPelsPerMeter = 0; 
  52. bih.biYPelsPerMeter = 0; 
  53. //calculate total size of RGBQUAD scanlines (DWORD aligned) 
  54. bih.biSizeImage = (((bih.biWidth * 3) + 3) & 0xFFFC) * bih.biHeight ; 
  55.  
  56. PGETFRAME pFrame; 
  57. pFrame=AVIStreamGetFrameOpen(pStream, NULL ); 
  58.  
  59. AVISTREAMINFO streaminfo; 
  60. AVIStreamInfo(pStream,&streaminfo,sizeof(AVISTREAMINFO)); 
  61.  
  62. //Get the first frame 
  63. BITMAPINFOHEADER bih2; 
  64. long lsize = sizeof(bih2); 
  65. int index=0; 
  66. for (int i=iFirstFrame; i<iNumFrames; i++) 
  67. index= i-iFirstFrame; 
  68. BYTE* pDIB = (BYTE*) AVIStreamGetFrame(pFrame, index); // 
  69. AVIStreamReadFormat(pStream,index,&bih2,&lsize); 
  70. BITMAPFILEHEADER stFileHdr; 
  71.  
  72. BYTE* Bits=new BYTE[bih2.biSizeImage]; 
  73. AVIStreamRead(pStream,index,1,Bits,bih2.biSizeImage,NULL,NULL); 
  74. //RtlMoveMemory(Bits, pDIB + sizeof(BITMAPINFOHEADER), bih2.biSizeImage); 
  75.  
  76. bih2.biClrUsed =0; 
  77. stFileHdr.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER); 
  78. stFileHdr.bfSize=sizeof(BITMAPFILEHEADER); 
  79. stFileHdr.bfType=0x4d42; 
  80.  
  81. CString FileName; 
  82. FileName.Format("Frame-%05d.bmp", index); 
  83. CString strtemp = strBmpDir; 
  84. strtemp += "//"
  85. strtemp += FileName; 
  86. FILE* fp=_tfopen(strtemp ,_T("wb")); 
  87. fwrite(&stFileHdr,1,sizeof(BITMAPFILEHEADER),fp); 
  88. fwrite(&bih2,1,sizeof(BITMAPINFOHEADER),fp); 
  89. int ff = fwrite(Bits,1,bih2.biSizeImage,fp); 
  90. int e = GetLastError(); 
  91. fclose(fp); 
  92. ///// 
  93. delete Bits; 
  94. //CreateFromPackedDIBPointer(pDIB, index); 
  95.  
  96. AVIStreamGetFrameClose(pFrame); 
  97.  
  98. //close the stream after finishing the task 
  99. if (pStream!=NULL) 
  100. AVIStreamRelease(pStream); 
  101. AVIFileExit(); 

下面的这个函数演示了如何将AVI文件中的每一桢图像单独取出来,保存为bmp文件。函数的头一个参数是avi文件名,第二个参数是存放bmp文件的文件夹。

  1. //生成avi 
  2. void Cbmp2aviDlg::BMPtoAVI(CString szAVIName, CString strBmpDir) 
  3. CFileFind finder; 
  4. strBmpDir += _T("//*.*"); 
  5. AVIFileInit(); 
  6. AVISTREAMINFO strhdr; 
  7. PAVIFILE pfile; 
  8. PAVISTREAM ps; 
  9. int nFrames =0; 
  10. HRESULT hr; 
  11.  
  12. BOOL bFind = finder.FindFile(strBmpDir); 
  13. while(bFind) 
  14. bFind = finder.FindNextFile(); 
  15. if(!finder.IsDots() && !finder.IsDirectory()) 
  16. CString str = finder.GetFilePath(); 
  17. FILE *fp = fopen(str,"rb"); 
  18. BITMAPFILEHEADER bmpFileHdr; 
  19. BITMAPINFOHEADER bmpInfoHdr; 
  20. fseek( fp,0,SEEK_SET); 
  21. fread(&bmpFileHdr,sizeof(BITMAPFILEHEADER),1, fp); 
  22. fread(&bmpInfoHdr,sizeof(BITMAPINFOHEADER),1, fp); 
  23.  
  24. BYTE *tmp_buf = NULL; 
  25. if(nFrames ==0 ) 
  26. AVIFileOpen(&pfile,szAviName,OF_WRITE | OF_CREATE,NULL); 
  27. _fmemset(&strhdr, 0, sizeof(strhdr)); 
  28. strhdr.fccType = streamtypeVIDEO;// stream type 
  29. strhdr.fccHandler = 0; 
  30. strhdr.dwScale = 1; 
  31. strhdr.dwRate = 15; // 15 fps 
  32. strhdr.dwSuggestedBufferSize = bmpInfoHdr.biSizeImage ; 
  33. SetRect(&strhdr.rcFrame, 0, 0, bmpInfoHdr.biWidth, bmpInfoHdr.biHeight); 
  34.  
  35. // And create the stream; 
  36. hr = AVIFileCreateStream(pfile,&ps,&strhdr); 
  37. // hr = AVIStreamSetFormat(ps,nFrames,&bmpInfoHdr,sizeof(bmpInfoHdr)); 
  38. tmp_buf = new BYTE[bmpInfoHdr.biWidth * bmpInfoHdr.biHeight * 3]; 
  39. fread(tmp_buf, 1, bmpInfoHdr.biWidth * bmpInfoHdr.biHeight * 3, fp); 
  40. hr = AVIStreamSetFormat(ps,nFrames,&bmpInfoHdr,sizeof(bmpInfoHdr)); 
  41. hr = AVIStreamWrite(ps, // stream pointer 
  42. nFrames , // time of this frame 
  43. 1, // number to write 
  44. (LPBYTE) tmp_buf, 
  45. bmpInfoHdr.biSizeImage , // size of this frame 
  46. AVIIF_KEYFRAME, // flags.... 
  47. NULL, 
  48. NULL); 
  49.  
  50. nFrames ++; 
  51. fclose(fp); 
  52.  
  53. AVIStreamClose(ps); 
  54.  
  55. if(pfile != NULL) 
  56. AVIFileRelease(pfile); 
  57. AVIFileExit(); 

结束语:

以上代码在 vc 6.0 和windows xp平台调试通过。这两个函数你可以直接在你的程序中使用,更详细的代码可以参见随着本文附上的示例源码。这里我要指出的是,这个AVI文件和bmp互相转换过程中,avi中的视频数据都是存放的是没有压缩的数据,如果你要分解AVI文件是经过压缩编码,比如,DVSD,MPEG4编码,首先你要采用相应的解码器对视频数据解码,然后将解码过的数据保存为bmp文件。好了,关于avi文件的介绍就到这里结束了。

(socx2007)
本站文章除注明转载外,均为本站原创或编译欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,同学习共成长。转载请注明:文章转载自:罗索实验室 [http://www.rosoo.net/a/201111/15325.html]
本文出处:blog.csdn.net/socx2007 作者:socx2007
顶一下
(1)
50%
踩一下
(1)
50%
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:点击我更换图片
栏目列表
将本文分享到微信
织梦二维码生成器
推荐内容