mirror of
https://github.com/Rockbox/rockbox.git
synced 2026-10-09 23:53:28 -04:00
Most of the churn here occurs because 'filesize' is one of the redefined filesystem functions, but some of the structs used by the native FS code also include a 'filesize' member variable which gets renamed by the macro in some but not all source files. It's easier to rename 'filesize()' to 'ffilesize()' rather than try to clean up the macro mess or renaming the struct members. There is weirdness with root_realpath() which now breaks on native builds because it was assumed to be unprefixed there. dir_get_info() was unprefixed everywhere but this just seems inconsistent; make it follow the FS_PREFIX() convention too. Change-Id: Ic3700c6234ea45f32679c1a8429d70fdb8f4088a
860 lines
26 KiB
C
860 lines
26 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
*
|
|
* Copyright (C) 2005 Magnus Holmgren
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <inttypes.h>
|
|
#include "platform.h"
|
|
|
|
#include "errno.h"
|
|
#include "metadata.h"
|
|
#include "metadata_common.h"
|
|
#include "metadata_parsers.h"
|
|
#include "logf.h"
|
|
#include "debug.h"
|
|
#include "replaygain.h"
|
|
|
|
#ifdef DEBUGF
|
|
#undef DEBUGF
|
|
#define DEBUGF(...)
|
|
#endif
|
|
|
|
#define MP4_3gp6 FOURCC('3', 'g', 'p', '6')
|
|
#define MP4_aART FOURCC('a', 'A', 'R', 'T')
|
|
#define MP4_alac FOURCC('a', 'l', 'a', 'c')
|
|
#define MP4_calb FOURCC(0xa9, 'a', 'l', 'b')
|
|
#define MP4_cART FOURCC(0xa9, 'A', 'R', 'T')
|
|
#define MP4_cgrp FOURCC(0xa9, 'g', 'r', 'p')
|
|
#define MP4_cgen FOURCC(0xa9, 'g', 'e', 'n')
|
|
#define MP4_chpl FOURCC('c', 'h', 'p', 'l')
|
|
#define MP4_cnam FOURCC(0xa9, 'n', 'a', 'm')
|
|
#define MP4_cwrt FOURCC(0xa9, 'w', 'r', 't')
|
|
#define MP4_ccmt FOURCC(0xa9, 'c', 'm', 't')
|
|
#define MP4_cday FOURCC(0xa9, 'd', 'a', 'y')
|
|
#define MP4_covr FOURCC('c', 'o', 'v', 'r')
|
|
#define MP4_disk FOURCC('d', 'i', 's', 'k')
|
|
#define MP4_esds FOURCC('e', 's', 'd', 's')
|
|
#define MP4_ftyp FOURCC('f', 't', 'y', 'p')
|
|
#define MP4_gnre FOURCC('g', 'n', 'r', 'e')
|
|
#define MP4_hdlr FOURCC('h', 'd', 'l', 'r')
|
|
#define MP4_ilst FOURCC('i', 'l', 's', 't')
|
|
#define MP4_mdat FOURCC('m', 'd', 'a', 't')
|
|
#define MP4_mdia FOURCC('m', 'd', 'i', 'a')
|
|
#define MP4_mdir FOURCC('m', 'd', 'i', 'r')
|
|
#define MP4_meta FOURCC('m', 'e', 't', 'a')
|
|
#define MP4_minf FOURCC('m', 'i', 'n', 'f')
|
|
#define MP4_moov FOURCC('m', 'o', 'o', 'v')
|
|
#define MP4_mp4a FOURCC('m', 'p', '4', 'a')
|
|
#define MP4_soun FOURCC('s', 'o', 'u', 'n')
|
|
#define MP4_stbl FOURCC('s', 't', 'b', 'l')
|
|
#define MP4_stsd FOURCC('s', 't', 's', 'd')
|
|
#define MP4_stts FOURCC('s', 't', 't', 's')
|
|
#define MP4_trak FOURCC('t', 'r', 'a', 'k')
|
|
#define MP4_trkn FOURCC('t', 'r', 'k', 'n')
|
|
#define MP4_udta FOURCC('u', 'd', 't', 'a')
|
|
#define MP4_extra FOURCC('-', '-', '-', '-')
|
|
|
|
/* Read the tag data from an MP4 file, storing up to buffer_size bytes in
|
|
* buffer.
|
|
*/
|
|
static unsigned long read_mp4_tag(int fd, unsigned int size_left, char* buffer,
|
|
unsigned int buffer_left)
|
|
{
|
|
unsigned long bytes_read = 0;
|
|
ssize_t rd_ret = 0;
|
|
ssize_t bytes_req;
|
|
#define MP4_TAG_HEADER_SIZE 16
|
|
|
|
|
|
if (size_left >= MP4_TAG_HEADER_SIZE)
|
|
{
|
|
/* Skip the data tag header - maybe we should parse it properly? */
|
|
lseek(fd, MP4_TAG_HEADER_SIZE, SEEK_CUR);
|
|
size_left -= MP4_TAG_HEADER_SIZE;
|
|
|
|
if (size_left > buffer_left)
|
|
bytes_req = buffer_left;
|
|
else
|
|
bytes_req = size_left;
|
|
|
|
rd_ret = read(fd, buffer, bytes_req);
|
|
if (rd_ret == bytes_req)
|
|
bytes_read = bytes_req;
|
|
else
|
|
{
|
|
/* read less than expected or an error from read() */
|
|
logf("Error %d, read_mp4_tag", rd_ret);
|
|
if (rd_ret < 0)
|
|
rd_ret = 0; /* Skip everything */
|
|
}
|
|
}
|
|
if (size_left > (unsigned int) rd_ret)
|
|
lseek(fd, size_left - rd_ret, SEEK_CUR);
|
|
|
|
|
|
return bytes_read;
|
|
}
|
|
|
|
/* Read a string tag from an MP4 file */
|
|
static 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 > 0 ? *buffer_left - 1 : 0);
|
|
unsigned int length = 0;
|
|
|
|
if (bytes_read)
|
|
{
|
|
/* Do not overwrite already available metadata. Especially when reading
|
|
* tags with e.g. multiple genres / artists. This way only the first
|
|
* of multiple entries is used, all following are dropped. */
|
|
if (*dest == NULL)
|
|
{
|
|
(*buffer)[bytes_read] = 0; /* zero-terminate for correct strlen().*/
|
|
length = strlen(*buffer) + 1;
|
|
length = MIN(length, ID3V2_MAX_ITEM_SIZE); /* Limit item size. */
|
|
|
|
*dest = *buffer;
|
|
(*buffer)[length-1] = 0; /* zero-terminate buffer. */
|
|
*buffer_left -= length;
|
|
*buffer += length;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*dest = NULL;
|
|
}
|
|
|
|
return length;
|
|
}
|
|
|
|
static unsigned int read_mp4_atom(int fd, uint32_t* size,
|
|
uint32_t* type, uint32_t 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;
|
|
}
|
|
else
|
|
{
|
|
size_left -= *size;
|
|
}
|
|
|
|
*size -= 8;
|
|
}
|
|
else
|
|
{
|
|
*size = size_left;
|
|
size_left = 0;
|
|
}
|
|
|
|
return size_left;
|
|
}
|
|
|
|
static unsigned int read_mp4_length(int fd, uint32_t* size)
|
|
{
|
|
unsigned int length = 0;
|
|
int bytes = 0;
|
|
unsigned char c = '\0';
|
|
|
|
do
|
|
{
|
|
read(fd, &c, 1);
|
|
bytes++;
|
|
(*size)--;
|
|
length = (length << 7) | (c & 0x7F);
|
|
}
|
|
while ((c & 0x80) && (bytes < 4) && (*size > 0));
|
|
|
|
return length;
|
|
}
|
|
|
|
static bool read_mp4_esds(int fd, struct mp3entry* id3, uint32_t* size)
|
|
{
|
|
unsigned char buf[8] = {0};
|
|
bool sbr = false;
|
|
bool sbr_signaled = false;
|
|
|
|
lseek(fd, 4, SEEK_CUR); /* Version and flags. */
|
|
read(fd, buf, 1); /* Verify ES_DescrTag. */
|
|
*size -= 5;
|
|
|
|
if (*buf == 3)
|
|
{
|
|
/* read length */
|
|
if (read_mp4_length(fd, size) < 20)
|
|
{
|
|
return sbr;
|
|
}
|
|
|
|
lseek(fd, 3, SEEK_CUR);
|
|
*size -= 3;
|
|
}
|
|
else
|
|
{
|
|
lseek(fd, 2, SEEK_CUR);
|
|
*size -= 2;
|
|
}
|
|
|
|
read(fd, buf, 1); /* Verify DecoderConfigDescrTab. */
|
|
*size -= 1;
|
|
|
|
if (*buf != 4)
|
|
{
|
|
return sbr;
|
|
}
|
|
|
|
if (read_mp4_length(fd, size) < 13)
|
|
{
|
|
return sbr;
|
|
}
|
|
|
|
lseek(fd, 13, SEEK_CUR); /* Skip audio type, bit rates, etc. */
|
|
read(fd, buf, 1);
|
|
*size -= 14;
|
|
|
|
if (*buf != 5) /* Verify DecSpecificInfoTag. */
|
|
{
|
|
return sbr;
|
|
}
|
|
|
|
{
|
|
static const int sample_rates[] =
|
|
{
|
|
96000, 88200, 64000, 48000, 44100, 32000,
|
|
24000, 22050, 16000, 12000, 11025, 8000
|
|
};
|
|
unsigned long bits;
|
|
unsigned int length;
|
|
unsigned int index;
|
|
unsigned int type;
|
|
|
|
/* Read the (leading part of the) decoder config. */
|
|
length = read_mp4_length(fd, size);
|
|
length = MIN(length, *size);
|
|
length = MIN(length, sizeof(buf));
|
|
memset(buf, 0, sizeof(buf));
|
|
read(fd, buf, length);
|
|
*size -= length;
|
|
|
|
/* Maybe time to write a simple read_bits function... */
|
|
|
|
/* Decoder config format:
|
|
* Object type - 5 bits
|
|
* Frequency index - 4 bits
|
|
* Channel configuration - 4 bits
|
|
* Also see libfaad/mp4.c AudioSpecificConfig2 (consider using it instead of manual parsing)
|
|
*/
|
|
bits = get_long_be(buf);
|
|
type = bits >> 27; /* Object type - 5 bits */
|
|
index = (bits >> 23) & 0xf; /* Frequency index - 4 bits */
|
|
|
|
if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
|
|
{
|
|
id3->frequency = sample_rates[index];
|
|
}
|
|
|
|
if (type == 5)
|
|
{
|
|
unsigned int old_index = index;
|
|
|
|
sbr = true;
|
|
index = (bits >> 15) & 0xf; /* Frequency index - 4 bits */
|
|
|
|
if (index == 15)
|
|
{
|
|
/* 17 bits read so far... */
|
|
bits = get_long_be(&buf[2]);
|
|
id3->frequency = (bits >> 7) & 0x00ffffff;
|
|
}
|
|
else if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
|
|
{
|
|
id3->frequency = sample_rates[index];
|
|
}
|
|
|
|
if (old_index == index)
|
|
{
|
|
/* Downsampled SBR */
|
|
id3->frequency *= 2;
|
|
}
|
|
}
|
|
/* Skip 13 bits from above, plus 3 bits, then read 11 bits */
|
|
else if ((length >= 4) && (((bits >> 5) & 0x7ff) == 0x2b7))
|
|
{
|
|
/* We found an extensionAudioObjectType */
|
|
type = bits & 0x1f; /* Object type - 5 bits*/
|
|
bits = get_long_be(&buf[4]);
|
|
|
|
if (type == 5)
|
|
{
|
|
sbr = bits >> 31;
|
|
sbr_signaled = true;
|
|
|
|
if (sbr)
|
|
{
|
|
unsigned int old_index = index;
|
|
|
|
/* 1 bit read so far */
|
|
index = (bits >> 27) & 0xf; /* Frequency index - 4 bits */
|
|
|
|
if (index == 15)
|
|
{
|
|
/* 5 bits read so far */
|
|
id3->frequency = (bits >> 3) & 0x00ffffff;
|
|
}
|
|
else if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
|
|
{
|
|
id3->frequency = sample_rates[index];
|
|
}
|
|
|
|
if (old_index == index)
|
|
{
|
|
/* Downsampled SBR */
|
|
id3->frequency *= 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#ifndef CODEC_AAC_SBR_DEC
|
|
//SBR_DEC is disabled so disable sbr implicit signalling
|
|
sbr_signaled = true;
|
|
#endif
|
|
if (!sbr && !sbr_signaled && id3->frequency <= 24000)
|
|
{
|
|
/* As stated in libfaad/mp4.c AudioSpecificConfig2:
|
|
* no SBR signalled, this could mean either implicit signalling or no SBR in this file
|
|
* MPEG specification states: assume SBR on files with samplerate <= 24000 Hz
|
|
*/
|
|
id3->frequency *= 2;
|
|
sbr = true;
|
|
}
|
|
}
|
|
|
|
return sbr;
|
|
}
|
|
|
|
static void read_mp4_tag_i_from_n(int fd, int *i, char** i_from_n_string, uint32_t size, unsigned int *buffer_left, char **buffer)
|
|
{
|
|
uint16_t x[3];
|
|
*i = 0;
|
|
*i_from_n_string = NULL;
|
|
if (read_mp4_tag(fd, size, (char*) &x, sizeof(x)) == sizeof(x))
|
|
{
|
|
*i = betoh16(x[1]);
|
|
int n = betoh16(x[2]);
|
|
if (n > 0)
|
|
{
|
|
unsigned int string_length = snprintf(*buffer, *buffer_left, "%d/%d", *i, n) + 1;
|
|
if (string_length <= *buffer_left)
|
|
{
|
|
*i_from_n_string = *buffer;
|
|
*buffer += string_length;
|
|
*buffer_left -= string_length;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool read_mp4_tags(int fd, struct mp3entry* id3,
|
|
uint32_t size_left)
|
|
{
|
|
uint32_t size;
|
|
uint32_t 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_aART:
|
|
read_mp4_tag_string(fd, size, &buffer, &buffer_left,
|
|
&id3->albumartist);
|
|
break;
|
|
|
|
case MP4_cgrp:
|
|
read_mp4_tag_string(fd, size, &buffer, &buffer_left,
|
|
&id3->grouping);
|
|
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_ccmt:
|
|
read_mp4_tag_string(fd, size, &buffer, &buffer_left,
|
|
&id3->comment);
|
|
break;
|
|
|
|
case MP4_cday:
|
|
read_mp4_tag_string(fd, size, &buffer, &buffer_left,
|
|
&id3->year_string);
|
|
|
|
/* Try to parse it as a year, for the benefit of the database.
|
|
*/
|
|
if(id3->year_string)
|
|
{
|
|
id3->year = atoi(id3->year_string);
|
|
if (id3->year < 1900)
|
|
{
|
|
id3->year = 0;
|
|
}
|
|
}
|
|
else
|
|
id3->year = 0;
|
|
|
|
break;
|
|
|
|
case MP4_gnre:
|
|
{
|
|
unsigned short genre;
|
|
const unsigned int g_size = sizeof(genre);
|
|
id3->genre_string = NULL;
|
|
if (read_mp4_tag(fd, size, (char*) &genre, g_size) == g_size)
|
|
id3->genre_string = id3_get_num_genre(betoh16(genre) - 1);
|
|
}
|
|
break;
|
|
|
|
case MP4_cgen:
|
|
read_mp4_tag_string(fd, size, &buffer, &buffer_left,
|
|
&id3->genre_string);
|
|
break;
|
|
|
|
case MP4_disk:
|
|
read_mp4_tag_i_from_n(fd, &id3->discnum, &id3->disc_string, size, &buffer_left, &buffer);
|
|
break;
|
|
|
|
case MP4_trkn: {
|
|
read_mp4_tag_i_from_n(fd, &id3->tracknum, &id3->track_string, size, &buffer_left, &buffer);
|
|
break;
|
|
}
|
|
#ifdef HAVE_ALBUMART
|
|
case MP4_covr:
|
|
{
|
|
int pos = lseek(fd, 0, SEEK_CUR) + 16;
|
|
id3->albumart.type = AA_TYPE_UNKNOWN;
|
|
|
|
if (read_mp4_tag(fd, size, buffer, 8) >= 4)
|
|
{
|
|
|
|
if (memcmp(buffer, "\xff\xd8\xff\xe0", 4) == 0)
|
|
{
|
|
id3->albumart.type = AA_TYPE_JPG;
|
|
}
|
|
else if (memcmp(buffer, "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", 8) == 0)
|
|
{
|
|
id3->albumart.type = AA_TYPE_PNG;
|
|
}
|
|
|
|
if (id3->albumart.type != AA_TYPE_UNKNOWN)
|
|
{
|
|
id3->albumart.pos = pos;
|
|
id3->albumart.size = size - 16;
|
|
id3->has_embedded_albumart = true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case MP4_extra:
|
|
{
|
|
char tag_name[TAG_NAME_LENGTH];
|
|
uint32_t sub_size;
|
|
ssize_t rd_ret;
|
|
/* "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)
|
|
{
|
|
rd_ret = read(fd, tag_name, sizeof(tag_name) - 1);
|
|
lseek(fd, sub_size - (sizeof(tag_name) - 1), SEEK_CUR);
|
|
sub_size = sizeof(tag_name) - 1;
|
|
}
|
|
else
|
|
{
|
|
rd_ret = read(fd, tag_name, sub_size);
|
|
}
|
|
if (rd_ret != (ssize_t)sub_size)
|
|
rd_ret = 0;
|
|
tag_name[rd_ret] = 0;
|
|
|
|
static const char *tn_options[] = {"composer", "iTunSMPB",
|
|
"musicbrainz track id", "album artist", NULL};
|
|
|
|
int tn_op = string_option(tag_name, tn_options, true);
|
|
|
|
|
|
if (tn_op == 0 && !cwrt) /*composer*/
|
|
{
|
|
read_mp4_tag_string(fd, size, &buffer, &buffer_left,
|
|
&id3->composer);
|
|
}
|
|
else if (tn_op == 1) /*iTunSMPB*/
|
|
{
|
|
char value[TAG_VALUE_LENGTH];
|
|
char* value_p = value;
|
|
char* any = NULL;
|
|
unsigned int length = sizeof(value);
|
|
|
|
read_mp4_tag_string(fd, size, &value_p, &length, &any);
|
|
id3->lead_trim = get_itunes_int32(value, 1);
|
|
id3->tail_trim = get_itunes_int32(value, 2);
|
|
DEBUGF("AAC: lead_trim %d, tail_trim %d\n",
|
|
id3->lead_trim, id3->tail_trim);
|
|
}
|
|
else if (tn_op == 2) /*musicbrainz track id*/
|
|
{
|
|
read_mp4_tag_string(fd, size, &buffer, &buffer_left,
|
|
&id3->mb_track_id);
|
|
}
|
|
else if (tn_op == 3) /*album artist*/
|
|
{
|
|
read_mp4_tag_string(fd, size, &buffer, &buffer_left,
|
|
&id3->albumartist);
|
|
}
|
|
else
|
|
{
|
|
char* any = NULL;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
lseek(fd, size, SEEK_CUR);
|
|
break;
|
|
}
|
|
}
|
|
while ((size_left > 0) && (errno == 0));
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool read_mp4_container(int fd, struct mp3entry* id3,
|
|
uint32_t size_left)
|
|
{
|
|
uint32_t size = 0;
|
|
uint32_t type = 0;
|
|
uint32_t handler = 0;
|
|
bool rc = true;
|
|
bool done = false;
|
|
|
|
do
|
|
{
|
|
size_left = read_mp4_atom(fd, &size, &type, size_left);
|
|
|
|
/* DEBUGF("Atom: '%c%c%c%c' (0x%08lx, %lu bytes left)\n",
|
|
(int) ((type >> 24) & 0xff), (int) ((type >> 16) & 0xff),
|
|
(int) ((type >> 8) & 0xff), (int) (type & 0xff),
|
|
type, size); */
|
|
|
|
switch (type)
|
|
{
|
|
case MP4_ftyp:
|
|
{
|
|
// filetype (supported ignore case values: m4a, m4b, mp42, 3gp6, qt, isom)
|
|
char filetype[4];
|
|
read(fd, &filetype, 4);
|
|
DEBUGF("MP4 file type: '%.4s'\n", filetype);
|
|
size -= 4;
|
|
}
|
|
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:
|
|
/* We need at least a size of 8 to read the next atom. */
|
|
if (handler == MP4_mdir && size>8)
|
|
{
|
|
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:
|
|
{
|
|
uint32_t entries;
|
|
unsigned int i;
|
|
|
|
/* Reset to false. */
|
|
id3->needs_upsampling_correction = false;
|
|
|
|
lseek(fd, 4, SEEK_CUR);
|
|
read_uint32be(fd, &entries);
|
|
id3->samples = 0;
|
|
|
|
for (i = 0; i < entries; i++)
|
|
{
|
|
uint32_t n;
|
|
uint32_t l;
|
|
|
|
read_uint32be(fd, &n);
|
|
read_uint32be(fd, &l);
|
|
|
|
/* Some AAC file use HE profile. In this case the number
|
|
* of output samples is doubled to a maximum of 2048
|
|
* samples per frame. This means that files which already
|
|
* report a frame size of 2048 in their header will not
|
|
* need any further special handling. */
|
|
if (id3->codectype==AFMT_MP4_AAC_HE && l<=1024)
|
|
{
|
|
l *= 2;
|
|
id3->needs_upsampling_correction = true;
|
|
}
|
|
|
|
id3->samples += (uint64_t) n * l;
|
|
}
|
|
|
|
size = 0;
|
|
}
|
|
break;
|
|
|
|
case MP4_mp4a:
|
|
{
|
|
uint32_t subsize;
|
|
uint32_t subtype;
|
|
|
|
/* Move to the next expected mp4 atom. */
|
|
lseek(fd, 28, SEEK_CUR);
|
|
read_mp4_atom(fd, &subsize, &subtype, size);
|
|
size -= 36;
|
|
|
|
if (subtype == MP4_esds)
|
|
{
|
|
/* Read esds metadata and return if AAC-HE/SBR is used. */
|
|
if (read_mp4_esds(fd, id3, &size))
|
|
id3->codectype = AFMT_MP4_AAC_HE;
|
|
else
|
|
id3->codectype = AFMT_MP4_AAC;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MP4_alac:
|
|
{
|
|
uint32_t frequency;
|
|
uint32_t subsize;
|
|
uint32_t subtype;
|
|
|
|
/* Move to the next expected mp4 atom. */
|
|
lseek(fd, 28, SEEK_CUR);
|
|
read_mp4_atom(fd, &subsize, &subtype, size);
|
|
size -= 36;
|
|
#if 0
|
|
/* We might need to parse for the alac metadata atom. */
|
|
while (!((subsize==28) && (subtype==MP4_alac)) && (size>0))
|
|
{
|
|
lseek(fd, -7, SEEK_CUR);
|
|
read_mp4_atom(fd, &subsize, &subtype, size);
|
|
size -= 1;
|
|
errno = 0; /* will most likely be set while parsing */
|
|
}
|
|
#endif
|
|
if (subtype == MP4_alac)
|
|
{
|
|
lseek(fd, 24, SEEK_CUR);
|
|
read_uint32be(fd, &frequency);
|
|
size -= 28;
|
|
id3->frequency = frequency;
|
|
id3->codectype = AFMT_MP4_ALAC;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case MP4_mdat:
|
|
/* Some AAC files appear to contain additional empty mdat chunks.
|
|
Ignore them. */
|
|
if(size == 0)
|
|
break;
|
|
/* mdat chunks accumulate! */
|
|
id3->filesize += size;
|
|
if(id3->samples > 0) {
|
|
/* We've already seen the moov chunk. */
|
|
done = true;
|
|
}
|
|
break;
|
|
|
|
case MP4_chpl:
|
|
{
|
|
/* ADDME: add support for real chapters. Right now it's only
|
|
* used for Nero's gapless hack */
|
|
uint8_t chapters = 0;
|
|
uint64_t timestamp = 0;
|
|
|
|
lseek(fd, 8, SEEK_CUR);
|
|
read_uint8(fd, &chapters);
|
|
size -= 9;
|
|
|
|
/* the first chapter will be used as the lead_trim */
|
|
if (chapters > 0) {
|
|
read_uint64be(fd, ×tamp);
|
|
id3->lead_trim = (timestamp * id3->frequency) / 10000000;
|
|
size -= 8;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Skip final seek. */
|
|
if (!done)
|
|
{
|
|
lseek(fd, size, SEEK_CUR);
|
|
}
|
|
} while (rc && (size_left > 0) && (errno == 0) && !done);
|
|
|
|
return rc;
|
|
}
|
|
|
|
bool get_mp4_metadata(int fd, struct mp3entry* id3)
|
|
{
|
|
id3->codectype = AFMT_UNKNOWN;
|
|
id3->filesize = 0;
|
|
errno = 0;
|
|
|
|
if (read_mp4_container(fd, id3, ffilesize(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 = ((int64_t) id3->samples * 1000) / id3->frequency;
|
|
|
|
id3->vbr = true; /* ALAC is native VBR, AAC very unlikely is CBR. */
|
|
|
|
if (id3->length <= 0)
|
|
{
|
|
logf("mp4 length invalid!");
|
|
return false;
|
|
}
|
|
|
|
id3->bitrate = ((int64_t) id3->filesize * 8) / id3->length;
|
|
DEBUGF("MP4 bitrate %d, frequency %ld Hz, length %ld ms\n",
|
|
id3->bitrate, id3->frequency, id3->length);
|
|
}
|
|
else
|
|
{
|
|
logf("MP4 metadata error");
|
|
DEBUGF("MP4 metadata error. errno %d, samples %ld, frequency %ld, "
|
|
"filesize %ld\n", errno, id3->samples, id3->frequency,
|
|
id3->filesize);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|