【问题标题】:How to play InputStream of an audio file that's not within a url or storage?如何播放不在 url 或存储中的音频文件的 InputStream?
【发布时间】:2016-08-01 19:59:11
【问题描述】:

背景

我已成功将音频文件 (3gp) 上传到 Google-Drive。

现在我希望能够在应用程序中播放文件。

Google Drive API 只允许获取存储在那里的文件的输入流。

问题

在我的情况下,输入的所有 MediaPlayer 功能都不可用,只有 InputSteam:

http://developer.android.com/reference/android/media/MediaPlayer.html#setDataSource(java.io.FileDescriptor)

我知道我可以将文件从 Google-Drive 保存到缓存并播放它,但我想避免存储处理,并即时播放文件。

我尝试过的

我尝试搜索此问题,但发现可能使用 AudioTrack (here)。也有可能使用新的 Jelly-Bean 功能(显示为 here,来自 here),但我不确定,因为它的级别很低。

可悲的是,使用 AudioTrack 我播放了错误的声音(噪音)。

我还注意到 MediaPlayer 可以选择将 dataSource 设置为 MediaDataSource (here) ,但我不仅不知道如何使用它,它还需要 API 23 及更高版本。

当然,我尝试使用 Google-Drive 中提供的 url,但这仅用于其他目的,并未定向到音频文件,因此无法使用 MediaPlayer。

问题

给定一个 InputStream,是否可以使用 AudioTrack 或其他东西来播放音频 3gp 文件?

是否有对此的支持库解决方案?

【问题讨论】:

  • 你有解决这个问题的方法吗?
  • @newenglander 没有。下面的答案对我来说太笼统了,我没有时间进一步调查。
  • @newenglander 我已经为它创建了一个请求:issuetracker.google.com/issues/37093023

标签: android android-mediaplayer inputstream playback audiotrack


【解决方案1】:

如果您的 minSdkVersion 为 23 或更高,您可以 use setDataSource(MediaDataSource) 并提供您自己的 abstract 子类 MediaDataSource class

对于旧设备,您应该能够使用从ParcelFileDescriptor 创建的管道。您将拥有一个将数据写入管道末端的线程,并将播放器末端的FileDescriptor(来自getFileDescriptor())传递给setDataSource(FileDescriptor)

【讨论】:

  • 关于 MediaDataSource,这是我找到的一种解决方案,但我没有看到任何有关如何使用它的示例。您能否为这两种方式显示一些代码?
  • @androiddeveloper:我没用过MediaDataSource;前几天我在帮助别人时遇到了它。虽然我没有使用ParcelFileDescriptor 管道进行媒体播放,但我已经将它用于media recordingserving documents 等。
  • 那太糟糕了。还是谢谢你。
  • @CommonsWare 我检查了setDataSource(FileDescriptor)。它产生`W/System.err: java.io.IOException: setDataSourceFD failed.: status=0x80000000 W/System.err: at android.media.MediaPlayer._setDataSource(Native Method) W/System.err: at android.media .MediaPlayer.setDataSource(MediaPlayer.java:1133) W/System.err: at android.media.MediaPlayer.setDataSource(MediaPlayer.java:1118)' 如果 FileDescriptor 下只有一个 InputStream 而 Android 文件系统上没有真实文件, 标记 :(
  • @isabsent:媒体播放需要文件或支持的流服务器协议(HTTP、RTSP 等)。当它不能倒带时它不能很好地工作,并且管道上的流(例如,ParcelFileDescriptor.createPipe())不能倒带。
【解决方案2】:

最简单的MediaDataSource实现示例:

import android.media.MediaDataSource;
import android.os.Build;
import android.support.annotation.RequiresApi;

import java.io.IOException;
import java.io.InputStream;

@RequiresApi(api = Build.VERSION_CODES.M)
public class InputStreamMediaDataSource extends MediaDataSource {
    private InputStream is;
    private long streamLength = -1, lastReadEndPosition;

    public InputStreamMediaDataSource(InputStream is, long streamLength) {
        this.is = is;
        this.streamLength = streamLength;
        if (streamLength <= 0){
            try {
                this.streamLength = is.available(); //Correct value of InputStream#available() method not always supported by InputStream implementation!
            } catch (IOException e) {
                e.printStackTrace();
            }
        }                 
    }

    @Override
    public synchronized void close() throws IOException {
        is.close();
    }

    @Override
    public synchronized int readAt(long position, byte[] buffer, int offset, int size) throws IOException {
        if (position >= streamLength)
            return -1;

        if (position + size > streamLength)
            size -= (position + size) - streamLength;

        if (position < lastReadEndPosition) {
            is.close();
            lastReadEndPosition = 0;
            is = getNewCopyOfInputStreamSomeHow();//new FileInputStream(mediaFile) for example.
        }

        long skipped = is.skip(position - lastReadEndPosition);
        if (skipped == position - lastReadEndPosition) {
            int bytesRead = is.read(buffer, offset, size);
            lastReadEndPosition = position + bytesRead;
            return bytesRead;
        } else {
            return -1;
        }
    }

    @Override
    public synchronized long getSize() throws IOException {
        return streamLength;
    }
}

要将其与 API >= 23 一起使用,您必须提供 streamLength 值,并且如果(何时)MediaPlayer 返回 - 即 position &lt; lastReadEndPosition,您必须知道如何创建 InputStream 的新副本。

用法示例:

你必须创建Activity,初始化MediaPlayer类(有很多文件播放的例子)并放置以下代码而不是旧的player.setDataSource("/path/to/media/file.3gp")

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    File file = new File("/path/to/media/file.3gp");//It is just an example! If you have real file on the phone memory, you don't need to wrap it to the InputStream to play it in MediaPlayer!
    player.setDataSource(new InputStreamMediaDataSource(new FileInputStream(file), file.length()));
} else
    player.setDataSource(this, mediaUri);

如果您的文件是 Google Drive 上的对象 com.google.api.services.drive.model.Filecom.google.api.services.drive.Drive drive,您可以获得

InputStream is = drive.getRequestFactory().buildGetRequest(new GenericUrl(file.getDownloadUrl())).execute().getContent();

Build.VERSION.SDK_INT &lt; Build.VERSION_CODES.M的情况下,我必须在Android设备的本地主机上设置HTTP服务器(通过NanoHTTPd)并通过uri-player.setDataSource(this, mediaUri)将字节流通过该服务器传输到MediaPlayer

【讨论】:

  • 能否请您说明如何使用它,以及如何处理 API
  • 请展示如何让“mediaUri”与 Drive API 提供的 InputStream 一起工作。这就是问题所在......
  • 我不确定 :( 我从未在文档中看到 ExoPlayer 可以在其输入上使用 InputSream。你读过吗?我想,有必要观看源代码来理解它。此外,我的要求是支持 API >= 14,但 ExoPlayer 仅支持 API >= 16。
  • 您必须在您的应用程序中使用方法ParcelFileDescriptor openFile(Uri uri, String mode) 实现ContentProvider。在这种方法中,您必须使用 ParceFileDescriptorInputStream 实现 @CommonsWare 方案,并使用 Uri content://your.content.provider.name/... 发送到外部世界意图,这将识别您的 InputStream。 VLC 将捕捉到这种意图并要求 Android 提供具有此类 InputStream 的应用程序(即您的应用程序 - 通过您的 ContentProvider)。之后,VLC 将开始抽出您提供的 InputStream。
  • 我没有示例应用程序,它是BestCrypt Explorer 的一部分。不过代码不多,可以稍后再写代码示例。
【解决方案3】:

对于任何有兴趣使用 MediaDataSource 实现的人,我创建了一个可以提前读取并缓存数据缓冲区的实现。它适用于任何 InputStream,我主要创建它用于使用 JCIFS SmbFile 读取网络文件。

您可以在https://github.com/SteveGreatApe/BufferedMediaDataSource找到它

【讨论】:

  • 虽然理论上可以回答这个问题,it would be preferable 在这里包含答案的基本部分,并提供链接以供参考。有关如何编写更好“基于链接”的答案的说明,请参阅here。谢谢!
  • 不错。谢谢。
【解决方案4】:

如果您询问设置我们音频路径的媒体播放器,也许此代码可以提供帮助。

File directory = Environment.getExternalStorageDirectory();
		File file = new File( directory + "/AudioRecorder" );
		String AudioSavePathInDevice = file.getAbsolutePath() + "/" + "sample.wav" ;
    
     mediaPlayer = new MediaPlayer();
     
      try {
            mediaPlayer.setDataSource(AudioSavePathInDevice);
            mediaPlayer.prepare();
          } catch (IOException e) {
              e.printStackTrace();
          }

      mediaPlayer.start();

【讨论】:

    猜你喜欢
    • 2014-04-21
    • 1970-01-01
    • 1970-01-01
    • 2021-02-07
    • 2011-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-07
    相关资源
    最近更新 更多