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 /*****************************************************************************
27 *****************************************************************************/
33 #include "chromecast.h"
34 #include <vlc_dialog.h>
37 #include <vlc_block.h>
38 #include <vlc_modules.h>
39 #include <vlc_httpd.h>
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
,
51 ~sout_access_out_sys_t();
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
);
65 void putCopy(block_t
*p_block
);
68 intf_sys_t
* const m_intf
;
70 httpd_client_t
*m_client
;
73 block_t
*m_copy_chain
;
74 block_t
**m_copy_last
;
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")
87 , b_supports_video(has_video
)
89 , first_video_keyframe_pts( -1 )
91 , cc_has_input( 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
);
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
;
125 vlc_mutex_t lock
; /* for input events cb */
127 intf_sys_t
* const p_intf
;
128 const bool b_supports_video
;
131 sout_stream_id_sys_t
* video_proxy_id
;
132 mtime_t first_video_keyframe_pts
;
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
;
147 bool UpdateOutput( sout_stream_t
* );
150 struct sout_stream_id_sys_t
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 /*****************************************************************************
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 /*****************************************************************************
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®." )
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
198 # define CONVERSION_QUALITY_DEFAULT CONVERSION_QUALITY_MEDIUM
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 */
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)
240 add_integer(SOUT_CFG_PREFIX
"port", CHROMECAST_CONTROL_PORT
, NULL
, NULL
, false)
242 add_bool(SOUT_CFG_PREFIX
"video", true, NULL
, NULL
, false)
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 )
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
)
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
)
260 set_subcategory(SUBCAT_SOUT_ACO
)
261 add_shortcut("chromecast-http")
262 set_capability("sout access", 0)
263 set_callbacks(AccessOpen
, AccessClose
)
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
);
272 if (p_fmt
->i_cat
== VIDEO_ES
)
273 p_sys
->video_proxy_id
= id
;
274 p_sys
->out_streams_added
++;
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
,
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
);
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;
333 block_Release(p_buffer
);
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
)
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
;
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
,
376 m_fifo
= block_FifoNew();
378 throw std::runtime_error( "block_FifoNew failed" );
379 m_url
= httpd_UrlNew(httpd_host
, psz_url
, NULL
, 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);
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
));
401 block_Release(m_header
);
408 void sout_access_out_sys_t::initCopy()
410 block_ChainRelease(m_copy_chain
);
412 m_copy_last
= &m_copy_chain
;
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
;
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()
439 fifo_put_back(m_copy_chain
);
445 void sout_access_out_sys_t::clear()
447 vlc_fifo_Lock(m_fifo
);
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
);
457 m_intf
->setPacing(false);
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
);
469 m_intf
->setPacing(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
)
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. */
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
)
511 i_min_buffer
= vlc_fifo_GetBytes(m_fifo
);
513 block_t
*p_first
= vlc_fifo_DequeueUnlocked(m_fifo
);
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
);
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
);
531 p_block
= block_Alloc(i_total_size
);
533 block_ChainExtract(p_first
, p_block
->p_buffer
, p_block
->i_buffer
);
534 block_ChainRelease(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;
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
;
560 i_answer_size
+= m_header
->i_buffer
;
562 answer
->p_body
= (uint8_t *) malloc(i_answer_size
);
565 answer
->i_body
= i_answer_size
;
566 answer
->i_body_offset
+= answer
->i_body
;
567 size_t i_block_offset
= 0;
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
);
579 httpd_MsgAdd(answer
, "Connection", "close");
581 vlc_fifo_Unlock(m_fifo
);
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
)
594 block_Release(m_header
);
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
);
625 vlc_fifo_Unlock(m_fifo
);
626 vlc_fifo_Signal(m_fifo
);
631 void sout_access_out_sys_t::close()
633 vlc_fifo_Lock(m_fifo
);
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
)
652 case ACCESS_OUT_CONTROLS_PACE
:
653 *va_arg(args
, bool *) = true;
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");
670 p_access
->pf_write
= AccessWrite
;
671 p_access
->pf_control
= AccessControl
;
672 p_access
->p_sys
= p_sys
;
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
;
685 /*****************************************************************************
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
)
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;
712 static void DelInternal(sout_stream_t
*p_stream
, sout_stream_id_sys_t
*id
,
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(); )
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
--;
744 es_format_Clean( &p_sys_id
->fmt
);
746 p_sys
->streams
.erase( 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);
771 * 0: Accept HEVC/VP9 & all supported audio formats
772 * 1: Transcode to h264 & accept all supported audio formats if the video codec
774 * 2: Transcode to H264 & MP3
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
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
)
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
)
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
)
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
;
830 sout_StreamChainDelete( 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;
844 first_video_keyframe_pts
= -1;
845 video_proxy_id
= NULL
;
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
);
855 msg_Dbg(p_stream
, "could not create sout chain:%s", sout
.c_str());
857 access_out_live
.clear();
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
);
875 if( p_sys_id
->fmt
.i_cat
== VIDEO_ES
)
877 else if( p_sys_id
->fmt
.i_cat
== SPU_ES
)
883 if (out_streams
.empty())
885 stopSoutChain( p_stream
);
886 access_out_live
.clear();
890 /* Ask to retry if we are not transcoding everything (because we can trust
892 p_intf
->setRetryOnFail(transcodingCanFallback());
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 );
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
);
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
);
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
);
963 p_original_spu
= p_es
;
964 new_streams
.push_back(*it
);
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
)
980 b_out_streams_changed
= true;
983 if (new_streams
.empty())
985 p_intf
->requestPlayerStop();
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
)
993 out_force_reload
= false;
995 std::stringstream ssout
;
996 int new_transcoding_state
= TRANSCODING_NONE
;
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." ) );
1011 perf_warning_shown
= true;
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
;
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;
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;
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
;
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
;
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
)
1062 /* TODO: provide audio samplerate and channels */
1063 ssout
<< "transcode{";
1065 if ( i_codec_audio
== 0 && p_original_audio
)
1068 && p_original_audio
->audio
.i_channels
> 2 && module_exists( "vorbis" ) )
1069 i_codec_audio
= VLC_CODEC_VORBIS
;
1071 i_codec_audio
= VLC_CODEC_MP3
;
1073 msg_Dbg( p_stream
, "Converting audio to %.4s", (const char*)&i_codec_audio
);
1075 vlc_fourcc_to_char( i_codec_audio
, s_fourcc
);
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
);
1090 vlc_fourcc_to_char( i_codec_video
, s_fourcc
);
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
;
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" );
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,";
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
)
1128 mime
= "audio/webm";
1130 mime
= "audio/x-matroska";
1135 mime
= "video/webm";
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();
1150 sout_stream_id_sys_t
*sout_stream_sys_t::GetSubId( sout_stream_t
*p_stream
,
1151 sout_stream_id_sys_t
*id
,
1156 assert( p_stream
->p_sys
== this );
1158 if ( update
&& UpdateOutput( p_stream
) == false )
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
;
1170 static int Send(sout_stream_t
*p_stream
, sout_stream_id_sys_t
*id
,
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);
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
)
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
);
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. */
1222 p_sys
->stopSoutChain( p_stream
);
1224 p_sys
->out_force_reload
= p_sys
->es_changed
= true;
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;
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
;
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" );
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
)
1278 p_intf
= new intf_sys_t( p_this
, i_local_server_port
, psz_ip
, i_device_port
,
1281 catch (const std::runtime_error
& err
)
1283 msg_Err( p_this
, "cannot load the Chromecast controller (%s)", err
.what() );
1286 catch (const std::bad_alloc
& )
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());
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
))
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
;
1334 httpd_HostDelete(httpd_host
);
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
;
1357 /* Delete last since p_intf and p_sys depends on httpd_host */
1358 httpd_HostDelete(httpd_host
);