From: Andre Noll Date: Sun, 23 Apr 2017 19:12:22 +0000 (+0200) Subject: Merge branch 'refs/heads/t/dynamic_chunks' X-Git-Tag: v0.6.0~7 X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=commitdiff_plain;h=9264932e105071a24d843785900e29e70c78d6ae;hp=674f20dd3fc5339a1e7a9f25c5e4a987aaf13d3c Merge branch 'refs/heads/t/dynamic_chunks' A rather long and intrusive series that finally improves the aac decoder and audio format handler. The merge had a minor conflict in error.h, which was easily resolved by removing both affected error codes. Cooking for a month. * refs/heads/t/dynamic_chunks: (24 commits) afh_recv: Check return value of afh_get_chunk(). aacdec: Decode only one frame per iteration. aacdec: Combine aac_open() and aacdec_open(). aacdec: Make "initialized" a boolean. aacdec: Make frame_info local to ->post_select(). aacdec: Remove pointless assignment. aacdec: Remove pointless check and improve comment. aacdec: Rename buffer variables. aacdec: Remove pointless variable p. aacdec: Remove check which is always true. aacdec: Kill pointless label. aacdec: Remove superfluous assignment. aacdec: Improve and silence error message. aacdec: Prefer NeAACDecInit() over NeAACDecInit2(). aacdec: Don't eat full buffer on errors. Combine aacdec and aac_common. Convert the aac audio format handler to libmp4ff. aac_afh: Don't create chunk tables any more. afh: Dynamic chunks. server: Store max chunk size in database. ... --- diff --git a/Makefile.in b/Makefile.in index 6e84b6fd..aff321fd 100644 --- a/Makefile.in +++ b/Makefile.in @@ -49,7 +49,6 @@ samplerate_cppflags := @samplerate_cppflags@ readline_cppflags := @readline_cppflags@ alsa_cppflags := @alsa_cppflags@ oss_cppflags := @oss_cppflags@ -mp4v2_cppflags := @mp4v2_cppflags@ id3tag_ldflags := @id3tag_ldflags@ ogg_ldflags := @ogg_ldflags@ @@ -69,6 +68,5 @@ osl_ldflags := @osl_ldflags@ curses_ldflags := @curses_ldflags@ crypto_ldflags := @crypto_ldflags@ iconv_ldflags := @iconv_ldflags@ -mp4v2_ldflags := @mp4v2_ldflags@ include Makefile.real diff --git a/Makefile.real b/Makefile.real index 5e4dbe68..d2d31667 100644 --- a/Makefile.real +++ b/Makefile.real @@ -204,7 +204,6 @@ $(object_dir)/mp3_afh.o $(dep_dir)/mp3_afh.d: CPPFLAGS += $(id3tag_cppflags) $(object_dir)/crypt.o $(dep_dir)/crypt.d: CPPFLAGS += $(openssl_cppflags) $(object_dir)/gcrypt.o $(dep_dir)/gcrypt.d: CPPFLAGS += $(gcrypt_cppflags) $(object_dir)/ao_write.o $(dep_dir)/ao_write.d: CPPFLAGS += $(ao_cppflags) -$(object_dir)/aac_afh.o $(dep_dir)/aac_afh.d: CPPFLAGS += $(mp4v2_cppflags) $(object_dir)/alsa%.o $(dep_dir)/alsa%.d: CPPFLAGS += $(alsa_cppflags) $(object_dir)/interactive.o $(dep_dir)/interactive.d \ @@ -223,7 +222,6 @@ $(object_dir)/mp3dec_filter.o $(dep_dir)/mp3dec_filter.d \ : CPPFLAGS += $(mad_cppflags) $(object_dir)/aacdec_filter.o $(dep_dir)/aacdec_filter.d \ -$(object_dir)/aac_common.o $(dep_dir)/aac_common.d \ $(object_dir)/aac_afh.o $(dep_dir)/aac_afh.d \ : CPPFLAGS += $(faad_cppflags) @@ -321,13 +319,6 @@ para_recv \ $(faad_ldflags) \ $(flac_ldflags) -para_server \ -para_play \ -para_afh \ -para_recv \ -: LDFLAGS += \ - $(mp4v2_ldflags) - para_afh para_recv para_server para_play: LDFLAGS += $(iconv_ldflags) $(foreach exe,$(executables),$(eval para_$(exe): $$($(exe)_objs))) diff --git a/NEWS.md b/NEWS.md index df1a80e3..9b6ef0a7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -26,6 +26,14 @@ NEWS installation directory. This feature is orthogonal to the --prefix option to configure. - Minor WMA cleanups. +- The aac audio format handler has been rewritten to use the mp4ff library. + See the manual for how to install the library on your system. +- New status item: max_chunk_size. The value is stored in a previously + unused field of the afhi object of the aft table. Although backwards + compatible, users are encouraged to re-add m4a files to populate + the new field. +- No more chunk tables for aac. Chunk boundaries are determined + dynamically at stream time. Downloads: [tarball](./releases/paraslash-git.tar.bz2), diff --git a/aac.h b/aac.h deleted file mode 100644 index eeed2528..00000000 --- a/aac.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright (C) 2006 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file aac.h Exported symbols from aac_common.c. */ - -#include - -NeAACDecHandle aac_open(void); -int aac_find_esds(char *buf, size_t buflen, size_t *skip, - unsigned long *decoder_length); -ssize_t aac_find_entry_point(char *buf, size_t buflen, size_t *skip); diff --git a/aac_afh.c b/aac_afh.c index 1c7fd706..8550a8ac 100644 --- a/aac_afh.c +++ b/aac_afh.c @@ -11,308 +11,328 @@ /** \file aac_afh.c para_server's aac audio format handler. */ #include -#include +#include #include "para.h" + +/* To get the mp4ff_tag_t and mp4ff_metadata_t typedefs. */ +#define USE_TAGGING +#include + #include "error.h" #include "portable_io.h" #include "afh.h" #include "string.h" -#include "aac.h" #include "fd.h" -static int aac_find_stsz(char *buf, size_t buflen, off_t *skip) + +struct aac_afh_context { + const void *map; + size_t mapsize; + size_t fpos; + int32_t track; + mp4ff_t *mp4ff; + mp4AudioSpecificConfig masc; + mp4ff_callback_t cb; +}; + +static uint32_t aac_afh_read_cb(void *user_data, void *dest, uint32_t want) { - int i; - - for (i = 0; i + 16 < buflen; i++) { - char *p = buf + i; - unsigned sample_count, sample_size; - - if (p[0] != 's' || p[1] != 't' || p[2] != 's' || p[3] != 'z') - continue; - PARA_DEBUG_LOG("found stsz@%d\n", i); - i += 8; - sample_size = read_u32_be(buf + i); - PARA_DEBUG_LOG("sample size: %u\n", sample_size); - i += 4; - sample_count = read_u32_be(buf + i); - i += 4; - PARA_DEBUG_LOG("sample count: %u\n", sample_count); - *skip = i; - return sample_count; + struct aac_afh_context *c = user_data; + uint32_t have, rv; + + if (want == 0 || c->fpos >= c->mapsize) { + PARA_INFO_LOG("failed attempt to read %u bytes @%zu\n", want, + c->fpos); + errno = EAGAIN; + return -1; } - return -E_STSZ; + have = c->mapsize - c->fpos; + rv = PARA_MIN(have, want); + PARA_DEBUG_LOG("reading %u bytes @%zu\n", rv, c->fpos); + memcpy(dest, c->map + c->fpos, rv); + c->fpos += rv; + return rv; } -static int atom_cmp(const char *buf1, const char *buf2) +static uint32_t aac_afh_seek_cb(void *user_data, uint64_t pos) { - return memcmp(buf1, buf2, 4)? 1 : 0; + struct aac_afh_context *c = user_data; + c->fpos = pos; + return 0; } -static int read_atom_header(char *buf, uint64_t *subsize, char type[5]) +static int32_t aac_afh_get_track(mp4ff_t *mp4ff, mp4AudioSpecificConfig *masc) { - uint64_t size = read_u32_be(buf); - - memcpy(type, buf + 4, 4); - type[4] = '\0'; - - PARA_DEBUG_LOG("size: %llu, type: %s\n", (long long unsigned)size, type); - if (size != 1) { - *subsize = size; - return 8; + int32_t i, rc, num_tracks = mp4ff_total_tracks(mp4ff); + + assert(num_tracks >= 0); + for (i = 0; i < num_tracks; i++) { + unsigned char *buf = NULL; + unsigned buf_size = 0; + + mp4ff_get_decoder_config(mp4ff, i, &buf, &buf_size); + if (buf) { + rc = NeAACDecAudioSpecificConfig(buf, buf_size, masc); + free(buf); + if (rc < 0) + continue; + return i; + } } - buf += 4; - size = 0; - size = read_u64_be(buf); - *subsize = size; - return 16; + return -1; /* no audio track */ } -static char *get_tag(char *p, int size) +static int aac_afh_open(const void *map, size_t mapsize, void **afh_context) { - char *buf; - - assert(size > 0); - buf = para_malloc(size + 1); - - memcpy(buf, p, size); - buf[size] = '\0'; - PARA_DEBUG_LOG("size: %d: %s\n", size, buf); - return buf; + int ret; + struct aac_afh_context *c = para_malloc(sizeof(*c)); + + c->map = map; + c->mapsize = mapsize; + c->fpos = 0; + c->cb.read = aac_afh_read_cb; + c->cb.seek = aac_afh_seek_cb; + c->cb.user_data = c; + + ret = -E_MP4FF_OPEN; + c->mp4ff = mp4ff_open_read(&c->cb); + if (!c->mp4ff) + goto free_ctx; + c->track = aac_afh_get_track(c->mp4ff, &c->masc); + ret = -E_MP4FF_TRACK; + if (c->track < 0) + goto close_mp4ff; + *afh_context = c; + return 0; +close_mp4ff: + mp4ff_close(c->mp4ff); +free_ctx: + free(c); + *afh_context = NULL; + return ret; } -static void read_tags(char *buf, size_t buflen, struct afh_info *afhi) +static void aac_afh_close(void *afh_context) { - char *p = buf; + struct aac_afh_context *c = afh_context; + mp4ff_close(c->mp4ff); + free(c); +} - while (p + 32 < buf + buflen) { - char *q, type1[5], type2[5]; - uint64_t size1, size2; - int ret, ret2; +/** + * Libmp4ff function to reposition the file to the given sample. + * + * \param f The opaque handle returned by mp4ff_open_read(). + * \param track The number of the (audio) track. + * \param sample Destination. + * + * We need this function to obtain the offset of the sample within the audio + * file. Unfortunately, it is not exposed in the mp4ff header. + * + * \return This function always returns 0. + */ +int32_t mp4ff_set_sample_position(mp4ff_t *f, const int32_t track, const int32_t sample); - ret = read_atom_header(p, &size1, type1); - ret2 = read_atom_header(p + ret, &size2, type2); +static int aac_afh_get_chunk(long unsigned chunk_num, void *afh_context, + const char **buf, size_t *len) +{ + struct aac_afh_context *c = afh_context; + int32_t ss; + size_t offset; + + assert(chunk_num <= INT_MAX); + /* this function always returns zero */ + mp4ff_set_sample_position(c->mp4ff, c->track, chunk_num); + offset = c->fpos; + ss = mp4ff_read_sample_getsize(c->mp4ff, c->track, chunk_num); + if (ss <= 0) + return -E_MP4FF_BAD_SAMPLE; + assert(ss + offset <= c->mapsize); + *buf = c->map + offset; + *len = ss; + return 1; +} - if (size2 <= 16 || atom_cmp(type2, "data")) { - p += size1; - continue; - } - size2 -= 16; - q = p + ret + ret2 + 8; - if (q + size2 > buf + buflen) - break; - if (!atom_cmp(type1, "\xa9" "ART")) - afhi->tags.artist = get_tag(q, size2); - else if (!atom_cmp(type1, "\xa9" "alb")) - afhi->tags.album = get_tag(q, size2); - else if (!atom_cmp(type1, "\xa9" "nam")) - afhi->tags.title = get_tag(q, size2); - else if (!atom_cmp(type1, "\xa9" "cmt")) - afhi->tags.comment = get_tag(q, size2); - else if (!atom_cmp(type1, "\xa9" "day")) - afhi->tags.year = get_tag(q, size2); - p += size1; - } +static void _aac_afh_get_taginfo(const mp4ff_t *mp4ff, struct taginfo *tags) +{ + mp4ff_meta_get_artist(mp4ff, &tags->artist); + mp4ff_meta_get_title(mp4ff, &tags->title); + mp4ff_meta_get_date(mp4ff, &tags->year); + mp4ff_meta_get_album(mp4ff, &tags->album); + mp4ff_meta_get_comment(mp4ff, &tags->comment); } -static void read_meta(char *buf, size_t buflen, struct afh_info *afhi) +/* + * Init m4a file and write some tech data to given pointers. + */ +static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd, + struct afh_info *afhi) { - char *p = buf; + int ret; + int32_t rv; + struct aac_afh_context *c; + int64_t tmp; + const char *buf; + size_t sz; + uint32_t n; + + ret = aac_afh_open(map, numbytes, (void **)&c); + if (ret < 0) + return ret; - while (p + 4 < buf + buflen) { + ret = -E_MP4FF_BAD_SAMPLERATE; + rv = mp4ff_get_sample_rate(c->mp4ff, c->track); + if (rv <= 0) + goto close; + afhi->frequency = rv; - if (p[0] != 'i' || p[1] != 'l' || p[2] != 's' || p[3] != 't') { - p++; - continue; - } - p += 4; - return read_tags(p, buflen - (p - buf), afhi); + ret = -E_MP4FF_BAD_CHANNEL_COUNT; + rv = mp4ff_get_channel_count(c->mp4ff, c->track); + if (rv <= 0) + goto close; + afhi->channels = rv; + + ret = -E_MP4FF_BAD_SAMPLE_COUNT; + rv = mp4ff_num_samples(c->mp4ff, c->track); + if (rv <= 0) + goto close; + afhi->chunks_total = rv; + afhi->max_chunk_size = 0; + for (n = 0; n < afhi->chunks_total; n++) { + if (aac_afh_get_chunk(n, c, &buf, &sz) < 0) + break; + afhi->max_chunk_size = PARA_MAX((size_t)afhi->max_chunk_size, sz); } + + tmp = c->masc.sbr_present_flag == 1? 2048 : 1024; + afhi->seconds_total = tmp * afhi->chunks_total / afhi->frequency; + ms2tv(1000 * tmp / afhi->frequency, &afhi->chunk_tv); + + if (aac_afh_get_chunk(0, c, &buf, &sz) >= 0) + numbytes -= buf - map; + afhi->bitrate = 8 * numbytes / afhi->seconds_total / 1000; + _aac_afh_get_taginfo(c->mp4ff, &afhi->tags); + ret = 1; +close: + aac_afh_close(c); + return ret; } -static void aac_get_taginfo(char *buf, size_t buflen, struct afh_info *afhi) +static uint32_t aac_afh_meta_read_cb(void *user_data, void *dest, uint32_t want) { - int i; - uint64_t subsize; - char type[5]; - - for (i = 0; i + 24 < buflen; i++) { - char *p = buf + i; - if (p[0] != 'm' || p[1] != 'e' || p[2] != 't' || p[3] != 'a') - continue; - PARA_INFO_LOG("found metadata at offset %d\n", i); - i += 8; - p = buf + i; - i += read_atom_header(p, &subsize, type); - p = buf + i; - return read_meta(p, buflen - i, afhi); - } - PARA_INFO_LOG("no meta data\n"); + int fd = *(int *)user_data; + return read(fd, dest, want); } -static ssize_t aac_compute_chunk_table(struct afh_info *afhi, - char *map, size_t numbytes) +static uint32_t aac_afh_meta_seek_cb(void *user_data, uint64_t pos) { - int ret, i; - size_t sum = 0; - off_t skip; + int fd = *(int *)user_data; + return lseek(fd, pos, SEEK_SET); +} - ret = aac_find_stsz(map, numbytes, &skip); - if (ret < 0) - return ret; - afhi->chunks_total = ret; - PARA_DEBUG_LOG("sz table has %" PRIu32 " entries\n", afhi->chunks_total); - afhi->chunk_table = para_malloc((afhi->chunks_total + 1) * sizeof(size_t)); - for (i = 1; i <= afhi->chunks_total; i++) { - if (skip + 4 > numbytes) - break; - sum += read_u32_be(map + skip); - afhi->chunk_table[i] = sum; - skip += 4; -// if (i < 10 || i + 10 > afhi->chunks_total) -// PARA_DEBUG_LOG("offset #%d: %zu\n", i, afhi->chunk_table[i]); - } - return skip; +static uint32_t aac_afh_meta_write_cb(void *user_data, void *dest, uint32_t want) +{ + int fd = *(int *)user_data; + return write(fd, dest, want); } -static int aac_set_chunk_tv(struct afh_info *afhi, - mp4AudioSpecificConfig *mp4ASC, uint32_t *seconds) +static uint32_t aac_afh_meta_truncate_cb(void *user_data) { - float tmp = mp4ASC->sbr_present_flag == 1? 2047 : 1023; - struct timeval total; - long unsigned ms; - - if (!mp4ASC->samplingFrequency) - return -E_MP4ASC; - ms = 1000.0 * afhi->chunks_total * tmp / mp4ASC->samplingFrequency; - ms2tv(ms, &total); - tv_divide(afhi->chunks_total, &total, &afhi->chunk_tv); - PARA_INFO_LOG("%luHz, %lus (%" PRIu32 " x %lums)\n", - mp4ASC->samplingFrequency, ms / 1000, - afhi->chunks_total, tv2ms(&afhi->chunk_tv)); - if (ms < 1000) - return -E_MP4ASC; - *seconds = ms / 1000; - return 1; + int fd = *(int *)user_data; + off_t offset = lseek(fd, 0, SEEK_CUR); + return ftruncate(fd, offset); } -/* - * Init m4a file and write some tech data to given pointers. - */ -static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd, - struct afh_info *afhi) +static void replace_tag(mp4ff_tag_t *tag, const char *new_val, bool *found) { - int i; - size_t skip; - ssize_t ret; - unsigned long rate = 0, decoder_len; - unsigned char channels = 0; - mp4AudioSpecificConfig mp4ASC; - NeAACDecHandle handle = NULL; - - ret = aac_find_esds(map, numbytes, &skip, &decoder_len); - if (ret < 0) - goto out; - aac_get_taginfo(map, numbytes, afhi); - handle = aac_open(); - ret = -E_AAC_AFH_INIT; - if (NeAACDecInit(handle, (unsigned char *)map + skip, decoder_len, - &rate, &channels)) - goto out; - if (!channels) - goto out; - PARA_DEBUG_LOG("rate: %lu, channels: %d\n", rate, channels); - ret = -E_MP4ASC; - if (NeAACDecAudioSpecificConfig((unsigned char *)map + skip, - numbytes - skip, &mp4ASC)) - goto out; - if (!mp4ASC.samplingFrequency) - goto out; - ret = aac_compute_chunk_table(afhi, map, numbytes); - if (ret < 0) - goto out; - skip = ret; - ret = aac_set_chunk_tv(afhi, &mp4ASC, &afhi->seconds_total); - if (ret < 0) - goto out; - ret = aac_find_entry_point(map + skip, numbytes - skip, &skip); - if (ret < 0) - goto out; - afhi->chunk_table[0] = ret; - for (i = 1; i<= afhi->chunks_total; i++) - afhi->chunk_table[i] += ret; - afhi->channels = channels; - afhi->frequency = rate; - ret = (afhi->chunk_table[afhi->chunks_total] - afhi->chunk_table[0]) * 8; /* bits */ - ret += (channels * afhi->seconds_total * 500); /* avoid rounding error */ - afhi->bitrate = ret / (channels * afhi->seconds_total * 1000); - ret = 1; -out: - if (handle) - NeAACDecClose(handle); - return ret; + free(tag->value); + tag->value = para_strdup(new_val); + *found = true; } -static int aac_rewrite_tags(const char *map, size_t mapsize, - struct taginfo *tags, int fd, const char *filename) +static void add_tag(mp4ff_metadata_t *md, const char *item, const char *value) { - MP4FileHandle h; - const MP4Tags *mdata; - int ret = write_all(fd, map, mapsize); + md->tags[md->count].item = para_strdup(item); + md->tags[md->count].value = para_strdup(value); + md->count++; +} +static int aac_afh_rewrite_tags(const char *map, size_t mapsize, + struct taginfo *tags, int fd, __a_unused const char *filename) +{ + int ret, i; + int32_t rv; + mp4ff_metadata_t metadata; + mp4ff_t *mp4ff; + mp4ff_callback_t cb = { + .read = aac_afh_meta_read_cb, + .seek = aac_afh_meta_seek_cb, + .write = aac_afh_meta_write_cb, + .truncate = aac_afh_meta_truncate_cb, + .user_data = &fd + }; + bool found_artist = false, found_title = false, found_album = false, + found_year = false, found_comment = false; + + ret = write_all(fd, map, mapsize); if (ret < 0) return ret; lseek(fd, 0, SEEK_SET); - h = MP4Modify(filename, 0); - if (!h) { - PARA_ERROR_LOG("MP4Modify() failed, fd = %d\n", fd); - return -E_MP4V2; - } - mdata = MP4TagsAlloc(); - assert(mdata); - if (!MP4TagsFetch(mdata, h)) { - PARA_ERROR_LOG("MP4Tags_Fetch() failed\n"); - ret = -E_MP4V2; - goto close; - } - if (!MP4TagsSetAlbum(mdata, tags->album)) { - PARA_ERROR_LOG("Could not set album\n"); - ret = -E_MP4V2; - goto tags_free; - } - if (!MP4TagsSetArtist(mdata, tags->artist)) { - PARA_ERROR_LOG("Could not set album\n"); - ret = -E_MP4V2; - goto tags_free; - } - if (!MP4TagsSetComments(mdata, tags->comment)) { - PARA_ERROR_LOG("Could not set comment\n"); - ret = -E_MP4V2; - goto tags_free; - } - if (!MP4TagsSetName(mdata, tags->title)) { - PARA_ERROR_LOG("Could not set title\n"); - ret = -E_MP4V2; - goto tags_free; - } - if (!MP4TagsSetReleaseDate(mdata, tags->year)) { - PARA_ERROR_LOG("Could not set release date\n"); - ret = -E_MP4V2; - goto tags_free; - } + mp4ff = mp4ff_open_read_metaonly(&cb); + if (!mp4ff) + return -E_MP4FF_OPEN; - if (!MP4TagsStore(mdata, h)) { - PARA_ERROR_LOG("Could not store tags\n"); - ret = -E_MP4V2; - goto tags_free; + ret = -E_MP4FF_META_READ; + rv = mp4ff_meta_get_num_items(mp4ff); + if (rv < 0) + goto close; + metadata.count = rv; + PARA_NOTICE_LOG("%d metadata item(s) found\n", rv); + + metadata.tags = para_malloc((metadata.count + 5) * sizeof(mp4ff_tag_t)); + for (i = 0; i < metadata.count; i++) { + mp4ff_tag_t *tag = metadata.tags + i; + + ret = -E_MP4FF_META_READ; + if (mp4ff_meta_get_by_index(mp4ff, i, + &tag->item, &tag->value) < 0) + goto free_tags; + PARA_INFO_LOG("found: %s: %s\n", tag->item, tag->value); + if (!strcmp(tag->item, "artist")) + replace_tag(tag, tags->artist, &found_artist); + else if (!strcmp(tag->item, "title")) + replace_tag(tag, tags->title, &found_title); + else if (!strcmp(tag->item, "album")) + replace_tag(tag, tags->album, &found_album); + else if (!strcmp(tag->item, "date")) + replace_tag(tag, tags->year, &found_year); + else if (!strcmp(tag->item, "comment")) + replace_tag(tag, tags->comment, &found_comment); } + if (!found_artist) + add_tag(&metadata, "artist", tags->artist); + if (!found_title) + add_tag(&metadata, "title", tags->title); + if (!found_album) + add_tag(&metadata, "album", tags->album); + if (!found_year) + add_tag(&metadata, "date", tags->year); + if (!found_comment) + add_tag(&metadata, "comment", tags->comment); + ret = -E_MP4FF_META_WRITE; + if (mp4ff_meta_update(&cb, &metadata) < 0) + goto free_tags; ret = 1; -tags_free: - MP4TagsFree(mdata); +free_tags: + for (; i > 0; i--) { + free(metadata.tags[i - 1].item); + free(metadata.tags[i - 1].value); + } + free(metadata.tags); close: - MP4Close(h, 0); + mp4ff_close(mp4ff); return ret; } @@ -326,5 +346,8 @@ void aac_afh_init(struct audio_format_handler *afh) { afh->get_file_info = aac_get_file_info, afh->suffixes = aac_suffixes; - afh->rewrite_tags = aac_rewrite_tags; + afh->rewrite_tags = aac_afh_rewrite_tags; + afh->open = aac_afh_open; + afh->get_chunk = aac_afh_get_chunk; + afh->close = aac_afh_close; } diff --git a/aac_common.c b/aac_common.c deleted file mode 100644 index 812c742c..00000000 --- a/aac_common.c +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2006 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ -/* - * based in parts on libfaad, Copyright (C) 2003-2005 M. Bakker, - * Ahead Software AG - */ - -/** \file aac_common.c Common functions of aac_afh and aadcec. */ - -#include "para.h" -#include "aac.h" -#include "error.h" -#include "portable_io.h" - -/** - * Get a new libfaad decoder handle. - * - * \return The handle returned by NeAACDecOpen(). - */ -NeAACDecHandle aac_open(void) -{ - NeAACDecHandle h = NeAACDecOpen(); - NeAACDecConfigurationPtr c = NeAACDecGetCurrentConfiguration(h); - - c->defObjectType = LC; - c->outputFormat = FAAD_FMT_16BIT; - c->downMatrix = 0; - NeAACDecSetConfiguration(h, c); - return h; -} - -static unsigned long aac_read_decoder_length(char *buf, int *description_len) -{ - uint8_t b; - uint8_t numBytes = 0; - unsigned long length = 0; - - do { - b = buf[numBytes]; - numBytes++; - length = (length << 7) | (b & 0x7F); - } while - ((b & 0x80) && numBytes < 4); - *description_len = numBytes; - return length; -} - -/** - * search for the position and the length of the decoder configuration - * - * \param buf buffer to seach - * \param buflen length of \a buf - * \param skip Upon succesful return, this contains the offset in \a buf where - * the decoder config starts. - * \param decoder_length result pointer that is filled in with the length of - * the decoder configuration on success. - * - * \return positive on success, negative on errors - */ -int aac_find_esds(char *buf, size_t buflen, size_t *skip, - unsigned long *decoder_length) -{ - size_t i; - - for (i = 0; i + 4 < buflen; i++) { - char *p = buf + i; - int description_len; - - if (p[0] != 'e' || p[1] != 's' || p[2] != 'd' || p[3] != 's') - continue; - i += 8; - p = buf + i; - PARA_INFO_LOG("found esds@%zu, next: %x\n", i, (unsigned)*p); - if (*p == 3) - i += 8; - else - i += 6; - p = buf + i; - PARA_INFO_LOG("next: %x\n", (unsigned)*p); - if (*p != 4) - continue; - i += 18; - p = buf + i; - PARA_INFO_LOG("next: %x\n", (unsigned)*p); - if (*p != 5) - continue; - i++; - p = buf + i; - *decoder_length = aac_read_decoder_length(p, &description_len); - PARA_INFO_LOG("decoder length: %lu\n", *decoder_length); - i += description_len; - *skip = i; - return 1; - } - return -E_ESDS; -} - -/** - * search for the first entry in the stco table - * - * \param buf buffer to seach - * \param buflen length of \a buf - * \param skip Upon succesful return, this contains the number - * of bytes to skip from the input buffer. - * - * \return the position of the first entry in the table on success, - * -E_STCO on errors. - */ -ssize_t aac_find_entry_point(char *buf, size_t buflen, size_t *skip) -{ - ssize_t ret; - size_t i; - - for (i = 0; i + 20 < buflen; i++) { - char *p = buf + i; - - if (p[0] != 's' || p[1] != 't' || p[2] != 'c' || p[3] != 'o') - continue; - PARA_INFO_LOG("found stco@%zu\n", i); - i += 12; - ret = read_u32_be(buf + i); /* first offset */ - i += 4; - PARA_INFO_LOG("entry point: %zd\n", ret); - *skip = i; - return ret; - } - PARA_WARNING_LOG("stco not found, buflen: %zu\n", buflen); - return -E_STCO; -} diff --git a/aacdec_filter.c b/aacdec_filter.c index 5725ce04..e1cf802c 100644 --- a/aacdec_filter.c +++ b/aacdec_filter.c @@ -11,8 +11,10 @@ /** \file aacdec_filter.c paraslash's aac (m4a) decoder. */ #include +#include #include "para.h" +#include "portable_io.h" #include "list.h" #include "sched.h" #include "ggo.h" @@ -20,7 +22,6 @@ #include "filter.h" #include "error.h" #include "string.h" -#include "aac.h" /** Give up decoding after that many errors. */ #define MAX_ERRORS 20 @@ -33,21 +34,12 @@ struct private_aacdec_data { /** the return value of aac_open */ NeAACDecHandle handle; - /** info about the currently decoded frame */ - NeAACDecFrameInfo frame_info; /** whether this instance of the aac decoder is already initialized */ - int initialized; - /** - * return value of aac_find_esds(). Used to call the right aacdec - * init function - */ - unsigned long decoder_length; + bool initialized; /** number of times the decoder returned an error */ unsigned error_count; /** number of bytes already consumed from the imput stream */ size_t consumed_total; - /** return value of aac_find_entry_point */ - size_t entry; /** The number of channels of the current stream. */ unsigned int channels; /** Current sample rate in Hz. */ @@ -64,11 +56,18 @@ static int aacdec_execute(struct btr_node *btrn, const char *cmd, char **result) static void aacdec_open(struct filter_node *fn) { + NeAACDecConfigurationPtr c; struct private_aacdec_data *padd = para_calloc(sizeof(*padd)); + padd->handle = NeAACDecOpen(); + c = NeAACDecGetCurrentConfiguration(padd->handle); + c->defObjectType = LC; + c->outputFormat = FAAD_FMT_16BIT; + c->downMatrix = 0; + NeAACDecSetConfiguration(padd->handle, c); + fn->private_data = padd; fn->min_iqs = 2048; - padd->handle = aac_open(); } static void aacdec_close(struct filter_node *fn) @@ -86,9 +85,9 @@ static int aacdec_post_select(__a_unused struct sched *s, void *context) struct btr_node *btrn = fn->btrn; struct private_aacdec_data *padd = fn->private_data; int i, ret; - char *p, *inbuf, *outbuffer; - char *btr_buf; - size_t len, skip, consumed, loaded; + char *inbuf, *outbuf, *btrbuf; + size_t len, consumed, loaded = 0; + NeAACDecFrameInfo frame_info; next_buffer: ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL); @@ -103,103 +102,58 @@ next_buffer: if (!padd->initialized) { unsigned long rate = 0; unsigned char channels = 0; - ret = aac_find_esds(inbuf, len, &skip, &padd->decoder_length); + ret = NeAACDecInit(padd->handle, (unsigned char *)inbuf, + len, &rate, &channels); + PARA_INFO_LOG("decoder init: %d\n", ret); if (ret < 0) { - PARA_INFO_LOG("%s\n", para_strerror(-ret)); - ret = NeAACDecInit(padd->handle, (unsigned char *)inbuf, - len, &rate, &channels); - PARA_INFO_LOG("decoder init: %d\n", ret); - if (ret < 0) { - ret = -E_AACDEC_INIT; - goto out; - } - consumed = ret; - } else { - PARA_INFO_LOG("decoder len: %lu\n", - padd->decoder_length); - consumed += skip; - p = inbuf + consumed; ret = -E_AACDEC_INIT; - if (NeAACDecInit2(padd->handle, (unsigned char *)p, - padd->decoder_length, &rate, - &channels) != 0) - goto out; + goto err; } + consumed = ret; padd->sample_rate = rate; padd->channels = channels; PARA_INFO_LOG("rate: %u, channels: %u\n", padd->sample_rate, padd->channels); - padd->initialized = 1; + padd->initialized = true; } - if (padd->decoder_length > 0) { - consumed = 0; - if (!padd->entry) { - ret = aac_find_entry_point(inbuf + consumed, - len - consumed, &skip); - if (ret < 0) { - ret = len; - goto out; - } - consumed += skip; - padd->entry = ret; - PARA_INFO_LOG("entry: %zu\n", padd->entry); - } - ret = len; - if (padd->consumed_total + len < padd->entry) - goto out; - if (padd->consumed_total < padd->entry) - consumed = padd->entry - padd->consumed_total; - } - for (; consumed < len; consumed++) - if ((inbuf[consumed] & 0xfe) == 0x20) - break; if (consumed >= len) goto success; - p = inbuf + consumed; //PARA_CRIT_LOG("consumed: %zu (%zu + %zu), have: %zu\n", padd->consumed_total + consumed, // padd->consumed_total, consumed, len - consumed); - outbuffer = NeAACDecDecode(padd->handle, &padd->frame_info, - (unsigned char *)p, len - consumed); - if (padd->frame_info.error) { - int err = padd->frame_info.error; + outbuf = NeAACDecDecode(padd->handle, &frame_info, + (unsigned char *)inbuf + consumed, len - consumed); + if (frame_info.error) { + int err = frame_info.error; ret = -E_AAC_DECODE; if (padd->error_count++ > MAX_ERRORS) goto err; - /* Suppress non-fatal bitstream error message at BOF/EOF */ - if (len < fn->min_iqs || padd->consumed_total == 0) { - consumed = len; - goto success; - } - PARA_ERROR_LOG("%s\n", NeAACDecGetErrorMessage(err)); - PARA_ERROR_LOG("consumed: %zu + %zu + %lu\n", + PARA_NOTICE_LOG("error #%u: (%s)\n", padd->error_count, + NeAACDecGetErrorMessage(err)); + PARA_NOTICE_LOG("consumed (total, buffer, frame): " + "%zu, %zu, %lu\n", padd->consumed_total, consumed, - padd->frame_info.bytesconsumed); - if (consumed < len) - consumed++; /* catch 21 */ + frame_info.bytesconsumed); + consumed++; /* just eat one byte and hope for the best */ goto success; } padd->error_count = 0; - //PARA_CRIT_LOG("decoder ate %lu\n", padd->frame_info.bytesconsumed); - consumed += padd->frame_info.bytesconsumed; - ret = consumed; - if (!padd->frame_info.samples) - goto out; - btr_buf = para_malloc(2 * padd->frame_info.samples); - loaded = 0; - for (i = 0; i < padd->frame_info.samples; i++) { - short sh = ((short *)outbuffer)[i]; - write_int16_host_endian(btr_buf + loaded, sh); + //PARA_CRIT_LOG("decoder ate %lu\n", frame_info.bytesconsumed); + consumed += frame_info.bytesconsumed; + if (!frame_info.samples) + goto success; + btrbuf = para_malloc(2 * frame_info.samples); + for (i = 0; i < frame_info.samples; i++) { + short sh = ((short *)outbuf)[i]; + write_int16_host_endian(btrbuf + loaded, sh); loaded += 2; } - btr_add_output(btr_buf, loaded, btrn); + btr_add_output(btrbuf, loaded, btrn); success: - ret = consumed; -out: - if (ret >= 0) { - padd->consumed_total += ret; - btr_consume(btrn, ret); + btr_consume(btrn, consumed); + padd->consumed_total += consumed; + if (loaded == 0) goto next_buffer; - } + return 1; err: assert(ret < 0); btr_remove_node(&fn->btrn); diff --git a/afh.c b/afh.c index 36c432e5..e6c46c3f 100644 --- a/afh.c +++ b/afh.c @@ -125,29 +125,38 @@ static void print_info(int audio_format_num, struct afh_info *afhi) free(msg); } -static void print_chunk_table(struct afh_info *afhi) +static void print_chunk_table(struct afh_info *afhi, int audio_format_id, + const void *map, size_t mapsize) { - int i; + int i, ret; + void *ctx = NULL; - if (conf.parser_friendly_given) { - printf("chunk_table: "); - for (i = 0; i <= afhi->chunks_total; i++) - printf("%u ", afhi->chunk_table[i]); - printf("\n"); - return; - } - for (i = 1; i <= afhi->chunks_total; i++) { + for (i = 0; i < afhi->chunks_total; i++) { struct timeval tv; long unsigned from, to; - tv_scale(i - 1, &afhi->chunk_tv, &tv); - from = tv2ms(&tv); + const char *buf; + size_t len; tv_scale(i, &afhi->chunk_tv, &tv); + from = tv2ms(&tv); + tv_scale(i + 1, &afhi->chunk_tv, &tv); to = tv2ms(&tv); - printf("%d [%lu.%03lu - %lu.%03lu] %u - %u (%u)\n", i - 1, - from / 1000, from % 1000, to / 1000, to % 1000, - afhi->chunk_table[i - 1], afhi->chunk_table[i], - afhi->chunk_table[i] - afhi->chunk_table[i - 1]); + ret = afh_get_chunk(i, afhi, audio_format_id, map, mapsize, + &buf, &len, &ctx); + if (ret < 0) { + PARA_ERROR_LOG("fatal: chunk %d: %s\n", i, + para_strerror(-ret)); + return; + } + if (!conf.parser_friendly_given) + printf("%d [%lu.%03lu - %lu.%03lu] ", i, from / 1000, + from % 1000, to / 1000, to % 1000); + printf("%td - %td", buf - (const char *)map, + buf + len - (const char *)map); + if (!conf.parser_friendly_given) + printf(" (%zu)", len); + printf("\n"); } + afh_close(ctx, audio_format_id); } __noreturn static void print_help_and_die(void) @@ -201,8 +210,8 @@ int main(int argc, char **argv) printf("File %d: %s\n", i + 1, conf.inputs[i]); print_info(audio_format_num, &afhi); if (conf.chunk_table_given) - print_chunk_table(&afhi); - printf("\n"); + print_chunk_table(&afhi, audio_format_num, + audio_file_data, audio_file_size); } clear_afhi(&afhi); } diff --git a/afh.h b/afh.h index 801c168c..6dc5a3fc 100644 --- a/afh.h +++ b/afh.h @@ -40,6 +40,8 @@ struct afh_info { * the current audio file. */ uint32_t *chunk_table; + /** Size of the largest chunk, introduced in v0.6.0. */ + uint32_t max_chunk_size; /** Period of time between sending data chunks. */ struct timeval chunk_tv; /** @@ -64,7 +66,10 @@ struct audio_file_data { int fd; /** Vss needs this for streaming. */ struct afh_info afhi; - /** Size of the largest chunk. */ + /** + * Size of the largest chunk. Superseded by afhi->max_chunk_size. May + * be removed after v0.6.1. + */ uint32_t max_chunk_size; /** Needed to get the audio file header. */ uint8_t audio_format_id; @@ -104,6 +109,26 @@ struct audio_format_handler { struct afh_info *afhi); /** Optional, used for header-rewriting. See \ref afh_get_header(). */ void (*get_header)(void *map, size_t mapsize, char **buf, size_t *len); + /** + * An audio format handler may signify support for dynamic chunks by + * defining ->get_chunk below. In this case the vss calls ->open() at + * BOS, ->get_chunk() for each chunk while streaming, and ->close() at + * EOS. The chunk table is not accessed at all. + * + * The function may return its (opaque) context through the last + * argument. The returned pointer is passed to subsequent calls to + * ->get_chunk() and ->close(). + */ + int (*open)(const void *map, size_t mapsize, void **afh_context); + /** + * Return a reference to one chunk. The returned pointer points to a + * portion of the memory mapped audio file. The caller must not call + * free() on it. + */ + int (*get_chunk)(long unsigned chunk_num, void *afh_context, + const char **buf, size_t *len); + /** Deallocate the resources occupied by ->open(). */ + void (*close)(void *afh_context); /** * Write audio file with altered tags, optional. * @@ -119,10 +144,12 @@ int guess_audio_format(const char *name); int compute_afhi(const char *path, char *data, size_t size, int fd, struct afh_info *afhi); const char *audio_format_name(int); -void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi, - void *map, const char **buf, size_t *len); +__must_check int afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi, + uint8_t audio_format_id, const void *map, size_t mapsize, + const char **buf, size_t *len, void **afh_context); +void afh_close(void *afh_context, uint8_t audio_format_id); int32_t afh_get_start_chunk(int32_t approx_chunk_num, - const struct afh_info *afhi); + const struct afh_info *afhi, uint8_t audio_format_id); void afh_get_header(struct afh_info *afhi, uint8_t audio_format_id, void *map, size_t mapsize, char **buf, size_t *len); void afh_free_header(char *header_buf, uint8_t audio_format_id); @@ -130,3 +157,5 @@ void clear_afhi(struct afh_info *afhi); unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **result); int afh_rewrite_tags(int audio_format_id, void *map, size_t mapsize, struct taginfo *tags, int output_fd, const char *filename); +void set_max_chunk_size(struct afh_info *afhi); +bool afh_supports_dynamic_chunks(int audio_format_id); diff --git a/afh_common.c b/afh_common.c index dfbf7513..6feb7c35 100644 --- a/afh_common.c +++ b/afh_common.c @@ -48,7 +48,7 @@ static struct audio_format_handler afl[] = { }, { .name = "aac", -#if defined(HAVE_MP4V2) +#if defined(HAVE_FAAD) .init = aac_afh_init, #endif }, @@ -88,7 +88,6 @@ static inline int next_audio_format(int format) if (afl[format].init) return format; } - } /** Iterate over each supported audio format. */ @@ -109,6 +108,22 @@ void afh_init(void) } } +/** + * Tell whether an audio format handler provides chunk tables. + * + * Each audio format handler either provides a chunk table or supports dynamic + * chunks. + * + * \param audio_format_id Offset in the afl array. + * + * \return True if dynamic chunks are supported, false if the audio format + * handler provides chunk tables. + */ +bool afh_supports_dynamic_chunks(int audio_format_id) +{ + return afl[audio_format_id].get_chunk; +} + /** * Guess the audio format judging from filename. * @@ -261,21 +276,73 @@ static inline size_t get_chunk_len(long unsigned chunk_num, /** * Get one chunk of audio data. * + * This implicitly calls the ->open method of the audio format handler at the + * first call. + * * \param chunk_num The number of the chunk to get. * \param afhi Describes the audio file. + * \param audio_format_id Determines the afh. * \param map The memory mapped audio file. + * \param mapsize Passed to the afh's ->open() method. * \param buf Result pointer. * \param len The length of the chunk in bytes. + * \param afh_context Value/result, determines whether ->open() is called. * * Upon return, \a buf will point so memory inside \a map. The returned buffer * must therefore not be freed by the caller. + * + * \return Standard. + */ +__must_check int afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi, + uint8_t audio_format_id, const void *map, size_t mapsize, + const char **buf, size_t *len, void **afh_context) +{ + struct audio_format_handler *afh = afl + audio_format_id; + + if (afh_supports_dynamic_chunks(audio_format_id)) { + int ret; + + if (!*afh_context) { + ret = afh->open(map, mapsize, afh_context); + if (ret < 0) + return ret; + } + ret = afl[audio_format_id].get_chunk(chunk_num, *afh_context, + buf, len); + if (ret < 0) { + afh->close(*afh_context); + *afh_context = NULL; + } + return ret; + } else { + size_t pos = afhi->chunk_table[chunk_num]; + *buf = map + pos; + *len = get_chunk_len(chunk_num, afhi); + return 0; + } +} + +/** + * Deallocate resources allocated due to dynamic chunk handling. + * + * This function should be called if afh_get_chunk() was called at least once. + * It is OK to call it even for audio formats which do not support dynamic + * chunks, in which case the function does nothing. + * + * \param afh_context As returned from the ->open method of the afh. + * \param audio_format_id Determines the afh. */ -void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi, - void *map, const char **buf, size_t *len) +void afh_close(void *afh_context, uint8_t audio_format_id) { - size_t pos = afhi->chunk_table[chunk_num]; - *buf = map + pos; - *len = get_chunk_len(chunk_num, afhi); + struct audio_format_handler *afh = afl + audio_format_id; + + if (!afh_supports_dynamic_chunks(audio_format_id)) + return; + if (!afh->close) + return; + if (!afh_context) + return; + afh->close(afh_context); } /** @@ -283,16 +350,22 @@ void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi, * * \param approx_chunk_num Upper bound for the chunk number to return. * \param afhi Needed for the chunk table. + * \param audio_format_id Determines the afh. * - * \return The first non-empty chunk <= \a approx_chunk_num. + * \return For audio format handlers which support dynamic chunks, the function + * returns the given chunk number. Otherwise it returns the first non-empty + * chunk <= \a approx_chunk_num. * * \sa \ref afh_get_chunk(). */ int32_t afh_get_start_chunk(int32_t approx_chunk_num, - const struct afh_info *afhi) + const struct afh_info *afhi, uint8_t audio_format_id) { int32_t k; + if (afh_supports_dynamic_chunks(audio_format_id)) + return approx_chunk_num; + for (k = PARA_MAX(0, approx_chunk_num); k >= 0; k--) if (get_chunk_len(k, afhi) > 0) return k; @@ -374,6 +447,7 @@ unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **re "%s: %" PRIu32 "\n" /* seconds total */ "%s: %lu: %lu\n" /* chunk time */ "%s: %" PRIu32 "\n" /* num chunks */ + "%s: %" PRIu32 "\n" /* max chunk size */ "%s: %s\n" /* techinfo */ "%s: %s\n" /* artist */ "%s: %s\n" /* title */ @@ -388,6 +462,7 @@ unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **re status_item_list[SI_CHUNK_TIME], (long unsigned)afhi->chunk_tv.tv_sec, (long unsigned)afhi->chunk_tv.tv_usec, status_item_list[SI_NUM_CHUNKS], afhi->chunks_total, + status_item_list[SI_MAX_CHUNK_SIZE], afhi->max_chunk_size, status_item_list[SI_TECHINFO], afhi->techinfo? afhi->techinfo : "", status_item_list[SI_ARTIST], afhi->tags.artist? afhi->tags.artist : "", status_item_list[SI_TITLE], afhi->tags.title? afhi->tags.title : "", @@ -397,6 +472,37 @@ unsigned afh_get_afhi_txt(int audio_format_num, struct afh_info *afhi, char **re ); } +/** + * Determine the maximal chunk size by investigating the chunk table. + * + * \param afhi Value/result. + * + * This function iterates over the chunk table and sets ->max_chunk_size + * accordingly. The function exists only for backward compatibility since as of + * version 0.6.0, para_server stores the maximal chunk size in its database. + * This function is only called if the database value is zero, indicating that + * the file was added by an older server version. + */ +void set_max_chunk_size(struct afh_info *afhi) +{ + uint32_t n, max = 0, old = 0; + + for (n = 0; n <= afhi->chunks_total; n++) { + uint32_t val = afhi->chunk_table[n]; + /* + * If the first chunk is the header, do not consider it for the + * calculation of the largest chunk size. + */ + if (n == 0 || (n == 1 && afhi->header_len > 0)) { + old = val; + continue; + } + max = PARA_MAX(max, val - old); + old = val; + } + afhi->max_chunk_size = max; +} + /** * Create a copy of the given file with altered meta tags. * diff --git a/afh_recv.c b/afh_recv.c index 28d8f398..1eac9a5b 100644 --- a/afh_recv.c +++ b/afh_recv.c @@ -31,6 +31,7 @@ struct private_afh_recv_data { long unsigned last_chunk; struct timeval stream_start; uint32_t current_chunk; + void *afh_context; }; static int afh_execute(struct btr_node *btrn, const char *cmd, char **result) @@ -58,7 +59,8 @@ static int afh_execute(struct btr_node *btrn, const char *cmd, char **result) return ret; if (x >= pard->afhi.chunks_total) return -ERRNO_TO_PARA_ERROR(EINVAL); - pard->first_chunk = afh_get_start_chunk(x, &pard->afhi); + pard->first_chunk = afh_get_start_chunk(x, &pard->afhi, + pard->audio_format_num); pard->current_chunk = pard->first_chunk; return 1; } @@ -110,11 +112,12 @@ static int afh_recv_open(struct receiver_node *rn) goto out_clear_afhi; if (conf->begin_chunk_arg >= 0) pard->first_chunk = afh_get_start_chunk( - conf->begin_chunk_arg, &pard->afhi); + conf->begin_chunk_arg, &pard->afhi, + pard->audio_format_num); else pard->first_chunk = afh_get_start_chunk( afhi->chunks_total + conf->begin_chunk_arg, - &pard->afhi); + &pard->afhi, pard->audio_format_num); if (conf->end_chunk_given) { ret = -ERRNO_TO_PARA_ERROR(EINVAL); if (PARA_ABS(conf->end_chunk_arg) > afhi->chunks_total) @@ -150,6 +153,7 @@ static void afh_recv_close(struct receiver_node *rn) clear_afhi(&pard->afhi); para_munmap(pard->map, pard->map_size); close(pard->fd); + afh_close(pard->afh_context, pard->audio_format_num); freep(&rn->private_data); } @@ -182,7 +186,7 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context) struct afh_info *afhi = &pard->afhi; int ret; char *buf; - const char *start, *end; + const char *start; size_t size; struct timeval chunk_time; @@ -202,11 +206,16 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context) } } if (!conf->just_in_time_given) { - afh_get_chunk(pard->first_chunk, afhi, pard->map, &start, &size); - afh_get_chunk(pard->last_chunk, afhi, pard->map, &end, &size); - end += size; - PARA_INFO_LOG("adding %td bytes\n", end - start); - btr_add_output_dont_free(start, end - start, btrn); + long unsigned n; + for (n = pard->first_chunk; n < pard->last_chunk; n++) { + ret = afh_get_chunk(n, afhi, pard->audio_format_num, + pard->map, pard->map_size, &start, &size, + &pard->afh_context); + if (ret < 0) + goto out; + PARA_INFO_LOG("adding %zu bytes\n", size); + btr_add_output_dont_free(start, size, btrn); + } ret = -E_RECV_EOF; goto out; } @@ -219,7 +228,12 @@ static int afh_recv_post_select(__a_unused struct sched *s, void *context) if (ret > 0) goto out; } - afh_get_chunk(pard->current_chunk, afhi, pard->map, &start, &size); + ret = afh_get_chunk(pard->current_chunk, afhi, + pard->audio_format_num, pard->map, + pard->map_size, &start, &size, + &pard->afh_context); + if (ret < 0) + goto out; PARA_DEBUG_LOG("adding chunk %u\n", pard->current_chunk); btr_add_output_dont_free(start, size, btrn); if (pard->current_chunk >= pard->last_chunk) { diff --git a/aft.c b/aft.c index bfcd1fb0..7d3a6e0d 100644 --- a/aft.c +++ b/aft.c @@ -335,8 +335,8 @@ enum afhi_offsets { CHUNKS_TOTAL_OFFSET = 20, /** The length of the audio file header (4 bytes). */ HEADER_LEN_OFFSET = 24, - /** Was: The start of the audio file header (4 bytes). */ - AFHI_UNUSED2_OFFSET = 28, + /** Size of the largest chunk in bytes. (4 bytes). */ + AFHI_MAX_CHUNK_SIZE_OFFSET = 28, /** The seconds part of the chunk time (4 bytes). */ CHUNK_TV_TV_SEC_OFFSET = 32, /** The microseconds part of the chunk time (4 bytes). */ @@ -376,7 +376,7 @@ static void save_afhi(struct afh_info *afhi, char *buf) write_u8(buf + AFHI_CHANNELS_OFFSET, afhi->channels); write_u32(buf + CHUNKS_TOTAL_OFFSET, afhi->chunks_total); write_u32(buf + HEADER_LEN_OFFSET, afhi->header_len); - write_u32(buf + AFHI_UNUSED2_OFFSET, 0); + write_u32(buf + AFHI_MAX_CHUNK_SIZE_OFFSET, afhi->max_chunk_size); write_u32(buf + CHUNK_TV_TV_SEC_OFFSET, afhi->chunk_tv.tv_sec); write_u32(buf + CHUNK_TV_TV_USEC_OFFSET, afhi->chunk_tv.tv_usec); p = buf + AFHI_INFO_STRING_OFFSET; @@ -398,6 +398,7 @@ static void load_afhi(const char *buf, struct afh_info *afhi) afhi->channels = read_u8(buf + AFHI_CHANNELS_OFFSET); afhi->chunks_total = read_u32(buf + CHUNKS_TOTAL_OFFSET); afhi->header_len = read_u32(buf + HEADER_LEN_OFFSET); + afhi->max_chunk_size = read_u32(buf + AFHI_MAX_CHUNK_SIZE_OFFSET); afhi->chunk_tv.tv_sec = read_u32(buf + CHUNK_TV_TV_SEC_OFFSET); afhi->chunk_tv.tv_usec = read_u32(buf + CHUNK_TV_TV_USEC_OFFSET); afhi->techinfo = (char *)buf + AFHI_INFO_STRING_OFFSET; @@ -408,42 +409,37 @@ static void load_afhi(const char *buf, struct afh_info *afhi) afhi->tags.comment = afhi->tags.album + strlen(afhi->tags.album) + 1; } +/* Only used for saving the chunk table, but not for loading. */ static unsigned sizeof_chunk_table(struct afh_info *afhi) { - if (!afhi) + if (!afhi || !afhi->chunk_table) return 0; return 4 * (afhi->chunks_total + 1); } -static uint32_t save_chunk_table(struct afh_info *afhi, char *buf) +static void save_chunk_table(struct afh_info *afhi, char *buf) { - int i; - uint32_t max = 0, old = 0; + uint32_t n; - for (i = 0; i <= afhi->chunks_total; i++) { - uint32_t val = afhi->chunk_table[i]; - write_u32(buf + 4 * i, val); - /* - * If the first chunk is the header, do not consider it for the - * calculation of the largest chunk size. - */ - if (i == 0 || (i == 1 && afhi->header_len > 0)) { - old = val; - continue; - } - max = PARA_MAX(max, val - old); - old = val; - } - return max; + if (!afhi->chunk_table) + return; + for (n = 0; n <= afhi->chunks_total; n++) + write_u32(buf + 4 * n, afhi->chunk_table[n]); } -static void load_chunk_table(struct afh_info *afhi, char *buf) +static void load_chunk_table(struct afh_info *afhi, const struct osl_object *ct) { int i; + size_t sz; - afhi->chunk_table = para_malloc(sizeof_chunk_table(afhi)); - for (i = 0; i <= afhi->chunks_total; i++) - afhi->chunk_table[i] = read_u32(buf + 4 * i); + if (!ct->data || ct->size < 4) { + afhi->chunk_table = NULL; + return; + } + sz = PARA_MIN(((size_t)afhi->chunks_total + 1) * 4, ct->size) + 1; + afhi->chunk_table = para_malloc(sz); + for (i = 0; i <= afhi->chunks_total && i * 4 + 3 < ct->size; i++) + afhi->chunk_table[i] = read_u32(ct->data + 4 * i); } /** @@ -638,7 +634,13 @@ static int save_afd(struct audio_file_data *afd) goto err; buf = shm_afd; buf += sizeof(*afd); - afd->max_chunk_size = save_chunk_table(&afd->afhi, buf); + save_chunk_table(&afd->afhi, buf); + if (afd->afhi.max_chunk_size == 0) { /* v0.5.x on-disk afhi */ + set_max_chunk_size(&afd->afhi); + PARA_NOTICE_LOG("max chunk size unset, re-add required\n"); + } else + PARA_INFO_LOG("using max chunk size from afhi\n"); + afd->max_chunk_size = afd->afhi.max_chunk_size; *(struct audio_file_data *)shm_afd = *afd; shm_detach(shm_afd); return shmid; @@ -663,14 +665,22 @@ int load_afd(int shmid, struct audio_file_data *afd) { void *shm_afd; int ret; + struct osl_object obj; ret = shm_attach(shmid, ATTACH_RO, &shm_afd); if (ret < 0) return ret; + ret = shm_size(shmid, &obj.size); + if (ret < 0) + goto detach; *afd = *(struct audio_file_data *)shm_afd; - load_chunk_table(&afd->afhi, shm_afd + sizeof(*afd)); + obj.data = shm_afd + sizeof(*afd); + obj.size -= sizeof(*afd); + load_chunk_table(&afd->afhi, &obj); + ret = 1; +detach: shm_detach(shm_afd); - return 1; + return ret; } static int get_local_time(uint64_t *seconds, char *buf, size_t size, @@ -820,7 +830,11 @@ static int print_chunk_table(struct ls_data *d, struct para_buffer *b) (long unsigned) d->afhi.chunk_tv.tv_usec ); buf = chunk_table_obj.data; - for (i = 0; i <= d->afhi.chunks_total; i++) + for ( + i = 0; + i <= d->afhi.chunks_total && 4 * i + 3 < chunk_table_obj.size; + i++ + ) para_printf(b, "%u ", (unsigned) read_u32(buf + 4 * i)); para_printf(b, "\n"); ret = 1; @@ -935,6 +949,8 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, WRITE_STATUS_ITEM(b, SI_CHUNK_TIME, "%lu\n", tv2ms(&afhi->chunk_tv)); WRITE_STATUS_ITEM(b, SI_NUM_CHUNKS, "%" PRIu32 "\n", afhi->chunks_total); + WRITE_STATUS_ITEM(b, SI_MAX_CHUNK_SIZE, "%" PRIu32 "\n", + afhi->max_chunk_size); WRITE_STATUS_ITEM(b, SI_TECHINFO, "%s\n", afhi->techinfo); WRITE_STATUS_ITEM(b, SI_ARTIST, "%s\n", afhi->tags.artist); WRITE_STATUS_ITEM(b, SI_TITLE, "%s\n", afhi->tags.title); @@ -1054,8 +1070,15 @@ again: d->afhi.chunk_table = afd->afhi.chunk_table = NULL; ret = osl(osl_open_disk_object(audio_file_table, current_aft_row, AFTCOL_CHUNKS, &chunk_table_obj)); - if (ret < 0) - return ret; + if (ret < 0) { + if (!afh_supports_dynamic_chunks(d->afsi.audio_format_id)) + return ret; + PARA_INFO_LOG("no chunk table for %s\n", d->path); + chunk_table_obj.data = NULL; + chunk_table_obj.size = 0; + } else { + PARA_INFO_LOG("chunk table: %zu bytes\n", chunk_table_obj.size); + } ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, &afd->fd); if (ret < 0) goto out; @@ -1072,7 +1095,7 @@ again: save_afsi(&new_afsi, &afsi_obj); /* in-place update */ afd->audio_format_id = d->afsi.audio_format_id; - load_chunk_table(&afd->afhi, chunk_table_obj.data); + load_chunk_table(&afd->afhi, &chunk_table_obj); aced.aft_row = current_aft_row; aced.old_afsi = &d->afsi; /* @@ -1085,7 +1108,8 @@ again: ret = save_afd(afd); out: free(afd->afhi.chunk_table); - osl_close_disk_object(&chunk_table_obj); + if (chunk_table_obj.data) + osl_close_disk_object(&chunk_table_obj); if (ret < 0) { PARA_ERROR_LOG("%s: %s\n", d->path, para_strerror(-ret)); ret = score_delete(current_aft_row); @@ -1736,6 +1760,7 @@ static int com_add_callback(struct afs_callback_arg *aca) &objs[AFTCOL_AFHI])); if (ret < 0) goto out; + /* truncate the file to size zero if there is no chunk table */ ret = osl(osl_update_object(audio_file_table, row, AFTCOL_CHUNKS, &objs[AFTCOL_CHUNKS])); if (ret < 0) diff --git a/configure.ac b/configure.ac index eed441d6..929d44f3 100644 --- a/configure.ac +++ b/configure.ac @@ -262,10 +262,12 @@ AC_DEFUN([NEED_FLAC_OBJECTS], [{ }]) ########################################################################### faad STASH_FLAGS -LIB_ARG_WITH([faad], [-lfaad]) +LIB_ARG_WITH([faad], [-lfaad -lmp4ff]) HAVE_FAAD=yes AC_CHECK_HEADER(neaacdec.h, [], HAVE_FAAD=no) +AC_CHECK_HEADER(mp4ff.h, [], HAVE_FAAD=no) AC_CHECK_LIB([faad], [NeAACDecOpen], [], HAVE_FAAD=no) +AC_CHECK_LIB([mp4ff], [mp4ff_meta_get_artist], [], HAVE_FAAD=no) LIB_SUBST_FLAGS(faad) UNSTASH_FLAGS ########################################################################### mad @@ -342,14 +344,6 @@ AC_CHECK_HEADER(samplerate.h, [], HAVE_SAMPLERATE=no) AC_CHECK_LIB([samplerate], [src_process], [], HAVE_SAMPLERATE=no) LIB_SUBST_FLAGS(samplerate) UNSTASH_FLAGS -########################################################################## mp4v2 -STASH_FLAGS -LIB_ARG_WITH([mp4v2], [-lmp4v2]) -HAVE_MP4V2=yes -AC_CHECK_HEADER([mp4v2/mp4v2.h], [], [HAVE_MP4V2=no]) -AC_CHECK_LIB([mp4v2], [MP4Read], [], [HAVE_MP4V2=no]) -LIB_SUBST_FLAGS(mp4v2) -UNSTASH_FLAGS ######################################################################### server if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes; then build_server="yes" @@ -405,8 +399,8 @@ if test -n "$CRYPTOLIB" && test $HAVE_OSL = yes; then NEED_SPEEX_OBJECTS() && server_errlist_objs="$server_errlist_objs spx_afh spx_common" NEED_OPUS_OBJECTS() && server_errlist_objs="$server_errlist_objs opus_afh opus_common" NEED_FLAC_OBJECTS && server_errlist_objs="$server_errlist_objs flac_afh" - if test $HAVE_FAAD = yes && test $HAVE_MP4V2 = yes; then - server_errlist_objs="$server_errlist_objs aac_afh aac_common" + if test $HAVE_FAAD = yes; then + server_errlist_objs="$server_errlist_objs aac_afh" fi server_objs="add_cmdline($server_cmdline_objs) $server_errlist_objs" AC_SUBST(server_objs, add_dot_o($server_objs)) @@ -526,7 +520,7 @@ if test -n "$CRYPTOLIB"; then audiod_audio_formats="$audiod_audio_formats flac" } if test $HAVE_FAAD = yes; then - audiod_errlist_objs="$audiod_errlist_objs aacdec_filter aac_common" + audiod_errlist_objs="$audiod_errlist_objs aacdec_filter" audiod_audio_formats="$audiod_audio_formats aac" fi if test $HAVE_MAD = yes; then @@ -687,7 +681,7 @@ NEED_FLAC_OBJECTS && { filters="$filters flacdec" } if test $HAVE_FAAD = yes; then - filter_errlist_objs="$filter_errlist_objs aacdec_filter aac_common" + filter_errlist_objs="$filter_errlist_objs aacdec_filter" filters="$filters aacdec" fi if test $HAVE_MAD = yes; then @@ -749,8 +743,8 @@ NEED_SPEEX_OBJECTS && recv_errlist_objs="$recv_errlist_objs spx_afh spx_common" NEED_OPUS_OBJECTS && recv_errlist_objs="$recv_errlist_objs opus_afh opus_common" NEED_FLAC_OBJECTS && recv_errlist_objs="$recv_errlist_objs flac_afh" -if test $HAVE_FAAD = yes -a $HAVE_MP4V2 = yes; then - recv_errlist_objs="$recv_errlist_objs aac_afh aac_common" +if test $HAVE_FAAD = yes; then + recv_errlist_objs="$recv_errlist_objs aac_afh" fi recv_objs="add_cmdline($recv_cmdline_objs) $recv_errlist_objs" AC_SUBST(receivers, "http dccp udp afh") @@ -787,8 +781,8 @@ NEED_FLAC_OBJECTS && { afh_errlist_objs="$afh_errlist_objs flac_afh" audio_format_handlers="$audio_format_handlers flac" } -if test $HAVE_FAAD = yes -a $HAVE_MP4V2 = yes; then - afh_errlist_objs="$afh_errlist_objs aac_afh aac_common" +if test $HAVE_FAAD = yes; then + afh_errlist_objs="$afh_errlist_objs aac_afh" audio_format_handlers="$audio_format_handlers aac" fi @@ -858,13 +852,7 @@ NEED_FLAC_OBJECTS && { play_errlist_objs="$play_errlist_objs flacdec_filter flac_afh" } if test $HAVE_FAAD = yes; then - play_errlist_objs="$play_errlist_objs aacdec_filter" -fi -if test $HAVE_MP4V2 = yes; then - play_errlist_objs="$play_errlist_objs aac_afh" -fi -if test $HAVE_MP4V2 = yes || test $HAVE_FAAD = yes; then - play_errlist_objs="$play_errlist_objs aac_common" + play_errlist_objs="$play_errlist_objs aac_afh aacdec_filter" fi if test $HAVE_MAD = yes; then play_cmdline_objs="$play_cmdline_objs mp3dec_filter" @@ -973,7 +961,7 @@ attributes_txt decoder_flags audiod_status play_time attributes_bitmap offset seconds_total stream_start current_time audiod_uptime image_id lyrics_id duration directory lyrics_name image_name path hash channels last_played num_chunks chunk_time amplification artist title year album -comment" +comment max_chunk_size" result= for i in $status_items; do @@ -1003,7 +991,6 @@ unix socket credentials: $have_ucred readline (interactive CLIs): $HAVE_READLINE id3 version 2 support: $HAVE_ID3TAG faad: $HAVE_FAAD -mp4v2: $HAVE_MP4V2 audio format handlers: $audio_format_handlers filters: $(echo $filters) diff --git a/error.h b/error.h index fad41729..32e525cd 100644 --- a/error.h +++ b/error.h @@ -8,7 +8,6 @@ /** Codes and messages. */ #define PARA_ERRORS \ - PARA_ERROR(AAC_AFH_INIT, "failed to init aac decoder"), \ PARA_ERROR(AACDEC_INIT, "failed to init aac decoder"), \ PARA_ERROR(AAC_DECODE, "aac decode error"), \ PARA_ERROR(ACL_PERM, "access denied by acl"), \ @@ -92,7 +91,6 @@ PARA_ERROR(EMPTY, "file is empty"), \ PARA_ERROR(ENCRYPT, "encrypt error"), \ PARA_ERROR(EOF, "end of file"), \ - PARA_ERROR(ESDS, "did not find esds atom"), \ PARA_ERROR(FEC_BAD_IDX, "invalid index vector"), \ PARA_ERROR(FECDEC_EOF, "received eof packet"), \ PARA_ERROR(FECDEC_OVERRUN, "fecdec output buffer overrun"), \ @@ -147,8 +145,15 @@ PARA_ERROR(MP3DEC_CORRUPT, "too many corrupt frames"), \ PARA_ERROR(MP3DEC_EOF, "mp3dec: end of file"), \ PARA_ERROR(MP3_INFO, "could not read mp3 info"), \ - PARA_ERROR(MP4ASC, "audio spec config error"), \ - PARA_ERROR(MP4V2, "mp4v2 library error"), \ + PARA_ERROR(MP4FF_BAD_CHANNEL_COUNT, "mp4ff: invalid number of channels"), \ + PARA_ERROR(MP4FF_BAD_SAMPLE, "mp4ff: invalid sample number"), \ + PARA_ERROR(MP4FF_BAD_SAMPLERATE, "mp4ff: invalid sample rate"), \ + PARA_ERROR(MP4FF_BAD_SAMPLE_COUNT, "mp4ff: invalid number of samples"), \ + PARA_ERROR(MP4FF_META_READ, "mp4ff: could not read mp4 metadata"), \ + PARA_ERROR(MP4FF_META_WRITE, "mp4ff: could not update mp4 metadata"), \ + PARA_ERROR(MP4FF_OPEN, "mp4ff: open failed"), \ + PARA_ERROR(MP4FF_TRACK, "mp4fF: no audio track"), \ + PARA_ERROR(MPI_PRINT, "could not convert multi-precision integer"), \ PARA_ERROR(MPI_SCAN, "could not scan multi-precision integer"), \ PARA_ERROR(NAME_TOO_LONG, "name too long for struct sockaddr_un"), \ PARA_ERROR(NO_AFHI, "audio format handler info required"), \ @@ -221,13 +226,11 @@ PARA_ERROR(SSH_PARSE, "could not parse ssh public key"), \ PARA_ERROR(STAT_ITEM_PARSE, "failed to parse status item"), \ PARA_ERROR(STATUS_TIMEOUT, "status item timeout"), \ - PARA_ERROR(STCO, "did not find stco atom"), \ PARA_ERROR(STREAM_PACKETIN, "ogg stream packet-in error"), \ PARA_ERROR(STREAM_PACKETOUT, "ogg stream packet-out error"), \ PARA_ERROR(STREAM_PAGEIN, "ogg stream page-in error"), \ PARA_ERROR(STREAM_PAGEOUT, "ogg stream page-out error"), \ PARA_ERROR(STRFTIME, "strftime() failed"), \ - PARA_ERROR(STSZ, "did not find stcz atom"), \ PARA_ERROR(SYNC_COMPLETE, "all buddies in sync"), \ PARA_ERROR(SYNC_LISTEN_FD, "no fd to listen on"), \ PARA_ERROR(SYNC_PAGEOUT, "ogg sync page-out error (no ogg file?)"), \ diff --git a/flac_afh.c b/flac_afh.c index 385d4f0c..e2d53802 100644 --- a/flac_afh.c +++ b/flac_afh.c @@ -391,6 +391,7 @@ static int flac_afh_read_chunks(struct private_flac_afh_data *pfad) break; } afhi->chunks_total = c; + set_max_chunk_size(afhi); ret = 1; free_decoder: FLAC__stream_decoder_finish(decoder); diff --git a/ipc.c b/ipc.c index 9488224a..d7f515df 100644 --- a/ipc.c +++ b/ipc.c @@ -161,6 +161,27 @@ int shm_attach(int id, enum shm_attach_mode mode, void **result) return *result == (void *) -1? -ERRNO_TO_PARA_ERROR(errno) : 1; } +/** + * Get the size of a shared memory segment. + * + * \param id The shared memory segment identifier. + * \param result Size in bytes is returned here, zero on errors. + * + * \return Standard. + * + * \sa shmctl(2). + */ +int shm_size(int id, size_t *result) +{ + struct shmid_ds ds; /* data structure */ + + *result = 0; + if (shmctl(id, IPC_STAT, &ds) < 0) + return -ERRNO_TO_PARA_ERROR(errno); + *result = ds.shm_segsz; + return 1; +} + /** * Detach a shared memory segment. * diff --git a/ipc.h b/ipc.h index c8d31c0c..c4437104 100644 --- a/ipc.h +++ b/ipc.h @@ -11,4 +11,5 @@ int shm_new(size_t size); int shm_attach(int id, enum shm_attach_mode mode, void **result); int shm_detach(void *addr); int shm_destroy(int id); +int shm_size(int id, size_t *result); size_t shm_get_shmmax(void); diff --git a/mp3_afh.c b/mp3_afh.c index 2115f71c..e5d0ff13 100644 --- a/mp3_afh.c +++ b/mp3_afh.c @@ -657,6 +657,7 @@ static int mp3_read_info(unsigned char *map, size_t numbytes, int fd, tv_divide(afhi->chunks_total, &total_time, &afhi->chunk_tv); PARA_DEBUG_LOG("%" PRIu32 "chunks, each %lums\n", afhi->chunks_total, tv2ms(&afhi->chunk_tv)); + set_max_chunk_size(afhi); ret = mp3_get_id3(map, numbytes, fd, &afhi->tags); afhi->techinfo = make_message("%cbr, %s, %s tags", vbr? 'v' : 'c', header_mode(&header), tag_versions[ret]); diff --git a/ogg_afh_common.c b/ogg_afh_common.c index 6e5a8934..734fd586 100644 --- a/ogg_afh_common.c +++ b/ogg_afh_common.c @@ -181,6 +181,7 @@ int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi, } } afhi->chunks_total = j; + set_max_chunk_size(afhi); set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv); ret = 0; out: diff --git a/server.h b/server.h index 8de691ca..7f276ab9 100644 --- a/server.h +++ b/server.h @@ -48,8 +48,6 @@ struct sender_command_data { * propagate to the stat command handlers. */ struct misc_meta_data { - /** The size of the current audio file in bytes. */ - size_t size; /** The "old" status flags -- commands may only read them. */ unsigned int vss_status_flags; /** The new status flags -- commands may set them. */ diff --git a/vss.c b/vss.c index 5484db9d..24dfc6bb 100644 --- a/vss.c +++ b/vss.c @@ -88,6 +88,8 @@ struct vss_task { enum afs_socket_status afsss; /** The memory mapped audio file. */ char *map; + /** The size of the memory mapping. */ + size_t mapsize; /** Used by the scheduler. */ struct task *task; /** Pointer to the header of the mapped audio file. */ @@ -96,6 +98,8 @@ struct vss_task { size_t header_len; /** Time between audio file headers are sent. */ struct timeval header_interval; + /* Only used if afh supports dynamic chunks. */ + void *afh_context; }; /** @@ -347,6 +351,8 @@ static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst) static void vss_get_chunk(int chunk_num, struct vss_task *vsst, char **buf, size_t *sz) { + int ret; + /* * Chunk zero is special for header streams: It is the first portion of * the audio file which consists of the audio file header. It may be @@ -360,8 +366,15 @@ static void vss_get_chunk(int chunk_num, struct vss_task *vsst, *sz = vsst->header_len; return; } - afh_get_chunk(chunk_num, &mmd->afd.afhi, vsst->map, (const char **)buf, - sz); + ret = afh_get_chunk(chunk_num, &mmd->afd.afhi, + mmd->afd.audio_format_id, vsst->map, vsst->mapsize, + (const char **)buf, sz, &vsst->afh_context); + if (ret < 0) { + PARA_WARNING_LOG("could not get chunk %d: %s\n", + chunk_num, para_strerror(-ret)); + *buf = NULL; + *sz = 0; + } } static void compute_group_size(struct vss_task *vsst, struct fec_group *g, @@ -846,7 +859,7 @@ static void vss_eof(struct vss_task *vsst) set_eof_barrier(vsst); afh_free_header(vsst->header_buf, mmd->afd.audio_format_id); vsst->header_buf = NULL; - para_munmap(vsst->map, mmd->size); + para_munmap(vsst->map, vsst->mapsize); vsst->map = NULL; mmd->chunks_sent = 0; //mmd->offset = 0; @@ -855,7 +868,9 @@ static void vss_eof(struct vss_task *vsst) mmd->afd.afhi.chunk_tv.tv_usec = 0; free(mmd->afd.afhi.chunk_table); mmd->afd.afhi.chunk_table = NULL; - mmd->size = 0; + vsst->mapsize = 0; + afh_close(vsst->afh_context, mmd->afd.audio_format_id); + vsst->afh_context = NULL; mmd->events++; } @@ -973,11 +988,11 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds) ret = -ERRNO_TO_PARA_ERROR(errno); goto err; } - mmd->size = statbuf.st_size; - ret = para_mmap(mmd->size, PROT_READ, MAP_PRIVATE | MAP_POPULATE, + ret = para_mmap(statbuf.st_size, PROT_READ, MAP_PRIVATE | MAP_POPULATE, passed_fd, 0, &vsst->map); if (ret < 0) goto err; + vsst->mapsize = statbuf.st_size; close(passed_fd); mmd->chunks_sent = 0; mmd->current_chunk = 0; @@ -986,7 +1001,7 @@ static void recv_afs_result(struct vss_task *vsst, fd_set *rfds) mmd->num_played++; mmd->new_vss_status_flags &= (~VSS_NEXT); afh_get_header(&mmd->afd.afhi, mmd->afd.audio_format_id, - vsst->map, mmd->size, &vsst->header_buf, &vsst->header_len); + vsst->map, vsst->mapsize, &vsst->header_buf, &vsst->header_len); return; err: free(mmd->afd.afhi.chunk_table); @@ -1076,7 +1091,7 @@ static void vss_send(struct vss_task *vsst) */ if (mmd->current_chunk > 0) { /* chunk 0 might be on the heap */ buf += len; - for (i = 0; i < 5 && buf < vsst->map + mmd->size; i++) { + for (i = 0; i < 5 && buf < vsst->map + vsst->mapsize; i++) { __a_unused volatile char x = *buf; buf += 4096; } @@ -1113,7 +1128,7 @@ static int vss_post_select(struct sched *s, void *context) set_eof_barrier(vsst); mmd->chunks_sent = 0; mmd->current_chunk = afh_get_start_chunk(mmd->repos_request, - &mmd->afd.afhi); + &mmd->afd.afhi, mmd->afd.audio_format_id); mmd->new_vss_status_flags &= ~VSS_REPOS; set_mmd_offset(); } diff --git a/web/manual.md b/web/manual.md index fef8123a..dff8a22a 100644 --- a/web/manual.md +++ b/web/manual.md @@ -248,8 +248,11 @@ recognized. The mp3 tagger also needs this library for modifying you need libogg, libvorbis, libvorbisfile. The corresponding Debian packages are called `libogg-dev` and `libvorbis-dev`. -- [libfaad](http://www.audiocoding.com/). For aac files (m4a) you -need libfaad (`libfaad-dev`). +- [libfaad and mp4ff](http://www.audiocoding.com/). For aac files +(m4a) you need libfaad and libmp4ff (package: `libfaad-dev`). Note +that for some distributions, e.g. Ubuntu, mp4ff is not part of the +libfaad package. Install the faad library from sources (available +through the above link) to get the mp4ff library and header files. - [speex](http://www.speex.org/). In order to stream or decode speex files, libspeex (`libspeex-dev`) is required. diff --git a/wma_afh.c b/wma_afh.c index 0d543195..6bf2d641 100644 --- a/wma_afh.c +++ b/wma_afh.c @@ -228,6 +228,7 @@ static int wma_make_chunk_table(char *buf, size_t buf_size, uint32_t packet_size } } afhi->chunks_total = j; + set_max_chunk_size(afhi); set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv); return 1; fail: