Skip to content

Commit 8776f8e

Browse files
Qizotkixelatedclaude
authored
Add multi encoder output support (#29)
* Add multi encoder output support * Fix initializer list order and redundant map lookups Reorder constructor initializer list to match member declaration order (origin, session, broadcast). Reduce double find() calls in AudioData and VideoData to a single lookup each. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Luke Curley <kixelated@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 38a1495 commit 8776f8e

2 files changed

Lines changed: 52 additions & 37 deletions

File tree

src/moq-output.cpp

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@ MoQOutput::MoQOutput(obs_data_t *, obs_output_t *output)
1414
total_bytes_sent(0),
1515
connect_time_ms(0),
1616
origin(moq_origin_create()),
17-
broadcast(moq_publish_create()),
1817
session(0),
19-
video(0),
20-
audio(0)
18+
broadcast(moq_publish_create())
2119
{
2220
}
2321

@@ -57,9 +55,15 @@ bool MoQOutput::Start()
5755

5856
path = obs_service_get_connect_info(service, OBS_SERVICE_CONNECT_INFO_STREAM_KEY);
5957

60-
const obs_encoder_t *encoder = obs_output_get_video_encoder2(output, 0);
58+
bool found_encoder = false;
59+
for (uint32_t idx = 0; idx < MAX_OUTPUT_VIDEO_ENCODERS; idx++) {
60+
if (obs_output_get_video_encoder2(output, idx)) {
61+
found_encoder = true;
62+
break;
63+
}
64+
}
6165

62-
if (!encoder) {
66+
if (!found_encoder) {
6367
LOG_ERROR("Failed to get video encoder");
6468
return false;
6569
}
@@ -113,15 +117,17 @@ void MoQOutput::Stop(bool signal)
113117
session = 0;
114118
}
115119

116-
if (video > 0) {
117-
moq_publish_media_close(video);
118-
video = 0;
120+
for (auto &[encoder, handle] : video_tracks) {
121+
if (handle > 0)
122+
moq_publish_media_close(handle);
119123
}
124+
video_tracks.clear();
120125

121-
if (audio > 0) {
122-
moq_publish_media_close(audio);
123-
audio = 0;
126+
for (auto &[encoder, handle] : audio_tracks) {
127+
if (handle > 0)
128+
moq_publish_media_close(handle);
124129
}
130+
audio_tracks.clear();
125131

126132
if (signal) {
127133
obs_output_signal_stop(output, OBS_OUTPUT_SUCCESS);
@@ -147,14 +153,18 @@ void MoQOutput::Data(struct encoder_packet *packet)
147153

148154
void MoQOutput::AudioData(struct encoder_packet *packet)
149155
{
150-
if (audio == 0) {
151-
AudioInit();
152-
}
156+
obs_encoder_t *encoder = packet->encoder;
153157

154-
if (audio < 0) {
158+
auto it = audio_tracks.find(encoder);
159+
if (it == audio_tracks.end()) {
160+
AudioInit(encoder);
161+
it = audio_tracks.find(encoder);
162+
}
163+
if (it == audio_tracks.end() || it->second < 0) {
155164
// We failed to initialize the audio track, so we can't write any data.
156165
return;
157166
}
167+
int handle = it->second;
158168

159169
// Add ~1 second offset to handle negative PTS from audio priming frames.
160170
// TODO: This is slightly wrong when den is not evenly divisible by num, but close enough.
@@ -166,7 +176,7 @@ void MoQOutput::AudioData(struct encoder_packet *packet)
166176

167177
auto pts_us = util_mul_div64(pts, 1000000ULL * packet->timebase_num, packet->timebase_den);
168178

169-
auto result = moq_publish_media_frame(audio, packet->data, packet->size, pts_us);
179+
auto result = moq_publish_media_frame(handle, packet->data, packet->size, pts_us);
170180
if (result < 0) {
171181
LOG_ERROR("Failed to write audio frame: %d", result);
172182
return;
@@ -177,13 +187,16 @@ void MoQOutput::AudioData(struct encoder_packet *packet)
177187

178188
void MoQOutput::VideoData(struct encoder_packet *packet)
179189
{
180-
if (video == 0) {
181-
VideoInit();
182-
}
190+
obs_encoder_t *encoder = packet->encoder;
183191

184-
if (video < 0) {
185-
return;
192+
auto it = video_tracks.find(encoder);
193+
if (it == video_tracks.end()) {
194+
VideoInit(encoder);
195+
it = video_tracks.find(encoder);
186196
}
197+
if (it == video_tracks.end() || it->second < 0)
198+
return;
199+
int handle = it->second;
187200

188201
// Add ~1 second offset to match audio for A/V sync.
189202
// TODO: This is slightly wrong when den is not evenly divisible by num, but close enough.
@@ -195,7 +208,7 @@ void MoQOutput::VideoData(struct encoder_packet *packet)
195208

196209
auto pts_us = util_mul_div64(pts, 1000000ULL * packet->timebase_num, packet->timebase_den);
197210

198-
auto result = moq_publish_media_frame(video, packet->data, packet->size, pts_us);
211+
auto result = moq_publish_media_frame(handle, packet->data, packet->size, pts_us);
199212
if (result < 0) {
200213
LOG_ERROR("Failed to write video frame: %d", result);
201214
return;
@@ -204,9 +217,8 @@ void MoQOutput::VideoData(struct encoder_packet *packet)
204217
total_bytes_sent += packet->size;
205218
}
206219

207-
void MoQOutput::VideoInit()
220+
void MoQOutput::VideoInit(obs_encoder_t *encoder)
208221
{
209-
obs_encoder_t *encoder = obs_output_get_video_encoder(output);
210222
if (!encoder) {
211223
LOG_ERROR("Failed to get video encoder");
212224
return;
@@ -247,18 +259,18 @@ void MoQOutput::VideoInit()
247259
}
248260

249261
// Intialize the media import module with the codec and initialization data.
250-
video = moq_publish_media_ordered(broadcast, moq_codec, strlen(moq_codec), extra_data, extra_size);
251-
if (video < 0) {
252-
LOG_ERROR("Failed to initialize video track: %d", video);
262+
int handle = moq_publish_media_ordered(broadcast, moq_codec, strlen(moq_codec), extra_data, extra_size);
263+
video_tracks[encoder] = handle;
264+
if (handle < 0) {
265+
LOG_ERROR("Failed to initialize video track: %d", handle);
253266
return;
254267
}
255268

256269
LOG_INFO("Video track initialized successfully");
257270
}
258271

259-
void MoQOutput::AudioInit()
272+
void MoQOutput::AudioInit(obs_encoder_t *encoder)
260273
{
261-
obs_encoder_t *encoder = obs_output_get_audio_encoder(output, 0);
262274
if (!encoder) {
263275
LOG_ERROR("Failed to get audio encoder");
264276
return;
@@ -286,9 +298,10 @@ void MoQOutput::AudioInit()
286298

287299
const char *codec = obs_encoder_get_codec(encoder);
288300

289-
audio = moq_publish_media_ordered(broadcast, codec, strlen(codec), extra_data, extra_size);
290-
if (audio < 0) {
291-
LOG_ERROR("Failed to initialize audio track: %d", audio);
301+
int handle = moq_publish_media_ordered(broadcast, codec, strlen(codec), extra_data, extra_size);
302+
audio_tracks[encoder] = handle;
303+
if (handle < 0) {
304+
LOG_ERROR("Failed to initialize audio track: %d", handle);
292305
return;
293306
}
294307

@@ -297,7 +310,8 @@ void MoQOutput::AudioInit()
297310

298311
void register_moq_output()
299312
{
300-
const uint32_t base_flags = OBS_OUTPUT_ENCODED | OBS_OUTPUT_SERVICE;
313+
const uint32_t base_flags = OBS_OUTPUT_ENCODED | OBS_OUTPUT_SERVICE | OBS_OUTPUT_MULTI_TRACK_VIDEO |
314+
OBS_OUTPUT_MULTI_TRACK_AUDIO;
301315

302316
const char *audio_codecs = "aac;opus";
303317
const char *video_codecs = "h264;hevc;av1";

src/moq-output.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <obs-module.h>
33

44
#include <chrono>
5+
#include <map>
56
#include <string>
67
#include "logger.h"
78

@@ -26,9 +27,9 @@ class MoQOutput
2627
}
2728

2829
private:
29-
void VideoInit();
30+
void VideoInit(obs_encoder_t *encoder);
3031
void VideoData(struct encoder_packet *packet);
31-
void AudioInit();
32+
void AudioInit(obs_encoder_t *encoder);
3233
void AudioData(struct encoder_packet *packet);
3334

3435
obs_output_t *output;
@@ -43,8 +44,8 @@ class MoQOutput
4344
int origin;
4445
int session;
4546
int broadcast;
46-
int video;
47-
int audio;
47+
std::map<obs_encoder_t *, int> video_tracks;
48+
std::map<obs_encoder_t *, int> audio_tracks;
4849
};
4950

5051
void register_moq_output();

0 commit comments

Comments
 (0)