chromecast: test encoder modules
[vlc.git] / modules / stream_out / chromecast / cast.cpp
blob92d286c7d7bf1bffba343d3a8e4e55f00e524222
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 #if 0
48 /* TODO: works only with internal spu and transcoding/blending for now */
49 #define CC_ENABLE_SPU
50 #endif
52 struct sout_access_out_sys_t
54 sout_access_out_sys_t(httpd_host_t *httpd_host, intf_sys_t * const intf,
55 const char *psz_url);
56 ~sout_access_out_sys_t();
58 void clear();
59 void stop();
60 void prepare(sout_stream_t *p_stream, const std::string &mime);
61 int url_cb(httpd_client_t *cl, httpd_message_t *answer, const httpd_message_t *query);
62 void fifo_put_back(block_t *);
63 ssize_t write(sout_access_out_t *p_access, block_t *p_block);
64 void close();
67 private:
68 void clearUnlocked();
69 void initCopy();
70 void putCopy(block_t *p_block);
71 void restoreCopy();
73 intf_sys_t * const m_intf;
74 httpd_url_t *m_url;
75 httpd_client_t *m_client;
76 vlc_fifo_t *m_fifo;
77 block_t *m_header;
78 block_t *m_copy_chain;
79 block_t **m_copy_last;
80 size_t m_copy_size;
81 bool m_eof;
82 std::string m_mime;
85 struct sout_stream_sys_t
87 sout_stream_sys_t(httpd_host_t *httpd_host, intf_sys_t * const intf, bool has_video, int port)
88 : httpd_host(httpd_host)
89 , access_out_live(httpd_host, intf, "/stream")
90 , p_out(NULL)
91 , p_intf(intf)
92 , b_supports_video(has_video)
93 , i_port(port)
94 , first_video_keyframe_pts( -1 )
95 , es_changed( true )
96 , cc_has_input( false )
97 , cc_reload( false )
98 , cc_flushing( false )
99 , has_video( false )
100 , out_force_reload( false )
101 , perf_warning_shown( false )
102 , transcoding_state( TRANSCODING_NONE )
103 , venc_opt_idx ( -1 )
104 , out_streams_added( 0 )
106 assert(p_intf != NULL);
107 vlc_mutex_init(&lock);
110 ~sout_stream_sys_t()
112 vlc_mutex_destroy(&lock);
115 bool canDecodeVideo( vlc_fourcc_t i_codec ) const;
116 bool canDecodeAudio( sout_stream_t* p_stream, vlc_fourcc_t i_codec,
117 const audio_format_t* p_fmt ) const;
118 bool startSoutChain(sout_stream_t* p_stream,
119 const std::vector<sout_stream_id_sys_t*> &new_streams,
120 const std::string &sout, int new_transcoding_state);
121 void stopSoutChain(sout_stream_t* p_stream);
122 sout_stream_id_sys_t *GetSubId( sout_stream_t*, sout_stream_id_sys_t*, bool update = true );
123 bool isFlushing( sout_stream_t* );
124 void setNextTranscodingState();
125 bool transcodingCanFallback() const;
127 httpd_host_t *httpd_host;
128 sout_access_out_sys_t access_out_live;
130 sout_stream_t *p_out;
131 std::string mime;
133 vlc_mutex_t lock; /* for input events cb */
135 intf_sys_t * const p_intf;
136 const bool b_supports_video;
137 const int i_port;
139 sout_stream_id_sys_t * video_proxy_id;
140 mtime_t first_video_keyframe_pts;
142 bool es_changed;
143 bool cc_has_input;
144 bool cc_reload;
145 bool cc_flushing;
146 bool has_video;
147 bool out_force_reload;
148 bool perf_warning_shown;
149 int transcoding_state;
150 int venc_opt_idx;
151 std::vector<sout_stream_id_sys_t*> streams;
152 std::vector<sout_stream_id_sys_t*> out_streams;
153 unsigned int out_streams_added;
154 unsigned int spu_streams_count;
156 private:
157 std::string GetVencOption( sout_stream_t *, vlc_fourcc_t *,
158 const video_format_t *, int );
159 std::string GetAcodecOption( sout_stream_t *, vlc_fourcc_t *, const audio_format_t *, int );
160 std::string GetVcodecOption( sout_stream_t *, vlc_fourcc_t *, const video_format_t *, int );
161 bool UpdateOutput( sout_stream_t * );
164 struct sout_stream_id_sys_t
166 es_format_t fmt;
167 sout_stream_id_sys_t *p_sub_id;
168 bool flushed;
171 #define SOUT_CFG_PREFIX "sout-chromecast-"
173 static const char DEFAULT_MUXER[] = "avformat{mux=matroska,options={live=1}}";
174 static const char DEFAULT_MUXER_WEBM[] = "avformat{mux=webm,options={live=1}}";
177 /*****************************************************************************
178 * Local prototypes
179 *****************************************************************************/
180 static int Open(vlc_object_t *);
181 static void Close(vlc_object_t *);
182 static int ProxyOpen(vlc_object_t *);
183 static int AccessOpen(vlc_object_t *);
184 static void AccessClose(vlc_object_t *);
186 static const char *const ppsz_sout_options[] = {
187 "ip", "port", "http-port", "video", NULL
190 /*****************************************************************************
191 * Module descriptor
192 *****************************************************************************/
194 #define HTTP_PORT_TEXT N_("HTTP port")
195 #define HTTP_PORT_LONGTEXT N_("This sets the HTTP port of the local server " \
196 "used to stream the media to the Chromecast.")
197 #define PERF_TEXT N_( "Performance warning" )
198 #define PERF_LONGTEXT N_( "Display a performance warning when transcoding" )
199 #define AUDIO_PASSTHROUGH_TEXT N_( "Enable Audio passthrough" )
200 #define AUDIO_PASSTHROUGH_LONGTEXT N_( "Disable if your receiver does not support Dolby®." )
202 enum {
203 CONVERSION_QUALITY_HIGH = 0,
204 CONVERSION_QUALITY_MEDIUM = 1,
205 CONVERSION_QUALITY_LOW = 2,
206 CONVERSION_QUALITY_LOWCPU = 3,
209 #if defined (__ANDROID__) || defined (__arm__) || (defined (TARGET_OS_IPHONE) && TARGET_OS_IPHONE)
210 # define CONVERSION_QUALITY_DEFAULT CONVERSION_QUALITY_LOW
211 #else
212 # define CONVERSION_QUALITY_DEFAULT CONVERSION_QUALITY_MEDIUM
213 #endif
215 static const int conversion_quality_list[] = {
216 CONVERSION_QUALITY_HIGH,
217 CONVERSION_QUALITY_MEDIUM,
218 CONVERSION_QUALITY_LOW,
219 CONVERSION_QUALITY_LOWCPU,
221 static const char *const conversion_quality_list_text[] = {
222 N_( "High (high quality and high bandwidth)" ),
223 N_( "Medium (medium quality and medium bandwidth)" ),
224 N_( "Low (low quality and low bandwidth)" ),
225 N_( "Low CPU (low quality but high bandwidth)" ),
228 #define CONVERSION_QUALITY_TEXT N_( "Conversion quality" )
229 #define CONVERSION_QUALITY_LONGTEXT N_( "Change this option to increase conversion speed or quality." )
231 #define IP_ADDR_TEXT N_("IP Address")
232 #define IP_ADDR_LONGTEXT N_("IP Address of the Chromecast.")
233 #define PORT_TEXT N_("Chromecast port")
234 #define PORT_LONGTEXT N_("The port used to talk to the Chromecast.")
236 /* Fifo size after we tell the demux to pace */
237 #define HTTPD_BUFFER_PACE INT64_C(2 * 1024 * 1024) /* 2 MB */
238 /* Fifo size after we drop packets (should not happen) */
239 #define HTTPD_BUFFER_MAX INT64_C(32 * 1024 * 1024) /* 32 MB */
240 #define HTTPD_BUFFER_COPY_MAX INT64_C(10 * 1024 * 1024) /* 10 MB */
242 vlc_module_begin ()
244 set_shortname(N_("Chromecast"))
245 set_description(N_("Chromecast stream output"))
246 set_capability("sout stream", 0)
247 add_shortcut("chromecast")
248 set_category(CAT_SOUT)
249 set_subcategory(SUBCAT_SOUT_STREAM)
250 set_callbacks(Open, Close)
252 add_string(SOUT_CFG_PREFIX "ip", NULL, NULL, NULL, false)
253 change_private()
254 add_integer(SOUT_CFG_PREFIX "port", CHROMECAST_CONTROL_PORT, NULL, NULL, false)
255 change_private()
256 add_bool(SOUT_CFG_PREFIX "video", true, NULL, NULL, false)
257 change_private()
258 add_integer(SOUT_CFG_PREFIX "http-port", HTTP_PORT, HTTP_PORT_TEXT, HTTP_PORT_LONGTEXT, false)
259 add_obsolete_string(SOUT_CFG_PREFIX "mux")
260 add_obsolete_string(SOUT_CFG_PREFIX "mime")
261 add_integer(SOUT_CFG_PREFIX "show-perf-warning", 1, PERF_TEXT, PERF_LONGTEXT, true )
262 change_private()
263 add_bool(SOUT_CFG_PREFIX "audio-passthrough", false, AUDIO_PASSTHROUGH_TEXT, AUDIO_PASSTHROUGH_LONGTEXT, false )
264 add_integer(SOUT_CFG_PREFIX "conversion-quality", CONVERSION_QUALITY_DEFAULT,
265 CONVERSION_QUALITY_TEXT, CONVERSION_QUALITY_LONGTEXT, false );
266 change_integer_list(conversion_quality_list, conversion_quality_list_text)
268 add_submodule()
269 /* sout proxy that start the cc input when all streams are loaded */
270 add_shortcut("chromecast-proxy")
271 set_capability("sout stream", 0)
272 set_callbacks(ProxyOpen, NULL)
273 add_submodule()
274 set_subcategory(SUBCAT_SOUT_ACO)
275 add_shortcut("chromecast-http")
276 set_capability("sout access", 0)
277 set_callbacks(AccessOpen, AccessClose)
278 vlc_module_end ()
280 static sout_stream_id_sys_t *ProxyAdd(sout_stream_t *p_stream, const es_format_t *p_fmt)
282 sout_stream_sys_t *p_sys = p_stream->p_sys;
283 sout_stream_id_sys_t *id = sout_StreamIdAdd(p_stream->p_next, p_fmt);
284 if (id)
286 if (p_fmt->i_cat == VIDEO_ES)
287 p_sys->video_proxy_id = id;
288 p_sys->out_streams_added++;
290 return id;
293 static void ProxyDel(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
295 sout_stream_sys_t *p_sys = p_stream->p_sys;
296 p_sys->out_streams_added--;
297 if (id == p_sys->video_proxy_id)
298 p_sys->video_proxy_id = NULL;
299 return sout_StreamIdDel(p_stream->p_next, id);
302 static int ProxySend(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
303 block_t *p_buffer)
305 sout_stream_sys_t *p_sys = p_stream->p_sys;
306 if (p_sys->cc_has_input
307 || p_sys->out_streams_added >= p_sys->out_streams.size() - p_sys->spu_streams_count)
309 if (p_sys->has_video)
311 // In case of video, the first block must be a keyframe
312 if (id == p_sys->video_proxy_id)
314 if (p_sys->first_video_keyframe_pts == -1
315 && p_buffer->i_flags & BLOCK_FLAG_TYPE_I)
316 p_sys->first_video_keyframe_pts = p_buffer->i_pts;
318 else // no keyframe for audio
319 p_buffer->i_flags &= ~BLOCK_FLAG_TYPE_I;
321 if (p_buffer->i_pts < p_sys->first_video_keyframe_pts
322 || p_sys->first_video_keyframe_pts == -1)
324 block_Release(p_buffer);
325 return VLC_SUCCESS;
329 mtime_t pause_delay = p_sys->p_intf->getPauseDelay();
330 if( p_buffer->i_pts != VLC_TS_INVALID )
331 p_buffer->i_pts -= pause_delay;
332 if( p_buffer->i_dts != VLC_TS_INVALID )
333 p_buffer->i_dts -= pause_delay;
335 int ret = sout_StreamIdSend(p_stream->p_next, id, p_buffer);
336 if (ret == VLC_SUCCESS && !p_sys->cc_has_input)
338 /* Start the chromecast only when all streams are added into the
339 * last sout (the http one) */
340 p_sys->p_intf->setHasInput(p_sys->mime);
341 p_sys->cc_has_input = true;
343 return ret;
345 else
347 block_Release(p_buffer);
348 return VLC_SUCCESS;
352 static void ProxyFlush(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
354 sout_StreamFlush(p_stream->p_next, id);
357 static int ProxyOpen(vlc_object_t *p_this)
359 sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
360 sout_stream_sys_t *p_sys = (sout_stream_sys_t *) var_InheritAddress(p_this, SOUT_CFG_PREFIX "sys");
361 if (p_sys == NULL || p_stream->p_next == NULL)
362 return VLC_EGENERIC;
364 p_stream->p_sys = (sout_stream_sys_t *) p_sys;
365 p_sys->out_streams_added = 0;
367 p_stream->pf_add = ProxyAdd;
368 p_stream->pf_del = ProxyDel;
369 p_stream->pf_send = ProxySend;
370 p_stream->pf_flush = ProxyFlush;
371 return VLC_SUCCESS;
374 static int httpd_url_cb(httpd_callback_sys_t *data, httpd_client_t *cl,
375 httpd_message_t *answer, const httpd_message_t *query)
377 sout_access_out_sys_t *p_sys = reinterpret_cast<sout_access_out_sys_t *>(data);
378 return p_sys->url_cb(cl, answer, query);
381 sout_access_out_sys_t::sout_access_out_sys_t(httpd_host_t *httpd_host,
382 intf_sys_t * const intf,
383 const char *psz_url)
384 : m_intf(intf)
385 , m_client(NULL)
386 , m_header(NULL)
387 , m_copy_chain(NULL)
388 , m_eof(true)
390 m_fifo = block_FifoNew();
391 if (!m_fifo)
392 throw std::runtime_error( "block_FifoNew failed" );
393 m_url = httpd_UrlNew(httpd_host, psz_url, NULL, NULL);
394 if (m_url == NULL)
396 block_FifoRelease(m_fifo);
397 throw std::runtime_error( "httpd_UrlNew failed" );
399 httpd_UrlCatch(m_url, HTTPD_MSG_GET, httpd_url_cb,
400 (httpd_callback_sys_t*)this);
401 initCopy();
404 sout_access_out_sys_t::~sout_access_out_sys_t()
406 httpd_UrlDelete(m_url);
407 block_FifoRelease(m_fifo);
410 void sout_access_out_sys_t::clearUnlocked()
412 block_ChainRelease(vlc_fifo_DequeueAllUnlocked(m_fifo));
413 if (m_header)
415 block_Release(m_header);
416 m_header = NULL;
418 m_eof = true;
419 initCopy();
422 void sout_access_out_sys_t::initCopy()
424 block_ChainRelease(m_copy_chain);
425 m_copy_chain = NULL;
426 m_copy_last = &m_copy_chain;
427 m_copy_size = 0;
430 void sout_access_out_sys_t::putCopy(block_t *p_block)
432 while (m_copy_size >= HTTPD_BUFFER_COPY_MAX)
434 assert(m_copy_chain);
435 block_t *copy = m_copy_chain;
436 m_copy_chain = copy->p_next;
437 m_copy_size -= copy->i_buffer;
438 block_Release(copy);
440 if (!m_copy_chain)
442 assert(m_copy_size == 0);
443 m_copy_last = &m_copy_chain;
445 block_ChainLastAppend(&m_copy_last, p_block);
446 m_copy_size += p_block->i_buffer;
449 void sout_access_out_sys_t::restoreCopy()
451 if (m_copy_chain)
453 fifo_put_back(m_copy_chain);
454 m_copy_chain = NULL;
455 initCopy();
459 void sout_access_out_sys_t::clear()
461 vlc_fifo_Lock(m_fifo);
462 clearUnlocked();
463 vlc_fifo_Unlock(m_fifo);
464 vlc_fifo_Signal(m_fifo);
467 void sout_access_out_sys_t::stop()
469 vlc_fifo_Lock(m_fifo);
470 clearUnlocked();
471 m_intf->setPacing(false);
472 m_client = NULL;
473 vlc_fifo_Unlock(m_fifo);
474 vlc_fifo_Signal(m_fifo);
477 void sout_access_out_sys_t::prepare(sout_stream_t *p_stream, const std::string &mime)
479 var_SetAddress(p_stream->p_sout, SOUT_CFG_PREFIX "access-out-sys", this);
481 vlc_fifo_Lock(m_fifo);
482 clearUnlocked();
483 m_intf->setPacing(false);
484 m_mime = mime;
485 m_eof = false;
486 vlc_fifo_Unlock(m_fifo);
489 void sout_access_out_sys_t::fifo_put_back(block_t *p_block)
491 block_t *p_fifo = vlc_fifo_DequeueAllUnlocked(m_fifo);
492 vlc_fifo_QueueUnlocked(m_fifo, p_block);
493 vlc_fifo_QueueUnlocked(m_fifo, p_fifo);
496 int sout_access_out_sys_t::url_cb(httpd_client_t *cl, httpd_message_t *answer,
497 const httpd_message_t *query)
499 if (!answer || !query || !cl)
500 return VLC_SUCCESS;
502 vlc_fifo_Lock(m_fifo);
504 if (!answer->i_body_offset)
506 /* When doing a lot a load requests, we can serve data to a client that
507 * will be closed (the close request is already sent). In that case, we
508 * should also serve data used by the old client to the new one. */
509 restoreCopy();
510 m_client = cl;
513 /* Send data per 512kB minimum */
514 size_t i_min_buffer = 524288;
515 while (m_client && vlc_fifo_GetBytes(m_fifo) < i_min_buffer && !m_eof)
516 vlc_fifo_Wait(m_fifo);
518 block_t *p_block = NULL;
519 if (m_client && vlc_fifo_GetBytes(m_fifo) > 0)
521 /* if less data is available, then we must be EOF */
522 if (vlc_fifo_GetBytes(m_fifo) < i_min_buffer)
524 assert(m_eof);
525 i_min_buffer = vlc_fifo_GetBytes(m_fifo);
527 block_t *p_first = vlc_fifo_DequeueUnlocked(m_fifo);
529 assert(p_first);
530 size_t i_total_size = p_first->i_buffer;
531 block_t *p_next = NULL, *p_cur = p_first;
533 while (i_total_size < i_min_buffer)
535 p_next = vlc_fifo_DequeueUnlocked(m_fifo);
536 assert(p_next);
537 i_total_size += p_next->i_buffer;
538 p_cur->p_next = p_next;
539 p_cur = p_cur->p_next;
541 assert(i_total_size >= i_min_buffer);
543 if (p_next != NULL)
545 p_block = block_Alloc(i_total_size);
546 if (p_block)
547 block_ChainExtract(p_first, p_block->p_buffer, p_block->i_buffer);
548 block_ChainRelease(p_first);
550 else
551 p_block = p_first;
553 if (vlc_fifo_GetBytes(m_fifo) < HTTPD_BUFFER_PACE)
554 m_intf->setPacing(false);
557 answer->i_proto = HTTPD_PROTO_HTTP;
558 answer->i_version= 0;
559 answer->i_type = HTTPD_MSG_ANSWER;
560 answer->i_status = 200;
562 if (p_block)
564 if (answer->i_body_offset == 0)
566 httpd_MsgAdd(answer, "Content-type", "%s", m_mime.c_str());
567 httpd_MsgAdd(answer, "Cache-Control", "no-cache");
568 httpd_MsgAdd(answer, "Connection", "close");
571 const bool send_header = answer->i_body_offset == 0 && m_header != NULL;
572 size_t i_answer_size = p_block->i_buffer;
573 if (send_header)
574 i_answer_size += m_header->i_buffer;
576 answer->p_body = (uint8_t *) malloc(i_answer_size);
577 if (answer->p_body)
579 answer->i_body = i_answer_size;
580 answer->i_body_offset += answer->i_body;
581 size_t i_block_offset = 0;
582 if (send_header)
584 memcpy(answer->p_body, m_header->p_buffer, m_header->i_buffer);
585 i_block_offset = m_header->i_buffer;
587 memcpy(&answer->p_body[i_block_offset], p_block->p_buffer, p_block->i_buffer);
590 putCopy(p_block);
592 if (!answer->i_body)
593 httpd_MsgAdd(answer, "Connection", "close");
595 vlc_fifo_Unlock(m_fifo);
596 return VLC_SUCCESS;
599 ssize_t sout_access_out_sys_t::write(sout_access_out_t *p_access, block_t *p_block)
601 size_t i_len = p_block->i_buffer;
603 vlc_fifo_Lock(m_fifo);
605 if (p_block->i_flags & BLOCK_FLAG_HEADER)
607 if (m_header)
608 block_Release(m_header);
609 m_header = p_block;
611 else
613 /* Drop buffer is the fifo is really full */
614 if (vlc_fifo_GetBytes(m_fifo) >= HTTPD_BUFFER_PACE)
616 /* XXX: Hackisk way to pace between the sout (controlled by the
617 * decoder thread) and the demux filter (controlled by the input
618 * thread). When the httpd FIFO reaches a specific size, we tell
619 * the demux filter to pace and wait a little before queing this
620 * block, but not too long since we don't want to block decoder
621 * thread controls. If the pacing fails (should not happen), we
622 * drop the first block in order to make room. The demux filter
623 * will be unpaced when the data is read from the httpd thread. */
625 m_intf->setPacing(true);
627 while (vlc_fifo_GetBytes(m_fifo) >= HTTPD_BUFFER_MAX)
629 block_t *p_drop = vlc_fifo_DequeueUnlocked(m_fifo);
630 msg_Warn(p_access, "httpd buffer full: dropping %zuB", p_drop->i_buffer);
631 block_Release(p_drop);
634 vlc_fifo_QueueUnlocked(m_fifo, p_block);
637 m_eof = false;
639 vlc_fifo_Unlock(m_fifo);
640 vlc_fifo_Signal(m_fifo);
642 return i_len;
645 void sout_access_out_sys_t::close()
647 vlc_fifo_Lock(m_fifo);
648 m_eof = true;
649 m_intf->setPacing(false);
650 vlc_fifo_Unlock(m_fifo);
651 vlc_fifo_Signal(m_fifo);
654 ssize_t AccessWrite(sout_access_out_t *p_access, block_t *p_block)
656 sout_access_out_sys_t *p_sys = p_access->p_sys;
657 return p_sys->write(p_access, p_block);
660 static int AccessControl(sout_access_out_t *p_access, int i_query, va_list args)
662 (void) p_access;
664 switch (i_query)
666 case ACCESS_OUT_CONTROLS_PACE:
667 *va_arg(args, bool *) = true;
668 break;
669 default:
670 return VLC_EGENERIC;
672 return VLC_SUCCESS;
675 static int AccessOpen(vlc_object_t *p_this)
677 sout_access_out_t *p_access = (sout_access_out_t*)p_this;
679 sout_access_out_sys_t *p_sys = (sout_access_out_sys_t *)
680 var_InheritAddress(p_access, SOUT_CFG_PREFIX "access-out-sys");
681 if (p_sys == NULL)
682 return VLC_EGENERIC;
684 p_access->pf_write = AccessWrite;
685 p_access->pf_control = AccessControl;
686 p_access->p_sys = p_sys;
688 return VLC_SUCCESS;
691 static void AccessClose(vlc_object_t *p_this)
693 sout_access_out_t *p_access = (sout_access_out_t*)p_this;
694 sout_access_out_sys_t *p_sys = p_access->p_sys;
696 p_sys->close();
699 /*****************************************************************************
700 * Sout callbacks
701 *****************************************************************************/
702 static sout_stream_id_sys_t *Add(sout_stream_t *p_stream, const es_format_t *p_fmt)
704 sout_stream_sys_t *p_sys = p_stream->p_sys;
705 vlc_mutex_locker locker(&p_sys->lock);
707 if (!p_sys->b_supports_video)
709 if (p_fmt->i_cat != AUDIO_ES)
710 return NULL;
713 sout_stream_id_sys_t *p_sys_id = (sout_stream_id_sys_t *)malloc( sizeof(sout_stream_id_sys_t) );
714 if (p_sys_id != NULL)
716 es_format_Copy( &p_sys_id->fmt, p_fmt );
717 p_sys_id->p_sub_id = NULL;
718 p_sys_id->flushed = false;
720 p_sys->streams.push_back( p_sys_id );
721 p_sys->es_changed = true;
723 return p_sys_id;
727 static void DelInternal(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
728 bool reset_config)
730 sout_stream_sys_t *p_sys = p_stream->p_sys;
732 for (std::vector<sout_stream_id_sys_t*>::iterator it = p_sys->streams.begin();
733 it != p_sys->streams.end(); )
735 sout_stream_id_sys_t *p_sys_id = *it;
736 if ( p_sys_id == id )
738 if ( p_sys_id->p_sub_id != NULL )
740 sout_StreamIdDel( p_sys->p_out, p_sys_id->p_sub_id );
741 for (std::vector<sout_stream_id_sys_t*>::iterator out_it = p_sys->out_streams.begin();
742 out_it != p_sys->out_streams.end(); )
744 if (*out_it == id)
746 p_sys->out_streams.erase(out_it);
747 p_sys->es_changed = reset_config;
748 p_sys->out_force_reload = reset_config;
749 if( p_sys_id->fmt.i_cat == VIDEO_ES )
750 p_sys->has_video = false;
751 else if( p_sys_id->fmt.i_cat == SPU_ES )
752 p_sys->spu_streams_count--;
753 break;
755 out_it++;
759 es_format_Clean( &p_sys_id->fmt );
760 free( p_sys_id );
761 p_sys->streams.erase( it );
762 break;
764 it++;
767 if ( p_sys->out_streams.empty() )
769 p_sys->stopSoutChain(p_stream);
770 p_sys->p_intf->requestPlayerStop();
771 p_sys->access_out_live.clear();
772 p_sys->transcoding_state = TRANSCODING_NONE;
776 static void Del(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
778 sout_stream_sys_t *p_sys = p_stream->p_sys;
780 vlc_mutex_locker locker(&p_sys->lock);
781 DelInternal(p_stream, id, true);
785 * Transcode steps:
786 * 0: Accept HEVC/VP9 & all supported audio formats
787 * 1: Transcode to h264 & accept all supported audio formats if the video codec
788 * was HEVC/VP9
789 * 2: Transcode to H264 & MP3
791 * Additionally:
792 * - Allow (E)AC3 passthrough depending on the audio-passthrough
793 * config value, except for the final step, where we just give up and transcode
794 * everything.
795 * - Disallow multichannel AAC
797 * Supported formats: https://developers.google.com/cast/docs/media
800 bool sout_stream_sys_t::canDecodeVideo( vlc_fourcc_t i_codec ) const
802 if( transcoding_state & TRANSCODING_VIDEO )
803 return false;
804 return i_codec == VLC_CODEC_H264 || i_codec == VLC_CODEC_HEVC
805 || i_codec == VLC_CODEC_VP8 || i_codec == VLC_CODEC_VP9;
808 bool sout_stream_sys_t::canDecodeAudio( sout_stream_t *p_stream,
809 vlc_fourcc_t i_codec,
810 const audio_format_t* p_fmt ) const
812 if( transcoding_state & TRANSCODING_AUDIO )
813 return false;
814 if ( i_codec == VLC_CODEC_A52 || i_codec == VLC_CODEC_EAC3 )
816 return var_InheritBool( p_stream, SOUT_CFG_PREFIX "audio-passthrough" );
818 if ( i_codec == VLC_FOURCC('h', 'a', 'a', 'c') ||
819 i_codec == VLC_FOURCC('l', 'a', 'a', 'c') ||
820 i_codec == VLC_FOURCC('s', 'a', 'a', 'c') ||
821 i_codec == VLC_CODEC_MPGA ||
822 i_codec == VLC_CODEC_MP4A )
824 return p_fmt->i_channels <= 2;
826 return i_codec == VLC_CODEC_VORBIS || i_codec == VLC_CODEC_OPUS ||
827 i_codec == VLC_CODEC_MP3;
830 void sout_stream_sys_t::stopSoutChain(sout_stream_t *p_stream)
832 (void) p_stream;
834 if ( unlikely( p_out != NULL ) )
836 for ( size_t i = 0; i < out_streams.size(); i++ )
838 if ( out_streams[i]->p_sub_id != NULL )
840 sout_StreamIdDel( p_out, out_streams[i]->p_sub_id );
841 out_streams[i]->p_sub_id = NULL;
844 out_streams.clear();
845 sout_StreamChainDelete( p_out, NULL );
846 p_out = NULL;
850 bool sout_stream_sys_t::startSoutChain(sout_stream_t *p_stream,
851 const std::vector<sout_stream_id_sys_t*> &new_streams,
852 const std::string &sout, int new_transcoding_state)
854 stopSoutChain( p_stream );
856 msg_Dbg( p_stream, "Creating chain %s", sout.c_str() );
857 cc_has_input = false;
858 cc_reload = false;
859 first_video_keyframe_pts = -1;
860 video_proxy_id = NULL;
861 has_video = false;
862 out_streams = new_streams;
863 spu_streams_count = 0;
864 transcoding_state = new_transcoding_state;
866 access_out_live.prepare( p_stream, mime );
868 p_out = sout_StreamChainNew( p_stream->p_sout, sout.c_str(), NULL, NULL);
869 if (p_out == NULL) {
870 msg_Dbg(p_stream, "could not create sout chain:%s", sout.c_str());
871 out_streams.clear();
872 access_out_live.clear();
873 return false;
876 /* check the streams we can actually add */
877 for (std::vector<sout_stream_id_sys_t*>::iterator it = out_streams.begin();
878 it != out_streams.end(); )
880 sout_stream_id_sys_t *p_sys_id = *it;
881 p_sys_id->p_sub_id = sout_StreamIdAdd( p_out, &p_sys_id->fmt );
882 if ( p_sys_id->p_sub_id == NULL )
884 msg_Err( p_stream, "can't handle %4.4s stream", (char *)&p_sys_id->fmt.i_codec );
885 es_format_Clean( &p_sys_id->fmt );
886 it = out_streams.erase( it );
888 else
890 if( p_sys_id->fmt.i_cat == VIDEO_ES )
891 has_video = true;
892 else if( p_sys_id->fmt.i_cat == SPU_ES )
893 spu_streams_count++;
894 ++it;
898 if (out_streams.empty())
900 stopSoutChain( p_stream );
901 access_out_live.clear();
902 return false;
905 /* Ask to retry if we are not transcoding everything (because we can trust
906 * what we encode) */
907 p_intf->setRetryOnFail(transcodingCanFallback());
909 return true;
912 void sout_stream_sys_t::setNextTranscodingState()
914 if (!(transcoding_state & TRANSCODING_VIDEO))
915 transcoding_state |= TRANSCODING_VIDEO;
916 else if (!(transcoding_state & TRANSCODING_AUDIO))
917 transcoding_state = TRANSCODING_AUDIO;
920 bool sout_stream_sys_t::transcodingCanFallback() const
922 return transcoding_state != (TRANSCODING_VIDEO|TRANSCODING_AUDIO);
925 static std::string GetVencVPXOption( sout_stream_t * /* p_stream */,
926 const video_format_t * /* p_vid */,
927 int /* i_quality */ )
929 return "venc=vpx{quality-mode=1}";
932 static std::string GetVencX264Option( sout_stream_t * /* p_stream */,
933 const video_format_t *p_vid,
934 int i_quality )
936 std::stringstream ssout;
937 static const char video_x264_preset_veryfast[] = "veryfast";
938 static const char video_x264_preset_ultrafast[] = "ultrafast";
939 const char *psz_video_x264_preset;
940 unsigned i_video_x264_crf_hd, i_video_x264_crf_720p;
942 switch ( i_quality )
944 case CONVERSION_QUALITY_HIGH:
945 i_video_x264_crf_hd = i_video_x264_crf_720p = 21;
946 psz_video_x264_preset = video_x264_preset_veryfast;
947 break;
948 case CONVERSION_QUALITY_MEDIUM:
949 i_video_x264_crf_hd = 23;
950 i_video_x264_crf_720p = 21;
951 psz_video_x264_preset = video_x264_preset_veryfast;
952 break;
953 case CONVERSION_QUALITY_LOW:
954 i_video_x264_crf_hd = i_video_x264_crf_720p = 23;
955 psz_video_x264_preset = video_x264_preset_veryfast;
956 break;
957 default:
958 case CONVERSION_QUALITY_LOWCPU:
959 i_video_x264_crf_hd = i_video_x264_crf_720p = 23;
960 psz_video_x264_preset = video_x264_preset_ultrafast;
961 break;
964 const bool b_hdres = p_vid == NULL || p_vid->i_height == 0 || p_vid->i_height >= 800;
965 unsigned i_video_x264_crf = b_hdres ? i_video_x264_crf_hd : i_video_x264_crf_720p;
967 ssout << "venc=x264{preset=" << psz_video_x264_preset
968 << ",crf=" << i_video_x264_crf << "}";
969 return ssout.str();
973 static struct
975 vlc_fourcc_t fcc;
976 std::string (*get_opt)( sout_stream_t *, const video_format_t *, int);
977 } venc_opt_list[] = {
978 { .fcc = VLC_CODEC_H264, .get_opt = GetVencX264Option },
979 { .fcc = VLC_CODEC_VP8, .get_opt = GetVencVPXOption },
980 { .fcc = VLC_CODEC_H264, .get_opt = NULL },
983 std::string
984 sout_stream_sys_t::GetVencOption( sout_stream_t *p_stream, vlc_fourcc_t *p_codec_video,
985 const video_format_t *p_vid, int i_quality )
987 for( size_t i = (venc_opt_idx == -1 ? 0 : venc_opt_idx);
988 i < ARRAY_SIZE(venc_opt_list); ++i )
990 std::stringstream ssout, ssvenc;
991 char fourcc[5];
992 ssvenc << "vcodec=";
993 vlc_fourcc_to_char( venc_opt_list[i].fcc, fourcc );
994 fourcc[4] = '\0';
995 ssvenc << fourcc << ',';
997 if( venc_opt_list[i].get_opt != NULL )
998 ssvenc << venc_opt_list[i].get_opt( p_stream, p_vid, i_quality ) << ',';
1000 if( venc_opt_list[i].get_opt == NULL
1001 || ( venc_opt_idx != -1 && (unsigned) venc_opt_idx == i) )
1003 venc_opt_idx = i;
1004 *p_codec_video = venc_opt_list[i].fcc;
1005 return ssvenc.str();
1008 /* Test if a module can encode with the specified options / fmt_video. */
1009 ssout << "transcode{" << ssvenc.str() << "}:dummy";
1011 sout_stream_t *p_sout_test =
1012 sout_StreamChainNew( p_stream->p_sout, ssout.str().c_str(), NULL, NULL );
1014 if( p_sout_test != NULL )
1016 p_sout_test->obj.flags |= OBJECT_FLAGS_QUIET|OBJECT_FLAGS_NOINTERACT;
1018 es_format_t fmt;
1019 es_format_InitFromVideo( &fmt, p_vid );
1020 fmt.i_codec = fmt.video.i_chroma = VLC_CODEC_I420;
1021 fmt.video.i_frame_rate = 30;
1022 fmt.video.i_frame_rate_base = 1;
1024 sout_stream_id_sys_t *id = sout_StreamIdAdd( p_sout_test, &fmt );
1026 es_format_Clean( &fmt );
1027 const bool success = id != NULL;
1029 if( id )
1030 sout_StreamIdDel( p_sout_test, id );
1031 sout_StreamChainDelete( p_sout_test, NULL );
1033 if( success )
1035 venc_opt_idx = i;
1036 *p_codec_video = venc_opt_list[i].fcc;
1037 return ssvenc.str();
1041 vlc_assert_unreachable();
1044 std::string
1045 sout_stream_sys_t::GetVcodecOption( sout_stream_t *p_stream, vlc_fourcc_t *p_codec_video,
1046 const video_format_t *p_vid, int i_quality )
1048 std::stringstream ssout;
1049 static const char video_maxres_hd[] = "maxwidth=1920,maxheight=1080";
1050 static const char video_maxres_720p[] = "maxwidth=1280,maxheight=720";
1052 ssout << GetVencOption( p_stream, p_codec_video, p_vid, i_quality );
1054 switch ( i_quality )
1056 case CONVERSION_QUALITY_HIGH:
1057 case CONVERSION_QUALITY_MEDIUM:
1058 ssout << video_maxres_hd << ',';
1059 break;
1060 default:
1061 ssout << video_maxres_720p << ',';
1064 if( p_vid == NULL
1065 || p_vid->i_frame_rate == 0 || p_vid->i_frame_rate_base == 0
1066 || ( p_vid->i_frame_rate / p_vid->i_frame_rate_base ) > 30 )
1068 /* Even force 24fps if the frame rate is unknown */
1069 msg_Warn( p_stream, "lowering frame rate to 24fps" );
1070 ssout << "fps=24,";
1073 msg_Dbg( p_stream, "Converting video to %.4s", (const char*)p_codec_video );
1075 return ssout.str();
1078 std::string
1079 sout_stream_sys_t::GetAcodecOption( sout_stream_t *p_stream, vlc_fourcc_t *p_codec_audio,
1080 const audio_format_t *p_aud, int i_quality )
1082 std::stringstream ssout;
1084 bool b_audio_mp3;
1086 /* If we were already transcoding: force mp3 because maybe the CC may
1087 * have failed because of vorbis. */
1088 if( transcoding_state & TRANSCODING_AUDIO )
1089 b_audio_mp3 = true;
1090 else
1092 switch ( i_quality )
1094 case CONVERSION_QUALITY_HIGH:
1095 case CONVERSION_QUALITY_MEDIUM:
1096 b_audio_mp3 = false;
1097 break;
1098 default:
1099 b_audio_mp3 = true;
1100 break;
1104 if ( !b_audio_mp3
1105 && p_aud->i_channels > 2 && module_exists( "vorbis" ) )
1106 *p_codec_audio = VLC_CODEC_VORBIS;
1107 else
1108 *p_codec_audio = VLC_CODEC_MP3;
1110 msg_Dbg( p_stream, "Converting audio to %.4s", (const char*)p_codec_audio );
1112 ssout << "acodec=";
1113 char fourcc[5];
1114 vlc_fourcc_to_char( *p_codec_audio, fourcc );
1115 fourcc[4] = '\0';
1116 ssout << fourcc << ',';
1118 /* XXX: higher vorbis qualities can cause glitches on some CC
1119 * devices (Chromecast 1 & 2) */
1120 if( *p_codec_audio == VLC_CODEC_VORBIS )
1121 ssout << "aenc=vorbis{quality=4},";
1122 return ssout.str();
1125 bool sout_stream_sys_t::UpdateOutput( sout_stream_t *p_stream )
1127 assert( p_stream->p_sys == this );
1129 if ( !es_changed )
1130 return true;
1132 es_changed = false;
1134 bool canRemux = true;
1135 vlc_fourcc_t i_codec_video = 0, i_codec_audio = 0;
1136 const es_format_t *p_original_audio = NULL;
1137 const es_format_t *p_original_video = NULL;
1138 const es_format_t *p_original_spu = NULL;
1139 bool b_out_streams_changed = false;
1140 std::vector<sout_stream_id_sys_t*> new_streams;
1142 for (std::vector<sout_stream_id_sys_t*>::iterator it = streams.begin(); it != streams.end(); ++it)
1144 const es_format_t *p_es = &(*it)->fmt;
1145 if (p_es->i_cat == AUDIO_ES && p_original_audio == NULL)
1147 if ( !canDecodeAudio( p_stream, p_es->i_codec, &p_es->audio ) )
1149 msg_Dbg( p_stream, "can't remux audio track %d codec %4.4s", p_es->i_id, (const char*)&p_es->i_codec );
1150 canRemux = false;
1152 else if (i_codec_audio == 0)
1153 i_codec_audio = p_es->i_codec;
1154 p_original_audio = p_es;
1155 new_streams.push_back(*it);
1157 else if (b_supports_video)
1159 if (p_es->i_cat == VIDEO_ES && p_original_video == NULL)
1161 if (!canDecodeVideo( p_es->i_codec ))
1163 msg_Dbg( p_stream, "can't remux video track %d codec %4.4s",
1164 p_es->i_id, (const char*)&p_es->i_codec );
1165 canRemux = false;
1167 else if (i_codec_video == 0 && !p_original_spu)
1168 i_codec_video = p_es->i_codec;
1169 p_original_video = p_es;
1170 new_streams.push_back(*it);
1172 #ifdef CC_ENABLE_SPU
1173 else if (p_es->i_cat == SPU_ES && p_original_spu == NULL)
1175 msg_Dbg( p_stream, "forcing video transcode because of subtitle '%4.4s'",
1176 (const char*)&p_es->i_codec );
1177 canRemux = false;
1178 i_codec_video = 0;
1179 p_original_spu = p_es;
1180 new_streams.push_back(*it);
1182 #endif
1183 else
1184 continue;
1186 else
1187 continue;
1189 bool b_found = out_force_reload;
1190 for (std::vector<sout_stream_id_sys_t*>::iterator out_it = out_streams.begin();
1191 out_it != out_streams.end() && !b_found; ++out_it)
1193 if (*out_it == *it)
1194 b_found = true;
1196 if (!b_found)
1197 b_out_streams_changed = true;
1200 if (new_streams.empty())
1202 p_intf->requestPlayerStop();
1203 return true;
1206 /* Don't restart sout and CC session if streams didn't change */
1207 if (!out_force_reload && new_streams.size() == out_streams.size() && !b_out_streams_changed)
1208 return true;
1210 out_force_reload = false;
1212 std::stringstream ssout;
1213 int new_transcoding_state = TRANSCODING_NONE;
1214 if ( !canRemux )
1216 if ( !perf_warning_shown && i_codec_video == 0 && p_original_video
1217 && var_InheritInteger( p_stream, SOUT_CFG_PREFIX "show-perf-warning" ) )
1219 int res = vlc_dialog_wait_question( p_stream,
1220 VLC_DIALOG_QUESTION_WARNING,
1221 _("Cancel"), _("OK"), _("Ok, Don't warn me again"),
1222 _("Performance warning"),
1223 _("Casting this video requires conversion. "
1224 "This conversion can use all the available power and "
1225 "could quickly drain your battery." ) );
1226 if ( res <= 0 )
1227 return false;
1228 perf_warning_shown = true;
1229 if ( res == 2 )
1230 config_PutInt(SOUT_CFG_PREFIX "show-perf-warning", 0 );
1233 const int i_quality = var_InheritInteger( p_stream, SOUT_CFG_PREFIX "conversion-quality" );
1235 /* TODO: provide audio samplerate and channels */
1236 ssout << "transcode{";
1237 if ( i_codec_audio == 0 && p_original_audio )
1239 ssout << GetAcodecOption( p_stream, &i_codec_audio,
1240 &p_original_audio->audio, i_quality );
1241 new_transcoding_state |= TRANSCODING_AUDIO;
1243 if ( i_codec_video == 0 && p_original_video )
1245 ssout << GetVcodecOption( p_stream, &i_codec_video,
1246 &p_original_video->video, i_quality );
1247 new_transcoding_state |= TRANSCODING_VIDEO;
1249 if ( p_original_spu )
1250 ssout << "soverlay,";
1251 ssout << "}:";
1254 const bool is_webm = ( i_codec_audio == 0 || i_codec_audio == VLC_CODEC_VORBIS ||
1255 i_codec_audio == VLC_CODEC_OPUS ) &&
1256 ( i_codec_video == 0 || i_codec_video == VLC_CODEC_VP8 ||
1257 i_codec_video == VLC_CODEC_VP9 );
1259 if ( !p_original_video )
1261 if( is_webm )
1262 mime = "audio/webm";
1263 else
1264 mime = "audio/x-matroska";
1266 else
1268 if ( is_webm )
1269 mime = "video/webm";
1270 else
1271 mime = "video/x-matroska";
1274 ssout << "chromecast-proxy:"
1275 << "std{mux=" << ( is_webm ? DEFAULT_MUXER_WEBM : DEFAULT_MUXER )
1276 << ",access=chromecast-http}";
1278 if ( !startSoutChain( p_stream, new_streams, ssout.str(),
1279 new_transcoding_state ) )
1280 p_intf->requestPlayerStop();
1281 return true;
1284 sout_stream_id_sys_t *sout_stream_sys_t::GetSubId( sout_stream_t *p_stream,
1285 sout_stream_id_sys_t *id,
1286 bool update )
1288 size_t i;
1290 assert( p_stream->p_sys == this );
1292 if ( update && UpdateOutput( p_stream ) == false )
1293 return NULL;
1295 for (i = 0; i < out_streams.size(); ++i)
1297 if ( id == (sout_stream_id_sys_t*) out_streams[i] )
1298 return out_streams[i]->p_sub_id;
1301 return NULL;
1304 bool sout_stream_sys_t::isFlushing( sout_stream_t *p_stream )
1306 (void) p_stream;
1308 /* Make sure that all out_streams are flushed when flushing. This avoids
1309 * too many sout/cc restart when a stream is sending data while one other
1310 * is flushing */
1312 if (!cc_flushing)
1313 return false;
1315 for (size_t i = 0; i < out_streams.size(); ++i)
1317 if ( !out_streams[i]->flushed )
1318 return true;
1321 cc_flushing = false;
1322 for (size_t i = 0; i < out_streams.size(); ++i)
1323 out_streams[i]->flushed = false;
1325 return false;
1328 static int Send(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
1329 block_t *p_buffer)
1331 sout_stream_sys_t *p_sys = p_stream->p_sys;
1332 vlc_mutex_locker locker(&p_sys->lock);
1334 if( p_sys->isFlushing( p_stream ) )
1336 block_Release( p_buffer );
1337 return VLC_SUCCESS;
1340 sout_stream_id_sys_t *next_id = p_sys->GetSubId( p_stream, id );
1341 if ( next_id == NULL )
1343 block_Release( p_buffer );
1344 return VLC_EGENERIC;
1347 int ret = sout_StreamIdSend(p_sys->p_out, next_id, p_buffer);
1348 if (ret != VLC_SUCCESS)
1349 DelInternal(p_stream, id, false);
1351 return ret;
1354 static void Flush( sout_stream_t *p_stream, sout_stream_id_sys_t *id )
1356 sout_stream_sys_t *p_sys = p_stream->p_sys;
1357 vlc_mutex_locker locker(&p_sys->lock);
1359 sout_stream_id_sys_t *next_id = p_sys->GetSubId( p_stream, id, false );
1360 if ( next_id == NULL )
1361 return;
1362 next_id->flushed = true;
1364 if( !p_sys->cc_flushing )
1366 p_sys->cc_flushing = true;
1368 p_sys->stopSoutChain( p_stream );
1370 p_sys->access_out_live.stop();
1372 if (p_sys->cc_has_input)
1374 p_sys->p_intf->requestPlayerStop();
1375 p_sys->cc_has_input = false;
1377 p_sys->out_force_reload = p_sys->es_changed = true;
1381 static void on_input_event_cb(void *data, enum cc_input_event event, union cc_input_arg arg )
1383 sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(data);
1384 sout_stream_sys_t *p_sys = p_stream->p_sys;
1386 vlc_mutex_locker locker(&p_sys->lock);
1387 switch (event)
1389 case CC_INPUT_EVENT_EOF:
1390 /* In case of EOF: stop the sout chain in order to drain all
1391 * sout/demuxers/access. If EOF changes to false, reset es_changed
1392 * in order to reload the sout from next Send calls. */
1393 if( arg.eof )
1394 p_sys->stopSoutChain( p_stream );
1395 else
1396 p_sys->out_force_reload = p_sys->es_changed = true;
1397 break;
1398 case CC_INPUT_EVENT_RETRY:
1399 p_sys->stopSoutChain( p_stream );
1400 if( p_sys->transcodingCanFallback() )
1402 p_sys->setNextTranscodingState();
1403 msg_Warn(p_stream, "Load failed detected. Switching to next "
1404 "configuration. Transcoding video%s",
1405 p_sys->transcoding_state & TRANSCODING_AUDIO ? "/audio" : "");
1406 p_sys->out_force_reload = p_sys->es_changed = true;
1408 break;
1412 /*****************************************************************************
1413 * Open: connect to the Chromecast and initialize the sout
1414 *****************************************************************************/
1415 static int Open(vlc_object_t *p_this)
1417 sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
1418 sout_stream_sys_t *p_sys = NULL;
1419 intf_sys_t *p_intf = NULL;
1420 char *psz_ip = NULL;
1421 sout_stream_t *p_sout = NULL;
1422 httpd_host_t *httpd_host = NULL;
1423 bool b_supports_video = true;
1424 int i_local_server_port;
1425 int i_device_port;
1426 std::stringstream ss;
1428 config_ChainParse(p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg);
1430 psz_ip = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "ip");
1431 if ( psz_ip == NULL )
1433 msg_Err( p_this, "missing Chromecast IP address" );
1434 goto error;
1437 i_device_port = var_InheritInteger(p_stream, SOUT_CFG_PREFIX "port");
1438 i_local_server_port = var_InheritInteger(p_stream, SOUT_CFG_PREFIX "http-port");
1440 var_Create(p_stream, "http-port", VLC_VAR_INTEGER);
1441 var_SetInteger(p_stream, "http-port", i_local_server_port);
1442 var_Create(p_stream, "http-host", VLC_VAR_STRING);
1443 var_SetString(p_stream, "http-host", "");
1444 httpd_host = vlc_http_HostNew(VLC_OBJECT(p_stream));
1445 if (httpd_host == NULL)
1446 goto error;
1450 p_intf = new intf_sys_t( p_this, i_local_server_port, psz_ip, i_device_port,
1451 httpd_host );
1453 catch (const std::runtime_error& err )
1455 msg_Err( p_this, "cannot load the Chromecast controller (%s)", err.what() );
1456 goto error;
1458 catch (const std::bad_alloc& )
1460 p_intf = NULL;
1461 goto error;
1464 /* check if we can open the proper sout */
1465 ss << "http{mux=" << DEFAULT_MUXER << "}";
1466 p_sout = sout_StreamChainNew( p_stream->p_sout, ss.str().c_str(), NULL, NULL);
1467 if (p_sout == NULL) {
1468 msg_Dbg(p_stream, "could not create sout chain:%s", ss.str().c_str());
1469 goto error;
1471 sout_StreamChainDelete( p_sout, NULL );
1473 b_supports_video = var_GetBool(p_stream, SOUT_CFG_PREFIX "video");
1477 p_sys = new sout_stream_sys_t( httpd_host, p_intf, b_supports_video,
1478 i_local_server_port );
1480 catch ( std::exception& ex )
1482 msg_Err( p_stream, "Failed to instantiate sout_stream_sys_t: %s", ex.what() );
1483 p_sys = NULL;
1484 goto error;
1487 p_intf->setOnInputEventCb(on_input_event_cb, p_stream);
1489 /* prevent sout-mux-caching since chromecast-proxy is already doing it */
1490 var_Create( p_stream->p_sout, "sout-mux-caching", VLC_VAR_INTEGER );
1491 var_SetInteger( p_stream->p_sout, "sout-mux-caching", 0 );
1493 var_Create( p_stream->p_sout, SOUT_CFG_PREFIX "sys", VLC_VAR_ADDRESS );
1494 var_SetAddress( p_stream->p_sout, SOUT_CFG_PREFIX "sys", p_sys );
1496 var_Create( p_stream->p_sout, SOUT_CFG_PREFIX "access-out-sys", VLC_VAR_ADDRESS );
1498 // Set the sout callbacks.
1499 p_stream->pf_add = Add;
1500 p_stream->pf_del = Del;
1501 p_stream->pf_send = Send;
1502 p_stream->pf_flush = Flush;
1504 p_stream->p_sys = p_sys;
1506 free(psz_ip);
1508 return VLC_SUCCESS;
1510 error:
1511 delete p_intf;
1512 if (httpd_host)
1513 httpd_HostDelete(httpd_host);
1514 free(psz_ip);
1515 delete p_sys;
1516 return VLC_EGENERIC;
1519 /*****************************************************************************
1520 * Close: destroy interface
1521 *****************************************************************************/
1522 static void Close(vlc_object_t *p_this)
1524 sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
1525 sout_stream_sys_t *p_sys = p_stream->p_sys;
1527 assert(p_sys->out_streams.empty() && p_sys->streams.empty());
1528 var_Destroy( p_stream->p_sout, SOUT_CFG_PREFIX "sys" );
1529 var_Destroy( p_stream->p_sout, SOUT_CFG_PREFIX "sout-mux-caching" );
1531 assert(p_sys->streams.empty() && p_sys->out_streams.empty());
1533 httpd_host_t *httpd_host = p_sys->httpd_host;
1534 delete p_sys->p_intf;
1535 delete p_sys;
1536 /* Delete last since p_intf and p_sys depends on httpd_host */
1537 httpd_HostDelete(httpd_host);