OpenSL ES меняет источник звука без воссоздания аудиоплеера
У меня есть макет, который имеет около 60 кнопок, и каждая из них при нажатии воспроизводит свой аудиофайл. У меня есть все мои аудиофайлы в формате mp3 в папке активов, и для их воспроизведения я в основном использую тот же код, который используется в проекте "native-audio" примеров Google NDK: https://github.com/googlesamples/android-ndk
У меня есть 10 идентичных собственных функций (только с переменными с уникальным именем), которые работают следующим образом.
Функция для воспроизведения звука:
jboolean Java_com_example_nativeaudio_Fretboard_player7play(JNIEnv* env, jclass clazz, jobject assetManager, jstring filename)
{
SLresult result;
// convert Java string to UTF-8
const char *utf8 = (*env)->GetStringUTFChars(env, filename, NULL);
assert(NULL != utf8);
// use asset manager to open asset by filename
AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);
assert(NULL != mgr);
AAsset* asset = AAssetManager_open(mgr, utf8, AASSET_MODE_UNKNOWN);
// release the Java string and UTF-8
(*env)->ReleaseStringUTFChars(env, filename, utf8);
// the asset might not be found
if (NULL == asset) {
return JNI_FALSE;
}
// open asset as file descriptor
off_t start, length;
int fd = AAsset_openFileDescriptor(asset, &start, &length);
assert(0 <= fd);
AAsset_close(asset);
// configure audio source
SLDataLocator_AndroidFD loc_fd = {SL_DATALOCATOR_ANDROIDFD, fd, start, length};
SLDataFormat_MIME format_mime = {SL_DATAFORMAT_MIME, NULL, SL_CONTAINERTYPE_UNSPECIFIED};
SLDataSource audioSrc = {&loc_fd, &format_mime};
// configure audio sink
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&loc_outmix, NULL};
// create audio player
const SLInterfaceID ids[3] = {SL_IID_SEEK, SL_IID_MUTESOLO, SL_IID_VOLUME};
const SLboolean req[3] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
result = (*engineEngine)->CreateAudioPlayer(engineEngine, &p7PlayerObject, &audioSrc, &audioSnk,
3, ids, req);
assert(SL_RESULT_SUCCESS == result);
(void)result;
// realize the player
result = (*p7PlayerObject)->Realize(p7PlayerObject, SL_BOOLEAN_FALSE);
assert(SL_RESULT_SUCCESS == result);
(void)result;
// get the play interface
result = (*p7PlayerObject)->GetInterface(p7PlayerObject, SL_IID_PLAY, &p7PlayerPlay);
assert(SL_RESULT_SUCCESS == result);
(void)result;
if (NULL != p7PlayerPlay) {
// play
result = (*p7PlayerPlay)->SetPlayState(p7PlayerPlay, SL_PLAYSTATE_PLAYING);
assert(SL_RESULT_SUCCESS == result);
(void)result;
}
return JNI_TRUE;
}
Функция, чтобы остановить этот звук:
void Java_com_example_nativeaudio_Fretboard_player7stop(JNIEnv* env, jclass clazz)
{
SLresult result;
// make sure the asset audio player was created
if (NULL != p7PlayerPlay) {
// set the player's state
result = (*p7PlayerPlay)->SetPlayState(p7PlayerPlay, SL_PLAYSTATE_STOPPED);
assert(SL_RESULT_SUCCESS == result);
(void)result;
// destroy file descriptor audio player object, and invalidate all associated interfaces
(*p7PlayerObject)->Destroy(p7PlayerObject);
p7PlayerObject = NULL;
p7PlayerPlay = NULL;
}
}
с этим легко справиться, но я хочу минимизировать задержку и избежать необходимости (*engineEngine)->CreateAudioPlayer()
каждый раз, когда я хочу воспроизвести другой файл. Есть ли способ просто изменить audioSrc, используемый аудиоплеером, без необходимости каждый раз уничтожать и воссоздавать его с нуля?
В качестве бонуса, где я могу прочитать больше об этом материале? Кажется, довольно сложно найти какую-либо информацию об OpenSL ES где-либо.
1 ответ
Мы находимся в одной лодке, сейчас я тоже знакомлюсь с NDK и OpenSL ES. Мой ответ основан на моем опыте, полностью состоящем из ~2 дней экспериментов, поэтому могут быть более подходящие подходы, но информация может помочь вам на вашем пути.
У меня есть 10 идентичных собственных функций (только с переменными с уникальным именем), которые работают следующим образом.
Если я правильно понял ваш случай, вам не нужны дублирующие функции для этого. Единственное, что отличается в этих вызовах - это нажатие кнопки и, в конечном счете, воспроизводимый звук, который можно передать в качестве параметров через вызов JNI. Вы можете сохранить созданный проигрыватель и данные в глобально доступной структуре, чтобы вы могли извлекать их, когда вам нужно остановить / воспроизвести их, возможно, используя buttonId в качестве ключа к карте.
[..] но я хочу минимизировать задержку и избегать необходимости (*engineEngine)->CreateAudioPlayer() каждый раз, когда я хочу воспроизвести другой файл. Есть ли способ просто изменить audioSrc, используемый аудиоплеером, без необходимости каждый раз уничтожать и воссоздавать его с нуля?
Да, постоянное создание и уничтожение игроков обходится дорого и может привести к фрагментации кучи (как указано в спецификации OpenSL ES 1.0). Во-первых, я думал, что он DynamicSourceItf позволит вам переключать источники данных, но кажется, что этот интерфейс не предназначен для такого использования, по крайней мере в Android 6 это возвращает "неподдерживаемую функцию".
Я сомневаюсь, что создание проигрывателя для каждого уникального звука было бы хорошим решением, тем более что воспроизведение одного и того же звука несколько раз друг над другом (как это обычно бывает в игре) потребовало бы произвольного количества дополнительных игроков для этого же звука.,
Буферные очереди
Buffer Queues - очереди отдельных буферов, которые игрок будет обрабатывать во время игры. Когда все буферы обработаны, игрок "останавливается" (хотя его официальное состояние все еще "играет"), но возобновляется, как только новые буферы ставятся в очередь.
Это позволяет вам создавать столько игроков, сколько вам требуется перекрывающихся звуков. Когда вы хотите воспроизвести звук, вы перебираете эти проигрыватели, пока не найдете тот, который в данный момент не обрабатывает буферы (BufferQueueItf->GetState(...)
предоставляет эту информацию или обратный вызов может быть зарегистрирован, чтобы вы могли помечать игроков как "бесплатные"). Затем вы ставите в очередь столько буферов, сколько необходимо для вашего звука, и они немедленно начнут воспроизводиться.
Насколько я знаю, формат Buffer Queue заблокирован при создании. Поэтому вы должны убедиться, что у вас есть все входные буферы в одном и том же формате, или вы создаете разные Buffer Queue (и проигрыватели) для каждого формата.
Android Simple BufferQueue
Согласно документации Android NDK, ожидается, что интерфейс Buffer Queue в будущем претерпит значительные изменения. Они извлекли упрощенный интерфейс с большей частью функциональности Buffer Queue и назвали его AndroidSimpleBufferQueue. Ожидается, что этот интерфейс не изменится, что сделает ваш код более надежным.
Основная функциональность, которую вы теряете при использовании AndroidSimpleBuffer Queue, заключается в возможности использовать исходные данные не из PCM, поэтому вам придется декодировать файлы перед использованием. Это можно сделать в OpenSL ES, используя AndroidSimpleBuffer Queue в качестве приемника. Более поздние API имеют дополнительную поддержку, используя MediaCodec, и это реализация NDK NDKMedia (посмотрите пример с родным кодеком).
Ресурсы
Документация NDK содержит важную информацию, которую трудно найти где-либо еще. Вот специфическая страница OpenSL ES.
Это может быть около 600 страниц и трудно переварить, но спецификация OpenSL ES 1.0 должна быть вашим основным источником информации. Я настоятельно рекомендую прочитать главу 4, поскольку она дает хороший обзор того, как все работает. Глава 3 содержит немного больше информации о конкретном дизайне. Затем я просто прыгаю, используя функцию поиска, чтобы читать по интерфейсам и объектам по ходу дела.
Понимание OpenSL ES
После того, как вы поняли основные принципы работы OpenSL, все выглядит довольно просто. Существуют медиа-объекты (проигрыватели и рекордеры и т. Д.), А также источники данных (входы) и приемники данных (выходы). По сути, вы подключаете вход к объекту мультимедиа, который направляет обработанные данные к подключенному выходу.
Источники, приемники и медиа-объекты все задокументированы в спецификации, включая их интерфейсы. Имея эту информацию, вы просто выбираете нужные вам строительные блоки и соединяете их вместе.
Обновление 29.07.16
Из моих тестов кажется, что и Buffer Queue, и AndroidSimpleBuffer Queue не поддерживают данные не-PCM, по крайней мере, не в системах, которые я тестировал (Nexus 7 @ 6.01, NVidia Shield K1 @ 6.0.1), поэтому вам нужно будет декодировать ваши данные, прежде чем вы сможете использовать его.
Я пытался использовать NDK-версии MediaExtractor и MediaCodec, но есть несколько предостережений, на которые следует обратить внимание:
MediaExtractor, похоже, неправильно возвращает информацию UUID, необходимую для декодирования с помощью crypto, по крайней мере, для файлов, которые я тестировал.
AMediaExtractor_getPsshInfo
возвращаетnullptr
,API не всегда ведет себя как комментарии в заявке заголовка. Например, проверка EOS (конец потока) в MediaExtractor представляется наиболее надежной путем проверки количества возвращаемых байтов вместо проверки
AMediaExtractor_advance
Возвращаемое значение функции.
Я бы порекомендовал остаться на Java для процесса декодирования, так как эти API более зрелые, определенно более проверенные, и вы могли бы извлечь из этого больше функциональности. Если у вас есть буферы необработанных данных PCM, вы можете передать их в собственный код, что позволит вам уменьшить задержку.