removed socket.io, added webrtc audio stream support and deep sleep. for webrtc we will use libdatachannel

This commit is contained in:
tdv
2025-10-09 16:27:32 +03:00
parent 5af104acf5
commit 78b7d495d4
2024 changed files with 1332 additions and 581855 deletions

View File

@@ -1,102 +1,135 @@
// src/Services/AudioStreamService.h
#pragma once
#include <sio_client.h>
#include <atomic>
#include <spdlog/spdlog.h>
#include <chrono>
#include <filesystem>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include <cstdio>
#include <array>
#include <spdlog/spdlog.h>
#include "WhipClient.h"
#include "ConfigService.h"
namespace snoop {
class AudioStreamService {
std::shared_ptr<sio::client> m_client;
std::string m_guid;
std::atomic<bool> m_isConnected = false;
std::atomic<bool> m_isInStreaming = false;
std::vector<char> m_audioBuffer;
std::mutex m_bufferMutex;
const unsigned long long int FLUSH_PERIOD = 5000;
const std::vector<char> PACKET_DELIMITER = {
static_cast<char>(0xFF),
static_cast<char>(0xFE),
static_cast<char>(0xFD),
static_cast<char>(0xFC)
};
unsigned long long int m_flushedAt = 0;
std::shared_ptr<ConfigService> m_cfg;
// WHIP
std::unique_ptr<WhipClient> m_whip;
std::mutex m_whipMutex;
std::string m_tmpKeyPath; // temp key extracted from keyctl (deleted on Stop)
public:
explicit AudioStreamService( std::shared_ptr<sio::client> client, std::string guid ) :
m_client( std::move( client ) ),
m_guid( std::move( guid ) ) {
SetupEventListeners();
}
explicit AudioStreamService(std::shared_ptr<ConfigService> cfg)
: m_cfg(std::move(cfg)) {}
~AudioStreamService() {
this->m_isConnected = false;
this->m_isInStreaming = false;
StopWhip();
}
void SendAudioData( const char* input, size_t size ) {
if( !this->m_isConnected || !this->m_isInStreaming ) {
return;
// Feed raw PCM (float32 interleaved), frames = samples per channel
void OnPCM(const float* interleaved, size_t frames) {
std::lock_guard lk(m_whipMutex);
if (m_whip) m_whip->PushPCM(interleaved, frames);
}
bool StartWhip(const std::string& whipUrl, int sampleRate=48000, int channels=1) {
std::lock_guard lk(m_whipMutex);
if (m_whip) {
spdlog::info("WHIP already started");
return true;
}
if (!m_cfg) {
spdlog::error("StartWhip requires ConfigService");
return false;
}
std::lock_guard lock( m_bufferMutex );
this->m_audioBuffer.insert(m_audioBuffer.end(), PACKET_DELIMITER.begin(), PACKET_DELIMITER.end());
this->m_audioBuffer.insert( m_audioBuffer.end(), input, input + size );
// certs from enrollment
std::filesystem::path ca = "/etc/iot/keys/issuing_ca.pem";
if (!std::filesystem::exists(ca)) ca = "/etc/iot/keys/ca_chain.pem";
std::filesystem::path crt = "/etc/iot/keys/device.crt.pem";
auto now = std::chrono::system_clock::now();
auto currentTime = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch() ).count();
if( currentTime >= this->m_flushedAt + FLUSH_PERIOD ) {
FlushBuffer();
// extract client key via keyctl
auto tmpKey = ExtractClientKeyTemp();
if (!tmpKey) {
spdlog::error("Cannot extract client key for WHIP (keyctl user iot-client-key)");
return false;
}
WhipClient::Params p{
.whipUrl = whipUrl,
.caPath = ca.string(),
.crtPath = crt.string(),
.keyPath = tmpKey->string(),
.sampleRate= sampleRate,
.channels = channels
};
m_whip = std::make_unique<WhipClient>(p);
try {
m_whip->Start();
spdlog::info("WHIP started");
m_tmpKeyPath = *tmpKey;
return true;
} catch (const std::exception& e) {
spdlog::error("WHIP start failed: {}", e.what());
std::error_code ec; std::filesystem::remove(*tmpKey, ec);
m_whip.reset();
return false;
}
}
void StopWhip() {
std::lock_guard lk(m_whipMutex);
if (m_whip) {
m_whip->Stop();
m_whip.reset();
}
if (!m_tmpKeyPath.empty()) {
std::error_code ec; std::filesystem::remove(m_tmpKeyPath, ec);
m_tmpKeyPath.clear();
}
}
private:
void SetupEventListeners() {
this->m_client->set_open_listener( [this]() {
spdlog::info( "Connected to server" );
this->m_client->socket( "/livestream" )->emit( "register_device", m_guid );
this->m_isConnected = true;
} );
static std::optional<std::filesystem::path> ExtractClientKeyTemp() {
auto exec = [](const std::string& cmd) {
std::array<char, 4096> buf{};
std::string out;
FILE* pipe = popen((cmd + " 2>&1").c_str(), "r");
if (!pipe) return std::string{};
while (fgets(buf.data(), (int)buf.size(), pipe) != nullptr) out.append(buf.data());
pclose(pipe);
return out;
};
auto trim = [](std::string s){
auto b=s.find_first_not_of(" \t\r\n"), e=s.find_last_not_of(" \t\r\n");
return (b==std::string::npos) ? std::string{} : s.substr(b, e-b+1);
};
this->m_client->set_close_listener( [this]( sio::client::close_reason const& reason ) {
this->m_isConnected = false;
this->m_isInStreaming = false;
spdlog::info( "Disconnected from server" );
} );
std::string id = trim(exec("keyctl search @s user iot-client-key | tail -n1"));
if (id.empty()) return std::nullopt;
this->m_client->set_fail_listener( []() {
spdlog::info( "Failed to connect to server" );
} );
this->m_client->socket( "/livestream" )->on( "start_streaming", [this]( sio::event& ev ) {
spdlog::info( "Start streaming command received" );
this->m_isInStreaming = true;
auto now = std::chrono::system_clock::now();
this->m_flushedAt = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch() ).count();
} );
this->m_client->socket( "/livestream" )->on( "stop_streaming", [this]( sio::event& ev ) {
spdlog::info( "Stop streaming command received" );
this->m_isInStreaming = false;
std::lock_guard lock( this->m_bufferMutex );
this->m_audioBuffer.clear();
} );
}
void FlushBuffer() {
if( this->m_audioBuffer.empty() ) {
return;
char tmpl[] = "/run/iot-whip-keyXXXXXX";
int fd = mkstemp(tmpl);
if (fd < 0) return std::nullopt;
close(fd);
std::filesystem::path p(tmpl);
exec("keyctl pipe " + id + " > " + p.string());
if (!std::filesystem::exists(p) || std::filesystem::file_size(p) == 0) {
std::error_code ec; std::filesystem::remove(p, ec);
return std::nullopt;
}
this->m_client->socket( "/livestream" )->emit( "audio_data",
std::make_shared<std::string>( this->m_audioBuffer.data(), this->m_audioBuffer.size() )
);
this->m_audioBuffer.clear();
auto now = std::chrono::system_clock::now();
this->m_flushedAt = std::chrono::duration_cast<std::chrono::milliseconds>( now.time_since_epoch() ).count();
return p;
}
};
}
} // namespace snoop