/* * Copyright 2002-2009, Haiku. * Distributed under the terms of the MIT License. * * Authors: * Marcus Overhagen * Jérôme Duval */ #include #include #include #include #include #include #include #include #include "SoundPlayNode.h" #include "MediaDebug.h" // Flags used internally in BSoundPlayer 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) { // block, but don't flush 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) { // Ordinarily we'd stop *all* of the nodes in the chain before // disconnecting. However, our node is already stopped, and we can't // stop the System Mixer. // So, we just disconnect from it, and release our references to the // nodes that we're using. We *are* supposed to do that even for global // nodes like the Mixer. 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) { // Release the mixer as it was acquired // through BMediaRoster::GetAudioMixer() err = roster->ReleaseNode(fMediaInput.node); if (err != B_OK) { TRACE("BSoundPlayer::~BSoundPlayer: Error releasing input node: " "%" B_PRId32 " (%s)\n", err, strerror(err)); } } cleanup: // Dispose of the player node // We do not call BMediaRoster::ReleaseNode(), since // the player was created by using "new". We could // call BMediaRoster::UnregisterNode(), but this is // supposed to be done by BMediaNode destructor automatically. // The node is deleted by the Release() when ref count reach 0. // Since we are the sole owners, and no one acquired it // this should be the case. The Quit() synchronization // is handled by the DeleteHook inheritance. // NOTE: this might be crucial when using a BMediaEventLooper. if (fPlayerNode != NULL && fPlayerNode->Release() != NULL) { TRACE("BSoundPlayer::~BSoundPlayer: Error the producer node " "appears to be acquired by someone else than us!"); } // do not delete fVolumeSlider, it belongs to the parameter web 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()); } // Add latency and a few ms to the nodes current time to // make sure that we give the producer enough time to run // buffers through the node chain, otherwise it'll start // up already late 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; // TODO: flush is ignored 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) { // wait until the node is stopped 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")); // Wait until all buffers on the way to the physical output have been // played 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 BeBook is inaccurate about the meaning of this function. 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(); } /*! Returns the current performance time of the sound player node 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(); // TODO: support the at_time and with_volume parameters 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; // silence 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; } // #pragma mark - protected BSoundPlayer void BSoundPlayer::SetInitError(status_t error) { CALLED(); fInitStatus = error; } // #pragma mark - private BSoundPlayer void BSoundPlayer::_SoundPlayBufferFunc(void *cookie, void *buffer, size_t size, const media_raw_audio_format &format) { // TODO: support more than one sound and make use of the format parameter 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)) { // will take care of removing the item and notifying others 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; } // The inputNode that our player node will be // connected with is either supplied by the user // or the system audio mixer 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; // Create the player node and register it 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; } // set the producer's time source to be the "default" time source, // which the system audio mixer uses too. 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; } // find a free media_input 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; } // find a free media_output 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; } // Set an appropriate run mode for the producer fInitStatus = roster->SetRunModeNode(fPlayerNode->Node(), BMediaNode::B_INCREASE_LATENCY); if (fInitStatus != B_OK) { TRACE("BSoundPlayer::_Init: Couldn't SetRunModeNode: %s\n", strerror(fInitStatus)); return; } // setup our requested format (can still have many wildcards) 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 // and connect the nodes 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(); } } // #pragma mark - public sound_error sound_error::sound_error(const char* string) { m_str_const = string; } const char* sound_error::what() const throw() { return m_str_const; }