diff --git a/.gitignore b/.gitignore index 8cfdfce..6e26e55 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,8 @@ audio solo.dbg solo.opt + +cmake-* +.idea + +build/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index f06b4b4..0000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "libspatialaudio"] - path = libspatialaudio - url = https://github.com/ILLIXR/libspatialaudio.git -[submodule "portaudio"] - path = portaudio - url = https://github.com/PortAudio/portaudio.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..994e9fe --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,53 @@ +cmake_minimum_required(VERSION 3.16) +set(CMAKE_VERBOSE_MAKEFILE True) + +project(audio_pipeline VERSION 1.0 LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +include(FindPkgConfig) + +set(CMAKE_CXX_FLAGS_DEBUG "-ggdb -Wall -fPIC -Wno-overloaded-virtual") +set(CMAKE_CXX_FLAGE_RELEASE "-O3 -DNDEBUG -Wall -fPIC -Wno-overloaded-virtual") +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-ggdb -O3 -Wall -fPIC -Wno-overloaded-virtual") +set(ILLIXR_INTEGRATION ON) +add_definitions(-DILLIXR_INTEGRATION) +set(ILLIXR_ROOT "" CACHE PATH "Path to ILLIXR headers") + +pkg_check_modules(PORTAUDIO REQUIRED portaudio-2.0>=19) +pkg_check_modules(SPATIALAUDIO REQUIRED spatialaudio) +find_package(spdlog REQUIRED) +find_package(SQLite3 REQUIRED) + +if (CMAKE_VERSION VERSION_LESS "3.30.0") + find_package(Boost REQUIRED COMPONENTS filesystem serialization iostreams) +else() + find_package(Boost REQUIRED COMPONENTS filesystem serialization iostreams CONFIG) +endif() + +add_executable(solo${ILLIXR_BUILD_SUFFIX}.exe src/main.cpp) +add_library(plugin.audio_pipeline${ILLIXR_BUILD_SUFFIX} SHARED + src/audio.cpp + include/audio.hpp + src/plugin.cpp + include/plugin.hpp + src/sound.cpp + include/sound.hpp + src/realtime.cpp + include/realtime.hpp +) +include_directories(${CMAKE_INSTALL_PREFIX}/include ${PROJECT_SOURCE_DIR}/include ${ILLIXR_ROOT}) + +if(ILLIXR_INTEGRATION) + target_compile_definitions(plugin.audio_pipeline${ILLIXR_BUILD_SUFFIX} PUBLIC -DAUDIO_SAMPLES=\"${CMAKE_INSTALL_PREFIX}/etc/audio_pipeline\") +endif() + +target_include_directories(plugin.audio_pipeline${ILLIXR_BUILD_SUFFIX} PUBLIC ${PORTAUDIO_INCLUDE_DIRS} ${SPATIALAUDIO_INCLUDE_DIRS}) +target_link_libraries(plugin.audio_pipeline${ILLIXR_BUILD_SUFFIX} PUBLIC ${PORTAUDIO_LIBRARIES} ${SPATIALAUDIO_LIBRARIES} spdlog::spdlog sqlite3) + +target_link_libraries(solo${ILLIXR_BUILD_SUFFIX}.exe PUBLIC plugin.audio_pipeline${ILLIXR_BUILD_SUFFIX} pthread spdlog::spdlog Boost::serialization) + +install(TARGETS plugin.audio_pipeline${ILLIXR_BUILD_SUFFIX} DESTINATION lib) +install(TARGETS solo${ILLIXR_BUILD_SUFFIX}.exe DESTINATION bin) +install(DIRECTORY ${CMAKE_SOURCE_DIR}/samples + DESTINATION etc/audio_pipeline) diff --git a/include/audio.h b/include/audio.h deleted file mode 100644 index d40fd1d..0000000 --- a/include/audio.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -#include "sound.h" -#include <vector> -#include <string> -#include <string_view> -#include <optional> -#include <pthread.h> - -namespace ILLIXR_AUDIO{ - class ABAudio{ - - public: - // Process types - enum class ProcessType { - FULL, // FULL for output wav file - ENCODE, // For profiling, do file reading and encoding without file output - DECODE // For profiling, do ambisonics decoding without file output - }; - ABAudio(std::string outputFilePath, ProcessType processType); - // Process a block (1024) samples of sound - void processBlock(); - // Load sound source files (predefined) - void loadSource(); - - // Buffer of most recent processed block for fast copying to audio buffer - short mostRecentBlockL[BLOCK_SIZE]; - short mostRecentBlockR[BLOCK_SIZE]; - bool buffer_ready; - - // Number of blocks left to process before this stream is complete - unsigned long num_blocks_left; - - private: - ProcessType processType; - // a list of sound sources in this audio - std::vector<Sound> soundSrcs; - // target output file - std::optional<std::ofstream> outputFile; - // decoder associated with this audio - CAmbisonicBinauralizer decoder; - // ambisonics rotator associated with this audio - CAmbisonicProcessor rotator; - // ambisonics zoomer associated with this audio - CAmbisonicZoomer zoomer; - - int frame = 0; - - // Generate dummy WAV output file header - void generateWAVHeader(); - // Read in data from WAV files and encode into ambisonics - void readNEncode(CBFormat& sumBF); - // Apply rotation and zoom effects to the ambisonics sound field - void rotateNZoom(CBFormat& sumBF); - // Write out a block of samples to the output file - void writeFile(float** resultSample); - - // Abort failed configuration - void configAbort(const std::string_view& compName) const; - - void updateRotation(); - void updateZoom(); - }; -} diff --git a/include/audio.hpp b/include/audio.hpp new file mode 100644 index 0000000..9e3cbcc --- /dev/null +++ b/include/audio.hpp @@ -0,0 +1,75 @@ +#pragma once + +#ifdef ILLIXR_INTEGRATION + +#include "illixr/switchboard.hpp" + +#endif + +#include "sound.hpp" + +#include <optional> +#include <pthread.h> +#include <string> +#include <string_view> +#include <vector> + +namespace ILLIXR::audio { +class ab_audio { +public: + // Process types + enum class process_type { + FULL, // FULL for output wav file + ENCODE, // For profiling, do file reading and encoding without file output + DECODE // For profiling, do ambisonics decoding without file output + }; + + ab_audio(std::string output_file_path, process_type proc_type_in); + + // Process a block (1024) samples of sound + void process_block(); + + // Load sound source files (predefined) +#ifdef ILLIXR_INTEGRATION + void load_source(const std::shared_ptr<ILLIXR::switchboard> &sb); +#else + void load_source(); +#endif + + // Buffer of most recent processed block for fast copying to audio buffer + short most_recent_block_L[BLOCK_SIZE]; + short most_recent_block_R[BLOCK_SIZE]; + bool buffer_ready; + + // Number of blocks left to process before this stream is complete + unsigned long num_blocks_left; + +private: + // Generate dummy WAV output file header + void generate_wav_header(); + + // Read in data from WAV files and encode into ambisonics + void read_and_encode(CBFormat &sum_bf); + + // Apply rotation and zoom effects to the ambisonics sound field + void rotate_and_zoom(CBFormat &sum_bf); + + // Write out a block of samples to the output file + void write_file(float **result_sample); + + // Abort failed configuration + void config_abort(const std::string_view &comp_name) const; + + void update_rotation(); + + void update_zoom(); + + process_type process_type_; + std::vector<sound> sound_srcs_; //!< a list of sound sources in this audio + std::optional<std::ofstream> output_file_; //!< target output file + CAmbisonicBinauralizer decoder_; //!< decoder associated with this audio + CAmbisonicProcessor rotator_; //!< ambisonics rotator associated with this audio + CAmbisonicZoomer zoomer_; //!< ambisonics zoomer associated with this audio + int frame_ = 0; +}; +} diff --git a/include/common b/include/common deleted file mode 120000 index 60d3b0a..0000000 --- a/include/common +++ /dev/null @@ -1 +0,0 @@ -../common \ No newline at end of file diff --git a/include/plugin.hpp b/include/plugin.hpp new file mode 100644 index 0000000..95aa0ba --- /dev/null +++ b/include/plugin.hpp @@ -0,0 +1,47 @@ +#pragma once +#include "audio.hpp" + +#include "illixr/data_format/pose.hpp" +#include "illixr/phonebook.hpp" +#include "illixr/relative_clock.hpp" +#include "illixr/switchboard.hpp" +#include "illixr/threadloop.hpp" + + +namespace ILLIXR { + +class audio_xcoding : public threadloop { +public: + audio_xcoding(phonebook *pb_, bool encoding); + + void _p_thread_setup() override; + + skip_option _p_should_skip() override; + + void _p_one_iteration() override; + +private: + const std::shared_ptr<switchboard> switchboard_; + const std::shared_ptr<relative_clock> clock_; + switchboard::reader<data_format::pose_type> pose_; + ILLIXR::audio::ab_audio xcoder_; + time_point last_time_; + static constexpr duration audio_period_{ + freq_to_period(static_cast<double>(SAMPLERATE) / static_cast<double>(BLOCK_SIZE))}; + bool encoding_; +}; + +class audio_pipeline : public plugin { +public: + [[maybe_unused]] audio_pipeline(const std::string &name_, phonebook *pb_) + : plugin{name_, pb_}, audio_encoding{pb_, true}, audio_decoding{pb_, false} {} + + void start() override; + + void stop() override; + +private: + audio_xcoding audio_encoding; + audio_xcoding audio_decoding; +}; +} diff --git a/include/realtime.h b/include/realtime.hpp similarity index 86% rename from include/realtime.h rename to include/realtime.hpp index 1f31d40..73002b6 100644 --- a/include/realtime.h +++ b/include/realtime.hpp @@ -3,10 +3,7 @@ * * The realtime audio support requires the PortAudio library. */ - -#ifndef REALTIME_H -#define REALTIME_H - +#pragma once /* * illixr_rt_init * @@ -23,6 +20,4 @@ * This function is blocking and will not return until the audio source is exhausted. * Launch this in an independent thread! */ -void *illixr_rt_init(void *audioObj); - -#endif // REALTIME_H +void *illixr_rt_init(void *audio_obj); diff --git a/include/sound.h b/include/sound.h deleted file mode 100644 index 69797ab..0000000 --- a/include/sound.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#include <string> -#include <fstream> -#include <memory> -#include <spatialaudio/Ambisonics.h> - -constexpr std::size_t SAMPLERATE {48000U}; -constexpr std::size_t BLOCK_SIZE {512U}; -constexpr std::size_t NORDER {3U}; -constexpr std::size_t NUM_SRCS {16U}; - -#define NUM_CHANNELS (OrderToComponents(NORDER, true)) - -namespace ILLIXR_AUDIO{ - class Sound{ - public: - Sound(std::string srcFile, unsigned nOrder, bool b3D); - // set sound src position - void setSrcPos(const PolarPoint& pos); - // set sound amplitude scale - void setSrcAmp(float ampScale); - // read sound samples from mono 16bit WAV file and encode into ambisonics format - std::weak_ptr<CBFormat> readInBFormat(); - private: - // corresponding sound src file - std::fstream srcFile; - // sample buffer HARDCODE - float sample[BLOCK_SIZE]; - // ambisonics format sound buffer - std::shared_ptr<CBFormat> BFormat; - // ambisonics encoder, containing format info, position info, etc. - CAmbisonicEncoderDist BEncoder; - // ambisonics position - PolarPoint srcPos; - // amplitude scale to avoid clipping - float amp; - - // Abort failed configuration - void configAbort(const std::string_view& compName) const; - }; -} diff --git a/include/sound.hpp b/include/sound.hpp new file mode 100644 index 0000000..b02a185 --- /dev/null +++ b/include/sound.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include <fstream> +#include <memory> +#include <spatialaudio/Ambisonics.h> +#include <string> + +constexpr std::size_t SAMPLERATE {48000U}; +constexpr std::size_t BLOCK_SIZE {512U}; +constexpr std::size_t NORDER {3U}; +constexpr std::size_t NUM_SRCS {16U}; + +#define NUM_CHANNELS (OrderToComponents(NORDER, true)) + +namespace ILLIXR::audio{ +class sound{ +public: + sound(std::string src_filename, unsigned n_order, bool b3D); + + // set sound src position + void set_src_pos(const PolarPoint& pos); + + // set sound amplitude scale + [[maybe_unused]] void set_src_amp(float amp_scale); + + // read sound samples from mono 16bit WAV file and encode into ambisonics format + std::weak_ptr<CBFormat> read_in_b_format(); +private: + // Abort failed configuration + void config_abort(const std::string_view& comp_name) const; + + std::fstream src_file_; //!< corresponding sound src file + float sample_[BLOCK_SIZE]; //!< sample buffer HARDCODE + std::shared_ptr<CBFormat> b_format_; //!< ambisonics format sound buffer + CAmbisonicEncoderDist b_encoder_; //!< ambisonics encoder, containing format info, position info, etc. + PolarPoint src_pos_; //!< ambisonics position + float amp_; //!< amplitude scale to avoid clipping +}; +} diff --git a/libspatialaudio b/libspatialaudio deleted file mode 160000 index 77a901e..0000000 --- a/libspatialaudio +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 77a901e337a63aa981745ab369ccf597834a37a5 diff --git a/portaudio b/portaudio deleted file mode 160000 index 7e2a33c..0000000 --- a/portaudio +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7e2a33c875c6b2b53a8925959496cc698765621f diff --git a/src/audio.cpp b/src/audio.cpp index 93985b1..bd37d0c 100644 --- a/src/audio.cpp +++ b/src/audio.cpp @@ -1,79 +1,98 @@ +#include "audio.hpp" + +#ifdef ILLIXR_INTEGRATION +#include "illixr/error_util.hpp" +#include "illixr/switchboard.hpp" +#endif /// ILLIXR_INTEGRATION + #include <iostream> #include <cassert> #include <cstdlib> -#include "audio.h" #ifdef ILLIXR_INTEGRATION -#include "../common/error_util.hpp" +#include <filesystem> #endif /// ILLIXR_INTEGRATION -std::string get_path() { + #ifdef ILLIXR_INTEGRATION - const char* AUDIO_ROOT_c_str = std::getenv("AUDIO_ROOT"); - if (!AUDIO_ROOT_c_str) { - ILLIXR::abort("Please define AUDIO_ROOT"); - } - std::string AUDIO_ROOT = std::string{AUDIO_ROOT_c_str}; - return AUDIO_ROOT + "samples/"; +std::string get_path(const std::shared_ptr<ILLIXR::switchboard>& sb) { + if(sb) { + std::string path = std::string{AUDIO_SAMPLES} + "/samples"; + if (std::filesystem::is_directory(path)) + return path; + const char *AUDIO_ROOT = sb->get_env_char("AUDIO_ROOT"); + if (!AUDIO_ROOT) + throw std::runtime_error("Ausio samples not found, please define AUDIO_ROOT"); + + return std::string{AUDIO_ROOT} + "/samples/"; + } else { #else - return "samples/"; -#endif /// ILLIXR_INTEGRATION + std::string get_path() { +#endif + return "samples/"; +#ifdef ILLIXR_INTEGRATION + } +#endif } -ILLIXR_AUDIO::ABAudio::ABAudio(std::string outputFilePath, ProcessType procTypeIn) - : processType {procTypeIn} - , outputFile { - processType == ILLIXR_AUDIO::ABAudio::ProcessType::FULL - ? std::make_optional<std::ofstream>(outputFilePath, std::ios_base::out | std::ios_base::binary) +ILLIXR::audio::ab_audio::ab_audio(std::string output_file_path, process_type proc_type_in) + : process_type_ {proc_type_in} + , output_file_ { + process_type_ == ILLIXR::audio::ab_audio::process_type::FULL + ? std::make_optional<std::ofstream>(output_file_path, std::ios_base::out | std::ios_base::binary) : std::nullopt - } -{ - if (processType == ILLIXR_AUDIO::ABAudio::ProcessType::FULL) { - generateWAVHeader(); + } { + if (process_type_ == ILLIXR::audio::ab_audio::process_type::FULL) { + generate_wav_header(); } unsigned int tailLength {0U}; /// Binauralizer as ambisonics decoder - if (!decoder.Configure(NORDER, true, SAMPLERATE, BLOCK_SIZE, tailLength)) { - configAbort("decoder"); + if (!decoder_.Configure(NORDER, true, SAMPLERATE, BLOCK_SIZE, tailLength)) { + config_abort("decoder"); } /// Processor to rotate - if (!rotator.Configure(NORDER, true, BLOCK_SIZE, tailLength)) { - configAbort("rotator"); + if (!rotator_.Configure(NORDER, true, BLOCK_SIZE, tailLength)) { + config_abort("rotator"); } /// Processor to zoom - if (!zoomer.Configure(NORDER, true, tailLength)) { - configAbort("zoomer"); + if (!zoomer_.Configure(NORDER, true, tailLength)) { + config_abort("zoomer"); } buffer_ready = false; num_blocks_left = 0; } - -void ILLIXR_AUDIO::ABAudio::loadSource(){ -#ifndef NDEBUG +#ifdef ILLIXR_INTEGRATION +void ILLIXR::audio::ab_audio::load_source(const std::shared_ptr<ILLIXR::switchboard>& sb){ +#else +void ILLIXR::audio::ab_audio::loadSource(){ +#endif /// Temporarily clear errno here if set (until merged with #225) if (errno > 0) { errno = 0; } -#endif /// NDEBUG /// Add a bunch of sound sources +#ifdef ILLIXR_INTEGRATION + const std::string samples_folder{get_path(sb)}; +#else const std::string samples_folder{get_path()}; - if (processType == ILLIXR_AUDIO::ABAudio::ProcessType::FULL) { - soundSrcs.emplace_back(samples_folder + "lectureSample.wav", NORDER, true); - soundSrcs.back().setSrcPos({ +#endif + if (process_type_ == ILLIXR::audio::ab_audio::process_type::FULL) { + sound_srcs_.emplace_back(samples_folder + "lectureSample.wav", NORDER, true); + sound_srcs_.back().set_src_pos({ .fAzimuth = -0.1f, .fElevation = 3.14f/2, .fDistance = 1 }); - soundSrcs.emplace_back(samples_folder + "radioMusicSample.wav", NORDER, true); - soundSrcs.back().setSrcPos({ + sound_srcs_.emplace_back(samples_folder + "radioMusicSample.wav", NORDER, true); + sound_srcs_.back().set_src_pos({ .fAzimuth = 1.0f, .fElevation = 0.0f, .fDistance = 5 @@ -86,12 +105,12 @@ void ILLIXR_AUDIO::ABAudio::loadSource(){ /// it has not been set /// The path here is broken, we need to specify a relative path like we do in kimera assert(errno == 0); - soundSrcs.emplace_back(samples_folder + "lectureSample.wav", NORDER, true); + sound_srcs_.emplace_back(samples_folder + "lectureSample.wav", NORDER, true); assert(errno == 0); - soundSrcs.back().setSrcPos({ + sound_srcs_.back().set_src_pos({ .fAzimuth = i * -0.1f, - .fElevation = i * 3.14f/2, + .fElevation = i * 3.14f / 2, .fDistance = i * 1.0f }); } @@ -99,7 +118,7 @@ void ILLIXR_AUDIO::ABAudio::loadSource(){ } -void ILLIXR_AUDIO::ABAudio::processBlock() { +void ILLIXR::audio::ab_audio::process_block() { float** resultSample = new float*[2]; resultSample[0] = new float[BLOCK_SIZE]; resultSample[1] = new float[BLOCK_SIZE]; @@ -108,18 +127,18 @@ void ILLIXR_AUDIO::ABAudio::processBlock() { CBFormat sumBF; sumBF.Configure(NORDER, true, BLOCK_SIZE); - if (processType != ILLIXR_AUDIO::ABAudio::ProcessType::DECODE) { - readNEncode(sumBF); + if (process_type_ != ILLIXR::audio::ab_audio::process_type::DECODE) { + read_and_encode(sumBF); } - if (processType != ILLIXR_AUDIO::ABAudio::ProcessType::ENCODE) { + if (process_type_ != ILLIXR::audio::ab_audio::process_type::ENCODE) { /// Processing garbage data if just decoding - rotateNZoom(sumBF); - decoder.Process(&sumBF, resultSample); + rotate_and_zoom(sumBF); + decoder_.Process(&sumBF, resultSample); } - if (processType == ILLIXR_AUDIO::ABAudio::ProcessType::FULL) { - writeFile(resultSample); + if (process_type_ == ILLIXR::audio::ab_audio::process_type::FULL) { + write_file(resultSample); if (num_blocks_left > 0) { num_blocks_left--; } @@ -132,11 +151,11 @@ void ILLIXR_AUDIO::ABAudio::processBlock() { /// Read from WAV files and encode into ambisonics format -void ILLIXR_AUDIO::ABAudio::readNEncode(CBFormat& sumBF) { - for (unsigned int soundIdx = 0U; soundIdx < soundSrcs.size(); ++soundIdx) { +void ILLIXR::audio::ab_audio::read_and_encode(CBFormat& sumBF) { + for (unsigned int soundIdx = 0U; soundIdx < sound_srcs_.size(); ++soundIdx) { /// 'readInBFormat' now returns a weak_ptr, ensuring that we don't access /// or destruct a freed resource - std::weak_ptr<CBFormat> tempBF_weak {soundSrcs[soundIdx].readInBFormat()}; + std::weak_ptr<CBFormat> tempBF_weak {sound_srcs_[soundIdx].read_in_b_format()}; std::shared_ptr<CBFormat> tempBF{tempBF_weak.lock()}; if (tempBF != nullptr) { @@ -147,13 +166,13 @@ void ILLIXR_AUDIO::ABAudio::readNEncode(CBFormat& sumBF) { } } else { static constexpr std::string_view read_fail_msg{ - "[ABAudio] Failed to read/encode. Sound has expired or been destroyed." + "[ab_audio] Failed to read/encode. Sound has expired or been destroyed." }; #ifdef ILLIXR_INTEGRATION - ILLIXR::abort(std::string{read_fail_msg}); + throw std::runtime_error(std::string{read_fail_msg}); #else std::cerr << read_fail_msg << std::endl; - std::abort(); + throw std::runtime_error(std::string{read_fail_msg}); #endif /// ILLIXR_INTEGRATION } } @@ -161,49 +180,49 @@ void ILLIXR_AUDIO::ABAudio::readNEncode(CBFormat& sumBF) { /// Simple rotation -void ILLIXR_AUDIO::ABAudio::updateRotation() { - frame++; - Orientation head(0,0,1.0*frame/1500*3.14*2); - rotator.SetOrientation(head); - rotator.Refresh(); +void ILLIXR::audio::ab_audio::update_rotation() { + frame_++; + Orientation head(0, 0, 1.0 * frame_ / 1500 * 3.14 * 2); + rotator_.SetOrientation(head); + rotator_.Refresh(); } /// Simple zoom -void ILLIXR_AUDIO::ABAudio::updateZoom() { - frame++; - zoomer.SetZoom(sinf(frame/100)); - zoomer.Refresh(); +void ILLIXR::audio::ab_audio::update_zoom() { + frame_++; + zoomer_.SetZoom(sinf(frame_/100)); + zoomer_.Refresh(); } /// Process some rotation and zoom effects -void ILLIXR_AUDIO::ABAudio::rotateNZoom(CBFormat& sumBF) { - updateRotation(); - rotator.Process(&sumBF, BLOCK_SIZE); - updateZoom(); - zoomer.Process(&sumBF, BLOCK_SIZE); +void ILLIXR::audio::ab_audio::rotate_and_zoom(CBFormat& sumBF) { + update_rotation(); + rotator_.Process(&sumBF, BLOCK_SIZE); + update_zoom(); + zoomer_.Process(&sumBF, BLOCK_SIZE); } -void ILLIXR_AUDIO::ABAudio::writeFile(float** resultSample) { +void ILLIXR::audio::ab_audio::write_file(float** resultSample) { /// Normalize(Clipping), then write into file for (std::size_t sampleIdx = 0U; sampleIdx < BLOCK_SIZE; ++sampleIdx) { resultSample[0][sampleIdx] = std::max(std::min(resultSample[0][sampleIdx], +1.0f), -1.0f); resultSample[1][sampleIdx] = std::max(std::min(resultSample[1][sampleIdx], +1.0f), -1.0f); int16_t tempSample0 = (int16_t)(resultSample[0][sampleIdx]/1.0 * 32767); int16_t tempSample1 = (int16_t)(resultSample[1][sampleIdx]/1.0 * 32767); - outputFile->write((char*)&tempSample0,sizeof(short)); - outputFile->write((char*)&tempSample1,sizeof(short)); + output_file_->write((char*)&tempSample0,sizeof(short)); + output_file_->write((char*)&tempSample1,sizeof(short)); /// Cache written block in object buffer until needed by realtime audio thread - mostRecentBlockL[sampleIdx] = tempSample0; - mostRecentBlockR[sampleIdx] = tempSample1; + most_recent_block_L[sampleIdx] = tempSample0; + most_recent_block_R[sampleIdx] = tempSample1; } } -namespace ILLIXR_AUDIO +namespace ILLIXR::audio { /// NOTE: WAV FILE SIZE is not correct typedef struct __attribute__ ((packed)) WAVHeader_t @@ -225,20 +244,18 @@ namespace ILLIXR_AUDIO } -void ILLIXR_AUDIO::ABAudio::generateWAVHeader() { +void ILLIXR::audio::ab_audio::generate_wav_header() { /// Brute force wav header WAVHeader wavh; - outputFile->write((char*)&wavh, sizeof(WAVHeader)); + output_file_->write((char*)&wavh, sizeof(WAVHeader)); } -void ILLIXR_AUDIO::ABAudio::configAbort(const std::string_view& compName) const +void ILLIXR::audio::ab_audio::config_abort(const std::string_view& comp_name) const { static constexpr std::string_view cfg_fail_msg{"[ABAudio] Failed to configure "}; -#ifdef ILLIXR_INTEGRATION - ILLIXR::abort(std::string{cfg_fail_msg} + std::string{compName}); -#else +#ifndef ILLIXR_INTEGRATION std::cerr << cfg_fail_msg << compName << std::endl; - std::abort(); #endif /// ILLIXR_INTEGRATION + throw std::runtime_error(std::string{cfg_fail_msg} + std::string{comp_name}); } diff --git a/src/audio_component.cpp b/src/audio_component.cpp deleted file mode 100644 index 78e34cf..0000000 --- a/src/audio_component.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include <future> -#include <thread> - -#include "common/data_format.hpp" -#include "common/phonebook.hpp" -#include "common/relative_clock.hpp" -#include "common/switchboard.hpp" -#include "common/threadloop.hpp" - -#include <audio.h> - -using namespace ILLIXR; - -class audio_xcoding : public threadloop -{ -public: - audio_xcoding(phonebook *pb_, bool encoding) - : threadloop{encoding ? "audio_encoding" : "audio_decoding", pb_} - , _m_sb{pb->lookup_impl<switchboard>()} - , _m_clock{pb->lookup_impl<RelativeClock>()} - , _m_pose{_m_sb->get_reader<pose_type>("slow_pose")} - , xcoder{"", encoding ? ILLIXR_AUDIO::ABAudio::ProcessType::ENCODE : ILLIXR_AUDIO::ABAudio::ProcessType::DECODE} - , encoding_{encoding} - { - xcoder.loadSource(); - } - - virtual void _p_thread_setup() override { - last_time = _m_clock->now(); - } - - virtual skip_option _p_should_skip() override { - last_time += audio_period; - std::this_thread::sleep_for(last_time - _m_clock->now()); - return skip_option::run; - } - - virtual void _p_one_iteration() override { - if (!encoding_) { - [[maybe_unused]] auto most_recent_pose = _m_pose.get_ro_nullable(); - } - xcoder.processBlock(); - } - -private: - const std::shared_ptr<switchboard> _m_sb; - const std::shared_ptr<RelativeClock> _m_clock; - switchboard::reader<pose_type> _m_pose; - ILLIXR_AUDIO::ABAudio xcoder; - time_point last_time; - static constexpr duration audio_period{freq2period(static_cast<double>(SAMPLERATE) / static_cast<double>(BLOCK_SIZE))}; - bool encoding_; -}; - -class audio_pipeline : public plugin { -public: - audio_pipeline(std::string name_, phonebook *pb_) - : plugin{name_, pb_} - , audio_encoding{pb_, true } - , audio_decoding{pb_, false} - { } - - virtual void start() override { - audio_encoding.start(); - audio_decoding.start(); - plugin::start(); - } - - virtual void stop() override { - audio_encoding.stop(); - audio_decoding.stop(); - plugin::stop(); - } - -private: - audio_xcoding audio_encoding; - audio_xcoding audio_decoding; -}; - -PLUGIN_MAIN(audio_pipeline) diff --git a/src/main.cpp b/src/main.cpp index 3318bf8..1dce50f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,14 @@ -#include <audio.h> +#include "illixr/switchboard.hpp" + +#include "audio.hpp" +#include "realtime.hpp" + #include <iostream> -#include <realtime.h> #include <pthread.h> int main(int argc, char const *argv[]) { - using namespace ILLIXR_AUDIO; + using namespace ILLIXR::audio; if (argc < 2) { std::cout << "Usage: " << argv[0] << " <number of size " << BLOCK_SIZE << " blocks to process> "; @@ -15,27 +18,27 @@ int main(int argc, char const *argv[]) } const int numBlocks = atoi(argv[1]); - ABAudio::ProcessType procType(ABAudio::ProcessType::FULL); + ab_audio::process_type procType(ab_audio::process_type::FULL); if (argc > 2){ if (!strcmp(argv[2], "encode")) - procType = ABAudio::ProcessType::ENCODE; + procType = ab_audio::process_type::ENCODE; else - procType = ABAudio::ProcessType::DECODE; + procType = ab_audio::process_type::DECODE; } - ABAudio audio("output.wav", procType); - audio.loadSource(); + ab_audio audio("output.wav", procType); + audio.load_source(std::make_shared<ILLIXR::switchboard>(nullptr)); audio.num_blocks_left = numBlocks; // Launch realtime audio thread for audio processing - if (procType == ABAudio::ProcessType::FULL) { + if (procType == ab_audio::process_type::FULL) { pthread_t rt_audio_thread; - pthread_create(&rt_audio_thread, NULL, illixr_rt_init, (void *)&audio); - pthread_join(rt_audio_thread, NULL); + pthread_create(&rt_audio_thread, nullptr, illixr_rt_init, (void *)&audio); + pthread_join(rt_audio_thread, nullptr); } else { for (int i = 0; i < numBlocks; ++i) { - audio.processBlock(); + audio.process_block(); } } diff --git a/src/plugin.cpp b/src/plugin.cpp new file mode 100644 index 0000000..d952ec4 --- /dev/null +++ b/src/plugin.cpp @@ -0,0 +1,51 @@ +#include "../include/plugin.hpp" + +#include <future> +#include <thread> + + +using namespace ILLIXR; + + +audio_xcoding::audio_xcoding(phonebook *pb_, bool encoding) + : threadloop{encoding ? "audio_encoding" : "audio_decoding", pb_}, + switchboard_{pb_->lookup_impl<switchboard>()}, clock_{pb_->lookup_impl<relative_clock>()}, + pose_{switchboard_->get_reader<data_format::pose_type>("slow_pose")}, xcoder_{"", encoding + ? ILLIXR::audio::ab_audio::process_type::ENCODE + : ILLIXR::audio::ab_audio::process_type::DECODE}, + encoding_{encoding} { + xcoder_.load_source(switchboard_); +} + +void audio_xcoding::_p_thread_setup() { + last_time_ = clock_->now(); +} + +threadloop::skip_option audio_xcoding::_p_should_skip() { + last_time_ += audio_period_; + std::this_thread::sleep_for(last_time_ - clock_->now()); + return skip_option::run; +} + +void audio_xcoding::_p_one_iteration() { + if (!encoding_) { + [[maybe_unused]] auto most_recent_pose = pose_.get_ro_nullable(); + } + xcoder_.process_block(); +} + + +void audio_pipeline::start() { + audio_encoding.start(); + audio_decoding.start(); + plugin::start(); +} + +void audio_pipeline::stop() { + audio_encoding.stop(); + audio_decoding.stop(); + plugin::stop(); +} + + +PLUGIN_MAIN(audio_pipeline) diff --git a/src/realtime.cpp b/src/realtime.cpp index be29188..59c84f5 100644 --- a/src/realtime.cpp +++ b/src/realtime.cpp @@ -1,16 +1,16 @@ -#include <realtime.h> -#include <stdio.h> -#include <math.h> -#include <sound.h> -#include <audio.h> -#include "portaudio.h" +#include "sound.hpp" +#include "audio.hpp" + +#include <cstdio> +#include <portaudio.h> +#include <realtime.hpp> #define SAMPLE_RATE ((SAMPLERATE)) #define AUDIO_FORMAT paInt16 typedef unsigned short sample_t; #define SILENCE ((sample_t)0x00) -using namespace ILLIXR_AUDIO; +using namespace ILLIXR::audio; /* * illixr_rt_cb @@ -20,25 +20,27 @@ using namespace ILLIXR_AUDIO; * When the realtime audio driver requires more buffered data, this callback * method is called. This is where ILLIXR feeds more audio data to the realtime audio engine. */ -static int illixr_rt_cb( const void *inputBuffer, void *outputBuffer, - unsigned long framesPerBuffer, - const PaStreamCallbackTimeInfo* timeInfo, - PaStreamCallbackFlags statusFlags, - void *userData ) { - - sample_t *out = (sample_t*)outputBuffer; - ABAudio *audioObj = (ABAudio *)userData; +static int illixr_rt_cb(const void *input_buffer, void *output_buffer, + unsigned long frames_per_buffer, + const PaStreamCallbackTimeInfo* time_info, + PaStreamCallbackFlags status_flags, + void *user_data ) { + (void) frames_per_buffer; + (void) time_info; + (void) status_flags; + auto *out = (sample_t*)output_buffer; + auto *audio_obj = (ab_audio *)user_data; int i; - (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) input_buffer; /* Prevent unused variable warnings. */ // As this is an interrupt context, lots of data processing is ill-advised. // In future iterations, all processing will be handled in a separate thread with proper // synchronization primatives. - audioObj->processBlock(); + audio_obj->process_block(); for (i = 0; i < BLOCK_SIZE; i++) { - *out++ = audioObj->mostRecentBlockL[i]; - *out++ = audioObj->mostRecentBlockR[i]; + *out++ = audio_obj->most_recent_block_L[i]; + *out++ = audio_obj->most_recent_block_R[i]; } return 0; @@ -46,6 +48,15 @@ static int illixr_rt_cb( const void *inputBuffer, void *outputBuffer, /*******************************************************************/ +void* print_err(const PaError err) { + Pa_Terminate(); + fprintf( stderr, "An error occured while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return (void *)err; + +} + /* * illixr_rt_init * @@ -62,64 +73,56 @@ static int illixr_rt_cb( const void *inputBuffer, void *outputBuffer, * This function is blocking and will not return until the audio source is exhausted. * Launch this in an independent thread! */ -void *illixr_rt_init(void *audioObj) -{ - PaStreamParameters outputParameters; +void *illixr_rt_init(void *audio_obj) { + PaStreamParameters output_parameters; PaStream* stream; PaError err; - PaTime streamOpened; - int i, totalSamps; + PaTime stream_opened; printf("Initializing audio hardware...\n"); err = Pa_Initialize(); if( err != paNoError ) - goto error; + return print_err(err); - outputParameters.device = Pa_GetDefaultOutputDevice(); /* Default output device. */ - if (outputParameters.device == paNoDevice) { - fprintf(stderr,"Error: No default output device.\n"); - goto error; + output_parameters.device = Pa_GetDefaultOutputDevice(); /* Default output device. */ + if (output_parameters.device == paNoDevice) { + fprintf(stderr,"Error: No default output device.\n"); + return print_err(err); } - outputParameters.channelCount = 2; /* Stereo output. */ - outputParameters.sampleFormat = AUDIO_FORMAT; - outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; - outputParameters.hostApiSpecificStreamInfo = NULL; + output_parameters.channelCount = 2; /* Stereo output. */ + output_parameters.sampleFormat = AUDIO_FORMAT; + output_parameters.suggestedLatency = Pa_GetDeviceInfo( output_parameters.device )->defaultLowOutputLatency; + output_parameters.hostApiSpecificStreamInfo = NULL; err = Pa_OpenStream( &stream, NULL, /* No input. */ - &outputParameters, + &output_parameters, SAMPLE_RATE, BLOCK_SIZE, /* Frames per buffer. */ paClipOff, /* We won't output out of range samples so don't bother clipping them. */ illixr_rt_cb, - audioObj ); + audio_obj); if( err != paNoError ) - goto error; + return print_err(err); - streamOpened = Pa_GetStreamTime( stream ); /* Time in seconds when stream was opened (approx). */ + stream_opened = Pa_GetStreamTime( stream ); /* Time in seconds when stream was opened (approx). */ printf("Launching stream!\n"); err = Pa_StartStream( stream ); if( err != paNoError ) - goto error; + return print_err(err); // Spin until the audio marks itself as being complete - while( ((ABAudio *)audioObj)->num_blocks_left > 0) { - Pa_Sleep(0.25f); + while( ((ab_audio *)audio_obj)->num_blocks_left > 0) { + Pa_Sleep(25); } printf("Stopping stream\n"); err = Pa_CloseStream( stream ); if( err != paNoError ) - goto error; + return print_err(err); Pa_Terminate(); return (void *)err; -error: - Pa_Terminate(); - fprintf( stderr, "An error occured while using the portaudio stream\n" ); - fprintf( stderr, "Error number: %d\n", err ); - fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); - return (void *)err; } diff --git a/src/sound.cpp b/src/sound.cpp index f8bf196..c3b3aab 100644 --- a/src/sound.cpp +++ b/src/sound.cpp @@ -1,46 +1,42 @@ -#include <algorithm> -#include <cassert> -#include <cstddef> -#include <cstdlib> -#include "sound.h" +#include "sound.hpp" #ifdef ILLIXR_INTEGRATION -#include "../common/error_util.hpp" +#include "illixr/error_util.hpp" #endif /// ILLIXR_INTEGRATION +#include <cassert> +#include <cstddef> -ILLIXR_AUDIO::Sound::Sound( - std::string srcFilename, - [[maybe_unused]] unsigned int nOrder, - [[maybe_unused]] bool b3D -) : srcFile{srcFilename, std::fstream::in} - , BFormat{std::make_shared<CBFormat>()} - , amp{1.0} -{ + +ILLIXR::audio::sound::sound(std::string src_filename, unsigned int n_order, bool b3D) + : src_file_{src_filename, std::fstream::in} + , b_format_{std::make_shared<CBFormat>()} + , amp_{1.0} { + (void) b3D; /// NOTE: This is currently only accepts mono channel 16-bit depth WAV file /// TODO: Change brutal read from wav file constexpr std::size_t SRC_FILE_SIZE {44U}; std::byte temp[SRC_FILE_SIZE]; - srcFile.read(reinterpret_cast<char*>(temp), sizeof(temp)); + src_file_.read(reinterpret_cast<char*>(temp), sizeof(temp)); /// BFormat file initialization - if (!BFormat->Configure(nOrder, true, BLOCK_SIZE)) { - configAbort("BFormat"); + if (!b_format_->Configure(n_order, true, BLOCK_SIZE)) { + config_abort("BFormat"); } - BFormat->Refresh(); + b_format_->Refresh(); /// Encoder initialization - if (!BEncoder.Configure(nOrder, true, SAMPLERATE)) { - configAbort("BEncoder"); + if (!b_encoder_.Configure(n_order, true, SAMPLERATE)) { + config_abort("b_encoder_"); } - BEncoder.Refresh(); + b_encoder_.Refresh(); - srcPos.fAzimuth = 0.0f; - srcPos.fElevation = 0.0f; - srcPos.fDistance = 0.0f; + src_pos_.fAzimuth = 0.0f; + src_pos_.fElevation = 0.0f; + src_pos_.fDistance = 0.0f; - BEncoder.SetPosition(srcPos); - BEncoder.Refresh(); + b_encoder_.SetPosition(src_pos_); + b_encoder_.Refresh(); /// Clear errno, as this constructor is setting the flag (with value 2) /// A temporary fix. @@ -48,43 +44,41 @@ ILLIXR_AUDIO::Sound::Sound( } -void ILLIXR_AUDIO::Sound::setSrcPos(const PolarPoint& pos) { - srcPos.fAzimuth = pos.fAzimuth; - srcPos.fElevation = pos.fElevation; - srcPos.fDistance = pos.fDistance; +void ILLIXR::audio::sound::set_src_pos(const PolarPoint& pos) { + src_pos_.fAzimuth = pos.fAzimuth; + src_pos_.fElevation = pos.fElevation; + src_pos_.fDistance = pos.fDistance; - BEncoder.SetPosition(srcPos); - BEncoder.Refresh(); + b_encoder_.SetPosition(src_pos_); + b_encoder_.Refresh(); } -void ILLIXR_AUDIO::Sound::setSrcAmp(float ampScale) { - amp = ampScale; +[[maybe_unused]] void ILLIXR::audio::sound::set_src_amp(float amp_scale) { + amp_ = amp_scale; } /// TODO: Change brutal read from wav file -std::weak_ptr<CBFormat> ILLIXR_AUDIO::Sound::readInBFormat() { +std::weak_ptr<CBFormat> ILLIXR::audio::sound::read_in_b_format() { float sampleTemp[BLOCK_SIZE]; - srcFile.read((char*)sampleTemp, BLOCK_SIZE * sizeof(short)); + src_file_.read((char*)sampleTemp, BLOCK_SIZE * sizeof(short)); /// Normalize samples to -1 to 1 float, with amplitude scale constexpr float SAMPLE_DIV {(2 << 14) - 1}; /// 32767.0f == 2^14 - 1 for (std::size_t i = 0U; i < BLOCK_SIZE; ++i) { - sample[i] = amp * (sampleTemp[i] / SAMPLE_DIV); + sample_[i] = amp_ * (sampleTemp[i] / SAMPLE_DIV); } - BEncoder.Process(sample, BLOCK_SIZE, BFormat.get()); - return BFormat; + b_encoder_.Process(sample_, BLOCK_SIZE, b_format_.get()); + return b_format_; } -void ILLIXR_AUDIO::Sound::configAbort(const std::string_view& compName) const +void ILLIXR::audio::sound::config_abort(const std::string_view& comp_name) const { static constexpr std::string_view cfg_fail_msg{"[Sound] Failed to configure "}; -#ifdef ILLIXR_INTEGRATION - ILLIXR::abort(std::string{cfg_fail_msg} + std::string{compName}); -#else +#ifndef ILLIXR_INTEGRATION std::cerr << cfg_fail_msg << compName << std::endl; - std::abort(); #endif /// ILLIXR_INTEGRATION + throw std::runtime_error(std::string{cfg_fail_msg} + std::string{comp_name}); }