summaryrefslogtreecommitdiff
path: root/lib/faad2/frontend/mp4read.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/faad2/frontend/mp4read.c')
-rw-r--r--lib/faad2/frontend/mp4read.c1109
1 files changed, 1109 insertions, 0 deletions
diff --git a/lib/faad2/frontend/mp4read.c b/lib/faad2/frontend/mp4read.c
new file mode 100644
index 00000000..a157d7d6
--- /dev/null
+++ b/lib/faad2/frontend/mp4read.c
@@ -0,0 +1,1109 @@
+/****************************************************************************
+ 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;
+}