在安卓中使用AudioTrack播放WAV文件

27

我正在使用Android,尝试让我的AudioTrack应用播放Windows的.wav文件(Tada.wav)。老实说,这不应该很难,但我听到了很多奇怪的东西。该文件保存在手机的小SD卡上,读取内容似乎不是问题,但当我播放文件时(使用我认为正确的参数),我会听到几秒钟的白噪声,然后声音似乎变成了可能正确的东西。

我已经成功地录制并回放了自己的声音——我根据这个示例的指导创建了一个.pcm文件:

http://emeadev.blogspot.com/2009/09/raw-audio-manipulation-in-android.html

(没有反向遮罩)......

有人有播放Android上的.wav文件的建议或者知道网络上的例子吗?

谢谢, R。

7个回答

27

我偶然发现了答案(坦白说,是通过尝试一些我认为不会起作用的东西),如果有人感兴趣的话...在我的原始代码中(源自原帖中链接的示例),数据是这样从文件中读取的:

    InputStream             is  = new FileInputStream       (file);
    BufferedInputStream     bis = new BufferedInputStream   (is, 8000);
    DataInputStream         dis = new DataInputStream       (bis);      //  Create a DataInputStream to read the audio data from the saved file

    int i = 0;                                                          //  Read the file into the "music" array
    while (dis.available() > 0)
    {
        music[i] = dis.readShort();                                     //  This assignment does not reverse the order
        i++;
    }

    dis.close();                                                        //  Close the input stream

在这个版本中,music[] 是一个 SHORTS 数组。因此,在这里使用 readShort() 方法似乎是有道理的,因为数据是 16 位 PCM... 但是,在 Android 上似乎会出现问题。我将代码改为以下内容:

     music=new byte[(int) file.length()];//size & length of the file
    InputStream             is  = new FileInputStream       (file);
    BufferedInputStream     bis = new BufferedInputStream   (is, 8000);
    DataInputStream         dis = new DataInputStream       (bis);      //  Create a DataInputStream to read the audio data from the saved file

    int i = 0;                                                          //  Read the file into the "music" array
    while (dis.available() > 0)
    {
        music[i] = dis.readByte();                                      //  This assignment does not reverse the order
        i++;
    }

    dis.close();                                                        //  Close the input stream
在这个版本中,music[] 是一个字节数组。我仍然告诉AudioTrack它是16位PCM数据,我的Android似乎没有问题将一个字节数组写入这样配置的AudioTrack...无论如何,它最终听起来是正确的,所以如果其他人想在他们的Android上播放Windows声音,那么这就是解决方案。啊,字节序......

这应该是被接受的答案,因为它回答了问题。 ;) - gary
我敢肯定我之前在这里留下了一句“谢谢你,Gary”的话,但现在却不见了……谢谢你,Gary! - Rich
1
这篇文章似乎是关于使用Java的short和byte读取WAV格式时的差异。 - Vanja
如何知道music[]的长度以进行初始化? - Fischer
基本上,只要“足够大”。如果您知道要发送的文件,请使用略大于其大小的值,否则您就必须从中随意取一个数字。 - Rich

12

我发现了很多长篇回答这个问题的方法。我的最终解决方案,虽然经过了许多剪切和粘贴,但实际上并不是完全属于我自己,总结如下:

public boolean play() {

    int i = 0;
    byte[] music = null;
    InputStream is = mContext.getResources().openRawResource(R.raw.noise);

    at = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
        AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT,
        minBufferSize, AudioTrack.MODE_STREAM);

    try{
        music = new byte[512];
        at.play();

        while((i = is.read(music)) != -1)
            at.write(music, 0, i);

    } catch (IOException e) {
        e.printStackTrace();
    }

    at.stop();
    at.release();
    return STOPPED;
}

STOPPED 只是作为一个信号返回的 "true",用于重置暂停/播放按钮。并且在类初始化器中:

public Mp3Track(Context context) {
    mContext = context;
    minBufferSize = AudioTrack.getMinBufferSize(44100,
        AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);
}

上下文只是调用活动中的 "this"。 您可以在sd卡上使用FileInputStream等。我的文件在res/raw中


1
谢谢您,我尝试了所有的答案,这个给出了最好的结果。虽然当音轨开始时仍会有一点音频点击声... - methodMan
我在开头和结尾处听到了点击声,而且播放超过了文件的结尾,继续播放甚至不在任何音频文件中的音频。很奇怪! - rraallvv
1
音频以慢动作播放。可能出了什么问题? - Darush

7
您是否在将文件的其余数据转储到缓冲区之前跳过了文件的前44个字节?这44个字节是WAVE头,如果您尝试播放它们,它们会听起来像随机噪音。
此外,您确定使用与您尝试播放的WAVE相同的属性创建了AudioTrack吗(采样率、比特率、通道数等)?Windows实际上在“文件属性”页面中向您提供了这些信息: alt text

我已经尝试了所有可能的跳过头部和不跳过头部的组合,但是运气并不太好。白噪声的持续时间远远超过了44个字节的长度。Tada.wav是一个168千字节的文件,所以“播放”头部应该几乎不可察觉,对吗? - Rich
1
正确,文件头会在很短的时间内生成垃圾数据。你确定你创建的AudioTrack与你尝试播放的WAVE文件具有相同的属性吗(采样率、比特率、声道数等)? - Aaron C
我有一个十六进制编辑器,我确信我能正确地从头部获取数字。我有点惊讶在互联网上找不到任何一种wav文件分析工具。我感到困惑,而且Windows Media Player肯定可以播放这个文件... - Rich
2
如果您想要对您的值进行合理性检查,Windows文件属性页面实际上可以很好地向您提供此信息。 - Aaron C
请注意,wav文件头并不总是44个字节。对于32位浮点数数据,头部为58个字节。 - Filip Kwiatkowski

2
正如Aaron C所说,您需要跳过初始的44个字节或(我更喜欢的方法)读取前44个字节作为WAVE头。这样,您就知道WAVE包含多少通道、每个样本的位数、长度等等。

在这里,您可以找到一个良好的WAVE头解析器/编写器的实现。


1
请不要使用糟糕的解析代码。WAV解析非常容易实现http://soundfile.sapp.org/doc/WaveFormat/,您将能够解析采样率、位深度和通道数,因此您会感到非常满意。
另外x86和ARM(至少默认情况下)都是小端字节序,因此本机字节序的WAV文件应该没有任何问题。

3
如果您能指出哪些解析代码是糟糕的,以及为什么它们糟糕,那么您的回答将更有用。 - arlomedia

1

请确认在AudioTrack构造函数中是否使用AudioTrack.MODE_STREAM而不是AudioTrack.MODE_STATIC

AudioTrack at = new AudioTrack(
  AudioManager.STREAM_MUSIC,
  sampleRate,
  AudioFormat.CHANNEL_IN_STEREO,
  AudioFormat.ENCODING_PCM_16BIT,
  // buffer length in bytes
  outputBufferSize,
  AudioTrack.MODE_STREAM
);

0

示例wav文件:
http://www.mauvecloud.net/sounds/pcm1644m.wav

示例代码:

public class AudioTrackPlayer {
    Context mContext;
    int minBufferSize;
    AudioTrack at;
    boolean STOPPED;

    public AudioTrackPlayer(Context context) {
        Log.d("------","init");
        mContext = context;
        minBufferSize = AudioTrack.getMinBufferSize(44100,
                AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT);
    }

    public boolean play() {
        Log.d("------","play");
        int i = 0;
        byte[] music = null;
        InputStream is = mContext.getResources().openRawResource(R.raw.pcm1644m);

        at = new AudioTrack(AudioManager.STREAM_MUSIC, 44100,
                AudioFormat.CHANNEL_CONFIGURATION_MONO, AudioFormat.ENCODING_PCM_16BIT,
                minBufferSize, AudioTrack.MODE_STREAM);
        try {
            music = new byte[512];
            at.play();

            while ((i = is.read(music)) != -1)
                at.write(music, 0, i);

        } catch (IOException e) {
            e.printStackTrace();
        }
        at.stop();
        at.release();
        return STOPPED;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接