[Patch/Hack] [MacGUI] VBR audio

Archive of historical development discussions
Discussions / Development has moved to GitHub
Forum rules
*******************************
Please be aware we are now using GitHub for issue tracking and feature requests.
- This section of the forum is now closed to new topics.

*******************************
Post Reply
Deleted User 11865

[Patch/Hack] [MacGUI] VBR audio

Post by Deleted User 11865 »

The CLI nightly builds let you use quality-based VBR audio with capable encoders (Core Audio AAC, Lame MP3, Vorbis), but this functionality isn't exposed in the MacGUI yet.

The following patch adds VBR "bitrates" to the bitrate drop-down for capable encoders, allowing you to use VBR audio from the MacGUI.

VBR settings are saved in presets, and should be compatible with LinGUI presets' VBR audio settings (untested), but not with presets created using previous versions of my patch (anything before 02/08/2012).

For VBR-capable encoders, the default VBR quality is used instead of the default bitrate where applicable.

http://paste.handbrake.fr/pastebin.php?show=3486

Code: Select all

Index: macosx/HBAudio.h
===================================================================
--- macosx/HBAudio.h	(revision 4979)
+++ macosx/HBAudio.h	(working copy)
@@ -17,6 +17,8 @@
 extern NSString *keyAudioMixdown;
 extern NSString *keyAudioSamplerate;
 extern NSString *keyAudioBitrate;
+extern NSString *keyAudioQuality;
+extern NSString *keyAudioCompatible;
 
 @interface HBAudio : NSObject
 
Index: macosx/HBAudio.m
===================================================================
--- macosx/HBAudio.m	(revision 4979)
+++ macosx/HBAudio.m	(working copy)
@@ -21,6 +21,8 @@
 NSString *keyAudioMixdown = @"mixdown";
 NSString *keyAudioSamplerate = @"samplerate";
 NSString *keyAudioBitrate = @"bitrate";
+NSString *keyAudioQuality = @"quality";
+NSString *keyAudioCompatible = @"compatible";
 
 static NSMutableArray *masterCodecArray = nil;
 static NSMutableArray *masterMixdownArray = nil;
@@ -142,9 +144,51 @@
             dict = [NSDictionary dictionaryWithObjectsAndKeys:
                     [NSString stringWithUTF8String: hb_audio_bitrates[i].string], keyAudioBitrateName,
                     [NSNumber numberWithInt: hb_audio_bitrates[i].rate], keyAudioBitrate,
+                    [NSNumber numberWithFloat: HB_INVALID_AUDIO_QUALITY], keyAudioQuality,
+                    [NSNumber numberWithInt: HB_ACODEC_MASK], keyAudioCompatible,
                     nil];
             [masterBitRateArray addObject: dict];
         }
+        // add VBR "bitrates" for all capable encoders
+        // each "bitrate" is encoder-specific (keyAudioCompatible)
+        for (i = 0; i < hb_audio_encoders_count; i++)
+        {
+            if (hb_get_default_audio_quality(hb_audio_encoders[i].encoder) != HB_INVALID_AUDIO_QUALITY)
+            {
+                int direction;
+                float j, low, high, granularity;
+                hb_get_audio_quality_limits(hb_audio_encoders[i].encoder,
+                                            &low, &high, &granularity, &direction);
+                if (direction)
+                {
+                    for (j = high; j >= low; j -= granularity)
+                    {
+                        dict = [NSDictionary dictionaryWithObjectsAndKeys:
+                                [HBAudioController bitrateNameFromQuality: j
+                                                                 forCodec: hb_audio_encoders[i].encoder], keyAudioBitrateName,
+                                [NSNumber numberWithInt: -1], keyAudioBitrate,
+                                [NSNumber numberWithFloat: j], keyAudioQuality,
+                                [NSNumber numberWithInt: hb_audio_encoders[i].encoder], keyAudioCompatible,
+                                nil];
+                        [masterBitRateArray addObject: dict];
+                    }
+                }
+                else
+                {
+                    for (j = low; j <= high; j += granularity)
+                    {
+                        dict = [NSDictionary dictionaryWithObjectsAndKeys:
+                                [HBAudioController bitrateNameFromQuality: j
+                                                                 forCodec: hb_audio_encoders[i].encoder], keyAudioBitrateName,
+                                [NSNumber numberWithInt: -1], keyAudioBitrate,
+                                [NSNumber numberWithFloat: j], keyAudioQuality,
+                                [NSNumber numberWithInt: hb_audio_encoders[i].encoder], keyAudioCompatible,
+                                nil];
+                        [masterBitRateArray addObject: dict];
+                    }
+                }
+            }
+        }
 
         [pool release];
     }
@@ -271,6 +315,7 @@
     int minBitRate;
     int maxBitRate;
     int currentBitRate;
+    int currentCompatibility;
     BOOL shouldAdd;
 
     unsigned int count = [masterBitRateArray count];
@@ -286,6 +331,7 @@
     int ourMixdown = [[[self mixdown] objectForKey: keyAudioMixdown] intValue];
     hb_get_audio_bitrate_limits(ourCodec, theSampleRate, ourMixdown, &minBitRate, &maxBitRate);
     int theDefaultBitRate = hb_get_default_audio_bitrate(ourCodec, theSampleRate, ourMixdown);
+    float theDefaultQuality = hb_get_default_audio_quality(ourCodec);
 
     BOOL codecIsPassthru = ([[codec objectForKey: keyAudioCodec] intValue] & HB_ACODEC_PASS_FLAG) ? YES : NO;
     BOOL codecIsLossless = (theDefaultBitRate == -1) ? YES : NO;
@@ -300,6 +346,8 @@
             sourceBitRate = [NSDictionary dictionaryWithObjectsAndKeys:
                              [NSString stringWithFormat: @"%d", trackInputBitRate], keyAudioBitrateName,
                              [NSNumber numberWithInt: trackInputBitRate], keyAudioBitrate,
+                             [NSNumber numberWithFloat: HB_INVALID_AUDIO_QUALITY], keyAudioQuality,
+                             [NSNumber numberWithInt: ourCodec], keyAudioCompatible,
                              nil];
         }
         [permittedBitRates addObject: sourceBitRate];
@@ -309,6 +357,8 @@
         NSDictionary *bitRateNotApplicable = [NSDictionary dictionaryWithObjectsAndKeys:
                                               [NSString stringWithString: @"N/A"], keyAudioBitrateName,
                                               [NSNumber numberWithInt: -1], keyAudioBitrate,
+                                              [NSNumber numberWithFloat: HB_INVALID_AUDIO_QUALITY], keyAudioQuality,
+                                              [NSNumber numberWithInt: ourCodec], keyAudioCompatible,
                                               nil];
         [permittedBitRates addObject: bitRateNotApplicable];
     }
@@ -318,9 +368,11 @@
         {
             dict = [masterBitRateArray objectAtIndex: i];
             currentBitRate = [[dict objectForKey: keyAudioBitrate] intValue];
+            currentCompatibility = !!(ourCodec & [[dict objectForKey: keyAudioCompatible] intValue]);            
 
             // First ensure the bitrate falls within range of the codec
-            shouldAdd = (currentBitRate >= minBitRate && currentBitRate <= maxBitRate);
+            shouldAdd = ((currentBitRate >= minBitRate && currentBitRate <= maxBitRate) || 
+                         (currentBitRate == -1 && currentCompatibility)); // VBR "bitrate" compatible with ourCodec
 
             if (shouldAdd)
             {
@@ -338,9 +390,18 @@
     [self setBitRates: permittedBitRates];
 
     // Select the proper one
-    if (shouldSetDefault)
+    if (shouldSetDefault || ![permittedBitRates containsObject: [self bitRate]])
     {
-        [self setBitRateFromName: [NSString stringWithFormat:@"%d", theDefaultBitRate]];
+        // set default quality for VBR-capable codecs
+        if (theDefaultQuality != HB_INVALID_AUDIO_QUALITY)
+        {
+            [self setBitRateFromName: [HBAudioController bitrateNameFromQuality: theDefaultQuality
+                                                                       forCodec: ourCodec]];
+        }
+        else
+        {
+            [self setBitRateFromName: [NSString stringWithFormat: @"%d", theDefaultBitRate]];
+        }
     }
 
     if (![self bitRate] || ![permittedBitRates containsObject: [self bitRate]])
Index: macosx/HBAudioController.h
===================================================================
--- macosx/HBAudioController.h	(revision 4979)
+++ macosx/HBAudioController.h	(working copy)
@@ -44,6 +44,11 @@
 - (void) settingTrackToNone: (HBAudio *) newNoneTrack;
 - (void) switchingTrackFromNone: (HBAudio *) noLongerNoneTrack;
 
+/* VBR audio convenience methods */
++ (NSString *) bitrateNameFromQuality: (float) aQuality forCodec: (int) aCodec;
++ (float) closestQualityFromValue: (float) aQuality forCodec: (int) aCodec;
++ (int) codecFromName: (NSString *) aCodecName;
+
 @end
 
 @interface HBAudioController (KVC)
Index: macosx/HBQueueController.mm
===================================================================
--- macosx/HBQueueController.mm	(revision 4979)
+++ macosx/HBQueueController.mm	(working copy)
@@ -1074,22 +1074,26 @@
 		NSString *base;
 		NSString *detailString;
 		NSNumber *drc;
-        NSNumber *gain;
-        BOOL autoPassthruPresent = NO;
+		NSNumber *gain;
+		NSNumber *quality;
+		BOOL autoPassthruPresent = NO;
 		for (unsigned int i = 1; i <= ourMaximumNumberOfAudioTracks; i++) {
 			base = [NSString stringWithFormat: @"Audio%d", i];
 			if (0 < [[item objectForKey: [base stringByAppendingString: @"Track"]] intValue])
             {
 				audioCodecSummary = [NSString stringWithFormat: @"%@", [item objectForKey: [base stringByAppendingString: @"Encoder"]]];
 				drc = [item objectForKey: [base stringByAppendingString: @"TrackDRCSlider"]];
-                gain = [item objectForKey: [base stringByAppendingString: @"TrackGainSlider"]];
-				detailString = [NSString stringWithFormat: @"%@ Encoder: %@ Mixdown: %@ SampleRate: %@(khz) Bitrate: %@(kbps), DRC: %@, Gain: %@",
+				gain = [item objectForKey: [base stringByAppendingString: @"TrackGainSlider"]];
+				quality = [item objectForKey: [NSString stringWithFormat: @"JobAudio%dQuality", i]];
+				detailString = [NSString stringWithFormat: @"%@ Encoder: %@ Mixdown: %@ Samplerate: %@ (kHz) %@: %@ %@, DRC: %@, Gain: %@",
 								[item objectForKey: [base stringByAppendingString: @"TrackDescription"]],
 								[item objectForKey: [base stringByAppendingString: @"Encoder"]],
 								[item objectForKey: [base stringByAppendingString: @"Mixdown"]],
 								[item objectForKey: [base stringByAppendingString: @"Samplerate"]],
-								[item objectForKey: [base stringByAppendingString: @"Bitrate"]],
-                                (0.0 < [drc floatValue]) ? (NSObject *)drc : (NSObject *)@"Off",
+								(HB_INVALID_AUDIO_QUALITY != [quality floatValue]) ? (NSObject *)@"Quality" : (NSObject *)@"Bitrate",
+								(HB_INVALID_AUDIO_QUALITY != [quality floatValue]) ? (NSObject *)quality : [item objectForKey: [base stringByAppendingString: @"Bitrate"]],
+								(HB_INVALID_AUDIO_QUALITY != [quality floatValue]) ? (NSObject *)@"(VBR)" : (NSObject *)@"(Kbps)",
+								(0.0 < [drc floatValue]) ? (NSObject *)drc : (NSObject *)@"Off",
 								(0.0 != [gain floatValue]) ? (NSObject *)gain : (NSObject *)@"Off"
 								]
                                 ;
Index: macosx/HBAudioController.m
===================================================================
--- macosx/HBAudioController.m	(revision 4979)
+++ macosx/HBAudioController.m	(working copy)
@@ -128,6 +128,7 @@
             [aDict setObject: [[anAudio mixdown] objectForKey: keyAudioMixdown] forKey: [prefix stringByAppendingString: @"Mixdown"]];
             [aDict setObject: sampleRateToUse forKey: [prefix stringByAppendingString: @"Samplerate"]];
             [aDict setObject: [[anAudio bitRate] objectForKey: keyAudioBitrate] forKey: [prefix stringByAppendingString: @"Bitrate"]];
+            [aDict setObject: [[anAudio bitRate] objectForKey: keyAudioQuality] forKey: [prefix stringByAppendingString: @"Quality"]];
         }
     }
 }
@@ -166,6 +167,7 @@
             audio->out.compression_level = hb_get_default_audio_compression(audio->out.codec);
             audio->out.mixdown = [[[anAudio mixdown] objectForKey: keyAudioMixdown] intValue];
             audio->out.bitrate = [[[anAudio bitRate] objectForKey: keyAudioBitrate] intValue];
+            audio->out.quality = [[[anAudio bitRate] objectForKey: keyAudioQuality] floatValue];
             audio->out.samplerate = [sampleRateToUse intValue];
             audio->out.dynamic_range_compression = [[anAudio drc] floatValue];
             audio->out.gain = [[anAudio gain] floatValue];
@@ -193,7 +195,13 @@
             [dict setObject: [[anAudio codec] objectForKey: keyAudioCodecName] forKey: @"AudioEncoder"];
             [dict setObject: [[anAudio mixdown] objectForKey: keyAudioMixdownName] forKey: @"AudioMixdown"];
             [dict setObject: [[anAudio sampleRate] objectForKey: keyAudioSampleRateName] forKey: @"AudioSamplerate"];
-            [dict setObject: [[anAudio bitRate] objectForKey: keyAudioBitrateName] forKey: @"AudioBitrate"];
+            [dict setObject:
+             [NSString stringWithFormat: @"%d", [[[anAudio bitRate] objectForKey: keyAudioBitrate] intValue]]
+                     forKey: @"AudioBitrate"];
+            [dict setObject: [[anAudio bitRate] objectForKey: keyAudioQuality] forKey: @"AudioTrackQuality"];
+            [dict setObject:
+             [NSNumber numberWithInt: ([[[anAudio bitRate] objectForKey: keyAudioQuality] floatValue] != HB_INVALID_AUDIO_QUALITY)]
+                     forKey: @"AudioTrackQualityEnable"];
             [dict setObject: [anAudio drc] forKey: @"AudioTrackDRCSlider"];
             [dict setObject: [anAudio gain] forKey: @"AudioTrackGainSlider"];
             [anArray addObject: dict];
@@ -344,6 +352,16 @@
             {
                 [dict setObject:[NSNumber numberWithFloat:0.0] forKey:@"AudioTrackGainSlider"];
             }
+            
+            // set default values for VBR audio if absent
+            if (![dict objectForKey: @"AudioTrackQualityEnable"])
+            {
+                [dict setObject:[NSNumber numberWithInt:0] forKey:@"AudioTrackQualityEnable"];
+            }
+            if (![dict objectForKey: @"AudioTrackQuality"])
+            {
+                [dict setObject:[NSNumber numberWithFloat:HB_INVALID_AUDIO_QUALITY] forKey:@"AudioTrackQuality"];
+            }
 
             // map legacy mixdowns
             key = [dict objectForKey: @"AudioMixdown"];
@@ -366,7 +384,19 @@
                 [newAudio setSampleRateFromName: [dict objectForKey: @"AudioSamplerate"]];
                 if (!fallenBack)
                 {
-                    [newAudio setBitRateFromName: [dict objectForKey: @"AudioBitrate"]];
+                    // VBR audio
+                    if ([[dict objectForKey: @"AudioTrackQualityEnable"] intValue])
+                    {
+                        [newAudio setBitRateFromName:
+                         [HBAudioController bitrateNameFromQuality:
+                          [[dict objectForKey: @"AudioTrackQuality"] floatValue]
+                                                          forCodec:
+                          [HBAudioController codecFromName: [dict objectForKey: @"AudioEncoder"]]]];
+                    }
+                    else
+                    {
+                        [newAudio setBitRateFromName: [dict objectForKey: @"AudioBitrate"]];
+                    }
                 }
                 [newAudio setDrc: [dict objectForKey: @"AudioTrackDRCSlider"]];
                 [newAudio setGain: [dict objectForKey: @"AudioTrackGainSlider"]];
@@ -645,6 +675,67 @@
 }
 
 #pragma mark -
+#pragma mark VBR audio convenience methods
+
++ (NSString *) bitrateNameFromQuality: (float) aQuality forCodec: (int) aCodec
+
+{
+    if (aQuality == HB_INVALID_AUDIO_QUALITY || !aCodec)
+        return nil;
+    int direction, precision;
+    float low, high, granularity, quality;
+    hb_get_audio_quality_limits(aCodec, &low, &high, &granularity, &direction);
+    precision = granularity >= 1.0 ? 0 : granularity >= 0.1 ? 1 : 2;
+    quality = [HBAudioController closestQualityFromValue: aQuality forCodec: aCodec];
+    return [NSString stringWithFormat: @"Q%.*f", precision, quality];
+}
+
++ (float) closestQualityFromValue: (float) aQuality forCodec: (int) aCodec
+
+{
+    int direction;
+    float i, low, high, granularity;
+    hb_get_audio_quality_limits(aCodec, &low, &high, &granularity, &direction);
+    // return the highest quality that is less than granularity away from aQuality
+    if (direction)
+    {
+        for (i = low; i <= high; i += granularity)
+        {
+            if ((aQuality - i) < granularity)
+            {
+                return hb_get_best_audio_quality(aCodec, i);
+            }
+        }
+    }
+    else
+    {
+        for (i = high; i >= low; i -= granularity)
+        {
+            if ((i - aQuality) < granularity)
+            {
+                return hb_get_best_audio_quality(aCodec, i);
+            }
+        }
+    }
+    return hb_get_default_audio_quality(aCodec);
+}
+
++ (int) codecFromName: (NSString *) aCodecName
+
+{
+    int i;
+    for (i = 0; i < hb_audio_encoders_count; i++)
+    {
+        if ([[NSString stringWithUTF8String: hb_audio_encoders[i].human_readable_name]
+                            isEqualToString: aCodecName])
+        {
+            return hb_audio_encoders[i].encoder;
+        }
+    }
+    return 0;
+}
+
+#pragma mark -
 #pragma mark KVC
 
 - (unsigned int) countOfAudioArray
Index: macosx/Controller.m
===================================================================
--- macosx/Controller.m	(revision 4979)
+++ macosx/Controller.m	(working copy)
@@ -3995,6 +3995,7 @@
 			audio->out.compression_level = hb_get_default_audio_compression(audio->out.codec);
 			audio->out.mixdown = [[queueToApply objectForKey: [prefix stringByAppendingString: @"Mixdown"]] intValue];
 			audio->out.bitrate = [[queueToApply objectForKey: [prefix stringByAppendingString: @"Bitrate"]] intValue];
+			audio->out.quality = [[queueToApply objectForKey: [prefix stringByAppendingString: @"Quality"]] floatValue];
 			audio->out.samplerate = [[queueToApply objectForKey: [prefix stringByAppendingString: @"Samplerate"]] intValue];
 			
 			hb_audio_add( job, audio );
This patch is only for people who build their own HandBrake and want to take advantage of VBR audio; official MacGUI VBR support will not look like this patch.

CLI users should use native VBR functionality (see --help).
User avatar
Ritsuka
HandBrake Team
Posts: 1657
Joined: Fri Jan 12, 2007 11:29 am

Re: [Patch/Hack] [Mac OS X] Use VBR with Core Audio AAC

Post by Ritsuka »

A recent audio listening test showed that core audio true vbr is not inevitably better: http://www.hydrogenaudio.org/forums/ind ... opic=90403
Deleted User 11865

Re: [Patch/Hack] [Mac OS X] Use VBR with Core Audio AAC

Post by Deleted User 11865 »

Ritsuka wrote:A recent audio listening test showed that core audio true vbr is not inevitably better: http://www.hydrogenaudio.org/forums/ind ... opic=90403
While it's certainly interesting, it's somewhat irrelevant (considering how close the results are). At the end of the day, TVBR still targets a quality while CVBR still targets a bitrate. It's just a fancy/improved variant of ABR.

If x264 2-pass ABR had a 5% compression efficiency edge over CRF, it wouldn't suddenly become the better choice.
Deleted User 11865

Re: [Patch/Hack] [MacGUI] VBR audio

Post by Deleted User 11865 »

Patch updated to work with all VBR-capable encoders.
Deleted User 11865

Re: [Patch/Hack] [MacGUI] VBR audio

Post by Deleted User 11865 »

Patch updated to apply to latest SVN (no changes).
Post Reply