3 This file is part of the HandBrake source code.
4 Homepage: <http://handbrake.fr/>.
5 It may be used under the terms of the GNU General Public License. */
7 #import "HBAudioTrack.h"
8 #import "HBAudioController.h"
10 #import "HBCodingUtilities.h"
13 NSString *keyAudioTrackIndex = @"keyAudioTrackIndex";
14 NSString *keyAudioTrackName = @"keyAudioTrackName";
15 NSString *keyAudioInputBitrate = @"keyAudioInputBitrate";
16 NSString *keyAudioInputSampleRate = @"keyAudioInputSampleRate";
17 NSString *keyAudioInputCodec = @"keyAudioInputCodec";
18 NSString *keyAudioInputCodecParam = @"keyAudioInputCodecParam";
19 NSString *keyAudioInputChannelLayout = @"keyAudioInputChannelLayout";
20 NSString *keyAudioTrackLanguageIsoCode = @"keyAudioTrackLanguageIsoCode";
22 NSString *keyAudioCodecName = @"keyAudioCodecName";
23 NSString *keyAudioSupportedMuxers = @"keyAudioSupportedMuxers";
24 NSString *keyAudioSampleRateName = @"keyAudioSampleRateName";
25 NSString *keyAudioBitrateName = @"keyAudioBitrateName";
26 NSString *keyAudioMustMatchTrack = @"keyAudioMustMatchTrack";
27 NSString *keyAudioMixdownName = @"keyAudioMixdownName";
29 NSString *keyAudioCodec = @"codec";
30 NSString *keyAudioMixdown = @"mixdown";
31 NSString *keyAudioSamplerate = @"samplerate";
32 NSString *keyAudioBitrate = @"bitrate";
34 static NSMutableArray *masterCodecArray = nil;
35 static NSMutableArray *masterMixdownArray = nil;
36 static NSMutableArray *masterBitRateArray = nil;
38 @interface NSArray (HBAudioSupport)
39 - (NSDictionary *) dictionaryWithObject: (id) anObject matchingKey: (NSString *) aKey;
40 - (NSDictionary *) lastDictionaryWithObject: (id) anObject matchingKey: (NSString *) aKey;
42 @implementation NSArray (HBAudioSupport)
43 - (NSDictionary *) dictionaryWithObject: (id) anObject matchingKey: (NSString *) aKey reverse: (BOOL) reverse
46 NSDictionary *retval = nil;
47 NSEnumerator *enumerator = reverse ? [self reverseObjectEnumerator] : [self objectEnumerator];
51 while (nil != (dict = [enumerator nextObject]) && !retval)
53 if (nil != (aValue = dict[aKey]) && [aValue isEqual: anObject])
60 - (NSDictionary *) dictionaryWithObject: (id) anObject matchingKey: (NSString *) aKey
62 return [self dictionaryWithObject: anObject matchingKey: aKey reverse: NO];
64 - (NSDictionary *) lastDictionaryWithObject: (id) anObject matchingKey: (NSString *) aKey
66 return [self dictionaryWithObject: anObject matchingKey: aKey reverse: YES];
71 @implementation HBAudioTrack
74 #pragma mark Object Setup
79 if ([HBAudioTrack class] == self)
81 masterCodecArray = [[NSMutableArray alloc] init]; // knowingly leaked
82 for (const hb_encoder_t *audio_encoder = hb_audio_encoder_get_next(NULL);
83 audio_encoder != NULL;
84 audio_encoder = hb_audio_encoder_get_next(audio_encoder))
86 id audioMustMatchTrack;
87 if ((audio_encoder->codec & HB_ACODEC_PASS_FLAG) &&
88 (audio_encoder->codec != HB_ACODEC_AUTO_PASS))
90 audioMustMatchTrack = @(audio_encoder->codec &
91 ~HB_ACODEC_PASS_FLAG);
95 audioMustMatchTrack = @NO;
97 [masterCodecArray addObject:@{keyAudioCodecName: @(audio_encoder->name),
98 keyAudioCodec: @(audio_encoder->codec),
99 keyAudioSupportedMuxers: @(audio_encoder->muxers),
100 keyAudioMustMatchTrack: audioMustMatchTrack}];
103 masterMixdownArray = [[NSMutableArray alloc] init]; // knowingly leaked
104 for (const hb_mixdown_t *mixdown = hb_mixdown_get_next(NULL);
106 mixdown = hb_mixdown_get_next(mixdown))
108 [masterMixdownArray addObject:@{keyAudioMixdownName: @(mixdown->name),
109 keyAudioMixdown: @(mixdown->amixdown)}];
112 masterBitRateArray = [[NSMutableArray alloc] init]; // knowingly leaked
113 for (const hb_rate_t *audio_bitrate = hb_audio_bitrate_get_next(NULL);
114 audio_bitrate != NULL;
115 audio_bitrate = hb_audio_bitrate_get_next(audio_bitrate))
117 [masterBitRateArray addObject:@{keyAudioBitrateName: @(audio_bitrate->name),
118 keyAudioBitrate: @(audio_bitrate->rate)}];
123 // Ensure the list of codecs is accurate
124 // Update the current value of codec based on the revised list
125 - (void) updateCodecs
128 NSMutableArray *permittedCodecs = [NSMutableArray array];
129 NSUInteger count = [masterCodecArray count];
132 // First get a list of the permitted codecs based on the internal rules
133 if (nil != self.track && self.enabled)
137 for (unsigned int i = 0; i < count; i++)
139 dict = masterCodecArray[i];
141 // First make sure only codecs permitted by the container are here
142 goodToAdd = !!([dict[keyAudioSupportedMuxers] intValue] &
143 [self.videoContainerTag intValue]);
145 // Now we make sure if DTS or AC3 is not available in the track it is not put in the codec list, but in a general way
146 if ([dict[keyAudioMustMatchTrack] boolValue])
148 if ([dict[keyAudioMustMatchTrack] intValue] != [self.track[keyAudioInputCodec] intValue])
156 [permittedCodecs addObject: dict];
161 // Now make sure the permitted list and the actual ones matches
162 [self setCodecs: permittedCodecs];
164 // Ensure our codec is on the list of permitted codecs
165 if (!self.codec || ![permittedCodecs containsObject: self.codec])
167 if (0 < [permittedCodecs count])
169 self.codec = permittedCodecs[0]; // This should be defaulting to Core Audio
178 - (void) updateMixdowns: (BOOL) shouldSetDefault
181 NSMutableArray *permittedMixdowns = [NSMutableArray array];
185 unsigned long long channelLayout = [self.track[keyAudioInputChannelLayout] unsignedLongLongValue];
186 NSUInteger count = [masterMixdownArray count];
187 int codecCodec = [self.codec[keyAudioCodec] intValue];
188 int theDefaultMixdown = hb_mixdown_get_default(codecCodec, channelLayout);
190 for (unsigned int i = 0; i < count; i++)
192 dict = masterMixdownArray[i];
193 currentMixdown = [dict[keyAudioMixdown] intValue];
195 if (hb_mixdown_is_supported(currentMixdown, codecCodec, channelLayout))
197 [permittedMixdowns addObject: dict];
203 permittedMixdowns = nil;
206 // Now make sure the permitted list and the actual ones matches
207 self.mixdowns = permittedMixdowns;
209 // Select the proper one
210 if (shouldSetDefault)
212 self.mixdown = [permittedMixdowns dictionaryWithObject: @(theDefaultMixdown)
213 matchingKey: keyAudioMixdown];
216 if (!self.mixdown || ![permittedMixdowns containsObject: self.mixdown])
218 self.mixdown = [permittedMixdowns lastObject];
222 - (void)validateSamplerate
224 int codec = [self.codec[keyAudioCodec] intValue];
225 int samplerate = [self.sampleRate[keyAudioSamplerate] intValue];
227 if (codec & HB_ACODEC_PASS_FLAG)
229 [self setSampleRateFromName:@"Auto"];
233 samplerate = hb_audio_samplerate_get_best(codec, samplerate, NULL);
234 [self setSampleRateFromName:@(hb_audio_samplerate_get_name(samplerate))];
238 - (void) updateBitRates: (BOOL) shouldSetDefault
241 NSMutableArray *permittedBitRates = [NSMutableArray array];
248 NSUInteger count = [masterBitRateArray count];
249 int trackInputBitRate = [self.track[keyAudioInputBitrate] intValue];
250 int theSampleRate = [self.sampleRate[keyAudioSamplerate] intValue];
252 if (0 == theSampleRate) // this means Auto
254 theSampleRate = [self.track[keyAudioInputSampleRate] intValue];
257 int ourCodec = [self.codec[keyAudioCodec] intValue];
258 int ourMixdown = [self.mixdown[keyAudioMixdown] intValue];
259 int theDefaultBitRate = hb_audio_bitrate_get_default(ourCodec, theSampleRate, ourMixdown);
260 hb_audio_bitrate_get_limits(ourCodec, theSampleRate, ourMixdown, &minBitRate, &maxBitRate);
262 BOOL codecIsPassthru = ([self.codec[keyAudioCodec] intValue] & HB_ACODEC_PASS_FLAG) ? YES : NO;
263 BOOL codecIsLossless = (theDefaultBitRate == -1) ? YES : NO;
267 NSDictionary *sourceBitRate = [masterBitRateArray dictionaryWithObject: @(trackInputBitRate)
268 matchingKey: keyAudioBitrate];
271 // the source bitrate isn't in the master array - create it
272 sourceBitRate = @{keyAudioBitrateName: [NSString stringWithFormat: @"%d", trackInputBitRate],
273 keyAudioBitrate: @(trackInputBitRate)};
275 [permittedBitRates addObject: sourceBitRate];
277 else if (codecIsLossless)
279 NSDictionary *bitRateNotApplicable = @{keyAudioBitrateName: @"N/A",
280 keyAudioBitrate: @-1};
281 [permittedBitRates addObject: bitRateNotApplicable];
285 for (unsigned int i = 0; i < count; i++)
287 dict = masterBitRateArray[i];
288 currentBitRate = [dict[keyAudioBitrate] intValue];
290 // First ensure the bitrate falls within range of the codec
291 shouldAdd = (currentBitRate >= minBitRate && currentBitRate <= maxBitRate);
295 [permittedBitRates addObject: dict];
302 permittedBitRates = nil;
305 // Make sure we are updated with the permitted list
306 self.bitRates = permittedBitRates;
308 // Select the proper one
309 if (shouldSetDefault)
311 [self setBitRateFromName: [NSString stringWithFormat:@"%d", theDefaultBitRate]];
314 if (!self.bitRate || ![permittedBitRates containsObject: self.bitRate])
316 self.bitRate = [permittedBitRates lastObject];
321 #pragma mark Accessors
323 - (NSArray *) sampleRates
325 NSMutableArray *samplerates = [[NSMutableArray alloc] init];
328 * Note that for the Auto value we use 0 for the sample rate because our controller will give back the track's
329 * input sample rate when it finds this 0 value as the selected sample rate. We do this because the input
330 * sample rate depends on the track, which means it depends on the title, so cannot be nicely set up here.
332 [samplerates addObject:@{keyAudioSampleRateName: @"Auto",
333 keyAudioSamplerate: @0}];
335 int codec = [self.codec[keyAudioCodec] intValue];
336 for (const hb_rate_t *audio_samplerate = hb_audio_samplerate_get_next(NULL);
337 audio_samplerate != NULL;
338 audio_samplerate = hb_audio_samplerate_get_next(audio_samplerate))
340 int rate = audio_samplerate->rate;
341 if (rate == hb_audio_samplerate_get_best(codec, rate, NULL))
343 [samplerates addObject:@{keyAudioSampleRateName: @(audio_samplerate->name),
344 keyAudioSamplerate: @(rate)}];
353 - (void)setVideoContainerTag:(NSNumber *)videoContainerTag
355 _videoContainerTag = videoContainerTag;
359 - (void)setTrack:(NSDictionary *)track
361 NSDictionary *oldValue = _track;
366 [self updateMixdowns: YES];
369 self.sampleRate = self.sampleRates[0]; // default to Auto
371 if ([self.dataSource.noneTrack isEqual: oldValue])
373 [self.delegate switchingTrackFromNone: self];
375 if ([self.dataSource.noneTrack isEqual: self.track])
377 [self.delegate settingTrackToNone: self];
382 - (void)setCodec:(NSDictionary *)codec
385 [self validateSamplerate];
386 [self updateMixdowns: YES];
387 [self updateBitRates: YES];
390 - (void)setMixdown:(NSDictionary *)mixdown
393 [self updateBitRates: YES];
394 [self.delegate mixdownChanged];
397 - (void)setSampleRate:(NSDictionary *)sampleRate
399 _sampleRate = sampleRate;
400 [self updateBitRates: NO];
404 #pragma mark Special Setters
406 - (void) setTrackFromIndex: (int) aValue
409 self.track = [self.dataSource.masterTrackArray dictionaryWithObject: @(aValue)
410 matchingKey: keyAudioTrackIndex];
413 // This returns whether it is able to set the actual codec desired.
414 - (BOOL) setCodecFromName: (NSString *) aValue
417 NSDictionary *dict = [self.codecs dictionaryWithObject: aValue matchingKey: keyAudioCodecName];
423 return (nil != dict);
426 - (void) setMixdownFromName: (NSString *) aValue
429 NSDictionary *dict = [self.mixdowns dictionaryWithObject: aValue matchingKey: keyAudioMixdownName];
437 - (void) setSampleRateFromName: (NSString *) aValue
440 NSDictionary *dict = [self.sampleRates dictionaryWithObject: aValue matchingKey: keyAudioSampleRateName];
444 self.sampleRate = dict;
448 - (void) setBitRateFromName: (NSString *) aValue
451 NSDictionary *dict = [self.bitRates dictionaryWithObject: aValue matchingKey: keyAudioBitrateName];
461 #pragma mark Validation
463 // Because we have indicated that the binding for the gain validates immediately we can implement the
464 // key value binding method to ensure the gain stays in our accepted range.
465 - (BOOL)validateGain:(id *)ioValue error:(NSError * __autoreleasing *)outError
471 if ([*ioValue intValue] < -20)
475 else if ([*ioValue intValue] > 20)
485 #pragma mark Bindings Support
490 return (nil != self.track) ? (![self.track isEqual: self.dataSource.noneTrack]) : NO;
493 - (BOOL) mixdownEnabled
496 BOOL retval = self.enabled;
500 int myMixdown = [self.mixdown[keyAudioMixdown] intValue];
501 if (myMixdown == HB_AMIXDOWN_NONE)
503 // "None" mixdown (passthru)
510 - (BOOL) bitrateEnabled
513 BOOL retval = self.enabled;
517 int myCodecCodec = [self.codec[keyAudioCodec] intValue];
518 int myCodecDefaultBitrate = hb_audio_bitrate_get_default(myCodecCodec, 0, 0);
519 if (myCodecDefaultBitrate < 0)
530 BOOL retval = self.enabled;
534 int myTrackParam = [self.track[keyAudioInputCodecParam] intValue];
535 int myTrackCodec = [self.track[keyAudioInputCodec] intValue];
536 int myCodecCodec = [self.codec[keyAudioCodec] intValue];
537 if (!hb_audio_can_apply_drc(myTrackCodec, myTrackParam, myCodecCodec))
545 - (BOOL) PassThruDisabled
548 BOOL retval = self.enabled;
552 int myCodecCodec = [self.codec[keyAudioCodec] intValue];
553 if (myCodecCodec & HB_ACODEC_PASS_FLAG)
561 + (NSSet *) keyPathsForValuesAffectingValueForKey: (NSString *) key
566 if ([key isEqualToString: @"enabled"])
568 retval = [NSSet setWithObjects: @"track", nil];
570 else if ([key isEqualToString: @"PassThruDisabled"])
572 retval = [NSSet setWithObjects: @"track", @"codec", nil];
574 else if ([key isEqualToString: @"DRCEnabled"])
576 retval = [NSSet setWithObjects: @"track", @"codec", nil];
578 else if ([key isEqualToString: @"bitrateEnabled"])
580 retval = [NSSet setWithObjects: @"track", @"codec", nil];
582 else if ([key isEqualToString: @"mixdownEnabled"])
584 retval = [NSSet setWithObjects: @"track", @"mixdown", nil];
589 #pragma mark - NSCopying
591 - (instancetype)copyWithZone:(NSZone *)zone
593 HBAudioTrack *copy = [[[self class] alloc] init];
597 copy->_track = [_track copy];
598 copy->_codec = [_codec copy];
599 copy->_mixdown = [_mixdown copy];
600 copy->_sampleRate = [_sampleRate copy];
601 copy->_bitRate = [_bitRate copy];
602 copy->_drc = [_drc copy];
603 copy->_gain = [_gain copy];
604 copy->_videoContainerTag = [_videoContainerTag copy];
606 copy->_codecs = [_codecs copy];
607 copy->_mixdowns = [_mixdowns copy];
608 copy->_bitRates = [_bitRates copy];
614 #pragma mark - NSCoding
616 + (BOOL)supportsSecureCoding
621 - (void)encodeWithCoder:(NSCoder *)coder
623 [coder encodeInt:1 forKey:@"HBAudioTrackVersion"];
625 encodeObject(_track);
626 encodeObject(_codec);
627 encodeObject(_mixdown);
628 encodeObject(_sampleRate);
629 encodeObject(_bitRate);
632 encodeObject(_videoContainerTag);
634 encodeObject(_codecs);
635 encodeObject(_mixdowns);
636 encodeObject(_bitRates);
639 - (instancetype)initWithCoder:(NSCoder *)decoder
643 decodeObject(_track, NSDictionary);
644 decodeObject(_codec, NSDictionary);
645 decodeObject(_mixdown, NSDictionary);
646 decodeObject(_sampleRate, NSDictionary);
647 decodeObject(_bitRate, NSDictionary);
648 decodeObject(_drc, NSNumber);
649 decodeObject(_gain, NSNumber);
650 decodeObject(_videoContainerTag, NSNumber);
652 decodeObject(_codecs, NSMutableArray);
653 decodeObject(_mixdowns, NSMutableArray);
654 decodeObject(_bitRates, NSArray);