diff options
| author | Carl Hetherington <cth@carlh.net> | 2024-03-19 17:44:10 +0100 |
|---|---|---|
| committer | Carl Hetherington <cth@carlh.net> | 2024-04-22 01:42:22 +0200 |
| commit | cca70e0824e6883f50838578897792476953ea24 (patch) | |
| tree | ab7c32822e94fe08d1bf03d6878c66cc95070720 | |
| parent | 066471f69400fc72e9c126ab36f5427329f30220 (diff) | |
Support MPEG2 compression.v1.9.6
| -rw-r--r-- | cscript | 2 | ||||
| -rw-r--r-- | src/exceptions.h | 9 | ||||
| -rw-r--r-- | src/ffmpeg_image.cc | 122 | ||||
| -rw-r--r-- | src/ffmpeg_image.h | 18 | ||||
| -rw-r--r-- | src/frame_info.h | 31 | ||||
| -rw-r--r-- | src/j2k_picture_asset_writer_common.cc | 4 | ||||
| -rw-r--r-- | src/mono_j2k_picture_asset_writer.cc | 2 | ||||
| -rw-r--r-- | src/mono_mpeg2_picture_asset.cc | 9 | ||||
| -rw-r--r-- | src/mono_mpeg2_picture_asset.h | 9 | ||||
| -rw-r--r-- | src/mono_mpeg2_picture_asset_writer.cc | 144 | ||||
| -rw-r--r-- | src/mono_mpeg2_picture_asset_writer.h | 68 | ||||
| -rw-r--r-- | src/mono_mpeg2_picture_frame.cc | 8 | ||||
| -rw-r--r-- | src/mono_mpeg2_picture_frame.h | 5 | ||||
| -rw-r--r-- | src/mpeg2_picture_asset.h | 13 | ||||
| -rw-r--r-- | src/mpeg2_picture_asset_writer.cc | 48 | ||||
| -rw-r--r-- | src/mpeg2_picture_asset_writer.h | 73 | ||||
| -rw-r--r-- | src/mpeg2_picture_asset_writer_common.cc | 93 | ||||
| -rw-r--r-- | src/mpeg2_transcode.cc | 73 | ||||
| -rw-r--r-- | src/mpeg2_transcode.h | 19 | ||||
| -rw-r--r-- | src/mxf.h | 3 | ||||
| -rw-r--r-- | src/stereo_j2k_picture_asset_writer.cc | 2 | ||||
| -rw-r--r-- | src/wscript | 5 | ||||
| -rw-r--r-- | test/mono_mpeg2_picture_write_test.cc | 101 | ||||
| -rw-r--r-- | test/wscript | 1 |
24 files changed, 857 insertions, 5 deletions
@@ -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': diff --git a/src/exceptions.h b/src/exceptions.h index 88ec253c..3858b763 100644 --- a/src/exceptions.h +++ b/src/exceptions.h @@ -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 index 00000000..deebe1b6 --- /dev/null +++ b/src/ffmpeg_image.cc @@ -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]; +} + + diff --git a/src/ffmpeg_image.h b/src/ffmpeg_image.h index 56b68574..0850b701 100644 --- a/src/ffmpeg_image.h +++ b/src/ffmpeg_image.h @@ -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; }; diff --git a/src/frame_info.h b/src/frame_info.h index a49f52a9..325350c4 100644 --- a/src/frame_info.h +++ b/src/frame_info.h @@ -36,6 +36,10 @@ #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; +}; + + } diff --git a/src/j2k_picture_asset_writer_common.cc b/src/j2k_picture_asset_writer_common.cc index 6ccc63be..a86da194 100644 --- a/src/j2k_picture_asset_writer_common.cc +++ b/src/j2k_picture_asset_writer_common.cc @@ -46,9 +46,9 @@ using std::shared_ptr; namespace dcp { -struct ASDCPStateBase +struct ASDCPJ2KStateBase { - ASDCPStateBase () + ASDCPJ2KStateBase() : frame_buffer (4 * Kumu::Megabyte) {} diff --git a/src/mono_j2k_picture_asset_writer.cc b/src/mono_j2k_picture_asset_writer.cc index 08639955..1188701e 100644 --- a/src/mono_j2k_picture_asset_writer.cc +++ b/src/mono_j2k_picture_asset_writer.cc @@ -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; }; diff --git a/src/mono_mpeg2_picture_asset.cc b/src/mono_mpeg2_picture_asset.cc index 548e936a..380da0fe 100644 --- a/src/mono_mpeg2_picture_asset.cc +++ b/src/mono_mpeg2_picture_asset.cc @@ -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)); +} diff --git a/src/mono_mpeg2_picture_asset.h b/src/mono_mpeg2_picture_asset.h index a740cc6c..8ef3653e 100644 --- a/src/mono_mpeg2_picture_asset.h +++ b/src/mono_mpeg2_picture_asset.h @@ -41,6 +41,7 @@ */ +#include "behaviour.h" #include "mpeg2_picture_asset.h" #include "mono_mpeg2_picture_asset_reader.h" @@ -48,11 +49,19 @@ 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 index 00000000..f8101c54 --- /dev/null +++ b/src/mono_mpeg2_picture_asset_writer.cc @@ -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 index 00000000..bf7a6cc9 --- /dev/null +++ b/src/mono_mpeg2_picture_asset_writer.h @@ -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; +}; + + +} diff --git a/src/mono_mpeg2_picture_frame.cc b/src/mono_mpeg2_picture_frame.cc index 3da67212..3c79a94c 100644 --- a/src/mono_mpeg2_picture_frame.cc +++ b/src/mono_mpeg2_picture_frame.cc @@ -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. diff --git a/src/mono_mpeg2_picture_frame.h b/src/mono_mpeg2_picture_frame.h index 13853a4e..6a7669f7 100644 --- a/src/mono_mpeg2_picture_frame.h +++ b/src/mono_mpeg2_picture_frame.h @@ -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; }; diff --git a/src/mpeg2_picture_asset.h b/src/mpeg2_picture_asset.h index df882202..38b8e0a0 100644 --- a/src/mpeg2_picture_asset.h +++ b/src/mpeg2_picture_asset.h @@ -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 index 00000000..f5ead775 --- /dev/null +++ b/src/mpeg2_picture_asset_writer.cc @@ -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 index 00000000..a370ef9b --- /dev/null +++ b/src/mpeg2_picture_asset_writer.h @@ -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 index 00000000..d46057f7 --- /dev/null +++ b/src/mpeg2_picture_asset_writer_common.cc @@ -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; +} diff --git a/src/mpeg2_transcode.cc b/src/mpeg2_transcode.cc index ab77f0e1..0ac2c1af 100644 --- a/src/mpeg2_transcode.cc +++ b/src/mpeg2_transcode.cc @@ -36,13 +36,16 @@ #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); +} diff --git a/src/mpeg2_transcode.h b/src/mpeg2_transcode.h index ef5c8351..1f13cfc3 100644 --- a/src/mpeg2_transcode.h +++ b/src/mpeg2_transcode.h @@ -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); +}; + + } @@ -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 (); diff --git a/src/stereo_j2k_picture_asset_writer.cc b/src/stereo_j2k_picture_asset_writer.cc index 8a655fbc..e59de02f 100644 --- a/src/stereo_j2k_picture_asset_writer.cc +++ b/src/stereo_j2k_picture_asset_writer.cc @@ -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; }; diff --git a/src/wscript b/src/wscript index 47b716cf..1793aa72 100644 --- a/src/wscript +++ b/src/wscript @@ -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 index 00000000..44ce5ae4 --- /dev/null +++ b/test/mono_mpeg2_picture_write_test.cc @@ -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(); +} + diff --git a/test/wscript b/test/wscript index ee5b754b..64b3ed59 100644 --- a/test/wscript +++ b/test/wscript @@ -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 |
