GStreamerで音楽聴きたい！

## はじめに

某ストリーミングを毎日録○しているのですが、いつからかオンデマンドサービスを録ると一部音が抜けるようになっちゃったんです。これは困ります。ということで、ffmpegで行っていたことをなにか違うものに置き換えようという話の最初の1歩です。

## GStreamerがおもしろそうだ！

そこで代わりになるソフトウェアかライブラリを探していたら
[GStreamer](https://gstreamer.freedesktop.org/)
というのを発見、というか思い出しました。これならいろいろできるかも？！

## とにかくサンプルコード

``` C++
#include <gst/gst.h>

#include <string>

int main() {
  // Make stream URL and pipeline string
  constexpr char stream_url[] =
      "https://radio-stream.nhk.jp/hls/live/2023507/nhkradiruakfm/master.m3u8";
  std::string pipeline_str = "playbin uri=";
  pipeline_str.append(stream_url);

  /* Initialize GStreamer */
  gst_init(nullptr, nullptr);

  /* Build the pipeline */
  GstElement *pipeline = gst_parse_launch(pipeline_str.c_str(), nullptr);
  /* Start playing */
  gst_element_set_state(pipeline, GST_STATE_PLAYING);

  /* Wait until error or EOS */
  GstBus *bus = gst_element_get_bus(pipeline);
  GstMessage *msg = gst_bus_timed_pop_filtered(
      bus, -1,
      static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS));

  if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR) {
    g_error(
        "An error occurred! Re-run with the GST_DEBUG=*:WARN environment "
        "variable set for more details.");
  }

  /* Free resources */
  gst_message_unref(msg);
  gst_object_unref(bus);
  gst_element_set_state(pipeline, GST_STATE_NULL);
  gst_object_unref(pipeline);
  return 0;
}
```

gst_parse_launch()に渡す文字列をC++のstringを使って作ってますが、これは次の改造で変更を少なくするためなので、本質ではありません。

このgst_parse_launch()にパイプライン構築のための文字列を渡すだけでとりあえず再生できちゃうのはとても便利です。また、playbinというエレメントはすばらしく便利です。なにしろ、入力がなんであっても適当に判断して適切なパイプラインを組んでくれるので、インスタントに使いたいときはとっても便利です。

## ちょっと改造

今回の目標は、某ストリームを何とかすることです。このストリームがhlsで配信されていることは、手を出したことのある方ならみんなごぞんじのことと思います。ということで、playbinを使わずに書き直してみましょう。

``` C++
#include <gst/gst.h>

#include <string>

int main(int argc, char *argv[]) {
  constexpr char stream_url[] =
      "https://radio-stream.nhk.jp/hls/live/2023507/nhkradiruakfm/master.m3u8";
  std::string pipeline_str = "curlhttpsrc location=";
  pipeline_str.append(stream_url);
  pipeline_str.append(" ! hlsdemux ! decodebin ! audioconvert ! autoaudiosink");

  /* Initialize GStreamer */
  gst_init(nullptr, nullptr);

  /* Build the pipeline */
  GstElement *pipeline = gst_parse_launch(pipeline_str.c_str(), nullptr);

  /* Start playing */
  gst_element_set_state(pipeline, GST_STATE_PLAYING);

  /* Wait until error or EOS */
  GstBus *bus = gst_element_get_bus(pipeline);
  GstMessage *msg = gst_bus_timed_pop_filtered(
      bus, GST_CLOCK_TIME_NONE,
      static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS));

  /* See next tutorial for proper error message handling/parsing */
  if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR) {
    g_error(
        "An error occurred! Re-run with the GST_DEBUG=*:WARN environment "
        "variable set for more details.");
  }

  /* Free resources */
  gst_message_unref(msg);
  gst_object_unref(bus);
  gst_element_set_state(pipeline, GST_STATE_NULL);
  gst_object_unref(pipeline);
  return 0;
}
```

再生できましたか？これで再生はちゃんとできました。時間を少し戻したいとか、そういう要求がある場合にはちゃんとコールバックを書いたりする必要がありますが、とにかく再生はできました。

## 録○してみる

このストリーミングはHE-AACでエンコードされています。できることならそのまま録りたいなと思います。デコードしてしまったらwav形式で保存するか、flacにでもしないとそのままの音質では保存できません。
流石にこのストリーミングで使われているコーデックは音が良いなと思います。ffmpegで同じ程度のビットレートでエンコードすると音がはっきり変わってしまいます。

ffmpegであれば、 "acodec copy"として、そのまま出力に渡しました。では、これを実現するには同しましょう。

以下のサンプルで動作しました。デコードするエレメントは入っていないので、おそらくデータはコピーされているだろうと推測しています。

```
#include <gst/gst.h>

#include <string>

int main(int argc, char *argv[]) {
  constexpr char stream_url[] =
      "https://radio-stream.nhk.jp/hls/live/2023507/nhkradiruakfm/master.m3u8";
  constexpr char output_file[] = "test.m4a";
  std::string pipeline_str = "curlhttpsrc location=";
  pipeline_str.append(stream_url);
  pipeline_str.append(
      " ! hlsdemux ! aacparse ! isofmp4mux ! filesink location=");
  pipeline_str.append(output_file);
  /* Initialize GStreamer */
  gst_init(nullptr, nullptr);

  /* Build the pipeline */
  GstElement *pipeline = gst_parse_launch(pipeline_str.c_str(), nullptr);

  /* Start playing */
  gst_element_set_state(pipeline, GST_STATE_PLAYING);

  /* Wait until error or EOS */
  GstBus *bus = gst_element_get_bus(pipeline);
  GstMessage *msg = gst_bus_timed_pop_filtered(
      bus, GST_CLOCK_TIME_NONE,
      static_cast<GstMessageType>(GST_MESSAGE_ERROR | GST_MESSAGE_EOS));

  /* See next tutorial for proper error message handling/parsing */
  if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ERROR) {
    g_error(
        "An error occurred! Re-run with the GST_DEBUG=*:WARN environment "
        "variable set for more details.");
  }

  /* Free resources */
  gst_message_unref(msg);
  gst_object_unref(bus);
  gst_element_set_state(pipeline, GST_STATE_NULL);
  gst_object_unref(pipeline);
  return 0;
}
```

さて、目的は達成できますやら。