mirror of
https://github.com/mstorsjo/fdk-aac.git
synced 2024-12-28 08:50:08 +01:00
384 lines
12 KiB
C
384 lines
12 KiB
C
/* ------------------------------------------------------------------
|
|
* Copyright (C) 2017 Martin Storsjo
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
|
* express or implied.
|
|
* See the License for the specific language governing permissions
|
|
* and limitations under the License.
|
|
* -------------------------------------------------------------------
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "aacenc_lib.h"
|
|
#include "aacdecoder_lib.h"
|
|
#include "wavreader.h"
|
|
#include "sha1.h"
|
|
|
|
|
|
static int encoder_input_samples, encoder_input_size;
|
|
static int decoder_output_skip;
|
|
static int16_t *encoder_input;
|
|
static int max_diff;
|
|
static uint64_t diff_sum, diff_samples;
|
|
static SHA1Context encode_hash, decode_hash;
|
|
|
|
static void init_encoder_input(void) {
|
|
encoder_input_samples = 0;
|
|
max_diff = 0;
|
|
diff_sum = diff_samples = 0;
|
|
}
|
|
|
|
static void free_encoder_input(void) {
|
|
free(encoder_input);
|
|
encoder_input = NULL;
|
|
encoder_input_size = 0;
|
|
}
|
|
|
|
static void append_encoder_input(const int16_t *input, int samples) {
|
|
if (encoder_input_samples + samples > encoder_input_size) {
|
|
int size = 2*(encoder_input_samples + samples);
|
|
int16_t *ptr = realloc(encoder_input, size * sizeof(*encoder_input));
|
|
if (!ptr)
|
|
abort();
|
|
encoder_input = ptr;
|
|
encoder_input_size = size;
|
|
}
|
|
memcpy(encoder_input + encoder_input_samples, input, samples * sizeof(*input));
|
|
encoder_input_samples += samples;
|
|
}
|
|
|
|
static void compare_decoder_output(const int16_t *output, int samples) {
|
|
int i;
|
|
// TODO: Stereo upconvert?
|
|
SHA1Input(&decode_hash, (const unsigned char*) output, samples * sizeof(*output));
|
|
if (decoder_output_skip > 0) {
|
|
int n = samples;
|
|
if (n > decoder_output_skip)
|
|
n = decoder_output_skip;
|
|
output += n;
|
|
samples -= n;
|
|
decoder_output_skip -= n;
|
|
if (samples <= 0)
|
|
return;
|
|
}
|
|
if (samples > encoder_input_samples)
|
|
samples = encoder_input_samples;
|
|
for (i = 0; i < samples; i++) {
|
|
int diff = abs(encoder_input[i] - output[i]);
|
|
if (diff > max_diff)
|
|
max_diff = diff;
|
|
diff_sum += diff;
|
|
diff_samples++;
|
|
}
|
|
memmove(encoder_input, encoder_input + samples, (encoder_input_samples - samples) * sizeof(*encoder_input));
|
|
encoder_input_samples -= samples;
|
|
}
|
|
|
|
static int decode(HANDLE_AACDECODER decoder, const uint8_t *ptr, int size, uint8_t *decoder_buffer, int decoder_buffer_size, int channels) {
|
|
AAC_DECODER_ERROR err;
|
|
CStreamInfo *info;
|
|
UINT valid, buffer_size;
|
|
SHA1Input(&encode_hash, ptr, size);
|
|
do {
|
|
valid = buffer_size = size;
|
|
err = aacDecoder_Fill(decoder, (UCHAR**) &ptr, &buffer_size, &valid);
|
|
ptr += buffer_size - valid;
|
|
size -= buffer_size - valid;
|
|
if (err == AAC_DEC_NOT_ENOUGH_BITS)
|
|
continue;
|
|
if (err != AAC_DEC_OK)
|
|
break;
|
|
err = aacDecoder_DecodeFrame(decoder, (INT_PCM *) decoder_buffer, decoder_buffer_size / sizeof(INT_PCM), 0);
|
|
if (!ptr && err != AAC_DEC_OK)
|
|
break;
|
|
if (err == AAC_DEC_NOT_ENOUGH_BITS)
|
|
continue;
|
|
if (err != AAC_DEC_OK) {
|
|
fprintf(stderr, "Decoding failed\n");
|
|
return 1;
|
|
}
|
|
info = aacDecoder_GetStreamInfo(decoder);
|
|
if (info->numChannels != channels) {
|
|
fprintf(stderr, "Mismatched number of channels, input %d, output %d\n", channels, info->numChannels);
|
|
return 1;
|
|
}
|
|
compare_decoder_output((int16_t*) decoder_buffer, info->numChannels * info->frameSize);
|
|
} while (size > 0);
|
|
return 0;
|
|
}
|
|
|
|
static int test_encode_decode(const char *infile, int aot, int afterburner, int eld_sbr, int vbr, int bitrate, int adts) {
|
|
void *wav;
|
|
int format, sample_rate, channels, bits_per_sample;
|
|
int input_size;
|
|
uint8_t* input_buf;
|
|
int16_t* convert_buf;
|
|
HANDLE_AACENCODER encoder;
|
|
CHANNEL_MODE mode;
|
|
AACENC_InfoStruct info = { 0 };
|
|
HANDLE_AACDECODER decoder;
|
|
int ret = 0;
|
|
int decoder_buffer_size = 2048 * 2 * 8;
|
|
uint8_t* decoder_buffer = malloc(decoder_buffer_size);
|
|
int avg_diff;
|
|
|
|
fprintf(stderr, "Testing encoding with aot %d afterburner %d eld_sbr %d vbr %d bitrate %d adts %d\n", aot, afterburner, eld_sbr, vbr, bitrate, adts);
|
|
init_encoder_input();
|
|
|
|
wav = wav_read_open(infile);
|
|
if (!wav) {
|
|
fprintf(stderr, "Unable to open wav file %s\n", infile);
|
|
return 1;
|
|
}
|
|
if (!wav_get_header(wav, &format, &channels, &sample_rate, &bits_per_sample, NULL)) {
|
|
fprintf(stderr, "Bad wav file %s\n", infile);
|
|
return 1;
|
|
}
|
|
if (format != 1) {
|
|
fprintf(stderr, "Unsupported WAV format %d\n", format);
|
|
return 1;
|
|
}
|
|
if (bits_per_sample != 16) {
|
|
fprintf(stderr, "Unsupported WAV sample depth %d\n", bits_per_sample);
|
|
return 1;
|
|
}
|
|
switch (channels) {
|
|
case 1: mode = MODE_1; break;
|
|
case 2: mode = MODE_2; break;
|
|
case 3: mode = MODE_1_2; break;
|
|
case 4: mode = MODE_1_2_1; break;
|
|
case 5: mode = MODE_1_2_2; break;
|
|
case 6: mode = MODE_1_2_2_1; break;
|
|
default:
|
|
fprintf(stderr, "Unsupported WAV channels %d\n", channels);
|
|
return 1;
|
|
}
|
|
if (aacEncOpen(&encoder, 0, channels) != AACENC_OK) {
|
|
fprintf(stderr, "Unable to open encoder\n");
|
|
return 1;
|
|
}
|
|
if (aacEncoder_SetParam(encoder, AACENC_AOT, aot) != AACENC_OK) {
|
|
fprintf(stderr, "Unable to set the AOT\n");
|
|
return 1;
|
|
}
|
|
if (aot == 39 && eld_sbr) {
|
|
if (aacEncoder_SetParam(encoder, AACENC_SBR_MODE, 1) != AACENC_OK) {
|
|
fprintf(stderr, "Unable to set SBR mode for ELD\n");
|
|
return 1;
|
|
}
|
|
}
|
|
if (aacEncoder_SetParam(encoder, AACENC_SAMPLERATE, sample_rate) != AACENC_OK) {
|
|
fprintf(stderr, "Unable to set the AOT\n");
|
|
return 1;
|
|
}
|
|
if (aacEncoder_SetParam(encoder, AACENC_CHANNELMODE, mode) != AACENC_OK) {
|
|
fprintf(stderr, "Unable to set the channel mode\n");
|
|
return 1;
|
|
}
|
|
if (aacEncoder_SetParam(encoder, AACENC_CHANNELORDER, 1) != AACENC_OK) {
|
|
fprintf(stderr, "Unable to set the wav channel order\n");
|
|
return 1;
|
|
}
|
|
if (vbr) {
|
|
if (aacEncoder_SetParam(encoder, AACENC_BITRATEMODE, vbr) != AACENC_OK) {
|
|
fprintf(stderr, "Unable to set the VBR bitrate mode\n");
|
|
return 1;
|
|
}
|
|
} else {
|
|
if (aacEncoder_SetParam(encoder, AACENC_BITRATE, bitrate) != AACENC_OK) {
|
|
fprintf(stderr, "Unable to set the bitrate\n");
|
|
return 1;
|
|
}
|
|
}
|
|
if (aacEncoder_SetParam(encoder, AACENC_TRANSMUX, adts ? 2 : 0) != AACENC_OK) {
|
|
fprintf(stderr, "Unable to set the ADTS transmux\n");
|
|
return 1;
|
|
}
|
|
if (aacEncoder_SetParam(encoder, AACENC_AFTERBURNER, afterburner) != AACENC_OK) {
|
|
fprintf(stderr, "Unable to set the afterburner mode\n");
|
|
return 1;
|
|
}
|
|
if (aacEncEncode(encoder, NULL, NULL, NULL, NULL) != AACENC_OK) {
|
|
fprintf(stderr, "Unable to initialize the encoder\n");
|
|
return 1;
|
|
}
|
|
if (aacEncInfo(encoder, &info) != AACENC_OK) {
|
|
fprintf(stderr, "Unable to get the encoder info\n");
|
|
return 1;
|
|
}
|
|
|
|
input_size = channels*2*info.frameLength;
|
|
input_buf = (uint8_t*) malloc(input_size);
|
|
convert_buf = (int16_t*) malloc(input_size);
|
|
|
|
decoder_output_skip = channels * info.nDelay;
|
|
|
|
decoder = aacDecoder_Open(adts ? TT_MP4_ADTS : TT_MP4_RAW, 1);
|
|
if (!adts) {
|
|
UCHAR *bufArray[] = { info.confBuf };
|
|
if (aacDecoder_ConfigRaw(decoder, (UCHAR**) bufArray, &info.confSize) != AAC_DEC_OK) {
|
|
fprintf(stderr, "Unable to set ASC\n");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
}
|
|
aacDecoder_SetParam(decoder, AAC_CONCEAL_METHOD, 1);
|
|
aacDecoder_SetParam(decoder, AAC_PCM_LIMITER_ENABLE, 0);
|
|
while (1) {
|
|
AACENC_BufDesc in_buf = { 0 }, out_buf = { 0 };
|
|
AACENC_InArgs in_args = { 0 };
|
|
AACENC_OutArgs out_args = { 0 };
|
|
int in_identifier = IN_AUDIO_DATA;
|
|
int in_size, in_elem_size;
|
|
int out_identifier = OUT_BITSTREAM_DATA;
|
|
int out_size, out_elem_size;
|
|
int read, i;
|
|
void *in_ptr, *out_ptr;
|
|
uint8_t outbuf[20480];
|
|
AACENC_ERROR err;
|
|
|
|
read = wav_read_data(wav, input_buf, input_size);
|
|
for (i = 0; i < read/2; i++) {
|
|
const uint8_t* in = &input_buf[2*i];
|
|
convert_buf[i] = in[0] | (in[1] << 8);
|
|
}
|
|
in_ptr = convert_buf;
|
|
in_size = read;
|
|
in_elem_size = 2;
|
|
|
|
in_buf.numBufs = 1;
|
|
in_buf.bufs = &in_ptr;
|
|
in_buf.bufferIdentifiers = &in_identifier;
|
|
in_buf.bufSizes = &in_size;
|
|
in_buf.bufElSizes = &in_elem_size;
|
|
|
|
if (read <= 0) {
|
|
in_args.numInSamples = -1;
|
|
} else {
|
|
in_args.numInSamples = read/2;
|
|
append_encoder_input(convert_buf, in_args.numInSamples);
|
|
}
|
|
out_ptr = outbuf;
|
|
out_size = sizeof(outbuf);
|
|
out_elem_size = 1;
|
|
out_buf.numBufs = 1;
|
|
out_buf.bufs = &out_ptr;
|
|
out_buf.bufferIdentifiers = &out_identifier;
|
|
out_buf.bufSizes = &out_size;
|
|
out_buf.bufElSizes = &out_elem_size;
|
|
|
|
if ((err = aacEncEncode(encoder, &in_buf, &out_buf, &in_args, &out_args)) != AACENC_OK) {
|
|
if (err == AACENC_ENCODE_EOF)
|
|
break;
|
|
fprintf(stderr, "Encoding failed\n");
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
if (out_args.numOutBytes == 0)
|
|
continue;
|
|
|
|
if (decode(decoder, outbuf, out_args.numOutBytes, decoder_buffer, decoder_buffer_size, channels)) {
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if (encoder_input_samples > 0) {
|
|
fprintf(stderr, "%d unmatched samples left at the end\n", encoder_input_samples);
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
avg_diff = 0;
|
|
if (diff_samples > 0)
|
|
avg_diff = diff_sum / diff_samples;
|
|
if (/*max_diff > 10000 ||*/ avg_diff > ((aot == 23) ? 2500 : (aot == 29) ? 1500 : 300)) {
|
|
fprintf(stderr, "max_diff %d, avg_diff %d\n", max_diff, avg_diff);
|
|
ret = 1;
|
|
goto end;
|
|
}
|
|
|
|
end:
|
|
free(input_buf);
|
|
free(convert_buf);
|
|
wav_read_close(wav);
|
|
aacEncClose(&encoder);
|
|
free(decoder_buffer);
|
|
aacDecoder_Close(decoder);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
const char* infile;
|
|
void *wav;
|
|
int sample_rate, channels;
|
|
int failures = 0;
|
|
int i;
|
|
unsigned char encode_digest[SHA_DIGEST_LENGTH], decode_digest[SHA_DIGEST_LENGTH];
|
|
if (argc < 2) {
|
|
printf("%s input.wav\n", argv[0]);
|
|
return 1;
|
|
}
|
|
infile = argv[1];
|
|
|
|
wav = wav_read_open(infile);
|
|
if (!wav) {
|
|
fprintf(stderr, "Unable to open wav file %s\n", infile);
|
|
return 1;
|
|
}
|
|
if (!wav_get_header(wav, NULL, &channels, &sample_rate, NULL, NULL)) {
|
|
fprintf(stderr, "Bad wav file %s\n", infile);
|
|
return 1;
|
|
}
|
|
wav_read_close(wav);
|
|
|
|
SHA1Reset(&encode_hash);
|
|
SHA1Reset(&decode_hash);
|
|
|
|
failures += test_encode_decode(infile, 2, 0, 0, 0, 64000, 0); // AAC-LC, without afterburner
|
|
for (i = 0; i < 2; i++)
|
|
failures += test_encode_decode(infile, 2, 1, 0, 0, 64000, i); // AAC-LC
|
|
for (i = 1; i <= 5; i++)
|
|
failures += test_encode_decode(infile, 2, 1, 0, i, 0, 0); // AAC-LC VBR
|
|
if (channels == 2) {
|
|
// HE-AACv2 only works for stereo; HE-AACv1 gets upconverted to stereo (which we don't match properly)
|
|
for (i = 0; i < 2; i++)
|
|
failures += test_encode_decode(infile, 5, 1, 0, 0, 64000, i); // HE-AAC
|
|
for (i = 1; i <= 5; i++)
|
|
failures += test_encode_decode(infile, 5, 1, 0, i, 0, 0); // HE-AAC VBR
|
|
for (i = 0; i < 2; i++)
|
|
failures += test_encode_decode(infile, 29, 1, 0, 0, 64000, i); // HE-AACv2
|
|
for (i = 1; i <= 5; i++)
|
|
failures += test_encode_decode(infile, 29, 1, 0, i, 0, 0); // HE-AACv2 VBR
|
|
}
|
|
if (channels == 1)
|
|
failures += test_encode_decode(infile, 23, 1, 0, 0, 64000, 0); // AAC-LD
|
|
failures += test_encode_decode(infile, 39, 1, 0, 0, 64000, 0); // AAC-ELD
|
|
failures += test_encode_decode(infile, 39, 1, 1, 0, 64000, 0); // AAC-ELD with SBR
|
|
|
|
free_encoder_input();
|
|
fprintf(stderr, "%d failures\n", failures);
|
|
SHA1Result(&encode_hash, encode_digest);
|
|
SHA1Result(&decode_hash, decode_digest);
|
|
printf("encode hash: ");
|
|
for (i = 0; i < SHA_DIGEST_LENGTH; i++)
|
|
printf("%02x", encode_digest[i]);
|
|
printf("\n");
|
|
printf("decode hash: ");
|
|
for (i = 0; i < SHA_DIGEST_LENGTH; i++)
|
|
printf("%02x", decode_digest[i]);
|
|
printf("\n");
|
|
return failures;
|
|
}
|