OpenCORE

OpenCORE <AVI文件的识别和解析部分>

一.OpenCORE整体结构

    逻辑层主要由PVPlayer,PlayerDriver,PVPlayerEngine来组成,其中PVPlayer主要接受上层的操作(JNI),PlayerDriver负责连接PVPlayer与PVPlayerEngine,主要是将从PVPlayer的指令交给engine来处理,PVPlayerEngine负责真正的播放处理,如注册节点,建立节点,初始化节点,建立节点间的联系等.

    OpenCORE数据处理部分是基于一种节点(Node)的结构,主要由sourcenode(parsernode), decnode, sinknode组成.sourcenode主要负责文件的解析,decnode负责对解析出来的数据进行解码,而sinknode是建立相应的输出管道video/audio,sinknode只是针对输出的一种抽象,真正工作的是PVMediaOutPutNode, PVMediaOutPutNode的作用主要是将数据分发给相应的Video输出和Audio输出.每个node都有一个自身的状态(state),在播放之前必须保证每一个涉及到的节点状态的正确性,才能完成播放,每个节点几乎都有从idle ->init->prepare->start的状态,,在播放之前,每个节点必须出于start状态.
   
    OpenCORE的大部分代码基于”命令构建->命令发出->异步处理->处理结果反馈”的一种模式,大部分的代码执行结果都保存在构建的命令或参数之中,因为有命令,就需要去维持一个命令队列,因此OpenCore的代码看起来很烦琐.异步提高了程序的执行效率,但是也会涉及到同步问题,opencore的代码却避免了这个问题,熟悉了opencore的代码之后,架构得也很清晰.例如,在engine中如果要去执行sourcenode的Init(),首先engine中会去构造一个PVPlayerEngineContext这个命令的上下文,然后调用parsernode的init函数,将这个上下文做为参数传递给init函数,init函数中又会去根据这个上下构建一个init的parsernode命令,然后加入到pasernode的命令队列之中,这时候结果已经返回给了engine,返回的结果仅仅是构建的这个命令加入parsernode命令队列的成功与否,在parsernode内部会继续在另一个线程中执行init的操作,最后init的操作执行完成后,通过ReportCmdCompleteEvent 这个函数,将执行的结果通知给engine,然后engine就可以根据结果进行下面的处理.

二.文件的播放流程
 
必要的步骤是:

    1.文件的识别
    2.根据识别的结果建立相应的sourcenode,初始化sourcenode,解析出流的格式(如音频,视频,文字流等).
    3.建立输出管道,初始化管道状态
    4.根据流的格式建立起相应的decNode,或者如果不需要decNode如字幕,PCM格式的音频等.初始化decNode.
    5.请求port(做为各节点输入输出数据的一个抽象的”端口”)
    6.配置mediaoutput输出环境(Config MIO(Media Input/Output)).如Video需要配置视频的宽和高,视频显示的宽和高,subformattype.音频需要配置声道数,采样率(Hz),format type.

完成以上的工作后,基本上就可以通过parsernode去解析文件,然后将数据通过相应的port发送出去,完成播放了

三.avi的解析(parserNode的实现).

    要完成avi的解析就先要对文件进行识别,识别之后才能建立相应的parsernode节点.工作原理:首先注册avi的识别方法,储存到一个STL容器之中,这个STL中还保存了其它文件的识别方法.文件过来之后,首先遍历这个STL容器,分别调用各个识别方法对文件进行识别,如果成功则说明这个文件是这个识别方法所支持的格式.而parserNode在进行注册的时候会指定这个parsernNode支持文件的格式,这样就可以一一对应起来.

1>  avi的识别recognizer实现
   
    文件的识别模块在/pvmi/recognizer/plugins下,可以仿照其它识别完成相应文件目录建立.类文件也可以参照其它格式的来建立.例如avi这里主要完成了pvaviffrec_factory.h/pvaviffrec_factory.cpp/pvaviff_plugin.h/ pvaviff_plugin.cpp几个文件factory类和plugin类可以像其它格式一样继承接口,实现其中的方法.例如
pvaviff_plugin.cpp中实现的方法:

PVMFStatus     PVAVIFFRecognizerPlugin::SupportedFormats(PVMFRecognizerMIMEStringList& aSupportedFormatsList)
{
    // Return AVI as supported type
    OSCL_HeapString<OsclMemAllocator> supportedformat = PVMF_MIME_AVIFF;
    aSupportedFormatsList.push_back(supportedformat);
    return PVMFSuccess;
}

这个方法用来返回这个识别方法支持的文件格式,在这里我们用了PVMF_MIME_AVIFF,  PVMF_MIME_AVIFF这个宏在Pvmf_format_type.h中被定义,
#define PVMF_MIME_AVIFF         "x-pvmf/mux/avi"
后面的字符串内容并没有特殊的含义,也不影响程序的执行.不过在业界对于各个媒体文件有专门Mime Type.

        
PVMFStatus PVAVIFFRecognizerPlugin::Recognize(PVMFDataStreamFactory& aSourceDataStreamFactory, PVMFRecognizerMIMEStringList* aFormatHint,
        Oscl_Vector<PVMFRecognizerResult, OsclMemAllocator>& aRecognizerResult)
{
   ...
}
这里是文件的识别方法avi是基于RIFF的一种文件结构,avi文件一般前4个字节用来标识RIFF这个标识,后4个字节来标识文件的大小,再后面4个字节,用来标识文件类型”AVI ”,因此可以确定文件类型为AVI.

PVMFStatus PVAVIFFRecognizerPlugin::GetRequiredMinBytesForRecognition(uint32& aBytes)
{
    aBytes = AVIFF_MIN_DATA_SIZE_FOR_RECOGNITION; //12
    return PVMFSuccess;
}
由上分析,avi的识别文件的大小必须至少是12个字节..

2>  avi的识别recognizer的注册.
  
    完成recognizer之后需要将这个recoginzer注册到系统中.在/engines/player/config/core/pv_player_node_registry_populator.cpp中RegisterAllRecognizers方法中进行注册,注意引入相关的头文件.

         tmpfac = OSCL_STATIC_CAST(PVMFRecognizerPluginFactory*, OSCL_NEW(PVAVIFFRecognizerFactory, ()));
        if(PVMFRecognizerRegistry::RegisterPlugin(*tmpfac) == PVMFSuccess)
        {
            aRegistry->RegisterRecognizer(tmpfac);
            nodeList->push_back(tmpfac);
        }
        else
        {
            OSCL_DELETE((PVAVIFFRecognizerFactory*)tmpfac);
            tmpfac = NULL;
            return;
        }

这样就完成recognizer部分

3> avi parserNode的实现.
  
   parserNode的主要功能是解析,除了在播放的时候解析出相应的流数据以供播放之外.parserNode还担任者其它节点在准备播放以前所需要的数据以完成各个状态的初始化工作. 如engine会调用方法去获得parserNode的流的类型,以此建立相应的decNode,并且初始化decNode.
   parserNode仅仅是一个解析的工作节点,真正的提供的解析类在/fileformats/下的各个文件夹.如avi的parser类是在/fileformats/avi/下.parserNode主要是去调用相应/fileformats/下解析类的API,去完成这个parserNode中各个方法的实现.
   在/nodes/下面有各个媒体的parserNode,可以仿照其它建立起avi的parsernode文件夹.如/nodes/pvaviffparsernode/,目录和类文件的建立也可以仿照其它来创建.这里必须的一个factory类,一个node类和一个port类.

Factory类

   factory类主要提供给外界用来创建parserNode类对象的一个工厂类.可以完全按照其它媒体格式文件的方式来完成这个avi的factory类,注意这里需要定义一个PVUuid,uuid是唯一标识.可以在控制台中直接输入 uuidgen 命令来生成一个uuid,初始化uuid即可.如
//uuidgen generate:64cf3733-a8bd-4f39-8a48-66951b54bbf2
#define KPVMFAviFFParserNodeUuid PVUuid(0x64cf3733,0xa8bd,0x4f39,0x8a,0x48,0x66,0x95,0x1b,0x54,0xbb,0xf2)
    
parserNode类

    parserNode类包括涉及到的一些其它类规模比较庞大,但是parserNode在OpenCORE框架中已经被定位,所要实现的接口,实现的功能等都已经被设定.因此可以仿照其它媒体格式来写这个avi 的parserNode,如Mp4.下面就是avi parsernode中的一些基本类.

pvmf_aviffparser_node.h

   在pvmf_aviffparser_node.h这个头文件中大约就有1千行的代码,有下面几个类
PVMFAVIFFParserNode        //parsernode类定义                             必须
PVMFAVIFFParserNodeCommand,//框架内实现avi的command类,可以直接copy mp4 .  必须
PVMFAVIFFPortIter,         //                                             可以不要
PVMFAVIParserNodeLoggerDestructDealloc //pvlogger用                       可以不要
  
在源码中可以看到 继承关系
class PVMFAVIFFParserNode:public OsclTimerObject,
    public PVMFNodeInterface,
    public PVMFDataSourceInitializationExtensionInterface,
    public PVMFTrackSelectionExtensionInterface,
    public PvmfDataSourcePlaybackControlInterface,
    public PVMFMetadataExtensionInterface,
    public PVMFTrackLevelInfoExtensionInterface,
    public PVMFCPMStatusObserver,
    public PvmiDataStreamObserver,
    public PVMIDatastreamuserInterface,
    public PVMFFormatProgDownloadSupportInterface,
    public OsclTimerObserver,
    public PVMFCPMPluginLicenseInterface,
    public PvmiCapabilityAndConfig,
    public PVMFMediaClockStateObserver, // For observing the playback clock states
    public PvmfDataSourceDirectionControlInterface
   
其中 OsclTimerObject,
     OsclTimerObserver,
     PVMFNodeInterface,
     PVMFDataSourceInitializationExtensionInterface,
     PVMFTrackSelectionExtensionInterface,
     PvmfDataSourcePlaybackControlInterface
     PVMFMediaClockStateObserver

这个几个是必要继承的类,需要去实现其中的纯虚函数.否则在pasernode的初始化过程中会不成功PVMFAVIFFParserNode类的头文件方法定义和变量定义基本上也都可以copy mp4的来,有的没有继承的类,是不必要去实现其中的虚方法和相关变量定义的.

pvmf_aviffparser_node.cpp

   这里是parserNode的实现,前面提到node部分工作大多基于命令的一种方式,因此我们在parsernode就需要去完成这个一结构.这部分代码基本上都可以参照MP4的来写,适应avi的即可.可以大部分复制mp4的代码过来,比如QueryInterface(查找接口)这一过程:

QueryInterface这个方法是在engine中频繁被调用的,主要是为了获得相应功能的指针,如parserNode是继承了 PVMFDataSourceInitializationExtensionInterface这个抽象类,因此我们要实现其中的纯虚方法如SetSourceInitializationData,但是在engine中并不是直接通过用parserNode的指针来调用SetSourceInitializationData这个方法,而是先通过QueryInterface的方式来先查找到这个PVMFDataSourceInitializationExtensionInterface的实例化指针,其实也就是parserNode指针本身, QueryInterface的最终作用就是根据参数uuid来查找这个子类是否支持了相应的抽象类,然后将本身parsernode指针转化为父类的指针,返回给engine,然后engine调用抽象类中被实现的方法,完成功能调用.

首先engine中会通过iSourceNode->QueryInterface来调用这个接口
在queryInterface之中

 PVMFAVIFFParserNodeCommand cmd;
 cmd.PVMFAVIFFParserNodeCommandBase::Construct(aSessionId, PVMF_GENERIC_NODE_QUERYINTERFACE, aUuid, aInterfacePtr, aContext);
 return QueueCommandL(cmd);

会构造一个PVMF_GENERIC_NODE_QUERYINTERFACE这个类型cmd,然后调用QueueCommandL把这个命令加入到队列之中
 QueueCommandL代码:

        PVMFCommandId id;
        id = iInputCommands.AddL(aCmd);
        PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "PVMFAVIFFParserNode::QueueCommandL() called id=%d", id));
        /* Wakeup the AO */
        RunIfNotReady();     //起动线程的Run来处理这个命令的队列
        return id;

在run函数中,会调用ProcessCommand来处理命令队列.   
     //Process commands.
    if (!iInputCommands.empty())
    {
        ProcessCommand();
    }

在ProcessCommand中
      
          case PVMF_GENERIC_NODE_QUERYINTERFACE:
            cmdstatus = DoQueryInterface(aCmd);
            CommandComplete(iInputCommands, aCmd, cmdstatus);
            break;

可见真正执行的是 DoQueryInterface这个方法,DoQueryInterface中又会调用queryInterface,根据uuid,转化指针.
CommandComplete 中结束命令操作,通过   
          //Report completion to the session observer.
            ReportCmdCompleteEvent(session, resp)
将结果通知给engine.

这是这个命令的执行过程,其它的大部分方法如,Init,RequestPort,Pause,Start,Reset等大部分都是以这种形式,因此代码大部分都可以直接copy mp4的,然后修改适应avi.

下面主要介绍一些针对avi这种媒体格式,而去实现的一些函数.从node的idle状态到start状态
     
 Init过程

PVMFStatus PVMFAVIFFParserNode::SetSourceInitializationData(OSCL_wString& aSourceURL,PVMFFormatType& aSourceFormat,OsclAny* aSourceData)
这个函数主要完成
      //cleanup any prior source.
      CleanupFileSource();
      iFilename = aSourceURL;

其它的一些代码如果是不支持在线播放或者CPM管理的基本上可以忽略

DoInit(),这个过程主要完成创建avi parser 类的指针,

PVMFStatus PVMFAVIFFParserNode::DoInit(PVMFAVIFFParserNodeCommand& aCmd)
{
   ...
}
在其它媒体格式中有去初始化CPM的(Content Policy Manager)的,这方面了解不多,但也不影响播放,所以没有在avi的parsernode中去实现这一块.Init的主要主要作用就完成parser类指针的创建.
可以看到在avi的源码中还最终回去调用ParseAVIFile这个函数:

 PVMFDataStreamFactory* dsFactory = iCPMContentAccessFactory;
    if ((dsFactory == NULL) && (iDataStreamFactory != NULL))
     {
        dsFactory = iDataStreamFactory;
     }

    int32 error = 0;

    OSCL_TRY(error, iAVIFileHandle = PVAviFile::CreateAviFileParser(iFilename, error, &iFileServer,dsFactory,iFileHandle));
其中dsFactory和iFileHandle如果不是需要在线播放,这两个参数不设置并不会影响.

Prepare过程

   在这之前engine需要获得一些媒体的其它信息,如媒体的作者,播放时长等.主要是通过 PVMFMetadataExtensionInterface这个抽象类的方法来实现.这个也可以不必实现,不是必须的.
如在avi中,我们只是提供了一个” duration”播放时长,留个engine使用.实现的几个方法
 uint32 GetNumMetadataKeys(char* aQueryKeyString = NULL);
 uint32 GetNumMetadataValues(PVMFMetadataList& aKeyList);
 PVMFCommandId GetNodeMetadataKeys(PVMFSessionId aSessionId, PVMFMetadataList& aKeyList, uint32 aStartingKeyIndex, int32 aMaxKeyEntries,
                                              char* aQueryKeyString = NULL, const OsclAny* aContextData = NULL);
 PVMFCommandId GetNodeMetadataValues(PVMFSessionId aSessionId, PVMFMetadataList& aKeyList,
                                                Oscl_Vector<PvmiKvp, OsclMemAllocator>& aValueList, uint32 aStartingValueIndex, int32 aMaxValueEntries, const OsclAny* aContextData = NULL);
 PVMFStatus ReleaseNodeMetadataKeys(PVMFMetadataList& aKeyList, uint32 aStartingKeyIndex, uint32 aEndKeyIndex);
 PVMFStatus ReleaseNodeMetadataValues(Oscl_Vector<PvmiKvp, OsclMemAllocator>& aValueList, uint32 aStartingValueIndex, uint32 aEndValueIndex);

uint32 PVMFAVIFFParserNode::GetNumMetadataKeys(char* aQueryString)
{
    uint32 num_entries = 0;
    if(aQueryString == NULL || iAVIFileHandle == NULL)
        return 0;
    if (oscl_strstr("duration", aQueryString) != NULL)
    {
        num_entries++;
    }
    return num_entries;
}
uint32 PVMFAVIFFParserNode::GetNumMetadataValues(PVMFMetadataList& aKeyList)
{
    uint32 num_entries = 0;
    uint32 numKeys = aKeyList.size();
    for(uint32 i=0;i < numKeys;i++)
    {
         if (oscl_strcmp(aKeyList[i].get_cstr(), "duration") == 0)
         {
             // Duration
                // Increment the counter for the number of values found so far
             ++num_entries;
         }
    }
    return num_entries;
}
GetNodeMetadataKeys 和GetNodeMetadataValues都是以命令形式执行.

  PVMFStatus PVMFAVIFFParserNode::DoGetMetadataKeys(PVMFAVIFFParserNodeCommand& aCmd)
{
   ...
}
这个函数主要作用是返回key的的列表,这个key的列表有parser类来决定.依靠解析出来的,我们这里只支持”duration”这个key
PVMFStatus PVMFAVIFFParserNode::DoGetMetadataValues(PVMFAVIFFParserNodeCommand& aCmd)
{
        PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE,
                    (0, "PVMFAVIFFParserNode::DoGetMetadataValues() In"));

    PVMFMetadataList* keylistptr_in = NULL;
    PVMFMetadataList* keylistptr = NULL;
    PVMFMetadataList completeKeyList;
    Oscl_Vector<PvmiKvp, OsclMemAllocator>* valuelistptr = NULL;
    uint32 starting_index;
    int32 max_entries;

    // Extract parameters from command structure
    aCmd.PVMFAVIFFParserNodeCommand::Parse(keylistptr_in,
                                           valuelistptr,
                                           starting_index,
                                           max_entries);

    if (iAVIFileHandle == NULL || keylistptr_in == NULL || valuelistptr == NULL)
    {
        // The list pointer is invalid, or we cannot access the mp3 ff library.
        return PVMFFailure;
    }

    PvmiKvp KeyVal;
    KeyVal.key = NULL;
    uint32 KeyLen = 0;
    uint32 numKeys = 0;
    PVMFMetadataList keylistptr_tmp = *keylistptr_in;
    numKeys = keylistptr_tmp.size();
    if (numKeys == 0 || starting_index > (numKeys - 1) || max_entries == 0)
    {
        return PVMFErrArgument;
    }

    //just support get duration here ...~~
    for(uint32 i=0;i<numKeys;i++)
    {
        if (oscl_strstr(keylistptr_tmp[i].get_cstr(), "duration"))
        {
             //duration
             uint32 duration = 0;
             duration = iAVIFileHandle->GetFileDuration()*1000;
            if (duration > 0)
            {
                KeyLen = oscl_strlen("duration") + 1; // for "duration;"
                KeyLen += oscl_strlen(PVMI_KVPVALTYPE_STRING); // for "valtype="
                KeyLen += oscl_strlen(PVMI_KVPVALTYPE_UINT32_STRING) + 1; // for "uint32;"
                KeyLen += oscl_strlen("timescale=1000") + 1; // for "timescale=1000" and NULL terminator

                // Allocate memory for the string
                int32 leavecode = OsclErrNone;
                KeyVal.key = (char*) AllocateKVPKeyArray(leavecode, PVMI_KVPVALTYPE_CHARPTR, KeyLen);
                if (OsclErrNone == leavecode)
                {
                    // Copy the key string
                    oscl_strncpy(KeyVal.key, "duration", oscl_strlen("duration") + 1);
                    oscl_strncat(KeyVal.key, ";", oscl_strlen(";"));
                    oscl_strncat(KeyVal.key, PVMI_KVPVALTYPE_STRING, oscl_strlen(PVMI_KVPVALTYPE_STRING));
                    oscl_strncat(KeyVal.key, PVMI_KVPVALTYPE_UINT32_STRING, oscl_strlen(PVMI_KVPVALTYPE_UINT32_STRING));
                    oscl_strncat(KeyVal.key, ";", oscl_strlen(";"));
                    oscl_strncat(KeyVal.key, "timescale=1000", oscl_strlen("timescale=1000"));
                    KeyVal.key[KeyLen-1] = NULL_TERM_CHAR;
                    // Copy the value
                    KeyVal.value.uint32_value = duration;
                    // Set the length and capacity
                    KeyVal.length = 1;
                    KeyVal.capacity = 1;
                }
                else
                {
                    // Memory allocation failed
                    KeyVal.key = NULL;
                    break;
                }
            }

        }
    }

    // Add the KVP to the list if the key string was created
    if (KeyVal.key != NULL)
    {
            int32 leavecode = OsclErrNone;
        leavecode = PushKVPValue(KeyVal, *valuelistptr);
        if (OsclErrNone != leavecode)
        {
            // push kvp failed
            return PVMFErrNoMemory;
        }
    }

    return PVMFSuccess;
}
这个函数的作用是根据key的值,去找到相应的value值.这里只是取得一个            
           uint32 duration = 0;
             duration = iAVIFileHandle->GetFileDuration()*1000;//单位ms
这部分代码的执行过程基本上都可以copy上面,我们只需要找到相应的key,并且把value赋值就可了.

 
GetMediaPresentationInfo取得流的类型

     此方法是比较重要的一个方法,是必去实现的.它完成的主要功能是对文件进行解析,解析出各个流的格式,比如音频流,视频流,文字流...等,并且为每个流指定一个mimi type,这个mime type会对应decNode支持的mime Type,从而将parserNode解析出来的流指定decNode.
   Avi中的实现:
PVMFStatus PVMFAVIFFParserNode::GetMediaPresentationInfo(PVMFMediaPresentationInfo& aInfo)
{
     ...
}
这个函数主要作用是填充 GetMediaPresentationInfo这个结构体,如在avi的实现中有
aInfo.setDurationValue(iAVIFileHandle->GetFileDuration()*1000);//设置持续时间ms
aInfo.setDurationTimeScale(timeScale);//设置timescale
aInfo.addTrackInfo(tmpTrackInfoVideo);//保存trackinfo,流信息
tmpTrackInfoVideo是一个PVMFTrackInfo的结构体,一个媒体文件可能有多个流,因此我们需要add多个trackinfo.在avi的GetStream2TrackInfo中,可以看到如何去填充这个trackinfo这个结构体.
     aTrack.setTrackID(i); //设置track   id
     aTrack.setPortTag(i);     //设置port tag    
     aTrack.setTrackDurationTimeScale(iAVIFileHandle->GetScale(i));
     aTrack.setTrackDurationValue(iAVIFileHandle->GetStreamDuration(i));
     aTrack.setTrackMimeType(mime_type);
因为针对各个媒体格式,我们解析所获得的mime type的类型是不同的,所以这部分的实现过程不同.因此需要有一些对这个多媒体格式的一些认识...不需要非常专业.
如在avi中如何获得video的mime type
      …
      iAVIFileHandle->GetFormatSpecificInfo(i, aFormatSpecificDataFrag);
      ...
         if(oscl_strstr(iAVIFileHandle->GetStreamMimeType(i).get_cstr(),"video"))
      {
            uint8 fmtType[4] = {0};
            uint32 size = 4;
            iAVIFileHandle->GetVideoFormatType((uint8*)fmtType, size, i);
            uint32 temp = MAKE_FOURCC(fmtType[0], fmtType[1], fmtType[2], fmtType[3]);

           BitmapInfoHhr* videoHdr = OSCL_STATIC_CAST(BitmapInfoHhr*,aFormatSpecificDataFrag.getMemFragPtr());
           if (!oscl_strncmp((char*)fmtType, "DIB ", size))
            {
               if (BITS_PER_SAMPLE12 == videoHdr->BiBitCount)
                 {
                      mime_type = _STRLIT_CHAR(PVMF_MIME_RGB12);
                      iSampleSizeVideo = videoHdr->BiBitCount;
                 }
               else if (BITS_PER_SAMPLE24 == videoHdr->BiBitCount)
                 {
                   mime_type = _STRLIT_CHAR(PVMF_MIME_RGB24);
                   iSampleSizeVideo = videoHdr->BiBitCount;
                 }
               else
                 {
                   return PVMFFailure;
                 }
            }
           else if (IsYUVFormat_Supported(temp))
            {
                  mime_type = _STRLIT_CHAR(PVMF_MIME_YUV420);
            }
           else
            {
                return PVMFFailure;
            }

       }
这段代码主要是获得视频流的mime type,主要是看parser类能提供些什么,这而avi的parser类提供了一个GetVideoFormatType这个方法,返回的是一个字符数组uint8 fmtType[4],因为我们无decNode只能解未经压缩的数据,因此我们将其和”DIB ”比较.然后再获得 BitmapInfoHhr,确定是 PVMF_MIME_RGB12还是PVMF_MIME_RGB24.另外如果是YUV支持的,直接将其type设置为 PVMF_MIME_YUV420.

DoRequestPort请求端口,内存池分配的实现
   
   请求port,port类是一个继承PvmfPortBaseImpl和PvmiCapabilityAndConfigPortFormatImpl.这个类很重要.负责联系decNode或者输出node(因为有可能有的格式不需要decNode)的一个”端口”.每个流会对应一个port.DoRequestPort里面会去每个流实例化一个在avi中PVAVIFFNodeTrackPortInfo对象,这个结构体里面储存了一些流的信息,每个格式可能不同,主要说一些共同部分.
主要有
    PVAVIFFNodeTrackPortInfo trackportinfo;
    trackportinfo.iTrackId = trackid;                //流的id
    trackportinfo.iPortInterface = outport;          //流的”port”端口
    trackportinfo.iFormatType = formattype;          //流的formattype
    trackportinfo.iFormatTypeInteger = PVMF_AVI_PARSER_NODE_AVI_AUDIO;//流的类型
    trackportinfo.iNumSamples = 10;                  //流的采样个数,在取采样数据的时候标识
                                                    //去多少个采样数据发送出去.一般视频为1
    trackportinfo.iMimeType = (*mimetype);      //流的mimetyoe
    trackportinfo.iClockConverter = clockconv;      //流的clock
    trackportinfo.iState = PVAVIFFNodeTrackPortInfo::TRACKSTATE_UNINITIALIZED;//流的状态
                                                                              
    trackportinfo.iTrackMaxDataSize = trackmaxqueuedepth*trackmaxdatasize;//流的采样数据大小
                                                  //解析的时候要将采样数据发送出去,这个大小就会
                                                  //决定我们去数据时,内存池分配的大小.
    trackportinfo.iTrackMaxQueueDepth =8;        //track的队列大小,好象没什么用...
    trackportinfo.iTrackDataMemoryPool = trackdatamempool;//流的内存池
    trackportinfo.iMediaDataImplAlloc = mediadataimplalloc;//为media分配内存的接口
    trackportinfo.iTextMediaDataImplAlloc = textmediadataimplalloc; //为text对象分配内存的接口                   (保留)
    trackportinfo.iMediaDataMemPool = mediadatamempool;//为mediaData分配的内存池
//    trackportinfo.iMediaDataGroupImplMemPool = mediadatagroupimplmempool;
//    trackportinfo.iMediaDataGroupAlloc = mediadatagroupalloc;
    trackportinfo.iNode = OSCL_STATIC_CAST(OsclTimerObject* , this);
    trackportinfo.iTimestamp = 0/*tsStartOffset*/;//时间戳
    trackportinfo.iSeqNum = 0;                    //流的数据序列 ,自增,每次发送数据后+1.
    trackportinfo.iSendBOS = true;              //标识是否需要发送BOS(Begin Of Stream),默认true
    trackportinfo.iLastTimestamp = 0; //标识上次采样的时间戳,以方便计算此次采样的持续时间(将本次         得到的时间戳-iLastTimestamp)即可.


完成各个node的DoRequestPort之后,engine就可以发命令播放了.在这里面还会在port连接(connect)过程中,会去设置Video/Audio MIO的一些必要参数,如果无法完成这一步,那么将会播放不成功(黑屏),在port类文件中会说明.

Start状态(播放),解析数据.发送数据

    执行流程是接受start命令(是被PVPlayerDataPath驱动),将Node的状态改为started,然后线程开始工作,执行HandleTrackState,处理track的状态,首先在初始化的时候track的状态是 UNINITIALIZED,然后将其改为GetState,如是第一次要发送BOS命令,GetState之后,如果出现异常或者流结束,退出.成功状态为SendState,
SendState成功后,继续将状态改为GetState,如此循环下去.知道流结束或者出现异常.
 
    HandleTrackState(处理track的状态)
    基本各个媒体都是相同可直接参照.
   
    SendBeginOfMediaStreamCommand(发送BOS命令)
    基本各个媒体都是相同可直接参照.
   
    RetrieveTrackData(解析流的数据)
    这个功能主要是将流的数据解析出来.如果是视频流,需要解析出来的每个sample(采样)的数据(data),时间戳(timestamp),和每个sample的持续时间(duration).基本上是一个sample一个sample的解析.而音频流可能需要多个sample一起解析,对于字幕等,可以仿照mp4的写,本文并没有涉及.
Avi中的实现:
        PVLOGGER_LOGMSG(PVLOGMSG_INST_LLDBG, iLogger, PVLOGMSG_STACK_TRACE, (0, "PVMFAVIFFParserNode::RetrieveTrackData() IN"));
    if (aTrackPortInfo.iState == PVAVIFFNodeTrackPortInfo::TRACKSTATE_DOWNLOAD_AUTOPAUSE)
    {
        PVMF_AVIFFPARSERNODE_LOGDATATRAFFIC((0, "PVMFAVIFFParserNode::RetrieveTrackData() - Auto Pause"));
        return false;
    }

    // Get the track ID
    uint32 trackid = aTrackPortInfo.iTrackId;
//
    // Create a data buffer from pool
    int errcode = OsclErrNoResources;
//
    OsclSharedPtr<PVMFMediaDataImpl> mediaDataImplOut;
    if (aTrackPortInfo.iFormatTypeInteger == PVMF_AVI_PARSER_NODE_3GPP_TIMED_TEXT)
    {
        //just reserved
        mediaDataImplOut = aTrackPortInfo.iTextMediaDataImplAlloc->allocate(aTrackPortInfo.iTrackMaxDataSize);
    }
    else
    {
        mediaDataImplOut = aTrackPortInfo.iMediaDataImplAlloc->allocate(aTrackPortInfo.iTrackMaxDataSize);
    }

    if (mediaDataImplOut.GetRep() != NULL)
    {
        errcode = OsclErrNone;
    }
    else
    {
        PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_INFO, (0, "PVMFAVIFFParserNode::RetrieveTrackData() No Resource Found"));
        aTrackPortInfo.iState = PVAVIFFNodeTrackPortInfo::TRACKSTATE_TRACKDATAPOOLEMPTY;
        aTrackPortInfo.iTrackDataMemoryPool->notifyfreeblockavailable(aTrackPortInfo, aTrackPortInfo.iTrackMaxDataSize);    // Enable flag to receive event when next deallocate() is called on pool
        return false;
    }

    // Now create a PVMF media data from pool
    errcode = OsclErrNoResources;
    PVMFSharedMediaDataPtr mediadataout;
    mediadataout = PVMFMediaData::createMediaData(mediaDataImplOut, aTrackPortInfo.iMediaDataMemPool);

    if (mediadataout.GetRep() != NULL)
    {
        errcode = OsclErrNone;
    }

    else
    {
        PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_INFO, (0, "PVMFAVIFFParserNode::RetrieveTrackData() Memory allocation for media data memory pool failed"));
        aTrackPortInfo.iState = PVAVIFFNodeTrackPortInfo::TRACKSTATE_MEDIADATAPOOLEMPTY;
        aTrackPortInfo.iMediaDataMemPool->notifyfreechunkavailable(aTrackPortInfo);     // Enable flag to receive event when next deallocate() is called on pool
        return false;
    }
    // Retrieve memory fragment to write to
    OsclRefCounterMemFrag refCtrMemFragOut;
    mediadataout->getMediaFragment(0, refCtrMemFragOut);

这段代码是从内存池中请求一块内存出来,和其它媒体几乎一致,用来储存解析出来的数据. Mediadataout是我们将要发送的数据.
    
     for(uint32 i =0 ; i < numSamples && nOutLength > 0 ;i++)
    {
        PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_INFO, (0, "PVMFAVIFFParserNode::RetrieveTrackData() nOutLength=%d",nOutLength));
        error = iAVIFileHandle->GetNextStreamMediaSample(trackid,pOutBuffer, nOutLength, ts);
        PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_INFO, (0, "PVMFAVIFFParserNode::RetrieveTrackData() get nOutLength=%d,ts=%d",nOutLength,ts));
       if(error != PV_AVI_FILE_PARSER_SUCCESS && error != PV_AVI_FILE_PARSER_EOS_REACHED)
       {
           PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_INFO, (0, "PVMFAVIFFParserNode::RetrieveTrackData() for->break==0"));
             break;
       }
       if( error == PV_AVI_FILE_PARSER_EOS_REACHED)
       {
           nOutLength = ts==0?0:nOutLength;
       }
       if(nOutLength > 0)
        {
            iGau.numMediaSamples++;
            iGau.info[i].len = nOutLength;
            iGau.info[i].ts  = ts;
            if(aTrackPortInfo.iLastTimestamp == 0)
            {
               iGau.info[i].ts_delta = 33;
            }
            else
               iGau.info[i].ts_delta = iGau.info[i].ts - aTrackPortInfo.iLastTimestamp;

            pOutBuffer +=  nOutLength;
           aTrackPortInfo.iLastTimestamp = iGau.info[i].ts;

        }
        else
        {
            PVLOGGER_LOGMSG(PVLOGMSG_INST_HLDBG, iLogger, PVLOGMSG_INFO, (0, "PVMFAVIFFParserNode::RetrieveTrackData() for->break==1"));
            break;
        }
       //reset
       nOutLength = nOutLengthTemp - nOutLength;
       nOutLengthTemp = nOutLength;
       ts = 0;
    }
这段代码就是根据 numSamples(采样数,视频是1,音频可能多个),通过解析方法 GetNextStreamMediaSample去取得sample数据和ts(时间戳).剩余部分就是将得到的数据去填充Mediadataout,和更新这个trackportinfo的一些信息了.
   
   SendTrackData 通过port将Retrieve的数据”发送”出去

   所谓的发送就是将数据以消息的形式通知其它节点的port,decNode或者输出的port. 这段代码各个媒体几乎相同,可仿照其它的来写.
   
    SendEndOfTrackCommand 发送流结束的命令
   基本上和其它媒体格式一样,稍微修改下就可以了.至此从处理流的状态到取数据,发送数据,结束数据就结束了

pvmf_aviffparser_outport.h
  
    主要有
    PVAVIFFNodeTrackPortInfo
    PVMFAVIFFParserOutPort
    PVAVIFFNodeTrackPortInfo这个类比较重要,在解析的时候经常用到trackportinfo这个类的对象.
    PVMFAVIFFParserOutPort,就是所谓的”port”类,继承2个类PvmfPortBaseImpl和PvmiCapabilityAndConfigPortFormatImpl.
    PVAVIFFNodeTrackPortInfo里面的变量或者方法基本各个媒体都相同,可以直接将其它的大部分移植过来,也可根据自己的需要添加其它变量.
    PVMFAVIFFParserOutPort继承了2个抽象类,必须去实现其中的虚方法,也可直接复用avi的.


pvmf_aviffparser_outport.cpp
  
  这里的代码基本上可以复制其它的.媒体文件 如mp4方式来完成.
  主要介绍Connect 函数

   Connect 建立连接
   在这一过程中,会去配置mio的一些参数,视频和音频.如果有未设置的,或者有明显的错误,比如视频的高度设置为0,将导致不能播放.
   视频主要是
   MOUT_VIDEO_WIDTH_KEY                  //视频宽
   MOUT_VIDEO_DISPLAY_WIDTH_KEY          //视频显示的宽
   MOUT_VIDEO_HEIGHT_KEY                 //视频的高
   MOUT_VIDEO_DISPLAY_HEIGHT_KEY         //显示的高
   MOUT_VIDEO_SUBFORMAT_KEY              //sub format
   音频是
   MOUT_AUDIO_FORMAT_KEY                 //audio的mime type 
   MOUT_AUDIO_SAMPLING_RATE_KEY          //采样率
   MOUT_AUDIO_NUM_CHANNELS_KEY           //声道数

这些值都可以通过解析来得到.实现过程几乎一样,只是要将取得的值设置即可.如avi的port中的实现.
   
    OsclAny* temp = NULL;
    aPort->QueryInterface(PVMI_CAPABILITY_AND_CONFIG_PVUUID, temp);
    PvmiCapabilityAndConfig *config = OSCL_STATIC_CAST(PvmiCapabilityAndConfig*, temp);
  
   if (!(pvmiSetPortFormatSpecificInfoSync(config, MOUT_VIDEO_WIDTH_KEY)))
    {
            PVMF_AVIFFPARSERNODE_LOGERROR((0, "PVMFAVIParserOutPort::Connect: Error - Unable To Decode Width To Peer"));
            return PVMFFailure;
    }
这个主要是调用pvmiSetPortFormatSpecificInfoSync方法
这个方法中的实现
    aFormatValType 就是 MOUT_VIDEO_WIDTH_KEY
       int32 width = iAVIFFParserNode->FindVideoWidth(trackInfoPtr->iTrackId);
        if (width > 0)
        {
            OsclMemAllocator alloc;
            PvmiKvp kvp;
            kvp.key = NULL;
            kvp.length = oscl_strlen(aFormatValType) + 1; // +1 for /0
            kvp.key = (PvmiKeyType)alloc.ALLOCATE(kvp.length);
            if (kvp.key == NULL)
            {
                return false;
            }
            oscl_strncpy(kvp.key, aFormatValType, kvp.length);

            kvp.value.uint32_value = (uint32)width;
            PvmiKvp* retKvp = NULL; // for return value
            int32 err;
            OSCL_TRY(err, aPort->setParametersSync(NULL, &kvp, 1, retKvp););
            /* ignore the error for now */
            alloc.deallocate((OsclAny*)(kvp.key));
        }
        return true;
其它几个参数的设置过程基本如此.对于音频参数的设置直接调用了
iAVIFFParserNode->SetAudioConfigSettings(config)这个方法.

以上就是parsernode实现的一些关键地方了.

4> avi parserNode注册.

   完成了parsernode我们需要将其注册进系统中
    /engines/player/config/core/pv_player_node_registry_populator.cpp在这个文件中
    RegisterAllNodes方法中
    添加

    nodeinfo.iInputTypes.clear();
    nodeinfo.iInputTypes.push_back(PVMF_MIME_AVIFF); //这个parsernode支持的格式,PVMF_MIMIE_AVIFF
    nodeinfo.iNodeUUID = KPVMFAviFFParserNodeUuid;   //定义的uuid
    nodeinfo.iOutputType.clear();
    nodeinfo.iOutputType.push_back(PVMF_MIME_FORMAT_UNKNOWN);//输出 unknown
    nodeinfo.iNodeCreateFunc = PVMFAVIFFParserNodeFactory::CreatePVMFAVIFFParserNode;
                                                   //创建parserNode的方法地址
    nodeinfo.iNodeReleaseFunc = PVMFAVIFFParserNodeFactory::DeletePVMFAVIFFParserNode;
                                                   //析购parserNode的方法地址
    aRegistry->RegisterNode(nodeinfo);             //注册到STL中
   
   
四.编译环境设置
 
   添加recognizer和parsernode目录后,每个目录下都需要有Android.mk文件,可以仿照其它的写,修改下即可.另外需要修改一些编译配置文件才能让系统去编译我们添加的文件.

/build_config/opencore_dynamic/Android_opencore_player.mk中
在20行左右添加   
    libpvaviffparsernode /
     libpvaviffrecognizer /
    
在50行左右添加
include   $(PV_TOP)/nodes/pvaviffparsernode/Android.mk
include   $(PV_TOP)/pvmi/recognizer/plugins/pvaviffrecognizer/Android.mk


五.启用PVLogger
   
   系统默认是没有启用pvlogger的,需要修改一些地方来启用这个log,log记录得很详细,作用很多,可以帮助查找错误或者去帮助阅读代码.
   
需要修改的地方.
   
/external/opencore/Config.mk

在第10行,

ifeq ($(ENABLE_PV_LOGGING),1)
  PV_CFLAGS += -DPVLOGGER_INST_LEVEL=5
endif

之前,添加一行

ENABLE_PV_LOGGING := 1   

/external/opencore/oscl/oscl/osclbase/src/pvlogger.h
在第138行左右添加
#define PVLOGGER_INST_LEVEL 5


完成这些之后,make sdk
启动模拟器,在sdcard中新建一个pvlogger.txt文件写入8即可
 echo 8 >/sdcard/ pvlogger.txt
或者可以过滤一些logger,可以这么写,比如只看PVPlayerEngine的log
 echo 8,PVPlayerEngine > /sdcard/pvlogger.txt



***************************************************************************
在 PVLoggerConfigFile可以看到一段话.

class PVLoggerConfigFile{
/* 
    To configure logging at runtime, a file pvlogger.txt must be located in the sdcard.
    The format for log level and logger tag in the file should be: "level,node".  Note that there should be no space between log level and logger tag.
    For example, pvlogger.txt can look like:
    1,PVPlayerEngine
    8,PVSocketNode
    Above example means log the message level PVLOGMSG_ALERT for PVPlayerEngine and PVLOGMSG_DEBUG for PVSocketNode.  See pvlogger.h for log level values.
*/
public:
    PVLoggerConfigFile():iLogFileRead(false)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值