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 
AudioOutput(QObject * parent)23 AudioOutput::AudioOutput(QObject *parent) :
24     QIODevice(parent),
25     m_initialized(false),
26     m_output(0),
27     m_volume(0.0f)
28 {
29 }
30 
init(int bits,int channels,int samplerate)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 
setDevice(QAudioDeviceInfo deviceInfo)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 
reinit()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 
start()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 
setVolume(float volume)94 void AudioOutput::setVolume(float volume)
95 {
96     m_volume = volume;
97 }
98 
output(const QByteArray & data)99 void 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 
flush()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 
stop()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 
apply_s16le_volume(float volume,uchar * data,int datalen)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 
readData(char * data,qint64 maxlen)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 
writeData(const char * data,qint64 len)165 qint64 AudioOutput::writeData(const char *data, qint64 len)
166 {
167     Q_UNUSED(data);
168     Q_UNUSED(len);
169 
170     return 0;
171 }
172 
bytesAvailable() const173 qint64 AudioOutput::bytesAvailable() const
174 {
175     return m_buffer.length() + QIODevice::bytesAvailable();
176 }
177 
isSequential() const178 bool AudioOutput::isSequential() const
179 {
180     return true;
181 }
182 
notified()183 void AudioOutput::notified()
184 {
185 }
186 
stateChanged(QAudio::State state)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 }
199