Изображения с DSLWP-B

Недавно Дэниел и команда DSLWP выложили в общий доступ данные со спутника DSLWP-B. Эти данные включают в себя телеметрию, а также изображения Луны и Земли. Сам спутник уже был штатно разбит о Луну, поэтому больше данных с него не будет. Мне же стало интересно, как передавались изображения с лунной орбиты. Плюс, это была бы отличная возможность проверить работу моего ssdv декодера.

Для начала я скачал dslwp-data репозиторий и попытался декодировать файлы *.ssdv. Эти файлы уже содержат фреймы SSDV в специфичном для DSLWP протоколе. Как обычно, для экономии канала, разработчики немного изменили этот протокол: нет полей Packet Type, Callsign и FEC.

Я написал небольшую обёртку, которая преобразует специфичные для DSLWP SSDV пакеты в стандартные. Однако, с первого раза ничего не получилось. Оказывается, для кодирования изображений использовалась цветовая субдискретизация 2х1 и она была просто сломана в ssdv4j.

Я переписал чудовищные циклы с хитрыми индексами:

for (int row = 0; row < DataUnitDecoder.PIXELS_PER_DU; row++) {
	for (int col = 0; col < DataUnitDecoder.PIXELS_PER_DU; col++) {
		int cbCrSourceIndex = row * DataUnitDecoder.PIXELS_PER_DU + col;
		for (int subRow = 0; subRow < yComponent.getMaxRows(); subRow++) {
			for (int subCol = 0; subCol < yComponent.getMaxCols(); subCol++) {
				int ySourceIndex = row * DataUnitDecoder.PIXELS_PER_DU * yComponent.getMaxCols() * yComponent.getMaxRows() + subRow * DataUnitDecoder.PIXELS_PER_DU * yComponent.getMaxCols() + col * yComponent.getMaxCols() + subCol;
				rgb[ySourceIndex] = convertToRgb(yComponent.getBuffer()[ySourceIndex], cbComponent.getBuffer()[cbCrSourceIndex], crComponent.getBuffer()[cbCrSourceIndex]);
			}
		}
	}
}

На чуть более простые:

int[] cbCrIndexMapping = getSrcIndexMapping(yComponent.getSubsamplingMode());
for (int row = 0; row < yComponent.getMaxRows() * DataUnitDecoder.PIXELS_PER_DU; row++) {
	for (int col = 0; col < yComponent.getMaxCols() * DataUnitDecoder.PIXELS_PER_DU; col++) {
		int sourceIndex = row * yComponent.getMaxCols() * DataUnitDecoder.PIXELS_PER_DU + col;
		rgb[sourceIndex] = convertToRgb(yComponent.getBuffer()[sourceIndex], cbComponent.getBuffer()[cbCrIndexMapping[sourceIndex]], crComponent.getBuffer()[cbCrIndexMapping[sourceIndex]]);
	}
}

Исправил несколько ошибок. И в целом, не было ничего сложного, кроме разных хитрых индексов. Единственную странность я обнаружил в спецификации SSDV. В каждом пакете есть такие поля как Width и Height измеряемые в количестве MCU. Чтобы получить высоту в пикселах, логично умножить Width на высоту одного MCU. Тут есть засада. В зависимости от цветовой субдискретизации высота MCU может быть как 8 пикселей (для 2x1), так и 16 (для 2x2). Поэтому для получения высоты картинки при 2x1 я просто умножал высоту на 8. Однако, это не сработало. Похоже MCU в SSDV понимается как блок пикселей 16х16. И совсем не совпадает с тем, что написано в стандарте jpeg. Сделав правильные выводы, я получил картинку:

Если же сравнивать мой декодер с официальным, то мне нравится больше мой. Он правильнее обрабатывает пропущенные пакеты и всегда выдаёт чёрный цвет.

После того, как я обработал все существующие пакеты, удалось найти несколько хороших кадров и сделать анимацию:

На ней отлично видно, как спутник облетает Луну, и то, как Земля вращается на заднем плане.