我想在毫秒范围内播放mp3声音文件。 所以我尝试通过描述这个question.
来做到这一点但我收到的结果对我来说几乎是好的。声音文件有不同的参数,如采样率......
所以,我从ringdroid库中获取代码。 这是基于AudioTrack.java
的两个java文件,解码器和播放器播放器:
public class CustomPlayer {
public interface OnCompletionListener {
public void onCompletion();
}
private ShortBuffer mSamples;
private int mSampleRate;
private int mChannels;
private int mNumSamples; // Number of samples per channel.
private AudioTrack mAudioTrack;
private short[] mBuffer;
private int mPlaybackStart; // Start offset, in samples.
private Thread mPlayThread;
private boolean mKeepPlaying;
private OnCompletionListener mListener;
public CustomPlayer(ShortBuffer samples, int sampleRate, int channels, int numSamples) {
mSamples = samples;
mSampleRate = sampleRate;
mChannels = channels;
mNumSamples = numSamples;
mPlaybackStart = 0;
int bufferSize = AudioTrack.getMinBufferSize(
mSampleRate,
mChannels == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT);
// make sure minBufferSize can contain at least 1 second of audio (16 bits sample).
if (bufferSize < mChannels * mSampleRate * 2) {
bufferSize = mChannels * mSampleRate * 2;
}
mBuffer = new short[bufferSize / 2]; // bufferSize is in Bytes.
mAudioTrack = new AudioTrack(
AudioManager.STREAM_MUSIC,
mSampleRate,
mChannels == 1 ? AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO,
AudioFormat.ENCODING_PCM_16BIT,
mBuffer.length * 2,
AudioTrack.MODE_STREAM);
// Check when player played all the given data and notify user if mListener is set.
mAudioTrack.setNotificationMarkerPosition(mNumSamples - 1); // Set the marker to the end.
mAudioTrack.setPlaybackPositionUpdateListener(
new AudioTrack.OnPlaybackPositionUpdateListener() {
@Override
public void onPeriodicNotification(AudioTrack track) {
}
@Override
public void onMarkerReached(AudioTrack track) {
stop();
if (mListener != null) {
mListener.onCompletion();
}
}
});
mPlayThread = null;
mKeepPlaying = true;
mListener = null;
}
public CustomPlayer(SoundFile sf) {
this(sf.getSamples(), sf.getSampleRate(), sf.getChannels(), sf.getNumSamples());
}
public void setOnCompletionListener(OnCompletionListener listener) {
mListener = listener;
}
public boolean isPlaying() {
return mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING;
}
public boolean isPaused() {
return mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED;
}
public void start() {
if (isPlaying()) {
return;
}
mKeepPlaying = true;
mAudioTrack.flush();
mAudioTrack.play();
// Setting thread feeding the audio samples to the audio hardware.
// (Assumes mChannels = 1 or 2).
mPlayThread = new Thread() {
public void run() {
int position = mPlaybackStart * mChannels;
mSamples.position(position);
int limit = mNumSamples * mChannels;
while (mSamples.position() < limit && mKeepPlaying) {
int numSamplesLeft = limit - mSamples.position();
if (numSamplesLeft >= mBuffer.length) {
mSamples.get(mBuffer);
} else {
for (int i = numSamplesLeft; i < mBuffer.length; i++) {
mBuffer[i] = 0;
}
mSamples.get(mBuffer, 0, numSamplesLeft);
}
// TODO(nfaralli): use the write method that takes a ByteBuffer as argument.
mAudioTrack.write(mBuffer, 0, mBuffer.length);
}
}
};
mPlayThread.start();
}
public void pause() {
if (isPlaying()) {
mAudioTrack.pause();
// mAudioTrack.write() should block if it cannot write.
}
}
public void stop() {
if (isPlaying() || isPaused()) {
mKeepPlaying = false;
mAudioTrack.pause(); // pause() stops the playback immediately.
mAudioTrack.stop(); // Unblock mAudioTrack.write() to avoid deadlocks.
if (mPlayThread != null) {
try {
mPlayThread.join();
} catch (InterruptedException e) {
}
mPlayThread = null;
}
mAudioTrack.flush(); // just in case...
}
}
public void release() {
stop();
mAudioTrack.release();
}
public void seekTo(int msec) {
boolean wasPlaying = isPlaying();
stop();
mPlaybackStart = (int) (msec * (mSampleRate / 1000.0));
if (mPlaybackStart > mNumSamples) {
mPlaybackStart = mNumSamples; // Nothing to play...
}
mAudioTrack.setNotificationMarkerPosition(mNumSamples - 1 - mPlaybackStart);
if (wasPlaying) {
start();
}
}
public int getCurrentPosition() {
return (int) ((mPlaybackStart + mAudioTrack.getPlaybackHeadPosition()) *
(1000.0 / mSampleRate));
}
}
解码器:
public class SoundFile {
private ProgressListener mProgressListener = null;
private String mFileType;
private int mFileSize;
private int mAvgBitRate; // Average bit rate in kbps.
private int mSampleRate;
private int mChannels;
private int mNumSamples; // total number of samples per channel in audio file
private ShortBuffer mDecodedSamples; // shared buffer with mDecodedBytes.
private int mNumFrames;
private int[] mFrameGains;
// Progress listener interface.
public interface ProgressListener {
boolean reportProgress(double fractionComplete);
}
// Custom exception for invalid inputs.
public class InvalidInputException extends Exception {
private static final long serialVersionUID = -2505698991597837165L;
public InvalidInputException(String message) {
super(message);
}
}
public static String[] getSupportedExtensions() {
return new String[]{"mp3"};
}
public static boolean isFilenameSupported(String filename) {
String[] extensions = getSupportedExtensions();
for (int i = 0; i < extensions.length; i++) {
if (filename.endsWith("." + extensions[i])) {
return true;
}
}
return false;
}
public String getFiletype() {
return mFileType;
}
public int getFileSizeBytes() {
return mFileSize;
}
public int getAvgBitrateKbps() {
return mAvgBitRate;
}
public int getSampleRate() {
return mSampleRate;
}
public int getChannels() {
return mChannels;
}
public int getNumSamples() {
return mNumSamples; // Number of samples per channel.
}
// Should be removed when the app will use directly the samples instead of the frames.
public int getNumFrames() {
return mNumFrames;
}
// Should be removed when the app will use directly the samples instead of the frames.
public int getSamplesPerFrame() {
return 1024; // just a fixed value here...
}
// Should be removed when the app will use directly the samples instead of the frames.
public int[] getFrameGains() {
return mFrameGains;
}
public ShortBuffer getSamples() {
if (mDecodedSamples != null) {
return mDecodedSamples.asReadOnlyBuffer();
} else {
return null;
}
}
private SoundFile() {
}
private void setProgressListener(ProgressListener progressListener) {
mProgressListener = progressListener;
}
public static SoundFile readMp3File(File file, int mSecStart) {
SoundFile soundFile = new SoundFile();
try {
soundFile.readFile(file, mSecStart);
} catch (IOException | InvalidInputException e) {
Log.e(SoundFile.class.getName(), "read File", e);
}
return soundFile;
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
private void readFile(File inputFile, int mSecStart) throws java.io.IOException, InvalidInputException {
MediaExtractor extractor = new MediaExtractor();
MediaFormat format = null;
int i;
File mInputFile = inputFile;
String[] components = mInputFile.getPath().split("\\.");
mFileType = components[components.length - 1];
mFileSize = (int) mInputFile.length();
extractor.setDataSource(mInputFile.getPath());
int numTracks = extractor.getTrackCount();
// find and select the first audio track present in the file.
for (i = 0; i < numTracks; i++) {
format = extractor.getTrackFormat(i);
if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
extractor.selectTrack(i);
break;
}
}
if (i == numTracks) {
throw new InvalidInputException("No audio track found in " + mInputFile);
}
mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
// Expected total number of samples per channel.
int expectedNumSamples =
(int) ((format.getLong(MediaFormat.KEY_DURATION) / 1000000.f) * mSampleRate + 0.5f);
MediaCodec codec = MediaCodec.createDecoderByType(format.getString(MediaFormat.KEY_MIME));
codec.configure(format, null, null, 0);
codec.start();
int decodedSamplesSize = 0; // size of the output buffer containing decoded samples.
byte[] decodedSamples = null;
ByteBuffer[] inputBuffers = codec.getInputBuffers();
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
int sample_size;
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
long presentation_time;
int tot_size_read = 0;
boolean done_reading = false;
ByteBuffer mDecodedBytes = ByteBuffer.allocate(1 << 20);
Boolean firstSampleData = true;
while (true) {
// read data from file and feed it to the decoder input buffers.
int inputBufferIndex = codec.dequeueInputBuffer(100);
if (!done_reading && inputBufferIndex >= 0) {
sample_size = extractor.readSampleData(inputBuffers[inputBufferIndex], 0);
if (firstSampleData
&& format.getString(MediaFormat.KEY_MIME).equals("audio/mp4a-latm")
&& sample_size == 2) {
extractor.advance();
tot_size_read += sample_size;
} else if (sample_size < 0) {
// All samples have been read.
codec.queueInputBuffer(
inputBufferIndex, 0, 0, -1, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
done_reading = true;
} else {
presentation_time = extractor.getSampleTime();
codec.queueInputBuffer(inputBufferIndex, 0, sample_size, presentation_time, 0);
extractor.advance();
tot_size_read += sample_size;
if (mProgressListener != null) {
if (!mProgressListener.reportProgress((float) (tot_size_read) / mFileSize)) {
// We are asked to stop reading the file. Returning immediately. The
// SoundFile object is invalid and should NOT be used afterward!
extractor.release();
extractor = null;
codec.stop();
codec.release();
codec = null;
return;
}
}
}
firstSampleData = false;
}
// Get decoded stream from the decoder output buffers.
int outputBufferIndex = codec.dequeueOutputBuffer(info, 100);
if (outputBufferIndex >= 0 && info.size > 0) {
if (decodedSamplesSize < info.size) {
decodedSamplesSize = info.size;
decodedSamples = new byte[decodedSamplesSize];
}
outputBuffers[outputBufferIndex].get(decodedSamples, 0, info.size);
outputBuffers[outputBufferIndex].clear();
if (mDecodedBytes.remaining() < info.size) {
int position = mDecodedBytes.position();
int newSize = (int) ((position * (1.0 * mFileSize / tot_size_read)) * 1.2);
if (newSize - position < info.size + 5 * (1 << 20)) {
newSize = position + info.size + 5 * (1 << 20);
}
ByteBuffer newDecodedBytes = null;
int retry = 10;
while (retry > 0) {
try {
newDecodedBytes = ByteBuffer.allocate(newSize);
break;
} catch (OutOfMemoryError oome) {
retry--;
}
}
if (retry == 0) {
break;
}
mDecodedBytes.rewind();
newDecodedBytes.put(mDecodedBytes);
mDecodedBytes = newDecodedBytes;
mDecodedBytes.position(position);
}
mDecodedBytes.put(decodedSamples, 0, info.size);
codec.releaseOutputBuffer(outputBufferIndex, false);
} else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
outputBuffers = codec.getOutputBuffers();
}
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0
|| (mDecodedBytes.position() / (2 * mChannels)) >= expectedNumSamples) {
break;
}
}
mNumSamples = mDecodedBytes.position() / (mChannels * 2); // One sample = 2 bytes.
mDecodedBytes.rewind();
mDecodedBytes.order(ByteOrder.LITTLE_ENDIAN);
mDecodedSamples = mDecodedBytes.asShortBuffer();
mAvgBitRate = (int) ((mFileSize * 8) * ((float) mSampleRate / mNumSamples) / 1000);
extractor.release();
extractor = null;
codec.stop();
codec.release();
codec = null;
mDecodedSamples.rewind();
}
public int getOneMillisecondBytes() {
return (int) (1000.0 * getSamplesPerFrame()) / (mSampleRate * 2);
}
}
我使用的代码:
SoundFile soundFile = SoundFile.readMp3File(audio, getTimeFromDate(start));
player = new CustomPlayer(soundFile);
player.seekTo(getTimeFromDate(start));
player.start();
final Timer timer = new Timer();
playerWatcher = new TimerTask() {
@Override
public void run() {
if (player == null) {
timer.cancel();
} else if (player.getCurrentPosition() >= finishMS) {
player.stop();
timer.cancel();
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (!doesReplicaHasExercise) {
onNextDialog();
} else {
DialogEditableTextView dialogEditableTextView = (DialogEditableTextView)
dialogMaker.lines.get(dialogMaker.lines.size() - 1);
dialogEditableTextView.startEditingFirstItem();
}
}
});
}
}
};
timer.schedule(playerWatcher, 0, 5);
所以,声音播放就像我预期的那样。声音中的单词就像我想要的那样,等于毫秒。但我有另一个问题,那就是长时间文件解码。我很难知道使用音频文件。我怎么能只解码我需要播放的部分???请帮帮我。
答案 0 :(得分:0)
似乎您已读取所有声音数据:
while (true) {
// read data from file and feed it to the decoder input buffers.
int inputBufferIndex = codec.dequeueInputBuffer(100);
if (!done_reading && inputBufferIndex >= 0) {
sample_size = extractor.readSampleData(inputBuffers[inputBufferIndex], 0);
if (firstSampleData
&& format.getString(MediaFormat.KEY_MIME).equals("audio/mp4a-latm")
&& sample_size == 2) {
extractor.advance();
tot_size_read += sample_size;
} else if (sample_size < 0) {
// All samples have been read.
codec.queueInputBuffer(
inputBufferIndex, 0, 0, -1, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
done_reading = true;
} else {
presentation_time = extractor.getSampleTime();
codec.queueInputBuffer(inputBufferIndex, 0, sample_size, presentation_time, 0);
extractor.advance();
tot_size_read += sample_size;
if (mProgressListener != null) {
if (!mProgressListener.reportProgress((float) (tot_size_read) / mFileSize)) {
// We are asked to stop reading the file. Returning immediately. The
// SoundFile object is invalid and should NOT be used afterward!
extractor.release();
extractor = null;
codec.stop();
codec.release();
codec = null;
return;
}
}
}
firstSampleData = false;
}