[PATCH 4/4] dlna: add a DLNA stream out
Shaleen Jain
shaleen at jain.sh
Tue Aug 14 14:13:08 CEST 2018
---
modules/services_discovery/Makefile.am | 4 +-
modules/services_discovery/upnp.cpp | 16 +
modules/services_discovery/upnp.hpp | 1 +
modules/stream_out/dlna.cpp | 690 +++++++++++++++++++++++++
modules/stream_out/dlna.hpp | 64 +++
5 files changed, 774 insertions(+), 1 deletion(-)
create mode 100644 modules/stream_out/dlna.cpp
create mode 100644 modules/stream_out/dlna.hpp
diff --git a/modules/services_discovery/Makefile.am b/modules/services_discovery/Makefile.am
index f63df23b32..3ea850c920 100644
--- a/modules/services_discovery/Makefile.am
+++ b/modules/services_discovery/Makefile.am
@@ -28,7 +28,9 @@ 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/dlna.hpp \
+ stream_out/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 8e7e32147d..7765f7a016 100644
--- a/modules/services_discovery/upnp.cpp
+++ b/modules/services_discovery/upnp.cpp
@@ -147,6 +147,22 @@ 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(Sout::OpenSout, Sout::CloseSout)
+
+ add_string(SOUT_CFG_PREFIX "ip", NULL, IP_ADDR_TEXT, IP_ADDR_LONGTEXT, false)
+ add_integer(SOUT_CFG_PREFIX "port", UPNP_CONTROL_PORT, 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 "mux", DEFAULT_MUXER, MUX_TEXT, MUX_LONGTEXT, false)
+ add_string(SOUT_CFG_PREFIX "mime", "video/x-matroska", MIME_TEXT, MIME_LONGTEXT, false)
+ add_string(SOUT_CFG_PREFIX "url", NULL, URL_TEXT, URL_LONGTEXT, false)
vlc_module_end()
/*
diff --git a/modules/services_discovery/upnp.hpp b/modules/services_discovery/upnp.hpp
index 4079692cd1..adf3503d2d 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.hpp"
#include <vlc_url.h>
diff --git a/modules/stream_out/dlna.cpp b/modules/stream_out/dlna.cpp
new file mode 100644
index 0000000000..4a7fd7008e
--- /dev/null
+++ b/modules/stream_out/dlna.cpp
@@ -0,0 +1,690 @@
+/*****************************************************************************
+ * 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_plugin.h>
+#include <vlc_renderer_discovery.h>
+#include <vlc_sout.h>
+
+const char* AV_TRANSPORT_SERVICE_TYPE = "urn:schemas-upnp-org:service:AVTransport:1";
+const char* CONNECTION_MANAGER_SERVICE_TYPE = "urn:schemas-upnp-org:service:ConnectionManager:1";
+
+static const char *const ppsz_sout_options[] = {
+ "ip", "port", "http-port", "mux", "mime", "video", "url", NULL
+};
+
+struct sout_stream_id_sys_t
+{
+ es_format_t fmt;
+ sout_stream_id_sys_t *p_sub_id;
+};
+
+struct sout_stream_sys_t
+{
+ UpnpInstanceWrapper *p_upnp;
+ Sout::MediaRenderer *renderer;
+
+ bool canDecodeAudio( vlc_fourcc_t i_codec ) const;
+ bool canDecodeVideo( vlc_fourcc_t i_codec ) const;
+ sout_stream_id_sys_t *GetSubId( sout_stream_t *p_stream,
+ sout_stream_id_sys_t *id );
+
+ sout_stream_t *p_out;
+ std::string sout;
+ std::string default_muxer;
+ std::string default_mime;
+ bool b_supports_video;
+ bool es_changed;
+ int http_port;
+
+ std::vector<sout_stream_id_sys_t*> streams;
+
+private:
+ int UpdateOutput( sout_stream_t *p_stream );
+
+};
+
+bool sout_stream_sys_t::canDecodeAudio(vlc_fourcc_t i_codec) 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;
+}
+
+bool sout_stream_sys_t::canDecodeVideo(vlc_fourcc_t i_codec) const
+{
+ return i_codec == VLC_CODEC_H264 || i_codec == VLC_CODEC_VP8;
+}
+
+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 );
+
+ if ( UpdateOutput( p_stream ) != VLC_SUCCESS )
+ return NULL;
+
+ for (i = 0; i < streams.size(); ++i)
+ {
+ if ( id == (sout_stream_id_sys_t*) streams[i] )
+ return streams[i]->p_sub_id;
+ }
+
+ msg_Err( p_stream, "unknown stream ID" );
+ return NULL;
+}
+
+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;
+ vlc_fourcc_t i_codec_video = 0, i_codec_audio = 0;
+
+ 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 (!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 );
+ canRemux = false;
+ }
+ else if (i_codec_audio == 0)
+ i_codec_audio = p_es->i_codec;
+ }
+ 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 );
+ canRemux = false;
+ }
+ else if (i_codec_video == 0)
+ i_codec_video = p_es->i_codec;
+ }
+ }
+
+ std::stringstream ssout;
+ if ( !canRemux )
+ {
+ /* TODO: provide audio samplerate and channels */
+ ssout << "transcode{";
+ char s_fourcc[5];
+ if ( i_codec_audio == 0 )
+ {
+ i_codec_audio = DEFAULT_TRANSCODE_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 );
+ s_fourcc[4] = '\0';
+ ssout << s_fourcc << ',';
+ }
+ if ( b_supports_video && i_codec_video == 0 )
+ {
+ i_codec_video = DEFAULT_TRANSCODE_VIDEO;
+ msg_Dbg( p_stream, "Converting video to %.4s",
+ (const char*)&i_codec_video );
+ /* TODO: provide maxwidth,maxheight */
+ ssout << "vcodec=";
+ vlc_fourcc_to_char( i_codec_video, s_fourcc );
+ s_fourcc[4] = '\0';
+ ssout << s_fourcc;
+ }
+ ssout << "}:";
+ }
+ std::string mime;
+ if ( !b_supports_video && default_muxer == DEFAULT_MUXER )
+ 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;
+
+ /* On some renderers, the stream must finish by the extension of file played*/
+ ssout << "http{dst=:" << http_port << "/stream.mp4"
+ << ",mux=" << default_muxer
+ << ",access=http{mime=" << mime << "}}";
+
+ if ( sout != ssout.str() )
+ {
+ const std::string chain = ssout.str();
+ msg_Dbg( p_stream, "Creating chain %s", chain.c_str() );
+ p_out = sout_StreamChainNew( p_stream->p_sout, chain.c_str(), NULL, NULL);
+ if (p_out == NULL) {
+ msg_Dbg(p_stream, "could not create sout chain:%s", chain.c_str());
+ return VLC_EGENERIC;
+ }
+ sout = chain;
+
+ char *ip = getIpv4ForMulticast();
+ if (ip == NULL)
+ {
+ ip = UpnpGetServerIpAddress();
+ }
+ if (ip == NULL)
+ {
+ msg_Dbg(p_stream, "could not get the local ip address");
+ return VLC_EGENERIC;
+ }
+
+ char *uri;
+ if (asprintf(&uri, "http://%s:%d/stream.mp4", ip, http_port) < 0) {
+ return VLC_ENOMEM;
+ }
+
+ msg_Dbg(p_stream, "AVTransportURI: %s", uri);
+ renderer->SetAVTransportURI(uri);
+ renderer->Play("1");
+ }
+
+ /* check the streams we can actually add */
+ for (std::vector<sout_stream_id_sys_t*>::iterator it = streams.begin();
+ it != streams.end(); )
+ {
+ sout_stream_id_sys_t *p_sys_id = *it;
+ p_sys_id->p_sub_id = reinterpret_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 );
+ it = streams.erase( it );
+ }
+ else
+ ++it;
+ }
+ return VLC_SUCCESS;
+}
+
+namespace Sout
+{
+
+char *MediaRenderer::getServiceURL(const char* type, const char *service)
+{
+ IXML_Document *p_description_doc = NULL;
+
+ UpnpDownloadXmlDoc(url.c_str(), &p_description_doc);
+ if (!p_description_doc)
+ return NULL;
+
+ IXML_NodeList* p_device_list = ixmlDocument_getElementsByTagName( p_description_doc, "device");
+ 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 *ret;
+ if (asprintf(&ret, "http://%s:%d%s", ip.c_str(), device_port, psz_control_url) < 0)
+ return NULL;
+ return ret;
+ }
+ }
+ return NULL;
+}
+
+int MediaRenderer::Play(const char *speed)
+{
+ IXML_Document* p_action = NULL;
+ IXML_Document* p_response = NULL;
+ int i_res;
+ const char *urn = "urn:schemas-upnp-org:service:AVTransport:1";
+
+ i_res = UpnpAddToAction( &p_action, "Play",
+ urn, "InstanceID", "0");
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ i_res = UpnpAddToAction( &p_action, "Play",
+ urn, "Speed", speed);
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ char *actionUrl = getServiceURL(urn, "controlURL");
+ if (actionUrl == NULL)
+ return VLC_EGENERIC;
+ i_res = UpnpSendAction(handle, actionUrl, urn, NULL, p_action, &p_response);
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ ixmlDocument_free(p_response);
+ ixmlDocument_free(p_action);
+ return VLC_SUCCESS;
+}
+
+int MediaRenderer::Pause()
+{
+ IXML_Document* p_action = NULL;
+ IXML_Document* p_response = NULL;
+ int i_res;
+ const char *urn = AV_TRANSPORT_SERVICE_TYPE;
+
+ i_res = UpnpAddToAction( &p_action, "Pause",
+ urn, "InstanceID", "0");
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ char *actionUrl = getServiceURL(urn, "controlURL");
+ if (actionUrl == NULL)
+ return VLC_EGENERIC;
+ i_res = UpnpSendAction(handle, actionUrl, urn, NULL, p_action, &p_response);
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ ixmlDocument_free(p_response);
+ ixmlDocument_free(p_action);
+ return VLC_SUCCESS;
+}
+
+int MediaRenderer::Stop()
+{
+ IXML_Document* p_action = NULL;
+ IXML_Document* p_response = NULL;
+ int i_res;
+ const char *urn = AV_TRANSPORT_SERVICE_TYPE;
+
+ i_res = UpnpAddToAction( &p_action, "Stop",
+ urn, "InstanceID", "0");
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ char *actionUrl = getServiceURL(urn, "controlURL");
+ if (actionUrl == NULL)
+ return VLC_EGENERIC;
+ i_res = UpnpSendAction(handle, actionUrl, urn, NULL, p_action, &p_response);
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ ixmlDocument_free(p_response);
+ ixmlDocument_free(p_action);
+ return VLC_SUCCESS;
+}
+
+int MediaRenderer::Next()
+{
+ IXML_Document* p_action = NULL;
+ IXML_Document* p_response = NULL;
+ int i_res;
+ const char *urn = AV_TRANSPORT_SERVICE_TYPE;
+
+ i_res = UpnpAddToAction( &p_action, "Next",
+ urn, "InstanceID", "0");
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ char *actionUrl = getServiceURL(urn, "controlURL");
+ if (actionUrl == NULL)
+ return VLC_EGENERIC;
+ i_res = UpnpSendAction(handle, actionUrl, urn, NULL, p_action, &p_response);
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ ixmlDocument_free(p_response);
+ ixmlDocument_free(p_action);
+ return VLC_SUCCESS;
+}
+
+int MediaRenderer::Previous()
+{
+ IXML_Document* p_action = NULL;
+ IXML_Document* p_response = NULL;
+ int i_res;
+ const char *urn = AV_TRANSPORT_SERVICE_TYPE;
+
+ i_res = UpnpAddToAction( &p_action, "Previous",
+ urn, "InstanceID", "0");
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ char *actionUrl = getServiceURL(urn, "controlURL");
+ if (actionUrl == NULL)
+ return VLC_EGENERIC;
+ i_res = UpnpSendAction(handle, actionUrl, urn, NULL, p_action, &p_response);
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ ixmlDocument_free(p_response);
+ ixmlDocument_free(p_action);
+ return VLC_SUCCESS;
+}
+
+int MediaRenderer::GetDeviceCapabilities()
+{
+ IXML_Document* p_action = NULL;
+ IXML_Document* p_response = NULL;
+ int i_res;
+ const char *urn = AV_TRANSPORT_SERVICE_TYPE;
+
+ i_res = UpnpAddToAction( &p_action, "GetDeviceCapabilities",
+ urn, "InstanceID", "0");
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ char *actionUrl = getServiceURL(urn, "controlURL");
+ if (actionUrl == NULL)
+ return VLC_EGENERIC;
+ i_res = UpnpSendAction(handle, actionUrl, urn, NULL, p_action, &p_response);
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ ixmlDocument_free(p_response);
+ ixmlDocument_free(p_action);
+ return VLC_SUCCESS;
+}
+
+int MediaRenderer::GetMediaInfo()
+{
+ IXML_Document* p_action = NULL;
+ IXML_Document* p_response = NULL;
+ int i_res;
+ const char *urn = AV_TRANSPORT_SERVICE_TYPE;
+
+ i_res = UpnpAddToAction( &p_action, "GetMediaInfo",
+ urn, "InstanceID", "0");
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ char *actionUrl = getServiceURL(urn, "controlURL");
+ if (actionUrl == NULL)
+ return VLC_EGENERIC;
+ i_res = UpnpSendAction(handle, actionUrl, urn, NULL, p_action, &p_response);
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ ixmlDocument_free(p_response);
+ ixmlDocument_free(p_action);
+ return VLC_SUCCESS;
+}
+
+int MediaRenderer::GetTransportSettings()
+{
+ IXML_Document* p_action = NULL;
+ IXML_Document* p_response = NULL;
+ int i_res;
+ const char *urn = AV_TRANSPORT_SERVICE_TYPE;
+
+ i_res = UpnpAddToAction( &p_action, "GetTransportSettings",
+ urn, "InstanceID", "0");
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ char *actionUrl = getServiceURL(urn, "controlURL");
+ if (actionUrl == NULL)
+ return VLC_EGENERIC;
+ i_res = UpnpSendAction(handle, actionUrl, urn, NULL, p_action, &p_response);
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ ixmlDocument_free(p_response);
+ ixmlDocument_free(p_action);
+ return VLC_SUCCESS;
+}
+
+int MediaRenderer::GetTransportInfo()
+{
+ IXML_Document* p_action = NULL;
+ IXML_Document* p_response = NULL;
+ int i_res;
+ const char *urn = AV_TRANSPORT_SERVICE_TYPE;
+
+ i_res = UpnpAddToAction( &p_action, "GetTransportInfo",
+ urn, "InstanceID", "0");
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ char *actionUrl = getServiceURL(urn, "controlURL");
+ if (actionUrl == NULL)
+ return VLC_EGENERIC;
+ i_res = UpnpSendAction(handle, actionUrl, urn, NULL, p_action, &p_response);
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ ixmlDocument_free(p_response);
+ ixmlDocument_free(p_action);
+ return VLC_SUCCESS;
+}
+
+int MediaRenderer::SetAVTransportURI(const char* uri)
+{
+ IXML_Document* p_action = NULL;
+ IXML_Document* p_response = NULL;
+ int i_res;
+ const char *urn = AV_TRANSPORT_SERVICE_TYPE;
+
+ i_res = UpnpAddToAction( &p_action, "SetAVTransportURI",
+ urn, "InstanceID", "0");
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ i_res = UpnpAddToAction( &p_action, "SetAVTransportURI",
+ urn, "CurrentURI", uri);
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ i_res = UpnpAddToAction( &p_action, "SetAVTransportURI",
+ urn, "CurrentURIMetaData", "NOT_IMPLEMENTED");
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ char *actionUrl = getServiceURL(urn, "controlURL");
+ if (actionUrl == NULL)
+ return VLC_EGENERIC;
+ i_res = UpnpSendAction(handle, actionUrl, urn, NULL, p_action, &p_response);
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ ixmlDocument_free(p_response);
+ ixmlDocument_free(p_action);
+ return VLC_SUCCESS;
+}
+
+int MediaRenderer::Subscribe()
+{
+ Upnp_SID sid;
+ int timeout = 5;
+ int i_res;
+
+ char *url = getServiceURL("urn:schemas-upnp-org:service:RenderingControl:1", "eventSubURL");
+ i_res = UpnpSubscribe(handle, url, &timeout, sid);
+ if (i_res != UPNP_E_SUCCESS)
+ return VLC_EGENERIC;
+ return VLC_SUCCESS;
+}
+
+MediaRenderer::MediaRenderer(sout_stream_t *p_stream)
+{
+ config_ChainParse(p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg);
+ ip = var_GetNonEmptyString( p_stream, SOUT_CFG_PREFIX "ip");
+ device_port = var_InheritInteger(p_stream, SOUT_CFG_PREFIX "port");
+ url = var_GetNonEmptyString (p_stream, SOUT_CFG_PREFIX "url");
+ sout_stream_sys_t *p_sys = reinterpret_cast<sout_stream_sys_t *>( p_stream->p_sys );
+ handle = p_sys->p_upnp->handle();
+}
+
+static void *Add(sout_stream_t *p_stream, const es_format_t *p_fmt)
+{
+ sout_stream_sys_t *p_sys = reinterpret_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 = reinterpret_cast<sout_stream_sys_t *>( p_stream->p_sys );
+ sout_stream_id_sys_t *id_sys = reinterpret_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 = reinterpret_cast<sout_stream_sys_t *>( p_stream->p_sys );
+ sout_stream_id_sys_t *id_sys = reinterpret_cast<sout_stream_id_sys_t*>( id );
+
+ id_sys = p_sys->GetSubId( p_stream, id_sys );
+ if ( id_sys == NULL )
+ return;
+
+ sout_StreamFlush( p_sys->p_out, id_sys );
+}
+
+static int Control(sout_stream_t *p_stream, int i_query, va_list args)
+{
+ sout_stream_sys_t *p_sys = reinterpret_cast<sout_stream_sys_t *>( p_stream->p_sys );
+
+ if (i_query == SOUT_STREAM_EMPTY)
+ return VLC_SUCCESS;
+ if (!p_sys->p_out->pf_control)
+ return VLC_EGENERIC;
+ return p_sys->p_out->pf_control(p_sys->p_out, i_query, args);
+}
+
+static void Del(sout_stream_t *p_stream, void *id)
+{
+ sout_stream_sys_t *p_sys = reinterpret_cast<sout_stream_sys_t *>( p_stream->p_sys );
+
+ for (size_t i = 0; i < p_sys->streams.size(); i++)
+ {
+ if (p_sys->streams[i] == id)
+ {
+ if ( p_sys->streams[i]->p_sub_id != NULL )
+ sout_StreamIdDel( p_sys->p_out, p_sys->streams[i]->p_sub_id );
+
+ es_format_Clean( &p_sys->streams[i]->fmt );
+ free( p_sys->streams[i] );
+ p_sys->streams.erase( p_sys->streams.begin() + i );
+ p_sys->es_changed = true;
+ break;
+ }
+ }
+
+ if (p_sys->streams.empty())
+ {
+ p_sys->renderer->Stop();
+
+ sout_StreamChainDelete(p_sys->p_out, NULL);
+ p_sys->p_out = NULL;
+ p_sys->sout = "";
+ }
+}
+
+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 = new(std::nothrow) sout_stream_sys_t;
+
+ p_stream->p_sys = p_sys;
+ if (!p_stream->p_sys)
+ return VLC_ENOMEM;
+
+ p_sys->p_upnp = UpnpInstanceWrapper::get( p_this );
+ if ( !p_sys->p_upnp )
+ {
+ delete p_sys;
+ return VLC_EGENERIC;
+ }
+
+ p_sys->renderer = new(std::nothrow) MediaRenderer(p_stream);
+ if ( !p_sys->renderer )
+ {
+ p_sys->p_upnp->release();
+ delete p_sys;
+ return VLC_EGENERIC;
+ }
+
+ if ( p_sys->renderer->url.c_str() == NULL)
+ {
+ msg_Err( p_stream, "missing Url" );
+ p_sys->p_upnp->release();
+ delete p_sys;
+ return VLC_EGENERIC;
+ }
+
+ p_sys->renderer->Subscribe();
+
+ p_sys->http_port = var_InheritInteger(p_stream, SOUT_CFG_PREFIX "http-port");
+ p_sys->b_supports_video = var_GetBool(p_stream, SOUT_CFG_PREFIX "video");
+ p_sys->default_muxer = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "mux");
+ p_sys->default_mime = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "mime");
+
+ 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;
+
+ return VLC_SUCCESS;
+}
+
+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 = reinterpret_cast<sout_stream_sys_t *>( p_stream->p_sys );
+
+ delete p_sys;
+}
+
+}
diff --git a/modules/stream_out/dlna.hpp b/modules/stream_out/dlna.hpp
new file mode 100644
index 0000000000..d0030904d1
--- /dev/null
+++ b/modules/stream_out/dlna.hpp
@@ -0,0 +1,64 @@
+#include "../services_discovery/upnp-wrapper.hpp"
+
+#include <vlc_fourcc.h>
+
+#define SOUT_CFG_PREFIX "sout-upnp-"
+
+#define UPNP_CONTROL_PORT 8009
+#define HTTP_PORT 8010
+
+#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 MUX_TEXT N_("Muxer")
+#define MUX_LONGTEXT N_("This sets the muxer used to stream to the UPnP Renderer.")
+#define MIME_TEXT N_("MIME content type")
+#define MIME_LONGTEXT N_("This sets the media MIME content type sent to the UPnP Renderer.")
+
+#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 URL_TEXT N_("description URL")
+#define URL_LONGTEXT N_("The Url used to get the xml descriptor of the UPnP Renderer")
+
+static const vlc_fourcc_t DEFAULT_TRANSCODE_AUDIO = VLC_CODEC_MP3;
+static const vlc_fourcc_t DEFAULT_TRANSCODE_VIDEO = VLC_CODEC_H264;
+static const char DEFAULT_MUXER[] = "avformat{mux=matroska,options={live=1}}}";
+
+namespace Sout
+{
+
+/* module callbacks */
+int OpenSout(vlc_object_t *);
+void CloseSout(vlc_object_t *);
+
+struct MediaRenderer
+{
+ MediaRenderer(sout_stream_t *p_stream);
+
+ ~MediaRenderer();
+
+ std::string ip;
+ std::string url;
+ int device_port;
+ UpnpClient_Handle handle;
+
+ char *getServiceURL(const char* type, const char* service);
+
+ int Subscribe();
+ int Play(const char *speed);
+ int Pause();
+ int Stop();
+ int Next();
+ int Previous();
+ int Seek();
+ int GetDeviceCapabilities();
+ int GetTransportSettings();
+ int GetMediaInfo();
+ int GetTransportInfo();
+ int SetAVTransportURI(const char* uri);
+};
+
+}
--
2.18.0
More information about the vlc-devel
mailing list