DVB-subtitle解析流程浅
DTV包含SUBTITLE和TTX。
PMT中分别有不同的描述符对应,如下图的TTX descripter=0x56。语言ISO-639="fin"
subtitle descriptor = 0x59,如下图,当前节目有两个SUB,德语/英语
对于TTX ATV/DTV下有些差异:
1、ATV下TTV Language语言为 东欧/西欧/俄语/阿拉伯语/波斯语 5中语言,即对应TTX FONT的5中字库
2、DTV下会更多一点,在ATV的基础上还有 泰语/希腊语/斯拉夫语/希伯来语 等
DTV TTX FONT ENUM如下:
typedef enum {
APP_PREFERRED_TTX_WEST = ,
APP_PREFERRED_TTX_GREEK,
APP_PREFERRED_TTX_EAST,
APP_PREFERRED_TTX_ARABIC,
APP_PREFERRED_TTX_CYRILLIC,
APP_PREFERRED_TTX_RUSSIAN,
APP_PREFERRED_TTX_HEBREW,
APP_PREFERRED_TTX_FARSI,
APP_PREFERRED_TTX_CENTRAL_EUROPEAN,
APP_PREFERRED_TTX_WEST_EUROPEAN_TURKISH,
APP_PREFERRED_TTX_RUSSIA2,
APP_PREFERRED_TTX_BYELORUSSIAN,
APP_PREFERRED_TTX_THAI,
APP_PREFERRED_TTX_MAX,
} APP_PreferredTTX_t;
根据DTV 主菜单的 TT Language语言进行切换:
switch (u32TempValue)
{
case APP_DIGITAL_TT_ENGLISH:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
case APP_DIGITAL_TT_FRENCH:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
case APP_DIGITAL_TT_GERMAN:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
case APP_DIGITAL_TT_ITALIAN:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
case APP_DIGITAL_TT_SPANISH:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
case APP_DIGITAL_TT_PORTUGUESE:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
case APP_DIGITAL_TT_DANISH:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
case APP_DIGITAL_TT_CZECH:
*u32DTTZone = APP_PREFERRED_TTX_EAST;
break;
case APP_DIGITAL_TT_DUTCH:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
case APP_DIGITAL_TT_FINNISH:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
case APP_DIGITAL_TT_SWEDISH:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
case APP_DIGITAL_TT_GREEK:
*u32DTTZone = APP_PREFERRED_TTX_GREEK;
break;
case APP_DIGITAL_TT_RUSSIAN:
*u32DTTZone = APP_PREFERRED_TTX_RUSSIAN;
break;
case APP_DIGITAL_TT_POLISH:
*u32DTTZone = APP_PREFERRED_TTX_EAST;
break;
case APP_DIGITAL_TT_NORWEGIAN:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
case APP_DIGITAL_TT_WELSH:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
case APP_DIGITAL_TT_GAELIC:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
case APP_DIGITAL_TT_IRISH:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
case APP_DIGITAL_TT_THAI:
*u32DTTZone = APP_PREFERRED_TTX_THAI;
break;
default:
*u32DTTZone = APP_PREFERRED_TTX_WEST;
break;
}
参考ISO13818-1,EN-300743,从TS流解析开始,结合MS6328的code将DVB Subtitle的解析过程简单梳理了一下。
一、TS PAT PMT PES (参考ISO13818-1)
- 由TS包中取出PAT表
从搜台开始pat 解析:
MW_DVB_SI_PSI_Parser ::_ScanStar()下m_pPatParser->Start(PAT_SCAN_TIMEOUT)
这里mapi_demux_section_filter *m_pSecFilter = m_pDemux->AllocateSectionFilter()返回一个demux section filter 指针
这个section指定了:
stSecConfig.u16PID = PID_PAT;
stSecConfig.au8MatchByte[0] = TID_PAS; //table id
指定pat相应的section
(//这里的section,用来分段传输psi中各个table信息的结构,是去掉了TS 包头以后的TS payload 数据)
SI以及MPEG-2 PSI tables在被插入TS 包之前会被分段成一个或多个section。Section是可变长度的,每个table的section被限制在1024 bytes,除了EIT 表的section为4096bytes。Refer to《en_300468》
这里是取TS中PSI数据,区别于PES数据)
注意:有效负载(payload):指包中跟在包头后面的bytes。比如:TS 包的有效负载包括一个PES_packet_header 和PES_packtet_data_bytes; 或者,一个指针域(pointer_field)和 PSI_sections 或者 私有数据。
一个PES 包的有效负载只包含PES_packtet_data_bytes。另外,TS 包的包头和调整区域(adaptation fields)不是有效负载。
…
m_pSecFilter->Init(stSecConfig)
m_pSecFilter->Start()
…
解析完pat,得到有用的信息,兵存储于Pat Table结构中:
mapi_si_PAT_parser::Parse() { PatTbl.ServiceIDInfo[u16ServiceIndex].u16ServiceID PatTbl.ServiceIDInfo[u16ServiceIndex].u16PmtPID u16ServiceIndex++; PatTbl.u16ServiceCount = u16ServiceIndex }
- 由PAT得到对应的PMT
在_PatReady()下会将对应的从pat解析时得到的PmtPID存储如pCurProg 信息中 (serviceIID,OriginalNetworkId,TransportStream_id,NetworkId),PMT 会根据curprog.u16PmtPID来解析。
_PMT_Monitor()下sm_pCurPmtParser->Start(xx, PMTPID, xx)
mapi_si_PMT_parser::Start()
stSecConfig.u16PID = u16PMTPid;
stSecConfig.au8MatchByte[0] = TID_PMS; //table id
底层就会解析出相应PID的section数据,存储到pmt table中。
每次pmt的刷新,会将信息存到curprog中,相应的信息会传递给subtitle service结构体,MW_DTV_Subtitle::m_Services,通过队列存储起来:u16Sub_Pid,u8subtitling_type。
- 根据PMT得出subtitle相关信息
有了PMT信息,就可以得出subtitle 的信息了,所以当打开一个相应的subtitle时,就可以通过索引找到存储起来的subtitle信息。
….
if(m_pDVBSubtitle_decoder->Connect() == TRUE) { m_pDVBSubtitle_decoder->SetCA_PageID(u16composition_page_id,u16ancillary_page_id); //composion_pageid ancillary_pageid m_pDVBSubtitle_decoder->Open(u16PID, bFileIn); m_enOpenType = EN_OPEN_SUBTITLE_DVB; bSuccess = TRUE; }
注:ancillary_page_id只对于一个subtitle流中复合多个subtitle services(eg,不同语言,或者normal/hearing impaired)才有用,否则与composition_page_id一样
根据subtitle的PID(u16PID)就开始了subtitle的解析。
二、Subtitle 解析
- 下面是PES的处理
MW_DVBSubtitleDecoder::Connect()
这里创建subtitle的render相关,并且会create 一个线程(__sbt_thread())来处理subittle的数据。
__sbt_thread():
MW_DVBSubtitleDecoder::__process_PES()
<1>.在MW_DTV_Subtite::Init()下会有m_pDVBSubtitle_decoder->AllocatePESFilter()会分配一个PESFilter,注意与上面的SectionFilter区别。
<2>. m_pPES_Filter->Open(u16PID) 这里会传递需要解析的PID给相应的Pesfilter,然后再根据pes包头取得数据存处于buffer(一个完整的PES packet)。
这里 Stream_id必须是0xBD,表示private_stream_1(具体可查看iso13818-1 Table2-18)
<3>__compute_PTS(buffer)
取得PTS_DTS_flags(2bit)(pu8bff[7] & 0xc0 == 0x80 或者 0xc0)
当此值为‘10’,表示PTS fields存在与PES 包头
当此值为‘11’,表示PTS fields和DTS fields都存在与PES 包头
当此值为‘00’,表示PTS或者DTS fields都不会存在与PES包头
‘01’是禁止的
PTS 33bit,分3部分,DTS 也是33bit,同样是由3部分组成,这里我们只取PTS,用不到DTS(具体可以参考iso13818-1关于PES packet结构的spec)
if(((pu8bff[] & 0xc0) == 0x80) || ((pu8bff[] & 0xc0) == 0xc0)) { u32tmp = pu8bff[]; u32PTSLow = (u32tmp & 0x06); u32PTSLow <<= ; u32tmp = pu8bff[]; u32tmp <<= ; u32PTSLow |= u32tmp; u32tmp = pu8bff[] & 0xfe; u32tmp <<= ; u32PTSLow |= u32tmp; u32tmp = pu8bff[]; u32tmp <<= ; u32PTSLow |= u32tmp; u32tmp = pu8bff[] & 0xfe; u32tmp >>= ; u32PTSLow |= u32tmp; }
(当然,还有PTS的特殊情况,这里就用到了system time clock(STC)的概念
理想情况下:如果解码器的时钟频率与编码器匹配,那么解码速率就与编码时一致。这时候任何正确的 PCR(由TS取得)就可以用作解码器的STC。但实际情况是不可能匹配的。一种方法是通过phase-locked loop(PLL)来强制解码器的clock与收到的数据流一致。)
然后会将取得的一个完整的PES:buffer数据以及PTS压入一个队列DataQueue,_push(pu8Buf, u16Len, u32PTS)
mapi_dvb_subtitle_decoder::Main(MAPI_U32 u32STC)
…
pSubtitle->__GetCurrentPTS(&u32STC);
mapi_interface::Get_mapi_dvb_subtitle()->Main(u32STC);
…
这个main函数就是处理DataQueue中的数据
void mapi_dvb_subtitle_decoder::Main(MAPI_U32 u32STC) { if(DataQueue_CheckPTS(u32STC)) //查找DataQueue中所有PTS,若有一小于当前STC,则满足条件 { QueueElement qeCur; if(_pop(&qeCur) == MAPI_TRUE) {
__decode(&qeCur); _freeQueueElement(&qeCur); } } }
到这里就进入了subtitle的decode部分了
- __decode()
Mapi_dvb_subtitle_decoder.cpp (参考EN_300743)
从code中mapi_dvb_subtitle_decoder::__decode(QueueElement *qe){}可见解析流程:
__decode_PES_Header()->__decode_Segment()->__render_Display()->__resetObject()
(这段可参考pes_packet结构)
<1>pes prefix,并取得pes length
m_sbt_stream.SkipLen(4);
m_sbt_stream.GetWord(&m_u16_PES_length);
指针向后移动6个字节
<2>__decode_PES_Header()
这里取得pes headerlen,做一些校验,并根据headerlen 将PES header剔除
找出PES header的界限?
<3> __decode_CheckStreamID()
这里开始就是剔除了PES header的数据,开始解析subtitling segment数据
当是DVB subtitle stream时,PES_packet_data_bytes的结构如下:
end_of_PES_data_field_marker 这个占不占空间呢? 貌似不占
data_identifier 必须是0x20,subtitle_steam_id必须是0x00
<4> __decode_Segment()
这里取得page_id,segment_length,
还记得上面open subtitle之前
m_pDVBSubtitle_decoder->SetCA_PageID(u16composition_page_id,u16ancillary_page_id)这个接口吗?
这是从PMT中取得的compositon_page_id,ancillary_page_id.
在这里需要判断此segment的pageid是否等于上面之一,否则就跳过这个segment
if((u16_page_id != m_u16CPID) && (u16_page_id != m_u16APID)) { // skip this segment, not whole PES m_sbt_stream.SkipLen(u16_segment_length); m_u16_PES_length -= u16_segment_length; }
Segment_length 是指segment_data_field()的数据bytes,其具体的数据结构是由segment_type指定
Segment的第二个字节表示segment_type,,指定了这个segment 数据的类型
1) case 0x14 display definition segment
这里是假定显示的宽度是720pixels 显示的高度是576lines
Segment 结构如下:
定义了display definition的一些相关 具体可参考(en300743 7.2.1)
2)case 0x10 page composition segment
这个类型的sgement结构如下:
__decode_Page():
取得regioncount = u16_segment_length / 6 (page segment的长度是6个字节)
通过循环,取得所有有效region的region id,以及该region显示的位置坐标(h,v)
存处于一个m_page的结构体中
page_time_out:表示page instance存在的时间。主要是为了防止错误发生时(比如page instance其time out没有被重新定义或没有删除)导致page instance 一直显示
page state:
0x00:normal case,用于page update,仅显page instance有改变的subtitle elements
此case下说明region_id已经存在,需要判断是否已经存在。当然一开始page state不会是nomal case的情况。????
0x01:acquisition point 用于page refresh ,显示下一个page instance要用到的所有的subtitle 元素
这里主要执行__resetObject() 重置object的索引,version等
0x10 :mode change,用于new page,显示new page需要的所有subtitle 元素
这里需reset region,reset object,CLUT索引,screen buffer等
3)case 0x11 region compositon segment
__decode_Region()
Region_depth指定了pixel depth
CLUT_id 指定了用于这个region的CLUTS
Object_id 指定了这个region内显示的object(通过循环取得所有的object)
Region_fill_flag定义为1后,说明该region需要用背景色填充,填充的颜色由region_8-bit_pixel-code,region_4-bit_pixel-code,region_2-bit_pixel-code指定;
region_8-bit_pixel-code:指定8bit的CLUT表的入口,但仅仅当该region_depth是8 bit的时候才有效;
region_4-bit_pixel-code:指定4 bit的CLUT表入口。当region depth是4bit可用;或者region depth是8bit,且region_level_of_compatibility是4bit的时候才有用;
region_2-bit_pixel-code:指定2bit的CLUT表入口。单region depth为2bit,或者region_level_of_compatibility为2bit的时候可用。
region_level_of_compatibility:表示解码器满足的最小CLUT类型(2bit、4bit、8bit)
如果page state是normal case的情况,说明region_id并没有改变(与已经解析出的region信息作比较),无需重新创建region
(但是这里如果新来的region width/height 与已经解析出来的region的width/height不一样,需要替换并重新创建这个region)
如果 page state是mode change或acquisition point,需要重新解析region_id,重新创建region
这里解出的region_width region_height,不能超过上面DDS 段解除的display width,display height
根据取出的region_id,region_width,region_heigth,pixel_depth,来创建__createRegionBuffer(),最终通过DirectFB 来绘制一个window
__createRegionBuffer()最终是通过IDirectFB*做了两件事CreateWindow、SetPallete。
也就是说一个新的region,就对应了一个新的IDirectFBWindow*
大致情况如下:
If (可以显示了) //这个应该是表示这种surface是最终要显示出来的。 一般只需要创建一个可以显示的surface就可以了
{
- 通过IDirectFB*取得IDirectFBDisplayLayer*,存为m_pDFBLayer;这样就可以用过m_pDFBLayer做进一步的操作了。
- m_pDFBLayer-> SetCooperativeLevel (m_pDFBLayer, DLSCL_ADMINISTRATIVE)//设置合作级别。这里应该是表示管理权限,枚举出window,并管理它们
- m_pDFBLayer->GetConfiguration(m_pDFBLayer,&config)//获得当前layer的配置信息,如width、height、pixelformat等(这里的width、height来自与region_width、region_height)
当然获取config是为了重新设置之,设置好后,需调用m_pDFBLayer->SetConfiguration()
- m_pDFBLayer->GetScreen(m_pDFBLayer,&p_screen);//取得screen指针
然后再根据screen的方法p_screen->GetSize(p_screen,&screenwidth,&screenheight),取得screen的宽和高。
获取screen的宽和高,主要是为了判断与当前video的宽和高的关系(MS6328这段代码没看懂?),然后通过
m_pDFBLayer-> SetScreenLocation (m_pDFBLayer,x,y,width,height)//来设置screen的位置
- m_pDFBLayer->Create(m_pDFBLayer,&desc,&m_pDFBWindow)//由layer来创建window,其中desc是一些配置信息
- m_pDFBWindow->GetSurface(m_pDFBWindow,&m_Windows[u8WinID].pSurface)//
每次创建一个window,都获取一个surface,并存储到window数组中,所以一个region就对应一个window的数组元素,也对应一个surface
- m_pDFBWindow->SetOpacity(m_pDFBWindow->,0/0xff)
0表示全透明,隐藏window,0xff表示不透明
}
else //这种方式创建的surface不可以显示到屏幕上
{
//也就是创建一个region时,同时创建一个surface(由超级接口IDirectFB插口创建),并存储入数组里(宽高像素格式pitch等)
直接由IDirectFB创建一个surface,存储到数组中
m_pDFB->CreateSurface(m_pDFB, &surDesc,&m_Windows[u8WinID].pSurface)
surDesc指定了一些配置信息
在官网的例子中,看到surDesc.caps = DSCAPS_PRIMARY | DSCAPS_FLIPPING;,设置为primary surface,应该同上面是一样的功能???
pixelformat:使用的是DSPF_LUT8
width:region_width
height:region_heigth
}
在取得surface之后,设置surface的调色板
- m_Windows[u8WinID].pSurface->GetPalette(m_Windows[u8WinID].pSurface,&palette)//这里为什么无需SetPalette?Surface创建后就能直接得到GetPalette??
- 接着palette->SetEntries(palette,DFBColor*color,num_colors,offset)
接着palette->Release(palette)//这个接口在官网的接口定义里没有找到?
Region segment解析完后得到了一系列索引信息,object索引,CLUT索引等;具体的内容
和颜色还需进一步解析相关的segment。
我们知道surface代表一块预留的内存,用来保存像素数据,那么像素数据从哪里来呢?
在解析object segment的时候,会将像素数据存储到surface对应的内存地址中。(具体见解析object)
4)case 0x12 CLUT definition segment
循环解析出所有的cult信息,用YCbCrT 颜色系统存储,需转换成ARGB
按公式将YCbCrT的信息转换成ARGB相关,T直接转换为A(透明度),Y=0表示透明
(略写)
5)case 0x13 object data segment
object_coidng_method ==‘0x00’ 编码方式是像素编码,这种是graphical object(一般是这种情况)
object_coding_method==’0x01’ 编码方式是字符编码,这种是字符格式的字幕
一个object 是由top field 和bottom field交错组成。
每一个对象,用于top filed 的pixel-data sub-block 和用于 bottom field的 pixel-data sub-block必须存储在相同的object_data_segment内。 如果bottom field没有数据,则取top field的数据作为bottom field的数据。
对于pixel-data sub_block的结构可参考 EN_300743 7.2.5,详细定义了pixel data的编码规则。
在new region的时候创建了surface,那么这个时候可以获取到surface对应的存储像素数据的内存地址,往该内存地址写入数据即可。
m_Windows[u8WinID].pSurface->Lock(m_Windows[u8WinID].pSurface, DSLF_WRITE, (void **) pu8Buffer, &u32pitch) //DSLF_WIRTE,表示该地址可写的权限、
pu8Buffer是数据指针,u32pitch是行距
写入像素数据的起始地址是这么计算的:pu8Buffer+ object_horizontal_position(region中取得)+ object_vertical_position*pitch
这里涉及到图像压缩算法 run length encoding(行程编码):将一扫描行中的颜色值相同的相邻像素用一个计算值和那些像素值的颜色值来代替。比如aaabccccccddeee,用3a1b6c2d3e来代替
比如
run_lenght_3-10指示了2—bit_pixel_code重复的次数。。
m_Windows[u8WinID].pSurface->Unlock(m_Windows[u8WinID].pSurface);//记得执行这步
到这一步,surface的内存中已经有像素数据了,但是并不会立即显示出来。
1.surface是单buffer的(这个怎么判断呢?)
将每个region对应的surface,用StretchBlit()输入到OFFSCREEN特殊的surface:
m_Windows[u8DstWinID].pSurface->StretchBlit(DstSurface,OFFSCREEN , pSrcDFBRect, pDstDFBRect)
当然要使用StretchBlit之前,还需先用SetBlittingFlags(sur*,DSBLIT_NOFX)、SetRenderOptions(sur*,DSRO_SMOOTH_UPSCALE)这两个接口设置一下。
- surface是double buffer
pSurface->GetCapabilities(m_Windows[u8DstWinID].pSurface, &surfaceCaps)//取得该surface的相关属性
if(DSCAPS_DOUBLE == (surfaceCaps & DSCAPS_DOUBLE))//来判断是否是double buffer
pSurface->Flip(m_Windows[u8DstWinID].pSurface, NULL, (DFBSurfaceFlipFlags)DSFLIP_NONE)//用Flip将数据强行送到屏幕显示
最后别忘了pSurface->ReleaseSource(pSurface)//把该surface可能的引用全部释放掉
尤其在用了StretchBlit()方法后
PS:
MS6328创建了两个特殊的surface:
- CreateWindow(ONSCREEN_GWIN_ID, &rect, MAPI_TRUE, m_pCallbackArgument);//表示这个surface是可以显示出来的,具体的步骤可见上述region 解析部分
- CreateWindow(OFFSCREEN_GWIN_ID, &rect, MAPI_FALSE, m_pCallbackArgument);//这个surface是不可以显示出来的作为转存数据使用。(上面每次new region创建的surface也是无需显示的,可显示的只要一个即可)
6)0x80 end of display set segment
提供一种模糊的指示给解码器,表示display set已经传输完成。虽然没有什么实际用处,但这个segment是一定需要的。
上面解析出来的相关数据,通过DirectFB就可以显示出subtitle了。
三、 DVB subtitle system Overview
CLUT color look up table 颜色索引表,CLUT_id存在于每个region中,用于将object中的颜色伪码转换成实际显示的颜色。
objects 的使用和配置是由region compositon segment定义的
regions的使用和配置 是由 page compositon segment定义的,page compositon segment 包含了一系列供显示的region,每个都有特定的位置。此外,region可以被定义而不被使用。
同一时间可以显示不止一个region。
一个subtitle stream可以传输多个subtitle service(eg,不同语言,或者normal/hearing impaired)。
在这种情况下,每个特定的subtitle service是用page-id来区分的。
在同一个subtitle stream中不同subtitle service之间可以共享subtitling data。
当然更多的情况是用不同的PID区分不同的subtitle service。
Figure 2: Example of two ways of conveying dual language subtitles (one using shared data)
图表2的情况是没有ancillary page的,看来只有同一个stream中有多个service时才会用到此辅助页面。
注意:
一个单独sbutitle stream是不能同时有高清(包含display_definition_segment)和标清的sbutitle service.
在一个subtitle stream中,一个page id 是分配给每个segment。Segments可以只包含特定的service 的数据,也可以包含用于共享service的数据。故,一个stream中的数据段最多有两种paige id values;
一种page id value 指定的数据段用于特定数据 (composioning page id) 这是必选
另一种paage id value 指定共享数据段 (ancillary page id),可选(包含log数据)。 可参考上图。
Display set
一个subtitle service中具有相同PTS的整套segments就成为 display set。 Display set中的最后一个段必须跟着一个 end_of-display-set 段,表示没有更多的与此PTS相关的subtitle 数据了。
Segment type
1.display definition segment 可选,用户定义display size 针对高清subtitle
2.page composiont segment 定义接下来显示的pages,每个page包含的regions,time-out信息 和页面状态
3.region composition segment 一个region里有一个或多个objects,但只有一个CLUT(由CLUT-id指定);带有region的配置,region的属性:水平,垂直的size,背景色,
4.CLUT definition segment
5.object data segment 有两种类型的object:绘图objects和文本objects
6.end of display set segment
段数据中的 page id value应当与 subtitle descriptor中的compositon_page_id 或者ancillary_page_id相等。
Page compostions 是不能由复合subtitle services所共享的
每个page composition 段中的page id 应当与compositon_page_id相等
总的来说,subtitle system中的数据层次结构如下:
- Transport Stream (TS)
- Transport packets with the same PID
- PES packets,with PTSs providing timing information
- Subtitle service
- Segments signalled by the compositon page id and optionally the ancillary page id
- Where appropriate,a display definition segment
Subtitle data,containing information on page composition,region composition,CLUTs,objects and end of display set.
DVB-subtitle解析流程浅的更多相关文章
- HTML页面加载和解析流程详细介绍
浏览器加载和渲染html的顺序 1. IE下载的顺序是从上到下,渲染的顺序也是从上到下,下载和渲染是同时进行的. 2. 在渲染到页面的某一部分时,其上面的所有部分都已经下载完成(并不是说所有相关联的元 ...
- html页面加载和解析流程
HTML页面加载和解析流程 用户输入网址(假设是个html页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回html文件: 浏览器开始载入html代码,发现<head>标签内有一 ...
- 用户访问网页流程、DNS 解析流程
一.用户访问流程 二.DNS解析流程 DNS( Domain Name System)是“域名系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,它用于 TCP/IP 网络,它所提供的 ...
- Android中measure过程、WRAP_CONTENT详解以及 xml布局文件解析流程浅析
转自:http://www.uml.org.cn/mobiledev/201211221.asp 今天,我着重讲解下如下三个内容: measure过程 WRAP_CONTENT.MATCH_PAREN ...
- 简单理解DNS解析流程(一)
0x0 简单理解dns DNS服务器里存着一张表 表中放着域名和IP地址,域名和IP地址以映射关系保存,即一对一 浏览器访问某个域名,实际上是访问它的ip地址 所以浏览器需要知道域名对应的ip地址 如 ...
- Vue 路由导航解析流程
Vue Router完整的导航解析流程
- BeanDefinition 解析流程【解析所有配置类】
BeanDefinition 解析流程 BeanDefinition 解析入口:ConfigurationClassPostProcessor#postProcessBeanDefinitionReg ...
- Vue Router 路由守卫:完整的导航解析流程
完整的导航解析流程 1 导航被触发. 2 在失活的组件里调用离开守卫. 3 调用全局的 beforeEach 守卫. 4 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+). ...
- Android的init过程:init.rc解析流程
这几天打算看下安卓的代码,看优秀的源代码也是一种学习过程,看源代码的过程就感觉到,安卓确实是深受linux内核的影响,不少数据结构的使用方法全然一致.花了一中午时间,研究了下init.rc解析过程,做 ...
随机推荐
- Python学习之LeetCode刷题之路——简单题【1、7、9】
1.两数之和 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个 ...
- 如何使用fio模拟线上环境
线上表现 这里我想通过fio来模拟线上的IO场景,那么如何模拟呢? 首先使用iostat看线上某个盘的 使用情况,这里我们需要关注的是 avgrq-sz, avgrq-qz. #iostat -dx ...
- Luogu P1297 [国家集训队]单选错位
P1297 [国家集训队]单选错位 题目背景 原 <网线切割>请前往P1577 题目描述 gx和lc去参加noip初赛,其中有一种题型叫单项选择题,顾名思义,只有一个选项是正确答案.试卷上 ...
- HTTP服务和APACHE2
HTTP服务和APACHE2 知识点 请求报文响应报文 错误码 请求重定向 编译安装 实现https curl工具 1. http协议 http协议版本 http/0.9, http/1.0, htt ...
- linux hexdump-显示文件十六进制格式
博主推荐:获取更多 linux文件内容查看命令 收藏:linux命令大全 hexdump命令一般用来查看“二进制”文件的十六进制编码,但实际上它能查看任何文件,而不只限于二进制文件. 语法 hexdu ...
- ACM-ICPC 2018 徐州赛区网络预赛 J. Maze Designer
传送门:https://nanti.jisuanke.com/t/31462 本题是一个树上的问题:结点间路径问题. 给定一个有N×M个结点的网格,并给出结点间建立墙(即拆除边)的代价.花费最小的代价 ...
- win7 32位机安装VMware win7 64位虚拟机
VMware10虚拟机怎么安装win7系统(详细教程):https://jingyan.baidu.com/article/86f4a73ec62e8f37d65269a1.html 然而上述教程想不 ...
- poj 1562 简单深搜
//搜八个方向即可 #include<stdio.h> #include<string.h> #define N 200 char ma[N][N]; int n,m,vis[ ...
- 【Tomcat】Tomcat替换猫的图片
参考:网页title上添加图片 直接替换Tomcat安装目录下ROOT下面的favicon.ico图标(名字与前面一样favicon.ico)
- 遇到很多次,要注意区分service调用,本地用户调用这些区别
WTSQueryUserToken返回1314 The WTSQueryUserToken function obtains the primary access token of the log ...