Initial commit to the repository
[deb_shairplay.git] / AirTV-Qt / audiooutput.cpp
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 }