From: Andre Noll Date: Wed, 28 Sep 2011 20:55:07 +0000 (+0200) Subject: Implement the flac audio format handler. X-Git-Tag: v0.4.9~2^2~2 X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=commitdiff_plain;h=2bba9bbea1af66262085ea5a15eaa11397ac8ce6;ds=sidebyside Implement the flac audio format handler. 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. --- diff --git a/error.h b/error.h index e2e245c9..7a2b1617 100644 --- 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?)"), \ diff --git a/flac_afh.c b/flac_afh.c index 4ac68cdc..b3a0f6e7 100644 --- a/flac_afh.c +++ b/flac_afh.c @@ -7,17 +7,332 @@ /** \file flac_afh.c Audio format handler for flac files. */ #include +#include +#include +#include #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}; /**