You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2382 lines
72 KiB
2382 lines
72 KiB
/*****************************************************************************
|
|
* 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/hevc_nal.h"
|
|
#include "../../packetizer/h264_nal.h" /* h264_AnnexB_get_spspps */
|
|
#include "../../packetizer/hxxx_nal.h"
|
|
#include "../../packetizer/iso_color_tables.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_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);
|
|
mp4mux_track_SetSamplePriv(p_stream, NULL, 0);
|
|
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, 0))
|
|
{
|
|
free(t);
|
|
return NULL;
|
|
}
|
|
t->i_track_id = id;
|
|
t->i_timescale = timescale;
|
|
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_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;
|
|
}
|
|
|
|
void 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(i_data)
|
|
{
|
|
memcpy(t->sample_priv.p_data, p_data, i_data);
|
|
t->sample_priv.i_data = i_data;
|
|
}
|
|
}
|
|
}
|
|
|
|
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(mp4mux_trackinfo_t *p_track)
|
|
{
|
|
bo_t *esds;
|
|
const uint8_t *p_extradata = NULL;
|
|
int i_extradata = 0;
|
|
uint8_t *p_extradata_allocated = NULL;
|
|
|
|
switch(p_track->fmt.i_codec)
|
|
{
|
|
case VLC_CODEC_SPU:
|
|
if(p_track->fmt.subs.spu.palette[0] == SPU_PALETTE_DEFINED)
|
|
{
|
|
#ifndef WORDS_BIGENDIAN
|
|
p_extradata = p_extradata_allocated = malloc(16*4);
|
|
if(p_extradata_allocated)
|
|
{
|
|
for(int i=0; i<16; i++)
|
|
SetDWBE(&p_extradata_allocated[i*4], p_track->fmt.subs.spu.palette[i+1]);
|
|
i_extradata = 16*4;
|
|
}
|
|
#else
|
|
p_extradata = (const uint8_t *) &p_track->fmt.subs.spu.palette[1];
|
|
i_extradata = 16 * sizeof(p_track->fmt.subs.spu.palette[1]);
|
|
#endif
|
|
}
|
|
break;
|
|
default:
|
|
p_extradata = p_track->fmt.p_extra;
|
|
i_extradata = p_track->fmt.i_extra;
|
|
break;
|
|
}
|
|
|
|
/* */
|
|
int i_decoder_specific_info_size = (i_extradata > 0) ? 5 + i_extradata : 0;
|
|
|
|
esds = box_full_new("esds", 0, 0);
|
|
if(!esds)
|
|
{
|
|
free(p_extradata_allocated);
|
|
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_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]);
|
|
|
|
free(p_extradata_allocated);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
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 != HEVC_DCR_VPS_COUNT)
|
|
{
|
|
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 != HEVC_DCR_SPS_COUNT)
|
|
{
|
|
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 != HEVC_DCR_PPS_COUNT)
|
|
{
|
|
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 != HEVC_DCR_SEI_COUNT)
|
|
{
|
|
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 != HEVC_DCR_SEI_COUNT)
|
|
{
|
|
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 *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 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;
|
|
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_MPGA:
|
|
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;
|
|
for (int i = 0; i < 6; i++)
|
|
bo_add_8(soun, 0); // reserved;
|
|
bo_add_16be(soun, 1); // data-reference-index
|
|
|
|
/* 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
|
|
bo_add_16be(soun, 2);
|
|
// sample size
|
|
if(i_qt_version == 0 && i_uncompressed_bps == 8)
|
|
bo_add_16be(soun, 8);
|
|
else
|
|
bo_add_16be(soun, 16);
|
|
// 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 &&
|
|
p_track->fmt.audio.i_rate >= (1<<16))
|
|
{
|
|
bo_add_32be(soun, 1<<16);
|
|
srat = GetSratBox(p_track->fmt.audio.i_rate);
|
|
}
|
|
else
|
|
{
|
|
bo_add_16be(soun, p_track->fmt.audio.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_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;
|
|
for (int i = 0; i < 6; i++)
|
|
bo_add_8(vide, 0); // reserved;
|
|
bo_add_16be(vide, 1); // data-reference-index
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
/* Sample Entry Header */
|
|
for (int i = 0; i < 6; i++)
|
|
bo_add_8(text, 0); // reserved;
|
|
bo_add_16be(text, 1); // data-reference-index
|
|
|
|
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
|
|
{
|
|
for (int i = 0; i < 6; i++)
|
|
bo_add_8(text, 0); // reserved;
|
|
bo_add_16be(text, 1); // data-reference-index
|
|
|
|
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;
|
|
|
|
/* Sample Entry Header */
|
|
for (int i = 0; i < 6; i++)
|
|
bo_add_8(tx3g, 0); // reserved;
|
|
bo_add_16be(tx3g, 1); // data-reference-index
|
|
|
|
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;
|
|
|
|
/* Sample Entry Header */
|
|
for (int i = 0; i < 6; i++)
|
|
bo_add_8(wvtt, 0); // reserved;
|
|
bo_add_16be(wvtt, 1); // data-reference-index
|
|
|
|
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;
|
|
|
|
/* Sample Entry Header */
|
|
for (int i = 0; i < 6; i++)
|
|
bo_add_8(stpp, 0); // reserved;
|
|
bo_add_16be(stpp, 1); // data-reference-index
|
|
|
|
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, 0);
|
|
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_MP4A:
|
|
case VLC_CODEC_MP4V:
|
|
case VLC_CODEC_MPGA:
|
|
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_H264:
|
|
if(!p_fmt->i_extra && 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_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:
|
|
/* Special case with smooth headers where we need to force frag TTML */
|
|
/* TTML currently not supported in sout, until we can keep original timestamps */
|
|
return i_brand == BRAND_smoo;
|
|
case VLC_CODEC_QTXT:
|
|
case VLC_CODEC_TX3G:
|
|
case VLC_CODEC_WEBVTT:
|
|
return !b_fragmented;
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|