chromecast: show perfs dialog only one time (if accepted)
[vlc.git] / modules / stream_out / chromecast / cast.cpp
blob695c3cc8f81d5e3ce168df161474efda0920076e
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;
145 private:
146 bool UpdateOutput( sout_stream_t * );
149 struct sout_stream_id_sys_t
151 es_format_t fmt;
152 sout_stream_id_sys_t *p_sub_id;
155 #define SOUT_CFG_PREFIX "sout-chromecast-"
157 static const vlc_fourcc_t DEFAULT_TRANSCODE_VIDEO = VLC_CODEC_H264;
158 static const char DEFAULT_MUXER[] = "avformat{mux=matroska,options={live=1}}";
161 /*****************************************************************************
162 * Local prototypes
163 *****************************************************************************/
164 static int Open(vlc_object_t *);
165 static void Close(vlc_object_t *);
166 static int ProxyOpen(vlc_object_t *);
167 static int AccessOpen(vlc_object_t *);
168 static void AccessClose(vlc_object_t *);
170 static const char *const ppsz_sout_options[] = {
171 "ip", "port", "http-port", "video", NULL
174 /*****************************************************************************
175 * Module descriptor
176 *****************************************************************************/
178 #define HTTP_PORT_TEXT N_("HTTP port")
179 #define HTTP_PORT_LONGTEXT N_("This sets the HTTP port of the local server " \
180 "used to stream the media to the Chromecast.")
181 #define PERF_TEXT N_( "Performance warning" )
182 #define PERF_LONGTEXT N_( "Display a performance warning when transcoding" )
183 #define AUDIO_PASSTHROUGH_TEXT N_( "Enable Audio passthrough" )
184 #define AUDIO_PASSTHROUGH_LONGTEXT N_( "Disable if your receiver does not support Dolby®." )
186 enum {
187 CONVERSION_QUALITY_HIGH = 0,
188 CONVERSION_QUALITY_MEDIUM = 1,
189 CONVERSION_QUALITY_LOW = 2,
190 CONVERSION_QUALITY_LOWCPU = 3,
193 #if defined (__ANDROID__) || defined (__arm__) || (defined (TARGET_OS_IPHONE) && TARGET_OS_IPHONE)
194 # define CONVERSION_QUALITY_DEFAULT CONVERSION_QUALITY_LOW
195 #else
196 # define CONVERSION_QUALITY_DEFAULT CONVERSION_QUALITY_MEDIUM
197 #endif
199 static const int conversion_quality_list[] = {
200 CONVERSION_QUALITY_HIGH,
201 CONVERSION_QUALITY_MEDIUM,
202 CONVERSION_QUALITY_LOW,
203 CONVERSION_QUALITY_LOWCPU,
205 static const char *const conversion_quality_list_text[] = {
206 N_( "High (high quality and high bandwidth)" ),
207 N_( "Medium (medium quality and medium bandwidth)" ),
208 N_( "Low (low quality and low bandwidth)" ),
209 N_( "Low CPU (low quality but high bandwidth)" ),
212 #define CONVERSION_QUALITY_TEXT N_( "Conversion quality" )
213 #define CONVERSION_QUALITY_LONGTEXT N_( "Change this option to increase conversion speed or quality." )
215 #define IP_ADDR_TEXT N_("IP Address")
216 #define IP_ADDR_LONGTEXT N_("IP Address of the Chromecast.")
217 #define PORT_TEXT N_("Chromecast port")
218 #define PORT_LONGTEXT N_("The port used to talk to the Chromecast.")
220 /* Fifo size after we tell the demux to pace */
221 #define HTTPD_BUFFER_PACE INT64_C(2 * 1024 * 1024) /* 2 MB */
222 /* Fifo size after we drop packets (should not happen) */
223 #define HTTPD_BUFFER_MAX INT64_C(32 * 1024 * 1024) /* 32 MB */
224 #define HTTPD_BUFFER_COPY_MAX INT64_C(10 * 1024 * 1024) /* 10 MB */
226 vlc_module_begin ()
228 set_shortname(N_("Chromecast"))
229 set_description(N_("Chromecast stream output"))
230 set_capability("sout stream", 0)
231 add_shortcut("chromecast")
232 set_category(CAT_SOUT)
233 set_subcategory(SUBCAT_SOUT_STREAM)
234 set_callbacks(Open, Close)
236 add_string(SOUT_CFG_PREFIX "ip", NULL, NULL, NULL, false)
237 change_private()
238 add_integer(SOUT_CFG_PREFIX "port", CHROMECAST_CONTROL_PORT, NULL, NULL, false)
239 change_private()
240 add_bool(SOUT_CFG_PREFIX "video", true, NULL, NULL, false)
241 change_private()
242 add_integer(SOUT_CFG_PREFIX "http-port", HTTP_PORT, HTTP_PORT_TEXT, HTTP_PORT_LONGTEXT, false)
243 add_obsolete_string(SOUT_CFG_PREFIX "mux")
244 add_obsolete_string(SOUT_CFG_PREFIX "mime")
245 add_integer(SOUT_CFG_PREFIX "show-perf-warning", 1, PERF_TEXT, PERF_LONGTEXT, true )
246 change_private()
247 add_bool(SOUT_CFG_PREFIX "audio-passthrough", false, AUDIO_PASSTHROUGH_TEXT, AUDIO_PASSTHROUGH_LONGTEXT, false )
248 add_integer(SOUT_CFG_PREFIX "conversion-quality", CONVERSION_QUALITY_DEFAULT,
249 CONVERSION_QUALITY_TEXT, CONVERSION_QUALITY_LONGTEXT, false );
250 change_integer_list(conversion_quality_list, conversion_quality_list_text)
252 add_submodule()
253 /* sout proxy that start the cc input when all streams are loaded */
254 add_shortcut("chromecast-proxy")
255 set_capability("sout stream", 0)
256 set_callbacks(ProxyOpen, NULL)
257 add_submodule()
258 set_subcategory(SUBCAT_SOUT_ACO)
259 add_shortcut("chromecast-http")
260 set_capability("sout access", 0)
261 set_callbacks(AccessOpen, AccessClose)
262 vlc_module_end ()
264 static sout_stream_id_sys_t *ProxyAdd(sout_stream_t *p_stream, const es_format_t *p_fmt)
266 sout_stream_sys_t *p_sys = p_stream->p_sys;
267 sout_stream_id_sys_t *id = sout_StreamIdAdd(p_stream->p_next, p_fmt);
268 if (id)
270 if (p_fmt->i_cat == VIDEO_ES)
271 p_sys->video_proxy_id = id;
272 p_sys->out_streams_added++;
274 return id;
277 static void ProxyDel(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
279 sout_stream_sys_t *p_sys = p_stream->p_sys;
280 p_sys->out_streams_added--;
281 if (id == p_sys->video_proxy_id)
282 p_sys->video_proxy_id = NULL;
283 return sout_StreamIdDel(p_stream->p_next, id);
286 static int ProxySend(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
287 block_t *p_buffer)
289 sout_stream_sys_t *p_sys = p_stream->p_sys;
290 if (p_sys->cc_has_input || p_sys->out_streams_added >= p_sys->out_streams.size())
292 if (p_sys->has_video)
294 // In case of video, the first block must be a keyframe
295 if (id == p_sys->video_proxy_id)
297 if (p_sys->first_video_keyframe_pts == -1
298 && p_buffer->i_flags & BLOCK_FLAG_TYPE_I)
299 p_sys->first_video_keyframe_pts = p_buffer->i_pts;
301 else // no keyframe for audio
302 p_buffer->i_flags &= ~BLOCK_FLAG_TYPE_I;
304 if (p_buffer->i_pts < p_sys->first_video_keyframe_pts
305 || p_sys->first_video_keyframe_pts == -1)
307 block_Release(p_buffer);
308 return VLC_SUCCESS;
312 mtime_t pause_delay = p_sys->p_intf->getPauseDelay();
313 if( p_buffer->i_pts != VLC_TS_INVALID )
314 p_buffer->i_pts -= pause_delay;
315 if( p_buffer->i_dts != VLC_TS_INVALID )
316 p_buffer->i_dts -= pause_delay;
318 int ret = sout_StreamIdSend(p_stream->p_next, id, p_buffer);
319 if (ret == VLC_SUCCESS && !p_sys->cc_has_input)
321 /* Start the chromecast only when all streams are added into the
322 * last sout (the http one) */
323 p_sys->p_intf->setHasInput(p_sys->mime);
324 p_sys->cc_has_input = true;
326 return ret;
328 else
330 block_Release(p_buffer);
331 return VLC_SUCCESS;
335 static void ProxyFlush(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
337 sout_StreamFlush(p_stream->p_next, id);
340 static int ProxyOpen(vlc_object_t *p_this)
342 sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
343 sout_stream_sys_t *p_sys = (sout_stream_sys_t *) var_InheritAddress(p_this, SOUT_CFG_PREFIX "sys");
344 if (p_sys == NULL || p_stream->p_next == NULL)
345 return VLC_EGENERIC;
347 p_stream->p_sys = (sout_stream_sys_t *) p_sys;
348 p_sys->out_streams_added = 0;
350 p_stream->pf_add = ProxyAdd;
351 p_stream->pf_del = ProxyDel;
352 p_stream->pf_send = ProxySend;
353 p_stream->pf_flush = ProxyFlush;
354 return VLC_SUCCESS;
357 static int httpd_url_cb(httpd_callback_sys_t *data, httpd_client_t *cl,
358 httpd_message_t *answer, const httpd_message_t *query)
360 sout_access_out_sys_t *p_sys = reinterpret_cast<sout_access_out_sys_t *>(data);
361 return p_sys->url_cb(cl, answer, query);
364 sout_access_out_sys_t::sout_access_out_sys_t(httpd_host_t *httpd_host,
365 intf_sys_t * const intf,
366 const char *psz_url)
367 : m_intf(intf)
368 , m_client(NULL)
369 , m_header(NULL)
370 , m_copy_chain(NULL)
371 , m_eof(true)
373 m_fifo = block_FifoNew();
374 if (!m_fifo)
375 throw std::runtime_error( "block_FifoNew failed" );
376 m_url = httpd_UrlNew(httpd_host, psz_url, NULL, NULL);
377 if (m_url == NULL)
379 block_FifoRelease(m_fifo);
380 throw std::runtime_error( "httpd_UrlNew failed" );
382 httpd_UrlCatch(m_url, HTTPD_MSG_GET, httpd_url_cb,
383 (httpd_callback_sys_t*)this);
384 initCopy();
387 sout_access_out_sys_t::~sout_access_out_sys_t()
389 httpd_UrlDelete(m_url);
390 block_FifoRelease(m_fifo);
393 void sout_access_out_sys_t::clearUnlocked()
395 block_ChainRelease(vlc_fifo_DequeueAllUnlocked(m_fifo));
396 if (m_header)
398 block_Release(m_header);
399 m_header = NULL;
401 m_eof = true;
402 initCopy();
405 void sout_access_out_sys_t::initCopy()
407 block_ChainRelease(m_copy_chain);
408 m_copy_chain = NULL;
409 m_copy_last = &m_copy_chain;
410 m_copy_size = 0;
413 void sout_access_out_sys_t::putCopy(block_t *p_block)
415 while (m_copy_size >= HTTPD_BUFFER_COPY_MAX)
417 assert(m_copy_chain);
418 block_t *copy = m_copy_chain;
419 m_copy_chain = copy->p_next;
420 m_copy_size -= copy->i_buffer;
421 block_Release(copy);
423 if (!m_copy_chain)
425 assert(m_copy_size == 0);
426 m_copy_last = &m_copy_chain;
428 block_ChainLastAppend(&m_copy_last, p_block);
429 m_copy_size += p_block->i_buffer;
432 void sout_access_out_sys_t::restoreCopy()
434 if (m_copy_chain)
436 fifo_put_back(m_copy_chain);
437 m_copy_chain = NULL;
438 initCopy();
442 void sout_access_out_sys_t::clear()
444 vlc_fifo_Lock(m_fifo);
445 clearUnlocked();
446 vlc_fifo_Unlock(m_fifo);
447 vlc_fifo_Signal(m_fifo);
450 void sout_access_out_sys_t::stop()
452 vlc_fifo_Lock(m_fifo);
453 clearUnlocked();
454 m_intf->setPacing(false);
455 m_client = NULL;
456 vlc_fifo_Unlock(m_fifo);
457 vlc_fifo_Signal(m_fifo);
460 void sout_access_out_sys_t::prepare(sout_stream_t *p_stream, const std::string &mime)
462 var_SetAddress(p_stream->p_sout, SOUT_CFG_PREFIX "access-out-sys", this);
464 vlc_fifo_Lock(m_fifo);
465 clearUnlocked();
466 m_intf->setPacing(false);
467 m_mime = mime;
468 m_eof = false;
469 vlc_fifo_Unlock(m_fifo);
472 void sout_access_out_sys_t::fifo_put_back(block_t *p_block)
474 block_t *p_fifo = vlc_fifo_DequeueAllUnlocked(m_fifo);
475 vlc_fifo_QueueUnlocked(m_fifo, p_block);
476 vlc_fifo_QueueUnlocked(m_fifo, p_fifo);
479 int sout_access_out_sys_t::url_cb(httpd_client_t *cl, httpd_message_t *answer,
480 const httpd_message_t *query)
482 if (!answer || !query || !cl)
483 return VLC_SUCCESS;
485 vlc_fifo_Lock(m_fifo);
487 if (!answer->i_body_offset)
489 /* When doing a lot a load requests, we can serve data to a client that
490 * will be closed (the close request is already sent). In that case, we
491 * should also serve data used by the old client to the new one. */
492 restoreCopy();
493 m_client = cl;
496 /* Send data per 512kB minimum */
497 size_t i_min_buffer = 524288;
498 while (m_client && vlc_fifo_GetBytes(m_fifo) < i_min_buffer && !m_eof)
499 vlc_fifo_Wait(m_fifo);
501 block_t *p_block = NULL;
502 if (m_client && vlc_fifo_GetBytes(m_fifo) > 0)
504 /* if less data is available, then we must be EOF */
505 if (vlc_fifo_GetBytes(m_fifo) < i_min_buffer)
507 assert(m_eof);
508 i_min_buffer = vlc_fifo_GetBytes(m_fifo);
510 block_t *p_first = vlc_fifo_DequeueUnlocked(m_fifo);
512 assert(p_first);
513 size_t i_total_size = p_first->i_buffer;
514 block_t *p_next = NULL, *p_cur = p_first;
516 while (i_total_size < i_min_buffer)
518 p_next = vlc_fifo_DequeueUnlocked(m_fifo);
519 assert(p_next);
520 i_total_size += p_next->i_buffer;
521 p_cur->p_next = p_next;
522 p_cur = p_cur->p_next;
524 assert(i_total_size >= i_min_buffer);
526 if (p_next != NULL)
528 p_block = block_Alloc(i_total_size);
529 if (p_block)
530 block_ChainExtract(p_first, p_block->p_buffer, p_block->i_buffer);
531 block_ChainRelease(p_first);
533 else
534 p_block = p_first;
536 if (vlc_fifo_GetBytes(m_fifo) < HTTPD_BUFFER_PACE)
537 m_intf->setPacing(false);
540 answer->i_proto = HTTPD_PROTO_HTTP;
541 answer->i_version= 0;
542 answer->i_type = HTTPD_MSG_ANSWER;
543 answer->i_status = 200;
545 if (p_block)
547 if (answer->i_body_offset == 0)
549 httpd_MsgAdd(answer, "Content-type", "%s", m_mime.c_str());
550 httpd_MsgAdd(answer, "Cache-Control", "no-cache");
551 httpd_MsgAdd(answer, "Connection", "close");
554 const bool send_header = answer->i_body_offset == 0 && m_header != NULL;
555 size_t i_answer_size = p_block->i_buffer;
556 if (send_header)
557 i_answer_size += m_header->i_buffer;
559 answer->p_body = (uint8_t *) malloc(i_answer_size);
560 if (answer->p_body)
562 answer->i_body = i_answer_size;
563 answer->i_body_offset += answer->i_body;
564 size_t i_block_offset = 0;
565 if (send_header)
567 memcpy(answer->p_body, m_header->p_buffer, m_header->i_buffer);
568 i_block_offset = m_header->i_buffer;
570 memcpy(&answer->p_body[i_block_offset], p_block->p_buffer, p_block->i_buffer);
573 putCopy(p_block);
575 if (!answer->i_body)
576 httpd_MsgAdd(answer, "Connection", "close");
578 vlc_fifo_Unlock(m_fifo);
579 return VLC_SUCCESS;
582 ssize_t sout_access_out_sys_t::write(sout_access_out_t *p_access, block_t *p_block)
584 size_t i_len = p_block->i_buffer;
586 vlc_fifo_Lock(m_fifo);
588 if (p_block->i_flags & BLOCK_FLAG_HEADER)
590 if (m_header)
591 block_Release(m_header);
592 m_header = p_block;
594 else
596 /* Drop buffer is the fifo is really full */
597 if (vlc_fifo_GetBytes(m_fifo) >= HTTPD_BUFFER_PACE)
599 /* XXX: Hackisk way to pace between the sout (controlled by the
600 * decoder thread) and the demux filter (controlled by the input
601 * thread). When the httpd FIFO reaches a specific size, we tell
602 * the demux filter to pace and wait a little before queing this
603 * block, but not too long since we don't want to block decoder
604 * thread controls. If the pacing fails (should not happen), we
605 * drop the first block in order to make room. The demux filter
606 * will be unpaced when the data is read from the httpd thread. */
608 m_intf->setPacing(true);
610 while (vlc_fifo_GetBytes(m_fifo) >= HTTPD_BUFFER_MAX)
612 block_t *p_drop = vlc_fifo_DequeueUnlocked(m_fifo);
613 msg_Warn(p_access, "httpd buffer full: dropping %zuB", p_drop->i_buffer);
614 block_Release(p_drop);
617 vlc_fifo_QueueUnlocked(m_fifo, p_block);
620 m_eof = false;
622 vlc_fifo_Unlock(m_fifo);
623 vlc_fifo_Signal(m_fifo);
625 return i_len;
628 void sout_access_out_sys_t::close()
630 vlc_fifo_Lock(m_fifo);
631 m_eof = true;
632 m_intf->setPacing(false);
633 vlc_fifo_Unlock(m_fifo);
634 vlc_fifo_Signal(m_fifo);
637 ssize_t AccessWrite(sout_access_out_t *p_access, block_t *p_block)
639 sout_access_out_sys_t *p_sys = p_access->p_sys;
640 return p_sys->write(p_access, p_block);
643 static int AccessControl(sout_access_out_t *p_access, int i_query, va_list args)
645 (void) p_access;
647 switch (i_query)
649 case ACCESS_OUT_CONTROLS_PACE:
650 *va_arg(args, bool *) = true;
651 break;
652 default:
653 return VLC_EGENERIC;
655 return VLC_SUCCESS;
658 static int AccessOpen(vlc_object_t *p_this)
660 sout_access_out_t *p_access = (sout_access_out_t*)p_this;
662 sout_access_out_sys_t *p_sys = (sout_access_out_sys_t *)
663 var_InheritAddress(p_access, SOUT_CFG_PREFIX "access-out-sys");
664 if (p_sys == NULL)
665 return VLC_EGENERIC;
667 p_access->pf_write = AccessWrite;
668 p_access->pf_control = AccessControl;
669 p_access->p_sys = p_sys;
671 return VLC_SUCCESS;
674 static void AccessClose(vlc_object_t *p_this)
676 sout_access_out_t *p_access = (sout_access_out_t*)p_this;
677 sout_access_out_sys_t *p_sys = p_access->p_sys;
679 p_sys->close();
682 /*****************************************************************************
683 * Sout callbacks
684 *****************************************************************************/
685 static sout_stream_id_sys_t *Add(sout_stream_t *p_stream, const es_format_t *p_fmt)
687 sout_stream_sys_t *p_sys = p_stream->p_sys;
688 vlc_mutex_locker locker(&p_sys->lock);
690 if (!p_sys->b_supports_video)
692 if (p_fmt->i_cat != AUDIO_ES)
693 return NULL;
696 sout_stream_id_sys_t *p_sys_id = (sout_stream_id_sys_t *)malloc( sizeof(sout_stream_id_sys_t) );
697 if (p_sys_id != NULL)
699 es_format_Copy( &p_sys_id->fmt, p_fmt );
700 p_sys_id->p_sub_id = NULL;
702 p_sys->streams.push_back( p_sys_id );
703 p_sys->es_changed = true;
705 return p_sys_id;
709 static void DelInternal(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
710 bool reset_config)
712 sout_stream_sys_t *p_sys = p_stream->p_sys;
714 for (std::vector<sout_stream_id_sys_t*>::iterator it = p_sys->streams.begin();
715 it != p_sys->streams.end(); )
717 sout_stream_id_sys_t *p_sys_id = *it;
718 if ( p_sys_id == id )
720 if ( p_sys_id->p_sub_id != NULL )
722 sout_StreamIdDel( p_sys->p_out, p_sys_id->p_sub_id );
723 for (std::vector<sout_stream_id_sys_t*>::iterator out_it = p_sys->out_streams.begin();
724 out_it != p_sys->out_streams.end(); )
726 if (*out_it == id)
728 p_sys->out_streams.erase(out_it);
729 p_sys->es_changed = reset_config;
730 p_sys->out_force_reload = reset_config;
731 if( p_sys_id->fmt.i_cat == VIDEO_ES )
732 p_sys->has_video = false;
733 break;
735 out_it++;
739 es_format_Clean( &p_sys_id->fmt );
740 free( p_sys_id );
741 p_sys->streams.erase( it );
742 break;
744 it++;
747 if ( p_sys->out_streams.empty() )
749 p_sys->stopSoutChain(p_stream);
750 p_sys->p_intf->requestPlayerStop();
751 p_sys->access_out_live.clear();
752 p_sys->transcoding_state = TRANSCODING_NONE;
756 static void Del(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
758 sout_stream_sys_t *p_sys = p_stream->p_sys;
760 vlc_mutex_locker locker(&p_sys->lock);
761 DelInternal(p_stream, id, true);
765 * Transcode steps:
766 * 0: Accept HEVC/VP9 & all supported audio formats
767 * 1: Transcode to h264 & accept all supported audio formats if the video codec
768 * was HEVC/VP9
769 * 2: Transcode to H264 & MP3
771 * Additionally:
772 * - Allow (E)AC3 passthrough depending on the audio-passthrough
773 * config value, except for the final step, where we just give up and transcode
774 * everything.
775 * - Disallow multichannel AAC
777 * Supported formats: https://developers.google.com/cast/docs/media
780 bool sout_stream_sys_t::canDecodeVideo( vlc_fourcc_t i_codec ) const
782 if( transcoding_state & TRANSCODING_VIDEO )
783 return false;
784 return i_codec == VLC_CODEC_H264 || i_codec == VLC_CODEC_HEVC
785 || i_codec == VLC_CODEC_VP8 || i_codec == VLC_CODEC_VP9;
788 bool sout_stream_sys_t::canDecodeAudio( sout_stream_t *p_stream,
789 vlc_fourcc_t i_codec,
790 const audio_format_t* p_fmt ) const
792 if( transcoding_state & TRANSCODING_AUDIO )
793 return false;
794 if ( i_codec == VLC_CODEC_A52 || i_codec == VLC_CODEC_EAC3 )
796 return var_InheritBool( p_stream, SOUT_CFG_PREFIX "audio-passthrough" );
798 if ( i_codec == VLC_FOURCC('h', 'a', 'a', 'c') ||
799 i_codec == VLC_FOURCC('l', 'a', 'a', 'c') ||
800 i_codec == VLC_FOURCC('s', 'a', 'a', 'c') ||
801 i_codec == VLC_CODEC_MPGA ||
802 i_codec == VLC_CODEC_MP4A )
804 return p_fmt->i_channels <= 2;
806 return i_codec == VLC_CODEC_VORBIS || i_codec == VLC_CODEC_OPUS ||
807 i_codec == VLC_CODEC_MP3;
810 void sout_stream_sys_t::stopSoutChain(sout_stream_t *p_stream)
812 (void) p_stream;
814 if ( unlikely( p_out != NULL ) )
816 for ( size_t i = 0; i < out_streams.size(); i++ )
818 if ( out_streams[i]->p_sub_id != NULL )
820 sout_StreamIdDel( p_out, out_streams[i]->p_sub_id );
821 out_streams[i]->p_sub_id = NULL;
824 out_streams.clear();
825 sout_StreamChainDelete( p_out, NULL );
826 p_out = NULL;
830 bool sout_stream_sys_t::startSoutChain(sout_stream_t *p_stream,
831 const std::vector<sout_stream_id_sys_t*> &new_streams,
832 const std::string &sout, int new_transcoding_state)
834 stopSoutChain( p_stream );
836 msg_Dbg( p_stream, "Creating chain %s", sout.c_str() );
837 cc_has_input = false;
838 cc_reload = false;
839 first_video_keyframe_pts = -1;
840 video_proxy_id = NULL;
841 has_video = false;
842 out_streams = new_streams;
843 transcoding_state = new_transcoding_state;
845 access_out_live.prepare( p_stream, mime );
847 p_out = sout_StreamChainNew( p_stream->p_sout, sout.c_str(), NULL, NULL);
848 if (p_out == NULL) {
849 msg_Dbg(p_stream, "could not create sout chain:%s", sout.c_str());
850 out_streams.clear();
851 access_out_live.clear();
852 return false;
855 /* check the streams we can actually add */
856 for (std::vector<sout_stream_id_sys_t*>::iterator it = out_streams.begin();
857 it != out_streams.end(); )
859 sout_stream_id_sys_t *p_sys_id = *it;
860 p_sys_id->p_sub_id = sout_StreamIdAdd( p_out, &p_sys_id->fmt );
861 if ( p_sys_id->p_sub_id == NULL )
863 msg_Err( p_stream, "can't handle %4.4s stream", (char *)&p_sys_id->fmt.i_codec );
864 es_format_Clean( &p_sys_id->fmt );
865 it = out_streams.erase( it );
867 else
869 if( p_sys_id->fmt.i_cat == VIDEO_ES )
870 has_video = true;
871 ++it;
875 if (out_streams.empty())
877 stopSoutChain( p_stream );
878 access_out_live.clear();
879 return false;
882 /* Ask to retry if we are not transcoding everything (because we can trust
883 * what we encode) */
884 p_intf->setRetryOnFail(transcodingCanFallback());
886 return true;
889 void sout_stream_sys_t::setNextTranscodingState()
891 if (!(transcoding_state & TRANSCODING_VIDEO))
892 transcoding_state |= TRANSCODING_VIDEO;
893 else if (!(transcoding_state & TRANSCODING_AUDIO))
894 transcoding_state = TRANSCODING_AUDIO;
897 bool sout_stream_sys_t::transcodingCanFallback() const
899 return transcoding_state != (TRANSCODING_VIDEO|TRANSCODING_AUDIO);
902 bool sout_stream_sys_t::UpdateOutput( sout_stream_t *p_stream )
904 assert( p_stream->p_sys == this );
906 if ( !es_changed )
907 return true;
909 es_changed = false;
911 bool canRemux = true;
912 vlc_fourcc_t i_codec_video = 0, i_codec_audio = 0;
913 const es_format_t *p_original_audio = NULL;
914 const es_format_t *p_original_video = NULL;
915 bool b_out_streams_changed = false;
916 std::vector<sout_stream_id_sys_t*> new_streams;
918 for (std::vector<sout_stream_id_sys_t*>::iterator it = streams.begin(); it != streams.end(); ++it)
920 const es_format_t *p_es = &(*it)->fmt;
921 if (p_es->i_cat == AUDIO_ES && p_original_audio == NULL)
923 if ( !canDecodeAudio( p_stream, p_es->i_codec, &p_es->audio ) )
925 msg_Dbg( p_stream, "can't remux audio track %d codec %4.4s", p_es->i_id, (const char*)&p_es->i_codec );
926 canRemux = false;
928 else if (i_codec_audio == 0)
929 i_codec_audio = p_es->i_codec;
930 p_original_audio = p_es;
931 new_streams.push_back(*it);
933 else if (b_supports_video)
935 if (p_es->i_cat == VIDEO_ES && p_original_video == NULL)
937 if (!canDecodeVideo( p_es->i_codec ))
939 msg_Dbg( p_stream, "can't remux video track %d codec %4.4s",
940 p_es->i_id, (const char*)&p_es->i_codec );
941 canRemux = false;
943 else if (i_codec_video == 0)
944 i_codec_video = p_es->i_codec;
945 p_original_video = p_es;
946 new_streams.push_back(*it);
948 else
949 continue;
950 /* TODO: else handle ttml/webvtt */
952 else
953 continue;
955 bool b_found = out_force_reload;
956 for (std::vector<sout_stream_id_sys_t*>::iterator out_it = out_streams.begin();
957 out_it != out_streams.end() && !b_found; ++out_it)
959 if (*out_it == *it)
960 b_found = true;
962 if (!b_found)
963 b_out_streams_changed = true;
966 if (new_streams.empty())
968 p_intf->requestPlayerStop();
969 return true;
972 /* Don't restart sout and CC session if streams didn't change */
973 if (!out_force_reload && new_streams.size() == out_streams.size() && !b_out_streams_changed)
974 return true;
976 out_force_reload = false;
978 std::stringstream ssout;
979 int new_transcoding_state = TRANSCODING_NONE;
980 if ( !canRemux )
982 if ( !perf_warning_shown && i_codec_video == 0 && p_original_video
983 && var_InheritInteger( p_stream, SOUT_CFG_PREFIX "show-perf-warning" ) )
985 int res = vlc_dialog_wait_question( p_stream,
986 VLC_DIALOG_QUESTION_WARNING,
987 _("Cancel"), _("OK"), _("Ok, Don't warn me again"),
988 _("Performance warning"),
989 _("Casting this video requires conversion. "
990 "This conversion can use all the available power and "
991 "could quickly drain your battery." ) );
992 if ( res <= 0 )
993 return false;
994 perf_warning_shown = true;
995 if ( res == 2 )
996 config_PutInt(p_stream, SOUT_CFG_PREFIX "show-perf-warning", 0 );
999 static const char video_maxres_hd[] = "maxwidth=1920,maxheight=1080";
1000 static const char video_maxres_720p[] = "maxwidth=1280,maxheight=720";
1001 static const char video_x264_preset_veryfast[] = "veryfast";
1002 static const char video_x264_preset_ultrafast[] = "ultrafast";
1004 const int i_quality = var_InheritInteger( p_stream, SOUT_CFG_PREFIX "conversion-quality" );
1005 const char *psz_video_maxres;
1006 const char *psz_video_x264_preset;
1007 unsigned i_video_x264_crf_hd, i_video_x264_crf_720p;
1008 bool b_audio_mp3;
1010 switch ( i_quality )
1012 case CONVERSION_QUALITY_HIGH:
1013 psz_video_maxres = video_maxres_hd;
1014 i_video_x264_crf_hd = i_video_x264_crf_720p = 21;
1015 psz_video_x264_preset = video_x264_preset_veryfast;
1016 b_audio_mp3 = false;
1017 break;
1018 case CONVERSION_QUALITY_MEDIUM:
1019 psz_video_maxres = video_maxres_hd;
1020 i_video_x264_crf_hd = 23;
1021 i_video_x264_crf_720p = 21;
1022 psz_video_x264_preset = video_x264_preset_veryfast;
1023 b_audio_mp3 = false;
1024 break;
1025 case CONVERSION_QUALITY_LOW:
1026 psz_video_maxres = video_maxres_720p;
1027 i_video_x264_crf_hd = i_video_x264_crf_720p = 23;
1028 psz_video_x264_preset = video_x264_preset_veryfast;
1029 b_audio_mp3 = true;
1030 break;
1031 default:
1032 case CONVERSION_QUALITY_LOWCPU:
1033 psz_video_maxres = video_maxres_720p;
1034 i_video_x264_crf_hd = i_video_x264_crf_720p = 23;
1035 psz_video_x264_preset = video_x264_preset_ultrafast;
1036 b_audio_mp3 = true;
1037 break;
1040 /* If we were already transcoding: force mp3 because maybe the CC may
1041 * have failed because of vorbis. */
1042 if (transcoding_state & TRANSCODING_AUDIO)
1043 b_audio_mp3 = true;
1045 /* TODO: provide audio samplerate and channels */
1046 ssout << "transcode{";
1047 char s_fourcc[5];
1048 if ( i_codec_audio == 0 && p_original_audio )
1050 if ( !b_audio_mp3
1051 && p_original_audio->audio.i_channels > 2 && module_exists( "vorbis" ) )
1052 i_codec_audio = VLC_CODEC_VORBIS;
1053 else
1054 i_codec_audio = VLC_CODEC_MP3;
1056 msg_Dbg( p_stream, "Converting audio to %.4s", (const char*)&i_codec_audio );
1057 ssout << "acodec=";
1058 vlc_fourcc_to_char( i_codec_audio, s_fourcc );
1059 s_fourcc[4] = '\0';
1060 ssout << s_fourcc << ',';
1062 /* XXX: higher vorbis qualities can cause glitches on some CC
1063 * devices (Chromecast 1 & 2) */
1064 if( i_codec_audio == VLC_CODEC_VORBIS )
1065 ssout << "aenc=vorbis{quality=4},";
1066 new_transcoding_state |= TRANSCODING_AUDIO;
1068 if ( i_codec_video == 0 && p_original_video )
1070 i_codec_video = DEFAULT_TRANSCODE_VIDEO;
1071 msg_Dbg( p_stream, "Converting video to %.4s", (const char*)&i_codec_video );
1072 ssout << "vcodec=";
1073 vlc_fourcc_to_char( i_codec_video, s_fourcc );
1074 s_fourcc[4] = '\0';
1075 ssout << s_fourcc << ',' << psz_video_maxres << ',';
1077 const video_format_t *p_vid = &p_original_video->video;
1078 const bool b_hdres = p_vid == NULL || p_vid->i_height == 0 || p_vid->i_height >= 800;
1079 unsigned i_video_x264_crf = b_hdres ? i_video_x264_crf_hd : i_video_x264_crf_720p;
1081 if( p_vid == NULL
1082 || p_vid->i_frame_rate == 0 || p_vid->i_frame_rate_base == 0
1083 || ( p_vid->i_frame_rate / p_vid->i_frame_rate_base ) > 30 )
1085 /* Even force 24fps if the frame rate is unknown */
1086 msg_Warn( p_stream, "lowering frame rate to 24fps" );
1087 ssout << "fps=24,";
1090 if( i_codec_video == VLC_CODEC_H264 )
1092 if ( module_exists("x264") )
1093 ssout << "venc=x264{preset=" << psz_video_x264_preset
1094 << ",crf=" << i_video_x264_crf << "},";
1096 new_transcoding_state |= TRANSCODING_VIDEO;
1098 ssout << "}:";
1100 if ( !p_original_video )
1101 mime = "audio/x-matroska";
1102 else if ( i_codec_audio == VLC_CODEC_VORBIS &&
1103 i_codec_video == VLC_CODEC_VP8 )
1104 mime = "video/webm";
1105 else
1106 mime = "video/x-matroska";
1108 ssout << "chromecast-proxy:"
1109 << "std{mux=" << DEFAULT_MUXER
1110 << ",access=chromecast-http}";
1112 if ( !startSoutChain( p_stream, new_streams, ssout.str(),
1113 new_transcoding_state ) )
1114 p_intf->requestPlayerStop();
1115 return true;
1118 sout_stream_id_sys_t *sout_stream_sys_t::GetSubId( sout_stream_t *p_stream,
1119 sout_stream_id_sys_t *id,
1120 bool update )
1122 size_t i;
1124 assert( p_stream->p_sys == this );
1126 if ( update && UpdateOutput( p_stream ) == false )
1127 return NULL;
1129 for (i = 0; i < out_streams.size(); ++i)
1131 if ( id == (sout_stream_id_sys_t*) out_streams[i] )
1132 return out_streams[i]->p_sub_id;
1135 return NULL;
1138 static int Send(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
1139 block_t *p_buffer)
1141 sout_stream_sys_t *p_sys = p_stream->p_sys;
1142 vlc_mutex_locker locker(&p_sys->lock);
1144 sout_stream_id_sys_t *next_id = p_sys->GetSubId( p_stream, id );
1145 if ( next_id == NULL )
1147 block_Release( p_buffer );
1148 return VLC_EGENERIC;
1151 int ret = sout_StreamIdSend(p_sys->p_out, next_id, p_buffer);
1152 if (ret != VLC_SUCCESS)
1153 DelInternal(p_stream, id, false);
1155 return ret;
1158 static void Flush( sout_stream_t *p_stream, sout_stream_id_sys_t *id )
1160 sout_stream_sys_t *p_sys = p_stream->p_sys;
1161 vlc_mutex_locker locker(&p_sys->lock);
1163 sout_stream_id_sys_t *next_id = p_sys->GetSubId( p_stream, id, false );
1164 if ( next_id == NULL )
1165 return;
1167 p_sys->access_out_live.stop();
1169 if (p_sys->cc_has_input)
1171 p_sys->p_intf->requestPlayerStop();
1172 p_sys->cc_has_input = false;
1174 p_sys->out_force_reload = p_sys->es_changed = true;
1177 static void on_input_event_cb(void *data, enum cc_input_event event, union cc_input_arg arg )
1179 sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(data);
1180 sout_stream_sys_t *p_sys = p_stream->p_sys;
1182 vlc_mutex_locker locker(&p_sys->lock);
1183 switch (event)
1185 case CC_INPUT_EVENT_EOF:
1186 /* In case of EOF: stop the sout chain in order to drain all
1187 * sout/demuxers/access. If EOF changes to false, reset es_changed
1188 * in order to reload the sout from next Send calls. */
1189 if( arg.eof )
1190 p_sys->stopSoutChain( p_stream );
1191 else
1192 p_sys->out_force_reload = p_sys->es_changed = true;
1193 break;
1194 case CC_INPUT_EVENT_RETRY:
1195 p_sys->stopSoutChain( p_stream );
1196 if( p_sys->transcodingCanFallback() )
1198 p_sys->setNextTranscodingState();
1199 msg_Warn(p_stream, "Load failed detected. Switching to next "
1200 "configuration. Transcoding video%s",
1201 p_sys->transcoding_state & TRANSCODING_AUDIO ? "/audio" : "");
1202 p_sys->out_force_reload = p_sys->es_changed = true;
1204 break;
1208 /*****************************************************************************
1209 * Open: connect to the Chromecast and initialize the sout
1210 *****************************************************************************/
1211 static int Open(vlc_object_t *p_this)
1213 sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
1214 sout_stream_sys_t *p_sys = NULL;
1215 intf_sys_t *p_intf = NULL;
1216 char *psz_ip = NULL;
1217 sout_stream_t *p_sout = NULL;
1218 httpd_host_t *httpd_host = NULL;
1219 bool b_supports_video = true;
1220 int i_local_server_port;
1221 int i_device_port;
1222 std::stringstream ss;
1224 config_ChainParse(p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg);
1226 psz_ip = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "ip");
1227 if ( psz_ip == NULL )
1229 msg_Err( p_this, "missing Chromecast IP address" );
1230 goto error;
1233 i_device_port = var_InheritInteger(p_stream, SOUT_CFG_PREFIX "port");
1234 i_local_server_port = var_InheritInteger(p_stream, SOUT_CFG_PREFIX "http-port");
1236 var_Create(p_stream, "http-port", VLC_VAR_INTEGER);
1237 var_SetInteger(p_stream, "http-port", i_local_server_port);
1238 var_Create(p_stream, "http-host", VLC_VAR_STRING);
1239 var_SetString(p_stream, "http-host", "");
1240 httpd_host = vlc_http_HostNew(VLC_OBJECT(p_stream));
1241 if (httpd_host == NULL)
1242 goto error;
1246 p_intf = new intf_sys_t( p_this, i_local_server_port, psz_ip, i_device_port,
1247 httpd_host );
1249 catch (const std::runtime_error& err )
1251 msg_Err( p_this, "cannot load the Chromecast controller (%s)", err.what() );
1252 goto error;
1254 catch (const std::bad_alloc& )
1256 p_intf = NULL;
1257 goto error;
1260 /* check if we can open the proper sout */
1261 ss << "http{mux=" << DEFAULT_MUXER << "}";
1262 p_sout = sout_StreamChainNew( p_stream->p_sout, ss.str().c_str(), NULL, NULL);
1263 if (p_sout == NULL) {
1264 msg_Dbg(p_stream, "could not create sout chain:%s", ss.str().c_str());
1265 goto error;
1267 sout_StreamChainDelete( p_sout, NULL );
1269 b_supports_video = var_GetBool(p_stream, SOUT_CFG_PREFIX "video");
1271 p_sys = new(std::nothrow) sout_stream_sys_t( httpd_host, p_intf, b_supports_video,
1272 i_local_server_port );
1273 if (unlikely(p_sys == NULL))
1274 goto error;
1276 p_intf->setOnInputEventCb(on_input_event_cb, p_stream);
1278 /* prevent sout-mux-caching since chromecast-proxy is already doing it */
1279 var_Create( p_stream->p_sout, "sout-mux-caching", VLC_VAR_INTEGER );
1280 var_SetInteger( p_stream->p_sout, "sout-mux-caching", 0 );
1282 var_Create( p_stream->p_sout, SOUT_CFG_PREFIX "sys", VLC_VAR_ADDRESS );
1283 var_SetAddress( p_stream->p_sout, SOUT_CFG_PREFIX "sys", p_sys );
1285 var_Create( p_stream->p_sout, SOUT_CFG_PREFIX "access-out-sys", VLC_VAR_ADDRESS );
1287 // Set the sout callbacks.
1288 p_stream->pf_add = Add;
1289 p_stream->pf_del = Del;
1290 p_stream->pf_send = Send;
1291 p_stream->pf_flush = Flush;
1293 p_stream->p_sys = p_sys;
1295 free(psz_ip);
1297 return VLC_SUCCESS;
1299 error:
1300 delete p_intf;
1301 if (httpd_host)
1302 httpd_HostDelete(httpd_host);
1303 free(psz_ip);
1304 delete p_sys;
1305 return VLC_EGENERIC;
1308 /*****************************************************************************
1309 * Close: destroy interface
1310 *****************************************************************************/
1311 static void Close(vlc_object_t *p_this)
1313 sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
1314 sout_stream_sys_t *p_sys = p_stream->p_sys;
1316 assert(p_sys->out_streams.empty() && p_sys->streams.empty());
1317 var_Destroy( p_stream->p_sout, SOUT_CFG_PREFIX "sys" );
1318 var_Destroy( p_stream->p_sout, SOUT_CFG_PREFIX "sout-mux-caching" );
1320 assert(p_sys->streams.empty() && p_sys->out_streams.empty());
1322 httpd_host_t *httpd_host = p_sys->httpd_host;
1323 delete p_sys->p_intf;
1324 delete p_sys;
1325 /* Delete last since p_intf and p_sys depends on httpd_host */
1326 httpd_HostDelete(httpd_host);