Медленный запуск для 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 и быть агрессивным относительно их очистки в условиях низкой памяти?

Ответ 1

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

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

Ответ 2

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

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

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

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

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

Ответ 3

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

Импортируйте платформу AudioToolbox

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

SystemSoundID mySound;

В вашем файле .m реализуйте его в методе 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;
}

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

AudioServicesPlaySystemSound(mySound);

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

Ответ 4

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

https://github.com/nicklockwood/SoundManager

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

Ответ 5

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

Короче говоря, предварите звуковую систему, загрузив и играя короткий, "пустой" звук, прежде чем делать что-либо еще. Код выглядит так для короткого mp3 файла, который я предварительно загружаю в свой метод контроллера viewDidLoad моего контроллера вида .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 файлах", чтобы найти и загрузить тот, который уже был сконструирован кем-то другим.

Ответ 6

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

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

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

Ответ 7

Здесь представлено простое расширение 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()
    }
}

Ответ 8

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

Ответ 9

Ответ

AVAudioPlayer *audioplayer;
[audioplayer prepareToPlay];