1 /*
2  * Copyright 2003-2021 The Music Player Daemon Project
3  * http://www.musicpd.org
4  * Copyright (C) 2014-2015 François 'mmu_man' Revol
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this program; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 
21 #include "HaikuOutputPlugin.hxx"
22 #include "../OutputAPI.hxx"
23 #include "mixer/MixerList.hxx"
24 #include "util/Domain.hxx"
25 #include "util/Math.hxx"
26 #include "system/Error.hxx"
27 #include "Log.hxx"
28 
29 #include <AppFileInfo.h>
30 #include <Application.h>
31 #include <Bitmap.h>
32 #include <IconUtils.h>
33 #include <MediaDefs.h>
34 #include <MediaRoster.h>
35 #include <Notification.h>
36 #include <OS.h>
37 #include <Resources.h>
38 #include <StringList.h>
39 #include <SoundPlayer.h>
40 
41 #include <string.h>
42 
43 #define UTF8_PLAY "\xE2\x96\xB6"
44 
45 class HaikuOutput final: AudioOutput {
46 	friend int haiku_output_get_volume(HaikuOutput &haiku);
47 	friend bool haiku_output_set_volume(HaikuOutput &haiku, unsigned volume);
48 
49 	size_t write_size;
50 
51 	media_raw_audio_format format;
52 	BSoundPlayer* sound_player;
53 
54 	sem_id new_buffer;
55 	sem_id buffer_done;
56 
57 	uint8* buffer;
58 	size_t buffer_size;
59 	size_t buffer_filled;
60 
61 	unsigned buffer_delay;
62 
63 public:
HaikuOutput(const ConfigBlock & block)64 	HaikuOutput(const ConfigBlock &block)
65 		:AudioOutput(0),
66 		 /* XXX: by default we should let the MediaKit propose the buffer size */
67 		 write_size(block.GetPositiveValue("write_size", 4096u)) {}
68 
69 	~HaikuOutput();
70 
71 	static AudioOutput *Create(EventLoop &event_loop,
72 				   const ConfigBlock &block);
73 
74 private:
75 	void Open(AudioFormat &audio_format) override;
76 	void Close() noexcept override;
77 
78 	size_t Play(const void *chunk, size_t size) override;
79 
80 	std::chrono::steady_clock::duration Delay() const noexcept override;
81 
82 	static void _FillBuffer(void* cookie, void* _buffer, size_t size,
83 		[[maybe_unused]] const media_raw_audio_format& _format);
84 	void FillBuffer(void* _buffer, size_t size,
85 		[[maybe_unused]] const media_raw_audio_format& _format);
86 
87 	void SendTag(const Tag &tag) override;
88 };
89 
90 static constexpr Domain haiku_output_domain("haiku_output");
91 
92 static void
initialize_application()93 initialize_application()
94 {
95 	// required to send the notification with a bitmap
96 	// TODO: actually Run() it and handle B_QUIT_REQUESTED
97 	// TODO: use some locking?
98 	if (be_app == NULL) {
99 		LogDebug(haiku_output_domain, "creating be_app");
100 		new BApplication("application/x-vnd.MusicPD");
101 	}
102 }
103 
104 static void
finalize_application()105 finalize_application()
106 {
107 	// TODO: use some locking?
108 	delete be_app;
109 	be_app = NULL;
110 	LogDebug(haiku_output_domain, "deleting be_app");
111 }
112 
113 static bool
haiku_test_default_device(void)114 haiku_test_default_device(void)
115 {
116 	BSoundPlayer testPlayer;
117 	return testPlayer.InitCheck() == B_OK;
118 
119 }
120 
121 inline AudioOutput *
Create(EventLoop &,const ConfigBlock & block)122 HaikuOutput::Create(EventLoop &, const ConfigBlock &block)
123 {
124 	initialize_application();
125 
126 	return new HaikuOutput(block);
127 }
128 
129 void
Close()130 HaikuOutput::Close() noexcept
131 {
132 	sound_player->SetHasData(false);
133 	delete_sem(new_buffer);
134 	delete_sem(buffer_done);
135 	sound_player->Stop();
136 	delete sound_player;
137 	sound_player = nullptr;
138 }
139 
~HaikuOutput()140 HaikuOutput::~HaikuOutput()
141 {
142 	finalize_application();
143 }
144 
145 void
_FillBuffer(void * cookie,void * buffer,size_t size,const media_raw_audio_format & format)146 HaikuOutput::_FillBuffer(void* cookie, void* buffer, size_t size,
147 	const media_raw_audio_format& format)
148 {
149 	HaikuOutput *ad = (HaikuOutput *)cookie;
150 	ad->FillBuffer(buffer, size, format);
151 }
152 
153 
154 void
FillBuffer(void * _buffer,size_t size,const media_raw_audio_format & _format)155 HaikuOutput::FillBuffer(void* _buffer, size_t size,
156 	[[maybe_unused]] const media_raw_audio_format& _format)
157 {
158 
159 	buffer = (uint8*)_buffer;
160 	buffer_size = size;
161 	buffer_filled = 0;
162 	bigtime_t start = system_time();
163 	release_sem(new_buffer);
164 	acquire_sem(buffer_done);
165 	bigtime_t w = system_time() - start;
166 
167 	if (w > 5000LL) {
168 		FmtDebug(haiku_output_domain,
169 			"haiku:fill_buffer waited {}us", w);
170 	}
171 
172 	if (buffer_filled < buffer_size) {
173 		memset(buffer + buffer_filled, 0,
174 			buffer_size - buffer_filled);
175 		FmtDebug(haiku_output_domain,
176 			 "haiku:fill_buffer filled {} size {} clearing remainder",
177 			 buffer_filled, buffer_size);
178 	}
179 }
180 
181 void
Open(AudioFormat & audio_format)182 HaikuOutput::Open(AudioFormat &audio_format)
183 {
184 	status_t err;
185 	format = media_multi_audio_format::wildcard;
186 
187 	switch (audio_format.format) {
188 	case SampleFormat::S8:
189 		format.format = media_raw_audio_format::B_AUDIO_CHAR;
190 		break;
191 
192 	case SampleFormat::S16:
193 		format.format = media_raw_audio_format::B_AUDIO_SHORT;
194 		break;
195 
196 	case SampleFormat::S32:
197 		format.format = media_raw_audio_format::B_AUDIO_INT;
198 		break;
199 
200 	case SampleFormat::FLOAT:
201 		format.format = media_raw_audio_format::B_AUDIO_FLOAT;
202 		break;
203 
204 	default:
205 		/* fall back to float */
206 		audio_format.format = SampleFormat::FLOAT;
207 		format.format = media_raw_audio_format::B_AUDIO_FLOAT;
208 		break;
209 	}
210 
211 	format.frame_rate = audio_format.sample_rate;
212 	format.byte_order = B_MEDIA_HOST_ENDIAN;
213 	format.channel_count = audio_format.channels;
214 
215 	buffer_size = 0;
216 
217 	if (write_size)
218 		format.buffer_size = write_size;
219 	else
220 		format.buffer_size = BMediaRoster::Roster()->AudioBufferSizeFor(
221 			format.channel_count, format.format,
222 			format.frame_rate, B_UNKNOWN_BUS) * 2;
223 
224 	FmtDebug(haiku_output_domain,
225 		 "using haiku driver ad: bs: {} ws: {} "
226 		 "channels {} rate {} fmt {:08x} bs {}",
227 		 buffer_size, write_size,
228 		 format.channel_count, format.frame_rate,
229 		 format.format, format.buffer_size);
230 
231 	sound_player = new BSoundPlayer(&format, "MPD Output",
232 		HaikuOutput::_FillBuffer, NULL, this);
233 
234 	err = sound_player->InitCheck();
235 	if (err != B_OK) {
236 		delete sound_player;
237 		sound_player = NULL;
238 		throw MakeErrno(err, "BSoundPlayer::InitCheck() failed");
239 	}
240 
241 	// calculate the allowable delay for the buffer (ms)
242 	buffer_delay = format.buffer_size;
243 	buffer_delay /= (format.format &
244 		media_raw_audio_format::B_AUDIO_SIZE_MASK);
245 	buffer_delay /= format.channel_count;
246 	buffer_delay *= 1000 / format.frame_rate;
247 	// half of the total buffer play time
248 	buffer_delay /= 2;
249 	FmtDebug(haiku_output_domain, "buffer delay: {} ms", buffer_delay);
250 
251 	new_buffer = create_sem(0, "New buffer request");
252 	buffer_done = create_sem(0, "Buffer done");
253 
254 	sound_player->SetVolume(1.0);
255 	sound_player->Start();
256 	sound_player->SetHasData(false);
257 }
258 
259 size_t
Play(const void * chunk,size_t size)260 HaikuOutput::Play(const void *chunk, size_t size)
261 {
262 	BSoundPlayer* const soundPlayer = sound_player;
263 	const uint8 *data = (const uint8 *)chunk;
264 
265 	if (!soundPlayer->HasData())
266 		soundPlayer->SetHasData(true);
267 	acquire_sem(new_buffer);
268 
269 	size_t bytesLeft = size;
270 	while (bytesLeft > 0) {
271 		if (buffer_filled == buffer_size) {
272 			// Request another buffer from BSoundPlayer
273 			release_sem(buffer_done);
274 			acquire_sem(new_buffer);
275 		}
276 
277 		const size_t copyBytes = std::min(bytesLeft, buffer_size
278 			- buffer_filled);
279 		memcpy(buffer + buffer_filled, data,
280 			copyBytes);
281 		buffer_filled += copyBytes;
282 		data += copyBytes;
283 		bytesLeft -= copyBytes;
284 	}
285 
286 
287 	if (buffer_filled < buffer_size) {
288 		// Continue filling this buffer the next time this function is called
289 		release_sem(new_buffer);
290 	} else {
291 		// Buffer is full
292 		release_sem(buffer_done);
293 		//soundPlayer->SetHasData(false);
294 	}
295 
296 	return size;
297 }
298 
299 inline std::chrono::steady_clock::duration
Delay() const300 HaikuOutput::Delay() const noexcept
301 {
302 	unsigned delay = buffer_filled ? 0 : buffer_delay;
303 
304 	//FmtDebug(haiku_output_domain,
305 	//		"delay={}", delay / 2);
306 	// XXX: doesn't work
307 	//return (delay / 2) ? 1 : 0;
308 	(void)delay;
309 
310 	return std::chrono::steady_clock::duration::zero();
311 }
312 
313 void
SendTag(const Tag & tag)314 HaikuOutput::SendTag(const Tag &tag)
315 {
316 	status_t err;
317 
318 	/* lazily initialized */
319 	static BBitmap *icon = NULL;
320 
321 	if (icon == NULL) {
322 		BAppFileInfo info;
323 		BResources resources;
324 		err = resources.SetToImage((const void *)&HaikuOutput::SendTag);
325 		BFile file(resources.File());
326 		err = info.SetTo(&file);
327 		icon = new BBitmap(BRect(0, 0, (float)B_LARGE_ICON - 1,
328 			(float)B_LARGE_ICON - 1), B_BITMAP_NO_SERVER_LINK, B_RGBA32);
329 		err = info.GetIcon(icon, B_LARGE_ICON);
330 		if (err != B_OK) {
331 			delete icon;
332 			icon = NULL;
333 		}
334 	}
335 
336 	BNotification notification(B_INFORMATION_NOTIFICATION);
337 
338 	BString messageId("mpd_");
339 	messageId << find_thread(NULL);
340 	notification.SetMessageID(messageId);
341 
342 	notification.SetGroup("Music Player Daemon");
343 
344 	char timebuf[16];
345 	unsigned seconds = 0;
346 	if (!tag.duration.IsNegative()) {
347 		seconds = tag.duration.ToS();
348 		snprintf(timebuf, sizeof(timebuf), "%02u:%02u:%02u",
349 			 seconds / 3600, (seconds % 3600) / 60, seconds % 60);
350 	}
351 
352 	BString artist;
353 	BString album;
354 	BString title;
355 	BString track;
356 	BString name;
357 
358 	for (const auto &item : tag)
359 	{
360 		switch (item.type) {
361 		case TAG_ARTIST:
362 		case TAG_ALBUM_ARTIST:
363 			if (artist.Length() == 0)
364 				artist << item.value;
365 			break;
366 		case TAG_ALBUM:
367 			if (album.Length() == 0)
368 				album << item.value;
369 			break;
370 		case TAG_TITLE:
371 			if (title.Length() == 0)
372 				title << item.value;
373 			break;
374 		case TAG_TRACK:
375 			if (track.Length() == 0)
376 				track << item.value;
377 			break;
378 		case TAG_NAME:
379 			if (name.Length() == 0)
380 				name << item.value;
381 			break;
382 		case TAG_GENRE:
383 		case TAG_DATE:
384 		case TAG_ORIGINAL_DATE:
385 		case TAG_PERFORMER:
386 		case TAG_COMMENT:
387 		case TAG_DISC:
388 		case TAG_COMPOSER:
389 		case TAG_MUSICBRAINZ_ARTISTID:
390 		case TAG_MUSICBRAINZ_ALBUMID:
391 		case TAG_MUSICBRAINZ_ALBUMARTISTID:
392 		case TAG_MUSICBRAINZ_TRACKID:
393 		default:
394 			FmtDebug(haiku_output_domain,
395 				 "tag item: type {} value '{}'\n",
396 				 item.type, item.value);
397 			break;
398 		}
399 	}
400 
401 	notification.SetTitle(UTF8_PLAY " Now Playing:");
402 
403 	BStringList content;
404 	if (name.Length())
405 		content.Add(name);
406 	if (artist.Length())
407 		content.Add(artist);
408 	if (album.Length())
409 		content.Add(album);
410 	if (track.Length())
411 		content.Add(track);
412 	if (title.Length())
413 		content.Add(title);
414 
415 	if (content.CountStrings() == 0)
416 		content.Add("(Unknown)");
417 
418 	BString full = content.Join(" " B_UTF8_BULLET " ");
419 
420 	if (seconds > 0)
421 		full << " (" << timebuf << ")";
422 
423 	notification.SetContent(full);
424 
425 	err = notification.SetIcon(icon);
426 
427 	notification.Send();
428 }
429 
430 int
haiku_output_get_volume(HaikuOutput & haiku)431 haiku_output_get_volume(HaikuOutput &haiku)
432 {
433 	BSoundPlayer* const soundPlayer = haiku.sound_player;
434 
435 	if (soundPlayer == NULL || soundPlayer->InitCheck() != B_OK)
436 		return 0;
437 
438 	return lround(soundPlayer->Volume() * 100);
439 }
440 
441 bool
haiku_output_set_volume(HaikuOutput & haiku,unsigned volume)442 haiku_output_set_volume(HaikuOutput &haiku, unsigned volume)
443 {
444 	BSoundPlayer* const soundPlayer = haiku.sound_player;
445 
446 	if (soundPlayer == NULL || soundPlayer->InitCheck() != B_OK)
447 		return false;
448 
449 	soundPlayer->SetVolume((float)volume / 100);
450 	return true;
451 }
452 
453 const struct AudioOutputPlugin haiku_output_plugin = {
454 	"haiku",
455 	haiku_test_default_device,
456 	&HaikuOutput::Create,
457 	&haiku_mixer_plugin,
458 };
459