Skip to content

Commit

Permalink
Updated readme.
Browse files Browse the repository at this point in the history
  • Loading branch information
kekyo committed Mar 29, 2022
1 parent 962b569 commit 2c67d03
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 2 deletions.
120 changes: 119 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,125 @@ By referring to these properties, the order of the frames can be determined even

## Master for frame processor (Advanced topic)

TODO: rewrite to what is frame processor.
Welcome to the underground dungeon, where FlashCap's frame processors are polished gems. There is no need to deal with frame processors unless you have a lot of experience with them. This explanation is only provided because frame processors exist, and most readers don't need to know about them.

The callback handler invocation triggers described in the previous section are internally realized by this frame processor. In other words, it is an abstraction of how frames are handled and how they behave.

The frame processor is implemented by inheriting a very simple base class:

```csharp
// (Will spare you the detailed definitions.)
public abstract class FrameProcessor : IDisposable
{
// Implement if necessary.
public virtual void Dispose()
{
}

// Get a pixel buffer.
protected PixelBuffer GetPixelBuffer()
{ /* ... */ }

// Perform capture using the device.
protected void Capture(CaptureDevice captureDevice,
IntPtr pData, int size,
double timestampMicroseconds, long frameIndex,
PixelBuffer buffer)
{ /* ... */ }

// Called when a frame is arrived.
public abstract void OnFrameArrived(
CaptureDevice captureDevice,
IntPtr pData, int size, double timestampMicroseconds, long frameIndex);
}
```

At the very least, you need to implement the `OnFrameArrived()` method.
This is literally called when a frame is arrived.
As you can see from the signature, it is passed a raw pointer, the size of the image data, a timestamp, and a frame number.

Note also that the return value is void.
This method cannot be used for asynchronous processing.
Any information passed as an argument is considered invalid when exiting this method.

Here is a typical implementation of this method:

```csharp
public sealed class CoolFrameProcessor : IDisposable
{
private readonly Action<PixelBuffer> action;

// Hold a delegate to run once captured.
public CoolFrameProcessor(Action<PixelBuffer> action) =>
this.action = action;

// Called when a frame is arrived.
public override void OnFrameArrived(
CaptureDevice captureDevice,
IntPtr pData, int size, double timestampMicroseconds, long frameIndex)
{
// Get a pixel buffer.
var buffer = base.GetPixelBuffer();

// Perform capture.
// Image data is stored in pixel buffer. (First copy occurs.)
base.Capture(
captureDevice,
pData, size,
timestampMicroseconds, frameIndex,
buffer);

// Invoke a delegate.
this.action(buffer);
}
}
```

Recall that this method is called each time a frame is arrived.
In other words, this example implementation creates a pixel buffer, captures it, and invoke the delegate every time a frame is arrived.

Let's try to use it:

```csharp
var devices = new CaptureDevices();
var descriptor0 = devices.EnumerateDevices().ElementAt(0);

// Open by specifying our frame processor.
using var device = await descriptor0.OpenWitFrameProcessorAsync(
descriptor0.Characteristics[0],
true, // transcode
new CoolFrameProcessor(buffer =>
{
// Captured pixel buffer is passed.
var image = buffer.ReferImage();

// Perform decode.
var bitmap = Bitmap.FromStream(image.AsStream());

// ...
});

device.Start();

// ...
```

Your first frame processor is ready.
And even if you don't actually run it, you're probably aware of its features and problems:

* The delegate is invoked in the shortest possible time when the frame arrives. (It is the fastest to the point where it is invoked.)
* `OnFrameArrived()` blocks until the delegate completes processing.
* The delegate assumes synchronous processing. Therefore, the decoding process takes time, and blocking this thread can easily cause frame dropping.
* If you use `async void` here to avoid blocking, access to the pixel buffer is at risk because it cannot wait for the delegate to complete.

For this reason, FlashCap uses a standard set of frame processors that can be abstracted and safely handled by `HandlerStrategies` values.
So where is the advantage of implementing your own custom frame processors?

It is possible to implement highly optimized frame and image data processing.
For example, pixel buffers are created efficiently, but we do not have to be used.
(Calling the `Capture()` method is optional.)
Since a pointer to the raw image data and its size are given by the arguments, it is possible to access the image data directly.
So, you can implement your own image data processing to achieve the fastest possible processing.

----

Expand Down
109 changes: 108 additions & 1 deletion README_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,114 @@ using var device = await descriptor0.OpenAsync(

## フレームプロセッサをマスターする (Advanced topic)

TODO: rewrite to what is frame processor.
地下ダンジョンへようこそ。FlashCapのフレームプロセッサは、磨けば光る宝石です。余程のことが無い限り、フレームプロセッサを取り扱う必要はありません。この解説は、フレームプロセッサが存在するから解説しているだけで、ほとんどの読者は把握不要です。

前節で解説した、コールバックハンドラの呼び出し契機は、内部的にはこのフレームプロセッサによって実現されています。つまり、フレームをどのように取り扱うのかや、その振る舞いを抽象化したものです。

フレームプロセッサは、非常に単純な基底クラスを継承して実装します:

```csharp
// (細かい定義は省きます)
public abstract class FrameProcessor : IDisposable
{
// 必要なら実装する
public virtual void Dispose()
{
}

// ピクセルバッファを取得する
protected PixelBuffer GetPixelBuffer()
{ /* ... */ }

// デバイスを使用してキャプチャを実行する
protected void Capture(CaptureDevice captureDevice,
IntPtr pData, int size,
double timestampMicroseconds, long frameIndex,
PixelBuffer buffer)
{ /* ... */ }

// フレームが到達した際に呼び出される
public abstract void OnFrameArrived(
CaptureDevice captureDevice,
IntPtr pData, int size, double timestampMicroseconds, long frameIndex);
}
```

少なくとも実装する必要があるのは、`OnFrameArrived()`メソッドです。これは、文字通りフレームが到達した時に呼び出されます。シグネチャを見れば分かる通り、生のポインタと画像データのサイズ、タイムスタンプ、そしてフレーム番号が渡されます。

戻り値がvoidである事にも注意して下さい。このメソッドは非同期処理に出来ません。引数で渡される情報は、このメソッドを抜けるときには無効とみなされます。

このメソッドの、典型的な実装例を示します:

```csharp
public sealed class CoolFrameProcessor : IDisposable
{
private readonly Action<PixelBuffer> action;

// キャプチャしたら実行するデリゲートを保持する
public CoolFrameProcessor(Action<PixelBuffer> action) =>
this.action = action;

// フレームが到達した際に呼び出される
public override void OnFrameArrived(
CaptureDevice captureDevice,
IntPtr pData, int size, double timestampMicroseconds, long frameIndex)
{
// ピクセルバッファを取得する
var buffer = base.GetPixelBuffer();

// キャプチャを実行する
// ピクセルバッファに画像データが格納される (最初のコピーが発生)
base.Capture(
captureDevice,
pData, size,
timestampMicroseconds, frameIndex,
buffer);

// デリゲートを呼び出す
this.action(buffer);
}
}
```

このメソッドが、フレーム到達時に毎回呼び出されることを思い出してください。つまり、この実装例は、フレーム到達毎にピクセルバッファを生成し、キャプチャし、そしてデリゲートを呼び出します。

ではこれを使ってみます:

```csharp
var devices = new CaptureDevices();
var descriptor0 = devices.EnumerateDevices().ElementAt(0);

// フレームプロセッサを指定してオープンする
using var device = await descriptor0.OpenWitFrameProcessorAsync(
descriptor0.Characteristics[0],
true, // transcode
new CoolFrameProcessor(buffer =>
{
// キャプチャされたピクセルバッファが渡される
var image = buffer.ReferImage();

// デコードする
var bitmap = Bitmap.FromStream(image.AsStream());

// ...
});

device.Start();

// ...
```

あなたの最初のフレームプロセッサが出来上がりました。そして、実際に動かさなくても、特徴と問題に気が付いていると思います:

* デリゲートは、フレーム到達時に最短で呼び出される。(呼び出されるところまでは最速である。)
* デリゲートの処理が完了するまで、`OnFrameArrived()`がブロックされる。
* デリゲートは、同期処理を想定している。そのため、デコード処理に時間がかかり、スレッドをブロックすると、容易にフレームドロップが発生する。
* もしここで、ブロックを回避するために`async void`を使用すると、デリゲートの完了を待てないので、ピクセルバッファへのアクセスは危険にさらされる。

このような理由で、FlashCapでは、`HandlerStrategies`による、抽象化され、安全に取り扱う事が出来る、標準のフレームプロセッサ群を使用するようになっています。では、カスタムフレームプロセッサを実装する利点がどこにあるのでしょうか?

それは、きわめて高度に最適化された、フレームと画像データの処理を実装できることです。例えば、ピクセルバッファは効率よく作られていますが、必ず使用しなければならないわけではありません。(`Capture()`メソッドの呼び出しは任意です。)引数によって、生の画像データへのポインタとサイズが与えられているため、画像データに直接アクセスすることは可能です。そこで、あなた独自の画像データ処理を実装すれば、最速の処理を実現する事が出来ます。

----

Expand Down

0 comments on commit 2c67d03

Please sign in to comment.