2023년 11월 24일 금요일

Rendering Video in Avalonia's NativeControlHost with SDL2.

 Add a NativeEmbeddingControl class that inherits from NativeControlHost in MainWindow.axaml.cs

 namespace AvaloniaDXPlayer  
 {  
   public partial class MainWindow : Window  
   {  
     public MainWindow()  
     {  
       InitializeComponent();  
     }  
     ...  
   public class NativeEmbeddingControl : NativeControlHost  
   {  
     public IntPtr Handle { get; private set; }  
   
     protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)  
     {  
       var handle = base.CreateNativeControlCore(parent);  
       Handle = handle.Handle;  
       Console.WriteLine($"Handle : {Handle}");  
       return handle;  
     }  
   }     
 }  


Add a NativeEmbeddingControl class that inherits from NativeControlHost in MainWindow.axaml.cs

 <Window xmlns="https://github.com/avaloniaui"  
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
     xmlns:nec="clr-namespace:AvaloniaDXPlayer"  
     mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"  
     x:Class="AvaloniaDXPlayer.MainWindow"  
     Width="800" Height="600" Title="AvaloniaDXPlayer">  
  <Grid>  
   <Grid.RowDefinitions>  
    <RowDefinition Height="*"/>  
    <RowDefinition Height="100"/>  
   </Grid.RowDefinitions>  
   <Grid Grid.Row="0" Background="Black">  
    <nec:NativeEmbeddingControl x:Name="host" Grid.Row="0" SizeChanged="Host_SizeChanged" />  
   </Grid>  
   <Canvas Grid.Row="1">  
    <TextBox x:Name="textURL" Margin="10" Width="300" Height="20" FontSize="14" Text=""/>  
    <Button x:Name="btnConnect" Content="Connect" Margin="320,10,0,0" />  
    <Button x:Name="btnClose" Content="Close" Margin="400,10,0,0" />  
    <Button x:Name="btnTest" Content="Test" Margin="460,10,0,0" />  
   </Canvas>  
  </Grid>  
 </Window>  
   


Initialize SDL2. During this, insert the Handle of NativeEmbeddingControl as an argument into SDL_CreateWindowFrom

 
#if defined(LINUX)
typedef void*              HWND;  
#endif
 ...  
 int VideoSDLDraw::init(HWND hwnd, int targetWidth, int targetHeight, int srcWidth, int srcHeight)  
 {  
      release();  
   
      DXPRINTF("VideoSDLDraw::init %dx%d, %dx%d\n", targetWidth, targetHeight, srcWidth, srcHeight);  
   
      MUTEX_LOCK(&m_mutex);  
   
      Uint32 pixelFormat = SDL_PIXELFORMAT_UNKNOWN;  
   
      m_pWindow = SDL_CreateWindowFrom(hwnd);  
      if (m_pWindow == NULL) {  
           DXPRINTF("Window could not be created! SDL Error: %s\n", SDL_GetError());  
           goto fail;  
      }  
   
      m_pRenderer = SDL_CreateRenderer(m_pWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);  
      if (m_pRenderer == NULL) {  
           DXPRINTF("Renderer could not be created! SDL Error: %s\n", SDL_GetError());  
           m_nTextureWidth = m_nTextureHeight = 0;  
           goto fail;  
      }  
      else {  
           //Initialize renderer color  
           SDL_SetRenderDrawColor(m_pRenderer, 0x00, 0x00, 0x00, 0xFF);  
           for (int i = 0; i < m_nTextDraw; i++) m_pTextDraw[i]->setSDLRenderer(m_pRenderer);  
      }  
 #if defined(LINUX)  
      if (targetWidth == 0 && targetHeight == 0) {  
           SDL_GetWindowSize(m_pWindow, &targetWidth, &targetHeight);  
      }  
 #endif  
      pixelFormat = SDL_GetWindowPixelFormat(m_pWindow);  
   
      m_pTexture = SDL_CreateTexture(m_pRenderer, pixelFormat, SDL_TEXTUREACCESS_STREAMING, targetWidth, targetHeight);  
      if (m_pTexture == NULL) {  
           DXPRINTF("Unable to create streamable texture! SDL Error: %s\n", SDL_GetError());  
           m_nTextureWidth = m_nTextureHeight = 0;  
           goto fail;  
      }  
   
      m_nTextureWidth = targetWidth;  
      m_nTextureHeight = targetHeight;  
   
      SetRectEmpty(&m_rectTargetLast);  
   
      SDL_RendererInfo rendererInfo;  
      SDL_GetRendererInfo(m_pRenderer, &rendererInfo);  
   
      DXPRINTF("SDL Renderer : %s\n", rendererInfo.name);  
   
      MUTEX_UNLOCK(&m_mutex);  
      return 0;  
   
 fail:  
      MUTEX_UNLOCK(&m_mutex);  
      return -1;  
 }  

After initialization as described above, render the decoded video data on m_pTexture with SDL2 rendering function. 
The result is as shown in the figure below.

Linux







Windows





2023년 11월 6일 월요일

2가지 예제를 통한 간단한 WPF 바인딩 원리


바인딩의 기본 개념은 위 그림과 같다. 
바인딩 타겟은 항상 DependencyProperty 여야하고 바인딩 소스는 DependencyProperty 나 일반 프로퍼티(.NET 프로퍼티)나 상관없다.
여기서

OneWay 바인딩은 소스 -> 타겟 
OneWayToSource 바인딩은 소스 <- 타겟
TwoWay 바인딩은 소스 <-> 타겟

으로 바인딩 된다.


1. TextBox 바인딩

화면을 위아래로 나눠서 상단에 텍스트 박스를 하단에 버튼 1개을 나타낸 간단한 UI를 만든다.

 <Window x:Class="TextBoxBind.MainWindow"  
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
     xmlns:local="clr-namespace:TextBoxBind"  
     mc:Ignorable="d"  
     Title="MainWindow" Height="450" Width="800">  
   <Grid>  
     <Grid.RowDefinitions>  
       <RowDefinition Height="50*"/>  
       <RowDefinition Height="50*"/>  
     </Grid.RowDefinitions>  
     <TextBox Grid.Row="0" Text="{Binding InputText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />  
     <Button x:Name="button" Content="Add Text" HorizontalAlignment="Left" Margin="42,29,0,0" Grid.Row="1" VerticalAlignment="Top" Click="button_Click"/>  
   </Grid>  
 </Window>  

위 xaml 코드에서 TextBox는 InputText 프로퍼티와 바인딩을 하며 Mode는 TwoWay, UpdateSourceTrigger는 PropertyChanged 이다.
즉 양방향 바인딩이고 데이터 변환시 즉시 반영되는 바인딩이다.
code behind 는 아래와 같다.

 using System;  
 using System.Collections.Generic;  
 using System.ComponentModel;  
 using System.Linq;  
 using System.Runtime.CompilerServices;  
 using System.Text;  
 using System.Threading;  
 using System.Threading.Tasks;  
 using System.Windows;  
 using System.Windows.Controls;  
 using System.Windows.Data;  
 using System.Windows.Documents;  
 using System.Windows.Input;  
 using System.Windows.Media;  
 using System.Windows.Media.Imaging;  
 using System.Windows.Navigation;  
 using System.Windows.Shapes;  
   
 namespace TextBoxBind  
 {  
   /// <summary>  
   /// MainWindow.xaml에 대한 상호 작용 논리  
   /// </summary>  
   public partial class MainWindow : Window, INotifyPropertyChanged  
   {  
     private string inputText;  
   
     public string InputText  
     {  
       get { return inputText; }  
       set  
       {  
         if (inputText != value)  
         {  
           Console.WriteLine($"InputText : {value}");  
           inputText = value;  
           OnPropertyChanged("InputText");
         }  
       }  
     }  
   
     public MainWindow()  
     {  
       InitializeComponent();  
       DataContext = this;  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   
     private void button_Click(object sender, RoutedEventArgs e)  
     {  
       Thread thread = new Thread(new ThreadStart(() => InputText += "AAA"));  
       thread.IsBackground = true;  
       thread.Start();  
     }  
   }  
 }  
   

Add Text 버튼클릭시 쓰레드로 InputText 프로퍼티의 값을 추가한다.
양방향 바인딩이므로 TextBox에 문자를 입력하면 InputText 값이 바뀌고 버튼을 클릭하면 TextBox 출력문자가 변경된다.('AAA'추가)

여기서 InputText의 setter 부분에 있는 OnPropertyChanged("InputText") 
를 주석처리하면 버튼을 클릭해도 TextBox에 수정내용 즉 InputText 값이 반영되지 않는다.

이유는 바인딩을 하는 순간 TextBox는 
public event PropertyChangedEventHandler PropertyChanged; 
델러게이트 이벤트 핸들러를 구독하게되고 이벤트 발생시 InputText의 getter를 통해 현재 값을 가져와서 UI에 반영(Text 프로퍼티에)하기 때문이다.

Mode=OneWayToSource 를 하면 바인딩 타겟(TextBox) -> 바인딩 소스(InputText) 로만 데이터 전달이 되는데 이 경우는 TextBox가 바로 InputText의 setter 를 호출하게 되는데 이때는 OnPropertyChanged("InputText") 호출이 필요없다.(다른 이유에서 필요할 수 있으므로 호출하는것이 좋다)


2. DataGrid 바인딩

화면을 좌우로 나눠서 왼쪽에 DataGrid 를 오른쪽에 버튼과 기타 텍스트박스 컨트롤을 배치했다.

 <Window x:Class="ListBindTest.MainWindow"  
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
     xmlns:local="clr-namespace:ListBindTest"  
     mc:Ignorable="d"  
     Title="MainWindow" Height="450" Width="800">  
   <Grid>  
     <Grid.ColumnDefinitions>  
       <ColumnDefinition Width="*"/>  
       <ColumnDefinition Width="*"/>  
     </Grid.ColumnDefinitions>  
     <DataGrid Grid.Column="0" ItemsSource="{Binding People}" SelectedItem="{Binding SelectedItem}" SelectionMode="Single" />  
     <Canvas Grid.Column="1">  
       <TextBlock x:Name="textBlock" Canvas.Left="41" TextWrapping="Wrap" Text="Name" Canvas.Top="35"/>  
       <TextBlock x:Name="textBlock_Copy" Canvas.Left="41" TextWrapping="Wrap" Text="Age" Canvas.Top="87" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       <TextBox x:Name="textName" Canvas.Left="101" Text="{Binding SelectedItem.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="35" Width="120"/>  
       <TextBox x:Name="textAge" Canvas.Left="101" Text="{Binding SelectedItem.Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="87" Width="120" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       <TextBlock x:Name="textBlock_Copy1" Canvas.Left="41" TextWrapping="Wrap" Text="Name" Canvas.Top="183" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       <TextBlock x:Name="textBlock_Copy2" Canvas.Left="41" TextWrapping="Wrap" Text="Age" Canvas.Top="235" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       <Canvas x:Name="canvasTest">  
         <TextBox x:Name="textName_Copy" Canvas.Left="101" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="183" Width="120" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
         <TextBox x:Name="textAge_Copy" Canvas.Left="101" Text="{Binding Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="235" Width="120" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       </Canvas>  
       <Button x:Name="button" Content="button" Canvas.Left="22" Canvas.Top="297" Click="button_Click"/>  
       <Button x:Name="button1" Content="button1" Canvas.Left="78" Canvas.Top="297" Click="button1_Click"/>  
     </Canvas>  
   </Grid>  
 </Window>     

code behind 는 아래와 같다.
 using System;  
 using System.Collections.Generic;  
 using System.Collections.ObjectModel;  
 using System.ComponentModel;  
 using System.Linq;  
 using System.Runtime.InteropServices;  
 using System.Text;  
 using System.Threading;  
 using System.Threading.Tasks;  
 using System.Windows;  
 using System.Windows.Controls;  
 using System.Windows.Data;  
 using System.Windows.Documents;  
 using System.Windows.Input;  
 using System.Windows.Media;  
 using System.Windows.Media.Imaging;  
 using System.Windows.Navigation;  
 using System.Windows.Shapes;  
   
 namespace ListBindTest  
 {  
   /// <summary>  
   /// MainWindow.xaml에 대한 상호 작용 논리  
   /// </summary>  
   public partial class MainWindow : Window  
   {  
     private MainViewModel viewModel = new MainViewModel();  
     private Person personTest = new Person();  
   
     public MainWindow()  
     {  
       InitializeComponent();  
   
       DataContext = viewModel;  
   
       personTest.Name = "Test";  
       personTest.Age = 100;  
       canvasTest.DataContext = personTest;  
     }  
   
     private void button_Click(object sender, RoutedEventArgs e)  
     {  
       Person p = new Person();  
       p.Name = personTest.Name;  
       p.Age = personTest.Age;  
   
       Thread thread = new Thread(new ThreadStart(() =>  
       {  
         this.Dispatcher.BeginInvoke(new Action(() =>  
         {  
           viewModel.People.Add(p);
         }));  
       }));  
       thread.IsBackground = true;  
       thread.Start();  
     }  
   
     private void button1_Click(object sender, RoutedEventArgs e)  
     {  
       Thread thread = new Thread(new ThreadStart(() =>  
       {  
         Person p = viewModel.SelectedItem;  
         if (p != null) p.Age++;  
       }));  
       thread.IsBackground = true;  
       thread.Start();  
     }  
   }  
   
  public class Person : INotifyPropertyChanged  
   {  
     private string _name;  
     private int _age;  
   
     public string Name  
     {  
       get { return _name; }  
       set  
       {  
         _name = value;  
         OnPropertyChanged(nameof(Name));  
       }  
     }  
   
     public int Age  
     {  
       get { return _age; }  
       set  
       {  
         _age = value;  
         OnPropertyChanged(nameof(Age));  
       }  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged(string propertyName)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   }  
   
   public class MainViewModel : INotifyPropertyChanged  
   {  
     private ObservableCollection<Person> _people;  
     private Person _selectedItem;  
   
     public MainViewModel()  
     {  
       _people = new ObservableCollection<Person>();  
       _people.Add(new Person { Name = "Alice", Age = 25 });  
       _people.Add(new Person { Name = "Bob", Age = 30 });  
       _people.Add(new Person { Name = "Charlie", Age = 35 });  
     }  
   
     public ObservableCollection<Person> People  
     {  
       get { return _people; }  
       set  
       {  
         _people = value;  
         OnPropertyChanged(nameof(People));  
       }  
     }  
   
     public Person SelectedItem  
     {  
       get { return _selectedItem; }  
       set  
       {  
         _selectedItem = value;  
         OnPropertyChanged(nameof(SelectedItem));  
       }  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged(string propertyName)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   }  
 }  
   

button을 클릭하면 DataGrid 에 아이템이 추가되는데 여기서 주의해야할 점은 바인딩된 ObservableCollection 에 아이템을 추가할때 반드시 UI 쓰레드에서 추가해야한다는 점이다.

UI 쓰레드로 추가하지 않으려면 BindingOperations.EnableCollectionSynchronization 메소드를 사용하는 방법이 있다.

 namespace ListBindTest  
 {  
   /// <summary>  
   /// MainWindow.xaml에 대한 상호 작용 논리  
   /// </summary>  
   public partial class MainWindow : Window  
   {  
     private MainViewModel viewModel = new MainViewModel();  
     private Person personTest = new Person();  
   
     public MainWindow()  
     {  
       InitializeComponent();  
   
       DataContext = viewModel;  
   
       personTest.Name = "Test";  
       personTest.Age = 100;  
       canvasTest.DataContext = personTest;  
     }  
   
     private void button_Click(object sender, RoutedEventArgs e)  
     {  
       Person p = new Person();  
       p.Name = personTest.Name;  
       p.Age = personTest.Age;  
   
       Thread thread = new Thread(new ThreadStart(() =>  
       {  
 #if false  
         this.Dispatcher.BeginInvoke(new Action(() =>  
         {  
           viewModel.People.Add(p);  
           //viewModel.People.Add(personTest);  
         }));  
 #else  
         viewModel.People.Add(p);  
 #endif  
       }));  
       thread.IsBackground = true;  
       thread.Start();        
     }  
   
     private void button1_Click(object sender, RoutedEventArgs e)  
     {  
       Thread thread = new Thread(new ThreadStart(() =>  
       {  
         //personTest.Name = personTest.Name + "X";  
         Person p = viewModel.SelectedItem;  
         if (p != null) p.Age++;  
       }));  
       thread.IsBackground = true;  
       thread.Start();  
     }  
   }  
   
  public class Person : INotifyPropertyChanged  
   {  
     private string _name;  
     private int _age;  
   
     public string Name  
     {  
       get { return _name; }  
       set  
       {  
         _name = value;  
         OnPropertyChanged(nameof(Name));  
       }  
     }  
   
     public int Age  
     {  
       get { return _age; }  
       set  
       {  
         _age = value;  
         OnPropertyChanged(nameof(Age));  
       }  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged(string propertyName)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   }  
   
   public class MainViewModel : INotifyPropertyChanged  
   {  
     private ObservableCollection<Person> _people;  
     private Person _selectedItem;  
   
     private object objLock = new object();  
   
     public MainViewModel()  
     {  
       _people = new ObservableCollection<Person>();  
       _people.Add(new Person { Name = "Alice", Age = 25 });  
       _people.Add(new Person { Name = "Bob", Age = 30 });  
       _people.Add(new Person { Name = "Charlie", Age = 35 });  
   
       BindingOperations.EnableCollectionSynchronization(_people, objLock);  
     }  
   
     public ObservableCollection<Person> People  
     {  
       get { return _people; }  
       set  
       {  
         _people = value;  
         OnPropertyChanged(nameof(People));  
       }  
     }  
   
     public Person SelectedItem  
     {  
       get { return _selectedItem; }  
       set  
       {  
         _selectedItem = value;  
         OnPropertyChanged(nameof(SelectedItem));  
       }  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged(string propertyName)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   }  
 }  

그리고 button1을 클릭하면 Age 값이 1증가하는데 이것은 Person 클래스가 INotifyPropertyChanged 를 구현해서 OnPropertyChanged(nameof(Age)) 를 호출하여 역시 1번 예제에서 설명한데로 public event PropertyChangedEventHandler PropertyChanged 를 구독중인 DataGrid 에게 변경여부를 알려주기 때문이다.

2023년 11월 1일 수요일

간단한 CA(Certificate Authority) 인증서 검증 원리

 https 등에서 인증서를 검증하는 원리에 대해 이해하려면 공개키/비밀키(비대칭키) 암호화 원리에 대해 간략하게 이해를 해야한다.

먼저 전자서명과 동작원리는 아래 그림을 보면 간단하게 이해할 수 있다.




위 그림에서 기존의 비대칭키 암호화 과정과 다른점은 암호화-복호화 과정이 공개키-비밀키가 아닌 비밀키-공개키가 사용된다는 것이다. 
이것은 평문을 복호화하는것이 아닌 평문의 지문(해쉬값)을 복호화하기 때문이다.(설명이 조금 부족한데 그림으로 이해하도록)

https 로 웹사이트 접속 후 해당 웹사이트로부터 인증서를 수신하고 이것을 검증하여 해당 사이트가 신뢰할 수 있는지 검증하는 방법은 다음과 같다.

먼저 웹사이트는 CA(인증기관)에 인증서 발급을 신청한다. CA는 해당 웹사이트를 검증한 후 신뢰할만하다고 판단되면
인증서를 발급하는데 다음의 과정을 거친다.(전자서명 원리와 같다)

1. 인증서 내용을 해쉬하여 지문을 생성한다.
2. 생성된 지문을 자신의 비밀키로 암호화하여 전자서명값을 생성한다.
3. 인증서 파일 내부에 인증서 내용, 지문, 전자서명값을 넣은 후 웹사이트에 전달한다.

이렇게 생성된 인증서는 아래 그림과 같이 구성된다.



여기서 인증서 내용은 대략 아래와 같다.

(1) 인증서의 소유자 이름, 
(2) 인증서 소유자의 공개 키 (당연히 비밀 키는 소유자가 가지고 있다), 
(3) 인증서의 유효 기간, 
(4) 고유한 UID
(5) 인증서의 기타 모든 값들을 해시화한 값 => 지문


여기서 가장 중요한 것이 (5)번 "지문"이다. 이 지문을 통해 전자서명을 검증하는것이다.



브라우저는 웹사이트로부터 인증서를 전달받으면 내장되어 있는 CA 기관의 공개키를 가지고 전자서명값을 복호화한 후 이것을 인증서 내용의 해쉬값(지문)과 비교하여 같으면 CA 기관이 인증한 것이라고 인식하는 것이다.

인증서가 검증이 되면 인증서 내용에 포함된 웹사이트의 공개키를 신뢰할 수 있으므로 이 공개키로 https 접속을 진행한다.