text:
/** 24.04.2010 ASP @file
*
*   Ref: [1] ISO/IEC 14496-12:2005
*
    Option 1: simple mp4 layout, when fseek() available:

        [MDAT]<interleaved a/v frames, in any order> [MOOV]<Stream description & data index>

        A/V data written in single huge MDAT box. fseek() needed to update size of the MDAT box.

    Option 2: simple mp4 layout, when fseek() NOT available:

        [MDAT]<media frame> [MDAT]<media frame> .... [MDAT]<media frame> [MOOV]<Stream description & data index>

        Each A/V frame written in it's MDAT box

    Options 3&4: fragmented mp4 layout:

        [MOOV]<Stream description> [MOOF][MDAT]<media frame>[MOOF][MDAT]<media frame> .... [MOOF][MDAT]<media frame>

        Each A/V frame written in it's MDAT box. Frame side info written in MOOF box before. There is no global index in this file
        If fseek() available, media duration in the stream description is updated when closing the file

**/
#include "mp4mux.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>

/************************************************************************/
/*      Build config                                                    */
/************************************************************************/
// if fseek() avaialable, use single MDAT atom per file, and update data size on close
#ifndef MP4E_CAN_USE_RANDOM_FILE_ACCESS
#define MP4E_CAN_USE_RANDOM_FILE_ACCESS   1
#endif

// How much memory needed for indexes
// Experimental data:
// file with 1 track = 560 bytes
// file with 2 tracks = 972 bytes
// track size = 412 bytes;
// file header size = 148 bytes
#define FILE_HEADER_BYTES 256       // file header
#define TRACK_HEADER_BYTES 512      // track header

// File timescale
#define MOOV_TIMESCALE 1000

typedef unsigned int mp4e_size_t;

/*
*   Sample descriptor
*   1 sample = 1 video frame (incl all slices)
*            = 1 audio frame
*/
typedef struct
{
    mp4e_size_t size;               // sample data size
    mp4e_size_t offset;             // sample data offset in the mp4 file
    unsigned duration;              // sample duration, x(1./MP4E_track_t::time_scale) seconds
    unsigned flag_random_access;    // 1 if sample intra-coded
} sample_t;

/*
*   Dynamically sized memory block ('vector')
*/
typedef struct
{
    unsigned char * data;           // malloc()-ed memory
    size_t bytes;                   // used size
    size_t capacity;                // allocated size
} asp_vector_t;

/*
*   Track descriptor
*   Track is a sequence of samples. There are 1 or several tracks in the mp4 file
*/
typedef struct
{
    MP4E_track_t info;              // Application-supplied track description
    asp_vector_t smpl;              // samples descriptor
    asp_vector_t vsps;              // SPS for video or DSI for audio
    asp_vector_t vpps;              // PPS for video, not used for audio
} track_t;

/*
*   MP4 file descriptor
*/
typedef struct MP4E_mux_tag
{
    asp_vector_t tracks;            // mp4 file tracks
    FILE * mp4file;                 // output file handle
    mp4e_size_t write_pos;          // ## of bytes written ~ current file position (until 1st fseek)
    char * text_comment;            // application-supplied file comment
    int enable_fragmentation;         // flag, indicating streaming-friendly 'fragmentation' mode
    int fragments_count;            // # of fragments in 'fragmentation' mode
} MP4E_mux_t;

/*
*   Endian-independent byte-write macros
*/
#define MP4_WR(x, n) *write_ptr++ = (unsigned char)((x) >> 8*n)
#define WR1(x) MP4_WR(x,0);
#define WR2(x) MP4_WR(x,1); MP4_WR(x,0);
#define WR3(x) MP4_WR(x,2); MP4_WR(x,1); MP4_WR(x,0);
#define WR4(x) MP4_WR(x,3); MP4_WR(x,2); MP4_WR(x,1); MP4_WR(x,0);
// esc-coded OD length
#define MP4_WRITE_OD_LEN(size) if (size > 0x7F) do { size -= 0x7F; WR1(0x00ff);} while (size > 0x7F); WR1(size)

#define MP4_WRITE_STRING(s) {size_t len = strlen(s) + 1; memcpy(write_ptr, s, len); write_ptr += len;}

#define MP4_WR4_PTR(p,x) (p)[0]=(char)((x) >> 8*3); (p)[1]=(char)((x) >> 8*2); (p)[2]=(char)((x) >> 8*1); (p)[3]=(char)((x));

// Finish atom: update atom size field
#define MP4_END_ATOM --stack; MP4_WR4_PTR((unsigned char*)*stack, write_ptr - *stack);}

// Initiate atom: save position of size field on stack
#define MP4_ATOM(x)  {*stack++ = write_ptr; write_ptr += 4; WR4(x);

// Atom with 'FullAtomVersionFlags' field
#define MP4_FULL_ATOM(x, flag)  MP4_ATOM(x); WR4(flag);

static int mp4e_write_index(MP4E_mux_t * mux);

/************************************************************************/
/*      File output (non-portable) stuff                                */
/************************************************************************/

/**
*   Data stream output function
*/
static size_t mp4e_fwrite(MP4E_mux_t * mux, const void *buffer, size_t size)
{
    mux->write_pos += size;
    return fwrite(buffer, size, 1, mux->mp4file);
}

/************************************************************************/
/*      Abstract vector data structure                                  */
/************************************************************************/
/**
*   Allocate vector with given size, return 1 on success, 0 on fail
*/
static int asp_vector_init(asp_vector_t * h, int capacity)
{
    h->bytes = 0;
    h->capacity = capacity;
    h->data = capacity ? (unsigned char *)malloc(capacity) : NULL;
    return !capacity || !!h->data;
}

/**
*   Deallocates vector memory
*/
static void asp_vector_reset(asp_vector_t * h)
{
    free(h->data);
    memset(h, 0, sizeof(asp_vector_t));
}

/**
*   Allocates given number of bytes at the end of vector data, increasing
*   vector memory if necessary.
*   Return allocated memory.
*/
static unsigned char * asp_vector_alloc_tail(asp_vector_t * h, size_t bytes)
{
    unsigned char * p;
    if (h->bytes + bytes > h->capacity)
    {
        size_t grow_by = (bytes > h->capacity/2 ? bytes : h->capacity/2);
        size_t new_size = (h->capacity + grow_by + 1024) & -1024; // less-realloc's variant
        p = (unsigned char *)realloc(h->data, new_size);
        if (!p)
        {
            return NULL;
        }
        h->data = p;
        h->capacity = new_size;
    }
    assert(h->capacity - h->bytes >= bytes);
    p = h->data + h->bytes;
    h->bytes += bytes;
    return p;
}

/**
*   Append data to the end of the vector (accumulate or enqueue)
*/
static unsigned char * asp_vector_put(asp_vector_t * h, const void * buf, int bytes)
{
    unsigned char * tail = asp_vector_alloc_tail(h, bytes);
    if (tail)
    {
        memcpy(tail, buf, bytes);
    }
    return tail;
}

/************************************************************************/
/*  Index data structure managment functions                            */
/************************************************************************/

/**
*   Destroy multiplexer object: release memory, close output file handle
*/
static void mp4e_free(MP4E_mux_t * mux)
{
    if (mux)
    {
        unsigned long ntr, ntracks = mux->tracks.bytes / sizeof(track_t);
        for (ntr = 0; ntr < ntracks; ntr++)
        {
            track_t* tr = ((track_t*)mux->tracks.data) + ntr;
            asp_vector_reset(&tr->vsps);
            asp_vector_reset(&tr->vpps);
            asp_vector_reset(&tr->smpl);
        }
        asp_vector_reset(&mux->tracks);
        fclose(mux->mp4file);
        free(mux->text_comment);
        free(mux);
    }
}

/**
*   Append new SPS/PPS to the list, keeping them in MP4 format (16-bit data_size + data)
*/
static int mp4e_sps_pps_append_mem(asp_vector_t * v, const void * mem, int bytes)
{
    size_t i;
    unsigned char size[2];
    const unsigned char * p = v->data;
    for (i = 0; i + 2 < v->bytes;)
    {
        int cb = p[i]*256 + p[i+1];
        if (cb == bytes && !memcmp(p+i+2, mem, cb))
        {
            return 1;
        }
        i += 2 + cb;
    }
    size[0] = bytes >> 8;
    size[1] = bytes;
    return asp_vector_put(v, size, 2) && asp_vector_put(v, mem, bytes);
}

/**
*   Count number of SPS/PPS in the list
*/
static int mp4e_sps_pps_items_count(asp_vector_t * v)
{
    int count= 0;
    size_t i;
    const unsigned char * p = v->data;
    for (i = 0; i + 2 < v->bytes;)
    {
        int cb = p[i]*256 + p[i+1];
        count++;
        i += 2 + cb;
    }
    return count;
}

/**
*   Return sum of track samples duration.
*/
static unsigned mp4e_get_track_duration(const track_t * tr)
{
    unsigned i, sum_duration = 0;
    const sample_t * s = (const sample_t *)tr->smpl.data;
    for (i = 0; i < tr->smpl.bytes/sizeof(sample_t); i++)
    {
        sum_duration += s[i].duration;
    }
    return sum_duration;
}

/**
*   calculate size of length field of OD box
*/
static int mp4e_od_size_of_size(int size)
{
    int i, size_of_size = 1;
    for (i = size; i > 0x7F; i -= 0x7F) size_of_size++;
    return size_of_size;
}

/**
*   Append sample descriptor to the samples list
*/
static int mp4e_add_sample_descriptor(MP4E_mux_t * mux, track_t * tr, int data_bytes, int duration, int kind)
{
    sample_t smp;
    smp.size = data_bytes;
    smp.offset = (mp4e_size_t)mux->write_pos;
    smp.duration = (duration ? duration : tr->info.default_duration);
    smp.flag_random_access = (kind == MP4E_SAMPLE_RANDOM_ACCESS);
    return NULL != asp_vector_put(&tr->smpl, &smp, sizeof(sample_t));
}

/************************************************************************/
/*  Data write functions                                                */
/************************************************************************/

/**
*    Write fixed file header: 'ftyp' box
*/
static int mp4e_write_file_header(MP4E_mux_t * mux)
{
    // hardcoded part of the file header
    static const unsigned char mp4e_box_ftyp[] =
    {
#if 1
        0,0,0,0x18,'f','t','y','p',
        'm','p','4','2',0,0,0,0,
        'm','p','4','2','i','s','o','m',
#else
        // as in ffmpeg
        0,0,0,0x20,'f','t','y','p',
        'i','s','o','m',0,0,2,0,
        'm','p','4','1','i','s','o','m',
        'i','s','o','2','a','v','c','1',
#endif
    };
    return mp4e_fwrite(mux, mp4e_box_ftyp, sizeof(mp4e_box_ftyp)) ? sizeof(mp4e_box_ftyp) : 0;
}

/**
*   Write data header: 'mdat' box
*/
static int mp4e_write_mdat_box(MP4E_mux_t * mux, unsigned data_bytes)
{
    unsigned char write_base[8], *write_ptr = write_base;     // for WR4 macro
    WR4(data_bytes);
    WR4(BOX_mdat);
    return mp4e_fwrite(mux, write_base, write_ptr - write_base);
}

/**
*   Write Movie Fragment: 'moof' box
*/
static int mp4e_write_fragment_header(MP4E_mux_t * mux, int track_num, int data_bytes, int duration, int kind)
{
    unsigned char write_base[888], *write_ptr = write_base;     // for WRITE_4 macro
    // atoms nesting stack
    unsigned char * stack_base[20];
    unsigned char ** stack = stack_base;
    unsigned flags;
    unsigned char * pdata_offset;
    enum
    {
        default_sample_duration_present = 0x000008,
        default_sample_flags_present = 0x000020,
    } e;

    track_t * tr = ((track_t*)mux->tracks.data) + track_num;

    MP4_ATOM(BOX_moof)
        MP4_FULL_ATOM(BOX_mfhd, 0)
            WR4(mux->fragments_count);  // start from 1
        MP4_END_ATOM
        MP4_ATOM(BOX_traf)
            flags = 0;
            if (tr->info.track_media_kind == e_video)
            {
                flags |= 0x20;          // default-sample-flags-present
            }
            else
            {
                flags |= 0x08;          // default-sample-duration-present
            }
            flags =  (tr->info.track_media_kind == e_video) ? 0x20020 : 0x20008;

            MP4_FULL_ATOM(BOX_tfhd, flags)
                WR4(track_num+1);       // track_ID
                if (tr->info.track_media_kind == e_video)
                {
                    flags  = 0x001;     // data-offset-present
                    flags |= 0x100;     // sample-duration-present
                    WR4(0x1010000);     // default_sample_flags
                }
                else
                {
                    WR4(duration);
                }
            MP4_END_ATOM
            if (tr->info.track_media_kind == e_audio)
            {
                flags  = 0;
                flags |= 0x001;         // data-offset-present
                flags |= 0x200;         // sample-size-present
                MP4_FULL_ATOM(BOX_trun, flags)
                    WR4(1);             // sample_count
                    pdata_offset = write_ptr; write_ptr += 4;   // save ptr to data_offset
                    WR4(duration);      // sample_duration
                MP4_END_ATOM
            }
            else if (kind == MP4E_SAMPLE_RANDOM_ACCESS)
            {
                flags  = 0;
                flags |= 0x001;         // data-offset-present
                flags |= 0x004;         // first-sample-flags-present
                flags |= 0x100;         // sample-duration-present
                flags |= 0x200;         // sample-size-present
                MP4_FULL_ATOM(BOX_trun, flags)
                    WR4(1);             // sample_count
                    pdata_offset = write_ptr; write_ptr += 4;   // save ptr to data_offset
                    WR4(0x2000000);     // first_sample_flags
                    WR4(duration);      // sample_duration
                    WR4(data_bytes);    // sample_size
                MP4_END_ATOM
            }
            else
            {
                flags  = 0;
                flags |= 0x001;         // data-offset-present
                flags |= 0x100;         // sample-duration-present
                flags |= 0x200;         // sample-size-present
                MP4_FULL_ATOM(BOX_trun, flags)
                    WR4(1);             // sample_count
                    pdata_offset = write_ptr; write_ptr += 4;   // save ptr to data_offset
                    WR4(duration);      // sample_duration
                    WR4(data_bytes);    // sample_size
                MP4_END_ATOM
            }
        MP4_END_ATOM
    MP4_END_ATOM
    MP4_WR4_PTR(pdata_offset, (write_ptr - write_base) + 8);

    return mp4e_fwrite(mux, write_base, write_ptr - write_base);
}

/**
*   Write file index 'moov' box with all its boxes and indexes
*/
static int mp4e_write_index(MP4E_mux_t * mux)
{
    // atoms nesting stack
    unsigned char * stack_base[20];
    unsigned char ** stack = stack_base;

    // in-memory indexes
    unsigned char * write_base, * write_ptr;

    unsigned int ntr, index_bytes, ntracks;
    int i, error_code;

    mp4e_size_t mdat_end = mux->write_pos;

    ntracks = (unsigned int)(mux->tracks.bytes / sizeof(track_t));
    index_bytes = FILE_HEADER_BYTES;
    if (mux->text_comment)
    {
        index_bytes += 128 + strlen(mux->text_comment);
    }
    for (ntr = 0; ntr < ntracks; ntr++)
    {
        track_t * tr = ((track_t*)mux->tracks.data) + ntr;
        index_bytes += TRACK_HEADER_BYTES;          // fixed amount (implementation-dependent)
        // may need extra 4 bytes for duration field + 4 bytes for worst-case random access box
        index_bytes += tr->smpl.bytes * (sizeof(sample_t) + 4 + 4) / sizeof(sample_t);
        index_bytes += tr->vsps.bytes;
        index_bytes += tr->vpps.bytes;
    }

    // Allocate index memory
    write_base = (unsigned char*)malloc(index_bytes);
    if (!write_base)
    {
        return MP4E_STATUS_NO_MEMORY;
    }
    write_ptr = write_base;

    //
    // Write index atoms; order taken from Table 1 of [1]
    //
    MP4_ATOM(BOX_moov);
        MP4_FULL_ATOM(BOX_mvhd, 0);
        WR4(0); // creation_time
        WR4(0); // modification_time

        if (ntracks)
        {
            track_t * tr = ((track_t*)mux->tracks.data) + 0;    // take 1st track
            unsigned duration = mp4e_get_track_duration(tr);
            duration = (unsigned)(duration * 1LL * MOOV_TIMESCALE / tr->info.time_scale);
            WR4(MOOV_TIMESCALE); // duration
            WR4(duration); // duration
        }

        WR4(0x00010000); // rate
        WR2(0x0100); // volume
        WR2(0); // reserved
        WR4(0); // reserved
        WR4(0); // reserved

        // matrix[9]
        WR4(0x00010000);WR4(0);WR4(0);
        WR4(0);WR4(0x00010000);WR4(0);
        WR4(0);WR4(0);WR4(0x40000000);

        // pre_defined[6]
        WR4(0); WR4(0); WR4(0);
        WR4(0); WR4(0); WR4(0);

        //next_track_ID is a non-zero integer that indicates a value to use for the track ID of the next track to be
        //added to this presentation. Zero is not a valid track ID value. The value of next_track_ID shall be
        //larger than the largest track-ID in use.
        WR4(ntracks+1);
        MP4_END_ATOM;

    for (ntr = 0; ntr < ntracks; ntr++)
    {
        track_t * tr = ((track_t*)mux->tracks.data) + ntr;
        unsigned duration = mp4e_get_track_duration(tr);
        int samples_count = (int)(tr->smpl.bytes / sizeof(sample_t));
        const sample_t * sample = (const sample_t *)tr->smpl.data;
        unsigned handler_type;
        const char * handler_ascii = NULL;

        if (mux->enable_fragmentation)
        {
            samples_count = 0;
        }
        else if (samples_count <= 0)
        {
            continue;   // skip empty track
        }

        switch (tr->info.track_media_kind)
        {
            case e_audio:
                handler_type = MP4_HANDLER_TYPE_SOUN;
                handler_ascii = "SoundHandler";
                break;
            case e_video:
                handler_type = MP4_HANDLER_TYPE_VIDE;
                handler_ascii = "VideoHandler";
                break;
            case e_private:
                handler_type = MP4_HANDLER_TYPE_GESM;
                break;
            default:
                return MP4E_STATUS_BAD_ARGUMENTS;
        }

        MP4_ATOM(BOX_trak);
            MP4_FULL_ATOM(BOX_tkhd, 7); // flag: 1=trak enabled; 2=track in movie; 4=track in preview
            WR4(0);             // creation_time
            WR4(0);             // modification_time
            WR4(ntr+1);         // track_ID
            WR4(0);             // reserved
            WR4((unsigned)(duration * 1LL * MOOV_TIMESCALE / tr->info.time_scale));
            WR4(0); WR4(0); // reserved[2]
            WR2(0);             // layer
            WR2(0);             // alternate_group
            WR2(0x0100);        // volume {if track_is_audio 0x0100 else 0};
            WR2(0);             // reserved

            // matrix[9]
            WR4(0x00010000);WR4(0);WR4(0);
            WR4(0);WR4(0x00010000);WR4(0);
            WR4(0);WR4(0);WR4(0x40000000);

            if (tr->info.track_media_kind == e_audio || tr->info.track_media_kind == e_private)
            {
                WR4(0); // width
                WR4(0); // height
            }
            else
            {
                WR4(tr->info.u.v.width*0x10000); // width
                WR4(tr->info.u.v.height*0x10000); // height
            }
            MP4_END_ATOM;

            MP4_ATOM(BOX_mdia);
                MP4_FULL_ATOM(BOX_mdhd, 0);
                WR4(0); // creation_time
                WR4(0); // modification_time
                WR4(tr->info.time_scale);
                WR4(duration); // duration
                {
                    int lang_code = ((tr->info.language[0]&31) << 10) | ((tr->info.language[1]&31) << 5) | (tr->info.language[2]&31);
                    WR2(lang_code); // language
                }
                WR2(0); // pre_defined
                MP4_END_ATOM;

                MP4_FULL_ATOM(BOX_hdlr, 0);
                WR4(0); // pre_defined
                WR4(handler_type); // handler_type
                WR4(0); WR4(0); WR4(0); // reserved[3]
                // name is a null-terminated string in UTF-8 characters which gives a human-readable name for the track type (for debugging and inspection purposes).
                // set mdia hdlr name field to what quicktime uses.
                // Sony smartphone may fail to decode short files w/o handler name
                if (handler_ascii)
                {
                    MP4_WRITE_STRING(handler_ascii);
                }
                else
                {
                    WR4(0);
                }
                MP4_END_ATOM;

                MP4_ATOM(BOX_minf);

                    if (tr->info.track_media_kind == e_audio)
                    {
                        // Sound Media Header Box
                        MP4_FULL_ATOM(BOX_smhd, 0);
                        WR2(0);   // balance
                        WR2(0);   // reserved
                        MP4_END_ATOM;
                    }
                    if (tr->info.track_media_kind == e_video)
                    {
                        // mandatory Video Media Header Box
                        MP4_FULL_ATOM(BOX_vmhd, 1);
                        WR2(0);   // graphicsmode
                        WR2(0); WR2(0); WR2(0); // opcolor[3]
                        MP4_END_ATOM;
                    }

                    MP4_ATOM(BOX_dinf);
                        MP4_FULL_ATOM(BOX_dref, 0);
                        WR4(1); // entry_count
                            // If the flag is set indicating that the data is in the same file as this box, then no string (not even an empty one)
                            // shall be supplied in the entry field.

                            // ASP the correct way to avoid supply the string, is to use flag 1
                            // otherwise ISO reference demux crashes
                            MP4_FULL_ATOM(BOX_url, 1);
                            MP4_END_ATOM;
                        MP4_END_ATOM;
                    MP4_END_ATOM;

                    MP4_ATOM(BOX_stbl);
                        MP4_FULL_ATOM(BOX_stsd, 0);
                        WR4(1); // entry_count;

                        if (tr->info.track_media_kind == e_audio ||
                            tr->info.track_media_kind == e_private
                        )
                        {
                            // AudioSampleEntry() assume MP4E_HANDLER_TYPE_SOUN
                            unsigned box_name = (tr->info.track_media_kind == e_audio) ? BOX_mp4a : BOX_mp4s;
                            MP4_ATOM(box_name);

                            // SampleEntry
                            WR4(0); WR2(0); // reserved[6]
                            WR2(1); // data_reference_index; - this is a tag for descriptor below

                            if (tr->info.track_media_kind == e_audio)
                            {
                                // AudioSampleEntry
                                WR4(0); WR4(0); // reserved[2]
                                WR2(tr->info.u.a.channelcount); // channelcount
                                WR2(16); // samplesize
                                WR4(0);  // pre_defined+reserved
                                WR4((tr->info.time_scale << 16));  // samplerate == = {timescale of media}<<16;
                            }

                                MP4_FULL_ATOM(BOX_esds, 0);
                                if (tr->vsps.bytes > 0)
                                {
                                    int dsi_bytes = (int)(tr->vsps.bytes) - 2; //  - two bytes size field
                                    int dsi_size_size = mp4e_od_size_of_size(dsi_bytes);
                                    int dcd_bytes = dsi_bytes + dsi_size_size + 1 + (1+1+3+4+4);
                                    int dcd_size_size = mp4e_od_size_of_size(dcd_bytes);
                                    int esd_bytes = dcd_bytes + dcd_size_size + 1 + 3;

                                    WR1(3); // OD_ESD
                                    MP4_WRITE_OD_LEN(esd_bytes);
                                    WR2(0); // ES_ID(2) // TODO - what is this?
                                    WR1(0); // flags(1)

                                    WR1(4); // OD_DCD
                                    MP4_WRITE_OD_LEN(dcd_bytes);
                                    if (tr->info.track_media_kind == e_audio)
                                    {
                                        WR1(tr->info.object_type_indication); // OD_DCD
                                        //WR1(MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3); // OD_DCD
                                        WR1(5<<2); // stream_type == AudioStream
                                    }
                                    else
                                    {
                                        // http://xhelmboyx.tripod.com/formats/mp4-layout.txt
                                        WR1(208); // 208 = private video
                                        WR1(32<<2); // stream_type == user private
                                    }
                                    WR3(tr->info.u.a.channelcount * 6144/8); // bufferSizeDB in bytes, constant as in reference decoder
                                    WR4(0); // maxBitrate TODO
                                    WR4(0); // avg_bitrate_bps TODO

                                    WR1(5); // OD_DSI
                                    MP4_WRITE_OD_LEN(dsi_bytes);
                                    for (i = 0; i < dsi_bytes; i++)
                                    {
                                        WR1(tr->vsps.data[2+i]);
                                    }
                                }
                                MP4_END_ATOM;
                            MP4_END_ATOM;
                        }

                        if (tr->info.track_media_kind == e_video)
                        {
                            unsigned int sps_count = mp4e_sps_pps_items_count(&tr->vsps);
                            unsigned int pps_count = mp4e_sps_pps_items_count(&tr->vpps);

                            MP4_ATOM(BOX_avc1);
                            // VisualSampleEntry  8.16.2
                            // extends SampleEntry
                            WR2(0); // reserved
                            WR2(0); // reserved
                            WR2(0); // reserved
                            WR2(1); // data_reference_index
                            WR2(0); // pre_defined
                            WR2(0); // reserved
                            WR4(0); // pre_defined
                            WR4(0); // pre_defined
                            WR4(0); // pre_defined
                            WR2(tr->info.u.v.width);
                            WR2(tr->info.u.v.height);
                            WR4(0x00480000); // horizresolution = 72 dpi
                            WR4(0x00480000); // vertresolution  = 72 dpi
                            WR4(0); // reserved
                            WR2(1); // frame_count
                            for (i = 0; i < 32; i++)
                            {
                                WR1(0); //  compressorname
                            }
                            WR2(24); // depth
                            WR2(0xffff); // pre_defined

                                MP4_ATOM(BOX_avcC);
                                // AVCDecoderConfigurationRecord 5.2.4.1.1
                                WR1(1); // configurationVersion
                                WR1(tr->vsps.data[2+1]); //
                                WR1(tr->vsps.data[2+2]); //
                                WR1(tr->vsps.data[2+3]); //
                                WR1(255);                 // 0xfc + NALU_len-1
                                WR1(0xe0 | sps_count);
                                for (i = 0; i < (int)tr->vsps.bytes; i++)
                                {
                                    WR1(tr->vsps.data[i]);
                                }

                                WR1(pps_count);
                                for (i = 0; i < (int)tr->vpps.bytes; i++)
                                {
                                    WR1(tr->vpps.data[i]);
                                }

                                MP4_END_ATOM;
                            MP4_END_ATOM;
                        }
                        MP4_END_ATOM;

                        /************************************************************************/
                        /*      indexes                                                         */
                        /************************************************************************/

                        // Time to Sample Box
                        MP4_FULL_ATOM(BOX_stts, 0);
                        {
                            unsigned char * pentry_count = write_ptr;
                            int cnt = 1, entry_count = 0;
                            WR4(0);
                            for (i = 0; i < samples_count; i++, cnt++)
                            {
                                if (i == samples_count-1 || sample[i].duration != sample[i+1].duration)
                                {
                                    WR4(cnt);
                                    WR4(sample[i].duration);
                                    cnt = 0;
                                    entry_count++;
                                }
                            }
                            MP4_WR4_PTR(pentry_count, entry_count);
                        }
                        MP4_END_ATOM;

                        // Sample To Chunk Box
                        MP4_FULL_ATOM(BOX_stsc, 0);
                        if (mux->enable_fragmentation)
                        {
                            WR4(0); // entry_count
                        }
                        else
                        {
                            WR4(1); // entry_count
                            WR4(1); // first_chunk;
                            WR4(1); // samples_per_chunk;
                            WR4(1); // sample_description_index;
                        }
                        MP4_END_ATOM;

                        // Sample Size Box
                        MP4_FULL_ATOM(BOX_stsz, 0);
                        WR4(0); // sample_size  If this field is set to 0, then the samples have different sizes, and those sizes
                                    //  are stored in the sample size table.
                        WR4(samples_count);  // sample_count;
                        for (i = 0; i < samples_count; i++)
                        {
                            WR4(sample[i].size);
                        }
                        MP4_END_ATOM;

                        // Chunk Offset Box
                        MP4_FULL_ATOM(BOX_stco, 0);
                        WR4(samples_count);  // entry_count
                        for (i = 0; i < samples_count; i++)
                        {
                            WR4(sample[i].offset);
                        }
                        MP4_END_ATOM;

                        // Sync Sample Box
                        {
                            int ra_count = 0;
                            for (i = 0; i < samples_count; i++)
                            {
                                ra_count += !!sample[i].flag_random_access;
                            }
                            if (ra_count != samples_count)
                            {
                                // If the sync sample box is not present, every sample is a random access point.
                                MP4_FULL_ATOM(BOX_stss, 0);
                                WR4(ra_count);
                                for (i = 0; i < samples_count; i++)
                                {
                                    if (sample[i].flag_random_access)
                                    {
                                        WR4(i+1);
                                    }
                                }
                                MP4_END_ATOM;
                            }
                        }
                    MP4_END_ATOM;
                MP4_END_ATOM;
            MP4_END_ATOM;
        MP4_END_ATOM;
    } // tracks loop

    if (mux->text_comment)
    {
        MP4_ATOM(BOX_udta);
            MP4_FULL_ATOM(BOX_meta, 0);
                MP4_FULL_ATOM(BOX_hdlr, 0);
                    WR4(0); // pre_defined
                    WR4(MP4_HANDLER_TYPE_MDIR); // handler_type
                    WR4(0); WR4(0); WR4(0); // reserved[3]
                    WR4(0); // name is a null-terminated string in UTF-8 characters which gives a human-readable name for the track type (for debugging and inspection purposes).
                MP4_END_ATOM;
                MP4_ATOM(BOX_ilst);
                    MP4_ATOM((unsigned)BOX_ccmt);
                        MP4_ATOM(BOX_data);
                            WR4(1); // type
                            WR4(0); //lang
                            MP4_WRITE_STRING(mux->text_comment);
                        MP4_END_ATOM;
                    MP4_END_ATOM;
                MP4_END_ATOM;
            MP4_END_ATOM;
        MP4_END_ATOM;
    }

    if (mux->enable_fragmentation)
    {
        track_t * tr = ((track_t*)mux->tracks.data) + 0;
        unsigned movie_duration = mp4e_get_track_duration(tr);

        MP4_ATOM(BOX_mvex);
            MP4_FULL_ATOM(BOX_mehd, 0);
                WR4(movie_duration);    // duration
            MP4_END_ATOM;
        for (ntr = 0; ntr < ntracks; ntr++)
        {
            MP4_FULL_ATOM(BOX_trex, 0);
                WR4(ntr+1);             // track_ID
                WR4(1);                 // default_sample_description_index
                WR4(0);                 // default_sample_duration
                WR4(0);                 // default_sample_size
                WR4(0);                 // default_sample_flags
            MP4_END_ATOM;
        }
        MP4_END_ATOM;
    }

    MP4_END_ATOM;   // moov atom

    assert((unsigned)(write_ptr - write_base) <= index_bytes);

    if (!mp4e_fwrite(mux, write_base, write_ptr - write_base))
    {
        error_code = MP4E_STATUS_FILE_WRITE_ERROR;
    }
    else
    {
        error_code = MP4E_STATUS_OK;
    }
    free(write_base);

#if MP4E_CAN_USE_RANDOM_FILE_ACCESS
    if (!mux->enable_fragmentation)
    {
        // update size of mdat box.
        fseek(mux->mp4file, 0, SEEK_SET);
        mp4e_write_mdat_box(mux, mdat_end - mp4e_write_file_header(mux));
    }
#endif

    return error_code;
}

/************************************************************************/
/*      Exported API functions                                          */
/************************************************************************/

/**
*   Allocates and initialize mp4 multiplexer
*   return multiplexer handle on success; NULL on failure
*/
MP4E_mux_t * MP4E__open(FILE * mp4file, int enable_fragmentation)
{
    MP4E_mux_t * mux;
    if (!mp4file)
    {
        return NULL;
    }

    mux = (MP4E_mux_t *)calloc(sizeof(MP4E_mux_t), 1);
    if (mux)
    {
        int success;
        mux->mp4file = mp4file;
        mux->enable_fragmentation = enable_fragmentation;
        asp_vector_init(&mux->tracks, 2*sizeof(track_t));

        success = !!mp4e_write_file_header(mux);
#if MP4E_CAN_USE_RANDOM_FILE_ACCESS
        if (!mux->enable_fragmentation)
        {
            success &= mp4e_write_mdat_box(mux, 0);    // Write stub, which would be updated later
        }
#endif
        if (!success)
        {
            mp4e_free(mux);
            mux = NULL;
        }
    }

    return mux;
}

/**
*   Closes MP4 multiplexer
*/
int MP4E__close(MP4E_mux_t * mux)
{
    int error_code = MP4E_STATUS_OK;
    if (!mux)
    {
        return MP4E_STATUS_BAD_ARGUMENTS;
    }

    if (mux->enable_fragmentation)
    {
#if MP4E_CAN_USE_RANDOM_FILE_ACCESS
        rewind(mux->mp4file);
        mp4e_write_file_header(mux);
        error_code = mp4e_write_index(mux);
#endif
    }
    else
    {
        error_code = mp4e_write_index(mux);
    }
    mp4e_free(mux);

    return error_code;
}

/**
*   Add new track, return track ID
*/
int MP4E__add_track(MP4E_mux_t * mux, const MP4E_track_t * track_data)
{
    track_t *tr;

    if (!mux || !track_data)
    {
        return MP4E_STATUS_BAD_ARGUMENTS;
    }
    if (mux->fragments_count)
    {
        return MP4E_STATUS_ENCODE_IN_PROGRESS;
    }

    tr = (track_t*)asp_vector_alloc_tail(&mux->tracks, sizeof(track_t));
    if (!tr)
    {
        return MP4E_STATUS_NO_MEMORY;
    }
    memset(tr, 0, sizeof(track_t));
    memcpy(&tr->info, track_data, sizeof(*track_data));
    if (!asp_vector_init(&tr->smpl, 256))
    {
        return MP4E_STATUS_NO_MEMORY;
    }
    asp_vector_init(&tr->vsps, 0);
    asp_vector_init(&tr->vpps, 0);
    return (int)(mux->tracks.bytes / sizeof(track_t)) - 1;
}

/**
*   Set track DSI. Used for audio tracks.
*/
int MP4E__set_dsi(MP4E_mux_t * mux, int track_id, const void * dsi, int bytes)
{
    track_t* tr = ((track_t*)mux->tracks.data) + track_id;
    assert(tr->info.track_media_kind == e_audio ||
           tr->info.track_media_kind == e_private);
    if (tr->vsps.bytes)
    {
        return MP4E_STATUS_ONLY_ONE_DSI_ALLOWED;   // only one DSI allowed
    }
    if (mux->fragments_count)
    {
        return MP4E_STATUS_ENCODE_IN_PROGRESS;
    }
    return mp4e_sps_pps_append_mem(&tr->vsps, dsi, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY;
}

/**
*   Set track SPS. Used for AVC video tracks.
*/
int MP4E__set_sps(MP4E_mux_t * mux, int track_id, const void * sps, int bytes)
{
    track_t* tr = ((track_t*)mux->tracks.data) + track_id;
    assert(tr->info.track_media_kind == e_video);
    if (mux->fragments_count)
    {
        return MP4E_STATUS_ENCODE_IN_PROGRESS;
    }
    return mp4e_sps_pps_append_mem(&tr->vsps, sps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY;
}

/**
*   Set track PPS. Used for AVC video tracks.
*/
int MP4E__set_pps(MP4E_mux_t * mux, int track_id, const void * pps, int bytes)
{
    track_t* tr = ((track_t*)mux->tracks.data) + track_id;
    assert(tr->info.track_media_kind == e_video);
    if (mux->fragments_count)
    {
        return MP4E_STATUS_ENCODE_IN_PROGRESS;
    }
    return mp4e_sps_pps_append_mem(&tr->vpps, pps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY;
}

/**
*   Add or remove MP4 file text comment according to Apple specs:
*   https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW1
*   http://atomicparsley.sourceforge.net/mpeg-4files.html
*   note that ISO did not specify comment format.
*/
int MP4E__set_text_comment(MP4E_mux_t * mux, const char * comment)
{
    if (!mux)
    {
        return MP4E_STATUS_BAD_ARGUMENTS;
    }
    if (mux->fragments_count)
    {
        return MP4E_STATUS_ENCODE_IN_PROGRESS;
    }

    // replace comment
    free(mux->text_comment);
    mux->text_comment = NULL;
    if (comment)
    {
        mux->text_comment = (char*)malloc(strlen(comment) + 1);
        if (mux->text_comment)
        {
            strcpy(mux->text_comment, comment);
        }
    }
    return MP4E_STATUS_OK;
}

/**
*   Add new sample to specified track
*/
int MP4E__put_sample(MP4E_mux_t * mux, int track_num, const void * data, int data_bytes, int duration, int kind)
{
    if (!mux || !data || track_num*sizeof(track_t) >= mux->tracks.bytes)
    {
        return MP4E_STATUS_BAD_ARGUMENTS;
    }

    if (mux->enable_fragmentation)
    {
        if (!mux->fragments_count++)
        {
            // write file headers before 1st sample
            int error_code = mp4e_write_index(mux);
            if (error_code)
            {
                return error_code;
            }
        }

        // write MOOF + MDAT + sample data
        if (!mp4e_write_fragment_header(mux, track_num, data_bytes, duration, kind))
        {
            return MP4E_STATUS_FILE_WRITE_ERROR;
        }

        // write MDAT box for each sample
        if (!mp4e_write_mdat_box(mux, data_bytes + 8))
        {
            return MP4E_STATUS_FILE_WRITE_ERROR;
        }
    }
    else
    {
#if !MP4E_CAN_USE_RANDOM_FILE_ACCESS
        // write MDAT box for each sample
        if (!mp4e_write_mdat_box(mux, data_bytes + 8))
        {
            return MP4E_STATUS_FILE_WRITE_ERROR;
        }
#endif
    }

    // update file index (after optional MDAT)
    // fragmented mode also may use optional index at the end of file (not yet implemented)
    if (!mp4e_add_sample_descriptor(mux, ((track_t*)mux->tracks.data) + track_num, data_bytes, duration, kind))
    {
        return MP4E_STATUS_NO_MEMORY;
    }

    // write sample data
    if (!mp4e_fwrite(mux, data, data_bytes))
    {
        return MP4E_STATUS_FILE_WRITE_ERROR;
    }

    return MP4E_STATUS_OK;
}

#ifdef mp4mux_test
/******************************************************************************
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!                                                                       !!!!
!!!!                 !!!!!!!!  !!!!!!!!   !!!!!!!   !!!!!!!!               !!!!
!!!!                    !!     !!        !!            !!                  !!!!
!!!!                    !!     !!        !!            !!                  !!!!
!!!!                    !!     !!!!!!     !!!!!!!      !!                  !!!!
!!!!                    !!     !!               !!     !!                  !!!!
!!!!                    !!     !!               !!     !!                  !!!!
!!!!                    !!     !!!!!!!!   !!!!!!!      !!                  !!!!
!!!!                                                                       !!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
******************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <math.h>
#include <memory.h>
#include "mp4mux.h"

const unsigned char avc[] = {0, 0, 1, 0xB3, 0, 0x10, 7, 0, 0, 1, 0xB6, 16, 0x60, 0x51, 0x82, 0x3D, 0xB7, 0xEF};
const unsigned char sps[] = {0x67, 0x42, 0xE0, 0x0A, 0xDA, 0x79};
const unsigned char pps[] = {0x68, 0xCE, 0x04, 0x72};
const unsigned char idr[] = {0,0,0,12,0x65, 0xB8, 0x23, 0xFF, 0xFF, 0xF0, 0xF4, 0x50, 0x00, 0x10, 0x11, 0xF8};
const unsigned char frm[] = {0,0,0,4,0x61, 0xE2, 0x3D, 0x40 };

const unsigned char aac_dsi[] = {0x12, 0x10};
const unsigned char aac[] = {
0x21, 0x10, 0x05, 0x20, 0xA4, 0x1B, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x37, 0xA7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x70
};

void test(FILE * output, int fragmentation_mode)
{
    int i, id_video, id_audio, id_private;
    static unsigned char dummy[100];
    // == Open file
    MP4E_mux_t * mp4 = MP4E__open(output, fragmentation_mode);

    // == Add audio track
    MP4E_track_t track;
    track.object_type_indication = MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3;
    strcpy((char*)track.language, "und");
    track.u.a.channelcount = 2;
    track.time_scale = 44100;
    track.default_duration = 1024;
    track.track_media_kind = e_audio;
    id_audio = MP4E__add_track(mp4, &track);

    // == Add video track
    track.track_media_kind = e_video;
    track.object_type_indication = MP4_OBJECT_TYPE_AVC;
    track.time_scale = 90000;
    track.default_duration = 90000 / 30;
    track.u.v.width = 16;
    track.u.v.height = 16;
    id_video = MP4E__add_track(mp4, &track);

    // == Add private data track
    track.track_media_kind = e_private;
    track.time_scale = 44100;
    track.default_duration = 1024;
    track.object_type_indication = MP4_OBJECT_TYPE_USER_PRIVATE;
    id_private = MP4E__add_track(mp4, &track);

    // == Supply SPS/PPS/DSI descriptors
    MP4E__set_sps(mp4, id_video, sps, sizeof(sps));
    MP4E__set_pps(mp4, id_video, pps, sizeof(pps));
    MP4E__set_dsi(mp4, id_audio, aac_dsi, sizeof(aac_dsi));
    for (i = 0; i < 10; i++)
    {
        dummy[i] = 16+i;
    }
    MP4E__set_dsi(mp4, id_private, dummy, 10);

    // == Append audio data
    for (i = 0; i < 2 * 44100 / 1024; i++)
    {
        MP4E__put_sample(mp4, id_audio, aac, sizeof(aac), 0, MP4E_SAMPLE_RANDOM_ACCESS);
    }
    // == Append video data
    for (i = 0; i < 30; i++)
    {
        MP4E__put_sample(mp4, id_video, idr, sizeof(idr), 0, MP4E_SAMPLE_RANDOM_ACCESS);
        MP4E__put_sample(mp4, id_video, frm, sizeof(frm), 0, MP4E_SAMPLE_DEFAULT);
    }
    // == Append private data
    for (i = 0; i < 2 * 44100 / 1024; i++)
    {
        int bytes = 100;
        int duration = 1024;
        dummy[0] = i;
        MP4E__put_sample(mp4, id_private, dummy, bytes, duration, MP4E_SAMPLE_DEFAULT);
    }

    // == Set file comment
    MP4E__set_text_comment(mp4, "test comment");

    // == Close session
    MP4E__close(mp4);
}

int main(int argc, char* argv[])
{
    FILE * file;
    char * output_file_name = (argc > 1)?argv[1]:"mp4mux_test.mp4";
    int fragmentation_mode = (argc > 2)?argv[2][0] == 'f':0;

    if (!output_file_name)
    {
        printf("ERROR: no file name given!\n");
        return 1;
    }
    file = fopen(output_file_name, "wb");
    if (!file)
    {
        printf("ERROR: can't open file %s!\n", output_file_name);
        return 1;
    }
    test(file, fragmentation_mode);

    return 0;
}

// dmc mp4mux.c -Dmp4mux_test -DMP4E_CAN_USE_RANDOM_FILE_ACCESS=1 && mp4mux.exe && del *.obj *.map mp4mux.exe
#endif  // mp4mux_test


text:
/** 20.09.2009 @file
*   ISO MP4 file parsing
*
*   Portability note: this module uses:
*   - Dynamic memory allocation (malloc(), realloc() and free()
*   - Direct file access (fgetc(), fread() & fseek())
*   - File size (fstat())
*
*   This module provide functions to decode mp4 indexes, and retrieve
*   file position and size for each sample in given track.
*
*   Integration scenario example:
*
*   // 1. Parse MP4 structure
*   if (MP4D__open(mp4, file_handle))
*   {
*       // 2. Find tracks, supported by the application
*       for (i = 0; i < mp4->track_count; i++)
*       {
*           if (supported(mp4->track[i].object_type_indication))
*           {
*               // 3. Initialize decoder from transparent 'Decoder Specific Info' data
*               init_decoder(mp4->track[i].dsi);
*
*               // 4. Read track data for each frame
*               for (k = 0; k < mp4->track[i].sample_count; k++)
*               {
*                   fseek(file_handle, MP4D__frame_offset(mp4, i, k, &frame_size, NULL, NULL), SEEK_SET);
*                   fread(buf, frame_size, 1, file_handle);
*                   decode(buf);
*               }
*           }
*       }
*   }
*
*/

#ifndef mp4demux_H_INCLUDED
#define mp4demux_H_INCLUDED

#include <stdio.h>
#include "mp4defs.h"

#ifdef __cplusplus
extern "C" {
#endif  //__cplusplus

/************************************************************************/
/*                  Portable 64-bit type definition                     */
/************************************************************************/

#if (defined(__GNUC__) && __GNUC__ >= 4) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901)
#   include <stdint.h>  // hope that all GCC compilers support this C99 extension
    typedef uint64_t mp4d_size_t;
#else
#   if defined (_MSC_VER)
    typedef unsigned __int64    mp4d_size_t;
#   else
    typedef unsigned long long  mp4d_size_t;
#   endif
    typedef unsigned int        uint32_t;
#endif

typedef struct
{
    unsigned         first_chunk;
    unsigned         samples_per_chunk;
} MP4D_sample_to_chunk_t;

typedef struct
{
    /************************************************************************/
    /*                 mandatory public data                                */
    /************************************************************************/
    // How many 'samples' in the track
    // The 'sample' is MP4 term, denoting audio or video frame
    unsigned sample_count;

    // Decoder-specific info (DSI) data
    unsigned char * dsi;

    // DSI data size
    unsigned dsi_bytes;

    // MP4 object type code
    // case 0x00: return "Forbidden";
    // case 0x01: return "Systems ISO/IEC 14496-1";
    // case 0x02: return "Systems ISO/IEC 14496-1";
    // case 0x20: return "Visual ISO/IEC 14496-2";
    // case 0x40: return "Audio ISO/IEC 14496-3";
    // case 0x60: return "Visual ISO/IEC 13818-2 Simple Profile";
    // case 0x61: return "Visual ISO/IEC 13818-2 Main Profile";
    // case 0x62: return "Visual ISO/IEC 13818-2 SNR Profile";
    // case 0x63: return "Visual ISO/IEC 13818-2 Spatial Profile";
    // case 0x64: return "Visual ISO/IEC 13818-2 High Profile";
    // case 0x65: return "Visual ISO/IEC 13818-2 422 Profile";
    // case 0x66: return "Audio ISO/IEC 13818-7 Main Profile";
    // case 0x67: return "Audio ISO/IEC 13818-7 LC Profile";
    // case 0x68: return "Audio ISO/IEC 13818-7 SSR Profile";
    // case 0x69: return "Audio ISO/IEC 13818-3";
    // case 0x6A: return "Visual ISO/IEC 11172-2";
    // case 0x6B: return "Audio ISO/IEC 11172-3";
    // case 0x6C: return "Visual ISO/IEC 10918-1";
    unsigned object_type_indication;

    /************************************************************************/
    /*                 informational public data, not needed for decoding   */
    /************************************************************************/
    // handler_type when present in a media box, is an integer containing one of
    // the following values, or a value from a derived specification:
    // 'vide' Video track
    // 'soun' Audio track
    // 'hint' Hint track
    unsigned handler_type;

    // Track duration: 64-bit value split into 2 variables
    unsigned duration_hi;
    unsigned duration_lo;

    // duration scale: duration = timescale*seconds
    unsigned timescale;

    // Average bitrate, bits per second
    unsigned avg_bitrate_bps;

    // Track language: 3-char ISO 639-2T code: "und", "eng", "rus", "jpn" etc...
    unsigned char language[4];

    // MP4 stream type
    // case 0x00: return "Forbidden";
    // case 0x01: return "ObjectDescriptorStream";
    // case 0x02: return "ClockReferenceStream";
    // case 0x03: return "SceneDescriptionStream";
    // case 0x04: return "VisualStream";
    // case 0x05: return "AudioStream";
    // case 0x06: return "MPEG7Stream";
    // case 0x07: return "IPMPStream";
    // case 0x08: return "ObjectContentInfoStream";
    // case 0x09: return "MPEGJStream";
    unsigned stream_type;

    union
    {
        // for handler_type == 'soun' tracks
        struct
        {
            unsigned channelcount;
            unsigned samplerate_hz;
        } audio;

        // for handler_type == 'vide' tracks
        struct
        {
            unsigned width;
            unsigned height;
        } video;
    } SampleDescription;

    /************************************************************************/
    /*                 private data: MP4 indexes                            */
    /************************************************************************/
    unsigned *entry_size;   // [sample_count]
    unsigned *timestamp;    // [sample_count]
    unsigned *duration;     // [sample_count]

    unsigned sample_to_chunk_count;
    MP4D_sample_to_chunk_t * sample_to_chunk;    // [sample_to_chunk_count]

    unsigned chunk_count;
    mp4d_size_t * chunk_offset;  // [chunk_count]

} MP4D_track_t;

typedef struct MP4D_demux_tag
{
    /************************************************************************/
    /*                 mandatory public data                                */
    /************************************************************************/
    // number of tracks in the movie
    unsigned track_count;

    // track data (public/private)
    MP4D_track_t * track;

    /************************************************************************/
    /*                 informational public data, not needed for decoding   */
    /************************************************************************/
    // Movie duration: 64-bit value split into 2 variables
    unsigned duration_hi;
    unsigned duration_lo;

    // duration scale: duration = timescale*seconds
    unsigned timescale;

    // Metadata tag (optional)
    // Tags provided 'as-is', without any re-encoding
    struct
    {
        unsigned char *title;
        unsigned char *artist;
        unsigned char *album;
        unsigned char *year;
        unsigned char *comment;
        unsigned char *genre;
    } tag;

} MP4D_demux_t;

/**
*   Parse given file as MP4 file.  Allocate and store data indexes.
*   return 1 on success, 0 on failure
*   Given file rewind()'ed on return.
*   The MP4 indexes may be stored at the end of file, so this
*   function may parse all file, using fseek().
*   It is guaranteed that function will read/seek the file sequentially,
*   and will never jump back.
*/
int MP4D__open(MP4D_demux_t * mp4, FILE * f);

/**
*   Return position and size for given sample from given track. The 'sample' is a
*   MP4 term for 'frame'
*
*   frame_bytes [OUT]   - return coded frame size in bytes
*   timestamp [OUT]     - return frame timestamp (in mp4->timescale units)
*   duration [OUT]      - return frame duration (in mp4->timescale units)
*
*   function return file offset for the frame
*/
mp4d_size_t MP4D__frame_offset(const MP4D_demux_t * mp4, unsigned int ntrack, unsigned int nsample, unsigned int * frame_bytes, unsigned * timestamp, unsigned * duration);

/**
*   De-allocated memory
*/
void MP4D__close(MP4D_demux_t * mp4);

/**
*   Helper functions to parse mp4.track[ntrack].dsi for H.264 SPS/PPS
*   Return pointer to internal mp4 memory, it must not be free()-ed
*
*   Example: process all SPS in MP4 file:
*       while (sps = MP4D__read_sps(mp4, num_of_avc_track, sps_count, &sps_bytes))
*       {
*           process(sps, sps_bytes);
*           sps_count++;
*       }
*/
const unsigned char * MP4D__read_sps(const MP4D_demux_t * mp4, unsigned int ntrack, int nsps, int * sps_bytes);
const unsigned char * MP4D__read_pps(const MP4D_demux_t * mp4, unsigned int ntrack, int npps, int * pps_bytes);

/**
*   Decode MP4D_track_t::stream_type to ASCII string
*/
const char * MP4D__stream_type_to_ascii(int stream_type);

/**
*   Decode MP4D_track_t::object_type_indication to ASCII string
*/
const char * MP4D__object_type_to_ascii(int object_type_indication);

#ifdef __cplusplus
}
#endif //__cplusplus

#endif //mp4demux_H_INCLUDED

repo_language: C

mime_type: text/x-c


text:
/** 25.09.2018 @file
*
*   Common MP4 definitions
*
*   Acronyms:
*       AVC = Advanced Video Coding (AKA H.264)
*       AAC = Advanced Audio Coding
*       OD  = Object descriptor
*       DSI = Decoder Specific Info (AAC element)
*       LC  = Low Complexity (AAC profile)
*       SPS = Sequence Parameter Set (H.264 element)
*       PPS = Picture Parameter Set (H.264 element)
*
*   The MP4 file has several tracks. Each track contains a number of 'samples'
*   (audio or video frames). Position and size of each sample in the track
*   encoded in the track index.
*
*/

#ifndef mp4defs_H_INCLUDED
#define mp4defs_H_INCLUDED

/************************************************************************/
/*          Some values of MP4X_track_t::object_type_indication         */
/************************************************************************/
enum
{
    // MPEG-4 AAC (all profiles)
    MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3 = 0x40,

    // MPEG-2 AAC, Main profile
    MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_MAIN_PROFILE = 0x66,

    // MPEG-2 AAC, LC profile
    MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_LC_PROFILE = 0x67,

    // MPEG-2 AAC, SSR profile
    MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_SSR_PROFILE = 0x68,

    // H.264 (AVC) video
    MP4_OBJECT_TYPE_AVC = 0x21,

    // http://www.mp4ra.org/object.html 0xC0-E0  && 0xE2 - 0xFE are specified as "user private"
    MP4_OBJECT_TYPE_USER_PRIVATE = 0xC0
};

/************************************************************************/
/*          Some values of MP4X_track_t::handler_type                   */
/************************************************************************/
enum
{
    // Video track : 'vide'
    MP4_HANDLER_TYPE_VIDE = 0x76696465,
    // Audio track : 'soun'
    MP4_HANDLER_TYPE_SOUN = 0x736F756E,
    // General MPEG-4 systems streams (without specific handler).
    // Used for private stream, as suggested in http://www.mp4ra.org/handler.html
    MP4_HANDLER_TYPE_GESM = 0x6765736D,
    // Text comment track
    MP4_HANDLER_TYPE_MDIR = 0x6d646972
};

/************************************************************************/
/*      Complete box list (most of them not used here)                  */
/************************************************************************/
#define FOUR_CHAR_INT( a, b, c, d ) (((a)<<24)|((b)<<16)|((c)<<8)|(d))
enum
{
    BOX_co64    = FOUR_CHAR_INT( 'c', 'o', '6', '4' ),//ChunkLargeOffsetAtomType
    BOX_stco    = FOUR_CHAR_INT( 's', 't', 'c', 'o' ),//ChunkOffsetAtomType
    BOX_crhd    = FOUR_CHAR_INT( 'c', 'r', 'h', 'd' ),//ClockReferenceMediaHeaderAtomType
    BOX_ctts    = FOUR_CHAR_INT( 'c', 't', 't', 's' ),//CompositionOffsetAtomType
    BOX_cprt    = FOUR_CHAR_INT( 'c', 'p', 'r', 't' ),//CopyrightAtomType
    BOX_url_    = FOUR_CHAR_INT( 'u', 'r', 'l', ' ' ),//DataEntryURLAtomType
    BOX_urn_    = FOUR_CHAR_INT( 'u', 'r', 'n', ' ' ),//DataEntryURNAtomType
    BOX_dinf    = FOUR_CHAR_INT( 'd', 'i', 'n', 'f' ),//DataInformationAtomType
    BOX_dref    = FOUR_CHAR_INT( 'd', 'r', 'e', 'f' ),//DataReferenceAtomType
    BOX_stdp    = FOUR_CHAR_INT( 's', 't', 'd', 'p' ),//DegradationPriorityAtomType
    BOX_edts    = FOUR_CHAR_INT( 'e', 'd', 't', 's' ),//EditAtomType
    BOX_elst    = FOUR_CHAR_INT( 'e', 'l', 's', 't' ),//EditListAtomType
    BOX_uuid    = FOUR_CHAR_INT( 'u', 'u', 'i', 'd' ),//ExtendedAtomType
    BOX_free    = FOUR_CHAR_INT( 'f', 'r', 'e', 'e' ),//FreeSpaceAtomType
    BOX_hdlr    = FOUR_CHAR_INT( 'h', 'd', 'l', 'r' ),//HandlerAtomType
    BOX_hmhd    = FOUR_CHAR_INT( 'h', 'm', 'h', 'd' ),//HintMediaHeaderAtomType
    BOX_hint    = FOUR_CHAR_INT( 'h', 'i', 'n', 't' ),//HintTrackReferenceAtomType
    BOX_mdia    = FOUR_CHAR_INT( 'm', 'd', 'i', 'a' ),//MediaAtomType
    BOX_mdat    = FOUR_CHAR_INT( 'm', 'd', 'a', 't' ),//MediaDataAtomType
    BOX_mdhd    = FOUR_CHAR_INT( 'm', 'd', 'h', 'd' ),//MediaHeaderAtomType
    BOX_minf    = FOUR_CHAR_INT( 'm', 'i', 'n', 'f' ),//MediaInformationAtomType
    BOX_moov    = FOUR_CHAR_INT( 'm', 'o', 'o', 'v' ),//MovieAtomType
    BOX_mvhd    = FOUR_CHAR_INT( 'm', 'v', 'h', 'd' ),//MovieHeaderAtomType
    BOX_stsd    = FOUR_CHAR_INT( 's', 't', 's', 'd' ),//SampleDescriptionAtomType
    BOX_stsz    = FOUR_CHAR_INT( 's', 't', 's', 'z' ),//SampleSizeAtomType
    BOX_stz2    = FOUR_CHAR_INT( 's', 't', 'z', '2' ),//CompactSampleSizeAtomType
    BOX_stbl    = FOUR_CHAR_INT( 's', 't', 'b', 'l' ),//SampleTableAtomType
    BOX_stsc    = FOUR_CHAR_INT( 's', 't', 's', 'c' ),//SampleToChunkAtomType
    BOX_stsh    = FOUR_CHAR_INT( 's', 't', 's', 'h' ),//ShadowSyncAtomType
    BOX_skip    = FOUR_CHAR_INT( 's', 'k', 'i', 'p' ),//SkipAtomType
    BOX_smhd    = FOUR_CHAR_INT( 's', 'm', 'h', 'd' ),//SoundMediaHeaderAtomType
    BOX_stss    = FOUR_CHAR_INT( 's', 't', 's', 's' ),//SyncSampleAtomType
    BOX_stts    = FOUR_CHAR_INT( 's', 't', 't', 's' ),//TimeToSampleAtomType
    BOX_trak    = FOUR_CHAR_INT( 't', 'r', 'a', 'k' ),//TrackAtomType
    BOX_tkhd    = FOUR_CHAR_INT( 't', 'k', 'h', 'd' ),//TrackHeaderAtomType
    BOX_tref    = FOUR_CHAR_INT( 't', 'r', 'e', 'f' ),//TrackReferenceAtomType
    BOX_udta    = FOUR_CHAR_INT( 'u', 'd', 't', 'a' ),//UserDataAtomType
    BOX_vmhd    = FOUR_CHAR_INT( 'v', 'm', 'h', 'd' ),//VideoMediaHeaderAtomType
    BOX_url     = FOUR_CHAR_INT( 'u', 'r', 'l', ' ' ),
    BOX_urn     = FOUR_CHAR_INT( 'u', 'r', 'n', ' ' ),

    BOX_gnrv    = FOUR_CHAR_INT( 'g', 'n', 'r', 'v' ),//GenericVisualSampleEntryAtomType
    BOX_gnra    = FOUR_CHAR_INT( 'g', 'n', 'r', 'a' ),//GenericAudioSampleEntryAtomType

    //V2 atoms
    BOX_ftyp    = FOUR_CHAR_INT( 'f', 't', 'y', 'p' ),//FileTypeAtomType
    BOX_padb    = FOUR_CHAR_INT( 'p', 'a', 'd', 'b' ),//PaddingBitsAtomType

    //MP4 Atoms
    BOX_sdhd    = FOUR_CHAR_INT( 's', 'd', 'h', 'd' ),//SceneDescriptionMediaHeaderAtomType
    BOX_dpnd    = FOUR_CHAR_INT( 'd', 'p', 'n', 'd' ),//StreamDependenceAtomType
    BOX_iods    = FOUR_CHAR_INT( 'i', 'o', 'd', 's' ),//ObjectDescriptorAtomType
    BOX_odhd    = FOUR_CHAR_INT( 'o', 'd', 'h', 'd' ),//ObjectDescriptorMediaHeaderAtomType
    BOX_mpod    = FOUR_CHAR_INT( 'm', 'p', 'o', 'd' ),//ODTrackReferenceAtomType
    BOX_nmhd    = FOUR_CHAR_INT( 'n', 'm', 'h', 'd' ),//MPEGMediaHeaderAtomType
    BOX_esds    = FOUR_CHAR_INT( 'e', 's', 'd', 's' ),//ESDAtomType
    BOX_sync    = FOUR_CHAR_INT( 's', 'y', 'n', 'c' ),//OCRReferenceAtomType
    BOX_ipir    = FOUR_CHAR_INT( 'i', 'p', 'i', 'r' ),//IPIReferenceAtomType
    BOX_mp4s    = FOUR_CHAR_INT( 'm', 'p', '4', 's' ),//MPEGSampleEntryAtomType
    BOX_mp4a    = FOUR_CHAR_INT( 'm', 'p', '4', 'a' ),//MPEGAudioSampleEntryAtomType
    BOX_mp4v    = FOUR_CHAR_INT( 'm', 'p', '4', 'v' ),//MPEGVisualSampleEntryAtomType

    // http://www.itscj.ipsj.or.jp/sc29/open/29view/29n7644t.doc
    BOX_avc1    = FOUR_CHAR_INT( 'a', 'v', 'c', '1' ),
    BOX_avc2    = FOUR_CHAR_INT( 'a', 'v', 'c', '2' ),
    BOX_svc1    = FOUR_CHAR_INT( 's', 'v', 'c', '1' ),
    BOX_avcC    = FOUR_CHAR_INT( 'a', 'v', 'c', 'C' ),
    BOX_svcC    = FOUR_CHAR_INT( 's', 'v', 'c', 'C' ),
    BOX_btrt    = FOUR_CHAR_INT( 'b', 't', 'r', 't' ),
    BOX_m4ds    = FOUR_CHAR_INT( 'm', '4', 'd', 's' ),
    BOX_seib    = FOUR_CHAR_INT( 's', 'e', 'i', 'b' ),

    //3GPP atoms
    BOX_samr    = FOUR_CHAR_INT( 's', 'a', 'm', 'r' ),//AMRSampleEntryAtomType
    BOX_sawb    = FOUR_CHAR_INT( 's', 'a', 'w', 'b' ),//WB_AMRSampleEntryAtomType
    BOX_damr    = FOUR_CHAR_INT( 'd', 'a', 'm', 'r' ),//AMRConfigAtomType
    BOX_s263    = FOUR_CHAR_INT( 's', '2', '6', '3' ),//H263SampleEntryAtomType
    BOX_d263    = FOUR_CHAR_INT( 'd', '2', '6', '3' ),//H263ConfigAtomType

    //V2 atoms - Movie Fragments
    BOX_mvex    = FOUR_CHAR_INT( 'm', 'v', 'e', 'x' ),//MovieExtendsAtomType
    BOX_trex    = FOUR_CHAR_INT( 't', 'r', 'e', 'x' ),//TrackExtendsAtomType
    BOX_moof    = FOUR_CHAR_INT( 'm', 'o', 'o', 'f' ),//MovieFragmentAtomType
    BOX_mfhd    = FOUR_CHAR_INT( 'm', 'f', 'h', 'd' ),//MovieFragmentHeaderAtomType
    BOX_traf    = FOUR_CHAR_INT( 't', 'r', 'a', 'f' ),//TrackFragmentAtomType
    BOX_tfhd    = FOUR_CHAR_INT( 't', 'f', 'h', 'd' ),//TrackFragmentHeaderAtomType
    BOX_trun    = FOUR_CHAR_INT( 't', 'r', 'u', 'n' ),//TrackFragmentRunAtomType
    BOX_mehd    = FOUR_CHAR_INT( 'm', 'e', 'h', 'd' ),//MovieExtendsHeaderBox

    // Object Descriptors (OD) data coding
    // These takes only 1 byte; this implementation translate <od_tag> to
    // <od_tag> + OD_BASE to keep API uniform and safe for string functions
    OD_BASE    = FOUR_CHAR_INT( '$', '$', '$', '0' ),//
    OD_ESD     = FOUR_CHAR_INT( '$', '$', '$', '3' ),//SDescriptor_Tag
    OD_DCD     = FOUR_CHAR_INT( '$', '$', '$', '4' ),//DecoderConfigDescriptor_Tag
    OD_DSI     = FOUR_CHAR_INT( '$', '$', '$', '5' ),//DecoderSpecificInfo_Tag
    OD_SLC     = FOUR_CHAR_INT( '$', '$', '$', '6' ),//SLConfigDescriptor_Tag

    BOX_meta   = FOUR_CHAR_INT( 'm', 'e', 't', 'a' ),
    BOX_ilst   = FOUR_CHAR_INT( 'i', 'l', 's', 't' ),

    // Metagata tags, see http://atomicparsley.sourceforge.net/mpeg-4files.html
    BOX_calb    = FOUR_CHAR_INT( '\xa9', 'a', 'l', 'b'),    // album
    BOX_cart    = FOUR_CHAR_INT( '\xa9', 'a', 'r', 't'),    // artist
    BOX_aART    = FOUR_CHAR_INT( 'a', 'A', 'R', 'T' ),      // album artist
    BOX_ccmt    = FOUR_CHAR_INT( '\xa9', 'c', 'm', 't'),    // comment
    BOX_cday    = FOUR_CHAR_INT( '\xa9', 'd', 'a', 'y'),    // year (as string)
    BOX_cnam    = FOUR_CHAR_INT( '\xa9', 'n', 'a', 'm'),    // title
    BOX_cgen    = FOUR_CHAR_INT( '\xa9', 'g', 'e', 'n'),    // custom genre (as string or as byte!)
    BOX_trkn    = FOUR_CHAR_INT( 't', 'r', 'k', 'n'),       // track number (byte)
    BOX_disk    = FOUR_CHAR_INT( 'd', 'i', 's', 'k'),       // disk number (byte)
    BOX_cwrt    = FOUR_CHAR_INT( '\xa9', 'w', 'r', 't'),    // composer
    BOX_ctoo    = FOUR_CHAR_INT( '\xa9', 't', 'o', 'o'),    // encoder
    BOX_tmpo    = FOUR_CHAR_INT( 't', 'm', 'p', 'o'),       // bpm (byte)
    BOX_cpil    = FOUR_CHAR_INT( 'c', 'p', 'i', 'l'),       // compilation (byte)
    BOX_covr    = FOUR_CHAR_INT( 'c', 'o', 'v', 'r'),       // cover art (JPEG/PNG)
    BOX_rtng    = FOUR_CHAR_INT( 'r', 't', 'n', 'g'),       // rating/advisory (byte)
    BOX_cgrp    = FOUR_CHAR_INT( '\xa9', 'g', 'r', 'p'),    // grouping
    BOX_stik    = FOUR_CHAR_INT( 's', 't', 'i', 'k'),       // stik (byte)  0 = Movie   1 = Normal  2 = Audiobook  5 = Whacked Bookmark  6 = Music Video  9 = Short Film  10 = TV Show  11 = Booklet  14 = Ringtone
    BOX_pcst    = FOUR_CHAR_INT( 'p', 'c', 's', 't'),       // podcast (byte)
    BOX_catg    = FOUR_CHAR_INT( 'c', 'a', 't', 'g'),       // category
    BOX_keyw    = FOUR_CHAR_INT( 'k', 'e', 'y', 'w'),       // keyword
    BOX_purl    = FOUR_CHAR_INT( 'p', 'u', 'r', 'l'),       // podcast URL (byte)
    BOX_egid    = FOUR_CHAR_INT( 'e', 'g', 'i', 'd'),       // episode global unique ID (byte)
    BOX_desc    = FOUR_CHAR_INT( 'd', 'e', 's', 'c'),       // description
    BOX_clyr    = FOUR_CHAR_INT( '\xa9', 'l', 'y', 'r'),    // lyrics (may be > 255 bytes)
    BOX_tven    = FOUR_CHAR_INT( 't', 'v', 'e', 'n'),       // tv episode number
    BOX_tves    = FOUR_CHAR_INT( 't', 'v', 'e', 's'),       // tv episode (byte)
    BOX_tvnn    = FOUR_CHAR_INT( 't', 'v', 'n', 'n'),       // tv network name
    BOX_tvsh    = FOUR_CHAR_INT( 't', 'v', 's', 'h'),       // tv show name
    BOX_tvsn    = FOUR_CHAR_INT( 't', 'v', 's', 'n'),       // tv season (byte)
    BOX_purd    = FOUR_CHAR_INT( 'p', 'u', 'r', 'd'),       // purchase date
    BOX_pgap    = FOUR_CHAR_INT( 'p', 'g', 'a', 'p'),       // Gapless Playback (byte)

    //BOX_aart   = FOUR_CHAR_INT( 'a', 'a', 'r', 't' ),     // Album artist
    BOX_cART    = FOUR_CHAR_INT( '\xa9', 'A', 'R', 'T'),    // artist
    BOX_gnre    = FOUR_CHAR_INT( 'g', 'n', 'r', 'e'),

    // 3GPP metatags  (http://cpansearch.perl.org/src/JHAR/MP4-Info-1.12/Info.pm)
    BOX_auth    = FOUR_CHAR_INT( 'a', 'u', 't', 'h'),       // author
    BOX_titl    = FOUR_CHAR_INT( 't', 'i', 't', 'l'),       // title
    BOX_dscp    = FOUR_CHAR_INT( 'd', 's', 'c', 'p'),       // description
    BOX_perf    = FOUR_CHAR_INT( 'p', 'e', 'r', 'f'),       // performer
    BOX_mean    = FOUR_CHAR_INT( 'm', 'e', 'a', 'n'),       //
    BOX_name    = FOUR_CHAR_INT( 'n', 'a', 'm', 'e'),       //
    BOX_data    = FOUR_CHAR_INT( 'd', 'a', 't', 'a'),       //

    // these from http://lists.mplayerhq.hu/pipermail/ffmpeg-devel/2008-September/053151.html
    BOX_albm    = FOUR_CHAR_INT( 'a', 'l', 'b', 'm'),      // album
    BOX_yrrc    = FOUR_CHAR_INT( 'y', 'r', 'r', 'c')       // album

};

#endif //mp4defs_H_INCLUDED


text:
_FILENAME=${0##*/}
CUR_DIR=${0/${_FILENAME}}
CUR_DIR=$(cd $(dirname ${CUR_DIR}); pwd)/$(basename ${CUR_DIR})/
FLAGS="-flto -O3 -std=gnu11 -fno-stack-protector -ffunction-sections -fdata-sections -Wl,--gc-sections -lm"
DEFS="-DNDEBUG"

pushd $CUR_DIR/..

gcc ${FLAGS} ${DEFS} -o mp4mux_stream_x86  src/mp4mux.c  -Dmp4mux_test -DMP4E_CAN_USE_RANDOM_FILE_ACCESS=0
gcc ${FLAGS} ${DEFS} -o mp4mux_file_x86  src/mp4mux.c -Dmp4mux_test -DMP4E_CAN_USE_RANDOM_FILE_ACCESS=1
gcc ${FLAGS} ${DEFS} -o mp4demux_x86  src/mp4demux.c   -Dmp4demux_test
gcc ${FLAGS} ${DEFS} -o mp4transcode_x86  test/mp4transcode_test.c src/mp4mux.c src/mp4demux.c -Isrc


text:
_FILENAME=${0##*/}
CUR_DIR=${0/${_FILENAME}}
CUR_DIR=$(cd $(dirname ${CUR_DIR}); pwd)/$(basename ${CUR_DIR})/

pushd $CUR_DIR/..

./mp4mux_stream_x86 mp4mux_stream.mp4
if ! cmp ./mp4mux_stream.mp4 vectors/ref/mp4mux_stream.mp4 >/dev/null 2>&1
then
    echo test failed
    exit 1
fi

./mp4mux_file_x86 mp4mux_file.mp4
if ! cmp ./mp4mux_file.mp4 vectors/ref/mp4mux_file.mp4 >/dev/null 2>&1
then
    echo test failed
    exit 1
fi

./mp4demux_x86 mp4mux_stream.mp4
if ! cmp ./track0.audio vectors/ref/track0.audio >/dev/null 2>&1
then
    echo test failed
    exit 1
fi
if ! cmp ./track1.264 vectors/ref/track1.264 >/dev/null 2>&1
then
    echo test failed
    exit 1
fi
if ! cmp ./track2.data vectors/ref/track2.data >/dev/null 2>&1
then
    echo test failed
    exit 1
fi
rm track0.audio
rm track1.264
rm track2.data

./mp4demux_x86 mp4mux_file.mp4
if ! cmp ./track0.audio vectors/ref/track0.audio >/dev/null 2>&1
then
    echo test failed
    exit 1
fi
if ! cmp ./track1.264 vectors/ref/track1.264 >/dev/null 2>&1
then
    echo test failed
    exit 1
fi
if ! cmp ./track2.data vectors/ref/track2.data >/dev/null 2>&1
then
    echo test failed
    exit 1
fi
rm track0.audio
rm track1.264
rm track2.data

./mp4transcode_x86 mp4mux_file.mp4 mp4mux_stream.mp4
if ! cmp ./transcoded.mp4 vectors/ref/transcoded.mp4 >/dev/null 2>&1
then
    echo test failed
    exit 1
fi
rm mp4mux_stream.mp4
rm mp4mux_file.mp4
rm transcoded.mp4

qemu-arm ./mp4mux_stream_arm_gcc mp4mux_stream.mp4
if ! cmp ./mp4mux_stream.mp4 vectors/ref/mp4mux_stream.mp4 >/dev/null 2>&1
then
    echo test failed
    exit 1
fi

qemu-arm ./mp4mux_file_arm_gcc mp4mux_file.mp4
if ! cmp ./mp4mux_file.mp4 vectors/ref/mp4mux_file.mp4 >/dev/null 2>&1
then
    echo test failed
    exit 1
fi

qemu-arm ./mp4demux_arm_gcc mp4mux_stream.mp4
if ! cmp ./track0.audio vectors/ref/track0.audio >/dev/null 2>&1
then
    echo test failed
    exit 1
fi
if ! cmp ./track1.264 vectors/ref/track1.264 >/dev/null 2>&1
then
    echo test failed
    exit 1
fi
if ! cmp ./track2.data vectors/ref/track2.data >/dev/null 2>&1
then
    echo test failed
    exit 1
fi
rm track0.audio
rm track1.264
rm track2.data

qemu-arm ./mp4demux_arm_gcc mp4mux_file.mp4
if ! cmp ./track0.audio vectors/ref/track0.audio >/dev/null 2>&1
then
    echo test failed
    exit 1
fi
if ! cmp ./track1.264 vectors/ref/track1.264 >/dev/null 2>&1
then
    echo test failed
    exit 1
fi
if ! cmp ./track2.data vectors/ref/track2.data >/dev/null 2>&1
then
    echo test failed
    exit 1
fi
rm track0.audio
rm track1.264
rm track2.data

qemu-arm ./mp4transcode_arm_gcc mp4mux_file.mp4 mp4mux_stream.mp4
if ! cmp ./transcoded.mp4 vectors/ref/transcoded.mp4 >/dev/null 2>&1
then
    echo test failed
    exit 1
fi

rm mp4mux_stream.mp4
rm mp4mux_file.mp4
rm transcoded.mp4

echo test passed


text:
_FILENAME=${0##*/}
CUR_DIR=${0/${_FILENAME}}
CUR_DIR=$(cd $(dirname ${CUR_DIR}); pwd)/$(basename ${CUR_DIR})/
FLAGS=-"static -mcpu=cortex-a8 -mfpu=neon -mfloat-abi=hard -flto -O3 -std=gnu11 -ffast-math -fomit-frame-pointer -ftree-vectorize -lm"
DEFS="-DNDEBUG"

pushd $CUR_DIR/..

arm-linux-gnueabihf-gcc ${FLAGS} ${DEFS} -o mp4mux_stream_arm_gcc  src/mp4mux.c  -Dmp4mux_test -DMP4E_CAN_USE_RANDOM_FILE_ACCESS=0
arm-linux-gnueabihf-gcc ${FLAGS} ${DEFS} -o mp4mux_file_arm_gcc  src/mp4mux.c -Dmp4mux_test -DMP4E_CAN_USE_RANDOM_FILE_ACCESS=1
arm-linux-gnueabihf-gcc ${FLAGS} ${DEFS} -o mp4demux_arm_gcc  src/mp4demux.c   -Dmp4demux_test
arm-linux-gnueabihf-gcc ${FLAGS} ${DEFS} -o mp4transcode_arm_gcc  test/mp4transcode_test.c src/mp4mux.c src/mp4demux.c -Isrc


text:
/** ASP 21.12.2016 @file
*
*/

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <math.h>
#include <memory.h>
#include "mp4demux.h"
#include "mp4mux.h"
// #include "mp4mux.c"
// #include "mp4demux.c"

int transcode(int argc, char* argv[])
{
    unsigned i, ntrack = 0;
    int ninput;
    int fragmentation_mode = 0;
    MP4E_mux_t * mux = MP4E__open(fopen("transcoded.mp4", "wb"), fragmentation_mode);

    for (ninput = 0; ninput < 2; ninput++)
    {
        MP4D_demux_t mp4 = {0,};
        char * file_name = (argc>1+ninput)?argv[1+ninput]:"input.mp4";
        FILE * input_file = fopen(file_name, "rb");
        if (!input_file)
        {
            printf("\ncant open %s\n", file_name);
            break;
        }
        MP4D__open(&mp4, input_file);

        for (ntrack = 0; ntrack < mp4.track_count; ntrack++)
//        for (ntrack = mp4.track_count - 1; ntrack >= 0; ntrack--)
        {
            MP4D_track_t *tr = mp4.track + ntrack;
            unsigned sum_duration = 0;
            MP4E_track_t tre;
            int trid;

            tre.object_type_indication = tr->object_type_indication;

            // Take video track from 1st file and audio from 2nd
            if (!(
            tr->handler_type == MP4_HANDLER_TYPE_VIDE && ninput == 0 ||
            tr->handler_type == MP4_HANDLER_TYPE_SOUN && ninput == 1
            )
            )
            {
                continue;
            }

            memcpy(tre.language, tr->language, 4);
            if (tr->handler_type == MP4_HANDLER_TYPE_VIDE)
            {
                tre.track_media_kind = e_video;
                tre.u.v.width = tr->SampleDescription.video.width;
                tre.u.v.height = tr->SampleDescription.video.height;
            }
            if (tr->handler_type == MP4_HANDLER_TYPE_SOUN)
            {
                tre.track_media_kind = e_audio;
                tre.u.a.channelcount = tr->SampleDescription.audio.channelcount;
            }
            if (tr->handler_type == MP4_HANDLER_TYPE_GESM)
            {
                tre.track_media_kind = e_private;
            }
            tre.time_scale = tr->timescale;
            tre.default_duration = tr->duration_lo;

            trid  = MP4E__add_track(mux, &tre);

            if (mp4.track[ntrack].object_type_indication == MP4_OBJECT_TYPE_AVC)
            {
                int sps_bytes, nsps;
                const void * sps;
                for (nsps = 0; NULL != (sps = MP4D__read_sps(&mp4, ntrack, nsps, &sps_bytes)); nsps++)
                {
                    MP4E__set_sps(mux, trid, sps, sps_bytes);
                }
                for (nsps = 0; NULL != (sps = MP4D__read_pps(&mp4, ntrack, nsps, &sps_bytes)); nsps++)
                {
                    MP4E__set_pps(mux, trid, sps, sps_bytes);
                }
            }
            else
            {
                MP4E__set_dsi(mux, trid, tr->dsi, tr->dsi_bytes);
            }
#define MAX_FRAMES ~0u
            for (i = 0; i < mp4.track[ntrack].sample_count && (tr->handler_type != MP4_HANDLER_TYPE_VIDE || i < MAX_FRAMES); i++)
            {
                int sample_kind = MP4E_SAMPLE_DEFAULT;
                unsigned frame_bytes, timestamp, duration;
                mp4d_size_t ofs = MP4D__frame_offset(&mp4, ntrack, i, &frame_bytes, &timestamp, &duration);
                unsigned char *mem = malloc(frame_bytes);
                sum_duration += duration;
                fseek(input_file, (long)ofs, SEEK_SET);
                fread(mem, 1, frame_bytes, input_file);

                if (!i || (tr->handler_type == MP4_HANDLER_TYPE_SOUN))
                {
                    sample_kind  = MP4E_SAMPLE_RANDOM_ACCESS;
                }

                // Ensure video duration is > 1 sec, extending last video frame duration
                if ((i == mp4.track[ntrack].sample_count-1 ||
                     i == (MAX_FRAMES-1)
                    )
                    && (tr->handler_type == MP4_HANDLER_TYPE_VIDE))
                {
                    if (sum_duration < tr->timescale)
                    {
                        duration += 100 + tr->timescale - sum_duration;
                    }
                }

                MP4E__put_sample(mux, trid, mem, frame_bytes, duration, sample_kind);
                free(mem);
            }
        }
        fclose(input_file);
        MP4D__close(&mp4);
    }

    MP4E__set_text_comment(mux, "transcoded");
    MP4E__close(mux);
    return 0;
}

int main(int argc, char* argv[])
{
    return transcode(argc, argv);
}


text:
theme: jekyll-theme-cayman
plugins:
  - jemoji

repo_name: frrrenchies/frrrenchies

mime_type: text/plain


text:
Version: 1.0

RestoreWorkspace: Default
SaveWorkspace: Default
AlwaysSaveHistory: Default

EnableCodeIndexing: Yes
UseSpacesForTab: Yes
NumSpacesForTab: 2
Encoding: UTF-8

RnwWeave: Sweave
LaTeX: pdfLaTeX


text:
# :rooster: frrrenchies :rooster:

Le répertoire collaboratif des **packages R** qui permettent de travailler avec des données concernant la France et des **ressources pédagogiques ou d'entraide francophones**.

Caveat : Pour le moment, le répertoire est limité aux packages permettant de travaillant avec des données concernant la France mais on pourra très bien ouvrir à d'autres pays francophones.

N'hésitez pas à contribuer !

## :package: Packages :package:

* [baacker](https://github.com/Bakaniko/baacker) par @Bakaniko : un package pour accéder à la [base de données des accidents corporels de la circulation](https://www.data.gouv.fr/datasets/53698f4ca3a729239d2036df)
* [banR](https://github.com/joelgombin/banR) par @joelgombin et @pachevalier : géocoder des données avec la base adresse nationale
* [BARIS](https://github.com/feddelegrand7/BARIS) par @feddelegrand7 : accéder directement aux données de data‧gouv.fr depuis R
* [CARTElette](https://github.com/antuki/CARTElette) par @antuki : création de couches cartographiques à partir du code officiel géographique (COG) et des couches IGN
* [COGiter](https://github.com/MaelTheuliere/COGiter) par @MaelTheuliere : Fonctions, données et fonds de carte pour gérer les données au territoire
* [COGugaison](https://github.com/antuki/COGugaison) par @antuki : package utile à la gestion des codes officiels géographiques (COG) dans les tables de données pour utiliser des données géographiques en historique
* [csatimer](https://github.com/benjaminguinaudeau/csatimer) par @benjaminguinaudeau : accès aux données du temps de parole (ARCOM, ex CSA) mise en forme.
* [DoReMIFaSol](https://inseefrlab.github.io/DoReMIFaSol) par [@pierre-lamarche](https://github.com/pierre-lamarche) : récupérer et utiliser les données de l'Insee avec R
* [entreprises](https://github.com/pachevalier/entreprises) par @pachevalier : accéder aux données des entreprises
* [fodr](https://github.com/Tutuchan/fodr) par @Tutuchan : un client pour accéder à l'API d'opendatasoft et récupérer les données de différents portails open data français
* [gouvdown](https://github.com/spyrales/gouvdown) par @RLesur, @tvroylandt, @MaelTheuliere, implémentation de la marque Etat pour ggplot et Rmarkdown.
* [insee](https://inseefr.github.io/R-Insee-Data/) par @[hadrilec](https://github.com/hadrilec) : accéder à la base de données macroéconomiques de l'Insee
* [inseeLocalData](https://github.com/InseeFrLab/inseeLocalData) : client R pour l'[API Données Locales](https://api.insee.fr/catalogue/site/themes/wso2/subthemes/insee/pages/item-info.jag?name=DonneesLocales&version=V0.1&provider=insee) de l'Insee.
* [legislatives2017](https://github.com/ThinkR-open/legislatives2017) par @ThinkR-open : les données des élections législatives dans un package R
* [lexiquer](https://github.com/ColinFay/lexiquer) : Un wrapper autour de la base de données lexicale Lexique 3.81.
* [opendatarte](https://cderv.github.io/opendatarte/) : Un wrapper pour accéder aux données du [portail RTE](https://data.rte-france.com/) développé par @cderv
* [nomensland](https://github.com/GuillaumePressiat/nomensland) par @GuillaumePressiat : nomenclatures et classifications pour le [PMSI](https://fr.wikipedia.org/wiki/Programme_de_m%C3%A9dicalisation_des_syst%C3%A8mes_d%27information)
* [pmeasyr](https://github.com/IM-APHP/pmeasyr) par  @GuillaumePressiat : lire les données du PMSI dans R
* [prenoms](https://github.com/ThinkR-open/prenoms) par @ThinkR-open : les [données des prénoms](https://www.data.gouv.fr/fr/datasets/fichier-des-prenoms-edition-2016/) dans un package R
* [propre‧rpls](https://gitlab.com/rdes_dreal/propre.rpls) : un package R pour automatiser la production des publications sur le parc social des Dreals
* [proustr](https://github.com/ColinFay/proustr) par @ColinFay :  outils pour le traitement du langage en français, et corpus de textes de Proust.
* [rgeoapi](https://github.com/ColinFay/rgeoapi) par @ColinFay : un client R pour l'[API Géo](https://api.gouv.fr/api/api-geo.html)
* [vacancesscolr](https://github.com/Tutuchan/vacancesscolr) par @Tutuchan : les [données des vacances scolaires](https://www.data.gouv.fr/fr/datasets/vacances-scolaires-par-zones/) dans R

## :books: Ressources pédagogiques :books:

* [Introduction à R et au tidyverse](https://juba.github.io/tidyverse/) par @juba
* [Logiciel R et programmation](http://egallic.fr/Enseignement/R/m1_stat_eco_logiciel_R.pdf) par @3wen
* [Programmer en R](https://fr.wikibooks.org/wiki/Programmer_en_R), wikibook collaboratif (licence CC-BY-SA)
* [Introduction à l'analyse d'enquêtes avec R et RStudio](http://larmarange.github.io/analyse-R/) par @larmarange
* [R et espace](https://framabook.org/r-et-espace/) : manuel d’initiation à la programmation avec R appliqué à l’analyse de l’information géographique, librement téléchargeable en ligne.
* Le [pôle bioinformatique lyonnais](http://pbil.univ-lyon1.fr/R/) propose depuis longtemps une somme très importante de documents, qui comprend des cours complets de statistiques utilisant R.
* [Introduction à la programmation en R](https://cran.r-project.org/doc/contrib/Goulet_introduction_programmation_R.pdf)
* [C'est l'enfeR](https://bioinfo-fr.net/cest-lenfer) donne des exemples de code simple avec R qui donnent des résultats surprenants.
* [Documentation francophone référencée sur CRAN](https://cran.opencpu.org/other-docs.html#nenglish)
* [Manuel d’analyse spatiale : Théorie et mise en oeuvre pratique avec R](https://www.insee.fr/fr/information/3635442), Insee Méthodes n° 131 - octobre 2018 par [@inseefr](https://github.com/inseefr)
* [Se former au logiciel R : initiation et perfectionnement](https://myrbookfr.netlify.com/), François Rebaudo, 2019-02-13.
* [Le grimoire statistique : Contes et stats R](http://perso.ens-lyon.fr/lise.vaudor/grimoireStat/_book/intro.html) par @lvaudor
* [UtilitR](https://www.utilitr.org/) manuel de R par les agents de l'Insee
* [Les données spatiales avec R](https://github.com/MaelTheuliere/rspatial) par @MaelTheuliere, support de cours sur l'analyse spatiale avec R et sf
* [Contes et stats R](http://perso.ens-lyon.fr/lise.vaudor/grimoireStat/_book/intro.html#pourquoi-ce-livre) par Lise Vaudor

### :mortar_board: Cours en ligne :mortar_board:

Cours hébergés par *France université numérique* :

* [Introduction à la statistique avec R](https://www.fun-mooc.fr/courses/course-v1:UPSUD+42001+session08/about)
* [Analyse des données multidimensionnelles](https://www.fun-mooc.fr/courses/agrocampusouest/40001S03/session03/about)

Cours sur *DataCamp* :

* [Apprendre R dans R et en francais](https://thinkr.fr/apprendre-r-dans-r-et-en-francais/) par @vincentguyader

Cours sur *OpenClassRoom* :

* [Effectuez vos études statistiques avec R](https://openclassrooms.com/courses/effectuez-vos-etudes-statistiques-avec-r)

Cours sur *fun-mooc* :

* [Le langage R Markdown](https://www.fun-mooc.fr/c4x/UPSUD/42001S02/asset/RMarkdown.html)
* [Cours listés sur developpez.com](https://r.developpez.com/cours/)

Autres ressources pédagogiques :

* [r-init](http://r.slmc.fr/), [r-perf](http://t.slmc.fr/perf) et [r-dev](https://github.com/martinchevalier/r_dev) support des formations R données à l'Insee par @martinchevalier
* [parcours r](https://mtes-mct.github.io/parcours-r/) formation R au ministère de la transition écologique et solidaire (MTES)

### :scroll: Slides de conférences :scroll:

* [Les SIG et la cartographie avec R - Rencontres R (FR)](https://github.com/statnmap/prez/blob/master/2018-07-06_RR2018_Statnmap.pdf) - @statnmap, Rencontres R 2018, Rennes
* [Données géographiques et cartographie sous R](https://antuki.github.io/slides/180306_RLadies_COGugaison_carto.html) - @antuki, Meet-Up RLadies Paris, mars 2018
* [Le text-mining expliqué à ma grand mère](https://github.com/ColinFay/conf/blob/master/2017-11-breizh-data-club/fay_colin_tm_explique_grand_mere.pdf) - @ColinFay, Meetup du Breizh Data Club
* [Les API, un enfeR ?](https://github.com/ColinFay/conf/blob/master/2017-06-Anglet/api_enfer_colin_fay_thinkr.pdf) - @ColinFay, Rencontres R 2017
* [Cartographie avec R](http://wukan.ums-riate.fr/r2016/) - Thimothee Giraud @rCarto, [Rencontres R 2016, Toulouse, juin 2016](https://r2016-toulouse.sciencesconf.org/resource/page/id/12), un tutoriel pour s'initier à la carto dans R

### :bookmark: Aides mémoire :bookmark:
* [Aide mémoire R](http://www.duclert.org/)
* [Cheatsheets – RStudio](https://www.rstudio.com/resources/cheatsheets/), des ressources francophones au niveau du bloc *Translation*

## :speech_balloon: Entraide et échanges :speech_balloon:

### :cat: Chat et discussions instantanées :cat:

* [grrr](https://r-grrr.slack.com) : communauté Slack francophone ouverte à tous, notamment aux débutants. Pour rejoindre les discussions, [suivre le lien d'invitation](https://join.slack.com/t/r-grrr/shared_invite/zt-46utbgb9-uvo_bg5cbuxOV~H10YUX8w)
* [R bloggers en français](https://www.r-bloggers.com/lang/-/french)
* Developpez.com [https://r.developpez.com/]

### :email: Listes mail :email:

* [semin-r](https://listes.mnhn.fr/wws/subscribe/semin-r) : liste de discussion du groupe des utilisateurs de R animé par le Muséum national d’Histoire naturelle.
* [r-soc](https://groupes.renater.fr/sympa/subscribe/r-soc) : liste dédiée aux utilisateurs de R en sciences sociales.

### :busts_in_silhouette: Communautaires :busts_in_silhouette:

* [Forum du Cirad](http://forums.cirad.fr/logiciel-R/) : forum du groupe des utilisateurs de R du Cirad, ouvert à tous.
* [Abcd'R](https://abcdr.guyader.pro/) : Site collaboratif de partage de script en français
* [Rzine](http://rzine.fr/) : Site collaboratif de référencement et de partage de ressources sur la pratique de R en sciences humaines et sociales

### :newspaper: Blogs :newspaper:

* [R-atique](http://perso.ens-lyon.fr/lise.vaudor), le blog de Lise Vaudor : un certain nombre de ressources, d'astuces et de conseils à ceux souhaitant utiliser R dans leurs travaux de recherche
* [Element-R](https://elementr.hypotheses.org/), R pour les sciences humaines et sociales
* [Rgeomatique](https://rgeomatic.hypotheses.org/), R pour les travaux de géomatique
* [Dacta](http://www.dacta.fr/blog/), qui a pris le relais du blog de [superstaticienne](https://superstatisticienne.fr/)- Statistiques, R et paillettes…
* [ThinkR](https://thinkr.fr/le-blog/), le blog d'une équipe qui pense en R. On y retrouve des tutoriels et plusieurs cheatsheets en français dans le texte, parmis d'autres choses tout aussi précieuses
* [StatnMap](https://statnmap.com/post/), le blog de Sébastien Rochette : des articles orientés modèles et cartographie, mais pas que...
* [Rtask](https://rtask.thinkr.fr), le blog technique de ThinkR. Astuces et autres joies de R

## :bowtie: Meetup :bowtie:

* [Paris R-Addict](https://www.meetup.com/fr-FR/rparis/)
* [Meetup R Nantes](https://www.meetup.com/fr-FR/Meetup-R-Nantes/)
* [Meetup R-Ladies Paris](https://www.meetup.com/fr-FR/rladies-paris/)
* [R user group Toulouse](https://r-toulouse.netlify.com/)
* [Meetup R-Ladies Montpellier](https://www.meetup.com/fr-FR/rladies-montpellier/)

## :date: Séminaires et conférences :date:

* [Conférences semin-r](http://rug.mnhn.fr/semin-r/) : conférences du groupe des utilisateurs de R animé par le Muséum national d’Histoire naturelle.
* [Séminaire RUSS](https://russ.site.ined.fr/fr/) : séminaires R à l'Usage des Sciences Sociales (EHESS, INED et CNRS).
* Rencontres R et conférences useR! organisées en France :
  - [useR! 2009 Rennes](https://www.r-project.org/conferences/useR-2009/)
  - [Rencontres R 2012 Bordeaux](http://r2012.bordeaux.inria.fr/)
  - [Rencontres R 2013 Lyon](http://r2013-lyon.sciencesconf.org/)
  - [Rencontres R 2014 Montpellier](http://r2014-mtp.sciencesconf.org/)
  - [Rencontres R 2015 Grenoble](http://r2015-grenoble.sciencesconf.org/)
  - [Rencontres R 2016 Toulouse](https://r2016-toulouse.sciencesconf.org/)
  - [Rencontres R 2017 Anglet](http://angletr2017.com/)
  - [Rencontres R 2018 Rennes](https://r2018-rennes.sciencesconf.org/)
  - [useR! 2019 Toulouse](http://www.user2019.fr/)
  - [Rencontres R 2020 AgroParisTech](https://paris2020.rencontresr.fr/) (reportées à 2021)

## :hash: Bot Twitter :hash:

* [@talk_rstatsfr](https://twitter.com/talk_rstatsfr) : Un bot twitter retweetant tous les tweets contenant le hashtag #RStatsFR


text:
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!

cff-version: 1.2.0
title: Thorsten-Voice
message: >-
  Please cite Thorsten-Voice project if you use
  datasets or trained TTS models.
type: dataset
authors:
  - given-names: Thorsten
    family-names: Müller
    email: tm@thorsten-voice.de
  - given-names: Dominik
    family-names: Kreutz
repository-code: 'https://github.com/thorstenMueller/Thorsten-Voice'
url: 'https://www.Thorsten-Voice.de'
abstract: >-
  A free to use, offline working, high quality german
  TTS voice should be available for every project
  without any license struggling.
keywords:
  - Thorsten
  - Voice
  - Open
  - German
  - TTS
  - Dataset

repo_name: thorstenMueller/Thorsten-Voice

repo_language: Python