* Copyright 2002-2009, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Marcus Overhagen
* Jérôme Duval
*/
#include <SoundPlayer.h>
#include <math.h>
#include <string.h>
#include <Autolock.h>
#include <MediaRoster.h>
#include <ParameterWeb.h>
#include <Sound.h>
#include <TimeSource.h>
#include "SoundPlayNode.h"
#include "MediaDebug.h"
enum {
F_NODES_CONNECTED = (1 << 0),
F_HAS_DATA = (1 << 1),
F_IS_STARTED = (1 << 2),
F_MUST_RELEASE_MIXER = (1 << 3),
};
static BSoundPlayer::play_id sCurrentPlayID = 1;
BSoundPlayer::BSoundPlayer(const char* name, BufferPlayerFunc playerFunction,
EventNotifierFunc eventNotifierFunction, void* cookie)
{
CALLED();
TRACE("BSoundPlayer::BSoundPlayer: default constructor used\n");
media_multi_audio_format format = media_multi_audio_format::wildcard;
_Init(NULL, &format, name, NULL, playerFunction, eventNotifierFunction,
cookie);
}
BSoundPlayer::BSoundPlayer(const media_raw_audio_format* _format,
const char* name, BufferPlayerFunc playerFunction,
EventNotifierFunc eventNotifierFunction, void* cookie)
{
CALLED();
TRACE("BSoundPlayer::BSoundPlayer: raw audio format constructor used\n");
media_multi_audio_format format = media_multi_audio_format::wildcard;
*(media_raw_audio_format*)&format = *_format;
#if DEBUG > 0
char buf[100];
media_format tmp; tmp.type = B_MEDIA_RAW_AUDIO; tmp.u.raw_audio = format;
string_for_format(tmp, buf, sizeof(buf));
TRACE("BSoundPlayer::BSoundPlayer: format %s\n", buf);
#endif
_Init(NULL, &format, name, NULL, playerFunction, eventNotifierFunction,
cookie);
}
BSoundPlayer::BSoundPlayer(const media_node& toNode,
const media_multi_audio_format* format, const char* name,
const media_input* input, BufferPlayerFunc playerFunction,
EventNotifierFunc eventNotifierFunction, void* cookie)
{
CALLED();
TRACE("BSoundPlayer::BSoundPlayer: multi audio format constructor used\n");
if ((toNode.kind & B_BUFFER_CONSUMER) == 0)
debugger("BSoundPlayer: toNode must have B_BUFFER_CONSUMER kind!\n");
#if DEBUG > 0
char buf[100];
media_format tmp; tmp.type = B_MEDIA_RAW_AUDIO; tmp.u.raw_audio = *format;
string_for_format(tmp, buf, sizeof(buf));
TRACE("BSoundPlayer::BSoundPlayer: format %s\n", buf);
#endif
_Init(&toNode, format, name, input, playerFunction, eventNotifierFunction,
cookie);
}
BSoundPlayer::~BSoundPlayer()
{
CALLED();
if ((fFlags & F_IS_STARTED) != 0) {
Stop(true, false);
}
status_t err;
BMediaRoster* roster = BMediaRoster::Roster();
if (roster == NULL) {
TRACE("BSoundPlayer::~BSoundPlayer: Couldn't get BMediaRoster\n");
goto cleanup;
}
if ((fFlags & F_NODES_CONNECTED) != 0) {
err = roster->Disconnect(fMediaOutput, fMediaInput);
if (err != B_OK) {
TRACE("BSoundPlayer::~BSoundPlayer: Error disconnecting nodes: "
"%" B_PRId32 " (%s)\n", err, strerror(err));
}
}
if ((fFlags & F_MUST_RELEASE_MIXER) != 0) {
err = roster->ReleaseNode(fMediaInput.node);
if (err != B_OK) {
TRACE("BSoundPlayer::~BSoundPlayer: Error releasing input node: "
"%" B_PRId32 " (%s)\n", err, strerror(err));
}
}
cleanup:
if (fPlayerNode != NULL && fPlayerNode->Release() != NULL) {
TRACE("BSoundPlayer::~BSoundPlayer: Error the producer node "
"appears to be acquired by someone else than us!");
}
delete fParameterWeb;
}
status_t
BSoundPlayer::InitCheck()
{
CALLED();
return fInitStatus;
}
media_raw_audio_format
BSoundPlayer::Format() const
{
CALLED();
if ((fFlags & F_NODES_CONNECTED) == 0)
return media_raw_audio_format::wildcard;
return fPlayerNode->Format();
}
status_t
BSoundPlayer::Start()
{
CALLED();
if ((fFlags & F_NODES_CONNECTED) == 0)
return B_NO_INIT;
if ((fFlags & F_IS_STARTED) != 0)
return B_OK;
BMediaRoster* roster = BMediaRoster::Roster();
if (!roster) {
TRACE("BSoundPlayer::Start: Couldn't get BMediaRoster\n");
return B_ERROR;
}
if (!fPlayerNode->TimeSource()->IsRunning()) {
roster->StartTimeSource(fPlayerNode->TimeSource()->Node(),
fPlayerNode->TimeSource()->RealTime());
}
status_t err = roster->StartNode(fPlayerNode->Node(),
fPlayerNode->TimeSource()->Now() + Latency() + 5000);
if (err != B_OK) {
TRACE("BSoundPlayer::Start: StartNode failed, %" B_PRId32, err);
return err;
}
if (fNotifierFunc != NULL)
fNotifierFunc(fCookie, B_STARTED, this);
SetHasData(true);
atomic_or(&fFlags, F_IS_STARTED);
return B_OK;
}
void
BSoundPlayer::Stop(bool block, bool flush)
{
CALLED();
TRACE("BSoundPlayer::Stop: block %d, flush %d\n", (int)block, (int)flush);
if ((fFlags & F_NODES_CONNECTED) == 0)
return;
if ((fFlags & F_IS_STARTED) != 0) {
BMediaRoster* roster = BMediaRoster::Roster();
if (roster == NULL) {
TRACE("BSoundPlayer::Stop: Couldn't get BMediaRoster\n");
return;
}
roster->StopNode(fPlayerNode->Node(), 0, true);
atomic_and(&fFlags, ~F_IS_STARTED);
}
if (block) {
int tries;
for (tries = 250; fPlayerNode->IsPlaying() && tries != 0; tries--)
snooze(2000);
DEBUG_ONLY(if (tries == 0)
TRACE("BSoundPlayer::Stop: waiting for node stop failed\n"));
snooze(Latency() + 2000);
}
if (fNotifierFunc)
fNotifierFunc(fCookie, B_STOPPED, this);
}
bigtime_t
BSoundPlayer::Latency()
{
CALLED();
if ((fFlags & F_NODES_CONNECTED) == 0)
return 0;
BMediaRoster *roster = BMediaRoster::Roster();
if (!roster) {
TRACE("BSoundPlayer::Latency: Couldn't get BMediaRoster\n");
return 0;
}
bigtime_t latency;
status_t err = roster->GetLatencyFor(fMediaOutput.node, &latency);
if (err != B_OK) {
TRACE("BSoundPlayer::Latency: GetLatencyFor failed %" B_PRId32
" (%s)\n", err, strerror(err));
return 0;
}
TRACE("BSoundPlayer::Latency: latency is %" B_PRId64 "\n", latency);
return latency;
}
void
BSoundPlayer::SetHasData(bool hasData)
{
CALLED();
if (hasData)
atomic_or(&fFlags, F_HAS_DATA);
else
atomic_and(&fFlags, ~F_HAS_DATA);
}
bool
BSoundPlayer::HasData()
{
CALLED();
return (atomic_get(&fFlags) & F_HAS_DATA) != 0;
}
BSoundPlayer::BufferPlayerFunc
BSoundPlayer::BufferPlayer() const
{
CALLED();
return fPlayBufferFunc;
}
void
BSoundPlayer::SetBufferPlayer(BufferPlayerFunc playerFunction)
{
CALLED();
BAutolock _(fLocker);
fPlayBufferFunc = playerFunction;
}
BSoundPlayer::EventNotifierFunc
BSoundPlayer::EventNotifier() const
{
CALLED();
return fNotifierFunc;
}
void
BSoundPlayer::SetNotifier(EventNotifierFunc eventNotifierFunction)
{
CALLED();
BAutolock _(fLocker);
fNotifierFunc = eventNotifierFunction;
}
void*
BSoundPlayer::Cookie() const
{
CALLED();
return fCookie;
}
void
BSoundPlayer::SetCookie(void *cookie)
{
CALLED();
BAutolock _(fLocker);
fCookie = cookie;
}
void
BSoundPlayer::SetCallbacks(BufferPlayerFunc playerFunction,
EventNotifierFunc eventNotifierFunction, void* cookie)
{
CALLED();
BAutolock _(fLocker);
SetBufferPlayer(playerFunction);
SetNotifier(eventNotifierFunction);
SetCookie(cookie);
}
The probably best interpretation is to return the time that
has elapsed since playing was started, whichs seems to match
"CurrentTime() returns the current media time"
*/
bigtime_t
BSoundPlayer::CurrentTime()
{
if ((fFlags & F_NODES_CONNECTED) == 0)
return 0;
return fPlayerNode->CurrentTime();
}
being used by the BSoundPlayer. Will return B_ERROR if the
BSoundPlayer object hasn't been properly initialized.
*/
bigtime_t
BSoundPlayer::PerformanceTime()
{
if ((fFlags & F_NODES_CONNECTED) == 0)
return (bigtime_t) B_ERROR;
return fPlayerNode->TimeSource()->Now();
}
status_t
BSoundPlayer::Preroll()
{
CALLED();
if ((fFlags & F_NODES_CONNECTED) == 0)
return B_NO_INIT;
BMediaRoster* roster = BMediaRoster::Roster();
if (roster == NULL) {
TRACE("BSoundPlayer::Preroll: Couldn't get BMediaRoster\n");
return B_ERROR;
}
status_t err = roster->PrerollNode(fMediaOutput.node);
if (err != B_OK) {
TRACE("BSoundPlayer::Preroll: Error while PrerollNode: %"
B_PRId32 " (%s)\n", err, strerror(err));
return err;
}
return B_OK;
}
BSoundPlayer::play_id
BSoundPlayer::StartPlaying(BSound* sound, bigtime_t atTime)
{
return StartPlaying(sound, atTime, 1.0);
}
BSoundPlayer::play_id
BSoundPlayer::StartPlaying(BSound* sound, bigtime_t atTime, float withVolume)
{
CALLED();
playing_sound* item = (playing_sound*)malloc(sizeof(playing_sound));
if (item == NULL)
return B_NO_MEMORY;
item->current_offset = 0;
item->sound = sound;
item->id = atomic_add(&sCurrentPlayID, 1);
item->delta = 0;
item->rate = 0;
item->volume = withVolume;
if (!fLocker.Lock()) {
free(item);
return B_ERROR;
}
sound->AcquireRef();
item->next = fPlayingSounds;
fPlayingSounds = item;
fLocker.Unlock();
SetHasData(true);
return item->id;
}
status_t
BSoundPlayer::SetSoundVolume(play_id id, float newVolume)
{
CALLED();
if (!fLocker.Lock())
return B_ERROR;
playing_sound *item = fPlayingSounds;
while (item) {
if (item->id == id) {
item->volume = newVolume;
fLocker.Unlock();
return B_OK;
}
item = item->next;
}
fLocker.Unlock();
return B_ENTRY_NOT_FOUND;
}
bool
BSoundPlayer::IsPlaying(play_id id)
{
CALLED();
if (!fLocker.Lock())
return B_ERROR;
playing_sound *item = fPlayingSounds;
while (item) {
if (item->id == id) {
fLocker.Unlock();
return true;
}
item = item->next;
}
fLocker.Unlock();
return false;
}
status_t
BSoundPlayer::StopPlaying(play_id id)
{
CALLED();
if (!fLocker.Lock())
return B_ERROR;
playing_sound** link = &fPlayingSounds;
playing_sound* item = fPlayingSounds;
while (item != NULL) {
if (item->id == id) {
*link = item->next;
sem_id waitSem = item->wait_sem;
item->sound->ReleaseRef();
free(item);
fLocker.Unlock();
_NotifySoundDone(id, true);
if (waitSem >= 0)
release_sem(waitSem);
return B_OK;
}
link = &item->next;
item = item->next;
}
fLocker.Unlock();
return B_ENTRY_NOT_FOUND;
}
status_t
BSoundPlayer::WaitForSound(play_id id)
{
CALLED();
if (!fLocker.Lock())
return B_ERROR;
playing_sound* item = fPlayingSounds;
while (item != NULL) {
if (item->id == id) {
sem_id waitSem = item->wait_sem;
if (waitSem < 0)
waitSem = item->wait_sem = create_sem(0, "wait for sound");
fLocker.Unlock();
return acquire_sem(waitSem);
}
item = item->next;
}
fLocker.Unlock();
return B_ENTRY_NOT_FOUND;
}
float
BSoundPlayer::Volume()
{
CALLED();
return pow(10.0, VolumeDB(true) / 20.0);
}
void
BSoundPlayer::SetVolume(float newVolume)
{
CALLED();
SetVolumeDB(20.0 * log10(newVolume));
}
float
BSoundPlayer::VolumeDB(bool forcePoll)
{
CALLED();
if (!fVolumeSlider)
return -94.0f;
if (!forcePoll && system_time() - fLastVolumeUpdate < 500000)
return fVolumeDB;
int32 count = fVolumeSlider->CountChannels();
float values[count];
size_t size = count * sizeof(float);
fVolumeSlider->GetValue(&values, &size, NULL);
fLastVolumeUpdate = system_time();
fVolumeDB = values[0];
return values[0];
}
void
BSoundPlayer::SetVolumeDB(float volumeDB)
{
CALLED();
if (!fVolumeSlider)
return;
float minDB = fVolumeSlider->MinValue();
float maxDB = fVolumeSlider->MaxValue();
if (volumeDB < minDB)
volumeDB = minDB;
if (volumeDB > maxDB)
volumeDB = maxDB;
int count = fVolumeSlider->CountChannels();
float values[count];
for (int i = 0; i < count; i++)
values[i] = volumeDB;
fVolumeSlider->SetValue(values, sizeof(float) * count, 0);
fVolumeDB = volumeDB;
fLastVolumeUpdate = system_time();
}
status_t
BSoundPlayer::GetVolumeInfo(media_node* _node, int32* _parameterID,
float* _minDB, float* _maxDB)
{
CALLED();
if (fVolumeSlider == NULL)
return B_NO_INIT;
if (_node != NULL)
*_node = fMediaInput.node;
if (_parameterID != NULL)
*_parameterID = fVolumeSlider->ID();
if (_minDB != NULL)
*_minDB = fVolumeSlider->MinValue();
if (_maxDB != NULL)
*_maxDB = fVolumeSlider->MaxValue();
return B_OK;
}
void
BSoundPlayer::SetInitError(status_t error)
{
CALLED();
fInitStatus = error;
}
void
BSoundPlayer::_SoundPlayBufferFunc(void *cookie, void *buffer, size_t size,
const media_raw_audio_format &format)
{
BSoundPlayer *player = (BSoundPlayer *)cookie;
if (!player->fLocker.Lock()) {
memset(buffer, 0, size);
return;
}
playing_sound *sound = player->fPlayingSounds;
if (sound == NULL) {
player->SetHasData(false);
player->fLocker.Unlock();
memset(buffer, 0, size);
return;
}
size_t used = 0;
if (!sound->sound->GetDataAt(sound->current_offset, buffer, size, &used)) {
player->StopPlaying(sound->id);
player->fLocker.Unlock();
memset(buffer, 0, size);
return;
}
sound->current_offset += used;
player->fLocker.Unlock();
if (used < size)
memset((uint8 *)buffer + used, 0, size - used);
}
status_t BSoundPlayer::_Reserved_SoundPlayer_0(void*, ...) { return B_ERROR; }
status_t BSoundPlayer::_Reserved_SoundPlayer_1(void*, ...) { return B_ERROR; }
status_t BSoundPlayer::_Reserved_SoundPlayer_2(void*, ...) { return B_ERROR; }
status_t BSoundPlayer::_Reserved_SoundPlayer_3(void*, ...) { return B_ERROR; }
status_t BSoundPlayer::_Reserved_SoundPlayer_4(void*, ...) { return B_ERROR; }
status_t BSoundPlayer::_Reserved_SoundPlayer_5(void*, ...) { return B_ERROR; }
status_t BSoundPlayer::_Reserved_SoundPlayer_6(void*, ...) { return B_ERROR; }
status_t BSoundPlayer::_Reserved_SoundPlayer_7(void*, ...) { return B_ERROR; }
void
BSoundPlayer::_Init(const media_node* node,
const media_multi_audio_format* format, const char* name,
const media_input* input, BufferPlayerFunc playerFunction,
EventNotifierFunc eventNotifierFunction, void* cookie)
{
CALLED();
fPlayingSounds = NULL;
fWaitingSounds = NULL;
fPlayerNode = NULL;
if (playerFunction == NULL) {
fPlayBufferFunc = _SoundPlayBufferFunc;
fCookie = this;
} else {
fPlayBufferFunc = playerFunction;
fCookie = cookie;
}
fNotifierFunc = eventNotifierFunction;
fVolumeDB = 0.0f;
fFlags = 0;
fInitStatus = B_ERROR;
fParameterWeb = NULL;
fVolumeSlider = NULL;
fLastVolumeUpdate = 0;
BMediaRoster* roster = BMediaRoster::Roster();
if (roster == NULL) {
TRACE("BSoundPlayer::_Init: Couldn't get BMediaRoster\n");
return;
}
media_node inputNode;
if (node) {
inputNode = *node;
} else {
fInitStatus = roster->GetAudioMixer(&inputNode);
if (fInitStatus != B_OK) {
TRACE("BSoundPlayer::_Init: Couldn't GetAudioMixer\n");
return;
}
fFlags |= F_MUST_RELEASE_MIXER;
}
media_output _output;
media_input _input;
int32 inputCount;
int32 outputCount;
media_format tryFormat;
fPlayerNode = new BPrivate::SoundPlayNode(name, this);
fInitStatus = roster->RegisterNode(fPlayerNode);
if (fInitStatus != B_OK) {
TRACE("BSoundPlayer::_Init: Couldn't RegisterNode: %s\n",
strerror(fInitStatus));
return;
}
media_node timeSource;
fInitStatus = roster->GetTimeSource(&timeSource);
if (fInitStatus != B_OK) {
TRACE("BSoundPlayer::_Init: Couldn't GetTimeSource: %s\n",
strerror(fInitStatus));
return;
}
fInitStatus = roster->SetTimeSourceFor(fPlayerNode->Node().node,
timeSource.node);
if (fInitStatus != B_OK) {
TRACE("BSoundPlayer::_Init: Couldn't SetTimeSourceFor: %s\n",
strerror(fInitStatus));
return;
}
if (!input) {
fInitStatus = roster->GetFreeInputsFor(inputNode, &_input, 1,
&inputCount, B_MEDIA_RAW_AUDIO);
if (fInitStatus != B_OK) {
TRACE("BSoundPlayer::_Init: Couldn't GetFreeInputsFor: %s\n",
strerror(fInitStatus));
return;
}
if (inputCount < 1) {
TRACE("BSoundPlayer::_Init: Couldn't find a free input\n");
fInitStatus = B_ERROR;
return;
}
} else {
_input = *input;
}
fInitStatus = roster->GetFreeOutputsFor(fPlayerNode->Node(), &_output, 1,
&outputCount, B_MEDIA_RAW_AUDIO);
if (fInitStatus != B_OK) {
TRACE("BSoundPlayer::_Init: Couldn't GetFreeOutputsFor: %s\n",
strerror(fInitStatus));
return;
}
if (outputCount < 1) {
TRACE("BSoundPlayer::_Init: Couldn't find a free output\n");
fInitStatus = B_ERROR;
return;
}
fInitStatus = roster->SetRunModeNode(fPlayerNode->Node(),
BMediaNode::B_INCREASE_LATENCY);
if (fInitStatus != B_OK) {
TRACE("BSoundPlayer::_Init: Couldn't SetRunModeNode: %s\n",
strerror(fInitStatus));
return;
}
tryFormat.type = B_MEDIA_RAW_AUDIO;
tryFormat.u.raw_audio = *format;
#if DEBUG > 0
char buf[100];
string_for_format(tryFormat, buf, sizeof(buf));
TRACE("BSoundPlayer::_Init: trying to connect with format %s\n", buf);
#endif
fInitStatus = roster->Connect(_output.source, _input.destination,
&tryFormat, &fMediaOutput, &fMediaInput);
if (fInitStatus != B_OK) {
TRACE("BSoundPlayer::_Init: Couldn't Connect: %s\n",
strerror(fInitStatus));
return;
}
fFlags |= F_NODES_CONNECTED;
_GetVolumeSlider();
TRACE("BSoundPlayer node %" B_PRId32 " has timesource %" B_PRId32 "\n",
fPlayerNode->Node().node, fPlayerNode->TimeSource()->Node().node);
}
void
BSoundPlayer::_NotifySoundDone(play_id id, bool gotToPlay)
{
CALLED();
Notify(B_SOUND_DONE, id, gotToPlay);
}
void
BSoundPlayer::_GetVolumeSlider()
{
CALLED();
ASSERT(fVolumeSlider == NULL);
BMediaRoster *roster = BMediaRoster::CurrentRoster();
if (!roster) {
TRACE("BSoundPlayer::_GetVolumeSlider failed to get BMediaRoster");
return;
}
if (!fParameterWeb && roster->GetParameterWebFor(fMediaInput.node, &fParameterWeb) < B_OK) {
TRACE("BSoundPlayer::_GetVolumeSlider couldn't get parameter web");
return;
}
int count = fParameterWeb->CountParameters();
for (int i = 0; i < count; i++) {
BParameter *parameter = fParameterWeb->ParameterAt(i);
if (parameter->Type() != BParameter::B_CONTINUOUS_PARAMETER)
continue;
if ((parameter->ID() >> 16) != fMediaInput.destination.id)
continue;
if (strcmp(parameter->Kind(), B_GAIN) != 0)
continue;
fVolumeSlider = (BContinuousParameter *)parameter;
break;
}
#if DEBUG >0
if (!fVolumeSlider) {
TRACE("BSoundPlayer::_GetVolumeSlider couldn't find volume control");
}
#endif
}
void
BSoundPlayer::Notify(sound_player_notification what, ...)
{
CALLED();
if (fLocker.Lock()) {
if (fNotifierFunc)
(*fNotifierFunc)(fCookie, what);
fLocker.Unlock();
}
}
void
BSoundPlayer::PlayBuffer(void* buffer, size_t size,
const media_raw_audio_format& format)
{
if (fLocker.Lock()) {
if (fPlayBufferFunc)
(*fPlayBufferFunc)(fCookie, buffer, size, format);
fLocker.Unlock();
}
}
sound_error::sound_error(const char* string)
{
m_str_const = string;
}
const char*
sound_error::what() const throw()
{
return m_str_const;
}