libgui 3.00
libgui is a GUI library for the GameCube, Wii, and Wii U
 
Loading...
Searching...
No Matches
GuiSoundOggPlayer.cpp
1/****************************************************************************
2 * libgui
3 * Daryl Borth 2026
4 * GuiSoundOggPlayer.cpp
5 ***************************************************************************/
6#include "GuiSoundOggPlayer.h"
7#include <malloc.h>
8#include <string.h>
9#include <unistd.h>
10
11#ifdef __WIIU__
12#include <coreinit/cache.h>
13#else
14#include <ogc/cache.h>
15#endif
16
17#define OGGPLAYER_THREAD_STACKSIZE (8 * 1024)
18
19GuiSoundOggPlayer::GuiSoundOggPlayer() : vfOpen(false), threadRunning(false), streamPaused(false), sampleRate(0), channels(0) {
20 pcmBuffer[0] = (uint8_t*)memalign(32, BUFFER_SIZE);
21 pcmBuffer[1] = (uint8_t*)memalign(32, BUFFER_SIZE);
22 bufferReady[0] = false;
23 bufferReady[1] = false;
24 decodeIndex = 0;
25 playIndex = 0;
26}
27
28GuiSoundOggPlayer::~GuiSoundOggPlayer() {
29 stop();
30 free(pcmBuffer[0]);
31 free(pcmBuffer[1]);
32}
33
34size_t GuiSoundOggPlayer::readOgg(void* ptr, size_t size, size_t nmemb, void* datasource) {
35 MemFile* mem = static_cast<MemFile*>(datasource);
36 size_t bytesToRead = size * nmemb;
37 if (mem->pos >= mem->size) return 0;
38 if (static_cast<size_t>(mem->pos) + bytesToRead > static_cast<size_t>(mem->size)) {
39 bytesToRead = static_cast<size_t>(mem->size - mem->pos);
40 }
41 memcpy(ptr, mem->data + mem->pos, bytesToRead);
42 mem->pos += bytesToRead;
43 return bytesToRead / size;
44}
45
46int GuiSoundOggPlayer::seekOgg(void* datasource, ogg_int64_t offset, int whence) {
47 MemFile* mem = static_cast<MemFile*>(datasource);
48 switch (whence) {
49 case SEEK_SET: mem->pos = offset; break;
50 case SEEK_CUR: mem->pos += offset; break;
51 case SEEK_END: mem->pos = mem->size + offset; break;
52 }
53 if (mem->pos < 0) mem->pos = 0;
54 if (mem->pos > mem->size) mem->pos = mem->size;
55 return 0;
56}
57
58int GuiSoundOggPlayer::closeOgg(void*) { return 0; }
59long GuiSoundOggPlayer::tellOgg(void* datasource) { return static_cast<MemFile*>(datasource)->pos; }
60
61bool GuiSoundOggPlayer::play(const uint8_t* data, int32_t length, int time_pos, bool loop) {
62 stop();
63
64 memFile.data = data;
65 memFile.size = length;
66 memFile.pos = 0;
67
68 ov_callbacks cb = { readOgg, seekOgg, closeOgg, tellOgg };
69 if (ov_open_callbacks(&memFile, &vf, nullptr, 0, cb) < 0) {
70 return false;
71 }
72 vfOpen = true;
73
74 vorbis_info* vi = ov_info(&vf, -1);
75 sampleRate = vi->rate;
76 channels = vi->channels;
77 streamLoop = loop;
78
79 if (time_pos > 0) ov_time_seek(&vf, time_pos);
80
81 bufferReady[0] = false;
82 bufferReady[1] = false;
83 decodeIndex = 0;
84 playIndex = 0;
85
86 threadRunning = true;
87 streamPaused = false;
88
89 if (!decodeThread.start(threadEntry, this, OGGPLAYER_THREAD_STACKSIZE, ThreadPriority::High)) {
90 // Don't leave isPlaying() true with no decode thread behind it
91 threadRunning = false;
92 ov_clear(&vf);
93 vfOpen = false;
94 return false;
95 }
96
97 return true;
98}
99
101 // The decode thread clears threadRunning itself when a non-looping stream
102 // hits EOF, so threadRunning says nothing about whether the thread still
103 // needs joining. Skipping the join left decodeThread's handle set (which
104 // makes the next Thread::start() fail) and leaked its stack/OSThread.
105 threadRunning = false;
106
107 if (decodeThread.isRunning())
108 decodeThread.join();
109
110 if (vfOpen) {
111 ov_clear(&vf);
112 vfOpen = false;
113 }
114}
115
117 streamPaused = pause;
118}
119
120const uint8_t* GuiSoundOggPlayer::getReadyBuffer(int32_t* outSize) {
121 if (bufferReady[playIndex]) {
122 *outSize = pcmBufferSize[playIndex];
123 return pcmBuffer[playIndex];
124 }
125 return nullptr;
126}
127
129 bufferReady[playIndex] = false;
130 playIndex ^= 1;
131}
132
133void* GuiSoundOggPlayer::threadEntry(void* arg) {
134 static_cast<GuiSoundOggPlayer*>(arg)->threadLoop();
135 return nullptr;
136}
137
138void GuiSoundOggPlayer::threadLoop() {
139 while (threadRunning && !decodeThread.stopRequested()) {
140 if (streamPaused) {
141 usleep(10000);
142 continue;
143 }
144
145 if (!bufferReady[decodeIndex]) {
146 int bytesRead = 0;
147 // Accumulate data until the buffer is full or the stream ends
148 while (bytesRead < BUFFER_SIZE && threadRunning && !streamPaused && !decodeThread.stopRequested()) {
149 int currentSection = 0;
150 long ret = ov_read(&vf, (char*)pcmBuffer[decodeIndex] + bytesRead, BUFFER_SIZE - bytesRead, &currentSection);
151
152 if (ret == 0) {
153 if (streamLoop) {
154 ov_pcm_seek(&vf, 0);
155 } else {
156 if (bytesRead == 0) threadRunning = false;
157 break;
158 }
159 } else if (ret < 0) {
160 if (ret != OV_HOLE && !streamLoop) {
161 if (bytesRead == 0) threadRunning = false;
162 break;
163 }
164 } else {
165 bytesRead += ret;
166 }
167 }
168
169 if (bytesRead > 0) {
170 DCFlushRange(pcmBuffer[decodeIndex], bytesRead);
171 pcmBufferSize[decodeIndex] = bytesRead;
172 bufferReady[decodeIndex] = true;
173 decodeIndex ^= 1;
174 } else if (!threadRunning) {
175 break;
176 }
177 } else {
178 usleep(2000); // 2ms hardware wait
179 }
180 }
181}
Platform-agnostic Tremor (integer OGG) decoder.
void consumeBuffer()
Releases the buffer last returned by getReadyBuffer(), letting the decode thread reuse that ring slot...
void pause(bool pause)
Pauses or resumes the decode thread without stopping it.
const uint8_t * getReadyBuffer(int32_t *outSize)
bool play(const uint8_t *data, int32_t length, int time_pos, bool loop)
Starts decoding data on a new thread.
void stop()
Stops playback and joins the decode thread.