一、TS流的分析
l TS流是如何产生的?
从上图可以看出,视频ES和音频ES通过打包器和共同或独立的系统时间基准形成一个个PES,通过TS复用器复用形成的传输流。注意这里的TS流是位流格式(分析Packet的时候会解释),也即是说TS流是可以按位读取的。
l TS流的格式是怎样的?
TS流是基于Packet的位流格式,每个包是188个字节(或204个字节,在188个字节后加上了16字节的CRC校验数据,其他格式一样)。整个TS流组成形式如下:
Packet Header(包头)信息说明 |
|||
1 |
sync_byte |
8bits |
同步字节 |
2 |
transport_error_indicator |
1bit |
错误指示信息(1:该包至少有1bits传输错误) |
3 |
payload_unit_start_indicator |
1bit |
负载单元开始标志(packet不满188字节时需填充) |
4 |
transport_priority |
1bit |
传输优先级标志(1:优先级高) |
5 |
PID |
13bits |
Packet ID号码,唯一的号码对应不同的包 |
6 |
transport_scrambling_control |
2bits |
加密标志(00:未加密;其他表示已加密) |
7 |
adaptation_field_control |
2bits |
附加区域控制 |
8 |
continuity_counter |
4bits |
包递增计数器 |
PID是TS流中唯一识别标志,Packet Data是什么内容就是由PID决定的。如果一个TS流中的一个Packet的Packet Header中的PID是0x0000,那么这个Packet的Packet Data就是DVB的PAT表而非其他类型数据(如Video、Audio或其他业务信息)。下表给出了一些表的PID值,这些值是固定的,不允许用于更改。
表 |
PID 值 |
PAT |
0x0000 |
CAT |
0x0001 |
TSDT |
0x0002 |
EIT,ST |
0x0012 |
RST,ST |
0x0013 |
TDT,TOT,ST |
0x0014 |
TS流的基本内容就是这些了。
回顾一下,TS流是一种位流(当然就是数字的),它是由ES流分割成PES后复用而成的;它经过网络传输被机顶盒接收到;数字电视机顶盒接收到TS流后将解析TS流。
TS流是由一个个Packet(包)构成的,每个包都是由Packet Header(包头)和Packet Data(包数据)组成的。其中Packet Header指示了该Packet是什么属性的,并给出了该Packet Data的数据的唯一网络标识符PID。
l PAT
PAT(Program Association Table):节目关联表,该表的PID是固定的0x0000,它的主要作用是指出该传输流ID,以及该路传输流中所对应的几路节目流的 MAP 表和网络信息表的PID。 节目关联表(PAT Program Association Table) 是数字电视系统中节目指示的根节点。 其包标识符(Packet IDentifier、简称PID)为0。终端设备(如机顶盒)搜索节目时最先都是从这张表开始搜索的。从PAT中解析出节目映射表(Program Map Table、简称PMT),再从PMT解析出基本元素(如视频、音频、数据等)的PID及节目号、再根据节目从节目业务描述表(Service Description Table、简称SDT)中搜索出节目名称。 CAT相关表是从PMT中得到。
l PMT
PMT(Program Map Table):节目映射表,该表的PID是由PAT提供给出的。通过该表可以得到一路节目中包含的信息,例如,该路节目由哪些流构成和这些流的类型(视频,音频,数据),指定节目中各流对应的PID,以及该节目的PCR所对应的PID。
l 程序需用TS流分析
它是一个单节目TS流, 格式为: 视频(codec: MPEG4, PID:0x11), 音频(codec: AC3, PID:0x14)
二、基本环境的配置
1、 新建项目--->Win32--->DLL、空项目
2、VC++目录--->包含目录
l C:\ProgramFiles\Microsoft SDKs\Windows\v7.1\Samples\multimedia\directshow\baseclasses
l C:\ProgramFiles\Microsoft SDKs\Windows\v7.1\Samples\multimedia\directshow\common
l C:\Program Files\Microsoft SDKs\Windows\v7.1\Include
3、VC++目录--->库目录
l C:\Program Files\Microsoft SDKs\Windows\v7.1\Lib
l C:\ProgramFiles\Microsoft SDKs\Windows\v7.1\Samples\multimedia\directshow\baseclasses\Debug
4、 链接器--->输入--->附加依赖项
三、编码实现
l Filter的注册
AMOVIESETUP_MEDIATYPE、
AMOVIESETUP_PIN、
AMOVIESETUP_FILTER
l 基类函数重写
//填充样本函数
//参数pMediaSample就是要传递到下一个Filter输入pin的样本
//把数据填充到pMediaSample中就是这个函数的功能
//overridden CSourceStream functions
HRESULT FillBuffer(IMediaSample *pms);
//协商每个CMediaSample数据块的大小
HRESULT DecideBufferSize(IMemAllocator *pIMemAlloc,
ALLOCATOR_PROPERTIES *pProperties);
//检测是否支持参数传入的媒体类型
HRESULT CheckMediaType(const CMediaType *pMediaType);
//获得媒体类型
//在枚举器中枚举支持的媒体类型时调用此函数得到PIN支持的媒体类型
//此函数设置pmt的各个成员,因此,由此函数的内容觉得PIN支持什么媒体类型
HRESULT GetMediaType(int iPosition, CMediaType *pmt);
// Used to create output queue objects
HRESULT Active();
HRESULT Inactive();
// Overriden to pass data to the output queues
HRESULT Deliver(IMediaSample *pMediaSample);
//填充Sample
HRESULT TSOutputPin::FillBuffer(IMediaSample *pms)
{
BYTE *pData;
long lDataLen=0;
int i=0;
if(m_hFile==NULL)
{
m_hFile = CreateFile(TEXT("E:\\zwj.ts"),
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if(m_hFile == INVALID_HANDLE_VALUE )
{
_cprintf("Creating file is failed\n");
m_hFile=NULL;
return E_FAIL;
}
_cprintf("Inside FillBuffer file is created\n");
}
///////////////////////////////////////////////
//获得Sample中存放数据的地址
pms->GetPointer(&pData);
//取得Sample分配的内存大小
lDataLen = pms->GetSize();
DWORD lDataRead;
if(!ReadFile(m_hFile,pData,lDataLen,&lDataRead,NULL))
{
printf("Read Audio data from file failed\n");
m_hFile=NULL;
return 0;
}
if(lDataRead <= 0)
SetFilePointer(m_hFile,NULL,0,FILE_BEGIN);
pms->SetActualDataLength((long)lDataRead);
//TODO now check with the TS file
// pms->SetActualDataLength(lDataLen);
return NOERROR;
}
//协商Sample的大小
HRESULT TSOutputPin::DecideBufferSize(IMemAllocator *pIMemAlloc,
ALLOCATOR_PROPERTIES *pProperties)
{
DbgLog((LOG_TRACE, 0, TEXT("DecideBufferSize")));
CAutoLock cAutoLock(m_pFilter->pStateLock());
ASSERT(pIMemAlloc);
ASSERT(pProperties);
HRESULT hr = NOERROR;
//确定buffer的个数和每个buffer大小
pProperties->cBuffers = DIB_OUTPUTPIN_QUEUE_SIZE;
pProperties->cbBuffer = DIB_TS_BUFFER_SIZE;
ASSERT(pProperties->cbBuffer);
// Ask the allocator to reserve us some sample memory, NOTE the function
// can succeed (that is return NOERROR) but still not have allocated the
// memory that we requested, so we must check we got whatever we wanted
//设置属性页
ALLOCATOR_PROPERTIES Actual;
hr = pIMemAlloc->SetProperties(pProperties,&Actual);
if (FAILED(hr)) {
return hr;
}
// Is this allocator unsuitable
if (Actual.cbBuffer < pProperties->cbBuffer) {
return E_FAIL;
}
// Make sure that we have only DIB_OUTPUTPIN_QUEUE_SIZE buffer
ASSERT( Actual.cBuffers == DIB_OUTPUTPIN_QUEUE_SIZE );
return NOERROR;
}
//检查媒体类型
//主要是对GetMediaType中设置的各个参数进行比较
HRESULT TSOutputPin::CheckMediaType(const CMediaType *pMediaType)
{
DbgLog((LOG_TRACE, 0, TEXT("CheckMediaType")));
CAutoLock cAutoLock(m_pFilter->pStateLock());
if (pMediaType->majortype != MEDIATYPE_Stream || pMediaType->subtype != MEDIASUBTYPE_MPEG2_TRANSPORT)
return S_FALSE;
return S_OK;
}
//获取媒体类型
//填充pmt
//最简单的源Filter,因此只支持一种类型,所以iPosition为0
HRESULT TSOutputPin::GetMediaType(int iPosition, CMediaType *pmt)
{
_cprintf("TSOutputPin::GetMediaType\n");
DbgLog((LOG_TRACE, 0, TEXT("GetMediaType")));
CAutoLock cAutoLock(m_pFilter->pStateLock());
if (iPosition < 0) {
return E_INVALIDARG;
}
if (iPosition >= 1) {
return VFW_S_NO_MORE_ITEMS;
}
pmt->majortype = MEDIATYPE_Stream;
pmt->subtype = MEDIASUBTYPE_MPEG2_TRANSPORT;
pmt->formattype = FORMAT_None ;
return S_OK;
}
l GraphEdit调试
打开Grapedit(在DirectX SDK的DirectX Utilities中), 插入TS Source和Mpeg-2 Demultiplexer Filter,再把它们对应的pin相连。 并配置Mpeg-2 Demultiplexer, 设置好各个pin对应的媒体类型,映射好PID,并将Pin输出的数据结构设为Elementary Stream (A/V only), 如下所示:
这时MPEG-2 Demultiplexer上应该会出现相应的输出pins,点右键选render pin即可。 Graph Edit会自动挑选解码器并完成连接。 连接好的Filter Graph如下图: