
示例工程移动开发【免费下载链接】ndk-samplesAndroid NDK samples with Android Studio项目地址https://gitcode.com/gh_mirrors/nd/ndk-samples点击查看免费下载Native Audio 是 ndk-samples 仓库中一个以 C OpenSL ES 音频 API 为核心、通过 JNI 与 Java 层交互的完整音频示例覆盖了播放Buffer Queue / URI / Asset FD 三种数据源与录音两大能力。阅读本文后你将掌握 OpenSL ES 引擎与输出混音器的搭建流程、三种音频播放器与录音器的完整创建范式、采样率重采样与线程互斥的实战写法并了解为何 Android 11 起官方推荐迁移到 Oboe 库。示例定位与技术选型根据 native-audio/README.md 的说明该示例通过 JNI 在 C 侧调用 OpenSL ES API 完成声音的播放与录制其创建的录音器与播放器并不走 fast audio path快速音频路径。这意味着它是一个侧重功能完整性而非最低延迟的教学示例适合用来理解 OpenSL ES 的完整对象模型。示例同时展示了 Android Studio 的 CMake 插件 C 支持的工作流源码位于 native-audio/app/src/main/cpp/native-audio-jni.c构建配置见 native-audio/app/src/main/cpp/CMakeLists.txt。重要的弃用警告README 特别强调OpenSL ES 自 Android 11 起已被弃用deprecated官方推荐开发者改用 Oboe 库。这是阅读本示例时必须牢记的前提OpenSL ES 的 API 形态与对象模型仍有很强的学习价值但新项目应优先采用 hello-oboe 示例所展示的 Oboe 方案。前置条件与运行步骤README 给出的运行环境要求为Android Studio 2.2 并随附 NDK仓库当前配置对构建系统版本要求更高实际以工程内 gradle/libs.versions.toml 与各模块 Gradle 配置为准。完整启动流程下载并启动 Android Studio打开示例目录即仓库中的native-audio目录打开File/Project Structure...点击Download或Select NDK location配置 NDK 路径点击Tools/Android/Sync Project with Gradle Files同步 Gradle点击Run/Run app部署到设备。运行后界面包含 Hello / Android / Sawtooth 三个短音效按钮、内嵌 soundtrack背景音乐、混音器控制Reverb、Mute、URI 播放控制组播放/暂停/循环/声道/音量/声像、录音与回放按钮布局定义见 native-audio/app/src/main/res/layout/main.xml。应用整体架构引擎、输出混音器与三类播放器从 native-audio-jni.c 的全局对象可以看出 OpenSL ES 典型的对象层次Engine引擎engineObject/engineEngine一切对象的创建入口Output Mix输出混音器outputMixObject所有播放器的音频汇聚目标并挂载可选的环境混响Environmental Reverb接口三类播放器Buffer Queue PlayerbqPlayerObject播放内存中的 PCM 短音效URI PlayeruriPlayerObject播放网络/本地 URIAsset FD PlayerfdPlayerObject通过文件描述符播放 APK 内嵌资源Recorder录音器recorderObject通过SLAndroidSimpleBufferQueue接收 PCM 数据。引擎与输出混音器创建流程Java_com_example_nativeaudio_NativeAudio_createEngine展示了标准三步法// 创建引擎 result slCreateEngine(engineObject, 0, NULL, 0, NULL, NULL); // Realize 后才可用 result (*engineObject)-Realize(engineObject, SL_BOOLEAN_FALSE); // 获取 ENGINE 接口用于创建其他对象 result (*engineObject)-GetInterface(engineObject, SL_IID_ENGINE, engineEngine);随后通过CreateOutputMix创建输出混音器并将环境混响声明为非必需接口SL_BOOLEAN_FALSEconst SLInterfaceID ids[1] {SL_IID_ENVIRONMENTALREVERB}; const SLboolean req[1] {SL_BOOLEAN_FALSE}; result (*engineEngine)-CreateOutputMix(engineEngine, outputMixObject, 1, ids, req);取混响接口后用SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR预设石质走廊设置属性源码注释明确指出该接口可能因特性缺失、CPU 负载过高或未申请/授予MODIFY_AUDIO_SETTINGS权限而失败因此示例对失败结果选择忽略——这正是 AndroidManifest.xml 声明MODIFY_AUDIO_SETTINGS权限的原因。权限声明一览AndroidManifest.xml 声明了三个权限与功能一一对应权限用途对应功能RECORD_AUDIO创建音频录音器录音按钮MODIFY_AUDIO_SETTINGS使用环境混响等音频效果Reverb 按钮INTERNETURI 播放器访问网络资源URI soundtrackJava 侧NativeAudio.java对RECORD_AUDIO使用了运行时权限申请ActivityCompat.requestPermissionsonRequestPermissionsResult回调拒绝时会弹出 Toast 提示并允许用户重试。Buffer Queue 播放器内存 PCM 播放与重采样Buffer Queue 播放器用于播放三份内置 PCM 数据hello、android两份预录音频由 hello_clip.h 与 android_clip.h 以 C 字符串数组形式内嵌均为8 kHz 单声道 16 位有符号小端PCM以及运行时合成的锯齿波sawtoothBuffer8000 帧由__attribute__((constructor))在加载时生成。数据源配置createBufferQueueAudioPlayer中音频源配置如下SLDataLocator_AndroidSimpleBufferQueue loc_bufq { SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2}; // 最多 2 个 buffer SLDataFormat_PCM format_pcm { SL_DATAFORMAT_PCM, 1, // 单声道 SL_SAMPLINGRATE_8, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN};Java 侧在onCreate中通过AudioManager.getProperty(PROPERTY_OUTPUT_SAMPLE_RATE)与PROPERTY_OUTPUT_FRAMES_PER_BUFFER查询设备原生采样率与每缓冲帧数仅 API 17传入原生层。当sampleRate 0时if (bqPlayerSampleRate) { format_pcm.samplesPerSec bqPlayerSampleRate; // 采样率单位毫赫兹(milliHz) }源码注释指出一旦采样率设置为原生采样率将触发 fast audio path同时为兼容 fast path创建播放器时动态裁剪接口数量——fast path 不支持SL_IID_EFFECTSEND因此CreateAudioPlayer的接口个数在bqPlayerSampleRate ? 2 : 3间切换。简易重采样实现由于内置剪辑为 8 kHz、录音为 16 kHz而播放器可能以设备原生采样率运行createResampledBuf实现了一个仅支持整数倍上采样的朴素重采样器要求bqPlayerSampleRate % srcRate 0即目标采样率必须能被源采样率整除否则返回 NULL 回退到原始 8 kHz 播放采用样本复制sample-and-hold方式将每个源样本重复upSampleRate次播放录音回放CLIP_PLAYBACK时若无法重采样则退化为简单降采样每两个 16 kHz 样本取一个将 16 kHz 数据折半为 8 kHz。for (int sample 0; sample srcSampleCount; sample) { for (int dup 0; dup upSampleRate; dup) { *workBuf src[sample]; } }播放回调与多缓冲bqPlayerCallback是缓冲队列播放完成回调每次一个 buffer 播完若nextCount 0且数据有效则Enqueue下一块数据继续播放否则释放重采样缓冲并解锁互斥锁。注释明确说明流式播放时应至少预入队 2 个 buffer而本示例只入队一个巨型 buffer以简化逻辑。URI 播放器与 Asset FD 播放器URI 播放器createUriAudioPlayer使用SLDataLocator_URI定位数据源格式声明为SL_CONTAINERTYPE_UNSPECIFIED的 MIME 流。源码注释指出无效 URI 在 Android 上并非在创建时被检测而是在 prepare/prefetch 阶段报错因此创建后需检查Realize返回值失败时调用Destroy并返回JNI_FALSE。该播放器还演示了完整的控制接口获取SL_IID_PLAY播放/暂停、SL_IID_SEEK循环、SL_IID_MUTESOLO声道静音/独奏、SL_IID_VOLUME音量。Java 界面上的静音/独奏/音量/声像控制均通过一组getMuteSolo()/getVolume()辅助函数选择当前可用的播放器来路由其中音量以millibel千分之一贝尔为单位——SeekBar 从 100 递减时衰减量millibel (100 - progress) * -50。已知问题URI 流式播放损坏README 的 Known Issues 明确记录URI Player 流式播放存在缺陷broken对应 upstream issue #229。Java 代码中同样可见其影响——onCreate末尾对 SDK 19 的设备禁用了全部 URI 相关控件uri_soundtrack、pause_uri、loop_uri、声道、音量、声像等并注释内部 bug idb/29321867等待后续系统版本修复后重新开放。因此该部分代码在当前 Android 版本上主要用于演示 API 形态。Asset FD 播放器createAssetAudioPlayer演示了如何播放 APK 内嵌资源// 通过 JNI 获取 AAssetManager AAssetManager* mgr AAssetManager_fromJava(env, assetManager); AAsset* asset AAssetManager_open(mgr, utf8, AASSET_MODE_UNKNOWN); // 将 asset 打开为文件描述符 off_t start, length; int fd AAsset_openFileDescriptor(asset, start, length); AAsset_close(asset); // 以 SL_DATALOCATOR_ANDROIDFD 作为数据源创建播放器 SLDataLocator_AndroidFD loc_fd {SL_DATALOCATOR_ANDROIDFD, fd, start, length};这是嵌入式 soundtrack按钮的实现路径Java 侧传入AssetManager与文件名background.mp3其版权信息见 native-audio/app/src/main/assets/README.txt原生层打开文件描述符后创建播放器并默认开启整文件循环SetLoop(SL_BOOLEAN_TRUE, 0, SL_TIME_UNKNOWN)。录音器单缓冲采集与互斥保护createAudioRecorder的音频源为SL_IODEVICE_AUDIOINPUT输入设备数据接收端为SLAndroidSimpleBufferQueuePCM 格式固定为16 kHz 单声道 16 位小端。录音缓冲为recorderBuffer[16000 * 5]约 5 秒。SLDataLocator_IODevice loc_dev {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT, SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};startRecording的核心动作是先停止上一次录音并Clear缓冲队列 → 入队一个空 buffer →SetRecordState(SL_RECORDSTATE_RECORDING)启动采集。bqRecorderCallback在缓冲填满时停止录音SL_RECORDSTATE_STOPPED并将recorderSize置为有效长度播放回放CLIP_PLAYBACK则把这段 16 kHz 数据重采样或降采样到 8 kHz 后送入 Buffer Queue 播放器完成录-放闭环。并发保护音频引擎互斥锁示例用pthread_mutex_t实现录音与播放的互斥audioEngineLock源码注释说明了设计动机防止录音尚未结束又发起第二次录音/播放导致崩溃。所有入口selectClip、startRecording均使用pthread_mutex_trylock非阻塞尝试加锁失败即返回JNI_FALSE让上层稍后重试锁的释放分散在播放/录音回调与入队失败分支中shutdown时统一pthread_mutex_destroy。这种trylock 回调解锁的模式虽然在回调路径中手工管理锁较为繁琐但清晰演示了音频回调线程与 UI 线程之间的同步边界是理解原生音频引擎线程模型的良好切入点。JNI 绑定与 CMake 构建Java 侧通过System.loadLibrary(native-audio-jni)加载动态库见 NativeAudio.java 的静态初始化块并以public static native声明了createEngine、createBufferQueueAudioPlayer、createUriAudioPlayer、createAssetAudioPlayer、selectClip、enableReverb、createAudioRecorder、startRecording、shutdown等 18 个原生方法C 侧以Java_com_example_nativeaudio_NativeAudio_前缀逐一实现。构建配置 native-audio/app/src/main/cpp/CMakeLists.txt 非常精简cmake_minimum_required(VERSION 3.22.1) set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} -Wall) add_library(native-audio-jni SHARED native-audio-jni.c) target_link_libraries(native-audio-jni android log OpenSLES)三个链接库各司其职androidAAssetManager 等原生接口、log日志输出、OpenSLESOpenSL ES 音频 API。小结与迁移建议本示例完整覆盖了 OpenSL ES 的核心编程范式引擎 → 输出混音器 →播放器/录音器的对象层次、Buffer Queue / URI / AndroidFD 三种数据源、回调驱动的缓冲管理、以及 JNI 与 CMake 的集成方式。需要再次强调 README 中的结论OpenSL ES 已从 Android 11 起弃用新项目应使用 hello-oboe 中演示的 Oboe 库若需更低延迟的音频路径可参考本仓库 audio-echo 示例对 Oboe 的实际应用。对于学习目的本示例依然是理解 Android 原生音频对象模型的经典教材。赞分享示例工程移动开发【免费下载链接】ndk-samplesAndroid NDK samples with Android Studio项目地址https://gitcode.com/gh_mirrors/nd/ndk-samples点击查看免费下载相关推荐ndk-samples 之 Audio-Echo基于 OpenSL ES 低延迟音频路径的回声示例全解析ndk samples 之 Audio Echo基于 OpenSL ES 低延迟音频路径的回声示例全解析 本篇技术指南以 Android NDK 官方示例仓库示例工程移动开发Android NDK音频回声示例解析OpenSL ES低延迟实现Android NDK音频回声示例解析OpenSL ES低延迟实现 项目概述 这个Android NDK音频回声示例展示了如何利用OpenSL ES API在示例工程移动开发Android Native音频完全指南OpenSL ES、Native Audio与Oboe库3个样本对比ndk-samplesAndroid Native音频完全指南OpenSL ES、Native Audio与Oboe库3个样本对比ndk samples 在 Android 官示例工程移动开发上一篇如何快速掌握麻雀AI助手5分钟上手指南与实用技巧下一篇wvp-GB28181-pro 从部署到接入第一台摄像机3 步搭起自己的国标视频平台创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考