You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1109 lines
26 KiB
1109 lines
26 KiB
/****************************************************************************
|
|
MP4 input module
|
|
|
|
Copyright (C) 2017 Krzysztof Nikiel
|
|
|
|
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 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
****************************************************************************/
|
|
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <limits.h>
|
|
|
|
#include "unicode_support.h"
|
|
#include "mp4read.h"
|
|
|
|
enum ATOM_TYPE
|
|
{
|
|
ATOM_STOP = 0 /* end of atoms */ ,
|
|
ATOM_NAME /* plain atom */ ,
|
|
ATOM_DESCENT, /* starts group of children */
|
|
ATOM_ASCENT, /* ends group */
|
|
ATOM_DATA,
|
|
};
|
|
|
|
typedef int (*parse_t)(int);
|
|
|
|
typedef struct
|
|
{
|
|
uint16_t opcode;
|
|
const char *name;
|
|
parse_t parse;
|
|
} creator_t;
|
|
|
|
#define STOP() {ATOM_STOP, NULL, NULL}
|
|
#define NAME(N) {ATOM_NAME, N, NULL}
|
|
#define DESCENT() {ATOM_DESCENT, NULL, NULL}
|
|
#define ASCENT() {ATOM_ASCENT, NULL, NULL}
|
|
#define DATA(N, F) {ATOM_NAME, N, NULL}, {ATOM_DATA, NULL, F}
|
|
|
|
mp4config_t mp4config = { 0 };
|
|
|
|
static FILE *g_fin = NULL;
|
|
|
|
enum {ERR_OK = 0, ERR_FAIL = -1, ERR_UNSUPPORTED = -2};
|
|
|
|
#define freeMem(A) if (*(A)) {free(*(A)); *(A) = NULL;}
|
|
|
|
static size_t datain(void *data, size_t size)
|
|
{
|
|
return fread(data, 1, size, g_fin);
|
|
}
|
|
|
|
static int stringin(char *txt, int sizemax)
|
|
{
|
|
int size;
|
|
for (size = 0; size < sizemax; size++)
|
|
{
|
|
if (fread(txt + size, 1, 1, g_fin) != 1)
|
|
return ERR_FAIL;
|
|
if (!txt[size])
|
|
break;
|
|
}
|
|
txt[sizemax-1] = '\0';
|
|
|
|
return size;
|
|
}
|
|
|
|
static uint32_t u32in(void)
|
|
{
|
|
uint8_t u8[4];
|
|
datain(&u8, 4);
|
|
return (uint32_t)u8[3] | ((uint32_t)u8[2] << 8) | ((uint32_t)u8[1] << 16) | ((uint32_t)u8[0] << 24);
|
|
}
|
|
|
|
static uint16_t u16in(void)
|
|
{
|
|
uint8_t u8[2];
|
|
datain(&u8, 2);
|
|
return (uint16_t)u8[1] | ((uint16_t)u8[0] << 8);
|
|
}
|
|
|
|
static int u8in(void)
|
|
{
|
|
uint8_t u8;
|
|
datain(&u8, 1);
|
|
return u8;
|
|
}
|
|
|
|
static int ftypin(int size)
|
|
{
|
|
enum {BUFSIZE = 40};
|
|
char buf[BUFSIZE];
|
|
uint32_t u32;
|
|
|
|
buf[4] = 0;
|
|
datain(buf, 4);
|
|
u32 = u32in();
|
|
|
|
if (mp4config.verbose.header)
|
|
fprintf(stderr, "Brand:\t\t\t%s(version %d)\n", buf, u32);
|
|
|
|
stringin(buf, BUFSIZE);
|
|
|
|
if (mp4config.verbose.header)
|
|
fprintf(stderr, "Compatible brands:\t%s\n", buf);
|
|
|
|
return size;
|
|
}
|
|
|
|
enum
|
|
{ SECSINDAY = 24 * 60 * 60 };
|
|
static char *mp4time(time_t t)
|
|
{
|
|
int y;
|
|
|
|
// subtract some seconds from the start of 1904 to the start of 1970
|
|
for (y = 1904; y < 1970; y++)
|
|
{
|
|
t -= 365 * SECSINDAY;
|
|
if (!(y & 3))
|
|
t -= SECSINDAY;
|
|
}
|
|
return ctime(&t);
|
|
}
|
|
|
|
static int mdhdin(int size)
|
|
{
|
|
// version/flags
|
|
u32in();
|
|
// Creation time
|
|
mp4config.ctime = u32in();
|
|
// Modification time
|
|
mp4config.mtime = u32in();
|
|
// Time scale
|
|
mp4config.samplerate = u32in();
|
|
// Duration
|
|
mp4config.samples = u32in();
|
|
// Language
|
|
u16in();
|
|
// pre_defined
|
|
u16in();
|
|
|
|
return size;
|
|
}
|
|
|
|
static int hdlr1in(int size)
|
|
{
|
|
uint8_t buf[5];
|
|
|
|
buf[4] = 0;
|
|
// version/flags
|
|
u32in();
|
|
// pre_defined
|
|
u32in();
|
|
// Component subtype
|
|
datain(buf, 4);
|
|
if (mp4config.verbose.header)
|
|
fprintf(stderr, "*track media type: '%s': ", buf);
|
|
if (memcmp("soun", buf, 4))
|
|
{
|
|
if (mp4config.verbose.header)
|
|
fprintf(stderr, "unsupported, skipping\n");
|
|
return ERR_UNSUPPORTED;
|
|
}
|
|
else
|
|
{
|
|
if (mp4config.verbose.header)
|
|
fprintf(stderr, "OK\n");
|
|
}
|
|
// reserved
|
|
u32in();
|
|
u32in();
|
|
u32in();
|
|
// name
|
|
// null terminate
|
|
u8in();
|
|
|
|
return size;
|
|
}
|
|
|
|
static int stsdin(int size)
|
|
{
|
|
// version/flags
|
|
u32in();
|
|
// Number of entries(one 'mp4a')
|
|
if (u32in() != 1) //fixme: error handling
|
|
return ERR_FAIL;
|
|
|
|
return size;
|
|
}
|
|
|
|
static int mp4ain(int size)
|
|
{
|
|
// Reserved (6 bytes)
|
|
u32in();
|
|
u16in();
|
|
// Data reference index
|
|
u16in();
|
|
// Version
|
|
u16in();
|
|
// Revision level
|
|
u16in();
|
|
// Vendor
|
|
u32in();
|
|
// Number of channels
|
|
mp4config.channels = u16in();
|
|
// Sample size (bits)
|
|
mp4config.bits = u16in();
|
|
// Compression ID
|
|
u16in();
|
|
// Packet size
|
|
u16in();
|
|
// Sample rate (16.16)
|
|
// fractional framerate, probably not for audio
|
|
// rate integer part
|
|
u16in();
|
|
// rate reminder part
|
|
u16in();
|
|
|
|
return size;
|
|
}
|
|
|
|
|
|
static uint32_t getsize(void)
|
|
{
|
|
int cnt;
|
|
uint32_t size = 0;
|
|
for (cnt = 0; cnt < 4; cnt++)
|
|
{
|
|
int tmp = u8in();
|
|
|
|
size <<= 7;
|
|
size |= (tmp & 0x7f);
|
|
if (!(tmp & 0x80))
|
|
break;
|
|
}
|
|
return size;
|
|
}
|
|
|
|
static int esdsin(int size)
|
|
{
|
|
// descriptor tree:
|
|
// MP4ES_Descriptor
|
|
// MP4DecoderConfigDescriptor
|
|
// MP4DecSpecificInfoDescriptor
|
|
// MP4SLConfigDescriptor
|
|
enum
|
|
{ TAG_ES = 3, TAG_DC = 4, TAG_DSI = 5, TAG_SLC = 6 };
|
|
|
|
// version/flags
|
|
u32in();
|
|
if (u8in() != TAG_ES)
|
|
return ERR_FAIL;
|
|
getsize();
|
|
// ESID
|
|
u16in();
|
|
// flags(url(bit 6); ocr(5); streamPriority (0-4)):
|
|
u8in();
|
|
|
|
if (u8in() != TAG_DC)
|
|
return ERR_FAIL;
|
|
getsize();
|
|
if (u8in() != 0x40) /* not MPEG-4 audio */
|
|
return ERR_FAIL;
|
|
// flags
|
|
u8in();
|
|
// buffer size (24 bits)
|
|
mp4config.buffersize = u16in() << 8;
|
|
mp4config.buffersize |= u8in();
|
|
// bitrate
|
|
mp4config.bitratemax = u32in();
|
|
mp4config.bitrateavg = u32in();
|
|
|
|
if (u8in() != TAG_DSI)
|
|
return ERR_FAIL;
|
|
mp4config.asc.size = getsize();
|
|
if (mp4config.asc.size > sizeof(mp4config.asc.buf))
|
|
return ERR_FAIL;
|
|
// get AudioSpecificConfig
|
|
datain(mp4config.asc.buf, mp4config.asc.size);
|
|
|
|
if (u8in() != TAG_SLC)
|
|
return ERR_FAIL;
|
|
getsize();
|
|
// "predefined" (no idea)
|
|
u8in();
|
|
|
|
return size;
|
|
}
|
|
|
|
/* stbl "Sample Table" layout:
|
|
* - stts "Time-to-Sample" - useless
|
|
* - stsc "Sample-to-Chunk" - condensed table chunk-to-num-samples
|
|
* - stsz "Sample Size" - size table
|
|
* - stco "Chunk Offset" - chunk starts
|
|
*
|
|
* When receiving stco we can combine stsc and stsz tables to produce final
|
|
* sample offsets.
|
|
*/
|
|
|
|
static int sttsin(int size)
|
|
{
|
|
uint32_t ntts;
|
|
|
|
if (size < 8)
|
|
return ERR_FAIL;
|
|
|
|
// version/flags
|
|
u32in();
|
|
ntts = u32in();
|
|
|
|
if (ntts < 1)
|
|
return ERR_FAIL;
|
|
|
|
/* 2 x uint32_t per entry */
|
|
if (((size - 8u) / 8u) < ntts)
|
|
return ERR_FAIL;
|
|
|
|
return size;
|
|
}
|
|
|
|
static int stscin(int size)
|
|
{
|
|
uint32_t i, tmp, firstchunk, prevfirstchunk, samplesperchunk;
|
|
|
|
if (size < 8)
|
|
return ERR_FAIL;
|
|
|
|
// version/flags
|
|
u32in();
|
|
|
|
mp4config.frame.nsclices = u32in();
|
|
|
|
tmp = sizeof(slice_info_t) * mp4config.frame.nsclices;
|
|
if (tmp < mp4config.frame.nsclices)
|
|
return ERR_FAIL;
|
|
mp4config.frame.map = malloc(tmp);
|
|
if (!mp4config.frame.map)
|
|
return ERR_FAIL;
|
|
|
|
/* 3 x uint32_t per entry */
|
|
if (((size - 8u) / 12u) < mp4config.frame.nsclices)
|
|
return ERR_FAIL;
|
|
|
|
prevfirstchunk = 0;
|
|
for (i = 0; i < mp4config.frame.nsclices; ++i) {
|
|
firstchunk = u32in();
|
|
samplesperchunk = u32in();
|
|
// id - unused
|
|
u32in();
|
|
if (firstchunk <= prevfirstchunk)
|
|
return ERR_FAIL;
|
|
if (samplesperchunk < 1)
|
|
return ERR_FAIL;
|
|
mp4config.frame.map[i].firstchunk = firstchunk;
|
|
mp4config.frame.map[i].samplesperchunk = samplesperchunk;
|
|
prevfirstchunk = firstchunk;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
static int stszin(int size)
|
|
{
|
|
uint32_t i, tmp;
|
|
|
|
if (size < 12)
|
|
return ERR_FAIL;
|
|
|
|
// version/flags
|
|
u32in();
|
|
// (uniform) Sample size
|
|
// TODO(eustas): add uniform sample size support?
|
|
u32in();
|
|
mp4config.frame.nsamples = u32in();
|
|
|
|
if (!mp4config.frame.nsamples)
|
|
return ERR_FAIL;
|
|
|
|
tmp = sizeof(frame_info_t) * mp4config.frame.nsamples;
|
|
if (tmp < mp4config.frame.nsamples)
|
|
return ERR_FAIL;
|
|
mp4config.frame.info = malloc(tmp);
|
|
if (!mp4config.frame.info)
|
|
return ERR_FAIL;
|
|
|
|
if ((size - 12u) / 4u < mp4config.frame.nsamples)
|
|
return ERR_FAIL;
|
|
|
|
for (i = 0; i < mp4config.frame.nsamples; i++)
|
|
{
|
|
mp4config.frame.info[i].len = u32in();
|
|
mp4config.frame.info[i].offset = 0;
|
|
if (mp4config.frame.maxsize < mp4config.frame.info[i].len)
|
|
mp4config.frame.maxsize = mp4config.frame.info[i].len;
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
static int stcoin(int size)
|
|
{
|
|
uint32_t numchunks, chunkn, slicen, samplesleft, i, offset;
|
|
uint32_t nextoffset;
|
|
|
|
if (size < 8)
|
|
return ERR_FAIL;
|
|
|
|
// version/flags
|
|
u32in();
|
|
|
|
// Number of entries
|
|
numchunks = u32in();
|
|
if ((numchunks < 1) || ((numchunks + 1) == 0))
|
|
return ERR_FAIL;
|
|
|
|
if ((size - 8u) / 4u < numchunks)
|
|
return ERR_FAIL;
|
|
|
|
chunkn = 0;
|
|
samplesleft = 0;
|
|
slicen = 0;
|
|
offset = 0;
|
|
|
|
for (i = 0; i < mp4config.frame.nsamples; ++i) {
|
|
if (samplesleft == 0)
|
|
{
|
|
chunkn++;
|
|
if (chunkn > numchunks)
|
|
return ERR_FAIL;
|
|
if (slicen < mp4config.frame.nsclices &&
|
|
(slicen + 1) < mp4config.frame.nsclices) {
|
|
if (chunkn == mp4config.frame.map[slicen + 1].firstchunk)
|
|
slicen++;
|
|
}
|
|
samplesleft = mp4config.frame.map[slicen].samplesperchunk;
|
|
offset = u32in();
|
|
}
|
|
mp4config.frame.info[i].offset = offset;
|
|
nextoffset = offset + mp4config.frame.info[i].len;
|
|
if (nextoffset < offset)
|
|
return ERR_FAIL;
|
|
offset = nextoffset;
|
|
samplesleft--;
|
|
}
|
|
|
|
freeMem(&mp4config.frame.map);
|
|
|
|
return size;
|
|
}
|
|
|
|
#if 0
|
|
static int tagtxt(char *tagname, const char *tagtxt)
|
|
{
|
|
//int txtsize = strlen(tagtxt);
|
|
int size = 0;
|
|
//int datasize = txtsize + 16;
|
|
|
|
#if 0
|
|
size += u32out(datasize + 8);
|
|
size += dataout(tagname, 4);
|
|
size += u32out(datasize);
|
|
size += dataout("data", 4);
|
|
size += u32out(1);
|
|
size += u32out(0);
|
|
size += dataout(tagtxt, txtsize);
|
|
#endif
|
|
|
|
return size;
|
|
}
|
|
|
|
static int tagu32(char *tagname, int n /*number of stored fields*/)
|
|
{
|
|
//int numsize = n * 4;
|
|
int size = 0;
|
|
//int datasize = numsize + 16;
|
|
|
|
#if 0
|
|
size += u32out(datasize + 8);
|
|
size += dataout(tagname, 4);
|
|
size += u32out(datasize);
|
|
size += dataout("data", 4);
|
|
size += u32out(0);
|
|
size += u32out(0);
|
|
#endif
|
|
|
|
return size;
|
|
}
|
|
#endif
|
|
|
|
static int metain(int size)
|
|
{
|
|
(void)size; /* why not used? */
|
|
// version/flags
|
|
u32in();
|
|
|
|
return ERR_OK;
|
|
}
|
|
|
|
static int hdlr2in(int size)
|
|
{
|
|
uint8_t buf[4];
|
|
|
|
// version/flags
|
|
u32in();
|
|
// Predefined
|
|
u32in();
|
|
// Handler type
|
|
datain(buf, 4);
|
|
if (memcmp(buf, "mdir", 4))
|
|
return ERR_FAIL;
|
|
datain(buf, 4);
|
|
if (memcmp(buf, "appl", 4))
|
|
return ERR_FAIL;
|
|
// Reserved
|
|
u32in();
|
|
u32in();
|
|
// null terminator
|
|
u8in();
|
|
|
|
return size;
|
|
}
|
|
|
|
static int ilstin(int size)
|
|
{
|
|
enum {NUMSET = 1, GENRE, EXTAG};
|
|
int read = 0;
|
|
|
|
static struct {
|
|
char *name;
|
|
char *id;
|
|
int flag;
|
|
} tags[] = {
|
|
{"Album ", "\xa9" "alb"},
|
|
{"Album Artist", "aART"},
|
|
{"Artist ", "\xa9" "ART"},
|
|
{"Comment ", "\xa9" "cmt"},
|
|
{"Cover image ", "covr"},
|
|
{"Compilation ", "cpil"},
|
|
{"Copyright ", "cprt"},
|
|
{"Date ", "\xa9" "day"},
|
|
{"Disc# ", "disk", NUMSET},
|
|
{"Genre ", "gnre", GENRE},
|
|
{"Grouping ", "\xa9" "grp"},
|
|
{"Lyrics ", "\xa9" "lyr"},
|
|
{"Title ", "\xa9" "nam"},
|
|
{"Rating ", "rtng"},
|
|
{"BPM ", "tmpo"},
|
|
{"Encoder ", "\xa9" "too"},
|
|
{"Track ", "trkn", NUMSET},
|
|
{"Composer ", "\xa9" "wrt"},
|
|
{0, "----", EXTAG},
|
|
{0},
|
|
};
|
|
|
|
static const char *genres[] = {
|
|
"Blues", "Classic Rock", "Country", "Dance",
|
|
"Disco", "Funk", "Grunge", "Hip-Hop",
|
|
"Jazz", "Metal", "New Age", "Oldies",
|
|
"Other", "Pop", "R&B", "Rap",
|
|
"Reggae", "Rock", "Techno", "Industrial",
|
|
"Alternative", "Ska", "Death Metal", "Pranks",
|
|
"Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop",
|
|
"Vocal", "Jazz+Funk", "Fusion", "Trance",
|
|
"Classical", "Instrumental", "Acid", "House",
|
|
"Game", "Sound Clip", "Gospel", "Noise",
|
|
"Alternative Rock", "Bass", "Soul", "Punk",
|
|
"Space", "Meditative", "Instrumental Pop", "Instrumental Rock",
|
|
"Ethnic", "Gothic", "Darkwave", "Techno-Industrial",
|
|
"Electronic", "Pop-Folk", "Eurodance", "Dream",
|
|
"Southern Rock", "Comedy", "Cult", "Gangsta",
|
|
"Top 40", "Christian Rap", "Pop/Funk", "Jungle",
|
|
"Native US", "Cabaret", "New Wave", "Psychadelic",
|
|
"Rave", "Showtunes", "Trailer", "Lo-Fi",
|
|
"Tribal", "Acid Punk", "Acid Jazz", "Polka",
|
|
"Retro", "Musical", "Rock & Roll", "Hard Rock",
|
|
"Folk", "Folk-Rock", "National Folk", "Swing",
|
|
"Fast Fusion", "Bebob", "Latin", "Revival",
|
|
"Celtic", "Bluegrass", "Avantgarde", "Gothic Rock",
|
|
"Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock",
|
|
"Big Band", "Chorus", "Easy Listening", "Acoustic",
|
|
"Humour", "Speech", "Chanson", "Opera",
|
|
"Chamber Music", "Sonata", "Symphony", "Booty Bass",
|
|
"Primus", "Porn Groove", "Satire", "Slow Jam",
|
|
"Club", "Tango", "Samba", "Folklore",
|
|
"Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle",
|
|
"Duet", "Punk Rock", "Drum Solo", "Acapella",
|
|
"Euro-House", "Dance Hall", "Goa", "Drum & Bass",
|
|
"Club - House", "Hardcore", "Terror", "Indie",
|
|
"BritPop", "Negerpunk", "Polsk Punk", "Beat",
|
|
"Christian Gangsta Rap", "Heavy Metal", "Black Metal", "Crossover",
|
|
"Contemporary Christian", "Christian Rock", "Merengue", "Salsa",
|
|
"Thrash Metal", "Anime", "JPop", "Synthpop",
|
|
"Unknown",
|
|
};
|
|
|
|
fprintf(stderr, "----------tag list-------------\n");
|
|
while(read < size)
|
|
{
|
|
int asize, dsize;
|
|
uint8_t id[5];
|
|
int cnt;
|
|
uint32_t type;
|
|
|
|
id[4] = 0;
|
|
|
|
asize = u32in();
|
|
read += asize;
|
|
asize -= 4;
|
|
if (datain(id, 4) < 4)
|
|
return ERR_FAIL;
|
|
asize -= 4;
|
|
|
|
for (cnt = 0; tags[cnt].id; cnt++)
|
|
{
|
|
if (!memcmp(id, tags[cnt].id, 4))
|
|
break;
|
|
}
|
|
|
|
if (tags[cnt].name)
|
|
fprintf(stderr, "%s : ", tags[cnt].name);
|
|
else
|
|
{
|
|
if (tags[cnt].flag != EXTAG)
|
|
fprintf(stderr, "'%s' : ", id);
|
|
}
|
|
|
|
dsize = u32in();
|
|
asize -= 4;
|
|
if (datain(id, 4) < 4)
|
|
return ERR_FAIL;
|
|
asize -= 4;
|
|
|
|
if (tags[cnt].flag != EXTAG)
|
|
{
|
|
if (memcmp(id, "data", 4))
|
|
return ERR_FAIL;
|
|
}
|
|
else
|
|
{
|
|
int spc;
|
|
|
|
if (memcmp(id, "mean", 4))
|
|
goto skip;
|
|
dsize -= 8;
|
|
while (dsize > 0)
|
|
{
|
|
u8in();
|
|
asize--;
|
|
dsize--;
|
|
}
|
|
if (asize >= 8)
|
|
{
|
|
dsize = u32in() - 8;
|
|
asize -= 4;
|
|
if (datain(id, 4) < 4)
|
|
return ERR_FAIL;
|
|
asize -= 4;
|
|
if (memcmp(id, "name", 4))
|
|
goto skip;
|
|
u32in();
|
|
asize -= 4;
|
|
dsize -= 4;
|
|
}
|
|
spc = 13 - dsize;
|
|
if (spc < 0) spc = 0;
|
|
while (dsize > 0)
|
|
{
|
|
fprintf(stderr, "%c",u8in());
|
|
asize--;
|
|
dsize--;
|
|
}
|
|
while (spc--)
|
|
fprintf(stderr, " ");
|
|
fprintf(stderr, ": ");
|
|
if (asize >= 8)
|
|
{
|
|
dsize = u32in() - 8;
|
|
asize -= 4;
|
|
if (datain(id, 4) < 4)
|
|
return ERR_FAIL;
|
|
asize -= 4;
|
|
if (memcmp(id, "data", 4))
|
|
goto skip;
|
|
u32in();
|
|
asize -= 4;
|
|
dsize -= 4;
|
|
}
|
|
while (dsize > 0)
|
|
{
|
|
fprintf(stderr, "%c",u8in());
|
|
asize--;
|
|
dsize--;
|
|
}
|
|
fprintf(stderr, "\n");
|
|
|
|
goto skip;
|
|
}
|
|
type = u32in();
|
|
asize -= 4;
|
|
u32in();
|
|
asize -= 4;
|
|
|
|
switch(type)
|
|
{
|
|
case 1:
|
|
while (asize > 0)
|
|
{
|
|
fprintf(stderr, "%c",u8in());
|
|
asize--;
|
|
}
|
|
break;
|
|
case 0:
|
|
switch(tags[cnt].flag)
|
|
{
|
|
case NUMSET:
|
|
u16in();
|
|
asize -= 2;
|
|
|
|
fprintf(stderr, "%d", u16in());
|
|
asize -= 2;
|
|
fprintf(stderr, "/%d", u16in());
|
|
asize -= 2;
|
|
break;
|
|
case GENRE:
|
|
{
|
|
uint16_t gnum = u16in();
|
|
asize -= 2;
|
|
if (!gnum)
|
|
goto skip;
|
|
gnum--;
|
|
if (gnum >= 147)
|
|
gnum = 147;
|
|
fprintf(stderr, "%s", genres[gnum]);
|
|
}
|
|
break;
|
|
default:
|
|
while(asize > 0)
|
|
{
|
|
fprintf(stderr, "%d/", u16in());
|
|
asize-=2;
|
|
}
|
|
}
|
|
break;
|
|
case 0x15:
|
|
//fprintf(stderr, "(8bit data)");
|
|
while(asize > 0)
|
|
{
|
|
fprintf(stderr, "%d", u8in());
|
|
asize--;
|
|
if (asize)
|
|
fprintf(stderr, "/");
|
|
}
|
|
break;
|
|
case 0xd:
|
|
fprintf(stderr, "(image data)");
|
|
break;
|
|
default:
|
|
fprintf(stderr, "(unknown data type)");
|
|
break;
|
|
}
|
|
fprintf(stderr, "\n");
|
|
|
|
skip:
|
|
// skip to the end of atom
|
|
while (asize > 0)
|
|
{
|
|
u8in();
|
|
asize--;
|
|
}
|
|
}
|
|
fprintf(stderr, "-------------------------------\n");
|
|
|
|
return size;
|
|
}
|
|
|
|
static creator_t *g_atom = 0;
|
|
static int parse(uint32_t *sizemax)
|
|
{
|
|
long apos = 0;
|
|
long aposmax = ftell(g_fin) + *sizemax;
|
|
uint32_t size;
|
|
|
|
if (g_atom->opcode != ATOM_NAME)
|
|
{
|
|
fprintf(stderr, "parse error: root is not a 'name' opcode\n");
|
|
return ERR_FAIL;
|
|
}
|
|
//fprintf(stderr, "looking for '%s'\n", (char *)g_atom->name);
|
|
|
|
// search for atom in the file
|
|
while (1)
|
|
{
|
|
char name[4];
|
|
uint32_t tmp;
|
|
|
|
apos = ftell(g_fin);
|
|
if (apos >= (aposmax - 8))
|
|
{
|
|
fprintf(stderr, "parse error: atom '%s' not found\n", g_atom->name);
|
|
return ERR_FAIL;
|
|
}
|
|
if ((tmp = u32in()) < 8)
|
|
{
|
|
fprintf(stderr, "invalid atom size %x @%lx\n", tmp, ftell(g_fin));
|
|
return ERR_FAIL;
|
|
}
|
|
|
|
size = tmp;
|
|
if (datain(name, 4) != 4)
|
|
{
|
|
// EOF
|
|
fprintf(stderr, "can't read atom name @%lx\n", ftell(g_fin));
|
|
return ERR_FAIL;
|
|
}
|
|
|
|
//fprintf(stderr, "atom: '%c%c%c%c'(%x)", name[0],name[1],name[2],name[3], size);
|
|
|
|
if (!memcmp(name, g_atom->name, 4))
|
|
{
|
|
//fprintf(stderr, "OK\n");
|
|
break;
|
|
}
|
|
//fprintf(stderr, "\n");
|
|
|
|
fseek(g_fin, apos + size, SEEK_SET);
|
|
}
|
|
*sizemax = size;
|
|
g_atom++;
|
|
if (g_atom->opcode == ATOM_DATA)
|
|
{
|
|
int err = g_atom->parse(size - 8);
|
|
if (err < ERR_OK)
|
|
{
|
|
fseek(g_fin, apos + size, SEEK_SET);
|
|
return err;
|
|
}
|
|
g_atom++;
|
|
}
|
|
if (g_atom->opcode == ATOM_DESCENT)
|
|
{
|
|
long apos2 = ftell(g_fin);
|
|
|
|
//fprintf(stderr, "descent\n");
|
|
g_atom++;
|
|
while (g_atom->opcode != ATOM_STOP)
|
|
{
|
|
uint32_t subsize = size - 8;
|
|
int ret;
|
|
if (g_atom->opcode == ATOM_ASCENT)
|
|
{
|
|
g_atom++;
|
|
break;
|
|
}
|
|
// TODO: does not feel well - we always return to the same point!
|
|
fseek(g_fin, apos2, SEEK_SET);
|
|
if ((ret = parse(&subsize)) < 0)
|
|
return ret;
|
|
}
|
|
//fprintf(stderr, "ascent\n");
|
|
}
|
|
|
|
fseek(g_fin, apos + size, SEEK_SET);
|
|
|
|
return ERR_OK;
|
|
}
|
|
|
|
static int moovin(int sizemax)
|
|
{
|
|
long apos = ftell(g_fin);
|
|
uint32_t atomsize;
|
|
creator_t *old_atom = g_atom;
|
|
int err, ret = sizemax;
|
|
|
|
static creator_t mvhd[] = {
|
|
NAME("mvhd"),
|
|
STOP()
|
|
};
|
|
static creator_t trak[] = {
|
|
NAME("trak"),
|
|
DESCENT(),
|
|
NAME("tkhd"),
|
|
NAME("mdia"),
|
|
DESCENT(),
|
|
DATA("mdhd", mdhdin),
|
|
DATA("hdlr", hdlr1in),
|
|
NAME("minf"),
|
|
DESCENT(),
|
|
NAME("smhd"),
|
|
NAME("dinf"),
|
|
NAME("stbl"),
|
|
DESCENT(),
|
|
DATA("stsd", stsdin),
|
|
DESCENT(),
|
|
DATA("mp4a", mp4ain),
|
|
DESCENT(),
|
|
DATA("esds", esdsin),
|
|
ASCENT(),
|
|
ASCENT(),
|
|
DATA("stts", sttsin),
|
|
DATA("stsc", stscin),
|
|
DATA("stsz", stszin),
|
|
DATA("stco", stcoin),
|
|
STOP()
|
|
};
|
|
|
|
g_atom = mvhd;
|
|
atomsize = sizemax + apos - ftell(g_fin);
|
|
if (parse(&atomsize) < 0) {
|
|
g_atom = old_atom;
|
|
return ERR_FAIL;
|
|
}
|
|
|
|
fseek(g_fin, apos, SEEK_SET);
|
|
|
|
while (1)
|
|
{
|
|
//fprintf(stderr, "TRAK\n");
|
|
g_atom = trak;
|
|
atomsize = sizemax + apos - ftell(g_fin);
|
|
if (atomsize < 8)
|
|
break;
|
|
//fprintf(stderr, "PARSE(%x)\n", atomsize);
|
|
err = parse(&atomsize);
|
|
//fprintf(stderr, "SIZE: %x/%x\n", atomsize, sizemax);
|
|
if (err >= 0)
|
|
break;
|
|
if (err != ERR_UNSUPPORTED) {
|
|
ret = err;
|
|
break;
|
|
}
|
|
//fprintf(stderr, "UNSUPP\n");
|
|
}
|
|
|
|
g_atom = old_atom;
|
|
return ret;
|
|
}
|
|
|
|
|
|
static creator_t g_head[] = {
|
|
DATA("ftyp", ftypin),
|
|
STOP()
|
|
};
|
|
|
|
static creator_t g_moov[] = {
|
|
DATA("moov", moovin),
|
|
//DESCENT(),
|
|
//NAME("mvhd"),
|
|
STOP()
|
|
};
|
|
|
|
static creator_t g_meta1[] = {
|
|
NAME("moov"),
|
|
DESCENT(),
|
|
NAME("udta"),
|
|
DESCENT(),
|
|
DATA("meta", metain),
|
|
DESCENT(),
|
|
DATA("hdlr", hdlr2in),
|
|
DATA("ilst", ilstin),
|
|
STOP()
|
|
};
|
|
|
|
static creator_t g_meta2[] = {
|
|
DATA("meta", metain),
|
|
DESCENT(),
|
|
DATA("hdlr", hdlr2in),
|
|
DATA("ilst", ilstin),
|
|
STOP()
|
|
};
|
|
|
|
|
|
int mp4read_frame(void)
|
|
{
|
|
if (mp4config.frame.current >= mp4config.frame.nsamples)
|
|
return ERR_FAIL;
|
|
|
|
// TODO(eustas): avoid no-op seeks
|
|
mp4read_seek(mp4config.frame.current);
|
|
|
|
mp4config.bitbuf.size = mp4config.frame.info[mp4config.frame.current].len;
|
|
|
|
if (fread(mp4config.bitbuf.data, 1, mp4config.bitbuf.size, g_fin)
|
|
!= mp4config.bitbuf.size)
|
|
{
|
|
fprintf(stderr, "can't read frame data(frame %d@0x%x)\n",
|
|
mp4config.frame.current,
|
|
mp4config.frame.info[mp4config.frame.current].offset);
|
|
|
|
return ERR_FAIL;
|
|
}
|
|
|
|
mp4config.frame.current++;
|
|
|
|
return ERR_OK;
|
|
}
|
|
|
|
int mp4read_seek(uint32_t framenum)
|
|
{
|
|
if (framenum > mp4config.frame.nsamples)
|
|
return ERR_FAIL;
|
|
if (fseek(g_fin, mp4config.frame.info[framenum].offset, SEEK_SET))
|
|
return ERR_FAIL;
|
|
|
|
mp4config.frame.current = framenum;
|
|
|
|
return ERR_OK;
|
|
}
|
|
|
|
static void mp4info(void)
|
|
{
|
|
fprintf(stderr, "Modification Time:\t\t%s\n", mp4time(mp4config.mtime));
|
|
fprintf(stderr, "Samplerate:\t\t%d\n", mp4config.samplerate);
|
|
fprintf(stderr, "Total samples:\t\t%d\n", mp4config.samples);
|
|
fprintf(stderr, "Total channels:\t\t%d\n", mp4config.channels);
|
|
fprintf(stderr, "Bits per sample:\t%d\n", mp4config.bits);
|
|
fprintf(stderr, "Buffer size:\t\t%d\n", mp4config.buffersize);
|
|
fprintf(stderr, "Max bitrate:\t\t%d\n", mp4config.bitratemax);
|
|
fprintf(stderr, "Average bitrate:\t%d\n", mp4config.bitrateavg);
|
|
fprintf(stderr, "Frames:\t\t\t%d\n", mp4config.frame.nsamples);
|
|
fprintf(stderr, "ASC size:\t\t%d\n", mp4config.asc.size);
|
|
fprintf(stderr, "Duration:\t\t%.1f sec\n", (float)mp4config.samples/mp4config.samplerate);
|
|
if (mp4config.frame.nsamples)
|
|
fprintf(stderr, "Data offset:\t%x\n", mp4config.frame.info[0].offset);
|
|
}
|
|
|
|
int mp4read_close(void)
|
|
{
|
|
freeMem(&mp4config.frame.info);
|
|
freeMem(&mp4config.frame.map);
|
|
freeMem(&mp4config.bitbuf.data);
|
|
|
|
return ERR_OK;
|
|
}
|
|
|
|
int mp4read_open(char *name)
|
|
{
|
|
uint32_t atomsize;
|
|
int ret;
|
|
|
|
mp4read_close();
|
|
|
|
g_fin = faad_fopen(name, "rb");
|
|
if (!g_fin)
|
|
return ERR_FAIL;
|
|
|
|
if (mp4config.verbose.header)
|
|
fprintf(stderr, "**** MP4 header ****\n");
|
|
g_atom = g_head;
|
|
atomsize = INT_MAX;
|
|
if (parse(&atomsize) < 0)
|
|
goto err;
|
|
g_atom = g_moov;
|
|
atomsize = INT_MAX;
|
|
rewind(g_fin);
|
|
if ((ret = parse(&atomsize)) < 0)
|
|
{
|
|
fprintf(stderr, "parse:%d\n", ret);
|
|
goto err;
|
|
}
|
|
|
|
// alloc frame buffer
|
|
mp4config.bitbuf.data = malloc(mp4config.frame.maxsize);
|
|
|
|
if (!mp4config.bitbuf.data)
|
|
goto err;
|
|
|
|
if (mp4config.verbose.header)
|
|
{
|
|
mp4info();
|
|
fprintf(stderr, "********************\n");
|
|
}
|
|
|
|
if (mp4config.verbose.tags)
|
|
{
|
|
rewind(g_fin);
|
|
g_atom = g_meta1;
|
|
atomsize = INT_MAX;
|
|
ret = parse(&atomsize);
|
|
if (ret < 0)
|
|
{
|
|
rewind(g_fin);
|
|
g_atom = g_meta2;
|
|
atomsize = INT_MAX;
|
|
ret = parse(&atomsize);
|
|
}
|
|
}
|
|
|
|
return ERR_OK;
|
|
err:
|
|
mp4read_close();
|
|
return ERR_FAIL;
|
|
}
|
|
|