[vlc-devel] [PATCH 4/4] dlna: add a DLNA stream out

Filip Roséen filip at atch.se
Thu Sep 20 16:02:39 CEST 2018


Hi Shaleen,

For what it is worth, do note that you are allowed to implement this
using `c++11` (which would come in handy to shorten some of the paths
in your implementation).

I do agree with j-b though related to querying what formats the upnp
device actually supports, nonetheless: good job, and keep it up.

On 2018-09-04 16:44, Shaleen Jain wrote:

> ---
>  NEWS                                   |   3 +
>  modules/MODULES_LIST                   |   1 +
>  modules/services_discovery/Makefile.am |   4 +-
>  modules/services_discovery/upnp.cpp    |  17 +
>  modules/services_discovery/upnp.hpp    |   1 +
>  modules/stream_out/dlna.cpp            | 620 +++++++++++++++++++++++++
>  modules/stream_out/dlna.hpp            |  94 ++++
>  7 files changed, 739 insertions(+), 1 deletion(-)
>  create mode 100644 modules/stream_out/dlna.cpp
>  create mode 100644 modules/stream_out/dlna.hpp
> 
> diff --git a/NEWS b/NEWS
> index 6bc94096de..c4dbef7e7d 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -37,6 +37,9 @@ Video output:
>   * Remove RealRTSP plugin
>   * Remove Real demuxer plugin
>  
> +Stream Output:
> + * Add DLNA/UPNP stream output module
> +
>  macOS:
>   * Remove Growl notification support
>  
> diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST
> index e1978cc6e4..04256242a1 100644
> --- a/modules/MODULES_LIST
> +++ b/modules/MODULES_LIST
> @@ -375,6 +375,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..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 6642e2b980..a75ec34dbd 100644
> --- a/modules/services_discovery/upnp.cpp
> +++ b/modules/services_discovery/upnp.cpp
> @@ -147,6 +147,23 @@ 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", 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 "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 "base_url", NULL, BASE_URL_TEXT, BASE_URL_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 5a0de1f284..711ef200c5 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>
>  #include <vlc_interrupt.h>
> diff --git a/modules/stream_out/dlna.cpp b/modules/stream_out/dlna.cpp
> new file mode 100644
> index 0000000000..8fe1e27ccc
> --- /dev/null
> +++ b/modules/stream_out/dlna.cpp
> @@ -0,0 +1,620 @@
> +/*****************************************************************************
> + * 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";

The above should either be declared in an anonymous namespace, or
explicitly declared `static`.

> +
> +static const char *const ppsz_sout_options[] = {
> +    "ip", "port", "http-port", "mux", "mime", "video", "base_url", "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
> +{
> +    sout_stream_sys_t(int http_port, bool supports_video,
> +            std::string default_mux, std::string default_mime)
> +        : p_out(NULL)
> +        , default_muxer(default_mux)
> +        , default_mime(default_mime)
> +        , b_supports_video(supports_video)
> +        , es_changed(true)
> +        , http_port(http_port)
> +    {
> +    }
> +
> +    std::shared_ptr<Sout::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;
> +    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;
> +    std::vector<sout_stream_id_sys_t*>  out_streams;
> +
> +    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;

Might be easier read if written as a switch, and avoids you having to
prefix each line with `i_codec ==`.

> +}
> +
> +bool sout_stream_sys_t::canDecodeVideo(vlc_fourcc_t i_codec) const
> +{
> +    return i_codec == VLC_CODEC_H264 || i_codec == VLC_CODEC_VP8;
> +}
> +
> +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)
> +{
> +    stopSoutChain( p_stream );

I can understand the rationale for unconditionally stopping any
previous instance on start, but it is not immediately obvious when one
sees an invocation of `startSoutChain`, and as such I would personally
prefer if the calleer is responsible for stopping any previous stream
prior to starting new ones (if that is intended).

> +
> +    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_Dbg(p_stream, "could not create sout chain:%s", sout.c_str());

Not being able to create a sout-chain must be worthy of something
higher than `msg_Dbg`, especially that no other diagnostic is issued
on the path that leads to the above.

> +        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)
> +{
> +    (void) p_stream;

`VLC_UNUSED`

> +
> +    if ( unlikely( p_out != NULL ) )

I would consider it a usage-error if `stopSoutChain` is invoked with
`NULL`, so is it not safe to leave out the above check - and, if it is
even possible to end up with such case, handle it ouside of this
function?

> +    {
> +        for ( size_t i = 0; i < out_streams.size(); i++ )
> +        {
> +            if ( out_streams[i]->p_sub_id != NULL )
> +            {

Maybe I am missing something, but I don't see any case where
`out_streams[i]->p_sub_id` would be `NULL`, but maybe I read the
implementation a little bit too fast.

> +                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)
> +{
> +    size_t i;

no need to declare `i` in this scope, do it as part of the for-loop
where it is actually relevant.

> +
> +    assert( p_stream->p_sys == this );
> +
> +    if ( update && UpdateOutput( p_stream ) != VLC_SUCCESS )
> +        return NULL;
> +
> +    for (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;
> +}
> +
> +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;
> +    std::vector<sout_stream_id_sys_t*> new_streams;
> +
> +    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;
> +            }
> +            new_streams.push_back(*it);
> +        }
> +        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;
> +            }
> +            new_streams.push_back(*it);
> +        }
> +    }
> +
> +    if (new_streams.empty())
> +    {
> +        return VLC_SUCCESS;
> +    }
> +
> +    std::stringstream ssout;

Use `std::ostringstream` as you are only writing to it.

> +    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;
> +
> +    ssout << "http{dst=:" << http_port << "/stream.mp4"
> +          << ",mux=" << default_muxer
> +          << ",access=http{mime=" << mime << "}}";

This suffers from the same problem as a previous bug in
`stream_out/chromecast`, see the below linked ticket.

 - https://trac.videolan.org/vlc/ticket/20380
 - https://trac.videolan.org/vlc/ticket/20890
 - http://git.videolan.org/?p=vlc.git;a=commit;h=710a2ef49669806e208d6845a494a01ed90b18db

> +
> +    if ( !startSoutChain( p_stream, new_streams, ssout.str() ) )
> +        return VLC_EGENERIC;
> +
> +    char *ip = getIpv4ForMulticast();
> +    if (ip == NULL)
> +    {
> +        ip = UpnpGetServerIpAddress();
> +    }
> +    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/stream.mp4", ip, http_port) < 0) {
> +        return VLC_ENOMEM;
> +    }
> +
> +    msg_Dbg(p_stream, "AVTransportURI: %s", uri);
> +    renderer->SetAVTransportURI(uri);
> +    renderer->Play("1");
> +
> +    return VLC_SUCCESS;
> +}
> +
> +namespace Sout
> +{
> +
> +char *MediaRenderer::getServiceURL(const char* type, const char *service)
> +{
> +    IXML_Document *p_description_doc = NULL;

This resource is never released, shouldn't it be to prevent leaks?

> +
> +    UpnpDownloadXmlDoc(device_url.c_str(), &p_description_doc);

Does the specification of `UpnpDownloadXmlDoc` really guarantee that
`p_description_doc` will be left untouched in case of error? If not
the error-check below is somewhat useless.

> +    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* psz_url = ( char* ) malloc( base_url.length() + strlen( psz_control_url ) + 1 );
> +            if ( psz_url )
> +            {
> +                if ( UpnpResolveURL( base_url.c_str(), psz_control_url, psz_url ) == UPNP_E_SUCCESS )
> +                    return psz_url;
> +            }
> +            free(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)
> +{
> +
> +    int ret;
> +    IXML_Document *action = NULL, *response = NULL;
> +
> +    /* Create action */
> +    action = UpnpMakeAction(action_name, service_type, 0, NULL);

This applies to the entire implementation, but if there is no need to
separate declaration from initialization, please do both at the same
time. It generally makes the implementation easier to read.

> +
> +    /* Add argument to action */
> +    std::list<std::pair<const char*, const char*>>::iterator arg;

If you are worried about the loop head getting too long and hard to
read, please introduce a `typedef` (or equivalent) instead of
declararing variables in a scope larger than necessary.

> +    for (arg=arguments.begin(); arg != arguments.end(); ++arg) {
> +      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 */
> +    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: %d (%s)\n", ret, UpnpGetErrorMessage(ret));
> +        if (response) ixmlDocument_free(response);
> +        return NULL;
> +    }
> +
> +    return response;
> +}
> +
> +int MediaRenderer::Play(const char *speed)
> +{
> +    IXML_Document* p_response = NULL;
> +    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));
> +
> +    p_response = SendAction("Play", AV_TRANSPORT_SERVICE_TYPE, arg_list);
> +    ixmlDocument_free(p_response);
> +    return VLC_SUCCESS;
> +}
> +
> +int MediaRenderer::Stop()
> +{
> +    IXML_Document* p_response = NULL;
> +    std::list<std::pair<const char*, const char*>> arg_list;
> +    arg_list.push_back(std::make_pair("InstanceID", "0"));
> +
> +    p_response = SendAction("Stop", AV_TRANSPORT_SERVICE_TYPE, arg_list);
> +    ixmlDocument_free(p_response);
> +    return VLC_SUCCESS;
> +}
> +
> +int MediaRenderer::SetAVTransportURI(const char* uri)
> +{
> +    IXML_Document* p_response = NULL;
> +    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
> +
> +    p_response = SendAction("SetAVTransportURI", AV_TRANSPORT_SERVICE_TYPE, arg_list);
> +    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 );
> +}
> +
> +static int Control(sout_stream_t *p_stream, int i_query, va_list args)
> +{
> +    sout_stream_sys_t *p_sys = static_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 = 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;
> +    bool b_supports_video = true;
> +    char *default_muxer = NULL;
> +    char *default_mime = NULL;
> +    char *base_url = NULL;
> +    char *device_url = NULL;
> +    int http_port;
> +
> +    config_ChainParse(p_stream, SOUT_CFG_PREFIX, ppsz_sout_options, p_stream->p_cfg);
> +
> +    http_port = var_InheritInteger(p_stream, SOUT_CFG_PREFIX "http-port");
> +    b_supports_video = var_GetBool(p_stream, SOUT_CFG_PREFIX "video");
> +    default_muxer = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "mux");
> +    default_mime = var_GetNonEmptyString(p_stream,  SOUT_CFG_PREFIX "mime");
> +
> +    try {
> +        p_sys = new sout_stream_sys_t(http_port, b_supports_video,
> +                                        default_muxer, default_mime);
> +    } catch ( std::exception& ex ) {
> +        msg_Err( p_stream, "Failed to instantiate sout_stream_sys_t: %s", ex.what() );
> +        return VLC_EGENERIC;
> +    }
> +
> +    base_url = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "base_url");
> +    device_url = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX "url");
> +    if ( device_url == NULL)
> +    {
> +        msg_Err( p_stream, "missing Url" );
> +        goto error;
> +    }
> +
> +    p_sys->p_upnp = UpnpInstanceWrapper::get( p_this );
> +    if ( !p_sys->p_upnp )
> +        goto error;
> +    try {
> +        p_sys->renderer = std::make_shared<Sout::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->pf_control = Control;
> +
> +    p_stream->p_sys = p_sys;
> +
> +    free(default_mime);
> +    free(default_muxer);
> +    free(base_url);
> +    free(device_url);
> +
> +    return VLC_SUCCESS;
> +
> +error:
> +    free(default_mime);
> +    free(default_muxer);
> +    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.hpp b/modules/stream_out/dlna.hpp
> new file mode 100644
> index 0000000000..9f966b8237
> --- /dev/null
> +++ b/modules/stream_out/dlna.hpp
> @@ -0,0 +1,94 @@
> +/*****************************************************************************
> + * 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.
> + *****************************************************************************/
> +
> +#include "../services_discovery/upnp-wrapper.hpp"
> +
> +#include <list>
> +
> +#include <vlc_fourcc.h>
> +
> +#define SOUT_CFG_PREFIX "sout-upnp-"
> +
> +#define HTTP_PORT         8080
> +
> +#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 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 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 *);
> +
> +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())
> +    {
> +    }
> +
> +    ~MediaRenderer()
> +    {
> +         parent = NULL;

The above assignment does nothing.

> +    }
> +
> +    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);
> +};
> +
> +}

Best Regards,\
Filip
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.videolan.org/pipermail/vlc-devel/attachments/20180920/9fda55b6/attachment.html>


More information about the vlc-devel mailing list