Was cooking for about a week.
mp3_afh: Detect both v1 and v2 tags.
ogg: Improve documentation of struct ogg_afh_callback_info.
ogg_afh: Add a comment about the three vorbis header packets.
afh_common.c: Avoid ifdefs.
afh loglevel adjustments.
wma: Rename comment_header.
afh: Fix fd leak.
afh: Unmap the audio file on errors.
- audiod memory leak fixes.
- Miscellaneous improvements to the build system.
- oss_writer improvements.
+ - Improved handling of mp3 files with both id3v1 and id3v2 tags.
Download: ./releases/paraslash-git.tar.bz2 (tarball)
}
ret = compute_afhi(conf.inputs[i], audio_file_data, audio_file_size,
fd, &afhi);
- if (ret < 0)
- goto out;
-
- audio_format_num = ret;
- 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");
- clear_afhi(&afhi);
+ if (ret >= 0) {
+ audio_format_num = ret;
+ 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");
+ clear_afhi(&afhi);
+ }
+ close(fd);
ret2 = para_munmap(audio_file_data, audio_file_size);
if (ret2 < 0 && ret >= 0)
ret = ret2;
#include "string.h"
#include "afh.h"
-/* The mp3 audio format handler does not need any libs. */
-void mp3_init(struct audio_format_handler *);
-
-#ifdef HAVE_OGGVORBIS
- void ogg_init(struct audio_format_handler *);
-#endif
-#ifdef HAVE_FAAD
- void aac_afh_init(struct audio_format_handler *);
-#endif
-#ifdef HAVE_SPEEX
- void spx_afh_init(struct audio_format_handler *);
-#endif
-#ifdef HAVE_FLAC
- void flac_afh_init(struct audio_format_handler *);
-#endif
-
-#ifdef HAVE_OPUS
- void opus_afh_init(struct audio_format_handler *);
-#endif
-
-void wma_afh_init(struct audio_format_handler *);
+typedef void afh_init_func(struct audio_format_handler *);
+/* It does not hurt to declare init functions which are not available. */
+extern afh_init_func mp3_init, ogg_init, aac_afh_init, wma_afh_init,
+ spx_afh_init, flac_afh_init, opus_afh_init;
/** The list of all status items */
const char *status_item_list[] = {STATUS_ITEM_ARRAY};
{
int i;
- PARA_INFO_LOG("supported audio formats: %s\n", AUDIO_FORMAT_HANDLERS);
+ PARA_NOTICE_LOG("supported audio formats: %s\n", AUDIO_FORMAT_HANDLERS);
FOR_EACH_AUDIO_FORMAT(i) {
- PARA_NOTICE_LOG("initializing %s handler\n",
+ PARA_INFO_LOG("initializing %s handler\n",
audio_format_name(i));
afl[i].init(&afl[i]);
}
return NULL;
}
-static void mp3_get_id3(__a_unused unsigned char *map,
- __a_unused size_t numbytes, int fd, struct taginfo *tags)
+/* this only sets values which are undefined so far */
+static void parse_frames(struct id3_tag *id3_t, struct taginfo *tags)
{
int i;
- struct id3_tag *id3_t;
- struct id3_file *id3_f = id3_file_fdopen(fd, ID3_FILE_MODE_READONLY);
-
- if (!id3_f)
- return;
- id3_t = id3_file_tag(id3_f);
- if (!id3_t) {
- id3_file_close(id3_f);
- return;
- }
+
for (i = 0; i < id3_t->nframes; i++) {
struct id3_frame *fr = id3_t->frames[i];
if (!strcmp(fr->id, ID3_FRAME_TITLE)) {
continue;
}
}
- id3_file_close(id3_f);
+}
+
+static int mp3_get_id3(unsigned char *map, size_t numbytes, __a_unused int fd,
+ struct taginfo *tags)
+{
+ int ret = 0;
+ struct id3_tag *id3_t;
+
+ /* id3v2 tags are usually located at the beginning. */
+ id3_t = id3_tag_parse(map, numbytes);
+ if (id3_t) {
+ parse_frames(id3_t, tags);
+ ret |= 2;
+ id3_tag_delete(id3_t);
+ }
+ /* Also look for an id3v1 tag at the end of the file. */
+ if (numbytes >= 128) {
+ id3_t = id3_tag_parse(map + numbytes - 128, 128);
+ if (id3_t) {
+ parse_frames(id3_t, tags);
+ ret |= 1;
+ id3_tag_delete(id3_t);
+ }
+ }
+ return ret;
}
#else /* HAVE_LIBID3TAG */
return string;
}
-static void mp3_get_id3(unsigned char *map, size_t numbytes, __a_unused int fd,
+static int mp3_get_id3(unsigned char *map, size_t numbytes, __a_unused int fd,
struct taginfo *tags)
{
char title[31], artist[31], album[31], year[5], comment[31];
if (numbytes < 128 || strncmp("TAG", (char *)map + numbytes - 128, 3)) {
PARA_DEBUG_LOG("no id3 v1 tag\n");
- return;
+ return 0;
}
fpos = numbytes - 125;
memcpy(title, map + fpos, 30);
tags->year = para_strdup(year);
tags->album = para_strdup(album);
tags->comment = para_strdup(comment);
+ return 1;
}
#endif /* HAVE_LIBID3TAG */
unsigned chunk_table_size = 1000; /* gets increased on demand */
off_t fpos = 0;
struct mp3header header;
+ const char *tag_versions[] = {"no", "id3v1", "id3v2", "id3v1+id3v2"};
afhi->chunks_total = 0;
afhi->chunk_table = para_malloc(chunk_table_size * sizeof(uint32_t));
tv_divide(afhi->chunks_total, &total_time, &afhi->chunk_tv);
PARA_DEBUG_LOG("%lu chunks, each %lums\n", afhi->chunks_total,
tv2ms(&afhi->chunk_tv));
- afhi->techinfo = make_message("%cbr, %s", vbr? 'v' : 'c',
- header_mode(&header));
- mp3_get_id3(map, numbytes, fd, &afhi->tags);
+ 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]);
return 1;
err_out:
PARA_ERROR_LOG("%s\n", para_strerror(-ret));
vorbis_comment vc;
};
+/*
+ * Vorbis uses three header packets, all of which are required: the
+ * identification header, the comments header, and the setup header.
+ *
+ * The identification header identifies the bitstream as Vorbis. It contains
+ * the Vorbis version and simple audio characteristics of the stream such as
+ * sample rate and number of channels.
+ *
+ * The comment header includes user text comments (tags) and a vendor string
+ * for the application/library that produced the bitstream.
+ *
+ * The setup header includes extensive CODEC setup information as well as the
+ * complete VQ and Huffman codebooks needed for decoding.
+ */
static int vorbis_packet_callback(ogg_packet *packet, int packet_num,
__a_unused int serial, struct afh_info *afhi, void *private_data)
{
*/
/**
- * Callback structure provided by vorbis/speex audio format handlers.
+ * Callback structure provided by audio format handlers.
*
- * Both audio formats utilize the ogg container format. Meta info about
- * the audio file is contained in the first three ogg packets.
+ * All audio formats which utilize the ogg container format define a callback
+ * function whose purpose is to extract the meta information about the audio
+ * file from the first few ogg packets of the bitstream.
*/
struct ogg_afh_callback_info {
/**
- * ogg_get_file_info() calls this function for each of the three
- * header packets. If this callback returns a negative value, the
- * audio file is considered invalid and the chunk table is not
- * created. If it returns zero, the end of the header has been
- * reached and no further ogg packets should be processed.
- */
+ * ogg_get_file_info() calls this function for the first three ogg
+ * packets, or until the callback returns a non-positive value. If it
+ * returns negative, the audio file was not recognized by the audio
+ * format handler. If it returns zero, the audio file is considered
+ * valid, and no further processing by the callback is needed. In this
+ * case the header chunk, i.e. chunk number zero, is defined as the
+ * concatenation of all packets that have been processed by the
+ * callback.
+ */
int (*packet_callback)(ogg_packet *packet, int packet_num,
int serial, struct afh_info *afhi, void *private_data);
- /** Vorbis/speex specific data. */
+ /** Data specific to the audio format handler. */
void *private_data;
};
return out;
}
-static const char comment_header[] = {
+static const char content_description_header[] = {
0x33, 0x26, 0xb2, 0x75, 0x8E, 0x66, 0xCF, 0x11,
0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c
};
const char *p, *end = buf + buf_size, *q;
uint16_t len1, len2, len3, len4;
- p = search_pattern(comment_header, sizeof(comment_header),
- buf, buf_size);
+ p = search_pattern(content_description_header,
+ sizeof(content_description_header), buf, buf_size);
if (!p || p + 34 >= end) {
- PARA_NOTICE_LOG("comment header not found\n");
+ PARA_NOTICE_LOG("content description header not found\n");
goto next;
}
p += 24;