final degree paper state
This commit is contained in:
parent
45c2e06bf8
commit
cff343e959
@ -3,11 +3,11 @@ cmake_minimum_required(VERSION 3.20)
|
|||||||
SET(APP_NAME wfr)
|
SET(APP_NAME wfr)
|
||||||
|
|
||||||
project(${APP_NAME})
|
project(${APP_NAME})
|
||||||
set(CMAKE_CXX_STANDARD 14)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
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
|
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/SampleReceiver.h
|
||||||
src/audio_types.h src/ServerBeacon.cpp src/ServerBeacon.h)
|
src/audio_types.h src/ServerBeacon.cpp src/ServerBeacon.h src/GUI.h src/globals.h src/Callback.h src/MultiStreamReceiver.cpp src/MultiStreamReceiver.h)
|
||||||
|
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
if (THREADS_FOUND)
|
if (THREADS_FOUND)
|
||||||
@ -16,6 +16,26 @@ endif (THREADS_FOUND)
|
|||||||
|
|
||||||
find_package(ALSA REQUIRED)
|
find_package(ALSA REQUIRED)
|
||||||
if (ALSA_FOUND)
|
if (ALSA_FOUND)
|
||||||
include_directories(${ALSA_INCLUDE_DIRS})
|
target_include_directories(${APP_NAME} PUBLIC ${ALSA_INCLUDE_DIRS})
|
||||||
target_link_libraries(${APP_NAME} ${ALSA_LIBRARIES})
|
target_link_libraries(${APP_NAME} ${ALSA_LIBRARIES})
|
||||||
endif (ALSA_FOUND)
|
endif (ALSA_FOUND)
|
||||||
|
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
|
||||||
|
if (GTK3_FOUND)
|
||||||
|
target_include_directories(${APP_NAME} PUBLIC ${GTK3_INCLUDE_DIRS})
|
||||||
|
target_link_directories(${APP_NAME} PUBLIC ${GTK3_LIBRARY_DIRS})
|
||||||
|
add_definitions(${GTK3_CFLAGS_OTHER})
|
||||||
|
target_link_libraries(${APP_NAME} ${GTK3_LIBRARIES})
|
||||||
|
endif(GTK3_FOUND)
|
||||||
|
|
||||||
|
pkg_check_modules(GTKMM REQUIRED gtkmm-3.0)
|
||||||
|
if (GTKMM_FOUND)
|
||||||
|
target_include_directories(${APP_NAME} PUBLIC ${GTKMM_INCLUDE_DIRS})
|
||||||
|
target_link_directories(${APP_NAME} PUBLIC ${GTKMM_LIBRARY_DIRS})
|
||||||
|
target_link_libraries(${APP_NAME} ${GTKMM_LIBRARIES})
|
||||||
|
endif(GTKMM_FOUND)
|
||||||
|
|
||||||
|
install(TARGETS ${APP_NAME} RUNTIME DESTINATION ${PROJECT_SOURCE_DIR}/runtime ARCHIVE DESTINATION ${PROJECT_SOURCE_DIR}/runtime)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-rdynamic")
|
||||||
15
MATLAB/read_multistream_dir.m
Normal file
15
MATLAB/read_multistream_dir.m
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
function read_multistream_dir(d, prefix, plotrange, onlydata)
|
||||||
|
% get directories containing samples
|
||||||
|
hint_file_name = strcat('./runtime/', d, '/node_dir_hint.txt');
|
||||||
|
hint_file = fopen(hint_file_name);
|
||||||
|
node_dirs = textscan(hint_file, '%s');
|
||||||
|
fclose(hint_file);
|
||||||
|
|
||||||
|
node_names = strrep(node_dirs{1,1}, prefix, "");
|
||||||
|
|
||||||
|
node_dirs = strcat('./runtime/', d, '/', node_dirs{1,1});
|
||||||
|
|
||||||
|
sync_datasets(node_dirs, node_names, plotrange, onlydata);
|
||||||
|
|
||||||
|
print(strcat('./runtime/', d, '/figure.svg'), '-dsvg');
|
||||||
|
end
|
||||||
@ -1,4 +1,4 @@
|
|||||||
function sds = sync_datasets(dss)
|
function sds = sync_datasets(dss, node_names, PLOTRANGE, ONLYDATA)
|
||||||
% load datasets
|
% load datasets
|
||||||
len = length(dss);
|
len = length(dss);
|
||||||
sds = {};
|
sds = {};
|
||||||
@ -22,15 +22,28 @@ function sds = sync_datasets(dss)
|
|||||||
sds{i} = ds;
|
sds{i} = ds;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
% reference dataset and timescale
|
||||||
|
ds_ref = sds{1};
|
||||||
|
ts_ref = ds_ref(:,1);
|
||||||
|
|
||||||
% create plot
|
% create plot
|
||||||
figure(1)
|
if (~ONLYDATA)
|
||||||
|
figure('Position', [100 100 1000 1000])
|
||||||
|
else
|
||||||
|
figure('Position', [100 100 1000 400])
|
||||||
|
end
|
||||||
|
clf
|
||||||
|
|
||||||
% plot samples
|
% plot samples
|
||||||
subplot(2,1,1);
|
if (~ONLYDATA)
|
||||||
|
subplot(3,1,1);
|
||||||
|
end
|
||||||
|
|
||||||
marks = ['-', '-o'];
|
marks = ["-x", "-o"];
|
||||||
|
|
||||||
PLOTRANGE = 1:100;
|
%PLOTRANGE = 1017:1020;
|
||||||
|
|
||||||
|
VOLT_PER_BIN = 42.80E-06;
|
||||||
|
|
||||||
% get beginning indices
|
% get beginning indices
|
||||||
for i=1:len
|
for i=1:len
|
||||||
@ -39,34 +52,90 @@ function sds = sync_datasets(dss)
|
|||||||
|
|
||||||
s = size(ds);
|
s = size(ds);
|
||||||
|
|
||||||
%for k = 2:s(2);
|
for k = 2:s(2)
|
||||||
for k = 2:2
|
%for k = 2:2
|
||||||
%plot(ts(1:100),ds(1:100,k), marks(k-1));
|
if (ONLYDATA)
|
||||||
plot(ts(PLOTRANGE),ds(PLOTRANGE,k), 'x');
|
mark = "-";
|
||||||
|
else
|
||||||
|
mark = marks(k-1);
|
||||||
|
end
|
||||||
|
|
||||||
|
plot(ts(PLOTRANGE), ds(PLOTRANGE,k) * VOLT_PER_BIN, mark);
|
||||||
hold on
|
hold on
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
grid on
|
grid on
|
||||||
xlabel("Time [s]");
|
|
||||||
ylabel("Sample");
|
if (ONLYDATA)
|
||||||
xlim([ts(PLOTRANGE(1)) ts(PLOTRANGE(end))]);
|
xlabel("Idő [s]");
|
||||||
|
end
|
||||||
|
|
||||||
|
ylabel("Feszültség [V]");
|
||||||
|
xlim([ts_ref(PLOTRANGE(1)) ts_ref(PLOTRANGE(end))]);
|
||||||
|
|
||||||
|
legend_lines = {};
|
||||||
|
|
||||||
|
for i=1:length(node_names)
|
||||||
|
for ch=1:2
|
||||||
|
legend_lines{2 * (i - 1) + ch, 1} = strcat(node_names{i,1}, " CH", num2str(ch));
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
legend(legend_lines);
|
||||||
|
|
||||||
|
if (ONLYDATA)
|
||||||
|
return;
|
||||||
|
end
|
||||||
|
|
||||||
|
% plot signal difference
|
||||||
|
subplot(3,1,2);
|
||||||
|
|
||||||
|
% normalize signals
|
||||||
|
ds = sds{1};
|
||||||
|
ds_norm_range{1} = ds(PLOTRANGE,2:end) ./ max(abs(ds(PLOTRANGE,2:end)));
|
||||||
|
legend_lines = {};
|
||||||
|
|
||||||
|
for i=2:length(node_names)
|
||||||
|
ds = sds{i};
|
||||||
|
ds_norm_range{i} = ds(PLOTRANGE,2:end) ./ max(abs(ds(PLOTRANGE,2:end)));
|
||||||
|
|
||||||
|
for ch=1:2
|
||||||
|
legend_lines{2 * (i - 2) + ch, 1} = strcat(node_names{i,1}, "-", node_names{1,1}, " CH", num2str(ch));
|
||||||
|
end
|
||||||
|
|
||||||
|
ts = ds(:,1);
|
||||||
|
diff_data = ((ds_norm_range{i} - ds_norm_range{1})); % ./ ds_norm_range{1});
|
||||||
|
|
||||||
|
% for l=1:length(diff_data)
|
||||||
|
% diff_data(l,:) = diff_data(l,:) ./ ds_norm_range{1}(l,:);
|
||||||
|
% end
|
||||||
|
|
||||||
|
mark = marks(k-1);
|
||||||
|
plot(ts(PLOTRANGE), diff_data, mark);
|
||||||
|
end
|
||||||
|
|
||||||
|
legend(legend_lines);
|
||||||
|
ylabel("Erősítéshibával kompenzált különbség")
|
||||||
|
xlim([ts_ref(PLOTRANGE(1)) ts_ref(PLOTRANGE(end))]);
|
||||||
|
|
||||||
% plot timestamp errors
|
% plot timestamp errors
|
||||||
subplot(2,1,2);
|
subplot(3,1,3);
|
||||||
|
|
||||||
ds_ref = sds{1};
|
legend_lines = {};
|
||||||
ts_ref = ds_ref(:,1);
|
|
||||||
|
|
||||||
for i = 2:len
|
for i = 2:len
|
||||||
ts_err = ts(PLOTRANGE) - ts_ref(PLOTRANGE);
|
ts_err = ts(PLOTRANGE) - ts_ref(PLOTRANGE);
|
||||||
plot(ts_ref(PLOTRANGE), ts_err * 1E+09);
|
plot(ts_ref(PLOTRANGE), ts_err * 1E+09, "-o");
|
||||||
hold on
|
hold on
|
||||||
|
|
||||||
|
legend_lines{i - 1, 1} = strcat(node_names{i,1}, "-", node_names{1,1});
|
||||||
end
|
end
|
||||||
|
|
||||||
grid on
|
grid on
|
||||||
xlabel("Time [s]");
|
xlabel("Idő [s]");
|
||||||
ylabel("Time error [ns]");
|
ylabel("Időhiba [ns]");
|
||||||
xlim([ts(PLOTRANGE(1)) ts(PLOTRANGE(end))]);
|
legend(legend_lines);
|
||||||
|
xlim([ts_ref(PLOTRANGE(1)) ts_ref(PLOTRANGE(end))]);
|
||||||
|
|
||||||
endfunction
|
end
|
||||||
|
|||||||
169
main.cpp
169
main.cpp
@ -5,12 +5,75 @@
|
|||||||
|
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
|
|
||||||
#include <math.h>
|
#include <cmath>
|
||||||
|
#include <memory>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include "src/ALSADevice.h"
|
#include "src/ALSADevice.h"
|
||||||
#include "src/SampleWriter.h"
|
#include "src/SampleWriter.h"
|
||||||
#include "src/audio_types.h"
|
#include "src/audio_types.h"
|
||||||
|
#include "src/ServerBeacon.h"
|
||||||
|
#include "src/GUI.h"
|
||||||
|
|
||||||
|
//uint8_t pRecvBuf[8096] __attribute__ ((aligned (32)));
|
||||||
|
|
||||||
|
std::shared_ptr<Globals> globs;
|
||||||
|
|
||||||
|
int main(int argc, char * argv[]) {
|
||||||
|
if (argc < 3) {
|
||||||
|
std::cout << "At least 2 parameters needed:" << std::endl << "wfr data_port multicast_if_addr" << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string mcastIFAddr = argv[2];
|
||||||
|
unsigned short beaconPort = atoi(argv[1]);
|
||||||
|
|
||||||
|
// -----------------
|
||||||
|
|
||||||
|
//ALSADevice dev(PERIOD_LEN, SAMPLE_RATE);
|
||||||
|
//MemoryPool<int16_t> pool(STEREO_BUF_LEN, 1000);
|
||||||
|
//SampleWriter<AudioSampleType> sw(std::string("test_") + argv[1], 2, STEREO_BUF_LEN / 2);
|
||||||
|
|
||||||
|
// create globals
|
||||||
|
globs = std::make_shared<Globals>();
|
||||||
|
|
||||||
|
// create beacon
|
||||||
|
auto beacon = globs->beacon = std::make_shared<ServerBeacon>();
|
||||||
|
beacon->setInterfaceAddr(mcastIFAddr);
|
||||||
|
|
||||||
|
// init and start GUI
|
||||||
|
Glib::init();
|
||||||
|
GUI gui(globs);
|
||||||
|
gui.run();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//int main() {
|
//int main() {
|
||||||
// long loops;
|
// long loops;
|
||||||
@ -117,81 +180,37 @@
|
|||||||
// return 0;
|
// return 0;
|
||||||
//}
|
//}
|
||||||
|
|
||||||
#define Fs (48000) // sampling frequency
|
//#define Fs (48000) // sampling frequency
|
||||||
#define F (440) // signal frequency
|
//#define F (440) // signal frequency
|
||||||
#define K (0.1) // amplitude
|
//#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));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
|
||||||
void generate_sine(int16_t *pBuf, uint32_t n) {
|
//std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/*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;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t pRecvBuf[8096] __attribute__ ((aligned (32)));
|
while (true) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
int main(int argc, char * argv[]) {
|
}*/
|
||||||
ALSADevice dev(PERIOD_LEN, SAMPLE_RATE);
|
|
||||||
MemoryPool<int16_t> pool(STEREO_BUF_LEN, 1000);
|
|
||||||
//SampleWriter<AudioSampleType> sw(std::string("test_") + argv[1], 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;
|
|
||||||
}
|
|
||||||
29
src/Callback.h
Normal file
29
src/Callback.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// Created by epagris on 2021. 11. 18..
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef WFR_CALLBACK_H
|
||||||
|
#define WFR_CALLBACK_H
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
template <typename... ParamTypes>
|
||||||
|
class CallbackBase {
|
||||||
|
public:
|
||||||
|
virtual void operator()(ParamTypes... params) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, typename... ParamTypes>
|
||||||
|
class Callback : public CallbackBase<ParamTypes...> {
|
||||||
|
public:
|
||||||
|
T * pObj; // pointer to object to call the function on
|
||||||
|
void(T::*pFN)(ParamTypes... params); // member function pointer
|
||||||
|
|
||||||
|
Callback(T * pObj, void(T::*pFN)(ParamTypes... params)) : pObj(pObj), pFN(pFN) {}
|
||||||
|
|
||||||
|
void operator()(ParamTypes... params) override {
|
||||||
|
std::invoke(pFN, *pObj, params...);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //WFR_CALLBACK_H
|
||||||
355
src/GUI.h
Normal file
355
src/GUI.h
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
//
|
||||||
|
// Created by epagris on 2021. 11. 14..
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef WFR_GUI_H
|
||||||
|
#define WFR_GUI_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#include <gtkmm-3.0/gtkmm.h>
|
||||||
|
|
||||||
|
#include "ServerBeacon.h"
|
||||||
|
#include "globals.h"
|
||||||
|
#include "Callback.h"
|
||||||
|
#include "MultiStreamReceiver.h"
|
||||||
|
|
||||||
|
class GUI : public Gtk::Application {
|
||||||
|
public:
|
||||||
|
template<typename T>
|
||||||
|
using RefPtr = Glib::RefPtr<T>;
|
||||||
|
|
||||||
|
// GLOBALS
|
||||||
|
private:
|
||||||
|
std::shared_ptr<Globals> mGlobs;
|
||||||
|
protected:
|
||||||
|
// tree view managing client nodes
|
||||||
|
class ClNodeTW {
|
||||||
|
private:
|
||||||
|
Gtk::TreeView *mpTW;
|
||||||
|
protected:
|
||||||
|
class ClNodeLSColumns : public Gtk::TreeModel::ColumnRecord {
|
||||||
|
public:
|
||||||
|
Gtk::TreeModelColumn<Glib::ustring> mColIP;
|
||||||
|
Gtk::TreeModelColumn<bool> mColUse;
|
||||||
|
Gtk::TreeModelColumn<in_addr_t> mColInAddr;
|
||||||
|
public:
|
||||||
|
ClNodeLSColumns() {
|
||||||
|
add(mColIP);
|
||||||
|
add(mColUse);
|
||||||
|
add(mColInAddr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
ClNodeLSColumns cols; // columns
|
||||||
|
RefPtr<Gtk::ListStore> listStore; // list store object
|
||||||
|
Gtk::CellRendererText ipR; // IP-renderer
|
||||||
|
Gtk::CellRendererToggle useR; // use? renderer
|
||||||
|
std::shared_ptr<CallbackBase<ClNodeTW *>> selectChangeCB; // kiválasztás változásakor hívódik meg
|
||||||
|
private:
|
||||||
|
void onToggleUse(const Glib::ustring &path) {
|
||||||
|
auto row = *(listStore->get_iter(path));
|
||||||
|
row[cols.mColUse] = !row[cols.mColUse];
|
||||||
|
|
||||||
|
if (selectChangeCB != nullptr) {
|
||||||
|
(*selectChangeCB)(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*void onRowActivate(const Gtk::TreePath &tp, Gtk::TreeViewColumn *pTVC) {
|
||||||
|
auto row = *(listStore->get_iter(tp));
|
||||||
|
auto command = "xdg-open http://" + row[cols.mColIP] + "/";
|
||||||
|
system(command.c_str());
|
||||||
|
}*/
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void addRenderers() {
|
||||||
|
mpTW->append_column("IP Address", ipR);
|
||||||
|
|
||||||
|
useR.set_activatable(true);
|
||||||
|
useR.signal_toggled().connect(sigc::mem_fun(*this, &ClNodeTW::onToggleUse));
|
||||||
|
|
||||||
|
mpTW->append_column("Use?", useR);
|
||||||
|
|
||||||
|
auto twCols = mpTW->get_columns();
|
||||||
|
twCols[0]->add_attribute(ipR, "text", 0);
|
||||||
|
twCols[1]->add_attribute(useR, "active", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ClNodeTW(Gtk::TreeView *pTW) : mpTW(pTW) {
|
||||||
|
listStore = Gtk::ListStore::create(cols); // construct list store
|
||||||
|
mpTW->set_model(listStore); // set model
|
||||||
|
//mpTW->set_activate_on_single_click(true);
|
||||||
|
addRenderers(); // add cell renderers
|
||||||
|
|
||||||
|
//mpTW->signal_row_activated().connect(sigc::mem_fun(*this, &ClNodeTW::onRowActivate));
|
||||||
|
}
|
||||||
|
|
||||||
|
void addRow(in_addr_t addr) {
|
||||||
|
auto row = *(listStore->append());
|
||||||
|
row[cols.mColInAddr] = addr;
|
||||||
|
row[cols.mColIP] = inet_ntoa({addr});
|
||||||
|
row[cols.mColUse] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setList(const std::list<in_addr_t> &addrs) {
|
||||||
|
// set of entries already in the list
|
||||||
|
std::list<in_addr_t> entriesFound;
|
||||||
|
std::list<Gtk::TreeIter> entriesToDelete;
|
||||||
|
|
||||||
|
bool atLeastOneSelectedLineDeleted = false;
|
||||||
|
|
||||||
|
// check for existing lines
|
||||||
|
listStore->foreach([&](const Gtk::TreePath &path, const Gtk::TreeIter &iter) {
|
||||||
|
in_addr_t addr = (*iter)[cols.mColInAddr];
|
||||||
|
|
||||||
|
// if entry is already in the list, then don't add it again
|
||||||
|
if (std::find(addrs.begin(), addrs.end(), addr) != addrs.end()) {
|
||||||
|
entriesFound.push_back(addr);
|
||||||
|
} else { // if value is not on the novel list, remove it
|
||||||
|
if ((*iter)[cols.mColUse]) {
|
||||||
|
atLeastOneSelectedLineDeleted |= true;
|
||||||
|
}
|
||||||
|
|
||||||
|
entriesToDelete.push_back(iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// delete non-existent lines
|
||||||
|
for (const auto &entry: entriesToDelete) {
|
||||||
|
listStore->erase(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add non-existent, novel lines
|
||||||
|
for (auto const &addr: addrs) {
|
||||||
|
if (std::find(entriesFound.begin(), entriesFound.end(), addr) == entriesFound.end()) {
|
||||||
|
addRow(addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send notification if a selected row is being deleted
|
||||||
|
if (atLeastOneSelectedLineDeleted && (selectChangeCB != nullptr)) {
|
||||||
|
(*selectChangeCB)(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<in_addr_t> getSelectedLines() const {
|
||||||
|
std::vector<in_addr_t> selectedLines;
|
||||||
|
|
||||||
|
// check for existing lines
|
||||||
|
listStore->foreach([&](const Gtk::TreePath &path, const Gtk::TreeIter &iter) {
|
||||||
|
if ((*iter)[cols.mColUse]) {
|
||||||
|
selectedLines.push_back((*iter)[cols.mColInAddr]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return selectedLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
in_addr_t getHighlightedRow() const {
|
||||||
|
auto iter = mpTW->get_selection();
|
||||||
|
|
||||||
|
if (iter->count_selected_rows() > 0) {
|
||||||
|
return (*mpTW->get_selection()->get_selected())[cols.mColInAddr];
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// BUILDER-RELATED THINGS
|
||||||
|
protected:
|
||||||
|
RefPtr<Gtk::Builder> mBuilder;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T *getWidget(const Glib::ustring &name) { // get widget by name
|
||||||
|
Gtk::Widget *pWidget;
|
||||||
|
mBuilder->get_widget(name, pWidget);
|
||||||
|
return reinterpret_cast<T *>(pWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected: // WIDGETS
|
||||||
|
Gtk::Button *wNodeRefreshBtn;
|
||||||
|
Gtk::Button *wStartStopCaptureBtn;
|
||||||
|
Gtk::FileChooserButton *wFolderChooser;
|
||||||
|
Gtk::TextView *wInfoPanel;
|
||||||
|
Gtk::TreeView *wClNodeTW;
|
||||||
|
std::shared_ptr<ClNodeTW> mClNodeTW; // instance
|
||||||
|
Gtk::Menu *wClNodeCtxMenu;
|
||||||
|
Gtk::MenuItem *wCtxEntryWebInterface;
|
||||||
|
Gtk::MenuItem *wCtxEntryTelnetTerm;
|
||||||
|
|
||||||
|
// status flags
|
||||||
|
protected:
|
||||||
|
struct {
|
||||||
|
bool clNodesSelected;
|
||||||
|
bool destinationFolderSelected;
|
||||||
|
bool captureRunning;
|
||||||
|
} mFlags;
|
||||||
|
|
||||||
|
// stream receiver
|
||||||
|
protected:
|
||||||
|
std::shared_ptr<MultiStreamReceiver> mpMSR; // pointer to a multistream receiver object
|
||||||
|
|
||||||
|
public:
|
||||||
|
void onActivateApp() {
|
||||||
|
Gtk::Window *pMainWin;
|
||||||
|
mBuilder->get_widget("main_win", pMainWin);
|
||||||
|
|
||||||
|
add_window(*pMainWin);
|
||||||
|
|
||||||
|
//mClNodeTW->addRow("10.42.0.64", true);
|
||||||
|
//mClNodeTW->addRow("10.42.0.65", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onScanDone(ServerBeacon *pSB) {
|
||||||
|
auto nodes = pSB->getNodesOnNetwork();
|
||||||
|
std::list<in_addr_t> ipList;
|
||||||
|
|
||||||
|
// convert binary IP addresses to strings
|
||||||
|
for (auto const &node: nodes) {
|
||||||
|
ipList.push_back(node.s_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
mClNodeTW->setList(ipList);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onRefreshBtnToggled() {
|
||||||
|
auto beacon = mGlobs->beacon;
|
||||||
|
|
||||||
|
wNodeRefreshBtn->set_sensitive(false);
|
||||||
|
|
||||||
|
std::shared_ptr<Callback<GUI, ServerBeacon *>> scanCB = std::make_shared<Callback<GUI, ServerBeacon *>>(this, &GUI::onScanDone);
|
||||||
|
|
||||||
|
// start beacon
|
||||||
|
beacon->stopBeacon();
|
||||||
|
beacon->setScanCallback(scanCB);
|
||||||
|
beacon->startBeacon();
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||||
|
|
||||||
|
mGlobs->beacon->stopBeacon();
|
||||||
|
|
||||||
|
wNodeRefreshBtn->set_sensitive(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onNodeSelectionChanged(ClNodeTW *pTW) {
|
||||||
|
auto selectedIPs = pTW->getSelectedLines();
|
||||||
|
mFlags.clNodesSelected = !selectedIPs.empty();
|
||||||
|
|
||||||
|
manageCaptureBtnState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onFolderSelect() {
|
||||||
|
mFlags.destinationFolderSelected = !wFolderChooser->get_filename().empty();
|
||||||
|
|
||||||
|
manageCaptureBtnState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onStartStopCaptureBtnClick() {
|
||||||
|
if (!mFlags.captureRunning) { // start capture
|
||||||
|
wFolderChooser->set_sensitive(false);
|
||||||
|
wClNodeTW->set_sensitive(false);
|
||||||
|
wStartStopCaptureBtn->set_label("Stop capture");
|
||||||
|
mFlags.captureRunning = true;
|
||||||
|
|
||||||
|
mpMSR = std::make_shared<MultiStreamReceiver>(mClNodeTW->getSelectedLines(), wFolderChooser->get_filename());
|
||||||
|
mpMSR->start();
|
||||||
|
mGlobs->beacon->execCmdOnAllTerms(std::string("snd connect ") + mGlobs->beacon->getInterfaceAddr() + " " + std::to_string(mpMSR->port));
|
||||||
|
} else {
|
||||||
|
mGlobs->beacon->execCmdOnAllTerms("snd disconnect");
|
||||||
|
mpMSR = nullptr;
|
||||||
|
|
||||||
|
wFolderChooser->set_sensitive(true);
|
||||||
|
wClNodeTW->set_sensitive(true);
|
||||||
|
|
||||||
|
wStartStopCaptureBtn->set_label("Start capture");
|
||||||
|
mFlags.captureRunning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool onShowCtxPopupMenu(GdkEventButton *event) {
|
||||||
|
if (event->type == GDK_2BUTTON_PRESS) {
|
||||||
|
if (!wClNodeCtxMenu->get_attach_widget()) {
|
||||||
|
wClNodeCtxMenu->attach_to_widget(*wClNodeTW);
|
||||||
|
}
|
||||||
|
|
||||||
|
wClNodeCtxMenu->popup(event->button, event->time);
|
||||||
|
return true; //It has been handled.
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onOpenWebInterface() {
|
||||||
|
in_addr_t node_addr = mClNodeTW->getHighlightedRow();
|
||||||
|
if (node_addr != 0) {
|
||||||
|
auto command = std::string("xdg-open http://") + inet_ntoa({node_addr}) + "/";
|
||||||
|
system(command.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onOpenTelnetClient() {
|
||||||
|
in_addr_t node_addr = mClNodeTW->getHighlightedRow();
|
||||||
|
if (node_addr != 0) {
|
||||||
|
auto port = mGlobs->beacon->getNodeNettermPort({node_addr});
|
||||||
|
auto command = std::string("putty -raw ") + inet_ntoa({node_addr}) + " " + std::to_string(port) + " &";
|
||||||
|
system(command.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void manageCaptureBtnState() {
|
||||||
|
wStartStopCaptureBtn->set_sensitive(mFlags.clNodesSelected && mFlags.destinationFolderSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void fetchWidgets() {
|
||||||
|
wNodeRefreshBtn = getWidget<Gtk::ToggleButton>("node_refresh_btn");
|
||||||
|
wStartStopCaptureBtn = getWidget<Gtk::Button>("start_stop_capture_btn");
|
||||||
|
wFolderChooser = getWidget<Gtk::FileChooserButton>("folder_chooser_btn");
|
||||||
|
wInfoPanel = getWidget<Gtk::TextView>("info_panel");
|
||||||
|
wClNodeTW = getWidget<Gtk::TreeView>("client_node_tw");
|
||||||
|
mClNodeTW = std::make_shared<ClNodeTW>(wClNodeTW);
|
||||||
|
|
||||||
|
wClNodeCtxMenu = getWidget<Gtk::Menu>("node_tw_ctx_menu");
|
||||||
|
wCtxEntryWebInterface = getWidget<Gtk::MenuItem>("ctx_web_interface");
|
||||||
|
wCtxEntryTelnetTerm = getWidget<Gtk::MenuItem>("ctx_telnet_term");
|
||||||
|
}
|
||||||
|
|
||||||
|
void connectSignals() {
|
||||||
|
signal_activate().connect(sigc::mem_fun(*this, &GUI::onActivateApp)); // app activate
|
||||||
|
wNodeRefreshBtn->signal_clicked().connect(sigc::mem_fun(*this, &GUI::onRefreshBtnToggled)); // refresh button
|
||||||
|
mClNodeTW->selectChangeCB = std::make_shared<Callback<GUI, ClNodeTW *>>(this, &GUI::onNodeSelectionChanged); // select change callback
|
||||||
|
wClNodeTW->signal_button_press_event().connect(sigc::mem_fun(*this, &GUI::onShowCtxPopupMenu)); // right click context menu popup
|
||||||
|
wFolderChooser->signal_file_set().connect(sigc::mem_fun(*this, &GUI::onFolderSelect)); // folder selection
|
||||||
|
wStartStopCaptureBtn->signal_clicked().connect(sigc::mem_fun(*this, &GUI::onStartStopCaptureBtnClick)); // start/stop click
|
||||||
|
|
||||||
|
wCtxEntryWebInterface->signal_activate().connect(sigc::mem_fun(*this, &GUI::onOpenWebInterface));
|
||||||
|
wCtxEntryTelnetTerm->signal_activate().connect(sigc::mem_fun(*this, &GUI::onOpenTelnetClient));
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetFlags() {
|
||||||
|
memset(&mFlags, 0, sizeof(mFlags));
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GUI(const std::shared_ptr<Globals> &globs) : Gtk::Application("com.epagris.samrecv"), mGlobs(globs) { // constr.
|
||||||
|
resetFlags();
|
||||||
|
mBuilder = Gtk::Builder::create_from_file("gui/samprecv.glade");
|
||||||
|
fetchWidgets();
|
||||||
|
connectSignals(); // connect signals
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //WFR_GUI_H
|
||||||
154
src/MultiStreamReceiver.cpp
Normal file
154
src/MultiStreamReceiver.cpp
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
//
|
||||||
|
// Created by epagris on 2021. 11. 22..
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "MultiStreamReceiver.h"
|
||||||
|
#include "audio_types.h"
|
||||||
|
|
||||||
|
MultiStreamReceiver::MultiStreamReceiver(const std::vector<in_addr_t> &nodes, const std::string& targetDir, unsigned short port) : port(port) {
|
||||||
|
mTargetDir = targetDir;
|
||||||
|
mRunning = false;
|
||||||
|
mClientNodes = nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiStreamReceiver::~MultiStreamReceiver() {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiStreamReceiver::startNetworking() {
|
||||||
|
// open socket
|
||||||
|
mSoc = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (mSoc == -1) {
|
||||||
|
throw std::runtime_error(std::string("Could not create UDP-socket in ") + __FUNCTION__ + "!");
|
||||||
|
}
|
||||||
|
|
||||||
|
int opt = 1;
|
||||||
|
if (setsockopt(mSoc, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)) == -1) {
|
||||||
|
throw std::runtime_error(std::string("Could not set socket options in ") + __FUNCTION__ + "!");
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr_in addr;
|
||||||
|
addr.sin_family = AF_INET;
|
||||||
|
addr.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
addr.sin_port = htons(port);
|
||||||
|
|
||||||
|
// bind
|
||||||
|
if (bind(mSoc, (const sockaddr *)&addr, sizeof(addr)) == -1) {
|
||||||
|
throw std::runtime_error(std::string("Could not bind socket options in ") + __FUNCTION__ + "!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiStreamReceiver::createSampleWriters() {
|
||||||
|
struct stat sb;
|
||||||
|
if (stat(mTargetDir.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(mTargetDir.c_str(), 0777) == -1) { // else: create directory for dataset
|
||||||
|
throw std::runtime_error("Could not create directory " + mTargetDir + "!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chdir(mTargetDir.c_str()) == -1) {
|
||||||
|
throw std::runtime_error("Could not change to target directory!");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string nodeDirHint = "";
|
||||||
|
|
||||||
|
for (unsigned int & mClientNode : mClientNodes) {
|
||||||
|
std::string datasetName = std::string("node_") + inet_ntoa({ mClientNode });
|
||||||
|
mpSampleWriters.emplace_back(std::make_shared<SampleWriter<int16_t>>(datasetName, 2, STEREO_BUF_LEN / 2));
|
||||||
|
nodeDirHint += datasetName + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream hintFile("node_dir_hint.txt", std::ios_base::out);
|
||||||
|
hintFile << nodeDirHint;
|
||||||
|
hintFile.close();
|
||||||
|
|
||||||
|
if (chdir("..") == -1) {
|
||||||
|
throw std::runtime_error("Could not change to target directory!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MultiStreamReceiver::start() {
|
||||||
|
if (mRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
|
||||||
|
createSampleWriters();
|
||||||
|
startNetworking();
|
||||||
|
|
||||||
|
// ----------------------------
|
||||||
|
|
||||||
|
mRunning = true;
|
||||||
|
mRecvThread = std::make_shared<std::thread>(fnRecv, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MultiStreamReceiver::stop() {
|
||||||
|
if (!mRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mRunning = false;
|
||||||
|
mRecvThread->join();
|
||||||
|
close(mSoc);
|
||||||
|
mpSampleWriters.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
#define RECV_BUFFER_SIZE (16000)
|
||||||
|
|
||||||
|
void MultiStreamReceiver::fnRecv(MultiStreamReceiver *pMSR) {
|
||||||
|
// select-related structures
|
||||||
|
fd_set read_fds;
|
||||||
|
timeval tv;
|
||||||
|
|
||||||
|
// receive buffer
|
||||||
|
std::shared_ptr<uint32_t> pWordBuf = std::shared_ptr<uint32_t>(new uint32_t[(RECV_BUFFER_SIZE / 4) + 1]); // to have 4-byte alignment
|
||||||
|
auto * pRecvBuf = reinterpret_cast<uint8_t *>(pWordBuf.get());
|
||||||
|
auto * pAudioPayload = reinterpret_cast<AudioPayload *>(pWordBuf.get());
|
||||||
|
|
||||||
|
while (pMSR->mRunning) {
|
||||||
|
// fd-set
|
||||||
|
FD_ZERO(&read_fds);
|
||||||
|
FD_SET(pMSR->mSoc, &read_fds);
|
||||||
|
|
||||||
|
// timeout
|
||||||
|
tv.tv_sec = 0;
|
||||||
|
tv.tv_usec = 1E+06 / 4;
|
||||||
|
|
||||||
|
// wait for data
|
||||||
|
int fd_cnt = select(pMSR->mSoc + 1, &read_fds, nullptr, nullptr, &tv);
|
||||||
|
|
||||||
|
// if data is available on the socket
|
||||||
|
if ((fd_cnt > 0) && FD_ISSET(pMSR->mSoc, &read_fds)) {
|
||||||
|
|
||||||
|
ssize_t recv_size;
|
||||||
|
if ((recv_size = recv(pMSR->mSoc, pRecvBuf, RECV_BUFFER_SIZE, 0)) <= 0) {
|
||||||
|
throw std::runtime_error("Receive error!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// get sample writer for the specific address
|
||||||
|
std::shared_ptr<SampleWriter<int16_t>> pSampleWriter = nullptr;
|
||||||
|
for (size_t i = 0; i < pMSR->mClientNodes.size(); i++) {
|
||||||
|
if (pMSR->mClientNodes[i] == pAudioPayload->addr) {
|
||||||
|
pSampleWriter = pMSR->mpSampleWriters[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if sample writer is found
|
||||||
|
if (pSampleWriter != nullptr) {
|
||||||
|
pSampleWriter->addSamples(pAudioPayload->pData, { pAudioPayload->timestamp_s, pAudioPayload->timestamp_ns });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
41
src/MultiStreamReceiver.h
Normal file
41
src/MultiStreamReceiver.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// Created by epagris on 2021. 11. 22..
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef WFR_MULTISTREAMRECEIVER_H
|
||||||
|
#define WFR_MULTISTREAMRECEIVER_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include "SampleWriter.h"
|
||||||
|
|
||||||
|
class MultiStreamReceiver {
|
||||||
|
public:
|
||||||
|
static constexpr unsigned short DEFAULT_PORT = 20220; // default port
|
||||||
|
private:
|
||||||
|
std::vector<in_addr_t> mClientNodes; // client node addresses
|
||||||
|
int mSoc; // UDP-socket for receiving samples
|
||||||
|
std::shared_ptr<std::thread> mRecvThread; // thread for reception
|
||||||
|
static void fnRecv(MultiStreamReceiver * pMSR); // routine function running in separate thread
|
||||||
|
bool mRunning;
|
||||||
|
std::vector<std::shared_ptr<SampleWriter<int16_t>>> mpSampleWriters; // sample writers for streams
|
||||||
|
std::string mTargetDir; // target directory
|
||||||
|
private:
|
||||||
|
void startNetworking(); // start networking
|
||||||
|
void createSampleWriters(); // construct sample writers
|
||||||
|
public:
|
||||||
|
const unsigned short port; // port
|
||||||
|
public:
|
||||||
|
explicit MultiStreamReceiver(const std::vector<in_addr_t>& nodes, const std::string& targetDir, unsigned short port = DEFAULT_PORT);
|
||||||
|
void start(); // start reception
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
virtual ~MultiStreamReceiver();
|
||||||
|
// stop reception
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //WFR_MULTISTREAMRECEIVER_H
|
||||||
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
#define DIRSEP '/'
|
#define DIRSEP '/'
|
||||||
|
|
||||||
template<typename T> // ch: number of channels, T: sample datatype
|
template<typename T> // T: sample datatype
|
||||||
class SampleWriter {
|
class SampleWriter {
|
||||||
public:
|
public:
|
||||||
static const std::string TS_FILENAME; // filename for timestamp file
|
static const std::string TS_FILENAME; // filename for timestamp file
|
||||||
|
|||||||
@ -5,17 +5,18 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <cstring>
|
||||||
#include "ServerBeacon.h"
|
#include "ServerBeacon.h"
|
||||||
|
|
||||||
const in_addr ServerBeacon::DEFAULT_MULTICAST_ADDR = {inet_addr("224.0.2.21")};
|
const in_addr ServerBeacon::DEFAULT_MULTICAST_ADDR = {inet_addr("224.0.2.21")};
|
||||||
|
|
||||||
ServerBeacon::ServerBeacon() : mRunning(false) {
|
ServerBeacon::ServerBeacon() : mRunning(false), mScanCallback(nullptr) {
|
||||||
setMulticastAddr(DEFAULT_MULTICAST_ADDR);
|
setMulticastAddr(DEFAULT_MULTICAST_ADDR);
|
||||||
setPort(DEFAULT_PORT);
|
setPort(DEFAULT_PORT);
|
||||||
setAnnouncePeriod(DEFAULT_ANNOUNCE_PERIOD_MS);
|
setAnnouncePeriod(DEFAULT_ANNOUNCE_PERIOD_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerBeacon::ServerBeacon(const in_addr &addr, unsigned short port, size_t announcePeriod_ms) : mRunning(false) {
|
ServerBeacon::ServerBeacon(const in_addr &addr, unsigned short port, size_t announcePeriod_ms) : mRunning(false), mScanCallback(nullptr) {
|
||||||
setMulticastAddr(addr);
|
setMulticastAddr(addr);
|
||||||
setPort(port);
|
setPort(port);
|
||||||
setAnnouncePeriod(announcePeriod_ms);
|
setAnnouncePeriod(announcePeriod_ms);
|
||||||
@ -62,18 +63,28 @@ void ServerBeacon::startBeacon() {
|
|||||||
int opt = 1;
|
int opt = 1;
|
||||||
setsockopt(mBeaconSoc, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
|
setsockopt(mBeaconSoc, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
|
||||||
|
|
||||||
// bind to ip-address and port
|
// fill-in ip-address and port
|
||||||
sockaddr_in addr;
|
mMulticastAddr.sin_family = AF_INET;
|
||||||
addr.sin_family = AF_INET;
|
mMulticastAddr.sin_addr = mAddr;
|
||||||
addr.sin_addr = mAddr;
|
mMulticastAddr.sin_port = htons(mBeaconPort);
|
||||||
addr.sin_port = htons(mBeaconPort);
|
|
||||||
|
|
||||||
if (bind(mBeaconSoc, (const sockaddr *) &addr, sizeof(addr)) == -1) {
|
// bind to port
|
||||||
|
sockaddr_in bindAddr = mMulticastAddr;
|
||||||
|
bindAddr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||||
|
|
||||||
|
if (bind(mBeaconSoc, (const sockaddr *) &bindAddr, sizeof(sockaddr_in)) == -1) {
|
||||||
std::cerr << "Could not bind to IP address and/or port in " << __FUNCTION__ << "!" << std::endl;
|
std::cerr << "Could not bind to IP address and/or port in " << __FUNCTION__ << "!" << std::endl;
|
||||||
close(mBeaconSoc);
|
close(mBeaconSoc);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// join multicast group
|
||||||
|
ip_mreq mreq;
|
||||||
|
mreq.imr_multiaddr = mAddr;
|
||||||
|
mIFAddr.s_addr = mreq.imr_interface.s_addr = inet_addr(mIFAddrStr.c_str());
|
||||||
|
setsockopt(mBeaconSoc, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));
|
||||||
|
setsockopt(mBeaconSoc, IPPROTO_IP, IP_MULTICAST_IF, &mreq.imr_interface, sizeof(in_addr));
|
||||||
|
|
||||||
// the beacon is running now!
|
// the beacon is running now!
|
||||||
mRunning = true;
|
mRunning = true;
|
||||||
|
|
||||||
@ -82,11 +93,27 @@ void ServerBeacon::startBeacon() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ServerBeacon::stopBeacon() {
|
void ServerBeacon::stopBeacon() {
|
||||||
|
if (!mRunning){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
mRunning = false;
|
mRunning = false;
|
||||||
|
mpBeaconThread->join();
|
||||||
|
close(mBeaconSoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::list<in_addr> ServerBeacon::getNodesOnNetwork() {
|
std::list<in_addr> ServerBeacon::getNodesOnNetwork() {
|
||||||
return std::list<in_addr>();
|
std::list<in_addr> nodeAddrs;
|
||||||
|
|
||||||
|
mBeaconMtx.lock();
|
||||||
|
|
||||||
|
for (auto nodeInfo : mNodes) {
|
||||||
|
nodeAddrs.push_back(nodeInfo.addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
mBeaconMtx.unlock();
|
||||||
|
|
||||||
|
return nodeAddrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -96,38 +123,143 @@ void ServerBeacon::fn_BeaconThread(ServerBeacon *pSB) {
|
|||||||
// beacon message used to send and receive information
|
// beacon message used to send and receive information
|
||||||
BeaconMsg msg;
|
BeaconMsg msg;
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
// select-related structures
|
// select-related structures
|
||||||
fd_set read_fds;
|
fd_set read_fds;
|
||||||
timeval tv;
|
timeval tv;
|
||||||
|
|
||||||
// fill-in select parameters
|
// list of detected nodes
|
||||||
FD_ZERO(&read_fds);
|
std::list<ClientNodeInfo> clNodes;
|
||||||
FD_SET(pSB->mBeaconSoc, &read_fds);
|
|
||||||
|
|
||||||
// fill-in timeout value
|
|
||||||
tv.tv_sec = pSB->mAnnPeriod_ms / 1000;
|
|
||||||
tv.tv_usec = (pSB->mAnnPeriod_ms % 1000) * 1000;
|
|
||||||
|
|
||||||
while (pSB->mRunning) {
|
while (pSB->mRunning) {
|
||||||
|
msg.senderAddr = pSB->mIFAddr; // set our address
|
||||||
msg.server_nClient = 1; // we are the server
|
msg.server_nClient = 1; // we are the server
|
||||||
msg.terminalPort = 0; // we have no inbound terminal port
|
msg.terminalPort = 0; // we have no inbound terminal port
|
||||||
|
|
||||||
send(pSB->mBeaconSoc, &msg, sizeof(BeaconMsg), 0); // send the announce message
|
sendto(pSB->mBeaconSoc, &msg, sizeof(BeaconMsg), 0, (sockaddr *) &pSB->mMulticastAddr, sizeof(sockaddr_in)); // send the announce message
|
||||||
|
|
||||||
// ------------------
|
// ------------------
|
||||||
|
|
||||||
// copy parameters
|
// fill-in select parameters
|
||||||
fd_set cur_rfds = read_fds;
|
FD_ZERO(&read_fds);
|
||||||
timeval cur_tv = tv;
|
FD_SET(pSB->mBeaconSoc, &read_fds);
|
||||||
|
|
||||||
while (select(1, &cur_rfds, nullptr, nullptr, &cur_tv) != 0) {
|
// fill-in timeout value
|
||||||
|
tv.tv_sec = pSB->mAnnPeriod_ms / 1000;
|
||||||
|
tv.tv_usec = (pSB->mAnnPeriod_ms % 1000) * 1000;
|
||||||
|
|
||||||
|
// clear list
|
||||||
|
clNodes.clear();
|
||||||
|
|
||||||
|
while (select(pSB->mBeaconSoc + 1, &read_fds, nullptr, nullptr, &tv) != 0) {
|
||||||
// receive response
|
// receive response
|
||||||
recv(pSB->mBeaconSoc, &msg, sizeof(BeaconMsg), 0);
|
recv(pSB->mBeaconSoc, &msg, sizeof(BeaconMsg), 0);
|
||||||
|
|
||||||
// store response
|
// consider only client responses
|
||||||
|
if (msg.server_nClient == 0) {
|
||||||
|
// store response
|
||||||
|
ClientNodeInfo clInfo;
|
||||||
|
clInfo.addr = msg.senderAddr;
|
||||||
|
clInfo.terminalPort = msg.terminalPort;
|
||||||
|
|
||||||
|
clNodes.push_back(clInfo);
|
||||||
|
|
||||||
|
//std::cout << inet_ntoa(clInfo.addr) << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------
|
||||||
|
|
||||||
|
// fill-in select parameters
|
||||||
|
FD_ZERO(&read_fds);
|
||||||
|
FD_SET(pSB->mBeaconSoc, &read_fds);
|
||||||
|
|
||||||
|
// fill-in timeout value
|
||||||
|
tv.tv_sec = pSB->mAnnPeriod_ms / 1000;
|
||||||
|
tv.tv_usec = (pSB->mAnnPeriod_ms % 1000) * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copy list of detected nodes
|
||||||
|
pSB->mBeaconMtx.lock();
|
||||||
|
|
||||||
|
pSB->mNodes = clNodes;
|
||||||
|
|
||||||
|
pSB->mBeaconMtx.unlock();
|
||||||
|
|
||||||
|
// invoke callback
|
||||||
|
if (pSB->mScanCallback != nullptr) {
|
||||||
|
(*pSB->mScanCallback)(pSB);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ServerBeacon::setScanCallback(const std::shared_ptr<CallbackBase<ServerBeacon *>>& scanCB) {
|
||||||
|
mScanCallback = scanCB;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerBeacon::setInterfaceAddr(const std::string &interfaceAddr) {
|
||||||
|
mIFAddrStr = interfaceAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ServerBeacon::getInterfaceAddr() const {
|
||||||
|
return mIFAddrStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned short ServerBeacon::getNodeNettermPort(in_addr addr) {
|
||||||
|
mBeaconMtx.lock();
|
||||||
|
|
||||||
|
int nettermPort = -1;
|
||||||
|
|
||||||
|
for (const auto& nodeInfo : mNodes) {
|
||||||
|
if (nodeInfo.addr.s_addr == addr.s_addr) {
|
||||||
|
nettermPort = nodeInfo.terminalPort;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mBeaconMtx.unlock();
|
||||||
|
|
||||||
|
if (nettermPort == -1) {
|
||||||
|
throw std::runtime_error(std::string("Node not found in database! (") + inet_ntoa(addr) + ")!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (unsigned short)nettermPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerBeacon::sendNettermCmd(in_addr addr, unsigned short port, const std::string &cmd) {
|
||||||
|
int soc = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||||
|
if (soc == -1) {
|
||||||
|
throw std::runtime_error("Could not create TCP socket to execute netterm commands!");
|
||||||
|
}
|
||||||
|
|
||||||
|
sockaddr_in serverAddr;
|
||||||
|
memset(&serverAddr, 0, sizeof(sockaddr_in));
|
||||||
|
serverAddr.sin_addr = addr;
|
||||||
|
serverAddr.sin_port = htons(port);
|
||||||
|
serverAddr.sin_family = AF_INET;
|
||||||
|
|
||||||
|
if (connect(soc, (const sockaddr *)&serverAddr, sizeof(sockaddr_in)) == -1) {
|
||||||
|
throw std::runtime_error(std::string("Could not connect to netterm! (") + inet_ntoa(addr) + ":" + std::to_string(port) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string nodeftty_cmd = "nodeftty";
|
||||||
|
send(soc, "nodeftty", nodeftty_cmd.size(), 0);
|
||||||
|
|
||||||
|
// send command
|
||||||
|
send(soc, cmd.c_str(), cmd.size(), 0);
|
||||||
|
|
||||||
|
close(soc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerBeacon::execCmdOnNode(in_addr addr, const std::string &cmd) {
|
||||||
|
unsigned short port = getNodeNettermPort(addr);
|
||||||
|
sendNettermCmd(addr, port, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerBeacon::execCmdOnAllTerms(const std::string &cmd) {
|
||||||
|
mBeaconMtx.lock();
|
||||||
|
|
||||||
|
for (const auto& nodeInfo : mNodes) {
|
||||||
|
sendNettermCmd(nodeInfo.addr, nodeInfo.terminalPort, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
mBeaconMtx.unlock();
|
||||||
|
}
|
||||||
@ -10,6 +10,8 @@
|
|||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include "Callback.h"
|
||||||
|
|
||||||
class ServerBeacon {
|
class ServerBeacon {
|
||||||
public:
|
public:
|
||||||
@ -24,20 +26,28 @@ private:
|
|||||||
};
|
};
|
||||||
private:
|
private:
|
||||||
int mBeaconSoc; // beacon socket
|
int mBeaconSoc; // beacon socket
|
||||||
in_addr mAddr; // address
|
in_addr mAddr; // multicast address
|
||||||
|
sockaddr_in mMulticastAddr; // multicast address structure
|
||||||
unsigned short mBeaconPort; // port
|
unsigned short mBeaconPort; // port
|
||||||
size_t mAnnPeriod_ms; // announce period
|
size_t mAnnPeriod_ms; // announce period
|
||||||
std::list<ClientNodeInfo> mNodes; // nodes
|
std::list<ClientNodeInfo> mNodes; // nodes
|
||||||
bool mRunning; // does the beacon operate?
|
bool mRunning; // does the beacon operate?
|
||||||
|
std::mutex mBeaconMtx; // beacon mutex
|
||||||
|
std::string mIFAddrStr; // multicast interface address STRING
|
||||||
|
in_addr mIFAddr; // multicast interface address
|
||||||
|
std::shared_ptr<CallbackBase<ServerBeacon *>> mScanCallback; // scan callback
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<std::thread> mpBeaconThread; // thread running the beacon
|
std::shared_ptr<std::thread> mpBeaconThread; // thread running the beacon
|
||||||
static void fn_BeaconThread(ServerBeacon * pSB); // function running in beacon thread
|
static void fn_BeaconThread(ServerBeacon * pSB); // function running in beacon thread
|
||||||
private:
|
private:
|
||||||
// structure for beacon MSG
|
// structure for beacon MSG
|
||||||
struct BeaconMsg {
|
struct BeaconMsg {
|
||||||
uint8_t server_nClient; // server or client
|
in_addr senderAddr; // address of sender
|
||||||
uint16_t terminalPort; // port of terminal
|
uint16_t terminalPort; // port of terminal
|
||||||
|
uint8_t server_nClient; // server or client
|
||||||
};
|
};
|
||||||
|
private:
|
||||||
|
void sendNettermCmd(in_addr addr, unsigned short port, const std::string& cmd); // send netterm cmd
|
||||||
public:
|
public:
|
||||||
ServerBeacon(); // constr.
|
ServerBeacon(); // constr.
|
||||||
explicit ServerBeacon(const in_addr& addr, unsigned short port, size_t announcePeriod_ms); // constr. with beacon port and announce period
|
explicit ServerBeacon(const in_addr& addr, unsigned short port, size_t announcePeriod_ms); // constr. with beacon port and announce period
|
||||||
@ -47,9 +57,15 @@ public:
|
|||||||
unsigned short getPort() const; // get beacon port
|
unsigned short getPort() const; // get beacon port
|
||||||
void setAnnouncePeriod(size_t period_ms); // set announce period
|
void setAnnouncePeriod(size_t period_ms); // set announce period
|
||||||
size_t getAnnouncePeriod() const; // get announce period
|
size_t getAnnouncePeriod() const; // get announce period
|
||||||
|
void setInterfaceAddr(const std::string &interfaceAddr); // set multicast interface address
|
||||||
|
std::string getInterfaceAddr() const; // get multicast interface address
|
||||||
void startBeacon(); // start the beacon
|
void startBeacon(); // start the beacon
|
||||||
void stopBeacon(); // stop the beacon
|
void stopBeacon(); // stop the beacon
|
||||||
std::list<in_addr> getNodesOnNetwork(); // get nodes connected to the same network
|
std::list<in_addr> getNodesOnNetwork(); // get nodes connected to the same network
|
||||||
|
unsigned short getNodeNettermPort(in_addr addr); // get network terminal port of ndoe
|
||||||
|
void setScanCallback(const std::shared_ptr<CallbackBase<ServerBeacon *>>& scanCB); // set scan callback
|
||||||
|
void execCmdOnNode(in_addr addr, const std::string& cmd); // execute command on a node through netterm
|
||||||
|
void execCmdOnAllTerms(const std::string& cmd); // multicast command to all netterms
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ typedef int16_t AudioSampleType;
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t timestamp_s, timestamp_ns; // timestamp
|
uint32_t timestamp_s, timestamp_ns; // timestamp
|
||||||
uint32_t sample_cnt; // count of samples in packet
|
uint32_t sample_cnt; // count of samples in packet
|
||||||
|
in_addr_t addr; // client node address
|
||||||
AudioSampleType pData[STEREO_BUF_LEN]; // buffer for stereo data
|
AudioSampleType pData[STEREO_BUF_LEN]; // buffer for stereo data
|
||||||
} AudioPayload;
|
} AudioPayload;
|
||||||
|
|
||||||
|
|||||||
15
src/globals.h
Normal file
15
src/globals.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// Created by epagris on 2021. 11. 16..
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef WFR_GLOBALS_H
|
||||||
|
#define WFR_GLOBALS_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "ServerBeacon.h"
|
||||||
|
|
||||||
|
struct Globals {
|
||||||
|
std::shared_ptr<ServerBeacon> beacon; // beacon to detect client nodes
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //WFR_GLOBALS_H
|
||||||
Loading…
x
Reference in New Issue
Block a user