surroundize

Tool/PipeWire filter to convert stereo audio to surround
git clone https://git.neptards.moe/u3shit/surroundize.git
Log | Files | Refs | README | LICENSE

converter.cpp (4385B)


      1 #include "decoder.hpp"
      2 
      3 #include <cstdio>
      4 #include <cstring>
      5 #include <iostream>
      6 
      7 using namespace std::string_literals;
      8 
      9 static constexpr std::size_t INPUTS = 2;
     10 
     11 static void FindCh(
     12   std::string_view sv, const std::vector<Channel::E>& channels,
     13   std::vector<int>& reorder, unsigned cur)
     14 {
     15   for (std::size_t i = 0; i < channels.size(); ++i)
     16     if (auto x = Channel::NAMES_PIPEWIRE[channels[i]];
     17         strlen(x) == sv.size() && !strncasecmp(x, sv.data(), sv.size()))
     18       if (reorder[i] == -1)
     19       {
     20         reorder[i] = cur;
     21         return;
     22       }
     23       else
     24         throw std::runtime_error{"Duplicate channel " + std::string{sv}};
     25   throw std::runtime_error{"Invalid channel " + std::string{sv}};
     26 }
     27 
     28 int Main(int argc, char** argv)
     29 {
     30   float rate = 1/48000.f;
     31   std::string_view channel_map;
     32   Decoder dec;
     33   {
     34     Option opts[] = {
     35       { "help", "", "Show help" },
     36       {
     37         "sample_rate", "FLOAT", "Sets sample rate of input (default: 48000 Hz)",
     38         [&](std::string_view sv)
     39         { rate = 1 / FromString<float>(sv); },
     40       },
     41       {
     42         "channel_map", "CHANNEL_MAP",
     43         "Set comma separated list of output channel order (help to get list of available channels)",
     44         [&](std::string_view sv) { channel_map = sv; },
     45       },
     46     };
     47 
     48     opts[0].apply = [&](std::string_view)
     49     {
     50       std::cerr << "Usage " << argv[0] << " [--options] < input > output\n";
     51       PrintHelp(opts);
     52       dec.PrintHelp();
     53       throw Exit{0};
     54     };
     55 
     56     auto dopt = dec.GetOptions();
     57     ParseArgs(argc, argv, [&](std::string_view k, std::string_view v)
     58     {
     59       if (HandleOption(opts, k, v)) return;
     60       auto dopt = dec.GetOptions();
     61       if (HandleOption(dopt, k, v)) return;
     62       throw ParseError("Unknown option "s + std::string{k});
     63     });
     64   }
     65   dec.Init(rate);
     66 
     67   std::vector<int> channel_reorder;
     68   if (channel_map == "help")
     69   {
     70     for (const auto m : dec.GetChannels())
     71       std::cerr << Channel::NAMES_PIPEWIRE[m] << '\n';
     72     return 0;
     73   }
     74   else if (!channel_map.empty())
     75   {
     76     auto chs = dec.GetChannels();
     77     channel_reorder.resize(chs.size(), -1);
     78     std::size_t begin = 0;
     79     unsigned i = 0;
     80     while (true)
     81     {
     82       auto end = channel_map.find_first_of(',', begin);
     83       auto it = channel_map.substr(begin, end-begin);
     84       FindCh(it, chs, channel_reorder, i++);
     85       if (end == std::string_view::npos) break;
     86       begin = end + 1;
     87     }
     88 
     89     if (i != chs.size())
     90       throw std::runtime_error{"Not enough channels in reorder"};
     91   }
     92 
     93   auto block_size = dec.GetBlockSize();
     94   auto read_size = INPUTS * (block_size ? block_size : 8192);
     95   auto outputs = dec.GetChannels().size();
     96   auto skip = outputs * dec.GetDelay();
     97   std::vector<float> in_buf(read_size);
     98 
     99   auto do_skip = [&](std::span<const float>& data)
    100   {
    101     auto cskip = std::min<std::size_t>(skip, data.size());
    102     data = data.subspan(cskip);
    103     skip -= cskip;
    104   };
    105 
    106   std::uint64_t total_read = 0, total_write = 0;
    107   std::vector<float> reorder_buf;
    108   auto wr = [&](std::span<const float> data)
    109   {
    110     if (const auto n = channel_reorder.size())
    111     {
    112       reorder_buf.resize(data.size());
    113       for (std::size_t i = 0; i < data.size(); i += n)
    114       {
    115         std::array<float, Channel::N_CHANNELS> buf;
    116         std::memcpy(buf.data(), data.data() + i, sizeof(float) * n);
    117         for (std::size_t j = 0; j < n; ++j)
    118           reorder_buf[i + std::size_t(channel_reorder[j])] = buf[j];
    119       }
    120       data = reorder_buf;
    121     }
    122 
    123     if (fwrite(data.data(), sizeof(float), data.size(), stdout) != data.size())
    124       throw std::runtime_error("Write error");
    125     total_write += data.size();
    126   };
    127 
    128   std::size_t n_read;
    129   while (true)
    130   {
    131     n_read = fread(in_buf.data(), sizeof(float), read_size, stdin);
    132     total_read += n_read;
    133     if (n_read != read_size) break;
    134 
    135     auto res = dec.Decode(std::span{in_buf});
    136     do_skip(res);
    137     wr(res);
    138   }
    139 
    140   // partial read, finish up
    141   auto os_read = (total_read / INPUTS) * outputs;
    142   auto do_chunk = [&]()
    143   {
    144     auto res = dec.Decode(std::span{in_buf});
    145     do_skip(res);
    146     wr(res.subspan(0, std::min(os_read - total_write, res.size())));
    147   };
    148 
    149   std::memset(in_buf.data() + n_read, 0, sizeof(float) * (read_size - n_read));
    150   do_chunk();
    151   if (os_read == total_write) return 0;
    152 
    153   std::memset(in_buf.data(), 0, sizeof(float) * in_buf.size());
    154   while (os_read > total_write) do_chunk();
    155 
    156   return 0;
    157 }