相信大家都看过直播,直播中的礼物特效非常的炫酷,今天就教大家如何实现这一过程。其原理就是在摄像头画面上播放透明的特效视频。首先需要准备好mp4格式的特效视频。然后使用opengles进行渲染,实现透明化。当然,由于没有服务器,只能白嫖了。只需要把制作好的特效视频上传到好看视频网站上面,通过抓包拿到播放地址,然后将地址上传到一篇博客上面即可。启动app时首先会爬取该博客上面的视频播放地址。通过这种方法即可以减小app的体积(仅有3.5M),又无需自己购买服务器。后期如果需要更改或修改特效视频,只需要修改博客上面的播放地址即可。
app下载地址:https://www.pgyer.com/T6he
特效视频的制作也很简单,视频格式如下所示:
视频的上半部分为我们想要的特效视频,下半部分为透明度,其值越接近255,则表示越不透明。在使用opengles渲染视频的时候,只需要把下半部分的像素值作为其透明度即可。
关于视频如何上传到好看视频网站,已经如何抓包获取视频播放地址,我在之前的一篇博客上面已经详细的说明了,如果不明白可前往查看。
上传完视频拿到播放地址以后,只需要发布一篇博客,并将视频的播放地址都发布到该博客上面即可。博客内容如下所示:
为了app自动获取特效的名称,需要在播放地址后面加上特效的名字,这样app特效的下拉框中就会自动填写这些特效名字。
使用camerax来显示摄像头画面。camerax相比于camera2更加方便,兼容性更广。
使用camerax首先需要添加摄像头权限
<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
添加相关依赖:
//CameraX
def camerax_version = "1.1.0-beta03"
// CameraX core library using camera2 implementation
implementation "androidx.camera:camera-camera2:$camerax_version"
// CameraX Lifecycle Library
implementation "androidx.camera:camera-lifecycle:$camerax_version"
// CameraX View class
implementation "androidx.camera:camera-view:1.0.0-alpha24"
开启摄像头代码:
//开启摄像头
if(allPermissionsGranted()){
startCamera(); //start camera if permission has been granted by user
} else{
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, 1001);
}
/**
* 获取摄像头权限
*/
private final String[] REQUIRED_PERMISSIONS = new String[]{"android.permission.CAMERA", "android.permission.WRITE_EXTERNAL_STORAGE"};
private boolean allPermissionsGranted() {
for (String permission : REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
/**
* 开始预览
*/
private CameraControl cameraControl;
private Executor executor = Executors.newSingleThreadExecutor();
private void startCamera() {
ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
ProcessCameraProvider.getInstance(this);
((ListenableFuture<?>) cameraProviderFuture).addListener(new Runnable() {
@SuppressLint("RestrictedApi")
@Override
public void run() {
try {
ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
//.setBackpressureStrategy(ImageAnalysis.STRATEGY_BLOCK_PRODUCER)//阻塞模式
//.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
//.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
.build();
imageAnalysis.setAnalyzer(executor, new ImageAnalysis.Analyzer() {
@Override
public void analyze(@NonNull @NotNull ImageProxy image) {
//yuv图像数据转bitmap
ImageProxy.PlaneProxy[] planes = image.getPlanes();
//cameraX 获取yuv
ByteBuffer yBuffer = planes[0].getBuffer();
ByteBuffer uBuffer = planes[1].getBuffer();
ByteBuffer vBuffer = planes[2].getBuffer();
int ySize = yBuffer.remaining();
int uSize = uBuffer.remaining();
int vSize = vBuffer.remaining();
byte[] nv21 = new byte[ySize + uSize + vSize];
yBuffer.get(nv21, 0, ySize);
vBuffer.get(nv21, ySize, vSize);
uBuffer.get(nv21, ySize + vSize, uSize);
//获取yuvImage
YuvImage yuvImage = new YuvImage(nv21, ImageFormat.NV21, image.getWidth(), image.getHeight(), null);
//输出流
ByteArrayOutputStream out = new ByteArrayOutputStream();
//压缩写入out
yuvImage.compressToJpeg(new Rect(0, 0, yuvImage.getWidth(), yuvImage.getHeight()), 50, out);
//转数组
byte[] imageBytes = out.toByteArray();
//生成bitmap
Bitmap bmp = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
// Bitmap rotateBitmap=null;
// Dector(bmp);
// Message message=new Message();
// message.what=2;
// message.obj=FPS;
// handler.sendMessage(message);
image.close();
}
});
//将相机的生命周期和activity的生命周期绑定,camerax 会自己释放
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
Preview preview = new Preview.Builder().build();
//创建图片的 capture
ImageCapture mImageCapture = new ImageCapture.Builder()
.setFlashMode(ImageCapture.FLASH_MODE_OFF)
.build();
//选择前置摄像头
CameraSelector cameraSelector = new CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build();
// Unbind use cases before rebinding
cameraProvider.unbindAll();
// Bind use cases to camera
//参数中如果有mImageCapture才能拍照,否则会报下错
//Not bound to a valid Camera [ImageCapture:androidx.camera.core.ImageCapture-bce6e930-b637-40ee-b9b9-
Camera camera = cameraProvider.bindToLifecycle(MainActivity.this, cameraSelector, preview, imageAnalysis,mImageCapture);
cameraControl = camera.getCameraControl();
cameraControl.setLinearZoom(0.1f);
preview.setSurfaceProvider(pvCameraPreview.getSurfaceProvider());
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, ContextCompat.getMainExecutor(this));
}
opengl片源渲染器代码:
"#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;" +
"uniform samplerExternalOES textureSampler;" +
"varying vec2 textureCoordinate;" +
"void main(){" +
"vec4 mycolor=texture2D(textureSampler,vec2(textureCoordinate.x,textureCoordinate.y/2.0));"+
"vec4 Alpha=texture2D(textureSampler,vec2(textureCoordinate.x,textureCoordinate.y/2.0+0.5));"+
"float alpha=1.0-(mycolor.r+mycolor.g+mycolor.b)/3.0;"+
"gl_FragColor=vec4(1.0-mycolor.rgb,alpha);" +
"}";
手机端如果打不开链接,可以使用电脑试一下。
gitee地址:https://gitee.com/mqwdasddqw/SpecialEffect3