Android-音视频(3):用AudioTrack播放音频PCM

1.介绍AudioTrck


1.1首先简单介绍什么是PCM?

脉冲编码调制(Pulse Code Modulation,PCM),它的作用是把一个时间连续,取值连续的模拟信号变换成时间离散,取值离散的数字信号后在信道中传输。脉冲编码调制就是对模拟信号先抽样,再对样值幅度量化,编码的过程。

pcm是一个通信上的概念,脉冲编码调制,是编码。wav是媒体概念,体现的是封装。wav文件可以封装pcm编码信息,也可以封装其他编码格式,例如mp3等

从手机麦克风采集的数据就是pcm原始数据。

1.2AudioTrck

首先它可以直接播放pcm音频数据,但是不能播放其它的格式如MP3,AAC,WAV等,不过更加上层的API如MediaPlay却可以播放这些格式,但是同时MediaPlay也失去了对底层数据流的一些操作,如AudioTrack可以控制每一帧数据,可以自己转化任意格式,自由性比较强大,而MediaPlay却不行。

AudioTrack播放有两种模式:

  • MODE_STREAM:在这种模式下,需要先play,然后通过write一次次把音频数据写到AudioTrack中。每次都需要把数据从用户提供的Buffer中拷贝到AudioTrack内部的Buffer中,这在一定程度上会使引入延时。为解决这一问题,AudioTrack就引入了第二种模式。
  • MODE_STATIC:这种模式下,需要先write,再play.。先把所有数据通过一次write调用传递到AudioTrack中的内部缓冲区,后续就不必再传递数据了。但它也有一个缺点,就是一次write的数据不能太多,否则系统无法分配足够的内存来存储全部数据。

1.3在看播放之前,先来了解一下AudioTrack是如何构造出来的。

在AudioTrack构造函数中,会接触到AudioManager.STREAM_MUSIC这个参数。

Android将系统的声音streamType分为好几种流类型,下面是几个常见的:

  • STREAM_ALARM:警告声
  • STREAM_MUSIC:音乐声,例如music等
  • STREAM_RING:铃声
  • STREAM_SYSTEM:系统声音,例如低电提示音,锁屏音等
  • STREAM_VOCIE_CALL:通话声

注意:上面这些类型的划分和音频数据本身并没有关系。例如MUSIC和RING类型都可以是某首MP3歌曲。另外,声音流类型的选择没有固定的标准,例如,铃声预览中的铃声可以设置为MUSIC类型。音频流类型的划分和Audio系统对音频的管理策略有关。

构造有多种:

下面两种对于低版本api可以用:

另外一种:

后面三个int参数就不用介绍了,上面有,介绍前两个参数(not null)。

format实例描述将通过此AudioTrack播放的数据的格式。见AudioFormat.Builder用于配置音频格式参数,如编码数据格式、通道和采样率,就是将之前的api的三个参数(数据格式,通道,采样率)合为一体,很容易理解。

attributes则是对之前的api的streamType的 一个改进。

streamType是描述音频的播放行为,是用来表述声音是用于干嘛的,在AudioAttributes可以用setUsage()设置,有以下值:

int USAGE_ALARM

当使用是警报时使用的用法值(例如:

int USAGE_ASSISTANCE_ACCESSIBILITY

当使用是用于可访问性时使用的用法值,例如与屏幕读取器一起使用。

int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE

当使用是驱动或导航指示时使用的用法值。

int USAGE_ASSISTANCE_SONIFICATION

当使用是声纳化时使用的用法值,例如用户界面声音。

int USAGE_ASSISTANT

用于用户查询、音频指令或帮助语句的音频响应的用法值。

int USAGE_GAME

当使用是用于游戏音频时使用的用法值。

int USAGE_MEDIA

当使用是媒体(如音乐或电影配乐)时使用的用法值。

int USAGE_NOTIFICATION

当使用是通知时要使用的用法值。

int USAGE_NOTIFICATION_COMMUNICATION_DELAYED

当使用是非即时通信类型(如电子邮件)的通知时,要使用的使用值。

int USAGE_NOTIFICATION_COMMUNICATION_INSTANT

当使用是“即时”通信(如聊天或SMS)的通知时使用的用法值。

int USAGE_NOTIFICATION_COMMUNICATION_REQUEST

当使用是输入/结束通信的请求时使用的用法值,例如VoIP通信或视频会议。

int USAGE_NOTIFICATION_EVENT

当使用是为了吸引用户的注意时使用的使用价值,例如提醒或低电量警告。

int USAGE_NOTIFICATION_RINGTONE

当使用是电话铃声时使用的使用价值。

int USAGE_UNKNOWN

用法未知时使用的用法值。

int USAGE_VOICE_COMMUNICATION

当使用是语音通信时使用的使用价值,如电话或VoIP。

int USAGE_VOICE_COMMUNICATION_SIGNALLING

当使用是在呼叫信号时使用的用法值,例如使用“繁忙”的嘟嘟声或DTMF音调。

AudioAttributes多加了一个东西就是内容类型,通过setContentType()设置,有以下值:

int CONTENT_TYPE_MOVIE

当内容类型是配乐时使用的内容类型值,通常伴随电影或电视节目。

int CONTENT_TYPE_MUSIC

当内容类型是音乐时要使用的内容类型值。

int CONTENT_TYPE_SONIFICATION

当内容类型是用于伴随用户操作的声音时使用的内容类型值,例如表示键单击的响声或声音效果,或事件,例如在游戏中接收奖励的声音的类型。

int CONTENT_TYPE_SPEECH

内容类型为语音时使用的内容类型值。

int CONTENT_TYPE_UNKNOWN

当内容类型未知时使用的内容类型值,或定义的内容类型值以外的值。

2.使用MODE_STREAM模式播放 audiorecordtest.pcm


//音频采样率 (MediaRecoder的采样率通常是8000Hz AAC的通常是44100Hz.设置采样率为44100目前为常用的采样率,官方文档表示这个值可以兼容所有的设置)    
private static final int mSampleRateInHz = 44100;    //声道    
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;
private static final String mFileName = Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+"audiorecordtest.pcm";

private void playPCM_STREAM() throws FileNotFoundException {
        if (maudioTrack != null){
            maudioTrack.stop();
            maudioTrack.release();
            maudioTrack = null;
        }
//先估算最小缓冲区大小
        mBufferSizeInBytes = AudioRecord.getMinBufferSize(mSampleRateInHz,mChannelConfig,mAudioFormat);
//创建AudioTrack
        maudioTrack = new AudioTrack(
          new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build(),
          new AudioFormat.Builder()
                .setSampleRate(mSampleRateInHz)
                .setEncoding(mAudioFormat)
                .setChannelMask(mChannelConfig)
                .build(),
          mBufferSizeInBytes,
          AudioTrack.MODE_STREAM, AudioManager.AUDIO_SESSION_ID_GENERATE
        );
        maudioTrack.play();  这个模式需要先play
        File file = new File(mFileName); //原始pcm文件
        final FileInputStream fileInputStream;
        if (file.exists()){
            fileInputStream = new FileInputStream(file);
            new Thread(){
                @Override
                public void run() {
                    try {
                        byte[] buffer = new byte[mBufferSizeInBytes];
                        while(fileInputStream.available() > 0){
                            int readCount = fileInputStream.read(buffer); //一次次的读取
                            //检测错误就跳过
                            if (readCount == AudioTrack.ERROR_INVALID_OPERATION|| readCount == AudioTrack.ERROR_BAD_VALUE){
                                continue;
                            }
                            if (readCount != -1 && readCount != 0){
//可以在这个位置用play()
                                //输出音频数据
                                maudioTrack.write(buffer,0,readCount); //一次次的write输出播放
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    Log.i("TAG","STREAM模式播放完成");
                }
            }.start();
        }
    }

3.使用MODE_STATIC模式播放 audiorecordtest.pcm


//pcm数据的大小
    byte[] databyte;
// static模式,需要将音频数据一次性write到AudioTrack的内部缓冲区
    private void playPCM_STATIC() {
        if (maudioTrack != null){
            maudioTrack.stop();
            maudioTrack.release();
            maudioTrack = null;
        }
        new AsyncTask<Void,Void,Void>(){
            @Override
            protected Void doInBackground(Void... voids) {
                try {
                    InputStream in = new FileInputStream(new File(mFileName));
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    int b;
                    while((b = in.read()) != -1){
                        out.write(b);
                    }
                    //得到数据大小
                    databyte = out.toByteArray();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                //创建AudioTrack.MODE_STATIC模式的AudioTrack
                maudioTrack = new AudioTrack(
                        new AudioAttributes.Builder()
                        .setUsage(AudioAttributes.USAGE_MEDIA)
                        .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                        .build(),
                        new AudioFormat.Builder()
                        .setSampleRate(mSampleRateInHz)
                        .setEncoding(mAudioFormat)
                        .setChannelMask(mChannelConfig)
                        .build(),
                        databyte.length,
                        AudioTrack.MODE_STATIC,AudioManager.AUDIO_SESSION_ID_GENERATE
                );
                //将数据 databyte 一次性写入AudioTrack,之后才能play
                maudioTrack.write(databyte,0,databyte.length);
                maudioTrack.play();
                Log.i("TAG","STATIC模式播放完成");
            }
        }.execute();
    }

猜你喜欢

转载自blog.csdn.net/qq_38261174/article/details/82871542