2 * Copyright 2002-2009, Haiku.
3 * Distributed under the terms of the MIT License.
11 #include <SoundPlayer.h>
17 #include <MediaRoster.h>
18 #include <ParameterWeb.h>
20 #include <TimeSource.h>
22 #include "SoundPlayNode.h"
27 // Flags used internally in BSoundPlayer
29 F_NODES_CONNECTED
= (1 << 0),
30 F_HAS_DATA
= (1 << 1),
31 F_IS_STARTED
= (1 << 2),
32 F_MUST_RELEASE_MIXER
= (1 << 3),
36 static BSoundPlayer::play_id sCurrentPlayID
= 1;
39 BSoundPlayer::BSoundPlayer(const char* name
, BufferPlayerFunc playerFunction
,
40 EventNotifierFunc eventNotifierFunction
, void* cookie
)
44 TRACE("BSoundPlayer::BSoundPlayer: default constructor used\n");
46 media_multi_audio_format format
= media_multi_audio_format::wildcard
;
48 _Init(NULL
, &format
, name
, NULL
, playerFunction
, eventNotifierFunction
,
53 BSoundPlayer::BSoundPlayer(const media_raw_audio_format
* _format
,
54 const char* name
, BufferPlayerFunc playerFunction
,
55 EventNotifierFunc eventNotifierFunction
, void* cookie
)
59 TRACE("BSoundPlayer::BSoundPlayer: raw audio format constructor used\n");
61 media_multi_audio_format format
= media_multi_audio_format::wildcard
;
62 *(media_raw_audio_format
*)&format
= *_format
;
66 media_format tmp
; tmp
.type
= B_MEDIA_RAW_AUDIO
; tmp
.u
.raw_audio
= format
;
67 string_for_format(tmp
, buf
, sizeof(buf
));
68 TRACE("BSoundPlayer::BSoundPlayer: format %s\n", buf
);
71 _Init(NULL
, &format
, name
, NULL
, playerFunction
, eventNotifierFunction
,
76 BSoundPlayer::BSoundPlayer(const media_node
& toNode
,
77 const media_multi_audio_format
* format
, const char* name
,
78 const media_input
* input
, BufferPlayerFunc playerFunction
,
79 EventNotifierFunc eventNotifierFunction
, void* cookie
)
83 TRACE("BSoundPlayer::BSoundPlayer: multi audio format constructor used\n");
85 if ((toNode
.kind
& B_BUFFER_CONSUMER
) == 0)
86 debugger("BSoundPlayer: toNode must have B_BUFFER_CONSUMER kind!\n");
90 media_format tmp
; tmp
.type
= B_MEDIA_RAW_AUDIO
; tmp
.u
.raw_audio
= *format
;
91 string_for_format(tmp
, buf
, sizeof(buf
));
92 TRACE("BSoundPlayer::BSoundPlayer: format %s\n", buf
);
95 _Init(&toNode
, format
, name
, input
, playerFunction
, eventNotifierFunction
,
100 BSoundPlayer::~BSoundPlayer()
104 if ((fFlags
& F_IS_STARTED
) != 0) {
105 // block, but don't flush
110 BMediaRoster
* roster
= BMediaRoster::Roster();
111 if (roster
== NULL
) {
112 TRACE("BSoundPlayer::~BSoundPlayer: Couldn't get BMediaRoster\n");
116 if ((fFlags
& F_NODES_CONNECTED
) != 0) {
117 // Ordinarily we'd stop *all* of the nodes in the chain before
118 // disconnecting. However, our node is already stopped, and we can't
119 // stop the System Mixer.
120 // So, we just disconnect from it, and release our references to the
121 // nodes that we're using. We *are* supposed to do that even for global
122 // nodes like the Mixer.
123 err
= roster
->Disconnect(fMediaOutput
, fMediaInput
);
125 TRACE("BSoundPlayer::~BSoundPlayer: Error disconnecting nodes: "
126 "%" B_PRId32
" (%s)\n", err
, strerror(err
));
130 if ((fFlags
& F_MUST_RELEASE_MIXER
) != 0) {
131 // Release the mixer as it was acquired
132 // through BMediaRoster::GetAudioMixer()
133 err
= roster
->ReleaseNode(fMediaInput
.node
);
135 TRACE("BSoundPlayer::~BSoundPlayer: Error releasing input node: "
136 "%" B_PRId32
" (%s)\n", err
, strerror(err
));
141 // Dispose of the player node
143 // We do not call BMediaRoster::ReleaseNode(), since
144 // the player was created by using "new". We could
145 // call BMediaRoster::UnregisterNode(), but this is
146 // supposed to be done by BMediaNode destructor automatically.
148 // The node is deleted by the Release() when ref count reach 0.
149 // Since we are the sole owners, and no one acquired it
150 // this should be the case. The Quit() synchronization
151 // is handled by the DeleteHook inheritance.
152 // NOTE: this might be crucial when using a BMediaEventLooper.
153 if (fPlayerNode
->Release() != NULL
) {
154 TRACE("BSoundPlayer::~BSoundPlayer: Error the producer node "
155 "appears to be acquired by someone else than us!");
158 // do not delete fVolumeSlider, it belongs to the parameter web
159 delete fParameterWeb
;
164 BSoundPlayer::InitCheck()
171 media_raw_audio_format
172 BSoundPlayer::Format() const
176 if ((fFlags
& F_NODES_CONNECTED
) == 0)
177 return media_raw_audio_format::wildcard
;
179 return fPlayerNode
->Format();
184 BSoundPlayer::Start()
188 if ((fFlags
& F_NODES_CONNECTED
) == 0)
191 if ((fFlags
& F_IS_STARTED
) != 0)
194 BMediaRoster
* roster
= BMediaRoster::Roster();
196 TRACE("BSoundPlayer::Start: Couldn't get BMediaRoster\n");
200 if (!fPlayerNode
->TimeSource()->IsRunning()) {
201 roster
->StartTimeSource(fPlayerNode
->TimeSource()->Node(),
202 fPlayerNode
->TimeSource()->RealTime());
205 // Add latency and a few ms to the nodes current time to
206 // make sure that we give the producer enough time to run
207 // buffers through the node chain, otherwise it'll start
210 status_t err
= roster
->StartNode(fPlayerNode
->Node(),
211 fPlayerNode
->TimeSource()->Now() + Latency() + 5000);
213 TRACE("BSoundPlayer::Start: StartNode failed, %" B_PRId32
, err
);
217 if (fNotifierFunc
!= NULL
)
218 fNotifierFunc(fCookie
, B_STARTED
, this);
221 atomic_or(&fFlags
, F_IS_STARTED
);
228 BSoundPlayer::Stop(bool block
, bool flush
)
232 TRACE("BSoundPlayer::Stop: block %d, flush %d\n", (int)block
, (int)flush
);
234 if ((fFlags
& F_NODES_CONNECTED
) == 0)
237 // TODO: flush is ignored
239 if ((fFlags
& F_IS_STARTED
) != 0) {
240 BMediaRoster
* roster
= BMediaRoster::Roster();
241 if (roster
== NULL
) {
242 TRACE("BSoundPlayer::Stop: Couldn't get BMediaRoster\n");
246 roster
->StopNode(fPlayerNode
->Node(), 0, true);
248 atomic_and(&fFlags
, ~F_IS_STARTED
);
252 // wait until the node is stopped
254 for (tries
= 250; fPlayerNode
->IsPlaying() && tries
!= 0; tries
--)
257 DEBUG_ONLY(if (tries
== 0)
258 TRACE("BSoundPlayer::Stop: waiting for node stop failed\n"));
260 // Wait until all buffers on the way to the physical output have been
262 snooze(Latency() + 2000);
266 fNotifierFunc(fCookie
, B_STOPPED
, this);
272 BSoundPlayer::Latency()
276 if ((fFlags
& F_NODES_CONNECTED
) == 0)
279 BMediaRoster
*roster
= BMediaRoster::Roster();
281 TRACE("BSoundPlayer::Latency: Couldn't get BMediaRoster\n");
286 status_t err
= roster
->GetLatencyFor(fMediaOutput
.node
, &latency
);
288 TRACE("BSoundPlayer::Latency: GetLatencyFor failed %" B_PRId32
289 " (%s)\n", err
, strerror(err
));
293 TRACE("BSoundPlayer::Latency: latency is %" B_PRId64
"\n", latency
);
300 BSoundPlayer::SetHasData(bool hasData
)
304 atomic_or(&fFlags
, F_HAS_DATA
);
306 atomic_and(&fFlags
, ~F_HAS_DATA
);
311 BSoundPlayer::HasData()
314 return (atomic_get(&fFlags
) & F_HAS_DATA
) != 0;
318 BSoundPlayer::BufferPlayerFunc
319 BSoundPlayer::BufferPlayer() const
322 return fPlayBufferFunc
;
327 BSoundPlayer::SetBufferPlayer(BufferPlayerFunc playerFunction
)
330 BAutolock
_(fLocker
);
332 fPlayBufferFunc
= playerFunction
;
336 BSoundPlayer::EventNotifierFunc
337 BSoundPlayer::EventNotifier() const
340 return fNotifierFunc
;
345 BSoundPlayer::SetNotifier(EventNotifierFunc eventNotifierFunction
)
348 BAutolock
_(fLocker
);
350 fNotifierFunc
= eventNotifierFunction
;
355 BSoundPlayer::Cookie() const
363 BSoundPlayer::SetCookie(void *cookie
)
366 BAutolock
_(fLocker
);
373 BSoundPlayer::SetCallbacks(BufferPlayerFunc playerFunction
,
374 EventNotifierFunc eventNotifierFunction
, void* cookie
)
377 BAutolock
_(fLocker
);
379 SetBufferPlayer(playerFunction
);
380 SetNotifier(eventNotifierFunction
);
385 /*! The BeBook is inaccurate about the meaning of this function.
386 The probably best interpretation is to return the time that
387 has elapsed since playing was started, whichs seems to match
388 "CurrentTime() returns the current media time"
391 BSoundPlayer::CurrentTime()
393 if ((fFlags
& F_NODES_CONNECTED
) == 0)
396 return fPlayerNode
->CurrentTime();
400 /*! Returns the current performance time of the sound player node
401 being used by the BSoundPlayer. Will return B_ERROR if the
402 BSoundPlayer object hasn't been properly initialized.
405 BSoundPlayer::PerformanceTime()
407 if ((fFlags
& F_NODES_CONNECTED
) == 0)
408 return (bigtime_t
) B_ERROR
;
410 return fPlayerNode
->TimeSource()->Now();
415 BSoundPlayer::Preroll()
419 if ((fFlags
& F_NODES_CONNECTED
) == 0)
422 BMediaRoster
* roster
= BMediaRoster::Roster();
423 if (roster
== NULL
) {
424 TRACE("BSoundPlayer::Preroll: Couldn't get BMediaRoster\n");
428 status_t err
= roster
->PrerollNode(fMediaOutput
.node
);
430 TRACE("BSoundPlayer::Preroll: Error while PrerollNode: %"
431 B_PRId32
" (%s)\n", err
, strerror(err
));
439 BSoundPlayer::play_id
440 BSoundPlayer::StartPlaying(BSound
* sound
, bigtime_t atTime
)
442 return StartPlaying(sound
, atTime
, 1.0);
446 BSoundPlayer::play_id
447 BSoundPlayer::StartPlaying(BSound
* sound
, bigtime_t atTime
, float withVolume
)
451 // TODO: support the at_time and with_volume parameters
452 playing_sound
* item
= (playing_sound
*)malloc(sizeof(playing_sound
));
456 item
->current_offset
= 0;
458 item
->id
= atomic_add(&sCurrentPlayID
, 1);
461 item
->volume
= withVolume
;
463 if (!fLocker
.Lock()) {
469 item
->next
= fPlayingSounds
;
470 fPlayingSounds
= item
;
479 BSoundPlayer::SetSoundVolume(play_id id
, float newVolume
)
485 playing_sound
*item
= fPlayingSounds
;
487 if (item
->id
== id
) {
488 item
->volume
= newVolume
;
497 return B_ENTRY_NOT_FOUND
;
502 BSoundPlayer::IsPlaying(play_id id
)
508 playing_sound
*item
= fPlayingSounds
;
510 if (item
->id
== id
) {
524 BSoundPlayer::StopPlaying(play_id id
)
530 playing_sound
** link
= &fPlayingSounds
;
531 playing_sound
* item
= fPlayingSounds
;
533 while (item
!= NULL
) {
534 if (item
->id
== id
) {
536 sem_id waitSem
= item
->wait_sem
;
537 item
->sound
->ReleaseRef();
541 _NotifySoundDone(id
, true);
543 release_sem(waitSem
);
553 return B_ENTRY_NOT_FOUND
;
558 BSoundPlayer::WaitForSound(play_id id
)
564 playing_sound
* item
= fPlayingSounds
;
565 while (item
!= NULL
) {
566 if (item
->id
== id
) {
567 sem_id waitSem
= item
->wait_sem
;
569 waitSem
= item
->wait_sem
= create_sem(0, "wait for sound");
572 return acquire_sem(waitSem
);
579 return B_ENTRY_NOT_FOUND
;
584 BSoundPlayer::Volume()
587 return pow(10.0, VolumeDB(true) / 20.0);
592 BSoundPlayer::SetVolume(float newVolume
)
595 SetVolumeDB(20.0 * log10(newVolume
));
600 BSoundPlayer::VolumeDB(bool forcePoll
)
604 return -94.0f
; // silence
606 if (!forcePoll
&& system_time() - fLastVolumeUpdate
< 500000)
609 int32 count
= fVolumeSlider
->CountChannels();
611 size_t size
= count
* sizeof(float);
612 fVolumeSlider
->GetValue(&values
, &size
, NULL
);
613 fLastVolumeUpdate
= system_time();
614 fVolumeDB
= values
[0];
621 BSoundPlayer::SetVolumeDB(float volumeDB
)
627 float minDB
= fVolumeSlider
->MinValue();
628 float maxDB
= fVolumeSlider
->MaxValue();
629 if (volumeDB
< minDB
)
631 if (volumeDB
> maxDB
)
634 int count
= fVolumeSlider
->CountChannels();
636 for (int i
= 0; i
< count
; i
++)
637 values
[i
] = volumeDB
;
638 fVolumeSlider
->SetValue(values
, sizeof(float) * count
, 0);
640 fVolumeDB
= volumeDB
;
641 fLastVolumeUpdate
= system_time();
646 BSoundPlayer::GetVolumeInfo(media_node
* _node
, int32
* _parameterID
,
647 float* _minDB
, float* _maxDB
)
650 if (fVolumeSlider
== NULL
)
654 *_node
= fMediaInput
.node
;
655 if (_parameterID
!= NULL
)
656 *_parameterID
= fVolumeSlider
->ID();
658 *_minDB
= fVolumeSlider
->MinValue();
660 *_maxDB
= fVolumeSlider
->MaxValue();
666 // #pragma mark - protected BSoundPlayer
670 BSoundPlayer::SetInitError(status_t error
)
677 // #pragma mark - private BSoundPlayer
681 BSoundPlayer::_SoundPlayBufferFunc(void *cookie
, void *buffer
, size_t size
,
682 const media_raw_audio_format
&format
)
684 // TODO: support more than one sound and make use of the format parameter
685 BSoundPlayer
*player
= (BSoundPlayer
*)cookie
;
686 if (!player
->fLocker
.Lock()) {
687 memset(buffer
, 0, size
);
691 playing_sound
*sound
= player
->fPlayingSounds
;
693 player
->SetHasData(false);
694 player
->fLocker
.Unlock();
695 memset(buffer
, 0, size
);
700 if (!sound
->sound
->GetDataAt(sound
->current_offset
, buffer
, size
, &used
)) {
701 // will take care of removing the item and notifying others
702 player
->StopPlaying(sound
->id
);
703 player
->fLocker
.Unlock();
704 memset(buffer
, 0, size
);
708 sound
->current_offset
+= used
;
709 player
->fLocker
.Unlock();
712 memset((uint8
*)buffer
+ used
, 0, size
- used
);
716 status_t
BSoundPlayer::_Reserved_SoundPlayer_0(void*, ...) { return B_ERROR
; }
717 status_t
BSoundPlayer::_Reserved_SoundPlayer_1(void*, ...) { return B_ERROR
; }
718 status_t
BSoundPlayer::_Reserved_SoundPlayer_2(void*, ...) { return B_ERROR
; }
719 status_t
BSoundPlayer::_Reserved_SoundPlayer_3(void*, ...) { return B_ERROR
; }
720 status_t
BSoundPlayer::_Reserved_SoundPlayer_4(void*, ...) { return B_ERROR
; }
721 status_t
BSoundPlayer::_Reserved_SoundPlayer_5(void*, ...) { return B_ERROR
; }
722 status_t
BSoundPlayer::_Reserved_SoundPlayer_6(void*, ...) { return B_ERROR
; }
723 status_t
BSoundPlayer::_Reserved_SoundPlayer_7(void*, ...) { return B_ERROR
; }
727 BSoundPlayer::_Init(const media_node
* node
,
728 const media_multi_audio_format
* format
, const char* name
,
729 const media_input
* input
, BufferPlayerFunc playerFunction
,
730 EventNotifierFunc eventNotifierFunction
, void* cookie
)
733 fPlayingSounds
= NULL
;
734 fWaitingSounds
= NULL
;
737 if (playerFunction
== NULL
) {
738 fPlayBufferFunc
= _SoundPlayBufferFunc
;
741 fPlayBufferFunc
= playerFunction
;
745 fNotifierFunc
= eventNotifierFunction
;
748 fInitStatus
= B_ERROR
;
749 fParameterWeb
= NULL
;
750 fVolumeSlider
= NULL
;
751 fLastVolumeUpdate
= 0;
753 BMediaRoster
* roster
= BMediaRoster::Roster();
754 if (roster
== NULL
) {
755 TRACE("BSoundPlayer::_Init: Couldn't get BMediaRoster\n");
759 // The inputNode that our player node will be
760 // connected with is either supplied by the user
761 // or the system audio mixer
762 media_node inputNode
;
766 fInitStatus
= roster
->GetAudioMixer(&inputNode
);
767 if (fInitStatus
!= B_OK
) {
768 TRACE("BSoundPlayer::_Init: Couldn't GetAudioMixer\n");
771 fFlags
|= F_MUST_RELEASE_MIXER
;
774 media_output _output
;
778 media_format tryFormat
;
780 // Create the player node and register it
781 fPlayerNode
= new BPrivate::SoundPlayNode(name
, this);
782 fInitStatus
= roster
->RegisterNode(fPlayerNode
);
783 if (fInitStatus
!= B_OK
) {
784 TRACE("BSoundPlayer::_Init: Couldn't RegisterNode: %s\n",
785 strerror(fInitStatus
));
789 // set the producer's time source to be the "default" time source,
790 // which the system audio mixer uses too.
791 media_node timeSource
;
792 fInitStatus
= roster
->GetTimeSource(&timeSource
);
793 if (fInitStatus
!= B_OK
) {
794 TRACE("BSoundPlayer::_Init: Couldn't GetTimeSource: %s\n",
795 strerror(fInitStatus
));
798 fInitStatus
= roster
->SetTimeSourceFor(fPlayerNode
->Node().node
,
800 if (fInitStatus
!= B_OK
) {
801 TRACE("BSoundPlayer::_Init: Couldn't SetTimeSourceFor: %s\n",
802 strerror(fInitStatus
));
806 // find a free media_input
808 fInitStatus
= roster
->GetFreeInputsFor(inputNode
, &_input
, 1,
809 &inputCount
, B_MEDIA_RAW_AUDIO
);
810 if (fInitStatus
!= B_OK
) {
811 TRACE("BSoundPlayer::_Init: Couldn't GetFreeInputsFor: %s\n",
812 strerror(fInitStatus
));
815 if (inputCount
< 1) {
816 TRACE("BSoundPlayer::_Init: Couldn't find a free input\n");
817 fInitStatus
= B_ERROR
;
824 // find a free media_output
825 fInitStatus
= roster
->GetFreeOutputsFor(fPlayerNode
->Node(), &_output
, 1,
826 &outputCount
, B_MEDIA_RAW_AUDIO
);
827 if (fInitStatus
!= B_OK
) {
828 TRACE("BSoundPlayer::_Init: Couldn't GetFreeOutputsFor: %s\n",
829 strerror(fInitStatus
));
832 if (outputCount
< 1) {
833 TRACE("BSoundPlayer::_Init: Couldn't find a free output\n");
834 fInitStatus
= B_ERROR
;
838 // Set an appropriate run mode for the producer
839 fInitStatus
= roster
->SetRunModeNode(fPlayerNode
->Node(),
840 BMediaNode::B_INCREASE_LATENCY
);
841 if (fInitStatus
!= B_OK
) {
842 TRACE("BSoundPlayer::_Init: Couldn't SetRunModeNode: %s\n",
843 strerror(fInitStatus
));
847 // setup our requested format (can still have many wildcards)
848 tryFormat
.type
= B_MEDIA_RAW_AUDIO
;
849 tryFormat
.u
.raw_audio
= *format
;
853 string_for_format(tryFormat
, buf
, sizeof(buf
));
854 TRACE("BSoundPlayer::_Init: trying to connect with format %s\n", buf
);
857 // and connect the nodes
858 fInitStatus
= roster
->Connect(_output
.source
, _input
.destination
,
859 &tryFormat
, &fMediaOutput
, &fMediaInput
);
860 if (fInitStatus
!= B_OK
) {
861 TRACE("BSoundPlayer::_Init: Couldn't Connect: %s\n",
862 strerror(fInitStatus
));
866 fFlags
|= F_NODES_CONNECTED
;
870 TRACE("BSoundPlayer node %" B_PRId32
" has timesource %" B_PRId32
"\n",
871 fPlayerNode
->Node().node
, fPlayerNode
->TimeSource()->Node().node
);
876 BSoundPlayer::_NotifySoundDone(play_id id
, bool gotToPlay
)
879 Notify(B_SOUND_DONE
, id
, gotToPlay
);
884 BSoundPlayer::_GetVolumeSlider()
888 ASSERT(fVolumeSlider
== NULL
);
890 BMediaRoster
*roster
= BMediaRoster::CurrentRoster();
892 TRACE("BSoundPlayer::_GetVolumeSlider failed to get BMediaRoster");
896 if (!fParameterWeb
&& roster
->GetParameterWebFor(fMediaInput
.node
, &fParameterWeb
) < B_OK
) {
897 TRACE("BSoundPlayer::_GetVolumeSlider couldn't get parameter web");
901 int count
= fParameterWeb
->CountParameters();
902 for (int i
= 0; i
< count
; i
++) {
903 BParameter
*parameter
= fParameterWeb
->ParameterAt(i
);
904 if (parameter
->Type() != BParameter::B_CONTINUOUS_PARAMETER
)
906 if ((parameter
->ID() >> 16) != fMediaInput
.destination
.id
)
908 if (strcmp(parameter
->Kind(), B_GAIN
) != 0)
910 fVolumeSlider
= (BContinuousParameter
*)parameter
;
915 if (!fVolumeSlider
) {
916 TRACE("BSoundPlayer::_GetVolumeSlider couldn't find volume control");
923 BSoundPlayer::Notify(sound_player_notification what
, ...)
926 if (fLocker
.Lock()) {
928 (*fNotifierFunc
)(fCookie
, what
);
935 BSoundPlayer::PlayBuffer(void* buffer
, size_t size
,
936 const media_raw_audio_format
& format
)
938 if (fLocker
.Lock()) {
940 (*fPlayBufferFunc
)(fCookie
, buffer
, size
, format
);
946 // #pragma mark - public sound_error
949 sound_error::sound_error(const char* string
)
951 m_str_const
= string
;
956 sound_error::what() const throw()