#include <utility>
#include <vector>
+#include <Eigen/Core>
+#include <Eigen/LU>
+#include <movit/colorspace_conversion_effect.h>
+
#include "bmusb/bmusb.h"
#include "shared/ffmpeg_raii.h"
#include "ffmpeg_util.h"
using namespace std::chrono;
using namespace bmusb;
using namespace movit;
+using namespace Eigen;
namespace {
format.cb_y_position = 1.0;
break;
default:
- fprintf(stderr, "Unknown chroma location coefficient enum %d from FFmpeg; choosing Rec. 709.\n",
+ fprintf(stderr, "Unknown chroma location coefficient enum %d from FFmpeg; choosing center.\n",
frame->chroma_location);
format.cb_x_position = 0.5;
format.cb_y_position = 0.5;
return format;
}
+RGBTriplet get_neutral_color(AVDictionary *metadata)
+{
+ if (metadata == nullptr) {
+ return RGBTriplet(1.0f, 1.0f, 1.0f);
+ }
+ AVDictionaryEntry *entry = av_dict_get(metadata, "WhitePoint", nullptr, 0);
+ if (entry == nullptr) {
+ return RGBTriplet(1.0f, 1.0f, 1.0f);
+ }
+
+ unsigned x_nom, x_den, y_nom, y_den;
+ if (sscanf(entry->value, " %u:%u , %u:%u", &x_nom, &x_den, &y_nom, &y_den) != 4) {
+ fprintf(stderr, "WARNING: Unable to parse white point '%s', using default white point\n", entry->value);
+ return RGBTriplet(1.0f, 1.0f, 1.0f);
+ }
+
+ double x = double(x_nom) / x_den;
+ double y = double(y_nom) / y_den;
+ double z = 1.0 - x - y;
+
+ Matrix3d rgb_to_xyz_matrix = movit::ColorspaceConversionEffect::get_xyz_matrix(COLORSPACE_sRGB);
+ Vector3d rgb = rgb_to_xyz_matrix.inverse() * Vector3d(x, y, z);
+
+ return RGBTriplet(rgb[0], rgb[1], rgb[2]);
+}
+
} // namespace
FFmpegCapture::FFmpegCapture(const string &filename, unsigned width, unsigned height)
if (last_pts == 0 && pts_origin == 0) {
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;
+ steady_clock::time_point now = steady_clock::now();
+ if (play_as_fast_as_possible) {
+ video_frame->received_timestamp = now;
+ audio_frame->received_timestamp = now;
+ next_frame_start = now;
+ } else {
+ 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;
+ }
+ }
+ 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);
-
- 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));
- }
- 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;
+ 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;
+ if (play_as_fast_as_possible) {
+ finished_wakeup = !producer_thread_should_quit.should_quit();
+ } else {
+ finished_wakeup = producer_thread_should_quit.sleep_until(next_frame_start);
}
- bool finished_wakeup = producer_thread_should_quit.sleep_until(next_frame_start);
if (finished_wakeup) {
if (audio_frame->len > 0) {
assert(audio_pts != -1);
// audio discontinuity.)
timecode += MAX_FPS * 2 + 1;
}
+ last_neutral_color = get_neutral_color(frame->metadata);
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);
start = compute_frame_start(last_pts, pts_origin, video_timebase, start, rate);
pts_origin = last_pts;
rate = cmd.new_rate;
+ play_as_fast_as_possible = (rate >= 10.0);
break;
}
}
if (resampler == nullptr) {
fprintf(stderr, "Allocating resampler failed.\n");
- exit(1);
+ abort();
}
if (swr_init(resampler) < 0) {
fprintf(stderr, "Could not open resample context.\n");
- exit(1);
+ abort();
}
last_src_format = AVSampleFormat(audio_avframe->format);
const_cast<const uint8_t **>(audio_avframe->data), audio_avframe->nb_samples);
if (out_samples < 0) {
fprintf(stderr, "Audio conversion failed.\n");
- exit(1);
+ abort();
}
audio_frame->len += out_samples * bytes_per_sample;