diff --git a/apps/buffering.c b/apps/buffering.c index 2c26c66..1483073 100644 --- a/apps/buffering.c +++ b/apps/buffering.c @@ -833,7 +833,7 @@ static void shrink_handle(struct memory_handle *h) if (h->next && h->filerem == 0 && (h->type == TYPE_ID3 || h->type == TYPE_CUESHEET || h->type == TYPE_BITMAP || h->type == TYPE_CODEC || - h->type == TYPE_ATOMIC_AUDIO)) + h->type == TYPE_ATOMIC_AUDIO || h->type == TYPE_BUFFER)) { /* metadata handle: we can move all of it */ uintptr_t handle_distance = diff --git a/apps/codec_thread.c b/apps/codec_thread.c index 21d55a7..06c560c 100644 --- a/apps/codec_thread.c +++ b/apps/codec_thread.c @@ -162,9 +162,9 @@ void codec_thread_do_callback(void (*fn)(void), unsigned int *id) static void* codec_get_buffer(size_t *size) { - if (codec_size >= CODEC_SIZE) + if (codec_size >= CODEC_SIZE - ALBUMART_SIZE) return NULL; - *size = CODEC_SIZE - codec_size; + *size = CODEC_SIZE - ALBUMART_SIZE - codec_size; return &codecbuf[codec_size]; } diff --git a/apps/metadata.h b/apps/metadata.h index 0d0f6c5..f9a7b1e 100644 --- a/apps/metadata.h +++ b/apps/metadata.h @@ -182,6 +182,21 @@ enum { ID3_VER_2_4 }; +#ifdef HAVE_ALBUMART +enum mp3_aa_type { + 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; @@ -261,6 +276,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/mp3.c b/apps/metadata/mp3.c index 49d5c73..e2529f7 100644 --- a/apps/metadata/mp3.c +++ b/apps/metadata/mp3.c @@ -288,6 +288,41 @@ 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->albumart.type = AA_TYPE_UNKNOWN; + + if (memcmp(++tag, "image/", 6) == 0) + { + /* ID3 v2.3+ */ + tag += 6; + if (memcmp(tag, "jpeg", 4) == 0) + entry->albumart.type = AA_TYPE_JPG; + else if (memcmp(tag, "png", 3) == 0) + entry->albumart.type = AA_TYPE_PNG; + + entry->albumart.pos += 14; + entry->albumart.size -= 14; + } + 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; + + entry->albumart.pos += 6; + entry->albumart.size -= 6; + } + + entry->embed_albumart = (entry->albumart.type != AA_TYPE_UNKNOWN); + return bufferpos; +} +#endif + /* parse user defined text, looking for album artist and replaygain * information. */ @@ -435,6 +470,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 }, @@ -954,6 +993,15 @@ static 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)))) + { + entry->albumart.pos = lseek(fd, 0, SEEK_CUR) - framelen; + entry->albumart.size = totframelen; + } + if( tr->ppFunc ) bufferpos = tr->ppFunc(entry, tag, bufferpos); diff --git a/apps/playback.c b/apps/playback.c index ebe4932..c2befe8 100644 --- a/apps/playback.c +++ b/apps/playback.c @@ -42,6 +42,7 @@ #ifdef HAVE_LCD_BITMAP #ifdef HAVE_ALBUMART #include "albumart.h" +#include "recorder/jpeg_load.h" #endif #endif #include "sound.h" @@ -141,6 +142,20 @@ static struct cuesheet *curr_cue = NULL; #define MAX_MULTIPLE_AA 2 #ifdef HAVE_ALBUMART +/* must be ALBUMART_SIZE >= BMP_ALBUMART_SIZE + working area size */ +#define BMP_ALBUMART_SIZE 0x10000 + +static struct embed_albumart { + /* The ID for the embed albumart's handle */ + int hid; + + /* The dimension for the embed albumart */ + struct dim dim; + + /* whether can use */ + bool usable; +} embed_aa; + static struct albumart_slot { struct dim dim; /* holds width, height of the albumart */ int used; /* counter, increments if something uses it */ @@ -648,22 +663,30 @@ bool audio_peek_track(struct mp3entry** id3, int offset) } #ifdef HAVE_ALBUMART + int playback_current_aa_hid(int slot) { - if (slot < 0) - return -1; int cur_idx; int offset = ci.new_track + wps_offset; + if (slot < 0) + return -1; + + if (embed_aa.usable) + return embed_aa.hid; + 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; + + /* set the embed albumart's width and height */ + embed_aa.dim = *dim; + /* 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) @@ -692,8 +715,12 @@ int playback_claim_aa_slot(struct dim *dim) void playback_release_aa_slot(int slot) { + if (slot < 0) + return; + /* invalidate the albumart_slot */ struct albumart_slot *aa_slot = &albumart_slots[slot]; + if (aa_slot->used > 0) aa_slot->used--; } @@ -964,6 +991,72 @@ static void buffering_handle_finished_callback(void *data) } } +#ifdef HAVE_ALBUMART +/* --- embed albumart --- */ +static bool audio_regist_embed_albumart(const struct mp3entry *id3) +{ + unsigned char *tmp_buffer = codecbuf + CODEC_SIZE - ALBUMART_SIZE; + unsigned char *bmp_buffer = codecbuf + CODEC_SIZE - BMP_ALBUMART_SIZE; + int fd; + int size; + int jpg_format = FORMAT_NATIVE|FORMAT_DITHER|FORMAT_KEEP_ASPECT; + struct bitmap *bmp; + + embed_aa.usable = false; + if (!id3 || !id3->embed_albumart || embed_aa.hid < 0) + return false; + + fd = open(id3->path, O_RDONLY); + if (fd < 0) + { + logf("does not open file: %s", id3->path); + return false; + } + + lseek(fd, id3->albumart.pos, SEEK_SET); + size = id3->albumart.size; + if (size > ALBUMART_SIZE - BMP_ALBUMART_SIZE) + size = ALBUMART_SIZE - BMP_ALBUMART_SIZE; + read(fd, tmp_buffer, size); + close(fd); + + if (id3->albumart.type == AA_TYPE_JPG) + { + if (bufgetdata(embed_aa.hid, sizeof(struct bitmap), (void**)&bmp) < 0) + { + logf("does not read the bitmap header"); + return false; + } + + if (embed_aa.dim.width > 0 && embed_aa.dim.height > 0) + { + bmp->width = embed_aa.dim.width; + bmp->height = embed_aa.dim.height; + jpg_format |= FORMAT_RESIZE; + } + bmp->data = bmp_buffer; + + size = decode_jpeg_mem(tmp_buffer, id3->albumart.size, + bmp, + BMP_ALBUMART_SIZE, + jpg_format, + NULL); + if (size < 0) + { + logf("bitmap convert failure: %d", size); + return false; + } + } + else + { + /* TODO embed albumart does not support non-JPEG formats */ + logf("does not support image format: %d", id3->albumart.type); + return false; + } + embed_aa.usable = true; + return true; +} +#endif /* --- Audio thread --- */ @@ -1006,6 +1099,8 @@ static void audio_update_trackinfo(void) ci.id3 = thistrack_id3; ci.curpos = 0; ci.taginfo_ready = &CUR_TI->taginfo_ready; + + audio_regist_embed_albumart(ci.id3); } /* Clear tracks between write and read, non inclusive */ @@ -1292,34 +1387,37 @@ static void audio_finish_load_track(void) { int i; char aa_path[MAX_PATH]; - FOREACH_ALBUMART(i) + + if (track_ridx != track_widx || !audio_regist_embed_albumart(track_id3)) { - /* 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; - /* 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))) + FOREACH_ALBUMART(i) { - int aa_hid = bufopen(aa_path, 0, TYPE_BITMAP, - &(albumart_slots[i].dim)); - - if(aa_hid == ERR_BUFFER_FULL) - { - filling = STATE_FULL; - logf("buffer is full for now (get album art)"); - return; /* No space for track's album art, not an error */ - } - else if (aa_hid < 0) + /* 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; + /* 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))) { - /* another error, ignore AlbumArt */ - logf("Album art loading failed"); + int aa_hid = bufopen(aa_path, 0, TYPE_BITMAP, + &(albumart_slots[i].dim)); + + if(aa_hid == ERR_BUFFER_FULL) + { + filling = STATE_FULL; + logf("buffer is full for now (get album art)\n"); + return; /* No space for track's album art, not an error */ + } + else if (aa_hid < 0) + { + /* another error, ignore AlbumArt */ + logf("Album art loading failed"); + } + tracks[track_widx].aa_hid[i] = aa_hid; } - tracks[track_widx].aa_hid[i] = aa_hid; } } - } #endif @@ -1703,6 +1801,10 @@ static void audio_stop_playback(void) /* Close all tracks */ audio_release_tracks(); + +#ifdef HAVE_ALBUMART + embed_aa.usable = false; +#endif } static void audio_play_start(size_t offset) @@ -2025,6 +2127,10 @@ static void audio_thread(void) /* release tracks to make sure all handles are closed */ audio_release_tracks(); + +#ifdef HAVE_ALBUMART + embed_aa.usable = false; +#endif break; #endif @@ -2122,6 +2228,13 @@ void audio_init(void) tracks[j].aa_hid[i] = -1; } } + + /* + * because the data of bitmap does not store the buffer, + * then the type does not set TYPE_BITMAP. + */ + embed_aa.hid = bufalloc(NULL, sizeof(struct bitmap), TYPE_BUFFER); + embed_aa.usable = false; #endif add_event(BUFFER_EVENT_REBUFFER, false, buffering_handle_rebuffer_callback); diff --git a/apps/playback.h b/apps/playback.h index 27e27ff..e4914a6 100644 --- a/apps/playback.h +++ b/apps/playback.h @@ -83,6 +83,16 @@ bool audio_buffer_state_trashed(void); #define AUDIO_HAVE_RECORDING #endif +/* + * the size of the embed albumart stored and working area + * must be ALBUMART_SIZE < CODEC_SIZE - max of all codec files. + */ +#ifdef HAVE_ALBUMART +#define ALBUMART_SIZE 0x24000 +#else +#define ALBUMART_SIZE 0 +#endif + enum { Q_NULL = 0, Q_AUDIO_PLAY = 1, diff --git a/apps/recorder/jpeg_load.c b/apps/recorder/jpeg_load.c old mode 100644 new mode 100755 index 1af65fa..e569b44 --- a/apps/recorder/jpeg_load.c +++ b/apps/recorder/jpeg_load.c @@ -31,11 +31,18 @@ #include "debug.h" #include "jpeg_load.h" /*#define JPEG_BS_DEBUG*/ -//#define ROCKBOX_DEBUG_JPEG +/*#define ROCKBOX_DEBUG_JPEG*/ /* for portability of below JPEG code */ #define MEMSET(p,v,c) memset(p,v,c) #define MEMCPY(d,s,c) memcpy(d,s,c) + #define INLINE static inline +#ifdef JPEG_FROM_MEM +#define MEM_INLINE static inline +#else +#define MEM_INLINE static +#endif + #define ENDIAN_SWAP16(n) n /* only for poor little endian machines */ #ifdef ROCKBOX_DEBUG_JPEG #define JDEBUGF DEBUGF @@ -73,14 +80,11 @@ typedef uint8_t jpeg_pix_t; */ struct jpeg { -#ifdef JPEG_FROM_MEM unsigned char *data; unsigned long len; -#else int fd; int buf_left; int buf_index; -#endif unsigned long int bitbuf; int bitbuf_bits; int marker_ind; @@ -131,9 +135,8 @@ struct jpeg struct img_part part; }; -#ifdef JPEG_FROM_MEM static struct jpeg jpeg; -#endif +static bool from_mem = false; INLINE unsigned range_limit(int value) { @@ -855,74 +858,81 @@ static const struct idct_entry idct_tbl[] = { /* JPEG decoder implementation */ -#ifdef JPEG_FROM_MEM -INLINE unsigned char *jpeg_getc(struct jpeg* p_jpeg) +INLINE void fill_buf(struct jpeg* p_jpeg) { - if (LIKELY(p_jpeg->len)) - { - p_jpeg->len--; - return p_jpeg->data++; - } else - return NULL; + p_jpeg->buf_left = read(p_jpeg->fd, p_jpeg->buf, JPEG_READ_BUF_SIZE); + p_jpeg->buf_index = 0; } -INLINE bool skip_bytes(struct jpeg* p_jpeg, int count) +INLINE bool skip_bytes_seek(struct jpeg* p_jpeg) { - if (p_jpeg->len >= (unsigned)count) - { - p_jpeg->len -= count; - p_jpeg->data += count; - return true; - } else { - p_jpeg->data += p_jpeg->len; - p_jpeg->len = 0; + if (UNLIKELY(lseek(p_jpeg->fd, -p_jpeg->buf_left, SEEK_CUR) < 0)) return false; - } -} - -INLINE void jpeg_putc(struct jpeg* p_jpeg) -{ - p_jpeg->len++; - p_jpeg->data--; -} -#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_index = 0; + p_jpeg->buf_left = 0; + return true; } -static unsigned char *jpeg_getc(struct jpeg* p_jpeg) +MEM_INLINE unsigned char *jpeg_getc(struct jpeg* p_jpeg) { +#ifndef JPEG_FROM_MEM + if (from_mem) + { +#endif + if (LIKELY(p_jpeg->len)) + { + p_jpeg->len--; + return p_jpeg->data++; + } else + return NULL; +#ifndef JPEG_FROM_MEM + } if (UNLIKELY(p_jpeg->buf_left < 1)) fill_buf(p_jpeg); if (UNLIKELY(p_jpeg->buf_left < 1)) return NULL; p_jpeg->buf_left--; return (p_jpeg->buf_index++) + p_jpeg->buf; +#endif } -INLINE bool skip_bytes_seek(struct jpeg* p_jpeg) -{ - if (UNLIKELY(lseek(p_jpeg->fd, -p_jpeg->buf_left, SEEK_CUR) < 0)) - return false; - p_jpeg->buf_left = 0; - return true; -} - -static bool skip_bytes(struct jpeg* p_jpeg, int count) +MEM_INLINE bool skip_bytes(struct jpeg* p_jpeg, int count) { +#ifndef JPEG_FROM_MEM + if (from_mem) + { +#endif + if (p_jpeg->len >= (unsigned)count) + { + p_jpeg->len -= count; + p_jpeg->data += count; + return true; + } else { + p_jpeg->data += p_jpeg->len; + p_jpeg->len = 0; + return false; + } +#ifndef JPEG_FROM_MEM + } p_jpeg->buf_left -= count; p_jpeg->buf_index += count; return p_jpeg->buf_left >= 0 || skip_bytes_seek(p_jpeg); +#endif } -static void jpeg_putc(struct jpeg* p_jpeg) +MEM_INLINE void jpeg_putc(struct jpeg* p_jpeg) { +#ifndef JPEG_FROM_MEM + if (from_mem) + { +#endif + p_jpeg->len++; + p_jpeg->data--; +#ifndef JPEG_FROM_MEM + } p_jpeg->buf_left++; p_jpeg->buf_index--; -} #endif +} #define e_skip_bytes(jpeg, count) \ do {\ @@ -1996,7 +2006,6 @@ static int calc_scale(int in_size, int out_size) return scale; } -#ifdef JPEG_FROM_MEM int get_jpeg_dim_mem(unsigned char *data, unsigned long len, struct dim *size) { @@ -2014,42 +2023,46 @@ int get_jpeg_dim_mem(unsigned char *data, unsigned long len, return 0; } -int decode_jpeg_mem(unsigned char *data, unsigned long len, -#else -int read_jpeg_fd(int fd, -#endif - struct bitmap *bm, - int maxsize, - int format, - const struct custom_format *cformat) +static int decode_jpeg(int fd, unsigned char *data, unsigned long len, + struct bitmap *bm, + int maxsize, + int format, + const struct custom_format *cformat) { bool resize = false, dither = false; struct rowset rset; struct dim src_dim; int status; int bm_size; -#ifdef JPEG_FROM_MEM - struct jpeg *p_jpeg = &jpeg; -#else - struct jpeg *p_jpeg = (struct jpeg*)bm->data; + struct jpeg *p_jpeg; int tmp_size = maxsize; - ALIGN_BUFFER(p_jpeg, tmp_size, sizeof(int)); - /* not enough memory for our struct jpeg */ - if ((size_t)tmp_size < sizeof(struct jpeg)) - return -1; -#endif + + if (from_mem) + p_jpeg = &jpeg; + else + { + p_jpeg = (struct jpeg*)bm->data; + + ALIGN_BUFFER(p_jpeg, tmp_size, sizeof(int)); + /* not enough memory for our struct jpeg */ + if ((size_t)tmp_size < sizeof(struct jpeg)) + return -1; + } + memset(p_jpeg, 0, sizeof(struct jpeg)); -#ifdef JPEG_FROM_MEM + + p_jpeg->fd = fd; p_jpeg->data = data; - p_jpeg->len = len; -#else - p_jpeg->fd = fd; -#endif + p_jpeg->len = len; + status = process_markers(p_jpeg); -#ifndef JPEG_FROM_MEM - JDEBUGF("position in file: %d buffer fill: %d\n", - (int)lseek(p_jpeg->fd, 0, SEEK_CUR), p_jpeg->buf_left); -#endif + + if (!from_mem) + { + JDEBUGF("position in file: %d buffer fill: %d\n", + (int)lseek(p_jpeg->fd, 0, SEEK_CUR), p_jpeg->buf_left); + } + if (status < 0) return status; if ((status & (DQT | SOF0)) != (DQT | SOF0)) @@ -2127,15 +2140,18 @@ int read_jpeg_fd(int fd, char *buf_start = (char *)bm->data + bm_size; char *buf_end = (char *)bm->data + maxsize; maxsize = buf_end - buf_start; -#ifndef JPEG_FROM_MEM - ALIGN_BUFFER(buf_start, maxsize, sizeof(uint32_t)); - if (maxsize < (int)sizeof(struct jpeg)) - return -1; - memmove(buf_start, p_jpeg, sizeof(struct jpeg)); - p_jpeg = (struct jpeg *)buf_start; - buf_start += sizeof(struct jpeg); - maxsize = buf_end - buf_start; -#endif + + if (!from_mem) + { + ALIGN_BUFFER(buf_start, maxsize, sizeof(uint32_t)); + if (maxsize < (int)sizeof(struct jpeg)) + return -1; + memmove(buf_start, p_jpeg, sizeof(struct jpeg)); + p_jpeg = (struct jpeg *)buf_start; + buf_start += sizeof(struct jpeg); + maxsize = buf_end - buf_start; + } + fix_huff_tables(p_jpeg); #ifdef HAVE_LCD_COLOR int decode_buf_size = (p_jpeg->x_mbl << p_jpeg->h_scale[1]) @@ -2212,4 +2228,26 @@ int read_jpeg_fd(int fd, return 0; } +int decode_jpeg_mem(unsigned char *data, unsigned long len, + struct bitmap *bm, + int maxsize, + int format, + const struct custom_format *cformat) +{ + from_mem = true; + return decode_jpeg(0, data, len, bm, maxsize, format, cformat); +} + +#ifndef JPEG_FROM_MEM +int read_jpeg_fd(int fd, + struct bitmap *bm, + int maxsize, + int format, + const struct custom_format *cformat) +{ + from_mem = false; + return decode_jpeg(fd, 0, 0, bm, maxsize, format, cformat); +} +#endif + /**************** end JPEG code ********************/ diff --git a/apps/recorder/jpeg_load.h b/apps/recorder/jpeg_load.h index 73b6c51..471014b 100644 --- a/apps/recorder/jpeg_load.h +++ b/apps/recorder/jpeg_load.h @@ -32,6 +32,7 @@ #ifndef _JPEG_LOAD_H #define _JPEG_LOAD_H +#ifndef JPEG_FROM_MEM int read_jpeg_file(const char* filename, struct bitmap *bm, int maxsize, @@ -43,5 +44,15 @@ int read_jpeg_fd(int fd, int maxsize, int format, const struct custom_format *cformat); +#endif + +int decode_jpeg_mem(unsigned char *data, unsigned long len, + struct bitmap *bm, + int maxsize, + int format, + const struct custom_format *cformat); + +int get_jpeg_dim_mem(unsigned char *data, unsigned long len, + struct dim *size); #endif /* _JPEG_JPEG_DECODER_H */