Skip to content

Commit 78efc93

Browse files
committed
utils : VideoRecorder : make tmpFrame and swsContext persistent
1 parent 87e451d commit 78efc93

File tree

2 files changed

+85
-43
lines changed

2 files changed

+85
-43
lines changed

src/candlewick/utils/VideoRecorder.cpp

Lines changed: 75 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,29 @@ extern "C" {
1515
namespace candlewick {
1616
namespace media {
1717

18+
static AVFrame *allocate_frame(AVPixelFormat pix_fmt, int width, int height) {
19+
AVFrame *frame;
20+
int ret;
21+
22+
frame = av_frame_alloc();
23+
if (!frame)
24+
return nullptr;
25+
26+
frame->format = pix_fmt;
27+
frame->width = width;
28+
frame->height = height;
29+
30+
ret = av_frame_get_buffer(frame, 0);
31+
if (ret < 0)
32+
terminate_with_message("Failed to allocate frame data: %s",
33+
av_err2str(ret));
34+
35+
return frame;
36+
}
37+
1838
struct VideoRecorderImpl {
19-
Uint32 m_width; //< Width of incoming frames
20-
Uint32 m_height; //< Height of incoming frames
39+
int m_width; //< Width of incoming frames
40+
int m_height; //< Height of incoming frames
2141
Uint32 m_frameCounter; //< Number of recorded frames
2242

2343
AVFormatContext *formatContext = nullptr;
@@ -26,9 +46,10 @@ namespace media {
2646
AVStream *videoStream = nullptr;
2747
SwsContext *swsContext = nullptr;
2848
AVFrame *frame = nullptr;
49+
AVFrame *tmpFrame = nullptr;
2950
AVPacket *packet = nullptr;
3051

31-
VideoRecorderImpl(Uint32 width, Uint32 height, const std::string &filename,
52+
VideoRecorderImpl(int width, int height, const std::string &filename,
3253
VideoRecorder::Settings settings);
3354

3455
VideoRecorderImpl(const VideoRecorderImpl &) = delete;
@@ -39,6 +60,20 @@ namespace media {
3960
void close() noexcept;
4061

4162
~VideoRecorderImpl() noexcept { this->close(); }
63+
64+
// delayed initialization, given actual input specs
65+
void lazyInit(AVPixelFormat inputFormat) {
66+
tmpFrame = allocate_frame(inputFormat, m_width, m_height);
67+
if (!tmpFrame)
68+
terminate_with_message("Failed to allocate temporary video frame.");
69+
70+
swsContext =
71+
sws_getContext(tmpFrame->width, tmpFrame->height, inputFormat,
72+
frame->width, frame->height, codecContext->pix_fmt,
73+
SWS_BILINEAR, nullptr, nullptr, nullptr);
74+
if (!swsContext)
75+
terminate_with_message("Failed to create SwsContext.");
76+
}
4277
};
4378

4479
void VideoRecorderImpl::close() noexcept {
@@ -49,21 +84,30 @@ namespace media {
4984

5085
// close out stream
5186
av_frame_free(&frame);
52-
// av_frame_free(&tmpFrame);
87+
av_frame_free(&tmpFrame);
5388
av_packet_free(&packet);
5489
avcodec_free_context(&codecContext);
5590

5691
avio_closep(&formatContext->pb);
5792
avformat_free_context(formatContext);
93+
sws_freeContext(swsContext);
5894
formatContext = nullptr;
5995
}
6096

61-
VideoRecorderImpl::VideoRecorderImpl(Uint32 width, Uint32 height,
97+
VideoRecorderImpl::VideoRecorderImpl(int width, int height,
6298
const std::string &filename,
6399
VideoRecorder::Settings settings)
64100
: m_width(width), m_height(height) {
65-
avformat_network_init();
101+
102+
if (settings.outputWidth == 0)
103+
settings.outputWidth = width;
104+
if (settings.outputHeight == 0)
105+
settings.outputHeight = height;
106+
66107
codec = avcodec_find_encoder(AV_CODEC_ID_H264);
108+
if (!codec) {
109+
terminate_with_message("Failed to find encoder for codec H264");
110+
}
67111

68112
int ret = avformat_alloc_output_context2(&formatContext, nullptr, nullptr,
69113
filename.c_str());
@@ -84,6 +128,8 @@ namespace media {
84128

85129
codecContext->width = settings.outputWidth;
86130
codecContext->height = settings.outputHeight;
131+
// use YUV420P as it is the most widely supported format
132+
// for H.264
87133
codecContext->pix_fmt = AV_PIX_FMT_YUV420P;
88134
codecContext->time_base = AVRational{1, settings.fps};
89135
codecContext->framerate = AVRational{settings.fps, 1};
@@ -120,51 +166,41 @@ namespace media {
120166
terminate_with_message("Failed to allocate AVPacket");
121167

122168
// principal frame
123-
frame = av_frame_alloc();
169+
frame = allocate_frame(codecContext->pix_fmt, codecContext->width,
170+
codecContext->height);
124171
if (!frame)
125-
terminate_with_message("Failed to allocate video frame.");
126-
frame->format = codecContext->pix_fmt;
127-
frame->width = codecContext->width;
128-
frame->height = codecContext->height;
129-
130-
ret = av_frame_get_buffer(frame, 0);
131-
if (ret < 0) {
132-
av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
133-
terminate_with_message("Failed to allocate frame data: %s", errbuf);
134-
}
172+
terminate_with_message("Failed to allocate frame.");
135173
}
136174

137175
void VideoRecorderImpl::writeFrame(const Uint8 *data, Uint32 payloadSize,
138176
AVPixelFormat avPixelFormat) {
139-
AVFrame *tmpFrame = av_frame_alloc();
140-
tmpFrame->format = avPixelFormat;
141-
tmpFrame->width = int(m_width);
142-
tmpFrame->height = int(m_height);
143-
144-
int ret = av_frame_get_buffer(tmpFrame, 0);
145-
char errbuf[AV_ERROR_MAX_STRING_SIZE]{0};
146-
if (ret < 0) {
147-
av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
148-
throw std::runtime_error(
149-
std::format("Failed to allocate frame: {:s}", errbuf));
177+
assert(frame);
178+
int ret;
179+
if (!tmpFrame) {
180+
lazyInit(avPixelFormat);
150181
}
151182

152-
memcpy(tmpFrame->data[0], data, payloadSize);
183+
ret = av_frame_make_writable(tmpFrame);
184+
if (ret < 0)
185+
terminate_with_message("Failed to make tmpFrame writable: %s",
186+
av_err2str(ret));
153187

154-
swsContext =
155-
sws_getContext(tmpFrame->width, tmpFrame->height, avPixelFormat,
156-
frame->width, frame->height, codecContext->pix_fmt,
157-
SWS_BILINEAR, nullptr, nullptr, nullptr);
188+
// copy input payload to tmp frame
189+
memcpy(tmpFrame->data[0], data, payloadSize);
158190

191+
// ensure frame writable
192+
ret = av_frame_make_writable(frame);
193+
if (ret < 0) {
194+
terminate_with_message("Failed to make frame writable: %s",
195+
av_err2str(ret));
196+
}
159197
frame->pts = m_frameCounter++;
160198

161199
sws_scale(swsContext, tmpFrame->data, tmpFrame->linesize, 0, m_height,
162200
frame->data, frame->linesize);
163201

164202
ret = avcodec_send_frame(codecContext, frame);
165203
if (ret < 0) {
166-
av_frame_free(&tmpFrame);
167-
sws_freeContext(swsContext);
168204
terminate_with_message("Error sending frame %s", av_err2str(ret));
169205
}
170206

@@ -174,8 +210,6 @@ namespace media {
174210
break;
175211
}
176212
if (ret < 0) {
177-
av_frame_free(&tmpFrame);
178-
sws_freeContext(swsContext);
179213
terminate_with_message("Error receiving packet from encoder: %s",
180214
av_err2str(ret));
181215
}
@@ -186,9 +220,6 @@ namespace media {
186220
av_interleaved_write_frame(formatContext, packet);
187221
av_packet_unref(packet);
188222
}
189-
190-
av_frame_free(&tmpFrame);
191-
sws_freeContext(swsContext);
192223
}
193224

194225
// WRAPPING CLASS
@@ -197,9 +228,10 @@ namespace media {
197228
VideoRecorder &VideoRecorder::operator=(VideoRecorder &&) noexcept = default;
198229

199230
VideoRecorder::VideoRecorder(Uint32 width, Uint32 height,
200-
const std::string &filename, Settings settings)
201-
: impl_(std::make_unique<VideoRecorderImpl>(width, height, filename,
202-
settings)) {}
231+
const std::string &filename, Settings settings) {
232+
impl_ = std::make_unique<VideoRecorderImpl>(int(width), int(height),
233+
filename, settings);
234+
}
203235

204236
VideoRecorder::VideoRecorder(Uint32 width, Uint32 height,
205237
const std::string &filename)

src/candlewick/utils/VideoRecorder.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ namespace media {
3333
VideoRecorder &operator=(VideoRecorder &&) noexcept;
3434

3535
bool initialized() const { return impl_ != nullptr; }
36+
37+
/// \brief Constructor for the video recorder.
38+
///
39+
/// \param width Input data width.
40+
/// \param height Input data height.
41+
/// \param settings Video recording settings (fps, bitrate, output file
42+
/// width and height).
43+
///
44+
/// \note If the settings' output dimensions are not set, they will
45+
/// automatically be set to be the input's dimensions.
3646
VideoRecorder(Uint32 width, Uint32 height, const std::string &filename,
3747
Settings settings);
3848

0 commit comments

Comments
 (0)