libgui 2.01
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
17GuiSoundOggPlayer::GuiSoundOggPlayer() : vfOpen(false), threadRunning(false), streamPaused(false), sampleRate(0), channels(0) {
18 pcmBuffer[0] = (uint8_t*)memalign(32, BUFFER_SIZE);
19 pcmBuffer[1] = (uint8_t*)memalign(32, BUFFER_SIZE);
20 bufferReady[0] = false;
21 bufferReady[1] = false;
22 decodeIndex = 0;
23 playIndex = 0;
24}
25
26GuiSoundOggPlayer::~GuiSoundOggPlayer() {
27 stop();
28 free(pcmBuffer[0]);
29 free(pcmBuffer[1]);
30}
31
32size_t GuiSoundOggPlayer::readOgg(void* ptr, size_t size, size_t nmemb, void* datasource) {
33 MemFile* mem = static_cast<MemFile*>(datasource);
34 size_t bytesToRead = size * nmemb;
35 if (mem->pos >= mem->size) return 0;
36 if (static_cast<size_t>(mem->pos) + bytesToRead > static_cast<size_t>(mem->size)) {
37 bytesToRead = static_cast<size_t>(mem->size - mem->pos);
38 }
39 memcpy(ptr, mem->data + mem->pos, bytesToRead);
40 mem->pos += bytesToRead;
41 return bytesToRead / size;
42}
43
44int GuiSoundOggPlayer::seekOgg(void* datasource, ogg_int64_t offset, int whence) {
45 MemFile* mem = static_cast<MemFile*>(datasource);
46 switch (whence) {
47 case SEEK_SET: mem->pos = offset; break;
48 case SEEK_CUR: mem->pos += offset; break;
49 case SEEK_END: mem->pos = mem->size + offset; break;
50 }
51 if (mem->pos < 0) mem->pos = 0;
52 if (mem->pos > mem->size) mem->pos = mem->size;
53 return 0;
54}
55
56int GuiSoundOggPlayer::closeOgg(void*) { return 0; }
57long GuiSoundOggPlayer::tellOgg(void* datasource) { return static_cast<MemFile*>(datasource)->pos; }
58
59bool GuiSoundOggPlayer::play(const uint8_t* data, int32_t length, int time_pos, bool loop) {
60 stop();
61
62 memFile.data = data;
63 memFile.size = length;
64 memFile.pos = 0;
65
66 ov_callbacks cb = { readOgg, seekOgg, closeOgg, tellOgg };
67 if (ov_open_callbacks(&memFile, &vf, nullptr, 0, cb) < 0) {
68 return false;
69 }
70 vfOpen = true;
71
72 vorbis_info* vi = ov_info(&vf, -1);
73 sampleRate = vi->rate;
74 channels = vi->channels;
75 streamLoop = loop;
76
77 if (time_pos > 0) ov_time_seek(&vf, time_pos);
78
79 bufferReady[0] = false;
80 bufferReady[1] = false;
81 decodeIndex = 0;
82 playIndex = 0;
83
84 threadRunning = true;
85 streamPaused = false;
86
87 if (!decodeThread.start(threadEntry, this, 16384, ThreadPriority::High)) {
88 // Don't leave isPlaying() true with no decode thread behind it
89 threadRunning = false;
90 ov_clear(&vf);
91 vfOpen = false;
92 return false;
93 }
94
95 return true;
96}
97
99 // The decode thread clears threadRunning itself when a non-looping stream
100 // hits EOF, so threadRunning says nothing about whether the thread still
101 // needs joining.
102 threadRunning = false;
103
104 if (decodeThread.isRunning())
105 decodeThread.join();
106
107 if (vfOpen) {
108 ov_clear(&vf);
109 vfOpen = false;
110 }
111}
112
114 streamPaused = pause;
115}
116
117const uint8_t* GuiSoundOggPlayer::getReadyBuffer(int32_t* outSize) {
118 if (bufferReady[playIndex]) {
119 *outSize = pcmBufferSize[playIndex];
120 return pcmBuffer[playIndex];
121 }
122 return nullptr;
123}
124
126 bufferReady[playIndex] = false;
127 playIndex ^= 1;
128}
129
130void* GuiSoundOggPlayer::threadEntry(void* arg) {
131 static_cast<GuiSoundOggPlayer*>(arg)->threadLoop();
132 return nullptr;
133}
134
135void GuiSoundOggPlayer::threadLoop() {
136 while (threadRunning && !decodeThread.stopRequested()) {
137 if (streamPaused) {
138 usleep(10000);
139 continue;
140 }
141
142 if (!bufferReady[decodeIndex]) {
143 int bytesRead = 0;
144 // Accumulate data until the buffer is full or the stream ends
145 while (bytesRead < BUFFER_SIZE && threadRunning && !streamPaused && !decodeThread.stopRequested()) {
146 int currentSection = 0;
147 long ret = ov_read(&vf, (char*)pcmBuffer[decodeIndex] + bytesRead, BUFFER_SIZE - bytesRead, &currentSection);
148
149 if (ret == 0) {
150 if (streamLoop) {
151 ov_pcm_seek(&vf, 0);
152 } else {
153 if (bytesRead == 0) threadRunning = false;
154 break;
155 }
156 } else if (ret < 0) {
157 if (ret != OV_HOLE && !streamLoop) {
158 if (bytesRead == 0) threadRunning = false;
159 break;
160 }
161 } else {
162 bytesRead += ret;
163 }
164 }
165
166 if (bytesRead > 0) {
167 DCFlushRange(pcmBuffer[decodeIndex], bytesRead);
168 pcmBufferSize[decodeIndex] = bytesRead;
169 bufferReady[decodeIndex] = true;
170 decodeIndex ^= 1;
171 } else if (!threadRunning) {
172 break;
173 }
174 } else {
175 usleep(2000); // 2ms hardware wait
176 }
177 }
178}
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.