2009-05-25 12 views
22

मैं आईफोन पर एक साधारण ऑडियो अनुक्रमक प्रोग्राम करना चाहता हूं लेकिन मुझे सटीक समय नहीं मिल रहा है। आखिरी दिनों में मैंने ऑडियो पर सभी संभावित ऑडियो तकनीकों का प्रयास किया, ऑडियो सर्विसेजप्लेसिस्टम साउंड और AVAudioPlayer और OpenQ से ऑडियोक्यूज़ से शुरू किया।आईफोन पर रीयल-टाइम सटीक ऑडियो अनुक्रमक प्रोग्राम कैसे करें?

मेरे आखिरी प्रयास में मैंने कोकोसडेन्सियन ध्वनि इंजन की कोशिश की जो ओपनएएल का उपयोग करता है और कई बफर में ध्वनि लोड करने की अनुमति देता है और फिर जब चाहें उन्हें चलाता है। यहाँ बुनियादी कोड है:

init:

int channelGroups[1]; 
channelGroups[0] = 8; 
soundEngine = [[CDSoundEngine alloc] init:channelGroups channelGroupTotal:1]; 

int i=0; 
for(NSString *soundName in [NSArray arrayWithObjects:@"base1", @"snare1", @"hihat1", @"dit", @"snare", nil]) 
{ 
    [soundEngine loadBuffer:i fileName:soundName fileType:@"wav"]; 
    i++; 
} 

[NSTimer scheduledTimerWithTimeInterval:0.14 target:self selector:@selector(drumLoop:) userInfo:nil repeats:YES]; 

initialisation मैं ध्वनि इंजन बनाने में, विभिन्न बफ़र्स के लिए कुछ ध्वनियों लोड और फिर NSTimer साथ अनुक्रमक पाश की स्थापना।

ऑडियो पाश:

- (void)drumLoop:(NSTimer *)timer 
{ 
for(int track=0; track<4; track++) 
{ 
    unsigned char note=pattern[track][step]; 
    if(note) 
     [soundEngine playSound:note-1 channelGroupId:0 pitch:1.0f pan:.5 gain:1.0 loop:NO]; 
} 

if(++step>=16) 
    step=0; 

} 

यह Thats और यह एकदम सही ढंग से काम करता है, लेकिन समय अस्थिर और अस्थिर है। जैसे ही कुछ और होता है (i.g. एक दृश्य में ड्राइंग) यह सिंक से बाहर चला जाता है।

जैसा कि मैं ध्वनि इंजन और ओपनएएल को समझता हूं, बफर लोड होते हैं (इनिट कोड में) और फिर alSourcePlay(source); के साथ तुरंत शुरू करने के लिए तैयार हैं - तो समस्या एनएसटीमर के साथ हो सकती है?

अब ऐपस्टोर में दर्जनों ध्वनि अनुक्रमक ऐप्स हैं और उनके पास सटीक समय है। आई जी ज़ूमिंग और ड्राइंग होने पर "इड्रम" में 180 बीपीएम में भी एकदम सही स्थिर धड़कन है। तो एक समाधान होना चाहिए।

क्या किसी के पास कोई विचार है?

अग्रिम में किसी भी मदद के लिए धन्यवाद!

सादर,

Walchy


अपने जवाब के लिए धन्यवाद। यह मुझे एक कदम आगे लाया लेकिन दुर्भाग्य से उद्देश्य के लिए नहीं। यहां मैंने किया है:

nextBeat=[[NSDate alloc] initWithTimeIntervalSinceNow:0.1]; 
[NSThread detachNewThreadSelector:@selector(drumLoop:) toTarget:self withObject:nil]; 

प्रारंभ में मैं अगली बीट के लिए समय स्टोर करता हूं और एक नया धागा बना देता हूं।

- (void)drumLoop:(id)info 
{ 
    [NSThread setThreadPriority:1.0]; 

    while(1) 
    { 
     for(int track=0; track<4; track++) 
     { 
      unsigned char note=pattern[track][step]; 
      if(note) 
       [soundEngine playSound:note-1 channelGroupId:0 pitch:1.0f pan:.5 gain:1.0 loop:NO]; 
     } 

     if(++step>=16) 
      step=0;  

     NSDate *newNextBeat=[[NSDate alloc] initWithTimeInterval:0.1 sinceDate:nextBeat]; 
     [nextBeat release]; 
     nextBeat=newNextBeat; 
     [NSThread sleepUntilDate:nextBeat]; 
    } 
} 

अनुक्रम लूप में मैंने थ्रेड प्राथमिकता को जितना संभव हो उतना उच्च सेट किया है और अनंत लूप में जाना है। ध्वनियों को चलाने के बाद मैं अगली बीट के लिए अगले पूर्ण समय की गणना करता हूं और थ्रेड को इस समय तक सोने के लिए भेजता हूं।

फिर से यह काम करता है और यह एनएसटीएचड के बिना मेरी कोशिशों की तुलना में अधिक स्थिर काम करता है लेकिन अगर कुछ और होता है, खासकर जीयूआई सामान यह अभी भी कमजोर है।

क्या आईफोन पर एनएसटीएचएड के साथ वास्तविक समय प्रतिक्रिया प्राप्त करने का कोई तरीका है?

सादर,

Walchy

+0

मुझे लगता है कि @Walchy दोपहर के भोजन के लिए बाहर है ... आश्चर्य जो जिस तरह से उसने अंततः इसे करने का फैसला किया? –

+0

दुर्भाग्य से मैं नमूना कोड पोस्ट नहीं कर सकता, लेकिन ऐप स्टोर में प्लेबैक ऐप के लिए, हम फ़ाइलों से पढ़े गए ऑडियो डेटा प्रदान करने के लिए कॉलबैक का उपयोग करते हैं। प्रत्येक फ़ाइल में एक ही ऑडियो विशेषताएं होती हैं, इसलिए सटीक समय प्रदान करने के लिए, हम ऑडियो फ़ाइल डेटा में उस नमूना बिंदु पर कूदते हैं।यह बहुत सटीक है (ऐप डाउनलोड करें और दिन का मुफ्त खेल प्राप्त करें)। हम लूप सेक्शन कर सकते हैं और फ़ाइल में सेक्शन पर जा सकते हैं, सभी एक बार में 20+ ट्रैक खेलते समय (अभी तक ट्रैक सीमा नहीं मारा है)। –

उत्तर

8

NSTimer जब यह आग पर कोई भी गारंटी नहीं है।यह रनलॉप पर आग के समय के लिए खुद को शेड्यूल करता है, और जब रनलोप टाइमर के आसपास आता है, तो यह देखता है कि कोई भी टाइमर पिछले-देनदार है। यदि ऐसा है, तो यह उनके चयनकर्ता चलाता है। विभिन्न प्रकार के कार्यों के लिए उत्कृष्ट; इस के लिए बेकार।

यहां एक कदम यह है कि आपको ऑडियो प्रोसेसिंग को अपने थ्रेड पर ले जाने और यूआई थ्रेड को बंद करने की आवश्यकता है। समय के लिए, आप सामान्य सी दृष्टिकोण का उपयोग करके अपना स्वयं का समय इंजन बना सकते हैं, लेकिन मैं CAAnimation और विशेष रूप से CAMediaTiming को देखकर शुरू करूंगा।

ध्यान रखें कि कोको में कई चीजें हैं जो केवल मुख्य धागे पर चलाने के लिए डिज़ाइन की गई हैं। उदाहरण के लिए, पृष्ठभूमि थ्रेड पर कोई यूआई काम न करें। सामान्य रूप से, दस्तावेज़ों को ध्यान से पढ़ें कि वे थ्रेड-सुरक्षा के बारे में क्या कहते हैं। लेकिन आम तौर पर, अगर धागे के बीच बहुत अधिक संचार नहीं होता है (जो कि ज्यादातर मामलों में आईएमओ नहीं होना चाहिए), कोको में धागे बहुत आसान हैं। एनएसटीएचएड देखें।

6

मैं दूरस्थियो आउटपुट का उपयोग कर कुछ ऐसा कर रहा हूं। मैं एनएसटीमर पर भरोसा नहीं करता हूं। मैं अपने सभी समय की गणना करने के लिए रेंडर कॉलबैक में प्रदान किए गए टाइमस्टैम्प का उपयोग करता हूं। मुझे नहीं पता कि आईफोन की एचजी दर कितनी सटीक है, लेकिन मुझे यकीन है कि यह 44100 हर्ट्ज के करीब है, इसलिए मैं केवल गणना करता हूं कि मुझे वर्तमान नमूना संख्या के आधार पर अगली बीट लोड करनी चाहिए।

रिमोट आईओ का उपयोग करने वाली एक उदाहरण परियोजना here को टाइमस्टैम्प तर्क में कॉलबैक प्रस्तुत करने पर एक नज़र डालें।

संपादित करें: इस दृष्टिकोण काम करने का उदाहरण (और app की दुकान पर, पाया जा सकता है here)

+0

बेशक आपको एक बीपीएम की आवश्यकता होगी, लेकिन आपके अगले नमूना प्लेबैक को शेड्यूल करने के लिए आपको अगली बीट तक नमूने मिलेंगे (यह आपके बीपीएम द्वारा परिभाषित किया जाएगा) और ऐसा करने के लिए वर्तमान टाइम स्टैंप का उपयोग करें। –

+0

पदों के लिए आपको कई बार धन्यवाद। – griotspeak

+0

ऑडियोटाइमस्टैम्प संरचना का कौन सा हिस्सा आप समय की गणना करने के लिए उपयोग करते हैं? मैं mSampleTime का उपयोग कर रहा हूँ। किसी भी राय पर उपयोग करने के लिए सबसे अच्छा है और क्यों? –

0

मैंने सोचा था कि समय प्रबंधन के लिए एक बेहतर दृष्टिकोण (उदाहरण के लिए, 120) एक धड़कन प्रति मिनट की स्थापना के लिए हो सकता है, और इसके बजाय उस से दूर जाओ। संगीत/संगीत अनुप्रयोग लिखते/लिखते समय मिनटों और सेकंड के मापन बेकार के करीब होते हैं।

यदि आप किसी अनुक्रमिक ऐप को देखते हैं, तो वे सभी समय के बजाय धड़कन से जाते हैं। चीजों के विपरीत तरफ, यदि आप एक वेवफ़ॉर्म संपादक देखते हैं, तो यह मिनट और सेकंड का उपयोग करता है।

मुझे इस कोड-वार को किसी भी माध्यम से लागू करने का सबसे अच्छा तरीका नहीं है, लेकिन मुझे लगता है कि यह दृष्टिकोण आपको सड़क के नीचे बहुत सारे सिरदर्द बचाएगा।

+0

आपको कैसे लगता है कि आप इसे मापने जा रहे हैं? 'एनएसटीमर 'जिटर से पीड़ित है, जैसा कि कोई अन्य तंत्र है जो निर्धारित थ्रेड पर निर्भर करता है .. यही कारण है कि काम करने का एकमात्र तरीका आउटपुट नमूना घड़ी के खिलाफ समय है। – marko

4

मैंने ऑडियोफाइल सर्विसेज एपीआई का उपयोग करते हुए एक रिमोटियो ऑडियो यूनीट और एक पृष्ठभूमि थ्रेड का उपयोग करने का विकल्प चुना जो स्विंग बफर भरता है (पढ़ने के लिए एक बफर, लिखने के लिए एक बफर जो तब स्वैप करता है)। तब बफर को ऑडियोयूनीट थ्रेड में संसाधित और मिश्रित किया जाता है। AudioUnit थ्रेड बीजीएनडी थ्रेड को इंगित करता है जब इसे अगले स्विंग बफर लोड करना प्रारंभ करना चाहिए। सभी प्रसंस्करण सी में था और पॉज़िक्स थ्रेड एपीआई का इस्तेमाल किया। सभी यूआई सामान ओबीजेसी में थे।

आईएमओ, ऑडियोयूनीट/ऑडियोफाइल सर्विसेज दृष्टिकोण लचीलापन और नियंत्रण की सबसे बड़ी डिग्री प्रदान करता है।

चीयर्स,

बेन

1

सच समय दृष्टिकोण सबसे सटीक तरीका ऑडियो नमूने गिनती और जो कुछ भी आप जब नमूने की एक निश्चित संख्या बीत चुका है क्या करना होगा है। आपकी आउटपुट नमूना दर ध्वनि से संबंधित सभी चीजों का आधार है, इसलिए यह मास्टर घड़ी है।

आपको प्रत्येक नमूने पर जांच करने की ज़रूरत नहीं है, ऐसा करने के लिए यह हर दो एमसीसी पर्याप्त होगा।

3

आपके यहां कुछ अच्छे जवाब दिए गए हैं, लेकिन मैंने सोचा कि मैं अपने लिए काम करने वाले समाधान के लिए कुछ कोड प्रदान करूंगा।जब मैंने इसका शोध करना शुरू किया, तो मैंने वास्तव में गेम के काम में कितने रन लूप की तलाश की और मुझे एक अच्छा समाधान मिला जो mach_absolute_time का उपयोग करके मेरे लिए बहुत ही प्रभावशाली रहा है।

आप here के बारे में कुछ पढ़ सकते हैं लेकिन इससे कम यह है कि यह नैनोसेकंद परिशुद्धता के साथ समय देता है। हालांकि, यह संख्या जो लौटाती है वह काफी समय नहीं है, यह आपके पास सीपीयू के साथ बदलती है, इसलिए आपको पहले mach_timebase_info_data_t संरचना बनाना होगा, और उसके बाद समय को सामान्य करने के लिए इसका उपयोग करना होगा।

// Gives a numerator and denominator that you can apply to mach_absolute_time to 
// get the actual nanoseconds 
mach_timebase_info_data_t info; 
mach_timebase_info(&info); 

uint64_t currentTime = mach_absolute_time(); 

currentTime *= info.numer; 
currentTime /= info.denom; 

और अगर हम हर 16 वीं टिप्पणी टिक यह चाहते थे, तुम कर सकते हो कुछ इस तरह:

uint64_t interval = (1000 * 1000 * 1000)/16; 
uint64_t nextTime = currentTime + interval; 

इस बिंदु पर, currentTime नैनोसेकंड में से कुछ नंबर शामिल हैं, और आप इसे चाहते हैं हर बार interval नैनोसेकंड पास करने के लिए टिकटें, जिसे हम nextTime में स्टोर करते हैं। फिर आप इस तरह थोड़ी देर के पाश, कुछ सेट कर सकते हैं:

while (_running) { 
    if (currentTime >= nextTime) { 
     // Do some work, play the sound files or whatever you like 
     nextTime += interval; 
    } 

    currentTime = mach_absolute_time(); 
    currentTime *= info.numer; 
    currentTime /= info.denom; 
} 

mach_timebase_info सामान थोड़ा भ्रामक है, लेकिन एक बार आप यह वहाँ में मिलता है, यह बहुत अच्छी तरह से काम करता है। यह मेरे ऐप्स के लिए बेहद प्रदर्शनकारी रहा है। यह भी ध्यान देने योग्य है कि आप इसे मुख्य धागे पर नहीं चलाना चाहते हैं, इसलिए इसे अपने धागे से दूर करना बुद्धिमान है। आप अपने स्वयं के विधि run कहा जाता है में सब से ऊपर कोड डाल सकता है, और की तरह कुछ के साथ इसे शुरू:-sourced खोलने

[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; 

सभी कोड आप यहाँ देख एक परियोजना मैं का सरलीकरण है, आप इसे देख सकते हैं और कर सकते हैं इसे अपने आप चलाएं here, अगर यह किसी भी मदद की है। चीयर्स।

+0

ध्यान रखें कि सिस्टम घड़ी (जैसे 'mach_absolute_time' द्वारा लौटाई गई) ऑडियो घड़ी के साथ सिंक्रोनस नहीं है जिस पर कोडेक को नमूने देखे जाते हैं। अनुक्रमक समय उत्पन्न करने के लिए ऑडियो रेंडर कॉलबैक में नमूना काउंटर का उपयोग करना एकमात्र विश्वसनीय तरीका है। – marko

+0

हाँ, मुझे कुछ ऐसी चीजें मिल गई हैं जो इस दृष्टिकोण से गलत हैं, जिसमें टाइमज़ोन सर्वर आदि से घड़ी अपडेट से प्रभावित हो रहा है। क्या आपके पास ऑडियो रेंडर में नमूना काउंटर में हुकिंग के लिए एक लिंक/संदर्भ है वापस कॉल करें? – pwightman

+0

लगभग सभी ऑडियो रेंडर हैंडलर (कोरऑडियो के साथ) में आपको या तो नमूना-गिनती दी जाती है - या इसे आपूर्ति के समय पैरामीटर से वापस परिवर्तित कर सकते हैं। इसका नतीजा यह है कि आपको रेंडर हैंडलर में किसी इवेंट कतार और प्रक्रिया की प्रक्रिया करने की आवश्यकता है (और तय करें कि पहले से ही किसी भी के साथ क्या करना है)। वीएसटी एसडीके में इसका एक उदाहरण है कि आप देखना चाहेंगे। यह मौलिक रूप से एयू के समान ही है। – marko

1

एक अतिरिक्त बात यह है कि वास्तविक समय जवाबदेही में सुधार हो सकता है आपके ऑडियो सत्र सक्रिय करने से पहले कुछ मिलीसेकंड (जैसे 0.005 के रूप में सेकंड) के लिए ऑडियो सत्र के kAudioSessionProperty_PreferredHardwareIOBufferDuration स्थापित कर रही है। इससे रिमोटियो कम कॉलबैक बफर को अधिक बार अनुरोध करने का कारण बनता है (वास्तविक समय थ्रेड पर)। इन रीयल-टाइम ऑडियो कॉलबैक में कोई महत्वपूर्ण समय न लें, या आप अपने ऐप के लिए ऑडियो थ्रेड और सभी ऑडियो को मार देंगे।

बस छोटे रिमोटियो कॉलबैक बफर की गणना करना एनएसटीमर का उपयोग करने से 10X अधिक सटीक और कम विलंबता के क्रम में है। और आपके ध्वनि मिश्रण की शुरुआत की स्थिति के लिए ऑडियो कॉलबैक बफर के भीतर नमूनों की गिनती आपको उप-मिलीसेकंद सापेक्ष समय प्रदान करेगी।

1

समय पाश में "कुछ काम करें" भाग के लिए गुजरे मापने और nextTime से इस अवधि को घटा कर बहुत सटीकता को बेहतर बनाता है:

while (loop == YES) 
{ 
    timerInterval = adjustedTimerInterval ; 

    startTime = CFAbsoluteTimeGetCurrent() ; 

    if (delegate != nil) 
    { 
     [delegate timerFired] ; // do some work 
    } 

    endTime = CFAbsoluteTimeGetCurrent() ; 

    diffTime = endTime - startTime ; // measure how long the call took. This result has to be subtracted from the interval! 

    endTime = CFAbsoluteTimeGetCurrent() + timerInterval-diffTime ; 

    while (CFAbsoluteTimeGetCurrent() < endTime) 
    { 
     // wait until the waiting interval has elapsed 
    } 
} 
1

, यदि आपके अनुक्रम समय से आगे का निर्माण एक सीमा नहीं है आप एक AVMutableComposition का उपयोग कर सटीक समय प्राप्त कर सकते हैं। इस समाधान के लिए

// setup your composition 

AVMutableComposition *composition = [[AVMutableComposition alloc] init]; 
NSDictionary *options = @{AVURLAssetPreferPreciseDurationAndTimingKey : @YES}; 

for (NSInteger i = 0; i < 4; i++) 
{ 
    AVMutableCompositionTrack* track = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; 
    NSURL *url = [[NSBundle mainBundle] URLForResource:[NSString stringWithFormat:@"sound_file_%i", i] withExtension:@"caf"]; 
    AVURLAsset *asset = [AVURLAsset URLAssetWithURL:url options:options]; 
    AVAssetTrack *assetTrack = [asset tracksWithMediaType:AVMediaTypeAudio].firstObject; 
    CMTimeRange timeRange = [assetTrack timeRange]; 

    Float64 t = i * 1.0; 
    NSError *error; 
    BOOL success = [track insertTimeRange:timeRange ofTrack:assetTrack atTime:CMTimeMake(t, 4) error:&error]; 
    NSAssert(success && !error, @"error creating composition"); 
} 

AVPlayerItem* playerItem = [AVPlayerItem playerItemWithAsset:composition]; 
self.avPlayer = [[AVPlayer alloc] initWithPlayerItem:playerItem]; 

// later when you want to play 

[self.avPlayer seekToTime:kCMTimeZero]; 
[self.avPlayer play]; 

मूल क्रेडिट: यह 4 समान रूप से लगता है 1 से अधिक दूसरा अदा कर सकता है http://forum.theamazingaudioengine.com/discussion/638#Item_5

और अधिक विस्तार: precise timing with AVMutableComposition

संबंधित मुद्दे