From bbd19fc2a84ae6c5f40118bbb506169e6b14b6b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wiesner=20Andr=C3=A1s?= Date: Thu, 4 Nov 2021 09:46:03 +0100 Subject: [PATCH] some part are working --- .idea/.gitignore | 8 ++ .idea/misc.xml | 4 + .idea/modules.xml | 8 ++ .idea/wfr.iml | 2 + CMakeLists.txt | 21 +++++ MATLAB/read_syncdata.m | 39 ++++++++ MATLAB/readdump.m | 5 ++ main.cpp | 197 +++++++++++++++++++++++++++++++++++++++++ src/ALSADevice.cpp | 177 ++++++++++++++++++++++++++++++++++++ src/ALSADevice.h | 38 ++++++++ src/MemoryPool.cpp | 6 ++ src/MemoryPool.h | 86 ++++++++++++++++++ src/SampleReceiver.h | 41 +++++++++ src/SampleWriter.h | 195 ++++++++++++++++++++++++++++++++++++++++ src/Timestamp.h | 112 +++++++++++++++++++++++ src/audio_types.h | 22 +++++ utils/Semaphore.cpp | 25 ++++++ utils/Semaphore.h | 26 ++++++ 18 files changed, 1012 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/wfr.iml create mode 100644 CMakeLists.txt create mode 100644 MATLAB/read_syncdata.m create mode 100644 MATLAB/readdump.m create mode 100644 main.cpp create mode 100644 src/ALSADevice.cpp create mode 100644 src/ALSADevice.h create mode 100644 src/MemoryPool.cpp create mode 100644 src/MemoryPool.h create mode 100644 src/SampleReceiver.h create mode 100644 src/SampleWriter.h create mode 100644 src/Timestamp.h create mode 100644 src/audio_types.h create mode 100644 utils/Semaphore.cpp create mode 100644 utils/Semaphore.h diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..79b3c94 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..d7dd859 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/wfr.iml b/.idea/wfr.iml new file mode 100644 index 0000000..f08604b --- /dev/null +++ b/.idea/wfr.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0f3430a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.20) + +SET(APP_NAME wfr) + +project(${APP_NAME}) +set(CMAKE_CXX_STANDARD 14) + +add_executable(${APP_NAME} main.cpp src/ALSADevice.cpp src/ALSADevice.h src/MemoryPool.cpp src/MemoryPool.h utils/Semaphore.h utils/Semaphore.cpp src/SampleWriter.h src/Timestamp.h + #src/SampleReceiver.h + src/audio_types.h) + +find_package(Threads REQUIRED) +if (THREADS_FOUND) + target_link_libraries(${APP_NAME} Threads::Threads) +endif (THREADS_FOUND) + +find_package(ALSA REQUIRED) +if (ALSA_FOUND) + include_directories(${ALSA_INCLUDE_DIRS}) + target_link_libraries(${APP_NAME} ${ALSA_LIBRARIES}) +endif (ALSA_FOUND) \ No newline at end of file diff --git a/MATLAB/read_syncdata.m b/MATLAB/read_syncdata.m new file mode 100644 index 0000000..4f3bf44 --- /dev/null +++ b/MATLAB/read_syncdata.m @@ -0,0 +1,39 @@ +% settings +DATASET_DIR = 'test'; +TS_FILE = 'ts'; +SAMPLE_FILE_PREFIX = 'ch_'; +FILE_EXT = 'dat'; +CHANNELS = 2; + +% computed values +FN_TS = strcat(DATASET_DIR, '/', TS_FILE, '.', FILE_EXT); + +for ch=0:1:(CHANNELS-1) + FN_SAMPLE{ch+1} = strcat(DATASET_DIR, '/', SAMPLE_FILE_PREFIX, num2str(ch), '.', FILE_EXT); +end + + +% load timestamps +f = fopen(FN_TS ,'r'); +ts = fread(f,[2,Inf],'uint32'); +fclose(f); + +% take first element as t0 +ts(1,:) = ts(1,:) - ts(1,1); + +% create fractional timestamp +ts = ts(1,:) + ts(2,:) / 1E+09; + +% get sample count +sample_cnt = length(ts); + +% load samples +s = zeros(CHANNELS, sample_cnt); +for ch=1:CHANNELS + f = fopen(FN_SAMPLE{ch},'r'); + d = fread(f, [1,Inf],'int16'); + s(ch,:) = d; + fclose(f); +end + +plot(ts(1,1:100), s(1,1:100)) \ No newline at end of file diff --git a/MATLAB/readdump.m b/MATLAB/readdump.m new file mode 100644 index 0000000..89a4ffd --- /dev/null +++ b/MATLAB/readdump.m @@ -0,0 +1,5 @@ +f = fopen('wf.dat','r'); +d = fread(f,[2, 44100],'int16'); +s = d(1,1:1000); +plot(s); +fclose(f); \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..8b58bb3 --- /dev/null +++ b/main.cpp @@ -0,0 +1,197 @@ +#include + +/* Use the newer ALSA API */ +#define ALSA_PCM_NEW_HW_PARAMS_API + +#include + +#include +#include +#include +#include "src/ALSADevice.h" +#include "src/SampleWriter.h" +#include "src/audio_types.h" + +//int main() { +// long loops; +// int rc; +// int size; +// snd_pcm_t *handle; +// snd_pcm_hw_params_t *params; +// unsigned int val; +// int dir; +// snd_pcm_uframes_t frames; +// +// /* Open PCM device for playback. */ +// rc = snd_pcm_open(&handle, "default",SND_PCM_STREAM_PLAYBACK, 0); +// if (rc < 0) { +// fprintf(stderr,"unable to open pcm device: %s\n", snd_strerror(rc)); +// exit(1); +// } +// +// /* Allocate a hardware parameters object. */ +// snd_pcm_hw_params_alloca(¶ms); +// +// /* Fill it in with default values. */ +// snd_pcm_hw_params_any(handle, params); +// +// /* Set the desired hardware parameters. */ +// +// /* Interleaved mode */ +// snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED); +// +// /* Signed 16-bit little-endian format */ +// snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE); +// +// /* Two channels (stereo) */ +// snd_pcm_hw_params_set_channels(handle, params, 2); +// +// /* 44100 bits/second sampling rate (CD quality) */ +// unsigned sampleRate = 44100; +// val = sampleRate; +// snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir); +// +// /* Set period size to 32 frames. */ +// frames = 32; +// snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir); +// +// /* Write the parameters to the driver */ +// rc = snd_pcm_hw_params(handle, params); +// if (rc < 0) { +// fprintf(stderr,"unable to set hw parameters: %s\n", snd_strerror(rc)); +// exit(1); +// } +// +// /* Use a buffer large enough to hold one period */ +// snd_pcm_hw_params_get_period_size(params, &frames, &dir); +// size = frames * 4; /* 2 bytes/sample, 2 channels */ +// int16_t buffer[frames * 2]; +// +// /* We want to loop for 5 seconds */ +// snd_pcm_hw_params_get_period_time(params, &val, &dir); +// /* 5 seconds in microseconds divided by +// * period time */ +// +// loops = 2 * 1000000 / val; +// +// float f = 440; +// float df = 2 * M_PI * f / sampleRate; +// float amplitude = 0.01; +// float phase = 0; +// +// //FILE* wf = fopen("wf.dat", "wb"); +// +// for (size_t l = 0; l < loops; l++) { +// for (size_t i = 0; i < 2 * frames; i += 2) { +// buffer[i] = 32767 * amplitude * sin(phase); +// buffer[i+1] = buffer[i]; +// phase += df; +// } +// +// //fwrite(buffer, frames * 4, 1, wf); +// +// if (phase > 2 * M_PI) { +// phase -= 2 * M_PI; +// } +// +// std::cout << std::to_string(snd_pcm_avail(handle)) << std::endl; +// +// rc = snd_pcm_writei(handle, buffer, frames); +// if (rc == -EPIPE) { +// /* EPIPE means underrun */ +// fprintf(stderr, "underrun occurred\n"); +// snd_pcm_prepare(handle); +// } else if (rc < 0) { +// fprintf(stderr, "error from writei: %s\n", snd_strerror(rc)); +// } else if (rc != (int) frames) { +// fprintf(stderr, "short write, write %d frames\n", rc); +// } +// } +// +// //fclose(wf); +// +// snd_pcm_drain(handle); +// snd_pcm_close(handle); +// //free(buffer); +// +// return 0; +//} + +#define Fs (48000) // sampling frequency +#define F (440) // signal frequency +#define K (0.1) // amplitude + +void generate_sine(int16_t *pBuf, uint32_t n) { + double y; + static double phase = 0, dt = 1.0 / Fs; + + uint32_t i = 0; + for (i = 0; i < n; i++) { + y = K * 0.5 * (1 + sin(phase)); + phase += 2 * M_PI * dt * F; + + if (phase > (2 * M_PI)) { + phase -= 2 * M_PI; + } + + pBuf[2 * i + 1] = pBuf[2 * i] = ((int16_t) (y * 0x7FFF)); + } + +} + +uint8_t pRecvBuf[8096] __attribute__ ((aligned (32))); + +int main(int argc, char * argv[]) { + ALSADevice dev(PERIOD_LEN, SAMPLE_RATE); + MemoryPool pool(STEREO_BUF_LEN, 1000); + //SampleWriter sw("test", 2, STEREO_BUF_LEN / 2); + + //std::this_thread::sleep_for(std::chrono::seconds(1)); + + /*for (size_t i = 0; i < 1000; i++) { + auto p = pool.alloc(); + generate_sine(p->pBlock, 324); + dev.write(p); + //std::cout << pool.avail() << std::endl; + } + + while (true) { + std::this_thread::sleep_for(std::chrono::seconds(1)); + }*/ + + int soc = socket(AF_INET, SOCK_DGRAM, 0); + + int opt = 1; + setsockopt(soc, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); + + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(atoi(argv[1])); + + bind(soc, (const sockaddr *)&addr, sizeof(addr)); + + AudioPayload * pAudioPayload = reinterpret_cast(pRecvBuf); + + while (true) { + //while (sw.getSampleCnt() < 100000) { + auto p = pool.alloc(); + + recv(soc, pRecvBuf, 8096, 0); + + //std::cout << pAudioPayload->timestamp_s << ' ' << pAudioPayload->timestamp_ns << std::endl; + + Timestamp ts = { pAudioPayload->timestamp_s, pAudioPayload->timestamp_ns }; + //sw.addSamples(pAudioPayload->pData, ts); + + memcpy(p->pBlock, pAudioPayload->pData, STEREO_BUF_LEN * sizeof(AudioSampleType)); + + dev.write(p); + + //std::cout << pool.avail() << std::endl; + } + + close(soc); + + return 0; +} \ No newline at end of file diff --git a/src/ALSADevice.cpp b/src/ALSADevice.cpp new file mode 100644 index 0000000..2a7d82f --- /dev/null +++ b/src/ALSADevice.cpp @@ -0,0 +1,177 @@ +// +// Created by epagris on 2021. 09. 20.. +// + +#include +#include +#include "ALSADevice.h" + +ALSADevice::ALSADevice(snd_pcm_uframes_t periodLen, unsigned sampleRate) { + mPeriodLen = periodLen; + mSampleRate = sampleRate; + openDevice(); +} + +ALSADevice::~ALSADevice() { + +} + +void ALSADevice::openDevice() { + /* Open PCM device for playback. */ + int ret; + ret = snd_pcm_open(&pmHandle, "default", SND_PCM_STREAM_PLAYBACK, 0); + if (ret < 0) { + fprintf(stderr, "Unable to open pcm device: %s\n", snd_strerror(ret)); + exit(1); + } + + /* Allocate a hardware parameters object. */ + snd_pcm_hw_params_alloca(&pmHwParams); + + /* Fill it in with default values. */ + snd_pcm_hw_params_any(pmHandle, pmHwParams); + + /* Interleaved mode */ + snd_pcm_hw_params_set_access(pmHandle, pmHwParams, SND_PCM_ACCESS_RW_INTERLEAVED); + + /* Signed 16-bit little-endian format */ + snd_pcm_hw_params_set_format(pmHandle, pmHwParams, SND_PCM_FORMAT_S16_LE); + + /* Two channels (stereo) */ + snd_pcm_hw_params_set_channels(pmHandle, pmHwParams, 2); + + /* Set sampling rate */ + int dir; + snd_pcm_hw_params_set_rate_near(pmHandle, pmHwParams, &mSampleRate, &dir); + + /* Set period size to mPeriodLen frames. */ + snd_pcm_hw_params_set_period_size_near(pmHandle, pmHwParams, &mPeriodLen, &dir); + + /* Write the parameters to the driver */ + ret = snd_pcm_hw_params(pmHandle, pmHwParams); + if (ret < 0) { + fprintf(stderr, "unable to set hw parameters: %s\n", snd_strerror(ret)); + exit(1); + } + + /* Use a buffer large enough to hold one period */ + snd_pcm_hw_params_get_period_size(pmHwParams, &mPeriodLen, &dir); + + /* Allocate sample buffer */ + //pmPeriodBufLeft = std::shared_ptr(new int16_t[mPeriodLen]); + //pmPeriodBufRight = std::shared_ptr(new int16_t[mPeriodLen]); + + /* Get buffer duration */ + snd_pcm_hw_params_get_period_time(pmHwParams, &mPeriodDuration_us, &dir); + + /* Start write thread */ + mRunning = true; + pmWriteThread = std::make_shared(fn_WriteThread, this); +} + +void ALSADevice::fn_WriteThread(ALSADevice *pDev) { + //int16_t pPeriodIntvBuf[pDev->mPeriodLen * 2]; // interlaved channel buffer + int16_t * pPlayBuf; // buffer to be passed to write routine + std::shared_ptr> pChunk; // memory chunk storing interleaved sample data + + int16_t pSilence[pDev->mPeriodLen * 2]; // buffer storing a single period-long silence + snd_pcm_format_set_silence(SND_PCM_FORMAT_S16_LE, pSilence, pDev->mPeriodLen * 2); // create silence + + snd_pcm_start(pDev->pmHandle); + + unsigned long long iterCnt = 0; + bool playbackStopped = true; // indicates wheter playback has been stopped due to frame dropoupts + size_t startThreshold = 10; // amount of frames available to play to start writing to decive + size_t optimalFillLevel = 6; // optimal amount of frames to be stored in the buffer + size_t minimalFramesStored = 3; // minimal amount of frames stored in queue to start playing + + while (pDev->mRunning) { + //snd_pcm_sframes_t avail = snd_pcm_avail(pDev->pmHandle); // get available frames on the hardware + + //pDev->mQMtx.lock(); + + // wait for data to arrive + pDev->mPlaySem.wait(); + + // start only if sufficient amount of frames are available + if (pDev->mChunkQ.size() < startThreshold && playbackStopped) { + continue; + } else { + playbackStopped = false; + } + + //pDev->mQMtx.unlock(); + + bool firstWriteIteration = true; // első iteráció a lejátszásra + + //std::cout << pDev->mChunkQ.size() << std::endl; + + // while the input queue contains at least a single chunk of sound data... + while (pDev->mChunkQ.size() > optimalFillLevel || (firstWriteIteration && pDev->mChunkQ.size() > minimalFramesStored)) { + pChunk = pDev->mChunkQ.front(); // get chunk + pPlayBuf = pChunk->pBlock; // change play buffer + + // write frames to device + snd_pcm_sframes_t ret = snd_pcm_writei(pDev->pmHandle, pPlayBuf, pDev->mPeriodLen); + + // error handling + if (ret == -EPIPE) { + std::cerr << "Underrun" << std::endl; + snd_pcm_prepare(pDev->pmHandle); + //playbackStopped = true; // stop playback, gather some frames + } else if (ret < 0) { + std::cerr << "Error from snd_pcm_writen: " << snd_strerror((int) ret) << std::endl; + //playbackStopped = true; // ... + } else if (ret != (int) pDev->mPeriodLen) { + std::cerr << "Short write, only " << std::to_string(ret) << " frames written!" << std::endl; + } else { // everything is OK + + // release chunks since data contained in them have been processed + pChunk->pPool->free(pChunk); + pChunk = nullptr; + + // pop frames written to device + pDev->mChunkQ.pop(); + + // first iteration done + firstWriteIteration = false; + } + + } + + // increment iteration counter + iterCnt++; + } +} + +void ALSADevice::write(const std::shared_ptr> &pChunk) { + // aquire lock on queue manipulation + //mQMtx.lock(); + + // store chunks into the queues + mChunkQ.push(pChunk); + + mPlaySem.post(); + + // release lock + //mQMtx.unlock(); +} + +void ALSADevice::closeDevice() { + mRunning = false; // FIXME... +} + +/* + * + + float f = 440; + float df = 2 * M_PI * f / pDev->mSampleRate; + float amplitude = 0.01; + float phase = 0; + + for (size_t i = 0; i < 2 * pDev->mPeriodLen; i += 2) { + pSilenceRight.get()[i] = 32767 * amplitude * sin(phase); + phase += df; + } + + */ \ No newline at end of file diff --git a/src/ALSADevice.h b/src/ALSADevice.h new file mode 100644 index 0000000..007287c --- /dev/null +++ b/src/ALSADevice.h @@ -0,0 +1,38 @@ +// +// Created by epagris on 2021. 09. 20.. +// + +#ifndef WFR_ALSADEVICE_H +#define WFR_ALSADEVICE_H + +#include +#include +#include +#include "MemoryPool.h" +#include "../utils/Semaphore.h" + +class ALSADevice { +private: + snd_pcm_uframes_t mPeriodLen; // alsa period length + snd_pcm_t * pmHandle; // handle to alsa device + snd_pcm_hw_params_t * pmHwParams; // pointer to hardware parameters + unsigned mSampleRate; // hardware sample rate + unsigned mPeriodDuration_us; // period duration in microseconds + bool mRunning; // flag controlling flow in write thread + std::queue>> mChunkQ; // queues storing received chunks of sound data + //std::mutex mQMtx; // mutex for queue management + Semaphore mPlaySem; // semaphore for playing samples +private: + static void fn_WriteThread(ALSADevice * pDev); // routine function executed in write thread + std::shared_ptr pmWriteThread; // thread managing write operations +private: + void openDevice(); + void closeDevice(); +public: + ALSADevice(snd_pcm_uframes_t periodLen, unsigned sampleRate); // constr. + virtual ~ALSADevice(); // destr. + void write(const std::shared_ptr> &pChunk); // function receiving a chunk of sound data +}; + + +#endif //WFR_ALSADEVICE_H diff --git a/src/MemoryPool.cpp b/src/MemoryPool.cpp new file mode 100644 index 0000000..86409aa --- /dev/null +++ b/src/MemoryPool.cpp @@ -0,0 +1,6 @@ +// +// Created by epagris on 2021. 09. 20.. +// + +#include "MemoryPool.h" + diff --git a/src/MemoryPool.h b/src/MemoryPool.h new file mode 100644 index 0000000..44b273a --- /dev/null +++ b/src/MemoryPool.h @@ -0,0 +1,86 @@ +// +// Created by epagris on 2021. 09. 20.. +// + +#ifndef WFR_MEMORYPOOL_H +#define WFR_MEMORYPOOL_H + + +#include +#include +#include +#include + +template +class MemoryPool; + +template +struct MemoryChunk{ +public: + T * pBlock; // memory block + MemoryPool * pPool; // pool owning chunk +}; + +template +class MemoryPool { +private: + size_t mChunkCnt, mChunkSize; // memory chunk size and count + size_t mBlockSize; // size of allocated contigous memory block in element count + std::shared_ptr pmBlock; // the block + std::queue>> mFreeChunks; +public: + void init(size_t chunkSize, size_t count) { + if (mChunkSize != 0 || mChunkCnt != 0 || mBlockSize != 0) { + throw std::runtime_error("Memory pool already initialized, cannot reinit!"); // memory pool already initialized, exit now + } + + // save parameters + mChunkSize = chunkSize; + mChunkCnt = count; + mBlockSize = mChunkSize * mChunkCnt; + + // allocate pool block + pmBlock = std::shared_ptr(new T[mBlockSize]); + + // create chunks + for (size_t i = 0; i < mChunkCnt; i++) { + std::shared_ptr> pChunk = std::make_shared>(); + pChunk->pBlock = pmBlock.get() + i*mChunkSize; + pChunk->pPool = this; + mFreeChunks.push(pChunk); + } + + } +public: + // default constr. + MemoryPool() : mChunkSize(0), mChunkCnt(0), mBlockSize(0) {}; + + // constructor with size parameters + MemoryPool(size_t chunkSize, size_t count) : mChunkSize(0), mChunkCnt(0), mBlockSize(0) // constr. (WARNING: chunkSize defines the amount of elements of T to fit in a chunk) + { + init(chunkSize, count); + } + + std::shared_ptr> alloc() // allocate a chunk of memory + { + std::shared_ptr> pChunk = mFreeChunks.front(); + mFreeChunks.pop(); + return pChunk; + } + + bool free(const std::shared_ptr>& pMemChunk) { // release a chunk of memory + if ((pMemChunk->pPool == this) && (pmBlock.get() <= pMemChunk->pBlock) && (pMemChunk->pBlock < pmBlock.get() + mBlockSize)) { + mFreeChunks.push(pMemChunk); + return true; + } else { + return false; + } + } + + size_t avail() const { // get count of available blocks + return mFreeChunks.size(); + } +}; + + +#endif //WFR_MEMORYPOOL_H diff --git a/src/SampleReceiver.h b/src/SampleReceiver.h new file mode 100644 index 0000000..1c7ef09 --- /dev/null +++ b/src/SampleReceiver.h @@ -0,0 +1,41 @@ +// +// Created by epagris on 2021. 11. 02.. +// + +#ifndef WFR_SAMPLERECEIVER_H +#define WFR_SAMPLERECEIVER_H + +#include "MemoryPool.h" +#include "Timestamp.h" + +#include + +template +class SampleReceiver { +private: + static constexpr size_t MEMORY_BLOCK_COUNT = 16; +private: + const size_t mChPN; // number of channels PAIRS + const size_t mBlockSize; // sample block size + MemoryPool mMemPool; // memory pool for buffers +private: + std::vector mOldestChkIdx, mNewestChkIdx; // vectors for storing oldest and chunk indices for each pair of channels + std::vector mLastTimestamp; // vector storing last timestamp for each pair of channels +public: + // constr. + SampleReceiver(size_t chPN, size_t blockSize) : mChPN(chPN), mBlockSize(blockSize) { + // calculate memory pool parameters [area for timestamps + area for samples] + size_t chunkSize = mChPN * mBlockSize * 2 * sizeof(uint32_t) + mChPN * 2 * mBlockSize * sizeof(T); + + // create memory pool + mMemPool.init(chunkSize, MEMORY_BLOCK_COUNT); + } + + // receive a block of data + bool recvBlock(const T* pData, const Timestamp& ts, size_t ch) { + + } +}; + + +#endif //WFR_SAMPLERECEIVER_H diff --git a/src/SampleWriter.h b/src/SampleWriter.h new file mode 100644 index 0000000..4c36be3 --- /dev/null +++ b/src/SampleWriter.h @@ -0,0 +1,195 @@ +// +// Created by epagris on 2021. 11. 01.. +// + +#ifndef WFR_SAMPLEWRITER_H +#define WFR_SAMPLEWRITER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include "Timestamp.h" + +#define DIRSEP '/' + +template // ch: number of channels, T: sample datatype +class SampleWriter { +public: + static const std::string TS_FILENAME; // filename for timestamp file + static const std::string SAMPLE_FILENAME_PREFIX; // filename prefix for sample file + static const std::string FILE_EXT; // datafile extensions +public: + // timestamp record + struct TimestampRecord { + public: + uint32_t ts_s, ts_ns; // timestamp + public: + TimestampRecord &operator=(const Timestamp &ts) { + ts_s = (uint32_t) ts.s; + ts_ns = (uint32_t) ts.ns; + return *this; + } + }; + +private: + const size_t mChN; // number of channel PAIRS, immutable + const size_t mBlockSize; // buffer size, immutable +private: + std::shared_ptr mpTs; // timestamp records + std::vector> mpSamples; // sample buffer array + + std::string mTsFileName; // timestamp filename + std::ofstream mTsFile; // timestamp file + std::vector mSampleFileNames; // sample file names + std::vector mSampleFiles; // sample output files +private: + Timestamp mTs[2]; // previous and current timestamp +private: + unsigned long long mSamplesN; // number of samples written until now +private: + // resize sample and record buffers + void prepareBuffers() { + // allocate timestamp data buffer + mpTs = std::shared_ptr(new TimestampRecord[mBlockSize]); + + // allocate sample data buffers + mpSamples.resize(mChN); + for (size_t i = 0; i < mChN; i++) { + mpSamples[i] = std::shared_ptr(new T[mBlockSize]); + } + } + + // prepare dataset environment + void prepareDatadir(const std::string &datasetName) { + struct stat sb; + if (stat(datasetName.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode)) { // if directory exists then leave it as it is + //unlink(datasetName.c_str()); FIXME + } else if (mkdir(datasetName.c_str(), 0777) == -1) { // else: create directory for dataset + throw std::runtime_error("Could not create directory " + datasetName + "!"); + } + + std::string datadir_dirsep = datasetName + DIRSEP; + + // generate file names + mTsFileName = TS_FILENAME + '.' + FILE_EXT; // timestamp + + // samples + mSampleFileNames.resize(mChN); + for (size_t i = 0; i < mChN; i++) { + mSampleFileNames[i] = SAMPLE_FILENAME_PREFIX + std::to_string(i) + '.' + FILE_EXT; + } + + // open timestamp file + mTsFile.open(datadir_dirsep + mTsFileName, std::ios_base::binary); + if (!mTsFile.is_open()) { + throw std::runtime_error("Could not open timestamp file " + mTsFileName + "!"); + } + + // open sample files + mSampleFiles.resize(mChN); + for (size_t i = 0; i < mChN; i++) { + mSampleFiles[i].open(datadir_dirsep + mSampleFileNames[i], std::ios_base::binary); + if (!mSampleFiles[i].is_open()) { + throw std::runtime_error("Could not open sample file " + mSampleFileNames[i] + "!"); + } + } + } + + // close files + void closeFiles() { + // close file storing timestamp files + mTsFile.close(); + + // close files stroring samples + for (size_t i = 0; i < mChN; i++) { + mSampleFiles[i].close(); + } + } + +public: + explicit SampleWriter(const std::string &datasetName, size_t chN, size_t bufSize) : mChN(chN), mBlockSize(bufSize), mSamplesN(0) { // constr. + prepareBuffers(); + prepareDatadir(datasetName); + } + + ~SampleWriter() { + closeFiles(); + } + + // add n samples to file + void addSamples(const T *pSamples, const Timestamp &ts) { + // shift FIFO and store new timestamp + mTs[1] = mTs[0]; + mTs[0] = ts; + + // if previous timestamp is empty, then don't store samples since time interpolation can not be done + if (mTs[1].empty()) { + return; + } + + // calculate time step size + Timestamp d = (mTs[0] - mTs[1]) / (double) mBlockSize; + + // timestamp for time interpolation + Timestamp ts_interp = mTs[1]; + + // get channel pointers + T *ppCh[mChN]; + for (size_t i = 0; i < mChN; i++) { + ppCh[i] = mpSamples[i].get(); + } + + // extract samples + for (size_t i = 0; i < mBlockSize; i++) { + // select record + T *pRawSamples = (short *) (pSamples + i * mChN); + + // store sample for a channel + for (size_t ch = 0; ch < mChN; ch++) { + ppCh[ch][i] = pRawSamples[ch]; + } + + // step time + ts_interp += d; + + // store timestamp + mpTs.get()[i] = ts_interp; + } + + // write to file newly created records + + // write timestamps + mTsFile.write((const char *) mpTs.get(), mBlockSize * sizeof(TimestampRecord)); + + // write samples + for (size_t ch = 0; ch < mChN; ch++) { + mSampleFiles[ch].write((const char *) ppCh[ch], mBlockSize * sizeof(T)); + } + + // increment counter + mSamplesN += mBlockSize; + } + + // get number of samples written out + unsigned long long getSampleCnt() const { + return mSamplesN; + } + +}; + +// static fields +template +const std::string SampleWriter::TS_FILENAME = "ts"; + +template +const std::string SampleWriter::SAMPLE_FILENAME_PREFIX = "ch_"; + +template +const std::string SampleWriter::FILE_EXT = "dat"; + +#endif //WFR_SAMPLEWRITER_H diff --git a/src/Timestamp.h b/src/Timestamp.h new file mode 100644 index 0000000..fc8dd3f --- /dev/null +++ b/src/Timestamp.h @@ -0,0 +1,112 @@ +// +// Created by epagris on 2021. 11. 01.. +// + +#ifndef WFR_TIMESTAMP_H +#define WFR_TIMESTAMP_H + +#include + +class Timestamp { +public: + static constexpr uint32_t NANO_PREFIX = 1E+09; +public: + int64_t s, ns; // time data +public: + // constr. + explicit Timestamp(int64_t s = 0, int64_t ns = 0): s(s), ns(ns) {}; + + // initialization with initializer list + Timestamp(const std::initializer_list& il) : s(0), ns(0) { + this->operator=(il); + } + + // assignment + Timestamp& operator=(const std::initializer_list& il) { + if (il.size() >= 2) { + s = *il.begin(); + ns = *(il.begin() + 1); + } + return *this; + } + + Timestamp& operator=(const Timestamp& other) { + s = other.s; + ns = other.ns; + return *this; + } + + // is it empty? + bool empty() const { + return (s == 0) && (ns == 0); + } + + // convert to nanoseconds + int64_t to_ns() const { + return (int64_t)NANO_PREFIX * s + ns; + } + + // convert from nanoseconds + void from_ns(int64_t ns) { + this->s = ns / (int64_t)NANO_PREFIX; + this->ns = (ns - s * NANO_PREFIX); + } + + // add timestamps + Timestamp& operator+=(const Timestamp& other) { + int64_t sum_ns = to_ns() + other.to_ns(); + from_ns(sum_ns); + return *this; + } + + // multiply timestamp + Timestamp operator*(double c) const { + Timestamp ts; + ts.from_ns((int64_t)(c * this->to_ns())); + return ts; + } + + // divide timestamp by scalar + Timestamp operator/(double d) const { + return this->operator*(1 / d); + } + + // substract timestamps + Timestamp& operator-=(const Timestamp& other) { + return this->operator+=(other * (-1.0)); + } + + // add timestamps + Timestamp operator+(const Timestamp& other) const { + Timestamp ts; + ts.from_ns(this->to_ns() + other.to_ns()); + return ts; + } + + // substract timestamps + Timestamp operator-(const Timestamp& other) const { + Timestamp ts = *this + other * (-1); + return ts; + } + + // comparison + bool operator>(const Timestamp& other) const { + return this->to_ns() > other.to_ns(); + } + + bool operator<(const Timestamp& other) const { + return this->to_ns() < other.to_ns(); + } + + bool operator==(const Timestamp& other) const { + return this->to_ns() == other.to_ns(); + } + + bool operator!=(const Timestamp& other) const { + return !this->operator==(other); + } + +}; + + +#endif //WFR_TIMESTAMP_H diff --git a/src/audio_types.h b/src/audio_types.h new file mode 100644 index 0000000..3cbd64b --- /dev/null +++ b/src/audio_types.h @@ -0,0 +1,22 @@ +// +// Created by epagris on 2021. 11. 02.. +// + +#ifndef WFR_AUDIO_TYPES_H +#define WFR_AUDIO_TYPES_H + +#include + +#define STEREO_BUF_LEN (648) +#define PERIOD_LEN (STEREO_BUF_LEN / 2) +#define SAMPLE_RATE (48000) + +typedef int16_t AudioSampleType; + +typedef struct { + uint32_t timestamp_s, timestamp_ns; // timestamp + uint32_t sample_cnt; // count of samples in packet + AudioSampleType pData[STEREO_BUF_LEN]; // buffer for stereo data +} AudioPayload; + +#endif //WFR_AUDIO_TYPES_H diff --git a/utils/Semaphore.cpp b/utils/Semaphore.cpp new file mode 100644 index 0000000..2715bb7 --- /dev/null +++ b/utils/Semaphore.cpp @@ -0,0 +1,25 @@ +// +// Created by epagris on 2021. 09. 12.. +// + +#include "Semaphore.h" + +#ifdef __linux__ + +Semaphore::Semaphore() { + sem_init(&mSem, 0, 0); +} + +Semaphore::~Semaphore() { + sem_destroy(&mSem); +} + +void Semaphore::post() { + sem_post(&mSem); +} + +void Semaphore::wait() { + sem_wait(&mSem); +} + +#endif \ No newline at end of file diff --git a/utils/Semaphore.h b/utils/Semaphore.h new file mode 100644 index 0000000..90a6125 --- /dev/null +++ b/utils/Semaphore.h @@ -0,0 +1,26 @@ +// +// Created by epagris on 2021. 09. 12.. +// + +#ifndef MULTIPROJECTOR_SEMAPHORE_H +#define MULTIPROJECTOR_SEMAPHORE_H + +#ifdef __linux__ +#include +#endif + +#ifdef __linux__ + + class Semaphore { + private: + sem_t mSem; + public: + Semaphore(); // konstr. + virtual ~Semaphore(); // destr. + void post(); // post + void wait(); // wait (blokkoló!) + }; + +#endif + +#endif //MULTIPROJECTOR_SEMAPHORE_H