X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=ffmpeg_capture.cpp;h=407f359104cff8444ac56cc9480c77c86366667c;hb=a315ebb10446bc372016bc0e9cd29d1aa3696eaa;hp=77af9e654067f643ca411c7641ae11963a115414;hpb=4a3c888ad7f62a41a86be68c989a0a62913c2b0e;p=nageru diff --git a/ffmpeg_capture.cpp b/ffmpeg_capture.cpp index 77af9e6..407f359 100644 --- a/ffmpeg_capture.cpp +++ b/ffmpeg_capture.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include extern "C" { @@ -27,6 +28,7 @@ extern "C" { #include "bmusb/bmusb.h" #include "ffmpeg_raii.h" +#include "ffmpeg_util.h" #include "flags.h" #include "image_input.h" @@ -44,6 +46,19 @@ steady_clock::time_point compute_frame_start(int64_t frame_pts, int64_t pts_orig return origin + duration_cast(pts / rate); } +bool changed_since(const std::string &pathname, const timespec &ts) +{ + if (ts.tv_sec < 0) { + return false; + } + struct stat buf; + if (stat(pathname.c_str(), &buf) != 0) { + fprintf(stderr, "%s: Couldn't check for new version, leaving the old in place.\n", pathname.c_str()); + return false; + } + return (buf.st_mtim.tv_sec != ts.tv_sec || buf.st_mtim.tv_nsec != ts.tv_nsec); +} + } // namespace FFmpegCapture::FFmpegCapture(const string &filename, unsigned width, unsigned height) @@ -51,6 +66,8 @@ FFmpegCapture::FFmpegCapture(const string &filename, unsigned width, unsigned he { // Not really used for anything. description = "Video: " + filename; + + avformat_network_init(); // In case someone wants this. } FFmpegCapture::~FFmpegCapture() @@ -78,7 +95,7 @@ void FFmpegCapture::start_bm_capture() return; } running = true; - producer_thread_should_quit = false; + producer_thread_should_quit.unquit(); producer_thread = thread(&FFmpegCapture::producer_thread_func, this); } @@ -88,7 +105,7 @@ void FFmpegCapture::stop_dequeue_thread() return; } running = false; - producer_thread_should_quit = true; + producer_thread_should_quit.quit(); producer_thread.join(); } @@ -117,17 +134,19 @@ void FFmpegCapture::producer_thread_func() snprintf(thread_name, sizeof(thread_name), "FFmpeg_C_%d", card_index); pthread_setname_np(pthread_self(), thread_name); - while (!producer_thread_should_quit) { + while (!producer_thread_should_quit.should_quit()) { string pathname = search_for_file(filename); if (filename.empty()) { fprintf(stderr, "%s not found, sleeping one second and trying again...\n", filename.c_str()); - sleep(1); + send_disconnected_frame(); + producer_thread_should_quit.sleep_for(seconds(1)); continue; } if (!play_video(pathname)) { // Error. fprintf(stderr, "Error when playing %s, sleeping one second and trying again...\n", pathname.c_str()); - sleep(1); + send_disconnected_frame(); + producer_thread_should_quit.sleep_for(seconds(1)); continue; } @@ -140,8 +159,42 @@ void FFmpegCapture::producer_thread_func() } } +void FFmpegCapture::send_disconnected_frame() +{ + // Send an empty frame to signal that we have no signal anymore. + FrameAllocator::Frame video_frame = video_frame_allocator->alloc_frame(); + if (video_frame.data) { + VideoFormat video_format; + video_format.width = width; + video_format.height = height; + video_format.stride = width * 4; + video_format.frame_rate_nom = 60; + video_format.frame_rate_den = 1; + video_format.is_connected = false; + + video_frame.len = width * height * 4; + memset(video_frame.data, 0, video_frame.len); + + frame_callback(timecode++, + video_frame, /*video_offset=*/0, video_format, + FrameAllocator::Frame(), /*audio_offset=*/0, AudioFormat()); + } +} + bool FFmpegCapture::play_video(const string &pathname) { + // Note: Call before open, not after; otherwise, there's a race. + // (There is now, too, but it tips the correct way. We could use fstat() + // if we had the file descriptor.) + timespec last_modified; + struct stat buf; + if (stat(pathname.c_str(), &buf) != 0) { + // Probably some sort of protocol, so can't stat. + last_modified.tv_sec = -1; + } else { + last_modified = buf.st_mtim; + } + auto format_ctx = avformat_open_input_unique(pathname.c_str(), nullptr, nullptr); if (format_ctx == nullptr) { fprintf(stderr, "%s: Error opening file\n", pathname.c_str()); @@ -153,25 +206,14 @@ bool FFmpegCapture::play_video(const string &pathname) return false; } - int video_stream_index = -1, audio_stream_index = -1; - AVRational video_timebase{ 1, 1 }; - for (unsigned i = 0; i < format_ctx->nb_streams; ++i) { - if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && - video_stream_index == -1) { - video_stream_index = i; - video_timebase = format_ctx->streams[i]->time_base; - } - if (format_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && - audio_stream_index == -1) { - audio_stream_index = i; - } - } + int video_stream_index = find_stream_index(format_ctx.get(), AVMEDIA_TYPE_VIDEO); if (video_stream_index == -1) { fprintf(stderr, "%s: No video stream found\n", pathname.c_str()); return false; } const AVCodecParameters *codecpar = format_ctx->streams[video_stream_index]->codecpar; + AVRational video_timebase = format_ctx->streams[video_stream_index]->time_base; AVCodecContextWithDeleter codec_ctx = avcodec_alloc_context3_unique(nullptr); if (avcodec_parameters_to_context(codec_ctx.get(), codecpar) < 0) { fprintf(stderr, "%s: Cannot fill codec parameters\n", pathname.c_str()); @@ -189,16 +231,14 @@ bool FFmpegCapture::play_video(const string &pathname) unique_ptr codec_ctx_cleanup( codec_ctx.get(), avcodec_close); - int64_t pts_origin = 0, last_pts = 0; - steady_clock::time_point start = steady_clock::now(); - steady_clock::time_point next_frame_start = start; + internal_rewind(); double rate = 1.0; unique_ptr sws_ctx(nullptr, sws_freeContext); int sws_last_width = -1, sws_last_height = -1; // Main loop. - while (!producer_thread_should_quit) { + while (!producer_thread_should_quit.should_quit()) { // Process any queued commands from other threads. vector commands; { @@ -211,8 +251,14 @@ bool FFmpegCapture::play_video(const string &pathname) if (av_seek_frame(format_ctx.get(), /*stream_index=*/-1, /*timestamp=*/0, /*flags=*/0) < 0) { fprintf(stderr, "%s: Rewind failed, stopping play.\n", pathname.c_str()); } - pts_origin = last_pts = 0; - start = next_frame_start = steady_clock::now(); + // If the file has changed since last time, return to get it reloaded. + // Note that depending on how you move the file into place, you might + // end up corrupting the one you're already playing, so this path + // might not trigger. + if (changed_since(pathname, last_modified)) { + return true; + } + internal_rewind(); break; case QueuedCommand::CHANGE_RATE: @@ -263,15 +309,21 @@ bool FFmpegCapture::play_video(const string &pathname) fprintf(stderr, "%s: Rewind failed, not looping.\n", pathname.c_str()); return true; } - pts_origin = last_pts = 0; - start = steady_clock::now(); + // If the file has changed since last time, return to get it reloaded. + // Note that depending on how you move the file into place, you might + // end up corrupting the one you're already playing, so this path + // might not trigger. + if (changed_since(pathname, last_modified)) { + return true; + } + internal_rewind(); continue; } if (sws_ctx == nullptr || sws_last_width != frame->width || sws_last_height != frame->height) { sws_ctx.reset( sws_getContext(frame->width, frame->height, (AVPixelFormat)frame->format, - width, height, AV_PIX_FMT_RGBA, + width, height, AV_PIX_FMT_BGRA, SWS_BICUBIC, nullptr, nullptr, nullptr)); sws_last_width = frame->width; sws_last_height = frame->height; @@ -287,6 +339,11 @@ bool FFmpegCapture::play_video(const string &pathname) video_format.stride = width * 4; video_format.frame_rate_nom = video_timebase.den; video_format.frame_rate_den = av_frame_get_pkt_duration(frame.get()) * video_timebase.num; + if (video_format.frame_rate_nom == 0 || video_format.frame_rate_den == 0) { + // Invalid frame rate. + video_format.frame_rate_nom = 60; + video_format.frame_rate_den = 1; + } video_format.has_signal = true; video_format.is_connected = true; @@ -307,11 +364,16 @@ bool FFmpegCapture::play_video(const string &pathname) audio_format.bits_per_sample = 32; audio_format.num_channels = 8; - // TODO: Make it interruptable somehow. - this_thread::sleep_until(next_frame_start); + producer_thread_should_quit.sleep_until(next_frame_start); frame_callback(timecode++, video_frame, 0, video_format, audio_frame, 0, audio_format); } return true; } + +void FFmpegCapture::internal_rewind() +{ + pts_origin = last_pts = 0; + start = next_frame_start = steady_clock::now(); +}