[vlc-commits] chromecast: Handle multiple transcode/remux configurations

Hugo Beauzée-Luyssen git at videolan.org
Tue Jan 16 17:19:19 CET 2018


vlc | branch: master | Hugo Beauzée-Luyssen <hugo at beauzee.fr> | Wed Jan 10 16:03:29 2018 +0100| [5810b8d6bdd02d8166f3eb5dc7a287be723eff52] | committer: Hugo Beauzée-Luyssen

chromecast: Handle multiple transcode/remux configurations

This is a best guess strategy, since we might receive a message stating
"LOAD_FAILED" (without any information about what failed), but we might
also receive nothing.

> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=5810b8d6bdd02d8166f3eb5dc7a287be723eff52
---

 modules/stream_out/chromecast/cast.cpp            | 112 ++++++++++++++++++----
 modules/stream_out/chromecast/chromecast.h        |   5 +-
 modules/stream_out/chromecast/chromecast_ctrl.cpp |  40 ++++++--
 3 files changed, 129 insertions(+), 28 deletions(-)

diff --git a/modules/stream_out/chromecast/cast.cpp b/modules/stream_out/chromecast/cast.cpp
index 2cb332d30c..37783103fd 100644
--- a/modules/stream_out/chromecast/cast.cpp
+++ b/modules/stream_out/chromecast/cast.cpp
@@ -49,6 +49,7 @@ struct sout_stream_sys_t
         , b_supports_video(has_video)
         , i_port(port)
         , es_changed( true )
+        , transcode_attempt_idx( 0 )
     {
         assert(p_intf != NULL);
     }
@@ -60,7 +61,8 @@ struct sout_stream_sys_t
     }
 
     bool canDecodeVideo( vlc_fourcc_t i_codec ) const;
-    bool canDecodeAudio( vlc_fourcc_t i_codec ) const;
+    bool canDecodeAudio( vlc_fourcc_t i_codec,
+                         const audio_format_t* p_fmt ) const;
     bool startSoutChain(sout_stream_t* p_stream);
 
     sout_stream_t     *p_out;
@@ -76,15 +78,19 @@ struct sout_stream_sys_t
 
     bool                               es_changed;
     std::vector<sout_stream_id_sys_t*> streams;
+    bool                               stream_started;
+    unsigned int                       transcode_attempt_idx;
 
 private:
     bool UpdateOutput( sout_stream_t * );
+    vlc_fourcc_t transcodeAudioFourCC(const audio_format_t* p_fmt );
+
 };
 
 #define SOUT_CFG_PREFIX "sout-chromecast-"
 
-static const vlc_fourcc_t DEFAULT_TRANSCODE_AUDIO = VLC_CODEC_MP4A;
 static const vlc_fourcc_t DEFAULT_TRANSCODE_VIDEO = VLC_CODEC_H264;
+static const unsigned int MAX_TRANSCODE_PASS = 3;
 static const char DEFAULT_MUXER[] = "avformat{mux=matroska,options={live=1}}}";
 
 
@@ -199,26 +205,55 @@ static void Del(sout_stream_t *p_stream, sout_stream_id_sys_t *id)
         sout_StreamChainDelete( p_sys->p_out, NULL );
         p_sys->p_out = NULL;
         p_sys->sout = "";
+        p_sys->stream_started = false;
+        p_sys->transcode_attempt_idx = 0;
     }
 }
 
+/**
+ * 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:
+ * - Disallow multichannel AAC
+ *
+ * Supported formats: https://developers.google.com/cast/docs/media
+ */
 
 bool sout_stream_sys_t::canDecodeVideo( vlc_fourcc_t i_codec ) const
 {
+    if ( transcode_attempt_idx == MAX_TRANSCODE_PASS - 1 )
+        return false;
+    if ( i_codec == VLC_CODEC_HEVC || i_codec == VLC_CODEC_VP9 )
+        return transcode_attempt_idx == 0;
     return i_codec == VLC_CODEC_H264 || i_codec == VLC_CODEC_VP8;
 }
 
-bool sout_stream_sys_t::canDecodeAudio( vlc_fourcc_t i_codec ) const
+bool sout_stream_sys_t::canDecodeAudio( vlc_fourcc_t i_codec,
+                                        const audio_format_t* p_fmt ) const
 {
-    return i_codec == VLC_CODEC_VORBIS ||
-        i_codec == VLC_CODEC_MP4A ||
-        i_codec == VLC_FOURCC('h', 'a', 'a', 'c') ||
-        i_codec == VLC_FOURCC('l', 'a', 'a', 'c') ||
-        i_codec == VLC_FOURCC('s', 'a', 'a', 'c') ||
-        i_codec == VLC_CODEC_OPUS ||
-        i_codec == VLC_CODEC_MP3 ||
-        i_codec == VLC_CODEC_A52 ||
-        i_codec == VLC_CODEC_EAC3;
+    if ( transcode_attempt_idx == MAX_TRANSCODE_PASS - 1 )
+        return false;
+    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') ||
+            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_A52 || i_codec == VLC_CODEC_EAC3 ||
+           i_codec == VLC_CODEC_MP3;
+}
+
+vlc_fourcc_t sout_stream_sys_t::transcodeAudioFourCC( const audio_format_t* p_fmt )
+{
+    if ( p_fmt->i_channels > 2 )
+        return VLC_CODEC_S16L;
+    return VLC_CODEC_MP3;
 }
 
 bool sout_stream_sys_t::startSoutChain( sout_stream_t *p_stream )
@@ -269,21 +304,25 @@ bool sout_stream_sys_t::UpdateOutput( sout_stream_t *p_stream )
 
     bool canRemux = true;
     vlc_fourcc_t i_codec_video = 0, i_codec_audio = 0;
+    const es_format_t *p_original_audio = NULL;
+    const es_format_t *p_original_video = NULL;
 
     for (std::vector<sout_stream_id_sys_t*>::iterator it = streams.begin(); it != streams.end(); ++it)
     {
         const es_format_t *p_es = &(*it)->fmt;
-        if (p_es->i_cat == AUDIO_ES)
+        if (p_es->i_cat == AUDIO_ES && p_original_audio == NULL)
         {
-            if (!canDecodeAudio( p_es->i_codec ))
+            if ( !canDecodeAudio( p_es->i_codec, &p_es->audio ) )
             {
                 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;
             }
             else if (i_codec_audio == 0)
                 i_codec_audio = p_es->i_codec;
+            p_original_audio = p_es;
         }
-        else if (b_supports_video && p_es->i_cat == VIDEO_ES)
+        else if (b_supports_video && p_es->i_cat == VIDEO_ES &&
+                 p_original_video == NULL )
         {
             if (!canDecodeVideo( p_es->i_codec ))
             {
@@ -292,9 +331,19 @@ bool sout_stream_sys_t::UpdateOutput( sout_stream_t *p_stream )
             }
             else if (i_codec_video == 0)
                 i_codec_video = p_es->i_codec;
+            p_original_video = p_es;
         }
     }
 
+    if ( transcode_attempt_idx == 1 && p_original_video != NULL &&
+         ( p_original_video->i_codec != VLC_CODEC_HEVC &&
+           p_original_video->i_codec != VLC_CODEC_VP9 ) )
+    {
+        msg_Dbg( p_stream, "Video format wasn't HEVC/VP9; skipping 2nd step and"
+                 " transcoding to h264/mp3" );
+        transcode_attempt_idx++;
+    }
+
     std::stringstream ssout;
     if ( !canRemux )
     {
@@ -315,9 +364,9 @@ bool sout_stream_sys_t::UpdateOutput( sout_stream_t *p_stream )
         /* TODO: provide audio samplerate and channels */
         ssout << "transcode{";
         char s_fourcc[5];
-        if ( i_codec_audio == 0 )
+        if ( i_codec_audio == 0 && p_original_audio )
         {
-            i_codec_audio = DEFAULT_TRANSCODE_AUDIO;
+            i_codec_audio = transcodeAudioFourCC( &p_original_audio->audio );
             msg_Dbg( p_stream, "Converting audio to %.4s", (const char*)&i_codec_audio );
             ssout << "acodec=";
             vlc_fourcc_to_char( i_codec_audio, s_fourcc );
@@ -356,6 +405,7 @@ bool sout_stream_sys_t::UpdateOutput( sout_stream_t *p_stream )
     {
         /* tell the chromecast to load the content */
         p_intf->setHasInput( mime );
+        stream_started = false;
     }
     else
     {
@@ -399,6 +449,34 @@ static int Send(sout_stream_t *p_stream, sout_stream_id_sys_t *id,
         block_Release( p_buffer );
         return VLC_EGENERIC;
     }
+    /**
+     * Check if the chromecast refused to handle the current configuration.
+     * Once the State switched to started, we know it has been accepted and don't
+     * need to monitor state changes anymore.
+     */
+    if ( p_sys->stream_started == false )
+    {
+        States s = p_sys->p_intf->state();
+        if ( s == LoadFailed && p_sys->es_changed == false )
+        {
+            if ( p_sys->transcode_attempt_idx > MAX_TRANSCODE_PASS - 1 )
+            {
+                msg_Err( p_stream, "All attempts failed. Giving up." );
+                block_Release( p_buffer );
+                return VLC_EGENERIC;
+            }
+            p_sys->transcode_attempt_idx++;
+            p_sys->es_changed = true;
+            msg_Dbg( p_stream, "Load failed detected. Switching to next "
+                     "configuration index: %u", p_sys->transcode_attempt_idx );
+        }
+        else if ( s == Playing || s == Paused )
+        {
+            msg_Dbg( p_stream, "Playback started: Current configuration (%u) "
+                     "accepted", p_sys->transcode_attempt_idx );
+            p_sys->stream_started = true;
+        }
+    }
 
     return sout_StreamIdSend(p_sys->p_out, id, p_buffer);
 }
diff --git a/modules/stream_out/chromecast/chromecast.h b/modules/stream_out/chromecast/chromecast.h
index a930fdc6ee..b1b1e8510a 100644
--- a/modules/stream_out/chromecast/chromecast.h
+++ b/modules/stream_out/chromecast/chromecast.h
@@ -78,6 +78,8 @@ enum States
     Launching,
     // The application is ready, but idle
     Ready,
+    // The chromecast rejected the media
+    LoadFailed,
     // A media session is being initiated
     Loading,
     Buffering,
@@ -156,6 +158,7 @@ struct intf_sys_t
 
     void requestPlayerSeek(mtime_t pos);
     void requestPlayerStop();
+    States state() const;
 
 private:
     bool handleMessages();
@@ -215,7 +218,7 @@ private:
     std::string m_appTransportId;
     std::string m_mediaSessionId;
 
-    vlc_mutex_t  m_lock;
+    mutable vlc_mutex_t  m_lock;
     vlc_cond_t   m_stateChangedCond;
     vlc_thread_t m_chromecastThread;
 
diff --git a/modules/stream_out/chromecast/chromecast_ctrl.cpp b/modules/stream_out/chromecast/chromecast_ctrl.cpp
index e98c454632..473e1fadd8 100644
--- a/modules/stream_out/chromecast/chromecast_ctrl.cpp
+++ b/modules/stream_out/chromecast/chromecast_ctrl.cpp
@@ -58,6 +58,8 @@ static const char* StateToStr( States s )
         return "Lauching";
     case Ready:
         return "Ready";
+    case LoadFailed:
+        return "LoadFailed";
     case Loading:
         return "Loading";
     case Buffering:
@@ -381,8 +383,12 @@ void intf_sys_t::processReceiverMessage( const castchannel::CastMessage& msg )
                 break;
             // else: fall through and warn
         default:
-            msg_Warn( m_module, "Unexpected RECEIVER_STATUS with state %s",
+            msg_Warn( m_module, "Unexpected RECEIVER_STATUS with state %s. "
+                      "Checking media status",
                       StateToStr( m_state ) );
+            // This is likely because the chromecast refused the playback, but
+            // let's check by explicitely probing the media status
+            m_communication.msgPlayerGetStatus( m_appTransportId );
             break;
         }
     }
@@ -420,14 +426,26 @@ void intf_sys_t::processMediaMessage( const castchannel::CastMessage& msg )
 
         vlc_mutex_locker locker( &m_lock );
 
-        if (newPlayerState == "IDLE")
+        if (newPlayerState == "IDLE" || newPlayerState.empty() == true )
         {
-            if ( m_state != Ready )
+            /* Idle state is expected when the media receiver application is
+             * started. In case the state is still Buffering, it denotes an error.
+             * In most case, we'd receive a RECEIVER_STATUS message, which causes
+             * use to ask for the MEDIA_STATUS before assuming an error occured.
+             * If the chromecast silently gave up on playing our stream, we also
+             * might have an empty status array.
+             * If the media load indeed failed, we need to try another
+             * transcode/remux configuration, or give up.
+             */
+            if ( m_state != Ready && m_state != LoadFailed )
             {
                 // The playback stopped
                 m_mediaSessionId = "";
                 m_time_playback_started = VLC_TS_INVALID;
-                setState( Ready );
+                if ( m_state == Buffering )
+                    setState( LoadFailed );
+                else
+                    setState( Ready );
             }
         }
         else
@@ -491,7 +509,7 @@ void intf_sys_t::processMediaMessage( const castchannel::CastMessage& msg )
                     setState( Loading );
                 }
             }
-            else if (!newPlayerState.empty())
+            else
                 msg_Warn( m_module, "Unknown Chromecast MEDIA_STATUS state %s", newPlayerState.c_str());
         }
     }
@@ -499,11 +517,7 @@ void intf_sys_t::processMediaMessage( const castchannel::CastMessage& msg )
     {
         msg_Err( m_module, "Media load failed");
         vlc_mutex_locker locker(&m_lock);
-        /* close the app to restart it */
-        if ( m_state == Launching )
-            m_communication.msgReceiverClose(m_appTransportId);
-        else
-            m_communication.msgReceiverGetStatus();
+        setState( LoadFailed );
     }
     else if (type == "LOAD_CANCELLED")
     {
@@ -622,6 +636,12 @@ void intf_sys_t::requestPlayerStop()
     queueMessage( Stop );
 }
 
+States intf_sys_t::state() const
+{
+    vlc_mutex_locker locker( &m_lock );
+    return m_state;
+}
+
 void intf_sys_t::requestPlayerSeek(mtime_t pos)
 {
     vlc_mutex_locker locker(&m_lock);



More information about the vlc-commits mailing list