cast.cpp 38.5 KB
Newer Older
1
/*****************************************************************************
2
 * cast.cpp: Chromecast sout module for vlc
3
 *****************************************************************************
4
 * Copyright © 2014-2015 VideoLAN
5 6 7
 *
 * Authors: Adrien Maglo <magsoft@videolan.org>
 *          Jean-Baptiste Kempf <jb@videolan.org>
8
 *          Steve Lhomme <robux4@videolabs.io>
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33
#include "chromecast.h"
34
#include <vlc_dialog.h>
35

36
#include <vlc_sout.h>
37
#include <vlc_block.h>
38
#include <vlc_modules.h>
39
#include <vlc_httpd.h>
40

41 42
#include <cassert>

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
struct sout_access_out_sys_t
{
    httpd_url_t       *m_url;
    vlc_fifo_t        *m_fifo;
    block_t           *m_header;
    bool               m_eof;
    std::string        m_mime;

    sout_access_out_sys_t(httpd_host_t *httpd_host, const char *psz_url);
    ~sout_access_out_sys_t();

    void clearUnlocked();
    void clear();
    void prepare(sout_stream_t *p_stream, const std::string &mime);
    int url_cb(httpd_client_t *cl, httpd_message_t *answer, const httpd_message_t *query);
};

60 61
struct sout_stream_sys_t
{
62
    sout_stream_sys_t(httpd_host_t *httpd_host, intf_sys_t * const intf, bool has_video, int port,
63
                      const char *psz_default_muxer, const char *psz_default_mime)
64
        : httpd_host(httpd_host)
65
        , access_out_live(httpd_host, "/stream")
66
        , p_out(NULL)
67 68
        , default_muxer(psz_default_muxer)
        , default_mime(psz_default_mime)
69
        , mime(psz_default_mime)
70
        , p_intf(intf)
71
        , b_supports_video(has_video)
72 73
        , i_port(port)
        , es_changed( true )
74 75
        , cc_has_input( false )
        , out_force_reload( false )
76
        , drained( false )
77
        , out_streams_added( 0 )
78
        , transcode_attempt_idx( 0 )
79
        , previous_state( Authenticating )
80
    {
81
        assert(p_intf != NULL);
82

83
    }
84

85 86 87
    ~sout_stream_sys_t()
    {
    }
88

89
    bool canDecodeVideo( vlc_fourcc_t i_codec ) const;
90
    bool canDecodeAudio( sout_stream_t* p_stream, vlc_fourcc_t i_codec,
91
                         const audio_format_t* p_fmt ) const;
92
    bool startSoutChain(sout_stream_t* p_stream,
93 94
                        const std::vector<sout_stream_id_sys_t*> &new_streams,
                        const std::string &sout);
95
    void stopSoutChain(sout_stream_t* p_stream);
96
    int  handleChromecastState(sout_stream_t* p_stream);
97 98

    httpd_host_t      *httpd_host;
99
    sout_access_out_sys_t access_out_live;
100 101 102 103

    sout_stream_t     *p_out;
    const std::string  default_muxer;
    const std::string  default_mime;
104
    std::string        mime;
105

106
    intf_sys_t * const p_intf;
107
    const bool b_supports_video;
108
    const int i_port;
109 110 111

    sout_stream_id_sys_t *GetSubId( sout_stream_t*, sout_stream_id_sys_t* );

112
    bool                               es_changed;
113
    bool                               cc_has_input;
114
    bool                               out_force_reload;
115
    bool                               drained;
116
    std::vector<sout_stream_id_sys_t*> streams;
117
    std::vector<sout_stream_id_sys_t*> out_streams;
118
    unsigned int                       out_streams_added;
119
    unsigned int                       transcode_attempt_idx;
120
    States                             previous_state;
121 122

private:
123
    bool UpdateOutput( sout_stream_t * );
124 125
};

126 127 128 129 130 131
struct sout_stream_id_sys_t
{
    es_format_t           fmt;
    sout_stream_id_sys_t  *p_sub_id;
};

132 133
#define SOUT_CFG_PREFIX "sout-chromecast-"

134
static const vlc_fourcc_t DEFAULT_TRANSCODE_VIDEO = VLC_CODEC_H264;
135
static const unsigned int MAX_TRANSCODE_PASS = 3;
Thomas Guillem's avatar
Thomas Guillem committed
136
static const char DEFAULT_MUXER[] = "avformat{mux=matroska,options={live=1}}";
137 138


139 140 141 142 143
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
static int Open(vlc_object_t *);
static void Close(vlc_object_t *);
144
static int ProxyOpen(vlc_object_t *);
145 146
static int AccessOpen(vlc_object_t *);
static void AccessClose(vlc_object_t *);
147 148

static const char *const ppsz_sout_options[] = {
149
    "ip", "port",  "http-port", "mux", "mime", "video", NULL
150 151 152 153 154 155 156
};

/*****************************************************************************
 * Module descriptor
 *****************************************************************************/

#define HTTP_PORT_TEXT N_("HTTP port")
157
#define HTTP_PORT_LONGTEXT N_("This sets the HTTP port of the local server " \
158
                              "used to stream the media to the Chromecast.")
159 160
#define HAS_VIDEO_TEXT N_("Video")
#define HAS_VIDEO_LONGTEXT N_("The Chromecast receiver can receive video.")
161 162 163 164
#define MUX_TEXT N_("Muxer")
#define MUX_LONGTEXT N_("This sets the muxer used to stream to the Chromecast.")
#define MIME_TEXT N_("MIME content type")
#define MIME_LONGTEXT N_("This sets the media MIME content type sent to the Chromecast.")
165 166
#define PERF_TEXT N_( "Performance warning" )
#define PERF_LONGTEXT N_( "Display a performance warning when transcoding" )
167
#define AUDIO_PASSTHROUGH_TEXT N_( "Enable Audio passthrough" )
168
#define AUDIO_PASSTHROUGH_LONGTEXT N_( "Disable if your receiver does not support Dolby®." )
169

170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
enum {
    CONVERSION_QUALITY_HIGH = 0,
    CONVERSION_QUALITY_MEDIUM = 1,
    CONVERSION_QUALITY_LOW = 2,
    CONVERSION_QUALITY_LOWCPU = 3,
};

#if defined (__ANDROID__) || defined (__arm__) || (defined (TARGET_OS_IPHONE) && TARGET_OS_IPHONE)
# define CONVERSION_QUALITY_DEFAULT CONVERSION_QUALITY_LOW
#else
# define CONVERSION_QUALITY_DEFAULT CONVERSION_QUALITY_MEDIUM
#endif

static const int conversion_quality_list[] = {
    CONVERSION_QUALITY_HIGH,
    CONVERSION_QUALITY_MEDIUM,
    CONVERSION_QUALITY_LOW,
    CONVERSION_QUALITY_LOWCPU,
};
static const char *const conversion_quality_list_text[] = {
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
190
    N_( "High (high quality and high bandwidth)" ),
191
    N_( "Medium (medium quality and medium bandwidth)" ),
Jean-Baptiste Kempf's avatar
Jean-Baptiste Kempf committed
192 193
    N_( "Low (low quality and low bandwidth)" ),
    N_( "Low CPU (low quality but high bandwidth)" ),
194 195 196 197 198
};

#define CONVERSION_QUALITY_TEXT N_( "Conversion quality" )
#define CONVERSION_QUALITY_LONGTEXT N_( "Change this option to increase conversion speed or quality." )

199 200 201 202 203
#define IP_ADDR_TEXT N_("IP Address")
#define IP_ADDR_LONGTEXT N_("IP Address of the Chromecast.")
#define PORT_TEXT N_("Chromecast port")
#define PORT_LONGTEXT N_("The port used to talk to the Chromecast.")

204 205
#define HTTPD_BUFFER_MAX INT64_C(32 * 1024 * 1024) /* 32 MB */

206 207 208 209 210 211 212 213 214 215
vlc_module_begin ()

    set_shortname(N_("Chromecast"))
    set_description(N_("Chromecast stream output"))
    set_capability("sout stream", 0)
    add_shortcut("chromecast")
    set_category(CAT_SOUT)
    set_subcategory(SUBCAT_SOUT_STREAM)
    set_callbacks(Open, Close)

216
    add_string(SOUT_CFG_PREFIX "ip", NULL, IP_ADDR_TEXT, IP_ADDR_LONGTEXT, false)
217
        change_private()
218
    add_integer(SOUT_CFG_PREFIX "port", CHROMECAST_CONTROL_PORT, PORT_TEXT, PORT_LONGTEXT, false)
219
        change_private()
220
    add_integer(SOUT_CFG_PREFIX "http-port", HTTP_PORT, HTTP_PORT_TEXT, HTTP_PORT_LONGTEXT, false)
221
    add_bool(SOUT_CFG_PREFIX "video", true, HAS_VIDEO_TEXT, HAS_VIDEO_LONGTEXT, false)
222 223
    add_string(SOUT_CFG_PREFIX "mux", DEFAULT_MUXER, MUX_TEXT, MUX_LONGTEXT, false)
    add_string(SOUT_CFG_PREFIX "mime", "video/x-matroska", MIME_TEXT, MIME_LONGTEXT, false)
224
    add_integer(SOUT_CFG_PREFIX "show-perf-warning", 1, PERF_TEXT, PERF_LONGTEXT, true )
225
        change_private()
226
    add_bool(SOUT_CFG_PREFIX "audio-passthrough", false, AUDIO_PASSTHROUGH_TEXT, AUDIO_PASSTHROUGH_LONGTEXT, false )
227 228 229
    add_integer(SOUT_CFG_PREFIX "conversion-quality", CONVERSION_QUALITY_DEFAULT,
                CONVERSION_QUALITY_TEXT, CONVERSION_QUALITY_LONGTEXT, false );
        change_integer_list(conversion_quality_list, conversion_quality_list_text)
230

231 232 233 234 235
    add_submodule()
        /* sout proxy that start the cc input when all streams are loaded */
        add_shortcut("chromecast-proxy")
        set_capability("sout stream", 0)
        set_callbacks(ProxyOpen, NULL)
236 237 238 239 240
    add_submodule()
        set_subcategory(SUBCAT_SOUT_ACO)
        add_shortcut("chromecast-http")
        set_capability("sout access", 0)
        set_callbacks(AccessOpen, AccessClose)
241 242
vlc_module_end ()

243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
static sout_stream_id_sys_t *ProxyAdd(sout_stream_t *p_stream, const es_format_t *p_fmt)
{
    sout_stream_sys_t *p_sys = p_stream->p_sys;
    sout_stream_id_sys_t *id = sout_StreamIdAdd(p_stream->p_next, p_fmt);
    if (id)
        p_sys->out_streams_added++;
    return id;
}

static void ProxyDel(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
{
    sout_stream_sys_t *p_sys = p_stream->p_sys;
    p_sys->out_streams_added--;
    return sout_StreamIdDel(p_stream->p_next, id);
}

static int ProxySend(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
                     block_t *p_buffer)
{
    sout_stream_sys_t *p_sys = p_stream->p_sys;
    if (p_sys->cc_has_input || p_sys->out_streams_added >= p_sys->out_streams.size())
    {
        int ret = sout_StreamIdSend(p_stream->p_next, id, p_buffer);
        if (ret == VLC_SUCCESS && !p_sys->cc_has_input)
        {
            /* Start the chromecast only when all streams are added into the
             * last sout (the http one) */
            p_sys->p_intf->setHasInput(p_sys->mime);
            p_sys->cc_has_input = true;
        }
        return ret;
    }
    else
    {
        block_Release(p_buffer);
        return VLC_SUCCESS;
    }
}

static void ProxyFlush(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
{
    return sout_StreamFlush(p_stream->p_next, id);
}

static int ProxyControl(sout_stream_t *p_stream, int i_query, va_list args)
{
289
    return sout_StreamControlVa( p_stream->p_next, i_query, args );
290 291 292 293 294
}

static int ProxyOpen(vlc_object_t *p_this)
{
    sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
295 296
    sout_stream_sys_t *p_sys = (sout_stream_sys_t *) var_InheritAddress(p_this, SOUT_CFG_PREFIX "sys");
    if (p_sys == NULL || p_stream->p_next == NULL)
297 298
        return VLC_EGENERIC;

299 300 301
    p_stream->p_sys = (sout_stream_sys_t *) p_sys;
    p_sys->out_streams_added = 0;

302 303 304 305 306 307 308 309
    p_stream->pf_add     = ProxyAdd;
    p_stream->pf_del     = ProxyDel;
    p_stream->pf_send    = ProxySend;
    p_stream->pf_flush   = ProxyFlush;
    p_stream->pf_control = ProxyControl;
    return VLC_SUCCESS;
}

310 311 312
static int httpd_url_cb(httpd_callback_sys_t *data, httpd_client_t *cl,
                        httpd_message_t *answer, const httpd_message_t *query)
{
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
    sout_access_out_sys_t *p_sys = (sout_access_out_sys_t *) data;
    return p_sys->url_cb(cl, answer, query);
}

sout_access_out_sys_t::sout_access_out_sys_t(httpd_host_t *httpd_host, const char *psz_url)
    : m_header(NULL)
    , m_eof(true)
{
    m_fifo = block_FifoNew();
    if (!m_fifo)
        throw std::runtime_error( "block_FifoNew failed" );
    m_url = httpd_UrlNew(httpd_host, psz_url, NULL, NULL);
    if (m_url == NULL)
    {
        block_FifoRelease(m_fifo);
        throw std::runtime_error( "httpd_UrlNew failed" );
    }
    httpd_UrlCatch(m_url, HTTPD_MSG_GET, httpd_url_cb,
                   (httpd_callback_sys_t*)this);

}

sout_access_out_sys_t::~sout_access_out_sys_t()
{
    block_FifoRelease(m_fifo);
    httpd_UrlDelete(m_url);
}

void sout_access_out_sys_t::clearUnlocked()
{
    block_ChainRelease(vlc_fifo_DequeueAllUnlocked(m_fifo));
    if (m_header)
    {
        block_Release(m_header);
        m_header = NULL;
    }
    m_eof = true;
}

void sout_access_out_sys_t::clear()
{
    vlc_fifo_Lock(m_fifo);
    clearUnlocked();
    vlc_fifo_Unlock(m_fifo);
    vlc_fifo_Signal(m_fifo);
}
359

360 361 362 363 364 365 366 367 368 369 370 371 372 373
void sout_access_out_sys_t::prepare(sout_stream_t *p_stream, const std::string &mime)
{
    var_SetAddress(p_stream->p_sout, SOUT_CFG_PREFIX "access-out-sys", this);

    vlc_fifo_Lock(m_fifo);
    clearUnlocked();
    m_mime = mime;
    m_eof = false;
    vlc_fifo_Unlock(m_fifo);
}

int sout_access_out_sys_t::url_cb(httpd_client_t *cl, httpd_message_t *answer,
                                  const httpd_message_t *query)
{
374 375 376
    if (!answer || !query || !cl)
        return VLC_SUCCESS;

377
    vlc_fifo_Lock(m_fifo);
378 379

    block_t *p_block = NULL;
380 381 382
    while ((p_block = vlc_fifo_DequeueUnlocked(m_fifo)) == NULL
       && !m_eof)
        vlc_fifo_Wait(m_fifo);
383 384 385 386 387 388

    /* Handle block headers */
    if (p_block)
    {
        if (p_block->i_flags & BLOCK_FLAG_HEADER)
        {
389 390 391
            if (m_header)
                block_Release(m_header);
            m_header = p_block;
392 393 394
        }
        if (answer->i_body_offset == 0)
        {
395
            if (m_header != NULL && p_block != m_header)
396 397
            {
                /* Using header block. Re-insert current block into fifo */
398 399 400 401
                block_t *p_fifo = vlc_fifo_DequeueAllUnlocked(m_fifo);
                vlc_fifo_QueueUnlocked(m_fifo, p_block);
                vlc_fifo_QueueUnlocked(m_fifo, p_fifo);
                p_block = m_header;
402 403 404
            }
        }
    }
405
    vlc_fifo_Unlock(m_fifo);
406 407 408 409 410 411 412 413 414 415 416

    answer->i_proto  = HTTPD_PROTO_HTTP;
    answer->i_version= 0;
    answer->i_type   = HTTPD_MSG_ANSWER;
    answer->i_status = 200;

    bool b_close = false;
    if (p_block)
    {
        if (answer->i_body_offset == 0)
        {
417
            httpd_MsgAdd(answer, "Content-type", "%s", m_mime.c_str());
418 419 420 421 422 423 424 425 426 427 428 429 430
            httpd_MsgAdd(answer, "Cache-Control", "no-cache");
            b_close = true;
        }
        answer->p_body = (uint8_t *) malloc(p_block->i_buffer);
        if (answer->p_body)
        {
            answer->i_body = p_block->i_buffer;
            answer->i_body_offset += answer->i_body ;
            memcpy(answer->p_body, p_block->p_buffer, p_block->i_buffer);
        }
        else
            b_close = true;

431
        if (p_block != m_header)
432 433 434 435 436 437 438 439 440 441 442
            block_Release(p_block);
    }
    else
        b_close = true;

    if (b_close)
        httpd_MsgAdd(answer, "Connection", "close");

    return VLC_SUCCESS;
}

443

444 445
static ssize_t AccessWrite(sout_access_out_t *p_access, block_t *p_block)
{
446
    sout_access_out_sys_t *p_sys = p_access->p_sys;
447 448
    size_t i_len = p_block->i_buffer;

449
    vlc_fifo_Lock(p_sys->m_fifo);
450

451
    while (vlc_fifo_GetBytes(p_sys->m_fifo) >= HTTPD_BUFFER_MAX)
452
    {
453
        block_t *p_drop = vlc_fifo_DequeueUnlocked(p_sys->m_fifo);
454 455 456
        msg_Warn(p_access, "httpd buffer full: dropping %zuB", p_drop->i_buffer);
        block_Release(p_drop);
    }
457
    vlc_fifo_QueueUnlocked(p_sys->m_fifo, p_block);
458

459 460
    vlc_fifo_Unlock(p_sys->m_fifo);
    vlc_fifo_Signal(p_sys->m_fifo);
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483

    return i_len;
}

static int AccessControl(sout_access_out_t *p_access, int i_query, va_list args)
{
    (void) p_access;

    switch (i_query)
    {
        case ACCESS_OUT_CONTROLS_PACE:
            *va_arg(args, bool *) = false;
            break;
        default:
            return VLC_EGENERIC;
    }
    return VLC_SUCCESS;
}

static int AccessOpen(vlc_object_t *p_this)
{
    sout_access_out_t *p_access = (sout_access_out_t*)p_this;

484 485
    sout_access_out_sys_t *p_sys = (sout_access_out_sys_t *)
        var_InheritAddress(p_access, SOUT_CFG_PREFIX "access-out-sys");
486 487 488
    if (p_sys == NULL)
        return VLC_EGENERIC;

489
    assert(!p_sys->m_eof);
490 491 492

    p_access->pf_write       = AccessWrite;
    p_access->pf_control     = AccessControl;
493
    p_access->p_sys          = p_sys;
494 495 496 497 498 499 500

    return VLC_SUCCESS;
}

static void AccessClose(vlc_object_t *p_this)
{
    sout_access_out_t *p_access = (sout_access_out_t*)p_this;
501
    sout_access_out_sys_t *p_sys = p_access->p_sys;
502

503 504 505 506
    vlc_fifo_Lock(p_sys->m_fifo);
    p_sys->m_eof = true;
    vlc_fifo_Unlock(p_sys->m_fifo);
    vlc_fifo_Signal(p_sys->m_fifo);
507 508
}

509 510 511
/*****************************************************************************
 * Sout callbacks
 *****************************************************************************/
512
static sout_stream_id_sys_t *Add(sout_stream_t *p_stream, const es_format_t *p_fmt)
513 514
{
    sout_stream_sys_t *p_sys = p_stream->p_sys;
515

516
    if (!p_sys->b_supports_video)
517 518 519 520
    {
        if (p_fmt->i_cat != AUDIO_ES)
            return NULL;
    }
521 522 523 524 525 526 527 528

    sout_stream_id_sys_t *p_sys_id = (sout_stream_id_sys_t *)malloc( sizeof(sout_stream_id_sys_t) );
    if (p_sys_id != NULL)
    {
        es_format_Copy( &p_sys_id->fmt, p_fmt );
        p_sys_id->p_sub_id = NULL;

        p_sys->streams.push_back( p_sys_id );
529
        p_sys->es_changed = true;
530 531
    }
    return p_sys_id;
532 533 534
}


535 536
static void DelInternal(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
                        bool reset_config)
537 538
{
    sout_stream_sys_t *p_sys = p_stream->p_sys;
539

540 541
    for (std::vector<sout_stream_id_sys_t*>::iterator it = p_sys->streams.begin();
         it != p_sys->streams.end(); )
542
    {
543 544
        sout_stream_id_sys_t *p_sys_id = *it;
        if ( p_sys_id == id )
545
        {
546
            if ( p_sys_id->p_sub_id != NULL )
547
            {
548
                sout_StreamIdDel( p_sys->p_out, p_sys_id->p_sub_id );
549 550 551 552 553 554
                for (std::vector<sout_stream_id_sys_t*>::iterator out_it = p_sys->out_streams.begin();
                     out_it != p_sys->out_streams.end(); )
                {
                    if (*out_it == id)
                    {
                        p_sys->out_streams.erase(out_it);
555 556
                        p_sys->es_changed = reset_config;
                        p_sys->out_force_reload = reset_config;
557 558 559 560 561
                        break;
                    }
                    out_it++;
                }
            }
562

563 564 565
            es_format_Clean( &p_sys_id->fmt );
            free( p_sys_id );
            p_sys->streams.erase( it );
566 567
            break;
        }
568
        it++;
569
    }
570

571
    if ( p_sys->out_streams.empty() )
572
    {
573
        p_sys->stopSoutChain(p_stream);
574
        p_sys->p_intf->requestPlayerStop();
575
        p_sys->access_out_live.clear();
576
        p_sys->transcode_attempt_idx = 0;
577
        p_sys->drained = false;
578 579 580
    }
}

581 582 583 584 585
static void Del(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
{
    DelInternal(p_stream, id, true);
}

586 587 588 589 590 591 592 593
/**
 * Transcode steps:
 * 0: Accept HEVC/VP9 & all supported audio formats
 * 1: Transcode to h264 & accept all supported audio formats if the video codec
 *    was HEVC/VP9
 * 2: Transcode to H264 & MP3
 *
 * Additionally:
594 595 596
 * - Allow (E)AC3 passthrough depending on the audio-passthrough
 *   config value, except for the final step, where we just give up and transcode
 *   everything.
597 598 599 600
 * - Disallow multichannel AAC
 *
 * Supported formats: https://developers.google.com/cast/docs/media
 */
601

602
bool sout_stream_sys_t::canDecodeVideo( vlc_fourcc_t i_codec ) const
603
{
604
    if ( transcode_attempt_idx != 0 )
605 606 607
        return false;
    if ( i_codec == VLC_CODEC_HEVC || i_codec == VLC_CODEC_VP9 )
        return transcode_attempt_idx == 0;
608
    return i_codec == VLC_CODEC_H264 || i_codec == VLC_CODEC_VP8;
609 610
}

611 612
bool sout_stream_sys_t::canDecodeAudio( sout_stream_t *p_stream,
                                        vlc_fourcc_t i_codec,
613
                                        const audio_format_t* p_fmt ) const
614
{
615 616
    if ( transcode_attempt_idx == MAX_TRANSCODE_PASS - 1 )
        return false;
617 618 619 620
    if ( i_codec == VLC_CODEC_A52 || i_codec == VLC_CODEC_EAC3 )
    {
        return var_InheritBool( p_stream, SOUT_CFG_PREFIX "audio-passthrough" );
    }
621 622 623
    if ( i_codec == VLC_FOURCC('h', 'a', 'a', 'c') ||
            i_codec == VLC_FOURCC('l', 'a', 'a', 'c') ||
            i_codec == VLC_FOURCC('s', 'a', 'a', 'c') ||
624
            i_codec == VLC_CODEC_MPGA ||
625 626 627 628 629 630 631 632
            i_codec == VLC_CODEC_MP4A )
    {
        return p_fmt->i_channels <= 2;
    }
    return i_codec == VLC_CODEC_VORBIS || i_codec == VLC_CODEC_OPUS ||
           i_codec == VLC_CODEC_MP3;
}

633
void sout_stream_sys_t::stopSoutChain(sout_stream_t *p_stream)
634
{
635 636
    (void) p_stream;

637
    if ( unlikely( p_out != NULL ) )
638
    {
639
        for ( size_t i = 0; i < out_streams.size(); i++ )
640
        {
641
            if ( out_streams[i]->p_sub_id != NULL )
642
            {
643
                sout_StreamIdDel( p_out, out_streams[i]->p_sub_id );
644 645
                out_streams[i]->p_sub_id = NULL;
            }
646
        }
647
        out_streams.clear();
648
        sout_StreamChainDelete( p_out, NULL );
649
        p_out = NULL;
650
    }
651 652 653
}

bool sout_stream_sys_t::startSoutChain(sout_stream_t *p_stream,
654 655
                                       const std::vector<sout_stream_id_sys_t*> &new_streams,
                                       const std::string &sout)
656 657
{
    stopSoutChain( p_stream );
658 659

    msg_Dbg( p_stream, "Creating chain %s", sout.c_str() );
660
    previous_state = Authenticating;
661
    cc_has_input = false;
662
    out_streams = new_streams;
663

664 665
    access_out_live.prepare( p_stream, mime );

666 667 668
    p_out = sout_StreamChainNew( p_stream->p_sout, sout.c_str(), NULL, NULL);
    if (p_out == NULL) {
        msg_Dbg(p_stream, "could not create sout chain:%s", sout.c_str());
669
        out_streams.clear();
670 671 672 673
        return false;
    }

    /* check the streams we can actually add */
674 675
    for (std::vector<sout_stream_id_sys_t*>::iterator it = out_streams.begin();
         it != out_streams.end(); )
676 677 678 679 680 681
    {
        sout_stream_id_sys_t *p_sys_id = *it;
        p_sys_id->p_sub_id = sout_StreamIdAdd( p_out, &p_sys_id->fmt );
        if ( p_sys_id->p_sub_id == NULL )
        {
            msg_Err( p_stream, "can't handle %4.4s stream", (char *)&p_sys_id->fmt.i_codec );
682
            es_format_Clean( &p_sys_id->fmt );
683
            it = out_streams.erase( it );
684 685 686 687
        }
        else
            ++it;
    }
688
    return out_streams.empty() == false;
689 690
}

691
bool sout_stream_sys_t::UpdateOutput( sout_stream_t *p_stream )
692 693 694
{
    assert( p_stream->p_sys == this );

695
    if ( !es_changed )
696
        return true;
697

698
    es_changed = false;
699

700 701
    bool canRemux = true;
    vlc_fourcc_t i_codec_video = 0, i_codec_audio = 0;
702 703
    const es_format_t *p_original_audio = NULL;
    const es_format_t *p_original_video = NULL;
704 705
    bool b_out_streams_changed = false;
    std::vector<sout_stream_id_sys_t*> new_streams;
706 707 708 709

    for (std::vector<sout_stream_id_sys_t*>::iterator it = streams.begin(); it != streams.end(); ++it)
    {
        const es_format_t *p_es = &(*it)->fmt;
710
        if (p_es->i_cat == AUDIO_ES && p_original_audio == NULL)
711
        {
712
            if ( !canDecodeAudio( p_stream, p_es->i_codec, &p_es->audio ) )
713
            {
714 715
                msg_Dbg( p_stream, "can't remux audio track %d codec %4.4s", p_es->i_id, (const char*)&p_es->i_codec );
                canRemux = false;
716
            }
717 718
            else if (i_codec_audio == 0)
                i_codec_audio = p_es->i_codec;
719
            p_original_audio = p_es;
720
            new_streams.push_back(*it);
721
        }
722
        else if (b_supports_video)
723
        {
724
            if (p_es->i_cat == VIDEO_ES && p_original_video == NULL)
725
            {
726 727 728 729 730 731 732 733 734
                if (!canDecodeVideo( p_es->i_codec ))
                {
                    msg_Dbg( p_stream, "can't remux video track %d codec %4.4s",
                             p_es->i_id, (const char*)&p_es->i_codec );
                    canRemux = false;
                }
                else if (i_codec_video == 0)
                    i_codec_video = p_es->i_codec;
                p_original_video = p_es;
735
                new_streams.push_back(*it);
736
            }
737 738
            else
                continue;
739
            /* TODO: else handle ttml/webvtt */
740
        }
741 742 743
        else
            continue;

744
        bool b_found = out_force_reload;
745 746 747 748 749 750 751 752
        for (std::vector<sout_stream_id_sys_t*>::iterator out_it = out_streams.begin();
             out_it != out_streams.end() && !b_found; ++out_it)
        {
            if (*out_it == *it)
                b_found = true;
        }
        if (!b_found)
            b_out_streams_changed = true;
753
    }
754

755
    if (new_streams.empty())
756 757
    {
        p_intf->requestPlayerStop();
758
        return true;
759
    }
760

761
    /* Don't restart sout and CC session if streams didn't change */
762
    if (!out_force_reload && new_streams.size() == out_streams.size() && !b_out_streams_changed)
763 764
        return true;

765
    out_force_reload = false;
766

767 768 769
    std::stringstream ssout;
    if ( !canRemux )
    {
770 771
        if ( i_codec_video == 0 && p_original_video
          && var_InheritInteger( p_stream, SOUT_CFG_PREFIX "show-perf-warning" ) )
772 773 774 775 776 777 778 779 780 781 782 783 784
        {
            int res = vlc_dialog_wait_question( p_stream,
                          VLC_DIALOG_QUESTION_WARNING,
                         _("Cancel"), _("OK"), _("Ok, Don't warn me again"),
                         _("Performance warning"),
                         _("Casting this video requires conversion. "
                           "This conversion can use all the available power and "
                           "could quickly drain your battery." ) );
            if ( res <= 0 )
                 return false;
            if ( res == 2 )
                config_PutInt(p_stream, SOUT_CFG_PREFIX "show-perf-warning", 0 );
        }
785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826

        static const char video_maxres_hd[] = "maxwidth=1920,maxheight=1080";
        static const char video_maxres_720p[] = "maxwidth=1280,maxheight=720";
        static const char video_x264_preset_veryfast[] = "veryfast";
        static const char video_x264_preset_ultrafast[] = "ultrafast";

        const int i_quality = var_InheritInteger( p_stream, SOUT_CFG_PREFIX "conversion-quality" );
        const char *psz_video_maxres;
        const char *psz_video_x264_preset;
        unsigned i_video_x264_crf_hd, i_video_x264_crf_720p;
        bool b_audio_mp3;

        switch ( i_quality )
        {
            case CONVERSION_QUALITY_HIGH:
                psz_video_maxres = video_maxres_hd;
                i_video_x264_crf_hd = i_video_x264_crf_720p = 21;
                psz_video_x264_preset = video_x264_preset_veryfast;
                b_audio_mp3 = false;
                break;
            case CONVERSION_QUALITY_MEDIUM:
                psz_video_maxres = video_maxres_hd;
                i_video_x264_crf_hd = 23;
                i_video_x264_crf_720p = 21;
                psz_video_x264_preset = video_x264_preset_veryfast;
                b_audio_mp3 = false;
                break;
            case CONVERSION_QUALITY_LOW:
                psz_video_maxres = video_maxres_720p;
                i_video_x264_crf_hd = i_video_x264_crf_720p = 23;
                psz_video_x264_preset = video_x264_preset_veryfast;
                b_audio_mp3 = true;
                break;
            default:
            case CONVERSION_QUALITY_LOWCPU:
                psz_video_maxres = video_maxres_720p;
                i_video_x264_crf_hd = i_video_x264_crf_720p = 23;
                psz_video_x264_preset = video_x264_preset_ultrafast;
                b_audio_mp3 = true;
                break;
        }

827 828 829
        /* TODO: provide audio samplerate and channels */
        ssout << "transcode{";
        char s_fourcc[5];
830
        if ( i_codec_audio == 0 && p_original_audio )
831
        {
832 833
            if ( !b_audio_mp3
              && p_original_audio->audio.i_channels > 2 && module_exists( "vorbis" ) )
834 835 836 837
                i_codec_audio = VLC_CODEC_VORBIS;
            else
                i_codec_audio = VLC_CODEC_MP3;

838 839 840 841 842
            msg_Dbg( p_stream, "Converting audio to %.4s", (const char*)&i_codec_audio );
            ssout << "acodec=";
            vlc_fourcc_to_char( i_codec_audio, s_fourcc );
            s_fourcc[4] = '\0';
            ssout << s_fourcc << ',';
843 844
            if( i_codec_audio == VLC_CODEC_VORBIS )
                ssout << "aenc=vorbis{quality=6},";
845
        }
846
        if ( i_codec_video == 0 && p_original_video )
847 848 849 850 851 852
        {
            i_codec_video = DEFAULT_TRANSCODE_VIDEO;
            msg_Dbg( p_stream, "Converting video to %.4s", (const char*)&i_codec_video );
            ssout << "vcodec=";
            vlc_fourcc_to_char( i_codec_video, s_fourcc );
            s_fourcc[4] = '\0';
853 854
            ssout << s_fourcc << ',' << psz_video_maxres << ',';

855
            const video_format_t *p_vid = &p_original_video->video;
856 857 858
            const bool b_hdres = p_vid == NULL || p_vid->i_height == 0 || p_vid->i_height >= 800;
            unsigned i_video_x264_crf = b_hdres ? i_video_x264_crf_hd : i_video_x264_crf_720p;

859 860 861 862 863 864 865 866 867
            if( p_vid == NULL
             || p_vid->i_frame_rate == 0 || p_vid->i_frame_rate_base == 0
             || ( p_vid->i_frame_rate / p_vid->i_frame_rate_base ) > 30 )
            {
                /* Even force 24fps if the frame rate is unknown */
                msg_Warn( p_stream, "lowering frame rate to 24fps" );
                ssout << "fps=24,";
            }

868 869 870 871 872 873
            if( i_codec_video == VLC_CODEC_H264 )
            {
                if ( module_exists("x264") )
                    ssout << "venc=x264{preset=" << psz_video_x264_preset
                          << ",crf=" << i_video_x264_crf << "},";
            }
874 875 876
        }
        ssout << "}:";
    }
877
    if ( !p_original_video && default_muxer == DEFAULT_MUXER )
878 879 880 881 882 883 884 885
        mime = "audio/x-matroska";
    else if ( i_codec_audio == VLC_CODEC_VORBIS &&
              i_codec_video == VLC_CODEC_VP8 &&
              default_muxer == DEFAULT_MUXER )
        mime = "video/webm";
    else
        mime = default_mime;

886
    ssout << "chromecast-proxy:"
887 888
          << "http{mux=" << default_muxer
          << ",access=chromecast-http";
889

890
    if ( !startSoutChain( p_stream, new_streams, ssout.str() ) )
891 892 893 894
    {
        p_intf->requestPlayerStop();

        sout_StreamChainDelete( p_out, NULL );
895
        access_out_live.clear();
896 897
        p_out = NULL;
    }
898
    return true;
899 900
}

901 902 903 904 905 906 907
sout_stream_id_sys_t *sout_stream_sys_t::GetSubId( sout_stream_t *p_stream,
                                                   sout_stream_id_sys_t *id )
{
    size_t i;

    assert( p_stream->p_sys == this );

908 909
    if ( UpdateOutput( p_stream ) == false )
        return NULL;
910

911
    for (i = 0; i < out_streams.size(); ++i)
912
    {
913 914
        if ( id == (sout_stream_id_sys_t*) out_streams[i] )
            return out_streams[i]->p_sub_id;
915 916 917 918 919
    }

    msg_Err( p_stream, "unknown stream ID" );
    return NULL;
}
920

921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944
int sout_stream_sys_t::handleChromecastState(sout_stream_t* p_stream)
{
    States s = p_intf->state();
    if (cc_has_input && previous_state != s)
    {
        if (!drained && s == LoadFailed && es_changed == false)
        {
            if (transcode_attempt_idx > MAX_TRANSCODE_PASS - 1)
            {
                msg_Err(p_stream, "All attempts failed. Giving up.");
                return VLC_EGENERIC;
            }
            transcode_attempt_idx++;
            es_changed = true;
            stopSoutChain(p_stream);
            msg_Warn(p_stream, "Load failed detected. Switching to next "
                     "configuration index: %u", transcode_attempt_idx);
            return VLC_ETIMEOUT;
        }
        else if (s == Playing || s == Paused)
        {
            msg_Dbg( p_stream, "Playback started: Current configuration (%u) "
                     "accepted", transcode_attempt_idx );
        }
945
        else if (s == Connected || s == TakenOver)
946
        {
947 948
            msg_Warn(p_stream, "chromecast %s, aborting...\n",
                     s == Connected ? "exited" : "was taken over");
949 950 951 952 953 954 955 956
            stopSoutChain(p_stream);
            return VLC_EGENERIC;
        }
        previous_state = s;
    }
    return VLC_SUCCESS;
}

957 958 959 960 961
static int Send(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
                block_t *p_buffer)
{
    sout_stream_sys_t *p_sys = p_stream->p_sys;

962 963
    sout_stream_id_sys_t *next_id = p_sys->GetSubId( p_stream, id );
    if ( next_id == NULL )
964 965
    {
        block_Release( p_buffer );
966
        return VLC_EGENERIC;
967
    }
968 969 970

    int ret = p_sys->handleChromecastState(p_stream);
    switch (ret)
971
    {
972 973 974 975 976 977 978 979 980 981
        case VLC_SUCCESS:
            break;
        case VLC_ETIMEOUT:
            next_id = p_sys->GetSubId( p_stream, id );
            if (next_id != NULL)
                break;
            /* fallthrough */
        default:
            block_Release( p_buffer );
            return VLC_EGENERIC;
982
    }
983

984
    ret = sout_StreamIdSend(p_sys->p_out, next_id, p_buffer);
985
    if (ret != VLC_SUCCESS)
986
        DelInternal(p_stream, id, false);
987 988 989

    p_sys->drained = false;

990
    return ret;
991 992
}

993 994 995 996
static void Flush( sout_stream_t *p_stream, sout_stream_id_sys_t *id )
{
    sout_stream_sys_t *p_sys = p_stream->p_sys;

997 998 999 1000 1001 1002 1003 1004 1005 1006
    if( p_sys->out_streams.empty() && p_sys->drained )
    {
        /* Worst case scenario that won't happen often: we terminated
         * everything since we were draining but the user requested a seek.
         * Change the es_changed value in order to re-create everything again.
         * */

        p_sys->es_changed = true;
    }

1007
    id = p_sys->GetSubId( p_stream, id );
1008
    if ( id == NULL || p_sys->drained )
1009 1010
        return;

1011
    /* a seek on the Chromecast flushes its buffers */
1012
    p_sys->p_intf->requestPlayerSeek( VLC_TS_INVALID );
1013

1014 1015 1016 1017 1018 1019 1020
    sout_StreamFlush( p_sys->p_out, id );
}

static int Control(sout_stream_t *p_stream, int i_query, va_list args)
{
    sout_stream_sys_t *p_sys = p_stream->p_sys;

1021 1022 1023
    if (i_query == SOUT_STREAM_EMPTY)
    {
        bool *b = va_arg( args, bool * );
1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034

        /* Close the whole sout chain. This will drain every streams, and send
         * the last data to the chromecast-http sout_access. This sout_access
         * won't close the http connection but will send an EOF in order to let
         * the CC download the last remaining data. */
        if( !p_sys->drained )
        {
            p_sys->stopSoutChain( p_stream );
            p_sys->drained = true;
        }

1035
        /* check if the Chromecast to be done playing */
1036 1037
        *b = p_sys->p_intf->isFinishedPlaying()
          || p_sys->handleChromecastState(p_stream) != VLC_SUCCESS;
1038 1039 1040
        return VLC_SUCCESS;
    }

1041 1042 1043
    if ( !p_sys->p_out->pf_control )
        return VLC_EGENERIC;

1044 1045
    return p_sys->p_out->pf_control( p_sys->p_out, i_query, args );
}
1046 1047 1048 1049 1050 1051

/*****************************************************************************
 * Open: connect to the Chromecast and initialize the sout
 *****************************************************************************/
static int Open(vlc_object_t *p_this)
{
1052
    sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
1053
    sout_stream_sys_t *p_sys = NULL;
1054
    intf_sys_t *p_intf = NULL;
1055
    char *psz_ip = NULL;
1056
    char *psz_mux = NULL;
1057
    char *psz_var_mime = NULL;
1058
    sout_stream_t *p_sout = NULL;
1059
    httpd_host_t *httpd_host = NULL;
1060
    bool b_supports_video = true;
1061
    int i_local_server_port;
1062
    int i_device_port;
1063
    std::stringstream ss;
1064

1065 1066 1067 1068
    vlc_interrupt_t *p_interrupt = vlc_interrupt_create();
    if (unlikely(p_interrupt == NULL))
        goto error;

1069
    config_ChainParse(p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg);
1070 1071 1072 1073 1074 1075 1076 1077 1078

    psz_ip = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "ip");
    if ( psz_ip == NULL )
    {
        msg_Err( p_this, "missing Chromecast IP address" );
        goto error;
    }

    i_device_port = var_InheritInteger(p_stream, SOUT_CFG_PREFIX "port");
1079
    i_local_server_port = var_InheritInteger(p_stream, SOUT_CFG_PREFIX "http-port");
1080

1081 1082 1083 1084 1085 1086 1087 1088
    var_Create(p_stream, "http-port", VLC_VAR_INTEGER);
    var_SetInteger(p_stream, "http-port", i_local_server_port);
    var_Create(p_stream, "http-host", VLC_VAR_STRING);
    var_SetString(p_stream, "http-host", "");
    httpd_host = vlc_http_HostNew(VLC_OBJECT(p_stream));
    if (httpd_host == NULL)
        goto error;

1089 1090
    try
    {
1091 1092
        p_intf = new intf_sys_t( p_this, i_local_server_port, psz_ip, i_device_port,
                                 p_interrupt, httpd_host );
1093 1094 1095
    }
    catch (const std::runtime_error& err )
    {
1096
        msg_Err( p_this, "cannot load the Chromecast controller (%s)", err.what() );
1097 1098 1099
        goto error;
    }
    catch (const std::bad_alloc& )
1100
    {
1101
        p_intf = NULL;
1102 1103
        goto error;
    }
1104

1105
    p_interrupt = NULL;
1106

1107
    psz_mux = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "mux");
1108
    if (psz_mux == NULL)
1109
    {
1110
        goto error;
1111
    }
1112
    psz_var_mime = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "mime");
1113
    if (psz_var_mime == NULL)
1114
        goto error;
1115

1116
    /* check if we can open the proper sout */
1117
    ss << "http{mux=" << psz_mux << "}";
1118 1119 1120 1121
    p_sout = sout_StreamChainNew( p_stream->p_sout, ss.str().c_str(), NULL, NULL);
    if (p_sout == NULL) {
        msg_Dbg(p_stream, "could not create sout chain:%s", ss.str().c_str());
        goto error;
1122
    }
1123
    sout_StreamChainDelete( p_sout, NULL );
1124

1125

1126
    b_supports_video = var_GetBool(p_stream, SOUT_CFG_PREFIX "video");
1127

1128
    p_sys = new(std::nothrow) sout_stream_sys_t( httpd_host, p_intf, b_supports_video,
1129
                                                 i_local_server_port, psz_mux, psz_var_mime );
1130 1131
    if (unlikely(p_sys == NULL))
        goto error;
1132

1133 1134 1135 1136 1137 1138 1139
    /* prevent sout-mux-caching since chromecast-proxy is already doing it */
    var_Create( p_stream->p_sout, "sout-mux-caching", VLC_VAR_INTEGER );
    var_SetInteger( p_stream->p_sout, "sout-mux-caching", 0 );

    var_Create( p_stream->p_sout, SOUT_CFG_PREFIX "sys", VLC_VAR_ADDRESS );
    var_SetAddress( p_stream->p_sout, SOUT_CFG_PREFIX "sys", p_sys );

1140 1141
    var_Create( p_stream->p_sout, SOUT_CFG_PREFIX "access-out-sys", VLC_VAR_ADDRESS );

1142
    // Set the sout callbacks.
1143 1144 1145 1146 1147
    p_stream->pf_add     = Add;
    p_stream->pf_del     = Del;
    p_stream->pf_send    = Send;
    p_stream->pf_flush   = Flush;
    p_stream->pf_control = Control;
1148

1149
    p_stream->p_sys = p_sys;
1150
    free(psz_ip);
1151
    free(psz_mux);
1152
    free(psz_var_mime);
1153 1154
    return VLC_SUCCESS;

1155
error:
1156 1157
    if (p_interrupt)
        vlc_interrupt_destroy(p_interrupt);
1158
    delete p_intf;
1159 1160
    if (httpd_host)
        httpd_HostDelete(httpd_host);
1161
    free(psz_ip);
1162
    free(psz_mux);
1163
    free(psz_var_mime);
1164
    delete p_sys;
1165 1166
    return VLC_EGENERIC;
}
1167 1168 1169 1170 1171 1172

/*****************************************************************************
 * Close: destroy interface
 *****************************************************************************/
static void Close(vlc_object_t *p_this)
{
1173
    sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
Thomas Guillem's avatar
Thomas Guillem committed
1174
    sout_stream_sys_t *p_sys = p_stream->p_sys;
1175

Thomas Guillem's avatar
Thomas Guillem committed
1176
    assert(p_sys->out_streams.empty() && p_sys->streams.empty());
1177
    var_Destroy( p_stream->p_sout, SOUT_CFG_PREFIX "sys" );
1178
    var_Destroy( p_stream->p_sout, SOUT_CFG_PREFIX "sout-mux-caching" );
Thomas Guillem's avatar
Thomas Guillem committed
1179

1180
    assert(p_sys->streams.empty() && p_sys->out_streams.empty());
1181 1182 1183

    httpd_host_t *httpd_host = p_sys->httpd_host;
    delete p_sys->p_intf;
Thomas Guillem's avatar
Thomas Guillem committed
1184
    delete p_sys;
1185 1186
    /* Delete last since p_intf and p_sys depends on httpd_host */
    httpd_HostDelete(httpd_host);
1187 1188
}