ReplayKit 制作的视频在 Xamarin 中不断跳帧
Video produced by ReplayKit is constantly skipping frames in Xamarin
我有一个非常基本的视频录制项目,它在 Swift 中运行良好,但是移植到 Xamarin 空白项目中的相同代码生成的视频每隔几秒就会不断跳帧。代码从 ViewDidLoad
开始,通过 UIButton
停止这里是下面的记录代码:
RPScreenRecorder rp = RPScreenRecorder.SharedRecorder;
AVAssetWriter assetWriter;
AVAssetWriterInput videoInput;
public override void ViewDidLoad()
{
base.ViewDidLoad();
StartScreenRecording();
}
public void StartScreenRecording()
{
VideoSettings videoSettings = new VideoSettings();
NSError wError;
assetWriter = new AVAssetWriter(videoSettings.OutputUrl, AVFileType.AppleM4A, out wError);
videoInput = new AVAssetWriterInput(AVMediaType.Video, videoSettings.OutputSettings);
videoInput.ExpectsMediaDataInRealTime = true;
assetWriter.AddInput(videoInput);
if (rp.Available)
{
rp.StartCaptureAsync((buffer, sampleType, error) =>
{
if (buffer.DataIsReady)
{
if (assetWriter.Status == AVAssetWriterStatus.Unknown)
{
assetWriter.StartWriting();
assetWriter.StartSessionAtSourceTime(buffer.PresentationTimeStamp);
}
if (assetWriter.Status == AVAssetWriterStatus.Failed)
{
return;
}
if (sampleType == RPSampleBufferType.Video)
{
if (videoInput.ReadyForMoreMediaData)
{
videoInput.AppendSampleBuffer(buffer);
}
}
}
});
}
}
public void StopRecording()
{
rp.StopCapture((error) => {
if (error == null)
{
assetWriter.FinishWriting(() => { });
}
});
}
下面是 VideoSettings 文件的样子:
public class VideoSettings
{
public string VideoFilename => "render";
public string VideoFilenameExt = "mp4";
public nfloat Width { get; set; }
public nfloat Height { get; set; }
public AVVideoCodec AvCodecKey => AVVideoCodec.H264;
public NSUrl OutputUrl
{
get
{
return GetFilename(VideoFilename,VideoFilenameExt);
}
}
private NSUrl GetFilename(string filename, string extension)
{
NSError error;
var docs = new NSFileManager().GetUrl(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomain.User, null, true, out error).ToString() + filename + 1 + "." + extension;
if (error == null)
{
return new NSUrl(docs);
}
return null;
}
public AVVideoSettingsCompressed OutputSettings
{
get
{
return new AVVideoSettingsCompressed
{
Codec = AvCodecKey,
Width = Convert.ToInt32(UIScreen.MainScreen.Bounds.Size.Width),
Height = Convert.ToInt32(UIScreen.MainScreen.Bounds.Size.Height)
};
}
}
}
TL;DR:您需要一个 try {} finally {}
块来帮助 GC 及时释放您的样本缓冲区:
try {
// Do stuff with the buffer.
} finally {
buffer.Dispose ();
}
长话短说:
这是因为 GC 的速度不够快,无法意识到需要释放 CMSampleBuffer 而您 运行 样本缓冲区不足,这就是您看到延迟的原因,因为直到一个新的缓冲区可用,它拍摄了该实际帧的快照。
也不必担心调用 Dispose ()
Xamarin 会做正确的事情,因此不需要额外的检查
public void Dispose ()
{
this.Dispose (true);
GC.SuppressFinalize (this);
}
protected virtual void Dispose (bool disposing)
{
if (this.invalidate.IsAllocated) {
this.invalidate.Free ();
}
if (this.handle != IntPtr.Zero) {
CFObject.CFRelease (this.handle);
this.handle = IntPtr.Zero;
}
}
因此您的代码应如下所示:
if (rp.Available)
{
// TODO: Also note that we are not using the Async version here
rp.StartCapture((buffer, sampleType, error) =>
{
try {
if (buffer.DataIsReady) {
if (assetWriter.Status == AVAssetWriterStatus.Unknown) {
assetWriter.StartWriting ();
assetWriter.StartSessionAtSourceTime (buffer.PresentationTimeStamp);
}
if (assetWriter.Status == AVAssetWriterStatus.Failed) {
return;
}
if (sampleType == RPSampleBufferType.Video) {
if (videoInput.ReadyForMoreMediaData) {
videoInput.AppendSampleBuffer (buffer);
}
}
}
} finally {
buffer.Dispose ();
}
}, null);
}
我有一个非常基本的视频录制项目,它在 Swift 中运行良好,但是移植到 Xamarin 空白项目中的相同代码生成的视频每隔几秒就会不断跳帧。代码从 ViewDidLoad
开始,通过 UIButton
停止这里是下面的记录代码:
RPScreenRecorder rp = RPScreenRecorder.SharedRecorder;
AVAssetWriter assetWriter;
AVAssetWriterInput videoInput;
public override void ViewDidLoad()
{
base.ViewDidLoad();
StartScreenRecording();
}
public void StartScreenRecording()
{
VideoSettings videoSettings = new VideoSettings();
NSError wError;
assetWriter = new AVAssetWriter(videoSettings.OutputUrl, AVFileType.AppleM4A, out wError);
videoInput = new AVAssetWriterInput(AVMediaType.Video, videoSettings.OutputSettings);
videoInput.ExpectsMediaDataInRealTime = true;
assetWriter.AddInput(videoInput);
if (rp.Available)
{
rp.StartCaptureAsync((buffer, sampleType, error) =>
{
if (buffer.DataIsReady)
{
if (assetWriter.Status == AVAssetWriterStatus.Unknown)
{
assetWriter.StartWriting();
assetWriter.StartSessionAtSourceTime(buffer.PresentationTimeStamp);
}
if (assetWriter.Status == AVAssetWriterStatus.Failed)
{
return;
}
if (sampleType == RPSampleBufferType.Video)
{
if (videoInput.ReadyForMoreMediaData)
{
videoInput.AppendSampleBuffer(buffer);
}
}
}
});
}
}
public void StopRecording()
{
rp.StopCapture((error) => {
if (error == null)
{
assetWriter.FinishWriting(() => { });
}
});
}
下面是 VideoSettings 文件的样子:
public class VideoSettings
{
public string VideoFilename => "render";
public string VideoFilenameExt = "mp4";
public nfloat Width { get; set; }
public nfloat Height { get; set; }
public AVVideoCodec AvCodecKey => AVVideoCodec.H264;
public NSUrl OutputUrl
{
get
{
return GetFilename(VideoFilename,VideoFilenameExt);
}
}
private NSUrl GetFilename(string filename, string extension)
{
NSError error;
var docs = new NSFileManager().GetUrl(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomain.User, null, true, out error).ToString() + filename + 1 + "." + extension;
if (error == null)
{
return new NSUrl(docs);
}
return null;
}
public AVVideoSettingsCompressed OutputSettings
{
get
{
return new AVVideoSettingsCompressed
{
Codec = AvCodecKey,
Width = Convert.ToInt32(UIScreen.MainScreen.Bounds.Size.Width),
Height = Convert.ToInt32(UIScreen.MainScreen.Bounds.Size.Height)
};
}
}
}
TL;DR:您需要一个 try {} finally {}
块来帮助 GC 及时释放您的样本缓冲区:
try {
// Do stuff with the buffer.
} finally {
buffer.Dispose ();
}
长话短说:
这是因为 GC 的速度不够快,无法意识到需要释放 CMSampleBuffer 而您 运行 样本缓冲区不足,这就是您看到延迟的原因,因为直到一个新的缓冲区可用,它拍摄了该实际帧的快照。
也不必担心调用 Dispose ()
Xamarin 会做正确的事情,因此不需要额外的检查
public void Dispose ()
{
this.Dispose (true);
GC.SuppressFinalize (this);
}
protected virtual void Dispose (bool disposing)
{
if (this.invalidate.IsAllocated) {
this.invalidate.Free ();
}
if (this.handle != IntPtr.Zero) {
CFObject.CFRelease (this.handle);
this.handle = IntPtr.Zero;
}
}
因此您的代码应如下所示:
if (rp.Available)
{
// TODO: Also note that we are not using the Async version here
rp.StartCapture((buffer, sampleType, error) =>
{
try {
if (buffer.DataIsReady) {
if (assetWriter.Status == AVAssetWriterStatus.Unknown) {
assetWriter.StartWriting ();
assetWriter.StartSessionAtSourceTime (buffer.PresentationTimeStamp);
}
if (assetWriter.Status == AVAssetWriterStatus.Failed) {
return;
}
if (sampleType == RPSampleBufferType.Video) {
if (videoInput.ReadyForMoreMediaData) {
videoInput.AppendSampleBuffer (buffer);
}
}
}
} finally {
buffer.Dispose ();
}
}, null);
}