Android Keypad按键播放音

背景知识

  • 对于音乐类型的音频资源,可以通过MediaPlayer来播放;
  • 对于音调,可以通过ToneGenerator来播放;
  • 对于提示音,可以通过Ringtone来播放;
  • 对于游戏中的音频资源,可以通过SoundPool来播放。

 

ToneGenerator提供了对DTMF音(ITU-T Q.23),以及呼叫监督音(3GPP TS 22.001)、专用音(3GPP TS 31.111)中规定的音频的支持,根据呼叫状态和漫游状态,该文件产生的音频路径为下行音频或者传输给扬声器或耳机。需要说明的是,DTMF音为WAV格式,相关的音频类型定义位于ToneGenerator.h文件中

 

代码分析

1. java上层调用,每次new都会重新创建一个AudioTrack

ToneGenerator.java (frameworks\base\media\java\android\media)

android_media_ToneGenerator.cpp (frameworks\base\core\jni)

比如:

mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF, 80);

 

2. ToneGenerator.cpp (frameworks\av\media\libaudioclient)

//每个按键音的定义,主要是频率,因为这是自动生成的PCM数据

const ToneGenerator::ToneDescriptor ToneGenerator::sToneDescriptors[] = {

{ .segments = { { .duration = ToneGenerator::TONEGEN_INF, .waveFreq = { 1336, 941, 0 }, 0, 0},

{ .duration = 0 , .waveFreq = { 0 }, 0, 0}},

.repeatCnt = ToneGenerator::TONEGEN_INF,

.repeatSegment = 0 }, // TONE_DTMF_0

...

}

 

ToneGenerator::ToneGenerator(audio_stream_type_t streamType, float volume, bool threadCanCallJava) {

//获取对应audio流的采样率

if (AudioSystem::getOutputSamplingRate(&mSamplingRate, streamType) != NO_ERROR) {

 

// Generate tone by chunks of 20 ms to keep cadencing precision

mProcessSize = (mSamplingRate * 20) / 1000;

 

if (initAudioTrack()) {

}

 

bool ToneGenerator::initAudioTrack() { //初始化播放tone的audiotrack

// Open audio track in mono, PCM 16bit, default sampling rate.

mpAudioTrack = new AudioTrack();

attr = AudioSystem::streamTypeToAttributes(streamType); //streamType = STREAM_DTMF

status_t status = mpAudioTrack->set(

AUDIO_STREAM_DEFAULT,

0, // sampleRate

AUDIO_FORMAT_PCM_16_BIT, //一次采样用16bit的数据

AUDIO_CHANNEL_OUT_MONO, //单声道

frameCount, //mProcessSize

AUDIO_OUTPUT_FLAG_FAST,

audioCallback,

this, // user

0, // notificationFrames

0, // sharedBuffer

mThreadCanCallJava,

AUDIO_SESSION_ALLOCATE,

AudioTrack::TRANSFER_CALLBACK,

nullptr,

AUDIO_UID_INVALID,

-1,

&attr);

mpAudioTrack->setVolume(mVolume);

}

 

//播放对应按键音, durationMs = -1

bool ToneGenerator::startTone(tone_type toneType, int durationMs) {

// Get descriptor for requested tone

mpNewToneDesc = &sToneDescriptors[toneType];

 

if (mState == TONE_INIT) {

if (prepareWave()) {

ALOGV("Immediate start, time %d", (unsigned int)(systemTime()/1000000));

lResult = true;

mState = TONE_STARTING;

if (clock_gettime(CLOCK_MONOTONIC, &mStartTime) != 0) {

mStartTime.tv_sec = 0;

}

mLock.unlock();

mpAudioTrack->start(); //开始播放声音,会调用audioCallback回调函数

mLock.lock();

if (mState == TONE_STARTING) {

ALOGV("Wait for start callback");

lStatus = mWaitCbkCond.waitRelative(mLock, seconds(3));//等待声音数据准备好

if (lStatus != NO_ERROR) {

ALOGE("--- Immediate start timed out, status %d", lStatus);

mState = TONE_IDLE;

lResult = false;

}

}

} else {

mState = TONE_IDLE;

}

} else {

 

}

 

bool ToneGenerator::prepareWave() {

// Remove existing wave generators if any

clearWaveGens(); //清除mWaveGens

mpToneDesc = mpNewToneDesc;

mMaxSmp = TONEGEN_INF;//因为mDurationMs == -1,设置播放数据无限大

 

while (mpToneDesc->segments[segmentIdx].duration) {

// Get total number of sine waves: needed to adapt sine wave gain.

unsigned int lNumWaves = numWaves(segmentIdx);//得到有两种频率的声音

unsigned int freqIdx = 0;

unsigned int frequency = mpToneDesc->segments[segmentIdx].waveFreq[freqIdx];

while (frequency) {

// Instantiate a wave generator if ot already done for this frequency

if (mWaveGens.indexOfKey(frequency) == NAME_NOT_FOUND) {

ToneGenerator::WaveGenerator *lpWaveGen =

new ToneGenerator::WaveGenerator(mSamplingRate, //采样率

frequency, //声音频率

TONEGEN_GAIN/lNumWaves); //声音大小

mWaveGens.add(frequency, lpWaveGen);

}

frequency = mpNewToneDesc->segments[segmentIdx].waveFreq[++freqIdx];

}

segmentIdx++;

}

}

 

//只要开始播放,audiotrack会通过该回调函数一直请求数据,一直到stop

void ToneGenerator::audioCallback(int event, void* user, void *info) {

AudioTrack::Buffer *buffer = static_cast<AudioTrack::Buffer *>(info); //获取存放声音的buffer

ToneGenerator *lpToneGen = static_cast<ToneGenerator *>(user);

int16_t *lpOut = buffer->i16;

unsigned int lNumSmp = buffer->size/sizeof(int16_t); //存放声音缓存的大小

const ToneDescriptor *lpToneDesc = lpToneGen->mpToneDesc; //获取按键音的描述参数

 

while (lNumSmp) {

switch (lpToneGen->mState) {

case TONE_PLAYING:

lWaveCmd = WaveGenerator::WAVEGEN_CONT;

break;

case TONE_STARTING:

ALOGV("Starting Cbk");

lWaveCmd = WaveGenerator::WAVEGEN_START;

break;

case TONE_STOPPED:

ALOGV("Stopped Cbk");

goto audioCallback_EndLoop;

}

 

// Exit if tone sequence is over, 用于检验无限播放时的退出

if (lpToneDesc->segments[lpToneGen->mCurSegment].duration == 0 ||

lpToneGen->mTotalSmp > lpToneGen->mMaxSmp) {

if (lpToneGen->mState == TONE_PLAYING) {

lpToneGen->mState = TONE_STOPPING;

}

if (lpToneDesc->segments[lpToneGen->mCurSegment].duration == 0) {

goto audioCallback_EndLoop;

}

// fade out before stopping if maximum duration reached

lWaveCmd = WaveGenerator::WAVEGEN_STOP;

lpToneGen->mNextSegSmp = TONEGEN_INF; // forced to skip state machine management below

}

 

if (lGenSmp) {

unsigned int lFreqIdx = 0;

uint16_t lFrequency = lpToneDesc->segments[lpToneGen->mCurSegment].waveFreq[lFreqIdx];

while (lFrequency != 0) {

WaveGenerator *lpWaveGen = lpToneGen->mWaveGens.valueFor(lFrequency);

lpWaveGen->getSamples(lpOut, lGenSmp, lWaveCmd); //根据频率生成pcm数据

lFrequency = lpToneDesc->segments[lpToneGen->mCurSegment].waveFreq[++lFreqIdx];

}

}

 

audioCallback_EndLoop:

switch (lpToneGen->mState) {

case TONE_STARTING:

ALOGV("Cbk starting track");

lpToneGen->mState = TONE_PLAYING;

lSignal = true;

break;

case TONE_STOPPING:

ALOGV("Cbk Stopping");

lpToneGen->mState = TONE_STOPPED;

// Force loop exit

lNumSmp = 0;

break;

case TONE_STOPPED:

lpToneGen->mState = TONE_INIT;

ALOGV("Cbk Stopped track");

lpToneGen->mpAudioTrack->stop(); //停止播放声音,整个循环从此退出!!!

// Force loop exit

lNumSmp = 0;

buffer->size = 0;

lSignal = true;

break;

}

if (lSignal)

lpToneGen->mWaitCbkCond.broadcast();

}

}

 

void ToneGenerator::stopTone() {

ALOGV("stopTone");

if (mState != TONE_IDLE && mState != TONE_INIT) {

if (mState == TONE_PLAYING || mState == TONE_STARTING || mState == TONE_RESTARTING) {

struct timespec stopTime;

// If the start time is valid, make sure that the number of audio samples produced

// corresponds at least to the time between the start and stop commands.

// This is needed in case of cold start of the output stream.

if ((mStartTime.tv_sec != 0) && (clock_gettime(CLOCK_MONOTONIC, &stopTime) == 0)) {

time_t sec = stopTime.tv_sec - mStartTime.tv_sec;

auto nsec = stopTime.tv_nsec - mStartTime.tv_nsec;

if (nsec < 0) {

--sec;

nsec += 1000000000;

}

 

if ((sec + 1) > ((time_t)(INT_MAX / mSamplingRate))) {

mMaxSmp = sec * mSamplingRate;

} else {

// mSamplingRate is always > 1000

sec = sec * 1000 + nsec / 1000000; // duration in milliseconds

//通过设置最大的声音播放数据大小来控制播放结束

mMaxSmp = (unsigned int)(((int64_t)sec * mSamplingRate) / 1000);

}

ALOGV("stopTone() forcing mMaxSmp to %d, total for far %d", mMaxSmp, mTotalSmp);

} else {

mState = TONE_STOPPING;

}

}

ALOGV("waiting cond");

status_t lStatus = mWaitCbkCond.waitRelative(mLock, seconds(3));

}

 

小结

1. 按键音的播放是通过startTone, stopTone来控制播放时长的。而不是靠duration延时来控制播放时间的

2. 按键音的采样率(48000hz)是根据播放流的类型(AudioManager.STREAM_DTMF)来决定的。

采样频率一般共分为11025Hz、22050Hz、24000Hz、44100Hz、48000Hz五个等级

  • 11025Hz能达到AM调幅广播的声音品质
  • 22050Hz和24000HZ能达到FM调频广播的声音品质
  • 44100Hz则是理论上的CD音质界限
  • 48000Hz则更加精确一些。ouser a

3. 如何减少自动生成的音频数据

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值