temp
This commit is contained in:
parent
dda8cd928e
commit
fe6315e468
@ -3,14 +3,11 @@ target_compile_options(blocky PRIVATE ${BLOCKY_C_FLAGS})
|
||||
|
||||
target_sources(blocky
|
||||
PUBLIC
|
||||
main.cc
|
||||
|
||||
bytes.hh
|
||||
args.hh
|
||||
common.hh
|
||||
embed.hh
|
||||
extract.hh
|
||||
video_encoder.hh
|
||||
video_decoder.hh
|
||||
embed.cc
|
||||
extract.cc
|
||||
main.cc
|
||||
)
|
||||
target_link_libraries(blocky
|
||||
PUBLIC
|
||||
|
92
blocky/args.hh
Normal file
92
blocky/args.hh
Normal file
@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
#include <args.hxx>
|
||||
|
||||
#include "common.hh"
|
||||
|
||||
|
||||
namespace blky::args {
|
||||
|
||||
using namespace ::args;
|
||||
|
||||
|
||||
struct uvec2_Reader final {
|
||||
void operator()(const std::string&, const std::string& value, uvec2& dst) {
|
||||
char _;
|
||||
if (3 != sscanf(value.c_str(), "%" PRIu32 "%c%" PRIu32, &dst.x, &_, &dst.y)) {
|
||||
throw std::runtime_error {"invalid uvec2 specification: "+value};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/* ---- root parser ---- */
|
||||
static inline ArgumentParser parser {
|
||||
"blocky is a command line tool for data hiding of video"
|
||||
};
|
||||
static inline Group commands { parser, "COMMAND:" };
|
||||
static inline HelpFlag help { parser, "help", "display this help menu", {'h', "help"}, Options::Global };
|
||||
|
||||
|
||||
/* ---- group: key ---- */
|
||||
static inline Group key { "key information:" };
|
||||
static inline ValueFlag<uvec2, uvec2_Reader> block_num {
|
||||
key, "8x8", "number of blocks", {"block-num"}, {8, 8}
|
||||
};
|
||||
static inline ValueFlag<uint32_t> feat_bits {
|
||||
key, "1", "bits per each step", {"feat-bits"}, 1
|
||||
};
|
||||
static inline ValueFlag<uint32_t> seed {
|
||||
key, "123", "random seed for hopping", {"seed"}, 123
|
||||
};
|
||||
static inline ValueFlag<uint32_t> utime {
|
||||
key, "10", "duration of each step (frames)", {"utime"}, 10
|
||||
};
|
||||
|
||||
|
||||
/* ---- group: stream source ---- */
|
||||
static inline Group stream { "stream source (choose one):", Group::Validators::AtMostOne };
|
||||
static inline Flag stream_stdin {
|
||||
stream, "stdin", "load from stdin (default)", {"stdin"}, true
|
||||
};
|
||||
static inline Flag stream_stdin_hex {
|
||||
stream, "stdin-hex", "load from stdin as HEX (e.g 'FF00'", {"stdin-hex"}
|
||||
};
|
||||
|
||||
|
||||
/* ---- command: embed ---- */
|
||||
static inline Group embed {};
|
||||
static inline Positional<std::string> embed_dst {
|
||||
embed, "dst", "destination video path", Options::Required
|
||||
};
|
||||
static inline Positional<std::string> embed_src {
|
||||
embed, "src", "source video path", Options::Required
|
||||
};
|
||||
static inline Command command_embed { commands, "embed", "embed stream to video", [](auto& p) {
|
||||
p.Add(embed);
|
||||
p.Add(stream);
|
||||
p.Add(key);
|
||||
p.Parse();
|
||||
Cmd_Embed();
|
||||
}};
|
||||
|
||||
|
||||
/* ---- command: extract ---- */
|
||||
static inline Group extract {};
|
||||
static inline Positional<std::string> extract_src {
|
||||
extract, "src", "source video path", Options::Required
|
||||
};
|
||||
static inline Command command_extract { commands, "extract", "extract stream from video", [](auto& p) {
|
||||
p.Add(extract);
|
||||
p.Add(key);
|
||||
p.Parse();
|
||||
Cmd_Extract();
|
||||
}};
|
||||
|
||||
} // namespace blky::args
|
@ -1,75 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "common.hh"
|
||||
|
||||
|
||||
namespace blky {
|
||||
|
||||
std::vector<uint32_t> BytesEncoder(
|
||||
const std::vector<uint8_t>& bytes,
|
||||
std::tuple<uint32_t, uint32_t>& block_num,
|
||||
uint8_t feat_bits,
|
||||
uint32_t block_idx,
|
||||
uint64_t seed) {
|
||||
const uint32_t block_num_all = std::get<0>(block_num)*std::get<1>(block_num);
|
||||
if (block_num_all == 0) throw std::runtime_error {"block num is zero"};
|
||||
|
||||
blky_encoder_t enc = {};
|
||||
enc.block_num = block_num_all;
|
||||
enc.feat_bits = feat_bits;
|
||||
enc.block_index = block_idx;
|
||||
enc.seed = seed;
|
||||
|
||||
std::vector<uint32_t> ret;
|
||||
for (auto c : bytes) {
|
||||
blky_encoder_feed(&enc, c);
|
||||
|
||||
uint32_t feat;
|
||||
while (blky_encoder_pop(&enc, &feat, false)) {
|
||||
ret.push_back(feat);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t feat;
|
||||
if (blky_encoder_pop(&enc, &feat, true)) {
|
||||
ret.push_back(feat);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> BytesDecoder(
|
||||
const std::vector<uint32_t>& features,
|
||||
std::tuple<uint32_t, uint32_t>& block_num,
|
||||
uint8_t feat_bits,
|
||||
uint64_t seed) {
|
||||
const uint32_t block_num_all = std::get<0>(block_num)*std::get<1>(block_num);
|
||||
if (block_num_all == 0) throw std::runtime_error {"block num is zero"};
|
||||
|
||||
blky_decoder_t de = {};
|
||||
de.block_num = block_num_all;
|
||||
de.feat_bits = feat_bits;
|
||||
de.seed = seed;
|
||||
|
||||
std::vector<uint8_t> ret;
|
||||
for (auto c : features) {
|
||||
if (!blky_decoder_feed(&de, c)) {
|
||||
throw std::runtime_error {"path corruption"};
|
||||
}
|
||||
|
||||
uint8_t feat;
|
||||
while (blky_decoder_pop(&de, &feat, false)) {
|
||||
ret.push_back(feat);
|
||||
}
|
||||
}
|
||||
uint8_t feat;
|
||||
if (blky_decoder_pop(&de, &feat, true)) {
|
||||
ret.push_back(feat);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace blky
|
@ -1,59 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
extern "C" {
|
||||
# include <liblocky.h>
|
||||
}
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace blky {
|
||||
|
||||
enum DataFlow {
|
||||
kBytes,
|
||||
kFeatures,
|
||||
kFeatureProbs,
|
||||
kVideo,
|
||||
};
|
||||
|
||||
static inline const std::string kDataFlowList = "bytes/features/video";
|
||||
static inline const std::unordered_map<std::string, DataFlow> kDataFlowMap = {
|
||||
{"bytes", kBytes},
|
||||
{"features", kFeatures},
|
||||
{"feature-probs", kFeatureProbs},
|
||||
{"video", kVideo},
|
||||
};
|
||||
struct uvec2 final { uint32_t x, y; };
|
||||
|
||||
|
||||
template <typename T>
|
||||
std::vector<T> ReadAll(auto& ist) noexcept {
|
||||
std::vector<T> ret;
|
||||
for (;;) {
|
||||
T v;
|
||||
ist >> v;
|
||||
if (ist.eof()) return ret;
|
||||
ret.push_back(v);
|
||||
}
|
||||
}
|
||||
/* ---- global state accessors ---- */
|
||||
std::istream& GetStream();
|
||||
|
||||
static uint8_t ToHex(char c) {
|
||||
if (!std::isxdigit(c)) throw std::runtime_error {"not xdigit"};
|
||||
return static_cast<uint8_t>(std::isalpha(c)? std::tolower(c)-'a'+0xA: c-'0');
|
||||
}
|
||||
|
||||
/* ---- command handlers ---- */
|
||||
void Cmd_Embed();
|
||||
void Cmd_Extract();
|
||||
|
||||
} // namespace blky
|
||||
|
||||
|
||||
namespace args {
|
||||
|
||||
auto& operator>>(auto& ist, std::tuple<uint32_t, uint32_t>& v) {
|
||||
ist >> std::get<0>(v) >> std::get<1>(v);
|
||||
return ist;
|
||||
}
|
||||
|
||||
} // namespace args
|
||||
|
10
blocky/embed.cc
Normal file
10
blocky/embed.cc
Normal file
@ -0,0 +1,10 @@
|
||||
#include "common.hh"
|
||||
|
||||
|
||||
namespace blky {
|
||||
|
||||
void Cmd_Embed() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // namespace blky
|
114
blocky/embed.hh
114
blocky/embed.hh
@ -1,114 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
|
||||
#include "video_decoder.hh"
|
||||
#include "video_encoder.hh"
|
||||
|
||||
|
||||
namespace blky {
|
||||
|
||||
void Embed(const std::vector<uint32_t>& features,
|
||||
const std::string& dst_path,
|
||||
const std::string& video_path,
|
||||
const std::tuple<uint32_t, uint32_t>& div,
|
||||
uint32_t utime) {
|
||||
assert(features.size());
|
||||
|
||||
const uint32_t div_x = std::get<0>(div);
|
||||
const uint32_t div_y = std::get<1>(div);
|
||||
|
||||
VideoDecoder dec {video_path};
|
||||
std::optional<VideoEncoder> enc;
|
||||
|
||||
std::vector<uint8_t> Y;
|
||||
std::vector<uint8_t> U;
|
||||
std::vector<uint8_t> V;
|
||||
|
||||
uint32_t last_feat = UINT32_MAX;
|
||||
std::vector<uint8_t> feat_pix;
|
||||
for (uint32_t time = 0; dec.Decode();) {
|
||||
const auto& src = dec.frame();
|
||||
if (src.iBufferStatus != 1) continue;
|
||||
++time;
|
||||
|
||||
const uint32_t w = static_cast<uint32_t>(src.UsrData.sSystemBuffer.iWidth);
|
||||
const uint32_t h = static_cast<uint32_t>(src.UsrData.sSystemBuffer.iHeight);
|
||||
|
||||
const uint32_t stride_y = static_cast<uint32_t>(src.UsrData.sSystemBuffer.iStride[0]);
|
||||
const uint32_t stride_uv = static_cast<uint32_t>(src.UsrData.sSystemBuffer.iStride[1]);
|
||||
|
||||
const uint8_t* const* srcp = src.pDst;
|
||||
|
||||
// copy buffer to modify
|
||||
Y.resize(w*h);
|
||||
U.resize(w*h/2/2);
|
||||
V.resize(w*h/2/2);
|
||||
for (uint32_t y = 0; y < h; ++y) {
|
||||
std::memcpy(Y.data()+y*w, srcp[0]+stride_y*y, w);
|
||||
}
|
||||
for (uint32_t y = 0; y < h/2; ++y) {
|
||||
std::memcpy(U.data()+y*(w/2), srcp[1]+stride_uv*y, w/2);
|
||||
std::memcpy(V.data()+y*(w/2), srcp[2]+stride_uv*y, w/2);
|
||||
}
|
||||
|
||||
// embed a feature to the buffer
|
||||
const uint32_t feat = features[(time/utime)%features.size()];
|
||||
|
||||
const uint32_t feat_x = feat%div_x;
|
||||
const uint32_t feat_y = feat/div_x;
|
||||
|
||||
const uint32_t feat_size_x = w/div_x;
|
||||
const uint32_t feat_size_y = h/div_y;
|
||||
|
||||
const uint32_t feat_offset_x = feat_x*feat_size_x;
|
||||
const uint32_t feat_offset_y = feat_y*feat_size_y;
|
||||
|
||||
if (feat != last_feat) {
|
||||
feat_pix.resize(feat_size_x*feat_size_y);
|
||||
for (uint32_t y = 0; y < feat_size_y; ++y) {
|
||||
const uint32_t ay = y+feat_offset_y;
|
||||
std::memcpy(feat_pix.data()+y*feat_size_x, Y.data()+ay*w+feat_offset_x, feat_size_x);
|
||||
//std::memcpy(feat_pix.data()+(y/2)*(feat_size_x/2), U.data()+(ay/2)*(w/2)+feat_offset_x/2, feat_size_x/2);
|
||||
//std::memcpy(feat_pix.data()+(y/2)*(feat_size_x/2)+feat_pix.size()/2, V.data()+(ay/2)*(w/2)+feat_offset_x/2, feat_size_x/2);
|
||||
}
|
||||
last_feat = feat;
|
||||
if (enc) enc->ForceIntraFrame();
|
||||
}
|
||||
for (uint32_t y = 0; y < feat_size_y; ++y) {
|
||||
const uint32_t ay = y+feat_offset_y;
|
||||
std::memcpy(Y.data()+ay*w+feat_offset_x, feat_pix.data()+y*feat_size_x, feat_size_x);
|
||||
//std::memcpy(U.data()+(ay/2)*(w/2)+feat_offset_x/2, feat_pix.data()+(y/2)*(feat_size_x/2), feat_size_x/2);
|
||||
//std::memcpy(V.data()+(ay/2)*(w/2)+feat_offset_x/2, feat_pix.data()+(y/2)*(feat_size_x/2)+feat_pix.size()/2, feat_size_x/2);
|
||||
}
|
||||
|
||||
// create an encoder if not yet
|
||||
if (!enc) {
|
||||
SEncParamBase param = {};
|
||||
param.iUsageType = CAMERA_VIDEO_REAL_TIME;
|
||||
param.iPicWidth = static_cast<int>(w);
|
||||
param.iPicHeight = static_cast<int>(h);
|
||||
param.fMaxFrameRate = 30;
|
||||
param.iTargetBitrate = 5000000;
|
||||
enc.emplace(dst_path, param);
|
||||
}
|
||||
|
||||
// encode
|
||||
SSourcePicture dst = {};
|
||||
dst.iColorFormat = videoFormatI420;
|
||||
dst.pData[0] = Y.data();
|
||||
dst.pData[1] = U.data();
|
||||
dst.pData[2] = V.data();
|
||||
dst.iStride[0] = static_cast<int>(w);
|
||||
dst.iStride[1] = static_cast<int>(w/2);
|
||||
dst.iStride[2] = static_cast<int>(w/2);
|
||||
dst.iPicWidth = static_cast<int>(w);
|
||||
dst.iPicHeight = static_cast<int>(h);
|
||||
dst.uiTimeStamp = static_cast<int64_t>(src.uiOutYuvTimeStamp);
|
||||
enc->Encode(dst);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blky
|
10
blocky/extract.cc
Normal file
10
blocky/extract.cc
Normal file
@ -0,0 +1,10 @@
|
||||
#include "common.hh"
|
||||
|
||||
|
||||
namespace blky {
|
||||
|
||||
void Cmd_Extract() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
} // namespace blky
|
@ -1,65 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include "common.hh"
|
||||
#include "video_decoder.hh"
|
||||
|
||||
|
||||
namespace blky {
|
||||
|
||||
std::vector<double> Extract(
|
||||
VideoDecoder& dec,
|
||||
std::tuple<uint32_t, uint32_t> block_num,
|
||||
std::tuple<uint32_t, uint32_t> sensor_num,
|
||||
uint32_t utime) {
|
||||
std::vector<double> ret;
|
||||
|
||||
blky_extractor_t ex = {};
|
||||
ex.block_num_x = std::get<0>(block_num);
|
||||
ex.block_num_y = std::get<1>(block_num);
|
||||
ex.sensor_num_block_x = std::get<0>(sensor_num);
|
||||
ex.sensor_num_block_y = std::get<1>(sensor_num);
|
||||
ex.samples_per_pix = 1;
|
||||
ex.pix_stride = 1;
|
||||
ex.utime = utime;
|
||||
ex.correl_max_var = 0.3;
|
||||
ex.correl_min_avg = 0.8;
|
||||
|
||||
blky_extractor_init(&ex);
|
||||
while (dec.Decode()) {
|
||||
const auto& frame = dec.frame();
|
||||
if (frame.iBufferStatus != 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint32_t w = static_cast<uint32_t>(frame.UsrData.sSystemBuffer.iWidth);
|
||||
const uint32_t h = static_cast<uint32_t>(frame.UsrData.sSystemBuffer.iHeight);
|
||||
|
||||
const uint32_t stride_y = static_cast<uint32_t>(frame.UsrData.sSystemBuffer.iStride[0]);
|
||||
//const uint32_t stride_uv = static_cast<uint32_t>(frame.UsrData.sSystemBuffer.iStride[1]);
|
||||
|
||||
const uint8_t* const* srcp = frame.pDst;
|
||||
|
||||
const double wf = static_cast<double>(w) / static_cast<double>(stride_y);
|
||||
const double verts[8] = {
|
||||
0, 0,
|
||||
0, 1,
|
||||
wf, 1,
|
||||
wf, 0,
|
||||
};
|
||||
if (blky_extractor_feed(&ex, srcp[0], stride_y, h, verts)) {
|
||||
const size_t psize = ret.size();
|
||||
ret.resize(ret.size() + ex.block_num);
|
||||
std::memcpy(ret.data() + psize, ex.probs, ex.block_num * sizeof(ret[0]));
|
||||
}
|
||||
}
|
||||
blky_extractor_deinit(&ex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace blky
|
@ -1,105 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <tuple>
|
||||
|
||||
#include "common.hh"
|
||||
|
||||
|
||||
namespace blky {
|
||||
|
||||
std::vector<uint32_t> PathfindFeatures(
|
||||
const std::vector<double>& probs,
|
||||
std::tuple<uint32_t, uint32_t>& block_num,
|
||||
uint8_t feat_bits,
|
||||
uint64_t seed) {
|
||||
const uint32_t num = std::get<0>(block_num) * std::get<1>(block_num);
|
||||
if (num == 0) throw std::runtime_error {"number of blocks is zero"};
|
||||
|
||||
const uint64_t dur = probs.size()/num;
|
||||
if (probs.size() == 0) {
|
||||
throw std::runtime_error {"size of probability matrix is empty"};
|
||||
}
|
||||
if (dur*num != probs.size()) {
|
||||
throw std::runtime_error {"size of probability matrix is mismatch"};
|
||||
}
|
||||
|
||||
blky_pathfinder_t pf = {};
|
||||
pf.block_num = num;
|
||||
pf.feat_bits = feat_bits;
|
||||
pf.seed = seed;
|
||||
|
||||
blky_pathfinder_init(&pf);
|
||||
|
||||
for (uint64_t t = 0; t < dur; ++t) {
|
||||
blky_pathfinder_feed(&pf, probs.data()+t*num);
|
||||
}
|
||||
assert(pf.step_last);
|
||||
|
||||
uint32_t max_idx = 0;
|
||||
double max_prob = -1;
|
||||
for (uint32_t i = 0; i < num; ++i) {
|
||||
if (max_prob < pf.probs[i]) {
|
||||
max_prob = pf.probs[i];
|
||||
max_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint32_t> ret;
|
||||
blky_pathfinder_step_t* step = pf.step_last;
|
||||
uint32_t idx = max_idx;
|
||||
for (;;) {
|
||||
ret.push_back(idx);
|
||||
if (!step) break;
|
||||
idx = step->indices[idx];
|
||||
step = step->prev;
|
||||
}
|
||||
std::reverse(ret.begin(), ret.end());
|
||||
|
||||
blky_pathfinder_deinit(&pf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<double> GenerateFeatureProbs(
|
||||
const std::vector<uint32_t>& features,
|
||||
std::tuple<uint32_t, uint32_t>& block_num,
|
||||
double false_positive,
|
||||
double false_negative,
|
||||
uint64_t seed,
|
||||
bool normalize) {
|
||||
const uint32_t num = std::get<0>(block_num) * std::get<1>(block_num);
|
||||
if (num == 0) throw std::runtime_error {"number of blocks is zero"};
|
||||
|
||||
std::vector<double> ret;
|
||||
for (auto c : features) {
|
||||
const auto begin = ret.size();
|
||||
|
||||
double sum = 0;
|
||||
for (uint32_t bi = 0; bi < num; ++bi) {
|
||||
seed = blky_numeric_xorshift64(seed);
|
||||
const double prob1 = static_cast<double>(seed%1000)/1000.;
|
||||
|
||||
seed = blky_numeric_xorshift64(seed);
|
||||
const double prob2 = static_cast<double>(seed%1000)/1000.;
|
||||
|
||||
seed = blky_numeric_xorshift64(seed);
|
||||
double fprob;
|
||||
if ((c == bi && prob1 >= false_negative) || prob2 < false_positive) {
|
||||
fprob = static_cast<double>(seed%200)/1000. + .8;
|
||||
} else {
|
||||
fprob = static_cast<double>(seed%800)/1000.;
|
||||
}
|
||||
ret.push_back(fprob);
|
||||
sum += fprob;
|
||||
}
|
||||
if (normalize) {
|
||||
for (size_t i = begin; i < ret.size(); ++i) {
|
||||
ret[i] /= sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace blky
|
304
blocky/main.cc
304
blocky/main.cc
@ -1,319 +1,25 @@
|
||||
#include <cassert>
|
||||
#include <exception>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
#include "common.hh"
|
||||
#include "bytes.hh"
|
||||
#include "embed.hh"
|
||||
#include "extract.hh"
|
||||
#include "features.hh"
|
||||
|
||||
#include <args.hxx>
|
||||
|
||||
#include "video_decoder.hh"
|
||||
#include "video_encoder.hh"
|
||||
#include "args.hh"
|
||||
|
||||
|
||||
using namespace blky;
|
||||
|
||||
|
||||
args::ArgumentParser parser(
|
||||
"liblocky command line tool",
|
||||
"liblocky allow you to embed a bit array into video data secretly");
|
||||
|
||||
args::MapFlag<std::string, DataFlow> from {
|
||||
parser, kDataFlowList, "input layer specifier", {"from"}, kDataFlowMap, args::Options::Required};
|
||||
|
||||
args::MapFlag<std::string, DataFlow> to {
|
||||
parser, kDataFlowList, "output layer specifier", {"to"}, kDataFlowMap, args::Options::Required};
|
||||
|
||||
args::Group src_group {
|
||||
parser, "source specifier", args::Group::Validators::Xor, args::Options::Required};
|
||||
args::Flag src_stdin {src_group, "src-stdin", "read from stdin", {"src-stdin", "stdin"}};
|
||||
args::Flag src_stdin_hex {src_group, "src-stdin-hex", "read hex text from stdin", {"src-stdin-hex", "stdin-hex"}};
|
||||
args::ValueFlag<std::string> src_video {src_group, "path", "video input", {"src-video"}};
|
||||
|
||||
args::Group dst_group {
|
||||
parser, "destination specifier", args::Group::Validators::Xor, args::Options::Required};
|
||||
args::Flag dst_stdout {dst_group, "dst-stdout", "write to stdout", {"dst-stdout", "stdout"}};
|
||||
args::Flag dst_stdout_hex {dst_group, "dst-stdout-hex", "write to stdout as hex text", {"dst-stdout-hex", "stdout-hex"}};
|
||||
args::ValueFlag<std::string> dst_video {dst_group, "path", "video output", {"dst-video"}};
|
||||
|
||||
args::Group param_group {
|
||||
parser, "general parameters", args::Group::Validators::DontCare
|
||||
};
|
||||
args::ValueFlag<std::tuple<uint32_t, uint32_t>> param_block_num {
|
||||
param_group,
|
||||
"int>0,int>0",
|
||||
"number of features",
|
||||
{"feature-num"},
|
||||
{8, 8}
|
||||
};
|
||||
args::ValueFlag<uint32_t> param_block_first {
|
||||
param_group,
|
||||
"int>=0",
|
||||
"an index of first block where feature will be embedded. used when encoding",
|
||||
{"feature-first-index"},
|
||||
0
|
||||
};
|
||||
args::ValueFlag<uint8_t> param_feat_bits {
|
||||
param_group,
|
||||
"int>0",
|
||||
"number of bits that can be represented by a single feature",
|
||||
{"feature-bits"},
|
||||
1
|
||||
};
|
||||
args::ValueFlag<uint8_t> param_seed {
|
||||
param_group,
|
||||
"int>0",
|
||||
"seed number for hopping randomization",
|
||||
{"seed"},
|
||||
123
|
||||
};
|
||||
args::ValueFlag<std::string> param_video {
|
||||
param_group,
|
||||
"path",
|
||||
"a video file where information is embed",
|
||||
{"path"},
|
||||
};
|
||||
args::ValueFlag<uint32_t> param_utime {
|
||||
param_group,
|
||||
"int>0",
|
||||
"a duration (milliseconds) of features",
|
||||
{"utime"},
|
||||
10
|
||||
};
|
||||
args::ValueFlag<std::tuple<uint32_t, uint32_t>> param_sensor_num {
|
||||
param_group,
|
||||
"int>0,int>0",
|
||||
"number of sensors (for extraction)",
|
||||
{"sensor-num"},
|
||||
{16, 16}
|
||||
};
|
||||
|
||||
args::Group probgen_group {
|
||||
parser, "params for feature probability generator", args::Group::Validators::DontCare
|
||||
};
|
||||
args::ValueFlag<double> probgen_false_positive {
|
||||
probgen_group,
|
||||
"0<=double<=1",
|
||||
"false positive ratio in feature probability generation",
|
||||
{"probgen-false-positive"},
|
||||
0
|
||||
};
|
||||
args::ValueFlag<double> probgen_false_negative {
|
||||
probgen_group,
|
||||
"0<=double<=1",
|
||||
"false negative ratio in feature probability generation",
|
||||
{"probgen-false-negative"},
|
||||
0
|
||||
};
|
||||
args::ValueFlag<uint64_t> probgen_seed {
|
||||
probgen_group,
|
||||
"int>0",
|
||||
"random seed",
|
||||
{"probgen-seed"},
|
||||
1
|
||||
};
|
||||
args::Flag probgen_normalize {
|
||||
probgen_group,
|
||||
"probgen-normalize",
|
||||
"normalize probabilities",
|
||||
{"probgen-normalize"},
|
||||
};
|
||||
|
||||
|
||||
std::vector<uint8_t> bytes;
|
||||
std::vector<uint32_t> features;
|
||||
std::vector<double> feature_probs;
|
||||
std::optional<VideoDecoder> decoder;
|
||||
using namespace ::blky::args;
|
||||
|
||||
int main(int argc, char** argv)
|
||||
try {
|
||||
parser.ParseCLI(argc, argv);
|
||||
|
||||
// read input
|
||||
switch (args::get(from)) {
|
||||
case kBytes:
|
||||
if (src_stdin) {
|
||||
std::string temp;
|
||||
std::cin >> temp;
|
||||
bytes = {temp.begin(), temp.end()};
|
||||
} else if (src_stdin_hex) {
|
||||
for (;;) {
|
||||
char buf[2];
|
||||
std::cin >> buf[0] >> buf[1];
|
||||
if (std::cin.eof()) break;
|
||||
bytes.push_back(static_cast<uint8_t>((ToHex(buf[0]) << 4) | ToHex(buf[1])));
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error {"invalid source format for bytes"};
|
||||
}
|
||||
break;
|
||||
|
||||
case kFeatures:
|
||||
if (src_stdin) {
|
||||
features = ReadAll<uint32_t>(std::cin);
|
||||
} else {
|
||||
throw std::runtime_error {"invalid source format for features"};
|
||||
}
|
||||
break;
|
||||
|
||||
case kFeatureProbs:
|
||||
if (src_stdin) {
|
||||
feature_probs = ReadAll<double>(std::cin);
|
||||
} else {
|
||||
throw std::runtime_error {"invalid source format for feature probs"};
|
||||
}
|
||||
break;
|
||||
|
||||
case kVideo:
|
||||
if (src_video) {
|
||||
decoder.emplace(args::get(src_video));
|
||||
} else {
|
||||
throw std::runtime_error {"invalid source format for video"};
|
||||
}
|
||||
}
|
||||
|
||||
if (args::get(from) < args::get(to)) {
|
||||
// execute encoding
|
||||
switch (args::get(from)) {
|
||||
case kBytes:
|
||||
if (args::get(to) == kBytes) break;
|
||||
features = BytesEncoder(
|
||||
bytes,
|
||||
args::get(param_block_num),
|
||||
args::get(param_feat_bits),
|
||||
args::get(param_block_first),
|
||||
args::get(param_seed));
|
||||
/* fallthrough */
|
||||
|
||||
case kFeatures:
|
||||
if (args::get(to) == kFeatures) break;
|
||||
if (args::get(to) == kFeatureProbs) {
|
||||
feature_probs = GenerateFeatureProbs(
|
||||
features,
|
||||
args::get(param_block_num),
|
||||
args::get(probgen_false_positive),
|
||||
args::get(probgen_false_negative),
|
||||
args::get(probgen_seed),
|
||||
probgen_normalize);
|
||||
break;
|
||||
}
|
||||
assert(dst_video);
|
||||
Embed(
|
||||
features,
|
||||
args::get(dst_video),
|
||||
args::get(param_video),
|
||||
args::get(param_block_num),
|
||||
args::get(param_utime));
|
||||
/* fallthrough */
|
||||
|
||||
case kVideo:
|
||||
if (args::get(to) == kVideo) break;
|
||||
assert(false);
|
||||
|
||||
case kFeatureProbs:
|
||||
throw std::runtime_error("couldn't start flow from the data");
|
||||
}
|
||||
|
||||
} else if (args::get(from) > args::get(to)) {
|
||||
// execute decoding
|
||||
switch (args::get(from)) {
|
||||
case kVideo:
|
||||
if (args::get(to) == kVideo) break;
|
||||
if (!decoder) {
|
||||
throw std::runtime_error {"decoder is empty"};
|
||||
}
|
||||
feature_probs = Extract(
|
||||
*decoder,
|
||||
args::get(param_block_num),
|
||||
args::get(param_sensor_num),
|
||||
args::get(param_utime));
|
||||
/* fallthrough */
|
||||
|
||||
case kFeatureProbs:
|
||||
if (args::get(to) == kFeatureProbs) break;
|
||||
features = PathfindFeatures(
|
||||
feature_probs,
|
||||
args::get(param_block_num),
|
||||
args::get(param_feat_bits),
|
||||
args::get(param_seed));
|
||||
/* fallthrough */
|
||||
|
||||
case kFeatures:
|
||||
if (args::get(to) == kFeatures) break;
|
||||
bytes = BytesDecoder(
|
||||
features,
|
||||
args::get(param_block_num),
|
||||
args::get(param_feat_bits),
|
||||
args::get(param_seed));
|
||||
/* fallthrough */
|
||||
|
||||
case kBytes:
|
||||
if (args::get(to) == kBytes) break;
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
// output
|
||||
switch (args::get(to)) {
|
||||
case kBytes:
|
||||
if (dst_stdout) {
|
||||
std::cout << std::string {bytes.begin(), bytes.end()} << std::endl;
|
||||
} else if (dst_stdout_hex) {
|
||||
for (auto c : bytes) {
|
||||
std::cout << std::hex << (int) c;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
} else {
|
||||
throw std::runtime_error {"invalid destination format for bytes"};
|
||||
}
|
||||
break;
|
||||
|
||||
case kFeatures:
|
||||
if (dst_stdout) {
|
||||
for (auto& f : features) std::cout << f << "\n";
|
||||
} else {
|
||||
throw std::runtime_error {"invalid destination format for features"};
|
||||
}
|
||||
break;
|
||||
|
||||
case kFeatureProbs:
|
||||
if (dst_stdout) {
|
||||
const auto size = args::get(param_block_num);
|
||||
const auto cols = std::get<0>(size) * std::get<1>(size);
|
||||
for (size_t i = 0; i < feature_probs.size();) {
|
||||
for (size_t j = 0; i < feature_probs.size() && j < cols; ++i, ++j) {
|
||||
std::cout << feature_probs[i] << " ";
|
||||
}
|
||||
std::cout << "\n";
|
||||
}
|
||||
} else {
|
||||
throw std::runtime_error {"invalid destination format for feature probs"};
|
||||
}
|
||||
break;
|
||||
|
||||
case kVideo:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
|
||||
} catch (const args::Help&) {
|
||||
} catch (const Help&) {
|
||||
std::cout << parser << std::endl;
|
||||
return 0;
|
||||
|
||||
} catch (const args::ParseError& e) {
|
||||
} catch (const ParseError& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
std::cerr << parser << std::endl;
|
||||
return 1;
|
||||
|
||||
} catch (const args::ValidationError& e) {
|
||||
} catch (const ValidationError& e) {
|
||||
std::cerr << e.what() << std::endl;
|
||||
std::cerr << parser << std::endl;
|
||||
return 1;
|
||||
|
||||
} catch (const std::runtime_error& e) {
|
||||
|
@ -1,179 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <minimp4.h>
|
||||
|
||||
#include <wels/codec_api.h>
|
||||
|
||||
|
||||
class VideoDecoder final {
|
||||
public:
|
||||
VideoDecoder() = delete;
|
||||
VideoDecoder(const std::string& path) :
|
||||
file_(path, std::ifstream::binary | std::ifstream::ate),
|
||||
size_(static_cast<size_t>(file_.tellg())) {
|
||||
// init objects
|
||||
SDecodingParam dparam = {};
|
||||
dparam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_DEFAULT;
|
||||
dparam.eEcActiveIdc = ERROR_CON_SLICE_COPY;
|
||||
|
||||
WelsCreateDecoder(&decoder_);
|
||||
decoder_->Initialize(&dparam);
|
||||
|
||||
int lv = WELS_LOG_DEBUG;
|
||||
decoder_->SetOption(DECODER_OPTION_TRACE_LEVEL, &lv);
|
||||
|
||||
demuxer_ = {};
|
||||
MP4D_open(&demuxer_, ReadCallback, this, static_cast<int64_t>(size_));
|
||||
|
||||
// find video track
|
||||
track_ = SIZE_MAX;
|
||||
for (size_t i = 0; i < demuxer_.track_count; ++i) {
|
||||
if (demuxer_.track[i].handler_type == MP4D_HANDLER_TYPE_VIDE) {
|
||||
if (track_ != SIZE_MAX) {
|
||||
throw std::runtime_error {"there are many video tracks"};
|
||||
}
|
||||
track_ = i;
|
||||
}
|
||||
}
|
||||
if (track_ == SIZE_MAX) {
|
||||
throw std::runtime_error {"there is no video track"};
|
||||
}
|
||||
|
||||
// setup decoder
|
||||
std::vector<uint8_t> temp_;
|
||||
|
||||
for (size_t i = 0;; ++i) {
|
||||
int size;
|
||||
auto sps = static_cast<const uint8_t*>(MP4D_read_sps(
|
||||
&demuxer_,
|
||||
static_cast<unsigned int>(track_),
|
||||
static_cast<int>(i),
|
||||
&size));
|
||||
if (!sps) break;
|
||||
|
||||
temp_.resize(static_cast<size_t>(4+size));
|
||||
temp_[0] = 0, temp_[1] = 0, temp_[2] = 0, temp_[3] = 1;
|
||||
std::memcpy(&temp_[4], sps, static_cast<size_t>(size));
|
||||
|
||||
if (decoder_->DecodeFrameNoDelay(temp_.data(), static_cast<int>(temp_.size()), yuv_, &frame_)) {
|
||||
throw std::runtime_error {"failed to decode SPS"};
|
||||
}
|
||||
}
|
||||
for (size_t i = 0;; ++i) {
|
||||
int size;
|
||||
auto pps = static_cast<const uint8_t*>(MP4D_read_pps(
|
||||
&demuxer_,
|
||||
static_cast<unsigned int>(track_),
|
||||
static_cast<int>(i),
|
||||
&size));
|
||||
if (!pps) break;
|
||||
|
||||
temp_.resize(static_cast<size_t>(4+size));
|
||||
temp_[0] = 0, temp_[1] = 0, temp_[2] = 0, temp_[3] = 1;
|
||||
std::memcpy(&temp_[4], pps, static_cast<size_t>(size));
|
||||
|
||||
if (decoder_->DecodeFrameNoDelay(temp_.data(), static_cast<int>(temp_.size()), yuv_, &frame_)) {
|
||||
throw std::runtime_error {"failed to decode SPS"};
|
||||
}
|
||||
}
|
||||
temp_.clear();
|
||||
}
|
||||
~VideoDecoder() noexcept {
|
||||
decoder_->Uninitialize();
|
||||
WelsDestroyDecoder(decoder_);
|
||||
|
||||
MP4D_close(&demuxer_);
|
||||
}
|
||||
VideoDecoder(const VideoDecoder&) = delete;
|
||||
VideoDecoder(VideoDecoder&&) = delete;
|
||||
VideoDecoder& operator=(const VideoDecoder&) = delete;
|
||||
VideoDecoder& operator=(VideoDecoder&&) = delete;
|
||||
|
||||
bool Decode() {
|
||||
if (temp_consumed_ >= temp_.size()) {
|
||||
if (count_ >= demuxer_.track[track_].sample_count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned size, time, dur;
|
||||
const auto off = MP4D_frame_offset(
|
||||
&demuxer_,
|
||||
static_cast<unsigned int>(track_),
|
||||
static_cast<unsigned int>(count_),
|
||||
&size, &time, &dur);
|
||||
|
||||
assert(size > 0);
|
||||
temp_.resize(size);
|
||||
temp_consumed_ = 0;
|
||||
|
||||
file_.seekg(static_cast<std::streamoff>(off));
|
||||
assert(file_);
|
||||
file_.read((char*) temp_.data(), size);
|
||||
assert(file_);
|
||||
|
||||
Decode();
|
||||
++count_;
|
||||
return true;
|
||||
|
||||
} else {
|
||||
auto& i = temp_consumed_;
|
||||
|
||||
const uint32_t nal_size = 4 +
|
||||
static_cast<uint32_t>((temp_[i+0] << 24) |
|
||||
(temp_[i+1] << 16) |
|
||||
(temp_[i+2] << 8) |
|
||||
(temp_[i+3] << 0));
|
||||
temp_[i ] = 0;
|
||||
temp_[i+1] = 0;
|
||||
temp_[i+2] = 0;
|
||||
temp_[i+3] = 1;
|
||||
if (decoder_->DecodeFrameNoDelay(temp_.data()+i, static_cast<int>(nal_size), yuv_, &frame_)) {
|
||||
throw std::runtime_error {"failed to decode a frame"};
|
||||
}
|
||||
i += nal_size;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const SBufferInfo& frame() const noexcept { return frame_; }
|
||||
const uint8_t* const* yuv() const noexcept { return yuv_; }
|
||||
|
||||
private:
|
||||
std::ifstream file_;
|
||||
size_t size_;
|
||||
|
||||
ISVCDecoder* decoder_;
|
||||
MP4D_demux_t demuxer_;
|
||||
|
||||
size_t track_;
|
||||
|
||||
uint8_t* yuv_[3] = {0};
|
||||
SBufferInfo frame_ = {};
|
||||
size_t count_ = 0;
|
||||
|
||||
size_t temp_consumed_ = 0;
|
||||
std::vector<uint8_t> temp_;
|
||||
|
||||
|
||||
static int ReadCallback(int64_t off, void* buf, size_t size, void* ptr) noexcept {
|
||||
auto self = (VideoDecoder*) ptr;
|
||||
|
||||
auto n = self->size_ - static_cast<size_t>(off) - size;
|
||||
if (size < n) n = size;
|
||||
|
||||
self->file_.seekg(off);
|
||||
assert(self->file_);
|
||||
|
||||
self->file_.read((char*) buf, static_cast<std::streamsize>(n));
|
||||
assert(self->file_);
|
||||
return 0;
|
||||
}
|
||||
};
|
@ -1,82 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
#include <minimp4.h>
|
||||
|
||||
#include <wels/codec_api.h>
|
||||
|
||||
|
||||
class VideoEncoder final {
|
||||
public:
|
||||
VideoEncoder() = delete;
|
||||
VideoEncoder(const std::string& path, const SEncParamBase& p) :
|
||||
file_(path, std::ofstream::binary), fps_(p.fMaxFrameRate) {
|
||||
if (WelsCreateSVCEncoder(&encoder_)) {
|
||||
throw std::runtime_error {"failed to init openh264 encoder"};
|
||||
}
|
||||
|
||||
muxer_ = MP4E_open(false, false, this, WriteCallback);
|
||||
if (MP4E_STATUS_OK != mp4_h26x_write_init(&writer_, muxer_, p.iPicWidth, p.iPicHeight, false)) {
|
||||
throw std::runtime_error {"failed to init h26x writer"};
|
||||
}
|
||||
|
||||
encoder_->Initialize(&p);
|
||||
|
||||
int lv = WELS_LOG_DEBUG;
|
||||
encoder_->SetOption(ENCODER_OPTION_TRACE_LEVEL, &lv);
|
||||
int fmt = videoFormatI420;
|
||||
encoder_->SetOption(ENCODER_OPTION_DATAFORMAT, &fmt);
|
||||
}
|
||||
~VideoEncoder() noexcept {
|
||||
encoder_->Uninitialize();
|
||||
WelsDestroySVCEncoder(encoder_);
|
||||
|
||||
MP4E_close(muxer_);
|
||||
mp4_h26x_write_close(&writer_);
|
||||
}
|
||||
|
||||
void Encode(const SSourcePicture& pic) {
|
||||
SFrameBSInfo info;
|
||||
if (cmResultSuccess != encoder_->EncodeFrame(&pic, &info)) {
|
||||
throw std::runtime_error {"failed to encode a frame"};
|
||||
}
|
||||
if (info.eFrameType == videoFrameTypeSkip) return;
|
||||
|
||||
for (size_t i = 0; i < static_cast<size_t>(info.iLayerNum); ++i) {
|
||||
const auto& lay = info.sLayerInfo[i];
|
||||
|
||||
uint8_t* buf = lay.pBsBuf;
|
||||
for (size_t j = 0; j < static_cast<size_t>(lay.iNalCount); ++j) {
|
||||
mp4_h26x_write_nal(&writer_, buf, lay.pNalLengthInByte[j], static_cast<unsigned int>(90000./fps_));
|
||||
buf += lay.pNalLengthInByte[j];
|
||||
}
|
||||
}
|
||||
}
|
||||
void ForceIntraFrame() noexcept {
|
||||
encoder_->ForceIntraFrame(true);
|
||||
}
|
||||
|
||||
private:
|
||||
std::ofstream file_;
|
||||
|
||||
ISVCEncoder* encoder_;
|
||||
MP4E_mux_t* muxer_;
|
||||
mp4_h26x_writer_t writer_;
|
||||
|
||||
double fps_;
|
||||
|
||||
|
||||
static int WriteCallback(int64_t off, const void* buf, size_t size, void* ptr) noexcept {
|
||||
auto self = (VideoEncoder*) ptr;
|
||||
self->file_.seekp(off);
|
||||
assert(self->file_);
|
||||
self->file_.write(static_cast<const char*>(buf), static_cast<std::streamsize>(size));
|
||||
return !self->file_;
|
||||
}
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user