ref: ddc184c244169848a27e0bf3b0dbf7a1d3e8fcc4
parent: 7bf78de938986d57c381f71f8db813daa95a2eca
author: knik <knik@users.sourceforge.net>
date: Fri Jul 21 10:27:24 EDT 2017
rename mp4atom -> mp4write
--- a/frontend/Makefile.am
+++ b/frontend/Makefile.am
@@ -1,7 +1,7 @@
bin_PROGRAMS = faac
man_MANS = ../docs/faac.1
-faac_SOURCES = main.c input.c mp4atom.c
+faac_SOURCES = main.c input.c mp4write.c
AM_CPPFLAGS = -I$(top_srcdir)/include
LDADD = $(top_builddir)/libfaac/libfaac.la -lm
--- a/frontend/main.c
+++ b/frontend/main.c
@@ -59,7 +59,7 @@
# include "getopt.c"
#endif
-#include "mp4atom.h"
+#include "mp4write.h"
#if !defined(HAVE_STRCASECMP) && !defined(_WIN32)
# define strcasecmp strcmp
--- a/frontend/mp4atom.c
+++ /dev/null
@@ -1,808 +1,0 @@
-/****************************************************************************
- MP4 output 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>
-#ifndef WORDS_BIGENDIAN
-//#include <byteswap.h>
-#endif
-#include <string.h>
-#include <time.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;
-
-#include "mp4atom.h"
-
-mp4config_t mp4config = { 0 };
-
-static FILE *g_fout = NULL;
-
-static inline uint32_t bswap32(uint32_t u32)
-{
-#ifndef WORDS_BIGENDIAN
- //return __bswap_32(u32);
- return __builtin_bswap32(u32);
-#endif
-}
-
-static inline uint16_t bswap16(uint16_t u16)
-{
-#ifndef WORDS_BIGENDIAN
- //return __bswap_16(u16);
- return __builtin_bswap16(u16);
-#endif
-}
-
-static int dataout(const void *data, int size)
-{
- if (fwrite(data, 1, size, g_fout) != size)
- {
- perror("mp4out");
- return -1;
- }
- return size;
-}
-
-static int stringout(const char *txt)
-{
- return dataout(txt, strlen(txt));
-}
-
-static int u32out(uint32_t u32)
-{
- u32 = bswap32(u32);
- return dataout(&u32, 4);
-}
-
-static int u16out(uint16_t u16)
-{
- u16 = bswap16(u16);
- return dataout(&u16, 2);
-}
-
-static int u8out(uint8_t u8)
-{
- if (fwrite(&u8, 1, 1, g_fout) != 1)
- {
- perror("mp4 out");
- return 0;
- }
- return 1;
-}
-
-static int ftypout(void)
-{
- int size = 0;
-
- size += stringout("isom");
- size += u32out(0);
- size += stringout("M4A ");
- size += stringout("mp42");
- size += stringout("mp41");
-
- return size;
-}
-
-enum
-{ SECSINDAY = 24 * 60 * 60 };
-static time_t mp4time(void)
-{
- int y;
- time_t t;
-
- time(&t);
-
- // add some time 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 t;
-}
-
-static int mvhdout(void)
-{
- int size = 0;
- int cnt;
-
- // version
- size += u8out(0);
- // flags
- size += u8out(0);
- size += u16out(0);
- // Creation time
- size += u32out(mp4time());
- // Modification time
- size += u32out(mp4time());
- // Time scale (samplerate)
- size += u32out(mp4config.samplerate);
- // Duration
- size += u32out(mp4config.samples);
- // rate
- size += u32out(0x00010000);
- // volume
- size += u16out(0x0100);
- // reserved
- size += u16out(0);
- size += u32out(0);
- size += u32out(0);
- // matrix
- size += u32out(0x00010000);
- size += u32out(0);
- size += u32out(0);
- size += u32out(0);
- size += u32out(0x00010000);
- size += u32out(0);
- size += u32out(0);
- size += u32out(0);
- size += u32out(0x40000000);
-
- for (cnt = 0; cnt < 6; cnt++)
- size += u32out(0);
- // Next track ID
- size += u32out(2);
-
- return size;
-};
-
-static int tkhdout(void)
-{
- int size = 0;
-
- // version
- size += u8out(0);
- // flags
- // bits 8-23
- size += u16out(0);
- // bits 0-7
- size += u8out(1 /*track enabled */ );
- // Creation time
- size += u32out(mp4time());
- // Modification time
- size += u32out(mp4time());
- // Track ID
- size += u32out(1);
- // Reserved
- size += u32out(0);
- // Duration
- size += u32out(mp4config.samples);
- // Reserved
- size += u32out(0);
- size += u32out(0);
- // Layer
- size += u16out(0);
- // Alternate group
- size += u16out(0);
- // Volume
- size += u16out(0x0100);
- // Reserved
- size += u16out(0);
- // matrix
- size += u32out(0x00010000);
- size += u32out(0);
- size += u32out(0);
- size += u32out(0);
- size += u32out(0x00010000);
- size += u32out(0);
- size += u32out(0);
- size += u32out(0);
- size += u32out(0x40000000);
-
- // Track width
- size += u32out(0);
- // Track height
- size += u32out(0);
-
- return size;
-};
-
-static int mdhdout(void)
-{
- int size = 0;
-
- // version/flags
- size += u32out(0);
- // Creation time
- size += u32out(mp4time());
- // Modification time
- size += u32out(mp4time());
- // Time scale
- size += u32out(mp4config.samplerate);
- // Duration
- size += u32out(mp4config.samples);
- // Language
- size += u16out(0 /*0=English */ );
- // pre_defined
- size += u16out(0);
-
- return size;
-};
-
-
-static int hdlr1out(void)
-{
- int size = 0;
-
- // version/flags
- size += u32out(0);
- // pre_defined
- size += u32out(0);
- // Component subtype
- size += stringout("soun");
- // reserved
- size += u32out(0);
- size += u32out(0);
- size += u32out(0);
- // name
- // null terminate
- size += u8out(0);
-
- return size;
-};
-
-static int smhdout(void)
-{
- int size = 0;
-
- // version/flags
- size += u32out(0);
- // Balance
- size += u16out(0 /*center */ );
- // Reserved
- size += u16out(0);
-
- return size;
-};
-
-static int drefout(void)
-{
- int size = 0;
-
- // version/flags
- size += u32out(0);
- // Number of entries
- size += u32out(1 /*url reference */ );
-
- return size;
-};
-
-static int urlout(void)
-{
- int size = 0;
-
- size += u32out(1);
-
- return size;
-};
-
-static int stsdout(void)
-{
- int size = 0;
-
- // version/flags
- size += u32out(0);
- // Number of entries(one 'mp4a')
- size += u32out(1);
-
- return size;
-};
-
-static int mp4aout(void)
-{
- int size = 0;
- // Reserved (6 bytes)
- size += u32out(0);
- size += u16out(0);
- // Data reference index
- size += u16out(1);
- // Version
- size += u16out(0);
- // Revision level
- size += u16out(0);
- // Vendor
- size += u32out(0);
- // Number of channels
- size += u16out(mp4config.channels);
- // Sample size (bits)
- size += u16out(mp4config.bits);
- // Compression ID
- size += u16out(0);
- // Packet size
- size += u16out(0);
- // Sample rate (16.16)
- // rate integer part
- size += u16out(mp4config.samplerate);
- // rate reminder part
- size += u16out(0);
-
- return size;
-}
-
-static int esdsout(void)
-{
- int size = 0;
- // descriptor definitions:
- // systems/mp4_file_format/libisomediafile/src/MP4Descriptors.h
- // systems/mp4_file_format/libisomediafile/src/MP4Descriptors.c
- //
- // descriptor tree:
- // MP4ES_Descriptor
- // MP4DecoderConfigDescriptor
- // MP4DecSpecificInfoDescriptor
- // MP4SLConfigDescriptor
- struct
- {
- int es;
- int dc; // DecoderConfig
- int dsi; // DecSpecificInfo
- int sl; // SLConfig
- } dsize;
-
- enum
- { TAG_ES = 3, TAG_DC = 4, TAG_DSI = 5, TAG_SLC = 6 };
-
- // calc sizes
-#define DESCSIZE(x) (x + 2/*.tag+.size*/)
- dsize.sl = 1;
- dsize.dsi = mp4config.asc.size;
- dsize.dc = 13 + DESCSIZE(dsize.dsi);
- dsize.es = 3 + DESCSIZE(dsize.dc) + DESCSIZE(dsize.sl);
-
- // output esds atom data
- // version/flags ?
- size += u32out(0);
- // mp4es
- size += u8out(TAG_ES);
- size += u8out(dsize.es);
- // ESID
- size += u16out(0);
- // flags(url(bit 6); ocr(5); streamPriority (0-4)):
- size += u8out(0);
-
- size += u8out(TAG_DC);
- size += u8out(dsize.dc);
- size += u8out(0x40 /*MPEG-4 audio */ );
- // DC flags: upstream(bit 1); streamType(2-7)
- // this field is typically 0x15 but I think it
- // could store "Object Type ID", why not
- size += u8out(2 /*AAC LC*/ << 2);
- // buffer size (24 bits)
- size += u16out(mp4config.buffersize >> 8);
- size += u8out(mp4config.buffersize && 0xff);
- // bitrate
- size += u32out(mp4config.bitratemax);
- size += u32out(mp4config.bitrateavg);
-
- size += u8out(TAG_DSI);
- size += u8out(dsize.dsi);
- // AudioSpecificConfig
- size += dataout(mp4config.asc.data, mp4config.asc.size);
-
- size += u8out(TAG_SLC);
- size += u8out(dsize.sl);
- // "predefined" (no idea)
- size += u8out(2);
-
- return size;
-}
-
-static int sttsout(void)
-{
- int size = 0;
-
- // version/flags
- size += u32out(0);
- // Number of entries
- size += u32out(1);
- // only one entry
- // Sample count (number of frames)
- size += u32out(mp4config.frame.ents);
- // Sample duration (samples per frame)
- size += u32out(mp4config.framesamples);
-
- return size;
-}
-
-static int stszout(void)
-{
- int size = 0;
- int cnt;
-
- // version/flags
- size += u32out(0);
- // Sample size
- size += u32out(0 /*i.e. variable size */ );
- // Number of entries
- if (!mp4config.frame.ents)
- return size;
- if (!mp4config.frame.data)
- return size;
-
- size += u32out(mp4config.frame.ents);
- for (cnt = 0; cnt < mp4config.frame.ents; cnt++)
- size += u32out(mp4config.frame.data[cnt]);
-
- return size;
-}
-
-static int stscout(void)
-{
- int size = 0;
-
- // version/flags
- size += u32out(0);
- // Number of entries
- size += u32out(2);
- // 1st entry
- // first chunk
- size += u32out(1);
- // frames per chunks (256 for simplicity)
- size += u32out(0x100);
- // sample id
- size += u32out(1);
- // 2nd entry
- // last chunk
- size += u32out((mp4config.frame.ents >> 8) + 1);
- // frames in last chunk
- size += u32out(mp4config.frame.ents & 0xff);
-
- // sample id
- size += u32out(1);
-
- return size;
-}
-
-static int stcoout(void)
-{
- int size = 0;
- int chunks = (mp4config.frame.ents >> 8) + 1;
- int cnt;
- uint32_t ofs = mp4config.mdatofs;
-
- // version/flags
- size += u32out(0);
- // Number of entries
- size += u32out(chunks);
- // Chunk offset table
- if (!mp4config.frame.ents)
- return size;
- if (!mp4config.frame.data)
- return size;
- for (cnt = 0; cnt < mp4config.frame.ents; cnt++)
- {
- if (!(cnt & 0xff))
- size += u32out(ofs);
- ofs += mp4config.frame.data[cnt];
- }
-
- return size;
-}
-
-static int tagtxt(char *tagname, const char *tagtxt)
-{
- int txtsize = strlen(tagtxt);
- int size = 0;
- int datasize = txtsize + 16;
-
- size += u32out(datasize + 8);
- size += dataout(tagname, 4);
- size += u32out(datasize);
- size += dataout("data", 4);
- size += u32out(1); // data type text
- size += u32out(0);
- size += dataout(tagtxt, txtsize);
-
- return size;
-}
-
-static int tagu32(char *tagname, int n /*number of stored fields*/)
-{
- int numsize = n * 4;
- int size = 0;
- int datasize = numsize + 16;
-
- size += u32out(datasize + 8);
- size += dataout(tagname, 4);
- size += u32out(datasize);
- size += dataout("data", 4);
- size += u32out(0); // data type uint32
- size += u32out(0);
-
- return size;
-}
-
-static int metaout(void)
-{
- int size = 0;
-
- // version/flags
- size += u32out(0);
-
- return size;
-}
-
-static int hdlr2out(void)
-{
- int size = 0;
-
- // version/flags
- size += u32out(0);
- // Predefined
- size += u32out(0);
- // Handler type
- size += stringout("mdir");
- size += stringout("appl");
- // Reserved
- size += u32out(0);
- size += u32out(0);
- // null terminator
- size += u8out(0);
-
- return size;
-};
-
-static int ilstout(void)
-{
- int size = 0;
-
- size += tagtxt("\xa9" "too", mp4config.tag.encoder);
- if (mp4config.tag.artist)
- size += tagtxt("\xa9" "ART", mp4config.tag.artist);
- if (mp4config.tag.composer)
- size += tagtxt("\xa9" "wrt", mp4config.tag.composer);
- if (mp4config.tag.title)
- size += tagtxt("\xa9" "nam", mp4config.tag.title);
- if (mp4config.tag.genre)
- size += tagtxt("gnre", mp4config.tag.genre);
- if (mp4config.tag.album)
- size += tagtxt("\xa9" "alb", mp4config.tag.album);
- if (mp4config.tag.compilation)
- {
- size += tagu32("cpil", 1);
- size += u32out(mp4config.tag.compilation);
- }
- if (mp4config.tag.trackno)
- {
- size += tagu32("trkn", 1);
- size += u32out(mp4config.tag.trackno);
- }
- if (mp4config.tag.discno)
- {
- size += tagu32("disk", 1);
- size += u32out(mp4config.tag.discno);
- }
- if (mp4config.tag.year)
- size += tagtxt("\xa9" "day", mp4config.tag.year);
-#if 0
- if (mp4config.tag.cover)
- size += tagtxt("\xa9" "covr", mp4config.tag.cover);
-#endif
- if (mp4config.tag.comment)
- size += tagtxt("\xa9" "cmt", mp4config.tag.comment);
-
- return size;
-};
-
-static creator_t g_head[] = {
- {ATOM_NAME, "ftyp"},
- {ATOM_DATA, ftypout},
- {ATOM_NAME, "free"},
- {ATOM_NAME, "mdat"},
- {0}
-};
-
-static creator_t g_tail[] = {
- {ATOM_NAME, "moov"},
- {ATOM_DESCENT},
- {ATOM_NAME, "mvhd"},
- {ATOM_DATA, mvhdout},
- {ATOM_NAME, "trak"},
- {ATOM_DESCENT},
- {ATOM_NAME, "tkhd"},
- {ATOM_DATA, tkhdout},
- {ATOM_NAME, "mdia"},
- {ATOM_DESCENT},
- {ATOM_NAME, "mdhd"},
- {ATOM_DATA, mdhdout},
- {ATOM_NAME, "hdlr"},
- {ATOM_DATA, hdlr1out},
- {ATOM_NAME, "minf"},
- {ATOM_DESCENT},
- {ATOM_NAME, "smhd"},
- {ATOM_DATA, smhdout},
- {ATOM_NAME, "dinf"},
- {ATOM_DESCENT},
- {ATOM_NAME, "dref"},
- {ATOM_DATA, drefout},
- {ATOM_DESCENT},
- {ATOM_NAME, "url "},
- {ATOM_DATA, urlout},
- {ATOM_ASCENT},
- {ATOM_ASCENT},
- {ATOM_NAME, "stbl"},
- {ATOM_DESCENT},
- {ATOM_NAME, "stsd"},
- {ATOM_DATA, stsdout},
- {ATOM_DESCENT},
- {ATOM_NAME, "mp4a"},
- {ATOM_DATA, mp4aout},
- {ATOM_DESCENT},
- {ATOM_NAME, "esds"},
- {ATOM_DATA, esdsout},
- {ATOM_ASCENT},
- {ATOM_ASCENT},
- {ATOM_NAME, "stts"},
- {ATOM_DATA, sttsout},
- {ATOM_NAME, "stsc"},
- {ATOM_DATA, stscout},
- {ATOM_NAME, "stsz"},
- {ATOM_DATA, stszout},
- {ATOM_NAME, "stco"},
- {ATOM_DATA, stcoout},
- {ATOM_ASCENT},
- {ATOM_ASCENT},
- {ATOM_ASCENT},
- {ATOM_ASCENT},
- {ATOM_NAME, "udta"},
- {ATOM_DESCENT},
- {ATOM_NAME, "meta"},
- {ATOM_DATA, metaout},
- {ATOM_DESCENT},
- {ATOM_NAME, "hdlr"},
- {ATOM_DATA, hdlr2out},
- {ATOM_NAME, "ilst"},
- {ATOM_DATA, ilstout},
- {0}
-};
-
-static creator_t *g_atom = 0;
-static int create(void)
-{
- long apos = ftell(g_fout);;
- int size;
-
- size = u32out(8);
- size += dataout(g_atom->data, 4);
-
- g_atom++;
- if (g_atom->opcode == ATOM_DATA)
- {
- size += ((int (*)(void)) g_atom->data) ();
- g_atom++;
- }
- if (g_atom->opcode == ATOM_DESCENT)
- {
- g_atom++;
- while (g_atom->opcode != ATOM_STOP)
- {
- if (g_atom->opcode == ATOM_ASCENT)
- {
- g_atom++;
- break;
- }
- size += create();
- }
- }
-
- fseek(g_fout, apos, SEEK_SET);
- u32out(size);
- fseek(g_fout, apos + size, SEEK_SET);
-
- return size;
-}
-
-enum {BUFSTEP = 0x4000};
-int mp4atom_frame(uint8_t * buf, int size, int samples)
-{
- if (mp4config.framesamples <= samples)
- {
- int bitrate = 8.0 * size * mp4config.samplerate / samples;
-
- if (mp4config.bitratemax < bitrate)
- mp4config.bitratemax = bitrate;
-
- mp4config.framesamples = samples;
- }
- if (mp4config.buffersize < size)
- mp4config.buffersize = size;
- mp4config.samples += samples;
- mp4config.mdatsize += dataout(buf, size);
-
- if (((mp4config.frame.ents + 1) * sizeof(*(mp4config.frame.data)))
- > mp4config.frame.bufsize)
- {
- mp4config.frame.bufsize += BUFSTEP;
- mp4config.frame.data = realloc(mp4config.frame.data,
- mp4config.frame.bufsize);
- }
- mp4config.frame.data[mp4config.frame.ents++] = size;
-
- return 0;
-}
-
-int mp4atom_close(void)
-{
- if (g_fout)
- {
- fseek(g_fout, mp4config.mdatofs - 8, SEEK_SET);
- u32out(mp4config.mdatsize + 8);
- fclose(g_fout);
- g_fout = 0;
- }
- if (mp4config.frame.data)
- {
- free(mp4config.frame.data);
- mp4config.frame.data = 0;
- }
- return 0;
-}
-
-int mp4atom_open(char *name)
-{
- mp4atom_close();
-
- g_fout = fopen(name, "wb");
- if (!g_fout)
- return 1;
-
- mp4config.mdatsize = 0;
- mp4config.frame.bufsize = BUFSTEP;
- mp4config.frame.data = malloc(mp4config.frame.bufsize);
-
- return 0;
-}
-
-
-int mp4atom_head(void)
-{
- g_atom = g_head;
- while (g_atom->opcode != ATOM_STOP)
- create();
- mp4config.mdatofs = ftell(g_fout);
-
- return 0;
-}
-
-int mp4atom_tail(void)
-{
- mp4config.bitrateavg = 8.0 * mp4config.mdatsize
- * mp4config.samplerate / mp4config.samples;
-
- g_atom = g_tail;
- while (g_atom->opcode != ATOM_STOP)
- create();
-
- return 0;
-}
--- a/frontend/mp4atom.h
+++ /dev/null
@@ -1,74 +1,0 @@
-/****************************************************************************
- MP4 output 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 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
- {
- uint16_t *data;
- uint32_t ents;
- uint32_t bufsize;
- } frame;
- // AudioSpecificConfig data:
- struct
- {
- uint8_t *data;
- unsigned long size;
- } asc;
- uint32_t mdatofs;
- uint32_t mdatsize;
-
- struct
- {
- // meta fields
- const char *encoder;
- const char *artist;
- const char *composer;
- const char *title;
- const char *genre;
- const char *album;
- uint8_t compilation;
- uint32_t trackno;
- uint32_t discno;
- const char *year;
- const char *cover; // cover filename
- const char *comment;
- } tag;
-} mp4config_t;
-
-extern mp4config_t mp4config;
-
-int mp4atom_open(char *name);
-int mp4atom_head(void);
-int mp4atom_tail(void);
-int mp4atom_frame(uint8_t * bitbuf, int bytesWritten, int frame_samples);
-int mp4atom_close(void);
--- /dev/null
+++ b/frontend/mp4write.c
@@ -1,0 +1,808 @@
+/****************************************************************************
+ MP4 output 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>
+#ifndef WORDS_BIGENDIAN
+//#include <byteswap.h>
+#endif
+#include <string.h>
+#include <time.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;
+
+#include "mp4write.h"
+
+mp4config_t mp4config = { 0 };
+
+static FILE *g_fout = NULL;
+
+static inline uint32_t bswap32(uint32_t u32)
+{
+#ifndef WORDS_BIGENDIAN
+ //return __bswap_32(u32);
+ return __builtin_bswap32(u32);
+#endif
+}
+
+static inline uint16_t bswap16(uint16_t u16)
+{
+#ifndef WORDS_BIGENDIAN
+ //return __bswap_16(u16);
+ return __builtin_bswap16(u16);
+#endif
+}
+
+static int dataout(const void *data, int size)
+{
+ if (fwrite(data, 1, size, g_fout) != size)
+ {
+ perror("mp4out");
+ return -1;
+ }
+ return size;
+}
+
+static int stringout(const char *txt)
+{
+ return dataout(txt, strlen(txt));
+}
+
+static int u32out(uint32_t u32)
+{
+ u32 = bswap32(u32);
+ return dataout(&u32, 4);
+}
+
+static int u16out(uint16_t u16)
+{
+ u16 = bswap16(u16);
+ return dataout(&u16, 2);
+}
+
+static int u8out(uint8_t u8)
+{
+ if (fwrite(&u8, 1, 1, g_fout) != 1)
+ {
+ perror("mp4 out");
+ return 0;
+ }
+ return 1;
+}
+
+static int ftypout(void)
+{
+ int size = 0;
+
+ size += stringout("isom");
+ size += u32out(0);
+ size += stringout("M4A ");
+ size += stringout("mp42");
+ size += stringout("mp41");
+
+ return size;
+}
+
+enum
+{ SECSINDAY = 24 * 60 * 60 };
+static time_t mp4time(void)
+{
+ int y;
+ time_t t;
+
+ time(&t);
+
+ // add some time 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 t;
+}
+
+static int mvhdout(void)
+{
+ int size = 0;
+ int cnt;
+
+ // version
+ size += u8out(0);
+ // flags
+ size += u8out(0);
+ size += u16out(0);
+ // Creation time
+ size += u32out(mp4time());
+ // Modification time
+ size += u32out(mp4time());
+ // Time scale (samplerate)
+ size += u32out(mp4config.samplerate);
+ // Duration
+ size += u32out(mp4config.samples);
+ // rate
+ size += u32out(0x00010000);
+ // volume
+ size += u16out(0x0100);
+ // reserved
+ size += u16out(0);
+ size += u32out(0);
+ size += u32out(0);
+ // matrix
+ size += u32out(0x00010000);
+ size += u32out(0);
+ size += u32out(0);
+ size += u32out(0);
+ size += u32out(0x00010000);
+ size += u32out(0);
+ size += u32out(0);
+ size += u32out(0);
+ size += u32out(0x40000000);
+
+ for (cnt = 0; cnt < 6; cnt++)
+ size += u32out(0);
+ // Next track ID
+ size += u32out(2);
+
+ return size;
+};
+
+static int tkhdout(void)
+{
+ int size = 0;
+
+ // version
+ size += u8out(0);
+ // flags
+ // bits 8-23
+ size += u16out(0);
+ // bits 0-7
+ size += u8out(1 /*track enabled */ );
+ // Creation time
+ size += u32out(mp4time());
+ // Modification time
+ size += u32out(mp4time());
+ // Track ID
+ size += u32out(1);
+ // Reserved
+ size += u32out(0);
+ // Duration
+ size += u32out(mp4config.samples);
+ // Reserved
+ size += u32out(0);
+ size += u32out(0);
+ // Layer
+ size += u16out(0);
+ // Alternate group
+ size += u16out(0);
+ // Volume
+ size += u16out(0x0100);
+ // Reserved
+ size += u16out(0);
+ // matrix
+ size += u32out(0x00010000);
+ size += u32out(0);
+ size += u32out(0);
+ size += u32out(0);
+ size += u32out(0x00010000);
+ size += u32out(0);
+ size += u32out(0);
+ size += u32out(0);
+ size += u32out(0x40000000);
+
+ // Track width
+ size += u32out(0);
+ // Track height
+ size += u32out(0);
+
+ return size;
+};
+
+static int mdhdout(void)
+{
+ int size = 0;
+
+ // version/flags
+ size += u32out(0);
+ // Creation time
+ size += u32out(mp4time());
+ // Modification time
+ size += u32out(mp4time());
+ // Time scale
+ size += u32out(mp4config.samplerate);
+ // Duration
+ size += u32out(mp4config.samples);
+ // Language
+ size += u16out(0 /*0=English */ );
+ // pre_defined
+ size += u16out(0);
+
+ return size;
+};
+
+
+static int hdlr1out(void)
+{
+ int size = 0;
+
+ // version/flags
+ size += u32out(0);
+ // pre_defined
+ size += u32out(0);
+ // Component subtype
+ size += stringout("soun");
+ // reserved
+ size += u32out(0);
+ size += u32out(0);
+ size += u32out(0);
+ // name
+ // null terminate
+ size += u8out(0);
+
+ return size;
+};
+
+static int smhdout(void)
+{
+ int size = 0;
+
+ // version/flags
+ size += u32out(0);
+ // Balance
+ size += u16out(0 /*center */ );
+ // Reserved
+ size += u16out(0);
+
+ return size;
+};
+
+static int drefout(void)
+{
+ int size = 0;
+
+ // version/flags
+ size += u32out(0);
+ // Number of entries
+ size += u32out(1 /*url reference */ );
+
+ return size;
+};
+
+static int urlout(void)
+{
+ int size = 0;
+
+ size += u32out(1);
+
+ return size;
+};
+
+static int stsdout(void)
+{
+ int size = 0;
+
+ // version/flags
+ size += u32out(0);
+ // Number of entries(one 'mp4a')
+ size += u32out(1);
+
+ return size;
+};
+
+static int mp4aout(void)
+{
+ int size = 0;
+ // Reserved (6 bytes)
+ size += u32out(0);
+ size += u16out(0);
+ // Data reference index
+ size += u16out(1);
+ // Version
+ size += u16out(0);
+ // Revision level
+ size += u16out(0);
+ // Vendor
+ size += u32out(0);
+ // Number of channels
+ size += u16out(mp4config.channels);
+ // Sample size (bits)
+ size += u16out(mp4config.bits);
+ // Compression ID
+ size += u16out(0);
+ // Packet size
+ size += u16out(0);
+ // Sample rate (16.16)
+ // rate integer part
+ size += u16out(mp4config.samplerate);
+ // rate reminder part
+ size += u16out(0);
+
+ return size;
+}
+
+static int esdsout(void)
+{
+ int size = 0;
+ // descriptor definitions:
+ // systems/mp4_file_format/libisomediafile/src/MP4Descriptors.h
+ // systems/mp4_file_format/libisomediafile/src/MP4Descriptors.c
+ //
+ // descriptor tree:
+ // MP4ES_Descriptor
+ // MP4DecoderConfigDescriptor
+ // MP4DecSpecificInfoDescriptor
+ // MP4SLConfigDescriptor
+ struct
+ {
+ int es;
+ int dc; // DecoderConfig
+ int dsi; // DecSpecificInfo
+ int sl; // SLConfig
+ } dsize;
+
+ enum
+ { TAG_ES = 3, TAG_DC = 4, TAG_DSI = 5, TAG_SLC = 6 };
+
+ // calc sizes
+#define DESCSIZE(x) (x + 2/*.tag+.size*/)
+ dsize.sl = 1;
+ dsize.dsi = mp4config.asc.size;
+ dsize.dc = 13 + DESCSIZE(dsize.dsi);
+ dsize.es = 3 + DESCSIZE(dsize.dc) + DESCSIZE(dsize.sl);
+
+ // output esds atom data
+ // version/flags ?
+ size += u32out(0);
+ // mp4es
+ size += u8out(TAG_ES);
+ size += u8out(dsize.es);
+ // ESID
+ size += u16out(0);
+ // flags(url(bit 6); ocr(5); streamPriority (0-4)):
+ size += u8out(0);
+
+ size += u8out(TAG_DC);
+ size += u8out(dsize.dc);
+ size += u8out(0x40 /*MPEG-4 audio */ );
+ // DC flags: upstream(bit 1); streamType(2-7)
+ // this field is typically 0x15 but I think it
+ // could store "Object Type ID", why not
+ size += u8out(2 /*AAC LC*/ << 2);
+ // buffer size (24 bits)
+ size += u16out(mp4config.buffersize >> 8);
+ size += u8out(mp4config.buffersize && 0xff);
+ // bitrate
+ size += u32out(mp4config.bitratemax);
+ size += u32out(mp4config.bitrateavg);
+
+ size += u8out(TAG_DSI);
+ size += u8out(dsize.dsi);
+ // AudioSpecificConfig
+ size += dataout(mp4config.asc.data, mp4config.asc.size);
+
+ size += u8out(TAG_SLC);
+ size += u8out(dsize.sl);
+ // "predefined" (no idea)
+ size += u8out(2);
+
+ return size;
+}
+
+static int sttsout(void)
+{
+ int size = 0;
+
+ // version/flags
+ size += u32out(0);
+ // Number of entries
+ size += u32out(1);
+ // only one entry
+ // Sample count (number of frames)
+ size += u32out(mp4config.frame.ents);
+ // Sample duration (samples per frame)
+ size += u32out(mp4config.framesamples);
+
+ return size;
+}
+
+static int stszout(void)
+{
+ int size = 0;
+ int cnt;
+
+ // version/flags
+ size += u32out(0);
+ // Sample size
+ size += u32out(0 /*i.e. variable size */ );
+ // Number of entries
+ if (!mp4config.frame.ents)
+ return size;
+ if (!mp4config.frame.data)
+ return size;
+
+ size += u32out(mp4config.frame.ents);
+ for (cnt = 0; cnt < mp4config.frame.ents; cnt++)
+ size += u32out(mp4config.frame.data[cnt]);
+
+ return size;
+}
+
+static int stscout(void)
+{
+ int size = 0;
+
+ // version/flags
+ size += u32out(0);
+ // Number of entries
+ size += u32out(2);
+ // 1st entry
+ // first chunk
+ size += u32out(1);
+ // frames per chunks (256 for simplicity)
+ size += u32out(0x100);
+ // sample id
+ size += u32out(1);
+ // 2nd entry
+ // last chunk
+ size += u32out((mp4config.frame.ents >> 8) + 1);
+ // frames in last chunk
+ size += u32out(mp4config.frame.ents & 0xff);
+
+ // sample id
+ size += u32out(1);
+
+ return size;
+}
+
+static int stcoout(void)
+{
+ int size = 0;
+ int chunks = (mp4config.frame.ents >> 8) + 1;
+ int cnt;
+ uint32_t ofs = mp4config.mdatofs;
+
+ // version/flags
+ size += u32out(0);
+ // Number of entries
+ size += u32out(chunks);
+ // Chunk offset table
+ if (!mp4config.frame.ents)
+ return size;
+ if (!mp4config.frame.data)
+ return size;
+ for (cnt = 0; cnt < mp4config.frame.ents; cnt++)
+ {
+ if (!(cnt & 0xff))
+ size += u32out(ofs);
+ ofs += mp4config.frame.data[cnt];
+ }
+
+ return size;
+}
+
+static int tagtxt(char *tagname, const char *tagtxt)
+{
+ int txtsize = strlen(tagtxt);
+ int size = 0;
+ int datasize = txtsize + 16;
+
+ size += u32out(datasize + 8);
+ size += dataout(tagname, 4);
+ size += u32out(datasize);
+ size += dataout("data", 4);
+ size += u32out(1); // data type text
+ size += u32out(0);
+ size += dataout(tagtxt, txtsize);
+
+ return size;
+}
+
+static int tagu32(char *tagname, int n /*number of stored fields*/)
+{
+ int numsize = n * 4;
+ int size = 0;
+ int datasize = numsize + 16;
+
+ size += u32out(datasize + 8);
+ size += dataout(tagname, 4);
+ size += u32out(datasize);
+ size += dataout("data", 4);
+ size += u32out(0); // data type uint32
+ size += u32out(0);
+
+ return size;
+}
+
+static int metaout(void)
+{
+ int size = 0;
+
+ // version/flags
+ size += u32out(0);
+
+ return size;
+}
+
+static int hdlr2out(void)
+{
+ int size = 0;
+
+ // version/flags
+ size += u32out(0);
+ // Predefined
+ size += u32out(0);
+ // Handler type
+ size += stringout("mdir");
+ size += stringout("appl");
+ // Reserved
+ size += u32out(0);
+ size += u32out(0);
+ // null terminator
+ size += u8out(0);
+
+ return size;
+};
+
+static int ilstout(void)
+{
+ int size = 0;
+
+ size += tagtxt("\xa9" "too", mp4config.tag.encoder);
+ if (mp4config.tag.artist)
+ size += tagtxt("\xa9" "ART", mp4config.tag.artist);
+ if (mp4config.tag.composer)
+ size += tagtxt("\xa9" "wrt", mp4config.tag.composer);
+ if (mp4config.tag.title)
+ size += tagtxt("\xa9" "nam", mp4config.tag.title);
+ if (mp4config.tag.genre)
+ size += tagtxt("gnre", mp4config.tag.genre);
+ if (mp4config.tag.album)
+ size += tagtxt("\xa9" "alb", mp4config.tag.album);
+ if (mp4config.tag.compilation)
+ {
+ size += tagu32("cpil", 1);
+ size += u32out(mp4config.tag.compilation);
+ }
+ if (mp4config.tag.trackno)
+ {
+ size += tagu32("trkn", 1);
+ size += u32out(mp4config.tag.trackno);
+ }
+ if (mp4config.tag.discno)
+ {
+ size += tagu32("disk", 1);
+ size += u32out(mp4config.tag.discno);
+ }
+ if (mp4config.tag.year)
+ size += tagtxt("\xa9" "day", mp4config.tag.year);
+#if 0
+ if (mp4config.tag.cover)
+ size += tagtxt("\xa9" "covr", mp4config.tag.cover);
+#endif
+ if (mp4config.tag.comment)
+ size += tagtxt("\xa9" "cmt", mp4config.tag.comment);
+
+ return size;
+};
+
+static creator_t g_head[] = {
+ {ATOM_NAME, "ftyp"},
+ {ATOM_DATA, ftypout},
+ {ATOM_NAME, "free"},
+ {ATOM_NAME, "mdat"},
+ {0}
+};
+
+static creator_t g_tail[] = {
+ {ATOM_NAME, "moov"},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "mvhd"},
+ {ATOM_DATA, mvhdout},
+ {ATOM_NAME, "trak"},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "tkhd"},
+ {ATOM_DATA, tkhdout},
+ {ATOM_NAME, "mdia"},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "mdhd"},
+ {ATOM_DATA, mdhdout},
+ {ATOM_NAME, "hdlr"},
+ {ATOM_DATA, hdlr1out},
+ {ATOM_NAME, "minf"},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "smhd"},
+ {ATOM_DATA, smhdout},
+ {ATOM_NAME, "dinf"},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "dref"},
+ {ATOM_DATA, drefout},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "url "},
+ {ATOM_DATA, urlout},
+ {ATOM_ASCENT},
+ {ATOM_ASCENT},
+ {ATOM_NAME, "stbl"},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "stsd"},
+ {ATOM_DATA, stsdout},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "mp4a"},
+ {ATOM_DATA, mp4aout},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "esds"},
+ {ATOM_DATA, esdsout},
+ {ATOM_ASCENT},
+ {ATOM_ASCENT},
+ {ATOM_NAME, "stts"},
+ {ATOM_DATA, sttsout},
+ {ATOM_NAME, "stsc"},
+ {ATOM_DATA, stscout},
+ {ATOM_NAME, "stsz"},
+ {ATOM_DATA, stszout},
+ {ATOM_NAME, "stco"},
+ {ATOM_DATA, stcoout},
+ {ATOM_ASCENT},
+ {ATOM_ASCENT},
+ {ATOM_ASCENT},
+ {ATOM_ASCENT},
+ {ATOM_NAME, "udta"},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "meta"},
+ {ATOM_DATA, metaout},
+ {ATOM_DESCENT},
+ {ATOM_NAME, "hdlr"},
+ {ATOM_DATA, hdlr2out},
+ {ATOM_NAME, "ilst"},
+ {ATOM_DATA, ilstout},
+ {0}
+};
+
+static creator_t *g_atom = 0;
+static int create(void)
+{
+ long apos = ftell(g_fout);;
+ int size;
+
+ size = u32out(8);
+ size += dataout(g_atom->data, 4);
+
+ g_atom++;
+ if (g_atom->opcode == ATOM_DATA)
+ {
+ size += ((int (*)(void)) g_atom->data) ();
+ g_atom++;
+ }
+ if (g_atom->opcode == ATOM_DESCENT)
+ {
+ g_atom++;
+ while (g_atom->opcode != ATOM_STOP)
+ {
+ if (g_atom->opcode == ATOM_ASCENT)
+ {
+ g_atom++;
+ break;
+ }
+ size += create();
+ }
+ }
+
+ fseek(g_fout, apos, SEEK_SET);
+ u32out(size);
+ fseek(g_fout, apos + size, SEEK_SET);
+
+ return size;
+}
+
+enum {BUFSTEP = 0x4000};
+int mp4atom_frame(uint8_t * buf, int size, int samples)
+{
+ if (mp4config.framesamples <= samples)
+ {
+ int bitrate = 8.0 * size * mp4config.samplerate / samples;
+
+ if (mp4config.bitratemax < bitrate)
+ mp4config.bitratemax = bitrate;
+
+ mp4config.framesamples = samples;
+ }
+ if (mp4config.buffersize < size)
+ mp4config.buffersize = size;
+ mp4config.samples += samples;
+ mp4config.mdatsize += dataout(buf, size);
+
+ if (((mp4config.frame.ents + 1) * sizeof(*(mp4config.frame.data)))
+ > mp4config.frame.bufsize)
+ {
+ mp4config.frame.bufsize += BUFSTEP;
+ mp4config.frame.data = realloc(mp4config.frame.data,
+ mp4config.frame.bufsize);
+ }
+ mp4config.frame.data[mp4config.frame.ents++] = size;
+
+ return 0;
+}
+
+int mp4atom_close(void)
+{
+ if (g_fout)
+ {
+ fseek(g_fout, mp4config.mdatofs - 8, SEEK_SET);
+ u32out(mp4config.mdatsize + 8);
+ fclose(g_fout);
+ g_fout = 0;
+ }
+ if (mp4config.frame.data)
+ {
+ free(mp4config.frame.data);
+ mp4config.frame.data = 0;
+ }
+ return 0;
+}
+
+int mp4atom_open(char *name)
+{
+ mp4atom_close();
+
+ g_fout = fopen(name, "wb");
+ if (!g_fout)
+ return 1;
+
+ mp4config.mdatsize = 0;
+ mp4config.frame.bufsize = BUFSTEP;
+ mp4config.frame.data = malloc(mp4config.frame.bufsize);
+
+ return 0;
+}
+
+
+int mp4atom_head(void)
+{
+ g_atom = g_head;
+ while (g_atom->opcode != ATOM_STOP)
+ create();
+ mp4config.mdatofs = ftell(g_fout);
+
+ return 0;
+}
+
+int mp4atom_tail(void)
+{
+ mp4config.bitrateavg = 8.0 * mp4config.mdatsize
+ * mp4config.samplerate / mp4config.samples;
+
+ g_atom = g_tail;
+ while (g_atom->opcode != ATOM_STOP)
+ create();
+
+ return 0;
+}
--- /dev/null
+++ b/frontend/mp4write.h
@@ -1,0 +1,74 @@
+/****************************************************************************
+ MP4 output 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 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
+ {
+ uint16_t *data;
+ uint32_t ents;
+ uint32_t bufsize;
+ } frame;
+ // AudioSpecificConfig data:
+ struct
+ {
+ uint8_t *data;
+ unsigned long size;
+ } asc;
+ uint32_t mdatofs;
+ uint32_t mdatsize;
+
+ struct
+ {
+ // meta fields
+ const char *encoder;
+ const char *artist;
+ const char *composer;
+ const char *title;
+ const char *genre;
+ const char *album;
+ uint8_t compilation;
+ uint32_t trackno;
+ uint32_t discno;
+ const char *year;
+ const char *cover; // cover filename
+ const char *comment;
+ } tag;
+} mp4config_t;
+
+extern mp4config_t mp4config;
+
+int mp4atom_open(char *name);
+int mp4atom_head(void);
+int mp4atom_tail(void);
+int mp4atom_frame(uint8_t * bitbuf, int bytesWritten, int frame_samples);
+int mp4atom_close(void);