diff options
author | Rodeo <[email protected]> | 2013-02-05 17:53:03 +0000 |
---|---|---|
committer | Rodeo <[email protected]> | 2013-02-05 17:53:03 +0000 |
commit | 5ca5ec3e6f4ebb47e92984a4ef859510df56b537 (patch) | |
tree | 22dbb88491ce5e6d7b8c844ebdb79581993f2e52 | |
parent | 4017dfc2050336c4476f89fd4c4e358449b9376e (diff) |
Audio dithering.
Works with encoders that accept 16-bit signed integers as input (currently, only ffflac).
When supported, the default method is standard triangular.
CLI users can request a specific dither algorithm via the --adither option.
git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@5241 b64f7644-9d1e-0410-96f1-a4d463321fa5
-rw-r--r-- | libhb/common.c | 57 | ||||
-rw-r--r-- | libhb/common.h | 18 | ||||
-rw-r--r-- | libhb/encavcodecaudio.c | 10 | ||||
-rw-r--r-- | libhb/work.c | 24 | ||||
-rw-r--r-- | macosx/HBAudioController.m | 2 | ||||
-rw-r--r-- | test/test.c | 106 |
6 files changed, 217 insertions, 0 deletions
diff --git a/libhb/common.c b/libhb/common.c index 355b458e5..cfbeb6185 100644 --- a/libhb/common.c +++ b/libhb/common.c @@ -83,6 +83,17 @@ int hb_audio_bitrates_count = sizeof(hb_audio_bitrates) / sizeof(hb_rate_t); static hb_error_handler_t *error_handler = NULL; +hb_dither_t hb_audio_dithers[] = +{ + { "default", "auto", AV_RESAMPLE_DITHER_NONE - 1, }, + { "none", "none", AV_RESAMPLE_DITHER_NONE, }, + { "rectangular", "rectangular", AV_RESAMPLE_DITHER_RECTANGULAR, }, + { "triangular", "triangular", AV_RESAMPLE_DITHER_TRIANGULAR, }, + { "triangular with high pass", "triangular_hp", AV_RESAMPLE_DITHER_TRIANGULAR_HP, }, + { "triangular with noise shaping", "triangular_ns", AV_RESAMPLE_DITHER_TRIANGULAR_NS, }, +}; +int hb_audio_dithers_count = sizeof(hb_audio_dithers) / sizeof(hb_dither_t); + hb_mixdown_t hb_audio_mixdowns[] = { { "None", "HB_AMIXDOWN_NONE", "none", HB_AMIXDOWN_NONE }, @@ -138,6 +149,8 @@ hb_rate_t* hb_get_audio_rates() { return hb_audio_rates; } int hb_get_audio_rates_count() { return hb_audio_rates_count; } hb_rate_t* hb_get_audio_bitrates() { return hb_audio_bitrates; } int hb_get_audio_bitrates_count() { return hb_audio_bitrates_count; } +hb_dither_t* hb_get_audio_dithers() { return hb_audio_dithers; } +int hb_get_audio_dithers_count() { return hb_audio_dithers_count; } hb_mixdown_t* hb_get_audio_mixdowns() { return hb_audio_mixdowns; } int hb_get_audio_mixdowns_count() { return hb_audio_mixdowns_count; } hb_encoder_t* hb_get_video_encoders() { return hb_video_encoders; } @@ -145,6 +158,47 @@ int hb_get_video_encoders_count() { return hb_video_encoders_count; } hb_encoder_t* hb_get_audio_encoders() { return hb_audio_encoders; } int hb_get_audio_encoders_count() { return hb_audio_encoders_count; } +int hb_audio_dither_get_default() +{ + // "auto" + return hb_audio_dithers[0].method; +} + +int hb_audio_dither_get_default_method() +{ + /* + * input could be s16 (possibly already dithered) converted to flt, so + * let's use a "low-risk" dither algorithm (standard triangular). + */ + return AV_RESAMPLE_DITHER_TRIANGULAR; +} + +int hb_audio_dither_is_supported(uint32_t codec) +{ + // encoder's input sample format must be s16(p) + switch (codec) + { + case HB_ACODEC_FFFLAC: + return 1; + default: + return 0; + } +} + +const char* hb_audio_dither_get_description(int method) +{ + int i; + for (i = 0; i < hb_audio_dithers_count; i++) + { + if (hb_audio_dithers[i].method == method) + { + return hb_audio_dithers[i].description; + } + } + return ""; +} + + int hb_mixdown_is_supported(int mixdown, uint32_t codec, uint64_t layout) { return (hb_mixdown_has_codec_support(mixdown, codec) && @@ -2240,6 +2294,7 @@ void hb_audio_config_init(hb_audio_config_t * audiocfg) audiocfg->out.dynamic_range_compression = 0; audiocfg->out.gain = 0; audiocfg->out.normalize_mix_level = 0; + audiocfg->out.dither_method = hb_audio_dither_get_default(); audiocfg->out.name = NULL; } @@ -2290,6 +2345,7 @@ int hb_audio_add(const hb_job_t * job, const hb_audio_config_t * audiocfg) audio->config.out.normalize_mix_level = 0; audio->config.out.compression_level = -1; audio->config.out.quality = HB_INVALID_AUDIO_QUALITY; + audio->config.out.dither_method = AV_RESAMPLE_DITHER_NONE; } else { @@ -2303,6 +2359,7 @@ int hb_audio_add(const hb_job_t * job, const hb_audio_config_t * audiocfg) audio->config.out.mixdown = audiocfg->out.mixdown; audio->config.out.gain = audiocfg->out.gain; audio->config.out.normalize_mix_level = audiocfg->out.normalize_mix_level; + audio->config.out.dither_method = audiocfg->out.dither_method; } if (audiocfg->out.name && *audiocfg->out.name) { diff --git a/libhb/common.h b/libhb/common.h index ac7d46287..aace4b688 100644 --- a/libhb/common.h +++ b/libhb/common.h @@ -70,6 +70,7 @@ typedef struct hb_handle_s hb_handle_t; typedef struct hb_list_s hb_list_t; typedef struct hb_rate_s hb_rate_t; +typedef struct hb_dither_s hb_dither_t; typedef struct hb_mixdown_s hb_mixdown_t; typedef struct hb_encoder_s hb_encoder_t; typedef struct hb_job_s hb_job_t; @@ -174,6 +175,13 @@ struct hb_rate_s int rate; }; +struct hb_dither_s +{ + const char *description; + const char *short_name; + int method; +}; + struct hb_mixdown_s { const char *human_readable_name; @@ -211,6 +219,8 @@ extern int hb_audio_rates_count; extern int hb_audio_rates_default; extern hb_rate_t hb_audio_bitrates[]; extern int hb_audio_bitrates_count; +extern hb_dither_t hb_audio_dithers[]; +extern int hb_audio_dithers_count; extern hb_mixdown_t hb_audio_mixdowns[]; extern int hb_audio_mixdowns_count; extern hb_encoder_t hb_video_encoders[]; @@ -226,6 +236,8 @@ int hb_get_audio_rates_count(); int hb_get_audio_rates_default(); hb_rate_t* hb_get_audio_bitrates(); int hb_get_audio_bitrates_count(); +hb_dither_t* hb_get_audio_dithers(); +int hb_get_audio_dithers_count(); hb_mixdown_t* hb_get_audio_mixdowns(); int hb_get_audio_mixdowns_count(); hb_encoder_t* hb_get_video_encoders(); @@ -233,6 +245,11 @@ int hb_get_video_encoders_count(); hb_encoder_t* hb_get_audio_encoders(); int hb_get_audio_encoders_count(); +int hb_audio_dither_get_default(); +int hb_audio_dither_get_default_method(); +int hb_audio_dither_is_supported(uint32_t codec); +const char* hb_audio_dither_get_description(int method); + int hb_mixdown_is_supported(int mixdown, uint32_t codec, uint64_t layout); int hb_mixdown_has_codec_support(int mixdown, uint32_t codec); int hb_mixdown_has_remix_support(int mixdown, uint64_t layout); @@ -519,6 +536,7 @@ struct hb_audio_config_s double dynamic_range_compression; /* Amount of DRC applied to this track */ double gain; /* Gain (in dB), negative is quieter */ int normalize_mix_level; /* mix level normalization (boolean) */ + int dither_method; /* dither algorithm */ char * name; /* Output track name */ } out; diff --git a/libhb/encavcodecaudio.c b/libhb/encavcodecaudio.c index 669691dc1..ecd75ff98 100644 --- a/libhb/encavcodecaudio.c +++ b/libhb/encavcodecaudio.c @@ -148,6 +148,16 @@ static int encavcodecaInit(hb_work_object_t *w, hb_job_t *job) context->channel_layout, 0); av_opt_set_int(pv->avresample, "out_channel_layout", context->channel_layout, 0); + if (hb_audio_dither_is_supported(audio->config.out.codec)) + { + // dithering needs the sample rate + av_opt_set_int(pv->avresample, "in_sample_rate", + context->sample_rate, 0); + av_opt_set_int(pv->avresample, "out_sample_rate", + context->sample_rate, 0); + av_opt_set_int(pv->avresample, "dither_method", + audio->config.out.dither_method, 0); + } if (avresample_open(pv->avresample)) { hb_error("encavcodecaInit: avresample_open() failed"); diff --git a/libhb/work.c b/libhb/work.c index 9ee2b0593..575bebdbd 100644 --- a/libhb/work.c +++ b/libhb/work.c @@ -486,6 +486,11 @@ void hb_display_job_info( hb_job_t * job ) { hb_log( " + dynamic range compression: %f", audio->config.out.dynamic_range_compression ); } + if (hb_audio_dither_is_supported(audio->config.out.codec)) + { + hb_log(" + dither: %s", + hb_audio_dither_get_description(audio->config.out.dither_method)); + } for( j = 0; j < hb_audio_encoders_count; j++ ) { if( hb_audio_encoders[j].encoder == audio->config.out.codec ) @@ -992,6 +997,25 @@ static void do_job( hb_job_t * job ) audio->config.out.bitrate = best_bitrate; } } + + /* sense-check the requested dither */ + if (hb_audio_dither_is_supported(audio->config.out.codec)) + { + if (audio->config.out.dither_method == + hb_audio_dither_get_default()) + { + /* "auto", enable with default settings */ + audio->config.out.dither_method = + hb_audio_dither_get_default_method(); + } + } + else if (audio->config.out.dither_method != + hb_audio_dither_get_default()) + { + /* specific dither requested but dithering not supported */ + hb_log("work: track %d, dithering not supported by codec", + audio->config.out.track); + } } } diff --git a/macosx/HBAudioController.m b/macosx/HBAudioController.m index b9b80bd53..5e4a32508 100644 --- a/macosx/HBAudioController.m +++ b/macosx/HBAudioController.m @@ -165,10 +165,12 @@ NSString *HBMixdownChangedNotification = @"HBMixdownChangedNotification"; audio->out.codec = [[[anAudio codec] objectForKey: keyAudioCodec] intValue]; audio->out.compression_level = hb_get_default_audio_compression(audio->out.codec); audio->out.mixdown = [[[anAudio mixdown] objectForKey: keyAudioMixdown] intValue]; + audio->out.normalize_mix_level = 0; audio->out.bitrate = [[[anAudio bitRate] objectForKey: keyAudioBitrate] intValue]; audio->out.samplerate = [sampleRateToUse intValue]; audio->out.dynamic_range_compression = [[anAudio drc] floatValue]; audio->out.gain = [[anAudio gain] floatValue]; + audio->out.dither_method = hb_audio_dither_get_default(); hb_audio_add(aJob, audio); free(audio); diff --git a/test/test.c b/test/test.c index 7cd2d727e..b3b3f7662 100644 --- a/test/test.c +++ b/test/test.c @@ -71,6 +71,7 @@ static int allowed_audio_copy = -1; static char * mixdowns = NULL; static char * dynamic_range_compression = NULL; static char * audio_gain = NULL; +static char ** audio_dither = NULL; static char ** normalize_mix_level = NULL; static char * atracks = NULL; static char * arates = NULL; @@ -156,6 +157,7 @@ static int ParseOptions( int argc, char ** argv ); static int CheckOptions( int argc, char ** argv ); static int HandleEvents( hb_handle_t * h ); +static int get_dither_for_string(const char *dither); static int get_acodec_for_string(const char *codec); static const char* get_string_for_acodec(int acodec); @@ -357,6 +359,7 @@ int main( int argc, char ** argv ) str_vfree(abitrates); str_vfree(acompressions); str_vfree(aqualities); + str_vfree(audio_dither); free(acodecs); free(arates); free(atracks); @@ -2089,6 +2092,58 @@ static int HandleEvents( hb_handle_t * h ) } /* Audio Gain */ + /* Audio Dither */ + if (audio_dither != NULL) + { + int dither_method = hb_audio_dither_get_default(); + for (i = 0; audio_dither[i] != NULL; i++) + { + dither_method = get_dither_for_string(audio_dither[i]); + audio = hb_list_audio_config_item(job->list_audio, i); + if (audio != NULL) + { + if (hb_audio_dither_is_supported(audio->out.codec)) + { + audio->out.dither_method = dither_method; + } + else if (dither_method != hb_audio_dither_get_default()) + { + fprintf(stderr, + "Ignoring dither %s, not supported by codec\n", + audio_dither[i]); + } + } + else + { + fprintf(stderr, "Ignoring dither %s, no audio tracks\n", + audio_dither[i]); + } + } + if (i < num_audio_tracks && i == 1) + { + /* + * We have fewer inputs than audio tracks, and we only have + * one input: use that for all tracks. + */ + while (i < num_audio_tracks) + { + audio = hb_list_audio_config_item(job->list_audio, i); + if (hb_audio_dither_is_supported(audio->out.codec)) + { + audio->out.dither_method = dither_method; + } + else if (dither_method != hb_audio_dither_get_default()) + { + fprintf(stderr, + "Ignoring dither %s, not supported by codec\n", + audio_dither[0]); + } + i++; + } + } + } + /* Audio Dither */ + /* Audio Mix Normalization */ i = 0; int norm = 0; @@ -2938,6 +2993,36 @@ static void ShowHelp() " NOT work with audio passthru (copy). Values are in\n" " dB. Negative values attenuate, positive values\n" " amplify. A 1 dB difference is barely audible.\n" + " --adither <string> Apply dithering to the audio before encoding.\n" + " Separated by commas for more than one audio track.\n" + " Only supported by some encoders ("); + for (i = j = 0; i < hb_audio_encoders_count; i++) + { + if (hb_audio_dither_is_supported(hb_audio_encoders[i].encoder)) + { + if (j) + fprintf(out, "/"); + fprintf(out, "%s", hb_audio_encoders[i].short_name); + j = 1; + } + } + fprintf(out, ").\n"); + fprintf(out, + " Options:\n"); + for (i = 0; i < hb_audio_dithers_count; i++) + { + if (hb_audio_dithers[i].method == hb_audio_dither_get_default()) + { + fprintf(out, " %s (default)\n", + hb_audio_dithers[i].short_name); + } + else + { + fprintf(out, " %s\n", + hb_audio_dithers[i].short_name); + } + } + fprintf(out, " -A, --aname <string> Audio track name(s),\n" " Separated by commas for more than one audio track.\n" "\n" @@ -3201,6 +3286,7 @@ static int ParseOptions( int argc, char ** argv ) #define H264_PROFILE 285 #define H264_LEVEL 286 #define NORMALIZE_MIX 287 + #define AUDIO_DITHER 288 for( ;; ) { @@ -3230,6 +3316,7 @@ static int ParseOptions( int argc, char ** argv ) { "normalize-mix", required_argument, NULL, NORMALIZE_MIX }, { "drc", required_argument, NULL, 'D' }, { "gain", required_argument, NULL, AUDIO_GAIN }, + { "adither", required_argument, NULL, AUDIO_DITHER }, { "subtitle", required_argument, NULL, 's' }, { "subtitle-forced", optional_argument, NULL, 'F' }, { "subtitle-burned", optional_argument, NULL, SUB_BURNED }, @@ -3447,6 +3534,12 @@ static int ParseOptions( int argc, char ** argv ) audio_gain = strdup( optarg ); } break; + case AUDIO_DITHER: + if (optarg != NULL) + { + audio_dither = str_split(optarg, ','); + } + break; case NORMALIZE_MIX: if( optarg != NULL ) { @@ -3974,6 +4067,19 @@ static int CheckOptions( int argc, char ** argv ) return 0; } +static int get_dither_for_string(const char *dither) +{ + int i; + for (i = 0; i < hb_audio_dithers_count; i++) + { + if (!strcasecmp(hb_audio_dithers[i].short_name, dither)) + { + return hb_audio_dithers[i].method; + } + } + return hb_audio_dither_get_default(); +} + static int get_acodec_for_string(const char *codec) { int i; |