Медленный запуск AVAudioPlayer при первом воспроизведении звука

Я пытаюсь устранить задержку запуска при воспроизведении (очень короткого - менее 2 секунд) аудиофайла через AVAudioPlayer на iPhone.

Во-первых, код:

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];

Я также реализую метод audioPlayerDidFinishPlaying, чтобы освободить AVAudioPlayer, как только я закончу.

При первом воспроизведении звука задержка ощутима - минимум 2 секунды. Однако после этого звук воспроизводится сразу. Я подозреваю, что виновником является то, что [NSData dataWithContentsOfMappedFile] сначала долго считывает из флеш-памяти, но затем быстро при последующих чтениях. Однако я не знаю, как это проверить.

Так ли это? Если да, следует ли мне просто предварительно кэшировать объекты NSData и быть агрессивными при их очистке в условиях нехватки памяти?


person John Biesnecker    schedule 22.05.2009    source источник
comment
Спасибо за информацию. Вы должны ответить на вопрос и принять свой ответ. Инициализация AVAudioPlayer при запуске гарантирует, что звук воспроизводится без задержки в остальной части приложения.   -  person brianegge    schedule 14.06.2010
comment
@johnbiesnecker воспроизводит любой звук с уровнем громкости 0, тогда все вызовы воспроизведения звука должны находиться внутри блока асинхронной глобальной очереди с приоритетом по умолчанию или высоким приоритетом, звук не должен воспроизводиться в основной очереди.   -  person Juan Boero    schedule 07.01.2016
comment
В дополнение к тому, что сказал @JuanPabloBoero, я также воспроизводил звук с нулевой громкостью при получении UIApplicationWillEnterForegroundNotification, в противном случае есть задержка после возобновления работы приложения из фона.   -  person Pang    schedule 17.01.2016
comment
@ Панг вау, я пропустил это, очень интересный момент.   -  person Juan Boero    schedule 17.01.2016


Ответы (9)


Задержка, похоже, связана с созданием экземпляра AVAudioPlayer в первый раз. Если я загружаю какой-либо звук, запускаю [audioPlayer prepareToPlay], а затем немедленно освобождаю его, время загрузки всего остального звука очень близко к незаметному. Итак, теперь я делаю это в applicationDidFinishLaunching, а все остальное работает хорошо.

Я не могу найти ничего об этом в документации, но, похоже, это так.

person John Biesnecker    schedule 21.06.2010
comment
Хм, я не знаю об этом. prepareToPlay у меня не сработал. prepareToPlay вроде как работает, но поскольку кажется, что он загружает аудио асинхронно, нет способа точно узнать, когда звук действительно готов к воспроизведению. См. Мой недавний ответ о решении, которое я придумал. - person Taylor Gautier; 15.03.2011
comment
Я также получаю задержку с AVQueuePlayer всякий раз, когда я впервые загружаю в него ресурс. Плеер инициализируется только один раз. Похоже на обычную проблему: stackoverflow .com / questions / 11171374 / - person Oren; 06.09.2014
comment
Он идеально подходит для меня, теперь мой инициализатор запускает prepareToPlay () над одним из звуков, и у меня больше нет задержки. Это некрасиво, но работает. - person Joel; 21.12.2020

Вот что я сделал (в отдельной теме):

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

[audioplayer prepareToPlay] кажется асинхронным методом, поэтому вы не можете быть уверены, когда он вернется, если звук действительно готов к воспроизведению.

В моем случае я вызываю start, чтобы начать играть - это выглядит синхронно. Тогда я немедленно прекращаю это. В любом случае в симуляторе я не слышу звука, исходящего от этого занятия. Теперь, когда звук «действительно» готов к воспроизведению, я назначаю локальную переменную переменной-члену, чтобы код вне потока имел к ней доступ.

Я должен сказать, что меня несколько удивляет, что даже на iOS 4 требуется около 2 секунд, чтобы просто загрузить аудиофайл длиной всего 4 секунды ....

person Taylor Gautier    schedule 15.03.2011

Если ваш аудиофайл имеет длину менее 30 секунд и имеет линейный формат PCM или IMA4 и упакован как .caf, .wav или .aiff, вы можете использовать системные звуки:

Импортируйте AudioToolbox Framework

В вашем .h файле создайте эту переменную:

SystemSoundID mySound;

В вашем файле .m реализуйте его в своем методе инициализации:

-(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;
}

В вашем методе IBAction вы вызываете звук следующим образом:

AudioServicesPlaySystemSound(mySound);

У меня это работает, воспроизводит звук, чертовски близкий к нажатию кнопки. Надеюсь, это тебе поможет.

person M Jesse    schedule 03.01.2012
comment
Это отличный ответ. Спасибо, что разместили это! - person SushiGrass Jacob; 07.12.2012

Я выбрал альтернативный подход, который мне подходит. Я видел эту технику, упомянутую в другом месте (хотя на данный момент я не помню, где это было).

Короче говоря, предварительно пролетите звуковую систему, загрузив и проиграв короткий «пустой» звук, прежде чем делать что-либо еще. Код выглядит следующим образом для короткого mp3-файла, который я предварительно загружаю в методе контроллера представленияviewDidLoad длительностью 0,1 секунды:

   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];  

Вы можете создать свой собственный пустой mp3-файл, если хотите, или выполнить поиск в Google по «пустым mp3-файлам», чтобы найти и загрузить тот, который уже был создан кем-то другим.

person hkatz    schedule 06.10.2011
comment
использование этого с включенным NSZombies показывает, что игроку будет отправлено сообщение об имплантации после освобождения. Я получил это, чтобы избежать этого, не выпуская его автоматически, а вместо этого вручную отправив ему сообщение [релиз плеера] в audioPlayerDidFinishPlaying: success: event (в обратном вызове ‹AVAudioPlayerDelegate›) - person unsynchronized; 21.12.2011
comment
далее до последнего комментария см. мой обновленный ответ на этот вопрос для решения исходного кода: stackoverflow.com/a/7426406/830899 - person unsynchronized; 21.12.2011

Я написал простую оболочку для AVAudioPlayer, которая предоставляет метод prepareToPlay, который действительно работает:

https://github.com/nicklockwood/SoundManager

По сути, он просто сканирует ваше приложение на наличие звуковых файлов, выбирает один и воспроизводит его с нулевой громкостью. Это инициализирует аудиооборудование и позволяет мгновенно воспроизвести следующий звук.

person Nick Lockwood    schedule 02.05.2012

Вот простое расширение Swift для AVAudioPlayer, которое использует идею воспроизведения и остановки при 0 громкости, представленную в предыдущих ответах. PrepareToPlay (), к сожалению, по крайней мере для меня, не помог.

extension AVAudioPlayer {
    private func initDelaylessPlayback() {
        volume = 0
        play()
        stop()
        volume = 1
    }

    convenience init(contentsOfWithoutDelay : URL) throws {
        try self.init(contentsOf: contentsOfWithoutDelay, fileTypeHint: nil)
        initDelaylessPlayback()
    }
}
person OwlOCR    schedule 14.08.2017

Другой обходной путь - создать короткий беззвучный звук и воспроизвести его при первом запуске проигрывателя.

  1. Установите уровень микрофона на 0 в Системных настройках.
  2. Откройте QuickTime.
  3. Создайте новую аудиозапись (Файл> Новая аудиозапись).
  4. Записывайте 1 или 2 секунды.
  5. Сохраните / экспортируйте аудиофайл. (он будет иметь расширение .m4a и размер около 2 КБ).
  6. Добавьте файл в свой проект и воспроизведите его.

Если вы не хотите создавать новый звуковой файл самостоятельно, вы можете загрузить короткий беззвучный аудиофайл здесь: https://www.dropbox.com/s/3u45x9v72ic70tk/silent.m4a?dl=0

person Adi Nugroho    schedule 25.11.2016

Я не знаю наверняка, но подозреваю, что объект NSData ленив и загружает содержимое файла по запросу. Вы можете попробовать «обмануть», позвонив [audioData getBytes:someBuffer length:1]; в какой-то момент, чтобы заставить его загрузить этот файл до того, как он понадобится.

person n8gray    schedule 23.05.2009

Ответ

AVAudioPlayer *audioplayer;
[audioplayer prepareToPlay];
person Ben    schedule 18.07.2010
comment
На самом деле [audioplayer prepareToPlay] не является ответом ... задержка, о которой я говорил, происходит, когда вы в первый раз выделяете и инициализируете объект AVAudioPlayer, прежде чем вы вызываете какой-либо из его методов. Однако это происходит только при первой загрузке. prepareToPlay просто предварительно загружает звук, но не устраняет задержку, о которой я действительно спрашивал. Между прочим, UIWebView демонстрирует то же поведение - если вы выделяете, а затем сразу освобождаете один, остальные загружаются очень быстро. - person John Biesnecker; 12.08.2010