This commit is contained in:
falsycat 2022-07-28 00:59:44 +09:00
parent dda8cd928e
commit fe6315e468
12 changed files with 128 additions and 972 deletions

View File

@ -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
View 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

View File

@ -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

View File

@ -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
View File

@ -0,0 +1,10 @@
#include "common.hh"
namespace blky {
void Cmd_Embed() {
// TODO
}
} // namespace blky

View File

@ -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
View File

@ -0,0 +1,10 @@
#include "common.hh"
namespace blky {
void Cmd_Extract() {
// TODO
}
} // namespace blky

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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;
}
};

View File

@ -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_;
}
};