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 #include diff --git a/modules/stream_out/dlna/dlna.cpp b/modules/stream_out/dlna/dlna.cpp new file mode 100644 index 0000000000..0158e9306b --- /dev/null +++ b/modules/stream_out/dlna/dlna.cpp @@ -0,0 +1,682 @@ +/***************************************************************************** + * dlna.cpp : DLNA/UPNP (renderer) sout module + ***************************************************************************** + * Copyright © 2018 VLC authors and VideoLAN + * + * Authors: Shaleen Jain + * William Ung + * + * 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 +#include +#include + +#include +#include +#include +#include +#include + +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 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 &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 streams; + std::vector 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() { + 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 &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(), 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::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_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 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( 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(getServerIPAddress()); + if (ip == nullptr) + { + 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() ) ) { + free(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> arguments) +{ + /* Create action */ + IXML_Document *action = UpnpMakeAction(action_name, service_type, 0, nullptr); + + /* Add argument to action */ + for (std::pair 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> 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> 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> 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( 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( p_stream->p_sys ); + sout_stream_id_sys_t *id_sys = static_cast( id ); + + id_sys = p_sys->GetSubId( p_stream, id_sys ); + if ( id_sys == nullptr ) + { + block_Release( p_buffer ); + 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( p_stream->p_sys ); + sout_stream_id_sys_t *id_sys = static_cast( 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( p_stream->p_sys ); + sout_stream_id_sys_t *id = static_cast( _id ); + + for (auto it = p_sys->streams.begin(); 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 (auto out_it = p_sys->out_streams.begin(); + out_it != p_sys->out_streams.end(); ++out_it) + { + if (*out_it == id) + { + p_sys->out_streams.erase(out_it); + break; + } + } + } + + 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(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(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( p_this ); + sout_stream_sys_t *p_sys = static_cast( 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 + * William Ung + * + * 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> 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 + * + * 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 + +#include +#include + +#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