summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarl Hetherington <cth@carlh.net>2020-11-08 22:34:18 +0100
committerCarl Hetherington <cth@carlh.net>2020-11-16 01:40:36 +0100
commite64a1a9aae0200d14feed49a4c6cf537bf5708a4 (patch)
treeb1b01bb8e6f1872309eb246434120de3b769e9e5
parentf5608308b17c72b3ee459c805663e0103de1d2a4 (diff)
Obey requests to change the video range of RGB content.
Video that comes in with RGB pixels will not have its video level ranges changed by libswscale (it only does this for YUV and greyscale). Here we add code to do it ourselves for RGB content coming in via image files (e.g. PNG/DPX etc). Part of #1851.
-rw-r--r--src/lib/ffmpeg_image_proxy.cc24
-rw-r--r--src/lib/ffmpeg_image_proxy.h6
-rw-r--r--src/lib/image.cc43
-rw-r--r--src/lib/image.h1
-rw-r--r--src/lib/image_decoder.cc2
-rw-r--r--src/lib/image_examiner.cc2
-rw-r--r--src/lib/types.cc28
-rw-r--r--src/lib/types.h5
-rw-r--r--src/lib/util.cc2
-rw-r--r--test/image_proxy_test.cc8
-rw-r--r--test/image_test.cc16
-rw-r--r--test/test.cc4
-rw-r--r--test/video_level_test.cc97
-rw-r--r--test/wscript1
14 files changed, 215 insertions, 24 deletions
diff --git a/src/lib/ffmpeg_image_proxy.cc b/src/lib/ffmpeg_image_proxy.cc
index 602185bb8..c978fc383 100644
--- a/src/lib/ffmpeg_image_proxy.cc
+++ b/src/lib/ffmpeg_image_proxy.cc
@@ -30,6 +30,7 @@
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
+#include <libavutil/pixdesc.h>
}
DCPOMATIC_DISABLE_WARNINGS
#include <libxml++/libxml++.h>
@@ -48,23 +49,26 @@ using boost::optional;
using boost::dynamic_pointer_cast;
using dcp::raw_convert;
-FFmpegImageProxy::FFmpegImageProxy (boost::filesystem::path path)
+FFmpegImageProxy::FFmpegImageProxy (boost::filesystem::path path, VideoRange video_range)
: _data (path)
+ , _video_range (video_range)
, _pos (0)
, _path (path)
{
}
-FFmpegImageProxy::FFmpegImageProxy (dcp::ArrayData data)
+FFmpegImageProxy::FFmpegImageProxy (dcp::ArrayData data, VideoRange video_range)
: _data (data)
+ , _video_range (video_range)
, _pos (0)
{
}
-FFmpegImageProxy::FFmpegImageProxy (shared_ptr<cxml::Node>, shared_ptr<Socket> socket)
- : _pos (0)
+FFmpegImageProxy::FFmpegImageProxy (shared_ptr<cxml::Node> node, shared_ptr<Socket> socket)
+ : _video_range (string_to_video_range(node->string_child("VideoRange")))
+ , _pos (0)
{
uint32_t const size = socket->read_uint32 ();
_data = dcp::ArrayData (size);
@@ -188,7 +192,16 @@ FFmpegImageProxy::image (optional<dcp::Size>) const
throw DecodeError (N_("could not decode video"));
}
- _image.reset (new Image (frame));
+ AVPixelFormat const pix_fmt = static_cast<AVPixelFormat>(frame->format);
+
+ _image.reset (new Image(frame));
+ if (_video_range == VIDEO_RANGE_VIDEO && av_pix_fmt_desc_get(pix_fmt)->flags & AV_PIX_FMT_FLAG_RGB) {
+ /* Asking for the video range to be converted by libswscale (in Image) will not work for
+ * RGB sources since that method only processes video range in YUV and greyscale. So we have
+ * to do it ourselves here.
+ */
+ _image->video_range_to_full_range();
+ }
av_packet_unref (&packet);
av_frame_free (&frame);
@@ -206,6 +219,7 @@ void
FFmpegImageProxy::add_metadata (xmlpp::Node* node) const
{
node->add_child("Type")->add_child_text (N_("FFmpeg"));
+ node->add_child("VideoRange")->add_child_text(video_range_to_string(_video_range));
}
void
diff --git a/src/lib/ffmpeg_image_proxy.h b/src/lib/ffmpeg_image_proxy.h
index 62b99d280..4fca899f4 100644
--- a/src/lib/ffmpeg_image_proxy.h
+++ b/src/lib/ffmpeg_image_proxy.h
@@ -19,6 +19,7 @@
*/
#include "image_proxy.h"
+#include "types.h"
#include <dcp/array_data.h>
#include <boost/thread/mutex.hpp>
#include <boost/filesystem.hpp>
@@ -26,8 +27,8 @@
class FFmpegImageProxy : public ImageProxy
{
public:
- explicit FFmpegImageProxy (boost::filesystem::path);
- explicit FFmpegImageProxy (dcp::ArrayData);
+ explicit FFmpegImageProxy (boost::filesystem::path, VideoRange video_range);
+ explicit FFmpegImageProxy (dcp::ArrayData, VideoRange video_range);
FFmpegImageProxy (boost::shared_ptr<cxml::Node> xml, boost::shared_ptr<Socket> socket);
Result image (
@@ -44,6 +45,7 @@ public:
private:
dcp::ArrayData _data;
+ VideoRange _video_range;
mutable int64_t _pos;
/** Path of a file that this image came from, if applicable; stored so that
failed-decode errors can give more detail.
diff --git a/src/lib/image.cc b/src/lib/image.cc
index 03f1bf6dc..891715a46 100644
--- a/src/lib/image.cc
+++ b/src/lib/image.cc
@@ -1319,3 +1319,46 @@ Image::as_png () const
return dcp::ArrayData (state.data, state.size);
}
+
+
+void
+Image::video_range_to_full_range ()
+{
+ switch (_pixel_format) {
+ case AV_PIX_FMT_RGB24:
+ {
+ float const factor = 256.0 / 219.0;
+ uint8_t* p = data()[0];
+ int const lines = sample_size(0).height;
+ for (int y = 0; y < lines; ++y) {
+ uint8_t* q = p;
+ for (int x = 0; x < line_size()[0]; ++x) {
+ *q = int((*q - 16) * factor);
+ ++q;
+ }
+ p += stride()[0];
+ }
+ break;
+ }
+ case AV_PIX_FMT_GBRP12LE:
+ {
+ float const factor = 4096.0 / 3504.0;
+ for (int c = 0; c < 3; ++c) {
+ uint16_t* p = reinterpret_cast<uint16_t*>(data()[c]);
+ int const lines = sample_size(c).height;
+ for (int y = 0; y < lines; ++y) {
+ uint16_t* q = p;
+ int const line_size_pixels = line_size()[c] / 2;
+ for (int x = 0; x < line_size_pixels; ++x) {
+ *q = int((*q - 256) * factor);
+ ++q;
+ }
+ }
+ }
+ break;
+ }
+ default:
+ throw PixelFormatError ("video_range_to_full_range()", _pixel_format);
+ }
+}
+
diff --git a/src/lib/image.h b/src/lib/image.h
index ab9b3c78a..c648fda1b 100644
--- a/src/lib/image.h
+++ b/src/lib/image.h
@@ -74,6 +74,7 @@ public:
void alpha_blend (boost::shared_ptr<const Image> image, Position<int> pos);
void copy (boost::shared_ptr<const Image> image, Position<int> pos);
void fade (float);
+ void video_range_to_full_range ();
void read_from_socket (boost::shared_ptr<Socket>);
void write_to_socket (boost::shared_ptr<Socket>) const;
diff --git a/src/lib/image_decoder.cc b/src/lib/image_decoder.cc
index 15187b11b..7757cc4aa 100644
--- a/src/lib/image_decoder.cc
+++ b/src/lib/image_decoder.cc
@@ -70,7 +70,7 @@ ImageDecoder::pass ()
*/
_image.reset (new J2KImageProxy (path, _image_content->video->size(), pf));
} else {
- _image.reset (new FFmpegImageProxy (path));
+ _image.reset (new FFmpegImageProxy(path, _image_content->video->range()));
}
}
diff --git a/src/lib/image_examiner.cc b/src/lib/image_examiner.cc
index 6586a0d09..aa80d0daa 100644
--- a/src/lib/image_examiner.cc
+++ b/src/lib/image_examiner.cc
@@ -63,7 +63,7 @@ ImageExaminer::ImageExaminer (shared_ptr<const Film> film, shared_ptr<const Imag
}
delete[] buffer;
} else {
- FFmpegImageProxy proxy(content->path(0));
+ FFmpegImageProxy proxy(content->path(0), content->video->range());
_video_size = proxy.image().image->size();
}
diff --git a/src/lib/types.cc b/src/lib/types.cc
index e7acf6992..5687a5d48 100644
--- a/src/lib/types.cc
+++ b/src/lib/types.cc
@@ -229,3 +229,31 @@ bool operator== (NamedChannel const& a, NamedChannel const& b)
return a.name == b.name && a.index == b.index;
}
+
+string
+video_range_to_string (VideoRange r)
+{
+ switch (r) {
+ case VIDEO_RANGE_FULL:
+ return "full";
+ case VIDEO_RANGE_VIDEO:
+ return "video";
+ default:
+ DCPOMATIC_ASSERT (false);
+ }
+}
+
+
+VideoRange
+string_to_video_range (string s)
+{
+ if (s == "full") {
+ return VIDEO_RANGE_FULL;
+ } else if (s == "video") {
+ return VIDEO_RANGE_VIDEO;
+ }
+
+ DCPOMATIC_ASSERT (false);
+ return VIDEO_RANGE_FULL;
+}
+
diff --git a/src/lib/types.h b/src/lib/types.h
index 2ba0408ad..a10b26a63 100644
--- a/src/lib/types.h
+++ b/src/lib/types.h
@@ -139,12 +139,17 @@ enum ChangeType
CHANGE_TYPE_CANCELLED
};
+
enum VideoRange
{
VIDEO_RANGE_FULL, ///< full, or "JPEG" (0-255 for 8-bit)
VIDEO_RANGE_VIDEO ///< video, or "MPEG" (16-235 for 8-bit)
};
+extern std::string video_range_to_string (VideoRange r);
+extern VideoRange string_to_video_range (std::string s);
+
+
/** Type of captions.
*
* The generally accepted definitions seem to be:
diff --git a/src/lib/util.cc b/src/lib/util.cc
index ac868c173..0a060e960 100644
--- a/src/lib/util.cc
+++ b/src/lib/util.cc
@@ -953,7 +953,7 @@ void
emit_subtitle_image (ContentTimePeriod period, dcp::SubtitleImage sub, dcp::Size size, shared_ptr<TextDecoder> decoder)
{
/* XXX: this is rather inefficient; decoding the image just to get its size */
- FFmpegImageProxy proxy (sub.png_image());
+ FFmpegImageProxy proxy (sub.png_image(), VIDEO_RANGE_FULL);
shared_ptr<Image> image = proxy.image().image;
/* set up rect with height and width */
dcpomatic::Rect<double> rect(0, 0, image->size().width / double(size.width), image->size().height / double(size.height));
diff --git a/test/image_proxy_test.cc b/test/image_proxy_test.cc
index 061df9eed..0999d4b10 100644
--- a/test/image_proxy_test.cc
+++ b/test/image_proxy_test.cc
@@ -53,14 +53,14 @@ BOOST_AUTO_TEST_CASE (j2k_image_proxy_same_test)
BOOST_AUTO_TEST_CASE (ffmpeg_image_proxy_same_test)
{
{
- shared_ptr<FFmpegImageProxy> proxy1(new FFmpegImageProxy(data_file0));
- shared_ptr<FFmpegImageProxy> proxy2(new FFmpegImageProxy(data_file0));
+ shared_ptr<FFmpegImageProxy> proxy1(new FFmpegImageProxy(data_file0, VIDEO_RANGE_FULL));
+ shared_ptr<FFmpegImageProxy> proxy2(new FFmpegImageProxy(data_file0, VIDEO_RANGE_FULL));
BOOST_CHECK (proxy1->same(proxy2));
}
{
- shared_ptr<FFmpegImageProxy> proxy1(new FFmpegImageProxy(data_file0));
- shared_ptr<FFmpegImageProxy> proxy2(new FFmpegImageProxy(data_file1));
+ shared_ptr<FFmpegImageProxy> proxy1(new FFmpegImageProxy(data_file0, VIDEO_RANGE_FULL));
+ shared_ptr<FFmpegImageProxy> proxy2(new FFmpegImageProxy(data_file1, VIDEO_RANGE_FULL));
BOOST_CHECK (!proxy1->same(proxy2));
}
}
diff --git a/test/image_test.cc b/test/image_test.cc
index bdd34c665..e2b1d71c7 100644
--- a/test/image_test.cc
+++ b/test/image_test.cc
@@ -137,7 +137,7 @@ BOOST_AUTO_TEST_CASE (compact_image_test)
void
alpha_blend_test_one (AVPixelFormat format, string suffix)
{
- shared_ptr<FFmpegImageProxy> proxy (new FFmpegImageProxy (TestPaths::private_data() / "prophet_frame.tiff"));
+ shared_ptr<FFmpegImageProxy> proxy (new FFmpegImageProxy (TestPaths::private_data() / "prophet_frame.tiff", VIDEO_RANGE_FULL));
shared_ptr<Image> raw = proxy->image().image;
shared_ptr<Image> background = raw->convert_pixel_format (dcp::YUV_TO_RGB_REC709, format, true, false);
@@ -259,7 +259,7 @@ BOOST_AUTO_TEST_CASE (merge_test2)
/** Test Image::crop_scale_window with YUV420P and some windowing */
BOOST_AUTO_TEST_CASE (crop_scale_window_test)
{
- shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/flat_red.png"));
+ shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/flat_red.png", VIDEO_RANGE_FULL));
shared_ptr<Image> raw = proxy->image().image;
shared_ptr<Image> out = raw->crop_scale_window(Crop(), dcp::Size(1998, 836), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_YUV420P, true, false);
shared_ptr<Image> save = out->scale(dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_RGB24, false, false);
@@ -277,7 +277,7 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test2)
BOOST_AUTO_TEST_CASE (crop_scale_window_test3)
{
- shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png"));
+ shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png", VIDEO_RANGE_FULL));
shared_ptr<Image> xyz = proxy->image().image->convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_RGB24, true, false);
shared_ptr<Image> cropped = xyz->crop_scale_window(Crop(512, 0, 0, 0), dcp::Size(1486, 1080), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_RGB24, false, false);
write_image(cropped, "build/test/crop_scale_window_test3.png");
@@ -286,7 +286,7 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test3)
BOOST_AUTO_TEST_CASE (crop_scale_window_test4)
{
- shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png"));
+ shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png", VIDEO_RANGE_FULL));
shared_ptr<Image> xyz = proxy->image().image->convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_RGB24, true, false);
shared_ptr<Image> cropped = xyz->crop_scale_window(Crop(512, 0, 0, 0), dcp::Size(1486, 1080), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_XYZ12LE, false, false);
write_image(cropped, "build/test/crop_scale_window_test4.png");
@@ -295,7 +295,7 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test4)
BOOST_AUTO_TEST_CASE (crop_scale_window_test5)
{
- shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png"));
+ shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png", VIDEO_RANGE_FULL));
shared_ptr<Image> xyz = proxy->image().image->convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_XYZ12LE, true, false);
shared_ptr<Image> cropped = xyz->crop_scale_window(Crop(512, 0, 0, 0), dcp::Size(1486, 1080), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_RGB24, false, false);
write_image(cropped, "build/test/crop_scale_window_test5.png");
@@ -304,7 +304,7 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test5)
BOOST_AUTO_TEST_CASE (crop_scale_window_test6)
{
- shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png"));
+ shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/player_seek_test_0.png", VIDEO_RANGE_FULL));
shared_ptr<Image> xyz = proxy->image().image->convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_XYZ12LE, true, false);
shared_ptr<Image> cropped = xyz->crop_scale_window(Crop(512, 0, 0, 0), dcp::Size(1486, 1080), dcp::Size(1998, 1080), dcp::YUV_TO_RGB_REC709, VIDEO_RANGE_FULL, AV_PIX_FMT_XYZ12LE, false, false);
write_image(cropped, "build/test/crop_scale_window_test6.png");
@@ -313,7 +313,7 @@ BOOST_AUTO_TEST_CASE (crop_scale_window_test6)
BOOST_AUTO_TEST_CASE (as_png_test)
{
- shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/3d_test/000001.png"));
+ shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/3d_test/000001.png", VIDEO_RANGE_FULL));
shared_ptr<Image> image_rgb = proxy->image().image;
shared_ptr<Image> image_bgr = image_rgb->convert_pixel_format(dcp::YUV_TO_RGB_REC709, AV_PIX_FMT_BGRA, true, false);
image_rgb->as_png().write ("build/test/as_png_rgb.png");
@@ -339,7 +339,7 @@ fade_test_format_black (AVPixelFormat f, string name)
static void
fade_test_format_red (AVPixelFormat f, float amount, string name)
{
- shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/flat_red.png"));
+ shared_ptr<FFmpegImageProxy> proxy(new FFmpegImageProxy("test/data/flat_red.png", VIDEO_RANGE_FULL));
shared_ptr<Image> red = proxy->image().image->convert_pixel_format(dcp::YUV_TO_RGB_REC709, f, true, false);
red->fade (amount);
string const filename = "fade_test_red_" + name + ".png";
diff --git a/test/test.cc b/test/test.cc
index 9ac202b80..5981697fb 100644
--- a/test/test.cc
+++ b/test/test.cc
@@ -336,9 +336,9 @@ static
double
rms_error (boost::filesystem::path ref, boost::filesystem::path check)
{
- FFmpegImageProxy ref_proxy (ref);
+ FFmpegImageProxy ref_proxy (ref, VIDEO_RANGE_FULL);
shared_ptr<Image> ref_image = ref_proxy.image().image;
- FFmpegImageProxy check_proxy (check);
+ FFmpegImageProxy check_proxy (check, VIDEO_RANGE_FULL);
shared_ptr<Image> check_image = check_proxy.image().image;
BOOST_REQUIRE_EQUAL (ref_image->pixel_format(), check_image->pixel_format());
diff --git a/test/video_level_test.cc b/test/video_level_test.cc
new file mode 100644
index 000000000..2849910b1
--- /dev/null
+++ b/test/video_level_test.cc
@@ -0,0 +1,97 @@
+/*
+ Copyright (C) 2020 Carl Hetherington <cth@carlh.net>
+
+ This file is part of DCP-o-matic.
+
+ DCP-o-matic is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DCP-o-matic is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
+
+*/
+
+
+/** @file test/video_level_test.cc
+ * @brief Test that video level ranges are handled correctly.
+ * @ingroup specific
+ */
+
+
+#include "lib/ffmpeg_image_proxy.h"
+#include "lib/image.h"
+#include "test.h"
+#include <boost/test/unit_test.hpp>
+
+
+using boost::shared_ptr;
+
+
+static
+shared_ptr<Image>
+grey_image (dcp::Size size, uint8_t pixel)
+{
+ shared_ptr<Image> grey(new Image(AV_PIX_FMT_RGB24, size, true));
+ for (int y = 0; y < size.height; ++y) {
+ uint8_t* p = grey->data()[0] + y * grey->stride()[0];
+ for (int x = 0; x < size.width; ++x) {
+ *p++ = pixel;
+ *p++ = pixel;
+ *p++ = pixel;
+ }
+ }
+
+ return grey;
+}
+
+
+BOOST_AUTO_TEST_CASE (ffmpeg_image_full_range_not_changed)
+{
+ dcp::Size size(640, 480);
+ uint8_t const grey_pixel = 128;
+ boost::filesystem::path const file = "build/test/ffmpeg_image_full_range_not_changed.png";
+
+ write_image (grey_image(size, grey_pixel), file);
+
+ FFmpegImageProxy proxy (file, VIDEO_RANGE_FULL);
+ ImageProxy::Result result = proxy.image ();
+ BOOST_REQUIRE (!result.error);
+
+ for (int y = 0; y < size.height; ++y) {
+ uint8_t* p = result.image->data()[0] + y * result.image->stride()[0];
+ for (int x = 0; x < size.width; ++x) {
+ BOOST_REQUIRE (*p++ == grey_pixel);
+ }
+ }
+}
+
+
+BOOST_AUTO_TEST_CASE (ffmpeg_image_video_range_expanded)
+{
+ dcp::Size size(640, 480);
+ uint8_t const grey_pixel = 128;
+ uint8_t const expanded_grey_pixel = static_cast<uint8_t>((grey_pixel - 16) * 256.0 / 219);
+ boost::filesystem::path const file = "build/test/ffmpeg_image_video_range_expanded.png";
+
+ write_image (grey_image(size, grey_pixel), file);
+
+ FFmpegImageProxy proxy (file, VIDEO_RANGE_VIDEO);
+ ImageProxy::Result result = proxy.image ();
+ BOOST_REQUIRE (!result.error);
+
+ for (int y = 0; y < size.height; ++y) {
+ uint8_t* p = result.image->data()[0] + y * result.image->stride()[0];
+ for (int x = 0; x < size.width; ++x) {
+ BOOST_REQUIRE_EQUAL (*p++, expanded_grey_pixel);
+ }
+ }
+}
+
+
diff --git a/test/wscript b/test/wscript
index 50fe91b43..176f4ca67 100644
--- a/test/wscript
+++ b/test/wscript
@@ -130,6 +130,7 @@ def build(bld):
util_test.cc
vf_test.cc
video_content_scale_test.cc
+ video_level_test.cc
video_mxf_content_test.cc
vf_kdm_test.cc
zipper_test.cc