diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d91d243 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +compile_commands.json +tags diff --git a/meson.build b/meson.build index 47704f8..5673c87 100644 --- a/meson.build +++ b/meson.build @@ -4,13 +4,22 @@ project('magicstore', 'cpp', ) libopencv_dep = dependency('opencv4') +libavcodec_dep = dependency('libavcodec', version: '>=58.134.100') +libavutil_dep = dependency('libavutil', version: '>=56.70.100') +libswscale_dep = dependency('libswscale', version: '>=5.9.100') executable(meson.project_name(), 'src/main.cpp', 'src/camera.cpp', 'src/file_handle.cpp', + 'src/ffmpeg/avcodec.cpp', + 'src/ffmpeg/avdeleter.cpp', + 'src/ffmpeg/decoder.cpp', dependencies: [ libopencv_dep, + libavcodec_dep, + libavutil_dep, + libswscale_dep, ], install: true, ) diff --git a/src/camera.cpp b/src/camera.cpp index 8ac0eea..8cbd4cb 100644 --- a/src/camera.cpp +++ b/src/camera.cpp @@ -97,8 +97,9 @@ void Camera::start_streaming() { allocate_buffers(); } -std::vector& Camera::read_frame (std::vector& out_buf) { - const char* const buffer = m_local->input_buffer; +std::vector& Camera::read_frame (std::vector& out_buf) { + static_assert(sizeof(char) == sizeof(std::uint8_t), "Unsupported architecture"); + const std::uint8_t* const buffer = reinterpret_cast(m_local->input_buffer); v4l2_buffer& buffer_info = m_local->buffer_info; // Queue the buffer diff --git a/src/camera.hpp b/src/camera.hpp index 745da10..be38777 100644 --- a/src/camera.hpp +++ b/src/camera.hpp @@ -34,7 +34,7 @@ public: FileHandle file_handle() const; void start_streaming(); - std::vector& read_frame (std::vector& out_buf); + std::vector& read_frame (std::vector& out_buf); FrameFormat frame_size() const; void set_frame_size (const FrameFormat& new_size); unsigned int stride() const; diff --git a/src/ffmpeg/avcodec.cpp b/src/ffmpeg/avcodec.cpp new file mode 100644 index 0000000..75e1bce --- /dev/null +++ b/src/ffmpeg/avcodec.cpp @@ -0,0 +1,51 @@ +#include "avcodec.hpp" +extern "C" { +#include +} //extern C +#include +#include + +namespace mgs::ffmpeg { +namespace { +::AVPixelFormat get_favourite_format_ifp (::AVCodecContext* s, const ::AVPixelFormat* fmt) { + const ::AVPixelFormat* curr_fmt = fmt; + const auto fav_format = static_cast<::AVPixelFormat>(Decoder::FavouriteFormat); + while (*curr_fmt != -1) { + if (*curr_fmt == fav_format) + return fav_format; + ++curr_fmt; + } + return ::avcodec_default_get_format(s, fmt); +} +} //unnamed namespace + +AVCodec::AVCodec (::AVCodecID id) : + m_avcodec(::avcodec_find_decoder(id)) +{ + if (not m_avcodec) + throw std::runtime_error("Unable to finde avcodec with id " + std::to_string(id)); +} + +AVCodec::~AVCodec() noexcept = default; + +Decoder AVCodec::make_decoder (unsigned int width, unsigned int height) { + ::AVCodec* const codec = m_avcodec.get(); + Decoder::UniqueAVCodecContext context{::avcodec_alloc_context3(codec)}; + if (not context) + throw std::runtime_error("Could not allocate video codec context"); + + + if (codec->capabilities & AV_CODEC_CAP_TRUNCATED) + context->flags |= AV_CODEC_FLAG_TRUNCATED; // we do not send complete frames + + context->width = width; + context->height = height; + context->get_format = &get_favourite_format_ifp; + + if (::avcodec_open2(context.get(), codec, nullptr) < 0) + throw std::runtime_error("Could not open codec"); + + return {std::move(context)}; +} + +} //namespace mgs::ffmpeg diff --git a/src/ffmpeg/avcodec.hpp b/src/ffmpeg/avcodec.hpp new file mode 100644 index 0000000..ba3b5c7 --- /dev/null +++ b/src/ffmpeg/avcodec.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "decoder.hpp" +#include "avdeleter.hpp" +extern "C" { +#include +} //extern C + +namespace mgs::ffmpeg { + +class AVCodec { +public: + AVCodec() = delete; + explicit AVCodec (::AVCodecID id); + ~AVCodec() noexcept; + + Decoder make_decoder (unsigned int width, unsigned int height); + +private: + unique_av_ptr m_avcodec; +}; +} //namespace mgs::ffmpeg diff --git a/src/ffmpeg/avdeleter.cpp b/src/ffmpeg/avdeleter.cpp new file mode 100644 index 0000000..2250455 --- /dev/null +++ b/src/ffmpeg/avdeleter.cpp @@ -0,0 +1,66 @@ +#include "avdeleter.hpp" +extern "C" { +#include +#include +} //extern C +#if !defined(NDEBUG) +# include +#endif + +namespace mgs::ffmpeg { +namespace { +void delete_avcodec_object (::AVCodec*) noexcept { + //no close function for this one +#if !defined(NDEBUG) + std::cout << "AVCodec object freed\n"; +#endif +} + +void delete_avcodec_object (::AVCodecContext* ptr) noexcept { + //avcodec_close(ptr); + //av_free(ptr); + avcodec_free_context(&ptr); +#if !defined(NDEBUG) + std::cout << "AVCodecContext object closed and freed\n"; +#endif +} + +void delete_avcodec_object (::AVFrame* ptr) noexcept { + av_frame_free(&ptr); +#if !defined(NDEBUG) + std::cout << "AVFrame object freed\n"; +#endif +} + +void delete_avcodec_object (::AVPacket* ptr) noexcept { + av_packet_free(&ptr); +#if !defined(NDEBUG) + std::cout << "AVPacket object freed\n"; +#endif +} + +void delete_avcodec_object (::SwsContext* ptr) noexcept { + sws_freeContext(ptr); +#if !defined(NDEBUG) + std::cout << "SwsContext object freed\n"; +#endif +} + +void delete_avcodec_object (std::uint8_t* ptr) noexcept { + ::av_free(ptr); +} +} //unnamed namespace + +template +void AVDeleter::operator() (T* ptr) const noexcept { + delete_avcodec_object(ptr); +} + +template struct AVDeleter<::AVCodec>; +template struct AVDeleter<::AVCodecContext>; +template struct AVDeleter<::AVPacket>; +template struct AVDeleter<::AVFrame>; +template struct AVDeleter<::SwsContext>; +template struct AVDeleter; + +} // namespace mgs::ffmpeg diff --git a/src/ffmpeg/avdeleter.hpp b/src/ffmpeg/avdeleter.hpp new file mode 100644 index 0000000..7f576af --- /dev/null +++ b/src/ffmpeg/avdeleter.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include + +struct AVCodec; +struct AVCodecContext; +struct AVPacket; +struct AVFrame; +struct SwsContext; + +namespace mgs::ffmpeg { +template +struct AVDeleter { + void operator()(T*) const noexcept; +}; + +template +using unique_av_ptr = std::unique_ptr< T, AVDeleter >; +} //namespace mgs::ffmpeg diff --git a/src/ffmpeg/decoder.cpp b/src/ffmpeg/decoder.cpp new file mode 100644 index 0000000..e930f9e --- /dev/null +++ b/src/ffmpeg/decoder.cpp @@ -0,0 +1,112 @@ +#include "decoder.hpp" +#include +extern "C" { +#include +#include +#include +} //extern C +#include +#include +#include + +namespace mgs::ffmpeg { +namespace { +constexpr int Align = 32; +} //unnamed namespace + +const int Decoder::FavouriteFormat = static_cast(::AVPixelFormat::AV_PIX_FMT_RGB24); + +Decoder::Decoder (UniqueAVCodecContext&& context) : + m_context(std::move(context)), + m_packet(::av_packet_alloc()), + m_frame1(::av_frame_alloc()), + m_frame2(::av_frame_alloc()) +{ +} + +Decoder::~Decoder() noexcept = default; + +std::size_t Decoder::decode (const std::uint8_t* input, std::size_t input_size, std::uint8_t* out, std::size_t out_size) { + //from https://stackoverflow.com/questions/55131040/how-to-decode-mjpeg-with-ffmpeg + if (not input_size) + return 0; + + UniqueAVMem buf_data {static_cast(::av_malloc(input_size))}; + std::copy(input, input + input_size, buf_data.get()); + if (::av_packet_from_data (m_packet.get(), buf_data.get(), input_size)) + throw std::runtime_error("Setting packet data failed"); + buf_data.release(); + + const int send_packet_ret = ::avcodec_send_packet(m_context.get(), m_packet.get()); + ::av_packet_unref(m_packet.get()); + if (send_packet_ret < 0) + return 0; + + if (not m_sws_context) + init_sws_context(); + + const int recv_frame_ret = ::avcodec_receive_frame(m_context.get(), m_frame1.get()); + if (recv_frame_ret < 0) + return 0; + + const std::size_t new_out_size = ::av_image_get_buffer_size( + static_cast<::AVPixelFormat>(FavouriteFormat), + m_context->width, + m_context->height, + Align + ); + + assert(m_frame2->data[0]); + ::sws_scale( + m_sws_context.get(), + m_frame1->data, + m_frame1->linesize, + 0, + m_frame1->height, + m_frame2->data, + m_frame2->linesize + ); + const std::size_t out_size_safe = std::min(out_size, new_out_size); + std::copy(m_frame2->data[0], m_frame2->data[0] + out_size_safe, out); + + return out_size_safe; +} + +void Decoder::init_sws_context() { + const std::size_t new_out_size = ::av_image_get_buffer_size( + static_cast<::AVPixelFormat>(FavouriteFormat), + m_context->width, + m_context->height, + Align + ); + + //are you calling this method before ffmpeg had a chance to autodetect + //the correct src pix format? + //it should be a side effect of avcodec_send_packet() + assert(::AVPixelFormat::AV_PIX_FMT_NONE != m_context->pix_fmt); + + m_sws_context = UniqueSwsContext{::sws_getContext( + m_context->width, //int src_width + m_context->height, //int src_height + m_context->pix_fmt, //AV_PIX_FMT_YUVJ422P, //AVPixelFormat src_format + m_context->width, //int dst_width + m_context->height, //int dst_height + static_cast<::AVPixelFormat>(FavouriteFormat), //AVPixelFormat dst_format + SWS_FAST_BILINEAR, //int flags + nullptr, //SwsFilter* src_filter + nullptr, //SwsFilter* dst_filter + nullptr //const double* param + )}; + m_frame2_mem.reset(static_cast(::av_malloc(new_out_size))); + ::av_image_fill_arrays( + m_frame2->data, + m_frame2->linesize, + m_frame2_mem.get(), + static_cast<::AVPixelFormat>(FavouriteFormat), + m_context->width, + m_context->height, + Align + ); +} + +} //namespace mgs::ffmpeg diff --git a/src/ffmpeg/decoder.hpp b/src/ffmpeg/decoder.hpp new file mode 100644 index 0000000..19727d5 --- /dev/null +++ b/src/ffmpeg/decoder.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "avdeleter.hpp" +#include +#include + +namespace mgs::ffmpeg { +class Decoder { +public: + typedef unique_av_ptr UniqueAVCodecContext; + typedef unique_av_ptr UniqueAVFrame; + typedef unique_av_ptr UniqueAVPacket; + typedef unique_av_ptr UniqueSwsContext; + typedef unique_av_ptr UniqueAVMem; + + static const int FavouriteFormat; + + Decoder (UniqueAVCodecContext&& context); + ~Decoder() noexcept; + std::size_t decode (const std::uint8_t* input, std::size_t input_size, std::uint8_t* out, std::size_t out_size); + +private: + void init_sws_context(); + + UniqueAVMem m_frame2_mem; + UniqueAVCodecContext m_context; + UniqueAVPacket m_packet; + UniqueAVFrame m_frame1; + UniqueAVFrame m_frame2; + UniqueSwsContext m_sws_context; +}; +} //namespace mgs::ffmpeg diff --git a/src/main.cpp b/src/main.cpp index e74a35c..a3eb7b0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ #include "camera.hpp" +#include "ffmpeg/avcodec.hpp" #if !defined(NDEBUG) # include # include @@ -45,14 +46,28 @@ int main() { mgs::Camera cam("/dev/video0", {}, false); cam.set_frame_size({1280, 720, MjpegFormat}); + mgs::ffmpeg::AVCodec avcodec{::AVCodecID::AV_CODEC_ID_MJPEG}; + auto cam_frame_format = cam.frame_size(); + auto decoder{avcodec.make_decoder(cam_frame_format.width, cam_frame_format.height)}; + print_supported_frame_formats(cam); cam.start_streaming(); - std::vector my_frame; + std::vector my_frame; auto data = cam.read_frame(my_frame); + std::cout << "Read frame of " << data.size() << " bytesfrom camera\n"; std::ofstream file_wr("temp.jpg", std::ios::binary | std::ios::out); - file_wr.write(data.data(), data.size()); + file_wr.write(reinterpret_cast(data.data()), data.size()); + + std::vector buff; + buff.resize(cam_frame_format.width * 3 * cam_frame_format.height); + const auto out_data_size = decoder.decode(data.data(), data.size(), buff.data(), buff.size()); + std::cout << "Allocated buffer size: " << buff.size() << '\n'; + std::cout << "Decoded buffer size: " << out_data_size << '\n'; + + std::ofstream rgb_file_wr("rgb.data", std::ios::binary | std::ios::out); + rgb_file_wr.write(reinterpret_cast(buff.data()), buff.size()); } catch (const std::runtime_error& err) { std::cerr << "Unhandled exception: " << err.what() << '\n';