точное время с AVMutableComposition

Я пытаюсь использовать AVMutableComposition для воспроизведения последовательности звуковых файлов в определенное время.

Когда представление загружается, я создаю композицию с намерением воспроизвести 4 звука, равномерно распределенные в течение 1 секунды. Неважно, насколько длинные или короткие звуки, я просто хочу запустить их ровно через 0, 0,25, 0,5 и 0,75 секунды:

AVMutableComposition *composition = [[AVMutableComposition alloc] init];
NSDictionary *options = @{AVURLAssetPreferPreciseDurationAndTimingKey : @YES};

for (NSInteger i = 0; i < 4; i++)
{
  AVMutableCompositionTrack* track = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
  NSURL *url = [[NSBundle mainBundle] URLForResource:[NSString stringWithFormat:@"sound_file_%i", i] withExtension:@"caf"];
  AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:options];
  AVAssetTrack *assetTrack = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject;
  CMTimeRange timeRange = [assetTrack timeRange];
  Float64 t = i * 0.25;
  NSError *error;
  BOOL success = [track insertTimeRange:timeRange ofTrack:assetTrack atTime:CMTimeMakeWithSeconds(t, 1) error:&error];
  if (!success)
  {
    NSLog(@"unsuccesful creation of composition");
  }
  if (error)
  {
    NSLog(@"composition creation error: %@", error);
  }
}

AVPlayerItem* playerItem = [AVPlayerItem playerItemWithAsset:composition];
self.avPlayer = [[AVPlayer alloc] initWithPlayerItem:playerItem];

Композиция создана успешно, без ошибок. Позже, когда я хочу воспроизвести последовательность, я делаю следующее:

[self.avPlayer seekToTime:CMTimeMakeWithSeconds(0, 1)];
[self.avPlayer play];

По какой-то причине звуки распределены неравномерно - а воспроизводятся почти все одновременно. Я попробовал то же самое с интервалом более 4 секунд, заменив расчет времени следующим образом:

Float64 t = i * 1.0;

И это прекрасно играет. Кажется, что любой временной интервал менее 1 секунды дает неожиданные результаты. Что мне не хватает? Разве AVCompositions нельзя использовать для интервалов времени менее 1 секунды? Или, может быть, я неправильно понимаю временные интервалы?


person sabajt    schedule 21.08.2014    source источник


Ответы (3)


Ваш CMTimeMakeWithSeconds(t, 1) находится в целых секундах «срезов», потому что ваша шкала времени установлена ​​на 1. Независимо от того, какая дробь t, atTime: всегда будет равняться 0. Вот почему это работает, когда вы увеличиваете его до 1 секунды (t = i * 1).

Вам нужно установить шкалу времени на 4, чтобы получить желаемые 0,25-секундные фрагменты. Поскольку CMTime теперь находится в 0,25-секундных срезах, вычисление i * 0,25 вам не понадобится. Просто используйте i напрямую; atTime:CMTimeMake(i, 4)

Если вам может потребоваться уточнение в будущем, вы должны учесть это сейчас, чтобы вам не пришлось корректировать код позже. Apple рекомендует использовать шкалу времени 600, поскольку она кратна обычной частоте кадров видео (24, 25 и 30 кадров в секунду), но она также отлично работает и для аудио. Итак, для вашей ситуации вы должны использовать 24 среза, чтобы получить значение 0,25 секунды; Float64 t = i * 24; atTime:CMTimeMake(t, 600)

Что касается вашей проблемы, когда все 4 звука воспроизводятся почти все одновременно, имейте в виду этот вопрос SO без ответа, где это происходит только при первом воспроизведении. Даже с внесенными выше изменениями вы все равно можете столкнуться с этой проблемой.

person Insomniac Software    schedule 27.08.2014
comment
Спасибо, это то, что мне не хватало. Одно исправление: CMTimeMakeWithSeconds должен быть просто CMTimeMake. Это помогло мне понять разницу. - person sabajt; 28.08.2014

Если каждый трек длится ровно 0,25 секунды, это ваша проблема:

Float64 t = i * 0.25;
NSError *error;
BOOL success = [track insertTimeRange:timeRange ofTrack:assetTrack atTime:CMTimeMakeWithSeconds(t, 1) error:&error];

Вам нужно отслеживать совокупный временной диапазон, добавленный на данный момент, и вставлять следующий трек в это время:

CMTime currentTime = kCMTimeZero;

for (NSInteger i = 0; i < 4; i++) {

   /* Code to create track for insertion */

    CMTimeRange trackTimeRange = [assetTrack timeRange];

    BOOL success = [track insertTimeRange:trackTimeRange
                                  ofTrack:assetTrack 
                                    atTime:currentTime
                                      error:&error];

    /* Error checking code */

    //Update time range for insertion
    currentTime = CMTimeAdd(currentTime,trackTimeRange.duration);
}
person ChrisH    schedule 25.08.2014
comment
Спасибо за ответ. Одно исправление в вашем коде, trackTimeRange, должно быть trackTimeRange.duration в вычислении currentTimeRange (должно быть CCTime). Однако на самом деле это не то, что я пытаюсь сделать: я хочу воспроизвести сэмплы ровно через 0, 0,25, 0,5 и 0,75 секунды, независимо от их продолжительности. Они могут перекрываться, или между ними может быть пространство. Ваше решение воспроизводит звуки последовательно, но ожидает окончания каждого из них, прежде чем запускать следующий. - person sabajt; 27.08.2014
comment
Ах да, извините ... Я также переименовал currentTimeRange в currentTime, поскольку это, конечно, объект CMTime. Судя по вашему обновленному вопросу, ответ будет более сложным. Выложу обновленное решение. - person ChrisH; 27.08.2014

Я немного изменил ваш код, извините, у меня не было времени его протестировать.

   AVMutableComposition *composition = [AVMutableComposition composition];
    NSDictionary *options = @{AVURLAssetPreferPreciseDurationAndTimingKey : @YES};

    CMTime totalDuration = kCMTimeZero;

    for (NSInteger i = 0; i < 4; i++)
    {
        AVMutableCompositionTrack* track = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];


        NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"Record_%i", i] ofType:@"caf"]];
        AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:url options:options];
        AVAssetTrack *assetTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
        CMTimeRange timeRange = [assetTrack timeRange];
        NSError *error;

        BOOL success = [track insertTimeRange:timeRange ofTrack:assetTrack atTime:CMTIME_COMPARE_INLINE(totalDuration, >, kCMTimeZero)? CMTimeAdd(totalDuration, CMTimeMake(1, 4)): totalDuration error:&error];

        if (!success)
        {
            NSLog(@"unsuccesful creation of composition");
        }
        if (error)
        {
            NSLog(@"composition creation error: %@", error);
        }
        totalDuration = CMTimeAdd(CMTimeAdd(totalDuration,CMTimeMake(1, 4)), asset.duration);
    }

    AVPlayerItem* playerItem = [AVPlayerItem playerItemWithAsset:composition];
    self.avPlayer = [[AVPlayer alloc] initWithPlayerItem:playerItem];

P.S. используйте kCMTimeZero вместо CMTimeMakeWithSeconds(0, 1).

person DimaC    schedule 21.08.2014
comment
Не получается создать композицию. AVFoundationErrorDomain Code = -11800 - person sabajt; 21.08.2014
comment
И расчет времени здесь неверен. Для индексов с 1 по 3 CMTime равен (значение = 0, шкала времени = 0, флаги = 0, эпоха = 0), что неверно. - person sabajt; 21.08.2014