Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 32 additions & 9 deletions frontend/encode_engine.c
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,26 @@ static void log_msgf(log_message_callback_t log_cb, void *user_data,
log_cb(level, msg, user_data);
}

/* Output-domain sample counts -> the rate mp4_set_format() declared
(HE-AAC's container is core rate, half output rate). One conversion,
used everywhere, so every container-domain number stays consistent. */
typedef struct { uint32_t div; uint64_t rounded_pos; } rate_conv_t;

static uint32_t rc_scalar(rate_conv_t rc, uint64_t v)
{
return (uint32_t)((v + rc.div / 2) / rc.div);
}

/* Per-frame durations must be derived from the running position, not
rounded individually, or rounding drift accumulates across frames. */
static uint32_t rc_advance(rate_conv_t *rc, uint64_t new_pos)
{
uint64_t rounded = rc_scalar(*rc, new_pos);
uint32_t delta = (uint32_t)(rounded - rc->rounded_pos);
rc->rounded_pos = rounded;
return delta;
}

static bool finalize_mp4(faac_encoder *hEncoder, const encode_options_t *opts,
log_message_callback_t log_cb, void *user_data)
{
Expand Down Expand Up @@ -509,6 +529,10 @@ int run_encoding_session_ext(const encode_options_t *opts,
unsigned long max_output_bytes = info.max_output_bytes;
uint16_t frame_size = (uint16_t)(samples_per_frame / num_channels);

/* Implicit SBR signaling expects the container declared at the core
(pre-SBR) rate, half the reconstructed output rate. */
rate_conv_t rc = { .div = (info.object_type == FAAC_OBJ_HE_AAC_V1) ? 2 : 1 };

pcmbuf = malloc(samples_per_frame * sizeof(float));
bitbuf = malloc(max_output_bytes * sizeof(unsigned char));

Expand All @@ -532,7 +556,7 @@ int run_encoding_session_ext(const encode_options_t *opts,
if (mp4_open(opts->output_filename, opts->overwrite) != 0)
FAIL("Couldn't create MP4 output file %s\n", opts->output_filename);
mp4_is_open = true;
mp4_set_format(sample_rate, num_channels, infile->samplebytes * 8);
mp4_set_format(rc_scalar(rc, sample_rate), num_channels, infile->samplebytes * 8);
}
else if (opts->output_filename)
{
Expand Down Expand Up @@ -659,13 +683,10 @@ int run_encoding_session_ext(const encode_options_t *opts,

if (bytes_written > 0)
{
uint64_t frame_samples = current_input_samples - encoded_samples;
if (frame_samples > frame_size)
frame_samples = frame_size;

if (opts->container_mp4)
{
if (mp4_write_frame(bitbuf, (uint32_t)bytes_written, (uint32_t)frame_samples) != 0)
uint32_t frame_dur = rc_advance(&rc, (uint64_t)current_frame * frame_size);
if (mp4_write_frame(bitbuf, (uint32_t)bytes_written, frame_dur) != 0)
FAIL("mp4_write_frame() failed\n");
}
else
Expand All @@ -674,7 +695,7 @@ int run_encoding_session_ext(const encode_options_t *opts,
FAIL("Output write failed\n");
}

encoded_samples += frame_samples;
encoded_samples += frame_size;
}

if (progress_cb)
Expand Down Expand Up @@ -708,11 +729,13 @@ int run_encoding_session_ext(const encode_options_t *opts,
if (opts->container_mp4 && mp4_is_open)
{
uint32_t priming = info.encoder_delay;
uint64_t total_output_samples = (uint64_t)current_frame * priming;
uint64_t total_output_samples = (uint64_t)current_frame * frame_size;
uint64_t padding = 0;
if (total_output_samples > (uint64_t)priming + current_input_samples)
padding = total_output_samples - (uint64_t)priming - current_input_samples;
mp4_set_gapless(priming, (uint32_t)padding, current_input_samples);

mp4_set_gapless(rc_scalar(rc, priming), rc_scalar(rc, padding),
rc_scalar(rc, current_input_samples));

if (!finalize_mp4(hEncoder, opts, log_cb, user_data))
{
Expand Down
54 changes: 52 additions & 2 deletions frontend/mp4write.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
#endif

#include "mp4write.h"
#ifdef _WIN32
#include "charset.h"
#endif

#if defined(__has_builtin)
#if __has_builtin(__builtin_bswap32) && __has_builtin(__builtin_bswap16)
Expand Down Expand Up @@ -105,6 +107,16 @@ static struct {
uint32_t bufsize;
} frame;

/* stts run-length table: (sample_count, sample_delta) pairs. Frames
don't all share one duration -- a short trailing/flush-drain frame
is common -- so this can't be collapsed to a single entry the way
frame.data (byte sizes, for stsz) is. */
struct {
struct { uint32_t count; uint32_t delta; } *data;
uint32_t ents;
uint32_t bufsize;
} durs;

struct {
const uint8_t *data;
unsigned long size;
Expand Down Expand Up @@ -342,6 +354,10 @@ static void reset_write_state(void) {
g_mp4.frame.data = NULL;
g_mp4.frame.ents = 0;
g_mp4.frame.bufsize = 0;
free(g_mp4.durs.data);
g_mp4.durs.data = NULL;
g_mp4.durs.ents = 0;
g_mp4.durs.bufsize = 0;
g_mp4.framesamples = 0;
g_mp4.samples = 0;
g_mp4.buffersize = 0;
Expand Down Expand Up @@ -499,6 +515,21 @@ int mp4_write_frame(const uint8_t *data, uint32_t size, uint32_t samples) {
g_mp4.frame.data[g_mp4.frame.ents++] = size;
if (g_mp4.buffersize < (uint16_t)size)
g_mp4.buffersize = (uint16_t)size;

if (g_mp4.durs.ents > 0 && g_mp4.durs.data[g_mp4.durs.ents - 1].delta == samples) {
g_mp4.durs.data[g_mp4.durs.ents - 1].count++;
} else {
if (g_mp4.durs.ents >= g_mp4.durs.bufsize) {
uint32_t new_cap = g_mp4.durs.bufsize ? g_mp4.durs.bufsize * 2 : 16;
void *tmp = realloc(g_mp4.durs.data, (size_t)new_cap * sizeof(*g_mp4.durs.data));
if (!tmp) return -1;
g_mp4.durs.data = tmp;
g_mp4.durs.bufsize = new_cap;
}
g_mp4.durs.data[g_mp4.durs.ents].count = 1;
g_mp4.durs.data[g_mp4.durs.ents].delta = samples;
g_mp4.durs.ents++;
}
return 0;
}

Expand Down Expand Up @@ -652,6 +683,22 @@ int mp4_finish(void) {
put_u32(0); put_u32(0);
end_atom(tkhd);

if (g_mp4.gapless.present && g_mp4.gapless.priming > 0) {
long edts = start_atom("edts");
long elst = start_atom("elst");
uint32_t elst_flags = use64_time ? (1U << 24) : 0;
put_u32(elst_flags);
put_u32(1); /* entry_count = 1 */
/* segment_duration: edit duration in movie timescale units */
put_time(g_mp4.gapless.original_samples, use64_time);
/* media_time: media start time in track timescale units (priming samples delay) */
put_time(g_mp4.gapless.priming, use64_time);
/* media_rate_integer (16-bit 1 = 0x0001) + media_rate_fraction (16-bit 0) */
put_u16(1); put_u16(0);
end_atom(elst);
end_atom(edts);
}

uint16_t packed_lang = pack_language(g_mp4.language);
long mdia = start_atom("mdia");
long mdhd = start_atom("mdhd");
Expand Down Expand Up @@ -713,8 +760,11 @@ int mp4_finish(void) {
end_atom(stsd);

long stts = start_atom("stts");
put_u32(0); put_u32(1);
put_u32(g_mp4.frame.ents); put_u32(g_mp4.framesamples);
put_u32(0); put_u32(g_mp4.durs.ents);
for (uint32_t i = 0; i < g_mp4.durs.ents; i++) {
put_u32(g_mp4.durs.data[i].count);
put_u32(g_mp4.durs.data[i].delta);
}
end_atom(stts);

long stsc = start_atom("stsc");
Expand Down
Loading