Xamarin Android中VideoRecordingActivity遇Invalid Preview Surface错误的解决办法
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
SurfaceCreatedbefore initializing the recorder—this guarantees the surface is valid when setting the preview display. - Explicit Encoder/Format: Using specific values instead of
Defaultavoids 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 yourAndroidManifest.xmland handled runtime permissions for API 23+. - Test on multiple devices—camera behavior can vary across manufacturers.
内容的提问来源于stack exchange,提问作者apex

