安卓音视频整理(一)——音频模块
这是关于安卓音视频的一个系列文章,大家可以从这里随意跳跃:
0.安卓音视频整理
1.安卓音视频整理(一)—— 音频模块
2.安卓音视频整理(二)—— 音视频编解码
3.安卓音视频整理(三)—— 图像模块
4.安卓音视频整理(四)—— 音视频播放器
5.安卓音视频整理(五)—— MediaSDK的封装
摘要:音频模块我将主要分 音频文件、音频录制、音频播放三大部分来叙述。音频文件将会重点描述音频的格式及编码;音频录制将介绍安卓中所有的可以录制音频的API;音频播放将介绍如何对音频文件进行播放。
1.音频文件
顾名思义,音频文件指的就是储存着音频的文件,其格式有 PCM、WAV、MP3、OGG、AAC、M4A等,之所以有不同格式的音频文件是因为其编码方式不同。
我们在日常生活中所用的大部分音频文件的编码都采用了PCM 编码,其能够达到音频数据最高保真的水平,因此能满足大众娱乐的各种需求。
可是,PCM 编码高保真的特性意味着要占用更大的存储空间,所以高音质和小存储成为了互相制约的问题,为了中和考虑这2方面,音频格式也就出现了非压缩(如:WAV)和 压缩(如:MP3)两类。
所谓 音频压缩 是指通过不同的计算方式,忽略人耳不易察觉的频段或者通过制造听觉上的错觉,大幅度降低音频数据的数量,而音质基本不变甚至更好。音频压缩分为了 有损压缩 (如:AAC、M4A)和 无损压缩(如:FLAC、APE) 2种,如下图所示:
注:再次强调一下上一篇文章里的内容,PCM不是一种音频格式,它是声音文件的元数据,也就是声音的数据,用的是PCM编码,没有文件头。需要经过某种格式的压缩、编码算法处理以后,再加上这种格式的文件头,才是这种格式的音频文件。
好了,接下来我们会围绕部分音频格式,重点讲解安卓系统种用到的 音频录制与播放功能。
2.音频录制
安卓系统提供了4种音频录制的API,开发者可以根据应用场景,选择不同的API实现音频文件的录制功能,其中包括:
- 通过Intent调用系统的录音器功能
- MediaRecorder 录制音频
- AudioRecorder 录制音频
- OpenSL ES 录制音频
安卓下录制音频需要录音权限、文件写入权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.RECORD_AUDIO" />
2.1 通过Intent调用系统的录音器功能
① 通过Intent调用系统的录音器功能,录制完毕后 保存的音频文件路径 uri 会在onActivityResult中返回。
private final static int REQUEST_RECORDER = 1;
private Uri uri;//去录制
public void startRecord(){Intent intent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);startActivityForResult(intent,REQUEST_RECORDER);
}
② 在onActivityResult获取返回信息
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (resultCode == RESULT_OK && REQUEST_RECORDER == requestCode){//返回 uriuri = data.getData();}
}
2.2 通过MediaRecorder来录制音频
MediaRecorder 类可用于音频和视频的录制。虽然已经集成了录音、编码、压缩,但只支持少量的音频格式录制,大概有.aac .amr .3gp等;不支持 wav、mp3。示例如下:
private MediaRecorder mRecorder;private boolean isRecording = false;private String mFileName="text.aac";/*** 开始录制*/private void startRecord() {try {// 如果正在录音,那么停止并释放MediaRecorderif (isRecording && mRecorder != null) {mRecorder.stop();mRecorder.release();mRecorder = null;}// 实例化 MediaRecordermRecorder = new MediaRecorder();// 设置声音源为麦克风mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 设置输出格式为 aacmRecorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);// 设置输出文件路径,mFileName为录音音频输出路径mRecorder.setOutputFile(mFileName);// 设置声音解码 AACmRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);// 媒体录制器准备mRecorder.prepare();// 开始录制mRecorder.start();isRecording = true;} catch (IOException e) {e.printStackTrace();}}/*** 停止录制*/private void stopRecord() {if (isRecording && mRecorder != null) {mRecorder.stop();mRecorder.release();mRecorder = null;}}
2.3 通过AudioRecorder来录制音频
输出的是 PCM 的声音数据,如果要得到直接能播放的文件,则需要经过编码;AudioRecorder的特点是可以捕获到声音的原始数据——音频流,可以边录制边对音频数据进行处理,比如编码、变声、混音等。示例如下:
// 录音状态private boolean isRecording = true;/*** 开始录音*/private void startRecord(){// 录音为耗时操作,开线程new Thread(){@Overridepublic void run() {// 设置音频输入源int audioSource = MediaRecorder.AudioSource.MIC;// 设置音频采样率int sampleRate = 44100;// 设置音频声道数int channelConfig = AudioFormat.CHANNEL_IN_STEREO;//双声道// 设置音频采样位数int audioFormat = AudioFormat.ENCODING_PCM_16BIT;// 获取最小缓存区大小int minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);// 创建录音对象AudioRecord audioRecord = new AudioRecord(audioSource, sampleRate, channelConfig, audioFormat, minBufferSize);try {// 开始audioRecord.startRecording();isRecording = true;while (isRecording) {int readSize = audioRecord.read(buffer, 0, minBufferSize);//buffer 即:录音得到的音频原始 PCM 数据,可以对音频数据进行各种处理,如:混音、变调、降噪、编码、边录边播、保存等操作//readSize 即:每次输出的音频原始 PCM 数据确切长度}// 录音停止audioRecord.stop();// 释放audioRecord.release();} catch (IOException e) {e.printStackTrace();}}}.start();}/*** 停止录音*/private void stop() {// 停止录音isRecording = false;}
2.4 通过OpenSL ES来录制音频
重点来了,后续我们的播放器SDK将要用 OpenSL ES 来处理整个音频部分的录制与播放。
OpenSL ES (Open Sound Library for Embedded Systems)是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速API。它为嵌入式移动多媒体设备上的本地应用程序开发者提供标准化, 高性能,低响应时间的音频功能实现方法,并实现软/硬件音频性能的直接跨平台部署,降低执行难度,促进高级音频市场的发展。简单来说OpenSL ES是一个嵌入式跨平台免费的音频处理库。
OpenSL ES 的开发流程主要有如下6个步骤:
- 创建接口对象
- 设置混音器
- 创建录音器(播放器)
- 设置缓冲队列和回调函数
- 设置播放状态
- 启动回调函数
OpenSL ES 可以根据开发者的需要,定制几乎可以满足一切安卓音频所需要的功能,十分的强大。我们采用NDK开发来,从而对OpenSL ES的音频功能进行逐个实现。
首先,要使用OpenSL ES的API,需要引入OpenSL ES的头文件,代码如下:
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
我发现讲概念好像不怎么通俗易懂,哎!来来来,直接看我封装的,上 代码:
OpenSLHelp.h
//
// Created by 86158 on 2019/8/22.
//#ifndef YorkAUDIO_OPENSLHELP_H
#define YorkAUDIO_OPENSLHELP_H
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <tgmath.h>
class OpenSLHelp {public://引擎接口SLObjectItf engineObject;SLEngineItf engineInterface;//混音器SLObjectItf outputMixObject;SLEnvironmentalReverbItf outputMixEnvironmentalReverb;SLEnvironmentalReverbSettings reverbSettings;//pcm播放SLObjectItf playerObject;SLPlayItf playInterface;SLVolumeItf pcmVolumePlay ;SLMuteSoloItf pcmMutePlay ;//pcm录音SLObjectItf recorderObject;SLRecordItf recorderInterface;//缓冲器队列接口SLAndroidSimpleBufferQueueItf queueInterface;SLDataLocator_AndroidSimpleBufferQueue android_queue;SLDataFormat_PCM formatPCM;SLDataLocator_OutputMix outputMix;SLDataLocator_IODevice device;SLDataSource source;SLDataSink sink;public:OpenSLHelp();~OpenSLHelp();void openSLHelperInit();void playDestroy();void recordDestroy();SLuint32 getChannelMask(int numChannels);SLuint32 getCurrentSampleRateForOpensles(int sample_rate);SLuint32 getCurrentFormatForOpensles(int file_bit);
};#endif//YorkAUDIO_OPENSLHELP_H
OpenSLHelp.cpp
//
// Created by 86158 on 2019/8/22.
//#include "OpenSLHelp.h"OpenSLHelp::OpenSLHelp() {}OpenSLHelp::~OpenSLHelp() {}void OpenSLHelp::openSLHelperInit() {engineObject = NULL;engineInterface = NULL;recorderObject = NULL;recorderInterface = NULL;outputMixObject = NULL;playerObject = NULL;playInterface = NULL;outputMixEnvironmentalReverb = NULL;reverbSettings = SL_I3DL2_ENVIRONMENT_PRESET_STONECORRIDOR;//初始化openSLslCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);(*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);(*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface);
}void OpenSLHelp::playDestroy() {if (playerObject != NULL) {(*playerObject)->Destroy(playerObject);playerObject = NULL;playInterface = NULL;pcmMutePlay = NULL;pcmVolumePlay = NULL;}if (playerObject == NULL&&outputMixObject!=NULL){(*outputMixObject)->Destroy(outputMixObject);outputMixObject=NULL;}if (playerObject == NULL && recorderObject == NULL) {if (queueInterface != NULL) {queueInterface = NULL;}if (engineObject != NULL) {(*engineObject)->Destroy(engineObject);engineObject = NULL;engineInterface = NULL;}}
}void OpenSLHelp::recordDestroy() {if (recorderObject != NULL) {(*recorderObject)->Destroy(recorderObject);recorderObject = NULL;recorderInterface = NULL;}if (playerObject == NULL && recorderObject == NULL) {if (queueInterface != NULL) {queueInterface = NULL;}if (engineObject != NULL) {(*engineObject)->Destroy(engineObject);engineObject = NULL;engineInterface = NULL;}}
}
//通道设置
SLuint32 OpenSLHelp::getChannelMask(int numChannels) {return numChannels > 1 ? SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT: SL_SPEAKER_FRONT_CENTER;
}
//位深设置
SLuint32 OpenSLHelp::getCurrentFormatForOpensles(int file_format) {SLuint32 bit;switch (file_format) {case 8:bit = SL_PCMSAMPLEFORMAT_FIXED_8;break;case 16:bit = SL_PCMSAMPLEFORMAT_FIXED_16;break;case 24:bit = SL_PCMSAMPLEFORMAT_FIXED_24;break;case 32:bit = SL_PCMSAMPLEFORMAT_FIXED_32;break;default:bit = SL_PCMSAMPLEFORMAT_FIXED_16;}return bit;
}
//采样率设置
SLuint32 OpenSLHelp::getCurrentSampleRateForOpensles(int sample_rate) {SLuint32 rate;switch (sample_rate) {case 8000:rate = SL_SAMPLINGRATE_8;break;case 11025:rate = SL_SAMPLINGRATE_11_025;break;case 12000:rate = SL_SAMPLINGRATE_12;break;case 16000:rate = SL_SAMPLINGRATE_16;break;case 22050:rate = SL_SAMPLINGRATE_22_05;break;case 24000:rate = SL_SAMPLINGRATE_24;break;case 32000:rate = SL_SAMPLINGRATE_32;break;case 44100:rate = SL_SAMPLINGRATE_44_1;break;case 48000:rate = SL_SAMPLINGRATE_48;break;case 64000:rate = SL_SAMPLINGRATE_64;break;case 88200:rate = SL_SAMPLINGRATE_88_2;break;case 96000:rate = SL_SAMPLINGRATE_96;break;case 192000:rate = SL_SAMPLINGRATE_192;break;default:rate = SL_SAMPLINGRATE_44_1;}return rate;}
怎么用呢?,这是录制部分的代码
YorkRecorder.cpp
//
// Created by 86158 on 2019/8/21.
//#include "YorkRecorder.h"YorkRecorder::YorkRecorder(YorkRecordstatus *recordstatus, OpenSLHelp *openSLHelp,RecordCallJava *callJava, int samplingRate,SLuint32 numChannels, int format) {this->recordstatus = recordstatus;this->openSLHelp = openSLHelp;this->recordCallJava = callJava;this->samplingRate = samplingRate;this->numChannels = numChannels;this->format = format;recorderSize = samplingRate * numChannels * 120 / 1000;//兼容视频录制yorkJiangzao = new YorkJiangzao();//环境降噪算法yorkRenSheng = new YorkRenSheng();//人声增强算法yorkLowCut = new YorkLowCut();//低切算法yorkGain = new YorkGain();//增益调节算法
}YorkRecorder::~YorkRecorder() {}void pcmBufferRecordCallBack(SLAndroidSimpleBufferQueueItf bf, void *context) {YorkRecorder *yorkRecorder = (YorkRecorder *) context;if (yorkRecorder != NULL) {if (!yorkRecorder->recordstatus->recording) {(*yorkRecorder->openSLHelp->recorderInterface)->SetRecordState(yorkRecorder->openSLHelp->recorderInterface, SL_RECORDSTATE_STOPPED);fclose(yorkRecorder->pcmFile);} else {//降噪if (yorkRecorder->jz_rate < 0) {yorkRecorder->yorkJiangzao->doJiangzao(yorkRecorder->jz_rate,yorkRecorder->buffer,yorkRecorder->buffer,yorkRecorder->recorderSize,yorkRecorder->format);}//人声增强if (yorkRecorder->rs_rate != 0) {yorkRecorder->yorkRenSheng->doRenSheng(yorkRecorder->rs_rate,yorkRecorder->buffer,yorkRecorder->buffer,yorkRecorder->recorderSize,yorkRecorder->format);}//低切if (yorkRecorder->lc_rate != 0) {yorkRecorder->yorkLowCut->doLowCut(yorkRecorder->lc_rate,yorkRecorder->buffer,yorkRecorder->buffer,yorkRecorder->recorderSize,yorkRecorder->format,yorkRecorder->samplingRate);}//增益调节if (yorkRecorder->gain_rate != 1) {yorkRecorder->yorkGain->setGain(yorkRecorder->gain_rate, yorkRecorder->buffer,yorkRecorder->buffer,yorkRecorder->recorderSize,yorkRecorder->format);}//返回数据yorkRecorder->recordCallJava->onCallRecordData(CHILD_THREAD, yorkRecorder->buffer,yorkRecorder->recorderSize);//做边录边播还有分贝yorkRecorder->recordCallJava->onCallRecordPlaydata(CHILD_THREAD, yorkRecorder->buffer,yorkRecorder->recorderSize,yorkRecorder->format);//显示时间if (yorkRecorder->recordstatus->recordingsave) {yorkRecorder->recordCallJava->onCallRecordTime(CHILD_THREAD,yorkRecorder->timer.getTimeShow());}//取完数据,需要调用Enqueue触发下一次数据回调(*yorkRecorder->openSLHelp->queueInterface)->Enqueue(yorkRecorder->openSLHelp->queueInterface, yorkRecorder->buffer,yorkRecorder->recorderSize);}}
}void YorkRecorder::recordOpenSLES() {if (LOG_DEBUG) {LOGD("InitRecord : -- sample_rate= %d ,channels= %d ,format= %d", samplingRate, numChannels, format)}buffer = (uint8_t *) av_malloc(recorderSize);//创建引擎对象openSLHelp->openSLHelperInit();//设置IO设备(麦克风)openSLHelp->device = {SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,SL_DEFAULTDEVICEID_AUDIOINPUT, NULL};openSLHelp->source = {&openSLHelp->device, NULL};//设置buffer队列openSLHelp->android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};//设置录制规格:PCM、2声道、44100HZ、16bitopenSLHelp->formatPCM = {SL_DATAFORMAT_PCM, numChannels,openSLHelp->getCurrentSampleRateForOpensles(samplingRate),openSLHelp->getCurrentFormatForOpensles(format),openSLHelp->getCurrentFormatForOpensles(format),openSLHelp->getChannelMask(numChannels),SL_BYTEORDER_LITTLEENDIAN};openSLHelp->sink = {&openSLHelp->android_queue, &openSLHelp->formatPCM};SLInterfaceID id[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE};SLboolean required[] = {SL_BOOLEAN_TRUE};//创建录制器(*openSLHelp->engineInterface)->CreateAudioRecorder(openSLHelp->engineInterface,&openSLHelp->recorderObject,&openSLHelp->source,&openSLHelp->sink,1,id,required);(*openSLHelp->recorderObject)->Realize(openSLHelp->recorderObject, SL_BOOLEAN_FALSE);(*openSLHelp->recorderObject)->GetInterface(openSLHelp->recorderObject, SL_IID_RECORD,&openSLHelp->recorderInterface);(*openSLHelp->recorderObject)->GetInterface(openSLHelp->recorderObject,SL_IID_ANDROIDSIMPLEBUFFERQUEUE,&openSLHelp->queueInterface);(*openSLHelp->queueInterface)->Enqueue(openSLHelp->queueInterface, buffer, recorderSize);(*openSLHelp->queueInterface)->RegisterCallback(openSLHelp->queueInterface,pcmBufferRecordCallBack, this);recordstatus->recording = true;//开始录音(*openSLHelp->recorderInterface)->SetRecordState(openSLHelp->recorderInterface,SL_RECORDSTATE_RECORDING);if (recordCallJava != NULL) {recordCallJava->onCallRecordStart(CHILD_THREAD);}if (LOG_DEBUG) {LOGD("Recorder started ...");}}void *timeSpace(void *data) {YorkRecorder *recorder = (YorkRecorder *) data;while (recorder->recordstatus->recording) {if (recorder->recordstatus->recordingsave) {usleep(1000);recorder->recordCallJava->onCallTimeSpace(CHILD_THREAD, recorder->timer.getTimeShow());}}pthread_exit(&recorder->thread_timeSpace);
}
/**
* 开始录制
*/
void YorkRecorder::startRecord() {yorkJiangzao->initJiangzao(recorderSize, samplingRate, numChannels, format);yorkRenSheng->initRenSheng();recordOpenSLES();pthread_create(&thread_timeSpace, NULL, timeSpace, this);
}/**
* 暂停录制
*/
void YorkRecorder::pauseRecord() {if (openSLHelp != NULL && openSLHelp->recorderInterface != NULL) {(*openSLHelp->recorderInterface)->SetRecordState(openSLHelp->recorderInterface,SL_RECORDSTATE_PAUSED);if (recordCallJava != NULL) {recordCallJava->onCallRecordPause(CHILD_THREAD);}if (LOG_DEBUG) {LOGD("Recorder paused ...");}}
}/**
* 继续录制
*/
void YorkRecorder::resumeRecord() {if (openSLHelp != NULL && openSLHelp->recorderInterface != NULL) {(*openSLHelp->recorderInterface)->SetRecordState(openSLHelp->recorderInterface,SL_RECORDSTATE_RECORDING);if (recordCallJava != NULL) {recordCallJava->onCallRecordStart(CHILD_THREAD);}if (LOG_DEBUG) {LOGD("Recorder resumed ...");}}
}/**
* 停止录制
*/
void YorkRecorder::stopRecord() {recordstatus->recording = false;if (yorkJiangzao != NULL) {yorkJiangzao->destryJiangzao();}if (openSLHelp != NULL) {SLuint32 state;(*openSLHelp->recorderInterface)->GetRecordState(openSLHelp->recorderInterface, &state);if (state == SL_RECORDSTATE_PAUSED || state == SL_RECORDSTATE_RECORDING) {(*openSLHelp->recorderInterface)->SetRecordState(openSLHelp->recorderInterface,SL_RECORDSTATE_STOPPED);}openSLHelp->recordDestroy();if (LOG_DEBUG) {LOGD("Recorder already stoped !");}}if (recordCallJava != NULL) {recordCallJava->onCallRecordStop(CHILD_THREAD);recordCallJava=NULL;}
}void YorkRecorder::setJZ(int jz_rate) {this->jz_rate = jz_rate;
}void YorkRecorder::setGain(double gain_rate) {this->gain_rate = gain_rate;
}void YorkRecorder::setLC(int lc_rate) {this->lc_rate = lc_rate;
}void YorkRecorder::setRenShenSwitch(double rs_rate) {this->rs_rate = rs_rate;
}int YorkRecorder::getRecordStates() {SLuint32 state;int ret = -1;if (openSLHelp != NULL && openSLHelp->recorderInterface != NULL) {(*openSLHelp->recorderInterface)->GetRecordState(openSLHelp->recorderInterface,&state);}if (state == SL_RECORDSTATE_RECORDING) {ret = 0;}return ret;
}void YorkRecorder::startRecordTime() {recordstatus->recordingsave = true;timer.Start();
}
void YorkRecorder::pauseRecordTime() {recordstatus->recordingsave = false;timer.Pause();;
}void YorkRecorder::resumeRecordTime() {recordstatus->recordingsave = true;timer.Start();
}
void YorkRecorder::stopRecordTime() {if (recordCallJava != NULL) {recordCallJava->onCallRecordResult(CHILD_THREAD, timer.getTimeShow());}recordstatus->recordingsave = false;timer.Stop();
}
这样就完成对OpenSL ES的音频录制功能封装。OpenSL ES还可以对音频进行播放,这部分我们在播放模块对它重点讲解。
以上,就是对安卓系统下的音频录制功能的介绍了。
3.音频播放
安卓系统提供了4种音频播放的API,开发者可以根据应用场景,选择不同的API实现对音频文件的播放。其中包括:
- AudioTrack 播放PCM音频
- MediaPlayer 播放音频
- SoundPool 播放音频
- JetPlayer 播放 .jet音频
- Ringtone 播放铃声音频
- OpenSL ES 播放PCM音频
3.1 AudioTrack 播放音频
AudioTrack 属于偏底层的音频播放API,MediaPlayerService的内部就是使用了 AudioTrack。
AudioTrack 用于单个音频的播放,播放PCM 数据,任何一种音频解码成 PCM 后都可以用 AudioTrack 来播放,所以 AudioTrack 相比于MediaPlayer,具有更精炼、高效、灵活的优点。
① AudioTreack 的2种播放模式
- 静态模式—static:静态的言下之意就是数据一次性交付给接收方。好处是简单高效,只需要进行一次操作就完成了数据的传递;缺点当然也很明显,对于数据量较大的音频回放,显然它是无法胜任的,因而通常只用于播放铃声、系统提醒等对内存小的操作
- 流模式-stream:流模式时数据是按照一定规律不断地传递给接收方的。理论上它可用于任何音频播放的场景,我们一般在音频文件过大、还有数据是实时产生的情况下,用流模式进行播放。
② AudioTreack 的播放示例
播放前需要先设置音频的基本参数,用音频数据的采样率、位深、通道数初始化 AudioTreack。示例:
/*** 开始播放*/private void startPlay(){new Thread(new Runnable() {@Overridepublic void run() {// 获取最小缓冲区int bufSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);// 实例化AudioTrack(设置缓冲区为最小缓冲区的2倍,至少要等于最小缓冲区)mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_OUT_STEREO,AudioFormat.ENCODING_PCM_16BIT, bufSize, AudioTrack.MODE_STREAM);// 设置播放频率mAudioTrack.setPlaybackRate(10) ;mAudioTrack.play();// 获取音乐文件输入流InputStream is = getResources().openRawResource(R.raw.pcm_441_stereo_16);byte[] buffer = new byte[bufSize*2] ;int len ;try {while((len=is.read(buffer,0,buffer.length)) != -1){// 将读取的数据,写入AudiotrackaudioTrack.write(buffer,0,buffer.length) ;}is.close();} catch (Exception e) {e.printStackTrace();}}}).start();}/*** 停止播放*/private void stopPlay(){if (this.mAudioTrack != null) {this.mAudioTrack.stop();this.mAudioTrack.release();this.mAudioTrack = null;}}
以上就是对 AudioTreack 实现音频播放的整理。
3.2 MediaPlayer 播放音频
MediaPlayer 支持:AAC、AMR、FLAC、MP3、MIDI、OGG、PCM 等音频格式,也可以播放在线的流式资源。
MediaPlayer 播放时会在framework 层创建对应的音频解码器,创建 AudioTrack,然后把解码后的PCM数流传递给 AudioTrack,AudioTrack 再传递给 AudioFlinger 进行混音,然后才传递给硬件播放,所以可以说 MediaPlayer 包含了 AudioTrack。我们后期要讲的播放器封装也是一样的逻辑。
以下是对 MediaPlayer 播放功能的一个封装,其中包括 音频源的设置、播放、暂停、继续播放、停止播放、获取播放状态、播放进度控制等封装。代码如下:
public class SampleMediaPlay implements OnPreparedListener, OnBufferingUpdateListener{public MediaPlayer mediaPlayer; // 媒体播放器private Timer mTimer = new Timer(); // 计时器private StateChangeListener mStateChangeListener;private int presses=0;private int totaltime=0;private AudioManager mAudioManager;public SampleMediaPlay(StateChangeListener mStateChangeListener,AudioManager audioManager) {super();this.mAudioManager=audioManager;this.mStateChangeListener=mStateChangeListener;try {mediaPlayer = new MediaPlayer();mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);// 设置媒体流类型mediaPlayer.setOnPreparedListener(this);mediaPlayer.setOnBufferingUpdateListener(this);} catch (Exception e) {e.printStackTrace();} mTimer.schedule(timerTask, 0, 50);}private AudioManager.OnAudioFocusChangeListener mFocusChangeListener=new AudioManager.OnAudioFocusChangeListener() {@Overridepublic void onAudioFocusChange(int focusChange) {if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {Log.e("york","短暂的失去焦点");pause();} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {Log.e("york","重新获取到焦点");play();} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {Log.e("york","长时间失去焦点哦");pause();} else if (focusChange == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {Log.e("york","焦点成功");}}};TimerTask timerTask = new TimerTask() {@Overridepublic void run() {if (mediaPlayer == null){return;}else if (mediaPlayer.isPlaying()) {handler.sendEmptyMessage(0); // 发送消息}} };Handler handler = new Handler() {public void handleMessage(android.os.Message msg) {if (mediaPlayer == null){return;}else if (mediaPlayer.isPlaying() ) {mediaPlayer.getCurrentPosition();mediaPlayer.getDuration();}};};// 设置数据源public void playUrl(String url) {if (mediaPlayer != null) {mAudioManager.requestAudioFocus(mFocusChangeListener,AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN);try { mediaPlayer.setDataSource(url);mediaPlayer.prepareAsync();mStateChangeListener.onUpData();} catch (Exception e) {e.printStackTrace();}}}//播放public void play() {if (mediaPlayer != null) {mAudioManager.requestAudioFocus(mFocusChangeListener,AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN);if(!mediaPlayer.isPlaying()){mediaPlayer.start();mStateChangeListener.onPlaying();}}}// 暂停public void pause() {if(mediaPlayer != null ){if(mediaPlayer.isPlaying()){mediaPlayer.pause();mStateChangeListener.onPauseing();}}}// 停止public void stop() {if (mediaPlayer != null) {timerTask.cancel();mTimer.cancel();mTimer=null;mAudioManager.abandonAudioFocus(mFocusChangeListener);pause();if(mediaPlayer != null ){if(mediaPlayer.isPlaying()){mediaPlayer.pause();}}mediaPlayer.stop(); mediaPlayer.release();mediaPlayer=null; }}@Overridepublic void onPrepared(MediaPlayer mp) {mAudioManager.requestAudioFocus(mFocusChangeListener,AudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN);mp.start();mStateChangeListener.onPlaying();}public int getCurrentPosition(){if(mediaPlayer==null){return 0;}else if(!mediaPlayer.isPlaying()){return presses;}else{presses= mediaPlayer.getCurrentPosition();return presses;}}public int getDuration(){if(mediaPlayer==null){return 0;}else if(!mediaPlayer.isPlaying()){return totaltime;}else{totaltime= mediaPlayer.getDuration();return totaltime;}}public void seekTo(int msec){mediaPlayer.seekTo(msec);}@Overridepublic void onBufferingUpdate(MediaPlayer mp, int percent) {}
}
3.3 SoundPool 播放音频
SoundPool可以同时播放多个短促的音频,而且占用的资源较少,比较适合短促、密集的场景,适合在程序中播放按键音,或者消息提示音等,也长用于游戏开发中音效的播放。不过比较尴尬的是,已经被安卓弃用了。
① SoundPool 构造器
/*** 参数maxStreams:指定支持多少个声音;* 参数streamType:指定声音类型:* 参数srcQuality:指定声音品质。*/new SoundPool(int maxStreams, int streamType, int srcQuality)
用以上实例化SoundPool对象
②SoundPool加载音频
在得到了SoundPool对象之后,接下来就可调用SoundPool的多个重载的load方法来加载声音了。
//从 resld 所对应的资源加载声音。
int load(Context context, int resld, int priority)
//加载 fd 所对应的文件的offset开始、长度为length的声音。
int load(FileDescriptor fd, long offset, long length, int priority)
//从afd 所对应的文件中加载声音。
int load(AssetFileDescriptor afd, int priority)
//从path 对应的文件去加载声音。
int load(String path, int priority)
上面4个方法加载声音之后,返回该声音的ID,程序通过ID来播放该声音。
③SoundPool播放指定ID声音
/*** 参数soundID:指定播放哪个声音; * 参数leftVolume、rightVolume:指定左、右的音量: * 参数priority:指定播放声音的优先级,数值越大,优先级越高; * 参数loop:指定是否循环,0:不循环,-1:循环,其他值表示要重复播放的次数;* 参数rate:指定播放的比率,数值可从0.5到2, 1为正常比率。*/int play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)
④释放
最后,release()方法,用于释放所有SoundPool对象占据的内存和资源,也可以指定要释放的ID。
以上为 SoundPool 播放音频文件的方法。
3.4 JetPlayer 播放音频
在Android中,还提供了对Jet播放的支持,Jet是由OHA联盟成员SONiVOX开发的一个交互音乐引擎。其包括两部分:JET播放器和JET引擎。JET常用于控制游戏的声音特效,采用MIDI(Musical Instrument Digital Interface)格式。
MIDI数据有一套音乐符号构成,而非实际的音乐,这些音乐符号的一个序列称为MIDI消息,Jet文件包含多个Jet段,而每个Jet段又包含多个轨迹,一个轨迹是MIDI 消息的一个序列。
JetPlayer类内部有个存放Jet段的队列,JetPlayer类的主要作用就是向队列中添加Jet段或者清空队列,其次就是控制Jet段的轨迹是否处于打开状态。需要注意的是,在Android开发中,JetPlayer是基于单子模式实现的,在整个系统中,仅存在一个JetPlayer的对象。
JetPlayer的常用方法包括:
getJetPlayer() //获得JetPlayer的句柄
clearQueue() //清空队列
setEventListener() //设置JetPlayer.OnJetEventListener监听器
loadJetFile() //加载Jet文件
queueJetSegment() //查询Jet段
play() //播放Jet文件
以下为.jet文件播放的示例:
private void startPlay(){// 获取JetPlayer播放器JetPlayer mJetPlayer = JetPlayer.getJetPlayer() ;//清空播放队列mJetPlayer.clearQueue() ;//绑定事件监听mJetPlayer.setEventListener(new JetPlayer.OnJetEventListener() {//播放次数记录int playNum = 1 ;@Overridepublic void onJetEvent(JetPlayer player, short segment, byte track, byte channel, byte controller, byte value) {Log.i("york","----->onJetEvent") ;}@Overridepublic void onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount) {Log.i("york","----->onJetUserIdUpdate") ;}@Overridepublic void onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments) {Log.i("york","----->onJetNumQueuedSegmentUpdate") ;}@Overridepublic void onJetPauseUpdate(JetPlayer player, int paused) {Log.i("york","----->onJetPauseUpdate") ;if(playNum == 2){playNum = -1 ;//释放资源,并关闭jet文件player.release();player.closeJetFile() ;}else{playNum++ ;}}});//加载资源mJetPlayer.loadJetFile(getResources().openRawResourceFd(R.raw.jetaudio)) ;byte sSegmentID = 0 ;//指定播放序列mJetPlayer.queueJetSegment(0, 0, 0, 0, 0, sSegmentID);mJetPlayer.queueJetSegment(1, 0, 1, 0, 0, sSegmentID);//开始播放mJetPlayer.play() ;}
3.5 Ringtone 播放音频
Ringtone提供了快速播放铃声、通知和其他类似声音的方法。
① Ringtone 实例化
RingtoneManager类提供了Ringtone 的实例化方法:
//通过铃声uri获取
static Ringtone getRingtone(Context context, Uri ringtoneUri)
//通过铃声检索位置获取
Ringtone getRingtone(int position)
② 重要类 RingtoneManager
// 2个构造方法
RingtoneManager(Activity activity)
RingtoneManager(Context context)// 获取指定声音类型(铃声、通知、闹铃等)的默认声音的Uri
static Uri getDefaultUri(int type)// 获取系统所有Ringtone的cursor
Cursor getCursor()// 获取cursor指定位置的Ringtone uri
Uri getRingtoneUri(int position)// 判断指定Uri是否为默认铃声
static boolean isDefault(Uri ringtoneUri)//获取指定uri的所属类型
static int getDefaultType(Uri defaultRingtoneUri)//将指定Uri设置为指定声音类型的默认声音
static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri)
③ Ringtone播放音频示例
/*** 播放来电铃声的默认音乐**/
private void playRingtoneDefault(){Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE) ;Ringtone mRingtone = RingtoneManager.getRingtone(this,uri);mRingtone.play();
}
ok,以上是Ringtone播放音频的相关介绍
3.6 OpenSL ES 播放音频
在文章的上面内容,已经对OpenSL ES 做了相应介绍。
OpenSL ES 的开发流程主要有如下6个步骤:
- 创建接口对象
- 设置混音器
- 创建录音器(播放器)
- 设置缓冲队列和回调函数
- 设置播放状态
- 启动回调函数
这里把音频播放部分的代码贴出来,如下:
YorkAudio.cpp
//
// Created by 86158 on 2019/8/19.
//#include <SLES/OpenSLES.h>
#include "YorkAudio.h"/**** @param type //0;边录边播;1,播放* @param dir* @param playstatus* @param sample_rate* @param chenll* @param format* @param callJava*/
YorkAudio::YorkAudio(int type, const char *dir, YorkPlaystatus *playstatus, int sample_rate,int channll, int format, PlayCallJava *callJava) {this->AudioType = type;this->playstatus = playstatus;this->callJava = callJava;queue = new YorkQueue(playstatus);this->sample_rate = sample_rate;this->chenell = channll;this->format = format;this->openSLHelp = new OpenSLHelp();pthread_mutex_init(&codecMutex, NULL);if (dir != NULL) {pcmFile = fopen(dir, "w+");}buffer = (uint8_t *) av_malloc(sample_rate * 2 * 2);sampleBuffer = static_cast<SAMPLETYPE *>(malloc(sample_rate * 2 * 2));soundTouch = new SoundTouch();soundTouch->setSampleRate(sample_rate);soundTouch->setChannels(chenell);soundTouch->setPitch(pitch);soundTouch->setTempo(speed);
}YorkAudio::~YorkAudio() {pthread_mutex_destroy(&codecMutex);
}int YorkAudio::resampleAudio(void **pcmbuf) {data_size = 0;while (playstatus != NULL && !playstatus->exit) {if (playstatus->seek) {av_usleep(1000 * 100);continue;} else if (AudioType == 1) {if (queue->getQueueSize() == 0) {//加载中if (!playstatus->load) {playstatus->load = true;callJava->onCallOnLoad(CHILD_THREAD, true);}av_usleep(1000 * 100);continue;} else {if (playstatus->load) {playstatus->load = false;callJava->onCallOnLoad(CHILD_THREAD, false);}}if (readFrameFinshed) {avPacket = av_packet_alloc();if (queue->getAvpacket(avPacket) != 0) {av_packet_free(&avPacket);av_free(avPacket);avPacket = NULL;continue;}pthread_mutex_lock(&codecMutex);//avPacket放入avCodecContextret = avcodec_send_packet(avCodecContext, avPacket);if (ret != 0) {av_packet_free(&avPacket);av_free(avPacket);avPacket = NULL;pthread_mutex_unlock(&codecMutex);continue;}}avFrame = av_frame_alloc();//avFrame就是解码出来 在avCodecContext里的音频帧ret = avcodec_receive_frame(avCodecContext, avFrame);if (ret == 0) {if (avFrame->channels && avFrame->channel_layout == 0) {avFrame->channel_layout = av_get_default_channel_layout(avFrame->channels);} else if (avFrame->channels == 0 && avFrame->channel_layout > 0) {avFrame->channels = av_get_channel_layout_nb_channels(avFrame->channel_layout);}//设置重采样的上下文SwrContext *swr_ctx;swr_ctx = swr_alloc_set_opts(NULL,SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,AV_SAMPLE_FMT_S16,//很奇怪,用其他的会炸裂avFrame->sample_rate,avFrame->channel_layout,(AVSampleFormat) avFrame->format,avFrame->sample_rate, NULL, NULL);if (!swr_ctx || swr_init(swr_ctx) < 0) {av_packet_free(&avPacket);av_free(avPacket);avPacket = NULL;av_frame_free(&avFrame);av_free(avFrame);avFrame = NULL;if (swr_ctx != NULL) {swr_free(&swr_ctx);swr_ctx = NULL;pthread_mutex_unlock(&codecMutex);}readFrameFinshed = true;continue;}//重采样函数swr_convert();nb = swr_convert(swr_ctx,&buffer,avFrame->nb_samples,(const uint8_t **) (avFrame->data),avFrame->nb_samples);int out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);data_size = nb * out_channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);now_time = avFrame->pts * av_q2d(time_base);if (now_time < audio_clock) {now_time = audio_clock;}audio_clock = now_time;*pcmbuf = buffer;av_frame_free(&avFrame);av_free(avFrame);avFrame = NULL;swr_free(&swr_ctx);pthread_mutex_unlock(&codecMutex);swr_ctx = NULL;break;} else {av_packet_free(&avPacket);av_free(avPacket);avPacket = NULL;av_frame_free(&avFrame);av_free(avFrame);avFrame = NULL;pthread_mutex_unlock(&codecMutex);continue;}}}return data_size;
}void pcmBufferPlayCallBack(SLAndroidSimpleBufferQueueItf bf, void *context) {YorkAudio *yorkAudio = (YorkAudio *) context;if (yorkAudio != NULL) {if (yorkAudio->chenell==2){//只有是立体声的才进行左右声道拆分int buffersize = yorkAudio->getSoundTouchData();if (buffersize > 0) {yorkAudio->audio_clock += buffersize / ((double) ((yorkAudio->sample_rate * 2 * 2)));if (yorkAudio->AudioType == 1) {if (yorkAudio->audio_clock - yorkAudio->last_time_change >= 0.0001) {yorkAudio->last_time_change = yorkAudio->audio_clock;}yorkAudio->callJava->onCallPlayData(CHILD_THREAD, yorkAudio->chenell,yorkAudio->sampleBuffer, buffersize * 4);}(*yorkAudio->openSLHelp->queueInterface)->Enqueue(yorkAudio->openSLHelp->queueInterface, (char *) yorkAudio->sampleBuffer,buffersize * 4);}} else{double buffersize = yorkAudio->resampleAudio(reinterpret_cast<void **>(&yorkAudio->out_buffer));if (buffersize > 0) {yorkAudio->audio_clock += buffersize / ((double) ((yorkAudio->sample_rate * 2 * 2)));if (yorkAudio->AudioType == 1) {if (yorkAudio->audio_clock - yorkAudio->last_time_change >= 0.0001) {yorkAudio->last_time_change = yorkAudio->audio_clock;}yorkAudio->callJava->onCallPlayData(CHILD_THREAD, yorkAudio->chenell,yorkAudio->buffer, buffersize);}(*yorkAudio->openSLHelp->queueInterface)->Enqueue(yorkAudio->openSLHelp->queueInterface, (char *) yorkAudio->buffer,buffersize);}}}
}int YorkAudio::resampleAudioToPCM() {data_size = 0;while (playstatus != NULL && playstatus->codec) {avPacket = av_packet_alloc();if (queue->getAvpacket(avPacket) != 0) {av_packet_free(&avPacket);av_free(avPacket);avPacket = NULL;continue;}ret = avcodec_send_packet(avCodecContext, avPacket);if (ret != 0) {av_packet_free(&avPacket);av_free(avPacket);avPacket = NULL;continue;}avFrame = av_frame_alloc();ret = avcodec_receive_frame(avCodecContext, avFrame);if (ret == 0) {readFrameFinshed = false;if (avFrame->channels && avFrame->channel_layout == 0) {avFrame->channel_layout = av_get_default_channel_layout(avFrame->channels);} else if (avFrame->channels == 0 && avFrame->channel_layout > 0) {avFrame->channels = av_get_channel_layout_nb_channels(avFrame->channel_layout);}//设置重采样的上下文SwrContext *swr_ctx;swr_ctx = swr_alloc_set_opts(NULL,SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,AV_SAMPLE_FMT_S16,avFrame->sample_rate,avFrame->channel_layout,(AVSampleFormat) avFrame->format,avFrame->sample_rate, NULL, NULL);if (!swr_ctx || swr_init(swr_ctx) < 0) {av_packet_free(&avPacket);av_free(avPacket);avPacket = NULL;av_frame_free(&avFrame);av_free(avFrame);avFrame = NULL;if (swr_ctx != NULL) {swr_free(&swr_ctx);swr_ctx = NULL;}continue;}nb = swr_convert(swr_ctx,&buffer,avFrame->nb_samples,(const uint8_t **) (avFrame->data),avFrame->nb_samples);int out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);data_size = nb * out_channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);fwrite(buffer, 1, data_size, pcmFile);decodenow = avFrame->pts * av_q2d(time_base);if (callJava != NULL) {callJava->onCallDecodePosition(CHILD_THREAD, decodenow / (audio_duration * 1.0));}av_packet_free(&avPacket);av_free(avPacket);avPacket = NULL;av_frame_free(&avFrame);av_free(avFrame);avFrame = NULL;swr_free(&swr_ctx);swr_ctx = NULL;} else {readFrameFinshed = true;av_packet_free(&avPacket);av_free(avPacket);avPacket = NULL;av_frame_free(&avFrame);av_free(avFrame);avFrame = NULL;continue;}if (queue->getQueueSize() == 0) {break;}}fclose(pcmFile);if (LOG_DEBUG) {LOGD("resample and pcm save finish ! ");}if (callJava != NULL) {callJava->onPCMdown(CHILD_THREAD);}return data_size;
}void *decodePlay(void *data) {YorkAudio *yorkAudio = (YorkAudio *) data;yorkAudio->playOpenSLES();pthread_exit(&yorkAudio->thread_play);
};void *decodc(void *data) {YorkAudio *yorkAudio = (YorkAudio *) data;yorkAudio->resampleAudioToPCM();pthread_exit(&yorkAudio->thread_codec);
};void YorkAudio::codec() {pthread_create(&thread_codec, NULL, decodc, this);
}void YorkAudio::playOpenSLES() {//1.初始化openSLopenSLHelp->openSLHelperInit();//2,创建混音器const SLInterfaceID mids[1] = {SL_IID_ENVIRONMENTALREVERB};const SLboolean mreq[1] = {SL_BOOLEAN_FALSE};(*openSLHelp->engineInterface)->CreateOutputMix(openSLHelp->engineInterface,&openSLHelp->outputMixObject, 1, mids, mreq);(*openSLHelp->outputMixObject)->Realize(openSLHelp->outputMixObject, SL_BOOLEAN_FALSE);(*openSLHelp->outputMixObject)->GetInterface(openSLHelp->outputMixObject,SL_IID_ENVIRONMENTALREVERB,&openSLHelp->outputMixEnvironmentalReverb);(*openSLHelp->outputMixEnvironmentalReverb)->SetEnvironmentalReverbProperties(openSLHelp->outputMixEnvironmentalReverb, &openSLHelp->reverbSettings);openSLHelp->outputMix = {SL_DATALOCATOR_OUTPUTMIX, openSLHelp->outputMixObject};openSLHelp->sink = {&openSLHelp->outputMix, 0};//3,配置PCM格式信息openSLHelp->android_queue = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};openSLHelp->formatPCM = {SL_DATAFORMAT_PCM,//播放pcm格式的数据2,//2个声道(立体声)openSLHelp->getCurrentSampleRateForOpensles(sample_rate),//44100hz的频率SL_PCMSAMPLEFORMAT_FIXED_16,//位数 16位SL_PCMSAMPLEFORMAT_FIXED_16,//和位数一致就行SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,//立体声(前左前右)SL_BYTEORDER_LITTLEENDIAN//结束标志};openSLHelp->source = {&openSLHelp->android_queue, &openSLHelp->formatPCM};const SLInterfaceID ids[4] = {SL_IID_BUFFERQUEUE, SL_IID_VOLUME, SL_IID_PLAYBACKRATE,SL_IID_MUTESOLO};const SLboolean req[4] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};(*openSLHelp->engineInterface)->CreateAudioPlayer(openSLHelp->engineInterface,&openSLHelp->playerObject,&openSLHelp->source,&openSLHelp->sink,4,ids, req);//4. 初始化播放器(*openSLHelp->playerObject)->Realize(openSLHelp->playerObject, SL_BOOLEAN_FALSE);(*openSLHelp->playerObject)->GetInterface(openSLHelp->playerObject, SL_IID_PLAY,&openSLHelp->playInterface);//获取声音接口(*openSLHelp->playerObject)->GetInterface(openSLHelp->playerObject, SL_IID_VOLUME,&openSLHelp->pcmVolumePlay);//获取声道接口(*openSLHelp->playerObject)->GetInterface(openSLHelp->playerObject, SL_IID_MUTESOLO,&openSLHelp->pcmMutePlay);(*openSLHelp->playerObject)->GetInterface(openSLHelp->playerObject, SL_IID_BUFFERQUEUE,&openSLHelp->queueInterface);(*openSLHelp->queueInterface)->RegisterCallback(openSLHelp->queueInterface,pcmBufferPlayCallBack, this);(*openSLHelp->playInterface)->SetPlayState(openSLHelp->playInterface, SL_PLAYSTATE_PLAYING);pcmBufferPlayCallBack(openSLHelp->queueInterface, this);}void YorkAudio::playAudio() {pthread_create(&thread_play, NULL, decodePlay, this);if (LOG_DEBUG) {LOGD("player start ...");}
}void YorkAudio::pauseAudio() {if (openSLHelp != NULL && openSLHelp->playInterface != NULL) {(*openSLHelp->playInterface)->SetPlayState(openSLHelp->playInterface, SL_PLAYSTATE_PAUSED);}if (LOG_DEBUG) {LOGD("player paused ...");}
}void YorkAudio::resumeAudio() {if (openSLHelp != NULL && openSLHelp->playInterface != NULL) {(*openSLHelp->playInterface)->SetPlayState(openSLHelp->playInterface, SL_PLAYSTATE_PLAYING);}if (LOG_DEBUG) {LOGD("player resumed ...");}
}void YorkAudio::releaseAudio() {if (queue != NULL) {delete (queue);queue = NULL;}if (buffer != NULL) {free(buffer);buffer = NULL;}if (soundTouch == NULL) {delete soundTouch;soundTouch = NULL;}if (openSLHelp != NULL && openSLHelp->playInterface != NULL) {(*openSLHelp->playInterface)->SetPlayState(openSLHelp->playInterface, SL_PLAYSTATE_STOPPED);openSLHelp->playDestroy();}if (avCodecContext != NULL) {avcodec_close(avCodecContext);avcodec_free_context(&avCodecContext);avCodecContext = NULL;}if (callJava != NULL) {callJava = NULL;}}void YorkAudio::getDB(int16_t *pcmdata, size_t pcmsize, int chenell, int format,PlayCallJava *calljava) {int db = 0;int db_left = 0;int db_right = 0;double sum = 0;double sum_left = 0;double sum_right = 0;if (chenell == 1) {short pervalue = 0;for (int i = 0; i < pcmsize; i += 12) {memcpy(&pervalue, pcmdata + i, 2);sum += abs(pervalue);}sum = sum / (pcmsize / 12);if (sum > 0) {db = (int) 20.0 * log10(sum);}} else {int32_t pervalue = 0;short per_left = 0;short per_right = 0;for (int i = 0; i < pcmsize; i += 12) {memcpy(&pervalue, pcmdata + i, 4);memcpy(&per_left, pcmdata + i, 2);memcpy(&per_right, pcmdata + i + 2, 2);sum += abs(pervalue);sum_left += abs(per_left);sum_right += abs(per_right);}sum = sum / (pcmsize / 12);sum_left = sum_left / (pcmsize / 12);sum_right = sum_right / (pcmsize / 12);if (sum > 0) {db = (int) 20.0 * log10(sum);}if (sum_right > 0) {db_right = (int) 20.0 * log10(sum_right);}if (sum_left > 0) {db_left = (int) 20.0 * log10(sum_left);}}if (calljava != NULL) {calljava->onCallPlayerDB(CHILD_THREAD, chenell, db, db_left, db_right);}
}int YorkAudio::getPlayStates() {SLuint32 state = -1;if (openSLHelp != NULL && openSLHelp->playInterface != NULL) {(*openSLHelp->playInterface)->GetPlayState(openSLHelp->playInterface, &state);}return state;
}void YorkAudio::setVolume(int percent) {if (openSLHelp->pcmVolumePlay != NULL) {if (percent > 30) {(*openSLHelp->pcmVolumePlay)->SetVolumeLevel(openSLHelp->pcmVolumePlay,(100 - percent) * -20);} else if (percent > 25) {(*openSLHelp->pcmVolumePlay)->SetVolumeLevel(openSLHelp->pcmVolumePlay,(100 - percent) * -22);} else if (percent > 20) {(*openSLHelp->pcmVolumePlay)->SetVolumeLevel(openSLHelp->pcmVolumePlay,(100 - percent) * -25);} else if (percent > 15) {(*openSLHelp->pcmVolumePlay)->SetVolumeLevel(openSLHelp->pcmVolumePlay,(100 - percent) * -28);} else if (percent > 10) {(*openSLHelp->pcmVolumePlay)->SetVolumeLevel(openSLHelp->pcmVolumePlay,(100 - percent) * -30);} else if (percent > 5) {(*openSLHelp->pcmVolumePlay)->SetVolumeLevel(openSLHelp->pcmVolumePlay,(100 - percent) * -34);} else if (percent > 3) {(*openSLHelp->pcmVolumePlay)->SetVolumeLevel(openSLHelp->pcmVolumePlay,(100 - percent) * -37);} else if (percent > 0) {(*openSLHelp->pcmVolumePlay)->SetVolumeLevel(openSLHelp->pcmVolumePlay,(100 - percent) * -40);} else {(*openSLHelp->pcmVolumePlay)->SetVolumeLevel(openSLHelp->pcmVolumePlay,(100 - percent) * -100);}}
}void YorkAudio::setMute(int mute) {if (LOG_DEBUG != NULL) {LOGD("setMute-> mute=%d", mute)}if (openSLHelp->pcmMutePlay != NULL) {if (mute == 0)//left{(*openSLHelp->pcmMutePlay)->SetChannelMute(openSLHelp->pcmMutePlay, 1, true);(*openSLHelp->pcmMutePlay)->SetChannelMute(openSLHelp->pcmMutePlay, 0, false);} else if (mute == 1)//right{(*openSLHelp->pcmMutePlay)->SetChannelMute(openSLHelp->pcmMutePlay, 1, false);(*openSLHelp->pcmMutePlay)->SetChannelMute(openSLHelp->pcmMutePlay, 0, true);} else if (mute == 2)//center{(*openSLHelp->pcmMutePlay)->SetChannelMute(openSLHelp->pcmMutePlay, 1, false);(*openSLHelp->pcmMutePlay)->SetChannelMute(openSLHelp->pcmMutePlay, 0, false);}}
}void YorkAudio::setPitch(float pitch) {this->pitch = pitch;if (LOG_DEBUG != NULL) {LOGD("setPitch-> pitch=%f", pitch)}if (soundTouch != NULL) {soundTouch->setPitchOctaves(pitch);}
}void YorkAudio::setSpeed(float speed) {if (speed > 0) {this->speed = speed;if (LOG_DEBUG != NULL) {LOGD("setSpeed-> speed=%f", speed)}if (soundTouch != NULL) {soundTouch->setTempo(speed);}}
}int YorkAudio::getSoundTouchData() {while (playstatus != NULL && !playstatus->exit) {out_buffer = NULL;if (finished) {finished = false;data_size = resampleAudio(reinterpret_cast<void **>(&out_buffer));if (data_size > 0) {for (int i = 0; i < data_size / 2 + 1; i++) {sampleBuffer[i] = (out_buffer[i * 2] | ((out_buffer[i * 2 + 1]) << 8));}soundTouch->putSamples(sampleBuffer, nb);num = soundTouch->receiveSamples(sampleBuffer, data_size / 4);} else {soundTouch->flush();}}if (num == 0) {finished = true;continue;} else {if (out_buffer == NULL) {num = soundTouch->receiveSamples(sampleBuffer, data_size / 4);if (num == 0) {finished = true;continue;}}return num;}}return 0;
}
以上就是我对安卓音频模块的整理。有不足之处或是有好建议可以留言找我。
上一篇:0.安卓音视频整理
下一篇:2.安卓音视频整理(二)—— 音视频编解码
个人网站: www.yorkyue.com
安卓音视频整理(一)——音频模块相关推荐
- 安卓音视频入门难,老司机分享音视频开发学习技巧与痛点
最近在写文章的时候,发现很多读者给我私信提问,想了解安卓音视频这块, 很多同学也对音视频开发有点浓厚的兴趣,但奈何没有系统的学习知识,仅靠自己苦苦钻研确实力不从心,今天就借这个机会分享一下我学习音视频 ...
- 音视频基础知识---音频编码格式
音视频基础知识汇总: 音视频基础知识---协议相关RTSP RTMP HLS 音视频基础知识---封装格式 音视频基础知识---视频编码格式 音视频基础知识---音频编码格式 音视频基础知识---像素 ...
- Android 音视频配音之音频提取、截断、混音、合并、合成(一)——从视频中提取音频文件
目录 前言 提取前提----了解提取需要用到的工具类:MediaExtractor.MediaCodec 1.MediaExtractor 2.MediaCodec 3.释放 具体提取转码代码 调用 ...
- Android 音视频配音之音频提取、截断、混音、合并、合成(二)——将提取的PCM根据时间戳截断
前言 通过上篇文章我们知道了如何从MP4视频中如何提取出PCM或者WAV,那么这篇文章则是如何截断PCM音频 该文章系列是视频配音,涉及到把背景音频从视频中提取出来.背景音频根据台词时间戳进行截断.截 ...
- moviepy音视频开发:音频剪辑基类AudioClip
☞ ░ 前往老猿Python博文目录 ░ 一.背景知识介绍 1.1.声音三要素: 音调:人耳对声音高低的感觉称为音调(也叫音频).音调主要与声波的频率有关.声波的频率高,则音调也高. 音量:也就是响度 ...
- Android音视频开发之音频录制和播放
Android音视频开发之音频录制和播放 1.封装音频录制工具类: public class RecorderAudioManagerUtils {private static volatile Re ...
- 【音视频开发】音频相关概念 - 总结
音频驱动 (音频相关概念) 音频驱动 音频驱动 (音频相关概念) 声音定义 声卡 声道 编解码 PCM(脉冲编码调制) 码率 音频压缩编码 有损压缩 无损压缩 无损压缩和有损压缩 有损压缩 无损压缩 ...
- 【ZEGO即构】音视频开发进阶 — 音频要素
"风声,雨声,读书声,声声入耳",关于声音,大家肯定都不陌生.作为最基础的信息载体之一,声音被用于社交沟通.唱歌娱乐,被用于人机语音交互.智能控制,在我们生活中的方方面面都在被感知 ...
- 安卓音视频基础:AudioRecord和AudioTrack的简单使用
在之前的文章安卓实现录音/播放/暂停/继续的功能中介绍了通过MediaRecorder和MediaPlayer实现简单的录音和播放功能,但相比于安卓二次封装后的API,AudioRecord和Audi ...
最新文章
- 条件随机场(CRF) - 1 - 简介
- mysql profile 查谒_MYSQL查询优化:profile功能
- Java Springboot应用部署
- 物理数据模型(PDM)-概念数据模型 (CDM)-面向对象模型 (OOM):适用于已经设计好数据库表结构了。...
- 中有atoi函数吗_C++ 多态的实现及原理,深挖vptr指针,手动调用虚函数
- 用uliweb 创建项目
- angular6 设置全局变量_angularjs 设置全局变量的3种方法
- PL / SQL教程
- 【系统分析师之路】第十七章 复盘多媒体基础知识
- 作为音乐创作人,你可知道?有可以自己写歌词的软件,专门写歌词的软件,创作歌词的软件,帮忙写歌词的软件
- 【晶体管电路设计】一、晶体管设计概述与共射极放大电路
- 监控freeswitch sip信令
- 【老生谈算法】matlab实现LSB算法源码——LSB算法
- Java学习笔记(六):Java泛型
- linux企业实战-haproxy(2)动静分离
- es数据更新时间_京东到家订单中心系统mysql到es的转化之路
- iPhone15将开放15W无线快充!
- 一阶电路实验报告心得_一阶电路实验报告5篇
- 如何做一个优秀的团队成员
- excel连接mysql速度太慢,excel表格数据太大-excel太大,运行缓慢该怎么办
热门文章
- 免费游戏开发引擎,各种游戏开发引擎
- 先知兵圣兵棋大赛初赛思路
- 一文学会Java死锁和CPU 100% 问题的排查技巧
- GIS | 利用ARCGIS完成最小成本路径选择
- 第五空间 hate_php
- 【理论恒叨】【立体匹配系列】经典AD-Census: (3)扫描线优化(Scanline Optimization)
- The new driver class is `com.mysql.cj.jdbc.Driver‘. The driver is automatically registered via...
- 【Jvm内存】EclipseMemoryAnalyzer分析内存
- 为什么软件开发周期总是预估的2-3倍
- 在宝能集团工作3年,谈谈我对宝能的认识