output: add native Haiku audio output and mixer support
Also uses the notification system to display tags.
This commit is contained in:
parent
352ec364f0
commit
7743647460
|
@ -1355,6 +1355,14 @@ liboutput_plugins_a_SOURCES += \
|
||||||
src/output/plugins/FifoOutputPlugin.hxx
|
src/output/plugins/FifoOutputPlugin.hxx
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if HAVE_HAIKU
|
||||||
|
liboutput_plugins_a_SOURCES += \
|
||||||
|
src/output/plugins/HaikuOutputPlugin.cxx \
|
||||||
|
src/output/plugins/HaikuOutputPlugin.hxx
|
||||||
|
libmixer_plugins_a_SOURCES += \
|
||||||
|
src/mixer/plugins/HaikuMixerPlugin.cxx src/mixer/plugins/HaikuMixerPlugin.hxx
|
||||||
|
endif
|
||||||
|
|
||||||
if ENABLE_PIPE_OUTPUT
|
if ENABLE_PIPE_OUTPUT
|
||||||
liboutput_plugins_a_SOURCES += \
|
liboutput_plugins_a_SOURCES += \
|
||||||
src/output/plugins/PipeOutputPlugin.cxx \
|
src/output/plugins/PipeOutputPlugin.cxx \
|
||||||
|
|
19
configure.ac
19
configure.ac
|
@ -333,6 +333,11 @@ AC_ARG_ENABLE(fifo,
|
||||||
[disable support for writing audio to a FIFO (default: enable)]),,
|
[disable support for writing audio to a FIFO (default: enable)]),,
|
||||||
enable_fifo=yes)
|
enable_fifo=yes)
|
||||||
|
|
||||||
|
AC_ARG_ENABLE(haiku,
|
||||||
|
AS_HELP_STRING([--enable-haiku],
|
||||||
|
[enable the Haiku output plugin (default: auto)]),,
|
||||||
|
enable_haiku=auto)
|
||||||
|
|
||||||
AC_ARG_ENABLE(httpd-output,
|
AC_ARG_ENABLE(httpd-output,
|
||||||
AS_HELP_STRING([--enable-httpd-output],
|
AS_HELP_STRING([--enable-httpd-output],
|
||||||
[enables the HTTP server output]),,
|
[enables the HTTP server output]),,
|
||||||
|
@ -1129,6 +1134,19 @@ fi
|
||||||
MPD_DEFINE_CONDITIONAL(enable_fifo, HAVE_FIFO,
|
MPD_DEFINE_CONDITIONAL(enable_fifo, HAVE_FIFO,
|
||||||
[support for writing audio to a FIFO])
|
[support for writing audio to a FIFO])
|
||||||
|
|
||||||
|
dnl ----------------------------------- Haiku ---------------------------------
|
||||||
|
if test x$enable_haiku = xauto; then
|
||||||
|
AC_CHECK_HEADER(media/MediaDefs.h,
|
||||||
|
[enable_haiku=yes],
|
||||||
|
[enable_haiku=no])
|
||||||
|
fi
|
||||||
|
if test x$enable_haiku = xyes; then
|
||||||
|
AC_DEFINE(HAVE_HAIKU,1,[Define for compiling Haiku support])
|
||||||
|
LIBS="$LIBS -lbe -lmedia"
|
||||||
|
fi
|
||||||
|
|
||||||
|
AM_CONDITIONAL(HAVE_HAIKU, test x$enable_haiku = xyes)
|
||||||
|
|
||||||
dnl ------------------------------- HTTPD Output ------------------------------
|
dnl ------------------------------- HTTPD Output ------------------------------
|
||||||
if test x$enable_httpd_output = xauto; then
|
if test x$enable_httpd_output = xauto; then
|
||||||
# handle HTTPD auto-detection: disable if no encoder is
|
# handle HTTPD auto-detection: disable if no encoder is
|
||||||
|
@ -1422,6 +1440,7 @@ printf '\nPlayback support:\n\t'
|
||||||
results(alsa,ALSA)
|
results(alsa,ALSA)
|
||||||
results(fifo,FIFO)
|
results(fifo,FIFO)
|
||||||
results(recorder_output,[File Recorder])
|
results(recorder_output,[File Recorder])
|
||||||
|
results(haiku,[Haiku])
|
||||||
results(httpd_output,[HTTP Daemon])
|
results(httpd_output,[HTTP Daemon])
|
||||||
results(jack,[JACK])
|
results(jack,[JACK])
|
||||||
printf '\n\t'
|
printf '\n\t'
|
||||||
|
|
|
@ -30,6 +30,7 @@ struct MixerPlugin;
|
||||||
extern const MixerPlugin null_mixer_plugin;
|
extern const MixerPlugin null_mixer_plugin;
|
||||||
extern const MixerPlugin software_mixer_plugin;
|
extern const MixerPlugin software_mixer_plugin;
|
||||||
extern const MixerPlugin alsa_mixer_plugin;
|
extern const MixerPlugin alsa_mixer_plugin;
|
||||||
|
extern const MixerPlugin haiku_mixer_plugin;
|
||||||
extern const MixerPlugin oss_mixer_plugin;
|
extern const MixerPlugin oss_mixer_plugin;
|
||||||
extern const MixerPlugin roar_mixer_plugin;
|
extern const MixerPlugin roar_mixer_plugin;
|
||||||
extern const MixerPlugin pulse_mixer_plugin;
|
extern const MixerPlugin pulse_mixer_plugin;
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2015 The Music Player Daemon Project
|
||||||
|
* Copyright (C) 2010-2011 Philipp 'ph3-der-loewe' Schafft
|
||||||
|
* Copyright (C) 2010-2011 Hans-Kristian 'maister' Arntzen
|
||||||
|
* Copyright (C) 2014-2015 François 'mmu_man' Revol
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "mixer/MixerInternal.hxx"
|
||||||
|
#include "output/plugins/HaikuOutputPlugin.hxx"
|
||||||
|
#include "Compiler.h"
|
||||||
|
|
||||||
|
class HaikuMixer final : public Mixer {
|
||||||
|
/** the base mixer class */
|
||||||
|
HaikuOutput &self;
|
||||||
|
|
||||||
|
public:
|
||||||
|
HaikuMixer(HaikuOutput &_output, MixerListener &_listener)
|
||||||
|
:Mixer(haiku_mixer_plugin, _listener),
|
||||||
|
self(_output) {}
|
||||||
|
|
||||||
|
/* virtual methods from class Mixer */
|
||||||
|
virtual bool Open(gcc_unused Error &error) override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Close() override {
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual int GetVolume(Error &error) override;
|
||||||
|
virtual bool SetVolume(unsigned volume, Error &error) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
static Mixer *
|
||||||
|
haiku_mixer_init(gcc_unused EventLoop &event_loop, AudioOutput &ao,
|
||||||
|
MixerListener &listener,
|
||||||
|
gcc_unused const ConfigBlock &block,
|
||||||
|
gcc_unused Error &error)
|
||||||
|
{
|
||||||
|
return new HaikuMixer((HaikuOutput &)ao, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
HaikuMixer::GetVolume(gcc_unused Error &error)
|
||||||
|
{
|
||||||
|
return haiku_output_get_volume(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
HaikuMixer::SetVolume(unsigned volume, gcc_unused Error &error)
|
||||||
|
{
|
||||||
|
return haiku_output_set_volume(self, volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MixerPlugin haiku_mixer_plugin = {
|
||||||
|
haiku_mixer_init,
|
||||||
|
false,
|
||||||
|
};
|
|
@ -24,6 +24,7 @@
|
||||||
#include "plugins/AoOutputPlugin.hxx"
|
#include "plugins/AoOutputPlugin.hxx"
|
||||||
#include "plugins/FifoOutputPlugin.hxx"
|
#include "plugins/FifoOutputPlugin.hxx"
|
||||||
#include "plugins/httpd/HttpdOutputPlugin.hxx"
|
#include "plugins/httpd/HttpdOutputPlugin.hxx"
|
||||||
|
#include "plugins/HaikuOutputPlugin.hxx"
|
||||||
#include "plugins/JackOutputPlugin.hxx"
|
#include "plugins/JackOutputPlugin.hxx"
|
||||||
#include "plugins/NullOutputPlugin.hxx"
|
#include "plugins/NullOutputPlugin.hxx"
|
||||||
#include "plugins/OpenALOutputPlugin.hxx"
|
#include "plugins/OpenALOutputPlugin.hxx"
|
||||||
|
@ -51,6 +52,9 @@ const AudioOutputPlugin *const audio_output_plugins[] = {
|
||||||
#ifdef HAVE_FIFO
|
#ifdef HAVE_FIFO
|
||||||
&fifo_output_plugin,
|
&fifo_output_plugin,
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef HAVE_HAIKU
|
||||||
|
&haiku_output_plugin,
|
||||||
|
#endif
|
||||||
#ifdef ENABLE_PIPE_OUTPUT
|
#ifdef ENABLE_PIPE_OUTPUT
|
||||||
&pipe_output_plugin,
|
&pipe_output_plugin,
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,532 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2015 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
* Copyright (C) 2014-2015 François 'mmu_man' Revol
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "HaikuOutputPlugin.hxx"
|
||||||
|
#include "../OutputAPI.hxx"
|
||||||
|
#include "../Wrapper.hxx"
|
||||||
|
#include "mixer/MixerList.hxx"
|
||||||
|
#include "util/Error.hxx"
|
||||||
|
#include "util/Domain.hxx"
|
||||||
|
#include "Log.hxx"
|
||||||
|
|
||||||
|
#include <AppFileInfo.h>
|
||||||
|
#include <Application.h>
|
||||||
|
#include <Bitmap.h>
|
||||||
|
#include <IconUtils.h>
|
||||||
|
#include <MediaDefs.h>
|
||||||
|
#include <MediaRoster.h>
|
||||||
|
#include <Notification.h>
|
||||||
|
#include <OS.h>
|
||||||
|
#include <Resources.h>
|
||||||
|
#include <StringList.h>
|
||||||
|
#include <SoundPlayer.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define UTF8_PLAY "\xE2\x96\xB6"
|
||||||
|
|
||||||
|
class HaikuOutput {
|
||||||
|
friend struct AudioOutputWrapper<HaikuOutput>;
|
||||||
|
friend int haiku_output_get_volume(HaikuOutput &haiku);
|
||||||
|
friend bool haiku_output_set_volume(HaikuOutput &haiku, unsigned volume);
|
||||||
|
|
||||||
|
AudioOutput base;
|
||||||
|
|
||||||
|
size_t write_size;
|
||||||
|
|
||||||
|
media_raw_audio_format* format;
|
||||||
|
BSoundPlayer* sound_player;
|
||||||
|
|
||||||
|
sem_id new_buffer;
|
||||||
|
sem_id buffer_done;
|
||||||
|
|
||||||
|
uint8* buffer;
|
||||||
|
size_t buffer_size;
|
||||||
|
size_t buffer_filled;
|
||||||
|
|
||||||
|
unsigned buffer_delay;
|
||||||
|
|
||||||
|
public:
|
||||||
|
HaikuOutput()
|
||||||
|
:base(haiku_output_plugin) {}
|
||||||
|
~HaikuOutput();
|
||||||
|
|
||||||
|
bool Initialize(const ConfigBlock &block, Error &error) {
|
||||||
|
return base.Configure(block, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static HaikuOutput *Create(const ConfigBlock &block, Error &error);
|
||||||
|
|
||||||
|
bool Open(AudioFormat &audio_format, Error &error);
|
||||||
|
|
||||||
|
void Close() {
|
||||||
|
DoClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Play(const void *chunk, size_t size, Error &error);
|
||||||
|
void Cancel();
|
||||||
|
|
||||||
|
bool Configure(const ConfigBlock &block, Error &error);
|
||||||
|
|
||||||
|
size_t Delay();
|
||||||
|
|
||||||
|
void FillBuffer(void* _buffer, size_t size,
|
||||||
|
gcc_unused const media_raw_audio_format& _format);
|
||||||
|
|
||||||
|
void SendTag(const Tag &tag);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
void DoClose();
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr Domain haiku_output_domain("haiku_output");
|
||||||
|
|
||||||
|
static void
|
||||||
|
haiku_output_error(Error &error_r, status_t err)
|
||||||
|
{
|
||||||
|
const char *error = strerror(err);
|
||||||
|
error_r.Set(haiku_output_domain, err, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
initialize_application()
|
||||||
|
{
|
||||||
|
// required to send the notification with a bitmap
|
||||||
|
// TODO: actually Run() it and handle B_QUIT_REQUESTED
|
||||||
|
// TODO: use some locking?
|
||||||
|
if (be_app == NULL) {
|
||||||
|
FormatDebug(haiku_output_domain, "creating be_app\n");
|
||||||
|
new BApplication("application/x-vnd.MusicPD");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
finalize_application()
|
||||||
|
{
|
||||||
|
// TODO: use some locking?
|
||||||
|
delete be_app;
|
||||||
|
be_app = NULL;
|
||||||
|
FormatDebug(haiku_output_domain, "deleting be_app\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
HaikuOutput::Configure(const ConfigBlock &block, Error &error)
|
||||||
|
{
|
||||||
|
/* XXX: by default we should let the MediaKit propose the buffer size */
|
||||||
|
write_size = block.GetBlockValue("write_size", 4096u);
|
||||||
|
|
||||||
|
format = (media_raw_audio_format*)malloc(
|
||||||
|
sizeof(media_raw_audio_format));
|
||||||
|
if (format == nullptr) {
|
||||||
|
haiku_output_error(error, B_NO_MEMORY);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
haiku_test_default_device(void)
|
||||||
|
{
|
||||||
|
BSoundPlayer testPlayer;
|
||||||
|
return testPlayer.InitCheck() == B_OK;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
inline HaikuOutput *
|
||||||
|
HaikuOutput::Create(const ConfigBlock &block, Error &error)
|
||||||
|
{
|
||||||
|
initialize_application();
|
||||||
|
|
||||||
|
HaikuOutput *ad = new HaikuOutput();
|
||||||
|
|
||||||
|
if (!ad->Initialize(block, error)) {
|
||||||
|
delete ad;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ad->Configure(block, error)) {
|
||||||
|
delete ad;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ad;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
HaikuOutput::DoClose()
|
||||||
|
{
|
||||||
|
sound_player->SetHasData(false);
|
||||||
|
delete_sem(new_buffer);
|
||||||
|
delete_sem(buffer_done);
|
||||||
|
sound_player->Stop();
|
||||||
|
delete sound_player;
|
||||||
|
sound_player = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
HaikuOutput::~HaikuOutput()
|
||||||
|
{
|
||||||
|
free(format);
|
||||||
|
delete_sem(new_buffer);
|
||||||
|
delete_sem(buffer_done);
|
||||||
|
|
||||||
|
finalize_application();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
fill_buffer(void* cookie, void* buffer, size_t size,
|
||||||
|
const media_raw_audio_format& format)
|
||||||
|
{
|
||||||
|
HaikuOutput *ad = (HaikuOutput *)cookie;
|
||||||
|
ad->FillBuffer(buffer, size, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
HaikuOutput::FillBuffer(void* _buffer, size_t size,
|
||||||
|
gcc_unused const media_raw_audio_format& _format)
|
||||||
|
{
|
||||||
|
|
||||||
|
buffer = (uint8*)_buffer;
|
||||||
|
buffer_size = size;
|
||||||
|
buffer_filled = 0;
|
||||||
|
bigtime_t start = system_time();
|
||||||
|
release_sem(new_buffer);
|
||||||
|
acquire_sem(buffer_done);
|
||||||
|
bigtime_t w = system_time() - start;
|
||||||
|
|
||||||
|
if (w > 5000LL) {
|
||||||
|
FormatDebug(haiku_output_domain,
|
||||||
|
"haiku:fill_buffer waited %Ldus\n", w);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer_filled < buffer_size) {
|
||||||
|
memset(buffer + buffer_filled, 0,
|
||||||
|
buffer_size - buffer_filled);
|
||||||
|
FormatDebug(haiku_output_domain,
|
||||||
|
"haiku:fill_buffer filled %d size %d clearing remainder\n",
|
||||||
|
(int)buffer_filled, (int)buffer_size);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool
|
||||||
|
HaikuOutput::Open(AudioFormat &audio_format, Error &error)
|
||||||
|
{
|
||||||
|
status_t err;
|
||||||
|
*format = media_multi_audio_format::wildcard;
|
||||||
|
|
||||||
|
switch (audio_format.format) {
|
||||||
|
case SampleFormat::S8:
|
||||||
|
format->format = media_raw_audio_format::B_AUDIO_CHAR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SampleFormat::S16:
|
||||||
|
format->format = media_raw_audio_format::B_AUDIO_SHORT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SampleFormat::S32:
|
||||||
|
format->format = media_raw_audio_format::B_AUDIO_INT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SampleFormat::FLOAT:
|
||||||
|
format->format = media_raw_audio_format::B_AUDIO_FLOAT;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
/* fall back to float */
|
||||||
|
audio_format.format = SampleFormat::FLOAT;
|
||||||
|
format->format = media_raw_audio_format::B_AUDIO_FLOAT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
format->frame_rate = audio_format.sample_rate;
|
||||||
|
format->byte_order = B_MEDIA_HOST_ENDIAN;
|
||||||
|
format->channel_count = audio_format.channels;
|
||||||
|
|
||||||
|
buffer_size = 0;
|
||||||
|
|
||||||
|
if (write_size)
|
||||||
|
format->buffer_size = write_size;
|
||||||
|
else
|
||||||
|
format->buffer_size = BMediaRoster::Roster()->AudioBufferSizeFor(
|
||||||
|
format->channel_count, format->format,
|
||||||
|
format->frame_rate, B_UNKNOWN_BUS) * 2;
|
||||||
|
|
||||||
|
FormatDebug(haiku_output_domain,
|
||||||
|
"using haiku driver ad: bs: %d ws: %d "
|
||||||
|
"channels %d rate %f fmt %08lx bs %d\n",
|
||||||
|
(int)buffer_size, (int)write_size,
|
||||||
|
(int)format->channel_count, format->frame_rate,
|
||||||
|
format->format, (int)format->buffer_size);
|
||||||
|
|
||||||
|
sound_player = new BSoundPlayer(format, "MPD Output",
|
||||||
|
fill_buffer, NULL, this);
|
||||||
|
|
||||||
|
err = sound_player->InitCheck();
|
||||||
|
if (err != B_OK) {
|
||||||
|
delete sound_player;
|
||||||
|
sound_player = NULL;
|
||||||
|
haiku_output_error(error, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the allowable delay for the buffer (ms)
|
||||||
|
buffer_delay = format->buffer_size;
|
||||||
|
buffer_delay /= (format->format &
|
||||||
|
media_raw_audio_format::B_AUDIO_SIZE_MASK);
|
||||||
|
buffer_delay /= format->channel_count;
|
||||||
|
buffer_delay *= 1000 / format->frame_rate;
|
||||||
|
// half of the total buffer play time
|
||||||
|
buffer_delay /= 2;
|
||||||
|
FormatDebug(haiku_output_domain,
|
||||||
|
"buffer delay: %d ms\n", buffer_delay);
|
||||||
|
|
||||||
|
new_buffer = create_sem(0, "New buffer request");
|
||||||
|
buffer_done = create_sem(0, "Buffer done");
|
||||||
|
|
||||||
|
sound_player->SetVolume(1.0);
|
||||||
|
sound_player->Start();
|
||||||
|
sound_player->SetHasData(false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t
|
||||||
|
HaikuOutput::Play(const void *chunk, size_t size, gcc_unused Error &error)
|
||||||
|
{
|
||||||
|
BSoundPlayer* const soundPlayer = sound_player;
|
||||||
|
const uint8 *data = (const uint8 *)chunk;
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
soundPlayer->SetHasData(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!soundPlayer->HasData())
|
||||||
|
soundPlayer->SetHasData(true);
|
||||||
|
acquire_sem(new_buffer);
|
||||||
|
|
||||||
|
size_t bytesLeft = size;
|
||||||
|
while (bytesLeft > 0) {
|
||||||
|
if (buffer_filled == buffer_size) {
|
||||||
|
// Request another buffer from BSoundPlayer
|
||||||
|
release_sem(buffer_done);
|
||||||
|
acquire_sem(new_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t copyBytes = std::min(bytesLeft, buffer_size
|
||||||
|
- buffer_filled);
|
||||||
|
memcpy(buffer + buffer_filled, data,
|
||||||
|
copyBytes);
|
||||||
|
buffer_filled += copyBytes;
|
||||||
|
data += copyBytes;
|
||||||
|
bytesLeft -= copyBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (buffer_filled < buffer_size) {
|
||||||
|
// Continue filling this buffer the next time this function is called
|
||||||
|
release_sem(new_buffer);
|
||||||
|
} else {
|
||||||
|
// Buffer is full
|
||||||
|
release_sem(buffer_done);
|
||||||
|
//soundPlayer->SetHasData(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t
|
||||||
|
HaikuOutput::Delay()
|
||||||
|
{
|
||||||
|
unsigned delay = buffer_filled ? 0 : buffer_delay;
|
||||||
|
|
||||||
|
//FormatDebug(haiku_output_domain,
|
||||||
|
// "delay=%d\n", delay / 2);
|
||||||
|
// XXX: doesn't work
|
||||||
|
//return (delay / 2) ? 1 : 0;
|
||||||
|
(void)delay;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
HaikuOutput::SendTag(const Tag &tag)
|
||||||
|
{
|
||||||
|
status_t err;
|
||||||
|
|
||||||
|
/* lazily initialized */
|
||||||
|
static BBitmap *icon = NULL;
|
||||||
|
|
||||||
|
if (icon == NULL) {
|
||||||
|
BAppFileInfo info;
|
||||||
|
BResources resources;
|
||||||
|
err = resources.SetToImage((const void *)&HaikuOutput::SendTag);
|
||||||
|
BFile file(resources.File());
|
||||||
|
err = info.SetTo(&file);
|
||||||
|
icon = new BBitmap(BRect(0, 0, (float)B_LARGE_ICON - 1,
|
||||||
|
(float)B_LARGE_ICON - 1), B_BITMAP_NO_SERVER_LINK, B_RGBA32);
|
||||||
|
err = info.GetIcon(icon, B_LARGE_ICON);
|
||||||
|
if (err != B_OK) {
|
||||||
|
delete icon;
|
||||||
|
icon = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BNotification notification(B_INFORMATION_NOTIFICATION);
|
||||||
|
|
||||||
|
BString messageId("mpd_");
|
||||||
|
messageId << find_thread(NULL);
|
||||||
|
notification.SetMessageID(messageId);
|
||||||
|
|
||||||
|
notification.SetGroup("Music Player Daemon");
|
||||||
|
|
||||||
|
char timebuf[16];
|
||||||
|
unsigned seconds = 0;
|
||||||
|
if (!tag.duration.IsNegative()) {
|
||||||
|
seconds = tag.duration.ToS();
|
||||||
|
snprintf(timebuf, sizeof(timebuf), "%02d:%02d:%02d",
|
||||||
|
seconds / 3600, (seconds % 3600) / 60, seconds % 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
BString artist;
|
||||||
|
BString album;
|
||||||
|
BString title;
|
||||||
|
BString track;
|
||||||
|
BString name;
|
||||||
|
|
||||||
|
for (const auto &item : tag)
|
||||||
|
{
|
||||||
|
switch (item.type) {
|
||||||
|
case TAG_ARTIST:
|
||||||
|
case TAG_ALBUM_ARTIST:
|
||||||
|
if (artist.Length() == 0)
|
||||||
|
artist << item.value;
|
||||||
|
break;
|
||||||
|
case TAG_ALBUM:
|
||||||
|
if (album.Length() == 0)
|
||||||
|
album << item.value;
|
||||||
|
break;
|
||||||
|
case TAG_TITLE:
|
||||||
|
if (title.Length() == 0)
|
||||||
|
title << item.value;
|
||||||
|
break;
|
||||||
|
case TAG_TRACK:
|
||||||
|
if (track.Length() == 0)
|
||||||
|
track << item.value;
|
||||||
|
break;
|
||||||
|
case TAG_NAME:
|
||||||
|
if (name.Length() == 0)
|
||||||
|
name << item.value;
|
||||||
|
break;
|
||||||
|
case TAG_GENRE:
|
||||||
|
case TAG_DATE:
|
||||||
|
case TAG_PERFORMER:
|
||||||
|
case TAG_COMMENT:
|
||||||
|
case TAG_DISC:
|
||||||
|
case TAG_COMPOSER:
|
||||||
|
case TAG_MUSICBRAINZ_ARTISTID:
|
||||||
|
case TAG_MUSICBRAINZ_ALBUMID:
|
||||||
|
case TAG_MUSICBRAINZ_ALBUMARTISTID:
|
||||||
|
case TAG_MUSICBRAINZ_TRACKID:
|
||||||
|
default:
|
||||||
|
FormatDebug(haiku_output_domain,
|
||||||
|
"tag item: type %d value '%s'\n", item.type, item.value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.SetTitle(UTF8_PLAY " Now Playing:");
|
||||||
|
|
||||||
|
BStringList content;
|
||||||
|
if (name.Length())
|
||||||
|
content.Add(name);
|
||||||
|
if (artist.Length())
|
||||||
|
content.Add(artist);
|
||||||
|
if (album.Length())
|
||||||
|
content.Add(album);
|
||||||
|
if (track.Length())
|
||||||
|
content.Add(track);
|
||||||
|
if (title.Length())
|
||||||
|
content.Add(title);
|
||||||
|
|
||||||
|
if (content.CountStrings() == 0)
|
||||||
|
content.Add("(Unknown)");
|
||||||
|
|
||||||
|
BString full = content.Join(" " B_UTF8_BULLET " ");
|
||||||
|
|
||||||
|
if (seconds > 0)
|
||||||
|
full << " (" << timebuf << ")";
|
||||||
|
|
||||||
|
notification.SetContent(full);
|
||||||
|
|
||||||
|
err = notification.SetIcon(icon);
|
||||||
|
|
||||||
|
notification.Send();
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
haiku_output_get_volume(HaikuOutput &haiku)
|
||||||
|
{
|
||||||
|
BSoundPlayer* const soundPlayer = haiku.sound_player;
|
||||||
|
|
||||||
|
if (soundPlayer == NULL || soundPlayer->InitCheck() != B_OK)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return (int)(soundPlayer->Volume() * 100 + 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
haiku_output_set_volume(HaikuOutput &haiku, unsigned volume)
|
||||||
|
{
|
||||||
|
BSoundPlayer* const soundPlayer = haiku.sound_player;
|
||||||
|
|
||||||
|
if (soundPlayer == NULL || soundPlayer->InitCheck() != B_OK)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
soundPlayer->SetVolume((float)volume / 100);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef AudioOutputWrapper<HaikuOutput> Wrapper;
|
||||||
|
|
||||||
|
const struct AudioOutputPlugin haiku_output_plugin = {
|
||||||
|
"haiku",
|
||||||
|
haiku_test_default_device,
|
||||||
|
&Wrapper::Init,
|
||||||
|
&Wrapper::Finish,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
&Wrapper::Open,
|
||||||
|
&Wrapper::Close,
|
||||||
|
&Wrapper::Delay,
|
||||||
|
&Wrapper::SendTag,
|
||||||
|
&Wrapper::Play,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
nullptr,
|
||||||
|
|
||||||
|
&haiku_mixer_plugin,
|
||||||
|
};
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2003-2015 The Music Player Daemon Project
|
||||||
|
* http://www.musicpd.org
|
||||||
|
* Copyright (C) 2014-2015 François 'mmu_man' Revol
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU 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 MPD_HAIKU_OUTPUT_PLUGIN_HXX
|
||||||
|
#define MPD_HAIKU_OUTPUT_PLUGIN_HXX
|
||||||
|
|
||||||
|
class HaikuOutput;
|
||||||
|
|
||||||
|
extern const struct AudioOutputPlugin haiku_output_plugin;
|
||||||
|
|
||||||
|
int
|
||||||
|
haiku_output_get_volume(HaikuOutput &haiku);
|
||||||
|
|
||||||
|
bool
|
||||||
|
haiku_output_set_volume(HaikuOutput &haiku, unsigned volume);
|
||||||
|
|
||||||
|
#endif
|
Loading…
Reference in New Issue