Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: orxonox.OLD/trunk/src/lib/sound/ogg_player.cc @ 10728

Last change on this file since 10728 was 10618, checked in by bknecht, 18 years ago

merged cleanup into trunk (only improvements)

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::MutexLocker 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::MutexLocker 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::MutexLocker 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::MutexLocker 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.