Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: orxonox.OLD/branches/terrain.older/src/lib/sound/ogg_player.cc @ 10649

Last change on this file since 10649 was 9140, checked in by bensch, 18 years ago

merged back

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