title: How to lossless concatenate ogg vorbis files?
body: <p>I'm trying to concatenate multiple ogg vorbis files into one.</p>
  
  <p>I know that theoretically it should be enough to do:</p>
  
  <pre><code>cat 1.ogg 2.ogg &gt; combined.ogg
  </code></pre>
  
  <p>But this has disadvantages:</p>
  
  <ul>
  <li>not all players support files created like this (gstreamer doesn't)</li>
  <li>the players that do, do not smoothly concatenate them but create ugly split second pauses</li>
  <li>seeking seems not to be possible</li>
  </ul>
  
  <p>I don't want to loose quality, so I could re-encode them into a lossless format like flac but that would let the file size explode.</p>
  
  <p>There seems to be no tool which does this. For example oggCat will re-encode the audio and thus lead to a slight loss in quality and the ffmpeg <a href="https://trac.ffmpeg.org/wiki/Concatenate#demuxer" rel="nofollow noreferrer">concat demuxer</a> will not work for all input files. I opened <a href="https://superuser.com/questions/864911">this superuser question</a> to find a tool but wrote my own when I figured that there is none.</p>
  
  <p>So I tried to use libogg and libvorbis to manually concatenate ogg packets from the input files into ogg pages of the output file. The assumption is, that all ogg input files were encoded using the exact same parameters.</p>
  
  <p>I came up with the following code:</p>
  
  <pre class="lang-C prettyprint-override"><code>#include &lt;ogg/ogg.h&gt;
  #include &lt;vorbis/codec.h&gt;
  #include &lt;stdio.h&gt;
  #include &lt;unistd.h&gt;
  #include &lt;stdbool.h&gt;
  #include &lt;sys/types.h&gt;
  #include &lt;sys/stat.h&gt;
  #include &lt;fcntl.h&gt;
  #include &lt;stdlib.h&gt;
  #include &lt;time.h&gt;
  
  int read_page(int fd, ogg_sync_state *state, ogg_page *page)
  {
      int ret;
      ssize_t bytes;
  
      while(ogg_sync_pageout(state, page) != 1) {
          char *buffer = ogg_sync_buffer(state, 4096);
          if (buffer == NULL) {
              fprintf(stderr, "ogg_sync_buffer failed\n");
              return -1;
          }
          bytes = read(fd, buffer, 4096);
          if (bytes == 0) {
              return -1;
          }
          ret = ogg_sync_wrote(state, bytes);
          if (ret != 0) {
              fprintf(stderr, "ogg_sync_wrote failed\n");
              return -1;
          }
      }
      return 0;
  }
  
  int main(int argc, char *argv[])
  {
      int ret;
      ogg_sync_state state;
      ogg_page page;
      int serial;
      ogg_stream_state sstate;
      bool found_bos;
      ogg_packet packet;
      int fd;
      int i;
      vorbis_info info;
      vorbis_comment comment;
      int vorbis_header_read;
      ssize_t bytes;
      ogg_stream_state out_stream;
      ogg_page out_page;
  
      if (argc &lt; 2) {
          fprintf(stderr, "usage: %s file.ogg\n", argv[0]);
          return 1;
      }
  
      srand(time(NULL));
      ogg_stream_init(&amp;out_stream, rand());
  
      // go through all input files
      for (i = 1; i &lt; argc; i++) {
          vorbis_header_read = 0;
          found_bos = false;
  
          fd = open(argv[i], O_RDONLY);
          if (fd &lt; 0) {
              fprintf(stderr, "cannot open %s\n", argv[1]);
              return 1;
          }
  
          ret = ogg_sync_init(&amp;state);
          if (ret != 0) {
              fprintf(stderr, "ogg_sync_init failed\n");
              return 1;
          }
  
          vorbis_info_init(&amp;info);
          vorbis_comment_init(&amp;comment);
  
          // go through all ogg pages
          while (read_page(fd, &amp;state, &amp;page) == 0) {
              serial = ogg_page_serialno(&amp;page);
  
              if (ogg_page_bos(&amp;page)) {
                  if (found_bos) {
                      fprintf(stderr, "cannot handle more than one stream\n");
                      return 1;
                  }
                  ret = ogg_stream_init(&amp;sstate, serial);
                  if (ret != 0) {
                      fprintf(stderr, "ogg_stream_init failed\n");
                      return 1;
                  }
                  found_bos = true;
              }
  
              if (!found_bos) {
                  fprintf(stderr, "cannot continue without bos\n");
                  return 1;
              }
  
              ret = ogg_stream_pagein(&amp;sstate, &amp;page);
              if (ret != 0) {
                  fprintf(stderr, "ogg_stream_pagein failed\n");
                  return 1;
              }
  
              // if this is the last page, then only write it if we are in the
              // last file
              if (ogg_page_eos(&amp;page) &amp;&amp; i != argc - 1) {
                  continue;
              }
  
              // go through all (hopefully vorbis) packets
              while((ret = ogg_stream_packetout(&amp;sstate, &amp;packet)) != 0) {
                  if (ret != 1) {
                      fprintf(stderr, "ogg_stream_packetout failed\n");
                      return 1;
                  }
  
                  // test if this stream is vorbis
                  if (vorbis_header_read == 0) {
                      ret = vorbis_synthesis_idheader(&amp;packet);
                      if (ret == 0) {
                          fprintf(stderr, "stream is not vorbis\n");
                          return 1;
                      }
                  }
  
                  // read exactly three vorbis headers
                  if (vorbis_header_read &lt; 3) {
                      ret = vorbis_synthesis_headerin(&amp;info, &amp;comment, &amp;packet);
                      if (ret != 0) {
                          fprintf(stderr, "vorbis_synthesis_headerin failed\n");
                          return 1;
                      }
                      // if this is the first file, copy the header packet to the
                      // output
                      if (i == 1) {
                          ret = ogg_stream_packetin(&amp;out_stream, &amp;packet);
                          if (ret != 0) {
                              fprintf(stderr, "ogg_stream_packetin failed\n");
                              return 1;
                          }
                      }
                      vorbis_header_read++;
                      continue;
                  }
  
                  // if this is the first file, write a page to the output
                  if (vorbis_header_read == 3 &amp;&amp; i == 1) {
                      while ((ret = ogg_stream_flush(&amp;out_stream, &amp;out_page)) != 0) {
                          bytes = write(STDOUT_FILENO, out_page.header, out_page.header_len);
                          if (bytes != out_page.header_len) {
                              fprintf(stderr, "write failed\n");
                              return 1;
                          }
                          bytes = write(STDOUT_FILENO, out_page.body, out_page.body_len);
                          if (bytes != out_page.body_len) {
                              fprintf(stderr, "write failed\n");
                              return 1;
                          }
                      }
                      vorbis_header_read++;
                  }
  
                  ogg_stream_packetin(&amp;out_stream, &amp;packet);
                  do {
                      ret = ogg_stream_pageout(&amp;out_stream, &amp;out_page);
                      if (ret == 0) break;
                      bytes = write(STDOUT_FILENO, out_page.header, out_page.header_len);
                      if (bytes != out_page.header_len) {
                          fprintf(stderr, "write failed\n");
                          return 1;
                      }
                      bytes = write(STDOUT_FILENO, out_page.body, out_page.body_len);
                      if (bytes != out_page.body_len) {
                          fprintf(stderr, "write failed\n");
                          return 1;
                      }
                  } while (!ogg_page_eos(&amp;out_page));
  
              }
          }
  
          vorbis_info_clear(&amp;info);
          vorbis_comment_clear(&amp;comment);
  
          ret = ogg_sync_clear(&amp;state);
          if (ret != 0) {
              fprintf(stderr, "ogg_sync_clear failed\n");
              return 1;
          }
  
          ret = ogg_stream_clear(&amp;sstate);
          if (ret != 0) {
              fprintf(stderr, "ogg_stream_clear failed\n");
              return 1;
          }
  
          close(fd);
      }
  
      ogg_stream_clear(&amp;out_stream);
  
      return 0;
  }
  </code></pre>
  
  <p>This nearly works but inserts barely audible click sounds at the points where the vorbis streams are joined.</p>
  
  <p>How to do this correctly?</p>
  
  <p>Can it be done at all?</p>