使用AudioTrack播放PCM音频数据(android)
众所周知,Android的MediaPlayer包含了Audio和video的播放功能,在Android的界面上,Music和Video两个应用程序都是调用MediaPlayer实现的。MediaPlayer在底层是基于OpenCore(PacketVideo)的库实现的,为了构建一个MediaPlayer程序,上层还包含了进程间通讯等内容,这种进程间通讯的基础是Android基本库中的Binder机制。但是该类只能对完整的音频文件进行操作,而不能直接对纯PCM音频数据操作。假如我们通过解码得到PCM数据源,又当如何将它们播放?没错,就是用AudioTrack这个类(MediaPlayer内部也是调用该类进行真正的播放音频流操作)下面这个DEMO演示了如何使用AudioTrack来播放PCM音频数据
废话不多说,先上效果图:

工程代码结构也较为简单:

简单说下思路,先把PCM音频数据从指定的路径文件读到内存,然后给AudioPlayer设置数据源,音频参数等,最后执行播放,暂停,停止等操作
贴上部分类代码片段:
- public class AudioParam {
- int mFrequency; // 采样率
- int mChannel; // 声道
- int mSampBit; // 采样精度
- }
- public interface PlayState {
- public static final int MPS_UNINIT = 0; // 未就绪
- public static final int MPS_PREPARE = 1; // 准备就绪(停止)
- public static final int MPS_PLAYING = 2; // 播放中
- public static final int MPS_PAUSE = 3; // 暂停
- }
AudioPlayer代码片段如下:
- public class AudioPlayer implements IPlayComplete{
- private final static String TAG = "AudioPlayer";
- public final static int STATE_MSG_ID = 0x0010;
- private Handler mHandler;
- private AudioParam mAudioParam; // 音频参数
- private byte[] mData; // 音频数据
- private AudioTrack mAudioTrack; // AudioTrack对象
- private boolean mBReady = false; // 播放源是否就绪
- private PlayAudioThread mPlayAudioThread; // 播放线程
- public AudioPlayer(Handler handler)
- {
- mHandler = handler;
- }
- public AudioPlayer(Handler handler,AudioParam audioParam)
- {
- mHandler = handler;
- setAudioParam(audioParam);
- }
- /*
- * 设置音频参数
- */
- public void setAudioParam(AudioParam audioParam)
- {
- mAudioParam = audioParam;
- }
- /*
- * 设置音频源
- */
- public void setDataSource(byte[] data)
- {
- mData = data;
- }
- /*
- * 就绪播放源
- */
- public boolean prepare()
- {
- if (mData == null || mAudioParam == null)
- {
- return false;
- }
- if (mBReady == true)
- {
- return true;
- }
- try {
- createAudioTrack();
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- return false;
- }
- mBReady = true;
- setPlayState(PlayState.MPS_PREPARE);
- return true;
- }
- private boolean mThreadExitFlag = false; // 线程退出标志
- private int mPrimePlaySize = 0; // 较优播放块大小
- private int mPlayOffset = 0; // 当前播放位置
- private int mPlayState = 0; // 当前播放状态
- /*
- * 播放音频的线程
- */
- class PlayAudioThread extends Thread
- {
- @Override
- public void run() {
- // TODO Auto-generated method stub
- Log.d(TAG, "PlayAudioThread run mPlayOffset = " + mPlayOffset);
- mAudioTrack.play();
- while(true)
- {
- if (mThreadExitFlag == true)
- {
- break;
- }
- try {
- int size = mAudioTrack.write(mData, mPlayOffset, mPrimePlaySize);
- mPlayOffset += mPrimePlaySize;
- } catch (Exception e) {
- // TODO: handle exception
- e.printStackTrace();
- AudioPlayer.this.onPlayComplete();
- break;
- }
- if (mPlayOffset >= mData.length)
- {
- AudioPlayer.this.onPlayComplete();
- break;
- }
- }
- mAudioTrack.stop();
- Log.d(TAG, "PlayAudioThread complete...");
- }
- }
下面来剖析以下如何使用AudioTrack来播放PCM音频数据
首先要构建一个AudioTrack对象:(需要采样率,声道,采样精度参数)
- private void createAudioTrack() throws Exception
- {
- // 获得构建对象的最小缓冲区大小
- int minBufSize = AudioTrack.getMinBufferSize(mAudioParam.mFrequency,
- mAudioParam.mChannel,
- mAudioParam.mSampBit);
- mPrimePlaySize = minBufSize * 2;
- Log.d(TAG, "mPrimePlaySize = " + mPrimePlaySize);
- // STREAM_ALARM:警告声
- // STREAM_MUSCI:音乐声,例如music等
- // STREAM_RING:铃声
- // STREAM_SYSTEM:系统声音
- // STREAM_VOCIE_CALL:电话声音
- mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
- mAudioParam.mFrequency,
- mAudioParam.mChannel,
- mAudioParam.mSampBit,
- minBufSize,
- AudioTrack.MODE_STREAM);
- // AudioTrack中有MODE_STATIC和MODE_STREAM两种分类。
- // STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到audiotrack中。
- // 这个和我们在socket中发送数据一样,应用层从某个地方获取数据,例如通过编解码得到PCM数据,然后write到audiotrack。
- // 这种方式的坏处就是总是在JAVA层和Native层交互,效率损失较大。
- // 而STATIC的意思是一开始创建的时候,就把音频数据放到一个固定的buffer,然后直接传给audiotrack,
- // 后续就不用一次次得write了。AudioTrack会自己播放这个buffer中的数据。
- // 这种方法对于铃声等内存占用较小,延时要求较高的声音来说很适用。
- }
然后开一个子线程从缓存区里分块取数据然后写入硬件设备进行播放
- private void startThread()
- {
- if (mPlayAudioThread == null)
- {
- mThreadExitFlag = false;
- mPlayAudioThread = new PlayAudioThread();
- mPlayAudioThread.start();
- }
- }
AudioTrack里有三个重要方法:
void play()
int write(byte[] audioData, int offsetInBytes, int sizeInBytes) (该方法是阻塞的)
void stop()
从前面那个线程代码可以看出,我们在写数据之前需要先执行 play(),然后才能进行write操作,当数据播放完毕或是线程被外部终止的时候最后调用stop()停止写数据;若执行了play操作但后面却没有执行write操作的话,或是write操作结束后没有调用stop,观察logcat会不断打印提示信息,这是提示我们对以上三个方法的调用要规范
只要大家设置的音频参数和音频数据都是正确的,就能顺畅的播放出声音,本例已经附带了用于测试的音频文件以及参数说明(已测试通过),具体看工程里音频数据这个文件夹下的readme.txt即可.网上有些童鞋反应说audiotrack播放音频不顺畅,如果数据源没问题的话估计是他们的demo里没有连续地执行write操作而导致的,其它的不多说了,觉得有用的童鞋自己写代码看吧。。。喜欢就顶一下吧!
代码链接如下:
https://github.com/dongweiq/study/AudioPlayerDemo
本文着重介绍audiotrack的使用,关于其底层原理,且看这位仁兄的文章:
http://www.cnblogs.com/innost/archive/2011/01/09/1931457.html
我的github地址:https://github.com/dongweiq/study
欢迎关注,欢迎star o(∩_∩)o 。有什么问题请邮箱联系 dongweiqmail@gmail.com qq714094450
使用AudioTrack播放PCM音频数据(android)的更多相关文章
- Android 音视频开发(三):使用 AudioTrack 播放PCM音频
一.AudioTrack 基本使用 AudioTrack 类可以完成Android平台上音频数据的输出任务.AudioTrack有两种数据加载模式(MODE_STREAM和MODE_STATIC),对 ...
- Android 音视频深入 二 AudioTrack播放pcm(附源码下载)
本篇项目地址,名字是录音和播放PCM,求starhttps://github.com/979451341/Audio-and-video-learning-materials 1.AudioTrack ...
- Android OpenSL ES 开发:Android OpenSL 录制 PCM 音频数据
一.实现说明 OpenSL ES的录音要比播放简单一些,在创建好引擎后,再创建好录音接口基本就可以录音了.在这里我们做的是流式录音,所以需要用至少2个buffer来缓存录制好的PCM数据,这里我们可以 ...
- 使用WindowsAPI实现播放PCM音频的方法
这篇文章主要介绍了使用WindowsAPI实现播放PCM音频的方法,很实用的一个功能,需要的朋友可以参考下 本文介绍了使用WindowsAPI实现播放PCM音频的方法,同前面一篇使用WindowsAP ...
- JavaCV FFmpeg采集麦克风PCM音频数据
前阵子用一个JavaCV的FFmpeg库实现了YUV视频数据地采集,同样的采集PCM音频数据也可以采用JavaCV的FFmpeg库. 传送门:JavaCV FFmpeg采集摄像头YUV数据 首先引入 ...
- AudioRecord 录制播放PCM音频
AudioRecord 与 MediaRecorder 区别 AudioRecord 基于字节流录制,输出的是pcm数据,未进行压缩,直接保存的pcm文件不能被播放器识别播放. 可以对音频文件进行实时 ...
- linux下mono播放PCM音频
测试环境: Ubuntu 14 MonoDevelop CodeBlocks 1.建立一个共享库(shared library) 这里用到了linux下的音频播放库,alsa-lib. al ...
- 使用WindowsAPI播放PCM音频
这一篇文章同上一篇<使用WindowsAPI获取录音音频>原理具有相似之处,不再详细介绍函数与结构体的参数 1. waveOutGetNumDevs 2. waveOutGetDevCap ...
- 11.3、Libgdx的音频之播放PCM音频
(官网:www.libgdx.cn) audio模块可以提供对音频硬件的直接访问. 音频硬件是通过AudioDevice接口进行的抽象. 以下创建一个新的AudioDevice实例: AudioDev ...
随机推荐
- innodb的innodb_buffer_pool_size和MyISAM的key_buffer_size
一. key_buffer_size 对MyISAM表来说非常重要. 如果只是使用MyISAM表,可以把它设置为可用内存的 30-40%.合理的值取决于索引大小.数据量以及负载 -- 记住,MyISA ...
- android之GridView实现九宫格布局
效果图: 代码如下: MyGridView.java /** * 自定义GridView 解决在scrollview中只显示第一行数据的问题 * Created by Spring on 2015/1 ...
- php魔法常量
有七个魔术常量它们的值随着它们在代码中的位置改变而改变.例如 __LINE__ 的值就依赖于它在脚本中所处的行来决定.这些特殊的常量不区分大小写,如下: 名称 说明 __LINE__ 文件中的当前行号 ...
- [转]C++宏定义详解
一.#define的基本用法 #define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率,但学生在学习时往往不能 理解该命令的本质 ...
- 请阐述调用Activity有哪几种方法,并写出相关的Java代码
请阐述调用Activity有哪几种方法,并写出相关的Java代码. 答案:可以采用两种方式调用Activity:显示调用和隐式调用.显示调用直接指定了Activity,代码如下: Intent int ...
- WDCP一些常用的一健安装包可选安装组件
为有更好的性能,也为更简洁的系统,一些不是常用或不是基本的功能,都将做为可选的安装组件需要用到的就安装 memcache的安装 wget -c http://down.wdlinux.cn/in/me ...
- PHPCMS v9 导航显示二级菜单,显示相邻栏目,内容页显示二级栏目
导航显示二级栏目 <div class="menu">{pc:content action="category" catid="0&quo ...
- AMH4.2 Ftp账号路径修改设置
AMH4.2的ftp控制有点不尽如人意,每个ftp账号只能对应一个站点:如果按照面板所提供的权限,有多少个站就得设置多少个ftp账号,这一操作就会非常麻烦,造成效率低下:不过AMH5.0已经能够通过面 ...
- Java实现SOAP协议 之 HelloWorld
Java实现SOAP协议 之 HelloWorld SOAP是把基于HTTP的WEB技术与XML的灵活性和可扩展性组合在了一起. 准备: 1. 准备一个web server.Tomcat 下载地址 h ...
- bzoj2821作诗
http://www.lydsy.com/JudgeOnline/problem.php?id=2821 分块 我们把数列分成$\sqrt{N}$块 记$f[i][j]$表示第i块到第j块的答案,这个 ...