[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