summaryrefslogtreecommitdiff
path: root/src/pcm
diff options
context:
space:
mode:
authorMax Kellermann <max@musicpd.org>2020-01-18 20:07:09 +0100
committerMax Kellermann <max@musicpd.org>2020-01-18 20:24:59 +0100
commitcd612c4eef0afc45f2ef7268224a789d89eeda56 (patch)
tree4019518f801ce405d9cbbf17596ce64cf0ce6c59 /src/pcm
parent914ad261edd4bf50b5c58edf76d744d3a36bd70e (diff)
AudioFormat: move to pcm/
Diffstat (limited to 'src/pcm')
-rw-r--r--src/pcm/AudioFormat.cxx75
-rw-r--r--src/pcm/AudioFormat.hxx234
-rw-r--r--src/pcm/AudioParser.cxx187
-rw-r--r--src/pcm/AudioParser.hxx42
-rw-r--r--src/pcm/CheckAudioFormat.cxx57
-rw-r--r--src/pcm/CheckAudioFormat.hxx44
-rw-r--r--src/pcm/meson.build6
7 files changed, 642 insertions, 3 deletions
diff --git a/src/pcm/AudioFormat.cxx b/src/pcm/AudioFormat.cxx
new file mode 100644
index 000000000..a3fc7f5d3
--- /dev/null
+++ b/src/pcm/AudioFormat.cxx
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2003-2020 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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 "AudioFormat.hxx"
+#include "util/StringBuffer.hxx"
+
+#include <assert.h>
+#include <stdio.h>
+
+void
+AudioFormat::ApplyMask(AudioFormat mask) noexcept
+{
+ assert(IsValid());
+ assert(mask.IsMaskValid());
+
+ if (mask.sample_rate != 0)
+ sample_rate = mask.sample_rate;
+
+ if (mask.format != SampleFormat::UNDEFINED)
+ format = mask.format;
+
+ if (mask.channels != 0)
+ channels = mask.channels;
+
+ assert(IsValid());
+}
+
+StringBuffer<24>
+ToString(const AudioFormat af) noexcept
+{
+ StringBuffer<24> buffer;
+ char *p = buffer.data();
+
+ if (af.format == SampleFormat::DSD && af.sample_rate > 0 &&
+ af.sample_rate % 44100 == 0) {
+ /* use shortcuts such as "dsd64" which implies the
+ sample rate */
+ p += sprintf(p, "dsd%u:", af.sample_rate * 8 / 44100);
+ } else {
+ const char *sample_format = af.format != SampleFormat::UNDEFINED
+ ? sample_format_to_string(af.format)
+ : "*";
+
+ if (af.sample_rate > 0)
+ p += sprintf(p, "%u:%s:", af.sample_rate,
+ sample_format);
+ else
+ p += sprintf(p, "*:%s:", sample_format);
+ }
+
+ if (af.channels > 0)
+ p += sprintf(p, "%u", af.channels);
+ else {
+ *p++ = '*';
+ *p = 0;
+ }
+
+ return buffer;
+}
diff --git a/src/pcm/AudioFormat.hxx b/src/pcm/AudioFormat.hxx
new file mode 100644
index 000000000..710ea7e25
--- /dev/null
+++ b/src/pcm/AudioFormat.hxx
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2003-2020 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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 MPD_AUDIO_FORMAT_HXX
+#define MPD_AUDIO_FORMAT_HXX
+
+#include "pcm/SampleFormat.hxx"
+#include "pcm/ChannelDefs.hxx"
+#include "util/Compiler.h"
+
+#include <cstdint>
+
+#include <stddef.h>
+
+template<size_t CAPACITY> class StringBuffer;
+
+/**
+ * This structure describes the format of a raw PCM stream.
+ */
+struct AudioFormat {
+ /**
+ * The sample rate in Hz. A better name for this attribute is
+ * "frame rate", because technically, you have two samples per
+ * frame in stereo sound.
+ */
+ uint32_t sample_rate;
+
+ /**
+ * The format samples are stored in. See the #sample_format
+ * enum for valid values.
+ */
+ SampleFormat format;
+
+ /**
+ * The number of channels.
+ *
+ * Channel order follows the FLAC convention
+ * (https://xiph.org/flac/format.html):
+ *
+ * - 1 channel: mono
+ * - 2 channels: left, right
+ * - 3 channels: left, right, center
+ * - 4 channels: front left, front right, back left, back right
+ * - 5 channels: front left, front right, front center, back/surround left, back/surround right
+ * - 6 channels: front left, front right, front center, LFE, back/surround left, back/surround right
+ * - 7 channels: front left, front right, front center, LFE, back center, side left, side right
+ * - 8 channels: front left, front right, front center, LFE, back left, back right, side left, side right
+ */
+ uint8_t channels;
+
+ AudioFormat() = default;
+
+ constexpr AudioFormat(uint32_t _sample_rate,
+ SampleFormat _format, uint8_t _channels)
+ :sample_rate(_sample_rate),
+ format(_format), channels(_channels) {}
+
+ static constexpr AudioFormat Undefined() {
+ return AudioFormat(0, SampleFormat::UNDEFINED,0);
+ }
+
+ /**
+ * Clears the object, i.e. sets all attributes to an undefined
+ * (invalid) value.
+ */
+ void Clear() {
+ sample_rate = 0;
+ format = SampleFormat::UNDEFINED;
+ channels = 0;
+ }
+
+ /**
+ * Checks whether the object has a defined value.
+ */
+ constexpr bool IsDefined() const {
+ return sample_rate != 0;
+ }
+
+ /**
+ * Checks whether the object is full, i.e. all attributes are
+ * defined. This is more complete than IsDefined(), but
+ * slower.
+ */
+ constexpr bool IsFullyDefined() const {
+ return sample_rate != 0 && format != SampleFormat::UNDEFINED &&
+ channels != 0;
+ }
+
+ /**
+ * Checks whether the object has at least one defined value.
+ */
+ constexpr bool IsMaskDefined() const {
+ return sample_rate != 0 || format != SampleFormat::UNDEFINED ||
+ channels != 0;
+ }
+
+ bool IsValid() const;
+ bool IsMaskValid() const;
+
+ constexpr bool operator==(const AudioFormat other) const {
+ return sample_rate == other.sample_rate &&
+ format == other.format &&
+ channels == other.channels;
+ }
+
+ constexpr bool operator!=(const AudioFormat other) const {
+ return !(*this == other);
+ }
+
+ void ApplyMask(AudioFormat mask) noexcept;
+
+ gcc_pure
+ AudioFormat WithMask(AudioFormat mask) const noexcept {
+ AudioFormat result = *this;
+ result.ApplyMask(mask);
+ return result;
+ }
+
+ gcc_pure
+ bool MatchMask(AudioFormat mask) const noexcept {
+ return WithMask(mask) == *this;
+ }
+
+ /**
+ * Returns the size of each (mono) sample in bytes.
+ */
+ unsigned GetSampleSize() const;
+
+ /**
+ * Returns the size of each full frame in bytes.
+ */
+ unsigned GetFrameSize() const;
+
+ template<typename D>
+ constexpr auto TimeToFrames(D t) const noexcept {
+ using Period = typename D::period;
+ return ((t.count() * sample_rate) / Period::den) * Period::num;
+ }
+
+ template<typename D>
+ constexpr size_t TimeToSize(D t) const noexcept {
+ return size_t(size_t(TimeToFrames(t)) * GetFrameSize());
+ }
+
+ template<typename D>
+ constexpr D FramesToTime(std::uintmax_t size) const noexcept {
+ using Rep = typename D::rep;
+ using Period = typename D::period;
+ return D(((Rep(1) * size / Period::num) * Period::den) / sample_rate);
+ }
+
+ template<typename D>
+ constexpr D SizeToTime(std::uintmax_t size) const noexcept {
+ return FramesToTime<D>(size / GetFrameSize());
+ }
+};
+
+/**
+ * Checks whether the sample rate is valid.
+ *
+ * @param sample_rate the sample rate in Hz
+ */
+constexpr bool
+audio_valid_sample_rate(unsigned sample_rate) noexcept
+{
+ return sample_rate > 0 && sample_rate < (1 << 30);
+}
+
+/**
+ * Returns false if the format is not valid for playback with MPD.
+ * This function performs some basic validity checks.
+ */
+inline bool
+AudioFormat::IsValid() const
+{
+ return audio_valid_sample_rate(sample_rate) &&
+ audio_valid_sample_format(format) &&
+ audio_valid_channel_count(channels);
+}
+
+/**
+ * Returns false if the format mask is not valid for playback with
+ * MPD. This function performs some basic validity checks.
+ */
+inline bool
+AudioFormat::IsMaskValid() const
+{
+ return (sample_rate == 0 ||
+ audio_valid_sample_rate(sample_rate)) &&
+ (format == SampleFormat::UNDEFINED ||
+ audio_valid_sample_format(format)) &&
+ (channels == 0 || audio_valid_channel_count(channels));
+}
+
+inline unsigned
+AudioFormat::GetSampleSize() const
+{
+ return sample_format_size(format);
+}
+
+inline unsigned
+AudioFormat::GetFrameSize() const
+{
+ return GetSampleSize() * channels;
+}
+
+/**
+ * Renders the #AudioFormat object into a string, e.g. for printing
+ * it in a log file.
+ *
+ * @param af the #AudioFormat object
+ * @return the string buffer
+ */
+gcc_const
+StringBuffer<24>
+ToString(AudioFormat af) noexcept;
+
+#endif
diff --git a/src/pcm/AudioParser.cxx b/src/pcm/AudioParser.cxx
new file mode 100644
index 000000000..ad8d0ad29
--- /dev/null
+++ b/src/pcm/AudioParser.cxx
@@ -0,0 +1,187 @@
+/*
+ * Copyright 2003-2020 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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.
+ */
+
+/*
+ * Parser functions for audio related objects.
+ *
+ */
+
+#include "AudioParser.hxx"
+#include "AudioFormat.hxx"
+#include "util/RuntimeError.hxx"
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+static uint32_t
+ParseSampleRate(const char *src, bool mask, const char **endptr_r)
+{
+ unsigned long value;
+ char *endptr;
+
+ if (mask && *src == '*') {
+ *endptr_r = src + 1;
+ return 0;
+ }
+
+ value = strtoul(src, &endptr, 10);
+ if (endptr == src) {
+ throw std::invalid_argument("Failed to parse the sample rate");
+ } else if (!audio_valid_sample_rate(value))
+ throw FormatInvalidArgument("Invalid sample rate: %lu", value);
+
+ *endptr_r = endptr;
+ return value;
+}
+
+static SampleFormat
+ParseSampleFormat(const char *src, bool mask, const char **endptr_r)
+{
+ unsigned long value;
+ char *endptr;
+ SampleFormat sample_format;
+
+ if (mask && *src == '*') {
+ *endptr_r = src + 1;
+ return SampleFormat::UNDEFINED;
+ }
+
+ if (*src == 'f') {
+ *endptr_r = src + 1;
+ return SampleFormat::FLOAT;
+ }
+
+ if (memcmp(src, "dsd", 3) == 0) {
+ *endptr_r = src + 3;
+ return SampleFormat::DSD;
+ }
+
+ value = strtoul(src, &endptr, 10);
+ if (endptr == src)
+ throw std::invalid_argument("Failed to parse the sample format");
+
+ switch (value) {
+ case 8:
+ sample_format = SampleFormat::S8;
+ break;
+
+ case 16:
+ sample_format = SampleFormat::S16;
+ break;
+
+ case 24:
+ if (memcmp(endptr, "_3", 2) == 0)
+ /* for backwards compatibility */
+ endptr += 2;
+
+ sample_format = SampleFormat::S24_P32;
+ break;
+
+ case 32:
+ sample_format = SampleFormat::S32;
+ break;
+
+ default:
+ throw FormatInvalidArgument("Invalid sample format: %lu",
+ value);
+ }
+
+ assert(audio_valid_sample_format(sample_format));
+
+ *endptr_r = endptr;
+ return sample_format;
+}
+
+static uint8_t
+ParseChannelCount(const char *src, bool mask, const char **endptr_r)
+{
+ unsigned long value;
+ char *endptr;
+
+ if (mask && *src == '*') {
+ *endptr_r = src + 1;
+ return 0;
+ }
+
+ value = strtoul(src, &endptr, 10);
+ if (endptr == src)
+ throw std::invalid_argument("Failed to parse the channel count");
+ else if (!audio_valid_channel_count(value))
+ throw FormatInvalidArgument("Invalid channel count: %u",
+ value);
+
+ *endptr_r = endptr;
+ return value;
+}
+
+AudioFormat
+ParseAudioFormat(const char *src, bool mask)
+{
+ AudioFormat dest;
+ dest.Clear();
+
+ if (strncmp(src, "dsd", 3) == 0) {
+ /* allow format specifications such as "dsd64" which
+ implies the sample rate */
+
+ char *endptr;
+ auto dsd = strtoul(src + 3, &endptr, 10);
+ if (endptr > src + 3 && *endptr == ':' &&
+ dsd >= 32 && dsd <= 4096 && dsd % 2 == 0) {
+ dest.sample_rate = dsd * 44100 / 8;
+ dest.format = SampleFormat::DSD;
+
+ src = endptr + 1;
+ dest.channels = ParseChannelCount(src, mask, &src);
+ if (*src != 0)
+ throw FormatInvalidArgument("Extra data after channel count: %s",
+ src);
+
+ return dest;
+ }
+ }
+
+ /* parse sample rate */
+
+ dest.sample_rate = ParseSampleRate(src, mask, &src);
+
+ if (*src++ != ':')
+ throw std::invalid_argument("Sample format missing");
+
+ /* parse sample format */
+
+ dest.format = ParseSampleFormat(src, mask, &src);
+
+ if (*src++ != ':')
+ throw std::invalid_argument("Channel count missing");
+
+ /* parse channel count */
+
+ dest.channels = ParseChannelCount(src, mask, &src);
+
+ if (*src != 0)
+ throw FormatInvalidArgument("Extra data after channel count: %s",
+ src);
+
+ assert(mask
+ ? dest.IsMaskValid()
+ : dest.IsValid());
+ return dest;
+}
diff --git a/src/pcm/AudioParser.hxx b/src/pcm/AudioParser.hxx
new file mode 100644
index 000000000..b105029e5
--- /dev/null
+++ b/src/pcm/AudioParser.hxx
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2003-2020 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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.
+ */
+
+/** \file
+ *
+ * Parser functions for audio related objects.
+ */
+
+#ifndef MPD_AUDIO_PARSER_HXX
+#define MPD_AUDIO_PARSER_HXX
+
+struct AudioFormat;
+
+/**
+ * Parses a string in the form "SAMPLE_RATE:BITS:CHANNELS" into an
+ * #AudioFormat.
+ *
+ * Throws #std::runtime_error on error.
+ *
+ * @param src the input string
+ * @param mask if true, then "*" is allowed for any number of items
+ */
+AudioFormat
+ParseAudioFormat(const char *src, bool mask);
+
+#endif
diff --git a/src/pcm/CheckAudioFormat.cxx b/src/pcm/CheckAudioFormat.cxx
new file mode 100644
index 000000000..c3de83824
--- /dev/null
+++ b/src/pcm/CheckAudioFormat.cxx
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2003-2020 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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 "CheckAudioFormat.hxx"
+#include "AudioFormat.hxx"
+#include "util/RuntimeError.hxx"
+
+void
+CheckSampleRate(unsigned long sample_rate)
+{
+ if (!audio_valid_sample_rate(sample_rate))
+ throw FormatRuntimeError("Invalid sample rate: %lu",
+ sample_rate);
+}
+
+void
+CheckSampleFormat(SampleFormat sample_format)
+{
+ if (!audio_valid_sample_format(sample_format))
+ throw FormatRuntimeError("Invalid sample format: %u",
+ unsigned(sample_format));
+}
+
+void
+CheckChannelCount(unsigned channels)
+{
+ if (!audio_valid_channel_count(channels))
+ throw FormatRuntimeError("Invalid channel count: %u",
+ channels);
+}
+
+AudioFormat
+CheckAudioFormat(unsigned long sample_rate,
+ SampleFormat sample_format, unsigned channels)
+{
+ CheckSampleRate(sample_rate);
+ CheckSampleFormat(sample_format);
+ CheckChannelCount(channels);
+
+ return AudioFormat(sample_rate, sample_format, channels);
+}
diff --git a/src/pcm/CheckAudioFormat.hxx b/src/pcm/CheckAudioFormat.hxx
new file mode 100644
index 000000000..71d42fbea
--- /dev/null
+++ b/src/pcm/CheckAudioFormat.hxx
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2003-2020 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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 MPD_CHECK_AUDIO_FORMAT_HXX
+#define MPD_CHECK_AUDIO_FORMAT_HXX
+
+#include "AudioFormat.hxx"
+
+void
+CheckSampleRate(unsigned long sample_rate);
+
+void
+CheckSampleFormat(SampleFormat sample_format);
+
+void
+CheckChannelCount(unsigned sample_format);
+
+/**
+ * Check #AudioFormat attributes and construct an #AudioFormat
+ * instance.
+ *
+ * Throws #std::runtime_error on error.
+ */
+AudioFormat
+CheckAudioFormat(unsigned long sample_rate,
+ SampleFormat sample_format, unsigned channels);
+
+#endif
diff --git a/src/pcm/meson.build b/src/pcm/meson.build
index 7cdb2f83a..75eb5fb19 100644
--- a/src/pcm/meson.build
+++ b/src/pcm/meson.build
@@ -1,7 +1,7 @@
pcm_basic_sources = [
- '../CheckAudioFormat.cxx',
- '../AudioFormat.cxx',
- '../AudioParser.cxx',
+ 'CheckAudioFormat.cxx',
+ 'AudioFormat.cxx',
+ 'AudioParser.cxx',
'SampleFormat.cxx',
'Interleave.cxx',
'Buffer.cxx',