Problema:
Beh, il problema è che si recupera la tua player.lastRenderTime in ogni corsa del ciclo for, prima playAt:
Quindi, in effetti ne riceverai uno diverso n ow -time per ogni giocatore!
Il modo in cui si farlo si potrebbe anche iniziare a tutti i giocatori nel ciclo con gioco: o playAtTime: nil !!! Si verificherebbe lo stesso risultato con una perdita di sincronizzazione ...
Per lo stesso motivo il lettore a corto-of-sync in modi diversi su dispositivi diversi, a seconda della velocità della macchina ;-) I suoi ora -Times sono numeri magici casuali - così, don' Supponiamo che funzioneranno sempre se solo capiteranno di lavorare nel tuo scenario. Anche il più piccolo ritardo a causa di un ciclo corsa occupato o CPU buttarti out-of-sync di nuovo ...
Soluzione:
Quello che realmente dovete fare è quello di ottenere ONE istantanea discreta di now = player.lastRenderTime prima del ciclo e utilizzare questa stessa ancora per ottenere un avvio sincronizzato in batch per tutto il lettore.
In questo modo non è nemmeno necessario ritardare l'avvio del lettore. Certo, il sistema ritaglierà alcuni dei fotogrammi principali - (ma ovviamente la stessa quantità per ogni giocatore ;-) - per compensare la differenza tra il tuo ora (che in realtà è già passato e passato) e l'attuale Playtime (che resta ancora da fare in un futuro molto prossimo) ma alla fine ricominciare tutto il lettore esattamente in sincrono come se effettivamente fossi davvero li ha iniziato a ora in passato. Questi fotogrammi ritagliati non sono quasi mai visibili e avrai tranquillità per quanto riguarda la reattività ...
Se ti occorrono questi fotogrammi - a causa di clic uditivi o artefatti all'avvio di file/segmenti/buffer - beh, shift il tuo ora per il futuro avviando il tuo giocatore in ritardo. Ma, naturalmente, si otterrà questo piccolo ritardo dopo aver colpito il pulsante di avvio - anche se naturalmente ancora in perfetta sincronia ...
Conclusione:
Il punto qui è quello di avere un unico riferimento ora -time per tutti i giocatori e per chiamare la playAtTime: ora metodi il più presto possibile dopo la cattura questa ora-riferimento. Maggiore è il divario, maggiore sarà la porzione di trame principali ritagliate, a meno che non sia fornito un ragionevole ritardo di avvio e lo si aggiunga al ora -time, che naturalmente causa una mancanza di risposta in forma di avvio ritardato dopo aver colpito il tuo pulsante Start.
E sempre essere consapevoli del fatto che - qualunque ritardo su qualsiasi dispositivo è prodotto dai meccanismi di buffering audio - è FUNZIONA NON effetto la sincronicità di qualsiasi quantità di giocatore, se fatto in proprio, di cui sopra modo! È NON ritardare l'audio, sia! Solo la finestra che ti consente di ascoltare l'audio verrà aperta in un secondo momento ...
Occorre considerare che:
- Se si va per il non-ritardato (super-reattiva) opzione di avvio e per qualsiasi motivo per cui si verifica un grande ritardo (tra il cattura di ora e l'inizio effettivo del lettore), sarà clip-off una grande parte di primo piano (fino a circa ~ 300ms/0,3 sec) della vostra audio. Ciò significa che quando avvii il tuo lettore inizierà subito ma non riprenderà dalla posizione che hai messo in pausa di recente ma piuttosto (fino a ~ 300 ms) più avanti nel tuo audio. Quindi la percezione acustica è che il gioco in pausa taglia fuori una parte del tuo audio mentre sei in movimento nonostante tutto sia perfettamente sincronizzato.
- Come il ritardo di avvio che fornite nel playAtTime : ora + myProvidedDelay chiamata di metodo è un valore costante fisso (che non ottiene regolato in modo dinamico per ospitare ritardo buffering o altra variabile parametri al sistema pesante carico) andando anche per l'opzione ritardata con un tempo di ritardo previsto più piccolo di circa ~ 300ms possono causare un ritaglio delle principali audio-campioni se il dispositivo-dipendente preparazione tempo supera il tempo di ritardo previsto.
- La quantità massima di ritaglio non aumenta (di progettazione) di questi ~ 300 ms. Per dimostrare basta forzare un ritaglio controllato (esatto per il campione) dei telai principali, ad es. aggiungendo un tempo di ritardo negativo a ora e si percepirà una crescente porzione audio ridotta aumentando questo valore negativo . Ogni valore negativo più grande di ~ 300 ms ottiene rettificato a ~ 300 ms. Quindi un ritardo negativo disponibile di 30 secondi si causare lo stesso comportamento come un valore negativo di 10, 6, 3 o 1 secondi, e naturalmente anche compresi negativi 0,8, 0,5 secondi giù a ~ 0,3
Questo esempio è utile per scopi dimostrativi, ma i valori di ritardo negativo non devono essere utilizzati nel codice di produzione.
ATTENZIONE:
La cosa più importante di tutto in una configurazione multi-giocatore è quello di mantenere il vostro player.pause in sincronia. A giugno 2016 non esiste ancora una strategia di uscita sincronizzata in AVAudioPlayerNode.
Solo un piccolo metodo di look-up o il logout qualcosa alla console in-tra due player.pause chiamate potrebbe costringere quest'ultimo uno da eseguire uno o più frame/campione (s) entro e non oltre il precedente. Quindi il tuo giocatore non si fermerebbe nella stessa posizione relativa nel tempo. E soprattutto - diversi dispositivi comporterebbero un comportamento diverso ...
Se ora li avvii nel modo sopra citato (sincronizzato), queste posizioni del giocatore attuale fuori sincrono della tua ultima pausa otterranno sicuramente forza -sync'ed al tuo nuovo now -posizionamento ad ogni playAtTime: - che essenzialmente significa che stai propagando il campione/i frame persi in futuro ad ogni nuovo avvio del tuo lettore. Questo naturalmente aggiunge a ogni nuovo ciclo di avvio/pausa e allarga il divario. Fatelo cinquanta o cento volte e ottenete già un buon effetto delay senza usare un effetto-unità audio ;-)
Dato che non abbiamo alcun controllo (su questo sistema) su questo fattore, l'unico rimedio è per mettere tutte le chiamate a player.pause diritte una dopo l'altra in una sequenza stretta senza nulla tra loro, come si può vedere negli esempi di seguito. Non gettarli in un ciclo for o qualcosa di simile - questa sarebbe una garanzia per finire fuori sincrono alla prossima pausa/avvio del tuo lettore ...
Se mantenere queste chiamate insieme è un Una soluzione al 100% perfetta o il run-loop sotto un grande carico della CPU potrebbe per caso interferire e forzare-separare le chiamate di pausa l'una dall'altra e causare frame drop - non so - almeno in settimane a fare i conti con l'API di AVAudioNode I non potrebbe in alcun modo forzare il mio multigiocatore per uscire fuori sincrono - tuttavia, non mi sento ancora molto comodo o sicuro con questa soluzione di pausa casuale non sincronizzata, ...
Codice-esempio e alternativa:
Se il motore è già in esecuzione hai un @property lastRenderTime in AVAudioNode - superclasse del lettore - Questo è il tuo biglietto per il 100% del campione-frame sync accurate. ..
AVAudioFormat *outputFormat = [playerA outputFormatForBus:0];
const float kStartDelayTime = 0.0; // seconds - in case you wanna delay the start
AVAudioFramePosition now = playerA.lastRenderTime.sampleTime;
AVAudioTime *startTime = [AVAudioTime timeWithSampleTime:(now + (kStartDelayTime * outputFormat.sampleRate)) atRate:outputFormat.sampleRate];
[playerA playAtTime: startTime];
[playerB playAtTime: startTime];
[playerC playAtTime: startTime];
[playerD playAtTime: startTime];
[player...
Tra l'altro - si può ottenere lo stesso risultato esatto campione-frame 100% con le classi/AVAudioRecorder AVAudioPlayer ...
NSTimeInterval startDelayTime = 0.0; // seconds - in case you wanna delay the start
NSTimeInterval now = playerA.deviceCurrentTime;
NSTimeIntervall startTime = now + startDelayTime;
[playerA playAtTime: startTime];
[playerB playAtTime: startTime];
[playerC playAtTime: startTime];
[playerD playAtTime: startTime];
[player...
Senza startDelayTime il primo 100-200ms di tutti i giocatori avranno tagliato fuori perché il comando di start ha effettivamente il suo tempo per il ciclo di esecuzione, anche se i giocatori hanno già iniziato (o meglio, stato programmato) 100 % in sincronizzazione a ora. Ma con un startDelayTime = 0,25 sei a posto. E non dimenticare mai prepareToPlay i tuoi giocatori in anticipo in modo che al momento dell'avvio non sia necessario eseguire alcun buffering o configurazione aggiuntivi: avviarli ragazzi ;-)
Bene, hai più nodi giocatore e su ognuno devi chiama il metodo 'play'.La mia ipotesi è che questa chiamata inizi immediatamente l'elaborazione e il lancio di materiale in audio thread. Poichè ogni 'play' è chiamato in momenti diversi probabilmente sta causando questo ritardo. Se il mio ragionamento è corretto e se è possibile avviare i nodi giocatore in un batch non lo so. In realtà sto utilizzando il ticket di supporto per gli sviluppatori Apple in cui ho fatto una domanda simile. Se avrò una risposta definitiva, tornerò su questa domanda. – Kegluneq
Il problema non è affatto il *** playAt: ***. È esattamente pensato per il lancio in gruppo. Devi solo usare un tempo di ancoraggio concreto. Vedere la mia risposta in basso ... – mramosch
Quindi, nonostante la soluzione in ANSWER il problema rimarrà se un dispositivo richiede più di 0,1 secondi per ottenere il suo atto insieme. Questi sono numeri magici purché non li pianifichino correttamente ... – mramosch