gpt4 book ai didi

java - OpenSL ES无需重新创建音频播放器即可更改音频源

转载 作者:搜寻专家 更新时间:2023-11-01 08:34:11 25 4
gpt4 key购买 nike

我有一个布局,约有60个按钮,每一个,当按下,播放不同的音频文件。我的所有音频文件都作为MP3放在我的assets文件夹中,要播放它们,我基本上使用的代码与google ndk示例“native audio”项目中使用的代码相同:
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的任何信息。

最佳答案

我们在同一条船上,我现在也在熟悉ndk和opensl es。我的答案是基于我的经验,完全由~2天的实验组成,因此可能会有更好的方法,但信息可能会帮助你的方式。
我有10个相同的本机函数(只有唯一命名的变量)工作如下..
如果我正确地理解了你的情况,你就不需要有重复的功能。在这些调用中唯一不同的是按下的按钮和最终要播放的声音,这可以通过jni调用作为参数传递。您可以将创建的播放器和数据存储在一个全局可访问的结构中,以便在需要停止/重播时检索它,可以使用buttonid作为映射的键。
[…]但我想最小化延迟,避免每次想播放不同的文件时都必须执行(*engineengine)->createAudioPlayer()。有没有什么方法可以改变音频播放器使用的audiosrc,而不必每次都从零开始销毁和重新创建它?
是的,不断地创建和销毁播放器是昂贵的,并且可能导致堆的碎片化(如opensl es 1.0规范中所述)。首先,我认为dynamicsourceitf允许您切换数据源,但似乎这个接口不打算像那样使用,至少在android 6上返回“feature unsupported”。
我怀疑为每个独特的声音创建一个播放器是一个很好的解决方案,特别是因为多次在彼此之上播放相同的声音(例如,在游戏中很常见)将需要任意数量的其他播放器来播放相同的声音。
缓冲队列
bufferqueues是播放器在播放时将处理的单个缓冲区的队列。当所有缓冲区都被处理后,播放器“停止”(虽然它的官方状态仍然是“播放”),但是只要新的缓冲区被排队,它就会恢复。
这允许你做的是创建尽可能多的播放器重叠的声音,你需要。当你想播放一个声音时,你要在这些播放机上迭代,直到找到一个当前没有处理缓冲区的播放机(BufferQueueItf->GetState(...)提供了这个信息,或者可以注册一个回调,这样你就可以将播放机标记为“空闲的”)。然后,根据声音的需要将尽可能多的缓冲区放入队列,然后立即开始播放。
据我所知,缓冲队列的格式在创建时被锁定。因此,必须确保所有输入缓冲区的格式相同,或者为每个格式创建不同的缓冲队列(和播放器)。
Android简单缓冲队列
根据android ndk文档,bufferqueue接口在未来有望有重大变化。他们提取了一个具有bufferqueue大部分功能的简化接口,并将其命名为androidsimplebufferqueue。此接口不会更改,因此使您的代码更经得起未来的考验。
使用androidsimplebufferqueue松脱的主要功能是能够使用非pcm源数据,因此在使用之前必须对文件进行解码。这可以在opensl es中使用androidsimplebufferqueue作为接收器来完成。更新的api还支持使用mediacodec,它是ndk实现ndkmedia(查看本机编解码器示例)。
资源
ndk文档确实包含了一些在其他地方很难找到的重要信息。Here'sOpenSL ES特定页面。
它可能接近600页,难以消化,但OpenSL ES 1.0 Specification应该是您的主要信息来源。我强烈建议阅读第四章,因为它很好地概述了事情的运作方式。第三章有更多关于具体设计的信息。然后,我就使用搜索功能来阅读界面和对象。
了解OpenSL ES
一旦您理解了opensl工作原理的基本原理,它看起来就相当简单了。有媒体对象(播放器和记录器等)和数据源(输入)和数据接收器(输出)。实际上,您将一个输入连接到一个媒体对象,该媒体对象将处理的数据路由到其连接的输出。
源、汇和媒体对象都记录在规范中,包括它们的接口。有了这些信息,你真正需要做的就是挑选你需要的构建块,并将它们组合在一起。
2016年7月29日更新
从我的测试来看,bufferqueue和androidsimplebufferqueue似乎都不支持非pcm数据,至少在我测试过的系统(nexus 7@6.01,nvidia shield k1@6.0.1)上不支持,因此在使用之前需要对数据进行解码。
我试过使用ndk版本的mediaExtractor和mediacodec,但有几个注意事项:
MediaExtractor似乎无法正确返回使用加密解码所需的Uuid信息,至少对于我测试过的文件而言是这样。AMediaExtractor_getPsshInfo返回anullptr
api并不总是表现为头声明中的注释。例如,通过检查返回的字节数而不是检查AMediaExtractor_advance函数的返回值,检查mediaextractor中的eos(流的末尾)似乎是最可靠的。
我建议在解码过程中使用java,因为这些api更加成熟,测试也更加充分,而且您可能会从中获得更多的功能。一旦有了原始pcm数据的缓冲区,就可以将其传递给本机代码,从而减少延迟。

关于java - OpenSL ES无需重新创建音频播放器即可更改音频源,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38092837/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com