diff options
author | Thomas Martitz <kugel@rockbox.org> | 2013-05-30 11:24:16 +0200 |
---|---|---|
committer | Thomas Martitz <kugel@rockbox.org> | 2013-12-23 12:17:38 +0100 |
commit | 22e802e80048defd401462e062afcb10093ac793 (patch) | |
tree | 09d24f7eb2a3b18e6563e838398b2715394f7c4c /apps/playback.c | |
parent | 64b9e1fa7b645daa36ca0018dc168d4f871fd538 (diff) | |
download | rockbox-22e802e80048defd401462e062afcb10093ac793.tar.gz rockbox-22e802e80048defd401462e062afcb10093ac793.zip |
playback,talk: Share audiobuffer via core_alloc_maximum().
This fixes the radioart crash that was the result of buffering.c working
on a freed buffer at the same time as buflib (radioart uses buffering.c for the
images). With this change the buffer is owned by buflib exclusively so this
cannot happen.
As a result, audio_get_buffer() doesn't exist anymore. Callers should call
core_alloc_maximum() directly. This buffer needs to be protected as usual
against movement if necessary (previously it was not protected at all which
cased the radioart crash), To get most of it they can adjust the willingness of
the talk engine to give its buffer away (at the expense of disabling voice
interface) with the new talk_buffer_set_policy() function.
Change-Id: I52123012208d04967876a304451d634e2bef3a33
Diffstat (limited to 'apps/playback.c')
-rw-r--r-- | apps/playback.c | 176 |
1 files changed, 47 insertions, 129 deletions
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 |