Skip to content

Commit 774ef05

Browse files
committed
Draft: VorbisDecoder, OggVorbisDecoder
1 parent ffb6c5e commit 774ef05

File tree

3 files changed

+271
-1
lines changed

3 files changed

+271
-1
lines changed

src/AudioTools/AudioCodecs/ContainerOgg.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#include "AudioTools/AudioCodecs/AudioCodecsBase.h"
44
#include "AudioTools/AudioCodecs/CodecOpus.h"
55
#include "AudioTools/CoreAudio/Buffers.h"
6-
#include "oggz/oggz.h"
6+
#include "oggz.h"
77

88
#define OGG_READ_SIZE (1024)
99
#define OGG_DEFAULT_BUFFER_SIZE (OGG_READ_SIZE)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#pragma once
2+
3+
#include "AudioTools/AudioCodecs/AudioCodecsBase.h"
4+
#include "AudioTools/AudioCodecs/VorbisDecoder.h"
5+
#include "AudioTools/AudioCodecs/ContainerOgg.h"
6+
7+
8+
namespace audio_tools {
9+
10+
/**
11+
* @brief Ogg Vorbis Decoder
12+
*
13+
* This class wraps VorbisDecoder in an Ogg container decoder, allowing
14+
* decoding of Ogg Vorbis streams with automatic packet extraction.
15+
*
16+
* Usage:
17+
* 1. Instantiate OggVorbisDecoder.
18+
* 2. Feed Ogg Vorbis data to the decoder.
19+
* 3. PCM output is provided via the underlying VorbisDecoder.
20+
*
21+
* @author Phil Schatzmann
22+
* @ingroup codecs
23+
* @ingroup decoder
24+
* @copyright GPLv3
25+
*/
26+
class OggVorbisDecoder : public OggContainerDecoder {
27+
public:
28+
/**
29+
* @brief Constructor for OggVorbisDecoder
30+
* Initializes the decoder and sets the underlying VorbisDecoder.
31+
*/
32+
OggVorbisDecoder() : OggContainerDecoder() { setDecoder(&vorbis); }
33+
34+
protected:
35+
/** @brief Underlying Vorbis decoder */
36+
VorbisDecoder vorbis;
37+
};
38+
39+
} // namespace audio_tools
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
#pragma once
2+
3+
4+
#include "AudioTools/AudioCodecs/AudioCodecsBase.h"
5+
#include "AudioTools/CoreAudio/Buffers.h"
6+
7+
#include <vorbis.h>
8+
9+
namespace audio_tools {
10+
11+
/**
12+
* @brief Vorbis Audio Decoder using low-level libvorbis API
13+
*
14+
* This decoder expects Ogg Vorbis packets to be provided via the write()
15+
* method. It parses the Vorbis headers, initializes the decoder, and outputs
16+
* PCM audio.
17+
*
18+
* Usage:
19+
* 1. Call begin() to reset the decoder.
20+
* 2. Feed the first three Vorbis header packets via write().
21+
* 3. Feed subsequent audio packets via write().
22+
* 4. Use setOutput() to set the PCM output destination.
23+
* 5. Call audioInfo() to retrieve stream parameters after header parsing.
24+
*
25+
* @author Phil Schatzmann
26+
* @ingroup codecs
27+
* @ingroup decoder
28+
* @copyright GPLv3
29+
*/
30+
class VorbisDecoder : public AudioDecoder {
31+
public:
32+
/**
33+
* @brief Constructor for VorbisDecoder
34+
* @param buffer_size Size of the PCM output buffer (default: 256)
35+
* @param header_packets Number of Vorbis header packets (default: 3)
36+
*
37+
* Initializes the decoder and allocates the PCM output buffer.
38+
*/
39+
VorbisDecoder(size_t buffer_size = 256, int header_packets = 3)
40+
: pcm_buffer_size(buffer_size), num_header_packets(header_packets) {}
41+
42+
/**
43+
* @brief Destructor for VorbisDecoder
44+
*
45+
* Cleans up all decoder resources.
46+
*/
47+
~VorbisDecoder() { end(); }
48+
49+
/**
50+
* @brief Resets decoder state and prepares for new Vorbis stream
51+
*
52+
* This method clears all decoder state, resizes the PCM output buffer,
53+
* and initializes Vorbis structures. Call this before feeding header packets.
54+
* @return true if successful
55+
*/
56+
bool begin() override {
57+
end();
58+
pcmout_buffer.resize(pcm_buffer_size);
59+
vorbis_info_init(&vi);
60+
vorbis_comment_init(&vc);
61+
active = true;
62+
return true;
63+
}
64+
65+
/**
66+
* @brief Cleans up all Vorbis decoder structures
67+
*/
68+
void end() override {
69+
vorbis_block_clear(&vb);
70+
vorbis_dsp_clear(&vd);
71+
vorbis_comment_clear(&vc);
72+
vorbis_info_clear(&vi);
73+
header_packets = 0;
74+
decoder_initialized = false;
75+
active = false;
76+
}
77+
78+
/**
79+
* @brief Feeds a Vorbis packet (header or audio) to the decoder
80+
*
81+
* The first three packets must be Vorbis headers. Subsequent packets are
82+
* audio. PCM output is written to the Print stream set via setOutput().
83+
*
84+
* @param data Pointer to packet data
85+
* @param len Length of packet data
86+
* @return Number of PCM bytes written to output
87+
*/
88+
size_t write(const uint8_t *data, size_t len) override {
89+
ogg_packet packet;
90+
packet.packet = (unsigned char *)data;
91+
packet.bytes = len;
92+
packet.b_o_s = (header_packets == 0) ? 1 : 0;
93+
packet.e_o_s = 0;
94+
packet.granulepos = 0;
95+
packet.packetno = header_packets;
96+
97+
if (num_header_packets == 0 && !decoder_initialized) {
98+
if (!initDecoder()) return 0;
99+
decoder_initialized = true;
100+
}
101+
if (header_packets < num_header_packets) {
102+
if (!parseHeaderPacket(packet, header_packets)) return 0;
103+
header_packets++;
104+
if (header_packets == num_header_packets) {
105+
if (!initDecoder()) return 0;
106+
decoder_initialized = true;
107+
}
108+
return 0;
109+
}
110+
if (header_packets == num_header_packets) {
111+
notifyAudioChange(audioInfo());
112+
}
113+
if (!decoder_initialized) return 0;
114+
return decodeAudioPacket(packet);
115+
}
116+
117+
/**
118+
* @brief Returns audio stream info (sample rate, channels, bits per sample)
119+
* @return AudioInfo struct with stream parameters
120+
*/
121+
AudioInfo audioInfo() override {
122+
AudioInfo info;
123+
if (vi.channels > 0 && vi.rate > 0) {
124+
info.sample_rate = vi.rate;
125+
info.channels = vi.channels;
126+
info.bits_per_sample = 16;
127+
}
128+
return info;
129+
}
130+
131+
/**
132+
* @brief Returns true if decoder is active
133+
*/
134+
operator bool() override { return active; }
135+
136+
protected:
137+
/** @brief Vorbis stream info (channels, sample rate, etc.) */
138+
vorbis_info vi{};
139+
/** @brief Vorbis comment metadata */
140+
vorbis_comment vc{};
141+
/** @brief Decoder state for synthesis */
142+
vorbis_dsp_state vd{};
143+
/** @brief Block structure for synthesis */
144+
vorbis_block vb{};
145+
/** @brief Output stream for PCM audio */
146+
Print *p_print = nullptr;
147+
/** @brief Decoder active state */
148+
bool active = false;
149+
/** @brief PCM output buffer size */
150+
size_t pcm_buffer_size = 256;
151+
/** @brief Number of Vorbis header packets */
152+
int num_header_packets = 3;
153+
/** @brief Buffer for interleaved PCM output */
154+
Vector<int16_t> pcmout_buffer;
155+
int header_packets = 0;
156+
bool decoder_initialized = false;
157+
/**
158+
* @brief Parses a Vorbis header packet
159+
* @param packet Ogg Vorbis header packet
160+
* @param header_packets Index of header packet (0, 1, 2)
161+
* @return true if successful
162+
*/
163+
bool parseHeaderPacket(ogg_packet &packet, int header_packets) {
164+
if (vorbis_synthesis_headerin(&vi, &vc, &packet) != 0) {
165+
LOGE("Header packet %d invalid", header_packets);
166+
return false;
167+
}
168+
return true;
169+
}
170+
171+
/**
172+
* @brief Initializes the Vorbis decoder after header parsing
173+
* @return true if successful
174+
*/
175+
bool initDecoder() {
176+
if (vorbis_synthesis_init(&vd, &vi) != 0) {
177+
LOGE("vorbis_synthesis_init failed");
178+
return false;
179+
}
180+
vorbis_block_init(&vd, &vb);
181+
return true;
182+
}
183+
184+
/**
185+
* @brief Decodes an audio packet and writes PCM to output
186+
* @param packet Ogg Vorbis audio packet
187+
* @return Number of PCM bytes written
188+
*/
189+
size_t decodeAudioPacket(ogg_packet &packet) {
190+
size_t total_written = 0;
191+
if (vorbis_synthesis(&vb, &packet) == 0) {
192+
vorbis_synthesis_blockin(&vd, &vb);
193+
float **pcm = nullptr;
194+
int samples = vorbis_synthesis_pcmout(&vd, &pcm);
195+
while (samples > 0 && pcm) {
196+
int chunk = (samples > pcm_buffer_size) ? pcm_buffer_size : samples;
197+
convertFloatToInt16PCM(pcm, chunk, vi.channels);
198+
if (!pcmout_buffer.empty() && p_print) {
199+
p_print->write((uint8_t *)pcmout_buffer.data(),
200+
pcmout_buffer.size() * sizeof(int16_t));
201+
total_written += pcmout_buffer.size() * sizeof(int16_t);
202+
pcmout_buffer.clear();
203+
}
204+
vorbis_synthesis_read(&vd, chunk);
205+
samples = vorbis_synthesis_pcmout(&vd, &pcm);
206+
}
207+
}
208+
return total_written;
209+
}
210+
211+
/**
212+
* @brief Converts float PCM to interleaved int16 PCM and stores in
213+
* pcmout_buffer
214+
* @param pcm Pointer to float PCM array [channels][samples]
215+
* @param samples Number of samples
216+
* @param channels Number of channels
217+
*/
218+
void convertFloatToInt16PCM(float **pcm, int samples, int channels) {
219+
for (int i = 0; i < samples; ++i) {
220+
for (int ch = 0; ch < channels; ++ch) {
221+
float val = pcm[ch][i];
222+
int16_t sample = (int16_t)(val * 32767.0f);
223+
if (sample > 32767) sample = 32767;
224+
if (sample < -32768) sample = -32768;
225+
pcmout_buffer.push_back(sample);
226+
}
227+
}
228+
}
229+
};
230+
231+
} // namespace audio_tools

0 commit comments

Comments
 (0)