NAO 机器人:将 ALSoundExtractor 移植到 qi 框架
Posted
技术标签:
【中文标题】NAO 机器人:将 ALSoundExtractor 移植到 qi 框架【英文标题】:NAO robot: porting ALSoundExtractor to qi framwork 【发布时间】:2020-08-03 15:11:46 【问题描述】:从 NAOqi 移植到 qi 框架时,我取得了部分成功。但是,我仍然有以下问题。 我不知道如何在qi框架中使用ALSoundExtractor实现声音处理。
在老脑奇中,有一个例子:
http://doc.aldebaran.com/2-8/dev/cpp/examples/audio/soundprocessing/soundprocessing.html
创建类的位置:
类 ALSoundProcessing:公共 ALSoundExtractor
然后声明一个覆盖虚函数的函数,用于声音处理:
无效进程(...)
我现在不知道的是:
-
如何在qi框架中创建一个继承自旧式类ALSoundExtractor的类?
如何声明重写虚函数的函数 - 从技术上讲,基类函数 process() 需要旧 AL:: 约定中的变量。
或者,还有其他方法可以读取音频通道吗?
【问题讨论】:
【参考方案1】:我从未使用过 ALExtractor 或 ALSoundExtractor,但这是我所知道的。
如何在qi框架中创建一个继承自旧式类ALSoundExtractor的类?
在老脑奇中,一个“ALExtractor”
可以从主进程(使用 autoload.ini)或另一个进程(称为远程模式)中运行。 qi 框架只支持远程模式。 可以从 ALExtractor 或 ALAudioExtractor 继承以分解出一些代码。这些类尚未移植到 qi 框架。所以如果你不想继续使用 libnaoqi,你应该想办法不用它们。好消息:从未真正需要从他们那里继承。您会发现自己处于与以下问题类似的位置,其中提取器是在 python 中实现的(因此不能从 C++ 类继承,也不能从 autoload.ini 加载到主进程中)。 NAO robot remote audio problems
如何声明重写虚函数的函数 - 从技术上讲,基类函数 process() 需要旧 AL:: 约定中的变量。
每当您使用“老脑奇”时,您实际上是在使用 qi 框架之上的兼容层。 因此,每当您使用“老脑奇”时,您就已经在使用 qi 框架。 libqi 的 qi::AnyValue 在运行时是可扩展的,libnaoqi 对其进行了扩展以使其知道如何处理 ALValue:如何将其转换为原始类型(浮点数、整数列表、字符串、缓冲区等)。
所以每当旧的 ALSoundExtractor 接收到一个 AL::ALvalue,它实际上是一个 qi::AnyValue,它在调用 process() 方法之前已被转换为一个 ALValue。 如果不链接 libnaoqi,则无法将值用作 ALValue,但可以将其用作 qi::AnyValue,甚至可以将其用作原始类型。
原来的原型是(cfr doxygen http://doc.aldebaran.com/2-8/ref/libalaudio/classAL_1_1ALSoundExtractor.html)是
void ALSoundExtractor::process (const int &nbOfChannels, const int &nbrOfSamplesByChannel, const AL_SOUND_FORMAT *buffer, const ALValue ×tamp);
由于时间戳可能是两个整数的列表,我会尝试这样的事情
void TmpSoundExtractor::process (const int &nbOfChannels, const int &nbrOfSamplesByChannel, qi::AnyValue buffer, const std::vector<int> ×tamp);
我不确定如何处理缓冲区变量,但首先让其余的工作。
【讨论】:
【参考方案2】:要使用这个 API,你必须写一个 Qi Service 来宣传这个方法:
void processRemote(
int nbOfChannels,
int nbrOfSamplesByChannel,
const qi::AnyValue& timestamp,
const qi::AnyValue& buffer)
std::pair<char*, size_t> charBuffer = value.unwrap().asRaw();
const signed short* data = (const signed short*)charBuffer.first;
// process the data like in the example.
请注意,使用 Qi 框架:
AL::ALValue
替换为 qi::AnyValue
。
获取二进制数据(aka“原始”)略有不同。
AL_SOUND_FORMAT
替换为 signed short*
。
ALSoundExtractor
不可用,需要我们自己转换成const AL_SOUND_FORMAT*
。
假设您的服务注册为"MySoundExtractor"
,您必须告诉ALAudioDevice
开始声音提取并将数据发送到您的服务,如下所示:
auto audio = session->service("ALAudioDevice").value();
int nNbrChannelFlag = 0; // ALL_Channels: 0, AL::LEFTCHANNEL: 1, AL::RIGHTCHANNEL: 2; AL::FRONTCHANNEL: 3 or AL::REARCHANNEL: 4.
int nDeinterleave = 0;
int nSampleRate = 48000;
audio->setClientPreferences("MySoundExtractor", nSampleRate, nNbrChannelFlag, nDeinterleave);
audio->subscribe("MySoundExtractor");
请注意,我没有测试此代码,所以请告诉我可能出了什么问题。
【讨论】:
我的代码暂时编译好了。但是,我进行了以下更改: qi::AnyObject audio = _session->service("ALAudioDevice"); audio.call<:anyvalue>("setClientPreferences", "MySoundExtractor", ....);通过您的通话 audio->setClentPreference(...) 我收到缺少成员的错误消息。我还没有测试代码。【参考方案3】:以下是最终对我有用并结束主题的内容。
// **************** service.h ****************
typedef signed short AL_SOUND_FORMAT; // copy from alaudio/alsoundextractor.h
class SoundProcessing
public:
SoundProcessing(qi::SessionPtr session);
void init(void); // a replacement for a function automatically called in NAOqi 2.1.4
virtual ~SoundProcessing(void);
void processRemote(const int& nbOfChannels, const int& nbrOfSamplesByChannel, const qi::AnyValue& timestamp, const qi::AnyValue& buffer);
private:
qi::SessionPtr _session;
qi::AnyObject audio;
;
// **************** service.cpp ****************
SoundProcessing::SoundProcessing(qi::SessionPtr session) : _session(session)
_session->waitForService("ALAudioDevice");
audio = _session->service("ALAudioDevice");
// constructor
QI_REGISTER_MT_OBJECT(SoundProcessing, init, processRemote);
SoundProcessing::~SoundProcessing(void)
audio.call<qi::AnyValue>("unsubscribe", "SoundProcessing");
// destructor
void SoundProcessing::init(void)
audio.call<qi::AnyValue>("setClientPreferences",
"SoundProcessing",
_FREQ48K, // 48000 Hz requested
0,
1
);
audio.call<qi::AnyValue>("subscribe", "SoundProcessing");
// SoundProcessing::init
void SoundProcessing::processRemote(const int& nbOfChannels,const int& nbrOfSamplesByChannel, const qi::AnyValue& timestamp, const qi::AnyValue& qibuffer)
std::pair<char*, size_t> charBuffer = qibuffer.unwrap().asRaw();
AL_SOUND_FORMAT *buffer = (AL_SOUND_FORMAT *)charBuffer.first;
(...)
// SoundProcessing::process
// **************** main.cpp ****************
int main(int argc, char* argv[])
qi::ApplicationSession app(argc, argv);
app.start();
qi::SessionPtr session = app.session();
session->registerService("SoundProcessing", qi::AnyObject(boost::make_shared<SoundProcessing>(session)));
qi::AnyObject sp = session->service("SoundProcessing");
sp.call<qi::AnyValue>("init");
app.run();
return 0;
【讨论】:
感谢和祝贺。请注意,服务被实例化为boost::shared_ptr
,复制时共享所有权。它被包裹在一个qi::AnyObject
中,该qi::AnyObject
转发与客户共享的所有权。因此,ALAudioDevice
使您的服务对象保持活动状态并非不可能,即使在取消注册之后也是如此。因此,您不应依赖析构函数来执行来自ALAudioDevice
的取消订阅。相反,请考虑为取消订阅公开不同的功能。
我会考虑的。事实上,模块无限期地运行并且析构函数永远不会被调用。目前,停止服务的唯一方法是杀死主进程。在 v. 2.1.4 中,当编译为库模块时,nao stop 命令正在调用所有已安装用户模块的析构函数。我不知道是否可以使用二进制模块。
我一直在思考这个问题,但我不明白这一点。当析构函数的主体被执行时,所有的私有变量仍然存在,尤其是有问题的服务对象。这意味着调用“取消订阅”是合法的。随后,服务对象和对象本身被删除。
调用unsubscribe
是合法的,但在你调用它之前,ALAudioDevice
将保留对你的服务的引用,这将使你的对象保持活动状态。因此在调用unsubscribe
之前不会调用析构函数。因此,除非您在其他地方调用unsubscribe
,否则您的服务将不会被取消订阅,直到会话关闭(或进程终止)。【参考方案4】:
以下是我所做的。代码可以编译,但大约一周左右我没有机会在真人机器人上对其进行测试。
typedef signed short AL_SOUND_FORMAT; // copy from alaudio/alsoundextractor.h
void process(const int& nbOfChannels, const int& nbrOfSamplesByChannel, const AL_SOUND_FORMAT *buffer, const qi::AnyValue& timeStamp); // I do not use the timeStamp variable in my code, so AnyValue would work?
qi::AnyObject audioDevice = _session->service("ALAudioDevice"); // same variable name as in the original ALSoundExtractor module, just as a convenience
audioDevice.call<qi::AnyValue>("setClientPreferences", audioDevice.call<qi::AnyValue>("getName"), 48000, 0, 1);
audioDevice.call<qi::AnyValue>("subscribe", audioDevice.call<qi::AnyValue>("getName")); // this is the key call
audioDevice.call<qi::AnyValue>("startDetection"); // is it still necessary?
我的问题是 - 我现在就做吗?如果我无法覆盖虚函数 process(),订阅我的模块是否保证回调我的 process(...)?
【讨论】:
这篇文章不是答案,而是对我的回复的评论。 这行不通,因为这里你没有写 Qi 服务。使用audioDevice.call<qi::AnyValue>("subscribe", audioDevice.call<qi::AnyValue>("getName"));
,您是在告诉 ALAudioDevice 使用缓冲区回调 ALAudioDevice(而不是您的服务!)。
我可以在这里明确地传递我的班级名称,但想调用 getName(),就像在旧的 NAOqi 中一样。坦率地说,我不确定如何设置该名称。在旧的 NAOqi 中,它是由 ALModule::createModule 设置的。现在应该由 session->registerService() 设置吗?
没错,这是你给session->registerService(...)
的名字以上是关于NAO 机器人:将 ALSoundExtractor 移植到 qi 框架的主要内容,如果未能解决你的问题,请参考以下文章