SatCam/STM32_mini/Src/sstv.c

272 wiersze
9.2 KiB
C

/*************************************************************************
*
* SatCam - Camera Module for PSAT-2
* Copyright (c) 2015-2017 Ales Povalac <alpov@alpov.net>
* Dept. of Radio Electronics, Brno University of Technology
*
* This work is licensed under the terms of the MIT license
*
*************************************************************************/
#include "cube.h"
#include "audio.h"
#include "comm.h"
#include "tjpgd.h"
#include "eeprom.h"
#include "sstv.h"
// JPEG decompression engine variables
static uint8_t workspace[3100] __attribute__ ((aligned(4)));
static uint8_t *jpeg_data;
static uint16_t jpeg_pos;
// for JPEG decompression: 320*16*3 = 15360 bytes
// for complete thumbnail: 80*60*3 = 14400 bytes
static uint8_t image_buffer[IMG_WIDTH*IMG_HEIGHT*3];
// overlay text buffer: 4 * up to 39 chars + trailing zero
static char text_buffer[4][TEXT_LEN];
static uint8_t sstv_mode;
IMPORT_BIN("Inc/8x13B.fnt", uint8_t, Font8x13B);
static bool sstv_audio_callback(uint8_t *buffer, uint8_t line);
/* User defined call-back function to input JPEG data */
static UINT tjd_input(JDEC* jd, uint8_t* buff, UINT nd)
{
if (buff) memcpy(buff, &jpeg_data[jpeg_pos], nd);
jpeg_pos += nd;
return nd;
}
/* User defined call-back function to output RGB bitmap */
static UINT tjd_thumbnail_output(JDEC* jd, void* bitmap, JRECT* rect)
{
uint8_t *src, *dst;
uint16_t y, bws, bwd;
/* Copy the decompressed RGB rectangular to the frame buffer (assuming RGB888 cfg) */
src = (uint8_t*)bitmap;
dst = image_buffer + 3 * (rect->top * (IMG_WIDTH/4) + rect->left); /* Left-top of destination rectangular */
bws = 3 * (rect->right - rect->left + 1); /* Width of source rectangular [byte] */
bwd = 3 * (IMG_WIDTH/4); /* Width of frame buffer [byte] */
for (y = rect->top; y <= rect->bottom; y++) {
memcpy(dst, src, bws); /* Copy a line */
src += bws; dst += bwd; /* Next line */
}
return 1; /* Continue to decompress */
}
/* User defined call-back function to output RGB bitmap */
static UINT tjd_full_output(JDEC* jd, void* bitmap, JRECT* rect)
{
uint8_t *src, *dst;
uint16_t y, bws, bwd;
/* Copy the decompressed RGB rectangular to the frame buffer (assuming RGB888 cfg) */
src = (uint8_t*)bitmap;
dst = image_buffer + 3 * ((rect->top % IMG_HEIGHT) * IMG_WIDTH + rect->left); /* Left-top of destination rectangular */
bws = 3 * (rect->right - rect->left + 1); /* Width of source rectangular [byte] */
bwd = 3 * IMG_WIDTH; /* Width of frame buffer [byte] */
for (y = rect->top; y <= rect->bottom; y++) {
memcpy(dst, src, bws); /* Copy a line */
src += bws; dst += bwd; /* Next line */
}
/* execute callback when line block finished */
if ((rect->bottom % IMG_HEIGHT) == (IMG_HEIGHT - 1) && rect->right == (IMG_WIDTH - 1)) {
return sstv_audio_callback(image_buffer, (rect->bottom / IMG_HEIGHT) + 1) ? 1 : 0;
}
return 1; /* Continue to decompress */
}
bool jpeg_thumbnail(uint8_t *jpeg, uint8_t **thumbnail)
{
/* prepare variables */
bool ok = true;
JDEC jdec;
jpeg_data = jpeg;
jpeg_pos = 0;
/* decompression */
if (ok && jd_prepare(&jdec, tjd_input, workspace, sizeof(workspace), NULL) != JDR_OK) ok = false;
if (ok && jdec.width != IMG_WIDTH) ok = false;
if (ok && jd_decomp(&jdec, tjd_thumbnail_output, 2) != JDR_OK) ok = false;
if (thumbnail != NULL)
*thumbnail = image_buffer;
if (!ok) syslog_event(LOG_JPEG_ERROR);
return ok;
}
bool jpeg_decompress(uint8_t *jpeg)
{
/* prepare variables */
bool ok = true;
JDEC jdec;
jpeg_data = jpeg;
jpeg_pos = 0;
/* decompression */
if (ok && jd_prepare(&jdec, tjd_input, workspace, sizeof(workspace), NULL) != JDR_OK) ok = false;
if (ok && jdec.width != IMG_WIDTH) ok = false;
if (ok && jd_decomp(&jdec, tjd_full_output, 0) != JDR_OK) ok = false;
if (!ok) syslog_event(LOG_JPEG_ERROR);
return true;
}
bool jpeg_test(uint8_t *jpeg, uint32_t length)
{
return jpeg_thumbnail(jpeg, NULL);
}
static void sstv_do_overlay(uint8_t *s, uint32_t color, uint8_t zoom, uint8_t part)
{
uint16_t h = Font8x13B[15]; /* Font size: height */
uint16_t w = Font8x13B[14]; /* Font size: width */
uint16_t x = 3; /* X offset */
while (x <= IMG_WIDTH - w*zoom) {
uint8_t chr = *s++; /* Load character */
if (chr >= 31 && chr <= 127) {
const uint8_t *fnt = Font8x13B; /* Load font */
fnt += 17 + chr * h; /* Goto start of the bitmap */
for (uint16_t i = 0; i < IMG_HEIGHT; i++) { /* Go through Y axis */
uint8_t chr_line = i/zoom + part*(IMG_HEIGHT/zoom);
if (chr_line < h) { /* Is current line mapped in font face? */
uint8_t d = fnt[chr_line]; /* Get next 8 horizontal dots */
for (uint16_t j = 0; j < w*zoom; j++) { /* Go through X axis */
uint16_t idx = i*IMG_WIDTH + (x+j);
if (zoom == 1) idx += IMG_WIDTH; /* No zoom -> shift line by 1px down */
if (d & 0x80) { /* color */
image_buffer[idx*3+0] = (color >> 16) & 0xFF;
image_buffer[idx*3+1] = (color >> 8) & 0xFF;
image_buffer[idx*3+2] = (color >> 0) & 0xFF;
} else { /* lower brightness */
image_buffer[idx*3+0] >>= 1;
image_buffer[idx*3+1] >>= 1;
image_buffer[idx*3+2] >>= 1;
}
if (j % zoom == zoom - 1) d <<= 1; /* Next horizontal bit */
}
}
}
}
x += w*zoom; /* Next character */
}
}
static bool sstv_audio_callback(uint8_t *buffer, uint8_t line)
{
// modes with 15 text lines
if (sstv_mode == 36 || sstv_mode == 72) {
// overlay basic white chars, no zoom
if (line == 1) sstv_do_overlay(text_buffer[OVERLAY_HEADER], 0xFFFFFF, 1, 0);
if (line == 15) sstv_do_overlay(text_buffer[OVERLAY_IMG], 0xFFFFFF, 1, 0);
// overlay up to 13 yellow chars on lines 3-5, zoom 3x
if (line == 3 || line == 4 || line == 5) sstv_do_overlay(text_buffer[OVERLAY_LARGE], 0xFFFF00, 3, line-3);
// overlay up to 19 red chars on lines 14-15, zoom 2x
if (line == 14 || line == 15) sstv_do_overlay(text_buffer[OVERLAY_FROM], 0xFF4040, 2, line-14);
}
// modes with 16 text lines
else if (sstv_mode == 73 || sstv_mode == 115) {
// overlay basic white chars, no zoom
if (line == 0) sstv_do_overlay(text_buffer[OVERLAY_HEADER], 0xFFFFFF, 1, 0);
if (line == 15) sstv_do_overlay(text_buffer[OVERLAY_IMG], 0xFFFFFF, 1, 0);
// overlay up to 13 yellow chars on lines 2-4, zoom 3x
if (line == 2 || line == 3 || line == 4) sstv_do_overlay(text_buffer[OVERLAY_LARGE], 0xFFFF00, 3, line-2);
// overlay up to 19 red chars on lines 14-15, zoom 2x
if (line == 14 || line == 15) sstv_do_overlay(text_buffer[OVERLAY_FROM], 0xFF4040, 2, line-14);
}
// send audio block
if (sstv_mode == 36) audio_robot36_color(buffer);
else if (sstv_mode == 72) audio_robot72_color(buffer);
else if (sstv_mode == 73) audio_mp73(buffer);
else if (sstv_mode == 115) audio_mp115(buffer);
return true; // continue
}
bool sstv_play_jpeg(uint8_t* jpeg, uint8_t mode)
{
bool ok = true;
/* check valid SSTV mode, default to Robot36 */
if (mode == 72 || mode == 73 || mode == 115) sstv_mode = mode;
else sstv_mode = 36;
audio_start();
audio_play_vox_start();
if (sstv_mode == 36) audio_play_vis(0x88); // Robot36
else if (sstv_mode == 72) audio_play_vis(0x0C); // Robot72
else if (sstv_mode == 73) audio_play_vis16(0x2523); // MP73
else if (sstv_mode == 115) audio_play_vis16(0x2923); // MP115
if (sstv_mode == 73 || sstv_mode == 115) {
// black header on line 0 for MP modes
memset(image_buffer, 0, sizeof(image_buffer));
ok = sstv_audio_callback(image_buffer, 0);
}
if (ok && jpeg != NULL) {
ok = jpeg_decompress(jpeg);
}
audio_play_vox_stop();
audio_stop();
return ok;
}
void sstv_set_overlay(uint8_t line, const char *overlay)
{
char s[TEXT_LEN];
if (overlay == NULL) s[0] = '\0';
else strncpy(s, overlay, TEXT_LEN);
switch (line) {
case OVERLAY_HEADER:
case OVERLAY_IMG:
if (strlen(overlay) > TEXT_Z1_WIDTH) s[TEXT_Z1_WIDTH] = '\0';
memset(text_buffer[line], 0x00, TEXT_Z1_WIDTH);
strcpy(text_buffer[line], s);
break;
case OVERLAY_LARGE:
if (strlen(overlay) > TEXT_Z3_WIDTH) s[TEXT_Z3_WIDTH] = '\0';
memset(text_buffer[line], 0x00, TEXT_Z3_WIDTH);
strcpy(text_buffer[line], s);
break;
case OVERLAY_FROM:
if (strlen(overlay) > TEXT_Z2_WIDTH) s[TEXT_Z2_WIDTH] = '\0';
memset(text_buffer[line], 0xFF, TEXT_Z2_WIDTH); // blanks for right alignment
strcpy(text_buffer[line] + (TEXT_Z2_WIDTH-strlen(s)), s);
break;
}
printf_debug("Overlay %d = '%s'", line, s);
}