[vlc-devel] [PATCH 2/3] dlna: add a DLNA stream out
Shaleen Jain
shaleen at jain.sh
Wed Nov 28 16:44:56 CET 2018
Add support for casting to a DLNA Media Renderers
which implement the AVTransport service with the
initial support of the default media format.
---
NEWS | 1 +
modules/MODULES_LIST | 1 +
modules/services_discovery/Makefile.am | 7 +-
modules/services_discovery/upnp.cpp | 38 ++
modules/services_discovery/upnp.hpp | 1 +
modules/stream_out/dlna/dlna.cpp | 713 ++++++++++++++++++++++++
modules/stream_out/dlna/dlna.hpp | 60 ++
modules/stream_out/dlna/dlna_common.hpp | 44 ++
8 files changed, 864 insertions(+), 1 deletion(-)
create mode 100644 modules/stream_out/dlna/dlna.cpp
create mode 100644 modules/stream_out/dlna/dlna.hpp
create mode 100644 modules/stream_out/dlna/dlna_common.hpp
diff --git a/NEWS b/NEWS
index ef49706065..5cc420271c 100644
--- a/NEWS
+++ b/NEWS
@@ -46,6 +46,7 @@ Video output:
Stream output:
* New SDI output with improved audio and ancillary support.
Candidate for deprecation of decklink vout/aout modules.
+ * Support for DLNA/UPNP renderers
macOS:
* Remove Growl notification support
diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST
index c5104b2234..0d407f082d 100644
--- a/modules/MODULES_LIST
+++ b/modules/MODULES_LIST
@@ -379,6 +379,7 @@ $Id$
* stream_out_delay: introduce delay in an ES when streaming
* stream_out_description: helper module for RTSP vod
* stream_out_display: displays a stream output chain
+ * stream_out_dlna: DLNA streaming output module
* stream_out_dummy: dummy stream out chain module
* stream_out_duplicate: duplicates a stream output chain
* stream_out_es: stream out module outputing ES
diff --git a/modules/services_discovery/Makefile.am b/modules/services_discovery/Makefile.am
index f63df23b32..1359460477 100644
--- a/modules/services_discovery/Makefile.am
+++ b/modules/services_discovery/Makefile.am
@@ -28,7 +28,12 @@ sd_LTLIBRARIES += $(LTLIBmtp)
libupnp_plugin_la_SOURCES = services_discovery/upnp.cpp services_discovery/upnp.hpp \
services_discovery/upnp-wrapper.hpp \
- services_discovery/upnp-wrapper.cpp
+ services_discovery/upnp-wrapper.cpp \
+ stream_out/renderer_common.hpp \
+ stream_out/renderer_common.cpp \
+ stream_out/dlna/dlna_common.hpp \
+ stream_out/dlna/dlna.hpp \
+ stream_out/dlna/dlna.cpp
libupnp_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) $(UPNP_CFLAGS)
libupnp_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(sddir)'
libupnp_plugin_la_LIBADD = $(UPNP_LIBS)
diff --git a/modules/services_discovery/upnp.cpp b/modules/services_discovery/upnp.cpp
index a58296b867..72439b7ff0 100644
--- a/modules/services_discovery/upnp.cpp
+++ b/modules/services_discovery/upnp.cpp
@@ -54,6 +54,23 @@ const char* SATIP_SERVER_DEVICE_TYPE = "urn:ses-com:device:SatIPServer:1";
#define UPNP_SEARCH_TIMEOUT_SECONDS 15
#define SATIP_CHANNEL_LIST N_("SAT>IP channel list")
#define SATIP_CHANNEL_LIST_URL N_("Custom SAT>IP channel list URL")
+
+#define HTTP_PORT 7070
+
+#define HTTP_PORT_TEXT N_("HTTP port")
+#define HTTP_PORT_LONGTEXT N_("This sets the HTTP port of the local server used to stream the media to the UPnP Renderer.")
+#define HAS_VIDEO_TEXT N_("Video")
+#define HAS_VIDEO_LONGTEXT N_("The UPnP Renderer can receive video.")
+
+#define IP_ADDR_TEXT N_("IP Address")
+#define IP_ADDR_LONGTEXT N_("IP Address of the UPnP Renderer.")
+#define PORT_TEXT N_("UPnP Renderer port")
+#define PORT_LONGTEXT N_("The port used to talk to the UPnP Renderer.")
+#define BASE_URL_TEXT N_("base URL")
+#define BASE_URL_LONGTEXT N_("The base Url relative to which all other UPnP operations must be called")
+#define URL_TEXT N_("description URL")
+#define URL_LONGTEXT N_("The Url used to get the xml descriptor of the UPnP Renderer")
+
static const char *const ppsz_satip_channel_lists[] = {
"Auto", "ASTRA_19_2E", "ASTRA_28_2E", "ASTRA_23_5E", "MasterList", "ServerList", "CustomList"
};
@@ -147,6 +164,27 @@ vlc_module_begin()
VLC_RD_PROBE_SUBMODULE
+ add_submodule()
+ set_shortname("dlna")
+ set_description(N_("UPnP/DLNA stream output"))
+ set_capability("sout stream", 0)
+ add_shortcut("dlna")
+ set_category(CAT_SOUT)
+ set_subcategory(SUBCAT_SOUT_STREAM)
+ set_callbacks(DLNA::OpenSout, DLNA::CloseSout)
+
+ add_string(SOUT_CFG_PREFIX "ip", NULL, IP_ADDR_TEXT, IP_ADDR_LONGTEXT, false)
+ add_integer(SOUT_CFG_PREFIX "port", NULL, PORT_TEXT, PORT_LONGTEXT, false)
+ add_integer(SOUT_CFG_PREFIX "http-port", HTTP_PORT, HTTP_PORT_TEXT, HTTP_PORT_LONGTEXT, false)
+ add_bool(SOUT_CFG_PREFIX "video", true, HAS_VIDEO_TEXT, HAS_VIDEO_LONGTEXT, false)
+ add_string(SOUT_CFG_PREFIX "base_url", NULL, BASE_URL_TEXT, BASE_URL_LONGTEXT, false)
+ add_string(SOUT_CFG_PREFIX "url", NULL, URL_TEXT, URL_LONGTEXT, false)
+
+ add_integer(RENDERER_CFG_PREFIX "show-perf-warning", 1, PERF_TEXT, PERF_LONGTEXT, true )
+ change_private()
+ add_integer(RENDERER_CFG_PREFIX "conversion-quality", CONVERSION_QUALITY_DEFAULT,
+ CONVERSION_QUALITY_TEXT, CONVERSION_QUALITY_LONGTEXT, false );
+ change_integer_list(conversion_quality_list, conversion_quality_list_text)
vlc_module_end()
/*
diff --git a/modules/services_discovery/upnp.hpp b/modules/services_discovery/upnp.hpp
index 5a0de1f284..3b2e80b209 100644
--- a/modules/services_discovery/upnp.hpp
+++ b/modules/services_discovery/upnp.hpp
@@ -33,6 +33,7 @@
#endif
#include "upnp-wrapper.hpp"
+#include "../stream_out/dlna/dlna_common.hpp"
#include <vlc_url.h>
#include <vlc_interrupt.h>
diff --git a/modules/stream_out/dlna/dlna.cpp b/modules/stream_out/dlna/dlna.cpp
new file mode 100644
index 0000000000..965269048f
--- /dev/null
+++ b/modules/stream_out/dlna/dlna.cpp
@@ -0,0 +1,713 @@
+/*****************************************************************************
+ * dlna.cpp : DLNA/UPNP (renderer) sout module
+ *****************************************************************************
+ * Copyright (C) 2004-2018 VLC authors and VideoLAN
+ *
+ * Authors: William Ung <william1.ung at epitech.eu>
+ * Shaleen Jain <shaleen at jain.sh>
+ *
+ * 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.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "dlna.hpp"
+
+#include <vector>
+#include <string>
+#include <sstream>
+
+#include <vlc_dialog.h>
+#include <vlc_rand.h>
+#include <vlc_sout.h>
+
+static const char* AV_TRANSPORT_SERVICE_TYPE = "urn:schemas-upnp-org:service:AVTransport:1";
+static const char* CONNECTION_MANAGER_SERVICE_TYPE = "urn:schemas-upnp-org:service:ConnectionManager:1";
+
+static const char *const ppsz_sout_options[] = {
+ "ip", "port", "http-port", "video", "base_url", "url", NULL
+};
+
+namespace DLNA
+{
+
+struct sout_stream_id_sys_t
+{
+ es_format_t fmt;
+ sout_stream_id_sys_t *p_sub_id;
+};
+
+struct sout_stream_sys_t
+{
+ sout_stream_sys_t(int http_port, bool supports_video)
+ : p_out( NULL )
+ , es_changed( true )
+ , b_supports_video( supports_video )
+ , perf_warning_shown( false )
+ , venc_opt_idx ( -1 )
+ , http_port( http_port )
+ {
+ }
+
+ std::shared_ptr<MediaRenderer> renderer;
+ UpnpInstanceWrapper *p_upnp;
+
+ bool canDecodeAudio( vlc_fourcc_t i_codec ) const;
+ bool canDecodeVideo( vlc_fourcc_t i_codec ) const;
+ bool startSoutChain( sout_stream_t* p_stream,
+ const std::vector<sout_stream_id_sys_t*> &new_streams,
+ const std::string &sout );
+ void stopSoutChain( sout_stream_t* p_stream );
+ sout_stream_id_sys_t *GetSubId( sout_stream_t *p_stream,
+ sout_stream_id_sys_t *id,
+ bool update = true );
+
+ sout_stream_t *p_out;
+ bool es_changed;
+ bool b_supports_video;
+ bool perf_warning_shown;
+ int venc_opt_idx;
+ int http_port;
+ std::vector<sout_stream_id_sys_t*> streams;
+ std::vector<sout_stream_id_sys_t*> out_streams;
+
+private:
+ std::string GetAcodecOption( sout_stream_t *, vlc_fourcc_t *, const audio_format_t *, int );
+ std::string GetVcodecOption( sout_stream_t *, vlc_fourcc_t *, const video_format_t *, int );
+ int UpdateOutput( sout_stream_t *p_stream );
+
+};
+
+char *getServerIPAddress() {
+ char *ip = NULL;
+#ifdef UPNP_ENABLE_IPV6
+#ifdef _WIN32
+ IP_ADAPTER_UNICAST_ADDRESS *p_best_ip = NULL;
+ wchar_t psz_uri[32];
+ DWORD strSize;
+ IP_ADAPTER_ADDRESSES *p_adapter, *addresses;
+
+ addresses = ListAdapters();
+ if (addresses == NULL)
+ return NULL;
+
+ p_adapter = addresses;
+ while (p_adapter != NULL)
+ {
+ if (isAdapterSuitable(p_adapter, false))
+ {
+ IP_ADAPTER_UNICAST_ADDRESS *p_unicast = p_adapter->FirstUnicastAddress;
+ while (p_unicast != NULL)
+ {
+ strSize = sizeof( psz_uri ) / sizeof( wchar_t );
+ if( WSAAddressToString( p_unicast->Address.lpSockaddr,
+ p_unicast->Address.iSockaddrLength,
+ NULL, psz_uri, &strSize ) == 0 )
+ {
+ if ( p_best_ip == NULL ||
+ p_best_ip->ValidLifetime > p_unicast->ValidLifetime )
+ {
+ p_best_ip = p_unicast;
+ }
+ }
+ p_unicast = p_unicast->Next;
+ }
+ }
+ p_adapter = p_adapter->Next;
+ }
+
+ if (p_best_ip != NULL)
+ {
+ strSize = sizeof( psz_uri ) / sizeof( wchar_t );
+ WSAAddressToString( p_best_ip->Address.lpSockaddr,
+ p_best_ip->Address.iSockaddrLength,
+ NULL, psz_uri, &strSize );
+ free(addresses);
+ return FromWide( psz_uri );
+ }
+ free(addresses);
+ return NULL;
+#endif /* _WIN32 */
+#else /* UPNP_ENABLE_IPV6 */
+ ip = getIpv4ForMulticast();
+#endif /* UPNP_ENABLE_IPV6 */
+ if (ip == NULL)
+ {
+ ip = UpnpGetServerIpAddress();
+ }
+ return ip;
+}
+
+bool sout_stream_sys_t::canDecodeAudio(vlc_fourcc_t i_codec) const
+{
+ return i_codec == VLC_CODEC_MP4A;
+}
+
+bool sout_stream_sys_t::canDecodeVideo(vlc_fourcc_t i_codec) const
+{
+ return i_codec == VLC_CODEC_H264;
+}
+
+bool sout_stream_sys_t::startSoutChain(sout_stream_t *p_stream,
+ const std::vector<sout_stream_id_sys_t*> &new_streams,
+ const std::string &sout)
+{
+ msg_Dbg( p_stream, "Creating chain %s", sout.c_str() );
+ out_streams = new_streams;
+
+ p_out = sout_StreamChainNew( p_stream->p_sout, sout.c_str(), NULL, NULL);
+ if (p_out == NULL) {
+ msg_Err(p_stream, "could not create sout chain:%s", sout.c_str());
+ out_streams.clear();
+ return false;
+ }
+
+ /* check the streams we can actually add */
+ for (std::vector<sout_stream_id_sys_t*>::iterator it = out_streams.begin();
+ it != out_streams.end(); )
+ {
+ sout_stream_id_sys_t *p_sys_id = *it;
+ p_sys_id->p_sub_id = static_cast<sout_stream_id_sys_t *>(
+ 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 );
+ es_format_Clean( &p_sys_id->fmt );
+ it = out_streams.erase( it );
+ }
+ else
+ ++it;
+ }
+
+ if (out_streams.empty())
+ {
+ stopSoutChain( p_stream );
+ return false;
+ }
+
+ return true;
+}
+
+void sout_stream_sys_t::stopSoutChain(sout_stream_t *p_stream)
+{
+ msg_Dbg( p_stream, "Destroying dlna sout chain");
+
+ for ( size_t i = 0; i < out_streams.size(); i++ )
+ {
+ sout_StreamIdDel( p_out, out_streams[i]->p_sub_id );
+ out_streams[i]->p_sub_id = NULL;
+ }
+ out_streams.clear();
+ sout_StreamChainDelete( p_out, NULL );
+ p_out = NULL;
+}
+
+sout_stream_id_sys_t *sout_stream_sys_t::GetSubId( sout_stream_t *p_stream,
+ sout_stream_id_sys_t *id,
+ bool update)
+{
+ assert( p_stream->p_sys == this );
+
+ if ( update && UpdateOutput( p_stream ) != VLC_SUCCESS )
+ return NULL;
+
+ for (size_t i = 0; i < out_streams.size(); ++i)
+ {
+ if ( id == (sout_stream_id_sys_t*) out_streams[i] )
+ return out_streams[i]->p_sub_id;
+ }
+
+ msg_Err( p_stream, "unknown stream ID" );
+ return NULL;
+}
+
+venc_options venc_opt_list[] = {
+#ifdef __APPLE__
+ { .fcc = VLC_CODEC_H264, .get_opt = GetVencAvcodecVTOption },
+#endif
+ { .fcc = VLC_CODEC_H264, .get_opt = GetVencQSVH264Option },
+ { .fcc = VLC_CODEC_H264, .get_opt = GetVencX264Option },
+ { .fcc = VLC_CODEC_H264, .get_opt = NULL },
+};
+
+std::string
+sout_stream_sys_t::GetVcodecOption( sout_stream_t *p_stream, vlc_fourcc_t *p_codec_video,
+ const video_format_t *p_vid, int i_quality )
+{
+ std::stringstream ssout;
+ static const char video_maxres_hd[] = "maxwidth=1920,maxheight=1080";
+ static const char video_maxres_720p[] = "maxwidth=1280,maxheight=720";
+
+ ssout << GetVencOption( p_stream, venc_opt_idx, venc_opt_list,
+ ARRAY_SIZE(venc_opt_list), p_codec_video, p_vid, i_quality );
+
+ switch ( i_quality )
+ {
+ case CONVERSION_QUALITY_HIGH:
+ case CONVERSION_QUALITY_MEDIUM:
+ ssout << ( ( p_vid->i_width > 1920 ) ? "width=1920," : "" ) << video_maxres_hd << ',';
+ break;
+ default:
+ ssout << ( ( p_vid->i_width > 1280 ) ? "width=1280," : "" ) << video_maxres_720p << ',';
+ }
+
+ if( 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,";
+ }
+
+ msg_Dbg( p_stream, "Converting video to %.4s", (const char*)p_codec_video );
+
+ return ssout.str();
+}
+
+std::string
+sout_stream_sys_t::GetAcodecOption( sout_stream_t *p_stream, vlc_fourcc_t *p_codec_audio,
+ const audio_format_t *p_aud, int i_quality )
+{
+ VLC_UNUSED(p_aud);
+ VLC_UNUSED(i_quality);
+ std::stringstream ssout;
+
+ msg_Dbg( p_stream, "Converting audio to %.4s", (const char*)p_codec_audio );
+
+ ssout << "acodec=";
+ char fourcc[5];
+ vlc_fourcc_to_char( *p_codec_audio, fourcc );
+ fourcc[4] = '\0';
+ ssout << fourcc << ',';
+
+ ssout << "aenc=avcodec{codec=aac},";
+ return ssout.str();
+}
+
+int sout_stream_sys_t::UpdateOutput( sout_stream_t *p_stream )
+{
+ assert( p_stream->p_sys == this );
+
+ if ( !es_changed )
+ return VLC_SUCCESS;
+
+ es_changed = false;
+
+ bool canRemux = true;
+ // To keep track of which stream needs transcoding if at all.
+ 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;
+ std::vector<sout_stream_id_sys_t*> new_streams;
+
+ for (sout_stream_id_sys_t *stream : streams)
+ {
+ const es_format_t *p_es = &stream->fmt;
+ if (p_es->i_cat == AUDIO_ES)
+ {
+ if (!canDecodeAudio( p_es->i_codec ))
+ {
+ msg_Dbg( p_stream, "can't remux audio track %d codec %4.4s",
+ p_es->i_id, (const char*)&p_es->i_codec );
+ p_original_audio = p_es;
+ canRemux = false;
+ }
+ else if (i_codec_audio == 0)
+ {
+ i_codec_audio = p_es->i_codec;
+ }
+ new_streams.push_back(stream);
+ }
+ else if (b_supports_video && p_es->i_cat == VIDEO_ES)
+ {
+ 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 );
+ p_original_video = p_es;
+ canRemux = false;
+ }
+ else if (i_codec_video == 0)
+ {
+ i_codec_video = p_es->i_codec;
+ }
+ new_streams.push_back(stream);
+ }
+ }
+
+ if (new_streams.empty())
+ return VLC_SUCCESS;
+
+ std::ostringstream ssout;
+ if ( !canRemux )
+ {
+ if ( !perf_warning_shown && i_codec_video == 0 && p_original_video
+ && var_InheritInteger( p_stream, RENDERER_CFG_PREFIX "show-perf-warning" ) )
+ {
+ 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;
+ perf_warning_shown = true;
+ if ( res == 2 )
+ config_PutInt(RENDERER_CFG_PREFIX "show-perf-warning", 0 );
+ }
+
+ const int i_quality = var_InheritInteger( p_stream, RENDERER_CFG_PREFIX "conversion-quality" );
+
+ /* TODO: provide audio samplerate and channels */
+ ssout << "transcode{";
+ if ( i_codec_audio == 0 && p_original_audio )
+ {
+ i_codec_audio = VLC_CODEC_MP4A;
+ ssout << GetAcodecOption( p_stream, &i_codec_audio,
+ &p_original_audio->audio, i_quality );
+ }
+ if ( i_codec_video == 0 && p_original_video )
+ {
+ i_codec_video = VLC_CODEC_H264;
+ ssout << GetVcodecOption( p_stream, &i_codec_video,
+ &p_original_video->video, i_quality );
+ }
+ ssout << "}:";
+ }
+
+ std::ostringstream ss;
+ ss << "/dlna"
+ << "/" << vlc_tick_now()
+ << "/" << static_cast<uint64_t>( vlc_mrand48() )
+ << "/stream";
+ std::string root_url = ss.str();
+
+ ssout << "http{dst=:" << http_port << root_url
+ << ",mux=" << "mp4stream"
+ << ",access=http{mime=" << "video/mp4" << "}}";
+
+ char *ip = getServerIPAddress();
+ if (ip == NULL)
+ {
+ msg_Err(p_stream, "could not get the local ip address");
+ return VLC_EGENERIC;
+ }
+
+ char *uri;
+ if (asprintf(&uri, "http://%s:%d%s", ip, http_port, root_url.c_str()) < 0) {
+ return VLC_ENOMEM;
+ }
+
+ if ( !startSoutChain( p_stream, new_streams, ssout.str() ) )
+ return VLC_EGENERIC;
+
+ msg_Dbg(p_stream, "AVTransportURI: %s", uri);
+ renderer->Stop();
+ renderer->SetAVTransportURI(uri);
+ renderer->Play("1");
+
+ free(uri);
+ return VLC_SUCCESS;
+}
+
+char *MediaRenderer::getServiceURL(const char* type, const char *service)
+{
+ IXML_Document *p_description_doc = NULL;
+ if (UpnpDownloadXmlDoc(device_url.c_str(), &p_description_doc) != UPNP_E_SUCCESS)
+ return NULL;
+
+ IXML_NodeList* p_device_list = ixmlDocument_getElementsByTagName( p_description_doc, "device");
+ free(p_description_doc);
+ if ( !p_device_list )
+ return NULL;
+
+ for (unsigned int i = 0; i < ixmlNodeList_length(p_device_list); ++i)
+ {
+ IXML_Element* p_device_element = ( IXML_Element* ) ixmlNodeList_item( p_device_list, i );
+ if( !p_device_element )
+ continue;
+
+ IXML_NodeList* p_service_list = ixmlElement_getElementsByTagName( p_device_element, "service" );
+ if ( !p_service_list )
+ continue;
+ for ( unsigned int j = 0; j < ixmlNodeList_length( p_service_list ); j++ )
+ {
+ IXML_Element* p_service_element = (IXML_Element*)ixmlNodeList_item( p_service_list, j );
+
+ const char* psz_service_type = xml_getChildElementValue( p_service_element, "serviceType" );
+ if ( !psz_service_type || !strstr(psz_service_type, type))
+ continue;
+ const char* psz_control_url = xml_getChildElementValue( p_service_element,
+ service );
+ if ( !psz_control_url )
+ continue;
+
+ char* psz_url = ( char* ) malloc( base_url.length() + strlen( psz_control_url ) + 1 );
+ if ( psz_url && UpnpResolveURL( base_url.c_str(), psz_control_url, psz_url ) == UPNP_E_SUCCESS )
+ return psz_url;
+ return NULL;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * Send an action to the control url of the service specified.
+ *
+ * \return the response as a IXML document or NULL for failure
+ **/
+IXML_Document *MediaRenderer::SendAction(const char* action_name,const char *service_type,
+ std::list<std::pair<const char*, const char*>> arguments)
+{
+ /* Create action */
+ IXML_Document *action = UpnpMakeAction(action_name, service_type, 0, NULL);
+
+ /* Add argument to action */
+ for (std::pair<const char*, const char*> arg : arguments) {
+ const char *arg_name, *arg_val;
+ arg_name = arg.first;
+ arg_val = arg.second;
+ UpnpAddToAction(&action, action_name, service_type, arg_name, arg_val);
+ }
+
+ /* Get the controlURL of the service */
+ char *control_url = getServiceURL(service_type, "controlURL");
+
+ /* Send action */
+ IXML_Document *response = NULL;
+ int ret = UpnpSendAction(handle, control_url, service_type,
+ NULL, action, &response);
+
+ /* Free action */
+ if (action) ixmlDocument_free(action);
+ if (control_url) free(control_url);
+
+ if (ret != UPNP_E_SUCCESS) {
+ msg_Err(parent, "Unable to send action: %s (%d: %s) response: %s",
+ action_name, ret, UpnpGetErrorMessage(ret), ixmlPrintDocument(response));
+ if (response) ixmlDocument_free(response);
+ return NULL;
+ }
+
+ return response;
+}
+
+int MediaRenderer::Play(const char *speed)
+{
+ std::list<std::pair<const char*, const char*>> arg_list;
+ arg_list.push_back(std::make_pair("InstanceID", "0"));
+ arg_list.push_back(std::make_pair("Speed", speed));
+
+ IXML_Document *p_response = SendAction("Play", AV_TRANSPORT_SERVICE_TYPE, arg_list);
+ if(!p_response)
+ {
+ return VLC_EGENERIC;
+ }
+ ixmlDocument_free(p_response);
+ return VLC_SUCCESS;
+}
+
+int MediaRenderer::Stop()
+{
+ std::list<std::pair<const char*, const char*>> arg_list;
+ arg_list.push_back(std::make_pair("InstanceID", "0"));
+
+ IXML_Document *p_response = SendAction("Stop", AV_TRANSPORT_SERVICE_TYPE, arg_list);
+ if(!p_response)
+ {
+ return VLC_EGENERIC;
+ }
+ ixmlDocument_free(p_response);
+ return VLC_SUCCESS;
+}
+
+int MediaRenderer::SetAVTransportURI(const char* uri)
+{
+ std::list<std::pair<const char*, const char*>> arg_list;
+ arg_list.push_back(std::make_pair("InstanceID", "0"));
+ arg_list.push_back(std::make_pair("CurrentURI", uri));
+ arg_list.push_back(std::make_pair("CurrentURIMetaData", "")); // NOT_IMPLEMENTED
+
+ IXML_Document *p_response = SendAction("SetAVTransportURI",
+ AV_TRANSPORT_SERVICE_TYPE, arg_list);
+ if(!p_response)
+ {
+ return VLC_EGENERIC;
+ }
+ ixmlDocument_free(p_response);
+ return VLC_SUCCESS;
+}
+
+static void *Add(sout_stream_t *p_stream, const es_format_t *p_fmt)
+{
+ sout_stream_sys_t *p_sys = static_cast<sout_stream_sys_t *>( p_stream->p_sys );
+
+ if (!p_sys->b_supports_video)
+ {
+ if (p_fmt->i_cat != AUDIO_ES)
+ return NULL;
+ }
+
+ 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);
+ p_sys->es_changed = true;
+ }
+ return p_sys_id;
+}
+
+static int Send(sout_stream_t *p_stream, void *id,
+ block_t *p_buffer)
+{
+ sout_stream_sys_t *p_sys = static_cast<sout_stream_sys_t *>( p_stream->p_sys );
+ sout_stream_id_sys_t *id_sys = static_cast<sout_stream_id_sys_t*>( id );
+
+ id_sys = p_sys->GetSubId( p_stream, id_sys );
+ if ( id_sys == NULL )
+ return VLC_EGENERIC;
+
+ return sout_StreamIdSend(p_sys->p_out, id_sys, p_buffer);
+}
+
+static void Flush( sout_stream_t *p_stream, void *id )
+{
+ sout_stream_sys_t *p_sys = static_cast<sout_stream_sys_t *>( p_stream->p_sys );
+ sout_stream_id_sys_t *id_sys = static_cast<sout_stream_id_sys_t*>( id );
+
+ id_sys = p_sys->GetSubId( p_stream, id_sys, false );
+ if ( id_sys == NULL )
+ return;
+
+ sout_StreamFlush( p_sys->p_out, id_sys );
+ p_sys->stopSoutChain( p_stream );
+ p_sys->es_changed = true;
+}
+
+static void Del(sout_stream_t *p_stream, void *_id)
+{
+ sout_stream_sys_t *p_sys = static_cast<sout_stream_sys_t *>( p_stream->p_sys );
+ sout_stream_id_sys_t *id = static_cast<sout_stream_id_sys_t *>( _id );
+
+ for (std::vector<sout_stream_id_sys_t*>::iterator it = p_sys->streams.begin();
+ it != p_sys->streams.end(); )
+ {
+ sout_stream_id_sys_t *p_sys_id = *it;
+ if ( p_sys_id == id )
+ {
+ if ( p_sys_id->p_sub_id != NULL )
+ {
+ sout_StreamIdDel( p_sys->p_out, p_sys_id->p_sub_id );
+ 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);
+ break;
+ }
+ out_it++;
+ }
+ }
+
+ es_format_Clean( &p_sys_id->fmt );
+ free( p_sys_id );
+ p_sys->streams.erase( it );
+ break;
+ }
+ it++;
+ }
+
+ if (p_sys->out_streams.empty())
+ {
+ p_sys->stopSoutChain(p_stream);
+ p_sys->renderer->Stop();
+ }
+}
+
+int OpenSout( vlc_object_t *p_this )
+{
+ sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>(p_this);
+ sout_stream_sys_t *p_sys = NULL;
+
+ config_ChainParse(p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg);
+
+ int http_port = var_InheritInteger(p_stream, SOUT_CFG_PREFIX "http-port");
+ bool b_supports_video = var_GetBool(p_stream, SOUT_CFG_PREFIX "video");
+ char *base_url = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "base_url");
+ char *device_url = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "url");
+ if ( device_url == NULL)
+ {
+ msg_Err( p_stream, "missing Url" );
+ goto error;
+ }
+
+ try {
+ p_sys = new sout_stream_sys_t(http_port, b_supports_video);
+ }
+ catch ( const std::exception& ex ) {
+ msg_Err( p_stream, "Failed to instantiate sout_stream_sys_t: %s", ex.what() );
+ return VLC_EGENERIC;
+ }
+
+ p_sys->p_upnp = UpnpInstanceWrapper::get( p_this );
+ if ( !p_sys->p_upnp )
+ goto error;
+ try {
+ p_sys->renderer = std::make_shared<MediaRenderer>(p_stream,
+ p_sys->p_upnp, base_url, device_url);
+ }
+ catch ( const std::bad_alloc& ) {
+ msg_Err( p_stream, "Failed to create a MediaRenderer");
+ p_sys->p_upnp->release();
+ goto error;
+ }
+
+ p_stream->pf_add = Add;
+ p_stream->pf_del = Del;
+ p_stream->pf_send = Send;
+ p_stream->pf_flush = Flush;
+
+ p_stream->p_sys = p_sys;
+
+ free(base_url);
+ free(device_url);
+
+ return VLC_SUCCESS;
+
+error:
+ free(base_url);
+ free(device_url);
+ delete p_sys;
+ return VLC_EGENERIC;
+}
+
+void CloseSout( vlc_object_t *p_this)
+{
+ sout_stream_t *p_stream = reinterpret_cast<sout_stream_t*>( p_this );
+ sout_stream_sys_t *p_sys = static_cast<sout_stream_sys_t *>( p_stream->p_sys );
+
+ p_sys->p_upnp->release();
+ delete p_sys;
+}
+
+}
diff --git a/modules/stream_out/dlna/dlna.hpp b/modules/stream_out/dlna/dlna.hpp
new file mode 100644
index 0000000000..59ff3d6c8d
--- /dev/null
+++ b/modules/stream_out/dlna/dlna.hpp
@@ -0,0 +1,60 @@
+/*****************************************************************************
+ * dlna.hpp : DLNA/UPNP (renderer) sout module header
+ *****************************************************************************
+ * Copyright (C) 2004-2018 VLC authors and VideoLAN
+ *
+ * Authors: William Ung <william1.ung at epitech.eu>
+ * Shaleen Jain <shaleen at jain.sh>
+ *
+ * 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.
+ *****************************************************************************/
+
+#ifndef DLNA_H
+#define DLNA_H
+
+#include "../../services_discovery/upnp-wrapper.hpp"
+#include "dlna_common.hpp"
+
+namespace DLNA
+{
+
+class MediaRenderer
+{
+public:
+ MediaRenderer(sout_stream_t *p_stream, UpnpInstanceWrapper *upnp,
+ std::string base_url, std::string device_url)
+ : parent(p_stream)
+ , base_url(base_url)
+ , device_url(device_url)
+ , handle(upnp->handle())
+ {
+ }
+
+ sout_stream_t *parent;
+ std::string base_url;
+ std::string device_url;
+ UpnpClient_Handle handle;
+
+ char *getServiceURL(const char* type, const char* service);
+ IXML_Document *SendAction(const char* action_name, const char *service_type,
+ std::list<std::pair<const char*, const char*>> arguments);
+
+ int Play(const char *speed);
+ int Stop();
+ int SetAVTransportURI(const char* uri);
+};
+
+}
+#endif /* DLNA_H */
diff --git a/modules/stream_out/dlna/dlna_common.hpp b/modules/stream_out/dlna/dlna_common.hpp
new file mode 100644
index 0000000000..b82e2510b1
--- /dev/null
+++ b/modules/stream_out/dlna/dlna_common.hpp
@@ -0,0 +1,44 @@
+/*****************************************************************************
+ * dlna.hpp : DLNA/UPNP (renderer) sout module header
+ *****************************************************************************
+ * Copyright (C) 2004-2018 VLC authors and VideoLAN
+ *
+ * Authors: William Ung <william1.ung at epitech.eu>
+ * Shaleen Jain <shaleen at jain.sh>
+ *
+ * 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.
+ *****************************************************************************/
+
+#ifndef DLNA_COMMON_H
+#define DLNA_COMMON_H
+
+#include <list>
+
+#include <vlc_common.h>
+#include <vlc_fourcc.h>
+
+#include "../renderer_common.hpp"
+
+#define SOUT_CFG_PREFIX "sout-dlna-"
+
+namespace DLNA
+{
+
+/* module callbacks */
+int OpenSout(vlc_object_t *);
+void CloseSout(vlc_object_t *);
+
+}
+#endif /* DLNA_COMMON_H */
--
2.19.2
More information about the vlc-devel
mailing list