2 * Many concepts and protocol specification in this code are taken
3 * from Shairport, by James Laird.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 #pragma GCC diagnostic ignored "-Wwrite-strings"
22 #include "AirTunesServer.h"
26 #include "utils/log.h"
27 #include "utils/StdString.h"
28 #include "network/Zeroconf.h"
29 #include "ApplicationMessenger.h"
30 #include "filesystem/FilePipe.h"
31 #include "Application.h"
32 #include "cores/paplayer/BXAcodec.h"
33 #include "music/tags/MusicInfoTag.h"
35 #include "utils/Variant.h"
36 #include "settings/AdvancedSettings.h"
38 using namespace XFILE;
40 DllLibShairport *CAirTunesServer::m_pLibShairport = NULL;
41 CAirTunesServer *CAirTunesServer::ServerInstance = NULL;
42 CStdString CAirTunesServer::m_macAddress;
46 XFILE::CFilePipe *pipe;
49 //audio output interface
50 void CAirTunesServer::AudioOutputFunctions::ao_initialize(void)
54 int CAirTunesServer::AudioOutputFunctions::ao_play(ao_device *device, char *output_samples, uint32_t num_bytes)
59 /*if (num_bytes && g_application.m_pPlayer)
60 g_application.m_pPlayer->SetCaching(CACHESTATE_NONE);*///TODO
62 ao_device_xbmc* device_xbmc = (ao_device_xbmc*) device;
64 #define NUM_OF_BYTES 64
66 unsigned int sentBytes = 0;
67 unsigned char buf[NUM_OF_BYTES];
68 while (sentBytes < num_bytes)
70 int n = (num_bytes - sentBytes < NUM_OF_BYTES ? num_bytes - sentBytes : NUM_OF_BYTES);
71 memcpy(buf, (char*) output_samples + sentBytes, n);
73 if (device_xbmc->pipe->Write(buf, n) == 0)
82 int CAirTunesServer::AudioOutputFunctions::ao_default_driver_id(void)
87 ao_device* CAirTunesServer::AudioOutputFunctions::ao_open_live(int driver_id, ao_sample_format *format,
90 ao_device_xbmc* device = new ao_device_xbmc();
92 device->pipe = new XFILE::CFilePipe;
93 device->pipe->OpenForWrite(XFILE::PipesManager::GetInstance().GetUniquePipeName());
94 device->pipe->SetOpenThreashold(300);
97 strncpy(header.fourcc, "BXA ", 4);
98 header.type = BXA_PACKET_TYPE_FMT;
99 header.bitsPerSample = format->bits;
100 header.channels = format->channels;
101 header.sampleRate = format->rate;
102 header.durationMs = 0;
104 if (device->pipe->Write(&header, sizeof(header)) == 0)
107 ThreadMessage tMsg = { TMSG_MEDIA_STOP };
108 g_application.getApplicationMessenger().SendMessage(tMsg, true);
111 item.SetPath(device->pipe->GetName());
112 item.SetMimeType("audio/x-xbmc-pcm");
113 item.SetProperty("isradio", true);
114 item.SetProperty("no-skip", true);
115 item.SetProperty("no-pause", true);
117 if (ao_get_option(option, "artist"))
118 item.GetMusicInfoTag()->SetArtist(ao_get_option(option, "artist"));
120 if (ao_get_option(option, "album"))
121 item.GetMusicInfoTag()->SetAlbum(ao_get_option(option, "album"));
123 if (ao_get_option(option, "name"))
124 item.GetMusicInfoTag()->SetTitle(ao_get_option(option, "name"));
126 g_application.getApplicationMessenger().PlayFile(item);
128 ThreadMessage tMsg2 = { TMSG_GUI_ACTIVATE_WINDOW, WINDOW_VISUALISATION, 0 };
129 g_application.getApplicationMessenger().SendMessage(tMsg2, true);
131 return (ao_device*) device;
134 int CAirTunesServer::AudioOutputFunctions::ao_close(ao_device *device)
136 ao_device_xbmc* device_xbmc = (ao_device_xbmc*) device;
137 device_xbmc->pipe->SetEof();
138 device_xbmc->pipe->Close();
139 delete device_xbmc->pipe;
141 ThreadMessage tMsg = { TMSG_MEDIA_STOP };
142 g_application.getApplicationMessenger().SendMessage(tMsg, true);
149 /* -- Device Setup/Playback/Teardown -- */
150 int CAirTunesServer::AudioOutputFunctions::ao_append_option(ao_option **options, const char *key, const char *value)
152 ao_option *op, *list;
154 op = (ao_option*) calloc(1,sizeof(ao_option));
155 if (op == NULL) return 0;
157 op->key = strdup(key);
158 op->value = strdup(value?value:"");
161 if ((list = *options) != NULL)
164 while (list->next != NULL)
176 void CAirTunesServer::AudioOutputFunctions::ao_free_options(ao_option *options)
180 while (options != NULL)
182 rest = options->next;
184 free(options->value);
190 char* CAirTunesServer::AudioOutputFunctions::ao_get_option(ao_option *options, const char* key)
193 while (options != NULL)
195 if (strcmp(options->key, key) == 0)
196 return options->value;
197 options = options->next;
203 bool CAirTunesServer::StartServer(int port, bool nonlocal, bool usePassword, const CStdString &password/*=""*/)
205 bool success = false;
206 CStdString pw = password;
207 CNetworkInterface *net = g_application.getNetwork().GetFirstConnectedInterface();
212 m_macAddress = net->GetMacAddress();
213 m_macAddress.Replace(":","");
214 while (m_macAddress.size() < 12)
216 m_macAddress = CStdString("0") + m_macAddress;
221 m_macAddress = "000102030405";
229 ServerInstance = new CAirTunesServer(port, nonlocal);
230 if (ServerInstance->Initialize(password))
232 ServerInstance->Create();
239 appName.Format("%s@XBMC", m_macAddress.c_str());
241 std::map<std::string, std::string> txt;
253 txt["txtvers"] = "1";
255 CZeroconf::GetInstance()->PublishService("servers.airtunes", "_raop._tcp", appName, port, txt);
261 void CAirTunesServer::StopServer(bool bWait)
265 if (m_pLibShairport->IsLoaded())
267 m_pLibShairport->shairport_exit();
269 ServerInstance->StopThread(bWait);
270 ServerInstance->Deinitialize();
273 delete ServerInstance;
274 ServerInstance = NULL;
277 CZeroconf::GetInstance()->RemoveService("servers.airtunes");
281 CAirTunesServer::CAirTunesServer(int port, bool nonlocal)
284 m_pLibShairport = new DllLibShairport();
287 CAirTunesServer::~CAirTunesServer()
289 if (m_pLibShairport->IsLoaded())
291 m_pLibShairport->Unload();
293 delete m_pLibShairport;
296 void CAirTunesServer::Process()
300 while (!m_bStop && m_pLibShairport->shairport_is_running())
302 m_pLibShairport->shairport_loop();
306 int shairport_log(const char* msg, size_t msgSize)
308 if( g_advancedSettings.m_logEnableAirtunes)
310 CLog::Log(LOGDEBUG, "AIRTUNES: %s", msg);
315 bool CAirTunesServer::Initialize(const CStdString &password)
324 hwStr.Format("--mac=%s", m_macAddress.c_str());
325 pwStr.Format("--password=%s",password.c_str());
327 if (!password.empty())
332 char *argv[] = { "--apname=XBMC", "--server_port=5000", (char*) hwStr.c_str(), (char *)pwStr.c_str(), NULL };
334 if (m_pLibShairport->Load())
337 struct AudioOutput ao;
338 ao.ao_initialize = AudioOutputFunctions::ao_initialize;
339 ao.ao_play = AudioOutputFunctions::ao_play;
340 ao.ao_default_driver_id = AudioOutputFunctions::ao_default_driver_id;
341 ao.ao_open_live = AudioOutputFunctions::ao_open_live;
342 ao.ao_close = AudioOutputFunctions::ao_close;
343 ao.ao_append_option = AudioOutputFunctions::ao_append_option;
344 ao.ao_free_options = AudioOutputFunctions::ao_free_options;
345 ao.ao_get_option = AudioOutputFunctions::ao_get_option;
346 struct printfPtr funcPtr;
347 funcPtr.extprintf = shairport_log;
349 m_pLibShairport->EnableDelayedUnload(false);
350 m_pLibShairport->shairport_set_ao(&ao);
351 m_pLibShairport->shairport_set_printf(&funcPtr);
352 m_pLibShairport->shairport_main(numArgs, argv);
358 void CAirTunesServer::Deinitialize()
360 if (m_pLibShairport && m_pLibShairport->IsLoaded())
362 m_pLibShairport->shairport_exit();
363 m_pLibShairport->Unload();