ref: 7436353f7a19f8cdf915fea36553dc33d9891ebb
parent: 1ba359ccc364a34b29b5e25f33f39ca6bebbb21f
author: Paul Brossier <piem@piem.org>
date: Wed Dec 12 19:37:44 EST 2018
[io] add sink_flac
--- /dev/null
+++ b/src/io/sink_flac.c
@@ -1,0 +1,349 @@
+/*
+ Copyright (C) 2018 Paul Brossier <piem@aubio.org>
+
+ This file is part of aubio.
+
+ aubio 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 3 of the License, or
+ (at your option) any later version.
+
+ aubio 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 aubio. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+/*
+ This file is largely inspired by `examples/c/encode/file/main.c` in the
+ flac source package (versions 1.3.2 and later) available online at
+ https://xiph.org/flac/
+*/
+
+#include "aubio_priv.h"
+
+#ifdef HAVE_FLAC
+
+#include "io/ioutils.h"
+#include "fmat.h"
+
+#include <FLAC/metadata.h>
+#include <FLAC/stream_encoder.h>
+
+#include <vorbis/vorbisenc.h>
+#include <string.h> // strerror
+#include <errno.h> // errno
+
+#define MAX_WRITE_SIZE 4096
+
+// swap host to little endian
+#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+#define HTOLES(x) SWAPS(x)
+#elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+#define HTOLES(x) x
+#else
+#ifdef HAVE_WIN_HACKS
+#define HTOLES(x) x
+#else
+#define HTOLES(x) SWAPS(htons(x))
+#endif
+#endif
+
+// convert to short, taking care of endianness
+#define FLOAT_TO_SHORT(x) (HTOLES((FLAC__int32)(x * 32768)))
+
+struct _aubio_sink_flac_t {
+ uint_t samplerate;
+ uint_t channels;
+ char_t *path;
+
+ FILE *fid; // file id
+ FLAC__StreamEncoder* encoder;
+ FLAC__int32 *buffer;
+ FLAC__StreamMetadata *metadata[2];
+};
+
+typedef struct _aubio_sink_flac_t aubio_sink_flac_t;
+
+uint_t aubio_sink_flac_preset_channels(aubio_sink_flac_t *s,
+ uint_t channels);
+uint_t aubio_sink_flac_preset_samplerate(aubio_sink_flac_t *s,
+ uint_t samplerate);
+uint_t aubio_sink_flac_open(aubio_sink_flac_t *s);
+uint_t aubio_sink_flac_close (aubio_sink_flac_t *s);
+void del_aubio_sink_flac (aubio_sink_flac_t *s);
+
+#if 0
+static void aubio_sink_vorbis_callback(const FLAC__StreamEncoder* encoder,
+ FLAC__uint64 bytes_written, FLAC__uint64 samples_written,
+ unsigned frames_writtten, unsigned total_frames_estimate,
+ void *client_data);
+#endif
+
+aubio_sink_flac_t * new_aubio_sink_flac (const char_t *uri,
+ uint_t samplerate)
+{
+ aubio_sink_flac_t * s = AUBIO_NEW(aubio_sink_flac_t);
+
+ s->path = AUBIO_ARRAY(char_t, strnlen(uri, PATH_MAX) + 1);
+ strncpy(s->path, uri, strnlen(uri, PATH_MAX) + 1);
+ s->path[strnlen(uri, PATH_MAX)] = '\0';
+
+ s->channels = 0;
+ s->samplerate = 0;
+
+ if ((sint_t)samplerate == 0)
+ return s;
+
+ aubio_sink_flac_preset_samplerate(s, samplerate);
+ s->channels = 1;
+
+ if (aubio_sink_flac_open(s) != AUBIO_OK)
+ goto failure;
+
+ return s;
+
+failure:
+ del_aubio_sink_flac(s);
+ return NULL;
+}
+
+void del_aubio_sink_flac (aubio_sink_flac_t *s)
+{
+ if (s->fid)
+ aubio_sink_flac_close(s);
+ if (s->buffer)
+ AUBIO_FREE(s->buffer);
+ if (s->path)
+ AUBIO_FREE(s->path);
+ AUBIO_FREE(s);
+}
+
+uint_t aubio_sink_flac_open(aubio_sink_flac_t *s)
+{
+ uint_t ret = AUBIO_FAIL;
+ FLAC__bool ok = true;
+ FLAC__StreamEncoderInitStatus init_status;
+ const unsigned comp_level = 5;
+ const unsigned bps = 16;
+
+ if (s->samplerate == 0 || s->channels == 0) return AUBIO_FAIL;
+
+ s->buffer = AUBIO_ARRAY(FLAC__int32, s->channels * MAX_WRITE_SIZE);
+ if (!s->buffer) {
+ AUBIO_ERR("sink_flac: failed allocating buffer for %s\n", s->path);
+ return AUBIO_FAIL;
+ }
+
+ s->fid = fopen((const char *)s->path, "wb");
+ if (!s->fid) {
+ AUBIO_ERR("sink_flac: failed opening %s, %s\n", s->path, strerror(errno));
+ return AUBIO_FAIL;
+ }
+
+ if((s->encoder = FLAC__stream_encoder_new()) == NULL) {
+ AUBIO_ERR("sink_flac: failed allocating encoder for %s\n", s->path);
+ goto failure;
+ }
+ ok &= FLAC__stream_encoder_set_verify(s->encoder, true);
+ ok &= FLAC__stream_encoder_set_compression_level(s->encoder, comp_level);
+ ok &= FLAC__stream_encoder_set_channels(s->encoder, s->channels);
+ ok &= FLAC__stream_encoder_set_bits_per_sample(s->encoder, bps);
+ ok &= FLAC__stream_encoder_set_sample_rate(s->encoder, s->samplerate);
+ // the total number of samples can not be estimated (streaming)
+ // it will be set by the encoder in FLAC__stream_encoder_finish
+ //ok &= FLAC__stream_encoder_set_total_samples_estimate(s->encoder, 0);
+
+ if (!ok) {
+ AUBIO_ERR("sink_flac: failed setting metadata for %s\n", s->path);
+ goto failure;
+ }
+
+ s->metadata[0] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT);
+ if (!s->metadata[0]) {
+ AUBIO_ERR("sink_flac: failed allocating vorbis comment %s\n", s->path);
+ goto failure;
+ }
+
+ s->metadata[1] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING);
+ if (!s->metadata[1]) {
+ AUBIO_ERR("sink_flac: failed allocating vorbis comment %s\n", s->path);
+ goto failure;
+ }
+
+ FLAC__StreamMetadata_VorbisComment_Entry entry;
+ ok = FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&entry,
+ "encoder", "aubio");
+ ok &= FLAC__metadata_object_vorbiscomment_append_comment(s->metadata[0],
+ entry, false);
+ if (!ok) {
+ AUBIO_ERR("sink_flac: failed setting metadata for %s\n", s->path);
+ goto failure;
+ }
+
+ // padding length
+ s->metadata[1]->length = 1234;
+ if (!FLAC__stream_encoder_set_metadata(s->encoder, s->metadata, 2)) {
+ AUBIO_ERR("sink_flac: failed setting metadata for %s\n", s->path);
+ goto failure;
+ }
+
+ // initialize encoder
+ init_status = FLAC__stream_encoder_init_file(s->encoder, s->path,
+ NULL, NULL);
+ //aubio_sink_vorbis_callback, s);
+ if (init_status == FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_SAMPLE_RATE) {
+ AUBIO_ERR("sink_flac: failed initilizing encoder for %s"
+ " (invalid samplerate %d)\n", s->path, s->samplerate);
+ goto failure;
+ } else if (init_status ==
+ FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_NUMBER_OF_CHANNELS) {
+ AUBIO_ERR("sink_flac: failed initilizing encoder for %s"
+ " (invalid number of channel %d)\n", s->path, s->channels);
+ goto failure;
+ } else if (init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) {
+ AUBIO_ERR("sink_flac: failed initilizing encoder for %s (%d)\n",
+ s->path, (int)init_status);
+ goto failure;
+ }
+
+ // mark success
+ ret = AUBIO_OK;
+
+failure:
+
+ return ret;
+}
+
+uint_t aubio_sink_flac_preset_samplerate(aubio_sink_flac_t *s,
+ uint_t samplerate)
+{
+ if (aubio_io_validate_samplerate("sink_flac", s->path, samplerate))
+ return AUBIO_FAIL;
+ s->samplerate = samplerate;
+ if (s->samplerate != 0 && s->channels != 0)
+ return aubio_sink_flac_open(s);
+ return AUBIO_OK;
+}
+
+uint_t aubio_sink_flac_preset_channels(aubio_sink_flac_t *s,
+ uint_t channels)
+{
+ if (aubio_io_validate_channels("sink_flac", s->path, channels)) {
+ return AUBIO_FAIL;
+ }
+ s->channels = channels;
+ // automatically open when both samplerate and channels have been set
+ if (s->samplerate != 0 && s->channels != 0) {
+ return aubio_sink_flac_open(s);
+ }
+ return AUBIO_OK;
+}
+
+uint_t aubio_sink_flac_get_samplerate(const aubio_sink_flac_t *s)
+{
+ return s->samplerate;
+}
+
+uint_t aubio_sink_flac_get_channels(const aubio_sink_flac_t *s)
+{
+ return s->channels;
+}
+
+void aubio_sink_flac_do(aubio_sink_flac_t *s, fvec_t *write_data,
+ uint_t write)
+{
+ uint_t c, v;
+ // fill buffer
+ if (!write) {
+ return;
+ } else if (write > MAX_WRITE_SIZE) {
+ AUBIO_ERR("sink_flac: max_write request %dm asked for %d,"
+ " failed writing to %s\n", write, MAX_WRITE_SIZE, s->path);
+ return;
+ } else {
+ for (c = 0; c < s->channels; c++) {
+ for (v = 0; v < write; v++) {
+ s->buffer[v * s->channels + c] = FLOAT_TO_SHORT(write_data->data[v]);
+ }
+ }
+ }
+ // send to encoder
+ FLAC__stream_encoder_process_interleaved(s->encoder,
+ (const FLAC__int32*)s->buffer, write);
+}
+
+void aubio_sink_flac_do_multi(aubio_sink_flac_t *s, fmat_t *write_data,
+ uint_t write)
+{
+ uint_t c, v;
+ uint_t channels = MIN(s->channels, write_data->height);
+ // fill buffer
+ if (!write) {
+ return;
+ } else if (write > MAX_WRITE_SIZE) {
+ AUBIO_ERR("sink_flac: max_write request %dm asked for %d,"
+ " failed writing to %s\n", write, MAX_WRITE_SIZE, s->path);
+ return;
+ } else {
+ for (c = 0; c < channels; c++) {
+ for (v = 0; v < write; v++) {
+ s->buffer[v * s->channels + c] = FLOAT_TO_SHORT(write_data->data[c][v]);
+ }
+ }
+ // send to encoder
+ FLAC__stream_encoder_process_interleaved(s->encoder,
+ (const FLAC__int32*)s->buffer, write);
+ }
+}
+
+uint_t aubio_sink_flac_close (aubio_sink_flac_t *s)
+{
+ uint_t ret = AUBIO_OK;
+
+ if (s->encoder) {
+ // mark the end of stream
+ if (!FLAC__stream_encoder_finish(s->encoder)) {
+ FLAC__StreamEncoderState state =
+ FLAC__stream_encoder_get_state(s->encoder);
+ AUBIO_ERR("sink_flac: Error closing encoder for %s (%s)\n",
+ s->path, FLAC__StreamEncoderStateString[state]);
+ ret &= AUBIO_FAIL;
+ }
+
+ FLAC__stream_encoder_delete(s->encoder);
+ }
+
+ if (s->metadata) {
+ // clean up metadata after stream finished
+ FLAC__metadata_object_delete(s->metadata[0]);
+ FLAC__metadata_object_delete(s->metadata[1]);
+ }
+
+ if (s->fid) {
+ if (fclose(s->fid)) {
+ AUBIO_ERR("sink_flac: Error closing file %s (%s)\n",
+ s->path, strerror(errno));
+ ret &= AUBIO_FAIL;
+ }
+ }
+ return ret;
+}
+
+#if 0
+static void aubio_sink_vorbis_callback(const FLAC__StreamEncoder* encoder UNUSED,
+ FLAC__uint64 bytes_written, FLAC__uint64 samples_written,
+ unsigned frames_written, unsigned total_frames_estimate,
+ void *client_data UNUSED)
+{
+ AUBIO_WRN("sink_flac: %d bytes_written, %d samples_written,"
+ " %d/%d frames writen\n",
+ bytes_written, samples_written, frames_written, total_frames_estimate);
+}
+#endif
+
+#endif /* HAVE_FLAC */
--
⑨