openFrameworksでの動画再生が重い問題の解決として、外部アプリに動画再生を任せて通信にしてみようと思い立ち、とりあえず以下の手順で実装してみました。
- WPFで動画再生してJPEG圧縮
- UDPで通信
- openFrameworksでofImageへ変換して描画
最後のoFでのJPEGエンコードで苦労したのでメモ。
コードはこちら:
(oF) : https://github.com/ma-ring/oF/tree/jpgEncoder/JpgEncoder
(WPF): https://github.com/ma-ring/oF/tree/jpgEncoder/VideoController
WPFで動画再生してJPEG圧縮
動画の再生はMediaPlayerで行います。
MediaPlayerをDrawingVisualに描いてbitmapを取得。
JpegBitmapEncoderという便利なクラスがあるのでこれでJPEG圧縮までOK。
private MediaPlayer mPlayer;
public MainWindow()
{
SetupClient();
InitializeComponent();
mPlayer = new MediaPlayer();
mPlayer.ScrubbingEnabled = true;
mPlayer.Open(new Uri(path, UriKind.Relative));
MediaVideo.Source = new Uri(path, UriKind.Relative);
play();
}
void sendFrame()
{
int width = (int)(MediaVideo.Width);
int height = (int)(MediaVideo.Height);
// 描画用の Visual を用意
var visual = new DrawingVisual();
using (var context = visual.RenderOpen())
{
context.DrawVideo(mPlayer, new System.Windows.Rect(0, 0, width, height));
}
var bitmap = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(visual);
//image.Source = bitmap;
var encoder = new JpegBitmapEncoder();
//encoder.QualityLevel = 30;
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (MemoryStream ms = new MemoryStream())
{
encoder.Save(ms);
byte[] data = ms.ToArray();
Debug.Print(data.ToString());
mSendClient.Send(data, data.Length, mRemoteIP, mRemotePort);
}
}
UDPで通信
こちらもそのまま。
private string mRemoteIP = "127.0.0.1";
private int mRemotePort = 5000;
private UdpClient mSendClient;
private void SetupClient()
{
System.Net.IPAddress ip = System.Net.IPAddress.Parse(mRemoteIP);
mSendClient = new UdpClient();
StartSending();
}
#include "FreeImage.h"
class ofApp : public ofBaseApp{
//---略
ofxUDPManager mReceiver;
ofImage mFrameImage;
//--略
}
#define IP "127.0.0.1"
#define PORT 5000
#define VIDEO_FILE_PATH "video.mp4"
#define MSG_BUFF_SIZE 100000
void ofApp::setup(){
mReceiver.Create();
mReceiver.Bind(PORT);
mReceiver.SetNonBlocking(true);
}
openFrameworksでofImageへ変換して描画
これが本題。
jpgEncoder関数としてまとめました。
結論としては、oFだとFreeImage (https://freeimage.sourceforge.io/) というライブラリが使えるので、こちらを利用しました。
void ofApp::update(){
//recieve
char recvMsg[MSG_BUFF_SIZE];
int recvSize = mReceiver.PeekReceive();
if (recvSize > 0) {
mReceiver.Receive(recvMsg, MSG_BUFF_SIZE);
jpgEncoder(&recvMsg[0], recvSize, mFrameImage);
}
}
void ofApp::jpgEncoder(char* src, int size, ofImage& dst) {
vector<unsigned char> data(src, src + size);
//mFrame = imdecode(data, 1);
FIMEMORY *hmem;
FREE_IMAGE_FORMAT fif;
FIBITMAP *bmp;
hmem = FreeImage_OpenMemory(&data[0], size);
fif = FreeImage_GetFileTypeFromMemory(hmem, 0);
bmp = FreeImage_LoadFromMemory(fif, hmem, 0);
dst.setFromPixels(FreeImage_GetBits(bmp), 1920 /4, 1080/4 , ofImageType::OF_IMAGE_COLOR, true);
dst.mirror(true, false);
dst.getPixelsRef().swapRgb();
dst.update();
}
ただそのままだとRGBの並びがおかしかったり反転してたりします。
pixelの並びが問題なんだろうなとは思いつつ、今回はお試しなのでmirrorとswapRgbで調整してます。
これはshaderにしてもいいかもしれない。
まとめ
たぶん本当に使うならSyphonとかSpoutを使うべきなんだろうなとは思います。
あとoF側でJPEG圧縮する方法を模索中です、だれか知見があれば教えてください。