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
7 changes: 7 additions & 0 deletions frontend/encode_engine.c
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,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 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);

if (!finalize_mp4(hEncoder, opts, log_cb, user_data))
{
ret = 1;
Expand Down
31 changes: 30 additions & 1 deletion frontend/mp4write.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ static struct {
uint32_t size;
} cover;

struct {
bool present;
uint32_t priming;
uint32_t padding;
uint64_t original_samples;
} gapless;

struct {
const char *name;
const char *value;
Expand Down Expand Up @@ -341,6 +348,7 @@ static void reset_write_state(void) {
g_mp4.mdatofs = 0;
g_mp4.mdatsize = 0;
memset(&g_mp4.bitrate, 0, sizeof(g_mp4.bitrate));
memset(&g_mp4.gapless, 0, sizeof(g_mp4.gapless));
free(g_membuf);
g_membuf = NULL;
}
Expand Down Expand Up @@ -434,6 +442,13 @@ void mp4_set_cover(const uint8_t *data, uint32_t size) {
g_mp4.cover.size = size;
}

void mp4_set_gapless(uint32_t priming, uint32_t padding, uint64_t original_samples) {
g_mp4.gapless.present = true;
g_mp4.gapless.priming = priming;
g_mp4.gapless.padding = padding;
g_mp4.gapless.original_samples = original_samples;
}

int mp4_add_custom_tag(const char *name, const char *value) {
if (g_mp4.customcnt >= g_mp4.customcap) {
uint32_t new_cap = g_mp4.customcap ? g_mp4.customcap * 2 : 8;
Expand Down Expand Up @@ -546,7 +561,11 @@ static void put_tag_ext(const char *mean, const char *name, const char *val) {
put_u32(0);
put_data(name, strlen(name));
end_atom(name_box);
put_itunes_data_box("data", ITUNES_DATA_TEXT, val, strlen(val));
long data_box = start_atom("data");
put_u32(ITUNES_DATA_TEXT);
put_u32(0);
put_data(val, strlen(val));
end_atom(data_box);
end_atom(box);
}

Expand Down Expand Up @@ -757,6 +776,16 @@ int mp4_finish(void) {
if (g_mp4.trackno) put_tag_index("trkn", (uint16_t)g_mp4.trackno, (uint16_t)g_mp4.ntracks);
if (g_mp4.discno) put_tag_index("disk", (uint16_t)g_mp4.discno, (uint16_t)g_mp4.ndiscs);
if (g_mp4.cover.data) put_tag_image(g_mp4.cover.data, g_mp4.cover.size);
if (g_mp4.gapless.present) {
char smpb[128];
snprintf(smpb, sizeof(smpb),
" 00000000 %08X %08X %08X%08X 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000",
g_mp4.gapless.priming,
g_mp4.gapless.padding,
(uint32_t)(g_mp4.gapless.original_samples >> 32),
(uint32_t)(g_mp4.gapless.original_samples & 0xFFFFFFFFULL));
put_tag_ext("com.apple.iTunes", "iTunSMPB", smpb);
}
for (uint32_t i = 0; i < g_mp4.customcnt; i++)
put_tag_ext("faac", g_mp4.custom[i].name, g_mp4.custom[i].value);
end_atom(ilst);
Expand Down
1 change: 1 addition & 0 deletions frontend/mp4write.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ void mp4_set_compilation(bool flag);
void mp4_set_track(uint16_t num, uint16_t total);
void mp4_set_disc(uint16_t num, uint16_t total);
void mp4_set_cover(const uint8_t *data, uint32_t size);
void mp4_set_gapless(uint32_t priming, uint32_t padding, uint64_t original_samples);
int mp4_add_custom_tag(const char *name, const char *value);
int mp4_write_frame(const uint8_t *data, uint32_t size, uint32_t samples);
int mp4_finish(void);
Expand Down
5 changes: 5 additions & 0 deletions include/faac.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ typedef struct faac_encoder_info {
uint32_t quant_quality; /* resolved quantizer quality */
int32_t pns_level; /* resolved PNS level, 0..10 */
uint32_t max_bit_rate; /* resolved peak cap, 0 if unlimited */

/* Priming delay in samples/channel at the output rate: leading samples the
* decoder must discard. Use verbatim for gapless tagging (e.g. iTunSMPB) --
* not the same as frame_samples for HE-AAC. */
uint32_t encoder_delay;
} faac_encoder_info;

/*
Expand Down
17 changes: 17 additions & 0 deletions libfaac/faac.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@
#include "config.h"
#endif

#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include <faac.h>
#include "frame.h"
#include "bitstream.h"
#include "sbr.h"
#include "resample.h"
#include "util.h"

/* The public enums are width-pinned to 32 bits by their FAAC_*_MAX sentinels;
Expand Down Expand Up @@ -279,6 +281,20 @@ FAACAPI faac_status faac_encoder_close(faac_encoder **enc)
return FAAC_OK;
}

/* LC: one frame of 50% MDCT overlap. HE-AAC: that same core delay at full
* rate (2*FRAME_LEN) plus one extra full-rate frame the SBR/resample pipeline
* buffers ahead of the core, net of the resampler's own FIR group delay.
* Verified against decoded output, not derived from spec. */
static uint32_t faacEncoderDelay(const faacEncStruct *h)
{
switch (h->config.aacObjectType) {
case LOW: return FRAME_LEN;
case HE_V1: return 3 * FRAME_LEN - RESAMPLE_FILTER_LEN / 2;
}
assert(0 && "faacEncoderDelay: unhandled aacObjectType");
return FRAME_LEN;
}

FAACAPI faac_status faac_encoder_get_info(faac_encoder *enc, faac_encoder_info *out)
{
faac_encoder_info info;
Expand All @@ -304,6 +320,7 @@ FAACAPI faac_status faac_encoder_get_info(faac_encoder *enc, faac_encoder_info *
info.quant_quality = (uint32_t)h->config.quantqual;
info.pns_level = (int32_t)h->config.pnslevel;
info.max_bit_rate = (uint32_t)h->config.maxBitRate;
info.encoder_delay = faacEncoderDelay(h);

/* Write at most the caller's struct_size so a newer library cannot overrun
* an older, smaller faac_encoder_info; report the byte count actually set. */
Expand Down
Loading