summaryrefslogtreecommitdiff
path: root/apps/metadata.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/metadata.c')
-rw-r--r--apps/metadata.c723
1 files changed, 427 insertions, 296 deletions
diff --git a/apps/metadata.c b/apps/metadata.c
index 1b2dde3a24..ff0cb76cbe 100644
--- a/apps/metadata.c
+++ b/apps/metadata.c
@@ -22,6 +22,7 @@
#include <ctype.h>
#include <inttypes.h>
+#include "errno.h"
#include "metadata.h"
#include "mp3_playback.h"
#include "logf.h"
@@ -40,6 +41,37 @@ enum tagtype { TAGTYPE_APE = 1, TAGTYPE_VORBIS };
#define TAG_NAME_LENGTH 32
#define TAG_VALUE_LENGTH 128
+#define MP4_ID(a, b, c, d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
+
+#define MP4_3gp6 MP4_ID('3', 'g', 'p', '6')
+#define MP4_alac MP4_ID('a', 'l', 'a', 'c')
+#define MP4_calb MP4_ID(0xa9, 'a', 'l', 'b')
+#define MP4_cART MP4_ID(0xa9, 'A', 'R', 'T')
+#define MP4_cnam MP4_ID(0xa9, 'n', 'a', 'm')
+#define MP4_cwrt MP4_ID(0xa9, 'w', 'r', 't')
+#define MP4_ftyp MP4_ID('f', 't', 'y', 'p')
+#define MP4_gnre MP4_ID('g', 'n', 'r', 'e')
+#define MP4_hdlr MP4_ID('h', 'd', 'l', 'r')
+#define MP4_ilst MP4_ID('i', 'l', 's', 't')
+#define MP4_M4A MP4_ID('M', '4', 'A', ' ')
+#define MP4_mdat MP4_ID('m', 'd', 'a', 't')
+#define MP4_mdia MP4_ID('m', 'd', 'i', 'a')
+#define MP4_mdir MP4_ID('m', 'd', 'i', 'r')
+#define MP4_meta MP4_ID('m', 'e', 't', 'a')
+#define MP4_minf MP4_ID('m', 'i', 'n', 'f')
+#define MP4_moov MP4_ID('m', 'o', 'o', 'v')
+#define MP4_mp4a MP4_ID('m', 'p', '4', 'a')
+#define MP4_mp42 MP4_ID('m', 'p', '4', '2')
+#define MP4_qt MP4_ID('q', 't', ' ', ' ')
+#define MP4_soun MP4_ID('s', 'o', 'u', 'n')
+#define MP4_stbl MP4_ID('s', 't', 'b', 'l')
+#define MP4_stsd MP4_ID('s', 't', 's', 'd')
+#define MP4_stts MP4_ID('s', 't', 't', 's')
+#define MP4_trak MP4_ID('t', 'r', 'a', 'k')
+#define MP4_trkn MP4_ID('t', 'r', 'k', 'n')
+#define MP4_udta MP4_ID('u', 'd', 't', 'a')
+#define MP4_extra MP4_ID('-', '-', '-', '-')
+
struct apetag_header
{
char id[8];
@@ -190,28 +222,31 @@ static void convert_endian(void *data, const char *format)
}
}
-/* read_uint32be() - read an unsigned integer from a big-endian
- (e.g. Quicktime) file. This is used by the .m4a parser
-*/
+/* Read an unsigned 16-bit integer from a big-endian file. */
#ifdef ROCKBOX_BIG_ENDIAN
-#define read_uint32be(fd,buf) read((fd),(buf),4)
+#define read_uint16be(fd, buf) read((fd), (buf), 2)
#else
-int read_uint32be(int fd, unsigned int* buf) {
- char tmp;
- char* p=(char*)buf;
- size_t n;
+int read_uint16be(int fd, unsigned short* buf)
+{
+ size_t n;
+
+ n = read(fd, (char*) buf, 2);
+ *buf = betoh16(*buf);
+ return n;
+}
+#endif
- n=read(fd,p,4);
- if (n==4) {
- tmp=p[0];
- p[0]=p[3];
- p[3]=tmp;
- tmp=p[2];
- p[2]=p[1];
- p[1]=tmp;
- }
+/* Read an unsigned 32-bit integer from a big-endian file. */
+#ifdef ROCKBOX_BIG_ENDIAN
+#define read_uint32be(fd,buf) read((fd), (buf), 4)
+#else
+int read_uint32be(int fd, unsigned int* buf)
+{
+ size_t n;
- return(n);
+ n = read(fd, (char*) buf, 4);
+ *buf = betoh32(*buf);
+ return n;
}
#endif
@@ -921,292 +956,388 @@ static bool get_wave_metadata(int fd, struct mp3entry* id3)
return true;
}
-static bool get_m4a_metadata(int fd, struct mp3entry* id3)
+/* Read the tag data from an MP4 file, storing up to buffer_size bytes in
+ * buffer.
+ */
+unsigned long read_mp4_tag(int fd, unsigned int size_left, char* buffer,
+ unsigned int buffer_left)
{
- unsigned char* buf;
- unsigned long totalsamples;
- int i,j,k;
- size_t n;
- size_t bytes_remaining;
- char* id3buf;
- unsigned int compressedsize;
- unsigned int sample_count;
- unsigned int sample_duration;
- int numentries;
- int entry_size;
- int size_remaining;
- int chunk_len;
- unsigned char chunk_id[4];
- int sub_chunk_len;
- unsigned char sub_chunk_id[4];
-
- /* A simple parser to read vital metadata from an ALAC file.
- This parser also works for AAC files - they are both stored in
- a Quicktime M4A container. */
-
- /* Use the trackname part of the id3 structure as a temporary buffer */
- buf=id3->path;
-
- lseek(fd, 0, SEEK_SET);
-
- totalsamples=0;
- compressedsize=0;
- /* read the chunks - we stop when we find the mdat chunk and set compressedsize */
- while (compressedsize==0) {
- n=read_uint32be(fd,&chunk_len);
-
- // This means it was a 64-bit file, so we have problems.
- if (chunk_len == 1) {
- logf("need 64bit support\n");
- return false;
- }
-
- n=read(fd,&chunk_id,4);
- if (n < 4)
- return false;
-
- if (memcmp(&chunk_id,"ftyp",4)==0) {
- /* Check for M4A type */
- n=read(fd,&chunk_id,4);
- if ((memcmp(&chunk_id,"M4A ",4)!=0) &&
- (memcmp(&chunk_id,"mp42",4)!=0)) {
- logf("Not an M4A file, aborting\n");
- return false;
- }
- /* Skip rest of chunk */
- lseek(fd, chunk_len - 8 - 4, SEEK_CUR); /* FIXME not 8 */
- } else if (memcmp(&chunk_id,"moov",4)==0) {
- size_remaining=chunk_len - 8; /* FIXME not 8 */
-
- while (size_remaining > 0) {
- n=read_uint32be(fd,&sub_chunk_len);
- if ((sub_chunk_len < 1) || (sub_chunk_len > size_remaining)) {
- logf("Strange sub_chunk_len value inside moov: %d (remaining: %d)\n",sub_chunk_len,size_remaining);
- return false;
+ unsigned int bytes_read = 0;
+
+ if (buffer_left == 0)
+ {
+ lseek(fd, size_left, SEEK_CUR); /* Skip everything */
+ }
+ else
+ {
+ /* Skip the data tag header - maybe we should parse it properly? */
+ lseek(fd, 16, SEEK_CUR);
+ size_left -= 16;
+
+ if (size_left > buffer_left)
+ {
+ read(fd, buffer, buffer_left);
+ lseek(fd, size_left - buffer_left, SEEK_CUR);
+ bytes_read = buffer_left;
+ }
+ else
+ {
+ read(fd, buffer, size_left);
+ bytes_read = size_left;
+ }
+ }
+
+ return bytes_read;
+}
+
+/* Read a string tag from an MP4 file */
+unsigned int read_mp4_tag_string(int fd, int size_left, char** buffer,
+ unsigned int* buffer_left, char** dest)
+{
+ unsigned int bytes_read = read_mp4_tag(fd, size_left, *buffer,
+ *buffer_left - 1);
+ unsigned int length = 0;
+
+ if (bytes_read)
+ {
+ (*buffer)[bytes_read] = 0;
+ *dest = *buffer;
+ length = strlen(*buffer) + 1;
+ *buffer_left -= length;
+ *buffer += length;
+ }
+ else
+ {
+ *dest = NULL;
+ }
+
+ return length;
+}
+
+static unsigned int read_mp4_atom(int fd, unsigned int* size,
+ unsigned int* type, unsigned int size_left)
+{
+ read_uint32be(fd, size);
+ read_uint32be(fd, type);
+
+ if (*size == 1)
+ {
+ /* FAT32 doesn't support files this big, so something seems to
+ * be wrong. (64-bit sizes should only be used when required.)
+ */
+ errno = EFBIG;
+ *type = 0;
+ return 0;
+ }
+
+ if (*size > 0)
+ {
+ if (*size > size_left)
+ {
+ size_left = 0;
}
- n=read(fd,&sub_chunk_id,4);
- size_remaining-=8;
-
- if (memcmp(&sub_chunk_id,"mvhd",4)==0) {
- /* We don't need anything from here - skip */
- lseek(fd, sub_chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
- size_remaining-=(sub_chunk_len-8);
- } else if (memcmp(&sub_chunk_id,"udta",4)==0) {
- /* The udta chunk contains the metadata - track, artist, album etc.
- The format appears to be:
- udta
- meta
- hdlr
- ilst
- .nam
- [rest of tags]
- free
-
- NOTE: This code was written by examination of some .m4a files
- produced by iTunes v4.9 - it may not therefore be 100%
- compliant with all streams. But it should fail gracefully.
- */
- j=(sub_chunk_len-8);
- size_remaining-=j;
- n=read_uint32be(fd,&sub_chunk_len);
- n=read(fd,&sub_chunk_id,4);
- j-=8;
- if (memcmp(&sub_chunk_id,"meta",4)==0) {
- lseek(fd, 4, SEEK_CUR);
- j-=4;
- n=read_uint32be(fd,&sub_chunk_len);
- n=read(fd,&sub_chunk_id,4);
- j-=8;
- if (memcmp(&sub_chunk_id,"hdlr",4)==0) {
- lseek(fd, sub_chunk_len - 8, SEEK_CUR);
- j-=(sub_chunk_len - 8);
- n=read_uint32be(fd,&sub_chunk_len);
- n=read(fd,&sub_chunk_id,4);
- j-=8;
- if (memcmp(&sub_chunk_id,"ilst",4)==0) {
- /* Here are the actual tags. We use the id3v2 300-byte buffer
- to store the string data */
- bytes_remaining=sizeof(id3->id3v2buf);
- id3->genre=255; /* Not every track is the Blues */
- id3buf=id3->id3v2buf;
- k=sub_chunk_len-8;
- j-=k;
- while (k > 0) {
- n=read_uint32be(fd,&sub_chunk_len);
- n=read(fd,&sub_chunk_id,4);
- k-=8;
- if (memcmp(sub_chunk_id,"\251nam",4)==0) {
- read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->title);
- } else if (memcmp(sub_chunk_id,"\251ART",4)==0) {
- read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->artist);
- } else if (memcmp(sub_chunk_id,"\251alb",4)==0) {
- read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->album);
- } else if (memcmp(sub_chunk_id,"\251gen",4)==0) {
- read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->genre_string);
- } else if (memcmp(sub_chunk_id,"\251day",4)==0) {
- read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->year_string);
- } else if (memcmp(sub_chunk_id,"trkn",4)==0) {
- if (sub_chunk_len==0x20) {
- read(fd,buf,sub_chunk_len-8);
- id3->tracknum=buf[19];
- } else {
- lseek(fd, sub_chunk_len-8,SEEK_CUR);
+ else
+ {
+ size_left -= *size;
+ }
+
+ *size -= 8;
+ }
+ else
+ {
+ *size = size_left;
+ size_left = 0;
+ }
+
+ return size_left;
+}
+
+static bool read_mp4_tags(int fd, struct mp3entry* id3,
+ unsigned int size_left)
+{
+ unsigned int size;
+ unsigned int type;
+ unsigned int buffer_left = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf);
+ char* buffer = id3->id3v2buf;
+ bool cwrt = false;
+
+ do
+ {
+ size_left = read_mp4_atom(fd, &size, &type, size_left);
+
+ /* DEBUGF("Tag atom: '%c%c%c%c' (%d bytes left)\n", type >> 24 & 0xff,
+ type >> 16 & 0xff, type >> 8 & 0xff, type & 0xff, size); */
+
+ switch (type)
+ {
+ case MP4_cnam:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->title);
+ break;
+
+ case MP4_cART:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->artist);
+ break;
+
+ case MP4_calb:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->album);
+ break;
+
+ case MP4_cwrt:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->composer);
+ cwrt = false;
+ break;
+
+ case MP4_gnre:
+ {
+ unsigned short genre;
+
+ read_mp4_tag(fd, size, (char*) &genre, sizeof(genre));
+ id3->genre = betoh16(genre);
+ }
+ break;
+
+ case MP4_trkn:
+ {
+ unsigned short n[2];
+
+ read_mp4_tag(fd, size, (char*) &n, sizeof(n));
+ id3->tracknum = betoh16(n[1]);
+ }
+ break;
+
+ case MP4_extra:
+ {
+ char tag_name[TAG_NAME_LENGTH];
+ unsigned int sub_size;
+
+ /* "mean" atom */
+ read_uint32be(fd, &sub_size);
+ size -= sub_size;
+ lseek(fd, sub_size - 4, SEEK_CUR);
+ /* "name" atom */
+ read_uint32be(fd, &sub_size);
+ size -= sub_size;
+ lseek(fd, 8, SEEK_CUR);
+ sub_size -= 12;
+
+ if (sub_size > sizeof(tag_name) - 1)
+ {
+ read(fd, tag_name, sizeof(tag_name) - 1);
+ lseek(fd, sub_size - sizeof(tag_name) - 1, SEEK_CUR);
+ tag_name[sizeof(tag_name) - 1] = 0;
+ }
+ else
+ {
+ read(fd, tag_name, sub_size);
+ tag_name[sub_size] = 0;
+ }
+
+ if ((strcasecmp(tag_name, "composer") == 0) && !cwrt)
+ {
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->composer);
+ }
+ else
+ {
+ char* any;
+ unsigned int length = read_mp4_tag_string(fd, size,
+ &buffer, &buffer_left, &any);
+
+ if (length > 0)
+ {
+ /* Re-use the read buffer as the dest buffer... */
+ buffer -= length;
+ buffer_left += length;
+
+ parse_replaygain(tag_name, buffer, id3, buffer,
+ buffer_left);
}
- } else {
- lseek(fd, sub_chunk_len-8,SEEK_CUR);
- }
- k-=(sub_chunk_len-8);
}
- }
}
- }
- /* Skip any remaining data in udta chunk */
- lseek(fd, j, SEEK_CUR);
- } else if (memcmp(&sub_chunk_id,"trak",4)==0) {
- /* Format of trak chunk:
- tkhd
- mdia
- mdhd
- hdlr
- minf
- smhd
- dinf
- stbl
- stsd - Samplerate, Samplesize, Numchannels
- stts - time_to_sample array - RLE'd table containing duration of each block
- stsz - sample_byte_size array - ?Size in bytes of each compressed block
- stsc - Seek table related?
- stco - Seek table related?
- */
-
- /* Skip tkhd - not needed */
- n=read_uint32be(fd,&sub_chunk_len);
- n=read(fd,&sub_chunk_id,4);
- if (memcmp(&sub_chunk_id,"tkhd",4)!=0) {
- logf("Expecting tkhd\n");
- return false;
- }
- lseek(fd, sub_chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
- size_remaining-=sub_chunk_len;
-
- /* Process mdia - skipping possible edts */
- n=read_uint32be(fd,&sub_chunk_len);
- n=read(fd,&sub_chunk_id,4);
- if (memcmp(&sub_chunk_id,"edts",4)==0) {
- lseek(fd, sub_chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
- size_remaining-=sub_chunk_len;
- n=read_uint32be(fd,&sub_chunk_len);
- n=read(fd,&sub_chunk_id,4);
- }
-
- if (memcmp(&sub_chunk_id,"mdia",4)!=0) {
- logf("Expecting mdia\n");
- return false;
- }
- size_remaining-=sub_chunk_len;
- j=sub_chunk_len-8;
-
- while (j > 0) {
- n=read_uint32be(fd,&sub_chunk_len);
- n=read(fd,&sub_chunk_id,4);
- j-=4;
- if (memcmp(&sub_chunk_id,"minf",4)==0) {
- j=sub_chunk_len-8;
- } else if (memcmp(&sub_chunk_id,"stbl",4)==0) {
- j=sub_chunk_len-8;
- } else if (memcmp(&sub_chunk_id,"stsd",4)==0) {
- n=read(fd,buf,sub_chunk_len-8);
- j-=sub_chunk_len;
- i=0;
- /* Skip version and flags */
- i+=4;
-
- numentries=(buf[i]<<24)|(buf[i+1]<<16)|(buf[i+2]<<8)|buf[i+3];
- i+=4;
- if (numentries!=1) {
- logf("ERROR: Expecting only one entry in stsd\n");
- }
-
- entry_size=(buf[i]<<24)|(buf[i+1]<<16)|(buf[i+2]<<8)|buf[i+3];
- i+=4;
-
- if (memcmp(&buf[i],"alac",4)==0) {
- id3->codectype=AFMT_ALAC;
- } else if (memcmp(&buf[i],"mp4a",4)==0) {
- id3->codectype=AFMT_AAC;
- } else {
- logf("Not an ALAC or AAC file\n");
- return false;
- }
-
- //numchannels=(buf[i+20]<<8)|buf[i+21]; /* Not used - assume Stereo */
- //samplesize=(buf[i+22]<<8)|buf[i+23]; /* Not used - assume 16-bit */
-
- /* Samplerate is 32-bit fixed point, but this works for < 65536 Hz */
- id3->frequency=(buf[i+28]<<8)|buf[i+29];
- } else if (memcmp(&sub_chunk_id,"stts",4)==0) {
- j-=sub_chunk_len;
- i=8;
- n=read(fd,buf,8);
- i+=8;
- numentries=(buf[4]<<24)|(buf[5]<<16)|(buf[6]<<8)|buf[7];
- for (k=0;k<numentries;k++) {
- n=read_uint32be(fd,&sample_count);
- n=read_uint32be(fd,&sample_duration);
- totalsamples+=sample_count*sample_duration;
- i+=8;
- }
- if (i > 0) lseek(fd, sub_chunk_len - i, SEEK_CUR);
- } else if (memcmp(&sub_chunk_id,"stsz",4)==0) {
- j-=sub_chunk_len;
- i=8;
- n=read(fd,buf,8);
- i+=8;
- numentries=(buf[4]<<24)|(buf[5]<<16)|(buf[6]<<8)|buf[7];
- for (k=0;k<numentries;k++) {
- n=read_uint32be(fd,&sample_count);
- n=read_uint32be(fd,&sample_duration);
- totalsamples+=sample_count*sample_duration;
- i+=8;
- }
- if (i > 0) lseek(fd, sub_chunk_len - i, SEEK_CUR);
- } else {
- lseek(fd, sub_chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
- j-=sub_chunk_len;
- }
- }
- } else {
- logf("Unexpected sub_chunk_id inside moov: %c%c%c%c\n",
- sub_chunk_id[0],sub_chunk_id[1],sub_chunk_id[2],sub_chunk_id[3]);
- return false;
+ break;
+
+ default:
+ lseek(fd, size, SEEK_CUR);
+ break;
}
- }
- } else if (memcmp(&chunk_id,"mdat",4)==0) {
- /* once we hit mdat we stop reading and return.
- * this is on the assumption that there is no furhter interesting
- * stuff in the stream. if there is stuff will fail (:()).
- * But we need the read pointer to be at the mdat stuff
- * for the decoder. And we don't want to rely on fseek/ftell,
- * as they may not always be avilable */
- lseek(fd, chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
- compressedsize=chunk_len-8;
- } else if (memcmp(&chunk_id,"free",4)==0) {
- /* these following atoms can be skipped !!!! */
- lseek(fd, chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
- } else {
- logf("(top) unknown chunk id: %c%c%c%c\n", chunk_id[0],chunk_id[1],chunk_id[2],chunk_id[3]);
- return false;
}
- }
+ while ((size_left > 0) && (errno == 0));
- id3->vbr=true; /* All ALAC files are VBR */
- id3->filesize=filesize(fd);
- id3->samples=totalsamples;
- id3->length=(10*totalsamples)/(id3->frequency/100);
- id3->bitrate=(compressedsize*8)/id3->length;;
+ return true;
+}
- return true;
-}
+static bool read_mp4_container(int fd, struct mp3entry* id3,
+ unsigned int size_left)
+{
+ unsigned int size;
+ unsigned int type;
+ unsigned int handler = 0;
+ bool rc = true;
+
+ do
+ {
+ size_left = read_mp4_atom(fd, &size, &type, size_left);
+
+ /* DEBUGF("Atom: '%c%c%c%c' (0x%08x, %d bytes left)\n",
+ (type >> 24) & 0xff, (type >> 16) & 0xff, (type >> 8) & 0xff,
+ type & 0xff, type, size); */
+
+ switch (type)
+ {
+ case MP4_ftyp:
+ {
+ unsigned int id;
+
+ read_uint32be(fd, &id);
+ size -= 4;
+
+ if ((id != MP4_M4A) && (id != MP4_mp42) && (id != MP4_qt)
+ && (id != MP4_3gp6))
+ {
+ DEBUGF("Unknown MP4 file type: '%c%c%c%c'\n",
+ id >> 24 & 0xff, id >> 16 & 0xff, id >> 8 & 0xff,
+ id & 0xff);
+ return false;
+ }
+ }
+ break;
+
+ case MP4_meta:
+ lseek(fd, 4, SEEK_CUR); /* Skip version */
+ size -= 4;
+ /* Fall through */
+
+ case MP4_moov:
+ case MP4_udta:
+ case MP4_mdia:
+ case MP4_stbl:
+ case MP4_trak:
+ rc = read_mp4_container(fd, id3, size);
+ size = 0;
+ break;
+
+ case MP4_ilst:
+ if (handler == MP4_mdir)
+ {
+ rc = read_mp4_tags(fd, id3, size);
+ size = 0;
+ }
+ break;
+
+ case MP4_minf:
+ if (handler == MP4_soun)
+ {
+ rc = read_mp4_container(fd, id3, size);
+ size = 0;
+ }
+ break;
+
+ case MP4_stsd:
+ lseek(fd, 8, SEEK_CUR);
+ size -= 8;
+ rc = read_mp4_container(fd, id3, size);
+ size = 0;
+ break;
+
+ case MP4_hdlr:
+ lseek(fd, 8, SEEK_CUR);
+ read_uint32be(fd, &handler);
+ size -= 12;
+ /* DEBUGF(" Handler '%c%c%c%c'\n", handler >> 24 & 0xff,
+ handler >> 16 & 0xff, handler >> 8 & 0xff,handler & 0xff); */
+ break;
+
+ case MP4_stts:
+ {
+ unsigned int entries;
+ unsigned int i;
+
+ lseek(fd, 4, SEEK_CUR);
+ read_uint32be(fd, &entries);
+ id3->samples = 0;
+
+ for (i = 0; i < entries; i++)
+ {
+ unsigned int n;
+ unsigned int l;
+
+ read_uint32be(fd, &n);
+ read_uint32be(fd, &l);
+ id3->samples += n * l;
+ }
+
+ size = 0;
+ }
+ break;
+
+ case MP4_mp4a:
+ case MP4_alac:
+ {
+ unsigned int frequency;
+
+ id3->codectype = (type == MP4_mp4a) ? AFMT_AAC : AFMT_ALAC;
+ lseek(fd, 22, SEEK_CUR);
+ read_uint32be(fd, &frequency);
+ size -= 26;
+ id3->frequency = frequency;
+ /* There're some child atoms here, but we don't need them */
+ }
+ break;
+
+ case MP4_mdat:
+ id3->filesize = size;
+ break;
+
+ default:
+ break;
+ }
+
+ lseek(fd, size, SEEK_CUR);
+ }
+ while (rc && (size_left > 0) && (errno == 0) && (id3->filesize == 0));
+ /* Break on non-zero filesize, since Rockbox currently doesn't support
+ * metadata after the mdat atom (which sets the filesize field).
+ */
+
+ return rc;
+}
+
+static bool get_mp4_metadata(int fd, struct mp3entry* id3)
+{
+ id3->codectype = AFMT_UNKNOWN;
+ id3->genre = 255;
+ id3->filesize = 0;
+ errno = 0;
+
+ if (read_mp4_container(fd, id3, filesize(fd)) && (errno == 0)
+ && (id3->samples > 0) && (id3->frequency > 0)
+ && (id3->filesize > 0))
+ {
+ if (id3->codectype == AFMT_UNKNOWN)
+ {
+ logf("Not an ALAC or AAC file");
+ return false;
+ }
+
+ id3->length = (id3->samples / id3->frequency) * 1000;
+ id3->bitrate = ((int64_t) id3->filesize * 8) / id3->length;
+ DEBUGF("MP4 bitrate %d, frequency %d Hz, length %d ms\n",
+ id3->bitrate, id3->frequency, id3->length);
+ }
+ else
+ {
+ logf("MP4 metadata error");
+ DEBUGF("MP4 metadata error. errno %d, length %d, frequency %d, filesize %d\n",
+ errno, id3->length, id3->frequency, id3->filesize);
+ return false;
+ }
+
+ return true;
+}
static bool get_musepack_metadata(int fd, struct mp3entry *id3)
{
@@ -1718,7 +1849,7 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
case AFMT_ALAC:
case AFMT_AAC:
- if (!get_m4a_metadata(fd, &(track->id3)))
+ if (!get_mp4_metadata(fd, &(track->id3)))
{
return false;
}