音视频
播放器
使用原生视频播放框架 MediaPlayer
MediaPlayer + TextureView示例代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:keepScreenOn="true"
android:gravity="center_vertical|center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextureView
android:id="@+id/video"
android:layout_width="wrap_content"
android:layout_height="400dp" />
</LinearLayout>
public class MainActivity extends AppCompatActivity {
private MediaPlayer mMediaPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextureView textureView = findViewById(R.id.video);
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
initPlayer(new Surface(surface));
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
return false;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
});
}
@Override
protected void onPause() {
super.onPause();
if (mMediaPlayer != null) mMediaPlayer.pause();
}
@Override
protected void onResume() {
super.onResume();
if (mMediaPlayer != null) mMediaPlayer.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mMediaPlayer != null) mMediaPlayer.release();
mMediaPlayer = null;
}
private void initPlayer(Surface surface){
try {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setSurface(surface);
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mMediaPlayer.setDataSource("https://v-cdn.zjol.com.cn/277003.mp4");
mMediaPlayer.prepareAsync();
mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mMediaPlayer.start();
mMediaPlayer.setLooping(true);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意:setAudioStreamType方法设置音频输出的模式,一般有三种AudioManager.STREAM_MUSIC、AudioManager.STREAM_ALARM、AudioManager.STREAM_SYSTEM
MediaPlayer 常用API
getCurrentPosition():得到当前的播放位置getDuration():得到文件的时间getVideoHeight():得到视频高度getVideoWidth():得到视频宽度isLooping():是否循环播放isPlaying():是否正在播放pause():暂停prepare():准备(同步)prepareAsync():准备(异步)release():释放MediaPlayer对象reset():重置MediaPlayer对象seekTo(int msec):指定播放的位置(以毫秒为单位的时间)setAudioStreamType(int streamtype):指定流媒体的类型setDisplay(SurfaceHolder sh):设置用SurfaceHolder来显示多媒体setLooping(boolean looping):设置是否循环播放setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener): 网络流媒体的缓冲监听setOnCompletionListener(MediaPlayer.OnCompletionListener listener): 网络流媒体播放结束监听setOnErrorListener(MediaPlayer.OnErrorListener listener): 设置错误信息监听setOnVideoSizeChangedListener(MediaPlayer.OnVideoSizeChangedListener listener): 视频尺寸监听setScreenOnWhilePlaying(boolean screenOn):设置是否使用SurfaceHolder显示setVolume(float leftVolume, float rightVolume):设置音量start():开始播放stop():停止播放
MediaPlayer 生命周期状态图

从 Idle 到 End 状态就是 MediaPlayer 整个生命周期
进入 Idle 状态 : MediaPlayer 刚被创建
new MediaPlayer()或者 调用了reset()方法后, 进入 Idle (闲置) 状态进入 End 状态 : 在 Idle 状态调用 release() 方法后, 会进入 End (结束) 状态
简单音效播放
安卓中,简单音效播放,建议使用更轻量级的SoundPool,因为MediaPlayer 存在一些缺陷:
延时时间较长,且资源占用率高
不支持多个音频同时播放
public void createSoundPool() {
if (mSoundPool == null) {
// 5.0以上
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mSoundPool = new SoundPool.Builder().setMaxStreams(5).build();
} else { // 5.0 以前
mSoundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
}
}
}
SoundPool(int maxStreams, int streamType, int srcQuality) 参数说明:
maxStreams指定支持多少个声音,SoundPool对象中允许同时存在的最大流的数量。streamType指定声音类型,一般使用STREAM_SYSTEM或STREAM_MUSICsrcQuality指定声音品质,该参数无效,一般直接设置为0
加载资源的四个方法,它们都会返回一个声音ID,后面通过这个ID来播放指定的声音:
int load(Context context, int resld, int priority):从 resld 对应的资源加载int load(FileDescriptor fd, long offset, long length, int priority):从文件描述符加载资源int load(AssetFileDescriptor afd, int priority):从asset目录读取资源int load(String path, int priority):通过存储路径去加载资源
注意,priority参数并没什么用处,一般设置为1。
play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)参数说明:
- soundID:load返回的声音ID
- leftVolume:左声道音量设置
- rightVolume:右声道音量设置
- priority:流的优先级,数值越高,优先级越大
- loop:指定是否循环:
-1表示无限循环,0表示不循环,其他值表示要重复播放的次数 - rate:指定播放速率。1.0的播放率可以使声音按照其原始频率,而2.0的播放速率,可以使声音按照其 原始频率的两倍播放。如果为0.5的播放率,则播放速率是原始频率的一半。播放速率的取值范围是0.5至2.0
录音
Android提供了两个用于录音的API接口:MediaRecorder 和AudioRecord:
MediaRecorder:使用简单,集成了录音、编码、压缩等功能,并支持少量的录音音频格式,录制的音频文件可以用系统自带播放器播放。但缺点是支持的格式过少且无法实时处理音频数据。AudioRecord:更底层的录音API。直接操纵硬件获取原始音频流数据,能实现对音频的实时处理以及边录边播功能,输出是PCM语音数据,如果保存成音频文件,不能直接被播放器播放,必须先写代码实现数据编码以及压缩。
关于MediaRecorder的使用,可查看官方 MediaRecorder文档
简单示例
//开始录制
private void startRecord(){
if(mRecorder == null){
File dir = new File(Environment.getExternalStorageDirectory(),"sounds");
if(!dir.exists()){
dir.mkdirs();
}
mRecorder = new MediaRecorder();
//音频输入源(麦克风)
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
//设置输出格式 :THREE_GPP/MPEG-4/RAW_AMR/Default (RAW_AMR只支持音频且音频编码要求为AMR_NB)
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
//设置编码格式: AAC/AMR_NB/AMR_MB/Default
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
// 设置保存路径
mRecorder.setOutputFile(dir.getAbsolutePath()+File.separator+System.currentTimeMillis()+".m4a");
try {
mRecorder.prepare();
//开始录制
mRecorder.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//停止录制,资源释放
private void stopRecord(){
if(mRecorder != null){
mRecorder.stop();
mRecorder.release();
mRecorder = null;
}
}
注意添加权限请求,6.0以上版本时,还需要动态权限处理
<!-- 内置存储写权限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 录音权限-->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
音频输出切换
手机音频的输出有扬声器(Speaker)、听筒(Telephone Receiver)、有线耳机(WiredHeadset)、蓝牙音箱(Bluetooth A2DP)等。一般情况下,插拔耳机、连接断开蓝牙耳机等操作,系统都会自动切换音频到相应的输出设备上。比如电话免提就是从听筒切换到外放扬声器,插入耳机就从扬声器切换到耳机。
除此外,我们可能还需要适应一些特定的需求,自行切换音频输出。还有一种情况,如音乐App需要在拔出耳机时暂停播放,避免突然切换到扬声器而导致的尴尬情况。在Android 开发中,需使用AudioManager来管理音频。
根据文档的描述,A2DP(Advanced Audio Distribution Profile)是一种单向的高品质音频数据传输链路,通常用于播放立体声音乐。SCO 则是一种双向的音频数据的传输链路,该链路只支持8K及16K单声道的音频数据,只能用于普通语音的传输。两者的主要区别是:A2DP只能播放,默认是打开的,而SCO既能录音也能播放,默认是关闭的。
// 从系统中获取到AudioManager对象
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
一些相关的方法:
isBluetoothA2dpOn():检查是否使用蓝牙A2DPisSpeakerphoneOn():检查扬声器是否打开isWiredHeadsetOn():检查线控耳机是否连接着(只用来检查耳机是否是插入状态,并不能判断当前的音频是通过耳机输出的)setSpeakerphoneOn(boolean on):选择使用扬声器setBluetoothScoOn(boolean on):选择使用蓝牙SCO耳机setMode(int mode):设置音频模式
// 切换到扬声器
public void switchSpeaker(){
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn(false);
mAudioManager.setSpeakerphoneOn(true);
}
// 切换到蓝牙音箱
public void switchHeadset(){
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mAudioManager.startBluetoothSco();
mAudioManager.setBluetoothScoOn(true);
mAudioManager.setSpeakerphoneOn( false);
}
// 切换到线控耳机
public void switchHeadset(){
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mAudioManager.stopBluetoothSco();
mAudioManager.setBluetoothScoOn( false);
mAudioManager.setSpeakerphoneOn(false);
}
// 切换到听筒
public void switchReceiver(){
audioManager.setSpeakerphoneOn(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
} else {
audioManager.setMode(AudioManager.MODE_IN_CALL);
}
}
最后需要注意添加权限:
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
当结束时,将音频输出模式恢复
audioManager.setMode(AudioManager.MODE_NORMA);
公众号“编程之路从0到1”