commit 33ed83bb93743d13e0594cc8c1b370e9495e3eee Author: Joshua Moerman Date: Wed Apr 21 14:22:27 2021 +0200 stuff diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..392cd04 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required(VERSION 3.5) + +project(ffmpegpp LANGUAGES CXX) + +# set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_CXX_STANDARD 14) +# set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_library(ffmpegpp STATIC + ffmpegpp.cpp + ffmpegpp.hpp +) + +find_path(AVUTIL_INCLUDE_DIR libavutil/avutil.h) +find_library(AVUTIL_LIBRARY avutil) + +find_path(SWSCALE_INCLUDE_DIR libswscale/swscale.h) +find_library(SWSCALE_LIBRARY swscale) + +find_path(AVCODEC_INCLUDE_DIR libavcodec/avcodec.h) +find_library(AVCODEC_LIBRARY avcodec) + +find_path(AVFORMAT_INCLUDE_DIR libavformat/avformat.h) +find_library(AVFORMAT_LIBRARY avformat) + +target_include_directories(ffmpegpp PRIVATE ${AVUTIL_INCLUDE_DIR} ${SWSCALE_INCLUDE_DIR} ${AVCODEC_INCLUDE_DIR} ${AVFORMAT_INCLUDE_DIR}) +target_link_libraries(ffmpegpp ${AVUTIL_LIBRARY} ${SWSCALE_LIBRARY} ${AVCODEC_LIBRARY} ${AVFORMAT_LIBRARY}) + +target_compile_definitions(ffmpegpp PRIVATE) diff --git a/ffmpegpp.cpp b/ffmpegpp.cpp new file mode 100644 index 0000000..9e3bbd2 --- /dev/null +++ b/ffmpegpp.cpp @@ -0,0 +1,165 @@ +#include "ffmpegpp.hpp" + +extern "C" { +#include +#include +#include +#include +} + +#include +#include + +namespace av { + +codec find_encoder(codec_id id) { + auto ptr = avcodec_find_encoder(static_cast(id)); + if(!ptr) throw error("Could not find codec"); + return {ptr}; +} + + +frame::frame() + : ptr(av_frame_alloc()) {} + +void frame::deleter::operator()(AVFrame *p) { + av_frame_free(&p); +} + + +packet::packet() + : ptr(av_packet_alloc()) {} + +void packet::deleter::operator()(AVPacket *p) { + av_packet_free(&p); +} + + +format_context::format_context() : ptr(avformat_alloc_context()) {} + +format_context::format_context(const std::string &url) { + AVFormatContext * p = nullptr; + avformat_open_input(&p, url.c_str(), nullptr, nullptr); + // This might be optional, but AFAIK people always do this after opening input + avformat_find_stream_info(p, nullptr); + ptr.reset(p); +} + +codec_context format_context::context_from_stream(int i) const { + (void)ptr->streams[i]->codecpar; + return {}; +} + +void format_context::deleter::operator()(AVFormatContext *p) { + avformat_free_context(p); +} + + +codec_context::codec_context() + : ptr(avcodec_alloc_context3(nullptr)) {} + +codec_context::codec_context(codec const & c) + : ptr(avcodec_alloc_context3(c.ptr)) {} + +void codec_context::open(const codec &c) { + if (avcodec_open2(ptr.get(), c.ptr, nullptr) < 0) { + throw error("Open coded failed"); + } +} + +void codec_context::send_frame(const frame & f) { + if (avcodec_send_frame(ptr.get(), f.ptr.get()) < 0) { + throw error("Could not send frame"); + } +} + +int codec_context::receive_packet(packet & p) { + auto ret = avcodec_receive_packet(ptr.get(), p.ptr.get()); + if (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + return ret; + if (ret < 0) + throw error("Could not receive packet"); + return ret; +} + +void codec_context::send_packet(const packet & p) { + if (avcodec_send_packet(ptr.get(), p.ptr.get()) < 0) + throw error("send packet error"); +} + +int codec_context::receive_frame(frame & f) { + auto ret = avcodec_receive_frame(ptr.get(), f.ptr.get()); + if (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + return ret; + if (ret < 0) + throw error("could not receive frame"); + return ret; +} + +void codec_context::set_parameters(const AVCodecParameters * par) { + if (avcodec_parameters_to_context(ptr.get(), par) < 0) + throw error("error with parameters"); +} + +void codec_context::deleter::operator()(AVCodecContext *p) { + avcodec_free_context(&p); +} + + +void encode_as_jpg(const frame & frame, const std::string & filename){ + const auto codec = av::find_encoder(AV_CODEC_ID_MJPEG); + + codec_context codec_ctx(codec); + codec_ctx.ptr->pix_fmt = static_cast(frame.ptr->format); + codec_ctx.ptr->width = frame.ptr->width; + codec_ctx.ptr->height = frame.ptr->height; + codec_ctx.ptr->time_base = av_make_q(1, 1); + + codec_ctx.open(codec); + codec_ctx.send_frame(frame); + + packet p; + std::ofstream file(filename); + + int ret = 0; + while (ret >= 0) { + ret = codec_ctx.receive_packet(p); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + return; + + file.write(reinterpret_cast(p.ptr->data), p.ptr->size); + av_packet_unref(p.ptr.get()); + } +} + +frame open_image(std::string const & filename){ + format_context format_ctx(filename); + + AVCodec * ptr; + const auto idx = av_find_best_stream(format_ctx.ptr.get(), AVMEDIA_TYPE_VIDEO, -1, -1, &ptr, 0); + if (idx < 0) + std::clog << "error finding best stream" << std::endl; + + const auto st = format_ctx.ptr->streams[idx]; + const auto dc = avcodec_find_decoder(st->codecpar->codec_id); + + if (!dc) + std::clog << "error finding decoder" << std::endl; + + codec_context codec_ctx({dc}); + codec_ctx.set_parameters(st->codecpar); + codec_ctx.open({dc}); + + packet p; + + // TODO: parse + av_parser_parse2(); + + codec_ctx.send_packet(p); + + frame f; + codec_ctx.receive_frame(f); + return f; +} + +} diff --git a/ffmpegpp.hpp b/ffmpegpp.hpp new file mode 100644 index 0000000..3c17250 --- /dev/null +++ b/ffmpegpp.hpp @@ -0,0 +1,78 @@ +#pragma once + +extern "C" { +typedef struct AVCodec AVCodec; +typedef struct AVCodecContext AVCodecContext; +typedef struct AVCodecParameters AVCodecParameters; +typedef struct AVFrame AVFrame; +typedef struct AVFormatContext AVFormatContext; +typedef struct AVPacket AVPacket; +} + +#include +#include + + +namespace av { + struct error : public std::runtime_error { + using runtime_error::runtime_error; + }; + + // an enum in reality + using codec_id = int; + + // Has no ownership + struct codec { + AVCodec * ptr; + }; + + codec find_encoder(codec_id id); + + struct frame { + frame(); + + struct deleter { void operator()(AVFrame * p); }; + std::unique_ptr ptr; + }; + + // TODO: are ref counted... How to do? + struct packet { + packet(); + + struct deleter { void operator()(AVPacket * p); }; + std::unique_ptr ptr; + }; + + struct codec_context { + codec_context(); + codec_context(codec const & c); + + // If constructed with codec, it must be the same + // need not be closed + void open(codec const & c); + + void send_frame(frame const & f); + int receive_packet(packet & p); + + void send_packet(packet const & p); + int receive_frame(frame & f); + + void set_parameters(const AVCodecParameters *par); + + struct deleter { void operator()(AVCodecContext * p); }; + std::unique_ptr ptr; + }; + + struct format_context { + format_context(); + format_context(const std::string & url); + + codec_context context_from_stream(int i) const; + + struct deleter { void operator()(AVFormatContext * p); }; + std::unique_ptr ptr; + }; + + void encode_as_jpg(const frame & frame, const std::string & filename); + frame open_image(const std::string & filename); +}