开发PS/TS流转ES的SDK

一、概述

PS和TS流在广播电视、影音制作、安防监控等领域应用得很广泛。其中,PS流很久前就被应用于DVD光盘里的电影格式的封装容器,我们看过的很多DVD格式电影(即VOB)就是用PS容器封装的;PS流最近几年也迅速应用在安防领域,国标GBT 28181标准规定了视音频流通过网络传输要封装为PS格式;而TS流(传输流)则应用于电视直播领域很长一段时间,电视机顶盒接收的节目数据格式就是MPEG2-TS格式。

关于PS、TS格式的结构组成和解析这篇文章就不提了,主要讲一下我写的PS/TS解析库的使用。

二、分离PS、TS流的SDK

我实现了一个支持PS、TS流解析的模块,该模块支持对PS/TS解封包,最终分立出ES帧,并通过回调接口将数据传给上层。该模块封装成动态库的形式,下面看看库接口的声明:

#include "SDKDef.h"


//初始化SDK
PLAYPT_API BOOL  PT_InitSDK();
//注销SDK
PLAYPT_API BOOL  PT_UnitSDK();
//获取SDK版本号
PLAYPT_API LONG  PT_GetSDKVersion();

//设置缓冲的条件,根据帧数或字节数
//下面两个条件只能一个生效,如果nBufferFrames非0则根据帧数缓冲;如果nBufferFrames为0,BufferBytes不为0,则根据字节数缓冲。
//参数:
//nBufferFrames -- 缓冲要达到的帧数
//BufferBytes -- 缓冲要达到的字节数

PLAYPT_API void  PT_SetBufferStreamParams(UINT nBufferFrames, UINT BufferBytes); //设置缓冲参数

//打开PS/TS格式的流,支持从文件或内存读取流
//参数:
//srcType -- 流来自于文件或内存,_FILE_SOURCE--来自于文件,_MEM_SOURCE---来自于内存
// lpszFilePath -- 文件路径,如果是内存流,传NULL
// nFileType --流类型(1--PS, 2--TS)
// bParseESStream -- 是否解析视音频流,提取信息。如果视频流格式不是MPEG2/MPEG4/H264这几种之一,则将该参数设为FALSE
// handle -- 返回的这个句柄来调用其他函数; 
// dwError -- 失败时返回的错误码; 
//
PLAYPT_API BOOL    PT_OpenFile(SOURCE_TYPE srcType, LPCTSTR lpszFilePath, int nFileType, BOOL bParseESStream, int & handle, DWORD & dwError);
PLAYPT_API BOOL    PT_CloseFile(int handle);  //关闭文件或内存流
PLAYPT_API BOOL    PT_Pause(int handle);  //暂停播放
PLAYPT_API BOOL    PT_Play(int handle);  //开始播放
PLAYPT_API BOOL    PT_IsRunning(int handle); //是否正在播放
PLAYPT_API BOOL    PT_AddStreamData(int handle, BYTE * pData, int nBytes ); //插入PS/TS流
PLAYPT_API BOOL    PT_SetFrameCallback(int handle, EsFrameCallback  lpFrameCB);  //设置数据回调(回调分离出来的ES流数据)

//获取视频流的信息。说明:当PT_OpenFile函数传入参数bParseESStream为True时才能调用该函数返回视频流的信息
PLAYPT_API BOOL    PT_GetVideoInfo(int handle, VideoEncodeFormat & videoformat, int& nWidth, int& nHeight, int & nTrackNum); 

//获取音频流的信息。说明:当PT_OpenFile函数传入参数bParseESStream为True时才能调用该函数返回音频流的信息
PLAYPT_API BOOL    PT_GetAudioInfo(int handle, AudioEncodeFormat & nType, int & nTrackNum);

PLAYPT_API BOOL    PT_GetFileDuration(int handle, __int64 & llDuration); //获取文件播放时长(只对文件有效)
PLAYPT_API BOOL    PT_Seek(int handle, __int64 llPos); //跳到某个播放时间点(只对文件有效)
PLAYPT_API BOOL    PT_GetPlayPos(int handle, __int64 & llPos); //获取当前播放时间点

//以下针对TS流
PLAYPT_API BOOL    PT_IsPMTFound(int handle); //流中是否找到PMT表(Program Map Table)信息
PLAYPT_API BOOL    PT_GetTSProgramSize(int handle, int & nProgramSize); //获取TS流中的节目个数
PLAYPT_API BOOL    PT_GetTSProgramInfo(int handle, int nProgramNum, int& nStreamNum, ESStreamInfo  streams[4]); //获取节目流信息

//以下针对PS流
PLAYPT_API BOOL    PT_GetPSStreamNum(int handle, int & nStreamNum); //获取流的数目(视频流+音频流)
PLAYPT_API BOOL    PT_GetPSStreamInfo(int handle, int& nStreamNum, ESStreamInfo  streams[4]); //获取每个流的信息(PES ID和StreamType)

这个库的用法很简单,下面说一下调用流程:

  1.  调用PT_InitSDK()接口初始化SDK。

  2.  调用 PT_OpenFile打开一个文件或打开内存流。 PT_OpenFile传入的参数包括:SOURCE_TYPE srcType(流的类型,文件或内存流), LPCTSTR lpszFilePath(文件路径), int nFileType(1--PS流,2--TS流),然后接口返回一个句柄,我们要把这个句柄保存起来,后面调用其他函数要用到这个句柄。如果打开成功,返回True,如果出错,则会返回false,参数dwError携带了出错的错误码。

 3. 获取流的属性。打开一个输入文件或内存流之后,可以调用PT_GetVideoInfo和PT_GetAudioInfo来获取视频和音频轨道的信息,还有可以通过接口PT_GetFileDuration获取文件播放时长。

 4. 设置回调函数。 调用PT_SetFrameCallback接口设置分离后数据的回调接收函数,这个回调函数的原型如下:

typedef int  (WINAPI * EsFrameCallback)(int handle, BYTE * pBuf, int nBufSize, int nTrackNum, int nStreamType, __int64 llPts, int nFrameType);

回调函数的说明和各个参数的意义:

//函数作用:回调视频帧、音频帧数据。
参数意义:

// handle -- SDK的播放句柄;
// pBuf -- ES帧的数据起始地址;
// nBufSize -- 数据的大小;
// nTrackNum--流ID号,区别不同流的唯一标识符;
// int nStreamType -- 流类型, 常见取值类型有: MPEG-4 视频流:0x10;H.264 视频流:0x1B;G.711 音频流:0x90;
// llPts -- 时间戳,以10000000L为单位;
// nFrameType -- 帧的类型。 nFrameType = 1, I帧;nFrameType = 2, P帧; nFrameType = 3, B帧

  5. 调用PT_Play接口运行“播放”任务,实际上SDK没有做解码和播放,只是做了Demux,即从TS/PS分离出视频和音频,如果用户设置了回调函数,则SDK会把分离出来的数据通过回调函数传递给应用层。

 6. 如果输入源是内存流,则需要不断地调用PT_AddStreamData接口把内存中的数据传给SDK,PT_AddStreamData需要传递的参数包括:SDK实例句柄,数据Buf的地址,Buff大小。如果输入源是文件,则在任务运行的过程中可以调用PT_GetPlayPos接口查询当前的播放进度。

7.  “播放”完成后调用 PT_CloseFile关闭输入源。

当然,使用上面这些接口时还有一些细节要理解,现在重点讲解一下:

1. PT_OpenFile传入的参数中有个参数:bParseESStream,这是一个很重要的参数,该参数会影响到内部对视音频流的解析。如果要获得音视频流的信息(音视频编码格式、视频分辨率等),则将该参数设置成True,但是打开该参数会增加函数的处理工作量,增加PT_OpenFile函数调用的时间。

2. 调用PT_OpenFile函数前必须先调用PT_AddStreamData向SDK插入数据,因为SDK内部实现了一个缓冲区(默认是2M字节大小),在打开流之前需要从缓冲区预读一段数据,根据读到的数据初始化内部一些变量,并获取PS/TS流的格式信息,以及每个流的编码格式信息(如果bParseESStream参数为True)。如果填充的数据不够,则PT_OpenFile函数会返回False,表示打开流失败。另外一些情况也可能会导致PT_OpenFile函数返回失败,比如执行超时,或解析视音频格式的信息出错了(当bParseESStream = True)。这个函数有个等待时间,如果超过5秒还没有达到缓冲的条件,则退出并返回失败;如果流有损坏或格式不正确,也会导致PT_OpenFile函数返回失败。

3. 我们可调用PT_SetBufferStreamParams接口来设置缓冲区要缓存多少数据才结束,其中第一个参数是缓冲的帧数,第二个参数是要缓冲的字节数。注意这两个变量只能同时有一个生效,优先是按帧数,其次是按字节数(建议按帧数缓冲,因为不会受码率大小影响)。这些参数会影响到PT_OpenFile函数的执行时间。如果设置了缓冲为5帧,则缓冲区至少要收到5个视频PES帧才初始化成功,并返回。默认情况,SDK缓冲4个视频PES帧才完成初始化过程。

4. 回调函数EsFrameCallback里有个参数--nTrackNum,这个是流ID,有什么用呢?对于PS流和TS流,这个值的意义有点不同。

  • 对于PS流,每个流有elementary_stream_id和stream_type,这个elementary_stream_id就是我们这里的nTrackNum,在PS流的格式定义中,视频流的ID和音频流的ID有个范围。一般的情况,视频流TrackNum是0xE0,音频流是0xC0。通过TrackNum我们就能知道回调的ES帧是视频流还是音频流,更具体些通过SreamType可以判断出流的类型和流的编码格式,每个类型值对应一种编码格式,比如视频:0x1b -- H264; 0x02 --MPEG2, 0x10 --MPEG4。但是,这个SDK目前还没有实现获取流的StreamType,回调返回的StreamType为0的,但是我们仍然能获得视音频流的格式,方法是通过另外的接口:PT_GetVideoInfo/PT_GetAudioInfo,使用这两个接口的前提条件是调用PT_OpenFile是传入的参数bParseESStream为True,并且PS流中各个Stream的格式是SDK能识别的(SDK能识别哪些视音频编码格式看SDKDef.h头文件中相关枚举类型的定义)。
  • 对于TS流,这个nTrackNum对应PMT(节目映射表)中的各个流或轨道的PID,每个PID还对应StreamType,跟PS流中的StreamType意义一样,表示某种视频或音频编码格式。SDK能读取TS流中的PMT表,获得各个流的PID和StreamType,通过调用以下接口能获取PMT中各个流的信息:
PLAYPT_API BOOL    PT_IsPMTFound(int handle); //流中是否找到PMT表(Program Map Table)信息
PLAYPT_API BOOL    PT_GetTSProgramSize(int handle, int & nProgramSize); //获取TS流中的节目个数
PLAYPT_API BOOL    PT_GetTSProgramInfo(int handle, int nProgramNum, int& nStreamNum, ESStreamInfo  streams[4]); //获取节目流信息

5. 目前这个SDK只能解析少数几种视音频格式,支持的格式可以看SDKDef.h文件里枚举类型VideoEncodeFormat和AudioEncodeFormat的定义,其中视频只支持MPEG1/MPEG2/MPEG4/H264。如果是别的格式怎么办呢?因为PS和TS容器都能包含很多种格式的视音频流,我不可能每一种都对其支持,那工作量是非常大的,但是为了提高兼容性,我设计的SDK允许容器中的流是任何类型的编码格式,但是,SDK内部不会对每一种格式的流都进行解析,比如视频,只对MPEG1/MPEG2/MPEG4/H264这几种格式进行解析和支持。在SDK接口上,提供一个参数:bParseESStream,这个参数就是前面的打开流接口:PT_OpenFile的第4个参数,这个参数让用户设置是否让SDK解析流的格式,如果是MPEG1/MPEG2/MPEG4/H264格式,建议将该变量设为True,如果是别的格式,就设成False。对于非SDK内部支持的格式,用户需要知道他们接收的PS流中各个流的编码格式,并自己用对应的解码器解码数据。

6. 关于时间戳PTS:在MPEG2 TS流的标准里,PTS是相对SCR(系统参考)的时间戳,是以90000为单位的,PTS到ms的转换公式是PTS/90,系统时钟频率为90Khz,所以如果是以ms为单位的播放器,PTS是要使用公式ms=pts/90来转换。但是在SDK内部PTS被转为以10000000L为单位。

特别说明:

1. 该PS/TS SDK只对流做解封装、分离和回调数据操作,解码和播放的逻辑是由应用层实现。

2. 提供的例子编译出来只能播放本地文件,如果要播放实时流,则需要在代码中取消宏_READ_STREAM_FROM_FILE的定义,这个宏会影响代码的调用处理分支,另外还要修改一些参数:比如绑定的UDP端口,修改代码主要在:CMainFrame:: OnStartReceiveStream。

2. PS/TS SDK目前不开源,只提供了头文件和动态库。但调用该SDK的例子(界面程序)是开源的,例子带视频解码和显示图像的功能。通过对例子代码的学习,读者可了解到一个PS/TS流的播放器是如何设计的、各个模块如何交互,构建起对文件播放器和实时流播放器的框架的认识。(个人觉得:掌握一个技术的工作原理和基本开发方法比获取完整的代码更加重要)

Demo的下载地址:https://download.csdn.net/download/zhoubotong2012/11971737

你可能感兴趣的:(音视频开发,PS,TS,demux)