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 : 85 fIsConnected(false), 86 fSoundCount(kInitSoundCount) 87 { 88 fConnection = new Connection; 89 memset(&fFormat, 0, sizeof(gs_audio_format)); 90 91 fInitError = Connect(); 92 93 fSounds = new GameSoundBuffer*[kInitSoundCount]; 94 for (int32 i = 0; i < kInitSoundCount; i++) 95 fSounds[i] = NULL; 96 } 97 98 99 BGameSoundDevice::~BGameSoundDevice() 100 { 101 BMediaRoster* roster = BMediaRoster::Roster(); 102 103 // We need to stop all the sounds before we stop the mixer 104 for (int32 i = 0; i < fSoundCount; i++) { 105 if (fSounds[i]) 106 fSounds[i]->StopPlaying(); 107 delete fSounds[i]; 108 } 109 110 if (fIsConnected) { 111 // stop the nodes if they are running 112 roster->StopNode(fConnection->producer, 0, true); 113 // synchronous stop 114 115 // Ordinarily we'd stop *all* of the nodes in the chain at this point. 116 // However, one of the nodes is the System Mixer, and stopping the 117 // Mixer is a Bad Idea (tm). So, we just disconnect from it, and 118 // release our references to the nodes that we're using. We *are* 119 // supposed to do that even for global nodes like the Mixer. 120 roster->Disconnect(fConnection->producer.node, fConnection->source, 121 fConnection->consumer.node, fConnection->destination); 122 123 roster->ReleaseNode(fConnection->producer); 124 roster->ReleaseNode(fConnection->consumer); 125 } 126 127 delete[] fSounds; 128 delete fConnection; 129 } 130 131 132 status_t 133 BGameSoundDevice::InitCheck() const 134 { 135 return fInitError; 136 } 137 138 139 const gs_audio_format& 140 BGameSoundDevice::Format() const 141 { 142 return fFormat; 143 } 144 145 146 const gs_audio_format& 147 BGameSoundDevice::Format(gs_id sound) const 148 { 149 return fSounds[sound - 1]->Format(); 150 } 151 152 153 void 154 BGameSoundDevice::SetInitError(status_t error) 155 { 156 fInitError = error; 157 } 158 159 160 status_t 161 BGameSoundDevice::CreateBuffer(gs_id* sound, const gs_audio_format* format, 162 const void* data, 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, const void* object, 183 const gs_audio_format* format) 184 { 185 if (!object || !sound) 186 return B_BAD_VALUE; 187 188 status_t err = B_MEDIA_TOO_MANY_BUFFERS; 189 int32 position = AllocateSound(); 190 191 if (position >= 0) { 192 fSounds[position] = new StreamingSoundBuffer(format, object); 193 err = fSounds[position]->Connect(&fConnection->producer); 194 } 195 196 if (err == B_OK) 197 *sound = gs_id(position+1); 198 return err; 199 } 200 201 202 void 203 BGameSoundDevice::ReleaseBuffer(gs_id sound) 204 { 205 if (sound <= 0) 206 return; 207 208 if (fSounds[sound - 1]) { 209 // We must stop playback befor destroying the sound or else 210 // we may recieve fatel errors. 211 fSounds[sound - 1]->StopPlaying(); 212 213 delete fSounds[sound - 1]; 214 fSounds[sound - 1] = NULL; 215 } 216 } 217 218 219 status_t 220 BGameSoundDevice::Buffer(gs_id sound, gs_audio_format* format, void*& data) 221 { 222 if (!format || sound <= 0) 223 return B_BAD_VALUE; 224 225 memcpy(format, &fSounds[sound-1]->Format(), sizeof(gs_audio_format)); 226 if (fSounds[sound-1]->Data()) { 227 data = malloc(format->buffer_size); 228 memcpy(data, fSounds[sound-1]->Data(), format->buffer_size); 229 } else 230 data = NULL; 231 232 return B_OK; 233 } 234 235 236 status_t 237 BGameSoundDevice::StartPlaying(gs_id sound) 238 { 239 if (sound <= 0) 240 return B_BAD_VALUE; 241 242 if (!fSounds[sound - 1]->IsPlaying()) { 243 // tell the producer to start playing the sound 244 return fSounds[sound - 1]->StartPlaying(); 245 } 246 247 fSounds[sound - 1]->Reset(); 248 return EALREADY; 249 } 250 251 252 status_t 253 BGameSoundDevice::StopPlaying(gs_id sound) 254 { 255 if (sound <= 0) 256 return B_BAD_VALUE; 257 258 if (fSounds[sound - 1]->IsPlaying()) { 259 // Tell the producer to stop play this sound 260 fSounds[sound - 1]->Reset(); 261 return fSounds[sound - 1]->StopPlaying(); 262 } 263 264 return EALREADY; 265 } 266 267 268 bool 269 BGameSoundDevice::IsPlaying(gs_id sound) 270 { 271 if (sound <= 0) 272 return false; 273 return fSounds[sound - 1]->IsPlaying(); 274 } 275 276 277 status_t 278 BGameSoundDevice::GetAttributes(gs_id sound, gs_attribute* attributes, 279 size_t attributeCount) 280 { 281 if (!fSounds[sound - 1]) 282 return B_ERROR; 283 284 return fSounds[sound - 1]->GetAttributes(attributes, attributeCount); 285 } 286 287 288 status_t 289 BGameSoundDevice::SetAttributes(gs_id sound, gs_attribute* attributes, 290 size_t attributeCount) 291 { 292 if (!fSounds[sound - 1]) 293 return B_ERROR; 294 295 return fSounds[sound - 1]->SetAttributes(attributes, attributeCount); 296 } 297 298 299 status_t 300 BGameSoundDevice::Connect() 301 { 302 BMediaRoster* roster = BMediaRoster::Roster(); 303 304 // create your own audio mixer 305 // TODO: Don't do this!!! See bug #575 306 // from #575. 307 // marcusoverhagen : tries to instantiate a new audio mixer, which is bad. 308 // It should use the system mixer. 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, 318 &fConnection->producer); 319 if (err != B_OK) 320 return err; 321 322 // retieve the system's audio mixer 323 err = roster->GetAudioMixer(&fConnection->consumer); 324 if (err != B_OK) 325 return err; 326 327 int32 count = 1; 328 media_input mixerInput; 329 err = roster->GetFreeInputsFor(fConnection->consumer, &mixerInput, 1, 330 &count); 331 if (err != B_OK) 332 return err; 333 334 count = 1; 335 media_output mixerOutput; 336 err = roster->GetFreeOutputsFor(fConnection->producer, &mixerOutput, 1, 337 &count); 338 if (err != B_OK) 339 return err; 340 341 media_format format(mixerOutput.format); 342 err = roster->Connect(mixerOutput.source, mixerInput.destination, 343 &format, &mixerOutput, &mixerInput); 344 if (err != B_OK) 345 return err; 346 347 // set the producer's time source to be the "default" time source, which 348 // the Mixer uses too. 349 roster->GetTimeSource(&fConnection->timeSource); 350 roster->SetTimeSourceFor(fConnection->producer.node, 351 fConnection->timeSource.node); 352 353 // Start our mixer's time source if need be. Chances are, it won't need to 354 // be, but if we forget to do this, our mixer might not do anything at all. 355 BTimeSource* mixerTimeSource = roster->MakeTimeSourceFor( 356 fConnection->producer); 357 if (!mixerTimeSource) 358 return B_ERROR; 359 360 if (!mixerTimeSource->IsRunning()) { 361 status_t err = roster->StartNode(mixerTimeSource->Node(), 362 BTimeSource::RealTime()); 363 if (err != B_OK) { 364 mixerTimeSource->Release(); 365 return err; 366 } 367 } 368 369 // Start up our mixer 370 bigtime_t tpNow = mixerTimeSource->Now(); 371 err = roster->StartNode(fConnection->producer, tpNow + 10000); 372 mixerTimeSource->Release(); 373 if (err != B_OK) 374 return err; 375 376 // the inputs and outputs might have been reassigned during the 377 // nodes' negotiation of the Connect(). That's why we wait until 378 // after Connect() finishes to save their contents. 379 fConnection->format = format; 380 fConnection->source = mixerOutput.source; 381 fConnection->destination = mixerInput.destination; 382 383 // Set an appropriate run mode for the producer 384 roster->SetRunModeNode(fConnection->producer, 385 BMediaNode::B_INCREASE_LATENCY); 386 387 media_to_gs_format(&fFormat, &format.u.raw_audio); 388 fIsConnected = true; 389 return B_OK; 390 } 391 392 393 int32 394 BGameSoundDevice::AllocateSound() 395 { 396 for (int32 i = 0; i < fSoundCount; i++) 397 if (!fSounds[i]) 398 return i; 399 400 // we need to allocate new space for the sound 401 GameSoundBuffer ** sounds = new GameSoundBuffer*[fSoundCount + kGrowth]; 402 for (int32 i = 0; i < fSoundCount; i++) 403 sounds[i] = fSounds[i]; 404 405 for (int32 i = fSoundCount; i < fSoundCount + kGrowth; i++) 406 sounds[i] = NULL; 407 408 // replace the old list 409 delete [] fSounds; 410 fSounds = sounds; 411 fSoundCount += kGrowth; 412 413 return fSoundCount - kGrowth; 414 } 415 416