使用MediaCodec+OpenSL编写简单的音频播放器

article/2025/7/4 2:37:19

使用MediaCodec+OpenSL编写简单的音频播放器

前言

通过MediaCodec Native API 和OpenSL编写一个简单的音频播放器。可以解码并播放一个mp3文件.

流程

初始化

  1. 使用AMediaExtractor解析Mp3文件,它可以得到音频文件的格式、以及帧(未解码)。
  2. 根据得到的音频信息(channelCount,channelMask,sampleRate)等可以创建出OpenSL AudioPlayer来播放解码后的音频。
  3. 创建根据解析得到的音频mime,创建合适的MediaCodec编码器

解码放入消费队列

  1. 使用AMediaCodec_start开始解码。
  2. 使用AMediaCodec_dequeueInputBuffer获取到一个空的buffer。
  3. 使用AMediaExtractor_readSampleData获取到未解码的数据放入到buffer,读完之后需要使用AMediaExtractor_advance将指针前移,这样下一次再读的时候就是下一个帧数据了。
  4. 使用AMediaCodec_queueInputBuffer获取一个输入buffer的index,使用index通过AMediaCodec_getInputBuffer获取输入buffer。
  5. 使用AMediaCodec_dequeueOutputBuffer获取一个输出buffer的index,使用index通过AMediaCodec_getOutputBuffer获取对应的输出buffer。
  6. 将输出buffer的内容放入到一个消费队列,带openSL播放器消费。

读取消费队列传给openSL

  1. 将openSL的AudioPlayer状态设置为开始,并传入一个空的buffer.opensl播放器会进行回调来获取数据。
  2. 在回掉中读取消费队列的数据。

代码片段

初始化

JNIEXPORT jlong JNICALL
Java_com_blueberry_videoplayer_SLMediaCodecAudio_initialize(JNIEnv *env, jobject thiz,jobject assetManager,jstring res_path) {auto engine = new AudioPlayerEngine();env->GetJavaVM(&engine->javaVm);// 创建消费队列engine->buffer_queue_ = new SafeQueue<MyBuffer>();const char *fileName = env->GetStringUTFChars(res_path, NULL);auto cAssetManager = AAssetManager_fromJava(env, assetManager);engine->asset_ = AAssetManager_open(cAssetManager, fileName, AASSET_MODE_RANDOM);off_t start = 0, len = 0;auto fd = AAsset_openFileDescriptor(engine->asset_, &start, &len);engine->mediaExtractor_ = AMediaExtractor_new();AMediaExtractor_setDataSourceFd(engine->mediaExtractor_, fd, start, len);AMediaExtractor_getTrackCount(engine->mediaExtractor_);auto format = AMediaExtractor_getTrackFormat(engine->mediaExtractor_, 0);const char *mime = nullptr;AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, reinterpret_cast<const char **>(&mime));AMediaFormat_getInt64(format, AMEDIAFORMAT_KEY_DURATION, &engine->metaData_.duration_);AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &engine->metaData_.channelCount_);AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &engine->metaData_.sampleRate_);AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, &engine->metaData_.bitrate_);const char *formatStr = AMediaFormat_toString(format);LOGD("the file format is %s", formatStr);AMediaExtractor_selectTrack(engine->mediaExtractor_, 0);// 创建解码器,并配置engine->codec_ = AMediaCodec_createDecoderByType(mime);AMediaCodec_configure(engine->codec_, format, nullptr, nullptr, 0);// 创建openSL引擎SLresult result = slCreateEngine(&engine->slEngineObj_, 0, nullptr, 0, nullptr, nullptr);result = (*engine->slEngineObj_)->Realize(engine->slEngineObj_, false);result = (*engine->slEngineObj_)->GetInterface(engine->slEngineObj_, SL_IID_ENGINE,&engine->slEngineItf_);// 根据音频格式信息,构建音频播放器的输入格式SLAndroidDataFormat_PCM_EX_ sourceFormat{.formatType = SL_DATAFORMAT_PCM,.numChannels = static_cast<SLuint32>(engine->metaData_.channelCount_),// millherzt.sampleRate = static_cast<SLuint32>(engine->metaData_.sampleRate_) * 1000,.bitsPerSample =SL_PCMSAMPLEFORMAT_FIXED_16,.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16,.channelMask =  SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,.endianness = SL_BYTEORDER_LITTLEENDIAN,.representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT};engine->silent_buf_ = new uint8_t[2 * 2 * 80]; // 80 frame per buf.engine->silent_buf_size_ = 2 * 2 * 80;// 输入数据使用内存队列SLDataLocator_AndroidSimpleBufferQueue androidSimpleBufferQueueLocator{.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,.numBuffers = 4};SLDataSource slDataSource{.pLocator = &androidSimpleBufferQueueLocator,.pFormat = &sourceFormat,};SLObjectItf outputMixObj;result = (*engine->slEngineItf_)->CreateOutputMix(engine->slEngineItf_,&outputMixObj,0,nullptr,nullptr);result = (*outputMixObj)->Realize(outputMixObj, false);SLDataLocator_OutputMix outputMixLocator{.locatorType = SL_DATALOCATOR_OUTPUTMIX,.outputMix = outputMixObj,};SLDataSink slDataSink{.pLocator = &outputMixLocator,.pFormat =  nullptr,};SLInterfaceID interfaces[2]{SL_IID_VOLUME,SL_IID_BUFFERQUEUE};SLboolean isRequired[2]{SL_BOOLEAN_TRUE,SL_BOOLEAN_TRUE};(*engine->slEngineItf_)->CreateAudioPlayer(engine->slEngineItf_,&engine->slPlayerObj_,&slDataSource, &slDataSink,sizeof(interfaces) /sizeof(interfaces[0]),interfaces, isRequired);(*engine->slPlayerObj_)->Realize(engine->slPlayerObj_, SL_BOOLEAN_FALSE);(*engine->slPlayerObj_)->GetInterface(engine->slPlayerObj_, SL_IID_PLAY,&engine->slPlayItf_);(*engine->slPlayerObj_)->GetInterface(engine->slPlayerObj_,SL_IID_ANDROIDSIMPLEBUFFERQUEUE,&engine->androidSimpleBufferQueueItf_);result = (*engine->androidSimpleBufferQueueItf_)->RegisterCallback(engine->androidSimpleBufferQueueItf_, bufferQueueCallback, engine);assert(result == SL_RESULT_SUCCESS);result = (*engine->slPlayItf_)->SetPlayState(engine->slPlayItf_, SL_PLAYSTATE_STOPPED);// 创建解码线程pthread_create(&engine->thread_, nullptr, threadRun, engine);env->ReleaseStringUTFChars(res_path, fileName);return reinterpret_cast<long>(engine);
}

解码

在这里插入图片描述
解码的逻辑配上这张图会更好理解一些。以解码为例:我会通过调用AMediaCodec_dequeueInputBuffer向编码器获取到空的buffer.然后放入我们的数据,再重新塞给解码器。该过程累死一个传送带,它由上层api驱而转动。获取解码后的数据时,我们通过
AMediaCodec_dequeueOutputBuffer来获取一个装有解码数据的buffer.将数据copy出后,在调用AMediaCodec_releaseOutputBuffer将buffer释放。

if (engine->status_ == Status::STARTED) {// 开始解码AMediaCodec_start(engine->codec_);while (engine->status_ == STARTED) {if (!engine->inputEnd) {// 读区一个输入bufferauto index = AMediaCodec_dequeueInputBuffer(engine->codec_, TIMEOUT_US);if (index >= 0) {size_t output_size = 0;auto buffer = AMediaCodec_getInputBuffer(engine->codec_, index,&output_size);// 将为解码输入放入buffer                                         auto sampleSize = AMediaExtractor_readSampleData(engine->mediaExtractor_,buffer,output_size);if (sampleSize <= 0) {sampleSize = 0;engine->inputEnd = true;}auto presentationTimeUs = AMediaExtractor_getSampleTime(engine->mediaExtractor_);// 将buffer放会解码器auto status = AMediaCodec_queueInputBuffer(engine->codec_,index,0,sampleSize,presentationTimeUs,engine->inputEnd? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM: 0);if (status != AMEDIA_OK) {LOGD("status:%d", status);break;}// 前移,以便下次AMediaExtractor_readSampleData将获取到新的数据AMediaExtractor_advance(engine->mediaExtractor_);}}// decodewhile (!engine->outputEnd) {AMediaCodecBufferInfo bufferInfo;// 获取一个输出buffer indexauto index = AMediaCodec_dequeueOutputBuffer(engine->codec_, &bufferInfo,TIMEOUT_US);if (index >= 0) {LOGD("process output index >=0,%d", index);if (bufferInfo.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {engine->outputEnd = true;}size_t buffer_size = 0;// 获取输出的bufferauto buffer = AMediaCodec_getOutputBuffer(engine->codec_, index,&buffer_size);if (bufferInfo.size > 0) {auto buf = new uint8_t[bufferInfo.size];memcpy(buf, &buffer[bufferInfo.offset], bufferInfo.size);MyBuffer myBuffer;myBuffer.len_ = bufferInfo.size;myBuffer.buf_ = buf;// 将解码后的数据放入到消费队列engine->buffer_queue_->enqueue(myBuffer);}// 释放输出bufferAMediaCodec_releaseOutputBuffer(engine->codec_, index, false);LOGD("bufferQueueCallback:index >=0 ");break;} else {switch (index) {case AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED  : {auto format = AMediaCodec_getOutputFormat(engine->codec_);const char *strFormat = AMediaFormat_toString(format);const char *pcmEncoding = nullptr;AMediaFormat_delete(format);break;}case AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED: {break;}case AMEDIACODEC_INFO_TRY_AGAIN_LATER: {}default: {break;}}}} }

OpenSL消费队列

void bufferQueueCallback(SLAndroidSimpleBufferQueueItf androidSimpleBufferQueueItf, void *context) {auto engine = reinterpret_cast<AudioPlayerEngine * >(context);auto queue = engine->buffer_queue_;auto myBuffer = queue->dequeue();if (myBuffer.buf_ != nullptr) {// 获取到消费队列中解码后的数据,放入opensl的内存队列中(*engine->androidSimpleBufferQueueItf_)->Enqueue(engine->androidSimpleBufferQueueItf_,myBuffer.buf_,myBuffer.len_);} else {(*engine->androidSimpleBufferQueueItf_)->Enqueue(engine->androidSimpleBufferQueueItf_,engine->silent_buf_,1);}return;
}

消费队列的定义

#include <queue>
#include <mutex>
#include <condition_variable>template <class T>
class SafeQueue
{
private:std::queue<T> q;mutable std::mutex m;std::condition_variable c;public:SafeQueue(/* args */) : m(), q(), c(){}~SafeQueue(){}void enqueue(T t){std::lock_guard<std::mutex> lock(m);q.push(t);c.notify_all();}T dequeue(void){std::unique_lock<std::mutex> lock(m);c.wait(lock, [this]{ return !q.empty(); });T val = q.front();q.pop();return val;}
};

参考

https://developer.android.com/ndk/reference/group/media
https://developer.android.com/reference/android/media/MediaCodec#queueInputBuffer(int,%20int,%20int,%20long,%20int)


http://chatgpt.dhexx.cn/article/IcjbIUXO.shtml

相关文章

使用Android高性能音频--OpenSL ES和AAudio

AAudio的概念介绍: AAudio 是作为 OpenSL ES 库的轻量级原生 Android 替代项而开发。 与 OpenSL ES 相比&#xff0c;AAudio API 不仅较小&#xff0c;而且容易使用。 AAudio 是在 Android O 版本中引入的全新 Android C API。 因此 API 是专为需要低延迟的高性能音频应用而设…

opensl学习笔记

这个是看opensl文档看到的一个简单的使用示例图&#xff0c;单纯看代码的话很难理解opensl播放和采集的原理&#xff0c;可以结合图来理解。使用Audio Player来播放音频&#xff0c;DataSource类型为SLDataSource&#xff0c;例如安卓设备就是SL_DATALOCATOR_ANDROIDSIMPLEBUFF…

使用OpenSL直接播放mp3

使用OpenSL直接播放mp3 前言 通过使用OpenSL来播放一个mp3文件来学习openSL的使用方式。 设计 在android平台播放mp3方式有多种方式入使用MediaPlayer、AudioTrack、OpenSL、oboe等。根据使用MediaPlayer,AudioTrack的经验一个播放器需要有的基础功能有加载数据、开始、暂停…

音视频开发之旅(36) -FFmpeg +OpenSL ES实现音频解码和播放

目录 OpenSL ES基本介绍OpenSL ES播放音频流程代码实现遇到的问题资料收获 上一篇我们通过AudioTrack实现了FFmpeg解码后的PCM音频数据的播放&#xff0c;在Android上还有一种播放音频的方式即OpenSL ES, 什么是OpenSL ES&#xff0c;这个我们平时接触的很少&#xff0c;原因…

Android OpenSL ES 开发:Android OpenSL 介绍和开发流程说明

一、Android OpenSL ES 介绍 OpenSL ES (Open Sound Library for Embedded Systems)是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速API。它为嵌入式移动多媒体设备上的本地应用程序开发者提供标准化, 高性能,低响应时间的音频功能实现方法&#xff0c;并实现软/硬件…

Xcode7 网络请求报错:The resource could not be loaded because the App Transport Security policy requir...

Google后查证&#xff0c;iOS9引入了新特性App Transport Security (ATS)。详情&#xff1a;App Transport Security (ATS)新特性要求App内访问的网络必须使用HTTPS协议。但是现在公司的项目使用的是HTTP协议&#xff0c;使用私有加密方式保证数据安全。现在也不能马上改成HTTP…

【用户自定义气象站】

自定义气象站系统由数据采集器、传感器、总线模块、网络模块、供电模块等组成&#xff0c;可实现野外无人看守的情况下长期监测。用户可根据自己的需求&#xff0c;自定义测量指标&#xff0c;以达到观测目的。 自定义气象站测量指标 自定义气象站测量参数指标 总辐射、光合…

自动气象站设备的防雷要点

自动气象站设备&#xff0c;其工作场所几乎都是在户外&#xff0c;雨打风吹、电闪雷鸣、日晒雨淋&#xff0c;由于是雷雨天气&#xff0c;气象站也要连续工作。如何科学防雷&#xff0c;直接影响到气象站的运行与使用寿命。 自动气象站设备的工作要点是获取气象环境参数&#x…

12,桥接模式-露娜的召唤师技能

一,前言 7种结构型设计模式:桥接模式,适配器模式,装饰模式,组合模式,享元模式,外观模式,代理模式上篇我们说了装饰模式:动态地将责任附加到对象上,在不修改任何底层代码的情况下,为对象赋予新的职责开发中,我们经常会遇到一个类有两个或两个以上的维度经常在变化 如果我们使用…

浮标水质监测站是什么

浮标水质监测站是设立在河流、湖泊、水库、近岸海域等流域内进行现场水质自动监测的监测仪器&#xff0c;是以水质监测仪为核心&#xff0c;运用传感器技术&#xff0c;结合浮标体、电源供电系统、数据传输设备组成的放置于水域内的小型水质监测系统。用于连续自动监测被测水体…

【赛纳斯】EC Raman电化学拉曼光谱检测系统推动科研新突破

【前言】   近日,Angew在线发表了厦门大学李剑锋教授团队在设计用于氧还原反应的先进材料及改进催化剂的设计最 新综述文章。该论文综述了双金属纳米催化剂有序度对氧还原反应的影响。论文第 一作者为&#xff1a;Heng-Quan Chen,Huajie Ze,Mu-Fei Yue,论文共同通讯作者为&am…

Ethercat学习-从站源码移植

文章目录 简介移植源码1.源码结构2.GD32硬件接口准备1.SPI接口2.PDI中断配置3.Sync0中断配置4.Sync1中断配置5.定时器中断配置 3.移植准备4.源码移植1.修改头文件名2.ecatport.c文件修改1.SPI部分修改2.中断部分3.修改HW_Init()4.报错修改 3.myapp.c文件修改 5.其他 简介 移植…

小说php 站点源码下载,PTCMS小说站源码

必装环境&#xff1a;nginx(apache.iis 也可)&#xff0c;mysql,php5.6,memcached php5.6 安装扩展 memcache 新建站点&#xff0c;注意新建时&#xff0c;PHP 版本必须选择 PHP5.6&#xff0c;不然程序会报错 1.上传网站文件到网站目录&#xff0c;新建网站伪静态选择 thinkph…

PHP是什么

PHP 是服务器端脚本语言。 您应当具备的基础知识 在继续学习之前&#xff0c;您需要对以下知识有基本的了解&#xff1a; HTMLCSS 如果您希望首先学习这些项目&#xff0c;请在我们的 首页 访问这些教程。 PHP 是什么&#xff1f; PHP 代表 PHP: Hypertext PreprocessorPHP 是一…

国家地表水水质自动监测站坐标每四小时数据(共1952个监测站,含省份、城市、河流、流域、断面名称、监测时间、水温、pH、DO、CODMn、TP、TN、NH3-N、浊度等)

1.监测范围 国家地表水水质自动监测网1952 个水质自动监测站。2.监测项目 监测项目为国家水质自动监测站配备的监测指标&#xff0c;主要包括五参数(水温、pH、溶解氧、电导率和浊度)、氨氮、高锰酸盐指数、总氮、总磷&#xff0c;部分水站增测总有机碳、叶绿素a、藻密度、VOCs…

php 跨站脚本,Piwigo register.php页面多个跨站脚本漏洞

发布日期&#xff1a;2010-05-06 更新日期&#xff1a;2010-05-11 受影响系统&#xff1a; Piwigo project Piwigo 2.0.9 描述&#xff1a; -------------------------------------------------------------------------------- BUGTRAQ ID: 39958 CVE(CAN) ID: CVE-2010-1707…

基于Modbus RTU 485通信协议实现对PH、溶解氧传感器的数据采集

modbus rtu 485协议采用的是一主多从方式通信&#xff0c;主机是普中的stm32f103zet6开发板&#xff0c;从机是传感器。代码已经在实物上测试通过&#xff0c;并且也用modbus精灵测试通过了。如果你没有stm32基础的话&#xff0c;建议先去B站搜索“正点原子”了解一下485串口通…

3、基于51单片机的智能水箱控制系统-温度-PH值-水位(仿真+程序+原理图)

目录 基于51单片机的智能水箱控制系统1、主要功能2、实验结果3、仿真工程4、原理图5、程序源码6、资源获取 基于51单片机的智能水箱控制系统 1、主要功能 51单片机检测水箱内温度&#xff0c;ph值&#xff1b;使用pid算法控制温度到设置值&#xff1b;普通控制ph值到设定值&a…

如何下载y站视频

今天看到了一篇B站视频的下载方法&#xff0c;学习了下&#xff0c;然后去看了下y站是不是也能下下来&#xff0c;居然被我试出来了&#xff0c;嘿嘿 B站文章链接&#xff1a;https://blog.csdn.net/Enderman_xiaohei/article/details/94718494 然后看一下y站的&#xff0c;打…

基于STM32(HAL库)的水质检测(浑浊度、PH值、温度、手机APP显示、wifi上云)

本系统由通过wifi将浑浊度、PH值、温度采集的数据发送到手机APP&#xff0c;超过设定的阈值报警。 一、硬件材料清单&#xff1a; 1、STM32C8T6&#xff1a;控制器 2、OLED显示屏&#xff1a;显示传感器采集的数据 3、PH传感器&#xff1a;检测PH值 4、TDS传感器&#xff1…