Making mosaic images with ffmpeg
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

126 lines
3.8 KiB

#include "av.hpp"
extern "C" {
#include <libavutil/frame.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
}
#include <iostream>
#include <string>
#include <cassert>
// Was not yet defined in <string> :(
static std::string operator "" s(const char *str, std::size_t len){
return {str, len};
}
namespace av {
format_context format_open_input(const std::string& filename, AVInputFormat* format, AVDictionary** options){
AVFormatContext * ctx = nullptr;
avformat_open_input(&ctx, filename.c_str(), format, options);
if(!ctx) throw error("Unable to open input " + filename);
// This might be optional, but AFAIK people always do this after opening input
avformat_find_stream_info(ctx, options);
return {ctx, [](auto x){ avformat_close_input(&x); }};
}
format_context format_alloc_context(){
auto ptr = avformat_alloc_context();
if(!ptr) throw error("Could not allocate AVFormatContext");
return {avformat_alloc_context(), &avformat_free_context};
}
codec find_encoder(AVCodecID codec_id){
auto ptr = avcodec_find_encoder(codec_id);
if(!ptr) throw error("Could not find codec");
return {ptr, nullptr};
}
codec_context context_alloc(codec const & codec){
auto ptr = avcodec_alloc_context3(codec.get());
if(!ptr) throw av::error("Could not allocate codec context");
return {ptr, [](auto x){ avcodec_free_context(&x); }};
}
codec_context context_from_stream(format_context const & ctx, size_t i){
return {ctx->streams[i]->codec, nullptr};
}
open_guard codec_open(codec_context & ctx, codec const & codec, AVDictionary** options){
if(!ctx) throw error("Invalid codec context");
if(!codec) throw error("Invalid codec");
if(avcodec_open2(ctx.get(), codec.get(), options) < 0) throw error("Could not open codec");
return {ctx.get(), [](auto x){ avcodec_close(x); }};
}
packet read_frame(format_context& ctx, packet_buffer& p){
auto ret = av_read_frame(ctx.get(), &p);
if(ret < 0){
return {nullptr, &av_free_packet};
} else {
return {&p, &av_free_packet};
}
}
frame frame_alloc() {
auto ptr = av_frame_alloc();
if(!ptr) throw error("Could not allocate AVFrame");
return {ptr, [](auto x){ av_frame_free(&x); }};
}
frame frame_clone(const frame& f){
auto ptr = av_frame_clone(f.get());
if(!ptr) throw error("Could not clone AVFrame");
return {ptr, [](auto x){ av_frame_free(&x); }};
}
AVPixelFormat get_format(const frame& f){
return static_cast<AVPixelFormat>(f->format);
}
void crop(frame& f, int left, int top, int width, int height){
f = crop(std::move(f), left, top, width, height);
}
frame crop(frame&& f, int left, int top, int width, int height){
// if(left + width > f->width || top + height > f->height) throw error("Crop sizes do not match");
auto ptr = reinterpret_cast<AVPicture*>(f.get());
auto ret = av_picture_crop(ptr, ptr, av::get_format(f), top, left);
if(ret < 0) throw error("Could not crop");
f->width = width;
f->height = height;
return std::move(f);
}
frame crop(const frame& f, int left, int top, int width, int height){
return crop(frame_clone(f), left, top, width, height);
}
bool codec_decode_video(codec_context& ctx, frame& f, const packet& pkt){
int finished = 0;
int ret = avcodec_decode_video2(ctx.get(), f.get(), &finished, pkt.get());
if (ret < 0) {
throw std::runtime_error("Error ["s + std::to_string(ret) + "] while decoding frame: "s + strerror(AVERROR(ret)));
}
return finished;
}
void codec_decode_video_remaining(codec_context& ctx, frame& f){
AVPacket pkt{0, 0, 0, 0, 0}; // data and size should be 0
int finished = 0;
int ret = avcodec_decode_video2(ctx.get(), f.get(), &finished, &pkt);
if (ret < 0) {
throw std::runtime_error("Error ["s + std::to_string(ret) + "] while decoding remaining frame: "s + strerror(AVERROR(ret)));
}
}
}