Implement the flac audio format handler.
authorAndre Noll <maan@systemlinux.org>
Wed, 28 Sep 2011 20:55:07 +0000 (22:55 +0200)
committerAndre Noll <maan@systemlinux.org>
Sun, 13 Nov 2011 13:48:04 +0000 (14:48 +0100)
This adds another audio format to para_server and para_afh. Since
meta data tags flac are essentially vorbis comments, they are fully
supported. However, the audio format handler accepts only flac
files with fixed block size, and ogg/flac is not supported either at
this point.

error.h
flac_afh.c

diff --git a/error.h b/error.h
index e2e245c9cc8d515c489c62008000b2864b06ad81..7a2b1617be8940d678933bd4687c5dee2e4de3f3 100644 (file)
--- a/error.h
+++ b/error.h
@@ -36,10 +36,21 @@ DEFINE_ERRLIST_OBJECT_ENUM;
 #define STDIN_ERRORS
 #define WRITE_ERRORS
 #define FLACDEC_FILTER_ERRORS
-#define FLAC_AFH_ERRORS
 
 extern const char **para_errlist[];
 
+#define FLAC_AFH_ERRORS \
+       PARA_ERROR(FLAC_CHAIN_ALLOC, "could not create metadata chain"), \
+       PARA_ERROR(FLAC_CHAIN_READ, "could not read meta chain"), \
+       PARA_ERROR(FLAC_ITER_ALLOC, "could not allocate meta iterator"), \
+       PARA_ERROR(FLAC_VARBLOCK, "variable blocksize not supported"), \
+       PARA_ERROR(FLAC_AFH_DECODER_ALLOC, "could not allocate stream decoder"), \
+       PARA_ERROR(FLAC_AFH_DECODER_INIT, "could not init stream decoder"), \
+       PARA_ERROR(FLAC_SKIP_META, "could not skip metadata"), \
+       PARA_ERROR(FLAC_DECODE_POS, "could not get decode position"), \
+       PARA_ERROR(FLAC_STREAMINFO, "could not read stream info meta block"), \
+
+
 #define OGG_AFH_COMMON_ERRORS \
        PARA_ERROR(STREAM_PACKETOUT, "ogg stream packet-out error (first packet)"), \
        PARA_ERROR(SYNC_PAGEOUT, "ogg sync page-out error (no ogg file?)"), \
index 4ac68cdce7063f8e4262a92fa25761d2cc9d77fa..b3a0f6e7abab9e6529bfda805f454e737485d30d 100644 (file)
 /** \file flac_afh.c Audio format handler for flac files. */
 
 #include <regex.h>
+#include <stdbool.h>
+#include <FLAC/stream_decoder.h>
+#include <FLAC/metadata.h>
 
 #include "para.h"
 #include "error.h"
 #include "afh.h"
+#include "string.h"
 
-static int flac_get_file_info(char *map, size_t numbytes, __a_unused int fd,
-               struct afh_info *afhi)
+struct private_flac_afh_data {
+       char *map;
+       size_t map_bytes;
+       size_t fpos;
+       struct afh_info *afhi;
+       unsigned blocksize;
+};
+
+static size_t copy_data(struct private_flac_afh_data *pfad, void *buf,
+               size_t want)
+{
+       size_t copy, have = pfad->map_bytes - pfad->fpos;
+
+       if (have == 0)
+               return 0;
+       copy = have < want? have : want;
+       memcpy(buf, pfad->map + pfad->fpos, copy);
+       pfad->fpos += copy;
+       return copy;
+}
+
+static size_t meta_read_cb(void *ptr, size_t size, size_t nmemb,
+               FLAC__IOHandle handle)
+{
+       struct private_flac_afh_data *pfad = handle;
+       return copy_data(pfad, ptr, nmemb * size);
+}
+
+static int meta_seek_cb(FLAC__IOHandle handle, FLAC__int64 offset, int whence)
+{
+       struct private_flac_afh_data *pfad = handle;
+
+       if (offset < 0)
+               return -1;
+
+       switch (whence) {
+       case SEEK_SET:
+               if (offset >= pfad->map_bytes)
+                       return -1;
+               pfad->fpos = offset;
+               return 0;
+       case SEEK_CUR:
+               if (pfad->fpos + offset >= pfad->map_bytes)
+                       return -1;
+               pfad->fpos += offset;
+               return 0;
+       case SEEK_END:
+               if (offset >= pfad->map_bytes)
+                       return -1;
+               pfad->fpos = offset;
+               return 0;
+       default:
+               return -1;
+       }
+}
+
+static FLAC__int64 meta_tell_cb(FLAC__IOHandle handle)
+{
+       struct private_flac_afh_data *pfad = handle;
+       return pfad->fpos;
+}
+
+static int meta_eof_cb(FLAC__IOHandle handle)
+{
+       struct private_flac_afh_data *pfad = handle;
+       return pfad->fpos == pfad->map_bytes - 1;
+}
+
+static int meta_close_cb(FLAC__IOHandle __a_unused handle)
 {
        return 0;
 }
 
+static void free_tags(struct taginfo *tags)
+{
+       freep(&tags->artist);
+       freep(&tags->title);
+       freep(&tags->album);
+       freep(&tags->year);
+       freep(&tags->comment);
+}
+
+static bool copy_if_tag_type(const char *tag, int taglen, const char *type,
+               char **p)
+{
+       char *q = key_value_copy(tag, taglen, type);
+       if (!q)
+               return false;
+       free(*p);
+       *p = q;
+       return true;
+}
+
+static void flac_read_vorbis_comments(FLAC__StreamMetadata_VorbisComment *vc,
+               struct taginfo *tags)
+{
+       int i;
+       FLAC__StreamMetadata_VorbisComment_Entry *comment = vc->comments;
+
+       PARA_INFO_LOG("found %u vorbis comments\n", vc->num_comments);
+       for (i = 0; i < vc->num_comments; i++) {
+               char *e = (char *)comment[i].entry;
+               int len = comment[i].length;
+               if (copy_if_tag_type(e, len, "artist", &tags->artist))
+                       continue;
+               if (copy_if_tag_type(e, len, "title", &tags->title))
+                       continue;
+               if (copy_if_tag_type(e, len, "album", &tags->album))
+                       continue;
+               if (copy_if_tag_type(e, len, "year", &tags->year))
+                       continue;
+               if (copy_if_tag_type(e, len, "comment", &tags->comment))
+                       continue;
+       }
+}
+
+static int flac_read_meta(struct private_flac_afh_data *pfad)
+{
+       int ret;
+       FLAC__IOCallbacks meta_callbacks = {
+               .read = meta_read_cb,
+               .write = NULL,
+               .seek = meta_seek_cb,
+               .tell = meta_tell_cb,
+               .eof = meta_eof_cb,
+               .close = meta_close_cb
+       };
+       FLAC__Metadata_Chain *chain;
+       FLAC__Metadata_Iterator *iter;
+       FLAC__StreamMetadata_StreamInfo *info = NULL;
+       FLAC__bool ok;
+
+       chain = FLAC__metadata_chain_new();
+       if (!chain)
+               return -E_FLAC_CHAIN_ALLOC;
+       ret = -E_FLAC_CHAIN_READ;
+       ok = FLAC__metadata_chain_read_with_callbacks(chain, pfad,
+               meta_callbacks);
+       if (!ok)
+               goto free_chain;
+       ret = -E_FLAC_ITER_ALLOC;
+       iter = FLAC__metadata_iterator_new();
+       if (!iter)
+               goto free_chain;
+       FLAC__metadata_iterator_init(iter, chain);
+       for (;;) {
+               FLAC__StreamMetadata *b;
+               b = FLAC__metadata_iterator_get_block(iter);
+               if (!b)
+                       break;
+               if (b->type == FLAC__METADATA_TYPE_STREAMINFO) {
+                       info = &b->data.stream_info;
+                       ret = -E_FLAC_VARBLOCK;
+                       if (info->min_blocksize != info->max_blocksize)
+                               goto free_iter;
+                       pfad->afhi->frequency = info->sample_rate;
+                       pfad->afhi->channels = info->channels;
+                       pfad->blocksize = info->min_blocksize;
+               }
+               if (b->type == FLAC__METADATA_TYPE_VORBIS_COMMENT)
+                       flac_read_vorbis_comments(&b->data.vorbis_comment,
+                               &pfad->afhi->tags);
+               ok = FLAC__metadata_iterator_next(iter);
+               if (!ok)
+                       break;
+       }
+       ret = info? 0: -E_FLAC_STREAMINFO;
+free_iter:
+       FLAC__metadata_iterator_delete(iter);
+free_chain:
+       FLAC__metadata_chain_delete(chain);
+       if (ret < 0)
+               free_tags(&pfad->afhi->tags);
+       return ret;
+}
+
+static FLAC__StreamDecoderReadStatus read_cb(
+               __a_unused const FLAC__StreamDecoder *decoder,
+               FLAC__byte buffer[], size_t *bytes, void *client_data)
+{
+       struct private_flac_afh_data *pfad = client_data;
+
+       assert(*bytes > 0);
+       *bytes = copy_data(pfad, buffer, *bytes);
+       if (*bytes == 0)
+               return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
+       else
+               return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
+}
+
+static FLAC__StreamDecoderTellStatus tell_cb(
+               __a_unused const FLAC__StreamDecoder *decoder,
+               FLAC__uint64 *absolute_byte_offset, void *client_data)
+{
+       struct private_flac_afh_data *pfad = client_data;
+
+       *absolute_byte_offset = pfad->fpos;
+       return FLAC__STREAM_DECODER_TELL_STATUS_OK;
+}
+
+/* libflac insits on this callback being present. */
+static FLAC__StreamDecoderWriteStatus write_cb(
+               __a_unused const FLAC__StreamDecoder *decoder,
+               __a_unused const FLAC__Frame *frame,
+               __a_unused const FLAC__int32 *const buffer[],
+               __a_unused void *client_data)
+{
+       return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+static void error_cb(
+               __a_unused const FLAC__StreamDecoder *decoder,
+               FLAC__StreamDecoderErrorStatus status,
+               __a_unused void *client_data)
+{
+       PARA_ERROR_LOG("%s\n", FLAC__StreamDecoderErrorStatusString[status]);
+}
+
+static int flac_afh_read_chunks(struct private_flac_afh_data *pfad)
+{
+       FLAC__StreamDecoder *decoder;
+       FLAC__StreamDecoderInitStatus init_status;
+       FLAC__bool ok;
+       FLAC__uint64 c;
+       unsigned chunk_table_size = 0;
+       int ret;
+       struct afh_info *afhi = pfad->afhi;
+
+       PARA_INFO_LOG("reading chunk table\n");
+       afhi->chunk_table = NULL;
+       decoder = FLAC__stream_decoder_new();
+       if (!decoder)
+               return -E_FLAC_AFH_DECODER_ALLOC;
+       ret = -E_FLAC_AFH_DECODER_INIT;
+       init_status = FLAC__stream_decoder_init_stream(
+               decoder,
+               read_cb,
+               NULL, /* seek */
+               tell_cb,
+               NULL, /* length */
+               NULL, /* eof */
+               write_cb,
+               NULL,
+               error_cb,
+               pfad
+       );
+       if (init_status != FLAC__STREAM_DECODER_INIT_STATUS_OK)
+               goto free_decoder;
+       ret = -E_FLAC_SKIP_META;
+       ok = FLAC__stream_decoder_process_until_end_of_metadata(decoder);
+       if (!ok)
+               goto free_decoder;
+       for (c = 0;; c++) {
+               FLAC__uint64 pos;
+               FLAC__StreamDecoderState state;
+
+               ret = -E_FLAC_DECODE_POS;
+               ok = FLAC__stream_decoder_get_decode_position(decoder, &pos);
+               if (!ok)
+                       goto free_decoder;
+               if (c >= chunk_table_size) {
+                       chunk_table_size = 2 * chunk_table_size + 100;
+                       afhi->chunk_table = para_realloc(afhi->chunk_table,
+                               chunk_table_size * sizeof(uint32_t));
+               }
+               afhi->chunk_table[c] = pos;
+
+               ok = FLAC__stream_decoder_skip_single_frame(decoder);
+               if (!ok)
+                       break;
+               state = FLAC__stream_decoder_get_state(decoder);
+               if (state == FLAC__STREAM_DECODER_END_OF_STREAM)
+                       break;
+       }
+       afhi->chunks_total = c;
+       ret = 1;
+free_decoder:
+       FLAC__stream_decoder_finish(decoder);
+       FLAC__stream_decoder_delete(decoder);
+       if (ret < 0)
+               freep(&afhi->chunk_table);
+       return ret;
+}
+
+static int flac_get_file_info(char *map, size_t map_bytes, __a_unused int fd,
+               struct afh_info *afhi)
+{
+       struct private_flac_afh_data pfad_struct = {
+               .map = map,
+               .map_bytes = map_bytes,
+               .afhi = afhi
+       }, *pfad = &pfad_struct;
+       int ret;
+       double chunk_time;
+
+       afhi->header_len = 0;
+       ret = flac_read_meta(pfad);
+       if (ret < 0)
+               return ret;
+       pfad->fpos = 0;
+       ret = flac_afh_read_chunks(pfad);
+       if (ret < 0) {
+               free_tags(&afhi->tags);
+               return ret;
+       }
+       afhi->techinfo = make_message("blocksize: %u", pfad->blocksize);
+       afhi->seconds_total = DIV_ROUND_UP(afhi->chunks_total * pfad->blocksize,
+               afhi->frequency);
+       afhi->bitrate = pfad->map_bytes * 8 / afhi->seconds_total / 1024;
+       chunk_time = (double)pfad->blocksize / afhi->frequency;
+       afhi->chunk_tv.tv_sec = chunk_time;
+       chunk_time *= 1000 * 1000;
+       chunk_time -= afhi->chunk_tv.tv_sec * 1000 * 1000;
+       afhi->chunk_tv.tv_usec = chunk_time;
+       return 1;
+}
+
 static const char* flac_suffixes[] = {"flac", NULL};
 
 /**