gstreamerのサンプルコードをmacで動かしてみた(gstreamer、c、c++)

#はじめに
この記事は「[完全に理解したTalk Advent Calendar 2020](https://qiita.com/advent-calendar/2020/easyeasy)」11日目の投稿記事です。
gstreamerを使う必要があり、頑張ってたら完全理解してしまったのでその話をします。

OSはMacOS
言語はc/c++
cmakeでビルドします。

#gstreamerとは
C言語で作られたライブラリで、
動画のストリーミングほか、メディアプレイヤーの再生などに使われます。
メインの処理以外は、プラグイン、ライブラリを読み込んで処理が行われています。
[Wikipedia](https://ja.wikipedia.org/wiki/GStreamer)さんより

#インストール
brewでインストールします。

```bash
$ brew update #必要であれば
$ brew install gstreamer
$ brew install gst-plugins-base
$ brew install gst-plugins-good
$ brew install gstreamermm #c++でバインドしたライブラリ
```
必要なものはこれらです。

```bash
$ brew install gst-plugins-ugly
$ brew install gst-plugins-bad
```
妙な名前でざわつくものもありますが、ugly、badは飛ばして大丈夫です。
[公式](https://gstreamer.freedesktop.org/documentation/additional/splitup.html?gi-language=c)に説明があるようです。。


#パスを通す

```bash
$ brew link gstreamer
$ brew link gst-plugins-base
$ brew link gst-plugins-good
$ brew link gstreamermm #c++でバインドしたライブラリ
```

brew linkで `/usr/local/lib`、`/usr/local/include`にシンボリックリンクします。

#cmakeを書く

gstreamerはcmakeのfindPackageは使えません。
ですが、pkg-configを使っていますのでcmakeでpkg_configを使ってリンクします。


cmake内で使われるpkg_configはパスが通っていない場合がありますので、
`set(ENV{PKG_CONFIG_PATH} "/usr/local/lib/pkgconfig")`
で一時的にパスを通します。
brewでインストールしリンクしたものは、`/usr/local/lib/`にあります。

```cmake
cmake_minimum_required(VERSION 3.15)
project(testGstreamer)

set(CMAKE_CXX_STANDARD 14)

set(ENV{PKG_CONFIG_PATH} "/usr/local/lib/pkgconfig")
find_package(PkgConfig REQUIRED)

#pkg_check_modules(GSTREAMER_LIB REQUIRED IMPORTED_TARGET  gstreamer-1.0) cで使う場合はこっち
pkg_check_modules(GSTREAMERMM_LIB REQUIRED  gstreamermm-1.0)

add_executable(testGstreamer main.cpp)


target_include_directories(testGstreamer PUBLIC ${GSTREAMERMM_LIB_INCLUDE_DIRS})
target_link_libraries(testGstreamer ${GSTREAMERMM_LIB_LIBRARIES})
target_compile_options(testGstreamer PUBLIC ${GSTREAMERMM_CFLAGS_OTHER})

#target_include_directories(testGstreamer PUBLIC ${GSTREAMER_LIB_INCLUDE_DIRS})　cで使う場合はこっち
#target_link_libraries(testGstreamer ${GSTREAMER_LIB_LIBRARIES}) cで使う場合はこっち
#target_compile_options(testGstreamer PUBLIC ${GSTREAMER_CFLAGS_OTHER}) cで使う場合はこっち
```

#exampleから引っ張ってきたコード

ここの[example](https://github.com/GNOME/gstreamermm/blob/master/examples/hello_world/main.cc)をビルドしました。

```c++

#include <gstreamermm.h>
#include <glibmm/main.h>
#include <glibmm/convert.h>
#include <iostream>
#include <stdlib.h>
#include <gstreamermm/playbin.h>

namespace
{

Glib::RefPtr<Glib::MainLoop> mainloop;

// This function is used to receive asynchronous messages in the main loop.
bool on_bus_message(const Glib::RefPtr<Gst::Bus>& /* bus */,
                    const Glib::RefPtr<Gst::Message>& message)
{
  switch(message->get_message_type()) {
  case Gst::MESSAGE_EOS:
    std::cout << std::endl << "End of stream" << std::endl;
    mainloop->quit();
    return false;
  case Gst::MESSAGE_ERROR:
  {
    Glib::RefPtr<Gst::MessageError> msgError =
        Glib::RefPtr<Gst::MessageError>::cast_static(message);

    if(msgError)
    {
      Glib::Error err;
      err = msgError->parse_error();
      std::cerr << "Error: " << err.what() << std::endl;
    }
    else
      std::cerr << "Error." << std::endl;

    mainloop->quit();
    return false;
  }
  default:
    break;
  }

  return true;
}

} // anonymous namespace

int main(int argc, char** argv)
{
  // Initialize gstreamermm:
  Gst::init(argc, argv);

  // Check input arguments:
  if(argc < 2)
  {
    std::cout << "Usage: " << argv[0] << " <media file or uri>" << std::endl;
    return EXIT_FAILURE;
  }

  // Create a playbin element.
#ifndef GSTREAMERMM_DISABLE_DEPRECATED
  Glib::RefPtr<Gst::PlayBin> playbin = Gst::PlayBin::create();
#else
  Glib::RefPtr<Gst::Element> playbin = Gst::ElementFactory::create_element("playbin");
#endif

  if(!playbin)
  {
    std::cerr << "The playbin2 element could not be created." << std::endl;
    return EXIT_FAILURE;
  }

  // Take the commandline argument and ensure that it is a uri:
  Glib::ustring uri;

  if(gst_uri_is_valid(argv[1]))
    uri = argv[1];
  else
    uri = Glib::filename_to_uri(argv[1]);

  // Set the playbyin2's uri property.
  playbin->set_property("uri", uri);

  // Create the main loop.
  mainloop = Glib::MainLoop::create();

  // Get the bus from the playbin, and add a bus watch to the default main
  // context with the default priority:
  Glib::RefPtr<Gst::Bus> bus = playbin->get_bus();
  bus->add_watch(sigc::ptr_fun(&on_bus_message));

  // Now set the playbin to the PLAYING state and start the main loop:
  std::cout << "Setting to PLAYING." << std::endl;
  playbin->set_state(Gst::STATE_PLAYING);
  std::cout << "Running." << std::endl;
  mainloop->run();

  // Clean up nicely:
  std::cout << "Returned. Setting state to NULL." << std::endl;
  playbin->set_state(Gst::STATE_NULL);

  return EXIT_SUCCESS;
}
```

#実行

```bash
$ ./testGstreamer https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm
Setting to PLAYING.
Running.

End of stream
Returned. Setting state to NULL.
```

![スクリーンショット 2020-12-11 18.31.40（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/886714/0be7f19a-8a6b-8d85-271d-7dcff6240b93.png)

上のような画面が出てダウンロードしながら再生が行われます。
また、実行時、引数をローカルファイルにしても再生されますし、
mp3でも再生が行われます。


#終わりに
今回は、gstreamerを使わないといけなくなったので、
まずは試しで使いました。

コマンドとしても用意されていますので、色々遊べると思います。

#参考
[公式](https://gstreamer.freedesktop.org/documentation/additional/splitup.html?gi-language=c)
[公式リファレンス](
https://gstreamer.freedesktop.org/documentation/tutorials/basic/hello-world.html?gi-language=c)