1 #include "soundout.h"
2 
3 #include <QDateTime>
4 #include <QAudioDeviceInfo>
5 #include <QAudioOutput>
6 #include <QSysInfo>
7 #include <qmath.h>
8 #include <QDebug>
9 
10 #include "Logger.hpp"
11 #include "Audio/AudioDevice.hpp"
12 
13 #include "moc_soundout.cpp"
14 
checkStream() const15 bool SoundOutput::checkStream () const
16 {
17   bool result {false};
18 
19   Q_ASSERT_X (m_stream, "SoundOutput", "programming error");
20   if (m_stream) {
21     switch (m_stream->error ())
22       {
23       case QAudio::OpenError:
24         Q_EMIT error (tr ("An error opening the audio output device has occurred."));
25         break;
26 
27       case QAudio::IOError:
28         Q_EMIT error (tr ("An error occurred during write to the audio output device."));
29         break;
30 
31       case QAudio::UnderrunError:
32         Q_EMIT error (tr ("Audio data not being fed to the audio output device fast enough."));
33         break;
34 
35       case QAudio::FatalError:
36         Q_EMIT error (tr ("Non-recoverable error, audio output device not usable at this time."));
37         break;
38 
39       case QAudio::NoError:
40         result = true;
41         break;
42       }
43   }
44   return result;
45 }
46 
setFormat(QAudioDeviceInfo const & device,unsigned channels,int frames_buffered)47 void SoundOutput::setFormat (QAudioDeviceInfo const& device, unsigned channels, int frames_buffered)
48 {
49   Q_ASSERT (0 < channels && channels < 3);
50   m_device = device;
51   m_channels = channels;
52   m_framesBuffered = frames_buffered;
53 }
54 
restart(QIODevice * source)55 void SoundOutput::restart (QIODevice * source)
56 {
57   if (!m_device.isNull ())
58     {
59       QAudioFormat format (m_device.preferredFormat ());
60       //  qDebug () << "Preferred audio output format:" << format;
61       format.setChannelCount (m_channels);
62       format.setCodec ("audio/pcm");
63       format.setSampleRate (48000);
64       format.setSampleType (QAudioFormat::SignedInt);
65       format.setSampleSize (16);
66       format.setByteOrder (QAudioFormat::Endian (QSysInfo::ByteOrder));
67       if (!format.isValid ())
68         {
69           Q_EMIT error (tr ("Requested output audio format is not valid."));
70         }
71       else if (!m_device.isFormatSupported (format))
72         {
73           Q_EMIT error (tr ("Requested output audio format is not supported on device."));
74         }
75       else
76         {
77           // qDebug () << "Selected audio output format:" << format;
78           m_stream.reset (new QAudioOutput (m_device, format));
79           checkStream ();
80           m_stream->setVolume (m_volume);
81           m_stream->setNotifyInterval(1000);
82           error_ = false;
83 
84           connect (m_stream.data(), &QAudioOutput::stateChanged, this, &SoundOutput::handleStateChanged);
85           connect (m_stream.data(), &QAudioOutput::notify, [this] () {checkStream ();});
86 
87           //      qDebug() << "A" << m_volume << m_stream->notifyInterval();
88         }
89     }
90   if (!m_stream)
91     {
92       if (!error_)
93         {
94           error_ = true;        // only signal error once
95           Q_EMIT error (tr ("No audio output device configured."));
96         }
97       return;
98     }
99   else
100     {
101       error_ = false;
102     }
103 
104   // we have to set this before every start on the stream because the
105   // Windows implementation seems to forget the buffer size after a
106   // stop.
107   //qDebug () << "SoundOut default buffer size (bytes):" << m_stream->bufferSize () << "period size:" << m_stream->periodSize ();
108   if (m_framesBuffered > 0)
109     {
110       m_stream->setBufferSize (m_stream->format().bytesForFrames (m_framesBuffered));
111     }
112   m_stream->setCategory ("production");
113   m_stream->start (source);
114   LOG_DEBUG ("Selected buffer size (bytes): " << m_stream->bufferSize () << " period size: " << m_stream->periodSize ());
115 }
116 
suspend()117 void SoundOutput::suspend ()
118 {
119   if (m_stream && QAudio::ActiveState == m_stream->state ())
120     {
121       m_stream->suspend ();
122       checkStream ();
123     }
124 }
125 
resume()126 void SoundOutput::resume ()
127 {
128   if (m_stream && QAudio::SuspendedState == m_stream->state ())
129     {
130       m_stream->resume ();
131       checkStream ();
132     }
133 }
134 
reset()135 void SoundOutput::reset ()
136 {
137   if (m_stream)
138     {
139       m_stream->reset ();
140       checkStream ();
141     }
142 }
143 
stop()144 void SoundOutput::stop ()
145 {
146   if (m_stream)
147     {
148       m_stream->reset ();
149       m_stream->stop ();
150     }
151   m_stream.reset ();
152 }
153 
attenuation() const154 qreal SoundOutput::attenuation () const
155 {
156   return -(20. * qLn (m_volume) / qLn (10.));
157 }
158 
setAttenuation(qreal a)159 void SoundOutput::setAttenuation (qreal a)
160 {
161   Q_ASSERT (0. <= a && a <= 999.);
162   m_volume = qPow(10.0, -a/20.0);
163   //  qDebug () << "SoundOut: attn = " << a << ", vol = " << m_volume;
164   if (m_stream)
165     {
166       m_stream->setVolume (m_volume);
167     }
168 }
169 
resetAttenuation()170 void SoundOutput::resetAttenuation ()
171 {
172   m_volume = 1.;
173   if (m_stream)
174     {
175       m_stream->setVolume (m_volume);
176     }
177 }
178 
handleStateChanged(QAudio::State newState)179 void SoundOutput::handleStateChanged (QAudio::State newState)
180 {
181   switch (newState)
182     {
183     case QAudio::IdleState:
184       Q_EMIT status (tr ("Idle"));
185       break;
186 
187     case QAudio::ActiveState:
188       Q_EMIT status (tr ("Sending"));
189       break;
190 
191     case QAudio::SuspendedState:
192       Q_EMIT status (tr ("Suspended"));
193       break;
194 
195 #if QT_VERSION >= QT_VERSION_CHECK (5, 10, 0)
196     case QAudio::InterruptedState:
197       Q_EMIT status (tr ("Interrupted"));
198       break;
199 #endif
200 
201     case QAudio::StoppedState:
202       if (!checkStream ())
203         {
204           Q_EMIT status (tr ("Error"));
205         }
206       else
207         {
208           Q_EMIT status (tr ("Stopped"));
209         }
210       break;
211     }
212 }
213