Read stream form Audio input

Objectifs:

  • Understand basis of river
  • Create a simple recording interface that print the average of sample absolute value.

When you will create an application based on the river audio interface you need :

Include:

Include manager and interface node

#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
#include <audio/river/Interface.hpp>

Initilize the River library:

We first need to initialize etk sub library (needed to select the log level of sub-libraries and file access abstraction

// the only one init for etk:
etk::init(_argc, _argv);

Now we will initilaize the river library. To do this We have 2 posibilities:

With a file:

// initialize river interface
river::init("DATA:configFileName.json");

With a json string:

static const std::string configurationRiver =
"{\n"
" microphone:{\n"
" io:'input',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
//" timestamp-mode:'trigered',\n"
" },\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024\n"
" }\n"
"}\n";
// initialize river interface
river::initString(configurationRiver);

For the example we select the second solution (faster to implement example and resource at the same position.

river::init / river::initString must be called only one time for all the application, this represent the hardware configuration. It is NOT dynamic

To understand the configuration file Please see River configuration file

This json is parsed by the {#ejson_mainpage_what} it contain some update like:

  • Optionnal " in the name of element. - The possibilities to remplace " with '.

Get the river interface manager:

An application can have many interface and only one Manager. And a process can contain many application.

Then, we will get the first application manager handle.

// Create the River manager for tha application or part of the application.
ememory::SharedPtr<audio::river::Manager> manager = audio::river::Manager::create("river_sample_read");

Note: You can get back the application handle when you create a new one with the same name.

Create your read interface:

Generic code:

// create interface:
//Get the generic input:
interface = manager->createInput(48000,
std::vector<audio::channel>(),
ioName);
if(interface == nullptr) {
std::cout << "nullptr interface" << std::endl;
return -1;
}

Here we create an interface with:

  • The frequency of 48000 Hz.
  • The default Low level definition channel
  • A data interface of 16 bits samples coded in [-32768..32767]
  • Select input interaface name "microphone"

set data callback:

The best way to get data is to instanciate a simple callback. The callback is called when sample arrive and you have the nbChunk/frequency to process the data, otherwise you can generate error in data stream.

// set callback mode ...
interface->setInputCallback(std::bind(&onDataReceived,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3,
std::placeholders::_4,
std::placeholders::_5,
std::placeholders::_6,
&outputNode));

Callback inplementation:

Simply declare your function and do what you want inside.

void onDataReceived(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map,
etk::FSNode* _outputNode) {
if ( _format != audio::format_int16
&& _format != audio::format_float) {
std::cout << "[ERROR] call wrong type ... (need int16_t.float)" << std::endl;
return;
}

start and stop the stream:

// start the stream
interface->start();
// wait 10 second ...
std::this_thread::sleep_for(std::chrono::seconds(10));
// stop the stream
interface->stop();

Remove interfaces:

// remove interface and manager.
interface.reset();
manager.reset();

Full Sample:

#include <audio/river/river.hpp>
#include <audio/river/Manager.hpp>
#include <audio/river/Interface.hpp>
#include <etk/etk.hpp>
#include <thread>
static const std::string configurationRiver =
"{\n"
" microphone:{\n"
" io:'input',\n"
" map-on:{\n"
" interface:'auto',\n"
" name:'default',\n"
//" timestamp-mode:'trigered',\n"
" },\n"
" frequency:0,\n"
" channel-map:['front-left', 'front-right'],\n"
" type:'auto',\n"
" nb-chunk:1024\n"
" }\n"
"}\n";
void onDataReceived(const void* _data,
const audio::Time& _time,
size_t _nbChunk,
enum audio::format _format,
uint32_t _frequency,
const std::vector<audio::channel>& _map,
etk::FSNode* _outputNode) {
if ( _format != audio::format_int16
&& _format != audio::format_float) {
std::cout << "[ERROR] call wrong type ... (need int16_t.float)" << std::endl;
return;
}
if (_outputNode->fileIsOpen() == false) {
if (_format != audio::format_int16) {
// get the curent power of the signal.
const int16_t* data = static_cast<const int16_t*>(_data);
int64_t value = 0;
for (size_t iii=0; iii<_nbChunk*_map.size(); ++iii) {
value += std::abs(data[iii]);
}
value /= (_nbChunk*_map.size());
std::cout << "Get data ... average=" << int32_t(value) << std::endl;
} else {
// get the curent power of the signal.
const float* data = static_cast<const float*>(_data);
float value = 0;
for (size_t iii=0; iii<_nbChunk*_map.size(); ++iii) {
value += std::abs(data[iii]);
}
value /= (_nbChunk*_map.size());
std::cout << "Get data ... average=" << float(value) << std::endl;
}
} else {
// just write data
//std::cout << "Get data ... chunks=" << _nbChunk << " time=" << _time << std::endl;
_outputNode->fileWrite(_data, _map.size()*audio::getFormatBytes(_format), _nbChunk);
}
}
int main(int _argc, const char **_argv) {
// the only one init for etk:
etk::init(_argc, _argv);
// local parameter:
std::string configFile;
std::string ioName="microphone";
std::string outputFileName = "";
for (int32_t iii=0; iii<_argc ; ++iii) {
std::string data = _argv[iii];
if ( data == "-h"
|| data == "--help") {
std::cout << "Help : " << std::endl;
std::cout << " --conf=xxx.json Input/output configuration" << std::endl;
std::cout << " --io=xxx name configuration input" << std::endl;
std::cout << " --file=yyy.raw File name to store data" << std::endl;
exit(0);
} else if (etk::start_with(data, "--conf=") == true) {
configFile = std::string(data.begin()+7, data.end());
std::cout << "Select config: " << configFile << std::endl;
} else if (etk::start_with(data, "--io=") == true) {
ioName = std::string(data.begin()+5, data.end());
std::cout << "Select io: " << ioName << std::endl;
} else if (etk::start_with(data, "--file=") == true) {
outputFileName = std::string(data.begin()+7, data.end());
std::cout << "Select output file name: " << outputFileName << std::endl;
}
}
// initialize river interface
if (configFile == "") {
audio::river::initString(configurationRiver);
} else {
audio::river::init(configFile);
}
// Create the River manager for tha application or part of the application.
ememory::SharedPtr<audio::river::Manager> manager = audio::river::Manager::create("river_sample_read");
// create interface:
//Get the generic input:
interface = manager->createInput(48000,
std::vector<audio::channel>(),
ioName);
if(interface == nullptr) {
std::cout << "nullptr interface" << std::endl;
return -1;
}
etk::FSNode outputNode;
// open output file if needed:
if (outputFileName != "") {
outputNode.setName(outputFileName);
outputNode.fileOpenWrite();
}
// set callback mode ...
interface->setInputCallback(std::bind(&onDataReceived,
std::placeholders::_1,
std::placeholders::_2,
std::placeholders::_3,
std::placeholders::_4,
std::placeholders::_5,
std::placeholders::_6,
&outputNode));
// start the stream
interface->start();
// wait 10 second ...
std::this_thread::sleep_for(std::chrono::seconds(10));
// stop the stream
interface->stop();
// remove interface and manager.
interface.reset();
manager.reset();
// close the output file
if (outputFileName != "") {
outputNode.fileClose();
}
return 0;
}