[vlc-devel] [PATCH 4/4] dlna: add a DLNA stream out
Shaleen Jain
shaleen at jain.sh
Sat Sep 22 10:25:06 CEST 2018
Hi Filip,
Thanks for the review.
On Thu, 2018-09-20 at 16:02 +0200, Filip Roséen wrote:
> 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).
C++ has a lot of boilerplate but I'll try and use it more where I can
(maybe you have a specific block in mind?).
> I do agree with j-b though related to querying what formats the upnp
> device actually supports, nonetheless: good job, and keep it up.
Thanks, Yes I'll post a new series with the format quering.
> 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.
Ok
> > + +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 ==.
Ok
> > +} + +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).
This is mainly because to enforce only ever having a single output
stream running considering every chain could have a transcoding step.
The soutChain start and stop are similar designs to that of chromecast.
> > + + 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.
Yes, changed to msg_Err()
> > + 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?
p_out here is the output stream chain which can be null if the stream
chain is not yet started. What is passed here is the input stream.
Again similar design of chromecast.
> > + { + 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.
Similar to what is done by chromecast.
> > + 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.
Ok
> > + + 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.
Ok
> > + 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/20380https://trac.videolan.org/vlc/ticket/20890http://git.videolan.org/?p=vlc.git;a=commit;h=710a2ef49669806e208d6845a494a01ed90b18db
The DLNA/UPNP spec specifies separate actions for syncronised playback
to multiple devices which I plan to add once the single playback is
merged and see how that could work with VLM. As for the webinterface,
it works for me locally while streaming similtaneously but to be safe I
have changed the http-port for dlna to 7070 to not be the same as the
web interface default port.
> > + + 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?
Yes, fixed.
> > + + 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.
Fixed, thanks.
> > + 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.
Ok
> > + + /* 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.
Done.
> > + 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.
Removed.
> > + } + + 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
> _______________________________________________vlc-devel mailing
> listTo unsubscribe or modify your subscription options:
> https://mailman.videolan.org/listinfo/vlc-devel
--
Regards,
Shaleen Jain
--
Regards,
Shaleen Jain
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.videolan.org/pipermail/vlc-devel/attachments/20180922/7a735bce/attachment.html>
More information about the vlc-devel
mailing list