//------------------------------------------------------------------------------ // Copyright (c) 2001-2002, OpenBeOS // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. // // File Name: BGameSoundDevice.cpp // Author: Christopher ML Zumwalt May (zummy@users.sf.net) // Description: Manages the game producer. The class may change with out // notice and was only inteneded for use by the GameKit at // this time. Use at your own risk. //------------------------------------------------------------------------------ #include #include #include #include #include #include #include #include #include #include #include "GSUtility.h" #include "GameSoundDevice.h" #include "GameSoundBuffer.h" #include "GameProducer.h" // BGameSoundDevice definitions ------------------------------------ const int32 kInitSoundCount = 32; const int32 kGrowth = 16; static int32 sDeviceCount = 0; static BGameSoundDevice* sDevice = NULL; static BLocker sDeviceRefCountLock = BLocker("GameSound device lock"); BGameSoundDevice* GetDefaultDevice() { BAutolock _(sDeviceRefCountLock); if (!sDevice) sDevice = new BGameSoundDevice(); sDeviceCount++; return sDevice; } void ReleaseDevice() { BAutolock _(sDeviceRefCountLock); sDeviceCount--; if (sDeviceCount <= 0) { delete sDevice; sDevice = NULL; } } // BGameSoundDevice ------------------------------------------------------- BGameSoundDevice::BGameSoundDevice() : fIsConnected(false), fSoundCount(kInitSoundCount) { fConnection = new Connection; memset(&fFormat, 0, sizeof(gs_audio_format)); fInitError = Connect(); fSounds = new GameSoundBuffer*[kInitSoundCount]; for (int32 i = 0; i < kInitSoundCount; i++) fSounds[i] = NULL; } BGameSoundDevice::~BGameSoundDevice() { BMediaRoster* roster = BMediaRoster::Roster(); // We need to stop all the sounds before we stop the mixer for (int32 i = 0; i < fSoundCount; i++) { if (fSounds[i]) fSounds[i]->StopPlaying(); delete fSounds[i]; } if (fIsConnected) { // stop the nodes if they are running roster->StopNode(fConnection->producer, 0, true); // synchronous stop // Ordinarily we'd stop *all* of the nodes in the chain at this point. // However, one of the nodes is the System Mixer, and stopping the // Mixer is a Bad Idea (tm). 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. roster->Disconnect(fConnection->producer.node, fConnection->source, fConnection->consumer.node, fConnection->destination); roster->ReleaseNode(fConnection->producer); roster->ReleaseNode(fConnection->consumer); } delete[] fSounds; delete fConnection; } status_t BGameSoundDevice::InitCheck() const { return fInitError; } const gs_audio_format& BGameSoundDevice::Format() const { return fFormat; } const gs_audio_format& BGameSoundDevice::Format(gs_id sound) const { return fSounds[sound - 1]->Format(); } void BGameSoundDevice::SetInitError(status_t error) { fInitError = error; } status_t BGameSoundDevice::CreateBuffer(gs_id* sound, const gs_audio_format* format, const void* data, int64 frames) { if (frames <= 0 || !sound) return B_BAD_VALUE; status_t err = B_MEDIA_TOO_MANY_BUFFERS; int32 position = AllocateSound(); if (position >= 0) { fSounds[position] = new SimpleSoundBuffer(format, data, frames); err = fSounds[position]->Connect(&fConnection->producer); } if (err == B_OK) *sound = gs_id(position + 1); return err; } status_t BGameSoundDevice::CreateBuffer(gs_id* sound, const void* object, const gs_audio_format* format) { if (!object || !sound) return B_BAD_VALUE; status_t err = B_MEDIA_TOO_MANY_BUFFERS; int32 position = AllocateSound(); if (position >= 0) { fSounds[position] = new StreamingSoundBuffer(format, object); err = fSounds[position]->Connect(&fConnection->producer); } if (err == B_OK) *sound = gs_id(position+1); return err; } void BGameSoundDevice::ReleaseBuffer(gs_id sound) { if (sound <= 0) return; if (fSounds[sound - 1]) { // We must stop playback befor destroying the sound or else // we may recieve fatel errors. fSounds[sound - 1]->StopPlaying(); delete fSounds[sound - 1]; fSounds[sound - 1] = NULL; } } status_t BGameSoundDevice::Buffer(gs_id sound, gs_audio_format* format, void*& data) { if (!format || sound <= 0) return B_BAD_VALUE; memcpy(format, &fSounds[sound-1]->Format(), sizeof(gs_audio_format)); if (fSounds[sound-1]->Data()) { data = malloc(format->buffer_size); memcpy(data, fSounds[sound-1]->Data(), format->buffer_size); } else data = NULL; return B_OK; } status_t BGameSoundDevice::StartPlaying(gs_id sound) { if (sound <= 0) return B_BAD_VALUE; if (!fSounds[sound - 1]->IsPlaying()) { // tell the producer to start playing the sound return fSounds[sound - 1]->StartPlaying(); } fSounds[sound - 1]->Reset(); return EALREADY; } status_t BGameSoundDevice::StopPlaying(gs_id sound) { if (sound <= 0) return B_BAD_VALUE; if (fSounds[sound - 1]->IsPlaying()) { // Tell the producer to stop play this sound fSounds[sound - 1]->Reset(); return fSounds[sound - 1]->StopPlaying(); } return EALREADY; } bool BGameSoundDevice::IsPlaying(gs_id sound) { if (sound <= 0) return false; return fSounds[sound - 1]->IsPlaying(); } status_t BGameSoundDevice::GetAttributes(gs_id sound, gs_attribute* attributes, size_t attributeCount) { if (!fSounds[sound - 1]) return B_ERROR; return fSounds[sound - 1]->GetAttributes(attributes, attributeCount); } status_t BGameSoundDevice::SetAttributes(gs_id sound, gs_attribute* attributes, size_t attributeCount) { if (!fSounds[sound - 1]) return B_ERROR; return fSounds[sound - 1]->SetAttributes(attributes, attributeCount); } status_t BGameSoundDevice::Connect() { BMediaRoster* roster = BMediaRoster::Roster(); // create your own audio mixer // TODO: Don't do this!!! See bug #575 // from #575. // marcusoverhagen : tries to instantiate a new audio mixer, which is bad. // It should use the system mixer. dormant_node_info mixer_dormant_info; int32 mixer_count = 1; // for now, we only care about the first we find. status_t err = roster->GetDormantNodes(&mixer_dormant_info, &mixer_count, 0, 0, 0, B_SYSTEM_MIXER, 0); if (err != B_OK) return err; //fMixer = new media_node; err = roster->InstantiateDormantNode(mixer_dormant_info, &fConnection->producer); if (err != B_OK) return err; // retieve the system's audio mixer err = roster->GetAudioMixer(&fConnection->consumer); if (err != B_OK) return err; int32 count = 1; media_input mixerInput; err = roster->GetFreeInputsFor(fConnection->consumer, &mixerInput, 1, &count); if (err != B_OK) return err; count = 1; media_output mixerOutput; err = roster->GetFreeOutputsFor(fConnection->producer, &mixerOutput, 1, &count); if (err != B_OK) return err; media_format format(mixerOutput.format); err = roster->Connect(mixerOutput.source, mixerInput.destination, &format, &mixerOutput, &mixerInput); if (err != B_OK) return err; // set the producer's time source to be the "default" time source, which // the Mixer uses too. roster->GetTimeSource(&fConnection->timeSource); roster->SetTimeSourceFor(fConnection->producer.node, fConnection->timeSource.node); // Start our mixer's time source if need be. Chances are, it won't need to // be, but if we forget to do this, our mixer might not do anything at all. BTimeSource* mixerTimeSource = roster->MakeTimeSourceFor( fConnection->producer); if (!mixerTimeSource) return B_ERROR; if (!mixerTimeSource->IsRunning()) { status_t err = roster->StartNode(mixerTimeSource->Node(), BTimeSource::RealTime()); if (err != B_OK) { mixerTimeSource->Release(); return err; } } // Start up our mixer bigtime_t tpNow = mixerTimeSource->Now(); err = roster->StartNode(fConnection->producer, tpNow + 10000); mixerTimeSource->Release(); if (err != B_OK) return err; // the inputs and outputs might have been reassigned during the // nodes' negotiation of the Connect(). That's why we wait until // after Connect() finishes to save their contents. fConnection->format = format; fConnection->source = mixerOutput.source; fConnection->destination = mixerInput.destination; // Set an appropriate run mode for the producer roster->SetRunModeNode(fConnection->producer, BMediaNode::B_INCREASE_LATENCY); media_to_gs_format(&fFormat, &format.u.raw_audio); fIsConnected = true; return B_OK; } int32 BGameSoundDevice::AllocateSound() { for (int32 i = 0; i < fSoundCount; i++) if (!fSounds[i]) return i; // we need to allocate new space for the sound GameSoundBuffer ** sounds = new GameSoundBuffer*[fSoundCount + kGrowth]; for (int32 i = 0; i < fSoundCount; i++) sounds[i] = fSounds[i]; for (int32 i = fSoundCount; i < fSoundCount + kGrowth; i++) sounds[i] = NULL; // replace the old list delete [] fSounds; fSounds = sounds; fSoundCount += kGrowth; return fSoundCount - kGrowth; }