【问题标题】:iPhone: Mix two audio files programmatically?iPhone:以编程方式混合两个音频文件?
【发布时间】:2012-01-28 03:38:56
【问题描述】:

我想要两个音频文件并以编程方式混合和播放。当我播放第一个音频文件时,经过一段时间(动态时间),当第一个音频文件的中间位置正在播放时,我需要添加第二个小音频文件和第一个音频文件,最后我需要保存为一个音频设备上的文件。它应该使用我包含的第二个混音器音频播放音频文件。

我浏览了很多论坛,但不知道具体如何实现这一点?

有人可以澄清我的以下疑问吗?

  1. 在这种情况下,我应该使用什么音频文件/格式?我可以使用 .avi 文件吗?
  2. 如何以编程方式将设置的动态时间后的第二个音频添加到第一个音频文件中?例如:如果第一个音频总时间是 2 分钟,我可能需要在第一个文件的 1 分钟或 1.5 分钟或 55 秒内混合第二个音频文件(3 秒音频)。它的动态。
  3. 如何将最终输出的音频文件保存在设备上?如果我以编程方式将音频文件保存在某处,我可以再次播放吗?

我不知道如何实现这一点。请提出您的想法!

【问题讨论】:

  • 不,您不能将其保存为 .avi,因为 avi 只是一个容器(也可以包含视频)。我不确定你必须在 iOS 设备上使用什么,但我想你必须编写一个 wav 文档(换句话说,纯音频峰值/波)。要结合这些,您需要具备我没有的高级音频处理知识。所以我不能说一些有关这方面的信息。

标签: iphone ipad audio mixing


【解决方案1】:

你好,你可以通过使用 av 基础来做到这一点

- (BOOL) combineVoices1
{
    NSError *error = nil;
    BOOL ok = NO;


    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,    NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];


    CMTime nextClipStartTime = kCMTimeZero;
    //Create AVMutableComposition Object.This object will hold our multiple AVMutableCompositionTrack.
    AVMutableComposition *composition = [[AVMutableComposition alloc] init];

    AVMutableCompositionTrack *compositionAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    [compositionAudioTrack setPreferredVolume:0.8];
    NSString *soundOne  =[[NSBundle mainBundle]pathForResource:@"test1" ofType:@"caf"];
    NSURL *url = [NSURL fileURLWithPath:soundOne];
    AVAsset *avAsset = [AVURLAsset URLAssetWithURL:url options:nil];
    NSArray *tracks = [avAsset tracksWithMediaType:AVMediaTypeAudio];
    AVAssetTrack *clipAudioTrack = [[avAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
    [compositionAudioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, avAsset.duration) ofTrack:clipAudioTrack atTime:kCMTimeZero error:nil];

    AVMutableCompositionTrack *compositionAudioTrack1 = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    [compositionAudioTrack setPreferredVolume:0.3];
    NSString *soundOne1  =[[NSBundle mainBundle]pathForResource:@"test" ofType:@"caf"];
    NSURL *url1 = [NSURL fileURLWithPath:soundOne1];
    AVAsset *avAsset1 = [AVURLAsset URLAssetWithURL:url1 options:nil];
    NSArray *tracks1 = [avAsset1 tracksWithMediaType:AVMediaTypeAudio];
    AVAssetTrack *clipAudioTrack1 = [[avAsset1 tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
    [compositionAudioTrack1 insertTimeRange:CMTimeRangeMake(kCMTimeZero, avAsset.duration) ofTrack:clipAudioTrack1 atTime:kCMTimeZero error:nil];


    AVMutableCompositionTrack *compositionAudioTrack2 = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    [compositionAudioTrack2 setPreferredVolume:1.0];
    NSString *soundOne2  =[[NSBundle mainBundle]pathForResource:@"song" ofType:@"caf"];
    NSURL *url2 = [NSURL fileURLWithPath:soundOne2];
    AVAsset *avAsset2 = [AVURLAsset URLAssetWithURL:url2 options:nil];
    NSArray *tracks2 = [avAsset2 tracksWithMediaType:AVMediaTypeAudio];
    AVAssetTrack *clipAudioTrack2 = [[avAsset2 tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
    [compositionAudioTrack1 insertTimeRange:CMTimeRangeMake(kCMTimeZero, avAsset2.duration) ofTrack:clipAudioTrack2 atTime:kCMTimeZero error:nil];



    AVAssetExportSession *exportSession = [AVAssetExportSession
                                           exportSessionWithAsset:composition
                                           presetName:AVAssetExportPresetAppleM4A];
    if (nil == exportSession) return NO;

    NSString *soundOneNew = [documentsDirectory stringByAppendingPathComponent:@"combined10.m4a"];
    //NSLog(@"Output file path - %@",soundOneNew);

    // configure export session  output with all our parameters
    exportSession.outputURL = [NSURL fileURLWithPath:soundOneNew]; // output path
    exportSession.outputFileType = AVFileTypeAppleM4A; // output file type

    // perform the export
    [exportSession exportAsynchronouslyWithCompletionHandler:^{

        if (AVAssetExportSessionStatusCompleted == exportSession.status) {
            NSLog(@"AVAssetExportSessionStatusCompleted");
        } else if (AVAssetExportSessionStatusFailed == exportSession.status) {
            // a failure may happen because of an event out of your control
            // for example, an interruption like a phone call comming in
            // make sure and handle this case appropriately
            NSLog(@"AVAssetExportSessionStatusFailed");
        } else {
            NSLog(@"Export Session Status: %d", exportSession.status);
        }
    }];


    return YES;


}

【讨论】:

    【解决方案2】:

    在这种情况下,我应该使用什么音频文件/格式?我可以使用 .avi 文件吗?

    您可以选择压缩或非压缩格式。常见的非压缩格式包括 Wav 和 AIFF。 CAF 可以表示压缩和非压缩数据。 .avi 不是一个选项(由操作系统提供)。

    如果文件很大并且需要考虑存储空间(在磁盘上),您可以考虑将 AAC 格式保存在 CAF(或简称为 .m4a)中。对于大多数应用程序,16 位样本就足够了,您还可以通过以适当的采样率保存这些文件来节省空间、内存和 CPU(参考:CD 为 44.1kHz)。

    由于 ExtAudioFile 接口抽象了转换过程,因此您不必更改程序即可比较压缩和非压缩格式的大小和速度差异以供您分发(CAF 中的 AAC 对于普通应用程序来说很好)。

    非压缩 CD 质量的音频每分钟将消耗大约 5.3 MB,每个通道。因此,如果您有 2 个立体声音频文件,每个 3 分钟长,以及 3 分钟的目标缓冲区,那么您的内存需求将约为 50 MB。

    由于您有“分钟”的音频,您可能需要考虑避免一次将所有音频数据加载到内存中。为了读取、操作和组合音频,您需要在内存中使用未压缩的表示形式,因此压缩格式在这里无济于事。同样,将压缩表示转换为 pcm 需要大量资源。读取压缩文件虽然字节数更少,但可能需要更多(或更少)时间。

    如何以编程方式将设置的动态时间后的第二个音频添加到第一个音频文件中?例如:如果第一个音频总时间为 2 分钟,我可能需要在第一个文件的 1 分钟或 1.5 分钟或 55 秒内混合第二个音频文件(3 秒音频)。它的动态。

    要读取文件并将它们转换为您想要使用的格式,请使用 ExtAudioFile API - 这将为您转换为您的目标样本格式。内存中常见的 PCM 样本表示形式包括 SInt32SInt16float,但根据应用程序和硬件(iOS 之外)的不同,它们可能会有很大差异。如果需要,ExtAudioFile API 还会将压缩格式转换为 PCM。

    您的输入音频文件应具有相同的采样率。如果没有,您将不得不重新采样音频,这是一个复杂的过程,也需要大量资源(如果正确/准确地完成)。如果您需要支持重采样,请将分配给完成此任务的时间加倍(此处不详述流程)。

    要添加声音,您需要从文件中请求 PCM 样本,处理并写入输出文件(或内存中的缓冲区)。

    要确定何时添加其他声音,您需要获取输入文件的采样率(通过 ExtAudioFileGetProperty)。如果您想在 55 秒时将第二个声音写入目标缓冲区,那么您将开始添加样本编号为 SampleRate * 55 的声音,其中 SampleRate 是您正在读取的文件的采样率。

    要混合音频,您只需使用这种形式(伪代码):

    mixed[i] = fileA[i] + fileB[i];
    

    但您必须确保避免上溢/下溢和其他算术错误。通常,您将使用一些整数值执行此过程,因为浮点计算可能需要很长时间(当有这么多时)。对于某些应用程序,您可以只移动和添加而不必担心溢出 - 这将有效地将每个输入减少一半,然后再添加它们。结果的幅度将是二分之一。如果您可以控制文件的内容(例如,它们都被捆绑为资源),那么您可以简单地确保文件中的峰值样本没有超过满量程值的一半(大约 -6dBFS)。当然,保存为浮点数会以引入更高的 CPU、内存和文件 i/o 需求为代价来解决这个问题。

    此时,您将打开 2 个文件以供读取,一个以供写入,然后是一些小的临时缓冲区,用于在写入输出文件之前处理和混合输入。为了提高效率,您应该分块执行这些请求(例如,从每个文件中读取 1024 个样本,处理样本,写入 1024 个样本)。 API 并不能保证缓存和缓冲的效率。

    如何将最终输出的音频文件保存在设备上?如果我以编程方式将音频文件保存在某处,我可以再次播放吗?

    ExtAudioFile API 可以满足您的读写需求。是的,您可以稍后阅读/播放。

    【讨论】:

      【解决方案3】:
      • 打开每个音频文件
      • 读取标题信息
      • 将原始未压缩音频作为每个文件的整数数组放入内存
      • 从文件 1 的数组中要混入文件 2 的点开始,循环遍历,将文件 2 的 int 值添加到文件 1 中,确保“剪辑”高于或低于最大值的任何值(这就是混合音频的方式。 .. 是的,这 很简单)。如果 file2 更长,则必须使第一个数组足够长以完全容纳 file2 的其余部分。
      • 写入新的标头信息,然后写入您添加了 file2 的数组中的音频。
      • 如果涉及压缩或文件无法放入内存,您可能需要实施更复杂的缓冲方案。

      【讨论】:

      • 简单地将两个流加在一起并在极值处进行剪辑听起来并不像(没有双关语)那样会产生非常有用的输出。这两个“输入”应适当缩放,以免发生截断。
      • 是的,基本上就是这样。希望这两个文件具有相同的格式、相同的采样率且未压缩,因此这是一个“简单”的数组添加问题(请记住,可能有两个通道)。对数据的第一次扫描将揭示是否会发生削波,然后可以应用缩放以保持最佳音量,同时避免削波。
      • @Sedate - 你是绝对正确的!但是,如果您回想起使用二手模拟混音器的车库乐队时代,您会记得不幸的事实——这就是现实世界中的情况。声音在没有缩放的情况下混合;当电平过多时,所产生的失真实际上称为“削波!” Hot Licks 提到的技术被称为“压缩”(尽管是一种幼稚的实现),对于模拟,它是另一个可以放入机架的盒子。但是,通常令人惊讶的是,结果不会剪辑。在 Audacity 中尝试(你安装了一个副本,不是吗 ;-)
      • *从我的评论压缩到@Sedate = 与消除文件流中的冗余数据无关 - 而是“压缩”音频(使波不那么高)以适应“信封”(系统设置处理的最小/最大峰值)。
      • 实际上,我所描述的技术相当于简单地调整主电平控制。可以在某种程度上动态地执行它,然后它将是“压缩”,但在这种情况下这是不必要的。不过,您是对的,即使没有任何调整,结果也可能不会剪辑。
      【解决方案4】:

      如果您要一次播放多个声音,请务必使用 *.caf 格式。 Apple 推荐它同时播放多种声音。就以编程方式混合它们而言,我假设您只是希望它们同时播放。在播放一种声音时,只需告诉另一种声音在您想要的任何时间播放即可。要设置特定时间,请使用 NSTimer (NSTimer Class Reference) 并创建一个方法以在计时器触发时播放声音。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-03-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多