diff options
Diffstat (limited to 'apps')
-rw-r--r-- | apps/gui/skin_engine/skin_render.c | 2 | ||||
-rw-r--r-- | apps/mpeg.c | 72 | ||||
-rw-r--r-- | apps/playback.c | 176 | ||||
-rw-r--r-- | apps/playback.h | 6 | ||||
-rwxr-xr-x | apps/playlist.c | 87 | ||||
-rw-r--r-- | apps/plugin.c | 19 | ||||
-rw-r--r-- | apps/plugin.h | 1 | ||||
-rw-r--r-- | apps/radio/radioart.c | 40 | ||||
-rw-r--r-- | apps/recorder/pcm_record.c | 20 | ||||
-rw-r--r-- | apps/recorder/recording.c | 10 | ||||
-rw-r--r-- | apps/talk.c | 761 | ||||
-rw-r--r-- | apps/talk.h | 21 |
12 files changed, 658 insertions, 557 deletions
diff --git a/apps/gui/skin_engine/skin_render.c b/apps/gui/skin_engine/skin_render.c index 28483cbc49..0d36711e91 100644 --- a/apps/gui/skin_engine/skin_render.c +++ b/apps/gui/skin_engine/skin_render.c | |||
@@ -280,13 +280,11 @@ static bool do_non_text_tags(struct gui_wps *gwps, struct skin_draw_info *info, | |||
280 | if (do_refresh && aa) | 280 | if (do_refresh && aa) |
281 | { | 281 | { |
282 | int handle = playback_current_aa_hid(data->playback_aa_slot); | 282 | int handle = playback_current_aa_hid(data->playback_aa_slot); |
283 | #if 0 /* FIXME: FS#12797*/ | ||
284 | if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF)) | 283 | if (in_radio_screen() || (get_radio_status() != FMRADIO_OFF)) |
285 | { | 284 | { |
286 | struct dim dim = {aa->width, aa->height}; | 285 | struct dim dim = {aa->width, aa->height}; |
287 | handle = radio_get_art_hid(&dim); | 286 | handle = radio_get_art_hid(&dim); |
288 | } | 287 | } |
289 | #endif | ||
290 | aa->draw_handle = handle; | 288 | aa->draw_handle = handle; |
291 | } | 289 | } |
292 | break; | 290 | break; |
diff --git a/apps/mpeg.c b/apps/mpeg.c index 2783a24085..5fd1bfdb82 100644 --- a/apps/mpeg.c +++ b/apps/mpeg.c | |||
@@ -527,7 +527,13 @@ static int shrink_callback(int handle, unsigned hints, void* start, size_t old_s | |||
527 | ssize_t size = (ssize_t)old_size - wanted_size; | 527 | ssize_t size = (ssize_t)old_size - wanted_size; |
528 | /* keep at least 256K for the buffering */ | 528 | /* keep at least 256K for the buffering */ |
529 | if ((size - extradata_size) < AUDIO_BUFFER_RESERVE) | 529 | if ((size - extradata_size) < AUDIO_BUFFER_RESERVE) |
530 | return BUFLIB_CB_CANNOT_SHRINK; | 530 | { |
531 | /* check if buflib needs the memory really hard. if yes we give | ||
532 | * up playback for now, otherwise refuse to shrink to keep at least | ||
533 | * 256K for the buffering */ | ||
534 | if ((hints & BUFLIB_SHRINK_POS_MASK) != BUFLIB_SHRINK_POS_MASK) | ||
535 | return BUFLIB_CB_CANNOT_SHRINK; | ||
536 | } | ||
531 | /* TODO: Do it without stopping playback, if possible */ | 537 | /* TODO: Do it without stopping playback, if possible */ |
532 | bool playing = (audio_status() & AUDIO_STATUS_PLAY) == AUDIO_STATUS_PLAY; | 538 | bool playing = (audio_status() & AUDIO_STATUS_PLAY) == AUDIO_STATUS_PLAY; |
533 | long offset = audio_current_track()->offset; | 539 | long offset = audio_current_track()->offset; |
@@ -539,7 +545,6 @@ static int shrink_callback(int handle, unsigned hints, void* start, size_t old_s | |||
539 | } | 545 | } |
540 | else | 546 | else |
541 | audio_stop(); | 547 | audio_stop(); |
542 | talk_buffer_steal(); /* we obtain control over the buffer */ | ||
543 | 548 | ||
544 | switch (hints & BUFLIB_SHRINK_POS_MASK) | 549 | switch (hints & BUFLIB_SHRINK_POS_MASK) |
545 | { | 550 | { |
@@ -551,6 +556,12 @@ static int shrink_callback(int handle, unsigned hints, void* start, size_t old_s | |||
551 | core_shrink(handle, start + wanted_size, size); | 556 | core_shrink(handle, start + wanted_size, size); |
552 | audio_reset_buffer_noalloc(start + wanted_size, size); | 557 | audio_reset_buffer_noalloc(start + wanted_size, size); |
553 | break; | 558 | break; |
559 | case BUFLIB_SHRINK_POS_MASK: | ||
560 | audiobuf_handle = core_free(audiobuf_handle); | ||
561 | mpeg_audiobuf = NULL; | ||
562 | talk_buffer_set_policy(TALK_BUFFER_DEFAULT); | ||
563 | playing = false; | ||
564 | break; | ||
554 | } | 565 | } |
555 | if (playing) | 566 | if (playing) |
556 | { /* safe to call even from the audio thread (due to queue_post()) */ | 567 | { /* safe to call even from the audio thread (due to queue_post()) */ |
@@ -565,45 +576,6 @@ static struct buflib_callbacks ops = { | |||
565 | .shrink_callback = shrink_callback, | 576 | .shrink_callback = shrink_callback, |
566 | }; | 577 | }; |
567 | 578 | ||
568 | static size_t audio_talkbuf_init(char *bufstart) | ||
569 | { | ||
570 | size_t ret = talkbuf_init(bufstart); | ||
571 | if (ret > (size_t)audiobuflen) /* does the voice even fit? */ | ||
572 | { | ||
573 | talk_buffer_steal(); | ||
574 | return 0; | ||
575 | } | ||
576 | return ret; | ||
577 | } | ||
578 | |||
579 | unsigned char * audio_get_buffer(bool talk_buf, size_t *buffer_size) | ||
580 | { | ||
581 | (void)talk_buf; /* always grab the voice buffer for now */ | ||
582 | |||
583 | if (audio_is_initialized) | ||
584 | audio_hard_stop(); | ||
585 | |||
586 | if (!buffer_size) /* special case for talk_init() */ | ||
587 | return NULL; | ||
588 | |||
589 | if (!audiobuf_handle) | ||
590 | { | ||
591 | size_t bufsize; | ||
592 | /* audio_hard_stop() frees audiobuf, so re-aquire */ | ||
593 | audiobuf_handle = core_alloc_maximum("audiobuf", &bufsize, &ops); | ||
594 | audiobuflen = bufsize; | ||
595 | if (buffer_size) | ||
596 | *buffer_size = audiobuflen; | ||
597 | } | ||
598 | mpeg_audiobuf = core_get_data(audiobuf_handle); | ||
599 | /* tell talk about the new buffer, don't re-enable just yet because the | ||
600 | * buffer is stolen */ | ||
601 | audio_talkbuf_init(mpeg_audiobuf); | ||
602 | |||
603 | return mpeg_audiobuf; | ||
604 | } | ||
605 | |||
606 | |||
607 | #ifndef SIMULATOR | 579 | #ifndef SIMULATOR |
608 | /* Send callback events to notify about removing old tracks. */ | 580 | /* Send callback events to notify about removing old tracks. */ |
609 | static void generate_unbuffer_events(void) | 581 | static void generate_unbuffer_events(void) |
@@ -2755,7 +2727,6 @@ size_t audio_buffer_available(void) | |||
2755 | 2727 | ||
2756 | static void audio_reset_buffer_noalloc(void* buf, size_t bufsize) | 2728 | static void audio_reset_buffer_noalloc(void* buf, size_t bufsize) |
2757 | { | 2729 | { |
2758 | talk_buffer_steal(); /* will use the mp3 buffer */ | ||
2759 | mpeg_audiobuf = buf; | 2730 | mpeg_audiobuf = buf; |
2760 | audiobuflen = bufsize; | 2731 | audiobuflen = bufsize; |
2761 | if (global_settings.cuesheet) | 2732 | if (global_settings.cuesheet) |
@@ -2764,16 +2735,20 @@ static void audio_reset_buffer_noalloc(void* buf, size_t bufsize) | |||
2764 | mpeg_audiobuf = SKIPBYTES(mpeg_audiobuf, sizeof(struct cuesheet)); | 2735 | mpeg_audiobuf = SKIPBYTES(mpeg_audiobuf, sizeof(struct cuesheet)); |
2765 | audiobuflen -= sizeof(struct cuesheet); | 2736 | audiobuflen -= sizeof(struct cuesheet); |
2766 | } | 2737 | } |
2767 | audio_talkbuf_init(mpeg_audiobuf); | ||
2768 | } | 2738 | } |
2769 | 2739 | ||
2770 | static void audio_reset_buffer(void) | 2740 | static void audio_reset_buffer(void) |
2771 | { | 2741 | { |
2772 | size_t bufsize = audiobuflen; | 2742 | size_t bufsize = audiobuflen; |
2773 | 2743 | ||
2774 | /* alloc buffer if it's was never allocated or freed by audio_hard_stop() */ | 2744 | /* alloc buffer if it's was never allocated or freed by audio_hard_stop() |
2745 | * because voice cannot be played during audio playback make | ||
2746 | * talk.c give up all buffers and disable itself */ | ||
2775 | if (!audiobuf_handle) | 2747 | if (!audiobuf_handle) |
2748 | { | ||
2749 | talk_buffer_set_policy(TALK_BUFFER_LOOSE); | ||
2776 | audiobuf_handle = core_alloc_maximum("audiobuf", &bufsize, &ops); | 2750 | audiobuf_handle = core_alloc_maximum("audiobuf", &bufsize, &ops); |
2751 | } | ||
2777 | 2752 | ||
2778 | audio_reset_buffer_noalloc(core_get_data(audiobuf_handle), bufsize); | 2753 | audio_reset_buffer_noalloc(core_get_data(audiobuf_handle), bufsize); |
2779 | } | 2754 | } |
@@ -2818,6 +2793,8 @@ void audio_play(long offset) | |||
2818 | 2793 | ||
2819 | void audio_stop(void) | 2794 | void audio_stop(void) |
2820 | { | 2795 | { |
2796 | if (audiobuf_handle <= 0) | ||
2797 | return; /* nothing to do, must be hard-stopped already */ | ||
2821 | #ifndef SIMULATOR | 2798 | #ifndef SIMULATOR |
2822 | mpeg_stop_done = false; | 2799 | mpeg_stop_done = false; |
2823 | queue_post(&mpeg_queue, MPEG_STOP, 0); | 2800 | queue_post(&mpeg_queue, MPEG_STOP, 0); |
@@ -2828,8 +2805,6 @@ void audio_stop(void) | |||
2828 | is_playing = false; | 2805 | is_playing = false; |
2829 | playing = false; | 2806 | playing = false; |
2830 | #endif /* SIMULATOR */ | 2807 | #endif /* SIMULATOR */ |
2831 | /* give voice our entire buffer */ | ||
2832 | audio_talkbuf_init(mpeg_audiobuf); | ||
2833 | } | 2808 | } |
2834 | 2809 | ||
2835 | /* dummy */ | 2810 | /* dummy */ |
@@ -2840,13 +2815,12 @@ void audio_stop_recording(void) | |||
2840 | 2815 | ||
2841 | void audio_hard_stop(void) | 2816 | void audio_hard_stop(void) |
2842 | { | 2817 | { |
2843 | audio_stop(); | ||
2844 | /* tell voice we obtain the buffer before freeing */ | ||
2845 | talk_buffer_steal(); | ||
2846 | if (audiobuf_handle > 0) | 2818 | if (audiobuf_handle > 0) |
2847 | { | 2819 | { |
2820 | audio_stop(); | ||
2848 | audiobuf_handle = core_free(audiobuf_handle); | 2821 | audiobuf_handle = core_free(audiobuf_handle); |
2849 | mpeg_audiobuf = NULL; | 2822 | mpeg_audiobuf = NULL; |
2823 | talk_buffer_set_policy(TALK_BUFFER_DEFAULT); | ||
2850 | } | 2824 | } |
2851 | } | 2825 | } |
2852 | 2826 | ||
diff --git a/apps/playback.c b/apps/playback.c index b240e95acd..5e234beb36 100644 --- a/apps/playback.c +++ b/apps/playback.c | |||
@@ -110,7 +110,6 @@ static enum audio_buffer_state | |||
110 | { | 110 | { |
111 | AUDIOBUF_STATE_TRASHED = -1, /* trashed; must be reset */ | 111 | AUDIOBUF_STATE_TRASHED = -1, /* trashed; must be reset */ |
112 | AUDIOBUF_STATE_INITIALIZED = 0, /* voice+audio OR audio-only */ | 112 | AUDIOBUF_STATE_INITIALIZED = 0, /* voice+audio OR audio-only */ |
113 | AUDIOBUF_STATE_VOICED_ONLY = 1, /* voice-only */ | ||
114 | } buffer_state = AUDIOBUF_STATE_TRASHED; /* (A,O) */ | 113 | } buffer_state = AUDIOBUF_STATE_TRASHED; /* (A,O) */ |
115 | 114 | ||
116 | /** Main state control **/ | 115 | /** Main state control **/ |
@@ -729,60 +728,42 @@ size_t audio_buffer_available(void) | |||
729 | /* Set up the audio buffer for playback | 728 | /* Set up the audio buffer for playback |
730 | * filebuflen must be pre-initialized with the maximum size */ | 729 | * filebuflen must be pre-initialized with the maximum size */ |
731 | static void audio_reset_buffer_noalloc( | 730 | static void audio_reset_buffer_noalloc( |
732 | void *filebuf, enum audio_buffer_state state) | 731 | void *filebuf) |
733 | { | 732 | { |
734 | /* | 733 | /* |
735 | * Layout audio buffer as follows: | 734 | * Layout audio buffer as follows: |
736 | * [[|TALK]|SCRATCH|BUFFERING|PCM] | 735 | * [|SCRATCH|BUFFERING|PCM] |
737 | */ | 736 | */ |
738 | |||
739 | /* see audio_get_recording_buffer if this is modified */ | ||
740 | logf("%s()", __func__); | 737 | logf("%s()", __func__); |
741 | 738 | ||
742 | /* If the setup of anything allocated before the file buffer is | 739 | /* If the setup of anything allocated before the file buffer is |
743 | changed, do check the adjustments after the buffer_alloc call | 740 | changed, do check the adjustments after the buffer_alloc call |
744 | as it will likely be affected and need sliding over */ | 741 | as it will likely be affected and need sliding over */ |
745 | |||
746 | /* Initially set up file buffer as all space available */ | ||
747 | size_t allocsize; | 742 | size_t allocsize; |
743 | /* Subtract whatever the pcm buffer says it used plus the guard | ||
744 | buffer */ | ||
745 | allocsize = pcmbuf_init(filebuf + filebuflen); | ||
748 | 746 | ||
749 | /* Subtract whatever voice needs (we're called when promoting | 747 | /* Make sure filebuflen is a pointer sized multiple after |
750 | the state only) */ | 748 | adjustment */ |
751 | allocsize = talkbuf_init(filebuf); | ||
752 | allocsize = ALIGN_UP(allocsize, sizeof (intptr_t)); | 749 | allocsize = ALIGN_UP(allocsize, sizeof (intptr_t)); |
753 | if (allocsize > filebuflen) | 750 | if (allocsize > filebuflen) |
754 | goto bufpanic; | 751 | goto bufpanic; |
755 | 752 | ||
756 | filebuf += allocsize; | ||
757 | filebuflen -= allocsize; | 753 | filebuflen -= allocsize; |
758 | 754 | ||
759 | if (state == AUDIOBUF_STATE_INITIALIZED) | 755 | /* Scratch memory */ |
760 | { | 756 | allocsize = scratch_mem_size(); |
761 | /* Subtract whatever the pcm buffer says it used plus the guard | 757 | if (allocsize > filebuflen) |
762 | buffer */ | 758 | goto bufpanic; |
763 | allocsize = pcmbuf_init(filebuf + filebuflen); | ||
764 | |||
765 | /* Make sure filebuflen is a pointer sized multiple after | ||
766 | adjustment */ | ||
767 | allocsize = ALIGN_UP(allocsize, sizeof (intptr_t)); | ||
768 | if (allocsize > filebuflen) | ||
769 | goto bufpanic; | ||
770 | |||
771 | filebuflen -= allocsize; | ||
772 | |||
773 | /* Scratch memory */ | ||
774 | allocsize = scratch_mem_size(); | ||
775 | if (allocsize > filebuflen) | ||
776 | goto bufpanic; | ||
777 | 759 | ||
778 | scratch_mem_init(filebuf); | 760 | scratch_mem_init(filebuf); |
779 | filebuf += allocsize; | 761 | filebuf += allocsize; |
780 | filebuflen -= allocsize; | 762 | filebuflen -= allocsize; |
781 | 763 | ||
782 | buffering_reset(filebuf, filebuflen); | 764 | buffering_reset(filebuf, filebuflen); |
783 | } | ||
784 | 765 | ||
785 | buffer_state = state; | 766 | buffer_state = AUDIOBUF_STATE_INITIALIZED; |
786 | 767 | ||
787 | #if defined(ROCKBOX_HAS_LOGF) && defined(LOGF_ENABLE) | 768 | #if defined(ROCKBOX_HAS_LOGF) && defined(LOGF_ENABLE) |
788 | /* Make sure everything adds up - yes, some info is a bit redundant but | 769 | /* Make sure everything adds up - yes, some info is a bit redundant but |
@@ -811,15 +792,24 @@ static int shrink_callback(int handle, unsigned hints, void* start, size_t old_s | |||
811 | /* codec messages */ | 792 | /* codec messages */ |
812 | { Q_AUDIO_PLAY, Q_AUDIO_PLAY }, | 793 | { Q_AUDIO_PLAY, Q_AUDIO_PLAY }, |
813 | }; | 794 | }; |
795 | bool give_up = false; | ||
814 | /* filebuflen is, at this point, the buffering.c buffer size, | 796 | /* filebuflen is, at this point, the buffering.c buffer size, |
815 | * i.e. the audiobuf except voice, scratch mem, pcm, ... */ | 797 | * i.e. the audiobuf except voice, scratch mem, pcm, ... */ |
816 | ssize_t extradata_size = old_size - filebuflen; | 798 | ssize_t extradata_size = old_size - filebuflen; |
817 | /* check what buflib requests */ | 799 | /* check what buflib requests */ |
818 | size_t wanted_size = (hints & BUFLIB_SHRINK_SIZE_MASK); | 800 | size_t wanted_size = (hints & BUFLIB_SHRINK_SIZE_MASK); |
819 | ssize_t size = (ssize_t)old_size - wanted_size; | 801 | ssize_t size = (ssize_t)old_size - wanted_size; |
820 | /* keep at least 256K for the buffering */ | 802 | |
821 | if ((size - extradata_size) < AUDIO_BUFFER_RESERVE) | 803 | if ((size - extradata_size) < AUDIO_BUFFER_RESERVE) |
822 | return BUFLIB_CB_CANNOT_SHRINK; | 804 | { |
805 | /* check if buflib needs the memory really hard. if yes we give | ||
806 | * up playback for now, otherwise refuse to shrink to keep at least | ||
807 | * 256K for the buffering */ | ||
808 | if ((hints & BUFLIB_SHRINK_POS_MASK) == BUFLIB_SHRINK_POS_MASK) | ||
809 | give_up = true; | ||
810 | else | ||
811 | return BUFLIB_CB_CANNOT_SHRINK; | ||
812 | } | ||
823 | 813 | ||
824 | 814 | ||
825 | /* TODO: Do it without stopping playback, if possible */ | 815 | /* TODO: Do it without stopping playback, if possible */ |
@@ -852,20 +842,26 @@ static int shrink_callback(int handle, unsigned hints, void* start, size_t old_s | |||
852 | #ifdef PLAYBACK_VOICE | 842 | #ifdef PLAYBACK_VOICE |
853 | voice_stop(); | 843 | voice_stop(); |
854 | #endif | 844 | #endif |
855 | /* we should be free to change the buffer now | 845 | |
856 | * set final buffer size before calling audio_reset_buffer_noalloc() | 846 | /* we should be free to change the buffer now */ |
847 | if (give_up) | ||
848 | { | ||
849 | buffer_state = AUDIOBUF_STATE_TRASHED; | ||
850 | audiobuf_handle = core_free(audiobuf_handle); | ||
851 | return BUFLIB_CB_OK; | ||
852 | } | ||
853 | /* set final buffer size before calling audio_reset_buffer_noalloc() | ||
857 | * (now it's the total size, the call will subtract voice etc) */ | 854 | * (now it's the total size, the call will subtract voice etc) */ |
858 | filebuflen = size; | 855 | filebuflen = size; |
859 | switch (hints & BUFLIB_SHRINK_POS_MASK) | 856 | switch (hints & BUFLIB_SHRINK_POS_MASK) |
860 | { | 857 | { |
861 | case BUFLIB_SHRINK_POS_BACK: | 858 | case BUFLIB_SHRINK_POS_BACK: |
862 | core_shrink(handle, start, size); | 859 | core_shrink(handle, start, size); |
863 | audio_reset_buffer_noalloc(start, buffer_state); | 860 | audio_reset_buffer_noalloc(start); |
864 | break; | 861 | break; |
865 | case BUFLIB_SHRINK_POS_FRONT: | 862 | case BUFLIB_SHRINK_POS_FRONT: |
866 | core_shrink(handle, start + wanted_size, size); | 863 | core_shrink(handle, start + wanted_size, size); |
867 | audio_reset_buffer_noalloc(start + wanted_size, | 864 | audio_reset_buffer_noalloc(start + wanted_size); |
868 | buffer_state); | ||
869 | break; | 865 | break; |
870 | } | 866 | } |
871 | if (playing || play_queued) | 867 | if (playing || play_queued) |
@@ -882,7 +878,7 @@ static struct buflib_callbacks ops = { | |||
882 | .shrink_callback = shrink_callback, | 878 | .shrink_callback = shrink_callback, |
883 | }; | 879 | }; |
884 | 880 | ||
885 | static void audio_reset_buffer(enum audio_buffer_state state) | 881 | static void audio_reset_buffer(void) |
886 | { | 882 | { |
887 | if (audiobuf_handle > 0) | 883 | if (audiobuf_handle > 0) |
888 | { | 884 | { |
@@ -890,9 +886,13 @@ static void audio_reset_buffer(enum audio_buffer_state state) | |||
890 | audiobuf_handle = 0; | 886 | audiobuf_handle = 0; |
891 | } | 887 | } |
892 | audiobuf_handle = core_alloc_maximum("audiobuf", &filebuflen, &ops); | 888 | audiobuf_handle = core_alloc_maximum("audiobuf", &filebuflen, &ops); |
893 | unsigned char *filebuf = core_get_data(audiobuf_handle); | ||
894 | 889 | ||
895 | audio_reset_buffer_noalloc(filebuf, state); | 890 | if (audiobuf_handle > 0) |
891 | audio_reset_buffer_noalloc(core_get_data(audiobuf_handle)); | ||
892 | else | ||
893 | /* someone is abusing core_alloc_maximum(). Fix this evil guy instead of | ||
894 | * trying to handle OOM without hope */ | ||
895 | panicf("%s(): OOM!\n", __func__); | ||
896 | } | 896 | } |
897 | 897 | ||
898 | /* Set the buffer margin to begin rebuffering when 'seconds' from empty */ | 898 | /* Set the buffer margin to begin rebuffering when 'seconds' from empty */ |
@@ -2033,7 +2033,7 @@ static int audio_fill_file_buffer(void) | |||
2033 | if (buffer_state != AUDIOBUF_STATE_INITIALIZED || | 2033 | if (buffer_state != AUDIOBUF_STATE_INITIALIZED || |
2034 | !pcmbuf_is_same_size()) | 2034 | !pcmbuf_is_same_size()) |
2035 | { | 2035 | { |
2036 | audio_reset_buffer(AUDIOBUF_STATE_INITIALIZED); | 2036 | audio_reset_buffer(); |
2037 | } | 2037 | } |
2038 | 2038 | ||
2039 | logf("Starting buffer fill"); | 2039 | logf("Starting buffer fill"); |
@@ -2464,7 +2464,7 @@ static void audio_start_playback(size_t offset, unsigned int flags) | |||
2464 | /* Mark the buffer dirty - if not playing, it will be reset next | 2464 | /* Mark the buffer dirty - if not playing, it will be reset next |
2465 | time */ | 2465 | time */ |
2466 | if (buffer_state == AUDIOBUF_STATE_INITIALIZED) | 2466 | if (buffer_state == AUDIOBUF_STATE_INITIALIZED) |
2467 | buffer_state = AUDIOBUF_STATE_VOICED_ONLY; | 2467 | buffer_state = AUDIOBUF_STATE_TRASHED; |
2468 | } | 2468 | } |
2469 | 2469 | ||
2470 | if (old_status != PLAY_STOPPED) | 2470 | if (old_status != PLAY_STOPPED) |
@@ -3511,88 +3511,6 @@ void audio_flush_and_reload_tracks(void) | |||
3511 | audio_queue_post(Q_AUDIO_FLUSH, 0); | 3511 | audio_queue_post(Q_AUDIO_FLUSH, 0); |
3512 | } | 3512 | } |
3513 | 3513 | ||
3514 | /* Return the pointer to the main audio buffer, optionally preserving | ||
3515 | voicing */ | ||
3516 | unsigned char * audio_get_buffer(bool talk_buf, size_t *buffer_size) | ||
3517 | { | ||
3518 | unsigned char *buf; | ||
3519 | |||
3520 | if (audio_is_initialized && thread_self() != audio_thread_id) | ||
3521 | { | ||
3522 | audio_hard_stop(); | ||
3523 | } | ||
3524 | /* else buffer_state will be AUDIOBUF_STATE_TRASHED at this point */ | ||
3525 | |||
3526 | if (buffer_size == NULL) | ||
3527 | { | ||
3528 | /* Special case for talk_init to use since it already knows it's | ||
3529 | trashed */ | ||
3530 | buffer_state = AUDIOBUF_STATE_TRASHED; | ||
3531 | return NULL; | ||
3532 | } | ||
3533 | |||
3534 | /* make sure buffer is freed and re-allocated to simplify code below | ||
3535 | * (audio_hard_stop() likely has done that already) */ | ||
3536 | if (audiobuf_handle > 0) | ||
3537 | audiobuf_handle = core_free(audiobuf_handle); | ||
3538 | |||
3539 | audiobuf_handle = core_alloc_maximum("audiobuf", &filebuflen, &ops); | ||
3540 | buf = core_get_data(audiobuf_handle); | ||
3541 | |||
3542 | if (buffer_state == AUDIOBUF_STATE_INITIALIZED) | ||
3543 | buffering_reset(NULL, 0); /* mark buffer invalid */ | ||
3544 | |||
3545 | if (talk_buf || !talk_voice_required()) | ||
3546 | { | ||
3547 | logf("get buffer: talk, audio"); | ||
3548 | /* Ok to use everything from audiobuf - voice is loaded, | ||
3549 | the talk buffer is not needed because voice isn't being used, or | ||
3550 | could be AUDIOBUF_STATE_TRASHED already. If state is | ||
3551 | AUDIOBUF_STATE_VOICED_ONLY, no problem as long as memory isn't | ||
3552 | written without the caller knowing what's going on. Changing certain | ||
3553 | settings may move it to a worse condition but the memory in use by | ||
3554 | something else will remain undisturbed. | ||
3555 | */ | ||
3556 | if (buffer_state != AUDIOBUF_STATE_TRASHED) | ||
3557 | { | ||
3558 | talk_buffer_steal(); | ||
3559 | buffer_state = AUDIOBUF_STATE_TRASHED; | ||
3560 | } | ||
3561 | } | ||
3562 | else | ||
3563 | { | ||
3564 | logf("get buffer: audio"); | ||
3565 | /* Safe to just return this if already AUDIOBUF_STATE_VOICED_ONLY or | ||
3566 | still AUDIOBUF_STATE_INITIALIZED */ | ||
3567 | size_t talkbuf_size = talkbuf_init(buf); | ||
3568 | buf += talkbuf_size; /* Skip talk buffer */ | ||
3569 | filebuflen -= talkbuf_size; | ||
3570 | buffer_state = AUDIOBUF_STATE_VOICED_ONLY; | ||
3571 | } | ||
3572 | |||
3573 | *buffer_size = filebuflen; | ||
3574 | return buf; | ||
3575 | } | ||
3576 | |||
3577 | /* Restore audio buffer to a particular state (promoting status) */ | ||
3578 | bool audio_restore_playback(int type) | ||
3579 | { | ||
3580 | switch (type) | ||
3581 | { | ||
3582 | case AUDIO_WANT_PLAYBACK: | ||
3583 | if (buffer_state != AUDIOBUF_STATE_INITIALIZED) | ||
3584 | audio_reset_buffer(AUDIOBUF_STATE_INITIALIZED); | ||
3585 | return true; | ||
3586 | case AUDIO_WANT_VOICE: | ||
3587 | if (buffer_state == AUDIOBUF_STATE_TRASHED) | ||
3588 | audio_reset_buffer(AUDIOBUF_STATE_VOICED_ONLY); | ||
3589 | return true; | ||
3590 | default: | ||
3591 | return false; | ||
3592 | } | ||
3593 | } | ||
3594 | |||
3595 | |||
3596 | /** --- Miscellaneous public interfaces --- **/ | 3514 | /** --- Miscellaneous public interfaces --- **/ |
3597 | 3515 | ||
3598 | #ifdef HAVE_ALBUMART | 3516 | #ifdef HAVE_ALBUMART |
diff --git a/apps/playback.h b/apps/playback.h index f56bbfdff0..177768ded3 100644 --- a/apps/playback.h +++ b/apps/playback.h | |||
@@ -80,12 +80,6 @@ void audio_set_cuesheet(bool enable); | |||
80 | void audio_set_crossfade(int enable); | 80 | void audio_set_crossfade(int enable); |
81 | #endif | 81 | #endif |
82 | 82 | ||
83 | enum | ||
84 | { | ||
85 | AUDIO_WANT_PLAYBACK = 0, | ||
86 | AUDIO_WANT_VOICE, | ||
87 | }; | ||
88 | bool audio_restore_playback(int type); /* Restores the audio buffer to handle the requested playback */ | ||
89 | size_t audio_get_filebuflen(void); | 83 | size_t audio_get_filebuflen(void); |
90 | 84 | ||
91 | unsigned int playback_status(void); | 85 | unsigned int playback_status(void); |
diff --git a/apps/playlist.c b/apps/playlist.c index 35b7e35baa..3d930cf3f9 100755 --- a/apps/playlist.c +++ b/apps/playlist.c | |||
@@ -104,6 +104,7 @@ | |||
104 | #include "rbunicode.h" | 104 | #include "rbunicode.h" |
105 | #include "root_menu.h" | 105 | #include "root_menu.h" |
106 | #include "plugin.h" /* To borrow a temp buffer to rewrite a .m3u8 file */ | 106 | #include "plugin.h" /* To borrow a temp buffer to rewrite a .m3u8 file */ |
107 | #include "panic.h" | ||
107 | 108 | ||
108 | #define PLAYLIST_CONTROL_FILE_VERSION 2 | 109 | #define PLAYLIST_CONTROL_FILE_VERSION 2 |
109 | 110 | ||
@@ -532,13 +533,6 @@ static int add_indices_to_playlist(struct playlist_info* playlist, | |||
532 | 533 | ||
533 | splash(0, ID2P(LANG_WAIT)); | 534 | splash(0, ID2P(LANG_WAIT)); |
534 | 535 | ||
535 | if (!buffer) | ||
536 | { | ||
537 | /* use mp3 buffer for maximum load speed */ | ||
538 | audio_stop(); | ||
539 | buffer = audio_get_buffer(false, &buflen); | ||
540 | } | ||
541 | |||
542 | store_index = true; | 536 | store_index = true; |
543 | 537 | ||
544 | while(1) | 538 | while(1) |
@@ -2077,8 +2071,26 @@ int playlist_create(const char *dir, const char *file) | |||
2077 | new_playlist(playlist, dir, file); | 2071 | new_playlist(playlist, dir, file); |
2078 | 2072 | ||
2079 | if (file) | 2073 | if (file) |
2080 | /* load the playlist file */ | 2074 | { |
2081 | add_indices_to_playlist(playlist, NULL, 0); | 2075 | /* dummy ops with no callbacks, needed because by |
2076 | * default buflib buffers can be moved around which must be avoided */ | ||
2077 | static struct buflib_callbacks dummy_ops; | ||
2078 | int handle; | ||
2079 | size_t buflen; | ||
2080 | /* use mp3 buffer for maximum load speed */ | ||
2081 | handle = core_alloc_maximum("temp", &buflen, &dummy_ops); | ||
2082 | if (handle > 0) | ||
2083 | { | ||
2084 | /* load the playlist file */ | ||
2085 | add_indices_to_playlist(playlist, core_get_data(handle), buflen); | ||
2086 | core_free(handle); | ||
2087 | } | ||
2088 | else | ||
2089 | { | ||
2090 | /* should not happen */ | ||
2091 | panicf("%s(): OOM", __func__); | ||
2092 | } | ||
2093 | } | ||
2082 | 2094 | ||
2083 | return 0; | 2095 | return 0; |
2084 | } | 2096 | } |
@@ -2094,14 +2106,22 @@ int playlist_resume(void) | |||
2094 | struct playlist_info* playlist = ¤t_playlist; | 2106 | struct playlist_info* playlist = ¤t_playlist; |
2095 | char *buffer; | 2107 | char *buffer; |
2096 | size_t buflen; | 2108 | size_t buflen; |
2109 | int handle; | ||
2097 | int nread; | 2110 | int nread; |
2098 | int total_read = 0; | 2111 | int total_read = 0; |
2099 | int control_file_size = 0; | 2112 | int control_file_size = 0; |
2100 | bool first = true; | 2113 | bool first = true; |
2101 | bool sorted = true; | 2114 | bool sorted = true; |
2115 | int result = -1; | ||
2102 | 2116 | ||
2117 | /* dummy ops with no callbacks, needed because by | ||
2118 | * default buflib buffers can be moved around which must be avoided */ | ||
2119 | static struct buflib_callbacks dummy_ops; | ||
2103 | /* use mp3 buffer for maximum load speed */ | 2120 | /* use mp3 buffer for maximum load speed */ |
2104 | buffer = (char *)audio_get_buffer(false, &buflen); | 2121 | handle = core_alloc_maximum("temp", &buflen, &dummy_ops); |
2122 | if (handle < 0) | ||
2123 | panicf("%s(): OOM", __func__); | ||
2124 | buffer = core_get_data(handle); | ||
2105 | 2125 | ||
2106 | empty_playlist(playlist, true); | 2126 | empty_playlist(playlist, true); |
2107 | 2127 | ||
@@ -2110,7 +2130,7 @@ int playlist_resume(void) | |||
2110 | if (playlist->control_fd < 0) | 2130 | if (playlist->control_fd < 0) |
2111 | { | 2131 | { |
2112 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); | 2132 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); |
2113 | return -1; | 2133 | goto out; |
2114 | } | 2134 | } |
2115 | playlist->control_created = true; | 2135 | playlist->control_created = true; |
2116 | 2136 | ||
@@ -2118,7 +2138,7 @@ int playlist_resume(void) | |||
2118 | if (control_file_size <= 0) | 2138 | if (control_file_size <= 0) |
2119 | { | 2139 | { |
2120 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); | 2140 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); |
2121 | return -1; | 2141 | goto out; |
2122 | } | 2142 | } |
2123 | 2143 | ||
2124 | /* read a small amount first to get the header */ | 2144 | /* read a small amount first to get the header */ |
@@ -2127,14 +2147,14 @@ int playlist_resume(void) | |||
2127 | if(nread <= 0) | 2147 | if(nread <= 0) |
2128 | { | 2148 | { |
2129 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); | 2149 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_ACCESS_ERROR)); |
2130 | return -1; | 2150 | goto out; |
2131 | } | 2151 | } |
2132 | 2152 | ||
2133 | playlist->started = true; | 2153 | playlist->started = true; |
2134 | 2154 | ||
2135 | while (1) | 2155 | while (1) |
2136 | { | 2156 | { |
2137 | int result = 0; | 2157 | result = 0; |
2138 | int count; | 2158 | int count; |
2139 | enum playlist_command current_command = PLAYLIST_COMMAND_COMMENT; | 2159 | enum playlist_command current_command = PLAYLIST_COMMAND_COMMENT; |
2140 | int last_newline = 0; | 2160 | int last_newline = 0; |
@@ -2195,7 +2215,10 @@ int playlist_resume(void) | |||
2195 | version = atoi(str1); | 2215 | version = atoi(str1); |
2196 | 2216 | ||
2197 | if (version != PLAYLIST_CONTROL_FILE_VERSION) | 2217 | if (version != PLAYLIST_CONTROL_FILE_VERSION) |
2198 | return -1; | 2218 | { |
2219 | result = -1; | ||
2220 | goto out; | ||
2221 | } | ||
2199 | 2222 | ||
2200 | update_playlist_filename(playlist, str2, str3); | 2223 | update_playlist_filename(playlist, str2, str3); |
2201 | 2224 | ||
@@ -2204,7 +2227,7 @@ int playlist_resume(void) | |||
2204 | /* NOTE: add_indices_to_playlist() overwrites the | 2227 | /* NOTE: add_indices_to_playlist() overwrites the |
2205 | audiobuf so we need to reload control file | 2228 | audiobuf so we need to reload control file |
2206 | data */ | 2229 | data */ |
2207 | add_indices_to_playlist(playlist, NULL, 0); | 2230 | add_indices_to_playlist(playlist, buffer, buflen); |
2208 | } | 2231 | } |
2209 | else if (str2[0] != '\0') | 2232 | else if (str2[0] != '\0') |
2210 | { | 2233 | { |
@@ -2242,7 +2265,10 @@ int playlist_resume(void) | |||
2242 | buffer */ | 2265 | buffer */ |
2243 | if (add_track_to_playlist(playlist, str3, position, | 2266 | if (add_track_to_playlist(playlist, str3, position, |
2244 | queue, total_read+(str3-buffer)) < 0) | 2267 | queue, total_read+(str3-buffer)) < 0) |
2245 | return -1; | 2268 | { |
2269 | result = -1; | ||
2270 | goto out; | ||
2271 | } | ||
2246 | 2272 | ||
2247 | playlist->last_insert_pos = last_position; | 2273 | playlist->last_insert_pos = last_position; |
2248 | 2274 | ||
@@ -2264,7 +2290,10 @@ int playlist_resume(void) | |||
2264 | 2290 | ||
2265 | if (remove_track_from_playlist(playlist, position, | 2291 | if (remove_track_from_playlist(playlist, position, |
2266 | false) < 0) | 2292 | false) < 0) |
2267 | return -1; | 2293 | { |
2294 | result = -1; | ||
2295 | goto out; | ||
2296 | } | ||
2268 | 2297 | ||
2269 | break; | 2298 | break; |
2270 | } | 2299 | } |
@@ -2291,7 +2320,10 @@ int playlist_resume(void) | |||
2291 | 2320 | ||
2292 | if (randomise_playlist(playlist, seed, false, | 2321 | if (randomise_playlist(playlist, seed, false, |
2293 | false) < 0) | 2322 | false) < 0) |
2294 | return -1; | 2323 | { |
2324 | result = -1; | ||
2325 | goto out; | ||
2326 | } | ||
2295 | sorted = false; | 2327 | sorted = false; |
2296 | break; | 2328 | break; |
2297 | } | 2329 | } |
@@ -2308,7 +2340,10 @@ int playlist_resume(void) | |||
2308 | playlist->first_index = atoi(str1); | 2340 | playlist->first_index = atoi(str1); |
2309 | 2341 | ||
2310 | if (sort_playlist(playlist, false, false) < 0) | 2342 | if (sort_playlist(playlist, false, false) < 0) |
2311 | return -1; | 2343 | { |
2344 | result = -1; | ||
2345 | goto out; | ||
2346 | } | ||
2312 | 2347 | ||
2313 | sorted = true; | 2348 | sorted = true; |
2314 | break; | 2349 | break; |
@@ -2421,13 +2456,14 @@ int playlist_resume(void) | |||
2421 | if (result < 0) | 2456 | if (result < 0) |
2422 | { | 2457 | { |
2423 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_INVALID)); | 2458 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_INVALID)); |
2424 | return result; | 2459 | goto out; |
2425 | } | 2460 | } |
2426 | 2461 | ||
2427 | if (useraborted) | 2462 | if (useraborted) |
2428 | { | 2463 | { |
2429 | splash(HZ*2, ID2P(LANG_CANCEL)); | 2464 | splash(HZ*2, ID2P(LANG_CANCEL)); |
2430 | return -1; | 2465 | result = -1; |
2466 | goto out; | ||
2431 | } | 2467 | } |
2432 | if (!newline || (exit_loop && count<nread)) | 2468 | if (!newline || (exit_loop && count<nread)) |
2433 | { | 2469 | { |
@@ -2435,7 +2471,8 @@ int playlist_resume(void) | |||
2435 | { | 2471 | { |
2436 | /* no newline at end of control file */ | 2472 | /* no newline at end of control file */ |
2437 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_INVALID)); | 2473 | splash(HZ*2, ID2P(LANG_PLAYLIST_CONTROL_INVALID)); |
2438 | return -1; | 2474 | result = -1; |
2475 | goto out; | ||
2439 | } | 2476 | } |
2440 | 2477 | ||
2441 | /* We didn't end on a newline or we exited loop prematurely. | 2478 | /* We didn't end on a newline or we exited loop prematurely. |
@@ -2464,7 +2501,9 @@ int playlist_resume(void) | |||
2464 | queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0); | 2501 | queue_post(&playlist_queue, PLAYLIST_LOAD_POINTERS, 0); |
2465 | #endif | 2502 | #endif |
2466 | 2503 | ||
2467 | return 0; | 2504 | out: |
2505 | core_free(handle); | ||
2506 | return result; | ||
2468 | } | 2507 | } |
2469 | 2508 | ||
2470 | /* | 2509 | /* |
diff --git a/apps/plugin.c b/apps/plugin.c index 18c49f4c27..d80e6a1e0e 100644 --- a/apps/plugin.c +++ b/apps/plugin.c | |||
@@ -801,6 +801,8 @@ static const struct plugin_api rockbox_api = { | |||
801 | the API gets incompatible */ | 801 | the API gets incompatible */ |
802 | }; | 802 | }; |
803 | 803 | ||
804 | static int plugin_buffer_handle; | ||
805 | |||
804 | int plugin_load(const char* plugin, const void* parameter) | 806 | int plugin_load(const char* plugin, const void* parameter) |
805 | { | 807 | { |
806 | struct plugin_header *p_hdr; | 808 | struct plugin_header *p_hdr; |
@@ -815,6 +817,8 @@ int plugin_load(const char* plugin, const void* parameter) | |||
815 | } | 817 | } |
816 | lc_close(current_plugin_handle); | 818 | lc_close(current_plugin_handle); |
817 | current_plugin_handle = pfn_tsr_exit = NULL; | 819 | current_plugin_handle = pfn_tsr_exit = NULL; |
820 | if (plugin_buffer_handle > 0) | ||
821 | plugin_buffer_handle = core_free(plugin_buffer_handle); | ||
818 | } | 822 | } |
819 | 823 | ||
820 | splash(0, ID2P(LANG_WAIT)); | 824 | splash(0, ID2P(LANG_WAIT)); |
@@ -878,6 +882,9 @@ int plugin_load(const char* plugin, const void* parameter) | |||
878 | touchscreen_set_mode(TOUCHSCREEN_BUTTON); | 882 | touchscreen_set_mode(TOUCHSCREEN_BUTTON); |
879 | #endif | 883 | #endif |
880 | 884 | ||
885 | /* allow voice to back off if the plugin needs lots of memory */ | ||
886 | talk_buffer_set_policy(TALK_BUFFER_LOOSE); | ||
887 | |||
881 | #ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE | 888 | #ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE |
882 | open_files = 0; | 889 | open_files = 0; |
883 | #endif | 890 | #endif |
@@ -891,8 +898,12 @@ int plugin_load(const char* plugin, const void* parameter) | |||
891 | { /* close handle if plugin is no tsr one */ | 898 | { /* close handle if plugin is no tsr one */ |
892 | lc_close(current_plugin_handle); | 899 | lc_close(current_plugin_handle); |
893 | current_plugin_handle = NULL; | 900 | current_plugin_handle = NULL; |
901 | if (plugin_buffer_handle > 0) | ||
902 | plugin_buffer_handle = core_free(plugin_buffer_handle); | ||
894 | } | 903 | } |
895 | 904 | ||
905 | talk_buffer_set_policy(TALK_BUFFER_DEFAULT); | ||
906 | |||
896 | /* Go back to the global setting in case the plugin changed it */ | 907 | /* Go back to the global setting in case the plugin changed it */ |
897 | #ifdef HAVE_TOUCHSCREEN | 908 | #ifdef HAVE_TOUCHSCREEN |
898 | touchscreen_set_mode(global_settings.touch_mode); | 909 | touchscreen_set_mode(global_settings.touch_mode); |
@@ -984,8 +995,12 @@ void* plugin_get_buffer(size_t *buffer_size) | |||
984 | */ | 995 | */ |
985 | void* plugin_get_audio_buffer(size_t *buffer_size) | 996 | void* plugin_get_audio_buffer(size_t *buffer_size) |
986 | { | 997 | { |
987 | audio_stop(); | 998 | /* dummy ops with no callbacks, needed because by |
988 | return audio_get_buffer(true, buffer_size); | 999 | * default buflib buffers can be moved around which must be avoided */ |
1000 | static struct buflib_callbacks dummy_ops; | ||
1001 | plugin_buffer_handle = core_alloc_maximum("plugin audio buf", buffer_size, | ||
1002 | &dummy_ops); | ||
1003 | return core_get_data(plugin_buffer_handle); | ||
989 | } | 1004 | } |
990 | 1005 | ||
991 | /* The plugin wants to stay resident after leaving its main function, e.g. | 1006 | /* The plugin wants to stay resident after leaving its main function, e.g. |
diff --git a/apps/plugin.h b/apps/plugin.h index af673b3afe..38d8889d9e 100644 --- a/apps/plugin.h +++ b/apps/plugin.h | |||
@@ -112,6 +112,7 @@ void* plugin_get_buffer(size_t *buffer_size); | |||
112 | #include "timefuncs.h" | 112 | #include "timefuncs.h" |
113 | #include "crc32.h" | 113 | #include "crc32.h" |
114 | #include "rbpaths.h" | 114 | #include "rbpaths.h" |
115 | #include "core_alloc.h" | ||
115 | 116 | ||
116 | #ifdef HAVE_ALBUMART | 117 | #ifdef HAVE_ALBUMART |
117 | #include "albumart.h" | 118 | #include "albumart.h" |
diff --git a/apps/radio/radioart.c b/apps/radio/radioart.c index 76697c6017..cab6aa29ce 100644 --- a/apps/radio/radioart.c +++ b/apps/radio/radioart.c | |||
@@ -32,6 +32,7 @@ | |||
32 | #include "kernel.h" | 32 | #include "kernel.h" |
33 | #include "string-extra.h" | 33 | #include "string-extra.h" |
34 | #include "filefuncs.h" | 34 | #include "filefuncs.h" |
35 | #include "core_alloc.h" | ||
35 | 36 | ||
36 | #define MAX_RADIOART_IMAGES 10 | 37 | #define MAX_RADIOART_IMAGES 10 |
37 | struct radioart { | 38 | struct radioart { |
@@ -158,14 +159,49 @@ static void buffer_reset_handler(void *data) | |||
158 | (void)data; | 159 | (void)data; |
159 | } | 160 | } |
160 | 161 | ||
162 | static int shrink_callback(int handle, unsigned hints, void* start, size_t old_size) | ||
163 | { | ||
164 | (void)start; | ||
165 | (void)old_size; | ||
166 | |||
167 | ssize_t old_size_s = old_size; | ||
168 | size_t size_hint = (hints & BUFLIB_SHRINK_SIZE_MASK); | ||
169 | ssize_t wanted_size = old_size_s - size_hint; | ||
170 | |||
171 | if (wanted_size <= 0) | ||
172 | { | ||
173 | core_free(handle); | ||
174 | buffering_reset(NULL, 0); | ||
175 | } | ||
176 | else | ||
177 | { | ||
178 | if (hints & BUFLIB_SHRINK_POS_FRONT) | ||
179 | start += size_hint; | ||
180 | |||
181 | buffering_reset(start, wanted_size); | ||
182 | core_shrink(handle, start, wanted_size); | ||
183 | buf = start; | ||
184 | |||
185 | /* one-shot */ | ||
186 | add_event(BUFFER_EVENT_BUFFER_RESET, true, buffer_reset_handler); | ||
187 | } | ||
188 | |||
189 | return BUFLIB_CB_OK; | ||
190 | } | ||
191 | |||
192 | static struct buflib_callbacks radioart_ops = { | ||
193 | .shrink_callback = shrink_callback, | ||
194 | }; | ||
195 | |||
161 | void radioart_init(bool entering_screen) | 196 | void radioart_init(bool entering_screen) |
162 | { | 197 | { |
163 | if (entering_screen) | 198 | if (entering_screen) |
164 | { | 199 | { |
165 | /* grab control over buffering */ | 200 | /* grab control over buffering */ |
166 | size_t bufsize; | 201 | size_t bufsize; |
167 | buf = audio_get_buffer(false, &bufsize); | 202 | int handle = core_alloc_maximum("radioart", &bufsize, &radioart_ops); |
168 | buffering_reset(buf, bufsize); | 203 | buffering_reset(core_get_data(handle), bufsize); |
204 | buf = core_get_data(handle); | ||
169 | /* one-shot */ | 205 | /* one-shot */ |
170 | add_event(BUFFER_EVENT_BUFFER_RESET, true, buffer_reset_handler); | 206 | add_event(BUFFER_EVENT_BUFFER_RESET, true, buffer_reset_handler); |
171 | } | 207 | } |
diff --git a/apps/recorder/pcm_record.c b/apps/recorder/pcm_record.c index 2170e473bc..799c733948 100644 --- a/apps/recorder/pcm_record.c +++ b/apps/recorder/pcm_record.c | |||
@@ -24,6 +24,7 @@ | |||
24 | #include "config.h" | 24 | #include "config.h" |
25 | #include "system.h" | 25 | #include "system.h" |
26 | #include "kernel.h" | 26 | #include "kernel.h" |
27 | #include "panic.h" | ||
27 | #include "string-extra.h" | 28 | #include "string-extra.h" |
28 | #include "pcm_record.h" | 29 | #include "pcm_record.h" |
29 | #include "codecs.h" | 30 | #include "codecs.h" |
@@ -40,6 +41,8 @@ | |||
40 | #include "spdif.h" | 41 | #include "spdif.h" |
41 | #endif | 42 | #endif |
42 | #include "audio_thread.h" | 43 | #include "audio_thread.h" |
44 | #include "core_alloc.h" | ||
45 | #include "talk.h" | ||
43 | 46 | ||
44 | /* Macros to enable logf for queues | 47 | /* Macros to enable logf for queues |
45 | logging on SYS_TIMEOUT can be disabled */ | 48 | logging on SYS_TIMEOUT can be disabled */ |
@@ -1402,11 +1405,22 @@ static void tally_prerecord_data(void) | |||
1402 | 1405 | ||
1403 | /** Event handlers for recording thread **/ | 1406 | /** Event handlers for recording thread **/ |
1404 | 1407 | ||
1408 | static int pcmrec_handle; | ||
1405 | /* Q_AUDIO_INIT_RECORDING */ | 1409 | /* Q_AUDIO_INIT_RECORDING */ |
1406 | static void on_init_recording(void) | 1410 | static void on_init_recording(void) |
1407 | { | 1411 | { |
1408 | send_event(RECORDING_EVENT_START, NULL); | 1412 | send_event(RECORDING_EVENT_START, NULL); |
1409 | rec_buffer = audio_get_buffer(true, &rec_buffer_size); | 1413 | /* dummy ops with no callbacks, needed because by |
1414 | * default buflib buffers can be moved around which must be avoided | ||
1415 | * FIXME: This buffer should play nicer and be shrinkable/movable */ | ||
1416 | static struct buflib_callbacks dummy_ops; | ||
1417 | talk_buffer_set_policy(TALK_BUFFER_LOOSE); | ||
1418 | pcmrec_handle = core_alloc_maximum("pcmrec", &rec_buffer_size, &dummy_ops); | ||
1419 | if (pcmrec_handle) | ||
1420 | /* someone is abusing core_alloc_maximum(). Fix this evil guy instead of | ||
1421 | * trying to handle OOM without hope */ | ||
1422 | panicf("%s(): OOM\n", __func__); | ||
1423 | rec_buffer = core_get_data(pcmrec_handle); | ||
1410 | init_rec_buffers(); | 1424 | init_rec_buffers(); |
1411 | init_state(); | 1425 | init_state(); |
1412 | pcm_init_recording(); | 1426 | pcm_init_recording(); |
@@ -1430,6 +1444,10 @@ static void on_close_recording(void) | |||
1430 | audio_set_output_source(AUDIO_SRC_PLAYBACK); | 1444 | audio_set_output_source(AUDIO_SRC_PLAYBACK); |
1431 | pcm_apply_settings(); | 1445 | pcm_apply_settings(); |
1432 | 1446 | ||
1447 | if (pcmrec_handle > 0) | ||
1448 | pcmrec_handle = core_free(pcmrec_handle); | ||
1449 | talk_buffer_set_policy(TALK_BUFFER_DEFAULT); | ||
1450 | |||
1433 | send_event(RECORDING_EVENT_STOP, NULL); | 1451 | send_event(RECORDING_EVENT_STOP, NULL); |
1434 | } | 1452 | } |
1435 | 1453 | ||
diff --git a/apps/recorder/recording.c b/apps/recorder/recording.c index 1c3460fbd0..4a7399b01e 100644 --- a/apps/recorder/recording.c +++ b/apps/recorder/recording.c | |||
@@ -691,15 +691,8 @@ void rec_set_source(int source, unsigned flags) | |||
691 | 691 | ||
692 | void rec_set_recording_options(struct audio_recording_options *options) | 692 | void rec_set_recording_options(struct audio_recording_options *options) |
693 | { | 693 | { |
694 | #if CONFIG_CODEC != SWCODEC | ||
695 | if (global_settings.rec_prerecord_time) | ||
696 | { | ||
697 | talk_buffer_steal(); /* will use the mp3 buffer */ | ||
698 | } | ||
699 | #else /* == SWCODEC */ | ||
700 | rec_set_source(options->rec_source, | 694 | rec_set_source(options->rec_source, |
701 | options->rec_source_flags | SRCF_RECORDING); | 695 | options->rec_source_flags | SRCF_RECORDING); |
702 | #endif /* CONFIG_CODEC != SWCODEC */ | ||
703 | 696 | ||
704 | audio_set_recording_options(options); | 697 | audio_set_recording_options(options); |
705 | } | 698 | } |
@@ -724,9 +717,6 @@ void rec_command(enum recording_command cmd) | |||
724 | /* steal mp3 buffer, create unique filename and start recording */ | 717 | /* steal mp3 buffer, create unique filename and start recording */ |
725 | pm_reset_clipcount(); | 718 | pm_reset_clipcount(); |
726 | pm_activate_clipcount(true); | 719 | pm_activate_clipcount(true); |
727 | #if CONFIG_CODEC != SWCODEC | ||
728 | talk_buffer_steal(); /* we use the mp3 buffer */ | ||
729 | #endif | ||
730 | audio_record(rec_create_filename(path_buffer)); | 720 | audio_record(rec_create_filename(path_buffer)); |
731 | break; | 721 | break; |
732 | case RECORDING_CMD_START_NEWFILE: | 722 | case RECORDING_CMD_START_NEWFILE: |
diff --git a/apps/talk.c b/apps/talk.c index b94dcf18ee..baf854fce3 100644 --- a/apps/talk.c +++ b/apps/talk.c | |||
@@ -79,8 +79,8 @@ const char* const file_thumbnail_ext = ".talk"; | |||
79 | 79 | ||
80 | #define LOADED_MASK 0x80000000 /* MSB */ | 80 | #define LOADED_MASK 0x80000000 /* MSB */ |
81 | 81 | ||
82 | /* swcodec: cap p_thumnail to MAX_THUMNAIL_BUFSIZE since audio keeps playing | 82 | /* swcodec: cap thumbnail buffer to MAX_THUMNAIL_BUFSIZE since audio keeps |
83 | * while voice | 83 | * playing while voice |
84 | * hwcodec: just use whatever is left in the audiobuffer, music | 84 | * hwcodec: just use whatever is left in the audiobuffer, music |
85 | * playback is impossible => no cap */ | 85 | * playback is impossible => no cap */ |
86 | #if CONFIG_CODEC == SWCODEC | 86 | #if CONFIG_CODEC == SWCODEC |
@@ -95,25 +95,17 @@ struct clip_entry /* one entry of the index table */ | |||
95 | int size; /* size of the clip */ | 95 | int size; /* size of the clip */ |
96 | }; | 96 | }; |
97 | 97 | ||
98 | struct voicefile /* file format of our voice file */ | 98 | struct voicefile_header /* file format of our voice file */ |
99 | { | 99 | { |
100 | int version; /* version of the voicefile */ | 100 | int version; /* version of the voicefile */ |
101 | int target_id; /* the rockbox target the file was made for */ | 101 | int target_id; /* the rockbox target the file was made for */ |
102 | int table; /* offset to index table, (=header size) */ | 102 | int table; /* offset to index table, (=header size) */ |
103 | int id1_max; /* number of "normal" clips contained in above index */ | 103 | int id1_max; /* number of "normal" clips contained in above index */ |
104 | int id2_max; /* number of "voice only" clips contained in above index */ | 104 | int id2_max; /* number of "voice only" clips contained in above index */ |
105 | struct clip_entry index[]; /* followed by the index tables */ | 105 | /* The header is folled by the index tables (n*struct clip_entry), |
106 | /* and finally the mp3 clips, not visible here, bitswapped | 106 | * which is followed by the mp3/speex encoded clip data */ |
107 | for SH based players */ | ||
108 | }; | 107 | }; |
109 | 108 | ||
110 | struct queue_entry /* one entry of the internal queue */ | ||
111 | { | ||
112 | unsigned char* buf; | ||
113 | long len; | ||
114 | }; | ||
115 | |||
116 | |||
117 | /***************** Globals *****************/ | 109 | /***************** Globals *****************/ |
118 | 110 | ||
119 | #if (CONFIG_CODEC == SWCODEC && MEMORYSIZE <= 2) || defined(ONDIO_SERIES) | 111 | #if (CONFIG_CODEC == SWCODEC && MEMORYSIZE <= 2) || defined(ONDIO_SERIES) |
@@ -125,7 +117,6 @@ struct queue_entry /* one entry of the internal queue */ | |||
125 | #endif | 117 | #endif |
126 | 118 | ||
127 | #ifdef TALK_PARTIAL_LOAD | 119 | #ifdef TALK_PARTIAL_LOAD |
128 | static unsigned char *clip_buffer; | ||
129 | static long max_clipsize; /* size of the biggest clip */ | 120 | static long max_clipsize; /* size of the biggest clip */ |
130 | static long buffered_id[QUEUE_SIZE]; /* IDs of the talk clips */ | 121 | static long buffered_id[QUEUE_SIZE]; /* IDs of the talk clips */ |
131 | static uint8_t clip_age[QUEUE_SIZE]; | 122 | static uint8_t clip_age[QUEUE_SIZE]; |
@@ -134,16 +125,13 @@ static uint8_t clip_age[QUEUE_SIZE]; | |||
134 | #endif | 125 | #endif |
135 | #endif | 126 | #endif |
136 | 127 | ||
137 | static char* voicebuf; /* root pointer to our buffer */ | ||
138 | static unsigned char* p_thumbnail = NULL; /* buffer for thumbnails */ | ||
139 | /* Multiple thumbnails can be loaded back-to-back in this buffer. */ | 128 | /* Multiple thumbnails can be loaded back-to-back in this buffer. */ |
140 | static volatile int thumbnail_buf_used SHAREDBSS_ATTR; /* length of data in | 129 | static volatile int thumbnail_buf_used SHAREDBSS_ATTR; /* length of data in |
141 | thumbnail buffer */ | 130 | thumbnail buffer */ |
142 | static long size_for_thumbnail; /* total thumbnail buffer size */ | 131 | static long size_for_thumbnail; /* total thumbnail buffer size */ |
143 | static struct voicefile* p_voicefile; /* loaded voicefile */ | 132 | static struct voicefile_header voicefile; /* loaded voicefile */ |
144 | static bool has_voicefile; /* a voicefile file is present */ | 133 | static bool has_voicefile; /* a voicefile file is present */ |
145 | static bool need_shutup; /* is there possibly any voice playing to be shutup */ | 134 | static bool need_shutup; /* is there possibly any voice playing to be shutup */ |
146 | static struct queue_entry queue[QUEUE_SIZE]; /* queue of scheduled clips */ | ||
147 | static bool force_enqueue_next; /* enqueue next utterance even if enqueue is false */ | 135 | static bool force_enqueue_next; /* enqueue next utterance even if enqueue is false */ |
148 | static int queue_write; /* write index of queue, by application */ | 136 | static int queue_write; /* write index of queue, by application */ |
149 | static int queue_read; /* read index of queue, by ISR context */ | 137 | static int queue_read; /* read index of queue, by ISR context */ |
@@ -158,18 +146,126 @@ static struct mutex queue_mutex SHAREDBSS_ATTR; | |||
158 | #endif /* CONFIG_CODEC */ | 146 | #endif /* CONFIG_CODEC */ |
159 | static int sent; /* how many bytes handed over to playback, owned by ISR */ | 147 | static int sent; /* how many bytes handed over to playback, owned by ISR */ |
160 | static unsigned char curr_hd[3]; /* current frame header, for re-sync */ | 148 | static unsigned char curr_hd[3]; /* current frame header, for re-sync */ |
161 | static int filehandle = -1; /* global, so we can keep the file open if needed */ | 149 | static int silence_offset; /* VOICE_PAUSE clip, used for termination */ |
162 | static unsigned char* p_silence; /* VOICE_PAUSE clip, used for termination */ | 150 | static long silence_length; /* length of the VOICE_PAUSE clip */ |
163 | static long silence_len; /* length of the VOICE_PAUSE clip */ | 151 | static unsigned long lastclip_offset; /* address of latest clip, for silence add */ |
164 | static unsigned char* p_lastclip; /* address of latest clip, for silence add */ | ||
165 | static unsigned long voicefile_size = 0; /* size of the loaded voice file */ | ||
166 | static unsigned char last_lang[MAX_FILENAME+1]; /* name of last used lang file (in talk_init) */ | 152 | static unsigned char last_lang[MAX_FILENAME+1]; /* name of last used lang file (in talk_init) */ |
167 | static bool talk_initialized; /* true if talk_init has been called */ | 153 | static bool talk_initialized; /* true if talk_init has been called */ |
154 | static bool give_buffer_away; /* true if we should give the buffers away in shrink_callback if requested */ | ||
168 | static int talk_temp_disable_count; /* if positive, temporarily disable voice UI (not saved) */ | 155 | static int talk_temp_disable_count; /* if positive, temporarily disable voice UI (not saved) */ |
156 | /* size of the loaded voice file | ||
157 | * offsets smaller than this denote a clip from teh voice file, | ||
158 | * offsets larger than this denote a thumbnail clip */ | ||
159 | static unsigned long voicefile_size; | ||
160 | |||
161 | struct queue_entry /* one entry of the internal queue */ | ||
162 | { | ||
163 | int offset, length; | ||
164 | }; | ||
165 | |||
166 | static struct queue_entry queue[QUEUE_SIZE]; /* queue of scheduled clips */ | ||
169 | 167 | ||
170 | 168 | ||
171 | /***************** Private implementation *****************/ | 169 | /***************** Private implementation *****************/ |
172 | 170 | ||
171 | static int thumb_handle; | ||
172 | static int talk_handle, talk_handle_locked; | ||
173 | |||
174 | #if CONFIG_CODEC != SWCODEC | ||
175 | |||
176 | /* on HWCODEC only voice xor audio can be active at a time */ | ||
177 | static bool check_audio_status(void) | ||
178 | { | ||
179 | if (audio_status()) /* busy, buffer in use */ | ||
180 | return false; | ||
181 | /* ensure playback is given up on the buffer */ | ||
182 | audio_hard_stop(); | ||
183 | return true; | ||
184 | } | ||
185 | |||
186 | /* ISR (mp3_callback()) must not run during moving of the clip buffer, | ||
187 | * because the MAS may get out-of-sync */ | ||
188 | static void sync_callback(int handle, bool sync_on) | ||
189 | { | ||
190 | (void) handle; | ||
191 | (void) sync_on; | ||
192 | #if CONFIG_CPU == SH7034 | ||
193 | if (sync_on) | ||
194 | CHCR3 &= ~0x0001; /* disable the DMA (and therefore the interrupt also) */ | ||
195 | else | ||
196 | CHCR3 |= 0x0001; /* re-enable the DMA */ | ||
197 | #endif | ||
198 | } | ||
199 | #else | ||
200 | #define check_audio_status() (true) | ||
201 | #endif | ||
202 | |||
203 | static int move_callback(int handle, void *current, void *new) | ||
204 | { | ||
205 | (void)handle;(void)current;(void)new; | ||
206 | if (UNLIKELY(talk_handle_locked)) | ||
207 | return BUFLIB_CB_CANNOT_MOVE; | ||
208 | return BUFLIB_CB_OK; | ||
209 | } | ||
210 | |||
211 | static int shrink_callback(int handle, unsigned hints, void *start, size_t old_size) | ||
212 | { | ||
213 | (void)start;(void)old_size; | ||
214 | int *h; | ||
215 | if (handle == talk_handle) | ||
216 | h = &talk_handle; | ||
217 | else // if (handle == thumb_handle) | ||
218 | h = &thumb_handle; | ||
219 | |||
220 | if (LIKELY(!talk_handle_locked) | ||
221 | && give_buffer_away | ||
222 | && (hints & BUFLIB_SHRINK_POS_MASK) == BUFLIB_SHRINK_POS_MASK) | ||
223 | { | ||
224 | *h = core_free(handle); | ||
225 | return BUFLIB_CB_OK; | ||
226 | } | ||
227 | return BUFLIB_CB_CANNOT_SHRINK; | ||
228 | } | ||
229 | |||
230 | static struct buflib_callbacks talk_ops = { | ||
231 | .move_callback = move_callback, | ||
232 | #if CONFIG_CODEC != SWCODEC | ||
233 | .sync_callback = sync_callback, | ||
234 | #endif | ||
235 | .shrink_callback = shrink_callback, | ||
236 | }; | ||
237 | |||
238 | |||
239 | static int index_handle, index_handle_locked; | ||
240 | static int index_move_callback(int handle, void *current, void *new) | ||
241 | { | ||
242 | (void)handle;(void)current;(void)new; | ||
243 | if (UNLIKELY(index_handle_locked)) | ||
244 | return BUFLIB_CB_CANNOT_MOVE; | ||
245 | return BUFLIB_CB_OK; | ||
246 | } | ||
247 | |||
248 | static int index_shrink_callback(int handle, unsigned hints, void *start, size_t old_size) | ||
249 | { | ||
250 | (void)start;(void)old_size; | ||
251 | if (LIKELY(!index_handle_locked) | ||
252 | && give_buffer_away | ||
253 | && (hints & BUFLIB_SHRINK_POS_MASK) == BUFLIB_SHRINK_POS_MASK) | ||
254 | { | ||
255 | index_handle = core_free(handle); | ||
256 | /* the clip buffer isn't usable without index table */ | ||
257 | if (LIKELY(!talk_handle_locked)) | ||
258 | talk_handle = core_free(talk_handle); | ||
259 | return BUFLIB_CB_OK; | ||
260 | } | ||
261 | return BUFLIB_CB_CANNOT_SHRINK; | ||
262 | } | ||
263 | |||
264 | static struct buflib_callbacks index_ops = { | ||
265 | .move_callback = index_move_callback, | ||
266 | .shrink_callback = index_shrink_callback, | ||
267 | }; | ||
268 | |||
173 | static int open_voicefile(void) | 269 | static int open_voicefile(void) |
174 | { | 270 | { |
175 | char buf[64]; | 271 | char buf[64]; |
@@ -188,38 +284,41 @@ static int open_voicefile(void) | |||
188 | 284 | ||
189 | 285 | ||
190 | /* fetch a clip from the voice file */ | 286 | /* fetch a clip from the voice file */ |
191 | static unsigned char* get_clip(long id, long* p_size) | 287 | static int get_clip(long id, long* p_size) |
192 | { | 288 | { |
193 | long clipsize; | 289 | int retval = -1; |
194 | unsigned char* clipbuf; | 290 | struct clip_entry* clipbuf; |
195 | 291 | size_t clipsize; | |
292 | |||
196 | if (id > VOICEONLY_DELIMITER) | 293 | if (id > VOICEONLY_DELIMITER) |
197 | { /* voice-only entries use the second part of the table. | 294 | { /* voice-only entries use the second part of the table. |
198 | The first string comes after VOICEONLY_DELIMITER so we need to | 295 | The first string comes after VOICEONLY_DELIMITER so we need to |
199 | substract VOICEONLY_DELIMITER + 1 */ | 296 | substract VOICEONLY_DELIMITER + 1 */ |
200 | id -= VOICEONLY_DELIMITER + 1; | 297 | id -= VOICEONLY_DELIMITER + 1; |
201 | if (id >= p_voicefile->id2_max) | 298 | if (id >= voicefile.id2_max) |
202 | return NULL; /* must be newer than we have */ | 299 | return -1; /* must be newer than we have */ |
203 | id += p_voicefile->id1_max; /* table 2 is behind table 1 */ | 300 | id += voicefile.id1_max; /* table 2 is behind table 1 */ |
204 | } | 301 | } |
205 | else | 302 | else |
206 | { /* normal use of the first table */ | 303 | { /* normal use of the first table */ |
207 | if (id >= p_voicefile->id1_max) | 304 | if (id >= voicefile.id1_max) |
208 | return NULL; /* must be newer than we have */ | 305 | return -1; /* must be newer than we have */ |
209 | } | 306 | } |
210 | 307 | ||
211 | clipsize = p_voicefile->index[id].size; | 308 | clipbuf = core_get_data(index_handle); |
309 | clipsize = clipbuf[id].size; | ||
212 | if (clipsize == 0) /* clip not included in voicefile */ | 310 | if (clipsize == 0) /* clip not included in voicefile */ |
213 | return NULL; | 311 | return -1; |
214 | 312 | ||
215 | #ifndef TALK_PARTIAL_LOAD | 313 | #ifndef TALK_PARTIAL_LOAD |
216 | clipbuf = (unsigned char *) p_voicefile + p_voicefile->index[id].offset; | 314 | retval = clipbuf[id].offset; |
217 | #endif | ||
218 | 315 | ||
219 | #ifdef TALK_PARTIAL_LOAD | 316 | #else |
220 | if (!(clipsize & LOADED_MASK)) | 317 | if (!(clipsize & LOADED_MASK)) |
221 | { /* clip needs loading */ | 318 | { /* clip needs loading */ |
222 | int idx = 0; | 319 | ssize_t ret; |
320 | int fd, idx = 0; | ||
321 | unsigned char *voicebuf; | ||
223 | if (id == VOICE_PAUSE) { | 322 | if (id == VOICE_PAUSE) { |
224 | idx = QUEUE_SIZE; /* we keep VOICE_PAUSE loaded */ | 323 | idx = QUEUE_SIZE; /* we keep VOICE_PAUSE loaded */ |
225 | } else { | 324 | } else { |
@@ -243,18 +342,29 @@ static unsigned char* get_clip(long id, long* p_size) | |||
243 | } | 342 | } |
244 | clip_age[idx] = 0; /* reset clip's age */ | 343 | clip_age[idx] = 0; /* reset clip's age */ |
245 | } | 344 | } |
246 | clipbuf = clip_buffer + idx * max_clipsize; | 345 | retval = idx * max_clipsize; |
346 | fd = open_voicefile(); | ||
347 | if (fd < 0) | ||
348 | return -1; /* open error */ | ||
349 | |||
350 | talk_handle_locked++; | ||
351 | voicebuf = core_get_data(talk_handle); | ||
352 | clipbuf = core_get_data(index_handle); | ||
353 | lseek(fd, clipbuf[id].offset, SEEK_SET); | ||
354 | ret = read(fd, &voicebuf[retval], clipsize); | ||
355 | close(fd); | ||
356 | talk_handle_locked--; | ||
247 | 357 | ||
248 | lseek(filehandle, p_voicefile->index[id].offset, SEEK_SET); | 358 | if (ret < 0 || clipsize != (size_t)ret) |
249 | if (read(filehandle, clipbuf, clipsize) != clipsize) | 359 | return -1; /* read error */ |
250 | return NULL; /* read error */ | ||
251 | 360 | ||
252 | p_voicefile->index[id].size |= LOADED_MASK; /* mark as loaded */ | 361 | clipbuf = core_get_data(index_handle); |
362 | clipbuf[id].size |= LOADED_MASK; /* mark as loaded */ | ||
253 | 363 | ||
254 | if (id != VOICE_PAUSE) { | 364 | if (id != VOICE_PAUSE) { |
255 | if (buffered_id[idx] >= 0) { | 365 | if (buffered_id[idx] >= 0) { |
256 | /* mark previously loaded clip as unloaded */ | 366 | /* mark previously loaded clip as unloaded */ |
257 | p_voicefile->index[buffered_id[idx]].size &= ~LOADED_MASK; | 367 | clipbuf[buffered_id[idx]].size &= ~LOADED_MASK; |
258 | } | 368 | } |
259 | buffered_id[idx] = id; | 369 | buffered_id[idx] = id; |
260 | } | 370 | } |
@@ -262,14 +372,13 @@ static unsigned char* get_clip(long id, long* p_size) | |||
262 | else | 372 | else |
263 | { /* clip is in memory already */ | 373 | { /* clip is in memory already */ |
264 | /* Find where it was loaded */ | 374 | /* Find where it was loaded */ |
265 | clipbuf = clip_buffer; | ||
266 | if (id == VOICE_PAUSE) { | 375 | if (id == VOICE_PAUSE) { |
267 | clipbuf += QUEUE_SIZE * max_clipsize; | 376 | retval = QUEUE_SIZE * max_clipsize; |
268 | } else { | 377 | } else { |
269 | int idx; | 378 | int idx; |
270 | for (idx=0; idx<QUEUE_SIZE; idx++) | 379 | for (idx=0; idx<QUEUE_SIZE; idx++) |
271 | if (buffered_id[idx] == id) { | 380 | if (buffered_id[idx] == id) { |
272 | clipbuf += idx * max_clipsize; | 381 | retval = idx * max_clipsize; |
273 | clip_age[idx] = 0; /* reset clip's age */ | 382 | clip_age[idx] = 0; /* reset clip's age */ |
274 | break; | 383 | break; |
275 | } | 384 | } |
@@ -279,153 +388,200 @@ static unsigned char* get_clip(long id, long* p_size) | |||
279 | #endif /* TALK_PARTIAL_LOAD */ | 388 | #endif /* TALK_PARTIAL_LOAD */ |
280 | 389 | ||
281 | *p_size = clipsize; | 390 | *p_size = clipsize; |
282 | return clipbuf; | 391 | return retval; |
283 | } | 392 | } |
284 | 393 | ||
285 | 394 | static bool load_index_table(int fd, const struct voicefile_header *hdr) | |
286 | /* load the voice file into the mp3 buffer */ | ||
287 | static void load_voicefile(bool probe, char* buf, size_t bufsize) | ||
288 | { | 395 | { |
289 | union voicebuf { | 396 | ssize_t ret; |
290 | unsigned char* buf; | 397 | struct clip_entry *buf; |
291 | struct voicefile* file; | ||
292 | }; | ||
293 | union voicebuf voicebuf; | ||
294 | 398 | ||
295 | size_t load_size, alloc_size; | 399 | if (index_handle > 0) /* nothing to do? */ |
296 | ssize_t got_size; | 400 | return true; |
297 | #ifdef ROCKBOX_LITTLE_ENDIAN | ||
298 | int i; | ||
299 | #endif | ||
300 | 401 | ||
301 | if (!probe) | 402 | ssize_t alloc_size = (hdr->id1_max + hdr->id2_max) * sizeof(struct clip_entry); |
302 | filehandle = open_voicefile(); | 403 | index_handle = core_alloc_ex("voice index", alloc_size, &index_ops); |
303 | if (filehandle < 0) /* failed to open */ | 404 | if (index_handle < 0) |
304 | goto load_err; | 405 | return false; |
305 | 406 | ||
306 | voicebuf.buf = buf; | 407 | index_handle_locked++; |
307 | if (!voicebuf.buf) | 408 | buf = core_get_data(index_handle); |
308 | goto load_err; | 409 | ret = read(fd, buf, alloc_size); |
309 | 410 | ||
310 | #ifdef TALK_PARTIAL_LOAD | 411 | #ifndef TALK_PARTIAL_LOAD |
311 | /* load only the header for now */ | 412 | int clips_offset, num_clips; |
312 | load_size = sizeof(struct voicefile); | 413 | /* adjust the offsets of the clips, they are relative to the file |
313 | #else | 414 | * TALK_PARTUAL_LOAD needs the file offset instead as it loads |
314 | /* load the entire file */ | 415 | * the clips later */ |
315 | load_size = filesize(filehandle); | 416 | clips_offset = hdr->table; |
417 | num_clips = hdr->id1_max + hdr->id2_max; | ||
418 | clips_offset += num_clips * sizeof(struct clip_entry); /* skip index */ | ||
419 | #endif | ||
420 | if (ret == alloc_size) | ||
421 | for (int i = 0; i < hdr->id1_max + hdr->id2_max; i++) | ||
422 | { | ||
423 | #ifdef ROCKBOX_LITTLE_ENDIAN | ||
424 | structec_convert(&buf[i], "ll", 1, true); | ||
316 | #endif | 425 | #endif |
317 | if (load_size > bufsize) /* won't fit? */ | 426 | #ifndef TALK_PARTIAL_LOAD |
318 | goto load_err; | 427 | buf[i].offset -= clips_offset; |
428 | #endif | ||
429 | } | ||
430 | |||
431 | index_handle_locked--; | ||
319 | 432 | ||
320 | got_size = read(filehandle, voicebuf.buf, load_size); | 433 | if (ret != alloc_size) |
321 | if (got_size != (ssize_t)load_size /* failure */) | 434 | index_handle = core_free(index_handle); |
322 | goto load_err; | ||
323 | 435 | ||
324 | alloc_size = load_size; | 436 | return ret == alloc_size; |
437 | } | ||
438 | |||
439 | static bool load_header(int fd, struct voicefile_header *hdr) | ||
440 | { | ||
441 | ssize_t got_size = read(fd, hdr, sizeof(*hdr)); | ||
442 | if (got_size != sizeof(*hdr)) | ||
443 | return false; | ||
325 | 444 | ||
326 | #ifdef ROCKBOX_LITTLE_ENDIAN | 445 | #ifdef ROCKBOX_LITTLE_ENDIAN |
327 | logf("Byte swapping voice file"); | 446 | logf("Byte swapping voice file"); |
328 | structec_convert(voicebuf.buf, "lllll", 1, true); | 447 | structec_convert(&voicefile, "lllll", 1, true); |
329 | #endif | 448 | #endif |
449 | return true; | ||
450 | } | ||
330 | 451 | ||
331 | /* format check */ | 452 | #ifndef TALK_PARTIAL_LOAD |
332 | if (voicebuf.file->table == sizeof(struct voicefile)) | 453 | static bool load_data(int fd, ssize_t size_to_read) |
333 | { | 454 | { |
334 | p_voicefile = voicebuf.file; | 455 | unsigned char *buf; |
335 | 456 | ssize_t ret; | |
336 | if (p_voicefile->version != VOICE_VERSION || | ||
337 | p_voicefile->target_id != TARGET_ID) | ||
338 | { | ||
339 | logf("Incompatible voice file"); | ||
340 | goto load_err; | ||
341 | } | ||
342 | } | ||
343 | else | ||
344 | goto load_err; | ||
345 | 457 | ||
346 | #ifdef TALK_PARTIAL_LOAD | 458 | if (size_to_read < 0) |
347 | /* load the index table, now that we know its size from the header */ | 459 | return false; |
348 | load_size = (p_voicefile->id1_max + p_voicefile->id2_max) | ||
349 | * sizeof(struct clip_entry); | ||
350 | 460 | ||
351 | if (load_size > bufsize) /* won't fit? */ | 461 | talk_handle = core_alloc_ex("voice data", size_to_read, &talk_ops); |
352 | goto load_err; | 462 | if (talk_handle < 0) |
463 | return false; | ||
353 | 464 | ||
354 | got_size = read(filehandle, &p_voicefile->index[0], load_size); | 465 | talk_handle_locked++; |
355 | if (got_size != (ssize_t)load_size) /* read error */ | 466 | buf = core_get_data(talk_handle); |
356 | goto load_err; | 467 | ret = read(fd, buf, size_to_read); |
468 | talk_handle_locked--; | ||
357 | 469 | ||
358 | alloc_size += load_size; | 470 | if (ret != size_to_read) |
359 | #else | 471 | talk_handle = core_free(talk_handle); |
360 | close(filehandle); | ||
361 | filehandle = -1; | ||
362 | #endif /* TALK_PARTIAL_LOAD */ | ||
363 | 472 | ||
364 | #ifdef ROCKBOX_LITTLE_ENDIAN | 473 | return ret == size_to_read; |
365 | for (i = 0; i < p_voicefile->id1_max + p_voicefile->id2_max; i++) | 474 | } |
366 | structec_convert(&p_voicefile->index[i], "ll", 1, true); | ||
367 | #endif | 475 | #endif |
368 | 476 | ||
369 | #ifdef TALK_PARTIAL_LOAD | 477 | static bool alloc_thumbnail_buf(void) |
370 | clip_buffer = (unsigned char *) p_voicefile + p_voicefile->table; | 478 | { |
371 | unsigned clips = p_voicefile->id1_max + p_voicefile->id2_max; | 479 | int handle; |
372 | clip_buffer += clips * sizeof(struct clip_entry); /* skip index */ | 480 | size_t size; |
481 | if (thumb_handle > 0) | ||
482 | return true; /* nothing to do? */ | ||
483 | #if CONFIG_CODEC == SWCODEC | ||
484 | /* try to allocate the max. first, and take whatever we can get if that | ||
485 | * fails */ | ||
486 | size = MAX_THUMBNAIL_BUFSIZE; | ||
487 | handle = core_alloc_ex("voice thumb", MAX_THUMBNAIL_BUFSIZE, &talk_ops); | ||
488 | if (handle < 0) | ||
489 | { | ||
490 | size = core_allocatable(); | ||
491 | handle = core_alloc_ex("voice thumb", size, &talk_ops); | ||
492 | } | ||
493 | #else | ||
494 | /* on HWCODEC, just use the rest of the remaining buffer, | ||
495 | * normal playback cannot happen anyway */ | ||
496 | handle = core_alloc_maximum("voice thumb", &size, &talk_ops); | ||
373 | #endif | 497 | #endif |
374 | if (!probe) { | 498 | thumb_handle = handle; |
375 | /* make sure to have the silence clip, if available */ | 499 | size_for_thumbnail = (handle > 0) ? size : 0; |
376 | p_silence = get_clip(VOICE_PAUSE, &silence_len); | 500 | return handle > 0; |
501 | } | ||
502 | |||
503 | /* load the voice file into the mp3 buffer */ | ||
504 | static bool load_voicefile_index(int fd) | ||
505 | { | ||
506 | if (fd < 0) /* failed to open */ | ||
507 | return false; | ||
508 | |||
509 | /* load the header first */ | ||
510 | if (!load_header(fd, &voicefile)) | ||
511 | return false; | ||
512 | |||
513 | /* format check */ | ||
514 | if (voicefile.table == sizeof(struct voicefile_header)) | ||
515 | { | ||
516 | if (voicefile.version == VOICE_VERSION && | ||
517 | voicefile.target_id == TARGET_ID) | ||
518 | { | ||
519 | if (load_index_table(fd, &voicefile)) | ||
520 | return true; | ||
521 | } | ||
377 | } | 522 | } |
378 | 523 | ||
524 | logf("Incompatible voice file"); | ||
525 | return false; | ||
526 | } | ||
527 | |||
528 | static bool load_voicefile_data(int fd, size_t max_size) | ||
529 | { | ||
379 | #ifdef TALK_PARTIAL_LOAD | 530 | #ifdef TALK_PARTIAL_LOAD |
380 | alloc_size += silence_len + QUEUE_SIZE; | 531 | /* just allocate, populate on an as-needed basis later */ |
532 | talk_handle = core_alloc_ex("voice data", max_size, &talk_ops); | ||
533 | if (talk_handle < 0) | ||
534 | goto load_err_free; | ||
535 | #else | ||
536 | size_t load_size, clips_size; | ||
537 | /* load the entire file into memory */ | ||
538 | clips_size = (voicefile.id1_max+voicefile.id2_max) * sizeof(struct clip_entry); | ||
539 | load_size = max_size - voicefile.table - clips_size; | ||
540 | if (!load_data(fd, load_size)) | ||
541 | goto load_err_free; | ||
381 | #endif | 542 | #endif |
382 | 543 | ||
383 | if (alloc_size > bufsize) | 544 | /* make sure to have the silence clip, if available |
384 | goto load_err; | 545 | * return value can be cached globally even for TALK_PARTIAL_LOAD because |
546 | * the VOICE_PAUSE clip is specially handled */ | ||
547 | silence_offset = get_clip(VOICE_PAUSE, &silence_length); | ||
385 | 548 | ||
386 | /* now move p_thumbnail behind the voice clip buffer */ | 549 | /* not an error if this fails here, might try again when the |
387 | p_thumbnail = voicebuf.buf + alloc_size; | 550 | * actual thumbnails are attempted to be played back */ |
388 | p_thumbnail += (long)p_thumbnail % 2; /* 16-bit align */ | 551 | alloc_thumbnail_buf(); |
389 | size_for_thumbnail = voicebuf.buf + bufsize - p_thumbnail; | ||
390 | #if CONFIG_CODEC == SWCODEC | ||
391 | size_for_thumbnail = MIN(size_for_thumbnail, MAX_THUMBNAIL_BUFSIZE); | ||
392 | #endif | ||
393 | if (size_for_thumbnail <= 0) | ||
394 | p_thumbnail = NULL; | ||
395 | 552 | ||
396 | return; | 553 | return true; |
397 | load_err: | 554 | |
398 | p_voicefile = NULL; | 555 | load_err_free: |
399 | has_voicefile = false; /* don't try again */ | 556 | index_handle = core_free(index_handle); |
400 | if (filehandle >= 0) | 557 | return false; |
401 | { | ||
402 | close(filehandle); | ||
403 | filehandle = -1; | ||
404 | } | ||
405 | return; | ||
406 | } | 558 | } |
407 | 559 | ||
408 | 560 | ||
409 | /* called in ISR context (on HWCODEC) if mp3 data got consumed */ | 561 | /* called in ISR context (on HWCODEC) if mp3 data got consumed */ |
410 | static void mp3_callback(const void** start, size_t* size) | 562 | static void mp3_callback(const void** start, size_t* size) |
411 | { | 563 | { |
412 | queue[queue_read].len -= sent; /* we completed this */ | 564 | queue[queue_read].length -= sent; /* we completed this */ |
413 | queue[queue_read].buf += sent; | 565 | queue[queue_read].offset += sent; |
414 | 566 | ||
415 | if (queue[queue_read].len > 0) /* current clip not finished? */ | 567 | if (queue[queue_read].length > 0) /* current clip not finished? */ |
416 | { /* feed the next 64K-1 chunk */ | 568 | { /* feed the next 64K-1 chunk */ |
569 | int offset; | ||
417 | #if CONFIG_CODEC != SWCODEC | 570 | #if CONFIG_CODEC != SWCODEC |
418 | sent = MIN(queue[queue_read].len, 0xFFFF); | 571 | sent = MIN(queue[queue_read].length, 0xFFFF); |
419 | #else | 572 | #else |
420 | sent = queue[queue_read].len; | 573 | sent = queue[queue_read].length; |
421 | #endif | 574 | #endif |
422 | *start = queue[queue_read].buf; | 575 | offset = queue[queue_read].offset; |
576 | if ((unsigned long)offset >= voicefile_size) | ||
577 | *start = core_get_data(thumb_handle) + offset - voicefile_size; | ||
578 | else | ||
579 | *start = core_get_data(talk_handle) + offset; | ||
423 | *size = sent; | 580 | *size = sent; |
424 | return; | 581 | return; |
425 | } | 582 | } |
426 | talk_queue_lock(); | 583 | talk_queue_lock(); |
427 | if(p_thumbnail | 584 | if(thumb_handle && (unsigned long)queue[queue_read].offset == voicefile_size+thumbnail_buf_used) |
428 | && queue[queue_read].buf == p_thumbnail +thumbnail_buf_used) | ||
429 | thumbnail_buf_used = 0; | 585 | thumbnail_buf_used = 0; |
430 | if (sent > 0) /* go to next entry */ | 586 | if (sent > 0) /* go to next entry */ |
431 | { | 587 | { |
@@ -436,24 +592,31 @@ re_check: | |||
436 | 592 | ||
437 | if (QUEUE_LEVEL != 0) /* queue is not empty? */ | 593 | if (QUEUE_LEVEL != 0) /* queue is not empty? */ |
438 | { /* start next clip */ | 594 | { /* start next clip */ |
595 | unsigned char *buf; | ||
439 | #if CONFIG_CODEC != SWCODEC | 596 | #if CONFIG_CODEC != SWCODEC |
440 | sent = MIN(queue[queue_read].len, 0xFFFF); | 597 | sent = MIN(queue[queue_read].length, 0xFFFF); |
441 | #else | 598 | #else |
442 | sent = queue[queue_read].len; | 599 | sent = queue[queue_read].length; |
443 | #endif | 600 | #endif |
444 | *start = p_lastclip = queue[queue_read].buf; | 601 | lastclip_offset = queue[queue_read].offset; |
602 | /* offsets larger than voicefile_size denote thumbnail clips */ | ||
603 | if (lastclip_offset >= voicefile_size) | ||
604 | buf = core_get_data(thumb_handle) + lastclip_offset - voicefile_size; | ||
605 | else | ||
606 | buf = core_get_data(talk_handle) + lastclip_offset; | ||
607 | *start = buf; | ||
445 | *size = sent; | 608 | *size = sent; |
446 | curr_hd[0] = p_lastclip[1]; | 609 | curr_hd[0] = buf[1]; |
447 | curr_hd[1] = p_lastclip[2]; | 610 | curr_hd[1] = buf[2]; |
448 | curr_hd[2] = p_lastclip[3]; | 611 | curr_hd[2] = buf[3]; |
449 | } | 612 | } |
450 | else if (p_silence != NULL /* silence clip available */ | 613 | else if (silence_offset > 0 /* silence clip available */ |
451 | && p_lastclip != p_silence /* previous clip wasn't silence */ | 614 | && lastclip_offset != (unsigned long)silence_offset /* previous clip wasn't silence */ |
452 | && !(p_lastclip >= p_thumbnail /* ..or thumbnail */ | 615 | && !(lastclip_offset >= voicefile_size /* ..or thumbnail */ |
453 | && p_lastclip < p_thumbnail +size_for_thumbnail)) | 616 | && lastclip_offset < voicefile_size +size_for_thumbnail)) |
454 | { /* add silence clip when queue runs empty playing a voice clip */ | 617 | { /* add silence clip when queue runs empty playing a voice clip */ |
455 | queue[queue_write].buf = p_silence; | 618 | queue[queue_write].offset = silence_offset; |
456 | queue[queue_write].len = silence_len; | 619 | queue[queue_write].length = silence_length; |
457 | queue_write = (queue_write + 1) & QUEUE_MASK; | 620 | queue_write = (queue_write + 1) & QUEUE_MASK; |
458 | 621 | ||
459 | goto re_check; | 622 | goto re_check; |
@@ -461,6 +624,7 @@ re_check: | |||
461 | else | 624 | else |
462 | { | 625 | { |
463 | *size = 0; /* end of data */ | 626 | *size = 0; /* end of data */ |
627 | talk_handle_locked--; | ||
464 | } | 628 | } |
465 | talk_queue_unlock(); | 629 | talk_queue_unlock(); |
466 | } | 630 | } |
@@ -478,6 +642,8 @@ void talk_force_shutup(void) | |||
478 | unsigned char* pos; | 642 | unsigned char* pos; |
479 | unsigned char* search; | 643 | unsigned char* search; |
480 | unsigned char* end; | 644 | unsigned char* end; |
645 | int len; | ||
646 | unsigned clip_offset; | ||
481 | if (QUEUE_LEVEL == 0) /* has ended anyway */ | 647 | if (QUEUE_LEVEL == 0) /* has ended anyway */ |
482 | return; | 648 | return; |
483 | 649 | ||
@@ -486,13 +652,17 @@ void talk_force_shutup(void) | |||
486 | #endif /* CONFIG_CPU == SH7034 */ | 652 | #endif /* CONFIG_CPU == SH7034 */ |
487 | /* search next frame boundary and continue up to there */ | 653 | /* search next frame boundary and continue up to there */ |
488 | pos = search = mp3_get_pos(); | 654 | pos = search = mp3_get_pos(); |
489 | end = queue[queue_read].buf + queue[queue_read].len; | 655 | clip_offset = queue[queue_read].offset; |
656 | if (clip_offset >= voicefile_size) | ||
657 | end = core_get_data(thumb_handle) + clip_offset - voicefile_size; | ||
658 | else | ||
659 | end = core_get_data(talk_handle) + clip_offset; | ||
660 | len = queue[queue_read].length; | ||
490 | 661 | ||
491 | if (pos >= queue[queue_read].buf | 662 | if (pos >= end && pos <= (end+len)) /* really our clip? */ |
492 | && pos <= end) /* really our clip? */ | ||
493 | { /* (for strange reasons this isn't nesessarily the case) */ | 663 | { /* (for strange reasons this isn't nesessarily the case) */ |
494 | /* find the next frame boundary */ | 664 | /* find the next frame boundary */ |
495 | while (search < end) /* search the remaining data */ | 665 | while (search < (end+len)) /* search the remaining data */ |
496 | { | 666 | { |
497 | if (*search++ != 0xFF) /* quick search for frame sync byte */ | 667 | if (*search++ != 0xFF) /* quick search for frame sync byte */ |
498 | continue; /* (this does the majority of the job) */ | 668 | continue; /* (this does the majority of the job) */ |
@@ -512,7 +682,7 @@ void talk_force_shutup(void) | |||
512 | sent = search-pos; | 682 | sent = search-pos; |
513 | 683 | ||
514 | queue_write = (queue_read + 1) & QUEUE_MASK; /* will be empty after next callback */ | 684 | queue_write = (queue_read + 1) & QUEUE_MASK; /* will be empty after next callback */ |
515 | queue[queue_read].len = sent; /* current one ends after this */ | 685 | queue[queue_read].length = sent; /* current one ends after this */ |
516 | 686 | ||
517 | #if CONFIG_CPU == SH7034 | 687 | #if CONFIG_CPU == SH7034 |
518 | DTCR3 = sent; /* let the DMA finish this frame */ | 688 | DTCR3 = sent; /* let the DMA finish this frame */ |
@@ -528,6 +698,7 @@ void talk_force_shutup(void) | |||
528 | mp3_play_stop(); | 698 | mp3_play_stop(); |
529 | talk_queue_lock(); | 699 | talk_queue_lock(); |
530 | queue_write = queue_read = 0; /* reset the queue */ | 700 | queue_write = queue_read = 0; /* reset the queue */ |
701 | talk_handle_locked = MAX(talk_handle_locked-1, 0); | ||
531 | thumbnail_buf_used = 0; | 702 | thumbnail_buf_used = 0; |
532 | talk_queue_unlock(); | 703 | talk_queue_unlock(); |
533 | need_shutup = false; | 704 | need_shutup = false; |
@@ -541,8 +712,9 @@ void talk_shutup(void) | |||
541 | } | 712 | } |
542 | 713 | ||
543 | /* schedule a clip, at the end or discard the existing queue */ | 714 | /* schedule a clip, at the end or discard the existing queue */ |
544 | static void queue_clip(unsigned char* buf, long size, bool enqueue) | 715 | static void queue_clip(unsigned long clip_offset, long size, bool enqueue) |
545 | { | 716 | { |
717 | unsigned char *buf; | ||
546 | int queue_level; | 718 | int queue_level; |
547 | 719 | ||
548 | if (!enqueue) | 720 | if (!enqueue) |
@@ -562,20 +734,25 @@ static void queue_clip(unsigned char* buf, long size, bool enqueue) | |||
562 | 734 | ||
563 | if (queue_level < QUEUE_SIZE - 1) /* space left? */ | 735 | if (queue_level < QUEUE_SIZE - 1) /* space left? */ |
564 | { | 736 | { |
565 | queue[queue_write].buf = buf; /* populate an entry */ | 737 | queue[queue_write].offset = clip_offset; /* populate an entry */ |
566 | queue[queue_write].len = size; | 738 | queue[queue_write].length = size; |
567 | queue_write = (queue_write + 1) & QUEUE_MASK; | 739 | queue_write = (queue_write + 1) & QUEUE_MASK; |
568 | } | 740 | } |
569 | talk_queue_unlock(); | 741 | talk_queue_unlock(); |
570 | 742 | ||
571 | if (queue_level == 0) | 743 | if (queue_level == 0) |
572 | { /* queue was empty, we have to do the initial start */ | 744 | { /* queue was empty, we have to do the initial start */ |
573 | p_lastclip = buf; | 745 | lastclip_offset = clip_offset; |
574 | #if CONFIG_CODEC != SWCODEC | 746 | #if CONFIG_CODEC != SWCODEC |
575 | sent = MIN(size, 0xFFFF); /* DMA can do no more */ | 747 | sent = MIN(size, 0xFFFF); /* DMA can do no more */ |
576 | #else | 748 | #else |
577 | sent = size; | 749 | sent = size; |
578 | #endif | 750 | #endif |
751 | talk_handle_locked++; | ||
752 | if (clip_offset >= voicefile_size) | ||
753 | buf = core_get_data(thumb_handle) + clip_offset - voicefile_size; | ||
754 | else | ||
755 | buf = core_get_data(talk_handle) + clip_offset; | ||
579 | mp3_play_data(buf, sent, mp3_callback); | 756 | mp3_play_data(buf, sent, mp3_callback); |
580 | curr_hd[0] = buf[1]; | 757 | curr_hd[0] = buf[1]; |
581 | curr_hd[1] = buf[2]; | 758 | curr_hd[1] = buf[2]; |
@@ -594,53 +771,11 @@ static void queue_clip(unsigned char* buf, long size, bool enqueue) | |||
594 | return; | 771 | return; |
595 | } | 772 | } |
596 | 773 | ||
597 | static void alloc_thumbnail_buf(void) | ||
598 | { | ||
599 | /* use the audio buffer now, need to release before loading a voice */ | ||
600 | p_thumbnail = voicebuf; | ||
601 | #if CONFIG_CODEC == SWCODEC | ||
602 | size_for_thumbnail = MAX_THUMBNAIL_BUFSIZE; | ||
603 | #endif | ||
604 | thumbnail_buf_used = 0; | ||
605 | } | ||
606 | |||
607 | /* common code for talk_init() and talk_buffer_steal() */ | ||
608 | static void reset_state(void) | ||
609 | { | ||
610 | queue_write = queue_read = 0; /* reset the queue */ | ||
611 | p_voicefile = NULL; /* indicate no voicefile (trashed) */ | ||
612 | p_thumbnail = NULL; /* no thumbnails either */ | ||
613 | |||
614 | #ifdef TALK_PARTIAL_LOAD | ||
615 | int i; | ||
616 | for(i=0; i<QUEUE_SIZE; i++) | ||
617 | buffered_id[i] = -1; | ||
618 | #endif | ||
619 | |||
620 | p_silence = NULL; /* pause clip not accessible */ | ||
621 | voicebuf = NULL; /* voice buffer is gone */ | ||
622 | } | ||
623 | |||
624 | #if CONFIG_CODEC == SWCODEC | ||
625 | static bool restore_state(void) | ||
626 | { | ||
627 | if (!voicebuf) | ||
628 | { | ||
629 | size_t size; | ||
630 | audio_restore_playback(AUDIO_WANT_VOICE); | ||
631 | voicebuf = audio_get_buffer(true, &size); | ||
632 | audio_get_buffer(false, &size); | ||
633 | } | ||
634 | |||
635 | return !!voicebuf; | ||
636 | } | ||
637 | #endif /* CONFIG_CODEC == SWCODEC */ | ||
638 | |||
639 | |||
640 | /***************** Public implementation *****************/ | 774 | /***************** Public implementation *****************/ |
641 | 775 | ||
642 | void talk_init(void) | 776 | void talk_init(void) |
643 | { | 777 | { |
778 | int filehandle; | ||
644 | talk_temp_disable_count = 0; | 779 | talk_temp_disable_count = 0; |
645 | if (talk_initialized && !strcasecmp(last_lang, global_settings.lang_file)) | 780 | if (talk_initialized && !strcasecmp(last_lang, global_settings.lang_file)) |
646 | { | 781 | { |
@@ -648,14 +783,6 @@ void talk_init(void) | |||
648 | return; | 783 | return; |
649 | } | 784 | } |
650 | 785 | ||
651 | #if defined(TALK_PROGRESSIVE_LOAD) || defined(TALK_PARTIAL_LOAD) | ||
652 | if (filehandle >= 0) | ||
653 | { | ||
654 | close(filehandle); | ||
655 | filehandle = -1; | ||
656 | } | ||
657 | #endif | ||
658 | |||
659 | #if CONFIG_CODEC == SWCODEC | 786 | #if CONFIG_CODEC == SWCODEC |
660 | if(!talk_initialized) | 787 | if(!talk_initialized) |
661 | mutex_init(&queue_mutex); | 788 | mutex_init(&queue_mutex); |
@@ -665,138 +792,118 @@ void talk_init(void) | |||
665 | strlcpy((char *)last_lang, (char *)global_settings.lang_file, | 792 | strlcpy((char *)last_lang, (char *)global_settings.lang_file, |
666 | MAX_FILENAME); | 793 | MAX_FILENAME); |
667 | 794 | ||
795 | /* reset some states */ | ||
796 | queue_write = queue_read = 0; /* reset the queue */ | ||
797 | memset(&voicefile, 0, sizeof(voicefile)); | ||
798 | |||
799 | #ifdef TALK_PARTIAL_LOAD | ||
800 | for(int i=0; i<QUEUE_SIZE; i++) | ||
801 | buffered_id[i] = -1; | ||
802 | #endif | ||
803 | |||
804 | silence_offset = -1; /* pause clip not accessible */ | ||
805 | voicefile_size = has_voicefile = 0; | ||
806 | /* need to free these as their size depends on the voice file, and | ||
807 | * this function is called when the talk voice file changes */ | ||
808 | if (index_handle > 0) index_handle = core_free(index_handle); | ||
809 | if (talk_handle > 0) talk_handle = core_free(talk_handle); | ||
810 | /* don't free thumb handle, it doesn't depend on the actual voice file | ||
811 | * and so we can re-use it if it's already allocated in any event */ | ||
812 | |||
668 | filehandle = open_voicefile(); | 813 | filehandle = open_voicefile(); |
669 | if (filehandle < 0) { | 814 | if (filehandle < 0) |
670 | has_voicefile = false; | ||
671 | voicefile_size = 0; | ||
672 | return; | 815 | return; |
673 | } | ||
674 | 816 | ||
675 | voicefile_size = filesize(filehandle); | 817 | if (!load_voicefile_index(filehandle)) |
676 | 818 | goto out; | |
677 | audio_get_buffer(false, NULL); /* Must tell audio to reinitialize */ | ||
678 | reset_state(); /* use this for most of our inits */ | ||
679 | 819 | ||
680 | #ifdef TALK_PARTIAL_LOAD | 820 | #ifdef TALK_PARTIAL_LOAD |
681 | size_t bufsize; | 821 | /* TALK_PARTIAL_LOAD loads the actual clip data later, and not all |
682 | char* buf = plugin_get_buffer(&bufsize); | 822 | * at once */ |
683 | /* we won't load the full file, we only need the index */ | 823 | unsigned num_clips = voicefile.id1_max + voicefile.id2_max; |
684 | load_voicefile(true, buf, bufsize); | 824 | struct clip_entry *clips = core_get_data(index_handle); |
685 | if (!p_voicefile) | ||
686 | return; | ||
687 | |||
688 | unsigned clips = p_voicefile->id1_max + p_voicefile->id2_max; | ||
689 | unsigned i; | ||
690 | int silence_size = 0; | 825 | int silence_size = 0; |
691 | 826 | ||
692 | for(i=0; i<clips; i++) { | 827 | for(unsigned i=0; i<num_clips; i++) { |
693 | int size = p_voicefile->index[i].size; | 828 | int size = clips[i].size; |
694 | if (size > max_clipsize) | 829 | if (size > max_clipsize) |
695 | max_clipsize = size; | 830 | max_clipsize = size; |
696 | if (i == VOICE_PAUSE) | 831 | if (i == VOICE_PAUSE) |
697 | silence_size = size; | 832 | silence_size = size; |
698 | } | 833 | } |
699 | 834 | ||
700 | voicefile_size = p_voicefile->table + clips * sizeof(struct clip_entry); | 835 | voicefile_size = voicefile.table + num_clips * sizeof(struct clip_entry); |
701 | voicefile_size += max_clipsize * QUEUE_SIZE + silence_size; | 836 | voicefile_size += max_clipsize * QUEUE_SIZE + silence_size; |
702 | p_voicefile = NULL; /* Don't pretend we can load talk clips just yet */ | ||
703 | #endif | ||
704 | |||
705 | 837 | ||
706 | /* test if we can open and if it fits in the audiobuffer */ | 838 | /* test if we can open and if it fits in the audiobuffer */ |
707 | size_t audiobufsz = audio_buffer_available(); | 839 | size_t audiobufsz = audio_buffer_available(); |
708 | if (voicefile_size <= audiobufsz) { | 840 | has_voicefile = audiobufsz >= voicefile_size; |
709 | has_voicefile = true; | 841 | |
710 | } else { | 842 | #else |
711 | has_voicefile = false; | 843 | /* load the compressed clip data into memory, in its entirety */ |
844 | voicefile_size = filesize(filehandle); | ||
845 | if (!load_voicefile_data(filehandle, voicefile_size)) | ||
846 | { | ||
712 | voicefile_size = 0; | 847 | voicefile_size = 0; |
848 | goto out; | ||
713 | } | 849 | } |
714 | 850 | has_voicefile = true; | |
715 | close(filehandle); /* close again, this was just to detect presence */ | 851 | #endif |
716 | filehandle = -1; | ||
717 | 852 | ||
718 | #if CONFIG_CODEC == SWCODEC | 853 | #if CONFIG_CODEC == SWCODEC |
719 | /* Safe to init voice playback engine now since we now know if talk is | 854 | /* Safe to init voice playback engine now since we now know if talk is |
720 | required or not */ | 855 | required or not */ |
721 | voice_thread_init(); | 856 | voice_thread_init(); |
722 | #endif | 857 | #endif |
858 | |||
859 | out: | ||
860 | close(filehandle); /* close again, this was just to detect presence */ | ||
861 | filehandle = -1; | ||
723 | } | 862 | } |
724 | 863 | ||
725 | #if CONFIG_CODEC == SWCODEC | 864 | #if CONFIG_CODEC == SWCODEC |
726 | /* return if a voice codec is required or not */ | 865 | /* return if a voice codec is required or not */ |
727 | bool talk_voice_required(void) | 866 | bool talk_voice_required(void) |
728 | { | 867 | { |
729 | return (voicefile_size != 0) /* Voice file is available */ | 868 | return (has_voicefile) /* Voice file is available */ |
730 | || (global_settings.talk_dir_clip) /* Thumbnail clips are required */ | 869 | || (global_settings.talk_dir_clip) /* Thumbnail clips are required */ |
731 | || (global_settings.talk_file_clip); | 870 | || (global_settings.talk_file_clip); |
732 | } | 871 | } |
733 | #endif | 872 | #endif |
734 | 873 | ||
735 | /* return size of voice file */ | ||
736 | static size_t talk_get_buffer_size(void) | ||
737 | { | ||
738 | #if CONFIG_CODEC == SWCODEC | ||
739 | return voicefile_size + MAX_THUMBNAIL_BUFSIZE; | ||
740 | #else | ||
741 | return audio_buffer_available(); | ||
742 | #endif | ||
743 | } | ||
744 | |||
745 | /* Sets the buffer for the voicefile and returns how many bytes of this | ||
746 | * buffer we will use for the voicefile */ | ||
747 | size_t talkbuf_init(char *bufstart) | ||
748 | { | ||
749 | bool changed = voicebuf != bufstart; | ||
750 | |||
751 | if (changed) /* must reload voice file */ | ||
752 | reset_state(); | ||
753 | |||
754 | if (bufstart) | ||
755 | voicebuf = bufstart; | ||
756 | |||
757 | return talk_get_buffer_size(); | ||
758 | } | ||
759 | |||
760 | /* somebody else claims the mp3 buffer, e.g. for regular play/record */ | 874 | /* somebody else claims the mp3 buffer, e.g. for regular play/record */ |
761 | void talk_buffer_steal(void) | 875 | void talk_buffer_set_policy(int policy) |
762 | { | 876 | { |
763 | #if CONFIG_CODEC != SWCODEC | 877 | switch(policy) |
764 | mp3_play_stop(); | ||
765 | #endif | ||
766 | #if defined(TALK_PROGRESSIVE_LOAD) || defined(TALK_PARTIAL_LOAD) | ||
767 | if (filehandle >= 0) | ||
768 | { | 878 | { |
769 | close(filehandle); | 879 | case TALK_BUFFER_DEFAULT: |
770 | filehandle = -1; | 880 | case TALK_BUFFER_HOLD: give_buffer_away = false; break; |
881 | case TALK_BUFFER_LOOSE: give_buffer_away = true; break; | ||
882 | default: DEBUGF("Ignoring unknown policy\n"); break; | ||
771 | } | 883 | } |
772 | #endif | ||
773 | reset_state(); | ||
774 | } | 884 | } |
775 | 885 | ||
776 | /* play a voice ID from voicefile */ | 886 | /* play a voice ID from voicefile */ |
777 | int talk_id(int32_t id, bool enqueue) | 887 | int talk_id(int32_t id, bool enqueue) |
778 | { | 888 | { |
889 | int clip; | ||
779 | long clipsize; | 890 | long clipsize; |
780 | unsigned char* clipbuf; | ||
781 | int32_t unit; | 891 | int32_t unit; |
782 | int decimals; | 892 | int decimals; |
783 | 893 | ||
784 | if (talk_temp_disable_count > 0) | 894 | if (talk_temp_disable_count > 0) |
785 | return -1; /* talking has been disabled */ | 895 | return -1; /* talking has been disabled */ |
786 | #if CONFIG_CODEC == SWCODEC | 896 | if (!check_audio_status()) |
787 | /* If talk buffer was stolen, it must be restored for voicefile's sake */ | ||
788 | if (!restore_state()) | ||
789 | return -1; /* cannot get any space */ | ||
790 | #else | ||
791 | if (audio_status()) /* busy, buffer in use */ | ||
792 | return -1; | 897 | return -1; |
793 | #endif | ||
794 | |||
795 | if (p_voicefile == NULL && has_voicefile) /* reload needed? */ | ||
796 | load_voicefile(false, voicebuf, talk_get_buffer_size()); | ||
797 | 898 | ||
798 | if (p_voicefile == NULL) /* still no voices? */ | 899 | if (has_voicefile && (talk_handle <= 0 || index_handle <= 0)) /* reload needed? */ |
799 | return -1; | 900 | { |
901 | int fd = open_voicefile(); | ||
902 | if (fd < 0 | ||
903 | || !load_voicefile_index(fd) | ||
904 | || !load_voicefile_data(fd, voicefile_size)) | ||
905 | return -1; | ||
906 | } | ||
800 | 907 | ||
801 | if (id == -1) /* -1 is an indication for silence */ | 908 | if (id == -1) /* -1 is an indication for silence */ |
802 | return -1; | 909 | return -1; |
@@ -814,8 +921,8 @@ int talk_id(int32_t id, bool enqueue) | |||
814 | return 0; /* and stop, end of special case */ | 921 | return 0; /* and stop, end of special case */ |
815 | } | 922 | } |
816 | 923 | ||
817 | clipbuf = get_clip(id, &clipsize); | 924 | clip = get_clip(id, &clipsize); |
818 | if (clipbuf == NULL) | 925 | if (clip < 0) |
819 | return -1; /* not present */ | 926 | return -1; /* not present */ |
820 | 927 | ||
821 | #ifdef LOGF_ENABLE | 928 | #ifdef LOGF_ENABLE |
@@ -825,7 +932,7 @@ int talk_id(int32_t id, bool enqueue) | |||
825 | logf("\ntalk_id: Say '%s'\n", str(id)); | 932 | logf("\ntalk_id: Say '%s'\n", str(id)); |
826 | #endif | 933 | #endif |
827 | 934 | ||
828 | queue_clip(clipbuf, clipsize, enqueue); | 935 | queue_clip(clip, clipsize, enqueue); |
829 | 936 | ||
830 | return 0; | 937 | return 0; |
831 | } | 938 | } |
@@ -859,23 +966,18 @@ static int _talk_file(const char* filename, | |||
859 | int fd; | 966 | int fd; |
860 | int size; | 967 | int size; |
861 | int thumb_used; | 968 | int thumb_used; |
969 | char *buf; | ||
862 | #if CONFIG_CODEC != SWCODEC | 970 | #if CONFIG_CODEC != SWCODEC |
863 | struct mp3entry info; | 971 | struct mp3entry info; |
864 | #endif | 972 | #endif |
865 | 973 | ||
866 | if (talk_temp_disable_count > 0) | 974 | if (talk_temp_disable_count > 0) |
867 | return -1; /* talking has been disabled */ | 975 | return -1; /* talking has been disabled */ |
868 | #if CONFIG_CODEC == SWCODEC | 976 | if (!check_audio_status()) |
869 | /* If talk buffer was stolen, it must be restored for thumbnail's sake */ | 977 | return -1; |
870 | if (!restore_state()) | ||
871 | return -1; /* cannot get any space */ | ||
872 | #else | ||
873 | if (audio_status()) /* busy, buffer in use */ | ||
874 | return -1; | ||
875 | #endif | ||
876 | 978 | ||
877 | if (p_thumbnail == NULL || size_for_thumbnail <= 0) | 979 | if (!alloc_thumbnail_buf()) |
878 | alloc_thumbnail_buf(); | 980 | return -1; |
879 | 981 | ||
880 | #if CONFIG_CODEC != SWCODEC | 982 | #if CONFIG_CODEC != SWCODEC |
881 | if(mp3info(&info, filename)) /* use this to find real start */ | 983 | if(mp3info(&info, filename)) /* use this to find real start */ |
@@ -905,8 +1007,10 @@ static int _talk_file(const char* filename, | |||
905 | lseek(fd, info.first_frame_offset, SEEK_SET); /* behind ID data */ | 1007 | lseek(fd, info.first_frame_offset, SEEK_SET); /* behind ID data */ |
906 | #endif | 1008 | #endif |
907 | 1009 | ||
908 | size = read(fd, p_thumbnail +thumb_used, | 1010 | talk_handle_locked++; |
909 | size_for_thumbnail -thumb_used); | 1011 | buf = core_get_data(thumb_handle); |
1012 | size = read(fd, buf+thumb_used, size_for_thumbnail - thumb_used); | ||
1013 | talk_handle_locked--; | ||
910 | close(fd); | 1014 | close(fd); |
911 | 1015 | ||
912 | /* ToDo: find audio, skip ID headers and trailers */ | 1016 | /* ToDo: find audio, skip ID headers and trailers */ |
@@ -914,7 +1018,8 @@ static int _talk_file(const char* filename, | |||
914 | if (size > 0) /* Don't play missing clips */ | 1018 | if (size > 0) /* Don't play missing clips */ |
915 | { | 1019 | { |
916 | #if CONFIG_CODEC != SWCODEC && !defined(SIMULATOR) | 1020 | #if CONFIG_CODEC != SWCODEC && !defined(SIMULATOR) |
917 | bitswap(p_thumbnail, size); | 1021 | /* bitswap doesnt yield() */ |
1022 | bitswap(core_get_data(thumb_handle), size); | ||
918 | #endif | 1023 | #endif |
919 | if(prefix_ids) | 1024 | if(prefix_ids) |
920 | /* prefix thumbnail by speaking these ids, but only now | 1025 | /* prefix thumbnail by speaking these ids, but only now |
@@ -922,9 +1027,9 @@ static int _talk_file(const char* filename, | |||
922 | spoken. */ | 1027 | spoken. */ |
923 | talk_idarray(prefix_ids, true); | 1028 | talk_idarray(prefix_ids, true); |
924 | talk_queue_lock(); | 1029 | talk_queue_lock(); |
925 | thumbnail_buf_used = thumb_used +size; | 1030 | thumbnail_buf_used = thumb_used + size; |
926 | talk_queue_unlock(); | 1031 | talk_queue_unlock(); |
927 | queue_clip(p_thumbnail +thumb_used, size, true); | 1032 | queue_clip(voicefile_size + thumb_used, size, true); |
928 | } | 1033 | } |
929 | 1034 | ||
930 | return size; | 1035 | return size; |
@@ -1012,10 +1117,8 @@ int talk_number(long n, bool enqueue) | |||
1012 | 1117 | ||
1013 | if (talk_temp_disable_count > 0) | 1118 | if (talk_temp_disable_count > 0) |
1014 | return -1; /* talking has been disabled */ | 1119 | return -1; /* talking has been disabled */ |
1015 | #if CONFIG_CODEC != SWCODEC | 1120 | if (!check_audio_status()) |
1016 | if (audio_status()) /* busy, buffer in use */ | 1121 | return -1; |
1017 | return -1; | ||
1018 | #endif | ||
1019 | 1122 | ||
1020 | if (!enqueue) | 1123 | if (!enqueue) |
1021 | talk_shutup(); /* cut off all the pending stuff */ | 1124 | talk_shutup(); /* cut off all the pending stuff */ |
@@ -1160,10 +1263,8 @@ int talk_value_decimal(long n, int unit, int decimals, bool enqueue) | |||
1160 | 1263 | ||
1161 | if (talk_temp_disable_count > 0) | 1264 | if (talk_temp_disable_count > 0) |
1162 | return -1; /* talking has been disabled */ | 1265 | return -1; /* talking has been disabled */ |
1163 | #if CONFIG_CODEC != SWCODEC | 1266 | if (!check_audio_status()) |
1164 | if (audio_status()) /* busy, buffer in use */ | 1267 | return -1; |
1165 | return -1; | ||
1166 | #endif | ||
1167 | 1268 | ||
1168 | /* special case for time duration */ | 1269 | /* special case for time duration */ |
1169 | if (unit == UNIT_TIME) | 1270 | if (unit == UNIT_TIME) |
@@ -1217,10 +1318,8 @@ int talk_spell(const char* spell, bool enqueue) | |||
1217 | 1318 | ||
1218 | if (talk_temp_disable_count > 0) | 1319 | if (talk_temp_disable_count > 0) |
1219 | return -1; /* talking has been disabled */ | 1320 | return -1; /* talking has been disabled */ |
1220 | #if CONFIG_CODEC != SWCODEC | 1321 | if (!check_audio_status()) |
1221 | if (audio_status()) /* busy, buffer in use */ | 1322 | return -1; |
1222 | return -1; | ||
1223 | #endif | ||
1224 | 1323 | ||
1225 | if (!enqueue) | 1324 | if (!enqueue) |
1226 | talk_shutup(); /* cut off all the pending stuff */ | 1325 | talk_shutup(); /* cut off all the pending stuff */ |
diff --git a/apps/talk.h b/apps/talk.h index 55e7208f1d..28be71884f 100644 --- a/apps/talk.h +++ b/apps/talk.h | |||
@@ -71,6 +71,26 @@ enum { | |||
71 | /* convenience macro to have both virtual pointer and ID as arguments */ | 71 | /* convenience macro to have both virtual pointer and ID as arguments */ |
72 | #define STR(id) ID2P(id), id | 72 | #define STR(id) ID2P(id), id |
73 | 73 | ||
74 | /* Policy values for how hard to try to keep the talk/voice buffers. | ||
75 | * Affects how genereous talk.c is when it's asked for memory in | ||
76 | * shrink_callbacks(). | ||
77 | * | ||
78 | * I.e. setting the policy to TALK_BUFFER_LOOSE, it will happily give its | ||
79 | * entire bufer away if asked for, e.g. due to a another module | ||
80 | * calling core_alloc_maximum(), TALK_BUFFER_HOLD on the other hand will | ||
81 | * make it keep the buffers so that a call to core_alloc_maximum() does not | ||
82 | * stop the speech-interface. | ||
83 | */ | ||
84 | enum talk_buffer_policies { | ||
85 | TALK_BUFFER_DEFAULT, | ||
86 | TALK_BUFFER_LOOSE, | ||
87 | TALK_BUFFER_HOLD, | ||
88 | }; | ||
89 | |||
90 | /* This sets the actual policy. Call this before core_alloc_maximum() to | ||
91 | * get the desired outcome */ | ||
92 | void talk_buffer_set_policy(int policy); | ||
93 | |||
74 | /* publish these strings, so they're stored only once (better than #define) */ | 94 | /* publish these strings, so they're stored only once (better than #define) */ |
75 | extern const char* const dir_thumbnail_name; /* "_dirname.talk" */ | 95 | extern const char* const dir_thumbnail_name; /* "_dirname.talk" */ |
76 | extern const char* const file_thumbnail_ext; /* ".talk" for file voicing */ | 96 | extern const char* const file_thumbnail_ext; /* ".talk" for file voicing */ |
@@ -81,7 +101,6 @@ bool talk_voice_required(void); /* returns true if voice codec required */ | |||
81 | #endif | 101 | #endif |
82 | int talk_get_bufsize(void); /* get the loaded voice file size */ | 102 | int talk_get_bufsize(void); /* get the loaded voice file size */ |
83 | size_t talkbuf_init(char* bufstart); | 103 | size_t talkbuf_init(char* bufstart); |
84 | void talk_buffer_steal(void); /* claim the mp3 buffer e.g. for play/record */ | ||
85 | bool is_voice_queued(void); /* Are there more voice clips to be spoken? */ | 104 | bool is_voice_queued(void); /* Are there more voice clips to be spoken? */ |
86 | int talk_id(int32_t id, bool enqueue); /* play a voice ID from voicefont */ | 105 | int talk_id(int32_t id, bool enqueue); /* play a voice ID from voicefont */ |
87 | /* play a thumbnail from file */ | 106 | /* play a thumbnail from file */ |