【问题标题】:ios: Play sound outside the main threadios:在主线程外播放声音
【发布时间】:2011-08-06 15:58:46
【问题描述】:

我做了一个简单的应用程序,当超过一定的噪音水平时会发出警报。因此,我有一个 AudioQueue 可以记录声音并测量记录声音的级别(仅重要的代码部分如下所示):

#import "AudioRecorder.h"

#include <AudioToolbox/AudioToolbox.h>

#include <iostream>

using namespace std;

@implementation AudioRecorder

@synthesize sp; //custom object SoundPlayer
@synthesize bias; //a bias, if the soundlevel exeeds this bias something happens

AudioRecorder* ar;    

//callback function to handle the audio data contained in the audio buffer.
//In my case it is called every 0.5 seconds
static void HandleInputBuffer (...) {
    ...
    char* levelMeterData = new char[size];
    AudioQueueGetProperty ( inAQ, kAudioQueueProperty_CurrentLevelMeter, levelMeterData, &size );
    AudioQueueLevelMeterState* meterState =  reinterpret_cast<AudioQueueLevelMeterState*>(levelMeterData);
    cout << "mAveragePower = " << meterState->mAveragePower << endl;
    cout << "mPeakPower = " << meterState->mPeakPower << endl;
    if( meterState->mPeakPower > ar.bias )
        [ar playAlarmSound];

}
...
//The constructor of the AudioRecorder class
-(id) init {
    self = [super init];
    if( self ) {
        ar = self;
        sp = [[SoundPlayer alloc] init];
    }
    return self;
}

-(void)playAlarmSound {
    [sp playSound];
}

所以这里基本上发生的情况如下:

我在 iphone 屏幕上点击一个按钮,该按钮导致音频队列从 iphone 中的麦克风录制声音。当队列已满时,将调用回调函数“HandleInputBuffer”来处理音频缓冲区中的数据。在我的特定情况下,处理数据意味着我想测量声音强度。如果强度超过偏差,则调用“playAlarmSound”方法。

所以这个调用发生在主线程之外,即在一个额外的线程中。

对象“sp”(SoundPlayer)有以下实现:

//callback function. Gets called when the sound has finished playing
void soundDidFinishPlaying( SystemSoundID ssID, void *clientData ) {
    NSLog(@"Finished playing system sound");
    sp.soundIsPlaying = NO;
}

-(id)init {
    self = [super init];
    if(self) {
        self.soundIsPlaying = NO;
        srand(time(NULL));
        soundFilenames = [[NSArray alloc] initWithObjects:@"Alarm Clock Bell.wav", @"Bark.wav", @"Cartoon Boing.wav", @"Chimpanzee Calls.wav", @"School Bell Ringing.wav", @"Sheep Bah.wav", @"Squeeze Toy.wav", @"Tape Rewinding.wav", nil];
        [self copySoundsIfNeeded];
        sp = self;
        [self playSound]; //gets played without any problems
    }
    return self;
}

-(void)playSound {
    if( !self.soundIsPlaying ) {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        int index = rand() % [soundFilenames count];
        NSString* filePath = [ [self getBasePath] stringByAppendingPathComponent: [soundFilenames objectAtIndex:index] ];

        NSFileManager *fileManager = [NSFileManager defaultManager];
        if( [fileManager fileExistsAtPath:filePath] )
            NSLog(@"File %@ exists", [soundFilenames objectAtIndex:index]);
        else
            NSLog(@"File %@ NOT exists", [soundFilenames objectAtIndex:index]);

        CFURLRef url = CFURLCreateWithFileSystemPath ( 0, (CFStringRef) filePath, kCFURLPOSIXPathStyle, NO );
        SystemSoundID outSystemSoundID;
        AudioServicesCreateSystemSoundID( url, &outSystemSoundID );
        AudioServicesAddSystemSoundCompletion ( outSystemSoundID, 0, 0, soundDidFinishPlaying, 0 );
        self.soundIsPlaying = YES;
        AudioServicesPlaySystemSound( outSystemSoundID );
        [pool drain];
    }
}

实际上代码工作正常,所以代码中应该没有错误,而且波形文件的格式是正确的。这就是发生的事情:

  1. iPhone 模拟器: 一切正常。在创建对象 SoundPlayer 时,在构造函数中调用 [self playSound] 会使模拟器播放随机声音,从而证明该方法已正确实现。然后我开始录音,当超过某个声级时,从 AudioRecorder(在一个单独的线程中)调用该方法,并且声音也正在播放。所以这是期望的行为。

  2. 实际 iPhone 设备: 创建对象 SoundPlayer 时,会播放随机声音,因此可以正常工作。但是当我开始录音并且超过噪音水平时,Soundplayer 中的方法 playSound 被 AudioRecorder 调用,但没有播放声音。我怀疑这是因为这发生在一个单独的线程中。但我不知道如何修复。

我已经尝试通过使用来自默认通知中心的通知来修复它,但它也不起作用。如何管理声音的播放?

【问题讨论】:

    标签: ios multithreading audio


    【解决方案1】:

    有几点需要考虑:

    1) 确保启用播放和录制:

    UInt32 category = kAudioSessionCategory_PlayAndRecord;
    AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
                            sizeof(category), &category);
    

    2) 检查-(void)playSound 中各种AudioServices* 调用的返回值,看看它实际失败的地方。

    3) 确保 AudioRecorder 类中的 sp 属性已正确初始化(即您调用 AudioRecorder 的正确 init 方法)

    4) (某些)iOS 设备可能不支持同时录制和播放的不同采样率。如果您的 .wav 文件的采样率与录音 AudioQueue 的采样率不同,则值得尝试调整其中一个。

    5) 不要调用[sp playSound],而是使用这个语句在主线程上调用它:

    [sp performSelectorOnMainThread:@selector(playSound) withObject:nil waitUntilDone:NO];
    

    另请注意,您的闹钟声音很可能会在录音中听到(尽管 iPhone 4 可以将其过滤掉)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-20
      相关资源
      最近更新 更多