У меня есть метод, который обновляет содержимое WriteableBitmap (FrameDataNew), который является свойством в моей модели просмотра (VM):
public WriteableBitmap FrameDataNew
{
get { return frameDataNew; }
set
{
frameDataNew = value;
OnProptertyChanged("FrameDataNew");
}
}
private WriteableBitmap frameDataNew = null;
Когда я получаю новое растровое изображение из написанного мной класса gstreamer, я обновляю FrameDataNew, чтобы отобразить на экране последний кадр. окно представляет собой простой элемент управления Image, источник которого привязан к FrameDataNew.
Следующий код отлично подходит для этого в моем обработчике событий:
/// <summary>
/// BitmapCaptured event handler for when a new video frame is received from the IP source
/// </summary>
/// <param name = "sender">The originating source of the event</param>
/// <param name = "NewBitmap">The new frame in Bitmap form</param>
private void PipeLine_BitmapCaptured(object sender, Bitmap NewBitmap)
{
// render to the screen
Dispatcher.Invoke(() =>
{
// check the existence of the WriteableBitmap and also the dimensions, create a new one if required
if ((VM.FrameDataNew == null) || (VM.FrameDataNew.Width != NewBitmap.Width) || (VM.FrameDataNew.Height != NewBitmap.Height))
VM.FrameDataNew = new WriteableBitmap(NewBitmap.Width, NewBitmap.Height, NewBitmap.HorizontalResolution, NewBitmap.VerticalResolution, PixelFormats.Bgr24, null);
// lock the bitmap data so we can use it
BitmapData data = NewBitmap.LockBits(new Rectangle(0, 0, NewBitmap.Width, NewBitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
// lock the backbuffer of the WritebaleBitmap so we can modify it
VM.FrameDataNew.Lock();
// Copy the bitmap's data directly to the on-screen buffers
CopyMemory(VM.FrameDataNew.BackBuffer, data.Scan0, (data.Stride * data.Height));
// Moves the back buffer to the front.
VM.FrameDataNew.AddDirtyRect(new Int32Rect(0, 0, data.Width, data.Height));
// unlock the back buffer again
VM.FrameDataNew.Unlock();
//
//NewBitmap.UnlockBits(data);
});
}
[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory")]
public static extern void CopyMemory(IntPtr Destination, IntPtr Source, int Length);
Теперь я хочу обновить свою программу для обработки нескольких конвейеров и WriteableBitmaps, чтобы я мог показывать несколько видеопотоков. Первое, что я сделал, - это создал статический класс утилит, чтобы я мог передать новое растровое изображение (NewBitmap) и WriteableBitmap (VM.FrameDataNew), которое я хочу обновить. Затем я вызвал его по мере необходимости, когда прибыл новый кадр:
Класс утилит (только код, связанный с моим вопросом):
public static class Utils
{
/// <summary>
/// Inject the source Bitmap into the Destination WriteableBitmap
/// </summary>
/// <param name = "Src">The source Bitmap</param>
/// <param name = "Dest">The destination WriteableBitmap</param>
public static void InjectBitmap(Bitmap Src, WriteableBitmap Dest)
{
if ((Dest == null) || (Dest.Width != Src.Width) || (Dest.Height != Src.Height))
Dest = new WriteableBitmap(Src.Width, Src.Height, Src.HorizontalResolution, Src.VerticalResolution, PixelFormats.Bgr24, null);
BitmapData data = Src.LockBits(new Rectangle(0, 0, Src.Width, Src.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
Dest.Lock();
// Copy the bitmap's data directly to the on-screen buffers
CopyMemory(Dest.BackBuffer, data.Scan0, (data.Stride * data.Height));
// Moves the back buffer to the front.
Dest.AddDirtyRect(new Int32Rect(0, 0, data.Width, data.Height));
Dest.Unlock();
//
Src.UnlockBits(data);
}
[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory")]
public static extern void CopyMemory(IntPtr Destination, IntPtr Source, int Length);
}
... и как я это называю:
/// <summary>
/// BitmapCaptured event handler for when a new video frame is received from the IP source
/// </summary>
/// <param name = "sender">The originating source of the event</param>
/// <param name = "NewBitmap">The new frame in Bitmap form</param>
private void PipeLine_BitmapCaptured(object sender, Bitmap NewBitmap)
{
// render to the screen
Dispatcher.Invoke(() =>
{
Utils.InjectBitmap(NewBitmap, VM.FrameDataNew);
});
}
Изображение больше не появляется на экране. Пошаговое выполнение кода выглядит так, как будто каждый раз, когда InjectBitmap () вызывается, значение WriteableBitmap равно нулю? Код создает новый в первом операторе if, но VM.FrameDataNew остается нулевым?
Я определенно нахожусь на грани своего опыта с этим, поэтому любая помощь будет очень признательна.
Обновлено: цель состоит в том, чтобы иметь наблюдаемую коллекцию WriteableBitmaps, чтобы я мог эффективно обрабатывать некоторые из них.
Причина проста: вы не назначаете вновь созданный объект свойству FrameDataNew
, поэтому его значение остается null
.
Не забывайте, что в C# экземпляры ссылочного типа передаются по ссылке.
Итак, в своем методе вы делаете следующее:
void InjectBitmap(Bitmap Src, WriteableBitmap Dest)
{
if (Dest == null)
Dest = new WriteableBitmap(/* ... */);
}
Но Dest
- это просто локальная переменная внутри вашего метода - аргументы метода можно рассматривать как локальные переменные метода.
Таким образом, вы назначаете вновь созданный экземпляр локальной переменной, которая, конечно же, будет потеряна при возврате метода.
Здесь вам нужен параметр ref
:
void InjectBitmap(Bitmap src, ref WriteableBitmap dest)
{
if (dest == null)
dest = new WriteableBitmap(/* ... */);
}
Обратите внимание, что я изменил регистр имени параметра, чтобы он соответствовал руководству по стилю C#.
Однако это не сработает для свойств, поэтому InjectBitmap(NewBitmap, VM.FrameDataNew)
не будет компилироваться, если FrameDataNew
является свойством.
Вы можете преобразовать FrameDataNew
в поле, но иметь непубличное изменяемое поле - плохая идея.
Вы можете использовать временную локальную переменную, но это как-то некрасиво:
var copy = VM.FrameDataNew;
InjectBitmap(NewBitmap, ref copy);
if (copy != VM.FrameDataNew)
VM.FrameDataNew = copy;
Я бы переосмыслил ваш дизайн, чтобы вам не понадобилось все это.
Между прочим, наличие нескольких вызовов Dispatcher.Invoke
в многопоточной среде (особенно если вызываемые методы медленные, а ваши медленные) снизит производительность вашего приложения и сделает пользовательский интерфейс невосприимчивым.
Спасибо за ваши комментарии, я думаю, на вопрос дан ответ :) Я все равно начал продвигать проект, и вместо WriteableBitmap я передаю ObservableCollection <WriteableBitmap> и номер канала, он отлично работал для этого подхода, поэтому я, вероятно, получил бы до этой стадии без необходимости задавать ироничный вопрос. Что касается производительности, я буду следить за отзывчивостью, когда у меня будет количество каналов, которое я хочу, и адресовать это там. Еще раз спасибо.
Вы реализовали BitmapCaptured самостоятельно, используя собственный приемник видео? Не могли бы вы опубликовать код? Спасибо!