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 using namespace XFILE;
61 using namespace ANNOUNCEMENT;
63 #if defined(HAVE_LIBSHAIRPLAY)
64 DllLibShairplay *CAirTunesServer::m_pLibShairplay = NULL;
66 DllLibShairport *CAirTunesServer::m_pLibShairport = NULL;
68 CAirTunesServer *CAirTunesServer::ServerInstance = NULL;
69 CStdString CAirTunesServer::m_macAddress;
71 //parse daap metadata - thx to project MythTV
72 std::map<std::string, std::string> decodeDMAP(const char *buffer, unsigned int size)
74 std::map<std::string, std::string> result;
75 unsigned int offset = 8;
79 tag.append(buffer + offset, 4);
81 uint32_t length = Endian_SwapBE32(*(uint32_t *)(buffer + offset));
82 offset += sizeof(uint32_t);
84 content.append(buffer + offset, length);//possible fixme - utf8?
86 result[tag] = content;
91 void CAirTunesServer::SetMetadataFromBuffer(const char *buffer, unsigned int size)
93 MUSIC_INFO::CMusicInfoTag tag;
94 std::map<std::string, std::string> metadata = decodeDMAP(buffer, size);
95 if(metadata["asal"].length())
96 tag.SetAlbum(metadata["asal"]);//album
97 if(metadata["minm"].length())
98 tag.SetTitle(metadata["minm"]);//title
99 if(metadata["asar"].length())
100 tag.SetArtist(metadata["asar"]);//artist
101 CApplicationMessenger::Get().SetCurrentSongTag(tag);
104 void CAirTunesServer::Announce(AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data)
106 if ( (flag & Player) && strcmp(sender, "xbmc") == 0 && strcmp(message, "OnStop") == 0)
109 CAirPlayServer::restoreVolume();
114 void CAirTunesServer::SetCoverArtFromBuffer(const char *buffer, unsigned int size)
116 XFILE::CFile tmpFile;
117 const char *tmpFileName = "special://temp/airtunes_album_thumb.jpg";
122 if (tmpFile.OpenForWrite(tmpFileName, true))
125 writtenBytes = tmpFile.Write(buffer, size);
130 //reset to empty before setting the new one
131 //else it won't get refreshed because the name didn't change
132 g_infoManager.SetCurrentAlbumThumb("");
133 g_infoManager.SetCurrentAlbumThumb(tmpFileName);
135 CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_REFRESH_THUMBS);
136 g_windowManager.SendThreadMessage(msg);
141 #if defined(HAVE_LIBSHAIRPLAY)
143 -----BEGIN RSA PRIVATE KEY-----\
144 MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\
145 wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\
146 wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\
147 /+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\
148 UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\
149 BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\
150 LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\
151 NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\
152 lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\
153 aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\
154 a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\
155 oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\
156 oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\
157 k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\
158 AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\
159 cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\
160 54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\
161 17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\
162 1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\
163 LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\
164 2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\
165 -----END RSA PRIVATE KEY-----"
167 void CAirTunesServer::AudioOutputFunctions::audio_set_metadata(void *cls, void *session, const void *buffer, int buflen)
169 CAirTunesServer::SetMetadataFromBuffer((char *)buffer, buflen);
172 void CAirTunesServer::AudioOutputFunctions::audio_set_coverart(void *cls, void *session, const void *buffer, int buflen)
174 CAirTunesServer::SetCoverArtFromBuffer((char *)buffer, buflen);
177 char *session="XBMC-AirTunes";
179 void* CAirTunesServer::AudioOutputFunctions::audio_init(void *cls, int bits, int channels, int samplerate)
181 XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls;
182 pipe->OpenForWrite(XFILE::PipesManager::GetInstance().GetUniquePipeName());
183 pipe->SetOpenThreashold(300);
185 Demux_BXA_FmtHeader header;
186 strncpy(header.fourcc, "BXA ", 4);
187 header.type = BXA_PACKET_TYPE_FMT_DEMUX;
188 header.bitsPerSample = bits;
189 header.channels = channels;
190 header.sampleRate = samplerate;
191 header.durationMs = 0;
193 if (pipe->Write(&header, sizeof(header)) == 0)
196 ThreadMessage tMsg = { TMSG_MEDIA_STOP };
197 CApplicationMessenger::Get().SendMessage(tMsg, true);
200 item.SetPath(pipe->GetName());
201 item.SetMimeType("audio/x-xbmc-pcm");
203 CApplicationMessenger::Get().PlayFile(item);
205 return session;//session
208 void CAirTunesServer::AudioOutputFunctions::audio_set_volume(void *cls, void *session, float volume)
210 //volume from -30 - 0 - -144 means mute
211 float volPercent = volume < -30.0f ? 0 : 1 - volume/-30;
213 CAirPlayServer::backupVolume();
215 if (CSettings::Get().GetBool("services.airplayvolumecontrol"))
216 g_application.SetVolume(volPercent, false);//non-percent volume 0.0-1.0
219 void CAirTunesServer::AudioOutputFunctions::audio_process(void *cls, void *session, const void *buffer, int buflen)
221 #define NUM_OF_BYTES 64
222 XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls;
224 unsigned char buf[NUM_OF_BYTES];
226 while (sentBytes < buflen)
228 int n = (buflen - sentBytes < NUM_OF_BYTES ? buflen - sentBytes : NUM_OF_BYTES);
229 memcpy(buf, (char*) buffer + sentBytes, n);
231 if (pipe->Write(buf, n) == 0)
238 void CAirTunesServer::AudioOutputFunctions::audio_flush(void *cls, void *session)
240 XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls;
244 void CAirTunesServer::AudioOutputFunctions::audio_destroy(void *cls, void *session)
246 XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls;
250 //fix airplay video for ios5 devices
251 //on ios5 when airplaying video
252 //the client first opens an airtunes stream
253 //while the movie is loading
254 //in that case we don't want to stop the player here
255 //because this would stop the airplaying video
257 if (!CAirPlayServer::IsPlaying())
260 ThreadMessage tMsg = { TMSG_MEDIA_STOP };
261 CApplicationMessenger::Get().SendMessage(tMsg, true);
262 CLog::Log(LOGDEBUG, "AIRTUNES: AirPlay not running - stopping player");
266 void shairplay_log(void *cls, int level, const char *msg)
268 int xbmcLevel = LOGINFO;
272 case RAOP_LOG_EMERG: // system is unusable
273 xbmcLevel = LOGFATAL;
275 case RAOP_LOG_ALERT: // action must be taken immediately
276 case RAOP_LOG_CRIT: // critical conditions
277 xbmcLevel = LOGSEVERE;
279 case RAOP_LOG_ERR: // error conditions
280 xbmcLevel = LOGERROR;
282 case RAOP_LOG_WARNING: // warning conditions
283 xbmcLevel = LOGWARNING;
285 case RAOP_LOG_NOTICE: // normal but significant condition
286 xbmcLevel = LOGNOTICE;
288 case RAOP_LOG_INFO: // informational
291 case RAOP_LOG_DEBUG: // debug-level messages
292 xbmcLevel = LOGDEBUG;
297 CLog::Log(xbmcLevel, "AIRTUNES: %s", msg);
302 struct ao_device_xbmc
304 XFILE::CPipeFile *pipe;
307 //audio output interface
308 void CAirTunesServer::AudioOutputFunctions::ao_initialize(void)
312 void CAirTunesServer::AudioOutputFunctions::ao_set_volume(float volume)
314 //volume from -30 - 0 - -144 means mute
315 float volPercent = volume < -30.0f ? 0 : 1 - volume/-30;
317 CAirPlayServer::backupVolume();
319 if (CSettings::Get().GetBool("services.airplayvolumecontrol"))
320 g_application.SetVolume(volPercent, false);//non-percent volume 0.0-1.0
324 int CAirTunesServer::AudioOutputFunctions::ao_play(ao_device *device, char *output_samples, uint32_t num_bytes)
329 /*if (num_bytes && g_application.m_pPlayer->HasPlayer())
330 g_application.m_pPlayer->SetCaching(CACHESTATE_NONE);*///TODO
332 ao_device_xbmc* device_xbmc = (ao_device_xbmc*) device;
334 #define NUM_OF_BYTES 64
336 unsigned int sentBytes = 0;
337 unsigned char buf[NUM_OF_BYTES];
338 while (sentBytes < num_bytes)
340 int n = (num_bytes - sentBytes < NUM_OF_BYTES ? num_bytes - sentBytes : NUM_OF_BYTES);
341 memcpy(buf, (char*) output_samples + sentBytes, n);
343 if (device_xbmc->pipe->Write(buf, n) == 0)
352 int CAirTunesServer::AudioOutputFunctions::ao_default_driver_id(void)
357 ao_device* CAirTunesServer::AudioOutputFunctions::ao_open_live(int driver_id, ao_sample_format *format,
360 ao_device_xbmc* device = new ao_device_xbmc();
362 device->pipe = new XFILE::CPipeFile;
363 device->pipe->OpenForWrite(XFILE::PipesManager::GetInstance().GetUniquePipeName());
364 device->pipe->SetOpenThreashold(300);
366 Demux_BXA_FmtHeader header;
367 strncpy(header.fourcc, "BXA ", 4);
368 header.type = BXA_PACKET_TYPE_FMT_DEMUX;
369 header.bitsPerSample = format->bits;
370 header.channels = format->channels;
371 header.sampleRate = format->rate;
372 header.durationMs = 0;
374 if (device->pipe->Write(&header, sizeof(header)) == 0)
381 ThreadMessage tMsg = { TMSG_MEDIA_STOP };
382 CApplicationMessenger::Get().SendMessage(tMsg, true);
385 item.SetPath(device->pipe->GetName());
386 item.SetMimeType("audio/x-xbmc-pcm");
388 if (ao_get_option(option, "artist"))
389 item.GetMusicInfoTag()->SetArtist(ao_get_option(option, "artist"));
391 if (ao_get_option(option, "album"))
392 item.GetMusicInfoTag()->SetAlbum(ao_get_option(option, "album"));
394 if (ao_get_option(option, "name"))
395 item.GetMusicInfoTag()->SetTitle(ao_get_option(option, "name"));
397 CApplicationMessenger::Get().PlayFile(item);
399 return (ao_device*) device;
402 int CAirTunesServer::AudioOutputFunctions::ao_close(ao_device *device)
404 ao_device_xbmc* device_xbmc = (ao_device_xbmc*) device;
405 device_xbmc->pipe->SetEof();
406 device_xbmc->pipe->Close();
407 delete device_xbmc->pipe;
409 //fix airplay video for ios5 devices
410 //on ios5 when airplaying video
411 //the client first opens an airtunes stream
412 //while the movie is loading
413 //in that case we don't want to stop the player here
414 //because this would stop the airplaying video
416 if (!CAirPlayServer::IsPlaying())
419 ThreadMessage tMsg = { TMSG_MEDIA_STOP };
420 CApplicationMessenger::Get().SendMessage(tMsg, true);
421 CLog::Log(LOGDEBUG, "AIRTUNES: AirPlay not running - stopping player");
429 void CAirTunesServer::AudioOutputFunctions::ao_set_metadata(const char *buffer, unsigned int size)
431 CAirTunesServer::SetMetadataFromBuffer(buffer, size);
434 void CAirTunesServer::AudioOutputFunctions::ao_set_metadata_coverart(const char *buffer, unsigned int size)
436 CAirTunesServer::SetCoverArtFromBuffer(buffer, size);
439 /* -- Device Setup/Playback/Teardown -- */
440 int CAirTunesServer::AudioOutputFunctions::ao_append_option(ao_option **options, const char *key, const char *value)
442 ao_option *op, *list;
444 op = (ao_option*) calloc(1,sizeof(ao_option));
445 if (op == NULL) return 0;
447 op->key = strdup(key);
448 op->value = strdup(value?value:"");
451 if ((list = *options) != NULL)
454 while (list->next != NULL)
466 void CAirTunesServer::AudioOutputFunctions::ao_free_options(ao_option *options)
470 while (options != NULL)
472 rest = options->next;
474 free(options->value);
480 char* CAirTunesServer::AudioOutputFunctions::ao_get_option(ao_option *options, const char* key)
483 while (options != NULL)
485 if (strcmp(options->key, key) == 0)
486 return options->value;
487 options = options->next;
493 int shairport_log(const char* msg, size_t msgSize)
495 if( g_advancedSettings.m_logEnableAirtunes)
497 CLog::Log(LOGDEBUG, "AIRTUNES: %s", msg);
504 bool CAirTunesServer::StartServer(int port, bool nonlocal, bool usePassword, const CStdString &password/*=""*/)
506 bool success = false;
507 CStdString pw = password;
508 CNetworkInterface *net = g_application.getNetwork().GetFirstConnectedInterface();
513 m_macAddress = net->GetMacAddress();
514 m_macAddress.Replace(":","");
515 while (m_macAddress.size() < 12)
517 m_macAddress = CStdString("0") + m_macAddress;
522 m_macAddress = "000102030405";
530 ServerInstance = new CAirTunesServer(port, nonlocal);
531 if (ServerInstance->Initialize(pw))
533 #if !defined(HAVE_LIBSHAIRPLAY)
534 ServerInstance->Create();
541 CStdString appName = StringUtils::Format("%s@%s",
542 m_macAddress.c_str(),
543 g_infoManager.GetLabel(SYSTEM_FRIENDLY_NAME).c_str());
545 std::vector<std::pair<std::string, std::string> > txt;
546 txt.push_back(std::make_pair("txtvers", "1"));
547 txt.push_back(std::make_pair("cn", "0,1"));
548 txt.push_back(std::make_pair("ch", "2"));
549 txt.push_back(std::make_pair("ek", "1"));
550 txt.push_back(std::make_pair("et", "0,1"));
551 txt.push_back(std::make_pair("sv", "false"));
552 txt.push_back(std::make_pair("tp", "UDP"));
553 txt.push_back(std::make_pair("sm", "false"));
554 txt.push_back(std::make_pair("ss", "16"));
555 txt.push_back(std::make_pair("sr", "44100"));
556 txt.push_back(std::make_pair("pw", usePassword?"true":"false"));
557 txt.push_back(std::make_pair("vn", "3"));
558 txt.push_back(std::make_pair("da", "true"));
559 txt.push_back(std::make_pair("vs", "130.14"));
560 txt.push_back(std::make_pair("md", "0,1,2"));
561 txt.push_back(std::make_pair("am", "Xbmc,1"));
563 CZeroconf::GetInstance()->PublishService("servers.airtunes", "_raop._tcp", appName, port, txt);
569 void CAirTunesServer::StopServer(bool bWait)
573 #if !defined(HAVE_LIBSHAIRPLAY)
574 if (m_pLibShairport->IsLoaded())
576 m_pLibShairport->shairport_exit();
579 ServerInstance->StopThread(bWait);
580 ServerInstance->Deinitialize();
583 delete ServerInstance;
584 ServerInstance = NULL;
587 CZeroconf::GetInstance()->RemoveService("servers.airtunes");
591 bool CAirTunesServer::IsRunning()
593 if (ServerInstance == NULL)
596 return ((CThread*)ServerInstance)->IsRunning();
599 CAirTunesServer::CAirTunesServer(int port, bool nonlocal) : CThread("AirTunesServer")
602 #if defined(HAVE_LIBSHAIRPLAY)
603 m_pLibShairplay = new DllLibShairplay();
604 m_pPipe = new XFILE::CPipeFile;
606 m_pLibShairport = new DllLibShairport();
608 CAnnouncementManager::AddAnnouncer(this);
611 CAirTunesServer::~CAirTunesServer()
613 #if defined(HAVE_LIBSHAIRPLAY)
614 if (m_pLibShairplay->IsLoaded())
616 m_pLibShairplay->Unload();
618 delete m_pLibShairplay;
621 if (m_pLibShairport->IsLoaded())
623 m_pLibShairport->Unload();
625 delete m_pLibShairport;
627 CAnnouncementManager::RemoveAnnouncer(this);
630 void CAirTunesServer::Process()
634 #if !defined(HAVE_LIBSHAIRPLAY)
635 while (!m_bStop && m_pLibShairport->shairport_is_running())
637 m_pLibShairport->shairport_loop();
642 bool CAirTunesServer::Initialize(const CStdString &password)
648 #if defined(HAVE_LIBSHAIRPLAY)
649 if (m_pLibShairplay->Load())
654 ao.audio_init = AudioOutputFunctions::audio_init;
655 ao.audio_set_volume = AudioOutputFunctions::audio_set_volume;
656 ao.audio_set_metadata = AudioOutputFunctions::audio_set_metadata;
657 ao.audio_set_coverart = AudioOutputFunctions::audio_set_coverart;
658 ao.audio_process = AudioOutputFunctions::audio_process;
659 ao.audio_flush = AudioOutputFunctions::audio_flush;
660 ao.audio_destroy = AudioOutputFunctions::audio_destroy;
661 m_pLibShairplay->EnableDelayedUnload(false);
662 m_pRaop = m_pLibShairplay->raop_init(1, &ao, RSA_KEY);//1 - we handle one client at a time max
663 ret = m_pRaop != NULL;
668 unsigned short port = (unsigned short)m_port;
670 m_pLibShairplay->raop_set_log_level(m_pRaop, RAOP_LOG_WARNING);
671 if(g_advancedSettings.m_logEnableAirtunes)
673 m_pLibShairplay->raop_set_log_level(m_pRaop, RAOP_LOG_DEBUG);
676 m_pLibShairplay->raop_set_log_callback(m_pRaop, shairplay_log, NULL);
678 CNetworkInterface *net = g_application.getNetwork().GetFirstConnectedInterface();
682 net->GetMacAddressRaw(macAdr);
685 ret = m_pLibShairplay->raop_start(m_pRaop, &port, macAdr, 6, password.c_str()) >= 0;
696 hwStr = StringUtils::Format("--mac=%s", m_macAddress.c_str());
697 pwStr = StringUtils::Format("--password=%s",password.c_str());
698 portStr = StringUtils::Format("--server_port=%d",m_port);
700 if (!password.empty())
705 char *argv[] = { "--apname=XBMC", (char*) portStr.c_str(), (char*) hwStr.c_str(), (char *)pwStr.c_str(), NULL };
707 if (m_pLibShairport->Load())
710 struct AudioOutput ao;
711 ao.ao_initialize = AudioOutputFunctions::ao_initialize;
712 ao.ao_play = AudioOutputFunctions::ao_play;
713 ao.ao_default_driver_id = AudioOutputFunctions::ao_default_driver_id;
714 ao.ao_open_live = AudioOutputFunctions::ao_open_live;
715 ao.ao_close = AudioOutputFunctions::ao_close;
716 ao.ao_append_option = AudioOutputFunctions::ao_append_option;
717 ao.ao_free_options = AudioOutputFunctions::ao_free_options;
718 ao.ao_get_option = AudioOutputFunctions::ao_get_option;
719 #ifdef HAVE_STRUCT_AUDIOOUTPUT_AO_SET_METADATA
720 ao.ao_set_metadata = AudioOutputFunctions::ao_set_metadata;
721 ao.ao_set_metadata_coverart = AudioOutputFunctions::ao_set_metadata_coverart;
723 #if defined(SHAIRPORT_AUDIOOUTPUT_VERSION)
724 #if SHAIRPORT_AUDIOOUTPUT_VERSION >= 2
725 ao.ao_set_volume = AudioOutputFunctions::ao_set_volume;
728 struct printfPtr funcPtr;
729 funcPtr.extprintf = shairport_log;
731 m_pLibShairport->EnableDelayedUnload(false);
732 m_pLibShairport->shairport_set_ao(&ao);
733 m_pLibShairport->shairport_set_printf(&funcPtr);
734 m_pLibShairport->shairport_main(numArgs, argv);
741 void CAirTunesServer::Deinitialize()
743 #if defined(HAVE_LIBSHAIRPLAY)
744 if (m_pLibShairplay && m_pLibShairplay->IsLoaded())
746 m_pLibShairplay->raop_stop(m_pRaop);
747 m_pLibShairplay->raop_destroy(m_pRaop);
748 m_pLibShairplay->Unload();
751 if (m_pLibShairport && m_pLibShairport->IsLoaded())
753 m_pLibShairport->shairport_exit();
754 m_pLibShairport->Unload();