Android:使用视频动态模糊表面
Android: Dynamically Blur Surface with Video
我正在构建一个 Android 应用程序,其中 ExoPlayer 在 SurfaceView 的表面上播放视频,我正在研究是否可以动态模糊正在播放的视频。
涉及首先生成要模糊的视图位图的模糊技术将不起作用,因为 SurfaceView 的表面部分不会出现在位图中。
在旧版本的 Android(例如 Surface.FX_SURFACE_BLUR)中,表面和视图曾经具有内置的模糊效果,但在较新的 API 中似乎已被弃用。
任何人都可以分享一些关于如何动态模糊表面的见解吗?谢谢。
Whosebug 上有很多问题,其中包含一些需要完成的小细节。我将回顾一下我使用的方法,希望它对某些人有用。
如果这是视频帧的静态模糊,以 TextureView
播放视频就足够了,使用 .getBitmap()
函数并使用诸如以下工具模糊生成的位图渲染脚本。但是,.getBitmap()
是在主 UI 线程上执行的,因此滞后于它试图复制其帧的视频。
要对每一帧执行模糊处理,最好的方法似乎是使用带有自定义渲染器的 GLSurfaceView。我使用 VidEffects pointed to from 中提供的代码作为一个很好的起点。
具有大半径的模糊可能需要大量计算。这就是为什么我首先使用两个单独的片段着色器(一个用于水平模糊,一个用于垂直模糊结果)来执行模糊。实际上,我最终只使用了一个片段着色器来应用 7x7 高斯内核。如果您的 GLSurfaceView
很大,请牢记一件非常重要的事情,即在 GLSurfaceView
的 SurfaceHolder
上调用 setFixedSize()
以使其分辨率低于屏幕分辨率。结果看起来不是很像素化,因为无论如何它都是模糊的,但是性能提升非常显着。
我制作的模糊在大多数设备上都能以 24fps 的速度播放,setFixedSize()
指定其分辨率为 100x70。
万一有人想要单通道片段着色器来完成圆...以下代码实现了 VidEffects. I adapted it from this example on ShaderToy.com.
中可用代码中定义的 ShaderInterface
public class BlurEffect2 implements ShaderInterface {
private final int mMaskSize;
private final int mWidth;
private final int mHeight;
public BlurEffect2(int maskSize, int width, int height) {
mMaskSize = maskSize;
mWidth = width;
mHeight = height;
}
@Override
public String getShader(GLSurfaceView mGlSurfaceView) {
float hStep = 1.0f / mWidth;
float vStep = 1.0f / mHeight;
return "#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
//"in" attributes from our vertex shader
"varying vec2 vTextureCoord;\n" +
//declare uniforms
"uniform samplerExternalOES sTexture;\n" +
"float normpdf(in float x, in float sigma) {\n" +
" return 0.39894 * exp(-0.5 * x * x / (sigma * sigma)) / sigma;\n" +
"}\n" +
"void main() {\n" +
" vec3 c = texture2D(sTexture, vTextureCoord).rgb;\n" +
//declare stuff
" const int mSize = " + mMaskSize + ";\n" +
" const int kSize = (mSize - 1) / 2;\n" +
" float kernel[ mSize];\n" +
" vec3 final_colour = vec3(0.0);\n" +
//create the 1-D kernel
" float sigma = 7.0;\n" +
" float Z = 0.0;\n" +
" for (int j = 0; j <= kSize; ++j) {\n" +
" kernel[kSize + j] = kernel[kSize - j] = normpdf(float(j), sigma);\n" +
" }\n" +
//get the normalization factor (as the gaussian has been clamped)
" for (int j = 0; j < mSize; ++j) {\n" +
" Z += kernel[j];\n" +
" }\n" +
//read out the texels
" for (int i = -kSize; i <= kSize; ++i) {\n" +
" for (int j = -kSize; j <= kSize; ++j) {\n" +
" final_colour += kernel[kSize + j] * kernel[kSize + i] * texture2D(sTexture, (vTextureCoord.xy + vec2(float(i)*" + hStep + ", float(j)*" + vStep + "))).rgb;\n" +
" }\n" +
" }\n" +
" gl_FragColor = vec4(final_colour / (Z * Z), 1.0);\n" +
"}";
}
}
正如 Michael 在上面指出的那样,您可以通过使用 setFixedSize
设置 SurfaceView
的大小来提高性能。
@BindView(R.id.video_snap)
VideoSurfaceView mVideoView;
@Override
public void showVideo(String cachedPath) {
mImageView.setVisibility(View.GONE);
mVideoView.setVisibility(View.VISIBLE);
//Get width and height of the video
final MediaMetadataRetriever mRetriever = new MediaMetadataRetriever();
mRetriever.setDataSource(cachedPath);
int width = Integer.parseInt(mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
int height = Integer.parseInt(mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
//divide the width and height by 10
width /= 10;
height /= 10;
//set the size of the surface to play on to 1/10 the width and height
mVideoView.getHolder().setFixedSize(width, height);
//Set up the media player
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setLooping(true);
try {
mMediaPlayer.setDataSource(cachedPath);
} catch (Exception e) {
Timber.e(e, e.getMessage());
}
//init and start the video player with the mask size set to 17
mVideoView.init(mMediaPlayer, new BlurEffect2(17, width, height));
mVideoView.onResume();
}
我正在构建一个 Android 应用程序,其中 ExoPlayer 在 SurfaceView 的表面上播放视频,我正在研究是否可以动态模糊正在播放的视频。
涉及首先生成要模糊的视图位图的模糊技术将不起作用,因为 SurfaceView 的表面部分不会出现在位图中。
在旧版本的 Android(例如 Surface.FX_SURFACE_BLUR)中,表面和视图曾经具有内置的模糊效果,但在较新的 API 中似乎已被弃用。
任何人都可以分享一些关于如何动态模糊表面的见解吗?谢谢。
Whosebug 上有很多问题,其中包含一些需要完成的小细节。我将回顾一下我使用的方法,希望它对某些人有用。
如果这是视频帧的静态模糊,以 TextureView
播放视频就足够了,使用 .getBitmap()
函数并使用诸如以下工具模糊生成的位图渲染脚本。但是,.getBitmap()
是在主 UI 线程上执行的,因此滞后于它试图复制其帧的视频。
要对每一帧执行模糊处理,最好的方法似乎是使用带有自定义渲染器的 GLSurfaceView。我使用 VidEffects pointed to from
具有大半径的模糊可能需要大量计算。这就是为什么我首先使用两个单独的片段着色器(一个用于水平模糊,一个用于垂直模糊结果)来执行模糊。实际上,我最终只使用了一个片段着色器来应用 7x7 高斯内核。如果您的 GLSurfaceView
很大,请牢记一件非常重要的事情,即在 GLSurfaceView
的 SurfaceHolder
上调用 setFixedSize()
以使其分辨率低于屏幕分辨率。结果看起来不是很像素化,因为无论如何它都是模糊的,但是性能提升非常显着。
我制作的模糊在大多数设备上都能以 24fps 的速度播放,setFixedSize()
指定其分辨率为 100x70。
万一有人想要单通道片段着色器来完成圆...以下代码实现了 VidEffects. I adapted it from this example on ShaderToy.com.
中可用代码中定义的ShaderInterface
public class BlurEffect2 implements ShaderInterface {
private final int mMaskSize;
private final int mWidth;
private final int mHeight;
public BlurEffect2(int maskSize, int width, int height) {
mMaskSize = maskSize;
mWidth = width;
mHeight = height;
}
@Override
public String getShader(GLSurfaceView mGlSurfaceView) {
float hStep = 1.0f / mWidth;
float vStep = 1.0f / mHeight;
return "#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
//"in" attributes from our vertex shader
"varying vec2 vTextureCoord;\n" +
//declare uniforms
"uniform samplerExternalOES sTexture;\n" +
"float normpdf(in float x, in float sigma) {\n" +
" return 0.39894 * exp(-0.5 * x * x / (sigma * sigma)) / sigma;\n" +
"}\n" +
"void main() {\n" +
" vec3 c = texture2D(sTexture, vTextureCoord).rgb;\n" +
//declare stuff
" const int mSize = " + mMaskSize + ";\n" +
" const int kSize = (mSize - 1) / 2;\n" +
" float kernel[ mSize];\n" +
" vec3 final_colour = vec3(0.0);\n" +
//create the 1-D kernel
" float sigma = 7.0;\n" +
" float Z = 0.0;\n" +
" for (int j = 0; j <= kSize; ++j) {\n" +
" kernel[kSize + j] = kernel[kSize - j] = normpdf(float(j), sigma);\n" +
" }\n" +
//get the normalization factor (as the gaussian has been clamped)
" for (int j = 0; j < mSize; ++j) {\n" +
" Z += kernel[j];\n" +
" }\n" +
//read out the texels
" for (int i = -kSize; i <= kSize; ++i) {\n" +
" for (int j = -kSize; j <= kSize; ++j) {\n" +
" final_colour += kernel[kSize + j] * kernel[kSize + i] * texture2D(sTexture, (vTextureCoord.xy + vec2(float(i)*" + hStep + ", float(j)*" + vStep + "))).rgb;\n" +
" }\n" +
" }\n" +
" gl_FragColor = vec4(final_colour / (Z * Z), 1.0);\n" +
"}";
}
}
正如 Michael 在上面指出的那样,您可以通过使用 setFixedSize
设置 SurfaceView
的大小来提高性能。
@BindView(R.id.video_snap)
VideoSurfaceView mVideoView;
@Override
public void showVideo(String cachedPath) {
mImageView.setVisibility(View.GONE);
mVideoView.setVisibility(View.VISIBLE);
//Get width and height of the video
final MediaMetadataRetriever mRetriever = new MediaMetadataRetriever();
mRetriever.setDataSource(cachedPath);
int width = Integer.parseInt(mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
int height = Integer.parseInt(mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
//divide the width and height by 10
width /= 10;
height /= 10;
//set the size of the surface to play on to 1/10 the width and height
mVideoView.getHolder().setFixedSize(width, height);
//Set up the media player
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setLooping(true);
try {
mMediaPlayer.setDataSource(cachedPath);
} catch (Exception e) {
Timber.e(e, e.getMessage());
}
//init and start the video player with the mask size set to 17
mVideoView.init(mMediaPlayer, new BlurEffect2(17, width, height));
mVideoView.onResume();
}