내 첫 번째 앱은 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)
은)는보고 :
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의 시뮬레이터에서 실행 중입니다. 내가 만든 유일한 변경 사항은이 게시물에 설명되어 있습니다!
내 _CoreAudio_ 코드 중 일부를 디버깅하는 데 도움이되는 NaN "catcher-silencer": – user3078414
@ 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
그런 식으로 작성하면 물론 불연속을 삽입 할 수 있지만 (NaN 및 다른 여러 원인으로 인해 스레드가 손상 될 수도 있음)이 코드는 _NaN 문제가 있다는 증거가 될 수 있으므로 검색 영역을 제한 할 수 있습니다 . 기본적으로 실시간 무한 루프를 디버깅하고 있으므로 실시간 관점에서 관찰해야합니다. – user3078414