Медленный запуск AVAudioPlayer при первом воспроизведении звука
-
23-08-2019 - |
Вопрос
Я пытаюсь устранить задержку запуска при воспроизведении (очень короткого - менее 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 и быть агрессивным в отношении их очистки в условиях нехватки памяти?
Решение
Задержка, по-видимому, связана с созданием экземпляра AVAudioPlayer в первый раз.Если я загружаю какой-либо звук, запускаю [AudioPlayer prepareToPlay], а затем немедленно выпускаю его, время загрузки всех других моих аудиозаписей очень близко к незаметному.Так что теперь я делаю это в applicationDidFinishLaunching, и все остальное работает хорошо.
Я не могу найти ничего по этому поводу в документах, но, похоже, это действительно так.
Другие советы
Вот что я сделал (в отдельном потоке):
[audioplayer start]
[audioplayer stop]
self.audioplayer = audioplayer
[audioplayer prepareToPlay], похоже, является асинхронным методом, поэтому вы не можете быть уверены, когда он вернется, действительно ли аудио готово к воспроизведению.
В моем случае я вызываю start, чтобы действительно начать воспроизведение - похоже, это происходит синхронно.Тогда я немедленно прекращаю это.В любом случае, в симуляторе я не слышу никакого звука, исходящего от этого действия.Теперь, когда звук "действительно" готов к воспроизведению, я присваиваю локальную переменную переменной-члену, чтобы код вне потока имел к ней доступ.
Должен сказать, я нахожу несколько удивительным, что даже на iOS 4 загрузка аудиофайла длиной всего 4 секунды занимает около 2 секунд....
Если ваш звук длится менее 30 секунд и выполнен в линейном формате PCM или IMA4 и упакован в формате .caf, .wav или .aiff, вы можете использовать системные звуки:
Импортируйте фреймворк AudioToolbox
В вашем файле .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);
У меня это работает, звук воспроизводится чертовски близко к моменту нажатия кнопки.Надеюсь, это поможет вам.
Я выбрал альтернативный подход, который работает для меня.Я видел эту технику, упомянутую в другом месте (хотя на данный момент я не помню, где это было).
Короче говоря, выполните предварительную настройку звуковой системы, загрузив и воспроизведя короткий, "пустой" звук, прежде чем делать что-либо еще.Код выглядит следующим образом для короткого mp3-файла, который я предварительно загружаю в свой view controllerviewDidLoad
метод длительностью 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", чтобы найти и загрузить тот, который уже создан кем-то другим.
Я написал простую оболочку для AVAudioPlayer, которая предоставляет метод prepareToPlay, который действительно работает:
https://github.com/nicklockwood/SoundManager
По сути, он просто сканирует ваше приложение на наличие звуковых файлов, выбирает один и воспроизводит его с нулевой громкостью.Это инициализирует аудиооборудование и позволяет мгновенно воспроизвести следующий звук.
Другим обходным путем является создание короткого и бесшумного аудио и воспроизведение его при первом запуске проигрывателя.
- Установите уровень микрофона равным 0 в системных настройках.
- Откройте QuickTime.
- Создайте новую аудиозапись (Файл > Новая аудиозапись).
- Записывайте в течение 1 или 2 секунд.
- Сохраните / экспортируйте аудиофайл.(он будет иметь расширение .m4a и размер около 2 кб).
- Добавьте файл в свой проект и воспроизведите его.
Если вы не хотите создавать новый звуковой файл самостоятельно, вы можете загрузить короткий и тихий аудиофайл здесь : https://www.dropbox.com/s/3u45x9v72ic70tk/silent.m4a?dl=0
Вот простое расширение 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()
}
}
Я не знаю наверняка, но я подозреваю, что объект NSData ленив и загружает содержимое файла по требованию.Вы можете попробовать "обмануть", позвонив [audioData getBytes:someBuffer length:1];
на каком-то раннем этапе заставить его загрузить этот файл до того, как он понадобится.
Ответ таков
AVAudioPlayer *audioplayer;
[audioplayer prepareToPlay];