diff options
| author | trialuser02 <trialuser02@90c681e8-e032-0410-971d-27865f9a5e38> | 2014-10-06 07:50:56 +0000 |
|---|---|---|
| committer | trialuser02 <trialuser02@90c681e8-e032-0410-971d-27865f9a5e38> | 2014-10-06 07:50:56 +0000 |
| commit | 76f436c36cdec74e73c12cea8d81051a7d75ec96 (patch) | |
| tree | 03fe155b5311f6dccbf0dfb2a9cb0954484a138c /src | |
| parent | 67e825df80708f31daf7a4f4cd6045ecf010efa6 (diff) | |
| download | qmmp-76f436c36cdec74e73c12cea8d81051a7d75ec96.tar.gz qmmp-76f436c36cdec74e73c12cea8d81051a7d75ec96.tar.bz2 qmmp-76f436c36cdec74e73c12cea8d81051a7d75ec96.zip | |
added multichannel support
git-svn-id: http://svn.code.sf.net/p/qmmp-dev/code/trunk/qmmp@4530 90c681e8-e032-0410-971d-27865f9a5e38
Diffstat (limited to 'src')
31 files changed, 588 insertions, 176 deletions
diff --git a/src/plugins/Effect/Effect.pro b/src/plugins/Effect/Effect.pro index 336647a4b..b073f08f3 100644 --- a/src/plugins/Effect/Effect.pro +++ b/src/plugins/Effect/Effect.pro @@ -1,15 +1,15 @@ include (../../../qmmp.pri) TEMPLATE = subdirs -SUBDIRS += crossfade stereo +#SUBDIRS += crossfade stereo contains(CONFIG, BS2B_PLUGIN){ -SUBDIRS += bs2b +#SUBDIRS += bs2b } unix { SUBDIRS += srconverter contains(CONFIG, LADSPA_PLUGIN){ - SUBDIRS += ladspa +# SUBDIRS += ladspa } } diff --git a/src/plugins/Effect/srconverter/srconverter.cpp b/src/plugins/Effect/srconverter/srconverter.cpp index fcd77bc04..324dbb051 100644 --- a/src/plugins/Effect/srconverter/srconverter.cpp +++ b/src/plugins/Effect/srconverter/srconverter.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2007-2013 by Ilya Kotov * + * Copyright (C) 2007-2014 by Ilya Kotov * * forkotov02@hotmail.ru * * * * This program is free software; you can redistribute it and/or modify * @@ -115,12 +115,12 @@ void SRConverter::applyEffect(Buffer *b) } } -void SRConverter::configure(quint32 freq, int chan, Qmmp::AudioFormat format) +void SRConverter::configure(quint32 freq, ChannelMap map, Qmmp::AudioFormat format) { freeSRC(); if(freq != m_overSamplingFs && format != Qmmp::PCM_S8) { - m_src_state = src_new(m_converter_type, chan, &m_srcError); + m_src_state = src_new(m_converter_type, map.count(), &m_srcError); if (m_src_state) { m_src_data.src_ratio = (float)m_overSamplingFs/(float)freq; @@ -129,7 +129,7 @@ void SRConverter::configure(quint32 freq, int chan, Qmmp::AudioFormat format) else qDebug("SRConverter: src_new(): %s", src_strerror(m_srcError)); } - Effect::configure(m_overSamplingFs, chan, format); + Effect::configure(m_overSamplingFs, map, format); m_sz = audioParameters().sampleSize(); } diff --git a/src/plugins/Effect/srconverter/srconverter.h b/src/plugins/Effect/srconverter/srconverter.h index f25ebc491..b214c866f 100644 --- a/src/plugins/Effect/srconverter/srconverter.h +++ b/src/plugins/Effect/srconverter/srconverter.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2007-2013 by Ilya Kotov * + * Copyright (C) 2007-2014 by Ilya Kotov * * forkotov02@hotmail.ru * * * * This program is free software; you can redistribute it and/or modify * @@ -34,7 +34,7 @@ public: virtual ~SRConverter(); void applyEffect(Buffer *b); - void configure(quint32 freq, int chan, Qmmp::AudioFormat format); + void configure(quint32 freq, ChannelMap map, Qmmp::AudioFormat format); private: void freeSRC(); diff --git a/src/plugins/Input/Input.pro b/src/plugins/Input/Input.pro index 9b00864e6..0149aa949 100644 --- a/src/plugins/Input/Input.pro +++ b/src/plugins/Input/Input.pro @@ -1,53 +1,53 @@ include(../../../qmmp.pri) -SUBDIRS += mad cue vorbis sndfile wavpack TEMPLATE = subdirs +SUBDIRS += mad #cue vorbis sndfile wavpack contains(CONFIG, FLAC_PLUGIN){ SUBDIRS += flac } contains(CONFIG, MUSEPACK_PLUGIN){ - SUBDIRS += mpc +# SUBDIRS += mpc } contains(CONFIG, MODPLUG_PLUGIN){ - SUBDIRS += modplug +# SUBDIRS += modplug } contains(CONFIG, FFMPEG_PLUGIN){ - contains(CONFIG, FFMPEG_LEGACY){ - SUBDIRS += ffmpeg_legacy - }else{ - SUBDIRS += ffmpeg - } +# contains(CONFIG, FFMPEG_LEGACY){ +# SUBDIRS += ffmpeg_legacy +# }else{ +# SUBDIRS += ffmpeg +# } } contains(CONFIG, GME_PLUGIN){ - SUBDIRS += gme +# SUBDIRS += gme } contains(CONFIG, OPUS_PLUGIN){ - SUBDIRS += opus +# SUBDIRS += opus } contains(CONFIG, CDAUDIO_PLUGIN){ - SUBDIRS += cdaudio +# SUBDIRS += cdaudio } contains(CONFIG, SID_PLUGIN){ - SUBDIRS += sid +# SUBDIRS += sid } unix{ contains(CONFIG, AAC_PLUGIN){ - SUBDIRS += aac +# SUBDIRS += aac } contains(CONFIG, WILDMIDI_PLUGIN){ - SUBDIRS += wildmidi +# SUBDIRS += wildmidi } diff --git a/src/plugins/Input/flac/decoder_flac.cpp b/src/plugins/Input/flac/decoder_flac.cpp index acd76f159..4dfca6839 100644 --- a/src/plugins/Input/flac/decoder_flac.cpp +++ b/src/plugins/Input/flac/decoder_flac.cpp @@ -393,17 +393,25 @@ bool DecoderFLAC::initialize() data()->ok = 0; return false; } + ChannelMap channel_map = findChannelMap(data()->channels); + + if(channel_map.isEmpty()) + { + qWarning("DecoderFLAC: unsupported number of channels: %d", data()->channels); + return false; + } + switch(data()->bits_per_sample) { case 8: - configure(data()->sample_rate, data()->channels, Qmmp::PCM_S8); + configure(data()->sample_rate, channel_map, Qmmp::PCM_S8); break; case 16: - configure(data()->sample_rate, data()->channels, Qmmp::PCM_S16LE); + configure(data()->sample_rate, channel_map, Qmmp::PCM_S16LE); break; case 24: case 32: - configure(data()->sample_rate, data()->channels, Qmmp::PCM_S32LE); + configure(data()->sample_rate, channel_map, Qmmp::PCM_S32LE); break; default: return false; @@ -555,3 +563,66 @@ uint DecoderFLAC::findID3v2(char *data, ulong size) //retuns ID3v2 tag size } return 0; } + +ChannelMap DecoderFLAC::findChannelMap(int channels) +{ + ChannelMap map; + switch (channels) + { + case 1: + map << Qmmp::CHAN_FRONT_LEFT; + break; + case 2: + map << Qmmp::CHAN_FRONT_LEFT + << Qmmp::CHAN_FRONT_RIGHT; + break; + case 3: + map << Qmmp::CHAN_FRONT_LEFT + << Qmmp::CHAN_FRONT_RIGHT + << Qmmp::CHAN_FRONT_CENTER; + break; + case 4: + map << Qmmp::CHAN_FRONT_LEFT + << Qmmp::CHAN_FRONT_RIGHT + << Qmmp::CHAN_REAR_LEFT + << Qmmp::CHAN_REAR_RIGHT; + break; + case 5: + map << Qmmp::CHAN_FRONT_LEFT + << Qmmp::CHAN_FRONT_RIGHT + << Qmmp::CHAN_FRONT_CENTER + << Qmmp::CHAN_REAR_LEFT + << Qmmp::CHAN_REAR_RIGHT; + break; + case 6: + map << Qmmp::CHAN_FRONT_LEFT + << Qmmp::CHAN_FRONT_RIGHT + << Qmmp::CHAN_FRONT_CENTER + << Qmmp::CHAN_LFE + << Qmmp::CHAN_REAR_LEFT + << Qmmp::CHAN_REAR_RIGHT; + break; + case 7: + map << Qmmp::CHAN_FRONT_LEFT + << Qmmp::CHAN_FRONT_RIGHT + << Qmmp::CHAN_FRONT_CENTER + << Qmmp::CHAN_LFE + << Qmmp::CHAN_REAR_CENTER + << Qmmp::CHAN_SIDE_LEFT + << Qmmp::CHAN_SIDE_RIGHT; + break; + case 8: + map << Qmmp::CHAN_FRONT_LEFT + << Qmmp::CHAN_FRONT_RIGHT + << Qmmp::CHAN_FRONT_CENTER + << Qmmp::CHAN_LFE + << Qmmp::CHAN_REAR_LEFT + << Qmmp::CHAN_REAR_RIGHT + << Qmmp::CHAN_SIDE_LEFT + << Qmmp::CHAN_SIDE_RIGHT; + break; + default: + ; + } + return map; +} diff --git a/src/plugins/Input/flac/decoder_flac.h b/src/plugins/Input/flac/decoder_flac.h index e64e376eb..4b4a2f668 100644 --- a/src/plugins/Input/flac/decoder_flac.h +++ b/src/plugins/Input/flac/decoder_flac.h @@ -82,6 +82,7 @@ private: // helper functions void deinit(); uint findID3v2(char *data, ulong size); //retuns ID3v2 tag size + ChannelMap findChannelMap(int channels); struct flac_data *m_data; qint64 length_in_bytes; diff --git a/src/plugins/Input/mad/decoder_mad.cpp b/src/plugins/Input/mad/decoder_mad.cpp index b4087c06b..08b2486fb 100644 --- a/src/plugins/Input/mad/decoder_mad.cpp +++ b/src/plugins/Input/mad/decoder_mad.cpp @@ -120,7 +120,12 @@ bool DecoderMAD::initialize() mad_frame_mute (&m_frame); m_stream.next_frame = 0; m_stream.sync = 0; - configure(m_freq, m_channels, Qmmp::PCM_S16LE); + ChannelMap map; + if(m_channels == 1) + map << Qmmp::CHAN_FRONT_LEFT; + else + map << Qmmp::CHAN_FRONT_LEFT << Qmmp::CHAN_FRONT_RIGHT; + configure(m_freq, map, Qmmp::PCM_S16LE); m_inited = true; return true; } diff --git a/src/plugins/Output/Output.pro b/src/plugins/Output/Output.pro index d07513257..7dc1f0ddb 100644 --- a/src/plugins/Output/Output.pro +++ b/src/plugins/Output/Output.pro @@ -4,20 +4,20 @@ TEMPLATE = subdirs win32:SUBDIRS += waveout win32:SUBDIRS += directsound -SUBDIRS += null +#SUBDIRS += null unix{ contains(CONFIG, JACK_PLUGIN){ - SUBDIRS += jack +# SUBDIRS += jack } contains(CONFIG, OSS_PLUGIN){ - SUBDIRS += oss +# SUBDIRS += oss } contains(CONFIG, PULSE_AUDIO_PLUGIN){ - SUBDIRS += pulseaudio +# SUBDIRS += pulseaudio } contains(CONFIG, ALSA_PLUGIN){ @@ -25,7 +25,7 @@ contains(CONFIG, ALSA_PLUGIN){ } contains(CONFIG, OSS4_PLUGIN){ - SUBDIRS += oss4 +# SUBDIRS += oss4 } } diff --git a/src/plugins/Output/alsa/outputalsa.cpp b/src/plugins/Output/alsa/outputalsa.cpp index 5774579f5..f12f02060 100644 --- a/src/plugins/Output/alsa/outputalsa.cpp +++ b/src/plugins/Output/alsa/outputalsa.cpp @@ -44,6 +44,18 @@ OutputALSA::OutputALSA() : m_inited(false) m_prebuf_size = 0; m_prebuf_fill = 0; m_can_pause = false; + + m_alsa_channels[SND_CHMAP_NA] = Qmmp::CHAN_NULL; + m_alsa_channels[SND_CHMAP_MONO] = Qmmp::CHAN_FRONT_CENTER; + m_alsa_channels[SND_CHMAP_FL] = Qmmp::CHAN_FRONT_LEFT; + m_alsa_channels[SND_CHMAP_FR] = Qmmp::CHAN_FRONT_RIGHT; + m_alsa_channels[SND_CHMAP_RL] = Qmmp::CHAN_REAR_LEFT; + m_alsa_channels[SND_CHMAP_RR] = Qmmp::CHAN_REAR_RIGHT; + m_alsa_channels[SND_CHMAP_FC] = Qmmp::CHAN_FRONT_CENTER; + m_alsa_channels[SND_CHMAP_LFE] = Qmmp::CHAN_LFE; + m_alsa_channels[SND_CHMAP_SL] = Qmmp::CHAN_SIDE_LEFT; + m_alsa_channels[SND_CHMAP_SR] = Qmmp::CHAN_SIDE_RIGHT; + m_alsa_channels[SND_CHMAP_RC] = Qmmp::CHAN_REAR_CENTER; } OutputALSA::~OutputALSA() @@ -52,7 +64,7 @@ OutputALSA::~OutputALSA() free (pcm_name); } -bool OutputALSA::initialize(quint32 freq, int chan, Qmmp::AudioFormat format) +bool OutputALSA::initialize(quint32 freq, ChannelMap map, Qmmp::AudioFormat format) { m_inited = false; @@ -141,12 +153,17 @@ bool OutputALSA::initialize(quint32 freq, int chan, Qmmp::AudioFormat format) qWarning("OutputALSA: The rate %d Hz is not supported by your hardware.\n==> Using %d Hz instead.", rate, exact_rate); rate = exact_rate; } - uint c = chan; + uint c = map.count(); if ((err = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) { qWarning("OutputALSA: Error setting channels: %s", snd_strerror(err)); return false; } + if (c != (uint)map.count()) + { + qWarning("OutputALSA: The channel number %d is not supported by your hardware", map.count()); + qWarning("==> Using %d instead.", c); + } if ((err = snd_pcm_hw_params_set_period_time_near(pcm_handle, hwparams, &period_time ,0)) < 0) { qWarning("OutputALSA: Error setting period time: %s", snd_strerror(err)); @@ -190,11 +207,30 @@ bool OutputALSA::initialize(quint32 freq, int chan, Qmmp::AudioFormat format) m_chunk_size = period_size; m_can_pause = snd_pcm_hw_params_can_pause(hwparams) && use_pause; qDebug("OutputALSA: can pause: %d", m_can_pause); - configure(rate, chan, format); //apply configuration + + //channel map configuration + snd_pcm_chmap_t *chmap = snd_pcm_get_chmap(pcm_handle); + if(!chmap) + { + qWarning("OutputALSA: Unable to receive current channel map: %s", snd_strerror(err)); + return false; + } + char tmp[256]; + memset(tmp,0,256); + snd_pcm_chmap_print(chmap, 256, tmp); + qDebug("OutputALSA: received channel map: %s",tmp); + ChannelMap out_map; + for(uint i = 0; i < chmap->channels; ++i) + { + if(m_alsa_channels.keys().contains(chmap->pos[i])) + out_map.append(m_alsa_channels.value(chmap->pos[i])); + else + out_map.append(Qmmp::CHAN_NULL); + } + configure(exact_rate, out_map, format); //apply configuration //create alsa prebuffer; m_prebuf_size = 2 * snd_pcm_frames_to_bytes(pcm_handle, m_chunk_size); //buffer for two periods m_prebuf = (uchar *)malloc(m_prebuf_size); - m_inited = true; return true; } diff --git a/src/plugins/Output/alsa/outputalsa.h b/src/plugins/Output/alsa/outputalsa.h index 1600e3b29..d378b813c 100644 --- a/src/plugins/Output/alsa/outputalsa.h +++ b/src/plugins/Output/alsa/outputalsa.h @@ -18,8 +18,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ -#ifndef OUTPUTALSA_H -#define OUTPUTALSA_H +#ifndef OUTPUTALSA_H +#define OUTPUTALSA_H class OutputALSA; @@ -28,6 +28,7 @@ extern "C" #include <alsa/asoundlib.h> } +#include <QHash> #include <qmmp/output.h> #include <qmmp/volume.h> @@ -38,7 +39,7 @@ public: OutputALSA(); ~OutputALSA(); - bool initialize(quint32, int, Qmmp::AudioFormat format); + bool initialize(quint32, ChannelMap map, Qmmp::AudioFormat format); //output api qint64 latency(); qint64 writeAudio(unsigned char *data, qint64 maxSize); @@ -63,6 +64,8 @@ private: qint64 m_prebuf_size; qint64 m_prebuf_fill; bool m_can_pause; + //channel conversions + QHash <quint16, Qmmp::ChannelPosition> m_alsa_channels; }; class VolumeALSA : public Volume diff --git a/src/qmmp/audioconverter.cpp b/src/qmmp/audioconverter.cpp index fc64dc7d5..064e47cec 100644 --- a/src/qmmp/audioconverter.cpp +++ b/src/qmmp/audioconverter.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2010 by Ilya Kotov * + * Copyright (C) 2010-2014 by Ilya Kotov * * forkotov02@hotmail.ru * * * * This program is free software; you can redistribute it and/or modify * @@ -47,10 +47,10 @@ AudioConverter::AudioConverter() m_format = Qmmp::PCM_UNKNOWM; } -void AudioConverter::configure(quint32 srate, int chan, Qmmp::AudioFormat f) +void AudioConverter::configure(quint32 srate, ChannelMap map, Qmmp::AudioFormat f) { m_format = f; - Effect::configure(srate, chan, Qmmp::PCM_S16LE); + Effect::configure(srate, map, Qmmp::PCM_S16LE); } void AudioConverter::applyEffect(Buffer *b) @@ -58,14 +58,14 @@ void AudioConverter::applyEffect(Buffer *b) switch(m_format) { case Qmmp::PCM_S8: - { - unsigned char *out = new unsigned char[b->nbytes*2]; - s8_to_s16((qint8 *)b->data, (qint16 *) out, b->nbytes); - delete [] b->data; - b->data = out; - b->nbytes <<= 1; - break; - } + { + unsigned char *out = new unsigned char[b->nbytes*2]; + s8_to_s16((qint8 *)b->data, (qint16 *) out, b->nbytes); + delete [] b->data; + b->data = out; + b->nbytes <<= 1; + break; + } case Qmmp::PCM_S24LE: s24_to_s16((qint32 *)b->data, (qint16 *)b->data, b->nbytes >> 2); b->nbytes >>= 1; diff --git a/src/qmmp/audioconverter_p.h b/src/qmmp/audioconverter_p.h index 6f59220f1..d01b09e5e 100644 --- a/src/qmmp/audioconverter_p.h +++ b/src/qmmp/audioconverter_p.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2010 by Ilya Kotov * + * Copyright (C) 2010-2014 by Ilya Kotov * * forkotov02@hotmail.ru * * * * This program is free software; you can redistribute it and/or modify * @@ -30,7 +30,7 @@ class AudioConverter : public Effect { public: AudioConverter(); - void configure(quint32 srate = 44100, int chan = 2, Qmmp::AudioFormat f = Qmmp::PCM_S16LE); + void configure(quint32 srate, ChannelMap map, Qmmp::AudioFormat f = Qmmp::PCM_S16LE); void applyEffect(Buffer *b); private: diff --git a/src/qmmp/audioparameters.cpp b/src/qmmp/audioparameters.cpp index b139b93c5..f1d8b6bdf 100644 --- a/src/qmmp/audioparameters.cpp +++ b/src/qmmp/audioparameters.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2009 by Ilya Kotov * + * Copyright (C) 2009-2014 by Ilya Kotov * * forkotov02@hotmail.ru * * * * This program is free software; you can redistribute it and/or modify * @@ -23,34 +23,33 @@ AudioParameters::AudioParameters() { m_srate = 0; - m_chan = 0; m_format = Qmmp::PCM_S16LE; } AudioParameters::AudioParameters(const AudioParameters &other) { m_srate = other.sampleRate(); - m_chan = other.channels(); + m_chan_map = other.channelMap(); m_format = other.format(); } -AudioParameters::AudioParameters(quint32 srate, int chan, Qmmp::AudioFormat format) +AudioParameters::AudioParameters(quint32 srate, const ChannelMap &map, Qmmp::AudioFormat format) { m_srate = srate; - m_chan = chan; + m_chan_map = map; m_format = format; } void AudioParameters::operator=(const AudioParameters &p) { m_srate = p.sampleRate(); - m_chan = p.channels(); + m_chan_map = p.channelMap(); m_format = p.format(); } bool AudioParameters::operator==(const AudioParameters &p) const { - return m_srate == p.sampleRate() && m_chan == p.channels() && m_format == p.format(); + return m_srate == p.sampleRate() && m_chan_map == p.channelMap() && m_format == p.format(); } bool AudioParameters::operator!=(const AudioParameters &p) const @@ -65,7 +64,12 @@ quint32 AudioParameters::sampleRate() const int AudioParameters::channels() const { - return m_chan; + return m_chan_map.count(); +} + +const ChannelMap AudioParameters::channelMap() const +{ + return m_chan_map; } Qmmp::AudioFormat AudioParameters::format() const diff --git a/src/qmmp/audioparameters.h b/src/qmmp/audioparameters.h index dc3620820..94e113eca 100644 --- a/src/qmmp/audioparameters.h +++ b/src/qmmp/audioparameters.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2009-2012 by Ilya Kotov * + * Copyright (C) 2009-2014 by Ilya Kotov * * forkotov02@hotmail.ru * * * * This program is free software; you can redistribute it and/or modify * @@ -22,6 +22,7 @@ #define AUDIOPARAMETERS_H #include <QtGlobal> +#include "channelmap.h" #include "qmmp.h" /*! @brief The AudioParameters class keeps information about audio settings. @@ -37,10 +38,10 @@ public: /*! * Constructs audio settings with the given parameters. * @param srate Sampling rate. - * @param chan Number of channels. + * @param map Channel map. * @param format PCM data format. */ - AudioParameters(quint32 srate, int chan, Qmmp::AudioFormat format); + AudioParameters(quint32 srate, const ChannelMap &map, Qmmp::AudioFormat format); /*! * Constructs a copy of \b other. */ @@ -65,6 +66,7 @@ public: * Returns number of channels. */ int channels() const; + const ChannelMap channelMap() const; /*! * Returns pcm format. */ @@ -80,7 +82,7 @@ public: private: quint32 m_srate; - int m_chan; + ChannelMap m_chan_map; Qmmp::AudioFormat m_format; }; diff --git a/src/qmmp/channelconverter.cpp b/src/qmmp/channelconverter.cpp new file mode 100644 index 000000000..622dc73ae --- /dev/null +++ b/src/qmmp/channelconverter.cpp @@ -0,0 +1,119 @@ +/*************************************************************************** + * Copyright (C) 2014 by Ilya Kotov * + * forkotov02@hotmail.ru * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "channelconverter_p.h" + +ChannelConverter::ChannelConverter(ChannelMap out_map) +{ + m_disabled = true; + m_tmp_buf = 0; + m_format = Qmmp::PCM_UNKNOWM; + m_frame_size = 0; + m_channels = 0; + m_out_map = out_map; + memset(m_reorder_array, 0, sizeof(m_reorder_array)); +} + +ChannelConverter::~ChannelConverter() +{ + if(m_tmp_buf) + { + delete [] m_tmp_buf; + m_tmp_buf = 0; + } +} + +void ChannelConverter::configure(quint32 srate, ChannelMap in_map, Qmmp::AudioFormat f) +{ + Effect::configure(srate, m_out_map, f); + + if((m_disabled = (in_map == m_out_map))) + return; + + m_channels = channels(); + m_frame_size = audioParameters().sampleSize() * channels(); + m_tmp_buf = new unsigned char[m_frame_size]; + m_format = f; + + QStringList reorderStringList; + for(int i = 0; i < m_channels; ++i) + { + m_reorder_array[i] = m_out_map.indexOf(in_map.at(i)); + reorderStringList << QString("%1").arg(m_reorder_array[i]); + } + + qDebug("ChannelConverter: {%s} ==> {%s}; {%s}", qPrintable(in_map.toString()), + qPrintable(m_out_map.toString()), qPrintable(reorderStringList.join(","))); +} + +void ChannelConverter::applyEffect(Buffer *b) +{ + if(m_disabled) + return; + + unsigned long i = 0; + int j = 0; + + switch(m_format) + { + case Qmmp::PCM_S8: + { + unsigned char *data = b->data; + unsigned char *prev_data = m_tmp_buf; + for(i = 0; i < b->nbytes/m_frame_size; ++i) + { + memcpy(m_tmp_buf, data, m_frame_size); + for(j = 0; j < m_channels; ++j) + data[j] = m_reorder_array[j] < 0 ? 0 : prev_data[m_reorder_array[j]]; + data += m_channels; + } + break; + } + case Qmmp::PCM_S16LE: + { + quint16 *data = (quint16 *) b->data; + quint16 *prev_data = (quint16 *) m_tmp_buf; + for(i = 0; i < b->nbytes/m_frame_size; ++i) + { + memcpy(m_tmp_buf, data, m_frame_size); + for(j = 0; j < m_channels; ++j) + data[j] = m_reorder_array[j] < 0 ? 0 : prev_data[m_reorder_array[j]]; + data += m_channels; + } + break; + } + case Qmmp::PCM_S24LE: + case Qmmp::PCM_S32LE: + { + quint32 *data = (quint32 *) b->data; + quint32 *prev_data = (quint32 *) m_tmp_buf; + for(i = 0; i < b->nbytes/m_frame_size; ++i) + { + memcpy(m_tmp_buf, data, m_frame_size); + for(j = 0; j < m_channels; ++j) + data[j] = m_reorder_array[j] < 0 ? 0 : prev_data[m_reorder_array[j]]; + data += m_channels; + } + break; + } + default: + ; + } +} diff --git a/src/qmmp/channelconverter_p.h b/src/qmmp/channelconverter_p.h new file mode 100644 index 000000000..7f41570fe --- /dev/null +++ b/src/qmmp/channelconverter_p.h @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2014 by Ilya Kotov * + * forkotov02@hotmail.ru * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef CHANNELCONVERTER_P_H +#define CHANNELCONVERTER_P_H + +#include "effect.h" + +/*! @internal + * @author Ilya Kotov <forkotov02@hotmail.ru> + */ +class ChannelConverter : public Effect +{ +public: + ChannelConverter(ChannelMap out_map); + ~ChannelConverter(); + void configure(quint32 srate, ChannelMap in_map, Qmmp::AudioFormat f); + void applyEffect(Buffer *b); + +private: + bool m_disabled; + int m_reorder_array[9]; + unsigned char *m_tmp_buf; + int m_frame_size; + int m_channels; + Qmmp::AudioFormat m_format; + ChannelMap m_out_map; +}; + +#endif // CHANNELCONVERTER_P_H diff --git a/src/qmmp/channelmap.cpp b/src/qmmp/channelmap.cpp index 196c69d6b..34fdde17a 100644 --- a/src/qmmp/channelmap.cpp +++ b/src/qmmp/channelmap.cpp @@ -18,16 +18,20 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ +#include <QStringList> +#include <QHash> #include "channelmap.h" -Qmmp::AudioChannel ChannelMap::m_internal_map[8] = { Qmmp::CHAN_FRONT_LEFT, +Qmmp::ChannelPosition ChannelMap::m_internal_map[9] = { Qmmp::CHAN_FRONT_LEFT, Qmmp::CHAN_FRONT_RIGHT, Qmmp::CHAN_REAR_LEFT, Qmmp::CHAN_REAR_RIGHT, - Qmmp::CHAN_CENTER, + Qmmp::CHAN_FRONT_CENTER, + Qmmp::CHAN_REAR_CENTER, Qmmp::CHAN_LFE, Qmmp::CHAN_SIDE_LEFT, - Qmmp::CHAN_SIDE_RIGHT }; + Qmmp::CHAN_SIDE_RIGHT, + }; ChannelMap::ChannelMap() { @@ -36,7 +40,7 @@ ChannelMap::ChannelMap() int ChannelMap::mask() const { int mask = 0; - foreach (Qmmp::AudioChannel channel, *this) + foreach (Qmmp::ChannelPosition channel, *this) { mask |= channel; } @@ -46,16 +50,37 @@ int ChannelMap::mask() const const ChannelMap ChannelMap::remaped() const { ChannelMap map; - for(int i = 0; i < 8; ++i) + for(int i = 0; i < 9; ++i) { - foreach (Qmmp::AudioChannel channel, *this) - { - if(channel == m_internal_map[i]) - { - map.append(channel); - break; - } - } + if(contains(m_internal_map[i])) + map.append(m_internal_map[i]); + } + while (map.count() < count()) + { + map.append(Qmmp::CHAN_NULL); } return map; } + +const QString ChannelMap::toString() const +{ + QStringList list; + QHash <Qmmp::ChannelPosition, QString> names; + names.insert(Qmmp::CHAN_NULL, "NA"); + names.insert(Qmmp::CHAN_FRONT_LEFT, "FL"); + names.insert(Qmmp::CHAN_FRONT_RIGHT, "FR"); + names.insert(Qmmp::CHAN_REAR_LEFT, "RL"); + names.insert(Qmmp::CHAN_REAR_RIGHT, "RR"); + names.insert(Qmmp::CHAN_FRONT_CENTER, "FC"); + names.insert(Qmmp::CHAN_REAR_CENTER, "RC"); + names.insert(Qmmp::CHAN_LFE, "LFE"); + names.insert(Qmmp::CHAN_SIDE_LEFT, "SL"); + names.insert(Qmmp::CHAN_SIDE_RIGHT, "SR"); + + + foreach (Qmmp::ChannelPosition channel, *this) + { + list << names.value(channel); + } + return list.join(","); +} diff --git a/src/qmmp/channelmap.h b/src/qmmp/channelmap.h index 5a9a96ef5..19196b8f5 100644 --- a/src/qmmp/channelmap.h +++ b/src/qmmp/channelmap.h @@ -24,16 +24,17 @@ #include <QList> #include "qmmp.h" -class ChannelMap : public QList<Qmmp::AudioChannel> +class ChannelMap : public QList<Qmmp::ChannelPosition> { public: ChannelMap(); int mask() const; const ChannelMap remaped() const; + const QString toString() const; private: - static Qmmp::AudioChannel m_internal_map[8]; + static Qmmp::ChannelPosition m_internal_map[9]; }; #endif // CHANNELMAP_H diff --git a/src/qmmp/decoder.cpp b/src/qmmp/decoder.cpp index 1b9bfff69..1aa9fc4de 100644 --- a/src/qmmp/decoder.cpp +++ b/src/qmmp/decoder.cpp @@ -33,9 +33,9 @@ void Decoder::setReplayGainInfo(const QMap<Qmmp::ReplayGainKey, double> &rg, boo m_hasHeadroom = headroom; } -void Decoder::configure(quint32 srate, int chan, Qmmp::AudioFormat format) +void Decoder::configure(quint32 srate, const ChannelMap &map, Qmmp::AudioFormat format) { - m_parameters = AudioParameters(srate, chan, format); + m_parameters = AudioParameters(srate, map, format); } qint64 Decoder::read(float *data, qint64 samples) @@ -49,7 +49,7 @@ qint64 Decoder::read(float *data, qint64 samples) void Decoder::next() {} -const QString Decoder::nextURL() +const QString Decoder::nextURL() const { return QString(); } diff --git a/src/qmmp/decoder.h b/src/qmmp/decoder.h index 195aefefb..8e76f3a93 100644 --- a/src/qmmp/decoder.h +++ b/src/qmmp/decoder.h @@ -14,6 +14,7 @@ #include "fileinfo.h" #include "qmmp.h" #include "audioparameters.h" +#include "channelmap.h" #include "decoderfactory.h" class QmmpPluginCache; @@ -82,7 +83,7 @@ public: * By default this function does nothing. * Reimplemet it if your decoder can play next track without stop/start cycle. */ - virtual const QString nextURL(); + virtual const QString nextURL() const; /*! * Returns detected audio parameters. */ @@ -174,12 +175,10 @@ protected: /*! * Use this function inside initialize() reimplementation to tell other plugins about audio parameters. * @param srate Sample rate. - * @param chan Number of channels. + * @param map Map of channels. * @param f Audio format. */ - void configure(quint32 srate = 44100, int chan = 2, Qmmp::AudioFormat f = Qmmp::PCM_S16LE); - - //void configure(quint32 srate = 44100, QList<Qmmp::AudioChannel> channels, Qmmp::AudioFormat f = Qmmp::PCM_S16LE); + void configure(quint32 srate, const ChannelMap &map, Qmmp::AudioFormat f = Qmmp::PCM_S16LE); private: static void loadPlugins(); diff --git a/src/qmmp/effect.cpp b/src/qmmp/effect.cpp index 8f797b7ea..99fc4c3b7 100644 --- a/src/qmmp/effect.cpp +++ b/src/qmmp/effect.cpp @@ -29,39 +29,45 @@ Effect::Effect() { m_freq = 0; - m_chan = 0; m_format = Qmmp::PCM_UNKNOWM; + m_channels = 0; m_factory = 0; } Effect::~Effect() {} -void Effect::configure(quint32 freq, int chan, Qmmp::AudioFormat format) +void Effect::configure(quint32 freq, ChannelMap map, Qmmp::AudioFormat format) { m_freq = freq; - m_chan = chan; + m_chan_map = map; + m_channels = map.count(); m_format = format; } -quint32 Effect::sampleRate() +quint32 Effect::sampleRate() const { return m_freq; } -int Effect::channels() +int Effect::channels() const { - return m_chan; + return m_channels; } -Qmmp::AudioFormat Effect::format() +const ChannelMap Effect::channelMap() const +{ + return m_chan_map; +} + +Qmmp::AudioFormat Effect::format() const { return m_format; } const AudioParameters Effect::audioParameters() const { - return AudioParameters(m_freq, m_chan, m_format); + return AudioParameters(m_freq, m_chan_map, m_format); } EffectFactory* Effect::factory() const diff --git a/src/qmmp/effect.h b/src/qmmp/effect.h index 3c9262145..b2fc2bf0f 100644 --- a/src/qmmp/effect.h +++ b/src/qmmp/effect.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2007-2013 by Ilya Kotov * + * Copyright (C) 2007-2014 by Ilya Kotov * * forkotov02@hotmail.ru * * * * This program is free software; you can redistribute it and/or modify * @@ -23,6 +23,7 @@ #include <QList> #include <QStringList> #include "audioparameters.h" +#include "channelmap.h" #include "buffer.h" class EffectFactory; @@ -51,22 +52,26 @@ public: * Prepares object for usage. * Subclasses that reimplement this function must call the base implementation. * @param srate Sample rate. - * @param chan Number of channels. + * @param map Map of channels. * @param f Audio format. */ - virtual void configure(quint32 srate = 44100, int chan = 2, Qmmp::AudioFormat f = Qmmp::PCM_S16LE); + virtual void configure(quint32 srate, ChannelMap map, Qmmp::AudioFormat f = Qmmp::PCM_S16LE); /*! * Returns samplerate. */ - quint32 sampleRate(); + quint32 sampleRate() const; /*! * Returns channels number. */ - int channels(); + int channels() const; + /*! + * Returns map of channels + */ + const ChannelMap channelMap() const; /*! * Returns audio format. */ - Qmmp::AudioFormat format(); + Qmmp::AudioFormat format() const; /*! * Returns audio parameters for output data. */ @@ -108,7 +113,8 @@ public: private: EffectFactory *m_factory; quint32 m_freq; - int m_chan; + int m_channels; + ChannelMap m_chan_map; Qmmp::AudioFormat m_format; static void loadPlugins(); static QList<QmmpPluginCache*> *m_cache; diff --git a/src/qmmp/output.cpp b/src/qmmp/output.cpp index 7a253047a..81031568e 100644 --- a/src/qmmp/output.cpp +++ b/src/qmmp/output.cpp @@ -17,30 +17,34 @@ Output::Output() { m_frequency = 0; - m_channels = 0; m_format = Qmmp::PCM_UNKNOWM; } -void Output::configure(quint32 freq, int chan, Qmmp::AudioFormat format) +void Output::configure(quint32 freq, ChannelMap map, Qmmp::AudioFormat format) { m_frequency = freq; - m_channels = chan; + m_chan_map = map; m_format = format; } AudioParameters Output::audioParameters() const { - return AudioParameters(m_frequency, m_channels, m_format); + return AudioParameters(m_frequency, m_chan_map, m_format); } -quint32 Output::sampleRate() +quint32 Output::sampleRate() const { return m_frequency; } -int Output::channels() +int Output::channels() const { - return m_channels; + return m_chan_map.count(); +} + +const ChannelMap Output::channelMap() const +{ + return m_chan_map; } Qmmp::AudioFormat Output::format() const diff --git a/src/qmmp/output.h b/src/qmmp/output.h index 8b2ab67e4..96949e799 100644 --- a/src/qmmp/output.h +++ b/src/qmmp/output.h @@ -12,6 +12,7 @@ #include <QIODevice> #include "outputfactory.h" #include "audioparameters.h" +#include "channelmap.h" class QTimer; class QmmpSettings; @@ -36,11 +37,11 @@ public: * Prepares object for usage and setups required audio parameters. * Subclass should reimplement this function. * @param freq Sample rate. - * @param chan Number of channels. + * @param map Map of channels. * @param format Audio format * @return initialization result (\b true - success, \b false - failure) */ - virtual bool initialize(quint32 freq, int chan, Qmmp::AudioFormat format) = 0; + virtual bool initialize(quint32 freq, ChannelMap map, Qmmp::AudioFormat format) = 0; /*! * Returns output interface latency in milliseconds. */ @@ -80,11 +81,13 @@ public: /*! * Returns samplerate. */ - quint32 sampleRate(); + quint32 sampleRate() const; /*! * Returns channels number. */ - int channels(); + int channels() const; + + const ChannelMap channelMap() const; /*! * Returns selected audio format. */ @@ -120,14 +123,14 @@ protected: /*! * Use this function inside initialize() reimplementation to tell about accepted audio parameters. * @param freq Sample rate. - * @param chan Number of channels. + * @param map Map of channels. * @param format Audio format. */ - void configure(quint32 freq, int chan, Qmmp::AudioFormat format); + void configure(quint32 freq, ChannelMap map, Qmmp::AudioFormat format); private: quint32 m_frequency; - int m_channels; + ChannelMap m_chan_map; Qmmp::AudioFormat m_format; static void loadPlugins(); static QList<QmmpPluginCache*> *m_cache; diff --git a/src/qmmp/outputwriter.cpp b/src/qmmp/outputwriter.cpp index 682318880..4199ae93b 100644 --- a/src/qmmp/outputwriter.cpp +++ b/src/qmmp/outputwriter.cpp @@ -22,6 +22,8 @@ #include "statehandler.h" #include "visual.h" #include "output.h" +#include "audioconverter_p.h" +#include "channelconverter_p.h" #include "volumecontrol_p.h" #include "outputwriter_p.h" @@ -61,7 +63,7 @@ OutputWriter::OutputWriter (QObject* parent) : QThread (parent) m_totalWritten = 0; m_currentMilliseconds = -1; m_bytesPerMillisecond = 0; - m_userStop = false; + m_user_stop = false; m_finish = false; m_visBuffer = 0; m_visBufferSize = 0; @@ -91,8 +93,14 @@ OutputWriter::~OutputWriter() } } -bool OutputWriter::initialize(quint32 freq, int chan, Qmmp::AudioFormat format) +bool OutputWriter::initialize(quint32 freq, ChannelMap map, Qmmp::AudioFormat format) { + QMap<Qmmp::AudioFormat, QString> formatNames; + formatNames.insert(Qmmp::PCM_S8, "s8"); + formatNames.insert(Qmmp::PCM_S16LE, "s16le"); + formatNames.insert(Qmmp::PCM_S24LE, "s24le"); + formatNames.insert(Qmmp::PCM_S32LE, "s32le"); + m_output = Output::create(); if(!m_output) { @@ -100,30 +108,41 @@ bool OutputWriter::initialize(quint32 freq, int chan, Qmmp::AudioFormat format) return false; } - if (!m_output->initialize(freq, chan, format)) + if (!m_output->initialize(freq, map, format)) { qWarning("OutputWriter: unable to initialize output"); delete m_output; m_output = 0; return false; } - m_frequency = m_output->sampleRate(); - m_channels = m_output->channels(); - m_format = m_output->format(); - QMap<Qmmp::AudioFormat, QString> formatNames; - formatNames.insert(Qmmp::PCM_S8, "s8"); - formatNames.insert(Qmmp::PCM_S16LE, "s16le"); - formatNames.insert(Qmmp::PCM_S24LE, "s24le"); - formatNames.insert(Qmmp::PCM_S32LE, "s32le"); - qDebug("OutputWriter: [%s] %d Hz, %d ch, %s", + m_frequency = freq; + m_channels = map.count(); + m_format = format; + m_chan_map = map; + + qDebug("OutputWriter: [%s] %u Hz, {%s}, %s ==> %u Hz, {%s}, %s", qPrintable(Output::currentFactory()->properties().shortName), - freq, chan, qPrintable(formatNames.value(format))); - m_bytesPerMillisecond = freq * chan * AudioParameters::sampleSize(format) / 1000; - m_recycler.configure(freq, chan, format); //calculate output buffer size + freq, + qPrintable(map.toString()), + qPrintable(formatNames.value(format)), + m_output->sampleRate(), + qPrintable(m_output->channelMap().toString()), + qPrintable(formatNames.value(m_output->format()))); + + if(!prepareConverters()) + { + qWarning("OutputWriter: unable to convert audio"); + delete m_output; + m_output = 0; + return false; + } + + m_bytesPerMillisecond = m_frequency * m_channels * AudioParameters::sampleSize(format) / 1000; + m_recycler.configure(m_frequency, m_channels, m_format); //calculate output buffer size //visual buffer if(m_visBuffer) delete [] m_visBuffer; - m_visBufferSize = QMMP_BLOCK_FRAMES * 2 * chan; //16-bit samples + m_visBufferSize = QMMP_BLOCK_FRAMES * 2 * m_channels; //16-bit samples if(m_format != Qmmp::PCM_S16LE) m_visBuffer = new unsigned char [m_visBufferSize]; m_useEq = m_eqEnabled && m_frequency && m_format == Qmmp::PCM_S16LE; @@ -146,7 +165,7 @@ void OutputWriter::pause() void OutputWriter::stop() { - m_userStop = true; + m_user_stop = true; } void OutputWriter::setMuted(bool muted) @@ -178,7 +197,7 @@ QMutex *OutputWriter::mutex() AudioParameters OutputWriter::audioParameters() const { - return AudioParameters(m_frequency, m_channels, m_format); + return AudioParameters(m_frequency, m_chan_map, m_format); } quint32 OutputWriter::sampleRate() @@ -196,6 +215,11 @@ Qmmp::AudioFormat OutputWriter::format() const return m_format; } +const ChannelMap OutputWriter::channelMap() const +{ + return m_chan_map; +} + int OutputWriter::sampleSize() const { return AudioParameters::sampleSize(m_format); @@ -243,6 +267,14 @@ void OutputWriter::dispatchVisual (Buffer *buffer) m_visBuffer = 0; } +void OutputWriter::applyConverters(Buffer *buffer) +{ + foreach (Effect *e, m_converters) + { + e->applyEffect(buffer); + } +} + void OutputWriter::clearVisuals() { foreach (Visual *visual, *Visual::visuals()) @@ -253,6 +285,45 @@ void OutputWriter::clearVisuals() } } +bool OutputWriter::prepareConverters() +{ + qDeleteAll(m_converters); + m_converters.clear(); + + AudioParameters ap = m_output->audioParameters(); + + if(channels() != m_output->channels()) + { + qWarning("OutputWriter: unsupported channel number"); + return false; + } + + if(format() != ap.format()) + { + if(m_output->format() == Qmmp::PCM_S16LE) + { + qDebug("OutputWriter: using 16 bit comverter"); + m_converters << new AudioConverter(); + m_converters.last()->configure(ap.sampleRate(), ap.channelMap(), ap.format()); + ap = m_converters.last()->audioParameters(); + } + else + { + qWarning("OutputWriter: unsupported audio format"); + return false; + } + } + + if(channelMap() != ap.channelMap()) + { + m_converters << new ChannelConverter(ap.channelMap()); + m_converters.last()->configure(ap.sampleRate(), channelMap(), ap.format()); + ap = m_converters.last()->audioParameters(); + } + + return true; +} + void OutputWriter::dispatch(qint64 elapsed, int bitrate, int frequency, @@ -305,7 +376,7 @@ void OutputWriter::run() m_prev_pause = m_pause; } recycler()->mutex()->lock (); - done = m_userStop || (m_finish && recycler()->empty()); + done = m_user_stop || (m_finish && recycler()->empty()); while (!done && (recycler()->empty() || m_pause)) { @@ -313,7 +384,7 @@ void OutputWriter::run() mutex()->unlock(); recycler()->cond()->wait(recycler()->mutex()); mutex()->lock (); - done = m_userStop || m_finish; + done = m_user_stop || m_finish; } status(); @@ -351,6 +422,7 @@ void OutputWriter::run() SoftwareVolume::instance()->changeVolume(b, m_channels, m_format); if (m_muted) memset(b->data, 0, b->nbytes); + applyConverters(b); l = 0; m = 0; while (l < b->nbytes && !m_pause && !m_prev_pause) diff --git a/src/qmmp/outputwriter_p.h b/src/qmmp/outputwriter_p.h index fcaecfc5f..f4096a9f5 100644 --- a/src/qmmp/outputwriter_p.h +++ b/src/qmmp/outputwriter_p.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2012 by Ilya Kotov * + * Copyright (C) 2012-2014 by Ilya Kotov * * forkotov02@hotmail.ru * * * * This program is free software; you can redistribute it and/or modify * @@ -25,11 +25,13 @@ #include <QMutex> #include "recycler_p.h" #include "audioparameters.h" +#include "channelmap.h" class QTimer; class QmmpSettings; class StateHandler; class Output; +class Effect; /** @internal @brief Output thread. @@ -45,11 +47,11 @@ public: /*! * Prepares object for usage and setups required audio parameters. * @param freq Sample rate. - * @param chan Number of channels. + * @param map Map of channels. * @param format Audio format * @return initialization result (\b true - success, \b false - failure) */ - bool initialize(quint32 freq, int chan, Qmmp::AudioFormat format); + bool initialize(quint32 freq, ChannelMap map, Qmmp::AudioFormat format); /*! * Requests playback to pause. If it was paused already, playback should resume. */ @@ -97,6 +99,7 @@ public: * Returns selected audio format. */ Qmmp::AudioFormat format() const; + const ChannelMap channelMap() const; /*! * Returns sample size in bytes. */ @@ -115,16 +118,20 @@ private: int channels); void dispatch(const Qmmp::State &state); void dispatchVisual(Buffer *buffer); + void applyConverters(Buffer *buffer); void clearVisuals(); + bool prepareConverters(); + bool m_skip; QMutex m_mutex; Recycler m_recycler; StateHandler *m_handler; quint32 m_frequency; int m_channels, m_kbps; + ChannelMap m_chan_map; Qmmp::AudioFormat m_format; qint64 m_bytesPerMillisecond; - bool m_userStop, m_pause; + bool m_user_stop, m_pause; bool m_prev_pause; bool m_finish; bool m_useEq, m_eqEnabled; @@ -134,6 +141,7 @@ private: QmmpSettings *m_settings; Output *m_output; bool m_muted; + QList<Effect *> m_converters; }; diff --git a/src/qmmp/qmmp.h b/src/qmmp/qmmp.h index 4103a3d17..a079e2a02 100644 --- a/src/qmmp/qmmp.h +++ b/src/qmmp/qmmp.h @@ -87,17 +87,18 @@ public: PCM_S32LE /*!< Signed 32 bit Little Endian */ }; - enum AudioChannel + enum ChannelPosition { - CHAN_NULL = 0x00, - CHAN_FRONT_LEFT = 0x01, - CHAN_FRONT_RIGHT = 0x02, - CHAN_REAR_LEFT = 0x04, - CHAN_REAR_RIGHT = 0x08, - CHAN_CENTER = 0x10, - CHAN_LFE = 0x20, - CHAN_SIDE_LEFT = 0x40, - CHAN_SIDE_RIGHT = 0x80 + CHAN_NULL = 0x00, + CHAN_FRONT_LEFT = 0x01, + CHAN_FRONT_RIGHT = 0x02, + CHAN_REAR_LEFT = 0x04, + CHAN_REAR_RIGHT = 0x08, + CHAN_FRONT_CENTER = 0x10, + CHAN_REAR_CENTER = 0x20, + CHAN_SIDE_LEFT = 0x40, + CHAN_SIDE_RIGHT = 0x80, + CHAN_LFE = 0x100, }; /*! diff --git a/src/qmmp/qmmp.pro b/src/qmmp/qmmp.pro index 158a14fd2..4516dff6c 100644 --- a/src/qmmp/qmmp.pro +++ b/src/qmmp/qmmp.pro @@ -38,7 +38,8 @@ HEADERS += \ outputwriter_p.h \ recycler_p.h \ qmmpplugincache_p.h \ - channelmap.h + channelmap.h \ + channelconverter_p.h SOURCES += recycler.cpp \ decoder.cpp \ output.cpp \ @@ -68,7 +69,8 @@ SOURCES += recycler.cpp \ qmmpevents.cpp \ outputwriter.cpp \ qmmpplugincache.cpp \ - channelmap.cpp + channelmap.cpp \ + channelconverter.cpp FORMS += unix:TARGET = ../../lib/qmmp win32:TARGET = ../../../bin/qmmp diff --git a/src/qmmp/qmmpaudioengine.cpp b/src/qmmp/qmmpaudioengine.cpp index d5e47b68d..45ea3bbcd 100644 --- a/src/qmmp/qmmpaudioengine.cpp +++ b/src/qmmp/qmmpaudioengine.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2009-2013 by Ilya Kotov * + * Copyright (C) 2009-2014 by Ilya Kotov * * forkotov02@hotmail.ru * * * * This program is free software; you can redistribute it and/or modify * @@ -32,6 +32,7 @@ #include "statehandler.h" #include "audioconverter_p.h" #include "qmmpaudioengine_p.h" +#include "channelconverter_p.h" #include "metadatamanager.h" #define TRANSPORT_TIMEOUT 5000 //ms @@ -170,7 +171,7 @@ void QmmpAudioEngine::addEffect(EffectFactory *factory) Effect *effect = Effect::create(factory); if(!effect) return; - effect->configure(m_ap.sampleRate(), m_ap.channels(), m_ap.format()); + effect->configure(m_ap.sampleRate(), m_ap.channelMap(), m_ap.format()); if(effect->audioParameters() == m_ap) { mutex()->lock(); @@ -568,34 +569,16 @@ OutputWriter *QmmpAudioEngine::createOutput() { OutputWriter *output = new OutputWriter(0); output->setMuted(m_muted); - if (!output->initialize(m_ap.sampleRate(), m_ap.channels(), m_ap.format())) + if (!output->initialize(m_ap.sampleRate(), m_ap.channelMap(), m_ap.format())) { delete output; StateHandler::instance()->dispatch(Qmmp::FatalError); return 0; } - if(output->audioParameters() != m_ap) - { - if(output->audioParameters().format() == Qmmp::PCM_S16LE) //output supports 16 bit only - { - Effect *effect = new AudioConverter(); - effect->configure(m_ap.sampleRate(), m_ap.channels(), m_ap.format()); - m_ap = effect->audioParameters(); - m_effects.append(effect); - qDebug("QmmpAudioEngine: output plugin requires 16 bit, using 16-bit converter"); - } - else - { - qWarning("QmmpAudioEngine: unsupported audio format"); - delete output; - StateHandler::instance()->dispatch(Qmmp::FatalError); - return 0; - } - } if(m_output_buf) delete [] m_output_buf; - m_bks = QMMP_BLOCK_FRAMES * m_ap.channels() * m_ap.sampleSize(); + m_bks = output->recycler()->blockSize(); m_output_size = m_bks * 4; m_output_buf = new unsigned char[m_output_size]; return output; @@ -621,10 +604,14 @@ void QmmpAudioEngine::prepareEffects(Decoder *d) QList <Effect *> tmp_effects = m_effects; m_effects.clear(); + m_effects << new ChannelConverter(m_ap.channelMap().remaped()); + m_effects.at(0)->configure(m_ap.sampleRate(), m_ap.channelMap(), m_ap.format()); + m_ap = m_effects.at(0)->audioParameters(); + if(m_settings->use16BitOutput()) { m_effects << new AudioConverter(); - m_effects.at(0)->configure(m_ap.sampleRate(), m_ap.channels(), m_ap.format()); + m_effects.at(0)->configure(m_ap.sampleRate(), m_ap.channelMap(), m_ap.format()); m_ap = m_effects.at(0)->audioParameters(); } @@ -648,7 +635,7 @@ void QmmpAudioEngine::prepareEffects(Decoder *d) if(!effect) { effect = Effect::create(factory); - effect->configure(m_ap.sampleRate(), m_ap.channels(), m_ap.format()); + effect->configure(m_ap.sampleRate(), m_ap.channelMap(), m_ap.format()); if (m_ap != effect->audioParameters()) { m_blockedEffects << effect; //list of effects which require restart diff --git a/src/qmmp/recycler.cpp b/src/qmmp/recycler.cpp index 533c453a3..15d31a0e6 100644 --- a/src/qmmp/recycler.cpp +++ b/src/qmmp/recycler.cpp @@ -140,3 +140,8 @@ unsigned long Recycler::size() const { return m_buffer_count * m_block_size; } + +unsigned long Recycler::blockSize() const +{ + return m_block_size; +} diff --git a/src/qmmp/recycler_p.h b/src/qmmp/recycler_p.h index 8ca458643..22443f8de 100644 --- a/src/qmmp/recycler_p.h +++ b/src/qmmp/recycler_p.h @@ -75,6 +75,11 @@ public: */ unsigned long size() const; // size in bytes /*! + * Returns block size in bytes. + */ + unsigned long blockSize() const; // size in bytes + + /*! * Returns mutex pointer. */ QMutex *mutex() |
