转自:https://www.jianshu.com/p/632dce664c3d

音频播放

音频播放声音分为MediaPlayer和AudioTrack两种方案的。MediaPlayer可以播放多种格式的声音文件,例如MP3,WAV,OGG,AAC,MIDI等。然而AudioTrack只能播放PCM数据流。当然两者之间还是有紧密的联系,MediaPlayer在播放音频时,在framework层还是会创建AudioTrack,把解码后的PCM数流传递给AudioTrack,最后由AudioFlinger进行混音,传递音频给硬件播放出来。利用AudioTrack播放只是跳过Mediaplayer的解码部分而已。

AudioTrack作用

AudioTrack是管理和播放单一音频资源的类。AudioTrack仅仅能播放已经解码的PCM流,用于PCM音频流的回放。

AudioTrack实现PCM音频播放

AudioTrack实现PCM音频播放五步走

  • 配置基本参数
  • 获取最小缓冲区大小
  • 创建AudioTrack对象
  • 获取PCM文件,转成DataInputStream
  • 开启/停止播放

直接上代码再分析

import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.AudioTrack;import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;public class AudioTrackManager {private AudioTrack mAudioTrack;private DataInputStream mDis;//播放文件的数据流private Thread mRecordThread;private boolean isStart = false;private volatile static AudioTrackManager mInstance;//音频流类型private static final int mStreamType = AudioManager.STREAM_MUSIC;//指定采样率 (MediaRecoder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100,目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)private static final int mSampleRateInHz=44100 ;//指定捕获音频的声道数目。在AudioFormat类中指定用于此的常量private static final int mChannelConfig= AudioFormat.CHANNEL_CONFIGURATION_MONO; //单声道//指定音频量化位数 ,在AudioFormaat类中指定了以下各种可能的常量。通常我们选择ENCODING_PCM_16BIT和ENCODING_PCM_8BIT PCM代表的是脉冲编码调制,它实际上是原始音频样本。//因此可以设置每个样本的分辨率为16位或者8位,16位将占用更多的空间和处理能力,表示的音频也更加接近真实。private static final int mAudioFormat=AudioFormat.ENCODING_PCM_16BIT;//指定缓冲区大小。调用AudioRecord类的getMinBufferSize方法可以获得。private int mMinBufferSize;//STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到audiotrack中。这个和我们在socket中发送数据一样,// 应用层从某个地方获取数据,例如通过编解码得到PCM数据,然后write到audiotrack。private static int mMode = AudioTrack.MODE_STREAM;public AudioTrackManager() {initData();}private void initData(){//根据采样率,采样精度,单双声道来得到frame的大小。mMinBufferSize = AudioTrack.getMinBufferSize(mSampleRateInHz,mChannelConfig, mAudioFormat);//计算最小缓冲区//注意,按照数字音频的知识,这个算出来的是一秒钟buffer的大小。//创建AudioTrackmAudioTrack = new AudioTrack(mStreamType, mSampleRateInHz,mChannelConfig,mAudioFormat,mMinBufferSize,mMode);}/*** 获取单例引用** @return*/public static AudioTrackManager getInstance() {if (mInstance == null) {synchronized (AudioTrackManager.class) {if (mInstance == null) {mInstance = new AudioTrackManager();}}}return mInstance;}/*** 销毁线程方法*/private void destroyThread() {try {isStart = false;if (null != mRecordThread && Thread.State.RUNNABLE == mRecordThread.getState()) {try {Thread.sleep(500);mRecordThread.interrupt();} catch (Exception e) {mRecordThread = null;}}mRecordThread = null;} catch (Exception e) {e.printStackTrace();} finally {mRecordThread = null;}}/*** 启动播放线程*/private void startThread() {destroyThread();isStart = true;if (mRecordThread == null) {mRecordThread = new Thread(recordRunnable);mRecordThread.start();}}/*** 播放线程*/Runnable recordRunnable = new Runnable() {@Overridepublic void run() {try {//设置线程的优先级android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO);byte[] tempBuffer = new byte[mMinBufferSize];int readCount = 0;while (mDis.available() > 0) {readCount= mDis.read(tempBuffer);if (readCount == AudioTrack.ERROR_INVALID_OPERATION || readCount == AudioTrack.ERROR_BAD_VALUE) {continue;}if (readCount != 0 && readCount != -1) {//一边播放一边写入语音数据//判断AudioTrack未初始化,停止播放的时候释放了,状态就为STATE_UNINITIALIZEDif(mAudioTrack.getState() == mAudioTrack.STATE_UNINITIALIZED){initData();}mAudioTrack.play();mAudioTrack.write(tempBuffer, 0, readCount);}}stopPlay();//播放完就停止播放} catch (Exception e) {e.printStackTrace();}}};/*** 播放文件* @param path* @throws Exception*/private void setPath(String path) throws Exception {File file = new File(path);mDis = new DataInputStream(new FileInputStream(file));}/*** 启动播放** @param path*/public void startPlay(String path) {try {
//            //AudioTrack未初始化
//            if(mAudioTrack.getState() == AudioTrack.STATE_UNINITIALIZED){
//                throw new RuntimeException("The AudioTrack is not uninitialized");
//            }//AudioRecord.getMinBufferSize的参数是否支持当前的硬件设备
//            else if (AudioTrack.ERROR_BAD_VALUE == mMinBufferSize || AudioTrack.ERROR == mMinBufferSize) {
//                throw new RuntimeException("AudioTrack Unable to getMinBufferSize");
//            }else{setPath(path);startThread();
//            }} catch (Exception e) {e.printStackTrace();}}/*** 停止播放*/public void stopPlay() {try {destroyThread();//销毁线程if (mAudioTrack != null) {if (mAudioTrack.getState() == AudioRecord.STATE_INITIALIZED) {//初始化成功mAudioTrack.stop();//停止播放}if (mAudioTrack != null) {mAudioTrack.release();//释放audioTrack资源}}if (mDis != null) {mDis.close();//关闭数据输入流}} catch (Exception e) {e.printStackTrace();}}}

配置基本参数

  • StreamType音频流类型

    最主要的几种STREAM

    1. AudioManager.STREAM_MUSIC:用于音乐播放的音频流。
    2. AudioManager.STREAM_SYSTEM:用于系统声音的音频流。
    3. AudioManager.STREAM_RING:用于电话铃声的音频流。
    4. AudioManager.STREAM_VOICE_CALL:用于电话通话的音频流。
    5. AudioManager.STREAM_ALARM:用于警报的音频流。
    6. AudioManager.STREAM_NOTIFICATION:用于通知的音频流。
    7. AudioManager.STREAM_BLUETOOTH_SCO:用于连接到蓝牙电话时的手机音频流。
    8. AudioManager.STREAM_SYSTEM_ENFORCED:在某些国家实施的系统声音的音频流。
    9. AudioManager.STREAM_DTMF:DTMF音调的音频流。
    10. AudioManager.STREAM_TTS:文本到语音转换(TTS)的音频流。

    为什么分那么多种类型,其实原因很简单,比如你在听music的时候接到电话,这个时候music播放肯定会停止,此时你只能听到电话,如果你调节音量的话,这个调节肯定只对电话起作用。当电话打完了,再回到music,你肯定不用再调节音量了。

    其实系统将这几种声音的数据分开管理,STREAM参数对AudioTrack来说,它的含义就是告诉系统,我现在想使用的是哪种类型的声音,这样系统就可以对应管理他们了。

  • MODE模式(static和stream两种)

    • AudioTrack.MODE_STREAM

      STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到AudioTrack中。这个和我们在socket中发送数据一样,应用层从某个地方获取数据,例如通过编解码得到PCM数据,然后write到AudioTrack。这种方式的坏处就是总是在JAVA层和Native层交互,效率损失较大。

    • AudioTrack.MODE_STATIC

      STATIC就是数据一次性交付给接收方。好处是简单高效,只需要进行一次操作就完成了数据的传递;缺点当然也很明显,对于数据量较大的音频回放,显然它是无法胜任的,因而通常只用于播放铃声、系统提醒等对内存小的操作

  • 采样率:mSampleRateInHz

    采样率 (MediaRecoder 的采样率通常是8000Hz AAC的通常是44100Hz。 设置采样率为44100,目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)

  • 通道数目:mChannelConfig

    首先得出声道数,目前最多只支持双声道。为什么最多只支持双声道?看下面的源码

      static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {int channelCount = 0;switch(channelConfig) {case AudioFormat.CHANNEL_OUT_MONO:case AudioFormat.CHANNEL_CONFIGURATION_MONO:channelCount = 1;break;case AudioFormat.CHANNEL_OUT_STEREO:case AudioFormat.CHANNEL_CONFIGURATION_STEREO:channelCount = 2;break;default:if (!isMultichannelConfigSupported(channelConfig)) {loge("getMinBufferSize(): Invalid channel configuration.");return ERROR_BAD_VALUE;} else {channelCount = AudioFormat.channelCountFromOutChannelMask(channelConfig);}}.......}
    
  • 音频量化位数:mAudioFormat(只支持8bit和16bit两种。)

      if ((audioFormat !=AudioFormat.ENCODING_PCM_16BIT)&& (audioFormat !=AudioFormat.ENCODING_PCM_8BIT)) {returnAudioTrack.ERROR_BAD_VALUE;}
    

最小缓冲区大小

mMinBufferSize取决于采样率、声道数和采样深度三个属性,那么具体是如何计算的呢?我们看一下源码

static public int getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) {....int size = native_get_min_buff_size(sampleRateInHz, channelCount, audioFormat);if (size <= 0) {loge("getMinBufferSize(): error querying hardware");return ERROR;}else {return size;}
}

看到源码缓冲区的大小的实现在nativen层中,接着看下native层代码实现:

rameworks/base/core/jni/android_media_AudioTrack.cppstatic jint android_media_AudioTrack_get_min_buff_size(JNIEnv*env,  jobject thiz,jint sampleRateInHertz,jint nbChannels, jint audioFormat) {int frameCount = 0;if(AudioTrack::getMinFrameCount(&frameCount, AUDIO_STREAM_DEFAULT,sampleRateInHertz) != NO_ERROR) {return -1;}return  frameCount * nbChannels * (audioFormat ==javaAudioTrackFields.PCM16 ? 2 : 1);}

这里又调用了getMinFrameCount,这个函数用于确定至少需要多少Frame才能保证音频正常播放。那么Frame代表了什么意思呢?可以想象一下视频中帧的概念,它代表了某个时间点的一幅图像。这里的Frame也是类似的,它应该是指某个特定时间点时的音频数据量,所以android_media_AudioTrack_get_min_buff_size中最后采用的计算公式就是:

至少需要多少帧每帧数据量 = frameCount * nbChannels * (audioFormat ==javaAudioTrackFields.PCM16 ? 2 : 1);
公式中frameCount就是需要的帧数,每一帧的数据量又等于:
Channel数
每个Channel数据量= nbChannels * (audioFormat ==javaAudioTrackFields.PCM16 ? 2 : 1)层层返回getMinBufferSize就得到了保障AudioTrack正常工作的最小缓冲区大小了。

创建AudioTrack对象

取到mMinBufferSize后,我们就可以创建一个AudioTrack对象了。它的构造函数原型是:

public AudioTrack(int streamType, int sampleRateInHz, int channelConfig, int audioFormat,int bufferSizeInBytes, int mode)
throws IllegalArgumentException {this(streamType, sampleRateInHz, channelConfig, audioFormat,bufferSizeInBytes, mode, AudioManager.AUDIO_SESSION_ID_GENERATE);
}

在源码中一层层往下看

public AudioTrack(AudioAttributes attributes, AudioFormat format, int bufferSizeInBytes,int mode, int sessionId)throws IllegalArgumentException {super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_AUDIOTRACK);.....// native initializationint initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,sampleRate, mChannelMask, mChannelIndexMask, mAudioFormat,mNativeBufferSizeInBytes, mDataLoadMode, session, 0 /*nativeTrackInJavaObj*/);if (initResult != SUCCESS) {loge("Error code "+initResult+" when initializing AudioTrack.");return; // with mState == STATE_UNINITIALIZED}mSampleRate = sampleRate[0];mSessionId = session[0];if (mDataLoadMode == MODE_STATIC) {mState = STATE_NO_STATIC_DATA;} else {mState = STATE_INITIALIZED;}baseRegisterPlayer();
}

最终看到了又在native_setup方法中,在native中initialization,看看实现些什么了

/*frameworks/base/core/jni/android_media_AudioTrack.cpp*/static int  android_media_AudioTrack_native_setup(JNIEnv*env, jobject thiz, jobject weak_this,jint streamType, jintsampleRateInHertz, jint javaChannelMask,jint audioFormat, jintbuffSizeInBytes, jint memoryMode, jintArray jSession){   .....sp<AudioTrack>lpTrack = new AudioTrack();.....AudioTrackJniStorage* lpJniStorage =new AudioTrackJniStorage();

这里调用了native_setup来创建一个本地AudioTrack对象,创建一个Storage对象,从这个Storage猜测这可能是存储音频数据的地方,我们再进入了解这个Storage对象。

if (memoryMode== javaAudioTrackFields.MODE_STREAM) {lpTrack->set(...audioCallback, //回调函数&(lpJniStorage->mCallbackData),//回调数据0,0,//shared memtrue,// thread cancall JavasessionId);//audio session ID} else if (memoryMode ==javaAudioTrackFields.MODE_STATIC) {...lpTrack->set(... audioCallback, &(lpJniStorage->mCallbackData),0,      lpJniStorage->mMemBase,// shared memtrue,// thread cancall JavasessionId);//audio session ID}....// native_setup结束

调用set函数为AudioTrack设置这些属性——我们只保留两种内存模式(STATIC和STREAM)有差异的地方,入参中的倒数第三个是lpJniStorage->mMemBase,而STREAM类型时为null(0)。太深了,对于基础的知识先研究到这里吧

获取PCM文件,转成DataInputStream

根据存放PCM的路径获取到PCM文件

/*** 播放文件* @param path* @throws Exception*/
private void setPath(String path) throws Exception {File file = new File(path);mDis = new DataInputStream(new FileInputStream(file));
}

开启/停止播放

  • 开始播放

      public void play()throws IllegalStateException {if (mState != STATE_INITIALIZED) {throw new IllegalStateException("play() called on uninitialized AudioTrack.");}//FIXME use lambda to pass startImpl to superclassfinal int delay = getStartDelayMs();if (delay == 0) {startImpl();} else {new Thread() {public void run() {try {Thread.sleep(delay);} catch (InterruptedException e) {e.printStackTrace();}baseSetStartDelayMs(0);try {startImpl();} catch (IllegalStateException e) {// fail silently for a state exception when it is happening after// a delayed start, as the player state could have changed between the// call to start() and the execution of startImpl()}}}.start();}}
    
  • 停止播放

    停止播放音频数据,如果是STREAM模式,会等播放完最后写入buffer的数据才会停止。如果立即停止,要调用pause()方法,然后调用flush方法,会舍弃还没有播放的数据。

    public void stop()throws IllegalStateException {if (mState != STATE_INITIALIZED) {throw new IllegalStateException("stop() called on uninitialized AudioTrack.");}// stop playingsynchronized(mPlayStateLock) {native_stop();baseStop();mPlayState = PLAYSTATE_STOPPED;mAvSyncHeader = null;mAvSyncBytesRemaining = 0;}
    }
    
  • 暂停播放

    暂停播放,调用play()重新开始播放。

  • 释放本地AudioTrack资源

    AudioTrack.release()

  • 返回当前的播放状态

    AudioTrack.getPlayState()

注意: flush()只在模式为STREAM下可用。将音频数据刷进等待播放的队列,任何写入的数据如果没有提交的话,都会被舍弃,但是并不能保证所有用于数据的缓冲空间都可用于后续的写入。

总结

  1. 播放一个PCM文件,按照上面的五步走。
  2. 注意参数有配置,如量化位数是8BIT还是16BIT等。
  3. 想更加了解AudioTrack里的方法就动手写一个demo深入了解那些方法的用途。
  4. 能不能续播(还没有验证)

作者:安仔夏天勤奋
链接:https://www.jianshu.com/p/632dce664c3d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

音频播放AudioTrack之入门篇相关推荐

  1. Android 音频播放——AudioTrack直接播PCM、MediaPlayer播媒体文件可以是audio

    http://www.cnblogs.com/stnlcd/p/7151438.html

  2. Android 音频开发(一) 基础入门篇

    今天主要讲解下Android音频开发的入门知识,希望对想入门却不知如何下手的朋友有所帮助,同时希望能得到高手的指点和帮助. 深入细化基础技能知识点 大致细化如下10个知识点. 音频开发的主要应用有哪些 ...

  3. 音视频开发入门基础知识(音频入门篇)

    RTSP实时音视频开发实战课程:<RTSP实时音视频开发实战> 音视频开发入门基础知识(音频入门篇) 目录 前言 音频的采集和播放 音频常见的格式 音频的编码 前言 在音视频开发入门基础知 ...

  4. STM32 USB AUDIO 基础篇①——通过STM32CubeMX生成USB Speaker音频播放Demo(史上最简单)

    文章目录 一.硬件原理 二.STM32CubeMX配置 2.1 RCC 2.2 SYS 2.3 I2C1 2.4 USART1 2.5 USB_OTG_FS 2.6 I2S2 2.7 USB_DEVI ...

  5. vue音乐播放器之入门篇

    第一章 前言 这里对应的是课程中的第一章到第三章,因为前三章内容比较少,我就把它们合并成一章.我比较懒,要让我多写三章的内容,没门.这一章会比较简单,是一个项目的准备过程,仅仅开发了两个很简单的基础组 ...

  6. 嵌入式平台音频播放器设计(基础篇)

    一.目的 相信不少同学都见过以前那种很小的MP3播放器(暴露年龄),高级一点的还带一个小的单色液晶屏,想必理工科男都想自己设计一款这样的一款播放器. 那么如何才能设计实现一个简单的音乐播放器呢? 本文 ...

  7. ios+html+音频播放,iOS音频篇:使用AVPlayer播放网络音乐

    2018-11-13更新:已更新工程配置和修改部分代码,Xcode9能直接运行此项目了.但由于项目中使用的豆瓣API已经停止支持,所以项目已不能正常演示,是否会继续更新就看缘分嘞 _... 引言 假如 ...

  8. [洪流学堂]Hololens开发入门篇3:使用基本功能开发一个小应用

    本文首发于"洪流学堂"公众号. 洪流学堂,让你快人几步 本教程基于Unity2017.2及Visual Studio 2017 本教程编写时间:2017年12月4日 本文内容提要 ...

  9. iOS 音频播放,录音,视频播放,拍照,视频录制

    iOS开发系列--音频播放.录音.视频播放.拍照.视频录制 2014-12-26 09:15 by KenshinCui, 149110 阅读, 67 评论, 收藏, 编辑 --iOS多媒体 概览 随 ...

最新文章

  1. 用一个创业故事串起操作系统原理(三)
  2. 博士申请 | 宾州州立大学 (PSU) 招收机器学习/对抗学习方向全奖博士
  3. 招聘面试的套路和原则
  4. JMetro版本4.8已发布
  5. 认真聊一下MySQL索引的底层实现!
  6. 如何从我的虚拟环境中更新pip本身?
  7. 一张图学会python3语法-一张图片在Python操作下的4种玩法(附源码)
  8. oracle分段分组函数,Oracle增强型分组函数
  9. Android开发:菜单栏Menu用法讲解
  10. 关于航模的几点总结积累
  11. vb.net 教程 8-15 数据库操作实例1
  12. WIN7 32 联想针式打印机 联想DP600+ 文字不全
  13. ev3无需使用计算机编程,Legoev3机器人怎么编程.docx
  14. spc 统计过程控制(Statistical Process Control)分析软件
  15. 文件服务器 配额,文件服务器配额邮件通知
  16. 料酒调味不宜用白酒代替
  17. 百度地图API调用实现获取经纬度以及标注
  18. zjs-my-diary-0220118
  19. 程序员即装逼又实用的Cmd命令行
  20. 【建议收藏】2020年中高级Android大厂面试秘籍,为你保驾护航金三银四,直通大厂(Android高级篇上)...

热门文章

  1. 磨人小问题-正正经经解决方法(2)——关于u盘(资料已备份)被写保护无法操作文件如何解决的问题——使用量产工具
  2. Angular深入理解管道Pipe
  3. weboffice 6版本实现在线word
  4. 浅谈 Adaboost 算法
  5. 记录一次htonl和ntohl的使用方法和差别
  6. 豆瓣音乐播放器XPlayer
  7. PTGui+PS生成全景图
  8. 钉钉接口报错java.net.UnknownHostException: oapi.dingtalk.com_无法访问oapi.dingtalk.com
  9. 宝付(上海宝付)用户说说那些“无故”被扣费背后的黑幕
  10. 「2022」字节-前端(互娱)笔试题