diff --git a/main.c b/main.c index 97f69f1..e1d1dd1 100644 --- a/main.c +++ b/main.c @@ -26,7 +26,7 @@ void exit_usage() { fprintf(stderr, - "Usage: ssdv [-e|-d] [-n] [-t ] [-c ] [-i ] [] []\n" + "Usage: ssdv [-e|-d] [-n] [-t ] [-c ] [-i ] [-q ] [] []\n" "\n" " -e Encode JPEG to SSDV packets.\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" " -c Set the callign. Accepts A-Z 0-9 and space, up to 6 characters.\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" "\n"); exit(-1); @@ -52,6 +53,7 @@ int main(int argc, char *argv[]) int errors; char callsign[7]; uint8_t image_id = 0; + int8_t quality; ssdv_t ssdv; uint8_t pkt[SSDV_PKT_SIZE], b[128], *jpeg; @@ -60,7 +62,7 @@ int main(int argc, char *argv[]) callsign[0] = '\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) { @@ -73,6 +75,7 @@ int main(int argc, char *argv[]) strncpy(callsign, optarg, 7); break; case 'i': image_id = atoi(optarg); break; + case 'q': quality = atoi(optarg); break; case 't': droptest = atoi(optarg); break; case 'v': verbose = 1; break; case '?': exit_usage(); @@ -166,7 +169,7 @@ int main(int argc, char *argv[]) break; 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); i = 0; diff --git a/ssdv.c b/ssdv.c index 726f5d3..557523f 100644 --- a/ssdv.c +++ b/ssdv.c @@ -45,6 +45,11 @@ static const uint8_t const sos[10] = { 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 */ static const uint8_t const std_dqt0[65] = { 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) { void *r; @@ -831,18 +891,19 @@ static char ssdv_have_marker_data(ssdv_t *s) 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)); s->image_id = image_id; s->callsign = encode_callsign(callsign); s->mode = S_ENCODING; s->type = type; + s->quality = 4 + quality; /* Quality levels are -4 to 3, adjust to 0-7 */ ssdv_set_packet_conf(s); /* 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->ddqt[0] = dload_standard_dqt(s, std_dqt0, s->quality); + 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][1] = dtblcpy(s, std_dht01, sizeof(std_dht01)); 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[10] = s->height >> 4; /* Height / 16 */ 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] |= s->mcu_mode & 0x03; /* MCU mode (2 bits) */ 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_APP0, 14, app0); - ssdv_write_marker(s, J_DQT, 65, std_dqt0); /* DQT Luminance */ - ssdv_write_marker(s, J_DQT, 65, std_dqt1); /* DQT Chrominance */ + ssdv_write_marker(s, J_DQT, 65, s->ddqt[0]); /* DQT Luminance */ + ssdv_write_marker(s, J_DQT, 65, s->ddqt[1]); /* DQT Chrominance */ /* Build SOF0 header */ b[0] = 8; /* Precision */ @@ -1124,16 +1186,12 @@ char ssdv_dec_init(ssdv_t *s) s->mode = S_DECODING; /* 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][1] = stblcpy(s, std_dht01, sizeof(std_dht01)); s->sdht[1][0] = stblcpy(s, std_dht10, sizeof(std_dht10)); s->sdht[1][1] = stblcpy(s, std_dht11, sizeof(std_dht11)); /* 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][1] = dtblcpy(s, std_dht01, sizeof(std_dht01)); 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->height = packet[10] << 4; s->mcu_count = packet[9] * packet[10]; + s->quality = (packet[11] >> 3) & 7; s->mcu_mode = packet[11] & 0x03; /* Configure the payload size and CRC position */ 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 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, "MCU blocks: %i\n", s->mcu_count); fprintf(stderr, "Sampling factor: %s\n", factor); + fprintf(stderr, "Quality level: %d\n", s->quality - 4); /* Output JPEG headers and enable byte stuffing */ 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->height = packet[10] << 4; info->eoi = (packet[11] >> 2) & 1; + info->quality = (packet[11] >> 3) & 7; info->mcu_mode = packet[11] & 0x03; info->mcu_offset = packet[12]; info->mcu_id = (packet[13] << 8) | packet[14]; diff --git a/ssdv.h b/ssdv.h index 70f42bf..c67e8da 100644 --- a/ssdv.h +++ b/ssdv.h @@ -63,6 +63,7 @@ typedef struct uint8_t mcu_mode; /* 0 = 2x2, 1 = 2x1, 2 = 1x2, 3 = 1x1 */ uint16_t mcu_id; uint16_t mcu_count; + uint8_t quality; /* JPEG quality level for encoding, 0-7 */ uint16_t packet_mcu_id; uint8_t packet_mcu_offset; @@ -135,6 +136,7 @@ typedef struct { uint16_t width; uint16_t height; uint8_t eoi; + uint8_t quality; uint16_t mcu_mode; uint8_t mcu_offset; uint16_t mcu_id; @@ -142,7 +144,7 @@ typedef struct { } ssdv_packet_info_t; /* 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_get_packet(ssdv_t *s); extern char ssdv_enc_feed(ssdv_t *s, uint8_t *buffer, size_t length);