Início lento para o AVAudioPlayer na primeira vez em que um som é reproduzido

Estou tentando eliminar o atraso de boot ao reproduzir um arquivo de áudio (muito curto – menos de 2 segundos) via AVAudioPlayer no iPhone.

Primeiro, o código:

NSString *audioFile = [NSString stringWithFormat:@"%@/%@.caf", [[NSBundle mainBundle] resourcePath], @"audiofile"]; NSData *audioData = [NSData dataWithContentsOfMappedFile:audioFile]; NSError *err; AVAudioPlayer *audioPlayer = [(AVAudioPlayer*)[AVAudioPlayer alloc] initWithData:audioData error:&err]; audioPlayer.delegate = self; [audioPlayer play]; 

Eu também implemento o método audioPlayerDidFinishPlaying para liberar o AVAudioPlayer assim que eu terminar.

A primeira vez que executo o áudio, o atraso é palpável – pelo menos 2 segundos. No entanto, depois disso, o som é reproduzido imediatamente. Eu suspeito que o culpado, então, é o [NSData dataWithContentsOfMappedFile] demorando muito tempo lendo inicialmente a partir do flash, mas depois sendo rápido em leituras posteriores. Não sei como testar isso, no entanto.

É esse o caso? Em caso afirmativo, devo apenas pré-armazenar em cache os objects NSData e ser agressivo sobre limpá-los em condições de pouca memory?

O atraso parece estar relacionado à instanciação do AVAudioPlayer pela primeira vez. Se eu carregar qualquer áudio, execute [audioPlayer prepareToPlay] e, em seguida, libere-o imediatamente, os tempos de carregamento de todo o meu outro áudio estão muito próximos de serem imperceptíveis. Então agora eu estou fazendo isso em applicationDidFinishLaunching e tudo mais corre bem.

Não consigo encontrar nada sobre isso nos documentos, mas certamente parece ser o caso.

Aqui está o que eu fiz (em um tópico separado):

 [audioplayer start] [audioplayer stop] self.audioplayer = audioplayer 

[audioplayer prepareToPlay] parece ser um método asynchronous, então você não pode ter certeza quando retorna se o áudio estiver pronto para tocar.

No meu caso, eu chamo o início para realmente começar a jogar – isso parece ser síncrono. Então eu paro imediatamente. No simulador de qualquer maneira eu não ouço nenhum som saindo dessa atividade. Agora que o som está “realmente” pronto para jogar, atribuo a variável local a uma variável de membro para que o código fora do segmento tenha access a ela.

Devo dizer que acho surpreendente que, mesmo no iOS 4, leve apenas 2 segundos para carregar um arquivo de áudio com apenas 4 segundos de duração ….

Se o seu áudio tiver menos de 30 segundos de duração e estiver no formato PCM ou IMA4 linear e estiver incluído como .caf, .wav ou .aiff, você poderá usar os sons do sistema:

Importe o framework do AudioToolbox

Em seu arquivo .h crie esta variável:

 SystemSoundID mySound; 

Em seu arquivo .m, implemente-o em seu método init:

 -(id)init{ if (self) { //Get path of VICTORY.WAV <-- the sound file in your bundle NSString* soundPath = [[NSBundle mainBundle] pathForResource:@"VICTORY" ofType:@"WAV"]; //If the file is in the bundle if (soundPath) { //Create a file URL with this path NSURL* soundURL = [NSURL fileURLWithPath:soundPath]; //Register sound file located at that URL as a system sound OSStatus err = AudioServicesCreateSystemSoundID((CFURLRef)soundURL, &mySound); if (err != kAudioServicesNoError) { NSLog(@"Could not load %@, error code: %ld", soundURL, err); } } } return self; } 

No seu método IBAction você chama o som com isto:

 AudioServicesPlaySystemSound(mySound); 

Isso funciona para mim, reproduz o som muito perto quando o botão é pressionado. Espero que isso ajude você.

Eu escrevi um wrapper simples para o AVAudioPlayer que fornece um método prepareToPlay que realmente funciona:

https://github.com/nicklockwood/SoundManager

Basicamente, ele apenas verifica seu aplicativo em busca de arquivos de som, escolhe um e o reproduz em volume zero. Isso inicializa o hardware de áudio e permite que o próximo som seja reproduzido instantaneamente.

Eu tomei uma abordagem alternativa que funciona para mim. Eu vi essa técnica mencionada em outro lugar (tho eu não me lembro no momento em que foi).

Resumindo, faça um pré-teste do sistema de som carregando e reproduzindo um som curto e “vazio” antes de fazer qualquer outra coisa. O código se parece com isso para um arquivo mp3 curto que eu pré- viewDidLoad na minha visão viewDidLoad método do controlador que é de 0,1 segundo duração:

  NSError* error = nil; NSString* soundfilePath = [[NSBundle mainBundle] pathForResource:@"point1sec" ofType:@"mp3"]; NSURL* soundfileURL = [NSURL fileURLWithPath:soundfilePath]; AVAudioPlayer* player = [[[AVAudioPlayer alloc] initWithContentsOfURL:soundfileURL error:&error] autorelease]; [player play]; 

Você pode criar seu próprio mp3 em branco, se quiser, ou fazer uma pesquisa no google em “mp3s em branco” para encontrar e baixar um já construído por outra pessoa.

Outra solução alternativa é criar um áudio curto e silencioso e reproduzi-lo na primeira vez que o player for iniciado.

  1. Defina o nível do microfone para 0 em Preferências do Sistema.
  2. Abra o QuickTime.
  3. Criar nova gravação de áudio (Arquivo> Nova gravação de áudio).
  4. Grave por 1 ou 2 segundos.
  5. Salvar / exportar o arquivo de áudio. (terá extensão .m4a e cerca de 2 kb de tamanho).
  6. Adicione o arquivo ao seu projeto e reproduza-o.

Se você não quiser criar um novo arquivo de som, baixe um arquivo de áudio curto e silencioso aqui: https://www.dropbox.com/s/3u45x9v72ic70tk/silent.m4a?dl=0

Aqui está uma simples extensão do Swift para o AVAudioPlayer, que usa a ideia de play-and-stop no volume 0 apresentada nas respostas anteriores. PrepareToPlay () infelizmente pelo menos para mim não fez o truque.

 extension AVAudioPlayer { private func initDelaylessPlayback() { volume = 0 play() stop() volume = 1 } convenience init(contentsOfWithoutDelay : URL) throws { try self.init(contentsOf: contentsOfWithoutDelay, fileTypeHint: nil) initDelaylessPlayback() } } 

Não sei ao certo, mas suspeito que o object NSData esteja sendo preguiçoso e carregando o conteúdo do arquivo sob demanda. Você pode tentar “trapacear” chamando [audioData getBytes:someBuffer length:1]; em algum ponto inicial para obtê-lo para carregar o arquivo antes que seja necessário.

A resposta é

 AVAudioPlayer *audioplayer; [audioplayer prepareToPlay];