Switched to new cleaner argument system

This commit is contained in:
AlexandreRouma
2022-02-24 20:49:53 +01:00
parent 5c138aa4a5
commit 2779516378
50 changed files with 777 additions and 205 deletions

View File

@ -4,7 +4,6 @@
#include <gui/style.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <options.h>
#include <gui/gui.h>
#include <dsp/pll.h>
#include <dsp/stream.h>

View File

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.13)
project(kg_sstv_decoder)
file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c")
add_library(kg_sstv_decoder SHARED ${SRC})
target_link_libraries(kg_sstv_decoder PRIVATE sdrpp_core)
set_target_properties(kg_sstv_decoder PROPERTIES PREFIX "")
target_include_directories(kg_sstv_decoder PRIVATE "src/")
if (MSVC)
target_compile_options(kg_sstv_decoder PRIVATE /O2 /Ob2 /std:c++17 /EHsc)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
target_compile_options(kg_sstv_decoder PRIVATE -O3 -std=c++17 -Wno-unused-command-line-argument -undefined dynamic_lookup)
else ()
target_compile_options(kg_sstv_decoder PRIVATE -O3 -std=c++17)
endif ()
# Install directives
install(TARGETS kg_sstv_decoder DESTINATION lib/sdrpp/plugins)

View File

@ -0,0 +1,133 @@
○ Transmission by MSK and 4L-FSK
MSK is a type of FSK method that is a modulation method for digital signals, and is the mark frequency of FSK.
Minimize the frequency between the number and the space frequency (Minimum Shift Keying)
4L-FSK is a 4-value FSK, and 4 modulation frequencies that can be taken with a type of FSK are provided.
This makes it possible to obtain twice the transmission speed compared to MSK.
4L-FSK is more susceptible to noise because the frequency interval is narrower than MSK.
It also becomes more susceptible to deterioration of transmission line characteristics.
There are two types of choices, but when the line condition such as FM is good, the image will be sent quickly, so
Or I think you should select 4L-FSK to send high quality images. wireless
If the SSB transmission in HF where the line condition is unstable is set to MSK, the data will be as it is.
I think it will be.
The first pull-down menu from the top of the pull-down menu on the right side of Send Image
You can select MSK or 4L-FSK in the new.
○ Transmission in convolutional code mode
The second pull-down menu from the top of the pull-down menu on the right side of Send Image
New is the choice of code processing. Here no code processing (NORM) or convolutional code processing
Set whether to perform (CONV).
When convolutional code processing is performed, it becomes stronger against noise, but the same data is sent.
It takes about twice as long to complete.
Please select the code processing on a case-by-case basis while checking the transmission status.
This setting can be changed even during transmission.
○ Sending and receiving text messages
With KG-STV, a maximum of 510 half-width characters (255 full-width characters) text message can be sent once.
You can send sage.
To send a text message, text in the input box under Resp BSR
And click Send Text on the right.
When you receive a text message, the text will be displayed in the box below.
○ KG-STV transmission standard
Modulation format: MSK or composite modulation of MSK and 4-value FSK
Modulation speed: 1200baud
Modulation frequency: MSK …… Space frequency 1200Hz Mark frequency 1800Hz
4L-FSK ...
'00' 1200Hz
'01' 1400Hz
'10' 1600Hz
'11' 1800Hz
Bandwidth: 500-2500Hz
Error correction: None or Viterbi code (NASA standard K = 7, R=1/2, P=[109, 79] code)
Whitening: Yes (add M-sequence code with a period of 127 bits to each bit)
Interleaver: None
Header: 01 repeat signal is 256 bits
Basic code configuration: Synchronous code + 54-bit length information chunk + arbitrary length data chunk
Error detection: Yes (CRC 16 CCITT)
Synchronous bit: 63-bit M-sequence code
Image compression: JPEG compatible (16x16 pixels, thinning 4: 1: 1)
Character code: Shift JIS compatible
Radio format: F1D (operation in SSB mode)
F2D (operation in FM mode)
Information chunk configuration (system code version '0')
┌─────┬─────┬───┬───┬───┬───┬────┬──────┬─────┐
│ sys │ com │ c │ m │ x │ y │ sc │ size │ CRC │
└─────┴─────┴───┴───┴───┴───┴────┴──────┴─────┘
0 4 8 9 10 16 22 26 38 54bit
Sys: system code
Com: command code
C: sign mode
M: Modulation mode
X: Image block position (X)
Y: Image block position (Y)
Sc: JPEG scale size
Size: data size
CRC: Error detection code
* Information chunks always perform MSK modulation + convolution processing.
Data chunk structure (system code version '0')
┌──────┬─────┐
│ data │ CRC │
└──────┴─────┘
* Data chunks are transmitted following the CRC code of the information chunks.
Command table (system code version '0')
┌─────┬────────┐
│ Command value │ Operation │ │
├─────┼────────┤
│ 0 │ text transmission │
├─────┼────────┤
│ 1 │ Image transmission │ │
├─────┼────────┤
│ 2 │ BSR response │ │
├─────┼────────┤
│ │ 3 │ end │ │
├─────┼────────┤
│ │ 4 │ BSR request │ │
├─────┼────────┤
│ 5 │ Canceled (suspended) │
├─────┼────────┤
│ 6 │ call sign │
└─────┴────────┘
* When starting transmission, the header code of 01 shall be transmitted first.
* One data format (code configuration) is
Synchronous code + information chunk + data chunk
It will be. However, the end signal, stop signal, and BSR signal do not include data chunks.
stomach.
* In the case of image data, this data format is continuous for the number of image blocks.
It shall be sent.
* In the case of text transmission, it shall be completed in one data format.
* When the transmission is completed normally, the end signal shall be transmitted three times at the end.
* When the transmission is canceled, the cancellation signal shall be transmitted three times at the end.
Synchronous code
000011100001001000110110010110101110111100110001010100111111010
Whitening code
1110110011000100100111001111100100000100011010101001101101001010000101100001100101111111010110111011110001110100010101110000001
○ KG-STV needs more experiments
KG-STV was born from the creator's own interest in digital image communication.
However, there is a lack of practical experience due to the ability of the person himself / herself.
Therefore, the actual luck is that the transmission time is slow, the image quality is poor, and it is difficult to receive.
I think there are many situations where it is not practical in terms of use.
In such a case, I would like to research as a new issue, so by all means
We hope that many people will use it and give us their impressions.
Inconvenience such as bugs and operational problems until the degree of completion is high after receiving your opinions
We apologize for the inconvenience, but thank you for your cooperation.
Creator: K.G (JJ0OBZ Myoko City, Niigata Prefecture)
-------------------------------------------------- -------------------------------------------------- --------------------
Created and written by K.G k.g8956@ymail.plala.or.jp
Today also from the day of the plane http://www2.plala.or.jp/hikokibiyori/
-------------------------------------------------- -------------------------------------------------- --------------------

View File

@ -0,0 +1,280 @@
#pragma once
#include <dsp/block.h>
#include <dsp/stream.h>
#include <dsp/types.h>
#include <dsp/routing.h>
#include <dsp/demodulator.h>
#include <dsp/sink.h>
#include <spdlog/spdlog.h>
extern "C" {
#include <correct.h>
}
#define KGSSTV_DEVIATION 300
#define KGSSTV_BAUDRATE 1200
#define KGSSTV_RRC_ALPHA 0.7f
#define KGSSTV_4FSK_HIGH_CUT 0.5f
// const uint8_t KGSSTV_SYNC_WORD[] = {
// 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0,
// 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0,
// 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0,
// 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0,
// 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
// 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
// 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1,
// 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0
// };
const uint8_t KGSSTV_SYNC_WORD[] = {
0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0,
0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0,
1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1,
0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0
};
const uint8_t KGSSTV_SCRAMBLING[] = {
1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0,
1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1,
0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0,
1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0,
0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1,
0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1,
1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0,
0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1
};
const uint8_t KGSSTV_SCRAMBLING_BYTES[] = {
0b11101100, 0b11000100, 0b10011100, 0b11111001, 0b00000100,
0b01101010, 0b10011011, 0b01001010, 0b00010110, 0b00011001,
0b01111111, 0b01011011, 0b10111100, 0b01110100, 0b01010111,
0b00000010
};
static const correct_convolutional_polynomial_t kgsstv_polynomial[] = {0155, 0117};
#define KGSSTV_SYNC_WORD_SIZE sizeof(KGSSTV_SYNC_WORD)
#define KGSSTV_SYNC_SCRAMBLING_SIZE sizeof(KGSSTV_SCRAMBLING)
namespace kgsstv {
// class Slice4FSK : public dsp::generic_block<Slice4FSK> {
// public:
// Slice4FSK() {}
// Slice4FSK(dsp::stream<float>* in) { init(in); }
// void init(dsp::stream<float>* in) {
// _in = in;
// dsp::generic_block<Slice4FSK>::registerInput(_in);
// dsp::generic_block<Slice4FSK>::registerOutput(&out);
// dsp::generic_block<Slice4FSK>::_block_init = true;
// }
// void setInput(dsp::stream<float>* in) {
// assert(dsp::generic_block<Slice4FSK>::_block_init);
// std::lock_guard<std::mutex> lck(dsp::generic_block<Slice4FSK>::ctrlMtx);
// dsp::generic_block<Slice4FSK>::tempStop();
// dsp::generic_block<Slice4FSK>::unregisterInput(_in);
// _in = in;
// dsp::generic_block<Slice4FSK>::registerInput(_in);
// dsp::generic_block<Slice4FSK>::tempStart();
// }
// int run() {
// int count = _in->read();
// if (count < 0) { return -1; }
// float val;
// for (int i = 0; i < count; i++) {
// val = _in->readBuf[i];
// out.writeBuf[i * 2] = (val > 0.0f);
// if (val > 0.0f) {
// out.writeBuf[(i * 2) + 1] = (val > KGSSTV_4FSK_HIGH_CUT);
// }
// else {
// out.writeBuf[(i * 2) + 1] = (val > -KGSSTV_4FSK_HIGH_CUT);
// }
// }
// _in->flush();
// if (!out.swap(count * 2)) { return -1; }
// return count;
// }
// dsp::stream<uint8_t> out;
// private:
// dsp::stream<float>* _in;
// };
class Deframer : public dsp::generic_block<Deframer> {
public:
Deframer() {}
Deframer(dsp::stream<float>* in) { init(in); }
void init(dsp::stream<float>* in) {
_in = in;
// TODO: Destroy
conv = correct_convolutional_create(2, 7, kgsstv_polynomial);
memset(convTmp, 0x00, 1024);
dsp::generic_block<Deframer>::registerInput(_in);
dsp::generic_block<Deframer>::registerOutput(&out);
dsp::generic_block<Deframer>::_block_init = true;
}
void setInput(dsp::stream<float>* in) {
assert(dsp::generic_block<Deframer>::_block_init);
std::lock_guard<std::mutex> lck(dsp::generic_block<Deframer>::ctrlMtx);
dsp::generic_block<Deframer>::tempStop();
dsp::generic_block<Deframer>::unregisterInput(_in);
_in = in;
dsp::generic_block<Deframer>::registerInput(_in);
dsp::generic_block<Deframer>::tempStart();
}
int run() {
int count = _in->read();
if (count < 0) { return -1; }
for (int i = 0; i < count; i++) {
if (syncing) {
// If sync broken, reset sync
if ((_in->readBuf[i] > 0.0f) && !KGSSTV_SYNC_WORD[match]) {
if (++err > 4) {
i -= match - 1;
match = 0;
err = 0;
continue;
}
}
// If full syncword was detected, switch to read mode
if (++match == KGSSTV_SYNC_WORD_SIZE) {
spdlog::warn("Frame detected");
syncing = false;
readCount = 0;
writeCount = 0;
}
}
else {
// // Process symbol
// if (!(readCount % 2)) {
// int bitOffset = writeCount & 0b111;
// int byteOffset = writeCount >> 3;
// if (!bitOffset) { convTmp[byteOffset] = 0; }
// convTmp[byteOffset] |= _in->readBuf[i] << (7 - bitOffset);
// writeCount++;
// }
// Process symbol
convTmp[readCount] = std::clamp<int>((_in->readBuf[i] + 1.0f) * 128.0f, 0, 255);
// When info was read, write data and get back to
if (++readCount == 108) {
match = 0;
err = 0;
syncing = true;
// Descramble
for (int j = 0; j < 108; j++) {
if (KGSSTV_SCRAMBLING[j]) {
convTmp[j] = 255 - convTmp[j];
}
//convTmp[j >> 3] ^= KGSSTV_SCRAMBLING[j] << (7 - (j & 0b111));
}
// Decode convolutional code
int convOutCount = correct_convolutional_decode_soft(conv, convTmp, 124, out.writeBuf);
spdlog::warn("Frames written: {0}, frameBytes: {1}", ++framesWritten, convOutCount);
if (!out.swap(7)) {
_in->flush();
return -1;
}
}
}
}
_in->flush();
return count;
}
dsp::stream<uint8_t> out;
private:
dsp::stream<float>* _in;
correct_convolutional* conv = NULL;
uint8_t convTmp[1024];
int match = 0;
int err = 0;
int readCount = 0;
int writeCount = 0;
bool syncing = true;
int framesWritten = 0;
};
class Decoder : public dsp::generic_hier_block<Decoder> {
public:
Decoder() {}
Decoder(dsp::stream<dsp::complex_t>* input, float sampleRate) {
init(input, sampleRate);
}
void init(dsp::stream<dsp::complex_t>* input, float sampleRate) {
_sampleRate = sampleRate;
demod.init(input, _sampleRate, KGSSTV_DEVIATION);
rrc.init(31, _sampleRate, KGSSTV_BAUDRATE, KGSSTV_RRC_ALPHA);
fir.init(&demod.out, &rrc);
recov.init(&fir.out, _sampleRate / KGSSTV_BAUDRATE, 1e-6f, 0.01f, 0.01f);
doubler.init(&recov.out);
//slicer.init(&doubler.outA);
deframer.init(&doubler.outA);
ns2.init(&deframer.out, "kgsstv_out.bin");
diagOut = &doubler.outB;
dsp::generic_hier_block<Decoder>::registerBlock(&demod);
dsp::generic_hier_block<Decoder>::registerBlock(&fir);
dsp::generic_hier_block<Decoder>::registerBlock(&recov);
dsp::generic_hier_block<Decoder>::registerBlock(&doubler);
//dsp::generic_hier_block<Decoder>::registerBlock(&slicer);
dsp::generic_hier_block<Decoder>::registerBlock(&deframer);
dsp::generic_hier_block<Decoder>::registerBlock(&ns2);
dsp::generic_hier_block<Decoder>::_block_init = true;
}
void setInput(dsp::stream<dsp::complex_t>* input) {
assert(dsp::generic_hier_block<Decoder>::_block_init);
demod.setInput(input);
}
dsp::stream<float>* diagOut = NULL;
private:
dsp::FloatFMDemod demod;
dsp::RRCTaps rrc;
dsp::FIR<float> fir;
dsp::MMClockRecovery<float> recov;
dsp::StreamDoubler<float> doubler;
// Slice4FSK slicer;
Deframer deframer;
dsp::FileSink<uint8_t> ns2;
float _sampleRate;
};
}

View File

@ -0,0 +1,193 @@
#include <imgui.h>
#include <config.h>
#include <core.h>
#include <gui/style.h>
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <filesystem>
#include <dsp/pll.h>
#include <dsp/stream.h>
#include <dsp/demodulator.h>
#include <dsp/window.h>
#include <dsp/resampling.h>
#include <dsp/processing.h>
#include <dsp/routing.h>
#include <dsp/sink.h>
#include <gui/widgets/folder_select.h>
#include <gui/widgets/symbol_diagram.h>
#include <fstream>
#include <chrono>
#include "kg_sstv_dsp.h"
#define CONCAT(a, b) ((std::string(a) + b).c_str())
SDRPP_MOD_INFO{
/* Name: */ "kg_sstv_decoder",
/* Description: */ "KG-SSTV Digital SSTV Decoder for SDR++",
/* Author: */ "Ryzerth",
/* Version: */ 0, 1, 0,
/* Max instances */ -1
};
ConfigManager config;
#define INPUT_SAMPLE_RATE 6000
class M17DecoderModule : public ModuleManager::Instance {
public:
M17DecoderModule(std::string name) : diag(0.8, 480) {
this->name = name;
// Load config
config.acquire();
if (!config.conf.contains(name)) {
config.conf[name]["showLines"] = false;
}
showLines = config.conf[name]["showLines"];
if (showLines) {
diag.lines.push_back(-0.75f);
diag.lines.push_back(-0.25f);
diag.lines.push_back(0.25f);
diag.lines.push_back(0.75f);
}
config.release(true);
// Initialize VFO
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, 3000, INPUT_SAMPLE_RATE, 3000, 3000, true);
vfo->setSnapInterval(250);
// Initialize DSP here
decoder.init(vfo->output, INPUT_SAMPLE_RATE);
reshape.init(decoder.diagOut, 480, 0);
diagHandler.init(&reshape.out, _diagHandler, this);
// Start DSO Here
decoder.start();
reshape.start();
diagHandler.start();
//stream.start();
gui::menu.registerEntry(name, menuHandler, this, this);
}
~M17DecoderModule() {
gui::menu.removeEntry(name);
// Stop DSP Here
if (enabled) {
decoder.stop();
reshape.stop();
diagHandler.stop();
sigpath::vfoManager.deleteVFO(vfo);
}
sigpath::sinkManager.unregisterStream(name);
}
void postInit() {}
void enable() {
double bw = gui::waterfall.getBandwidth();
vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp<double>(0, -bw / 2.0, bw / 2.0), 9600, INPUT_SAMPLE_RATE, 9600, 9600, true);
vfo->setSnapInterval(250);
// Set Input of demod here
decoder.setInput(vfo->output);
// Start DSP here
decoder.start();
reshape.start();
diagHandler.start();
enabled = true;
}
void disable() {
// Stop DSP here
decoder.stop();
reshape.stop();
diagHandler.stop();
sigpath::vfoManager.deleteVFO(vfo);
enabled = false;
}
bool isEnabled() {
return enabled;
}
private:
static void menuHandler(void* ctx) {
M17DecoderModule* _this = (M17DecoderModule*)ctx;
float menuWidth = ImGui::GetContentRegionAvail().x;
if (!_this->enabled) { style::beginDisabled(); }
ImGui::SetNextItemWidth(menuWidth);
_this->diag.draw();
if (ImGui::Checkbox(CONCAT("Show Reference Lines##m17_showlines_", _this->name), &_this->showLines)) {
if (_this->showLines) {
_this->diag.lines.push_back(-0.75f);
_this->diag.lines.push_back(-0.25f);
_this->diag.lines.push_back(0.25f);
_this->diag.lines.push_back(0.75f);
}
else {
_this->diag.lines.clear();
}
config.acquire();
config.conf[_this->name]["showLines"] = _this->showLines;
config.release(true);
}
if (!_this->enabled) { style::endDisabled(); }
}
static void _diagHandler(float* data, int count, void* ctx) {
M17DecoderModule* _this = (M17DecoderModule*)ctx;
float* buf = _this->diag.acquireBuffer();
memcpy(buf, data, count * sizeof(float));
_this->diag.releaseBuffer();
}
std::string name;
bool enabled = true;
// DSP Chain
VFOManager::VFO* vfo;
kgsstv::Decoder decoder;
dsp::Reshaper<float> reshape;
dsp::HandlerSink<float> diagHandler;
dsp::stream<float> dummy;
ImGui::SymbolDiagram diag;
bool showLines = false;
};
MOD_EXPORT void _INIT_() {
// Create default recording directory
json def = json({});
config.setPath(core::args["root"].s() + "/kg_sstv_decoder_config.json");
config.load(def);
config.enableAutoSave();
}
MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
return new M17DecoderModule(name);
}
MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
delete (M17DecoderModule*)instance;
}
MOD_EXPORT void _END_() {
config.disableAutoSave();
config.save();
}

View File

@ -5,7 +5,6 @@
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <options.h>
#include <filesystem>
#include <dsp/pll.h>
#include <dsp/stream.h>
@ -296,7 +295,7 @@ private:
MOD_EXPORT void _INIT_() {
// Create default recording directory
json def = json({});
config.setPath(options::opts.root + "/m17_decoder_config.json");
config.setPath(core::args["root"].s() + "/m17_decoder_config.json");
config.load(def);
config.enableAutoSave();
}

View File

@ -5,7 +5,6 @@
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <options.h>
#include <filesystem>
#include <dsp/pll.h>
#include <dsp/stream.h>
@ -244,14 +243,15 @@ private:
MOD_EXPORT void _INIT_() {
// Create default recording directory
if (!std::filesystem::exists(options::opts.root + "/recordings")) {
std::string root = core::args["root"];
if (!std::filesystem::exists(root + "/recordings")) {
spdlog::warn("Recordings directory does not exist, creating it");
if (!std::filesystem::create_directory(options::opts.root + "/recordings")) {
if (!std::filesystem::create_directory(root + "/recordings")) {
spdlog::error("Could not create recordings directory");
}
}
json def = json({});
config.setPath(options::opts.root + "/meteor_demodulator_config.json");
config.setPath(root + "/meteor_demodulator_config.json");
config.load(def);
config.enableAutoSave();
}

View File

@ -1,5 +1,4 @@
#include "radio_module.h"
#include <options.h>
SDRPP_MOD_INFO{
/* Name: */ "radio",
@ -11,7 +10,7 @@ SDRPP_MOD_INFO{
MOD_EXPORT void _INIT_() {
json def = json({});
config.setPath(options::opts.root + "/radio_config.json");
config.setPath(core::args["root"].s() + "/radio_config.json");
config.load(def);
config.enableAutoSave();
}

View File

@ -5,7 +5,6 @@
#include <gui/gui.h>
#include <signal_path/signal_path.h>
#include <module.h>
#include <options.h>
#include <dsp/pll.h>
#include <dsp/stream.h>