Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
16 changes: 16 additions & 0 deletions src/viam/api/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ if (VIAMCPPSDK_USE_DYNAMIC_PROTOS)
${PROTO_GEN_DIR}/component/arm/v1/arm.grpc.pb.h
${PROTO_GEN_DIR}/component/arm/v1/arm.pb.cc
${PROTO_GEN_DIR}/component/arm/v1/arm.pb.h
${PROTO_GEN_DIR}/component/audioin/v1/audioin.grpc.pb.cc
${PROTO_GEN_DIR}/component/audioin/v1/audioin.grpc.pb.h
${PROTO_GEN_DIR}/component/audioin/v1/audioin.pb.cc
${PROTO_GEN_DIR}/component/audioin/v1/audioin.pb.h
${PROTO_GEN_DIR}/component/audioout/v1/audioout.grpc.pb.cc
${PROTO_GEN_DIR}/component/audioout/v1/audioout.grpc.pb.h
${PROTO_GEN_DIR}/component/audioout/v1/audioout.pb.cc
${PROTO_GEN_DIR}/component/audioout/v1/audioout.pb.h
${PROTO_GEN_DIR}/component/base/v1/base.grpc.pb.cc
${PROTO_GEN_DIR}/component/base/v1/base.grpc.pb.h
${PROTO_GEN_DIR}/component/base/v1/base.pb.cc
Expand Down Expand Up @@ -294,6 +302,10 @@ target_sources(viamapi
${PROTO_GEN_DIR}/common/v1/common.pb.cc
${PROTO_GEN_DIR}/component/arm/v1/arm.grpc.pb.cc
${PROTO_GEN_DIR}/component/arm/v1/arm.pb.cc
${PROTO_GEN_DIR}/component/audioin/v1/audioin.grpc.pb.cc
${PROTO_GEN_DIR}/component/audioin/v1/audioin.pb.cc
${PROTO_GEN_DIR}/component/audioout/v1/audioout.grpc.pb.cc
${PROTO_GEN_DIR}/component/audioout/v1/audioout.pb.cc
${PROTO_GEN_DIR}/component/base/v1/base.grpc.pb.cc
${PROTO_GEN_DIR}/component/base/v1/base.pb.cc
${PROTO_GEN_DIR}/component/board/v1/board.grpc.pb.cc
Expand Down Expand Up @@ -358,6 +370,10 @@ target_sources(viamapi
${PROTO_GEN_DIR}/../../viam/api/common/v1/common.pb.h
${PROTO_GEN_DIR}/../../viam/api/component/arm/v1/arm.grpc.pb.h
${PROTO_GEN_DIR}/../../viam/api/component/arm/v1/arm.pb.h
${PROTO_GEN_DIR}/../../viam/api/component/audioin/v1/audioin.grpc.pb.h
${PROTO_GEN_DIR}/../../viam/api/component/audioin/v1/audioin.pb.h
${PROTO_GEN_DIR}/../../viam/api/component/audioout/v1/audioout.grpc.pb.h
${PROTO_GEN_DIR}/../../viam/api/component/audioout/v1/audioout.pb.h
${PROTO_GEN_DIR}/../../viam/api/component/base/v1/base.grpc.pb.h
${PROTO_GEN_DIR}/../../viam/api/component/base/v1/base.pb.h
${PROTO_GEN_DIR}/../../viam/api/component/board/v1/board.grpc.pb.h
Expand Down
96 changes: 39 additions & 57 deletions src/viam/examples/modules/audioin/client.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>

#include <viam/sdk/common/audio.hpp>
#include <viam/sdk/common/instance.hpp>
#include <viam/sdk/common/proto_value.hpp>
#include <viam/sdk/components/audio_in.hpp>
Expand All @@ -18,22 +18,34 @@ int main() {
// 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
std::string host("xarm-main.aqb785vhl4.viam.cloud");
DialOptions dial_opts;
dial_opts.set_entity(std::string("88dcef8e-db7f-47dc-9b0f-eb08fdc5a97d"));
Copy link
Collaborator

Choose a reason for hiding this comment

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

don't forget to revert these before merging! also in the future it's best to git stash these changes rather than committing them while working on the PR

Copy link
Member Author

Choose a reason for hiding this comment

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

oops yeah did not mean to commit this, reverted


// 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);
Credentials credentials("api-key", "eou798fi90d1fytv66c9lntv1ndwxj4g");

boost::optional<DialOptions> opts(dial_options);
std::string address(uri);
Options options(1, opts);
dial_opts.set_credentials(credentials);
boost::optional<DialOptions> opts(dial_opts);
Options options(0, opts);

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

// // 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:";
Expand All @@ -49,9 +61,8 @@ int main() {
return EXIT_FAILURE;
}

// Get audio properties
VIAM_SDK_LOG(info) << "Getting audio properties...";
AudioIn::properties props = audio_in->get_properties();
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;
Expand All @@ -66,8 +77,7 @@ int main() {
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;
<< " - length: " << chunk.audio_data.size() << " bytes";

for (const auto& byte : chunk.audio_data) {
all_audio_data.push_back(static_cast<uint8_t>(byte));
Expand All @@ -77,52 +87,24 @@ int main() {
};

// Get 2 seconds of audio (with previous_timestamp = 0 to start from now)
audio_in->get_audio("pcm16", chunk_handler, 2.0, 0);
audio_in->get_audio(audio_codecs::PCM_16, 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";
try {
write_wav_file(filename,
all_audio_data,
audio_codecs::PCM_16,
props.sample_rate_hz,
props.num_channels);
VIAM_SDK_LOG(info) << "Audio saved to " << filename;
VIAM_SDK_LOG(info) << "To play: open " << filename << " (or use any audio player)";
} catch (const std::exception& e) {
VIAM_SDK_LOG(error) << "Failed to write WAV file: " << e.what();
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);
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;
}
97 changes: 65 additions & 32 deletions src/viam/examples/modules/audioin/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <sstream>
#include <vector>

#include <viam/sdk/common/audio.hpp>
#include <viam/sdk/common/exception.hpp>
#include <viam/sdk/common/instance.hpp>
#include <viam/sdk/common/proto_value.hpp>
Expand Down Expand Up @@ -46,6 +47,15 @@ class SineWaveAudioIn : public AudioIn, public Reconfigurable {

private:
double frequency_{440.0};

static double generate_sine_sample(double frequency, double amplitude, double time_seconds);
static int16_t float_to_pcm16(double sample_value);
static std::vector<uint8_t> pcm16_samples_to_bytes(const std::vector<int16_t>& samples);
static audio_chunk create_audio_chunk(const std::vector<int16_t>& samples,
const std::string& codec,
int sample_rate_hz,
int num_channels,
int sequence_number);
};

std::vector<std::string> SineWaveAudioIn::validate(const ResourceConfig& cfg) {
Expand Down Expand Up @@ -79,9 +89,53 @@ ProtoStruct SineWaveAudioIn::do_command(const ProtoStruct& command) {
return command;
}

AudioIn::properties SineWaveAudioIn::get_properties(const ProtoStruct&) {
AudioIn::properties props;
props.supported_codecs = {"pcm16"};
// Generates a single audio sample representing a sine wave at the given frequency, amplitude, and
// time.
double SineWaveAudioIn::generate_sine_sample(double frequency,
double amplitude,
double time_seconds) {
return amplitude * std::sin(2.0 * M_PI * frequency * time_seconds);
}

// Converts a normalized floating-point sample (-1.0 to 1.0) to 16-bit PCM format.
int16_t SineWaveAudioIn::float_to_pcm16(double sample_value) {
return static_cast<int16_t>(sample_value * 32767.0);
}

std::vector<uint8_t> SineWaveAudioIn::pcm16_samples_to_bytes(const std::vector<int16_t>& samples) {
std::vector<uint8_t> bytes(samples.size() * sizeof(int16_t));
std::copy(reinterpret_cast<const uint8_t*>(samples.data()),
reinterpret_cast<const uint8_t*>(samples.data()) + bytes.size(),
bytes.begin());
return bytes;
}

AudioIn::audio_chunk SineWaveAudioIn::create_audio_chunk(const std::vector<int16_t>& samples,
const std::string& codec,
int sample_rate_hz,
int num_channels,
int sequence_number) {
audio_chunk chunk;
chunk.audio_data = pcm16_samples_to_bytes(samples);
chunk.info.codec = codec;
chunk.info.sample_rate_hz = sample_rate_hz;
chunk.info.num_channels = num_channels;

auto now = std::chrono::system_clock::now();
auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch());
// Set both start and end timestamps to the current system time in nanoseconds.
// In a real application, start_timestamp_ns would mark the time the audio chunk begins,
// and end_timestamp_ns would mark when it ends. Here they are equal for simplicity.
chunk.start_timestamp_ns = nanos;
chunk.end_timestamp_ns = nanos;
chunk.sequence_number = sequence_number;

return chunk;
}

properties SineWaveAudioIn::get_properties(const ProtoStruct&) {
properties props;
props.supported_codecs = {audio_codecs::PCM_16};
props.sample_rate_hz = 44100;
props.num_channels = 1;
return props;
Expand All @@ -92,9 +146,9 @@ void SineWaveAudioIn::get_audio(std::string const& codec,
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
const int sample_rate = 44100;
const double amplitude = 0.5;
const int chunk_size = 1024;

int total_samples = static_cast<int>(duration_seconds * sample_rate);
int num_chunks = (total_samples + chunk_size - 1) / chunk_size;
Expand All @@ -107,37 +161,16 @@ void SineWaveAudioIn::get_audio(std::string const& codec,
std::vector<int16_t> samples;
samples.reserve(samples_in_chunk);

// Generate sine wave samples
// Create each sample and put in chunk.
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);

// Convert to 16-bit PCM
int16_t pcm_sample = static_cast<int16_t>(sample_value * 32767.0);
samples.push_back(pcm_sample);
double time_seconds = static_cast<double>(sample_idx) / sample_rate;
double sample_value = generate_sine_sample(frequency_, amplitude, time_seconds);
samples.push_back(float_to_pcm16(sample_value));
}

audio_chunk chunk;

// Convert int16_t samples to uint8_t bytes
chunk.audio_data.resize(samples.size() * sizeof(int16_t));
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;
audio_chunk chunk = create_audio_chunk(samples, codec, sample_rate, 1, chunk_idx);

// Call the chunk handler callback
if (!chunk_handler(std::move(chunk))) {
VIAM_RESOURCE_LOG(info) << "Chunk handler returned false, stopping";
break;
Expand Down
3 changes: 3 additions & 0 deletions src/viam/sdk/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ endif()

target_sources(viamsdk
PRIVATE
common/audio.cpp
common/client_helper.cpp
common/exception.cpp
common/instance.cpp
Expand Down Expand Up @@ -166,6 +167,7 @@ target_sources(viamsdk
../..
${CMAKE_CURRENT_BINARY_DIR}/../..
FILES
../../viam/sdk/common/audio.hpp
../../viam/sdk/common/client_helper.hpp
../../viam/sdk/common/exception.hpp
../../viam/sdk/common/instance.hpp
Expand All @@ -177,6 +179,7 @@ target_sources(viamsdk
../../viam/sdk/common/version_metadata.hpp
../../viam/sdk/common/world_state.hpp
../../viam/sdk/components/arm.hpp
../../viam/sdk/components/audio_in.hpp
../../viam/sdk/components/base.hpp
../../viam/sdk/components/board.hpp
../../viam/sdk/components/button.hpp
Expand Down
Loading
Loading