Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

C++での共有メモリを用いたアプリ間の動画データ通信

今回はC++で共有メモリを用いてアプリ間で動画データを通信をまとめたいと思います!!

前回実施したgtkmmを用いたGUIアプリに対して、別アプリで加工した動画データを渡したいな??🤔
と思い、調べてみました!!
elsammit-beginnerblg.hatenablog.com



■共有メモリでのアプリ間通信

まず共有メモリでのアプリ間通信ついてです。
そもそも、共有メモリ通信の手順ですが、
 ・共有メモリ用のkey発行
 ・サイズ指定したセグメントとkeyを紐づけて共有メモリ作成
 ・プロセス(アプリ)と共有メモリを紐付け
 ・処理実行
 ・プロセス(アプリ)と共有メモリの紐付け解除
 ・メモリを解放
となります。
詳しくまとめられているのでご参照下さい。
qiita.com

ソースコードも掲載されているので、試しに動かしてみるといいかもです。
私は受信側を操作するのが面倒だったのでこちらのようにコードを作成しました。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <unistd.h>
using namespace std;


int main(){
    // 作成済みの共有メモリのIDを取得する
    const string file_path = "./test.dat";
    const int id = 50;

    const key_t key = ftok(file_path.c_str(), id);

    const int seg_id = shmget(key, 0, 0);
    if(seg_id == -1){
        cerr << "Failed to acquire segment" << endl;
        return EXIT_FAILURE;
    }

    char* const shared_memory = reinterpret_cast<char*>(shmat(seg_id, 0, 0));


    while(flag == 0){
        sleep(1);
        printf("%s\n", shared_memory);
    }

    shmdt(shared_memory);

    return 0;
}

■共有メモリでのアプリ間の画像データ通信

次は画像データの通信です。
interprocessというモジュールを用いて動画データの共有いたします。

【interprocessとは】
Boost.Interprocessは、プロセス間通信をサポートするための各種機能を提供しているライブラリとのこと。
ライブラリはビルドを必要としないが、Boost DateTime Library を利用している箇所があるため注意すること。
※参考:https://boostjp.github.io/tips/memory_mapped_file.html

ではコードの紹介です。
今回のコードですが、画像サイズが640 x480の場合です。
もし画像サイズを変更したい場合には640*480の数値を変更すればOKです。
但し、送受信でサイズを合わせないとSegmentation faultが発生しますので注意。

まずは受信側のコードです。
実施していることは、先ほどの共有メモリとほとんど同様で、
・Keyやサイズを指定して共有メモリを確保
・共有メモリから画像データを取得
・jpgデータとして画像データを保存
になります。

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <unistd.h>
#include <opencv2/opencv.hpp>
using namespace boost::interprocess;

typedef allocator<unsigned char, managed_shared_memory::segment_manager> shmalloc;
typedef vector<unsigned char, shmalloc> shmimage;


int main( int argc, char* argv[] )
{

  managed_shared_memory shm(open_only, "NAME");
  shmimage *image = shm.find< shmimage >("Image").first;


  cv::Mat_<cv::Vec3b> im(480, 640);
  shmimage& imref = *image;
  memcpy(im.data, &imref[0], 640*480*3);
  cv::imwrite("received.jpg", im);

  usleep(1000*10);

}

次に送信側。
こちらも受信側と同様で、
・Keyやサイズを指定して共有メモリを確保
・jpgデータをimreadでMatデータとして読み込み
・共有メモリへMatデータ書き込み
になります。

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <unistd.h>
#include <opencv2/opencv.hpp>

using namespace boost::interprocess;

typedef allocator < unsigned char, managed_shared_memory::segment_manager> shmalloc;
typedef vector< unsigned char, shmalloc > shmimage;

int main(  )
{

  shared_memory_object::remove("NAME");
  managed_shared_memory shm(open_or_create, "NAME", 640*480*3+1024);


  const shmalloc alloc_inst (shm.get_segment_manager());


  shmimage * image = shm.construct < shmimage >("Image")(alloc_inst);
  shmimage & imref = * image;
  imref.resize(640*480*3);


  cv::Mat_<cv::Vec3b> im = cv::imread("画像データパス");
  memcpy(&imref[0], im.data, 640*480*3);

  usleep(1000*10);

}

"NAME"には共有メモリとして登録する任意の名前を指定すればOKです。
但し、送受信で名前は同じにしてください。

■共有メモリでのアプリ間の動画データ通信

では最後に動画データを共有メモリで通信する方法です。
先ほどと同様に画像サイズは640 x 480です。
もし画像サイズを変えたい場合には640*480の数値を変更下さい。
但し、送受信でサイズを合わせないとsegmentation faultが発生しますので注意。

まずは受信側!!

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <unistd.h>
#include <opencv2/opencv.hpp>

using namespace boost::interprocess;

typedef allocator < unsigned char, managed_shared_memory::segment_manager> shmalloc;
typedef vector< unsigned char, shmalloc > shmimage;

int main()
{

  shared_memory_object::remove("test");
  managed_shared_memory shm(open_or_create, "test", 640*480*3+1024);


  const shmalloc alloc_inst (shm.get_segment_manager());

  shmimage * image = shm.construct < shmimage >("Image")(alloc_inst);
  shmimage & imref = * image;
  imref.resize(640*480*3);

  const int CAP_WIDTH = 640;
  const int CAP_HEIGHT = 480;

  cv::VideoCapture cap(0);
  if(!cap.isOpened()){
    printf("open error \n");
    return -1;
  }
  
  cap.set(cv::CAP_PROP_FRAME_WIDTH, CAP_WIDTH);
  cap.set(cv::CAP_PROP_FRAME_HEIGHT, CAP_HEIGHT);

  while(1){
    cv::Mat im;
    if(!cap.read(im)){
      printf("can't read frame \n");
      continue;
    }
    memcpy(&imref[0], im.data, 640*480*3)
  }
}

先ほどの画像受信の場合と異なる点は、

  cv::VideoCapture cap(0);
  if(!cap.isOpened()){
    printf("open error \n");
    return -1;
  }
  
  cap.set(cv::CAP_PROP_FRAME_WIDTH, CAP_WIDTH);
  cap.set(cv::CAP_PROP_FRAME_HEIGHT, CAP_HEIGHT);

  while(1){
    cv::Mat im;
    if(!cap.read(im)){
      printf("can't read frame \n");
      continue;
    }
    memcpy(&imref[0], im.data, 640*480*3)
  }
}

で、VideoCaptureで動画データを取得し、各フレーム毎のデータを共有メモリに保存することを繰り返します。

次に送信!!

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/sync/interprocess_mutex.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include <unistd.h>
#include <opencv2/opencv.hpp>
using namespace boost::interprocess;

typedef allocator<unsigned char, managed_shared_memory::segment_manager> shmalloc;
typedef vector<unsigned char, shmalloc> shmimage;


int main( int argc, char* argv[] )
{
  managed_shared_memory shm(open_only, "test");
  shmimage *image = shm.find< shmimage >("Image").first;

  cv::Mat_<cv::Vec3b> im(480, 640);
  shmimage& imref = *image;
  while(1){
    memcpy(im.data, &imref[0], 640*480*3);
    if(im.empty()){
      printf("empty!! \n");
      continue;
    }

    cv::imshow("test", im);
    cv::waitKey(1);
  }
}

画像データの場合と異なる点は、

  while(1){
    memcpy(im.data, &imref[0], 640*480*3);
    if(im.empty()){
      printf("empty!! \n");
      continue;
    }

    cv::imshow("test", im);
    cv::waitKey(1);
  }

です。
共有メモリからデータを読み出してimshowで画面表示する、
を繰り返して実施しています。

■最後に

今回は動画データを共有メモリで通信する方法をまとめてみました。
GUIは動画表示するだけ、
動画データへの加工は別アプリで実施したかったのでこちらの制御が出来たのは結構うれしい!!

動画データの通信方法として、仮想ビデオデバイスも候補にあるのですが、、、
仮想ビデオデバイスは生成できる数が限られているので、別の方法も知っておくのはいいことですね。
elsammit-beginnerblg.hatenablog.com