WinGui: Fix another instance of the Caliburn vs Json.net sillyness where objects...
[HandBrake.git] / macosx / HBAudioTrack.m
blobed51df1afd63c4e284f3828c4d303e003745b3b4
1 /*  HBAudioTrack.m $
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"
9 #import "HBJob.h"
10 #import "HBCodingUtilities.h"
11 #import "hb.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;
41 @end
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];
48     NSDictionary *dict;
49     id aValue;
51     while (nil != (dict = [enumerator nextObject]) && !retval)
52     {
53         if (nil != (aValue = dict[aKey]) && [aValue isEqual: anObject])
54         {
55             retval = dict;
56         }
57     }
58     return retval;
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];
69 @end
71 @implementation HBAudioTrack
73 #pragma mark -
74 #pragma mark Object Setup
76 + (void) initialize
79     if ([HBAudioTrack class] == self)
80     {
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))
85         {
86             id audioMustMatchTrack;
87             if ((audio_encoder->codec &  HB_ACODEC_PASS_FLAG) &&
88                 (audio_encoder->codec != HB_ACODEC_AUTO_PASS))
89             {
90                 audioMustMatchTrack = @(audio_encoder->codec &
91                                                       ~HB_ACODEC_PASS_FLAG);
92             }
93             else
94             {
95                 audioMustMatchTrack = @NO;
96             }
97             [masterCodecArray addObject:@{keyAudioCodecName:       @(audio_encoder->name),
98                                           keyAudioCodec:           @(audio_encoder->codec),
99                                           keyAudioSupportedMuxers: @(audio_encoder->muxers),
100                                           keyAudioMustMatchTrack:    audioMustMatchTrack}];
101         }
103         masterMixdownArray = [[NSMutableArray alloc] init]; // knowingly leaked
104         for (const hb_mixdown_t *mixdown = hb_mixdown_get_next(NULL);
105              mixdown != NULL;
106              mixdown  = hb_mixdown_get_next(mixdown))
107         {
108             [masterMixdownArray addObject:@{keyAudioMixdownName: @(mixdown->name),
109                                             keyAudioMixdown:     @(mixdown->amixdown)}];
110         }
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))
116         {
117             [masterBitRateArray addObject:@{keyAudioBitrateName: @(audio_bitrate->name),
118                                             keyAudioBitrate:     @(audio_bitrate->rate)}];
119         }
120     }
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];
130     NSDictionary *dict;
132     // First get a list of the permitted codecs based on the internal rules
133     if (nil != self.track && self.enabled)
134     {
135         BOOL goodToAdd;
137         for (unsigned int i = 0; i < count; i++)
138         {
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])
147             {
148                 if ([dict[keyAudioMustMatchTrack] intValue] != [self.track[keyAudioInputCodec] intValue])
149                 {
150                     goodToAdd = NO;
151                 }
152             }
154             if (goodToAdd)
155             {
156                 [permittedCodecs addObject: dict];
157             }
158         }
159     }
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])
166     {
167         if (0 < [permittedCodecs count])
168         {
169             self.codec = permittedCodecs[0]; // This should be defaulting to Core Audio
170         }
171         else
172         {
173             self.codec = nil;
174         }
175     }
178 - (void) updateMixdowns: (BOOL) shouldSetDefault
181     NSMutableArray *permittedMixdowns = [NSMutableArray array];
182     NSDictionary *dict;
183     int currentMixdown;
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++)
191     {
192         dict = masterMixdownArray[i];
193         currentMixdown = [dict[keyAudioMixdown] intValue];
195         if (hb_mixdown_is_supported(currentMixdown, codecCodec, channelLayout))
196         {
197             [permittedMixdowns addObject: dict];
198         }
199     }
201     if (!self.enabled)
202     {
203         permittedMixdowns = nil;
204     }
206     // Now make sure the permitted list and the actual ones matches
207     self.mixdowns = permittedMixdowns;
209     // Select the proper one
210     if (shouldSetDefault)
211     {
212         self.mixdown = [permittedMixdowns dictionaryWithObject: @(theDefaultMixdown)
213                                                       matchingKey: keyAudioMixdown];
214     }
216     if (!self.mixdown || ![permittedMixdowns containsObject: self.mixdown])
217     {
218         self.mixdown = [permittedMixdowns lastObject];
219     }
222 - (void)validateSamplerate
224     int codec      = [self.codec[keyAudioCodec] intValue];
225     int samplerate = [self.sampleRate[keyAudioSamplerate] intValue];
227     if (codec & HB_ACODEC_PASS_FLAG)
228     {
229         [self setSampleRateFromName:@"Auto"];
230     }
231     else if (samplerate)
232     {
233         samplerate = hb_audio_samplerate_get_best(codec, samplerate, NULL);
234         [self setSampleRateFromName:@(hb_audio_samplerate_get_name(samplerate))];
235     }
238 - (void) updateBitRates: (BOOL) shouldSetDefault
241     NSMutableArray *permittedBitRates = [NSMutableArray array];
242     NSDictionary *dict;
243     int minBitRate;
244     int maxBitRate;
245     int currentBitRate;
246     BOOL shouldAdd;
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
253     {
254         theSampleRate = [self.track[keyAudioInputSampleRate] intValue];
255     }
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;
265     if (codecIsPassthru)
266     {
267         NSDictionary *sourceBitRate = [masterBitRateArray dictionaryWithObject: @(trackInputBitRate)
268                                                                    matchingKey: keyAudioBitrate];
269         if (!sourceBitRate)
270         {
271             // the source bitrate isn't in the master array - create it
272             sourceBitRate = @{keyAudioBitrateName: [NSString stringWithFormat: @"%d", trackInputBitRate],
273                               keyAudioBitrate: @(trackInputBitRate)};
274         }
275         [permittedBitRates addObject: sourceBitRate];
276     }
277     else if (codecIsLossless)
278     {
279         NSDictionary *bitRateNotApplicable = @{keyAudioBitrateName: @"N/A",
280                                                keyAudioBitrate: @-1};
281         [permittedBitRates addObject: bitRateNotApplicable];
282     }
283     else
284     {
285         for (unsigned int i = 0; i < count; i++)
286         {
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);
293             if (shouldAdd)
294             {
295                 [permittedBitRates addObject: dict];
296             }
297         }
298     }
300     if (!self.enabled)
301     {
302         permittedBitRates = nil;
303     }
305     // Make sure we are updated with the permitted list
306     self.bitRates = permittedBitRates;
308     // Select the proper one
309     if (shouldSetDefault)
310     {
311         [self setBitRateFromName: [NSString stringWithFormat:@"%d", theDefaultBitRate]];
312     }
314     if (!self.bitRate || ![permittedBitRates containsObject: self.bitRate])
315     {
316         self.bitRate = [permittedBitRates lastObject];
317     }
320 #pragma mark -
321 #pragma mark Accessors
323 - (NSArray *) sampleRates
325     NSMutableArray *samplerates = [[NSMutableArray alloc] init];
327     /*
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.
331      */
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))
339     {
340         int rate = audio_samplerate->rate;
341         if (rate == hb_audio_samplerate_get_best(codec, rate, NULL))
342         {
343             [samplerates addObject:@{keyAudioSampleRateName: @(audio_samplerate->name),
344                                      keyAudioSamplerate:     @(rate)}];
345         }
346     }
347     return samplerates;
350 #pragma mark -
351 #pragma mark Setters
353 - (void)setVideoContainerTag:(NSNumber *)videoContainerTag
355     _videoContainerTag = videoContainerTag;
356     [self updateCodecs];
359 - (void)setTrack:(NSDictionary *)track
361     NSDictionary *oldValue = _track;
362     _track = track;
363     if (nil != _track)
364     {
365         [self updateCodecs];
366         [self updateMixdowns: YES];
367         if (self.enabled)
368         {
369             self.sampleRate = self.sampleRates[0]; // default to Auto
370         }
371         if ([self.dataSource.noneTrack isEqual: oldValue])
372         {
373             [self.delegate switchingTrackFromNone: self];
374         }
375         if ([self.dataSource.noneTrack isEqual: self.track])
376         {
377             [self.delegate settingTrackToNone: self];
378         }
379     }
382 - (void)setCodec:(NSDictionary *)codec
384     _codec = codec;
385     [self validateSamplerate];
386     [self updateMixdowns: YES];
387     [self updateBitRates: YES];
390 - (void)setMixdown:(NSDictionary *)mixdown
392     _mixdown = mixdown;
393     [self updateBitRates: YES];
394     [self.delegate mixdownChanged];
397 - (void)setSampleRate:(NSDictionary *)sampleRate
399     _sampleRate = sampleRate;
400     [self updateBitRates: NO];
403 #pragma mark -
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];
419     if (nil != dict)
420     {
421         self.codec = dict;
422     }
423     return (nil != dict);
426 - (void) setMixdownFromName: (NSString *) aValue
429     NSDictionary *dict = [self.mixdowns dictionaryWithObject: aValue matchingKey: keyAudioMixdownName];
431     if (nil != dict)
432     {
433         self.mixdown = dict;
434     }
437 - (void) setSampleRateFromName: (NSString *) aValue
440     NSDictionary *dict = [self.sampleRates dictionaryWithObject: aValue matchingKey: keyAudioSampleRateName];
442     if (nil != dict)
443     {
444         self.sampleRate = dict;
445     }
448 - (void) setBitRateFromName: (NSString *) aValue
451     NSDictionary *dict = [self.bitRates dictionaryWithObject: aValue matchingKey: keyAudioBitrateName];
453     if (nil != dict)
454     {
455         self.bitRate = dict;
456     }
460 #pragma mark -
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
467     BOOL retval = YES;
469     if (nil != *ioValue)
470     {
471         if ([*ioValue intValue] < -20)
472         {
473             *ioValue = @(-20);
474         }
475         else if ([*ioValue intValue] > 20)
476         {
477             *ioValue = @20;
478         }
479     }
481     return retval;
484 #pragma mark -
485 #pragma mark Bindings Support
487 - (BOOL) enabled
490     return (nil != self.track) ? (![self.track isEqual: self.dataSource.noneTrack]) : NO;
493 - (BOOL) mixdownEnabled
496     BOOL retval = self.enabled;
498     if (retval)
499     {
500         int myMixdown = [self.mixdown[keyAudioMixdown] intValue];
501         if (myMixdown == HB_AMIXDOWN_NONE)
502         {
503             // "None" mixdown (passthru)
504             retval = NO;
505         }
506     }
507     return retval;
510 - (BOOL) bitrateEnabled
513     BOOL retval = self.enabled;
515     if (retval)
516     {
517         int myCodecCodec          = [self.codec[keyAudioCodec] intValue];
518         int myCodecDefaultBitrate = hb_audio_bitrate_get_default(myCodecCodec, 0, 0);
519         if (myCodecDefaultBitrate < 0)
520         {
521             retval = NO;
522         }
523     }
524     return retval;
527 - (BOOL) DRCEnabled
530     BOOL retval = self.enabled;
532     if (retval)
533     {
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))
538         {
539             retval = NO;
540         }
541     }
542     return retval;
545 - (BOOL) PassThruDisabled
548     BOOL retval = self.enabled;
550     if (retval)
551     {
552         int myCodecCodec = [self.codec[keyAudioCodec] intValue];
553         if (myCodecCodec & HB_ACODEC_PASS_FLAG)
554         {
555             retval = NO;
556         }
557     }
558     return retval;
561 + (NSSet *) keyPathsForValuesAffectingValueForKey: (NSString *) key
564     NSSet *retval = nil;
566     if ([key isEqualToString: @"enabled"])
567     {
568         retval = [NSSet setWithObjects: @"track", nil];
569     }
570     else if ([key isEqualToString: @"PassThruDisabled"])
571     {
572         retval = [NSSet setWithObjects: @"track", @"codec", nil];
573     }
574     else if ([key isEqualToString: @"DRCEnabled"])
575     {
576         retval = [NSSet setWithObjects: @"track", @"codec", nil];
577     }
578     else if ([key isEqualToString: @"bitrateEnabled"])
579     {
580         retval = [NSSet setWithObjects: @"track", @"codec", nil];
581     }
582     else if ([key isEqualToString: @"mixdownEnabled"])
583     {
584         retval = [NSSet setWithObjects: @"track", @"mixdown", nil];
585     }
586     return retval;
589 #pragma mark - NSCopying
591 - (instancetype)copyWithZone:(NSZone *)zone
593     HBAudioTrack *copy = [[[self class] alloc] init];
595     if (copy)
596     {
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];
609     }
611     return copy;
614 #pragma mark - NSCoding
616 + (BOOL)supportsSecureCoding
618     return YES;
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);
630     encodeObject(_drc);
631     encodeObject(_gain);
632     encodeObject(_videoContainerTag);
634     encodeObject(_codecs);
635     encodeObject(_mixdowns);
636     encodeObject(_bitRates);
639 - (instancetype)initWithCoder:(NSCoder *)decoder
641     self = [super init];
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);
656     return self;
659 @end