add VideoDecoder/Encoder for blocky CLI tool
This commit is contained in:
parent
b88b2fe47e
commit
c9f19b960f
@ -7,9 +7,13 @@ target_sources(blocky
|
||||
|
||||
bytes.hh
|
||||
common.hh
|
||||
video_encoder.hh
|
||||
video_decoder.hh
|
||||
)
|
||||
target_link_libraries(blocky
|
||||
PUBLIC
|
||||
args
|
||||
liblocky
|
||||
minimp4
|
||||
openh264
|
||||
)
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include <exception>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
#include "common.hh"
|
||||
@ -10,13 +11,16 @@
|
||||
|
||||
#include <args.hxx>
|
||||
|
||||
#include "video_decoder.hh"
|
||||
#include "video_encoder.hh"
|
||||
|
||||
|
||||
using namespace blky;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
args::ArgumentParser parser(
|
||||
"liblocky command line tool",
|
||||
"liblocky allow you to embed bits into video data secretly");
|
||||
"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};
|
||||
@ -28,11 +32,13 @@ int main(int argc, char** argv) {
|
||||
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
|
||||
@ -97,12 +103,15 @@ int main(int argc, char** argv) {
|
||||
{"probgen-normalize"},
|
||||
};
|
||||
|
||||
try {
|
||||
parser.ParseCLI(argc, argv);
|
||||
|
||||
std::vector<uint8_t> bytes;
|
||||
std::vector<uint32_t> features;
|
||||
std::vector<double> feature_probs;
|
||||
std::optional<VideoDecoder> decoder;
|
||||
|
||||
int main(int argc, char** argv)
|
||||
try {
|
||||
parser.ParseCLI(argc, argv);
|
||||
|
||||
// read input
|
||||
switch (args::get(from)) {
|
||||
@ -140,7 +149,11 @@ int main(int argc, char** argv) {
|
||||
break;
|
||||
|
||||
case kVideo:
|
||||
assert(false);
|
||||
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)) {
|
||||
@ -253,6 +266,7 @@ int main(int argc, char** argv) {
|
||||
case kVideo:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
|
||||
} catch (const args::Help&) {
|
||||
std::cout << parser << std::endl;
|
||||
@ -272,5 +286,3 @@ int main(int argc, char** argv) {
|
||||
std::cerr << "runtime error: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
169
blocky/video_decoder.hh
Normal file
169
blocky/video_decoder.hh
Normal file
@ -0,0 +1,169 @@
|
||||
#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;
|
||||
|
||||
void Decode() {
|
||||
if (temp_consumed_ >= temp_.size()) {
|
||||
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();
|
||||
|
||||
} 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(), static_cast<int>(nal_size), yuv_, &frame_)) {
|
||||
throw std::runtime_error {"failed to decode a frame"};
|
||||
}
|
||||
i += nal_size;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
79
blocky/video_encoder.hh
Normal file
79
blocky/video_encoder.hh
Normal file
@ -0,0 +1,79 @@
|
||||
#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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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