* Fix a typo in the dpkg changelog.
[deb_shairplay.git] / AirTV-Qt / audiooutput.cpp
CommitLineData
15c988f7
JB
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
15#include "audiooutput.h"
16
17#include <QDebug>
18#include <QtEndian>
19#include <math.h>
20
21#define BUFFER_SIZE (64*1024)
22
23AudioOutput::AudioOutput(QObject *parent) :
24 QIODevice(parent),
25 m_initialized(false),
26 m_output(0),
27 m_volume(0.0f)
28{
29}
30
31bool 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
51bool 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
62void 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
83void 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
94void AudioOutput::setVolume(float volume)
95{
96 m_volume = volume;
97}
98
99void AudioOutput::output(const QByteArray & data)
100{
101 if (m_output && m_output->state() != QAudio::StoppedState) {
102 // Append input data to the end of buffer
103 m_buffer.append(data);
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
121void 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
130void 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
140static 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
151qint64 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
165qint64 AudioOutput::writeData(const char *data, qint64 len)
166{
167 Q_UNUSED(data);
168 Q_UNUSED(len);
169
170 return 0;
171}
172
173qint64 AudioOutput::bytesAvailable() const
174{
175 return m_buffer.length() + QIODevice::bytesAvailable();
176}
177
178bool AudioOutput::isSequential() const
179{
180 return true;
181}
182
183void AudioOutput::notified()
184{
185}
186
187void 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}