f29de1853da9192f98cd9222ccf3472b8c654835
[camargo/neiasound.git] / src / nSoundStreamer.cpp
1 // Copyright (C) 2015 Lucas Pires Camargo
2 // 
3 // This file is part of neiasound - Qt-style OpenAL wrapper for games.
4 // 
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 // 
9 // 1. Redistributions of source code must retain the above copyright notice,
10 // this list of conditions and the following disclaimer.
11 // 
12 // 2. Redistributions in binary form must reproduce the above copyright notice,
13 // this list of conditions and the following disclaimer in the documentation
14 // and/or other materials provided with the distribution.
15 // 
16 // THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT ``AS IS'' AND ANY EXPRESS
17 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 // OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19 // NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 #include "nSoundStreamer.h"
27 #include "nSoundSystem.h"
28 #include "nSoundSource.h"
29 #include "nSoundBag.h"
30 #include "nSoundStream.h"
31 #include "nSoundStreamerPlaylist.h"
32 #include "AL/al.h"
33
34
35 const int nSS_BUFFER_SIZE = 4096;
36
37 nSoundStreamer::nSoundStreamer(QString name, nSoundSource * source, nSoundStreamerPlaylist * playlist, nSoundSystem * parent) :
38     QObject(parent)
39 {
40     setObjectName(name);
41     m_playlist = playlist;
42
43     if(!playlist->itemCount()) {
44         qWarning("nSoundStreamer has no items in playlist");
45         return;
46     }
47
48     // make sure we can stream to source
49     alGetError();
50     int sourceType;
51     alGetSourcei(source->openalHandle(), AL_SOURCE_TYPE, &sourceType);
52     if(sourceType == AL_STATIC)
53         throw QString("nSoundStreamer::nSoundStreamer(...): Tried to create stream \"")+name+("\" to an AL_STATIC source.");
54     m_source = source;
55
56     // create OpenAL buffers
57     alGetError();
58     unsigned int buffers[3];
59     alGenBuffers(3, buffers);
60     m_buffer0 = buffers[0];
61     m_buffer1 = buffers[1];
62     m_buffer2 = buffers[2];
63     if(alGetError()!=AL_NO_ERROR)
64         throw QString("nSoundStreamer::nSoundStreamer(...): Failed to create streaming buffers for \"")+name+QString("\".");
65
66
67     //reserve buffer memory
68     m_currentStream = 0;
69     nSoundStream * stream = m_playlist->m_items[m_currentStream].m_soundStream;
70     m_bag = new nSoundBag(stream->format(),
71         (stream->frames() < nSS_BUFFER_SIZE? stream->frames() : nSS_BUFFER_SIZE),
72         stream->frequency(), this);
73
74     //fill in initial data and queue buffers
75     m_keepStreaming = fillAndQueueBuffer(m_buffer0);
76     if(m_keepStreaming) m_keepStreaming = fillAndQueueBuffer(m_buffer1);
77     if(m_keepStreaming) m_keepStreaming = fillAndQueueBuffer(m_buffer2);
78
79     // start threaded updater
80     QThread * updaterThread = new QThread(0);
81     updaterThread->setObjectName(objectName() + "_THREAD");
82     m_updater = new nSoundStreamerUpdater(this);
83     m_updater->moveToThread(updaterThread);
84     updaterThread->start();
85     updaterThread->setPriority(QThread::LowPriority);
86     m_updater->setup();
87
88 }
89
90 nSoundStreamer::~nSoundStreamer()
91 {
92     if(!m_playlist->itemCount()) {
93         return;
94     }
95
96     QMutexLocker lock(&_mutex);
97
98     m_updater->_keepGoing = false;
99     alGetError();
100
101     m_source->stop();
102
103     int queuedBuffers;
104     alGetSourcei(m_source->openalHandle(), AL_BUFFERS_QUEUED, &queuedBuffers);
105     while(queuedBuffers--)
106     {
107         unsigned int buffer;
108         alSourceUnqueueBuffers(m_source->openalHandle(), 1, &buffer);
109         if(alGetError()!=AL_NO_ERROR)
110         {
111             qWarning("nSoundStreamer::~nSoundStreamer(): Failed to unqueue buffer");
112         }
113     }
114
115     unsigned int buffers[3];
116     buffers[0] = m_buffer0;
117     buffers[1] = m_buffer1;
118     buffers[2] = m_buffer2;
119     alDeleteBuffers(3, buffers);
120
121     if(alGetError()!=AL_NO_ERROR)
122         qWarning("nSoundStreamer::~nSoundStreamer(): Failed to destroy buffers.");
123
124     delete m_bag;
125 }
126
127 void nSoundStreamer::update(float frameTime)
128 {
129
130     if(!m_playlist->itemCount()) {
131         return;
132     }
133
134     QMutexLocker lock(&_mutex);
135
136     if(m_keepStreaming)
137     {
138         unsigned int sourceHandle = m_source->openalHandle();
139
140         alGetError();
141         int processedBuffers;
142         alGetSourcei(sourceHandle, AL_BUFFERS_PROCESSED, &processedBuffers);
143
144         while(processedBuffers--)
145         {
146             unsigned int buffer;
147             alSourceUnqueueBuffers(sourceHandle, 1, &buffer);
148             if(alGetError()!=AL_NO_ERROR)
149                 throw QString("nSoundStreamer::update(...): Failed to unqueue buffer.");
150
151             if(m_keepStreaming) m_keepStreaming = fillAndQueueBuffer(buffer);
152         }
153     }
154
155
156 }
157
158 void nSoundStreamer::rewind()
159 {
160     if(!m_playlist->itemCount()) {
161         return;
162     }
163
164     bool playing = m_source->state() == nSoundSource::SSS_PLAYING;
165     m_source->stop();
166     m_playlist->m_items[m_currentStream].m_soundStream->rewind();
167     m_currentStream = 0;
168
169     update(0);
170     if(playing) m_source->play();
171
172 }
173
174 bool nSoundStreamer::fillAndQueueBuffer(unsigned int buffer)
175 {
176     bool keep = true;
177
178     nSoundBag * bag = m_bag;
179     quint64 frames = bag->m_frames;
180     quint64 readFrames = 0;
181     int byteFactor = nSoundFormat_getFramesize(bag->m_format);
182
183     do
184     {
185         nSoundStream * stream = m_playlist->m_items[m_currentStream].m_soundStream;
186
187         readFrames += stream->read(m_bag->m_data+(readFrames*byteFactor), m_bag->m_frames - readFrames);
188
189         if(readFrames!=frames)
190         {
191             stream->rewind();
192             if( ! m_playlist->item(m_currentStream).m_loop)
193             {
194                 m_currentStream++;
195                 if(m_currentStream==m_playlist->m_items.size())
196                     if(m_playlist->loopPlaylist())m_currentStream = 0;
197                     else keep = false;
198             }
199         }
200     }while ( keep && (readFrames < frames));
201
202     alGetError();
203     alBufferData(buffer, openalFormat(m_bag->m_format), m_bag->m_data, readFrames*byteFactor, m_bag->m_frequency);
204     if(alGetError()!=AL_NO_ERROR)
205         throw QString("nSoundStreamer::fillAndQueueBuffer(...): Failed to refill buffer.");
206
207     alSourceQueueBuffers(m_source->openalHandle(), 1, &buffer);
208     if(alGetError()!=AL_NO_ERROR)
209         throw QString("nSoundStreamer::fillAndQueueBuffer(...): Failed to queue buffer.");
210
211     return keep;
212 }
213
214 int nSoundStreamer::openalFormat(nSoundFormat format)
215 {
216     switch(format)
217     {
218     case SF_8BIT_MONO:
219         return AL_FORMAT_MONO8;
220
221     case SF_8BIT_STEREO:
222         return AL_FORMAT_STEREO8;
223
224     case SF_16BIT_MONO:
225         return AL_FORMAT_MONO16;
226
227     case SF_16BIT_STEREO:
228         return AL_FORMAT_STEREO16;
229     }
230
231     return -1;
232 }
233
234
235 nSoundStreamerUpdater::nSoundStreamerUpdater(nSoundStreamer *parent) : QObject(0),
236     _streamer(parent),
237     _keepGoing(true)
238 {
239     startTimer(static_cast<int>(nSS_BUFFER_SIZE / 44100.0 * 1000));
240 }
241
242 nSoundStreamerUpdater::~nSoundStreamerUpdater()
243 {
244
245 }
246
247 void nSoundStreamerUpdater::setup()
248 {
249 }
250
251 void nSoundStreamerUpdater::timerEvent(QTimerEvent * evt)
252 {
253     if(_keepGoing)
254         _streamer->update(0);
255     else {
256         deleteLater();
257         QThread::currentThread()->deleteLater();
258         QThread::currentThread()->exit(0);
259     }
260 }