Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: orxonox.OLD/branches/new_class_id/src/lib/sound/ogg_player.cc @ 9843

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

some nicer output, and the music does nod stall anymore.

Just so you all know (at least interessted once): Resources can now be displayed through:
Shell → ResourceManager debug

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