Detecting Blow through iPhone MIC in Cocos and the

2019-09-17 02:52发布

问题:

I am working on an app which is related to the recipes.In this app there is a section where user can blow air through the mic and can change the image as well the content on it by using animation transition CurlDown.I am able to detect the blow by using the following code,

- (void)viewDidLoad {
    [super viewDidLoad];

    NSURL *url = [NSURL fileURLWithPath:@"/dev/null"];

    NSDictionary *settings = [NSDictionary dictionaryWithObjectsAndKeys:
                              [NSNumber numberWithFloat: 44100.0],                 AVSampleRateKey,
                              [NSNumber numberWithInt: kAudioFormatAppleLossless], AVFormatIDKey,
                              [NSNumber numberWithInt: 1],                         AVNumberOfChannelsKey,
                              [NSNumber numberWithInt: AVAudioQualityMax],         AVEncoderAudioQualityKey,
                              nil];

    NSError *error;

    recorder = [[AVAudioRecorder alloc] initWithURL:url settings:settings error:&error];

    if (recorder) {
        [recorder prepareToRecord];
        recorder.meteringEnabled = YES;
        [recorder record];

        levelTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target: self selector: @selector(levelTimerCallback:) userInfo:nil repeats:YES];


    }
    else{
//       NSLog([error description]);
    }

    image =[[UIImageView alloc] init];
    image.image =[UIImage imageNamed:@"Recipie.png"];
    image.frame =CGRectMake(50, 100, 150, 200);
    [self.view addSubview:image];

}


- (void)levelTimerCallback:(NSTimer *)timer {

    [recorder updateMeters];
    const double ALPHA = 0.05;
    double peakPowerForChannel = pow(10, (0.05 * [recorder peakPowerForChannel:0]));
    lowPassResults = ALPHA * peakPowerForChannel + (1.0 - ALPHA) * lowPassResults;  
//  NSLog(@"Average input: %f Peak input: %f Low pass results: %f", [recorder averagePowerForChannel:0], [recorder peakPowerForChannel:0], lowPassResults);
    if (lowPassResults >0.055 )
    {
        NSLog(@"Mic blow detected");
        [self changeFrame];

    }

}

-(void)changeFrame
{
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1];
    [UIView setAnimationTransition:UIViewAnimationTransitionCurlDown forView:image cache:NO];
    [UIView commitAnimations];

}

But my problem is,when i blow first time through the mic then image animated 5 to 7 times and when i blow second time then images animates 9 to 10 times,i want this animation single times on every blow detection. Please suggest me how can i do this using this code or it will be better for me if somebody can share the code for this section.

回答1:

I suspect the problem comes from the fact that you are calling levelTimerCallback each second. So, when a blow come in, for the whole duration of the blow your callback will make the image change.

A workaround to this would be using a BOOL flag:

@property (nonatomic) BOOL blowDetected;

In levelTimerCallback, you keep track of when a blow is detected and when it ends and you do change images only for new blows:

- (void)levelTimerCallback:(NSTimer *)timer {

  [recorder updateMeters];
  const double ALPHA = 0.05;
  double peakPowerForChannel = pow(10, (0.05 * [recorder peakPowerForChannel:0]));
  lowPassResults = ALPHA * peakPowerForChannel + (1.0 - ALPHA) * lowPassResults;  
  if (lowPassResults > 0.055)
  {
    NSLog(@"Blow detected with power: %f", lowPassResults);
    if (!self.blowDetected) {
      self.blowDetected = YES;
      NSLog(@"Mic blow detected");
      [self changeFrame];
    }
  } else {
    NSLog(@"Blow not detected with residual power: %f", lowPassResults);
    self.blowDetected = NO;
  }
}

This should prevent multiple image changes for the same blow...

Now, this will work fine when between a blow and the next one you wait enough time so that the power currently detected by the mic decreases below the 0.055 threshold. This means that any blow occurring before that will be ignored.

To improve this, instead of simply filtering the signal, we could simply try and detect when the filtered value increases; so I would suggest the following implementation:

- (void)levelTimerCallback:(NSTimer *)timer {

  [recorder updateMeters];
  const double ALPHA = 0.05;
  double peakPowerForChannel = pow(10, (0.05 * [recorder peakPowerForChannel:0]));
  double currentLowPassResults = ALPHA * peakPowerForChannel + (1.0 - ALPHA) * lowPassResults;  
  if (currentLowPassResults > 0.055)
  {
    NSLog(@"Blow detected with power: %f", lowPassResults);
    if (!self.blowDetected || currentLowPassResult > K * lowPassResults) {
      self.blowDetected = YES;
      NSLog(@"Mic blow detected");
      [self changeFrame];
    }
  } else {
    NSLog(@"Blow not detected with residual power: %f", lowPassResults);
    self.blowDetected = NO;
  }
  lowPassResult = currentLowPassResults;
}

You could find an optimal value for K by doing some tests.

In the latter implementation:

  1. when a blow is first detected, we change the image and go into mode "wait for blow to extinguish" (self.blowDetected == YES);

  2. in mode "wait for blow to extinguish", we do not change images, unless we detect a new blow, characterized by the fact that we have a recording power significantly larger than the current level.



回答2:

Use return as you get first lowpass results >0.55

I have solve the issue have a look.

-(void)readyToBlow1 {

  NSURL *url = [NSURL fileURLWithPath:@"/dev/null"];

  NSDictionary *settings = [NSDictionary dictionaryWithObjectsAndKeys:
                          [NSNumber numberWithFloat: 44100.0],                 AVSampleRateKey,
                          [NSNumber numberWithInt: kAudioFormatAppleLossless], AVFormatIDKey,
                          [NSNumber numberWithInt: 1],                         AVNumberOfChannelsKey,
                          [NSNumber numberWithInt: AVAudioQualityMax],         AVEncoderAudioQualityKey,
                          nil];

NSError *error;

recorder = [[AVAudioRecorder alloc] initWithURL:url settings:settings error:&error];

if (recorder) {
    [recorder prepareToRecord];
    recorder.meteringEnabled = YES;
    [recorder record];
    levelTimer = [NSTimer scheduledTimerWithTimeInterval: 0.01 target: self selector: @selector(levelTimerCallback1:) userInfo: nil repeats: YES];
 } 
  else
     NSLog(@"%@",[error description]);

}

 - (void)levelTimerCallback1:(NSTimer *)timer {

    [recorder updateMeters];

const double ALPHA = 0.05;
double peakPowerForChannel = pow(10, (0.05 * [recorder peakPowerForChannel:0]));
lowPassResults = ALPHA * peakPowerForChannel + (1.0 - ALPHA) * lowPassResults;
//NSLog(@"lowPassResults= %f",lowPassResults);

if (lowPassResults > 0.55)
{

    lowPassResults = 0.0;

    [self invalidateTimers];


    NextPhase *objNextView =[[NextPhase alloc]init];

    [UIView transitionFromView:self.view
                        toView:objNextView.view
                      duration:2.0
                       options:UIViewAnimationOptionTransitionCurlUp
                    completion:^(BOOL finished) {
                    }
     ];

    [self.navigationController pushViewController:objNextView animated:NO];

    **return;**


  }

}