1

我使用 mp4parser 将视频与动态暂停合并并录制视频捕获,最长录制时间为 6 秒。在以最小暂停/录制录制视频时预览其工作正常,如果我尝试超过 3 次暂停/录制意味着最后一个视频文件没有与音频正确合并。在视频开始时同步正常,但在结束时视频挂起,音频在屏幕上播放,剩余文件持续时间约为 1 秒。

我的录音管理器

public class RecordingManager implements Camera.ErrorCallback, MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener {

    private static final String TAG = RecordingManager.class.getSimpleName();
    private static final int FOCUS_AREA_RADIUS = 32;
    private static final int FOCUS_MAX_VALUE = 1000;
    private static final int FOCUS_MIN_VALUE = -1000;
    private static final long MINIMUM_RECORDING_TIME = 2000;
    private static final int MAXIMUM_RECORDING_TIME = 70 * 1000;
    private static final long LOW_STORAGE_THRESHOLD = 5 * 1024 * 1024;
    private static final long RECORDING_FILE_LIMIT = 100 * 1024 * 1024;

    private boolean paused = true;

    private MediaRecorder mediaRecorder = null;
    private boolean recording = false;

    private FrameLayout previewFrame = null;

    private boolean mPreviewing = false;

//    private TextureView mTextureView = null;
//    private SurfaceTexture mSurfaceTexture = null;
//    private boolean mSurfaceTextureReady = false;
//
    private SurfaceView surfaceView = null;
    private SurfaceHolder surfaceHolder = null;
    private boolean surfaceViewReady = false;

    private Camera camera = null;
    private Camera.Parameters cameraParameters = null;
    private CamcorderProfile camcorderProfile = null;

    private int mOrientation = -1;
    private OrientationEventListener mOrientationEventListener = null;

    private long mStartRecordingTime;
    private int mVideoWidth;
    private int mVideoHeight;
    private long mStorageSpace;

    private Handler mHandler = new Handler();
//    private Runnable mUpdateRecordingTimeTask = new Runnable() {
//        @Override
//        public void run() {
//            long recordingTime = System.currentTimeMillis() - mStartRecordingTime;
//            Log.d(TAG, String.format("Recording time:%d", recordingTime));
//            mHandler.postDelayed(this, CLIP_GRAPH_UPDATE_INTERVAL);
//        }
//    };
    private Runnable mStopRecordingTask = new Runnable() {
        @Override
        public void run() {
            stopRecording();
        }
    };

    private static RecordingManager mInstance = null;
    private Activity currentActivity = null;
    private String destinationFilepath = "";
    private String snapshotFilepath = "";

    public static RecordingManager getInstance(Activity activity, FrameLayout previewFrame) {
        if (mInstance == null || mInstance.currentActivity != activity) {
            mInstance = new RecordingManager(activity, previewFrame);
        }
        return mInstance;
    }

    private RecordingManager(Activity activity, FrameLayout previewFrame) {
        currentActivity = activity;
        this.previewFrame = previewFrame;
    }

    public int getVideoWidth() {
        return this.mVideoWidth;
    }
    public int getVideoHeight() {
        return this.mVideoHeight;
    }
    public void setDestinationFilepath(String filepath) {
        this.destinationFilepath = filepath;
    }
    public String getDestinationFilepath() {
        return this.destinationFilepath;
    }
    public void setSnapshotFilepath(String filepath) {
        this.snapshotFilepath = filepath;
    }
    public String getSnapshotFilepath() {
        return this.snapshotFilepath;
    }
    public void init(String videoPath, String snapshotPath) {
        Log.v(TAG, "init.");
        setDestinationFilepath(videoPath);
        setSnapshotFilepath(snapshotPath);
        if (!Utils.isExternalStorageAvailable()) {
            showStorageErrorAndFinish();
            return;
        }

        openCamera();
        if (camera == null) {
            showCameraErrorAndFinish();
            return;
        }



    public void onResume() {
        Log.v(TAG, "onResume.");
        paused = false;

        // Open the camera
        if (camera == null) {
            openCamera();
            if (camera == null) {
                showCameraErrorAndFinish();
                return;
            }
        }

        // Initialize the surface texture or surface view
//        if (useTexture() && mTextureView == null) {
//            initTextureView();
//            mTextureView.setVisibility(View.VISIBLE);
//        } else if (!useTexture() && mSurfaceView == null) {
            initSurfaceView();
            surfaceView.setVisibility(View.VISIBLE);
//        }

        // Start the preview
        if (!mPreviewing) {
            startPreview();
        }
    }

    private void openCamera() {
        Log.v(TAG, "openCamera");
        try {
            camera = Camera.open();
            camera.setErrorCallback(this);
            camera.setDisplayOrientation(90); // Since we only support portrait mode
            cameraParameters = camera.getParameters();
        } catch (RuntimeException e) {
            e.printStackTrace();
            camera = null;
        }
    }

    private void closeCamera() {
        Log.v(TAG, "closeCamera");
        if (camera == null) {
            Log.d(TAG, "Already stopped.");
            return;
        }

        camera.setErrorCallback(null);
        if (mPreviewing) {
            stopPreview();
        }
        camera.release();
        camera = null;
    }




    private void initSurfaceView() {
        surfaceView = new SurfaceView(currentActivity);
        surfaceView.getHolder().addCallback(new SurfaceViewCallback());
        surfaceView.setVisibility(View.GONE);
        FrameLayout.LayoutParams params = new LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, Gravity.CENTER);
        surfaceView.setLayoutParams(params);
        Log.d(TAG, "add surface view to preview frame");
        previewFrame.addView(surfaceView);
    }

    private void releaseSurfaceView() {
        if (surfaceView != null) {
            previewFrame.removeAllViews();
            surfaceView = null;
            surfaceHolder = null;
            surfaceViewReady = false;
        }
    }

    private void startPreview() {
//        if ((useTexture() && !mSurfaceTextureReady) || (!useTexture() && !mSurfaceViewReady)) {
//            return;
//        }

        Log.v(TAG, "startPreview.");
        if (mPreviewing) {
            stopPreview();
        }

        setCameraParameters();
        resizePreview();

        try {
//            if (useTexture()) {
//                mCamera.setPreviewTexture(mSurfaceTexture);
//            } else {
                camera.setPreviewDisplay(surfaceHolder);
//            }
            camera.startPreview();
            mPreviewing = true;
        } catch (Exception e) {
            closeCamera();
            e.printStackTrace();
            Log.e(TAG, "startPreview failed.");
        }

    }

    private void stopPreview() {
        Log.v(TAG, "stopPreview");
        if (camera != null) {
            camera.stopPreview();
            mPreviewing = false;
        }
    }

    public void onPause() {
        paused = true;

        if (recording) {
            stopRecording();
        }
        closeCamera();

//        if (useTexture()) {
//            releaseSurfaceTexture();
//        } else {
            releaseSurfaceView();
//        }
    }

    private void setCameraParameters() {
        if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_720P)) {
            camcorderProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
        } else if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_480P)) {
            camcorderProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
        } else {
            camcorderProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
        }
        mVideoWidth = camcorderProfile.videoFrameWidth;
        mVideoHeight = camcorderProfile.videoFrameHeight;
        camcorderProfile.fileFormat = MediaRecorder.OutputFormat.MPEG_4;
        camcorderProfile.videoFrameRate = 30;

        Log.v(TAG, "mVideoWidth=" + mVideoWidth + " mVideoHeight=" + mVideoHeight);
        cameraParameters.setPreviewSize(mVideoWidth, mVideoHeight);

        if (cameraParameters.getSupportedWhiteBalance().contains(Camera.Parameters.WHITE_BALANCE_AUTO)) {
            cameraParameters.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
        }

        if (cameraParameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
            cameraParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
        }

        cameraParameters.setRecordingHint(true);
        cameraParameters.set("cam_mode", 1);

        camera.setParameters(cameraParameters);
        cameraParameters = camera.getParameters();

        camera.setDisplayOrientation(90);
        android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
        Log.d(TAG, info.orientation + " degree");
    }

    private void resizePreview() {
        Log.d(TAG, String.format("Video size:%d|%d", mVideoWidth, mVideoHeight));

        Point optimizedSize = getOptimizedPreviewSize(mVideoWidth, mVideoHeight);
        Log.d(TAG, String.format("Optimized size:%d|%d", optimizedSize.x, optimizedSize.y));

        ViewGroup.LayoutParams params = (ViewGroup.LayoutParams) previewFrame.getLayoutParams();
        params.width = optimizedSize.x;
        params.height = optimizedSize.y;
        previewFrame.setLayoutParams(params);
    }

    public void setOrientation(int ori) {
        this.mOrientation = ori;
    }

    public void setOrientationEventListener(OrientationEventListener listener) {
        this.mOrientationEventListener = listener;
    }

    public Camera getCamera() {
        return camera;
    }

    @SuppressWarnings("serial")
    public void setFocusArea(float x, float y) {
        if (camera != null) {
            int viewWidth = surfaceView.getWidth();
            int viewHeight = surfaceView.getHeight();

            int focusCenterX = FOCUS_MAX_VALUE - (int) (x / viewWidth * (FOCUS_MAX_VALUE - FOCUS_MIN_VALUE));
            int focusCenterY = FOCUS_MIN_VALUE + (int) (y / viewHeight * (FOCUS_MAX_VALUE - FOCUS_MIN_VALUE));
            final int left = focusCenterY - FOCUS_AREA_RADIUS < FOCUS_MIN_VALUE ? FOCUS_MIN_VALUE : focusCenterY - FOCUS_AREA_RADIUS;
            final int top = focusCenterX - FOCUS_AREA_RADIUS < FOCUS_MIN_VALUE ? FOCUS_MIN_VALUE : focusCenterX - FOCUS_AREA_RADIUS;
            final int right = focusCenterY + FOCUS_AREA_RADIUS > FOCUS_MAX_VALUE ? FOCUS_MAX_VALUE : focusCenterY + FOCUS_AREA_RADIUS;
            final int bottom = focusCenterX + FOCUS_AREA_RADIUS > FOCUS_MAX_VALUE ? FOCUS_MAX_VALUE : focusCenterX + FOCUS_AREA_RADIUS;

            Camera.Parameters params = camera.getParameters();
            params.setFocusAreas(new ArrayList<Camera.Area>() {
                {
                    add(new Camera.Area(new Rect(left, top, right, bottom), 1000));
                }
            });
            camera.setParameters(params);
            camera.autoFocus(new AutoFocusCallback() {
                @Override
                public void onAutoFocus(boolean success, Camera camera) {
                    Log.d(TAG, "onAutoFocus");
                }
            });
        }
    }

    public void startRecording(String destinationFilepath) {
        if (!recording) {
            updateStorageSpace();
            setDestinationFilepath(destinationFilepath);
            if (mStorageSpace <= LOW_STORAGE_THRESHOLD) {
                Log.v(TAG, "Storage issue, ignore the start request");
                Toast.makeText(currentActivity, "Storage issue, ignore the recording request", Toast.LENGTH_LONG).show();
                return;
            }

            if (!prepareMediaRecorder()) {
                Toast.makeText(currentActivity, "prepareMediaRecorder failed.", Toast.LENGTH_LONG).show();
                return;
            }

            Log.d(TAG, "Successfully prepare media recorder.");
            try {
                mediaRecorder.start();
            } catch (RuntimeException e) {
                Log.e(TAG, "MediaRecorder start failed.");
                releaseMediaRecorder();
                return;
            }

            mStartRecordingTime = System.currentTimeMillis();

            if (mOrientationEventListener != null) {
                mOrientationEventListener.disable();
            }

            recording = true;
        }
    }

    public void stopRecording() {
        if (recording) {
            if (!paused) {
                // Capture at least 1 second video
                long currentTime = System.currentTimeMillis();
                if (currentTime - mStartRecordingTime < MINIMUM_RECORDING_TIME) {
                    mHandler.postDelayed(mStopRecordingTask, MINIMUM_RECORDING_TIME - (currentTime - mStartRecordingTime));
                    return;
                }
            }

            if (mOrientationEventListener != null) {
                mOrientationEventListener.enable();
            }

//            mHandler.removeCallbacks(mUpdateRecordingTimeTask);

            try {
                mediaRecorder.setOnErrorListener(null);
                mediaRecorder.setOnInfoListener(null);
                mediaRecorder.stop(); // stop the recording
                Toast.makeText(currentActivity, "Video file saved.", Toast.LENGTH_LONG).show();

                long stopRecordingTime = System.currentTimeMillis();
                Log.d(TAG, String.format("stopRecording. file:%s duration:%d", destinationFilepath, stopRecordingTime - mStartRecordingTime));

                // Calculate the duration of video
                MediaMetadataRetriever mmr = new MediaMetadataRetriever();
                mmr.setDataSource(this.destinationFilepath);
                String _length = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
                if (_length != null) {
                    Log.d(TAG, String.format("clip duration:%d", Long.parseLong(_length)));
                }

                // Taking the snapshot of video
                Bitmap snapshot = ThumbnailUtils.createVideoThumbnail(this.destinationFilepath, Thumbnails.MICRO_KIND);
                try {
                    FileOutputStream out = new FileOutputStream(this.snapshotFilepath);
                    snapshot.compress(Bitmap.CompressFormat.JPEG, 70, out);
                    out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }

//                mActivity.showPlayButton();

            } catch (RuntimeException e) {
                e.printStackTrace();
                Log.e(TAG, e.getMessage());
                // if no valid audio/video data has been received when stop() is
                // called
            } finally {
//          

                releaseMediaRecorder(); // release the MediaRecorder object
                if (!paused) {
                    cameraParameters = camera.getParameters();
                }
                recording = false;
            }

        }
    }

    public void setRecorderOrientation(int orientation) {
        // For back camera only
        if (orientation != -1) {
            Log.d(TAG, "set orientationHint:" + (orientation + 135) % 360 / 90 * 90);
            mediaRecorder.setOrientationHint((orientation + 135) % 360 / 90 * 90);
        }else {
            Log.d(TAG, "not set orientationHint to mediaRecorder");
        }
    }

    private boolean prepareMediaRecorder() {
        mediaRecorder = new MediaRecorder();

        camera.unlock();
        mediaRecorder.setCamera(camera);

        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

        mediaRecorder.setProfile(camcorderProfile);

        mediaRecorder.setMaxDuration(MAXIMUM_RECORDING_TIME);
        mediaRecorder.setOutputFile(this.destinationFilepath);

        try {
            mediaRecorder.setMaxFileSize(Math.min(RECORDING_FILE_LIMIT, mStorageSpace - LOW_STORAGE_THRESHOLD));
        } catch (RuntimeException exception) {
        }

        setRecorderOrientation(mOrientation);

        if (!useTexture()) {
            mediaRecorder.setPreviewDisplay(surfaceHolder.getSurface());
        }

        try {
            mediaRecorder.prepare();
        } catch (IllegalStateException e) {
            releaseMediaRecorder();
            return false;
        } catch (IOException e) {
            releaseMediaRecorder();
            return false;
        }

        mediaRecorder.setOnErrorListener(this);
        mediaRecorder.setOnInfoListener(this);

        return true;

    }

    private void releaseMediaRecorder() {
        if (mediaRecorder != null) {
            mediaRecorder.reset(); // clear recorder configuration
            mediaRecorder.release(); // release the recorder object
            mediaRecorder = null;
            camera.lock(); // lock camera for later use
        }
    }

    private Point getOptimizedPreviewSize(int videoWidth, int videoHeight) {
        Display display = currentActivity.getWindowManager().getDefaultDisplay();
        Point size = new Point();
        display.getSize(size);

        Point optimizedSize = new Point();
        optimizedSize.x = size.x;
        optimizedSize.y = (int) ((float) videoWidth / (float) videoHeight * size.x);

        return optimizedSize;
    }

    private void showCameraErrorAndFinish() {
        DialogInterface.OnClickListener buttonListener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                currentActivity.finish();
            }
        };
        new AlertDialog.Builder(currentActivity).setCancelable(false)
                .setTitle("Camera error")
                .setMessage("Cannot connect to the camera.")
                .setNeutralButton("OK", buttonListener)
                .show();
    }

    private void showStorageErrorAndFinish() {
        DialogInterface.OnClickListener buttonListener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                currentActivity.finish();
            }
        };
        new AlertDialog.Builder(currentActivity).setCancelable(false)
                .setTitle("Storage error")
                .setMessage("Cannot read external storage.")
                .setNeutralButton("OK", buttonListener)
                .show();
    }

    private void updateStorageSpace() {
        mStorageSpace = getAvailableSpace();
        Log.v(TAG, "updateStorageSpace mStorageSpace=" + mStorageSpace);
    }

    private long getAvailableSpace() { 
        String state = Environment.getExternalStorageState();
        Log.d(TAG, "External storage state=" + state);
        if (Environment.MEDIA_CHECKING.equals(state)) {
            return -1;
        }
        if (!Environment.MEDIA_MOUNTED.equals(state)) {
            return -1;
        }

        File directory = currentActivity.getExternalFilesDir("vine");
        directory.mkdirs();
        if (!directory.isDirectory() || !directory.canWrite()) {
            return -1;
        }

        try {
            StatFs stat = new StatFs(directory.getAbsolutePath());
            return stat.getAvailableBlocks() * (long) stat.getBlockSize();
        } catch (Exception e) {
            Log.i(TAG, "Fail to access external storage", e);
        }
        return -1;
    }

    private boolean useTexture() {
        return false;
//        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
    }

    private class SurfaceViewCallback implements SurfaceHolder.Callback {

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            Log.v(TAG, "surfaceChanged. width=" + width + ". height=" + height);
        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            Log.v(TAG, "surfaceCreated");
            surfaceViewReady = true;
            surfaceHolder = holder;
            startPreview();
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            Log.d(TAG, "surfaceDestroyed");
            surfaceViewReady = false;
        }

    }

    @Override
    public void onError(int error, Camera camera) {
        Log.e(TAG, "Camera onError. what=" + error + ".");
        if (error == Camera.CAMERA_ERROR_SERVER_DIED) {

        } else if (error == Camera.CAMERA_ERROR_UNKNOWN) {

        }
    }

    @Override
    public void onInfo(MediaRecorder mr, int what, int extra) {
        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
            stopRecording();
        } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
            stopRecording();
            Toast.makeText(currentActivity, "Size limit reached", Toast.LENGTH_LONG).show();
        }
    }

    @Override
    public void onError(MediaRecorder mr, int what, int extra) {
        Log.e(TAG, "MediaRecorder onError. what=" + what + ". extra=" + extra);
        if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
            stopRecording();
        }
    }

}

视频工具

public class VideoUtils {
    private static final String TAG = VideoUtils.class.getSimpleName();

    static double[] matrix = new double[] { 0.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0,
            0.0, 1.0 };

    public static boolean MergeFiles(String speratedDirPath,
            String targetFileName) {
        File videoSourceDirFile = new File(speratedDirPath);
        String[] videoList = videoSourceDirFile.list();
        List<Track> videoTracks = new LinkedList<Track>();
        List<Track> audioTracks = new LinkedList<Track>();
        for (String file : videoList) {
            Log.d(TAG, "source files" + speratedDirPath
                    + File.separator + file);
            try {
                FileChannel fc = new FileInputStream(speratedDirPath
                        + File.separator + file).getChannel();
                Movie movie = MovieCreator.build(fc);
                for (Track t : movie.getTracks()) {
                    if (t.getHandler().equals("soun")) {
                        audioTracks.add(t);
                    }
                    if (t.getHandler().equals("vide")) {

                        videoTracks.add(t);
                    }
                }
            } catch (FileNotFoundException e) {
                e.printStackTrace();
                return false;
            } catch (IOException e) {
                e.printStackTrace();
                return false;
            }
        }

        Movie result = new Movie();

        try {
            if (audioTracks.size() > 0) {
                result.addTrack(new AppendTrack(audioTracks
                        .toArray(new Track[audioTracks.size()])));
            }
            if (videoTracks.size() > 0) {
                result.addTrack(new AppendTrack(videoTracks
                        .toArray(new Track[videoTracks.size()])));
            }
            IsoFile out = new DefaultMp4Builder().build(result);



            FileChannel fc = new RandomAccessFile(
                    String.format(targetFileName), "rw").getChannel();

            Log.d(TAG, "target file:" + targetFileName);
            TrackBox tb = out.getMovieBox().getBoxes(TrackBox.class).get(1);

            TrackHeaderBox tkhd = tb.getTrackHeaderBox();
            double[] b = tb.getTrackHeaderBox().getMatrix();

            tkhd.setMatrix(matrix);

            fc.position(0);
            out.getBox(fc);
            fc.close();
            for (String file : videoList) {
                File TBRFile = new File(speratedDirPath + File.separator + file);
                TBRFile.delete();
            }
            boolean a = videoSourceDirFile.delete();
            Log.d(TAG, "try to delete dir:" + a);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return false;
        }

        return true;
    }

    public static boolean clearFiles(String speratedDirPath) {
        File videoSourceDirFile = new File(speratedDirPath);
        if (videoSourceDirFile != null
                && videoSourceDirFile.listFiles() != null) {
            File[] videoList = videoSourceDirFile.listFiles();
            for (File video : videoList) {
                video.delete();
            }
            videoSourceDirFile.delete();
        }
        return true;
    }

    public static int createSnapshot(String videoFile, int kind, String snapshotFilepath) {
        return 0;
    };

    public static int createSnapshot(String videoFile, int width, int height, String snapshotFilepath) {
        return 0;
    }
}

我的参考代码项目链接是

https://github.com/jwfing/AndroidVideoKit

4

2 回答 2

3

我已经实施了一种替代解决方案来实现相同的结果。我没有创建多个文件来在 android 中实现像暂停和记录功能这样的 vine,而是创建了 2 个单独的记录器并管理时间戳。

这是库代码:

https://github.com/sourab-sharma/TouchToRecord

于 2013-10-01T09:49:22.090 回答
0

首先,录音机似乎无法启动。

然后你试图停止一个从未准备或开始的视频,它告诉你“在状态 0 中停止调用”。我想您正在尝试播放无法录制的视频。在调用之前,您应该先调用isPlaying以检查视频是否正在播放stopPlayback

于 2013-09-20T02:46:34.190 回答