第五章 开发指南
5.1 视频采集安卓端(spydroid)
作为远端采集端,App启动后,连接并保活至服务器。采集安卓摄像头视频和mic声音,进行H264和AAC编码(这里spydroid实现了硬编码,目前大部分Android音视频采集都支持硬编码),再通过RTSP和RTP,将实时音视频数据推送到流媒体服务器,并由流媒体服务器进行转发和分发,实现直播。
这里主要就是RTSP/RTP的推送过程,下面章节中DSS的先侦听后推送式流媒体转发详细描述了这个过程,咱们这里直接修改spydroid中的RTSPClient就可以实现ANNOUNCE/SETUP/PLAY/RTP过程了。
5.1.1 Package
net.majorkernelpanic.http主要是介绍http server,spydroid自身内置http服务器,客户端可以通过在VLC等播放器中输入//ip:8080/播放。
net.majorkernelpanic.spydroid主要是Application。
net.majorkernelpanic.spydroid.api主要是几个服务。
net.majorkernelpanic.spydroid.ui主要是activity的界面部分。
net.majorkernelpanic.mp4主要是介绍提取mp4文件的profile,sps,pps等信息。
net.majorkernelpanic.streaming.rtsp 主要是介绍rtsp服务器部分,spydroid自身内置rtsp服务器,客户端可以通过在VLC等播放器中输入rtsp://ip:8086/播放。
net.majorkernelpanic.streaming.rtp主要是介绍rtp协议通信。
net.majorkernelpanic.spydroid主要是activity的界面部分。
net.majorkernelpanic.streaming主要是stream接口和抽象类。
net.majorkernelpanic.streaming.audio介绍音频部分。
net.majorkernelpanic.streaming.video介绍视频部分。
5.1.2 spydroid运行流程
程序运行时,进入net.majorkernelpanic.spydroid.SpydroidActivity,该activity 运行时候,开启http server ,rtsp server,RTSP Client。这里重点关注rtsp server服务与RTSP Client服务。
RTSP Server服务
rtspserver开启后,启动一个线程RequestListenerThread,负责监听客户端(这里用VLC)的请求
public void start() throws IOException
{
if (running) return;
running = true;
listenerThread = new RequestListenerThread(port, handler);
listenerThread.start();
}
当有客户端请求的时候,开启一个workerTread线程。一个线程session代表一个请求
new WorkerThread(server.accept(), handler).start();
VLC向rtsp服务器进行交互时,这里就需要用到rtsp协议的内容了,主要分为Options,Describe,Setup,play,teardown这5步骤。关于RTSP协议请查看我们提供的《RTSP协议详解中文版》。
options请求时,发送可用的状态。describe请求时,发送流类型,在这里是h264视频流,以及mp4 的profile,sps,pps,在不同手机上,profile,sps,pps的数值不一定相同。这个是通过提取录制的该手机上的mp4文件的内容得到的。 除了H264,这里也可以是H263视频流,或者其他audio音频流。这里重点查看generateSessionDescriptor()方法,比如在这里,选择H264,那么就可以看看H264Stream这个类的这个方法,看看它是如何获取profile ,sps,pps的setup请求时,主要关注stream.prepare(),stream.start()方法,prepare()的时候调用初始化视频录制的参数,比如H264编码,分辨率,帧数等相关信息。而start()方法就开始通过localsocket把录制的视频以流的形式发送到本地,而H264Packetizer通过获取其输入流,然后对其rtp打包处理,发送。
mMediaRecorder = new MediaRecorder();
mMediaRecorder.setCamera(mCamera);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mMediaRecorder.setVideoEncoder(mVideoEncoder);
mMediaRecorder.setPreviewDisplay(mSurfaceView.getHolder().getSurface());
mMediaRecorder.setVideoSize(mRequestedQuality.resX, mRequestedQuality.resY);
//mMediaRecorder.setVideoFrameRate(mRequestedQuality.framerate);
mMediaRecorder.setVideoEncodingBitRate((int)(mRequestedQuality.bitrate * 0.8));
mMediaRecorder.setOutputFile(TESTFILE);
mMediaRecorder.setMaxDuration(3000);
// We wait a little and stop recording
mMediaRecorder.setOnInfoListener(new MediaRecorder.OnInfoListener()
{
public void onInfo(MediaRecorder mr, int what, int extra)
{
Log.d(TAG, "MediaRecorder callback called !");
if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED)
{
Log.d(TAG, "MediaRecorder: MAX_DURATION_REACHED");
}
else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED)
{
Log.d(TAG, "MediaRecorder: MAX_FILESIZE_REACHED");
}
else if (what == MediaRecorder.MEDIA_RECORDER_INFO_UNKNOWN)
{
Log.d(TAG, "MediaRecorder: INFO_UNKNOWN");
}
else
{
Log.d(TAG, "WTF ?");
}
mLock.release();
}
});
// Start recording
mMediaRecorder.prepare();
mMediaRecorder.start();
RTSP Client服务
Spydroid本身并没有RTSP Client功能,但是其提供了一个RTSPClient,这里我们参照RTSPServer直接修改spydroid中的RTSPClient实现ANNOUNCE/SETUP/PLAY/RTP过程了。
RTSPClient启动后,执行startStream(),首先进行Session的设置。
public void setSession()
{
Session msession = new Session();
msession = null;
try
{
msession = UriParser.parse("rtsp://" + mTmpParameters.host + ":" + mTmpParameters.port);
}
catch (IllegalStateException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
catch (IOException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
msession.setOrigin("127.0.0.1");
if (msession.getDestination() == null)
{
msession.setDestination("mTmpParameters.host");
}
try
{
msession.syncConfigure();
}
catch (CameraInUseException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
catch (StorageUnavailableException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
catch (ConfNotSupportedException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
catch (InvalidSurfaceException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
catch (RuntimeException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
catch (IOException e)
{
// TODO 自动生成的 catch 块
e.printStackTrace();
}
mTmpParameters.session = msession;
}
当然还有一些流被送到的路径的设置,RTSP服务器的目的地址的设置,如果在服务器上启用身份验证,你需要设置用户名/密码等等。所有准备工作准备无误后尝试连接服务器。
private void tryConnection() throws IOException
{
mCSeq = 0;
mSocket = new Socket(mParameters.host, mParameters.port);
mBufferedReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
mOutputStream = mSocket.getOutputStream();
sendRequestAnnounce();
sendRequestSetup();
sendRequestRecord();
}
在这个函数中可以清楚的看到,RTSPClient连接服务器推送视频流所发送的请求及顺序。
ANNOUNCE:该方法方法有两个目的:当从客户端发往服务器端,ANNOUNCE向服务器端上传请求URL所标识的表示或媒体对象的描述。当从服务器端发往客户端,ANNOUNCE实时更新会话描述。当一个新的媒体流加入一个表示(例如:在一个现场表示活动期间)时,整个表示而不仅是所增加的部分,应该被重发,以便部分删除。
SETUP:让服务器给流分配资源,启动RTSP会话。
RECORD(PLAY):启动SETUP所分配的流的数据传输。
5.2 流媒体服务器(Darwin Streaming Server)
5.2.1 DSS的框架
服务器的作用是充当网络客户和服务器模块的接口,其中网络客户使用RTP和RTSP协议来发送请求和接收响应,而服务器模块则负责处理请求和向客户端发送数据包。核心服务器通过创建四种类型的线程来完成自己的工作,具体如下:
•服务器自己拥有的主线程(Main Thread)。这个线程负责检查服务器是否需要关闭,记录状态信息,或者打印统计信息。
•空闲任务线程(Idle Task Thread)。空闲任务线程管理一个周期性的任务队列。该任务队列有两种类型:超时任务和套接口任务。
•事件线程(Event Thread)。事件线程负责侦听套接口事件,比如收到RTSP请求和RTP数据包,然后把事件传递给任务线程。
•一个或者多个任务(Task)线程。任务线程从事件线程中接收RTSP和RTP请求,然后把请求传递到恰当的服务器模块进行处理,把数据包发送给客户端。缺省情况下,核心服务器为每一个处理器创建一个任务线程。
5.2.2 模块
媒体服务器使用模块来响应各种请求及完成任务。有三种类型的模块:
1. 内容管理模块
内容管理模块负责管理与媒体源相关的RTSP请求和响应,比如一个文件或者一个广播。每个模块负责解释客户的请求,读取和解析它们的支持文件或者网络源,并且以RTSP和RTP的方式进行响应。在某些情况下,比如流化mp3的模块,使用的则是HTTP。
QTSSFileModule,QTSSReflectorModule,QTSSRelayModule,和QTSSMP3StreamingModule都是内容管理模块。
2. 服务器支持模块
服务器支持模块执行服务器数据的收集和记录功能。服务器模块包括QTSSErrorLogModule, QTSSAccessLogModule,QTSSWebStatsModule,QTSSWebDebugModule, QTSSAdminModule,和QTSSPOSIXFileSystemModule。
3. 访问控制模块
访问控制模块提供鉴权和授权功能,以及操作URL路径提供支持。
访问控制模块包括QTSSAccessModule,QTSSHomeDirectoryModule,QTSSHttpFileModule,和QTSSSpamDefenseModule。
5.2.3 数据
当一个模块需要访问客户请求的RTSP报头时,可以通过QTSS.h这个API头文件中定义的请求对象来访问相应的请求信息。举例来说,RTSPRequestInterface类实现了API字典元素,这些元素可以通过API来进行访问。名称是以“Interface”结尾的对象,比如RTSPRequestInterface,RTSPSessionInterface,和QTSServerInterface,则用于实现模块的API。
下面是重要的接口类:
•QTSServerInterface — 这是内部数据的存储对象,在API中标识为QTSS_ServerObject。在API中的每一个QTSS_ServerAttributes都在基类中声明和实现。
•RTSPSessionInterace — 这是内部数据的存储对象,在API中标识为qtssRTSPSessionObjectType。在API中的每一个QTSS_RTSPSessionAttributes都在基类中声明和实现。
•RTPSessionInterface — 这是内部数据的存储对象,在API中标识为QTSS_ClientSessionObject。在API中的每一个QTSS_ClientSessionAttributes都在基类中声明和实现。
•RTSPRequestInterface — 这是内部数据的存储对象,在API中标识为QTSS_RTSPRequestObject。在API中的每一个QTSS_RTSPRequestAttributes都在基类中声明和实现。
5.2.4 源代码的组织
Server.tproj
这个目录包含核心服务器(core server)的代码,可以分成三个子系统:
•服务器内核。这个子系统中的类都有一个QTSS前缀。QTSServer负责处理服务器的启动和关闭。QTSServerInterface负责保存服务器全局变量,以及收集服务器的各种统计信息。QTSSPrefs是存储服务器偏好设定的地方。QTSSModule,QTSSModuleInterface,和QTSSCallbacks类的唯一目的就是支持QTSS的模块API。
•RTSP子系统。这些类负责解析和处理RTSP请求,以及实现QTSS模块API的RTSP部分。其中的几个类直接对应QTSS API的一些元素(比如,RTSPRequestInterface类就是对应于QTSS_RTSPRequestObject对象)。每个RTSP TCP连接都有一个RTSP会话对象与之相对应。RTSPSession对象是一个Task对象,负责处理与RTSP相关的事件。
•RTP子系统。这些类处理媒体数据的发送。RTPSession对象包含与所有RTSP会话ID相关联的数据。每个RTPSession都是一个Task对象,可以接受核心服务器的调度来进行RTP数据包的发送。RTPStream对象代表一个单独的RTP流,一个RTPSession对象可以和任何数目的RTPStream对象相关联。这两个对象实现了QTSS模块API中的针对RTP的部分。
CommonUtilitiesLib
这个目录含有一个工具箱,包括线程管理,数据结构,网络,和文本解析工具。Darwin流媒体服务器及其相关工具通过这些类对类似或者相同的任务进行抽象,以减少重复代码;这些类的封装简化了较高层次的代码;借助这些类还分离了专用于不同平台的代码。下面是对目录下的各个类的简短描述:
•OS类。这些类在时间,条件变量,互斥锁,和线程方面提供了专用于不同平台的代码抽象。这些类包括OS,OSCond,OSMutex,OSThread,和OSFileSource;数据结构则包括OSQueue,OSHashTable,OSHeap,和OSRef。
•套接口类(Sockets)。这些类为TCP和UDP网络通讯方面提供了专用于不同平台的代码抽象。通常情况下,套接口类是异步的(或者说是非阻塞的),可以发送事件给Task对象。这些类有:EventContext,Socket,UDPSocket,UDPDemuxer,UDPSocketPool,TCPSocket,和TCPListenerSocket。
•解析工具。这些类负责解析和格式化文本。包括StringParser,StringFormatter,StrPtrLen,和StringTranslator。
•Task(任务):这些类实现了服务器的异步事件机制。
QTFileLib
流媒体服务器的一个主要特性就是它能够将索引完成(hinted)的QuickTime电影文件通过RTSP和RTP协议提供给客户。这个目录包含QTFile库的源代码,包括负责解析索引完成的QuickTime文件的代码。服务器的RTPFileModule通过调用QTFile库来从索引过的QuickTime文件中取得数据包和元数据。QTFile库可以解析下面几种文件类型:.mov,.mp4(.mov的一种修改版本),和.3gpp(.mov的一种修改版本)。
APICommonCode
这个目录包含与API相关的类的源代码,比如moduletils,或者诸如记录文件的管理这样的公共模块函数。
APIModules
这个目录包含流媒体服务器模块目录,每个模块都有一个目录。
RTSPClientLib
这个目录包含实现RTSP客户端的源代码,这些代码可以用于连接服务器,只要该连接协议被支持。
RTCPUtilitiesLib
这个目录包含解析RTCP请求的源代码。
APIStubLib
这个目录包含API的定义和支持文件。
HTTPUtilitiesLib
这个目录包含解析HTTP请求的源代码。
5.2.5 流转发
引用Darwin开发文档里面的一段来介绍一下流转发的拉模式和推模式:
Darwin支持两种自动播送的场景:
•先拉后推。为了发起自动播送,RTSP客户会发送标准的RTSP请求来向服务器请求一个流,然后服务器将该流中继到一个或者多个流媒体服务器。这种场景在"先拉后推"部分中加以描述。
•先侦听后推送。在这个场景中,自动播送在流媒体服务器接收到ANNOUNCE请求时被发起。这个场景在"先侦听后推送"部分中进行描述。
先拉后推
用户可以通过发送标准的DESCRIBE/SETUP/PLAY请求来向远程的源中请求一个流,然后将它中继转发到一个或者多个目的地。当只希望让外部流的一份拷贝占用其内部连接的带宽时,这个功能可能有用。中继转发获取一份拷贝进行多份的复制和转发、分发到请求的客户端。图 1.提供了一个先拉后推(pull-then-push)场景的实例。
图1.先拉后推式
以图1.作为参考,先拉后推场景的步骤如下:
1.流媒体服务器A(转发服务器)发送标准的RTSP客户DESCRIBE/SETUP/PLAY请求给远程服务器,即流媒体服务器B。
2.发起请求的中继“客户端”(流媒体服务器A)开始接受流,然后向该输入流的中继配置中列出的所有目的地发送ANNOUNCE推送请求。
在Darwin中,实现拉模式转发的模块为QTSSRelayModule,每一路转发会话为一个RelaySession对象,转发列表存储于队列sSessionQueue中
QTSSRelayModule一开始Initialize()读取配置文件中关于转发文件路径存储于sRelayPrefs静态变量中
./relayconfig.xml
在ReadRelayPrefsFile()中读取sRelayPrefs中配置并解析出分发列表,并对每一个分发配置中的source配置创建RelaySessionCreator开启分发,并加入到sSessionQueue中,RTSPSourceInfo::RelaySessionCreator::Run() 再通过RTSPSourceInfo::RunCreateSession()开始DESCRIBE/SETUP/PLAY拉取数据,RTSP流程成功后,再配置RTP数据分发的地址,将RTP数据推送至分发列表(即destination列表与source列表同一级)中,即实现了Darwin文档中所述的先拉后推模式。
先侦听后推送
流媒体服务器可以被配置为将ANNOUNCE请求创建的输入流自动发送到一个或者多个目的地。这可能可以用于配制自动播送网络。图 2.提供了一个先侦听后推送的场景的实例。
图2.先侦听后推送式
以图2.作为参考,先侦听后推送场景的步骤如下:
•远程机器(IpCamera等前端设备或者中继服务器)向流媒体服务器A发送一个ANNOUNCE请求。流媒体服务器可以接受或者否认这个请求。如果它接受了请求,则流媒体服务器会检查其中继配置,以确定这个流是否应该被中继。
•如果该流应该被中继,则流媒体服务器将向自身发送标准的RTSP客户DESCRIBE/SETUP/PLAY请求。
•发出请求的中继“客户”(流媒体服务器A)开始接收流,然后向相应的输入流的中继配置中列出的所有目的地发送一个ANNOUCE请求。
注意:我们在实际的需求中常常遇到的场景为,前端设备RTSP Announce上线至中继服务器,上报其流媒体SDP信息,前端设备再经过'被触发',通过SETUP/PLAY流程发起流推送,客户端再以拉模式拉取实时视频流,是一种先推后拉(push-then-pull)模式,如果客户端请求的视频流存在,则直接转发已经获取的拷贝进行分发。
具体的RTSP推送流程大致为:Announce、Setup、Play、RTP(DSS为RTP over TCP)。这种模式的转发通常用于类似于3G视频监控这种难以穿透的网络类型的数据的转发。我们就不具体介绍关于DSS对会话的维护以及各自自定义的RTSP头字段的操作等等,主要就步骤:Announce->Setup->Play->RTP数据接收与转发进行详细的分析。在DSS中,处理推送报文的模块为QTSSReflectorModule,其中维护了一个静态的转发列表sSessionMap,用于存储各个转发会话的信息。下面就对具体的报文解析和数据处理进行分析。
Announce:RTSP Announce命令为源端向服务器端主动发起的上报本地媒体sdp信息的命令,处理函数为QTSSReflectorModule模块的DoAnnounce()函数,这里就只对该函数的重点部分进行解析,不全部一一描述了。首先判断server配置中的enable_broadcast_announce字段是否为true,开启了广播推送转发,在通过获取inParams->inRTSPRequest(在RTSPSession::Run调用前复制的当前请求的rtspRequest对象)的字典中的qtssRTSPReqLocalPath键值作为标识转发的唯一区别(例如:.\Movies/test.sdp,必须以sdp结尾,可以修改sSDPSuffix进行配置),这里的值既是一个标识,又是一个路径,用于存储获取到的sdp数据,后面此标识作为存储于sSessionMap中对象的键值。函数中通过对头字段的解析,获取到Content-Length:字段值,进而去读取具体的spd值,再存储到qtssRTSPReqLocalPath路径中,返回200 OK。
Setup:这里的只解析DoSetup中isPush为true(表示为推送的Session)这条路路径,具体isPush值由Setup请求中的mode值有关,mode="receive" || mode="record"表示isPush为true,
else
{
theSession = DoSessionSetup(inParams, qtssRTSPReqFilePathTrunc, isPush, &foundSession);//根据前面Announce中存储于qtssRTSPReqLocalPath的路径读取sdp信息,创建转发会话ReflectorSession,或者直接引用已经存在的Session
if (theSession == NULL)
return QTSS_RequestFailed;
// This is an incoming data session. Set the Reflector Session in the ClientSession
theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &theSession, sizeof(theSession));//ReflectorSession附属于RTPSession中的sClientBroadcastSessionAttr字典
Assert(theErr == QTSS_NoErr);
//qtss_printf("QTSSReflectorModule.cpp:SETsession sClientBroadcastSessionAttr=%"_U32BITARG_" theSession=%"_U32BITARG_" err=%"_S32BITARG_" \n",(UInt32)sClientBroadcastSessionAttr, (UInt32) theSession,theErr);
(void) QTSS_SetValue(inParams->inClientSession, qtssCliSesTimeoutMsec, 0, &sBroadcasterSessionTimeoutMilliSecs, sizeof(sBroadcasterSessionTimeoutMilliSecs));
}
这里需要注意的是,当我们前面已经有一路相同qtssRTSPReqLocalPath路径的ReflectorSession存在的时候,将不进行再创建,直接Resolve原有的ReflectorSession,所以会出现一种情况,当开始的推送与后面进行的推送音视频sdp不一致的时候,就会出现错误,所以, ReflectorSession的引用与释放需要注意!
完成ReflectorSession的创建,下一步解析track ID,具体的解析方法可以根据自己的实际应用,有的按照track%d解析,有的按照trackID=%d解析,再根据trackId获取具体的track sdp信息,AddRTPStream创建对应于具体track的RTP流
theStreamInfo->fSetupToReceive = true;//标识流转发的建立
// This is an incoming data session. Set the Reflector Session in the ClientSession
theErr = QTSS_SetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &theSession, sizeof(theSession));//设置转发会话的RTPSession字典的sClientBroadcastSessionAttr字段
Assert(theErr == QTSS_NoErr);
if (theSession != NULL)
theSession->AddBroadcasterClientSession(inParams);//设置ReflectorSession的fBroadcasterSession属性为inParams->inClientSession,呵呵比较乱噢,相当于相互引用
Play: 具体到DoPlay过程,isPush为true的路径就比较简单了,只是将推送的RTSPSession中的sRTSPBroadcastSessionAttr属性设置为前面DoSetup中获取到的ReflectorSession
theLen = sizeof(inSession);
theErr = QTSS_GetValue(inParams->inClientSession, sClientBroadcastSessionAttr, 0, &inSession, &theLen);//DoSetup()中已经设置sClientBroadcastSessionAttr属性
if (theErr != QTSS_NoErr)
return QTSS_RequestFailed;
theErr = QTSS_SetValue(inParams->inClientSession, sKillClientsEnabledAttr, 0, &sTearDownClientsOnDisconnect, sizeof(sTearDownClientsOnDisconnect));
if (theErr != QTSS_NoErr)
return QTSS_RequestFailed;
Assert(inSession != NULL);
theErr = QTSS_SetValue(inParams->inRTSPSession, sRTSPBroadcastSessionAttr, 0, &inSession, sizeof(inSession));//设置到inParams->inRTSPSession的sRTSPBroadcastSessionAttr属性
if (theErr != QTSS_NoErr)
return QTSS_RequestFailed;
RTP数据处理:ProcessRTPData(),这里只处理RTP over TCP的数据,根据RTP数据中的channel值,调用特定的ReflectorStream进行处理和转发,具体函数为:ProcessRTPData(),先通过前面在DoSetup() & isPush为true时设置的sRTSPBroadcastSessionAttr属性,获取ReflectorSession
ReflectorSession *theSession = NULL;
UInt32 theLen = sizeof(theSession);
QTSS_Error theErr = QTSS_GetValue(inParams->inRTSPSession, sRTSPBroadcastSessionAttr, 0, &theSession, &theLen);
if (theSession == NULL || theErr != QTSS_NoErr)
return QTSS_NoErr;
再根据channelID获取具体的ReflectorStream并进行数据推送,给具体的ReflectorStream进行处理
UInt32 inIndex = packetChannel / 2; // one stream per every 2 channels rtcp channel handled below
ReflectorStream *theStream = NULL;
if (inIndex < numStreams)
{
theStream = theSession->GetStreamByIndex(inIndex);//获取对应track的ReflectorStream
SourceInfo::StreamInfo *theStreamInfo = theStream->GetStreamInfo();
UInt16 serverReceivePort = theStreamInfo->fPort;
Bool16 isRTCP = false;
if (theStream != NULL)
{
if (packetChannel & 1)
{
serverReceivePort ++;
isRTCP = true;
}
theStream->PushPacket(rtpPacket, packetDataLen, isRTCP); //推送数据给ReflectorStream并转发给分发列表
}
}
5.2.6 二次开发模块添加的要求
每个DSS模块必须实现两个函数:一个是Main函数,服务器在启动时将调用这个函数进行必要的初始化。另一个是Dispatch函数,通过实现此函数,服务器可调用DSS模块并完成特定处理。对于编译到服务器里面的模块,其主函数的地址必须传递到服务器的模块Main函数中。
具体实现时,Main函数必须命名为MyModule_Main,其中MyModule是模块的文件名。此函数的实现通常如下所示:
QTSS_Error MyModule_Main(void *inPrivateArgs)
{
return _stublibrary_main(inPrivateArgs, MyModuleDispatch);
}
每个DSS模块都必须提供一个Dispatch函数。服务器为了特定的目的需要使用某个模块时,是通过调用该模块的Dispatch函数来实现的,调用时必须将任务的名称及相应的参数传递给该函数。在DSS中,使用角色(Role)这个术语来描述特定的任务。Dispatch函数的格式如下所示:
void MyModuleDispatch(QTSS_Role inRole,QTSS_RoleParamPtr inParams);
其中MyModuleDispatch是Dispatch函数的名称;MyModule是模块的文件名;inRole是角色的名称,只有注册了该角色的模块才会被调用;inParams则是一个结构体,可用于传递相应的参数。
5.3 视频播放器(VLC)
我们提供了一个裁剪过适用于此系统的VLC,关于原版详细分析如下。
5.3.1 eclipse 调试源码
原版源码:项目/源码/vlc_source_android.Zip
其中源码获取是:git clone git://git.videolan.org/vlc-ports/android.git
源码介绍
相关源码介绍 : VLC 源码依赖于 另外四个工程;
•vlc-android 工程 : VLC 的主要源码;
•appcompat 工程 : 低版本兼容库, VLC 源码 vlc-android 需要依赖该工程;
•cardview 工程 : VLC 源码 vlc-android 需要依赖该工程;
•libvlc 工程 : VLC 源码 vlc-android 需要依赖该工程;
•WheelView 工程 : VLC 源码 vlc-android 需要依赖该工程;
源码导入
将源码导入 eclipse : 主需要重新设置一下依赖, 其它不用修改;
vlc 源码依赖 : vlc 依赖其余的四个工程, 下面的是 project.properties 内容;
target=android-21
android.library.reference.1=..\\libvlc
android.library.reference.2=../appcompat
android.library.reference.3=../cardview
android.library.reference.4=../WheelView
执行安装
执行效果 :
5.3.2 包结构详解
VLC Android 源码包结构分析 :
•主包结构截图 : org.vedio.vlc 包下的内容;
•audio 包 : 音频相关的包;
•gui 包 : 界面 UI 相关包;
•interfaces 包 : 定义各种接口;
•widget 包 : 自定 义组件相关的包;
•utils 包 : 相关工具类;
5.3.3 主要类介绍
注意 : 以下介请结合自己的理解去阅读代码
(1) org.videolan.vlc 下类介绍
org.videolan.vlc 下类介绍 :
•MediaDatabase 介绍 : 数据库操作相关类, 该类中定义了 SQLiteOpenHelper 子类, 并且定义了几个数据库, 创建了以下数据表 directories_table 路径表, media_table 媒体信息表, playlist_table 单个播放列表, playlist_media_table 播放列表集合表, searchhistory_table 搜索记录表, mrl_table mrl 表;
•MediaGroup 介绍 : 继承了 Media 类(在 libvlc 中维护, 维护视频音频后缀名称或扩展名), 用于维护一个 Media 集合;
•MediaLibrary 介绍 : Media 相关库, 该类中维护了一个条目列表, 主要对这个条目列表进行操作 (疑问, 没看懂);
•PhoneStateReceiver 介绍 : 广播接收者, 一个监听手机电话状态的广播接收者, 如果有电话打入, 或结束通过, 进行对应的操作;
•RemoteControlClientReceiver 介绍 : 广播接收者, 通过远程 wifi, 蓝牙, 屏幕锁定解锁 等接收事件, 进行响应的操作;
•Thumbnailer 介绍 : 是 Runnable 子类, thumbnail 用于代表要执行的动作, 等待播放的流媒体;
•VLCApplication 介绍 : Application 子类, 进行全局的设置;
•VLCCallbackTask 介绍 : AsyncTask 的子类, 这是个回调的帮助类, 能够在线程中更容易实现回调;
•VLCCrashHandler 介绍 : 用于处理未捕获的崩溃信息, 打印到日志 或者 文件中;
(2) org.videolan.vlc.audio 下类介绍
org.videolan.vlc.audio 包类介绍 :
•AudioService 介绍 : 集成 Service, 播放音频的后台服务;
•AudioServiceController 介绍 : 音频服务控制类;
•RepeatType 介绍 : 重复类型枚举定义, 不重复, 重复一次, 循环;
(3) org.videolan.vlc.widget 下类介绍
org.videolan.vlc.widget 类介绍 :
•AnimatedCoverView 介绍 : 继承 View 组件, 自定义组件, 动画切换相关的 自定义 View;
•AudioMediaSwitcher 介绍 : 继承结构 AudioMediaSwitcher -> FlingViewGroup -> ViewGroup, 音频媒体切换相关类;
•AudioPlaylistItemViewGroup 介绍 : 继承结构 AudioPlaylistItemViewGroup -> FlingViewGroup, 音频播放列表相关类;
•ContentLinearLayout 介绍 : 继承 LinearLayout, 重写了 onInterceptTouchEvent 方法, 用于拦截触摸事件, 当媒体正在播放的时候, 如果触摸子组件, 触发事件, 会影响播放, 此时我们需要拦截这些触摸事件;
•EqualizerBar 介绍 : 继承 LinearLayout, 均衡器调节条;
•ExpandableLayout 介绍 : 主要内容;
•FlingViewGroup 介绍 : 继承 ViewGroup, 主要是修改了一些手势操作, 覆盖重写了 onScrollChanged, onTouchEvent, onInterceptTouchEvent 方法;
•HeaderScrollView 介绍 : 继承 HorizontalScrollView, 横向滑动的 View 组件;
•SlidingPaneLayout 介绍 : 继承 ViewGroup, 这个类是从 Android 中剥离出来的, 属于 support-v4 中的一个类, 如果想要上下滑动, 不是左右侧划, 需要修改一些地方;
•VerticalSeekBar 介绍 : 继承 SeekBar, 这个组件是一个垂直的拖动条;
•VLCAppWidgetProvider 介绍 : 集成 AppWidgetProvider 类, App 组件提供者, 相当与一个广播接收者;
(4) org.videolan.vlc.util 下类介绍
org.videolan.vlc.util 包类介绍 :
•AndroidDevices 类 : 获取手机相关信息, 是否有内存卡, 手机型号, 获取存储路径, 获取媒体目录;
•BitmapCache 类 : 图片缓存相关类, 使用 LruCache 实现图片的流畅缓存;
•BitmapUtil 类 : 处理位图相关类, 提供 边缘切割, 缩放, 从缓存中获取图片;
•CustomDirectories 类 : 管理用户信息存放路径;
•Logcat 类 : 获取日志, 将日志输出到文件中;
•MurmurHash : MurmurHash算法:高运算性能,低碰撞率,由Austin Appleby创建于2008年;
•Preferences : SharedPreferences 操作相关;
•Strings : 用于处理字符串相关的工具类;
•Util : 一些小公共方法;
•VLCInstance : libvlc 相关的类, 与 libvlc 工程相关;
•VLCRunnable : 继承 Runnable, 线程相关类;
•WeakHandler : 继承 Handler;
(5) org.videolan.vlc.interfaces 下类介绍
org.videolan.vlc.interfaces 包介绍 :
•IAudioPlayer 介绍 : 音频播放接口, 提供了更新 和 更新进度条方法;
•IAudioPlayerControl 介绍 : 音频播放控制接口, 提供了一系列的音频控制方法;
(4) org.videolan.vlc.audio 下类介绍
org.videolan.vlc.gui.audio.widget 包介绍 :
•CoverMediaSwitcher 介绍 : 继承结构 CoverMediaSwitcher -> AudioMediaSwitcher -> FlingViewGroup -> ViewGroup;
•HeaderMediaSwitcher 介绍 : 继承结构 HeaderMediaSwitcher -> AudioMediaSwitcher -> FlingViewGroup -> ViewGroup;