From:http://yate.null.ro/pmwiki/index.php?n=Main.CppTutorial3
Yate的编解码模块不处理任何消息,而是通过API方式调用。其他模块通常两者机制都使用。实现一个编解码模块要求需要对编解码库和算法有相当深的理解与研究。在Yate中实现它是相当简单的。希望这个教程对你有帮助。 在这个教程中我们将使用到以下的ate API
1. class TranslatorFactory 2. class DataTranslator 3. class DataBlock 4. class FormatRepository 5. struct FormatInfo 6. struct TranslatorCaps
假定你已经看过我的第一个教程。这里我们iLBC编解码库为例子。我们使用iLBC 20ms的编解码器。本教程的编解码库和项目中的代码并不是100%的相同。所以如果你想使用iLBC,必须从CVS或Release版本获取最新代码,而不是使用本例代码运用于实际项目之中。 第一步:创建一个插件(Plugin) 我们需从Plugin派生出一个类。虽然普通模块一般都是从类Module派生,但是由于我们无需处理消息,因此我们只需要类Plugin派生,Plugin的核心功能已经能满足咱们的需求。我们同样需要从TranslatorFactory派生出一个类,使得我们的模块具有编解码的能力。你这样声明你的类:
代码语言:javascript复制class iLBCPlugin : public Plugin, public TranslatorFactory { public: iLBCPlugin(); ~iLBCPlugin(); virtual void initialize() { } };
注意,从Plugin类继承的initialize方法。我们不需要为ILBC 20ms编解码库做任何事情。 第二步:申明一个TranslatorCaps结构体变量 模块需通过某种途径来指定说明他所支持的编码和解码的数据格式。编解码器将一种格式数据转化成另外一种格式数据使用的编码/解码。编解码器通过使用slin(Signed Linear Audio)格式,编码成指定的数据格式(我们的例子是 iLBC 20ms),解码过程亦是将iLBC 20ms转换成slin数据。但是如果你的编解码器能够编码或解码除了slin之外的格式,你可以在TranslatorCaps指定编解码的数据格式,并通知Yate。在我们的例子中我们只能编码slin和解码iLBC 20ms。总而言之,TranslatorCaps是你的编解码器数据类型转换的表格。TranslatorCaps变量声明为全局变量。
代码语言:javascript复制static TranslatorCaps caps[] = { { 0, 0 }, { 0, 0 }, { 0, 0 } };
关于这点后面详述 第三步:定义编解码器功能 在构造函数中,我们将用到类FormatRepository定义编解码器的功能,FormatRepository仅有两个静态的public成员变量。FormatInfo是一个含有编解码信息(例名称、比特率、帧数、通道数等等)的结构体。我们需要使用FormatRepository来构建FormatInfo信息。我们在Plugin插件的构造函数中做这事。
代码语言:javascript复制iLBCPlugin::iLBCPlugin() { Output("Loaded module iLBC - based on libiLBC"); const FormatInfo* f = FormatRepository::addFormat("ilbc",1900,38); caps[0].src = caps[1].dest = f; caps[0].dest = caps[1].src = FormatRepository::getFormat("slin"); }
FormatRepository::addFormat函数有六个参数,后三个有缺省值,Kdoc文档的注释如下 1.name 标准无空白小写的格式名称 2 drate 每秒数据样本大小 Data rate in octets/xsecond, 0 for variable 3 fsize 每帧大小(字节) Frame size in octets/frame, 0 for non-framed formats Yate 3.0版本参数 *2 fsize 每帧大小 Data frame size in octets/frame, 0 for non-framed formats *3 ftime 每帧时间间隔单位为ms (实际代码确实微妙) Data frame duration in microseconds, 0 for variable 4 type 格式类型 audio video text 5 srate 采样率 音频 样本数/秒(audio) 1e-6 帧数/秒(video) 0位置 6 nchan 通道数量 缺省为1
最后三个参数的缺省值 1. "audio" 2. 8000 3. 1 你可以根据你的需要和编解码器使用而不同参数。 在addFormat中参数name值为ilbc,第二个参数字节/秒(octets/second)可由帧大小乘以每秒帧数而得。在编解码器中我们是帧/ms,因此你须将转换成帧/秒。例ilbc 20ms我们每秒帧数为1000/20=50,所以我们得到的值应该为
代码语言:javascript复制 1900 = 38*(1000/20)
帧大小仅仅是编解码帧大小, iLBC 20ms每帧大小为38字节,也就是38 octects,GSM每帧大小33字节。
所以,我们的格式转换表的应该是这样的:
IN OUT
slin ilbc (编码)
ilbc slin (解码)
第四步 提供所需功能
我们需要向YATE提供我们的编解码器功能,因此我们需实现这个方法
代码语言:javascript复制const TranslatorCaps* iLBCPlugin::getCapabilities() const { return caps; }
第五步 编解码器类 所有编解码器相关功能都封转到编解码类中。我们需要再次从YATE的某个类中派生出一个类,这个类为DataTranslator。在运行中我们可以拥有多个实例。
代码语言:javascript复制class iLBCCodec : public DataTranslator { public: iLBCCodec(const char* sFormat, const char* dFormat, bool encoding); ~iLBCCodec(); virtual void Consume(const DataBlock& data, unsigned long timeDelta); private: bool m_encoding; iLBC_Enc_Inst_t m_enc; iLBC_Dec_Inst_t m_dec; DataBlock m_data; };
m_enc和m_dec分包负责编码和解码iLBC指定的数据。这些类型由iLBC类库定义,在我们的代码中没有定义。Consume是我们实际执行编码和解码的方法。现在我们来写构造函数 第六步 编解码器的构造函数
代码语言:javascript复制iLBCCodec::iLBCCodec(const char* sFormat, const char* dFormat, bool encoding) : DataTranslator(sFormat,dFormat), m_encoding(encoding) { Debug(DebugAll,"iLBCCodec::iLBCCodec(/"%s/",/"%s/",%scoding) [%p]", sFormat,dFormat, m_encoding ? "en" : "de",this); count ; memset(&m_enc,0,sizeof(m_enc)); memset(&m_dec,0,sizeof(m_dec)); initEncode(&m_enc,20); initDecode(&m_dec,20,0); }
第七步 编解码器数据和实例的初始化 至今为止我们仅仅向Yate提供了我们编解码器的相关信息。有趣的是,Yate在需要做数据转化时,还需要创建一个编解码器。因此一个编解码器应该是可由任一通道根据编解码的需求来创建的。
代码语言:javascript复制DataTranslator* iLBCPlugin::create(const DataFormat& sFormat, const DataFormat& dFormat) { if (sFormat == "slin" && dFormat == "ilbc") return new iLBCCodec(sFormat,dFormat,true); else if (sFormat == "ilbc" && dFormat == "slin") return new iLBCCodec(sFormat,dFormat,false); else return 0; }
这里,我们需要检查/查找IN和OUT的数据格式。根据我们选择的编解码器。这里我们用slin数据转换成ilbc类型数据,称为编码,反之为解码。