some part are working
This commit is contained in:
commit
bbd19fc2a8
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal 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
4
.idea/misc.xml
generated
Normal 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
8
.idea/modules.xml
generated
Normal 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
2
.idea/wfr.iml
generated
Normal 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
21
CMakeLists.txt
Normal 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
39
MATLAB/read_syncdata.m
Normal 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
5
MATLAB/readdump.m
Normal 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
197
main.cpp
Normal 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(¶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<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
177
src/ALSADevice.cpp
Normal 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
38
src/ALSADevice.h
Normal 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
6
src/MemoryPool.cpp
Normal file
@ -0,0 +1,6 @@
|
||||
//
|
||||
// Created by epagris on 2021. 09. 20..
|
||||
//
|
||||
|
||||
#include "MemoryPool.h"
|
||||
|
86
src/MemoryPool.h
Normal file
86
src/MemoryPool.h
Normal 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
41
src/SampleReceiver.h
Normal 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
195
src/SampleWriter.h
Normal 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
112
src/Timestamp.h
Normal 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
22
src/audio_types.h
Normal 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
25
utils/Semaphore.cpp
Normal 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
26
utils/Semaphore.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user