Skip to content

Commit 1730abd

Browse files
authored
Merge pull request #415 from theori-io/here-images
Decode HERE Images
2 parents cc91134 + 6df1f99 commit 1730abd

File tree

11 files changed

+316
-17
lines changed

11 files changed

+316
-17
lines changed

include/nrsc5.h

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ enum
173173
NRSC5_EVENT_STATION_LOCATION,
174174
NRSC5_EVENT_AUDIO_SERVICE_DESCRIPTOR,
175175
NRSC5_EVENT_DATA_SERVICE_DESCRIPTOR,
176-
NRSC5_EVENT_EMERGENCY_ALERT
176+
NRSC5_EVENT_EMERGENCY_ALERT,
177+
NRSC5_EVENT_HERE_IMAGE
177178
};
178179

179180
enum
@@ -249,6 +250,12 @@ enum
249250
NRSC5_ALERT_CATEGORY_TEST = 30
250251
};
251252

253+
enum
254+
{
255+
NRSC5_HERE_IMAGE_TRAFFIC = 8,
256+
NRSC5_HERE_IMAGE_WEATHER = 13
257+
};
258+
252259
/**
253260
* Station Information Service *Audio* service descriptor. This is a
254261
* linked list element that may point to further audio service
@@ -363,6 +370,7 @@ struct nrsc5_event_t
363370
* - `NRSC5_EVENT_AUDIO_SERVICE_DESCRIPTOR` : SIS audio service descriptor, see `asd` member
364371
* - `NRSC5_EVENT_DATA_SERVICE_DESCRIPTOR` : SIS data service descriptor, see `dsd` member
365372
* - `NRSC5_EVENT_EMERGENCY_ALERT` : emergency alert, see `emergency_alert` member
373+
* - `NRSC5_EVENT_HERE_IMAGE` : HERE Images traffic/weather map, see `here_image` member
366374
*/
367375
unsigned int event;
368376
union
@@ -506,6 +514,20 @@ struct nrsc5_event_t
506514
int num_locations;
507515
const int *locations;
508516
} emergency_alert;
517+
struct {
518+
int image_type; /**< NRSC5_HERE_IMAGE_TRAFFIC or NRSC5_HERE_IMAGE_WEATHER */
519+
int seq; /**< sequence number (1-15); increments when traffic/weather image changes */
520+
int n1; /**< part number (1-9) for traffic, or incrementing sequence number for weather */
521+
int n2; /**< number of parts (9) for traffic, or incrementing sequence number for weather */
522+
unsigned int timestamp; /**< unix timestamp of traffic or weather image */
523+
float latitude1; /**< latitude of north map edge */
524+
float longitude1; /**< longitude of west map edge */
525+
float latitude2; /**< latitude of south map edge */
526+
float longitude2; /**< longitude of east map edge */
527+
const char *name; /**< filename, e.g. "trafficMap_1_2_rdhs.png" or "WeatherImage_0_0_rdhs.png" */
528+
unsigned int size; /**< size of image file, in bytes */
529+
const uint8_t *data; /**< contents of image file */
530+
} here_image;
509531
};
510532
};
511533
/**

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ add_library (
3737
acquire.c
3838
decode.c
3939
frame.c
40+
here_images.c
4041
input.c
4142
nrsc5.c
4243
output.c

src/here_images.c

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* This program is free software: you can redistribute it and/or modify
3+
* it under the terms of the GNU General Public License as published by
4+
* the Free Software Foundation, either version 3 of the License, or
5+
* (at your option) any later version.
6+
*
7+
* This program is distributed in the hope that it will be useful,
8+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
9+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10+
* GNU General Public License for more details.
11+
*
12+
* You should have received a copy of the GNU General Public License
13+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
14+
*/
15+
16+
#include <string.h>
17+
#include <time.h>
18+
19+
#include "here_images.h"
20+
#include "private.h"
21+
22+
static void process_packet(here_images_t *st)
23+
{
24+
if (st->payload_len < 28)
25+
{
26+
log_warn("HERE Image frame too short");
27+
return;
28+
}
29+
30+
int image_type = (st->buffer[0] >> 4);
31+
int seq = (st->buffer[0] & 0x0f);
32+
33+
if ((image_type != NRSC5_HERE_IMAGE_TRAFFIC) && (image_type != NRSC5_HERE_IMAGE_WEATHER))
34+
{
35+
log_warn("Unknown HERE Image type: %d", image_type);
36+
return;
37+
}
38+
39+
int n1 = (st->buffer[2] << 8) | st->buffer[3];
40+
int n2 = (st->buffer[4] << 8) | st->buffer[5];
41+
unsigned int timestamp = ((unsigned int)st->buffer[9] << 24) | (st->buffer[10] << 16)
42+
| (st->buffer[11] << 8) | st->buffer[12];
43+
44+
int lat1 = ((st->buffer[14] & 0x7f) << 18) | (st->buffer[15] << 10) | (st->buffer[16] << 2) | (st->buffer[17] >> 6);
45+
if (st->buffer[14] & 0x80)
46+
lat1 = -lat1;
47+
48+
int lon1 = ((st->buffer[17] & 0x1f) << 20) | (st->buffer[18] << 12) | (st->buffer[19] << 4) | (st->buffer[20] >> 4);
49+
if (st->buffer[17] & 0x20)
50+
lon1 = -lon1;
51+
52+
int lat2 = ((st->buffer[20] & 0x07) << 22) | (st->buffer[21] << 14) | (st->buffer[22] << 6) | (st->buffer[23] >> 2);
53+
if (st->buffer[20] & 0x08)
54+
lat2 = -lat2;
55+
56+
int lon2 = ((st->buffer[23] & 0x01) << 24) | (st->buffer[24] << 16) | (st->buffer[25] << 8) | st->buffer[26];
57+
if (st->buffer[23] & 0x02)
58+
lon2 = -lon2;
59+
60+
int filename_len = st->buffer[27];
61+
62+
if (st->payload_len < 34 + filename_len)
63+
{
64+
log_warn("HERE Image frame too short");
65+
return;
66+
}
67+
68+
int file_len = (st->buffer[32 + filename_len] << 8) | st->buffer[33 + filename_len];
69+
70+
if (st->payload_len < 34 + filename_len + file_len)
71+
{
72+
log_warn("HERE Image frame too short");
73+
return;
74+
}
75+
76+
st->buffer[28 + filename_len] = '\0';
77+
78+
nrsc5_report_here_image(st->radio, image_type, seq, n1, n2, timestamp,
79+
lat1 / 100000.f, lon1 / 100000.f, lat2 / 100000.f, lon2 / 100000.f,
80+
(char *)&st->buffer[28], file_len, &st->buffer[34 + filename_len]);
81+
}
82+
83+
void here_images_push(here_images_t *st, uint16_t seq, unsigned int len, uint8_t *buf)
84+
{
85+
if (seq != st->expected_seq)
86+
{
87+
memset(st->buffer, 0, sizeof(st->buffer));
88+
st->payload_len = -1;
89+
st->sync_state = 0;
90+
}
91+
92+
for (unsigned int offset = 0; offset < len; offset++)
93+
{
94+
st->sync_state <<= 8;
95+
st->sync_state |= buf[offset];
96+
97+
if (st->payload_len == -1) // waiting for sync
98+
{
99+
if (((st->sync_state >> 16) & 0xffffffff) == 0xfff7fff7)
100+
{
101+
st->payload_len = st->sync_state & 0xffff;
102+
st->buffer_idx = 0;
103+
}
104+
}
105+
else
106+
{
107+
st->buffer[st->buffer_idx++] = buf[offset];
108+
if (st->buffer_idx == (st->payload_len + 2))
109+
{
110+
process_packet(st);
111+
st->payload_len = -1;
112+
}
113+
}
114+
}
115+
116+
st->expected_seq = (seq + 1) & 0xffff;
117+
}
118+
119+
void here_images_reset(here_images_t *st)
120+
{
121+
st->expected_seq = -1;
122+
}
123+
124+
void here_images_init(here_images_t *st, nrsc5_t *radio)
125+
{
126+
st->radio = radio;
127+
}

src/here_images.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
3+
#include <stdint.h>
4+
#include <nrsc5.h>
5+
6+
#define MAX_PAYLOAD_BYTES (65536 + 2)
7+
8+
typedef struct
9+
{
10+
nrsc5_t *radio;
11+
uint8_t buffer[MAX_PAYLOAD_BYTES];
12+
int buffer_idx;
13+
int expected_seq;
14+
int payload_len;
15+
uint64_t sync_state;
16+
} here_images_t;
17+
18+
void here_images_push(here_images_t *st, uint16_t seq, unsigned int len, uint8_t *buf);
19+
void here_images_reset(here_images_t *st);
20+
void here_images_init(here_images_t *st, nrsc5_t *radio);

src/main.c

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -227,17 +227,42 @@ static void dump_aas_file(state_t *st, const nrsc5_event_t *evt)
227227
#else
228228
#define PATH_SEPARATOR "/"
229229
#endif
230-
char fullpath[strlen(st->aas_files_path) + strlen(evt->lot.name) + 16];
230+
231+
const char *name;
232+
const uint8_t *data;
233+
unsigned int size;
234+
unsigned int number;
235+
236+
switch (evt->event)
237+
{
238+
case NRSC5_EVENT_LOT:
239+
name = evt->lot.name;
240+
data = evt->lot.data;
241+
size = evt->lot.size;
242+
number = evt->lot.lot;
243+
break;
244+
case NRSC5_EVENT_HERE_IMAGE:
245+
name = evt->here_image.name;
246+
data = evt->here_image.data;
247+
size = evt->here_image.size;
248+
number = evt->here_image.timestamp;
249+
break;
250+
default:
251+
log_error("invalid event type");
252+
return;
253+
}
254+
255+
char fullpath[strlen(st->aas_files_path) + strlen(name) + 16];
231256
FILE *fp;
232257

233-
sprintf(fullpath, "%s" PATH_SEPARATOR "%d_%s", st->aas_files_path, evt->lot.lot, evt->lot.name);
258+
sprintf(fullpath, "%s" PATH_SEPARATOR "%d_%s", st->aas_files_path, number, name);
234259
fp = fopen(fullpath, "wb");
235260
if (fp == NULL)
236261
{
237262
log_warn("Failed to open %s (%d)", fullpath, errno);
238263
return;
239264
}
240-
fwrite(evt->lot.data, 1, evt->lot.size, fp);
265+
fwrite(data, 1, size, fp);
241266
fclose(fp);
242267
}
243268

@@ -284,6 +309,7 @@ static void callback(const nrsc5_event_t *evt, void *opaque)
284309
nrsc5_sig_component_t *sig_component;
285310
nrsc5_id3_comment_t *comment;
286311
const char *name;
312+
char time_str[64];
287313

288314
switch (evt->event)
289315
{
@@ -376,7 +402,6 @@ static void callback(const nrsc5_event_t *evt, void *opaque)
376402
case NRSC5_EVENT_LOT:
377403
if (st->aas_files_path)
378404
dump_aas_file(st, evt);
379-
char time_str[64];
380405
strftime(time_str, sizeof(time_str), "%Y-%m-%dT%H:%M:%SZ", evt->lot.expiry_utc);
381406
log_info("LOT file: port=%04X lot=%d name=%s size=%d mime=%08X expiry=%s", evt->lot.port, evt->lot.lot, evt->lot.name, evt->lot.size, evt->lot.mime, time_str);
382407
break;
@@ -467,6 +492,24 @@ static void callback(const nrsc5_event_t *evt, void *opaque)
467492
evt->audio_service.common_delay,
468493
evt->audio_service.latency);
469494
break;
495+
case NRSC5_EVENT_HERE_IMAGE:
496+
if (st->aas_files_path)
497+
dump_aas_file(st, evt);
498+
time_t ts = (time_t) evt->here_image.timestamp;
499+
strftime(time_str, sizeof(time_str), "%Y-%m-%dT%H:%M:%SZ", gmtime(&ts));
500+
log_info("HERE Image: type=%s, seq=%d, n1=%d, n2=%d, time=%s, lat1=%.5f, lon1=%.5f, lat2=%.5f, lon2=%.5f, name=%s, size=%d",
501+
evt->here_image.image_type == NRSC5_HERE_IMAGE_TRAFFIC ? "TRAFFIC" : "WEATHER",
502+
evt->here_image.seq,
503+
evt->here_image.n1,
504+
evt->here_image.n2,
505+
time_str,
506+
evt->here_image.latitude1,
507+
evt->here_image.longitude1,
508+
evt->here_image.latitude2,
509+
evt->here_image.longitude2,
510+
evt->here_image.name,
511+
evt->here_image.size);
512+
break;
470513
}
471514
}
472515

src/nrsc5.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,3 +1041,26 @@ void nrsc5_report_emergency_alert(nrsc5_t *st, const char *message, const uint8_
10411041

10421042
nrsc5_report(st, &evt);
10431043
}
1044+
1045+
void nrsc5_report_here_image(nrsc5_t *st, int image_type, int seq, int n1, int n2, unsigned int timestamp,
1046+
float latitude1, float longitude1, float latitude2, float longitude2,
1047+
const char *name, unsigned int size, const uint8_t *data)
1048+
{
1049+
nrsc5_event_t evt;
1050+
1051+
evt.event = NRSC5_EVENT_HERE_IMAGE;
1052+
evt.here_image.image_type = image_type;
1053+
evt.here_image.seq = seq;
1054+
evt.here_image.n1 = n1;
1055+
evt.here_image.n2 = n2;
1056+
evt.here_image.timestamp = timestamp;
1057+
evt.here_image.latitude1 = latitude1;
1058+
evt.here_image.longitude1 = longitude1;
1059+
evt.here_image.latitude2 = latitude2;
1060+
evt.here_image.longitude2 = longitude2;
1061+
evt.here_image.name = name;
1062+
evt.here_image.size = size;
1063+
evt.here_image.data = data;
1064+
1065+
nrsc5_report(st, &evt);
1066+
}

src/output.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "output.h"
2727
#include "private.h"
2828
#include "unicode.h"
29+
#include "here_images.h"
2930

3031
void output_align(output_t *st, unsigned int program, unsigned int stream_id, unsigned int offset)
3132
{
@@ -169,6 +170,8 @@ void output_reset(output_t *st)
169170
st->aacdec[i] = NULL;
170171
#endif
171172
}
173+
174+
here_images_reset(&st->here_images);
172175
}
173176

174177
void output_init(output_t *st, nrsc5_t *radio)
@@ -181,6 +184,7 @@ void output_init(output_t *st, nrsc5_t *radio)
181184
#endif
182185

183186
memset(st->services, 0, sizeof(st->services));
187+
here_images_init(&st->here_images, radio);
184188

185189
output_reset(st);
186190
}
@@ -649,6 +653,8 @@ static void process_port(output_t *st, uint16_t port_id, uint16_t seq, uint8_t *
649653
case AAS_TYPE_STREAM:
650654
{
651655
nrsc5_report_stream(st->radio, port_id, seq, len, buf, component->service_ext, component->component_ext);
656+
if (component->data.mime == NRSC5_MIME_HERE_IMAGE)
657+
here_images_push(&st->here_images, seq, len, buf);
652658
break;
653659
}
654660
case AAS_TYPE_PACKET:

src/output.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#pragma once
22

33
#include "config.h"
4+
#include "here_images.h"
45

56
#include <nrsc5.h>
67

@@ -100,6 +101,7 @@ typedef struct
100101
int16_t silence[NRSC5_AUDIO_FRAME_SAMPLES * 2];
101102
#endif
102103
sig_service_t services[MAX_SIG_SERVICES];
104+
here_images_t here_images;
103105
} output_t;
104106

105107
void output_align(output_t *st, unsigned int program, unsigned int stream_id, unsigned int offset);

src/private.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,6 @@ void nrsc5_report_dsd(nrsc5_t *st, unsigned int access, unsigned int type, uint3
8080
void nrsc5_report_emergency_alert(nrsc5_t *st, const char *message, const uint8_t *control_data,
8181
int control_data_length, int category1, int category2,
8282
int location_format, int num_locations, const int *locations);
83+
void nrsc5_report_here_image(nrsc5_t *st, int image_type, int seq, int n1, int n2, unsigned int timestamp,
84+
float latitude1, float longitude1, float latitude2, float longitude2,
85+
const char *name, unsigned int size, const uint8_t *data);

support/cli.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,16 @@ def callback(self, evt_type, evt):
318318
evt.digital_audio_gain,
319319
evt.common_delay,
320320
evt.latency)
321+
elif evt_type == nrsc5.EventType.HERE_IMAGE:
322+
time_str = evt.timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
323+
logging.info("HERE Image: type=%s, seq=%d, n1=%d, n2=%d, time=%s, lat1=%.5f, lon1=%.5f, lat2=%.5f, lon2=%.5f, name=%s, size=%d",
324+
evt.image_type.name, evt.seq, evt.n1, evt.n2, time_str, evt.latitude1, evt.longitude1,
325+
evt.latitude2, evt.longitude2, evt.name, len(evt.data))
326+
if self.args.dump_aas_files:
327+
time_int = int(evt.timestamp.timestamp())
328+
path = os.path.join(self.args.dump_aas_files, f"{time_int}_{evt.name}")
329+
with open(path, "wb") as file:
330+
file.write(evt.data)
321331

322332

323333
if __name__ == "__main__":

0 commit comments

Comments
 (0)