文章的标题已经表明了,我想提一个简单的问题,播放视频的时候我觉得视频的声音太大或者太小了,我想调整一下声音,怎么办?

我想大多数同学想笑了,这是一个问题吗?每个手机都有调整音量大小的物理按键,你操作一下就行了,例如对Android平台,系统都提供了操作声音音量的设置接口,MediaPlayer提供了setVoume函数,AudioTrack也提供了setVolume函数,你直接设置一下不就行了吗?这个问题似乎有点多余,不太专业。但是我只说一句,让用户频繁的操作,这似乎不是一个友好的应用开发者应该说的话。

那么问题来了,可以在用户无感知的情况下自动调整声音的大小,达到一个让用户满意的音量吗?

又有人开始说了,这个也简单,我还是操作系统提供的了seVolume接口函数,只不过不是让用户操作设置,我自己利用程序操作设置不就行了吗?也能打成神不知鬼不觉的地步。我的意思是,千万别,这样的思路更加糟糕,因为系统提供的setVolume直接操作底层的StreamType对应的输出音量,影响的是手机上所有的应用,你一顿操作猛如虎,直接让手机上的所有应用都被迫接收你这种“比较流氓”的改变。

那么问题来了?能不能在不影响其他外部应用和手机硬件设置的前提下改变输出的音量大小?这是本文需要分享的东西。

在开始之前,我觉得有必要分析一下什么是声音?

声音的本质是一种能量波,由振动而产生的能量波,通过传输介质传输出去。声音有三个属性:

  • 音调:声音频率的高低叫做音调(Pitch),是声音的三个主要的主观属性,即音量(响度)、音调、音色(也称音品) 之一。表示人的听觉分辨一个声音的调子高低的程度。音调主要由声音的频率决定,同时也与声音强度有关
  • 音量:人主观上感觉声音的大小(俗称音量),由“振幅”(amplitude)和人离声源的距离决定,振幅越大响度越大,人和声源的距离越小,响度越大。(单位:分贝dB)
  • 音色:又称声音的品质,波形决定了声音的音色。声音因不同物体材料的特性而具有不同特性,音色本身是一种抽象的东西,但波形是把这个抽象直观的表现。音色不同,波形则不同。典型的音色波形有方波,锯齿波,正弦波,脉冲波等。不同的音色,通过波形,完全可以分辨的。

同等条件下,波长是决定音调高低的因素。

Android 设置媒体音量为100 手机媒体音量设置_python

同等条件下,振幅是决定音量高低的因素。

Android 设置媒体音量为100 手机媒体音量设置_Android 设置媒体音量为100_02

同等条件下,波纹是决定音色因素。

Android 设置媒体音量为100 手机媒体音量设置_Android 设置媒体音量为100_03

通过上面简单的分析,我们已经知道声音的音量实际上就是由声波的振幅决定的,我们需要调整声波的振幅。播放一个视频,需要经历下面几步:

  • 输入视频url
  • 确定视频的封装格式
  • 开始解封装
  • 识别视频的轨道数据
  • 分离轨道数据,音频轨道、视频轨道
  • 解码视频数据为原始数据,解码音频数据为原始数据
  • 做好音视频同步
  • 渲染视频原始数据,播放音频原始数据

上面加黑标红的部分就是我们改变声音振幅的地方,只有将声音数据解码为原始数据,我们加工原始数据的音频流,然后送到AudioTrack或者OpenSL ES内部播放即可。

例如我们使用开源的ExoPlayer播放器,我们想实现这个功能(当然原生的肯定是没有提供这个功能的)。我们需要在解码出音频数据之后,操作解码之后的音频帧数据,调整振幅,然后将得到的数据输出,渲染播放即可满足要求。下面的代码中volumeFlag表示调整振幅的系数,例如我想将声音的振幅调整为原始的1.2倍,那么volumeFlag就是1.2

private void scaleSamples(short samples[],
                            int position,
                            int numSamples,
                            float volumeFlag) {
    int fixedPointVolume = (int)(volumeFlag * 4096.0f);
    int start = position * channelCount;
    int stop = start + numSamples * channelCount;


    for(int xSample = start; xSample < stop; xSample++) {
      int value = (samples[xSample] * fixedPointVolume) >> 12;
      if(value > 32767) {
        value = 32767;
      } else if(value < -32767) {
        value = -32767;
      }
      samples[xSample] = (short)value;
    }
  }

但是日常生活中我们衡量声音大小使用dB(分贝)衡量的,如果服务器和客户端联调的话,我肯定是告知当前的平均分贝和标准分贝是多少。

  • 平均分贝:计算音频的每一帧数据的分贝,输出平均分贝
  • 标准分贝:当前情况下多少分贝是最合适的分贝

平均分贝我播放器肯定是无法获知的,视频没有播放完成,我们无法获知,但是服务器知道,可以传到客户端,那么分贝这振幅系数之间如何换算?

result = 20 * log(Cur/Max)
Cur表示当前振幅
Max表示最大振幅
所以声音的分贝总是负的(Android平台下是的)。
volume(dB) = 20 * log(Cur / Max)
(所有Android下面计算的分贝大小总是负的)
  • volume 表示计算出来的分贝值
  • Max表示最大振幅
  • Cur表示当前振幅
输入的参数有两个: MeanVolume , BaseVolume
MeanVolume: 平均分贝
BaseVolume: 基准分贝


BaseVolume - MeanVolume = result
20*log(CurBase/Max) - 20*log(Cur/Max) = result
20*log(CurBase/Cur)=result
CurBase/Cur = 10^(result/20)
CurBase = Cur * 10^(result/20)

所以我们最终决定的调整振幅大小的系统是(10^(result/20))