juhovh-shairplay/AirTV-Qt/audiooutput.cpp

199 lines
4.9 KiB
C++

/**
* Copyright (C) 2011-2012 Juho Vähä-Herttua
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
#include "audiooutput.h"
#include <QDebug>
#include <QtEndian>
#include <math.h>
#define BUFFER_SIZE (64*1024)
AudioOutput::AudioOutput(QObject *parent) :
QIODevice(parent),
m_initialized(false),
m_output(0),
m_volume(0.0f)
{
}
bool AudioOutput::init(int bits, int channels, int samplerate)
{
if (m_initialized) {
return false;
}
if (bits != 16) {
return false;
}
m_format.setSampleSize(bits);
m_format.setChannels(channels);
m_format.setFrequency(samplerate);
m_format.setCodec("audio/pcm");
m_format.setByteOrder(QAudioFormat::LittleEndian);
m_format.setSampleType(QAudioFormat::SignedInt);
m_initialized = setDevice(QAudioDeviceInfo::defaultOutputDevice());
return m_initialized;
}
bool AudioOutput::setDevice(QAudioDeviceInfo deviceInfo)
{
if (!deviceInfo.isFormatSupported(m_format)) {
qDebug() << "Format not supported!";
return false;
}
m_deviceInfo = deviceInfo;
this->reinit();
return true;
}
void AudioOutput::reinit()
{
bool running = false;
if (m_output && m_output->state() != QAudio::StoppedState) {
running = true;
}
this->stop();
// Reinitialize audio output
delete m_output;
m_output = 0;
m_output = new QAudioOutput(m_deviceInfo, m_format, this);
// Set constant values to new audio output
connect(m_output, SIGNAL(notify()), SLOT(notified()));
connect(m_output, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State)));
if (running) {
this->start();
}
}
void AudioOutput::start()
{
if (m_output == 0 || m_output->state() != QAudio::StoppedState) {
return;
}
this->open(QIODevice::ReadOnly);
m_buffer.clear();
m_output->start(this);
m_output->suspend();
}
void AudioOutput::setVolume(float volume)
{
m_volume = volume;
}
void AudioOutput::output(const QByteArray & data)
{
if (m_output && m_output->state() != QAudio::StoppedState) {
// Append input data to the end of buffer
m_buffer.append(data);
// Check if our buffer has grown too large
if (m_buffer.length() > 2*BUFFER_SIZE) {
// There could be a better way to handle this
this->flush();
}
// If audio is suspended and buffer is full, resume
if (m_output->state() == QAudio::SuspendedState) {
if (m_buffer.length() >= BUFFER_SIZE) {
qDebug() << "Resuming...";
m_output->resume();
}
}
}
}
void AudioOutput::flush()
{
// Flushing buffers is a bit tricky...
// Don't modify this unless you're sure
this->stop();
m_output->reset();
this->start();
}
void AudioOutput::stop()
{
if (m_output && m_output->state() != QAudio::StoppedState) {
// Stop audio output
m_output->stop();
m_buffer.clear();
this->close();
}
}
static void apply_s16le_volume(float volume, uchar *data, int datalen)
{
int samples = datalen/2;
float mult = pow(10.0,0.05*volume);
for (int i=0; i<samples; i++) {
qint16 val = qFromLittleEndian<qint16>(data+i*2)*mult;
qToLittleEndian<qint16>(val, data+i*2);
}
}
qint64 AudioOutput::readData(char *data, qint64 maxlen)
{
// Calculate output length, always full samples
int outlen = qMin(m_buffer.length(), (int)maxlen);
if (outlen%2 != 0) {
outlen += 1;
}
memcpy(data, m_buffer.data(), outlen);
apply_s16le_volume(m_volume, (uchar *)data, outlen);
m_buffer.remove(0, outlen);
return outlen;
}
qint64 AudioOutput::writeData(const char *data, qint64 len)
{
Q_UNUSED(data);
Q_UNUSED(len);
return 0;
}
qint64 AudioOutput::bytesAvailable() const
{
return m_buffer.length() + QIODevice::bytesAvailable();
}
bool AudioOutput::isSequential() const
{
return true;
}
void AudioOutput::notified()
{
}
void AudioOutput::stateChanged(QAudio::State state)
{
// Start buffering again in case of underrun...
// Required on Windows, otherwise it stalls idle
if (state == QAudio::IdleState && m_output->error() == QAudio::UnderrunError) {
// This check is required, because Mac OS X underruns often
if (m_buffer.length() < BUFFER_SIZE) {
m_output->suspend();
}
}
qWarning() << "state = " << state;
}