diff --git a/include/libav/codec/context.hpp b/include/libav/codec/context.hpp new file mode 100644 index 0000000..82345fb --- /dev/null +++ b/include/libav/codec/context.hpp @@ -0,0 +1,100 @@ +#ifndef LIBAV_CODEC_CONTEXT_HPP +#define LIBAV_CODEC_CONTEXT_HPP + +#include "libav/fmt/context.hpp" + +#include //exchange + +extern "C"{ +# include +} + +namespace libav::codec{ + + class context + { + private: + AVCodecContext* m_context = nullptr; + const AVCodec* m_codec = nullptr; + bool m_open = false; + public: + context(const libav::fmt::input_context& ctx, int index){ + AVCodecParameters* cparms = ctx->streams[index]->codecpar; + m_codec = avcodec_find_decoder(cparms->codec_id); + m_context = avcodec_alloc_context3(m_codec); + if(!m_context) + return; + avcodec_parameters_to_context(m_context, cparms); + } + context(const libav::fmt::output_context& ctx, int index){ + AVCodecParameters* cparms = ctx->streams[index]->codecpar; + m_codec = avcodec_find_encoder(cparms->codec_id); + m_context = avcodec_alloc_context3(m_codec); + if(!m_context) + return; + avcodec_parameters_to_context(m_context, cparms); + } + context(const AVCodec* codec){ + m_codec = codec; + m_context = avcodec_alloc_context3(m_codec); + } + context(const context&) = delete; + context(context&& c)noexcept: + m_context(std::exchange(c.m_context, nullptr)), + m_codec(c.m_codec), + m_open(c.m_open){} + ~context(void){ + reset(); + } + + context& operator=(const context&) = delete; + context& operator=(context&& c)noexcept{ + std::swap(m_context, c.m_context); + m_codec = c.m_codec; + std::swap(m_open, c.m_open); + return *this; + } + bool open(void){ + int ret = avcodec_open2(m_context, m_codec, NULL); + return (m_open = (ret >= 0)); + } + void reset(void){ + if(m_open) + avcodec_close(m_context); + if(m_context) + avcodec_free_context(&m_context); + m_open = false; + m_context = nullptr; + m_codec = nullptr; + } + AVCodecContext* release(void){ + return std::exchange(m_context, nullptr); + } + + AVCodecContext* operator->(void){ + return m_context; + } + const AVCodecContext* operator->(void)const{ + return m_context; + } + + AVCodecContext* get(void){ + return m_context; + } + const AVCodecContext* get(void)const{ + return m_context; + } + operator AVCodecContext*(void){ + return m_context; + } + operator const AVCodecContext*(void)const{ + return m_context; + } + operator bool(void)const{ + return m_context; + } + }; + +} + +#endif diff --git a/include/libav/fmt/context.hpp b/include/libav/fmt/context.hpp index 6fb3a4e..ff9fce8 100644 --- a/include/libav/fmt/context.hpp +++ b/include/libav/fmt/context.hpp @@ -22,9 +22,18 @@ namespace libav::fmt{ reset(); } } + input_context(const input_context&) = delete; + input_context(input_context&& i)noexcept: + m_context(std::exchange(i.m_context, nullptr)){} ~input_context(void){ reset(); } + + input_context& operator=(const input_context&) = delete; + input_context& operator=(input_context&& i){ + std::swap(m_context, i.m_context); + return *this; + } void reset(AVFormatContext* context = nullptr){ if(m_context) avformat_close_input(&m_context); @@ -65,13 +74,22 @@ namespace libav::fmt{ return; if(!(m_context->oformat->flags & AVFMT_NOFILE)){ if(avio_open(&m_context->pb, filename, AVIO_FLAG_WRITE) < 0){ - reset(); + avformat_free_context(m_context); } } } + output_context(const output_context&) = delete; + output_context(output_context&& o)noexcept: + m_context(std::exchange(o.m_context, nullptr)){} ~output_context(void){ reset(); } + + output_context& operator=(const output_context&) = delete; + output_context& operator=(output_context&& o)noexcept{ + std::swap(m_context, o.m_context); + return *this; + } void reset(AVFormatContext* context = nullptr){ if(m_context){ if(!(m_context->oformat->flags & AVFMT_NOFILE)){ diff --git a/include/libav/frame.hpp b/include/libav/frame.hpp new file mode 100644 index 0000000..a716639 --- /dev/null +++ b/include/libav/frame.hpp @@ -0,0 +1,81 @@ +#ifndef LIBAV_FRAME_HPP +#define LIBAV_FRAME_HPP + +extern "C"{ +# include +} + +#include //exchange + +namespace libav{ + + class frame + { + private: + AVFrame* m_frame; + bool m_freep = false; + public: + frame(void){ + m_frame = av_frame_alloc(); + m_frame->data[0] = nullptr; + } + frame(int width, int height, int format): + frame() + { + m_frame->width = width; + m_frame->height = height; + m_frame->format = format; + } + frame(const frame&) = delete; + frame(frame&& f)noexcept: + m_frame(std::exchange(f.m_frame, nullptr)), + m_freep(f.m_freep){} + ~frame(void){ + reset(); + } + frame& operator=(const frame&) = delete; + frame& operator=(frame&& f)noexcept{ + std::swap(m_frame, f.m_frame); + std::swap(m_freep, f.m_freep); + return *this; + } + void reset(AVFrame* frame = nullptr){ + if(m_frame){ + if(m_freep){ + av_freep(&m_frame->data[0]); + } + av_frame_unref(m_frame); + av_frame_free(&m_frame); + } + m_frame = frame; + } + void set_freep(bool b = true){ + m_freep = b; + } + AVFrame* release(void){ + return std::exchange(m_frame, nullptr); + } + + AVFrame* operator->(void){ + return m_frame; + } + const AVFrame* operator->(void)const{ + return m_frame; + } + AVFrame* get(void){ + return m_frame; + } + const AVFrame* get(void)const{ + return m_frame; + } + operator AVFrame*(void){ + return m_frame; + } + operator const AVFrame*(void)const{ + return m_frame; + } + }; + +} + +#endif diff --git a/include/libav/libav.hpp b/include/libav/libav.hpp index a0dec19..bd30b6b 100644 --- a/include/libav/libav.hpp +++ b/include/libav/libav.hpp @@ -13,6 +13,8 @@ extern "C"{ #endif namespace libav::fmt{} +namespace libav::codec{} namespace libavfmt = libav::fmt; +namespace libavcodec = libav::codec; #endif diff --git a/include/libav/packet.hpp b/include/libav/packet.hpp index 6d2b93c..f74f5f7 100644 --- a/include/libav/packet.hpp +++ b/include/libav/packet.hpp @@ -24,9 +24,21 @@ namespace libav{ m_pkt->size = 0; } } + packet(const packet&) = delete; + packet(packet&& p)noexcept: + m_pkt(std::exchange(p.m_pkt, nullptr)){} + constexpr packet(AVPacket* pkt): + m_pkt(pkt){} + ~packet(void){ reset(); } + + packet& operator=(const packet&) = delete; + packet& operator=(packet&& p)noexcept{ + std::swap(m_pkt, p.m_pkt); + return *this; + } void reset(AVPacket* packet = nullptr){ if(m_pkt) av_packet_free(&m_pkt); diff --git a/include/raii/video_man.hpp b/include/raii/video_man.hpp deleted file mode 100644 index b8a003a..0000000 --- a/include/raii/video_man.hpp +++ /dev/null @@ -1,80 +0,0 @@ -/** - This file is a part of rexy's matrix bot - Copyright (C) 2019 rexy712 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -#ifndef RAII_VIDEO_MAN_HPP -#define RAII_VIDEO_MAN_HPP - -#include //unique_ptr -#include "raii/string.hpp" - -//needs to be extern C because quality library writing -extern "C"{ -# include //AVCodecContext, AVCodec -# include //AVFormatContext -# include //sws_scale -# include //av_image_alloc -} - -namespace raii{ - - class video_man - { - public: - using context_type = std::unique_ptr; - using frame_type = std::unique_ptr; - using packet_type = std::unique_ptr; - - private: - AVFormatContext* m_vid; - AVCodec* m_codec; - AVCodecContext* m_context; - int m_stream_index = -1; - - - public: - video_man(const raii::string_base& filename); - video_man(const video_man& v) = delete; - video_man(video_man&& v); - ~video_man(void); - - int height(void)const; - int width(void)const; - raii::string get_filetype(void)const; - raii::string get_mimetype(void)const; - packet_type create_jpg_thumbnail(void); - - AVFormatContext* release(void); - void reset(const raii::string_base& filename); - void reset(void); - - operator bool(void)const; - private: - void _init(const raii::string_base& filename); - AVFormatContext* _open_video_file(const raii::string_base& filename); - static AVFrame* _init_frame(int w, int h, int format); - static void _change_color_range(SwsContext* sws_ctx); - static context_type _get_jpeg_context(const AVCodecContext* decoder); - static void _context_deleter(AVCodecContext* ptr); - static void _frame_deleter(AVFrame* ptr); - static void _packet_deleter(AVPacket* ptr); - }; - - -} - -#endif diff --git a/src/matrix.cpp b/src/matrix.cpp index beee2de..2a339c7 100644 --- a/src/matrix.cpp +++ b/src/matrix.cpp @@ -25,7 +25,16 @@ #include "raii/rjp_ptr.hpp" #include "raii/filerd.hpp" #include "fat_strings.hpp" -#include "raii/video_man.hpp" + +#include "libav/frame.hpp" +#include "libav/codec/context.hpp" +#include "libav/fmt/context.hpp" +#include "libav/packet.hpp" + +extern "C"{ +# include //sws_scale +# include //av_image_alloc +} namespace matrix{ @@ -221,23 +230,98 @@ namespace matrix{ return upload_audio(filename, raii::static_string()); } #ifdef HAS_FFMPEG + libavcodec::context _get_jpeg_context(const AVCodecContext* src){ + AVCodec* jpg_codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG); + libavcodec::context jctx(jpg_codec); + jctx->pix_fmt = AV_PIX_FMT_YUVJ420P; + jctx->width = src->width; + jctx->height = src->height; + jctx->codec_id = AV_CODEC_ID_MJPEG; + jctx->codec_type = AVMEDIA_TYPE_VIDEO; + if(src->time_base.num == 0 || src->time_base.den == 0) + jctx->time_base = AVRational{1, 30}; + else + jctx->time_base = src->time_base; + jctx.open(); + return jctx; + } + void _change_color_range(SwsContext* sws_ctx){ + int dummy[4]; + int src_range, dest_range; + int br, con, sat; + sws_getColorspaceDetails(sws_ctx, (int**)&dummy, &src_range, (int**)&dummy, &dest_range, &br, &con, &sat); + const int* coefs = sws_getCoefficients(SWS_CS_DEFAULT); + src_range = 2; + sws_setColorspaceDetails(sws_ctx, coefs, src_range, coefs, dest_range, br, con, sat); + } + libav::packet create_jpeg_thumbnail(libavfmt::input_context& src, libavcodec::context& src_ctx, int index){ + libavcodec::context jpg_context = _get_jpeg_context(src_ctx); + libav::frame src_frame(src_ctx->width, src_ctx->height, src_ctx->pix_fmt); + libav::frame jpg_frame(src_ctx->width, src_ctx->height, jpg_context->pix_fmt); + + libav::packet packet; + SwsContext* sws_ctx = sws_getContext(src_ctx->width, src_ctx->height, src_ctx->pix_fmt, + src_ctx->width, src_ctx->height, AV_PIX_FMT_YUV420P, + SWS_BILINEAR, NULL, NULL, NULL); + _change_color_range(sws_ctx); + + av_image_alloc(jpg_frame->data, jpg_frame->linesize, src_ctx->width, src_ctx->height, jpg_context->pix_fmt, 32); + jpg_frame.set_freep(); + while(av_read_frame(src, packet) >= 0){ + if(packet->stream_index == index){ + avcodec_send_packet(src_ctx, packet); + if(avcodec_receive_frame(src_ctx, src_frame) >= 0) + break; + } + av_packet_unref(packet); + } + sws_scale(sws_ctx, src_frame->data, src_frame->linesize, 0, src_ctx->height, jpg_frame->data, jpg_frame->linesize); + + sws_freeContext(sws_ctx); + + if(avcodec_send_frame(jpg_context, jpg_frame) < 0){ + return libav::packet(nullptr); + } + if(avcodec_receive_packet(jpg_context, packet) < 0){ + return libav::packet(nullptr); + } + return packet; + } + raii::string get_mimetype(const libav::fmt::input_context& ctx){ + const char* first = strstr(ctx->iformat->name, ","); + if(first) + return raii::string("video/" + raii::static_string(ctx->iformat->name, first - ctx->iformat->name)); + return raii::string("video/" + raii::static_string(ctx->iformat->name)); + } video_info bot::upload_video(const raii::string_base& filename, const raii::string_base& alias){ video_info ret = {}; - raii::video_man context(filename); - if(!context) + libav::fmt::input_context in(filename); + int stream_index = -1; + for(size_t i = 0;i < in->nb_streams;++i){ + if(in->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){ + stream_index = i; + break; + } + } + if(stream_index == -1) return {}; - auto packet = context.create_jpg_thumbnail(); + + libavcodec::context src_ctx(in, stream_index); + src_ctx.open(); + libav::packet packet = create_jpeg_thumbnail(in, src_ctx, stream_index); + raii::curl_llist header("Content-Type: image/jpeg"); - ret.thumb_width = context.width(); - ret.thumb_height = context.height(); - ret.width = context.width(); - ret.height = context.height(); - ret.mimetype = context.get_mimetype(); - context.reset(); //early cleanup to save memory + ret.thumb_width = src_ctx->width; + ret.thumb_height = src_ctx->height; + ret.width = src_ctx->width; + ret.height = src_ctx->height; + ret.mimetype = get_mimetype(in); ret.thumbsize = packet->size; ret.thumburl = _post_and_find(raii::static_string((char*)packet->data, packet->size), m_urls.file_upload, header, "content_uri"_ss); + src_ctx.reset(); + in.reset(); packet.reset(); header = raii::curl_llist(raii::string("Content-Type:" + ret.mimetype)); @@ -251,11 +335,10 @@ namespace matrix{ audio_info bot::upload_audio(const raii::string_base& filename, const raii::string_base& alias){ audio_info ret = {}; - raii::video_man context(filename); - if(!context) - return {}; - ret.mimetype = context.get_mimetype(); - context.reset(); + libav::fmt::input_context in(filename); + + ret.mimetype = get_mimetype(in); + in.reset(); raii::curl_llist header(raii::string("Content-Type:" + ret.mimetype)); raii::filerd fd(filename); if(!fd) return {}; diff --git a/src/raii/video_man.cpp b/src/raii/video_man.cpp deleted file mode 100644 index ed12fff..0000000 --- a/src/raii/video_man.cpp +++ /dev/null @@ -1,193 +0,0 @@ -/** - This file is a part of rexy's matrix bot - Copyright (C) 2019 rexy712 - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -*/ - -#include "raii/video_man.hpp" -#include "raii/static_string.hpp" - -#include //exchange - -//av_register_all is required before ffmpeg 4.0 and is deprecated after that -#if LIBAVFORMAT_VERSION_MAJOR >= 58 && LIBAVFORMAT_VERSION_MINOR >= 7 -# define REGISTER_LIBAV() -#else -# define REGISTER_LIBAV() av_register_all() -#endif - -namespace raii{ - - video_man::video_man(const raii::string_base& filename){ - REGISTER_LIBAV(); - _init(filename); - } - video_man::video_man(video_man&& v): - m_vid(std::exchange(v.m_vid, nullptr)){} - video_man::~video_man(void){ - reset(); - } - - int video_man::height(void)const{ - return m_context->height; - } - int video_man::width(void)const{ - return m_context->width; - } - raii::string video_man::get_filetype(void)const{ - const char* first = strstr(m_vid->iformat->name, ","); - if(first) - return raii::string(raii::static_string(m_vid->iformat->name, first - m_vid->iformat->name)); - return raii::string(raii::static_string(m_vid->iformat->name)); - } - raii::string video_man::get_mimetype(void)const{ - const char* first = strstr(m_vid->iformat->name, ","); - if(first) - return raii::string("video/" + raii::static_string(m_vid->iformat->name, first - m_vid->iformat->name)); - return raii::string("video/" + raii::static_string(m_vid->iformat->name)); - } - auto video_man::create_jpg_thumbnail(void) -> packet_type{ - context_type jpg_context = _get_jpeg_context(m_context); - frame_type frame(_init_frame(m_context->width, m_context->height, m_context->pix_fmt), _frame_deleter); - frame_type jpg_frame(_init_frame(m_context->width, m_context->height, jpg_context->pix_fmt), _frame_deleter); - packet_type packet(av_packet_alloc(), _packet_deleter); - SwsContext* sws_ctx = sws_getContext(m_context->width, m_context->height, m_context->pix_fmt, - m_context->width, m_context->height, AV_PIX_FMT_YUV420P, - SWS_BILINEAR, NULL, NULL, NULL); - _change_color_range(sws_ctx); - - av_image_alloc(jpg_frame->data, jpg_frame->linesize, m_context->width, m_context->height, jpg_context->pix_fmt, 32); - while(av_read_frame(m_vid, packet.get()) >= 0){ - if(packet->stream_index == m_stream_index){ - avcodec_send_packet(m_context, packet.get()); - if(avcodec_receive_frame(m_context, frame.get()) >= 0) - break; - } - av_packet_unref(packet.get()); - } - sws_scale(sws_ctx, frame->data, frame->linesize, 0, m_context->height, jpg_frame->data, jpg_frame->linesize); - - sws_freeContext(sws_ctx); - - packet_type jpg_packet(av_packet_alloc(), _packet_deleter); - if(avcodec_send_frame(jpg_context.get(), jpg_frame.get()) < 0){ - av_freep(&jpg_frame->data[0]); - return packet_type(nullptr, _packet_deleter); - } - if(avcodec_receive_packet(jpg_context.get(), jpg_packet.get()) < 0){ - return packet_type(nullptr, _packet_deleter); - } - - av_freep(&jpg_frame->data[0]); - return jpg_packet; - } - - AVFormatContext* video_man::release(void){ - avcodec_close(m_context); - avcodec_free_context(&m_context); - m_context = nullptr; - m_codec = nullptr; - m_stream_index = -1; - return std::exchange(m_vid, nullptr); - } - void video_man::reset(const raii::string_base& filename){ - reset(); - _init(filename); - } - void video_man::reset(void){ - avcodec_close(m_context); - avcodec_free_context(&m_context); - avformat_close_input(&m_vid); - m_context = nullptr; - m_vid = nullptr; - m_codec = nullptr; - m_stream_index = -1; - } - video_man::operator bool(void)const{ - return m_vid; - } - void video_man::_init(const raii::string_base& filename){ - m_vid = _open_video_file(filename); - if(!m_vid) - return; - - AVCodecParameters* codec_parms = m_vid->streams[m_stream_index]->codecpar; - m_codec = avcodec_find_decoder(codec_parms->codec_id); - m_context = avcodec_alloc_context3(m_codec); - avcodec_parameters_to_context(m_context, codec_parms); - avcodec_open2(m_context, m_codec, NULL); - } - AVFormatContext* video_man::_open_video_file(const raii::string_base& filename){ - AVFormatContext* context = NULL; - avformat_open_input(&context, filename.get(), NULL, NULL); - avformat_find_stream_info(context, NULL); - for(size_t i = 0;i < context->nb_streams;++i){ - if(context->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){ - m_stream_index = i; - break; - } - } - if(m_stream_index != -1) - return context; - avformat_close_input(&context); - return nullptr; - } - AVFrame* video_man::_init_frame(int w, int h, int format){ - AVFrame* f = av_frame_alloc(); - f->data[0] = NULL; - f->width = w; - f->height = h; - f->format = format; - return f; - } - void video_man::_change_color_range(SwsContext* sws_ctx){ - //we want to convert from YUVJ420P to YUV420P for sws_scale to be happy. but we actually want to output as YUVJ420P. - //so we change the color range to use the full range like YUVJ420P. dummy is used to ignore unneeded returns. - int dummy[4]; - int src_range, dest_range; - int br, con, sat; - sws_getColorspaceDetails(sws_ctx, (int**)&dummy, &src_range, (int**)&dummy, &dest_range, &br, &con, &sat); - const int* coefs = sws_getCoefficients(SWS_CS_DEFAULT); - src_range = 2; - sws_setColorspaceDetails(sws_ctx, coefs, src_range, coefs, dest_range, br, con, sat); - } - auto video_man::_get_jpeg_context(const AVCodecContext* decoder) -> context_type{ - AVCodec* jpg_codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG); - context_type jpg_context(avcodec_alloc_context3(jpg_codec), _context_deleter); - jpg_context->pix_fmt = AV_PIX_FMT_YUVJ420P; - jpg_context->width = decoder->width; - jpg_context->height = decoder->height; - jpg_context->codec_id = AV_CODEC_ID_MJPEG; - jpg_context->codec_type = AVMEDIA_TYPE_VIDEO; - if(decoder->time_base.num == 0 || decoder->time_base.den == 0) - jpg_context->time_base = AVRational{1, 30}; - else - jpg_context->time_base = decoder->time_base; - avcodec_open2(jpg_context.get(), jpg_codec, NULL); - return jpg_context; - } - void video_man::_context_deleter(AVCodecContext* ptr){ - avcodec_close(ptr); - avcodec_free_context(&ptr); - } - void video_man::_frame_deleter(AVFrame* ptr){ - av_frame_unref(ptr); - av_frame_free(&ptr); - } - void video_man::_packet_deleter(AVPacket* ptr){ - av_packet_unref(ptr); - av_packet_free(&ptr); - } -}