Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  libmp4mux.c   Sprache: C

 

/*****************************************************************************
 * libmp4mux.c: mp4/mov muxer
 *****************************************************************************
 * Copyright (C) 2001, 2002, 2003, 2006, 20115 VLC authors and VideoLAN
 *
 * Authors: Laurent Aimar <fenrir@via.ecp.fr>
 *          Gildas Bazin <gbazin at videolan dot org>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include "libmp4mux.h"
#include "../../demux/mp4/libmp4.h" /* flags */
#include "../../packetizer/h266_nal.h"
#include "../../packetizer/hevc_nal.h"
#include "../../packetizer/h264_nal.h" /* h264_AnnexB_get_spspps */
#include "../../packetizer/hxxx_nal.h"
#include "../../packetizer/iso_color_tables.h"
#include "../../codec/ttml/ttml.h"

#include <vlc_arrays.h>
#include <vlc_es.h>
#include <vlc_iso_lang.h>
#include <vlc_bits.h>
#include <vlc_arrays.h>
#include <vlc_text_style.h>
#include <assert.h>
#include <time.h>

struct mp4mux_trackinfo_t
{
    unsigned i_track_id;
    es_format_t   fmt;

    /* index */
    unsigned int i_samples_count;
    unsigned int i_samples_max;
    mp4mux_sample_t *samples;

    /* XXX: needed for other codecs too, see lavf */
    struct
    {
        size_t   i_data;
        uint8_t *p_data;
    } sample_priv;

    /* stats */
    vlc_tick_t   i_read_duration;
    uint32_t     i_timescale;
    bool         b_hasbframes;
    enum mp4mux_interlacing e_interlace;

    /* frags */
    vlc_tick_t   i_trex_default_length;
    uint32_t     i_trex_default_size;

    /* edit list */
    unsigned int i_edits_count;
    mp4mux_edit_t *p_edits;

};

struct mp4mux_handle_t
{
    unsigned options;
    vlc_array_t tracks;
    struct
    {
        vlc_fourcc_t i_major;
        uint32_t     i_minor;
        DECL_ARRAY(vlc_fourcc_t) extra;
    } brands;
};

static void mp4mux_AddExtraBrandForFormat(mp4mux_handle_t *h, const es_format_t *fmt)
{
    switch(fmt->i_codec)
    {
        case VLC_CODEC_H264:
            mp4mux_AddExtraBrand(h, BRAND_avc1);
            break;
        case VLC_CODEC_HEVC:
            mp4mux_AddExtraBrand(h, BRAND_hevc);
            break;
        case VLC_CODEC_AV1:
            mp4mux_AddExtraBrand(h, BRAND_av01);
            mp4mux_AddExtraBrand(h, BRAND_iso6);
            break;
        case VLC_CODEC_MP3:
        case VLC_CODEC_MP2:
        case VLC_CODEC_MPGA:
        case VLC_CODEC_MP4V:
        case VLC_CODEC_DIV1:
        case VLC_CODEC_DIV2:
        case VLC_CODEC_DIV3:
        case VLC_CODEC_H263:
            mp4mux_AddExtraBrand(h, BRAND_mp41);
            break;
        case VLC_CODEC_MP4A:
            mp4mux_AddExtraBrand(h, BRAND_mp41);
            if(vlc_array_count(&h->tracks) == 1)
                mp4mux_AddExtraBrand(h, BRAND_M4A);
            break;
        default:
            break;
    }
}

static bool mp4mux_trackinfo_Init(mp4mux_trackinfo_t *p_stream, unsigned i_id,
                                  uint32_t i_timescale)
{
    memset(p_stream, 0, sizeof(*p_stream));
    p_stream->i_track_id = i_id;

    p_stream->i_timescale   = i_timescale;
    es_format_Init(&p_stream->fmt, 0, 0);

    return true;
}

static void mp4mux_trackinfo_Clear(mp4mux_trackinfo_t *p_stream)
{
    es_format_Clean(&p_stream->fmt);
    int ret = mp4mux_track_SetSamplePriv(p_stream, NULL, 0);
    assert(ret == VLC_SUCCESS);

    free(p_stream->samples);
    free(p_stream->p_edits);
}

mp4mux_trackinfo_t * mp4mux_track_Add(mp4mux_handle_t *h, unsigned id,
                                      const es_format_t *fmt, uint32_t timescale)
{
    if(unlikely(id == 0))
        return NULL;
    mp4mux_trackinfo_t *t = malloc(sizeof(*t));
    if(!t || !mp4mux_trackinfo_Init(t, 0, timescale))
    {
        free(t);
        return NULL;
    }
    t->i_track_id = id;
    es_format_Init(&t->fmt, fmt->i_cat, fmt->i_codec);
    es_format_Copy(&t->fmt, fmt);
    vlc_array_append(&h->tracks, t);
    mp4mux_AddExtraBrandForFormat(h, fmt);
    return t;
}

bool mp4mux_track_AddEdit(mp4mux_trackinfo_t *t, const mp4mux_edit_t *p_newedit)
{
    if(t->i_edits_count + 1 < t->i_edits_count)
        return false;
    mp4mux_edit_t *p_realloc = vlc_reallocarray( t->p_edits,
                                                 t->i_edits_count + 1,
                                                 sizeof(*p_realloc) );
    if(unlikely(!p_realloc))
        return false;

    t->p_edits = p_realloc;
    t->p_edits[t->i_edits_count++] = *p_newedit;

    return true;
}

const mp4mux_edit_t *mp4mux_track_GetLastEdit(const mp4mux_trackinfo_t *t)
{
    if(t->i_edits_count)
        return &t->p_edits[t->i_edits_count - 1];
    else return NULL;
}

void mp4mux_track_DebugEdits(vlc_object_t *obj, const mp4mux_trackinfo_t *t)
{
    for( unsigned i=0; i<t->i_edits_count; i++ )
    {
        msg_Dbg(obj, "tk %d elst media time %" PRId64 " duration %" PRIu64 " offset %" PRId64 ,
                t->i_track_id,
                t->p_edits[i].i_start_time,
                t->p_edits[i].i_duration,
                t->p_edits[i].i_start_offset);
    }
}

bool mp4mux_track_AddSample(mp4mux_trackinfo_t *t, const mp4mux_sample_t *entry)
{
    /* XXX: -1 to always have 2 entry for easy adding of empty SPU */
    if (t->i_samples_count + 2 >= t->i_samples_max)
    {
        if(t->i_samples_max + 1000 < t->i_samples_max)
            return false;
        mp4mux_sample_t *p_realloc = vlc_reallocarray(t->samples,
                                                      t->i_samples_max + 1000,
                                                      sizeof(*p_realloc));
        if(!p_realloc)
            return false;
        t->samples = p_realloc;
        t->i_samples_max += 1000;
    }
    t->samples[t->i_samples_count++] = *entry;
    if(!t->b_hasbframes && entry->i_pts_dts != 0)
        t->b_hasbframes = true;
    t->i_read_duration += __MAX(0, entry->i_length);
    return true;
}

const mp4mux_sample_t *mp4mux_track_GetLastSample(const mp4mux_trackinfo_t *t)
{
    if(t->i_samples_count)
        return &t->samples[t->i_samples_count - 1];
    else return NULL;
}

unsigned mp4mux_track_GetSampleCount(const mp4mux_trackinfo_t *t)
{
    return t->i_samples_count;
}

void mp4mux_track_UpdateLastSample(mp4mux_trackinfo_t *t,
                                   const mp4mux_sample_t *entry)
{
    if(t->i_samples_count)
    {
        mp4mux_sample_t *e = &t->samples[t->i_samples_count - 1];
        t->i_read_duration -= e->i_length;
        t->i_read_duration += entry->i_length;
        *e = *entry;
    }
}

vlc_tick_t mp4mux_track_GetDefaultSampleDuration(const mp4mux_trackinfo_t *t)
{
    return t->i_trex_default_length;
}

uint32_t mp4mux_track_GetDefaultSampleSize(const mp4mux_trackinfo_t *t)
{
    return t->i_trex_default_size;
}

const es_format_t * mp4mux_track_GetFmt(const mp4mux_trackinfo_t *t)
{
    return &t->fmt;
}

bool mp4mux_track_HasBFrames(const mp4mux_trackinfo_t *t)
{
    return t->b_hasbframes;
}

void mp4mux_track_SetHasBFrames(mp4mux_trackinfo_t *t)
{
    t->b_hasbframes = true;
}

uint32_t mp4mux_track_GetTimescale(const mp4mux_trackinfo_t *t)
{
    return t->i_timescale;
}

vlc_tick_t mp4mux_track_GetDuration(const mp4mux_trackinfo_t *t)
{
    return t->i_read_duration;
}

void mp4mux_track_ForceDuration(mp4mux_trackinfo_t *t, vlc_tick_t d)
{
    t->i_read_duration = d;
}

uint32_t mp4mux_track_GetID(const mp4mux_trackinfo_t *t)
{
    return t->i_track_id;
}

void mp4mux_track_ChangeID(mp4mux_trackinfo_t *t, uint32_t id)
{
    t->i_track_id = id;
}

void mp4mux_track_SetInterlacing(mp4mux_trackinfo_t *t, enum mp4mux_interlacing i)
{
    t->e_interlace = i;
}

enum mp4mux_interlacing mp4mux_track_GetInterlacing(const mp4mux_trackinfo_t *t)
{
    return t->e_interlace;
}

int mp4mux_track_SetSamplePriv(mp4mux_trackinfo_t *t,
                               const uint8_t *p_data, size_t i_data)
{
    if(t->sample_priv.p_data)
    {
        free(t->sample_priv.p_data);
        t->sample_priv.p_data = NULL;
        t->sample_priv.i_data = 0;
    }

    if(p_data && i_data)
    {
        t->sample_priv.p_data = malloc(i_data);
        if (t->sample_priv.p_data == NULL)
            return VLC_ENOMEM;
        memcpy(t->sample_priv.p_data, p_data, i_data);
        t->sample_priv.i_data = i_data;
    }
    return VLC_SUCCESS;
}

bool mp4mux_track_HasSamplePriv(const mp4mux_trackinfo_t *t)
{
    return t->sample_priv.i_data != 0;
}

void mp4mux_ShiftSamples(mp4mux_handle_t *h, int64_t offset)
{
    for(size_t i_track = 0; i_track < vlc_array_count(&h->tracks); i_track++)
    {
        mp4mux_trackinfo_t *t = vlc_array_item_at_index(&h->tracks, i_track);
        for (unsigned i = 0; i < t->i_samples_count; i++)
        {
            mp4mux_sample_t *sample = t->samples;
            sample[i].i_pos += offset;
        }
    }
}

mp4mux_handle_t * mp4mux_New(enum mp4mux_options options)
{
    mp4mux_handle_t *h = malloc(sizeof(*h));
    vlc_array_init(&h->tracks);
    ARRAY_INIT(h->brands.extra);
    h->brands.i_major = 0;
    h->brands.i_minor = 0;
    h->options = options;
    return h;
}

void mp4mux_Delete(mp4mux_handle_t *h)
{
    for(size_t i=0; i<vlc_array_count(&h->tracks); i++)
    {
        mp4mux_trackinfo_t *t = vlc_array_item_at_index(&h->tracks, i);
        mp4mux_trackinfo_Clear(t);
        free(t);
    }
    vlc_array_clear(&h->tracks);
    ARRAY_RESET(h->brands.extra);
    free(h);
}

void mp4mux_Set64BitExt(mp4mux_handle_t *h)
{
    /* FIXME FIXME
     * Quicktime actually doesn't like the 64 bits extensions !!! */

    if(h->options & QUICKTIME)
        return;

    h->options |= USE64BITEXT;
}

bool mp4mux_Is(mp4mux_handle_t *h, enum mp4mux_options o)
{
    return h->options & o;
}

void mp4mux_SetBrand(mp4mux_handle_t *h, vlc_fourcc_t i_major, uint32_t i_minor)
{
    h->brands.i_major = i_major;
    h->brands.i_minor = i_minor;
    mp4mux_AddExtraBrand(h, i_major);
}

void mp4mux_AddExtraBrand(mp4mux_handle_t *h, vlc_fourcc_t b)
{
    for(int i=0; i<h->brands.extra.i_size; i++)
        if(h->brands.extra.p_elems[i] == b)
            return;
    ARRAY_APPEND(h->brands.extra, b);
}

bo_t *box_new(const char *fcc)
{
    bo_t *box = malloc(sizeof(*box));
    if (!box)
        return NULL;

    if(!bo_init(box, 1024))
    {
        bo_free(box);
        return NULL;
    }

    bo_add_32be  (box, 0);
    bo_add_fourcc(box, fcc);

    return box;
}

bo_t *box_full_new(const char *fcc, uint8_t v, uint32_t f)
{
    bo_t *box = box_new(fcc);
    if (!box)
        return NULL;

    bo_add_8     (box, v);
    bo_add_24be  (box, f);

    return box;
}

void box_fix(bo_t *box, uint32_t i_size)
{
    bo_set_32be(box, 0, i_size);
}

void box_gather (bo_t *box, bo_t *box2)
{
    if(box2 && box2->b && box && box->b)
    {
        box_fix(box2, bo_size( box2 ));
        size_t i_offset = bo_size( box );
        box->b = block_Realloc(box->b, 0, box->b->i_buffer + box2->b->i_buffer);
        if(likely(box->b))
            memcpy(&box->b->p_buffer[i_offset], box2->b->p_buffer, box2->b->i_buffer);
    }
    bo_free(box2);
}

static inline void bo_add_mp4_tag_descr(bo_t *box, uint8_t tag, uint32_t size)
{
    bo_add_8(box, tag);
    for (int i = 3; i>0; i--)
        bo_add_8(box, (size>>(7*i)) | 0x80);
    bo_add_8(box, size & 0x7F);
}

static int64_t get_timestamp(void)
{
    int64_t i_timestamp = time(NULL);

    i_timestamp += 2082844800; // MOV/MP4 start date is 1/1/1904
    // 208284480 is (((1970 - 1904) * 365) + 17) * 24 * 60 * 60

    return i_timestamp;
}

/****************************************************************************/

static void matrix_apply_rotation(es_format_t *fmt, uint32_t mvhd_matrix[9])
{
    enum video_orientation_t orientation = ORIENT_NORMAL;
    if (fmt->i_cat == VIDEO_ES)
        orientation = fmt->video.orientation;

#define ATAN(a, b) \
    do { \
        mvhd_matrix[1] = ((uint32_t)(a)) << 16; \
        mvhd_matrix[0] = ((uint32_t)(b)) << 16; \
    } while(0)

    switch (orientation) {
    case ORIENT_ROTATED_90:  ATAN( 1,  0); break;
    case ORIENT_ROTATED_180: ATAN( 0, -1); break;
    case ORIENT_ROTATED_270: ATAN(-1,  0); break;
    default:                 ATAN( 0,  1); break;
    }

    mvhd_matrix[3] = mvhd_matrix[0] ? 0 : 0x10000;
    mvhd_matrix[4] = mvhd_matrix[1] ? 0 : 0x10000;
}

static void AddEdit(bo_t *elst,
                    int64_t i_movie_scaled_duration,
                    int64_t i_media_scaled_time,
                    bool b_64_ext)
{
    if(b_64_ext)
    {
        bo_add_64be(elst, i_movie_scaled_duration);
        bo_add_64be(elst, i_media_scaled_time);
    }
    else
    {
        bo_add_32be(elst, i_movie_scaled_duration);
        bo_add_32be(elst, i_media_scaled_time);
    }
    bo_add_16be(elst, 1);
    bo_add_16be(elst, 0);
}

static bo_t *GetEDTS( mp4mux_trackinfo_t *p_track, uint32_t i_movietimescale, bool b_64_ext)
{
    if(p_track->i_edits_count == 0)
        return NULL;

    bo_t *edts = box_new("edts");
    bo_t *elst = box_full_new("elst", b_64_ext ? 1 : 0, 0);
    if(!elst || !edts)
    {
        bo_free(elst);
        bo_free(edts);
        return NULL;
    }

    uint32_t i_total_edits = p_track->i_edits_count;
    for(unsigned i=0; i<p_track->i_edits_count; i++)
    {
        /* !WARN! media time must start sample time 0, we need a -1 edit for start offsets */
        if(p_track->p_edits[i].i_start_offset != 0)
            i_total_edits++;
    }

    bo_add_32be(elst, i_total_edits);

    for(unsigned i=0; i<p_track->i_edits_count; i++)
    {
        if(p_track->p_edits[i].i_start_offset != 0)
        {
            AddEdit(elst,
                    samples_from_vlc_tick(p_track->p_edits[i].i_start_offset, i_movietimescale),
                    -1,
                    b_64_ext);
        }

        /* !WARN AGAIN! Uses different Timescales ! */
        AddEdit(elst,
                samples_from_vlc_tick(p_track->p_edits[i].i_duration, i_movietimescale),
                samples_from_vlc_tick(p_track->p_edits[i].i_start_time, p_track->i_timescale),
                b_64_ext);
    }

    box_gather(edts, elst);
    return edts;
}

static bo_t *GetESDS(const mp4mux_trackinfo_t *p_track)
{
    bo_t *esds;
    const uint8_t *p_extradata = p_track->fmt.p_extra;
    int i_extradata = p_track->fmt.i_extra;
    uint32_t local_palette[ARRAY_SIZE(p_track->fmt.subs.spu.palette)];

    switch(p_track->fmt.i_codec)
    {
        case VLC_CODEC_SPU:
            if(p_track->fmt.subs.spu.b_palette)
            {
#ifndef WORDS_BIGENDIAN
                for(size_t i=0; i<ARRAY_SIZE(p_track->fmt.subs.spu.palette); i++)
                    SetDWBE(&local_palette[i], p_track->fmt.subs.spu.palette[i]);
                p_extradata = (const uint8_t *) local_palette;
#else
                p_extradata = (const uint8_t *) p_track->fmt.subs.spu.palette;
#endif
                i_extradata = sizeof(p_track->fmt.subs.spu.palette);
            }
            break;
    }

    /* */
    int i_decoder_specific_info_size = (i_extradata > 0) ? 5 + i_extradata : 0;

    esds = box_full_new("esds", 0, 0);
    if(!esds)
    {
        return NULL;
    }

    /* Compute Max bitrate */
    int64_t i_bitrate_avg = 0;
    int64_t i_bitrate_max = 0;
    /* Compute avg/max bitrate */
    for (unsigned i = 0; i < p_track->i_samples_count; i++) {
        i_bitrate_avg += p_track->samples[i].i_size;
        if (p_track->samples[i].i_length > 0) {
            int64_t i_bitrate = CLOCK_FREQ * 8 * p_track->samples[i].i_size / p_track->samples[i].i_length;
            if (i_bitrate > i_bitrate_max)
                i_bitrate_max = i_bitrate;
        }
    }

    if (p_track->i_read_duration > 0)
        i_bitrate_avg = CLOCK_FREQ * 8 * i_bitrate_avg / p_track->i_read_duration;
    else
        i_bitrate_avg = 0;
    if (i_bitrate_max <= 1)
        i_bitrate_max = 0x7fffffff;

    /* ES_Descriptor ISO/IEC 14496-1 */
    bo_add_mp4_tag_descr(esds, 0x03, 3 + 5 + 13 + i_decoder_specific_info_size + 5 + 1);
    bo_add_16be(esds, p_track->i_track_id);
    bo_add_8   (esds, 0x1f);      // flags=0|streamPriority=0x1f

    /* DecoderConfigDescr ISO/IEC 14496-1
        http://mp4ra.org/#/object_types */

    bo_add_mp4_tag_descr(esds, 0x04, 13 + i_decoder_specific_info_size);

    uint8_t i_object_profile_indication;
    switch(p_track->fmt.i_codec)
    {
    case VLC_CODEC_MP4V:
        i_object_profile_indication = 0x20; /* Visual 14496-2 */
        break;
    case VLC_CODEC_MPGV:
        if(p_track->fmt.i_original_fourcc == VLC_CODEC_MP1V)
        {
            i_object_profile_indication = 0x6a; /* Visual ISO/IEC 11172-2  */
            break;
        }
        /* fallthrough */
    case VLC_CODEC_MP2V:
        /* MPEG-I=0x6b, MPEG-II = 0x60 -> 0x65 */
        i_object_profile_indication = 0x61; /* Visual 13818-2 Main Profile */
        break;
    case VLC_CODEC_MP1V:
        i_object_profile_indication = 0x6a; /* Visual ISO/IEC 11172-2  */
        break;
    case VLC_CODEC_MP4A:
        /* FIXME for mpeg2-aac == 0x66->0x68 */
        i_object_profile_indication = 0x40; /* Audio 14496-3 */
        break;
    case VLC_CODEC_MP3:
    case VLC_CODEC_MP2:
    case VLC_CODEC_MPGA:
        i_object_profile_indication =
            p_track->fmt.audio.i_rate < 32000 ? 0x69 /* Audio 13818-3 */
                                              : 0x6b;/* Audio 11172-3 */
        break;
    case VLC_CODEC_DTS:
        i_object_profile_indication = 0xa9; /* Core Substream */
        break;
    case VLC_CODEC_SPU:
        i_object_profile_indication = 0xe0; /* NeroDigital SPU mapping */
        break;
    default:
        i_object_profile_indication = 0xFE; /* No profile specified */
        break;
    }

    uint8_t i_stream_type;
    switch(p_track->fmt.i_cat)
    {
        case VIDEO_ES:
            i_stream_type = 0x04;
            break;
        case AUDIO_ES:
            i_stream_type = 0x05;
            break;
        case SPU_ES:
            i_stream_type = 0x0D;
            break;
        default:
            i_stream_type = 0x20; /* Private */
            break;
    }

    bo_add_8   (esds, i_object_profile_indication);
    bo_add_8   (esds, (i_stream_type << 2) | 1);
    bo_add_24be(esds, 1024 * 1024);       // bufferSizeDB
    bo_add_32be(esds, i_bitrate_max);     // maxBitrate
    bo_add_32be(esds, i_bitrate_avg);     // avgBitrate

    if (i_extradata > 0) {
        /* DecoderSpecificInfo */
        bo_add_mp4_tag_descr(esds, 0x05, i_extradata);

        for (int i = 0; i < i_extradata; i++)
            bo_add_8(esds, p_extradata[i]);
    }

    /* SL_Descr mandatory */
    bo_add_mp4_tag_descr(esds, 0x06, 1);
    bo_add_8    (esds, 0x02);  // sl_predefined

    return esds;
}

static bo_t *GetEndaTag(bool b_le)
{
    bo_t *enda = box_new("enda");
    if(enda)
        bo_add_16be(enda, !!b_le);
    return enda;
}

static bo_t *GetFrmaTag(const char format[4])
{
    bo_t *frma = box_new("frma");
    if(frma)
        bo_add_mem(frma, 4, format);
    return frma;
}

static bo_t *GetWaveTag(const char *fcc, bo_t *extraboxes[], size_t i_extraboxes)
{
    bo_t *wave;
    bo_t *box;

    wave = box_new("wave");
    if(wave)
    {
        box = GetFrmaTag(fcc);
        box_gather(wave, box);

        for(size_t i=0; i<i_extraboxes; i++)
            box_gather(wave, extraboxes[i]);

        /* The last atom in the siDecompressionParam atom must be a terminator atom. */
        bo_add_32be(wave, 8); /* new empty box */
        bo_add_32be(wave, 0); /* box label */
    }
    return wave;
}

static bo_t *GetDamrTag(es_format_t *p_fmt)
{
    bo_t *damr = box_new("damr");
    if(!damr)
        return NULL;

    bo_add_fourcc(damr, "REFC");
    bo_add_8(damr, 0);

    if (p_fmt->i_codec == VLC_CODEC_AMR_NB)
        bo_add_16be(damr, 0x81ff); /* Mode set (all modes for AMR_NB) */
    else
        bo_add_16be(damr, 0x83ff); /* Mode set (all modes for AMR_WB) */
    bo_add_16be(damr, 0x1); /* Mode change period (no restriction) */

    return damr;
}

/* OpusHead identification header, mapping table excluded:
 *   magic(8) version(1) channels(1) preskip(2le) rate(4le) gain(2le) family(1) */

#define OPUS_HEAD_SIZE 19
#define XIPH_LACING_SIZE 2

/* Opus in ISOBMFF (Opus-in-MP4 spec, ch. 4.3.2): the 'dOps' box carries the
 * same fields as the Ogg "OpusHead" identification header, minus the magic
 * number. */

static bo_t *GetDOpsTag(const uint8_t *p_extra, size_t i_extra)
{
    if(i_extra > XIPH_LACING_SIZE + OPUS_HEAD_SIZE &&
       !memcmp(&p_extra[XIPH_LACING_SIZE], "OpusHead", 8))
    {
        const size_t i_header_size = p_extra[1];
        if(i_header_size > i_extra - XIPH_LACING_SIZE)
            return NULL;
        p_extra += XIPH_LACING_SIZE;
        i_extra = i_header_size;
    }
    else if(i_extra < OPUS_HEAD_SIZE || memcmp(p_extra, "OpusHead", 8))
        return NULL;

    const uint8_t i_channels = p_extra[9];
    const uint8_t i_mapping_family = p_extra[18];
    if(i_channels == 0)
        return NULL;
    size_t i_maptable_size = 0;
    /* Family 0 carries no mapping table. */
    if(i_mapping_family != 0)
    {
        /* Family 1, 2 and 255 carry the stream counts followed by a table of
         * i_channels entries. Family 3 replaces the table with a demixing
         * matrix which the spec doesn't support. */

        if(i_mapping_family > 2 && i_mapping_family != 255)
            return NULL;
        i_maptable_size = 2 + i_channels; /* streams(1) coupled(1) map(channels) */
        if(i_extra < OPUS_HEAD_SIZE + i_maptable_size)
            return NULL;
    }

    bo_t *dops = box_new("dOps");
    if(!dops)
        return NULL;

    bo_add_8(dops, 0); /* Version 0 */
    bo_add_8(dops, i_channels);
    bo_add_16be(dops, GetWLE(&p_extra[10]));  /* PreSkip */
    bo_add_32be(dops, GetDWLE(&p_extra[12])); /* InputSampleRate */
    bo_add_16be(dops, GetWLE(&p_extra[16]));  /* OutputGain */
    bo_add_8(dops, i_mapping_family);
    if(i_maptable_size)
        bo_add_mem(dops, i_maptable_size, &p_extra[OPUS_HEAD_SIZE]);

    return dops;
}

#define FLAC_MAGIC_SIZE 4
#define FLAC_BLOCK_HEADER_SIZE 4
#define FLAC_STREAMINFO_SIZE 34

static const uint8_t *FlacStreamInfo(const uint8_t *p_extra, size_t i_extra)
{
    if(i_extra >= FLAC_MAGIC_SIZE && !memcmp(p_extra, "fLaC", FLAC_MAGIC_SIZE))
    {
        p_extra += FLAC_MAGIC_SIZE;
        i_extra -= FLAC_MAGIC_SIZE;
        /* The first block has to be a complete STREAMINFO. */
        if(i_extra < FLAC_BLOCK_HEADER_SIZE + FLAC_STREAMINFO_SIZE ||
           (p_extra[0] & 0x7F) != 0 || /* BLOCK_TYPE 0 */
           (GetDWBE(p_extra) & 0x00FFFFFF) != FLAC_STREAMINFO_SIZE)
            return NULL;
        return &p_extra[FLAC_BLOCK_HEADER_SIZE];
    }

    return (i_extra >= FLAC_STREAMINFO_SIZE) ? p_extra : NULL;
}

/* FLAC in ISOBMFF (isoflac.txt, ch. 3.3.2): the 'dfLa' box holds the FLAC
 * metadata blocks, of which only STREAMINFO is required. */

static bo_t *GetDfLaTag(const uint8_t *p_extra, size_t i_extra)
{
    const uint8_t *p_streaminfo = FlacStreamInfo(p_extra, i_extra);
    if(!p_streaminfo)
        return NULL;

    bo_t *dfla = box_new("dfLa");
    if(!dfla)
        return NULL;

    bo_add_32be(dfla, 0); /* FullBox: version 0, flags 0 */
    bo_add_8(dfla, 0x80); /* last-metadata-block=1, BLOCK_TYPE=0 */
    bo_add_24be(dfla, FLAC_STREAMINFO_SIZE);
    bo_add_mem(dfla, FLAC_STREAMINFO_SIZE, p_streaminfo);

    return dfla;
}

static bo_t *GetD263Tag(void)
{
    bo_t *d263 = box_new("d263");
    if(!d263)
        return NULL;

    bo_add_fourcc(d263, "VLC ");
    bo_add_16be(d263, 0xa);
    bo_add_8(d263, 0);

    return d263;
}

static bo_t *GetHvcCTag(const uint8_t *p_extra, size_t i_extra,
                        bool b_completeness)
{

    /* Generate hvcC box matching iso/iec 14496-15 3rd edition */
    bo_t *hvcC = box_new("hvcC");
    if(!hvcC || !i_extra)
        return hvcC;

    /* Extradata is already an HEVCDecoderConfigurationRecord */
    if(hevc_ishvcC(p_extra, i_extra))
    {
        (void) bo_add_mem(hvcC, i_extra, p_extra);
        return hvcC;
    }

    struct hevc_dcr_params params = { .i_vps_count = 0 };
    const uint8_t *p_nal;
    size_t i_nal;

    hxxx_iterator_ctx_t it;
    hxxx_iterator_init(&it, p_extra, i_extra, 0);
    while(hxxx_annexb_iterate_next(&it, &p_nal, &i_nal))
    {
        switch (hevc_getNALType(p_nal))
        {
            case HEVC_NAL_VPS:
                if(params.i_vps_count != ARRAY_SIZE(params.p_vps))
                {
                    params.p_vps[params.i_vps_count] = p_nal;
                    params.rgi_vps[params.i_vps_count] = i_nal;
                    params.i_vps_count++;
                }
                break;
            case HEVC_NAL_SPS:
                if(params.i_sps_count != ARRAY_SIZE(params.p_sps))
                {
                    params.p_sps[params.i_sps_count] = p_nal;
                    params.rgi_sps[params.i_sps_count] = i_nal;
                    params.i_sps_count++;
                }
                break;
            case HEVC_NAL_PPS:
                if(params.i_pps_count != ARRAY_SIZE(params.p_pps))
                {
                    params.p_pps[params.i_pps_count] = p_nal;
                    params.rgi_pps[params.i_pps_count] = i_nal;
                    params.i_pps_count++;
                }
                break;
            case HEVC_NAL_PREF_SEI:
                if(params.i_seipref_count != ARRAY_SIZE(params.p_seipref))
                {
                    params.p_seipref[params.i_seipref_count] = p_nal;
                    params.rgi_seipref[params.i_seipref_count] = i_nal;
                    params.i_seipref_count++;
                }
                break;
            case HEVC_NAL_SUFF_SEI:
                if(params.i_seisuff_count != ARRAY_SIZE(params.p_seisuff))
                {
                    params.p_seisuff[params.i_seisuff_count] = p_nal;
                    params.rgi_seisuff[params.i_seisuff_count] = i_nal;
                    params.i_seisuff_count++;
                }
                break;

            default:
                break;
        }
    }

    size_t i_dcr;
    uint8_t *p_dcr = hevc_create_dcr(¶ms, 4, b_completeness, &i_dcr);
    if(!p_dcr)
    {
        bo_free(hvcC);
        return NULL;
    }

    bo_add_mem(hvcC, i_dcr, p_dcr);
    free(p_dcr);

    return hvcC;
}

static bo_t *GetVvcCTag(const uint8_t *p_extra, size_t i_extra,
                        bool b_completeness)
{

    /* Generate hvcC box matching iso/iec 14496-15 3rd edition */
    bo_t *vvcC = box_full_new("vvcC", 0, 0);
    if(!vvcC || !i_extra)
        return vvcC;

    /* Extradata is already an VVCDecoderConfigurationRecord */
    if(h266_isvvcC(p_extra, i_extra))
    {
        (void) bo_add_mem(vvcC, i_extra, p_extra);
        return vvcC;
    }

    struct h266_dcr_params params = { .i_vps_count = 0, .p_values = NULL };
    const uint8_t *p_nal;
    size_t i_nal;

    hxxx_iterator_ctx_t it;
    hxxx_iterator_init(&it, p_extra, i_extra, 0);
    while(hxxx_annexb_iterate_next(&it, &p_nal, &i_nal))
        h266_add_NALtoParams(p_nal, i_nal, ¶ms);

    size_t i_dcr;
    uint8_t *p_dcr = h266_create_DecoderConfigurationRecord(¶ms, b_completeness, &i_dcr);
    if(!p_dcr)
    {
        bo_free(vvcC);
        return NULL;
    }

    bo_add_mem(vvcC, i_dcr, p_dcr);
    free(p_dcr);

    return vvcC;
}

static bo_t *GetWaveFormatExTag(es_format_t *p_fmt, const char *tag)
{
    bo_t *box = box_new(tag);
    if(!box)
        return NULL;

    uint16_t wFormatTag;
    fourcc_to_wf_tag(p_fmt->i_codec, &wFormatTag);
    bo_add_16le(box, wFormatTag); //wFormatTag
    bo_add_16le(box, p_fmt->audio.i_channels); //nChannels
    bo_add_32le(box, p_fmt->audio.i_rate); //nSamplesPerSec
    bo_add_32le(box, p_fmt->i_bitrate / 8); //nAvgBytesPerSec
    bo_add_16le(box, p_fmt->audio.i_blockalign); //nBlockAlign
    bo_add_16le(box, p_fmt->audio.i_bitspersample);  //wBitsPerSample
    bo_add_16le(box, p_fmt->i_extra); //cbSize

    bo_add_mem(box, p_fmt->i_extra, p_fmt->p_extra);

    return box;
}

static bo_t *GetxxxxTag(const uint8_t *p_extra, size_t i_extra,
                        const char *tag)
{
    bo_t *box = box_new(tag);
    if(!box)
        return NULL;
    bo_add_mem(box, i_extra, p_extra);
    return box;
}

static bo_t *GetFielBox(enum mp4mux_interlacing i)
{
    bo_t *p_box = box_new("fiel");
    if(p_box)
    {
        const uint16_t values[] =
        {
            [INTERLACING_NONE]         = 0x0100,
            [INTERLACING_SINGLE_FIELD] = 0x0200,
            [INTERLACING_TOPBOTTOM]    = 0x0201,
            [INTERLACING_BOTTOMTOP   ] = 0x0206,
        };
        bo_add_16be(p_box, values[i]);
    }
    return p_box;
}

static bo_t *GetColrBox(const video_format_t *p_vfmt, bool b_mov)
{
    bo_t *p_box = box_new("colr");
    if(p_box)
    {
        bo_add_mem(p_box, 4, b_mov ? "nclc" : "nclx");
        bo_add_16be(p_box, vlc_primaries_to_iso_23001_8_cp(p_vfmt->primaries));
        bo_add_16be(p_box, vlc_xfer_to_iso_23001_8_tc(p_vfmt->transfer));
        bo_add_16be(p_box, vlc_coeffs_to_iso_23001_8_mc(p_vfmt->space));
        bo_add_8(p_box, p_vfmt->color_range == COLOR_RANGE_FULL ? 0x80 : 0x00);
    }
    return p_box;
}

static bo_t *GetMdcv(const video_format_t *p_vfmt)
{
    if(!p_vfmt->mastering.max_luminance)
        return NULL;
    bo_t *p_box = box_new("mdcv");
    if(p_box)
    {
        for(int i=0; i<6; i++)
            bo_add_16be(p_box, p_vfmt->mastering.primaries[i]);
        bo_add_16be(p_box, p_vfmt->mastering.white_point[0]);
        bo_add_16be(p_box, p_vfmt->mastering.white_point[1]);
        bo_add_32be(p_box, p_vfmt->mastering.max_luminance);
        bo_add_32be(p_box, p_vfmt->mastering.min_luminance);
    }
    return p_box;
}

static bo_t *GetClli(const video_format_t *p_vfmt)
{
    if(!p_vfmt->lighting.MaxFALL)
        return NULL;
    bo_t *p_box = box_new("clli");
    if(p_box)
    {
        bo_add_16be(p_box, p_vfmt->lighting.MaxCLL);
        bo_add_16be(p_box, p_vfmt->lighting.MaxFALL);
    }
    return p_box;
}

static bo_t *GetAvcCTag(const uint8_t *p_extra, size_t i_extra)
{
    bo_t    *avcC = box_new("avcC");/* FIXME use better value */
    if(!avcC)
        return NULL;
    const uint8_t *p_sps, *p_pps, *p_ext;
    size_t i_sps_size, i_pps_size, i_ext_size;

    if(! h264_AnnexB_get_spspps(p_extra, i_extra,
                        &p_sps, &i_sps_size,
                        &p_pps, &i_pps_size,
                        &p_ext, &i_ext_size ) )
    {
        p_sps = p_pps = p_ext = NULL;
        i_sps_size = i_pps_size = i_ext_size = 0;
    }

    bo_add_8(avcC, 1);      /* configuration version */
    bo_add_8(avcC, i_sps_size > 3 ? p_sps[1] : PROFILE_H264_MAIN);
    bo_add_8(avcC, i_sps_size > 3 ? p_sps[2] : 64);
    bo_add_8(avcC, i_sps_size > 3 ? p_sps[3] : 30);       /* level, 5.1 */
    bo_add_8(avcC, 0xff);   /* 0b11111100 | lengthsize = 0x11 */

    bo_add_8(avcC, 0xe0 | (i_sps_size > 0 ? 1 : 0));   /* 0b11100000 | sps_count */
    if (i_sps_size > 0) {
        bo_add_16be(avcC, i_sps_size);
        bo_add_mem(avcC, i_sps_size, p_sps);
    }

    bo_add_8(avcC, (i_pps_size > 0 ? 1 : 0));   /* pps_count */
    if (i_pps_size > 0) {
        bo_add_16be(avcC, i_pps_size);
        bo_add_mem(avcC, i_pps_size, p_pps);
    }

    if( i_sps_size > 3 &&
       (p_sps[1] == PROFILE_H264_HIGH ||
        p_sps[1] == PROFILE_H264_HIGH_10 ||
        p_sps[1] == PROFILE_H264_HIGH_422 ||
        p_sps[1] == PROFILE_H264_HIGH_444 ||
        p_sps[1] == PROFILE_H264_HIGH_444_PREDICTIVE) )
    {
        h264_sequence_parameter_set_t *p_spsdata = h264_decode_sps( p_sps, i_sps_size, true );
        if( p_spsdata )
        {
            uint8_t data[3];
            if( h264_get_chroma_luma( p_spsdata, &data[0], &data[1], &data[2]) )
            {
                bo_add_8(avcC, 0xFC | data[0]);
                bo_add_8(avcC, 0xF8 | (data[1] - 8));
                bo_add_8(avcC, 0xF8 | (data[2] - 8));
                bo_add_8(avcC, (i_ext_size > 0 ? 1 : 0));
                if (i_ext_size > 0) {
                    bo_add_16be(avcC, i_ext_size);
                    bo_add_mem(avcC, i_ext_size, p_ext);
                }
            }
            h264_release_sps( p_spsdata );
        }
    }

    return avcC;
}

/* TODO: No idea about these values */
static bo_t *GetSVQ3Tag(const uint8_t *p_extra, size_t i_extra)
{
    bo_t *smi = box_new("SMI ");
    if(!smi)
        return NULL;

    if (i_extra > 0x4e) {
        const uint8_t *p_end = &p_extra[i_extra];
        const uint8_t *p     = &p_extra[0x46];

        while (p + 8 < p_end) {
            int i_size = GetDWBE(p);
            if (i_size <= 1) /* FIXME handle 1 as long size */
                break;
            if (!strncmp((const char *)&p[4], "SMI ", 4)) {
                bo_add_mem(smi, p_end - p - 8, &p[8]);
                return smi;
            }
            p += i_size;
        }
    }

    /* Create a dummy one in fallback */
    bo_add_fourcc(smi, "SEQH");
    bo_add_32be(smi, 0x5);
    bo_add_32be(smi, 0xe2c0211d);
    bo_add_8(smi, 0xc0);

    return smi;
}

static bo_t *GetUdtaTag(mp4mux_handle_t *muxh)
{
    bo_t *udta = box_new("udta");
    if (!udta)
        return NULL;

    /* Requirements */
    for (unsigned int i = 0; i < vlc_array_count(&muxh->tracks); i++) {
        mp4mux_trackinfo_t *p_stream = vlc_array_item_at_index(&muxh->tracks, i);
        vlc_fourcc_t codec = p_stream->fmt.i_codec;

        if (codec == VLC_CODEC_MP4V || codec == VLC_CODEC_MP4A) {
            bo_t *box = box_new("\251req");
            if(!box)
                break;
            /* String length */
            bo_add_16be(box, sizeof("QuickTime 6.0 or greater") - 1);
            bo_add_16be(box, 0);
            bo_add_mem(box, sizeof("QuickTime 6.0 or greater") - 1,
                        (uint8_t *)"QuickTime 6.0 or greater");
            box_gather(udta, box);
            break;
        }
    }

    /* Encoder */
    {
        bo_t *box = box_new("\251enc");
        if(box)
        {
            /* String length */
            bo_add_16be(box, sizeof(PACKAGE_STRING " stream output") - 1);
            bo_add_16be(box, 0);
            bo_add_mem(box, sizeof(PACKAGE_STRING " stream output") - 1,
                        (uint8_t*)PACKAGE_STRING " stream output");
            box_gather(udta, box);
        }
    }
#if 0
    /* Misc atoms */
    vlc_meta_t *p_meta = p_mux->p_sout->p_meta;
    if (p_meta) {
#define ADD_META_BOX(type, box_string) { \
        bo_t *box = NULL;  \
        if (vlc_meta_Get(p_meta, vlc_meta_##type)) \
            box = box_new("\251" box_string); \
        if (box) { \
            bo_add_16be(box, strlen(vlc_meta_Get(p_meta, vlc_meta_##type))); \
            bo_add_16be(box, 0); \
            bo_add_mem(box, strlen(vlc_meta_Get(p_meta, vlc_meta_##type)), \
                        (uint8_t*)(vlc_meta_Get(p_meta, vlc_meta_##type))); \
            box_gather(udta, box); \
        } }

        ADD_META_BOX(Title, "nam");
        ADD_META_BOX(Artist, "ART");
        ADD_META_BOX(Genre, "gen");
        ADD_META_BOX(Copyright, "cpy");
        ADD_META_BOX(Description, "des");
        ADD_META_BOX(Date, "day");
        ADD_META_BOX(URL, "url");
#undef ADD_META_BOX
    }
#endif
    return udta;
}

static bo_t *GetSratBox(uint32_t i_sample_rate)
{
    bo_t *srat = box_full_new("srat", 0, 0);
    if(srat)
        bo_add_32be(srat, i_sample_rate);
    return srat;
}

static void FillSampleDescBoxHeader(bo_t *bo, uint16_t reference_index)
{
    for (int i = 0; i < 6; i++)
        bo_add_8(bo, 0); // reserved;
    bo_add_16be(bo, reference_index);// data-reference-index
}

static bo_t *GetSounBox(vlc_object_t *p_obj, mp4mux_trackinfo_t *p_track, bool b_mov)
{
    VLC_UNUSED(p_obj);

    bool b_descr = false;
    const vlc_fourcc_t codec = p_track->fmt.i_codec;
    const audio_format_t *afmt = &p_track->fmt.audio;
    char fcc[4];
    bo_t *specificbox = NULL;
    uint16_t i_qt_version = 1;
    uint16_t i_compression_id = 0xFFFE;
    uint32_t i_uncompressed_bps = 0;
    uint32_t i_rate = afmt->i_rate;
    uint16_t i_samplesize = 16;
    bo_t *srat = NULL;

    /* codec specific extradata */
    const uint8_t *p_extradata = p_track->fmt.p_extra;
    size_t i_extradata = p_track->fmt.i_extra;
    if(p_track->sample_priv.i_data)
    {
        p_extradata = p_track->sample_priv.p_data;
        i_extradata = p_track->sample_priv.i_data;
    }

    switch(codec)
    {
        case VLC_CODEC_A52:
            memcpy(fcc, "ac-3", 4);
            if(i_extradata >= 3)
            {
                specificbox = GetxxxxTag(p_extradata, i_extradata, "dac3");
                if(b_mov)
                    specificbox = GetWaveTag(fcc, &specificbox, 1);
            }
            break;
        case VLC_CODEC_AMR_NB:
            memcpy(fcc, "samr", 4);
            specificbox = GetDamrTag(&p_track->fmt);
            if(b_mov)
                specificbox = GetWaveTag(fcc, &specificbox, 1);
            break;
        case VLC_CODEC_DTS:
            if(b_mov)
            {
                memcpy(fcc, "dtsc", 4);
            } else {
                memcpy(fcc, "mp4a", 4);
                b_descr = true;
            }
            break;
        case VLC_CODEC_EAC3:
            memcpy(fcc, "ec-3", 4);
            if(i_extradata >= 5)
            {
                specificbox = GetxxxxTag(p_extradata, i_extradata, "dec3");
                if(b_mov)
                    specificbox = GetWaveTag(fcc, &specificbox, 1);
            }
            break;
        case VLC_CODEC_MP4A:
            memcpy(fcc, "mp4a", 4);
            if(b_mov)
            {
                bo_t *extraboxes[2] = {NULL};
                extraboxes[0] = box_new("mp4a");
                if(extraboxes[0])
                    bo_add_32be(extraboxes[0], 0);
                extraboxes[1] = GetESDS(p_track);
                specificbox = GetWaveTag("mp4a", extraboxes, 2);
            } else b_descr = true;
            break;
        case VLC_CODEC_OPUS:
            memcpy(fcc, "Opus", 4);
            specificbox = GetDOpsTag(p_extradata, i_extradata);
            i_qt_version = 0;
            i_rate = 48000;
            break;
        case VLC_CODEC_FLAC:
        {
            memcpy(fcc, "fLaC", 4);
            specificbox = GetDfLaTag(p_extradata, i_extradata);
            i_qt_version = 0;
            const uint8_t *p_si = FlacStreamInfo(p_extradata, i_extradata);
            if(p_si) /* bits per sample: 5 bits at offset 100 of STREAMINFO */
                i_samplesize = (((p_si[12] & 0x01) << 4) | (p_si[13] >> 4)) + 1;
            break;
        }
        case VLC_CODEC_MPGA:
        case VLC_CODEC_MP2:
        case VLC_CODEC_MP3:
            if (b_mov) {
                /* mpeg audio in mov */
                memcpy(fcc, ".mp3", 4);
                i_qt_version = 0;
            } else {
                memcpy(fcc, "mp4a", 4);
                b_descr = true;
            }
            break;
        case VLC_CODEC_WMAP:
            memcpy(fcc, "wma ", 4);
            specificbox = GetWaveFormatExTag(&p_track->fmt, "wfex");
            break;
        // v0 only Qt codecs
        case VLC_CODEC_U8:
            i_uncompressed_bps = 8;
            i_qt_version = 0;
            memcpy(fcc, "raw ", 4);
            break;
        case VLC_CODEC_ALAW:
            i_qt_version = 0;
            memcpy(fcc, "alaw", 4);
            break;
        case VLC_CODEC_MULAW:
            i_qt_version = 0;
            memcpy(fcc, "ulaw", 4);
            break;
        case VLC_CODEC_S16B:
            i_uncompressed_bps = 16;
            i_qt_version = 0;
            memcpy(fcc, "twos", 4);
            break;
        // v1
        case VLC_CODEC_S16L:
            i_uncompressed_bps = 16;
            memcpy(fcc, "sowt", 4);
            break;
        case VLC_CODEC_S24B: /* only v1 uncompressed audio */
        case VLC_CODEC_S24L:
        case VLC_CODEC_S32B:
        case VLC_CODEC_S32L:
        case VLC_CODEC_F32B:
        case VLC_CODEC_F32L:
        case VLC_CODEC_F64B:
        case VLC_CODEC_F64L:
        {
            i_uncompressed_bps = aout_BitsPerSample(codec);
            const char * uncompressedcc;
            if(i_uncompressed_bps <= 32)
            {
                if(i_uncompressed_bps == 24)
                    uncompressedcc = "in24";
                else if((VLC_FOURCC('f',0,0,0) & codec) != VLC_FOURCC('f',0,0,0))
                    uncompressedcc = "in32";
                else
                    uncompressedcc = "fl32";
            }
            else uncompressedcc = "fl64";
            memcpy(fcc, uncompressedcc, 4);
            const bool b_le = ((codec & VLC_FOURCC(0,0,0,'b')) != VLC_FOURCC(0,0,0,'b'));
            bo_t *extraboxes[1] = { GetEndaTag(b_le) };
            specificbox = GetWaveTag(fcc, extraboxes, 1);
            break;
        }
        default:
            vlc_fourcc_to_char(codec, fcc);
            break;
    }

    bo_t *soun = box_new(fcc);
    if(!soun)
        return NULL;
    FillSampleDescBoxHeader(soun, 1);

    /* SoundDescription */
    bo_add_16be(soun, i_qt_version);
    bo_add_16be(soun, 0);         // revision level (0)
    bo_add_32be(soun, 0);         // vendor
    // channel-count
    if(i_qt_version == 0 && afmt->i_channels == 1)
        bo_add_16be(soun, 1);
    else if(b_mov)
        bo_add_16be(soun, (i_qt_version == 2) ? 3 : 2); // only 1|2 for v0/1, 3 for v3
    else
        bo_add_16be(soun, afmt->i_channels);

    // sample size
    if(i_qt_version == 0 && i_uncompressed_bps == 8)
        bo_add_16be(soun, 8);
    else
        bo_add_16be(soun, i_samplesize);
    // compression id
    if(!b_mov)
        bo_add_16be(soun, 0);
    else
        bo_add_16be(soun, i_compression_id);
    bo_add_16be(soun, 0);         // packet size (0)

    if(!b_mov && i_qt_version > 0 && i_rate >= (1<<16))
    {
        bo_add_32be(soun, 1<<16);
        srat = GetSratBox(i_rate);
    }
    else
    {
        /* Halve the rate until it fits in the 16 bits constraint of the default sound box,
         * it's a well known workaround implemented by ffmpeg and mandated by the flac spec.
         * Decoders are expected to take the real rate from the codec configuration. */

        while(i_rate >= (1<<16))
            i_rate /= 2;
        bo_add_16be(soun, i_rate);                    // sampleratehi
        bo_add_16be(soun, 0);                         // sampleratelo
    }

    /* Extended data for SoundDescription V1 */
    if (i_qt_version == 1)
    {
        const uint32_t i_samples_per_packet = (i_uncompressed_bps && i_uncompressed_bps < 16)
                                            ? 1
                                            : afmt->i_frame_length;
        const uint32_t i_bytes_per_packet = afmt->i_channels
                                          ? afmt->i_frame_length / afmt->i_channels
                                          : 0;
        bo_add_32be(soun, i_samples_per_packet);
        bo_add_32be(soun, i_bytes_per_packet);
        bo_add_32be(soun, p_track->fmt.audio.i_bytes_per_frame); /* bytes per frame */
        bo_add_32be(soun, 2); /* bytes per sample */
    }

    box_gather(soun, specificbox);

    /* Add an ES Descriptor */
    if (b_descr)
       box_gather(soun, GetESDS(p_track));

    box_gather(soun, srat);

    return soun;
}

static bo_t *GetVideBox(vlc_object_t *p_obj, mp4mux_trackinfo_t *p_track, bool b_mov)
{
    VLC_UNUSED(p_obj);

    char fcc[4];
    bool b_colr = false, b_fiel = false;

    static_assert(VLC_CODEC_YUV4 == VLC_FOURCC('y','u','v','4'), "incorrect fcc for yuv4");
    static_assert(VLC_CODEC_V210 == VLC_FOURCC('v','2','1','0'), "incorrect fcc for v210");
    static_assert(VLC_CODEC_V308 == VLC_FOURCC('v','3','0','8'), "incorrect fcc for v308");
    static_assert(VLC_CODEC_V408 == VLC_FOURCC('v','4','0','8'), "incorrect fcc for v408");
    static_assert(VLC_CODEC_V410 == VLC_FOURCC('v','4','1','0'), "incorrect fcc for v410");

    switch(p_track->fmt.i_codec)
    {
    case VLC_CODEC_MP4V:
    case VLC_CODEC_MPGV: memcpy(fcc, "mp4v", 4); break;
    case VLC_CODEC_MJPG: memcpy(fcc, "mjpa", 4); break;
    case VLC_CODEC_SVQ1: memcpy(fcc, "SVQ1", 4); break;
    case VLC_CODEC_SVQ3: memcpy(fcc, "SVQ3", 4); break;
    case VLC_CODEC_H263: memcpy(fcc, "s263", 4); break;
    case VLC_CODEC_H264: memcpy(fcc, "avc1", 4); break;
    case VLC_CODEC_VC1 : memcpy(fcc, "vc-1", 4); break;
    /* FIXME: find a way to know if no non-VCL units are in the stream (->hvc1)
     * see 14496-15 8.4.1.1.1 */

    case VLC_CODEC_HEVC: memcpy(fcc, "hev1", 4); break;
    case VLC_CODEC_VVC:  memcpy(fcc, "vvc1", 4); break;
    case VLC_CODEC_YV12: memcpy(fcc, "yv12", 4); b_colr = b_fiel = true; break;
    case VLC_CODEC_YUYV: memcpy(fcc, "YUY2", 4); b_colr = b_fiel= true; break;
    case VLC_CODEC_UYVY: memcpy(fcc, "2vuy", 4); b_colr = b_fiel= true; break;
    case VLC_CODEC_YUV4:
    case VLC_CODEC_V210:
    case VLC_CODEC_V308:
    case VLC_CODEC_V408:
    case VLC_CODEC_V410:
            vlc_fourcc_to_char(p_track->fmt.i_codec, fcc);
            b_colr = b_fiel = true;
            break;
    default:
        vlc_fourcc_to_char(p_track->fmt.i_codec, fcc);
        break;
    }

    bo_t *vide = box_new(fcc);
    if(!vide)
        return NULL;
    FillSampleDescBoxHeader(vide, 1);

    bo_add_16be(vide, 0);         // predefined;
    bo_add_16be(vide, 0);         // reserved;
    for (int i = 0; i < 3; i++)
        bo_add_32be(vide, 0);     // predefined;

    bo_add_16be(vide, p_track->fmt.video.i_visible_width);  // i_width
    bo_add_16be(vide, p_track->fmt.video.i_visible_height); // i_height

    bo_add_32be(vide, 0x00480000);                // h 72dpi
    bo_add_32be(vide, 0x00480000);                // v 72dpi

    bo_add_32be(vide, 0);         // data size, always 0
    bo_add_16be(vide, 1);         // frames count per sample

    // compressor name;
    uint8_t compressor_name[32] = {0};
    switch (p_track->fmt.i_codec)
    {
        case VLC_CODEC_AV1:
            memcpy(compressor_name, "\012AOM Coding", 11);
            break;
        default:
            break;
    }
    bo_add_mem(vide, 32, compressor_name);

    bo_add_16be(vide, 0x18);      // depth
    bo_add_16be(vide, 0xffff);    // predefined

    /* codec specific extradata */
    const uint8_t *p_extradata = p_track->fmt.p_extra;
    size_t i_extradata = p_track->fmt.i_extra;
    if(p_track->sample_priv.i_data)
    {
        p_extradata = p_track->sample_priv.p_data;
        i_extradata = p_track->sample_priv.i_data;
    }

    /* add an ES Descriptor */
    switch(p_track->fmt.i_codec)
    {
    case VLC_CODEC_AV1:
        box_gather(vide, GetxxxxTag(p_extradata, i_extradata, "av1C"));
        b_colr = true;
        break;

    case VLC_CODEC_MP4V:
    case VLC_CODEC_MPGV:
        box_gather(vide, GetESDS(p_track));
        break;

    case VLC_CODEC_H263:
        box_gather(vide, GetD263Tag());
        break;

    case VLC_CODEC_SVQ3:
        box_gather(vide, GetSVQ3Tag(p_extradata, i_extradata));
        break;

    case VLC_CODEC_H264:
        box_gather(vide, GetAvcCTag(p_extradata, i_extradata));
        break;

    case VLC_CODEC_VC1:
        box_gather(vide, GetxxxxTag(p_extradata, i_extradata, "dvc1"));
            break;

    case VLC_CODEC_HEVC:
        /* Write HvcC without forcing VPS/SPS/PPS/SEI array_completeness */
        box_gather(vide, GetHvcCTag(p_extradata, i_extradata, false));
        break;

    case VLC_CODEC_VVC:
        box_gather(vide, GetVvcCTag(p_extradata, i_extradata, false));
        break;
    }

    if(b_colr)
        box_gather(vide, GetColrBox(&p_track->fmt.video, b_mov));
    if(b_fiel)
        box_gather(vide, GetFielBox(p_track->e_interlace));

    box_gather(vide, GetMdcv(&p_track->fmt.video));
    box_gather(vide, GetClli(&p_track->fmt.video));

    return vide;
}

static bo_t *GetTextBox(vlc_object_t *p_obj, mp4mux_trackinfo_t *p_track, bool b_mov)
{
    VLC_UNUSED(p_obj);
    if(p_track->fmt.i_codec == VLC_CODEC_QTXT)
    {
        bo_t *text = box_new("text");
        if(!text)
            return NULL;

        FillSampleDescBoxHeader(text, 1);

        if(p_track->fmt.i_extra >= 44)
        {
            /* Copy the original sample description format */
            bo_add_mem(text, p_track->fmt.i_extra, p_track->fmt.p_extra);
        }
        else
        {
            bo_add_32be(text, 0);         // display flags
            bo_add_32be(text, 0);         // justification
            for (int i = 0; i < 3; i++)
                bo_add_16be(text, 0);     // background color

            bo_add_64be(text, 0);         // box text
            bo_add_64be(text, 0);         // reserved

            bo_add_16be(text, 0);         // font-number
            bo_add_16be(text, 0);         // font-face
            bo_add_8(text, 0);            // reserved
            bo_add_16be(text, 0);         // reserved

            for (int i = 0; i < 3; i++)
                bo_add_16be(text, 0xff);  // foreground color

            bo_add_8(text, 5);
            bo_add_mem(text, 5,  (void*)"Serif");
        }
        return text;
    }
    else if(p_track->fmt.i_codec == VLC_CODEC_SUBT ||
            p_track->fmt.i_codec == VLC_CODEC_TX3G)
    {
        bo_t *tx3g = box_new("tx3g");
        if(!tx3g)
            return NULL;

        FillSampleDescBoxHeader(tx3g, 1);

        if(p_track->fmt.i_codec == VLC_CODEC_TX3G &&
           p_track->fmt.i_extra >= 32)
        {
            /* Copy the original sample description format */
            bo_add_mem(tx3g, p_track->fmt.i_extra, p_track->fmt.p_extra);
        }
        else /* Build TTXT(tx3g) sample desc */
        {
            /* tx3g sample description */
            bo_add_32be(tx3g, 0);         // display flags
            bo_add_16be(tx3g, 0);         // justification

            bo_add_32be(tx3g, 0);         // background color

            /* BoxRecord */
            bo_add_64be(tx3g, 0);

            /* StyleRecord*/
            bo_add_16be(tx3g, 0);         // startChar
            bo_add_16be(tx3g, 0);         // endChar
            bo_add_16be(tx3g, 0);         // default font ID
            bo_add_8(tx3g, 0);            // face style flags
            bo_add_8(tx3g, STYLE_DEFAULT_FONT_SIZE);  // font size
            bo_add_32be(tx3g, 0xFFFFFFFFU);// foreground color

            /* FontTableBox */
            bo_t *ftab = box_new("ftab");
            if(ftab)
            {
                bo_add_16be(ftab, b_mov ? 2 : 3); // Entry Count
                /* Font Record */
                bo_add_8(ftab, 5);
                bo_add_mem(ftab, 5,  (void*)"Serif");
                bo_add_8(ftab, 10);
                bo_add_mem(ftab, 10, (void*) (b_mov ? "Sans-Serif" : "Sans-serif"));
                if(!b_mov) /* qt only allows "Serif" and "Sans-Serif" */
                {
                    bo_add_8(ftab, 9);
                    bo_add_mem(ftab, 9,  (void*)"Monospace");
                }

                box_gather(tx3g, ftab);
            }
        }

        return tx3g;
    }
    else if(p_track->fmt.i_codec == VLC_CODEC_WEBVTT)
    {
        bo_t *wvtt = box_new("wvtt");
        if(!wvtt)
            return NULL;

        FillSampleDescBoxHeader(wvtt, 1);

        bo_t *ftab = box_new("vttc");
        box_gather(wvtt, ftab);

        return wvtt;
    }
    else if(p_track->fmt.i_codec == VLC_CODEC_TTML)
    {
        bo_t *stpp = box_new("stpp");
        if(!stpp)
            return NULL;

        FillSampleDescBoxHeader(stpp, 1);

        bo_add_mem(stpp, sizeof(TT_NS)-1, TT_NS);
        bo_add_8(stpp, ' ');
        bo_add_mem(stpp, sizeof(TT_NS_STYLING), TT_NS_STYLING);
        bo_add_8(stpp, '\0'); // schema
        bo_add_8(stpp, '\0'); // ext-mime

        return stpp;
    }
    else if(p_track->fmt.i_codec == VLC_CODEC_SPU)
    {
        bo_t *mp4s = box_full_new("mp4s", 0, 0);
        bo_add_32be(mp4s, 1); // index
        box_gather(mp4s, GetESDS(p_track));
        return mp4s;
    }

    return NULL;
}

static int64_t GetScaledEntryDuration( const mp4mux_sample_t *p_entry, uint32_t i_timescale,
                                       vlc_tick_t *pi_total_mtime, int64_t *pi_total_scaled )
{
    const vlc_tick_t i_totalscaledtototalmtime = vlc_tick_from_samples(*pi_total_scaled, i_timescale);
    const vlc_tick_t i_diff = *pi_total_mtime - i_totalscaledtototalmtime;

    /* Ensure to compensate the drift due to loss from time, and from scale, conversions */
    int64_t i_scaled = samples_from_vlc_tick(p_entry->i_length + i_diff, i_timescale);
    *pi_total_mtime += p_entry->i_length;
    *pi_total_scaled += i_scaled;

    return i_scaled;
}

static bo_t *GetStblBox(vlc_object_t *p_obj, mp4mux_trackinfo_t *p_track, bool b_mov, bool b_stco64)
{
    /* sample description */
    bo_t *stsd = box_full_new("stsd", 0, 0);
    if(!stsd)
        return NULL;
    bo_add_32be(stsd, 1);
    if (p_track->fmt.i_cat == AUDIO_ES)
        box_gather(stsd, GetSounBox(p_obj, p_track, b_mov));
    else if (p_track->fmt.i_cat == VIDEO_ES)
        box_gather(stsd, GetVideBox(p_obj, p_track, b_mov));
    else if (p_track->fmt.i_cat == SPU_ES)
        box_gather(stsd, GetTextBox(p_obj, p_track, b_mov));

    /* chunk offset table */
    bo_t *stco;

    if (b_stco64) {
        /* 64 bits version */
        stco = box_full_new("co64", 0, 0);
    } else {
        /* 32 bits version */
        stco = box_full_new("stco", 0, 0);
    }
    if(!stco)
    {
        bo_free(stsd);
        return NULL;
    }
    bo_add_32be(stco, 0);     // entry-count (fixed latter)

    /* sample to chunk table */
    bo_t *stsc = box_full_new("stsc", 0, 0);
    if(!stsc)
    {
        bo_free(stco);
        bo_free(stsd);
        return NULL;
    }
    bo_add_32be(stsc, 0);     // entry-count (fixed latter)

    unsigned i_chunk = 0;
    unsigned i_stsc_last_val = 0, i_stsc_entries = 0;
    for (unsigned i = 0; i < p_track->i_samples_count; i_chunk++) {
        mp4mux_sample_t *entry = p_track->samples;
        int i_first = i;

        if (b_stco64)
            bo_add_64be(stco, entry[i].i_pos);
        else
            bo_add_32be(stco, entry[i].i_pos);

        for (; i < p_track->i_samples_count; i++)
            if (i >= p_track->i_samples_count - 1 ||
                    entry[i].i_pos + entry[i].i_size != entry[i+1].i_pos) {
                i++;
                break;
            }

        /* Add entry to the stsc table */
        if (i_stsc_last_val != i - i_first) {
            bo_add_32be(stsc, 1 + i_chunk);   // first-chunk
            bo_add_32be(stsc, i - i_first) ;  // samples-per-chunk
            bo_add_32be(stsc, 1);             // sample-descr-index
            i_stsc_last_val = i - i_first;
            i_stsc_entries++;
        }
    }

    /* Fix stco entry count */
    bo_swap_32be(stco, 12, i_chunk);
    if(p_obj)
        msg_Dbg(p_obj, "created %d chunks (stco)", i_chunk);

    /* Fix stsc entry count */
    bo_swap_32be(stsc, 12, i_stsc_entries );

    /* add stts */
    bo_t *stts = box_full_new("stts", 0, 0);
    if(!stts)
    {
        bo_free(stsd);
        bo_free(stco);
        bo_free(stsc);
        return NULL;
    }
    bo_add_32be(stts, 0);     // entry-count (fixed latter)

    vlc_tick_t i_total_mtime = 0;
    int64_t i_total_scaled = 0;
    unsigned i_index = 0;
    for (unsigned i = 0; i < p_track->i_samples_count; i_index++) {
        int     i_first = i;

        int64_t i_scaled = GetScaledEntryDuration(&p_track->samples[i], p_track->i_timescale,
                                                  &i_total_mtime, &i_total_scaled);
        for (unsigned j=i+1; j < p_track->i_samples_count; j++)
        {
            vlc_tick_t i_total_mtime_next = i_total_mtime;
            int64_t i_total_scaled_next = i_total_scaled;
            int64_t i_scalednext = GetScaledEntryDuration(&p_track->samples[j], p_track->i_timescale,
                                                          &i_total_mtime_next, &i_total_scaled_next);
            if( i_scalednext != i_scaled )
                break;

            i_total_mtime = i_total_mtime_next;
            i_total_scaled = i_total_scaled_next;
            i = j;
        }

        bo_add_32be(stts, ++i - i_first); // sample-count
        bo_add_32be(stts, i_scaled); // sample-delta
    }
    bo_swap_32be(stts, 12, i_index);

    //msg_Dbg(p_obj, "total sout duration %"PRId64" reconverted from scaled %"PRId64,
    //                i_total_mtime, vlc_tick_from_samples(i_total_scaled, p_track->i_timescale) );

    /* composition time handling */
    bo_t *ctts = NULL;
    if ( p_track->b_hasbframes && (ctts = box_full_new("ctts", 0, 0)) )
    {
        bo_add_32be(ctts, 0);
        i_index = 0;
        for (unsigned i = 0; i < p_track->i_samples_count; i_index++)
        {
            int     i_first = i;
            vlc_tick_t i_offset = p_track->samples[i].i_pts_dts;

            for (; i < p_track->i_samples_count; ++i)
                if (i == p_track->i_samples_count || p_track->samples[i].i_pts_dts != i_offset)
                    break;

            bo_add_32be(ctts, i - i_first); // sample-count
            bo_add_32be(ctts, samples_from_vlc_tick(i_offset, p_track->i_timescale) ); // sample-offset
        }
        bo_swap_32be(ctts, 12, i_index);
    }

    bo_t *stsz = box_full_new("stsz", 0, 0);
    if(!stsz)
    {
        bo_free(stsd);
        bo_free(stco);
        bo_free(stts);
        return NULL;
    }
    int i_size = 0;
    for (unsigned i = 0; i < p_track->i_samples_count; i++)
    {
        if ( i == 0 )
            i_size = p_track->samples[i].i_size;
        else if ( p_track->samples[i].i_size != i_size )
        {
            i_size = 0;
            break;
        }
    }
    bo_add_32be(stsz, i_size);                         // sample-size
    bo_add_32be(stsz, p_track->i_samples_count);       // sample-count
    if ( i_size == 0 ) // all samples have different size
    {
        for (unsigned i = 0; i < p_track->i_samples_count; i++)
            bo_add_32be(stsz, p_track->samples[i].i_size); // sample-size
    }

    /* create stss table */
    bo_t *stss = NULL;
    i_index = 0;
    if ( p_track->fmt.i_cat == VIDEO_ES || p_track->fmt.i_cat == AUDIO_ES )
    {
        vlc_tick_t i_interval = -1;
        for (unsigned i = 0; i < p_track->i_samples_count; i++)
        {
            if ( i_interval != -1 )
            {
                i_interval += p_track->samples[i].i_length + p_track->samples[i].i_pts_dts;
                if ( i_interval < VLC_TICK_FROM_SEC(2) )
                    continue;
            }

            if (p_track->samples[i].i_flags & BLOCK_FLAG_TYPE_I) {
                if (stss == NULL) {
                    stss = box_full_new("stss", 0, 0);
                    if(!stss)
                        break;
                    bo_add_32be(stss, 0); /* fixed later */
                }
                bo_add_32be(stss, 1 + i);
                i_index++;
                i_interval = 0;
            }
        }
    }

    if (stss)
        bo_swap_32be(stss, 12, i_index);

    /* Now gather all boxes into stbl */
    bo_t *stbl = box_new("stbl");
    if(!stbl)
    {
        bo_free(stsd);
        bo_free(stco);
        bo_free(stts);
        bo_free(stsz);
        bo_free(stss);
        bo_free(ctts);
        return NULL;
    }
    box_gather(stbl, stsd);
    box_gather(stbl, stts);
    if (stss)
        box_gather(stbl, stss);
    if (ctts)
        box_gather(stbl, ctts);
    box_gather(stbl, stsc);
    box_gather(stbl, stsz);
    box_gather(stbl, stco);

    return stbl;
}

static unsigned ApplyARtoWidth(const video_format_t *vfmt)
{
    if (vfmt->i_sar_num > 0 && vfmt->i_sar_den > 0)
    {
        return (int64_t)vfmt->i_sar_num *
               (int64_t)vfmt->i_visible_width / vfmt->i_sar_den;
    }
    else return vfmt->i_visible_width;
}

bo_t * mp4mux_GetMoov(mp4mux_handle_t *h, vlc_object_t *p_obj, vlc_tick_t i_duration)
{
    bo_t            *moov, *mvhd;

    uint32_t        i_movie_timescale = 90000;
    int64_t         i_timestamp = get_timestamp();

    /* Important for smooth streaming where its (not muxed here) media time offsets
     * are in timescale == track timescale */

    if( vlc_array_count(&h->tracks) == 1 )
        i_movie_timescale = ((mp4mux_trackinfo_t *)vlc_array_item_at_index(&h->tracks, 0))->i_timescale;

    moov = box_new("moov");
    if(!moov)
        return NULL;
    /* Create general info */
    if( i_duration == 0 && (h->options & FRAGMENTED) == 0 )
    {
        for (unsigned int i = 0; i < vlc_array_count(&h->tracks); i++) {
            mp4mux_trackinfo_t *p_stream = vlc_array_item_at_index(&h->tracks, i);
            i_duration = __MAX(i_duration, p_stream->i_read_duration);
        }
        if(p_obj)
            msg_Dbg(p_obj, "movie duration %"PRId64"s", SEC_FROM_VLC_TICK(i_duration));
    }
    int64_t i_movie_duration = samples_from_vlc_tick(i_duration, i_movie_timescale);

    /* *** add /moov/mvhd *** */
    if ((h->options & USE64BITEXT) == 0) {
        mvhd = box_full_new("mvhd", 0, 0);
        if(!mvhd)
        {
            bo_free(moov);
            return NULL;
        }
        bo_add_32be(mvhd, i_timestamp);   // creation time
        bo_add_32be(mvhd, i_timestamp);   // modification time
        bo_add_32be(mvhd, i_movie_timescale);  // timescale
        bo_add_32be(mvhd, i_movie_duration);  // duration
    } else {
        mvhd = box_full_new("mvhd", 1, 0);
        if(!mvhd)
        {
            bo_free(moov);
            return NULL;
        }
        bo_add_64be(mvhd, i_timestamp);   // creation time
        bo_add_64be(mvhd, i_timestamp);   // modification time
        bo_add_32be(mvhd, i_movie_timescale);  // timescale
        bo_add_64be(mvhd, i_movie_duration);  // duration
    }
    bo_add_32be(mvhd, 0x10000);           // rate
    bo_add_16be(mvhd, 0x100);             // volume
    bo_add_16be(mvhd, 0);                 // reserved
    for (int i = 0; i < 2; i++)
        bo_add_32be(mvhd, 0);             // reserved

    uint32_t mvhd_matrix[9] = { 0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000 };

    for (int i = 0; i < 9; i++)
        bo_add_32be(mvhd, mvhd_matrix[i]);// matrix
    for (int i = 0; i < 6; i++)
        bo_add_32be(mvhd, 0);             // pre-defined

    /* Next available track id */
    const mp4mux_trackinfo_t *lasttrack = vlc_array_count(&h->tracks)
                                        ? vlc_array_item_at_index(&h->tracks, vlc_array_count(&h->tracks) - 1)
                                        : NULL;
    uint32_t i_next_track_id = 1;
    if(lasttrack)
    {
        if(lasttrack->i_track_id < 0xFFFFFFFFU)
            i_next_track_id += lasttrack->i_track_id;
        else
            i_next_track_id = 0xFFFFFFFFU;
    }
    bo_add_32be(mvhd, i_next_track_id); // next-track-id

    box_gather(moov, mvhd);

    for (unsigned int i_trak = 0; i_trak < vlc_array_count(&h->tracks); i_trak++) {
        mp4mux_trackinfo_t *p_stream = vlc_array_item_at_index(&h->tracks, i_trak);

        int64_t i_stream_duration;
        if ( (h->options & FRAGMENTED) == 0 )
            i_stream_duration = samples_from_vlc_tick(p_stream->i_read_duration, i_movie_timescale);
        else
            i_stream_duration = 0;

        /* *** add /moov/trak *** */
        bo_t *trak = box_new("trak");
        if(!trak)
            continue;

        /* *** add /moov/trak/tkhd *** */
        bo_t *tkhd;
        if ((h->options & USE64BITEXT) == 0) {
            if (h->options & QUICKTIME)
                tkhd = box_full_new("tkhd", 0, 0x0f);
            else
                tkhd = box_full_new("tkhd", 0, 1);
            if(!tkhd)
            {
                bo_free(trak);
                continue;
            }
            bo_add_32be(tkhd, i_timestamp);       // creation time
            bo_add_32be(tkhd, i_timestamp);       // modification time
            bo_add_32be(tkhd, p_stream->i_track_id);
            bo_add_32be(tkhd, 0);                     // reserved 0
            bo_add_32be(tkhd, i_stream_duration); // duration
        } else {
            if (h->options & QUICKTIME)
                tkhd = box_full_new("tkhd", 1, 0x0f);
            else
                tkhd = box_full_new("tkhd", 1, 1);
            if(!tkhd)
            {
                bo_free(trak);
                continue;
            }
            bo_add_64be(tkhd, i_timestamp);       // creation time
            bo_add_64be(tkhd, i_timestamp);       // modification time
            bo_add_32be(tkhd, p_stream->i_track_id);
            bo_add_32be(tkhd, 0);                     // reserved 0
            bo_add_64be(tkhd, i_stream_duration); // duration
        }

        for (int i = 0; i < 2; i++)
            bo_add_32be(tkhd, 0);                 // reserved
        bo_add_16be(tkhd, 0);                     // layer
        bo_add_16be(tkhd, 0);                     // pre-defined
        // volume
        bo_add_16be(tkhd, p_stream->fmt.i_cat == AUDIO_ES ? 0x100 : 0);
        bo_add_16be(tkhd, 0);                     // reserved
        matrix_apply_rotation(&p_stream->fmt, mvhd_matrix);
        for (int i = 0; i < 9; i++)
            bo_add_32be(tkhd, mvhd_matrix[i]);    // matrix
        if (p_stream->fmt.i_cat == AUDIO_ES) {
            bo_add_32be(tkhd, 0);                 // width (presentation)
            bo_add_32be(tkhd, 0);                 // height(presentation)
        } else if (p_stream->fmt.i_cat == VIDEO_ES) {
            // width (presentation)
            bo_add_32be(tkhd, ApplyARtoWidth(&p_stream->fmt.video) << 16);
            // height(presentation)
            bo_add_32be(tkhd, p_stream->fmt.video.i_visible_height << 16);
        } else {
            unsigned i_width = 320;
            unsigned i_height = 200;
            /* Find video track for SPU representation */
            for (unsigned int i = 0; i < vlc_array_count(&h->tracks); i++)
            {
                const mp4mux_trackinfo_t *tk = vlc_array_item_at_index(&h->tracks, i);
                if (tk->fmt.i_cat != VIDEO_ES)
                    continue;
                i_width = ApplyARtoWidth(&tk->fmt.video);
                i_height = tk->fmt.video.i_visible_height;
                break;
            }
            bo_add_32be(tkhd, i_width << 16);     // width (presentation)
            bo_add_32be(tkhd, i_height << 16);    // height(presentation)
        }

        box_gather(trak, tkhd);

        /* *** add /moov/trak/edts and elst */
        bo_t *edts = GetEDTS(p_stream, i_movie_timescale, h->options & USE64BITEXT);
        if(edts)
            box_gather(trak, edts);

        /* *** add /moov/trak/mdia *** */
        bo_t *mdia = box_new("mdia");
        if(!mdia)
        {
            bo_free(trak);
            continue;
        }

        /* media header */
        bo_t *mdhd;
        if ((h->options & USE64BITEXT) == 0) {
            mdhd = box_full_new("mdhd", 0, 0);
            if(!mdhd)
            {
                bo_free(mdia);
                bo_free(trak);
                continue;
            }
            bo_add_32be(mdhd, i_timestamp);   // creation time
            bo_add_32be(mdhd, i_timestamp);   // modification time
            bo_add_32be(mdhd, p_stream->i_timescale); // timescale
            bo_add_32be(mdhd, i_stream_duration * p_stream->i_timescale / i_movie_timescale);  // duration
        } else {
            mdhd = box_full_new("mdhd", 1, 0);
            if(!mdhd)
            {
                bo_free(mdia);
                bo_free(trak);
                continue;
            }
            bo_add_64be(mdhd, i_timestamp);   // creation time
            bo_add_64be(mdhd, i_timestamp);   // modification time
            bo_add_32be(mdhd, p_stream->i_timescale); // timescale
            bo_add_64be(mdhd, i_stream_duration * p_stream->i_timescale / i_movie_timescale);  // duration
        }

        if (p_stream->fmt.psz_language) {
            char *psz = p_stream->fmt.psz_language;
            const iso639_lang_t *pl = vlc_find_iso639(psz, false);
            uint16_t lang = 0x0;

            if (pl)
                lang = ((pl->psz_iso639_2T[0] - 0x60) << 10) |
                       ((pl->psz_iso639_2T[1] - 0x60) <<  5) |
                       ((pl->psz_iso639_2T[2] - 0x60));
            bo_add_16be(mdhd, lang);          // language
        } else
            bo_add_16be(mdhd, 0   );          // language
        bo_add_16be(mdhd, 0   );              // predefined
        box_gather(mdia, mdhd);

        /* handler reference */
        bo_t *hdlr = box_full_new("hdlr", 0, 0);
        if(!hdlr)
        {
            bo_free(mdia);
            bo_free(trak);
            continue;
        }

        if (h->options & QUICKTIME)
            bo_add_fourcc(hdlr, "mhlr");         // media handler
        else
            bo_add_32be(hdlr, 0);

        if (p_stream->fmt.i_cat == AUDIO_ES)
            bo_add_fourcc(hdlr, "soun");
        else if (p_stream->fmt.i_cat == VIDEO_ES)
            bo_add_fourcc(hdlr, "vide");
        else if (p_stream->fmt.i_cat == SPU_ES)
        {
            /* text/tx3g 3GPP */
            /* sbtl/tx3g Apple subs */
            /* text/text Apple textmedia */
            if(p_stream->fmt.i_codec == VLC_CODEC_TX3G)
                bo_add_fourcc(hdlr, (h->options & QUICKTIME) ? "sbtl" : "text");
            else if(p_stream->fmt.i_codec == VLC_CODEC_TTML)
                bo_add_fourcc(hdlr, "sbtl");
            else if(p_stream->fmt.i_codec == VLC_CODEC_SPU)
                bo_add_fourcc(hdlr, "subp");
            else
                bo_add_fourcc(hdlr, "text");
        }

        bo_add_32be(hdlr, 0);         // reserved
        bo_add_32be(hdlr, 0);         // reserved
        bo_add_32be(hdlr, 0);         // reserved

        if (h->options & QUICKTIME)
            bo_add_8(hdlr, 12);   /* Pascal string for .mov */

        if (p_stream->fmt.i_cat == AUDIO_ES)
            bo_add_mem(hdlr, 12, (uint8_t*)"SoundHandler");
        else if (p_stream->fmt.i_cat == VIDEO_ES)
            bo_add_mem(hdlr, 12, (uint8_t*)"VideoHandler");
        else
        {
            if( p_stream->fmt.i_codec == VLC_CODEC_SPU )
            {
                char language[13] = { 0 };
                if( p_stream->fmt.psz_language )
                    strncpy( language, p_stream->fmt.psz_language, 12 );
                bo_add_mem(hdlr, 12, language);
            }
            else
                bo_add_mem(hdlr, 12, (uint8_t*)"Text Handler");
        }

        if ((h->options & QUICKTIME) == 0)
            bo_add_8(hdlr, 0);   /* asciiz string for .mp4, yes that's BRAIN DAMAGED F**K MP4 */

        box_gather(mdia, hdlr);

        /* minf*/
        bo_t *minf = box_new("minf");
        if(!minf)
        {
            bo_free(mdia);
            bo_free(trak);
            continue;
        }

        /* add smhd|vmhd */
        if (p_stream->fmt.i_cat == AUDIO_ES) {
            bo_t *smhd = box_full_new("smhd", 0, 0);
            if(smhd)
            {
                bo_add_16be(smhd, 0);     // balance
                bo_add_16be(smhd, 0);     // reserved

                box_gather(minf, smhd);
            }
        } else if (p_stream->fmt.i_cat == VIDEO_ES) {
            bo_t *vmhd = box_full_new("vmhd", 0, 1);
            if(vmhd)
            {
                bo_add_16be(vmhd, 0);     // graphicsmode
                for (int i = 0; i < 3; i++)
                    bo_add_16be(vmhd, 0); // opcolor
                box_gather(minf, vmhd);
            }
        } else if (p_stream->fmt.i_cat == SPU_ES) {
            if((h->options & QUICKTIME) &&
               (p_stream->fmt.i_codec == VLC_CODEC_SUBT||
                p_stream->fmt.i_codec == VLC_CODEC_TX3G||
                p_stream->fmt.i_codec == VLC_CODEC_QTXT))
            {
                bo_t *gmin = box_full_new("gmin", 0, 1);
                if(gmin)
                {
                    bo_add_16be(gmin, 0);     // graphicsmode
                    for (int i = 0; i < 3; i++)
                        bo_add_16be(gmin, 0); // opcolor
                    bo_add_16be(gmin, 0);     // balance
                    bo_add_16be(gmin, 0);     // reserved

                    bo_t *gmhd = box_new("gmhd");
                    if(gmhd)
                    {
                        box_gather(gmhd, gmin);
                        box_gather(minf, gmhd);
                    }
                    else bo_free(gmin);
                }
            }
        }

        /* dinf */
        bo_t *dref = box_full_new("dref", 0, 0);
        if(dref)
        {
            bo_add_32be(dref, 1);

            bo_t *url = box_full_new("url ", 0, 0x01);
            if(url)
                box_gather(dref, url);

            bo_t *dinf = box_new("dinf");
            if(dinf)
            {
                box_gather(dinf, dref);

                /* append dinf to mdia */
                box_gather(minf, dinf);
            }
            else bo_free(dref);
        }

        /* add stbl */
        bo_t *stbl;
        if (h->options & FRAGMENTED)
        {
            uint32_t i_backup = p_stream->i_samples_count;
            p_stream->i_samples_count = 0;
            stbl = GetStblBox(p_obj, p_stream, h->options & QUICKTIME, h->options & USE64BITEXT);
            p_stream->i_samples_count = i_backup;
        }
        else
            stbl = GetStblBox(p_obj, p_stream, h->options & QUICKTIME, h->options & USE64BITEXT);

        /* append stbl to minf */
        box_gather(minf, stbl);

        /* append minf to mdia */
        box_gather(mdia, minf);

        /* append mdia to trak */
        box_gather(trak, mdia);

        /* append trak to moov */
        box_gather(moov, trak);
    }

    /* Add user data tags */
    box_gather(moov, GetUdtaTag(h));

    if ( h->options & FRAGMENTED )
    {
        bo_t *mvex = box_new("mvex");
        if( mvex )
        {
            if( i_movie_duration )
            {
                bo_t *mehd = box_full_new("mehd", (h->options & USE64BITEXT) ? 1 : 0, 0);
                if(mehd)
                {
                    if((h->options & USE64BITEXT))
                        bo_add_64be(mehd, i_movie_duration);
                    else
                        bo_add_32be(mehd, i_movie_duration);
                    box_gather(mvex, mehd);
                }
            }

            for (unsigned int i = 0; mvex && i < vlc_array_count(&h->tracks); i++)
            {
                mp4mux_trackinfo_t *p_stream = vlc_array_item_at_index(&h->tracks, i);

                /* Try to find some defaults */
                if ( p_stream->i_samples_count )
                {
                    // FIXME: find highest occurrence
                    p_stream->i_trex_default_length = p_stream->samples[0].i_length;
                    p_stream->i_trex_default_size = p_stream->samples[0].i_size;
                }
                else
                {
                    p_stream->i_trex_default_length = 1;
                    p_stream->i_trex_default_size = 1;
                }

                /* *** add /mvex/trex *** */
                bo_t *trex = box_full_new("trex", 0, 0);
                bo_add_32be(trex, p_stream->i_track_id);
                bo_add_32be(trex, 1); // sample desc index
                bo_add_32be(trex, samples_from_vlc_tick(p_stream->i_trex_default_length, p_stream->i_timescale)); // sample duration
                bo_add_32be(trex, p_stream->i_trex_default_size); // sample size
                bo_add_32be(trex, 0); // sample flags
                box_gather(mvex, trex);
            }
            box_gather(moov, mvex);
        }
    }

    if(moov->b)
        box_fix(moov, bo_size(moov));
    return moov;
}

bo_t *mp4mux_GetFtyp(const mp4mux_handle_t *h)
{
    bo_t *box = box_new("ftyp");
    if(box)
    {
        bo_add_fourcc(box, &h->brands.i_major);
        bo_add_32be  (box, h->brands.i_minor);
        for(int i=0; i<h->brands.extra.i_size; i++)
            bo_add_fourcc(box, &h->brands.extra.p_elems[i]);
        if(!box->b)
        {
            free(box);
            return NULL;
        }
        box_fix(box, bo_size(box));
    }
    return box;
}

bool mp4mux_CanMux(vlc_object_t *p_obj, const es_format_t *p_fmt,
                   vlc_fourcc_t i_brand, bool b_fragmented)
{
    switch(p_fmt->i_codec)
    {
    case VLC_CODEC_U8:
    case VLC_CODEC_S16L:
    case VLC_CODEC_S16B:
    case VLC_CODEC_S24L:
    case VLC_CODEC_S24B:
    case VLC_CODEC_S32L:
    case VLC_CODEC_S32B:
    case VLC_CODEC_F32L:
    case VLC_CODEC_F32B:
    case VLC_CODEC_F64L:
    case VLC_CODEC_F64B:
    case VLC_CODEC_MULAW:
    case VLC_CODEC_ALAW:

    case VLC_CODEC_A52:
    case VLC_CODEC_DTS:
    case VLC_CODEC_EAC3:
    case VLC_CODEC_MP4V:
    case VLC_CODEC_MPGA:
    case VLC_CODEC_MP2:
    case VLC_CODEC_MP3:
    case VLC_CODEC_MPGV:
    case VLC_CODEC_MP2V:
    case VLC_CODEC_MP1V:
    case VLC_CODEC_MJPG:
    case VLC_CODEC_MJPGB:
    case VLC_CODEC_SVQ1:
    case VLC_CODEC_SVQ3:
    case VLC_CODEC_H263:
    case VLC_CODEC_AMR_NB:
    case VLC_CODEC_AMR_WB:
    case VLC_CODEC_YUV4:
    case VLC_CODEC_YV12:
    case VLC_CODEC_UYVY:
    case VLC_CODEC_YUYV:
    case VLC_CODEC_V210:
    case VLC_CODEC_V308:
    case VLC_CODEC_V408:
    case VLC_CODEC_V410:
    case VLC_CODEC_VC1:
    case VLC_CODEC_WMAP:
    case VLC_CODEC_AV1:
        break;
    case VLC_CODEC_OPUS:
    case VLC_CODEC_FLAC:
    {
        if(i_brand == BRAND_qt__)
        {
            if(p_obj)
                msg_Err(p_obj, "%4.4s has no QuickTime mapping",
                        (const char *)&p_fmt->i_codec);
            return false;
        }
        bo_t *config = (p_fmt->i_codec == VLC_CODEC_OPUS)
                     ? GetDOpsTag(p_fmt->p_extra, p_fmt->i_extra)
                     : GetDfLaTag(p_fmt->p_extra, p_fmt->i_extra);
        if(!config)
        {
            if(p_obj)
                msg_Err(p_obj, "Missing or invalid extradata for %4.4s",
                        (const char *)&p_fmt->i_codec);
            return false;
        }
        bo_free(config);
        break;
    }
    case VLC_CODEC_MP4A:
        if(!p_fmt->i_extra)
        {
            if(p_obj)
                msg_Err(p_obj, "Missing AudioDescriptor for AAC");
            return false;
        }
        break;
    case VLC_CODEC_H264:
        if(p_fmt->i_extra)
        {
            /* we only accept annexB for now */
            if(((const uint8_t*)p_fmt->p_extra)[0] == 0x01)
            {
                if(p_obj)
                    msg_Err(p_obj, "H264 input is not annexB. Missing packetizer ?");
                return false;
            }
        }
        else if(p_obj)
        {
            msg_Warn(p_obj, "H264 muxing from AnnexB source will set an incorrect default profile");
        }
        break;
    case VLC_CODEC_HEVC:
        if(!p_fmt->i_extra)
        {
            if(p_obj)
                msg_Err(p_obj, "HEVC muxing from AnnexB source is unsupported");
            return false;
        }
        break;
    case VLC_CODEC_VVC:
        if(!p_fmt->i_extra)
        {
            if(p_obj)
                msg_Err(p_obj, "H266 muxing from AnnexB source is unsupported");
            return false;
        }
        break;
    case VLC_CODEC_SPU:
            return i_brand != BRAND_qt__;
    case VLC_CODEC_SUBT:
        if(p_obj)
            msg_Warn(p_obj, "subtitle track added like in .mov (even when creating .mp4)");
        return !b_fragmented;
    case VLC_CODEC_TTML:
        return true;
    case VLC_CODEC_QTXT:
    case VLC_CODEC_TX3G:
    case VLC_CODEC_WEBVTT:
        return !b_fragmented;
    default:
        return false;
    }
    return true;
}

Messung
CHZG
84979592

¤ Dauer der Verarbeitung: 0.30 Sekunden  (vorverarbeitet am  2026-11-04) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104856
#Domains=417808