Fixed: Don't busywait for navsounds to finish.
[xbmc:jj72orguks-xbmc.git] / guilib / GUISound.cpp
1 /*
2  *      Copyright (C) 2005-2008 Team XBMC
3  *      http://www.xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "system.h"
23 #include "GUISound.h"
24 #include "AudioContext.h"
25 #include "Settings.h"
26 #include "FileSystem/File.h"
27 #include "utils/log.h"
28 #ifdef HAS_SDL_AUDIO
29 #include <SDL/SDL_mixer.h>
30 #include "FileSystem/SpecialProtocol.h"
31 #endif
32 #ifndef HAS_SDL_AUDIO
33
34 typedef struct
35 {
36   char chunk_id[4];
37   long chunksize;
38 } WAVE_CHUNK;
39
40 typedef struct
41 {
42   char riff[4];
43   long filesize;
44   char rifftype[4];
45 } WAVE_RIFFHEADER;
46 #else
47 #define GUI_SOUND_CHANNEL 0
48 #endif
49
50 CGUISound::CGUISound()
51 {
52   m_soundBuffer=NULL;
53 }
54
55 CGUISound::~CGUISound()
56 {
57 #ifdef _WIN32
58   FreeBuffer();
59 #elif defined(HAS_SDL_AUDIO)
60   Mix_FreeChunk(m_soundBuffer);
61 #endif
62 }
63
64 // \brief Loads a wav file by filename
65 bool CGUISound::Load(const CStdString& strFile)
66 {
67 #ifdef _WIN32
68   LPBYTE pbData=NULL;
69   WAVEFORMATEX wfx;
70   int size=0;
71   if (!LoadWav(strFile, &wfx, &pbData, &size))
72     return false;
73
74   bool bReady=(CreateBuffer(&wfx, size) && FillBuffer(pbData, size));
75
76   if (!bReady)
77     FreeBuffer();
78
79   delete[] pbData;
80
81   return bReady;
82 #elif defined(HAS_SDL_AUDIO)
83   m_soundBuffer = Mix_LoadWAV(_P(strFile));
84   if (!m_soundBuffer)
85     return false;
86
87   return true;
88 #else
89   return false;
90 #endif
91 }
92
93 // \brief Starts playback of the sound
94 void CGUISound::Play()
95 {
96   if (m_soundBuffer)
97   {
98 #ifdef _WIN32
99     m_soundBuffer->Play(0, 0, 0);
100 #elif defined(HAS_SDL_AUDIO)
101     Mix_PlayChannel(GUI_SOUND_CHANNEL, m_soundBuffer, 0);
102 #endif
103   }
104 }
105
106 // \brief returns true if the sound is playing
107 bool CGUISound::IsPlaying()
108 {
109 #ifdef _WIN32
110   if (m_soundBuffer)
111   {
112     DWORD dwStatus;
113     m_soundBuffer->GetStatus(&dwStatus);
114     return (dwStatus & DSBSTATUS_PLAYING);
115   }
116
117   return false;
118 #elif defined(HAS_SDL_AUDIO)
119   return Mix_Playing(GUI_SOUND_CHANNEL) != 0;
120 #else
121   return false;
122 #endif
123 }
124
125 // \brief Stops playback if the sound
126 void CGUISound::Stop()
127 {
128   if (m_soundBuffer)
129   {
130 #ifdef _WIN32
131     m_soundBuffer->Stop();
132 #elif defined(HAS_SDL_AUDIO)
133     Mix_HaltChannel(GUI_SOUND_CHANNEL);
134 #endif
135     Wait();
136   }
137 }
138
139 // \brief Sets the volume of the sound
140 void CGUISound::SetVolume(int level)
141 {
142   if (m_soundBuffer)
143   {
144 #ifdef _WIN32
145     m_soundBuffer->SetVolume(level);
146 #elif defined(HAS_SDL_AUDIO)
147     Mix_Volume(GUI_SOUND_CHANNEL, level);
148 #endif
149   }
150 }
151
152 void CGUISound::Wait(uint32_t millis/*=500*/)
153 {
154   millis /= 10;
155   while (IsPlaying() && millis--)
156     Sleep(10);
157 }
158
159 #ifdef _WIN32
160 bool CGUISound::CreateBuffer(LPWAVEFORMATEX wfx, int iLength)
161 {
162   //  Set up DSBUFFERDESC structure
163   DSBUFFERDESC dsbdesc;
164   memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
165   dsbdesc.dwSize=sizeof(DSBUFFERDESC);
166   // directsound requires ctrlvolume to be set
167   dsbdesc.dwFlags = DSBCAPS_CTRLVOLUME;
168   dsbdesc.dwBufferBytes=iLength;
169   dsbdesc.lpwfxFormat=wfx;
170
171   LPDIRECTSOUND directSound=g_audioContext.GetDirectSoundDevice();
172   if (!directSound)
173     return false;
174
175   //  Create buffer
176   if (FAILED(directSound->CreateSoundBuffer(&dsbdesc, &m_soundBuffer, NULL)))
177   {
178     m_soundBuffer = NULL;
179     CLog::Log(LOGERROR, __FUNCTION__" Creating sound buffer failed!");
180     return false;
181   }
182
183   //  Make effects as loud as possible
184   m_soundBuffer->SetVolume(g_settings.m_nVolumeLevel);
185
186   return true;
187 }
188
189 bool CGUISound::FillBuffer(LPBYTE pbData, int iLength)
190 {
191   if (!m_soundBuffer)
192     return false;
193
194   LPVOID lpvWrite;
195   DWORD  dwLength;
196
197   if (SUCCEEDED(m_soundBuffer->Lock(0, 0, &lpvWrite, &dwLength, NULL, NULL, DSBLOCK_ENTIREBUFFER)))
198   {
199     memcpy(lpvWrite, pbData, iLength);
200     m_soundBuffer->Unlock(lpvWrite, dwLength, NULL, 0);
201     return true;
202   }
203
204   CLog::Log(LOGERROR, __FUNCTION__" Filling sound buffer failed!");
205
206   return false;
207 }
208
209 void CGUISound::FreeBuffer()
210 {
211   if (IsPlaying())
212     Stop();
213
214   SAFE_RELEASE(m_soundBuffer);
215 }
216
217 bool CGUISound::LoadWav(const CStdString& strFile, WAVEFORMATEX* wfx, LPBYTE* ppWavData, int* pDataSize)
218 {
219   XFILE::CFile file;
220   if (!file.Open(strFile))
221     return false;
222
223   // read header
224   WAVE_RIFFHEADER riffh;
225   file.Read(&riffh, sizeof(WAVE_RIFFHEADER));
226
227   // file valid?
228   if (strncmp(riffh.riff, "RIFF", 4)!=0 && strncmp(riffh.rifftype, "WAVE", 4)!=0)
229   {
230     file.Close();
231     return false;
232   }
233
234   long offset=0;
235   offset += sizeof(WAVE_RIFFHEADER);
236   offset -= sizeof(WAVE_CHUNK);
237
238   // parse chunks
239   do
240   {
241     WAVE_CHUNK chunk;
242
243     // always seeking to the start of a chunk
244     file.Seek(offset + sizeof(WAVE_CHUNK), SEEK_SET);
245     file.Read(&chunk, sizeof(WAVE_CHUNK));
246
247     if (!strncmp(chunk.chunk_id, "fmt ", 4))
248     { // format chunk
249       memset(wfx, 0, sizeof(WAVEFORMATEX));
250       file.Read(wfx, 16);
251       // we only need 16 bytes of the fmt chunk
252       if (chunk.chunksize-16>0)
253         file.Seek(chunk.chunksize-16, SEEK_CUR);
254     }
255     else if (!strncmp(chunk.chunk_id, "data", 4))
256     { // data chunk
257       *ppWavData=new BYTE[chunk.chunksize+1];
258       file.Read(*ppWavData, chunk.chunksize);
259       *pDataSize=chunk.chunksize;
260
261       if (chunk.chunksize & 1)
262         offset++;
263     }
264     else
265     { // other chunk - unused, just skip
266       file.Seek(chunk.chunksize, SEEK_CUR);
267     }
268
269     offset+=(chunk.chunksize+sizeof(WAVE_CHUNK));
270
271     if (offset & 1)
272       offset++;
273
274   } while (offset+(int)sizeof(WAVE_CHUNK) < riffh.filesize);
275
276   file.Close();
277   return (*ppWavData!=NULL);
278 }
279
280 #endif