译者注:这是原文地址。另外,关键的单词我保留原文】
作者:Hasselt University - Expertise Centre for Digital Media
1. 引言
这个是 EMIPLIB 的说明手册,EMIPLIB 即 EDM Media over IP libray。这个库开发于 Hasselt 大学 (http://www.uhasselt.be) 的 Expertise Centre for Digital Media (http://www.edm.uhasselt.be)研究所。正如这个库的名称所示、这个库的目标是让各种媒体更好地在网络传输,包括但不限于音视频。
1.1 许可证
【译者注:此处略】
1.2 联系方式
这个库的主页地址可以在这里找。EMIPLIB有个邮件列表。你可以发送邮件到 [email protected] 来订阅它。
2. 设计哲学
为了提供灵活的框架,这个库旨在提供大量的小 component。这些 component 各自负责特定任务,比如,音频采样、把音频写到声音文件或传输音频包。这些 component 可以分布在一个,允许不同 component 交换 message 的 chain 上。这样,在不同 component 之间创建 link,我们可以创建更多的复杂应用。
除了上述的核心库,EMIPLIB也着力提供一系列用于组合 component 的 wrapper类。这样方便了 EMIPLIB 用户:除了把不同 component 手动 link 在一起,我们还可以简单地创建一个VoIP的 session 对象。如果需要更灵活的用法,用户完全可以非常简单地手动组合不同 component。
3. 库的核心内容
在这章节我们详述库的核心内容。我们由浅入深阐述:
3.1 基础
如“设计哲学”这一节所述、核心库包含3部分:
- components
- component chains
- messages
MIPComponent 的子类即为 component,并且可以嵌入在 component chain(即 MIPComponentChain)。当 chain 被激活时,message 通过 component chain 的 link 而传递。这样的 message 继承自 MIPMessage 类。
假设我们有两个 component ,一个声卡输入 component ‘sndin‘ 和一个声卡输出 component ‘sndout‘。 我们把这些组件放在一个叫做‘loopchain‘的 chain 中并启动之:
loopchain.setChainStart(&sndin); loopchain.addConnection(&sndin,&sndout); loopchain.start();
这样就可以告知 chain,第一个 component 是 ‘sndin‘, 然后放置在一条 ‘sndin‘ 到 ‘sndout‘ 的link。chain 启动时,一个后台线程就会开始执行下面的事情:
- MIPSYSTEMMESSAGE_TYPE_WAITTIME 类型的 MIPSystemMessage 被创建,并从 chain 的起始端发送。在这个例子中, chain 的起始端是声卡输入 component,并且,在接收到一定数量的采样之前,这个component(比如麦克风)会一直等待着。
- 这条 chain 会通过已有的 link 传递数据。在这个案例中,从声卡输入到声卡输出 component 只有一条 link,因此,数据从声卡输入 component 采样并传到声卡输出 component。这个例子中的消息就会包含原始的音频采样。当声卡输出 component 收到消息的时候,扬声器就会播放声音。正如你可见,这个例子描述一个回声效果应用。
- 当所有的 message 被分发出去后,线程会继续循环,并且发送一个 MIPSYSTEMMESSAGE_TYPE_WAITTIME message 发送到第一个组件。当 MIPComponentChain::stop 方法被调用,或发生错误的时,后台进程将会结束。
所以,chain 的第一个 component 是 timer(定时器)。在这个案例中,是由声卡输入 component 自身的来完成的。
3.2 细节
首先我们描述一下这个 chain 是如何建立的,之后叙述 message 是如何传递的。最后叙述反馈机制。
3.2.1 建立 chain
Component 的连接可以由 MIPComponentChain::addConnection 方法来指定。这样可以简单地在连接列表中添加特定连接,并存储在chain内部。我们可以使用 MIPComponentChain::setChainStart 方法来设定起始组件。这个组件将会接受第一个消息:MIPSYSTEMMESSAGE_TYPE_WAITTIME 子类型的 MIPSystemMessage。
当调用一个 MIPComponentChain::start 函数的时候,之前建立的连接列表会根据正确的message分布队列来重排序。下图是一个以 timing component 为起始的 chain。假设link是按照以下数字的顺序建立的:
把 component 放置在不同层是算法重拍连接的结果。第一层是 MIPComponentChain::setChainStart 方法的结果。算法迭代所有连接,寻找以 计时器 component 为起始的连接。对于每个连接都会从旧连接列表中移除,并加入到新的重排序的连接列表中。连接的末端被加入到下一层。当第一层被处理后,算法就会迭代地处理下一层的 component。最后处理的结果如上图,重排后的连接顺序是:4, 2, 1, 6, 3, 5.
如果没有指定起始component或算法找到一些连接不可为,就会返回错误了。
3.2.2 分发 message
如果 chain 可以开始的话,后台进程就会大量产生,在不同的 component 之间分发 message。Chain 自身创建一个 MIPSYSTEMMESSAGE_TYPE_WAITTIME 子类型的 MIPSystemMessage 实例,然后发送自身到 chain 的第一个 component,使用 MIPComponent::push 方法。Chain 在调用期间会用 MIPComponent::lock 和 MIPComponent::unlock 方法 lock 住 component。
这个 message 告诉第一个 component 进入等待状态,直到其他的 message 可以被分发。当这个 component 的 push 方法返回时,后台线程开始迭代已排序的连接列表。对于列表的每个连接,头和尾 component 都被锁住了。通过 MIPComponent::pull 接口,Message 从连接的头 component 提取出来,并且通过 MIPComponent::push 接口输入连接的尾 component。关于 push 和 pull 方法的更多信息可参见 MIPComponent 文档。
如果 push 或 pull 方法返回 false,线程会调用 MIPComponentChain::onThreadExit 成员方法然后退出。产生错误的 component 的名称和错误描述会传递给这个函数的参数。
3.2.3 反馈 chain
Message 分发系统只支持单向地传递。对于许多原因,我们可能希望逆向传递信息。这可以通过使用 MIPComponentChain::addConnection 成员函数的第三个参数来指定是否反馈。
在开始后台进程前,反馈 chain 就已经基于连接列表的信息建立起来了。例如,假设上图中,连接1和连接3被标记为反馈连接,反馈 chain 就将由 RTP component, RTP 音频编码器, 声卡输入组成。
当后台线程运行时,分发 message 后,反馈 message 也会被分发。对于每个反馈chain,一个 MIPFeedback message 会被创建,并且通过 MIPComponent::processFeedback 成员函数在 chain 的每个反馈 component 来传递。通过实现这个函数,一个 component 可以获取或修改反馈信息。
注意,许多反馈 chain 可以同时存在,它们甚至可以在 chain 的末端拥有同样的 component。然而,一个 component 不能同时处理两个 chain 的反馈信息。
3.2.4 分支与合并
“建立 chain” 一节的例子已经展示了从指定的 component(比如定时器),你可以创建连接到许多不同的component(比如 message dumper 和声卡输入 component),这甚至可以让 subchain 合并一起。在下列例子中,用上了两个不同的音频文件 component,读取每个音频文件的数据:
每个文件的数据将重采样,并统一采样率。一个混音器把两个文件的音频合并成一个输出流,然后经过核实的编码方式输出至声卡。
现在我们假设,其中一个声音文件已经具有合适的采样率。这样把它的数据传递给重采样器就不必要了,你可能会认为下图才是好通路:
于是,连接顺序将会是这样的:
Timer -> Soundfile input 2 Timer -> Soundfile input 1 Soundfile input 2 -> Mixer Soundfile input 1 -> Sampling rate converter Mixer -> Sample encoder Sampling rate converter -> Mixer Sample encoder -> Soundcard output
这种情况下,当一路音轨还从重采样器到混音器路上,另一路从混音器到采样编码器了。好的情况下,在两路音轨到混音器会有时差;差的情况下较后的音轨可能会被丢弃掉。
所以一般情况下,当使用分支与合并,你可能更愿意为不同的分支分配相同层数的 component 来避免这个问题——你可以使用 MIPComponentAlias 类来辅助解决。
4. 教程
【译者注:几乎都是代码,就不翻译了】
5. 可用 component
查看 component 的最简单的方法是看看目录结构
警告:
原则上,一个指定的 component 实例可以被用于多个 chain 上,每个 chain 在开始使用的过程中会被锁住。然而,事实上只有少数几个 component 可以被多个链条使用。大部分的 component 会引发同步问题。
6. 可用 wrapper
目前有两个 wrapper 可被使用:
- MIPAudioSession: 这是一个简单的 VoIP session。它可以使用多种音频压缩编解码器。
- MIPVideoSession: 这是一个简单的 VoIP session。它可以使用 libavcodec 的 H.263+ 编解码器。
【译文】EMIPLIB 1.1.1 Doxygen