2011-05-30 23:37:33 +01:00
|
|
|
|
2011-05-31 09:00:51 +01:00
|
|
|
/* SSDV - Slow Scan Digital Video */
|
|
|
|
/*=======================================================================*/
|
2016-04-03 23:27:54 +01:00
|
|
|
/* Copyright 2011-2016 Philip Heron <phil@sanslogic.co.uk> */
|
2011-05-31 09:00:51 +01:00
|
|
|
/* */
|
|
|
|
/* This program is free software: you can redistribute it and/or modify */
|
|
|
|
/* it under the terms of the GNU General Public License as published by */
|
|
|
|
/* the Free Software Foundation, either version 3 of the License, or */
|
|
|
|
/* (at your option) any later version. */
|
|
|
|
/* */
|
|
|
|
/* This program is distributed in the hope that it will be useful, */
|
|
|
|
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
|
|
|
|
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
|
|
|
|
/* GNU General Public License for more details. */
|
|
|
|
/* */
|
|
|
|
/* You should have received a copy of the GNU General Public License */
|
|
|
|
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
|
2011-05-30 23:37:33 +01:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "ssdv.h"
|
|
|
|
|
|
|
|
void exit_usage()
|
|
|
|
{
|
2014-08-28 20:35:31 +01:00
|
|
|
fprintf(stderr,
|
2023-03-04 13:10:13 +00:00
|
|
|
"\n"
|
|
|
|
"Usage: ssdv [-e|-d] [-n] [-t <percentage>] [-c <callsign>] [-i <id>] [-q <level>] [-l <length>] [<in file>] [<out file>]\n"
|
2014-08-28 20:35:31 +01:00
|
|
|
"\n"
|
|
|
|
" -e Encode JPEG to SSDV packets.\n"
|
|
|
|
" -d Decode SSDV packets to JPEG.\n"
|
|
|
|
"\n"
|
2015-10-20 09:59:48 +01:00
|
|
|
" -n Encode packets with no FEC.\n"
|
2014-08-28 20:35:31 +01:00
|
|
|
" -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"
|
2016-06-25 00:12:46 +01:00
|
|
|
" -q Set the JPEG quality level (0 to 7, defaults to 4).\n"
|
2023-03-04 13:10:13 +00:00
|
|
|
" -l Set packet length in bytes (max: 256, default 256).\n"
|
2015-10-20 00:17:20 +01:00
|
|
|
" -v Print data for each packet decoded.\n"
|
2023-03-04 13:10:13 +00:00
|
|
|
"\n"
|
|
|
|
"Packet Length\n"
|
|
|
|
"\n"
|
|
|
|
"The packet length must be specified for both encoding and decoding if not\n"
|
|
|
|
"the default 256 bytes. Smaller packets will increase overhead.\n"
|
2014-08-28 20:35:31 +01:00
|
|
|
"\n");
|
2011-05-30 23:37:33 +01:00
|
|
|
exit(-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
int c, i;
|
|
|
|
FILE *fin = stdin;
|
|
|
|
FILE *fout = stdout;
|
|
|
|
char encode = -1;
|
2014-08-28 20:13:46 +01:00
|
|
|
char type = SSDV_TYPE_NORMAL;
|
2011-05-30 23:37:33 +01:00
|
|
|
int droptest = 0;
|
2015-10-20 00:15:15 +01:00
|
|
|
int verbose = 0;
|
|
|
|
int errors;
|
2012-02-25 20:06:15 +00:00
|
|
|
char callsign[7];
|
2011-05-30 23:37:33 +01:00
|
|
|
uint8_t image_id = 0;
|
2016-06-25 00:12:46 +01:00
|
|
|
int8_t quality = 4;
|
2023-03-04 13:10:13 +00:00
|
|
|
int pkt_length = SSDV_PKT_SIZE;
|
2011-05-30 23:37:33 +01:00
|
|
|
ssdv_t ssdv;
|
2023-04-14 14:57:41 +01:00
|
|
|
int skipped;
|
2011-05-30 23:37:33 +01:00
|
|
|
|
|
|
|
uint8_t pkt[SSDV_PKT_SIZE], b[128], *jpeg;
|
|
|
|
size_t jpeg_length;
|
|
|
|
|
2012-02-25 20:06:15 +00:00
|
|
|
callsign[0] = '\0';
|
|
|
|
|
2011-05-30 23:37:33 +01:00
|
|
|
opterr = 0;
|
2023-03-04 13:10:13 +00:00
|
|
|
while((c = getopt(argc, argv, "ednc:i:q:l:t:v")) != -1)
|
2011-05-30 23:37:33 +01:00
|
|
|
{
|
|
|
|
switch(c)
|
|
|
|
{
|
|
|
|
case 'e': encode = 1; break;
|
|
|
|
case 'd': encode = 0; break;
|
2014-08-28 20:13:46 +01:00
|
|
|
case 'n': type = SSDV_TYPE_NOFEC; break;
|
2012-02-25 20:06:15 +00:00
|
|
|
case 'c':
|
|
|
|
if(strlen(optarg) > 6)
|
2023-03-04 13:10:40 +00:00
|
|
|
{
|
|
|
|
fprintf(stderr, "Warning: callsign cropped to 6 characters.\n");
|
|
|
|
}
|
2020-07-05 12:44:07 +01:00
|
|
|
strncpy(callsign, optarg, 6);
|
|
|
|
callsign[6] = '\0';
|
2012-02-25 20:06:15 +00:00
|
|
|
break;
|
2011-05-30 23:37:33 +01:00
|
|
|
case 'i': image_id = atoi(optarg); break;
|
2016-06-16 23:29:30 +01:00
|
|
|
case 'q': quality = atoi(optarg); break;
|
2023-03-04 13:10:13 +00:00
|
|
|
case 'l': pkt_length = atoi(optarg); break;
|
2011-05-30 23:37:33 +01:00
|
|
|
case 't': droptest = atoi(optarg); break;
|
2015-10-20 00:15:15 +01:00
|
|
|
case 'v': verbose = 1; break;
|
2011-05-30 23:37:33 +01:00
|
|
|
case '?': exit_usage();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
c = argc - optind;
|
|
|
|
if(c > 2) exit_usage();
|
|
|
|
|
|
|
|
for(i = 0; i < c; i++)
|
|
|
|
{
|
|
|
|
if(!strcmp(argv[optind + i], "-")) continue;
|
|
|
|
|
|
|
|
switch(i)
|
|
|
|
{
|
|
|
|
case 0:
|
|
|
|
fin = fopen(argv[optind + i], "rb");
|
|
|
|
if(!fin)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Error opening '%s' for input:\n", argv[optind + i]);
|
|
|
|
perror("fopen");
|
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1:
|
|
|
|
fout = fopen(argv[optind + i], "wb");
|
|
|
|
if(!fout)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Error opening '%s' for output:\n", argv[optind + i]);
|
|
|
|
perror("fopen");
|
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch(encode)
|
|
|
|
{
|
|
|
|
case 0: /* Decode */
|
2023-03-04 13:10:13 +00:00
|
|
|
|
2011-05-30 23:37:33 +01:00
|
|
|
if(droptest > 0) fprintf(stderr, "*** NOTE: Drop test enabled: %i ***\n", droptest);
|
|
|
|
|
2023-03-04 13:10:13 +00:00
|
|
|
if(ssdv_dec_init(&ssdv, pkt_length) != SSDV_OK)
|
|
|
|
{
|
|
|
|
return(-1);
|
|
|
|
}
|
2011-05-30 23:37:33 +01:00
|
|
|
|
|
|
|
jpeg_length = 1024 * 1024 * 4;
|
|
|
|
jpeg = malloc(jpeg_length);
|
|
|
|
ssdv_dec_set_buffer(&ssdv, jpeg, jpeg_length);
|
|
|
|
|
|
|
|
i = 0;
|
2023-04-14 14:57:41 +01:00
|
|
|
while(fread(pkt, pkt_length, 1, fin) > 0)
|
2011-05-30 23:37:33 +01:00
|
|
|
{
|
|
|
|
/* Drop % of packets */
|
|
|
|
if(droptest && (rand() / (RAND_MAX / 100) < droptest)) continue;
|
|
|
|
|
|
|
|
/* Test the packet is valid */
|
2023-04-14 14:57:41 +01:00
|
|
|
skipped = 0;
|
|
|
|
while((c = ssdv_dec_is_packet(pkt, pkt_length, &errors)) != 0)
|
|
|
|
{
|
|
|
|
/* Read 1 byte at a time until a new packet is found */
|
|
|
|
memmove(&pkt[0], &pkt[1], pkt_length - 1);
|
|
|
|
|
|
|
|
if(fread(&pkt[pkt_length - 1], 1, 1, fin) <= 0)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
skipped++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* No valid packet was found before EOF */
|
|
|
|
if(c != 0) break;
|
2015-10-20 00:15:15 +01:00
|
|
|
|
|
|
|
if(verbose)
|
|
|
|
{
|
|
|
|
ssdv_packet_info_t p;
|
|
|
|
|
2023-04-14 14:57:41 +01:00
|
|
|
if(skipped > 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Skipped %d bytes.\n", skipped);
|
|
|
|
}
|
|
|
|
|
2015-10-20 00:15:15 +01:00
|
|
|
ssdv_dec_header(&p, pkt);
|
2023-04-14 15:01:45 +01:00
|
|
|
fprintf(stderr, "Decoded image packet. Callsign: \"%s\", Image ID: %d, Resolution: %dx%d, Packet ID: %d (%d errors corrected)\n"
|
2016-06-30 10:11:20 +01:00
|
|
|
">> Type: %d, Quality: %d, EOI: %d, MCU Mode: %d, MCU Offset: %d, MCU ID: %d/%d\n",
|
2015-10-20 00:15:15 +01:00
|
|
|
p.callsign_s,
|
|
|
|
p.image_id,
|
|
|
|
p.width,
|
|
|
|
p.height,
|
|
|
|
p.packet_id,
|
|
|
|
errors,
|
2015-10-20 09:59:48 +01:00
|
|
|
p.type,
|
2016-06-30 10:11:20 +01:00
|
|
|
p.quality,
|
2015-10-20 00:15:15 +01:00
|
|
|
p.eoi,
|
|
|
|
p.mcu_mode,
|
|
|
|
p.mcu_offset,
|
|
|
|
p.mcu_id,
|
|
|
|
p.mcu_count
|
|
|
|
);
|
|
|
|
}
|
2011-05-30 23:37:33 +01:00
|
|
|
|
|
|
|
/* Feed it to the decoder */
|
|
|
|
ssdv_dec_feed(&ssdv, pkt);
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
ssdv_dec_get_jpeg(&ssdv, &jpeg, &jpeg_length);
|
|
|
|
fwrite(jpeg, 1, jpeg_length, fout);
|
|
|
|
free(jpeg);
|
|
|
|
|
|
|
|
fprintf(stderr, "Read %i packets\n", i);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 1: /* Encode */
|
2023-03-04 13:10:13 +00:00
|
|
|
|
|
|
|
if(ssdv_enc_init(&ssdv, type, callsign, image_id, quality, pkt_length) != SSDV_OK)
|
|
|
|
{
|
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
|
2011-05-30 23:37:33 +01:00
|
|
|
ssdv_enc_set_buffer(&ssdv, pkt);
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
|
|
|
|
while(1)
|
|
|
|
{
|
|
|
|
while((c = ssdv_enc_get_packet(&ssdv)) == SSDV_FEED_ME)
|
|
|
|
{
|
|
|
|
size_t r = fread(b, 1, 128, fin);
|
|
|
|
|
|
|
|
if(r <= 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "Premature end of file\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
ssdv_enc_feed(&ssdv, b, r);
|
|
|
|
}
|
|
|
|
|
|
|
|
if(c == SSDV_EOI)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "ssdv_enc_get_packet said EOI\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if(c != SSDV_OK)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "ssdv_enc_get_packet failed: %i\n", c);
|
|
|
|
return(-1);
|
|
|
|
}
|
|
|
|
|
2023-03-04 13:10:13 +00:00
|
|
|
fwrite(pkt, 1, pkt_length, fout);
|
2011-05-30 23:37:33 +01:00
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "Wrote %i packets\n", i);
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "No mode specified.\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(fin != stdin) fclose(fin);
|
|
|
|
if(fout != stdout) fclose(fout);
|
|
|
|
|
|
|
|
return(0);
|
|
|
|
}
|
|
|
|
|