有人成功使用ParcelFileDescriptor和createPipe()实现MediaPlayer吗?

20

关于我最近关于MediaRecordercreatePipe()的问题, 以及在这个其他的SO问题中讨论了createPipe()技术,现在我正在尝试使用通过ParcelFileDescriptorcreatePipe()提供的内容使MediaPlayer工作。

这个示例项目包含了我迄今为止的工作。它基于一个早期的示例,播放存储为原始资源的OGG剪辑。因此,我知道我的剪辑没有问题。

我已经更改了我的MediaPlayer设置:

  private void loadClip() {
    try {
      mp=new MediaPlayer();
      mp.setDataSource(this,
                       PipeProvider.CONTENT_URI.buildUpon()
                                               .appendPath("clip.ogg")
                                               .build());
      mp.setOnCompletionListener(this);
      mp.prepare();
    }
    catch (Exception e) {
      goBlooey(e);
    }
  }

通过登录PipeProvider,我发现我的Uri已经被正确构建。

PipeProvider此示例项目中的相同,可用于向Adobe Reader提供PDF,这限制了我的代码出错的可能性。 :-)

具体来说,openFile()ParcelFileDescriptor创建一个管道:

  @Override
  public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
    ParcelFileDescriptor[] pipe=null;

    try {
      pipe=ParcelFileDescriptor.createPipe();
      AssetManager assets=getContext().getResources().getAssets();

      new TransferTask(assets.open(uri.getLastPathSegment()),
                       new AutoCloseOutputStream(pipe[1])).start();
    }
    catch (IOException e) {
      Log.e(getClass().getSimpleName(), "Exception opening pipe", e);
      throw new FileNotFoundException("Could not open pipe for: "
          + uri.toString());
    }

    return(pipe[0]);
  }

在后台线程中进行典型的流到流复制:

  static class TransferTask extends Thread {
    InputStream in;
    OutputStream out;

    TransferTask(InputStream in, OutputStream out) {
      this.in=in;
      this.out=out;
    }

    @Override
    public void run() {
      byte[] buf=new byte[1024];
      int len;

      try {
        while ((len=in.read(buf)) > 0) {
          out.write(buf, 0, len);
        }

        in.close();
        out.close();
      }
      catch (IOException e) {
        Log.e(getClass().getSimpleName(),
              "Exception transferring file", e);
      }
    }
  }

然而,MediaPlayer 无法正常工作:
10-16 13:33:13.203: E/MediaPlayer(3060): Unable to to create media player
10-16 13:33:13.203: D/MediaPlayer(3060): Couldn't open file on client side, trying server side
10-16 13:33:13.207: E/TransferTask(3060): Exception transferring file
10-16 13:33:13.207: E/TransferTask(3060): java.io.IOException: write failed: EPIPE (Broken pipe)
10-16 13:33:13.207: E/TransferTask(3060):   at libcore.io.IoBridge.write(IoBridge.java:462)
10-16 13:33:13.207: E/TransferTask(3060):   at java.io.FileOutputStream.write(FileOutputStream.java:187)
10-16 13:33:13.207: E/TransferTask(3060):   at com.commonsware.android.audiolstream.PipeProvider$TransferTask.run(PipeProvider.java:120)
10-16 13:33:13.207: E/TransferTask(3060): Caused by: libcore.io.ErrnoException: write failed: EPIPE (Broken pipe)
10-16 13:33:13.207: E/TransferTask(3060):   at libcore.io.Posix.writeBytes(Native Method)
10-16 13:33:13.207: E/TransferTask(3060):   at libcore.io.Posix.write(Posix.java:178)
10-16 13:33:13.207: E/TransferTask(3060):   at libcore.io.BlockGuardOs.write(BlockGuardOs.java:191)
10-16 13:33:13.207: E/TransferTask(3060):   at libcore.io.IoBridge.write(IoBridge.java:457)
10-16 13:33:13.207: E/TransferTask(3060):   ... 2 more
10-16 13:33:13.211: E/MediaPlayer(3060): Unable to to create media player
10-16 13:33:13.218: E/TransferTask(3060): Exception transferring file
10-16 13:33:13.218: E/TransferTask(3060): java.io.IOException: write failed: EPIPE (Broken pipe)
10-16 13:33:13.218: E/TransferTask(3060):   at libcore.io.IoBridge.write(IoBridge.java:462)
10-16 13:33:13.218: E/TransferTask(3060):   at java.io.FileOutputStream.write(FileOutputStream.java:187)
10-16 13:33:13.218: E/TransferTask(3060):   at com.commonsware.android.audiolstream.PipeProvider$TransferTask.run(PipeProvider.java:120)
10-16 13:33:13.218: E/TransferTask(3060): Caused by: libcore.io.ErrnoException: write failed: EPIPE (Broken pipe)
10-16 13:33:13.218: E/TransferTask(3060):   at libcore.io.Posix.writeBytes(Native Method)
10-16 13:33:13.218: E/TransferTask(3060):   at libcore.io.Posix.write(Posix.java:178)
10-16 13:33:13.218: E/TransferTask(3060):   at libcore.io.BlockGuardOs.write(BlockGuardOs.java:191)
10-16 13:33:13.218: E/TransferTask(3060):   at libcore.io.IoBridge.write(IoBridge.java:457)
10-16 13:33:13.218: E/TransferTask(3060):   ... 2 more

有没有人看到过使用createPipe()MediaPlayer提供媒体的工作代码?

提前感谢!


你成功使用过ParcelFileDescriptor.createPipe()吗? - Crossle Song
4个回答

12

我不确定这个能不能工作。当我运行这段代码时,我看到了这个跟踪信息:

I/AudioSystem(30916): getting audio flinger
I/AudioSystem(30916): returning new audio session id
D/IAudioFlinger(30916): newAudioSessionId In
D/AudioFlinger(28138): nextUniqueId, current 178
D/IAudioFlinger(30916): newAudioSessionId Out, id = 178
D/MediaPlayer(30916): setDataSource(Context context, content://com.commonsware.android.audiolstream/clip.ogg, Map<String, String> headers) in
D/MediaPlayer(30916): setDataSource(FileDescriptor fd) in
E/MediaPlayerService(28138): offset error

“偏移错误”来自 AOSP 中 MediaPlayerService.cpp 的以下代码行,它会在管道的读取端执行 fstat() 操作:

status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64_t length)
{
    struct stat sb;
    int ret = fstat(fd, &sb);

    ....

    if (offset >= sb.st_size) {
        LOGE("offset error");
        ::close(fd);
        return UNKNOWN_ERROR;
    }

在我的经验中,MediaPlayer存在很多类似于这样的问题。我从未见过它能够用于除了本地文件和(非常有Bug的)HTTP流媒体以外的任何东西。最终我选择移植FFmpeg来解决它的诸多缺陷。

另外,sb.st_size被报告为-1(通过Java层ParcelFileDescriptor的getStatSize()方法)。错误处理程序关闭了描述符,因此不久之后出现了"broken pipe"错误。


1
“而且通过在Java层上的ParcelFileDescriptor上调用getStatSize(),sb.st_size被报告为-1” - 好吧,这似乎是问题所在,至少部分原因。我们应该有一些方法来说明这个管道将传输X字节,这样getStatSize()就可以返回一个有效的值,至少对于我们预先知道最终大小的情况下是如此。但是,由于这个对象跨越进程边界传递,我们不能真正地覆盖getStatSize()。谢谢您的见解! - CommonsWare
1
在我看来,MediaPlayer根本不应该使用fstat,它应该直接从给定的描述符中读取,而不对描述符的类型做出任何假设。 - Reuben Scratton

8
我曾试过使用PipeDataWriter将管道与ContentProvider一起使用以在MediaPlayer中使用管道(它基本上使用了一个管道和一个线程)。
问题是,至少对于视频内容,MediaPlayer期望的文件描述符必须是可寻址的,而您无法对管道执行fseek操作。

2
啊,非常有趣。这肯定是一个合理的解释。我会保持这个问题一段时间,看看是否有人有解决方法。谢谢! - CommonsWare
我仍在尝试使用MediaPlayer播放加密视频,而不必编写自己的视频播放器和解码器... - luciofm
1
我尝试过在后台线程中使用HTTP流传输到MediaPlayer,它确实可以工作,但可能会非常不稳定。它会随机断开连接(可能是乐观的超时)。 - Reuben Scratton
HTTP 速度慢,但对于音频来说还是可以使用的。当涉及到视频和需要可寻址流的播放器时,即使使用外部播放器如 VLC 或 MX,它也无法正常工作。 - duckduckgo
1
答案是正确的。至少对于android-28,函数头明确指出:“FileDescriptor必须是可寻址的(注意:LocalSocket不可寻址)”。如何到达那里:在Android Studio中选择代码中的setDataSource()调用,然后按Cmd-B。请注意,几乎所有参数变体都会内部调用低级文件描述符变体,并且这种情况会受到该限制的影响。 - Andreas K. aus M.
显示剩余2条评论

1
From Api level 23 onwards, you can use MediaDataSource class.


import java.io.*;
import android.media.MediaDataSource;

public class MyAudioSource extends MediaDataSource {
    private final byte[] buf;
    private final ByteArrayInputStream is;

    public MyAudioSource(byte[] buf){
        super();
        this.buf=buf;
        is=new ByteArrayInputStream(buf);
    }

    public long getSize() {
        return buf.length;
    }

    public int readAt(long position, byte[] buffer, int offset, int size){
        is.reset();
        is.skip(position);
        return is.read(buffer,offset,size);
    }
}



Now use above class for MediaPlayer like:

    // some how get your audio buffer in buf
    MyAudioSource mas = new MyAudioSource(buf);
    mediaPlayer.setDataSource(mas);

1
readAt() 要求您能够从数据流中的任意 position 读取。由于 createPipe() 会导致一个不可寻址的流,我们不能仅仅让 readAt() 在管道上调用某些东西来读取这些字节。客户端可能会对从管道中读取的字节进行自己的缓冲,以便满足先前和当前位置的 readAt() 调用。但是客户端需要知道所有这些都是必需的;我的问题的重点是在没有客户端解决方法的情况下使 createPipe() 定义的流正常工作。但感谢您指出这一点! - CommonsWare
我同意你的看法,我的答案并没有解决与管道相关的问题。我也想从缓冲区中获取MediaPlayer源以播放小型mp3文件。最终谷歌指向了Stackoverflow。为了避免这个管道和寻找相关问题,我只是浏览了MediaPlayer API,并找到了这个MediaDataSource方法。 - mahadevan gss

0
在论文中,您的ContentProvider上的openAssetFile()可以被覆盖。可以返回一个带有声明大小和偏移量的AssetFileDescriptor。
@Override
public AssetFileDescriptor openAssetFile(Uri uri, String mode)
        throws FileNotFoundException {

    ParcelFileDescriptor fd = openFile(uri, mode);
    return fd != null ? new AssetFileDescriptor(fd, offset, size) : null;

}

这些值将传递给本地的setDataSource(),在MediaPlayer上(请查看MediaPlayer.java以了解更多信息)。

如果在MediaPlayerService.cpp中的错误检查是(offset >= sb.st_size),则偏移量小于-1(内容的预定大小)或正声明大小不会触发错误。

这应该是一个干净的黑客起点,但我在测试中运气不佳。愚蠢的MediaPlayer似乎在播放之前读取整个“文件”,导致出现破损的管道。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接