136 lines
4.1 KiB
C++
136 lines
4.1 KiB
C++
// src/Services/AudioStreamService.h
|
|
#pragma once
|
|
|
|
#include <atomic>
|
|
#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<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<ConfigService> cfg)
|
|
: m_cfg(std::move(cfg)) {}
|
|
|
|
~AudioStreamService() {
|
|
StopWhip();
|
|
}
|
|
|
|
// Feed raw PCM (float32 interleaved), frames = samples per channel
|
|
void OnOpus(const unsigned char* opusData, size_t opusBytes, int pcmFramesPerChannel) {
|
|
std::lock_guard lk(m_whipMutex);
|
|
if (m_whip) m_whip->PushOpus(opusData, opusBytes, pcmFramesPerChannel);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// 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";
|
|
|
|
// 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:
|
|
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);
|
|
};
|
|
|
|
std::string id = trim(exec("keyctl search @s user iot-client-key | tail -n1"));
|
|
if (id.empty()) return std::nullopt;
|
|
|
|
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;
|
|
}
|
|
return p;
|
|
}
|
|
};
|
|
|
|
} // namespace snoop
|