Декодирование картинки fox-1d

Люди по-прежнему запускают спутники в космос, а я по-прежнему пытаюсь декодировать с них сигналы. На этот раз я решил разобраться со спутниками семейства Fox. Они передают достаточно много данных:

  • телеметрия
  • результаты исследований радиации
  • картинки

Если первые два типа данных достаточно стандартны, то вот картинки - это то, перед чем я не могу устоять.

Формат передачи данных

Эти спутники могут передавать информацию с помощью двух протоколов:

  • медленный. Скорость 200 бит в секунду. Используется для передачи телеметрии.
  • быстрый. 9600 бит в секунду. Передача телеметрии, картинок и результатов исследований

Для декодирования картинки, необходимо разобраться с “быстрым” протоколом передачи данных. Тут важно знать, что этот протокол включается только по команде с Земли, когда нужно получить большой объём данных. А значит он выключен всё остальное время. Это очень сильно отличается от того, как Метеор-М и jy1sat передают изображения, где изображения передаются постоянно.

Демодуляция

Сигнал “быстрого” протокола - это FSK модулированный сигнал 9600 бит в секунду. Ничего особенного тут нет, стандартный FskDemodulator из jradio вполне подходит.

Единственная странность - это крайне короткое синхрослово. Несмотря на то, что длина самого пакета 5272 байт, синхрослово длиной всего 10бит. Это значит две вещи:

  • нужно искать полное совпадение в потоке бит. Иначе, сильно увеличится количество ложных срабатываний. А это в свою очередь сильно будет нагружать и без того не быстрый алгоритм Рид-Соломона
  • сильно уменьшается помехоустойчивость. Несмотря на то, что для всего сообщения применяются коды Рида-Соломона, ошибка в одном бите в синхрослове полностью отбрасывает декодирование всего пакета.

Помехоустойчивое декодирование

В Fox-* примется достаточно интересная комбинация кодов - код 8b10b и код Рида-Соломона. Код 8b10b работает достаточно интересным способом: каждый передаваемый байт (8 бит) кодируется 10 битами. При этом эти 10 бит выбраны таким образом, чтобы иметь максимальное Хэмминг расстояние.

Если приёмник получает 10 бит, то он пытается найти их в таблице поиска и получить переданный байт. Если такого значения нет, то считается, что переданный байт не был корректно принят. Какая же польза от этого знания? А это используется в алгоритме Рида-Соломона. Зная, какие байты точно не были корректно приняты, можно передать это знание Риду-Соломону и немного увеличить шансы на восстановление данных.

До этого момента всё шло хорошо и достаточно понятно. Дальше же начинается череда странных решений и неоднозначных реализаций. Для начала, 5272 байт - это достаточно много для одного пакета. Если же учитывать код 8b10b, то общее количество бит будет - 52720. Чем длиннее пакет, тем больше вероятность возникновения помех и больше вероятность ошибок приёма.

Весь пакет кодируется с помощью интерливинга 21 кода Рида-Соломона. Но так, как 5272 не делится нацело на 21, то используется padding нулевых байт при декодировании:

0
0
1
1
2
2
20
20
21
21
22
22
23
23
40
40
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
4599
4599
4598
4598
4600
4600
4601
4601
4620
4620
5271
5271
5250
5250
4602
4602
Коды чётности Рида Соломона
Коды чётности Рида Соломона
Данные
Данные
Text is not SVG - cannot display

Как видно из схемы выше, алгоритм получения данных будет достаточно нетривиальным. Нулевые байты дописываются слева к каждому коду. При этом первое слово имеет на один байт больше. Во всех других проколах, с которыми мне приходилось сталкиваться, интерливинг делался для сообщений, которые нацело делились на 255. Причина, по которой была выбрана длина 5272 байт, остаётся для меня загадкой.

Декодирование картинки

Но самое странное - это то, как эти 5272 байт используются. Формат пакета следующий:

Размер (байт) Описание
6 Заголовок. Содержит id спутника и тип телеметрии
60 Текущая телеметрия. Различные показатели спутника в текущий момент
60 Максимальные значения показателей за период измерений
60 Минимальные значения показателей за период измерений
1 Количество "линий" передаваемого изображения. Линия - это кусочек изображения высотой 8 пикселов и шириной 320 пикселей в формате JPEG
Переменная длина "Линии" изображения
1 Количество пакетов радиационого эксперимента. Передаётся либо изображение, либо данные эксперимента
Переменная длина Данные эксперимента

В каждом пакете передаётся всего 3 “линии” изображения. Если сложить все байты вместе, то получится около 1340 байт. Из-за кодирования jpeg может быть чуть больше или чуть меньше, но никак не 5272. Оказывается оставшиеся байты используются в experiment on the radiation hardness of COTS memory modules. В официальном клиенте я не нашёл способа декодировать эти данные.

Декодирование jpeg

Способ, которым передаются кусочки изображения, совсем отличается от Метеор-М и SSDV. Каждая “линия” изображения состоит из:

Название Размер (байт) Описание
Preamble 6 Байты синхронизации. Не очень понятно зачем они нужны, так как изображение уже находится внутри пакета.
Counter 1 ID картинки. Крайне необходимое поле для того, чтобы понять, когда закончилась одна картинка и началась другая
Length 10 бит Длина JPEG данных
ID линии 6 бит ID линии внутри изображения. От 0 до 29.
JPEG данные переменная длина Непосредственно данные изображения

Я уже было расчехлил свои таблицы Хаффмана и настроил таблицы квантования, как выяснилось, что можно и без них. Дело в том, что DC коэффициенты обнуляются для каждого следующего кусочка. А это значит, что они независимы и их можно как-есть сохранить в файл и назвать его картинка.jpg. В java код выглядит так:

baos.write(HEADER);
for (PictureScanLine cur : currentBatch) {
	int missingLines = cur.getLineNumber() - previousLineNumber - 1;
	for (int i = 0; i < missingLines; i++) {
		appendLine(baos, EMPTY_LINE, previousLineNumber + i + 1);
	}
	previousLineNumber = cur.getLineNumber();
	appendLine(baos, cur.getData(), cur.getLineNumber());
}

if (previousLineNumber + 1 != TOTAL_LINES) {
	int missingLines = TOTAL_LINES - previousLineNumber - 1;
	for (int i = 0; i < missingLines; i++) {
		appendLine(baos, EMPTY_LINE, previousLineNumber + i + 1);
	}
}
baos.write(FOOTER);

При этом HEADER, EMPTY_LINE и FOOTER - это заранее созданные массивы байт. После того как байты записаны в ByteArrayOutputStream на выходе получается стандартный jpeg файл. Тут важно не забыть добавлять тэг RSTn после каждой линии, чтобы сбросить DC коэффициенты в 0.

Результат

В результате один пакет Fox-1D позволяет получить вот такую симпатичную картинку из космоса:

Если присмотреться, то справа можно увидеть странную зелёную полосу:

Я попытался разобраться из-за чего она получилась, но не нашёл ничего подозрительного. Jpeg файл выглядел правильным, и никаких ошибок декодирования не было. Вполне возможно, это баг камеры на спутнике.

Если же сравнивать результаты с официальным клиентом, то мне мой нравится больше. В официальном декодере даже нет поддержки пропущенных линий: