diff options
Diffstat (limited to 'apps')
-rw-r--r-- | apps/playback.c | 214 |
1 files changed, 124 insertions, 90 deletions
diff --git a/apps/playback.c b/apps/playback.c index 9c81210dcc..c4f590c34f 100644 --- a/apps/playback.c +++ b/apps/playback.c | |||
@@ -197,68 +197,87 @@ static bool audio_is_initialized = false; | |||
197 | /* TBD: Split out "audio" and "playback" (ie. calling) threads */ | 197 | /* TBD: Split out "audio" and "playback" (ie. calling) threads */ |
198 | 198 | ||
199 | /* Main state control */ | 199 | /* Main state control */ |
200 | static volatile bool audio_codec_loaded; /* Is codec loaded? (C/A-) */ | 200 | static volatile bool audio_codec_loaded; /* Is codec loaded? (C/A-) */ |
201 | static volatile bool playing; /* Is audio playing? (A) */ | 201 | static volatile bool playing; /* Is audio playing? (A) */ |
202 | static volatile bool paused; /* Is audio paused? (A/C-) */ | 202 | static volatile bool paused; /* Is audio paused? (A/C-) */ |
203 | static volatile bool filling IDATA_ATTR; /* Is file buffer currently being refilled? (A/C-) */ | 203 | static volatile bool filling IDATA_ATTR; /* Is file buffer refilling? (A/C-) */ |
204 | 204 | ||
205 | /* Ring buffer where tracks and codecs are loaded */ | 205 | /* Ring buffer where compressed audio and codecs are loaded */ |
206 | static unsigned char *filebuf; /* Pointer to start of ring buffer (A/C-) */ | 206 | static unsigned char *filebuf; /* Start of buffer (A/C-) */ |
207 | size_t filebuflen; /* Total size of the ring buffer FIXME: make static (A/C-)*/ | 207 | /* FIXME: make filebuflen static */ |
208 | static volatile size_t buf_ridx IDATA_ATTR; /* Ring buffer read position (A/C) FIXME? should be (C/A-) */ | 208 | size_t filebuflen; /* Size of buffer (A/C-) */ |
209 | static volatile size_t buf_widx IDATA_ATTR; /* Ring buffer read position (A/C-) */ | 209 | /* FIXME: make buf_ridx (C/A-) */ |
210 | 210 | static volatile size_t buf_ridx IDATA_ATTR; /* Buffer read position (A/C)*/ | |
211 | #define BUFFER_STATE_TRASHED -1 /* Buffer is in a trashed state and must be reset */ | 211 | static volatile size_t buf_widx IDATA_ATTR; /* Buffer write position (A/C-) */ |
212 | #define BUFFER_STATE_NORMAL 0 /* Buffer is arranged for voice and audio */ | 212 | |
213 | #define BUFFER_STATE_VOICED_ONLY 1 /* Buffer is arranged for voice-only use */ | 213 | /* Possible arrangements of the buffer */ |
214 | #define BUFFER_STATE_TRASHED -1 /* trashed; must be reset */ | ||
215 | #define BUFFER_STATE_NORMAL 0 /* voice+audio OR audio-only */ | ||
216 | #define BUFFER_STATE_VOICED_ONLY 1 /* voice-only */ | ||
214 | static int buffer_state = BUFFER_STATE_TRASHED; /* Buffer state */ | 217 | static int buffer_state = BUFFER_STATE_TRASHED; /* Buffer state */ |
215 | 218 | ||
219 | /* Compressed ring buffer helper macros */ | ||
220 | /* Buffer pointer (p) plus value (v), wrapped if necessary */ | ||
216 | #define RINGBUF_ADD(p,v) ((p+v)<filebuflen ? p+v : p+v-filebuflen) | 221 | #define RINGBUF_ADD(p,v) ((p+v)<filebuflen ? p+v : p+v-filebuflen) |
222 | /* Buffer pointer (p) minus value (v), wrapped if necessary */ | ||
217 | #define RINGBUF_SUB(p,v) ((p>=v) ? p-v : p+filebuflen-v) | 223 | #define RINGBUF_SUB(p,v) ((p>=v) ? p-v : p+filebuflen-v) |
218 | #define RINGBUF_ADD_CROSS(p1,v,p2) ((p1<p2)?(int)(p1+v)-(int)p2:(int)(p1+v-p2)-(int)filebuflen) | 224 | /* How far value (v) plus buffer pointer (p1) will cross buffer pointer (p2) */ |
219 | #define FILEBUFUSED RINGBUF_SUB(buf_widx, buf_ridx) /* Bytes available in the buffer */ | 225 | #define RINGBUF_ADD_CROSS(p1,v,p2) \ |
220 | 226 | ((p1<p2)?(int)(p1+v)-(int)p2:(int)(p1+v-p2)-(int)filebuflen) | |
221 | /* Track info buffer */ | 227 | /* Bytes available in the buffer */ |
222 | static struct track_info tracks[MAX_TRACK]; /* Track info structure about songs in the file buffer (A/C-) */ | 228 | #define FILEBUFUSED RINGBUF_SUB(buf_widx, buf_ridx) |
223 | static volatile int track_ridx; /* Track being decoded (A/C-) */ | 229 | |
224 | static int track_widx; /* Track being buffered (A) */ | 230 | /* Track info structure about songs in the file buffer (A/C-) */ |
225 | static bool track_changed; /* Set to indicate track has changed (A) */ | 231 | static struct track_info tracks[MAX_TRACK]; |
226 | static struct track_info *prev_ti; /* Pointer to previous track played info (A/C-) */ | 232 | static volatile int track_ridx; /* Track being decoded (A/C-) */ |
227 | 233 | static int track_widx; /* Track being buffered (A) */ | |
228 | #define CUR_TI (&tracks[track_ridx]) /* Pointer to current track playing info (A/C-) */ | 234 | |
229 | 235 | static struct track_info *prev_ti; /* Previous track info pointer (A/C-) */ | |
230 | /* Audio buffering controls */ | 236 | #define CUR_TI (&tracks[track_ridx]) /* Playing track info pointer (A/C-) */ |
231 | static int last_peek_offset; /* Step count to the next unbuffered track (A) */ | 237 | |
232 | static int current_fd; /* Partially loaded track file handle to continue buffering (A) */ | 238 | /* Set by the audio thread when the current track information has updated |
239 | * and the WPS may need to update its cached information */ | ||
240 | static bool track_changed; | ||
241 | |||
242 | /* Information used only for filling the buffer */ | ||
243 | /* Playlist steps from playing track to next track to be buffered (A) */ | ||
244 | static int last_peek_offset; | ||
245 | /* Partially loaded track file handle to continue buffering (A) */ | ||
246 | static int current_fd; | ||
233 | 247 | ||
234 | /* Scrobbler support */ | 248 | /* Scrobbler support */ |
235 | static unsigned long prev_track_elapsed; /* Previous track elapsed time (C/A-) */ | 249 | static unsigned long prev_track_elapsed; /* Previous track elapsed time (C/A-)*/ |
236 | 250 | ||
237 | /* Track change controls */ | 251 | /* Track change controls */ |
238 | static bool automatic_skip = false; /* Was the skip being executed manual or automatic? (C/A-) */ | 252 | static bool automatic_skip = false; /* Who initiated in-progress skip? (C/A-) */ |
239 | static bool playlist_end = false; /* Have we reached end of the current playlist? (A) */ | 253 | static bool playlist_end = false; /* Has the current playlist ended? (A) */ |
240 | static bool dir_skip = false; /* Is a directory skip pending? (A) */ | 254 | static bool dir_skip = false; /* Is a directory skip pending? (A) */ |
241 | static bool new_playlist = false; /* Are we starting a new playlist? (A) */ | 255 | static bool new_playlist = false; /* Are we starting a new playlist? (A) */ |
242 | static int wps_offset = 0; /* Pending track change offset, to keep WPS responsive (A) */ | 256 | /* Pending track change offset, to keep WPS responsive (A) */ |
243 | 257 | static int wps_offset = 0; | |
244 | /* Callbacks.. */ | 258 | |
245 | void (*track_changed_callback)(struct mp3entry *id3); /* ...when current track has really changed */ | 259 | /* Callbacks which applications or plugins may set */ |
246 | void (*track_buffer_callback)(struct mp3entry *id3, bool last_track); /* ...when track has been buffered */ | 260 | /* When the playing track has changed from the user's perspective */ |
247 | void (*track_unbuffer_callback)(struct mp3entry *id3, bool last_track); /* ...when track is being unbuffered */ | 261 | void (*track_changed_callback)(struct mp3entry *id3); |
262 | /* When a track has been buffered */ | ||
263 | void (*track_buffer_callback)(struct mp3entry *id3, bool last_track); | ||
264 | /* When a track's buffer has been overwritten or cleared */ | ||
265 | void (*track_unbuffer_callback)(struct mp3entry *id3, bool last_track); | ||
248 | 266 | ||
249 | /* Configuration */ | 267 | /* Configuration */ |
250 | static size_t conf_watermark; /* Low water mark (A/C) FIXME */ | 268 | static size_t conf_watermark; /* Level to trigger filebuf fill (A/C) FIXME */ |
251 | static size_t conf_filechunk; /* Largest chunk the codec accepts (A/C) FIXME */ | 269 | static size_t conf_filechunk; /* Largest chunk the codec accepts (A/C) FIXME */ |
252 | static size_t conf_preseek; /* Codec pre-seek margin (A/C) FIXME */ | 270 | static size_t conf_preseek; /* Codec pre-seek margin (A/C) FIXME */ |
253 | static size_t buffer_margin; /* Buffer margin aka anti-skip buffer (A/C-) */ | 271 | static size_t buffer_margin; /* Buffer margin aka anti-skip buffer (A/C-) */ |
254 | static bool v1first = false; /* ID3 data control, true if V1 then V2 (A) */ | 272 | static bool v1first = false; /* ID3 data control, true if V1 then V2 (A) */ |
255 | #if MEM > 8 | 273 | #if MEM > 8 |
256 | static size_t high_watermark; /* High watermark for rebuffer (A/V/other) */ | 274 | static size_t high_watermark; /* High watermark for rebuffer (A/V/other) */ |
257 | #endif | 275 | #endif |
258 | 276 | ||
259 | /* Multiple threads */ | 277 | /* Multiple threads */ |
260 | static const char *get_codec_filename(int enc_spec); /* Returns codec filename (A-/C-/V-) */ | 278 | static const char *get_codec_filename(int enc_spec); /* (A-/C-/V-) */ |
261 | static void set_filebuf_watermark(int seconds); /* Set low watermark (A/C) FIXME */ | 279 | /* Set the watermark to trigger buffer fill (A/C) FIXME */ |
280 | static void set_filebuf_watermark(int seconds); | ||
262 | 281 | ||
263 | /* Audio thread */ | 282 | /* Audio thread */ |
264 | static struct event_queue audio_queue; | 283 | static struct event_queue audio_queue; |
@@ -277,9 +296,9 @@ static struct event_queue codec_queue; | |||
277 | static long codec_stack[(DEFAULT_STACK_SIZE + 0x2000)/sizeof(long)] | 296 | static long codec_stack[(DEFAULT_STACK_SIZE + 0x2000)/sizeof(long)] |
278 | IBSS_ATTR; | 297 | IBSS_ATTR; |
279 | static const char codec_thread_name[] = "codec"; | 298 | static const char codec_thread_name[] = "codec"; |
280 | struct thread_entry *codec_thread_p; /* For modifying thread priority later. */ | 299 | struct thread_entry *codec_thread_p; /* For modifying thread priority later. */ |
281 | 300 | ||
282 | volatile int current_codec IDATA_ATTR; /* Current codec (normal/voice) */ | 301 | volatile int current_codec IDATA_ATTR; /* Current codec (normal/voice) */ |
283 | 302 | ||
284 | /* Voice thread */ | 303 | /* Voice thread */ |
285 | #ifdef PLAYBACK_VOICE | 304 | #ifdef PLAYBACK_VOICE |
@@ -296,24 +315,29 @@ static const char voice_thread_name[] = "voice codec"; | |||
296 | extern unsigned char codecbuf[]; /* DRAM codec swap buffer */ | 315 | extern unsigned char codecbuf[]; /* DRAM codec swap buffer */ |
297 | 316 | ||
298 | #ifdef SIMULATOR | 317 | #ifdef SIMULATOR |
299 | static unsigned char sim_iram[CODEC_IRAM_SIZE]; /* IRAM codec swap buffer for sim*/ | 318 | /* IRAM codec swap buffer for sim*/ |
319 | static unsigned char sim_iram[CODEC_IRAM_SIZE]; | ||
300 | #undef CODEC_IRAM_ORIGIN | 320 | #undef CODEC_IRAM_ORIGIN |
301 | #define CODEC_IRAM_ORIGIN sim_iram | 321 | #define CODEC_IRAM_ORIGIN sim_iram |
302 | #endif | 322 | #endif |
303 | 323 | ||
304 | static unsigned char *iram_buf[2] = { NULL, NULL }; /* Ptr to IRAM buffers for normal/voice codecs */ | 324 | /* Pointer to IRAM buffers for normal/voice codecs */ |
305 | static unsigned char *dram_buf[2] = { NULL, NULL }; /* Ptr to DRAM buffers for normal/voice codecs */ | 325 | static unsigned char *iram_buf[2] = { NULL, NULL }; |
306 | static struct mutex mutex_codecthread; /* Mutex to control which codec (normal/voice) is running */ | 326 | /* Pointer to DRAM buffers for normal/voice codecs */ |
327 | static unsigned char *dram_buf[2] = { NULL, NULL }; | ||
328 | /* Mutex to control which codec (normal/voice) is running */ | ||
329 | static struct mutex mutex_codecthread; | ||
307 | 330 | ||
308 | /* Voice state */ | 331 | /* Voice state */ |
309 | static volatile bool voice_thread_start; /* Set to trigger voice playback (A/V) */ | 332 | static volatile bool voice_thread_start; /* Triggers voice playback (A/V) */ |
310 | static volatile bool voice_is_playing; /* Is voice currently playing? (V) */ | 333 | static volatile bool voice_is_playing; /* Is voice currently playing? (V) */ |
311 | static volatile bool voice_codec_loaded; /* Is voice codec loaded (V/A-) */ | 334 | static volatile bool voice_codec_loaded; /* Is voice codec loaded (V/A-) */ |
312 | static char *voicebuf; | 335 | static char *voicebuf; |
313 | static size_t voice_remaining; | 336 | static size_t voice_remaining; |
314 | 337 | ||
315 | #ifdef IRAM_STEAL | 338 | #ifdef IRAM_STEAL |
316 | static bool voice_iram_stolen = false; /* Voice IRAM has been stolen for other use */ | 339 | /* Voice IRAM has been stolen for other use */ |
340 | static bool voice_iram_stolen = false; | ||
317 | #endif | 341 | #endif |
318 | 342 | ||
319 | static void (*voice_getmore)(unsigned char** start, int* size); | 343 | static void (*voice_getmore)(unsigned char** start, int* size); |
@@ -655,7 +679,7 @@ void audio_next(void) | |||
655 | 679 | ||
656 | LOGFQUEUE("audio > audio Q_AUDIO_SKIP 1"); | 680 | LOGFQUEUE("audio > audio Q_AUDIO_SKIP 1"); |
657 | queue_post(&audio_queue, Q_AUDIO_SKIP, 1); | 681 | queue_post(&audio_queue, Q_AUDIO_SKIP, 1); |
658 | /* Keep wps fast while our message travels inside deep playback queues. */ | 682 | /* Update wps while our message travels inside deep playback queues. */ |
659 | wps_offset++; | 683 | wps_offset++; |
660 | track_changed = true; | 684 | track_changed = true; |
661 | } | 685 | } |
@@ -676,7 +700,7 @@ void audio_prev(void) | |||
676 | 700 | ||
677 | LOGFQUEUE("audio > audio Q_AUDIO_SKIP -1"); | 701 | LOGFQUEUE("audio > audio Q_AUDIO_SKIP -1"); |
678 | queue_post(&audio_queue, Q_AUDIO_SKIP, -1); | 702 | queue_post(&audio_queue, Q_AUDIO_SKIP, -1); |
679 | /* Keep wps fast while our message travels inside deep playback queues. */ | 703 | /* Update wps while our message travels inside deep playback queues. */ |
680 | wps_offset--; | 704 | wps_offset--; |
681 | track_changed = true; | 705 | track_changed = true; |
682 | } | 706 | } |
@@ -1047,7 +1071,8 @@ static bool voice_pcmbuf_insert_split_callback( | |||
1047 | if (playing) | 1071 | if (playing) |
1048 | { | 1072 | { |
1049 | pcmbuf_mix_voice(output_size); | 1073 | pcmbuf_mix_voice(output_size); |
1050 | if ((pcmbuf_usage() < 10 || pcmbuf_mix_free() < 30) && audio_codec_loaded) | 1074 | if ((pcmbuf_usage() < 10 || pcmbuf_mix_free() < 30) && |
1075 | audio_codec_loaded) | ||
1051 | swap_codec(); | 1076 | swap_codec(); |
1052 | } | 1077 | } |
1053 | else | 1078 | else |
@@ -1807,8 +1832,24 @@ static void codec_discard_codec_callback(void) | |||
1807 | #endif | 1832 | #endif |
1808 | } | 1833 | } |
1809 | 1834 | ||
1835 | static inline void codec_gapless_track_change(void) { | ||
1836 | /* callback keeps the progress bar moving while the pcmbuf empties */ | ||
1837 | pcmbuf_set_position_callback(codec_pcmbuf_position_callback); | ||
1838 | /* set the pcmbuf callback for when the track really changes */ | ||
1839 | pcmbuf_set_event_handler(codec_pcmbuf_track_changed_callback); | ||
1840 | } | ||
1841 | |||
1842 | static inline void codec_crossfade_track_change(void) { | ||
1843 | /* Initiate automatic crossfade mode */ | ||
1844 | pcmbuf_crossfade_init(false); | ||
1845 | /* Notify the wps that the track change starts now */ | ||
1846 | codec_track_changed(); | ||
1847 | } | ||
1848 | |||
1810 | static void codec_track_skip_done(bool was_manual) | 1849 | static void codec_track_skip_done(bool was_manual) |
1811 | { | 1850 | { |
1851 | int crossfade_mode = global_settings.crossfade; | ||
1852 | |||
1812 | /* Manual track change (always crossfade or flush audio). */ | 1853 | /* Manual track change (always crossfade or flush audio). */ |
1813 | if (was_manual) | 1854 | if (was_manual) |
1814 | { | 1855 | { |
@@ -1818,34 +1859,24 @@ static void codec_track_skip_done(bool was_manual) | |||
1818 | } | 1859 | } |
1819 | /* Automatic track change w/crossfade, if not in "Track Skip Only" mode. */ | 1860 | /* Automatic track change w/crossfade, if not in "Track Skip Only" mode. */ |
1820 | else if (pcmbuf_is_crossfade_enabled() && !pcmbuf_is_crossfade_active() | 1861 | else if (pcmbuf_is_crossfade_enabled() && !pcmbuf_is_crossfade_active() |
1821 | && global_settings.crossfade != CROSSFADE_ENABLE_TRACKSKIP ) | 1862 | && crossfade_mode != CROSSFADE_ENABLE_TRACKSKIP) |
1822 | { | 1863 | { |
1823 | if ( global_settings.crossfade | 1864 | if (crossfade_mode == CROSSFADE_ENABLE_SHUFFLE_AND_TRACKSKIP) |
1824 | == CROSSFADE_ENABLE_SHUFFLE_AND_TRACKSKIP ) | ||
1825 | { | 1865 | { |
1826 | if (global_settings.playlist_shuffle) /* shuffle mode is on, so crossfade: */ | 1866 | if (global_settings.playlist_shuffle) |
1827 | { | 1867 | /* shuffle mode is on, so crossfade: */ |
1828 | pcmbuf_crossfade_init(false); | 1868 | codec_crossfade_track_change(); |
1829 | codec_track_changed(); | 1869 | else |
1830 | } | 1870 | /* shuffle mode is off, so do a gapless track change */ |
1831 | else /* shuffle mode is off, so do a gapless track change */ | 1871 | codec_gapless_track_change(); |
1832 | { | ||
1833 | pcmbuf_set_position_callback(codec_pcmbuf_position_callback); /* Gapless playback */ | ||
1834 | pcmbuf_set_event_handler(codec_pcmbuf_track_changed_callback); /* copied from below */ | ||
1835 | } | ||
1836 | } | 1872 | } |
1837 | else /* normal crossfade: */ | 1873 | else |
1838 | { | 1874 | /* normal crossfade: */ |
1839 | pcmbuf_crossfade_init(false); | 1875 | codec_crossfade_track_change(); |
1840 | codec_track_changed(); | ||
1841 | } | ||
1842 | } | 1876 | } |
1843 | /* Gapless playback. */ | ||
1844 | else | 1877 | else |
1845 | { | 1878 | /* normal gapless playback. */ |
1846 | pcmbuf_set_position_callback(codec_pcmbuf_position_callback); | 1879 | codec_gapless_track_change(); |
1847 | pcmbuf_set_event_handler(codec_pcmbuf_track_changed_callback); | ||
1848 | } | ||
1849 | } | 1880 | } |
1850 | 1881 | ||
1851 | static bool codec_load_next_track(void) | 1882 | static bool codec_load_next_track(void) |
@@ -2076,7 +2107,8 @@ static void codec_thread(void) | |||
2076 | * triggering the WPS exit */ | 2107 | * triggering the WPS exit */ |
2077 | while(pcm_is_playing()) | 2108 | while(pcm_is_playing()) |
2078 | { | 2109 | { |
2079 | CUR_TI->id3.elapsed = CUR_TI->id3.length - pcmbuf_get_latency(); | 2110 | CUR_TI->id3.elapsed = |
2111 | CUR_TI->id3.length - pcmbuf_get_latency(); | ||
2080 | sleep(1); | 2112 | sleep(1); |
2081 | } | 2113 | } |
2082 | LOGFQUEUE("codec > audio Q_AUDIO_STOP"); | 2114 | LOGFQUEUE("codec > audio Q_AUDIO_STOP"); |
@@ -2092,7 +2124,8 @@ static void codec_thread(void) | |||
2092 | } | 2124 | } |
2093 | else | 2125 | else |
2094 | { | 2126 | { |
2095 | const char *codec_fn = get_codec_filename(CUR_TI->id3.codectype); | 2127 | const char *codec_fn = |
2128 | get_codec_filename(CUR_TI->id3.codectype); | ||
2096 | LOGFQUEUE("codec > codec Q_CODEC_LOAD_DISK"); | 2129 | LOGFQUEUE("codec > codec Q_CODEC_LOAD_DISK"); |
2097 | queue_post(&codec_queue, Q_CODEC_LOAD_DISK, | 2130 | queue_post(&codec_queue, Q_CODEC_LOAD_DISK, |
2098 | (intptr_t)codec_fn); | 2131 | (intptr_t)codec_fn); |
@@ -2516,7 +2549,8 @@ static bool audio_loadcodec(bool start_play) | |||
2516 | int prev_track; | 2549 | int prev_track; |
2517 | char codec_path[MAX_PATH]; /* Full path to codec */ | 2550 | char codec_path[MAX_PATH]; /* Full path to codec */ |
2518 | 2551 | ||
2519 | const char * codec_fn = get_codec_filename(tracks[track_widx].id3.codectype); | 2552 | const char * codec_fn = |
2553 | get_codec_filename(tracks[track_widx].id3.codectype); | ||
2520 | if (codec_fn == NULL) | 2554 | if (codec_fn == NULL) |
2521 | return false; | 2555 | return false; |
2522 | 2556 | ||
@@ -3520,9 +3554,9 @@ static void audio_playback_init(void) | |||
3520 | id3_voice.length = 1000000L; | 3554 | id3_voice.length = 1000000L; |
3521 | #endif | 3555 | #endif |
3522 | 3556 | ||
3523 | codec_thread_p = create_thread(codec_thread, codec_stack, | 3557 | codec_thread_p = create_thread( |
3524 | sizeof(codec_stack), | 3558 | codec_thread, codec_stack, sizeof(codec_stack), |
3525 | codec_thread_name IF_PRIO(, PRIORITY_PLAYBACK)); | 3559 | codec_thread_name IF_PRIO(, PRIORITY_PLAYBACK)); |
3526 | 3560 | ||
3527 | while (1) | 3561 | while (1) |
3528 | { | 3562 | { |