From 7d6e1e0bf6ece1e050b132090260c2c56acc2d4e Mon Sep 17 00:00:00 2001 From: Edouard DUPIN Date: Thu, 19 Feb 2015 22:00:21 +0100 Subject: [PATCH] [DEV] add portaudio --- data/hardwareLinux.json | 1 + lutin_river.py | 12 ++- river/Interface.cpp | 45 ++++++++++- river/Interface.h | 3 + river/io/Manager.cpp | 30 +++++++- river/io/Manager.h | 2 +- river/io/Node.cpp | 114 ++++++++++++++++------------ river/io/Node.h | 1 + river/io/NodeAEC.cpp | 57 +++++++++++++- river/io/NodeAEC.h | 2 + river/io/NodeAirTAudio.cpp | 2 +- river/io/NodePortAudio.cpp | 152 +++++++++++++++++++++++++++++++++++++ river/io/NodePortAudio.h | 46 +++++++++++ 13 files changed, 411 insertions(+), 56 deletions(-) create mode 100644 river/io/NodePortAudio.cpp create mode 100644 river/io/NodePortAudio.h diff --git a/data/hardwareLinux.json b/data/hardwareLinux.json index e86e916..3ab2fa8 100644 --- a/data/hardwareLinux.json +++ b/data/hardwareLinux.json @@ -94,6 +94,7 @@ # AEC algo definition algo:"river-remover", algo-mode:"cutter", + feedback-delay:10000, # in nanosecond mux-demux-type:"int16", } } \ No newline at end of file diff --git a/lutin_river.py b/lutin_river.py index 0468d23..b04455d 100644 --- a/lutin_river.py +++ b/lutin_river.py @@ -17,11 +17,21 @@ def create(target): 'river/CircularBuffer.cpp', 'river/io/Node.cpp', 'river/io/NodeAirTAudio.cpp', + 'river/io/NodePortAudio.cpp', 'river/io/NodeAEC.cpp', 'river/io/Manager.cpp' ]) + if False: + myModule.add_optionnal_module_depend('airtaudio', "__AIRTAUDIO_INFERFACE__") + myModule.add_optionnal_module_depend('portaudio', "__PORTAUDIO_INFERFACE__") + else: + myModule.compile_flags_CC([ + '-D__AIRTAUDIO_INFERFACE__', + '-D__PORTAUDIO_INFERFACE__' + ]) + myModule.add_module_depend(['airtaudio', 'portaudio']) - myModule.add_module_depend(['audio', 'airtaudio', 'drain', 'ejson']) + myModule.add_module_depend(['audio', 'drain', 'ejson']) myModule.add_export_path(tools.get_current_path(__file__)) # add the currrent module at the diff --git a/river/Interface.cpp b/river/Interface.cpp index 26c8833..71f60bf 100644 --- a/river/Interface.cpp +++ b/river/Interface.cpp @@ -335,4 +335,47 @@ void river::Interface::systemVolumeChange() { return; } algo->volumeChange(); -} \ No newline at end of file +} + +static void link(etk::FSNode& _node, const std::string& _first, const std::string& _op, const std::string& _second) { + if (_op == "->") { + _node << " " << _first << " -> " << _second << ";\n"; + } else if (_op == "<-") { + _node << " " << _first << " -> " <<_second<< " [color=transparent];\n"; + _node << " " << _second << " -> " << _first << " [constraint=false];\n"; + } +} + +void river::Interface::generateDot(etk::FSNode& _node, const std::string& _nameIO) { + _node << "subgraph clusterInterface_" << m_uid << " {\n"; + _node << " color=orange;\n"; + _node << " label=\"[" << m_uid << "] Interface : " << m_name << "\";\n"; + + _node << " subgraph clusterInterface_" << m_uid << "_process {\n"; + _node << " label=\"Drain::Process\";\n"; + _node << " node [shape=ellipse];\n"; + _node << " INTERFACE_ALGO_" << m_uid << "_in [ label=\"format=xxx\n freq=yyy\n channelMap={left,right}\n in\" ];\n"; + _node << " INTERFACE_ALGO_" << m_uid << "_out [ label=\"format=xxx\n freq=yyy\n channelMap={left,right}\n out\" ];\n"; + _node << " }\n"; + if ( m_mode == river::modeInterface_input + || m_mode == river::modeInterface_feedback) { + link(_node, _nameIO, "->", "INTERFACE_ALGO_" + etk::to_string(m_uid) + "_in"); + link(_node, "INTERFACE_ALGO_" + etk::to_string(m_uid) + "_in", "->", "INTERFACE_ALGO_" + etk::to_string(m_uid) + "_out"); + } else { + link(_node, _nameIO, "<-", "INTERFACE_ALGO_" + etk::to_string(m_uid) + "_out"); + link(_node, "INTERFACE_ALGO_" + etk::to_string(m_uid) + "_out", "<-", "INTERFACE_ALGO_" + etk::to_string(m_uid) + "_in"); + } + _node << " node [shape=Mdiamond];\n"; + if (m_mode == river::modeInterface_input) { + _node << " API_" << m_uid << "_input [ label=\"API\nINPUT\" ];\n"; + link(_node, "INTERFACE_ALGO_" + etk::to_string(m_uid) + "_out", "->", "API_" + etk::to_string(m_uid) + "_input"); + } else if (m_mode == river::modeInterface_feedback) { + _node << " API_" << m_uid << "_feedback [ label=\"API\nFEEDBACK\" ];\n"; + link(_node, "INTERFACE_ALGO_" + etk::to_string(m_uid) + "_out", "->", "API_" + etk::to_string(m_uid) + "_feedback"); + } else if (m_mode == river::modeInterface_output) { + _node << " API_" << m_uid << "_output [ label=\"API\nOUTPUT\" ];\n"; + link(_node, "INTERFACE_ALGO_" + etk::to_string(m_uid) + "_in", "<-", "API_" + etk::to_string(m_uid) + "_output"); + } + _node << "}\n"; +} + diff --git a/river/Interface.h b/river/Interface.h index 96fdc5c..9f1469d 100644 --- a/river/Interface.h +++ b/river/Interface.h @@ -20,6 +20,7 @@ #include #include #include +#include namespace river { namespace io { @@ -204,6 +205,8 @@ namespace river { virtual void systemNeedOutputData(std::chrono::system_clock::time_point _time, void* _data, size_t _nbChunk, size_t _chunkSize); virtual void systemVolumeChange(); float m_volume; //!< Local channel Volume + public: + virtual void generateDot(etk::FSNode& _node, const std::string& _nameIO); }; }; diff --git a/river/io/Manager.cpp b/river/io/Manager.cpp index 2913aa0..4c87b39 100644 --- a/river/io/Manager.cpp +++ b/river/io/Manager.cpp @@ -10,11 +10,15 @@ #include "Node.h" #include "NodeAEC.h" #include "NodeAirTAudio.h" +#include "NodePortAudio.h" #include #undef __class__ #define __class__ "io::Manager" +#ifdef __PORTAUDIO_INFERFACE__ + #include +#endif static std::string basicAutoConfig = "{\n" @@ -55,6 +59,22 @@ river::io::Manager::Manager() { m_config.parse(basicAutoConfig); } // TODO : Load virtual.json and check if all is correct ... + + #ifdef __PORTAUDIO_INFERFACE__ + PaError err = Pa_Initialize(); + if(err != paNoError) { + RIVER_WARNING("Can not initialize portaudio : " << Pa_GetErrorText(err)); + } + #endif +}; + +river::io::Manager::~Manager() { + #ifdef __PORTAUDIO_INFERFACE__ + PaError err = Pa_Terminate(); + if(err != paNoError) { + RIVER_WARNING("Can not initialize portaudio : " << Pa_GetErrorText(err)); + } + #endif }; @@ -64,13 +84,16 @@ std::shared_ptr river::io::Manager::getInstance() { } std::shared_ptr river::io::Manager::getNode(const std::string& _name) { + RIVER_WARNING("Get node : " << _name); for (auto &it : m_list) { std::shared_ptr tmppp = it.lock(); if ( tmppp != nullptr && _name == tmppp->getName()) { + RIVER_WARNING(" find it ... "); return tmppp; } } + RIVER_WARNING("Create a new one : " << _name); // check if the node can be open : const std::shared_ptr tmpObject = m_config.getObject(_name); if (tmpObject != nullptr) { @@ -81,6 +104,11 @@ std::shared_ptr river::io::Manager::getNode(const std::string& std::shared_ptr tmp = river::io::NodeAirTAudio::create(_name, tmpObject); m_list.push_back(tmp); return tmp; + } else if ( ioType == "PAinput" + || ioType == "PAoutput") { + std::shared_ptr tmp = river::io::NodePortAudio::create(_name, tmpObject); + m_list.push_back(tmp); + return tmp; } else if (ioType == "aec") { std::shared_ptr tmp = river::io::NodeAEC::create(_name, tmpObject); m_list.push_back(tmp); @@ -152,7 +180,7 @@ void river::io::Manager::generateDot(const std::string& _filename) { return; } node << "digraph G {" << "\n"; - node << " rankdir=\"RL\";\n"; + node << " rankdir=\"LR\";\n"; int32_t id = 0; for (auto &it2 : m_list) { std::shared_ptr val = it2.lock(); diff --git a/river/io/Manager.h b/river/io/Manager.h index bcecdbe..61aa091 100644 --- a/river/io/Manager.h +++ b/river/io/Manager.h @@ -33,7 +33,7 @@ namespace river { /** * @brief Destructor */ - virtual ~Manager() {}; + virtual ~Manager(); private: ejson::Document m_config; // harware configuration std::vector > m_listKeepAlive; //!< list of all Node that might be keep alive sone time diff --git a/river/io/Node.cpp b/river/io/Node.cpp index 0dd375f..9f6df79 100644 --- a/river/io/Node.cpp +++ b/river/io/Node.cpp @@ -38,6 +38,7 @@ river::io::Node::Node(const std::string& _name, const std::shared_ptrgetStringValue("io"); if ( interfaceType == "input" + || interfaceType == "PAinput" || interfaceType == "aec") { m_isInput = true; } else { @@ -102,6 +103,19 @@ river::io::Node::~Node() { RIVER_INFO("-----------------------------------------------------------------"); }; +size_t river::io::Node::getNumberOfInterface(enum river::modeInterface _interfaceType) { + size_t out = 0; + for (auto &it : m_list) { + if (it == nullptr) { + continue; + } + if (it->getMode() == _interfaceType) { + out++; + } + } + return out; +} + void river::io::Node::registerAsRemote(const std::shared_ptr& _interface) { auto it = m_listAvaillable.begin(); while (it != m_listAvaillable.end()) { @@ -116,8 +130,8 @@ void river::io::Node::registerAsRemote(const std::shared_ptr& void river::io::Node::interfaceAdd(const std::shared_ptr& _interface) { { std::unique_lock lock(m_mutex); - for (size_t iii=0; iii< m_list.size(); ++iii) { - if (_interface == m_list[iii]) { + for (auto &it : m_list) { + if (_interface == it) { return; } } @@ -226,81 +240,85 @@ int32_t river::io::Node::newOutput(void* _outputBuffer, return 0; } +static void link(etk::FSNode& _node, const std::string& _first, const std::string& _op, const std::string& _second) { + if (_op == "->") { + _node << " " << _first << " -> " << _second << ";\n"; + } else if (_op == "<-") { + _node << " " << _first << " -> " <<_second<< " [color=transparent];\n"; + _node << " " << _second << " -> " << _first << " [constraint=false];\n"; + } +} + + void river::io::Node::generateDot(etk::FSNode& _node) { _node << "subgraph clusterNode_" << m_uid << " {\n"; _node << " color=blue;\n"; - _node << " label=\"IO::Node : " << m_name << "\";\n"; + _node << " label=\"[" << m_uid << "] IO::Node : " << m_name << "\";\n"; if (m_isInput == true) { - _node << " node [shape=larrow];\n"; + _node << " node [shape=rarrow];\n"; _node << " NODE_" << m_uid << "_HW_interface [ label=\"HW interface\n interface=ALSA\n stream=MICROPHONE\n type=input\" ];\n"; _node << " subgraph clusterNode_" << m_uid << "_process {\n"; _node << " label=\"Drain::Process\";\n"; _node << " node [shape=ellipse];\n"; - _node << " ALGO_" << m_uid << "_in [ label=\"format=xxx\n freq=yyy\n channelMap={left,right}\" ];\n"; - _node << " ALGO_" << m_uid << "_out [ label=\"format=xxx\n freq=yyy\n channelMap={left,right}\" ];\n"; + _node << " node_ALGO_" << m_uid << "_in [ label=\"format=xxx\n freq=yyy\n channelMap={left,right}\" ];\n"; + _node << " node_ALGO_" << m_uid << "_out [ label=\"format=xxx\n freq=yyy\n channelMap={left,right}\" ];\n"; _node << " }\n"; _node << " node [shape=square];\n"; _node << " NODE_" << m_uid << "_demuxer [ label=\"DEMUXER\n format=xxx\" ];\n"; // Link all nodes : - _node << " NODE_" << m_uid << "_HW_interface -> ALGO_" << m_uid << "_in [arrowhead=\"open\"];\n"; - _node << " ALGO_" << m_uid << "_in -> ALGO_" << m_uid << "_out [arrowhead=\"open\"];\n"; - _node << " ALGO_" << m_uid << "_out -> NODE_" << m_uid << "_demuxer [arrowhead=\"open\"];\n"; + _node << " NODE_" << m_uid << "_HW_interface -> node_ALGO_" << m_uid << "_in [arrowhead=\"open\"];\n"; + _node << " node_ALGO_" << m_uid << "_in -> node_ALGO_" << m_uid << "_out [arrowhead=\"open\"];\n"; + _node << " node_ALGO_" << m_uid << "_out -> NODE_" << m_uid << "_demuxer [arrowhead=\"open\"];\n"; } else { - _node << " node [shape=rarrow];\n"; + size_t nbOutput = getNumberOfInterface(river::modeInterface_output); + size_t nbfeedback = getNumberOfInterface(river::modeInterface_feedback); + _node << " node [shape=larrow];\n"; _node << " NODE_" << m_uid << "_HW_interface [ label=\"HW interface\n interface=ALSA\n stream=SPEAKER\n type=output\" ];\n"; - _node << " subgraph clusterNode_" << m_uid << "_process {\n"; - _node << " label=\"Drain::Process\";\n"; - _node << " node [shape=ellipse];\n"; - _node << " ALGO_" << m_uid << "_out [ label=\"format=xxx\n freq=yyy\n channelMap={left,right}\" ];\n"; - _node << " ALGO_" << m_uid << "_in [ label=\"format=xxx\n freq=yyy\n channelMap={left,right}\" ];\n"; - _node << " }\n"; + if (nbOutput>0) { + _node << " subgraph clusterNode_" << m_uid << "_process {\n"; + _node << " label=\"Drain::Process\";\n"; + _node << " node [shape=ellipse];\n"; + _node << " node_ALGO_" << m_uid << "_out [ label=\"format=xxx\n freq=yyy\n channelMap={left,right}\" ];\n"; + _node << " node_ALGO_" << m_uid << "_in [ label=\"format=xxx\n freq=yyy\n channelMap={left,right}\" ];\n"; + _node << " }\n"; + } _node << " node [shape=square];\n"; - _node << " NODE_" << m_uid << "_muxer [ label=\"MUXER\n format=xxx\" ];\n"; - _node << " NODE_" << m_uid << "_demuxer [ label=\"DEMUXER\n format=xxx\" ];\n"; + if (nbOutput>0) { + _node << " NODE_" << m_uid << "_muxer [ label=\"MUXER\n format=xxx\" ];\n"; + } + if (nbfeedback>0) { + _node << " NODE_" << m_uid << "_demuxer [ label=\"DEMUXER\n format=xxx\" ];\n"; + } // Link all nodes : - _node << " NODE_" << m_uid << "_muxer -> ALGO_" << m_uid << "_in [arrowhead=\"open\"];\n"; - _node << " ALGO_" << m_uid << "_in -> ALGO_" << m_uid << "_out [arrowhead=\"open\"];\n"; - _node << " ALGO_" << m_uid << "_out -> NODE_" << m_uid << "_HW_interface [arrowhead=\"open\"];\n"; - _node << " NODE_" << m_uid << "_HW_interface -> NODE_" << m_uid << "_demuxer [arrowhead=\"open\"];\n"; - _node << " { rank=same; NODE_" << m_uid << "_demuxer; NODE_" << m_uid << "_muxer }\n"; + if (nbOutput>0) { + link(_node, "NODE_" + etk::to_string(m_uid) + "_HW_interface", "<-", "node_ALGO_" + etk::to_string(m_uid) + "_out"); + link(_node, "node_ALGO_" + etk::to_string(m_uid) + "_out", "<-", "node_ALGO_" + etk::to_string(m_uid) + "_in"); + link(_node, "node_ALGO_" + etk::to_string(m_uid) + "_in", "<-", "NODE_" + etk::to_string(m_uid) + "_muxer"); + } + if (nbfeedback>0) { + _node << " NODE_" << m_uid << "_HW_interface -> NODE_" << m_uid << "_demuxer [arrowhead=\"open\"];\n"; + } + if ( nbOutput>0 + && nbfeedback>0) { + _node << " { rank=same; NODE_" << m_uid << "_demuxer; NODE_" << m_uid << "_muxer }\n"; + } } + _node << "}\n"; for (auto &it : m_list) { if (it != nullptr) { if (it->getMode() == modeInterface_input) { - _node << " interface_" << it->m_uid << " [ label=\"name=" << it->getName() << "\n type=input\" ];\n"; - _node << " NODE_" << m_uid << "_demuxer -> interface_" << it->m_uid << " [ arrowhead=\"open\"];\n"; + it->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_demuxer"); } else if (it->getMode() == modeInterface_output) { - _node << " interface_" << it->m_uid << " [ label=\"name=" << it->getName() << "\n type=output\" ];\n"; - _node << " interface_" << it->m_uid << " -> NODE_" << m_uid << "_muxer [ arrowhead=\"open\"];\n"; + it->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_muxer"); } else if (it->getMode() == modeInterface_feedback) { - _node << " interface_" << it->m_uid << " [ label=\"name=" << it->getName() << "\n type=feedback\" ];\n"; - _node << " NODE_" << m_uid << "_demuxer -> interface_" << it->m_uid << " [ arrowhead=\"open\"];\n"; + it->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_demuxer"); } else { } } } - - /* - // configure display: - _node << " node [shape=record, fontname=Helvetica, fontsize=10, color=lightsteelblue1, style=filled];\n"; - //_node << " node [shape=diamond, fontname=Helvetica, fontsize=10, color=orangered, style=filled];\n" - //_node << " node [shape=ellipse, fontname=Helvetica, fontsize=8, color=aquamarine2, style=filled];\n"; - // add elements - _node << " NODE_" << m_uid << " [ label=\"name=" << m_name << "\n type=" << (m_isInput?"input":"output") << "\" ];\n"; - // add IO - _node << " node [shape=record, fontname=Helvetica, fontsize=8, color=aquamarine2, style=filled];\n"; - int32_t id = 0; - for (auto &it : m_list) { - if (it != nullptr) { - _node << " interface_" << it->m_uid << " [ label=\"name=" << it->getName() << "\" ];\n"; - _node << " NODE_" << m_uid << " -> interface_" << it->m_uid << " [ arrowhead=\"open\"];\n"; - } - } - */ - _node << "}\n"; } diff --git a/river/io/Node.h b/river/io/Node.h index 2a77871..f2bc967 100644 --- a/river/io/Node.h +++ b/river/io/Node.h @@ -67,6 +67,7 @@ namespace river { protected: std::vector > m_listAvaillable; //!< List of all interface that exist on this Node std::vector > m_list; + size_t getNumberOfInterface(enum river::modeInterface _interfaceType); public: void registerAsRemote(const std::shared_ptr& _interface); void interfaceAdd(const std::shared_ptr& _interface); diff --git a/river/io/NodeAEC.cpp b/river/io/NodeAEC.cpp index 38b59bb..78d1a20 100644 --- a/river/io/NodeAEC.cpp +++ b/river/io/NodeAEC.cpp @@ -127,7 +127,7 @@ river::io::NodeAEC::NodeAEC(const std::string& _name, const std::shared_ptr m_sampleTime) { + RIVER_INFO("check delta " << delta.count() << " > " << m_sampleTime.count()); + if (delta > m_sampleTime) { // Synchronize if possible if (MicTime < fbTime) { RIVER_INFO("micTime < fbTime : Change Microphone time start " << fbTime); @@ -349,7 +356,7 @@ void river::io::NodeAEC::process() { while (true) { MicTime = m_bufferMicrophone.getReadTimeStamp(); fbTime = m_bufferFeedBack.getReadTimeStamp(); - RIVER_INFO(" process 256 samples ... " << MicTime); + RIVER_INFO(" process 256 samples ... micTime=" << MicTime << " fbTime=" << fbTime << " delta = " << (MicTime-fbTime).count()); m_bufferMicrophone.read(&dataMic[0], 256); m_bufferFeedBack.read(&dataFB[0], 256); SAVE_FILE_MACRO(int16_t, "REC_Microphone_sync.raw", &dataMic[0], 256*2); @@ -370,3 +377,47 @@ void river::io::NodeAEC::processAEC(void* _dataMic, void* _dataFB, uint32_t _nbC newInput(_dataMic, _nbChunk, _time); } + +void river::io::NodeAEC::generateDot(etk::FSNode& _node) { + _node << "subgraph clusterNode_" << m_uid << " {\n"; + _node << " color=blue;\n"; + _node << " label=\"[" << m_uid << "] IO::Node : " << m_name << "\";\n"; + + _node << " node [shape=box];\n"; + // TODO : Create a structure ... + _node << " NODE_" << m_uid << "_HW_AEC [ label=\"AEC\" ];\n"; + _node << " subgraph clusterNode_" << m_uid << "_process {\n"; + _node << " label=\"Drain::Process\";\n"; + _node << " node [shape=ellipse];\n"; + _node << " node_ALGO_" << m_uid << "_in [ label=\"format=xxx\n freq=yyy\n channelMap={left,right}\" ];\n"; + _node << " node_ALGO_" << m_uid << "_out [ label=\"format=xxx\n freq=yyy\n channelMap={left,right}\" ];\n"; + + _node << " }\n"; + _node << " node [shape=square];\n"; + _node << " NODE_" << m_uid << "_demuxer [ label=\"DEMUXER\n format=xxx\" ];\n"; + // Link all nodes : + _node << " NODE_" << m_uid << "_HW_AEC -> node_ALGO_" << m_uid << "_in;\n"; + _node << " node_ALGO_" << m_uid << "_in -> node_ALGO_" << m_uid << "_out;\n"; + _node << " node_ALGO_" << m_uid << "_out -> NODE_" << m_uid << "_demuxer;\n"; + _node << "}\n"; + if (m_interfaceMicrophone != nullptr) { + _node << " API_" << m_interfaceMicrophone->m_uid << "_input -> NODE_" << m_uid << "_HW_AEC;\n"; + } + if (m_interfaceFeedBack != nullptr) { + _node << " API_" << m_interfaceFeedBack->m_uid << "_feedback -> NODE_" << m_uid << "_HW_AEC;\n"; + } + + for (auto &it : m_list) { + if (it != nullptr) { + if (it->getMode() == modeInterface_input) { + it->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_demuxer"); + } else if (it->getMode() == modeInterface_output) { + it->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_muxer"); + } else if (it->getMode() == modeInterface_feedback) { + it->generateDot(_node, "NODE_" + etk::to_string(m_uid) + "_demuxer"); + } else { + + } + } + } +} \ No newline at end of file diff --git a/river/io/NodeAEC.h b/river/io/NodeAEC.h index e63dc2d..cde98c6 100644 --- a/river/io/NodeAEC.h +++ b/river/io/NodeAEC.h @@ -53,6 +53,8 @@ namespace river { std::chrono::nanoseconds m_sampleTime; //!< represent the sample time at the specify frequency. void process(); void processAEC(void* _dataMic, void* _dataFB, uint32_t _nbChunk, const std::chrono::system_clock::time_point& _time); + public: + virtual void generateDot(etk::FSNode& _node); }; } } diff --git a/river/io/NodeAirTAudio.cpp b/river/io/NodeAirTAudio.cpp index 7caf01a..c610ff6 100644 --- a/river/io/NodeAirTAudio.cpp +++ b/river/io/NodeAirTAudio.cpp @@ -12,7 +12,7 @@ #undef __class__ #define __class__ "io::NodeAirTAudio" -std::string asString(const std::chrono::system_clock::time_point& tp) { +static std::string asString(const std::chrono::system_clock::time_point& tp) { // convert to system time: std::time_t t = std::chrono::system_clock::to_time_t(tp); // convert in human string diff --git a/river/io/NodePortAudio.cpp b/river/io/NodePortAudio.cpp new file mode 100644 index 0000000..5ce779c --- /dev/null +++ b/river/io/NodePortAudio.cpp @@ -0,0 +1,152 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2015, Edouard DUPIN, all right reserved + * @license APACHE v2.0 (see license file) + */ + +#include +#include + +#include + +#undef __class__ +#define __class__ "io::NodePortAudio" + +static std::string asString(const std::chrono::system_clock::time_point& tp) { + // convert to system time: + std::time_t t = std::chrono::system_clock::to_time_t(tp); + // convert in human string + std::string ts = std::ctime(&t); + // remove \n + ts.resize(ts.size()-1); + return ts; +} + +namespace std { + static std::ostream& operator <<(std::ostream& _os, const std::chrono::system_clock::time_point& _obj) { + std::chrono::microseconds us = std::chrono::duration_cast(_obj.time_since_epoch()); + _os << us.count(); + return _os; + } +} + +static int portAudioStreamCallback(const void *_input, + void *_output, + unsigned long _frameCount, + const PaStreamCallbackTimeInfo* _timeInfo, + PaStreamCallbackFlags _statusFlags, + void *_userData) { + river::io::NodePortAudio* myClass = reinterpret_cast(_userData); + int64_t sec = int64_t(_timeInfo->inputBufferAdcTime); + int64_t nsec = (_timeInfo->inputBufferAdcTime-double(sec))*1000000000LL; + std::chrono::system_clock::time_point timeInput = std::chrono::system_clock::from_time_t(sec) + std::chrono::nanoseconds(nsec); + sec = int64_t(_timeInfo->outputBufferDacTime); + nsec = (_timeInfo->outputBufferDacTime-double(sec))*1000000000LL; + std::chrono::system_clock::time_point timeOutput = std::chrono::system_clock::from_time_t(sec) + std::chrono::nanoseconds(nsec); + return myClass->duplexCallback(_input, + timeInput, + _output, + timeOutput, + _frameCount, + _statusFlags); +} + +int32_t river::io::NodePortAudio::duplexCallback(const void* _inputBuffer, + const std::chrono::system_clock::time_point& _timeInput, + void* _outputBuffer, + const std::chrono::system_clock::time_point& _timeOutput, + uint32_t _nbChunk, + PaStreamCallbackFlags _status) { + std::unique_lock lock(m_mutex); + // TODO : Manage status ... + if (_inputBuffer != nullptr) { + RIVER_VERBOSE("data Input size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size()); + newInput(_inputBuffer, _nbChunk, _timeInput); + } + if (_outputBuffer != nullptr) { + RIVER_VERBOSE("data Output size request :" << _nbChunk << " [BEGIN] status=" << _status << " nbIO=" << m_list.size()); + newOutput(_outputBuffer, _nbChunk, _timeOutput); + } + return 0; +} + + +std::shared_ptr river::io::NodePortAudio::create(const std::string& _name, const std::shared_ptr& _config) { + return std::shared_ptr(new river::io::NodePortAudio(_name, _config)); +} + +river::io::NodePortAudio::NodePortAudio(const std::string& _name, const std::shared_ptr& _config) : + Node(_name, _config) { + drain::IOFormatInterface interfaceFormat = getInterfaceFormat(); + drain::IOFormatInterface hardwareFormat = getHarwareFormat(); + /** + map-on:{ # select hardware interface and name + interface:"alsa", # interface : "alsa", "pulse", "core", ... + name:"default", # name of the interface + }, + nb-chunk:1024 # number of chunk to open device (create the latency anf the frequency to call user) + */ + enum airtaudio::type typeInterface = airtaudio::type_undefined; + std::string streamName = "default"; + const std::shared_ptr tmpObject = m_config->getObject("map-on"); + if (tmpObject == nullptr) { + RIVER_WARNING("missing node : 'map-on' ==> auto map : 'auto:default'"); + } else { + std::string value = tmpObject->getStringValue("interface", "default"); + typeInterface = airtaudio::getTypeFromString(value); + streamName = tmpObject->getStringValue("name", "default"); + } + int32_t nbChunk = m_config->getNumberValue("nb-chunk", 1024); + + PaError err = 0; + if (m_isInput == true) { + err = Pa_OpenDefaultStream(&m_stream, + hardwareFormat.getMap().size(), + 0, + paInt16, + hardwareFormat.getFrequency(), + nbChunk, + &portAudioStreamCallback, + this); + } else { + err = Pa_OpenDefaultStream(&m_stream, + 0, + hardwareFormat.getMap().size(), + paInt16, + hardwareFormat.getFrequency(), + nbChunk, + &portAudioStreamCallback, + this); + } + if( err != paNoError ) { + RIVER_ERROR("Can not create Stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") << " err : " << Pa_GetErrorText(err)); + } + m_process.updateInterAlgo(); +} + +river::io::NodePortAudio::~NodePortAudio() { + std::unique_lock lock(m_mutex); + RIVER_INFO("close input stream"); + PaError err = Pa_CloseStream( m_stream ); + if( err != paNoError ) { + RIVER_ERROR("Remove stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") << " can not Remove stream ... " << Pa_GetErrorText(err)); + } +}; + +void river::io::NodePortAudio::start() { + std::unique_lock lock(m_mutex); + RIVER_INFO("Start stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") ); + PaError err = Pa_StartStream(m_stream); + if( err != paNoError ) { + RIVER_ERROR("Start stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") << " can not start stream ... " << Pa_GetErrorText(err)); + } +} + +void river::io::NodePortAudio::stop() { + std::unique_lock lock(m_mutex); + RIVER_INFO("Stop stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") ); + PaError err = Pa_StopStream(m_stream); + if( err != paNoError ) { + RIVER_ERROR("Start stream : '" << m_name << "' mode=" << (m_isInput?"input":"output") << " can not stop stream ... " << Pa_GetErrorText(err)); + } +} diff --git a/river/io/NodePortAudio.h b/river/io/NodePortAudio.h new file mode 100644 index 0000000..3525581 --- /dev/null +++ b/river/io/NodePortAudio.h @@ -0,0 +1,46 @@ +/** @file + * @author Edouard DUPIN + * @copyright 2015, Edouard DUPIN, all right reserved + * @license APACHE v2.0 (see license file) + */ + +#ifndef __RIVER_IO_NODE_PORTAUDIO_H__ +#define __RIVER_IO_NODE_PORTAUDIO_H__ + +#include +#include +#include + +namespace river { + namespace io { + class Manager; + class NodePortAudio : public Node { + protected: + /** + * @brief Constructor + */ + NodePortAudio(const std::string& _name, const std::shared_ptr& _config); + public: + static std::shared_ptr create(const std::string& _name, const std::shared_ptr& _config); + /** + * @brief Destructor + */ + virtual ~NodePortAudio(); + protected: + PaStream* m_stream; + public: + int32_t duplexCallback(const void* _inputBuffer, + const std::chrono::system_clock::time_point& _timeInput, + void* _outputBuffer, + const std::chrono::system_clock::time_point& _timeOutput, + uint32_t _nbChunk, + PaStreamCallbackFlags _status); + protected: + virtual void start(); + virtual void stop(); + }; + } +} + +#endif +