java - android音频文件在毫秒范围内播放

标签 java android audio

我想在毫秒范围内播放 mp3 声音文件。 所以我尝试按照描述这样做 question.

但我收到的结果对我来说几乎很好。声音文件具有不同的参数,例如采样率...

所以,我从 ringdroid 获取代码图书馆。 这是两个java文件,解码器和播放器,基于AudioTrack.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);

所以,声音播放就像我预期的那样。声音中的单词按照我想要的方式播放,范围毫秒。但我还有另一个问题,那就是文件解码时间太长。我不知道如何处理音频文件。我如何才能只解码我需要播放的部分???请帮助我。

最佳答案

看来您读取了所有声音数据:

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;
    }

关于java - android音频文件在毫秒范围内播放,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36937169/

相关文章:

android - 使用CheckBoxPreference切换手机的声音,振动和互联网访问的开/关

java - Spring roo,这个探测器相当于什么

java - 在 Java 应用程序中使用 "bcc"发送电子邮件而不使用 "to"

android - ADT 10.0.1 中的问题

java - 如何修复合并 list 错误

android - Android native 录音机采样率

在 Mobile Safari 上缓存声音

java - Vaadin Grid DateRenderer 不适用

java - Spring Cloud Discovery First根本不起作用

java - Android中的字节数据类型内存大小