Add a quality option to SSDV. Provides 8 quality levels, -4 to 3.

This commit is contained in:
Philip Heron 2016-06-16 23:29:30 +01:00
parent e6a3cfbe42
commit b7128b16be
3 changed files with 86 additions and 14 deletions

9
main.c
View File

@ -26,7 +26,7 @@
void exit_usage() void exit_usage()
{ {
fprintf(stderr, fprintf(stderr,
"Usage: ssdv [-e|-d] [-n] [-t <percentage>] [-c <callsign>] [-i <id>] [<in file>] [<out file>]\n" "Usage: ssdv [-e|-d] [-n] [-t <percentage>] [-c <callsign>] [-i <id>] [-q <level>] [<in file>] [<out file>]\n"
"\n" "\n"
" -e Encode JPEG to SSDV packets.\n" " -e Encode JPEG to SSDV packets.\n"
" -d Decode SSDV packets to JPEG.\n" " -d Decode SSDV packets to JPEG.\n"
@ -35,6 +35,7 @@ void exit_usage()
" -t For testing, drops the specified percentage of packets while decoding.\n" " -t For testing, drops the specified percentage of packets while decoding.\n"
" -c Set the callign. Accepts A-Z 0-9 and space, up to 6 characters.\n" " -c Set the callign. Accepts A-Z 0-9 and space, up to 6 characters.\n"
" -i Set the image ID (0-255).\n" " -i Set the image ID (0-255).\n"
" -q Set the JPEG quality level (-4 to 3, defaults to 0).\n"
" -v Print data for each packet decoded.\n" " -v Print data for each packet decoded.\n"
"\n"); "\n");
exit(-1); exit(-1);
@ -52,6 +53,7 @@ int main(int argc, char *argv[])
int errors; int errors;
char callsign[7]; char callsign[7];
uint8_t image_id = 0; uint8_t image_id = 0;
int8_t quality;
ssdv_t ssdv; ssdv_t ssdv;
uint8_t pkt[SSDV_PKT_SIZE], b[128], *jpeg; uint8_t pkt[SSDV_PKT_SIZE], b[128], *jpeg;
@ -60,7 +62,7 @@ int main(int argc, char *argv[])
callsign[0] = '\0'; callsign[0] = '\0';
opterr = 0; opterr = 0;
while((c = getopt(argc, argv, "ednc:i:t:v")) != -1) while((c = getopt(argc, argv, "ednc:i:q:t:v")) != -1)
{ {
switch(c) switch(c)
{ {
@ -73,6 +75,7 @@ int main(int argc, char *argv[])
strncpy(callsign, optarg, 7); strncpy(callsign, optarg, 7);
break; break;
case 'i': image_id = atoi(optarg); break; case 'i': image_id = atoi(optarg); break;
case 'q': quality = atoi(optarg); break;
case 't': droptest = atoi(optarg); break; case 't': droptest = atoi(optarg); break;
case 'v': verbose = 1; break; case 'v': verbose = 1; break;
case '?': exit_usage(); case '?': exit_usage();
@ -166,7 +169,7 @@ int main(int argc, char *argv[])
break; break;
case 1: /* Encode */ case 1: /* Encode */
ssdv_enc_init(&ssdv, type, callsign, image_id); ssdv_enc_init(&ssdv, type, callsign, image_id, quality);
ssdv_enc_set_buffer(&ssdv, pkt); ssdv_enc_set_buffer(&ssdv, pkt);
i = 0; i = 0;

87
ssdv.c
View File

@ -45,6 +45,11 @@ static const uint8_t const sos[10] = {
0x03,0x01,0x00,0x02,0x11,0x03,0x11,0x00,0x3F,0x00, 0x03,0x01,0x00,0x02,0x11,0x03,0x11,0x00,0x3F,0x00,
}; };
/* Quantisation table scaling factors for each quality level 0-7 */
static const uint16_t const dqt_scales[8] = {
5000, 357, 172, 116, 100, 58, 28, 0
};
/* Quantisation tables */ /* Quantisation tables */
static const uint8_t const std_dqt0[65] = { static const uint8_t const std_dqt0[65] = {
0x00,0x10,0x0C,0x0C,0x0E,0x0C,0x0A,0x10,0x0E,0x0E,0x0E,0x12,0x12,0x10,0x14,0x18, 0x00,0x10,0x0C,0x0C,0x0E,0x0C,0x0A,0x10,0x0E,0x0E,0x0E,0x12,0x12,0x10,0x14,0x18,
@ -135,6 +140,61 @@ static char *strbits(uint32_t value, uint8_t bits)
} }
*/ */
static void load_standard_dqt(uint8_t *dst, const uint8_t *table, uint8_t quality)
{
int i;
uint16_t scale_factor;
uint32_t temp;
/* Copy the table ID */
*dst++ = *table++;
/* Load the scaling factor */
if(quality > 7) quality = 7;
scale_factor = dqt_scales[quality];
/* Copy the remaining 64 coefficients, while applying the scaling factor */
for(i = 0; i < 64; i++)
{
temp = *table++;
temp = (temp * scale_factor + 50) / 100;
/* limit the values to the valid range */
if(temp == 0) temp = 1;
if(temp > 255) temp = 255;
*dst++ = temp;
}
}
static void *sload_standard_dqt(ssdv_t *s, const uint8_t *table, uint8_t quality)
{
uint8_t *r;
/* DQT is 65 bytes long, ensure there is space */
if(s->stbl_len + 65 > TBL_LEN + HBUFF_LEN) return(NULL);
r = &s->stbls[s->stbl_len];
load_standard_dqt(r, table, quality);
s->stbl_len += 65;
return(r);
}
static void *dload_standard_dqt(ssdv_t *s, const uint8_t *table, uint8_t quality)
{
uint8_t *r;
/* DQT is 65 bytes long, ensure there is space */
if(s->dtbl_len + 65 > TBL_LEN + HBUFF_LEN) return(NULL);
r = &s->dtbls[s->dtbl_len];
load_standard_dqt(r, table, quality);
s->dtbl_len += 65;
return(r);
}
static void *stblcpy(ssdv_t *s, const void *src, size_t n) static void *stblcpy(ssdv_t *s, const void *src, size_t n)
{ {
void *r; void *r;
@ -831,18 +891,19 @@ static char ssdv_have_marker_data(ssdv_t *s)
return(SSDV_OK); return(SSDV_OK);
} }
char ssdv_enc_init(ssdv_t *s, uint8_t type, char *callsign, uint8_t image_id) char ssdv_enc_init(ssdv_t *s, uint8_t type, char *callsign, uint8_t image_id, int8_t quality)
{ {
memset(s, 0, sizeof(ssdv_t)); memset(s, 0, sizeof(ssdv_t));
s->image_id = image_id; s->image_id = image_id;
s->callsign = encode_callsign(callsign); s->callsign = encode_callsign(callsign);
s->mode = S_ENCODING; s->mode = S_ENCODING;
s->type = type; s->type = type;
s->quality = 4 + quality; /* Quality levels are -4 to 3, adjust to 0-7 */
ssdv_set_packet_conf(s); ssdv_set_packet_conf(s);
/* Prepare the output JPEG tables */ /* Prepare the output JPEG tables */
s->ddqt[0] = dtblcpy(s, std_dqt0, sizeof(std_dqt0)); s->ddqt[0] = dload_standard_dqt(s, std_dqt0, s->quality);
s->ddqt[1] = dtblcpy(s, std_dqt1, sizeof(std_dqt1)); s->ddqt[1] = dload_standard_dqt(s, std_dqt1, s->quality);
s->ddht[0][0] = dtblcpy(s, std_dht00, sizeof(std_dht00)); s->ddht[0][0] = dtblcpy(s, std_dht00, sizeof(std_dht00));
s->ddht[0][1] = dtblcpy(s, std_dht01, sizeof(std_dht01)); s->ddht[0][1] = dtblcpy(s, std_dht01, sizeof(std_dht01));
s->ddht[1][0] = dtblcpy(s, std_dht10, sizeof(std_dht10)); s->ddht[1][0] = dtblcpy(s, std_dht10, sizeof(std_dht10));
@ -972,6 +1033,7 @@ char ssdv_enc_get_packet(ssdv_t *s)
s->out[9] = s->width >> 4; /* Width / 16 */ s->out[9] = s->width >> 4; /* Width / 16 */
s->out[10] = s->height >> 4; /* Height / 16 */ s->out[10] = s->height >> 4; /* Height / 16 */
s->out[11] = 0x00; s->out[11] = 0x00;
s->out[11] |= s->quality << 3; /* Quality level */
s->out[11] |= (r == SSDV_EOI ? 1 : 0) << 2; /* EOI flag (1 bit) */ s->out[11] |= (r == SSDV_EOI ? 1 : 0) << 2; /* EOI flag (1 bit) */
s->out[11] |= s->mcu_mode & 0x03; /* MCU mode (2 bits) */ s->out[11] |= s->mcu_mode & 0x03; /* MCU mode (2 bits) */
s->out[12] = mcu_offset; /* Next MCU offset */ s->out[12] = mcu_offset; /* Next MCU offset */
@ -1044,8 +1106,8 @@ static void ssdv_out_headers(ssdv_t *s)
ssdv_write_marker(s, J_SOI, 0, 0); ssdv_write_marker(s, J_SOI, 0, 0);
ssdv_write_marker(s, J_APP0, 14, app0); ssdv_write_marker(s, J_APP0, 14, app0);
ssdv_write_marker(s, J_DQT, 65, std_dqt0); /* DQT Luminance */ ssdv_write_marker(s, J_DQT, 65, s->ddqt[0]); /* DQT Luminance */
ssdv_write_marker(s, J_DQT, 65, std_dqt1); /* DQT Chrominance */ ssdv_write_marker(s, J_DQT, 65, s->ddqt[1]); /* DQT Chrominance */
/* Build SOF0 header */ /* Build SOF0 header */
b[0] = 8; /* Precision */ b[0] = 8; /* Precision */
@ -1124,16 +1186,12 @@ char ssdv_dec_init(ssdv_t *s)
s->mode = S_DECODING; s->mode = S_DECODING;
/* Prepare the source JPEG tables */ /* Prepare the source JPEG tables */
s->sdqt[0] = stblcpy(s, std_dqt0, sizeof(std_dqt0));
s->sdqt[1] = stblcpy(s, std_dqt1, sizeof(std_dqt1));
s->sdht[0][0] = stblcpy(s, std_dht00, sizeof(std_dht00)); s->sdht[0][0] = stblcpy(s, std_dht00, sizeof(std_dht00));
s->sdht[0][1] = stblcpy(s, std_dht01, sizeof(std_dht01)); s->sdht[0][1] = stblcpy(s, std_dht01, sizeof(std_dht01));
s->sdht[1][0] = stblcpy(s, std_dht10, sizeof(std_dht10)); s->sdht[1][0] = stblcpy(s, std_dht10, sizeof(std_dht10));
s->sdht[1][1] = stblcpy(s, std_dht11, sizeof(std_dht11)); s->sdht[1][1] = stblcpy(s, std_dht11, sizeof(std_dht11));
/* Prepare the output JPEG tables */ /* Prepare the output JPEG tables */
s->ddqt[0] = dtblcpy(s, std_dqt0, sizeof(std_dqt0));
s->ddqt[1] = dtblcpy(s, std_dqt1, sizeof(std_dqt1));
s->ddht[0][0] = dtblcpy(s, std_dht00, sizeof(std_dht00)); s->ddht[0][0] = dtblcpy(s, std_dht00, sizeof(std_dht00));
s->ddht[0][1] = dtblcpy(s, std_dht01, sizeof(std_dht01)); s->ddht[0][1] = dtblcpy(s, std_dht01, sizeof(std_dht01));
s->ddht[1][0] = dtblcpy(s, std_dht10, sizeof(std_dht10)); s->ddht[1][0] = dtblcpy(s, std_dht10, sizeof(std_dht10));
@ -1182,12 +1240,19 @@ char ssdv_dec_feed(ssdv_t *s, uint8_t *packet)
s->width = packet[9] << 4; s->width = packet[9] << 4;
s->height = packet[10] << 4; s->height = packet[10] << 4;
s->mcu_count = packet[9] * packet[10]; s->mcu_count = packet[9] * packet[10];
s->quality = (packet[11] >> 3) & 7;
s->mcu_mode = packet[11] & 0x03; s->mcu_mode = packet[11] & 0x03;
/* Configure the payload size and CRC position */ /* Configure the payload size and CRC position */
ssdv_set_packet_conf(s); ssdv_set_packet_conf(s);
switch(s->mcu_mode) /* Generate the DQT tables */
s->sdqt[0] = sload_standard_dqt(s, std_dqt0, s->quality);
s->sdqt[1] = sload_standard_dqt(s, std_dqt1, s->quality);
s->ddqt[0] = dload_standard_dqt(s, std_dqt0, s->quality);
s->ddqt[1] = dload_standard_dqt(s, std_dqt1, s->quality);
switch(s->mcu_mode & 3)
{ {
case 0: factor = "2x2"; s->ycparts = 4; break; case 0: factor = "2x2"; s->ycparts = 4; break;
case 1: factor = "1x2"; s->ycparts = 2; s->mcu_count *= 2; break; case 1: factor = "1x2"; s->ycparts = 2; s->mcu_count *= 2; break;
@ -1201,6 +1266,7 @@ char ssdv_dec_feed(ssdv_t *s, uint8_t *packet)
fprintf(stderr, "Resolution: %ix%i\n", s->width, s->height); fprintf(stderr, "Resolution: %ix%i\n", s->width, s->height);
fprintf(stderr, "MCU blocks: %i\n", s->mcu_count); fprintf(stderr, "MCU blocks: %i\n", s->mcu_count);
fprintf(stderr, "Sampling factor: %s\n", factor); fprintf(stderr, "Sampling factor: %s\n", factor);
fprintf(stderr, "Quality level: %d\n", s->quality - 4);
/* Output JPEG headers and enable byte stuffing */ /* Output JPEG headers and enable byte stuffing */
ssdv_out_headers(s); ssdv_out_headers(s);
@ -1398,6 +1464,7 @@ void ssdv_dec_header(ssdv_packet_info_t *info, uint8_t *packet)
info->width = packet[9] << 4; info->width = packet[9] << 4;
info->height = packet[10] << 4; info->height = packet[10] << 4;
info->eoi = (packet[11] >> 2) & 1; info->eoi = (packet[11] >> 2) & 1;
info->quality = (packet[11] >> 3) & 7;
info->mcu_mode = packet[11] & 0x03; info->mcu_mode = packet[11] & 0x03;
info->mcu_offset = packet[12]; info->mcu_offset = packet[12];
info->mcu_id = (packet[13] << 8) | packet[14]; info->mcu_id = (packet[13] << 8) | packet[14];

4
ssdv.h
View File

@ -63,6 +63,7 @@ typedef struct
uint8_t mcu_mode; /* 0 = 2x2, 1 = 2x1, 2 = 1x2, 3 = 1x1 */ uint8_t mcu_mode; /* 0 = 2x2, 1 = 2x1, 2 = 1x2, 3 = 1x1 */
uint16_t mcu_id; uint16_t mcu_id;
uint16_t mcu_count; uint16_t mcu_count;
uint8_t quality; /* JPEG quality level for encoding, 0-7 */
uint16_t packet_mcu_id; uint16_t packet_mcu_id;
uint8_t packet_mcu_offset; uint8_t packet_mcu_offset;
@ -135,6 +136,7 @@ typedef struct {
uint16_t width; uint16_t width;
uint16_t height; uint16_t height;
uint8_t eoi; uint8_t eoi;
uint8_t quality;
uint16_t mcu_mode; uint16_t mcu_mode;
uint8_t mcu_offset; uint8_t mcu_offset;
uint16_t mcu_id; uint16_t mcu_id;
@ -142,7 +144,7 @@ typedef struct {
} ssdv_packet_info_t; } ssdv_packet_info_t;
/* Encoding */ /* Encoding */
extern char ssdv_enc_init(ssdv_t *s, uint8_t type, char *callsign, uint8_t image_id); extern char ssdv_enc_init(ssdv_t *s, uint8_t type, char *callsign, uint8_t image_id, int8_t quality);
extern char ssdv_enc_set_buffer(ssdv_t *s, uint8_t *buffer); extern char ssdv_enc_set_buffer(ssdv_t *s, uint8_t *buffer);
extern char ssdv_enc_get_packet(ssdv_t *s); extern char ssdv_enc_get_packet(ssdv_t *s);
extern char ssdv_enc_feed(ssdv_t *s, uint8_t *buffer, size_t length); extern char ssdv_enc_feed(ssdv_t *s, uint8_t *buffer, size_t length);