mirror of
https://github.com/vgmstream/vgmstream.git
synced 2025-02-16 10:52:32 +01:00
Move custom mp4 code from KTAC
This commit is contained in:
parent
8d893f133a
commit
bda98d71e1
@ -639,6 +639,25 @@ size_t switch_opus_get_encoder_delay(off_t offset, STREAMFILE* sf);
|
||||
size_t ue4_opus_get_encoder_delay(off_t offset, STREAMFILE* sf);
|
||||
size_t ea_opus_get_encoder_delay(off_t offset, STREAMFILE* sf);
|
||||
size_t fsb_opus_get_encoder_delay(off_t offset, STREAMFILE* sf);
|
||||
|
||||
/* ffmpeg_decoder_custom_mp4.c*/
|
||||
typedef struct {
|
||||
int channels;
|
||||
int sample_rate;
|
||||
int32_t num_samples;
|
||||
|
||||
uint32_t stream_offset;
|
||||
uint32_t stream_size;
|
||||
uint32_t table_offset;
|
||||
uint32_t table_entries;
|
||||
|
||||
int encoder_delay;
|
||||
int end_padding;
|
||||
int frame_samples;
|
||||
} mp4_custom_t;
|
||||
|
||||
ffmpeg_codec_data* init_ffmpeg_mp4_custom_std(STREAMFILE* sf, mp4_custom_t* mp4);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
446
src/coding/ffmpeg_decoder_custom_mp4.c
Normal file
446
src/coding/ffmpeg_decoder_custom_mp4.c
Normal file
@ -0,0 +1,446 @@
|
||||
#include "coding.h"
|
||||
#include "../streamfile.h"
|
||||
#include <string.h>
|
||||
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
|
||||
/**
|
||||
* Makes a MP4 header for MP4 raw data with a separate frame table, simulating a real MP4 that
|
||||
* also has such table embedded in their custom chunks.
|
||||
*/
|
||||
//TODO: segfautls with certain audio files (ffmpeg buf?)
|
||||
|
||||
/* *********************************************************** */
|
||||
|
||||
/* Helpers to make a M4A header, an insane soup of chunks (AKA "atoms").
|
||||
* Needs *A LOT* of atoms and fields so this is more elaborate than usual.
|
||||
* - https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFPreface/qtffPreface.html
|
||||
*/
|
||||
|
||||
/* generic additions */
|
||||
typedef struct {
|
||||
uint8_t* out;
|
||||
int bytes;
|
||||
} m4a_state_t;
|
||||
|
||||
typedef struct {
|
||||
STREAMFILE* sf;
|
||||
mp4_custom_t* mp4; /* config */
|
||||
uint8_t* out; /* current position */
|
||||
int bytes; /* written bytes */
|
||||
m4a_state_t chunks; /* chunks offsets are absolute, save position until we know header size */
|
||||
} m4a_header_t;
|
||||
|
||||
static void add_u32b(m4a_header_t* h, uint32_t value) {
|
||||
put_u32be(h->out, value);
|
||||
h->out += 0x04;
|
||||
h->bytes += 0x04;
|
||||
}
|
||||
|
||||
static void add_u24b(m4a_header_t* h, uint32_t value) {
|
||||
put_u16be(h->out + 0x00, (value >> 8u) & 0xFFFF);
|
||||
put_u8 (h->out + 0x02, (value >> 0u) & 0xFF);
|
||||
h->out += 0x03;
|
||||
h->bytes += 0x03;
|
||||
}
|
||||
|
||||
static void add_u16b(m4a_header_t* h, uint16_t value) {
|
||||
put_u16be(h->out, value);
|
||||
h->out += 0x02;
|
||||
h->bytes += 0x02;
|
||||
}
|
||||
|
||||
static void add_u8(m4a_header_t* h, uint32_t value) {
|
||||
put_u8(h->out, value);
|
||||
h->out += 0x01;
|
||||
h->bytes += 0x01;
|
||||
}
|
||||
|
||||
static void add_name(m4a_header_t* h, const char* name) {
|
||||
memcpy(h->out, name, 0x4);
|
||||
h->out += 0x04;
|
||||
h->bytes += 0x04;
|
||||
}
|
||||
|
||||
static void add_atom(m4a_header_t* h, const char* name, uint32_t size) {
|
||||
add_u32b(h, size);
|
||||
add_name(h, name);
|
||||
}
|
||||
|
||||
/* register + write final size for atoms of variable/complex size */
|
||||
static void save_atom(m4a_header_t* h, m4a_state_t* s) {
|
||||
s->out = h->out;
|
||||
s->bytes = h->bytes;
|
||||
}
|
||||
|
||||
static void load_atom(m4a_header_t* h, m4a_state_t* s) {
|
||||
put_u32be(s->out, h->bytes - s->bytes);
|
||||
}
|
||||
|
||||
/* common atoms */
|
||||
|
||||
static void add_ftyp(m4a_header_t* h) {
|
||||
add_atom(h, "ftyp", 0x18);
|
||||
add_name(h, "M4A "); /* major brand */
|
||||
add_u32b(h, 512); /* minor version */
|
||||
add_name(h, "isom"); /* compatible brands */
|
||||
add_name(h, "iso2"); /* compatible brands */
|
||||
}
|
||||
|
||||
static void add_free(m4a_header_t* h) {
|
||||
add_atom(h, "free", 0x08);
|
||||
}
|
||||
|
||||
static void add_mdat(m4a_header_t* h) {
|
||||
add_atom(h, "mdat", 0x08 + h->mp4->stream_size);
|
||||
}
|
||||
|
||||
/* variable atoms */
|
||||
|
||||
static void add_stco(m4a_header_t* h) {
|
||||
add_atom(h, "stco", 0x10 + 1 * 0x04);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 1); /* Number of entries */
|
||||
/* there may be an entry per frame, but only first seems needed */
|
||||
save_atom(h, &h->chunks);
|
||||
add_u32b(h, 0); /* Absolute offset N */
|
||||
}
|
||||
|
||||
static void add_stsz(m4a_header_t* h) {
|
||||
int i;
|
||||
uint32_t size;
|
||||
|
||||
add_atom(h, "stsz", 0x14 + h->mp4->table_entries * 0x04);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 0); /* Sample size (CBR) */
|
||||
add_u32b(h, h->mp4->table_entries); /* Number of entries (VBR) */
|
||||
|
||||
for (i = 0; i < h->mp4->table_entries; i++) {
|
||||
size = read_u32le(h->mp4->table_offset + i * 0x04, h->sf);
|
||||
add_u32b(h, size); /* Sample N */
|
||||
}
|
||||
}
|
||||
|
||||
static void add_stsc(m4a_header_t* h) {
|
||||
add_atom(h, "stsc", 0x1c);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 1); /* Number of entries */
|
||||
add_u32b(h, 1); /* First chunk */
|
||||
add_u32b(h, h->mp4->table_entries); /* Samples per chunk */
|
||||
add_u32b(h, 1); /* Sample description ID */
|
||||
}
|
||||
|
||||
static void add_stts(m4a_header_t* h) {
|
||||
add_atom(h, "stts", 0x18);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 1); /* Number of entries */
|
||||
add_u32b(h, h->mp4->table_entries); /* Sample count */
|
||||
add_u32b(h, h->mp4->frame_samples); /* Sample duration */
|
||||
}
|
||||
|
||||
/* from mpeg4audio.c (also see ff_mp4_read_dec_config_descr) */
|
||||
static const int m4a_sample_rates[16] = {
|
||||
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350
|
||||
};
|
||||
static const uint8_t m4a_channels[14] = {
|
||||
0,
|
||||
1, // mono (1/0)
|
||||
2, // stereo (2/0)
|
||||
3, // 3/0
|
||||
4, // 3/1
|
||||
5, // 3/2
|
||||
6, // 3/2.1
|
||||
8, // 5/2.1
|
||||
//0,
|
||||
//0,
|
||||
//0,
|
||||
//7, // 3/3.1
|
||||
//8, // 3/2/2.1
|
||||
//24 // 3/3/3 - 5/2/3 - 3/0/0.2
|
||||
};
|
||||
|
||||
static void add_esds(m4a_header_t* h) {
|
||||
uint16_t config = 0;
|
||||
|
||||
/* ES_descriptor (TLV format see ISO 14496-1) and DecSpecificInfoTag define actual decoding
|
||||
- config (channels/rate/etc), other atoms with the same stuff is just info
|
||||
* - http://ecee.colorado.edu/~ecen5653/ecen5653/papers/ISO%2014496-1%202004.PDF */
|
||||
|
||||
{
|
||||
uint8_t object_type = 0x02; /* 0x00=none, 0x01=AAC main, 0x02=AAC LC */
|
||||
uint8_t sr_index = 0;
|
||||
uint8_t ch_index = 0;
|
||||
uint8_t unknown = 0;
|
||||
int i;
|
||||
for (i = 0; i < 16; i++) {
|
||||
if (m4a_sample_rates[i] == h->mp4->sample_rate) {
|
||||
sr_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (m4a_channels[i] == h->mp4->channels) {
|
||||
ch_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
config |= (object_type & 0x1F) << 11; /* 5b */
|
||||
config |= (sr_index & 0x0F) << 7; /* 4b */
|
||||
config |= (ch_index & 0x0F) << 3; /* 4b */
|
||||
config |= (unknown & 0x07) << 0; /* 3b */
|
||||
}
|
||||
|
||||
add_atom(h, "esds", 0x33);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
|
||||
add_u8 (h, 0x03); /* ES_DescrTag */
|
||||
add_u32b(h, 0x80808022); /* size 0x22 */
|
||||
add_u16b(h, 0x0000); /* stream Id */
|
||||
add_u8 (h, 0x00); /* flags */
|
||||
|
||||
add_u8 (h, 0x04); /* DecoderConfigDescrTag */
|
||||
add_u32b(h, 0x80808014); /* size 0x14 */
|
||||
add_u8 (h, 0x40); /* object type (0x40=audio) */
|
||||
add_u8 (h, 0x15); /* stream type (6b: 0x5=audio) + upstream (1b) + reserved (1b: const 1) */
|
||||
add_u24b(h, 0x000000); /* buffer size */
|
||||
add_u32b(h, 0); /* max bitrate (256000?)*/
|
||||
add_u32b(h, 0); /* average bitrate (256000?) */
|
||||
|
||||
add_u8 (h, 0x05); /* DecSpecificInfoTag */
|
||||
add_u32b(h, 0x80808002); /* size 0x02 */
|
||||
add_u16b(h, config); /* actual decoder info */
|
||||
|
||||
add_u8 (h, 0x06); /* SLConfigDescrTag */
|
||||
add_u32b(h, 0x80808001); /* size 0x01 */
|
||||
add_u8 (h, 0x02); /* predefined (2=default) */
|
||||
}
|
||||
|
||||
static void add_mp4a(m4a_header_t* h) {
|
||||
add_atom(h, "mp4a", 0x57);
|
||||
add_u32b(h, 0); /* ? */
|
||||
add_u32b(h, 1); /* Data reference index */
|
||||
add_u32b(h, 0); /* Reserved */
|
||||
add_u32b(h, 0); /* Reserved 2 */
|
||||
add_u16b(h, h->mp4->channels); /* Channel count */
|
||||
add_u16b(h, 16); /* Sample size */
|
||||
add_u32b(h, 0); /* Pre-defined */
|
||||
add_u16b(h, h->mp4->sample_rate); /* Sample rate */
|
||||
add_u16b(h, 0); /* ? */
|
||||
add_esds(h); /* elementary stream descriptor */
|
||||
}
|
||||
|
||||
static void add_stsd(m4a_header_t* h) {
|
||||
add_atom(h, "stsd", 0x67);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 1); /* Number of entries */
|
||||
add_mp4a(h);
|
||||
}
|
||||
|
||||
static void add_stbl(m4a_header_t* h) {
|
||||
m4a_state_t s;
|
||||
|
||||
save_atom(h, &s);
|
||||
add_atom(h, "stbl", 0x00);
|
||||
add_stsd(h); /* Sample description */
|
||||
add_stts(h); /* Time-to-sample */
|
||||
add_stsc(h); /* Sample-to-chunk */
|
||||
add_stsz(h); /* Sample size */
|
||||
add_stco(h); /* Chunk offset */
|
||||
load_atom(h, &s);
|
||||
}
|
||||
|
||||
static void add_dinf(m4a_header_t* h) {
|
||||
add_atom(h, "dinf", 0x24);
|
||||
add_atom(h, "dref", 0x1c);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 1); /* Number of entries */
|
||||
add_atom(h, "url ", 0x0c);
|
||||
add_u32b(h, 1); /* Version (1 byte) + Flags (3 byte) */
|
||||
}
|
||||
|
||||
static void add_smhd(m4a_header_t* h) {
|
||||
add_atom(h, "smhd", 0x10);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u16b(h, 0); /* Balance */
|
||||
add_u16b(h, 0); /* Reserved */
|
||||
}
|
||||
|
||||
static void add_minf(m4a_header_t* h) {
|
||||
m4a_state_t s;
|
||||
|
||||
save_atom(h, &s);
|
||||
add_atom(h, "minf", 0x00);
|
||||
add_smhd(h);
|
||||
add_dinf(h);
|
||||
add_stbl(h);
|
||||
load_atom(h, &s);
|
||||
}
|
||||
|
||||
static void add_hdlr(m4a_header_t* h) {
|
||||
add_atom(h, "hdlr", 0x22);
|
||||
add_u32b(h, 0); /* version (1 byte) + flags (3 byte) */
|
||||
add_u32b(h, 0); /* Component type */
|
||||
add_name(h, "soun"); /* Component subtype */
|
||||
add_u32b(h, 0); /* Component manufacturer */
|
||||
add_u32b(h, 0); /* Component flags */
|
||||
add_u32b(h, 0); /* Component flags mask */
|
||||
add_u16b(h, 0); /* Component name */
|
||||
}
|
||||
|
||||
static void add_mdhd(m4a_header_t* h) {
|
||||
add_atom(h, "mdhd", 0x20);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 0); /* Creation time */
|
||||
add_u32b(h, 0); /* Modification time */
|
||||
add_u32b(h, h->mp4->sample_rate); /* Time scale */
|
||||
add_u32b(h, h->mp4->num_samples); /* Duration */
|
||||
add_u16b(h, 0); /* Language (0xC455=eng?) */
|
||||
add_u16b(h, 0); /* Quality */
|
||||
}
|
||||
|
||||
static void add_mdia(m4a_header_t* h) {
|
||||
m4a_state_t s;
|
||||
|
||||
save_atom(h, &s);
|
||||
add_atom(h, "mdia", 0x00);
|
||||
add_mdhd(h);
|
||||
add_hdlr(h);
|
||||
add_minf(h);
|
||||
load_atom(h, &s);
|
||||
}
|
||||
|
||||
static void add_tkhd(m4a_header_t* h) {
|
||||
add_atom(h, "tkhd", 0x5C);
|
||||
add_u32b(h, 0x00000001); /* Version (1 byte) + Flags (3 byte), 1=track enabled */
|
||||
add_u32b(h, 0); /* Creation time */
|
||||
add_u32b(h, 0); /* Modification time */
|
||||
add_u32b(h, 1); /* Track ID */
|
||||
add_u32b(h, 0); /* Reserved 1 */
|
||||
add_u32b(h, h->mp4->num_samples); /* Duration */
|
||||
add_u32b(h, 0); /* Reserved 1 */
|
||||
add_u32b(h, 0); /* Reserved 2 */
|
||||
add_u16b(h, 0); /* Layer */
|
||||
add_u16b(h, 0); /* Alternate group (1?) */
|
||||
add_u16b(h, 0x0100); /* Volume */
|
||||
add_u16b(h, 0); /* Reserved */
|
||||
add_u32b(h, 0x00010000); /* matrix_A */
|
||||
add_u32b(h, 0); /* matrix_B */
|
||||
add_u32b(h, 0); /* matrix_U */
|
||||
add_u32b(h, 0); /* matrix_C */
|
||||
add_u32b(h, 0x00010000); /* matrix_D */
|
||||
add_u32b(h, 0); /* matrix_V */
|
||||
add_u32b(h, 0); /* matrix_X */
|
||||
add_u32b(h, 0); /* matrix_Y */
|
||||
add_u32b(h, 0x40000000); /* matrix_W */
|
||||
add_u32b(h, 0); /* Width */
|
||||
add_u32b(h, 0); /* Height */
|
||||
}
|
||||
|
||||
static void add_trak(m4a_header_t* h) {
|
||||
m4a_state_t s;
|
||||
|
||||
save_atom(h, &s);
|
||||
add_atom(h, "trak", 0x00);
|
||||
add_tkhd(h);
|
||||
add_mdia(h);
|
||||
load_atom(h, &s);
|
||||
}
|
||||
|
||||
static void add_mvhd(m4a_header_t* h) {
|
||||
add_atom(h, "mvhd", 0x6c);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 0); /* Creation time */
|
||||
add_u32b(h, 0); /* Modification time */
|
||||
add_u32b(h, h->mp4->sample_rate); /* Time scale */
|
||||
add_u32b(h, h->mp4->num_samples); /* Duration */
|
||||
add_u32b(h, 0x00010000); /* Preferred rate */
|
||||
add_u16b(h, 0x0100); /* Preferred volume */
|
||||
add_u32b(h, 0); /* Reserved 1 */
|
||||
add_u32b(h, 0); /* Reserved 2 */
|
||||
add_u16b(h, 0); /* Reserved 3 */
|
||||
add_u32b(h, 0x00010000); /* matrix_A */
|
||||
add_u32b(h, 0); /* matrix_B */
|
||||
add_u32b(h, 0); /* matrix_U */
|
||||
add_u32b(h, 0); /* matrix_C */
|
||||
add_u32b(h, 0x00010000); /* matrix_D */
|
||||
add_u32b(h, 0); /* matrix_V */
|
||||
add_u32b(h, 0); /* matrix_X */
|
||||
add_u32b(h, 0); /* matrix_Y */
|
||||
add_u32b(h, 0x40000000); /* matrix_W */
|
||||
add_u32b(h, 0); /* Preview time */
|
||||
add_u32b(h, 0); /* Preview duration */
|
||||
add_u32b(h, 0); /* Poster time */
|
||||
add_u32b(h, 0); /* Selection time */
|
||||
add_u32b(h, 0); /* Selection duration */
|
||||
add_u32b(h, 0); /* Current time */
|
||||
add_u32b(h, 2); /* Next track ID */
|
||||
}
|
||||
|
||||
static void add_moov(m4a_header_t* h) {
|
||||
m4a_state_t s;
|
||||
|
||||
save_atom(h, &s);
|
||||
add_atom(h, "moov", 0x00);
|
||||
add_mvhd(h);
|
||||
add_trak(h);
|
||||
//add_udta(h);
|
||||
load_atom(h, &s);
|
||||
}
|
||||
|
||||
/* *** */
|
||||
|
||||
static int make_m4a_header(uint8_t* buf, int buf_len, mp4_custom_t* mp4, STREAMFILE* sf) {
|
||||
m4a_header_t h = {0};
|
||||
|
||||
if (buf_len < 0x400 + mp4->table_entries * 0x4) /* approx */
|
||||
goto fail;
|
||||
|
||||
h.sf = sf;
|
||||
h.mp4 = mp4;
|
||||
h.out = buf;
|
||||
|
||||
add_ftyp(&h);
|
||||
add_free(&h);
|
||||
add_moov(&h);
|
||||
add_mdat(&h);
|
||||
|
||||
|
||||
/* define absolute chunk offset after all calcs */
|
||||
put_u32be(h.chunks.out, h.bytes);
|
||||
|
||||
return h.bytes;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ************************************************************************* */
|
||||
|
||||
ffmpeg_codec_data* init_ffmpeg_mp4_custom_std(STREAMFILE* sf, mp4_custom_t* mp4) {
|
||||
ffmpeg_codec_data* ffmpeg_data = NULL;
|
||||
int bytes;
|
||||
uint8_t* buf = NULL;
|
||||
int buf_len = 0x800 + mp4->table_entries * 0x4; /* approx max sum of atom chunks is ~0x400 */
|
||||
|
||||
if (buf_len > 0x100000) /* ??? */
|
||||
goto fail;
|
||||
|
||||
buf = malloc(buf_len);
|
||||
if (!buf) goto fail;
|
||||
|
||||
bytes = make_m4a_header(buf, buf_len, mp4, sf);
|
||||
ffmpeg_data = init_ffmpeg_header_offset(sf, buf, bytes, mp4->stream_offset, mp4->stream_size);
|
||||
if (!ffmpeg_data) goto fail;
|
||||
|
||||
/* not part of fake header since it's kinda complex to add (iTunes string comment) */
|
||||
ffmpeg_set_skip_samples(ffmpeg_data, mp4->encoder_delay);
|
||||
|
||||
free(buf);
|
||||
return ffmpeg_data;
|
||||
fail:
|
||||
free(buf);
|
||||
free_ffmpeg(ffmpeg_data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
@ -184,6 +184,7 @@
|
||||
<ClCompile Include="coding\coding_utils.c" />
|
||||
<ClCompile Include="coding\ffmpeg_decoder.c" />
|
||||
<ClCompile Include="coding\ffmpeg_decoder_utils.c" />
|
||||
<ClCompile Include="coding\ffmpeg_decoder_custom_mp4.c" />
|
||||
<ClCompile Include="coding\ffmpeg_decoder_custom_opus.c" />
|
||||
<ClCompile Include="coding\lsf_decoder.c" />
|
||||
<ClCompile Include="coding\mp4_aac_decoder.c" />
|
||||
|
@ -1819,6 +1819,9 @@
|
||||
<ClCompile Include="coding\ffmpeg_decoder_utils.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="coding\ffmpeg_decoder_custom_mp4.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="coding\ffmpeg_decoder_custom_opus.c">
|
||||
<Filter>coding\Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
476
src/meta/ktac.c
476
src/meta/ktac.c
@ -3,26 +3,16 @@
|
||||
|
||||
|
||||
typedef struct {
|
||||
int channels;
|
||||
int sample_rate;
|
||||
int loop_flag;
|
||||
int32_t num_samples;
|
||||
int32_t loop_start;
|
||||
int32_t loop_end;
|
||||
uint32_t file_size;
|
||||
uint32_t stream_offset;
|
||||
uint32_t stream_size;
|
||||
uint32_t table_offset;
|
||||
uint32_t table_entries;
|
||||
|
||||
mp4_custom_t mp4;
|
||||
int type;
|
||||
int encoder_delay;
|
||||
int end_padding;
|
||||
int frame_samples;
|
||||
} ktac_header_t;
|
||||
|
||||
|
||||
static int make_m4a_header(uint8_t* buf, int buf_len, ktac_header_t* ktac, STREAMFILE* sf);
|
||||
|
||||
/* KTAC - Koei Tecmo custom AAC [Kin'iro no Corda 3 (Vita), Shingeki no Kyojin: Shichi kara no Dasshutsu (3DS), Dynasty Warriors (PS4)] */
|
||||
VGMSTREAM* init_vgmstream_ktac(STREAMFILE* sf) {
|
||||
VGMSTREAM* vgmstream = NULL;
|
||||
@ -40,21 +30,21 @@ VGMSTREAM* init_vgmstream_ktac(STREAMFILE* sf) {
|
||||
ktac.file_size = read_u32le(0x08,sf);
|
||||
if (ktac.file_size != get_streamfile_size(sf))
|
||||
goto fail;
|
||||
ktac.stream_offset = read_u32le(0x0c,sf);
|
||||
ktac.stream_size = read_u32le(0x10,sf);
|
||||
ktac.type = read_u32le(0x14,sf);
|
||||
ktac.sample_rate = read_u32le(0x18,sf);
|
||||
ktac.num_samples = read_u32le(0x1c,sf); /* full samples */
|
||||
ktac.channels = read_u16le(0x20,sf);
|
||||
ktac.frame_samples = read_u16le(0x22,sf);
|
||||
ktac.encoder_delay = read_u16le(0x24,sf);
|
||||
ktac.end_padding = read_u16le(0x26,sf);
|
||||
ktac.loop_start = read_u32le(0x28,sf);
|
||||
ktac.loop_end = read_u32le(0x2c,sf);
|
||||
ktac.mp4.stream_offset = read_u32le(0x0c,sf);
|
||||
ktac.mp4.stream_size = read_u32le(0x10,sf);
|
||||
ktac.type = read_u32le(0x14,sf);
|
||||
ktac.mp4.sample_rate = read_u32le(0x18,sf);
|
||||
ktac.mp4.num_samples = read_u32le(0x1c,sf); /* full samples */
|
||||
ktac.mp4.channels = read_u16le(0x20,sf);
|
||||
ktac.mp4.frame_samples = read_u16le(0x22,sf);
|
||||
ktac.mp4.encoder_delay = read_u16le(0x24,sf);
|
||||
ktac.mp4.end_padding = read_u16le(0x26,sf);
|
||||
ktac.loop_start = read_u32le(0x28,sf);
|
||||
ktac.loop_end = read_u32le(0x2c,sf);
|
||||
/* 0x30: ? (big, related to loops) */
|
||||
/* 0x34: ? (always null) */
|
||||
ktac.table_offset = read_u32le(0x38,sf);
|
||||
ktac.table_entries= read_u32le(0x3c,sf);
|
||||
ktac.mp4.table_offset = read_u32le(0x38,sf);
|
||||
ktac.mp4.table_entries = read_u32le(0x3c,sf);
|
||||
|
||||
ktac.loop_flag = (ktac.loop_end > 0);
|
||||
|
||||
@ -65,41 +55,23 @@ VGMSTREAM* init_vgmstream_ktac(STREAMFILE* sf) {
|
||||
|
||||
|
||||
/* build the VGMSTREAM */
|
||||
vgmstream = allocate_vgmstream(ktac.channels, ktac.loop_flag);
|
||||
vgmstream = allocate_vgmstream(ktac.mp4.channels, ktac.loop_flag);
|
||||
if (!vgmstream) goto fail;
|
||||
|
||||
vgmstream->meta_type = meta_KTAC;
|
||||
vgmstream->sample_rate = ktac.sample_rate;
|
||||
vgmstream->num_samples = ktac.num_samples - ktac.encoder_delay - ktac.end_padding;
|
||||
vgmstream->loop_start_sample = ktac.loop_start * ktac.frame_samples - ktac.encoder_delay;
|
||||
vgmstream->loop_end_sample = ktac.loop_end * ktac.frame_samples - ktac.encoder_delay;
|
||||
vgmstream->sample_rate = ktac.mp4.sample_rate;
|
||||
vgmstream->num_samples = ktac.mp4.num_samples - ktac.mp4.encoder_delay - ktac.mp4.end_padding;
|
||||
vgmstream->loop_start_sample = ktac.loop_start * ktac.mp4.frame_samples - ktac.mp4.encoder_delay;
|
||||
vgmstream->loop_end_sample = ktac.loop_end * ktac.mp4.frame_samples - ktac.mp4.encoder_delay;
|
||||
|
||||
/* KTAC uses AAC, but not type found in .aac (that has headered frames, like mp3) but raw
|
||||
* packets + frame size table (similar to .mp4/m4a). We make a fake M4A header to feed FFmpeg */
|
||||
* packets + frame size table (similar to .mp4/m4a). We set config for FFmpeg's fake M4A header */
|
||||
#ifdef VGM_USE_FFMPEG
|
||||
{
|
||||
ffmpeg_codec_data* ffmpeg_data = NULL;
|
||||
int bytes;
|
||||
uint8_t* buf = NULL;
|
||||
int buf_len = 0x400 + ktac.table_entries * 0x4;
|
||||
|
||||
if (buf_len > 0x100000) /* ??? */
|
||||
goto fail;
|
||||
|
||||
buf = malloc(buf_len);
|
||||
if (!buf) goto fail;
|
||||
|
||||
bytes = make_m4a_header(buf, buf_len, &ktac, sf);
|
||||
ffmpeg_data = init_ffmpeg_header_offset(sf, buf, bytes, ktac.stream_offset, ktac.stream_size);
|
||||
free(buf);
|
||||
|
||||
if (!ffmpeg_data) goto fail;
|
||||
vgmstream->codec_data = ffmpeg_data;
|
||||
vgmstream->codec_data = init_ffmpeg_mp4_custom_std(sf, &ktac.mp4);
|
||||
if (!vgmstream->codec_data) goto fail;
|
||||
vgmstream->coding_type = coding_FFmpeg;
|
||||
vgmstream->layout_type = layout_none;
|
||||
|
||||
/* not part of fake header since it's kinda complex to add (iTunes string comment) */
|
||||
ffmpeg_set_skip_samples(ffmpeg_data, ktac.encoder_delay);
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
@ -110,405 +82,3 @@ fail:
|
||||
close_vgmstream(vgmstream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* *********************************************************** */
|
||||
|
||||
/* Helpers for M4A headers, an insane soup of chunks (AKA "atoms").
|
||||
* Needs *A LOT* of atoms and fields so this is more elaborate than usual.
|
||||
* - https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/QTFFPreface/qtffPreface.html
|
||||
*/
|
||||
|
||||
/* generic additions */
|
||||
typedef struct {
|
||||
uint8_t* out;
|
||||
int bytes;
|
||||
} m4a_state_t;
|
||||
|
||||
typedef struct {
|
||||
STREAMFILE* sf;
|
||||
ktac_header_t* ktac; /* config */
|
||||
uint8_t* out; /* current position */
|
||||
int bytes; /* written bytes */
|
||||
m4a_state_t chunks; /* chunks offsets are absolute, save position until we know header size */
|
||||
} m4a_header_t;
|
||||
|
||||
static void add_u32b(m4a_header_t* h, uint32_t value) {
|
||||
put_u32be(h->out, value);
|
||||
h->out += 0x04;
|
||||
h->bytes += 0x04;
|
||||
}
|
||||
|
||||
static void add_u24b(m4a_header_t* h, uint32_t value) {
|
||||
put_u16be(h->out + 0x00, (value >> 8u) & 0xFFFF);
|
||||
put_u8 (h->out + 0x02, (value >> 0u) & 0xFF);
|
||||
h->out += 0x03;
|
||||
h->bytes += 0x03;
|
||||
}
|
||||
|
||||
static void add_u16b(m4a_header_t* h, uint16_t value) {
|
||||
put_u16be(h->out, value);
|
||||
h->out += 0x02;
|
||||
h->bytes += 0x02;
|
||||
}
|
||||
|
||||
static void add_u8(m4a_header_t* h, uint32_t value) {
|
||||
put_u8(h->out, value);
|
||||
h->out += 0x01;
|
||||
h->bytes += 0x01;
|
||||
}
|
||||
|
||||
static void add_name(m4a_header_t* h, const char* name) {
|
||||
memcpy(h->out, name, 0x4);
|
||||
h->out += 0x04;
|
||||
h->bytes += 0x04;
|
||||
}
|
||||
|
||||
static void add_atom(m4a_header_t* h, const char* name, uint32_t size) {
|
||||
add_u32b(h, size);
|
||||
add_name(h, name);
|
||||
}
|
||||
|
||||
/* register + write final size for atoms of variable/complex size */
|
||||
static void save_atom(m4a_header_t* h, m4a_state_t* s) {
|
||||
s->out = h->out;
|
||||
s->bytes = h->bytes;
|
||||
}
|
||||
|
||||
static void load_atom(m4a_header_t* h, m4a_state_t* s) {
|
||||
put_u32be(s->out, h->bytes - s->bytes);
|
||||
}
|
||||
|
||||
/* common atoms */
|
||||
|
||||
static void add_ftyp(m4a_header_t* h) {
|
||||
add_atom(h, "ftyp", 0x18);
|
||||
add_name(h, "M4A "); /* major brand */
|
||||
add_u32b(h, 512); /* minor version */
|
||||
add_name(h, "isom"); /* compatible brands */
|
||||
add_name(h, "iso2"); /* compatible brands */
|
||||
}
|
||||
|
||||
static void add_free(m4a_header_t* h) {
|
||||
add_atom(h, "free", 0x08);
|
||||
}
|
||||
|
||||
static void add_mdat(m4a_header_t* h) {
|
||||
add_atom(h, "mdat", 0x08 + h->ktac->stream_size);
|
||||
}
|
||||
|
||||
/* variable atoms */
|
||||
|
||||
|
||||
static void add_stco(m4a_header_t* h) {
|
||||
add_atom(h, "stco", 0x10 + 1 * 0x04);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 1); /* Number of entries */
|
||||
/* there may be an entry per frame, but only first seems needed */
|
||||
save_atom(h, &h->chunks);
|
||||
add_u32b(h, 0); /* Absolute offset N */
|
||||
}
|
||||
static void add_stsz(m4a_header_t* h) {
|
||||
int i;
|
||||
|
||||
add_atom(h, "stsz", 0x14 + h->ktac->table_entries * 0x04);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 0); /* Sample size (CBR) */
|
||||
add_u32b(h, h->ktac->table_entries); /* Number of entries (VBR) */
|
||||
for (i = 0; i < h->ktac->table_entries; i++) {
|
||||
uint32_t size = read_u32le(h->ktac->table_offset + i*0x04, h->sf);
|
||||
add_u32b(h, size); /* Sample N */
|
||||
}
|
||||
}
|
||||
|
||||
static void add_stsc(m4a_header_t* h) {
|
||||
add_atom(h, "stsc", 0x1c);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 1); /* Number of entries */
|
||||
add_u32b(h, 1); /* First chunk */
|
||||
add_u32b(h, h->ktac->table_entries); /* Samples per chunk */
|
||||
add_u32b(h, 1); /* Sample description ID */
|
||||
}
|
||||
|
||||
static void add_stts(m4a_header_t* h) {
|
||||
add_atom(h, "stts", 0x18);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 1); /* Number of entries */
|
||||
add_u32b(h, h->ktac->table_entries); /* Sample count */
|
||||
add_u32b(h, h->ktac->frame_samples); /* Sample duration */
|
||||
}
|
||||
|
||||
/* from mpeg4audio.c (also see ff_mp4_read_dec_config_descr) */
|
||||
static const int m4a_sample_rates[16] = {
|
||||
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350
|
||||
};
|
||||
static const uint8_t m4a_channels[14] = {
|
||||
0,
|
||||
1, // mono (1/0)
|
||||
2, // stereo (2/0)
|
||||
3, // 3/0
|
||||
4, // 3/1
|
||||
5, // 3/2
|
||||
6, // 3/2.1
|
||||
8, // 5/2.1
|
||||
//0,
|
||||
//0,
|
||||
//0,
|
||||
//7, // 3/3.1
|
||||
//8, // 3/2/2.1
|
||||
//24 // 3/3/3 - 5/2/3 - 3/0/0.2
|
||||
};
|
||||
|
||||
static void add_esds(m4a_header_t* h) {
|
||||
uint16_t config = 0;
|
||||
|
||||
/* ES_descriptor (TLV format see ISO 14496-1) and DecSpecificInfoTag define actual decoding
|
||||
- config (channels/rate/etc), other atoms with the same stuff is just info
|
||||
* - http://ecee.colorado.edu/~ecen5653/ecen5653/papers/ISO%2014496-1%202004.PDF */
|
||||
|
||||
{
|
||||
uint8_t object_type = 0x02; /* 0x00=none, 0x01=AAC main, 0x02=AAC LC */
|
||||
uint8_t sr_index = 0;
|
||||
uint8_t ch_index = 0;
|
||||
uint8_t unknown = 0;
|
||||
int i;
|
||||
for (i = 0; i < 16; i++) {
|
||||
if (m4a_sample_rates[i] == h->ktac->sample_rate) {
|
||||
sr_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (m4a_channels[i] == h->ktac->channels) {
|
||||
ch_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
config |= (object_type & 0x1F) << 11; /* 5b */
|
||||
config |= (sr_index & 0x0F) << 7; /* 4b */
|
||||
config |= (ch_index & 0x0F) << 3; /* 4b */
|
||||
config |= (unknown & 0x07) << 0; /* 3b */
|
||||
}
|
||||
|
||||
add_atom(h, "esds", 0x33);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
|
||||
add_u8 (h, 0x03); /* ES_DescrTag */
|
||||
add_u32b(h, 0x80808022); /* size 0x22 */
|
||||
add_u16b(h, 0x0000); /* stream Id */
|
||||
add_u8 (h, 0x00); /* flags */
|
||||
|
||||
add_u8 (h, 0x04); /* DecoderConfigDescrTag */
|
||||
add_u32b(h, 0x80808014); /* size 0x14 */
|
||||
add_u8 (h, 0x40); /* object type (0x40=audio) */
|
||||
add_u8 (h, 0x15); /* stream type (6b: 0x5=audio) + upstream (1b) + reserved (1b: const 1) */
|
||||
add_u24b(h, 0x000000); /* buffer size */
|
||||
add_u32b(h, 0); /* max bitrate (256000?)*/
|
||||
add_u32b(h, 0); /* average bitrate (256000?) */
|
||||
|
||||
add_u8 (h, 0x05); /* DecSpecificInfoTag */
|
||||
add_u32b(h, 0x80808002); /* size 0x02 */
|
||||
add_u16b(h, config); /* actual decoder info */
|
||||
|
||||
add_u8 (h, 0x06); /* SLConfigDescrTag */
|
||||
add_u32b(h, 0x80808001); /* size 0x01 */
|
||||
add_u8 (h, 0x02); /* predefined (2=default) */
|
||||
}
|
||||
|
||||
static void add_mp4a(m4a_header_t* h) {
|
||||
add_atom(h, "mp4a", 0x57);
|
||||
add_u32b(h, 0); /* ? */
|
||||
add_u32b(h, 1); /* Data reference index */
|
||||
add_u32b(h, 0); /* Reserved */
|
||||
add_u32b(h, 0); /* Reserved 2 */
|
||||
add_u16b(h, h->ktac->channels); /* Channel count */
|
||||
add_u16b(h, 16); /* Sample size */
|
||||
add_u32b(h, 0); /* Pre-defined */
|
||||
add_u16b(h, h->ktac->sample_rate); /* Sample rate */
|
||||
add_u16b(h, 0); /* ? */
|
||||
add_esds(h); /* elementary stream descriptor */
|
||||
}
|
||||
|
||||
static void add_stsd(m4a_header_t* h) {
|
||||
add_atom(h, "stsd", 0x67);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 1); /* Number of entries */
|
||||
add_mp4a(h);
|
||||
}
|
||||
|
||||
static void add_stbl(m4a_header_t* h) {
|
||||
m4a_state_t s;
|
||||
|
||||
save_atom(h, &s);
|
||||
add_atom(h, "stbl", 0x00);
|
||||
add_stsd(h); /* Sample description */
|
||||
add_stts(h); /* Time-to-sample */
|
||||
add_stsc(h); /* Sample-to-chunk */
|
||||
add_stsz(h); /* Sample size */
|
||||
add_stco(h); /* Chunk offset */
|
||||
load_atom(h, &s);
|
||||
}
|
||||
|
||||
static void add_dinf(m4a_header_t* h) {
|
||||
add_atom(h, "dinf", 0x24);
|
||||
add_atom(h, "dref", 0x1c);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 1); /* Number of entries */
|
||||
add_atom(h, "url ", 0x0c);
|
||||
add_u32b(h, 1); /* Version (1 byte) + Flags (3 byte) */
|
||||
}
|
||||
|
||||
static void add_smhd(m4a_header_t* h) {
|
||||
add_atom(h, "smhd", 0x10);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u16b(h, 0); /* Balance */
|
||||
add_u16b(h, 0); /* Reserved */
|
||||
}
|
||||
|
||||
static void add_minf(m4a_header_t* h) {
|
||||
m4a_state_t s;
|
||||
|
||||
save_atom(h, &s);
|
||||
add_atom(h, "minf", 0x00);
|
||||
add_smhd(h);
|
||||
add_dinf(h);
|
||||
add_stbl(h);
|
||||
load_atom(h, &s);
|
||||
}
|
||||
|
||||
static void add_hdlr(m4a_header_t* h) {
|
||||
add_atom(h, "hdlr", 0x22);
|
||||
add_u32b(h, 0); /* version (1 byte) + flags (3 byte) */
|
||||
add_u32b(h, 0); /* Component type */
|
||||
add_name(h, "soun"); /* Component subtype */
|
||||
add_u32b(h, 0); /* Component manufacturer */
|
||||
add_u32b(h, 0); /* Component flags */
|
||||
add_u32b(h, 0); /* Component flags mask */
|
||||
add_u16b(h, 0); /* Component name */
|
||||
}
|
||||
|
||||
static void add_mdhd(m4a_header_t* h) {
|
||||
add_atom(h, "mdhd", 0x20);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 0); /* Creation time */
|
||||
add_u32b(h, 0); /* Modification time */
|
||||
add_u32b(h, h->ktac->sample_rate); /* Time scale */
|
||||
add_u32b(h, h->ktac->num_samples); /* Duration */
|
||||
add_u16b(h, 0); /* Language (0xC455=eng?) */
|
||||
add_u16b(h, 0); /* Quality */
|
||||
}
|
||||
|
||||
static void add_mdia(m4a_header_t* h) {
|
||||
m4a_state_t s;
|
||||
|
||||
save_atom(h, &s);
|
||||
add_atom(h, "mdia", 0x00);
|
||||
add_mdhd(h);
|
||||
add_hdlr(h);
|
||||
add_minf(h);
|
||||
load_atom(h, &s);
|
||||
}
|
||||
|
||||
static void add_tkhd(m4a_header_t* h) {
|
||||
add_atom(h, "tkhd", 0x5C);
|
||||
add_u32b(h, 0x00000001); /* Version (1 byte) + Flags (3 byte), 1=track enabled */
|
||||
add_u32b(h, 0); /* Creation time */
|
||||
add_u32b(h, 0); /* Modification time */
|
||||
add_u32b(h, 1); /* Track ID */
|
||||
add_u32b(h, 0); /* Reserved 1 */
|
||||
add_u32b(h, h->ktac->num_samples); /* Duration */
|
||||
add_u32b(h, 0); /* Reserved 1 */
|
||||
add_u32b(h, 0); /* Reserved 2 */
|
||||
add_u16b(h, 0); /* Layer */
|
||||
add_u16b(h, 0); /* Alternate group (1?) */
|
||||
add_u16b(h, 0x0100); /* Volume */
|
||||
add_u16b(h, 0); /* Reserved */
|
||||
add_u32b(h, 0x00010000); /* matrix_A */
|
||||
add_u32b(h, 0); /* matrix_B */
|
||||
add_u32b(h, 0); /* matrix_U */
|
||||
add_u32b(h, 0); /* matrix_C */
|
||||
add_u32b(h, 0x00010000); /* matrix_D */
|
||||
add_u32b(h, 0); /* matrix_V */
|
||||
add_u32b(h, 0); /* matrix_X */
|
||||
add_u32b(h, 0); /* matrix_Y */
|
||||
add_u32b(h, 0x40000000); /* matrix_W */
|
||||
add_u32b(h, 0); /* Width */
|
||||
add_u32b(h, 0); /* Height */
|
||||
}
|
||||
|
||||
static void add_trak(m4a_header_t* h) {
|
||||
m4a_state_t s;
|
||||
|
||||
save_atom(h, &s);
|
||||
add_atom(h, "trak", 0x00);
|
||||
add_tkhd(h);
|
||||
add_mdia(h);
|
||||
load_atom(h, &s);
|
||||
}
|
||||
|
||||
static void add_mvhd(m4a_header_t* h) {
|
||||
add_atom(h, "mvhd", 0x6c);
|
||||
add_u32b(h, 0); /* Version (1 byte) + Flags (3 byte) */
|
||||
add_u32b(h, 0); /* Creation time */
|
||||
add_u32b(h, 0); /* Modification time */
|
||||
add_u32b(h, h->ktac->sample_rate); /* Time scale */
|
||||
add_u32b(h, h->ktac->num_samples); /* Duration */
|
||||
add_u32b(h, 0x00010000); /* Preferred rate */
|
||||
add_u16b(h, 0x0100); /* Preferred volume */
|
||||
add_u32b(h, 0); /* Reserved 1 */
|
||||
add_u32b(h, 0); /* Reserved 2 */
|
||||
add_u16b(h, 0); /* Reserved 3 */
|
||||
add_u32b(h, 0x00010000); /* matrix_A */
|
||||
add_u32b(h, 0); /* matrix_B */
|
||||
add_u32b(h, 0); /* matrix_U */
|
||||
add_u32b(h, 0); /* matrix_C */
|
||||
add_u32b(h, 0x00010000); /* matrix_D */
|
||||
add_u32b(h, 0); /* matrix_V */
|
||||
add_u32b(h, 0); /* matrix_X */
|
||||
add_u32b(h, 0); /* matrix_Y */
|
||||
add_u32b(h, 0x40000000); /* matrix_W */
|
||||
add_u32b(h, 0); /* Preview time */
|
||||
add_u32b(h, 0); /* Preview duration */
|
||||
add_u32b(h, 0); /* Poster time */
|
||||
add_u32b(h, 0); /* Selection time */
|
||||
add_u32b(h, 0); /* Selection duration */
|
||||
add_u32b(h, 0); /* Current time */
|
||||
add_u32b(h, 2); /* Next track ID */
|
||||
}
|
||||
|
||||
static void add_moov(m4a_header_t* h) {
|
||||
m4a_state_t s;
|
||||
|
||||
save_atom(h, &s);
|
||||
add_atom(h, "moov", 0x00);
|
||||
add_mvhd(h);
|
||||
add_trak(h);
|
||||
//add_udta(h);
|
||||
load_atom(h, &s);
|
||||
}
|
||||
|
||||
/* *** */
|
||||
|
||||
static int make_m4a_header(uint8_t* buf, int buf_len, ktac_header_t* ktac, STREAMFILE* sf) {
|
||||
m4a_header_t h = {0};
|
||||
|
||||
if (buf_len < 0x300 + ktac->table_entries * 0x4) /* approx */
|
||||
goto fail;
|
||||
|
||||
h.sf = sf;
|
||||
h.ktac = ktac;
|
||||
h.out = buf;
|
||||
|
||||
add_ftyp(&h);
|
||||
add_free(&h);
|
||||
add_moov(&h);
|
||||
add_mdat(&h);
|
||||
|
||||
|
||||
/* define absolute chunk offset after all calcs */
|
||||
put_u32be(h.chunks.out, h.bytes);
|
||||
|
||||
return h.bytes;
|
||||
fail:
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user