Support MPEG2 compression. v1.9.6
authorCarl Hetherington <cth@carlh.net>
Tue, 19 Mar 2024 16:44:10 +0000 (17:44 +0100)
committerCarl Hetherington <cth@carlh.net>
Sun, 21 Apr 2024 23:42:22 +0000 (01:42 +0200)
24 files changed:
cscript
src/exceptions.h
src/ffmpeg_image.cc [new file with mode: 0644]
src/ffmpeg_image.h
src/frame_info.h
src/j2k_picture_asset_writer_common.cc
src/mono_j2k_picture_asset_writer.cc
src/mono_mpeg2_picture_asset.cc
src/mono_mpeg2_picture_asset.h
src/mono_mpeg2_picture_asset_writer.cc [new file with mode: 0644]
src/mono_mpeg2_picture_asset_writer.h [new file with mode: 0644]
src/mono_mpeg2_picture_frame.cc
src/mono_mpeg2_picture_frame.h
src/mpeg2_picture_asset.h
src/mpeg2_picture_asset_writer.cc [new file with mode: 0644]
src/mpeg2_picture_asset_writer.h [new file with mode: 0644]
src/mpeg2_picture_asset_writer_common.cc [new file with mode: 0644]
src/mpeg2_transcode.cc
src/mpeg2_transcode.h
src/mxf.h
src/stereo_j2k_picture_asset_writer.cc
src/wscript
test/mono_mpeg2_picture_write_test.cc [new file with mode: 0644]
test/wscript

diff --git a/cscript b/cscript
index d9ee7cf7c2e69e24d0d8e74c56439b05938486cd..495bec31b823e3d99a7e49f5f7bdcb68692a0dbc 100644 (file)
--- a/cscript
+++ b/cscript
@@ -38,7 +38,7 @@ def dependencies(target, options):
     deps = [
         ('libcxml', 'v0.17.9', options),
         ('openjpeg', 'ad8edaacd54a862940d0a77c41ecda5858b54d6e'),
-        ('asdcplib', '4b5d6e8d27dfd5fb282590538068662f4dbbf1c9')
+        ('asdcplib', 'v1.0.1')
     ]
 
     if target.platform == 'linux':
index 88ec253c36595e9fa0e65553f208031ef14863a5..3858b763899372cca1be1d948aed59a014a6c3c7 100644 (file)
@@ -158,6 +158,15 @@ public:
 };
 
 
+class MPEG2CompressionError : public MiscError
+{
+public:
+       explicit MPEG2CompressionError(std::string message)
+               : MiscError(message)
+       {}
+};
+
+
 class BadContentKindError : public ReadError
 {
 public:
diff --git a/src/ffmpeg_image.cc b/src/ffmpeg_image.cc
new file mode 100644 (file)
index 0000000..deebe1b
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+    Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp 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.
+
+    libdcp 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 libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#include "ffmpeg_image.h"
+#include "types.h"
+extern "C" {
+#include <libavutil/pixfmt.h>
+}
+
+
+using namespace dcp;
+
+
+FFmpegImage::FFmpegImage(int64_t pts)
+{
+       auto const width = size().width;
+       auto const height = size().height;
+
+       _frame = av_frame_alloc();
+       if (!_frame) {
+               throw std::bad_alloc();
+       }
+
+       _frame->buf[0] = av_buffer_alloc(width * height);
+       _frame->buf[1] = av_buffer_alloc(width * height / 4);
+       _frame->buf[2] = av_buffer_alloc(width * height / 4);
+
+       _frame->linesize[0] = width;
+       _frame->linesize[1] = width / 2;
+       _frame->linesize[2] = width / 2;
+
+       for (auto i = 0; i < 3; ++i) {
+               _frame->data[i] = _frame->buf[i]->data;
+       }
+
+       _frame->width = width;
+       _frame->height = height;
+       _frame->format = AV_PIX_FMT_YUV420P;
+       _frame->pts = pts;
+}
+
+
+void
+FFmpegImage::set_pts(int64_t pts)
+{
+       _frame->pts = pts;
+}
+
+
+uint8_t*
+FFmpegImage::y()
+{
+       return _frame->data[0];
+}
+
+
+int
+FFmpegImage::y_stride() const
+{
+       return _frame->linesize[0];
+}
+
+
+uint8_t*
+FFmpegImage::u()
+{
+       return _frame->data[1];
+}
+
+
+int
+FFmpegImage::u_stride() const
+{
+       return _frame->linesize[1];
+}
+
+
+uint8_t*
+FFmpegImage::v()
+{
+       return _frame->data[2];
+}
+
+
+int
+FFmpegImage::v_stride() const
+{
+       return _frame->linesize[2];
+}
+
+
index 56b68574406cc9b379e668b4e30a9bc993b5d469..0850b70178cffe58723215b74eb8936e05a74d13 100644 (file)
@@ -36,6 +36,7 @@
 #define LIBDCP_FFMPEG_IMAGE_H
 
 
+#include "types.h"
 extern "C" {
 #include <libavutil/frame.h>
 }
@@ -49,6 +50,8 @@ namespace dcp {
 class FFmpegImage
 {
 public:
+       explicit FFmpegImage(int64_t pts);
+
        explicit FFmpegImage(AVFrame* frame)
                : _frame(frame)
        {}
@@ -74,6 +77,21 @@ public:
                return _frame;
        }
 
+       uint8_t* y();
+       int y_stride() const;
+
+       uint8_t* u();
+       int u_stride() const;
+
+       uint8_t* v();
+       int v_stride() const;
+
+       Size size() const {
+               return { 1920, 1080 };
+       }
+
+       void set_pts(int64_t pts);
+
 private:
        AVFrame* _frame = nullptr;
 };
index a49f52a912c8f06ba6f67214c47b4cb430e270a2..325350c49cb0cf99bc4b7cf0a5e25a139b8a22a0 100644 (file)
 #define LIBDCP_FRAME_INFO_H
 
 
+#include "warnings.h"
+LIBDCP_DISABLE_WARNINGS
+#include <asdcp/AS_DCP.h>
+LIBDCP_ENABLE_WARNINGS
 #include <stdint.h>
 #include <string>
 
@@ -72,6 +76,33 @@ struct J2KFrameInfo : public FrameInfo
 };
 
 
+struct MPEG2FrameInfo : public FrameInfo
+{
+       MPEG2FrameInfo() = default;
+
+       MPEG2FrameInfo(
+               uint64_t offset_,
+               uint64_t size_,
+               std::string hash_,
+               ASDCP::MPEG2::FrameType_t type_,
+               bool gop_start_,
+               bool closed_gop_,
+               uint8_t temporal_offset_
+               )
+               : FrameInfo(offset_, size_, hash_)
+               , type(type_)
+               , gop_start(gop_start_)
+               , closed_gop(closed_gop_)
+               , temporal_offset(temporal_offset_)
+       {}
+
+       ASDCP::MPEG2::FrameType_t type;
+       bool gop_start;
+       bool closed_gop;
+       uint8_t temporal_offset;
+};
+
+
 }
 
 
index 6ccc63be8131cf0500ef2391df1e37441b702e12..a86da19485be36a98f1695a1b40c4b562beba56c 100644 (file)
@@ -46,9 +46,9 @@ using std::shared_ptr;
 namespace dcp {
 
 
-struct ASDCPStateBase
+struct ASDCPJ2KStateBase
 {
-       ASDCPStateBase ()
+       ASDCPJ2KStateBase()
                : frame_buffer (4 * Kumu::Megabyte)
        {}
 
index 08639955cc3a326d98b201023e19ca378ad944ef..1188701e641a05507785bff67f578c61df6e5c66 100644 (file)
@@ -57,7 +57,7 @@ using std::shared_ptr;
 using namespace dcp;
 
 
-struct MonoJ2KPictureAssetWriter::ASDCPState : public ASDCPStateBase
+struct MonoJ2KPictureAssetWriter::ASDCPState : public ASDCPJ2KStateBase
 {
        ASDCP::JP2K::MXFWriter mxf_writer;
 };
index 548e936a0b538776e1361f06a025d75d4ec26936..380da0fe29160164f8b929c23acb2f406be73cbc 100644 (file)
@@ -35,6 +35,7 @@
 #include "filesystem.h"
 #include "mono_mpeg2_picture_asset.h"
 #include "mono_mpeg2_picture_asset_reader.h"
+#include "mono_mpeg2_picture_asset_writer.h"
 #include <asdcp/AS_DCP.h>
 
 
@@ -75,3 +76,11 @@ MonoMPEG2PictureAsset::start_read () const
        return shared_ptr<MonoMPEG2PictureAssetReader>(new MonoMPEG2PictureAssetReader(this, key(), standard()));
 
 }
+
+
+shared_ptr<MPEG2PictureAssetWriter>
+MonoMPEG2PictureAsset::start_write(boost::filesystem::path file, Behaviour behaviour)
+{
+       /* Can't use make_shared here as the MonoJ2KPictureAssetWriter constructor is private */
+       return shared_ptr<MonoMPEG2PictureAssetWriter>(new MonoMPEG2PictureAssetWriter(this, file, behaviour == Behaviour::OVERWRITE_EXISTING));
+}
index a740cc6ce1ea9543f02999b01bdca880336e2b4e..8ef3653e45e5822fb0175deb6ce6c8c72a45b755 100644 (file)
@@ -41,6 +41,7 @@
  */
 
 
+#include "behaviour.h"
 #include "mpeg2_picture_asset.h"
 #include "mono_mpeg2_picture_asset_reader.h"
 
 namespace dcp {
 
 
+class MonoMPEG2PictureAssetWriter;
+
+
 class MonoMPEG2PictureAsset : public MPEG2PictureAsset
 {
 public:
+       MonoMPEG2PictureAsset(Fraction edit_rate)
+               : MPEG2PictureAsset(edit_rate)
+       {}
+
        explicit MonoMPEG2PictureAsset(boost::filesystem::path file);
 
+       std::shared_ptr<MPEG2PictureAssetWriter> start_write(boost::filesystem::path file, Behaviour behaviour) override;
        std::shared_ptr<MonoMPEG2PictureAssetReader> start_read() const;
 };
 
diff --git a/src/mono_mpeg2_picture_asset_writer.cc b/src/mono_mpeg2_picture_asset_writer.cc
new file mode 100644 (file)
index 0000000..f8101c5
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+    Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp 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.
+
+    libdcp 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 libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#include "mono_mpeg2_picture_asset_writer.h"
+#include "mpeg2_picture_asset.h"
+#include "mpeg2_picture_asset_writer.h"
+
+
+using std::string;
+using namespace dcp;
+
+
+struct MonoMPEG2PictureAssetWriter::ASDCPState : public ASDCPMPEG2StateBase
+{
+       ASDCP::MPEG2::MXFWriter mxf_writer;
+};
+
+
+
+
+MonoMPEG2PictureAssetWriter::MonoMPEG2PictureAssetWriter(MPEG2PictureAsset* asset, boost::filesystem::path file, bool overwrite)
+       : MPEG2PictureAssetWriter(asset, file, overwrite)
+       , _state(new MonoMPEG2PictureAssetWriter::ASDCPState)
+{
+       asset->set_file(file);
+}
+
+
+MonoMPEG2PictureAssetWriter::~MonoMPEG2PictureAssetWriter()
+{
+       try {
+               /* Last-resort finalization to close the file, at least */
+               if (!_finalized) {
+                       _state->mxf_writer.Finalize();
+               }
+       } catch (...) {}
+}
+
+
+void
+MonoMPEG2PictureAssetWriter::start(uint8_t const * data, int size)
+{
+       dcp::start(this, _state, _picture_asset, data, size);
+       _picture_asset->set_frame_rate (_picture_asset->edit_rate());
+}
+
+
+MPEG2FrameInfo
+MonoMPEG2PictureAssetWriter::write(uint8_t const * data, int size)
+{
+       DCP_ASSERT(!_finalized);
+
+       if (!_started) {
+               start(data, size);
+       }
+
+       ASDCP::MPEG2::FrameBuffer buffer;
+       buffer.SetData(const_cast<uint8_t*>(data), size);
+       buffer.Size(size);
+       buffer.PlaintextOffset(0);
+
+       auto const before_offset = _state->mxf_writer.Tell();
+
+       string hash;
+       auto const r = _state->mxf_writer.WriteFrame(buffer, _crypto_context->context(), _crypto_context->hmac(), &hash);
+       if (ASDCP_FAILURE(r)) {
+               boost::throw_exception(MXFFileError("error in writing video MXF", _file.string(), r));
+       }
+
+       ++_frames_written;
+       return MPEG2FrameInfo(
+               before_offset,
+               _state->mxf_writer.Tell() - before_offset,
+               hash,
+               buffer.FrameType(),
+               buffer.GOPStart(),
+               buffer.ClosedGOP(),
+               buffer.TemporalOffset()
+               );
+}
+
+
+void
+MonoMPEG2PictureAssetWriter::fake_write(MPEG2FrameInfo const& info)
+{
+       DCP_ASSERT(_started);
+       DCP_ASSERT(!_finalized);
+
+       DCP_ASSERT(false);
+
+       auto r = _state->mxf_writer.FakeWriteFrame(info.size, info.type, info.gop_start, info.closed_gop, info.temporal_offset);
+       if (ASDCP_FAILURE(r)) {
+               boost::throw_exception(MXFFileError("error in writing video MXF", _file.string(), r));
+       }
+
+       ++_frames_written;
+}
+
+
+bool
+MonoMPEG2PictureAssetWriter::finalize()
+{
+       if (_started) {
+               auto r = _state->mxf_writer.Finalize();
+               if (ASDCP_FAILURE(r)) {
+                       boost::throw_exception(MXFFileError("error in finalizing video MXF", _file.string(), r));
+               }
+       }
+
+       _picture_asset->_intrinsic_duration = _frames_written;
+       return MPEG2PictureAssetWriter::finalize();
+}
+
diff --git a/src/mono_mpeg2_picture_asset_writer.h b/src/mono_mpeg2_picture_asset_writer.h
new file mode 100644 (file)
index 0000000..bf7a6cc
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+    Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp 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.
+
+    libdcp 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 libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#include "mpeg2_picture_asset_writer.h"
+
+
+#include "mpeg2_picture_asset_writer_common.cc"
+
+
+namespace dcp {
+
+
+class MonoMPEG2PictureAsset;
+
+
+class MonoMPEG2PictureAssetWriter : public MPEG2PictureAssetWriter
+{
+public:
+       ~MonoMPEG2PictureAssetWriter();
+
+       MPEG2FrameInfo write(uint8_t const *, int) override;
+       void fake_write(MPEG2FrameInfo const& info) override;
+       bool finalize() override;
+
+private:
+       friend class MonoMPEG2PictureAsset;
+
+       MonoMPEG2PictureAssetWriter(MPEG2PictureAsset* asset, boost::filesystem::path file, bool overwrite);
+
+       void start(uint8_t const *, int);
+
+       struct ASDCPState;
+       std::shared_ptr<ASDCPState> _state;
+};
+
+
+}
index 3da67212bbb447de39c0bdba1f7b37df6e49ee26..3c79a94c6c93bdc84a4b2ebd0963a104b2e2ac70 100644 (file)
@@ -42,6 +42,14 @@ using namespace dcp;
 
 
 
+MonoMPEG2PictureFrame::MonoMPEG2PictureFrame(uint8_t const* data, int size)
+{
+       _buffer = make_shared<ASDCP::MPEG2::FrameBuffer>(size);
+       memcpy(_buffer->Data(), data, size);
+       _buffer->Size(size);
+}
+
+
 /** Make a picture frame from a 2D (monoscopic) asset.
  *  @param reader Reader for the asset's MXF file.
  *  @param n Frame within the asset, not taking EntryPoint into account.
index 13853a4ebc99f37ae3a5a8482bdd70136164c8ad..6a7669f78fa065046044035f40106c188663ca73 100644 (file)
@@ -46,9 +46,13 @@ namespace dcp {
 class MonoMPEG2PictureFrame : public Data
 {
 public:
+       MonoMPEG2PictureFrame(uint8_t const * data, int size);
+
        MonoMPEG2PictureFrame(MonoMPEG2PictureFrame const&) = delete;
        MonoMPEG2PictureFrame& operator=(MonoMPEG2PictureFrame const&) = delete;
 
+       /* XXX: couldn't we just return the frame buffer */
+
        /** @return Pointer to MPEG2 data */
        uint8_t const * data() const override;
 
@@ -66,6 +70,7 @@ private:
 
        MonoMPEG2PictureFrame(ASDCP::MPEG2::MXFReader* reader, int n, std::shared_ptr<DecryptionContext>, bool check_hmac);
 
+       /* XXX why is this a shared_ptr? */
        std::shared_ptr<ASDCP::MPEG2::FrameBuffer> _buffer;
 };
 
index df8822026772098f380dbe15479a5d6fdb599766..38b8e0a03b6b01c509b050da270c2251f47c7c2a 100644 (file)
@@ -41,6 +41,8 @@
  */
 
 
+#include "behaviour.h"
+#include "mpeg2_picture_asset_writer.h"
 #include "picture_asset.h"
 #include <boost/filesystem/path.hpp>
 
@@ -58,11 +60,22 @@ namespace dcp {
 class MPEG2PictureAsset : public PictureAsset
 {
 public:
+       MPEG2PictureAsset(Fraction edit_rate)
+               : PictureAsset(edit_rate, Standard::INTEROP)
+       {}
+
        explicit MPEG2PictureAsset(boost::filesystem::path file);
 
+       virtual std::shared_ptr<MPEG2PictureAssetWriter> start_write(
+               boost::filesystem::path file,
+               Behaviour behaviour
+               ) = 0;
+
        static std::string static_pkl_type(Standard standard);
 
 protected:
+       friend class MonoMPEG2PictureAssetWriter;
+
        void read_video_descriptor(ASDCP::MPEG2::VideoDescriptor const& descriptor);
 
 private:
diff --git a/src/mpeg2_picture_asset_writer.cc b/src/mpeg2_picture_asset_writer.cc
new file mode 100644 (file)
index 0000000..f5ead77
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+    Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp 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.
+
+    libdcp 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 libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#include "mpeg2_picture_asset.h"
+#include "mpeg2_picture_asset_writer.h"
+
+
+using namespace dcp;
+
+
+MPEG2PictureAssetWriter::MPEG2PictureAssetWriter(MPEG2PictureAsset* asset, boost::filesystem::path file, bool overwrite)
+       : AssetWriter(asset, file)
+       , _picture_asset(asset)
+       , _overwrite(overwrite)
+{
+       asset->set_file(file);
+}
diff --git a/src/mpeg2_picture_asset_writer.h b/src/mpeg2_picture_asset_writer.h
new file mode 100644 (file)
index 0000000..a370ef9
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+    Copyright (C) 2024 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp 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.
+
+    libdcp 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 libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#ifndef LIBDCP_MPEG2_PICTURE_ASSET_WRITER_H
+#define LIBDCP_MPEG2_PICTURE_ASSET_WRITER_H
+
+
+#include "asset_writer.h"
+#include "frame_info.h"
+
+
+namespace dcp {
+
+
+class Data;
+class MPEG2PictureAsset;
+
+
+class MPEG2PictureAssetWriter : public AssetWriter
+{
+public:
+       virtual MPEG2FrameInfo write(uint8_t const* data, int size) = 0;
+       virtual void fake_write(MPEG2FrameInfo const& info) = 0;
+
+       MPEG2FrameInfo write(Data const& data);
+
+protected:
+       template <class P, class Q>
+       friend void start(MPEG2PictureAssetWriter *, std::shared_ptr<P>, Q *, uint8_t const *, int);
+
+       MPEG2PictureAssetWriter(MPEG2PictureAsset* asset, boost::filesystem::path file, bool overwrite);
+
+       MPEG2PictureAsset* _picture_asset = nullptr;
+       bool _overwrite = false;
+};
+
+
+}
+
+
+#endif
+
diff --git a/src/mpeg2_picture_asset_writer_common.cc b/src/mpeg2_picture_asset_writer_common.cc
new file mode 100644 (file)
index 0000000..d46057f
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+    Copyright (C) 2012-2021 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp 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.
+
+    libdcp 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 libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+/** @file  src/mpeg2_picture_asset_writer_common.cc
+ *  @brief Common parts of MPEG2PictureAssetWriter
+ */
+
+
+#include "filesystem.h"
+
+
+using std::shared_ptr;
+
+
+namespace dcp {
+
+
+class MPEG2PictureAssetWriter;
+
+
+struct ASDCPMPEG2StateBase
+{
+       ASDCP::MPEG2::Parser mpeg2_parser;
+       ASDCP::WriterInfo writer_info;
+       ASDCP::MPEG2::VideoDescriptor video_descriptor;
+};
+
+
+}
+
+
+template <class P, class Q>
+void dcp::start(MPEG2PictureAssetWriter* writer, shared_ptr<P> state, Q* asset, uint8_t const * data, int size)
+{
+       asset->set_file (writer->_file);
+
+       if (ASDCP_FAILURE(state->mpeg2_parser.OpenRead(data, size))) {
+               boost::throw_exception(MiscError("could not parse MPEG2 frame"));
+       }
+
+       state->mpeg2_parser.FillVideoDescriptor(state->video_descriptor);
+       state->video_descriptor.EditRate = ASDCP::Rational(asset->edit_rate().numerator, asset->edit_rate().denominator);
+
+       asset->set_size(Size(state->video_descriptor.StoredWidth, state->video_descriptor.StoredHeight));
+       asset->set_screen_aspect_ratio(Fraction(state->video_descriptor.AspectRatio.Numerator, state->video_descriptor.AspectRatio.Denominator));
+
+       asset->fill_writer_info(&state->writer_info, asset->id());
+
+       auto r = state->mxf_writer.OpenWrite(
+               dcp::filesystem::fix_long_path(*asset->file()).string().c_str(),
+               state->writer_info,
+               state->video_descriptor,
+               16384,
+               writer->_overwrite
+               );
+
+       if (ASDCP_FAILURE(r)) {
+               boost::throw_exception(MXFFileError("could not open MXF file for writing", asset->file()->string(), r));
+       }
+
+       writer->_started = true;
+}
index ab77f0e13c6fc2764211e55d09bb55cc2a91b5ae..0ac2c1af0a92073848813d2aef2b15c93ff48fe5 100644 (file)
 #include "exceptions.h"
 #include "mono_mpeg2_picture_frame.h"
 #include "mpeg2_transcode.h"
+#include "scope_guard.h"
 extern "C" {
 #include <libavcodec/avcodec.h>
 }
 
 
+using std::make_shared;
 using std::shared_ptr;
 using std::vector;
+using boost::optional;
 using namespace dcp;
 
 
@@ -141,3 +144,73 @@ MPEG2Decompressor::decompress_packet(AVPacket* packet)
        return images;
 }
 
+
+MPEG2Compressor::MPEG2Compressor(dcp::Size size, int video_frame_rate, int64_t bit_rate)
+{
+       _codec = avcodec_find_encoder_by_name("mpeg2video");
+       if (!_codec) {
+               throw MPEG2CodecError("could not find codec");
+       }
+
+       _context = avcodec_alloc_context3(_codec);
+       if (!_context) {
+               throw MPEG2CodecError("could not allocate codec context");
+       }
+
+       _context->width = size.width;
+       _context->height = size.height;
+       _context->time_base = AVRational{1, video_frame_rate};
+       _context->pix_fmt = AV_PIX_FMT_YUV420P;
+       _context->bit_rate = bit_rate;
+
+       int const r = avcodec_open2(_context, _codec, nullptr);
+       if (r < 0) {
+               avcodec_free_context(&_context);
+               throw MPEG2CodecError("could not open codec");
+       }
+}
+
+
+optional<MPEG2Compressor::IndexedFrame>
+MPEG2Compressor::send_and_receive(AVFrame const* frame)
+{
+       int r = avcodec_send_frame(_context, frame);
+       if (r < 0) {
+               throw MPEG2CompressionError(String::compose("avcodec_send_frame failed (%1", r));
+       }
+
+       auto packet = av_packet_alloc();
+       if (!packet) {
+               throw MPEG2CompressionError("could not allocate packet");
+       }
+
+       r = avcodec_receive_packet(_context, packet);
+       if (r < 0 && r != AVERROR(EAGAIN)) {
+               throw MPEG2CompressionError(String::compose("avcodec_receive_packet failed (%1)", r));
+       }
+
+       ScopeGuard sg = [&packet]() {
+               av_packet_free(&packet);
+       };
+
+       if (packet->size == 0) {
+               return {};
+       }
+
+       DCP_ASSERT(_context->time_base.num == 1);
+       return IndexedFrame{make_shared<MonoMPEG2PictureFrame>(packet->data, packet->size), std::round(static_cast<double>(packet->pts) / _context->time_base.den)};
+}
+
+
+optional<MPEG2Compressor::IndexedFrame>
+MPEG2Compressor::compress_frame(FFmpegImage const& image)
+{
+       return send_and_receive(image.frame());
+}
+
+
+optional<MPEG2Compressor::IndexedFrame>
+MPEG2Compressor::flush()
+{
+       return send_and_receive(nullptr);
+}
index ef5c83513985b904ed93ec3dfb2083c973976332..1f13cfc34177b034aab6313b6f694f739bc9d088 100644 (file)
@@ -83,6 +83,25 @@ private:
 };
 
 
+class MPEG2Compressor : public MPEG2Codec
+{
+public:
+       MPEG2Compressor(dcp::Size size, int video_frame_rate, int64_t bit_rate);
+
+       MPEG2Compressor(MPEG2Compressor const&) = delete;
+       MPEG2Compressor& operator=(MPEG2Compressor const&) = delete;
+
+       /** Frame data with frame index within the asset */
+       typedef std::pair<std::shared_ptr<MonoMPEG2PictureFrame>, int64_t> IndexedFrame;
+
+       boost::optional<IndexedFrame> compress_frame(FFmpegImage const& image);
+       boost::optional<IndexedFrame> flush();
+
+private:
+       boost::optional<IndexedFrame> send_and_receive(AVFrame const* frame);
+};
+
+
 }
 
 
index 68ae561f31572b88f21117db5975212b57c6c5e9..1692fbd03a499c981f1b87964cb8bee4fa513b06 100644 (file)
--- a/src/mxf.h
+++ b/src/mxf.h
@@ -65,6 +65,7 @@ namespace dcp
 
 class MXFMetadata;
 class J2KPictureAssetWriter;
+class MPEG2PictureAssetWriter;
 
 
 /** @class MXF
@@ -137,6 +138,8 @@ public:
 protected:
        template <class P, class Q>
        friend void start (J2KPictureAssetWriter* writer, std::shared_ptr<P> state, Q* mxf, uint8_t const * data, int size);
+       template <class P, class Q>
+       friend void start (MPEG2PictureAssetWriter* writer, std::shared_ptr<P> state, Q* mxf, uint8_t const * data, int size);
 
        MXF ();
 
index 8a655fbcb9f0586200f84311134794050e5600aa..e59de02f17e0e458f349bfec54539d3f8f31a473 100644 (file)
@@ -54,7 +54,7 @@ using std::shared_ptr;
 using namespace dcp;
 
 
-struct StereoJ2KPictureAssetWriter::ASDCPState : public ASDCPStateBase
+struct StereoJ2KPictureAssetWriter::ASDCPState : public ASDCPJ2KStateBase
 {
        ASDCP::JP2K::MXFSWriter mxf_writer;
 };
index 47b716cfffe7529f1457f29888a37fdca1920890..1793aa723a8740a72566300abac6578ced893ce9 100644 (file)
@@ -58,6 +58,7 @@ def build(bld):
              exceptions.cc
              file.cc
              filesystem.cc
+             ffmpeg_image.cc
              font_asset.cc
              fsk.cc
              gamma_transfer_function.cc
@@ -78,8 +79,10 @@ def build(bld):
              mono_j2k_picture_asset_writer.cc
              mono_j2k_picture_frame.cc
              mono_mpeg2_picture_asset.cc
+             mono_mpeg2_picture_asset_writer.cc
              mono_mpeg2_picture_frame.cc
              mpeg2_picture_asset.cc
+             mpeg2_picture_asset_writer.cc
              mpeg2_transcode.cc
              mxf.cc
              name_format.cc
@@ -185,6 +188,7 @@ def build(bld):
               local_time.h
               locale_convert.h
               metadata.h
+              mpeg2_picture_asset_writer.h
               modified_gamma_transfer_function.h
               mono_j2k_picture_asset.h
               mono_j2k_picture_asset_reader.h
@@ -192,6 +196,7 @@ def build(bld):
               mono_j2k_picture_frame.h
               mono_mpeg2_picture_asset.h
               mono_mpeg2_picture_asset_reader.h
+              mono_mpeg2_picture_asset_writer.h
               mono_mpeg2_picture_frame.h
               mpeg2_picture_asset.h
               mpeg2_transcode.h
diff --git a/test/mono_mpeg2_picture_write_test.cc b/test/mono_mpeg2_picture_write_test.cc
new file mode 100644 (file)
index 0000000..44ce5ae
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+    Copyright (C) 2023 Carl Hetherington <cth@carlh.net>
+
+    This file is part of libdcp.
+
+    libdcp 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.
+
+    libdcp 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 libdcp.  If not, see <http://www.gnu.org/licenses/>.
+
+    In addition, as a special exception, the copyright holders give
+    permission to link the code of portions of this program with the
+    OpenSSL library under certain conditions as described in each
+    individual source file, and distribute linked combinations
+    including the two.
+
+    You must obey the GNU General Public License in all respects
+    for all of the code used other than OpenSSL.  If you modify
+    file(s) with this exception, you may extend this exception to your
+    version of the file(s), but you are not obligated to do so.  If you
+    do not wish to do so, delete this exception statement from your
+    version.  If you delete this exception statement from all source
+    files in the program, then also delete it here.
+*/
+
+
+#include "mono_mpeg2_picture_asset.h"
+#include "mpeg2_transcode.h"
+#include "test.h"
+#include <boost/test/unit_test.hpp>
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+}
+
+
+BOOST_AUTO_TEST_CASE(mpeg_mono_picture_write_test)
+{
+       boost::filesystem::path dir = "build/test/mpeg2_mono_picture_write_test";
+
+       boost::system::error_code ec;
+       boost::filesystem::remove_all(dir);
+       boost::filesystem::create_directories(dir);
+
+       dcp::MonoMPEG2PictureAsset asset(dcp::Fraction{24, 1});
+       auto writer = asset.start_write(dir / "test.mxf", dcp::Behaviour::MAKE_NEW);
+
+       dcp::MPEG2Compressor compressor({1920, 1080}, 24, 50000000);
+       dcp::FFmpegImage image(int64_t(0));
+       for (auto y = 0; y < 1080; ++y) {
+               uint8_t* py = image.y() + y * image.y_stride();
+               for (auto x = 0; x < 1920; ++x) {
+                       if (x < 640) {
+                               *py++ = 76;
+                       } else if (x < 1280) {
+                               *py++ = 149;
+                       } else {
+                               *py++ = 29;
+                       }
+               }
+       }
+
+       for (auto y = 0; y < 540; ++y) {
+               uint8_t* pu = image.u() + y * image.u_stride();
+               uint8_t* pv = image.v() + y * image.v_stride();
+               for (auto x = 0; x < 960; ++x) {
+                       if (x < 320) {
+                               *pu++ = 84;
+                               *pv++ = 255;
+                       } else if (x < 640) {
+                               *pu++ = 43;
+                               *pv++ = 21;
+                       } else {
+                               *pu++ = 255;
+                               *pv++ = 107;
+                       }
+               }
+       }
+
+       for (auto i = 0; i < 24; ++i) {
+               image.set_pts(int64_t(i));
+               if (auto compressed = compressor.compress_frame(image)) {
+                       writer->write(compressed->first->data(), compressed->first->size());
+               }
+       }
+
+       if (auto compressed = compressor.flush()) {
+               writer->write(compressed->first->data(), compressed->first->size());
+       }
+
+       writer->finalize();
+}
+
index ee5b754b6998fbda500167bad218706c5f37e7fd..64b3ed598765db979064c17da7b0330245c7420f 100644 (file)
@@ -95,6 +95,7 @@ def build(bld):
                  markers_test.cc
                  mca_test.cc
                  mono_mpeg2_picture_read_test.cc
+                 mono_mpeg2_picture_write_test.cc
                  kdm_test.cc
                  key_test.cc
                  language_tag_test.cc