2 * Many concepts and protocol specification in this code are taken
3 * from Shairport, by James Laird.
5 * Copyright (C) 2011-2013 Team XBMC
8 * This Program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2.1, or (at your option)
13 * This Program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with XBMC; see the file COPYING. If not, see
20 * <http://www.gnu.org/licenses/>.
24 #include "network/Network.h"
25 #if !defined(TARGET_WINDOWS)
26 #pragma GCC diagnostic ignored "-Wwrite-strings"
29 #include "AirTunesServer.h"
32 #include "network/AirPlayServer.h"
37 #include "utils/log.h"
38 #include "utils/StdString.h"
39 #include "network/Zeroconf.h"
40 #include "ApplicationMessenger.h"
41 #include "filesystem/PipeFile.h"
42 #include "Application.h"
43 #include "cores/dvdplayer/DVDDemuxers/DVDDemuxBXA.h"
44 #include "filesystem/File.h"
45 #include "music/tags/MusicInfoTag.h"
47 #include "GUIInfoManager.h"
48 #include "guilib/GUIWindowManager.h"
49 #include "utils/Variant.h"
50 #include "utils/StringUtils.h"
51 #include "settings/AdvancedSettings.h"
52 #include "settings/Settings.h"
53 #include "utils/EndianSwap.h"
55 #include "interfaces/AnnouncementManager.h"
60 #define TMP_COVERART_PATH "special://temp/airtunes_album_thumb.jpg"
62 using namespace XFILE;
63 using namespace ANNOUNCEMENT;
65 #if defined(HAVE_LIBSHAIRPLAY)
66 DllLibShairplay *CAirTunesServer::m_pLibShairplay = NULL;
68 DllLibShairport *CAirTunesServer::m_pLibShairport = NULL;
70 CAirTunesServer *CAirTunesServer::ServerInstance = NULL;
71 CStdString CAirTunesServer::m_macAddress;
72 std::string CAirTunesServer::m_metadata[3];
73 CCriticalSection CAirTunesServer::m_metadataLock;
74 bool CAirTunesServer::m_streamStarted = false;
76 //parse daap metadata - thx to project MythTV
77 std::map<std::string, std::string> decodeDMAP(const char *buffer, unsigned int size)
79 std::map<std::string, std::string> result;
80 unsigned int offset = 8;
84 tag.append(buffer + offset, 4);
86 uint32_t length = Endian_SwapBE32(*(uint32_t *)(buffer + offset));
87 offset += sizeof(uint32_t);
89 content.append(buffer + offset, length);//possible fixme - utf8?
91 result[tag] = content;
96 void CAirTunesServer::RefreshMetadata()
98 CSingleLock lock(m_metadataLock);
99 MUSIC_INFO::CMusicInfoTag tag;
100 if (m_metadata[0].length())
101 tag.SetAlbum(m_metadata[0]);//album
102 if (m_metadata[1].length())
103 tag.SetTitle(m_metadata[1]);//title
104 if (m_metadata[2].length())
105 tag.SetArtist(m_metadata[2]);//artist
107 CApplicationMessenger::Get().SetCurrentSongTag(tag);
110 void CAirTunesServer::RefreshCoverArt()
112 CSingleLock lock(m_metadataLock);
113 //reset to empty before setting the new one
114 //else it won't get refreshed because the name didn't change
115 g_infoManager.SetCurrentAlbumThumb("");
117 g_infoManager.SetCurrentAlbumThumb(TMP_COVERART_PATH);
119 CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_REFRESH_THUMBS);
120 g_windowManager.SendThreadMessage(msg);
123 void CAirTunesServer::SetMetadataFromBuffer(const char *buffer, unsigned int size)
126 std::map<std::string, std::string> metadata = decodeDMAP(buffer, size);
127 CSingleLock lock(m_metadataLock);
129 if(metadata["asal"].length())
130 m_metadata[0] = metadata["asal"];//album
131 if(metadata["minm"].length())
132 m_metadata[1] = metadata["minm"];//title
133 if(metadata["asar"].length())
134 m_metadata[2] = metadata["asar"];//artist
139 void CAirTunesServer::Announce(AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data)
141 if ( (flag & Player) && strcmp(sender, "xbmc") == 0)
143 if (strcmp(message, "OnPlay") == 0 && m_streamStarted)
151 void CAirTunesServer::SetCoverArtFromBuffer(const char *buffer, unsigned int size)
153 XFILE::CFile tmpFile;
158 CSingleLock lock(m_metadataLock);
160 if (tmpFile.OpenForWrite(TMP_COVERART_PATH, true))
163 writtenBytes = tmpFile.Write(buffer, size);
173 #if defined(HAVE_LIBSHAIRPLAY)
175 -----BEGIN RSA PRIVATE KEY-----\
176 MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\
177 wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\
178 wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\
179 /+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\
180 UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\
181 BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\
182 LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\
183 NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\
184 lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\
185 aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\
186 a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\
187 oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\
188 oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\
189 k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\
190 AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\
191 cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\
192 54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\
193 17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\
194 1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\
195 LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\
196 2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\
197 -----END RSA PRIVATE KEY-----"
199 void CAirTunesServer::AudioOutputFunctions::audio_set_metadata(void *cls, void *session, const void *buffer, int buflen)
201 CAirTunesServer::SetMetadataFromBuffer((char *)buffer, buflen);
204 void CAirTunesServer::AudioOutputFunctions::audio_set_coverart(void *cls, void *session, const void *buffer, int buflen)
206 CAirTunesServer::SetCoverArtFromBuffer((char *)buffer, buflen);
209 char *session="XBMC-AirTunes";
211 void* CAirTunesServer::AudioOutputFunctions::audio_init(void *cls, int bits, int channels, int samplerate)
213 XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls;
214 pipe->OpenForWrite(XFILE::PipesManager::GetInstance().GetUniquePipeName());
215 pipe->SetOpenThreashold(300);
217 Demux_BXA_FmtHeader header;
218 strncpy(header.fourcc, "BXA ", 4);
219 header.type = BXA_PACKET_TYPE_FMT_DEMUX;
220 header.bitsPerSample = bits;
221 header.channels = channels;
222 header.sampleRate = samplerate;
223 header.durationMs = 0;
225 if (pipe->Write(&header, sizeof(header)) == 0)
228 ThreadMessage tMsg = { TMSG_MEDIA_STOP };
229 CApplicationMessenger::Get().SendMessage(tMsg, true);
232 item.SetPath(pipe->GetName());
233 item.SetMimeType("audio/x-xbmc-pcm");
234 m_streamStarted = true;
236 CApplicationMessenger::Get().PlayFile(item);
238 return session;//session
241 void CAirTunesServer::AudioOutputFunctions::audio_set_volume(void *cls, void *session, float volume)
243 //volume from -30 - 0 - -144 means mute
244 float volPercent = volume < -30.0f ? 0 : 1 - volume/-30;
246 CAirPlayServer::backupVolume();
248 if (CSettings::Get().GetBool("services.airplayvolumecontrol"))
249 g_application.SetVolume(volPercent, false);//non-percent volume 0.0-1.0
252 void CAirTunesServer::AudioOutputFunctions::audio_process(void *cls, void *session, const void *buffer, int buflen)
254 #define NUM_OF_BYTES 64
255 XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls;
257 unsigned char buf[NUM_OF_BYTES];
259 while (sentBytes < buflen)
261 int n = (buflen - sentBytes < NUM_OF_BYTES ? buflen - sentBytes : NUM_OF_BYTES);
262 memcpy(buf, (char*) buffer + sentBytes, n);
264 if (pipe->Write(buf, n) == 0)
271 void CAirTunesServer::AudioOutputFunctions::audio_flush(void *cls, void *session)
273 XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls;
277 void CAirTunesServer::AudioOutputFunctions::audio_destroy(void *cls, void *session)
279 XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls;
283 //fix airplay video for ios5 devices
284 //on ios5 when airplaying video
285 //the client first opens an airtunes stream
286 //while the movie is loading
287 //in that case we don't want to stop the player here
288 //because this would stop the airplaying video
290 if (!CAirPlayServer::IsPlaying())
293 ThreadMessage tMsg = { TMSG_MEDIA_STOP };
294 CApplicationMessenger::Get().SendMessage(tMsg, true);
295 CLog::Log(LOGDEBUG, "AIRTUNES: AirPlay not running - stopping player");
298 m_streamStarted = false;
301 void shairplay_log(void *cls, int level, const char *msg)
303 int xbmcLevel = LOGINFO;
307 case RAOP_LOG_EMERG: // system is unusable
308 xbmcLevel = LOGFATAL;
310 case RAOP_LOG_ALERT: // action must be taken immediately
311 case RAOP_LOG_CRIT: // critical conditions
312 xbmcLevel = LOGSEVERE;
314 case RAOP_LOG_ERR: // error conditions
315 xbmcLevel = LOGERROR;
317 case RAOP_LOG_WARNING: // warning conditions
318 xbmcLevel = LOGWARNING;
320 case RAOP_LOG_NOTICE: // normal but significant condition
321 xbmcLevel = LOGNOTICE;
323 case RAOP_LOG_INFO: // informational
326 case RAOP_LOG_DEBUG: // debug-level messages
327 xbmcLevel = LOGDEBUG;
332 CLog::Log(xbmcLevel, "AIRTUNES: %s", msg);
337 struct ao_device_xbmc
339 XFILE::CPipeFile *pipe;
342 //audio output interface
343 void CAirTunesServer::AudioOutputFunctions::ao_initialize(void)
347 void CAirTunesServer::AudioOutputFunctions::ao_set_volume(float volume)
349 //volume from -30 - 0 - -144 means mute
350 float volPercent = volume < -30.0f ? 0 : 1 - volume/-30;
352 CAirPlayServer::backupVolume();
354 if (CSettings::Get().GetBool("services.airplayvolumecontrol"))
355 g_application.SetVolume(volPercent, false);//non-percent volume 0.0-1.0
359 int CAirTunesServer::AudioOutputFunctions::ao_play(ao_device *device, char *output_samples, uint32_t num_bytes)
364 /*if (num_bytes && g_application.m_pPlayer->HasPlayer())
365 g_application.m_pPlayer->SetCaching(CACHESTATE_NONE);*///TODO
367 ao_device_xbmc* device_xbmc = (ao_device_xbmc*) device;
369 #define NUM_OF_BYTES 64
371 unsigned int sentBytes = 0;
372 unsigned char buf[NUM_OF_BYTES];
373 while (sentBytes < num_bytes)
375 int n = (num_bytes - sentBytes < NUM_OF_BYTES ? num_bytes - sentBytes : NUM_OF_BYTES);
376 memcpy(buf, (char*) output_samples + sentBytes, n);
378 if (device_xbmc->pipe->Write(buf, n) == 0)
387 int CAirTunesServer::AudioOutputFunctions::ao_default_driver_id(void)
392 ao_device* CAirTunesServer::AudioOutputFunctions::ao_open_live(int driver_id, ao_sample_format *format,
395 ao_device_xbmc* device = new ao_device_xbmc();
397 device->pipe = new XFILE::CPipeFile;
398 device->pipe->OpenForWrite(XFILE::PipesManager::GetInstance().GetUniquePipeName());
399 device->pipe->SetOpenThreashold(300);
401 Demux_BXA_FmtHeader header;
402 strncpy(header.fourcc, "BXA ", 4);
403 header.type = BXA_PACKET_TYPE_FMT_DEMUX;
404 header.bitsPerSample = format->bits;
405 header.channels = format->channels;
406 header.sampleRate = format->rate;
407 header.durationMs = 0;
409 if (device->pipe->Write(&header, sizeof(header)) == 0)
416 ThreadMessage tMsg = { TMSG_MEDIA_STOP };
417 CApplicationMessenger::Get().SendMessage(tMsg, true);
420 item.SetPath(device->pipe->GetName());
421 item.SetMimeType("audio/x-xbmc-pcm");
423 if (ao_get_option(option, "artist"))
424 item.GetMusicInfoTag()->SetArtist(ao_get_option(option, "artist"));
426 if (ao_get_option(option, "album"))
427 item.GetMusicInfoTag()->SetAlbum(ao_get_option(option, "album"));
429 if (ao_get_option(option, "name"))
430 item.GetMusicInfoTag()->SetTitle(ao_get_option(option, "name"));
432 m_streamStarted = true;
434 CApplicationMessenger::Get().PlayFile(item);
436 return (ao_device*) device;
439 int CAirTunesServer::AudioOutputFunctions::ao_close(ao_device *device)
441 ao_device_xbmc* device_xbmc = (ao_device_xbmc*) device;
442 device_xbmc->pipe->SetEof();
443 device_xbmc->pipe->Close();
444 delete device_xbmc->pipe;
446 //fix airplay video for ios5 devices
447 //on ios5 when airplaying video
448 //the client first opens an airtunes stream
449 //while the movie is loading
450 //in that case we don't want to stop the player here
451 //because this would stop the airplaying video
453 if (!CAirPlayServer::IsPlaying())
456 ThreadMessage tMsg = { TMSG_MEDIA_STOP };
457 CApplicationMessenger::Get().SendMessage(tMsg, true);
458 CLog::Log(LOGDEBUG, "AIRTUNES: AirPlay not running - stopping player");
462 m_streamStarted = false;
467 void CAirTunesServer::AudioOutputFunctions::ao_set_metadata(const char *buffer, unsigned int size)
469 CAirTunesServer::SetMetadataFromBuffer(buffer, size);
472 void CAirTunesServer::AudioOutputFunctions::ao_set_metadata_coverart(const char *buffer, unsigned int size)
474 CAirTunesServer::SetCoverArtFromBuffer(buffer, size);
477 /* -- Device Setup/Playback/Teardown -- */
478 int CAirTunesServer::AudioOutputFunctions::ao_append_option(ao_option **options, const char *key, const char *value)
480 ao_option *op, *list;
482 op = (ao_option*) calloc(1,sizeof(ao_option));
483 if (op == NULL) return 0;
485 op->key = strdup(key);
486 op->value = strdup(value?value:"");
489 if ((list = *options) != NULL)
492 while (list->next != NULL)
504 void CAirTunesServer::AudioOutputFunctions::ao_free_options(ao_option *options)
508 while (options != NULL)
510 rest = options->next;
512 free(options->value);
518 char* CAirTunesServer::AudioOutputFunctions::ao_get_option(ao_option *options, const char* key)
521 while (options != NULL)
523 if (strcmp(options->key, key) == 0)
524 return options->value;
525 options = options->next;
531 int shairport_log(const char* msg, size_t msgSize)
533 if( g_advancedSettings.m_logEnableAirtunes)
535 CLog::Log(LOGDEBUG, "AIRTUNES: %s", msg);
542 bool CAirTunesServer::StartServer(int port, bool nonlocal, bool usePassword, const CStdString &password/*=""*/)
544 bool success = false;
545 CStdString pw = password;
546 CNetworkInterface *net = g_application.getNetwork().GetFirstConnectedInterface();
551 m_macAddress = net->GetMacAddress();
552 StringUtils::Replace(m_macAddress, ":","");
553 while (m_macAddress.size() < 12)
555 m_macAddress = CStdString("0") + m_macAddress;
560 m_macAddress = "000102030405";
568 ServerInstance = new CAirTunesServer(port, nonlocal);
569 if (ServerInstance->Initialize(pw))
571 #if !defined(HAVE_LIBSHAIRPLAY)
572 ServerInstance->Create();
579 CStdString appName = StringUtils::Format("%s@%s",
580 m_macAddress.c_str(),
581 g_infoManager.GetLabel(SYSTEM_FRIENDLY_NAME).c_str());
583 std::vector<std::pair<std::string, std::string> > txt;
584 txt.push_back(std::make_pair("txtvers", "1"));
585 txt.push_back(std::make_pair("cn", "0,1"));
586 txt.push_back(std::make_pair("ch", "2"));
587 txt.push_back(std::make_pair("ek", "1"));
588 txt.push_back(std::make_pair("et", "0,1"));
589 txt.push_back(std::make_pair("sv", "false"));
590 txt.push_back(std::make_pair("tp", "UDP"));
591 txt.push_back(std::make_pair("sm", "false"));
592 txt.push_back(std::make_pair("ss", "16"));
593 txt.push_back(std::make_pair("sr", "44100"));
594 txt.push_back(std::make_pair("pw", usePassword?"true":"false"));
595 txt.push_back(std::make_pair("vn", "3"));
596 txt.push_back(std::make_pair("da", "true"));
597 txt.push_back(std::make_pair("vs", "130.14"));
598 txt.push_back(std::make_pair("md", "0,1,2"));
599 txt.push_back(std::make_pair("am", "Xbmc,1"));
601 CZeroconf::GetInstance()->PublishService("servers.airtunes", "_raop._tcp", appName, port, txt);
607 void CAirTunesServer::StopServer(bool bWait)
611 #if !defined(HAVE_LIBSHAIRPLAY)
612 if (m_pLibShairport->IsLoaded())
614 m_pLibShairport->shairport_exit();
617 ServerInstance->StopThread(bWait);
618 ServerInstance->Deinitialize();
621 delete ServerInstance;
622 ServerInstance = NULL;
625 CZeroconf::GetInstance()->RemoveService("servers.airtunes");
629 bool CAirTunesServer::IsRunning()
631 if (ServerInstance == NULL)
634 return ((CThread*)ServerInstance)->IsRunning();
637 CAirTunesServer::CAirTunesServer(int port, bool nonlocal) : CThread("AirTunesServer")
640 #if defined(HAVE_LIBSHAIRPLAY)
641 m_pLibShairplay = new DllLibShairplay();
642 m_pPipe = new XFILE::CPipeFile;
644 m_pLibShairport = new DllLibShairport();
646 CAnnouncementManager::AddAnnouncer(this);
649 CAirTunesServer::~CAirTunesServer()
651 #if defined(HAVE_LIBSHAIRPLAY)
652 if (m_pLibShairplay->IsLoaded())
654 m_pLibShairplay->Unload();
656 delete m_pLibShairplay;
659 if (m_pLibShairport->IsLoaded())
661 m_pLibShairport->Unload();
663 delete m_pLibShairport;
665 CAnnouncementManager::RemoveAnnouncer(this);
668 void CAirTunesServer::Process()
672 #if !defined(HAVE_LIBSHAIRPLAY)
673 while (!m_bStop && m_pLibShairport->shairport_is_running())
675 m_pLibShairport->shairport_loop();
680 bool CAirTunesServer::Initialize(const CStdString &password)
686 #if defined(HAVE_LIBSHAIRPLAY)
687 if (m_pLibShairplay->Load())
692 ao.audio_init = AudioOutputFunctions::audio_init;
693 ao.audio_set_volume = AudioOutputFunctions::audio_set_volume;
694 ao.audio_set_metadata = AudioOutputFunctions::audio_set_metadata;
695 ao.audio_set_coverart = AudioOutputFunctions::audio_set_coverart;
696 ao.audio_process = AudioOutputFunctions::audio_process;
697 ao.audio_flush = AudioOutputFunctions::audio_flush;
698 ao.audio_destroy = AudioOutputFunctions::audio_destroy;
699 m_pLibShairplay->EnableDelayedUnload(false);
700 m_pRaop = m_pLibShairplay->raop_init(1, &ao, RSA_KEY);//1 - we handle one client at a time max
701 ret = m_pRaop != NULL;
706 unsigned short port = (unsigned short)m_port;
708 m_pLibShairplay->raop_set_log_level(m_pRaop, RAOP_LOG_WARNING);
709 if(g_advancedSettings.m_logEnableAirtunes)
711 m_pLibShairplay->raop_set_log_level(m_pRaop, RAOP_LOG_DEBUG);
714 m_pLibShairplay->raop_set_log_callback(m_pRaop, shairplay_log, NULL);
716 CNetworkInterface *net = g_application.getNetwork().GetFirstConnectedInterface();
720 net->GetMacAddressRaw(macAdr);
723 ret = m_pLibShairplay->raop_start(m_pRaop, &port, macAdr, 6, password.c_str()) >= 0;
734 hwStr = StringUtils::Format("--mac=%s", m_macAddress.c_str());
735 pwStr = StringUtils::Format("--password=%s",password.c_str());
736 portStr = StringUtils::Format("--server_port=%d",m_port);
738 if (!password.empty())
743 char *argv[] = { "--apname=XBMC", (char*) portStr.c_str(), (char*) hwStr.c_str(), (char *)pwStr.c_str(), NULL };
745 if (m_pLibShairport->Load())
748 struct AudioOutput ao;
749 ao.ao_initialize = AudioOutputFunctions::ao_initialize;
750 ao.ao_play = AudioOutputFunctions::ao_play;
751 ao.ao_default_driver_id = AudioOutputFunctions::ao_default_driver_id;
752 ao.ao_open_live = AudioOutputFunctions::ao_open_live;
753 ao.ao_close = AudioOutputFunctions::ao_close;
754 ao.ao_append_option = AudioOutputFunctions::ao_append_option;
755 ao.ao_free_options = AudioOutputFunctions::ao_free_options;
756 ao.ao_get_option = AudioOutputFunctions::ao_get_option;
757 #ifdef HAVE_STRUCT_AUDIOOUTPUT_AO_SET_METADATA
758 ao.ao_set_metadata = AudioOutputFunctions::ao_set_metadata;
759 ao.ao_set_metadata_coverart = AudioOutputFunctions::ao_set_metadata_coverart;
761 #if defined(SHAIRPORT_AUDIOOUTPUT_VERSION)
762 #if SHAIRPORT_AUDIOOUTPUT_VERSION >= 2
763 ao.ao_set_volume = AudioOutputFunctions::ao_set_volume;
766 struct printfPtr funcPtr;
767 funcPtr.extprintf = shairport_log;
769 m_pLibShairport->EnableDelayedUnload(false);
770 m_pLibShairport->shairport_set_ao(&ao);
771 m_pLibShairport->shairport_set_printf(&funcPtr);
772 m_pLibShairport->shairport_main(numArgs, argv);
779 void CAirTunesServer::Deinitialize()
781 #if defined(HAVE_LIBSHAIRPLAY)
782 if (m_pLibShairplay && m_pLibShairplay->IsLoaded())
784 m_pLibShairplay->raop_stop(m_pRaop);
785 m_pLibShairplay->raop_destroy(m_pRaop);
786 m_pLibShairplay->Unload();
789 if (m_pLibShairport && m_pLibShairport->IsLoaded())
791 m_pLibShairport->shairport_exit();
792 m_pLibShairport->Unload();