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 &timestamp);

由于时间戳可能是两个整数的列表,我会尝试这样的事情

void TmpSoundExtractor::process (const int &nbOfChannels, const int &nbrOfSamplesByChannel, qi::AnyValue buffer, const std::vector<int> &timestamp);

我不确定如何处理缓冲区变量,但首先让其余的工作。

【讨论】:

【参考方案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&lt;qi::AnyValue&gt;("subscribe", audioDevice.call&lt;qi::AnyValue&gt;("getName"));,您是在告诉 ALAudioDevice 使用缓冲区回调 ALAudioDevice(而不是您的服务!)。 我可以在这里明确地传递我的班级名称,但想调用 getName(),就像在旧的 NAOqi 中一样。坦率地说,我不确定如何设置该名称。在旧的 NAOqi 中,它是由 ALModule::createModule 设置的。现在应该由 session->registerService() 设置吗? 没错,这是你给session-&gt;registerService(...)的名字

以上是关于NAO 机器人:将 ALSoundExtractor 移植到 qi 框架的主要内容,如果未能解决你的问题,请参考以下文章

使用 python 让 nao 机器人说出存储在变量中的内容

Pepper 和 NAO 机器人的远程视频流

带有 Nao 机器人的 Websocket 双向概念

NAO机器人程序设计——接力赛准备

NAO机器人程序设计——接力赛准备

NAO机器人程序设计——接力赛准备