http://blog.csdn.net/menguio/article/details/6406240

AVI文件在opencore框架下的解析

参考相关文档及opencore中pv_avifile_parse等实现,分析opencore下AVI文件解析的实现过程。

1. AVI容器介绍
    AVI是微软1992年推出用于对抗苹果Quicktime的技术,尽管国际学术界公认AVI已经属于被淘汰的技术,但是由于windows的通用性,和简单易懂的开发API,还在被广泛使用。
  AVI的文件结构分为头部、主体和索引三部分。主体中图像数据和声音数据是交互存放的。从尾部的索引可以索引跳到自己想放的位置。AVI本身只是提供了这么一个框架,内部的图像数据和声音顺据格式可以是任意的编码形式。因为索引放在了文件尾部,所以在播internet流媒体时已属力不从心。很简单的例子,从网络上下载的片子,如果没有下载完成,是很难正常播放出来。
另外一个问题是AVI对高码率VBR音频文件支持不好。VBR全称是Variable BitRate,就是动态比特率,可以根据当前的需要定义不同的比特率,避免了浪费,并且提高了利用率。随之问题也就来了,因为容器里的图像和声音是分开的,所以播放时需要一个图像和声音的同步过程,如果CBR音轨的话因为码率是定值,同步不成为问题,可是VBR音轨是不断的在变换,而AVI没有时间戳去让VBR音轨和图像同步,这样就会产生图像声音不同步的问题。
兼容的视频编码:MPEG-2、MPEG-4、MPEG-4 H.264、VC-1(支持不太好)、VC-1;兼容的音频编码:Linear PCM、DTS-HD DTS、Dolby Digital、AC3、Dolby Digital Plus、Dolby TrueHD、DTS Digital Surround。

2 . AVI文件组织方式
    AVI(Audio Video Interleaved的缩写)是一种RIFF(Resource Interchange File Format的缩写)文件格式,多用于音视频捕捉、编辑、回放等应用程序中。通常情况下,一个AVI文件可以包含多个不同类型的媒体流(典型的情况下有一个音频流和一个视频流,现在有非标准插件可加入最多两个音频轨道),不过含有单一音频流或单一视频流的AVI文件也是合法的。AVI可以算是Windows操作系统上最基本的、也是最常用的一种媒体文件格式。
    RIFF格式是一种树状的结构,其基本组成单元为LIST和CHUNK,分别如树的节点和叶子。RIFF格式也类似windows文件系统的组织形式,windows文件系统有目录和文件,分别对应RIFF中的LIST和CHUNK。Windows文件系统中的目录可以包含子目录和文件,而文件是保存数据的基本单元,RIFF也使用了这样的结构。在RIFF文件中,数据保存的基本单元是CHUNK,可用于保存音视频数据或者一些参数信息,LIST相当于文件系统的目录,可以包含多个CHUNK或者多个LIST。
    AVI文件的两种原子类型:
(1)LIST
    typedef struct{
        DWORD dwList;
        DWORD dwSize;
        DWORD dwFourCC;
        BYTE  data[dwSize -4];  //contain lists and chunks
    }LIST;
(2)CHUNK
    typedef struct{
        DWORD dwFourCC;
        DWORD dwSize;
        BYTE  data[dwSize ];   //contain headers or video/audio data
    }CHUNK;
    用LIST类型的只有 ‘RIFF’或’LIST’,类似树枝,而CHUNK类似叶子。下图中ID为RIFF、hdrl、strl、odml、INFO、movi均为LIST类型,hdrl LST块定义AVI文件的数据格式,movi LIST包含音视频序列数据,idx1 CHUNK存放索引数据,该块可选。

AVI Tree

文件结构图
    
2.1 RIFF LIST结构
    dwList为’RIFF’,dwFourCC为’AVI’,当前不支持AVIX(open DML AVI),这是一个LIST,后续的filedata部分包含信息块、数据块和索引块,可以是LIST也可以是CHUNK,需要递归解析(注:Pv_avifile_parse.cpp中ParseFile函数采用循环解析所有的第一层LIST,但没有解析LIST中具体内容)。在这个解析过程中需要先解析出第一个chunktype为RIFF和第二个chunktype必须为AVI(与MPEG4Extractor.cpp类似,用压栈保存chunktype),后续的fileData采用递归解析。
    
2.2 HDRL LIST信息块
    dwList为’LIST’,dwFourCC为’hdrl’,data部分包含了avih CHUNK和其他strl LIST,需要递归解析。Size部分是指从dwFourCC开始的字段大小。
2.2.1 AVIH CHUNK
    AVIH CHUNK中data字段结构如下:

AVIH Data
    Opencore 的PV_avifile_header.cpp代码中解析了上述所有字段iMicroSecPerFrame、iMaxBytesPerSec、iPadding、iFlags、iTotalFrames、iInitialFrames等,这些字段保存在PVAviFileMainHeaderStruct结构体中,其中iFlags字段用对应位表示5个标志:AVIF_COPYRIGHTED、AVIF_HASINDEX、AVIF_ISINTERLEAVED、AVIF_MUSTUSEINDEX、AVIF_WASCAPTUREFILE。

2.2.2 STRL LIST
    这个LIST部分包括以下几个CHUNK:STRH、STRF、STRD、STRN、JUNK。iStreamListSize保存STRL LIST的data部分长度。
2.2.2.1 STRH CHUNK
    STRH CHUNK中data字段结构如下:
    
    
    注意:wPriority和wLanguage并不是四字节存储,解析时注意偏移位置,dWFlags有两种取值:AVISF_VIDEO_PALCHANGES、AVISF_DISABLED。
    rcFrame包括上left、top、right、bottom四个值,分别用short int(占两个字节)保存。
    帧率的计算:SamplingRate(samples/second)= dwRate/dwScale。

2.2.2.2 STRF CHUNK
    Strf结构与strh的fccType定义的类型有关。AVIStream::fccType的可能取值有:PV_2_AUDIO、PV_2_VIDEO、MIDI和TEXT。
(1)AVIStream::fccType为视频PV_2_VIDEO时,STRF CHUNK的结构为BITMAPINFO:
    typedef struct
    {
        BitmapInfoHhr  BmiHeader;  //header
        uint32         BmiColorsCount;  //number of RGBQuads actually present
        RGBQuad      BmiColors[MAX_COLOR_TABLE_SIZE];
    } BitMapInfoStruct;
    RGBQuad结构如下:
    typedef struct
    {
        uint8   Blue;
            uint8   Green;
        uint8   Red;
        uint8   Reserved;
    } RGBQuad;
    BitmapInfoHhr结构如下:

biClrUsed标识了BmiColorsCount,当这个字段值为0时,需要由biBiBitCount字段(每帧的比特数)决定color table的大小(BmiColorsCount):
    biBiBitCount =1时,color table size = 2^1;
    biBiBitCount = 2时,color table size = 2^2;
    biBiBitCount = 4时,color table size = 2^4;
    biBiBitCount = 8时,color table size = 2^8;
    biBiBitCount = 16/32时,color table size由biCompression的值决定:
        BiCompression= BI_BITFIELDS,color table size = 3;
        BiCompression= BI_ALPHABITFIELDS,color table size = 4;
        BiCompression= BI_RGB,color table size = 0;
    biBiBitCount = 24时,color table size = 0;
    BmiColors数组用来存放color table entry,详见ParseStreamFormat函数中的解析过程。

(2) AVIStream::fccType为音频PV_2_AUDIO时,STRF CHUNK结构为WAVEFORMAT:

2.2.2.3 STRD CHUNK(可选)
    strd chunk为可选部分,紧跟在strf chunk后,保存编解码器需要的一些配置信息,文件规范中没有具体说明,参考opencore代码,包含size和data部分。
    DWORD iCodecSpecificHdrDataSize;  //大小
    BYTE   ipCodecSpecificHdrData[DataSize];    //数据

2.2.2.4 STRN CHUNK(可选)
    DWORD strnSz;  //大小
    CHAR   iStreamName [MAX_STRN_SZ];    //数据
    保存流的名字,当strnSz<MAX_STRN_SZ时,iStreamName保存strnSz个BYTE;当strnSz>MAX_STRN_SZ,保存MAX_STRN_SZ 个BYTE。PVAviFileStreamlist函数中临时分配的空间strn似乎多此一举。

2.2.2.5 JUNK CHUNK
    为了字节对齐填充的字段,不解析。

2.2.3 JUNK LIST
    为了字节对齐填充的字段,可不解析。

2.3 MOVI LIST数据块
    dwList为’LIST’,dwFourCC为’movi’。Size部分是指从dwFourCC开始的字段大小。movi列表保存的是真正的媒体流数据,其数据组织方式有两种。可以将数据块直接嵌在‘movi’列表里面,也可以将几个数据块分组成一个‘rec ’列表后再编排进‘movi’列表(读取数据时最好将整个rec读出)。每个数据块使用了一个四字符码来表征它的类型,这个四字符码由2个字节的类型码和2个字节的流编号组成。标准的类型码定义如下:‘db’(非压缩视频帧)、‘dc’(压缩视频帧)、‘pc’(改用新的调色板)、‘wb’(音缩视频)。比如第一个流(Stream 0)是音频,则表征音频数据块的四字符码为‘00wb’;第二个流(Stream 1)是视频,则表征视频数据块的四字符码为‘00db’或‘00dc’。对于视频数据来说,在AVI数据序列中间还可以定义一个新的调色板,每个改变的调色板数据块用‘xxpc’来表征,新的调色板使用一个数据结构AVIPALCHANGE来定义。(注意:如果一个流的调色板中途可能改变,应在这个流格式的描述中,也就是PVAviFileStreamHeaderStruct结构的dwFlags中包含一个AVISF_VIDEO_PALCHANGES标记)。另外,文字流数据块可以使用随意的类型码表征。

MOVI LIST的data部分每一个子chunk存放的是sample信息,sample所属的stream no可由indx的字节/16计算而来。
    
2.4 IDX1 LIST索引块(非必需)
    dwList为’LIST’,dwFourCC为’idx1’。Size部分是指从dwFourCC开始的字段大小。结构如下(如下为AVI1.0的结构,AVI2.0不同):
    
    索引块包含数据块在文件中的位置索引,能提高avi文件的读写速度,其中存放着一组AVIINDEXENTRY结构数据,这个块并不是必需的,也许不存在。dwChunkId包含了两字节的stream number(该sample所属的stream号)和两个字节的封装类型码,因此这个字段解析上有一个实现细节,dwChunkId值类似‘00dc’:十六进制30 30 64 63(转成十进制ASCII码为48 48 100 99对应字符“00dc”),chunkId前两个字节包含了streamno,并且用ASCII码表示,如存放的是’0’和’0’字符,最后转成十进制为0*10+0=0(PV_atoi),“01wb”(30 31 77 62)前两个字节转成十进制就是1。
    iIndexTable是streams数组,每一个成员又是IndxTblVector类型的,即iIndexTable[aStreamNo]是一个IndxTblVector类型,IndxTblVector的每个成员是IdxTblType类型。每个iIndexTable[aStreamNo]又是一个数组,每个成员是一个sample。相当于二维数组,第一维大小sizeof(iIndexTable)= stream numbers,sizeof(IndxTblVector)=一个stream中的sample数,iIndexTable中的最小信息单元是一个sample。

2.5 INFO LIST
    dwList为’LIST’,dwFourCC为’info’,Size部分是指从dwFourCC开始的字段大小。Opencore代码中没有解析这一部分。

2.6 JUNK LIST
    为了字节对齐填充的字段,可不解析。

2.7 关键计算过程
2.7.1 计算时间戳
    参考GetNextMediaSample中的计算部分
    音频根据帧率计算:该帧大小arSize/每帧所占的字节数=samplecount
    iTimeStampAudio = iTimeStampAudio+(sampleCount * 1000) / samplingRate
    视频根据帧间隔计算:frameDurationInms =(iMainHeader.iMicroSecPerFrame) / 1000
    arTimeStamp = (iStreamSampleCount[aStreamNo] * (frameDurationInms))

2.7.2 查找sample
    (1) 确定时间,相对于媒体时间坐标系统;
    (2) 查找对应该时间戳的sample序号(需要在解析的时候保存到iIndexTable表,如果不存在该索引表,需要在解析movi chunk时记录下来);
    (3) 根据sample号查找该sample在文件中位移和大小(索引表iIndexTable或movi chunk);

2.7.3 查找关键帧
必须要用Idx1中的标志位??

3 Opencore代码分析
    分析代码:Pv_avixxx.cpp、Pvmi_mio_avi_wav_file.cpp
3.1 数据结构存储方式

数据结构图
    PVAviParser包括PVAviFileHeader和PVAviFileIdxChunk,包括HDRL LIST、IDX1 CHUNK。
    PVAviFileHeader类包含PVAviFileStreamlist类型的vector—>iStreamList列表,一个strl list对应一个PVAviFileStreamlist对象,hdrl list中可以包含多个strl list(流)。
    PVAviFileStreamlist(HDRL LIST的data部分)包含PVAviFileStreamHeaderStruct(avih chunk)和PVAviFileStreamFormatStruct,PVAviFileStreamFormatStruct存储的是strl list解析出来的data数据,其中的strf chunk结构取决于strh的fccType类型,对应有BitMapInfoStrunct和WaveFromatExStruct两种,因此在PVAviFileStreamFormatStruct中iVidBitMapInfo和iAudWaveFormatEx是以共用体定义的。
    为了查找到sample等信息,这里有几个重要的字段:
    Oscl_Vector < uint32,OsclMemAllocator >  iStreamCount;   文件中的stream数
    Oscl_Vector < uint32,OsclMemAllocator >  iStreamSampleCount; 该数值从0开始,表示当前该stream中已读到的sample数,该字段随着读取文件的过程更新,因此该stream下次要读取的sample号 = 已经读取的sample数。
    //stores current offset if index table is not present
uint32  iSampleOffset;                    当前要读取的最新sample位移
//store latest sample offset if index table is not present
    Oscl_Vector < uint32,OsclMemAllocator >  iStreamSampleOffset;   链表,存放每个stream下次要读取的sample位移(位移相对文件开始,如果stream0的sample1已经读取,那么存放的是sample2的位移)

3.2 过程分析

解析过程图
3.2.1 文件解析入口
    Pv_avifile_parse.cpp中ParseFile()函数采用循环解析所有的第一层LIST,同时递归解析子LIST或CHUNK。在这个过程中保存了以下信息:iFileSize (文件大小:从AVI开始的部分)、iHeaderChunkSize(hdrl list data部分的长度)、ipFileHeader(hdrl list的数据部分指针)、iMovieChunkSize(movi list data部分的长度)、iMovieChunkStartOffset(movi chunk在文件中的偏移,第一帧偏移,当indx table不存在时使用)、iSampleOffset(= iMovieChunkStartOffset,保存当前位移,当indx table不存在时使用)、iStreamCount、iStreamSampleCount、iIndxChunkSize、ipIdxChunk(Idx的数据部分指针) 、iIdxChunkPresent,递归解析PVAviFileHeader :OSCL_NEW(PVAviFileHeader..)、PVAviFileIdxChunk :OSCL_NEW(PVAviFileIdxChunk..)。

3.2.2 解析HDRL  LIST
    PVAviFileHeader解析HDRL LIST的数据部分,从AVIH Chunk开始,过程与ParseFile类似,调用ParseMainHeader解析AVIH Chunk,调用OSCL_NEW(PVAviFileStreamlist,..)递归解析STRL,JUNK部分可不解析。
    ParseMainHeader()解析AVIH Chunk,具体解析字段内容见2.2.1。
    PVAviFileStreamlist()解析STRL LIST,过程与PVAviFileHeader和ParseFile类似。ParseStreamHeader解析STRH Data部分,具体字段参考2.2.2.1。ParseStreamFormat解析STRF部分,strf结构与strh fccType定义的类型相关,参考2.2.2.2。

3.2.3 解析IDX1 CHUNK
    PVAviFileIdxChunk解析IDX1 CHUNK,类似二维数组,第一维是stream,第二维是stream中的sample信息。

3.3 内部接口、对外接口及关键函数实现
入口:Pv_avifile.cpp
模块内部接口:
3.3.1 Pv_avifile_streamlist.cpp中的接口
3.3.1.1获取指定流的音频格式 GetAudioFormat(uint32 aStreamNo) -> GetAudioFormat()
    HDRL LIST解析后保存了streamlist(PVAviFileStreamlist类型),通过strf chunk读取WaveFormatExStruct中的FormatTag字段。
3.3.1.2 获取指定流的声道数GetNumAudioChannels(uint32 aStreamNo)-> GetNumAudioChannels()。
    HDRL LIST解析后保存了streamlist(PVAviFileStreamlist类型),通过strf chunk读取WaveFormatExStruct中的Channels字段。

3.3.2 Pv_avifile_streamlist.cpp中的接口,提供PVAviFileStreamlist中的相关信息
3.3.2.1获取当前流的类型GetStreamMimeType(),由iStreamTypeFCC决定,有四种类型:AUDS、VIDS、MIDS、TXTS。
3.3.2.2 GetHandlerType(uint8* aHdlr, uint32& aSize)获取strh中的fccHandler。
3.3.2.3 GetBitsPerSample()获取sample的bit数。
3.3.2.4 GetAudioFormat()获取音频格式,被PVAviFileHeader::tAudioFormat(uint32 aStreamNo)调用。
3.3.2.5 GetNumAudioChannels()获取声道数,被PVAviFileHeader::tNumAudioChannels(uint32 aStreamNo)调用。
3.3.2.6 GetVideoWidth()获取视频宽度。
3.3.2.7 GetVideoHeight()获取视频高度。
3.3.2.8 GetFormatSpecificInfo获取strf chunk解析的内容。
3.3.2.9 GetCodecSpecificData获取解码信息。

3.3.3 Pv_avifile_parser_utils.cpp 提供模块内公共函数
3.3.3.1 ReadNextChunkType读取下一个chunk的类型。
3.3.3.2 read32 读取四个字节,涉及到字节序转换。
3.3.3.3 read8读取一个字节,不考虑字节序。
3.3.3.4 read16读取两个字节,考虑字节序转换。
3.3.3.5 GetStreamNumber 将前两个字节存放的ASCII码值转换成十进制

模块的对外接口:(Pv_avifile.h)
3.3.4 Pv_avifile_parser.cpp提供读帧的对外接口
    Pv_avifile_parse.h和Pv_avifile.h中有相关说明
3.3.4.1 PVAviFileParser::GetNextMediaSample(uint32& arStreamNo, uint8* aBuffer,
                                    uint32& arSize, uint32& arTimeStamp)
    从movi chunk中按顺序读取帧信息,arStreamNo表示当前sample所属的stream number,aBuffer返回media sample buffer,size是返回的sample大小,arTimeStamp是sample的时间戳信息。
    具体实现过程:iSampleOffset从当前位移读取sample,CurrOff表示上次读取的sample位移。

3.3.4.2 PVAviFileParser::GetNextStreamMediaSample(uint32 aStreamNo, uint8* aBuffer, uint32& arSize, uint32& arTimeStamp) 获取stream中的下一个sample的buffer、大小和时间戳信息(iStreamSampleCount[aStreamNo]表示当前stream中要读取的下一个sample号,iStreamSampleOffset[aStreamNo]表示当前stream中要读取的下一个sample的位移)
    具体实现过程:不存在indx时,直接通过movi chunk的起始位移、该stream最新的sample位移(即下一次要读取的sample位移)等信息进行计算;存在indx时,从ipIdxChunk中存放的每个sample信息获取。
问题:stream中的sample是连续存放的么?各个stream的sample之间应该是交错存放的
    iIndexTable表中每一stream的sample是按序存放的,在movi chunk中同一stream的sample也是按时间顺序存放的,但其中夹杂着其他stream的sample信息,总的而言,按时间戳顺序存放。

3.3.4.3 PVAviFileParser::GetNextStreamSampleInfo(uint32 aStreamNo, uint32& arSize, uint32& arOffset)获取stream中的下一次要读取的sample位移和大小(以iStreamSampleOffset[aStreamNo]决定的位移为基准进行查找)
    具体实现过程:不存在indx chunk,直接返回失败;存在indx chunk时,从ipIdxChunk中获取位移和大小,位移有两种情况:一是相对于文件开始,另一种是相对movi开始处的位移。ipIdxChunk->GetOffset(aStreamNo, iStreamSampleCount[aStreamNo])

4 参考资料
【1】http://blog.csdn.net/njuitjf/archive/2010/06/19/5680632.aspx
【2】http://www.jmcgowan.com/avi.html
【3】http://msdn.microsoft.com/en-us/library/ms779636.aspx
【4】http://msdn.microsoft.com/en-us/library/dd756808(v=VS.85).aspx
【5】http://pvdtools.sourceforge.net/aviformat.txt
【6】http://blog.csdn.net/ydfy6/archive/2009/09/18/4567230.aspx
【7】http://www.featheast.com/it/video-on-the-web-html5
【8】http://www.codesoso.com/default.aspx
【9】http://soft.cnzer.cn/view-17836-3.html
【10】http://doxygen.reactos.org/d1/dc8/avifile_8c_source.html#l00203
    其他PDF文档在smb://192.168.9.200/ds/lei.yi/avi下
AVI spec here
http://www.wotsit.org/list.asp?page=2&fc=0&search=&al=
AVI player implementations
http://sourceforge.net/project/showf...group_id=91593
http://avifile.sourceforge.net/
http://freshmeat.net/
http://www.topshareware.com/avi-parser/downloads/1.htm
http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/4a284338-4809-4cdd-8cbd-50b4f440fdde/
http://doxygen.reactos.org/d1/dc8/avifile_8c_source.html
ffmpeg

5 工具
    RIFFSpot.exe 该工具不能具体解析movi list中的内容,只能粗略解析文件结构
    Linux下的ghex2显示文件的十六进制信息

AVI文件在opencore框架下的解析相关推荐

  1. java struts2上传文件_java Struts2框架下实现文件上传功能

    本文实例为大家分享了struts2框架实现文件上传的方法,供大家参考,具体内容如下 struts2的配置过程 (1)在项目中加入jar包 (2)web.xml中filter(过滤器)的配置 xmlns ...

  2. vc++实现avi文件的操作 用于视频解析及录制(转)

    vc++实现avi文件的操作 为了对avi进行读写,微软提供了一套API,总共50个函数,他们的用途主要有两类,一个是avi文件的操作,一类是数据流streams的操作. 1.打开和关闭文件 AVIF ...

  3. AVI文件格式解析+AVI文件解析工具

    (转)AVI文件格式解析+AVI文件解析工具 AVI文件解析工具下载地址:http://download.csdn.net/detail/zjq634359531/7556659 AVI(Audio ...

  4. 【转】解析.Net框架下的XML编程技术

    [引自突破思维的禁忌的博客]一.前言 XML是微软.Net战略的一个重要组成部分,而且它可谓是XML Web服务的基石,所以掌握.Net框架下的XML技术自然显得非常重要了.本文将指导大家如何运用C# ...

  5. springMVC框架下JQuery传递并解析Json数据

    json作为一种轻量级的数据交换格式,在前后台数据交换中占领着很重要的地位.Json的语法很简单,採用的是键值对表示形式.JSON 能够将 JavaScript 对象中表示的一组数据转换为字符串,然后 ...

  6. ABP框架下文件下載

    ABP框架下添加DevExpress框架生成報表,記錄一下學習過程 方式1,原始方式大概思路,打開Controller的方法CreateInRecordReport創建一個本地臨時文件,返回文件名,再 ...

  7. 基于vue框架下使用Element-UI获取文件MD5值并上传

    基于vue框架下使用Element-UI获取文件MD5值并上传 使用插件: spark-md5 .vue页面 <el-uploadclass="avatar-uploader idca ...

  8. Redis:Spring框架下Redis的配置和调用,xml文件中redis的配置,redisTemplate的使用和jedis的使用

    老规矩了,再次重复一遍,配置XML文件为Spring框架所属,所使用的框架是Spring,非SpringBoot!!! Spring框架整合Redis并且使用 1.配置文件 <bean clas ...

  9. 使用VisualStudio2022插件(Visual Studio Installer Projects 2022)打包 .Net 6 框架下的 WPF项目 为安装文件

    目录 序言 一.还是安装Visual Studio Installer Projects 2022插件 二.创建Setup Project项目 2.1在现有解决方案中添加Setup Project项目 ...

  10. ssm框架下的文件上传和下载

    ssm下的文件上传和下载 1. 文件上传 1.1 文件上传需要的依赖 文件上传需要使用到 commons-fileupload 和 commons-io 两个 jar 包. <dependenc ...

最新文章

  1. 7000 字 23 张图,Pandas一键生成炫酷的动态交互式图表
  2. SAP MM 采购订单与相关合同的价格差异问题分析
  3. CenterNet2:比强更强的二阶段网络,COCO成绩最高达到56.4mPA
  4. Android 使用图片铺满某个区域
  5. unix网络编程 的环境配置
  6. .NET Core HttpClient请求异常思考
  7. java幻灯片播放代码_简单常用的幻灯片播放实现代码
  8. 神经网络不学习的原因
  9. mysql df_DF学Mysql(一)——数据库基本操作
  10. Json字符串和Java对象互相转换
  11. JS Bin 在线编辑代码,所见所得
  12. ValidatorUtil验证工具类判断手机、ip地址、邮箱,身份证等
  13. 信息系统项目管理--案例分析笔记
  14. PHP常用代码大全(新手入门必备)
  15. Android开发眼镜店管理系统,智能眼镜店管理系统(基于BS架构互联网版)下载_智能眼镜店管理系统(基于BS架构互联网版)官方下载-太平洋下载中心...
  16. 网络安全基础——对称加密算法和非对称加密算法(+CA数字证书)
  17. “繁盛计划”,纾困中国餐饮难题背后的美团式解法
  18. CSDN如何上传gif图片
  19. [转载]【电子书下载神器】太给力了!你还找不到想要的电子书吗?
  20. make: *** [Makefile:44:obj/start.o] 错误 127

热门文章

  1. 简单了解一下电商系统中的SPU、SKU、ID,它们都是什么意思,三者又有什么区别和联系呢?
  2. Apache Spark 不过时的六大理由
  3. 如何删除浏览器7654导航首页
  4. 设计分享 | STM32F103RCT6利用ULN2003驱动步进电机正反转
  5. 面试时会问到的项目中的问题总汇
  6. 华为谷歌安装器 Android6.0,gms安装器华为
  7. linux内核旋转屏幕,全志A33屏幕旋转(Android)
  8. 京东联盟api集成的坑
  9. CentOS7安装显卡驱动
  10. cαr怎么发音_韵母a的发音情况是()。