Commit | Line | Data |
---|---|---|
23e7e3ae JVH |
1 | /** |
2 | * Copyright (C) 2011-2012 Juho Vähä-Herttua | |
3 | * | |
4 | * This library is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU Lesser General Public | |
6 | * License as published by the Free Software Foundation; either | |
7 | * version 2.1 of the License, or (at your option) any later version. | |
8 | * | |
9 | * This library is distributed in the hope that it will be useful, | |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | * Lesser General Public License for more details. | |
13 | */ | |
14 | ||
2340bcd3 JVH |
15 | #include "audiooutput.h" |
16 | ||
17 | #include <QDebug> | |
18 | #include <QtEndian> | |
19 | #include <math.h> | |
20 | ||
21 | #define BUFFER_SIZE (64*1024) | |
22 | ||
23 | AudioOutput::AudioOutput(QObject *parent) : | |
24 | QIODevice(parent), | |
25 | m_initialized(false), | |
26 | m_output(0), | |
27 | m_volume(0.0f) | |
28 | { | |
29 | } | |
30 | ||
31 | bool AudioOutput::init(int bits, int channels, int samplerate) | |
32 | { | |
33 | if (m_initialized) { | |
34 | return false; | |
35 | } | |
36 | if (bits != 16) { | |
37 | return false; | |
38 | } | |
39 | ||
40 | m_format.setSampleSize(bits); | |
41 | m_format.setChannels(channels); | |
42 | m_format.setFrequency(samplerate); | |
43 | m_format.setCodec("audio/pcm"); | |
44 | m_format.setByteOrder(QAudioFormat::LittleEndian); | |
45 | m_format.setSampleType(QAudioFormat::SignedInt); | |
46 | ||
47 | m_initialized = setDevice(QAudioDeviceInfo::defaultOutputDevice()); | |
48 | return m_initialized; | |
49 | } | |
50 | ||
51 | bool AudioOutput::setDevice(QAudioDeviceInfo deviceInfo) | |
52 | { | |
53 | if (!deviceInfo.isFormatSupported(m_format)) { | |
54 | qDebug() << "Format not supported!"; | |
55 | return false; | |
56 | } | |
57 | m_deviceInfo = deviceInfo; | |
58 | this->reinit(); | |
59 | return true; | |
60 | } | |
61 | ||
62 | void AudioOutput::reinit() | |
63 | { | |
64 | bool running = false; | |
65 | if (m_output && m_output->state() != QAudio::StoppedState) { | |
66 | running = true; | |
67 | } | |
68 | this->stop(); | |
69 | ||
70 | // Reinitialize audio output | |
71 | delete m_output; | |
72 | m_output = 0; | |
73 | m_output = new QAudioOutput(m_deviceInfo, m_format, this); | |
74 | ||
75 | // Set constant values to new audio output | |
76 | connect(m_output, SIGNAL(notify()), SLOT(notified())); | |
77 | connect(m_output, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State))); | |
78 | if (running) { | |
79 | this->start(); | |
80 | } | |
81 | } | |
82 | ||
83 | void AudioOutput::start() | |
84 | { | |
85 | if (m_output == 0 || m_output->state() != QAudio::StoppedState) { | |
86 | return; | |
87 | } | |
88 | this->open(QIODevice::ReadOnly); | |
89 | m_buffer.clear(); | |
90 | m_output->start(this); | |
91 | m_output->suspend(); | |
92 | } | |
93 | ||
94 | void AudioOutput::setVolume(float volume) | |
95 | { | |
96 | m_volume = volume; | |
97 | } | |
98 | ||
9434b30c | 99 | void AudioOutput::output(const QByteArray & data) |
2340bcd3 JVH |
100 | { |
101 | if (m_output && m_output->state() != QAudio::StoppedState) { | |
102 | // Append input data to the end of buffer | |
9434b30c | 103 | m_buffer.append(data); |
2340bcd3 JVH |
104 | |
105 | // Check if our buffer has grown too large | |
106 | if (m_buffer.length() > 2*BUFFER_SIZE) { | |
107 | // There could be a better way to handle this | |
108 | this->flush(); | |
109 | } | |
110 | ||
111 | // If audio is suspended and buffer is full, resume | |
112 | if (m_output->state() == QAudio::SuspendedState) { | |
113 | if (m_buffer.length() >= BUFFER_SIZE) { | |
114 | qDebug() << "Resuming..."; | |
115 | m_output->resume(); | |
116 | } | |
117 | } | |
118 | } | |
119 | } | |
120 | ||
121 | void AudioOutput::flush() | |
122 | { | |
123 | // Flushing buffers is a bit tricky... | |
124 | // Don't modify this unless you're sure | |
125 | this->stop(); | |
126 | m_output->reset(); | |
127 | this->start(); | |
128 | } | |
129 | ||
130 | void AudioOutput::stop() | |
131 | { | |
132 | if (m_output && m_output->state() != QAudio::StoppedState) { | |
133 | // Stop audio output | |
134 | m_output->stop(); | |
135 | m_buffer.clear(); | |
136 | this->close(); | |
137 | } | |
138 | } | |
139 | ||
140 | static void apply_s16le_volume(float volume, uchar *data, int datalen) | |
141 | { | |
142 | int samples = datalen/2; | |
143 | float mult = pow(10.0,0.05*volume); | |
144 | ||
145 | for (int i=0; i<samples; i++) { | |
146 | qint16 val = qFromLittleEndian<qint16>(data+i*2)*mult; | |
147 | qToLittleEndian<qint16>(val, data+i*2); | |
148 | } | |
149 | } | |
150 | ||
151 | qint64 AudioOutput::readData(char *data, qint64 maxlen) | |
152 | { | |
153 | // Calculate output length, always full samples | |
154 | int outlen = qMin(m_buffer.length(), (int)maxlen); | |
155 | if (outlen%2 != 0) { | |
156 | outlen += 1; | |
157 | } | |
158 | ||
159 | memcpy(data, m_buffer.data(), outlen); | |
160 | apply_s16le_volume(m_volume, (uchar *)data, outlen); | |
161 | m_buffer.remove(0, outlen); | |
162 | return outlen; | |
163 | } | |
164 | ||
165 | qint64 AudioOutput::writeData(const char *data, qint64 len) | |
166 | { | |
167 | Q_UNUSED(data); | |
168 | Q_UNUSED(len); | |
169 | ||
170 | return 0; | |
171 | } | |
172 | ||
173 | qint64 AudioOutput::bytesAvailable() const | |
174 | { | |
175 | return m_buffer.length() + QIODevice::bytesAvailable(); | |
176 | } | |
177 | ||
178 | bool AudioOutput::isSequential() const | |
179 | { | |
180 | return true; | |
181 | } | |
182 | ||
183 | void AudioOutput::notified() | |
184 | { | |
185 | } | |
186 | ||
187 | void AudioOutput::stateChanged(QAudio::State state) | |
188 | { | |
189 | // Start buffering again in case of underrun... | |
190 | // Required on Windows, otherwise it stalls idle | |
191 | if (state == QAudio::IdleState && m_output->error() == QAudio::UnderrunError) { | |
192 | // This check is required, because Mac OS X underruns often | |
193 | if (m_buffer.length() < BUFFER_SIZE) { | |
194 | m_output->suspend(); | |
195 | } | |
196 | } | |
197 | qWarning() << "state = " << state; | |
198 | } |