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
48 /* TODO: works only with internal spu and transcoding/blending for now */
52 struct sout_access_out_sys_t
54 sout_access_out_sys_t(httpd_host_t
*httpd_host
, intf_sys_t
* const intf
,
56 ~sout_access_out_sys_t();
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
);
70 void putCopy(block_t
*p_block
);
73 intf_sys_t
* const m_intf
;
75 httpd_client_t
*m_client
;
78 block_t
*m_copy_chain
;
79 block_t
**m_copy_last
;
85 typedef struct sout_stream_id_sys_t sout_stream_id_sys_t
;
87 struct sout_stream_sys_t
89 sout_stream_sys_t(httpd_host_t
*httpd_host
, intf_sys_t
* const intf
, bool has_video
, int port
)
90 : httpd_host(httpd_host
)
91 , access_out_live(httpd_host
, intf
, "/stream")
94 , b_supports_video(has_video
)
96 , first_video_keyframe_pts( -1 )
98 , cc_has_input( false )
100 , cc_flushing( false )
102 , out_force_reload( false )
103 , perf_warning_shown( false )
104 , transcoding_state( TRANSCODING_NONE
)
105 , venc_opt_idx ( -1 )
106 , out_streams_added( 0 )
108 assert(p_intf
!= NULL
);
109 vlc_mutex_init(&lock
);
114 vlc_mutex_destroy(&lock
);
117 bool canDecodeVideo( vlc_fourcc_t i_codec
) const;
118 bool canDecodeAudio( sout_stream_t
* p_stream
, vlc_fourcc_t i_codec
,
119 const audio_format_t
* p_fmt
) const;
120 bool startSoutChain(sout_stream_t
* p_stream
,
121 const std::vector
<sout_stream_id_sys_t
*> &new_streams
,
122 const std::string
&sout
, int new_transcoding_state
);
123 void stopSoutChain(sout_stream_t
* p_stream
);
124 sout_stream_id_sys_t
*GetSubId( sout_stream_t
*, sout_stream_id_sys_t
*, bool update
= true );
125 bool isFlushing( sout_stream_t
* );
126 void setNextTranscodingState();
127 bool transcodingCanFallback() const;
129 httpd_host_t
*httpd_host
;
130 sout_access_out_sys_t access_out_live
;
132 sout_stream_t
*p_out
;
135 vlc_mutex_t lock
; /* for input events cb */
137 intf_sys_t
* const p_intf
;
138 const bool b_supports_video
;
141 sout_stream_id_sys_t
* video_proxy_id
;
142 vlc_tick_t first_video_keyframe_pts
;
149 bool out_force_reload
;
150 bool perf_warning_shown
;
151 int transcoding_state
;
153 std::vector
<sout_stream_id_sys_t
*> streams
;
154 std::vector
<sout_stream_id_sys_t
*> out_streams
;
155 unsigned int out_streams_added
;
156 unsigned int spu_streams_count
;
159 std::string
GetVencOption( sout_stream_t
*, vlc_fourcc_t
*,
160 const video_format_t
*, int );
161 std::string
GetAcodecOption( sout_stream_t
*, vlc_fourcc_t
*, const audio_format_t
*, int );
162 std::string
GetVcodecOption( sout_stream_t
*, vlc_fourcc_t
*, const video_format_t
*, int );
163 bool UpdateOutput( sout_stream_t
* );
166 struct sout_stream_id_sys_t
169 sout_stream_id_sys_t
*p_sub_id
;
173 #define SOUT_CFG_PREFIX "sout-chromecast-"
175 static const char DEFAULT_MUXER
[] = "avformat{mux=matroska,options={live=1}}";
176 static const char DEFAULT_MUXER_WEBM
[] = "avformat{mux=webm,options={live=1}}";
179 /*****************************************************************************
181 *****************************************************************************/
182 static int Open(vlc_object_t
*);
183 static void Close(vlc_object_t
*);
184 static int ProxyOpen(vlc_object_t
*);
185 static int AccessOpen(vlc_object_t
*);
186 static void AccessClose(vlc_object_t
*);
188 static const char *const ppsz_sout_options
[] = {
189 "ip", "port", "http-port", "video", NULL
192 /*****************************************************************************
194 *****************************************************************************/
196 #define HTTP_PORT_TEXT N_("HTTP port")
197 #define HTTP_PORT_LONGTEXT N_("This sets the HTTP port of the local server " \
198 "used to stream the media to the Chromecast.")
199 #define PERF_TEXT N_( "Performance warning" )
200 #define PERF_LONGTEXT N_( "Display a performance warning when transcoding" )
201 #define AUDIO_PASSTHROUGH_TEXT N_( "Enable Audio passthrough" )
202 #define AUDIO_PASSTHROUGH_LONGTEXT N_( "Disable if your receiver does not support Dolby®." )
205 CONVERSION_QUALITY_HIGH
= 0,
206 CONVERSION_QUALITY_MEDIUM
= 1,
207 CONVERSION_QUALITY_LOW
= 2,
208 CONVERSION_QUALITY_LOWCPU
= 3,
211 #if defined (__ANDROID__) || defined (__arm__) || (defined (TARGET_OS_IPHONE) && TARGET_OS_IPHONE)
212 # define CONVERSION_QUALITY_DEFAULT CONVERSION_QUALITY_LOW
214 # define CONVERSION_QUALITY_DEFAULT CONVERSION_QUALITY_MEDIUM
217 static const int conversion_quality_list
[] = {
218 CONVERSION_QUALITY_HIGH
,
219 CONVERSION_QUALITY_MEDIUM
,
220 CONVERSION_QUALITY_LOW
,
221 CONVERSION_QUALITY_LOWCPU
,
223 static const char *const conversion_quality_list_text
[] = {
224 N_( "High (high quality and high bandwidth)" ),
225 N_( "Medium (medium quality and medium bandwidth)" ),
226 N_( "Low (low quality and low bandwidth)" ),
227 N_( "Low CPU (low quality but high bandwidth)" ),
230 #define CONVERSION_QUALITY_TEXT N_( "Conversion quality" )
231 #define CONVERSION_QUALITY_LONGTEXT N_( "Change this option to increase conversion speed or quality." )
233 #define IP_ADDR_TEXT N_("IP Address")
234 #define IP_ADDR_LONGTEXT N_("IP Address of the Chromecast.")
235 #define PORT_TEXT N_("Chromecast port")
236 #define PORT_LONGTEXT N_("The port used to talk to the Chromecast.")
238 /* Fifo size after we tell the demux to pace */
239 #define HTTPD_BUFFER_PACE INT64_C(2 * 1024 * 1024) /* 2 MB */
240 /* Fifo size after we drop packets (should not happen) */
241 #define HTTPD_BUFFER_MAX INT64_C(32 * 1024 * 1024) /* 32 MB */
242 #define HTTPD_BUFFER_COPY_MAX INT64_C(10 * 1024 * 1024) /* 10 MB */
246 set_shortname(N_("Chromecast"))
247 set_description(N_("Chromecast stream output"))
248 set_capability("sout stream", 0)
249 add_shortcut("chromecast")
250 set_category(CAT_SOUT
)
251 set_subcategory(SUBCAT_SOUT_STREAM
)
252 set_callbacks(Open
, Close
)
254 add_string(SOUT_CFG_PREFIX
"ip", NULL
, NULL
, NULL
, false)
256 add_integer(SOUT_CFG_PREFIX
"port", CHROMECAST_CONTROL_PORT
, NULL
, NULL
, false)
258 add_bool(SOUT_CFG_PREFIX
"video", true, NULL
, NULL
, false)
260 add_integer(SOUT_CFG_PREFIX
"http-port", HTTP_PORT
, HTTP_PORT_TEXT
, HTTP_PORT_LONGTEXT
, false)
261 add_obsolete_string(SOUT_CFG_PREFIX
"mux")
262 add_obsolete_string(SOUT_CFG_PREFIX
"mime")
263 add_integer(SOUT_CFG_PREFIX
"show-perf-warning", 1, PERF_TEXT
, PERF_LONGTEXT
, true )
265 add_bool(SOUT_CFG_PREFIX
"audio-passthrough", false, AUDIO_PASSTHROUGH_TEXT
, AUDIO_PASSTHROUGH_LONGTEXT
, false )
266 add_integer(SOUT_CFG_PREFIX
"conversion-quality", CONVERSION_QUALITY_DEFAULT
,
267 CONVERSION_QUALITY_TEXT
, CONVERSION_QUALITY_LONGTEXT
, false );
268 change_integer_list(conversion_quality_list
, conversion_quality_list_text
)
271 /* sout proxy that start the cc input when all streams are loaded */
272 add_shortcut("chromecast-proxy")
273 set_capability("sout stream", 0)
274 set_callbacks(ProxyOpen
, NULL
)
276 set_subcategory(SUBCAT_SOUT_ACO
)
277 add_shortcut("chromecast-http")
278 set_capability("sout access", 0)
279 set_callbacks(AccessOpen
, AccessClose
)
282 static void *ProxyAdd(sout_stream_t
*p_stream
, const es_format_t
*p_fmt
)
284 sout_stream_sys_t
*p_sys
= reinterpret_cast<sout_stream_sys_t
*>( p_stream
->p_sys
);
285 sout_stream_id_sys_t
*id
= reinterpret_cast<sout_stream_id_sys_t
*>( sout_StreamIdAdd(p_stream
->p_next
, p_fmt
) );
288 if (p_fmt
->i_cat
== VIDEO_ES
)
289 p_sys
->video_proxy_id
= id
;
290 p_sys
->out_streams_added
++;
295 static void ProxyDel(sout_stream_t
*p_stream
, void *_id
)
297 sout_stream_sys_t
*p_sys
= reinterpret_cast<sout_stream_sys_t
*>( p_stream
->p_sys
);
298 sout_stream_id_sys_t
*id
= reinterpret_cast<sout_stream_id_sys_t
*>( _id
);
299 p_sys
->out_streams_added
--;
300 if (id
== p_sys
->video_proxy_id
)
301 p_sys
->video_proxy_id
= NULL
;
302 return sout_StreamIdDel(p_stream
->p_next
, id
);
305 static int ProxySend(sout_stream_t
*p_stream
, void *_id
, block_t
*p_buffer
)
307 sout_stream_sys_t
*p_sys
= reinterpret_cast<sout_stream_sys_t
*>( p_stream
->p_sys
);
308 sout_stream_id_sys_t
*id
= reinterpret_cast<sout_stream_id_sys_t
*>( _id
);
309 if (p_sys
->cc_has_input
310 || p_sys
->out_streams_added
>= p_sys
->out_streams
.size() - p_sys
->spu_streams_count
)
312 if (p_sys
->has_video
)
314 // In case of video, the first block must be a keyframe
315 if (id
== p_sys
->video_proxy_id
)
317 if (p_sys
->first_video_keyframe_pts
== -1
318 && p_buffer
->i_flags
& BLOCK_FLAG_TYPE_I
)
319 p_sys
->first_video_keyframe_pts
= p_buffer
->i_pts
;
321 else // no keyframe for audio
322 p_buffer
->i_flags
&= ~BLOCK_FLAG_TYPE_I
;
324 if (p_buffer
->i_pts
< p_sys
->first_video_keyframe_pts
325 || p_sys
->first_video_keyframe_pts
== -1)
327 block_Release(p_buffer
);
332 vlc_tick_t pause_delay
= p_sys
->p_intf
->getPauseDelay();
333 if( p_buffer
->i_pts
!= VLC_TICK_INVALID
)
334 p_buffer
->i_pts
-= pause_delay
;
335 if( p_buffer
->i_dts
!= VLC_TICK_INVALID
)
336 p_buffer
->i_dts
-= pause_delay
;
338 int ret
= sout_StreamIdSend(p_stream
->p_next
, id
, p_buffer
);
339 if (ret
== VLC_SUCCESS
&& !p_sys
->cc_has_input
)
341 /* Start the chromecast only when all streams are added into the
342 * last sout (the http one) */
343 p_sys
->p_intf
->setHasInput(p_sys
->mime
);
344 p_sys
->cc_has_input
= true;
350 block_Release(p_buffer
);
355 static void ProxyFlush(sout_stream_t
*p_stream
, void *id
)
357 sout_StreamFlush(p_stream
->p_next
, id
);
360 static int ProxyOpen(vlc_object_t
*p_this
)
362 sout_stream_t
*p_stream
= reinterpret_cast<sout_stream_t
*>(p_this
);
363 sout_stream_sys_t
*p_sys
= (sout_stream_sys_t
*) var_InheritAddress(p_this
, SOUT_CFG_PREFIX
"sys");
364 if (p_sys
== NULL
|| p_stream
->p_next
== NULL
)
367 p_stream
->p_sys
= (sout_stream_sys_t
*) p_sys
;
368 p_sys
->out_streams_added
= 0;
370 p_stream
->pf_add
= ProxyAdd
;
371 p_stream
->pf_del
= ProxyDel
;
372 p_stream
->pf_send
= ProxySend
;
373 p_stream
->pf_flush
= ProxyFlush
;
377 static int httpd_url_cb(httpd_callback_sys_t
*data
, httpd_client_t
*cl
,
378 httpd_message_t
*answer
, const httpd_message_t
*query
)
380 sout_access_out_sys_t
*p_sys
= reinterpret_cast<sout_access_out_sys_t
*>(data
);
381 return p_sys
->url_cb(cl
, answer
, query
);
384 sout_access_out_sys_t::sout_access_out_sys_t(httpd_host_t
*httpd_host
,
385 intf_sys_t
* const intf
,
393 m_fifo
= block_FifoNew();
395 throw std::runtime_error( "block_FifoNew failed" );
396 m_url
= httpd_UrlNew(httpd_host
, psz_url
, NULL
, NULL
);
399 block_FifoRelease(m_fifo
);
400 throw std::runtime_error( "httpd_UrlNew failed" );
402 httpd_UrlCatch(m_url
, HTTPD_MSG_GET
, httpd_url_cb
,
403 (httpd_callback_sys_t
*)this);
407 sout_access_out_sys_t::~sout_access_out_sys_t()
409 httpd_UrlDelete(m_url
);
410 block_FifoRelease(m_fifo
);
413 void sout_access_out_sys_t::clearUnlocked()
415 block_ChainRelease(vlc_fifo_DequeueAllUnlocked(m_fifo
));
418 block_Release(m_header
);
425 void sout_access_out_sys_t::initCopy()
427 block_ChainRelease(m_copy_chain
);
429 m_copy_last
= &m_copy_chain
;
433 void sout_access_out_sys_t::putCopy(block_t
*p_block
)
435 while (m_copy_size
>= HTTPD_BUFFER_COPY_MAX
)
437 assert(m_copy_chain
);
438 block_t
*copy
= m_copy_chain
;
439 m_copy_chain
= copy
->p_next
;
440 m_copy_size
-= copy
->i_buffer
;
445 assert(m_copy_size
== 0);
446 m_copy_last
= &m_copy_chain
;
448 block_ChainLastAppend(&m_copy_last
, p_block
);
449 m_copy_size
+= p_block
->i_buffer
;
452 void sout_access_out_sys_t::restoreCopy()
456 fifo_put_back(m_copy_chain
);
462 void sout_access_out_sys_t::clear()
464 vlc_fifo_Lock(m_fifo
);
466 vlc_fifo_Unlock(m_fifo
);
467 vlc_fifo_Signal(m_fifo
);
470 void sout_access_out_sys_t::stop()
472 vlc_fifo_Lock(m_fifo
);
474 m_intf
->setPacing(false);
476 vlc_fifo_Unlock(m_fifo
);
477 vlc_fifo_Signal(m_fifo
);
480 void sout_access_out_sys_t::prepare(sout_stream_t
*p_stream
, const std::string
&mime
)
482 var_SetAddress(p_stream
->p_sout
, SOUT_CFG_PREFIX
"access-out-sys", this);
484 vlc_fifo_Lock(m_fifo
);
486 m_intf
->setPacing(false);
489 vlc_fifo_Unlock(m_fifo
);
492 void sout_access_out_sys_t::fifo_put_back(block_t
*p_block
)
494 block_t
*p_fifo
= vlc_fifo_DequeueAllUnlocked(m_fifo
);
495 vlc_fifo_QueueUnlocked(m_fifo
, p_block
);
496 vlc_fifo_QueueUnlocked(m_fifo
, p_fifo
);
499 int sout_access_out_sys_t::url_cb(httpd_client_t
*cl
, httpd_message_t
*answer
,
500 const httpd_message_t
*query
)
502 if (!answer
|| !query
|| !cl
)
505 vlc_fifo_Lock(m_fifo
);
507 if (!answer
->i_body_offset
)
509 /* When doing a lot a load requests, we can serve data to a client that
510 * will be closed (the close request is already sent). In that case, we
511 * should also serve data used by the old client to the new one. */
516 /* Send data per 512kB minimum */
517 size_t i_min_buffer
= 524288;
518 while (m_client
&& vlc_fifo_GetBytes(m_fifo
) < i_min_buffer
&& !m_eof
)
519 vlc_fifo_Wait(m_fifo
);
521 block_t
*p_block
= NULL
;
522 if (m_client
&& vlc_fifo_GetBytes(m_fifo
) > 0)
524 /* if less data is available, then we must be EOF */
525 if (vlc_fifo_GetBytes(m_fifo
) < i_min_buffer
)
528 i_min_buffer
= vlc_fifo_GetBytes(m_fifo
);
530 block_t
*p_first
= vlc_fifo_DequeueUnlocked(m_fifo
);
533 size_t i_total_size
= p_first
->i_buffer
;
534 block_t
*p_next
= NULL
, *p_cur
= p_first
;
536 while (i_total_size
< i_min_buffer
)
538 p_next
= vlc_fifo_DequeueUnlocked(m_fifo
);
540 i_total_size
+= p_next
->i_buffer
;
541 p_cur
->p_next
= p_next
;
542 p_cur
= p_cur
->p_next
;
544 assert(i_total_size
>= i_min_buffer
);
548 p_block
= block_Alloc(i_total_size
);
550 block_ChainExtract(p_first
, p_block
->p_buffer
, p_block
->i_buffer
);
551 block_ChainRelease(p_first
);
556 if (vlc_fifo_GetBytes(m_fifo
) < HTTPD_BUFFER_PACE
)
557 m_intf
->setPacing(false);
560 answer
->i_proto
= HTTPD_PROTO_HTTP
;
561 answer
->i_version
= 0;
562 answer
->i_type
= HTTPD_MSG_ANSWER
;
563 answer
->i_status
= 200;
567 if (answer
->i_body_offset
== 0)
569 httpd_MsgAdd(answer
, "Content-type", "%s", m_mime
.c_str());
570 httpd_MsgAdd(answer
, "Cache-Control", "no-cache");
571 httpd_MsgAdd(answer
, "Connection", "close");
574 const bool send_header
= answer
->i_body_offset
== 0 && m_header
!= NULL
;
575 size_t i_answer_size
= p_block
->i_buffer
;
577 i_answer_size
+= m_header
->i_buffer
;
579 answer
->p_body
= (uint8_t *) malloc(i_answer_size
);
582 answer
->i_body
= i_answer_size
;
583 answer
->i_body_offset
+= answer
->i_body
;
584 size_t i_block_offset
= 0;
587 memcpy(answer
->p_body
, m_header
->p_buffer
, m_header
->i_buffer
);
588 i_block_offset
= m_header
->i_buffer
;
590 memcpy(&answer
->p_body
[i_block_offset
], p_block
->p_buffer
, p_block
->i_buffer
);
596 httpd_MsgAdd(answer
, "Connection", "close");
598 vlc_fifo_Unlock(m_fifo
);
602 ssize_t
sout_access_out_sys_t::write(sout_access_out_t
*p_access
, block_t
*p_block
)
604 size_t i_len
= p_block
->i_buffer
;
606 vlc_fifo_Lock(m_fifo
);
608 if (p_block
->i_flags
& BLOCK_FLAG_HEADER
)
611 block_Release(m_header
);
616 /* Drop buffer is the fifo is really full */
617 if (vlc_fifo_GetBytes(m_fifo
) >= HTTPD_BUFFER_PACE
)
619 /* XXX: Hackisk way to pace between the sout (controlled by the
620 * decoder thread) and the demux filter (controlled by the input
621 * thread). When the httpd FIFO reaches a specific size, we tell
622 * the demux filter to pace and wait a little before queing this
623 * block, but not too long since we don't want to block decoder
624 * thread controls. If the pacing fails (should not happen), we
625 * drop the first block in order to make room. The demux filter
626 * will be unpaced when the data is read from the httpd thread. */
628 m_intf
->setPacing(true);
630 while (vlc_fifo_GetBytes(m_fifo
) >= HTTPD_BUFFER_MAX
)
632 block_t
*p_drop
= vlc_fifo_DequeueUnlocked(m_fifo
);
633 msg_Warn(p_access
, "httpd buffer full: dropping %zuB", p_drop
->i_buffer
);
634 block_Release(p_drop
);
637 vlc_fifo_QueueUnlocked(m_fifo
, p_block
);
642 vlc_fifo_Unlock(m_fifo
);
643 vlc_fifo_Signal(m_fifo
);
648 void sout_access_out_sys_t::close()
650 vlc_fifo_Lock(m_fifo
);
652 m_intf
->setPacing(false);
653 vlc_fifo_Unlock(m_fifo
);
654 vlc_fifo_Signal(m_fifo
);
657 ssize_t
AccessWrite(sout_access_out_t
*p_access
, block_t
*p_block
)
659 sout_access_out_sys_t
*p_sys
= reinterpret_cast<sout_access_out_sys_t
*>( p_access
->p_sys
);
660 return p_sys
->write(p_access
, p_block
);
663 static int AccessControl(sout_access_out_t
*p_access
, int i_query
, va_list args
)
669 case ACCESS_OUT_CONTROLS_PACE
:
670 *va_arg(args
, bool *) = true;
678 static int AccessOpen(vlc_object_t
*p_this
)
680 sout_access_out_t
*p_access
= (sout_access_out_t
*)p_this
;
682 sout_access_out_sys_t
*p_sys
= (sout_access_out_sys_t
*)
683 var_InheritAddress(p_access
, SOUT_CFG_PREFIX
"access-out-sys");
687 p_access
->pf_write
= AccessWrite
;
688 p_access
->pf_control
= AccessControl
;
689 p_access
->p_sys
= p_sys
;
694 static void AccessClose(vlc_object_t
*p_this
)
696 sout_access_out_t
*p_access
= (sout_access_out_t
*)p_this
;
697 sout_access_out_sys_t
*p_sys
= reinterpret_cast<sout_access_out_sys_t
*>( p_access
->p_sys
);
702 /*****************************************************************************
704 *****************************************************************************/
705 static void *Add(sout_stream_t
*p_stream
, const es_format_t
*p_fmt
)
707 sout_stream_sys_t
*p_sys
= reinterpret_cast<sout_stream_sys_t
*>( p_stream
->p_sys
);
708 vlc_mutex_locker
locker(&p_sys
->lock
);
710 if (!p_sys
->b_supports_video
)
712 if (p_fmt
->i_cat
!= AUDIO_ES
)
716 sout_stream_id_sys_t
*p_sys_id
= (sout_stream_id_sys_t
*)malloc( sizeof(sout_stream_id_sys_t
) );
717 if (p_sys_id
!= NULL
)
719 es_format_Copy( &p_sys_id
->fmt
, p_fmt
);
720 p_sys_id
->p_sub_id
= NULL
;
721 p_sys_id
->flushed
= false;
723 p_sys
->streams
.push_back( p_sys_id
);
724 p_sys
->es_changed
= true;
730 static void DelInternal(sout_stream_t
*p_stream
, void *_id
, bool reset_config
)
732 sout_stream_sys_t
*p_sys
= reinterpret_cast<sout_stream_sys_t
*>( p_stream
->p_sys
);
733 sout_stream_id_sys_t
*id
= reinterpret_cast<sout_stream_id_sys_t
*>( _id
);
735 for (std::vector
<sout_stream_id_sys_t
*>::iterator it
= p_sys
->streams
.begin();
736 it
!= p_sys
->streams
.end(); )
738 sout_stream_id_sys_t
*p_sys_id
= *it
;
739 if ( p_sys_id
== id
)
741 if ( p_sys_id
->p_sub_id
!= NULL
)
743 sout_StreamIdDel( p_sys
->p_out
, p_sys_id
->p_sub_id
);
744 for (std::vector
<sout_stream_id_sys_t
*>::iterator out_it
= p_sys
->out_streams
.begin();
745 out_it
!= p_sys
->out_streams
.end(); )
749 p_sys
->out_streams
.erase(out_it
);
750 p_sys
->es_changed
= reset_config
;
751 p_sys
->out_force_reload
= reset_config
;
752 if( p_sys_id
->fmt
.i_cat
== VIDEO_ES
)
753 p_sys
->has_video
= false;
754 else if( p_sys_id
->fmt
.i_cat
== SPU_ES
)
755 p_sys
->spu_streams_count
--;
762 es_format_Clean( &p_sys_id
->fmt
);
764 p_sys
->streams
.erase( it
);
770 if ( p_sys
->out_streams
.empty() )
772 p_sys
->stopSoutChain(p_stream
);
773 p_sys
->p_intf
->requestPlayerStop();
774 p_sys
->access_out_live
.clear();
775 p_sys
->transcoding_state
= TRANSCODING_NONE
;
779 static void Del(sout_stream_t
*p_stream
, void *_id
)
781 sout_stream_sys_t
*p_sys
= reinterpret_cast<sout_stream_sys_t
*>( p_stream
->p_sys
);
782 sout_stream_id_sys_t
*id
= reinterpret_cast<sout_stream_id_sys_t
*>( _id
);
784 vlc_mutex_locker
locker(&p_sys
->lock
);
785 DelInternal(p_stream
, id
, true);
790 * 0: Accept HEVC/VP9 & all supported audio formats
791 * 1: Transcode to h264 & accept all supported audio formats if the video codec
793 * 2: Transcode to H264 & MP3
796 * - Allow (E)AC3 passthrough depending on the audio-passthrough
797 * config value, except for the final step, where we just give up and transcode
799 * - Disallow multichannel AAC
801 * Supported formats: https://developers.google.com/cast/docs/media
804 bool sout_stream_sys_t::canDecodeVideo( vlc_fourcc_t i_codec
) const
806 if( transcoding_state
& TRANSCODING_VIDEO
)
808 return i_codec
== VLC_CODEC_H264
|| i_codec
== VLC_CODEC_HEVC
809 || i_codec
== VLC_CODEC_VP8
|| i_codec
== VLC_CODEC_VP9
;
812 bool sout_stream_sys_t::canDecodeAudio( sout_stream_t
*p_stream
,
813 vlc_fourcc_t i_codec
,
814 const audio_format_t
* p_fmt
) const
816 if( transcoding_state
& TRANSCODING_AUDIO
)
818 if ( i_codec
== VLC_CODEC_A52
|| i_codec
== VLC_CODEC_EAC3
)
820 return var_InheritBool( p_stream
, SOUT_CFG_PREFIX
"audio-passthrough" );
822 if ( i_codec
== VLC_FOURCC('h', 'a', 'a', 'c') ||
823 i_codec
== VLC_FOURCC('l', 'a', 'a', 'c') ||
824 i_codec
== VLC_FOURCC('s', 'a', 'a', 'c') ||
825 i_codec
== VLC_CODEC_MP4A
)
827 return p_fmt
->i_channels
<= 2;
829 return i_codec
== VLC_CODEC_VORBIS
|| i_codec
== VLC_CODEC_OPUS
||
830 i_codec
== VLC_CODEC_MP3
;
833 void sout_stream_sys_t::stopSoutChain(sout_stream_t
*p_stream
)
837 if ( unlikely( p_out
!= NULL
) )
839 for ( size_t i
= 0; i
< out_streams
.size(); i
++ )
841 if ( out_streams
[i
]->p_sub_id
!= NULL
)
843 sout_StreamIdDel( p_out
, out_streams
[i
]->p_sub_id
);
844 out_streams
[i
]->p_sub_id
= NULL
;
848 sout_StreamChainDelete( p_out
, NULL
);
853 bool sout_stream_sys_t::startSoutChain(sout_stream_t
*p_stream
,
854 const std::vector
<sout_stream_id_sys_t
*> &new_streams
,
855 const std::string
&sout
, int new_transcoding_state
)
857 stopSoutChain( p_stream
);
859 msg_Dbg( p_stream
, "Creating chain %s", sout
.c_str() );
860 cc_has_input
= false;
862 first_video_keyframe_pts
= -1;
863 video_proxy_id
= NULL
;
865 out_streams
= new_streams
;
866 spu_streams_count
= 0;
867 transcoding_state
= new_transcoding_state
;
869 access_out_live
.prepare( p_stream
, mime
);
871 p_out
= sout_StreamChainNew( p_stream
->p_sout
, sout
.c_str(), NULL
, NULL
);
873 msg_Dbg(p_stream
, "could not create sout chain:%s", sout
.c_str());
875 access_out_live
.clear();
879 /* check the streams we can actually add */
880 for (std::vector
<sout_stream_id_sys_t
*>::iterator it
= out_streams
.begin();
881 it
!= out_streams
.end(); )
883 sout_stream_id_sys_t
*p_sys_id
= *it
;
884 p_sys_id
->p_sub_id
= reinterpret_cast<sout_stream_id_sys_t
*>( sout_StreamIdAdd( p_out
, &p_sys_id
->fmt
) );
885 if ( p_sys_id
->p_sub_id
== NULL
)
887 msg_Err( p_stream
, "can't handle %4.4s stream", (char *)&p_sys_id
->fmt
.i_codec
);
888 es_format_Clean( &p_sys_id
->fmt
);
889 it
= out_streams
.erase( it
);
893 if( p_sys_id
->fmt
.i_cat
== VIDEO_ES
)
895 else if( p_sys_id
->fmt
.i_cat
== SPU_ES
)
901 if (out_streams
.empty())
903 stopSoutChain( p_stream
);
904 access_out_live
.clear();
908 /* Ask to retry if we are not transcoding everything (because we can trust
910 p_intf
->setRetryOnFail(transcodingCanFallback());
915 void sout_stream_sys_t::setNextTranscodingState()
917 if (!(transcoding_state
& TRANSCODING_VIDEO
))
918 transcoding_state
|= TRANSCODING_VIDEO
;
919 else if (!(transcoding_state
& TRANSCODING_AUDIO
))
920 transcoding_state
= TRANSCODING_AUDIO
;
923 bool sout_stream_sys_t::transcodingCanFallback() const
925 return transcoding_state
!= (TRANSCODING_VIDEO
|TRANSCODING_AUDIO
);
928 static std::string
GetVencVPXOption( sout_stream_t
* /* p_stream */,
929 const video_format_t
* /* p_vid */,
930 int /* i_quality */ )
932 return "venc=vpx{quality-mode=1}";
935 static std::string
GetVencQSVH264Option( sout_stream_t
* /* p_stream */,
936 const video_format_t
* /* p_vid */,
939 std::stringstream ssout
;
940 static const char video_target_usage_quality
[] = "quality";
941 static const char video_target_usage_balanced
[] = "balanced";
942 static const char video_target_usage_speed
[] = "speed";
943 static const char video_bitrate_high
[] = "vb=8000000";
944 static const char video_bitrate_low
[] = "vb=3000000";
945 const char *psz_video_target_usage
;
946 const char *psz_video_bitrate
;
950 case CONVERSION_QUALITY_HIGH
:
951 psz_video_target_usage
= video_target_usage_quality
;
952 psz_video_bitrate
= video_bitrate_high
;
954 case CONVERSION_QUALITY_MEDIUM
:
955 psz_video_target_usage
= video_target_usage_balanced
;
956 psz_video_bitrate
= video_bitrate_high
;
958 case CONVERSION_QUALITY_LOW
:
959 psz_video_target_usage
= video_target_usage_balanced
;
960 psz_video_bitrate
= video_bitrate_low
;
963 case CONVERSION_QUALITY_LOWCPU
:
964 psz_video_target_usage
= video_target_usage_speed
;
965 psz_video_bitrate
= video_bitrate_low
;
969 ssout
<< "venc=qsv{target-usage=" << psz_video_target_usage
<<
970 "}," << psz_video_bitrate
;
974 static std::string
GetVencX264Option( sout_stream_t
* /* p_stream */,
975 const video_format_t
*p_vid
,
978 std::stringstream ssout
;
979 static const char video_x264_preset_veryfast
[] = "veryfast";
980 static const char video_x264_preset_ultrafast
[] = "ultrafast";
981 const char *psz_video_x264_preset
;
982 unsigned i_video_x264_crf_hd
, i_video_x264_crf_720p
;
986 case CONVERSION_QUALITY_HIGH
:
987 i_video_x264_crf_hd
= i_video_x264_crf_720p
= 21;
988 psz_video_x264_preset
= video_x264_preset_veryfast
;
990 case CONVERSION_QUALITY_MEDIUM
:
991 i_video_x264_crf_hd
= 23;
992 i_video_x264_crf_720p
= 21;
993 psz_video_x264_preset
= video_x264_preset_veryfast
;
995 case CONVERSION_QUALITY_LOW
:
996 i_video_x264_crf_hd
= i_video_x264_crf_720p
= 23;
997 psz_video_x264_preset
= video_x264_preset_veryfast
;
1000 case CONVERSION_QUALITY_LOWCPU
:
1001 i_video_x264_crf_hd
= i_video_x264_crf_720p
= 23;
1002 psz_video_x264_preset
= video_x264_preset_ultrafast
;
1006 const bool b_hdres
= p_vid
== NULL
|| p_vid
->i_height
== 0 || p_vid
->i_height
>= 800;
1007 unsigned i_video_x264_crf
= b_hdres
? i_video_x264_crf_hd
: i_video_x264_crf_720p
;
1009 ssout
<< "venc=x264{preset=" << psz_video_x264_preset
1010 << ",crf=" << i_video_x264_crf
<< "}";
1015 static std::string
GetVencAvcodecVTOption( sout_stream_t
* /* p_stream */,
1016 const video_format_t
* p_vid
,
1019 std::stringstream ssout
;
1020 ssout
<< "venc=avcodec{codec=h264_videotoolbox,options{realtime=1}}";
1023 /* Here, performances issues won't come from videotoolbox but from
1024 * some old chromecast devices */
1026 case CONVERSION_QUALITY_HIGH
:
1028 case CONVERSION_QUALITY_MEDIUM
:
1029 ssout
<< ",vb=8000000";
1031 case CONVERSION_QUALITY_LOW
:
1032 case CONVERSION_QUALITY_LOWCPU
:
1033 ssout
<< ",vb=3000000";
1044 std::string (*get_opt
)( sout_stream_t
*, const video_format_t
*, int);
1045 } venc_opt_list
[] = {
1047 { .fcc
= VLC_CODEC_H264
, .get_opt
= GetVencAvcodecVTOption
},
1049 { .fcc
= VLC_CODEC_H264
, .get_opt
= GetVencQSVH264Option
},
1050 { .fcc
= VLC_CODEC_H264
, .get_opt
= GetVencX264Option
},
1051 { .fcc
= VLC_CODEC_VP8
, .get_opt
= GetVencVPXOption
},
1052 { .fcc
= VLC_CODEC_H264
, .get_opt
= NULL
},
1056 sout_stream_sys_t::GetVencOption( sout_stream_t
*p_stream
, vlc_fourcc_t
*p_codec_video
,
1057 const video_format_t
*p_vid
, int i_quality
)
1059 for( size_t i
= (venc_opt_idx
== -1 ? 0 : venc_opt_idx
);
1060 i
< ARRAY_SIZE(venc_opt_list
); ++i
)
1062 std::stringstream ssout
, ssvenc
;
1064 ssvenc
<< "vcodec=";
1065 vlc_fourcc_to_char( venc_opt_list
[i
].fcc
, fourcc
);
1067 ssvenc
<< fourcc
<< ',';
1069 if( venc_opt_list
[i
].get_opt
!= NULL
)
1070 ssvenc
<< venc_opt_list
[i
].get_opt( p_stream
, p_vid
, i_quality
) << ',';
1072 if( venc_opt_list
[i
].get_opt
== NULL
1073 || ( venc_opt_idx
!= -1 && (unsigned) venc_opt_idx
== i
) )
1076 *p_codec_video
= venc_opt_list
[i
].fcc
;
1077 return ssvenc
.str();
1080 /* Test if a module can encode with the specified options / fmt_video. */
1081 ssout
<< "transcode{" << ssvenc
.str() << "}:dummy";
1083 sout_stream_t
*p_sout_test
=
1084 sout_StreamChainNew( p_stream
->p_sout
, ssout
.str().c_str(), NULL
, NULL
);
1086 if( p_sout_test
!= NULL
)
1088 p_sout_test
->obj
.flags
|= OBJECT_FLAGS_QUIET
|OBJECT_FLAGS_NOINTERACT
;
1091 es_format_InitFromVideo( &fmt
, p_vid
);
1092 fmt
.i_codec
= fmt
.video
.i_chroma
= VLC_CODEC_I420
;
1094 /* Test the maximum size/fps we will encode */
1095 fmt
.video
.i_visible_width
= fmt
.video
.i_width
= 1920;
1096 fmt
.video
.i_visible_height
= fmt
.video
.i_height
= 1080;
1097 fmt
.video
.i_frame_rate
= 30;
1098 fmt
.video
.i_frame_rate_base
= 1;
1100 void *id
= sout_StreamIdAdd( p_sout_test
, &fmt
);
1102 es_format_Clean( &fmt
);
1103 const bool success
= id
!= NULL
;
1106 sout_StreamIdDel( p_sout_test
, id
);
1107 sout_StreamChainDelete( p_sout_test
, NULL
);
1112 *p_codec_video
= venc_opt_list
[i
].fcc
;
1113 return ssvenc
.str();
1117 vlc_assert_unreachable();
1121 sout_stream_sys_t::GetVcodecOption( sout_stream_t
*p_stream
, vlc_fourcc_t
*p_codec_video
,
1122 const video_format_t
*p_vid
, int i_quality
)
1124 std::stringstream ssout
;
1125 static const char video_maxres_hd
[] = "maxwidth=1920,maxheight=1080";
1126 static const char video_maxres_720p
[] = "maxwidth=1280,maxheight=720";
1128 ssout
<< GetVencOption( p_stream
, p_codec_video
, p_vid
, i_quality
);
1130 switch ( i_quality
)
1132 case CONVERSION_QUALITY_HIGH
:
1133 case CONVERSION_QUALITY_MEDIUM
:
1134 ssout
<< video_maxres_hd
<< ',';
1137 ssout
<< video_maxres_720p
<< ',';
1141 || p_vid
->i_frame_rate
== 0 || p_vid
->i_frame_rate_base
== 0
1142 || ( p_vid
->i_frame_rate
/ p_vid
->i_frame_rate_base
) > 30 )
1144 /* Even force 24fps if the frame rate is unknown */
1145 msg_Warn( p_stream
, "lowering frame rate to 24fps" );
1149 msg_Dbg( p_stream
, "Converting video to %.4s", (const char*)p_codec_video
);
1155 sout_stream_sys_t::GetAcodecOption( sout_stream_t
*p_stream
, vlc_fourcc_t
*p_codec_audio
,
1156 const audio_format_t
*p_aud
, int i_quality
)
1158 std::stringstream ssout
;
1162 /* If we were already transcoding: force mp3 because maybe the CC may
1163 * have failed because of vorbis. */
1164 if( transcoding_state
& TRANSCODING_AUDIO
)
1168 switch ( i_quality
)
1170 case CONVERSION_QUALITY_HIGH
:
1171 case CONVERSION_QUALITY_MEDIUM
:
1172 b_audio_mp3
= false;
1181 && p_aud
->i_channels
> 2 && module_exists( "vorbis" ) )
1182 *p_codec_audio
= VLC_CODEC_VORBIS
;
1184 *p_codec_audio
= VLC_CODEC_MP3
;
1186 msg_Dbg( p_stream
, "Converting audio to %.4s", (const char*)p_codec_audio
);
1190 vlc_fourcc_to_char( *p_codec_audio
, fourcc
);
1192 ssout
<< fourcc
<< ',';
1194 /* XXX: higher vorbis qualities can cause glitches on some CC
1195 * devices (Chromecast 1 & 2) */
1196 if( *p_codec_audio
== VLC_CODEC_VORBIS
)
1197 ssout
<< "aenc=vorbis{quality=4},";
1201 bool sout_stream_sys_t::UpdateOutput( sout_stream_t
*p_stream
)
1203 assert( p_stream
->p_sys
== this );
1210 bool canRemux
= true;
1211 vlc_fourcc_t i_codec_video
= 0, i_codec_audio
= 0;
1212 const es_format_t
*p_original_audio
= NULL
;
1213 const es_format_t
*p_original_video
= NULL
;
1214 const es_format_t
*p_original_spu
= NULL
;
1215 bool b_out_streams_changed
= false;
1216 std::vector
<sout_stream_id_sys_t
*> new_streams
;
1218 for (std::vector
<sout_stream_id_sys_t
*>::iterator it
= streams
.begin(); it
!= streams
.end(); ++it
)
1220 const es_format_t
*p_es
= &(*it
)->fmt
;
1221 if (p_es
->i_cat
== AUDIO_ES
&& p_original_audio
== NULL
)
1223 if ( !canDecodeAudio( p_stream
, p_es
->i_codec
, &p_es
->audio
) )
1225 msg_Dbg( p_stream
, "can't remux audio track %d codec %4.4s", p_es
->i_id
, (const char*)&p_es
->i_codec
);
1228 else if (i_codec_audio
== 0)
1229 i_codec_audio
= p_es
->i_codec
;
1230 p_original_audio
= p_es
;
1231 new_streams
.push_back(*it
);
1233 else if (b_supports_video
)
1235 if (p_es
->i_cat
== VIDEO_ES
&& p_original_video
== NULL
)
1237 if (!canDecodeVideo( p_es
->i_codec
))
1239 msg_Dbg( p_stream
, "can't remux video track %d codec %4.4s",
1240 p_es
->i_id
, (const char*)&p_es
->i_codec
);
1243 else if (i_codec_video
== 0 && !p_original_spu
)
1244 i_codec_video
= p_es
->i_codec
;
1245 p_original_video
= p_es
;
1246 new_streams
.push_back(*it
);
1248 #ifdef CC_ENABLE_SPU
1249 else if (p_es
->i_cat
== SPU_ES
&& p_original_spu
== NULL
)
1251 msg_Dbg( p_stream
, "forcing video transcode because of subtitle '%4.4s'",
1252 (const char*)&p_es
->i_codec
);
1255 p_original_spu
= p_es
;
1256 new_streams
.push_back(*it
);
1265 bool b_found
= out_force_reload
;
1266 for (std::vector
<sout_stream_id_sys_t
*>::iterator out_it
= out_streams
.begin();
1267 out_it
!= out_streams
.end() && !b_found
; ++out_it
)
1273 b_out_streams_changed
= true;
1276 if (new_streams
.empty())
1278 p_intf
->requestPlayerStop();
1282 /* Don't restart sout and CC session if streams didn't change */
1283 if (!out_force_reload
&& new_streams
.size() == out_streams
.size() && !b_out_streams_changed
)
1286 out_force_reload
= false;
1288 std::stringstream ssout
;
1289 int new_transcoding_state
= TRANSCODING_NONE
;
1292 if ( !perf_warning_shown
&& i_codec_video
== 0 && p_original_video
1293 && var_InheritInteger( p_stream
, SOUT_CFG_PREFIX
"show-perf-warning" ) )
1295 int res
= vlc_dialog_wait_question( p_stream
,
1296 VLC_DIALOG_QUESTION_WARNING
,
1297 _("Cancel"), _("OK"), _("Ok, Don't warn me again"),
1298 _("Performance warning"),
1299 _("Casting this video requires conversion. "
1300 "This conversion can use all the available power and "
1301 "could quickly drain your battery." ) );
1304 perf_warning_shown
= true;
1306 config_PutInt(SOUT_CFG_PREFIX
"show-perf-warning", 0 );
1309 const int i_quality
= var_InheritInteger( p_stream
, SOUT_CFG_PREFIX
"conversion-quality" );
1311 /* TODO: provide audio samplerate and channels */
1312 ssout
<< "transcode{";
1313 if ( i_codec_audio
== 0 && p_original_audio
)
1315 ssout
<< GetAcodecOption( p_stream
, &i_codec_audio
,
1316 &p_original_audio
->audio
, i_quality
);
1317 new_transcoding_state
|= TRANSCODING_AUDIO
;
1319 if ( i_codec_video
== 0 && p_original_video
)
1321 ssout
<< GetVcodecOption( p_stream
, &i_codec_video
,
1322 &p_original_video
->video
, i_quality
);
1323 new_transcoding_state
|= TRANSCODING_VIDEO
;
1325 if ( p_original_spu
)
1326 ssout
<< "soverlay,";
1330 const bool is_webm
= ( i_codec_audio
== 0 || i_codec_audio
== VLC_CODEC_VORBIS
||
1331 i_codec_audio
== VLC_CODEC_OPUS
) &&
1332 ( i_codec_video
== 0 || i_codec_video
== VLC_CODEC_VP8
||
1333 i_codec_video
== VLC_CODEC_VP9
);
1335 if ( !p_original_video
)
1338 mime
= "audio/webm";
1340 mime
= "audio/x-matroska";
1345 mime
= "video/webm";
1347 mime
= "video/x-matroska";
1350 ssout
<< "chromecast-proxy:"
1351 << "std{mux=" << ( is_webm
? DEFAULT_MUXER_WEBM
: DEFAULT_MUXER
)
1352 << ",access=chromecast-http}";
1354 if ( !startSoutChain( p_stream
, new_streams
, ssout
.str(),
1355 new_transcoding_state
) )
1356 p_intf
->requestPlayerStop();
1360 sout_stream_id_sys_t
*sout_stream_sys_t::GetSubId( sout_stream_t
*p_stream
,
1361 sout_stream_id_sys_t
*id
,
1366 assert( p_stream
->p_sys
== this );
1368 if ( update
&& UpdateOutput( p_stream
) == false )
1371 for (i
= 0; i
< out_streams
.size(); ++i
)
1373 if ( id
== (sout_stream_id_sys_t
*) out_streams
[i
] )
1374 return out_streams
[i
]->p_sub_id
;
1380 bool sout_stream_sys_t::isFlushing( sout_stream_t
*p_stream
)
1384 /* Make sure that all out_streams are flushed when flushing. This avoids
1385 * too many sout/cc restart when a stream is sending data while one other
1391 for (size_t i
= 0; i
< out_streams
.size(); ++i
)
1393 if ( !out_streams
[i
]->flushed
)
1397 cc_flushing
= false;
1398 for (size_t i
= 0; i
< out_streams
.size(); ++i
)
1399 out_streams
[i
]->flushed
= false;
1404 static int Send(sout_stream_t
*p_stream
, void *_id
, block_t
*p_buffer
)
1406 sout_stream_sys_t
*p_sys
= reinterpret_cast<sout_stream_sys_t
*>( p_stream
->p_sys
);
1407 sout_stream_id_sys_t
*id
= reinterpret_cast<sout_stream_id_sys_t
*>( _id
);
1408 vlc_mutex_locker
locker(&p_sys
->lock
);
1410 if( p_sys
->isFlushing( p_stream
) )
1412 block_Release( p_buffer
);
1416 sout_stream_id_sys_t
*next_id
= p_sys
->GetSubId( p_stream
, id
);
1417 if ( next_id
== NULL
)
1419 block_Release( p_buffer
);
1420 return VLC_EGENERIC
;
1423 int ret
= sout_StreamIdSend(p_sys
->p_out
, next_id
, p_buffer
);
1424 if (ret
!= VLC_SUCCESS
)
1425 DelInternal(p_stream
, id
, false);
1430 static void Flush( sout_stream_t
*p_stream
, void *_id
)
1432 sout_stream_sys_t
*p_sys
= reinterpret_cast<sout_stream_sys_t
*>( p_stream
->p_sys
);
1433 sout_stream_id_sys_t
*id
= reinterpret_cast<sout_stream_id_sys_t
*>( _id
);
1434 vlc_mutex_locker
locker(&p_sys
->lock
);
1436 sout_stream_id_sys_t
*next_id
= p_sys
->GetSubId( p_stream
, id
, false );
1437 if ( next_id
== NULL
)
1439 next_id
->flushed
= true;
1441 if( !p_sys
->cc_flushing
)
1443 p_sys
->cc_flushing
= true;
1445 p_sys
->stopSoutChain( p_stream
);
1447 p_sys
->access_out_live
.stop();
1449 if (p_sys
->cc_has_input
)
1451 p_sys
->p_intf
->requestPlayerStop();
1452 p_sys
->cc_has_input
= false;
1454 p_sys
->out_force_reload
= p_sys
->es_changed
= true;
1458 static void on_input_event_cb(void *data
, enum cc_input_event event
, union cc_input_arg arg
)
1460 sout_stream_t
*p_stream
= reinterpret_cast<sout_stream_t
*>(data
);
1461 sout_stream_sys_t
*p_sys
= reinterpret_cast<sout_stream_sys_t
*>( p_stream
->p_sys
);
1463 vlc_mutex_locker
locker(&p_sys
->lock
);
1466 case CC_INPUT_EVENT_EOF
:
1467 /* In case of EOF: stop the sout chain in order to drain all
1468 * sout/demuxers/access. If EOF changes to false, reset es_changed
1469 * in order to reload the sout from next Send calls. */
1471 p_sys
->stopSoutChain( p_stream
);
1473 p_sys
->out_force_reload
= p_sys
->es_changed
= true;
1475 case CC_INPUT_EVENT_RETRY
:
1476 p_sys
->stopSoutChain( p_stream
);
1477 if( p_sys
->transcodingCanFallback() )
1479 p_sys
->setNextTranscodingState();
1480 msg_Warn(p_stream
, "Load failed detected. Switching to next "
1481 "configuration. Transcoding video%s",
1482 p_sys
->transcoding_state
& TRANSCODING_AUDIO
? "/audio" : "");
1483 p_sys
->out_force_reload
= p_sys
->es_changed
= true;
1489 /*****************************************************************************
1490 * Open: connect to the Chromecast and initialize the sout
1491 *****************************************************************************/
1492 static int Open(vlc_object_t
*p_this
)
1494 sout_stream_t
*p_stream
= reinterpret_cast<sout_stream_t
*>(p_this
);
1495 sout_stream_sys_t
*p_sys
= NULL
;
1496 intf_sys_t
*p_intf
= NULL
;
1497 char *psz_ip
= NULL
;
1498 sout_stream_t
*p_sout
= NULL
;
1499 httpd_host_t
*httpd_host
= NULL
;
1500 bool b_supports_video
= true;
1501 int i_local_server_port
;
1503 std::stringstream ss
;
1505 config_ChainParse(p_stream
, SOUT_CFG_PREFIX
, ppsz_sout_options
, p_stream
->p_cfg
);
1507 psz_ip
= var_GetNonEmptyString( p_stream
, SOUT_CFG_PREFIX
"ip");
1508 if ( psz_ip
== NULL
)
1510 msg_Err( p_this
, "missing Chromecast IP address" );
1514 i_device_port
= var_InheritInteger(p_stream
, SOUT_CFG_PREFIX
"port");
1515 i_local_server_port
= var_InheritInteger(p_stream
, SOUT_CFG_PREFIX
"http-port");
1517 var_Create(p_stream
, "http-port", VLC_VAR_INTEGER
);
1518 var_SetInteger(p_stream
, "http-port", i_local_server_port
);
1519 var_Create(p_stream
, "http-host", VLC_VAR_STRING
);
1520 var_SetString(p_stream
, "http-host", "");
1521 httpd_host
= vlc_http_HostNew(VLC_OBJECT(p_stream
));
1522 if (httpd_host
== NULL
)
1527 p_intf
= new intf_sys_t( p_this
, i_local_server_port
, psz_ip
, i_device_port
,
1530 catch (const std::runtime_error
& err
)
1532 msg_Err( p_this
, "cannot load the Chromecast controller (%s)", err
.what() );
1535 catch (const std::bad_alloc
& )
1541 /* check if we can open the proper sout */
1542 ss
<< "http{mux=" << DEFAULT_MUXER
<< "}";
1543 p_sout
= sout_StreamChainNew( p_stream
->p_sout
, ss
.str().c_str(), NULL
, NULL
);
1544 if (p_sout
== NULL
) {
1545 msg_Dbg(p_stream
, "could not create sout chain:%s", ss
.str().c_str());
1548 sout_StreamChainDelete( p_sout
, NULL
);
1550 b_supports_video
= var_GetBool(p_stream
, SOUT_CFG_PREFIX
"video");
1554 p_sys
= new sout_stream_sys_t( httpd_host
, p_intf
, b_supports_video
,
1555 i_local_server_port
);
1557 catch ( std::exception
& ex
)
1559 msg_Err( p_stream
, "Failed to instantiate sout_stream_sys_t: %s", ex
.what() );
1564 p_intf
->setOnInputEventCb(on_input_event_cb
, p_stream
);
1566 /* prevent sout-mux-caching since chromecast-proxy is already doing it */
1567 var_Create( p_stream
->p_sout
, "sout-mux-caching", VLC_VAR_INTEGER
);
1568 var_SetInteger( p_stream
->p_sout
, "sout-mux-caching", 0 );
1570 var_Create( p_stream
->p_sout
, SOUT_CFG_PREFIX
"sys", VLC_VAR_ADDRESS
);
1571 var_SetAddress( p_stream
->p_sout
, SOUT_CFG_PREFIX
"sys", p_sys
);
1573 var_Create( p_stream
->p_sout
, SOUT_CFG_PREFIX
"access-out-sys", VLC_VAR_ADDRESS
);
1575 // Set the sout callbacks.
1576 p_stream
->pf_add
= Add
;
1577 p_stream
->pf_del
= Del
;
1578 p_stream
->pf_send
= Send
;
1579 p_stream
->pf_flush
= Flush
;
1581 p_stream
->p_sys
= p_sys
;
1590 httpd_HostDelete(httpd_host
);
1593 return VLC_EGENERIC
;
1596 /*****************************************************************************
1597 * Close: destroy interface
1598 *****************************************************************************/
1599 static void Close(vlc_object_t
*p_this
)
1601 sout_stream_t
*p_stream
= reinterpret_cast<sout_stream_t
*>(p_this
);
1602 sout_stream_sys_t
*p_sys
= reinterpret_cast<sout_stream_sys_t
*>( p_stream
->p_sys
);
1604 assert(p_sys
->out_streams
.empty() && p_sys
->streams
.empty());
1605 var_Destroy( p_stream
->p_sout
, SOUT_CFG_PREFIX
"sys" );
1606 var_Destroy( p_stream
->p_sout
, SOUT_CFG_PREFIX
"sout-mux-caching" );
1608 assert(p_sys
->streams
.empty() && p_sys
->out_streams
.empty());
1610 httpd_host_t
*httpd_host
= p_sys
->httpd_host
;
1611 delete p_sys
->p_intf
;
1613 /* Delete last since p_intf and p_sys depends on httpd_host */
1614 httpd_HostDelete(httpd_host
);