#include <windows.h>
#endif
#include "compat.h"
-#include "wav_reader.h"
+#include "pcm_reader.h"
#include "aacenc.h"
#include "m4af.h"
#include "progress.h"
#include "version.h"
+#include "metadata.h"
#define PROGNAME "fdkaac"
-static volatile g_interrupted = 0;
+static volatile int g_interrupted = 0;
#if HAVE_SIGACTION
static void signal_handler(int signum)
{
int i, sigs[] = { SIGINT, SIGHUP, SIGTERM };
for (i = 0; i < sizeof(sigs)/sizeof(sigs[0]); ++i) {
- struct sigaction sa = { 0 };
+ struct sigaction sa;
+ memset(&sa, 0, sizeof sa);
sa.sa_handler = signal_handler;
sa.sa_flags |= SA_RESTART;
sigaction(sigs[i], &sa, 0);
" 29: MPEG-4 HE-AAC v2 (SBR+PS)\n"
" 23: MPEG-4 AAC LD\n"
" 39: MPEG-4 AAC ELD\n"
-" 129: MPEG-2 AAC LC\n"
-" 132: MPEG-2 HE-AAC (SBR)\n"
-" 156: MPEG-2 HE-AAC v2 (SBR+PS)\n"
" -b, --bitrate <n> Bitrate in bits per seconds (for CBR)\n"
" -m, --bitrate-mode <n> Bitrate configuration\n"
" 0: CBR (default)\n"
" -a, --afterburner <n> Afterburner\n"
" 0: Off\n"
" 1: On(default)\n"
-" -L, --lowdelay-sbr Enable ELD-SBR (AAC ELD only)\n"
-" -s, --sbr-signaling <n> SBR signaling mode\n"
-" 0: Implicit, backward compatible(default)\n"
-" 1: Explicit SBR and implicit PS\n"
-" 2: Explicit hierarchical signaling\n"
+" -L, --lowdelay-sbr <-1|0|1> Configure SBR activity on AAC ELD\n"
+" -1: Use ELD SBR auto configurator\n"
+" 0: Disable SBR on ELD (default)\n"
+" 1: Enable SBR on ELD\n"
+" -s, --sbr-ratio <0|1|2> Controls activation of downsampled SBR\n"
+" 0: Use lib default (default)\n"
+" 1: downsampled SBR (default for ELD+SBR)\n"
+" 2: dual-rate SBR (default for HE-AAC)\n"
" -f, --transport-format <n> Transport format\n"
" 0: RAW (default, muxed into M4A)\n"
" 1: ADIF\n"
" transport layer\n"
"\n"
" -o <filename> Output filename\n"
-" --ignorelength Ignore length of WAV header\n"
+" -G, --gapless-mode <n> Encoder delay signaling for gapless playback\n"
+" 0: iTunSMPB (default)\n"
+" 1: ISO standard (edts + sgpd)\n"
+" 2: Both\n"
+" --include-sbr-delay Count SBR decoder delay in encoder delay\n"
+" This is not iTunes compatible, but is default\n"
+" behavior of FDK library.\n"
+" -I, --ignorelength Ignore length of WAV header\n"
" -S, --silent Don't print progress messages\n"
+" --moov-before-mdat Place moov box before mdat box on m4a output\n"
"\n"
"Options for raw (headerless) input:\n"
" -R, --raw Treat input as raw (by default WAV is\n"
" --tag-from-file <fcc>:<filename>\n"
" Same as above, but value is read from file.\n"
" --long-tag <name>:<value> Set arbitrary tag as iTunes custom metadata.\n"
+" --tag-from-json <filename[?dot_notation]>\n"
+" Read tags from JSON. By default, tags are\n"
+" assumed to be direct children of the root\n"
+" object(dictionary).\n"
+" Optionally, position of the dictionary\n"
+" that contains tags can be specified with\n"
+" dotted notation.\n"
+" Example:\n"
+" --tag-from-json /path/to/json?format.tags\n"
, fdkaac_version);
}
-typedef struct aacenc_tag_entry_t {
- uint32_t tag;
- const char *name;
- const char *data;
- uint32_t data_size;
- int is_file_name;
-} aacenc_tag_entry_t;
-
typedef struct aacenc_param_ex_t {
AACENC_PARAMS
char *input_filename;
+ FILE *input_fp;
char *output_filename;
+ FILE *output_fp;
+ unsigned gapless_mode;
+ unsigned include_sbr_delay;
unsigned ignore_length;
int silent;
+ int moov_before_mdat;
int is_raw;
unsigned raw_channels;
unsigned raw_rate;
const char *raw_format;
- aacenc_tag_entry_t *tag_table;
- unsigned tag_count;
- unsigned tag_table_capacity;
-} aacenc_param_ex_t;
+ aacenc_tag_store_t tags;
+ aacenc_tag_store_t source_tags;
+ aacenc_translate_generic_text_tag_ctx_t source_tag_ctx;
-static
-void param_add_itmf_entry(aacenc_param_ex_t *params, uint32_t tag,
- const char *key, const char *value, uint32_t size,
- int is_file_name)
-{
- aacenc_tag_entry_t *entry;
- if (params->tag_count == params->tag_table_capacity) {
- unsigned newsize = params->tag_table_capacity;
- newsize = newsize ? newsize * 2 : 1;
- params->tag_table =
- realloc(params->tag_table, newsize * sizeof(aacenc_tag_entry_t));
- params->tag_table_capacity = newsize;
- }
- entry = params->tag_table + params->tag_count;
- entry->tag = tag;
- if (tag == M4AF_FOURCC('-','-','-','-'))
- entry->name = key;
- entry->data = value;
- entry->data_size = size;
- entry->is_file_name = is_file_name;
- params->tag_count++;
-}
+ char *json_filename;
+} aacenc_param_ex_t;
static
int parse_options(int argc, char **argv, aacenc_param_ex_t *params)
{
int ch;
- unsigned n;
+ int n;
+#define OPT_INCLUDE_SBR_DELAY M4AF_FOURCC('s','d','l','y')
+#define OPT_MOOV_BEFORE_MDAT M4AF_FOURCC('m','o','o','v')
#define OPT_RAW_CHANNELS M4AF_FOURCC('r','c','h','n')
#define OPT_RAW_RATE M4AF_FOURCC('r','r','a','t')
#define OPT_RAW_FORMAT M4AF_FOURCC('r','f','m','t')
#define OPT_SHORT_TAG M4AF_FOURCC('s','t','a','g')
#define OPT_SHORT_TAG_FILE M4AF_FOURCC('s','t','g','f')
#define OPT_LONG_TAG M4AF_FOURCC('l','t','a','g')
+#define OPT_TAG_FROM_JSON M4AF_FOURCC('t','f','j','s')
static struct option long_options[] = {
{ "help", no_argument, 0, 'h' },
{ "bitrate-mode", required_argument, 0, 'm' },
{ "bandwidth", required_argument, 0, 'w' },
{ "afterburner", required_argument, 0, 'a' },
- { "lowdelay-sbr", no_argument, 0, 'L' },
- { "sbr-signaling", required_argument, 0, 's' },
+ { "lowdelay-sbr", required_argument, 0, 'L' },
+ { "sbr-ratio", required_argument, 0, 's' },
{ "transport-format", required_argument, 0, 'f' },
{ "adts-crc-check", no_argument, 0, 'C' },
{ "header-period", required_argument, 0, 'P' },
+ { "gapless-mode", required_argument, 0, 'G' },
+ { "include-sbr-delay", no_argument, 0, OPT_INCLUDE_SBR_DELAY },
{ "ignorelength", no_argument, 0, 'I' },
{ "silent", no_argument, 0, 'S' },
+ { "moov-before-mdat", no_argument, 0, OPT_MOOV_BEFORE_MDAT },
{ "raw", no_argument, 0, 'R' },
{ "raw-channels", required_argument, 0, OPT_RAW_CHANNELS },
{ "tag", required_argument, 0, OPT_SHORT_TAG },
{ "tag-from-file", required_argument, 0, OPT_SHORT_TAG_FILE },
{ "long-tag", required_argument, 0, OPT_LONG_TAG },
+ { "tag-from-json", required_argument, 0, OPT_TAG_FROM_JSON },
{ 0, 0, 0, 0 },
};
params->afterburner = 1;
aacenc_getmainargs(&argc, &argv);
- while ((ch = getopt_long(argc, argv, "hp:b:m:w:a:Ls:f:CP:Io:SR",
+ while ((ch = getopt_long(argc, argv, "hp:b:m:w:a:L:s:f:CP:G:Io:SR",
long_options, 0)) != EOF) {
switch (ch) {
case 'h':
params->afterburner = n;
break;
case 'L':
- params->lowdelay_sbr = 1;
+ if (sscanf(optarg, "%d", &n) != 1 || n < -1 || n > 1) {
+ fprintf(stderr, "invalid arg for lowdelay-sbr\n");
+ return -1;
+ }
+ params->lowdelay_sbr = n;
break;
case 's':
if (sscanf(optarg, "%u", &n) != 1 || n > 2) {
- fprintf(stderr, "invalid arg for sbr-signaling\n");
+ fprintf(stderr, "invalid arg for sbr-ratio\n");
return -1;
}
- params->sbr_signaling = n;
+ params->sbr_ratio = n;
break;
case 'f':
if (sscanf(optarg, "%u", &n) != 1) {
}
params->transport_format = n;
break;
- case 'c':
+ case 'C':
params->adts_crc_check = 1;
break;
case 'P':
case 'o':
params->output_filename = optarg;
break;
+ case 'G':
+ if (sscanf(optarg, "%u", &n) != 1 || n > 2) {
+ fprintf(stderr, "invalid arg for gapless-mode\n");
+ return -1;
+ }
+ params->gapless_mode = n;
+ break;
+ case OPT_INCLUDE_SBR_DELAY:
+ params->include_sbr_delay = 1;
+ break;
case 'I':
params->ignore_length = 1;
break;
case 'S':
params->silent = 1;
break;
+ case OPT_MOOV_BEFORE_MDAT:
+ params->moov_before_mdat = 1;
+ break;
case 'R':
params->is_raw = 1;
break;
case M4AF_TAG_TRACK:
case M4AF_TAG_DISK:
case M4AF_TAG_TEMPO:
- param_add_itmf_entry(params, ch, 0, optarg, strlen(optarg), 0);
+ aacenc_add_tag_to_store(¶ms->tags, ch, 0, optarg,
+ strlen(optarg), 0);
break;
case OPT_SHORT_TAG:
case OPT_SHORT_TAG_FILE:
for (; *optarg; ++optarg)
fcc = ((fcc << 8) | (*optarg & 0xff));
}
- param_add_itmf_entry(params, fcc, optarg, val, strlen(val),
- ch == OPT_SHORT_TAG_FILE);
+ aacenc_add_tag_to_store(¶ms->tags, fcc, optarg,
+ val, strlen(val),
+ ch == OPT_SHORT_TAG_FILE);
}
break;
+ case OPT_TAG_FROM_JSON:
+ params->json_filename = optarg;
+ break;
default:
return usage(), -1;
}
};
static
-int write_sample(FILE *ofp, m4af_ctx_t *m4af,
- const void *data, uint32_t size, uint32_t duration)
+int write_sample(FILE *ofp, m4af_ctx_t *m4af, aacenc_frame_t *frame)
{
if (!m4af) {
- fwrite(data, 1, size, ofp);
+ fwrite(frame->data, 1, frame->size, ofp);
if (ferror(ofp)) {
fprintf(stderr, "ERROR: fwrite(): %s\n", strerror(errno));
return -1;
}
- } else if (m4af_write_sample(m4af, 0, data, size, duration) < 0) {
+ } else if (m4af_write_sample(m4af, 0, frame->data, frame->size, 0) < 0) {
fprintf(stderr, "ERROR: failed to write m4a sample\n");
return -1;
}
return 0;
}
+static int do_smart_padding(int profile)
+{
+ return profile == 2 || profile == 5 || profile == 29;
+}
+
static
-int encode(wav_reader_t *wavf, HANDLE_AACENCODER encoder,
- uint32_t frame_length, FILE *ofp, m4af_ctx_t *m4af,
- int show_progress)
+int encode(aacenc_param_ex_t *params, pcm_reader_t *reader,
+ HANDLE_AACENCODER encoder, uint32_t frame_length,
+ m4af_ctx_t *m4af)
{
- uint8_t *ibuf = 0;
- int16_t *pcmbuf = 0;
- uint32_t pcmsize = 0;
- uint8_t *obuf = 0;
- uint32_t olen;
- uint32_t osize = 0;
+ int16_t *ibuf = 0, *ip;
+ aacenc_frame_t obuf[2] = {{ 0 }}, *obp;
+ unsigned flip = 0;
int nread = 1;
- int consumed;
int rc = -1;
- int frames_written = 0;
+ int remaining, consumed;
+ int frames_written = 0, encoded = 0;
aacenc_progress_t progress = { 0 };
- const pcm_sample_description_t *format = wav_get_format(wavf);
+ const pcm_sample_description_t *fmt = pcm_get_format(reader);
+ const int is_padding = do_smart_padding(params->profile);
+
+ ibuf = malloc(frame_length * fmt->bytes_per_frame);
+ aacenc_progress_init(&progress, pcm_get_length(reader), fmt->sample_rate);
- ibuf = malloc(frame_length * format->bytes_per_frame);
- aacenc_progress_init(&progress, wav_get_length(wavf), format->sample_rate);
- do {
+ for (;;) {
if (g_interrupted)
nread = 0;
- else if (nread) {
- if ((nread = wav_read_frames(wavf, ibuf, frame_length)) < 0) {
+ if (nread > 0) {
+ if ((nread = pcm_read_frames(reader, ibuf, frame_length)) < 0) {
fprintf(stderr, "ERROR: read failed\n");
goto END;
- } else if (nread > 0) {
- if (pcm_convert_to_native_sint16(format, ibuf, nread,
- &pcmbuf, &pcmsize) < 0) {
- fprintf(stderr, "ERROR: unsupported sample format\n");
- goto END;
- }
}
- if (show_progress)
- aacenc_progress_update(&progress, wav_get_position(wavf),
- format->sample_rate * 2);
+ if (!params->silent)
+ aacenc_progress_update(&progress, pcm_get_position(reader),
+ fmt->sample_rate * 2);
}
- if ((consumed = aac_encode_frame(encoder, format, pcmbuf, nread,
- &obuf, &olen, &osize)) < 0)
- goto END;
- if (olen > 0) {
- if (write_sample(ofp, m4af, obuf, olen, frame_length) < 0)
+ ip = ibuf;
+ remaining = nread;
+ do {
+ obp = &obuf[flip];
+ consumed = aac_encode_frame(encoder, fmt, ip, remaining, obp);
+ if (consumed < 0) goto END;
+ if (consumed == 0 && obp->size == 0) goto DONE;
+ if (obp->size == 0) break;
+
+ remaining -= consumed;
+ ip += consumed * fmt->channels_per_frame;
+ if (is_padding) {
+ /*
+ * As we pad 1 frame at beginning and ending by our extrapolator,
+ * we want to drop them.
+ * We delay output by 1 frame by double buffering, and discard
+ * second frame and final frame from the encoder.
+ * Since sbr_header is included in the first frame (in case of
+ * SBR), we cannot discard first frame. So we pick second instead.
+ */
+ flip ^= 1;
+ ++encoded;
+ if (encoded == 1 || encoded == 3)
+ continue;
+ }
+ if (write_sample(params->output_fp, m4af, &obuf[flip]) < 0)
goto END;
++frames_written;
- }
- } while (nread > 0 || olen > 0);
-
- if (show_progress)
- aacenc_progress_finish(&progress, wav_get_position(wavf));
+ } while (remaining > 0);
+ }
+DONE:
+ /*
+ * When interrupted, we haven't pulled out last extrapolated frames
+ * from the reader. Therefore, we have to write the final outcome.
+ */
+ if (g_interrupted) {
+ if (write_sample(params->output_fp, m4af, &obp[flip^1]) < 0)
+ goto END;
+ ++frames_written;
+ }
+ if (!params->silent)
+ aacenc_progress_finish(&progress, pcm_get_position(reader));
rc = frames_written;
END:
if (ibuf) free(ibuf);
- if (pcmbuf) free(pcmbuf);
- if (obuf) free(obuf);
+ if (obuf[0].data) free(obuf[0].data);
+ if (obuf[1].data) free(obuf[1].data);
return rc;
}
-static
-char *load_tag_from_file(const char *path, uint32_t *data_size)
-{
- FILE *fp = 0;
- char *data = 0;
- int64_t size;
-
- if ((fp = aacenc_fopen(path, "rb")) == NULL) {
- aacenc_fprintf(stderr, "WARNING: %s: %s\n", path, strerror(errno));
- goto END;
- }
- fseeko(fp, 0, SEEK_END);
- size = ftello(fp);
- if (size > 5*1024*1024) {
- aacenc_fprintf(stderr, "WARNING: %s: size too large\n", path);
- goto END;
- }
- fseeko(fp, 0, SEEK_SET);
- data = malloc(size + 1);
- if (data) fread(data, 1, size, fp);
- data[size] = 0;
- *data_size = (uint32_t)size;
-END:
- if (fp) fclose(fp);
- return data;
-}
-
-static
-void put_tag_entry(m4af_ctx_t *m4af, const aacenc_tag_entry_t *tag)
-{
- unsigned m, n = 0;
- const char *data = tag->data;
- uint32_t data_size = tag->data_size;
- char *file_contents = 0;
-
- if (tag->is_file_name) {
- data = file_contents = load_tag_from_file(tag->data, &data_size);
- if (!data) return;
- }
- switch (tag->tag) {
- case M4AF_TAG_TRACK:
- if (sscanf(data, "%u/%u", &m, &n) >= 1)
- m4af_add_itmf_track_tag(m4af, m, n);
- break;
- case M4AF_TAG_DISK:
- if (sscanf(data, "%u/%u", &m, &n) >= 1)
- m4af_add_itmf_disk_tag(m4af, m, n);
- break;
- case M4AF_TAG_GENRE_ID3:
- if (sscanf(data, "%u", &n) == 1)
- m4af_add_itmf_genre_tag(m4af, n);
- break;
- case M4AF_TAG_TEMPO:
- if (sscanf(data, "%u", &n) == 1)
- m4af_add_itmf_int16_tag(m4af, tag->tag, n);
- break;
- case M4AF_TAG_COMPILATION:
- case M4AF_FOURCC('a','k','I','D'):
- case M4AF_FOURCC('h','d','v','d'):
- case M4AF_FOURCC('p','c','s','t'):
- case M4AF_FOURCC('p','g','a','p'):
- case M4AF_FOURCC('r','t','n','g'):
- case M4AF_FOURCC('s','t','i','k'):
- if (sscanf(data, "%u", &n) == 1)
- m4af_add_itmf_int8_tag(m4af, tag->tag, n);
- break;
- case M4AF_FOURCC('a','t','I','D'):
- case M4AF_FOURCC('c','m','I','D'):
- case M4AF_FOURCC('c','n','I','D'):
- case M4AF_FOURCC('g','e','I','D'):
- case M4AF_FOURCC('s','f','I','D'):
- case M4AF_FOURCC('t','v','s','n'):
- case M4AF_FOURCC('t','v','s','s'):
- if (sscanf(data, "%u", &n) == 1)
- m4af_add_itmf_int32_tag(m4af, tag->tag, n);
- break;
- case M4AF_FOURCC('p','l','I','D'):
- {
- int64_t qn;
- if (sscanf(data, "%" SCNd64, &qn) == 1)
- m4af_add_itmf_int64_tag(m4af, tag->tag, qn);
- break;
- }
- case M4AF_TAG_ARTWORK:
- {
- int data_type = 0;
- if (!memcmp(data, "GIF", 3))
- data_type = M4AF_GIF;
- else if (!memcmp(data, "\xff\xd8\xff", 3))
- data_type = M4AF_JPEG;
- else if (!memcmp(data, "\x89PNG", 4))
- data_type = M4AF_PNG;
- if (data_type)
- m4af_add_itmf_short_tag(m4af, tag->tag, data_type,
- data, data_size);
- break;
- }
- case M4AF_FOURCC('-','-','-','-'):
- {
- char *u8 = aacenc_to_utf8(data);
- m4af_add_itmf_long_tag(m4af, tag->name, u8);
- free(u8);
- break;
- }
- case M4AF_TAG_TITLE:
- case M4AF_TAG_ARTIST:
- case M4AF_TAG_ALBUM:
- case M4AF_TAG_GENRE:
- case M4AF_TAG_DATE:
- case M4AF_TAG_COMPOSER:
- case M4AF_TAG_GROUPING:
- case M4AF_TAG_COMMENT:
- case M4AF_TAG_LYRICS:
- case M4AF_TAG_TOOL:
- case M4AF_TAG_ALBUM_ARTIST:
- case M4AF_TAG_DESCRIPTION:
- case M4AF_TAG_LONG_DESCRIPTION:
- case M4AF_TAG_COPYRIGHT:
- case M4AF_FOURCC('a','p','I','D'):
- case M4AF_FOURCC('c','a','t','g'):
- case M4AF_FOURCC('k','e','y','w'):
- case M4AF_FOURCC('p','u','r','d'):
- case M4AF_FOURCC('p','u','r','l'):
- case M4AF_FOURCC('s','o','a','a'):
- case M4AF_FOURCC('s','o','a','l'):
- case M4AF_FOURCC('s','o','a','r'):
- case M4AF_FOURCC('s','o','c','o'):
- case M4AF_FOURCC('s','o','n','m'):
- case M4AF_FOURCC('s','o','s','n'):
- case M4AF_FOURCC('t','v','e','n'):
- case M4AF_FOURCC('t','v','n','n'):
- case M4AF_FOURCC('t','v','s','h'):
- case M4AF_FOURCC('x','i','d',' '):
- case M4AF_FOURCC('\xa9','e','n','c'):
- case M4AF_FOURCC('\xa9','s','t','3'):
- {
- char *u8 = aacenc_to_utf8(data);
- m4af_add_itmf_string_tag(m4af, tag->tag, u8);
- free(u8);
- break;
- }
- default:
- fprintf(stderr, "WARNING: unknown/unsupported tag: %c%c%c%c\n",
- tag->tag >> 24, (tag->tag >> 16) & 0xff,
- (tag->tag >> 8) & 0xff, tag->tag & 0xff);
- }
- if (file_contents) free(file_contents);
-}
-
static
void put_tool_tag(m4af_ctx_t *m4af, const aacenc_param_ex_t *params,
HANDLE_AACENCODER encoder)
{
char tool_info[256];
char *p = tool_info;
- LIB_INFO *lib_info = 0;
+ LIB_INFO lib_info;
p += sprintf(p, PROGNAME " %s, ", fdkaac_version);
-
- lib_info = calloc(FDK_MODULE_LAST, sizeof(LIB_INFO));
- if (aacEncGetLibInfo(lib_info) == AACENC_OK) {
- int i;
- for (i = 0; i < FDK_MODULE_LAST; ++i)
- if (lib_info[i].module_id == FDK_AACENC)
- break;
- p += sprintf(p, "libfdk-aac %s, ", lib_info[i].versionStr);
- }
- free(lib_info);
+ aacenc_get_lib_info(&lib_info);
+ p += sprintf(p, "libfdk-aac %s, ", lib_info.versionStr);
if (params->bitrate_mode)
sprintf(p, "VBR mode %d", params->bitrate_mode);
else
HANDLE_AACENCODER encoder)
{
unsigned i;
- aacenc_tag_entry_t *tag = params->tag_table;
- for (i = 0; i < params->tag_count; ++i, ++tag)
- put_tag_entry(m4af, tag);
+ aacenc_tag_entry_t *tag;
+
+ tag = params->source_tags.tag_table;
+ for (i = 0; i < params->source_tags.tag_count; ++i, ++tag)
+ aacenc_write_tag_entry(m4af, tag);
+
+ if (params->json_filename)
+ aacenc_write_tags_from_json(m4af, params->json_filename);
+
+ tag = params->tags.tag_table;
+ for (i = 0; i < params->tags.tag_count; ++i, ++tag)
+ aacenc_write_tag_entry(m4af, tag);
put_tool_tag(m4af, params, encoder);
- if (m4af_finalize(m4af) < 0) {
+ if (m4af_finalize(m4af, params->moov_before_mdat) < 0) {
fprintf(stderr, "ERROR: failed to finalize m4a\n");
return -1;
}
const char *ext_org = strrchr(base, '.');
if (ext_org) ilen = ext_org - base;
p = malloc(ilen + ext_len + 1);
- sprintf(p, "%.*s%s", ilen, base, ext);
+ sprintf(p, "%.*s%s", (int)ilen, base, ext);
}
return p;
}
return 0;
}
+static pcm_io_vtbl_t pcm_io_vtbl = {
+ read_callback, seek_callback, tell_callback
+};
+static pcm_io_vtbl_t pcm_io_vtbl_noseek = { read_callback, 0, tell_callback };
+
+static
+pcm_reader_t *open_input(aacenc_param_ex_t *params)
+{
+ pcm_io_context_t io = { 0 };
+ pcm_reader_t *reader = 0;
+
+ if ((params->input_fp = aacenc_fopen(params->input_filename, "rb")) == 0) {
+ aacenc_fprintf(stderr, "ERROR: %s: %s\n", params->input_filename,
+ strerror(errno));
+ goto FAIL;
+ }
+ io.cookie = params->input_fp;
+ if (aacenc_seekable(params->input_fp))
+ io.vtbl = &pcm_io_vtbl;
+ else
+ io.vtbl = &pcm_io_vtbl_noseek;
+
+ if (params->is_raw) {
+ int bytes_per_channel;
+ pcm_sample_description_t desc = { 0 };
+ if (parse_raw_spec(params->raw_format, &desc) < 0) {
+ fprintf(stderr, "ERROR: invalid raw-format spec\n");
+ goto FAIL;
+ }
+ desc.sample_rate = params->raw_rate;
+ desc.channels_per_frame = params->raw_channels;
+ bytes_per_channel = (desc.bits_per_channel + 7) / 8;
+ desc.bytes_per_frame = params->raw_channels * bytes_per_channel;
+ if ((reader = raw_open(&io, &desc)) == 0) {
+ fprintf(stderr, "ERROR: failed to open raw input\n");
+ goto FAIL;
+ }
+ } else {
+ int c;
+ ungetc(c = getc(params->input_fp), params->input_fp);
+
+ switch (c) {
+ case 'R':
+ if ((reader = wav_open(&io, params->ignore_length)) == 0) {
+ fprintf(stderr, "ERROR: broken / unsupported input file\n");
+ goto FAIL;
+ }
+ break;
+ case 'c':
+ params->source_tag_ctx.add = aacenc_add_tag_entry_to_store;
+ params->source_tag_ctx.add_ctx = ¶ms->source_tags;
+ if ((reader = caf_open(&io,
+ aacenc_translate_generic_text_tag,
+ ¶ms->source_tag_ctx)) == 0) {
+ fprintf(stderr, "ERROR: broken / unsupported input file\n");
+ goto FAIL;
+ }
+ break;
+ default:
+ fprintf(stderr, "ERROR: unsupported input file\n");
+ goto FAIL;
+ }
+ }
+ reader = pcm_open_native_converter(reader);
+ if (reader && PCM_IS_FLOAT(pcm_get_format(reader)))
+ reader = limiter_open(reader);
+ if (reader && (reader = pcm_open_sint16_converter(reader)) != 0) {
+ if (do_smart_padding(params->profile))
+ reader = extrapolater_open(reader);
+ }
+ return reader;
+FAIL:
+ return 0;
+}
+
int main(int argc, char **argv)
{
- wav_io_context_t wav_io = { read_callback, seek_callback, tell_callback };
- m4af_io_callbacks_t
- m4af_io = { 0, write_callback, seek_callback, tell_callback };
+ static m4af_io_callbacks_t m4af_io = {
+ read_callback, write_callback, seek_callback, tell_callback
+ };
aacenc_param_ex_t params = { 0 };
int result = 2;
- FILE *ifp = 0;
- FILE *ofp = 0;
char *output_filename = 0;
- wav_reader_t *wavf = 0;
+ pcm_reader_t *reader = 0;
HANDLE_AACENCODER encoder = 0;
AACENC_InfoStruct aacinfo = { 0 };
m4af_ctx_t *m4af = 0;
const pcm_sample_description_t *sample_format;
- int downsampled_timescale = 0;
int frame_count = 0;
- struct stat stb = { 0 };
+ int sbr_mode = 0;
+ unsigned scale_shift = 0;
setlocale(LC_CTYPE, "");
setbuf(stderr, 0);
if (parse_options(argc, argv, ¶ms) < 0)
return 1;
- if ((ifp = aacenc_fopen(params.input_filename, "rb")) == 0) {
- aacenc_fprintf(stderr, "ERROR: %s: %s\n", params.input_filename,
- strerror(errno));
+ if ((reader = open_input(¶ms)) == 0)
goto END;
+
+ sample_format = pcm_get_format(reader);
+
+ sbr_mode = aacenc_is_sbr_active((aacenc_param_t*)¶ms);
+ if (sbr_mode && !aacenc_is_sbr_ratio_available()) {
+ fprintf(stderr, "WARNING: Only dual-rate SBR is available "
+ "for this version\n");
+ params.sbr_ratio = 2;
}
- if (fstat(fileno(ifp), &stb) == 0 && (stb.st_mode & S_IFMT) != S_IFREG) {
- wav_io.seek = 0;
- wav_io.tell = 0;
- }
- if (!params.is_raw) {
- if ((wavf = wav_open(&wav_io, ifp, params.ignore_length)) == 0) {
- fprintf(stderr, "ERROR: broken / unsupported input file\n");
- goto END;
- }
- } else {
- int bytes_per_channel;
- pcm_sample_description_t desc = { 0 };
- if (parse_raw_spec(params.raw_format, &desc) < 0) {
- fprintf(stderr, "ERROR: invalid raw-format spec\n");
- goto END;
- }
- desc.sample_rate = params.raw_rate;
- desc.channels_per_frame = params.raw_channels;
- bytes_per_channel = (desc.bits_per_channel + 7) / 8;
- desc.bytes_per_frame = params.raw_channels * bytes_per_channel;
- if ((wavf = raw_open(&wav_io, ifp, &desc)) == 0) {
- fprintf(stderr, "ERROR: failed to open raw input\n");
- goto END;
- }
+ scale_shift = aacenc_is_dual_rate_sbr((aacenc_param_t*)¶ms);
+ params.sbr_signaling = 0;
+ if (sbr_mode) {
+ if (params.transport_format == TT_MP4_LOAS || !scale_shift)
+ params.sbr_signaling = 2;
+ if (params.transport_format == TT_MP4_RAW &&
+ aacenc_is_explicit_bw_compatible_sbr_signaling_available())
+ params.sbr_signaling = 1;
}
- sample_format = wav_get_format(wavf);
-
if (aacenc_init(&encoder, (aacenc_param_t*)¶ms, sample_format,
&aacinfo) < 0)
goto END;
params.output_filename = output_filename;
}
- if ((ofp = aacenc_fopen(params.output_filename, "wb")) == 0) {
+ if ((params.output_fp = aacenc_fopen(params.output_filename, "wb+")) == 0) {
aacenc_fprintf(stderr, "ERROR: %s: %s\n", params.output_filename,
strerror(errno));
goto END;
}
handle_signals();
+
if (!params.transport_format) {
uint32_t scale;
unsigned framelen = aacinfo.frameLength;
- int sbr_mode = aacenc_is_sbr_active((aacenc_param_t*)¶ms);
- int sig_mode = aacEncoder_GetParam(encoder, AACENC_SIGNALING_MODE);
- if (sbr_mode && !sig_mode)
- downsampled_timescale = 1;
- scale = sample_format->sample_rate >> downsampled_timescale;
- if ((m4af = m4af_create(M4AF_CODEC_MP4A, scale, &m4af_io, ofp)) < 0)
+ scale = sample_format->sample_rate >> scale_shift;
+ if ((m4af = m4af_create(M4AF_CODEC_MP4A, scale, &m4af_io,
+ params.output_fp)) < 0)
goto END;
- m4af_set_decoder_specific_info(m4af, 0, aacinfo.confBuf,
- aacinfo.confSize);
- m4af_set_fixed_frame_duration(m4af, 0,
- framelen >> downsampled_timescale);
+ m4af_set_num_channels(m4af, 0, sample_format->channels_per_frame);
+ m4af_set_fixed_frame_duration(m4af, 0, framelen >> scale_shift);
+ if (aacenc_is_explicit_bw_compatible_sbr_signaling_available())
+ m4af_set_decoder_specific_info(m4af, 0,
+ aacinfo.confBuf, aacinfo.confSize);
+ else {
+ uint8_t mp4asc[32];
+ uint32_t ascsize = sizeof(mp4asc);
+ aacenc_mp4asc((aacenc_param_t*)¶ms, aacinfo.confBuf,
+ aacinfo.confSize, mp4asc, &ascsize);
+ m4af_set_decoder_specific_info(m4af, 0, mp4asc, ascsize);
+ }
+ m4af_set_vbr_mode(m4af, 0, params.bitrate_mode);
+ m4af_set_priming_mode(m4af, params.gapless_mode + 1);
m4af_begin_write(m4af);
}
- frame_count = encode(wavf, encoder, aacinfo.frameLength, ofp, m4af,
- !params.silent);
+ frame_count = encode(¶ms, reader, encoder, aacinfo.frameLength, m4af);
if (frame_count < 0)
goto END;
if (m4af) {
+ uint32_t padding;
+#if AACENCODER_LIB_VL0 < 4
uint32_t delay = aacinfo.encoderDelay;
- int64_t frames_read = wav_get_position(wavf);
- uint32_t padding = frame_count * aacinfo.frameLength
- - frames_read - aacinfo.encoderDelay;
- m4af_set_priming(m4af, 0, delay >> downsampled_timescale,
- padding >> downsampled_timescale);
+ if (sbr_mode && params.profile != AOT_ER_AAC_ELD
+ && !params.include_sbr_delay)
+ delay -= 481 << scale_shift;
+#else
+ uint32_t delay = params.include_sbr_delay ? aacinfo.nDelay
+ : aacinfo.nDelayCore;
+#endif
+ int64_t frames_read = pcm_get_position(reader);
+
+ padding = frame_count * aacinfo.frameLength - frames_read - delay;
+ m4af_set_priming(m4af, 0, delay >> scale_shift, padding >> scale_shift);
if (finalize_m4a(m4af, ¶ms, encoder) < 0)
goto END;
}
result = 0;
END:
- if (wavf) wav_teardown(&wavf);
- if (ifp) fclose(ifp);
+ if (reader) pcm_teardown(&reader);
+ if (params.input_fp) fclose(params.input_fp);
if (m4af) m4af_teardown(&m4af);
- if (ofp) fclose(ofp);
+ if (params.output_fp) fclose(params.output_fp);
if (encoder) aacEncClose(&encoder);
if (output_filename) free(output_filename);
- if (params.tag_table) free(params.tag_table);
+ if (params.tags.tag_table)
+ aacenc_free_tag_store(¶ms.tags);
+ if (params.source_tags.tag_table)
+ aacenc_free_tag_store(¶ms.source_tags);
return result;
}