-
Notifications
You must be signed in to change notification settings - Fork 45
/
png_saver.cpp
112 lines (92 loc) · 3.91 KB
/
png_saver.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#include "png_saver.h"
#include <fstream>
extern "C" {
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
}
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"
void PngSaver::save(const AVFrame* frame, const std::string& filename) {
try {
// use FFmpeg PNG encoder if available in this libavcodec build, as it is faster and generally produces smaller files than stb
save_with_ffmpeg(frame, filename);
} catch (const PngSaver::EncodingException& e) {
// fall back on stb implementation
save_with_stb(frame, filename);
}
}
void PngSaver::save_with_ffmpeg(const AVFrame* frame, const std::string& filename) {
const AVCodec* codec = avcodec_find_encoder(AV_CODEC_ID_PNG);
if (!codec) {
throw EncodingException("Codec not found");
}
const AVFrame* frame_to_save = frame;
// Convert AV_PIX_FMT_RGB48LE to AV_PIX_FMT_RGB48BE
AVFramePtr converted_frame(nullptr);
if (frame->format == AV_PIX_FMT_RGB48LE) {
converted_frame.reset(convert(frame, AV_PIX_FMT_RGB48BE));
frame_to_save = converted_frame.get();
}
AVCodecContextPtr codec_ctx(avcodec_alloc_context3(codec));
if (!codec_ctx) {
throw EncodingException("Could not allocate video codec context");
}
codec_ctx->width = frame_to_save->width;
codec_ctx->height = frame_to_save->height;
codec_ctx->pix_fmt = static_cast<enum AVPixelFormat>(frame_to_save->format);
codec_ctx->time_base = {1, 25};
if (avcodec_open2(codec_ctx.get(), codec, nullptr) < 0) {
throw EncodingException("Could not open codec");
}
AVPacketPtr packet(av_packet_alloc());
if (!packet) {
throw EncodingException("Could not allocate packet");
}
if (avcodec_send_frame(codec_ctx.get(), frame_to_save) < 0) {
throw EncodingException("Error sending a frame for encoding");
}
if (avcodec_receive_packet(codec_ctx.get(), packet.get()) < 0) {
throw EncodingException("Error during encoding");
}
try {
std::ofstream file(filename, std::ios::out | std::ios::binary);
if (!file.is_open()) {
throw IOException("Could not open file: " + filename);
}
file.write(reinterpret_cast<const char*>(packet->data), packet->size);
file.close();
} catch (const std::ios_base::failure& e) {
throw IOException("IO error while writing file " + filename + ": " + e.what());
}
}
void PngSaver::save_with_stb(const AVFrame* frame, const std::string& filename) {
if (frame->format == AV_PIX_FMT_RGB24) {
if (stbi_write_png(filename.c_str(), frame->width, frame->height, 3, frame->data[0], frame->linesize[0]) == 0) {
throw IOException("Error while writing PNG via stb: " + filename);
}
} else if (frame->format == AV_PIX_FMT_RGB48LE) {
if (stbi_write_png_16(filename.c_str(), frame->width, frame->height, 3, frame->data[0], frame->linesize[0]) == 0) {
throw IOException("Error while writing PNG via stb: " + filename);
}
} else {
throw EncodingException("Pixel format not supported by stb");
}
}
AVFrame* PngSaver::convert(const AVFrame* frame, const AVPixelFormat output_format) {
struct SwsContext* sws_ctx = sws_getContext(frame->width, frame->height, static_cast<AVPixelFormat>(frame->format), frame->width, frame->height, output_format, SWS_BILINEAR, nullptr, nullptr, nullptr);
if (!sws_ctx) {
throw EncodingException("Could not initialize the conversion context");
}
AVFrame* converted_frame = av_frame_alloc();
if (!converted_frame) {
sws_freeContext(sws_ctx);
throw EncodingException("Could not allocate converted frame");
}
converted_frame->format = output_format;
converted_frame->width = frame->width;
converted_frame->height = frame->height;
av_image_alloc(converted_frame->data, converted_frame->linesize, converted_frame->width, converted_frame->height, output_format, 32);
sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, converted_frame->data, converted_frame->linesize);
sws_freeContext(sws_ctx);
return converted_frame;
}