/* HBAudioTrack.m $
This file is part of the HandBrake source code.
Homepage: .
It may be used under the terms of the GNU General Public License. */
#import "HBAudioTrack.h"
#import "HBAudioController.h"
#import "HBJob.h"
#import "HBCodingUtilities.h"
#import "hb.h"
NSString *keyAudioTrackIndex = @"keyAudioTrackIndex";
NSString *keyAudioTrackName = @"keyAudioTrackName";
NSString *keyAudioInputBitrate = @"keyAudioInputBitrate";
NSString *keyAudioInputSampleRate = @"keyAudioInputSampleRate";
NSString *keyAudioInputCodec = @"keyAudioInputCodec";
NSString *keyAudioInputCodecParam = @"keyAudioInputCodecParam";
NSString *keyAudioInputChannelLayout = @"keyAudioInputChannelLayout";
NSString *keyAudioTrackLanguageIsoCode = @"keyAudioTrackLanguageIsoCode";
#define DEFAULT_SAMPLERATE 48000
@interface HBAudioTrack ()
@property (nonatomic, readwrite) BOOL validating;
@end
@implementation HBAudioTrack
- (instancetype)init
{
self = [super init];
if (self)
{
// Defaults settings
_encoder = HB_ACODEC_CA_AAC;
_container = HB_MUX_MKV;
_sampleRate = 0;
_bitRate = 160;
_mixdown = HB_AMIXDOWN_DOLBYPLII;
}
return self;
}
- (instancetype)initWithTrackIdx:(NSUInteger)index
container:(int)container
dataSource:(id)dataSource
delegate:(id)delegate;
{
self = [super init];
if (self)
{
_dataSource = dataSource;
_sourceTrackIdx = index;
_container = container;
[self validateSettings];
_delegate = delegate;
}
return self;
}
- (void)validateSettings
{
if (_sourceTrackIdx)
{
if (self.encoder == 0)
{
self.encoder = HB_ACODEC_CA_AAC;
self.bitRate = 160;
}
else
{
self.encoder = [self sanatizeEncoderValue:self.encoder];
}
}
else
{
self.encoder = 0;
self.mixdown = 0;
self.sampleRate = 0;
self.bitRate = -1;
}
}
#pragma mark - Track properties
- (void)setSourceTrackIdx:(NSUInteger)sourceTrackIdx
{
if (sourceTrackIdx != _sourceTrackIdx)
{
[[self.undo prepareWithInvocationTarget:self] setSourceTrackIdx:_sourceTrackIdx];
}
NSUInteger oldIdx = _sourceTrackIdx;
_sourceTrackIdx = sourceTrackIdx;
if (!(self.undo.isUndoing || self.undo.isRedoing))
{
[self validateSettings];
if (oldIdx != sourceTrackIdx)
{
[self.delegate track:self didChangeSourceFrom:oldIdx];
}
}
}
- (void)setContainer:(int)container
{
if (container != _container)
{
[[self.undo prepareWithInvocationTarget:self] setContainer:_container];
}
_container = container;
if (!(self.undo.isUndoing || self.undo.isRedoing))
{
if (self.encoder)
{
self.encoder = [self sanatizeEncoderValue:self.encoder];
}
}
}
- (void)setEncoder:(int)encoder
{
if (encoder != _encoder)
{
[[self.undo prepareWithInvocationTarget:self] setEncoder:_encoder];
}
_encoder = encoder;
if (!(self.undo.isUndoing || self.undo.isRedoing) && !self.validating)
{
self.validating = YES;
self.mixdown = [self sanatizeMixdownValue:self.mixdown];
self.sampleRate = [self sanatizeSamplerateValue:self.sampleRate];
self.bitRate = [self sanatizeBitrateValue:self.bitRate];
[self.delegate encoderChanged];
self.validating = NO;
}
}
- (void)setMixdown:(int)mixdown
{
if (mixdown != _mixdown)
{
[[self.undo prepareWithInvocationTarget:self] setMixdown:_mixdown];
}
_mixdown = mixdown;
if (!(self.undo.isUndoing || self.undo.isRedoing) && !self.validating)
{
self.validating = YES;
self.bitRate = [self sanatizeBitrateValue:self.bitRate];
self.validating = NO;
}
}
- (void)setSampleRate:(int)sampleRate
{
if (sampleRate != _sampleRate)
{
[[self.undo prepareWithInvocationTarget:self] setSampleRate:_sampleRate];
}
_sampleRate = sampleRate;
if (!(self.undo.isUndoing || self.undo.isRedoing) && !self.validating)
{
self.validating = YES;
self.bitRate = [self sanatizeBitrateValue:self.bitRate];
self.validating = NO;
}
}
- (void)setBitRate:(int)bitRate
{
if (bitRate != _bitRate)
{
[[self.undo prepareWithInvocationTarget:self] setBitRate:_bitRate];
}
_bitRate = bitRate;
}
- (void)setGain:(double)gain
{
if (gain != _gain)
{
[[self.undo prepareWithInvocationTarget:self] setGain:_gain];
}
_gain = gain;
}
// Because we have indicated that the binding for the gain validates immediately we can implement the
// key value binding method to ensure the gain stays in our accepted range.
- (BOOL)validateGain:(id *)ioValue error:(NSError * __autoreleasing *)outError
{
BOOL retval = YES;
if (nil != *ioValue)
{
if ([*ioValue intValue] < -20)
{
*ioValue = @(-20);
}
else if ([*ioValue intValue] > 20)
{
*ioValue = @20;
}
}
return retval;
}
- (void)setDrc:(double)drc
{
if (drc != _drc)
{
[[self.undo prepareWithInvocationTarget:self] setDrc:_drc];
}
_drc = drc;
}
#pragma mark - Validation
- (int)sanatizeEncoderValue:(int)proposedEncoder
{
if (proposedEncoder)
{
NSDictionary *sourceTrack = [_dataSource sourceTrackAtIndex:_sourceTrackIdx];
int inputCodec = [sourceTrack[keyAudioInputCodec] intValue];
hb_encoder_t *proposedEncoderInfo = hb_audio_encoder_get_from_codec(proposedEncoder);
if (proposedEncoderInfo && proposedEncoderInfo->muxers & self.container)
{
// If the codec is passthru, see if the new source supports it.
if (proposedEncoderInfo->codec & HB_ACODEC_PASS_FLAG)
{
if ((proposedEncoderInfo->codec & inputCodec & HB_ACODEC_PASS_MASK))
{
return proposedEncoder;
}
}
else
{
return proposedEncoder;
}
}
return hb_audio_encoder_get_default(self.container);
}
else
{
return proposedEncoder;
}
}
- (int)sanatizeMixdownValue:(int)proposedMixdown
{
NSDictionary *sourceTrack = [_dataSource sourceTrackAtIndex:_sourceTrackIdx];
unsigned long long channelLayout = [sourceTrack[keyAudioInputChannelLayout] unsignedLongLongValue];
if (!hb_mixdown_is_supported(proposedMixdown, self.encoder, channelLayout))
{
return hb_mixdown_get_default(self.encoder, channelLayout);
}
return proposedMixdown;
}
- (int)sanatizeSamplerateValue:(int)proposedSamplerate
{
if (self.encoder & HB_ACODEC_PASS_FLAG)
{
return 0; // Auto (same as source)
}
else if (proposedSamplerate)
{
return hb_audio_samplerate_find_closest(proposedSamplerate, self.encoder);
}
return proposedSamplerate;
}
- (int)sanatizeBitrateValue:(int)proposedBitrate
{
if (self.encoder & HB_ACODEC_PASS_FLAG)
{
return -1;
}
else if (proposedBitrate == -1) // switching from passthru
{
return hb_audio_bitrate_get_default(self.encoder,
self.sampleRate ? self.sampleRate : DEFAULT_SAMPLERATE,
self.mixdown);
}
else
{
return hb_audio_bitrate_get_best(self.encoder, proposedBitrate, self.sampleRate, self.mixdown);
}
}
#pragma mark - Options
- (NSArray *)encoders
{
NSMutableArray *encoders = [[NSMutableArray alloc] init];
NSDictionary *sourceTrack = [_dataSource sourceTrackAtIndex:_sourceTrackIdx];
int inputCodec = [sourceTrack[keyAudioInputCodec] intValue];
for (const hb_encoder_t *audio_encoder = hb_audio_encoder_get_next(NULL);
audio_encoder != NULL;
audio_encoder = hb_audio_encoder_get_next(audio_encoder))
{
if (audio_encoder->muxers & self.container &&
audio_encoder->codec != HB_ACODEC_AUTO_PASS)
{
if (audio_encoder->codec & HB_ACODEC_PASS_FLAG)
{
// If the codec is passthru, show only the supported ones.
if ((audio_encoder->codec & inputCodec & HB_ACODEC_PASS_MASK))
{
[encoders addObject:@(audio_encoder->name)];
}
}
else
{
[encoders addObject:@(audio_encoder->name)];
}
}
}
return encoders;
}
- (NSArray *)mixdowns
{
NSMutableArray *mixdowns = [[NSMutableArray alloc] init];
NSDictionary *sourceTrack = [_dataSource sourceTrackAtIndex:_sourceTrackIdx];
unsigned long long channelLayout = [sourceTrack[keyAudioInputChannelLayout] unsignedLongLongValue];
for (const hb_mixdown_t *mixdown = hb_mixdown_get_next(NULL);
mixdown != NULL;
mixdown = hb_mixdown_get_next(mixdown))
{
if (hb_mixdown_is_supported(mixdown->amixdown, self.encoder, channelLayout))
{
[mixdowns addObject:@(mixdown->name)];
}
}
return mixdowns;
}
- (NSArray *)sampleRates
{
NSMutableArray *sampleRates = [[NSMutableArray alloc] init];
[sampleRates addObject:@"Auto"];
for (const hb_rate_t *audio_samplerate = hb_audio_samplerate_get_next(NULL);
audio_samplerate != NULL;
audio_samplerate = hb_audio_samplerate_get_next(audio_samplerate))
{
int rate = audio_samplerate->rate;
if (rate == hb_audio_samplerate_find_closest(rate, self.encoder))
{
[sampleRates addObject:@(audio_samplerate->name)];
}
}
return sampleRates;
}
- (NSArray *)bitRates
{
int minBitRate = 0;
int maxBitRate = 0;
hb_audio_bitrate_get_limits(self.encoder, self.sampleRate, self.mixdown, &minBitRate, &maxBitRate);
NSMutableArray *bitRates = [[NSMutableArray alloc] init];
for (const hb_rate_t *audio_bitrate = hb_audio_bitrate_get_next(NULL);
audio_bitrate != NULL;
audio_bitrate = hb_audio_bitrate_get_next(audio_bitrate))
{
if (audio_bitrate->rate >= minBitRate && audio_bitrate->rate <= maxBitRate)
{
[bitRates addObject:@(audio_bitrate->name)];
}
}
return bitRates;
}
#pragma mark - KVO UI Additions
- (NSArray *)sourceTracksArray
{
return [self.dataSource sourceTracksArray];
}
- (BOOL)isEnabled
{
return self.sourceTrackIdx != 0;
}
- (BOOL)mixdownEnabled
{
BOOL retval = self.isEnabled;
if (retval && self.mixdown == HB_AMIXDOWN_NONE)
{
// "None" mixdown (passthru)
retval = NO;
}
return retval;
}
- (BOOL)bitrateEnabled
{
BOOL retval = self.isEnabled;
if (retval)
{
int myCodecDefaultBitrate = hb_audio_bitrate_get_default(self.encoder, 0, 0);
if (myCodecDefaultBitrate < 0)
{
retval = NO;
}
}
return retval;
}
- (BOOL)drcEnabled
{
BOOL retval = self.isEnabled;
if (retval)
{
NSDictionary *sourceTrack = [_dataSource sourceTrackAtIndex:_sourceTrackIdx];
int inputCodec = [sourceTrack[keyAudioInputCodec] intValue];
int inputCodecParam = [sourceTrack[keyAudioInputCodecParam] intValue];
if (!hb_audio_can_apply_drc(inputCodec, inputCodecParam, self.encoder))
{
retval = NO;
}
}
return retval;
}
- (BOOL)passThruDisabled
{
BOOL retval = YES;
if (self.encoder & HB_ACODEC_PASS_FLAG)
{
retval = NO;
}
return retval;
}
#pragma mark - KVO
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
NSSet *retval = nil;
if ([key isEqualToString:@"bitrateEnabled"] ||
[key isEqualToString:@"passThruDisabled"] ||
[key isEqualToString:@"mixdownEnabled"])
{
retval = [NSSet setWithObjects:@"encoder", nil];
}
else if ([key isEqualToString:@"mixdowns"] ||
[key isEqualToString:@"drcEnabled"])
{
retval = [NSSet setWithObjects:@"sourceTrackIdx", @"encoder", nil];
}
else if ([key isEqualToString:@"sampleRates"])
{
retval = [NSSet setWithObjects:@"encoder", @"mixdown", nil];
}
else if ([key isEqualToString:@"bitRates"])
{
retval = [NSSet setWithObjects:@"encoder", @"mixdown", @"sampleRate", nil];
}
else if ([key isEqualToString:@"encoders"])
{
retval = [NSSet setWithObjects:@"container", @"sourceTrackIdx", nil];
}
else
{
retval = [NSSet set];
}
return retval;
}
- (void)setNilValueForKey:(NSString *)key
{
if ([key isEqualToString:@"drc"] || [key isEqualToString:@"gain"])
{
[self setValue:@0 forKey:key];
}
}
#pragma mark - NSCopying
- (instancetype)copyWithZone:(NSZone *)zone
{
HBAudioTrack *copy = [[[self class] alloc] init];
if (copy)
{
copy->_sourceTrackIdx = _sourceTrackIdx;
copy->_container = _container;
copy->_encoder = _encoder;
copy->_mixdown = _mixdown;
copy->_sampleRate = _sampleRate;
copy->_bitRate = _bitRate;
copy->_gain = _gain;
copy->_drc = _drc;
}
return copy;
}
#pragma mark - NSCoding
+ (BOOL)supportsSecureCoding
{
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeInt:3 forKey:@"HBAudioTrackVersion"];
encodeInteger(_sourceTrackIdx);
encodeInt(_container);
encodeInt(_encoder);
encodeInt(_mixdown);
encodeInt(_sampleRate);
encodeInt(_bitRate);
encodeDouble(_gain);
encodeDouble(_drc);
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [super init];
decodeInteger(_sourceTrackIdx); if (_sourceTrackIdx < 0) { goto fail; }
decodeInt(_container); if (_container != HB_MUX_MP4 && _container != HB_MUX_MKV && _container != HB_MUX_WEBM) { goto fail; }
decodeInt(_encoder); if (_encoder < 0) { goto fail; }
decodeInt(_mixdown); if (_mixdown < 0) { goto fail; }
decodeInt(_sampleRate); if (_sampleRate < 0) { goto fail; }
decodeInt(_bitRate); if (_bitRate < -1) { goto fail; }
decodeDouble(_gain);
decodeDouble(_drc);
return self;
fail:
return nil;
}
@end