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]];
42 [[NSNotificationCenter defaultCenter] removeObserver: self];
43 [masterTrackArray release];
46 [self setVideoContainerTag: nil];
51 - (void) setHBController: (id) aController
54 NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
55 myController = aController;
57 /* register that we are interested in changes made to the video container */
58 [center addObserver: self selector: @selector(containerChanged:) name: HBContainerChangedNotification object: aController];
59 [center addObserver: self selector: @selector(titleChanged:) name: HBTitleChangedNotification object: aController];
64 #pragma mark HBController Support
66 - (void) prepareAudioForQueueFileJob: (NSMutableDictionary *) aDict
69 unsigned int audioArrayCount = [self countOfAudioArray];
70 for (unsigned int counter = 0; counter < audioArrayCount; counter++) {
71 HBAudio *anAudio = [self objectInAudioArrayAtIndex: counter];
72 if (YES == [anAudio enabled]) {
73 NSString *prefix = [NSString stringWithFormat: @"Audio%d", counter + 1];
74 NSNumber *sampleRateToUse = (0 == [[[anAudio sampleRate] objectForKey: keyAudioSamplerate] intValue]) ?
75 [[anAudio track] objectForKey: keyAudioInputSampleRate] :
76 [[anAudio sampleRate] objectForKey: keyAudioSamplerate];
78 [aDict setObject: [[anAudio track] objectForKey: keyAudioTrackIndex] forKey: [prefix stringByAppendingString: @"Track"]];
79 [aDict setObject: [[anAudio track] objectForKey: keyAudioTrackName] forKey: [prefix stringByAppendingString: @"TrackDescription"]];
80 [aDict setObject: [[anAudio codec] objectForKey: keyAudioCodecName] forKey: [prefix stringByAppendingString: @"Encoder"]];
81 [aDict setObject: [[anAudio mixdown] objectForKey: keyAudioMixdownName] forKey: [prefix stringByAppendingString: @"Mixdown"]];
82 [aDict setObject: [[anAudio sampleRate] objectForKey: keyAudioSampleRateName] forKey: [prefix stringByAppendingString: @"Samplerate"]];
83 [aDict setObject: [[anAudio bitRate] objectForKey: keyAudioBitrateName] forKey: [prefix stringByAppendingString: @"Bitrate"]];
84 [aDict setObject: [anAudio drc] forKey: [prefix stringByAppendingString: @"TrackDRCSlider"]];
86 prefix = [NSString stringWithFormat: @"JobAudio%d", counter + 1];
87 [aDict setObject: [[anAudio codec] objectForKey: keyAudioCodec] forKey: [prefix stringByAppendingString: @"Encoder"]];
88 [aDict setObject: [[anAudio mixdown] objectForKey: keyAudioMixdown] forKey: [prefix stringByAppendingString: @"Mixdown"]];
89 [aDict setObject: sampleRateToUse forKey: [prefix stringByAppendingString: @"Samplerate"]];
90 [aDict setObject: [[anAudio bitRate] objectForKey: keyAudioBitrate] forKey: [prefix stringByAppendingString: @"Bitrate"]];
96 - (void) prepareAudioForJob: (hb_job_t *) aJob
101 // First clear out any audio tracks in the job currently
102 int audiotrack_count = hb_list_count(aJob->list_audio);
103 for(i = 0; i < audiotrack_count; i++)
105 hb_audio_t *temp_audio = (hb_audio_t *) hb_list_item(aJob->list_audio, 0);
106 hb_list_rem(aJob->list_audio, temp_audio);
109 // Now add audio tracks based on the current settings
110 unsigned int audioArrayCount = [self countOfAudioArray];
111 for (i = 0; i < audioArrayCount; i++) {
112 HBAudio *anAudio = [self objectInAudioArrayAtIndex: i];
113 if (YES == [anAudio enabled]) {
114 NSNumber *sampleRateToUse = (0 == [[[anAudio sampleRate] objectForKey: keyAudioSamplerate] intValue]) ?
115 [[anAudio track] objectForKey: keyAudioInputSampleRate] :
116 [[anAudio sampleRate] objectForKey: keyAudioSamplerate];
118 hb_audio_config_t *audio = (hb_audio_config_t *) calloc(1, sizeof(*audio));
119 hb_audio_config_init(audio);
120 audio->in.track = [[[anAudio track] objectForKey: keyAudioTrackIndex] intValue] - 1;
121 /* We go ahead and assign values to our audio->out.<properties> */
122 audio->out.track = audio->in.track;
123 audio->out.codec = [[[anAudio codec] objectForKey: keyAudioCodec] intValue];
124 audio->out.mixdown = [[[anAudio mixdown] objectForKey: keyAudioMixdown] intValue];
125 audio->out.bitrate = [[[anAudio bitRate] objectForKey: keyAudioBitrate] intValue];
126 audio->out.samplerate = [sampleRateToUse intValue];
127 audio->out.dynamic_range_compression = [[anAudio drc] floatValue];
129 hb_audio_add(aJob, audio);
136 - (void) prepareAudioForPreset: (NSMutableArray *) anArray
139 unsigned int audioArrayCount = [self countOfAudioArray];
142 for (i = 0; i < audioArrayCount; i++) {
143 HBAudio *anAudio = [self objectInAudioArrayAtIndex: i];
144 if (YES == [anAudio enabled]) {
145 NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity: 7];
146 [dict setObject: [[anAudio track] objectForKey: keyAudioTrackIndex] forKey: @"AudioTrack"];
147 [dict setObject: [[anAudio track] objectForKey: keyAudioTrackName] forKey: @"AudioTrackDescription"];
148 [dict setObject: [[anAudio codec] objectForKey: keyAudioCodecName] forKey: @"AudioEncoder"];
149 [dict setObject: [[anAudio mixdown] objectForKey: keyAudioMixdownName] forKey: @"AudioMixdown"];
150 [dict setObject: [[anAudio sampleRate] objectForKey: keyAudioSampleRateName] forKey: @"AudioSamplerate"];
151 [dict setObject: [[anAudio bitRate] objectForKey: keyAudioBitrateName] forKey: @"AudioBitrate"];
152 [dict setObject: [anAudio drc] forKey: @"AudioTrackDRCSlider"];
153 [anArray addObject: dict];
160 - (void) addTracksFromQueue: (NSMutableDictionary *) aQueue
165 int maximumNumberOfAllowedAudioTracks = [HBController maximumNumberOfAllowedAudioTracks];
167 // Reinitialize the configured list of audio tracks
168 [audioArray release];
169 audioArray = [[NSMutableArray alloc] init];
171 // The following is the pattern to follow, but with Audio%dTrack being the key to seek...
172 // Can we assume that there will be no skip in the data?
173 for (unsigned int i = 1; i <= maximumNumberOfAllowedAudioTracks; i++) {
174 base = [NSString stringWithFormat: @"Audio%d", i];
175 value = [[aQueue objectForKey: [base stringByAppendingString: @"Track"]] intValue];
177 HBAudio *newAudio = [[HBAudio alloc] init];
178 [newAudio setController: self];
179 [self insertObject: newAudio inAudioArrayAtIndex: [self countOfAudioArray]];
180 [newAudio setVideoContainerTag: [self videoContainerTag]];
181 [newAudio setTrackFromIndex: value];
182 [newAudio setCodecFromName: [aQueue objectForKey: [base stringByAppendingString: @"Encoder"]]];
183 [newAudio setMixdownFromName: [aQueue objectForKey: [base stringByAppendingString: @"Mixdown"]]];
184 [newAudio setSampleRateFromName: [aQueue objectForKey: [base stringByAppendingString: @"Samplerate"]]];
185 [newAudio setBitRateFromName: [aQueue objectForKey: [base stringByAppendingString: @"Bitrate"]]];
186 [newAudio setDrc: [aQueue objectForKey: [base stringByAppendingString: @"TrackDRCSlider"]]];
191 [self switchingTrackFromNone: nil]; // see if we need to add one to the list
196 // This routine takes the preset and will return the value for the key AudioList
197 // if it exists, otherwise it creates an array from the data in the present.
198 - (NSArray *) _presetAudioArrayFromPreset: (NSMutableDictionary *) aPreset
201 NSArray *retval = [aPreset objectForKey: @"AudioList"];
204 int maximumNumberOfAllowedAudioTracks = [HBController maximumNumberOfAllowedAudioTracks];
206 NSMutableArray *whatToUse = [NSMutableArray array];
207 for (unsigned int i = 1; i <= maximumNumberOfAllowedAudioTracks; i++) {
208 base = [NSString stringWithFormat: @"Audio%d", i];
209 if (nil != [aPreset objectForKey: [base stringByAppendingString: @"Track"]]) {
210 [whatToUse addObject: [NSDictionary dictionaryWithObjectsAndKeys:
211 [aPreset objectForKey: [base stringByAppendingString: @"Encoder"]], @"AudioEncoder",
212 [aPreset objectForKey: [base stringByAppendingString: @"Mixdown"]], @"AudioMixdown",
213 [aPreset objectForKey: [base stringByAppendingString: @"Samplerate"]], @"AudioSamplerate",
214 [aPreset objectForKey: [base stringByAppendingString: @"Bitrate"]], @"AudioBitrate",
215 [aPreset objectForKey: [base stringByAppendingString: @"TrackDRCSlider"]], @"AudioTrackDRCSlider",
224 // This uses the templateAudioArray from the preset to create the audios for the specified trackIndex
225 - (void) _processPresetAudioArray: (NSArray *) templateAudioArray forTrack: (unsigned int) trackIndex andType: (int) aType
228 NSEnumerator *enumerator = [templateAudioArray objectEnumerator];
232 while (nil != (dict = [enumerator nextObject])) {
233 HBAudio *newAudio = [[HBAudio alloc] init];
234 [newAudio setController: self];
235 [self insertObject: newAudio inAudioArrayAtIndex: [self countOfAudioArray]];
236 [newAudio setVideoContainerTag: [self videoContainerTag]];
237 [newAudio setTrackFromIndex: trackIndex];
238 key = [dict objectForKey: @"AudioEncoder"];
240 YES == [[NSUserDefaults standardUserDefaults] boolForKey: @"UseCoreAudio"] &&
241 YES == [key isEqualToString: @"AAC (faac)"]
243 key = @"AAC (CoreAudio)";
245 // If our preset wants us to support a codec that the track does not support, instead
246 // of changing the codec we remove the audio instead.
247 if (YES == [newAudio setCodecFromName: key]) {
248 [newAudio setMixdownFromName: [dict objectForKey: @"AudioMixdown"]];
249 [newAudio setSampleRateFromName: [dict objectForKey: @"AudioSamplerate"]];
250 [newAudio setBitRateFromName: [dict objectForKey: @"AudioBitrate"]];
251 [newAudio setDrc: [dict objectForKey: @"AudioTrackDRCSlider"]];
254 [self removeObjectFromAudioArrayAtIndex: [self countOfAudioArray] - 1];
261 - (void) addTracksFromPreset: (NSMutableDictionary *) aPreset
264 id whatToUse = [self _presetAudioArrayFromPreset: aPreset];
266 // Reinitialize the configured list of audio tracks
267 [audioArray release];
268 audioArray = [[NSMutableArray alloc] init];
270 [self _processPresetAudioArray: whatToUse forTrack: 1 andType: [[aPreset objectForKey: @"Type"] intValue]];
272 [self switchingTrackFromNone: nil]; // see if we need to add one to the list
277 - (void) addAllTracksFromPreset: (NSMutableDictionary *) aPreset
280 id whatToUse = [self _presetAudioArrayFromPreset: aPreset];
282 // Reinitialize the configured list of audio tracks
283 [audioArray release];
284 audioArray = [[NSMutableArray alloc] init];
286 for (unsigned int i = 1; i < [masterTrackArray count]; i++) {
287 [self _processPresetAudioArray: whatToUse forTrack: i andType: [[aPreset objectForKey: @"Type"] intValue]];
290 [self switchingTrackFromNone: nil]; // see if we need to add one to the list
296 - (BOOL) anyCodecMatches: (int) aCodecValue
300 unsigned int audioArrayCount = [self countOfAudioArray];
301 for (unsigned int i = 0; i < audioArrayCount && NO == retval; i++) {
302 HBAudio *anAudio = [self objectInAudioArrayAtIndex: i];
303 if (YES == [anAudio enabled] && aCodecValue == [[[anAudio codec] objectForKey: keyAudioCodec] intValue]) {
310 - (void) addNewAudioTrack
313 HBAudio *newAudio = [[HBAudio alloc] init];
314 [newAudio setController: self];
315 [self insertObject: newAudio inAudioArrayAtIndex: [self countOfAudioArray]];
316 [newAudio setVideoContainerTag: [self videoContainerTag]];
317 [newAudio setTrack: noneTrack];
318 [newAudio setDrc: [NSNumber numberWithFloat: 0.0]];
324 #pragma mark Notification Handling
326 - (void) settingTrackToNone: (HBAudio *) newNoneTrack
329 // If this is not the last track in the array we need to remove it. We then need to see if a new
330 // one needs to be added (in the case when we were at maximum count and this switching makes it
331 // so we are no longer at maximum.
332 unsigned int index = [audioArray indexOfObject: newNoneTrack];
334 if (NSNotFound != index && index < [self countOfAudioArray] - 1) {
335 [self removeObjectFromAudioArrayAtIndex: index];
337 [self switchingTrackFromNone: nil]; // see if we need to add one to the list
341 - (void) switchingTrackFromNone: (HBAudio *) noLongerNoneTrack
344 int count = [self countOfAudioArray];
346 int maximumNumberOfAllowedAudioTracks = [HBController maximumNumberOfAllowedAudioTracks];
348 // If there is no last track that is None and we are less than our maximum number of permitted tracks, we add one.
349 if (count < maximumNumberOfAllowedAudioTracks) {
351 HBAudio *lastAudio = [self objectInAudioArrayAtIndex: count - 1];
352 if (YES == [lastAudio enabled]) {
361 if (YES == needToAdd) {
362 [self addNewAudioTrack];
367 // This gets called whenever the video container changes.
368 - (void) containerChanged: (NSNotification *) aNotification
371 NSDictionary *notDict = [aNotification userInfo];
373 [self setVideoContainerTag: [notDict objectForKey: keyContainerTag]];
375 // Update each of the instances because this value influences possible settings.
376 NSEnumerator *enumerator = [audioArray objectEnumerator];
377 HBAudio *audioObject;
379 while (nil != (audioObject = [enumerator nextObject])) {
380 [audioObject setVideoContainerTag: [self videoContainerTag]];
385 - (void) titleChanged: (NSNotification *) aNotification
388 NSDictionary *notDict = [aNotification userInfo];
389 NSData *theData = [notDict objectForKey: keyTitleTag];
390 hb_title_t *title = NULL;
392 [theData getBytes: &title length: sizeof(title)];
394 hb_audio_config_t *audio;
395 hb_list_t *list = title->list_audio;
396 int i, count = hb_list_count(list);
398 // Reinitialize the master list of available audio tracks from this title
399 [masterTrackArray release];
400 masterTrackArray = [[NSMutableArray alloc] init];
402 noneTrack = [[NSDictionary dictionaryWithObjectsAndKeys:
403 [NSNumber numberWithInt: 0], keyAudioTrackIndex,
404 NSLocalizedString(@"None", @"None"), keyAudioTrackName,
405 [NSNumber numberWithInt: 0], keyAudioInputCodec,
407 [masterTrackArray addObject: noneTrack];
408 for (i = 0; i < count; i++) {
409 audio = (hb_audio_config_t *) hb_list_audio_config_item(list, i);
410 [masterTrackArray addObject: [NSDictionary dictionaryWithObjectsAndKeys:
411 [NSNumber numberWithInt: i + 1], keyAudioTrackIndex,
412 [NSString stringWithFormat: @"%d: %s", i, audio->lang.description], keyAudioTrackName,
413 [NSNumber numberWithInt: audio->in.bitrate / 1000], keyAudioInputBitrate,
414 [NSNumber numberWithInt: audio->in.samplerate], keyAudioInputSampleRate,
415 [NSNumber numberWithInt: audio->in.codec], keyAudioInputCodec,
416 [NSNumber numberWithInt: audio->in.channel_layout], keyAudioInputChannelLayout,
421 // Reinitialize the configured list of audio tracks
422 [audioArray release];
423 audioArray = [[NSMutableArray alloc] init];
431 - (unsigned int) countOfAudioArray
434 return [audioArray count];
437 - (HBAudio *) objectInAudioArrayAtIndex: (unsigned int) index
440 return [audioArray objectAtIndex: index];
443 - (void) insertObject: (HBAudio *) audioObject inAudioArrayAtIndex: (unsigned int) index;
446 [audioArray insertObject: audioObject atIndex: index];
450 - (void) removeObjectFromAudioArrayAtIndex: (unsigned int) index
453 [audioArray removeObjectAtIndex: index];