+
+void MJPEGEncoder::add_stream(HTTPD::StreamID stream_id)
+{
+ AVFormatContextWithCloser avctx;
+
+ // Set up the mux. We don't use the Mux wrapper, because it's geared towards
+ // a situation with only one video stream (and possibly one audio stream)
+ // with known width/height, and we don't need the extra functionality it provides.
+ avctx.reset(avformat_alloc_context());
+ avctx->oformat = av_guess_format("nut", nullptr, nullptr);
+
+ uint8_t *buf = (uint8_t *)av_malloc(MUX_BUFFER_SIZE);
+ avctx->pb = avio_alloc_context(buf, MUX_BUFFER_SIZE, 1, &ffmpeg_contexts[stream_id], nullptr, nullptr, nullptr);
+ avctx->pb->write_data_type = &MJPEGEncoder::write_packet2_thunk;
+ avctx->flags = AVFMT_FLAG_CUSTOM_IO;
+
+ if (stream_id.type == HTTPD::MULTICAM_STREAM) {
+ for (unsigned card_idx = 0; card_idx < global_flags.card_to_mjpeg_stream_export.size(); ++card_idx) {
+ add_video_stream(avctx.get());
+ }
+ for (unsigned card_idx = 0; card_idx < global_flags.card_to_mjpeg_stream_export.size(); ++card_idx) {
+ add_audio_stream(avctx.get());
+ }
+ } else {
+ assert(stream_id.type == HTTPD::SIPHON_STREAM);
+ add_video_stream(avctx.get());
+ add_audio_stream(avctx.get());
+ }
+ finalize_mux(avctx.get());
+
+ Stream s;
+ s.avctx = move(avctx);
+ streams[stream_id] = move(s);
+}
+
+void MJPEGEncoder::update_siphon_streams()
+{
+ // Bring the list of streams into sync with what the clients need.
+ for (auto it = streams.begin(); it != streams.end(); ) {
+ if (it->first.type != HTTPD::SIPHON_STREAM) {
+ ++it;
+ continue;
+ }
+ if (httpd->get_num_connected_siphon_clients(it->first.index) == 0) {
+ av_free(it->second.avctx->pb->buffer);
+ streams.erase(it++);
+ } else {
+ ++it;
+ }
+ }
+ for (unsigned stream_idx = 0; stream_idx < MAX_VIDEO_CARDS; ++stream_idx) {
+ HTTPD::StreamID stream_id{ HTTPD::SIPHON_STREAM, stream_idx };
+ if (streams.count(stream_id) == 0 && httpd->get_num_connected_siphon_clients(stream_idx) > 0) {
+ add_stream(stream_id);
+ }
+ }
+}
+
+void MJPEGEncoder::create_ffmpeg_context(HTTPD::StreamID stream_id)
+{
+ ffmpeg_contexts.emplace(stream_id, WritePacket2Context{ this, stream_id });
+}