A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2005-2007 Miika Pekkarinen
11 * Copyright (C) 2007-2008 Nicolas Pennequin
12 * Copyright (C) 2011 Michael Sevakis
13 *
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version 2
17 * of the License, or (at your option) any later version.
18 *
19 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
20 * KIND, either express or implied.
21 *
22 ****************************************************************************/
23
24#include "config.h"
25#include "system.h"
26#include "kernel.h"
27#include "codecs.h"
28#include "codec_thread.h"
29#include "pcmbuf.h"
30#include "audio_thread.h"
31#include "playback.h"
32#include "buffering.h"
33#include "dsp_core.h"
34#include "metadata.h"
35#include "settings.h"
36
37/* Define LOGF_ENABLE to enable logf output in this file */
38/*#define LOGF_ENABLE*/
39#include "logf.h"
40
41/* macros to enable logf for queues
42 logging on SYS_TIMEOUT can be disabled */
43#ifdef SIMULATOR
44/* Define this for logf output of all queuing except SYS_TIMEOUT */
45#define PLAYBACK_LOGQUEUES
46/* Define this to logf SYS_TIMEOUT messages */
47/*#define PLAYBACK_LOGQUEUES_SYS_TIMEOUT*/
48#endif
49
50#ifdef PLAYBACK_LOGQUEUES
51#define LOGFQUEUE logf
52#else
53#define LOGFQUEUE(...)
54#endif
55
56#ifdef PLAYBACK_LOGQUEUES_SYS_TIMEOUT
57#define LOGFQUEUE_SYS_TIMEOUT logf
58#else
59#define LOGFQUEUE_SYS_TIMEOUT(...)
60#endif
61
62/* Variables are commented with the threads that use them:
63 * A=audio, C=codec
64 * - = reads only
65 *
66 * Unless otherwise noted, the extern variables are located
67 * in playback.c.
68 */
69
70/* Q_LOAD_CODEC parameter data */
71struct codec_load_info
72{
73 int hid; /* audio handle id (specify < 0 to use afmt) */
74 int afmt; /* codec specification (AFMT_*) */
75};
76
77
78/** --- Main state control --- **/
79
80static int codec_type = AFMT_UNKNOWN; /* Codec type (C,A-) */
81
82/* Private interfaces to main playback control */
83extern void audio_codec_update_elapsed(unsigned long elapsed);
84extern void audio_codec_update_offset(size_t offset);
85extern void audio_codec_complete(int status);
86extern void audio_codec_seek_complete(void);
87extern struct codec_api ci; /* from codecs.c */
88
89/* Codec thread */
90static unsigned int codec_thread_id; /* For modifying thread priority later */
91static struct event_queue codec_queue SHAREDBSS_ATTR;
92static struct queue_sender_list codec_queue_sender_list SHAREDBSS_ATTR;
93
94/* Workaround stack overflow in opus codec on highmem devices (see FS#13060). */
95/* Fixed 2019-8-14 (see FS#13131) */
96#if 0 /*!defined(CPU_COLDFIRE) && (MEMORYSIZE >= 8) && defined(IRAMSIZE) && IRAMSIZE > (32 * 1024)*/
97#define WORKAROUND_FS13060 0x800
98#else
99#define WORKAROUND_FS13060 0
100#endif
101
102static long codec_stack[(DEFAULT_STACK_SIZE + 0x2000 + WORKAROUND_FS13060)/sizeof(long)] IBSS_ATTR;
103static const char codec_thread_name[] = "codec";
104
105static void unload_codec(void);
106
107/* Messages are only ever sent one at a time to the codec from the audio
108 thread. This is important for correct operation unless playback is
109 stopped. */
110
111/* static routines */
112static void codec_queue_ack(intptr_t ackme)
113{
114 queue_reply(&codec_queue, ackme);
115}
116
117static intptr_t codec_queue_send(long id, intptr_t data)
118{
119 return queue_send(&codec_queue, id, data);
120}
121
122/* Poll the state of the codec queue. Returns < 0 if the message is urgent
123 and any state should exit, > 0 if it's a run message (and it was
124 scrubbed), 0 if message was ignored. */
125static int codec_check_queue__have_msg(void)
126{
127 struct queue_event ev;
128
129 queue_peek(&codec_queue, &ev);
130
131 /* Seek, pause or stop? Just peek and return if so. Codec
132 must handle the command after returing. Inserts will not
133 be allowed until it complies. */
134 switch (ev.id)
135 {
136 case Q_CODEC_SEEK:
137 LOGFQUEUE("codec - Q_CODEC_SEEK %ld", ev.id);
138 return -1;
139 case Q_CODEC_PAUSE:
140 LOGFQUEUE("codec - Q_CODEC_PAUSE %ld", ev.id);
141 return -1;
142 case Q_CODEC_STOP:
143 LOGFQUEUE("codec - Q_CODEC_STOP %ld", ev.id);
144 return -1;
145 }
146
147 /* This is in error in this context unless it's "go, go, go!" */
148 queue_wait(&codec_queue, &ev);
149
150 if (ev.id == Q_CODEC_RUN)
151 {
152 logf("codec < Q_CODEC_RUN: already running!");
153 codec_queue_ack(Q_CODEC_RUN);
154 return 1;
155 }
156
157 /* Ignore it */
158 logf("codec < bad req %ld (%s)", ev.id, __func__);
159 codec_queue_ack(Q_NULL);
160 return 0;
161}
162
163/* Does the audio format type equal CODEC_TYPE_ENCODER? */
164static inline bool type_is_encoder(int afmt)
165{
166#ifdef AUDIO_HAVE_RECORDING
167 return (afmt & CODEC_TYPE_MASK) == CODEC_TYPE_ENCODER;
168#else
169 return false;
170 (void)afmt;
171#endif
172}
173
174/**************************************/
175
176
177/** --- Miscellaneous external functions --- **/
178const char * get_codec_filename(int cod_spec)
179{
180 const char *fname;
181
182#ifdef HAVE_RECORDING
183 /* Can choose decoder or encoder if one available */
184 int type = cod_spec & CODEC_TYPE_MASK;
185 int afmt = cod_spec & CODEC_AFMT_MASK;
186 int tmp_fmt = afmt;
187 if ((unsigned)afmt >= AFMT_NUM_CODECS)
188 {
189 type = AFMT_UNKNOWN | (type & CODEC_TYPE_MASK);
190 tmp_fmt = AFMT_UNKNOWN;
191 }
192 fname = (type == CODEC_TYPE_ENCODER) ?
193 get_codec_enc_root_fn(tmp_fmt) :
194 audio_formats[tmp_fmt].codec_root_fn;
195
196 logf("%s: %d - %s",
197 (type == CODEC_TYPE_ENCODER) ? "Encoder" : "Decoder",
198 afmt, fname ? fname : "<unknown>");
199#else /* !HAVE_RECORDING */
200 /* Always decoder */
201 if ((unsigned)cod_spec >= AFMT_NUM_CODECS)
202 cod_spec = AFMT_UNKNOWN;
203 fname = audio_formats[cod_spec].codec_root_fn;
204 logf("Codec: %d - %s", cod_spec, fname ? fname : "<unknown>");
205#endif /* HAVE_RECORDING */
206
207 return fname;
208}
209
210/* Borrow the codec thread and return the ID */
211void codec_thread_do_callback(void (*fn)(void), unsigned int *id)
212{
213 /* Set id before telling thread to call something; it may be
214 * needed before this function returns. */
215 if (id != NULL)
216 *id = codec_thread_id;
217
218 /* Codec thread will signal just before entering callback */
219 LOGFQUEUE("codec >| Q_CODEC_DO_CALLBACK");
220 codec_queue_send(Q_CODEC_DO_CALLBACK, (intptr_t)fn);
221}
222
223
224/** --- codec API callbacks --- **/
225
226static void codec_pcmbuf_insert_callback(
227 const void *ch1, const void *ch2, int count)
228{
229 struct dsp_buffer src;
230 src.remcount = count;
231 src.pin[0] = ch1;
232 src.pin[1] = ch2;
233 src.proc_mask = 0;
234
235 while (LIKELY(queue_empty(&codec_queue)) ||
236 codec_check_queue__have_msg() >= 0)
237 {
238 struct dsp_buffer dst;
239 dst.remcount = 0;
240 dst.bufcount = MAX(src.remcount, 1024); /* Arbitrary min request */
241
242 if ((dst.p16out = pcmbuf_request_buffer(&dst.bufcount)) == NULL)
243 {
244 cancel_cpu_boost();
245
246 /* It may be awhile before space is available but we want
247 "instant" response to any message */
248 queue_wait_w_tmo(&codec_queue, NULL, HZ/20);
249 }
250 else
251 {
252 dsp_process(ci.dsp, &src, &dst, true);
253
254 if (dst.remcount > 0)
255 {
256 pcmbuf_write_complete(dst.remcount, ci.id3->elapsed,
257 ci.id3->offset);
258 }
259 else if (src.remcount <= 0)
260 {
261 return; /* No input remains and DSP purged */
262 }
263 }
264 }
265}
266
267/* helper function, not a callback */
268static bool codec_advance_buffer_counters(size_t amount)
269{
270 if (bufadvance(ci.audio_hid, amount) < 0)
271 {
272 bufseek(ci.audio_hid, ci.filesize);
273 ci.curpos = ci.filesize;
274 return false;
275 }
276
277 ci.curpos += amount;
278 return true;
279}
280
281/* copy up-to size bytes into ptr and return the actual size copied */
282static size_t codec_filebuf_callback(void *ptr, size_t size)
283{
284 ssize_t copy_n = bufread(ci.audio_hid, size, ptr);
285
286 /* Nothing requested OR nothing left */
287 if (copy_n <= 0)
288 return 0;
289
290 /* Update read and other position pointers */
291 codec_advance_buffer_counters(copy_n);
292
293 /* Return the actual amount of data copied to the buffer */
294 return copy_n;
295}
296
297static void * codec_request_buffer_callback(size_t *realsize, size_t reqsize)
298{
299 size_t copy_n = reqsize;
300 ssize_t ret;
301 void *ptr;
302
303 ret = bufgetdata(ci.audio_hid, reqsize, &ptr);
304 if (ret >= 0)
305 copy_n = MIN((size_t)ret, reqsize);
306 else
307 copy_n = 0;
308
309 if (copy_n == 0)
310 ptr = NULL;
311
312 *realsize = copy_n;
313 return ptr;
314}
315
316static void codec_advance_buffer_callback(size_t amount)
317{
318 if (!codec_advance_buffer_counters(amount))
319 return;
320
321 audio_codec_update_offset(ci.curpos);
322}
323
324static bool codec_seek_buffer_callback(size_t newpos)
325{
326 logf("codec_seek_buffer_callback");
327
328 int ret = bufseek(ci.audio_hid, newpos);
329 if (ret == 0)
330 {
331 ci.curpos = newpos;
332 return true;
333 }
334
335 return false;
336}
337
338static void codec_seek_complete_callback(void)
339{
340 logf("seek_complete");
341
342 /* Clear DSP */
343 dsp_configure(ci.dsp, DSP_FLUSH, 0);
344
345 /* Sync position */
346 audio_codec_update_offset(ci.curpos);
347
348 /* Post notification to audio thread */
349 audio_codec_seek_complete();
350
351 /* Wait for urgent or go message */
352 do
353 {
354 queue_wait(&codec_queue, NULL);
355 }
356 while (codec_check_queue__have_msg() == 0);
357}
358
359static void codec_configure_callback(int setting, intptr_t value)
360{
361 dsp_configure(ci.dsp, setting, value);
362}
363
364static long codec_get_command_callback(intptr_t *param)
365{
366 yield();
367
368 if (LIKELY(queue_empty(&codec_queue)))
369 return CODEC_ACTION_NULL; /* As you were */
370
371 /* Process the message - return requested action and data (if any should
372 be expected) */
373 while (1)
374 {
375 long action = CODEC_ACTION_NULL;
376 struct queue_event ev;
377
378 queue_peek(&codec_queue, &ev); /* Find out what it is */
379
380 intptr_t id = ev.id;
381
382 switch (id)
383 {
384 case Q_NULL:
385 LOGFQUEUE("codec < Q_NULL");
386 break;
387
388 case Q_CODEC_RUN: /* Already running */
389 LOGFQUEUE("codec < Q_CODEC_RUN");
390 break;
391
392 case Q_CODEC_PAUSE: /* Stay here and wait */
393 LOGFQUEUE("codec < Q_CODEC_PAUSE");
394 queue_wait(&codec_queue, &ev); /* Remove message */
395 codec_queue_ack(Q_CODEC_PAUSE);
396 queue_wait(&codec_queue, NULL); /* Wait for next (no remove) */
397 continue;
398
399 case Q_CODEC_SEEK: /* Audio wants codec to seek */
400 LOGFQUEUE("codec < Q_CODEC_SEEK %ld", ev.data);
401 *param = ev.data;
402 action = CODEC_ACTION_SEEK_TIME;
403 trigger_cpu_boost();
404 break;
405
406 case Q_CODEC_STOP: /* Must only return 0 in main loop */
407 LOGFQUEUE("codec < Q_CODEC_STOP: %ld", ev.data);
408#ifdef HAVE_RECORDING
409 if (type_is_encoder(codec_type))
410 {
411 /* Stream finish request (soft stop)? */
412 if (ev.data && param)
413 {
414 /* ev.data is pointer to size */
415 *param = ev.data;
416 action = CODEC_ACTION_STREAM_FINISH;
417 break;
418 }
419 }
420 else
421#endif /* HAVE_RECORDING */
422 {
423 dsp_configure(ci.dsp, DSP_FLUSH, 0); /* Discontinuity */
424 }
425
426 return CODEC_ACTION_HALT; /* Leave in queue */
427
428 default: /* This is in error in this context. */
429 logf("codec bad req %ld (%s)", ev.id, __func__);
430 id = Q_NULL;
431 }
432
433 queue_wait(&codec_queue, &ev); /* Actually remove it */
434 codec_queue_ack(id);
435 return action;
436 }
437}
438
439static bool codec_loop_track_callback(void)
440{
441 return global_settings.repeat_mode == REPEAT_ONE;
442}
443
444void codec_strip_filesize_callback(off_t size)
445{
446 if (bufstripsize(ci.audio_hid, size) >= 0)
447 ci.filesize = size;
448}
449
450/** --- CODEC THREAD --- **/
451
452/* Handle Q_CODEC_LOAD */
453static void load_codec(const struct codec_load_info *ev_data)
454{
455 int status = CODEC_ERROR;
456 /* Save a local copy so we can let the audio thread go ASAP */
457 struct codec_load_info data = *ev_data;
458 bool const encoder = type_is_encoder(data.afmt);
459
460 if (codec_type != AFMT_UNKNOWN)
461 {
462 /* Must have unloaded it first */
463 logf("a codec is already loaded");
464 if (data.hid >= 0)
465 bufclose(data.hid);
466 return;
467 }
468
469 trigger_cpu_boost();
470
471 if (!encoder)
472 {
473 /* Do this now because codec may set some things up at load time */
474 dsp_configure(ci.dsp, DSP_RESET, 0);
475 }
476
477 if (data.hid >= 0)
478 {
479 /* First try buffer load */
480 status = codec_load_buf(data.hid, &ci);
481 bufclose(data.hid);
482 }
483
484 if (status < 0)
485 {
486 /* Either not a valid handle or the buffer method failed */
487 const char *codec_fn = get_codec_filename(data.afmt);
488 if (codec_fn)
489 status = codec_load_file(codec_fn, &ci);
490 }
491
492 /* Types must agree */
493 if (status >= 0 && encoder == !!codec_get_enc_callback())
494 {
495 codec_type = data.afmt;
496 codec_queue_ack(Q_CODEC_LOAD);
497 return;
498 }
499
500 /* Failed - get rid of it */
501 unload_codec();
502}
503
504/* Handle Q_CODEC_RUN */
505static void run_codec(void)
506{
507 bool const encoder = type_is_encoder(codec_type);
508 int status;
509
510 if (codec_type == AFMT_UNKNOWN)
511 {
512 logf("no codec to run");
513 return;
514 }
515
516 codec_queue_ack(Q_CODEC_RUN);
517
518 trigger_cpu_boost();
519 dsp_configure(ci.dsp, DSP_SET_OUT_FREQUENCY, pcmbuf_get_frequency());
520
521 if (!encoder)
522 {
523 /* This will be either the initial buffered offset or where it left off
524 if it remained buffered and we're skipping back to it and it is best
525 to have ci.curpos in sync with the handle's read position - it's the
526 codec's responsibility to ensure it has the correct positions -
527 playback is sorta dumb and only has a vague idea about what to
528 buffer based upon what metadata has to say */
529 ci.curpos = bufftell(ci.audio_hid);
530
531 /* Pin the codec's audio data in place */
532 buf_pin_handle(ci.audio_hid, true);
533 }
534
535 status = codec_run_proc();
536
537 if (!encoder)
538 {
539 /* Codec is done with it - let it move */
540 buf_pin_handle(ci.audio_hid, false);
541
542 /* Notify audio that we're done for better or worse - advise of the
543 status */
544 audio_codec_complete(status);
545 }
546}
547
548/* Handle Q_CODEC_SEEK */
549static void seek_codec(unsigned long time)
550{
551 if (codec_type == AFMT_UNKNOWN)
552 {
553 logf("no codec to seek");
554 codec_queue_ack(Q_CODEC_SEEK);
555 codec_seek_complete_callback();
556 return;
557 }
558
559 /* Post it up one level */
560 queue_post(&codec_queue, Q_CODEC_SEEK, time);
561 codec_queue_ack(Q_CODEC_SEEK);
562
563 /* Have to run it again */
564 run_codec();
565}
566
567/* Handle Q_CODEC_UNLOAD */
568static void unload_codec(void)
569{
570 /* Tell codec to clean up */
571 codec_type = AFMT_UNKNOWN;
572 codec_close();
573}
574
575/* Handle Q_CODEC_DO_CALLBACK */
576static void do_callback(void (* callback)(void))
577{
578 codec_queue_ack(Q_CODEC_DO_CALLBACK);
579
580 if (callback)
581 {
582 commit_discard_idcache();
583 callback();
584 commit_dcache();
585 }
586}
587
588/* Codec thread function */
589static void NORETURN_ATTR codec_thread(void)
590{
591 struct queue_event ev;
592
593 while (1)
594 {
595 cancel_cpu_boost();
596
597 queue_wait(&codec_queue, &ev);
598
599 switch (ev.id)
600 {
601 case Q_CODEC_LOAD:
602 LOGFQUEUE("codec < Q_CODEC_LOAD");
603 load_codec((const struct codec_load_info *)ev.data);
604 break;
605
606 case Q_CODEC_RUN:
607 LOGFQUEUE("codec < Q_CODEC_RUN");
608 run_codec();
609 break;
610
611 case Q_CODEC_PAUSE:
612 LOGFQUEUE("codec < Q_CODEC_PAUSE");
613 break;
614
615 case Q_CODEC_SEEK:
616 LOGFQUEUE("codec < Q_CODEC_SEEK: %lu", (unsigned long)ev.data);
617 seek_codec(ev.data);
618 break;
619
620 case Q_CODEC_UNLOAD:
621 LOGFQUEUE("codec < Q_CODEC_UNLOAD");
622 unload_codec();
623 break;
624
625 case Q_CODEC_DO_CALLBACK:
626 LOGFQUEUE("codec < Q_CODEC_DO_CALLBACK");
627 do_callback((void (*)(void))ev.data);
628 break;
629
630 default:
631 LOGFQUEUE("codec < default : %ld", ev.id);
632 }
633 }
634}
635
636
637/** --- Miscellaneous external interfaces -- **/
638
639/* Initialize playback's codec interface */
640void INIT_ATTR codec_thread_init(void)
641{
642 /* Init API */
643 ci.dsp = dsp_get_config(CODEC_IDX_AUDIO);
644 ci.codec_get_buffer = codec_get_buffer_callback;
645 ci.pcmbuf_insert = codec_pcmbuf_insert_callback;
646 ci.set_elapsed = audio_codec_update_elapsed;
647 ci.read_filebuf = codec_filebuf_callback;
648 ci.request_buffer = codec_request_buffer_callback;
649 ci.advance_buffer = codec_advance_buffer_callback;
650 ci.seek_buffer = codec_seek_buffer_callback;
651 ci.seek_complete = codec_seek_complete_callback;
652 ci.set_offset = audio_codec_update_offset;
653 ci.configure = codec_configure_callback;
654 ci.get_command = codec_get_command_callback;
655 ci.loop_track = codec_loop_track_callback;
656 ci.strip_filesize = codec_strip_filesize_callback;
657
658 /* Init threading */
659 queue_init(&codec_queue, false);
660 codec_thread_id = create_thread(
661 codec_thread, codec_stack, sizeof(codec_stack), 0,
662 codec_thread_name IF_PRIO(, PRIORITY_PLAYBACK)
663 IF_COP(, CPU));
664 queue_enable_queue_send(&codec_queue, &codec_queue_sender_list,
665 codec_thread_id);
666}
667
668#ifdef HAVE_PRIORITY_SCHEDULING
669/* Obtain codec thread's current priority */
670int codec_thread_get_priority(void)
671{
672 return thread_get_priority(codec_thread_id);
673}
674
675/* Set the codec thread's priority and return the old value */
676int codec_thread_set_priority(int priority)
677{
678 return thread_set_priority(codec_thread_id, priority);
679}
680#endif /* HAVE_PRIORITY_SCHEDULING */
681
682
683/** --- Functions for audio thread use --- **/
684
685/* Load a decoder or encoder and set the format type */
686bool codec_load(int hid, int cod_spec)
687{
688 struct codec_load_info parm = { hid, cod_spec };
689
690 LOGFQUEUE("audio >| codec Q_CODEC_LOAD: %d, %d", hid, cod_spec);
691 return codec_queue_send(Q_CODEC_LOAD, (intptr_t)&parm) != 0;
692}
693
694/* Begin decoding the current file */
695void codec_go(void)
696{
697 LOGFQUEUE("audio >| codec Q_CODEC_RUN");
698 codec_queue_send(Q_CODEC_RUN, 0);
699}
700
701/* Instruct the codec to seek to the specified time (should be properly
702 paused or stopped first to avoid possible buffering deadlock) */
703void codec_seek(long time)
704{
705 LOGFQUEUE("audio > codec Q_CODEC_SEEK: %ld", time);
706 codec_queue_send(Q_CODEC_SEEK, time);
707}
708
709/* Pause the codec and make it wait for further instructions inside the
710 command callback */
711bool codec_pause(void)
712{
713 LOGFQUEUE("audio >| codec Q_CODEC_PAUSE");
714 return codec_queue_send(Q_CODEC_PAUSE, 0) != Q_NULL;
715}
716
717/* Stop codec if running - codec stays resident if loaded */
718void codec_stop(void)
719{
720 /* Wait until it's in the main loop */
721 LOGFQUEUE("audio >| codec Q_CODEC_STOP: 0");
722 while (codec_queue_send(Q_CODEC_STOP, 0) != Q_NULL);
723}
724
725#ifdef HAVE_RECORDING
726/* Tells codec to take final encoding step and then exit -
727 Returns minimum buffer size required or 0 if complete */
728size_t codec_finish_stream(void)
729{
730 size_t size = 0;
731
732 LOGFQUEUE("audio >| codec Q_CODEC_STOP: &size");
733 if (codec_queue_send(Q_CODEC_STOP, (intptr_t)&size) != Q_NULL)
734 {
735 /* Sync to keep size in scope and get response */
736 LOGFQUEUE("audio >| codec Q_NULL");
737 codec_queue_send(Q_NULL, 0);
738
739 if (size == 0)
740 codec_stop(); /* Replied with 0 size */
741 }
742 /* else thread running in the main loop */
743
744 return size;
745}
746#endif /* HAVE_RECORDING */
747
748/* Call the codec's exit routine and close all references */
749void codec_unload(void)
750{
751 codec_stop();
752 LOGFQUEUE("audio >| codec Q_CODEC_UNLOAD");
753 codec_queue_send(Q_CODEC_UNLOAD, 0);
754}
755
756/* Return the afmt type of the loaded codec - sticks until calling
757 codec_unload unless initial load failed */
758int codec_loaded(void)
759{
760 return codec_type;
761}