forked from raspberrypi/pico-playground
-
Notifications
You must be signed in to change notification settings - Fork 0
/
popcorn.c
1658 lines (1533 loc) · 65.5 KB
/
popcorn.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
/**
* This source file is the result of prototyping work. It is not yet very examplary.
* In the future it will be cleaned up and commented for a blog post.
* In the meanwhile enter at your own risk.
*/
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/scanvideo.h"
#include "pico/scanvideo/composable_scanline.h"
#include "pico/audio_i2s.h"
#include "pico/multicore.h"
#include "pico/sync.h"
#include "pico/sd_card.h"
#include "hardware/divider.h"
#include "hardware/irq.h"
#include "platypus.h"
#include "font.h"
#ifdef VGABOARD_BUTTON_A_PIN
#define USE_VGABOARD_BUTTONS 1
#else
#define USE_UART_INPUT 1
#endif
// ----------------------------------------------------------------
// DEBUGGING
CU_REGISTER_DEBUG_PINS(frame_generation, audio_buffering)
//CU_SELECT_DEBUG_PINS(frame_generation)
//CU_SELECT_DEBUG_PINS(audio_buffering)
#ifdef ENABLE_STRICT_ASSERTIONS
#define strict_assert(x) assert(x)
#else
#define strict_assert(x) ((void)0)
#endif
#define popcorn_debug(format, args...) (void)0
static const struct scanvideo_mode vga_mode_320x120_60 =
{
.default_timing = &vga_timing_640x480_60_default,
.pio_program = &video_24mhz_composable,
.width = 320,
// only 120 logical scan lines (since we deal with two pixel rows at a time)
.height = 120,
.xscale = 2,
.yscale = 4, // * 4 = 480
};
#define vga_mode vga_mode_320x120_60
struct text_element {
const char *text;
uint16_t color;
int width;
};
struct movie {
struct text_element text;
uint32_t start_sector;
uint32_t current_sector;
} *movies;
uint movie_count;
#define NAME_COLOR PICO_SCANVIDEO_PIXEL_FROM_RGB5(0x10, 0x10, 0x10)
#define INSTR_COLOR1 PICO_SCANVIDEO_PIXEL_FROM_RGB5(0x10, 0x08, 0x08)
#define INSTR_COLOR2 PICO_SCANVIDEO_PIXEL_FROM_RGB5(0x08, 0x10, 0x10)
struct movie static_movies[] = {
{.text = {"<unknown name>", NAME_COLOR, 0}, .start_sector = 0},
};
#if USE_UART_INPUT
struct text_element instructions[] = {
{ "SPACE - toggle play/pause", INSTR_COLOR1 },
{ "ENTER - toggle display", INSTR_COLOR2 },
{ "'0' -> '4' - stopped -> fast", INSTR_COLOR1 },
{ "'+' / '-' - faster / slower(-mo)", INSTR_COLOR2 },
{ "'.' / ',' - step + / - one frame", INSTR_COLOR1 },
{ "'r' - reverse play direction!", INSTR_COLOR2 },
{ "'n' / 'p' - next / previous movie", INSTR_COLOR1 },
{ "'[' / ']' - down / up volume", INSTR_COLOR2 },
};
#define DISPLAY_NAME_AFTER_FRAME_COUNT 1
#elif defined(USE_VGABOARD_BUTTONS)
struct text_element instructions[] = {
{"Short press while playing", INSTR_COLOR1},
{"Slower : Pause : Faster", INSTR_COLOR1},
{"Short press while paused", INSTR_COLOR2},
{"< Step : Play : Step >", INSTR_COLOR2},
{"Medium press", INSTR_COLOR1},
{"< File : Toggle Menu : File >", INSTR_COLOR1},
{"Long Press", INSTR_COLOR2},
{"< Vol : Toggle Direction : Vol >", INSTR_COLOR2},
};
#define DISPLAY_NAME_AFTER_FRAME_COUNT 4
#endif
uint32_t display_base_frame;
uint current_movie;
uint registered_current_movie; // as seen by decode
static struct font *font12;
static struct font *font18;
static semaphore_t video_setup_complete;
static struct mutex frame_logic_mutex;
static uint8_t playback_forwards = 1;
static int8_t playback_speed;
static int8_t hold_frame_count;
static int remaining_hold_frames = 1;
static int32_t next_frame_sector_override = -1;
static int32_t audio_sector_pairs_to_post_process;
static int32_t volume = 0x100;
static uint32_t total_audio_sectors;
static bool show_menu = false;
static int text_roller = 0;
static void init_core(int core);
static void handle_input();
#if USE_VGABOARD_BUTTONS
volatile uint32_t button_state = 0; // sorry graham
static const uint button_pins[] = {0, 6, 11};
int32_t last_button_time[3];
uint32_t last_button_state;
const uint VSYNC_PIN = PICO_SCANVIDEO_COLOR_PIN_BASE + PICO_SCANVIDEO_COLOR_PIN_COUNT + 1;
// Registered as GPIO interrupt on both edges of vsync. On vsync assertion,
// set pins to input. On deassertion, sample and set back to output.
void vga_board_button_irq_handler() {
int vsync_current_level = gpio_get(VSYNC_PIN);
gpio_acknowledge_irq(VSYNC_PIN, vsync_current_level ? GPIO_IRQ_EDGE_RISE : GPIO_IRQ_EDGE_FALL);
// Note v_sync_polarity == 1 means active-low because anything else would be confusing
if (vsync_current_level != scanvideo_get_mode().default_timing->v_sync_polarity) {
for (int i = 0; i < count_of(button_pins); ++i) {
gpio_pull_down(button_pins[i]);
gpio_set_oeover(button_pins[i], GPIO_OVERRIDE_LOW);
}
} else {
uint32_t state = 0;
for (int i = 0; i < count_of(button_pins); ++i) {
state |= gpio_get(button_pins[i]) << i;
gpio_set_oeover(button_pins[i], GPIO_OVERRIDE_NORMAL);
}
button_state = state;
}
}
void vga_board_init_buttons() {
gpio_set_irq_enabled(VSYNC_PIN, GPIO_IRQ_EDGE_FALL | GPIO_IRQ_EDGE_RISE, true);
irq_set_exclusive_handler(IO_IRQ_BANK0, vga_board_button_irq_handler);
irq_set_enabled(IO_IRQ_BANK0, true);
}
#endif
struct font {
uint width_words;
uint height;
uint glyph_words; // == width_words * height;
uint32_t *pixels;
};
struct font *build_font(const lv_font_t *font, uint width_words) {
struct font *f = (struct font *) calloc(1, sizeof(struct font));
f->width_words = width_words;
f->height = font->line_height;
uint16_t colors[16];
for (int i = 0; i < count_of(colors); i++) {
#ifdef PLATYPUS_565
colors[i] = 0x41 * i + 0x800 * (i / 2);
#else
colors[i] = 0x21 * i + 0x400 * (i/2);
#endif
}
uint font_size_words = font->line_height * width_words;
f->pixels = (uint32_t *) calloc(4, font->dsc->cmaps->range_length * font_size_words);
f->glyph_words = f->width_words * f->height;
uint32_t *p = f->pixels;
for (int c = 0; c < font->dsc->cmaps->range_length; c++) {
// inefficient but simple
assert(p == f->pixels + c * f->glyph_words);
const lv_font_fmt_txt_glyph_dsc_t *g = &font->dsc->glyph_dsc[c + 1];
const uint8_t *b = font->dsc->glyph_bitmap + g->bitmap_index;
int bi = 0;
for (int y = 0; y < f->height; y++) {
int ey = y - f->height + font->base_line + g->ofs_y + g->box_h;
for (int x = 0; x < f->width_words * 2; x++) {
uint32_t pixel;
int ex = x - g->ofs_x;
if (ex >= 0 && ex < g->box_w && ey >= 0 && ey < g->box_h) {
pixel = bi & 1 ? colors[b[bi >> 1] & 0xf] : colors[b[bi >> 1] >> 4];
bi++;
} else {
pixel = 0;
}
if (!(x & 1)) {
*p = pixel;
} else {
*p++ |= pixel << 16;
}
}
if (ey >= 0 && ey < g->box_h) {
for (int x = f->width_words * 2 - g->ofs_x; x < g->box_w; x++) {
bi++;
}
}
}
}
return f;
}
static void __attribute__((noinline)) __time_critical_func(reverse_sector_pair)(uint32_t *s1, uint32_t *s2) {
for (int i = 0; i < 128; i++) {
uint32_t tmp = s1[i];
s1[i] = s2[127 - i];
s2[127 - i] = tmp;
}
}
#define IMAGE_DATA_K 126
#define AUDIO_BUFFER_K 6
#define IMAGE_DATA_WORDS (IMAGE_DATA_K * 256)
// todo see where we write off the end of this (hence need for + 128)
uint32_t image_data[
128 + IMAGE_DATA_K * 1024 / 4] = {1}; // force into data not BSS
#define NUM_AUDIO_BUFFERS 2
uint32_t audio_buffer[AUDIO_BUFFER_K * NUM_AUDIO_BUFFERS * 1024 / 4] = {1};
uint32_t *const audio_buffer_start[NUM_AUDIO_BUFFERS] = {
audio_buffer,
audio_buffer + AUDIO_BUFFER_K * 256,
#if NUM_AUDIO_BUFFERS > 2
audio_buffer + AUDIO_BUFFER_K * 512
#endif
};
extern const uint8_t atlantis_glyph_bitmap[];
extern const uint8_t atlantis_glyph_widths[];
#define menu_glypth_bitmap atlantis_glyph_bitmap
#define menu_glyph_widths atlantis_glyph_widths
#define MENU_GLYPH_MIN 32
#define MENU_GLYPH_COUNT 95
#define MENU_GLYPH_MAX (MENU_GLYPH_MIN + MENU_GLYPH_COUNT - 1)
#define MENU_GLYPH_HEIGHT 9
#define MENU_GLYPH_Y_OFFSET 2
#define MENU_GLYPH_ADVANCE 1
#define OVERLAY_WIDTH 80
#define OVERLAY_HEIGHT (17 + MENU_GLYPH_HEIGHT)
#define OVERLAY_X ((160 - OVERLAY_WIDTH) / 2)
#define OVERLAY_END 110
#define OVERLAY_START (OVERLAY_END - OVERLAY_HEIGHT)
static uint32_t overlay[OVERLAY_WIDTH * OVERLAY_HEIGHT * 2];
struct audio_buffer *audio_buffers[NUM_AUDIO_BUFFERS];
struct audio_buffer_pool *audio_buffer_pool;
#define MOVIE_ROWS 120
// +1 so we can tell full from empty
#define ROW_OFFSET_CIRCLE_SIZE (MOVIE_ROWS * 2 + 1 + 10)
static uint16_t row_buffer_offsets[ROW_OFFSET_CIRCLE_SIZE];
static uint row_wrap_add(uint a, uint b) {
assert(a < ROW_OFFSET_CIRCLE_SIZE && b <= MOVIE_ROWS);
a += b;
if (a >= ROW_OFFSET_CIRCLE_SIZE) a -= ROW_OFFSET_CIRCLE_SIZE;
return a;
}
static uint row_wrap_sub(uint a, uint b) {
assert(a < ROW_OFFSET_CIRCLE_SIZE && b < ROW_OFFSET_CIRCLE_SIZE);
a += ROW_OFFSET_CIRCLE_SIZE - b;
if (a >= ROW_OFFSET_CIRCLE_SIZE) a -= ROW_OFFSET_CIRCLE_SIZE;
return a;
}
#ifdef ENABLE_STRICT_ASSERTIONS
uint16_t ram_buffer_owning_row[RAM_BUFFER_K*1024 / 4] = {1};
#endif
static inline void set_owning_row(__unused uint from, __unused uint count, __unused uint16_t row) {
#ifdef ENABLE_STRICT_ASSERTIONS
static uint16_t last_owning_row;
if (row != (uint16_t)-1)
{
assert(row == last_owning_row || row == row_wrap_add(last_owning_row, 1));
last_owning_row = row;
}
popcorn_debug("set owning row %04x->%04x %d\n", from, from+count, row);
for(int x = 0; x < count; x++)
{
// todo mark as unread once we track read amounts better.
ram_buffer_owning_row[from + x] = row; // 0x4000|row; // we mark this way as unread yet
}
#endif
}
// + 1 for null end marker, and +1 for split buffer during wrap
static uint32_t scatter[(PICO_SD_MAX_BLOCK_COUNT + 2) * 4];
static uint32_t frame_header_sector[128];
struct frame_header {
uint32_t mark0;
uint32_t mark1;
uint32_t magic;
uint8_t major, minior, debug, spare;
uint32_t sector_number; // relative to start of stream
uint32_t frame_number;
uint8_t hh, mm, ss, ff; // good old CD days (bcd)
uint32_t header_words;
uint16_t width;
uint16_t height;
uint32_t image_words;
uint32_t audio_words; // just to confirm really
uint32_t audio_freq;
uint8_t audio_channels; // always assume 16 bit
uint8_t pad[3];
uint32_t unused[4]; // little space for expansion
uint32_t total_sectors;
uint32_t last_sector;
// 1, 2, 4, 8 frame increments
uint32_t forward_frame_sector[4];
uint32_t backward_frame_sectors[4];
// h/2 + 1 row_offsets, last one should == image_words
uint16_t row_offsets[];
} __attribute__((packed));
static struct decoder_state_state {
enum {
INIT,
NEW_FRAME,
NEED_FRAME_HEADER_SECTOR,
READING_FRAME_HEADER_SECTOR,
NEED_VIDEO_SECTORS,
PREPPING_VIDEO_SECTORS,
READING_VIDEO_SECTORS,
AWAIT_AUDIO_BUFFER,
NEED_AUDIO_SECTORS,
READING_AUDIO_SECTORS,
AUDIO_BUFFER_READY,
POST_PROCESSING_AUDIO_SECTORS,
ERROR,
FRAME_READY,
HIT_END,
} state;
struct {
uint32_t sector_base;
uint16_t frame_base_row;
uint16_t write_buffer_offset;
uint16_t remaining_row_words;
// todo we should combine these
uint16_t frame_row_count;
uint16_t row_index;
} video_read, video_read_rollback;
struct {
uint32_t sector_base;
uint16_t sector_count;
} current_sd_read;
struct {
uint16_t valid_from_row;
uint16_t valid_to_row;
// we display from frame_start to valid_to_row, and then wrap back prior to display_start (i.e.
// remaining data from previous frame in a pinch)...
// todo right now we always try and keep MOVIE_ROWS valid lines ending at valid_to_row
uint16_t display_start_row;
} rows;
struct {
volatile enum {
BS_EMPTY, BS_FILLING, BS_QUEUED
} buffer_state[2];
uint load_thread_buffer_index;
uint32_t sector_base;
} audio;
bool hold_frame;
bool paused;
uint8_t unpause;
bool awaiting_first_frame;
uint32_t display_time_code;
bool loaded_audio_this_frame;
} ds;
uint32_t last_display_time_code = -1;
static uint32_t waste[128]; // todo we can move this to no write thru XIP cache alias
void popcorn_producer_pool_give_buffer(struct audio_connection *connection, struct audio_buffer *buffer) {
// hand directly to consumer
queue_full_audio_buffer(connection->consumer_pool, buffer);
}
void popcorn_consumer_pool_give_buffer(struct audio_connection *connection, struct audio_buffer *buffer) {
for (int i = 0; i < NUM_AUDIO_BUFFERS; i++) {
if (buffer == audio_buffers[i]) {
DEBUG_PINS_CLR(audio_buffering, i + 1);
ds.audio.buffer_state[i] = BS_EMPTY;
return;
}
}
__breakpoint();
__builtin_unreachable();
}
static struct audio_connection popcorn_passthru_connection = {
.consumer_pool_take = consumer_pool_take_buffer_default,
.consumer_pool_give = popcorn_consumer_pool_give_buffer,
.producer_pool_take = producer_pool_take_buffer_default,
.producer_pool_give = popcorn_producer_pool_give_buffer,
};
#define PLATYPUS_MAGIC (('T'<<24)|('A'<<16)|('L'<<8)|'P')
static inline bool row_index_in_range(uint index, uint from, uint to) {
if (from <= to) {
return index >= from && index < to;
} else {
return index >= from || index < to;
}
}
static uint peek_upcoming_row(struct frame_header *head, int ahead) {
if (ds.video_read.frame_row_count + ahead >= MOVIE_ROWS) {
return 0;
}
return head->row_offsets[ds.video_read.frame_row_count + ahead + 1] -
head->row_offsets[ds.video_read.frame_row_count + ahead];
}
struct gpt_entry {
uint64_t ptype1, ptype2;
uint64_t guid1, guid2;
uint64_t first_lba;
uint64_t last_lba;
uint64_t attributes;
uint16_t u_name[36];
};
#pragma GCC push_options
#pragma GCC optimize("O3")
static void __attribute__((noinline)) update_audio_sector_volume(uint32_t *_samples) {
int16_t *samples = (int16_t *) _samples;
for (int i = 0; i < 256; i++) {
samples[i] = (samples[i] * volume) >> 8;
}
}
#pragma GCC pop_options
static void __time_critical_func(handle_prep_video_sectors)(struct frame_header *head) {
bool done = false;
// we are making a decision about how many sectors we can read (we read up to MAX_BLOCK_COUNT - 1) in case one is split
uint32_t *p = scatter;
int sector_count = 0;
uint32_t part1_buffer_offset;
uint32_t part1_size;
// note part2 is always loaded at ram offset 0
uint32_t part2_size;
uint buffer_offset_limit = row_buffer_offsets[ds.rows.valid_from_row];
uint frame_row_count_limit = row_wrap_sub(ds.rows.valid_from_row,
row_wrap_add(ds.video_read.frame_base_row, 1));
// constraint 1) we must have enough space for the scatter information (part1 + (part2) + CRC) * 2 = 6
while (sector_count < PICO_SD_MAX_BLOCK_COUNT && p < scatter + count_of(scatter) - 6) {
uint words_until_wrap = IMAGE_DATA_WORDS - ds.video_read.write_buffer_offset;
bool may_wrap = buffer_offset_limit < ds.video_read.write_buffer_offset ||
ds.rows.valid_from_row == ds.rows.valid_to_row; // (latter is empty buffer)
uint linear_words = may_wrap ? words_until_wrap : buffer_offset_limit - ds.video_read.write_buffer_offset;
popcorn_debug("s %d r %d val %04x ->| %04x (%d %d %d)", (uint) ds.video_read.sector_base + sector_count,
ds.video_read.frame_row_count,
ds.video_read.write_buffer_offset, buffer_offset_limit,
ds.rows.valid_from_row, ds.rows.display_start_row, ds.rows.valid_to_row);
if (may_wrap) {
popcorn_debug(" may-wrap @ %04x", IMAGE_DATA_WORDS);
}
popcorn_debug("\n");
// constraint 2) we must have space for a sector - conservatively we assume if we have to wrap we need space
// for a whole sector at the beginning of the buffer
if (linear_words < 128 && !may_wrap) {
popcorn_debug(" no room and can't wrap\n");
break;
}
uint row_index = ds.video_read.row_index;
if (!ds.video_read.remaining_row_words) {
// we need a new row
uint remaining_row_words = peek_upcoming_row(head, 0);
row_index = row_wrap_add(ds.video_read.frame_base_row, ds.video_read.frame_row_count);
if (!remaining_row_words) {
done = true;
break;
}
if (ds.video_read.frame_row_count == frame_row_count_limit) {
popcorn_debug(" would start new row on sector boundary but out of rows space\n");
break;
}
ds.video_read.remaining_row_words = remaining_row_words; // we can't update this prior
popcorn_debug(" start new row on sector boundary ri %d\n", row_index);
// we have a new row, so decide whether it goes here or after wrap
if (ds.video_read.remaining_row_words > words_until_wrap) {
if (ds.video_read.write_buffer_offset <= buffer_offset_limit) {
popcorn_debug(" too early to wrap\n");
break;
}
popcorn_debug(" moving entire new row to beginning\n");
ds.video_read.write_buffer_offset = 0;
words_until_wrap = linear_words = buffer_offset_limit;
may_wrap = false;
}
row_buffer_offsets[row_index] = ds.video_read.write_buffer_offset;
ds.video_read.frame_row_count++;
ds.video_read.row_index = row_index;
}
assert(ds.video_read.remaining_row_words); // there should indeed be row words then
if (ds.video_read.remaining_row_words >= 128) {
uint this_time_words = MIN(ds.video_read.remaining_row_words, 128);
if (linear_words < this_time_words) {
popcorn_debug(" not enough space yet\n");
break;
}
popcorn_debug(" 128 of %d\n", ds.video_read.remaining_row_words);
part1_buffer_offset = ds.video_read.write_buffer_offset;
part1_size = 128;
part2_size = 0;
set_owning_row(part1_buffer_offset, part1_size, row_index);
ds.video_read.remaining_row_words -= this_time_words;
ds.video_read.write_buffer_offset += 128;
} else {
// note that here we are in the middle of a row, therefore we cannot be splitting, which is why we care
// about linear_words
if (linear_words < ds.video_read.remaining_row_words) {
popcorn_debug(" not enough space yet even for remaining\n");
break;
}
part1_buffer_offset = ds.video_read.write_buffer_offset;
part1_size = ds.video_read.remaining_row_words;
part2_size = 0;
set_owning_row(part1_buffer_offset, part1_size, row_index);
uint consumed = ds.video_read.remaining_row_words;
uint saved_remaining_row_words = ds.video_read.remaining_row_words;
ds.video_read.write_buffer_offset += ds.video_read.remaining_row_words;
popcorn_debug(" %d remaining of ri %d, then ", ds.video_read.remaining_row_words, row_index);
ds.video_read.remaining_row_words = 0;
bool rollback = false;
int rows_ahead = 0;
while (consumed < 128 && !rollback) {
assert(!ds.video_read.remaining_row_words);
ds.video_read.remaining_row_words = peek_upcoming_row(head, rows_ahead);
if (!ds.video_read.remaining_row_words) {
if (part2_size) {
part2_size += 128 - consumed;
} else {
part1_size += 128 - consumed;
}
set_owning_row(ds.video_read.write_buffer_offset, 128 - consumed, -1);
done = true;
break; // end of frame
}
if ((ds.video_read.frame_row_count + rows_ahead) >= frame_row_count_limit) {
assert((ds.video_read.frame_row_count + rows_ahead) == frame_row_count_limit);
popcorn_debug(" out of rows space; rollback!\n");
rollback = true;
} else {
uint to_consume = MIN(128 - consumed, ds.video_read.remaining_row_words);
row_index = row_wrap_add(ds.video_read.frame_base_row,
ds.video_read.frame_row_count + rows_ahead);
if (consumed + ds.video_read.remaining_row_words > words_until_wrap) {
// this row will have to wrap anyway
if (!may_wrap || (ds.video_read.remaining_row_words >= buffer_offset_limit &&
buffer_offset_limit < 128)) {
popcorn_debug("not enough space to wrap .... rollback!");
rollback = true;
} else {
assert(may_wrap);
popcorn_debug("wrap, and ");
assert(!part2_size);
part2_size += to_consume;
ds.video_read.write_buffer_offset = 0;
set_owning_row(ds.video_read.write_buffer_offset, to_consume, row_index);
words_until_wrap = linear_words = buffer_offset_limit;
may_wrap = false;
}
} else {
if (part2_size) {
// if we've already wrapped, then we need to add to that
part2_size += to_consume;
} else {
part1_size += to_consume;
}
set_owning_row(ds.video_read.write_buffer_offset, to_consume, row_index);
}
if (!rollback) {
popcorn_debug("%d from new row ri = %d(of %d)", to_consume, row_index,
(uint) ds.video_read.remaining_row_words);
row_buffer_offsets[row_index] = ds.video_read.write_buffer_offset;
ds.video_read.remaining_row_words -= to_consume;
ds.video_read.write_buffer_offset += to_consume;
consumed += to_consume;
rows_ahead++;
}
}
}
popcorn_debug("\n");
if (rollback) {
// we had to rollback
ds.video_read.remaining_row_words = saved_remaining_row_words;
ds.video_read.write_buffer_offset = part1_buffer_offset;
break;
}
ds.video_read.frame_row_count += rows_ahead;
ds.video_read.row_index = row_index;
}
// we must read a whole sector
assert(part1_size + part2_size == 128);
popcorn_debug(" read %d at %04x ", (uint) part1_size, (uint) part1_buffer_offset);
*p++ = native_safe_hw_ptr(image_data + part1_buffer_offset);
*p++ = part1_size;
if (part2_size) {
popcorn_debug(" and %d at 0000", (uint) part2_size);
*p++ = native_safe_hw_ptr(image_data); // part2 always at start of buffer
*p++ = part2_size;
}
popcorn_debug("\n");
// CRC
*p++ = native_safe_hw_ptr(waste);
*p++ = 2;
sector_count++;
}
if (sector_count) {
*p++ = 0;
*p++ = 0;
assert(p <= scatter + count_of(scatter));
ds.current_sd_read.sector_base = ds.video_read.sector_base;
ds.current_sd_read.sector_count = sector_count;
popcorn_debug("starting read %d secs @ %04x?(%04x) -> %04x(%04x)\n", sector_count,
row_buffer_offsets[row_wrap_add(ds.video_read.frame_base_row,
ds.video_read_rollback.frame_row_count)],
ds.video_read_rollback.write_buffer_offset,
row_buffer_offsets[row_wrap_add(ds.video_read.frame_base_row,
ds.video_read.frame_row_count - 1)],
ds.video_read.write_buffer_offset);
sd_readblocks_scatter_async(scatter, ds.video_read.sector_base, sector_count);
ds.state = READING_VIDEO_SECTORS;
} else if (done) {
if (!ds.loaded_audio_this_frame) {
ds.state = AWAIT_AUDIO_BUFFER;
} else {
ds.state = FRAME_READY;
}
} else {
ds.state = HIT_END;
}
}
static void __time_critical_func(handle_audio_buffer_ready)(const struct frame_header *head) {
DEBUG_PINS_CLR(audio_buffering, 4);
ds.loaded_audio_this_frame = true;
ds.audio.buffer_state[ds.audio.load_thread_buffer_index] = BS_QUEUED;
if (!ds.paused && (playback_speed > -3 && playback_speed < 3)) {
if (playback_forwards) {
audio_buffers[ds.audio.load_thread_buffer_index]->buffer->bytes = (uint8_t *) audio_buffer_start[ds.audio.load_thread_buffer_index];
} else {
// need to offset the audio because it is now right aligned
audio_buffers[ds.audio.load_thread_buffer_index]->buffer->bytes = (uint8_t *) (
audio_buffer_start[ds.audio.load_thread_buffer_index] + (127u & -head->audio_words));
}
DEBUG_PINS_SET(audio_buffering, ds.audio.load_thread_buffer_index + 1);
give_audio_buffer(audio_buffer_pool, audio_buffers[ds.audio.load_thread_buffer_index]);
} else {
ds.audio.buffer_state[ds.audio.load_thread_buffer_index] = BS_EMPTY;
}
ds.audio.load_thread_buffer_index++;
if (ds.audio.load_thread_buffer_index == NUM_AUDIO_BUFFERS) ds.audio.load_thread_buffer_index = 0;
ds.state = NEED_VIDEO_SECTORS;
}
static void __time_critical_func(handle_need_frame_header_sector)(struct frame_header *head) {
head->mark0 = 0; // mark as invalid
popcorn_debug("starting scatter read %d\n", (uint) vs.sector_num);
const int sector_count = 1;
assert(sector_count <= PICO_SD_MAX_BLOCK_COUNT);
uint32_t *p = scatter;
for (int i = 0; i < sector_count; i++) {
*p++ = native_safe_hw_ptr((frame_header_sector + i * 128));
*p++ = 128;
// for now we read the CRCs also
*p++ = native_safe_hw_ptr(waste);
*p++ = 2;
}
*p++ = 0;
*p++ = 0;
ds.current_sd_read.sector_count = sector_count;
sd_readblocks_scatter_async(scatter, ds.current_sd_read.sector_base, ds.current_sd_read.sector_count);
ds.state = READING_FRAME_HEADER_SECTOR;
}
static void __time_critical_func(handle_reading_frame_header_sector)() {
if (sd_scatter_read_complete(NULL)) {
struct frame_header *head = (struct frame_header *) frame_header_sector;
if (head->mark0 != 0xffffffff || head->mark1 != 0xffffffff || head->magic != PLATYPUS_MAGIC) {
printf("no header found @ %d\n", (int) ds.current_sd_read.sector_base);
ds.current_sd_read.sector_base++;
ds.state = NEED_FRAME_HEADER_SECTOR;
} else {
assert(ds.current_sd_read.sector_count == 1);
if (head->header_words > 128) {
printf("expect 1 sector header\n");
ds.current_sd_read.sector_base++;
ds.state = NEED_FRAME_HEADER_SECTOR;
} else {
movies[registered_current_movie].current_sector = ds.current_sd_read.sector_base;
ds.display_time_code = (head->hh << 24u) | (head->mm << 16u) | (head->ss << 8u) | (head->ff);
ds.current_sd_read.sector_base++;
// skip audio sectors
ds.audio.sector_base = ds.current_sd_read.sector_base;
ds.current_sd_read.sector_base += (head->audio_words + 127) / 128;
ds.video_read.sector_base = ds.current_sd_read.sector_base;
ds.video_read.frame_base_row = ds.rows.valid_to_row;
ds.video_read.frame_row_count = 0;
ds.state = NEED_VIDEO_SECTORS;
}
}
}
}
static void handle_init(const struct frame_header *head) {
movie_count = 1;
movies = static_movies;
if (sd_init_4pins() < 0) {
ds.state = ERROR;
} else {
sd_readblocks_sync(image_data, 1, 1);
const struct gpt_header {
uint64_t signature;
uint32_t revision;
uint32_t size;
uint32_t crc;
uint32_t _pad;
uint64_t lba;
uint64_t backup_lba;
uint64_t first_usable_lba;
uint64_t last_usabble_lba;
uint64_t guid1, guid2;
uint64_t table_lba;
uint32_t table_count;
uint32_t table_entry_size;
uint32_t table_crc;
} __packed gpt_header = *(const struct gpt_header *) image_data;
// todo crc
if (gpt_header.signature == 0x5452415020494645ULL && gpt_header.size == sizeof(struct gpt_header)) {
printf("Found GPT\n");
int sectors = (gpt_header.table_count * gpt_header.table_entry_size + 511) / 512;
int read_sectors = MAX(sectors, 32);
printf(" reading %d/%d sectors starting at %d\n", read_sectors, sectors,
(int) gpt_header.table_lba);
sd_readblocks_sync(image_data, gpt_header.table_lba, read_sectors);
uint8_t *buffer = (uint8_t *) image_data;
movie_count = 0;
for (uint i = 0; i < gpt_header.table_count; i++) {
struct gpt_entry *gpt_entry = (struct gpt_entry *) (buffer + i * gpt_header.table_entry_size);
if (gpt_entry->ptype1 || gpt_entry->ptype2) {
sd_readblocks_sync(frame_header_sector, gpt_entry->first_lba, 1);
if (head->mark0 == 0xffffffff && head->mark1 == 0xffffffff ||
head->magic == PLATYPUS_MAGIC) {
movie_count++;
} else {
gpt_entry->ptype1 = gpt_entry->ptype2 = 0;
}
}
}
if (!movie_count) {
panic("No movies found");
}
movies = (struct movie *) calloc(movie_count, sizeof(struct movie));
for (uint i = 0, j = 0; i < gpt_header.table_count; i++) {
struct gpt_entry *gpt_entry = (struct gpt_entry *) (buffer + i * gpt_header.table_entry_size);
if (gpt_entry->ptype1 || gpt_entry->ptype2) {
movies[j].start_sector = gpt_entry->first_lba;
movies[j].text.color = NAME_COLOR;
const int MAX_TEXT = 20; // random ... must be less than 36
char *ascii = (char *) gpt_entry->u_name;
for (int k = 0; k < MAX_TEXT; k++) {
char c = gpt_entry->u_name[k];
ascii[k] = c; // overwrite the string at half speed
if (!c) break;
}
ascii[MAX_TEXT] = 0;
movies[j].text.text = strdup(ascii);
printf("'%s' at %08x\n", movies[j].text.text, (uint) movies[j].start_sector);
j++;
}
}
} else {
printf("No GPT found, so assuming single movie\n");
}
}
ds.audio.buffer_state[0] = ds.audio.buffer_state[1] = BS_EMPTY;
ds.audio.load_thread_buffer_index = 0;
ds.rows.valid_from_row = ds.rows.valid_to_row = 0;
ds.rows.display_start_row = 0;
ds.awaiting_first_frame = 1;
ds.hold_frame = true;
registered_current_movie = current_movie;
ds.current_sd_read.sector_base = movies[current_movie].start_sector;
ds.state = NEW_FRAME;
}
static void __time_critical_func(handle_new_frame)() {
ds.state = NEED_FRAME_HEADER_SECTOR;
ds.loaded_audio_this_frame = false;
}
static void __time_critical_func(handle_reading_video_sectors)() {
assert(ds.current_sd_read.sector_count);
if (sd_scatter_read_complete(NULL)) {
// todo arguably we can track the read in progress to update rows as they become available
uint row_count = ds.video_read.frame_row_count;
// check for incomplete row
if (ds.video_read.remaining_row_words) {
assert(row_count);
// todo is this correct.... seems like we have a partial row visible, or maybe a partial row invisible which is fine
popcorn_debug("acknowledge %d words from previous\n", ds.video_read.remaining_row_words);
row_count--;
}
uint __unused was = ds.rows.valid_to_row;
ds.rows.valid_to_row = row_wrap_add(ds.video_read.frame_base_row, row_count);
popcorn_debug("completed read %d->%d\n", was, ds.rows.valid_to_row);
ds.video_read.sector_base += ds.current_sd_read.sector_count;
ds.current_sd_read.sector_base = ds.video_read.sector_base;
ds.current_sd_read.sector_count = 0;
ds.state = NEED_VIDEO_SECTORS;
}
}
static void __time_critical_func(handle_reading_audio_sectors)(const struct frame_header *head) {
if (sd_scatter_read_complete(NULL)) {
// todo we have DMA completely capable of reading backwards - seems like a strange thing to expose in any lower level API though
// still we could use DMA to reverse the buffers for us (although it is a bit complicated to not step on our toes)
if (!playback_forwards || volume != 0x100) {
total_audio_sectors = (head->audio_words + 127) / 128;
if (total_audio_sectors & 1) {
panic("expected even sector count");
}
audio_sector_pairs_to_post_process = total_audio_sectors / 2; // we do them in pairs
ds.state = POST_PROCESSING_AUDIO_SECTORS;
} else {
ds.state = AUDIO_BUFFER_READY;
}
}
}
static void __time_critical_func(handle_frame_ready)(const struct frame_header *head) {
// not much to do really now other than start reading data for next sector
ds.awaiting_first_frame = false;
uint index = playback_speed >= 0 ? MIN(playback_speed, 3) : 0;
if (next_frame_sector_override != -1) {
ds.current_sd_read.sector_base = next_frame_sector_override;
next_frame_sector_override = -1;
registered_current_movie = current_movie;
} else {
uint32_t next_sector;
if (ds.paused) {
next_sector = head->sector_number;
} else {
if (playback_forwards) {
next_sector = head->forward_frame_sector[index];
} else {
next_sector = head->backward_frame_sectors[index];
}
}
if (next_sector == 0xffffffff) {
if (playback_forwards) {
next_sector = 0;
} else {
next_sector = head->last_sector;
}
}
ds.current_sd_read.sector_base = movies[registered_current_movie].start_sector + next_sector;
}
if (playback_speed < 0) hold_frame_count = 1 - playback_speed;
else hold_frame_count = 1;
ds.state = NEW_FRAME;
}
static void __time_critical_func(handle_await_audio_buffer)() {
if (ds.audio.buffer_state[ds.audio.load_thread_buffer_index] == BS_EMPTY) {
ds.audio.buffer_state[ds.audio.load_thread_buffer_index] = BS_FILLING;
ds.state = NEED_AUDIO_SECTORS;
}
}
static void __time_critical_func(handle_hit_end)() {
if (ds.hold_frame && ds.audio.buffer_state[ds.audio.load_thread_buffer_index] == BS_EMPTY &&
!ds.loaded_audio_this_frame) {
ds.audio.buffer_state[ds.audio.load_thread_buffer_index] = BS_FILLING;
ds.state = NEED_AUDIO_SECTORS;
} else {
ds.state = NEED_VIDEO_SECTORS;
}
}
static void __time_critical_func(handle_post_processing_audio_sectors)() {
assert(audio_sector_pairs_to_post_process > 0);
audio_sector_pairs_to_post_process--;
if (!playback_forwards) {
reverse_sector_pair(audio_buffer_start[ds.audio.load_thread_buffer_index] +
128 * audio_sector_pairs_to_post_process,
audio_buffer_start[ds.audio.load_thread_buffer_index] +
128 * (total_audio_sectors - 1 - audio_sector_pairs_to_post_process));
}
if (volume != 0x100) {
update_audio_sector_volume(audio_buffer_start[ds.audio.load_thread_buffer_index] +
128 * audio_sector_pairs_to_post_process);
update_audio_sector_volume(audio_buffer_start[ds.audio.load_thread_buffer_index] +
128 * (total_audio_sectors - 1 - audio_sector_pairs_to_post_process));
}
if (!audio_sector_pairs_to_post_process) {
ds.state = AUDIO_BUFFER_READY;
}
}
static void __time_critical_func(handle_need_audio_sectors)(const struct frame_header *head) {
DEBUG_PINS_SET(audio_buffering, 4);
assert(ds.audio.buffer_state[ds.audio.load_thread_buffer_index] == BS_FILLING);
audio_buffers[ds.audio.load_thread_buffer_index]->sample_count = head->audio_words;
// todo update sd.current_read_sector for consistency...
// can't do it until we pick the next frame sector explicitly rather than just happening into it.
sd_readblocks_async(audio_buffer_start[ds.audio.load_thread_buffer_index], ds.audio.sector_base,
(head->audio_words + 127) / 128);
ds.state = READING_AUDIO_SECTORS;
}
static void __time_critical_func(handle_need_video_sectors)() {
ds.video_read_rollback = ds.video_read;
ds.state = PREPPING_VIDEO_SECTORS;
}
static void __attribute__((noinline)) __time_critical_func(sd_state_update)() {
struct frame_header *head = (struct frame_header *) frame_header_sector;
switch (ds.state) {
case NEW_FRAME:
handle_new_frame();
break;
case NEED_FRAME_HEADER_SECTOR:
handle_need_frame_header_sector(head);
break;
case READING_FRAME_HEADER_SECTOR:
handle_reading_frame_header_sector();
break;
case READING_VIDEO_SECTORS:
handle_reading_video_sectors();
break;
case INIT:
handle_init(head);
break;
case ERROR:
panic("doh!");
case AWAIT_AUDIO_BUFFER:
handle_await_audio_buffer();
break;
case HIT_END:
handle_hit_end();
break;
case READING_AUDIO_SECTORS:
handle_reading_audio_sectors(head);
break;
case POST_PROCESSING_AUDIO_SECTORS:
handle_post_processing_audio_sectors();
break;
case FRAME_READY:
handle_frame_ready(head);
break;
case NEED_VIDEO_SECTORS:
handle_need_video_sectors();
break;
case NEED_AUDIO_SECTORS:
handle_need_audio_sectors(head);