1 //------------------------------------------------------------------------------ 2 // Copyright (c) 2001-2002, OpenBeOS 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a 5 // copy of this software and associated documentation files (the "Software"), 6 // to deal in the Software without restriction, including without limitation 7 // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 // and/or sell copies of the Software, and to permit persons to whom the 9 // Software is furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 // DEALINGS IN THE SOFTWARE. 21 // 22 // File Name: BGameSoundDevice.cpp 23 // Author: Christopher ML Zumwalt May (zummy@users.sf.net) 24 // Description: Manages the game producer. The class may change with out 25 // notice and was only inteneded for use by the GameKit at 26 // this time. Use at your own risk. 27 //------------------------------------------------------------------------------ 28 29 #include <stdlib.h> 30 #include <stdio.h> 31 #include <string.h> 32 33 #include <Autolock.h> 34 #include <List.h> 35 #include <Locker.h> 36 #include <MediaRoster.h> 37 #include <MediaAddOn.h> 38 #include <TimeSource.h> 39 #include <MediaTheme.h> 40 41 #include "GSUtility.h" 42 #include "GameSoundDevice.h" 43 #include "GameSoundBuffer.h" 44 #include "GameProducer.h" 45 46 // BGameSoundDevice definitions ------------------------------------ 47 const int32 kInitSoundCount = 32; 48 const int32 kGrowth = 16; 49 50 static int32 sDeviceCount = 0; 51 static BGameSoundDevice* sDevice = NULL; 52 static BLocker sDeviceRefCountLock = BLocker("GameSound device lock"); 53 54 55 BGameSoundDevice * 56 GetDefaultDevice() 57 { 58 BAutolock _(sDeviceRefCountLock); 59 60 if (!sDevice) 61 sDevice = new BGameSoundDevice(); 62 63 sDeviceCount++; 64 return sDevice; 65 } 66 67 68 void 69 ReleaseDevice() 70 { 71 BAutolock _(sDeviceRefCountLock); 72 73 sDeviceCount--; 74 75 if (sDeviceCount <= 0) { 76 delete sDevice; 77 sDevice = NULL; 78 } 79 } 80 81 82 // BGameSoundDevice ------------------------------------------------------- 83 BGameSoundDevice::BGameSoundDevice() 84 : fIsConnected(false), 85 fSoundCount(kInitSoundCount) 86 { 87 fConnection = new Connection; 88 memset(&fFormat, 0, sizeof(gs_audio_format)); 89 90 fInitError = Connect(); 91 92 fSounds = new GameSoundBuffer*[kInitSoundCount]; 93 for (int32 i = 0; i < kInitSoundCount; i++) 94 fSounds[i] = NULL; 95 } 96 97 98 BGameSoundDevice::~BGameSoundDevice() 99 { 100 BMediaRoster* roster = BMediaRoster::Roster(); 101 102 // We need to stop all the sounds before we stop the mixer 103 for (int32 i = 0; i < fSoundCount; i++) { 104 if (fSounds[i]) 105 fSounds[i]->StopPlaying(); 106 delete fSounds[i]; 107 } 108 109 if (fIsConnected) { 110 // stop the nodes if they are running 111 roster->StopNode(fConnection->producer, 0, true); 112 // synchronous stop 113 114 // Ordinarily we'd stop *all* of the nodes in the chain at this point. However, 115 // one of the nodes is the System Mixer, and stopping the Mixer is a Bad Idea (tm). 116 // So, we just disconnect from it, and release our references to the nodes that 117 // we're using. We *are* supposed to do that even for global nodes like the Mixer. 118 roster->Disconnect(fConnection->producer.node, fConnection->source, 119 fConnection->consumer.node, fConnection->destination); 120 121 roster->ReleaseNode(fConnection->producer); 122 roster->ReleaseNode(fConnection->consumer); 123 } 124 125 delete[] fSounds; 126 delete fConnection; 127 } 128 129 130 status_t 131 BGameSoundDevice::InitCheck() const 132 { 133 return fInitError; 134 } 135 136 137 const gs_audio_format & 138 BGameSoundDevice::Format() const 139 { 140 return fFormat; 141 } 142 143 144 const gs_audio_format & 145 BGameSoundDevice::Format(gs_id sound) const 146 { 147 return fSounds[sound - 1]->Format(); 148 } 149 150 151 void 152 BGameSoundDevice::SetInitError(status_t error) 153 { 154 fInitError = error; 155 } 156 157 158 status_t 159 BGameSoundDevice::CreateBuffer(gs_id * sound, 160 const gs_audio_format * format, 161 const void * data, 162 int64 frames) 163 { 164 if (frames <= 0 || !sound) 165 return B_BAD_VALUE; 166 167 status_t err = B_MEDIA_TOO_MANY_BUFFERS; 168 int32 position = AllocateSound(); 169 170 if (position >= 0) { 171 fSounds[position] = new SimpleSoundBuffer(format, data, frames); 172 err = fSounds[position]->Connect(&fConnection->producer); 173 } 174 175 if (err == B_OK) 176 *sound = gs_id(position + 1); 177 return err; 178 } 179 180 181 status_t 182 BGameSoundDevice::CreateBuffer(gs_id * sound, 183 const void * object, 184 const gs_audio_format * format) 185 { 186 if (!object || !sound) 187 return B_BAD_VALUE; 188 189 status_t err = B_MEDIA_TOO_MANY_BUFFERS; 190 int32 position = AllocateSound(); 191 192 if (position >= 0) { 193 fSounds[position] = new StreamingSoundBuffer(format, object); 194 err = fSounds[position]->Connect(&fConnection->producer); 195 } 196 197 if (err == B_OK) 198 *sound = gs_id(position+1); 199 return err; 200 } 201 202 203 void 204 BGameSoundDevice::ReleaseBuffer(gs_id sound) 205 { 206 if (sound <= 0) 207 return; 208 209 if (fSounds[sound - 1]) { 210 // We must stop playback befor destroying the sound or else 211 // we may recieve fatel errors. 212 fSounds[sound - 1]->StopPlaying(); 213 214 delete fSounds[sound - 1]; 215 fSounds[sound - 1] = NULL; 216 } 217 } 218 219 220 status_t 221 BGameSoundDevice::Buffer(gs_id sound, gs_audio_format* format, void *& data) 222 { 223 if (!format || sound <= 0) 224 return B_BAD_VALUE; 225 226 memcpy(format, &fSounds[sound-1]->Format(), sizeof(gs_audio_format)); 227 if (fSounds[sound-1]->Data()) { 228 data = malloc(format->buffer_size); 229 memcpy(data, fSounds[sound-1]->Data(), format->buffer_size); 230 } else 231 data = NULL; 232 233 return B_OK; 234 } 235 236 237 status_t 238 BGameSoundDevice::StartPlaying(gs_id sound) 239 { 240 if (sound <= 0) 241 return B_BAD_VALUE; 242 243 if (!fSounds[sound - 1]->IsPlaying()) { 244 // tell the producer to start playing the sound 245 return fSounds[sound - 1]->StartPlaying(); 246 } 247 248 fSounds[sound - 1]->Reset(); 249 return EALREADY; 250 } 251 252 253 status_t 254 BGameSoundDevice::StopPlaying(gs_id sound) 255 { 256 if (sound <= 0) 257 return B_BAD_VALUE; 258 259 if (fSounds[sound - 1]->IsPlaying()) { 260 // Tell the producer to stop play this sound 261 fSounds[sound - 1]->Reset(); 262 return fSounds[sound - 1]->StopPlaying(); 263 } 264 265 return EALREADY; 266 } 267 268 269 bool 270 BGameSoundDevice::IsPlaying(gs_id sound) 271 { 272 if (sound <= 0) 273 return false; 274 return fSounds[sound - 1]->IsPlaying(); 275 } 276 277 278 status_t 279 BGameSoundDevice::GetAttributes(gs_id sound, 280 gs_attribute * attributes, 281 size_t attributeCount) 282 { 283 if (!fSounds[sound - 1]) 284 return B_ERROR; 285 286 return fSounds[sound - 1]->GetAttributes(attributes, attributeCount); 287 } 288 289 290 status_t 291 BGameSoundDevice::SetAttributes(gs_id sound, 292 gs_attribute * attributes, 293 size_t attributeCount) 294 { 295 if (!fSounds[sound - 1]) 296 return B_ERROR; 297 298 return fSounds[sound - 1]->SetAttributes(attributes, attributeCount); 299 } 300 301 302 status_t 303 BGameSoundDevice::Connect() 304 { 305 BMediaRoster* roster = BMediaRoster::Roster(); 306 307 // create your own audio mixer 308 // TODO: Don't do this!!! See bug #575 309 dormant_node_info mixer_dormant_info; 310 int32 mixer_count = 1; // for now, we only care about the first we find. 311 status_t err = roster->GetDormantNodes(&mixer_dormant_info, 312 &mixer_count, 0, 0, 0, B_SYSTEM_MIXER, 0); 313 if (err != B_OK) 314 return err; 315 316 //fMixer = new media_node; 317 err = roster->InstantiateDormantNode(mixer_dormant_info, &fConnection->producer); 318 if (err != B_OK) 319 return err; 320 321 // retieve the system's audio mixer 322 err = roster->GetAudioMixer(&fConnection->consumer); 323 if (err != B_OK) 324 return err; 325 326 int32 count = 1; 327 media_input mixerInput; 328 err = roster->GetFreeInputsFor(fConnection->consumer, &mixerInput, 1, &count); 329 if (err != B_OK) 330 return err; 331 332 count = 1; 333 media_output mixerOutput; 334 err = roster->GetFreeOutputsFor(fConnection->producer, &mixerOutput, 1, &count); 335 if (err != B_OK) 336 return err; 337 338 media_format format(mixerOutput.format); 339 err = roster->Connect(mixerOutput.source, mixerInput.destination, 340 &format, &mixerOutput, &mixerInput); 341 if (err != B_OK) 342 return err; 343 344 // set the producer's time source to be the "default" time source, which 345 // the Mixer uses too. 346 roster->GetTimeSource(&fConnection->timeSource); 347 roster->SetTimeSourceFor(fConnection->producer.node, fConnection->timeSource.node); 348 349 // Start our mixer's time source if need be. Chances are, it won't need to be, 350 // but if we forget to do this, our mixer might not do anything at all. 351 BTimeSource* mixerTimeSource = roster->MakeTimeSourceFor(fConnection->producer); 352 if (!mixerTimeSource) 353 return B_ERROR; 354 355 if (!mixerTimeSource->IsRunning()) { 356 status_t err = roster->StartNode(mixerTimeSource->Node(), BTimeSource::RealTime()); 357 if (err != B_OK) { 358 mixerTimeSource->Release(); 359 return err; 360 } 361 } 362 363 // Start up our mixer 364 bigtime_t tpNow = mixerTimeSource->Now(); 365 err = roster->StartNode(fConnection->producer, tpNow + 10000); 366 mixerTimeSource->Release(); 367 if (err != B_OK) 368 return err; 369 370 // the inputs and outputs might have been reassigned during the 371 // nodes' negotiation of the Connect(). That's why we wait until 372 // after Connect() finishes to save their contents. 373 fConnection->format = format; 374 fConnection->source = mixerOutput.source; 375 fConnection->destination = mixerInput.destination; 376 377 // Set an appropriate run mode for the producer 378 roster->SetRunModeNode(fConnection->producer, BMediaNode::B_INCREASE_LATENCY); 379 380 media_to_gs_format(&fFormat, &format.u.raw_audio); 381 fIsConnected = true; 382 return B_OK; 383 } 384 385 386 int32 387 BGameSoundDevice::AllocateSound() 388 { 389 for (int32 i = 0; i < fSoundCount; i++) 390 if (!fSounds[i]) 391 return i; 392 393 // we need to allocate new space for the sound 394 GameSoundBuffer ** sounds = new GameSoundBuffer*[fSoundCount + kGrowth]; 395 for (int32 i = 0; i < fSoundCount; i++) 396 sounds[i] = fSounds[i]; 397 398 for (int32 i = fSoundCount; i < fSoundCount + kGrowth; i++) 399 sounds[i] = NULL; 400 401 // replace the old list 402 delete [] fSounds; 403 fSounds = sounds; 404 fSoundCount += kGrowth; 405 406 return fSoundCount - kGrowth; 407 } 408 409