2016-06-08 1 views
2

내 첫 번째 앱은 iOS 6 이후로 더 이상 사용되지 않는 메소드를 사용하여 music audio from a sine look-up table을 합성했습니다. this blog 및 Apple AVFoundationFramework의 도움을 받아 AudioSession에 대한 경고를 해결하기 위해 방금 수정했습니다. 오디오 세션 경고가 이제 해결되었고 응용 프로그램은 이전처럼 오디오를 생성합니다. 현재 iOS 9에서 실행됩니다.NaN이 코어 오디오 iOS 앱에서 가끔 충돌을 일으킬 수 있습니까?

그러나 명백한 이유로 앱이 가끔 충돌합니다. 나는 this SO post을 체크 아웃했으나 원시 오디오 데이터를 생성하는 것보다 액세스하는 것으로 보이기 때문에 타이밍 문제를 다루지 않을 수 있습니다. 나는 버퍼링 문제가 있다고 생각하지만 코드에서 무엇인가를 변경하거나 미세 조정하기 전에 이것이 무엇인지 이해할 필요가있다.

사용자가 수정 된 앱을 사용할 수 있도록 마감일을 지키므로 비슷한 문제를 처리 한 사람의 의견을 듣고 참 고맙게 생각합니다.

여기에 문제가 있습니다. 앱이 시뮬레이터보고 디버그로 전환 : 디버그 Navigator에서

com.apple.coreaudio.AQClient (8):EXC_BAD_ACCESS (code=1, address=0xffffffff10626000) 

, 스레드 8 (com.apple.coreaudio.AQClient (8)은)는보고 :

fillBuffer에서이 코드 줄이 강조

0 -[Synth fillBuffer:frames:] 
    1 -[PlayView audioBufferPlayer:fillBuffer:format:] 
    2 playCallback 

float sineValue = (1.0f - b)*sine[a] + b*sine[c]; 

...이 코드는 audioBufferPlayer에 있습니다.

여기

[player.delegate audioBufferPlayer:player fillBuffer:inBuffer format:player.audioFormat]; 

0 ... 및 playCallBack은 (above 함 데모에서와 본질적으로 동일한 대리인) audioBufferPlayer위한 코드이다. ...

- (id)init 
{  
    if ((self = [super init])) { 
    // The audio buffer is managed (filled up etc.) within its own thread (Audio Queue thread) 
    // Since we are also responding to changes from the GUI, we need a lock so both threads 
    // do not attempt to change the same value independently. 

     synthLock = [[NSLock alloc] init]; 

    // Synth and the AudioBufferPlayer must use the same sample rate. 

     float sampleRate = 44100.0f; 

    // Initialise synth to fill the audio buffer with audio samples. 

     synth = [[Synth alloc] initWithSampleRate:sampleRate]; 

    // Initialise note buttons 

     buttons = [[NSMutableArray alloc] init]; 

    // Initialise the audio buffer. 

     player = [[AudioBufferPlayer alloc] initWithSampleRate:sampleRate channels:1 bitsPerChannel:16 packetsPerBuffer:1024]; 
     player.delegate = self; 
     player.gain = 0.9f; 
     [[AVAudioSession sharedInstance] setActive:YES error:nil]; 

    } 
    return self; 
} // initialisation 

... 여기

static void playCallback(void* inUserData, AudioQueueRef inAudioQueue, AudioQueueBufferRef inBuffer) 
{ 
    AudioBufferPlayer* player = (AudioBufferPlayer*) inUserData; 
    if (player.playing){ 
     [player.delegate audioBufferPlayer:player fillBuffer:inBuffer format:player.audioFormat]; 
     AudioQueueEnqueueBuffer(inAudioQueue, inBuffer, 0, NULL); 
    } 
} 

... 및 playCallback

위해 (초기화에 myViewController)

- (void)audioBufferPlayer:(AudioBufferPlayer*)audioBufferPlayer fillBuffer:(AudioQueueBufferRef)buffer format:(AudioStreamBasicDescription)audioFormat    
    { 
    [synthLock lock]; 
    int packetsPerBuffer = buffer->mAudioDataBytesCapacity/audioFormat.mBytesPerPacket; 
    int packetsWritten = [synth fillBuffer:buffer->mAudioData frames:packetsPerBuffer]; 
    buffer->mAudioDataByteSize = packetsWritten * audioFormat.mBytesPerPacket;  
    [synthLock unlock]; 

    } 

는 오디오

를 합성 fillBuffer위한 코드
- (int)fillBuffer:(void*)buffer frames:(int)frames 
{ 
    SInt16* p = (SInt16*)buffer; 

    // Loop through the frames (or "block size"), then consider each sample for each tone. 

    for (int f = 0; f < frames; ++f) 
    { 
     float m = 0.0f; // the mixed value for this frame 

     for (int n = 0; n < MAX_TONE_EVENTS; ++n) 
     { 
      if (tones[n].state == STATE_INACTIVE) // only active tones 
       continue; 

    // recalculate a 30sec envelope and place in a look-up table 
    // Longer notes need to interpolate through the envelope 

      int a = (int)tones[n].envStep;  // integer part (like a floored float) 
      float b = tones[n].envStep - a;   // decimal part (like doing a modulo) 

     // c allows us to calculate if we need to wrap around 

      int c = a + 1;       // (like a ceiling of integer part) 
      if (c >= envLength) c = a;    // don't wrap around 

    /////////////// LOOK UP ENVELOPE TABLE ///////////////// 

    // uses table look-up with interpolation for both level and pitch envelopes 
    // 'b' is a value interpolated between 2 successive samples 'a' and 'c')    
    // first, read values for the level envelope 

      float envValue = (1.0f - b)*tones[n].levelEnvelope[a] + b*tones[n].levelEnvelope[c]; 

    // then the pitch envelope 

      float pitchFactorValue = (1.0f - b)*tones[n].pitchEnvelope[a] + b*tones[n].pitchEnvelope[c]; 

    // Advance envelope pointer one step 

      tones[n].envStep += tones[n].envDelta; 

    // Turn note off at the end of the envelope. 
      if (((int)tones[n].envStep) >= envLength){ 
       tones[n].state = STATE_INACTIVE; 
       continue; 
      } 

     // Precalculated Sine look-up table    
      a = (int)tones[n].phase;     // integer part 
      b = tones[n].phase - a;      // decimal part 
      c = a + 1; 
      if (c >= sineLength) c -= sineLength;  // wrap around 

    ///////////////// LOOK UP OF SINE TABLE /////////////////// 

      float sineValue = (1.0f - b)*sine[a] + b*sine[c]; 

    // Wrap round when we get to the end of the sine look-up table. 

      tones[n].phase += (tones[n].frequency * pitchFactorValue); // calculate frequency for each point in the pitch envelope 
      if (((int)tones[n].phase) >= sineLength) 
       tones[n].phase -= sineLength; 

    ////////////////// RAMP NOTE OFF IF IT HAS BEEN UNPRESSED 

      if (tones[n].state == STATE_UNPRESSED) { 
       tones[n].gain -= 0.0001;     
      if (tones[n].gain <= 0) { 
       tones[n].state = STATE_INACTIVE; 
       } 
      } 

    //////////////// FINAL SAMPLE VALUE /////////////////// 

      float s = sineValue * envValue * gain * tones[n].gain; 

    // Clip the signal, if needed. 

      if (s > 1.0f) s = 1.0f; 
      else if (s < -1.0f) s = -1.0f; 

    // Add the sample to the out-going signal 
     m += s; 
     } 

    // Write the sample mix to the buffer as a 16-bit word. 
    p[f] = (SInt16)(m * 0x7FFF); 
    } 
return frames; 
} 

저는 su가 아닙니다. 그것이 빨간색 청어인지 아닌지는 알지만 여러 디버그 레지스터에서 NaN을 발견했습니다. fillBuffer (위 참조)에서 사인 조회에 대한 위상 증가분을 계산하는 중에 발생합니다. 이 계산은 44.1kHz의 샘플링 속도로 모든 샘플을 최대 12 개 부분으로 처리하고 iPhone 4에서 iOS 4에서 작업했습니다. iOS 9의 시뮬레이터에서 실행 중입니다. 내가 만든 유일한 변경 사항은이 게시물에 설명되어 있습니다!

+1

내 _CoreAudio_ 코드 중 일부를 디버깅하는 데 도움이되는 NaN "catcher-silencer": – user3078414

+0

@ user3078414 - 죄송합니다. NaN catcher-silencer는 'float sineValue = (1.0f - b) * sine [a] + b * sine [c];'와 같은 식으로 작동합니다. 'if (b! = b) b = 0;'을 의미합니까? ? 또는 'if (sineValue! = sineValue) sineValue = 0;' ? 파형에 가끔 불연속이 삽입되지 않습니까? – Greg

+1

그런 식으로 작성하면 물론 불연속을 삽입 할 수 있지만 (NaN 및 다른 여러 원인으로 인해 스레드가 손상 될 수도 있음)이 코드는 _NaN 문제가 있다는 증거가 될 수 있으므로 검색 영역을 제한 할 수 있습니다 . 기본적으로 실시간 무한 루프를 디버깅하고 있으므로 실시간 관점에서 관찰해야합니다. – user3078414

답변

1

NaN 문제는 Core Audio와 직접적인 관련이 없음이 밝혀졌습니다. 이는 내 코드의 다른 영역에서 변경된 가장자리 조건으로 인해 발생했습니다. 실제 문제는 실시간으로 사운드 엔벨로프의 지속 시간을 계산하는 동안 시도 된 0으로 나누는 것입니다.

그러나 그 문제의 원인을 파악할 때, 사전 iOS 7 오디오 세션이 AVFoundation을 기반으로하는 작업 설정으로 대체되었다고 확신합니다.감사의 말은 내 초기 코드 Matthijs Hollemans의 소스와 Mario Diana의 블로그에서 필요한 변경 사항을 설명하는 것입니다.

처음에는 내 iPhone의 사운드 레벨이 시뮬레이터의 사운드 레벨보다 훨씬 낮았습니다. 파운드리가 제기 한 here 문제입니다. 나는 그것이 필요한 다른 파운드리의

- (void)configureAVAudioSession 

희망이 도움이 될 사람과 마리오의

- (BOOL)setUpAudioSession 

를 대체함으로써 이러한 개선을 포함 발견했다.

관련 문제