Lucas Clemente Vella
2015-Oct-08 05:49 UTC
[opus] [PATCH 0/1] opusenc support for WavPack input
This patch to opus-tools adds optional support to WavPack lossless format as input to opusenc. Like support to FLAC, it depends on an external library, libwavpack, and may be disabled on configure. Lucas Clemente Vella (1): Reading input from WavPack files. Makefile.am | 7 +- configure.ac | 37 ++++++++ src/audio-in.c | 71 ++++++++------- src/opusenc.c | 19 +++- src/opusenc.h | 3 + src/wavpack.c | 276 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/wavpack.h | 40 +++++++++ 7 files changed, 414 insertions(+), 39 deletions(-) create mode 100644 src/wavpack.c create mode 100644 src/wavpack.h -- 2.1.4
Lucas Clemente Vella
2015-Oct-08 05:49 UTC
[opus] [PATCH 1/1] Reading input from WavPack files.
Added optional support for WavPack file format using libwavpack. --- Makefile.am | 7 +- configure.ac | 37 ++++++++ src/audio-in.c | 71 ++++++++------- src/opusenc.c | 19 +++- src/opusenc.h | 3 + src/wavpack.c | 276 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/wavpack.h | 40 +++++++++ 7 files changed, 414 insertions(+), 39 deletions(-) create mode 100644 src/wavpack.c create mode 100644 src/wavpack.h diff --git a/Makefile.am b/Makefile.am index 2f1ac76..e91f9dc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,6 +27,7 @@ noinst_HEADERS = src/arch.h \ win32/unicode_support.h \ src/cpusupport.h \ src/wave_out.h \ + src/wavpack.h \ src/wav_io.h EXTRA_DIST = Makefile.unix \ @@ -51,10 +52,10 @@ dist_man_MANS = man/opusenc.1 man/opusdec.1 man/opusinfo.1 resampler_CPPFLAGS = -DSPX_RESAMPLE_EXPORT= -DRANDOM_PREFIX=opustools -DOUTSIDE_SPEEX -DFLOATING_POINT -opusenc_SOURCES = src/opus_header.c src/opusenc.c src/picture.c src/resample.c src/audio-in.c src/diag_range.c src/flac.c src/lpc.c win32/unicode_support.c +opusenc_SOURCES = src/opus_header.c src/opusenc.c src/picture.c src/resample.c src/audio-in.c src/diag_range.c src/flac.c src/lpc.c src/wavpack.c win32/unicode_support.c opusenc_CPPFLAGS = $(AM_CPPFLAGS) $(resampler_CPPFLAGS) -opusenc_CFLAGS = $(AM_CFLAGS) $(FLAC_CFLAGS) -opusenc_LDADD = $(OPUS_LIBS) $(FLAC_LIBS) $(OGG_LIBS) $(LIBM) +opusenc_CFLAGS = $(AM_CFLAGS) $(FLAC_CFLAGS) $(WAVPACK_CFLAGS) +opusenc_LDADD = $(OPUS_LIBS) $(FLAC_LIBS) $(WAVPACK_LIBS) $(OGG_LIBS) $(LIBM) opusenc_MANS = man/opusenc.1 opusdec_SOURCES = src/opus_header.c src/wav_io.c src/wave_out.c src/opusdec.c src/resample.c src/diag_range.c win32/unicode_support.c diff --git a/configure.ac b/configure.ac index edff53f..9bd173e 100644 --- a/configure.ac +++ b/configure.ac @@ -194,6 +194,42 @@ AS_IF([test "$with_flac" = "yes"], AC_DEFINE([HAVE_LIBFLAC],[1],[FLAC]) ]) +dnl check for wavpack +AC_ARG_WITH([wavpack], + [AS_HELP_STRING([--without-wavpack],[disable WavPack support])],, + [with_wavpack=yes]) + +AS_IF([test "$with_wavpack" = "yes"], + [ + AS_IF([test "$HAVE_PKG_CONFIG" = "yes"], + [PKG_CHECK_MODULES([WAVPACK],[wavpack >= 4.70.0])], + [ + dnl fall back to AC_CHECK_LIB + AC_CHECK_LIB([wavpack],[WavpackGetLibraryVersion], + [ + WAVPACK_LIBS="-lwavpack" + ], + [ + AC_MSG_ERROR([ + libwavpack 4.70 or later is required to build this package! + Please install it or configure with --disable-wavpack. + ]) + ] + ) + AC_CHECK_HEADER([wavpack/wavpack.h],, + [ + AC_MSG_ERROR([ + libwavpack headers are required to build this package! + Please install the development version of libwavpack + or configure with --disable-wavpack. + ]) + ] + ) + ]) + + AC_DEFINE([HAVE_LIBWAVPACK],[1],[Have libwavpack available.]) + ]) + dnl check for pcap AC_CHECK_LIB([pcap], [pcap_open_live], [ AC_DEFINE([HAVE_PCAP], 1, [Define if building with libpcap support]) @@ -315,6 +351,7 @@ AC_MSG_NOTICE([ Assertion checking: ............ ${enable_assertions} FLAC input: .................... ${with_flac} + WavPack input: ................. ${with_wavpack} ------------------------------------------------------------------------ diff --git a/src/audio-in.c b/src/audio-in.c index 8ed037b..1d38b1b 100644 --- a/src/audio-in.c +++ b/src/audio-in.c @@ -75,6 +75,7 @@ #include "lpc.h" #include "opus_header.h" #include "flac.h" +#include "wavpack.h" /* Macros to read header data */ #define READ_U32_LE(buf) \ @@ -95,6 +96,7 @@ input_format formats[] = { {aiff_id, 12, aiff_open, wav_close, "aiff", N_("AIFF/AIFC file reader")}, {flac_id, 4, flac_open, flac_close, "flac", N_("FLAC file reader")}, {oggflac_id, 33, flac_open, flac_close, "ogg", N_("Ogg FLAC file reader")}, + {wavpack_id, 4, wavpack_open, wavpack_close, "wv", N_("WavPack file reader")}, {NULL, 0, NULL, NULL, NULL, NULL} }; @@ -433,6 +435,42 @@ int wav_id(unsigned char *buf, int len) return 1; } +void wav_mask_warn(unsigned int mask, const char *tname) +{ + switch(mask){ + case 1539: /* 4.0 using side surround instead of back */ + fprintf(stderr, _("WARNING: %s file uses side surround instead of rear for quadraphonic;\n" + "remapping side speakers to rear in encoding.\n"), tname); + break; + case 1551: /* 5.1 using side instead of rear */ + fprintf(stderr, _("WARNING: %s file uses side surround instead of rear for 5.1;\n" + "remapping side speakers to rear in encoding.\n"), tname); + break; + case 319: /* 6.1 using rear instead of side */ + fprintf(stderr, _("WARNING: %s file uses rear surround instead of side for 6.1;\n" + "remapping rear speakers to side in encoding.\n"), tname); + break; + case 255: /* 7.1 'Widescreen' */ + fprintf(stderr, _("WARNING: %s file is a 7.1 'Widescreen' channel mapping;\n" + "remapping speakers to Vorbis 7.1 format.\n"), tname); + break; + case 0: /* default/undeclared */ + case 1: /* mono */ + case 3: /* stereo */ + case 51: /* quad */ + case 55: /* 5.0 */ + case 63: /* 5.1 */ + case 1807: /* 6.1 */ + case 1599: /* 7.1 */ + break; + default: + fprintf(stderr, _("WARNING: Unknown WAV surround channel mask: %d\n" + "Blindly mapping speakers using default SMPTE/ITU ordering.\n"), + mask); + break; + } +} + int wav_open(FILE *in, oe_enc_opt *opt, unsigned char *oldbuf, int buflen) { unsigned char buf[40]; @@ -501,38 +539,7 @@ int wav_open(FILE *in, oe_enc_opt *opt, unsigned char *oldbuf, int buflen) format.mask = READ_U32_LE(buf+20); /* warn the user if the format mask is not a supported/expected type */ - switch(format.mask){ - case 1539: /* 4.0 using side surround instead of back */ - fprintf(stderr, _("WARNING: WAV file uses side surround instead of rear for quadraphonic;\n" - "remapping side speakers to rear in encoding.\n")); - break; - case 1551: /* 5.1 using side instead of rear */ - fprintf(stderr, _("WARNING: WAV file uses side surround instead of rear for 5.1;\n" - "remapping side speakers to rear in encoding.\n")); - break; - case 319: /* 6.1 using rear instead of side */ - fprintf(stderr, _("WARNING: WAV file uses rear surround instead of side for 6.1;\n" - "remapping rear speakers to side in encoding.\n")); - break; - case 255: /* 7.1 'Widescreen' */ - fprintf(stderr, _("WARNING: WAV file is a 7.1 'Widescreen' channel mapping;\n" - "remapping speakers to Vorbis 7.1 format.\n")); - break; - case 0: /* default/undeclared */ - case 1: /* mono */ - case 3: /* stereo */ - case 51: /* quad */ - case 55: /* 5.0 */ - case 63: /* 5.1 */ - case 1807: /* 6.1 */ - case 1599: /* 7.1 */ - break; - default: - fprintf(stderr, _("WARNING: Unknown WAV surround channel mask: %d\n" - "Blindly mapping speakers using default SMPTE/ITU ordering.\n"), - format.mask); - break; - } + wav_mask_warn(format.mask, "WAV"); format.format = READ_U16_LE(buf+24); } else diff --git a/src/opusenc.c b/src/opusenc.c index 0f83194..ef002dd 100644 --- a/src/opusenc.c +++ b/src/opusenc.c @@ -111,11 +111,19 @@ void usage(void) printf("Usage: opusenc [options] input_file output_file.opus\n"); printf("\n"); printf("Encodes input_file using Opus.\n"); + printf("It can read the WAV, AIFF,%s%s or raw files.\n", #if defined(HAVE_LIBFLAC) - printf("It can read the WAV, AIFF, FLAC, Ogg/FLAC, or raw files.\n"); + " FLAC, Ogg/FLAC," #else - printf("It can read the WAV, AIFF, or raw files.\n"); + "" #endif + , +#if defined(HAVE_LIBWAVPACK) + " WavPack," +#else + "" +#endif + ); printf("\nGeneral options:\n"); printf(" -h, --help This help\n"); printf(" -V, --version Version information\n"); @@ -380,6 +388,7 @@ int main(int argc, char **argv) inopt.ignorelength=0; inopt.copy_comments=1; inopt.copy_pictures=1; + inopt.is_lossy=0; start_time = time(NULL); srand(((getpid()&65535)<<15)^start_time); @@ -635,6 +644,7 @@ int main(int argc, char **argv) perror(inFile); exit(1); } + inopt.infilename = inFile; } if(inopt.rawmode){ @@ -810,8 +820,9 @@ int main(int argc, char **argv) else if(opus_app==OPUS_APPLICATION_RESTRICTED_LOWDELAY)fprintf(stderr," (low-delay)\n"); else fprintf(stderr," (unknown)\n"); fprintf(stderr,"-----------------------------------------------------\n"); - fprintf(stderr," Input: %0.6gkHz %d channel%s\n", - header.input_sample_rate/1000.,chan,chan<2?"":"s"); + fprintf(stderr," Input: %0.6gkHz %d channel%s%s\n", + header.input_sample_rate/1000.,chan,chan<2?"":"s", + inopt.is_lossy ? " (lossy)" : ""); fprintf(stderr," Output: %d channel%s (",header.channels,header.channels<2?"":"s"); if(header.nb_coupled>0)fprintf(stderr,"%d coupled",header.nb_coupled*2); if(header.nb_streams-header.nb_coupled>0)fprintf(stderr, diff --git a/src/opusenc.h b/src/opusenc.h index 91c1548..ca76349 100644 --- a/src/opusenc.h +++ b/src/opusenc.h @@ -39,6 +39,7 @@ typedef struct int comments_length; int copy_comments; int copy_pictures; + char is_lossy; } oe_enc_opt; void setup_scaler(oe_enc_opt *opt, float scale); @@ -106,4 +107,6 @@ void clear_resample(oe_enc_opt *opt); long wav_read(void *, float *buffer, int samples); long wav_ieee_read(void *, float *buffer, int samples); +void wav_mask_warn(unsigned int mask, const char *tname); + #endif /* __OPUSENC_H */ diff --git a/src/wavpack.c b/src/wavpack.c new file mode 100644 index 0000000..4b2604e --- /dev/null +++ b/src/wavpack.c @@ -0,0 +1,276 @@ +/*Copyright 2012-2015, Xiph.Org Foundation and contributors. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/ +#if defined(HAVE_CONFIG_H) +# include <config.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include "opus_header.h" +#include "wavpack.h" + +#if defined(HAVE_LIBWAVPACK) + +/* Stream reader used by libwavpack: */ + +struct input_stream { + FILE *fd; + unsigned char *buf; + int32_t buf_remaining; +}; + +static int32_t read_bytes (void *is, void *data, int32_t bcount) +{ + struct input_stream *s = is; + int32_t min = 0; + if(s->buf_remaining) { + min = bcount < s->buf_remaining ? bcount : s->buf_remaining; + memcpy(data, s->buf, min); + + bcount -= min; + data = min + (char*)data; + + s->buf_remaining -= min; + s->buf += min; + } + return min + (int32_t) fread (data, 1, bcount, s->fd); +} + +static uint32_t get_pos (void *is) +{ + struct input_stream *s = is; + return ftell (s->fd); +} + +static int set_pos_abs (void *is, uint32_t pos) +{ + struct input_stream *s = is; + s->buf_remaining = 0; + return fseek (s->fd, pos, SEEK_SET); +} + +static int set_pos_rel (void *is, int32_t delta, int mode) +{ + struct input_stream *s = is; + s->buf_remaining = 0; + return fseek (s->fd, delta, mode); +} + +static int push_back_byte (void *is, int c) +{ + struct input_stream *s = is; + if(s->buf_remaining) { + ++s->buf_remaining; + *(--s->buf) = c; + return c; + } + return ungetc (c, s->fd); +} + +static uint32_t get_length (void *is) +{ + struct input_stream *s = is; + struct stat statbuf; + + if (!s->fd || fstat (fileno (s->fd), &statbuf) || !(statbuf.st_mode & S_IFREG)) + return 0; + + return statbuf.st_size; +} + +static int can_seek (void *is) +{ + struct input_stream *s = is; + struct stat statbuf; + + return s->fd && !fstat (fileno (s->fd), &statbuf) && (statbuf.st_mode & S_IFREG); +} + +static WavpackStreamReader reader = { + .read_bytes = read_bytes, + .get_pos = get_pos, + .set_pos_abs = set_pos_abs, + .set_pos_rel = set_pos_rel, + .push_back_byte = push_back_byte, + .get_length = get_length, + .can_seek = can_seek, + .write_bytes = NULL +}; + +/* Decoding data. */ +struct decoding_data +{ + WavpackContext *ctx; + + struct input_stream wv_is; + struct input_stream wvc_is; + + const int *channel_permute; + unsigned short channels; + + char is_fixed; + float fixed_range; +}; + +static long wavpack_read(void *src, float *buffer, int samples) +{ + struct decoding_data *data = src; + int32_t *i_buf = (int32_t *)buffer; + + long ret = WavpackUnpackSamples(data->ctx, i_buf, samples); + + /* Reorder channels */ + if(data->channel_permute) { + int32_t tmp[8]; /* Maximum number of channels with definite mapping. */ + for(int i = 0; i < ret; ++i) { + int32_t *frame = &i_buf[i * data->channels]; + memcpy(tmp, frame, data->channels * 4); + for(unsigned short j = 0; j < data->channels; ++j) + frame[j] = tmp[data->channel_permute[j]]; + } + } + + /* Convert to float */ + if(data->is_fixed) { + for(unsigned i = 0; i < ret * data->channels; ++i) + buffer[i] = i_buf[i] / data->fixed_range; + } + + return ret; +} + +int wavpack_id(unsigned char *buf,int len){ + /* Sanity check. */ + if(len < 4) + return 0; + + return memcmp(buf, "wvpk", 4) == 0; +} + +int wavpack_open(FILE *in,oe_enc_opt *opt,unsigned char *oldbuf,int buflen){ + struct decoding_data *data = calloc(1, sizeof *data); + int flags = OPEN_NORMALIZE; + + /* If we have a filename, we try to find the corresponding wvc file. */ + if(opt->infilename) { + char *wvc_fname = malloc(strlen(opt->infilename) + 2); + + strcpy(wvc_fname, opt->infilename); + strcat(wvc_fname, "c"); + FILE *fd = fopen(wvc_fname, "rb"); + free(wvc_fname); + + if(fd) { + data->wvc_is.fd = fd; + flags |= OPEN_WVC; + } + } + + data->wv_is.fd = in; + data->wv_is.buf = oldbuf; + data->wv_is.buf_remaining = buflen; + + /* Open the stream decoder. */ + { + char err_msg[80]; /* This size is given by libwavpack docs. */ + + data->ctx = WavpackOpenFileInputEx(&reader, &data->wv_is, + data->wvc_is.fd ? &data->wvc_is : NULL, err_msg, flags, 0); + if(!data->ctx) { + fprintf(stderr, _("Error: Parsing of WavPack file failed:\n%s\n"), err_msg); + if(data->wvc_is.fd) + fclose(data->wvc_is.fd); + return 0; + } + } + + int mode = WavpackGetMode(data->ctx); + opt->is_lossy = !(mode & MODE_LOSSLESS); + + /* Determine sample format */ + data->is_fixed = !(mode & MODE_FLOAT); + if(data->is_fixed) { + const float ranges[] = {128.0f, 32768.0f, 8388608.0f, 2147483648.0f}; + data->fixed_range = ranges[WavpackGetBytesPerSample(data->ctx) - 1]; + } + + /* Set the options. */ + opt->read_samples = wavpack_read; + opt->readdata = data; + + data->channels = opt->channels = WavpackGetNumChannels(data->ctx); + opt->total_samples_per_channel = WavpackGetNumSamples(data->ctx); + opt->rate = WavpackGetSampleRate(data->ctx); + opt->samplesize = WavpackGetBitsPerSample(data->ctx); + + /* Wavpack uses the same mask and channel ordering of WAV. */ + unsigned wav_mask = WavpackGetChannelMask(data->ctx); + wav_mask_warn(wav_mask, "WavPack"); + + if (opt->channels <= 8) + /* Where we know the mappings, use them. */ + data->channel_permute = wav_permute_matrix[data->channels-1]; + else + data->channel_permute = NULL; + + return 1; +} + +void wavpack_close(void *client_data){ + struct decoding_data *data = client_data; + + WavpackCloseFile(data->ctx); + + if(data->wvc_is.fd) + fclose(data->wvc_is.fd); + + free(data); +} + +#else + +/*WavPack support is disabled.*/ + +int wavpack_id(unsigned char *buf,int len){ + (void)buf; + (void)len; + return 0; +} + +int wavpack_open(FILE *in,oe_enc_opt *opt,unsigned char *oldbuf,int buflen){ + (void)in; + (void)opt; + (void)oldbuf; + (void)buflen; + return 0; +} + +void wavpack_close(void *client_data){ + (void)client_data; +} + +#endif diff --git a/src/wavpack.h b/src/wavpack.h new file mode 100644 index 0000000..2a5c39c --- /dev/null +++ b/src/wavpack.h @@ -0,0 +1,40 @@ +/*Copyright 2012-2015, Xiph.Org Foundation and contributors. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/ +#ifndef __WAVPACK_H +# define __WAVPACK_H + +#include <stdio.h> + +# if defined(HAVE_LIBWAVPACK) +# include <wavpack/wavpack.h> +# endif + +#include "opusenc.h" + +int wavpack_id(unsigned char *buf,int len); +int wavpack_open(FILE *in,oe_enc_opt *opt,unsigned char *oldbuf,int buflen); +void wavpack_close(void *client_data); + +#endif -- 2.1.4
A general comment: it seems like there is no support for reading metadata tags or replaygain information. Most users of this feature are going to be encoding music from their personal collection into opus, which means that metadata is pretty important. I'm good with support for this in a future patch, but currently this means that reading wavpack from opusenc is worse than converting to flac first. Maybe there should be a warning printed, or it should be off by default for now? On 10/07/2015 10:49 PM, Lucas Clemente Vella wrote:> This patch to opus-tools adds optional support to WavPack > lossless format as input to opusenc. > > Like support to FLAC, it depends on an external library, > libwavpack, and may be disabled on configure. > > Lucas Clemente Vella (1): > Reading input from WavPack files. > > Makefile.am | 7 +- > configure.ac | 37 ++++++++ > src/audio-in.c | 71 ++++++++------- > src/opusenc.c | 19 +++- > src/opusenc.h | 3 + > src/wavpack.c | 276 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ > src/wavpack.h | 40 +++++++++ > 7 files changed, 414 insertions(+), 39 deletions(-) > create mode 100644 src/wavpack.c > create mode 100644 src/wavpack.h >