ref: 8d045444cb72091b8b479684ffe7ee8e662afb43
parent: f58984cbceb6dd62589069f9b7e8ae5425c3438c
author: knik <knik@users.sourceforge.net>
date: Wed Jul 26 08:14:01 EDT 2017
initial version of new mp4 input code
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,4 @@
+ - New MP4 input module
- NeAACDecGetVersion:
new api function to get version and copyright strings
--- a/frontend/Makefile.am
+++ b/frontend/Makefile.am
@@ -1,9 +1,8 @@
bin_PROGRAMS = faad
man1_MANS = faad.man
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_srcdir)/common/mp4ff
+AM_CPPFLAGS = -I$(top_srcdir)/include
-faad_LDADD = $(top_builddir)/libfaad/libfaad.la \
- $(top_builddir)/common/mp4ff/libmp4ff.a
+faad_LDADD = $(top_builddir)/libfaad/libfaad.la
-faad_SOURCES = main.c audio.c audio.h
+faad_SOURCES = main.c audio.c audio.h mp4read.c mp4read.h
--- a/frontend/main.c
+++ b/frontend/main.c
@@ -50,9 +50,9 @@
#include <getopt.h>
#include <neaacdec.h>
-#include <mp4ff.h>
#include "audio.h"
+#include "mp4read.h"
#ifdef HAVE_GETOPT_H
# include <getopt.h>
@@ -767,35 +767,6 @@
return frameInfo.error;
}
-static int GetAACTrack(mp4ff_t *infile)
-{
- /* find AAC track */
- int i, rc;
- int numTracks = mp4ff_total_tracks(infile);
-
- for (i = 0; i < numTracks; i++)
- {
- unsigned char *buff = NULL;
- int buff_size = 0;
- mp4AudioSpecificConfig mp4ASC;
-
- mp4ff_get_decoder_config(infile, i, &buff, &buff_size);
-
- if (buff)
- {
- rc = NeAACDecAudioSpecificConfig(buff, buff_size, &mp4ASC);
- free(buff);
-
- if (rc < 0)
- continue;
- return i;
- }
- }
-
- /* can't decode this */
- return -1;
-}
-
static const unsigned long srates[] =
{
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000,
@@ -811,12 +782,10 @@
unsigned char channels;
void *sample_buffer;
- mp4ff_t *infile;
- long sampleId, numSamples, startSampleId;
+ long sampleId, startSampleId;
audio_file *aufile;
- FILE *mp4File;
FILE *adtsFile;
unsigned char *adtsData;
int adtsDataSize;
@@ -826,9 +795,6 @@
NeAACDecFrameInfo frameInfo;
mp4AudioSpecificConfig mp4ASC;
- unsigned char *buffer;
- int buffer_size;
-
char percents[200];
int percent, old_percent = -1;
@@ -836,25 +802,26 @@
/* for gapless decoding */
unsigned int useAacLength = 1;
- unsigned int initial = 1;
unsigned int framesize;
- unsigned long timescale;
+ unsigned decoded;
-
- /* initialise the callback structure */
- mp4ff_callback_t *mp4cb = malloc(sizeof(mp4ff_callback_t));
-
if (strcmp(mp4file, "-") == 0 ) {
faad_fprintf(stderr, "Cannot open stdin for MP4 input \n");
return 1;
}
- mp4File = fopen(mp4file, "rb");
- mp4cb->read = read_callback;
- mp4cb->seek = seek_callback;
- mp4cb->user_data = mp4File;
+ if (!quiet)
+ {
+ mp4config.verbose.header = 1;
+ mp4config.verbose.tags = 1;
+ }
+ if (mp4read_open(mp4file))
+ {
+ /* unable to open file */
+ faad_fprintf(stderr, "Error opening file: %s\n", mp4file);
+ return 1;
+ }
-
hDecoder = NeAACDecOpen();
/* Set configuration */
@@ -874,52 +841,27 @@
}
}
- infile = mp4ff_open_read(mp4cb);
- if (!infile)
- {
- /* unable to open file */
- faad_fprintf(stderr, "Error opening file: %s\n", mp4file);
- return 1;
- }
-
- if ((track = GetAACTrack(infile)) < 0)
- {
- faad_fprintf(stderr, "Unable to find correct AAC sound track in the MP4 file.\n");
- NeAACDecClose(hDecoder);
- mp4ff_close(infile);
- free(mp4cb);
- fclose(mp4File);
- return 1;
- }
-
- buffer = NULL;
- buffer_size = 0;
- mp4ff_get_decoder_config(infile, track, &buffer, &buffer_size);
-
- if(NeAACDecInit2(hDecoder, buffer, buffer_size,
+ if(NeAACDecInit2(hDecoder, mp4config.asc.buf, mp4config.asc.size,
&samplerate, &channels) < 0)
{
/* If some error initializing occured, skip the file */
faad_fprintf(stderr, "Error initializing decoder library.\n");
NeAACDecClose(hDecoder);
- mp4ff_close(infile);
- free(mp4cb);
- fclose(mp4File);
+ mp4read_close();
return 1;
}
- timescale = mp4ff_time_scale(infile, track);
framesize = 1024;
useAacLength = 0;
+ decoded = 0;
- if (buffer)
+ if (mp4config.asc.size)
{
- if (NeAACDecAudioSpecificConfig(buffer, buffer_size, &mp4ASC) >= 0)
+ if (NeAACDecAudioSpecificConfig(mp4config.asc.buf, mp4config.asc.size, &mp4ASC) >= 0)
{
if (mp4ASC.frameLengthFlag == 1) framesize = 960;
if (mp4ASC.sbr_present_flag == 1) framesize *= 2;
}
- free(buffer);
}
/* print some mp4 file info */
@@ -928,58 +870,28 @@
char *tag = NULL, *item = NULL;
int k, j;
char *ot[6] = { "NULL", "MAIN AAC", "LC AAC", "SSR AAC", "LTP AAC", "HE AAC" };
- long samples = mp4ff_num_samples(infile, track);
- float f = 1024.0;
float seconds;
- if (mp4ASC.sbr_present_flag == 1)
- {
- f = f * 2.0;
- }
- seconds = (float)samples*(float)(f-1.0)/(float)mp4ASC.samplingFrequency;
+ seconds = (float)mp4config.samples/(float)mp4ASC.samplingFrequency;
*song_length = seconds;
faad_fprintf(stderr, "%s\t%.3f secs, %d ch, %d Hz\n\n", ot[(mp4ASC.objectTypeIndex > 5)?0:mp4ASC.objectTypeIndex],
seconds, mp4ASC.channelsConfiguration, mp4ASC.samplingFrequency);
-
-#define PRINT_MP4_METADATA
-#ifdef PRINT_MP4_METADATA
- j = mp4ff_meta_get_num_items(infile);
- for (k = 0; k < j; k++)
- {
- if (mp4ff_meta_get_by_index(infile, k, &item, &tag))
- {
- if (item != NULL && tag != NULL)
- {
- faad_fprintf(stderr, "%s: %s\n", item, tag);
- free(item); item = NULL;
- free(tag); tag = NULL;
- }
- }
- }
- if (j > 0) faad_fprintf(stderr, "\n");
-#endif
}
if (infoOnly)
{
NeAACDecClose(hDecoder);
- mp4ff_close(infile);
- free(mp4cb);
- fclose(mp4File);
+ mp4read_close();
return 0;
}
- numSamples = mp4ff_num_samples(infile, track);
-
startSampleId = 0;
- if (seek_to > 0.1) {
- int32_t sample = mp4ff_find_sample(infile, track, (int64_t)(seek_to * timescale), NULL);
- if (sample > 0 && sample < numSamples)
- startSampleId = sample;
- }
+ if (seek_to > 0.1)
+ startSampleId = (int64_t)(seek_to * mp4config.samplerate / framesize);
- for (sampleId = startSampleId; sampleId < numSamples; sampleId++)
+ mp4read_seek(startSampleId);
+ for (sampleId = startSampleId; sampleId < mp4config.frame.ents; sampleId++)
{
int rc;
long dur;
@@ -986,24 +898,11 @@
unsigned int sample_count;
unsigned int delay = 0;
- /* get acces unit from MP4 file */
- buffer = NULL;
- buffer_size = 0;
+ if (mp4read_frame())
+ break;
- dur = mp4ff_get_sample_duration(infile, track, sampleId);
- rc = mp4ff_read_sample(infile, track, sampleId, &buffer, &buffer_size);
- if (rc == 0)
- {
- faad_fprintf(stderr, "Reading from MP4 file failed.\n");
- NeAACDecClose(hDecoder);
- mp4ff_close(infile);
- free(mp4cb);
- fclose(mp4File);
- return 1;
- }
+ sample_buffer = NeAACDecDecode(hDecoder, &frameInfo, mp4config.bitbuf.data, mp4config.bitbuf.size);
- sample_buffer = NeAACDecDecode(hDecoder, &frameInfo, buffer, buffer_size);
-
if (adts_out == 1)
{
adtsData = MakeAdtsHeader(&adtsDataSize, &frameInfo, 0);
@@ -1011,32 +910,30 @@
/* write the adts header */
fwrite(adtsData, 1, adtsDataSize, adtsFile);
- fwrite(buffer, 1, frameInfo.bytesconsumed, adtsFile);
+ fwrite(mp4config.bitbuf.data, 1, frameInfo.bytesconsumed, adtsFile);
}
- if (buffer) free(buffer);
+ dur = frameInfo.samples / frameInfo.channels;
+ decoded += dur;
- if (!noGapless)
+ if (decoded > mp4config.samples)
+ dur += mp4config.samples - decoded;
+
+ if (dur > framesize)
{
- if (sampleId == 0) dur = 0;
+ faad_fprintf(stderr, "Warning: excess frame detected in MP4 file.\n");
+ dur = framesize;
+ }
- if (useAacLength || (timescale != samplerate)) {
+ if (!noGapless)
+ {
+ if (useAacLength || (mp4config.samplerate != samplerate)) {
sample_count = frameInfo.samples;
} else {
sample_count = (unsigned int)(dur * frameInfo.channels);
if (sample_count > frameInfo.samples)
sample_count = frameInfo.samples;
-
- if (!useAacLength && !initial && (sampleId < numSamples/2) && (sample_count != frameInfo.samples))
- {
- faad_fprintf(stderr, "MP4 seems to have incorrect frame duration, using values from AAC data.\n");
- useAacLength = 1;
- sample_count = frameInfo.samples;
- }
}
-
- if (initial && (sample_count < framesize*frameInfo.channels) && (frameInfo.samples > sample_count))
- delay = frameInfo.samples - sample_count;
} else {
sample_count = frameInfo.samples;
}
@@ -1064,9 +961,7 @@
if (aufile == NULL)
{
NeAACDecClose(hDecoder);
- mp4ff_close(infile);
- free(mp4cb);
- fclose(mp4File);
+ mp4read_close();
return 0;
}
}
@@ -1073,9 +968,7 @@
first_time = 0;
}
- if (sample_count > 0) initial = 0;
-
- percent = min((int)(sampleId*100)/numSamples, 100);
+ percent = min((int)(sampleId*100)/mp4config.frame.ents, 100);
if (percent > old_percent)
{
old_percent = percent;
@@ -1106,13 +999,10 @@
fclose(adtsFile);
}
- mp4ff_close(infile);
+ mp4read_close();
if (!first_time && !adts_out)
close_audio_file(aufile);
-
- free(mp4cb);
- fclose(mp4File);
return frameInfo.error;
}
--- /dev/null
+++ b/frontend/mp4read.c
@@ -1,0 +1,1004 @@
+/****************************************************************************
+ 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/>.
+****************************************************************************/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <time.h>
+#include <limits.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 struct
+{
+ uint16_t opcode;
+ void *data;
+} creator_t;
+
+
+mp4config_t mp4config = { 0 };
+
+static FILE *g_fin = NULL;
+
+static inline uint32_t bswap32(uint32_t u32)
+{
+#ifndef WORDS_BIGENDIAN
+ return __builtin_bswap32(u32);
+#endif
+}
+
+static inline uint16_t bswap16(uint16_t u16)
+{
+#ifndef WORDS_BIGENDIAN
+ return __builtin_bswap16(u16);
+#endif
+}
+
+enum {ERR_OK = 0, ERR_FAIL = -1, ERR_UNSUPPORTED = -2};
+
+static int datain(void *data, int size)
+{
+ if (fread(data, 1, size, g_fin) != size)
+ return ERR_FAIL;
+ return size;
+}
+
+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;
+ }
+
+ return size;
+}
+
+static uint32_t u32in(void)
+{
+ uint32_t u32;
+ datain(&u32, 4);
+ u32 = bswap32(u32);
+ return u32;
+}
+
+static uint16_t u16in(void)
+{
+ uint16_t u16;
+ datain(&u16, 2);
+ u16 = bswap16(u16);
+ return u16;
+}
+
+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, "unsuported, 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 int esdsin(int size)
+{
+ // descriptor tree:
+ // MP4ES_Descriptor
+ // MP4DecoderConfigDescriptor
+ // MP4DecSpecificInfoDescriptor
+ // MP4SLConfigDescriptor
+ enum
+ { TAG_ES = 3, TAG_DC = 4, TAG_DSI = 5, TAG_SLC = 6 };
+
+ 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;
+ }
+
+ // 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;
+}
+
+static int sttsin(int size)
+{
+ if (size < 16) //min stts size
+ return ERR_FAIL;
+
+ return size;
+}
+
+static int stszin(int size)
+{
+ int cnt;
+ uint32_t ofs;
+
+ // version/flags
+ u32in();
+ // Sample size
+ u32in();
+ // Number of entries
+ mp4config.frame.ents = u32in();
+ // fixme error checking
+ // fixme: check atom size
+ mp4config.frame.data = malloc(sizeof(*mp4config.frame.data)
+ * (mp4config.frame.ents + 1));
+ ofs = 0;
+ mp4config.frame.data[0] = ofs;
+ for (cnt = 0; cnt < mp4config.frame.ents; cnt++)
+ {
+ uint32_t fsize = u32in();
+
+ ofs += fsize;
+ if (mp4config.frame.maxsize < fsize)
+ mp4config.frame.maxsize = fsize;
+
+ mp4config.frame.data[cnt + 1] = ofs;
+ }
+
+ return size;
+}
+
+static int stcoin(int size)
+{
+ // version/flags
+ u32in();
+ // Number of entries
+ if (u32in() < 1)
+ return ERR_FAIL;
+ // first chunk offset
+ mp4config.mdatofs = u32in();
+ // ignore the rest
+
+ 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)
+{
+ // 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"},
+ {"Tille ", "\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;
+ }
+ spc = 17 - 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;
+ }
+ 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:
+ {
+ uint8_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->data);
+
+ // 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", (char *)g_atom->data);
+ 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->data, 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 = ((int (*)(int)) g_atom->data)(size - 8);
+ if (err < ERR_OK)
+ {
+ fseek(g_fin, apos + size, SEEK_SET);
+ return err;
+ }
+ g_atom++;
+ }
+ if (g_atom->opcode == ATOM_DESCENT)
+ {
+ long apos = 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;
+ }
+ fseek(g_fin, apos, 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;
+ int err;
+
+ static creator_t mvhd[] = {
+ {ATOM_NAME, "mvhd"},
+ {0}
+ };
+ static creator_t trak[] = {
+ {ATOM_NAME, "trak"},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "tkhd"},
+ {ATOM_NAME, "mdia"},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "mdhd"},
+ {ATOM_DATA, mdhdin},
+ {ATOM_NAME, "hdlr"},
+ {ATOM_DATA, hdlr1in},
+ {ATOM_NAME, "minf"},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "smhd"},
+ {ATOM_NAME, "dinf"},
+ {ATOM_NAME, "stbl"},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "stsd"},
+ {ATOM_DATA, stsdin},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "mp4a"},
+ {ATOM_DATA, mp4ain},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "esds"},
+ {ATOM_DATA, esdsin},
+ {ATOM_ASCENT},
+ {ATOM_ASCENT},
+ {ATOM_NAME, "stts"},
+ {ATOM_DATA, sttsin},
+ {ATOM_NAME, "stsc"},
+ {ATOM_NAME, "stsz"},
+ {ATOM_DATA, stszin},
+ {ATOM_NAME, "stco"},
+ {ATOM_DATA, stcoin},
+ {0}
+ };
+
+ g_atom = mvhd;
+ atomsize = sizemax + apos - ftell(g_fin);
+ if (parse(&atomsize) < 0)
+ 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)
+ return sizemax;
+ if (err != ERR_UNSUPPORTED)
+ return err;
+ //fprintf(stderr, "UNSUPP\n");
+ }
+
+ return sizemax;
+}
+
+
+static creator_t g_head[] = {
+ {ATOM_NAME, "ftyp"},
+ {ATOM_DATA, ftypin},
+ {0}
+};
+
+static creator_t g_moov[] = {
+ {ATOM_NAME, "moov"},
+ {ATOM_DATA, moovin},
+ //{ATOM_DESCENT},
+ //{ATOM_NAME, "mvhd"},
+ {0}
+};
+
+static creator_t g_meta1[] = {
+ {ATOM_NAME, "moov"},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "udta"},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "meta"},
+ {ATOM_DATA, metain},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "hdlr"},
+ {ATOM_DATA, hdlr2in},
+ {ATOM_NAME, "ilst"},
+ {ATOM_DATA, ilstin},
+ {0}
+};
+
+static creator_t g_meta2[] = {
+ {ATOM_NAME, "meta"},
+ {ATOM_DATA, metain},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "hdlr"},
+ {ATOM_DATA, hdlr2in},
+ {ATOM_NAME, "ilst"},
+ {ATOM_DATA, ilstin},
+ {0}
+};
+
+
+int mp4read_frame(void)
+{
+ if (mp4config.frame.current >= mp4config.frame.ents)
+ return ERR_FAIL;
+
+ mp4config.bitbuf.size = mp4config.frame.data[mp4config.frame.current + 1]
+ - mp4config.frame.data[mp4config.frame.current];
+
+ 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.data[mp4config.frame.current]);
+
+ return ERR_FAIL;
+ }
+
+ mp4config.frame.current++;
+
+ return ERR_OK;
+}
+
+int mp4read_seek(int framenum)
+{
+ if (framenum > mp4config.frame.ents)
+ return ERR_FAIL;
+ if (fseek(g_fin, mp4config.mdatofs + mp4config.frame.data[framenum], SEEK_SET))
+ return ERR_FAIL;
+
+ mp4config.frame.current = framenum;
+
+ return ERR_OK;
+}
+
+static void mp4info(void)
+{
+ fprintf(stderr, "Modification Time:\t\%s", 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, "Samples per frame:\t%d\n", mp4config.framesamples);
+ fprintf(stderr, "Frames:\t\t\t%d\n", mp4config.frame.ents);
+ fprintf(stderr, "ASC size:\t\t%d\n", mp4config.asc.size);
+ fprintf(stderr, "Duration:\t\t%.1f sec\n", (float)mp4config.samples/mp4config.samplerate);
+ fprintf(stderr, "Data offset/size:\t%x/%x\n", mp4config.mdatofs, mp4config.mdatsize);
+}
+
+int mp4read_close(void)
+{
+#define FREE(x) if(x){free(x);x=0;}
+ FREE(mp4config.frame.data);
+ FREE(mp4config.bitbuf.data);
+
+ return ERR_OK;
+}
+
+int mp4read_open(char *name)
+{
+ uint32_t atomsize;
+ int ret;
+
+ mp4read_close();
+
+ g_fin = 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
+ // fixme: error checking
+ mp4config.bitbuf.data = malloc(mp4config.frame.maxsize);
+
+ if (mp4config.verbose.header)
+ {
+ mp4info();
+ fprintf(stderr, "********************\n");
+ }
+
+ if (mp4config.verbose.tags)
+ {
+ fprintf(stderr, "/moov/meta:\n");
+ rewind(g_fin);
+ g_atom = g_meta1;
+ atomsize = INT_MAX;
+ ret = parse(&atomsize);
+ if (ret < 0)
+ {
+ fprintf(stderr, "/meta:\n");
+ rewind(g_fin);
+ g_atom = g_meta2;
+ atomsize = INT_MAX;
+ ret = parse(&atomsize);
+ }
+ }
+
+ return ERR_OK;
+err:
+ mp4read_close();
+ return ERR_FAIL;
+}
--- /dev/null
+++ b/frontend/mp4read.h
@@ -1,0 +1,66 @@
+/****************************************************************************
+ 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/>.
+****************************************************************************/
+
+#include <stdint.h>
+
+typedef struct
+{
+ uint32_t ctime, mtime;
+ uint32_t samplerate;
+ // total sound samples
+ uint32_t samples;
+ uint32_t channels;
+ // sample depth
+ uint32_t bits;
+ // buffer config
+ uint16_t buffersize;
+ uint32_t bitratemax;
+ uint32_t bitrateavg;
+ uint32_t framesamples;
+ struct
+ {
+ uint32_t *data;
+ uint32_t ents;
+ int current;
+ int maxsize;
+ } frame;
+ // AudioSpecificConfig data:
+ struct
+ {
+ uint8_t buf[10];
+ int size;
+ } asc;
+ uint32_t mdatofs;
+ uint32_t mdatsize;
+ struct {
+ int size;
+ uint8_t *data;
+ } bitbuf;
+ struct {
+ int header;
+ int tags;
+ } verbose;
+} mp4config_t;
+
+extern mp4config_t mp4config;
+
+int mp4read_open(char *name);
+int mp4read_seek(int framenum);
+int mp4read_frame(void);
+int mp4read_close(void);