Compare commits

...

29 Commits
bk2 ... main

Author SHA1 Message Date
af394452c0 revert improvement of aprob calculation 2023-01-15 15:35:39 +09:00
9a115f9990 remove debug code 2023-01-13 22:35:56 +09:00
dd4f0262d2 implement negative-sample option to conv/aprob_fprob 2022-11-09 22:00:56 +09:00
c83a0716c1 allow user to specify bitrate of stego video 2022-11-09 14:20:52 +09:00
6faa2c0ae0 improve bmap sorting 2022-10-14 00:42:52 +09:00
8262bf29b2 implement encoding a part of the host video 2022-10-14 00:02:50 +09:00
0fde521ffe implement fmap randomization while the conversion, bmap -> fmap 2022-10-13 12:02:26 +09:00
b61b6dd21b improve a calculation of feature probability 2022-10-12 20:51:09 +09:00
df6302c699 implement a converter from bmap to fmap 2022-10-12 12:50:21 +09:00
4ddd15f34e implement a converter from aprob to bmap 2022-10-10 22:40:00 +09:00
83ee7f5f1e enhance fmap to add new parameter, time 2022-10-10 22:02:33 +09:00
e6e8a68c09 rename terms 2022-10-05 11:44:56 +09:00
662c86234f enable compiler warnings 2022-09-09 11:59:30 +09:00
a6dd9dfa88 rename fidx -> bidx 2022-09-09 11:26:01 +09:00
73af0a3069 fix stego_fprob to skip empty frame 2022-09-08 11:42:51 +09:00
8239d6edc9 add new converter: fcode <-> fidx 2022-09-07 16:53:30 +09:00
0f8134edf5 fix an issue that block-h param is not applied properly 2022-09-07 15:31:05 +09:00
85c45e32c0 rename exp dir -> sh 2022-09-07 11:39:21 +09:00
eb55cb5d43 add new converter: dcode <-> fcode 2022-09-07 10:26:46 +09:00
1e2569f144 rename video -> stego 2022-09-07 09:52:46 +09:00
7b15cf2347 split step map generator from converter 2022-09-07 09:51:45 +09:00
74efa6d70c change name of conversion step 2022-09-07 09:08:44 +09:00
e0f175c6ba add new converter: cprob -> code 2022-09-06 21:22:28 +09:00
55a1095490 rename video_bprob -> video_fprob 2022-09-06 20:11:21 +09:00
ecece4021f add 'uvfix' option to conv/bidx_video 2022-09-06 20:09:31 +09:00
c90ec1a935 add fprob_calc.sh 2022-09-06 18:15:15 +09:00
dc67745175 improve precision of block probabilities 2022-09-06 17:28:18 +09:00
edd91ef692 add new converter: block indices -> video stream 2022-09-06 14:28:06 +09:00
de9eb51fbc add new converter: video -> block probabilities 2022-09-06 14:27:21 +09:00
18 changed files with 1561 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/build/
/exp/

15
CMakeLists.txt Normal file
View File

@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.18)
project(blocky C CXX)
option(BLOCKY_STATIC OFF)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(thirdparty)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -pedantic-errors -Wextra")
add_subdirectory(conv)
add_subdirectory(gen)

23
README.md Normal file
View File

@ -0,0 +1,23 @@
blocky
====
## CMake command
When you built openh264 on `/home/user/openh264`:
```
cmake -DCMAKE_CXX_FLAGS=-isystem\ /home/user/openh264/codec/api\ -L/home/user/openh264 ..
```
## ffmpeg useful commands
```
ffmpeg -i in.mp4 -vframes 300 "%d.png"
```
```
ffmpeg -r 30 -i "%d.png" -vcodec libx264 -pix_fmt yuv420p out.mp4
```
```
ffmpeg -i src.mp4 -t 10s cut.mp4
```

29
conv/CMakeLists.txt Normal file
View File

@ -0,0 +1,29 @@
# ---- main procedural
add_executable(dcode_feat common.hh dcode_feat.cc)
target_link_libraries(dcode_feat PRIVATE args)
add_executable(feat_block common.hh feat_block.cc)
target_link_libraries(feat_block PRIVATE args)
add_executable(block_stego common.hh block_stego.cc)
target_link_libraries(block_stego PRIVATE args minimp4 openh264)
add_executable(stego_aprob common.hh stego_aprob.cc)
target_link_libraries(stego_aprob PRIVATE args minimp4 openh264)
add_executable(aprob_fprob common.hh aprob_fprob.cc)
target_link_libraries(aprob_fprob PRIVATE args)
add_executable(fprob_feat common.hh fprob_feat.cc)
target_link_libraries(fprob_feat PRIVATE args)
add_executable(feat_dcode common.hh feat_dcode.cc)
target_link_libraries(feat_dcode PRIVATE args)
# ---- procedural of preprocessing
add_executable(aprob_bmap common.hh aprob_bmap.cc)
target_link_libraries(aprob_bmap PRIVATE args)
add_executable(bmap_fmap common.hh bmap_fmap.cc)
target_link_libraries(bmap_fmap PRIVATE args)

72
conv/aprob_bmap.cc Normal file
View File

@ -0,0 +1,72 @@
#include <fstream>
#include <iostream>
#include <string>
#include <args.hxx>
#include "common.hh"
namespace param {
using namespace ::args;
ArgumentParser parser {
"converter: aprob -> bmap"
};
HelpFlag help {
parser, "help", "display this menu", {'h', "help"},
};
ValueFlag<double> mid {
parser, "probability", "desired value of alter-probability to select", {"mid"}, 0.5,
};
ValueFlag<double> max_dist {
parser, "max distance", "maximum distance from mid value", {"max-dist"}, 0.2,
};
ValueFlag<uint32_t> min_bnum {
parser, "number of blocks", "minimum number of blocks to select (prior than max-dist)", {"min-bnum"}, 32,
};
} // namespace param
static void Exec() {
const auto aprob = ReadMatrix<double>(std::cin);
const auto mid = args::get(param::mid);
const auto max_dist = args::get(param::max_dist);
const auto min_bnum = args::get(param::min_bnum);
std::vector<std::pair<size_t, double>> vec;
for (auto& probs : aprob) {
vec.reserve(probs.size());
vec.clear();
for (auto prob : probs) {
vec.emplace_back(vec.size(), std::abs(prob-mid));
}
std::sort(vec.begin(), vec.end(), [](auto& a, auto& b) { return a.second < b.second; });
Enforce(vec.size() >= min_bnum, "cannot satisfy min-bnum limitation");
for (auto itr = vec.begin(); itr < vec.end(); ++itr) {
if (itr >= vec.begin()+min_bnum) { // min-bnum is satisfied
if (itr->second > max_dist) break; // max-dist is unsatisfied
}
std::cout << itr->first << ' ';
}
std::cout << '\n';
}
}
int main(int argc, char** argv)
try {
param::parser.ParseCLI(argc, argv);
Exec();
return EXIT_SUCCESS;
} catch (const args::Help&) {
std::cout << param::parser << std::endl;
return EXIT_SUCCESS;
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}

76
conv/aprob_fprob.cc Normal file
View File

@ -0,0 +1,76 @@
#include <algorithm>
#include <fstream>
#include <iostream>
#include <numeric>
#include <string>
#include <args.hxx>
#include "common.hh"
namespace param {
using namespace ::args;
ArgumentParser parser {
"converter: alter-probability matrix -> feature probability matrix"
};
HelpFlag help {
parser, "help", "display this menu", {'h', "help"},
};
ValueFlag<std::string> fmap {
parser, "path", "feature map file path", {"fmap"},
};
ValueFlag<size_t> negative_sample {
parser, "samples", "number of samples used to calculate negative factor (deprecated and no effect)", {"negative-sample"}, 16,
};
} // namespace param
static void Exec() {
const auto aprobs = ReadMatrix<double>(std::cin);
Enforce(aprobs.size() > 0 && aprobs[0].size() > 0, "empty matrix");
std::ifstream fmap_st {args::get(param::fmap)};
Enforce(!!fmap_st, "fmap path is invalid");
const auto fmap = ReadTensor3<uint32_t>(fmap_st);
Enforce(fmap.size() > 0 && fmap[0].size() > 0, "empty fmap");
std::vector<double> negatives;
for (size_t t = 0; t < aprobs.size(); ++t) {
const auto tidx = t % fmap.size();
for (size_t c = 0; c < fmap[tidx].size(); ++c) {
const auto& blocks = fmap[tidx][c];
double positive = 0;
for (const auto b : blocks) {
positive += aprobs[t][b];
}
if (blocks.size() > 0) {
positive /= blocks.size();
} else {
positive = 1;
}
std::cout << positive << ' ';
}
std::cout << '\n';
}
}
int main(int argc, char** argv)
try {
param::parser.ParseCLI(argc, argv);
Exec();
return EXIT_SUCCESS;
} catch (const args::Help&) {
std::cout << param::parser << std::endl;
return EXIT_SUCCESS;
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}

328
conv/block_stego.cc Normal file
View File

@ -0,0 +1,328 @@
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
#include <args.hxx>
#include <minimp4.h>
#include <codec/api/wels/codec_api.h>
#include "common.hh"
namespace param {
using namespace ::args;
ArgumentParser parser {
"converter: block indices + host -> stego"
};
HelpFlag help {
parser, "help", "display this menu", {'h', "help"},
};
ValueFlag<int32_t> bw {
parser, "128", "width of blocks (px)", {"block-w"}, 128,
};
ValueFlag<int32_t> bh {
parser, "128", "height of blocks (px)", {"block-h"}, 128,
};
ValueFlag<int32_t> utime {
parser, "10", "duration of each feature (frame)", {"utime"}, 10,
};
ValueFlag<int32_t> dur {
parser, "0", "number of features to be extracted (ut)", {"dur"}, 0,
};
ValueFlag<int32_t> offset {
parser, "0", "number of offset frames to start extraction", {"offset"}, 0,
};
Flag only_body {
parser, "only-body", "cut off head and tail that no feature is embedded", {"only-body"},
};
Flag uvfix {
parser, "uvfix", "fix UV values in feature", {"uvfix"},
};
Positional<std::string> dst {
parser, "path", "destination video file path",
};
Positional<std::string> src {
parser, "path", "source video file path",
};
ValueFlag<int32_t> bitrate {
parser, "bitrate", "bitrate of stego video", {"bitrate"}, 5000*1000,
};
// from stdin
std::vector<std::vector<int32_t>> indices;
} // namespace param
static void Embed(int32_t t, Frame& dst, const Frame& base) {
const auto bw = args::get(param::bw);
const auto bh = args::get(param::bh);
const auto hbw = bw/2;
const auto hbh = bh/2;
const auto bx_cnt = dst.w / bw;
const auto by_cnt = dst.h / bh;
t = t%param::indices.size();
for (auto idx : param::indices[t]) {
Enforce(idx >= 0, "block index underflow");
const auto bx = idx%bx_cnt;
const auto by = idx/bx_cnt;
Enforce(by < by_cnt, "block index overflow");
for (int32_t y = 0; y < bh; ++y) {
for (int32_t x = 0; x < bw; ++x) {
const auto off = (by*bh+y)*base.w + (bx*bw+x);
dst.Y[off] = base.Y[off];
}
}
if (param::uvfix) {
for (int32_t y = 0; y < hbh; ++y) {
for (int32_t x = 0; x < hbw; ++x) {
const auto off = (by*hbh+y)*base.hw + (bx*hbw+x);
dst.U[off] = base.U[off];
dst.V[off] = base.V[off];
}
}
}
}
}
static void Exec() {
const auto bw = args::get(param::bw);
const auto bh = args::get(param::bh);
const auto ut = args::get(param::utime);
const auto dur = args::get(param::dur);
const auto offset = args::get(param::offset);
Enforce(bw > 0 && bh > 0, "block size must be greater than 0");
Enforce(ut > 0, "utime must be greater than 0");
// read indices
param::indices = ReadMatrix<int32_t>(std::cin);
Enforce(param::indices.size() > 0, "empty indices");
// open source video stream
const auto srcpath = args::get(param::src);
std::ifstream srcst {srcpath.c_str(), std::ifstream::binary | std::ifstream::ate};
Enforce(!!srcst, "source video stream is invalid");
const int64_t srcsz = srcst.tellg();
// open destination video stream
const auto dstpath = args::get(param::dst);
std::ofstream dstst {dstpath.c_str(), std::ifstream::binary};
Enforce(!!dstst, "destination video stream is invalid");
// init decoder
ISVCDecoder* dec;
Enforce(0 == WelsCreateDecoder(&dec), "decoder creation failure");
SDecodingParam decp = {};
decp.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_DEFAULT;
decp.eEcActiveIdc = ERROR_CON_SLICE_COPY;
Enforce(0 == dec->Initialize(&decp), "decoder init failure");
int declv = WELS_LOG_INFO;
dec->SetOption(DECODER_OPTION_TRACE_LEVEL, &declv);
uint8_t* yuv[3] = {0};
SBufferInfo frame = {};
// demuxer
MP4D_demux_t dem = {};
MP4D_open(&dem, [](int64_t off, void* buf, size_t sz, void* ptr) {
auto& st = *reinterpret_cast<std::ifstream*>(ptr);
st.seekg(off);
Enforce(!!st, "seek failure");
st.read(reinterpret_cast<char*>(buf), sz);
Enforce(!!st, "read failure");
return 0;
}, &srcst, srcsz);
// find video track
size_t ti;
for (ti = 0; ti < dem.track_count; ++ti) {
const auto& t = dem.track[ti];
if (t.handler_type == MP4D_HANDLER_TYPE_VIDE) {
break;
}
}
Enforce(ti < dem.track_count, "no video track");
const auto& tra = dem.track[ti];
// calc params
const auto tscale = tra.timescale;
const auto dur_t =
(static_cast<uint64_t>(tra.duration_hi) << 32) |
static_cast<uint64_t>(tra.duration_lo);
const auto dursec = static_cast<float>(dur_t)/static_cast<float>(tscale);
const float fps = static_cast<float>(tra.sample_count)/dursec;
const auto fps9 = static_cast<int>(90000/fps);
const int32_t w = tra.SampleDescription.video.width;
const int32_t h = tra.SampleDescription.video.height;
// init encoder
ISVCEncoder* enc;
Enforce(0 == WelsCreateSVCEncoder(&enc), "encoder creation failure");
SEncParamBase encp = {};
encp.iUsageType = CAMERA_VIDEO_REAL_TIME;
encp.fMaxFrameRate = fps;
encp.iPicWidth = w;
encp.iPicHeight = h;
encp.iTargetBitrate = args::get(param::bitrate);
Enforce(0 == enc->Initialize(&encp), "encoder init failure");
int enclv = WELS_LOG_INFO;
enc->SetOption(ENCODER_OPTION_TRACE_LEVEL, &enclv);
// init muxer
MP4E_mux_t* mux = MP4E_open(
false, false, &dstst,
[](int64_t off, const void* buf, size_t size, void* ptr) {
auto& st = *reinterpret_cast<std::ostream*>(ptr);
st.seekp(off);
Enforce(!!st, "muxer seek failure");
st.write(reinterpret_cast<const char*>(buf), size);
Enforce(!!st, "muxer write failure");
return 0;
});
mp4_h26x_writer_t writer;
Enforce(
MP4E_STATUS_OK == mp4_h26x_write_init(&writer, mux, w, h, false),
"failed to init mp4_h26x_writer_t");
// consume SPS
std::vector<uint8_t> nal;
for (size_t si = 0;; ++si) {
int sz;
auto sps = reinterpret_cast<const uint8_t*>(MP4D_read_sps(&dem, ti, si, &sz));
if (!sps) break;
CopyNal(nal, sps, sz);
const auto ret = dec->DecodeFrameNoDelay(nal.data(), nal.size(), yuv, &frame);
Enforce(ret == 0, "SPS decode failure");
}
// consume PPS
for (size_t si = 0;; ++si) {
int sz;
auto pps = reinterpret_cast<const uint8_t*>(MP4D_read_pps(&dem, ti, si, &sz));
if (!pps) break;
CopyNal(nal, pps, sz);
const auto ret = dec->DecodeFrameNoDelay(nal.data(), nal.size(), yuv, &frame);
Enforce(ret == 0, "PPS decode failure");
}
// decode frame
Frame bf = {};
int32_t fidx = 0;
for (size_t si = 0; si < tra.sample_count; ++si) {
unsigned fsz, ftime, fdur;
const auto off = MP4D_frame_offset(&dem, ti, si, &fsz, &ftime, &fdur);
srcst.seekg(off);
Enforce(!!srcst, "NAL seek failure");
nal.resize(fsz);
srcst.read(reinterpret_cast<char*>(nal.data()), fsz);
Enforce(!!srcst, "NAL read failure");
// decode all nal blocks
for (size_t i = 0; i < nal.size();) {
uint32_t sz =
(nal[i] << 24) | (nal[i+1] << 16) | (nal[i+2] << 8) | (nal[i+3] << 0);
nal[i+0] = 0;
nal[i+1] = 0;
nal[i+2] = 0;
nal[i+3] = 1;
sz += 4;
// retrieve a frame
const auto ret = dec->DecodeFrameNoDelay(&nal[i], sz, yuv, &frame);
Enforce(ret == 0, "frame decode failure");
// handle decoded frame
if (frame.iBufferStatus) {
bool encode_frame = !param::only_body;
bool keep_frame = false;
// alter the frame if it's not the first
Frame cf = {yuv, frame};
if (offset <= fidx && (dur == 0 || fidx-offset < dur*ut)) {
const auto t = (fidx-offset)/ut;
const auto tf = (fidx-offset)%ut;
if (tf > 0) {
Embed(t, cf, bf);
}
encode_frame = true;
keep_frame = (tf == 0);
}
// encode
if (encode_frame) {
SFrameBSInfo info;
SSourcePicture pic = cf.GetSourcePic();
Enforce(cmResultSuccess == enc->EncodeFrame(&pic, &info),
"encode failure");
// write buffer
if (info.eFrameType != videoFrameTypeSkip) {
for (int li = 0; li < info.iLayerNum; ++li) {
const auto& l = info.sLayerInfo[li];
uint8_t* buf = l.pBsBuf;
for (int ni = 0; ni < l.iNalCount; ++ni) {
mp4_h26x_write_nal(
&writer, buf, l.pNalLengthInByte[ni], fps9);
buf += l.pNalLengthInByte[ni];
}
}
}
}
// save the frame if it's the first
if (keep_frame) {
bf = std::move(cf);
}
++fidx;
}
i += sz;
}
}
// tear down
MP4E_close(mux);
mp4_h26x_write_close(&writer);
}
int main(int argc, char** argv)
try {
param::parser.ParseCLI(argc, argv);
Exec();
return EXIT_SUCCESS;
} catch (const args::Help&) {
std::cout << param::parser << std::endl;
return EXIT_SUCCESS;
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}

137
conv/bmap_fmap.cc Normal file
View File

@ -0,0 +1,137 @@
#include <cassert>
#include <cmath>
#include <fstream>
#include <iostream>
#include <string>
#include <utility>
#include <args.hxx>
#include "common.hh"
namespace param {
using namespace ::args;
ArgumentParser parser {
"converter: bmap -> fmap"
};
HelpFlag help {
parser, "help", "display this menu", {'h', "help"},
};
ValueFlag<uint32_t> fnum {
parser, "64", "number of feature kinds", {"fnum"}, 64,
};
ValueFlag<uint32_t> seed {
parser, "0", "random seed to randomize selection of combination (0=no randomize)", {"seed"}, 0,
};
} // namespace param
static bool NextCombination(auto begin, auto end) noexcept {
auto first = std::find(begin, end, true);
if (first == end) {
return false;
}
if (!NextCombination(first+1, end)) {
if (first+1 >= end || *(first+1)) {
return false;
}
const auto n = std::count(first+2, end, true);
std::fill(first+2 , first+2+n, true);
std::fill(first+2+n, end , false);
*first = false;
*(first+1) = true;
}
return true;
}
static uint32_t XorShift(uint32_t* v) noexcept {
*v ^= *v << 13;
*v ^= *v >> 17;
*v ^= *v << 5;
return *v;
}
static void Exec() {
const auto bmap = ReadMatrix<uint32_t>(std::cin);
const auto fnum = args::get(param::fnum);
for (uint32_t t = 0; t < bmap.size(); ++t) {
const auto& blocks = bmap[t];
const auto bnum = static_cast<uint32_t>(blocks.size());
Enforce(fnum < pow(bnum, 2), "number of blocks is too less");
// calc skip vector
std::vector<uint32_t> skip(fnum);
uint32_t nCr = 1;
for (uint32_t f = 0, r = 1; f < fnum; ++r) {
nCr *= bnum - (r-1);
nCr /= r;
f += nCr;
if (f <= fnum || 0 == args::get(param::seed)) {
const auto n = fnum - (f-nCr);
auto begin = skip.begin() + f-nCr;
std::fill(begin, begin+n, 1);
} else {
uint32_t seed = args::get(param::seed);
uint64_t sum = 0;
for (uint32_t i = f-nCr; i < fnum; ++i) {
skip[i] = XorShift(&seed);
sum += skip[i];
}
sum += XorShift(&seed);
const auto coe = nCr*1. / sum;
for (uint32_t i = f-nCr; i < fnum; ++i) {
skip[i] = std::max(uint32_t {1}, static_cast<uint32_t>(skip[i] * coe));
}
}
}
std::vector<bool> C(bnum);
uint32_t r = 0;
for (uint32_t f = 0; f < fnum; ++f) {
for (uint32_t s = 0; s < skip[f]; ++s) {
if (!NextCombination(C.begin(), C.end())) {
++r;
assert(r <= bnum);
std::fill(C.begin(), C.begin()+r, 1);
std::fill(C.begin()+r, C.end(), 0);
s = 0; // s will be 1 on next iteration
}
}
auto itr = C.begin();
for (uint32_t i = 0; i < r; ++i, ++itr) {
itr = std::find(itr, C.end(), true);
if (itr >= C.end()) {
for (const auto& b : C) std::cout << !!b << ',';
std::cout << std::endl;
assert(false);
}
const auto idx = std::distance(C.begin(), itr);
std::cout << blocks[idx] << ' ';
}
std::cout << '\n';
}
std::cout << "----\n";
}
}
int main(int argc, char** argv)
try {
param::parser.ParseCLI(argc, argv);
Exec();
return EXIT_SUCCESS;
} catch (const args::Help&) {
std::cout << param::parser << std::endl;
return EXIT_SUCCESS;
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}

111
conv/common.hh Normal file
View File

@ -0,0 +1,111 @@
#pragma once
#include <cstdint>
#include <cstring>
#include <stdexcept>
#include <sstream>
#include <string>
#include <vector>
#include <codec/api/wels/codec_api.h>
inline void Enforce(bool eval, const std::string& msg) {
if (!eval) {
throw std::runtime_error {msg};
}
}
template <typename T>
auto ReadMatrix(std::istream& st) noexcept {
std::vector<std::vector<T>> ret;
std::string line;
while (std::getline(st, line)) {
std::istringstream sst {line};
ret.emplace_back(std::istream_iterator<T> {sst},
std::istream_iterator<T> {});
}
return ret;
}
template <typename T>
auto ReadTensor3(std::istream& st) noexcept {
std::vector<std::vector<std::vector<T>>> ret(1);
std::string line;
while (std::getline(st, line)) {
if (line == "----") {
ret.push_back({});
} else {
std::istringstream sst {line};
ret.back().emplace_back(std::istream_iterator<T> {sst},
std::istream_iterator<T> {});
}
}
if (ret.back().empty()) ret.pop_back();
return ret;
}
// ---- MP4 utilities
inline void CopyNal(std::vector<uint8_t>& v, const uint8_t* buf, size_t sz) noexcept {
v.resize(sz+4);
v[0] = 0;
v[1] = 0;
v[2] = 0;
v[3] = 1;
std::memcpy(&v[4], buf, sz);
}
struct Frame {
std::vector<uint8_t> Y;
std::vector<uint8_t> U;
std::vector<uint8_t> V;
int32_t w, h;
int32_t hw, hh;
Frame() = default;
Frame(uint8_t* yuv[3], const SBufferInfo& frame) {
w = static_cast<int32_t>(frame.UsrData.sSystemBuffer.iWidth);
h = static_cast<int32_t>(frame.UsrData.sSystemBuffer.iHeight);
hw = w/2;
hh = h/2;
const auto ystride = static_cast<int32_t>(frame.UsrData.sSystemBuffer.iStride[0]);
const auto uvstride = static_cast<int32_t>(frame.UsrData.sSystemBuffer.iStride[1]);
Y.resize(w*h);
for (int32_t y = 0; y < h; ++y) {
const auto src = yuv[0] + y*ystride;
const auto dst = Y.data() + y*w;
std::memcpy(dst, src, w);
}
U.resize(hw*hh);
V.resize(hw*hh);
for (int32_t y = 0; y < hh; ++y) {
const auto srcu = yuv[1] + y*uvstride;
const auto srcv = yuv[2] + y*uvstride;
const auto dstu = U.data() + y*hw;
const auto dstv = V.data() + y*hw;
std::memcpy(dstu, srcu, hw);
std::memcpy(dstv, srcv, hw);
}
}
SSourcePicture GetSourcePic() noexcept {
SSourcePicture ret;
ret.iPicWidth = w;
ret.iPicHeight = h;
ret.iColorFormat = videoFormatI420;
ret.iStride[0] = w;
ret.iStride[1] = hw;
ret.iStride[2] = hw;
ret.pData[0] = Y.data();
ret.pData[1] = U.data();
ret.pData[2] = V.data();
return ret;
}
};

70
conv/dcode_feat.cc Normal file
View File

@ -0,0 +1,70 @@
#include <fstream>
#include <iostream>
#include <string>
#include <args.hxx>
#include "common.hh"
namespace param {
using namespace ::args;
ArgumentParser parser {
"converter: data code -> feature"
};
HelpFlag help {
parser, "help", "display this menu", {'h', "help"},
};
ValueFlag<std::string> smap {
parser, "path", "step map file path", {"smap"},
};
ValueFlag<uint32_t> first {
parser, "0", "first feature", {"first"}, 0
};
ValueFlag<uint32_t> fnum {
parser, "50", "number of feature kinds", {"fnum"}, 50
};
} // namespace param
static void Exec() {
const auto fnum = args::get(param::fnum);
Enforce(fnum > 0, "fnum must be greater than 0");
std::ifstream smap_st {args::get(param::smap)};
Enforce(!!smap_st, "smap path is invalid");
const auto smap = ReadMatrix<uint32_t>(smap_st);
Enforce(smap.size() > 0 && smap[0].size() > 0, "empty smap");
const auto bn = smap[0].size();
for (auto& br : smap) {
Enforce(br.size() == bn, "all node should have the same number of branch");
}
uint32_t feat = args::get(param::first);
std::cout << feat << '\n';
for (uint32_t dcode, t = 0; std::cin >> dcode; ++t) {
Enforce(dcode < bn, "dcode must be lower than number of branch");
Enforce(fnum*(t+1) <= smap.size(), "smap row shortage");
feat = smap[t*fnum + feat][dcode];
std::cout << feat << '\n';
}
}
int main(int argc, char** argv)
try {
param::parser.ParseCLI(argc, argv);
Exec();
return EXIT_SUCCESS;
} catch (const args::Help&) {
std::cout << param::parser << std::endl;
return EXIT_SUCCESS;
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}

57
conv/feat_block.cc Normal file
View File

@ -0,0 +1,57 @@
#include <fstream>
#include <iostream>
#include <string>
#include <args.hxx>
#include "common.hh"
namespace param {
using namespace ::args;
ArgumentParser parser {
"converter: feature -> block"
};
HelpFlag help {
parser, "help", "display this menu", {'h', "help"},
};
ValueFlag<std::string> fmap {
parser, "path", "path to feature map", {"fmap"},
};
} // namespace param
static void Exec() {
std::ifstream fmap_st {args::get(param::fmap)};
Enforce(!!fmap_st, "fmap path is invalid");
const auto fmap = ReadTensor3<uint32_t>(fmap_st);
Enforce(fmap.size() > 0 && fmap[0].size() > 0, "empty fmap");
for (auto& fmap_t : fmap) {
Enforce(fmap_t.size() == fmap[0].size(), "fmap is broken");
}
for (size_t feat, t = 0; std::cin >> feat; ++t) {
const auto tidx = t % fmap.size();
Enforce(feat < fmap[tidx].size(), "feat overflow");
for (const auto idx : fmap[tidx][feat]) {
std::cout << idx << ' ';
}
std::cout << '\n';
}
}
int main(int argc, char** argv)
try {
param::parser.ParseCLI(argc, argv);
Exec();
return EXIT_SUCCESS;
} catch (const args::Help&) {
std::cout << param::parser << std::endl;
return EXIT_SUCCESS;
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}

71
conv/feat_dcode.cc Normal file
View File

@ -0,0 +1,71 @@
#include <fstream>
#include <iostream>
#include <string>
#include <args.hxx>
#include "common.hh"
namespace param {
using namespace ::args;
ArgumentParser parser {
"converter: feature code probability matrix -> feature code"
};
HelpFlag help {
parser, "help", "display this menu", {'h', "help"},
};
ValueFlag<std::string> smap {
parser, "path", "step map file path", {"smap"},
};
ValueFlag<uint32_t> fnum {
parser, "50", "number of feature kinds", {"fnum"}, 50
};
} // namespace param
static void Exec() {
const auto fnum = args::get(param::fnum);
Enforce(fnum > 0, "fnum must be greater than 0");
std::ifstream smap_st {args::get(param::smap)};
Enforce(!!smap_st, "smap path is invalid");
const auto smap = ReadMatrix<uint32_t>(smap_st);
Enforce(smap.size() > 0 && smap[0].size() > 0, "empty smap");
const auto bn = smap[0].size();
for (auto& br : smap) {
Enforce(br.size() == bn, "all node should have the same number of branch");
}
size_t feat_p;
std::cin >> feat_p;
for (uint32_t feat, t = 0; std::cin >> feat; ++t) {
Enforce(feat < fnum, "feat overflow");
Enforce(fnum*(t+1) <= smap.size(), "smap row shortage");
const auto& row = smap[t*fnum + feat_p];
auto itr = std::find(row.begin(), row.end(), feat);
Enforce(itr != row.end(), "invalid step detected");
std::cout << std::distance(row.begin(), itr) << '\n';
feat_p = feat;
}
}
int main(int argc, char** argv)
try {
param::parser.ParseCLI(argc, argv);
Exec();
return EXIT_SUCCESS;
} catch (const args::Help&) {
std::cout << param::parser << std::endl;
return EXIT_SUCCESS;
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}

109
conv/fprob_feat.cc Normal file
View File

@ -0,0 +1,109 @@
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
#include <args.hxx>
#include "common.hh"
namespace param {
using namespace ::args;
ArgumentParser parser {
"converter: feature probability matrix -> feature series"
};
HelpFlag help {
parser, "help", "display this menu", {'h', "help"},
};
ValueFlag<std::string> smap {
parser, "path", "step map file path", {"smap"},
};
Flag output_prob {
parser, "output-prob", "prints path probability at last", {"output-prob", "prob"},
};
} // namespace param
static void Exec() {
const auto fprobs = ReadMatrix<double>(std::cin);
Enforce(fprobs.size() > 0 && fprobs[0].size() > 0, "empty matrix");
const auto dur = fprobs.size();
const auto n = fprobs[0].size();
std::ifstream smap_st {args::get(param::smap)};
Enforce(!!smap_st, "smap path is invalid");
const auto smap = ReadMatrix<uint32_t>(smap_st);
Enforce(smap.size() >= dur*n, "smap row shortage");
struct Step {
double prob = -1;
size_t from = 0;
};
std::vector<Step> steps((dur+1)*n);
for (size_t i = 0; i < n; ++i) {
steps[i].prob = fprobs[0][i];
}
for (size_t t = 1; t < dur; ++t) {
Enforce(fprobs[t].size() == n, "ill-formed matrix");
for (size_t i = 0; i < n; ++i) {
const auto& cur = steps[(t-1)*n + i];
for (auto j : smap[(t-1)*n+i]) {
auto& next = steps[t*n + j];
const auto sum = cur.prob + fprobs[t][j];
if (next.prob < sum) {
next.prob = sum;
next.from = i;
}
}
}
}
double max_prob = -1;
size_t max_idx = 0;
for (size_t i = 0; i < n; ++i) {
const auto& step = steps[(dur-1)*n + i];
if (max_prob < step.prob) {
max_prob = step.prob;
max_idx = i;
}
}
std::vector<size_t> path = {max_idx};
path.reserve(dur);
for (size_t t = dur-1; t > 0; --t) {
path.push_back(steps[t*n + path.back()].from);
}
for (auto itr = path.rbegin(); itr < path.rend(); ++itr) {
std::cout << *itr << '\n';
}
if (param::output_prob) {
std::cout << max_prob/static_cast<double>(path.size())*100 << "%" << std::endl;
}
}
int main(int argc, char** argv)
try {
param::parser.ParseCLI(argc, argv);
Exec();
return EXIT_SUCCESS;
} catch (const args::Help&) {
std::cout << param::parser << std::endl;
return EXIT_SUCCESS;
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}

328
conv/stego_aprob.cc Normal file
View File

@ -0,0 +1,328 @@
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
#include <args.hxx>
#include <minimp4.h>
#include <codec/api/wels/codec_api.h>
#include "common.hh"
namespace param {
using namespace ::args;
ArgumentParser parser {
"converter: stego -> alter-probability matrix"
};
HelpFlag help {
parser, "help", "display this menu", {'h', "help"},
};
ValueFlag<int32_t> bw {
parser, "128", "width of blocks (px)", {"block-w"}, 128,
};
ValueFlag<int32_t> bh {
parser, "128", "height of blocks (px)", {"block-h"}, 128,
};
ValueFlag<int32_t> utime {
parser, "10", "duration of each feature (frame)", {"utime"}, 10,
};
ValueFlag<int32_t> dur {
parser, "0", "number of features to be extracted (ut)", {"dur"}, 0,
};
ValueFlag<int32_t> offset {
parser, "0", "number of offset frames to start extraction", {"offset"}, 0,
};
ValueFlag<int32_t> bmix {
parser, "8", "x interval of blockmatch (px)", {"bm-ix"}, 8,
};
ValueFlag<int32_t> bmiy {
parser, "8", "y interval of blockmatch (px)", {"bm-iy"}, 8,
};
ValueFlag<int32_t> bmsw {
parser, "4", "width of blockmatch search region (px)", {"bm-sw"}, 4,
};
ValueFlag<int32_t> bmsh {
parser, "4", "height of blockmatch search region (px)", {"bm-sh"}, 4,
};
enum Output {
kProb,
kIndex,
kLen,
kVec,
kNull,
};
const std::unordered_map<std::string, Output> kOutput = {
{"prob", kProb},
{"index", kIndex},
{"len", kLen},
{"vec", kVec},
{"null", kNull},
};
MapFlag<std::string, Output> output {
parser, "prob", "output type (len, vec, null)", {"output"}, kOutput,
};
Positional<std::string> vpath {
parser, "path", "video file path",
};
} // namespace param
struct Vec {
double x, y, score, len;
};
static Vec BlockMatching(const Frame& cf, const Frame& pf, int32_t bx, int32_t by) {
const auto bw = args::get(param::bw);
const auto bh = args::get(param::bh);
const auto bmix = args::get(param::bmix);
const auto bmiy = args::get(param::bmiy);
const auto bmsw = args::get(param::bmsw);
const auto bmsh = args::get(param::bmsh);
int32_t min_sx = 0, min_sy = 0;
double min_score = 1e+100; // INF
for (int32_t sy = -bmsh; sy < bmsh; ++sy) {
for (int32_t sx = -bmsw; sx < bmsw; ++sx) {
double score = 0;
for (int32_t y = 0; y < bh; y += bmiy) {
for (int32_t x = 0; x < bw; x += bmix) {
const auto c_off = (bx+x) + (by+y)*cf.w;
const auto p_off = (bx+x+sx) + (by+y+sy)*cf.w;
const auto diff = static_cast<double>(cf.Y[c_off] - pf.Y[p_off]);
score += diff*diff;
}
}
if (score < min_score) {
min_score = score;
min_sx = sx;
min_sy = sy;
}
}
}
const auto sxf = static_cast<double>(min_sx) / static_cast<double>(bmsw);
const auto syf = static_cast<double>(min_sy) / static_cast<double>(bmsh);
const auto scf = static_cast<double>(min_score) / static_cast<double>(UINT8_MAX*(bw/bmix)*(bh/bmiy));
return { .x = sxf, .y = syf, .score = scf, .len = std::sqrt(sxf*sxf+syf*syf), };
}
static Vec EachBlock(const Frame& cf, const Frame& pf, int32_t bx, int32_t by) {
const auto v = BlockMatching(cf, pf, bx, by);
switch (args::get(param::output)) {
case param::kLen:
std::cout << v.len << '\n';
break;
case param::kVec:
std::cout << bx << " " << by << " " << v.x << " " << v.y << " " << v.score << '\n';
break;
default:
break;
}
return v;
}
static void EachFrame(int32_t t, const Frame& cf, const Frame& pf) {
const auto bw = args::get(param::bw);
const auto bh = args::get(param::bh);
const auto ut = args::get(param::utime);
Enforce(cf.w == pf.w && cf.h == pf.h, "variable frame size is not allowed");
Enforce(cf.w > bw && cf.h > bh, "block size must be less than frame size");
struct Block {
double len, score;
};
static std::vector<Block> blocks;
if (t == 1) {
blocks.clear();
blocks.resize((cf.w/bw) * (cf.h/bh));
}
auto block = blocks.data();
for (int32_t by = 0; by+bh <= cf.h; by+=bh) {
for (int32_t bx = 0; bx+bw <= cf.w; bx+=bw) {
const auto v = EachBlock(cf, pf, bx, by);
block->score += v.score;
block->len += v.len;
++block;
}
}
switch (args::get(param::output)) {
case param::kLen:
case param::kVec:
std::cout << std::endl;
break;
case param::kIndex:
case param::kProb:
if (t == ut-1) {
for (size_t i = 0; i < blocks.size(); ++i) {
const auto len = blocks[i].len/(ut-1)/std::sqrt(2); // length calculation
const auto score = blocks[i].score/(ut-1);
const auto prob = std::clamp((1-len) * (1-score), 0., 1.);
if (args::get(param::output) == param::kIndex) {
if (prob > 0.95) std::cout << i << ' ';
} else {
std::cout << prob << ' ';
}
}
std::cout << std::endl;
}
break;
default:
break;
}
}
static void Exec() {
const auto bw = args::get(param::bw);
const auto bh = args::get(param::bh);
const auto ut = args::get(param::utime);
const auto dur = args::get(param::dur);
const auto offset = args::get(param::offset);
Enforce(bw > 0 && bh > 0, "block size must be greater than 0");
Enforce(ut > 0, "utime must be greater than 0");
const auto bmix = args::get(param::bmix);
const auto bmiy = args::get(param::bmiy);
const auto bmsw = args::get(param::bmsw);
const auto bmsh = args::get(param::bmsh);
Enforce(bmix > 0 && bmiy > 0, "block matching search interval must be greater than 0");
Enforce(bmsw > 0 && bmsh > 0, "block matching search region size must be greater than 0");
// open video stream
const auto vpath = args::get(param::vpath);
std::ifstream vst {vpath.c_str(), std::ifstream::binary | std::ifstream::ate};
Enforce(!!vst, "video stream is invalid");
const auto vsz = vst.tellg();
// init decoder
ISVCDecoder* dec;
Enforce(0 == WelsCreateDecoder(&dec), "decoder creation failure");
SDecodingParam decp = {};
decp.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_DEFAULT;
decp.eEcActiveIdc = ERROR_CON_SLICE_COPY;
Enforce(0 == dec->Initialize(&decp), "decoder init failure");
int declv = WELS_LOG_DEBUG;
dec->SetOption(DECODER_OPTION_TRACE_LEVEL, &declv);
uint8_t* yuv[3] = {0};
SBufferInfo frame = {};
// demux
MP4D_demux_t dem = {};
MP4D_open(&dem, [](int64_t off, void* buf, size_t sz, void* ptr) {
auto& vst = *reinterpret_cast<std::ifstream*>(ptr);
vst.seekg(off);
Enforce(!!vst, "seek failure");
vst.read(reinterpret_cast<char*>(buf), sz);
Enforce(!!vst, "read failure");
return 0;
}, &vst, vsz);
// find video track
size_t ti;
for (ti = 0; ti < dem.track_count; ++ti) {
const auto& tra = dem.track[ti];
if (tra.handler_type == MP4D_HANDLER_TYPE_VIDE) {
break;
}
}
Enforce(ti < dem.track_count, "no video track");
const auto& tra = dem.track[ti];
// consume SPS
std::vector<uint8_t> nal;
for (size_t si = 0;; ++si) {
int sz;
auto sps = reinterpret_cast<const uint8_t*>(MP4D_read_sps(&dem, ti, si, &sz));
if (!sps) break;
CopyNal(nal, sps, sz);
const auto ret = dec->DecodeFrameNoDelay(nal.data(), nal.size(), yuv, &frame);
Enforce(ret == 0, "SPS decode failure");
}
// consume PPS
for (size_t si = 0;; ++si) {
int sz;
auto pps = reinterpret_cast<const uint8_t*>(MP4D_read_pps(&dem, ti, si, &sz));
if (!pps) break;
CopyNal(nal, pps, sz);
const auto ret = dec->DecodeFrameNoDelay(nal.data(), nal.size(), yuv, &frame);
Enforce(ret == 0, "PPS decode failure");
}
// decode frame
Frame pf = {};
int32_t fidx = 0;
for (size_t si = 0; si < tra.sample_count; ++si) {
unsigned fsz, ftime, fdur;
const auto off = MP4D_frame_offset(&dem, ti, si, &fsz, &ftime, &fdur);
vst.seekg(off);
Enforce(!!vst, "NAL seek failure");
nal.resize(fsz);
vst.read(reinterpret_cast<char*>(nal.data()), fsz);
Enforce(!!vst, "NAL read failure");
for (size_t i = 0; i < nal.size();) {
uint32_t sz =
(nal[i] << 24) | (nal[i+1] << 16) | (nal[i+2] << 8) | nal[i+3];
nal[i+0] = 0;
nal[i+1] = 0;
nal[i+2] = 0;
nal[i+3] = 1;
sz += 4;
const auto ret = dec->DecodeFrameNoDelay(&nal[i], sz, yuv, &frame);
Enforce(ret == 0, "frame decode failure");
i += sz;
if (offset <= fidx && (dur == 0 || fidx-offset < dur*ut)) {
Frame cf = {yuv, frame};
if (cf.w == 0 || cf.h == 0) continue;
const auto tf = (fidx-offset)%ut;
if (tf > 0) {
EachFrame(tf, cf, pf);
}
pf = std::move(cf);
}
++fidx;
}
}
}
int main(int argc, char** argv)
try {
param::parser.ParseCLI(argc, argv);
Exec();
return EXIT_SUCCESS;
} catch (const args::Help&) {
std::cout << param::parser << std::endl;
return EXIT_SUCCESS;
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}

2
gen/CMakeLists.txt Normal file
View File

@ -0,0 +1,2 @@
add_executable(smap smap.cc)
target_link_libraries(smap PRIVATE args)

93
gen/smap.cc Normal file
View File

@ -0,0 +1,93 @@
#include <cassert>
#include <cstdlib>
#include <iostream>
#include <string>
#include <unordered_map>
#include <args.hxx>
namespace param {
using namespace ::args;
ArgumentParser parser {
"converter: feature indices + host -> stego"
};
HelpFlag help {
parser, "help", "display this menu", {'h', "help"},
};
ValueFlag<size_t> dur {
parser, "100", "duration", {"dur"}, 100,
};
ValueFlag<size_t> fnum {
parser, "50", "number of feature code alphabet", {"fnum"}, 50,
};
ValueFlag<size_t> branch {
parser, "2", "number of branch", {"branch"}, 2,
};
enum Algo {
kIncrement,
};
const std::unordered_map<std::string, Algo> kAlgo = {
{"inc", kIncrement},
};
MapFlag<std::string, Algo> algo {
parser, "inc", "generator algorithm", {"algo", "algorithm"}, kAlgo,
};
Group inc {
parser, "increment algorithm parameters"
};
ValueFlag<size_t> inc_min {
inc, "0", "min stride of each move", {"inc-min"}, 0,
};
Flag inc_time {
inc, "inc-time", "add current time value", {"inc-time"},
};
} // namespace param
size_t Step(size_t t, size_t c, size_t b) {
const auto fnum = args::get(param::fnum);
size_t ret;
switch (args::get(param::algo)) {
case param::kIncrement:
ret = c+b+args::get(param::inc_min);
if (param::inc_time) {
ret += t;
}
return ret%fnum;
default:
assert(false);
std::abort();
}
}
void Exec() {
for (size_t t = 0; t < args::get(param::dur); ++t) {
for (size_t c = 0; c < args::get(param::fnum); ++c) {
for (size_t b = 0; b < args::get(param::branch); ++b) {
std::cout << Step(t, c, b) << ' ';
}
std::cout << '\n';
}
}
}
int main(int argc, char** argv)
try {
param::parser.ParseCLI(argc, argv);
Exec();
return EXIT_SUCCESS;
} catch (const args::Help&) {
std::cout << param::parser << std::endl;
return EXIT_SUCCESS;
} catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}

36
thirdparty/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,36 @@
set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
include(FetchContent)
# ---- args ----
# repository: https://github.com/Taywee/args
# license : MIT
FetchContent_Declare(
args
URL "https://github.com/Taywee/args/archive/refs/tags/6.3.0.zip"
)
set(ARGS_BUILD_EXAMPLE OFF)
set(ARGS_BUILD_UNITTESTS OFF)
FetchContent_MakeAvailable(args)
# ---- minimp4 ----
# repository: https://github.com/lieff/minimp4
# license : CC0
FetchContent_Declare(
minimp4
URL "https://github.com/lieff/minimp4/archive/4575afb4f69ace25a1a048e25cc86bf8c8d14f2b.zip"
)
FetchContent_Populate(minimp4)
add_library(minimp4)
target_include_directories(minimp4 PUBLIC SYSTEM ${minimp4_SOURCE_DIR})
target_sources(minimp4
PUBLIC
"${minimp4_SOURCE_DIR}/minimp4.h"
PRIVATE
minimp4.c
)

2
thirdparty/minimp4.c vendored Normal file
View File

@ -0,0 +1,2 @@
#define MINIMP4_IMPLEMENTATION
#include <minimp4.h>