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

Jean-Baptiste Kempf jb at videolan.org
Tue Sep 4 19:24:25 CEST 2018


Hello,

Can you explain how you query what codecs are supported by the devices?

Because for example, I doubt many devices support Opus inside Mp4.

Best,

On Tue, 4 Sep 2018, at 13:14, 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";
> +
> +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;
> +}
> +
> +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 );
> +
> +    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());
> +        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;
> +
> +    if ( unlikely( p_out != NULL ) )
> +    {
> +        for ( size_t i = 0; i < out_streams.size(); i++ )
> +        {
> +            if ( out_streams[i]->p_sub_id != NULL )
> +            {
> +                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;
> +
> +    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;
> +    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 << "}}";
> +
> +    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;
> +
> +    UpnpDownloadXmlDoc(device_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* 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);
> +
> +    /* Add argument to action */
> +    std::list<std::pair<const char*, const char*>>::iterator arg;
> +    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;
> +    }
> +
> +    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);
> +};
> +
> +}
> -- 
> 2.18.0
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel


-- 
Jean-Baptiste Kempf -  President
+33 672 704 734


More information about the vlc-devel mailing list