diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-14 22:19:21 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-10-14 22:19:21 +0300 |
commit | b073e4c6aad0a4c35ea89c4592d5b48a39bb61f5 (patch) | |
tree | 4c35506cd40ba0af0ef3c1d526435b8abd1b9f70 /src | |
parent | 625e3046cc636db5b0325b96b4c22610252a5ab4 (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.c | 111 | ||||
-rw-r--r-- | src/gmrequest.c | 3 | ||||
-rw-r--r-- | src/macos.h | 8 | ||||
-rw-r--r-- | src/main.c | 29 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 23 |
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 | ||
35 | iDeclareType(ContentSpec) | 39 | iDeclareType(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 | ||
73 | enum iDecoderStatus { | 80 | enum 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 | ||
158 | static enum iDecoderStatus decodeVorbis_Decoder_(iDecoder *d, iRanges inputRange) { | 165 | static 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 | |||
176 | static 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); | 237 | enum 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 | ||
300 | iDefineTypeConstructionArgs(Decoder, (iInputBuf *input, const iContentSpec *spec), | 366 | iDefineTypeConstructionArgs(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 | ||
29 | iBool shouldDefaultToMetalRenderer_MacOS (void); | 29 | iBool shouldDefaultToMetalRenderer_MacOS (void); |
30 | 30 | ||
31 | void setupApplication_MacOS (void); | 31 | void enableMomentumScroll_MacOS (void); |
32 | void insertMenuItems_MacOS (const char *menuLabel, int atIndex, const iMenuItem *items, size_t count); | 32 | void registerURLHandler_MacOS (void); |
33 | void handleCommand_MacOS (const char *cmd); | 33 | void setupApplication_MacOS (void); |
34 | void insertMenuItems_MacOS (const char *menuLabel, int atIndex, const iMenuItem *items, size_t count); | ||
35 | void handleCommand_MacOS (const char *cmd); | ||
@@ -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 |
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, 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) |
33 | extern void enableMomentumScroll_MacOS(void); | 26 | # include "macos.h" |
34 | extern 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 | ||
41 | int main(int argc, char **argv) { | 40 | int 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 | } |