]> git.sesse.net Git - nageru/blobdiff - ffmpeg_capture.cpp
Support audio-only FFmpeg inputs. Somewhat wonky, though.
[nageru] / ffmpeg_capture.cpp
index c79ecf337fe8213efe16b26f3e3cd436c5c4dfe5..5f95da240448a5ec90efd45cd9c80b9b1a4dd2e3 100644 (file)
@@ -208,7 +208,6 @@ YCbCrFormat decode_ycbcr_format(const AVPixFmtDescriptor *desc, const AVFrame *f
 FFmpegCapture::FFmpegCapture(const string &filename, unsigned width, unsigned height)
        : filename(filename), width(width), height(height), video_timebase{1, 1}
 {
-       // Not really used for anything.
        description = "Video: " + filename;
 
        last_frame = steady_clock::now();
@@ -283,9 +282,15 @@ void FFmpegCapture::producer_thread_func()
        pthread_setname_np(pthread_self(), thread_name);
 
        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());
+               string filename_copy;
+               {
+                       lock_guard<mutex> lock(filename_mu);
+                       filename_copy = filename;
+               }
+
+               string pathname = search_for_file(filename_copy);
+               if (pathname.empty()) {
+                       fprintf(stderr, "%s not found, sleeping one second and trying again...\n", filename_copy.c_str());
                        send_disconnected_frame();
                        producer_thread_should_quit.sleep_for(seconds(1));
                        continue;
@@ -322,6 +327,7 @@ void FFmpegCapture::send_disconnected_frame()
                if (pixel_format == bmusb::PixelFormat_8BitBGRA) {
                        video_format.stride = width * 4;
                        video_frame.len = width * height * 4;
+                       memset(video_frame.data, 0, video_frame.len);
                } else {
                        video_format.stride = width;
                        current_frame_ycbcr_format.luma_coefficients = YCBCR_REC_709;
@@ -334,8 +340,9 @@ void FFmpegCapture::send_disconnected_frame()
                        current_frame_ycbcr_format.cr_x_position = 0.0f;
                        current_frame_ycbcr_format.cr_y_position = 0.0f;
                        video_frame.len = width * height * 2;
+                       memset(video_frame.data, 0, width * height);
+                       memset(video_frame.data + width * height, 128, width * height);  // Valid for both NV12 and planar.
                }
-               memset(video_frame.data, 0, video_frame.len);
 
                frame_callback(-1, AVRational{1, TIMEBASE}, -1, AVRational{1, TIMEBASE}, timecode++,
                        video_frame, /*video_offset=*/0, video_format,
@@ -373,29 +380,36 @@ bool FFmpegCapture::play_video(const string &pathname)
        }
 
        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;
-       }
-
        int audio_stream_index = find_stream_index(format_ctx.get(), AVMEDIA_TYPE_AUDIO);
 
-       // Open video decoder.
-       const AVCodecParameters *video_codecpar = format_ctx->streams[video_stream_index]->codecpar;
-       AVCodec *video_codec = avcodec_find_decoder(video_codecpar->codec_id);
-       video_timebase = format_ctx->streams[video_stream_index]->time_base;
-       AVCodecContextWithDeleter video_codec_ctx = avcodec_alloc_context3_unique(nullptr);
-       if (avcodec_parameters_to_context(video_codec_ctx.get(), video_codecpar) < 0) {
-               fprintf(stderr, "%s: Cannot fill video codec parameters\n", pathname.c_str());
-               return false;
-       }
-       if (video_codec == nullptr) {
-               fprintf(stderr, "%s: Cannot find video decoder\n", pathname.c_str());
+       if (video_stream_index == -1 && audio_stream_index == -1) {
+               fprintf(stderr, "%s: No audio nor video stream found\n", pathname.c_str());
                return false;
        }
-       if (avcodec_open2(video_codec_ctx.get(), video_codec, nullptr) < 0) {
-               fprintf(stderr, "%s: Cannot open video decoder\n", pathname.c_str());
-               return false;
+       if (video_stream_index == -1) {
+               fprintf(stderr, "%s: No video stream found, assuming audio-only.\n", pathname.c_str());
+       }
+       const bool audio_only_stream = (video_stream_index == -1);
+
+       // Open video decoder, if we have video.
+       AVCodecContextWithDeleter video_codec_ctx;
+       if (video_stream_index != -1) {
+               const AVCodecParameters *video_codecpar = format_ctx->streams[video_stream_index]->codecpar;
+               AVCodec *video_codec = avcodec_find_decoder(video_codecpar->codec_id);
+               video_timebase = format_ctx->streams[video_stream_index]->time_base;
+               video_codec_ctx = avcodec_alloc_context3_unique(nullptr);
+               if (avcodec_parameters_to_context(video_codec_ctx.get(), video_codecpar) < 0) {
+                       fprintf(stderr, "%s: Cannot fill video codec parameters\n", pathname.c_str());
+                       return false;
+               }
+               if (video_codec == nullptr) {
+                       fprintf(stderr, "%s: Cannot find video decoder\n", pathname.c_str());
+                       return false;
+               }
+               if (avcodec_open2(video_codec_ctx.get(), video_codec, nullptr) < 0) {
+                       fprintf(stderr, "%s: Cannot open video decoder\n", pathname.c_str());
+                       return false;
+               }
        }
        unique_ptr<AVCodecContext, decltype(avcodec_close)*> video_codec_ctx_cleanup(
                video_codec_ctx.get(), avcodec_close);
@@ -441,7 +455,7 @@ bool FFmpegCapture::play_video(const string &pathname)
                if (error) {
                        return false;
                }
-               if (frame == nullptr) {
+               if (frame == nullptr && !(audio_only_stream && audio_frame->len > 0)) {
                        // EOF. Loop back to the start if we can.
                        if (av_seek_frame(format_ctx.get(), /*stream_index=*/-1, /*timestamp=*/0, /*flags=*/0) < 0) {
                                fprintf(stderr, "%s: Rewind failed, not looping.\n", pathname.c_str());
@@ -464,33 +478,75 @@ bool FFmpegCapture::play_video(const string &pathname)
                        continue;
                }
 
-               VideoFormat video_format = construct_video_format(frame.get(), video_timebase);
-               UniqueFrame video_frame = make_video_frame(frame.get(), pathname, &error);
-               if (error) {
-                       return false;
+               VideoFormat video_format;
+               UniqueFrame video_frame;
+               if (!audio_only_stream) {
+                       video_format = construct_video_format(frame.get(), video_timebase);
+                       video_frame = make_video_frame(frame.get(), pathname, &error);
+                       if (error) {
+                               return false;
+                       }
                }
 
-               for ( ;; ) {
+               int64_t frame_pts = audio_only_stream ? audio_pts : frame->pts;
+               AVRational timebase = audio_only_stream ? audio_timebase : video_timebase;
+               for ( ;; ) {  // Try sending the frame in a loop as long as we get interrupted (then break).
                        if (last_pts == 0 && pts_origin == 0) {
-                               pts_origin = frame->pts;        
+                               pts_origin = frame_pts;
                        }
-                       next_frame_start = compute_frame_start(frame->pts, pts_origin, video_timebase, start, rate);
-                       if (first_frame && last_frame_was_connected) {
-                               // If reconnect took more than one second, this is probably a live feed,
-                               // and we should reset the resampler. (Or the rate is really, really low,
-                               // in which case a reset on the first frame is fine anyway.)
-                               if (duration<double>(next_frame_start - last_frame).count() >= 1.0) {
-                                       last_frame_was_connected = false;
+                       next_frame_start = compute_frame_start(frame_pts, pts_origin, timebase, start, rate);
+                       if (audio_only_stream) {
+                               audio_frame->received_timestamp = next_frame_start;
+                       } else {
+                               if (first_frame && last_frame_was_connected) {
+                                       // If reconnect took more than one second, this is probably a live feed,
+                                       // and we should reset the resampler. (Or the rate is really, really low,
+                                       // in which case a reset on the first frame is fine anyway.)
+                                       if (duration<double>(next_frame_start - last_frame).count() >= 1.0) {
+                                               last_frame_was_connected = false;
+                                       }
+                               }
+                               video_frame->received_timestamp = next_frame_start;
+
+                               // The easiest way to get all the rate conversions etc. right is to move the
+                               // audio PTS into the video PTS timebase and go from there. (We'll get some
+                               // rounding issues, but they should not be a big problem.)
+                               int64_t audio_pts_as_video_pts = av_rescale_q(audio_pts, audio_timebase, video_timebase);
+                               audio_frame->received_timestamp = compute_frame_start(audio_pts_as_video_pts, pts_origin, video_timebase, start, rate);
+
+                               if (audio_frame->len != 0) {
+                                       // The received timestamps in Nageru are measured after we've just received the frame.
+                                       // However, pts (especially audio pts) is at the _beginning_ of the frame.
+                                       // If we have locked audio, the distinction doesn't really matter, as pts is
+                                       // on a relative scale and a fixed offset is fine. But if we don't, we will have
+                                       // a different number of samples each time, which will cause huge audio jitter
+                                       // and throw off the resampler.
+                                       //
+                                       // In a sense, we should have compensated by adding the frame and audio lengths
+                                       // to video_frame->received_timestamp and audio_frame->received_timestamp respectively,
+                                       // but that would mean extra waiting in sleep_until(). All we need is that they
+                                       // are correct relative to each other, though (and to the other frames we send),
+                                       // so just align the end of the audio frame, and we're fine.
+                                       size_t num_samples = (audio_frame->len * 8) / audio_format.bits_per_sample / audio_format.num_channels;
+                                       double offset = double(num_samples) / OUTPUT_FREQUENCY -
+                                               double(video_format.frame_rate_den) / video_format.frame_rate_nom;
+                                       audio_frame->received_timestamp += duration_cast<steady_clock::duration>(duration<double>(offset));
                                }
                        }
-                       video_frame->received_timestamp = next_frame_start;
-
-                       // The easiest way to get all the rate conversions etc. right is to move the
-                       // audio PTS into the video PTS timebase and go from there. (We'll get some
-                       // rounding issues, but they should not be a big problem.)
-                       int64_t audio_pts_as_video_pts = av_rescale_q(audio_pts, audio_timebase, video_timebase);
-                       audio_frame->received_timestamp = compute_frame_start(audio_pts_as_video_pts, pts_origin, video_timebase, start, rate);
 
+                       steady_clock::time_point now = steady_clock::now();
+                       if (duration<double>(now - next_frame_start).count() >= 0.1) {
+                               // If we don't have enough CPU to keep up, or if we have a live stream
+                               // where the initial origin was somehow wrong, we could be behind indefinitely.
+                               // In particular, this will give the audio resampler problems as it tries
+                               // to speed up to reduce the delay, hitting the low end of the buffer every time.
+                               fprintf(stderr, "%s: Playback %.0f ms behind, resetting time scale\n",
+                                       pathname.c_str(),
+                                       1e3 * duration<double>(now - next_frame_start).count());
+                               pts_origin = frame_pts;
+                               start = next_frame_start = now;
+                               timecode += MAX_FPS * 2 + 1;
+                       }
                        bool finished_wakeup = producer_thread_should_quit.sleep_until(next_frame_start);
                        if (finished_wakeup) {
                                if (audio_frame->len > 0) {
@@ -503,7 +559,7 @@ bool FFmpegCapture::play_video(const string &pathname)
                                        // audio discontinuity.)
                                        timecode += MAX_FPS * 2 + 1;
                                }
-                               frame_callback(frame->pts, video_timebase, audio_pts, audio_timebase, timecode++,
+                               frame_callback(frame_pts, video_timebase, audio_pts, audio_timebase, timecode++,
                                        video_frame.get_and_release(), 0, video_format,
                                        audio_frame.get_and_release(), 0, audio_format);
                                first_frame = false;
@@ -532,7 +588,7 @@ bool FFmpegCapture::play_video(const string &pathname)
                                }
                        }
                }
-               last_pts = frame->pts;
+               last_pts = frame_pts;
        }
        return true;
 }
@@ -597,6 +653,7 @@ AVFrameWithDeleter FFmpegCapture::decode_frame(AVFormatContext *format_ctx, AVCo
        AVFrameWithDeleter video_avframe = av_frame_alloc_unique();
        bool eof = false;
        *audio_pts = -1;
+       bool has_audio = false;
        do {
                AVPacket pkt;
                unique_ptr<AVPacket, decltype(av_packet_unref)*> pkt_cleanup(
@@ -615,9 +672,7 @@ AVFrameWithDeleter FFmpegCapture::decode_frame(AVFormatContext *format_ctx, AVCo
                                        return AVFrameWithDeleter(nullptr);
                                }
                        } else if (pkt.stream_index == audio_stream_index) {
-                               if (*audio_pts == -1) {
-                                       *audio_pts = pkt.pts;
-                               }
+                               has_audio = true;
                                if (avcodec_send_packet(audio_codec_ctx, &pkt) < 0) {
                                        fprintf(stderr, "%s: Cannot send packet to audio codec.\n", pathname.c_str());
                                        *error = true;
@@ -629,10 +684,13 @@ AVFrameWithDeleter FFmpegCapture::decode_frame(AVFormatContext *format_ctx, AVCo
                }
 
                // Decode audio, if any.
-               if (*audio_pts != -1) {
+               if (has_audio) {
                        for ( ;; ) {
                                int err = avcodec_receive_frame(audio_codec_ctx, audio_avframe.get());
                                if (err == 0) {
+                                       if (*audio_pts == -1) {
+                                               *audio_pts = audio_avframe->pts;
+                                       }
                                        convert_audio(audio_avframe.get(), audio_frame, audio_format);
                                } else if (err == AVERROR(EAGAIN)) {
                                        break;
@@ -644,14 +702,18 @@ AVFrameWithDeleter FFmpegCapture::decode_frame(AVFormatContext *format_ctx, AVCo
                        }
                }
 
-               // Decode video, if we have a frame.
-               int err = avcodec_receive_frame(video_codec_ctx, video_avframe.get());
-               if (err == 0) {
-                       frame_finished = true;
-                       break;
-               } else if (err != AVERROR(EAGAIN)) {
-                       fprintf(stderr, "%s: Cannot receive frame from video codec.\n", pathname.c_str());
-                       *error = true;
+               if (video_codec_ctx != nullptr) {
+                       // Decode video, if we have a frame.
+                       int err = avcodec_receive_frame(video_codec_ctx, video_avframe.get());
+                       if (err == 0) {
+                               frame_finished = true;
+                               break;
+                       } else if (err != AVERROR(EAGAIN)) {
+                               fprintf(stderr, "%s: Cannot receive frame from video codec.\n", pathname.c_str());
+                               *error = true;
+                               return AVFrameWithDeleter(nullptr);
+                       }
+               } else {
                        return AVFrameWithDeleter(nullptr);
                }
        } while (!eof);
@@ -710,7 +772,7 @@ void FFmpegCapture::convert_audio(const AVFrame *audio_avframe, FrameAllocator::
                }
 
                av_opt_set_int(resampler, "in_channel_layout",  channel_layout,                             0);
-               av_opt_set_int(resampler, "out_channel_layout", AV_CH_LAYOUT_STEREO,                        0);
+               av_opt_set_int(resampler, "out_channel_layout", AV_CH_LAYOUT_STEREO_DOWNMIX,                0);
                av_opt_set_int(resampler, "in_sample_rate",     av_frame_get_sample_rate(audio_avframe),    0);
                av_opt_set_int(resampler, "out_sample_rate",    OUTPUT_FREQUENCY,                           0);
                av_opt_set_int(resampler, "in_sample_fmt",      audio_avframe->format,                      0);