5 // Created on 2010-08-24.
8 #import "HBAudioController.h"
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";
21 @implementation HBAudioController
24 #pragma mark Accessors
26 @synthesize masterTrackArray;
27 @synthesize noneTrack;
28 @synthesize videoContainerTag;
33 if (self = [super init]) {
34 [self setVideoContainerTag: [NSNumber numberWithInt: HB_MUX_MP4]];
35 audioArray = [[NSMutableArray alloc] init];
43 [[NSNotificationCenter defaultCenter] removeObserver: self];
44 [masterTrackArray release];
47 [self setVideoContainerTag: nil];
52 - (void) setHBController: (id) aController
55 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
56 myController = aController;
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];
64 - (void) _clearAudioArray
67 while (0 < [self countOfAudioArray]) {
68 [self removeObjectFromAudioArrayAtIndex: 0];
74 #pragma mark HBController Support
76 - (void) prepareAudioForQueueFileJob: (NSMutableDictionary *) aDict
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];
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"]];
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"]];
106 - (void) prepareAudioForJob: (hb_job_t *) aJob
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++)
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);
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];
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];
139 hb_audio_add(aJob, audio);
146 - (void) prepareAudioForPreset: (NSMutableArray *) anArray
149 unsigned int audioArrayCount = [self countOfAudioArray];
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];
170 - (void) addTracksFromQueue: (NSMutableDictionary *) aQueue
175 int maximumNumberOfAllowedAudioTracks = [HBController maximumNumberOfAllowedAudioTracks];
177 // Reinitialize the configured list of audio tracks
178 [self _clearAudioArray];
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];
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"]]];
200 [self switchingTrackFromNone: nil]; // see if we need to add one to the list
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
210 NSArray *retval = [aPreset objectForKey: @"AudioList"];
213 int maximumNumberOfAllowedAudioTracks = [HBController maximumNumberOfAllowedAudioTracks];
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",
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
237 NSEnumerator *enumerator = [templateAudioArray objectEnumerator];
240 int maximumNumberOfAllowedAudioTracks = [HBController maximumNumberOfAllowedAudioTracks];
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"];
252 YES == [[NSUserDefaults standardUserDefaults] boolForKey: @"UseCoreAudio"] &&
253 YES == [key isEqualToString: @"AAC (faac)"]
255 key = @"AAC (CoreAudio)";
257 if (YES == [[NSUserDefaults standardUserDefaults] boolForKey: @"AC3PassthruDefaultsToAC3"] &&
258 YES == [key isEqualToString: @"AC3 Passthru"]) {
259 if (NO == [newAudio setCodecFromName: key]) {
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"]];
272 [newAudio setDrc: [dict objectForKey: @"AudioTrackDRCSlider"]];
275 [self removeObjectFromAudioArrayAtIndex: [self countOfAudioArray] - 1];
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
287 unsigned int retval = defaultIfNotFound;
288 int count = [masterTrackArray count];
289 NSString *languageTitle;
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]) {
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
308 static NSMutableDictionary *retval = nil;
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];
325 - (void) addTracksFromPreset: (NSMutableDictionary *) aPreset allTracks: (BOOL) allTracks
328 id whatToUse = [self _presetAudioArrayFromPreset: aPreset];
329 NSString *preferredLanguageName = [[NSUserDefaults standardUserDefaults] stringForKey: @"DefaultLanguage"];
330 int preferredLanguage = [self _trackWithTitlePrefix: preferredLanguageName defaultIfNotFound: 1];
332 // Reinitialize the configured list of audio tracks
333 [self _clearAudioArray];
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]];
348 - (void) _ensureAtLeastOneNonEmptyTrackExists
351 int count = [self countOfAudioArray];
352 if (0 == count || NO == [[self objectInAudioArrayAtIndex: 0] enabled]) {
353 [self addTracksFromPreset: [self _defaultPreset] allTracks: NO];
355 [self switchingTrackFromNone: nil]; // this ensures there is a None track at the end of the list
359 - (void) addTracksFromPreset: (NSMutableDictionary *) aPreset
362 [self addTracksFromPreset: aPreset allTracks: NO];
363 [self _ensureAtLeastOneNonEmptyTrackExists];
367 - (void) addAllTracksFromPreset: (NSMutableDictionary *) aPreset
370 [self addTracksFromPreset: aPreset allTracks: YES];
371 [self _ensureAtLeastOneNonEmptyTrackExists];
375 - (BOOL) anyCodecMatches: (int) aCodecValue
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]) {
389 - (void) addNewAudioTrack
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]];
403 #pragma mark Notification Handling
405 - (void) settingTrackToNone: (HBAudio *) newNoneTrack
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];
413 if (NSNotFound != index && index < [self countOfAudioArray] - 1) {
414 [self removeObjectFromAudioArrayAtIndex: index];
416 [self switchingTrackFromNone: nil]; // see if we need to add one to the list
420 - (void) switchingTrackFromNone: (HBAudio *) noLongerNoneTrack
423 int count = [self countOfAudioArray];
425 int maximumNumberOfAllowedAudioTracks = [HBController maximumNumberOfAllowedAudioTracks];
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) {
430 HBAudio *lastAudio = [self objectInAudioArrayAtIndex: count - 1];
431 if (YES == [lastAudio enabled]) {
440 if (YES == needToAdd) {
441 [self addNewAudioTrack];
446 // This gets called whenever the video container changes.
447 - (void) containerChanged: (NSNotification *) aNotification
450 NSDictionary *notDict = [aNotification userInfo];
452 [self setVideoContainerTag: [notDict objectForKey: keyContainerTag]];
454 // Update each of the instances because this value influences possible settings.
455 NSEnumerator *enumerator = [audioArray objectEnumerator];
456 HBAudio *audioObject;
458 while (nil != (audioObject = [enumerator nextObject])) {
459 [audioObject setVideoContainerTag: [self videoContainerTag]];
464 - (void) titleChanged: (NSNotification *) aNotification
467 NSDictionary *notDict = [aNotification userInfo];
468 NSData *theData = [notDict objectForKey: keyTitleTag];
469 hb_title_t *title = NULL;
471 [theData getBytes: &title length: sizeof(title)];
473 hb_audio_config_t *audio;
474 hb_list_t *list = title->list_audio;
475 int i, count = hb_list_count(list);
477 // Reinitialize the master list of available audio tracks from this title
478 [masterTrackArray release];
479 masterTrackArray = [[NSMutableArray alloc] init];
481 noneTrack = [[NSDictionary dictionaryWithObjectsAndKeys:
482 [NSNumber numberWithInt: 0], keyAudioTrackIndex,
483 NSLocalizedString(@"None", @"None"), keyAudioTrackName,
484 [NSNumber numberWithInt: 0], keyAudioInputCodec,
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,
500 // Reinitialize the configured list of audio tracks
501 [self _clearAudioArray];
503 if (NO == [myController hasValidPresetSelected]) {
504 [self _ensureAtLeastOneNonEmptyTrackExists];
512 - (unsigned int) countOfAudioArray
515 return [audioArray count];
518 - (HBAudio *) objectInAudioArrayAtIndex: (unsigned int) index
521 return [audioArray objectAtIndex: index];
524 - (void) insertObject: (HBAudio *) audioObject inAudioArrayAtIndex: (unsigned int) index;
527 [audioArray insertObject: audioObject atIndex: index];
531 - (void) removeObjectFromAudioArrayAtIndex: (unsigned int) index
534 [audioArray removeObjectAtIndex: index];