[vlc-devel] [PATCH 2/4] dlna: add a DLNA stream out
Hugo Beauzée-Luyssen
hugo at beauzee.fr
Thu Jan 10 12:28:18 CET 2019
Hi,
On Wed, Jan 9, 2019, at 4:10 PM, Shaleen Jain wrote:
> Add support for casting to a DLNA Media Renderers
> implementing the AVTransport service with the
> initial support of the default media format.
> ---
> NEWS | 1 +
> modules/MODULES_LIST | 1 +
> modules/services_discovery/Makefile.am | 7 +-
> modules/services_discovery/upnp.cpp | 33 ++
> modules/services_discovery/upnp.hpp | 1 +
> modules/stream_out/dlna/dlna.cpp | 677 ++++++++++++++++++++++++
> modules/stream_out/dlna/dlna.hpp | 60 +++
> modules/stream_out/dlna/dlna_common.hpp | 43 ++
> po/POTFILES.in | 1 +
> 9 files changed, 823 insertions(+), 1 deletion(-)
> create mode 100644 modules/stream_out/dlna/dlna.cpp
> create mode 100644 modules/stream_out/dlna/dlna.hpp
> create mode 100644 modules/stream_out/dlna/dlna_common.hpp
>
> diff --git a/NEWS b/NEWS
> index bb5cb78bdd..73ffb29152 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -47,6 +47,7 @@ Video output:
> Stream output:
> * New SDI output with improved audio and ancillary support.
> Candidate for deprecation of decklink vout/aout modules.
> + * Support for DLNA/UPNP renderers
>
> macOS:
> * Remove Growl notification support
> diff --git a/modules/MODULES_LIST b/modules/MODULES_LIST
> index 440fb2ab25..20f84cd1c5 100644
> --- a/modules/MODULES_LIST
> +++ b/modules/MODULES_LIST
> @@ -379,6 +379,7 @@ $Id$
> * stream_out_delay: introduce delay in an ES when streaming
> * stream_out_description: helper module for RTSP vod
> * stream_out_display: displays a stream output chain
> + * stream_out_dlna: DLNA streaming output module
> * stream_out_dummy: dummy stream out chain module
> * stream_out_duplicate: duplicates a stream output chain
> * stream_out_es: stream out module outputing ES
> diff --git a/modules/services_discovery/Makefile.am b/modules/
> services_discovery/Makefile.am
> index f63df23b32..1359460477 100644
> --- a/modules/services_discovery/Makefile.am
> +++ b/modules/services_discovery/Makefile.am
> @@ -28,7 +28,12 @@ sd_LTLIBRARIES += $(LTLIBmtp)
>
> libupnp_plugin_la_SOURCES = services_discovery/upnp.cpp
> services_discovery/upnp.hpp \
> services_discovery/upnp-wrapper.hpp \
> - services_discovery/upnp-wrapper.cpp
> + services_discovery/upnp-wrapper.cpp \
> + stream_out/renderer_common.hpp \
> + stream_out/renderer_common.cpp \
> + stream_out/dlna/dlna_common.hpp \
> + stream_out/dlna/dlna.hpp \
> + stream_out/dlna/dlna.cpp
> libupnp_plugin_la_CXXFLAGS = $(AM_CXXFLAGS) $(UPNP_CFLAGS)
> libupnp_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(sddir)'
> libupnp_plugin_la_LIBADD = $(UPNP_LIBS)
> diff --git a/modules/services_discovery/upnp.cpp b/modules/
> services_discovery/upnp.cpp
> index 828511bc12..4c08c26a0b 100644
> --- a/modules/services_discovery/upnp.cpp
> +++ b/modules/services_discovery/upnp.cpp
> @@ -54,6 +54,23 @@ const char* SATIP_SERVER_DEVICE_TYPE =
> "urn:ses-com:device:SatIPServer:1";
> #define UPNP_SEARCH_TIMEOUT_SECONDS 15
> #define SATIP_CHANNEL_LIST N_("SAT>IP channel list")
> #define SATIP_CHANNEL_LIST_URL N_("Custom SAT>IP channel list URL")
> +
> +#define HTTP_PORT 7070
> +
> +#define HTTP_PORT_TEXT N_("HTTP port")
> +#define HTTP_PORT_LONGTEXT N_("This sets the HTTP port of the local
> server used to stream the media to the UPnP Renderer.")
> +#define HAS_VIDEO_TEXT N_("Video")
> +#define HAS_VIDEO_LONGTEXT N_("The UPnP Renderer can receive video.")
> +
> +#define IP_ADDR_TEXT N_("IP Address")
> +#define IP_ADDR_LONGTEXT N_("IP Address of the UPnP Renderer.")
> +#define PORT_TEXT N_("UPnP Renderer port")
> +#define PORT_LONGTEXT N_("The port used to talk to the UPnP Renderer.")
> +#define BASE_URL_TEXT N_("base URL")
> +#define BASE_URL_LONGTEXT N_("The base Url relative to which all other
> UPnP operations must be called")
> +#define URL_TEXT N_("description URL")
> +#define URL_LONGTEXT N_("The Url used to get the xml descriptor of the
> UPnP Renderer")
> +
> static const char *const ppsz_satip_channel_lists[] = {
> "Auto", "ASTRA_19_2E", "ASTRA_28_2E", "ASTRA_23_5E", "MasterList",
> "ServerList", "CustomList"
> };
> @@ -147,6 +164,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(DLNA::OpenSout, DLNA::CloseSout)
> +
> + add_string(SOUT_CFG_PREFIX "ip", NULL, IP_ADDR_TEXT,
> IP_ADDR_LONGTEXT, false)
> + add_integer(SOUT_CFG_PREFIX "port", NULL, PORT_TEXT,
> PORT_LONGTEXT, false)
> + add_integer(SOUT_CFG_PREFIX "http-port", HTTP_PORT,
> HTTP_PORT_TEXT, HTTP_PORT_LONGTEXT, false)
> + add_bool(SOUT_CFG_PREFIX "video", true, HAS_VIDEO_TEXT,
> HAS_VIDEO_LONGTEXT, false)
> + add_string(SOUT_CFG_PREFIX "base_url", NULL, BASE_URL_TEXT,
> BASE_URL_LONGTEXT, false)
> + add_string(SOUT_CFG_PREFIX "url", NULL, URL_TEXT, URL_LONGTEXT,
> false)
> + add_renderer_opts(SOUT_CFG_PREFIX)
> vlc_module_end()
>
> /*
> diff --git a/modules/services_discovery/upnp.hpp b/modules/
> services_discovery/upnp.hpp
> index 5a0de1f284..3b2e80b209 100644
> --- a/modules/services_discovery/upnp.hpp
> +++ b/modules/services_discovery/upnp.hpp
> @@ -33,6 +33,7 @@
> #endif
>
> #include "upnp-wrapper.hpp"
> +#include "../stream_out/dlna/dlna_common.hpp"
>
> #include <vlc_url.h>
> #include <vlc_interrupt.h>
> diff --git a/modules/stream_out/dlna/dlna.cpp b/modules/stream_out/dlna/
> dlna.cpp
> new file mode 100644
> index 0000000000..ccef3fb7b8
> --- /dev/null
> +++ b/modules/stream_out/dlna/dlna.cpp
> @@ -0,0 +1,677 @@
> +/
> *****************************************************************************
> + * dlna.cpp : DLNA/UPNP (renderer) sout module
> +
> *****************************************************************************
> + * Copyright © 2018 VLC authors and VideoLAN
> + *
> + * Authors: Shaleen Jain <shaleen at jain.sh>
> + * William Ung <william1.ung at epitech.eu>
> + *
> + * 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_cxx_helpers.hpp>
> +#include <vlc_dialog.h>
> +#include <vlc_rand.h>
> +#include <vlc_sout.h>
> +
> +static const char* AV_TRANSPORT_SERVICE_TYPE =
> "urn:schemas-upnp-org:service:AVTransport:1";
> +static const char* CONNECTION_MANAGER_SERVICE_TYPE =
> "urn:schemas-upnp-org:service:ConnectionManager:1";
> +
> +static const char *const ppsz_sout_options[] = {
> + "ip", "port", "http-port", "video", "base_url", "url", nullptr
> +};
> +
> +namespace DLNA
> +{
> +
> +struct sout_stream_id_sys_t
> +{
> + es_format_t fmt;
> + sout_stream_id_sys_t *p_sub_id;
> +};
> +
> +struct sout_stream_sys_t
> +{
> + sout_stream_sys_t(int http_port, bool supports_video)
> + : p_out( nullptr )
> + , es_changed( true )
> + , b_supports_video( supports_video )
> + , perf_warning_shown( false )
> + , venc_opt_idx ( -1 )
> + , http_port( http_port )
> + {
> + }
> +
> + std::shared_ptr<MediaRenderer> renderer;
> + UpnpInstanceWrapper *p_upnp;
> +
> + bool canDecodeAudio( vlc_fourcc_t i_codec ) const;
> + bool canDecodeVideo( vlc_fourcc_t i_codec ) const;
> + bool startSoutChain( sout_stream_t* p_stream,
> + const std::vector<sout_stream_id_sys_t*>
> &new_streams,
> + const std::string &sout );
> + void stopSoutChain( sout_stream_t* p_stream );
> + sout_stream_id_sys_t *GetSubId( sout_stream_t *p_stream,
> + sout_stream_id_sys_t *id,
> + bool update = true );
> +
> + sout_stream_t *p_out;
> + bool es_changed;
> + bool b_supports_video;
> + bool perf_warning_shown;
> + int venc_opt_idx;
> + int http_port;
> + std::vector<sout_stream_id_sys_t*> streams;
> + std::vector<sout_stream_id_sys_t*> out_streams;
> +
> +private:
> + std::string GetAcodecOption( sout_stream_t *, vlc_fourcc_t *, const
> audio_format_t *, int );
> + int UpdateOutput( sout_stream_t *p_stream );
> +
> +};
> +
> +char *getServerIPAddress() {
This looks duplicated & slightly modified from upnp-wrapper, can't it refactored to use the same function?
> + char *ip = nullptr;
> +#ifdef UPNP_ENABLE_IPV6
> +#ifdef _WIN32
> + IP_ADAPTER_UNICAST_ADDRESS *p_best_ip = nullptr;
> + wchar_t psz_uri[32];
> + DWORD strSize;
> + IP_ADAPTER_ADDRESSES *p_adapter, *addresses;
> +
> + addresses = ListAdapters();
> + if (addresses == nullptr)
> + return nullptr;
> +
> + p_adapter = addresses;
> + while (p_adapter != nullptr)
> + {
> + if (isAdapterSuitable(p_adapter, false))
> + {
> + IP_ADAPTER_UNICAST_ADDRESS *p_unicast = p_adapter-
> >FirstUnicastAddress;
> + while (p_unicast != nullptr)
> + {
> + strSize = sizeof( psz_uri ) / sizeof( wchar_t );
> + if( WSAAddressToString( p_unicast->Address.lpSockaddr,
> + p_unicast-
> >Address.iSockaddrLength,
> + nullptr, psz_uri, &strSize ) ==
> 0 )
> + {
> + if ( p_best_ip == nullptr ||
> + p_best_ip->ValidLifetime > p_unicast-
> >ValidLifetime )
> + {
> + p_best_ip = p_unicast;
> + }
> + }
> + p_unicast = p_unicast->Next;
> + }
> + }
> + p_adapter = p_adapter->Next;
> + }
> +
> + if (p_best_ip != nullptr)
> + {
> + strSize = sizeof( psz_uri ) / sizeof( wchar_t );
> + WSAAddressToString( p_best_ip->Address.lpSockaddr,
> + p_best_ip->Address.iSockaddrLength,
> + nullptr, psz_uri, &strSize );
> + free(addresses);
> + return FromWide( psz_uri );
> + }
> + free(addresses);
> + return nullptr;
> +#endif /* _WIN32 */
> +#else /* UPNP_ENABLE_IPV6 */
> + ip = getIpv4ForMulticast();
> +#endif /* UPNP_ENABLE_IPV6 */
> + if (ip == nullptr)
> + {
> + ip = strdup(UpnpGetServerIpAddress());
> + }
> + return ip;
> +}
> +
> +bool sout_stream_sys_t::canDecodeAudio(vlc_fourcc_t i_codec) const
> +{
> + return i_codec == VLC_CODEC_MP4A;
> +}
> +
> +bool sout_stream_sys_t::canDecodeVideo(vlc_fourcc_t i_codec) const
> +{
> + return i_codec == VLC_CODEC_H264;
> +}
> +
> +bool sout_stream_sys_t::startSoutChain(sout_stream_t *p_stream,
> + const
> std::vector<sout_stream_id_sys_t*> &new_streams,
> + const std::string &sout)
> +{
> + msg_Dbg( p_stream, "Creating chain %s", sout.c_str() );
> + out_streams = new_streams;
> +
> + p_out = sout_StreamChainNew( p_stream->p_sout, sout.c_str(),
> nullptr, nullptr);
> + if (p_out == nullptr) {
> + msg_Err(p_stream, "could not create sout chain:%s",
> sout.c_str());
> + out_streams.clear();
> + return false;
> + }
> +
> + /* check the streams we can actually add */
> + for (std::vector<sout_stream_id_sys_t*>::iterator it =
> out_streams.begin();
> + it != out_streams.end(); )
> + {
> + sout_stream_id_sys_t *p_sys_id = *it;
> + p_sys_id->p_sub_id = static_cast<sout_stream_id_sys_t *>(
> + sout_StreamIdAdd( p_out, &p_sys_id->fmt ) );
> + if ( p_sys_id->p_sub_id == nullptr )
> + {
> + msg_Err( p_stream, "can't handle %4.4s stream",
> + (char *)&p_sys_id->fmt.i_codec );
> + es_format_Clean( &p_sys_id->fmt );
> + it = out_streams.erase( it );
> + }
> + else
> + ++it;
> + }
> +
> + if (out_streams.empty())
> + {
> + stopSoutChain( p_stream );
> + return false;
> + }
> +
> + return true;
> +}
> +
> +void sout_stream_sys_t::stopSoutChain(sout_stream_t *p_stream)
> +{
> + msg_Dbg( p_stream, "Destroying dlna sout chain");
> +
> + for ( size_t i = 0; i < out_streams.size(); i++ )
> + {
> + sout_StreamIdDel( p_out, out_streams[i]->p_sub_id );
> + out_streams[i]->p_sub_id = nullptr;
> + }
> + out_streams.clear();
> + sout_StreamChainDelete( p_out, nullptr );
> + p_out = nullptr;
> +}
> +
> +sout_stream_id_sys_t *sout_stream_sys_t::GetSubId( sout_stream_t
> *p_stream,
> + sout_stream_id_sys_t
> *id,
> + bool update)
> +{
> + assert( p_stream->p_sys == this );
> +
> + if ( update && UpdateOutput( p_stream ) != VLC_SUCCESS )
> + return nullptr;
> +
> + for (size_t i = 0; i < out_streams.size(); ++i)
> + {
> + if ( id == (sout_stream_id_sys_t*) out_streams[i] )
> + return out_streams[i]->p_sub_id;
> + }
> +
> + msg_Err( p_stream, "unknown stream ID" );
> + return nullptr;
> +}
> +
> +std::string
> +sout_stream_sys_t::GetAcodecOption( sout_stream_t *p_stream,
> vlc_fourcc_t *p_codec_audio,
> + const audio_format_t *p_aud, int
> i_quality )
> +{
> + VLC_UNUSED(p_aud);
> + VLC_UNUSED(i_quality);
> + std::stringstream ssout;
> +
> + msg_Dbg( p_stream, "Converting audio to %.4s", (const
> char*)p_codec_audio );
> +
> + ssout << "acodec=";
> + char fourcc[5];
> + vlc_fourcc_to_char( *p_codec_audio, fourcc );
> + fourcc[4] = '\0';
> + ssout << fourcc << ',';
> +
> + ssout << "aenc=avcodec{codec=aac},";
> + return ssout.str();
> +}
> +
> +int sout_stream_sys_t::UpdateOutput( sout_stream_t *p_stream )
> +{
> + assert( p_stream->p_sys == this );
> +
> + if ( !es_changed )
> + return VLC_SUCCESS;
> +
> + es_changed = false;
> +
> + bool canRemux = true;
> + // To keep track of which stream needs transcoding if at all.
> + vlc_fourcc_t i_codec_video = 0, i_codec_audio = 0;
> + const es_format_t *p_original_audio = nullptr;
> + const es_format_t *p_original_video = nullptr;
> + std::vector<sout_stream_id_sys_t*> new_streams;
> +
> + for (sout_stream_id_sys_t *stream : streams)
> + {
> + const es_format_t *p_es = &stream->fmt;
> + if (p_es->i_cat == AUDIO_ES)
> + {
> + if (!canDecodeAudio( p_es->i_codec ))
> + {
> + msg_Dbg( p_stream, "can't remux audio track %d codec
> %4.4s",
> + p_es->i_id, (const char*)&p_es->i_codec );
> + p_original_audio = p_es;
> + canRemux = false;
> + }
> + else if (i_codec_audio == 0)
> + {
> + i_codec_audio = p_es->i_codec;
> + }
> + new_streams.push_back(stream);
> + }
> + else if (b_supports_video && p_es->i_cat == VIDEO_ES)
> + {
> + if (!canDecodeVideo( p_es->i_codec ))
> + {
> + msg_Dbg( p_stream, "can't remux video track %d codec
> %4.4s",
> + p_es->i_id, (const char*)&p_es->i_codec );
> + p_original_video = p_es;
> + canRemux = false;
> + }
> + else if (i_codec_video == 0)
> + {
> + i_codec_video = p_es->i_codec;
> + }
> + new_streams.push_back(stream);
> + }
> + }
> +
> + if (new_streams.empty())
> + return VLC_SUCCESS;
> +
> + std::ostringstream ssout;
> + if ( !canRemux )
> + {
> + if ( !perf_warning_shown && i_codec_video == 0 &&
> p_original_video
> + && var_InheritInteger( p_stream, RENDERER_CFG_PREFIX "show-
> perf-warning" ) )
> + {
> + int res = vlc_dialog_wait_question( p_stream,
> + VLC_DIALOG_QUESTION_WARNING,
> + _("Cancel"), _("OK"), _("Ok, Don't warn me
> again"),
> + _("Performance warning"),
> + _("Casting this video requires conversion. "
> + "This conversion can use all the available
> power and "
> + "could quickly drain your battery." ) );
> + if ( res <= 0 )
> + return false;
> + perf_warning_shown = true;
> + if ( res == 2 )
> + config_PutInt(RENDERER_CFG_PREFIX "show-perf-warning",
> 0 );
> + }
> +
> + const int i_quality = var_InheritInteger( p_stream,
> SOUT_CFG_PREFIX "conversion-quality" );
> +
> + /* TODO: provide audio samplerate and channels */
> + ssout << "transcode{";
> + if ( i_codec_audio == 0 && p_original_audio )
> + {
> + i_codec_audio = VLC_CODEC_MP4A;
> + ssout << GetAcodecOption( p_stream, &i_codec_audio,
> + &p_original_audio->audio,
> i_quality );
> + }
> + if ( i_codec_video == 0 && p_original_video )
> + {
> + i_codec_video = VLC_CODEC_H264;
> + try {
> + ssout << vlc_sout_renderer_GetVcodecOption( p_stream,
> + { i_codec_video },
> + &p_original_video->video,
> i_quality );
> + } catch(const std::exception& e) {
> + return VLC_EGENERIC ;
> + }
> + }
> + ssout << "}:";
> + }
> +
> + std::ostringstream ss;
> + ss << "/dlna"
> + << "/" << vlc_tick_now()
> + << "/" << static_cast<uint64_t>( vlc_mrand48() )
> + << "/stream";
> + std::string root_url = ss.str();
> +
> + ssout << "http{dst=:" << http_port << root_url
> + << ",mux=" << "mp4stream"
> + << ",access=http{mime=" << "video/mp4" << "}}";
> +
> + auto ip = vlc::wrap_cptr<char>(getServerIPAddress());
> + if (ip.get() == nullptr)
You can compare std::unique_ptr against nullptr directly
> + {
> + msg_Err(p_stream, "could not get the local ip address");
> + return VLC_EGENERIC;
> + }
> +
> + char *uri;
> + if (asprintf(&uri, "http://%s:%d%s", ip.get(), http_port,
> root_url.c_str()) < 0) {
> + return VLC_ENOMEM;
> + }
> +
> + if ( !startSoutChain( p_stream, new_streams, ssout.str() ) )
Leaking uri
> + return VLC_EGENERIC;
> +
> + msg_Dbg(p_stream, "AVTransportURI: %s", uri);
> + renderer->Stop();
> + renderer->SetAVTransportURI(uri);
> + renderer->Play("1");
> +
> + free(uri);
> + return VLC_SUCCESS;
> +}
> +
> +char *MediaRenderer::getServiceURL(const char* type, const char
> *service)
> +{
> + IXML_Document *p_description_doc = nullptr;
> + if (UpnpDownloadXmlDoc(device_url.c_str(), &p_description_doc) !=
> UPNP_E_SUCCESS)
> + return nullptr;
> +
> + IXML_NodeList* p_device_list =
> ixmlDocument_getElementsByTagName( p_description_doc, "device");
> + free(p_description_doc);
> + if ( !p_device_list )
> + return nullptr;
> +
> + for (unsigned int i = 0; i < ixmlNodeList_length(p_device_list); +
> +i)
> + {
> + IXML_Element* p_device_element = ( IXML_Element* )
> ixmlNodeList_item( p_device_list, i );
> + if( !p_device_element )
> + continue;
> +
> + IXML_NodeList* p_service_list =
> ixmlElement_getElementsByTagName( p_device_element, "service" );
> + if ( !p_service_list )
> + continue;
> + for ( unsigned int j = 0; j <
> ixmlNodeList_length( p_service_list ); j++ )
> + {
> + IXML_Element* p_service_element =
> (IXML_Element*)ixmlNodeList_item( p_service_list, j );
> +
> + const char* psz_service_type =
> xml_getChildElementValue( p_service_element, "serviceType" );
> + if ( !psz_service_type || !strstr(psz_service_type, type))
> + continue;
> + const char* psz_control_url =
> xml_getChildElementValue( p_service_element,
> +
> service );
> + if ( !psz_control_url )
> + continue;
> +
> + char* psz_url = ( char* ) malloc( base_url.length() +
> strlen( psz_control_url ) + 1 );
> + if ( psz_url && UpnpResolveURL( base_url.c_str(),
> psz_control_url, psz_url ) == UPNP_E_SUCCESS )
> + return psz_url;
> + return nullptr;
> + }
> + }
> + return nullptr;
> +}
> +
> +/**
> + * Send an action to the control url of the service specified.
> + *
> + * \return the response as a IXML document or nullptr for failure
> + **/
> +IXML_Document *MediaRenderer::SendAction(const char* action_name,const
> char *service_type,
> + std::list<std::pair<const char*, const char*>>
> arguments)
> +{
> + /* Create action */
> + IXML_Document *action = UpnpMakeAction(action_name, service_type,
> 0, nullptr);
> +
> + /* Add argument to action */
> + for (std::pair<const char*, const char*> arg : arguments) {
> + const char *arg_name, *arg_val;
> + arg_name = arg.first;
> + arg_val = arg.second;
> + UpnpAddToAction(&action, action_name, service_type, arg_name,
> arg_val);
> + }
> +
> + /* Get the controlURL of the service */
> + char *control_url = getServiceURL(service_type, "controlURL");
> +
> + /* Send action */
> + IXML_Document *response = nullptr;
> + int ret = UpnpSendAction(handle, control_url, service_type,
> + nullptr, action, &response);
> +
> + /* Free action */
> + ixmlDocument_free(action);
> + free(control_url);
> +
> + if (ret != UPNP_E_SUCCESS) {
> + msg_Err(parent, "Unable to send action: %s (%d: %s) response:
> %s",
> + action_name, ret, UpnpGetErrorMessage(ret),
> ixmlPrintDocument(response));
> + if (response) ixmlDocument_free(response);
> + return nullptr;
> + }
> +
> + return response;
> +}
> +
> +int MediaRenderer::Play(const char *speed)
> +{
> + std::list<std::pair<const char*, const char*>> arg_list = {
> + {"InstanceID", "0"},
> + {"Speed", speed},
> + };
> +
> + IXML_Document *p_response = SendAction("Play",
> AV_TRANSPORT_SERVICE_TYPE, arg_list);
> + if(!p_response)
> + {
> + return VLC_EGENERIC;
> + }
> + ixmlDocument_free(p_response);
> + return VLC_SUCCESS;
> +}
> +
> +int MediaRenderer::Stop()
> +{
> + std::list<std::pair<const char*, const char*>> arg_list = {
> + {"InstanceID", "0"},
> + };
> +
> + IXML_Document *p_response = SendAction("Stop",
> AV_TRANSPORT_SERVICE_TYPE, arg_list);
> + if(!p_response)
> + {
> + return VLC_EGENERIC;
> + }
> + ixmlDocument_free(p_response);
> + return VLC_SUCCESS;
> +}
> +
> +int MediaRenderer::SetAVTransportURI(const char* uri)
> +{
> + std::list<std::pair<const char*, const char*>> arg_list = {
> + {"InstanceID", "0"},
> + {"CurrentURI", uri},
> + {"CurrentURIMetaData", ""}, // NOT_IMPLEMENTED
> + };
> +
> + IXML_Document *p_response = SendAction("SetAVTransportURI",
> + AV_TRANSPORT_SERVICE_TYPE,
> arg_list);
> + if(!p_response)
> + {
> + return VLC_EGENERIC;
> + }
> + ixmlDocument_free(p_response);
> + return VLC_SUCCESS;
> +}
> +
> +static void *Add(sout_stream_t *p_stream, const es_format_t *p_fmt)
> +{
> + sout_stream_sys_t *p_sys = static_cast<sout_stream_sys_t
> *>( p_stream->p_sys );
> +
> + if (!p_sys->b_supports_video)
> + {
> + if (p_fmt->i_cat != AUDIO_ES)
> + return nullptr;
> + }
> +
> + sout_stream_id_sys_t *p_sys_id = (sout_stream_id_sys_t
> *)malloc(sizeof(sout_stream_id_sys_t));
> + if(p_sys_id != nullptr)
> + {
> + es_format_Copy(&p_sys_id->fmt, p_fmt);
> + p_sys_id->p_sub_id = nullptr;
> + 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 == nullptr )
> + 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 == nullptr )
> + return;
> +
> + sout_StreamFlush( p_sys->p_out, id_sys );
> + p_sys->stopSoutChain( p_stream );
> + p_sys->es_changed = true;
> +}
> +
> +static void Del(sout_stream_t *p_stream, void *_id)
> +{
> + sout_stream_sys_t *p_sys = static_cast<sout_stream_sys_t
> *>( p_stream->p_sys );
> + sout_stream_id_sys_t *id = static_cast<sout_stream_id_sys_t
> *>( _id );
> +
> + for (std::vector<sout_stream_id_sys_t*>::iterator it = p_sys-
> >streams.begin();
This is a good candidate for auto (as are quite a few other places), or in this case, even a range-based loop
> + it != p_sys->streams.end(); ++it)
> + {
> + sout_stream_id_sys_t *p_sys_id = *it;
> + if ( p_sys_id == id )
> + {
> + if ( p_sys_id->p_sub_id != nullptr )
> + {
> + 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;
> + }
> + }
> +
> + 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 = nullptr;
> +
> + config_ChainParse(p_stream, SOUT_CFG_PREFIX, ppsz_sout_options,
> p_stream->p_cfg);
> +
> + int http_port = var_InheritInteger(p_stream, SOUT_CFG_PREFIX "http-
> port");
> + bool b_supports_video = var_GetBool(p_stream, SOUT_CFG_PREFIX
> "video");
> + char *base_url = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX
> "base_url");
> + char *device_url = var_GetNonEmptyString(p_stream, SOUT_CFG_PREFIX
> "url");
> + if ( device_url == nullptr)
> + {
> + msg_Err( p_stream, "missing Url" );
> + goto error;
> + }
> +
> + try {
> + p_sys = new sout_stream_sys_t(http_port, b_supports_video);
> + }
> + catch ( const std::exception& ex ) {
> + msg_Err( p_stream, "Failed to instantiate sout_stream_sys_t:
> %s", ex.what() );
> + return VLC_EGENERIC;
> + }
> +
> + p_sys->p_upnp = UpnpInstanceWrapper::get( p_this );
> + if ( !p_sys->p_upnp )
> + goto error;
> + try {
> + p_sys->renderer = std::make_shared<MediaRenderer>(p_stream,
> + p_sys->p_upnp, base_url, device_url);
> + }
> + catch ( const std::bad_alloc& ) {
> + msg_Err( p_stream, "Failed to create a MediaRenderer");
> + p_sys->p_upnp->release();
> + goto error;
> + }
> +
> + p_stream->pf_add = Add;
> + p_stream->pf_del = Del;
> + p_stream->pf_send = Send;
> + p_stream->pf_flush = Flush;
> +
> + p_stream->p_sys = p_sys;
> +
> + free(base_url);
> + free(device_url);
> +
> + return VLC_SUCCESS;
> +
> +error:
> + free(base_url);
> + free(device_url);
> + delete p_sys;
> + return VLC_EGENERIC;
> +}
> +
> +void CloseSout( vlc_object_t *p_this)
> +{
> + sout_stream_t *p_stream =
> reinterpret_cast<sout_stream_t*>( p_this );
> + sout_stream_sys_t *p_sys = static_cast<sout_stream_sys_t
> *>( p_stream->p_sys );
> +
> + p_sys->p_upnp->release();
> + delete p_sys;
> +}
> +
> +}
> diff --git a/modules/stream_out/dlna/dlna.hpp b/modules/stream_out/dlna/
> dlna.hpp
> new file mode 100644
> index 0000000000..9768d38336
> --- /dev/null
> +++ b/modules/stream_out/dlna/dlna.hpp
> @@ -0,0 +1,60 @@
> +/
> *****************************************************************************
> + * dlna.hpp : DLNA/UPNP (renderer) sout module header
> +
> *****************************************************************************
> + * Copyright © 2018 VLC authors and VideoLAN
> + *
> + * Authors: Shaleen Jain <shaleen at jain.sh>
> + * William Ung <william1.ung at epitech.eu>
> + *
> + * This program is free software; you can redistribute it and/or modify
> it
> + * under the terms of the GNU Lesser General Public License as
> published by
> + * the Free Software Foundation; either version 2.1 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> License
> + * along with this program; if not, write to the Free Software
> Foundation,
> + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
> +
> *****************************************************************************/
> +
> +#ifndef DLNA_H
> +#define DLNA_H
> +
> +#include "../../services_discovery/upnp-wrapper.hpp"
> +#include "dlna_common.hpp"
> +
> +namespace DLNA
> +{
> +
> +class MediaRenderer
> +{
> +public:
> + MediaRenderer(sout_stream_t *p_stream, UpnpInstanceWrapper *upnp,
> + std::string base_url, std::string device_url)
> + : parent(p_stream)
> + , base_url(base_url)
> + , device_url(device_url)
> + , handle(upnp->handle())
> + {
> + }
> +
> + sout_stream_t *parent;
> + std::string base_url;
> + std::string device_url;
> + UpnpClient_Handle handle;
> +
> + char *getServiceURL(const char* type, const char* service);
> + IXML_Document *SendAction(const char* action_name, const char
> *service_type,
> + std::list<std::pair<const char*, const char*>>
> arguments);
> +
> + int Play(const char *speed);
> + int Stop();
> + int SetAVTransportURI(const char* uri);
> +};
> +
> +}
> +#endif /* DLNA_H */
> diff --git a/modules/stream_out/dlna/dlna_common.hpp b/modules/
> stream_out/dlna/dlna_common.hpp
> new file mode 100644
> index 0000000000..3e063c596c
> --- /dev/null
> +++ b/modules/stream_out/dlna/dlna_common.hpp
> @@ -0,0 +1,43 @@
> +/
> *****************************************************************************
> + * dlna_common.hpp : DLNA common header
> +
> *****************************************************************************
> + * Copyright © 2018 VLC authors and VideoLAN
> + *
> + * Authors: Shaleen Jain <shaleen at jain.sh>
> + *
> + * This program is free software; you can redistribute it and/or modify
> it
> + * under the terms of the GNU Lesser General Public License as
> published by
> + * the Free Software Foundation; either version 2.1 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> License
> + * along with this program; if not, write to the Free Software
> Foundation,
> + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
> +
> *****************************************************************************/
> +
> +#ifndef DLNA_COMMON_H
> +#define DLNA_COMMON_H
> +
> +#include <list>
> +
> +#include <vlc_common.h>
> +#include <vlc_fourcc.h>
> +
> +#include "../renderer_common.hpp"
> +
> +#define SOUT_CFG_PREFIX "sout-dlna-"
> +
> +namespace DLNA
> +{
> +
> +/* module callbacks */
> +int OpenSout(vlc_object_t *);
> +void CloseSout(vlc_object_t *);
> +
> +}
> +#endif /* DLNA_COMMON_H */
> diff --git a/po/POTFILES.in b/po/POTFILES.in
> index 98655a050b..96eb497c4f 100644
> --- a/po/POTFILES.in
> +++ b/po/POTFILES.in
> @@ -1070,6 +1070,7 @@ modules/stream_out/cycle.c
> modules/stream_out/delay.c
> modules/stream_out/description.c
> modules/stream_out/display.c
> +modules/stream_out/dlna/dlna.hpp
> modules/stream_out/dummy.c
> modules/stream_out/duplicate.c
> modules/stream_out/es.c
> --
> 2.20.1
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel
--
Hugo Beauzée-Luyssen
hugo at beauzee.fr
More information about the vlc-devel
mailing list