使用AudioTrack播放230MB/20分钟PCM16位WAV文件的技术咨询
Great question! Let's break down how to play your WAV file correctly with AudioTrack—especially since it's a 230MB large file that needs careful memory handling to avoid crashes.
First, let's align your audio parameters with what AudioTrack expects (your ffmpeg output gives all the critical details):
- Sample Rate: 44100 Hz (matches your file)
- Channels: Stereo (use
AudioFormat.CHANNEL_OUT_STEREO; noteCHANNEL_CONFIGURATION_STEREOis deprecated since API 16, so we'll add a backward-compatible check) - Encoding: 16-bit PCM (
AudioFormat.ENCODING_PCM_16BIT, which matches yourpcm_s16leformat) - Bitrate: 1411 kb/s (this is automatically calculated from the above parameters, so no extra config needed)
Since your file is far too large to load entirely into memory, we'll use STREAM mode for AudioTrack. This lets us read chunks of the file and play them incrementally, avoiding OutOfMemoryErrors.
Step-by-Step Implementation
1. Add Required Permissions
If your WAV file is stored in external storage, add this to your AndroidManifest.xml (and request runtime permission if targeting API 23+):
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
2. Full Robust Code Example
Here's a complete implementation with stream-based playback, error handling, and proper resource cleanup:
import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; import android.os.Build; import java.io.FileInputStream; import java.io.IOException; public class WavStreamPlayer { private AudioTrack audioTrack; private Thread playbackThread; private boolean isPlaying = false; // Match your WAV file's parameters private static final int SAMPLE_RATE = 44100; private static final int CHANNEL_CONFIG = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN ? AudioFormat.CHANNEL_OUT_STEREO : AudioFormat.CHANNEL_CONFIGURATION_STEREO; private static final int AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT; public void startPlayback(String wavFilePath) { // Calculate the minimum buffer size required for stream mode int minBufferSize = AudioTrack.getMinBufferSize( SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_ENCODING ); // Validate buffer size calculation if (minBufferSize == AudioTrack.ERROR_BAD_VALUE || minBufferSize == AudioTrack.ERROR) { throw new RuntimeException("Failed to calculate valid buffer size"); } // Initialize AudioTrack with a buffer larger than the minimum for smoother playback audioTrack = new AudioTrack( AudioManager.STREAM_MUSIC, SAMPLE_RATE, CHANNEL_CONFIG, AUDIO_ENCODING, minBufferSize * 2, // Double the min buffer for better stability AudioTrack.MODE_STREAM ); if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) { throw new RuntimeException("Failed to initialize AudioTrack"); } isPlaying = true; playbackThread = new Thread(() -> { FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(wavFilePath); byte[] playbackBuffer = new byte[minBufferSize * 2]; int bytesRead; // Start playback audioTrack.play(); // Read and write chunks until end of file or playback is stopped while (isPlaying && (bytesRead = fileInputStream.read(playbackBuffer)) != -1) { int bytesWritten = audioTrack.write(playbackBuffer, 0, bytesRead); if (bytesWritten == AudioTrack.ERROR_INVALID_OPERATION || bytesWritten == AudioTrack.ERROR_BAD_VALUE) { throw new RuntimeException("Failed to write audio data to AudioTrack"); } } } catch (IOException e) { e.printStackTrace(); } finally { // Clean up resources regardless of success/failure if (fileInputStream != null) { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } stopPlayback(); } }); playbackThread.start(); } public void stopPlayback() { isPlaying = false; if (audioTrack != null) { if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { audioTrack.stop(); } audioTrack.release(); audioTrack = null; } if (playbackThread != null) { try { playbackThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } playbackThread = null; } } }
Key Details to Note:
- Buffer Size: We use
AudioTrack.getMinBufferSize()to get the minimum required buffer, then double it for smoother playback (adjust this multiplier based on your device's performance if needed). - Deprecated Constants: We handle older Android versions (pre-API 16) with a version check to avoid using deprecated channel configs.
- Threading: Playback runs on a background thread to avoid blocking the main UI thread (critical for app responsiveness).
- Resource Cleanup: Always
release()the AudioTrack and close the file stream when playback ends or is stopped to prevent memory leaks. - Error Handling: Added checks for initialization failures and write errors to make the implementation robust.
Usage Example
Call this from your Activity/Fragment (don't forget to request runtime storage permissions first if your file is in external storage):
WavStreamPlayer player = new WavStreamPlayer(); player.startPlayback("/storage/emulated/0/your_folder/1.wav"); // To stop playback later (e.g., in onPause() or a button click): // player.stopPlayback();
Why Avoid STATIC Mode?
STATIC mode loads the entire audio file into memory before playing. For your 230MB file, this would consume massive amounts of RAM and almost certainly trigger an OutOfMemoryError—so STREAM mode is the only practical choice here.
内容的提问来源于stack exchange,提问作者Santhosh

