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