OSDN Git Service

MacGui: Remove Target Size as a rate control option as it doesn't really work correct...
[handbrake-jp/handbrake-jp-git.git] / macosx / HBAudioController.m
1 //
2 //  HBAudioController.m
3 //  HandBrake
4 //
5 //  Created on 2010-08-24.
6 //
7
8 #import "HBAudioController.h"
9 #import "Controller.h"
10 #import "HBAudio.h"
11 #import "hb.h"
12
13 NSString *keyAudioTrackIndex = @"keyAudioTrackIndex";
14 NSString *keyAudioTrackName = @"keyAudioTrackName";
15 NSString *keyAudioInputBitrate = @"keyAudioInputBitrate";
16 NSString *keyAudioInputSampleRate = @"keyAudioInputSampleRate";
17 NSString *keyAudioInputCodec = @"keyAudioInputCodec";
18 NSString *keyAudioInputChannelLayout = @"keyAudioInputChannelLayout";
19 NSString *HBMixdownChangedNotification = @"HBMixdownChangedNotification";
20
21 @implementation HBAudioController
22
23 #pragma mark -
24 #pragma mark Accessors
25
26 @synthesize masterTrackArray;
27 @synthesize noneTrack;
28 @synthesize videoContainerTag;
29
30 - (id) init
31
32 {
33         if (self = [super init]) {
34                 [self setVideoContainerTag: [NSNumber numberWithInt: HB_MUX_MP4]];
35                 audioArray = [[NSMutableArray alloc] init];
36         }
37         return self;
38 }
39
40 - (void) dealloc
41
42 {
43         [[NSNotificationCenter defaultCenter] removeObserver: self];
44         [masterTrackArray release];
45         [noneTrack release];
46         [audioArray release];
47         [self setVideoContainerTag: nil];
48         [super dealloc];
49         return;
50 }
51
52 - (void) setHBController: (id) aController
53
54 {
55         NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
56         myController = aController;
57
58         /* register that we are interested in changes made to the video container */
59         [center addObserver: self selector: @selector(containerChanged:) name: HBContainerChangedNotification object: aController];
60         [center addObserver: self selector: @selector(titleChanged:) name: HBTitleChangedNotification object: aController];
61         return;
62 }
63
64 - (void) _clearAudioArray
65
66 {
67         while (0 < [self countOfAudioArray]) {
68                 [self removeObjectFromAudioArrayAtIndex: 0];
69         }
70         return;
71 }
72
73 #pragma mark -
74 #pragma mark HBController Support
75
76 - (void) prepareAudioForQueueFileJob: (NSMutableDictionary *) aDict
77
78 {
79         unsigned int audioArrayCount = [self countOfAudioArray];
80         for (unsigned int counter = 0; counter < audioArrayCount; counter++) {
81                 HBAudio *anAudio = [self objectInAudioArrayAtIndex: counter];
82                 if (YES == [anAudio enabled]) {
83                         NSString *prefix = [NSString stringWithFormat: @"Audio%d", counter + 1];
84                         NSNumber *sampleRateToUse = (0 == [[[anAudio sampleRate] objectForKey: keyAudioSamplerate] intValue]) ?
85                                                                 [[anAudio track] objectForKey: keyAudioInputSampleRate] :
86                                                                 [[anAudio sampleRate] objectForKey: keyAudioSamplerate];
87                 
88                         [aDict setObject: [[anAudio track] objectForKey: keyAudioTrackIndex] forKey: [prefix stringByAppendingString: @"Track"]];
89                         [aDict setObject: [[anAudio track] objectForKey: keyAudioTrackName] forKey: [prefix stringByAppendingString: @"TrackDescription"]];
90                         [aDict setObject: [[anAudio codec] objectForKey: keyAudioCodecName] forKey: [prefix stringByAppendingString: @"Encoder"]];
91                         [aDict setObject: [[anAudio mixdown] objectForKey: keyAudioMixdownName] forKey: [prefix stringByAppendingString: @"Mixdown"]];
92                         [aDict setObject: [[anAudio sampleRate] objectForKey: keyAudioSampleRateName] forKey: [prefix stringByAppendingString: @"Samplerate"]];
93                         [aDict setObject: [[anAudio bitRate] objectForKey: keyAudioBitrateName] forKey: [prefix stringByAppendingString: @"Bitrate"]];
94                         [aDict setObject: [anAudio drc] forKey: [prefix stringByAppendingString: @"TrackDRCSlider"]];
95                 
96                         prefix = [NSString stringWithFormat: @"JobAudio%d", counter + 1];
97                         [aDict setObject: [[anAudio codec] objectForKey: keyAudioCodec] forKey: [prefix stringByAppendingString: @"Encoder"]];
98                         [aDict setObject: [[anAudio mixdown] objectForKey: keyAudioMixdown] forKey: [prefix stringByAppendingString: @"Mixdown"]];
99                         [aDict setObject: sampleRateToUse forKey: [prefix stringByAppendingString: @"Samplerate"]];
100                         [aDict setObject: [[anAudio bitRate] objectForKey: keyAudioBitrate] forKey: [prefix stringByAppendingString: @"Bitrate"]];
101                 }
102         }
103         return;
104 }
105
106 - (void) prepareAudioForJob: (hb_job_t *) aJob
107
108 {
109         unsigned int i;
110         
111         //      First clear out any audio tracks in the job currently
112     int audiotrack_count = hb_list_count(aJob->list_audio);
113     for(i = 0; i < audiotrack_count; i++)
114     {
115         hb_audio_t *temp_audio = (hb_audio_t *) hb_list_item(aJob->list_audio, 0);
116         hb_list_rem(aJob->list_audio, temp_audio);
117     }
118
119         //      Now add audio tracks based on the current settings
120         unsigned int audioArrayCount = [self countOfAudioArray];
121         for (i = 0; i < audioArrayCount; i++) {
122                 HBAudio *anAudio = [self objectInAudioArrayAtIndex: i];
123                 if (YES == [anAudio enabled]) {
124                         NSNumber *sampleRateToUse = (0 == [[[anAudio sampleRate] objectForKey: keyAudioSamplerate] intValue]) ?
125                                                                                 [[anAudio track] objectForKey: keyAudioInputSampleRate] :
126                                                                                 [[anAudio sampleRate] objectForKey: keyAudioSamplerate];
127                         
128                         hb_audio_config_t *audio = (hb_audio_config_t *) calloc(1, sizeof(*audio));
129                         hb_audio_config_init(audio);
130                         audio->in.track = [[[anAudio track] objectForKey: keyAudioTrackIndex] intValue] - 1;
131                         /* We go ahead and assign values to our audio->out.<properties> */
132                         audio->out.track = audio->in.track;
133                         audio->out.codec = [[[anAudio codec] objectForKey: keyAudioCodec] intValue];
134                         audio->out.mixdown = [[[anAudio mixdown] objectForKey: keyAudioMixdown] intValue];
135                         audio->out.bitrate = [[[anAudio bitRate] objectForKey: keyAudioBitrate] intValue];
136                         audio->out.samplerate = [sampleRateToUse intValue];
137                         audio->out.dynamic_range_compression = [[anAudio drc] floatValue];
138         
139                         hb_audio_add(aJob, audio);
140                         free(audio);
141                 }
142         }
143         return;
144 }
145
146 - (void) prepareAudioForPreset: (NSMutableArray *) anArray
147
148 {
149         unsigned int audioArrayCount = [self countOfAudioArray];
150         unsigned int i;
151
152         for (i = 0; i < audioArrayCount; i++) {
153                 HBAudio *anAudio = [self objectInAudioArrayAtIndex: i];
154                 if (YES == [anAudio enabled]) {
155                         NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity: 7];
156                         [dict setObject: [[anAudio track] objectForKey: keyAudioTrackIndex] forKey: @"AudioTrack"];
157                         [dict setObject: [[anAudio track] objectForKey: keyAudioTrackName] forKey: @"AudioTrackDescription"];
158                         [dict setObject: [[anAudio codec] objectForKey: keyAudioCodecName] forKey: @"AudioEncoder"];
159                         [dict setObject: [[anAudio mixdown] objectForKey: keyAudioMixdownName] forKey: @"AudioMixdown"];
160                         [dict setObject: [[anAudio sampleRate] objectForKey: keyAudioSampleRateName] forKey: @"AudioSamplerate"];
161                         [dict setObject: [[anAudio bitRate] objectForKey: keyAudioBitrateName] forKey: @"AudioBitrate"];
162                         [dict setObject: [anAudio drc] forKey: @"AudioTrackDRCSlider"];
163                         [anArray addObject: dict];
164                         [dict release];
165                 }
166         }
167         return;
168 }
169
170 - (void) addTracksFromQueue: (NSMutableDictionary *) aQueue
171
172 {
173         NSString *base;
174         int value;
175         int maximumNumberOfAllowedAudioTracks = [HBController maximumNumberOfAllowedAudioTracks];
176
177         //      Reinitialize the configured list of audio tracks
178         [self _clearAudioArray];
179         
180         //      The following is the pattern to follow, but with Audio%dTrack being the key to seek...
181         //      Can we assume that there will be no skip in the data?
182         for (unsigned int i = 1; i <= maximumNumberOfAllowedAudioTracks; i++) {
183                 base = [NSString stringWithFormat: @"Audio%d", i];
184                 value = [[aQueue objectForKey: [base stringByAppendingString: @"Track"]] intValue];
185                 if (0 < value) {
186                         HBAudio *newAudio = [[HBAudio alloc] init];
187                         [newAudio setController: self];
188                         [self insertObject: newAudio inAudioArrayAtIndex: [self countOfAudioArray]];
189                         [newAudio setVideoContainerTag: [self videoContainerTag]];
190                         [newAudio setTrackFromIndex: value];
191                         [newAudio setCodecFromName: [aQueue objectForKey: [base stringByAppendingString: @"Encoder"]]];
192                         [newAudio setMixdownFromName: [aQueue objectForKey: [base stringByAppendingString: @"Mixdown"]]];
193                         [newAudio setSampleRateFromName: [aQueue objectForKey: [base stringByAppendingString: @"Samplerate"]]];
194                         [newAudio setBitRateFromName: [aQueue objectForKey: [base stringByAppendingString: @"Bitrate"]]];
195                         [newAudio setDrc: [aQueue objectForKey: [base stringByAppendingString: @"TrackDRCSlider"]]];
196                         [newAudio release];
197                 }
198         }
199
200         [self switchingTrackFromNone: nil];     // see if we need to add one to the list
201         
202         return;
203 }
204
205 //      This routine takes the preset and will return the value for the key AudioList
206 //      if it exists, otherwise it creates an array from the data in the present.
207 - (NSArray *) _presetAudioArrayFromPreset: (NSMutableDictionary *) aPreset
208
209 {
210         NSArray *retval = [aPreset objectForKey: @"AudioList"];
211
212         if (nil == retval) {
213                 int maximumNumberOfAllowedAudioTracks = [HBController maximumNumberOfAllowedAudioTracks];
214                 NSString *base;
215                 NSMutableArray *whatToUse = [NSMutableArray array];
216                 for (unsigned int i = 1; i <= maximumNumberOfAllowedAudioTracks; i++) {
217                         base = [NSString stringWithFormat: @"Audio%d", i];
218                         if (nil != [aPreset objectForKey: [base stringByAppendingString: @"Track"]]) {
219                                 [whatToUse addObject: [NSDictionary dictionaryWithObjectsAndKeys:
220                                                                            [aPreset objectForKey: [base stringByAppendingString: @"Encoder"]], @"AudioEncoder",
221                                                                            [aPreset objectForKey: [base stringByAppendingString: @"Mixdown"]], @"AudioMixdown",
222                                                                            [aPreset objectForKey: [base stringByAppendingString: @"Samplerate"]], @"AudioSamplerate",
223                                                                            [aPreset objectForKey: [base stringByAppendingString: @"Bitrate"]], @"AudioBitrate",
224                                                                            [aPreset objectForKey: [base stringByAppendingString: @"TrackDRCSlider"]], @"AudioTrackDRCSlider",
225                                                                            nil]];
226                         }
227                 }
228                 retval = whatToUse;
229         }
230         return retval;
231 }
232
233 //      This uses the templateAudioArray from the preset to create the audios for the specified trackIndex
234 - (void) _processPresetAudioArray: (NSArray *) templateAudioArray forTrack: (unsigned int) trackIndex andType: (int) aType
235
236 {
237         NSEnumerator *enumerator = [templateAudioArray objectEnumerator];
238         NSDictionary *dict;
239         NSString *key;
240         int maximumNumberOfAllowedAudioTracks = [HBController maximumNumberOfAllowedAudioTracks];
241         
242         while (nil != (dict = [enumerator nextObject])) {
243                 if ([self countOfAudioArray] < maximumNumberOfAllowedAudioTracks) {
244                         BOOL fallenBack = NO;
245                         HBAudio *newAudio = [[HBAudio alloc] init];
246                         [newAudio setController: self];
247                         [self insertObject: newAudio inAudioArrayAtIndex: [self countOfAudioArray]];
248                         [newAudio setVideoContainerTag: [self videoContainerTag]];
249                         [newAudio setTrackFromIndex: trackIndex];
250                         key = [dict objectForKey: @"AudioEncoder"];
251                         if (0 == aType &&
252                                 YES == [[NSUserDefaults standardUserDefaults] boolForKey: @"UseCoreAudio"] &&
253                                 YES == [key isEqualToString: @"AAC (faac)"]
254                                 ) {
255                                 key = @"AAC (CoreAudio)";
256                         }
257                         if (YES == [[NSUserDefaults standardUserDefaults] boolForKey: @"AC3PassthruDefaultsToAC3"] &&
258                                 YES == [key isEqualToString: @"AC3 Passthru"]) {
259                                 if (NO == [newAudio setCodecFromName: key]) {
260                                         key = @"AC3";
261                                         fallenBack = YES;
262                                 }
263                         }
264                         //      If our preset wants us to support a codec that the track does not support, instead
265                         //      of changing the codec we remove the audio instead.
266                         if (YES == [newAudio setCodecFromName: key]) {
267                                 [newAudio setMixdownFromName: [dict objectForKey: @"AudioMixdown"]];
268                                 [newAudio setSampleRateFromName: [dict objectForKey: @"AudioSamplerate"]];
269                                 if (NO == fallenBack) {
270                                         [newAudio setBitRateFromName: [dict objectForKey: @"AudioBitrate"]];
271                                         }
272                                 [newAudio setDrc: [dict objectForKey: @"AudioTrackDRCSlider"]];
273                         }
274                         else {
275                                 [self removeObjectFromAudioArrayAtIndex: [self countOfAudioArray] - 1];
276                         }
277                         [newAudio release];
278                 }
279         }
280         return;
281 }
282
283 //      This matches the FIRST track with the specified prefix, otherwise it uses the defaultIfNotFound value
284 - (unsigned int) _trackWithTitlePrefix: (NSString *) prefix defaultIfNotFound: (unsigned int) defaultIfNotFound
285
286 {
287         unsigned int retval = defaultIfNotFound;
288         int count = [masterTrackArray count];
289         NSString *languageTitle;
290         BOOL found = NO;
291         
292         //      We search for the prefix noting that our titles have the format %d: %s where the %s is the prefix
293         for (unsigned int i = 1; i < count && NO == found; i++) {       //      Note that we skip the "None" track
294                 languageTitle = [[masterTrackArray objectAtIndex: i] objectForKey: keyAudioTrackName];
295                 if (YES == [[languageTitle substringFromIndex: [languageTitle rangeOfString: @" "].location + 1] hasPrefix: prefix]) {
296                         retval = i;
297                         found = YES;
298                 }
299         }
300         return retval;
301 }
302
303 //      When we add a track and we do not have a preset to use for the track we use
304 //      this bogus preset to do the dirty work.
305 - (NSMutableDictionary *) _defaultPreset
306
307 {
308         static NSMutableDictionary *retval = nil;
309         
310         if (nil == retval) {
311                 retval = [[NSMutableDictionary dictionaryWithObjectsAndKeys:
312                 [NSArray arrayWithObject:
313                 [NSDictionary dictionaryWithObjectsAndKeys:
314                 [NSNumber numberWithInt: 1],     @"AudioTrack",
315                 @"AAC (faac)",                   @"AudioEncoder",
316                 @"Dolby Pro Logic II",           @"AudioMixdown",
317                 @"Auto",                         @"AudioSamplerate",
318                 @"160",                          @"AudioBitrate",
319                 [NSNumber numberWithFloat: 0.0], @"AudioTrackDRCSlider",
320                 nil]], @"AudioList", nil] retain];
321         }
322         return retval;
323 }
324
325 - (void) addTracksFromPreset: (NSMutableDictionary *) aPreset allTracks: (BOOL) allTracks
326
327 {
328         id whatToUse = [self _presetAudioArrayFromPreset: aPreset];
329         NSString *preferredLanguageName = [[NSUserDefaults standardUserDefaults] stringForKey: @"DefaultLanguage"];
330         int preferredLanguage = [self _trackWithTitlePrefix: preferredLanguageName defaultIfNotFound: 1];
331
332         //      Reinitialize the configured list of audio tracks
333         [self _clearAudioArray];
334         
335         [self _processPresetAudioArray: whatToUse forTrack: preferredLanguage andType: [[aPreset objectForKey: @"Type"] intValue]];
336         if (YES == allTracks) {
337                 unsigned int count = [masterTrackArray count];
338                 for (unsigned int i = 1; i < count; i++) {
339                         if (i != preferredLanguage) {
340                                 [self _processPresetAudioArray: whatToUse forTrack: i andType: [[aPreset objectForKey: @"Type"] intValue]];
341                         }
342                 }
343         }
344
345         return;
346 }
347
348 - (void) _ensureAtLeastOneNonEmptyTrackExists
349
350 {
351         int count = [self countOfAudioArray];
352         if (0 == count || NO == [[self objectInAudioArrayAtIndex: 0] enabled]) {
353                 [self addTracksFromPreset: [self _defaultPreset] allTracks: NO];
354                 }
355         [self switchingTrackFromNone: nil];     //      this ensures there is a None track at the end of the list
356         return;
357 }
358
359 - (void) addTracksFromPreset: (NSMutableDictionary *) aPreset
360
361 {
362         [self addTracksFromPreset: aPreset allTracks: NO];
363         [self _ensureAtLeastOneNonEmptyTrackExists];
364         return;
365 }
366
367 - (void) addAllTracksFromPreset: (NSMutableDictionary *) aPreset
368
369 {
370         [self addTracksFromPreset: aPreset allTracks: YES];
371         [self _ensureAtLeastOneNonEmptyTrackExists];
372         return;
373 }
374
375 - (BOOL) anyCodecMatches: (int) aCodecValue
376
377 {
378         BOOL retval = NO;
379         unsigned int audioArrayCount = [self countOfAudioArray];
380         for (unsigned int i = 0; i < audioArrayCount && NO == retval; i++) {
381                 HBAudio *anAudio = [self objectInAudioArrayAtIndex: i];
382         if (YES == [anAudio enabled] && aCodecValue == [[[anAudio codec] objectForKey: keyAudioCodec] intValue]) {
383                         retval = YES;
384                 }
385         }
386         return retval;
387 }
388
389 - (void) addNewAudioTrack
390
391 {
392         HBAudio *newAudio = [[HBAudio alloc] init];
393         [newAudio setController: self];
394         [self insertObject: newAudio inAudioArrayAtIndex: [self countOfAudioArray]];
395         [newAudio setVideoContainerTag: [self videoContainerTag]];
396         [newAudio setTrack: noneTrack];
397         [newAudio setDrc: [NSNumber numberWithFloat: 0.0]];
398         [newAudio release];     
399         return;
400 }
401
402 #pragma mark -
403 #pragma mark Notification Handling
404
405 - (void) settingTrackToNone: (HBAudio *) newNoneTrack
406
407 {
408         //      If this is not the last track in the array we need to remove it.  We then need to see if a new
409         //      one needs to be added (in the case when we were at maximum count and this switching makes it
410         //      so we are no longer at maximum.
411         unsigned int index = [audioArray indexOfObject: newNoneTrack];
412
413         if (NSNotFound != index && index < [self countOfAudioArray] - 1) {
414                 [self removeObjectFromAudioArrayAtIndex: index];
415         }
416         [self switchingTrackFromNone: nil];     // see if we need to add one to the list
417         return;
418 }
419
420 - (void) switchingTrackFromNone: (HBAudio *) noLongerNoneTrack
421
422 {
423         int count = [self countOfAudioArray];
424         BOOL needToAdd = NO;
425         int maximumNumberOfAllowedAudioTracks = [HBController maximumNumberOfAllowedAudioTracks];
426
427         //      If there is no last track that is None and we are less than our maximum number of permitted tracks, we add one.
428         if (count < maximumNumberOfAllowedAudioTracks) {
429                 if (0 < count) {
430                         HBAudio *lastAudio = [self objectInAudioArrayAtIndex: count - 1];
431                         if (YES == [lastAudio enabled]) {
432                                 needToAdd = YES;
433                         }
434                 }
435                 else {
436                         needToAdd = YES;
437                 }
438         }
439
440         if (YES == needToAdd) {
441                 [self addNewAudioTrack];
442         }
443         return;
444 }
445
446 //      This gets called whenever the video container changes.
447 - (void) containerChanged: (NSNotification *) aNotification
448
449 {
450         NSDictionary *notDict = [aNotification userInfo];
451
452         [self setVideoContainerTag: [notDict objectForKey: keyContainerTag]];
453
454         //      Update each of the instances because this value influences possible settings.
455         NSEnumerator *enumerator = [audioArray objectEnumerator];
456         HBAudio *audioObject;
457
458         while (nil != (audioObject = [enumerator nextObject])) {
459                 [audioObject setVideoContainerTag: [self videoContainerTag]];
460         }
461         return;
462 }
463
464 - (void) titleChanged: (NSNotification *) aNotification
465
466 {
467         NSDictionary *notDict = [aNotification userInfo];
468         NSData *theData = [notDict objectForKey: keyTitleTag];
469         hb_title_t *title = NULL;
470
471         [theData getBytes: &title length: sizeof(title)];
472         if (title) {
473                 hb_audio_config_t *audio;
474                 hb_list_t *list = title->list_audio;
475                 int i, count = hb_list_count(list);
476
477                 //      Reinitialize the master list of available audio tracks from this title
478                 [masterTrackArray release];
479                 masterTrackArray = [[NSMutableArray alloc] init];
480                 [noneTrack release];
481                 noneTrack = [[NSDictionary dictionaryWithObjectsAndKeys:
482                                          [NSNumber numberWithInt: 0], keyAudioTrackIndex,
483                                          NSLocalizedString(@"None", @"None"), keyAudioTrackName,
484                                          [NSNumber numberWithInt: 0], keyAudioInputCodec,
485                                                          nil] retain];
486                 [masterTrackArray addObject: noneTrack];
487                 for (i = 0; i < count; i++) {
488                         audio = (hb_audio_config_t *) hb_list_audio_config_item(list, i);
489                         [masterTrackArray addObject: [NSDictionary dictionaryWithObjectsAndKeys:
490                                                                                   [NSNumber numberWithInt: i + 1], keyAudioTrackIndex,
491                                                                                   [NSString stringWithFormat: @"%d: %s", i, audio->lang.description], keyAudioTrackName,
492                                                                                   [NSNumber numberWithInt: audio->in.bitrate / 1000], keyAudioInputBitrate,
493                                                                                   [NSNumber numberWithInt: audio->in.samplerate], keyAudioInputSampleRate,
494                                                                                   [NSNumber numberWithInt: audio->in.codec], keyAudioInputCodec,
495                                                                                   [NSNumber numberWithInt: audio->in.channel_layout], keyAudioInputChannelLayout,
496                                                                                   nil]];
497                 }
498         }
499
500         //      Reinitialize the configured list of audio tracks
501         [self _clearAudioArray];
502
503         if (NO == [myController hasValidPresetSelected]) {
504                 [self _ensureAtLeastOneNonEmptyTrackExists];
505         }
506         return;
507 }
508
509 #pragma mark -
510 #pragma mark KVC
511
512 - (unsigned int) countOfAudioArray
513
514 {
515         return [audioArray count];
516 }
517
518 - (HBAudio *) objectInAudioArrayAtIndex: (unsigned int) index
519
520 {
521         return [audioArray objectAtIndex: index];
522 }
523
524 - (void) insertObject: (HBAudio *) audioObject inAudioArrayAtIndex: (unsigned int) index;
525
526 {
527         [audioArray insertObject: audioObject atIndex: index];
528         return;
529 }
530
531 - (void) removeObjectFromAudioArrayAtIndex: (unsigned int) index
532
533 {
534         [audioArray removeObjectAtIndex: index];
535         return;
536 }
537
538 @end