Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
012902e
[WORKFLOW] Updating protos from viamrobotics/api, commit: e1124aeb085…
viambot Oct 16, 2025
572f247
audio in wrapper
oliviamiller Oct 20, 2025
8a7b980
cleanup
oliviamiller Oct 20, 2025
e98cf28
clean up protos
oliviamiller Oct 20, 2025
ab0bfdf
add example
oliviamiller Oct 20, 2025
b773f7a
clean up
oliviamiller Oct 21, 2025
0d2725d
add get geometries
oliviamiller Oct 21, 2025
fc0869b
run clang format
oliviamiller Oct 21, 2025
75fde70
mock lint
oliviamiller Oct 21, 2025
41b598d
dont use std::byte
oliviamiller Oct 21, 2025
65de978
fix naming conflict with audio_info
oliviamiller Oct 21, 2025
6d495c7
fix equal operator
oliviamiller Oct 21, 2025
716790b
add test cases
oliviamiller Oct 21, 2025
728d566
format
oliviamiller Oct 21, 2025
b997866
pr comments
oliviamiller Oct 21, 2025
7616ba3
lint
oliviamiller Oct 21, 2025
abc6bd5
lint again
oliviamiller Oct 21, 2025
9910fad
properties to audio_properties
oliviamiller Oct 22, 2025
7628375
Merge branch 'main' into audio
lia-viam Oct 22, 2025
ff84ded
include tuple
oliviamiller Oct 22, 2025
2febd1f
Merge branch 'audio' of https://github.com/oliviamiller/viam-cpp-sdk …
oliviamiller Oct 22, 2025
5787c7b
make vars constants
oliviamiller Oct 22, 2025
54ddbac
include tuple in right file
oliviamiller Oct 22, 2025
08e96f6
make request id const
oliviamiller Oct 22, 2025
dd97070
fix M_PI not found on windows
oliviamiller Oct 22, 2025
0c7e883
lint
oliviamiller Oct 22, 2025
faad112
fix typo
oliviamiller Oct 22, 2025
5f66fb2
lint
oliviamiller Oct 22, 2025
deab294
module example
oliviamiller Oct 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions codesamples/apis.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
"client": "Arm",
"func": "get_end_position",
"args": []
},
"audio_in": {
"client": "AudioIn",
"func": "get_properties",
"args": []
},
"button": {
"client": "Button",
Expand Down
1 change: 1 addition & 0 deletions src/viam/examples/modules/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
add_subdirectory(tflite)
add_subdirectory(simple)
add_subdirectory(complex)
add_subdirectory(audioin)
43 changes: 43 additions & 0 deletions src/viam/examples/modules/audioin/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2023 Viam Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

add_executable(audioin_module
main.cpp
)

target_link_libraries(audioin_module
PRIVATE Threads::Threads
viam-cpp-sdk::viamsdk
)

install(
TARGETS audioin_module
COMPONENT examples
)

add_executable(audioin_client)
target_sources(audioin_client
PRIVATE
client.cpp
)


target_link_libraries(audioin_client
viam-cpp-sdk::viamsdk
)

install(
TARGETS audioin_client
COMPONENT examples
)
34 changes: 34 additions & 0 deletions src/viam/examples/modules/audioin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# VIAM Simple Module Example
This example goes through how to create a custom audio input resource using Viam's C++ SDK, and how to connect it to a Robot.

This is a limited document. For a more in-depth understanding of modules, see the [documentation](https://docs.viam.com/registry/).

Refer to main.cpp and the comments throughout for more information. For other C++ module examples, refer to the [simple module example](https://github.com/viamrobotics/viam-cpp-sdk/tree/main/src/viam/examples/modules/simple) or [complex module example](https://github.com/viamrobotics/viam-cpp-sdk/tree/main/src/viam/examples/modules/complex).

For a fully fleshed-out example of a C++ module that uses Github CI to upload to the Viam Registry, take a look at [module-example-cpp](https://github.com/viamrobotics/module-example-cpp). For a list of example modules in different Viam SDKs, take a look [here](https://github.com/viamrobotics/upload-module/#example-repos).

This is a limited document. For a more in-depth understanding of modules generally, see the [documentation](https://docs.viam.com/program/extend/modular-resources/).


Example Configuration:
```json{
"components": [
{
"name": "sinewave-audio",
"api": "rdk:component:audio_in",
"model": "viam:audio_in:sinewave",
"attributes": {
"frequency": 440
}
}
],
"modules": [
{
"type": "local",
"name": "my-module",
"executable_path": "/home/viam-cpp-sdk/build/src/viam/examples/modules/audioin/audioin_module"
}
]
}
```

128 changes: 128 additions & 0 deletions src/viam/examples/modules/audioin/client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

#include <viam/sdk/common/instance.hpp>
#include <viam/sdk/common/proto_value.hpp>
#include <viam/sdk/components/audio_in.hpp>
#include <viam/sdk/log/logging.hpp>
#include <viam/sdk/robot/client.hpp>
#include <viam/sdk/rpc/dial.hpp>

using namespace viam::sdk;

int main() {
// Every Viam C++ SDK program must have one and only one Instance object which is created before
// any other C++ SDK objects and stays alive until all Viam C++ SDK objects are destroyed.
Instance inst;

// Update these with your robot's connection details
const char* uri = ""; // replace with your robot's URI
DialOptions dial_options;
dial_options.set_allow_insecure_downgrade(true); // set to false if connecting securely

// Uncomment and fill out your credentials details if connecting securely
// std::string type = "api-key";
// std::string payload = "your-api-key-here";
// Credentials credentials(type, payload);
// dial_options.set_credentials(credentials);

boost::optional<DialOptions> opts(dial_options);
std::string address(uri);
Options options(1, opts);

std::shared_ptr<RobotClient> robot = RobotClient::at_address(address, options);

// Print resources
VIAM_SDK_LOG(info) << "Resources:";
std::vector<Name> resource_names = robot->resource_names();
for (const Name& resource : resource_names) {
VIAM_SDK_LOG(info) << " " << resource;
}

// Get the AudioIn component (update with your component name)
auto audio_in = robot->resource_by_name<AudioIn>("sinewave-audio");
if (!audio_in) {
VIAM_SDK_LOG(error) << "could not get 'sinewave-audio' resource from robot";
return EXIT_FAILURE;
}

// Get audio properties
VIAM_SDK_LOG(info) << "Getting audio properties...";
AudioIn::properties props = audio_in->get_properties();
VIAM_SDK_LOG(info) << "Audio properties:";
VIAM_SDK_LOG(info) << " sample_rate_hz: " << props.sample_rate_hz;
VIAM_SDK_LOG(info) << " num_channels: " << props.num_channels;
VIAM_SDK_LOG(info) << " supported_codecs: " << props.supported_codecs.size() << " codecs";

VIAM_SDK_LOG(info) << "Retrieving 2 seconds of audio...";

std::vector<uint8_t> all_audio_data;
int chunk_count = 0;

// Define chunk handler to collect audio data
auto chunk_handler = [&](AudioIn::audio_chunk&& chunk) -> bool {
chunk_count++;
VIAM_SDK_LOG(info) << "Received chunk " << chunk_count
<< " - length: " << chunk.audio_data.size()
<< " bytes, timestamp: " << chunk.start_timestamp_ns;

for (const auto& byte : chunk.audio_data) {
all_audio_data.push_back(static_cast<uint8_t>(byte));
}

return true; // Continue receiving chunks
};

// Get 2 seconds of audio (with previous_timestamp = 0 to start from now)
audio_in->get_audio("pcm16", chunk_handler, 2.0, 0);

VIAM_SDK_LOG(info) << "Total audio data received: " << all_audio_data.size() << " bytes";
VIAM_SDK_LOG(info) << "Total chunks: " << chunk_count;

// Save to a WAV file
std::string filename = "sine_wave_audio.wav";
std::ofstream outfile(filename, std::ios::binary);
if (!outfile.is_open()) {
VIAM_SDK_LOG(error) << "Failed to open file for writing";
return EXIT_FAILURE;
}

// WAV file parameters
uint32_t sample_rate = props.sample_rate_hz;
uint16_t num_channels = props.num_channels;
uint16_t bits_per_sample = 16; // 16-bit PCM
uint32_t data_size = all_audio_data.size();
uint32_t byte_rate = sample_rate * num_channels * (bits_per_sample / 8);
uint16_t block_align = num_channels * (bits_per_sample / 8);

// Write WAV header
// RIFF chunk descriptor
outfile.write("RIFF", 4);
uint32_t chunk_size = 36 + data_size;
outfile.write(reinterpret_cast<const char*>(&chunk_size), 4);
outfile.write("WAVE", 4);

outfile.write("fmt ", 4);
uint32_t subchunk1_size = 16; // PCM
outfile.write(reinterpret_cast<const char*>(&subchunk1_size), 4);
uint16_t audio_format = 1; // PCM
outfile.write(reinterpret_cast<const char*>(&audio_format), 2);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of opaque as written, I think let's do a helper function template (or generic lambda) that expresses this in terms of sizeof, which is what I assume is going on

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a template and helper function for writing wav files

outfile.write(reinterpret_cast<const char*>(&num_channels), 2);
outfile.write(reinterpret_cast<const char*>(&sample_rate), 4);
outfile.write(reinterpret_cast<const char*>(&byte_rate), 4);
outfile.write(reinterpret_cast<const char*>(&block_align), 2);
outfile.write(reinterpret_cast<const char*>(&bits_per_sample), 2);

outfile.write("data", 4);
outfile.write(reinterpret_cast<const char*>(&data_size), 4);
outfile.write(reinterpret_cast<const char*>(all_audio_data.data()), data_size);

outfile.close();
VIAM_SDK_LOG(info) << "Audio saved to " << filename;
VIAM_SDK_LOG(info) << "To play: open " << filename << " (or use any audio player)";

return EXIT_SUCCESS;
}
171 changes: 171 additions & 0 deletions src/viam/examples/modules/audioin/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#include <chrono>
#include <cmath>
#include <cstring>
#include <iostream>
#include <memory>
#include <sstream>
#include <vector>

#include <viam/sdk/common/exception.hpp>
#include <viam/sdk/common/instance.hpp>
#include <viam/sdk/common/proto_value.hpp>
#include <viam/sdk/components/audio_in.hpp>
#include <viam/sdk/components/sensor.hpp>
#include <viam/sdk/config/resource.hpp>
#include <viam/sdk/log/logging.hpp>
#include <viam/sdk/module/service.hpp>
#include <viam/sdk/registry/registry.hpp>
#include <viam/sdk/resource/reconfigurable.hpp>

using namespace viam::sdk;

// Implements an AudioIn component that generates a sine wave for testing
class SineWaveAudioIn : public AudioIn, public Reconfigurable {
public:
SineWaveAudioIn(const ResourceConfig& cfg) : AudioIn(cfg.name()) {
this->reconfigure({}, cfg);
}

static std::vector<std::string> validate(const ResourceConfig&);

void reconfigure(const Dependencies&, const ResourceConfig&) override;

ProtoStruct do_command(const ProtoStruct&) override;

std::vector<GeometryConfig> get_geometries(const ProtoStruct&) override {
throw Exception("method not supported");
}

properties get_properties(const ProtoStruct&) override;

void get_audio(std::string const& codec,
std::function<bool(audio_chunk&& chunk)> const& chunk_handler,
double const& duration_seconds,
int64_t const& previous_timestamp,
const ProtoStruct& extra) override;

private:
double frequency_{440.0};
};

std::vector<std::string> SineWaveAudioIn::validate(const ResourceConfig& cfg) {
auto itr = cfg.attributes().find("frequency");
if (itr != cfg.attributes().end()) {
const double* freq = itr->second.get<double>();
if (!freq) {
throw Exception("frequency must be a number value");
}
if (*freq <= 0.0 || *freq > 20000.0) {
throw Exception("frequency must be between 0 and 20000 Hz");
}
}
return {};
}

void SineWaveAudioIn::reconfigure(const Dependencies&, const ResourceConfig& cfg) {
auto itr = cfg.attributes().find("frequency");
if (itr != cfg.attributes().end()) {
const double* freq = itr->second.get<double>();
if (freq) {
frequency_ = *freq;
}
}
}

ProtoStruct SineWaveAudioIn::do_command(const ProtoStruct& command) {
for (const auto& entry : command) {
VIAM_RESOURCE_LOG(info) << "Command entry " << entry.first;
}
return command;
}

AudioIn::properties SineWaveAudioIn::get_properties(const ProtoStruct&) {
AudioIn::properties props;
props.supported_codecs = {"pcm16"};
props.sample_rate_hz = 44100;
props.num_channels = 1;
return props;
}

void SineWaveAudioIn::get_audio(std::string const& codec,
std::function<bool(audio_chunk&& chunk)> const& chunk_handler,
double const& duration_seconds,
int64_t const& previous_timestamp,
const ProtoStruct& extra) {
const int sample_rate = 44100; // 44.1 kHz
const double amplitude = 0.5; // Half volume
const int chunk_size = 1024; // Samples per chunk

int total_samples = static_cast<int>(duration_seconds * sample_rate);
int num_chunks = (total_samples + chunk_size - 1) / chunk_size;

VIAM_RESOURCE_LOG(info) << "Generating sine wave: " << frequency_ << "Hz, " << duration_seconds
<< "s, " << num_chunks << " chunks";

for (int chunk_idx = 0; chunk_idx < num_chunks; ++chunk_idx) {
int samples_in_chunk = std::min(chunk_size, total_samples - (chunk_idx * chunk_size));
std::vector<int16_t> samples;
samples.reserve(samples_in_chunk);

// Generate sine wave samples
for (int i = 0; i < samples_in_chunk; ++i) {
int sample_idx = chunk_idx * chunk_size + i;
double t = static_cast<double>(sample_idx) / sample_rate;
double sample_value = amplitude * std::sin(2.0 * M_PI * frequency_ * t);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's put as much of this stuff as possible into named helpers since it's a bit mysterious and I believe used a couple times

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its only used here but I added helper functions for clarity


// Convert to 16-bit PCM
int16_t pcm_sample = static_cast<int16_t>(sample_value * 32767.0);
samples.push_back(pcm_sample);
}

audio_chunk chunk;

// Convert int16_t samples to uint8_t bytes
chunk.audio_data.resize(samples.size() * sizeof(int16_t));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think let's do this with std::copy and/or boost::span

std::memcpy(chunk.audio_data.data(), samples.data(), chunk.audio_data.size());

// Set audio info
chunk.info.codec = codec;
chunk.info.sample_rate_hz = sample_rate;
chunk.info.num_channels = 1;

// Get current timestamp in nanoseconds
auto now = std::chrono::system_clock::now();
auto nanos =
std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()).count();
chunk.start_timestamp_ns = nanos;
chunk.end_timestamp_ns = nanos;
chunk.sequence = chunk_idx;

// Call the chunk handler callback
if (!chunk_handler(std::move(chunk))) {
VIAM_RESOURCE_LOG(info) << "Chunk handler returned false, stopping";
break;
}
}

VIAM_RESOURCE_LOG(info) << "Finished generating sine wave";
}

int main(int argc, char** argv) try {
// Every Viam C++ SDK program must have one and only one Instance object which is created before
// any other C++ SDK objects and stays alive until all Viam C++ SDK objects are destroyed.
Instance inst;

Model sinewave_model("viam", "audio_in", "sinewave");

std::shared_ptr<ModelRegistration> mr = std::make_shared<ModelRegistration>(
API::get<AudioIn>(),
sinewave_model,
[](Dependencies, ResourceConfig cfg) { return std::make_unique<SineWaveAudioIn>(cfg); },
&SineWaveAudioIn::validate);

std::vector<std::shared_ptr<ModelRegistration>> mrs = {mr};
auto my_mod = std::make_shared<ModuleService>(argc, argv, mrs);
my_mod->serve();

return EXIT_SUCCESS;
} catch (const viam::sdk::Exception& ex) {
std::cerr << "main failed with exception: " << ex.what() << "\n";
return EXIT_FAILURE;
}
Loading
Loading