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