summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-10-14 22:19:21 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-10-14 22:19:21 +0300
commitb073e4c6aad0a4c35ea89c4592d5b48a39bb61f5 (patch)
tree4c35506cd40ba0af0ef3c1d526435b8abd1b9f70 /src
parent625e3046cc636db5b0325b96b4c22610252a5ab4 (diff)
Use libmpg123 to decode MPEG audio
mpg123 is configured as an optional dependency. Works for full files currently.
Diffstat (limited to 'src')
-rw-r--r--src/audio/player.c111
-rw-r--r--src/gmrequest.c3
-rw-r--r--src/macos.h8
-rw-r--r--src/main.c29
-rw-r--r--src/ui/documentwidget.c23
5 files changed, 137 insertions, 37 deletions
diff --git a/src/audio/player.c b/src/audio/player.c
index b2a6ae15..ef238378 100644
--- a/src/audio/player.c
+++ b/src/audio/player.c
@@ -30,6 +30,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
30#include <the_Foundation/thread.h> 30#include <the_Foundation/thread.h>
31#include <SDL_audio.h> 31#include <SDL_audio.h>
32 32
33#if defined (LAGRANGE_ENABLE_MPG123)
34# include <mpg123.h>
35#endif
36
33/*----------------------------------------------------------------------------------------------*/ 37/*----------------------------------------------------------------------------------------------*/
34 38
35iDeclareType(ContentSpec) 39iDeclareType(ContentSpec)
@@ -68,6 +72,9 @@ struct Impl_Decoder {
68 uint64_t currentSample; 72 uint64_t currentSample;
69 uint64_t totalSamples; /* zero if unknown */ 73 uint64_t totalSamples; /* zero if unknown */
70 stb_vorbis * vorbis; 74 stb_vorbis * vorbis;
75#if defined (LAGRANGE_ENABLE_MPG123)
76 mpg123_handle * mpeg;
77#endif
71}; 78};
72 79
73enum iDecoderStatus { 80enum iDecoderStatus {
@@ -155,8 +162,18 @@ static enum iDecoderStatus decodeWav_Decoder_(iDecoder *d, iRanges inputRange) {
155 return ok_DecoderStatus; 162 return ok_DecoderStatus;
156} 163}
157 164
158static enum iDecoderStatus decodeVorbis_Decoder_(iDecoder *d, iRanges inputRange) { 165static void writePending_Decoder_(iDecoder *d) {
159 iUnused(inputRange); 166 /* Write as much as we can. */
167 lock_Mutex(&d->outputMutex);
168 size_t avail = vacancy_SampleBuf(&d->output);
169 size_t n = iMin(avail, size_Array(&d->pendingOutput));
170 write_SampleBuf(&d->output, constData_Array(&d->pendingOutput), n);
171 removeN_Array(&d->pendingOutput, 0, n);
172 unlock_Mutex(&d->outputMutex);
173 d->currentSample += n;
174}
175
176static enum iDecoderStatus decodeVorbis_Decoder_(iDecoder *d) {
160 const iBlock *input = &d->input->data; 177 const iBlock *input = &d->input->data;
161 if (!d->vorbis) { 178 if (!d->vorbis) {
162 lock_Mutex(&d->input->mtx); 179 lock_Mutex(&d->input->mtx);
@@ -213,14 +230,47 @@ static enum iDecoderStatus decodeVorbis_Decoder_(iDecoder *d, iRanges inputRange
213 } 230 }
214 } 231 }
215 } 232 }
216 /* Write as much as we can. */ 233 writePending_Decoder_(d);
217 lock_Mutex(&d->outputMutex); 234 return status;
218 size_t avail = vacancy_SampleBuf(&d->output); 235}
219 size_t n = iMin(avail, size_Array(&d->pendingOutput)); 236
220 write_SampleBuf(&d->output, constData_Array(&d->pendingOutput), n); 237enum iDecoderStatus decodeMpeg_Decoder_(iDecoder *d) {
221 removeN_Array(&d->pendingOutput, 0, n); 238 enum iDecoderStatus status = ok_DecoderStatus;
222 unlock_Mutex(&d->outputMutex); 239#if defined (LAGRANGE_ENABLE_MPG123)
223 d->currentSample += n; 240 const iBlock *input = &d->input->data;
241 if (!d->mpeg) {
242 d->mpeg = mpg123_new(NULL, NULL);
243 mpg123_format_none(d->mpeg);
244 mpg123_format(d->mpeg, d->outputFreq, d->output.numChannels, MPG123_ENC_SIGNED_16);
245 mpg123_open_feed(d->mpeg);
246 lock_Mutex(&d->input->mtx);
247 mpg123_feed(d->mpeg, constData_Block(input), size_Block(input));
248 unlock_Mutex(&d->input->mtx);
249 long r; int ch, enc;
250 mpg123_getformat(d->mpeg, &r, &ch, &enc);
251 iAssert(r == d->outputFreq);
252 iAssert(ch == d->output.numChannels);
253 iAssert(enc == MPG123_ENC_SIGNED_16);
254 }
255 while (size_Array(&d->pendingOutput) < d->output.count) {
256 int16_t buffer[512];
257 size_t bytesWritten = 0;
258 const int rc = mpg123_read(d->mpeg, buffer, sizeof(buffer), &bytesWritten);
259 const float gain = d->gain;
260 for (size_t i = 0; i < bytesWritten / 2; i++) {
261 buffer[i] *= gain;
262 }
263 pushBackN_Array(&d->pendingOutput, buffer, bytesWritten / 2 / d->output.numChannels);
264 if (rc == MPG123_NEED_MORE) {
265 status = needMoreInput_DecoderStatus;
266 break;
267 }
268 else if (rc == MPG123_DONE) {
269 break;
270 }
271 }
272 writePending_Decoder_(d);
273#endif
224 return status; 274 return status;
225} 275}
226 276
@@ -242,7 +292,11 @@ static iThreadResult run_Decoder_(iThread *thread) {
242 status = decodeWav_Decoder_(d, inputRange); 292 status = decodeWav_Decoder_(d, inputRange);
243 break; 293 break;
244 case vorbis_DecoderType: 294 case vorbis_DecoderType:
245 status = decodeVorbis_Decoder_(d, inputRange); 295 status = decodeVorbis_Decoder_(d);
296 break;
297 case mpeg_DecoderType:
298 status = decodeMpeg_Decoder_(d);
299 break;
246 default: 300 default:
247 break; 301 break;
248 } 302 }
@@ -280,6 +334,9 @@ void init_Decoder(iDecoder *d, iInputBuf *input, const iContentSpec *spec) {
280 d->currentSample = 0; 334 d->currentSample = 0;
281 d->totalSamples = spec->totalSamples; 335 d->totalSamples = spec->totalSamples;
282 d->vorbis = NULL; 336 d->vorbis = NULL;
337#if defined (LAGRANGE_ENABLE_MPG123)
338 d->mpeg = NULL;
339#endif
283 init_Mutex(&d->outputMutex); 340 init_Mutex(&d->outputMutex);
284 d->thread = new_Thread(run_Decoder_); 341 d->thread = new_Thread(run_Decoder_);
285 setUserData_Thread(d->thread, d); 342 setUserData_Thread(d->thread, d);
@@ -295,6 +352,15 @@ void deinit_Decoder(iDecoder *d) {
295 deinit_Mutex(&d->outputMutex); 352 deinit_Mutex(&d->outputMutex);
296 deinit_SampleBuf(&d->output); 353 deinit_SampleBuf(&d->output);
297 deinit_Array(&d->pendingOutput); 354 deinit_Array(&d->pendingOutput);
355 if (d->vorbis) {
356 stb_vorbis_close(d->vorbis);
357 }
358#if defined (LAGRANGE_ENABLE_MPG123)
359 if (d->mpeg) {
360 mpg123_close(d->mpeg);
361 mpg123_delete(d->mpeg);
362 }
363#endif
298} 364}
299 365
300iDefineTypeConstructionArgs(Decoder, (iInputBuf *input, const iContentSpec *spec), 366iDefineTypeConstructionArgs(Decoder, (iInputBuf *input, const iContentSpec *spec),
@@ -336,6 +402,11 @@ static iContentSpec contentSpec_Player_(const iPlayer *d) {
336 !cmp_String(&d->mime, "audio/x-vorbis+ogg")) { 402 !cmp_String(&d->mime, "audio/x-vorbis+ogg")) {
337 content.type = vorbis_DecoderType; 403 content.type = vorbis_DecoderType;
338 } 404 }
405#if defined (LAGRANGE_ENABLE_MPG123)
406 else if (!cmp_String(&d->mime, "audio/mpeg") || !cmp_String(&d->mime, "audio/mp3")) {
407 content.type = mpeg_DecoderType;
408 }
409#endif
339 else { 410 else {
340 /* TODO: Could try decoders to see if one works? */ 411 /* TODO: Could try decoders to see if one works? */
341 content.type = none_DecoderType; 412 content.type = none_DecoderType;
@@ -438,6 +509,24 @@ static iContentSpec contentSpec_Player_(const iPlayer *d) {
438 content.inputFormat = AUDIO_F32; /* actually stb_vorbis provides floats */ 509 content.inputFormat = AUDIO_F32; /* actually stb_vorbis provides floats */
439 stb_vorbis_close(vrb); 510 stb_vorbis_close(vrb);
440 } 511 }
512 else if (content.type == mpeg_DecoderType) {
513#if defined (LAGRANGE_ENABLE_MPG123)
514 mpg123_handle *mh = mpg123_new(NULL, NULL);
515 mpg123_open_feed(mh);
516 mpg123_feed(mh, constData_Block(&d->data->data), size_Block(&d->data->data));
517 long rate = 0;
518 int channels = 0;
519 int encoding = 0;
520 if (mpg123_getformat(mh, &rate, &channels, &encoding) == MPG123_OK) {
521 content.output.freq = rate;
522 content.output.channels = channels;
523 content.inputFormat = AUDIO_S16;
524 content.output.format = AUDIO_S16;
525 }
526 mpg123_close(mh);
527 mpg123_delete(mh);
528#endif
529 }
441 iAssert(content.inputFormat == content.output.format || 530 iAssert(content.inputFormat == content.output.format ||
442 (content.inputFormat == AUDIO_S24LSB && content.output.format == AUDIO_S16) || 531 (content.inputFormat == AUDIO_S24LSB && content.output.format == AUDIO_S16) ||
443 (content.inputFormat == AUDIO_F64LSB && content.output.format == AUDIO_F32)); 532 (content.inputFormat == AUDIO_F64LSB && content.output.format == AUDIO_F32));
diff --git a/src/gmrequest.c b/src/gmrequest.c
index dd2c2720..7b6414d2 100644
--- a/src/gmrequest.c
+++ b/src/gmrequest.c
@@ -460,6 +460,9 @@ void submit_GmRequest(iGmRequest *d) {
460 else if (endsWithCase_String(path, ".ogg")) { 460 else if (endsWithCase_String(path, ".ogg")) {
461 setCStr_String(&d->resp.meta, "audio/ogg"); 461 setCStr_String(&d->resp.meta, "audio/ogg");
462 } 462 }
463 else if (endsWithCase_String(path, ".mp3")) {
464 setCStr_String(&d->resp.meta, "audio/mpeg");
465 }
463 else { 466 else {
464 setCStr_String(&d->resp.meta, "application/octet-stream"); 467 setCStr_String(&d->resp.meta, "application/octet-stream");
465 } 468 }
diff --git a/src/macos.h b/src/macos.h
index 700d50f8..ee06b69e 100644
--- a/src/macos.h
+++ b/src/macos.h
@@ -28,6 +28,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
28 28
29iBool shouldDefaultToMetalRenderer_MacOS (void); 29iBool shouldDefaultToMetalRenderer_MacOS (void);
30 30
31void setupApplication_MacOS (void); 31void enableMomentumScroll_MacOS (void);
32void insertMenuItems_MacOS (const char *menuLabel, int atIndex, const iMenuItem *items, size_t count); 32void registerURLHandler_MacOS (void);
33void handleCommand_MacOS (const char *cmd); 33void setupApplication_MacOS (void);
34void insertMenuItems_MacOS (const char *menuLabel, int atIndex, const iMenuItem *items, size_t count);
35void handleCommand_MacOS (const char *cmd);
diff --git a/src/main.c b/src/main.c
index 464176e6..de9132ab 100644
--- a/src/main.c
+++ b/src/main.c
@@ -20,25 +20,25 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 20(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ 21SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#include <the_Foundation/commandline.h>
24#include <stdio.h>
25#if defined (iPlatformMsys)
26# define SDL_MAIN_HANDLED
27#endif
28#include <SDL.h>
29
30#include "app.h" 23#include "app.h"
31 24
32#if defined (iPlatformApple) 25#if defined (iPlatformApple)
33extern void enableMomentumScroll_MacOS(void); 26# include "macos.h"
34extern void registerURLHandler_MacOS(void);
35#endif 27#endif
36
37#if defined (iPlatformMsys) 28#if defined (iPlatformMsys)
38# include "win32.h" 29# include "win32.h"
30# define SDL_MAIN_HANDLED
39#endif 31#endif
32#if defined (LAGRANGE_ENABLE_MPG123)
33# include <mpg123.h>
34#endif
35
36#include <the_Foundation/commandline.h>
37#include <SDL.h>
38#include <stdio.h>
40 39
41int main(int argc, char **argv) { 40int main(int argc, char **argv) {
41 printf("Lagrange: A Beautiful Gemini Client\n");
42#if defined (iPlatformApple) 42#if defined (iPlatformApple)
43 enableMomentumScroll_MacOS(); 43 enableMomentumScroll_MacOS();
44 registerURLHandler_MacOS(); 44 registerURLHandler_MacOS();
@@ -48,9 +48,11 @@ int main(int argc, char **argv) {
48 setDPIAware_Win32(); 48 setDPIAware_Win32();
49 SDL_SetMainReady(); 49 SDL_SetMainReady();
50#endif 50#endif
51 /* Initialize libraries. */
52#if defined (LAGRANGE_ENABLE_MPG123)
53 mpg123_init();
54#endif
51 init_Foundation(); 55 init_Foundation();
52 printf("Lagrange: A Beautiful Gemini Client\n");
53 /* Initialize SDL. */
54 SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1"); 56 SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
55 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { 57 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
56 fprintf(stderr, "SDL init failed: %s\n", SDL_GetError()); 58 fprintf(stderr, "SDL init failed: %s\n", SDL_GetError());
@@ -58,5 +60,8 @@ int main(int argc, char **argv) {
58 } 60 }
59 run_App(argc, argv); 61 run_App(argc, argv);
60 SDL_Quit(); 62 SDL_Quit();
63#if defined (LAGRANGE_ENABLE_MPG123)
64 mpg123_exit();
65#endif
61 return 0; 66 return 0;
62} 67}
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 5adb8bd0..b540a999 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -1584,17 +1584,18 @@ static iBool processAudioPlayerEvents_DocumentWidget_(iDocumentWidget *d, const
1584 if (contains_Rect(rect, mouse)) { 1584 if (contains_Rect(rect, mouse)) {
1585 iPlayerUI ui; 1585 iPlayerUI ui;
1586 init_PlayerUI(&ui, plr, rect); 1586 init_PlayerUI(&ui, plr, rect);
1587 if (ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEMOTION) { 1587 if (ev->type == SDL_MOUSEBUTTONDOWN && flags_Player(plr) & adjustingVolume_PlayerFlag &&
1588 if (ev->type == SDL_MOUSEBUTTONDOWN && 1588 contains_Rect(adjusted_Rect(ui.volumeAdjustRect,
1589 flags_Player(plr) & adjustingVolume_PlayerFlag && 1589 zero_I2(),
1590 contains_Rect(adjusted_Rect(ui.volumeAdjustRect, 1590 init_I2(-height_Rect(ui.volumeAdjustRect), 0)),
1591 zero_I2(), 1591 mouse)) {
1592 init_I2(-height_Rect(ui.volumeAdjustRect), 0)), 1592 setGrabbedPlayer_DocumentWidget_(d, run);
1593 mouse)) { 1593 processEvent_Click(&d->click, ev);
1594 setGrabbedPlayer_DocumentWidget_(d, run); 1594 /* The rest is done in the DocumentWidget click responder. */
1595 processEvent_Click(&d->click, ev); 1595 refresh_Widget(d);
1596 /* The rest is done in the DocumentWidget click responder. */ 1596 return iTrue;
1597 } 1597 }
1598 else if (ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEMOTION) {
1598 refresh_Widget(d); 1599 refresh_Widget(d);
1599 return iTrue; 1600 return iTrue;
1600 } 1601 }