diff --git a/CMakeLists.txt b/CMakeLists.txt index 17ef352e58e8191f84a7a9388f6d3e823d5a1535..008047b9e567579fc3bc8f40ff2dcab5a993273e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,8 @@ find_package( URIParser REQUIRED ) find_package( MsgPack REQUIRED ) find_package( Eigen3 REQUIRED ) +# find_package( ffmpeg ) + if (WITH_OPTFLOW) # TODO check that cudaoptflow.hpp exists (OpenCV built with correct contrib modules) set(HAVE_OPTFLOW true) @@ -179,6 +181,9 @@ find_library(UUID_LIBRARIES NAMES uuid libuuid) else() endif() +# For ftl2mkv +check_include_file("libavformat/avformat.h" HAVE_AVFORMAT) + # Check for optional opencv components set(CMAKE_REQUIRED_INCLUDES ${OpenCV_INCLUDE_DIRS}) check_include_file_cxx("opencv2/viz.hpp" HAVE_VIZ) @@ -222,6 +227,10 @@ add_subdirectory(applications/calibration) add_subdirectory(applications/groupview) add_subdirectory(applications/player) +if (HAVE_AVFORMAT) + add_subdirectory(applications/ftl2mkv) +endif() + if (BUILD_RENDERER) add_subdirectory(components/renderers) set(HAVE_RENDER) diff --git a/applications/ftl2mkv/CMakeLists.txt b/applications/ftl2mkv/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..96ed5b890040e77516aec786ca767f6e8d430117 --- /dev/null +++ b/applications/ftl2mkv/CMakeLists.txt @@ -0,0 +1,11 @@ +set(FTL2MKVSRC + src/main.cpp +) + +add_executable(ftl2mkv ${FTL2MKVSRC}) + +target_include_directories(ftl2mkv PRIVATE src) + +target_link_libraries(ftl2mkv ftlcommon ftlcodecs ftlrgbd Threads::Threads ${OpenCV_LIBS} avutil avformat avcodec) + + diff --git a/applications/ftl2mkv/src/main.cpp b/applications/ftl2mkv/src/main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4ee909feefc277a9134e19c432268452d8c67ebc --- /dev/null +++ b/applications/ftl2mkv/src/main.cpp @@ -0,0 +1,241 @@ +#include <loguru.hpp> +#include <ftl/configuration.hpp> +#include <ftl/codecs/reader.hpp> +#include <ftl/codecs/packet.hpp> +#include <ftl/rgbd/camera.hpp> + +#include <fstream> + +#include <Eigen/Eigen> + +extern "C" { + #include <libavformat/avformat.h> +} + +using ftl::codecs::codec_t; +using ftl::codecs::Channel; + +static AVStream *add_video_stream(AVFormatContext *oc, const ftl::codecs::Packet &pkt) +{ + AVCodecContext *c; + AVStream *st; + + st = avformat_new_stream(oc, 0); + if (!st) { + fprintf(stderr, "Could not alloc stream\n"); + exit(1); + } + + AVCodecID codec_id; + switch (pkt.codec) { + case codec_t::HEVC : codec_id = AV_CODEC_ID_HEVC; break; + } + + //c = st->codec; + //c->codec_id = codec_id; + //c->codec_type = AVMEDIA_TYPE_VIDEO; + + st->time_base.den = 20; + st->time_base.num = 1; + //st->id = oc->nb_streams-1; + //st->nb_frames = 0; + st->codecpar->codec_id = codec_id; + st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; + st->codecpar->width = ftl::codecs::getWidth(pkt.definition); + st->codecpar->height = ftl::codecs::getHeight(pkt.definition); + st->codecpar->format = AV_PIX_FMT_NV12; + st->codecpar->bit_rate = 4000000; + + /* put sample parameters */ + //c->bit_rate = 4000000; + /* resolution must be a multiple of two */ + //c->width = ftl::codecs::getWidth(pkt.definition); + // c->height = ftl::codecs::getHeight(pkt.definition); + /* time base: this is the fundamental unit of time (in seconds) in terms + of which frame timestamps are represented. for fixed-fps content, + timebase should be 1/framerate and timestamp increments should be + identically 1. */ + //c->time_base.den = 20; // Frame rate + //c->time_base.num = 1; + //c->gop_size = 10; /* emit one intra frame every twelve frames at most */ + //c->pix_fmt = AV_PIX_FMT_ABGR; + + // some formats want stream headers to be separate + //if(oc->oformat->flags & AVFMT_GLOBALHEADER) + // st->codecpar-> |= CODEC_FLAG_GLOBAL_HEADER; + + return st; +} + + +int main(int argc, char **argv) { + std::string filename; + + auto root = ftl::configure(argc, argv, "player_default"); + + std::string outputfile = root->value("out", std::string("output.mkv")); + std::vector<std::string> paths = *root->get<std::vector<std::string>>("paths"); + + if (paths.size() == 0) { + LOG(ERROR) << "Missing input ftl file."; + return -1; + } else { + filename = paths[0]; + } + + std::ifstream f; + f.open(filename); + if (!f.is_open()) { + LOG(ERROR) << "Could not open file: " << filename; + return -1; + } + + ftl::codecs::Reader r(f); + if (!r.begin()) { + LOG(ERROR) << "Bad ftl file format"; + return -1; + } + + AVOutputFormat *fmt; + AVFormatContext *oc; + AVStream *video_st[10] = {nullptr}; + + av_register_all(); + + fmt = av_guess_format(NULL, outputfile.c_str(), NULL); + + if (!fmt) { + LOG(ERROR) << "Could not find suitable output format"; + return -1; + } + + /* allocate the output media context */ + oc = avformat_alloc_context(); + if (!oc) { + LOG(ERROR) << "Memory error"; + return -1; + } + oc->oformat = fmt; + snprintf(oc->filename, sizeof(oc->filename), "%s", outputfile.c_str()); + + /* open the output file, if needed */ + if (!(fmt->flags & AVFMT_NOFILE)) { + if (avio_open(&oc->pb, outputfile.c_str(), AVIO_FLAG_WRITE) < 0) { + LOG(ERROR) << "Could not open output file: " << outputfile; + return -1; + } + } + + LOG(INFO) << "Converting..."; + + int current_stream = root->value("stream", 0); + int current_channel = 0; + + //bool stream_added[10] = {false}; + + // TODO: In future, find a better way to discover number of streams... + // Read entire file to find all streams before reading again to write data + bool res = r.read(90000000000000, [¤t_stream,¤t_channel,&r,&video_st,oc](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + if (spkt.channel != static_cast<ftl::codecs::Channel>(current_channel)) return; + if (spkt.streamID == current_stream || current_stream == 255) { + + if (pkt.codec == codec_t::POSE) { + return; + } + + if (pkt.codec == codec_t::CALIBRATION) { + return; + } + + if (spkt.streamID >= 10) return; // TODO: Allow for more than 10 + + if (video_st[spkt.streamID] == nullptr) { + video_st[spkt.streamID] = add_video_stream(oc, pkt); + } + } + }); + + r.end(); + f.clear(); + f.seekg(0); + if (!r.begin()) { + LOG(ERROR) << "Bad ftl file format"; + return -1; + } + + av_dump_format(oc, 0, "output.mkv", 1); + if (avformat_write_header(oc, NULL) < 0) { + LOG(ERROR) << "Failed to write stream header"; + } + + bool seen_key[10] = {false}; + + res = r.read(90000000000000, [¤t_stream,¤t_channel,&r,&video_st,oc,&seen_key](const ftl::codecs::StreamPacket &spkt, const ftl::codecs::Packet &pkt) { + if (spkt.channel != static_cast<ftl::codecs::Channel>(current_channel)) return; + if (spkt.streamID == current_stream || current_stream == 255) { + + if (pkt.codec == codec_t::POSE) { + return; + } + + if (pkt.codec == codec_t::CALIBRATION) { + return; + } + + //LOG(INFO) << "Reading packet: (" << (int)spkt.streamID << "," << (int)spkt.channel << ") " << (int)pkt.codec << ", " << (int)pkt.definition; + + if (spkt.streamID >= 10) return; // TODO: Allow for more than 10 + + bool keyframe = false; + if (pkt.codec == codec_t::HEVC) { + // Obtain NAL unit type + int nal_type = (pkt.data[4] >> 1) & 0x3F; + // A type of 32 = VPS unit (so in this case a key frame) + if (nal_type == 32) { + seen_key[spkt.streamID] = true; + keyframe = true; + } + } + if (!seen_key[spkt.streamID]) return; + + AVPacket avpkt; + av_init_packet(&avpkt); + if (keyframe) avpkt.flags |= AV_PKT_FLAG_KEY; + avpkt.pts = spkt.timestamp - r.getStartTime(); + avpkt.dts = avpkt.pts; + avpkt.stream_index= video_st[spkt.streamID]->index; + avpkt.data= const_cast<uint8_t*>(pkt.data.data()); + avpkt.size= pkt.data.size(); + avpkt.duration = 1; + + //LOG(INFO) << "write frame: " << avpkt.pts << "," << avpkt.stream_index << "," << avpkt.size; + + /* write the compressed frame in the media file */ + auto ret = av_write_frame(oc, &avpkt); + if (ret != 0) { + LOG(ERROR) << "Error writing frame: " << ret; + } + } + }); + + av_write_trailer(oc); + //avcodec_close(video_st->codec); + + for (int i=0; i<10; ++i) { + if (video_st[i]) av_free(video_st[i]); + } + + if (!(fmt->flags & AVFMT_NOFILE)) { + /* close the output file */ + avio_close(oc->pb); + } + + av_free(oc); + + if (!res) LOG(INFO) << "Done."; + + r.end(); + + ftl::running = false; + return 0; +} diff --git a/components/codecs/src/reader.cpp b/components/codecs/src/reader.cpp index c12c6c6dce50b3ac28b57cba76016216823bb12a..b2d146422abefc388a57036e6fe86286ee8439e9 100644 --- a/components/codecs/src/reader.cpp +++ b/components/codecs/src/reader.cpp @@ -57,7 +57,6 @@ bool Reader::read(int64_t ts, const std::function<void(const ftl::codecs::Stream msgpack::object_handle msg; if (!buffer_.next(msg)) { - LOG(INFO) << "NO Message: " << buffer_.nonparsed_size(); partial = true; continue; }