42 #include <QDebug> |
42 #include <QDebug> |
43 #include <QVBoxLayout> |
43 #include <QVBoxLayout> |
44 |
44 |
45 #include <QAudioOutput> |
45 #include <QAudioOutput> |
46 #include <QAudioDeviceInfo> |
46 #include <QAudioDeviceInfo> |
|
47 #include <QtCore/qmath.h> |
|
48 #include <QtCore/qendian.h> |
47 #include "audiooutput.h" |
49 #include "audiooutput.h" |
48 |
50 |
49 #ifndef M_PI |
51 const QString AudioTest::PushModeLabel(tr("Enable push mode")); |
50 #define M_PI 3.14159265358979323846 |
52 const QString AudioTest::PullModeLabel(tr("Enable pull mode")); |
51 #endif |
53 const QString AudioTest::SuspendLabel(tr("Suspend playback")); |
52 |
54 const QString AudioTest::ResumeLabel(tr("Resume playback")); |
53 #define SECONDS 1 |
55 |
54 #define FREQ 600 |
56 const int DurationSeconds = 1; |
55 #define SYSTEM_FREQ 44100 |
57 const int ToneFrequencyHz = 600; |
56 |
58 const int DataFrequencyHz = 44100; |
57 Generator::Generator(QObject *parent) |
59 const int BufferSize = 32768; |
58 :QIODevice( parent ) |
60 |
59 { |
61 |
60 finished = false; |
62 Generator::Generator(const QAudioFormat &format, |
61 buffer = new char[SECONDS*SYSTEM_FREQ*4+1000]; |
63 qint64 durationUs, |
62 t=buffer; |
64 int frequency, |
63 len=fillData(t,FREQ,SECONDS); /* mono FREQHz sine */ |
65 QObject *parent) |
64 pos = 0; |
66 : QIODevice(parent) |
65 total = len; |
67 , m_pos(0) |
|
68 { |
|
69 generateData(format, durationUs, frequency); |
66 } |
70 } |
67 |
71 |
68 Generator::~Generator() |
72 Generator::~Generator() |
69 { |
73 { |
70 delete [] buffer; |
74 |
71 } |
75 } |
72 |
76 |
73 void Generator::start() |
77 void Generator::start() |
74 { |
78 { |
75 open(QIODevice::ReadOnly); |
79 open(QIODevice::ReadOnly); |
76 } |
80 } |
77 |
81 |
78 void Generator::stop() |
82 void Generator::stop() |
79 { |
83 { |
|
84 m_pos = 0; |
80 close(); |
85 close(); |
81 } |
86 } |
82 |
87 |
83 int Generator::putShort(char *t, unsigned int value) |
88 void Generator::generateData(const QAudioFormat &format, qint64 durationUs, int frequency) |
84 { |
89 { |
85 *(unsigned char *)(t++)=value&255; |
90 const int channelBytes = format.sampleSize() / 8; |
86 *(unsigned char *)(t)=(value/256)&255; |
91 const int sampleBytes = format.channels() * channelBytes; |
87 return 2; |
92 |
88 } |
93 qint64 length = (format.frequency() * format.channels() * (format.sampleSize() / 8)) |
89 |
94 * durationUs / 100000; |
90 int Generator::fillData(char *start, int frequency, int seconds) |
95 |
91 { |
96 Q_ASSERT(length % sampleBytes == 0); |
92 int i, len=0; |
97 Q_UNUSED(sampleBytes) // suppress warning in release builds |
93 int value; |
98 |
94 for(i=0; i<seconds*SYSTEM_FREQ; i++) { |
99 m_buffer.resize(length); |
95 value=(int)(32767.0*sin(2.0*M_PI*((double)(i))*(double)(frequency)/SYSTEM_FREQ)); |
100 unsigned char *ptr = reinterpret_cast<unsigned char *>(m_buffer.data()); |
96 putShort(start, value); |
101 int sampleIndex = 0; |
97 start += 4; |
102 |
98 len+=2; |
103 while (length) { |
99 } |
104 const qreal x = qSin(2 * M_PI * frequency * qreal(sampleIndex % format.frequency()) / format.frequency()); |
100 return len; |
105 for (int i=0; i<format.channels(); ++i) { |
101 } |
106 if (format.sampleSize() == 8 && format.sampleType() == QAudioFormat::UnSignedInt) { |
102 |
107 const quint8 value = static_cast<quint8>((1.0 + x) / 2 * 255); |
103 qint64 Generator::readData(char *data, qint64 maxlen) |
108 *reinterpret_cast<quint8*>(ptr) = value; |
104 { |
109 } else if (format.sampleSize() == 8 && format.sampleType() == QAudioFormat::SignedInt) { |
105 int len = maxlen; |
110 const qint8 value = static_cast<qint8>(x * 127); |
106 if(len > 16384) |
111 *reinterpret_cast<quint8*>(ptr) = value; |
107 len = 16384; |
112 } else if (format.sampleSize() == 16 && format.sampleType() == QAudioFormat::UnSignedInt) { |
108 |
113 quint16 value = static_cast<quint16>((1.0 + x) / 2 * 65535); |
109 if(len < (SECONDS*SYSTEM_FREQ*2)-pos) { |
114 if (format.byteOrder() == QAudioFormat::LittleEndian) |
110 // Normal |
115 qToLittleEndian<quint16>(value, ptr); |
111 memcpy(data,t+pos,len); |
116 else |
112 pos+=len; |
117 qToBigEndian<quint16>(value, ptr); |
113 return len; |
118 } else if (format.sampleSize() == 16 && format.sampleType() == QAudioFormat::SignedInt) { |
114 } else { |
119 qint16 value = static_cast<qint16>(x * 32767); |
115 // Whats left and reset to start |
120 if (format.byteOrder() == QAudioFormat::LittleEndian) |
116 qint64 left = (SECONDS*SYSTEM_FREQ*2)-pos; |
121 qToLittleEndian<qint16>(value, ptr); |
117 memcpy(data,t+pos,left); |
122 else |
118 pos=0; |
123 qToBigEndian<qint16>(value, ptr); |
119 return left; |
124 } |
120 } |
125 |
|
126 ptr += channelBytes; |
|
127 length -= channelBytes; |
|
128 } |
|
129 ++sampleIndex; |
|
130 } |
|
131 } |
|
132 |
|
133 qint64 Generator::readData(char *data, qint64 len) |
|
134 { |
|
135 qint64 total = 0; |
|
136 while (len - total) { |
|
137 const qint64 chunk = qMin((m_buffer.size() - m_pos), len - total); |
|
138 memcpy(data, m_buffer.constData() + m_pos, chunk); |
|
139 m_pos = (m_pos + chunk) % m_buffer.size(); |
|
140 total += chunk; |
|
141 } |
|
142 return total; |
121 } |
143 } |
122 |
144 |
123 qint64 Generator::writeData(const char *data, qint64 len) |
145 qint64 Generator::writeData(const char *data, qint64 len) |
124 { |
146 { |
125 Q_UNUSED(data); |
147 Q_UNUSED(data); |
126 Q_UNUSED(len); |
148 Q_UNUSED(len); |
127 |
149 |
128 return 0; |
150 return 0; |
129 } |
151 } |
130 |
152 |
|
153 qint64 Generator::bytesAvailable() const |
|
154 { |
|
155 return m_buffer.size() + QIODevice::bytesAvailable(); |
|
156 } |
|
157 |
131 AudioTest::AudioTest() |
158 AudioTest::AudioTest() |
132 { |
159 : m_pullTimer(new QTimer(this)) |
133 QWidget *window = new QWidget; |
160 , m_modeButton(0) |
134 QVBoxLayout* layout = new QVBoxLayout; |
161 , m_suspendResumeButton(0) |
135 |
162 , m_deviceBox(0) |
136 deviceBox = new QComboBox(this); |
163 , m_device(QAudioDeviceInfo::defaultOutputDevice()) |
|
164 , m_generator(0) |
|
165 , m_audioOutput(0) |
|
166 , m_output(0) |
|
167 , m_buffer(BufferSize, 0) |
|
168 { |
|
169 initializeWindow(); |
|
170 initializeAudio(); |
|
171 } |
|
172 |
|
173 void AudioTest::initializeWindow() |
|
174 { |
|
175 QScopedPointer<QWidget> window(new QWidget); |
|
176 QScopedPointer<QVBoxLayout> layout(new QVBoxLayout); |
|
177 |
|
178 m_deviceBox = new QComboBox(this); |
137 foreach (const QAudioDeviceInfo &deviceInfo, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) |
179 foreach (const QAudioDeviceInfo &deviceInfo, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) |
138 deviceBox->addItem(deviceInfo.deviceName(), qVariantFromValue(deviceInfo)); |
180 m_deviceBox->addItem(deviceInfo.deviceName(), qVariantFromValue(deviceInfo)); |
139 connect(deviceBox,SIGNAL(activated(int)),SLOT(deviceChanged(int))); |
181 connect(m_deviceBox,SIGNAL(activated(int)),SLOT(deviceChanged(int))); |
140 layout->addWidget(deviceBox); |
182 layout->addWidget(m_deviceBox); |
141 |
183 |
142 button = new QPushButton(this); |
184 m_modeButton = new QPushButton(this); |
143 button->setText(tr("Click for Push Mode")); |
185 m_modeButton->setText(PushModeLabel); |
144 connect(button,SIGNAL(clicked()),SLOT(toggle())); |
186 connect(m_modeButton, SIGNAL(clicked()), SLOT(toggleMode())); |
145 layout->addWidget(button); |
187 layout->addWidget(m_modeButton); |
146 |
188 |
147 button2 = new QPushButton(this); |
189 m_suspendResumeButton = new QPushButton(this); |
148 button2->setText(tr("Click To Suspend")); |
190 m_suspendResumeButton->setText(SuspendLabel); |
149 connect(button2,SIGNAL(clicked()),SLOT(togglePlay())); |
191 connect(m_suspendResumeButton, SIGNAL(clicked()), SLOT(toggleSuspendResume())); |
150 layout->addWidget(button2); |
192 layout->addWidget(m_suspendResumeButton); |
151 |
193 |
152 window->setLayout(layout); |
194 window->setLayout(layout.data()); |
153 setCentralWidget(window); |
195 layout.take(); // ownership transferred |
154 window->show(); |
196 |
155 |
197 setCentralWidget(window.data()); |
156 buffer = new char[BUFFER_SIZE]; |
198 QWidget *const windowPtr = window.take(); // ownership transferred |
157 |
199 windowPtr->show(); |
158 gen = new Generator(this); |
200 } |
159 |
201 |
160 pullMode = true; |
202 void AudioTest::initializeAudio() |
161 |
203 { |
162 timer = new QTimer(this); |
204 connect(m_pullTimer, SIGNAL(timeout()), SLOT(pullTimerExpired())); |
163 connect(timer,SIGNAL(timeout()),SLOT(writeMore())); |
205 |
164 |
206 m_pullMode = true; |
165 gen->start(); |
207 |
166 |
208 m_format.setFrequency(DataFrequencyHz); |
167 settings.setFrequency(SYSTEM_FREQ); |
209 m_format.setChannels(1); |
168 settings.setChannels(1); |
210 m_format.setSampleSize(16); |
169 settings.setSampleSize(16); |
211 m_format.setCodec("audio/pcm"); |
170 settings.setCodec("audio/pcm"); |
212 m_format.setByteOrder(QAudioFormat::LittleEndian); |
171 settings.setByteOrder(QAudioFormat::LittleEndian); |
213 m_format.setSampleType(QAudioFormat::SignedInt); |
172 settings.setSampleType(QAudioFormat::SignedInt); |
|
173 |
214 |
174 QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); |
215 QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); |
175 if (!info.isFormatSupported(settings)) { |
216 if (!info.isFormatSupported(m_format)) { |
176 qWarning()<<"default format not supported try to use nearest"; |
217 qWarning() << "Default format not supported - trying to use nearest"; |
177 settings = info.nearestFormat(settings); |
218 m_format = info.nearestFormat(m_format); |
178 } |
219 } |
179 |
220 |
180 if(settings.sampleSize() != 16) { |
221 m_generator = new Generator(m_format, DurationSeconds*1000000, ToneFrequencyHz, this); |
181 qWarning()<<"audio device doesn't support 16 bit samples, example cannot run"; |
222 |
182 return; |
223 createAudioOutput(); |
183 } |
224 } |
184 |
225 |
185 audioOutput = new QAudioOutput(settings,this); |
226 void AudioTest::createAudioOutput() |
186 connect(audioOutput,SIGNAL(notify()),SLOT(status())); |
227 { |
187 connect(audioOutput,SIGNAL(stateChanged(QAudio::State)),SLOT(state(QAudio::State))); |
228 delete m_audioOutput; |
188 |
229 m_audioOutput = 0; |
189 audioOutput->start(gen); |
230 m_audioOutput = new QAudioOutput(m_device, m_format, this); |
|
231 connect(m_audioOutput, SIGNAL(notify()), SLOT(notified())); |
|
232 connect(m_audioOutput, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State))); |
|
233 m_generator->start(); |
|
234 m_audioOutput->start(m_generator); |
190 } |
235 } |
191 |
236 |
192 AudioTest::~AudioTest() |
237 AudioTest::~AudioTest() |
193 { |
238 { |
194 delete [] buffer; |
239 |
195 } |
240 } |
196 |
241 |
197 void AudioTest::deviceChanged(int idx) |
242 void AudioTest::deviceChanged(int index) |
198 { |
243 { |
199 timer->stop(); |
244 m_pullTimer->stop(); |
200 gen->stop(); |
245 m_generator->stop(); |
201 audioOutput->stop(); |
246 m_audioOutput->stop(); |
202 audioOutput->disconnect(this); |
247 m_audioOutput->disconnect(this); |
203 delete audioOutput; |
248 m_device = m_deviceBox->itemData(index).value<QAudioDeviceInfo>(); |
204 |
249 createAudioOutput(); |
205 device = deviceBox->itemData(idx).value<QAudioDeviceInfo>(); |
250 } |
206 audioOutput = new QAudioOutput(device,settings,this); |
251 |
207 connect(audioOutput,SIGNAL(notify()),SLOT(status())); |
252 void AudioTest::notified() |
208 connect(audioOutput,SIGNAL(stateChanged(QAudio::State)),SLOT(state(QAudio::State))); |
253 { |
209 gen->start(); |
254 qWarning() << "bytesFree = " << m_audioOutput->bytesFree() |
210 audioOutput->start(gen); |
255 << ", " << "elapsedUSecs = " << m_audioOutput->elapsedUSecs() |
211 } |
256 << ", " << "processedUSecs = " << m_audioOutput->processedUSecs(); |
212 |
257 } |
213 void AudioTest::status() |
258 |
214 { |
259 void AudioTest::pullTimerExpired() |
215 qWarning()<<"byteFree = "<<audioOutput->bytesFree()<<" bytes, elapsedUSecs = "<<audioOutput->elapsedUSecs()<<", processedUSecs = "<<audioOutput->processedUSecs(); |
260 { |
216 } |
261 if (m_audioOutput && m_audioOutput->state() != QAudio::StoppedState) { |
217 |
262 int chunks = m_audioOutput->bytesFree()/m_audioOutput->periodSize(); |
218 void AudioTest::writeMore() |
263 while (chunks) { |
219 { |
264 const qint64 len = m_generator->read(m_buffer.data(), m_audioOutput->periodSize()); |
220 if(!audioOutput) |
265 if (len) |
221 return; |
266 m_output->write(m_buffer.data(), len); |
222 |
267 if (len != m_audioOutput->periodSize()) |
223 if(audioOutput->state() == QAudio::StoppedState) |
268 break; |
224 return; |
269 --chunks; |
225 |
270 } |
226 int l; |
271 } |
227 int out; |
272 } |
228 |
273 |
229 int chunks = audioOutput->bytesFree()/audioOutput->periodSize(); |
274 void AudioTest::toggleMode() |
230 while(chunks) { |
275 { |
231 l = gen->read(buffer,audioOutput->periodSize()); |
276 m_pullTimer->stop(); |
232 if(l > 0) |
277 m_audioOutput->stop(); |
233 out = output->write(buffer,l); |
278 |
234 if(l != audioOutput->periodSize()) |
279 if (m_pullMode) { |
235 break; |
280 m_modeButton->setText(PullModeLabel); |
236 chunks--; |
281 m_output = m_audioOutput->start(); |
237 } |
282 m_pullMode = false; |
238 } |
283 m_pullTimer->start(20); |
239 |
|
240 void AudioTest::toggle() |
|
241 { |
|
242 // Change between pull and push modes |
|
243 |
|
244 timer->stop(); |
|
245 audioOutput->stop(); |
|
246 |
|
247 if (pullMode) { |
|
248 button->setText("Click for Pull Mode"); |
|
249 output = audioOutput->start(); |
|
250 pullMode = false; |
|
251 timer->start(20); |
|
252 } else { |
284 } else { |
253 button->setText("Click for Push Mode"); |
285 m_modeButton->setText(PushModeLabel); |
254 pullMode = true; |
286 m_pullMode = true; |
255 audioOutput->start(gen); |
287 m_audioOutput->start(m_generator); |
256 } |
288 } |
257 } |
289 |
258 |
290 m_suspendResumeButton->setText(SuspendLabel); |
259 void AudioTest::togglePlay() |
291 } |
260 { |
292 |
261 // toggle suspend/resume |
293 void AudioTest::toggleSuspendResume() |
262 if(audioOutput->state() == QAudio::SuspendedState) { |
294 { |
263 qWarning()<<"status: Suspended, resume()"; |
295 if (m_audioOutput->state() == QAudio::SuspendedState) { |
264 audioOutput->resume(); |
296 qWarning() << "status: Suspended, resume()"; |
265 button2->setText("Click To Suspend"); |
297 m_audioOutput->resume(); |
266 } else if (audioOutput->state() == QAudio::ActiveState) { |
298 m_suspendResumeButton->setText(SuspendLabel); |
267 qWarning()<<"status: Active, suspend()"; |
299 } else if (m_audioOutput->state() == QAudio::ActiveState) { |
268 audioOutput->suspend(); |
300 qWarning() << "status: Active, suspend()"; |
269 button2->setText("Click To Resume"); |
301 m_audioOutput->suspend(); |
270 } else if (audioOutput->state() == QAudio::StoppedState) { |
302 m_suspendResumeButton->setText(ResumeLabel); |
271 qWarning()<<"status: Stopped, resume()"; |
303 } else if (m_audioOutput->state() == QAudio::StoppedState) { |
272 audioOutput->resume(); |
304 qWarning() << "status: Stopped, resume()"; |
273 button2->setText("Click To Suspend"); |
305 m_audioOutput->resume(); |
274 } else if (audioOutput->state() == QAudio::IdleState) { |
306 m_suspendResumeButton->setText(SuspendLabel); |
275 qWarning()<<"status: IdleState"; |
307 } else if (m_audioOutput->state() == QAudio::IdleState) { |
276 } |
308 qWarning() << "status: IdleState"; |
277 } |
309 } |
278 |
310 } |
279 void AudioTest::state(QAudio::State state) |
311 |
280 { |
312 void AudioTest::stateChanged(QAudio::State state) |
281 qWarning()<<" state="<<state; |
313 { |
282 } |
314 qWarning() << "state = " << state; |
|
315 } |