DVB-subtitle解析流程浅

Posted qiuri2008

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了DVB-subtitle解析流程浅相关的知识,希望对你有一定的参考价值。

    参考ISO13818-1,EN-300743,TS流解析开始,结合MS6328codeDVB Subtitle的解析过程简单梳理了一下。

 

一、TS  PAT  PMT  PES (参考ISO13818-1)

  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 包之前会被分段成一个或多个sectionSection是可变长度的,每个tablesection被限制在1024 bytes,除了EIT 表的section4096bytesRefer toen_300468

这里是取TSPSI数据,区别于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

}

 

  1. PAT得到对应的PMT

_PatReady()下会将对应的从pat解析时得到的PmtPID存储如pCurProg 信息中 (serviceIIDOriginalNetworkId,TransportStream_id,NetworkId),PMT 会根据curprog.u16PmtPID来解析。

 

_PMT_Monitor()sm_pCurPmtParser->Startxx, PMTPID, xx

mapi_si_PMT_parser::Start()

         stSecConfig.u16PID = u16PMTPid;

         stSecConfig.au8MatchByte[0] = TID_PMS; //table id

底层就会解析出相应PIDsection数据,存储到pmt table中。

每次pmt的刷新,会将信息存到curprog中,相应的信息会传递给subtitle service结构体,MW_DTV_Subtitle::m_Services,通过队列存储起来:u16Sub_Pid,u8subtitling_type。

 

  1. 根据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 serviceseg,不同语言,或者normal/hearing impaired)才有用,否则与composition_page_id一样

 

根据subtitlePIDu16PID)就开始了subtitle的解析。

 

 

二、Subtitle 解析

  1. 下面是PES的处理

 MW_DVBSubtitleDecoder::Connect()

这里创建subtitlerender相关,并且会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_flags2bit(pu8bff[7] & 0xc0 == 0x80 或者 0xc0)  

   当此值为10’,表示PTS fields存在与PES 包头

   当此值为11’,表示PTS fieldsDTS fields都存在与PES 包头

   当此值为00’,表示PTS或者DTS fields都不会存在与PES包头

   ‘01’是禁止的

PTS   33bit,分3部分,DTS 也是33bit,同样是由3部分组成,这里我们只取PTS,用不到DTS(具体可以参考iso13818-1关于PES packet结构的spec

if(((pu8bff[7] & 0xc0) == 0x80) || ((pu8bff[7] & 0xc0) == 0xc0))

    {

        u32tmp = pu8bff[9];

        u32PTSLow = (u32tmp & 0x06);

        u32PTSLow <<= 29;

        u32tmp = pu8bff[10];

        u32tmp <<= 22;

        u32PTSLow |= u32tmp;

         u32tmp = pu8bff[11] & 0xfe;

        u32tmp <<= 14;

        u32PTSLow |= u32tmp;

         u32tmp = pu8bff[12];

        u32tmp <<= 7;

        u32PTSLow |= u32tmp;
 
        u32tmp = pu8bff[13] & 0xfe;

        u32tmp >>= 1;

        u32PTSLow |= u32tmp;

}

(当然,还有PTS的特殊情况,这里就用到了system time clockSTC)的概念

理想情况下:如果解码器的时钟频率与编码器匹配,那么解码速率就与编码时一致。这时候任何正确的 PCR(由TS取得)就可以用作解码器的STC。但实际情况是不可能匹配的。一种方法是通过phase-locked loop(PLL)来强制解码器的clock与收到的数据流一致。)

 

然后会将取得的一个完整的PESbuffer数据以及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);

        }

    }

}

 

到这里就进入了subtitledecode部分了

 

  1. __decode()   

Mapi_dvb_subtitle_decoder.cpp (参考EN_300743)

 codemapi_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.

在这里需要判断此segmentpageid是否等于上面之一,否则就跳过这个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 数据的类型

1case 0x14   display definition segment

这里是假定显示的宽度是720pixels 显示的高度是576lines

  Segment 结构如下:

 

 定义了display definition的一些相关 具体可参考(en300743 7.2.1

2case 0x10  page composition segment

      这个类型的sgement结构如下:

   

 

 __decode_Page():

取得regioncount = u16_segment_length / 6 page segment的长度是6个字节)

通过循环,取得所有有效regionregion id,以及该region显示的位置坐标(hv

存处于一个m_page的结构体中

   

page_time_out:表示page instance存在的时间。主要是为了防止错误发生时(比如page instancetime out没有被重新定义或没有删除)导致page instance 一直显示

page state

  0x00normal case,用于page update,仅显page instance有改变的subtitle elements

   case下说明region_id已经存在,需要判断是否已经存在。当然一开始page state不会是nomal case的情况。????

 0x01acquisition point 用于page refresh ,显示下一个page instance要用到的所有的subtitle 元素

          这里主要执行__resetObject()  重置object的索引,version

0x10 mode change,用于new  page,显示new page需要的所有subtitle 元素  

              这里需reset region,reset object,CLUT索引,screen buffer

 3case 0x11  region compositon segment

 

 __decode_Region()

 Region_depth指定了pixel depth

 CLUT_id  指定了用于这个regionCLUTS

 Object_id  指定了这个region内显示的object(通过循环取得所有的object

Region_fill_flag定义为1后,说明该region需要用背景色填充,填充的颜色由region_8-bit_pixel-coderegion_4-bit_pixel-coderegion_2-bit_pixel-code指定;

region_8-bit_pixel-code:指定8bitCLUT表的入口,但仅仅当该region_depth8 bit的时候才有效;

region_4-bit_pixel-code:指定4 bitCLUT表入口。当region depth4bit可用;或者region depth8bit,且region_level_of_compatibility4bit的时候才有用;

region_2-bit_pixel-code:指定2bitCLUT表入口。单region depth2bit,或者region_level_of_compatibility2bit的时候可用。

region_level_of_compatibility:表示解码器满足的最小CLUT类型(2bit4bit8bit

 

 如果page statenormal case的情况,说明region_id并没有改变(与已经解析出的region信息作比较),无需重新创建region

     但是这里如果新来的region width/height 与已经解析出来的regionwidth/height不一样,需要替换并重新创建这个region

 如果 page statemode changeacquisition point,需要重新解析region_id,重新创建region

 这里解出的region_width region_height,不能超过上面DDS 段解除的display width,display height

 根据取出的region_id,region_width,region_heigthpixel_depth,来创建__createRegionBuffer(),最终通过DirectFB 来绘制一个window

  __createRegionBuffer()最终是通过IDirectFB*做了两件事CreateWindowSetPallete

 也就是说一个新的region,就对应了一个新的IDirectFBWindow*

 

  大致情况如下:

 If (可以显示了) //这个应该是表示这种surface是最终要显示出来的。  一般只需要创建一个可以显示的surface就可以了

  {

  1. 通过IDirectFB*取得IDirectFBDisplayLayer*,存为m_pDFBLayer;这样就可以用过m_pDFBLayer做进一步的操作了。
  2. m_pDFBLayer-> SetCooperativeLevel (m_pDFBLayer, DLSCL_ADMINISTRATIVE)//设置合作级别。这里应该是表示管理权限,枚举出window,并管理它们
  3. m_pDFBLayer->GetConfiguration(m_pDFBLayer,&config)//获得当前layer的配置信息,如widthheightpixelformat等(这里的widthheight来自与region_widthregion_height

当然获取config是为了重新设置之,设置好后,需调用m_pDFBLayer->SetConfiguration()

  1. 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的位置

  1. m_pDFBLayer->Create(m_pDFBLayer,&desc,&m_pDFBWindow)//layer来创建window,其中desc是一些配置信息
  2. m_pDFBWindow->GetSurface(m_pDFBWindow,&m_Windows[u8WinID].pSurface)//

每次创建一个window,都获取一个surface,并存储到window数组中,所以一个region就对应一个window的数组元素,也对应一个surface

  1. m_pDFBWindow->SetOpacitym_pDFBWindow->0/0xff

0表示全透明,隐藏window0xff表示不透明

}

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

widthregion_width

heightregion_heigth

}

 

在取得surface之后,设置surface的调色板

  1. m_Windows[u8WinID].pSurface->GetPalette(m_Windows[u8WinID].pSurface,&palette)//这里为什么无需SetPaletteSurface创建后就能直接得到GetPalette??
  2. 接着palette->SetEntries(palette,DFBColor*color,num_colors,offset)

接着palette->Release(palette)//这个接口在官网的接口定义里没有找到?

 

 

Region segment解析完后得到了一系列索引信息,object索引,CLUT索引等;具体的内容

和颜色还需进一步解析相关的segment

我们知道surface代表一块预留的内存,用来保存像素数据,那么像素数据从哪里来呢?

在解析object segment的时候,会将像素数据存储到surface对应的内存地址中。(具体见解析object

 

4case 0x12  CLUT definition segment

   循环解析出所有的cult信息,用YCbCrT 颜色系统存储,需转换成ARGB

按公式将YCbCrT的信息转换成ARGB相关,T直接转换为A(透明度),Y=0表示透明

(略写)

5case 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 fieldpixel-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_positionregion中取得)+ object_vertical_position*pitch

这里涉及到图像压缩算法 run length encoding(行程编码):将一扫描行中的颜色值相同的相邻像素用一个计算值和那些像素值的颜色值来代替。比如aaabccccccddeee,用3a1b6c2d3e来代替

比如

run_lenght_3-10指示了2bit_pixel_code重复的次数。。

 

 

m_Windows[u8WinID].pSurface->Unlock(m_Windows[u8WinID].pSurface)//记得执行这步

 

 

到这一步,surface的内存中已经有像素数据了,但是并不会立即显示出来。

1.surface是单buffer的(这个怎么判断呢?)

将每个region对应的surface,用StretchBlit()输入到OFFSCREEN特殊的surface

m_Windows[u8DstWinID].pSurface->StretchBlitDstSurface,OFFSCREEN , pSrcDFBRect, pDstDFBRect)

当然要使用StretchBlit之前,还需先用SetBlittingFlagssur*DSBLIT_NOFX)、SetRenderOptionssur*DSRO_SMOOTH_UPSCALE)这两个接口设置一下。

 

  1. surfacedouble 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

  1. CreateWindow(ONSCREEN_GWIN_ID, &rect, MAPI_TRUE, m_pCallbackArgument);//表示这个surface是可以显示出来的,具体的步骤可见上述region 解析部分
  2. CreateWindow(OFFSCREEN_GWIN_ID, &rect, MAPI_FALSE, m_pCallbackArgument);//这个surface是不可以显示出来的作为转存数据使用。(上面每次new region创建的surface也是无需显示的,可显示的只要一个即可)

 

 

 

 

   60x80 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 serviceeg,不同语言,或者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 是分配给每个segmentSegments可以只包含特定的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 setDisplay set中的最后一个段必须跟着一个 end_of-display-set 段,表示没有更多的与此PTS相关的subtitle 数据了。

 

Segment type

1.display definition segment    可选,用户定义display size  针对高清subtitle

2.page composiont segment     定义接下来显示的pages,每个page包含的regionstime-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中的数据层次结构如下:

  1. Transport Stream  TS
  2. Transport packets with the same PID
  3. PES packetswith PTSs providing timing information
  4. Subtitle service
  5. Segments signalled by the compositon page id and optionally the ancillary page id
  6. Where appropriate,a display definition segment

Subtitle data,containing information on page composition,region composition,CLUTs,objects and end of display set.

以上是关于DVB-subtitle解析流程浅的主要内容,如果未能解决你的问题,请参考以下文章

测试浅谈(原则简单流程)

VSCode自定义代码片段——git命令操作一个完整流程

V8 编译浅谈

VSCode自定义代码片段15——git命令操作一个完整流程

VSCode自定义代码片段15——git命令操作一个完整流程

龙叔python-直接赋值,深拷贝,浅拷贝的简单解析