diff --git a/conv/CMakeLists.txt b/conv/CMakeLists.txt index 2d2e5fc..4935b1d 100644 --- a/conv/CMakeLists.txt +++ b/conv/CMakeLists.txt @@ -1,4 +1,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${BLKY_CXX_FLAGS} -I ${PROJECT_SOURCE_DIR}") -add_executable(video_bprob video_bprob.cc) +add_executable(bidx_video common.hh bidx_video.cc) +target_link_libraries(bidx_video PRIVATE args minimp4 openh264) + +add_executable(video_bprob common.hh video_bprob.cc) target_link_libraries(video_bprob PRIVATE args minimp4 openh264) diff --git a/conv/bidx_video.cc b/conv/bidx_video.cc new file mode 100644 index 0000000..28e5aa0 --- /dev/null +++ b/conv/bidx_video.cc @@ -0,0 +1,302 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "conv/common.hh" + + +namespace param { +using namespace ::args; + +ArgumentParser parser { + "converter: video + indices -> video" +}; +HelpFlag help { + parser, "help", "display this menu", {'h', "help"}, +}; + +ValueFlag bw { + parser, "128", "width of blocks (px)", {"block-w"}, 128, +}; +ValueFlag bh { + parser, "128", "height of blocks (px)", {"block-h"}, 128, +}; +ValueFlag utime { + parser, "10", "duration of each feature (frame)", {"utime"}, 10, +}; + +Positional dst { + parser, "path", "destination video file path", +}; +Positional src { + parser, "path", "source video file path", +}; + +// from stdin +std::vector> indices; + +} // namespace param + + +// util +static std::vector> ReadIndices( + std::istream&) noexcept; + +static void Embed(int32_t t, Frame& dst, const Frame& base) { + const auto bw = args::get(param::bw); + const auto bh = args::get(param::bw); + + 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]) { + 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] = (x == 0 || y == 0)? 0: base.Y[off]; // TODO: remove test code + } + } + } +} + +static void Exec() { + const auto bw = args::get(param::bw); + const auto bh = args::get(param::bw); + const auto ut = args::get(param::utime); + 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 = ReadIndices(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(ptr); + st.seekg(off); + Enforce(!!st, "seek failure"); + st.read(reinterpret_cast(buf), sz); + Enforce(!!st, "read failure"); + return 0; + }, &srcst, srcsz); + + // find video track + int 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& t = dem.track[ti]; + + // calc params + const auto tscale = t.timescale; + const auto dur = + (static_cast(t.duration_hi) << 32) | + static_cast(t.duration_lo); + const auto dursec = static_cast(dur)/static_cast(tscale); + + const float fps = static_cast(t.sample_count)/dursec; + const auto fps9 = static_cast(90000/fps); + const int32_t w = t.SampleDescription.video.width; + const int32_t h = t.SampleDescription.video.height; + + // init encoder + ISVCEncoder* enc; + Enforce(0 == WelsCreateSVCEncoder(&enc), "encoder creation failure"); + + SEncParamBase encp = {}; + encp.iUsageType = SCREEN_CONTENT_REAL_TIME; + encp.fMaxFrameRate = fps; + encp.iPicWidth = w; + encp.iPicHeight = h; + encp.iTargetBitrate = 5000000; + 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(ptr); + st.seekp(off); + Enforce(!!st, "muxer seek failure"); + st.write(reinterpret_cast(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 nal; + for (size_t si = 0;; ++si) { + int sz; + auto sps = reinterpret_cast(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(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 < t.sample_count; ++si) { + unsigned fsz, time, dur; + const auto off = MP4D_frame_offset(&dem, ti, si, &fsz, &time, &dur); + + srcst.seekg(off); + Enforce(!!srcst, "NAL seek failure"); + + nal.resize(fsz); + srcst.read(reinterpret_cast(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) { + // alter the frame if it's not the first + Frame cf = {yuv, frame}; + if (fidx%ut > 0) { + Embed(fidx/ut, cf, bf); + } + + // encode + 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 (fidx%ut == 0) { + 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; +} + + + +static std::vector> ReadIndices(std::istream& st) noexcept { + std::vector> ret; + + std::string line; + while (std::getline(st, line)) { + std::istringstream sst {line}; + ret.emplace_back(std::istream_iterator {sst}, + std::istream_iterator {}); + } + return ret; +} diff --git a/conv/common.hh b/conv/common.hh index d9236ec..fc8496b 100644 --- a/conv/common.hh +++ b/conv/common.hh @@ -1,7 +1,12 @@ #pragma once +#include +#include #include #include +#include + +#include inline void Enforce(bool eval, const std::string& msg) { diff --git a/conv/video_bprob.cc b/conv/video_bprob.cc index c99f98d..05b01e2 100644 --- a/conv/video_bprob.cc +++ b/conv/video_bprob.cc @@ -73,25 +73,10 @@ Positional vpath { } // namespace param -struct Frame { - std::vector Y; - std::vector U; - std::vector V; - - int32_t w, h; - int32_t hw, hh; - - Frame() = default; - Frame(uint8_t* yuv[3], const SBufferInfo& frame); -}; - struct Vec { double x, y; }; -// utilities -static void CopyNal(std::vector&, const uint8_t* buf, size_t sz) noexcept; - static Vec BlockMatching(const Frame& cf, const Frame& pf, int32_t bx, int32_t by) { const auto bmw = args::get(param::bmw); @@ -205,14 +190,14 @@ static void Exec() { // init decoder ISVCDecoder* dec; - WelsCreateDecoder(&dec); + Enforce(0 == WelsCreateDecoder(&dec), "decoder creation failure"); SDecodingParam decp = {}; decp.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_DEFAULT; decp.eEcActiveIdc = ERROR_CON_SLICE_COPY; - dec->Initialize(&decp); + Enforce(0 == dec->Initialize(&decp), "decoder init failure"); - int declv = WELS_LOG_INFO; + int declv = WELS_LOG_DEBUG; dec->SetOption(DECODER_OPTION_TRACE_LEVEL, &declv); uint8_t* yuv[3] = {0}; @@ -287,7 +272,7 @@ static void Exec() { nal[i+3] = 1; sz += 4; - const auto ret = dec->DecodeFrameNoDelay(&nal[i], fsz, yuv, &frame); + const auto ret = dec->DecodeFrameNoDelay(&nal[i], sz, yuv, &frame); Enforce(ret == 0, "frame decode failure"); Frame cf = {yuv, frame}; @@ -314,42 +299,3 @@ try { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } - - -Frame::Frame(uint8_t* yuv[3], const SBufferInfo& frame) { - w = static_cast(frame.UsrData.sSystemBuffer.iWidth); - h = static_cast(frame.UsrData.sSystemBuffer.iHeight); - hw = w/2; - hh = h/2; - - const auto ystride = static_cast(frame.UsrData.sSystemBuffer.iStride[0]); - const auto uvstride = static_cast(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 offset = y*uvstride; - const auto srcu = yuv[0] + y*uvstride; - const auto srcv = yuv[1] + 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); - } -} - -static void CopyNal(std::vector& 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); -}