我正在使用 GLSurfaceView 创建一个录制应用程序,但是当我进入 PIP 模式时,GLSurfaceView 的 Surface 太大,因此无法很好地看到屏幕。
看图片。
[1]:https ://i.stack.imgur.com/GYebT.jpg
[2]:https ://i.stack.imgur.com/XUlhA.jpg
两个屏幕都在拍摄同一个场景。如图所示,对于 PIP 模式,屏幕被放大。
VideoCallFragment.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".VideoCallFragment">
<com.jypark.glsurfaceandremoteservice.gles.AspectRatioFrameLayout
android:id="@+id/aspect_frame_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
app:resize_mode="fill"
>
<android.opengl.GLSurfaceView
android:id="@+id/gl_surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</com.jypark.glsurfaceandremoteservice.gles.AspectRatioFrameLayout>
<ImageButton
android:id="@+id/ib_recording"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_recording"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="32dp"
android:contentDescription="@string/recording_image_button_desc"
/>
</RelativeLayout>
VideoCallFragment.java
public class VideoCallFragment extends Fragment implements SurfaceTexture.OnFrameAvailableListener{
private AspectRatioFrameLayout aspectRatioFrameLayout;
private GLSurfaceView glSurfaceView;
private ImageButton imageButton;
private Messenger messenger;
private RenderHandler mRendererHandler;
private VideoRenderAndRecorder mRenderer;
private static final TextureMovieEncoder sVideoEncoder = new TextureMovieEncoder();
private Camera mCamera;
private int RECORD_WIDTH = 480;
private int RECORD_HEIGHT = 640;
private int VIDEO_WIDTH = 1080;
private int VIDEO_HEIGHT = 1920;
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
showLog("onServiceConnected");
messenger = new Messenger(service);
try{
Message reply = new Message();
reply.replyTo = new Messenger(replyHandler);
reply.what = MSG_SERVICE_BIND;
messenger.send(reply);
}
catch (RemoteException e){
showLog("service error: " + e.getMessage());
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
showLog("onServiceDisconnected");
}
};
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
createGLRenderer();
bindToService();
}
private void createGLRenderer(){
mRendererHandler = new RenderHandler(this);
mRenderer = new VideoRenderAndRecorder(mRendererHandler, sVideoEncoder);
}
private void bindToService(){
ComponentName cn = new ComponentName("com.jypark.glsurfaceandremoteservice", "com.jypark.glsurfaceandremoteservice.VideoRecordService");
Intent intent = new Intent();
intent.setComponent(cn);
requireActivity().bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
private final Handler replyHandler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case MSG_SET_SURFACE_TEXTURE:
showLog("MSG_SET_SURFACE_TEXTURE");
break;
}
}
};
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_video_call, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initViewBinding(view);
setGlSurfaceView();
setSize();
imageButton.setOnClickListener(v->{
});
}
private void initViewBinding(View view){
glSurfaceView = view.findViewById(R.id.gl_surface_view);
imageButton = view.findViewById(R.id.ib_recording);
aspectRatioFrameLayout = view.findViewById(R.id.aspect_frame_layout);
}
private void setGlSurfaceView(){
glSurfaceView.setEGLContextClientVersion(2); // select GLES 2.0
glSurfaceView.setRenderer(mRenderer);
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
private void setSize(){
RECORD_WIDTH = isLandscape()? 640 : 480;
RECORD_HEIGHT = isLandscape()? 480 : 640;
DisplayMetrics metrics = getResources().getDisplayMetrics();
VIDEO_HEIGHT = metrics.heightPixels;
VIDEO_WIDTH = metrics.widthPixels;
}
@Override
public void onResume() {
super.onResume();
if(mCamera==null) openCamera();
glSurfaceView.onResume();
}
private void openCamera() {
if (mCamera != null) {
throw new RuntimeException("camera already initialized");
}
Camera.CameraInfo info = new Camera.CameraInfo();
// Try to find a front-facing camera (e.g. for videoconferencing).
int numCameras = Camera.getNumberOfCameras();
for (int i = 0; i < numCameras; i++) {
Camera.getCameraInfo(i, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
mCamera = Camera.open(i);
break;
}
}
if (mCamera == null) {
showLog("No front-facing camera found; opening default");
mCamera = Camera.open(); // opens first back-facing camera
}
if (mCamera == null) {
throw new RuntimeException("Unable to open camera");
}
Camera.Parameters parms = mCamera.getParameters();
CameraUtils.choosePreviewSize(parms, VIDEO_WIDTH, VIDEO_HEIGHT);
// Give the camera a hint that we're recording video. This can have a big
// impact on frame rate.
parms.setRecordingHint(true);
// leave the frame rate set to default
mCamera.setParameters(parms);
int[] fpsRange = new int[2];
Camera.Size mCameraPreviewSize = parms.getPreviewSize();
parms.getPreviewFpsRange(fpsRange);
int mCameraPreviewWidth = mCameraPreviewSize.width;
int mCameraPreviewHeight = mCameraPreviewSize.height;
if(!isLandscape()){
showLog("Portrait");
mCamera.setDisplayOrientation(90);
aspectRatioFrameLayout.setAspectRatio((float) mCameraPreviewHeight / mCameraPreviewWidth);
}
else{
showLog("Landscape");
mCamera.setDisplayOrientation(0);
aspectRatioFrameLayout.setAspectRatio((float) mCameraPreviewWidth / mCameraPreviewHeight);
}
}
private boolean isLandscape(){
int rotation = requireActivity().getWindowManager().getDefaultDisplay().getRotation();
return rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270;
}
@Override
public void onPause() {
super.onPause();
}
private void releaseCamera() {
if (mCamera != null) {
mCamera.stopPreview();
mCamera.release();
mCamera = null;
showLog("releaseCamera -- done");
}
}
@Override
public void onDestroy() {
super.onDestroy();
mRendererHandler.invalidateHandler(); // paranoia
releaseCamera();
glSurfaceView.queueEvent(new Runnable() {
@Override public void run() {
// Tell the renderer that it's about to be paused so it can clean up.
mRenderer.notifyPausing();
}
});
glSurfaceView.onPause();
}
public void showLog(String msg){
String TAG = "VideoCallFragment";
Log.d("확인", TAG + " -> " + msg);
}
private void handleSetSurfaceTexture(SurfaceTexture st) {
st.setOnFrameAvailableListener(this);
try {
mCamera.setPreviewTexture(st);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
mCamera.startPreview();
}
@Override
public void onFrameAvailable(SurfaceTexture st) {
glSurfaceView.requestRender();
}
public static class RenderHandler extends Handler{
// Weak reference to the Activity; only access this from the UI thread.
private final WeakReference<VideoCallFragment> mWeakFragment;
public RenderHandler(VideoCallFragment fragment) {
mWeakFragment = new WeakReference<VideoCallFragment>(fragment);
}
/**
* Drop the reference to the activity. Useful as a paranoid measure to ensure that
* attempts to access a stale Activity through a handler are caught.
*/
public void invalidateHandler() {
mWeakFragment.clear();
}
@Override // runs on UI thread
public void handleMessage(Message inputMessage) {
int what = inputMessage.what;
VideoCallFragment fragment = mWeakFragment.get();
if (fragment == null) {
return;
}
switch (what){
case MSG_SET_SURFACE_TEXTURE:
fragment.handleSetSurfaceTexture((SurfaceTexture) inputMessage.obj);
break;
case MSG_SURFACE_CHANGE:
// fragment.setGLSurfaceSizeChange(inputMessage.arg1, inputMessage.arg2);
break;
default:
throw new RuntimeException("unknown msg " + what);
}
}
}
@Override
public void onPictureInPictureModeChanged(boolean isPip) {
super.onPictureInPictureModeChanged(isPip);
showLog("onPictureInPictureModeChanged isPip:" + isPip);
if(isPip){
imageButton.setVisibility(View.INVISIBLE);
}
else{
imageButton.setVisibility(View.VISIBLE);
}
}
}
清单.xml
<activity android:name=".MainActivity"
android:supportsPictureInPicture="true"
android:launchMode="singleTask"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
/>
如何使屏幕在 PIP 模式下看起来保持原始比例?