@@ -15,9 +15,29 @@ extern "C" {
1515namespace candlewick {
1616namespace 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)
0 commit comments