AVMutableComposition的精确定时

6
我正在尝试使用AVMutableComposition在精确的时间播放一系列音频文件。
当视图加载时,我创建了一个组合,以便在1秒内平均播放4个声音。声音的长度不重要,我只想在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秒的时间间隔似乎都会产生意想不到的结果。我错过了什么吗?AVComposition不应该用于小于1秒的时间间隔吗?或者我可能误解了时间间隔的含义?

3个回答

2
您的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) 如果您未来可能需要更精确,请现在考虑它,这样您就不必后来调整代码了。苹果建议使用600的时间刻度,因为它是常见视频帧速率(24、25和30 FPS)的倍数,但对仅音频也可以使用。因此,对于您的情况,您将使用24个“片段”来获得您的0.25秒值;Float64 t = i * 24; atTime:CMTimeMake(t, 600) 至于您所有4个声音几乎同时播放的问题,请注意这个未回答的SO问题,其中仅在第一次播放时发生。即使进行了上述更改,您仍可能遇到此问题。

1
谢谢,这就是我所缺少的。这里有一个更正:CMTimeMakeWithSeconds应该改为CMTimeMake。这篇文章帮助我理解了它们之间的区别 - sabajt

1
除非每个轨道的长度恰好为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);
}

谢谢你的回答。你的代码有一个小错误,trackTimeRange在计算currentTimeRange时需要使用trackTimeRange.duration(需要是CCTime类型)。但这实际上不是我想要做的:我想在恰好0、0.25、0.5和0.75秒播放样本,而不考虑它们的持续时间。它们可能重叠,也可能之间有空隙。你的解决方案按顺序播放声音,但会等待每个声音播放完毕后才播放下一个。 - sabajt
啊,对不起...我还将currentTimeRange重命名为currentTime,因为它实际上是一个CMTime对象。根据您更新的问题,答案要复杂得多。我会发布一个更新的解决方案。 - ChrisH

0

我稍微改了一下你的代码,抱歉我没时间测试它。

   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];

提示:使用kCMTimeZero代替CMTimeMakeWithSeconds(0, 1)


这并不能成功创建一个合成。AVFoundationErrorDomain Code=-11800。 - sabajt
这里的时间计算有误。对于索引1到3,CMTime的值为(value = 0,timescale = 0,flags = 0,epoch = 0),这不是正确的结果。 - sabajt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接