A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 761 lines 21 kB view raw
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}