分享

HEVC编码 | Embracing Dreams | 第6页

 gljin_cn 2016-10-10

个人博客,如需转载,请注明来自DDingDreams的个人博客:http://www./wordpress/
解析函数入口
首先进入编码程序的入口文件,encmain.cpp,在下面的代码parseCfg函数处打断点,这是配置文件的解析函数。在c++中,可以直接抛出异常之后自己进行捕捉处理,try关键字中可以抛出异常,throw用于抛出异常,catch用于捕获异常并进行中断或者处理。

try   //encmain.cpp
  {
    if(!cTAppEncTop.parseCfg( argc, argv ))
    {
      cTAppEncTop.destroy();
      return 1;
    }
  }
  catch (po::ParseFailure& e)
  {
    cerr << "Error parsing option \""<< e.arg <<"\" with argument \""<< e.val <<"\"." << endl;
    return 1;
  }

抛出的异常可以是系 统的标准exception,也可以像本文中的ParseFailure是自定义的异常。

class TEncTop : public TEncCfg

TEncTop类中并不含有parseCfg函数,而是从TEncCfg类继承来的解析函数。
注释1:destroy
而对于TAppEncCfg类给出create和destroy,以及解析函数的注释如下:

  Void  create    ();                    ///< create option handling class
  Void  destroy   ();                         ///< destroy option handling class
  Bool  parseCfg  ( Int argc, Char* argv[] ); //< parse configuration file to fill member variables

那么对象cTAppEncTop调用的destory函数其实是类EncTop重载的destory函数。
注释2:cerr
还有提到的cerr是输出函数:
cout:写到标准输出的ostream对象,cout经过缓冲后输出,默认情况下是显示器。这是一个被缓冲的输出,是标准输出;
cerr:输出到标准错误的ostream对象,常用于程序错误信息,cerr不经过缓冲而直接输出,一般用于迅速输出出错信息,是标准错误,默认情况下被关联到标准输出流,但它不被缓冲,也就说错误消息可以直接发送到显示器,而无需等到缓冲区或者新的换行符时,才被显示。

解析函数
其参数为main函数的参数+1,即argc表示输入参数的个数,argv[]为指向输入参数字符串的指针。其中argv[0]指向输入的程序路径及名称。

Bool TAppEncCfg::parseCfg( Int argc, Char* argv[] )
{
  Bool do_help = false;
  
#if !H_MV
  string cfg_InputFile;
#endif
  string cfg_BitstreamFile;
#if !H_MV
  string cfg_ReconFile;
#endif
#if H_MV
  vector   cfg_dimensionLength; 
  string        cfg_profiles;
  string        cfg_levels; 
  string        cfg_tiers; 
#if H_3D 
  cfg_dimensionLength.push_back( 2  );  // depth
  cfg_dimensionLength.push_back( 32 );  // texture 
#else
  cfg_dimensionLength.push_back( 64 ); 
#endif 
#endif
  string cfg_dQPFile;
  string cfgColumnWidth;
  string cfgRowHeight;
  string cfg_ScalingListFile;
  string cfg_startOfCodedInterval;
  string cfg_codedPivotValue;
  string cfg_targetPivotValue;
  po::Options opts;
  opts.addOptions()
  ("help", do_help, false, "this help text")
  ("c", po::parseConfigFile, "configuration file name")
......
......

注释1:H_MV & HEVC_EXT
其中H_MV的宏定义如下:

#define H_MV          ( HEVC_EXT != 0)

而HEVC_EXT又是什么呢?
定义如下:

/* HEVC_EXT might be defined by compiler/makefile options.
   
   Linux makefiles support the following settings:   
   make             -> HEVC_EXT not defined    
   make HEVC_EXT=0  -> H_MV=0 H_3D=0   --> plain HM
   make HEVC_EXT=1  -> H_MV=1 H_3D=0   --> MV only 
   make HEVC_EXT=2  -> H_MV=1 H_3D=1   --> full 3D 
*/

#ifndef HEVC_EXT
#define HEVC_EXT                    2

也就是说HEVC_EXT=0的话,则为纯粹的HEVC编码,若HEVC_EXT=1,则为多视点编码,而我用的是多视点+深度(MVD)数据格式,那么应该用3D模式,所以HEVC_EXT=2;
注释2:vector
vector是C++标准模板库中的部分内容,它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库。vector之所以被认为是一个容器,是因为它能够像容器一样存放各种类型的对象,简单地说,vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。
要想使用vector需要包含头文件:

#include <vector>

还需要using namespace std;使用标准库的命名空间,下面会对命名空间有介绍。
一般用法如:

vector c   //创建一个空的vector。
 
vector  c1(c2)   //复制一个vector。
 
vector  c(n)   //创建一个vector,含有n个数据,数据均已缺省构造产生。
 
vector  c(n, elem)   //创建一个含有n个elem拷贝的vector。
 
vector  c(beg,end)   //创建一个以[beg;end)区间的vector。
 
c.~ vector ()   //销毁所有数据,释放内存。 
vector vInts;
vector vInts(500);  //这个声明是指vInts里面可以放500个int类型的数据
vector vInts(500,int(0)); //指vInts里面可以放500个int类型的数据,并且初始化都为0;
vector vIntsCopy(vInts);  //创建一个拷贝;
vInts.push_back(int(5));    //向vInts添加数据int类型5;
int nSize = vInts.empty() ? -1 : static_cast(vInts.size());   //求vInts的size,若为空,则赋值为-1
//vector访问数据两种用法:
//vector::at();像数组一样会进行边界检查
//vector::operator[];//不进行边界检查
//用例如下:
vector v;
v.reserve(10);          //为v分配了10个int型内存空间
for(int i=0; i<7; i++)
    v.push_back(i);     //v增加了7条数据,下标从0~6
try
{
 int iVal1 = v[7];  // not bounds checked - will not throw
 int iVal2 = v.at(7); // bounds checked - will throw if out of range
}
catch(const exception& e)
{
 cout << e.what();
}
////Vector成员函数
c.assign(beg,end)   //将[beg; end)区间中的数据赋值给c。
c.assign(n,elem)   //将n个elem的拷贝赋值给c。
c.at(idx)   //传回索引idx所指的数据,如果idx越界,抛出out_of_range。
c.back()   //传回最后一个数据,不检查这个数据是否存在。
c.begin()   //传回迭代器重的第一个数据。
c.capacity()   //返回容器中数据个数。
c.clear()   //移除容器中所有数据。
c.empty()   //判断容器是否为空。
c.end()   //指向迭代器中的最后一个数据地址。
c.erase(pos)   //删除pos位置的数据,传回下一个数据的位置。
c.erase(beg,end)   //删除[beg,end)区间的数据,传回下一个数据的位置。
c.front()   //传回第一个数据。
get_allocator   //使用构造函数返回一个拷贝。
c.insert(pos,elem)    //在pos位置插入一个elem拷贝,传回新数据位置。
c.insert(pos,n,elem)   //在pos位置插入n个elem数据。无返回值。
c.insert(pos,beg,end)   //在pos位置插入在[beg,end)区间的数据。无返回值。
c.max_size()   //返回容器中最大数据的数量。
c.pop_back()   //删除最后一个数据。
c.push_back(elem)   //在尾部加入一个数据。
c.rbegin()   //传回一个逆向队列的第一个数据。
c.rend()   //传回一个逆向队列的最后一个数据的下一个位置。
c.resize(num)   //重新指定队列的长度。
c.reserve()   //保留适当的容量。
c.size()   //返回容器中实际数据的个数。
c1.swap(c2)    //将c1和c2元素互换。
swap(c1,c2)   //将c1和c2元素互换。

注释3:namespace

using namespace std;
namespace po = df::program_options_lite;

这段代码来自3D-HEVC,using namespace std;表示使用C++标准库命名空间。

使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突。在C++中,变量、函数和类都是大量存在的。如果没有命名空间,这些变量、函数、类的名称将都存在于全局命名空间中,会导致很多冲突。比如,如果我们在自己的程序中定义了一个函数toupper(),这将重写标准库中的toupper()函 数,这是因为这两个函数都是位于全局命名空间中的。命名冲突还会发生在一个程序中使用两个或者更多的第三方库的情况中。此时,很有可能,其中一个库中的名 称和另外一个库中的名称是相同的,这样就冲突了。这种情况会经常发生在类的名称上。比如,我们在自己的程序中定义了一个Stack类,而我们程序中使用的某个库中也可能定义了一个同名的类,此时名称就冲突了。

Namespace 关键字的出现就是针对这种问题的。由于这种机制对于声明于其中的名称都进行了本地化,就使得相同的名称可以在不同的上下文中使用,而不会引起名称的冲突。或许命名空间最大的受益者就是C++中的标准库了。在命名空间出现之前,整个C++库都是定义在全局命名空间中的(这当然也是唯一的命名空间)。引入命名空间后,C++库就被定义到自己的名称空间中了,称之为std。这样就减少了名称冲突的可能性。我们也可以在自己的程序中创建自己的命名空间,这样可以对我们认为可能导致冲突的名称进行本地化。这点在我们创建类或者是函数库的时候是特别重要的。
在命名空间中声明的标识符是可以被直接引用的,不需要任何的命名空间的修饰符。

namespace CounterNameSpace
{
  int upperbound;  
  int lowerbound;
  class couter
  {
  }
}

然而,既然命名空间定义了一个范围,那么我们在命名空间之外就需要使用范围解析运算符来引用命名空间中的对象。例如,在命名空间CounterNameSpace定义的范围之外给upperbound赋值为10,就必须这样写:

CounterNameSpace::upperbound = 10;

或者在CounterNameSpace定义的范围之外想要声明一个counter类的对象就必须这样写:

CounterNameSpace::counter obj;

一般来讲,在命名空间之外想要访问命名空间内部的成员需要在成员前面加上命名空间和范围解析运算符。
但是,一旦声明了counter类型的对象,就没有必须在对该对象的任何成员使用这种修饰符了。

//访问命名空间或者成员:
using namespace 命名空间名称;
using 命名空间名称::成员;

第一种形式中的命名空间名称就是我们要访问的命名空间。该命名空间中的所有成员都会被引入到当前范围中。也就是说,他们都变成当前命名空间的一部分了,使用的时候不再需要使用范围限定符了。
第二种形式只是让指定的命名空间中的指定成员在当前范围中变为可见。

using CounterNameSpace::lowerbound; //只有lowerbound当前是可见的
lowerbound = 10; //这样写是合法的,因为lowerbound成员当前是可见的
using CounterNameSpace; //所有CounterNameSpace空间的成员当前都是可见的
upperbound = 100; //这样写是合法的,因为所有的CounterNameSpace成员目前都是可见的

相同的空间名称是可以被多次声明的,这种声明向相互补充的。这就使得命名空间可以被分割到几个文件中甚至是同一个文件的不同地方中。
当我们用using引入一个命名空间的时候,如果之前有引用过别的命名空间(或者同一个命名空间),则不会覆盖掉对之前的引入,而是对之前引入内容的补充。也就是说,到最后,上述程序中的std和CounterNameSpace这两个命名空间都变成全局空间了。
没有名称的命名空间
有一种特殊的命名空间,叫做未命名的命名空间。这种没有名称的命名空间使得我们可以创建在一个文件范围里可用的命名空间。其一般形式如下:
namespace
{
//声明
}
我 们可以使用这种没有名称的命名空间创建只有在声明他的文件中才可见的标识符。也即是说,只有在声明这个命名空间的文件中,它的成员才是可见的,它的成员才 是可以被直接使用的,不需要命名空间名称来修饰。对于其他文件,该命名空间是不可见的。
把全局名称的作用域限制在声明他的文件的一 种方式就是把它声明为静态的。尽管C++是支持静态全局声明的,但是更好的方式就是使用这里的未命名的命名空间。
标准C++把自己的整个库定义在std命名空间中。
using namespace std;
这样写是为了把std命名空间的成员都引入到当前的命名空间中,以便我们可以直接使用其中的函数和类,而不用每次都写上std::。
如果我们的程序中只是少量地使用了std命名空间中的成员,或者是引入std命名空间可能导致命名空间的冲突的话,我们就没有必要使用using namespace std;了。然而,如果在程序中我们要多次使用std命名空间的成员,则采用using namespace std;的方式把std命名空间的成员都引入到当前命名空间中会显得方便很多,而不用每次都单独在使用的时候显示指定。

struct Options
如上面的parseCfg配置文件解析函数,从下面的一小段代码之后就开始为变量赋值了。

po::Options opts;
  opts.addOptions()
  ("help", do_help, false, "this help text")
  ("c", po::parseConfigFile, "configuration file name")
......
......

“po”其实就是命名空间,如下的代码中可见:

namespace df
{
  namespace program_options_lite
  {
    struct Options;//Options结构体其实就是在df嵌套program_options_lite两层命名空间中的。
    ......
......
using namespace std;
namespace po = df::program_options_lite;//那么其实,po就是命名空间df嵌套下的program_options_lite,那么通过po就可以直接调用Options结构体了。
po::Options opts;  //声明了一个Options结构体对象opts。

注释1:addOptions函数
由上面代码可见,其带有(“help”, do_help, false, “this help text”)这样的四个参数。

//addOptions()函数:
OptionSpecific Options::addOptions()  //上述的addOpitons函数,只是返回了OptionSpecific指针。
    {
      return OptionSpecific(*this);
    }

首先来看一下po命名空间下Options结构,其中包括addOptions函数和Names结构体。

    class OptionSpecific;
    struct Options
    {
      ~Options();
      
      OptionSpecific addOptions();   //这个addOptions函数返回的类型为OptionSpecific;
      
      struct Names
      {
        Names() : opt(0) {};//冒号后面其实是对成员opt的初始化
        ~Names()
        {
          if (opt)
            delete opt;
        }
        std::list opt_long;//这两个都为string数据类型的链表
        std::list opt_short;
        OptionBase* opt;
      };

      void addOption(OptionBase *opt);
      
      typedef std::list<Names*> NamesPtrList;//这是Names数据机构的链表宏定义
      NamesPtrList opt_list;   //声明一个Names结构链表对象
      
      typedef std::map<std::string, NamesPtrList> NamesMap;//map以string数据类型为key值,上面Names数据机构的链表为value。
      NamesMap opt_long_map; //这里声明了两个map对象
      NamesMap opt_short_map;
    };

函数调用addOptions方法,实质函数是通过返回下面的OptionSpecific类指针,来调用了options结构体中的addOption(OptionBase *opt);方法,先来看OptionSpecific类:

    /* Class with templated overloaded operator(), for use by Options::addOptions() */
    class OptionSpecific
    {
    public:
      OptionSpecific(Options& parent_) : parent(parent_) {}//OptionSpecific的含参数构造函数,其中其参数通过冒号后面的常量赋值方法来赋值,即参数传入的parent_赋给了OptionSpecific的参数parent,此段代码的最后可见,parent是OptionSpecific私有变量,类型为Options结构。
      
      /**
       * Add option described by name to the parent Options list,
       *   with storage for the option's value
       *   with default_val as the default value
       *   with desc as an optional help description
       */
//下面这段其实是重载运算符(),这()内的四个参数传给了parent.addOption,根据()内的参数类型不同,采用了模板数据类型,以及重载了三个不同的()运算方法。
      template
      OptionSpecific&
      operator()(const std::string& name, T& storage, T default_val, const std::string& desc = "")
      {
        parent.addOption(new Option(name, storage, default_val, desc));
        return *this;
      }
      
#if H_MV
      template
      OptionSpecific&
        operator()(const std::string& name, std::vector& storage, T default_val, unsigned uiMaxNum, const std::string& desc = "" )
      {
        std::string cNameBuffer;
        std::string cDescBuffer;

        storage.resize(uiMaxNum);
        for ( unsigned int uiK = 0; uiK < uiMaxNum; uiK++ )
        {
          cNameBuffer       .resize( name.size() + 10 );
          cDescBuffer.resize( desc.size() + 10 );

          Bool duplicate = (uiK != 0); 
          // isn't there are sprintf function for string??
          sprintf((char*) cNameBuffer.c_str()       ,name.c_str(),uiK,uiK);

          if ( !duplicate )
          {          
            sprintf((char*) cDescBuffer.c_str(),desc.c_str(),uiK,uiK);
          }

          cNameBuffer.resize( std::strlen(cNameBuffer.c_str()) );  
          cDescBuffer.resize( std::strlen(cDescBuffer.c_str()) ); 
          

          parent.addOption(new Option( cNameBuffer, (storage[uiK]), default_val, cDescBuffer, duplicate ));
        }

        return *this;
      }
#endif
      /**
       * Add option described by name to the parent Options list,
       *   with desc as an optional help description
       * instead of storing the value somewhere, a function of type
       * OptionFunc::Func is called.  It is upto this function to correctly
       * handle evaluating the option's value.
       */
      OptionSpecific&
      operator()(const std::string& name, OptionFunc::Func *func, const std::string& desc = "")
      {
        parent.addOption(new OptionFunc(name, parent, func, desc));
        return *this;
      }
    private:
      Options& parent;
    };
    
  }; /* namespace: program_options_lite */
}; /* namespace: df */

我们来看一下addOptions具体做了些什么工作,以参数(“help”, do_help, false, “this help text”)为例:
首先程序进入

template
      OptionSpecific&
      operator()(const std::string& name, T& storage, T default_val, const std::string& desc = "")
      {
        parent.addOption(new Option(name, storage, default_val, desc));
        return *this;
      }
//name传入了“help”,storage传入了"Bool do_help = false;"这个BOOL变量,default_val=false,desc传入的是"this help text";

利用小括号运算符重载,将()内的四个参数传入,接着调用addOptions方法调用OptionBase和OptionSpecifc的构造函数。将四个参数存储在以Names结构体为数据类型的双向链表opt_list中。

 void Options::addOption(OptionBase *opt)//我们看到上面调用addOption的时候都是传入的option对象或者OptionFunc对象,而这里传入的对象为OptionBase.查看option和OptionFunc的定义可知,他们都继承自OptionBase。

//当四个参数传进来,先进入OptionBase的构造函数,然后再进行option或者OptionFunc的构造函数。
    {
//opt对象的参数opt_string为“help”,opt_desc为“this help text”;
      Names* names = new Names();
//Names包含一个OptionBase类,即string类型的name和desc,和一个长链表,一个短链表。
      names->opt = opt;//将传入的OptionBase参数赋值给names的成员opt。
      string& opt_string = opt->opt_string;//“help”
,取第一个参数,即OptionBase的name参数      
      size_t opt_start = 0;
      for (size_t opt_end = 0; opt_end != string::npos;)//
      {
        opt_end = opt_string.find_first_of(',', opt_start);//找opt_string字符串中第一个“,”的位置,如果没找到“,”,返回一个特别的标志c++中用npos表示
        bool force_short = 0;
        if (opt_string[opt_start] == '-')
//若起始的字符为“-”,则字符的起始位置+1,force_short=1,是用来强制字符“-”开始的字符串长度只为“1”:
        {
          opt_start++;
          force_short = 1;
        }
        string opt_name = opt_string.substr(opt_start, opt_end - opt_start);
//取opt_string子串
        if (force_short || opt_name.size() == 1)
//若是短字符串,存入names中的短链表,并在Options的短map中存入。
        {
          names->opt_short.push_back(opt_name);
          opt_short_map[opt_name].push_back(names);
        }
        else
//若是长字符串,存入names中的长链表,并在Options的长map中存入       
        {
          names->opt_long.push_back(opt_name);
          opt_long_map[opt_name].push_back(names);
        }
        opt_start += opt_end + 1;
//取完第一个逗号前面的字符串,opt_start向后挪去取第二个字符串
      }
      opt_list.push_back(names);
//将字符串构建的names结构存入opt_list,这是以Names结构为数据类型的链表。
    }


 void Options::addOption(OptionBase *opt)//这个方法只是解析了name字符串,即传入的第一个参数。
   struct OptionBase//其只有三个参数,opt_string,opt_desc,opt_duplicate
    {
#if H_MV      
      OptionBase(const std::string& name, const std::string& desc, bool duplicate = false)
        : opt_string(name), opt_desc(desc), opt_duplicate(duplicate)//这里是OptionBase的三参数构造函数,同时给三个参数初始化;
#else
      OptionBase(const std::string& name, const std::string& desc)
      : opt_string(name), opt_desc(desc)//两参数构造函数
#endif
      {};
      
      virtual ~OptionBase() {}
      
      /* parse argument arg, to obtain a value for the option */
      virtual void parse(const std::string& arg) = 0;
      /* set the argument to the default value */
      virtual void setDefault() = 0;
      
      std::string opt_string;
      std::string opt_desc;
#if H_MV
      bool        opt_duplicate; 
#endif
    };
    /** Type specific option storage */
    template
    struct Option : public OptionBase//OptionBase的子类
    {
#if H_MV
      Option(const std::string& name, T& storage, T default_val, const std::string& desc, bool duplicate = false)
        : OptionBase(name, desc, duplicate), opt_storage(storage), opt_default_val(default_val)
//其构造函数先调用父类OptionBase的构造函数OptionBase(name, desc, duplicate), 然后再调用子类的构造函数,同时给自己的两个变量赋值。Option的构造函数的参数顺序,第一和最后两个给父类赋值。第二和第三个参数给自己的变量赋值。
#else
      Option(const std::string& name, T& storage, T default_val, const std::string& desc)
      : OptionBase(name, desc), opt_storage(storage), opt_default_val(default_val)
#endif
      {}
      
      void parse(const std::string& arg);//解析函数
      
      void setDefault()
//设置默认值。
      {
        opt_storage = opt_default_val;
      }
      
      T& opt_storage;
      T opt_default_val;
    };

也就是说,当这段addOption运行后,Options结构的对象中,opt_list存储情况如下:
opt_list链表中第0个数据即为Names结构,opt_list[0]中,opt_long[1]为”help”,opt_short为空,opt的对象,opt_string为”help”,opt_desc为”this help text”,opt_duplicate为false。同时Names的对象还存储了opt_storage和opt_default_val的内容。
相应的opt_long_map链表也存储了这些内容。
下图是执行两次addOptions方法后,opts对象的数据结构:
执行两次addOptions后,opts输出的数据结构

小括号运算符重载,五个参数

//addOptions中一句代码如下,五个参数的小括号重载调用:
("InputFile_%d,i_%d",       m_pchInputFileList,       (char *) 0 , MAX_NUM_LAYER_IDS , "original Yuv input file name %d")

其调用的函数是如下:

#if H_MV//多视点序列输入,传入MAX_NUM_LAYER_IDS=63,也就是可以输入63个序列
      template
      OptionSpecific&
        operator()(const std::string& name, std::vector& storage, T default_val, unsigned uiMaxNum, const std::string& desc = "" )
      {
        std::string cNameBuffer;
        std::string cDescBuffer;//定义两个string类型的buffer

        storage.resize(uiMaxNum);//设置存储空间的大小
        for ( unsigned int uiK = 0; uiK < uiMaxNum; uiK++ )
        {
          cNameBuffer       .resize( name.size() + 10 );
          cDescBuffer.resize( desc.size() + 10 );//设置两个string变量的buffer的空间

          Bool duplicate = (uiK != 0); //duplicate=false的时候来标志第一个输入序列,此时要输入desc信息
          // isn't there are sprintf function for string??
          sprintf((char*) cNameBuffer.c_str()       ,name.c_str(),uiK,uiK);//name.c_str()格式化数据后存入cNameBuffer.c_str() 

          if ( !duplicate )
          {          
            sprintf((char*) cDescBuffer.c_str(),desc.c_str(),uiK,uiK);//第一个序列时调用的,将desc.c_str()格式化存入cDescBuffer.c_str().
          }

          cNameBuffer.resize( std::strlen(cNameBuffer.c_str()) );  //确定buffer的内容之后再重设空间大小。
          cDescBuffer.resize( std::strlen(cDescBuffer.c_str()) ); 
          

          parent.addOption(new Option( cNameBuffer, (storage[uiK]), default_val, cDescBuffer, duplicate ));//根据传入的uiMaxNum这个上限值生成对应的name后,Options对象格式化。所以,根据传入的uiMaxNum=63,那么inputfile_%d,i_%d,从0-62,生成63个string,调用63次addOption函数。
        }

        return *this;
      }
#endif
       呵呵

老大的呵呵一笑很倾城啊!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多