|
@@ -1,510 +0,0 @@
|
|
|
-using AIPlatform.Protocol.Utilities;
|
|
|
-using Emgu.CV;
|
|
|
-using Emgu.CV.CvEnum;
|
|
|
-using System;
|
|
|
-using System.Collections.Generic;
|
|
|
-using System.IO;
|
|
|
-using System.Runtime.InteropServices;
|
|
|
-using System.Threading;
|
|
|
-using System.Windows;
|
|
|
-using System.Windows.Media;
|
|
|
-using System.Windows.Media.Imaging;
|
|
|
-using System.Windows.Threading;
|
|
|
-
|
|
|
-namespace aipdev.Managers
|
|
|
-{
|
|
|
- internal class FileVideoPlayer : IPlayer
|
|
|
- {
|
|
|
- private byte[] _videoData;
|
|
|
- private VideoCapture _videoCapture;
|
|
|
- private Mat _currentFrame;
|
|
|
- private DispatcherTimer _frameTimer;
|
|
|
- private Thread _playThread;
|
|
|
- private CancellationTokenSource _cancellationTokenSource;
|
|
|
- private bool _pausing;
|
|
|
- private int _startTickCount;
|
|
|
- private int _displayCount;
|
|
|
- private int _fps;
|
|
|
- private bool _isRepeat;
|
|
|
- private readonly object _videoReaderLocker = new object();
|
|
|
- private string _tempFilePath;//临时文件
|
|
|
- private double _frameIntervalTime;
|
|
|
- private int _frameCount;
|
|
|
- private int _frameIndex;
|
|
|
- private bool _hasPlayOnce;
|
|
|
- private List<BitmapSource> _imageList = new List<BitmapSource>();
|
|
|
- private bool _isPlaying;
|
|
|
- private bool _isPaused;
|
|
|
- private readonly object _frameLock = new object(); // 用于同步的锁对象
|
|
|
- private long _archivedImageId;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 当前帧索引
|
|
|
- /// </summary>
|
|
|
- public int FrameIndex => _frameIndex;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 视频总帧数
|
|
|
- /// </summary>
|
|
|
- public int TotalFrameCount => _frameCount;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 当前帧的时间戳
|
|
|
- /// </summary>
|
|
|
- public double FrameTime => _frameIntervalTime * _frameIndex / 1000;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 每一帧的时间间隔
|
|
|
- /// </summary>
|
|
|
- public double FrameIntervalTime => _frameIntervalTime;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 接收到图像输入的事件
|
|
|
- /// </summary>
|
|
|
- public event EventHandler<BitmapSource> InputFrameReceived;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// FPS变更的事件
|
|
|
- /// </summary>
|
|
|
- public event EventHandler<int> FPSChanged;
|
|
|
-
|
|
|
- public bool IsPaused => _pausing;
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// FileVideoPlayer构造函数
|
|
|
- /// </summary>
|
|
|
- /// <param name="videoData">视频数据</param>
|
|
|
- public FileVideoPlayer(long archivedImageId, byte[] videoData)
|
|
|
- {
|
|
|
- _archivedImageId = archivedImageId;
|
|
|
- _videoData = videoData;
|
|
|
- }
|
|
|
-
|
|
|
- private void ResetParameter()
|
|
|
- {
|
|
|
- _frameCount = 1;
|
|
|
- _frameIndex = 0;
|
|
|
- _frameIntervalTime = 0;
|
|
|
- _hasPlayOnce = false;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 播放
|
|
|
- /// </summary>
|
|
|
- /// <returns>播放成功为True,播放失败为False</returns>
|
|
|
- public bool Play(bool isRepeat = true)
|
|
|
- {
|
|
|
- _isRepeat = isRepeat;
|
|
|
- var retryCount = 0;
|
|
|
- while (_playThread != null && _playThread.IsAlive && retryCount < 3)
|
|
|
- {
|
|
|
- Thread.Sleep(50);
|
|
|
- retryCount++;
|
|
|
- }
|
|
|
- if (_playThread != null && _playThread.IsAlive)
|
|
|
- {
|
|
|
- Logger.WriteLineError($"FileVideoPlayer Play:The Play Thread Still Alive;");
|
|
|
- }
|
|
|
- ResetParameter();
|
|
|
-
|
|
|
- var result = VideoPlay(isRepeat);
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
- private void OnNewFrame(BitmapSource e)
|
|
|
- {
|
|
|
- InputFrameReceived?.Invoke(this, e);
|
|
|
- }
|
|
|
-
|
|
|
- private bool VideoPlay(bool isRepeat = true)
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- // 将视频数据写入临时文件
|
|
|
- if (!Directory.Exists(CommonConfigManager.VideoTempFolder))
|
|
|
- {
|
|
|
- Directory.CreateDirectory(CommonConfigManager.VideoTempFolder);
|
|
|
- }
|
|
|
- _tempFilePath = Path.Combine(CommonConfigManager.VideoTempFolder, $"{_archivedImageId}.tmp");
|
|
|
- using (var fileStream = new FileStream(_tempFilePath, FileMode.Create))
|
|
|
- {
|
|
|
- fileStream.Write(_videoData, 0, _videoData.Length);
|
|
|
- }
|
|
|
-
|
|
|
- // 使用临时文件路径创建 VideoCapture 对象
|
|
|
- _videoCapture = new VideoCapture(_tempFilePath);
|
|
|
- if (!_videoCapture.IsOpened)
|
|
|
- {
|
|
|
- MessageBox.Show(Application.Current.MainWindow, "无法打开视频文件", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- // 获取帧率、帧总数
|
|
|
- var frameRate = _videoCapture.Get(CapProp.Fps);
|
|
|
- _frameCount = (int)_videoCapture.Get(CapProp.FrameCount);
|
|
|
- _frameIntervalTime = (int)(1000 / frameRate);
|
|
|
-
|
|
|
- LoadVideo();
|
|
|
- StartPlayThread(isRepeat);
|
|
|
- return true;
|
|
|
- }
|
|
|
- catch (Exception ex)
|
|
|
- {
|
|
|
- MessageBox.Show(Application.Current.MainWindow, $"视频播放错误:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
- Logger.WriteLineError($"视频播放错误:{ex}");
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void DoVideoPlay(bool isRepeat, CancellationToken token)
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- int lastTickCount = Environment.TickCount;
|
|
|
- _startTickCount = -1;
|
|
|
-
|
|
|
- if (_hasPlayOnce && !isRepeat)
|
|
|
- {
|
|
|
- Stop();
|
|
|
- Play(false);
|
|
|
- return;
|
|
|
- }
|
|
|
- // 检查取消令牌是否请求了取消
|
|
|
- token.ThrowIfCancellationRequested();
|
|
|
-
|
|
|
- while (!_pausing)
|
|
|
- {
|
|
|
- // 再次检查取消令牌
|
|
|
- token.ThrowIfCancellationRequested();
|
|
|
-
|
|
|
- BitmapSource bitmap = null;
|
|
|
- lock (_videoReaderLocker)
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- bitmap = SkipToFrame(_frameIndex);
|
|
|
- }
|
|
|
- catch (Exception ex)
|
|
|
- {
|
|
|
- MessageBox.Show(Application.Current.MainWindow, $"读取帧错误:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
- Logger.WriteLineError($"读取帧错误:{ex}");
|
|
|
- }
|
|
|
- }
|
|
|
- if (bitmap == null)
|
|
|
- {
|
|
|
- break;
|
|
|
- }
|
|
|
- OnNewFrame(bitmap);
|
|
|
-
|
|
|
- if (_startTickCount == -1)
|
|
|
- {
|
|
|
- _displayCount = 0;
|
|
|
- _startTickCount = Environment.TickCount;
|
|
|
- lastTickCount = _startTickCount;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- var currentTickCount = Environment.TickCount;
|
|
|
- var fpsTimeUsed = currentTickCount - _startTickCount;
|
|
|
- _displayCount++;
|
|
|
- if (fpsTimeUsed > 1000)
|
|
|
- {
|
|
|
- var fps = (int)((double)_displayCount / fpsTimeUsed * 1000);
|
|
|
- if (_fps != fps)
|
|
|
- {
|
|
|
- _fps = fps;
|
|
|
- FPSChanged?.Invoke(this, _fps);
|
|
|
- }
|
|
|
- _startTickCount = currentTickCount;
|
|
|
- _displayCount = 0;
|
|
|
- }
|
|
|
- }
|
|
|
- var timeUsed = Environment.TickCount - lastTickCount;
|
|
|
- lastTickCount = Environment.TickCount;
|
|
|
- var waitTime = (int)_frameIntervalTime - timeUsed + 20;//+20是因为误差,如果不加20,一秒30帧的视频能1秒放45帧
|
|
|
- Thread.Sleep(waitTime < 0 ? 1 : waitTime);
|
|
|
-
|
|
|
- // 如果到尾了,就从头开始
|
|
|
- if (_frameIndex >= _frameCount - 1)
|
|
|
- {
|
|
|
- _frameIndex = 0;
|
|
|
- if (!_isRepeat)
|
|
|
- {
|
|
|
- _hasPlayOnce = true;
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- _frameIndex += 1;
|
|
|
- }
|
|
|
- }
|
|
|
- if (_pausing)
|
|
|
- {
|
|
|
- // 如果到尾了,就从头开始
|
|
|
- if (_frameIndex == 0)
|
|
|
- {
|
|
|
- _frameIndex = _frameCount - 1;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- _frameIndex -= 1;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- catch (OperationCanceledException)
|
|
|
- {
|
|
|
- // 取消操作已经被请求,正常退出
|
|
|
- Logger.WriteLineInfo("Video playback cancelled.");
|
|
|
- }
|
|
|
- catch (Exception ex)
|
|
|
- {
|
|
|
- MessageBox.Show(Application.Current.MainWindow, $"视频播放错误:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
- Logger.WriteLineError($"视频播放错误:{ex}");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void Next()
|
|
|
- {
|
|
|
- // 如果到尾了,就从头开始
|
|
|
- if (_frameIndex >= _frameCount - 1)
|
|
|
- {
|
|
|
- _frameIndex = 0;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- _frameIndex += 1;
|
|
|
- }
|
|
|
- BitmapSource bitmap = null;
|
|
|
- lock (_videoReaderLocker)
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- bitmap = SkipToFrame(_frameIndex);
|
|
|
- }
|
|
|
- catch (Exception ex)
|
|
|
- {
|
|
|
- MessageBox.Show(Application.Current.MainWindow, $"读取帧错误:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
- Logger.WriteLineError($"读取帧错误:{ex}");
|
|
|
- }
|
|
|
- }
|
|
|
- if (bitmap != null)
|
|
|
- {
|
|
|
- OnNewFrame(bitmap);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void Previous()
|
|
|
- {
|
|
|
- // 如果到尾了,就从头开始
|
|
|
- if (_frameIndex == 0)
|
|
|
- {
|
|
|
- _frameIndex = _frameCount - 1;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- _frameIndex -= 1;
|
|
|
- }
|
|
|
- BitmapSource bitmap = null;
|
|
|
- lock (_videoReaderLocker)
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- bitmap = SkipToFrame(_frameIndex);
|
|
|
- }
|
|
|
- catch (Exception ex)
|
|
|
- {
|
|
|
- MessageBox.Show(Application.Current.MainWindow, $"读取帧错误:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
- Logger.WriteLineError($"读取帧错误:{ex}");
|
|
|
- }
|
|
|
- }
|
|
|
- if (bitmap != null)
|
|
|
- {
|
|
|
- OnNewFrame(bitmap);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public void JumpToIndex(int index)
|
|
|
- {
|
|
|
- _frameIndex = index;
|
|
|
- // 如果到尾了,就从头开始
|
|
|
- if (_frameIndex >= _frameCount - 1)
|
|
|
- {
|
|
|
- _frameIndex = _frameCount - 1;
|
|
|
- }
|
|
|
- BitmapSource bitmap = null;
|
|
|
- lock (_videoReaderLocker)
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- bitmap = SkipToFrame(_frameIndex);
|
|
|
- }
|
|
|
- catch (Exception ex)
|
|
|
- {
|
|
|
- MessageBox.Show(Application.Current.MainWindow, $"读取帧错误:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
- Logger.WriteLineError($"读取帧错误:{ex}");
|
|
|
- }
|
|
|
- }
|
|
|
- if (bitmap != null)
|
|
|
- {
|
|
|
- OnNewFrame(bitmap);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 暂停播放
|
|
|
- /// </summary>
|
|
|
- public void Pause()
|
|
|
- {
|
|
|
- _pausing = true;
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 继续播放
|
|
|
- /// </summary>
|
|
|
- public void Continue()
|
|
|
- {
|
|
|
- StartPlayThread(_isRepeat);
|
|
|
- }
|
|
|
-
|
|
|
- private void StartPlayThread(bool isRepeat = true)
|
|
|
- {
|
|
|
- _pausing = false;
|
|
|
- // 创建一个新的取消令牌源
|
|
|
- _cancellationTokenSource = new CancellationTokenSource();
|
|
|
- var token = _cancellationTokenSource.Token;
|
|
|
-
|
|
|
- if (_playThread == null || !_playThread.IsAlive)
|
|
|
- {
|
|
|
- _playThread = new Thread(() => DoVideoPlay(isRepeat, token))
|
|
|
- {
|
|
|
- IsBackground = true,
|
|
|
- Name = "aiplabeler_FileVideoPlay",
|
|
|
- };
|
|
|
- _playThread.Start();
|
|
|
- //Logger.WriteLineInfo("Start Play Thread");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 停止播放
|
|
|
- /// </summary>
|
|
|
- public void Stop()
|
|
|
- {
|
|
|
- _pausing = true;
|
|
|
- lock (_videoReaderLocker)
|
|
|
- {
|
|
|
- if (_videoCapture != null)
|
|
|
- {
|
|
|
- // 释放 VideoCapture 资源
|
|
|
- _videoCapture.Release();
|
|
|
- _videoCapture.Dispose();
|
|
|
- _videoCapture = null;
|
|
|
- }
|
|
|
-
|
|
|
- // 取消线程
|
|
|
- if (_cancellationTokenSource != null)
|
|
|
- {
|
|
|
- _cancellationTokenSource.Cancel();
|
|
|
- _cancellationTokenSource = null; // 取消后,将令牌源设置为null,以便下次可以创建新的
|
|
|
- }
|
|
|
-
|
|
|
- // 删除临时文件
|
|
|
- File.Delete(_tempFilePath);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 此方法尝试将视频播放头跳转到指定帧数。注意,由于视频流的特性,无法精确跳转到指定帧数。通常需要一个缓冲区来存储一些前后的帧来平滑跳转。
|
|
|
- /// </summary>
|
|
|
- /// <param name="frameNumber"></param>
|
|
|
- /// <returns></returns>
|
|
|
- private BitmapSource SkipToFrame(int frameNumber)
|
|
|
- {
|
|
|
- return _imageList[frameNumber];
|
|
|
- }
|
|
|
-
|
|
|
- /// <summary>
|
|
|
- /// 加载视频帧,便于平滑跳转
|
|
|
- /// </summary>
|
|
|
- private void LoadVideo()
|
|
|
- {
|
|
|
- _imageList.Clear();
|
|
|
- if (_videoCapture != null && _videoCapture.IsOpened)
|
|
|
- {
|
|
|
- var frame = new Mat();
|
|
|
- // 读取一帧图像到Mat对象中
|
|
|
- while (_videoCapture.Read(frame))
|
|
|
- {
|
|
|
- // 检查Mat对象是否包含有效的图像数据
|
|
|
- if (!frame.IsEmpty)
|
|
|
- {
|
|
|
- // 这里处理帧。例如,显示帧或将帧保存为图像文件。
|
|
|
- _currentFrame = frame;
|
|
|
- var bitmap = ToBitmapSource(frame);
|
|
|
- _imageList.Add(bitmap);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private BitmapSource ToBitmapSource(Mat mat)
|
|
|
- {
|
|
|
- if (mat == null || mat.IsEmpty)
|
|
|
- {
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- int width = mat.Cols;
|
|
|
- int height = mat.Rows;
|
|
|
- int channels = mat.NumberOfChannels;
|
|
|
- int bytesPerChannel = (mat.Depth == DepthType.Cv32F) ? 4 : 1;
|
|
|
- int bytesPerPixel = channels * bytesPerChannel;
|
|
|
- int stride = width * bytesPerPixel;
|
|
|
- int bufferSize = height * stride;
|
|
|
-
|
|
|
- // 锁定Mat对象的数据并获取指针
|
|
|
- GCHandle pinnedArray = GCHandle.Alloc(mat.GetData(), GCHandleType.Pinned);
|
|
|
- IntPtr pointer = pinnedArray.AddrOfPinnedObject();
|
|
|
-
|
|
|
- try
|
|
|
- {
|
|
|
- // 创建BitmapSource
|
|
|
- PixelFormat pixelFormat;
|
|
|
- if (channels == 3)
|
|
|
- {
|
|
|
- pixelFormat = PixelFormats.Bgr24; // 对于3通道BGR图像
|
|
|
- }
|
|
|
- else if (channels == 4)
|
|
|
- {
|
|
|
- pixelFormat = PixelFormats.Bgra32; // 对于4通道BGRA图像
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- throw new NotSupportedException("Unsupported number of channels in the image.");
|
|
|
- }
|
|
|
-
|
|
|
- BitmapSource bitmapSource = BitmapSource.Create(
|
|
|
- width,
|
|
|
- height,
|
|
|
- 96,
|
|
|
- 96,
|
|
|
- pixelFormat,
|
|
|
- null,
|
|
|
- pointer,
|
|
|
- bufferSize,
|
|
|
- stride);
|
|
|
-
|
|
|
- // 防止BitmapSource被释放时非托管内存也被释放
|
|
|
- bitmapSource.Freeze();
|
|
|
-
|
|
|
- return bitmapSource;
|
|
|
- }
|
|
|
- finally
|
|
|
- {
|
|
|
- // 释放锁定的数组
|
|
|
- pinnedArray.Free();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|