From 1abfcc56af7de73c2088d7971f744778b5842ed8 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 15 Jan 2010 09:23:36 +0100 Subject: [PATCH] audio_format: support packed 24 bit samples --- Makefile.am | 3 ++ NEWS | 1 + doc/user.xml | 11 +++++ src/audio_format.c | 3 ++ src/audio_format.h | 9 ++++ src/audio_parser.c | 7 +++- src/decoder/flac_pcm.c | 1 + src/pcm_convert.c | 48 ++++++++++++++++++++++ src/pcm_convert.h | 3 ++ src/pcm_format.c | 41 +++++++++++++++++++ src/pcm_pack.c | 93 ++++++++++++++++++++++++++++++++++++++++++ src/pcm_pack.h | 59 +++++++++++++++++++++++++++ 12 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 src/pcm_pack.c create mode 100644 src/pcm_pack.h diff --git a/Makefile.am b/Makefile.am index f9e6a86fa..1197483e9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -279,6 +279,7 @@ src_mpd_SOURCES = \ src/pcm_mix.c \ src/pcm_byteswap.c \ src/pcm_channels.c \ + src/pcm_pack.c \ src/pcm_format.c \ src/pcm_resample.c \ src/pcm_resample_fallback.c \ @@ -888,6 +889,7 @@ test_run_filter_SOURCES = test/run_filter.c \ src/conf.c src/tokenizer.c src/utils.c \ src/pcm_volume.c src/pcm_convert.c src/pcm_byteswap.c \ src/pcm_format.c src/pcm_channels.c src/pcm_dither.c \ + src/pcm_pack.c \ src/pcm_resample.c src/pcm_resample_fallback.c \ src/audio_check.c \ src/audio_format.c \ @@ -935,6 +937,7 @@ test_run_convert_SOURCES = test/run_convert.c \ src/audio_parser.c \ src/pcm_channels.c \ src/pcm_format.c \ + src/pcm_pack.c \ src/pcm_dither.c \ src/pcm_byteswap.c \ src/pcm_resample.c \ diff --git a/NEWS b/NEWS index fae16e339..0702c9436 100644 --- a/NEWS +++ b/NEWS @@ -78,6 +78,7 @@ ver 0.16 (20??/??/??) * log: redirect stdout/stderr to /dev/null if syslog is used * set the close-on-exec flag on all file descriptors * pcm_volume, pcm_mix: implemented 32 bit support +* support packed 24 bit samples * CUE sheet support * obey $(sysconfdir) for default mpd.conf location * build with large file support by default diff --git a/doc/user.xml b/doc/user.xml index 09868eb33..15b20d521 100644 --- a/doc/user.xml +++ b/doc/user.xml @@ -313,6 +313,17 @@ cd mpd-version *:*:* is equal to not having a format specification. + + The following values are valid for + bits: 8 + (signed 8 bit integer samples), + 16, 24 (signed + 24 bit integer samples padded to 32 bit), + 24_3 (signed 24 bit integer + samples, no padding, 3 bytes per sample), + 32 (signed 32 bit integer + samples). + diff --git a/src/audio_format.c b/src/audio_format.c index cdb0f25b2..13403fbc1 100644 --- a/src/audio_format.c +++ b/src/audio_format.c @@ -41,6 +41,9 @@ sample_format_to_string(enum sample_format format) case SAMPLE_FORMAT_S16: return "16"; + case SAMPLE_FORMAT_S24: + return "24_3"; + case SAMPLE_FORMAT_S24_P32: return "24"; diff --git a/src/audio_format.h b/src/audio_format.h index 7cd606b65..dd32731c3 100644 --- a/src/audio_format.h +++ b/src/audio_format.h @@ -29,6 +29,11 @@ enum sample_format { SAMPLE_FORMAT_S8, SAMPLE_FORMAT_S16, + /** + * Signed 24 bit integer samples, without padding. + */ + SAMPLE_FORMAT_S24, + /** * Signed 24 bit integer samples, packed in 32 bit integers * (the most significant byte is filled with the sign bit). @@ -156,6 +161,7 @@ audio_valid_sample_format(enum sample_format format) switch (format) { case SAMPLE_FORMAT_S8: case SAMPLE_FORMAT_S16: + case SAMPLE_FORMAT_S24: case SAMPLE_FORMAT_S24_P32: case SAMPLE_FORMAT_S32: return true; @@ -235,6 +241,9 @@ static inline unsigned audio_format_sample_size(const struct audio_format *af) case SAMPLE_FORMAT_S16: return 2; + case SAMPLE_FORMAT_S24: + return 3; + case SAMPLE_FORMAT_S24_P32: case SAMPLE_FORMAT_S32: return 4; diff --git a/src/audio_parser.c b/src/audio_parser.c index 60318fc99..039ffa1ab 100644 --- a/src/audio_parser.c +++ b/src/audio_parser.c @@ -28,6 +28,7 @@ #include "audio_check.h" #include +#include #include /** @@ -97,7 +98,11 @@ parse_sample_format(const char *src, bool mask, break; case 24: - sample_format = SAMPLE_FORMAT_S24_P32; + if (memcmp(endptr, "_3", 2) == 0) { + sample_format = SAMPLE_FORMAT_S24; + endptr += 2; + } else + sample_format = SAMPLE_FORMAT_S24_P32; break; case 32: diff --git a/src/decoder/flac_pcm.c b/src/decoder/flac_pcm.c index 3130ba35e..bf6e2612c 100644 --- a/src/decoder/flac_pcm.c +++ b/src/decoder/flac_pcm.c @@ -101,6 +101,7 @@ flac_convert(void *dest, position, end); break; + case SAMPLE_FORMAT_S24: case SAMPLE_FORMAT_UNDEFINED: /* unreachable */ assert(false); diff --git a/src/pcm_convert.c b/src/pcm_convert.c index 06e4d05a2..5fe89b53a 100644 --- a/src/pcm_convert.c +++ b/src/pcm_convert.c @@ -22,6 +22,7 @@ #include "pcm_channels.h" #include "pcm_format.h" #include "pcm_byteswap.h" +#include "pcm_pack.h" #include "audio_format.h" #include @@ -40,6 +41,7 @@ void pcm_convert_init(struct pcm_convert_state *state) pcm_dither_24_init(&state->dither); pcm_buffer_init(&state->format_buffer); + pcm_buffer_init(&state->pack_buffer); pcm_buffer_init(&state->channels_buffer); pcm_buffer_init(&state->byteswap_buffer); } @@ -49,6 +51,7 @@ void pcm_convert_deinit(struct pcm_convert_state *state) pcm_resample_deinit(&state->resample); pcm_buffer_deinit(&state->format_buffer); + pcm_buffer_deinit(&state->pack_buffer); pcm_buffer_deinit(&state->channels_buffer); pcm_buffer_deinit(&state->byteswap_buffer); } @@ -164,6 +167,45 @@ pcm_convert_24(struct pcm_convert_state *state, return buf; } +/** + * Convert to 24 bit packed samples (aka S24_3LE / S24_3BE). + */ +static const void * +pcm_convert_24_packed(struct pcm_convert_state *state, + const struct audio_format *src_format, + const void *src_buffer, size_t src_size, + const struct audio_format *dest_format, + size_t *dest_size_r, + GError **error_r) +{ + assert(dest_format->format == SAMPLE_FORMAT_S24); + + /* use the normal 24 bit conversion first */ + + struct audio_format audio_format; + audio_format_init(&audio_format, dest_format->sample_rate, + SAMPLE_FORMAT_S24_P32, dest_format->channels); + + const int32_t *buffer; + size_t buffer_size; + + buffer = pcm_convert_24(state, src_format, src_buffer, src_size, + &audio_format, &buffer_size, error_r); + if (buffer == NULL) + return NULL; + + /* now convert to packed 24 bit */ + + unsigned num_samples = buffer_size / 4; + size_t dest_size = num_samples * 3; + + uint8_t *dest = pcm_buffer_get(&state->pack_buffer, dest_size); + pcm_pack_24(dest, buffer, num_samples, dest_format->reverse_endian); + + *dest_size_r = dest_size; + return dest; +} + static const int32_t * pcm_convert_32(struct pcm_convert_state *state, const struct audio_format *src_format, @@ -234,6 +276,12 @@ pcm_convert(struct pcm_convert_state *state, dest_format, dest_size_r, error_r); + case SAMPLE_FORMAT_S24: + return pcm_convert_24_packed(state, + src_format, src, src_size, + dest_format, dest_size_r, + error_r); + case SAMPLE_FORMAT_S24_P32: return pcm_convert_24(state, src_format, src, src_size, diff --git a/src/pcm_convert.h b/src/pcm_convert.h index a940f2169..01ba2c787 100644 --- a/src/pcm_convert.h +++ b/src/pcm_convert.h @@ -39,6 +39,9 @@ struct pcm_convert_state { /** the buffer for converting the sample format */ struct pcm_buffer format_buffer; + /** the buffer for converting to/from packed samples */ + struct pcm_buffer pack_buffer; + /** the buffer for converting the channel count */ struct pcm_buffer channels_buffer; diff --git a/src/pcm_format.c b/src/pcm_format.c index 54682354e..3fd76a987 100644 --- a/src/pcm_format.c +++ b/src/pcm_format.c @@ -21,6 +21,7 @@ #include "pcm_format.h" #include "pcm_dither.h" #include "pcm_buffer.h" +#include "pcm_pack.h" static void pcm_convert_8_to_16(int16_t *out, const int8_t *in, @@ -48,6 +49,15 @@ pcm_convert_32_to_16(struct pcm_dither *dither, pcm_dither_32_to_16(dither, out, in, num_samples); } +static int32_t * +pcm_convert_24_to_24p32(struct pcm_buffer *buffer, const uint8_t *src, + unsigned num_samples) +{ + int32_t *dest = pcm_buffer_get(buffer, num_samples * 4); + pcm_unpack_24(dest, src, num_samples, false); + return dest; +} + const int16_t * pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, enum sample_format src_format, const void *src, @@ -55,6 +65,7 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, { unsigned num_samples; int16_t *dest; + int32_t *dest32; switch (src_format) { case SAMPLE_FORMAT_UNDEFINED: @@ -74,6 +85,19 @@ pcm_convert_to_16(struct pcm_buffer *buffer, struct pcm_dither *dither, *dest_size_r = src_size; return src; + case SAMPLE_FORMAT_S24: + /* convert to S24_P32 first */ + num_samples = src_size / 3; + + dest32 = pcm_convert_24_to_24p32(buffer, src, num_samples); + dest = (int16_t *)dest32; + + /* convert to 16 bit in-place */ + *dest_size_r = num_samples * sizeof(*dest); + pcm_convert_24_to_16(dither, dest, dest32, + num_samples); + return dest; + case SAMPLE_FORMAT_S24_P32: num_samples = src_size / 4; *dest_size_r = num_samples * sizeof(*dest); @@ -158,6 +182,12 @@ pcm_convert_to_24(struct pcm_buffer *buffer, num_samples); return dest; + case SAMPLE_FORMAT_S24: + num_samples = src_size / 3; + *dest_size_r = num_samples * sizeof(*dest); + + return pcm_convert_24_to_24p32(buffer, src, num_samples); + case SAMPLE_FORMAT_S24_P32: *dest_size_r = src_size; return src; @@ -235,6 +265,17 @@ pcm_convert_to_32(struct pcm_buffer *buffer, num_samples); return dest; + case SAMPLE_FORMAT_S24: + /* convert to S24_P32 first */ + num_samples = src_size / 3; + + dest = pcm_convert_24_to_24p32(buffer, src, num_samples); + + /* convert to 32 bit in-place */ + *dest_size_r = num_samples * sizeof(*dest); + pcm_convert_24_to_32(dest, dest, num_samples); + return dest; + case SAMPLE_FORMAT_S24_P32: num_samples = src_size / 4; *dest_size_r = num_samples * sizeof(*dest); diff --git a/src/pcm_pack.c b/src/pcm_pack.c new file mode 100644 index 000000000..9af0ab1ed --- /dev/null +++ b/src/pcm_pack.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2010 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 "pcm_pack.h" + +#include + +static void +pack_sample(uint8_t *dest, const int32_t *src0, bool reverse_endian) +{ + const uint8_t *src = (const uint8_t *)src0; + + if ((G_BYTE_ORDER == G_BIG_ENDIAN) != reverse_endian) + ++src; + + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src++; +} + +void +pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples, + bool reverse_endian) +{ + /* duplicate loop to help the compiler's optimizer (constant + parameter to the pack_sample() inline function) */ + + if (G_LIKELY(!reverse_endian)) { + while (num_samples-- > 0) { + pack_sample(dest, src++, false); + dest += 3; + } + } else { + while (num_samples-- > 0) { + pack_sample(dest, src++, true); + dest += 3; + } + } +} + +static void +unpack_sample(int32_t *dest0, const uint8_t *src, bool reverse_endian) +{ + uint8_t *dest = (uint8_t *)dest0; + + if ((G_BYTE_ORDER == G_BIG_ENDIAN) != reverse_endian) + /* extend the sign bit to the most fourth byte */ + *dest++ = *src & 0x80 ? 0xff : 0x00; + + *dest++ = *src++; + *dest++ = *src++; + *dest++ = *src; + + if ((G_BYTE_ORDER == G_LITTLE_ENDIAN) != reverse_endian) + /* extend the sign bit to the most fourth byte */ + *dest++ = *src & 0x80 ? 0xff : 0x00; +} + +void +pcm_unpack_24(int32_t *dest, const uint8_t *src, unsigned num_samples, + bool reverse_endian) +{ + /* duplicate loop to help the compiler's optimizer (constant + parameter to the unpack_sample() inline function) */ + + if (G_LIKELY(!reverse_endian)) { + while (num_samples-- > 0) { + unpack_sample(dest++, src, false); + src += 3; + } + } else { + while (num_samples-- > 0) { + unpack_sample(dest++, src, true); + src += 3; + } + } +} diff --git a/src/pcm_pack.h b/src/pcm_pack.h new file mode 100644 index 000000000..3c99eaa35 --- /dev/null +++ b/src/pcm_pack.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2003-2010 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 + * + * Library for working with packed 24 bit samples. + */ + +#ifndef PCM_PACK_H +#define PCM_PACK_H + +#include +#include + +/** + * Converts padded 24 bit samples (4 bytes per sample) to packed 24 + * bit samples (3 bytes per sample). + * + * This function can be used to convert a buffer in-place. + * + * @param dest the destination buffer (array of triples) + * @param src the source buffer + * @param num_samples the number of samples to convert + * @param reverse_endian is src and dest in non-host byte order? + */ +void +pcm_pack_24(uint8_t *dest, const int32_t *src, unsigned num_samples, + bool reverse_endian); + +/** + * Converts packed 24 bit samples (3 bytes per sample) to padded 24 + * bit samples (4 bytes per sample). + * + * @param dest the destination buffer + * @param src the source buffer (array of triples) + * @param num_samples the number of samples to convert + * @param reverse_endian is src and dest in non-host byte order? + */ +void +pcm_unpack_24(int32_t *dest, const uint8_t *src, unsigned num_samples, + bool reverse_endian); + +#endif