mirror of
https://github.com/UberGuidoZ/Flipper.git
synced 2026-09-18 18:11:30 +00:00
Reorg, added more sources
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
App(
|
||||
appid="BadApple",
|
||||
name="Bad Apple",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="bad_apple_main",
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_icon="bad_apple_10px.png",
|
||||
fap_category="Misc"
|
||||
)
|
||||
@@ -0,0 +1,183 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <stm32wbxx_ll_tim.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <storage/storage.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include "bad_apple.h"
|
||||
#include "video_player.h"
|
||||
|
||||
#define TAG "badapple"
|
||||
|
||||
typedef enum {
|
||||
BadAppleEventTypeInput,
|
||||
BadAppleEventTypeTick,
|
||||
} BadAppleEventType;
|
||||
|
||||
typedef struct {
|
||||
BadAppleEventType type;
|
||||
InputEvent* input;
|
||||
} BadAppleEvent;
|
||||
|
||||
// Screen is 128x64 px
|
||||
static void app_draw_callback(Canvas* canvas, void* ctx) {
|
||||
BadAppleCtx* inst = ctx;
|
||||
|
||||
canvas_clear(canvas);
|
||||
canvas_draw_xbm(canvas, VIDEO_X, VIDEO_Y, VIDEO_WIDTH, SCREEN_HEIGHT, inst->framebuffer);
|
||||
canvas_draw_box(canvas, 0, 0, VIDEO_X, SCREEN_HEIGHT);
|
||||
canvas_draw_box(
|
||||
canvas, VIDEO_X + VIDEO_WIDTH, 0, SCREEN_WIDTH - VIDEO_WIDTH - VIDEO_X, SCREEN_HEIGHT);
|
||||
}
|
||||
|
||||
static void app_input_callback(InputEvent* input_event, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
BadAppleEvent event = {.type = BadAppleEventTypeInput, .input = input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
BadAppleCtx* bad_apple_ctx_alloc(void) {
|
||||
BadAppleCtx* inst = malloc(sizeof(BadAppleCtx));
|
||||
memset(inst, 0, sizeof(BadAppleCtx));
|
||||
|
||||
if(inst) {
|
||||
inst->storage = furi_record_open(RECORD_STORAGE);
|
||||
inst->video_file = storage_file_alloc(inst->storage);
|
||||
inst->file_buffer_offset = sizeof(inst->file_buffer);
|
||||
}
|
||||
|
||||
return inst;
|
||||
}
|
||||
|
||||
void bad_apple_ctx_free(BadAppleCtx* inst) {
|
||||
if(inst) {
|
||||
storage_file_free(inst->video_file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
free(inst);
|
||||
}
|
||||
}
|
||||
|
||||
void bad_apple_load_next_video_chunk(BadAppleCtx* inst) {
|
||||
size_t bytes_to_read = sizeof(inst->file_buffer);
|
||||
uint8_t* buf_ptr = inst->file_buffer;
|
||||
while(bytes_to_read > 0) {
|
||||
uint16_t curr_bytes_to_read = bytes_to_read > (UINT16_MAX / 2 + 1) ? (UINT16_MAX / 2 + 1) :
|
||||
bytes_to_read;
|
||||
uint16_t read = storage_file_read(inst->video_file, buf_ptr, curr_bytes_to_read);
|
||||
bytes_to_read -= read;
|
||||
buf_ptr += read;
|
||||
if(read == 0) break;
|
||||
}
|
||||
inst->file_buffer_offset = 0;
|
||||
}
|
||||
|
||||
uint8_t bad_apple_read_byte(BadAppleCtx* inst) {
|
||||
if(inst->file_buffer_offset >= sizeof(inst->file_buffer)) {
|
||||
bad_apple_load_next_video_chunk(inst);
|
||||
}
|
||||
return inst->file_buffer[inst->file_buffer_offset++];
|
||||
}
|
||||
|
||||
void bad_apple_timer_isr(void* ctx) {
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
BadAppleEvent event = {.type = BadAppleEventTypeTick};
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
LL_TIM_ClearFlag_UPDATE(TIM2);
|
||||
}
|
||||
|
||||
void bad_apple_timer_setup(BadAppleCtx* inst, void* ctx) {
|
||||
UNUSED(inst);
|
||||
|
||||
LL_TIM_InitTypeDef tim_init = {
|
||||
.Prescaler = 63999,
|
||||
.CounterMode = LL_TIM_COUNTERMODE_UP,
|
||||
.Autoreload = 30,
|
||||
};
|
||||
|
||||
LL_TIM_Init(TIM2, &tim_init);
|
||||
LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL);
|
||||
LL_TIM_DisableCounter(TIM2);
|
||||
LL_TIM_SetCounter(TIM2, 0);
|
||||
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, bad_apple_timer_isr, ctx);
|
||||
LL_TIM_EnableIT_UPDATE(TIM2);
|
||||
}
|
||||
|
||||
void bad_apple_timer_deinit(void) {
|
||||
LL_TIM_DisableCounter(TIM2);
|
||||
LL_TIM_DisableIT_UPDATE(TIM2);
|
||||
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL);
|
||||
LL_TIM_DeInit(TIM2);
|
||||
}
|
||||
|
||||
int32_t bad_apple_main(void* p) {
|
||||
UNUSED(p);
|
||||
BadAppleCtx* ctx = bad_apple_ctx_alloc();
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(BadAppleEvent));
|
||||
|
||||
// Configure view port
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, app_draw_callback, ctx);
|
||||
view_port_input_callback_set(view_port, app_input_callback, event_queue);
|
||||
|
||||
// Register view port in GUI
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// Frame rate: 32 FPS
|
||||
bad_apple_timer_setup(ctx, event_queue);
|
||||
|
||||
bool is_opened = storage_file_open(ctx->video_file, VIDEO_PATH, FSAM_READ, FSOM_OPEN_EXISTING);
|
||||
if(is_opened) {
|
||||
BadAppleEvent event;
|
||||
notification_message(notification, &sequence_display_backlight_enforce_on);
|
||||
|
||||
bool running = true;
|
||||
LL_TIM_EnableCounter(TIM2);
|
||||
while(running) {
|
||||
if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) {
|
||||
if(event.type == BadAppleEventTypeInput) {
|
||||
InputEvent* input_event = event.input;
|
||||
if(input_event->type == InputTypeLong) {
|
||||
if(input_event->key == InputKeyBack) {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
} else if(event.type == BadAppleEventTypeTick) {
|
||||
// FURI_LOG_D(TAG, "Update frame");
|
||||
if(!vp_play_frame(ctx)) {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
view_port_update(view_port);
|
||||
}
|
||||
LL_TIM_DisableCounter(TIM2);
|
||||
notification_message(notification, &sequence_display_backlight_enforce_auto);
|
||||
storage_file_close(ctx->video_file);
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
bad_apple_timer_deinit();
|
||||
bad_apple_ctx_free(ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 64
|
||||
#define VIDEO_WIDTH 104
|
||||
#define VIDEO_HEIGHT 80
|
||||
#define FILE_BUFFER_SIZE (1024 * 64)
|
||||
#define VIDEO_PATH EXT_PATH("apps_data/bad_apple/video.bin")
|
||||
#define VIDEO_X ((SCREEN_WIDTH - VIDEO_WIDTH) / 2)
|
||||
#define VIDEO_Y 0
|
||||
|
||||
typedef struct {
|
||||
uint8_t framebuffer[VIDEO_HEIGHT * VIDEO_WIDTH / 8];
|
||||
uint8_t file_buffer[FILE_BUFFER_SIZE];
|
||||
uint32_t file_buffer_offset;
|
||||
uint32_t frame_write_offset; // bit index
|
||||
Storage* storage;
|
||||
File* video_file;
|
||||
} BadAppleCtx;
|
||||
|
||||
uint8_t bad_apple_read_byte(BadAppleCtx* inst);
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,137 @@
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include "bad_apple.h"
|
||||
#include "video_player.h"
|
||||
|
||||
#define TAG "video_player"
|
||||
|
||||
static void vp_decode_rle(BadAppleCtx* ctx);
|
||||
static void vp_decode_delta(BadAppleCtx* ctx);
|
||||
|
||||
int vp_play_frame(BadAppleCtx* ctx) {
|
||||
// Check frame type
|
||||
// FURI_LOG_D(TAG, "Buffer offset: %04lx", ctx->file_buffer_offset);
|
||||
uint8_t b = bad_apple_read_byte(ctx);
|
||||
// FURI_LOG_D(TAG, "Frame byte: %02x", b);
|
||||
switch(b) {
|
||||
case 1: // PFrame (delta)
|
||||
vp_decode_delta(ctx);
|
||||
break;
|
||||
case 2: // IFrame (rle)
|
||||
vp_decode_rle(ctx);
|
||||
break;
|
||||
case 3: // DFrame (duplicate)
|
||||
break;
|
||||
case 4: // next page (not supported)
|
||||
case 5: // end
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline void vp_write_pixel(BadAppleCtx* ctx, bool color) {
|
||||
if(color) {
|
||||
// White
|
||||
ctx->framebuffer[ctx->frame_write_offset / 8] &= ~(1 << (ctx->frame_write_offset % 8));
|
||||
} else {
|
||||
// Black
|
||||
ctx->framebuffer[ctx->frame_write_offset / 8] |= (1 << (ctx->frame_write_offset % 8));
|
||||
}
|
||||
++ctx->frame_write_offset;
|
||||
}
|
||||
|
||||
static inline void vp_set_rect_fast(BadAppleCtx* ctx, int x, int y) {
|
||||
ctx->frame_write_offset = y * VIDEO_WIDTH + x;
|
||||
}
|
||||
|
||||
static void vp_decode_rle(BadAppleCtx* ctx) {
|
||||
int i = 0;
|
||||
int repeat_byte = bad_apple_read_byte(ctx);
|
||||
|
||||
// Set rect to video area
|
||||
vp_set_rect_fast(ctx, 0, 0);
|
||||
|
||||
while(i < VIDEO_WIDTH * VIDEO_HEIGHT) {
|
||||
int count;
|
||||
unsigned pixels;
|
||||
int j;
|
||||
int b = bad_apple_read_byte(ctx);
|
||||
|
||||
if(b == repeat_byte) {
|
||||
count = bad_apple_read_byte(ctx);
|
||||
if(count == 0) count = 256;
|
||||
pixels = bad_apple_read_byte(ctx);
|
||||
} else {
|
||||
count = 1;
|
||||
pixels = (unsigned)b;
|
||||
}
|
||||
|
||||
for(j = 0; j < count; ++j) {
|
||||
bool out_color;
|
||||
int k;
|
||||
unsigned loop_pixels = pixels;
|
||||
|
||||
for(k = 0; k < 8; ++k) {
|
||||
if(loop_pixels & 0x80)
|
||||
out_color = COLOR_WHITE;
|
||||
else
|
||||
out_color = COLOR_BLACK;
|
||||
|
||||
vp_write_pixel(ctx, out_color);
|
||||
loop_pixels <<= 1;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void vp_decode_delta(BadAppleCtx* ctx) {
|
||||
int frame_header[(VIDEO_HEIGHT + 7) / 8];
|
||||
uint i;
|
||||
int fh_byte = 0;
|
||||
int fh_index = 0;
|
||||
|
||||
for(i = 0; i < sizeof(frame_header) / sizeof(frame_header[0]); ++i) {
|
||||
frame_header[i] = bad_apple_read_byte(ctx);
|
||||
}
|
||||
|
||||
for(i = 0; i < VIDEO_HEIGHT; ++i) {
|
||||
if(i % 8 == 0) fh_byte = frame_header[fh_index++];
|
||||
|
||||
if(fh_byte & 0x80) {
|
||||
int j;
|
||||
int sl_byte = 0;
|
||||
|
||||
for(j = 0; j < VIDEO_WIDTH;) {
|
||||
if(j % (8 * 8) == 0) sl_byte = bad_apple_read_byte(ctx);
|
||||
|
||||
if(sl_byte & 0x80) {
|
||||
unsigned out_color;
|
||||
int k;
|
||||
unsigned pixel_group = bad_apple_read_byte(ctx);
|
||||
|
||||
// Note: this needs to be revised for screen width not multiple of 8
|
||||
vp_set_rect_fast(ctx, j, i);
|
||||
|
||||
for(k = 0; k < 8 && j < VIDEO_WIDTH; ++k, ++j) {
|
||||
if(pixel_group & 0x80)
|
||||
out_color = COLOR_WHITE;
|
||||
else
|
||||
out_color = COLOR_BLACK;
|
||||
|
||||
vp_write_pixel(ctx, out_color);
|
||||
pixel_group <<= 1;
|
||||
}
|
||||
} else {
|
||||
j += 8;
|
||||
}
|
||||
sl_byte <<= 1;
|
||||
}
|
||||
}
|
||||
fh_byte <<= 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#define COLOR_WHITE true
|
||||
#define COLOR_BLACK false
|
||||
|
||||
int vp_play_frame(BadAppleCtx* ctx);
|
||||
Reference in New Issue
Block a user