Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: orxonox.OLD/branches/gui/src/lib/sound/ogg_player.cc @ 7997

Last change on this file since 7997 was 7849, checked in by bensch, 19 years ago

trunk: Sound is not lost, if the MusicEngine does not get his time in time :)

File size: 12.9 KB
Line 
1/*
2   orxonox - the future of 3D-vertical-scrollers
3
4   Copyright (C) 2004 orx
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2, or (at your option)
9   any later version.
10
11### File Specific:
12   main-programmer: Benjamin Grauer
13   co-programmer: ...
14
15    Beware:
16   The Ogg-Player is a __Threaded__ Stream, this means, that invoking play()
17   creates a Thread, that loops through the Music-File. The Thread seems
18   to be safe, but it is not guarantied.
19
20   -------------------------------------------------------------------
21   A Simple Version of this can be found at http://www.devmaster.net
22   Thanks a lot for the nice work, and the easy portability to our Project.
23*/
24#define DEBUG_SPECIAL_MODULE DEBUG_MODULE_SOUND
25
26#include <iostream>
27
28#include "ogg_player.h"
29
30#include "sound_engine.h"
31
32#include "debug.h"
33
34namespace OrxSound
35{
36  /**
37   * initializes an Ogg-player from a file
38   * @param fileName the file to load
39   */
40  OggPlayer::OggPlayer(const std::string& fileName)
41  {
42    this->setClassID(CL_SOUND_OGG_PLAYER, "OggPlayer");
43
44    this->state = OggPlayer::None;
45
46    this->source = 0;
47    this->buffers[0] = 0;
48    this->buffers[1] = 0;
49    this->musicThreadID = NULL;
50
51    if (!fileName.empty())
52    {
53      if (this->open(fileName))
54        this->setName(fileName);
55    }
56
57  }
58
59  /**
60   * @brief deletes the OggPlayer
61   */
62  OggPlayer::~OggPlayer()
63  {
64    this->release();
65  }
66
67  ///////////////
68  // INTERFACE //
69  ///////////////
70  /**
71   * @brief opens a file for playback
72   * @param fileName the file to open
73   */
74  bool OggPlayer::open(const std::string& fileName)
75  {
76    OrxThread::MutexLock musicLock(&this->musicMutex);
77    // release old Ogg-File (if loaded)
78    if (this->state & OggPlayer::FileOpened)
79      this->release();
80
81    // allocating Buffers
82    if (this->buffers[0] == 0)
83      alGenBuffers(2, this->buffers);
84    SoundEngine::checkError("Allocating Buffers", __LINE__);
85    if (this->buffers[0] != 0 && this->buffers[1] != 0)
86      state |= OggPlayer::BuffersAllocated;
87    else
88    {
89      PRINTF(2)("Unable to allocate al-Buffers\n");
90      this->release();
91      return false;
92    }
93    // allocating source
94    if (this->source == 0)
95      SoundEngine::getInstance()->popALSource(this->source);
96    if (this->source != 0)
97      state |= OggPlayer::SourceAllocated;
98    else
99    {
100      PRINTF(2)("No more Sources Availiable (maybe you should consider raising the source-count.)\n");
101      this->release();
102      return false;
103    }
104
105    // opening the FILE;
106    int result;
107    if(!(oggFile = fopen(fileName.c_str(), "rb")))
108    {
109      PRINTF(2)("Could not open Ogg file.");
110      this->release();
111      return false;
112    }
113    // reading the Stream.
114    if((result = ov_open(oggFile, &oggStream, NULL, 0)) < 0)
115    {
116      PRINTF(2)("Could not open Ogg stream. %s", getVorbisError(result));
117      fclose(oggFile);
118      this->release();
119      return false;
120    }
121    this->state |= OggPlayer::FileOpened;
122
123    // acquiring the vorbis-properties.
124    vorbisInfo = ov_info(&oggStream, -1);
125    vorbisComment = ov_comment(&oggStream, -1);
126
127    if(vorbisInfo->channels == 1)
128      format = AL_FORMAT_MONO16;
129    else
130      format = AL_FORMAT_STEREO16;
131
132    // setting the Source Properties.
133    alSource3f(source, AL_POSITION,        0.0, 0.0, 0.0);
134    alSource3f(source, AL_VELOCITY,        0.0, 0.0, 0.0);
135    alSource3f(source, AL_DIRECTION,       0.0, 0.0, 0.0);
136    alSourcef (source, AL_ROLLOFF_FACTOR,  0.0          );
137    alSourcei (source, AL_SOURCE_RELATIVE, AL_TRUE      );
138    alSourcef (source, AL_GAIN,            SoundEngine::getInstance()->getMusicVolume());
139    SoundEngine::checkError("OggPlayer::open()::SetSourceProperties", __LINE__);
140
141    // Set to State Stopped
142    this->state |= OggPlayer::Stopped;
143    return true;
144  }
145
146
147  /**
148   * @brief start Playing Music.
149   * @returns true on success false otherwise.
150   */
151  bool OggPlayer::play()
152  {
153    if (!(this->state & OggPlayer::FileOpened))
154      return false;
155
156    this->state &= ~(OggPlayer::Stopped | OggPlayer::Paused);
157
158    if (!this->playback())
159      return false;
160
161    if (this->musicThreadID == NULL)
162      return ((this->musicThreadID = SDL_CreateThread(OggPlayer::musicThread, (void*)this)) != NULL);
163    return true;
164  }
165
166  /**
167   * @brief stop the OggPlayer from Playing.
168   */
169  void OggPlayer::stop()
170  {
171    this->state &= ~(OggPlayer::Playing | OggPlayer::Paused);
172    this->state |= OggPlayer::Stopped;
173
174    this->suspend();
175    this->rewind();
176  }
177
178  /**
179   * @brief Pause the Playing.
180   */
181  void OggPlayer::pause()
182  {
183    this->state &= ~OggPlayer::Playing;
184
185    if (!(this->state & OggPlayer::Stopped))
186      this->state |= OggPlayer::Paused;
187
188    this->suspend();
189  }
190
191  /**
192   * @brief rewind to the beginning, and play (if already playing)
193   */
194  void OggPlayer::rewind()
195  {
196    this->jumpTo(0.0f);
197  }
198
199  /**
200   * @brief jump to Second timeCode in the MusicFile
201   * @param timeCode where to jump to.
202   */
203  void OggPlayer::jumpTo(float timeCode)
204  {
205
206    if (this->state & OggPlayer::FileOpened)
207    {
208      OrxThread::MutexLock musicLock(&this->musicMutex);
209      ov_time_seek(&this->oggStream, timeCode);
210    }
211  }
212
213  /**
214   * @returns the Length of the Music in Seconds
215   */
216  float OggPlayer::length()
217  {
218    if (this->state & OggPlayer::FileOpened)
219      return ov_time_total(&this->oggStream, -1);
220    else
221      return 0.0f;
222  }
223
224
225  /**
226   * @returns true if the file is playing
227   */
228  bool OggPlayer::isPlaying()
229  {
230    if (!(this->state & OggPlayer::FileOpened))
231      return false;
232    ALenum state;
233
234    alGetSourcei(this->source, AL_SOURCE_STATE, &state);
235
236    return (state == AL_PLAYING);
237  }
238
239
240
241  ////////////////////////
242  // INTERNAL FUNCTIONS //
243  ////////////////////////
244  /**
245   * @brief creates a Thread for Playing back the music even if the rest of the Engine is slow
246   * @param oggPlayer the OggPlayer to play back
247   * @returns -1 on error.
248   */
249  int OggPlayer::musicThread(void* oggPlayer)
250  {
251    if (oggPlayer == NULL)
252      return -1;
253    OggPlayer* ogg = (OggPlayer*)oggPlayer;
254
255    PRINTF(4)("STARTIG AUDIO THREAD\n");
256    while (ogg->state & OggPlayer::Playing)
257    {
258      {
259        OrxThread::MutexLock musicLock(&ogg->musicMutex);
260        ogg->update();
261      }
262      SDL_Delay(10);
263    }
264    PRINTF(4)("End the AudioThread\n");
265  }
266
267
268  /**
269   * @brief plays back the sound
270   * @return true if running, false otherwise
271   */
272  bool OggPlayer::playback()
273  {
274    if (!(this->state & OggPlayer::FileOpened))
275      return false;
276
277    if(this->state & OggPlayer::Playing)
278      return true;
279    this->state |= OggPlayer::Playing;
280
281    OrxThread::MutexLock musicLock(&this->musicMutex);
282    if(!this->stream(this->buffers[0]) || !this->stream(this->buffers[1]))
283    {
284      this->state &= ~OggPlayer::Playing;
285      return false;
286    }
287
288    alSourceQueueBuffers(this->source, 2, this->buffers);
289    if (DEBUG_LEVEL >= 3)
290      SoundEngine::checkError("OggPlayer::playback()::alSourceQueueBuffers", __LINE__);
291
292    alSourcePlay(this->source);
293    if (DEBUG_LEVEL >= 3)
294      SoundEngine::checkError("OggPlayer::playback()::alSourcePlay", __LINE__);
295    return true;
296  }
297
298
299  /**
300   * @brief waits for the AudioThread to be finished.
301   */
302  void OggPlayer::suspend()
303  {
304    if (this->musicThreadID != NULL)
305    {
306      assert (!(this->state & Playing));
307      SDL_WaitThread(this->musicThreadID, NULL);
308      this->musicThreadID = NULL;
309    }
310    if (this->state & OggPlayer::SourceAllocated)
311    {
312      alSourceStop(this->source);
313      alSourcei(this->source, AL_BUFFER, 0);
314    }
315  }
316
317  /**
318   * @brief updates the stream, this has to be done every few parts of a second, for sound-consistency
319   * @returns true, if the Sound is playing flawlessly
320   */
321  bool OggPlayer::update()
322  {
323    int processed = 0;
324    bool active = true;
325
326    alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed);
327
328    if (DEBUG_LEVEL >= 3)
329      SoundEngine::checkError("OggPlayer::update()::alGetSourceI", __LINE__);
330
331    while(processed--)
332    {
333      ALuint buffer;
334
335      alSourceUnqueueBuffers(source, 1, &buffer);
336      if (DEBUG_LEVEL >= 3)
337        SoundEngine::checkError("OggPlayer::update()::unqueue", __LINE__);
338
339      active = stream(buffer);
340
341      alSourceQueueBuffers(source, 1, &buffer);
342      if (DEBUG_LEVEL >= 3)
343        SoundEngine::checkError("OggPlayer::update()::queue", __LINE__);
344    }
345
346    int play;
347    alGetSourcei(source, AL_SOURCE_STATE, &play);
348
349    if (play != AL_PLAYING)
350    {
351      alSourcePlay(source);
352      PRINTF(2)("Filling Audio Gap\n");
353    }
354
355
356    return active;
357  }
358
359  /**
360   * @brief gets a new Stream from buffer
361   * @param buffer the buffer to get the stream from
362   * @return true, if everything worked as planed
363   */
364  bool OggPlayer::stream(ALuint buffer)
365  {
366    if (unlikely(!(this->state & Playing)))
367      return false;
368    char pcm[OGG_PLAYER_BUFFER_SIZE];
369    int  size = 0;
370    int  section;
371    int  result;
372
373    while(size < OGG_PLAYER_BUFFER_SIZE)
374    {
375      result = ov_read(&this->oggStream, pcm + size, OGG_PLAYER_BUFFER_SIZE - size, 0, 2, 1, &section);
376
377      if(result > 0)
378        size += result;
379      else if(result < 0)
380        throw getVorbisError(result);
381      else /* eof */
382        ov_time_seek(&this->oggStream, 0.0);
383    }
384
385    if(size == 0)
386      return false;
387
388    alBufferData(buffer, format, pcm, size, vorbisInfo->rate);
389    if (DEBUG_LEVEL >= 3)
390      SoundEngine::checkError("OggPlayer::stream()::BUFFER", __LINE__);
391
392    return true;
393  }
394
395
396  /**
397   * @brief releases a stream
398   */
399  void OggPlayer::release()
400  {
401    if (this->state & OggPlayer::SourceAllocated)
402    {
403      assert(alIsSource(this->source));
404      if (this->state & OggPlayer::Playing);
405      {
406        // Kill the Music Thread.
407        this->state &= ~OggPlayer::Playing;
408        this->suspend();
409
410        SoundEngine::checkError("OggPlayer::release()::alSourceStop", __LINE__);
411      }
412      empty();
413      alSourcei(this->source, AL_BUFFER, 0);
414      SoundEngine::getInstance()->pushALSource(this->source);
415      this->source = 0;
416      this->state &= ~SourceAllocated;
417    }
418    if (this->state & OggPlayer::BuffersAllocated)
419    {
420      assert (this->buffers[0] != 0 && this->buffers[1] != 0);
421      alDeleteBuffers(2, buffers);
422      SoundEngine::checkError("OggPlayer::release()::alDeleteBuffers", __LINE__);
423      this->buffers[0] = 0;
424      this->buffers[1] = 0;
425      this->state &= ~OggPlayer::BuffersAllocated;
426    }
427
428    if (this->state & OggPlayer::FileOpened)
429    {
430      ov_clear(&oggStream);
431      this->state &= ~OggPlayer::FileOpened;
432    }
433
434  }
435
436
437  /**
438   * @brief empties the buffers
439   */
440  void OggPlayer::empty()
441  {
442    int queued;
443
444    alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
445
446    while(queued--)
447    {
448      ALuint buffer;
449
450      alSourceUnqueueBuffers(source, 1, &buffer);
451      SoundEngine::checkError("OggPlayer::empty()::unqueue Buffers", __LINE__);
452    }
453  }
454
455
456  /////////////////////
457  // DEBUG_LEVEL FUNCTIONS //
458  /////////////////////
459  /**
460   * displays some info about the ogg-file
461   */
462  void OggPlayer::debug() const
463  {
464    std::cout
465    << "version         " << vorbisInfo->version         << "\n"
466    << "channels        " << vorbisInfo->channels        << "\n"
467    << "rate (hz)       " << vorbisInfo->rate            << "\n"
468    << "bitrate upper   " << vorbisInfo->bitrate_upper   << "\n"
469    << "bitrate nominal " << vorbisInfo->bitrate_nominal << "\n"
470    << "bitrate lower   " << vorbisInfo->bitrate_lower   << "\n"
471    << "bitrate window  " << vorbisInfo->bitrate_window  << "\n"
472    << "\n"
473    << "vendor " << vorbisComment->vendor << "\n";
474
475    for(int i = 0; i < vorbisComment->comments; i++)
476      std::cout << "   " << vorbisComment->user_comments[i] << "\n";
477
478    std::cout << std::endl;
479  }
480
481
482  void OggPlayer::printState() const
483  {
484    PRINTF(0)("OggPlayer is in the following States: ");
485    if (this->state & OggPlayer::FileOpened)
486      PRINT(0)("FileOpened ");
487    if (this->state & OggPlayer::SourceAllocated)
488      PRINT(0)("SourceAllocated ");
489    if (this->state & OggPlayer::BuffersAllocated)
490      PRINT(0)("BuffersAllocated ");
491    if (this->state & OggPlayer::Stopped)
492      PRINT(0)("Stopped ");
493    if (this->state & OggPlayer::Playing)
494      PRINT(0)("Playing ");
495    if (this->state & OggPlayer::Paused)
496      PRINT(0)("Paused ");
497    if (this->state & OggPlayer::Error)
498      PRINT(0)("Error ");
499    PRINT(0)("\n");
500  }
501
502  /**
503   * returns errors
504   * @param code the error-code
505   * @return the error as a String
506   */
507  const char* OggPlayer::getVorbisError(int code)
508  {
509    switch(code)
510    {
511      case OV_EREAD:
512        return ("Read from media.");
513      case OV_ENOTVORBIS:
514        return ("Not Vorbis data.");
515      case OV_EVERSION:
516        return ("Vorbis version mismatch.");
517      case OV_EBADHEADER:
518        return ("Invalid Vorbis header.");
519      case OV_EFAULT:
520        return ("Internal logic fault (bug or heap/stack corruption.");
521      default:
522        return ("Unknown Ogg error.");
523    }
524  }
525
526}
Note: See TracBrowser for help on using the repository browser.