[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