diff --git a/CMakeLists.txt b/CMakeLists.txt index ba6be72..9a73145 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,13 +3,14 @@ project( snoop_device ) set( CMAKE_CXX_STANDARD 23 ) +find_package(OpenSSL REQUIRED) + add_subdirectory( third_party/portaudio ) add_subdirectory( third_party/opus ) add_subdirectory( third_party/cpp-httplib ) add_subdirectory( third_party/nlohmann_json ) add_subdirectory( third_party/spdlog ) add_subdirectory( third_party/libogg ) -add_subdirectory( third_party/socket.io-client-cpp ) add_subdirectory( third_party/libdatachannel ) option(USE_ALSA_ADAPTER "Use ALSA Adapter" OFF) @@ -39,4 +40,17 @@ set( HEADERS add_executable( ${PROJECT_NAME} ${SOURCES} ${HEADERS} ) target_include_directories( ${PROJECT_NAME} PRIVATE src ) -target_link_libraries( ${PROJECT_NAME} PRIVATE portaudio_static opus sioclient_tls nlohmann_json spdlog::spdlog_header_only Ogg::ogg httplib::httplib ) +# Enable HTTPS (mTLS) support in cpp-httplib across ALL TUs of this target +target_compile_definitions(${PROJECT_NAME} PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT) + +target_link_libraries( ${PROJECT_NAME} PRIVATE + portaudio_static + opus + nlohmann_json + spdlog::spdlog_header_only + Ogg::ogg + httplib::httplib + datachannel + OpenSSL::SSL + OpenSSL::Crypto +) diff --git a/load-iot-key.sh b/load-iot-key.sh index 62ac2b6..fbb5f99 100644 --- a/load-iot-key.sh +++ b/load-iot-key.sh @@ -2,6 +2,12 @@ set -e CPU_SERIAL=$(awk '/Serial/ {print $3}' /proc/cpuinfo) + +# Fallback if CPU_SERIAL is empty +if [[ -z "$CPU_SERIAL" ]]; then + CPU_SERIAL="999999999999" +fi + KEK=$(echo -n "$CPU_SERIAL" | \ openssl dgst -sha256 -hmac "server-provided-salt" | \ awk '{print $2}') diff --git a/src/Services/AudioStreamService.h b/src/Services/AudioStreamService.h index e91b4ed..dfeaf32 100644 --- a/src/Services/AudioStreamService.h +++ b/src/Services/AudioStreamService.h @@ -37,9 +37,9 @@ public: } // Feed raw PCM (float32 interleaved), frames = samples per channel - void OnPCM(const float* interleaved, size_t frames) { + void OnOpus(const unsigned char* opusData, size_t opusBytes, int pcmFramesPerChannel) { std::lock_guard lk(m_whipMutex); - if (m_whip) m_whip->PushPCM(interleaved, frames); + if (m_whip) m_whip->PushOpus(opusData, opusBytes, pcmFramesPerChannel); } bool StartWhip(const std::string& whipUrl, int sampleRate=48000, int channels=1) { diff --git a/src/Services/AudioWriterService.h b/src/Services/AudioWriterService.h index 6c79087..b217f7d 100644 --- a/src/Services/AudioWriterService.h +++ b/src/Services/AudioWriterService.h @@ -207,7 +207,7 @@ namespace snoop return u; } - std::unique_ptr MakeClientMTLS(const Url &u, + std::unique_ptr MakeClientMTLS(const Url &u, const std::filesystem::path &ca, const std::filesystem::path &crt, const std::filesystem::path &key) @@ -215,10 +215,9 @@ namespace snoop if (u.scheme == "https") { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - auto cli = std::make_unique(u.host.c_str(), u.port); + auto cli = std::make_unique(u.host.c_str(), u.port, crt.string().c_str(), key.string().c_str(), std::string()); cli->enable_server_certificate_verification(true); cli->set_ca_cert_path(ca.string().c_str()); - cli->set_client_cert_file(crt.string().c_str(), key.string().c_str(), nullptr); cli->set_connection_timeout(10); cli->set_read_timeout(120); cli->set_write_timeout(120); @@ -227,14 +226,14 @@ namespace snoop throw std::runtime_error("HTTPS baseUrl but CPPHTTPLIB_OPENSSL_SUPPORT is not enabled"); #endif } - else - { - auto cli = std::make_unique(u.host.c_str(), u.port); - cli->set_connection_timeout(10); - cli->set_read_timeout(120); - cli->set_write_timeout(120); - return cli; - } + // else + // { + // auto cli = std::make_unique(u.host.c_str(), u.port); + // cli->set_connection_timeout(10); + // cli->set_read_timeout(120); + // cli->set_write_timeout(120); + // return cli; + // } } // ----------------------------- Existing logic (adjusted) ----------------------------- @@ -421,7 +420,7 @@ namespace snoop } } - bool SendRecordedFileMTLS(httplib::Client &client, + bool SendRecordedFileMTLS(httplib::SSLClient &client, const std::string &filepath, unsigned long long int startedAt, unsigned long long int stoppedAt) diff --git a/src/Services/DeviceControlService.h b/src/Services/DeviceControlService.h index 6c49ccb..3a3c71a 100644 --- a/src/Services/DeviceControlService.h +++ b/src/Services/DeviceControlService.h @@ -1,5 +1,5 @@ #pragma once - +#define CPPHTTPLIB_OPENSSL_SUPPORT #include #include #include @@ -90,7 +90,7 @@ namespace snoop // If already inside window -> enter immediately if (now >= startMs && now < stopMs) { - EnterDeepSleepUntil(stopMs); + this->EnterDeepSleepUntil(stopMs); } else { @@ -101,6 +101,42 @@ namespace snoop } } + static std::optional ParseRfc3339UtcToMs(const std::string &s) + { + // Minimal, robust-ish parser for "YYYY-MM-DDTHH:MM:SS[.frac]Z" + // For production, consider date::parse or a small RFC3339 lib. + std::tm tm{}; + char z = 'Z'; + double frac = 0.0; + int n = 0; + // accept with optional fractional seconds + if (sscanf(s.c_str(), "%d-%d-%dT%d:%d:%d%n", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, + &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &n) != 6) + return std::nullopt; + tm.tm_year -= 1900; + tm.tm_mon -= 1; + const char *p = s.c_str() + n; + if (*p == '.') + { + // consume fractional + ++p; + while (isdigit(*p)) + ++p; // ignore exact fraction + } + if (*p != 'Z') + return std::nullopt; + time_t tt = timegm(&tm); + if (tt < 0) + return std::nullopt; + return (long long)tt * 1000LL; + } + + static long long NowMs() + { + using namespace std::chrono; + return duration_cast(system_clock::now().time_since_epoch()).count(); + } + private: std::shared_ptr m_cfg; Handlers m_handlers; @@ -161,42 +197,6 @@ namespace snoop return u; } - static long long NowMs() - { - using namespace std::chrono; - return duration_cast(system_clock::now().time_since_epoch()).count(); - } - - static std::optional ParseRfc3339UtcToMs(const std::string &s) - { - // Minimal, robust-ish parser for "YYYY-MM-DDTHH:MM:SS[.frac]Z" - // For production, consider date::parse or a small RFC3339 lib. - std::tm tm{}; - char z = 'Z'; - double frac = 0.0; - int n = 0; - // accept with optional fractional seconds - if (sscanf(s.c_str(), "%d-%d-%dT%d:%d:%d%n", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, - &tm.tm_hour, &tm.tm_min, &tm.tm_sec, &n) != 6) - return std::nullopt; - tm.tm_year -= 1900; - tm.tm_mon -= 1; - const char *p = s.c_str() + n; - if (*p == '.') - { - // consume fractional - ++p; - while (isdigit(*p)) - ++p; // ignore exact fraction - } - if (*p != 'Z') - return std::nullopt; - time_t tt = timegm(&tm); - if (tt < 0) - return std::nullopt; - return (long long)tt * 1000LL; - } - static std::optional JsonTimeToMs(const nlohmann::json &j) { if (j.contains("start_ms") && j.contains("stop_ms")) @@ -248,19 +248,17 @@ namespace snoop } // Create HTTPS client configured for mTLS (or HTTP if base is http) - std::unique_ptr MakeClient(const Url &u, - const std::filesystem::path &ca, - const std::filesystem::path &crt, - const std::filesystem::path &key) + std::unique_ptr MakeClient(const Url &u, + const std::filesystem::path &ca, + const std::filesystem::path &crt, + const std::filesystem::path &key) { if (u.scheme == "https") { #ifdef CPPHTTPLIB_OPENSSL_SUPPORT - auto cli = std::make_unique(u.host.c_str(), u.port); + auto cli = std::make_unique(u.host.c_str(), u.port, crt.string().c_str(), key.string().c_str(), std::string()); cli->enable_server_certificate_verification(true); cli->set_ca_cert_path(ca.string().c_str()); - cli->set_client_cert_file(crt.string().c_str(), key.string().c_str(), nullptr); - // Recommended timeouts for long polling-ish flows cli->set_connection_timeout(10); cli->set_read_timeout(60); cli->set_write_timeout(60); @@ -269,14 +267,14 @@ namespace snoop throw std::runtime_error("CPPHTTPLIB_OPENSSL_SUPPORT not enabled but https URL provided"); #endif } - else - { - auto cli = std::make_unique(u.host.c_str(), u.port); - cli->set_connection_timeout(10); - cli->set_read_timeout(60); - cli->set_write_timeout(60); - return cli; - } + // else + // { + // auto cli = std::make_unique(u.host.c_str(), u.port); + // cli->set_connection_timeout(10); + // cli->set_read_timeout(60); + // cli->set_write_timeout(60); + // return cli; + // } } // simple jittered sleep @@ -375,7 +373,7 @@ namespace snoop return tasks; } - void PostResult(httplib::Client &cli, const std::string &guid, + void PostResult(httplib::SSLClient &cli, const std::string &guid, uint64_t taskId, bool success, const std::string &resultJson, const std::string &err) { @@ -431,7 +429,7 @@ namespace snoop catch (const std::exception &e) { spdlog::error("Key extraction failed: {}", e.what()); - SleepWithJitterOnce(rng, m_cfg->GetPollingSeconds(), m_cfg->GetJitterSeconds()); + SleepWithJitterOnce(rng, m_cfg->GetPollingInterwall(), m_cfg->GetJitter()); continue; } const auto now = NowMs(); @@ -512,7 +510,7 @@ namespace snoop std::filesystem::remove(*tmpKey, ec); } - SleepWithJitterOnce(rng, m_cfg->GetPollingSeconds(), m_cfg->GetJitterSeconds()); + SleepWithJitterOnce(rng, m_cfg->GetPollingInterwall(), m_cfg->GetJitter()); } } } diff --git a/src/Services/EnrollmentService.h b/src/Services/EnrollmentService.h index 6de7e13..ef48884 100644 --- a/src/Services/EnrollmentService.h +++ b/src/Services/EnrollmentService.h @@ -136,7 +136,7 @@ public: { std::filesystem::create_directories(keystoreDir); const std::string cmd = - "openssl enc -aes-256-gcm -pbkdf2 -salt " + "openssl enc -aes-256-cbc -pbkdf2 -salt " "-pass pass:" + kek + " " "-in " + keyName + " " "-out " + encKeyPath.string() + " " diff --git a/src/Services/WhipClient.h b/src/Services/WhipClient.h index 7bdeffd..0fc4a1b 100644 --- a/src/Services/WhipClient.h +++ b/src/Services/WhipClient.h @@ -1,6 +1,6 @@ // src/Services/WhipClient.h #pragma once - +#define CPPHTTPLIB_OPENSSL_SUPPORT #include #include #include @@ -11,53 +11,54 @@ #include #include #include +#include +#include +#include #include #include #include // build with CPPHTTPLIB_OPENSSL_SUPPORT -#include // libdatachannel +#include +#include +#include +#include -namespace snoop { +namespace snoop +{ -class WhipClient { -public: - struct Params { - std::string whipUrl; // full WHIP endpoint (may already include ?token=...) - std::string caPath; // CA chain - std::string crtPath; // client cert - std::string keyPath; // client key (temp extracted from keyctl) - int sampleRate = 48000; - int channels = 1; - }; + class WhipClient + { + public: + struct Params + { + std::string whipUrl; // full WHIP endpoint (may include ?token=...) + std::string caPath; // CA chain + std::string crtPath; // client cert + std::string keyPath; // client key (temp extracted from keyctl) + int sampleRate = 48000; + int channels = 1; // 1 or 2; timestamp advance uses PCM frames per channel + }; - explicit WhipClient(Params p) - : m_p(std::move(p)) {} + explicit WhipClient(Params p) : m_p(std::move(p)) {} - ~WhipClient() { - Stop(); - } + ~WhipClient() { Stop(); } - void Start() { - std::lock_guard lk(m_mtx); - if (m_started) return; + void Start() + { + std::lock_guard lk(m_mtx); + if (m_started) + return; - // ----- PeerConnection ----- - rtc::Configuration cfg; - // No STUN/TURN required for MediaMTX WHIP (server is public/ICE-lite). - // cfg.iceServers = { }; // default + rtc::Configuration cfg; + m_pc = std::make_shared(cfg); - m_pc = std::make_shared(cfg); - - // Optional: observe connection state / ICE - m_pc->onStateChange([this](rtc::PeerConnection::State s){ - spdlog::info("WHIP pc state: {}", (int)s); - }); - m_pc->onGatheringStateChange([this](rtc::PeerConnection::GatheringState s){ - spdlog::info("WHIP gathering state: {}", (int)s); - }); - m_pc->onLocalDescription([this](rtc::Description desc){ - // When we have the local SDP, POST (WHIP) to get the answer + m_pc->onStateChange([this](rtc::PeerConnection::State s) + { spdlog::info("WHIP pc state: {}", (int)s); }); + m_pc->onGatheringStateChange([this](rtc::PeerConnection::GatheringState s) + { spdlog::info("WHIP gathering state: {}", (int)s); }); + m_pc->onLocalDescription([this](rtc::Description desc) + { try { const std::string offer = std::string(desc); auto [answer, resourceUrl] = PostOfferWHIP(offer); @@ -66,158 +67,171 @@ public: spdlog::info("WHIP remote description set"); } catch (const std::exception& e) { spdlog::error("WHIP POST offer failed: {}", e.what()); + } }); + m_pc->onLocalCandidate([this](rtc::Candidate c) + { + if (!m_resourceUrl) return; + try { PatchCandidateWHIP(c); } + catch (const std::exception& e) { spdlog::warn("WHIP PATCH candidate failed: {}", e.what()); } }); + + // Create a sendonly audio track + rtc::Description::Audio audioDesc("audio", rtc::Description::Direction::SendOnly); + // Opus PT typically negotiated to 111 by browsers; we'll use 111 in RTP header + m_track = m_pc->addTrack(audioDesc); + + // Initialize RTP state (random SSRC/seq) + std::mt19937 rng{std::random_device{}()}; + m_ssrc = std::uniform_int_distribution()(rng); + m_seq = std::uniform_int_distribution()(rng); + m_ts = 0; // RTP clock for Opus is 48kHz + + // Create SDP offer (triggers onLocalDescription) + m_pc->setLocalDescription(); + + m_started = true; + } + + void Stop() + { + std::lock_guard lk(m_mtx); + if (!m_started) + return; + + m_track.reset(); + m_pc.reset(); + + m_resourceUrl.reset(); + m_started = false; + } + + // Call this from your Opus encoder callback. + // opusData: encoded Opus frame bytes + // opusBytes: length + // pcmFramesPerChannel: number of PCM samples per channel represented by this Opus frame (e.g., 960 for 20ms @48k). + void PushOpus(const unsigned char *opusData, size_t opusBytes, int pcmFramesPerChannel) + { + std::lock_guard lk(m_mtx); + if (!m_track || !m_started) + return; + + // Build RTP header (12 bytes) + std::array rtp{}; + rtp[0] = 0x80; // V=2, P=0, X=0, CC=0 + rtp[1] = 0x80 | (m_pt & 0x7F); // M=1 (end of frame), PT + rtp[2] = uint8_t(m_seq >> 8); + rtp[3] = uint8_t(m_seq & 0xFF); + rtp[4] = uint8_t(m_ts >> 24); + rtp[5] = uint8_t((m_ts >> 16) & 0xFF); + rtp[6] = uint8_t((m_ts >> 8) & 0xFF); + rtp[7] = uint8_t(m_ts & 0xFF); + rtp[8] = uint8_t(m_ssrc >> 24); + rtp[9] = uint8_t((m_ssrc >> 16) & 0xFF); + rtp[10] = uint8_t((m_ssrc >> 8) & 0xFF); + rtp[11] = uint8_t(m_ssrc & 0xFF); + + // Concatenate header + payload + rtc::binary packet; + packet.resize(rtp.size() + opusBytes); + std::memcpy(packet.data(), rtp.data(), rtp.size()); + std::memcpy(packet.data() + rtp.size(), opusData, opusBytes); + + // Send RTP on the media track + m_track->send(packet); // libdatachannel expects RTP bytes on tracks + + // Advance RTP state + ++m_seq; + // For Opus @ 48k clock, timestamp increments by the PCM frame count per channel + // (do not multiply by channels) + m_ts += static_cast(pcmFramesPerChannel); + } + + private: + Params m_p; + std::shared_ptr m_pc; + std::shared_ptr m_track; + std::optional m_resourceUrl; + std::mutex m_mtx; + std::atomic m_started{false}; + + // RTP state + uint32_t m_ssrc = 0; + uint16_t m_seq = 0; + uint32_t m_ts = 0; + uint8_t m_pt = 111; // dynamic payload type commonly used for Opus + + // --- WHIP HTTP helpers (mTLS-capable) --- + + static std::tuple ExtractAnswerAndLocation(const httplib::Result &r) + { + if (!r) + throw std::runtime_error("No HTTP result"); + if (r->status != 201 && r->status != 200) + throw std::runtime_error("Unexpected WHIP status: " + std::to_string(r->status)); + std::string answer = r->body; + std::string resourceUrl; + if (r->has_header("Location")) + resourceUrl = r->get_header_value("Location"); + return {answer, resourceUrl}; + } + + std::pair PostOfferWHIP(const std::string &sdpOffer) + { + auto [cli, path] = MakeClientForUrl(sdpOffer, m_p.whipUrl); + auto res = cli->Post(path.c_str(), sdpOffer, "application/sdp"); + auto [answer, resUrl] = ExtractAnswerAndLocation(res); + return {answer, resUrl}; + } + + void PatchCandidateWHIP(const rtc::Candidate &cand) + { + if (!m_resourceUrl) + return; + auto [cli, path] = MakeClientForUrl(std::string(""), *m_resourceUrl); + std::string frag = "a=" + std::string(cand); + auto res = cli->Patch(path.c_str(), frag, "application/trickle-ice-sdpfrag"); + if (!res || (res->status < 200 || res->status >= 300)) + { + spdlog::warn("WHIP PATCH {} -> {}", path, res ? res->status : -1); } - }); - m_pc->onLocalCandidate([this](rtc::Candidate c) { - // Trickle via PATCH to WHIP resource (if server requires) - if (!m_resourceUrl.has_value()) return; - try { - PatchCandidateWHIP(c); - } catch (const std::exception& e) { - spdlog::warn("WHIP PATCH candidate failed: {}", e.what()); - } - }); - - // ----- Audio track / source ----- - m_audioSource = rtc::CreateAudioSource(m_p.sampleRate, m_p.channels); - m_track = m_pc->addTrack(m_audioSource); - - // ----- Create offer ----- - m_pc->setLocalDescription(); // triggers onLocalDescription callback - - m_started = true; - } - - void Stop() { - std::lock_guard lk(m_mtx); - if (!m_started) return; - - if (m_track) m_track = nullptr; - if (m_audioSource) m_audioSource = nullptr; - if (m_pc) m_pc = nullptr; - - // No explicit WHIP DELETE here, but you can add it if server supports deleting the resource. - - // cleanup resource url - m_resourceUrl.reset(); - m_started = false; - } - - // Feed PCM (float32) in interleaved format. frames = samples per channel. - void PushPCM(const float* interleaved, size_t frames) { - std::lock_guard lk(m_mtx); - if (!m_audioSource) return; - // libdatachannel expects planar/int16? Actually CreateAudioSource accepts float32 - // and uses (frames, channels) to encode Opus internally. - // Push signature is: audioSource->pushFloat(interleaved, frames, channels, sampleRate) - m_audioSource->pushFloat(interleaved, (int)frames, m_p.channels, m_p.sampleRate); - } - -private: - Params m_p; - std::shared_ptr m_pc; - std::shared_ptr m_audioSource; - std::shared_ptr m_track; - std::optional m_resourceUrl; - std::mutex m_mtx; - std::atomic m_started{false}; - - // --- WHIP HTTP helpers (mTLS-capable) --- - - static std::tuple ExtractAnswerAndLocation(const httplib::Result& r) { - // WHIP server responds 201 Created, body = SDP answer (text/plain), - // Location header = resource URL for PATCH candidates. - if (!r) throw std::runtime_error("No HTTP result"); - if (r->status != 201 && r->status != 200) - throw std::runtime_error("Unexpected WHIP status: " + std::to_string(r->status)); - std::string answer = r->body; - std::string resourceUrl; - if (r->has_header("Location")) resourceUrl = r->get_header_value("Location"); - return {answer, resourceUrl}; - } - - std::pair PostOfferWHIP(const std::string& sdpOffer) { - auto [cli, path] = MakeClientForUrl(m_p.whipUrl); - // WHIP requires: - // POST Content-Type: application/sdp Body: offer SDP - // Response: 201 Created, body: answer SDP, Location: resource URL - auto res = cli->Post(path.c_str(), sdpOffer, "application/sdp"); - auto [answer, resUrl] = ExtractAnswerAndLocation(res); - return {answer, resUrl}; - } - - void PatchCandidateWHIP(const rtc::Candidate& cand) { - if (!m_resourceUrl) return; - auto [cli, path] = MakeClientForUrl(*m_resourceUrl); - // WHIP trickle: PATCH resource with Content-Type: application/trickle-ice-sdpfrag - // Body is an SDP fragment with "a=candidate:..." - std::string frag = "a=" + std::string(cand); - auto res = cli->Patch(path.c_str(), frag, "application/trickle-ice-sdpfrag"); - if (!res || (res->status < 200 || res->status >= 300)) { - spdlog::warn("WHIP PATCH {} -> {}", path, res ? res->status : -1); } - } - // Parse URL, return httplib client + path, configured with mTLS if https - static std::pair, std::string> - MakeClientForUrl(const std::string& url) { - // scheme://host[:port]/path... - static const std::regex re(R"(^(https?)://([^/:]+)(?::(\d+))?(/.*)?$)"); - std::smatch m; - if (!std::regex_match(url, m, re)) { - throw std::runtime_error("Invalid URL: " + url); - } - std::string scheme = m[1].str(); - std::string host = m[2].str(); - int port = m[3].matched ? std::stoi(m[3].str()) : (scheme=="https"?443:80); - std::string path = m[4].matched ? m[4].str() : "/"; + // Build client + path for URL; uses mTLS when https + std::pair, std::string> + MakeClientForUrl(const std::string &body, const std::string &url) + { + (void)body; + static const std::regex re(R"(^(https?)://([^/:]+)(?::(\d+))?(/.*)?$)"); + std::smatch m; + if (!std::regex_match(url, m, re)) + throw std::runtime_error("Invalid URL: " + url); + std::string scheme = m[1].str(); + std::string host = m[2].str(); + int port = m[3].matched ? std::stoi(m[3].str()) : (scheme == "https" ? 443 : 80); + std::string path = m[4].matched ? m[4].str() : "/"; - // We need the mTLS params; capture via thread-local? We’ll pass via this pointer. - // To access instance fields here, make this non-static or pass params through a lambda. - // Simpler: look up global thread-local; or we restructure to capture this. - // For cleanliness, we’ll make a non-static wrapper below. - throw std::logic_error("Use MakeClientForUrl_inst instead"); - } - - std::pair, std::string> - MakeClientForUrl_inst(const std::string& url) { - static const std::regex re(R"(^(https?)://([^/:]+)(?::(\d+))?(/.*)?$)"); - std::smatch m; - if (!std::regex_match(url, m, re)) { - throw std::runtime_error("Invalid URL: " + url); - } - std::string scheme = m[1].str(); - std::string host = m[2].str(); - int port = m[3].matched ? std::stoi(m[3].str()) : (scheme=="https"?443:80); - std::string path = m[4].matched ? m[4].str() : "/"; - - if (scheme == "https") { + if (scheme == "https") + { #ifndef CPPHTTPLIB_OPENSSL_SUPPORT - throw std::runtime_error("https URL but CPPHTTPLIB_OPENSSL_SUPPORT not enabled"); + throw std::runtime_error("https URL but CPPHTTPLIB_OPENSSL_SUPPORT not enabled"); #else - auto cli = std::make_unique(host.c_str(), port); - cli->enable_server_certificate_verification(true); - cli->set_ca_cert_path(m_p.caPath.c_str()); - cli->set_client_cert_file(m_p.crtPath.c_str(), m_p.keyPath.c_str(), nullptr); - cli->set_connection_timeout(10); - cli->set_read_timeout(60); - cli->set_write_timeout(60); - return {std::move(cli), path}; + auto cli = std::make_unique(host.c_str(), port, m_p.crtPath, m_p.keyPath, std::string()); + cli->enable_server_certificate_verification(true); + cli->set_ca_cert_path(m_p.caPath.c_str()); + cli->set_connection_timeout(10); + cli->set_read_timeout(60); + cli->set_write_timeout(60); + return {std::move(cli), path}; #endif - } else { - auto cli = std::make_unique(host.c_str(), port); - cli->set_connection_timeout(10); - cli->set_read_timeout(60); - cli->set_write_timeout(60); - return {std::move(cli), path}; + } + // else + // { + // auto cli = std::make_unique(host.c_str(), port); + // cli->set_connection_timeout(10); + // cli->set_read_timeout(60); + // cli->set_write_timeout(60); + // return {std::move(cli), path}; + // } } - } - - // Overload to call instance version - std::pair, std::string> - MakeClientForUrl(const std::string& url) { - return MakeClientForUrl_inst(url); - } -}; + }; } // namespace snoop diff --git a/src/main.cpp b/src/main.cpp index a8b07fa..366a5b6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -49,20 +49,12 @@ namespace snoop // WHIP-only streamer AudioStreamService streamer(configService); - // PCM tap -> feed WHIP - std::function pcmTap = - [&](const float *interleaved, int frames) - { - streamer.OnPCM(interleaved, static_cast(frames)); - }; - // Encoder: keep writing to local Ogg via writerService; (no Socket.IO send) TAudioEncoder encoder(sampleRate, channels, framesPerBuffer, [&](auto input, auto size) -> int { writerService->WriteAudioData(input, size, framesPerBuffer); - return paContinue; }, - pcmTap // NEW: raw PCM to WHIP - ); + streamer.OnOpus(reinterpret_cast(input),static_cast(size),framesPerBuffer); + return paContinue; }); snoop::DeviceControlService::Controls controls{ .stopStreamNow = [&]() @@ -70,13 +62,13 @@ namespace snoop .stopRecordingNow = [&]() { writerService->StopRecordingNow(); }}; - g_taskSvc = std::make_unique(configService, handlers, controls); + snoop::DeviceControlService::Handlers handlers{}; + static std::unique_ptr g_taskSvc; // ------------------------------ { - snoop::DeviceControlService::Handlers handlers{}; - handlers.onStartStream = [](const snoop::DeviceControlService::Task &t) + handlers.onStartStream = [&](const snoop::DeviceControlService::Task &t) { spdlog::info("start_stream payload: {}", t.payload.dump()); // TODO: start your streaming pipeline using payload["whipUrl"], etc. @@ -96,7 +88,7 @@ namespace snoop bool ok = streamer.StartWhip(whipUrl, sampleRate, channels); return snoop::DeviceControlService::HandlerResult{ok, R"({"whip":"started"})", ok ? "" : "failed"}; }; - handlers.onStopStream = [](const snoop::DeviceControlService::Task &t) + handlers.onStopStream = [&](const snoop::DeviceControlService::Task &t) { spdlog::info("stop_stream payload: {}", t.payload.dump()); // TODO: stop streaming @@ -191,11 +183,10 @@ namespace snoop return snoop::DeviceControlService::HandlerResult{false, "{}", e.what()}; } }; - - // static std::unique_ptr g_taskSvc; - // g_taskSvc = std::make_unique(configService, handlers); } + g_taskSvc = std::make_unique(configService, handlers, controls); + while (true) { std::this_thread::sleep_for(std::chrono::seconds(1)); diff --git a/toolchain/build.sh b/toolchain/build.sh index 77b6530..06bb6d8 100755 --- a/toolchain/build.sh +++ b/toolchain/build.sh @@ -1,33 +1,49 @@ #!/bin/bash SYSROOT="$(pwd)/sysroot" -CLANG_PATH=/usr/local/opt/llvm@16/bin +# CLANG_PATH=/usr/local/opt/llvm@16/bin +CLANG_PATH="/usr/lib/llvm-14/bin" export PATH="$CLANG_PATH:$PATH" export CC="clang" export CXX="clang++" export LD="ld.lld" -export PKG_CONFIG_PATH=$SYSROOT/usr/lib/arm-linux-gnueabihf/pkgconfig -export CFLAGS="--target=armv7-linux-gnueabihf -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard --sysroot=$SYSROOT" -export CXXFLAGS="--target=armv7-linux-gnueabihf -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard --sysroot=$SYSROOT" -export LDFLAGS="--target=armv7-linux-gnueabihf -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard -fuse-ld=lld --sysroot=$SYSROOT" +# export PKG_CONFIG_PATH=$SYSROOT/usr/lib/arm-linux-gnueabihf/pkgconfig +# export CFLAGS="--target=armv7-linux-gnueabihf -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard --sysroot=$SYSROOT" +# export CXXFLAGS="--target=armv7-linux-gnueabihf -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard --sysroot=$SYSROOT" +# export LDFLAGS="--target=armv7-linux-gnueabihf -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard -fuse-ld=lld --sysroot=$SYSROOT" BUILD_DIR="./build" +# mkdir -p "$BUILD_DIR" +# rm -rf "$BUILD_DIR"/* + +rm -rf "$BUILD_DIR" mkdir -p "$BUILD_DIR" -rm -rf "$BUILD_DIR"/* cd "$BUILD_DIR" +# cmake -GNinja \ +# -DCMAKE_SYSTEM_NAME=Linux \ +# -DCMAKE_SYSROOT="$SYSROOT" \ +# -DUSE_ALSA_ADAPTER=ON \ +# ../../ +# # -DCMAKE_BUILD_TYPE=Debug \ +# # -DCMAKE_C_FLAGS="$CFLAGS -g" \ +# # -DCMAKE_CXX_FLAGS="$CXXFLAGS -g" \ +# # -DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS -g" \ +# # ../../ + cmake -GNinja \ -DCMAKE_SYSTEM_NAME=Linux \ - -DCMAKE_SYSROOT="$SYSROOT" \ -DUSE_ALSA_ADAPTER=ON \ - ../../ -# -DCMAKE_BUILD_TYPE=Debug \ -# -DCMAKE_C_FLAGS="$CFLAGS -g" \ -# -DCMAKE_CXX_FLAGS="$CXXFLAGS -g" \ -# -DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS -g" \ -# ../../ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER="$CC" \ + -DCMAKE_CXX_COMPILER="$CXX" \ + -DCMAKE_C_FLAGS="$CFLAGS" \ + -DCMAKE_CXX_FLAGS="$CXXFLAGS" \ + -DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS" \ + .. + ninja \ No newline at end of file