Questa guida mostra come integrare moqxr in un'applicazione usando l'API C++ in openmoq/publisher/publisher_api.h.
#include "openmoq/publisher/publisher_api.h"Tipi principali:
openmoq::publisher::PublisherConfigopenmoq::publisher::Publisheropenmoq::publisher::PreparedPublish
Create un PublisherConfig una sola volta e passatelo a Publisher.
#include "openmoq/publisher/publisher_api.h"
openmoq::publisher::PublisherConfig config;
config.draft_version = openmoq::publisher::DraftVersion::kDraft14;
config.track_namespace = "media";
config.forward = false;
config.publish_catalog = false;
config.include_sap = false;
config.split_cmaf_chunks = true;
config.paced = false;
config.loop = false;
config.subscriber_timeout = std::chrono::seconds(30);
openmoq::publisher::Publisher publisher(config);Per workflow basati su file o stream bufferizzati, preparate prima i media:
auto prepared = publisher.prepare_file("sample.mp4");oppure:
std::ifstream input("sample.mp4", std::ios::binary);
auto prepared = publisher.prepare_stream(input, "sample.mp4");PreparedPublish contiene:
input_bytes: byte MP4 originaliplan: piano di pubblicazione generato da quei byte
Questo è utile per applicazioni più grandi che vogliono:
- ispezionare o approvare l'output del piano prima della pubblicazione
- salvare lo stato del piano
- pubblicare lo stesso asset preparato verso più endpoint
Renderizzare il piano per logging o debug:
std::string plan_text = publisher.render_plan(prepared);Emettere su disco il catalogo generato e gli oggetti media:
publisher.emit_objects(prepared, "out");Costruite EndpointConfig e l'eventuale TlsConfig.
openmoq::publisher::transport::EndpointConfig endpoint;
endpoint.transport = openmoq::publisher::transport::TransportKind::kRawQuic;
endpoint.host = "relay.example.com";
endpoint.port = 4433;openmoq::publisher::transport::EndpointConfig endpoint;
endpoint.transport = openmoq::publisher::transport::TransportKind::kWebTransport;
endpoint.host = "relay.example.com";
endpoint.port = 443;
endpoint.path = "/moq";
endpoint.path_explicit = true;Controlli TLS opzionali:
openmoq::publisher::transport::TlsConfig tls;
tls.insecure_skip_verify = false;
// tls.ca_path = "...";
// tls.certificate_path = "...";
// tls.private_key_path = "...";Usate il contenuto preparato insieme all'endpoint:
const auto status = publisher.publish(prepared, endpoint, tls);
if (!status.ok) {
// status.message includes context such as:
// "transport connect failed: ..."
// "transport publish failed: ..."
} else {
const auto disconnect_status = publisher.disconnect(0);
if (!disconnect_status.ok) {
// handle disconnect_status.message
}
}Helper di comodità:
publish_file(path, endpoint, tls)publish_stream(input, source_name, endpoint, tls)
Per pipeline live, ad esempio ffmpeg che invia MP4 frammentato tramite pipe:
const auto status = publisher.publish_live(std::cin, endpoint, tls);
if (!status.ok) {
// handle status.message
} else {
const auto disconnect_status = publisher.disconnect(0);
if (!disconnect_status.ok) {
// handle disconnect_status.message
}
}publish_live(...) usa parsing incrementale e un flusso di pubblicazione live invece di bufferizzare fino a EOF.
Per impostazione predefinita, l'API applica l'ALPN appropriato al trasporto:
- Raw QUIC + draft-14:
moq-00 - Raw QUIC + draft-16:
moqt-16 - Raw QUIC + draft-17:
moqt-17 - Raw QUIC + draft-18:
moqt-18 - WebTransport:
h3
Per WebTransport, l'API invia l'offerta del protocollo applicativo MoQ separatamente dall'ALPN QUIC tramite WT-Available-Protocols: draft-16 offre "moqt-16", draft-17 offre "moqt-17", draft-18 offre "moqt-18" e draft-14 mantiene il comportamento legacy senza sottoprotocollo. Le virgolette fanno parte della sintassi HTTP Structured Fields; l'ALPN Raw QUIC resta il token senza virgolette.
Se la vostra applicazione ha già impostato l'ALPN dell'endpoint e vuole mantenerlo:
const bool endpoint_alpn_overridden = true;
auto status = publisher.publish(prepared, endpoint, tls, endpoint_alpn_overridden);Lo stesso flag di override esiste su:
publish_file(...)publish_stream(...)publish_live(...)
Tutte le chiamate di pubblicazione dell'API restituiscono TransportStatus:
status.ok == true: successostatus.ok == false: errore; ispezionatestatus.message
Modello consigliato:
auto status = publisher.publish_file("sample.mp4", endpoint, tls);
if (!status.ok) {
// log status.message
// map to app-level retry/backoff policy
} else {
auto disconnect_status = publisher.disconnect(0);
if (!disconnect_status.ok) {
// log disconnect_status.message
}
}Per un'integrazione in stile servizio:
- Costruite un
Publisherper ogni profilo di configurazione runtime. - All'ingestione, chiamate
prepare_file(...)oprepare_stream(...). - Salvate o ispezionate i metadati di
PreparedPublishsecondo necessità. - Pubblicate verso uno o più endpoint con
publish(...). - Per input continui, eseguite
publish_live(...)in un thread worker. - Usate i messaggi
TransportStatusper metriche e decisioni di retry.
L'API publisher è bloccante: publish(...), publish_file(...), publish_stream(...) e publish_live(...) eseguono la sessione sul thread chiamante. Poiché non esiste un ciclo di polling integrato, le statistiche sono esposte come riepilogo strutturato dell'operazione di pubblicazione corrente o più recente, non come stream di telemetria live.
stats() può essere chiamato in sicurezza da un thread separato mentre una chiamata publish* è bloccata; i contatori si aggiornano mentre gli oggetti vengono serviti. Questo è il modo supportato per permettere a un front-end GUI di pilotare un "pannello statistiche": eseguite il polling su un timer, ad esempio una volta al secondo, sul thread UI mentre la pubblicazione gira su un worker. I contatori sono aggiornati per ogni modalità di pubblicazione (publish, publish_file, publish_stream, publish_live); le revisioni precedenti li aggiornavano solo per publish_live, facendo sembrare stats() non implementato per la pubblicazione batch.
const auto stats = publisher.stats();
std::cout << "bytes=" << stats.bytes_published
<< " objects=" << stats.objects_published
<< " groups=" << stats.groups_published << "\n";Campi correnti:
publishingLive: indica se la sessione attiva è in modalità pubblicazione livebytesPublished: byte totali di payload pubblicati nella sessione corrente o precedenteobjectsPublished: oggetti totali pubblicati nella sessione corrente o precedentegroupsPublished: unità (track, group) totali pubblicate nella sessione corrente o precedentesplitCmafChunks: modalità di packaging corrente (true= chunk separati,false= chunk uniti)includeSap: indica se il packaging di track/oggetti SAP è abilitatotransport,host,port,path: contesto endpoint per la sessione corrente o precedenteconnectionId: ultimo ID di connessione di trasporto notolastError: ultimo errore a livello publisher, se presente
stats_json() rimane disponibile per le integrazioni esistenti, ma è deprecato perché un'API di polling JSON implica un supporto alla telemetria runtime che l'API publisher bloccante non fornisce.
transport:"raw_quic"o"webtransport"host: host dell'endpoint configuratoport: porta dell'endpoint configuratopath: percorso dell'endpoint configuratoconnectionId: identificatore della connessione di trasporto, se disponibilelastError: ultimo messaggio di errore tracciato
Esempio:
{
"active": true,
"connected": true,
"publishingLive": false,
"bytesPublished": 123456,
"objectsPublished": 84,
"groupsPublished": 42,
"splitCmafChunks": true,
"includeSap": false,
"transport": "webtransport",
"host": "relay.example.com",
"port": 443,
"path": "/moq",
"connectionId": "wt-140735229359104",
"lastError": ""
}#include "openmoq/publisher/publisher_api.h"
#include <chrono>
#include <iostream>
int main() {
using namespace openmoq::publisher;
using namespace openmoq::publisher::transport;
PublisherConfig config;
config.draft_version = DraftVersion::kDraft14;
config.track_namespace = "media";
config.split_cmaf_chunks = true;
config.subscriber_timeout = std::chrono::seconds(30);
Publisher publisher(config);
EndpointConfig endpoint;
endpoint.transport = TransportKind::kWebTransport;
endpoint.host = "relay.example.com";
endpoint.port = 443;
endpoint.path = "/moq";
endpoint.path_explicit = true;
TlsConfig tls;
tls.insecure_skip_verify = false;
const auto status = publisher.publish_file("sample.mp4", endpoint, tls);
if (!status.ok) {
std::cerr << "publish failed: " << status.message << "\n";
return 1;
}
const auto disconnect_status = publisher.disconnect(0);
if (!disconnect_status.ok) {
std::cerr << "disconnect failed: " << disconnect_status.message << "\n";
return 1;
}
std::cout << "publish complete\n";
return 0;
}publish_live(...) consuma un singolo stream di byte MP4.
Per la pubblicazione live multi-track, il modello comune è:
- Eseguire encoder video e audio su thread separati.
- Muxare i campioni codificati in MP4 frammentato (prima
ftyp/moov, poi coppiemoof/mdat) su un thread muxer. - Inserire i byte muxati in una pipe thread-safe.
- Passare il lato di lettura della pipe a
publish_live(...).
Schema di esempio:
#include "openmoq/publisher/publisher_api.h"
#include <atomic>
#include <condition_variable>
#include <cstdint>
#include <cstring>
#include <deque>
#include <iostream>
#include <mutex>
#include <optional>
#include <streambuf>
#include <thread>
#include <vector>
// Minimal thread-safe byte pipe exposed as std::istream.
class BytePipeBuf : public std::streambuf {
public:
BytePipeBuf() { setg(buffer_, buffer_, buffer_); }
void push_bytes(const std::uint8_t* data, std::size_t size) {
{
std::lock_guard<std::mutex> lock(mutex_);
queue_.insert(queue_.end(), data, data + size);
}
cv_.notify_one();
}
void close() {
{
std::lock_guard<std::mutex> lock(mutex_);
closed_ = true;
}
cv_.notify_one();
}
protected:
int_type underflow() override {
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [&] { return closed_ || !queue_.empty(); });
if (queue_.empty()) {
return traits_type::eof();
}
const std::size_t n = std::min(queue_.size(), sizeof(buffer_));
for (std::size_t i = 0; i < n; ++i) {
buffer_[i] = queue_.front();
queue_.pop_front();
}
setg(buffer_, buffer_, buffer_ + static_cast<std::ptrdiff_t>(n));
return traits_type::to_int_type(*gptr());
}
private:
std::mutex mutex_;
std::condition_variable cv_;
std::deque<std::uint8_t> queue_;
bool closed_ = false;
char buffer_[4096]{};
};
class BytePipeIStream : public std::istream {
public:
BytePipeIStream() : std::istream(&buf_) {}
BytePipeBuf& pipe() { return buf_; }
private:
BytePipeBuf buf_;
};
int main() {
using namespace openmoq::publisher;
using namespace openmoq::publisher::transport;
PublisherConfig config;
config.draft_version = DraftVersion::kDraft14;
config.track_namespace = "media";
config.split_cmaf_chunks = true;
config.paced = true;
Publisher publisher(config);
EndpointConfig endpoint;
endpoint.transport = TransportKind::kWebTransport;
endpoint.host = "relay.example.com";
endpoint.port = 443;
endpoint.path = "/moq";
endpoint.path_explicit = true;
TlsConfig tls;
tls.insecure_skip_verify = false;
BytePipeIStream live_input;
std::atomic<bool> running{true};
// Replace this with your real fMP4 muxer output callback.
auto write_muxed_fragment = [&](const std::vector<std::uint8_t>& fragment_bytes) {
live_input.pipe().push_bytes(fragment_bytes.data(), fragment_bytes.size());
};
std::thread video_encoder([&] {
while (running.load()) {
// 1) Encode next video frame (H264/H265/AV1 etc.)
// 2) Send encoded sample to muxer
// muxer.add_video_sample(...);
// muxer callback eventually calls write_muxed_fragment(...)
}
});
std::thread audio_encoder([&] {
while (running.load()) {
// 1) Encode next audio frame (AAC/Opus etc.)
// 2) Send encoded sample to muxer
// muxer.add_audio_sample(...);
// muxer callback eventually calls write_muxed_fragment(...)
}
});
// Optional: dedicated muxer thread if your muxer is not internally threaded.
std::thread muxer_thread([&] {
// Emit the init segment first, then fragmented MP4 moof/mdat pairs.
// For the current live path, prefer track-separated fragments, such as
// ffmpeg output generated with +separate_moof, so each moof/mdat pair
// maps cleanly to one media track.
// Each emitted chunk calls write_muxed_fragment(fragment_bytes).
});
const TransportStatus status = publisher.publish_live(live_input, endpoint, tls);
if (!status.ok) {
std::cerr << "live publish failed: " << status.message << "\n";
} else {
const TransportStatus disconnect_status = publisher.disconnect(0);
if (!disconnect_status.ok) {
std::cerr << "disconnect failed: " << disconnect_status.message << "\n";
}
}
running.store(false);
live_input.pipe().close();
if (video_encoder.joinable()) {
video_encoder.join();
}
if (audio_encoder.joinable()) {
audio_encoder.join();
}
if (muxer_thread.joinable()) {
muxer_thread.join();
}
return status.ok ? 0 : 1;
}- Mantenete
publish_live(...)sul proprio thread worker in modo che la pipeline di ingestione possa continuare indipendentemente. - Assicuratevi che il muxer emetta un ordine MP4 frammentato valido: prima
ftyp+moov, poi coppiemoof/mdat. - Applicate backpressure nella coda o nella pipe per evitare una crescita illimitata della memoria se la pubblicazione di rete rallenta.
- Timestampate audio e video da una timeline comune prima del muxing per preservare la sincronizzazione A/V.
- Dopo il completamento della pubblicazione, chiamate
disconnect(0)per una chiusura esplicita e ordinata. - Quindi fermate gli encoder, svuotate il muxer, chiudete la pipe e fate join dei thread.