ios:在主线程外播放声音
我制作了一个简单的应用程序,当超过一定的噪音水平时会发出警报。因此,我有一个 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];
}
}
实际上代码工作正常,因此代码中不应该有错误,并且波形文件的格式也是正确的。这就是发生的情况:
iPhone 模拟器: 一切正常。当创建 SoundPlayer 对象时,在构造函数中调用 [self playSound] 会使模拟器播放随机声音,从而证明该方法已正确实现。然后我开始录音,当超过某个声级时,会从 AudioRecorder(在单独的线程中)调用该方法,并且也会播放声音。所以这是期望的行为。
实际的 iPhone 设备: 当创建 SoundPlayer 对象时,会播放随机声音,因此效果很好。但是当我开始录音并且噪音水平超过时,AudioRecorder 会调用 Soundplayer 中的 playSound 方法,但不会播放声音。我怀疑这是因为这发生在一个单独的线程中。但我不知道如何修复这个问题。
我已经尝试使用默认通知中心的通知来修复它,但它也不起作用。我怎样才能播放声音?
I made a simple app that plays an alarm when a certain noise level is exceeded. So therefore I have an AudioQueue that records sound and meters the level of the recorded sound (only important code parts shown below):
#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];
}
So what basically happens here is the following:
I tap on the iphone screen on a button that causes the audioqueue to record sound from the mic in the iphone. When a queue is full the callback function "HandleInputBuffer" gets called to handle the data in the audio buffer. Handling the data means in my particular case that I want to measure the sound intensity. If the intensity exceeds a bias the method "playAlarmSound" get invoked.
So this invocation happens outside the main thread i.e. in an extra thread.
The object "sp" (SoundPlayer) has the following implementation:
//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];
}
}
Actually the code works fine, so there should be no errors in the code, also the format of the wave files is correct. And this is what happens:
iPhone Simulator:
Everything works fine. When the object SoundPlayer is created the call to [self playSound] in the constructor causes the simulator to play a random sound, so that proves that the method is correctly implemented. Then I start soundrecording and when a certain soundlevel is exceeded the method gets invoked from AudioRecorder (in a separated thread) and the sounds are also being played. So this is the desired behavior.Actual iPhone device:
When the object SoundPlayer is created a random sound is played, so this works fine. BUT when I start audio recording and a noise level is exceeded the method playSound in Soundplayer gets invoked by AudioRecorder but no sound is played. I suspect this is because this happens in a separated thread. But I have no idea how this could be repaired.
I already tried to repair it by using notifications form the default notification center but it didn't work either. How can I manage that the sound gets played?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
需要考虑以下几点:
1) 确保启用播放和录音:
2) 检查
-(void)playSound
中各种AudioServices*
调用的返回值看看它实际上失败在哪里。3) 确保 AudioRecorder 类中的
sp
属性已正确初始化(即调用 AudioRecorder 的正确 init 方法)4) (某些)iOS 设备可能不支持不同的采样率用于同时记录和播放。如果您的 .wav 文件的采样率与录音 AudioQueue 的采样率不同,则值得尝试调整其中任何一个。
5) 不要调用
[sp playSound]
使用此语句在主线程上调用它:另请注意,很可能,您的闹钟声音将在录音中听到(尽管 iPhone 4 可以将其过滤掉) )。
a couple of things to consider:
1) make sure you enable play&record:
2) check the return values of the various
AudioServices*
calls in-(void)playSound
to see where it actually fails.3) make sure that the
sp
property in class AudioRecorder is properly initialized (i.e. that you call the correct init method of AudioRecorder)4) It is possible that (some) iOS devices do not support different sampling rates for simultaneous record & playback. If your .wav files have a different sampling rate than your recording AudioQueue, it's worth a try to adapt either one.
5) instead of calling
[sp playSound]
use this statement to call it on the main thread:Also note that most likely, your alarm sound will be heard on the recording (though iPhone 4 can filter it out).