Android:如何使用MediaExtractor从下载音频文件中提取音频样本

时间:2018-01-15 20:34:12

标签: android audio-streaming mediacodec mediaextractor

我正在制作一个非常简单的音乐应用程序,我试图找出如何做到这一点:

  • 下载音频文件并在本地录制文件
  • 随时 ,从文件中提取音频帧(在下载期间或之后)

1)对于下载部分,我使用this example之后的Retrofit。简而言之,它允许我下载文件,并在下载时将其记录在本地(因此我不必等待下载结束以访问文件的数据)。

2)对于帧提取部分,我使用MediaExtractorMediaCodec,如下所示:

MediaCodec codec;
MediaExtractor extractor;

MediaFormat format;
ByteBuffer[] codecInputBuffers;
ByteBuffer[] codecOutputBuffers;
Boolean sawInputEOS = false;
Boolean sawOutputEOS = false;
AudioTrack mAudioTrack;
MediaCodec.BufferInfo info;

File outputFile = null;
FileDescriptor fileDescriptor = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
   ...
   // the file being downloaded:
   outputFile = new File(directory, "test.mp3");
   try {
      FileInputStream fileInputStream = new FileInputStream(outputFile);
      fileDescriptor = fileInputStream.getFD();
   }
   catch (Exception e) {}
}

// Called once when enough data to extract.
private void onAudioFileReady() {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            // thread :
            Process.setThreadPriority(Process.THREAD_PRIORITY_AUDIO);

            // audio :
            extractor = new MediaExtractor();

            // the extractor only extracts the already downloaded part of the file:
            try {
                //  extractor.setDataSource(url);
                //  extractor.setDataSource(outputFile.getAbsolutePath());
                //  extractor.setDataSource(MainActivity.this, Uri.parse(outputFile.getAbsolutePath()), null);
                extractor.setDataSource(fileDescriptor);
            }
            catch (IOException e) {}

            format = extractor.getTrackFormat(0);
            String mime = format.getString(MediaFormat.KEY_MIME);
            int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);

            try {
                codec = MediaCodec.createDecoderByType(mime);
            }
            catch (IOException e) {}
            codec.configure(format, null, null, 0);
            codec.start();

            codecInputBuffers = codec.getInputBuffers();
            codecOutputBuffers = codec.getOutputBuffers();

            extractor.selectTrack(0);

            int minBufferSize = AudioTrack.getMinBufferSize(
                    sampleRate,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT);

            mAudioTrack = new AudioTrack(
                    AudioManager.STREAM_MUSIC,
                    sampleRate,
                    AudioFormat.CHANNEL_OUT_STEREO,
                    AudioFormat.ENCODING_PCM_16BIT,
                    minBufferSize,
                    AudioTrack.MODE_STREAM
            );
            info = new MediaCodec.BufferInfo();
            mAudioTrack.play();

            do {
                input();
                output();
            }
            while (!sawInputEOS);
        }
    });
    thread.start();
}

private void input() {
    int inputBufferIndex = codec.dequeueInputBuffer(-1);
    if (inputBufferIndex >= 0) {
        ByteBuffer byteBuffer = codecInputBuffers[inputBufferIndex];
        int sampleSize = extractor.readSampleData(byteBuffer, 0);
        long presentationTimeUs = 0;

        if (sampleSize < 0) {
            Log.w(LOG_TAG, "Saw input end of stream!");
            sampleSize = 0;
        }
        else {
            presentationTimeUs = extractor.getSampleTime();
        }

        codec.queueInputBuffer(inputBufferIndex,
                0,
                sampleSize,
                presentationTimeUs,
                sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);

        // doesn't seem to work:
        extractor.advance();
    }
}

private void output() {
    final int res = codec.dequeueOutputBuffer(info, -1);
    if (res >= 0) {
        ByteBuffer buf = codecOutputBuffers[res];

        final byte[] chunk = new byte[info.size];
        buf.get(chunk);
        buf.clear();

        if (chunk.length > 0) {
            mAudioTrack.write(chunk, 0, chunk.length);
        }
        codec.releaseOutputBuffer(res, false);

        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
            sawOutputEOS = true;
        }
    }
    else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
        codecOutputBuffers = codec.getOutputBuffers();
    }
    else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
        final MediaFormat oformat = codec.getOutputFormat();
        mAudioTrack.setPlaybackRate(oformat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
    }
}

它的作用:

调用onAudioFileReady()时,此代码将提取并播放文件的音频样本,但仅播放已下载的。当它到达已下载部分的末尾时,MediaExtractor停止(看起来extractor.advance()不希望继续提取...),即使有更多可用数据...

我想要实现的目标:

我希望能够继续提取文件的音频样本,只要当然有足够的数据。

重要:

此时,您可能会问为什么我不只是使用extractor.setDataSource(url)。原因如下:

  • 我想在本地保存音频文件,以便稍后播放
  • 我希望能够播放这首歌,即使在下载开始很久之后

有谁知道如何实现这一目标?在此先感谢您的帮助。

0 个答案:

没有答案