From 32b87c91cf51d730ff5abc8347884219918fad66 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Tue, 11 Feb 2020 18:15:24 +0100 Subject: [PATCH] Make Futatabi copy Exif data for interpolated frames. This means that interpolated frames also get the right white balance, so now only faded frames get none. --- futatabi/jpeg_frame.h | 2 ++ futatabi/jpeg_frame_view.cpp | 10 ++++++++++ futatabi/vaapi_jpeg_decoder.cpp | 10 ++++++++++ futatabi/video_stream.cpp | 15 +++++++++++---- futatabi/video_stream.h | 1 + 5 files changed, 34 insertions(+), 4 deletions(-) diff --git a/futatabi/jpeg_frame.h b/futatabi/jpeg_frame.h index edc7381..6fd0d4b 100644 --- a/futatabi/jpeg_frame.h +++ b/futatabi/jpeg_frame.h @@ -2,6 +2,7 @@ #define _JPEG_FRAME_H 1 #include +#include struct Frame { bool is_semiplanar = false; @@ -11,6 +12,7 @@ struct Frame { unsigned width, height; unsigned chroma_subsampling_x, chroma_subsampling_y; unsigned pitch_y, pitch_chroma; + std::string exif_data; }; #endif // !defined(_JPEG_FRAME_H) diff --git a/futatabi/jpeg_frame_view.cpp b/futatabi/jpeg_frame_view.cpp index 943b3e1..d471634 100644 --- a/futatabi/jpeg_frame_view.cpp +++ b/futatabi/jpeg_frame_view.cpp @@ -109,6 +109,8 @@ shared_ptr decode_jpeg(const string &jpeg) return get_black_frame(); } + jpeg_save_markers(&dinfo, JPEG_APP0 + 1, 0xFFFF); + if (dinfo.num_components != 3) { fprintf(stderr, "Not a color JPEG. (%d components, Y=%dx%d, Cb=%dx%d, Cr=%dx%d)\n", dinfo.num_components, @@ -159,6 +161,14 @@ shared_ptr decode_jpeg(const string &jpeg) frame->pitch_y = luma_width_blocks * DCTSIZE; frame->pitch_chroma = chroma_width_blocks * DCTSIZE; + if (dinfo.marker_list != nullptr && + dinfo.marker_list->marker == JPEG_APP0 + 1 && + dinfo.marker_list->data_length >= 4 && + memcmp(dinfo.marker_list->data, "Exif", 4) == 0) { + frame->exif_data.assign(reinterpret_cast(dinfo.marker_list->data), + dinfo.marker_list->data_length); + } + if (!error_mgr.run([&dinfo, &frame, v_mcu_size, mcu_height_blocks] { JSAMPROW yptr[v_mcu_size], cbptr[v_mcu_size], crptr[v_mcu_size]; JSAMPARRAY data[3] = { yptr, cbptr, crptr }; diff --git a/futatabi/vaapi_jpeg_decoder.cpp b/futatabi/vaapi_jpeg_decoder.cpp index d18a873..f34654d 100644 --- a/futatabi/vaapi_jpeg_decoder.cpp +++ b/futatabi/vaapi_jpeg_decoder.cpp @@ -337,6 +337,8 @@ shared_ptr decode_jpeg_vaapi(const string &jpeg) } JPEGDestroyer destroy_dinfo(&dinfo); + jpeg_save_markers(&dinfo, JPEG_APP0 + 1, 0xFFFF); + jpeg_mem_src(&dinfo, reinterpret_cast(jpeg.data()), jpeg.size()); if (!error_mgr.run([&dinfo] { jpeg_read_header(&dinfo, true); })) { return nullptr; @@ -566,6 +568,14 @@ shared_ptr decode_jpeg_vaapi(const string &jpeg) frame->pitch_y = dinfo.image_width; frame->pitch_chroma = dinfo.image_width / 2; + if (dinfo.marker_list != nullptr && + dinfo.marker_list->marker == JPEG_APP0 + 1 && + dinfo.marker_list->data_length >= 4 && + memcmp(dinfo.marker_list->data, "Exif", 4) == 0) { + frame->exif_data.assign(reinterpret_cast(dinfo.marker_list->data), + dinfo.marker_list->data_length); + } + va_status = vaUnmapBuffer(va_dpy->va_dpy, resources.image.buf); CHECK_VASTATUS_RET(va_status, "vaUnmapBuffer"); diff --git a/futatabi/video_stream.cpp b/futatabi/video_stream.cpp index 3cd56a0..9a120b5 100644 --- a/futatabi/video_stream.cpp +++ b/futatabi/video_stream.cpp @@ -79,7 +79,7 @@ struct VectorDestinationManager { }; static_assert(std::is_standard_layout::value, ""); -string encode_jpeg(const uint8_t *y_data, const uint8_t *cb_data, const uint8_t *cr_data, unsigned width, unsigned height) +string encode_jpeg(const uint8_t *y_data, const uint8_t *cb_data, const uint8_t *cr_data, unsigned width, unsigned height, const string exif_data) { VectorDestinationManager dest; @@ -112,6 +112,10 @@ string encode_jpeg(const uint8_t *y_data, const uint8_t *cb_data, const uint8_t // (and nothing else). jpeg_write_marker(&cinfo, JPEG_COM, (const JOCTET *)"CS=ITU601", strlen("CS=ITU601")); + if (!exif_data.empty()) { + jpeg_write_marker(&cinfo, JPEG_APP0 + 1, (const JOCTET *)exif_data.data(), exif_data.size()); + } + JSAMPROW yptr[8], cbptr[8], crptr[8]; JSAMPARRAY data[3] = { yptr, cbptr, crptr }; for (unsigned y = 0; y < height; y += 8) { @@ -234,7 +238,7 @@ VideoStream::VideoStream(AVFormatContext *file_avctx) unique_ptr cb_or_cr(new uint8_t[(global_flags.width / 2) * global_flags.height]); memset(y.get(), 16, global_flags.width * global_flags.height); memset(cb_or_cr.get(), 128, (global_flags.width / 2) * global_flags.height); - last_frame = encode_jpeg(y.get(), cb_or_cr.get(), cb_or_cr.get(), global_flags.width, global_flags.height); + last_frame = encode_jpeg(y.get(), cb_or_cr.get(), cb_or_cr.get(), global_flags.width, global_flags.height, /*exif_data=*/""); if (file_avctx != nullptr) { with_subtitles = Mux::WITHOUT_SUBTITLES; @@ -481,6 +485,9 @@ void VideoStream::schedule_interpolated_frame(steady_clock::time_point local_pts bool did_decode; shared_ptr frame = decode_jpeg_with_cache(frame_spec, DECODE_IF_NOT_IN_CACHE, &frame_reader, &did_decode); ycbcr_converter->prepare_chain_for_conversion(frame)->render_to_fbo(resources->input_fbos[frame_no], global_flags.width, global_flags.height); + if (frame_no == 1) { + qf.exif_data = frame->exif_data; // Use the white point from the last frame. + } } glGenerateTextureMipmap(resources->input_tex); @@ -705,7 +712,7 @@ void VideoStream::encode_thread_func() shared_ptr frame = frame_from_pbo(qf.resources->pbo_contents, global_flags.width, global_flags.height); // Now JPEG encode it, and send it on to the stream. - string jpeg = encode_jpeg(frame->y.get(), frame->cb.get(), frame->cr.get(), global_flags.width, global_flags.height); + string jpeg = encode_jpeg(frame->y.get(), frame->cb.get(), frame->cr.get(), global_flags.width, global_flags.height, move(frame->exif_data)); AVPacket pkt; av_init_packet(&pkt); @@ -727,7 +734,7 @@ void VideoStream::encode_thread_func() } // Now JPEG encode it, and send it on to the stream. - string jpeg = encode_jpeg(frame->y.get(), frame->cb.get(), frame->cr.get(), global_flags.width, global_flags.height); + string jpeg = encode_jpeg(frame->y.get(), frame->cb.get(), frame->cr.get(), global_flags.width, global_flags.height, move(qf.exif_data)); if (qf.flow_tex != 0) { compute_flow->release_texture(qf.flow_tex); } diff --git a/futatabi/video_stream.h b/futatabi/video_stream.h index 12e8ed1..a6215e9 100644 --- a/futatabi/video_stream.h +++ b/futatabi/video_stream.h @@ -142,6 +142,7 @@ private: std::function)> display_decoded_func; // Same, except for INTERPOLATED and FADED_INTERPOLATED. std::string subtitle; // Blank for none. + std::string exif_data; // Blank for none. // Audio, in stereo interleaved 32-bit PCM. If empty and not of type SILENCE, one frame's worth of silence samples // is synthesized. -- 2.39.2