当前位置: 代码网 > 科技>人工智能>物联网 > AudioTrack播放PCM音频

AudioTrack播放PCM音频

2024年08月01日 物联网 我要评论
我们先简单看下AudioTrack提供了哪些API。

目录

1、mediaplayer和audiotrack

2 audiotrack的介绍

2.1. 构造方法

2.2. action  写入、播放、暂停、停止、释放

2.3. 状态机(getstate以及getplaystate)

2.4 具体实现

3 audiotrack模式

3.1 static模式

3.2   stream模式

4 遇到的问题


1、mediaplayer和audiotrack

android sdk 中提供了三种播放声音的api,常见的是mediaplayer和audiotrack

● 其中audiotrack管理、播放单一音频资源。可以将pcm音频数据传输到音频接收器,以供播放,只能播放源码流即pcm,wav封装格式的音频也可以用audiotrack播放,但是wav头部分在播放解析是会发出噪音。

● 而mediaplayer可以播放多种格式的音频文件,比如 mp3 aac等,因为mediaplayer会在framework层创建对应的音频解码器。

既然mediaplayer可以播放那么多的音频格式,为什么我们还要学习audiotrack呐?

首先mediaplayer在framwork层还是会创建audiotrack,把解码后的pcm流传递给audiotrack,再传递给audiofliger进行混音播放。

每一个音频流对应一个audiotrack,audiotrack会在创建时注册到audioflinger中,audioflinger把所有的audiotrack进行混合mixer,然后输送到audiohardware进行播放。

android最多可以同时创建32个音频流。在短视频编辑等应用领域,**对视频进行添加配乐进行编辑,需要把视频中的音轨和配乐中的音轨进行解码pcm进行混合再编码**。**再或者我们在剪映等视频编辑app可以添加多个音轨**,就想audition一样强大。这些都都需要我们对audiotrack有一定的了解掌握。

2 audiotrack的介绍

我们先简单看下audiotrack提供了哪些api

2.1. 构造方法

public audiotrack(int streamtype, int samplerateinhz, int channelconfig, int audioformat,

 int buffersizeinbytes, int mode)

 其中采样率samplerateinhz、声道数channelconfig、音频格式audioformat以及音频缓冲区大小buffersizeinbytes 这四个概念和上一篇《audiorecord录制pcm音频》中的介绍的audiorecord的构造方法的参数意义以及获取方式基本一致。下面我们看下另外两个参数streamtype以及mode

streamtype音频流的类型,有如下几种

●  audiomanager#stream_voice_call:电话声音

● audiomanager#stream_system:系统声音

● audiomanager#stream_ring:铃声

● audiomanager#stream_music:音乐声

● audiomanager#stream_alarm:闹铃声

●  audiomanager#stream_notification:通知声

这里我们使用的是audiomanager#stream_music。

 下面我们重点看下mode

 @param mode streaming or static buffer.

 **mode_static and mode_stream**

● static模式:一次性将所有的数据放到一个固定的buffer,然后直接传送给audiotrack,简单有效,通常应用于播放铃声或者系统提示音等,占用内存较少的音频数据

● stream模式:一次一次的将音频数据流写入到audiotrack对象中,并持续处于阻塞状态,当数据从java层到native层执行播放完毕后才返回,这种方式可以避免由于音频过大导致内存占用过多。当然对应的不足就是总是在java和native层进行交互,并且阻塞知道播放完毕,效率损失较大。

2.2. action  写入、播放、暂停、停止、释放

write(byte audiodata, int offsetinbytes, int sizeinbytes)把pcm数据写入到audiotrack对象

 播放、暂停、停止、释放 常规的播放action操作。

2.3. 状态机(getstate以及getplaystate)

audiotrack中有两个state,一个是audiotrack是否已经初始化,后续的action操作都依赖于此,这个有点类似mediaplayer的prepared状态,只有处于**prepared**状态之后才可以进行其他播放相关操作

 另外一个就是**playstate**,用于记录判断当前处于什么播放状态。

 状态的改变加速,处理多线程同步问题

 private final object mplaystatelock = new object();

2.4 具体实现

我们那上一篇《audiorecord录制pcm音频》中示例代码产生的pcm作为audiotrack的数据播放源来。跟进mode不同,分别实现

3 audiotrack模式

3.1 static模式

//1. 初始化参数和buffer
private void initaudiotrackparams() {
    samplerateinhz = 44100;
    channels = audioformat.channel_out_mono;//错误的写成了channel_in_mono
    audioformat = audioformat.encoding_pcm_16bit;
    buffersize = audiotrack.getminbuffersize(samplerateinhz, channels, audioformat);
    pcmfile = new file(getexternalfilesdir(environment.directory_music), "raw.pcm");
    if (pcmfile.exists()) {
        haspcmfile = true;
    }
}

private void initstaticbuff() {
    //staic模式是一次读取全部的数据,在play之前要先完成{@link audiotrack.write()}
    if (audiotrackthread != null) {
        audiotrackthread.interrupt();
    }
    audiotrackthread = new thread(new runnable() {
        @override
        public void run() {
            fileinputstream fileinputstream = null;
            try {
                //init audiotrack 需要先确定buffersize
                fileinputstream = new fileinputstream(pcmfile);
                long size = fileinputstream.getchannel().size();
                staicbuff = new byte[(int) size];
                bytearrayoutputstream bytearrayoutputstream = new bytearrayoutputstream(staicbuff.length);
                int bytevalue = 0;
                long starttime = system.currenttimemillis();
                while ((bytevalue = fileinputstream.read()) != -1) {
                    //                        log.d(tag, "run: " + bytevalue);
                    //耗时操作
                    bytearrayoutputstream.write(bytevalue);
                }
                log.d(tag, "bytearrayoutputstream write time: " + (system.currenttimemillis() - starttime));
                staicbuff = bytearrayoutputstream.tobytearray();
                isreadying = true;
            } catch (ioexception e) {
                e.printstacktrace();
            } catch (throwable e) {
                e.printstacktrace();
            } finally {
                if (fileinputstream != null) {
                    try {
                        fileinputstream.close();
                    } catch (ioexception e) {
                        e.printstacktrace();
                    }
                }
                log.d(tag, "playwithstaicmode: end");
            }
        }
    });
    audiotrackthread.start();
}

//. 2. 点击播放
private void play(byte[] staicbuff) {
    //1. static模式是一次读去pcm到内存,比较耗时,只有读取完之后才可以调用play
    if (!isreadying) {
        toast.maketext(this, "请稍后", toast.length_short).show();
        return;
    }

    //2.如果正在播放中,重复点击播放,则停止当次播放,调用reloadstaticdata重新加载数据,然后play
    if (isplaying) {
        audiotrack.stop();
        audiotrack.reloadstaticdata();
        log.d(tag, "playwithstaicmode: reloadstaticdata");
        audiotrack.play();
        return;
    }

    //3。否则,就先释放audiotrack,然后重新初始化audiotrack进行
    releaseaudiotrack();
    int state = initaudiotrackwithmode(audiotrack.mode_static, staicbuff.length);
    if (state == audiotrack.state_uninitialized) {
        log.e(tag, "run: state is uninit");
        return;
    }

    //4. 把pcm写入audiotrack,然后进行播放
    long starttime = system.currenttimemillis();
    int result = audiotrack.write(staicbuff, 0, staicbuff.length);
    log.d(tag, "audiotrack.write staic: result=" + result+" totaltime="+ (system.currenttimemillis() - starttime));
    audiotrack.play();
    isplaying = true;
}

private void pauseplay() {
    if (audiotrack != null) {
        if (audiotrack.getstate() == audiotrack.state_initialized) {
            audiotrack.pause();
            audiotrack.flush();
        }
        isplaying = false;
        log.d(tag, "pauseplay: isplaying false");
    }
    if (audiotrackthread != null) {
        audiotrackthread.interrupt();
    }
}

private void releaseaudiotrack() {
    if (audiotrack != null && audiotrack.getstate() == audiotrack.state_initialized) {
        audiotrack.stop();
        audiotrack.release();
        isplaying = false;
        log.d(tag, "pauseplay: isplaying false");
    }

    if (audiotrackthread != null) {
        audiotrackthread.interrupt();
    }
}

3.2   stream模式

1. 初始化参数   
    private void initaudiotrackparams() {
    samplerateinhz = 44100;
    channels = audioformat.channel_out_mono;//错误的写成了channel_in_mono
    audioformat = audioformat.encoding_pcm_16bit;
    buffersize = audiotrack.getminbuffersize(samplerateinhz, channels, audioformat);
    pcmfile = new file(getexternalfilesdir(environment.directory_music), "convert.wav");//"raw.pcm"
    if (pcmfile.exists()) {
        haspcmfile = true;
    }
}

2. 点击进行播放
    private void play() {
    releaseaudiotrack();
    int state = initaudiotrackwithmode(audiotrack.mode_stream, buffersize);
    if (state == audiotrack.state_uninitialized) {
        log.e(tag, "run: state is uninit");
        return;
    }

    audiotrackthread = new thread(new runnable() {
        @override
        public void run() {
            fileinputstream fileinputstream = null;
            try {
                fileinputstream = new fileinputstream(pcmfile);
                byte[] buffer = new byte[buffersize / 2];
                int readcount;
                log.d(tag, "run: threadid=" + thread.currentthread() + " playstate=" + audiotrack.getplaystate());
                //stream模式,可以先调用play
                audiotrack.play();
                while (fileinputstream.available() > 0) {
                    readcount = fileinputstream.read(buffer);
                    if (readcount == audiotrack.error_bad_value || readcount == audiotrack.error_invalid_operation) {
                        continue;
                    }
                    if (audiotrack == null) {
                        return;
                    } else {
                        log.i(tag, "run: audiotrack.getstate()" + audiotrack.getstate() + " audiotrack.getplaystate()=" + audiotrack.getplaystate());
                    }
                    //                        audiotrack.getplaystate()
                    //一次一次的写入pcm数据到audiotrack.由于是在子线程中进行write,快速连续点击可能主线程触发了stop或者release,导致子线程write异常:illegalstateexception: unable to retrieve audiotrack pointer for write()
                    //所以加playstate的判断
                    if (readcount > 0 && audiotrack != null && audiotrack.getplaystate() == audiotrack.playstate_playing && audiotrack.getstate() == audiotrack.state_initialized) {
                        audiotrack.write(buffer, 0, readcount);
                    }
                }
            } catch (ioexception | illegalstateexception e) {
                e.printstacktrace();
                log.e(tag, "play: " + e.getmessage());
            } finally {
                if (fileinputstream != null) {
                    try {
                        fileinputstream.close();
                    } catch (ioexception e) {
                        e.printstacktrace();
                    }
                }
                log.d(tag, "playwithstreammode: end  threadid=" + thread.currentthread());
            }
        }
    });
    audiotrackthread.start();
}

private void pauseplay() {
    if (audiotrack != null) {
        if (audiotrack.getstate() > audiotrack.state_uninitialized) {
            audiotrack.pause();
        }
        log.d(tag, "pauseplay: isplaying false getplaystate= " + audiotrack.getplaystate());
    }
    if (audiotrackthread != null) {
        audiotrackthread.interrupt();
    }
}

private void releaseaudiotrack() {
    if (audiotrack != null && audiotrack.getstate() == audiotrack.state_initialized) {
        audiotrack.stop();
        audiotrack.release();
        log.d(tag, "pauseplay: isplaying false");

    }
    if (audiotrackthread != null) {
        audiotrackthread.interrupt();
    }
}

4 遇到的问题

纸上得来终觉浅,绝知此事要实践

 本篇文章原计划昨天完成,但是在实践中遇到了不少问题,不过最终都得以解决,记录如下

1. stream模式快速点击 声音重叠,如何停止:在触发播放前先停止和释放auidotrack,然后在进行init,在audiotrack写入数据的线程中write操作要做好audiottrack的状态判断。具体实现见上面小节的代码

2. 如何监听播放进度:audiotrack有没有像mediaplayer的丰富的监听回调,比如说,播放进度,播放完成回调,异常回调等。遗憾的是还真没有,针对static模式的播放结束监听倒是可以借助setnotificationmarkerposition 和 setplaybackpositionupdatelistener来判断来判断。具体见上面小节中static模式的实现

3. staic模式下有时候无法播放;音频在快速连续点击中加了isplaying的片段,如果正在playing中有触发了play,会先stop然后调用audiotrack.reloadstaticdata()加载数据流,再进行播放,但是发现快速连续点击是间隔一次才会播放生效,原因还是audiotrack资源没有被正确使用,改为了先release在进行init的方式。

4. illegalstateexception: unable to retrieve audiotrack pointer for write():这个异常是stream模式时在主线程出发了stop或者release,而在audiotrack子线程write时抛出的异常,原因就是播放状态不对,如果已经处于stropped状态,再进行write操作就会报这个错误,所以write时加个playstate状态的检验。具体解决实现见上面小节的代码。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com