手动绘制后在 MediaPlayer 中使用 Surface
Using a Surface in MediaPlayer after it has been manually drawn on
我有一个用作 MediaPlayer 显示的 SurfaceView。我还需要通过 locking/unlocking 和 Canvas 在同一个 Surface 上手动绘制。不过这永远不会同时发生!
问题是一旦我锁定和解锁 Surface,MediaPlayer 将无法使用。反之则没有问题。我可以在 Surface 上播放视频,重置播放器,然后在 Surface 上随心所欲地手动绘制。
这个有效:
- 使用 MediaPlayer 在 Surface 上播放视频。
- 重置 MediaPlayer 并通过 locking/unlocking canvas.
在表面上手动绘制
这失败了:
- 锁定表面,在上面绘图,然后再次解锁。
- 使用 MediaPlayer 在 Surface 上播放视频。
当调用 MediaPlayer.prepare() 时,它将失败并出现 IllegalStateException。奇怪的是,异常的消息是“null”。
在 SurfaceTexture(例如,在 GL 上下文中)中使用的 Surface 也会发生同样的情况。
我想知道 Surface 在 locking/unlocking 之后会发生什么,MediaPlayer 不再喜欢它了。
我可能需要最终使用多个 Surface 并在它们之间切换。但我想避免这种情况,因为我有一个混合了多个 SurfaceViews 以及使用 SurfaceTextures 的 GlSurfaceViews 的设置。
这里是重现该现象的一些代码。确保 VIDEO_FILE 指向有效的视频。
MainActivity.java:
import android.app.Activity;
import android.graphics.Canvas;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity {
// Make sure this points to a valid video
private String VIDEO_FILE = "/sdcard/Movies/some-video.mp4";
private MediaPlayer mediaPlayer;
private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder;
private Button playButton;
private Button stopButton;
private Button drawButton;
private boolean alternate;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mediaPlayer = new MediaPlayer();
surfaceView = (SurfaceView)findViewById(R.id.video_surfaceview);
surfaceHolder = surfaceView.getHolder();
playButton = (Button)findViewById(R.id.play_button);
stopButton = (Button)findViewById(R.id.stop_button);
drawButton = (Button)findViewById(R.id.draw_button);
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mediaPlayer.setSurface(surfaceHolder.getSurface());
// Make sure we only do things when surface is ready
playButton.setEnabled(true);
stopButton.setEnabled(true);
drawButton.setEnabled(true);
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
}
});
playButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mediaPlayer.setDisplay(surfaceHolder);
try {
mediaPlayer.setDataSource(VIDEO_FILE);
mediaPlayer.prepare();
mediaPlayer.start();
} catch (Exception e) {
Log.e("Main", "Play failed: " + e.getMessage(), e);
}
}
});
stopButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mediaPlayer.reset();
}
});
drawButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Surface surface = surfaceHolder.getSurface();
Canvas canvas = surfaceHolder.lockCanvas(null);
// Toggle between green and red
if (alternate) {
canvas.drawARGB(255, 255, 0, 0);
} else {
canvas.drawARGB(255, 0, 255, 0);
}
alternate = !alternate;
surfaceHolder.unlockCanvasAndPost(canvas);
}
});
}
}
layout/main_activity.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/video_surfaceview"
android:layout_width="fill_parent"
android:layout_height="300dp" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/play_button"
android:enabled="false"
android:text="Play"></Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/stop_button"
android:enabled="false"
android:text="Stop"></Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/draw_button"
android:enabled="false"
android:text="Draw"></Button>
</LinearLayout>
共有三个按钮:"Play"、"Stop"、"Draw"。您可以按 "Play" 和 "Stop" 几次(应该会按预期工作)。然后按 "Draw" 多次(这应该绘制交替的红色和绿色)。现在再次按 "Play"(这会失败)。
如果一开始就使用"Draw",那么"Play"将永远行不通。
确保在播放视频时先按 "Stop" 再按 "Draw"。否则它会崩溃(出于正当理由)。
我只是想知道我是否应该为我为这个问题获得的风滚草徽章感到自豪,并决定对该主题进行最后一次研究。然后我偶然发现的第一件事是关于 Surface 和 SurfaceHolder 的架构文档:
https://source.android.com/devices/graphics/arch-sh
这很有趣,因为它似乎包含了我问题的确切答案:
When you lock a Surface for Canvas access, the "CPU renderer" connects
to the producer side of the BufferQueue and does not disconnect until
the Surface is destroyed. Most other producers (like GLES) can be
disconnected and reconnected to a Surface, but the Canvas-based "CPU
renderer" cannot. This means you can't draw on a surface with GLES or
send it frames from a video decoder if you've ever locked it for a
Canvas.
与此同时,无论如何我已经实现了一个具有多个表面的解决方案。我想我现在会坚持下去。
感谢收听。
我有一个用作 MediaPlayer 显示的 SurfaceView。我还需要通过 locking/unlocking 和 Canvas 在同一个 Surface 上手动绘制。不过这永远不会同时发生!
问题是一旦我锁定和解锁 Surface,MediaPlayer 将无法使用。反之则没有问题。我可以在 Surface 上播放视频,重置播放器,然后在 Surface 上随心所欲地手动绘制。
这个有效:
- 使用 MediaPlayer 在 Surface 上播放视频。
- 重置 MediaPlayer 并通过 locking/unlocking canvas. 在表面上手动绘制
这失败了:
- 锁定表面,在上面绘图,然后再次解锁。
- 使用 MediaPlayer 在 Surface 上播放视频。
当调用 MediaPlayer.prepare() 时,它将失败并出现 IllegalStateException。奇怪的是,异常的消息是“null”。
在 SurfaceTexture(例如,在 GL 上下文中)中使用的 Surface 也会发生同样的情况。
我想知道 Surface 在 locking/unlocking 之后会发生什么,MediaPlayer 不再喜欢它了。
我可能需要最终使用多个 Surface 并在它们之间切换。但我想避免这种情况,因为我有一个混合了多个 SurfaceViews 以及使用 SurfaceTextures 的 GlSurfaceViews 的设置。
这里是重现该现象的一些代码。确保 VIDEO_FILE 指向有效的视频。
MainActivity.java:
import android.app.Activity;
import android.graphics.Canvas;
import android.media.MediaPlayer;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
public class MainActivity extends Activity {
// Make sure this points to a valid video
private String VIDEO_FILE = "/sdcard/Movies/some-video.mp4";
private MediaPlayer mediaPlayer;
private SurfaceView surfaceView;
private SurfaceHolder surfaceHolder;
private Button playButton;
private Button stopButton;
private Button drawButton;
private boolean alternate;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mediaPlayer = new MediaPlayer();
surfaceView = (SurfaceView)findViewById(R.id.video_surfaceview);
surfaceHolder = surfaceView.getHolder();
playButton = (Button)findViewById(R.id.play_button);
stopButton = (Button)findViewById(R.id.stop_button);
drawButton = (Button)findViewById(R.id.draw_button);
surfaceHolder.addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mediaPlayer.setSurface(surfaceHolder.getSurface());
// Make sure we only do things when surface is ready
playButton.setEnabled(true);
stopButton.setEnabled(true);
drawButton.setEnabled(true);
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
}
});
playButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mediaPlayer.setDisplay(surfaceHolder);
try {
mediaPlayer.setDataSource(VIDEO_FILE);
mediaPlayer.prepare();
mediaPlayer.start();
} catch (Exception e) {
Log.e("Main", "Play failed: " + e.getMessage(), e);
}
}
});
stopButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mediaPlayer.reset();
}
});
drawButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Surface surface = surfaceHolder.getSurface();
Canvas canvas = surfaceHolder.lockCanvas(null);
// Toggle between green and red
if (alternate) {
canvas.drawARGB(255, 255, 0, 0);
} else {
canvas.drawARGB(255, 0, 255, 0);
}
alternate = !alternate;
surfaceHolder.unlockCanvasAndPost(canvas);
}
});
}
}
layout/main_activity.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/video_surfaceview"
android:layout_width="fill_parent"
android:layout_height="300dp" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/play_button"
android:enabled="false"
android:text="Play"></Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/stop_button"
android:enabled="false"
android:text="Stop"></Button>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/draw_button"
android:enabled="false"
android:text="Draw"></Button>
</LinearLayout>
共有三个按钮:"Play"、"Stop"、"Draw"。您可以按 "Play" 和 "Stop" 几次(应该会按预期工作)。然后按 "Draw" 多次(这应该绘制交替的红色和绿色)。现在再次按 "Play"(这会失败)。
如果一开始就使用"Draw",那么"Play"将永远行不通。
确保在播放视频时先按 "Stop" 再按 "Draw"。否则它会崩溃(出于正当理由)。
我只是想知道我是否应该为我为这个问题获得的风滚草徽章感到自豪,并决定对该主题进行最后一次研究。然后我偶然发现的第一件事是关于 Surface 和 SurfaceHolder 的架构文档:
https://source.android.com/devices/graphics/arch-sh
这很有趣,因为它似乎包含了我问题的确切答案:
When you lock a Surface for Canvas access, the "CPU renderer" connects to the producer side of the BufferQueue and does not disconnect until the Surface is destroyed. Most other producers (like GLES) can be disconnected and reconnected to a Surface, but the Canvas-based "CPU renderer" cannot. This means you can't draw on a surface with GLES or send it frames from a video decoder if you've ever locked it for a Canvas.
与此同时,无论如何我已经实现了一个具有多个表面的解决方案。我想我现在会坚持下去。
感谢收听。