lots of changes in code for ssl client
This commit is contained in:
@@ -3,13 +3,14 @@ project( snoop_device )
|
|||||||
|
|
||||||
set( CMAKE_CXX_STANDARD 23 )
|
set( CMAKE_CXX_STANDARD 23 )
|
||||||
|
|
||||||
|
find_package(OpenSSL REQUIRED)
|
||||||
|
|
||||||
add_subdirectory( third_party/portaudio )
|
add_subdirectory( third_party/portaudio )
|
||||||
add_subdirectory( third_party/opus )
|
add_subdirectory( third_party/opus )
|
||||||
add_subdirectory( third_party/cpp-httplib )
|
add_subdirectory( third_party/cpp-httplib )
|
||||||
add_subdirectory( third_party/nlohmann_json )
|
add_subdirectory( third_party/nlohmann_json )
|
||||||
add_subdirectory( third_party/spdlog )
|
add_subdirectory( third_party/spdlog )
|
||||||
add_subdirectory( third_party/libogg )
|
add_subdirectory( third_party/libogg )
|
||||||
add_subdirectory( third_party/socket.io-client-cpp )
|
|
||||||
add_subdirectory( third_party/libdatachannel )
|
add_subdirectory( third_party/libdatachannel )
|
||||||
|
|
||||||
option(USE_ALSA_ADAPTER "Use ALSA Adapter" OFF)
|
option(USE_ALSA_ADAPTER "Use ALSA Adapter" OFF)
|
||||||
@@ -39,4 +40,17 @@ set( HEADERS
|
|||||||
|
|
||||||
add_executable( ${PROJECT_NAME} ${SOURCES} ${HEADERS} )
|
add_executable( ${PROJECT_NAME} ${SOURCES} ${HEADERS} )
|
||||||
target_include_directories( ${PROJECT_NAME} PRIVATE src )
|
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
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
CPU_SERIAL=$(awk '/Serial/ {print $3}' /proc/cpuinfo)
|
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" | \
|
KEK=$(echo -n "$CPU_SERIAL" | \
|
||||||
openssl dgst -sha256 -hmac "server-provided-salt" | \
|
openssl dgst -sha256 -hmac "server-provided-salt" | \
|
||||||
awk '{print $2}')
|
awk '{print $2}')
|
||||||
|
|||||||
@@ -37,9 +37,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Feed raw PCM (float32 interleaved), frames = samples per channel
|
// 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);
|
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) {
|
bool StartWhip(const std::string& whipUrl, int sampleRate=48000, int channels=1) {
|
||||||
|
|||||||
@@ -207,7 +207,7 @@ namespace snoop
|
|||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<httplib::Client> MakeClientMTLS(const Url &u,
|
std::unique_ptr<httplib::SSLClient> MakeClientMTLS(const Url &u,
|
||||||
const std::filesystem::path &ca,
|
const std::filesystem::path &ca,
|
||||||
const std::filesystem::path &crt,
|
const std::filesystem::path &crt,
|
||||||
const std::filesystem::path &key)
|
const std::filesystem::path &key)
|
||||||
@@ -215,10 +215,9 @@ namespace snoop
|
|||||||
if (u.scheme == "https")
|
if (u.scheme == "https")
|
||||||
{
|
{
|
||||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
auto cli = std::make_unique<httplib::SSLClient>(u.host.c_str(), u.port);
|
auto cli = std::make_unique<httplib::SSLClient>(u.host.c_str(), u.port, crt.string().c_str(), key.string().c_str(), std::string());
|
||||||
cli->enable_server_certificate_verification(true);
|
cli->enable_server_certificate_verification(true);
|
||||||
cli->set_ca_cert_path(ca.string().c_str());
|
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_connection_timeout(10);
|
||||||
cli->set_read_timeout(120);
|
cli->set_read_timeout(120);
|
||||||
cli->set_write_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");
|
throw std::runtime_error("HTTPS baseUrl but CPPHTTPLIB_OPENSSL_SUPPORT is not enabled");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else
|
// else
|
||||||
{
|
// {
|
||||||
auto cli = std::make_unique<httplib::Client>(u.host.c_str(), u.port);
|
// auto cli = std::make_unique<httplib::Client>(u.host.c_str(), u.port);
|
||||||
cli->set_connection_timeout(10);
|
// cli->set_connection_timeout(10);
|
||||||
cli->set_read_timeout(120);
|
// cli->set_read_timeout(120);
|
||||||
cli->set_write_timeout(120);
|
// cli->set_write_timeout(120);
|
||||||
return cli;
|
// return cli;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------- Existing logic (adjusted) -----------------------------
|
// ----------------------------- Existing logic (adjusted) -----------------------------
|
||||||
@@ -421,7 +420,7 @@ namespace snoop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SendRecordedFileMTLS(httplib::Client &client,
|
bool SendRecordedFileMTLS(httplib::SSLClient &client,
|
||||||
const std::string &filepath,
|
const std::string &filepath,
|
||||||
unsigned long long int startedAt,
|
unsigned long long int startedAt,
|
||||||
unsigned long long int stoppedAt)
|
unsigned long long int stoppedAt)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -90,7 +90,7 @@ namespace snoop
|
|||||||
// If already inside window -> enter immediately
|
// If already inside window -> enter immediately
|
||||||
if (now >= startMs && now < stopMs)
|
if (now >= startMs && now < stopMs)
|
||||||
{
|
{
|
||||||
EnterDeepSleepUntil(stopMs);
|
this->EnterDeepSleepUntil(stopMs);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -101,6 +101,42 @@ namespace snoop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::optional<long long> 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<milliseconds>(system_clock::now().time_since_epoch()).count();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<ConfigService> m_cfg;
|
std::shared_ptr<ConfigService> m_cfg;
|
||||||
Handlers m_handlers;
|
Handlers m_handlers;
|
||||||
@@ -161,42 +197,6 @@ namespace snoop
|
|||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
static long long NowMs()
|
|
||||||
{
|
|
||||||
using namespace std::chrono;
|
|
||||||
return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::optional<long long> 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<long long> JsonTimeToMs(const nlohmann::json &j)
|
static std::optional<long long> JsonTimeToMs(const nlohmann::json &j)
|
||||||
{
|
{
|
||||||
if (j.contains("start_ms") && j.contains("stop_ms"))
|
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)
|
// Create HTTPS client configured for mTLS (or HTTP if base is http)
|
||||||
std::unique_ptr<httplib::Client> MakeClient(const Url &u,
|
std::unique_ptr<httplib::SSLClient> MakeClient(const Url &u,
|
||||||
const std::filesystem::path &ca,
|
const std::filesystem::path &ca,
|
||||||
const std::filesystem::path &crt,
|
const std::filesystem::path &crt,
|
||||||
const std::filesystem::path &key)
|
const std::filesystem::path &key)
|
||||||
{
|
{
|
||||||
if (u.scheme == "https")
|
if (u.scheme == "https")
|
||||||
{
|
{
|
||||||
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
auto cli = std::make_unique<httplib::SSLClient>(u.host.c_str(), u.port);
|
auto cli = std::make_unique<httplib::SSLClient>(u.host.c_str(), u.port, crt.string().c_str(), key.string().c_str(), std::string());
|
||||||
cli->enable_server_certificate_verification(true);
|
cli->enable_server_certificate_verification(true);
|
||||||
cli->set_ca_cert_path(ca.string().c_str());
|
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_connection_timeout(10);
|
||||||
cli->set_read_timeout(60);
|
cli->set_read_timeout(60);
|
||||||
cli->set_write_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");
|
throw std::runtime_error("CPPHTTPLIB_OPENSSL_SUPPORT not enabled but https URL provided");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else
|
// else
|
||||||
{
|
// {
|
||||||
auto cli = std::make_unique<httplib::Client>(u.host.c_str(), u.port);
|
// auto cli = std::make_unique<httplib::Client>(u.host.c_str(), u.port);
|
||||||
cli->set_connection_timeout(10);
|
// cli->set_connection_timeout(10);
|
||||||
cli->set_read_timeout(60);
|
// cli->set_read_timeout(60);
|
||||||
cli->set_write_timeout(60);
|
// cli->set_write_timeout(60);
|
||||||
return cli;
|
// return cli;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// simple jittered sleep
|
// simple jittered sleep
|
||||||
@@ -375,7 +373,7 @@ namespace snoop
|
|||||||
return tasks;
|
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,
|
uint64_t taskId, bool success,
|
||||||
const std::string &resultJson, const std::string &err)
|
const std::string &resultJson, const std::string &err)
|
||||||
{
|
{
|
||||||
@@ -431,7 +429,7 @@ namespace snoop
|
|||||||
catch (const std::exception &e)
|
catch (const std::exception &e)
|
||||||
{
|
{
|
||||||
spdlog::error("Key extraction failed: {}", e.what());
|
spdlog::error("Key extraction failed: {}", e.what());
|
||||||
SleepWithJitterOnce(rng, m_cfg->GetPollingSeconds(), m_cfg->GetJitterSeconds());
|
SleepWithJitterOnce(rng, m_cfg->GetPollingInterwall(), m_cfg->GetJitter());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto now = NowMs();
|
const auto now = NowMs();
|
||||||
@@ -512,7 +510,7 @@ namespace snoop
|
|||||||
std::filesystem::remove(*tmpKey, ec);
|
std::filesystem::remove(*tmpKey, ec);
|
||||||
}
|
}
|
||||||
|
|
||||||
SleepWithJitterOnce(rng, m_cfg->GetPollingSeconds(), m_cfg->GetJitterSeconds());
|
SleepWithJitterOnce(rng, m_cfg->GetPollingInterwall(), m_cfg->GetJitter());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ public:
|
|||||||
{
|
{
|
||||||
std::filesystem::create_directories(keystoreDir);
|
std::filesystem::create_directories(keystoreDir);
|
||||||
const std::string cmd =
|
const std::string cmd =
|
||||||
"openssl enc -aes-256-gcm -pbkdf2 -salt "
|
"openssl enc -aes-256-cbc -pbkdf2 -salt "
|
||||||
"-pass pass:" + kek + " "
|
"-pass pass:" + kek + " "
|
||||||
"-in " + keyName + " "
|
"-in " + keyName + " "
|
||||||
"-out " + encKeyPath.string() + " "
|
"-out " + encKeyPath.string() + " "
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// src/Services/WhipClient.h
|
// src/Services/WhipClient.h
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#define CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@@ -11,53 +11,54 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <random>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include <httplib.h> // build with CPPHTTPLIB_OPENSSL_SUPPORT
|
#include <httplib.h> // build with CPPHTTPLIB_OPENSSL_SUPPORT
|
||||||
|
|
||||||
#include <rtc/rtc.hpp> // libdatachannel
|
#include <rtc/rtc.hpp>
|
||||||
|
#include <rtc/peerconnection.hpp>
|
||||||
|
#include <rtc/description.hpp>
|
||||||
|
#include <rtc/track.hpp>
|
||||||
|
|
||||||
namespace snoop {
|
namespace snoop
|
||||||
|
{
|
||||||
|
|
||||||
class WhipClient {
|
class WhipClient
|
||||||
public:
|
{
|
||||||
struct Params {
|
public:
|
||||||
std::string whipUrl; // full WHIP endpoint (may already include ?token=...)
|
struct Params
|
||||||
std::string caPath; // CA chain
|
{
|
||||||
std::string crtPath; // client cert
|
std::string whipUrl; // full WHIP endpoint (may include ?token=...)
|
||||||
std::string keyPath; // client key (temp extracted from keyctl)
|
std::string caPath; // CA chain
|
||||||
int sampleRate = 48000;
|
std::string crtPath; // client cert
|
||||||
int channels = 1;
|
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)
|
explicit WhipClient(Params p) : m_p(std::move(p)) {}
|
||||||
: m_p(std::move(p)) {}
|
|
||||||
|
|
||||||
~WhipClient() {
|
~WhipClient() { Stop(); }
|
||||||
Stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Start() {
|
void Start()
|
||||||
std::lock_guard lk(m_mtx);
|
{
|
||||||
if (m_started) return;
|
std::lock_guard lk(m_mtx);
|
||||||
|
if (m_started)
|
||||||
|
return;
|
||||||
|
|
||||||
// ----- PeerConnection -----
|
rtc::Configuration cfg;
|
||||||
rtc::Configuration cfg;
|
m_pc = std::make_shared<rtc::PeerConnection>(cfg);
|
||||||
// No STUN/TURN required for MediaMTX WHIP (server is public/ICE-lite).
|
|
||||||
// cfg.iceServers = { }; // default
|
|
||||||
|
|
||||||
m_pc = std::make_shared<rtc::PeerConnection>(cfg);
|
m_pc->onStateChange([this](rtc::PeerConnection::State s)
|
||||||
|
{ spdlog::info("WHIP pc state: {}", (int)s); });
|
||||||
// Optional: observe connection state / ICE
|
m_pc->onGatheringStateChange([this](rtc::PeerConnection::GatheringState s)
|
||||||
m_pc->onStateChange([this](rtc::PeerConnection::State s){
|
{ spdlog::info("WHIP gathering state: {}", (int)s); });
|
||||||
spdlog::info("WHIP pc state: {}", (int)s);
|
m_pc->onLocalDescription([this](rtc::Description desc)
|
||||||
});
|
{
|
||||||
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
|
|
||||||
try {
|
try {
|
||||||
const std::string offer = std::string(desc);
|
const std::string offer = std::string(desc);
|
||||||
auto [answer, resourceUrl] = PostOfferWHIP(offer);
|
auto [answer, resourceUrl] = PostOfferWHIP(offer);
|
||||||
@@ -66,158 +67,171 @@ public:
|
|||||||
spdlog::info("WHIP remote description set");
|
spdlog::info("WHIP remote description set");
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
spdlog::error("WHIP POST offer failed: {}", e.what());
|
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<uint32_t>()(rng);
|
||||||
|
m_seq = std::uniform_int_distribution<uint16_t>()(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<uint8_t, 12> 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<uint32_t>(pcmFramesPerChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Params m_p;
|
||||||
|
std::shared_ptr<rtc::PeerConnection> m_pc;
|
||||||
|
std::shared_ptr<rtc::Track> m_track;
|
||||||
|
std::optional<std::string> m_resourceUrl;
|
||||||
|
std::mutex m_mtx;
|
||||||
|
std::atomic<bool> 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<std::string, std::string> 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<std::string, std::string> 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<rtc::PeerConnection> m_pc;
|
|
||||||
std::shared_ptr<rtc::AudioSource> m_audioSource;
|
|
||||||
std::shared_ptr<rtc::Track> m_track;
|
|
||||||
std::optional<std::string> m_resourceUrl;
|
|
||||||
std::mutex m_mtx;
|
|
||||||
std::atomic<bool> m_started{false};
|
|
||||||
|
|
||||||
// --- WHIP HTTP helpers (mTLS-capable) ---
|
|
||||||
|
|
||||||
static std::tuple<std::string,std::string> 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<std::string,std::string> PostOfferWHIP(const std::string& sdpOffer) {
|
|
||||||
auto [cli, path] = MakeClientForUrl(m_p.whipUrl);
|
|
||||||
// WHIP requires:
|
|
||||||
// POST <endpoint> 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
|
// Build client + path for URL; uses mTLS when https
|
||||||
static std::pair<std::unique_ptr<httplib::Client>, std::string>
|
std::pair<std::unique_ptr<httplib::SSLClient>, std::string>
|
||||||
MakeClientForUrl(const std::string& url) {
|
MakeClientForUrl(const std::string &body, const std::string &url)
|
||||||
// scheme://host[:port]/path...
|
{
|
||||||
static const std::regex re(R"(^(https?)://([^/:]+)(?::(\d+))?(/.*)?$)");
|
(void)body;
|
||||||
std::smatch m;
|
static const std::regex re(R"(^(https?)://([^/:]+)(?::(\d+))?(/.*)?$)");
|
||||||
if (!std::regex_match(url, m, re)) {
|
std::smatch m;
|
||||||
throw std::runtime_error("Invalid URL: " + url);
|
if (!std::regex_match(url, m, re))
|
||||||
}
|
throw std::runtime_error("Invalid URL: " + url);
|
||||||
std::string scheme = m[1].str();
|
std::string scheme = m[1].str();
|
||||||
std::string host = m[2].str();
|
std::string host = m[2].str();
|
||||||
int port = m[3].matched ? std::stoi(m[3].str()) : (scheme=="https"?443:80);
|
int port = m[3].matched ? std::stoi(m[3].str()) : (scheme == "https" ? 443 : 80);
|
||||||
std::string path = m[4].matched ? m[4].str() : "/";
|
std::string path = m[4].matched ? m[4].str() : "/";
|
||||||
|
|
||||||
// We need the mTLS params; capture via thread-local? We’ll pass via this pointer.
|
if (scheme == "https")
|
||||||
// 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::unique_ptr<httplib::Client>, 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") {
|
|
||||||
#ifndef CPPHTTPLIB_OPENSSL_SUPPORT
|
#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
|
#else
|
||||||
auto cli = std::make_unique<httplib::SSLClient>(host.c_str(), port);
|
auto cli = std::make_unique<httplib::SSLClient>(host.c_str(), port, m_p.crtPath, m_p.keyPath, std::string());
|
||||||
cli->enable_server_certificate_verification(true);
|
cli->enable_server_certificate_verification(true);
|
||||||
cli->set_ca_cert_path(m_p.caPath.c_str());
|
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_connection_timeout(10);
|
cli->set_read_timeout(60);
|
||||||
cli->set_read_timeout(60);
|
cli->set_write_timeout(60);
|
||||||
cli->set_write_timeout(60);
|
return {std::move(cli), path};
|
||||||
return {std::move(cli), path};
|
|
||||||
#endif
|
#endif
|
||||||
} else {
|
}
|
||||||
auto cli = std::make_unique<httplib::Client>(host.c_str(), port);
|
// else
|
||||||
cli->set_connection_timeout(10);
|
// {
|
||||||
cli->set_read_timeout(60);
|
// auto cli = std::make_unique<httplib::Client>(host.c_str(), port);
|
||||||
cli->set_write_timeout(60);
|
// cli->set_connection_timeout(10);
|
||||||
return {std::move(cli), path};
|
// cli->set_read_timeout(60);
|
||||||
|
// cli->set_write_timeout(60);
|
||||||
|
// return {std::move(cli), path};
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Overload to call instance version
|
|
||||||
std::pair<std::unique_ptr<httplib::Client>, std::string>
|
|
||||||
MakeClientForUrl(const std::string& url) {
|
|
||||||
return MakeClientForUrl_inst(url);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace snoop
|
} // namespace snoop
|
||||||
|
|||||||
25
src/main.cpp
25
src/main.cpp
@@ -49,20 +49,12 @@ namespace snoop
|
|||||||
// WHIP-only streamer
|
// WHIP-only streamer
|
||||||
AudioStreamService streamer(configService);
|
AudioStreamService streamer(configService);
|
||||||
|
|
||||||
// PCM tap -> feed WHIP
|
|
||||||
std::function<void(const float *, int)> pcmTap =
|
|
||||||
[&](const float *interleaved, int frames)
|
|
||||||
{
|
|
||||||
streamer.OnPCM(interleaved, static_cast<size_t>(frames));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Encoder: keep writing to local Ogg via writerService; (no Socket.IO send)
|
// Encoder: keep writing to local Ogg via writerService; (no Socket.IO send)
|
||||||
TAudioEncoder encoder(sampleRate, channels, framesPerBuffer, [&](auto input, auto size) -> int
|
TAudioEncoder encoder(sampleRate, channels, framesPerBuffer, [&](auto input, auto size) -> int
|
||||||
{
|
{
|
||||||
writerService->WriteAudioData(input, size, framesPerBuffer);
|
writerService->WriteAudioData(input, size, framesPerBuffer);
|
||||||
return paContinue; },
|
streamer.OnOpus(reinterpret_cast<const unsigned char*>(input),static_cast<size_t>(size),framesPerBuffer);
|
||||||
pcmTap // NEW: raw PCM to WHIP
|
return paContinue; });
|
||||||
);
|
|
||||||
|
|
||||||
snoop::DeviceControlService::Controls controls{
|
snoop::DeviceControlService::Controls controls{
|
||||||
.stopStreamNow = [&]()
|
.stopStreamNow = [&]()
|
||||||
@@ -70,13 +62,13 @@ namespace snoop
|
|||||||
.stopRecordingNow = [&]()
|
.stopRecordingNow = [&]()
|
||||||
{ writerService->StopRecordingNow(); }};
|
{ writerService->StopRecordingNow(); }};
|
||||||
|
|
||||||
g_taskSvc = std::make_unique<snoop::DeviceControlService>(configService, handlers, controls);
|
snoop::DeviceControlService::Handlers handlers{};
|
||||||
|
static std::unique_ptr<snoop::DeviceControlService> 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());
|
spdlog::info("start_stream payload: {}", t.payload.dump());
|
||||||
// TODO: start your streaming pipeline using payload["whipUrl"], etc.
|
// TODO: start your streaming pipeline using payload["whipUrl"], etc.
|
||||||
@@ -96,7 +88,7 @@ namespace snoop
|
|||||||
bool ok = streamer.StartWhip(whipUrl, sampleRate, channels);
|
bool ok = streamer.StartWhip(whipUrl, sampleRate, channels);
|
||||||
return snoop::DeviceControlService::HandlerResult{ok, R"({"whip":"started"})", ok ? "" : "failed"};
|
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());
|
spdlog::info("stop_stream payload: {}", t.payload.dump());
|
||||||
// TODO: stop streaming
|
// TODO: stop streaming
|
||||||
@@ -191,11 +183,10 @@ namespace snoop
|
|||||||
return snoop::DeviceControlService::HandlerResult{false, "{}", e.what()};
|
return snoop::DeviceControlService::HandlerResult{false, "{}", e.what()};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// static std::unique_ptr<snoop::DeviceControlService> g_taskSvc;
|
|
||||||
// g_taskSvc = std::make_unique<snoop::DeviceControlService>(configService, handlers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_taskSvc = std::make_unique<snoop::DeviceControlService>(configService, handlers, controls);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||||
|
|||||||
@@ -1,33 +1,49 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
SYSROOT="$(pwd)/sysroot"
|
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 PATH="$CLANG_PATH:$PATH"
|
||||||
export CC="clang"
|
export CC="clang"
|
||||||
export CXX="clang++"
|
export CXX="clang++"
|
||||||
export LD="ld.lld"
|
export LD="ld.lld"
|
||||||
|
|
||||||
export PKG_CONFIG_PATH=$SYSROOT/usr/lib/arm-linux-gnueabihf/pkgconfig
|
# 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 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 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 LDFLAGS="--target=armv7-linux-gnueabihf -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=hard -fuse-ld=lld --sysroot=$SYSROOT"
|
||||||
|
|
||||||
BUILD_DIR="./build"
|
BUILD_DIR="./build"
|
||||||
|
# mkdir -p "$BUILD_DIR"
|
||||||
|
# rm -rf "$BUILD_DIR"/*
|
||||||
|
|
||||||
|
rm -rf "$BUILD_DIR"
|
||||||
mkdir -p "$BUILD_DIR"
|
mkdir -p "$BUILD_DIR"
|
||||||
rm -rf "$BUILD_DIR"/*
|
|
||||||
|
|
||||||
cd "$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 \
|
cmake -GNinja \
|
||||||
-DCMAKE_SYSTEM_NAME=Linux \
|
-DCMAKE_SYSTEM_NAME=Linux \
|
||||||
-DCMAKE_SYSROOT="$SYSROOT" \
|
|
||||||
-DUSE_ALSA_ADAPTER=ON \
|
-DUSE_ALSA_ADAPTER=ON \
|
||||||
../../
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
# -DCMAKE_BUILD_TYPE=Debug \
|
-DCMAKE_C_COMPILER="$CC" \
|
||||||
# -DCMAKE_C_FLAGS="$CFLAGS -g" \
|
-DCMAKE_CXX_COMPILER="$CXX" \
|
||||||
# -DCMAKE_CXX_FLAGS="$CXXFLAGS -g" \
|
-DCMAKE_C_FLAGS="$CFLAGS" \
|
||||||
# -DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS -g" \
|
-DCMAKE_CXX_FLAGS="$CXXFLAGS" \
|
||||||
# ../../
|
-DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS" \
|
||||||
|
..
|
||||||
|
|
||||||
|
|
||||||
ninja
|
ninja
|
||||||
Reference in New Issue
Block a user