From f577a6a22c646669e56c5436859d2f5ec8b421e8 Mon Sep 17 00:00:00 2001 From: Thomas Martitz Date: Wed, 9 Feb 2011 20:13:13 +0000 Subject: Embedded album art support in MP3/ID3v2 tags. - Support is limited to non-desync jpeg in id3v2 tags. Other formats (hopefully) follow in the future. - Embedded album art takes precedence over files in album art files. - No additional buffers are used, the jpeg is read directly from the audio file. Flyspray: FS#11216 Author: Yoshihisa Uchida and I git-svn-id: svn://svn.rockbox.org/rockbox/trunk@29259 a1c6a512-1295-4272-9138-f99709370657 --- apps/buffering.c | 29 ++++++++++++++---- apps/metadata.h | 21 +++++++++++++ apps/metadata/id3tags.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++ apps/playback.c | 35 +++++++++++++++++----- apps/playback.h | 7 +++++ apps/recorder/jpeg_load.c | 43 ++++++++++++++++++++++----- apps/recorder/jpeg_load.h | 23 ++++++++++++++ 7 files changed, 213 insertions(+), 21 deletions(-) diff --git a/apps/buffering.c b/apps/buffering.c index 123c7bc85f..9c0ad138f7 100644 --- a/apps/buffering.c +++ b/apps/buffering.c @@ -52,6 +52,7 @@ #include "albumart.h" #include "jpeg_load.h" #include "bmp.h" +#include "playback.h" #endif #define GUARD_BUFSIZE (32*1024) @@ -908,10 +909,12 @@ static bool fill_buffer(void) /* Given a file descriptor to a bitmap file, write the bitmap data to the buffer, with a struct bitmap and the actual data immediately following. Return value is the total size (struct + data). */ -static int load_image(int fd, const char *path, struct dim *dim) +static int load_image(int fd, const char *path, struct bufopen_bitmap_data *data) { int rc; struct bitmap *bmp = (struct bitmap *)&buffer[buf_widx]; + struct dim *dim = data->dim; + struct mp3_albumart *aa = data->embedded_albumart; /* get the desired image size */ bmp->width = dim->width, bmp->height = dim->height; @@ -928,8 +931,13 @@ static int load_image(int fd, const char *path, struct dim *dim) - sizeof(struct bitmap); #ifdef HAVE_JPEG - int pathlen = strlen(path); - if (strcmp(path + pathlen - 4, ".bmp")) + if (aa != NULL) + { + lseek(fd, aa->pos, SEEK_SET); + rc = clip_jpeg_fd(fd, aa->size, bmp, free, FORMAT_NATIVE|FORMAT_DITHER| + FORMAT_RESIZE|FORMAT_KEEP_ASPECT, NULL); + } + else if (strcmp(path + strlen(path) - 4, ".bmp")) rc = read_jpeg_fd(fd, bmp, free, FORMAT_NATIVE|FORMAT_DITHER| FORMAT_RESIZE|FORMAT_KEEP_ASPECT, NULL); else @@ -1010,7 +1018,18 @@ int bufopen(const char *file, size_t offset, enum data_type type, if (fd < 0) return ERR_FILE_ERROR; - size_t size = filesize(fd); + size_t size = 0; +#ifdef HAVE_ALBUMART + if (type == TYPE_BITMAP) + { /* if albumart is embedded, the complete file is not buffered, + * but only the jpeg part; filesize() would be wrong */ + struct bufopen_bitmap_data *aa = (struct bufopen_bitmap_data*)user_data; + if (aa->embedded_albumart) + size = aa->embedded_albumart->size; + } +#endif + if (size == 0) + size = filesize(fd); bool can_wrap = type==TYPE_PACKET_AUDIO || type==TYPE_CODEC; size_t adjusted_offset = offset; @@ -1058,7 +1077,7 @@ int bufopen(const char *file, size_t offset, enum data_type type, /* Bitmap file: we load the data instead of the file */ int rc; mutex_lock(&llist_mod_mutex); /* Lock because load_bitmap yields */ - rc = load_image(fd, file, (struct dim*)user_data); + rc = load_image(fd, file, (struct bufopen_bitmap_data*)user_data); mutex_unlock(&llist_mod_mutex); if (rc <= 0) { diff --git a/apps/metadata.h b/apps/metadata.h index b99d4d09d6..8d20882835 100644 --- a/apps/metadata.h +++ b/apps/metadata.h @@ -190,6 +190,22 @@ enum { ID3_VER_2_4 }; +#ifdef HAVE_ALBUMART +enum mp3_aa_type { + AA_TYPE_UNSYNC = -1, + AA_TYPE_UNKNOWN, + AA_TYPE_BMP, + AA_TYPE_PNG, + AA_TYPE_JPG, +}; + +struct mp3_albumart { + enum mp3_aa_type type; + int size; + off_t pos; +}; +#endif + struct mp3entry { char path[MAX_PATH]; char* title; @@ -277,6 +293,11 @@ struct mp3entry { long album_peak; #endif +#ifdef HAVE_ALBUMART + bool embed_albumart; + struct mp3_albumart albumart; +#endif + /* Cuesheet support */ struct cuesheet *cuesheet; diff --git a/apps/metadata/id3tags.c b/apps/metadata/id3tags.c index 75056a273c..a20a59f06e 100644 --- a/apps/metadata/id3tags.c +++ b/apps/metadata/id3tags.c @@ -290,6 +290,63 @@ static int parsegenre( struct mp3entry* entry, char* tag, int bufferpos ) } } +#ifdef HAVE_ALBUMART +/* parse embed albumart */ +static int parsealbumart( struct mp3entry* entry, char* tag, int bufferpos ) +{ + entry->embed_albumart = false; + + /* we currently don't support unsynchronizing albumart */ + if (entry->albumart.type == AA_TYPE_UNSYNC) + return bufferpos; + + entry->albumart.type = AA_TYPE_UNKNOWN; + + char *start = tag; + /* skip text encoding */ + tag += 1; + + if (memcmp(tag, "image/", 6) == 0) + { + /* ID3 v2.3+ */ + tag += 6; + if (strcmp(tag, "jpeg") == 0) + { + entry->albumart.type = AA_TYPE_JPG; + tag += 5; + } + else if (strcmp(tag, "png") == 0) + { + entry->albumart.type = AA_TYPE_PNG; + tag += 4; + } + } + else + { + /* ID3 v2.2 */ + if (memcmp(tag, "JPG", 3) == 0) + entry->albumart.type = AA_TYPE_JPG; + else if (memcmp(tag, "PNG", 3) == 0) + entry->albumart.type = AA_TYPE_PNG; + tag += 3; + } + + if (entry->albumart.type != AA_TYPE_UNKNOWN) + { + /* skip picture type */ + tag += 1; + /* skip description */ + tag = strchr(tag, '\0') + 1; + /* fixup offset&size for image data */ + entry->albumart.pos += tag - start; + entry->albumart.size -= tag - start; + entry->embed_albumart = true; + } + /* return bufferpos as we didn't store anything in id3v2buf */ + return bufferpos; +} +#endif + /* parse user defined text, looking for album artist and replaygain * information. */ @@ -439,6 +496,10 @@ static const struct tag_resolver taglist[] = { { "COM", 3, offsetof(struct mp3entry, comment), NULL, false }, { "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre, false }, { "TCO", 3, offsetof(struct mp3entry, genre_string), &parsegenre, false }, +#ifdef HAVE_ALBUMART + { "APIC", 4, 0, &parsealbumart, true }, + { "PIC", 3, 0, &parsealbumart, true }, +#endif { "TXXX", 4, 0, &parseuser, false }, #if CONFIG_CODEC == SWCODEC { "RVA2", 4, 0, &parserva2, true }, @@ -961,6 +1022,21 @@ void setid3v2title(int fd, struct mp3entry *entry) if (ptag && !*ptag) *ptag = tag; + /* albumart */ + if ((!entry->embed_albumart) && + ((tr->tag_length == 4 && !memcmp( header, "APIC", 4)) || + (tr->tag_length == 3 && !memcmp( header, "PIC" , 3)))) + { + if (unsynch || (global_unsynch && version <= ID3_VER_2_3)) + entry->albumart.type = AA_TYPE_UNSYNC; + else + { + entry->albumart.pos = lseek(fd, 0, SEEK_CUR) - framelen; + entry->albumart.size = totframelen; + entry->albumart.type = AA_TYPE_UNKNOWN; + } + } + if( tr->ppFunc ) bufferpos = tr->ppFunc(entry, tag, bufferpos); diff --git a/apps/playback.c b/apps/playback.c index fe7b74893a..9030161f4a 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -142,6 +142,7 @@ static struct cuesheet *curr_cue = NULL; #define MAX_MULTIPLE_AA SKINNABLE_SCREENS_COUNT #ifdef HAVE_ALBUMART + static struct albumart_slot { struct dim dim; /* holds width, height of the albumart */ int used; /* counter, increments if something uses it */ @@ -228,7 +229,6 @@ static bool audio_have_tracks(void); static void audio_reset_buffer(void); static void audio_stop_playback(void); - /**************************************/ @@ -647,6 +647,7 @@ bool audio_peek_track(struct mp3entry** id3, int offset) } #ifdef HAVE_ALBUMART + int playback_current_aa_hid(int slot) { if (slot < 0) @@ -656,13 +657,13 @@ int playback_current_aa_hid(int slot) cur_idx = track_ridx + offset; cur_idx &= MAX_TRACK_MASK; - return tracks[cur_idx].aa_hid[slot]; } int playback_claim_aa_slot(struct dim *dim) { int i; + /* first try to find a slot already having the size to reuse it * since we don't want albumart of the same size buffered multiple times */ FOREACH_ALBUMART(i) @@ -693,6 +694,7 @@ void playback_release_aa_slot(int slot) { /* invalidate the albumart_slot */ struct albumart_slot *aa_slot = &albumart_slots[slot]; + if (aa_slot->used > 0) aa_slot->used--; } @@ -1315,19 +1317,37 @@ static void audio_finish_load_track(void) { int i; char aa_path[MAX_PATH]; + FOREACH_ALBUMART(i) { /* albumart_slots may change during a yield of bufopen, * but that's no problem */ if (tracks[track_widx].aa_hid[i] >= 0 || !albumart_slots[i].used) continue; + + /* we can only decode jpeg for embedded AA */ + bool embedded_albumart = + track_id3->embed_albumart && track_id3->albumart.type == AA_TYPE_JPG; /* find_albumart will error out if the wps doesn't have AA */ - if (find_albumart(track_id3, aa_path, sizeof(aa_path), - &(albumart_slots[i].dim))) + if (embedded_albumart || find_albumart(track_id3, aa_path, + sizeof(aa_path), &(albumart_slots[i].dim))) { - int aa_hid = bufopen(aa_path, 0, TYPE_BITMAP, - &(albumart_slots[i].dim)); - + int aa_hid; + struct bufopen_bitmap_data user_data = { + .dim = &(albumart_slots[i].dim), + .embedded_albumart = NULL, + }; + if (embedded_albumart) + { + user_data.embedded_albumart = &(track_id3->albumart); + aa_hid = bufopen(track_id3->path, 0, + TYPE_BITMAP, &user_data); + } + else + { + aa_hid = bufopen(aa_path, 0, TYPE_BITMAP, + &user_data); + } if(aa_hid == ERR_BUFFER_FULL) { filling = STATE_FULL; @@ -1342,7 +1362,6 @@ static void audio_finish_load_track(void) tracks[track_widx].aa_hid[i] = aa_hid; } } - } #endif diff --git a/apps/playback.h b/apps/playback.h index 27e27ff240..475e2fb662 100644 --- a/apps/playback.h +++ b/apps/playback.h @@ -29,6 +29,7 @@ #ifdef HAVE_ALBUMART #include "bmp.h" +#include "metadata.h" /* * Returns the handle id of the buffered albumart for the given slot id **/ @@ -50,6 +51,12 @@ int playback_claim_aa_slot(struct dim *dim); * * Save to call from other threads */ void playback_release_aa_slot(int slot); + +struct bufopen_bitmap_data { + struct dim *dim; + struct mp3_albumart *embedded_albumart; +}; + #endif /* Functions */ diff --git a/apps/recorder/jpeg_load.c b/apps/recorder/jpeg_load.c index 1af65fab6c..cd13934921 100644 --- a/apps/recorder/jpeg_load.c +++ b/apps/recorder/jpeg_load.c @@ -75,12 +75,12 @@ struct jpeg { #ifdef JPEG_FROM_MEM unsigned char *data; - unsigned long len; #else int fd; int buf_left; int buf_index; #endif + unsigned long len; unsigned long int bitbuf; int bitbuf_bits; int marker_ind; @@ -888,8 +888,12 @@ INLINE void jpeg_putc(struct jpeg* p_jpeg) #else INLINE void fill_buf(struct jpeg* p_jpeg) { - p_jpeg->buf_left = read(p_jpeg->fd, p_jpeg->buf, JPEG_READ_BUF_SIZE); + p_jpeg->buf_left = read(p_jpeg->fd, p_jpeg->buf, + (p_jpeg->len >= JPEG_READ_BUF_SIZE)? + JPEG_READ_BUF_SIZE : p_jpeg->len); p_jpeg->buf_index = 0; + if (p_jpeg->buf_left > 0) + p_jpeg->len -= p_jpeg->buf_left; } static unsigned char *jpeg_getc(struct jpeg* p_jpeg) @@ -1960,7 +1964,9 @@ block_end: * *****************************************************************************/ #ifndef JPEG_FROM_MEM -int read_jpeg_file(const char* filename, +int clip_jpeg_file(const char* filename, + int offset, + unsigned long jpeg_size, struct bitmap *bm, int maxsize, int format, @@ -1975,11 +1981,20 @@ int read_jpeg_file(const char* filename, DEBUGF("read_jpeg_file: can't open '%s', rc: %d\n", filename, fd); return fd * 10 - 1; } - - ret = read_jpeg_fd(fd, bm, maxsize, format, cformat); + lseek(fd, offset, SEEK_SET); + ret = clip_jpeg_fd(fd, jpeg_size, bm, maxsize, format, cformat); close(fd); return ret; } + +int read_jpeg_file(const char* filename, + struct bitmap *bm, + int maxsize, + int format, + const struct custom_format *cformat) +{ + return clip_jpeg_file(filename, 0, 0, bm, maxsize, format, cformat); +} #endif static int calc_scale(int in_size, int out_size) @@ -2014,10 +2029,11 @@ int get_jpeg_dim_mem(unsigned char *data, unsigned long len, return 0; } -int decode_jpeg_mem(unsigned char *data, unsigned long len, +int decode_jpeg_mem(unsigned char *data, #else -int read_jpeg_fd(int fd, +int clip_jpeg_fd(int fd, #endif + unsigned long len, struct bitmap *bm, int maxsize, int format, @@ -2039,11 +2055,13 @@ int read_jpeg_fd(int fd, return -1; #endif memset(p_jpeg, 0, sizeof(struct jpeg)); + p_jpeg->len = len; #ifdef JPEG_FROM_MEM p_jpeg->data = data; - p_jpeg->len = len; #else p_jpeg->fd = fd; + if (p_jpeg->len == 0) + p_jpeg->len = filesize(p_jpeg->fd); #endif status = process_markers(p_jpeg); #ifndef JPEG_FROM_MEM @@ -2212,4 +2230,13 @@ int read_jpeg_fd(int fd, return 0; } +int read_jpeg_fd(int fd, + struct bitmap *bm, + int maxsize, + int format, + const struct custom_format *cformat) +{ + return clip_jpeg_fd(fd, 0, bm, maxsize, format, cformat); +} + /**************** end JPEG code ********************/ diff --git a/apps/recorder/jpeg_load.h b/apps/recorder/jpeg_load.h index 73b6c51bf3..6ff96dabad 100644 --- a/apps/recorder/jpeg_load.h +++ b/apps/recorder/jpeg_load.h @@ -44,4 +44,27 @@ int read_jpeg_fd(int fd, int format, const struct custom_format *cformat); +/** + * read embedded jpeg files as above. Needs an offset and length into + * the file + **/ +int clip_jpeg_file(const char* filename, + int offset, + unsigned long jpeg_size, + struct bitmap *bm, + int maxsize, + int format, + const struct custom_format *cformat); + +/** + * read embedded jpeg files as above. Needs an open file descripter, and + * assumes the caller has lseek()'d to the start of the jpeg blob + **/ +int clip_jpeg_fd(int fd, + unsigned long jpeg_size, + struct bitmap *bm, + int maxsize, + int format, + const struct custom_format *cformat); + #endif /* _JPEG_JPEG_DECODER_H */ -- cgit v1.2.3