Support Vorbis comments from a multiplexed Ogg stream (#1370)

Ogg::File::readPages() read pages from all logical bitstreams and
indexed their packets into one global list. In a multiplexed file the
Theora and Vorbis packets got interleaved, so packet 0 was a Theora
header rather than the expected Vorbis type-3 comment header.

Before reading packets, the Vorbis reader now selects the Vorbis
logical bitstream (the one whose first packet is the Vorbis
identification header), so it reads the correct stream regardless of
position. Packet parsing is scoped to the selected bitstream, so
packets from other codecs in the same file are ignored.

tests/data/multiplex.ogg is generated using

ffmpeg -hide_banner -y \
  -f lavfi -i "color=c=navy:s=500x500:r=1:d=2" \
  -f lavfi -i "sine=frequency=440:sample_rate=48000:duration=2" \
  -map 0:v -map 1:a \
  -c:v libtheora -q:v 3 \
  -c:a libvorbis -ac 2 -q:a 2 \
  -flags +bitexact -fflags +bitexact \
  -metadata:s:a:0 TITLE="Paper Lights" \
  -metadata:s:a:0 encoder= -metadata:s:v:0 encoder= \
  -f ogg tests/data/multiplex.ogg
This commit is contained in:
Urs Fleisch
2026-08-05 20:57:29 +02:00
committed by GitHub
parent d781aaed7f
commit e547578ae8
5 changed files with 142 additions and 22 deletions
Binary file not shown.
+41
View File
@@ -32,6 +32,7 @@
#include "oggfile.h"
#include "vorbisfile.h"
#include "oggpageheader.h"
#include "plainfile.h"
#include <cppunit/extensions/HelperMacros.h>
#include "utils.h"
@@ -47,6 +48,7 @@ class TestOGG : public CppUnit::TestFixture
CPPUNIT_TEST(testDictInterface1);
CPPUNIT_TEST(testDictInterface2);
CPPUNIT_TEST(testAudioProperties);
CPPUNIT_TEST(testMultiplexed);
CPPUNIT_TEST(testPageChecksum);
CPPUNIT_TEST(testPageGranulePosition);
CPPUNIT_TEST_SUITE_END();
@@ -200,6 +202,45 @@ public:
CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->bitrateMinimum());
}
void testMultiplexed()
{
// A multiplexed Ogg stream where the Vorbis logical bitstream is not the
// first one (it is preceded by a Theora video stream). The Vorbis comment
// and audio properties must still be read from the Vorbis stream.
ScopedFileCopy copy("multiplex", ".ogg");
string filename = copy.fileName();
{
Vorbis::File f(filename.c_str());
CPPUNIT_ASSERT(f.isValid());
CPPUNIT_ASSERT(f.tag());
CPPUNIT_ASSERT_EQUAL(String("Paper Lights"), f.tag()->title());
CPPUNIT_ASSERT(f.audioProperties());
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
CPPUNIT_ASSERT_EQUAL(48000, f.audioProperties()->sampleRate());
f.tag()->setTitle("Changed Title");
CPPUNIT_ASSERT(f.save());
}
{
Vorbis::File f(filename.c_str());
CPPUNIT_ASSERT(f.isValid());
CPPUNIT_ASSERT_EQUAL(String("Changed Title"), f.tag()->title());
f.tag()->setTitle("Paper Lights");
CPPUNIT_ASSERT(f.save());
}
{
Vorbis::File f(filename.c_str());
CPPUNIT_ASSERT(f.isValid());
CPPUNIT_ASSERT_EQUAL(String("Paper Lights"), f.tag()->title());
}
// Check if the modified file is byte for byte equal to the original file
const ByteVector origData = PlainFile(TEST_FILE_PATH_C("multiplex.ogg")).readAll();
const ByteVector fileData = PlainFile(filename.c_str()).readAll();
CPPUNIT_ASSERT(origData == fileData);
}
void testPageChecksum()
{
ScopedFileCopy copy("empty", ".ogg");