some part are working

This commit is contained in:
Wiesner András 2021-11-04 09:46:03 +01:00
commit bbd19fc2a8
18 changed files with 1012 additions and 0 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@ -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

4
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/wfr.iml" filepath="$PROJECT_DIR$/.idea/wfr.iml" />
</modules>
</component>
</project>

2
.idea/wfr.iml generated Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />

21
CMakeLists.txt Normal file
View File

@ -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)

39
MATLAB/read_syncdata.m Normal file
View File

@ -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))

5
MATLAB/readdump.m Normal file
View File

@ -0,0 +1,5 @@
f = fopen('wf.dat','r');
d = fread(f,[2, 44100],'int16');
s = d(1,1:1000);
plot(s);
fclose(f);

197
main.cpp Normal file
View File

@ -0,0 +1,197 @@
#include <iostream>
/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API
#include <alsa/asoundlib.h>
#include <math.h>
#include <sys/socket.h>
#include <netinet/in.h>
#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(&params);
//
// /* 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<int16_t> pool(STEREO_BUF_LEN, 1000);
//SampleWriter<AudioSampleType> 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<AudioPayload *>(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;
}

177
src/ALSADevice.cpp Normal file
View File

@ -0,0 +1,177 @@
//
// Created by epagris on 2021. 09. 20..
//
#include <iostream>
#include <cmath>
#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<int16_t>(new int16_t[mPeriodLen]);
//pmPeriodBufRight = std::shared_ptr<int16_t>(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<std::thread>(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<MemoryChunk<int16_t>> 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<MemoryChunk<int16_t>> &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;
}
*/

38
src/ALSADevice.h Normal file
View File

@ -0,0 +1,38 @@
//
// Created by epagris on 2021. 09. 20..
//
#ifndef WFR_ALSADEVICE_H
#define WFR_ALSADEVICE_H
#include <alsa/asoundlib.h>
#include <thread>
#include <mutex>
#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<std::shared_ptr<MemoryChunk<int16_t>>> 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<std::thread> 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<MemoryChunk<int16_t>> &pChunk); // function receiving a chunk of sound data
};
#endif //WFR_ALSADEVICE_H

6
src/MemoryPool.cpp Normal file
View File

@ -0,0 +1,6 @@
//
// Created by epagris on 2021. 09. 20..
//
#include "MemoryPool.h"

86
src/MemoryPool.h Normal file
View File

@ -0,0 +1,86 @@
//
// Created by epagris on 2021. 09. 20..
//
#ifndef WFR_MEMORYPOOL_H
#define WFR_MEMORYPOOL_H
#include <cstddef>
#include <cstdint>
#include <memory>
#include <queue>
template <typename T>
class MemoryPool;
template <typename T>
struct MemoryChunk{
public:
T * pBlock; // memory block
MemoryPool<T> * pPool; // pool owning chunk
};
template <typename T>
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<T> pmBlock; // the block
std::queue<std::shared_ptr<MemoryChunk<T>>> 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<T>(new T[mBlockSize]);
// create chunks
for (size_t i = 0; i < mChunkCnt; i++) {
std::shared_ptr<MemoryChunk<T>> pChunk = std::make_shared<MemoryChunk<T>>();
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<MemoryChunk<T>> alloc() // allocate a chunk of memory
{
std::shared_ptr<MemoryChunk<T>> pChunk = mFreeChunks.front();
mFreeChunks.pop();
return pChunk;
}
bool free(const std::shared_ptr<MemoryChunk<T>>& 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

41
src/SampleReceiver.h Normal file
View File

@ -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 <vector>
template<typename T>
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<T> mMemPool; // memory pool for buffers
private:
std::vector<ssize_t> mOldestChkIdx, mNewestChkIdx; // vectors for storing oldest and chunk indices for each pair of channels
std::vector<Timestamp> 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

195
src/SampleWriter.h Normal file
View File

@ -0,0 +1,195 @@
//
// Created by epagris on 2021. 11. 01..
//
#ifndef WFR_SAMPLEWRITER_H
#define WFR_SAMPLEWRITER_H
#include <fstream>
#include <string>
#include <memory>
#include <iostream>
#include <sys/stat.h>
#include <vector>
#include <set>
#include <queue>
#include "Timestamp.h"
#define DIRSEP '/'
template<typename T> // 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<TimestampRecord> mpTs; // timestamp records
std::vector<std::shared_ptr<T>> mpSamples; // sample buffer array
std::string mTsFileName; // timestamp filename
std::ofstream mTsFile; // timestamp file
std::vector<std::string> mSampleFileNames; // sample file names
std::vector<std::ofstream> 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<TimestampRecord>(new TimestampRecord[mBlockSize]);
// allocate sample data buffers
mpSamples.resize(mChN);
for (size_t i = 0; i < mChN; i++) {
mpSamples[i] = std::shared_ptr<T>(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<typename T>
const std::string SampleWriter<T>::TS_FILENAME = "ts";
template<typename T>
const std::string SampleWriter<T>::SAMPLE_FILENAME_PREFIX = "ch_";
template<typename T>
const std::string SampleWriter<T>::FILE_EXT = "dat";
#endif //WFR_SAMPLEWRITER_H

112
src/Timestamp.h Normal file
View File

@ -0,0 +1,112 @@
//
// Created by epagris on 2021. 11. 01..
//
#ifndef WFR_TIMESTAMP_H
#define WFR_TIMESTAMP_H
#include <cstdint>
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<int64_t>& il) : s(0), ns(0) {
this->operator=(il);
}
// assignment
Timestamp& operator=(const std::initializer_list<int64_t>& 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

22
src/audio_types.h Normal file
View File

@ -0,0 +1,22 @@
//
// Created by epagris on 2021. 11. 02..
//
#ifndef WFR_AUDIO_TYPES_H
#define WFR_AUDIO_TYPES_H
#include <cstdint>
#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

25
utils/Semaphore.cpp Normal file
View File

@ -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

26
utils/Semaphore.h Normal file
View File

@ -0,0 +1,26 @@
//
// Created by epagris on 2021. 09. 12..
//
#ifndef MULTIPROJECTOR_SEMAPHORE_H
#define MULTIPROJECTOR_SEMAPHORE_H
#ifdef __linux__
#include <semaphore.h>
#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