iPhone:如何通过编程混合两个音频文件?

14

我想要两个音频文件并在程序中混合和播放它们。当我播放第一个音频文件时,在一段时间(动态时间)之后,我需要将第二个小音频文件添加到正在播放的第一个音频文件的中间位置,最后我需要将其作为一个音频文件保存到设备上。它应该使用混音器音频与音频文件一起播放,我包含了第二个音频文件。

我已经阅读了许多论坛,但无法确切地知道如何实现这一点?

有人能澄清我的下面几个疑问吗?

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

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


不,你不能将其保存为 .avi,因为 .avi 只是一个容器(也可以包含视频)。我不确定你在 iOS 设备上需要使用什么,但我猜你需要编写一个 wav 文档(换句话说,纯音频峰值/波形)。要合并这些内容,你需要具有高级的音频处理知识,而我没有。因此,我无法提供有关此方面的信息。 - user207616
http://developer.apple.com/library/ios/#codinghowtos/AudioAndVideo/_index.html - Jacob Jennings
4个回答

6
  • 打开每个音频文件
  • 读取头部信息
  • 将原始未压缩的音频作为整数数组存储到内存中,每个文件一个数组
  • 从你想混合文件2的文件1的数组点开始循环,将文件2的整数值加到文件1中,确保将任何超过或低于最大值的值进行“剪辑”(这就是如何混合音频...是的,就是这么简单)。如果文件2比较长,你需要将第一个数组长度设置为足够长,以完全容纳文件2的其余部分。
  • 写入新的头部信息,然后写入从添加文件2所在的数组传输而来的音频。
  • 如果涉及压缩或文件无法适应内存,则可能需要实现更复杂的缓冲方案。

简单地将两个流合并在一起,并在极端值处削减似乎不会产生非常有用的输出。这两个“输入”应该适当地进行缩放,以使不需要截断发生。 - Aidan Steele
没错,基本上就是这样。希望这两个文件的格式相同,采样率相同,并且没有压缩,这样就可以简单地进行数组加法(记住可能有两个通道)。首先对数据进行扫描,以确定是否会出现削波,然后可以应用缩放来保持最佳音量并避免削波。 - Hot Licks
@Sedate - 你是完全正确的!但如果你回忆起你在车库乐队时用过的模拟混音器,你会记得不幸的真相——在现实世界中就是这样。声音被混合时没有进行缩放;当级别过高时,产生的失真实际上被称为“削波”! Hot Licks提到的技术称为“压缩”(虽然是一种天真的实现),对于模拟,需要另一个盒子放入机架中。通常令人惊讶的是,结果不会削波。在Audacity中尝试一下(您已经安装了副本,不是吗;-) - FastAl
将我的评论中提到的压缩与 @Sedate 没有关系,它不是指消除文件流中的冗余数据,而是指“压缩”音频(使波形变得更矮)以适应一个“信封”(系统设置的最小/最大峰值)。 - FastAl
实际上,我所描述的技术等同于简单地调整主音量控制。可以在某种程度上动态地进行,那么它就是“压缩”,但在这种情况下这是不必要的。你说得对,即使没有任何调整,结果也可能不会削波。 - Hot Licks

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样本表示包括SInt32、SInt16和float,但这可以根据应用程序和硬件(超出iOS)而有很大变化。如果需要,ExtAudioFile API也会将压缩格式转换为PCM。
您的输入音频文件应具有相同的采样率。如果不是,则必须重新采样音频,这是一个复杂的过程,也需要大量资源(如果正确/准确执行)。如果您需要支持重新采样,则将分配给完成此任务所需的时间加倍(此处未详细说明过程)。
要添加声音,您将从文件请求PCM样本,进行处理,然后写入输出文件(或内存缓冲区)。
为了确定何时添加其他声音,您需要通过ExtAudioFileGetProperty获取输入文件的采样率。如果您想在目标缓冲区中将第二个声音写入55秒处,则可以从样品编号SampleRate * 55开始添加声音,其中SampleRate是您正在读取的文件的采样率。
要混合音频,您只需使用以下形式(伪代码):
mixed[i] = fileA[i] + fileB[i];

但是你必须确保避免溢出/下溢和其他算术错误。通常,您将使用某些整数值执行此过程,因为浮点计算可能需要很长时间(当存在这么多计算时)。对于某些应用程序,您可以只进行移位和加法而不必担心溢出-这将在添加它们之前有效地将每个输入减半。结果的振幅将减少一半。如果您控制文件内容(例如,它们都作为资源捆绑在一起),则可以简单地确保文件中的任何峰值样本均不超过全幅值的一半(约为-6dBFS)。当然,保存为浮点数将解决此问题,但会增加更高的CPU、内存和文件I/O需求。

此时,您需要打开两个文件进行读取,一个文件进行写入,然后使用一些小的临时缓冲区来处理和混合输入,然后将其写入输出文件。为了提高效率,您应该以块的形式执行这些请求(例如,从每个文件读取1024个样本,处理样本,写入1024个样本)。API对于缓存和缓冲效率没有太多保证。

如何将最终输出音频文件保存到设备上?如果我在程序中保存音频文件,我能再次播放吗?

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


2

你可以通过使用AV Foundation来实现这一点。

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


}

0
如果你想同时播放多个声音,一定要使用 *.caf 格式。苹果推荐它用于同时播放多个声音。关于在程序中混合它们,我假设你只是想让它们同时播放。当一个声音正在播放时,只需告诉另一个声音在任何时间播放即可。要设置特定的时间,请使用 NSTimer(NSTimer Class Reference)并创建一个方法,在计时器触发时播放声音。

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