diff options
-rw-r--r-- | apps/plugin.c | 5 | ||||
-rw-r--r-- | apps/plugin.h | 9 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/audio_thread.c | 93 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/mpeg_misc.h | 9 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/mpeg_settings.c | 174 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/mpeg_settings.h | 18 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/mpegplayer.h | 17 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/pcm_output.c | 35 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/pcm_output.h | 2 | ||||
-rw-r--r-- | apps/plugins/mpegplayer/video_thread.c | 12 |
10 files changed, 298 insertions, 76 deletions
diff --git a/apps/plugin.c b/apps/plugin.c index 1f773cfd2d..6862ddb08e 100644 --- a/apps/plugin.c +++ b/apps/plugin.c | |||
@@ -586,6 +586,11 @@ static const struct plugin_api rockbox_api = { | |||
586 | #endif | 586 | #endif |
587 | sound_unit, | 587 | sound_unit, |
588 | sound_val2phys, | 588 | sound_val2phys, |
589 | dsp_set_crossfeed, | ||
590 | dsp_set_eq, | ||
591 | dsp_dither_enable, | ||
592 | dsp_configure, | ||
593 | dsp_process, | ||
589 | #endif /* CONFIG_CODEC == SWCODEC */ | 594 | #endif /* CONFIG_CODEC == SWCODEC */ |
590 | }; | 595 | }; |
591 | 596 | ||
diff --git a/apps/plugin.h b/apps/plugin.h index f91d803854..164a2c9847 100644 --- a/apps/plugin.h +++ b/apps/plugin.h | |||
@@ -119,7 +119,7 @@ | |||
119 | #define PLUGIN_MAGIC 0x526F634B /* RocK */ | 119 | #define PLUGIN_MAGIC 0x526F634B /* RocK */ |
120 | 120 | ||
121 | /* increase this every time the api struct changes */ | 121 | /* increase this every time the api struct changes */ |
122 | #define PLUGIN_API_VERSION 98 | 122 | #define PLUGIN_API_VERSION 99 |
123 | 123 | ||
124 | /* update this to latest version if a change to the api struct breaks | 124 | /* update this to latest version if a change to the api struct breaks |
125 | backwards compatibility (and please take the opportunity to sort in any | 125 | backwards compatibility (and please take the opportunity to sort in any |
@@ -723,6 +723,13 @@ struct plugin_api { | |||
723 | #endif | 723 | #endif |
724 | const char * (*sound_unit)(int setting); | 724 | const char * (*sound_unit)(int setting); |
725 | int (*sound_val2phys)(int setting, int value); | 725 | int (*sound_val2phys)(int setting, int value); |
726 | void (*dsp_set_crossfeed)(bool enable); | ||
727 | void (*dsp_set_eq)(bool enable); | ||
728 | void (*dsp_dither_enable)(bool enable); | ||
729 | intptr_t (*dsp_configure)(struct dsp_config *dsp, int setting, | ||
730 | intptr_t value); | ||
731 | int (*dsp_process)(struct dsp_config *dsp, char *dest, | ||
732 | const char *src[], int count); | ||
726 | #endif /* CONFIG_CODEC == SWCODEC */ | 733 | #endif /* CONFIG_CODEC == SWCODEC */ |
727 | }; | 734 | }; |
728 | 735 | ||
diff --git a/apps/plugins/mpegplayer/audio_thread.c b/apps/plugins/mpegplayer/audio_thread.c index 78d28e40c5..2bb766ad88 100644 --- a/apps/plugins/mpegplayer/audio_thread.c +++ b/apps/plugins/mpegplayer/audio_thread.c | |||
@@ -27,10 +27,13 @@ | |||
27 | struct pts_queue_slot; | 27 | struct pts_queue_slot; |
28 | struct audio_thread_data | 28 | struct audio_thread_data |
29 | { | 29 | { |
30 | struct queue_event ev; /* Our event queue to receive commands */ | 30 | struct queue_event ev; /* Our event queue to receive commands */ |
31 | int state; /* Thread state */ | 31 | int state; /* Thread state */ |
32 | int status; /* Media status (STREAM_PLAYING, etc.) */ | 32 | int status; /* Media status (STREAM_PLAYING, etc.) */ |
33 | int mad_errors; /* A count of the errors in each frame */ | 33 | int mad_errors; /* A count of the errors in each frame */ |
34 | unsigned samplerate; /* Current stream sample rate */ | ||
35 | int nchannels; /* Number of audio channels */ | ||
36 | struct dsp_config *dsp; /* The DSP we're using */ | ||
34 | }; | 37 | }; |
35 | 38 | ||
36 | /* The audio stack is stolen from the core codec thread (but not in uisim) */ | 39 | /* The audio stack is stolen from the core codec thread (but not in uisim) */ |
@@ -402,6 +405,8 @@ static void audio_thread_msg(struct audio_thread_data *td) | |||
402 | 405 | ||
403 | td->status = STREAM_STOPPED; | 406 | td->status = STREAM_STOPPED; |
404 | td->state = TSTATE_INIT; | 407 | td->state = TSTATE_INIT; |
408 | td->samplerate = 0; | ||
409 | td->nchannels = 0; | ||
405 | 410 | ||
406 | init_mad(); | 411 | init_mad(); |
407 | td->mad_errors = 0; | 412 | td->mad_errors = 0; |
@@ -460,12 +465,19 @@ static void audio_thread(void) | |||
460 | { | 465 | { |
461 | struct audio_thread_data td; | 466 | struct audio_thread_data td; |
462 | 467 | ||
468 | rb->memset(&td, 0, sizeof (td)); | ||
463 | td.status = STREAM_STOPPED; | 469 | td.status = STREAM_STOPPED; |
464 | td.state = TSTATE_EOS; | 470 | td.state = TSTATE_EOS; |
465 | 471 | ||
466 | /* We need this here to init the EMAC for Coldfire targets */ | 472 | /* We need this here to init the EMAC for Coldfire targets */ |
467 | init_mad(); | 473 | init_mad(); |
468 | 474 | ||
475 | td.dsp = (struct dsp_config *)rb->dsp_configure(NULL, DSP_MYDSP, | ||
476 | CODEC_IDX_AUDIO); | ||
477 | rb->sound_set_pitch(1000); | ||
478 | rb->dsp_configure(td.dsp, DSP_RESET, 0); | ||
479 | rb->dsp_configure(td.dsp, DSP_SET_SAMPLE_DEPTH, MAD_F_FRACBITS); | ||
480 | |||
469 | goto message_wait; | 481 | goto message_wait; |
470 | 482 | ||
471 | /* This is the decoding loop. */ | 483 | /* This is the decoding loop. */ |
@@ -594,64 +606,56 @@ static void audio_thread(void) | |||
594 | mad_synth_frame(&synth, &frame); | 606 | mad_synth_frame(&synth, &frame); |
595 | 607 | ||
596 | /** Output **/ | 608 | /** Output **/ |
609 | if (frame.header.samplerate != td.samplerate) | ||
610 | { | ||
611 | td.samplerate = frame.header.samplerate; | ||
612 | rb->dsp_configure(td.dsp, DSP_SWITCH_FREQUENCY, | ||
613 | td.samplerate); | ||
614 | } | ||
597 | 615 | ||
598 | /* TODO: Output through core dsp. We'll still use our own PCM buffer | 616 | if (MAD_NCHANNELS(&frame.header) != td.nchannels) |
599 | since the core pcm buffer has no timestamping or clock facilities */ | 617 | { |
618 | td.nchannels = MAD_NCHANNELS(&frame.header); | ||
619 | rb->dsp_configure(td.dsp, DSP_SET_STEREO_MODE, | ||
620 | td.nchannels == 1 ? | ||
621 | STEREO_MONO : STEREO_NONINTERLEAVED); | ||
622 | } | ||
623 | |||
624 | td.state = TSTATE_RENDER_WAIT; | ||
600 | 625 | ||
601 | /* Add a frame of audio to the pcm buffer. Maximum is 1152 samples. */ | 626 | /* Add a frame of audio to the pcm buffer. Maximum is 1152 samples. */ |
602 | render_wait: | 627 | render_wait: |
603 | if (synth.pcm.length > 0) | 628 | if (synth.pcm.length > 0) |
604 | { | 629 | { |
605 | struct pcm_frame_header *pcm_insert = pcm_output_get_buffer(); | 630 | struct pcm_frame_header *dst_hdr = pcm_output_get_buffer(); |
606 | int16_t *audio_data = (int16_t *)pcm_insert->data; | 631 | const char *src[2] = |
607 | unsigned length = synth.pcm.length; | 632 | { (char *)synth.pcm.samples[0], (char *)synth.pcm.samples[1] }; |
608 | ssize_t size = sizeof(*pcm_insert) + length*4; | 633 | int out_count = (synth.pcm.length * CLOCK_RATE |
609 | 634 | + (td.samplerate - 1)) / td.samplerate; | |
610 | td.state = TSTATE_RENDER_WAIT; | 635 | ssize_t size = sizeof(*dst_hdr) + out_count*4; |
611 | 636 | ||
612 | /* Wait for required amount of free buffer space */ | 637 | /* Wait for required amount of free buffer space */ |
613 | while (pcm_output_free() < size) | 638 | while (pcm_output_free() < size) |
614 | { | 639 | { |
615 | /* Wait one frame */ | 640 | /* Wait one frame */ |
616 | int timeout = synth.pcm.length*HZ / synth.pcm.samplerate; | 641 | int timeout = out_count*HZ / td.samplerate; |
617 | str_get_msg_w_tmo(&audio_str, &td.ev, MAX(timeout, 1)); | 642 | str_get_msg_w_tmo(&audio_str, &td.ev, MAX(timeout, 1)); |
618 | if (td.ev.id != SYS_TIMEOUT) | 643 | if (td.ev.id != SYS_TIMEOUT) |
619 | goto message_process; | 644 | goto message_process; |
620 | } | 645 | } |
621 | 646 | ||
622 | pcm_insert->time = audio_queue.curr->time; | 647 | out_count = rb->dsp_process(td.dsp, dst_hdr->data, src, |
623 | pcm_insert->size = size; | 648 | synth.pcm.length); |
624 | 649 | ||
625 | /* As long as we're on this timestamp, the time is just incremented | 650 | if (out_count <= 0) |
626 | by the number of samples */ | 651 | break; |
627 | audio_queue.curr->time += length; | ||
628 | |||
629 | if (MAD_NCHANNELS(&frame.header) == 2) | ||
630 | { | ||
631 | int32_t *left = &synth.pcm.samples[0][0]; | ||
632 | int32_t *right = &synth.pcm.samples[1][0]; | ||
633 | 652 | ||
634 | do | 653 | dst_hdr->size = sizeof(*dst_hdr) + out_count*4; |
635 | { | 654 | dst_hdr->time = audio_queue.curr->time; |
636 | /* libmad outputs s3.28 */ | ||
637 | *audio_data++ = clip_sample(*left++ >> 13); | ||
638 | *audio_data++ = clip_sample(*right++ >> 13); | ||
639 | } | ||
640 | while (--length > 0); | ||
641 | } | ||
642 | else /* mono */ | ||
643 | { | ||
644 | int32_t *mono = &synth.pcm.samples[0][0]; | ||
645 | 655 | ||
646 | do | 656 | /* As long as we're on this timestamp, the time is just |
647 | { | 657 | incremented by the number of samples */ |
648 | int32_t s = clip_sample(*mono++ >> 13); | 658 | audio_queue.curr->time += out_count; |
649 | *audio_data++ = s; | ||
650 | *audio_data++ = s; | ||
651 | } | ||
652 | while (--length > 0); | ||
653 | } | ||
654 | /**/ | ||
655 | 659 | ||
656 | /* Make this data available to DMA */ | 660 | /* Make this data available to DMA */ |
657 | pcm_output_add_data(); | 661 | pcm_output_add_data(); |
@@ -712,9 +716,10 @@ bool audio_thread_init(void) | |||
712 | rb->queue_init(audio_str.hdr.q, false); | 716 | rb->queue_init(audio_str.hdr.q, false); |
713 | rb->queue_enable_queue_send(audio_str.hdr.q, &audio_str_queue_send); | 717 | rb->queue_enable_queue_send(audio_str.hdr.q, &audio_str_queue_send); |
714 | 718 | ||
719 | /* One-up on the priority since the core DSP over-yields internally */ | ||
715 | audio_str.thread = rb->create_thread( | 720 | audio_str.thread = rb->create_thread( |
716 | audio_thread, audio_stack, audio_stack_size, 0, | 721 | audio_thread, audio_stack, audio_stack_size, 0, |
717 | "mpgaudio" IF_PRIO(,PRIORITY_PLAYBACK) IF_COP(, CPU)); | 722 | "mpgaudio" IF_PRIO(,PRIORITY_PLAYBACK-1) IF_COP(, CPU)); |
718 | 723 | ||
719 | if (audio_str.thread == NULL) | 724 | if (audio_str.thread == NULL) |
720 | return false; | 725 | return false; |
diff --git a/apps/plugins/mpegplayer/mpeg_misc.h b/apps/plugins/mpegplayer/mpeg_misc.h index 2da9c2e313..6c5a41655b 100644 --- a/apps/plugins/mpegplayer/mpeg_misc.h +++ b/apps/plugins/mpegplayer/mpeg_misc.h | |||
@@ -168,15 +168,6 @@ void stream_scan_normalize(struct stream_scan *sk); | |||
168 | * direction, otherwise opposite the scan direction */ | 168 | * direction, otherwise opposite the scan direction */ |
169 | void stream_scan_offset(struct stream_scan *sk, off_t by); | 169 | void stream_scan_offset(struct stream_scan *sk, off_t by); |
170 | 170 | ||
171 | /** Audio helpers **/ | ||
172 | static inline int32_t clip_sample(int32_t sample) | ||
173 | { | ||
174 | if ((int16_t)sample != sample) | ||
175 | sample = 0x7fff ^ (sample >> 31); | ||
176 | |||
177 | return sample; | ||
178 | } | ||
179 | |||
180 | /** Time helpers **/ | 171 | /** Time helpers **/ |
181 | struct hms | 172 | struct hms |
182 | { | 173 | { |
diff --git a/apps/plugins/mpegplayer/mpeg_settings.c b/apps/plugins/mpegplayer/mpeg_settings.c index fa8fad8b4c..fc99460d74 100644 --- a/apps/plugins/mpegplayer/mpeg_settings.c +++ b/apps/plugins/mpegplayer/mpeg_settings.c | |||
@@ -109,6 +109,11 @@ static struct configdata config[] = | |||
109 | {TYPE_INT, 0, INT_MAX, &settings.displayoptions, "Display options", | 109 | {TYPE_INT, 0, INT_MAX, &settings.displayoptions, "Display options", |
110 | NULL, NULL}, | 110 | NULL, NULL}, |
111 | #endif | 111 | #endif |
112 | {TYPE_INT, 0, 2, &settings.tone_controls, "Tone controls", NULL, NULL}, | ||
113 | {TYPE_INT, 0, 2, &settings.channel_modes, "Channel modes", NULL, NULL}, | ||
114 | {TYPE_INT, 0, 2, &settings.crossfeed, "Crossfeed", NULL, NULL}, | ||
115 | {TYPE_INT, 0, 2, &settings.equalizer, "Equalizer", NULL, NULL}, | ||
116 | {TYPE_INT, 0, 2, &settings.dithering, "Dithering", NULL, NULL}, | ||
112 | }; | 117 | }; |
113 | 118 | ||
114 | static const struct opt_items noyes[2] = { | 119 | static const struct opt_items noyes[2] = { |
@@ -121,6 +126,11 @@ static const struct opt_items enabledisable[2] = { | |||
121 | { "Enable", -1 }, | 126 | { "Enable", -1 }, |
122 | }; | 127 | }; |
123 | 128 | ||
129 | static const struct opt_items globaloff[2] = { | ||
130 | { "Force off", -1 }, | ||
131 | { "Use sound setting", -1 }, | ||
132 | }; | ||
133 | |||
124 | static long mpeg_menu_sysevent_id; | 134 | static long mpeg_menu_sysevent_id; |
125 | 135 | ||
126 | void mpeg_menu_sysevent_clear(void) | 136 | void mpeg_menu_sysevent_clear(void) |
@@ -184,6 +194,70 @@ static bool mpeg_set_option(const char* string, | |||
184 | return usb; | 194 | return usb; |
185 | } | 195 | } |
186 | 196 | ||
197 | /* Sync a particular audio setting to global or mpegplayer forced off */ | ||
198 | static void sync_audio_setting(int setting, bool global) | ||
199 | { | ||
200 | int val0, val1; | ||
201 | |||
202 | switch (setting) | ||
203 | { | ||
204 | case MPEG_AUDIO_TONE_CONTROLS: | ||
205 | if (global || settings.tone_controls) | ||
206 | { | ||
207 | val0 = rb->global_settings->bass; | ||
208 | val1 = rb->global_settings->treble; | ||
209 | } | ||
210 | else | ||
211 | { | ||
212 | val0 = rb->sound_default(SOUND_BASS); | ||
213 | val1 = rb->sound_default(SOUND_TREBLE); | ||
214 | } | ||
215 | rb->sound_set(SOUND_BASS, val0); | ||
216 | rb->sound_set(SOUND_TREBLE, val1); | ||
217 | break; | ||
218 | |||
219 | case MPEG_AUDIO_CHANNEL_MODES: | ||
220 | val0 = (global || settings.channel_modes) ? | ||
221 | rb->global_settings->channel_config : | ||
222 | SOUND_CHAN_STEREO; | ||
223 | rb->sound_set(SOUND_CHANNELS, val0); | ||
224 | break; | ||
225 | |||
226 | case MPEG_AUDIO_CROSSFEED: | ||
227 | rb->dsp_set_crossfeed((global || settings.crossfeed) ? | ||
228 | rb->global_settings->crossfeed : false); | ||
229 | break; | ||
230 | |||
231 | case MPEG_AUDIO_EQUALIZER: | ||
232 | rb->dsp_set_eq((global || settings.equalizer) ? | ||
233 | rb->global_settings->eq_enabled : false); | ||
234 | break; | ||
235 | |||
236 | case MPEG_AUDIO_DITHERING: | ||
237 | rb->dsp_dither_enable((global || settings.dithering) ? | ||
238 | rb->global_settings->dithering_enabled : false); | ||
239 | break; | ||
240 | } | ||
241 | } | ||
242 | |||
243 | /* Sync all audio settings to global or mpegplayer forced off */ | ||
244 | static void sync_audio_settings(bool global) | ||
245 | { | ||
246 | static const int setting_index[] = | ||
247 | { | ||
248 | MPEG_AUDIO_TONE_CONTROLS, | ||
249 | MPEG_AUDIO_CHANNEL_MODES, | ||
250 | MPEG_AUDIO_CROSSFEED, | ||
251 | MPEG_AUDIO_EQUALIZER, | ||
252 | MPEG_AUDIO_DITHERING, | ||
253 | }; | ||
254 | unsigned i; | ||
255 | |||
256 | for (i = 0; i < ARRAYLEN(setting_index); i++) | ||
257 | { | ||
258 | sync_audio_setting(setting_index[i], global); | ||
259 | } | ||
260 | } | ||
187 | 261 | ||
188 | #ifndef HAVE_LCD_COLOR | 262 | #ifndef HAVE_LCD_COLOR |
189 | /* Cheapo splash implementation for the grey surface */ | 263 | /* Cheapo splash implementation for the grey surface */ |
@@ -736,6 +810,79 @@ static void display_options(void) | |||
736 | menu_exit(menu_id); | 810 | menu_exit(menu_id); |
737 | } | 811 | } |
738 | 812 | ||
813 | static void audio_options(void) | ||
814 | { | ||
815 | int result; | ||
816 | int menu_id; | ||
817 | bool menu_quit = false; | ||
818 | |||
819 | static const struct menu_item items[] = { | ||
820 | [MPEG_AUDIO_TONE_CONTROLS] = | ||
821 | { "Tone Controls", NULL }, | ||
822 | [MPEG_AUDIO_CHANNEL_MODES] = | ||
823 | { "Channel Modes", NULL }, | ||
824 | [MPEG_AUDIO_CROSSFEED] = | ||
825 | { "Crossfeed", NULL }, | ||
826 | [MPEG_AUDIO_EQUALIZER] = | ||
827 | { "Equalizer", NULL }, | ||
828 | [MPEG_AUDIO_DITHERING] = | ||
829 | { "Dithering", NULL }, | ||
830 | }; | ||
831 | |||
832 | menu_id = menu_init(rb, items, ARRAYLEN(items), | ||
833 | mpeg_menu_sysevent_callback, NULL, NULL, NULL); | ||
834 | |||
835 | rb->button_clear_queue(); | ||
836 | |||
837 | while (!menu_quit) | ||
838 | { | ||
839 | mpeg_menu_sysevent_clear(); | ||
840 | result = menu_show(menu_id); | ||
841 | |||
842 | switch (result) | ||
843 | { | ||
844 | case MPEG_AUDIO_TONE_CONTROLS: | ||
845 | mpeg_set_option("Tone Controls", &settings.tone_controls, INT, | ||
846 | globaloff, 2, NULL); | ||
847 | sync_audio_setting(result, false); | ||
848 | break; | ||
849 | |||
850 | case MPEG_AUDIO_CHANNEL_MODES: | ||
851 | mpeg_set_option("Channel Modes", &settings.channel_modes, | ||
852 | INT, globaloff, 2, NULL); | ||
853 | sync_audio_setting(result, false); | ||
854 | break; | ||
855 | |||
856 | case MPEG_AUDIO_CROSSFEED: | ||
857 | mpeg_set_option("Crossfeed", &settings.crossfeed, INT, | ||
858 | globaloff, 2, NULL); | ||
859 | sync_audio_setting(result, false); | ||
860 | break; | ||
861 | |||
862 | case MPEG_AUDIO_EQUALIZER: | ||
863 | mpeg_set_option("Equalizer", &settings.equalizer, INT, | ||
864 | globaloff, 2, NULL); | ||
865 | sync_audio_setting(result, false); | ||
866 | break; | ||
867 | |||
868 | case MPEG_AUDIO_DITHERING: | ||
869 | mpeg_set_option("Dithering", &settings.dithering, INT, | ||
870 | globaloff, 2, NULL); | ||
871 | sync_audio_setting(result, false); | ||
872 | break; | ||
873 | |||
874 | default: | ||
875 | menu_quit = true; | ||
876 | break; | ||
877 | } | ||
878 | |||
879 | if (mpeg_menu_sysevent() != 0) | ||
880 | menu_quit = true; | ||
881 | } | ||
882 | |||
883 | menu_exit(menu_id); | ||
884 | } | ||
885 | |||
739 | static void resume_options(void) | 886 | static void resume_options(void) |
740 | { | 887 | { |
741 | static const struct opt_items items[MPEG_RESUME_NUM_OPTIONS] = { | 888 | static const struct opt_items items[MPEG_RESUME_NUM_OPTIONS] = { |
@@ -775,6 +922,8 @@ int mpeg_menu(unsigned flags) | |||
775 | struct menu_item items[] = { | 922 | struct menu_item items[] = { |
776 | [MPEG_MENU_DISPLAY_SETTINGS] = | 923 | [MPEG_MENU_DISPLAY_SETTINGS] = |
777 | { "Display Options", NULL }, | 924 | { "Display Options", NULL }, |
925 | [MPEG_MENU_AUDIO_SETTINGS] = | ||
926 | { "Audio Options", NULL }, | ||
778 | [MPEG_MENU_ENABLE_START_MENU] = | 927 | [MPEG_MENU_ENABLE_START_MENU] = |
779 | { "Resume Options", NULL }, | 928 | { "Resume Options", NULL }, |
780 | [MPEG_MENU_CLEAR_RESUMES] = | 929 | [MPEG_MENU_CLEAR_RESUMES] = |
@@ -809,6 +958,10 @@ int mpeg_menu(unsigned flags) | |||
809 | display_options(); | 958 | display_options(); |
810 | break; | 959 | break; |
811 | 960 | ||
961 | case MPEG_MENU_AUDIO_SETTINGS: | ||
962 | audio_options(); | ||
963 | break; | ||
964 | |||
812 | case MPEG_MENU_ENABLE_START_MENU: | 965 | case MPEG_MENU_ENABLE_START_MENU: |
813 | resume_options(); | 966 | resume_options(); |
814 | break; | 967 | break; |
@@ -849,6 +1002,11 @@ void init_settings(const char* filename) | |||
849 | #if MPEG_OPTION_DITHERING_ENABLED | 1002 | #if MPEG_OPTION_DITHERING_ENABLED |
850 | settings.displayoptions = 0; /* No visual effects */ | 1003 | settings.displayoptions = 0; /* No visual effects */ |
851 | #endif | 1004 | #endif |
1005 | settings.tone_controls = false; | ||
1006 | settings.channel_modes = false; | ||
1007 | settings.crossfeed = false; | ||
1008 | settings.equalizer = false; | ||
1009 | settings.dithering = false; | ||
852 | 1010 | ||
853 | configfile_init(rb); | 1011 | configfile_init(rb); |
854 | 1012 | ||
@@ -886,6 +1044,9 @@ void init_settings(const char* filename) | |||
886 | { | 1044 | { |
887 | settings.resume_time = 0; | 1045 | settings.resume_time = 0; |
888 | } | 1046 | } |
1047 | |||
1048 | /* Set our audio options */ | ||
1049 | sync_audio_settings(false); | ||
889 | } | 1050 | } |
890 | 1051 | ||
891 | void save_settings(void) | 1052 | void save_settings(void) |
@@ -911,4 +1072,17 @@ void save_settings(void) | |||
911 | configfile_update_entry(SETTINGS_FILENAME, "Display options", | 1072 | configfile_update_entry(SETTINGS_FILENAME, "Display options", |
912 | settings.displayoptions); | 1073 | settings.displayoptions); |
913 | #endif | 1074 | #endif |
1075 | configfile_update_entry(SETTINGS_FILENAME, "Tone controls", | ||
1076 | settings.tone_controls); | ||
1077 | configfile_update_entry(SETTINGS_FILENAME, "Channel modes", | ||
1078 | settings.channel_modes); | ||
1079 | configfile_update_entry(SETTINGS_FILENAME, "Crossfeed", | ||
1080 | settings.crossfeed); | ||
1081 | configfile_update_entry(SETTINGS_FILENAME, "Equalizer", | ||
1082 | settings.equalizer); | ||
1083 | configfile_update_entry(SETTINGS_FILENAME, "Dithering", | ||
1084 | settings.dithering); | ||
1085 | |||
1086 | /* Restore audio options */ | ||
1087 | sync_audio_settings(true); | ||
914 | } | 1088 | } |
diff --git a/apps/plugins/mpegplayer/mpeg_settings.h b/apps/plugins/mpegplayer/mpeg_settings.h index 4d6da478ea..1557ff433e 100644 --- a/apps/plugins/mpegplayer/mpeg_settings.h +++ b/apps/plugins/mpegplayer/mpeg_settings.h | |||
@@ -1,7 +1,7 @@ | |||
1 | 1 | ||
2 | #include "plugin.h" | 2 | #include "plugin.h" |
3 | 3 | ||
4 | #define SETTINGS_VERSION 3 | 4 | #define SETTINGS_VERSION 4 |
5 | #define SETTINGS_MIN_VERSION 1 | 5 | #define SETTINGS_MIN_VERSION 1 |
6 | #define SETTINGS_FILENAME "mpegplayer.cfg" | 6 | #define SETTINGS_FILENAME "mpegplayer.cfg" |
7 | 7 | ||
@@ -24,6 +24,15 @@ enum mpeg_option_id | |||
24 | MPEG_OPTION_SKIP_FRAMES, | 24 | MPEG_OPTION_SKIP_FRAMES, |
25 | }; | 25 | }; |
26 | 26 | ||
27 | enum mpeg_audio_option_id | ||
28 | { | ||
29 | MPEG_AUDIO_TONE_CONTROLS, | ||
30 | MPEG_AUDIO_CHANNEL_MODES, | ||
31 | MPEG_AUDIO_CROSSFEED, | ||
32 | MPEG_AUDIO_EQUALIZER, | ||
33 | MPEG_AUDIO_DITHERING, | ||
34 | }; | ||
35 | |||
27 | enum mpeg_resume_id | 36 | enum mpeg_resume_id |
28 | { | 37 | { |
29 | MPEG_RESUME_MENU_ALWAYS = 0, | 38 | MPEG_RESUME_MENU_ALWAYS = 0, |
@@ -46,6 +55,7 @@ enum mpeg_start_id | |||
46 | enum mpeg_menu_id | 55 | enum mpeg_menu_id |
47 | { | 56 | { |
48 | MPEG_MENU_DISPLAY_SETTINGS, | 57 | MPEG_MENU_DISPLAY_SETTINGS, |
58 | MPEG_MENU_AUDIO_SETTINGS, | ||
49 | MPEG_MENU_ENABLE_START_MENU, | 59 | MPEG_MENU_ENABLE_START_MENU, |
50 | MPEG_MENU_CLEAR_RESUMES, | 60 | MPEG_MENU_CLEAR_RESUMES, |
51 | MPEG_MENU_QUIT, | 61 | MPEG_MENU_QUIT, |
@@ -62,6 +72,12 @@ struct mpeg_settings { | |||
62 | #if MPEG_OPTION_DITHERING_ENABLED | 72 | #if MPEG_OPTION_DITHERING_ENABLED |
63 | int displayoptions; | 73 | int displayoptions; |
64 | #endif | 74 | #endif |
75 | /* Audio options - simple on/off specification */ | ||
76 | int tone_controls; | ||
77 | int channel_modes; | ||
78 | int crossfeed; | ||
79 | int equalizer; | ||
80 | int dithering; | ||
65 | }; | 81 | }; |
66 | 82 | ||
67 | extern struct mpeg_settings settings; | 83 | extern struct mpeg_settings settings; |
diff --git a/apps/plugins/mpegplayer/mpegplayer.h b/apps/plugins/mpegplayer/mpegplayer.h index 11bc1ea669..b92af38e6c 100644 --- a/apps/plugins/mpegplayer/mpegplayer.h +++ b/apps/plugins/mpegplayer/mpegplayer.h | |||
@@ -53,18 +53,19 @@ enum mpeg_malloc_reason_t | |||
53 | #define AUDIOBUF_ALLOC_SIZE (AUDIOBUF_SIZE+AUDIOBUF_GUARD_SIZE) | 53 | #define AUDIOBUF_ALLOC_SIZE (AUDIOBUF_SIZE+AUDIOBUF_GUARD_SIZE) |
54 | 54 | ||
55 | /** PCM buffer **/ | 55 | /** PCM buffer **/ |
56 | #define CLOCK_RATE 44100 /* Our clock rate in ticks/second (samplerate) */ | 56 | #define CLOCK_RATE NATIVE_FREQUENCY /* Our clock rate in ticks/second (samplerate) */ |
57 | 57 | ||
58 | /* Define this as "1" to have a test tone instead of silence clip */ | 58 | /* Define this as "1" to have a test tone instead of silence clip */ |
59 | #define SILENCE_TEST_TONE 0 | 59 | #define SILENCE_TEST_TONE 0 |
60 | 60 | ||
61 | #define PCMOUT_BUFSIZE (CLOCK_RATE) /* 1s */ | 61 | #define PCMOUT_BUFSIZE (CLOCK_RATE/2*4) /* 1/2s */ |
62 | #define PCMOUT_GUARD_SIZE (1152*4 + sizeof (struct pcm_frame_header)) | 62 | #define PCMOUT_GUARD_SAMPLES ((CLOCK_RATE*576+7999)/8000) /* Worst upsampling case */ |
63 | #define PCMOUT_ALLOC_SIZE (PCMOUT_BUFSIZE + PCMOUT_GUARD_SIZE) | 63 | #define PCMOUT_GUARD_SIZE (PCMOUT_GUARD_SAMPLES*4 + sizeof (struct pcm_frame_header)) |
64 | /* Start pcm playback @ 25% full */ | 64 | #define PCMOUT_ALLOC_SIZE (PCMOUT_BUFSIZE + PCMOUT_GUARD_SIZE) |
65 | #define PCMOUT_PLAY_WM (PCMOUT_BUFSIZE/4) | 65 | /* Start pcm playback @ 25% full */ |
66 | /* No valid audio frame is smaller */ | 66 | #define PCMOUT_PLAY_WM (PCMOUT_BUFSIZE/4) |
67 | #define PCMOUT_LOW_WM (sizeof (struct pcm_frame_header)) | 67 | /* No valid audio frame is smaller */ |
68 | #define PCMOUT_LOW_WM (sizeof (struct pcm_frame_header)) | ||
68 | 69 | ||
69 | /** disk buffer **/ | 70 | /** disk buffer **/ |
70 | #define DISK_BUF_LOW_WATERMARK (1024*1024) | 71 | #define DISK_BUF_LOW_WATERMARK (1024*1024) |
diff --git a/apps/plugins/mpegplayer/pcm_output.c b/apps/plugins/mpegplayer/pcm_output.c index ac89308af1..e17f635f72 100644 --- a/apps/plugins/mpegplayer/pcm_output.c +++ b/apps/plugins/mpegplayer/pcm_output.c | |||
@@ -33,6 +33,7 @@ static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_head IBSS_ATTR; | |||
33 | static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_tail IBSS_ATTR; | 33 | static struct pcm_frame_header * ALIGNED_ATTR(4) pcmbuf_tail IBSS_ATTR; |
34 | 34 | ||
35 | /* Bytes */ | 35 | /* Bytes */ |
36 | static ssize_t pcmbuf_curr_size IBSS_ATTR; /* Size of currently playing frame */ | ||
36 | static uint64_t pcmbuf_read IBSS_ATTR; /* Number of bytes read by DMA */ | 37 | static uint64_t pcmbuf_read IBSS_ATTR; /* Number of bytes read by DMA */ |
37 | static uint64_t pcmbuf_written IBSS_ATTR; /* Number of bytes written by source */ | 38 | static uint64_t pcmbuf_written IBSS_ATTR; /* Number of bytes written by source */ |
38 | static ssize_t pcmbuf_threshold IBSS_ATTR; /* Non-silence threshold */ | 39 | static ssize_t pcmbuf_threshold IBSS_ATTR; /* Non-silence threshold */ |
@@ -42,6 +43,9 @@ static uint32_t clock_base IBSS_ATTR; /* Our base clock */ | |||
42 | static uint32_t clock_start IBSS_ATTR; /* Clock at playback start */ | 43 | static uint32_t clock_start IBSS_ATTR; /* Clock at playback start */ |
43 | static int32_t clock_adjust IBSS_ATTR; /* Clock drift adjustment */ | 44 | static int32_t clock_adjust IBSS_ATTR; /* Clock drift adjustment */ |
44 | 45 | ||
46 | int pcm_skipped = 0; | ||
47 | int pcm_underruns = 0; | ||
48 | |||
45 | /* Small silence clip. ~5.80ms @ 44.1kHz */ | 49 | /* Small silence clip. ~5.80ms @ 44.1kHz */ |
46 | static int16_t silence[256*2] ALIGNED_ATTR(4) = { 0 }; | 50 | static int16_t silence[256*2] ALIGNED_ATTR(4) = { 0 }; |
47 | 51 | ||
@@ -51,7 +55,7 @@ static inline void pcm_advance_buffer(struct pcm_frame_header **p, | |||
51 | { | 55 | { |
52 | *p = SKIPBYTES(*p, size); | 56 | *p = SKIPBYTES(*p, size); |
53 | if (*p >= pcmbuf_end) | 57 | if (*p >= pcmbuf_end) |
54 | *p = pcm_buffer; | 58 | *p = SKIPBYTES(*p, -PCMOUT_BUFSIZE); |
55 | } | 59 | } |
56 | 60 | ||
57 | /* Inline internally but not externally */ | 61 | /* Inline internally but not externally */ |
@@ -68,7 +72,13 @@ inline ssize_t pcm_output_free(void) | |||
68 | /* Audio DMA handler */ | 72 | /* Audio DMA handler */ |
69 | static void get_more(unsigned char **start, size_t *size) | 73 | static void get_more(unsigned char **start, size_t *size) |
70 | { | 74 | { |
71 | ssize_t sz = pcm_output_used(); | 75 | ssize_t sz; |
76 | |||
77 | /* Free-up the last frame played frame if any */ | ||
78 | pcmbuf_read += pcmbuf_curr_size; | ||
79 | pcmbuf_curr_size = 0; | ||
80 | |||
81 | sz = pcm_output_used(); | ||
72 | 82 | ||
73 | if (sz > pcmbuf_threshold) | 83 | if (sz > pcmbuf_threshold) |
74 | { | 84 | { |
@@ -95,16 +105,20 @@ static void get_more(unsigned char **start, size_t *size) | |||
95 | /* Frame more than 100ms late - drop it */ | 105 | /* Frame more than 100ms late - drop it */ |
96 | pcm_advance_buffer(&pcmbuf_head, sz); | 106 | pcm_advance_buffer(&pcmbuf_head, sz); |
97 | pcmbuf_read += sz; | 107 | pcmbuf_read += sz; |
108 | pcm_skipped++; | ||
98 | if (pcmbuf_read < pcmbuf_written) | 109 | if (pcmbuf_read < pcmbuf_written) |
99 | continue; | 110 | continue; |
111 | |||
112 | /* Ran out so revert to default watermark */ | ||
113 | pcmbuf_threshold = PCMOUT_PLAY_WM; | ||
100 | } | 114 | } |
101 | else if (offset < 100*CLOCK_RATE/1000) | 115 | else if (offset < 100*CLOCK_RATE/1000) |
102 | { | 116 | { |
103 | /* Frame less than 100ms early - play it */ | 117 | /* Frame less than 100ms early - play it */ |
104 | *start = (unsigned char *)pcmbuf_head->data; | 118 | *start = pcmbuf_head->data; |
105 | 119 | ||
106 | pcm_advance_buffer(&pcmbuf_head, sz); | 120 | pcm_advance_buffer(&pcmbuf_head, sz); |
107 | pcmbuf_read += sz; | 121 | pcmbuf_curr_size = sz; |
108 | 122 | ||
109 | sz -= sizeof (struct pcm_frame_header); | 123 | sz -= sizeof (struct pcm_frame_header); |
110 | 124 | ||
@@ -124,6 +138,9 @@ static void get_more(unsigned char **start, size_t *size) | |||
124 | else | 138 | else |
125 | { | 139 | { |
126 | /* Ran out so revert to default watermark */ | 140 | /* Ran out so revert to default watermark */ |
141 | if (pcmbuf_threshold == PCMOUT_LOW_WM) | ||
142 | pcm_underruns++; | ||
143 | |||
127 | pcmbuf_threshold = PCMOUT_PLAY_WM; | 144 | pcmbuf_threshold = PCMOUT_PLAY_WM; |
128 | } | 145 | } |
129 | 146 | ||
@@ -164,8 +181,9 @@ void pcm_output_flush(void) | |||
164 | rb->pcm_play_stop(); | 181 | rb->pcm_play_stop(); |
165 | 182 | ||
166 | pcmbuf_threshold = PCMOUT_PLAY_WM; | 183 | pcmbuf_threshold = PCMOUT_PLAY_WM; |
167 | pcmbuf_read = pcmbuf_written = 0; | 184 | pcmbuf_curr_size = pcmbuf_read = pcmbuf_written = 0; |
168 | pcmbuf_head = pcmbuf_tail = pcm_buffer; | 185 | pcmbuf_head = pcmbuf_tail = pcm_buffer; |
186 | pcm_skipped = pcm_underruns = 0; | ||
169 | 187 | ||
170 | /* Restart if playing state was current */ | 188 | /* Restart if playing state was current */ |
171 | if (playing && !paused) | 189 | if (playing && !paused) |
@@ -251,10 +269,9 @@ bool pcm_output_init(void) | |||
251 | pcmbuf_head = pcm_buffer; | 269 | pcmbuf_head = pcm_buffer; |
252 | pcmbuf_tail = pcm_buffer; | 270 | pcmbuf_tail = pcm_buffer; |
253 | pcmbuf_end = SKIPBYTES(pcm_buffer, PCMOUT_BUFSIZE); | 271 | pcmbuf_end = SKIPBYTES(pcm_buffer, PCMOUT_BUFSIZE); |
254 | pcmbuf_read = 0; | 272 | pcmbuf_curr_size = pcmbuf_read = pcmbuf_written = 0; |
255 | pcmbuf_written = 0; | ||
256 | 273 | ||
257 | rb->pcm_set_frequency(SAMPR_44); | 274 | rb->pcm_set_frequency(NATIVE_FREQUENCY); |
258 | 275 | ||
259 | #if INPUT_SRC_CAPS != 0 | 276 | #if INPUT_SRC_CAPS != 0 |
260 | /* Select playback */ | 277 | /* Select playback */ |
@@ -264,7 +281,7 @@ bool pcm_output_init(void) | |||
264 | 281 | ||
265 | #if SILENCE_TEST_TONE | 282 | #if SILENCE_TEST_TONE |
266 | /* Make the silence clip a square wave */ | 283 | /* Make the silence clip a square wave */ |
267 | const int16_t silence_amp = 32767 / 16; | 284 | const int16_t silence_amp = INT16_MAX / 16; |
268 | unsigned i; | 285 | unsigned i; |
269 | 286 | ||
270 | for (i = 0; i < ARRAYLEN(silence); i += 2) | 287 | for (i = 0; i < ARRAYLEN(silence); i += 2) |
diff --git a/apps/plugins/mpegplayer/pcm_output.h b/apps/plugins/mpegplayer/pcm_output.h index 8a230b87a5..94ca6eed3b 100644 --- a/apps/plugins/mpegplayer/pcm_output.h +++ b/apps/plugins/mpegplayer/pcm_output.h | |||
@@ -29,6 +29,8 @@ struct pcm_frame_header /* Header added to pcm data every time a decoded | |||
29 | unsigned char data[]; /* open array of audio data */ | 29 | unsigned char data[]; /* open array of audio data */ |
30 | } ALIGNED_ATTR(4); | 30 | } ALIGNED_ATTR(4); |
31 | 31 | ||
32 | extern int pcm_skipped, pcm_underruns; | ||
33 | |||
32 | bool pcm_output_init(void); | 34 | bool pcm_output_init(void); |
33 | void pcm_output_exit(void); | 35 | void pcm_output_exit(void); |
34 | void pcm_output_flush(void); | 36 | void pcm_output_flush(void); |
diff --git a/apps/plugins/mpegplayer/video_thread.c b/apps/plugins/mpegplayer/video_thread.c index feee643d12..6508d28d1d 100644 --- a/apps/plugins/mpegplayer/video_thread.c +++ b/apps/plugins/mpegplayer/video_thread.c | |||
@@ -63,15 +63,21 @@ static void draw_fps(struct video_thread_data *td) | |||
63 | uint32_t start; | 63 | uint32_t start; |
64 | uint32_t clock_ticks = stream_get_ticks(&start); | 64 | uint32_t clock_ticks = stream_get_ticks(&start); |
65 | int fps = 0; | 65 | int fps = 0; |
66 | int buf_pct; | ||
66 | char str[80]; | 67 | char str[80]; |
67 | 68 | ||
68 | clock_ticks -= start; | 69 | clock_ticks -= start; |
69 | if (clock_ticks != 0) | 70 | if (clock_ticks != 0) |
70 | fps = muldiv_uint32(CLOCK_RATE*100, td->num_drawn, clock_ticks); | 71 | fps = muldiv_uint32(CLOCK_RATE*100, td->num_drawn, clock_ticks); |
71 | 72 | ||
72 | rb->snprintf(str, sizeof(str), "%d.%02d %d %d ", | 73 | buf_pct = muldiv_uint32(100, pcm_output_used(), PCMOUT_BUFSIZE); |
74 | |||
75 | rb->snprintf(str, sizeof(str), "v:%d.%02d %d %d a:%02d%% %d %d ", | ||
76 | /* Video information */ | ||
73 | fps / 100, fps % 100, td->num_skipped, | 77 | fps / 100, fps % 100, td->num_skipped, |
74 | td->info->display_picture->temporal_reference); | 78 | td->info->display_picture->temporal_reference, |
79 | /* Audio information */ | ||
80 | buf_pct, pcm_underruns, pcm_skipped); | ||
75 | lcd_(putsxy)(0, 0, str); | 81 | lcd_(putsxy)(0, 0, str); |
76 | 82 | ||
77 | vo_lock(); | 83 | vo_lock(); |
@@ -277,7 +283,6 @@ static int sync_decoder(struct video_thread_data *td, | |||
277 | while (1) | 283 | while (1) |
278 | { | 284 | { |
279 | mpeg2_state_t mp2state = mpeg2_parse(td->mpeg2dec); | 285 | mpeg2_state_t mp2state = mpeg2_parse(td->mpeg2dec); |
280 | rb->yield(); | ||
281 | 286 | ||
282 | switch (mp2state) | 287 | switch (mp2state) |
283 | { | 288 | { |
@@ -697,7 +702,6 @@ static void video_thread(void) | |||
697 | 702 | ||
698 | picture_decode: | 703 | picture_decode: |
699 | mp2state = mpeg2_parse (td.mpeg2dec); | 704 | mp2state = mpeg2_parse (td.mpeg2dec); |
700 | rb->yield(); | ||
701 | 705 | ||
702 | switch (mp2state) | 706 | switch (mp2state) |
703 | { | 707 | { |