Кто-нибудь имеет MediaPlayer, работающий с ParcelFileDescriptor и createPipe()?
В связи с моим недавним вопросом о MediaRecorder
и createPipe()
и обсуждением метода createPipe()
в этот другой вопрос SO, теперь я пытаюсь получить MediaPlayer
для работы с содержимым, обслуживаемым ContentProvider
через ParcelFileDescriptor
и createPipe()
.
Этот примерный проект имеет мою работу на сегодняшний день. Он основан на более раннем образце, который воспроизводит клип 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
является тем же, что и в этот примерный проект, который работает для работы с PDF файлами в Adobe Reader, что ограничивает мой код может быть.: -)
В частности, 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
?
Спасибо заранее!
Ответы
Ответ 1
Я не уверен, что это может когда-либо работать. Когда я запускаю этот код, я вижу эту трассировку:
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
Эта ошибка смещения происходит из следующих строк в MediaPlayerService.cpp в AOSP, где она имеет 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;
}
И sb.st_size сообщается как -1 (через getStatSize() в ParcelFileDescriptor на уровне Java). Обработчик ошибок закрывает дескриптор, а значит, и ошибку сломанной трубы вскоре после этого.
В моем опыте MediaPlayer имеет много сломанных бит, подобных этому. Я никогда не видел, чтобы это работало ни на чем, а непосредственно на локальных файлах и (очень затруднительно) для потоковой передачи HTTP. Я закончил перенос FFmpeg, чтобы обойти многочисленные неудачи.
Ответ 2
Я пытался использовать каналы с MediaPlayer через ContentProvider с помощью PipeDataWriter (который в основном использует канал и поток).
Проблема заключается в том, что файловый дескриптор, ожидаемый MediaPlayer, по крайней мере для видеоконтента, должен быть доступен для поиска, и вы не можете делать fseek на канале.
Ответ 3
В тезисе openAssetFile()
на вашем ContentProvider
можно переопределить. 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;
}
Эти значения передаются в native setDataSource()
on MediaPlayer
(проверьте MediaPlayer.java, чтобы узнать больше).
Если проверка ошибки в MediaPlayerService.cpp(offset >= sb.st_size), смещение-минор, а затем -1 (предполагаемый размер содержимого) или положительный объявленный размер, не вызывает ошибку.
Это должно быть хорошей отправной точкой для чистого взлома, но мне не повезло в моих тестах. Немой MediaPlayer, кажется, прочитал весь "файл" перед игрой, в результате чего сломанный трубопровод впереди.