音视频

播放器

使用原生视频播放框架 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_MUSICAudioManager.STREAM_ALARMAudioManager.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 生命周期状态图

757858-20150913153559919-1943967807

从 Idle 到 End 状态就是 MediaPlayer 整个生命周期

  • 进入 Idle 状态 : MediaPlayer 刚被创建 new MediaPlayer() 或者 调用了 reset() 方法后, 进入 Idle (闲置) 状态

  • 进入 End 状态 : 在 Idle 状态调用 release() 方法后, 会进入 End (结束) 状态

简单音效播放

安卓中,简单音效播放,建议使用更轻量级的SoundPool,因为MediaPlayer 存在一些缺陷:

  1. 延时时间较长,且资源占用率高

  2. 不支持多个音频同时播放

    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_SYSTEMSTREAM_MUSIC
  • srcQuality指定声音品质,该参数无效,一般直接设置为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接口:MediaRecorderAudioRecord:

  • 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():检查是否使用蓝牙A2DP
  • isSpeakerphoneOn():检查扬声器是否打开
  • 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”

20190301102949549

Copyright © Arcticfox 2021 all right reserved,powered by Gitbook文档修订于: 2022-05-01 12:20:20

results matching ""

    No results matching ""