Как исправить искажения в записанном видео на Android - PullRequest
0 голосов
/ 06 октября 2019

В моем приложении я хочу использовать функцию захвата видео с экрана устройства.
Я записываю записанные видеокоды в один сервис, но на некоторых устройствах после сохранения захваченного видео и его воспроизведения искажаем записанное видео.

Я вставляю сюда свои сервисные коды и знаю, что мои коды огромны.
Но на самом деле мне нужна помощь.

Воспроизведение сохраненного видео с эффектом изображения!
Нажмите, чтобысм. изображение

Мои сервисные коды:

public class RecorderService extends Service {
    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
    private int WIDTH, HEIGHT, FPS, DENSITY_DPI;
    private int BITRATE;
    private boolean isBound = false;
    private String audioRecSource;
    private String SAVEPATH, videoName;
    private AudioManager mAudioManager;
    private FloatingControlService floatingControlService;

    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 0);
        ORIENTATIONS.append(Surface.ROTATION_90, 90);
        ORIENTATIONS.append(Surface.ROTATION_180, 180);
        ORIENTATIONS.append(Surface.ROTATION_270, 270);
    }

    private int screenOrientation;
    private String saveLocation;

    private boolean isRecording;
    private NotificationManager mNotificationManager;
    Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message message) {
            Toast.makeText(RecorderService.this, “Capture video“, Toast.LENGTH_SHORT).show();
        }
    };

    private Intent data;
    private int result;

    private WindowManager window;
    private MediaProjection mMediaProjection;
    private VirtualDisplay mVirtualDisplay;
    private MediaProjectionCallback mMediaProjectionCallback;
    private MediaRecorder mMediaRecorder;

    //Service connection to manage the connection state between this service and the bounded service
    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //Get the service instance
            FloatingControlService.ServiceBinder binder = (FloatingControlService.ServiceBinder) service;
            floatingControlService = binder.getService();
            isBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            floatingControlService = null;
            isBound = false;
        }
    };

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        videoName = GoodPrefs.getInstance().getString(ConstKeys.TEST_NAME, "Testady");
        //return super.onStartCommand(intent, flags, startId);
        //Find the action to perform from intent
        switch (intent.getAction()) {
            case ConstKeys.SCREEN_RECORDING_START:
                if (!isRecording) {
                    screenOrientation = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
                    data = intent.getParcelableExtra(ConstKeys.RECORDER_INTENT_DATA);
                    result = intent.getIntExtra(ConstKeys.RECORDER_INTENT_RESULT, Activity.RESULT_OK);

                    getValues();

                    startRecording();

                    openTestApp();

                } else {
                    Toast.makeText(this, “Recording already used“, Toast.LENGTH_SHORT).show();
                }
                break;
            case ConstKeys.SCREEN_RECORDING_PAUSE:
                break;
            case ConstKeys.SCREEN_RECORDING_RESUME:
                break;
            case ConstKeys.SCREEN_RECORDING_STOP:
                stopRecording();
                break;
        }

        return START_STICKY;
    }

    private void openTestApp() {
        String runnableApp = GoodPrefs.getInstance().getString(ConstKeys.TEST_RUNNABLE, "");
        String packageName = GoodPrefs.getInstance().getString(ConstKeys.TEST_PACKAGE_NAME, "");
        if (!App.isEmptyString(runnableApp)) {
            //Browser
            if (runnableApp.equals(ConstKeys.BROWSER)) {
                String siteUrl = GoodPrefs.getInstance().getString(ConstKeys.TEST_DIRECTION, "");
                startActivity(App.openUrlWithBrowser(this, siteUrl, packageName));
            } else {
                startActivity(App.openAppWithPackage(this, packageName));
            }
        }
    }

    private void stopRecording() {
        stopScreenSharing();
    }

    private void startRecording() {
        //Initialize MediaRecorder class and initialize it with preferred configuration
        mMediaRecorder = new MediaRecorder();
        mMediaRecorder.setOnErrorListener((mr, what, extra) -> {
            Toast.makeText(this, "Field capture video", Toast.LENGTH_SHORT).show();
            destroyMediaProjection();
        });

        initRecorder();

        //Set Callback for MediaProjection
        mMediaProjectionCallback = new MediaProjectionCallback();
        MediaProjectionManager mProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);

        //Initialize MediaProjection using data received from Intent
        mMediaProjection = mProjectionManager.getMediaProjection(result, data);
        mMediaProjection.registerCallback(mMediaProjectionCallback, null);

        /* Create a new virtual display with the actual default display
         * and pass it on to MediaRecorder to start recording */
        mVirtualDisplay = createVirtualDisplay();
        try {
            mMediaRecorder.start();

            //Floating button service
            Intent floatingControlsIntent = new Intent(this, FloatingControlService.class);
            startService(floatingControlsIntent);
            bindService(floatingControlsIntent,
                    serviceConnection, BIND_AUTO_CREATE);

            //Set the state of the recording
            if (isBound)
                floatingControlService.setRecordingState(ConstKeys.RecordingState.RECORDING);

            isRecording = true;

        } catch (IllegalStateException e) {
            Toast.makeText(this, “Field capture video“, Toast.LENGTH_SHORT).show();
            isRecording = false;
            mMediaProjection.stop();
            stopSelf();
        }
    }

    //Virtual display created by mirroring the actual physical display
    private VirtualDisplay createVirtualDisplay() {
        return mMediaProjection.createVirtualDisplay("MainActivity",
                WIDTH, HEIGHT, DENSITY_DPI,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                mMediaRecorder.getSurface(), null /*Callbacks*/, null
                /*Handler*/);
    }

    public int getBestSampleRate() {
        AudioManager am = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);
        String sampleRateString = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
        int samplingRate = (sampleRateString == null) ? 44100 : Integer.parseInt(sampleRateString);

        return samplingRate;
    }

    private boolean getMediaCodecFor(String format) {
        MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
        MediaFormat mediaFormat = MediaFormat.createVideoFormat(
                format,
                WIDTH,
                HEIGHT
        );
        String encoder = list.findEncoderForFormat(mediaFormat);
        if (encoder == null) {
            Log.d("Null Encoder: ", format);
            return false;
        }
        Log.d("Encoder", encoder);
        return !encoder.startsWith("OMX.google");
    }

    private int getBestVideoEncoder() {
        int VideoCodec = MediaRecorder.VideoEncoder.DEFAULT;
        if (getMediaCodecFor(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                VideoCodec = MediaRecorder.VideoEncoder.HEVC;
            }
        } else if (getMediaCodecFor(MediaFormat.MIMETYPE_VIDEO_AVC))
            VideoCodec = MediaRecorder.VideoEncoder.H264;
        return VideoCodec;
    }

    /* Initialize MediaRecorder with desired default values and values set by user. Everything is
     * pretty much self explanatory */
    private void initRecorder() {
        boolean mustRecAudio = false;
        try {
            String audioBitRate = "128";
            String audioSamplingRate = "68000";
            String audioChannel = "1";
            switch (audioRecSource) {
                case "1":
                    mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
                    mustRecAudio = true;
                    break;
                case "2":
                    mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
                    mMediaRecorder.setAudioEncodingBitRate(Integer.parseInt(audioBitRate));
                    mMediaRecorder.setAudioSamplingRate(Integer.parseInt(audioSamplingRate));
                    mMediaRecorder.setAudioChannels(Integer.parseInt(audioChannel));
                    mustRecAudio = true;
                    break;
                case "3":
                    mAudioManager.setParameters("screenRecordAudioSource=8");
                    mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.REMOTE_SUBMIX);
                    mMediaRecorder.setAudioEncodingBitRate(Integer.parseInt(audioBitRate));
                    mMediaRecorder.setAudioSamplingRate(Integer.parseInt(audioSamplingRate));
                    mMediaRecorder.setAudioChannels(Integer.parseInt(audioChannel));
                    mustRecAudio = true;
                    break;
            }
            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
            mMediaRecorder.setOutputFile(SAVEPATH);
            mMediaRecorder.setVideoSize(WIDTH, HEIGHT);
            mMediaRecorder.setVideoEncoder(getBestVideoEncoder());
            mMediaRecorder.setMaxFileSize(getFreeSpaceInBytes());
            if (mustRecAudio)
                mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            mMediaRecorder.setVideoEncodingBitRate(BITRATE);
            mMediaRecorder.setVideoFrameRate(FPS);
            mMediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private long getFreeSpaceInBytes() {
        StatFs FSStats = new StatFs(saveLocation);
        long bytesAvailable = FSStats.getAvailableBytes();// * FSStats.getBlockCountLong();
        return bytesAvailable;
    }

    //Start service as a foreground service. We dont want the service to be killed in case of low memory
    private void startNotificationForeGround(Notification notification, int ID) {
        startForeground(ID, notification);
    }

    //Update existing notification with its ID and new Notification data
    private void updateNotification(Notification notification, int ID) {
        getManager().notify(ID, notification);
    }

    private NotificationManager getManager() {
        if (mNotificationManager == null) {
            mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        }
        return mNotificationManager;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    //Get user's choices for user choosable settings
    public void getValues() {
        String res = getResolution();
        setWidthHeight(res);
        FPS = 25;
        BITRATE = 7130317;
        audioRecSource = "1";
        saveLocation = Environment.getExternalStorageDirectory() + File.separator + ConstKeys.APPDIR;
        File saveDir = new File(saveLocation);
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) && !saveDir.isDirectory()) {
            saveDir.mkdirs();
        }

        String saveFileName = getFileSaveName();
        SAVEPATH = saveLocation + File.separator + saveFileName + ".mp4";
    }

    /* The PreferenceScreen save values as string and we save the user selected video resolution as
     * WIDTH x HEIGHT. Lets split the string on 'x' and retrieve width and height */
    private void setWidthHeight(String res) {
        String[] widthHeight = res.split("x");
        String orientationPrefs = "auto";
        switch (orientationPrefs) {
            case "auto":
                if (screenOrientation == 0 || screenOrientation == 2) {
                    WIDTH = Integer.parseInt(widthHeight[0]);
                    HEIGHT = Integer.parseInt(widthHeight[1]);
                } else {
                    HEIGHT = Integer.parseInt(widthHeight[0]);
                    WIDTH = Integer.parseInt(widthHeight[1]);
                }
                break;
            case "portrait":
                WIDTH = Integer.parseInt(widthHeight[0]);
                HEIGHT = Integer.parseInt(widthHeight[1]);
                break;
            case "landscape":
                HEIGHT = Integer.parseInt(widthHeight[0]);
                WIDTH = Integer.parseInt(widthHeight[1]);
                break;
        }
    }

    //Get the device resolution in pixels
    private String getResolution() {
        DisplayMetrics metrics = new DisplayMetrics();
        window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        window.getDefaultDisplay().getRealMetrics(metrics);
        DENSITY_DPI = metrics.densityDpi;
        int width = metrics.widthPixels;
        width = 480;
        float aspectRatio = getAspectRatio(metrics);
        int height = calculateClosestHeight(width, aspectRatio);
        //String res = width + "x" + (int) (width * getAspectRatio(metrics));
        String res = width + "x" + height;
        return res;
    }

    private int calculateClosestHeight(int width, float aspectRatio) {
        int calculatedHeight = (int) (width * aspectRatio);
        if (calculatedHeight / 16 != 0) {
            int quotient = calculatedHeight / 16;

            calculatedHeight = 16 * quotient;

        }
        return calculatedHeight;
    }

    private float getAspectRatio(DisplayMetrics metrics) {
        float screen_width = metrics.widthPixels;
        float screen_height = metrics.heightPixels;
        float aspectRatio;
        if (screen_width > screen_height) {
            aspectRatio = screen_width / screen_height;
        } else {
            aspectRatio = screen_height / screen_width;
        }
        return aspectRatio;
    }

    //Return filename of the video to be saved formatted as chosen by the user
    private String getFileSaveName() {
        String filename = "yyyyMMdd_HHmmss";

        //Required to handle preference change
        filename = filename.replace("hh", "HH");
        String prefix = videoName;
        Date today = Calendar.getInstance().getTime();
        SimpleDateFormat formatter = new SimpleDateFormat(filename);
        return prefix + "_" + formatter.format(today);
    }

    /* Its weird that android does not index the files immediately once its created and that causes
     * trouble for user in finding the video in gallery. Let's explicitly announce the file creation
     * to android and index it */
    private void indexFile() {
        //Create a new ArrayList and add the newly created video file path to it
        ArrayList<String> toBeScanned = new ArrayList<>();
        toBeScanned.add(SAVEPATH);
        String[] toBeScannedStr = new String[toBeScanned.size()];
        toBeScannedStr = toBeScanned.toArray(toBeScannedStr);

        //Request MediaScannerConnection to scan the new file and index it
        MediaScannerConnection.scanFile(this, toBeScannedStr, null, (path, uri) -> {
            //Show toast on main thread
            Message message = mHandler.obtainMessage();
            message.sendToTarget();
            stopSelf();
        });
    }

    //Stop and destroy all the objects used for screen recording
    private void destroyMediaProjection() {
        this.mAudioManager.setParameters("screenRecordAudioSource=0");
        try {
            mMediaRecorder.stop();
            indexFile();
        } catch (RuntimeException e) {
            if (new File(SAVEPATH).delete())
                Toast.makeText(this, “Failed to store video“, Toast.LENGTH_SHORT).show();
        } finally {
            mMediaRecorder.reset();
            mVirtualDisplay.release();
            mMediaRecorder.release();
            if (mMediaProjection != null) {
                mMediaProjection.unregisterCallback(mMediaProjectionCallback);
                mMediaProjection.stop();
                mMediaProjection = null;
            }
            stopSelf();
        }
        isRecording = false;
    }

    private void stopScreenSharing() {
        if (mVirtualDisplay == null) {
            return;
        }
        destroyMediaProjection();
    }

    private class MediaProjectionCallback extends MediaProjection.Callback {
        @Override
        public void onStop() {
            stopScreenSharing();
        }
    }
}

Как я могу это исправить?

...