]> git.sesse.net Git - nageru/blobdiff - nageru/ffmpeg_capture.cpp
Use AVFrame::duration instead of AVFrame::pkt_duration.
[nageru] / nageru / ffmpeg_capture.cpp
index e099d18a34a5073ba1f8bc1b07158d40f778f720..4b9d477648cdbc8ee7e3efedfb79e112e972a2b3 100644 (file)
@@ -44,14 +44,17 @@ extern "C" {
 #include <srt/srt.h>
 #endif
 
-#define FRAME_SIZE (8 << 20)  // 8 MB.
-
 using namespace std;
 using namespace std::chrono;
 using namespace bmusb;
 using namespace movit;
 using namespace Eigen;
 
+// Avoid deprecation warnings, but we don't want to drop FFmpeg 5.1 support just yet.
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 30, 100)
+#define pkt_duration duration
+#endif
+
 namespace {
 
 steady_clock::time_point compute_frame_start(int64_t frame_pts, int64_t pts_origin, const AVRational &video_timebase, const steady_clock::time_point &origin, double rate)
@@ -125,8 +128,8 @@ AVPixelFormat decide_dst_format(AVPixelFormat src_format, bmusb::PixelFormat dst
                if (desc->comp[0].depth != 8) continue;
 
                // Same or better chroma resolution only.
-               int chroma_w_diff = desc->log2_chroma_w - src_desc->log2_chroma_w;
-               int chroma_h_diff = desc->log2_chroma_h - src_desc->log2_chroma_h;
+               int chroma_w_diff = src_desc->log2_chroma_w - desc->log2_chroma_w;
+               int chroma_h_diff = src_desc->log2_chroma_h - desc->log2_chroma_h;
                if (chroma_w_diff < 0 || chroma_h_diff < 0)
                        continue;
 
@@ -503,7 +506,15 @@ AVPixelFormat get_hw_format(AVCodecContext *ctx, const AVPixelFormat *fmt)
                fprintf(stderr, "Decoder '%s' does not support device type '%s'.\n", ctx->codec->name, av_hwdevice_get_type_name(type));
        }
 
-       // We found no VA-API formats, so take the best software format.
+       // We found no VA-API formats, so take the first software format.
+       for (const AVPixelFormat *fmt_ptr = fmt; *fmt_ptr != -1; ++fmt_ptr) {
+               if ((av_pix_fmt_desc_get(*fmt_ptr)->flags & AV_PIX_FMT_FLAG_HWACCEL) == 0) {
+                       fprintf(stderr, "Falling back to software format %s.\n", av_get_pix_fmt_name(*fmt_ptr));
+                       return *fmt_ptr;
+               }
+       }
+
+       // Fallback: Just return anything. (Should never really happen.)
        return fmt[0];
 }
 
@@ -525,10 +536,13 @@ bool FFmpegCapture::play_video(const string &pathname)
 
        AVFormatContextWithCloser format_ctx;
        if (srt_sock == -1) {
-               // Regular file.
+               // Regular file (or stream).
+               frame_timeout_started = steady_clock::now();
+               frame_timeout_valid = true;
                format_ctx = avformat_open_input_unique(pathname.c_str(), /*fmt=*/nullptr,
                        /*options=*/nullptr,
                        AVIOInterruptCB{ &FFmpegCapture::interrupt_cb_thunk, this });
+               frame_timeout_valid = false;
        } else {
 #ifdef HAVE_SRT
                // SRT socket, already opened.
@@ -647,11 +661,18 @@ bool FFmpegCapture::play_video(const string &pathname)
 
                int64_t audio_pts;
                bool error;
+               frame_timeout_started = steady_clock::now();
+               frame_timeout_valid = true;
                AVFrameWithDeleter frame = decode_frame(format_ctx.get(), video_codec_ctx.get(), audio_codec_ctx.get(),
                        pathname, video_stream_index, audio_stream_index, subtitle_stream_index, audio_frame.get(), &audio_format, &audio_pts, &error);
+               frame_timeout_valid = false;
+               if (should_interrupt.load()) {
+                       // Abort no matter whether we got a frame or not.
+                       return false;
+               }
                if (error) {
                        if (++consecutive_errors >= 100) {
-                               fprintf(stderr, "More than 100 consecutive video frames, aborting playback.\n");
+                               fprintf(stderr, "More than 100 consecutive error video frames, aborting playback.\n");
                                return false;
                        } else {
                                continue;
@@ -885,34 +906,31 @@ AVFrameWithDeleter FFmpegCapture::decode_frame(AVFormatContext *format_ctx, AVCo
        *audio_pts = -1;
        bool has_audio = false;
        do {
-               AVPacket pkt;
-               unique_ptr<AVPacket, decltype(av_packet_unref)*> pkt_cleanup(
-                       &pkt, av_packet_unref);
-               av_init_packet(&pkt);
-               pkt.data = nullptr;
-               pkt.size = 0;
-               if (av_read_frame(format_ctx, &pkt) == 0) {
-                       if (pkt.stream_index == audio_stream_index && audio_callback != nullptr) {
-                               audio_callback(&pkt, format_ctx->streams[audio_stream_index]->time_base);
+               AVPacketWithDeleter pkt = av_packet_alloc_unique();
+               pkt->data = nullptr;
+               pkt->size = 0;
+               if (av_read_frame(format_ctx, pkt.get()) == 0) {
+                       if (pkt->stream_index == audio_stream_index && audio_callback != nullptr) {
+                               audio_callback(pkt.get(), format_ctx->streams[audio_stream_index]->time_base);
                        }
-                       if (pkt.stream_index == video_stream_index && video_callback != nullptr) {
-                               video_callback(&pkt, format_ctx->streams[video_stream_index]->time_base);
+                       if (pkt->stream_index == video_stream_index && video_callback != nullptr) {
+                               video_callback(pkt.get(), format_ctx->streams[video_stream_index]->time_base);
                        }
-                       if (pkt.stream_index == video_stream_index && global_flags.transcode_video) {
-                               if (avcodec_send_packet(video_codec_ctx, &pkt) < 0) {
+                       if (pkt->stream_index == video_stream_index && global_flags.transcode_video) {
+                               if (avcodec_send_packet(video_codec_ctx, pkt.get()) < 0) {
                                        fprintf(stderr, "%s: Cannot send packet to video codec.\n", pathname.c_str());
                                        *error = true;
                                        return AVFrameWithDeleter(nullptr);
                                }
-                       } else if (pkt.stream_index == audio_stream_index && global_flags.transcode_audio) {
+                       } else if (pkt->stream_index == audio_stream_index && global_flags.transcode_audio) {
                                has_audio = true;
-                               if (avcodec_send_packet(audio_codec_ctx, &pkt) < 0) {
+                               if (avcodec_send_packet(audio_codec_ctx, pkt.get()) < 0) {
                                        fprintf(stderr, "%s: Cannot send packet to audio codec.\n", pathname.c_str());
                                        *error = true;
                                        return AVFrameWithDeleter(nullptr);
                                }
-                       } else if (pkt.stream_index == subtitle_stream_index) {
-                               last_subtitle = string(reinterpret_cast<const char *>(pkt.data), pkt.size);
+                       } else if (pkt->stream_index == subtitle_stream_index) {
+                               last_subtitle = string(reinterpret_cast<const char *>(pkt->data), pkt->size);
                                has_last_subtitle = true;
                        }
                } else {
@@ -1138,6 +1156,16 @@ UniqueFrame FFmpegCapture::make_video_frame(const AVFrame *frame, const string &
 
                current_frame_ycbcr_format = decode_ycbcr_format(desc, frame, is_mjpeg, &last_colorspace, &last_chroma_location);
        }
+
+       // FIXME: Currently, if the video is too high-res for one of the allocated
+       // frames, we simply refuse to scale it here to avoid crashes. It would be better
+       // if we could somehow signal getting larger frames, especially as 4K is a thing now.
+       if (video_frame->len > FRAME_SIZE) {
+               fprintf(stderr, "%s: Decoded frame would be larger than supported FRAME_SIZE (%zu > %u), not decoding.\n", pathname.c_str(), video_frame->len, FRAME_SIZE);
+               *error = true;
+               return video_frame;
+       }
+
        sws_scale(sws_ctx.get(), frame->data, frame->linesize, 0, frame->height, pic_data, linesizes);
 
        return video_frame;
@@ -1150,6 +1178,21 @@ int FFmpegCapture::interrupt_cb_thunk(void *opaque)
 
 int FFmpegCapture::interrupt_cb()
 {
+       // If ten seconds is gone without anything happening, we assume that
+       // we are in a network stream that died and FFmpeg just didn't
+       // pick it up (or perhaps it just hung, keeping the connection open).
+       // Called back approximately every 100 ms if something is hanging,
+       // so we get more than enough accuracy for our purposes.
+       if (!should_interrupt && frame_timeout_valid &&
+           duration<double>(steady_clock::now() - frame_timeout_started).count() >= 10.0) {
+               string filename_copy;
+               {
+                       lock_guard<mutex> lock(filename_mu);
+                       filename_copy = filename;
+               }
+               fprintf(stderr, "%s: No frame for more than 10 seconds, restarting stream.\n", filename.c_str());
+               should_interrupt = true;
+       }
        return should_interrupt.load();
 }