chromecast: add SPU blending support
[vlc.git] / modules / stream_out / chromecast / cast.cpp
blob7394cbf90cf328513c8df86beb9816dd3616bb88
1 /*****************************************************************************
2 * cast.cpp: Chromecast sout module for vlc
3 *****************************************************************************
4 * Copyright © 2014-2015 VideoLAN
6 * Authors: Adrien Maglo <magsoft@videolan.org>
7 * Jean-Baptiste Kempf <jb@videolan.org>
8 * Steve Lhomme <robux4@videolabs.io>
10 * This program is free software; you can redistribute it and/or modify it
11 * under the terms of the GNU Lesser General Public License as published by
12 * the Free Software Foundation; either version 2.1 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
25 /*****************************************************************************
26 * Preamble
27 *****************************************************************************/
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
33 #include "chromecast.h"
34 #include <vlc_dialog.h>
36 #include <vlc_sout.h>
37 #include <vlc_block.h>
38 #include <vlc_modules.h>
39 #include <vlc_httpd.h>
41 #include <cassert>
43 #define TRANSCODING_NONE 0x0
44 #define TRANSCODING_VIDEO 0x1
45 #define TRANSCODING_AUDIO 0x2
47 struct sout_access_out_sys_t
49 sout_access_out_sys_t(httpd_host_t *httpd_host, intf_sys_t * const intf,
50 const char *psz_url);
51 ~sout_access_out_sys_t();
53 void clear();
54 void stop();
55 void prepare(sout_stream_t *p_stream, const std::string &mime);
56 int url_cb(httpd_client_t *cl, httpd_message_t *answer, const httpd_message_t *query);
57 void fifo_put_back(block_t *);
58 ssize_t write(sout_access_out_t *p_access, block_t *p_block);
59 void close();
62 private:
63 void clearUnlocked();
64 void initCopy();
65 void putCopy(block_t *p_block);
66 void restoreCopy();
68 intf_sys_t * const m_intf;
69 httpd_url_t *m_url;
70 httpd_client_t *m_client;
71 vlc_fifo_t *m_fifo;
72 block_t *m_header;
73 block_t *m_copy_chain;
74 block_t **m_copy_last;
75 size_t m_copy_size;
76 bool m_eof;
77 std::string m_mime;
80 struct sout_stream_sys_t
82 sout_stream_sys_t(httpd_host_t *httpd_host, intf_sys_t * const intf, bool has_video, int port)
83 : httpd_host(httpd_host)
84 , access_out_live(httpd_host, intf, "/stream")
85 , p_out(NULL)
86 , p_intf(intf)
87 , b_supports_video(has_video)
88 , i_port(port)
89 , first_video_keyframe_pts( -1 )
90 , es_changed( true )
91 , cc_has_input( false )
92 , cc_reload( false )
93 , has_video( false )
94 , out_force_reload( false )
95 , perf_warning_shown( false )
96 , transcoding_state( TRANSCODING_NONE )
97 , out_streams_added( 0 )
99 assert(p_intf != NULL);
100 vlc_mutex_init(&lock);
103 ~sout_stream_sys_t()
105 vlc_mutex_destroy(&lock);
108 bool canDecodeVideo( vlc_fourcc_t i_codec ) const;
109 bool canDecodeAudio( sout_stream_t* p_stream, vlc_fourcc_t i_codec,
110 const audio_format_t* p_fmt ) const;
111 bool startSoutChain(sout_stream_t* p_stream,
112 const std::vector<sout_stream_id_sys_t*> &new_streams,
113 const std::string &sout, int new_transcoding_state);
114 void stopSoutChain(sout_stream_t* p_stream);
115 sout_stream_id_sys_t *GetSubId( sout_stream_t*, sout_stream_id_sys_t*, bool update = true );
116 void setNextTranscodingState();
117 bool transcodingCanFallback() const;
119 httpd_host_t *httpd_host;
120 sout_access_out_sys_t access_out_live;
122 sout_stream_t *p_out;
123 std::string mime;
125 vlc_mutex_t lock; /* for input events cb */
127 intf_sys_t * const p_intf;
128 const bool b_supports_video;
129 const int i_port;
131 sout_stream_id_sys_t * video_proxy_id;
132 mtime_t first_video_keyframe_pts;
134 bool es_changed;
135 bool cc_has_input;
136 bool cc_reload;
137 bool has_video;
138 bool out_force_reload;
139 bool perf_warning_shown;
140 int transcoding_state;
141 std::vector<sout_stream_id_sys_t*> streams;
142 std::vector<sout_stream_id_sys_t*> out_streams;
143 unsigned int out_streams_added;
144 unsigned int spu_streams_count;
146 private:
147 bool UpdateOutput( sout_stream_t * );
150 struct sout_stream_id_sys_t
152 es_format_t fmt;
153 sout_stream_id_sys_t *p_sub_id;
156 #define SOUT_CFG_PREFIX "sout-chromecast-"
158 static const vlc_fourcc_t DEFAULT_TRANSCODE_VIDEO = VLC_CODEC_H264;
159 static const char DEFAULT_MUXER[] = "avformat{mux=matroska,options={live=1}}";
160 static const char DEFAULT_MUXER_WEBM[] = "avformat{mux=webm,options={live=1}}";
163 /*****************************************************************************
164 * Local prototypes
165 *****************************************************************************/
166 static int Open(vlc_object_t *);
167 static void Close(vlc_object_t *);
168 static int ProxyOpen(vlc_object_t *);
169 static int AccessOpen(vlc_object_t *);
170 static void AccessClose(vlc_object_t *);
172 static const char *const ppsz_sout_options[] = {
173 "ip", "port", "http-port", "video", NULL
176 /*****************************************************************************
177 * Module descriptor
178 *****************************************************************************/
180 #define HTTP_PORT_TEXT N_("HTTP port")
181 #define HTTP_PORT_LONGTEXT N_("This sets the HTTP port of the local server " \
182 "used to stream the media to the Chromecast.")
183 #define PERF_TEXT N_( "Performance warning" )
184 #define PERF_LONGTEXT N_( "Display a performance warning when transcoding" )
185 #define AUDIO_PASSTHROUGH_TEXT N_( "Enable Audio passthrough" )
186 #define AUDIO_PASSTHROUGH_LONGTEXT N_( "Disable if your receiver does not support Dolby®." )
188 enum {
189 CONVERSION_QUALITY_HIGH = 0,
190 CONVERSION_QUALITY_MEDIUM = 1,
191 CONVERSION_QUALITY_LOW = 2,
192 CONVERSION_QUALITY_LOWCPU = 3,
195 #if defined (__ANDROID__) || defined (__arm__) || (defined (TARGET_OS_IPHONE) && TARGET_OS_IPHONE)
196 # define CONVERSION_QUALITY_DEFAULT CONVERSION_QUALITY_LOW
197 #else
198 # define CONVERSION_QUALITY_DEFAULT CONVERSION_QUALITY_MEDIUM
199 #endif
201 static const int conversion_quality_list[] = {
202 CONVERSION_QUALITY_HIGH,
203 CONVERSION_QUALITY_MEDIUM,
204 CONVERSION_QUALITY_LOW,
205 CONVERSION_QUALITY_LOWCPU,
207 static const char *const conversion_quality_list_text[] = {
208 N_( "High (high quality and high bandwidth)" ),
209 N_( "Medium (medium quality and medium bandwidth)" ),
210 N_( "Low (low quality and low bandwidth)" ),
211 N_( "Low CPU (low quality but high bandwidth)" ),
214 #define CONVERSION_QUALITY_TEXT N_( "Conversion quality" )
215 #define CONVERSION_QUALITY_LONGTEXT N_( "Change this option to increase conversion speed or quality." )
217 #define IP_ADDR_TEXT N_("IP Address")
218 #define IP_ADDR_LONGTEXT N_("IP Address of the Chromecast.")
219 #define PORT_TEXT N_("Chromecast port")
220 #define PORT_LONGTEXT N_("The port used to talk to the Chromecast.")
222 /* Fifo size after we tell the demux to pace */
223 #define HTTPD_BUFFER_PACE INT64_C(2 * 1024 * 1024) /* 2 MB */
224 /* Fifo size after we drop packets (should not happen) */
225 #define HTTPD_BUFFER_MAX INT64_C(32 * 1024 * 1024) /* 32 MB */
226 #define HTTPD_BUFFER_COPY_MAX INT64_C(10 * 1024 * 1024) /* 10 MB */
228 vlc_module_begin ()
230 set_shortname(N_("Chromecast"))
231 set_description(N_("Chromecast stream output"))
232 set_capability("sout stream", 0)
233 add_shortcut("chromecast")
234 set_category(CAT_SOUT)
235 set_subcategory(SUBCAT_SOUT_STREAM)
236 set_callbacks(Open, Close)
238 add_string(SOUT_CFG_PREFIX "ip", NULL, NULL, NULL, false)
239 change_private()
240 add_integer(SOUT_CFG_PREFIX "port", CHROMECAST_CONTROL_PORT, NULL, NULL, false)
241 change_private()
242 add_bool(SOUT_CFG_PREFIX "video", true, NULL, NULL, false)
243 change_private()
244 add_integer(SOUT_CFG_PREFIX "http-port", HTTP_PORT, HTTP_PORT_TEXT, HTTP_PORT_LONGTEXT, false)
245 add_obsolete_string(SOUT_CFG_PREFIX "mux")
246 add_obsolete_string(SOUT_CFG_PREFIX "mime")
247 add_integer(SOUT_CFG_PREFIX "show-perf-warning", 1, PERF_TEXT, PERF_LONGTEXT, true )
248 change_private()
249 add_bool(SOUT_CFG_PREFIX "audio-passthrough", false, AUDIO_PASSTHROUGH_TEXT, AUDIO_PASSTHROUGH_LONGTEXT, false )
250 add_integer(SOUT_CFG_PREFIX "conversion-quality", CONVERSION_QUALITY_DEFAULT,
251 CONVERSION_QUALITY_TEXT, CONVERSION_QUALITY_LONGTEXT, false );
252 change_integer_list(conversion_quality_list, conversion_quality_list_text)
254 add_submodule()
255 /* sout proxy that start the cc input when all streams are loaded */
256 add_shortcut("chromecast-proxy")
257 set_capability("sout stream", 0)
258 set_callbacks(ProxyOpen, NULL)
259 add_submodule()
260 set_subcategory(SUBCAT_SOUT_ACO)
261 add_shortcut("chromecast-http")
262 set_capability("sout access", 0)
263 set_callbacks(AccessOpen, AccessClose)
264 vlc_module_end ()
266 static sout_stream_id_sys_t *ProxyAdd(sout_stream_t *p_stream, const es_format_t *p_fmt)
268 sout_stream_sys_t *p_sys = p_stream->p_sys;
269 sout_stream_id_sys_t *id = sout_StreamIdAdd(p_stream->p_next, p_fmt);
270 if (id)
272 if (p_fmt->i_cat == VIDEO_ES)
273 p_sys->video_proxy_id = id;
274 p_sys->out_streams_added++;
276 return id;
279 static void ProxyDel(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
281 sout_stream_sys_t *p_sys = p_stream->p_sys;
282 p_sys->out_streams_added--;
283 if (id == p_sys->video_proxy_id)
284 p_sys->video_proxy_id = NULL;
285 return sout_StreamIdDel(p_stream->p_next, id);
288 static int ProxySend(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
289 block_t *p_buffer)
291 sout_stream_sys_t *p_sys = p_stream->p_sys;
292 if (p_sys->cc_has_input
293 || p_sys->out_streams_added >= p_sys->out_streams.size() - p_sys->spu_streams_count)
295 if (p_sys->has_video)
297 // In case of video, the first block must be a keyframe
298 if (id == p_sys->video_proxy_id)
300 if (p_sys->first_video_keyframe_pts == -1
301 && p_buffer->i_flags & BLOCK_FLAG_TYPE_I)
302 p_sys->first_video_keyframe_pts = p_buffer->i_pts;
304 else // no keyframe for audio
305 p_buffer->i_flags &= ~BLOCK_FLAG_TYPE_I;
307 if (p_buffer->i_pts < p_sys->first_video_keyframe_pts
308 || p_sys->first_video_keyframe_pts == -1)
310 block_Release(p_buffer);
311 return VLC_SUCCESS;
315 mtime_t pause_delay = p_sys->p_intf->getPauseDelay();
316 if( p_buffer->i_pts != VLC_TS_INVALID )
317 p_buffer->i_pts -= pause_delay;
318 if( p_buffer->i_dts != VLC_TS_INVALID )
319 p_buffer->i_dts -= pause_delay;
321 int ret = sout_StreamIdSend(p_stream->p_next, id, p_buffer);
322 if (ret == VLC_SUCCESS && !p_sys->cc_has_input)
324 /* Start the chromecast only when all streams are added into the
325 * last sout (the http one) */
326 p_sys->p_intf->setHasInput(p_sys->mime);
327 p_sys->cc_has_input = true;
329 return ret;
331 else
333 block_Release(p_buffer);
334 return VLC_SUCCESS;
338 static void ProxyFlush(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
340 sout_StreamFlush(p_stream->p_next, id);
343 static int ProxyOpen(vlc_object_t *p_this)
345 sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
346 sout_stream_sys_t *p_sys = (sout_stream_sys_t *) var_InheritAddress(p_this, SOUT_CFG_PREFIX "sys");
347 if (p_sys == NULL || p_stream->p_next == NULL)
348 return VLC_EGENERIC;
350 p_stream->p_sys = (sout_stream_sys_t *) p_sys;
351 p_sys->out_streams_added = 0;
353 p_stream->pf_add = ProxyAdd;
354 p_stream->pf_del = ProxyDel;
355 p_stream->pf_send = ProxySend;
356 p_stream->pf_flush = ProxyFlush;
357 return VLC_SUCCESS;
360 static int httpd_url_cb(httpd_callback_sys_t *data, httpd_client_t *cl,
361 httpd_message_t *answer, const httpd_message_t *query)
363 sout_access_out_sys_t *p_sys = reinterpret_cast<sout_access_out_sys_t *>(data);
364 return p_sys->url_cb(cl, answer, query);
367 sout_access_out_sys_t::sout_access_out_sys_t(httpd_host_t *httpd_host,
368 intf_sys_t * const intf,
369 const char *psz_url)
370 : m_intf(intf)
371 , m_client(NULL)
372 , m_header(NULL)
373 , m_copy_chain(NULL)
374 , m_eof(true)
376 m_fifo = block_FifoNew();
377 if (!m_fifo)
378 throw std::runtime_error( "block_FifoNew failed" );
379 m_url = httpd_UrlNew(httpd_host, psz_url, NULL, NULL);
380 if (m_url == NULL)
382 block_FifoRelease(m_fifo);
383 throw std::runtime_error( "httpd_UrlNew failed" );
385 httpd_UrlCatch(m_url, HTTPD_MSG_GET, httpd_url_cb,
386 (httpd_callback_sys_t*)this);
387 initCopy();
390 sout_access_out_sys_t::~sout_access_out_sys_t()
392 httpd_UrlDelete(m_url);
393 block_FifoRelease(m_fifo);
396 void sout_access_out_sys_t::clearUnlocked()
398 block_ChainRelease(vlc_fifo_DequeueAllUnlocked(m_fifo));
399 if (m_header)
401 block_Release(m_header);
402 m_header = NULL;
404 m_eof = true;
405 initCopy();
408 void sout_access_out_sys_t::initCopy()
410 block_ChainRelease(m_copy_chain);
411 m_copy_chain = NULL;
412 m_copy_last = &m_copy_chain;
413 m_copy_size = 0;
416 void sout_access_out_sys_t::putCopy(block_t *p_block)
418 while (m_copy_size >= HTTPD_BUFFER_COPY_MAX)
420 assert(m_copy_chain);
421 block_t *copy = m_copy_chain;
422 m_copy_chain = copy->p_next;
423 m_copy_size -= copy->i_buffer;
424 block_Release(copy);
426 if (!m_copy_chain)
428 assert(m_copy_size == 0);
429 m_copy_last = &m_copy_chain;
431 block_ChainLastAppend(&m_copy_last, p_block);
432 m_copy_size += p_block->i_buffer;
435 void sout_access_out_sys_t::restoreCopy()
437 if (m_copy_chain)
439 fifo_put_back(m_copy_chain);
440 m_copy_chain = NULL;
441 initCopy();
445 void sout_access_out_sys_t::clear()
447 vlc_fifo_Lock(m_fifo);
448 clearUnlocked();
449 vlc_fifo_Unlock(m_fifo);
450 vlc_fifo_Signal(m_fifo);
453 void sout_access_out_sys_t::stop()
455 vlc_fifo_Lock(m_fifo);
456 clearUnlocked();
457 m_intf->setPacing(false);
458 m_client = NULL;
459 vlc_fifo_Unlock(m_fifo);
460 vlc_fifo_Signal(m_fifo);
463 void sout_access_out_sys_t::prepare(sout_stream_t *p_stream, const std::string &mime)
465 var_SetAddress(p_stream->p_sout, SOUT_CFG_PREFIX "access-out-sys", this);
467 vlc_fifo_Lock(m_fifo);
468 clearUnlocked();
469 m_intf->setPacing(false);
470 m_mime = mime;
471 m_eof = false;
472 vlc_fifo_Unlock(m_fifo);
475 void sout_access_out_sys_t::fifo_put_back(block_t *p_block)
477 block_t *p_fifo = vlc_fifo_DequeueAllUnlocked(m_fifo);
478 vlc_fifo_QueueUnlocked(m_fifo, p_block);
479 vlc_fifo_QueueUnlocked(m_fifo, p_fifo);
482 int sout_access_out_sys_t::url_cb(httpd_client_t *cl, httpd_message_t *answer,
483 const httpd_message_t *query)
485 if (!answer || !query || !cl)
486 return VLC_SUCCESS;
488 vlc_fifo_Lock(m_fifo);
490 if (!answer->i_body_offset)
492 /* When doing a lot a load requests, we can serve data to a client that
493 * will be closed (the close request is already sent). In that case, we
494 * should also serve data used by the old client to the new one. */
495 restoreCopy();
496 m_client = cl;
499 /* Send data per 512kB minimum */
500 size_t i_min_buffer = 524288;
501 while (m_client && vlc_fifo_GetBytes(m_fifo) < i_min_buffer && !m_eof)
502 vlc_fifo_Wait(m_fifo);
504 block_t *p_block = NULL;
505 if (m_client && vlc_fifo_GetBytes(m_fifo) > 0)
507 /* if less data is available, then we must be EOF */
508 if (vlc_fifo_GetBytes(m_fifo) < i_min_buffer)
510 assert(m_eof);
511 i_min_buffer = vlc_fifo_GetBytes(m_fifo);
513 block_t *p_first = vlc_fifo_DequeueUnlocked(m_fifo);
515 assert(p_first);
516 size_t i_total_size = p_first->i_buffer;
517 block_t *p_next = NULL, *p_cur = p_first;
519 while (i_total_size < i_min_buffer)
521 p_next = vlc_fifo_DequeueUnlocked(m_fifo);
522 assert(p_next);
523 i_total_size += p_next->i_buffer;
524 p_cur->p_next = p_next;
525 p_cur = p_cur->p_next;
527 assert(i_total_size >= i_min_buffer);
529 if (p_next != NULL)
531 p_block = block_Alloc(i_total_size);
532 if (p_block)
533 block_ChainExtract(p_first, p_block->p_buffer, p_block->i_buffer);
534 block_ChainRelease(p_first);
536 else
537 p_block = p_first;
539 if (vlc_fifo_GetBytes(m_fifo) < HTTPD_BUFFER_PACE)
540 m_intf->setPacing(false);
543 answer->i_proto = HTTPD_PROTO_HTTP;
544 answer->i_version= 0;
545 answer->i_type = HTTPD_MSG_ANSWER;
546 answer->i_status = 200;
548 if (p_block)
550 if (answer->i_body_offset == 0)
552 httpd_MsgAdd(answer, "Content-type", "%s", m_mime.c_str());
553 httpd_MsgAdd(answer, "Cache-Control", "no-cache");
554 httpd_MsgAdd(answer, "Connection", "close");
557 const bool send_header = answer->i_body_offset == 0 && m_header != NULL;
558 size_t i_answer_size = p_block->i_buffer;
559 if (send_header)
560 i_answer_size += m_header->i_buffer;
562 answer->p_body = (uint8_t *) malloc(i_answer_size);
563 if (answer->p_body)
565 answer->i_body = i_answer_size;
566 answer->i_body_offset += answer->i_body;
567 size_t i_block_offset = 0;
568 if (send_header)
570 memcpy(answer->p_body, m_header->p_buffer, m_header->i_buffer);
571 i_block_offset = m_header->i_buffer;
573 memcpy(&answer->p_body[i_block_offset], p_block->p_buffer, p_block->i_buffer);
576 putCopy(p_block);
578 if (!answer->i_body)
579 httpd_MsgAdd(answer, "Connection", "close");
581 vlc_fifo_Unlock(m_fifo);
582 return VLC_SUCCESS;
585 ssize_t sout_access_out_sys_t::write(sout_access_out_t *p_access, block_t *p_block)
587 size_t i_len = p_block->i_buffer;
589 vlc_fifo_Lock(m_fifo);
591 if (p_block->i_flags & BLOCK_FLAG_HEADER)
593 if (m_header)
594 block_Release(m_header);
595 m_header = p_block;
597 else
599 /* Drop buffer is the fifo is really full */
600 if (vlc_fifo_GetBytes(m_fifo) >= HTTPD_BUFFER_PACE)
602 /* XXX: Hackisk way to pace between the sout (controlled by the
603 * decoder thread) and the demux filter (controlled by the input
604 * thread). When the httpd FIFO reaches a specific size, we tell
605 * the demux filter to pace and wait a little before queing this
606 * block, but not too long since we don't want to block decoder
607 * thread controls. If the pacing fails (should not happen), we
608 * drop the first block in order to make room. The demux filter
609 * will be unpaced when the data is read from the httpd thread. */
611 m_intf->setPacing(true);
613 while (vlc_fifo_GetBytes(m_fifo) >= HTTPD_BUFFER_MAX)
615 block_t *p_drop = vlc_fifo_DequeueUnlocked(m_fifo);
616 msg_Warn(p_access, "httpd buffer full: dropping %zuB", p_drop->i_buffer);
617 block_Release(p_drop);
620 vlc_fifo_QueueUnlocked(m_fifo, p_block);
623 m_eof = false;
625 vlc_fifo_Unlock(m_fifo);
626 vlc_fifo_Signal(m_fifo);
628 return i_len;
631 void sout_access_out_sys_t::close()
633 vlc_fifo_Lock(m_fifo);
634 m_eof = true;
635 m_intf->setPacing(false);
636 vlc_fifo_Unlock(m_fifo);
637 vlc_fifo_Signal(m_fifo);
640 ssize_t AccessWrite(sout_access_out_t *p_access, block_t *p_block)
642 sout_access_out_sys_t *p_sys = p_access->p_sys;
643 return p_sys->write(p_access, p_block);
646 static int AccessControl(sout_access_out_t *p_access, int i_query, va_list args)
648 (void) p_access;
650 switch (i_query)
652 case ACCESS_OUT_CONTROLS_PACE:
653 *va_arg(args, bool *) = true;
654 break;
655 default:
656 return VLC_EGENERIC;
658 return VLC_SUCCESS;
661 static int AccessOpen(vlc_object_t *p_this)
663 sout_access_out_t *p_access = (sout_access_out_t*)p_this;
665 sout_access_out_sys_t *p_sys = (sout_access_out_sys_t *)
666 var_InheritAddress(p_access, SOUT_CFG_PREFIX "access-out-sys");
667 if (p_sys == NULL)
668 return VLC_EGENERIC;
670 p_access->pf_write = AccessWrite;
671 p_access->pf_control = AccessControl;
672 p_access->p_sys = p_sys;
674 return VLC_SUCCESS;
677 static void AccessClose(vlc_object_t *p_this)
679 sout_access_out_t *p_access = (sout_access_out_t*)p_this;
680 sout_access_out_sys_t *p_sys = p_access->p_sys;
682 p_sys->close();
685 /*****************************************************************************
686 * Sout callbacks
687 *****************************************************************************/
688 static sout_stream_id_sys_t *Add(sout_stream_t *p_stream, const es_format_t *p_fmt)
690 sout_stream_sys_t *p_sys = p_stream->p_sys;
691 vlc_mutex_locker locker(&p_sys->lock);
693 if (!p_sys->b_supports_video)
695 if (p_fmt->i_cat != AUDIO_ES)
696 return NULL;
699 sout_stream_id_sys_t *p_sys_id = (sout_stream_id_sys_t *)malloc( sizeof(sout_stream_id_sys_t) );
700 if (p_sys_id != NULL)
702 es_format_Copy( &p_sys_id->fmt, p_fmt );
703 p_sys_id->p_sub_id = NULL;
705 p_sys->streams.push_back( p_sys_id );
706 p_sys->es_changed = true;
708 return p_sys_id;
712 static void DelInternal(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
713 bool reset_config)
715 sout_stream_sys_t *p_sys = p_stream->p_sys;
717 for (std::vector<sout_stream_id_sys_t*>::iterator it = p_sys->streams.begin();
718 it != p_sys->streams.end(); )
720 sout_stream_id_sys_t *p_sys_id = *it;
721 if ( p_sys_id == id )
723 if ( p_sys_id->p_sub_id != NULL )
725 sout_StreamIdDel( p_sys->p_out, p_sys_id->p_sub_id );
726 for (std::vector<sout_stream_id_sys_t*>::iterator out_it = p_sys->out_streams.begin();
727 out_it != p_sys->out_streams.end(); )
729 if (*out_it == id)
731 p_sys->out_streams.erase(out_it);
732 p_sys->es_changed = reset_config;
733 p_sys->out_force_reload = reset_config;
734 if( p_sys_id->fmt.i_cat == VIDEO_ES )
735 p_sys->has_video = false;
736 else if( p_sys_id->fmt.i_cat == SPU_ES )
737 p_sys->spu_streams_count--;
738 break;
740 out_it++;
744 es_format_Clean( &p_sys_id->fmt );
745 free( p_sys_id );
746 p_sys->streams.erase( it );
747 break;
749 it++;
752 if ( p_sys->out_streams.empty() )
754 p_sys->stopSoutChain(p_stream);
755 p_sys->p_intf->requestPlayerStop();
756 p_sys->access_out_live.clear();
757 p_sys->transcoding_state = TRANSCODING_NONE;
761 static void Del(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
763 sout_stream_sys_t *p_sys = p_stream->p_sys;
765 vlc_mutex_locker locker(&p_sys->lock);
766 DelInternal(p_stream, id, true);
770 * Transcode steps:
771 * 0: Accept HEVC/VP9 & all supported audio formats
772 * 1: Transcode to h264 & accept all supported audio formats if the video codec
773 * was HEVC/VP9
774 * 2: Transcode to H264 & MP3
776 * Additionally:
777 * - Allow (E)AC3 passthrough depending on the audio-passthrough
778 * config value, except for the final step, where we just give up and transcode
779 * everything.
780 * - Disallow multichannel AAC
782 * Supported formats: https://developers.google.com/cast/docs/media
785 bool sout_stream_sys_t::canDecodeVideo( vlc_fourcc_t i_codec ) const
787 if( transcoding_state & TRANSCODING_VIDEO )
788 return false;
789 return i_codec == VLC_CODEC_H264 || i_codec == VLC_CODEC_HEVC
790 || i_codec == VLC_CODEC_VP8 || i_codec == VLC_CODEC_VP9;
793 bool sout_stream_sys_t::canDecodeAudio( sout_stream_t *p_stream,
794 vlc_fourcc_t i_codec,
795 const audio_format_t* p_fmt ) const
797 if( transcoding_state & TRANSCODING_AUDIO )
798 return false;
799 if ( i_codec == VLC_CODEC_A52 || i_codec == VLC_CODEC_EAC3 )
801 return var_InheritBool( p_stream, SOUT_CFG_PREFIX "audio-passthrough" );
803 if ( i_codec == VLC_FOURCC('h', 'a', 'a', 'c') ||
804 i_codec == VLC_FOURCC('l', 'a', 'a', 'c') ||
805 i_codec == VLC_FOURCC('s', 'a', 'a', 'c') ||
806 i_codec == VLC_CODEC_MPGA ||
807 i_codec == VLC_CODEC_MP4A )
809 return p_fmt->i_channels <= 2;
811 return i_codec == VLC_CODEC_VORBIS || i_codec == VLC_CODEC_OPUS ||
812 i_codec == VLC_CODEC_MP3;
815 void sout_stream_sys_t::stopSoutChain(sout_stream_t *p_stream)
817 (void) p_stream;
819 if ( unlikely( p_out != NULL ) )
821 for ( size_t i = 0; i < out_streams.size(); i++ )
823 if ( out_streams[i]->p_sub_id != NULL )
825 sout_StreamIdDel( p_out, out_streams[i]->p_sub_id );
826 out_streams[i]->p_sub_id = NULL;
829 out_streams.clear();
830 sout_StreamChainDelete( p_out, NULL );
831 p_out = NULL;
835 bool sout_stream_sys_t::startSoutChain(sout_stream_t *p_stream,
836 const std::vector<sout_stream_id_sys_t*> &new_streams,
837 const std::string &sout, int new_transcoding_state)
839 stopSoutChain( p_stream );
841 msg_Dbg( p_stream, "Creating chain %s", sout.c_str() );
842 cc_has_input = false;
843 cc_reload = false;
844 first_video_keyframe_pts = -1;
845 video_proxy_id = NULL;
846 has_video = false;
847 out_streams = new_streams;
848 spu_streams_count = 0;
849 transcoding_state = new_transcoding_state;
851 access_out_live.prepare( p_stream, mime );
853 p_out = sout_StreamChainNew( p_stream->p_sout, sout.c_str(), NULL, NULL);
854 if (p_out == NULL) {
855 msg_Dbg(p_stream, "could not create sout chain:%s", sout.c_str());
856 out_streams.clear();
857 access_out_live.clear();
858 return false;
861 /* check the streams we can actually add */
862 for (std::vector<sout_stream_id_sys_t*>::iterator it = out_streams.begin();
863 it != out_streams.end(); )
865 sout_stream_id_sys_t *p_sys_id = *it;
866 p_sys_id->p_sub_id = sout_StreamIdAdd( p_out, &p_sys_id->fmt );
867 if ( p_sys_id->p_sub_id == NULL )
869 msg_Err( p_stream, "can't handle %4.4s stream", (char *)&p_sys_id->fmt.i_codec );
870 es_format_Clean( &p_sys_id->fmt );
871 it = out_streams.erase( it );
873 else
875 if( p_sys_id->fmt.i_cat == VIDEO_ES )
876 has_video = true;
877 else if( p_sys_id->fmt.i_cat == SPU_ES )
878 spu_streams_count++;
879 ++it;
883 if (out_streams.empty())
885 stopSoutChain( p_stream );
886 access_out_live.clear();
887 return false;
890 /* Ask to retry if we are not transcoding everything (because we can trust
891 * what we encode) */
892 p_intf->setRetryOnFail(transcodingCanFallback());
894 return true;
897 void sout_stream_sys_t::setNextTranscodingState()
899 if (!(transcoding_state & TRANSCODING_VIDEO))
900 transcoding_state |= TRANSCODING_VIDEO;
901 else if (!(transcoding_state & TRANSCODING_AUDIO))
902 transcoding_state = TRANSCODING_AUDIO;
905 bool sout_stream_sys_t::transcodingCanFallback() const
907 return transcoding_state != (TRANSCODING_VIDEO|TRANSCODING_AUDIO);
910 bool sout_stream_sys_t::UpdateOutput( sout_stream_t *p_stream )
912 assert( p_stream->p_sys == this );
914 if ( !es_changed )
915 return true;
917 es_changed = false;
919 bool canRemux = true;
920 vlc_fourcc_t i_codec_video = 0, i_codec_audio = 0;
921 const es_format_t *p_original_audio = NULL;
922 const es_format_t *p_original_video = NULL;
923 const es_format_t *p_original_spu = NULL;
924 bool b_out_streams_changed = false;
925 std::vector<sout_stream_id_sys_t*> new_streams;
927 for (std::vector<sout_stream_id_sys_t*>::iterator it = streams.begin(); it != streams.end(); ++it)
929 const es_format_t *p_es = &(*it)->fmt;
930 if (p_es->i_cat == AUDIO_ES && p_original_audio == NULL)
932 if ( !canDecodeAudio( p_stream, p_es->i_codec, &p_es->audio ) )
934 msg_Dbg( p_stream, "can't remux audio track %d codec %4.4s", p_es->i_id, (const char*)&p_es->i_codec );
935 canRemux = false;
937 else if (i_codec_audio == 0)
938 i_codec_audio = p_es->i_codec;
939 p_original_audio = p_es;
940 new_streams.push_back(*it);
942 else if (b_supports_video)
944 if (p_es->i_cat == VIDEO_ES && p_original_video == NULL)
946 if (!canDecodeVideo( p_es->i_codec ))
948 msg_Dbg( p_stream, "can't remux video track %d codec %4.4s",
949 p_es->i_id, (const char*)&p_es->i_codec );
950 canRemux = false;
952 else if (i_codec_video == 0 && !p_original_spu)
953 i_codec_video = p_es->i_codec;
954 p_original_video = p_es;
955 new_streams.push_back(*it);
957 else if (p_es->i_cat == SPU_ES && p_original_spu == NULL)
959 msg_Dbg( p_stream, "forcing video transcode because of subtitle '%4.4s'",
960 p_es->i_id, (const char*)&p_es->i_codec );
961 canRemux = false;
962 i_codec_video = 0;
963 p_original_spu = p_es;
964 new_streams.push_back(*it);
966 else
967 continue;
969 else
970 continue;
972 bool b_found = out_force_reload;
973 for (std::vector<sout_stream_id_sys_t*>::iterator out_it = out_streams.begin();
974 out_it != out_streams.end() && !b_found; ++out_it)
976 if (*out_it == *it)
977 b_found = true;
979 if (!b_found)
980 b_out_streams_changed = true;
983 if (new_streams.empty())
985 p_intf->requestPlayerStop();
986 return true;
989 /* Don't restart sout and CC session if streams didn't change */
990 if (!out_force_reload && new_streams.size() == out_streams.size() && !b_out_streams_changed)
991 return true;
993 out_force_reload = false;
995 std::stringstream ssout;
996 int new_transcoding_state = TRANSCODING_NONE;
997 if ( !canRemux )
999 if ( !perf_warning_shown && i_codec_video == 0 && p_original_video
1000 && var_InheritInteger( p_stream, SOUT_CFG_PREFIX "show-perf-warning" ) )
1002 int res = vlc_dialog_wait_question( p_stream,
1003 VLC_DIALOG_QUESTION_WARNING,
1004 _("Cancel"), _("OK"), _("Ok, Don't warn me again"),
1005 _("Performance warning"),
1006 _("Casting this video requires conversion. "
1007 "This conversion can use all the available power and "
1008 "could quickly drain your battery." ) );
1009 if ( res <= 0 )
1010 return false;
1011 perf_warning_shown = true;
1012 if ( res == 2 )
1013 config_PutInt(p_stream, SOUT_CFG_PREFIX "show-perf-warning", 0 );
1016 static const char video_maxres_hd[] = "maxwidth=1920,maxheight=1080";
1017 static const char video_maxres_720p[] = "maxwidth=1280,maxheight=720";
1018 static const char video_x264_preset_veryfast[] = "veryfast";
1019 static const char video_x264_preset_ultrafast[] = "ultrafast";
1021 const int i_quality = var_InheritInteger( p_stream, SOUT_CFG_PREFIX "conversion-quality" );
1022 const char *psz_video_maxres;
1023 const char *psz_video_x264_preset;
1024 unsigned i_video_x264_crf_hd, i_video_x264_crf_720p;
1025 bool b_audio_mp3;
1027 switch ( i_quality )
1029 case CONVERSION_QUALITY_HIGH:
1030 psz_video_maxres = video_maxres_hd;
1031 i_video_x264_crf_hd = i_video_x264_crf_720p = 21;
1032 psz_video_x264_preset = video_x264_preset_veryfast;
1033 b_audio_mp3 = false;
1034 break;
1035 case CONVERSION_QUALITY_MEDIUM:
1036 psz_video_maxres = video_maxres_hd;
1037 i_video_x264_crf_hd = 23;
1038 i_video_x264_crf_720p = 21;
1039 psz_video_x264_preset = video_x264_preset_veryfast;
1040 b_audio_mp3 = false;
1041 break;
1042 case CONVERSION_QUALITY_LOW:
1043 psz_video_maxres = video_maxres_720p;
1044 i_video_x264_crf_hd = i_video_x264_crf_720p = 23;
1045 psz_video_x264_preset = video_x264_preset_veryfast;
1046 b_audio_mp3 = true;
1047 break;
1048 default:
1049 case CONVERSION_QUALITY_LOWCPU:
1050 psz_video_maxres = video_maxres_720p;
1051 i_video_x264_crf_hd = i_video_x264_crf_720p = 23;
1052 psz_video_x264_preset = video_x264_preset_ultrafast;
1053 b_audio_mp3 = true;
1054 break;
1057 /* If we were already transcoding: force mp3 because maybe the CC may
1058 * have failed because of vorbis. */
1059 if (transcoding_state & TRANSCODING_AUDIO)
1060 b_audio_mp3 = true;
1062 /* TODO: provide audio samplerate and channels */
1063 ssout << "transcode{";
1064 char s_fourcc[5];
1065 if ( i_codec_audio == 0 && p_original_audio )
1067 if ( !b_audio_mp3
1068 && p_original_audio->audio.i_channels > 2 && module_exists( "vorbis" ) )
1069 i_codec_audio = VLC_CODEC_VORBIS;
1070 else
1071 i_codec_audio = VLC_CODEC_MP3;
1073 msg_Dbg( p_stream, "Converting audio to %.4s", (const char*)&i_codec_audio );
1074 ssout << "acodec=";
1075 vlc_fourcc_to_char( i_codec_audio, s_fourcc );
1076 s_fourcc[4] = '\0';
1077 ssout << s_fourcc << ',';
1079 /* XXX: higher vorbis qualities can cause glitches on some CC
1080 * devices (Chromecast 1 & 2) */
1081 if( i_codec_audio == VLC_CODEC_VORBIS )
1082 ssout << "aenc=vorbis{quality=4},";
1083 new_transcoding_state |= TRANSCODING_AUDIO;
1085 if ( i_codec_video == 0 && p_original_video )
1087 i_codec_video = DEFAULT_TRANSCODE_VIDEO;
1088 msg_Dbg( p_stream, "Converting video to %.4s", (const char*)&i_codec_video );
1089 ssout << "vcodec=";
1090 vlc_fourcc_to_char( i_codec_video, s_fourcc );
1091 s_fourcc[4] = '\0';
1092 ssout << s_fourcc << ',' << psz_video_maxres << ',';
1094 const video_format_t *p_vid = &p_original_video->video;
1095 const bool b_hdres = p_vid == NULL || p_vid->i_height == 0 || p_vid->i_height >= 800;
1096 unsigned i_video_x264_crf = b_hdres ? i_video_x264_crf_hd : i_video_x264_crf_720p;
1098 if( p_vid == NULL
1099 || p_vid->i_frame_rate == 0 || p_vid->i_frame_rate_base == 0
1100 || ( p_vid->i_frame_rate / p_vid->i_frame_rate_base ) > 30 )
1102 /* Even force 24fps if the frame rate is unknown */
1103 msg_Warn( p_stream, "lowering frame rate to 24fps" );
1104 ssout << "fps=24,";
1107 if( i_codec_video == VLC_CODEC_H264 )
1109 if ( module_exists("x264") )
1110 ssout << "venc=x264{preset=" << psz_video_x264_preset
1111 << ",crf=" << i_video_x264_crf << "},";
1113 new_transcoding_state |= TRANSCODING_VIDEO;
1115 if ( p_original_spu )
1116 ssout << "soverlay,";
1117 ssout << "}:";
1120 const bool is_webm = ( i_codec_audio == 0 || i_codec_audio == VLC_CODEC_VORBIS ||
1121 i_codec_audio == VLC_CODEC_OPUS ) &&
1122 ( i_codec_video == 0 || i_codec_video == VLC_CODEC_VP8 ||
1123 i_codec_video == VLC_CODEC_VP9 );
1125 if ( !p_original_video )
1127 if( is_webm )
1128 mime = "audio/webm";
1129 else
1130 mime = "audio/x-matroska";
1132 else
1134 if ( is_webm )
1135 mime = "video/webm";
1136 else
1137 mime = "video/x-matroska";
1140 ssout << "chromecast-proxy:"
1141 << "std{mux=" << ( is_webm ? DEFAULT_MUXER_WEBM : DEFAULT_MUXER )
1142 << ",access=chromecast-http}";
1144 if ( !startSoutChain( p_stream, new_streams, ssout.str(),
1145 new_transcoding_state ) )
1146 p_intf->requestPlayerStop();
1147 return true;
1150 sout_stream_id_sys_t *sout_stream_sys_t::GetSubId( sout_stream_t *p_stream,
1151 sout_stream_id_sys_t *id,
1152 bool update )
1154 size_t i;
1156 assert( p_stream->p_sys == this );
1158 if ( update && UpdateOutput( p_stream ) == false )
1159 return NULL;
1161 for (i = 0; i < out_streams.size(); ++i)
1163 if ( id == (sout_stream_id_sys_t*) out_streams[i] )
1164 return out_streams[i]->p_sub_id;
1167 return NULL;
1170 static int Send(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
1171 block_t *p_buffer)
1173 sout_stream_sys_t *p_sys = p_stream->p_sys;
1174 vlc_mutex_locker locker(&p_sys->lock);
1176 sout_stream_id_sys_t *next_id = p_sys->GetSubId( p_stream, id );
1177 if ( next_id == NULL )
1179 block_Release( p_buffer );
1180 return VLC_EGENERIC;
1183 int ret = sout_StreamIdSend(p_sys->p_out, next_id, p_buffer);
1184 if (ret != VLC_SUCCESS)
1185 DelInternal(p_stream, id, false);
1187 return ret;
1190 static void Flush( sout_stream_t *p_stream, sout_stream_id_sys_t *id )
1192 sout_stream_sys_t *p_sys = p_stream->p_sys;
1193 vlc_mutex_locker locker(&p_sys->lock);
1195 sout_stream_id_sys_t *next_id = p_sys->GetSubId( p_stream, id, false );
1196 if ( next_id == NULL )
1197 return;
1199 p_sys->access_out_live.stop();
1201 if (p_sys->cc_has_input)
1203 p_sys->p_intf->requestPlayerStop();
1204 p_sys->cc_has_input = false;
1206 p_sys->out_force_reload = p_sys->es_changed = true;
1209 static void on_input_event_cb(void *data, enum cc_input_event event, union cc_input_arg arg )
1211 sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(data);
1212 sout_stream_sys_t *p_sys = p_stream->p_sys;
1214 vlc_mutex_locker locker(&p_sys->lock);
1215 switch (event)
1217 case CC_INPUT_EVENT_EOF:
1218 /* In case of EOF: stop the sout chain in order to drain all
1219 * sout/demuxers/access. If EOF changes to false, reset es_changed
1220 * in order to reload the sout from next Send calls. */
1221 if( arg.eof )
1222 p_sys->stopSoutChain( p_stream );
1223 else
1224 p_sys->out_force_reload = p_sys->es_changed = true;
1225 break;
1226 case CC_INPUT_EVENT_RETRY:
1227 p_sys->stopSoutChain( p_stream );
1228 if( p_sys->transcodingCanFallback() )
1230 p_sys->setNextTranscodingState();
1231 msg_Warn(p_stream, "Load failed detected. Switching to next "
1232 "configuration. Transcoding video%s",
1233 p_sys->transcoding_state & TRANSCODING_AUDIO ? "/audio" : "");
1234 p_sys->out_force_reload = p_sys->es_changed = true;
1236 break;
1240 /*****************************************************************************
1241 * Open: connect to the Chromecast and initialize the sout
1242 *****************************************************************************/
1243 static int Open(vlc_object_t *p_this)
1245 sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
1246 sout_stream_sys_t *p_sys = NULL;
1247 intf_sys_t *p_intf = NULL;
1248 char *psz_ip = NULL;
1249 sout_stream_t *p_sout = NULL;
1250 httpd_host_t *httpd_host = NULL;
1251 bool b_supports_video = true;
1252 int i_local_server_port;
1253 int i_device_port;
1254 std::stringstream ss;
1256 config_ChainParse(p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg);
1258 psz_ip = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "ip");
1259 if ( psz_ip == NULL )
1261 msg_Err( p_this, "missing Chromecast IP address" );
1262 goto error;
1265 i_device_port = var_InheritInteger(p_stream, SOUT_CFG_PREFIX "port");
1266 i_local_server_port = var_InheritInteger(p_stream, SOUT_CFG_PREFIX "http-port");
1268 var_Create(p_stream, "http-port", VLC_VAR_INTEGER);
1269 var_SetInteger(p_stream, "http-port", i_local_server_port);
1270 var_Create(p_stream, "http-host", VLC_VAR_STRING);
1271 var_SetString(p_stream, "http-host", "");
1272 httpd_host = vlc_http_HostNew(VLC_OBJECT(p_stream));
1273 if (httpd_host == NULL)
1274 goto error;
1278 p_intf = new intf_sys_t( p_this, i_local_server_port, psz_ip, i_device_port,
1279 httpd_host );
1281 catch (const std::runtime_error& err )
1283 msg_Err( p_this, "cannot load the Chromecast controller (%s)", err.what() );
1284 goto error;
1286 catch (const std::bad_alloc& )
1288 p_intf = NULL;
1289 goto error;
1292 /* check if we can open the proper sout */
1293 ss << "http{mux=" << DEFAULT_MUXER << "}";
1294 p_sout = sout_StreamChainNew( p_stream->p_sout, ss.str().c_str(), NULL, NULL);
1295 if (p_sout == NULL) {
1296 msg_Dbg(p_stream, "could not create sout chain:%s", ss.str().c_str());
1297 goto error;
1299 sout_StreamChainDelete( p_sout, NULL );
1301 b_supports_video = var_GetBool(p_stream, SOUT_CFG_PREFIX "video");
1303 p_sys = new(std::nothrow) sout_stream_sys_t( httpd_host, p_intf, b_supports_video,
1304 i_local_server_port );
1305 if (unlikely(p_sys == NULL))
1306 goto error;
1308 p_intf->setOnInputEventCb(on_input_event_cb, p_stream);
1310 /* prevent sout-mux-caching since chromecast-proxy is already doing it */
1311 var_Create( p_stream->p_sout, "sout-mux-caching", VLC_VAR_INTEGER );
1312 var_SetInteger( p_stream->p_sout, "sout-mux-caching", 0 );
1314 var_Create( p_stream->p_sout, SOUT_CFG_PREFIX "sys", VLC_VAR_ADDRESS );
1315 var_SetAddress( p_stream->p_sout, SOUT_CFG_PREFIX "sys", p_sys );
1317 var_Create( p_stream->p_sout, SOUT_CFG_PREFIX "access-out-sys", VLC_VAR_ADDRESS );
1319 // Set the sout callbacks.
1320 p_stream->pf_add = Add;
1321 p_stream->pf_del = Del;
1322 p_stream->pf_send = Send;
1323 p_stream->pf_flush = Flush;
1325 p_stream->p_sys = p_sys;
1327 free(psz_ip);
1329 return VLC_SUCCESS;
1331 error:
1332 delete p_intf;
1333 if (httpd_host)
1334 httpd_HostDelete(httpd_host);
1335 free(psz_ip);
1336 delete p_sys;
1337 return VLC_EGENERIC;
1340 /*****************************************************************************
1341 * Close: destroy interface
1342 *****************************************************************************/
1343 static void Close(vlc_object_t *p_this)
1345 sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
1346 sout_stream_sys_t *p_sys = p_stream->p_sys;
1348 assert(p_sys->out_streams.empty() && p_sys->streams.empty());
1349 var_Destroy( p_stream->p_sout, SOUT_CFG_PREFIX "sys" );
1350 var_Destroy( p_stream->p_sout, SOUT_CFG_PREFIX "sout-mux-caching" );
1352 assert(p_sys->streams.empty() && p_sys->out_streams.empty());
1354 httpd_host_t *httpd_host = p_sys->httpd_host;
1355 delete p_sys->p_intf;
1356 delete p_sys;
1357 /* Delete last since p_intf and p_sys depends on httpd_host */
1358 httpd_HostDelete(httpd_host);