You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xamarin Android中VideoRecordingActivity遇Invalid Preview Surface错误的解决办法

Fixing "Invalid Preview Surface" Error in Xamarin.Android Video Recording

Hey there! That "Invalid Preview Surface" error is usually popping up because you're trying to attach the camera preview to a Surface that hasn't fully initialized yet. Let's walk through how to fix this step by step.

Why This Happens

In your current code, you're calling PrepareVideoRecorder() right in OnCreate(). But at that point, the VideoView's SurfaceHolder hasn't finished creating the underlying surface—so when you try to set it as the preview display for MediaRecorder, it's still invalid.

Step-by-Step Fixes

1. Use SurfaceHolder Callback to Wait for Surface Readiness

You need to hook into the SurfaceHolder's lifecycle events to make sure you only initialize the camera and recorder after the surface is fully created.

2. Revised Activity Code

Here's the updated version of your VideoRecordingActivity.cs with the necessary fixes:

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);
    SetContentView(Resource.Layout.Main);
    videoPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath + "/myVideo.mp4";
    btnRecord = FindViewById<Button>(Resource.Id.btnRecord);
    imgBtnCancel = FindViewById<Button>(Resource.Id.btnCancel);
    imgBtnOk = FindViewById<Button>(Resource.Id.btnConfirm);
    videoView = FindViewById<VideoView>(Resource.Id.SampleVideoView);

    // Set up SurfaceHolder callback to monitor surface lifecycle
    var surfaceHolder = videoView.Holder;
    surfaceHolder.AddCallback(new SurfaceCallback(this));

    btnRecord.Click += delegate {
        if (!hasRecordClick) {
            if (_recordOk && _mediaRecorder != null) {
                _mediaRecorder.Start();
                hasRecordClick = true;
            }
        } else {
            if (_mediaRecorder != null) {
                try {
                    _mediaRecorder.Stop();
                } catch (Exception ex) {
                    // Handle cases where recording didn't start properly
                    Console.WriteLine("Stop failed: {0}", ex);
                }
                ReleaseMediaRecorder();
                camera?.Lock();
                btnRecord.Visibility = ViewStates.Gone;
                imgBtnOk.Visibility = ViewStates.Visible;
                imgBtnCancel.Visibility = ViewStates.Visible;
            }
        }
    };
}

bool PrepareVideoRecorder()
{
    try {
        camera = Camera.Open();
        _mediaRecorder = new MediaRecorder();
        
        camera.Unlock();
        _mediaRecorder.SetCamera(camera);

        // Use explicit formats/encoders for better device compatibility
        _mediaRecorder.SetVideoSource(VideoSource.Camera);
        _mediaRecorder.SetAudioSource(AudioSource.Camcorder);
        _mediaRecorder.SetOutputFormat(OutputFormat.Mpeg4);
        _mediaRecorder.SetVideoEncoder(VideoEncoder.H264);
        _mediaRecorder.SetAudioEncoder(AudioEncoder.Aac);

        _mediaRecorder.SetOutputFile(GetOutputMediaFilePath());

        // Set optimal preview size matching the surface dimensions
        var parameters = camera.GetParameters();
        var previewSize = GetOptimalPreviewSize(parameters.SupportedPreviewSizes, videoView.Width, videoView.Height);
        parameters.SetPreviewSize(previewSize.Width, previewSize.Height);
        camera.SetParameters(parameters);
        
        // Now the surface is ready, so set preview display safely
        _mediaRecorder.SetPreviewDisplay(videoView.Holder.Surface);

        _mediaRecorder.Prepare();
        camera.StartPreview(); // Start camera preview immediately
        return true;
    } catch (Exception ex) {
        Console.WriteLine("Prepare failed: {0}", ex);
        ReleaseMediaRecorder();
        return false;
    }
}

// Helper to find the best preview size for the surface
private Camera.Size GetOptimalPreviewSize(IList<Camera.Size> sizes, int w, int h)
{
    const double AspectTolerance = 0.1;
    double targetRatio = (double)w / h;

    if (sizes == null) return null;

    Camera.Size optimalSize = null;
    double minDiff = double.MaxValue;

    int targetHeight = h;

    foreach (Camera.Size size in sizes)
    {
        double ratio = (double)size.Width / size.Height;
        if (Math.Abs(ratio - targetRatio) > AspectTolerance) continue;
        if (Math.Abs(size.Height - targetHeight) < minDiff)
        {
            optimalSize = size;
            minDiff = Math.Abs(size.Height - targetHeight);
        }
    }

    // Fallback to closest size if no matching aspect ratio
    if (optimalSize == null)
    {
        minDiff = double.MaxValue;
        foreach (Camera.Size size in sizes)
        {
            if (Math.Abs(size.Height - targetHeight) < minDiff)
            {
                optimalSize = size;
                minDiff = Math.Abs(size.Height - targetHeight);
            }
        }
    }

    return optimalSize;
}

private void ReleaseMediaRecorder()
{
    _mediaRecorder?.Release();
    _mediaRecorder = null;
    camera?.Unlock();
}

// Callback class to handle surface lifecycle events
private class SurfaceCallback : Java.Lang.Object, ISurfaceHolderCallback
{
    private readonly VideoRecordingActivity _activity;

    public SurfaceCallback(VideoRecordingActivity activity)
    {
        _activity = activity;
    }

    public void SurfaceCreated(ISurfaceHolder holder)
    {
        // Initialize recorder only after the surface is fully created
        _activity._recordOk = _activity.PrepareVideoRecorder();
    }

    public void SurfaceChanged(ISurfaceHolder holder, [GeneratedEnum] Format format, int width, int height)
    {
        // Adjust preview size if surface dimensions change
        if (_activity.camera != null)
        {
            var parameters = _activity.camera.GetParameters();
            var previewSize = _activity.GetOptimalPreviewSize(parameters.SupportedPreviewSizes, width, height);
            parameters.SetPreviewSize(previewSize.Width, previewSize.Height);
            _activity.camera.SetParameters(parameters);
            _activity.camera.StartPreview();
        }
    }

    public void SurfaceDestroyed(ISurfaceHolder holder)
    {
        // Clean up resources when the surface is destroyed
        _activity.ReleaseMediaRecorder();
        _activity.camera?.Release();
        _activity.camera = null;
    }
}

Key Changes Explained

  • SurfaceHolder Callback: We wait for SurfaceCreated before initializing the recorder—this guarantees the surface is valid when setting the preview display.
  • Explicit Encoder/Format: Using specific values instead of Default avoids device-specific inconsistencies.
  • Optimal Preview Size: Matching the camera's preview size to the surface dimensions prevents stretching and additional compatibility issues.
  • Safer Resource Handling: Added null checks and error handling for MediaRecorder.Stop() to avoid crashes if recording never started.

Additional Tips

  • Ensure you've declared all required permissions (CAMERA, RECORD_AUDIO, WRITE_EXTERNAL_STORAGE) in your AndroidManifest.xml and handled runtime permissions for API 23+.
  • Test on multiple devices—camera behavior can vary across manufacturers.

内容的提问来源于stack exchange,提问作者apex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:38:28