Add some really ugly scons scripts to compile libraries
[deb_shairplay.git] / AirTV-Qt / audiooutput.cpp
CommitLineData
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
9AudioOutput::AudioOutput(QObject *parent) :
10 QIODevice(parent),
11 m_initialized(false),
12 m_output(0),
13 m_volume(0.0f)
14{
15}
16
17bool 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
37bool 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
48void 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
69void 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
80void AudioOutput::setVolume(float volume)
81{
82 m_volume = volume;
83}
84
85void 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
107void 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
116void 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
126static 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
137qint64 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
151qint64 AudioOutput::writeData(const char *data, qint64 len)
152{
153 Q_UNUSED(data);
154 Q_UNUSED(len);
155
156 return 0;
157}
158
159qint64 AudioOutput::bytesAvailable() const
160{
161 return m_buffer.length() + QIODevice::bytesAvailable();
162}
163
164bool AudioOutput::isSequential() const
165{
166 return true;
167}
168
169void AudioOutput::notified()
170{
171}
172
173void 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}