【问题标题】:Play looping audio using AudioTrack使用 AudioTrack 播放循环音频
【发布时间】:2016-03-24 01:17:42
【问题描述】:

由于MediaPlayer 的一些众所周知的错误,例如循环轨道之间出现的小间隙,我想过渡到使用 Android 的功能 AudioTrack 而不是 MediaPlayer

有人建议我使用AudioTrack,但还没有找到很多使用它的例子。我确实在 SO 上找到了一个关于 AudioTrack 的问题,并使用其中一些代码来破解一些东西:

public class TestActivity extends Activity implements Runnable {

    Button playButton;
    byte[] byteData = null;
    int bufSize;
    AudioTrack myAT = null;
    Thread playThread = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        playButton = (Button) findViewById(R.id.testButton);

        InputStream inputStream = getResources().openRawResource(R.raw.whitenoise_wav);
        try {
            byteData = new byte[ inputStream.available()];
        } catch (IOException e) {
            e.printStackTrace();
        }

        try {
            inputStream.read(byteData);
            inputStream.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        initialize();

        playButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {

                playThread.start();
            }
        });
    }

    void initialize() {

        bufSize = android.media.AudioTrack.getMinBufferSize(44100,
                AudioFormat.CHANNEL_CONFIGURATION_STEREO,
                AudioFormat.ENCODING_PCM_16BIT);

        myAT = new AudioTrack(AudioManager.STREAM_MUSIC,
                44100, AudioFormat.CHANNEL_CONFIGURATION_STEREO,
                AudioFormat.ENCODING_PCM_16BIT, bufSize,
                AudioTrack.MODE_STREAM);
        myAT.setVolume(.2f);

        playThread = new Thread(this);
    }

    public void run() {
        if (myAT != null) {
            myAT.play();
            myAT.setLoopPoints(0, byteData.length, 6);
            myAT.write(byteData, 0, byteData.length);
        }
    }
}

所以这似乎播放了整个音轨(约 1:00 分钟)然后停止。现在这里的最终目标是两个有 2 个单独的音轨同时播放和循环。我目前在/res/raw/ 目录中有音轨,但如果这样会更好,我可以将它们移动到一个简单的assets 文件夹中。我当前对AudioTrack 的实现是否正确?如果是这样,我将如何让它循环?

总之:如何使用AudioTrack 无间隙地播放循环音频?

欢迎提出有关获取循环音频的替代方法的建议,例如第三方库。

【问题讨论】:

标签: android audiotrack


【解决方案1】:

您不能使用配置了AudioTrack.MODE_STREAM 的 AudioTrack 进行循环播放。如果使用MODE_STREAMAudioTrack 需要不断填充新样本。

但是你可以用AudioTrack.MODE_STATIC配置它并传递整个缓冲区来播放(我的意思是:如果你需要混合两个样本,你必须传递混合样本)。

setLoopPoints:设置循环点和循环次数。循环可以是无限的。与 setPlaybackHeadPosition 类似,必须停止或暂停轨道才能更改循环点,并且必须使用 MODE_STATIC 模式。

请注意,AudioTrack 播放原始 PCM 样本,不支持 WAV、MP3 或其他容器。

【讨论】:

  • 如果文件存储在您的原始资源中,您可以在获取资源时返回InputStream 并将其输入AudioTrack。通过循环,我的意思是任何正确循环它的方式,无论是连续输入数据还是输入一次并自动循环,它根本不能有间隙。
  • 我明白你的意思,我知道.setLoopingPoints() 的自动循环只能在静态模式下完成。我正在寻找一个使用MODE_STATIC 和/或MODE_STREAM 的无缝循环示例,因为我遇到的大多数示例都没有真正起作用。我玩了一下,使用静态模式和.setLoopingPoints 得到了一个有效的实现,尽管它有时在播放过程中会出现故障。我还注意到,如果它没有在新线程中播放,它会在启动时产生响亮的启动声音,然后进入静音状态。在主线程上玩有问题吗?
【解决方案2】:

this的例子,好像是类似的问题,解决了连续喂AudioTrack的问题。

class ToneGenerator {
    int sampleRate = 8000;
    double sample[] = null;
    byte generatedSnd[] = null;
    int m_ifreq = 400;
    Thread m_PlayThread = null;
    boolean m_bStop = false;
    AudioTrack m_audioTrack = null;
    int m_play_length = 1000;//in seconds

    static public void PlayTone(int freq, int play_length) {
        ToneGenerator player = new ToneGenerator();
        player.m_ifreq = freq;
        player.m_play_length = play_length;
        player.play();
    }

    synchronized void stop() {
        m_bStop = true;
        if (m_PlayThread != null) {
            try {
                m_PlayThread.interrupt();
                m_PlayThread.join();
                m_PlayThread = null;
            } catch (Exception e) {

            }
        }
        if (m_audioTrack != null) {
            m_audioTrack.stop();
            m_audioTrack.release();
            m_audioTrack = null;
        }
    }

    synchronized void play() {
        m_bStop = false;
        m_PlayThread = new Thread() {
            public void run() {
                try {
                    int iToneStep = 0;

                    m_audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
                            sampleRate, AudioFormat.CHANNEL_OUT_MONO,
                            AudioFormat.ENCODING_PCM_16BIT, 2 * sampleRate,
                            AudioTrack.MODE_STREAM);

                    while (!m_bStop && m_play_length-- > 0) {
                        genTone(iToneStep++);

                        m_audioTrack.write(generatedSnd, 0, generatedSnd.length);
                        if (iToneStep == 1) {
                            m_audioTrack.play();
                        }
                    }
                } catch (Exception e) {
                    Log.e("Tone", e.toString());
                } catch (OutOfMemoryError e) {
                    Log.e("Tone", e.toString());
                }

            }
        };
        m_PlayThread.start();
    }

    //Generate tone data for 1 seconds
    synchronized void genTone(int iStep) {
        sample = new double[sampleRate];

        for (int i = 0; i < sampleRate; ++i) {
            sample[i] = Math.sin(2 * Math.PI * (i + iStep * sampleRate) / (sampleRate / m_ifreq));
        }

        // convert to 16 bit pcm sound array
        // assumes the sample buffer is normalised.
        generatedSnd = new byte[2 * sampleRate];
        int idx = 0;
        for (final double dVal : sample) {
            // scale to maximum amplitude
            final short val = (short) ((dVal * 32767));
            // in 16 bit wav PCM, first byte is the low order byte
            generatedSnd[idx++] = (byte) (val & 0x00ff);
            generatedSnd[idx++] = (byte) ((val & 0xff00) >>> 8);
        }
    }

}

【讨论】:

  • 为什么是 2 * sampleRate?
  • 因为是16bit采样,所以你做8*2
【解决方案3】:

您正在传递流的字节长度。 setLoopPoints() 期望流中的音频块数。请参阅文档中的 getBufferSizeInFrames()。

但是。我提醒您,大多数特定的音频格式都不是基于块的;它们是基于样本的。 MP3 是唯一的例外;它在 1152 字节边界上对齐。如果源内容是 MP3 并且没有明确创作为块对齐,那么无缝循环是不可能的。您似乎使用“whitenoise.wav”作为您的音频源。除非此文件的长度明确与块大小对齐,否则循环时可能会出现音频伪影。

解决方法是在循环时交叉淡化开始和结束帧块,并自行处理所有缓冲,但您必须自己编写代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多