33#include " helper/c_helpers.hpp"
44#include " helper/constants.hpp"
55#include " helper/git_helper.hpp"
6+ #include " helper/graphic_utils.hpp"
67#include " video_renderer.hpp"
78
89extern " C" {
910#include < libavcodec/avcodec.h>
1011#include < libavformat/avformat.h>
1112#include < libavutil/avutil.h>
1213#include < libavutil/log.h>
14+ #include < libswscale/swscale.h>
1315}
1416
1517#include < csignal>
18+ #include < future>
1619#include < sys/wait.h>
1720#include < unistd.h>
1821
1922struct Decoder {
2023 int pipe;
21- pid_t pid;
24+ std::future<std::optional<std::string>> encoding_thread;
25+ std::atomic<bool > should_cancel;
2226};
2327
2428// general information and usage from: https://friendlyuser.github.io/posts/tech/cpp/Using_FFmpeg_in_C++_A_Comprehensive_Guide/
@@ -51,8 +55,13 @@ namespace {
5155 return result;
5256 }
5357
54- std::optional<std::string>
55- start_encoding (u32 fps, shapes::UPoint size, const std::filesystem::path& destination_path) {
58+ std::optional<std::string> start_encoding (
59+ u32 fps,
60+ shapes::UPoint size,
61+ const std::filesystem::path& destination_path,
62+ int input_fd,
63+ const std::unique_ptr<Decoder>& decoder
64+ ) {
5665
5766 ScopeDeferMultiple<void , void *> scope_defer{};
5867
@@ -66,8 +75,6 @@ namespace {
6675 return fmt::format (" Cannot allocate an input format context" );
6776 }
6877
69- scope_defer.add ([input_format_ctx](void *) { avformat_free_context (input_format_ctx); }, nullptr );
70-
7178 const std::string resolution = fmt::format (" {}x{}" , size.x , size.y );
7279
7380 const std::string framerate = fmt::format (" {}" , fps);
@@ -87,8 +94,10 @@ namespace {
8794 // "-r {framerate}"
8895 av_dict_set (&input_options, " framerate" , framerate.c_str (), 0 );
8996
90- // "-i -"
91- auto av_input_ret = avformat_open_input (&input_format_ctx, " fd:" , input_fmt, &input_options);
97+ std::string input_url = fmt::format (" pipe:{}" , input_fd);
98+
99+ // "-i pipe:{fd}"
100+ auto av_input_ret = avformat_open_input (&input_format_ctx, input_url.c_str (), input_fmt, &input_options);
92101 if (av_input_ret != 0 ) {
93102 return fmt::format (" Could not open input file stdin: {}" , av_error_to_string (av_input_ret));
94103 }
@@ -108,7 +117,7 @@ namespace {
108117 }
109118
110119
111- /* select the video stream */
120+ // select the video stream
112121 const AVCodec* input_decoder = nullptr ;
113122 auto video_stream_index = av_find_best_stream (input_format_ctx, AVMEDIA_TYPE_VIDEO, -1 , -1 , &input_decoder, 0 );
114123 if (video_stream_index < 0 ) {
@@ -129,23 +138,23 @@ namespace {
129138 auto codec_paramaters_ret = avcodec_parameters_to_context (input_codec_context, input_video_stream->codecpar );
130139 if (codec_paramaters_ret < 0 ) {
131140 return fmt::format (
132- " Cannot set the input codec context paramaters : {}" , av_error_to_string (codec_paramaters_ret)
141+ " Cannot set the input codec context parameters : {}" , av_error_to_string (codec_paramaters_ret)
133142 );
134143 }
135144
136145 /* Inform the decoder about the timebase for the packet timestamps.
137146 * This is highly recommended, but not mandatory. */
138147 input_codec_context->pkt_timebase = input_video_stream->time_base ;
139148
140- // NOTE: we also could set this to the provided u32 , but this also uses that and converts it to the expected format
149+ // NOTE: we also could set this to the provided u32, but this also uses that and converts it to the expected format (fractional)
141150 input_codec_context->framerate = av_guess_frame_rate (input_format_ctx, input_video_stream, nullptr );
142151
143152 auto codec_open_ret = avcodec_open2 (input_codec_context, input_decoder, nullptr );
144153 if (codec_open_ret != 0 ) {
145154 return fmt::format (" Cannot initializer the codec for the input: {}" , av_error_to_string (codec_open_ret));
146155 }
147156
148- av_dump_format (input_format_ctx, 0 , " fd: " , 0 );
157+ av_dump_format (input_format_ctx, 0 , input_url. c_str () , 0 );
149158
150159 // output setup
151160
@@ -192,8 +201,9 @@ namespace {
192201 output_codec_context->height = input_codec_context->height ;
193202 output_codec_context->width = input_codec_context->width ;
194203 output_codec_context->sample_aspect_ratio = input_codec_context->sample_aspect_ratio ;
204+ output_codec_context->framerate = input_codec_context->framerate ;
195205
196- /* video time_base can be set to whatever is handy and supported by encoder */
206+ // video time_base can be set to whatever is handy and supported by encoder
197207 output_codec_context->time_base = av_inv_q (input_codec_context->framerate );
198208
199209 AVDictionary* output_options = nullptr ;
@@ -202,6 +212,9 @@ namespace {
202212 // "-crf 20"
203213 av_dict_set (&output_options, " crf" , " 20" , 0 );
204214
215+ av_dict_set (&output_options, " video_size" , resolution.c_str (), 0 );
216+
217+
205218 auto codec_open_out_ret = avcodec_open2 (output_codec_context, output_encoder, &output_options);
206219 if (codec_open_out_ret != 0 ) {
207220 return fmt::format (
@@ -260,47 +273,143 @@ namespace {
260273
261274 scope_defer.add ([&pkt](void *) { av_packet_free (&pkt); }, nullptr );
262275
276+ AVFrame* decode_frame = av_frame_alloc ();
277+
278+ if (decode_frame == nullptr ) {
279+ return " Could not allocate decode AVFrame" ;
280+ }
281+
282+ scope_defer.add ([&decode_frame](void *) { av_frame_free (&decode_frame); }, nullptr );
283+
284+
285+ decode_frame->format = input_codec_context->pix_fmt ;
286+ decode_frame->width = input_codec_context->width ;
287+ decode_frame->height = input_codec_context->height ;
288+
289+ auto frame_buffer_ret = av_frame_get_buffer (decode_frame, 0 );
290+ if (frame_buffer_ret < 0 ) {
291+ return fmt::format (" Could not allocate decode frame buffer: {}" , av_error_to_string (frame_buffer_ret));
292+ }
293+
294+ AVFrame* encode_frame = av_frame_alloc ();
295+
296+ if (encode_frame == nullptr ) {
297+ return " Could not allocate encode AVFrame" ;
298+ }
299+
300+ scope_defer.add ([&encode_frame](void *) { av_frame_free (&encode_frame); }, nullptr );
301+
302+
303+ encode_frame->format = output_codec_context->pix_fmt ;
304+ encode_frame->width = output_codec_context->width ;
305+ encode_frame->height = output_codec_context->height ;
306+
307+ auto outp_frame_buffer_ret = av_frame_get_buffer (encode_frame, 0 );
308+ if (outp_frame_buffer_ret < 0 ) {
309+ return fmt::format (" Could not allocate encode frame buffer: {}" , av_error_to_string (outp_frame_buffer_ret));
310+ }
311+
312+ // allocate conversion context (for frame conversion)
313+ SwsContext* sws_ctx = sws_getContext (
314+ input_codec_context->width , input_codec_context->height , input_codec_context->pix_fmt ,
315+ output_codec_context->width , output_codec_context->height , output_codec_context->pix_fmt , SWS_BICUBIC,
316+ nullptr , nullptr , nullptr
317+ );
318+ if (sws_ctx == nullptr ) {
319+ return " Could not allocate conversion context" ;
320+ }
321+
263322 while (true ) {
264- auto read_ret = av_read_frame (input_format_ctx, pkt);
265- if (read_ret < 0 )
266- break ;
323+ // check atomic bool, if we are cancelled
324+ // NOTE: the video is garbage after this, since we don't close it correctly (which isn't the intention of this)
325+ if (decoder->should_cancel ) {
326+ return std::nullopt ;
327+ }
267328
329+ // retrieve unencoded (raw) packet from input
330+ auto read_frame_ret = av_read_frame (input_format_ctx, pkt);
331+ if (read_frame_ret == AVERROR_EOF) {
332+ break ;
333+ } else if (read_frame_ret < 0 ) {
334+ return fmt::format (" Receiving a frame from the input failed: {}" , av_error_to_string (read_frame_ret));
335+ }
268336
269- auto send_pkt_ret = avcodec_send_packet (output_codec_context, pkt);
270- if (send_pkt_ret < 0 ) {
337+ // send raw packet in packet to decoder
338+ auto send_pkt_ret = avcodec_send_packet (input_codec_context, pkt);
339+ if (send_pkt_ret != 0 ) {
340+ if (send_pkt_ret == AVERROR (EAGAIN)) {
341+ return " Decoding failed: Output was not read correctly" ;
342+ }
271343 return fmt::format (" Decoding failed: {}" , av_error_to_string (send_pkt_ret));
272344 }
273345
274- int write_ret = 0 ;
346+ int read_ret = 0 ;
275347
276- while (write_ret >= 0 ) {
277- write_ret = avcodec_receive_packet (output_codec_context, pkt);
278- if (write_ret == AVERROR (EAGAIN) || write_ret == AVERROR_EOF) {
348+ // encode and write as much frames as possible
349+ while (read_ret >= 0 ) {
350+
351+ // get decoded frame, if one is present
352+ read_ret = avcodec_receive_frame (input_codec_context, decode_frame);
353+ if (read_ret == AVERROR (EAGAIN) || read_ret == AVERROR_EOF) {
279354 break ;
355+ } else if (read_ret < 0 ) {
356+ return fmt::format (" Receiving a frame from the decoder failed: {}" , av_error_to_string (read_ret));
357+ }
280358
281- } else if (write_ret < 0 ) {
282- return fmt::format (" Encoding a frame failed: {}" , av_error_to_string (write_ret));
359+ // convert to correct output pixel format
360+ read_ret = sws_scale_frame (sws_ctx, encode_frame, decode_frame);
361+ if (read_ret < 0 ) {
362+ return fmt::format (" Frame conversion failed: {}" , av_error_to_string (read_ret));
283363 }
284364
285- /* rescale output packet timestamp values from codec to stream timebase */
286- av_packet_rescale_ts (pkt, output_codec_context->time_base , out_stream->time_base );
287- pkt->stream_index = out_stream->index ;
365+ // copy the pts from the decoded frame
366+ encode_frame->pts = decode_frame->pts ;
288367
289- /* Write the compressed frame to the media file. */
290- write_ret = av_interleaved_write_frame (output_format_ctx, pkt);
291- /* pkt is now blank (av_interleaved_write_frame() takes ownership of
368+ // encode decoded and converted frame with output encoder
369+ read_ret = avcodec_send_frame (output_codec_context, encode_frame);
370+ if (read_ret != 0 ) {
371+ return fmt::format (" Encoding failed: {}" , av_error_to_string (read_ret));
372+ }
373+
374+ int write_ret = 0 ;
375+
376+ // write all encoded packets
377+ while (write_ret >= 0 ) {
378+
379+ // get encoded packet, if one is present
380+ write_ret = avcodec_receive_packet (output_codec_context, pkt);
381+ if (write_ret == AVERROR (EAGAIN) || write_ret == AVERROR_EOF) {
382+ break ;
383+ } else if (write_ret < 0 ) {
384+ return fmt::format (
385+ " Receiving a packet from the encoder failed: {}" , av_error_to_string (write_ret)
386+ );
387+ }
388+
389+ // prepare packet for muxing
390+ pkt->stream_index = out_stream->index ;
391+
392+ // rescale output packet timestamp values from codec to stream timebase
393+ av_packet_rescale_ts (pkt, output_codec_context->time_base , out_stream->time_base );
394+
395+
396+ // Write the compressed packet (frame inside that) to the media file.
397+ write_ret = av_interleaved_write_frame (output_format_ctx, pkt);
398+ /* pkt is now blank (av_interleaved_write_frame() takes ownership of
292399 * its contents and resets pkt), so that no unreferencing is necessary.
293400 * This would be different if one used av_write_frame(). */
294- if (write_ret < 0 ) {
295- return fmt::format (" Writing an output packet failed: {}" , av_error_to_string (write_ret));
401+ if (write_ret < 0 ) {
402+ return fmt::format (" Writing an output packet failed: {}" , av_error_to_string (write_ret));
403+ }
296404 }
297405 }
298-
299- // reset the packe, so that it's ready for the next cycle
300- av_packet_unref (pkt);
301406 }
302407
303- av_write_trailer (output_format_ctx);
408+
409+ auto trailer_ret = av_write_trailer (output_format_ctx);
410+ if (trailer_ret != 0 ) {
411+ return fmt::format (" Writing the trailer failed: {}" , av_error_to_string (trailer_ret));
412+ }
304413
305414 return std::nullopt ;
306415 }
@@ -317,79 +426,53 @@ std::optional<std::string> VideoRendererBackend::setup(u32 fps, shapes::UPoint s
317426 std::array<int , 2 > pipefd = { 0 , 0 };
318427
319428 if (pipe (pipefd.data ()) < 0 ) {
320- return fmt::format (" FFMPEG: Could not create a pipe: {}" , strerror (errno));
429+ return fmt::format (" Could not create a pipe: {}" , strerror (errno));
321430 }
322431
323- pid_t child = fork ();
324- if (child < 0 ) {
325- return fmt::format ( " FFMPEG: could not fork a child: {} " , strerror (errno) );
326- }
432+ std::future<std::optional<std::string>> encoding_thread =
433+ std::async (std::launch::async, [pipefd, fps, size, this ] -> std::optional<std::string> {
434+ utils::set_thread_name ( " ffmpeg encoder " );
435+ auto result = start_encoding (fps, size, this -> m_destination_path , pipefd[READ_END], this -> m_decoder );
327436
328- if (child == 0 ) {
329- if (dup2 (pipefd.at (READ_END), STDIN_FILENO) < 0 ) {
330- std::cerr << " FFMPEG CHILD: could not reopen read end of pipe as stdin: " << strerror (errno) << " \n " ;
331- std::exit (1 );
332- }
333- close (pipefd[WRITE_END]);
334-
335- auto result = start_encoding (fps, size, m_destination_path);
437+ if (close (pipefd[READ_END]) < 0 ) {
438+ spdlog::warn (" could not close read end of the pipe: {}" , strerror (errno));
439+ }
336440
337- if (result.has_value ()) {
338- std::cerr << " FFMPEG CHILD: could not run embedded ffmpeg as a child process: " << result.value () << " \n " ;
339- std::exit (1 );
340- }
441+ if (result.has_value ()) {
442+ return fmt::format (" ffmpeg error: {}" , result.value ());
443+ }
341444
342- std::exit ( 0 ) ;
343- }
445+ return std::nullopt ;
446+ });
344447
345- if (close (pipefd[READ_END]) < 0 ) {
346- spdlog::error (" FFMPEG: could not close read end of the pipe on the parent's end: {}" , strerror (errno));
347- }
348448
349- m_decoder = std::make_unique<Decoder>(pipefd[WRITE_END], child );
449+ m_decoder = std::make_unique<Decoder>(pipefd[WRITE_END], std::move (encoding_thread), false );
350450 return std::nullopt ;
351451}
352452
353453bool VideoRendererBackend::add_frame (SDL_Surface* surface) {
354454 if (write (m_decoder->pipe , surface->pixels , static_cast <size_t >(surface->h ) * surface->pitch ) < 0 ) {
355- spdlog::error (" FFMPEG: failed to write into ffmpeg pipe: {}" , strerror (errno));
455+ spdlog::error (" failed to write into ffmpeg pipe: {}" , strerror (errno));
356456 return false ;
357457 }
358458 return true ;
359459}
360460
361461bool VideoRendererBackend::finish (bool cancel) {
362462
463+ if (cancel) {
464+ m_decoder->should_cancel = true ;
465+ }
363466
364467 if (close (m_decoder->pipe ) < 0 ) {
365- spdlog::warn (" FFMPEG: could not close write end of the pipe on the parent's end : {}" , strerror (errno));
468+ spdlog::warn (" could not close write end of the pipe: {}" , strerror (errno));
366469 }
367470
368- if (cancel)
369- kill (m_decoder->pid , SIGKILL);
370-
371- while (true ) {
372- int wstatus = 0 ;
373- if (waitpid (m_decoder->pid , &wstatus, 0 ) < 0 ) {
374- spdlog::error (" FFMPEG: could not wait for ffmpeg child process to finish: {}" , strerror (errno));
375- return false ;
376- }
377-
378- if (WIFEXITED (wstatus)) {
379- int exit_status = WEXITSTATUS (wstatus);
380- if (exit_status != 0 ) {
381- spdlog::error (" FFMPEG: ffmpeg exited with code {}" , exit_status);
382- return false ;
383- }
384-
385- return true ;
386- }
387-
388- if (WIFSIGNALED (wstatus)) {
389- spdlog::error (" FFMPEG: ffmpeg got terminated by {}" , strsignal (WTERMSIG (wstatus)));
390- return false ;
391- }
471+ m_decoder->encoding_thread .wait ();
472+ auto result = m_decoder->encoding_thread .get ();
473+ if (result.has_value ()) {
474+ spdlog::error (" FFMPEG error: {}" , result.value ());
475+ return false ;
392476 }
393-
394- UNREACHABLE ();
477+ return true ;
395478}
0 commit comments