From 7118688204681d1cd46afccfb3fd948137836f39 Mon Sep 17 00:00:00 2001 From: wrandelshofer Date: Sat, 5 Oct 2024 15:22:24 +0200 Subject: [PATCH] Clear ByteArrayImageOutputStream before reusing it. --- org.monte.demo.moviewriter/pom.xml | 4 - .../org/monte/demo/moviewriter/Main.java | 58 +- .../media/jcodec/codec/JCodecH264Codec.java | 2 +- org.monte.media.jmf/pom.xml | 5 + .../media/jmf/codec/video/TSCCCodec.java | 1 + .../monte/media/av/codec/video/JPEGCodec.java | 1 + .../monte/media/av/codec/video/PNGCodec.java | 1 + .../media/av/codec/video/TechSmithCodec.java | 1 + .../av/codec/video/TechSmithCodecCore.java | 149 +- .../org/monte/media/avi/AVIWriter.java | 3 +- .../monte/media/avi/codec/video/DIBCodec.java | 1 + .../media/avi/codec/video/RunLengthCodec.java | 1 + .../jcodec/api/NotImplementedException.java | 9 + .../impl/jcodec/api/transcode/PixelStore.java | 42 + .../api/transcode/VideoFrameWithPacket.java | 39 + .../jcodec/codecs/common/biari/MConst.java | 35 + .../jcodec/codecs/common/biari/MDecoder.java | 163 ++ .../jcodec/codecs/common/biari/MEncoder.java | 163 ++ .../impl/jcodec/codecs/h264/H264Const.java | 649 ++++++++ .../impl/jcodec/codecs/h264/H264Decoder.java | 436 ++++++ .../impl/jcodec/codecs/h264/H264Encoder.java | 533 +++++++ .../impl/jcodec/codecs/h264/H264Utils.java | 1101 ++++++++++++++ .../impl/jcodec/codecs/h264/H264Utils2.java | 11 + .../impl/jcodec/codecs/h264/POCManager.java | 96 ++ .../codecs/h264/decode/BlockInterpolator.java | 1153 +++++++++++++++ .../codecs/h264/decode/CABACContst.java | 357 +++++ .../codecs/h264/decode/CAVLCReader.java | 99 ++ .../h264/decode/ChromaPredictionBuilder.java | 495 +++++++ .../codecs/h264/decode/CoeffTransformer.java | 508 +++++++ .../codecs/h264/decode/DeblockerInput.java | 40 + .../codecs/h264/decode/DecoderState.java | 45 + .../codecs/h264/decode/FrameReader.java | 123 ++ .../decode/Intra16x16PredictionBuilder.java | 254 ++++ .../decode/Intra4x4PredictionBuilder.java | 661 +++++++++ .../decode/Intra8x8PredictionBuilder.java | 428 ++++++ .../jcodec/codecs/h264/decode/MBlock.java | 222 +++ .../h264/decode/MBlockDecoderBDirect.java | 342 +++++ .../codecs/h264/decode/MBlockDecoderBase.java | 229 +++ .../codecs/h264/decode/MBlockDecoderIPCM.java | 28 + .../h264/decode/MBlockDecoderInter.java | 361 +++++ .../h264/decode/MBlockDecoderInter8x8.java | 338 +++++ .../h264/decode/MBlockDecoderIntra16x16.java | 65 + .../h264/decode/MBlockDecoderIntraNxN.java | 81 + .../h264/decode/MBlockDecoderUtils.java | 154 ++ .../codecs/h264/decode/MBlockReader.java | 10 + .../codecs/h264/decode/MBlockSkipDecoder.java | 102 ++ .../codecs/h264/decode/PredictionMerger.java | 150 ++ .../codecs/h264/decode/RefListManager.java | 167 +++ .../codecs/h264/decode/SliceDecoder.java | 215 +++ .../codecs/h264/decode/SliceHeaderReader.java | 275 ++++ .../codecs/h264/decode/SliceReader.java | 1090 ++++++++++++++ .../h264/decode/aso/FlatMBlockMapper.java | 54 + .../h264/decode/aso/MBToSliceGroupMap.java | 34 + .../codecs/h264/decode/aso/MapManager.java | 124 ++ .../jcodec/codecs/h264/decode/aso/Mapper.java | 23 + .../h264/decode/aso/PrebuiltMBlockMapper.java | 67 + .../h264/decode/aso/SliceGroupMapBuilder.java | 242 +++ .../h264/decode/deblock/DeblockingFilter.java | 461 ++++++ .../jcodec/codecs/h264/encode/CAVLCRate.java | 45 + .../codecs/h264/encode/CQPRateControl.java | 92 ++ .../codecs/h264/encode/DumbRateControl.java | 53 + .../jcodec/codecs/h264/encode/EncodedMB.java | 72 + .../codecs/h264/encode/EncodingContext.java | 112 ++ .../codecs/h264/encode/H264EncoderUtils.java | 53 + .../h264/encode/H264FixedRateControl.java | 62 + .../jcodec/codecs/h264/encode/H264RDO.java | 5 + .../h264/encode/IntraPredEstimator.java | 124 ++ .../codecs/h264/encode/MBDeblocker.java | 418 ++++++ .../codecs/h264/encode/MBEncoderHelper.java | 156 ++ .../codecs/h264/encode/MBWriterI16x16.java | 259 ++++ .../codecs/h264/encode/MBWriterINxN.java | 148 ++ .../codecs/h264/encode/MBWriterP16x16.java | 182 +++ .../codecs/h264/encode/MotionEstimator.java | 341 +++++ .../codecs/h264/encode/RateControl.java | 22 + .../impl/jcodec/codecs/h264/io/CABAC.java | 749 ++++++++++ .../impl/jcodec/codecs/h264/io/CAVLC.java | 341 +++++ .../codecs/h264/io/model/AspectRatio.java | 33 + .../jcodec/codecs/h264/io/model/Frame.java | 125 ++ .../codecs/h264/io/model/HRDParameters.java | 32 + .../jcodec/codecs/h264/io/model/MBType.java | 58 + .../codecs/h264/io/model/NALReasemble.java | 202 +++ .../jcodec/codecs/h264/io/model/NALUnit.java | 36 + .../codecs/h264/io/model/NALUnitType.java | 67 + .../h264/io/model/PictureParameterSet.java | 419 ++++++ .../h264/io/model/PredictionWeightTable.java | 32 + .../codecs/h264/io/model/RefPicMarking.java | 54 + .../h264/io/model/RefPicMarkingIDR.java | 29 + .../impl/jcodec/codecs/h264/io/model/SEI.java | 91 ++ .../codecs/h264/io/model/SeqParameterSet.java | 708 +++++++++ .../h264/io/model/SeqParameterSetExt.java | 54 + .../codecs/h264/io/model/SliceHeader.java | 99 ++ .../codecs/h264/io/model/SliceType.java | 53 + .../codecs/h264/io/model/VUIParameters.java | 77 + .../codecs/h264/io/write/CAVLCWriter.java | 87 ++ .../codecs/h264/io/write/NALUnitWriter.java | 54 + .../h264/io/write/SliceHeaderWriter.java | 239 +++ .../impl/jcodec/codecs/h264/mp4/AvcCBox.java | 183 +++ .../media/impl/jcodec/common/ArrayUtil.java | 565 +++++++ .../jcodec/common/AutoFileChannelWrapper.java | 115 ++ .../media/impl/jcodec/common/CodecMeta.java | 27 + .../impl/jcodec/common/IntArrayList.java | 179 +++ .../media/impl/jcodec/common/IntIntMap.java | 85 ++ .../impl/jcodec/common/IntObjectMap.java | 74 + .../media/impl/jcodec/common/JCodecUtil2.java | 56 + .../impl/jcodec/common/Preconditions.java | 1308 +++++++++++++++++ .../media/impl/jcodec/common/StringUtils.java | 290 ++++ .../monte/media/impl/jcodec/common/Tuple.java | 267 ++++ .../impl/jcodec/common/UsedViaReflection.java | 9 + .../impl/jcodec/common/VideoCodecMeta.java | 79 + .../impl/jcodec/common/VideoDecoder.java | 46 + .../impl/jcodec/common/VideoEncoder.java | 83 ++ .../media/impl/jcodec/common/io/AutoPool.java | 58 + .../impl/jcodec/common/io/AutoResource.java | 13 + .../impl/jcodec/common/io/BitReader.java | 244 +++ .../impl/jcodec/common/io/BitWriter.java | 111 ++ .../jcodec/common/io/FileChannelWrapper.java | 63 + .../media/impl/jcodec/common/io/IOUtils.java | 102 ++ .../media/impl/jcodec/common/io/NIOUtils.java | 531 +++++++ .../jcodec/common/io/SeekableByteChannel.java | 24 + .../impl/jcodec/common/io/StringReader.java | 42 + .../media/impl/jcodec/common/io/VLC.java | 171 +++ .../impl/jcodec/common/io/VLCBuilder.java | 68 + .../jcodec/common/logging/BufferLogSink.java | 30 + .../impl/jcodec/common/logging/LogLevel.java | 5 + .../impl/jcodec/common/logging/LogSink.java | 5 + .../impl/jcodec/common/logging/Logger.java | 98 ++ .../impl/jcodec/common/logging/Message.java | 50 + .../jcodec/common/logging/OutLogSink.java | 82 ++ .../impl/jcodec/common/model/ColorSpace.java | 124 ++ .../impl/jcodec/common/model/Packet.java | 140 ++ .../impl/jcodec/common/model/Picture.java | 390 +++++ .../impl/jcodec/common/model/PictureHiBD.java | 199 +++ .../impl/jcodec/common/model/Rational.java | 215 +++ .../jcodec/common/model/RationalLarge.java | 187 +++ .../media/impl/jcodec/common/model/Rect.java | 73 + .../media/impl/jcodec/common/model/Size.java | 56 + .../jcodec/common/model/TapeTimecode.java | 74 + .../media/impl/jcodec/common/tools/Debug.java | 80 + .../impl/jcodec/common/tools/MainUtils.java | 498 +++++++ .../impl/jcodec/common/tools/MathUtil.java | 151 ++ .../jcodec/containers/mp4/IBoxFactory.java | 9 + .../impl/jcodec/containers/mp4/TimeUtil.java | 35 + .../impl/jcodec/containers/mp4/boxes/Box.java | 144 ++ .../jcodec/containers/mp4/boxes/FullBox.java | 45 + .../jcodec/containers/mp4/boxes/Header.java | 159 ++ .../containers/mp4/boxes/MovieHeaderBox.java | 173 +++ .../jcodec/containers/mp4/boxes/NodeBox.java | 224 +++ .../containers/mp4/boxes/SampleEntry.java | 57 + .../mp4/boxes/VideoSampleEntry.java | 172 +++ .../monte/media/impl/jcodec/impl/AWTUtil.java | 109 ++ .../media/impl/jcodec/platform/Platform.java | 183 +++ .../media/impl/jcodec/scale/ColorUtil.java | 81 + .../media/impl/jcodec/scale/RgbToBgr.java | 31 + .../media/impl/jcodec/scale/RgbToYuv420j.java | 78 + .../media/impl/jcodec/scale/RgbToYuv420p.java | 78 + .../media/impl/jcodec/scale/RgbToYuv422p.java | 38 + .../media/impl/jcodec/scale/Transform.java | 20 + .../media/impl/jcodec/scale/Yuv420jToRgb.java | 92 ++ .../media/impl/jcodec/scale/Yuv420pToRgb.java | 131 ++ .../impl/jcodec/scale/Yuv420pToYuv422p.java | 72 + .../media/impl/jcodec/scale/Yuv422pToRgb.java | 47 + .../impl/jcodec/scale/Yuv422pToYuv420p.java | 32 + .../impl/jcodec/scale/Yuv444jToYuv420j.java | 30 + .../media/io/ByteArrayImageOutputStream.java | 4 +- .../org/monte/media/io/ImageInputStreams.java | 26 + .../org/monte/media/mp4/MP4Writer.java | 3 +- .../media/mp4/codec/video/H264Codec.java | 206 +++ .../media/mp4/codec/video/H264CodecSpi.java | 22 + .../mp4/codec/video/JCodecPictureCodec.java | 58 + .../codec/video/JCodecPictureCodecSpi.java | 22 + .../media/quicktime/QuickTimeWriter.java | 6 +- .../quicktime/codec/video/AnimationCodec.java | 1 + .../media/quicktime/codec/video/RawCodec.java | 2 + .../media/av/codec/video/RgbImageBuilder.java | 49 + .../av/codec/video/TechSmithCodecTest.java | 112 ++ 175 files changed, 27974 insertions(+), 111 deletions(-) create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/api/NotImplementedException.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/api/transcode/PixelStore.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/api/transcode/VideoFrameWithPacket.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/common/biari/MConst.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/common/biari/MDecoder.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/common/biari/MEncoder.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Const.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Decoder.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Encoder.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Utils.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Utils2.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/POCManager.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/BlockInterpolator.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/CABACContst.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/CAVLCReader.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/ChromaPredictionBuilder.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/CoeffTransformer.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/DeblockerInput.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/DecoderState.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/FrameReader.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/Intra16x16PredictionBuilder.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/Intra4x4PredictionBuilder.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/Intra8x8PredictionBuilder.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlock.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderBDirect.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderBase.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderIPCM.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderInter.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderInter8x8.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderIntra16x16.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderIntraNxN.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderUtils.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockReader.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockSkipDecoder.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/PredictionMerger.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/RefListManager.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/SliceDecoder.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/SliceHeaderReader.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/SliceReader.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/FlatMBlockMapper.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/MBToSliceGroupMap.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/MapManager.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/Mapper.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/PrebuiltMBlockMapper.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/SliceGroupMapBuilder.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/deblock/DeblockingFilter.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/CAVLCRate.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/CQPRateControl.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/DumbRateControl.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/EncodedMB.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/EncodingContext.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/H264EncoderUtils.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/H264FixedRateControl.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/H264RDO.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/IntraPredEstimator.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBDeblocker.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBEncoderHelper.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBWriterI16x16.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBWriterINxN.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBWriterP16x16.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MotionEstimator.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/RateControl.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/CABAC.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/CAVLC.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/AspectRatio.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/Frame.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/HRDParameters.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/MBType.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/NALReasemble.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/NALUnit.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/NALUnitType.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/PictureParameterSet.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/PredictionWeightTable.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/RefPicMarking.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/RefPicMarkingIDR.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SEI.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SeqParameterSet.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SeqParameterSetExt.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SliceHeader.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SliceType.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/VUIParameters.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/write/CAVLCWriter.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/write/NALUnitWriter.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/write/SliceHeaderWriter.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/mp4/AvcCBox.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/ArrayUtil.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/AutoFileChannelWrapper.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/CodecMeta.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/IntArrayList.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/IntIntMap.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/IntObjectMap.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/JCodecUtil2.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/Preconditions.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/StringUtils.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/Tuple.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/UsedViaReflection.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/VideoCodecMeta.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/VideoDecoder.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/VideoEncoder.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/AutoPool.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/AutoResource.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/BitReader.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/BitWriter.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/FileChannelWrapper.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/IOUtils.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/NIOUtils.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/SeekableByteChannel.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/StringReader.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/VLC.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/VLCBuilder.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/BufferLogSink.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/LogLevel.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/LogSink.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/Logger.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/Message.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/OutLogSink.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/ColorSpace.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Packet.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Picture.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/PictureHiBD.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Rational.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/RationalLarge.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Rect.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Size.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/TapeTimecode.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/tools/Debug.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/tools/MainUtils.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/tools/MathUtil.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/IBoxFactory.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/TimeUtil.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/Box.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/FullBox.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/Header.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/MovieHeaderBox.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/NodeBox.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/SampleEntry.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/VideoSampleEntry.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/impl/AWTUtil.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/platform/Platform.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/ColorUtil.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToBgr.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToYuv420j.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToYuv420p.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToYuv422p.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Transform.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv420jToRgb.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv420pToRgb.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv420pToYuv422p.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv422pToRgb.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv422pToYuv420p.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv444jToYuv420j.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/io/ImageInputStreams.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/H264Codec.java create mode 100755 org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/H264CodecSpi.java create mode 100644 org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/JCodecPictureCodec.java create mode 100755 org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/JCodecPictureCodecSpi.java create mode 100644 org.monte.media/src/test/java/org.monte.media/org/monte/media/av/codec/video/RgbImageBuilder.java diff --git a/org.monte.demo.moviewriter/pom.xml b/org.monte.demo.moviewriter/pom.xml index 3fdd077..6697359 100644 --- a/org.monte.demo.moviewriter/pom.xml +++ b/org.monte.demo.moviewriter/pom.xml @@ -59,10 +59,6 @@ ch.randelshofer org.monte.media - - ch.randelshofer - org.monte.media.jcodec - diff --git a/org.monte.demo.moviewriter/src/main/java/org.monte.demo.moviewriter/org/monte/demo/moviewriter/Main.java b/org.monte.demo.moviewriter/src/main/java/org.monte.demo.moviewriter/org/monte/demo/moviewriter/Main.java index 0b3fb73..046eaa3 100755 --- a/org.monte.demo.moviewriter/src/main/java/org.monte.demo.moviewriter/org/monte/demo/moviewriter/Main.java +++ b/org.monte.demo.moviewriter/src/main/java/org.monte.demo.moviewriter/org/monte/demo/moviewriter/Main.java @@ -30,6 +30,7 @@ import static org.monte.media.av.FormatKeys.EncodingKey; import static org.monte.media.av.FormatKeys.FrameRateKey; +import static org.monte.media.av.FormatKeys.KeyFrameIntervalKey; import static org.monte.media.av.FormatKeys.MediaTypeKey; import static org.monte.media.av.codec.video.VideoFormatKeys.DepthKey; import static org.monte.media.av.codec.video.VideoFormatKeys.ENCODING_AVC1; @@ -136,34 +137,35 @@ public static void main(String[] args) { try { var m = new Main(); - m.test(new File("moviewriterdemo-h264-motion0.mp4"), new Format(EncodingKey, ENCODING_AVC1, DepthKey, 24, QualityKey, 0.75f, MotionSearchRangeKey, 0)); - m.test(new File("moviewriterdemo-h264-motion0.mov"), new Format(EncodingKey, ENCODING_AVC1, DepthKey, 24, MotionSearchRangeKey, 0)); - m.test(new File("moviewriterdemo-h264-motion0.avi"), new Format(EncodingKey, ENCODING_AVC1, DepthKey, 24, MotionSearchRangeKey, 0)); - m.test(new File("moviewriterdemo-h264-motion16.mp4"), new Format(DepthKey, 24, QualityKey, 0.75f, MotionSearchRangeKey, 16)); - m.test(new File("moviewriterdemo-h264-motion16.mov"), new Format(EncodingKey, ENCODING_AVC1, DepthKey, 24, MotionSearchRangeKey, 16)); - m.test(new File("moviewriterdemo-jpg-q0.75.avi"), new Format(EncodingKey, ENCODING_AVI_MJPG, DepthKey, 24, QualityKey, 0.75f)); - m.test(new File("moviewriterdemo-jpg-q0.75.mov"), new Format(EncodingKey, ENCODING_QUICKTIME_JPEG, DepthKey, 24, QualityKey, 0.75f)); - m.test(new File("moviewriterdemo-png.avi"), new Format(EncodingKey, ENCODING_AVI_PNG, DepthKey, 24)); - m.test(new File("moviewriterdemo-png.mov"), new Format(EncodingKey, ENCODING_QUICKTIME_PNG, DepthKey, 24)); - m.test(new File("moviewriterdemo-png.zip"), new Format(EncodingKey, ENCODING_AVI_PNG, DepthKey, 24)); - m.test(new File("moviewriterdemo-raw24.avi"), new Format(EncodingKey, ENCODING_AVI_DIB, DepthKey, 24)); - m.test(new File("moviewriterdemo-raw24.mov"), new Format(EncodingKey, ENCODING_QUICKTIME_RAW, DepthKey, 24)); - m.test(new File("moviewriterdemo-raw8.avi"), new Format(EncodingKey, ENCODING_AVI_DIB, DepthKey, 8)); - m.test(new File("moviewriterdemo-raw8.mov"), new Format(EncodingKey, ENCODING_QUICKTIME_RAW, DepthKey, 8)); - m.test(new File("moviewriterdemo-raw8gray.avi"), new Format(EncodingKey, ENCODING_AVI_DIB, DepthKey, 8, PixelFormatKey, PixelFormat.GRAY)); - m.test(new File("moviewriterdemo-rle16.mov"), new Format(EncodingKey, ENCODING_QUICKTIME_ANIMATION, DepthKey, 16)); - m.test(new File("moviewriterdemo-rle24.mov"), new Format(EncodingKey, ENCODING_QUICKTIME_ANIMATION, DepthKey, 24)); - m.test(new File("moviewriterdemo-rle8.avi"), new Format(EncodingKey, ENCODING_AVI_RLE8, DepthKey, 8)); - m.test(new File("moviewriterdemo-rle8.mov"), new Format(EncodingKey, ENCODING_QUICKTIME_ANIMATION, DepthKey, 8)); - m.test(new File("moviewriterdemo-rle8gray.avi"), new Format(EncodingKey, ENCODING_AVI_RLE8, DepthKey, 8, PixelFormatKey, PixelFormat.GRAY)); - m.test(new File("moviewriterdemo-tscc16.avi"), new Format(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 16)); - m.test(new File("moviewriterdemo-tscc16.mov"), new Format(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 16)); - m.test(new File("moviewriterdemo-tscc24.avi"), new Format(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 24)); - m.test(new File("moviewriterdemo-tscc24.mov"), new Format(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 24)); - m.test(new File("moviewriterdemo-tscc8.avi"), new Format(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 8)); - m.test(new File("moviewriterdemo-tscc8.mov"), new Format(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 8)); - m.test(new File("moviewriterdemo-tscc8gray.avi"), new Format(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 8, PixelFormatKey, PixelFormat.GRAY)); - m.test(new File("moviewriterdemo-tscc8gray.mov"), new Format(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 8, PixelFormatKey, PixelFormat.GRAY)); + Format baseFormat = new Format(QualityKey, 0.75f, KeyFrameIntervalKey, 60); + m.test(new File("moviewriterdemo-h264-motion0.avi"), baseFormat.prepend(EncodingKey, ENCODING_AVC1, DepthKey, 24, MotionSearchRangeKey, 0)); + m.test(new File("moviewriterdemo-h264-motion0.mov"), baseFormat.prepend(EncodingKey, ENCODING_AVC1, DepthKey, 24, MotionSearchRangeKey, 0)); + m.test(new File("moviewriterdemo-h264-motion0.mp4"), baseFormat.prepend(EncodingKey, ENCODING_AVC1, DepthKey, 24, MotionSearchRangeKey, 0)); + m.test(new File("moviewriterdemo-h264-motion16.mov"), baseFormat.prepend(EncodingKey, ENCODING_AVC1, DepthKey, 24, MotionSearchRangeKey, 16)); + m.test(new File("moviewriterdemo-h264-motion16.mp4"), baseFormat.prepend(EncodingKey, ENCODING_AVC1, DepthKey, 24, MotionSearchRangeKey, 16)); + m.test(new File("moviewriterdemo-jpg-q0.75.avi"), baseFormat.prepend(EncodingKey, ENCODING_AVI_MJPG, DepthKey, 24, QualityKey, 0.75f)); + m.test(new File("moviewriterdemo-jpg-q0.75.mov"), baseFormat.prepend(EncodingKey, ENCODING_QUICKTIME_JPEG, DepthKey, 24, QualityKey, 0.75f)); + m.test(new File("moviewriterdemo-png.avi"), baseFormat.prepend(EncodingKey, ENCODING_AVI_PNG, DepthKey, 24)); + m.test(new File("moviewriterdemo-png.mov"), baseFormat.prepend(EncodingKey, ENCODING_QUICKTIME_PNG, DepthKey, 24)); + m.test(new File("moviewriterdemo-png.zip"), baseFormat.prepend(EncodingKey, ENCODING_AVI_PNG, DepthKey, 24)); + m.test(new File("moviewriterdemo-raw24.avi"), baseFormat.prepend(EncodingKey, ENCODING_AVI_DIB, DepthKey, 24)); + m.test(new File("moviewriterdemo-raw24.mov"), baseFormat.prepend(EncodingKey, ENCODING_QUICKTIME_RAW, DepthKey, 24)); + m.test(new File("moviewriterdemo-raw8.avi"), baseFormat.prepend(EncodingKey, ENCODING_AVI_DIB, DepthKey, 8)); + m.test(new File("moviewriterdemo-raw8.mov"), baseFormat.prepend(EncodingKey, ENCODING_QUICKTIME_RAW, DepthKey, 8)); + m.test(new File("moviewriterdemo-raw8gray.avi"), baseFormat.prepend(EncodingKey, ENCODING_AVI_DIB, DepthKey, 8, PixelFormatKey, PixelFormat.GRAY)); + m.test(new File("moviewriterdemo-rle16.mov"), baseFormat.prepend(EncodingKey, ENCODING_QUICKTIME_ANIMATION, DepthKey, 16)); + m.test(new File("moviewriterdemo-rle24.mov"), baseFormat.prepend(EncodingKey, ENCODING_QUICKTIME_ANIMATION, DepthKey, 24)); + m.test(new File("moviewriterdemo-rle8.avi"), baseFormat.prepend(EncodingKey, ENCODING_AVI_RLE8, DepthKey, 8)); + m.test(new File("moviewriterdemo-rle8.mov"), baseFormat.prepend(EncodingKey, ENCODING_QUICKTIME_ANIMATION, DepthKey, 8)); + m.test(new File("moviewriterdemo-rle8gray.avi"), baseFormat.prepend(EncodingKey, ENCODING_AVI_RLE8, DepthKey, 8, PixelFormatKey, PixelFormat.GRAY)); + m.test(new File("moviewriterdemo-tscc16.avi"), baseFormat.prepend(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 16)); + m.test(new File("moviewriterdemo-tscc16.mov"), baseFormat.prepend(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 16)); + m.test(new File("moviewriterdemo-tscc24.avi"), baseFormat.prepend(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 24)); + m.test(new File("moviewriterdemo-tscc24.mov"), baseFormat.prepend(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 24)); + m.test(new File("moviewriterdemo-tscc8.avi"), baseFormat.prepend(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 8)); + m.test(new File("moviewriterdemo-tscc8.mov"), baseFormat.prepend(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 8)); + m.test(new File("moviewriterdemo-tscc8gray.avi"), baseFormat.prepend(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 8, PixelFormatKey, PixelFormat.GRAY)); + m.test(new File("moviewriterdemo-tscc8gray.mov"), baseFormat.prepend(EncodingKey, ENCODING_AVI_TECHSMITH_SCREEN_CAPTURE, DepthKey, 8, PixelFormatKey, PixelFormat.GRAY)); } catch (IOException ex) { ex.printStackTrace(); } diff --git a/org.monte.media.jcodec/src/main/java/org.monte.media.jcodec/org/monte/media/jcodec/codec/JCodecH264Codec.java b/org.monte.media.jcodec/src/main/java/org.monte.media.jcodec/org/monte/media/jcodec/codec/JCodecH264Codec.java index 48354f6..13e87b7 100644 --- a/org.monte.media.jcodec/src/main/java/org.monte.media.jcodec/org/monte/media/jcodec/codec/JCodecH264Codec.java +++ b/org.monte.media.jcodec/src/main/java/org.monte.media.jcodec/org/monte/media/jcodec/codec/JCodecH264Codec.java @@ -1,5 +1,5 @@ /* - * @(#)JCodecH264Codec.java + * @(#)H264Codec.java * Copyright © 2024 Werner Randelshofer, Switzerland. MIT License. */ diff --git a/org.monte.media.jmf/pom.xml b/org.monte.media.jmf/pom.xml index dc46637..ac58edb 100644 --- a/org.monte.media.jmf/pom.xml +++ b/org.monte.media.jmf/pom.xml @@ -94,5 +94,10 @@ javax.media jmf + + org.junit.jupiter + junit-jupiter + test + diff --git a/org.monte.media.jmf/src/main/java/org.monte.media.jmf/org/monte/media/jmf/codec/video/TSCCCodec.java b/org.monte.media.jmf/src/main/java/org.monte.media.jmf/org/monte/media/jmf/codec/video/TSCCCodec.java index feac0bf..27ddf0e 100755 --- a/org.monte.media.jmf/src/main/java/org.monte.media.jmf/org/monte/media/jmf/codec/video/TSCCCodec.java +++ b/org.monte.media.jmf/src/main/java/org.monte.media.jmf/org/monte/media/jmf/codec/video/TSCCCodec.java @@ -179,6 +179,7 @@ protected int encode(Buffer in, Buffer out) { out.setFormat(outputFormat); ByteArrayImageOutputStream tmp = new ByteArrayImageOutputStream(ArrayUtil.reuseByteArray(out.getData(), 32)); + tmp.clear(); VideoFormat outvf = outputFormat; boolean isKeyframe = isSet(in, Buffer.FLAG_KEY_FRAME) || frameCounter % (int) outvf.getFrameRate() == 0; diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/JPEGCodec.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/JPEGCodec.java index 5c5e0be..c2dab90 100755 --- a/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/JPEGCodec.java +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/JPEGCodec.java @@ -122,6 +122,7 @@ public int encode(Buffer in, Buffer out) { return CODEC_FAILED; } ByteArrayImageOutputStream tmp = new ByteArrayImageOutputStream(ArrayUtil.reuseByteArray(out.data, 32)); + tmp.clear(); try { ImageWriter iw = ImageIO.getImageWritersByMIMEType("image/jpeg").next(); diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/PNGCodec.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/PNGCodec.java index d4a2adc..184cb83 100755 --- a/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/PNGCodec.java +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/PNGCodec.java @@ -143,6 +143,7 @@ public int encode(Buffer in, Buffer out) { } ByteArrayImageOutputStream tmp = new ByteArrayImageOutputStream(ArrayUtil.reuseByteArray(out.data, 32)); + tmp.clear(); try { ImageWriter iw = ImageIO.getImageWritersByMIMEType("image/png").next(); diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/TechSmithCodec.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/TechSmithCodec.java index e4e3265..12aefcc 100755 --- a/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/TechSmithCodec.java +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/TechSmithCodec.java @@ -329,6 +329,7 @@ public int encode(Buffer in, Buffer out) { } ByteArrayImageOutputStream tmp = new ByteArrayImageOutputStream(ArrayUtil.reuseByteArray(out.data, 32)); + tmp.clear(); Integer keyFrameInterval = outputFormat.get(KeyFrameIntervalKey, outputFormat.get(FrameRateKey).intValue()); boolean isKeyframe = frameCounter == 0 diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/TechSmithCodecCore.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/TechSmithCodecCore.java index ecc5a6e..2ddc026 100755 --- a/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/TechSmithCodecCore.java +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/av/codec/video/TechSmithCodecCore.java @@ -163,6 +163,11 @@ */ public class TechSmithCodecCore extends AbstractVideoCodecCore { + private static final byte ESCAPE_OP = (byte) 0x00; + private static final byte PADDING_OP = (byte) 0x00; + private static final byte END_OF_LINE_OP = (byte) 0x00; + private static final byte END_OF_BITMAP_OP = (byte) 0x01; + private static final byte SKIP_OP = (byte) 0x02; private byte[] temp2; private int[] palette; private ByteBuffer bbuf; @@ -611,8 +616,8 @@ public void encodeDelta8(ImageOutputStream out, byte[] data, byte[] prev, int wi } while (verticalOffset > 0 || skipCount > 0) { - bbuf.put((byte) 0x00); // Escape code - bbuf.put((byte) 0x02); // Skip OP-code + bbuf.put(ESCAPE_OP); // Escape code + bbuf.put(SKIP_OP); // Skip OP-code bbuf.put((byte) min(255, skipCount)); // horizontal offset bbuf.put((byte) min(255, verticalOffset)); // vertical offset skipCount -= min(255, skipCount); @@ -645,11 +650,11 @@ public void encodeDelta8(ImageOutputStream out, byte[] data, byte[] prev, int wi literalCount--; } else { int literalRun = min(254, literalCount); - bbuf.put((byte) 0); // Escape code + bbuf.put(ESCAPE_OP); // Escape code bbuf.put((byte) literalRun); // Literal OP-code bbuf.put(data, xy - literalCount, literalRun); if ((literalRun & 1) == 1) { - bbuf.put((byte) 0); // pad byte + bbuf.put(PADDING_OP); // pad byte } literalCount -= literalRun; } @@ -660,10 +665,10 @@ public void encodeDelta8(ImageOutputStream out, byte[] data, byte[] prev, int wi xy += skipCount - 1; } else if (skipCount >= repeatCount) { while (skipCount > 0) { - bbuf.put((byte) 0); // Escape code + bbuf.put(ESCAPE_OP); // Escape code bbuf.put((byte) 0x0002); // Skip OP-code bbuf.put((byte) min(255, skipCount)); - bbuf.put((byte) 0); + bbuf.put(PADDING_OP); xy += min(255, skipCount); skipCount -= min(255, skipCount); } @@ -684,21 +689,21 @@ public void encodeDelta8(ImageOutputStream out, byte[] data, byte[] prev, int wi literalCount--; } else { int literalRun = min(254, literalCount); - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalRun); // Literal OP-code bbuf.put(data, xy - literalCount, literalRun); if ((literalRun & 1) == 1) { - bbuf.put((byte) 0); // pad byte + bbuf.put(PADDING_OP); // pad byte } literalCount -= literalRun; } } - bbuf.put((byte) 0); // Escape code - bbuf.put((byte) 0x00); // End of line OP-code + bbuf.put(ESCAPE_OP); // Escape code + bbuf.put(END_OF_LINE_OP); // End of line OP-code } - bbuf.put((byte) 0); // Escape code - bbuf.put((byte) 0x01);// End of bitmap + bbuf.put(ESCAPE_OP); // Escape code + bbuf.put(END_OF_BITMAP_OP);// End of bitmap deflateBBuf(out); } @@ -742,8 +747,8 @@ public void encodeDelta8to24(ImageOutputStream out, byte[] data, byte[] prev, in } while (verticalOffset > 0 || skipCount > 0) { - bbuf.put((byte) 0x00); // Escape code - bbuf.put((byte) 0x02); // Skip OP-code + bbuf.put(ESCAPE_OP); // Escape code + bbuf.put(SKIP_OP); // Skip OP-code bbuf.put((byte) min(255, skipCount)); // horizontal offset bbuf.put((byte) min(255, verticalOffset)); // vertical offset skipCount -= min(255, skipCount); @@ -780,7 +785,7 @@ public void encodeDelta8to24(ImageOutputStream out, byte[] data, byte[] prev, in literalCount--; } else { int literalRun = min(254, literalCount); - bbuf.put((byte) 0); // Escape code + bbuf.put(ESCAPE_OP); // Escape code bbuf.put((byte) literalRun); // Literal OP-code for (int i = xy - literalCount, end = xy - literalCount + literalRun; i < end; i++) { writeInt24LE(bbuf, palette[data[i] & 0xff]); @@ -794,10 +799,10 @@ public void encodeDelta8to24(ImageOutputStream out, byte[] data, byte[] prev, in xy += skipCount - 1; } else if (skipCount >= repeatCount) { while (skipCount > 0) { - bbuf.put((byte) 0); // Escape code + bbuf.put(ESCAPE_OP); // Escape code bbuf.put((byte) 0x0002); // Skip OP-code bbuf.put((byte) min(255, skipCount)); - bbuf.put((byte) 0); + bbuf.put(END_OF_LINE_OP); xy += min(255, skipCount); skipCount -= min(255, skipCount); } @@ -818,7 +823,7 @@ public void encodeDelta8to24(ImageOutputStream out, byte[] data, byte[] prev, in literalCount--; } else { int literalRun = min(254, literalCount); - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalRun); // Literal OP-code for (int i = xy - literalCount, end = xy - literalCount + literalRun; i < end; i++) { writeInt24LE(bbuf, palette[data[i] & 0xff]); @@ -832,11 +837,11 @@ public void encodeDelta8to24(ImageOutputStream out, byte[] data, byte[] prev, in } } - bbuf.put((byte) 0); // Escape code - bbuf.put((byte) 0x00); // End of line OP-code + bbuf.put(ESCAPE_OP); // Escape code + bbuf.put(END_OF_LINE_OP); // End of line OP-code } - bbuf.put((byte) 0); // Escape code - bbuf.put((byte) 0x01);// End of bitmap + bbuf.put(ESCAPE_OP); // Escape code + bbuf.put(END_OF_BITMAP_OP);// End of bitmap deflateBBuf(out); } @@ -933,7 +938,7 @@ public void encodeKey8(ImageOutputStream out, byte[] data, int width, int height if (repeatCount < 3) { literalCount++; if (literalCount == 254) { - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalCount); // Literal OP-code bbuf.put(data, xy - literalCount + 1, literalCount); literalCount = 0; @@ -946,11 +951,11 @@ public void encodeKey8(ImageOutputStream out, byte[] data, int width, int height bbuf.put((byte) data[xy - literalCount]); } } else { - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalCount); // Literal OP-code bbuf.put(data, xy - literalCount, literalCount); if ((literalCount & 1) == 1) { - bbuf.put((byte) 0); // pad byte + bbuf.put(PADDING_OP); // pad byte } literalCount = 0; } @@ -969,21 +974,21 @@ public void encodeKey8(ImageOutputStream out, byte[] data, int width, int height bbuf.put((byte) data[xy - literalCount]); } } else { - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalCount); bbuf.put(data, xy - literalCount, literalCount); if ((literalCount & 1) == 1) { - bbuf.put((byte) 0); // pad byte + bbuf.put(PADDING_OP); // pad byte } } literalCount = 0; } - bbuf.put((byte) 0); - bbuf.put((byte) 0x0000);// End of line + bbuf.put(ESCAPE_OP); + bbuf.put(END_OF_LINE_OP);// End of line } - bbuf.put((byte) 0); - bbuf.put((byte) 0x0001);// End of bitmap + bbuf.put(ESCAPE_OP); + bbuf.put(END_OF_BITMAP_OP);// End of bitmap //bbuf.toOutputStream(out); deflateBBuf(out); @@ -1024,7 +1029,7 @@ public void encodeKey8to24(ImageOutputStream out, byte[] data, int width, int he if (repeatCount < 3) { literalCount++; if (literalCount == 254) { - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalCount); // Literal OP-code for (int i = xy - literalCount + 1, end = xy + 1; i < end; i++) { writeInt24LE(bbuf, palette[data[i] & 0xff]); @@ -1040,7 +1045,7 @@ public void encodeKey8to24(ImageOutputStream out, byte[] data, int width, int he writeInt24LE(bbuf, palette[data[xy - literalCount] & 0xff]); } } else { - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalCount); // Literal OP-code for (int i = xy - literalCount, end = xy; i < end; i++) { writeInt24LE(bbuf, palette[data[i] & 0xff]); @@ -1066,7 +1071,7 @@ public void encodeKey8to24(ImageOutputStream out, byte[] data, int width, int he writeInt24LE(bbuf, palette[data[xy - literalCount] & 0xff]); } } else { - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalCount); for (int i = xy - literalCount, end = xy; i < end; i++) { writeInt24LE(bbuf, palette[data[i] & 0xff]); @@ -1079,11 +1084,11 @@ public void encodeKey8to24(ImageOutputStream out, byte[] data, int width, int he literalCount = 0; } - bbuf.put((byte) 0); - bbuf.put((byte) 0x0000);// End of line + bbuf.put(ESCAPE_OP); + bbuf.put(END_OF_LINE_OP);// End of line } - bbuf.put((byte) 0); - bbuf.put((byte) 0x0001);// End of bitmap + bbuf.put(ESCAPE_OP); + bbuf.put(END_OF_BITMAP_OP);// End of bitmap deflateBBuf(out); } @@ -1124,8 +1129,8 @@ public void encodeDelta16(ImageOutputStream out, short[] data, short[] prev, int } while (verticalOffset > 0 || skipCount > 0) { - bbuf.put((byte) 0x00); // Escape code - bbuf.put((byte) 0x02); // Skip OP-code + bbuf.put(ESCAPE_OP); // Escape code + bbuf.put(SKIP_OP); // Skip OP-code bbuf.put((byte) min(255, skipCount)); // horizontal offset bbuf.put((byte) min(255, verticalOffset)); // vertical offset skipCount -= min(255, skipCount); @@ -1158,7 +1163,7 @@ public void encodeDelta16(ImageOutputStream out, short[] data, short[] prev, int literalCount--; } else { int literalRun = min(254, literalCount); - bbuf.put((byte) 0); // Escape code + bbuf.put(ESCAPE_OP); // Escape code bbuf.put((byte) literalRun); // Literal OP-code writeInts16LE(bbuf, data, xy - literalCount, literalRun); literalCount -= literalRun; @@ -1170,8 +1175,8 @@ public void encodeDelta16(ImageOutputStream out, short[] data, short[] prev, int xy += skipCount - 1; } else if (skipCount >= repeatCount) { while (skipCount > 0) { - bbuf.put((byte) 0); // Escape code - bbuf.put((byte) 0x02); // Skip OP-code + bbuf.put(ESCAPE_OP); // Escape code + bbuf.put(SKIP_OP); // Skip OP-code bbuf.put((byte) min(255, skipCount)); // horizontal skip bbuf.put((byte) 0); // vertical skip xy += min(255, skipCount); @@ -1194,19 +1199,19 @@ public void encodeDelta16(ImageOutputStream out, short[] data, short[] prev, int literalCount--; } else { int literalRun = min(254, literalCount); - bbuf.put((byte) 0); // Escape code + bbuf.put(ESCAPE_OP); // Escape code bbuf.put((byte) literalRun); // Literal OP-code writeInts16LE(bbuf, data, xy - literalCount, literalRun); literalCount -= literalRun; } } - bbuf.put((byte) 0); // Escape code - bbuf.put((byte) 0x00); // End of line OP-code + bbuf.put(ESCAPE_OP); // Escape code + bbuf.put(END_OF_LINE_OP); // End of line OP-code } - bbuf.put((byte) 0); // Escape code - bbuf.put((byte) 0x01);// End of bitmap OP-code + bbuf.put(ESCAPE_OP); // Escape code + bbuf.put(END_OF_BITMAP_OP);// End of bitmap deflateBBuf(out); } @@ -1246,7 +1251,7 @@ public void encodeKey24(ImageOutputStream out, int[] data, int width, int height if (repeatCount < 3) { literalCount++; if (literalCount == 254) { - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalCount); // Literal OP-code writeInts24LE(bbuf, data, xy - literalCount + 1, literalCount); literalCount = 0; @@ -1259,7 +1264,7 @@ public void encodeKey24(ImageOutputStream out, int[] data, int width, int height writeInt24LE(bbuf, data[xy - literalCount]); } } else { - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalCount); // Literal OP-code writeInts24LE(bbuf, data, xy - literalCount, literalCount); ///if (literalCount & 1 == 1) { @@ -1282,7 +1287,7 @@ public void encodeKey24(ImageOutputStream out, int[] data, int width, int height writeInt24LE(bbuf, data[xy - literalCount]); } } else { - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalCount); writeInts24LE(bbuf, data, xy - literalCount, literalCount); ///if (literalCount & 1 == 1) { @@ -1292,11 +1297,11 @@ public void encodeKey24(ImageOutputStream out, int[] data, int width, int height literalCount = 0; } - bbuf.put((byte) 0); - bbuf.put((byte) 0x0000);// End of line + bbuf.put(ESCAPE_OP); + bbuf.put(END_OF_LINE_OP);// End of line } - bbuf.put((byte) 0); - bbuf.put((byte) 0x0001);// End of bitmap + bbuf.put(ESCAPE_OP); + bbuf.put(END_OF_BITMAP_OP);// End of bitmap deflateBBuf(out); } @@ -1337,8 +1342,8 @@ public void encodeDelta24(ImageOutputStream out, int[] data, int[] prev, int wid } while (verticalOffset > 0 || skipCount > 0) { - bbuf.put((byte) 0x00); // Escape code - bbuf.put((byte) 0x02); // Skip OP-code + bbuf.put(ESCAPE_OP); // Escape code + bbuf.put(SKIP_OP); // Skip OP-code bbuf.put((byte) min(255, skipCount)); // horizontal offset bbuf.put((byte) min(255, verticalOffset)); // vertical offset skipCount -= min(255, skipCount); @@ -1371,7 +1376,7 @@ public void encodeDelta24(ImageOutputStream out, int[] data, int[] prev, int wid literalCount--; } else { int literalRun = min(254, literalCount); - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalRun); // Literal OP-code writeInts24LE(bbuf, data, xy - literalCount, literalRun); ///if (literalRun & 1 == 1) { @@ -1386,10 +1391,10 @@ public void encodeDelta24(ImageOutputStream out, int[] data, int[] prev, int wid xy += skipCount - 1; } else if (skipCount >= repeatCount) { while (skipCount > 0) { - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) 0x0002); // Skip OP-code bbuf.put((byte) min(255, skipCount)); - bbuf.put((byte) 0); + bbuf.put(END_OF_LINE_OP); xy += min(255, skipCount); skipCount -= min(255, skipCount); } @@ -1410,7 +1415,7 @@ public void encodeDelta24(ImageOutputStream out, int[] data, int[] prev, int wid literalCount--; } else { int literalRun = min(254, literalCount); - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalRun); // Literal OP-code writeInts24LE(bbuf, data, xy - literalCount, literalRun); ///if (literalRun & 1 == 1) { @@ -1420,12 +1425,12 @@ public void encodeDelta24(ImageOutputStream out, int[] data, int[] prev, int wid } } - bbuf.put((byte) 0); // Escape code - bbuf.put((byte) 0x00); // End of line OP-code + bbuf.put(ESCAPE_OP); // Escape code + bbuf.put(END_OF_LINE_OP); // End of line OP-code } - bbuf.put((byte) 0); // Escape code - bbuf.put((byte) 0x01);// End of bitmap + bbuf.put(ESCAPE_OP); // Escape code + bbuf.put(END_OF_BITMAP_OP);// End of bitmap deflateBBuf(out); } @@ -1464,7 +1469,7 @@ public void encodeKey16(ImageOutputStream out, short[] data, int width, int heig if (repeatCount < 3) { literalCount++; if (literalCount == 254) { - bbuf.put((byte) 0); // Escape code + bbuf.put(ESCAPE_OP); // Escape code bbuf.put((byte) literalCount); // Literal OP-code writeInts16LE(bbuf, data, xy - literalCount + 1, literalCount); literalCount = 0; @@ -1477,7 +1482,7 @@ public void encodeKey16(ImageOutputStream out, short[] data, int width, int heig bbuf.putShort(data[xy - literalCount]); } } else { - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalCount); // Literal OP-code writeInts16LE(bbuf, data, xy - literalCount, literalCount); ///if (literalCount & 1 == 1) { @@ -1500,7 +1505,7 @@ public void encodeKey16(ImageOutputStream out, short[] data, int width, int heig bbuf.putShort(data[xy - literalCount]); } } else { - bbuf.put((byte) 0); + bbuf.put(ESCAPE_OP); bbuf.put((byte) literalCount); writeInts16LE(bbuf, data, xy - literalCount, literalCount); ///if (literalCount & 1 == 1) { @@ -1510,11 +1515,11 @@ public void encodeKey16(ImageOutputStream out, short[] data, int width, int heig literalCount = 0; } - bbuf.put((byte) 0); - bbuf.put((byte) 0x0000);// End of line + bbuf.put(ESCAPE_OP); + bbuf.put(END_OF_LINE_OP);// End of line } - bbuf.put((byte) 0); - bbuf.put((byte) 0x0001);// End of bitmap + bbuf.put(ESCAPE_OP); + bbuf.put(END_OF_BITMAP_OP);// End of bitmap deflateBBuf(out); } diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/avi/AVIWriter.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/avi/AVIWriter.java index 5d4e425..da53438 100755 --- a/org.monte.media/src/main/java/org.monte.media/org/monte/media/avi/AVIWriter.java +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/avi/AVIWriter.java @@ -162,7 +162,8 @@ private int addVideoTrack(Format vf) throws IOException { int tr = addVideoTrack(vf.get(EncodingKey), vf.get(FrameRateKey).getDenominator(), vf.get(FrameRateKey).getNumerator(), vf.get(WidthKey), vf.get(HeightKey), vf.get(DepthKey, 24), - vf.get(KeyFrameIntervalKey, vf.get(FrameRateKey).floor(1).intValue())); + vf.get(KeyFrameIntervalKey, vf.get(FrameRateKey).floor(1).intValue()) + ); setPalette(tr, vf.get(PaletteKey)); setCompressionQuality(tr, vf.get(QualityKey, 1.0f)); return tr; diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/avi/codec/video/DIBCodec.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/avi/codec/video/DIBCodec.java index d55e92e..5ad09e0 100755 --- a/org.monte.media/src/main/java/org.monte.media/org/monte/media/avi/codec/video/DIBCodec.java +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/avi/codec/video/DIBCodec.java @@ -189,6 +189,7 @@ public int encode(Buffer in, Buffer out) { } ByteArrayImageOutputStream tmp = new ByteArrayImageOutputStream(ArrayUtil.reuseByteArray(out.data, 32)); + tmp.clear(); // Handle sub-image // FIXME - Scanline stride must be a multiple of four. diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/avi/codec/video/RunLengthCodec.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/avi/codec/video/RunLengthCodec.java index 263b23a..6646efe 100755 --- a/org.monte.media/src/main/java/org.monte.media/org/monte/media/avi/codec/video/RunLengthCodec.java +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/avi/codec/video/RunLengthCodec.java @@ -171,6 +171,7 @@ private int encode(Buffer in, Buffer out) { } ByteArrayImageOutputStream tmp = new ByteArrayImageOutputStream(ArrayUtil.reuseByteArray(out.data, 32)); + tmp.clear(); // Handle sub-image Rectangle r; diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/api/NotImplementedException.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/api/NotImplementedException.java new file mode 100644 index 0000000..a104932 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/api/NotImplementedException.java @@ -0,0 +1,9 @@ +package org.monte.media.impl.jcodec.api; + +public class NotImplementedException extends RuntimeException { + + public NotImplementedException(String string) { + super(string); + } + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/api/transcode/PixelStore.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/api/transcode/PixelStore.java new file mode 100644 index 0000000..76c9650 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/api/transcode/PixelStore.java @@ -0,0 +1,42 @@ +package org.monte.media.impl.jcodec.api.transcode; + +import org.monte.media.impl.jcodec.common.model.ColorSpace; +import org.monte.media.impl.jcodec.common.model.Picture; + +public interface PixelStore { + public static class LoanerPicture { + private Picture picture; + private int refCnt; + + public LoanerPicture(Picture picture, int refCnt) { + this.picture = picture; + this.refCnt = refCnt; + } + + public Picture getPicture() { + return picture; + } + + public int getRefCnt() { + return refCnt; + } + + public void decRefCnt() { + --refCnt; + } + + public boolean unused() { + return refCnt <= 0; + } + + public void incRefCnt() { + ++refCnt; + } + } + + LoanerPicture getPicture(int width, int height, ColorSpace color); + + void putBack(LoanerPicture frame); + + void retake(LoanerPicture frame); +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/api/transcode/VideoFrameWithPacket.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/api/transcode/VideoFrameWithPacket.java new file mode 100644 index 0000000..0c7bef8 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/api/transcode/VideoFrameWithPacket.java @@ -0,0 +1,39 @@ +package org.monte.media.impl.jcodec.api.transcode; + +import org.monte.media.impl.jcodec.api.transcode.PixelStore.LoanerPicture; +import org.monte.media.impl.jcodec.common.model.Packet; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class VideoFrameWithPacket implements Comparable { + private Packet packet; + private LoanerPicture frame; + + public VideoFrameWithPacket(Packet inFrame, LoanerPicture dec2) { + this.packet = inFrame; + this.frame = dec2; + } + + @Override + public int compareTo(VideoFrameWithPacket arg) { + if (arg == null) + return -1; + else { + long pts1 = packet.getPts(); + long pts2 = arg.packet.getPts(); + return pts1 > pts2 ? 1 : (pts1 == pts2 ? 0 : -1); + } + } + + public Packet getPacket() { + return packet; + } + + public LoanerPicture getFrame() { + return frame; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/common/biari/MConst.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/common/biari/MConst.java new file mode 100644 index 0000000..8f0d1e6 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/common/biari/MConst.java @@ -0,0 +1,35 @@ +package org.monte.media.impl.jcodec.codecs.common.biari; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Shared constants for H264 CABAC M encoder/decoder + * + * @author The JCodec project + */ +public class MConst { + public static final int[][] rangeLPS = new int[][]{ + {128, 128, 128, 123, 116, 111, 105, 100, 95, 90, 85, 81, 77, 73, 69, 66, 62, 59, 56, 53, 51, 48, 46, 43, + 41, 39, 37, 35, 33, 32, 30, 29, 27, 26, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 14, 13, 12, 12, + 11, 11, 10, 10, 9, 9, 8, 8, 7, 7, 7, 6, 6, 6, 2 + + }, + {176, 167, 158, 150, 142, 135, 128, 122, 116, 110, 104, 99, 94, 89, 85, 80, 76, 72, 69, 65, 62, 59, 56, + 53, 50, 48, 45, 43, 41, 39, 37, 35, 33, 31, 30, 28, 27, 26, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, + 14, 14, 13, 12, 12, 11, 11, 10, 9, 9, 9, 8, 8, 7, 7, 2 + + }, + {208, 197, 187, 178, 169, 160, 152, 144, 137, 130, 123, 117, 111, 105, 100, 95, 90, 86, 81, 77, 73, 69, + 66, 63, 59, 56, 54, 51, 48, 46, 43, 41, 39, 37, 35, 33, 32, 30, 29, 27, 26, 25, 23, 22, 21, 20, 19, + 18, 17, 16, 15, 15, 14, 13, 12, 12, 11, 11, 10, 10, 9, 9, 8, 2 + + }, + {240, 227, 216, 205, 195, 185, 175, 166, 158, 150, 142, 135, 128, 122, 116, 110, 104, 99, 94, 89, 85, 80, + 76, 72, 69, 65, 62, 59, 56, 53, 50, 48, 45, 43, 41, 39, 37, 35, 33, 31, 30, 28, 27, 25, 24, 23, 22, + 21, 20, 19, 18, 17, 16, 15, 14, 14, 13, 12, 12, 11, 11, 10, 9, 2}}; + + public static final int[] transitLPS = new int[]{0, 0, 1, 2, 2, 4, 4, 5, 6, 7, 8, 9, 9, 11, 11, 12, 13, 13, 15, + 15, 16, 16, 18, 18, 19, 19, 21, 21, 22, 22, 23, 24, 24, 25, 26, 26, 27, 27, 28, 29, 29, 30, 30, 30, 31, 32, + 32, 33, 33, 33, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 63}; +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/common/biari/MDecoder.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/common/biari/MDecoder.java new file mode 100644 index 0000000..a0d3a14 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/common/biari/MDecoder.java @@ -0,0 +1,163 @@ +package org.monte.media.impl.jcodec.codecs.common.biari; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * H264 CABAC M-Coder ( decoder module ) + * + * @author The JCodec project + */ +public class MDecoder { + + private ByteBuffer _in; + private int range; + private int code; + private int nBitsPending; + private int[][] cm; + + public MDecoder(ByteBuffer _in, int[][] cm) { + this._in = _in; + this.range = 510; + this.cm = cm; + + initCodeRegister(); + } + + /** + * Initializes code register. Loads 9 bits from the stream into working area + * of code register ( bits 8 - 16) leaving 7 bits in the pending area of + * code register (bits 0 - 7) + * + * @throws IOException + */ + protected void initCodeRegister() { + readOneByte(); + if (nBitsPending != 8) + throw new RuntimeException("Empty stream"); + code <<= 8; + readOneByte(); + code <<= 1; + nBitsPending -= 9; + } + + protected void readOneByte() { + if (!_in.hasRemaining()) + return; + int b = _in.get() & 0xff; + code |= b; + nBitsPending += 8; + } + + /** + * Decodes one bin from arithmetice code word + *

+ * //@param cm + * + * @return + * @throws IOException + */ + public int decodeBin(int m) { + int bin; + + int qIdx = (range >> 6) & 0x3; + int rLPS = MConst.rangeLPS[qIdx][cm[0][m]]; + range -= rLPS; + int rs8 = range << 8; + + if (code < rs8) { + // MPS + if (cm[0][m] < 62) + cm[0][m]++; + + renormalize(); + + bin = cm[1][m]; + } else { + // LPS + range = rLPS; + code -= rs8; + + renormalize(); + + bin = 1 - cm[1][m]; + + if (cm[0][m] == 0) + cm[1][m] = 1 - cm[1][m]; + + cm[0][m] = MConst.transitLPS[cm[0][m]]; + } + +// System.out.println("CABAC BIT [" + m + "]: " + bin); + return bin; + } + + /** + * Special decoding process for 'end of slice' flag. Uses probability state + * 63. + * + * @param cm + * @return + * @throws IOException + */ + public int decodeFinalBin() { + range -= 2; + + if (code < (range << 8)) { + renormalize(); +// System.out.println("CABAC BIT [-2]: 0"); + return 0; + } else { +// System.out.println("CABAC BIT [-2]: 1"); + return 1; + } + } + + /** + * Special decoding process for symbols with uniform distribution + * + * @return + * @throws IOException + */ + public int decodeBinBypass() { + code <<= 1; + + --nBitsPending; + if (nBitsPending <= 0) + readOneByte(); + + int tmp = code - (range << 8); + if (tmp < 0) { +// System.out.println("CABAC BIT [-1]: 0"); + return 0; + } else { +// System.out.println("CABAC BIT [-1]: 1"); + code = tmp; + return 1; + } + } + + /** + * Shifts the current interval to either 1/2 or 0 (code = (code << 1) & + * 0x1ffff) and scales it by 2 (range << 1). + *

+ * Reads new byte from the input stream into code value if there are no more + * bits pending + * + * @throws IOException + */ + private void renormalize() { + while (range < 256) { + range <<= 1; + code <<= 1; + code &= 0x1ffff; + + --nBitsPending; + if (nBitsPending <= 0) + readOneByte(); + } + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/common/biari/MEncoder.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/common/biari/MEncoder.java new file mode 100644 index 0000000..2564c14 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/common/biari/MEncoder.java @@ -0,0 +1,163 @@ +package org.monte.media.impl.jcodec.codecs.common.biari; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * H264 CABAC M-encoder + * + * @author The JCodec project + */ +public class MEncoder { + private ByteBuffer out; + + // Encoder state + private int range; + private int offset; + + // Carry propagation related + private int onesOutstanding; + private boolean zeroBorrowed; + + // Output related + private int outReg; + private int bitsInOutReg; + + private int[][] models; + + public MEncoder(ByteBuffer out, int[][] models) { + range = 510; + + this.models = models; + this.out = out; + } + + /** + * Encodes one bin in normal mode using supplied context model + * + * @param bin + * @param cm + * @throws IOException + */ + public void encodeBin(int model, int bin) { + + int qs = (range >> 6) & 0x3; + int rangeLPS = MConst.rangeLPS[qs][models[0][model]]; + range -= rangeLPS; + + if (bin != models[1][model]) { + offset += range; + range = rangeLPS; + if (models[0][model] == 0) + models[1][model] = 1 - models[1][model]; + + models[0][model] = MConst.transitLPS[models[0][model]]; + } else { + if (models[0][model] < 62) + models[0][model]++; + } + + renormalize(); + } + + /** + * Codes one bin in bypass mode for symbols with uniform probability + * distribution + * + * @param bin + * @throws IOException + */ + public void encodeBinBypass(int bin) { + offset <<= 1; + if (bin == 1) { + offset += range; + } + + if ((offset & 0x400) != 0) { + flushOutstanding(1); + offset &= 0x3ff; + } else if ((offset & 0x200) != 0) { + offset &= 0x1ff; + ++onesOutstanding; + } else { + flushOutstanding(0); + } + } + + /** + * Codes termination flag. Range for LPS is preset to be 2 + * + * @param bin + * @throws IOException + */ + public void encodeBinFinal(int bin) { + range -= 2; + if (bin == 0) { + renormalize(); + } else { + offset += range; + range = 2; + renormalize(); + } + } + + public void finishEncoding() { + flushOutstanding((offset >> 9) & 0x1); + putBit((offset >> 8) & 0x1); + stuffBits(); + } + + private void renormalize() { + while (range < 256) { + if (offset < 256) { + flushOutstanding(0); + } else if (offset < 512) { + offset &= 0xff; + ++onesOutstanding; + } else { + offset &= 0x1ff; + flushOutstanding(1); + } + + range <<= 1; + offset <<= 1; + } + } + + private void flushOutstanding(int hasCarry) { + if (zeroBorrowed) + putBit(hasCarry); + + int trailingBit = 1 - hasCarry; + for (; onesOutstanding > 0; onesOutstanding--) + putBit(trailingBit); + + zeroBorrowed = true; + } + + private void putBit(int bit) { + outReg = (outReg << 1) | bit; + ++bitsInOutReg; + + if (bitsInOutReg == 8) { + out.put((byte) outReg); + outReg = 0; + bitsInOutReg = 0; + } + } + + private void stuffBits() { + if (bitsInOutReg == 0) { + out.put((byte) 128); + } else { + outReg = (outReg << 1) | 1; + outReg <<= (8 - (bitsInOutReg + 1)); + out.put((byte) outReg); + outReg = 0; + bitsInOutReg = 0; + } + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Const.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Const.java new file mode 100644 index 0000000..1478b4a --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Const.java @@ -0,0 +1,649 @@ +package org.monte.media.impl.jcodec.codecs.h264; + +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; +import org.monte.media.impl.jcodec.common.io.VLC; +import org.monte.media.impl.jcodec.common.io.VLCBuilder; +import org.monte.media.impl.jcodec.common.model.Picture; + +import java.util.Arrays; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.Bi; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.Direct; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.L0; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.L1; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class H264Const { + + public final static VLC[] CoeffToken = new VLC[10]; + public final static VLC coeffTokenChromaDCY420; + public final static VLC coeffTokenChromaDCY422; + public final static VLC[] run; + public static int[] lambda = new int[]{14, 18, 23, 29, 36, 46, 58, 73, 91, 115, 145, 183, 230, 290, 366, 461, 581, + 731, 922, 1161, 1463, 1843, 2322, 2926, 3686, 4645, 5852, 7373, 9289, 11704, 14746, 18578, 23407, 29491, + 37157, 46814, 58982, 74313, 93629, 117965, 148626, 187257, 235930, 297253, 374515, 471859, 594505, 749030, + 943718, 1189011, 1498060, 1887437, 2378021, 2996119, 3774874, 4756043, 5992238, 7549747, 9512085, 11984477, + 15099494, 19024171, 23968953, 30198989, 38048342, 47937907, 60397978, 76096683, 95875813, 120795955, + 120795955, 120795955, 120795955, 120795955, 120795955, 120795955, 120795955, 120795955, 120795955, + 120795955, 120795955, 120795955}; + + static { + VLCBuilder vbl = new VLCBuilder(); + + vbl.set((0 << 4) | 0, "1"); + + vbl.set(coeffToken(1, 0), "000101"); + vbl.set(coeffToken(1, 1), "01"); + vbl.set(coeffToken(2, 0), "00000111"); + vbl.set(coeffToken(2, 1), "000100"); + vbl.set(coeffToken(2, 2), "001"); + vbl.set(coeffToken(3, 0), "000000111"); + vbl.set(coeffToken(3, 1), "00000110"); + vbl.set(coeffToken(3, 2), "0000101"); + vbl.set(coeffToken(3, 3), "00011"); + vbl.set(coeffToken(4, 0), "0000000111"); + vbl.set(coeffToken(4, 1), "000000110"); + vbl.set(coeffToken(4, 2), "00000101"); + vbl.set(coeffToken(4, 3), "000011"); + vbl.set(coeffToken(5, 0), "00000000111"); + vbl.set(coeffToken(5, 1), "0000000110"); + vbl.set(coeffToken(5, 2), "000000101"); + vbl.set(coeffToken(5, 3), "0000100"); + vbl.set(coeffToken(6, 0), "0000000001111"); + vbl.set(coeffToken(6, 1), "00000000110"); + vbl.set(coeffToken(6, 2), "0000000101"); + vbl.set(coeffToken(6, 3), "00000100"); + vbl.set(coeffToken(7, 0), "0000000001011"); + vbl.set(coeffToken(7, 1), "0000000001110"); + vbl.set(coeffToken(7, 2), "00000000101"); + vbl.set(coeffToken(7, 3), "000000100"); + vbl.set(coeffToken(8, 0), "0000000001000"); + vbl.set(coeffToken(8, 1), "0000000001010"); + vbl.set(coeffToken(8, 2), "0000000001101"); + vbl.set(coeffToken(8, 3), "0000000100"); + vbl.set(coeffToken(9, 0), "00000000001111"); + vbl.set(coeffToken(9, 1), "00000000001110"); + vbl.set(coeffToken(9, 2), "0000000001001"); + vbl.set(coeffToken(9, 3), "00000000100"); + vbl.set(coeffToken(10, 0), "00000000001011"); + vbl.set(coeffToken(10, 1), "00000000001010"); + vbl.set(coeffToken(10, 2), "00000000001101"); + vbl.set(coeffToken(10, 3), "0000000001100"); + vbl.set(coeffToken(11, 0), "000000000001111"); + vbl.set(coeffToken(11, 1), "000000000001110"); + vbl.set(coeffToken(11, 2), "00000000001001"); + vbl.set(coeffToken(11, 3), "00000000001100"); + vbl.set(coeffToken(12, 0), "000000000001011"); + vbl.set(coeffToken(12, 1), "000000000001010"); + vbl.set(coeffToken(12, 2), "000000000001101"); + vbl.set(coeffToken(12, 3), "00000000001000"); + vbl.set(coeffToken(13, 0), "0000000000001111"); + vbl.set(coeffToken(13, 1), "000000000000001"); + vbl.set(coeffToken(13, 2), "000000000001001"); + vbl.set(coeffToken(13, 3), "000000000001100"); + vbl.set(coeffToken(14, 0), "0000000000001011"); + vbl.set(coeffToken(14, 1), "0000000000001110"); + vbl.set(coeffToken(14, 2), "0000000000001101"); + vbl.set(coeffToken(14, 3), "000000000001000"); + vbl.set(coeffToken(15, 0), "0000000000000111"); + vbl.set(coeffToken(15, 1), "0000000000001010"); + vbl.set(coeffToken(15, 2), "0000000000001001"); + vbl.set(coeffToken(15, 3), "0000000000001100"); + vbl.set(coeffToken(16, 0), "0000000000000100"); + vbl.set(coeffToken(16, 1), "0000000000000110"); + vbl.set(coeffToken(16, 2), "0000000000000101"); + vbl.set(coeffToken(16, 3), "0000000000001000"); + CoeffToken[0] = CoeffToken[1] = vbl.getVLC(); + } + + static { + VLCBuilder vbl = new VLCBuilder(); + vbl.set(coeffToken(0, 0), "11"); + vbl.set(coeffToken(1, 0), "001011"); + vbl.set(coeffToken(1, 1), "10"); + vbl.set(coeffToken(2, 0), "000111"); + vbl.set(coeffToken(2, 1), "00111"); + vbl.set(coeffToken(2, 2), "011"); + vbl.set(coeffToken(3, 0), "0000111"); + vbl.set(coeffToken(3, 1), "001010"); + vbl.set(coeffToken(3, 2), "001001"); + vbl.set(coeffToken(3, 3), "0101"); + vbl.set(coeffToken(4, 0), "00000111"); + vbl.set(coeffToken(4, 1), "000110"); + vbl.set(coeffToken(4, 2), "000101"); + vbl.set(coeffToken(4, 3), "0100"); + vbl.set(coeffToken(5, 0), "00000100"); + vbl.set(coeffToken(5, 1), "0000110"); + vbl.set(coeffToken(5, 2), "0000101"); + vbl.set(coeffToken(5, 3), "00110"); + vbl.set(coeffToken(6, 0), "000000111"); + vbl.set(coeffToken(6, 1), "00000110"); + vbl.set(coeffToken(6, 2), "00000101"); + vbl.set(coeffToken(6, 3), "001000"); + vbl.set(coeffToken(7, 0), "00000001111"); + vbl.set(coeffToken(7, 1), "000000110"); + vbl.set(coeffToken(7, 2), "000000101"); + vbl.set(coeffToken(7, 3), "000100"); + vbl.set(coeffToken(8, 0), "00000001011"); + vbl.set(coeffToken(8, 1), "00000001110"); + vbl.set(coeffToken(8, 2), "00000001101"); + vbl.set(coeffToken(8, 3), "0000100"); + vbl.set(coeffToken(9, 0), "000000001111"); + vbl.set(coeffToken(9, 1), "00000001010"); + vbl.set(coeffToken(9, 2), "00000001001"); + vbl.set(coeffToken(9, 3), "000000100"); + vbl.set(coeffToken(10, 0), "000000001011"); + vbl.set(coeffToken(10, 1), "000000001110"); + vbl.set(coeffToken(10, 2), "000000001101"); + vbl.set(coeffToken(10, 3), "00000001100"); + vbl.set(coeffToken(11, 0), "000000001000"); + vbl.set(coeffToken(11, 1), "000000001010"); + vbl.set(coeffToken(11, 2), "000000001001"); + vbl.set(coeffToken(11, 3), "00000001000"); + vbl.set(coeffToken(12, 0), "0000000001111"); + vbl.set(coeffToken(12, 1), "0000000001110"); + vbl.set(coeffToken(12, 2), "0000000001101"); + vbl.set(coeffToken(12, 3), "000000001100"); + vbl.set(coeffToken(13, 0), "0000000001011"); + vbl.set(coeffToken(13, 1), "0000000001010"); + vbl.set(coeffToken(13, 2), "0000000001001"); + vbl.set(coeffToken(13, 3), "0000000001100"); + vbl.set(coeffToken(14, 0), "0000000000111"); + vbl.set(coeffToken(14, 1), "00000000001011"); + vbl.set(coeffToken(14, 2), "0000000000110"); + vbl.set(coeffToken(14, 3), "0000000001000"); + vbl.set(coeffToken(15, 0), "00000000001001"); + vbl.set(coeffToken(15, 1), "00000000001000"); + vbl.set(coeffToken(15, 2), "00000000001010"); + vbl.set(coeffToken(15, 3), "0000000000001"); + vbl.set(coeffToken(16, 0), "00000000000111"); + vbl.set(coeffToken(16, 1), "00000000000110"); + vbl.set(coeffToken(16, 2), "00000000000101"); + vbl.set(coeffToken(16, 3), "00000000000100"); + CoeffToken[2] = CoeffToken[3] = vbl.getVLC(); + } + + static { + VLCBuilder vbl = new VLCBuilder(); + + vbl.set(coeffToken(0, 0), "1111"); + vbl.set(coeffToken(1, 0), "001111"); + vbl.set(coeffToken(1, 1), "1110"); + vbl.set(coeffToken(2, 0), "001011"); + vbl.set(coeffToken(2, 1), "01111"); + vbl.set(coeffToken(2, 2), "1101"); + vbl.set(coeffToken(3, 0), "001000"); + vbl.set(coeffToken(3, 1), "01100"); + vbl.set(coeffToken(3, 2), "01110"); + vbl.set(coeffToken(3, 3), "1100"); + vbl.set(coeffToken(4, 0), "0001111"); + vbl.set(coeffToken(4, 1), "01010"); + vbl.set(coeffToken(4, 2), "01011"); + vbl.set(coeffToken(4, 3), "1011"); + vbl.set(coeffToken(5, 0), "0001011"); + vbl.set(coeffToken(5, 1), "01000"); + vbl.set(coeffToken(5, 2), "01001"); + vbl.set(coeffToken(5, 3), "1010"); + vbl.set(coeffToken(6, 0), "0001001"); + vbl.set(coeffToken(6, 1), "001110"); + vbl.set(coeffToken(6, 2), "001101"); + vbl.set(coeffToken(6, 3), "1001"); + vbl.set(coeffToken(7, 0), "0001000"); + vbl.set(coeffToken(7, 1), "001010"); + vbl.set(coeffToken(7, 2), "001001"); + vbl.set(coeffToken(7, 3), "1000"); + vbl.set(coeffToken(8, 0), "00001111"); + vbl.set(coeffToken(8, 1), "0001110"); + vbl.set(coeffToken(8, 2), "0001101"); + vbl.set(coeffToken(8, 3), "01101"); + vbl.set(coeffToken(9, 0), "00001011"); + vbl.set(coeffToken(9, 1), "00001110"); + vbl.set(coeffToken(9, 2), "0001010"); + vbl.set(coeffToken(9, 3), "001100"); + vbl.set(coeffToken(10, 0), "000001111"); + vbl.set(coeffToken(10, 1), "00001010"); + vbl.set(coeffToken(10, 2), "00001101"); + vbl.set(coeffToken(10, 3), "0001100"); + vbl.set(coeffToken(11, 0), "000001011"); + vbl.set(coeffToken(11, 1), "000001110"); + vbl.set(coeffToken(11, 2), "00001001"); + vbl.set(coeffToken(11, 3), "00001100"); + vbl.set(coeffToken(12, 0), "000001000"); + vbl.set(coeffToken(12, 1), "000001010"); + vbl.set(coeffToken(12, 2), "000001101"); + vbl.set(coeffToken(12, 3), "00001000"); + vbl.set(coeffToken(13, 0), "0000001101"); + vbl.set(coeffToken(13, 1), "000000111"); + vbl.set(coeffToken(13, 2), "000001001"); + vbl.set(coeffToken(13, 3), "000001100"); + vbl.set(coeffToken(14, 0), "0000001001"); + vbl.set(coeffToken(14, 1), "0000001100"); + vbl.set(coeffToken(14, 2), "0000001011"); + vbl.set(coeffToken(14, 3), "0000001010"); + vbl.set(coeffToken(15, 0), "0000000101"); + vbl.set(coeffToken(15, 1), "0000001000"); + vbl.set(coeffToken(15, 2), "0000000111"); + vbl.set(coeffToken(15, 3), "0000000110"); + vbl.set(coeffToken(16, 0), "0000000001"); + vbl.set(coeffToken(16, 1), "0000000100"); + vbl.set(coeffToken(16, 2), "0000000011"); + vbl.set(coeffToken(16, 3), "0000000010"); + CoeffToken[4] = CoeffToken[5] = CoeffToken[6] = CoeffToken[7] = vbl.getVLC(); + } + + static { + VLCBuilder vbl = new VLCBuilder(); + vbl.set(coeffToken(0, 0), "000011"); + vbl.set(coeffToken(1, 0), "000000"); + vbl.set(coeffToken(1, 1), "000001"); + vbl.set(coeffToken(2, 0), "000100"); + vbl.set(coeffToken(2, 1), "000101"); + vbl.set(coeffToken(2, 2), "000110"); + vbl.set(coeffToken(3, 0), "001000"); + vbl.set(coeffToken(3, 1), "001001"); + vbl.set(coeffToken(3, 2), "001010"); + vbl.set(coeffToken(3, 3), "001011"); + vbl.set(coeffToken(4, 0), "001100"); + vbl.set(coeffToken(4, 1), "001101"); + vbl.set(coeffToken(4, 2), "001110"); + vbl.set(coeffToken(4, 3), "001111"); + vbl.set(coeffToken(5, 0), "010000"); + vbl.set(coeffToken(5, 1), "010001"); + vbl.set(coeffToken(5, 2), "010010"); + vbl.set(coeffToken(5, 3), "010011"); + vbl.set(coeffToken(6, 0), "010100"); + vbl.set(coeffToken(6, 1), "010101"); + vbl.set(coeffToken(6, 2), "010110"); + vbl.set(coeffToken(6, 3), "010111"); + vbl.set(coeffToken(7, 0), "011000"); + vbl.set(coeffToken(7, 1), "011001"); + vbl.set(coeffToken(7, 2), "011010"); + vbl.set(coeffToken(7, 3), "011011"); + vbl.set(coeffToken(8, 0), "011100"); + vbl.set(coeffToken(8, 1), "011101"); + vbl.set(coeffToken(8, 2), "011110"); + vbl.set(coeffToken(8, 3), "011111"); + vbl.set(coeffToken(9, 0), "100000"); + vbl.set(coeffToken(9, 1), "100001"); + vbl.set(coeffToken(9, 2), "100010"); + vbl.set(coeffToken(9, 3), "100011"); + vbl.set(coeffToken(10, 0), "100100"); + vbl.set(coeffToken(10, 1), "100101"); + vbl.set(coeffToken(10, 2), "100110"); + vbl.set(coeffToken(10, 3), "100111"); + vbl.set(coeffToken(11, 0), "101000"); + vbl.set(coeffToken(11, 1), "101001"); + vbl.set(coeffToken(11, 2), "101010"); + vbl.set(coeffToken(11, 3), "101011"); + vbl.set(coeffToken(12, 0), "101100"); + vbl.set(coeffToken(12, 1), "101101"); + vbl.set(coeffToken(12, 2), "101110"); + vbl.set(coeffToken(12, 3), "101111"); + vbl.set(coeffToken(13, 0), "110000"); + vbl.set(coeffToken(13, 1), "110001"); + vbl.set(coeffToken(13, 2), "110010"); + vbl.set(coeffToken(13, 3), "110011"); + vbl.set(coeffToken(14, 0), "110100"); + vbl.set(coeffToken(14, 1), "110101"); + vbl.set(coeffToken(14, 2), "110110"); + vbl.set(coeffToken(14, 3), "110111"); + vbl.set(coeffToken(15, 0), "111000"); + vbl.set(coeffToken(15, 1), "111001"); + vbl.set(coeffToken(15, 2), "111010"); + vbl.set(coeffToken(15, 3), "111011"); + vbl.set(coeffToken(16, 0), "111100"); + vbl.set(coeffToken(16, 1), "111101"); + vbl.set(coeffToken(16, 2), "111110"); + vbl.set(coeffToken(16, 3), "111111"); + CoeffToken[8] = vbl.getVLC(); + } + + static { + VLCBuilder vbl = new VLCBuilder(); + vbl.set(coeffToken(0, 0), "01"); + vbl.set(coeffToken(1, 0), "000111"); + vbl.set(coeffToken(1, 1), "1"); + vbl.set(coeffToken(2, 0), "000100"); + vbl.set(coeffToken(2, 1), "000110"); + vbl.set(coeffToken(2, 2), "001"); + vbl.set(coeffToken(3, 0), "000011"); + vbl.set(coeffToken(3, 1), "0000011"); + vbl.set(coeffToken(3, 2), "0000010"); + vbl.set(coeffToken(3, 3), "000101"); + vbl.set(coeffToken(4, 0), "000010"); + vbl.set(coeffToken(4, 1), "00000011"); + vbl.set(coeffToken(4, 2), "00000010"); + vbl.set(coeffToken(4, 3), "0000000"); + coeffTokenChromaDCY420 = vbl.getVLC(); + } + + static { + VLCBuilder vbl = new VLCBuilder(); + vbl.set(coeffToken(0, 0), "1"); + vbl.set(coeffToken(1, 0), "0001111"); + vbl.set(coeffToken(1, 1), "01"); + vbl.set(coeffToken(2, 0), "0001110"); + vbl.set(coeffToken(2, 1), "0001101"); + vbl.set(coeffToken(2, 2), "001"); + vbl.set(coeffToken(3, 0), "000000111"); + vbl.set(coeffToken(3, 1), "0001100"); + vbl.set(coeffToken(3, 2), "0001011"); + vbl.set(coeffToken(3, 3), "00001"); + vbl.set(coeffToken(4, 0), "000000110"); + vbl.set(coeffToken(4, 1), "000000101"); + vbl.set(coeffToken(4, 2), "0001010"); + vbl.set(coeffToken(4, 3), "000001"); + vbl.set(coeffToken(5, 0), "0000000111"); + vbl.set(coeffToken(5, 1), "0000000110"); + vbl.set(coeffToken(5, 2), "000000100"); + vbl.set(coeffToken(5, 3), "0001001"); + vbl.set(coeffToken(6, 0), "00000000111"); + vbl.set(coeffToken(6, 1), "00000000110"); + vbl.set(coeffToken(6, 2), "0000000101"); + vbl.set(coeffToken(6, 3), "0001000"); + vbl.set(coeffToken(7, 0), "000000000111"); + vbl.set(coeffToken(7, 1), "000000000110"); + vbl.set(coeffToken(7, 2), "00000000101"); + vbl.set(coeffToken(7, 3), "0000000100"); + vbl.set(coeffToken(8, 0), "0000000000111"); + vbl.set(coeffToken(8, 1), "000000000101"); + vbl.set(coeffToken(8, 2), "000000000100"); + vbl.set(coeffToken(8, 3), "00000000100"); + coeffTokenChromaDCY422 = vbl.getVLC(); + } + + static { + run = new VLC[]{new VLCBuilder().set(0, "1").set(1, "0").getVLC(), + new VLCBuilder().set(0, "1").set(1, "01").set(2, "00").getVLC(), + new VLCBuilder().set(0, "11").set(1, "10").set(2, "01").set(3, "00").getVLC(), + new VLCBuilder().set(0, "11").set(1, "10").set(2, "01").set(3, "001").set(4, "000").getVLC(), + new VLCBuilder().set(0, "11").set(1, "10").set(2, "011").set(3, "010").set(4, "001").set(5, "000") + .getVLC(), + new VLCBuilder().set(0, "11").set(1, "000").set(2, "001").set(3, "011").set(4, "010").set(5, "101") + .set(6, "100").getVLC(), + new VLCBuilder().set(0, "111").set(1, "110").set(2, "101").set(3, "100").set(4, "011").set(5, "010") + .set(6, "001").set(7, "0001").set(8, "00001").set(9, "000001").set(10, "0000001") + .set(11, "00000001").set(12, "000000001").set(13, "0000000001").set(14, "00000000001") + .getVLC()}; + } + + public final static VLC[] totalZeros16 = { + + new VLCBuilder().set(0, "1").set(1, "011").set(2, "010").set(3, "0011").set(4, "0010").set(5, "00011") + .set(6, "00010").set(7, "000011").set(8, "000010").set(9, "0000011").set(10, "0000010") + .set(11, "00000011").set(12, "00000010").set(13, "000000011").set(14, "000000010") + .set(15, "000000001").getVLC(), + + new VLCBuilder().set(0, "111").set(1, "110").set(2, "101").set(3, "100").set(4, "011").set(5, "0101") + .set(6, "0100").set(7, "0011").set(8, "0010").set(9, "00011").set(10, "00010").set(11, "000011") + .set(12, "000010").set(13, "000001").set(14, "000000").getVLC(), + + new VLCBuilder().set(0, "0101").set(1, "111").set(2, "110").set(3, "101").set(4, "0100").set(5, "0011") + .set(6, "100").set(7, "011").set(8, "0010").set(9, "00011").set(10, "00010").set(11, "000001") + .set(12, "00001").set(13, "000000").getVLC(), + + new VLCBuilder().set(0, "00011").set(1, "111").set(2, "0101").set(3, "0100").set(4, "110").set(5, "101") + .set(6, "100").set(7, "0011").set(8, "011").set(9, "0010").set(10, "00010").set(11, "00001") + .set(12, "00000").getVLC(), + + new VLCBuilder().set(0, "0101").set(1, "0100").set(2, "0011").set(3, "111").set(4, "110").set(5, "101") + .set(6, "100").set(7, "011").set(8, "0010").set(9, "00001").set(10, "0001").set(11, "00000") + .getVLC(), + + new VLCBuilder().set(0, "000001").set(1, "00001").set(2, "111").set(3, "110").set(4, "101").set(5, "100") + .set(6, "011").set(7, "010").set(8, "0001").set(9, "001").set(10, "000000").getVLC(), + + new VLCBuilder().set(0, "000001").set(1, "00001").set(2, "101").set(3, "100").set(4, "011").set(5, "11") + .set(6, "010").set(7, "0001").set(8, "001").set(9, "000000").getVLC(), + + new VLCBuilder().set(0, "000001").set(1, "0001").set(2, "00001").set(3, "011").set(4, "11").set(5, "10") + .set(6, "010").set(7, "001").set(8, "000000").getVLC(), + + new VLCBuilder().set(0, "000001").set(1, "000000").set(2, "0001").set(3, "11").set(4, "10").set(5, "001") + .set(6, "01").set(7, "00001").getVLC(), + + new VLCBuilder().set(0, "00001").set(1, "00000").set(2, "001").set(3, "11").set(4, "10").set(5, "01") + .set(6, "0001").getVLC(), + + new VLCBuilder().set(0, "0000").set(1, "0001").set(2, "001").set(3, "010").set(4, "1").set(5, "011") + .getVLC(), + + new VLCBuilder().set(0, "0000").set(1, "0001").set(2, "01").set(3, "1").set(4, "001").getVLC(), + + new VLCBuilder().set(0, "000").set(1, "001").set(2, "1").set(3, "01").getVLC(), + + new VLCBuilder().set(0, "00").set(1, "01").set(2, "1").getVLC(), + + new VLCBuilder().set(0, "0").set(1, "1").getVLC()}; + + public final static VLC[] totalZeros4 = { + new VLCBuilder().set(0, "1").set(1, "01").set(2, "001").set(3, "000").getVLC(), + + new VLCBuilder().set(0, "1").set(1, "01").set(2, "00").getVLC(), + + new VLCBuilder().set(0, "1").set(1, "0").getVLC()}; + + public final static VLC[] totalZeros8 = { + new VLCBuilder().set(0, "1").set(1, "010").set(2, "011").set(3, "0010").set(4, "0011").set(5, "0001") + .set(6, "00001").set(7, "00000").getVLC(), + + new VLCBuilder().set(0, "000").set(1, "01").set(2, "001").set(3, "100").set(4, "101").set(5, "110") + .set(6, "111").getVLC(), + + new VLCBuilder().set(0, "000").set(1, "001").set(2, "01").set(3, "10").set(4, "110").set(5, "111").getVLC(), + + new VLCBuilder().set(0, "110").set(1, "00").set(2, "01").set(3, "10").set(4, "111").getVLC(), + + new VLCBuilder().set(0, "00").set(1, "01").set(2, "10").set(3, "11").getVLC(), + + new VLCBuilder().set(0, "00").set(1, "01").set(2, "1").getVLC(), + + new VLCBuilder().set(0, "0").set(1, "1").getVLC()}; + + public enum PartPred { + L0, L1, Bi, Direct; + } + + public final static PartPred[][] bPredModes = {null, {PartPred.L0}, {PartPred.L1}, {PartPred.Bi}, + {PartPred.L0, PartPred.L0}, {PartPred.L0, PartPred.L0}, {PartPred.L1, PartPred.L1}, + {PartPred.L1, PartPred.L1}, {PartPred.L0, PartPred.L1}, {PartPred.L0, PartPred.L1}, + {PartPred.L1, PartPred.L0}, {PartPred.L1, PartPred.L0}, {PartPred.L0, PartPred.Bi}, + {PartPred.L0, PartPred.Bi}, {PartPred.L1, PartPred.Bi}, {PartPred.L1, PartPred.Bi}, + {PartPred.Bi, PartPred.L0}, {PartPred.Bi, PartPred.L0}, {PartPred.Bi, PartPred.L1}, + {PartPred.Bi, PartPred.L1}, {PartPred.Bi, PartPred.Bi}, {PartPred.Bi, PartPred.Bi}}; + + public final static MBType[] bMbTypes = {MBType.B_Direct_16x16, MBType.B_L0_16x16, MBType.B_L1_16x16, + MBType.B_Bi_16x16, MBType.B_L0_L0_16x8, MBType.B_L0_L0_8x16, MBType.B_L1_L1_16x8, MBType.B_L1_L1_8x16, + MBType.B_L0_L1_16x8, MBType.B_L0_L1_8x16, MBType.B_L1_L0_16x8, MBType.B_L1_L0_8x16, MBType.B_L0_Bi_16x8, + MBType.B_L0_Bi_8x16, MBType.B_L1_Bi_16x8, MBType.B_L1_Bi_8x16, MBType.B_Bi_L0_16x8, MBType.B_Bi_L0_8x16, + MBType.B_Bi_L1_16x8, MBType.B_Bi_L1_8x16, MBType.B_Bi_Bi_16x8, MBType.B_Bi_Bi_8x16, MBType.B_8x8}; + + public final static int[] bPartW = {0, 16, 16, 16, 16, 8, 16, 8, 16, 8, 16, 8, 16, 8, 16, 8, 16, 8, 16, 8, 16, 8}; + public final static int[] bPartH = {0, 16, 16, 16, 8, 16, 8, 16, 8, 16, 8, 16, 8, 16, 8, 16, 8, 16, 8, 16, 8, 16}; + + public final static int[] BLK_X = new int[]{0, 4, 0, 4, 8, 12, 8, 12, 0, 4, 0, 4, 8, 12, 8, 12}; + public final static int[] BLK_Y = new int[]{0, 0, 4, 4, 0, 0, 4, 4, 8, 8, 12, 12, 8, 8, 12, 12}; + + public final static int[] BLK_8x8_X = new int[]{0, 8, 0, 8}; + public final static int[] BLK_8x8_Y = new int[]{0, 0, 8, 8}; + + public final static int[] BLK_DISP_MAP = {0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 12, 13, 10, 11, 14, 15}; + + public final static int[] MB_DISP_OFF_LEFT = new int[]{0, 1, 0, 1, 2, 3, 2, 3, 0, 1, 0, 1, 2, 3, 2, 3}; + public final static int[] MB_DISP_OFF_TOP = new int[]{0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 3, 3, 2, 2, 3, 3}; + + public final static int[] QP_SCALE_CR = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 34, 35, 35, 36, 36, 37, 37, 37, 38, 38, 38, + 39, 39, 39, 39}; + + public static final Picture NO_PIC = Picture.createPicture(0, 0, null, null); + public static final int[] BLK_8x8_MB_OFF_LUMA = {0, 8, 128, 136}; + public static final int[] BLK_8x8_MB_OFF_CHROMA = {0, 4, 32, 36}; + public static final int[] BLK_4x4_MB_OFF_LUMA = {0, 4, 8, 12, 64, 68, 72, 76, 128, 132, 136, 140, 192, 196, 200, + 204}; + public static final int[] BLK_8x8_IND = {0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 3, 3, 2, 2, 3, 3}; + public static final int[][] BLK8x8_BLOCKS = {{0, 1, 4, 5}, {2, 3, 6, 7}, {8, 9, 12, 13}, + {10, 11, 14, 15}}; + public static final int[][] ARRAY = {{0}, {1}, {2}, {3}}; + + public static final int[] CODED_BLOCK_PATTERN_INTRA_COLOR = new int[]{47, 31, 15, 0, 23, 27, 29, 30, 7, 11, 13, + 14, 39, 43, 45, 46, 16, 3, 5, 10, 12, 19, 21, 26, 28, 35, 37, 42, 44, 1, 2, 4, 8, 17, 18, 20, 24, 6, 9, 22, + 25, 32, 33, 34, 36, 40, 38, 41}; + + public static final int[] CODED_BLOCK_PATTERN_INTRA_COLOR_INV = new int[]{3, 29, 30, 17, 31, 18, 37, 8, 32, 38, + 19, 9, 20, 10, 11, 2, 16, 33, 34, 21, 35, 22, 39, 4, 36, 40, 23, 5, 24, 6, 7, 1, 41, 42, 43, 25, 44, 26, 46, + 12, 45, 47, 27, 13, 28, 14, 15, 0 + }; + + public static final int[] coded_block_pattern_intra_monochrome = new int[]{15, 0, 7, 11, 13, 14, 3, 5, 10, 12, 1, + 2, 4, 8, 6, 9}; + + public static final int[] CODED_BLOCK_PATTERN_INTER_COLOR = new int[]{0, 16, 1, 2, 4, 8, 32, 3, 5, 10, 12, 15, 47, + 7, 11, 13, 14, 6, 9, 31, 35, 37, 42, 44, 33, 34, 36, 40, 39, 43, 45, 46, 17, 18, 20, 24, 19, 21, 26, 28, 23, + 27, 29, 30, 22, 25, 38, 41}; + + private static int[] inverse(int[] arr) { + int[] inv = new int[arr.length]; + for (int i = 0; i < inv.length; i++) { + inv[arr[i]] = i; + } + return inv; + } + + public static final int[] CODED_BLOCK_PATTERN_INTER_COLOR_INV = inverse(CODED_BLOCK_PATTERN_INTER_COLOR); + + public static final int[] coded_block_pattern_inter_monochrome = new int[]{0, 1, 2, 4, 8, 3, 5, 10, 12, 15, 7, 11, + 13, 14, 6, 9}; + + public static final int[] sig_coeff_map_8x8 = {0, 1, 2, 3, 4, 5, 5, 4, 4, 3, 3, 4, 4, 4, 5, 5, 4, 4, 4, 4, 3, 3, 6, + 7, 7, 7, 8, 9, 10, 9, 8, 7, 7, 6, 11, 12, 13, 11, 6, 7, 8, 9, 14, 10, 9, 8, 6, 11, 12, 13, 11, 6, 9, 14, 10, + 9, 11, 12, 13, 11, 14, 10, 12}; + + public static final int[] sig_coeff_map_8x8_mbaff = {0, 1, 1, 2, 2, 3, 3, 4, 5, 6, 7, 7, 7, 8, 4, 5, 6, 9, 10, 10, + 8, 11, 12, 11, 9, 9, 10, 10, 8, 11, 12, 11, 9, 9, 10, 10, 8, 11, 12, 11, 9, 9, 10, 10, 8, 13, 13, 9, 9, 10, + 10, 8, 13, 13, 9, 9, 10, 10, 14, 14, 14, 14, 14}; + + public static final int[] last_sig_coeff_map_8x8 = {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, + 7, 7, 7, 8, 8, 8}; + + public static final int[] identityMapping16 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + public static final int[] identityMapping4 = {0, 1, 2, 3}; + public static final PartPred[] bPartPredModes = {Direct, L0, L1, Bi, L0, L0, L1, L1, Bi, Bi, L0, L1, Bi}; + public static final int[] bSubMbTypes = {0, 0, 0, 0, 1, 2, 1, 2, 1, 2, 3, 3, 3}; + + public static final int[] LUMA_4x4_BLOCK_LUT = new int[256]; + public static final int[] LUMA_4x4_POS_LUT = new int[256]; + public static final int[] LUMA_8x8_BLOCK_LUT = new int[256]; + public static final int[] LUMA_8x8_POS_LUT = new int[256]; + public static final int[] CHROMA_BLOCK_LUT = new int[64]; + public static final int[] CHROMA_POS_LUT = new int[64]; + + public static final int[][] COMP_BLOCK_4x4_LUT = {LUMA_4x4_BLOCK_LUT, CHROMA_BLOCK_LUT, CHROMA_BLOCK_LUT}; + public static final int[][] COMP_POS_4x4_LUT = {LUMA_4x4_POS_LUT, CHROMA_POS_LUT, CHROMA_POS_LUT}; + + public static final int[][] COMP_BLOCK_8x8_LUT = {LUMA_8x8_BLOCK_LUT, CHROMA_BLOCK_LUT, CHROMA_BLOCK_LUT}; + public static final int[][] COMP_POS_8x8_LUT = {LUMA_8x8_POS_LUT, CHROMA_POS_LUT, CHROMA_POS_LUT}; + + static { + int[] tmp = new int[16]; + + for (int blk = 0; blk < 16; blk++) { + for (int i = 0; i < 16; i++) { + tmp[i] = i; + } + putBlk(tmp, BLK_X[blk], BLK_Y[blk], 4, 4, 16, LUMA_4x4_POS_LUT); + Arrays.fill(tmp, blk); + putBlk(tmp, BLK_X[blk], BLK_Y[blk], 4, 4, 16, LUMA_4x4_BLOCK_LUT); + } + for (int blk = 0; blk < 4; blk++) { + for (int i = 0; i < 16; i++) { + tmp[i] = i; + } + putBlk(tmp, BLK_X[blk], BLK_Y[blk], 4, 4, 8, CHROMA_POS_LUT); + Arrays.fill(tmp, blk); + putBlk(tmp, BLK_X[blk], BLK_Y[blk], 4, 4, 8, CHROMA_BLOCK_LUT); + } + tmp = new int[64]; + for (int blk = 0; blk < 4; blk++) { + for (int i = 0; i < 64; i++) { + tmp[i] = i; + } + putBlk(tmp, BLK_8x8_X[blk], BLK_8x8_Y[blk], 8, 8, 16, LUMA_8x8_POS_LUT); + Arrays.fill(tmp, blk); + putBlk(tmp, BLK_8x8_X[blk], BLK_8x8_Y[blk], 8, 8, 16, LUMA_8x8_BLOCK_LUT); + } + } + + private static void putBlk(int[] _in, int blkX, int blkY, int blkW, int blkH, int stride, int[] out) { + for (int line = 0, srcOff = 0, dstOff = blkY * stride + blkX; line < blkH; line++) { + for (int i = 0; i < blkW; i++) + out[dstOff + i] = _in[srcOff + i]; + srcOff += blkW; + dstOff += stride; + } + } + + private static int[][] buildPixSplitMap4x4() { + int[][] result = new int[][]{{0, 1, 2, 3, 16, 17, 18, 19, 32, 33, 34, 35, 48, 49, 50, 51}, new int[16], + new int[16], new int[16], new int[16], new int[16], new int[16], new int[16], new int[16], new int[16], + new int[16], new int[16], new int[16], new int[16], new int[16], new int[16]}; + for (int blkY = 0, blk = 0, off = 0; blkY < 4; ++blkY) { + for (int blkX = 0; blkX < 4; ++blkX, ++blk, off += 4) { + for (int i = 0; i < 16; i++) + result[blk][i] = result[0][i] + off; + } + off += 48; + } + return result; + } + + private static int[][] buildPixSplitMap2x2() { + int[][] result = new int[][]{{0, 1, 2, 3, 8, 9, 10, 11, 16, 17, 18, 19, 24, 25, 26, 27}, new int[16], + new int[16], new int[16]}; + for (int blkY = 0, blk = 0, off = 0; blkY < 2; ++blkY) { + for (int blkX = 0; blkX < 2; ++blkX, ++blk, off += 4) { + for (int i = 0; i < 16; i++) + result[blk][i] = result[0][i] + off; + } + off += 24; + } + return result; + } + + public static boolean usesList(PartPred pred, int l) { + return pred == Bi ? true : (pred == L0 && l == 0 || pred == L1 && l == 1); + } + + public static final int coeffToken(int totalCoeff, int trailingOnes) { + return (totalCoeff << 4) | trailingOnes; + } + + public static final int[][] PIX_MAP_SPLIT_4x4 = buildPixSplitMap4x4(); + public static final int[][] PIX_MAP_SPLIT_2x2 = buildPixSplitMap2x2(); + + public static final int PROFILE_CAVLC_INTRA = 44; + public static final int PROFILE_BASELINE = 66; + public static final int PROFILE_MAIN = 77; + public static final int PROFILE_EXTENDED = 88; + public static final int PROFILE_HIGH = 100; + public static final int PROFILE_HIGH_10 = 110; + public static final int PROFILE_HIGH_422 = 122; + public static final int PROFILE_HIGH_444 = 244; + + public static final int[] defaultScalingList4x4Intra = {6, 13, 13, 20, 20, 20, 28, 28, 28, 28, 32, 32, 32, 37, 37, + 42}; + public static final int[] defaultScalingList4x4Inter = {10, 14, 14, 20, 20, 20, 24, 24, 24, 24, 27, 27, 27, 30, 30, + 34}; + public static final int[] defaultScalingList8x8Intra = {6, 10, 10, 13, 11, 13, 16, 16, 16, 16, 18, 18, 18, 18, 18, + 23, 23, 23, 23, 23, 23, 25, 25, 25, 25, 25, 25, 25, 27, 27, 27, 27, 27, 27, 27, 27, 29, 29, 29, 29, 29, 29, + 29, 31, 31, 31, 31, 31, 31, 33, 33, 33, 33, 33, 36, 36, 36, 36, 38, 38, 38, 40, 40, 42}; + public static final int[] defaultScalingList8x8Inter = {9, 13, 13, 15, 13, 15, 17, 17, 17, 17, 19, 19, 19, 19, 19, + 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22, 22, 24, 24, 24, 24, 24, 24, 24, 24, 25, 25, 25, 25, 25, 25, + 25, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 30, 30, 30, 30, 32, 32, 32, 33, 33, 35}; +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Decoder.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Decoder.java new file mode 100644 index 0000000..b8cda5e --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Decoder.java @@ -0,0 +1,436 @@ +package org.monte.media.impl.jcodec.codecs.h264; + +import org.monte.media.impl.jcodec.codecs.h264.H264Utils.MvList2D; +import org.monte.media.impl.jcodec.codecs.h264.decode.DeblockerInput; +import org.monte.media.impl.jcodec.codecs.h264.decode.FrameReader; +import org.monte.media.impl.jcodec.codecs.h264.decode.SliceDecoder; +import org.monte.media.impl.jcodec.codecs.h264.decode.SliceHeaderReader; +import org.monte.media.impl.jcodec.codecs.h264.decode.SliceReader; +import org.monte.media.impl.jcodec.codecs.h264.decode.deblock.DeblockingFilter; +import org.monte.media.impl.jcodec.codecs.h264.io.model.Frame; +import org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnit; +import org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnitType; +import org.monte.media.impl.jcodec.codecs.h264.io.model.PictureParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.RefPicMarking; +import org.monte.media.impl.jcodec.codecs.h264.io.model.RefPicMarkingIDR; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.common.IntObjectMap; +import org.monte.media.impl.jcodec.common.UsedViaReflection; +import org.monte.media.impl.jcodec.common.VideoCodecMeta; +import org.monte.media.impl.jcodec.common.VideoDecoder; +import org.monte.media.impl.jcodec.common.io.BitReader; +import org.monte.media.impl.jcodec.common.logging.Logger; +import org.monte.media.impl.jcodec.common.model.ColorSpace; +import org.monte.media.impl.jcodec.common.model.Rect; +import org.monte.media.impl.jcodec.common.model.Size; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PROFILE_BASELINE; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PROFILE_HIGH; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PROFILE_MAIN; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.wrap; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * MPEG 4 AVC ( H.264 ) Decoder + *

+ * Conforms to H.264 ( ISO/IEC 14496-10 ) specifications + * + * @author The JCodec project + */ +public class H264Decoder extends VideoDecoder { + + private Frame[] sRefs; + private IntObjectMap lRefs; + private List pictureBuffer; + private POCManager poc; + private FrameReader reader; + private ExecutorService tp; + private boolean threaded; + + public H264Decoder() { + pictureBuffer = new ArrayList(); + poc = new POCManager(); + this.threaded = Runtime.getRuntime().availableProcessors() > 1; + if (threaded) { + tp = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() { + public Thread newThread(Runnable r) { + Thread t = Executors.defaultThreadFactory().newThread(r); + t.setDaemon(true); + return t; + } + }); + } + reader = new FrameReader(); + } + + /** + * Constructs this decoder from a portion of a stream that contains AnnexB + * delimited (00 00 00 01) SPS/PPS NAL units. SPS/PPS NAL units are 0x67 and + * 0x68 respectfully. + * + * @param codecPrivate + */ + public static H264Decoder createH264DecoderFromCodecPrivate(ByteBuffer codecPrivate) { + H264Decoder d = new H264Decoder(); + for (ByteBuffer bb : H264Utils.splitFrame(codecPrivate.duplicate())) { + NALUnit nu = NALUnit.read(bb); + if (nu.type == NALUnitType.SPS) { + d.reader.addSps(bb); + } else if (nu.type == NALUnitType.PPS) { + d.reader.addPps(bb); + } + } + return d; + } + + @Override + public Frame decodeFrame(ByteBuffer data, byte[][] buffer) { + return decodeFrameFromNals(H264Utils.splitFrame(data), buffer); + } + + public Frame decodeFrameFromNals(List nalUnits, byte[][] buffer) { + return new FrameDecoder(this).decodeFrame(nalUnits, buffer); + } + + private static final class SliceDecoderRunnable implements Runnable { + private final SliceReader sliceReader; + private final Frame result; + private FrameDecoder fdec; + + private SliceDecoderRunnable(FrameDecoder fdec, SliceReader sliceReader, Frame result) { + this.fdec = fdec; + this.sliceReader = sliceReader; + this.result = result; + } + + public void run() { + new SliceDecoder(fdec.activeSps, fdec.dec.sRefs, fdec.dec.lRefs, fdec.di, result) + .decodeFromReader(sliceReader); + } + } + + static class FrameDecoder { + private SeqParameterSet activeSps; + private DeblockingFilter filter; + private SliceHeader firstSliceHeader; + private NALUnit firstNu; + private H264Decoder dec; + private DeblockerInput di; + + public FrameDecoder(H264Decoder decoder) { + this.dec = decoder; + } + + public Frame decodeFrame(List nalUnits, byte[][] buffer) { + List sliceReaders = dec.reader.readFrame(nalUnits); + if (sliceReaders == null || sliceReaders.size() == 0) + return null; + final Frame result = init(sliceReaders.get(0), buffer); + if (dec.threaded && sliceReaders.size() > 1) { + List> futures = new ArrayList>(); + for (SliceReader sliceReader : sliceReaders) { + futures.add(dec.tp.submit(new SliceDecoderRunnable(this, sliceReader, result))); + } + + for (Future future : futures) { + waitForSure(future); + } + + } else { + for (SliceReader sliceReader : sliceReaders) { + new SliceDecoder(activeSps, dec.sRefs, dec.lRefs, di, result).decodeFromReader(sliceReader); + } + } + + filter.deblockFrame(result); + + updateReferences(result); + + return result; + } + + private void waitForSure(Future future) { + while (true) { + try { + future.get(); + break; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + private void updateReferences(Frame picture) { + if (firstNu.nal_ref_idc != 0) { + if (firstNu.type == NALUnitType.IDR_SLICE) { + performIDRMarking(firstSliceHeader.refPicMarkingIDR, picture); + } else { + performMarking(firstSliceHeader.refPicMarkingNonIDR, picture); + } + } + } + + private Frame init(SliceReader sliceReader, byte[][] buffer) { + firstNu = sliceReader.getNALUnit(); + + firstSliceHeader = sliceReader.getSliceHeader(); + activeSps = firstSliceHeader.sps; + + validateSupportedFeatures(firstSliceHeader.sps, firstSliceHeader.pps); + + int picWidthInMbs = activeSps.picWidthInMbsMinus1 + 1; + + if (dec.sRefs == null) { + dec.sRefs = new Frame[1 << (firstSliceHeader.sps.log2MaxFrameNumMinus4 + 4)]; + dec.lRefs = new IntObjectMap(); + } + + di = new DeblockerInput(activeSps); + + Frame result = createFrame(activeSps, buffer, firstSliceHeader.frameNum, firstSliceHeader.sliceType, + di.mvs, di.refsUsed, dec.poc.calcPOC(firstSliceHeader, firstNu)); + + filter = new DeblockingFilter(picWidthInMbs, activeSps.bitDepthChromaMinus8 + 8, di); + + return result; + } + + private void validateSupportedFeatures(SeqParameterSet sps, PictureParameterSet pps) { + if (sps.mbAdaptiveFrameFieldFlag) + throw new RuntimeException("Unsupported h264 feature: MBAFF."); + if (sps.bitDepthLumaMinus8 != 0 || sps.bitDepthChromaMinus8 != 0) + throw new RuntimeException("Unsupported h264 feature: High bit depth."); + if (sps.chromaFormatIdc != ColorSpace.YUV420J) + throw new RuntimeException("Unsupported h264 feature: " + sps.chromaFormatIdc + " color."); + if (!sps.frameMbsOnlyFlag || sps.fieldPicFlag) + throw new RuntimeException("Unsupported h264 feature: interlace."); + if (pps.constrainedIntraPredFlag) + throw new RuntimeException("Unsupported h264 feature: constrained intra prediction."); +// if (sps.getScalingMatrix() != null || pps.extended != null && pps.extended.getScalingMatrix() != null) +// throw new RuntimeException("Unsupported h264 feature: scaling list."); + if (sps.qpprimeYZeroTransformBypassFlag) + throw new RuntimeException("Unsupported h264 feature: qprime zero transform bypass."); + if (sps.profileIdc != PROFILE_BASELINE && sps.profileIdc != PROFILE_MAIN && sps.profileIdc != PROFILE_HIGH) + throw new RuntimeException("Unsupported h264 feature: " + sps.profileIdc + " profile."); + } + + public void performIDRMarking(RefPicMarkingIDR refPicMarkingIDR, Frame picture) { + clearAll(); + dec.pictureBuffer.clear(); + + Frame saved = saveRef(picture); + if (refPicMarkingIDR.isUseForlongTerm()) { + dec.lRefs.put(0, saved); + saved.setShortTerm(false); + } else + dec.sRefs[firstSliceHeader.frameNum] = saved; + } + + private Frame saveRef(Frame decoded) { + Frame frame = dec.pictureBuffer.size() > 0 ? dec.pictureBuffer.remove(0) : Frame.createFrame(decoded); + frame.copyFromFrame(decoded); + return frame; + } + + private void releaseRef(Frame picture) { + if (picture != null) { + dec.pictureBuffer.add(picture); + } + } + + public void clearAll() { + for (int i = 0; i < dec.sRefs.length; i++) { + releaseRef(dec.sRefs[i]); + dec.sRefs[i] = null; + } + int[] keys = dec.lRefs.keys(); + for (int i = 0; i < keys.length; i++) { + releaseRef(dec.lRefs.get(keys[i])); + } + dec.lRefs.clear(); + } + + public void performMarking(RefPicMarking refPicMarking, Frame picture) { + Frame saved = saveRef(picture); + + if (refPicMarking != null) { + RefPicMarking.Instruction[] instructions = refPicMarking.getInstructions(); + for (int i = 0; i < instructions.length; i++) { + RefPicMarking.Instruction instr = instructions[i]; + switch (instr.getType()) { + case REMOVE_SHORT: + unrefShortTerm(instr.getArg1()); + break; + case REMOVE_LONG: + unrefLongTerm(instr.getArg1()); + break; + case CONVERT_INTO_LONG: + convert(instr.getArg1(), instr.getArg2()); + break; + case TRUNK_LONG: + truncateLongTerm(instr.getArg1() - 1); + break; + case CLEAR: + clearAll(); + break; + case MARK_LONG: + saveLong(saved, instr.getArg1()); + saved = null; + } + } + } + if (saved != null) + saveShort(saved); + + int maxFrames = 1 << (activeSps.log2MaxFrameNumMinus4 + 4); + if (refPicMarking == null) { + int maxShort = Math.max(1, activeSps.numRefFrames - dec.lRefs.size()); + int min = Integer.MAX_VALUE, num = 0, minFn = 0; + for (int i = 0; i < dec.sRefs.length; i++) { + if (dec.sRefs[i] != null) { + int fnWrap = unwrap(firstSliceHeader.frameNum, dec.sRefs[i].getFrameNo(), maxFrames); + if (fnWrap < min) { + min = fnWrap; + minFn = dec.sRefs[i].getFrameNo(); + } + num++; + } + } + if (num > maxShort) { + releaseRef(dec.sRefs[minFn]); + dec.sRefs[minFn] = null; + } + } + } + + private int unwrap(int thisFrameNo, int refFrameNo, int maxFrames) { + return refFrameNo > thisFrameNo ? refFrameNo - maxFrames : refFrameNo; + } + + private void saveShort(Frame saved) { + dec.sRefs[firstSliceHeader.frameNum] = saved; + } + + private void saveLong(Frame saved, int longNo) { + Frame prev = dec.lRefs.get(longNo); + if (prev != null) + releaseRef(prev); + saved.setShortTerm(false); + + dec.lRefs.put(longNo, saved); + } + + private void truncateLongTerm(int maxLongNo) { + int[] keys = dec.lRefs.keys(); + for (int i = 0; i < keys.length; i++) { + if (keys[i] > maxLongNo) { + releaseRef(dec.lRefs.get(keys[i])); + dec.lRefs.remove(keys[i]); + } + } + } + + private void convert(int shortNo, int longNo) { + int ind = wrap(firstSliceHeader.frameNum - shortNo, + 1 << (firstSliceHeader.sps.log2MaxFrameNumMinus4 + 4)); + releaseRef(dec.lRefs.get(longNo)); + dec.lRefs.put(longNo, dec.sRefs[ind]); + dec.sRefs[ind] = null; + dec.lRefs.get(longNo).setShortTerm(false); + } + + private void unrefLongTerm(int longNo) { + releaseRef(dec.lRefs.get(longNo)); + dec.lRefs.remove(longNo); + } + + private void unrefShortTerm(int shortNo) { + int ind = wrap(firstSliceHeader.frameNum - shortNo, + 1 << (firstSliceHeader.sps.log2MaxFrameNumMinus4 + 4)); + releaseRef(dec.sRefs[ind]); + dec.sRefs[ind] = null; + } + } + + public static Frame createFrame(SeqParameterSet sps, byte[][] buffer, int frameNum, SliceType frameType, + MvList2D mvs, Frame[][][] refsUsed, int POC) { + int width = sps.picWidthInMbsMinus1 + 1 << 4; + int height = SeqParameterSet.getPicHeightInMbs(sps) << 4; + + Rect crop = null; + if (sps.frameCroppingFlag) { + int sX = sps.frameCropLeftOffset << 1; + int sY = sps.frameCropTopOffset << 1; + int w = width - (sps.frameCropRightOffset << 1) - sX; + int h = height - (sps.frameCropBottomOffset << 1) - sY; + crop = new Rect(sX, sY, w, h); + } + return new Frame(width, height, buffer, ColorSpace.YUV420, crop, frameNum, frameType, mvs, refsUsed, POC); + } + + public void addSps(List spsList) { + reader.addSpsList(spsList); + } + + public void addPps(List ppsList) { + reader.addPpsList(ppsList); + } + + @UsedViaReflection + public static int probe(ByteBuffer data) { + boolean validSps = false, validPps = false, validSh = false; + for (ByteBuffer nalUnit : H264Utils.splitFrame(data.duplicate())) { + NALUnit marker = NALUnit.read(nalUnit); + if (marker.type == NALUnitType.IDR_SLICE || marker.type == NALUnitType.NON_IDR_SLICE) { + BitReader reader = BitReader.createBitReader(nalUnit); + validSh = validSh(SliceHeaderReader.readPart1(reader)); + break; + } else if (marker.type == NALUnitType.SPS) { + validSps = validSps(SeqParameterSet.read(nalUnit)); + } else if (marker.type == NALUnitType.PPS) { + validPps = validPps(PictureParameterSet.read(nalUnit)); + } + } + + return (validSh ? 60 : 0) + (validSps ? 20 : 0) + (validPps ? 20 : 0); + } + + private static boolean validSh(SliceHeader sh) { + return sh.firstMbInSlice == 0 && sh.sliceType != null && sh.picParameterSetId < 2; + } + + private static boolean validSps(SeqParameterSet sps) { + return sps.bitDepthChromaMinus8 < 4 && sps.bitDepthLumaMinus8 < 4 && sps.chromaFormatIdc != null + && sps.seqParameterSetId < 2 && sps.picOrderCntType <= 2; + } + + private static boolean validPps(PictureParameterSet pps) { + return pps.picInitQpMinus26 <= 26 && pps.seqParameterSetId <= 2 && pps.picParameterSetId <= 2; + } + + @Override + public VideoCodecMeta getCodecMeta(ByteBuffer data) { + List rawSPS = H264Utils.getRawSPS(data.duplicate()); + List rawPPS = H264Utils.getRawPPS(data.duplicate()); + if (rawSPS.size() == 0) { + Logger.warn("Can not extract metadata from the packet not containing an SPS."); + return null; + } + SeqParameterSet sps = SeqParameterSet.read(rawSPS.get(0)); + Size size = H264Utils.getPicSize(sps); +//, H264Utils.saveCodecPrivate(rawSPS, rawPPS) + return org.monte.media.impl.jcodec.common.VideoCodecMeta.createSimpleVideoCodecMeta(size, ColorSpace.YUV420); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Encoder.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Encoder.java new file mode 100644 index 0000000..356bd2a --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Encoder.java @@ -0,0 +1,533 @@ +package org.monte.media.impl.jcodec.codecs.h264; + +import org.monte.media.impl.jcodec.codecs.h264.encode.CQPRateControl; +import org.monte.media.impl.jcodec.codecs.h264.encode.EncodedMB; +import org.monte.media.impl.jcodec.codecs.h264.encode.EncodingContext; +import org.monte.media.impl.jcodec.codecs.h264.encode.IntraPredEstimator; +import org.monte.media.impl.jcodec.codecs.h264.encode.MBDeblocker; +import org.monte.media.impl.jcodec.codecs.h264.encode.MBEncoderHelper; +import org.monte.media.impl.jcodec.codecs.h264.encode.MBWriterI16x16; +import org.monte.media.impl.jcodec.codecs.h264.encode.MBWriterINxN; +import org.monte.media.impl.jcodec.codecs.h264.encode.MBWriterP16x16; +import org.monte.media.impl.jcodec.codecs.h264.encode.MotionEstimator; +import org.monte.media.impl.jcodec.codecs.h264.encode.RateControl; +import org.monte.media.impl.jcodec.codecs.h264.io.CAVLC; +import org.monte.media.impl.jcodec.codecs.h264.io.model.Frame; +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; +import org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnit; +import org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnitType; +import org.monte.media.impl.jcodec.codecs.h264.io.model.PictureParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.RefPicMarkingIDR; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter; +import org.monte.media.impl.jcodec.codecs.h264.io.write.SliceHeaderWriter; +import org.monte.media.impl.jcodec.common.Tuple._3; +import org.monte.media.impl.jcodec.common.VideoEncoder; +import org.monte.media.impl.jcodec.common.io.BitWriter; +import org.monte.media.impl.jcodec.common.io.FileChannelWrapper; +import org.monte.media.impl.jcodec.common.io.NIOUtils; +import org.monte.media.impl.jcodec.common.logging.Logger; +import org.monte.media.impl.jcodec.common.model.ColorSpace; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.common.model.Size; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import java.nio.ByteBuffer; +import java.util.LinkedList; +import java.util.List; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.escapeNAL; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * MPEG 4 AVC ( H.264 ) Encoder + *

+ * Conforms to H.264 ( ISO/IEC 14496-10 ) specifications + * + * @author The JCodec project + */ +public class H264Encoder extends VideoEncoder { + + // private static final int QP = 20; + private static final int KEY_INTERVAL_DEFAULT = 25; + private static final int MOTION_SEARCH_RANGE_DEFAULT = 16; + + public static H264Encoder createH264Encoder() { + return new H264Encoder(new CQPRateControl(24)); + } + + private RateControl rc; + private int frameNumber; + private int keyInterval; + private int motionSearchRange; + + private int maxPOC; + + private int maxFrameNumber; + + private SeqParameterSet sps; + + private PictureParameterSet pps; + + private MBWriterI16x16 mbEncoderI16x16; + private MBWriterINxN mbEncoderINxN; + private MBWriterP16x16 mbEncoderP16x16; + + private Picture ref; + private Picture picOut; + private EncodedMB[] topEncoded; + + private boolean psnrEn; + private long[] sum_se = new long[3]; + private long[] g_sum_se = new long[3]; + private int frameCount; + private long totalSize; + private EncodingContext context; + private H264Decoder decoder; + private boolean enableRdo; + private String decodedDump; + private FileChannelWrapper dumpOut; + + public H264Encoder(RateControl rc) { + this.rc = rc; + this.keyInterval = KEY_INTERVAL_DEFAULT; + this.motionSearchRange = MOTION_SEARCH_RANGE_DEFAULT; + } + + public int getKeyInterval() { + return keyInterval; + } + + public void setKeyInterval(int keyInterval) { + this.keyInterval = keyInterval; + } + + public int getMotionSearchRange() { + return motionSearchRange; + } + + public void setMotionSearchRange(int motionSearchRange) { + this.motionSearchRange = motionSearchRange; + } + + public boolean isPsnrEn() { + return psnrEn; + } + + public void setPsnrEn(boolean psnrEn) { + this.psnrEn = psnrEn; + } + + public void setEncDecMismatch(boolean test) { + this.decoder = new H264Decoder(); + } + + public void setEnableRdo(boolean enableRdo) { + this.enableRdo = enableRdo; + } + + public void setDecodedDump(String decodedDump) { + this.decodedDump = decodedDump; + } + + /** + * Encode this picture into h.264 frame. Frame type will be selected by encoder. + */ + public EncodedFrame encodeFrame(Picture pic, ByteBuffer _out) { + if (pic.getColor() != ColorSpace.YUV420J) + throw new IllegalArgumentException("Input picture color is not supported: " + pic.getColor()); + + if (frameNumber >= keyInterval) { + frameNumber = 0; + } + + SliceType sliceType = frameNumber == 0 ? SliceType.I : SliceType.P; + boolean idr = frameNumber == 0; + + ByteBuffer data = doEncodeFrame(pic, _out, idr, frameNumber++, sliceType); + if (psnrEn) { + savePsnrStats(data.remaining()); + } + if (decoder != null) { + checkEncDecMatch(NIOUtils.cloneBuffer(data)); + } + + frameCount++; + return new EncodedFrame(data, idr); + } + + + private void checkEncDecMatch(ByteBuffer data) { + Picture tmp = picOut.createCompatible(); + Frame decoded = decoder.decodeFrame(data.duplicate(), tmp.getData()); + decoded.setCrop(null); + decoded.setColor(picOut.getColor()); + _3 mm = decoded.firstMismatch(picOut); + if (mm != null) { + int cw = 3 + (mm.v2 == 0 ? 1 : 0); + throw new RuntimeException( + String.format("Encoder-decoder mismatch %d vs %d, f:%d pl:%d x:%d y:%d mbX:%d mbY:%d", + decoded.pixAt(mm.v0, mm.v1, mm.v2), picOut.pixAt(mm.v0, mm.v1, mm.v2), frameCount, mm.v2, + mm.v0, mm.v1, mm.v0 >> cw, mm.v1 >> cw)); + } + } + + private void savePsnrStats(int size) { + for (int p = 0; p < 3; p++) { + g_sum_se[p] += sum_se[p]; + sum_se[p] = 0; + } + totalSize += size; + } + + private double calcPsnr(long sum, int p) { + int luma = p == 0 ? 1 : 0; + int pixCnt = (sps.picHeightInMapUnitsMinus1 + 1) * (sps.picWidthInMbsMinus1 + 1) << (6 + (luma * 2)); + double mse = (double) sum / pixCnt; + return 10 * Math.log10((255 * 255) / mse); + } + + /** + * Encode this picture as an IDR frame. IDR frame starts a new independently + * decodeable video sequence + * + * @param pic + * @param _out + * @return + */ + public ByteBuffer encodeIDRFrame(Picture pic, ByteBuffer _out) { + frameNumber = 0; + return doEncodeFrame(pic, _out, true, frameNumber, SliceType.I); + } + + /** + * Encode this picture as a P-frame. P-frame is an frame predicted from one or + * more of the previosly decoded frame and is usually 10x less in size then the + * IDR frame. + * + * @param pic + * @param _out + * @return + */ + public ByteBuffer encodePFrame(Picture pic, ByteBuffer _out) { + frameNumber++; + return doEncodeFrame(pic, _out, true, frameNumber, SliceType.P); + } + + public ByteBuffer doEncodeFrame(Picture pic, ByteBuffer _out, boolean idr, int frameNumber, SliceType frameType) { + ByteBuffer dup = _out.duplicate(); + int maxSize = Math.min(dup.remaining(), pic.getWidth() * pic.getHeight()); + maxSize -= (maxSize >>> 6); // 1.5% to account for escaping + int qp = rc.startPicture(pic.getSize(), maxSize, frameType); + + if (idr) { + sps = initSPS(new Size(pic.getCroppedWidth(), pic.getCroppedHeight())); + pps = initPPS(); + + maxPOC = 1 << (sps.log2MaxPicOrderCntLsbMinus4 + 4); + maxFrameNumber = 1 << (sps.log2MaxFrameNumMinus4 + 4); + } + + if (idr) { + dup.putInt(0x1); + new NALUnit(NALUnitType.SPS, 3).write(dup); + writeSPS(dup, sps); + + dup.putInt(0x1); + new NALUnit(NALUnitType.PPS, 3).write(dup); + writePPS(dup, pps); + } + + int mbWidth = sps.picWidthInMbsMinus1 + 1; + int mbHeight = sps.picHeightInMapUnitsMinus1 + 1; + + context = new EncodingContext(mbWidth, mbHeight); + + picOut = Picture.create(mbWidth << 4, mbHeight << 4, ColorSpace.YUV420J); + + topEncoded = new EncodedMB[mbWidth]; + + encodeSlice(sps, pps, pic, dup, idr, frameNumber, frameType, qp); + + putLastMBLine(); + + ref = picOut; + + dup.flip(); + return dup; + } + + private void writePPS(ByteBuffer dup, PictureParameterSet pps) { + ByteBuffer tmp = ByteBuffer.allocate(1024); + pps.write(tmp); + tmp.flip(); + escapeNAL(tmp, dup); + } + + private void writeSPS(ByteBuffer dup, SeqParameterSet sps) { + ByteBuffer tmp = ByteBuffer.allocate(1024); + sps.write(tmp); + tmp.flip(); + escapeNAL(tmp, dup); + } + + public PictureParameterSet initPPS() { + PictureParameterSet pps = new PictureParameterSet(); + pps.picInitQpMinus26 = 0; // start with qp = 26 + return pps; + } + + public SeqParameterSet initSPS(Size sz) { + SeqParameterSet sps = new SeqParameterSet(); + sps.picWidthInMbsMinus1 = ((sz.getWidth() + 15) >> 4) - 1; + sps.picHeightInMapUnitsMinus1 = ((sz.getHeight() + 15) >> 4) - 1; + sps.chromaFormatIdc = ColorSpace.YUV420J; + sps.profileIdc = 66; + sps.levelIdc = 40; + sps.numRefFrames = 1; + sps.frameMbsOnlyFlag = true; + sps.log2MaxFrameNumMinus4 = Math.max(0, MathUtil.log2(keyInterval) - 3); + + int codedWidth = (sps.picWidthInMbsMinus1 + 1) << 4; + int codedHeight = (sps.picHeightInMapUnitsMinus1 + 1) << 4; + sps.frameCroppingFlag = codedWidth != sz.getWidth() || codedHeight != sz.getHeight(); + sps.frameCropRightOffset = (codedWidth - sz.getWidth() + 1) >> 1; + sps.frameCropBottomOffset = (codedHeight - sz.getHeight() + 1) >> 1; + + return sps; + } + + private void encodeSlice(SeqParameterSet sps, PictureParameterSet pps, Picture pic, ByteBuffer dup, boolean idr, + int frameNum, SliceType sliceType, int sliceQp) { + if (idr && sliceType != SliceType.I) { + idr = false; + Logger.warn("Illegal value of idr = true when sliceType != I"); + } + context.cavlc = new CAVLC[]{new CAVLC(sps, pps, 2, 2), new CAVLC(sps, pps, 1, 1), new CAVLC(sps, pps, 1, 1)}; + mbEncoderI16x16 = new MBWriterI16x16(); + mbEncoderINxN = new MBWriterINxN(); + mbEncoderP16x16 = new MBWriterP16x16(sps, ref); + + dup.putInt(0x1); + new NALUnit(idr ? NALUnitType.IDR_SLICE : NALUnitType.NON_IDR_SLICE, 3).write(dup); + SliceHeader sh = new SliceHeader(); + sh.sliceType = sliceType; + if (idr) + sh.refPicMarkingIDR = new RefPicMarkingIDR(false, false); + sh.pps = pps; + sh.sps = sps; + sh.picOrderCntLsb = (frameNum << 1) % maxPOC; + sh.frameNum = frameNum % maxFrameNumber; + sh.sliceQpDelta = sliceQp - (pps.picInitQpMinus26 + 26); + + ByteBuffer buf = ByteBuffer.allocate(pic.getWidth() * pic.getHeight()); + BitWriter sliceData = new BitWriter(buf); + SliceHeaderWriter.write(sh, idr, 2, sliceData); + MotionEstimator estimator = new MotionEstimator(ref, sps, motionSearchRange); + context.prevQp = sliceQp; + + int mbWidth = sps.picWidthInMbsMinus1 + 1; + int mbHeight = sps.picHeightInMapUnitsMinus1 + 1; + int oldQp = sliceQp; + for (int mbY = 0, mbAddr = 0; mbY < mbHeight; mbY++) { + for (int mbX = 0; mbX < mbWidth; mbX++, mbAddr++) { + if (sliceType == SliceType.P) { + CAVLCWriter.writeUE(sliceData, 0); // number of skipped mbs + } + + int qpDelta = rc.initialQpDelta(pic, mbX, mbY); + int mbQp = oldQp + qpDelta; + + int[] mv = null; + if (ref != null) + mv = estimator.mvEstimate(pic, mbX, mbY); + + NonRdVector params = new NonRdVector(mv, IntraPredEstimator.getLumaMode(pic, context, mbX, mbY), + IntraPredEstimator.getLumaPred4x4(pic, context, mbX, mbY, mbQp), + IntraPredEstimator.getChromaMode(pic, context, mbX, mbY)); + + EncodedMB outMB = new EncodedMB(); + outMB.setPos(mbX, mbY); + BitWriter candidate; + EncodingContext fork; + do { + candidate = sliceData.fork(); + fork = context.fork(); + rdMacroblock(fork, outMB, sliceType, pic, mbX, mbY, candidate, sliceQp, mbQp, params); + qpDelta = rc.accept(candidate.position() - sliceData.position()); + if (qpDelta != 0) + mbQp += qpDelta; + } while (qpDelta != 0); + estimator.mvSave(mbX, mbY, new int[]{outMB.mx[0], outMB.my[0], outMB.mr[0]}); + sliceData = candidate; + context = fork; + oldQp = mbQp; + + context.update(outMB); + if (psnrEn) + calcMse(pic, outMB, mbX, mbY, sum_se); + + new MBDeblocker().deblockMBP(outMB, mbX > 0 ? topEncoded[mbX - 1] : null, + mbY > 0 ? topEncoded[mbX] : null); + addToReference(outMB, mbX, mbY); + } + } + sliceData.write1Bit(1); + sliceData.flush(); + buf = sliceData.getBuffer(); + buf.flip(); + + escapeNAL(buf, dup); + } + + private void calcMse(Picture pic, EncodedMB out, int mbX, int mbY, long[] out_se) { + byte[] patch = new byte[256]; + for (int p = 0; p < 3; p++) { + byte[] outPix = out.getPixels().getData()[p]; + int luma = p == 0 ? 1 : 0; + MBEncoderHelper.take(pic.getPlaneData(p), pic.getPlaneWidth(p), pic.getPlaneHeight(p), mbX << (3 + luma), + mbY << (3 + luma), patch, 8 << luma, 8 << luma); + for (int i = 0; i < (64 << (luma * 2)); i++) { + int q = outPix[i] - patch[i]; + out_se[p] += q * q; + } + } + } + + public static class RdVector { + public MBType mbType; + public int qp; + + public RdVector(MBType mbType, int qp) { + this.mbType = mbType; + this.qp = qp; + } + } + + public static class NonRdVector { + public int[] mv; + public int lumaPred16x16; + public int[] lumaPred4x4; + public int chrPred; + + public NonRdVector(int[] mv, int lumaPred16x16, int[] lumaPred4x4, int chrPred) { + this.mv = mv; + this.lumaPred16x16 = lumaPred16x16; + this.lumaPred4x4 = lumaPred4x4; + this.chrPred = chrPred; + } + } + + private void rdMacroblock(EncodingContext ctx, EncodedMB outMB, SliceType sliceType, Picture pic, int mbX, int mbY, + BitWriter candidate, int sliceQp, int mbQp, NonRdVector params) { + if (!enableRdo) { + RdVector vector = sliceType == SliceType.P ? new RdVector(MBType.P_16x16, mbQp) + : new RdVector(MBType.I_16x16, mbQp); + encodeCand(ctx, outMB, sliceType, pic, mbX, mbY, candidate, params, vector); + return; + } + + List cands = new LinkedList(); + cands.add(new RdVector(MBType.I_16x16, mbQp)); + cands.add(new RdVector(MBType.I_NxN, mbQp)); + if (sliceType == SliceType.P) { + cands.add(new RdVector(MBType.P_16x16, mbQp)); + } + long bestRd = Long.MAX_VALUE; + RdVector bestVector = null; + + for (RdVector rdVector : cands) { + EncodingContext candCtx = ctx.fork(); + BitWriter candBits = candidate.fork(); + long rdCost = tryVector(candCtx, sliceType, pic, mbX, mbY, candBits, sliceQp, params, rdVector); + if (rdCost < bestRd) { + bestRd = rdCost; + bestVector = rdVector; + } + } + encodeCand(ctx, outMB, sliceType, pic, mbX, mbY, candidate, params, bestVector); + } + + private long tryVector(EncodingContext ctx, SliceType sliceType, Picture pic, int mbX, int mbY, BitWriter candidate, + int sliceQp, NonRdVector params, RdVector vector) { + int start = candidate.position(); + EncodedMB outMB = new EncodedMB(); + outMB.setPos(mbX, mbY); + encodeCand(ctx, outMB, sliceType, pic, mbX, mbY, candidate, params, vector); + + long[] se = new long[3]; + calcMse(pic, outMB, mbX, mbY, se); + long mse = (se[0] + se[1] + se[2]) / 384; + int bits = candidate.position() - start; + return rdCost(mse, bits, H264Const.lambda[sliceQp]); + } + + private long rdCost(long mse, int bits, int lambda) { + return mse + ((lambda * bits) >> 8); + } + + private void encodeCand(EncodingContext ctx, EncodedMB outMB, SliceType sliceType, Picture pic, int mbX, int mbY, + BitWriter candidate, NonRdVector params, RdVector vector) { + if (vector.mbType == MBType.I_16x16) { + BitWriter tmp = new BitWriter(ByteBuffer.allocate(1024)); + boolean cbpLuma = mbEncoderI16x16.encodeMacroblock(ctx, pic, mbX, mbY, tmp, outMB, vector.qp, params); + int cbpChroma = mbEncoderI16x16.getCbpChroma(pic, mbX, mbY); + + int i16x16TypeOffset = (cbpLuma ? 12 : 0) + cbpChroma * 4 + params.lumaPred16x16; + int mbTypeOffset = sliceType == SliceType.P ? 5 : 0; + + CAVLCWriter.writeUE(candidate, mbTypeOffset + vector.mbType.code() + i16x16TypeOffset); + candidate.writeOther(tmp); + } else if (vector.mbType == MBType.P_16x16) { + CAVLCWriter.writeUE(candidate, vector.mbType.code()); + mbEncoderP16x16.encodeMacroblock(ctx, pic, mbX, mbY, candidate, outMB, vector.qp, params); + } else if (vector.mbType == MBType.I_NxN) { + CAVLCWriter.writeUE(candidate, sliceType == SliceType.P ? 5 : 0); + mbEncoderINxN.encodeMacroblock(ctx, pic, mbX, mbY, candidate, outMB, vector.qp, params); + } else + throw new RuntimeException("Macroblock of type " + vector.mbType + " is not supported."); + } + + private void addToReference(EncodedMB outMB, int mbX, int mbY) { + if (mbY > 0) + MBEncoderHelper.putBlkPic(picOut, topEncoded[mbX].getPixels(), mbX << 4, (mbY - 1) << 4); + topEncoded[mbX] = outMB; + } + + private void putLastMBLine() { + int mbWidth = sps.picWidthInMbsMinus1 + 1; + int mbHeight = sps.picHeightInMapUnitsMinus1 + 1; + for (int mbX = 0; mbX < mbWidth; mbX++) + MBEncoderHelper.putBlkPic(picOut, topEncoded[mbX].getPixels(), mbX << 4, (mbHeight - 1) << 4); + } + + @Override + public ColorSpace[] getSupportedColorSpaces() { + return new ColorSpace[]{ColorSpace.YUV420J}; + } + + @Override + public int estimateBufferSize(Picture frame) { + return Math.max(1 << 16, frame.getWidth() * frame.getHeight()); + } + + @Override + public void finish() { + if (psnrEn) { + int fc = frameCount + 1; + long avgSum = (g_sum_se[0] + g_sum_se[1] * 4 + g_sum_se[2] * 4) / 3; + double avgPsnr = calcPsnr(avgSum / fc, 0); + double yPsnr = calcPsnr(g_sum_se[0] / fc, 0); + double uPsnr = calcPsnr(g_sum_se[1] / fc, 1); + double vPsnr = calcPsnr(g_sum_se[2] / fc, 2); + Logger.info(String.format("PSNR AVG:%.3f Y:%.3f U:%.3f V:%.3f kbps:%.3f", avgPsnr, yPsnr, uPsnr, vPsnr, + (double) (8 * 25 * (totalSize / fc)) / 1000)); + } + if (dumpOut != null) + NIOUtils.closeQuietly(dumpOut); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Utils.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Utils.java new file mode 100644 index 0000000..ab74803 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Utils.java @@ -0,0 +1,1101 @@ +package org.monte.media.impl.jcodec.codecs.h264; + +import org.monte.media.impl.jcodec.codecs.h264.decode.SliceHeaderReader; +import org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnit; +import org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnitType; +import org.monte.media.impl.jcodec.codecs.h264.io.model.PictureParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.codecs.h264.io.write.SliceHeaderWriter; +import org.monte.media.impl.jcodec.codecs.h264.mp4.AvcCBox; +import org.monte.media.impl.jcodec.common.IntArrayList; +import org.monte.media.impl.jcodec.common.io.BitReader; +import org.monte.media.impl.jcodec.common.io.BitWriter; +import org.monte.media.impl.jcodec.common.io.FileChannelWrapper; +import org.monte.media.impl.jcodec.common.io.NIOUtils; +import org.monte.media.impl.jcodec.common.io.SeekableByteChannel; +import org.monte.media.impl.jcodec.common.model.Size; +import org.monte.media.impl.jcodec.containers.mp4.boxes.Box; +import org.monte.media.impl.jcodec.containers.mp4.boxes.Box.LeafBox; +import org.monte.media.impl.jcodec.containers.mp4.boxes.NodeBox; +import org.monte.media.impl.jcodec.containers.mp4.boxes.SampleEntry; +import org.monte.media.impl.jcodec.containers.mp4.boxes.VideoSampleEntry; + +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static java.util.Arrays.asList; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class H264Utils { + + public static ByteBuffer nextNALUnit(ByteBuffer buf) { + skipToNALUnit(buf); + + if (buf.hasArray()) + return gotoNALUnitWithArray(buf); + else + return gotoNALUnit(buf); + } + + public static final void skipToNALUnit(ByteBuffer buf) { + if (!buf.hasRemaining()) + return; + + int val = 0xffffffff; + while (buf.hasRemaining()) { + val <<= 8; + val |= (buf.get() & 0xff); + if ((val & 0xffffff) == 1) { + buf.position(buf.position()); + break; + } + } + } + + /** + * Finds next Nth H.264 bitstream NAL unit (0x00000001) and returns the data + * that preceeds it as a ByteBuffer slice + *

+ * Segment byte order is always little endian + *

+ * TODO: emulation prevention + * + * @param buf + * @return + */ + public static final ByteBuffer gotoNALUnit(ByteBuffer buf) { + + if (!buf.hasRemaining()) + return null; + + int from = buf.position(); + ByteBuffer result = buf.slice(); + result.order(ByteOrder.BIG_ENDIAN); + + int val = 0xffffffff; + while (buf.hasRemaining()) { + val <<= 8; + val |= (buf.get() & 0xff); + if ((val & 0xffffff) == 1) { + buf.position(buf.position() - (val == 1 ? 4 : 3)); + result.limit(buf.position() - from); + break; + } + } + return result; + } + + /** + * Finds next Nth H.264 bitstream NAL unit (0x00000001) and returns the data + * that preceeds it as a ByteBuffer slice + *

+ * Segment byte order is always little endian + * + * @param buf + * @return data + */ + public static final ByteBuffer gotoNALUnitWithArray(ByteBuffer buf) { + + if (!buf.hasRemaining()) + return null; + + int from = buf.position(); + ByteBuffer result = buf.slice(); + result.order(ByteOrder.BIG_ENDIAN); + + byte[] arr = buf.array(); + int pos = from + buf.arrayOffset(); + int posFrom = pos; + int lim = buf.limit() + buf.arrayOffset(); + + while (pos < lim) { + byte b = arr[pos]; + + if ((b & 254) == 0) { + while (b == 0 && ++pos < lim) + b = arr[pos]; + + if (b == 1) { + if (pos - posFrom >= 2 && arr[pos - 1] == 0 && arr[pos - 2] == 0) { + int lenSize = (pos - posFrom >= 3 && arr[pos - 3] == 0) ? 4 : 3; + + buf.position(pos + 1 - buf.arrayOffset() - lenSize); + result.limit(buf.position() - from); + + return result; + } + } + } + + pos += 3; + } + + buf.position(buf.limit()); + + return result; + } + + public static final void unescapeNAL(ByteBuffer _buf) { + if (_buf.remaining() < 2) + return; + ByteBuffer _in = _buf.duplicate(); + ByteBuffer out = _buf.duplicate(); + byte p1 = _in.get(); + out.put(p1); + byte p2 = _in.get(); + out.put(p2); + while (_in.hasRemaining()) { + byte b = _in.get(); + if (p1 != 0 || p2 != 0 || b != 3) + out.put(b); + p1 = p2; + p2 = b; + } + _buf.limit(out.position()); + } + + public static final void escapeNALinplace(ByteBuffer src) { + int[] loc = searchEscapeLocations(src); + + int old = src.limit(); + src.limit(src.limit() + loc.length); + + for (int newPos = src.limit() - 1, oldPos = old - 1, locIdx = loc.length - 1; newPos >= src + .position(); newPos--, oldPos--) { + src.put(newPos, src.get(oldPos)); + if (locIdx >= 0 && loc[locIdx] == oldPos) { + newPos--; + src.put(newPos, (byte) 3); + locIdx--; + } + } + } + + private static int[] searchEscapeLocations(ByteBuffer src) { + IntArrayList points = IntArrayList.createIntArrayList(); + ByteBuffer search = src.duplicate(); + int p = search.getShort(); + while (search.hasRemaining()) { + byte b = search.get(); + if (p == 0 && (b & ~3) == 0) { + points.add(search.position() - 1); + p = 3; + } + p = (p << 8) & 0xffff; + p |= b & 0xff; + } + int[] array = points.toArray(); + return array; + } + + public static final void escapeNAL(ByteBuffer src, ByteBuffer dst) { + byte p1 = src.get(), p2 = src.get(); + dst.put(p1); + dst.put(p2); + while (src.hasRemaining()) { + byte b = src.get(); + if (p1 == 0 && p2 == 0 && (b & 0xff) <= 3) { + dst.put((byte) 3); + p1 = p2; + p2 = 3; + } + dst.put(b); + p1 = p2; + p2 = b; + } + } + + public static List splitMOVPacket(ByteBuffer buf, AvcCBox avcC) { + List result = new ArrayList(); + int nls = avcC.getNalLengthSize(); + ByteBuffer dup = buf.duplicate(); + while (dup.remaining() >= nls) { + int len = readLen(dup, nls); + if (len == 0) + break; + result.add(NIOUtils.read(dup, len)); + } + return result; + } + + private static int readLen(ByteBuffer dup, int nls) { + switch (nls) { + case 1: + return dup.get() & 0xff; + case 2: + return dup.getShort() & 0xffff; + case 3: + return ((dup.getShort() & 0xffff) << 8) | (dup.get() & 0xff); + case 4: + return dup.getInt(); + default: + throw new IllegalArgumentException("NAL Unit length size can not be " + nls); + } + } + + /** + * Encodes AVC frame in ISO BMF format. Takes Annex B format. + *

+ * Scans the packet for each NAL Unit starting with 00 00 00 01 and replaces + * this 4 byte sequence with 4 byte integer representing this NAL unit + * length. + * + * @param avcFrame AVC frame encoded in Annex B NAL unit format + */ + public static void encodeMOVPacketInplace(ByteBuffer avcFrame) { + + ByteBuffer dup = avcFrame.duplicate(); + ByteBuffer d1 = avcFrame.duplicate(); + + for (int tot = d1.position(); ; ) { + ByteBuffer buf = H264Utils.nextNALUnit(dup); + if (buf == null) + break; + d1.position(tot); + d1.putInt(buf.remaining()); + tot += buf.remaining() + 4; + } + } + + /** + * Encodes AVC frame in ISO BMF format. Takes Annex B format. + *

+ * Scans the packet for each NAL Unit starting with 00 00 00 01 and replaces + * this 4 byte sequence with 4 byte integer representing this NAL unit + * length. + * + * @param avcFrame AVC frame encoded in Annex B NAL unit format + */ + public static ByteBuffer encodeMOVPacket(ByteBuffer avcFrame) { + + ByteBuffer dup = avcFrame.duplicate(); + + List list = new ArrayList(); + ByteBuffer buf; + int totalLen = 0; + while ((buf = H264Utils.nextNALUnit(dup)) != null) { + list.add(buf); + totalLen += buf.remaining(); + } + ByteBuffer result = ByteBuffer.allocate(list.size() * 4 + totalLen); + for (ByteBuffer byteBuffer : list) { + result.putInt(byteBuffer.remaining()); + result.put(byteBuffer); + } + result.flip(); + return result; + } + + /** + * Decodes AVC packet in ISO BMF format into Annex B format. + *

+ * Replaces NAL unit size integers with 00 00 00 01 start codes. If the + * space allows the transformation is done inplace. + * + * @param result + */ + public static ByteBuffer decodeMOVPacket(ByteBuffer result, AvcCBox avcC) { + if (avcC.getNalLengthSize() == 4) { + decodeMOVPacketInplace(result, avcC); + return result; + } + return decodeMOVPacketNewBuf(result, avcC); + } + + public static ByteBuffer decodeMOVPacketNewBuf(ByteBuffer result, AvcCBox avcC) { + return joinNALUnits(splitMOVPacket(result, avcC)); + } + + /** + * Decodes AVC packet in ISO BMF format into Annex B format. + *

+ * Inplace replaces NAL unit size integers with 00 00 00 01 start codes. + * + * @param result + */ + public static void decodeMOVPacketInplace(ByteBuffer result, AvcCBox avcC) { + if (avcC.getNalLengthSize() != 4) + throw new IllegalArgumentException("Can only inplace decode AVC MOV packet with nal_length_size = 4."); + ByteBuffer dup = result.duplicate(); + while (dup.remaining() >= 4) { + int size = dup.getInt(); + dup.position(dup.position() - 4); + dup.putInt(1); + dup.position(dup.position() + size); + } + } + + /** + * Wipes AVC parameter sets ( SPS/PPS ) from the packet + * + * @param _in AVC frame encoded in Annex B NAL unit format + * @param out Buffer where packet without PS will be put + * @param spsList Storage for leading SPS structures ( can be null, then all + * leading SPSs are discarded ). + * @param ppsList Storage for leading PPS structures ( can be null, then all + * leading PPSs are discarded ). + */ + public static void wipePS(ByteBuffer _in, ByteBuffer out, List spsList, List ppsList) { + + ByteBuffer dup = _in.duplicate(); + while (dup.hasRemaining()) { + ByteBuffer buf = H264Utils.nextNALUnit(dup); + if (buf == null) + break; + + NALUnit nu = NALUnit.read(buf.duplicate()); + if (nu.type == NALUnitType.PPS) { + if (ppsList != null) + ppsList.add(NIOUtils.duplicate(buf)); + } else if (nu.type == NALUnitType.SPS) { + if (spsList != null) + spsList.add(NIOUtils.duplicate(buf)); + } else if (out != null) { + out.putInt(1); + out.put(buf); + } + } + if (out != null) + out.flip(); + } + + /** + * Wipes AVC parameter sets ( SPS/PPS ) from the packet ( inplace operation + * ) + * + * @param _in AVC frame encoded in Annex B NAL unit format + * @param spsList Storage for leading SPS structures ( can be null, then all + * leading SPSs are discarded ). + * @param ppsList Storage for leading PPS structures ( can be null, then all + * leading PPSs are discarded ). + */ + public static void wipePSinplace(ByteBuffer _in, Collection spsList, Collection ppsList) { + ByteBuffer dup = _in.duplicate(); + while (dup.hasRemaining()) { + ByteBuffer buf = H264Utils.nextNALUnit(dup); + if (buf == null) + break; + + NALUnit nu = NALUnit.read(buf); + if (nu.type == NALUnitType.PPS) { + if (ppsList != null) + ppsList.add(NIOUtils.duplicate(buf)); + _in.position(dup.position()); + } else if (nu.type == NALUnitType.SPS) { + if (spsList != null) + spsList.add(NIOUtils.duplicate(buf)); + _in.position(dup.position()); + } else if (nu.type == NALUnitType.IDR_SLICE || nu.type == NALUnitType.NON_IDR_SLICE) + break; + } + } + + public static AvcCBox createAvcC(SeqParameterSet sps, PictureParameterSet pps, int nalLengthSize) { + ByteBuffer serialSps = ByteBuffer.allocate(512); + sps.write(serialSps); + serialSps.flip(); + H264Utils.escapeNALinplace(serialSps); + + ByteBuffer serialPps = ByteBuffer.allocate(512); + pps.write(serialPps); + serialPps.flip(); + H264Utils.escapeNALinplace(serialPps); + + AvcCBox avcC = AvcCBox.createAvcCBox(sps.profileIdc, 0, sps.levelIdc, nalLengthSize, asList(serialSps), + asList(serialPps)); + + return avcC; + } + + public static AvcCBox createAvcCFromList(List initSPS, List initPPS, + int nalLengthSize) { + List serialSps = saveSPS(initSPS); + List serialPps = savePPS(initPPS); + + SeqParameterSet sps = initSPS.get(0); + return AvcCBox.createAvcCBox(sps.profileIdc, 0, sps.levelIdc, nalLengthSize, serialSps, serialPps); + } + + /** + * @param initPPS + * @return + */ + public static List savePPS(List initPPS) { + List serialPps = new ArrayList(); + for (PictureParameterSet pps : initPPS) { + ByteBuffer bb1 = ByteBuffer.allocate(512); + pps.write(bb1); + bb1.flip(); + H264Utils.escapeNALinplace(bb1); + serialPps.add(bb1); + } + return serialPps; + } + + /** + * @param initSPS + * @return + */ + public static List saveSPS(List initSPS) { + List serialSps = new ArrayList(); + for (SeqParameterSet sps : initSPS) { + ByteBuffer bb1 = ByteBuffer.allocate(512); + sps.write(bb1); + bb1.flip(); + H264Utils.escapeNALinplace(bb1); + serialSps.add(bb1); + } + return serialSps; + } + + /** + * Creates a MP4 sample entry given AVC/H.264 codec private. + * + * @param codecPrivate Array containing AnnexB delimited (00 00 00 01) SPS/PPS NAL + * units. + * @return MP4 sample entry + */ + public static SampleEntry createMOVSampleEntryFromBytes(ByteBuffer codecPrivate) { + List rawSPS = getRawSPS(codecPrivate.duplicate()); + List rawPPS = getRawPPS(codecPrivate.duplicate()); + return createMOVSampleEntryFromSpsPpsList(rawSPS, rawPPS, 4); + } + + public static SampleEntry createMOVSampleEntryFromSpsPpsList(List spsList, List ppsList, + int nalLengthSize) { + AvcCBox avcC = createAvcCFromPS(spsList, ppsList, nalLengthSize); + + return createMOVSampleEntryFromAvcC(avcC); + } + + /** + * Creates a MP4 sample entry given AVC/H.264 codec private. + * + * @param codecPrivate Array containing AnnexB delimited (00 00 00 01) SPS/PPS NAL + * units. + * @return MP4 sample entry + */ + public static AvcCBox createAvcCFromBytes(ByteBuffer codecPrivate) { + List rawSPS = getRawSPS(codecPrivate.duplicate()); + List rawPPS = getRawPPS(codecPrivate.duplicate()); + return createAvcCFromPS(rawSPS, rawPPS, 4); + } + + public static AvcCBox createAvcCFromPS(List spsList, List ppsList, int nalLengthSize) { + SeqParameterSet sps = readSPS(NIOUtils.duplicate(spsList.get(0))); + return AvcCBox.createAvcCBox(sps.profileIdc, 0, sps.levelIdc, nalLengthSize, spsList, ppsList); + } + + public static SampleEntry createMOVSampleEntryFromAvcC(AvcCBox avcC) { + SeqParameterSet sps = SeqParameterSet.read(avcC.getSpsList().get(0).duplicate()); + int codedWidth = (sps.picWidthInMbsMinus1 + 1) << 4; + int codedHeight = SeqParameterSet.getPicHeightInMbs(sps) << 4; + + SampleEntry se = VideoSampleEntry.videoSampleEntry("avc1", getPicSize(sps), "JCodec"); + se.add(avcC); + return se; + } + + public static SampleEntry createMOVSampleEntryFromSpsPps(SeqParameterSet initSPS, PictureParameterSet initPPS, + int nalLengthSize) { + ByteBuffer bb1 = ByteBuffer.allocate(512), bb2 = ByteBuffer.allocate(512); + initSPS.write(bb1); + initPPS.write(bb2); + bb1.flip(); + bb2.flip(); + return createMOVSampleEntryFromBuffer(bb1, bb2, nalLengthSize); + } + + public static SampleEntry createMOVSampleEntryFromBuffer(ByteBuffer sps, ByteBuffer pps, int nalLengthSize) { + return createMOVSampleEntryFromSpsPpsList(Arrays.asList(new ByteBuffer[]{sps}), + Arrays.asList(new ByteBuffer[]{pps}), nalLengthSize); + } + + public static boolean iFrame(ByteBuffer _data) { + ByteBuffer data = _data.duplicate(); + ByteBuffer segment; + while ((segment = H264Utils.nextNALUnit(data)) != null) { + NALUnitType type = NALUnit.read(segment).type; + if (type == NALUnitType.IDR_SLICE || type == NALUnitType.NON_IDR_SLICE) { + unescapeNAL(segment); + BitReader reader = BitReader.createBitReader(segment); + SliceHeader part1 = SliceHeaderReader.readPart1(reader); + return part1.sliceType == SliceType.I; + } + } + return false; + } + + public static boolean isByteBufferIDRSlice(ByteBuffer _data) { + ByteBuffer data = _data.duplicate(); + ByteBuffer segment; + while ((segment = H264Utils.nextNALUnit(data)) != null) { + if (NALUnit.read(segment).type == NALUnitType.IDR_SLICE) + return true; + } + return false; + } + + public static boolean idrSlice(List _data) { + for (ByteBuffer segment : _data) { + if (NALUnit.read(segment.duplicate()).type == NALUnitType.IDR_SLICE) + return true; + } + return false; + } + + public static void saveRawFrame(ByteBuffer data, AvcCBox avcC, File f) throws IOException { + SeekableByteChannel raw = NIOUtils.writableChannel(f); + saveStreamParams(avcC, raw); + raw.write(data.duplicate()); + raw.close(); + } + + public static void saveStreamParams(AvcCBox avcC, SeekableByteChannel raw) throws IOException { + ByteBuffer bb = ByteBuffer.allocate(1024); + for (ByteBuffer byteBuffer : avcC.getSpsList()) { + raw.write(ByteBuffer.wrap(new byte[]{0, 0, 0, 1, 0x67})); + + H264Utils.escapeNAL(byteBuffer.duplicate(), bb); + bb.flip(); + raw.write(bb); + bb.clear(); + } + for (ByteBuffer byteBuffer : avcC.getPpsList()) { + raw.write(ByteBuffer.wrap(new byte[]{0, 0, 0, 1, 0x68})); + H264Utils.escapeNAL(byteBuffer.duplicate(), bb); + bb.flip(); + raw.write(bb); + bb.clear(); + } + } + + public static List splitFrame(ByteBuffer frame) { + ArrayList result = new ArrayList(); + + ByteBuffer segment; + while ((segment = H264Utils.nextNALUnit(frame)) != null) { + result.add(segment); + } + + return result; + } + + /** + * Joins buffers containing individual NAL units into a single AnnexB + * delimited buffer. Each NAL unit will be separated with 00 00 00 01 + * markers. Allocates a new byte buffer and writes data into it. + * + * @param nalUnits //@param out + */ + public static ByteBuffer joinNALUnits(List nalUnits) { + int size = 0; + for (ByteBuffer nal : nalUnits) { + size += 4 + nal.remaining(); + } + ByteBuffer allocate = ByteBuffer.allocate(size); + joinNALUnitsToBuffer(nalUnits, allocate); + allocate.flip(); + return allocate; + } + + /** + * Joins buffers containing individual NAL units into a single AnnexB + * delimited buffer. Each NAL unit will be separated with 00 00 00 01 + * markers. + * + * @param nalUnits + * @param out + */ + public static void joinNALUnitsToBuffer(List nalUnits, ByteBuffer out) { + for (ByteBuffer nal : nalUnits) { + out.putInt(1); + out.put(nal.duplicate()); + } + } + + public static ByteBuffer getAvcCData(AvcCBox avcC) { + ByteBuffer bb = ByteBuffer.allocate(2048); + avcC.doWrite(bb); + bb.flip(); + return bb; + } + + public static AvcCBox parseAVCC(VideoSampleEntry vse) { + Box lb = NodeBox.findFirst(vse, Box.class, "avcC"); + if (lb instanceof AvcCBox) + return (AvcCBox) lb; + else if (lb != null) { + return parseAVCCFromBuffer(((LeafBox) lb).getData().duplicate()); + } + return null; + } + + public static ByteBuffer saveCodecPrivate(List spsList, List ppsList) { + int totalCodecPrivateSize = 0; + for (ByteBuffer byteBuffer : spsList) { + totalCodecPrivateSize += byteBuffer.remaining() + 5; + } + for (ByteBuffer byteBuffer : ppsList) { + totalCodecPrivateSize += byteBuffer.remaining() + 5; + } + + ByteBuffer bb = ByteBuffer.allocate(totalCodecPrivateSize); + for (ByteBuffer byteBuffer : spsList) { + bb.putInt(1); + bb.put((byte) 0x67); + bb.put(byteBuffer.duplicate()); + } + for (ByteBuffer byteBuffer : ppsList) { + bb.putInt(1); + bb.put((byte) 0x68); + bb.put(byteBuffer.duplicate()); + } + bb.flip(); + return bb; + } + + public static ByteBuffer avcCToAnnexB(AvcCBox avcC) { + return saveCodecPrivate(avcC.getSpsList(), avcC.getPpsList()); + } + + public static AvcCBox parseAVCCFromBuffer(ByteBuffer bb) { + return AvcCBox.parseAvcCBox(bb); + } + + public static ByteBuffer writeSPS(SeqParameterSet sps, int approxSize) { + ByteBuffer output = ByteBuffer.allocate(approxSize + 8); + sps.write(output); + output.flip(); + H264Utils.escapeNALinplace(output); + return output; + } + + public static SeqParameterSet readSPS(ByteBuffer data) { + ByteBuffer input = NIOUtils.duplicate(data); + H264Utils.unescapeNAL(input); + SeqParameterSet sps = SeqParameterSet.read(input); + return sps; + } + + public static ByteBuffer writePPS(PictureParameterSet pps, int approxSize) { + ByteBuffer output = ByteBuffer.allocate(approxSize + 8); + pps.write(output); + output.flip(); + H264Utils.escapeNALinplace(output); + return output; + } + + public static PictureParameterSet readPPS(ByteBuffer data) { + ByteBuffer input = NIOUtils.duplicate(data); + H264Utils.unescapeNAL(input); + PictureParameterSet pps = PictureParameterSet.read(input); + return pps; + } + + public static PictureParameterSet findPPS(List ppss, int id) { + for (PictureParameterSet pps : ppss) { + if (pps.picParameterSetId == id) + return pps; + } + return null; + } + + public static SeqParameterSet findSPS(List spss, int id) { + for (SeqParameterSet sps : spss) { + if (sps.seqParameterSetId == id) + return sps; + } + return null; + } + + public abstract static class SliceHeaderTweaker { + + protected List sps; + protected List pps; + + protected abstract void tweak(SliceHeader sh); + + public SliceHeader run(ByteBuffer is, ByteBuffer os, NALUnit nu) { + ByteBuffer nal = os.duplicate(); + + H264Utils.unescapeNAL(is); + + BitReader reader = BitReader.createBitReader(is); + SliceHeader sh = SliceHeaderReader.readPart1(reader); + + PictureParameterSet pp = findPPS(pps, sh.picParameterSetId); + + return part2(is, os, nu, findSPS(sps, pp.picParameterSetId), pp, nal, reader, sh); + } + + public SliceHeader runSpsPps(ByteBuffer is, ByteBuffer os, NALUnit nu, SeqParameterSet sps, + PictureParameterSet pps) { + ByteBuffer nal = os.duplicate(); + + H264Utils.unescapeNAL(is); + + BitReader reader = BitReader.createBitReader(is); + SliceHeader sh = SliceHeaderReader.readPart1(reader); + + return part2(is, os, nu, sps, pps, nal, reader, sh); + } + + private SliceHeader part2(ByteBuffer is, ByteBuffer os, NALUnit nu, SeqParameterSet sps, + PictureParameterSet pps, ByteBuffer nal, BitReader reader, SliceHeader sh) { + BitWriter writer = new BitWriter(os); + SliceHeaderReader.readPart2(sh, nu, sps, pps, reader); + + tweak(sh); + + SliceHeaderWriter.write(sh, nu.type == NALUnitType.IDR_SLICE, nu.nal_ref_idc, writer); + + if (pps.entropyCodingModeFlag) + copyDataCABAC(is, os, reader, writer); + else + copyDataCAVLC(is, os, reader, writer); + + nal.limit(os.position()); + + H264Utils.escapeNALinplace(nal); + + os.position(nal.limit()); + + return sh; + } + + private void copyDataCAVLC(ByteBuffer is, ByteBuffer os, BitReader reader, BitWriter writer) { + int wLeft = 8 - writer.curBit(); + if (wLeft != 0) + writer.writeNBit(reader.readNBit(wLeft), wLeft); + writer.flush(); + + // Copy with shift + int shift = reader.curBit(); + if (shift != 0) { + int mShift = 8 - shift; + int inp = reader.readNBit(mShift); + reader.stop(); + + while (is.hasRemaining()) { + int out = inp << shift; + inp = is.get() & 0xff; + out |= inp >> mShift; + + os.put((byte) out); + } + os.put((byte) (inp << shift)); + } else { + reader.stop(); + os.put(is); + } + } + + private void copyDataCABAC(ByteBuffer is, ByteBuffer os, BitReader reader, BitWriter writer) { + long bp = reader.curBit(); + if (bp != 0) { + long rem = reader.readNBit(8 - (int) bp); + if ((1 << (8 - bp)) - 1 != rem) + throw new RuntimeException("Invalid CABAC padding"); + } + + if (writer.curBit() != 0) + writer.writeNBit(0xff, 8 - writer.curBit()); + writer.flush(); + reader.stop(); + + os.put(is); + } + } + + public static Size getPicSize(SeqParameterSet sps) { + int w = (sps.picWidthInMbsMinus1 + 1) << 4; + int h = SeqParameterSet.getPicHeightInMbs(sps) << 4; + if (sps.frameCroppingFlag) { + w -= (sps.frameCropLeftOffset + sps.frameCropRightOffset) << sps.chromaFormatIdc.compWidth[1]; + h -= (sps.frameCropTopOffset + sps.frameCropBottomOffset) << sps.chromaFormatIdc.compHeight[1]; + } + return new Size(w, h); + } + + public static List readSPSFromBufferList(List spsList) { + List result = new ArrayList(); + for (ByteBuffer byteBuffer : spsList) { + result.add(readSPS(NIOUtils.duplicate(byteBuffer))); + } + return result; + } + + public static List readPPSFromBufferList(List ppsList) { + List result = new ArrayList(); + for (ByteBuffer byteBuffer : ppsList) { + result.add(readPPS(NIOUtils.duplicate(byteBuffer))); + } + return result; + } + + public static List writePPSList(List allPps) { + List result = new ArrayList(); + for (PictureParameterSet pps : allPps) { + result.add(writePPS(pps, 64)); + } + return result; + } + + public static List writeSPSList(List allSps) { + List result = new ArrayList(); + for (SeqParameterSet sps : allSps) { + result.add(writeSPS(sps, 256)); + } + return result; + } + + public static void dumpFrame(FileChannelWrapper ch, SeqParameterSet[] values, PictureParameterSet[] values2, + List nalUnits) throws IOException { + for (int i = 0; i < values.length; i++) { + SeqParameterSet sps = values[i]; + NIOUtils.writeInt(ch, 1); + NIOUtils.writeByte(ch, (byte) 0x67); + ch.write(writeSPS(sps, 128)); + } + + for (int i = 0; i < values2.length; i++) { + PictureParameterSet pps = values2[i]; + NIOUtils.writeInt(ch, 1); + NIOUtils.writeByte(ch, (byte) 0x68); + ch.write(writePPS(pps, 256)); + } + + for (ByteBuffer byteBuffer : nalUnits) { + NIOUtils.writeInt(ch, 1); + ch.write(byteBuffer.duplicate()); + } + } + + public static void toNAL(ByteBuffer codecPrivate, SeqParameterSet sps, PictureParameterSet pps) { + ByteBuffer bb1 = ByteBuffer.allocate(512), bb2 = ByteBuffer.allocate(512); + sps.write(bb1); + pps.write(bb2); + bb1.flip(); + bb2.flip(); + + putNAL(codecPrivate, bb1, 0x67); + putNAL(codecPrivate, bb2, 0x68); + } + + public static void toNALList(ByteBuffer codecPrivate, List spsList2, List ppsList2) { + for (ByteBuffer byteBuffer : spsList2) + putNAL(codecPrivate, byteBuffer, 0x67); + for (ByteBuffer byteBuffer : ppsList2) + putNAL(codecPrivate, byteBuffer, 0x68); + } + + private static void putNAL(ByteBuffer codecPrivate, ByteBuffer byteBuffer, int nalType) { + ByteBuffer dst = ByteBuffer.allocate(byteBuffer.remaining() * 2); + escapeNAL(byteBuffer, dst); + dst.flip(); + codecPrivate.putInt(1); + codecPrivate.put((byte) nalType); + codecPrivate.put(dst); + } + + /** + * Parses a list of SPS NAL units out of the codec private array. + * + * @param codecPrivate An AnnexB formatted set of SPS/PPS NAL units. + * @return A list of ByteBuffers containing PPS NAL units. + */ + public static List getRawPPS(ByteBuffer codecPrivate) { + return getRawNALUnitsOfType(codecPrivate, NALUnitType.PPS); + } + + /** + * Parses a list of SPS NAL units out of the codec private array. + * + * @param codecPrivate An AnnexB formatted set of SPS/PPS NAL units. + * @return A list of ByteBuffers containing SPS NAL units. + */ + public static List getRawSPS(ByteBuffer codecPrivate) { + return getRawNALUnitsOfType(codecPrivate, NALUnitType.SPS); + } + + public static List getRawNALUnitsOfType(ByteBuffer codecPrivate, NALUnitType type) { + List result = new ArrayList(); + for (ByteBuffer bb : splitFrame(codecPrivate.duplicate())) { + NALUnit nu = NALUnit.read(bb); + if (nu.type == type) { + result.add(bb); + } + } + return result; + } + + /** + * A collection of functions to work with a compact representation of a motion vector. + *

+ * Motion vector is represented as long: + *

+ * ||rrrrrr|vvvvvvvvvvvv|hhhhhhhhhhhhhh|| + */ + public static class Mv { + public static int mvX(int mv) { + return (mv << 18) >> 18; + } + + public static int mvY(int mv) { + return ((mv << 6) >> 20); + } + + public static int mvRef(int mv) { + return (mv >> 26); + } + + public static int packMv(int mvx, int mvy, int r) { + return ((r & 0x3f) << 26) | ((mvy & 0xfff) << 14) | (mvx & 0x3fff); + } + + public static int mvC(int mv, int comp) { + return comp == 0 ? mvX(mv) : mvY(mv); + } + } + + /** + * A collection of functions to work with a compact representation of a + * motion vector list. + *

+ * Motion vector list contains interleaved pairs of forward and backward + * motion vectors packed into integers. + */ + public static class MvList { + private int[] list; + private static final int NA = Mv.packMv(0, 0, -1); + + public MvList(int size) { + list = new int[size << 1]; + clear(); + } + + public void clear() { + for (int i = 0; i < list.length; i += 2) { + list[i] = list[i + 1] = NA; + } + } + + public int mv0X(int off) { + return Mv.mvX(list[off << 1]); + } + + public int mv0Y(int off) { + return Mv.mvY(list[off << 1]); + } + + public int mv0R(int off) { + return Mv.mvRef(list[off << 1]); + } + + public int mv1X(int off) { + return Mv.mvX(list[(off << 1) + 1]); + } + + public int mv1Y(int off) { + return Mv.mvY(list[(off << 1) + 1]); + } + + public int mv1R(int off) { + return Mv.mvRef(list[(off << 1) + 1]); + } + + public int getMv(int off, int forward) { + return list[(off << 1) + forward]; + } + + public void setMv(int off, int forward, int mv) { + list[(off << 1) + forward] = mv; + } + + public void setPair(int off, int mv0, int mv1) { + list[(off << 1)] = mv0; + list[(off << 1) + 1] = mv1; + } + + public void copyPair(int off, MvList other, int otherOff) { + list[(off << 1)] = other.list[otherOff << 1]; + list[(off << 1) + 1] = other.list[(otherOff << 1) + 1]; + } + } + + public static class MvList2D { + private int[] list; + private int stride; + private int width; + private int height; + private static final int NA = Mv.packMv(0, 0, -1); + + public MvList2D(int width, int height) { + list = new int[(width << 1) * height]; + stride = width << 1; + this.width = width; + this.height = height; + clear(); + } + + public void clear() { + for (int i = 0; i < list.length; i += 2) { + list[i] = list[i + 1] = NA; + } + } + + public int mv0X(int offX, int offY) { + return Mv.mvX(list[(offX << 1) + stride * offY]); + } + + public int mv0Y(int offX, int offY) { + return Mv.mvY(list[(offX << 1) + stride * offY]); + } + + public int mv0R(int offX, int offY) { + return Mv.mvRef(list[(offX << 1) + stride * offY]); + } + + public int mv1X(int offX, int offY) { + return Mv.mvX(list[(offX << 1) + stride * offY + 1]); + } + + public int mv1Y(int offX, int offY) { + return Mv.mvY(list[(offX << 1) + stride * offY + 1]); + } + + public int mv1R(int offX, int offY) { + return Mv.mvRef(list[(offX << 1) + stride * offY + 1]); + } + + public int getMv(int offX, int offY, int forward) { + return list[(offX << 1) + stride * offY + forward]; + } + + public void setMv(int offX, int offY, int forward, int mv) { + list[(offX << 1) + stride * offY + forward] = mv; + } + + public int getHeight() { + return height; + } + + public int getWidth() { + return width; + } + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Utils2.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Utils2.java new file mode 100644 index 0000000..d91f1a8 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/H264Utils2.java @@ -0,0 +1,11 @@ +package org.monte.media.impl.jcodec.codecs.h264; + +public class H264Utils2 { + + public static int golomb2Signed(int val) { + int sign = ((val & 0x1) << 1) - 1; + val = ((val >> 1) + (val & 0x1)) * sign; + return val; + } + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/POCManager.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/POCManager.java new file mode 100644 index 0000000..0e4fdcd --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/POCManager.java @@ -0,0 +1,96 @@ +package org.monte.media.impl.jcodec.codecs.h264; + +import org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnit; +import org.monte.media.impl.jcodec.codecs.h264.io.model.RefPicMarking.InstrType; +import org.monte.media.impl.jcodec.codecs.h264.io.model.RefPicMarking.Instruction; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; + +import static org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnitType.IDR_SLICE; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * POC ( Picture Order Count ) manager + *

+ * Picture Order Count is used to represent an order of picture in a GOP ( Group + * of Pictures ) this is needed to correctly reorder and B-framed GOPs. POC is + * also used when building lists of reference pictures ( see 8.2.4.2 ). + *

+ * There are 3 possible ways of assigning POC to decoded pictures: + *

+ * - Explicit, i.e. POC is directly specified in a slice header in form + . is a significant part of POC ( see 8.2.1.1 ). - + * Frame based type 1 ( see 8.2.1.2 ). - Frame based type 2 ( see 8.2.1.3 ). + * + * @author The JCodec project + */ +public class POCManager { + + private int prevPOCMsb; + private int prevPOCLsb; + + public int calcPOC(SliceHeader firstSliceHeader, NALUnit firstNu) { + switch (firstSliceHeader.sps.picOrderCntType) { + case 0: + return calcPOC0(firstSliceHeader, firstNu); + case 1: + return calcPOC1(firstSliceHeader, firstNu); + case 2: + return calcPOC2(firstSliceHeader, firstNu); + default: + throw new RuntimeException("POC no!!!"); + } + + } + + private int calcPOC2(SliceHeader firstSliceHeader, NALUnit firstNu) { + return firstSliceHeader.frameNum << 1; + } + + private int calcPOC1(SliceHeader firstSliceHeader, NALUnit firstNu) { + return firstSliceHeader.frameNum << 1; + } + + private int calcPOC0(SliceHeader firstSliceHeader, NALUnit firstNu) { + if (firstNu.type == IDR_SLICE) { + prevPOCMsb = prevPOCLsb = 0; + } + int maxPOCLsbDiv2 = 1 << (firstSliceHeader.sps.log2MaxPicOrderCntLsbMinus4 + 3), maxPOCLsb = maxPOCLsbDiv2 << 1; + int POCLsb = firstSliceHeader.picOrderCntLsb; + + int POCMsb, POC; + if ((POCLsb < prevPOCLsb) && ((prevPOCLsb - POCLsb) >= maxPOCLsbDiv2)) + POCMsb = prevPOCMsb + maxPOCLsb; + else if ((POCLsb > prevPOCLsb) && ((POCLsb - prevPOCLsb) > maxPOCLsbDiv2)) + POCMsb = prevPOCMsb - maxPOCLsb; + else + POCMsb = prevPOCMsb; + + POC = POCMsb + POCLsb; + + if (firstNu.nal_ref_idc > 0) { + if (hasMMCO5(firstSliceHeader, firstNu)) { + prevPOCMsb = 0; + prevPOCLsb = POC; + } else { + prevPOCMsb = POCMsb; + prevPOCLsb = POCLsb; + } + } + + return POC; + } + + private boolean hasMMCO5(SliceHeader firstSliceHeader, NALUnit firstNu) { + if (firstNu.type != IDR_SLICE && firstSliceHeader.refPicMarkingNonIDR != null) { + Instruction[] instructions = firstSliceHeader.refPicMarkingNonIDR.getInstructions(); + for (int i = 0; i < instructions.length; i++) { + Instruction instruction = instructions[i]; + if (instruction.getType() == InstrType.CLEAR) + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/BlockInterpolator.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/BlockInterpolator.java new file mode 100644 index 0000000..a47027b --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/BlockInterpolator.java @@ -0,0 +1,1153 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.common.model.Picture; + +import static java.lang.System.arraycopy; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Interpolator that operates on block level + * + * @author The JCodec project + */ +public class BlockInterpolator { + + private int[] tmp1; + private int[] tmp2; + private byte[] tmp3; + private LumaInterpolator[] safe; + private LumaInterpolator[] unsafe; + + public BlockInterpolator() { + this.tmp1 = new int[1024]; + this.tmp2 = new int[1024]; + this.tmp3 = new byte[1024]; + this.safe = initSafe(); + this.unsafe = initUnsafe(); + } + + /** + * Get block of ( possibly interpolated ) luma pixels + */ + public void getBlockLuma(Picture pic, Picture out, int off, int x, int y, int w, int h) { + int xInd = x & 0x3; + int yInd = y & 0x3; + + int xFp = x >> 2; + int yFp = y >> 2; + if (xFp < 2 || yFp < 2 || xFp > pic.getWidth() - w - 5 || yFp > pic.getHeight() - h - 5) { + unsafe[(yInd << 2) + xInd].getLuma(pic.getData()[0], pic.getWidth(), pic.getHeight(), out.getPlaneData(0), + off, out.getPlaneWidth(0), xFp, yFp, w, h); + } else { + safe[(yInd << 2) + xInd].getLuma(pic.getData()[0], pic.getWidth(), pic.getHeight(), out.getPlaneData(0), + off, out.getPlaneWidth(0), xFp, yFp, w, h); + } + } + + public static void getBlockChroma(byte[] pels, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + int xInd = x & 0x7; + int yInd = y & 0x7; + + int xFull = x >> 3; + int yFull = y >> 3; + + if (xFull < 0 || xFull > picW - blkW - 1 || yFull < 0 || yFull > picH - blkH - 1) { + if (xInd == 0 && yInd == 0) { + getChroma00Unsafe(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, blkW, blkH); + } else if (yInd == 0) { + + getChromaX0Unsafe(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, xInd, blkW, blkH); + + } else if (xInd == 0) { + getChroma0XUnsafe(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, yInd, blkW, blkH); + } else { + getChromaXXUnsafe(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, xInd, yInd, blkW, blkH); + } + } else { + if (xInd == 0 && yInd == 0) { + getChroma00(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, blkW, blkH); + } else if (yInd == 0) { + + getChromaX0(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, xInd, blkW, blkH); + + } else if (xInd == 0) { + getChroma0X(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, yInd, blkW, blkH); + } else { + getChromaXX(pels, picW, picH, blk, blkOff, blkStride, xFull, yFull, xInd, yInd, blkW, blkH); + } + } + } + + /** + * Fullpel (0, 0) + */ + static void getLuma00(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + + int off = y * picW + x; + for (int j = 0; j < blkH; j++) { + arraycopy(pic, off, blk, blkOff, blkW); + off += picW; + blkOff += blkStride; + } + } + + /** + * Fullpel (0, 0) unsafe + */ + static void getLuma00Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + int maxH = picH - 1; + int maxW = picW - 1; + + for (int j = 0; j < blkH; j++) { + int lineStart = clip(j + y, 0, maxH) * picW; + + for (int i = 0; i < blkW; i++) { + blk[blkOff + i] = pic[lineStart + clip(x + i, 0, maxW)]; + } + blkOff += blkStride; + } + } + + /** + * Halfpel (2,0) horizontal, int argument version + */ + static void getLuma20NoRoundInt(int[] pic, int picW, int[] blk, int blkOff, int blkStride, int x, int y, int blkW, + int blkH) { + + int off = y * picW + x; + for (int j = 0; j < blkH; j++) { + int off1 = -2; + for (int i = 0; i < blkW; i++) { + int a = pic[off + off1] + pic[off + off1 + 5]; + int b = pic[off + off1 + 1] + pic[off + off1 + 4]; + int c = pic[off + off1 + 2] + pic[off + off1 + 3]; + blk[blkOff + i] = a + 5 * ((c << 2) - b); + ++off1; + } + off += picW; + blkOff += blkStride; + } + } + + /** + * Halfpel (2,0) horizontal + */ + static void getLuma20NoRound(byte[] pic, int picW, int[] blk, int blkOff, int blkStride, int x, int y, int blkW, + int blkH) { + + int off = y * picW + x; + for (int j = 0; j < blkH; j++) { + int off1 = -2; + for (int i = 0; i < blkW; i++) { + int a = pic[off + off1] + pic[off + off1 + 5]; + int b = pic[off + off1 + 1] + pic[off + off1 + 4]; + int c = pic[off + off1 + 2] + pic[off + off1 + 3]; + blk[blkOff + i] = a + 5 * ((c << 2) - b); + ++off1; + } + off += picW; + blkOff += blkStride; + } + } + + static void getLuma20(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + int off = y * picW + x; + for (int j = 0; j < blkH; j++) { + int off1 = -2; + for (int i = 0; i < blkW; i++) { + int a = pic[off + off1] + pic[off + off1 + 5]; + int b = pic[off + off1 + 1] + pic[off + off1 + 4]; + int c = pic[off + off1 + 2] + pic[off + off1 + 3]; + blk[blkOff + i] = (byte) clip((a + 5 * ((c << 2) - b) + 16) >> 5, -128, 127); + ++off1; + } + off += picW; + blkOff += blkStride; + } + } + + /** + * Halfpel (2, 0) horizontal unsafe + */ + static void getLuma20UnsafeNoRound(byte[] pic, int picW, int picH, int[] blk, int blkOff, int blkStride, int x, + int y, int blkW, int blkH) { + int maxW = picW - 1; + int maxH = picH - 1; + + for (int i = 0; i < blkW; i++) { + int ipos_m2 = clip(x + i - 2, 0, maxW); + int ipos_m1 = clip(x + i - 1, 0, maxW); + int ipos = clip(x + i, 0, maxW); + int ipos_p1 = clip(x + i + 1, 0, maxW); + int ipos_p2 = clip(x + i + 2, 0, maxW); + int ipos_p3 = clip(x + i + 3, 0, maxW); + + int boff = blkOff; + for (int j = 0; j < blkH; j++) { + int lineStart = clip(j + y, 0, maxH) * picW; + + int a = pic[lineStart + ipos_m2] + pic[lineStart + ipos_p3]; + int b = pic[lineStart + ipos_m1] + pic[lineStart + ipos_p2]; + int c = pic[lineStart + ipos] + pic[lineStart + ipos_p1]; + + blk[boff + i] = a + 5 * ((c << 2) - b); + + boff += blkStride; + } + } + } + + void getLuma20Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + + getLuma20UnsafeNoRound(pic, picW, picH, tmp1, blkOff, blkStride, x, y, blkW, blkH); + + for (int i = 0; i < blkW; i++) { + int boff = blkOff; + for (int j = 0; j < blkH; j++) { + blk[boff + i] = (byte) clip((tmp1[boff + i] + 16) >> 5, -128, 127); + boff += blkStride; + } + } + } + + /** + * Halfpel (0, 2) vertical + */ + static void getLuma02NoRoundInt(int[] pic, int picW, int[] blk, int blkOff, int blkStride, int x, int y, int blkW, + int blkH) { + int off = (y - 2) * picW + x, picWx2 = picW + picW, picWx3 = picWx2 + picW, picWx4 = picWx3 + picW, picWx5 = picWx4 + + picW; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + int a = pic[off + i] + pic[off + i + picWx5]; + int b = pic[off + i + picW] + pic[off + i + picWx4]; + int c = pic[off + i + picWx2] + pic[off + i + picWx3]; + + blk[blkOff + i] = a + 5 * ((c << 2) - b); + } + off += picW; + blkOff += blkStride; + } + } + + /** + * Halfpel (0, 2) vertical + */ + static void getLuma02NoRound(byte[] pic, int picW, int[] blk, int blkOff, int blkStride, int x, int y, int blkW, + int blkH) { + int off = (y - 2) * picW + x, picWx2 = picW + picW, picWx3 = picWx2 + picW, picWx4 = picWx3 + picW, picWx5 = picWx4 + + picW; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + int a = pic[off + i] + pic[off + i + picWx5]; + int b = pic[off + i + picW] + pic[off + i + picWx4]; + int c = pic[off + i + picWx2] + pic[off + i + picWx3]; + + blk[blkOff + i] = a + 5 * ((c << 2) - b); + } + off += picW; + blkOff += blkStride; + } + } + + static void getLuma02(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + + int off = (y - 2) * picW + x, picWx2 = picW + picW, picWx3 = picWx2 + picW, picWx4 = picWx3 + picW, picWx5 = picWx4 + + picW; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + int a = pic[off + i] + pic[off + i + picWx5]; + int b = pic[off + i + picW] + pic[off + i + picWx4]; + int c = pic[off + i + picWx2] + pic[off + i + picWx3]; + blk[blkOff + i] = (byte) clip((a + 5 * ((c << 2) - b) + 16) >> 5, -128, 127); + } + off += picW; + blkOff += blkStride; + } + } + + /** + * Hpel (0, 2) vertical unsafe + */ + static void getLuma02UnsafeNoRound(byte[] pic, int picW, int picH, int[] blk, int blkOff, int blkStride, int x, + int y, int blkW, int blkH) { + int maxH = picH - 1; + int maxW = picW - 1; + + for (int j = 0; j < blkH; j++) { + int offP0 = clip(y + j - 2, 0, maxH) * picW; + int offP1 = clip(y + j - 1, 0, maxH) * picW; + int offP2 = clip(y + j, 0, maxH) * picW; + int offP3 = clip(y + j + 1, 0, maxH) * picW; + int offP4 = clip(y + j + 2, 0, maxH) * picW; + int offP5 = clip(y + j + 3, 0, maxH) * picW; + + for (int i = 0; i < blkW; i++) { + int pres_x = clip(x + i, 0, maxW); + + int a = pic[pres_x + offP0] + pic[pres_x + offP5]; + int b = pic[pres_x + offP1] + pic[pres_x + offP4]; + int c = pic[pres_x + offP2] + pic[pres_x + offP3]; + + blk[blkOff + i] = a + 5 * ((c << 2) - b); + } + blkOff += blkStride; + } + } + + void getLuma02Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + + getLuma02UnsafeNoRound(pic, picW, picH, tmp1, blkOff, blkStride, x, y, blkW, blkH); + + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + blk[blkOff + i] = (byte) clip((tmp1[blkOff + i] + 16) >> 5, -128, 127); + } + blkOff += blkStride; + } + } + + /** + * Qpel: (1,0) horizontal + */ + static void getLuma10(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + + getLuma20(pic, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + + int off = y * picW + x; + for (int j = 0; j < blkH; j++) { + + for (int i = 0; i < blkW; i++) { + + blk[blkOff + i] = (byte) ((blk[blkOff + i] + pic[off + i] + 1) >> 1); + } + off += picW; + blkOff += blkStride; + } + } + + /** + * Qpel: (1,0) horizontal unsafe + */ + void getLuma10Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + int maxH = picH - 1; + int maxW = picW - 1; + + getLuma20Unsafe(pic, picW, picH, blk, blkOff, blkStride, x, y, blkW, blkH); + + for (int j = 0; j < blkH; j++) { + int lineStart = clip(j + y, 0, maxH) * picW; + + for (int i = 0; i < blkW; i++) { + blk[blkOff + i] = (byte) ((blk[blkOff + i] + pic[lineStart + clip(x + i, 0, maxW)] + 1) >> 1); + } + blkOff += blkStride; + } + } + + /** + * Qpel (3,0) horizontal + */ + static void getLuma30(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + + getLuma20(pic, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + + int off = y * picW + x; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + blk[blkOff + i] = (byte) ((pic[off + i + 1] + blk[blkOff + i] + 1) >> 1); + } + off += picW; + blkOff += blkStride; + } + } + + /** + * Qpel horizontal (3, 0) unsafe + */ + void getLuma30Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + int maxH = picH - 1; + int maxW = picW - 1; + + getLuma20Unsafe(pic, picW, picH, blk, blkOff, blkStride, x, y, blkW, blkH); + + for (int j = 0; j < blkH; j++) { + int lineStart = clip(j + y, 0, maxH) * picW; + + for (int i = 0; i < blkW; i++) { + blk[blkOff + i] = (byte) ((blk[blkOff + i] + pic[lineStart + clip(x + i + 1, 0, maxW)] + 1) >> 1); + } + blkOff += blkStride; + } + } + + /** + * Qpel vertical (0, 1) + */ + static void getLuma01(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + + getLuma02(pic, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + + int off = y * picW + x; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + blk[blkOff + i] = (byte) ((blk[blkOff + i] + pic[off + i] + 1) >> 1); + } + off += picW; + blkOff += blkStride; + } + } + + /** + * Qpel vertical (0, 1) unsafe + */ + void getLuma01Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + int maxH = picH - 1; + int maxW = picW - 1; + + getLuma02Unsafe(pic, picW, picH, blk, blkOff, blkStride, x, y, blkW, blkH); + + for (int j = 0; j < blkH; j++) { + int lineStart = clip(y + j, 0, maxH) * picW; + for (int i = 0; i < blkW; i++) { + blk[blkOff + i] = (byte) ((blk[blkOff + i] + pic[lineStart + clip(x + i, 0, maxW)] + 1) >> 1); + } + blkOff += blkStride; + } + } + + /** + * Qpel vertical (0, 3) + */ + static void getLuma03(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + + getLuma02(pic, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + + int off = y * picW + x; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + blk[blkOff + i] = (byte) ((blk[blkOff + i] + pic[off + i + picW] + 1) >> 1); + } + off += picW; + blkOff += blkStride; + + } + } + + /** + * Qpel vertical (0, 3) unsafe + */ + void getLuma03Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + int maxH = picH - 1; + int maxW = picW - 1; + + getLuma02Unsafe(pic, picW, picH, blk, blkOff, blkStride, x, y, blkW, blkH); + + for (int j = 0; j < blkH; j++) { + int lineStart = clip(y + j + 1, 0, maxH) * picW; + for (int i = 0; i < blkW; i++) { + blk[blkOff + i] = (byte) ((blk[blkOff + i] + pic[lineStart + clip(x + i, 0, maxW)] + 1) >> 1); + } + blkOff += blkStride; + } + } + + /** + * Hpel horizontal, Qpel vertical (2, 1) + */ + void getLuma21(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + getLuma20NoRound(pic, picW, tmp1, 0, blkW, x, y - 2, blkW, blkH + 7); + getLuma02NoRoundInt(tmp1, blkW, tmp2, blkOff, blkStride, 0, 2, blkW, blkH); + + int off = blkW << 1; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127); + int rounded2 = clip((tmp1[off + i] + 16) >> 5, -128, 127); + blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1); + } + blkOff += blkStride; + off += blkW; + } + } + + /** + * Qpel vertical (2, 1) unsafe + */ + void getLuma21Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + getLuma20UnsafeNoRound(pic, picW, imgH, tmp1, 0, blkW, x, y - 2, blkW, blkH + 7); + getLuma02NoRoundInt(tmp1, blkW, tmp2, blkOff, blkStride, 0, 2, blkW, blkH); + + int off = blkW << 1; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127); + int rounded2 = clip((tmp1[off + i] + 16) >> 5, -128, 127); + blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1); + } + blkOff += blkStride; + off += blkW; + } + } + + /** + * Hpel horizontal, Hpel vertical (2, 2) + */ + void getLuma22(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + getLuma20NoRound(pic, picW, tmp1, 0, blkW, x, y - 2, blkW, blkH + 7); + getLuma02NoRoundInt(tmp1, blkW, tmp2, blkOff, blkStride, 0, 2, blkW, blkH); + + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + blk[blkOff + i] = (byte) (clip((tmp2[blkOff + i] + 512) >> 10, -128, 127)); + } + blkOff += blkStride; + } + } + + /** + * Hpel (2, 2) unsafe + */ + void getLuma22Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + getLuma20UnsafeNoRound(pic, picW, imgH, tmp1, 0, blkW, x, y - 2, blkW, blkH + 7); + getLuma02NoRoundInt(tmp1, blkW, tmp2, blkOff, blkStride, 0, 2, blkW, blkH); + + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + blk[blkOff + i] = (byte) clip((tmp2[blkOff + i] + 512) >> 10, -128, 127); + } + blkOff += blkStride; + } + } + + /** + * Hpel horizontal, Qpel vertical (2, 3) + */ + void getLuma23(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + getLuma20NoRound(pic, picW, tmp1, 0, blkW, x, y - 2, blkW, blkH + 7); + getLuma02NoRoundInt(tmp1, blkW, tmp2, blkOff, blkStride, 0, 2, blkW, blkH); + + int off = blkW << 1; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127); + int rounded2 = clip((tmp1[off + i + blkW] + 16) >> 5, -128, 127); + blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1); + } + blkOff += blkStride; + off += blkW; + } + } + + /** + * Qpel (2, 3) unsafe + */ + void getLuma23Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + getLuma20UnsafeNoRound(pic, picW, imgH, tmp1, 0, blkW, x, y - 2, blkW, blkH + 7); + getLuma02NoRoundInt(tmp1, blkW, tmp2, blkOff, blkStride, 0, 2, blkW, blkH); + + int off = blkW << 1; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127); + int rounded2 = clip((tmp1[off + i + blkW] + 16) >> 5, -128, 127); + blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1); + } + blkOff += blkStride; + off += blkW; + } + } + + /** + * Qpel horizontal, Hpel vertical (1, 2) + */ + void getLuma12(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + + int tmpW = blkW + 7; + + getLuma02NoRound(pic, picW, tmp1, 0, tmpW, x - 2, y, tmpW, blkH); + getLuma20NoRoundInt(tmp1, tmpW, tmp2, blkOff, blkStride, 2, 0, blkW, blkH); + + int off = 2; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127); + int rounded2 = clip((tmp1[off + i] + 16) >> 5, -128, 127); + blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1); + } + blkOff += blkStride; + off += tmpW; + } + } + + /** + * Qpel (1, 2) unsafe + */ + void getLuma12Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + int tmpW = blkW + 7; + + getLuma02UnsafeNoRound(pic, picW, imgH, tmp1, 0, tmpW, x - 2, y, tmpW, blkH); + getLuma20NoRoundInt(tmp1, tmpW, tmp2, blkOff, blkStride, 2, 0, blkW, blkH); + + int off = 2; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127); + int rounded2 = clip((tmp1[off + i] + 16) >> 5, -128, 127); + blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1); + } + blkOff += blkStride; + off += tmpW; + } + } + + /** + * Qpel horizontal, Hpel vertical (3, 2) + */ + void getLuma32(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + int tmpW = blkW + 7; + + getLuma02NoRound(pic, picW, tmp1, 0, tmpW, x - 2, y, tmpW, blkH); + getLuma20NoRoundInt(tmp1, tmpW, tmp2, blkOff, blkStride, 2, 0, blkW, blkH); + + int off = 2; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127); + int rounded2 = clip((tmp1[off + i + 1] + 16) >> 5, -128, 127); + blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1); + } + blkOff += blkStride; + off += tmpW; + } + } + + /** + * Qpel (3, 2) unsafe + */ + void getLuma32Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + int tmpW = blkW + 7; + + getLuma02UnsafeNoRound(pic, picW, imgH, tmp1, 0, tmpW, x - 2, y, tmpW, blkH); + getLuma20NoRoundInt(tmp1, tmpW, tmp2, blkOff, blkStride, 2, 0, blkW, blkH); + + int off = 2; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + int rounded = clip((tmp2[blkOff + i] + 512) >> 10, -128, 127); + int rounded2 = clip((tmp1[off + i + 1] + 16) >> 5, -128, 127); + blk[blkOff + i] = (byte) ((rounded + rounded2 + 1) >> 1); + } + blkOff += blkStride; + off += tmpW; + } + } + + /** + * Qpel horizontal, Qpel vertical (3, 3) + */ + void getLuma33(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + getLuma20(pic, picW, blk, blkOff, blkStride, x, y + 1, blkW, blkH); + getLuma02(pic, picW, tmp3, 0, blkW, x + 1, y, blkW, blkH); + + merge(blk, tmp3, blkOff, blkStride, blkW, blkH); + } + + /** + * Qpel (3, 3) unsafe + */ + void getLuma33Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + getLuma20Unsafe(pic, picW, imgH, blk, blkOff, blkStride, x, y + 1, blkW, blkH); + getLuma02Unsafe(pic, picW, imgH, tmp3, 0, blkW, x + 1, y, blkW, blkH); + + merge(blk, tmp3, blkOff, blkStride, blkW, blkH); + } + + /** + * Qpel horizontal, Qpel vertical (1, 1) + */ + void getLuma11(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + getLuma20(pic, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + getLuma02(pic, picW, tmp3, 0, blkW, x, y, blkW, blkH); + + merge(blk, tmp3, blkOff, blkStride, blkW, blkH); + } + + /** + * Qpel (1, 1) unsafe + */ + void getLuma11Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + getLuma20Unsafe(pic, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + getLuma02Unsafe(pic, picW, imgH, tmp3, 0, blkW, x, y, blkW, blkH); + + merge(blk, tmp3, blkOff, blkStride, blkW, blkH); + } + + /** + * Qpel horizontal, Qpel vertical (1, 3) + */ + void getLuma13(byte[] pic, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, int blkH) { + getLuma20(pic, picW, blk, blkOff, blkStride, x, y + 1, blkW, blkH); + getLuma02(pic, picW, tmp3, 0, blkW, x, y, blkW, blkH); + + merge(blk, tmp3, blkOff, blkStride, blkW, blkH); + } + + /** + * Qpel (1, 3) unsafe + */ + void getLuma13Unsafe(byte[] pic, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + getLuma20Unsafe(pic, picW, imgH, blk, blkOff, blkStride, x, y + 1, blkW, blkH); + getLuma02Unsafe(pic, picW, imgH, tmp3, 0, blkW, x, y, blkW, blkH); + + merge(blk, tmp3, blkOff, blkStride, blkW, blkH); + } + + /** + * Qpel horizontal, Qpel vertical (3, 1) + */ + void getLuma31(byte[] pels, int picW, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, + int blkH) { + getLuma20(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + getLuma02(pels, picW, tmp3, 0, blkW, x + 1, y, blkW, blkH); + + merge(blk, tmp3, blkOff, blkStride, blkW, blkH); + } + + /** + * Qpel (3, 1) unsafe + */ + void getLuma31Unsafe(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + getLuma20Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + getLuma02Unsafe(pels, picW, imgH, tmp3, 0, blkW, x + 1, y, blkW, blkH); + + merge(blk, tmp3, blkOff, blkStride, blkW, blkH); + } + + private static void merge(byte[] first, byte[] second, int blkOff, int blkStride, int blkW, int blkH) { + int tOff = 0; + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + first[blkOff + i] = (byte) ((first[blkOff + i] + second[tOff + i] + 1) >> 1); + } + blkOff += blkStride; + tOff += blkW; + } + } + + /** + * Chroma (0,0) + */ + private static void getChroma00(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + int off = y * picW + x; + for (int j = 0; j < blkH; j++) { + arraycopy(pic, off, blk, blkOff, blkW); + off += picW; + blkOff += blkStride; + } + } + + private static void getChroma00Unsafe(byte[] pic, int picW, int picH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + int maxH = picH - 1; + int maxW = picW - 1; + + for (int j = 0; j < blkH; j++) { + int lineStart = clip(j + y, 0, maxH) * picW; + + for (int i = 0; i < blkW; i++) { + blk[blkOff + i] = pic[lineStart + clip(x + i, 0, maxW)]; + } + blkOff += blkStride; + } + } + + /** + * Chroma (X,0) + */ + private static void getChroma0X(byte[] pels, int picW, int picH, byte[] blk, int blkOff, int blkStride, int fullX, + int fullY, int fracY, int blkW, int blkH) { + int w00 = fullY * picW + fullX; + int w01 = w00 + (fullY < picH - 1 ? picW : 0); + int eMy = 8 - fracY; + + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + + blk[blkOff + i] = (byte) ((eMy * pels[w00 + i] + fracY * pels[w01 + i] + 4) >> 3); + } + w00 += picW; + w01 += picW; + blkOff += blkStride; + } + } + + private static void getChroma0XUnsafe(byte[] pels, int picW, int picH, byte[] blk, int blkOff, int blkStride, int fullX, + int fullY, int fracY, int blkW, int blkH) { + + int maxW = picW - 1; + int maxH = picH - 1; + int eMy = 8 - fracY; + + for (int j = 0; j < blkH; j++) { + int off00 = clip(fullY + j, 0, maxH) * picW; + int off01 = clip(fullY + j + 1, 0, maxH) * picW; + + for (int i = 0; i < blkW; i++) { + int w00 = clip(fullX + i, 0, maxW) + off00; + int w01 = clip(fullX + i, 0, maxW) + off01; + + blk[blkOff + i] = (byte) ((eMy * pels[w00] + fracY * pels[w01] + 4) >> 3); + } + blkOff += blkStride; + } + } + + /** + * Chroma (X,0) + */ + private static void getChromaX0(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int fullX, + int fullY, int fracX, int blkW, int blkH) { + int w00 = fullY * picW + fullX; + int w10 = w00 + (fullX < picW - 1 ? 1 : 0); + int eMx = 8 - fracX; + + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + blk[blkOff + i] = (byte) ((eMx * pels[w00 + i] + fracX * pels[w10 + i] + 4) >> 3); + } + w00 += picW; + w10 += picW; + blkOff += blkStride; + } + } + + private static void getChromaX0Unsafe(byte[] pels, int picW, int picH, byte[] blk, int blkOff, int blkStride, int fullX, + int fullY, int fracX, int blkW, int blkH) { + int eMx = 8 - fracX; + int maxW = picW - 1; + int maxH = picH - 1; + + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + int w00 = clip(fullY + j, 0, maxH) * picW + clip(fullX + i, 0, maxW); + int w10 = clip(fullY + j, 0, maxH) * picW + clip(fullX + i + 1, 0, maxW); + + blk[blkOff + i] = (byte) ((eMx * pels[w00] + fracX * pels[w10] + 4) >> 3); + } + blkOff += blkStride; + } + } + + /** + * Chroma (X,X) + */ + private static void getChromaXX(byte[] pels, int picW, int picH, byte[] blk, int blkOff, int blkStride, int fullX, + int fullY, int fracX, int fracY, int blkW, int blkH) { + int w00 = fullY * picW + fullX; + int w01 = w00 + (fullY < picH - 1 ? picW : 0); + int w10 = w00 + (fullX < picW - 1 ? 1 : 0); + int w11 = w10 + w01 - w00; + int eMx = 8 - fracX; + int eMy = 8 - fracY; + + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + + blk[blkOff + i] = (byte) ((eMx * eMy * pels[w00 + i] + fracX * eMy * pels[w10 + i] + eMx * fracY + * pels[w01 + i] + fracX * fracY * pels[w11 + i] + 32) >> 6); + } + blkOff += blkStride; + w00 += picW; + w01 += picW; + w10 += picW; + w11 += picW; + } + } + + private static void getChromaXXUnsafe(byte[] pels, int picW, int picH, byte[] blk, int blkOff, int blkStride, int fullX, + int fullY, int fracX, int fracY, int blkW, int blkH) { + int maxH = picH - 1; + int maxW = picW - 1; + + int eMx = 8 - fracX; + int eMy = 8 - fracY; + + for (int j = 0; j < blkH; j++) { + for (int i = 0; i < blkW; i++) { + int w00 = clip(fullY + j, 0, maxH) * picW + clip(fullX + i, 0, maxW); + int w01 = clip(fullY + j + 1, 0, maxH) * picW + clip(fullX + i, 0, maxW); + int w10 = clip(fullY + j, 0, maxH) * picW + clip(fullX + i + 1, 0, maxW); + int w11 = clip(fullY + j + 1, 0, maxH) * picW + clip(fullX + i + 1, 0, maxW); + + blk[blkOff + i] = (byte) ((eMx * eMy * pels[w00] + fracX * eMy * pels[w10] + eMx * fracY * pels[w01] + + fracX * fracY * pels[w11] + 32) >> 6); + } + blkOff += blkStride; + } + } + + private interface LumaInterpolator { + void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, int blkW, + int blkH); + } + + private LumaInterpolator[] initSafe() { + final BlockInterpolator self = this; + return new LumaInterpolator[]{ + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + BlockInterpolator.getLuma00(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + BlockInterpolator.getLuma10(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + BlockInterpolator.getLuma20(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + BlockInterpolator.getLuma30(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + BlockInterpolator.getLuma01(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma11(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma21(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma31(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + BlockInterpolator.getLuma02(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma12(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma22(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma32(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + BlockInterpolator.getLuma03(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma13(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma23(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma33(pels, picW, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }}; + } + + private LumaInterpolator[] initUnsafe() { + final BlockInterpolator self = this; + return new LumaInterpolator[]{ + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + BlockInterpolator.getLuma00Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma10Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma20Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma30Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma01Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma11Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma21Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma31Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma02Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma12Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma22Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma32Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma03Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma13Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, + + new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma23Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }, new LumaInterpolator() { + @Override + public void getLuma(byte[] pels, int picW, int imgH, byte[] blk, int blkOff, int blkStride, int x, int y, + int blkW, int blkH) { + self.getLuma33Unsafe(pels, picW, imgH, blk, blkOff, blkStride, x, y, blkW, blkH); + } + }}; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/CABACContst.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/CABACContst.java new file mode 100644 index 0000000..2c1f9a8 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/CABACContst.java @@ -0,0 +1,357 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * This code is borrowed from ffmpeg/libavcodec/h264_cabac.c + * + * @author The JCodec project + */ +public class CABACContst { + + public static int[] cabac_context_init_I_A = {20, 2, 3, 20, 2, 3, -28, -23, -6, -1, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, -9, 4, 0, -7, 13, 3, 0, 1, 0, -17, -13, 0, -7, -21, -27, -31, -24, -18, -27, -21, + -30, -17, -12, -16, -11, -12, -2, -15, -13, -3, -8, -10, -30, -1, -6, -7, -20, -4, -5, -7, -22, -7, -11, + -3, -5, -4, -4, -12, -7, -7, 8, 5, -2, 1, 0, -2, 1, 7, 10, 0, 11, 1, 0, 5, 31, 1, 7, 28, 16, 14, -13, -15, + -13, -13, -12, -10, -16, -10, -7, -13, -19, 1, 0, -5, 18, -8, -15, 0, -4, 2, -11, -3, 15, -13, 0, 0, 21, + -15, 9, 16, 0, 12, 24, 15, 8, 13, 15, 13, 10, 12, 6, 20, 15, 4, 1, 0, 7, 12, 11, 15, 11, 13, 16, 12, 10, + 30, 18, 10, 17, 17, 0, 26, 22, 26, 30, 28, 33, 37, 33, 40, 38, 33, 40, 41, 38, 41, 30, 27, 26, 37, 35, 38, + 38, 37, 38, 42, 35, 39, 14, 27, 21, 12, 2, -3, -6, -5, -3, -2, 0, 1, -2, -1, -9, -5, -5, -3, -2, 0, -16, + -8, -10, -6, -10, -12, -15, -10, -6, -4, -12, -8, -7, -9, -17, -11, -20, -11, -6, -4, -13, -13, -11, -19, + -8, -5, -4, -2, -3, -13, -10, -12, -13, -14, 0, -6, -6, -8, 0, -1, 0, -2, -2, -5, -3, -4, -9, -1, 0, 3, 10, + -7, 15, 14, 16, 12, 1, 20, 18, 5, 1, 10, 17, 9, -12, -11, -16, -7, -8, -7, -9, -13, 4, -3, -3, -6, 10, -1, + -1, -7, -14, 2, 0, -5, 0, -11, 1, 0, -14, 3, 4, -1, -13, 11, 5, 12, 15, 6, 7, 12, 18, 13, 13, 15, 12, 13, + 15, 14, 14, 17, 17, 24, 21, 25, 31, 22, 19, 14, 10, 7, -2, -4, -3, 9, -12, 36, 36, 32, 37, 44, 34, 34, 40, + 33, 35, 33, 38, 33, 23, 13, 29, 26, 22, 31, 35, 34, 34, 36, 34, 32, 35, 34, 39, 30, 34, 29, 19, 31, 31, 25, + -17, -20, -18, -11, -15, -14, -26, -15, -14, 0, -14, -24, -23, -24, -11, 23, 26, 40, 49, 44, 45, 44, 33, + 19, -3, -1, 1, 1, 0, -2, 0, 1, 0, -9, -14, -13, -15, -12, -18, -10, -9, -14, -10, -10, -10, -5, -9, -5, 2, + 21, 24, 28, 28, 29, 29, 35, 29, 14, -17, -12, -16, -11, -12, -2, -15, -13, -3, -8, -10, -30, -17, -12, -16, + -11, -12, -2, -15, -13, -3, -8, -10, -30, -7, -11, -3, -5, -4, -4, -12, -7, -7, 8, 5, -2, 1, 0, -2, 1, 7, + 10, 0, 11, 1, 0, 5, 31, 1, 7, 28, 16, 14, -13, -15, -13, -13, -12, -10, -16, -10, -7, -13, -19, 1, 0, -5, + 18, -7, -11, -3, -5, -4, -4, -12, -7, -7, 8, 5, -2, 1, 0, -2, 1, 7, 10, 0, 11, 1, 0, 5, 31, 1, 7, 28, 16, + 14, -13, -15, -13, -13, -12, -10, -16, -10, -7, -13, -19, 1, 0, -5, 18, 24, 15, 8, 13, 15, 13, 10, 12, 6, + 20, 15, 4, 1, 0, 7, 12, 11, 15, 11, 13, 16, 12, 10, 30, 18, 10, 17, 17, 0, 26, 22, 26, 30, 28, 33, 37, 33, + 40, 38, 33, 40, 41, 38, 41, 24, 15, 8, 13, 15, 13, 10, 12, 6, 20, 15, 4, 1, 0, 7, 12, 11, 15, 11, 13, 16, + 12, 10, 30, 18, 10, 17, 17, 0, 26, 22, 26, 30, 28, 33, 37, 33, 40, 38, 33, 40, 41, 38, 41, -17, -20, -18, + -11, -15, -14, -26, -15, -14, 0, -14, -24, -23, -24, -11, -14, -13, -15, -12, -18, -10, -9, -14, -10, -10, + -10, -5, -9, -5, 2, 23, 26, 40, 49, 44, 45, 44, 33, 19, 21, 24, 28, 28, 29, 29, 35, 29, 14, -3, -1, 1, 1, + 0, -2, 0, 1, 0, -9, -17, -20, -18, -11, -15, -14, -26, -15, -14, 0, -14, -24, -23, -24, -11, -14, -13, -15, + -12, -18, -10, -9, -14, -10, -10, -10, -5, -9, -5, 2, 23, 26, 40, 49, 44, 45, 44, 33, 19, 21, 24, 28, 28, + 29, 29, 35, 29, 14, -3, -1, 1, 1, 0, -2, 0, 1, 0, -9, -6, -6, -8, 0, -1, 0, -2, -2, -5, -3, -4, -9, -1, 0, + 3, 10, -7, 15, 14, 16, 12, 1, 20, 18, 5, 1, 10, 17, 9, -12, -11, -16, -7, -8, -7, -9, -13, 4, -3, -3, -6, + 10, -1, -1, -6, -6, -8, 0, -1, 0, -2, -2, -5, -3, -4, -9, -1, 0, 3, 10, -7, 15, 14, 16, 12, 1, 20, 18, 5, + 1, 10, 17, 9, -12, -11, -16, -7, -8, -7, -9, -13, 4, -3, -3, -6, 10, -1, -1, 15, 6, 7, 12, 18, 13, 13, 15, + 12, 13, 15, 14, 14, 17, 17, 24, 21, 25, 31, 22, 19, 14, 10, 7, -2, -4, -3, 9, -12, 36, 36, 32, 37, 44, 34, + 34, 40, 33, 35, 33, 38, 33, 23, 13, 15, 6, 7, 12, 18, 13, 13, 15, 12, 13, 15, 14, 14, 17, 17, 24, 21, 25, + 31, 22, 19, 14, 10, 7, -2, -4, -3, 9, -12, 36, 36, 32, 37, 44, 34, 34, 40, 33, 35, 33, 38, 33, 23, 13, -3, + -6, -5, -3, -2, 0, 1, -2, -1, -9, -5, -5, -3, -2, 0, -16, -8, -10, -6, -10, -12, -15, -10, -6, -4, -12, -8, + -7, -9, -17, -3, -6, -5, -3, -2, 0, 1, -2, -1, -9, -5, -5, -3, -2, 0, -16, -8, -10, -6, -10, -12, -15, -10, + -6, -4, -12, -8, -7, -9, -17, -3, -8, -10, -30, -3, -8, -10, -30, -3, -8, -10, -30}; + + public static int[] cabac_context_init_I_B = {-15, 54, 74, -15, 54, 74, 127, 104, 53, 54, 51, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 41, 63, 63, 63, 83, 86, 97, 72, 41, 62, 11, 55, 69, 127, 102, 82, 74, 107, 127, 127, 127, + 95, 127, 114, 127, 123, 115, 122, 115, 63, 68, 84, 104, 70, 93, 90, 127, 74, 97, 91, 127, 56, 82, 76, 125, + 93, 87, 77, 71, 63, 68, 84, 62, 65, 61, 56, 66, 64, 61, 78, 50, 52, 35, 44, 38, 45, 46, 44, 17, 51, 50, 19, + 33, 62, 108, 100, 101, 91, 94, 88, 84, 86, 83, 87, 94, 70, 72, 74, 59, 102, 100, 95, 75, 72, 75, 71, 46, + 69, 62, 65, 37, 72, 57, 54, 62, 72, 0, 9, 25, 18, 9, 19, 37, 18, 29, 33, 30, 45, 58, 62, 61, 38, 45, 39, + 42, 44, 45, 41, 49, 34, 42, 55, 51, 46, 89, -19, -17, -17, -25, -20, -23, -27, -23, -28, -17, -11, -15, -6, + 1, 17, -6, 3, 22, -16, -4, -8, -3, 3, 5, 0, 16, 22, 48, 37, 60, 68, 97, 71, 42, 50, 54, 62, 58, 63, 72, 74, + 91, 67, 27, 39, 44, 46, 64, 68, 78, 77, 86, 92, 55, 60, 62, 65, 73, 76, 80, 88, 110, 97, 84, 79, 73, 74, + 86, 96, 97, 117, 78, 33, 48, 53, 62, 71, 79, 86, 90, 97, 0, 93, 84, 79, 66, 71, 62, 60, 59, 75, 62, 58, 66, + 79, 71, 68, 44, 62, 36, 40, 27, 29, 44, 36, 32, 42, 48, 62, 46, 64, 104, 97, 96, 88, 85, 85, 85, 88, 66, + 77, 76, 76, 58, 76, 83, 99, 95, 95, 76, 74, 70, 75, 68, 65, 73, 62, 62, 68, 75, 55, 64, 70, 6, 19, 16, 14, + 13, 11, 15, 16, 23, 23, 20, 26, 44, 40, 47, 17, 21, 22, 27, 29, 35, 50, 57, 63, 77, 82, 94, 69, 109, -35, + -34, -26, -30, -32, -18, -15, -15, -7, -5, 0, 2, 13, 35, 58, -3, 0, 30, -7, -15, -3, 3, -1, 5, 11, 5, 12, + 11, 29, 26, 39, 66, 21, 31, 50, 120, 112, 114, 85, 92, 89, 71, 81, 80, 68, 70, 56, 68, 50, 74, -13, -13, + -15, -14, 3, 6, 34, 54, 82, 75, 23, 34, 43, 54, 55, 61, 64, 68, 92, 106, 97, 90, 90, 88, 73, 79, 86, 73, + 70, 69, 66, 64, 58, 59, -10, -11, -8, -1, 3, 9, 20, 36, 67, 123, 115, 122, 115, 63, 68, 84, 104, 70, 93, + 90, 127, 123, 115, 122, 115, 63, 68, 84, 104, 70, 93, 90, 127, 93, 87, 77, 71, 63, 68, 84, 62, 65, 61, 56, + 66, 64, 61, 78, 50, 52, 35, 44, 38, 45, 46, 44, 17, 51, 50, 19, 33, 62, 108, 100, 101, 91, 94, 88, 84, 86, + 83, 87, 94, 70, 72, 74, 59, 93, 87, 77, 71, 63, 68, 84, 62, 65, 61, 56, 66, 64, 61, 78, 50, 52, 35, 44, 38, + 45, 46, 44, 17, 51, 50, 19, 33, 62, 108, 100, 101, 91, 94, 88, 84, 86, 83, 87, 94, 70, 72, 74, 59, 0, 9, + 25, 18, 9, 19, 37, 18, 29, 33, 30, 45, 58, 62, 61, 38, 45, 39, 42, 44, 45, 41, 49, 34, 42, 55, 51, 46, 89, + -19, -17, -17, -25, -20, -23, -27, -23, -28, -17, -11, -15, -6, 1, 17, 0, 9, 25, 18, 9, 19, 37, 18, 29, 33, + 30, 45, 58, 62, 61, 38, 45, 39, 42, 44, 45, 41, 49, 34, 42, 55, 51, 46, 89, -19, -17, -17, -25, -20, -23, + -27, -23, -28, -17, -11, -15, -6, 1, 17, 120, 112, 114, 85, 92, 89, 71, 81, 80, 68, 70, 56, 68, 50, 74, + 106, 97, 90, 90, 88, 73, 79, 86, 73, 70, 69, 66, 64, 58, 59, -13, -13, -15, -14, 3, 6, 34, 54, 82, -10, + -11, -8, -1, 3, 9, 20, 36, 67, 75, 23, 34, 43, 54, 55, 61, 64, 68, 92, 120, 112, 114, 85, 92, 89, 71, 81, + 80, 68, 70, 56, 68, 50, 74, 106, 97, 90, 90, 88, 73, 79, 86, 73, 70, 69, 66, 64, 58, 59, -13, -13, -15, + -14, 3, 6, 34, 54, 82, -10, -11, -8, -1, 3, 9, 20, 36, 67, 75, 23, 34, 43, 54, 55, 61, 64, 68, 92, 93, 84, + 79, 66, 71, 62, 60, 59, 75, 62, 58, 66, 79, 71, 68, 44, 62, 36, 40, 27, 29, 44, 36, 32, 42, 48, 62, 46, 64, + 104, 97, 96, 88, 85, 85, 85, 88, 66, 77, 76, 76, 58, 76, 83, 93, 84, 79, 66, 71, 62, 60, 59, 75, 62, 58, + 66, 79, 71, 68, 44, 62, 36, 40, 27, 29, 44, 36, 32, 42, 48, 62, 46, 64, 104, 97, 96, 88, 85, 85, 85, 88, + 66, 77, 76, 76, 58, 76, 83, 6, 19, 16, 14, 13, 11, 15, 16, 23, 23, 20, 26, 44, 40, 47, 17, 21, 22, 27, 29, + 35, 50, 57, 63, 77, 82, 94, 69, 109, -35, -34, -26, -30, -32, -18, -15, -15, -7, -5, 0, 2, 13, 35, 58, 6, + 19, 16, 14, 13, 11, 15, 16, 23, 23, 20, 26, 44, 40, 47, 17, 21, 22, 27, 29, 35, 50, 57, 63, 77, 82, 94, 69, + 109, -35, -34, -26, -30, -32, -18, -15, -15, -7, -5, 0, 2, 13, 35, 58, 71, 42, 50, 54, 62, 58, 63, 72, 74, + 91, 67, 27, 39, 44, 46, 64, 68, 78, 77, 86, 92, 55, 60, 62, 65, 73, 76, 80, 88, 110, 71, 42, 50, 54, 62, + 58, 63, 72, 74, 91, 67, 27, 39, 44, 46, 64, 68, 78, 77, 86, 92, 55, 60, 62, 65, 73, 76, 80, 88, 110, 70, + 93, 90, 127, 70, 93, 90, 127, 70, 93, 90, 127}; + + public static int[][] cabac_context_init_PB_A = { + {20, 2, 3, 20, 2, 3, -28, -23, -6, -1, 7, 23, 23, 21, 1, 0, -37, 5, -13, -11, 1, 12, -4, 17, 18, 9, 29, + 26, 16, 9, -46, -20, 1, -13, -11, 1, -6, -17, -6, 9, -3, -6, -11, 6, 7, -5, 2, 0, -3, -10, 5, 4, + -3, 0, -7, -5, -4, -5, -7, 1, 0, 0, 0, 0, -9, 4, 0, -7, 13, 3, 0, -4, -3, -27, -28, -25, -23, -28, + -20, -16, -22, -21, -18, -13, -29, -7, -5, -7, -13, -3, -1, -1, -9, -3, -9, -8, -23, 5, 6, 6, 6, + -1, 0, -4, -8, -2, -6, -1, -7, 2, 5, -3, 1, 6, -4, 1, -4, 0, 2, -2, 11, 4, 1, 11, 18, 12, 13, 13, + -10, -7, -2, 13, 9, -7, 9, 2, 5, -2, 0, 0, -13, -5, -1, 4, -6, 4, 14, 4, 13, 3, 1, 9, 7, 16, 5, 4, + 11, -5, -1, 0, 22, 5, 14, -1, 0, 9, 11, 2, 3, 0, 0, 2, 2, 0, 4, 2, 6, 0, 3, 2, 4, 6, 6, 7, 6, 6, + 11, 14, 8, -1, 7, -3, 15, 22, -1, 25, 30, 28, 28, 32, 34, 30, 30, 32, 31, 26, 26, 37, 28, 17, 1, 5, + 9, 16, 18, 18, 22, 24, 23, 18, 20, 11, 9, 9, -1, -2, -9, -6, -2, 0, 0, -3, -2, -4, -4, -8, -17, -9, + 3, 0, 0, 0, -6, -7, -12, -11, -30, 1, -3, -1, 1, 2, -6, 0, 0, -3, -10, 0, -4, 5, 7, 1, -2, -3, -3, + -11, 0, 8, 10, 14, 13, 2, 0, -3, -6, -8, 0, -13, -16, -10, -21, -18, -14, -22, -21, -18, -21, -23, + -26, -10, -12, -5, -9, -22, -5, 9, -4, -10, -1, 7, 9, 5, 12, 15, 18, 17, 10, 7, -1, 7, 8, 9, 6, 2, + 13, 10, 6, 5, 13, 4, 6, -2, -2, 6, 10, 9, 12, 3, 14, 10, -3, 13, 17, 7, 7, 13, 10, 26, 14, 11, 9, + 18, 21, 23, 32, 32, 34, 39, 42, 41, 46, 38, 21, 45, 53, 48, 65, 43, 39, 30, 18, 20, 0, -14, -5, + -19, -35, 27, 28, 31, 27, 34, 30, 24, 33, 22, 26, 21, 26, 23, 16, 14, 8, 6, 17, 21, 23, 26, 27, 28, + 28, 23, 24, 28, 23, 19, 22, 22, 11, 12, 11, 14, -4, -7, -5, -9, -8, -10, -19, -12, -16, -15, -20, + -19, -16, -22, -20, 9, 26, 33, 39, 41, 45, 49, 45, 36, -6, -7, -7, -8, -5, -12, -6, -5, -8, -8, -5, + -6, -10, -7, -17, -18, -4, -10, -9, -9, -1, -8, -14, 0, 2, 21, 33, 39, 46, 51, 60, 61, 55, 42, -7, + -5, -7, -13, -3, -1, -1, -9, -3, -9, -8, -23, -7, -5, -7, -13, -3, -1, -1, -9, -3, -9, -8, -23, -2, + -6, -1, -7, 2, 5, -3, 1, 6, -4, 1, -4, 0, 2, -2, 11, 4, 1, 11, 18, 12, 13, 13, -10, -7, -2, 13, 9, + -7, 9, 2, 5, -2, 0, 0, -13, -5, -1, 4, -6, 4, 14, 4, 13, -2, -6, -1, -7, 2, 5, -3, 1, 6, -4, 1, -4, + 0, 2, -2, 11, 4, 1, 11, 18, 12, 13, 13, -10, -7, -2, 13, 9, -7, 9, 2, 5, -2, 0, 0, -13, -5, -1, 4, + -6, 4, 14, 4, 13, 11, 2, 3, 0, 0, 2, 2, 0, 4, 2, 6, 0, 3, 2, 4, 6, 6, 7, 6, 6, 11, 14, 8, -1, 7, + -3, 15, 22, -1, 25, 30, 28, 28, 32, 34, 30, 30, 32, 31, 26, 26, 37, 28, 17, 11, 2, 3, 0, 0, 2, 2, + 0, 4, 2, 6, 0, 3, 2, 4, 6, 6, 7, 6, 6, 11, 14, 8, -1, 7, -3, 15, 22, -1, 25, 30, 28, 28, 32, 34, + 30, 30, 32, 31, 26, 26, 37, 28, 17, -4, -7, -5, -9, -8, -10, -19, -12, -16, -15, -20, -19, -16, + -22, -20, -5, -6, -10, -7, -17, -18, -4, -10, -9, -9, -1, -8, -14, 0, 2, 9, 26, 33, 39, 41, 45, 49, + 45, 36, 21, 33, 39, 46, 51, 60, 61, 55, 42, -6, -7, -7, -8, -5, -12, -6, -5, -8, -8, -4, -7, -5, + -9, -8, -10, -19, -12, -16, -15, -20, -19, -16, -22, -20, -5, -6, -10, -7, -17, -18, -4, -10, -9, + -9, -1, -8, -14, 0, 2, 9, 26, 33, 39, 41, 45, 49, 45, 36, 21, 33, 39, 46, 51, 60, 61, 55, 42, -6, + -7, -7, -8, -5, -12, -6, -5, -8, -8, -13, -16, -10, -21, -18, -14, -22, -21, -18, -21, -23, -26, + -10, -12, -5, -9, -22, -5, 9, -4, -10, -1, 7, 9, 5, 12, 15, 18, 17, 10, 7, -1, 7, 8, 9, 6, 2, 13, + 10, 6, 5, 13, 4, 6, -13, -16, -10, -21, -18, -14, -22, -21, -18, -21, -23, -26, -10, -12, -5, -9, + -22, -5, 9, -4, -10, -1, 7, 9, 5, 12, 15, 18, 17, 10, 7, -1, 7, 8, 9, 6, 2, 13, 10, 6, 5, 13, 4, 6, + 14, 11, 9, 18, 21, 23, 32, 32, 34, 39, 42, 41, 46, 38, 21, 45, 53, 48, 65, 43, 39, 30, 18, 20, 0, + -14, -5, -19, -35, 27, 28, 31, 27, 34, 30, 24, 33, 22, 26, 21, 26, 23, 16, 14, 14, 11, 9, 18, 21, + 23, 32, 32, 34, 39, 42, 41, 46, 38, 21, 45, 53, 48, 65, 43, 39, 30, 18, 20, 0, -14, -5, -19, -35, + 27, 28, 31, 27, 34, 30, 24, 33, 22, 26, 21, 26, 23, 16, 14, -6, -2, 0, 0, -3, -2, -4, -4, -8, -17, + -9, 3, 0, 0, 0, -6, -7, -12, -11, -30, 1, -3, -1, 1, 2, -6, 0, 0, -3, -10, -6, -2, 0, 0, -3, -2, + -4, -4, -8, -17, -9, 3, 0, 0, 0, -6, -7, -12, -11, -30, 1, -3, -1, 1, 2, -6, 0, 0, -3, -10, -3, -9, + -8, -23, -3, -9, -8, -23, -3, -9, -8, -23}, + + {20, 2, 3, 20, 2, 3, -28, -23, -6, -1, 7, 22, 34, 16, -2, 4, -29, 2, -6, -13, 5, 9, -3, 10, 26, 19, 40, + 57, 41, 26, -45, -15, -4, -6, -13, 5, 6, -13, 0, 8, -2, -5, -10, 2, 2, -3, -3, 1, -3, -6, 0, -3, + -7, -5, -1, -1, 1, -2, -5, 0, 0, 0, 0, 0, -9, 4, 0, -7, 13, 3, 13, 7, 2, -39, -18, -17, -26, -35, + -24, -23, -27, -24, -21, -18, -36, 0, -5, -7, -4, 0, 0, -15, -35, -2, -12, -9, -31, 3, 7, 7, 8, -3, + 0, -7, -9, -13, -13, -9, -14, -8, -12, -23, -24, -10, -20, -17, -78, -70, -50, -46, -4, -5, -4, -8, + 2, -1, -7, -6, -8, -34, -3, 32, 30, -44, 0, -5, 0, -1, -3, -8, -25, -14, -5, 5, 2, 0, -9, -11, 18, + -4, 0, 7, 9, 18, 9, 5, 9, 0, 0, 2, 19, -4, 15, 12, 9, 0, 4, 10, 10, 33, 52, 18, 28, 35, 38, 34, 39, + 32, 102, 0, 56, 33, 29, 37, 51, 39, 52, 69, 67, 44, 32, 55, 32, 0, 27, 33, 34, 36, 38, 38, 34, 35, + 34, 32, 37, 35, 30, 28, 26, 29, 0, 2, 8, 14, 18, 17, 21, 17, 20, 18, 27, 16, 7, 16, 11, 10, -10, + -23, -15, -7, 0, -5, -11, -9, -9, -10, -34, -21, -3, -5, -7, -11, -15, -17, -25, -25, -28, -11, + -10, -10, -10, -9, -16, -7, -4, -5, -9, 2, -9, 1, 11, 5, -2, -2, 0, -8, 3, 7, 10, 17, 16, 3, -1, + -5, -1, -4, 0, -21, -23, -20, -26, -25, -17, -27, -27, -17, -26, -27, -33, -10, -14, -8, -17, -28, + -6, -2, -4, -9, -8, -1, 5, 1, 9, 0, 1, 7, -7, -6, -16, -2, 2, -6, -3, 2, -3, -3, 0, 9, -1, -2, -2, + -1, -9, 14, 16, 0, 18, 11, 12, 10, 2, 12, 13, 0, 3, 19, 3, 18, 19, 18, 14, 26, 31, 33, 33, 37, 39, + 42, 47, 45, 49, 41, 32, 69, 63, 66, 77, 54, 52, 41, 36, 40, 30, 28, 23, 12, 11, 37, 39, 40, 38, 46, + 42, 40, 49, 38, 40, 38, 46, 31, 29, 25, 12, 11, 26, 22, 23, 27, 33, 26, 30, 27, 18, 25, 18, 12, 21, + 14, 11, 25, 21, 21, -5, -6, -10, -7, -17, -18, -4, -10, -9, -9, -1, -8, -14, 0, 2, 17, 32, 42, 49, + 53, 64, 68, 66, 47, -5, 0, -1, -2, -2, -9, -6, -4, -4, -7, -3, -3, -7, -6, -12, -14, -3, -6, -5, + -5, 0, -4, -9, 1, 2, 17, 32, 42, 49, 53, 64, 68, 66, 47, 0, -5, -7, -4, 0, 0, -15, -35, -2, -12, + -9, -31, 0, -5, -7, -4, 0, 0, -15, -35, -2, -12, -9, -31, -13, -13, -9, -14, -8, -12, -23, -24, + -10, -20, -17, -78, -70, -50, -46, -4, -5, -4, -8, 2, -1, -7, -6, -8, -34, -3, 32, 30, -44, 0, -5, + 0, -1, -3, -8, -25, -14, -5, 5, 2, 0, -9, -11, 18, -13, -13, -9, -14, -8, -12, -23, -24, -10, -20, + -17, -78, -70, -50, -46, -4, -5, -4, -8, 2, -1, -7, -6, -8, -34, -3, 32, 30, -44, 0, -5, 0, -1, -3, + -8, -25, -14, -5, 5, 2, 0, -9, -11, 18, 4, 10, 10, 33, 52, 18, 28, 35, 38, 34, 39, 32, 102, 0, 56, + 33, 29, 37, 51, 39, 52, 69, 67, 44, 32, 55, 32, 0, 27, 33, 34, 36, 38, 38, 34, 35, 34, 32, 37, 35, + 30, 28, 26, 29, 4, 10, 10, 33, 52, 18, 28, 35, 38, 34, 39, 32, 102, 0, 56, 33, 29, 37, 51, 39, 52, + 69, 67, 44, 32, 55, 32, 0, 27, 33, 34, 36, 38, 38, 34, 35, 34, 32, 37, 35, 30, 28, 26, 29, -5, -6, + -10, -7, -17, -18, -4, -10, -9, -9, -1, -8, -14, 0, 2, -3, -3, -7, -6, -12, -14, -3, -6, -5, -5, 0, + -4, -9, 1, 2, 17, 32, 42, 49, 53, 64, 68, 66, 47, 17, 32, 42, 49, 53, 64, 68, 66, 47, -5, 0, -1, + -2, -2, -9, -6, -4, -4, -7, -5, -6, -10, -7, -17, -18, -4, -10, -9, -9, -1, -8, -14, 0, 2, -3, -3, + -7, -6, -12, -14, -3, -6, -5, -5, 0, -4, -9, 1, 2, 17, 32, 42, 49, 53, 64, 68, 66, 47, 17, 32, 42, + 49, 53, 64, 68, 66, 47, -5, 0, -1, -2, -2, -9, -6, -4, -4, -7, -21, -23, -20, -26, -25, -17, -27, + -27, -17, -26, -27, -33, -10, -14, -8, -17, -28, -6, -2, -4, -9, -8, -1, 5, 1, 9, 0, 1, 7, -7, -6, + -16, -2, 2, -6, -3, 2, -3, -3, 0, 9, -1, -2, -2, -21, -23, -20, -26, -25, -17, -27, -27, -17, -26, + -27, -33, -10, -14, -8, -17, -28, -6, -2, -4, -9, -8, -1, 5, 1, 9, 0, 1, 7, -7, -6, -16, -2, 2, -6, + -3, 2, -3, -3, 0, 9, -1, -2, -2, 19, 18, 14, 26, 31, 33, 33, 37, 39, 42, 47, 45, 49, 41, 32, 69, + 63, 66, 77, 54, 52, 41, 36, 40, 30, 28, 23, 12, 11, 37, 39, 40, 38, 46, 42, 40, 49, 38, 40, 38, 46, + 31, 29, 25, 19, 18, 14, 26, 31, 33, 33, 37, 39, 42, 47, 45, 49, 41, 32, 69, 63, 66, 77, 54, 52, 41, + 36, 40, 30, 28, 23, 12, 11, 37, 39, 40, 38, 46, 42, 40, 49, 38, 40, 38, 46, 31, 29, 25, -23, -15, + -7, 0, -5, -11, -9, -9, -10, -34, -21, -3, -5, -7, -11, -15, -17, -25, -25, -28, -11, -10, -10, + -10, -9, -16, -7, -4, -5, -9, -23, -15, -7, 0, -5, -11, -9, -9, -10, -34, -21, -3, -5, -7, -11, + -15, -17, -25, -25, -28, -11, -10, -10, -10, -9, -16, -7, -4, -5, -9, -2, -12, -9, -31, -2, -12, + -9, -31, -2, -12, -9, -31}, + {20, 2, 3, 20, 2, 3, -28, -23, -6, -1, 7, 29, 25, 14, -10, -3, -27, 26, -4, -24, 5, 6, -17, 14, 20, 20, + 29, 54, 37, 12, -32, -22, -2, -4, -24, 5, -6, -14, -6, 4, -11, -15, -21, 19, 20, 4, 6, 1, -5, -13, + 5, 6, -3, -1, 3, -4, -2, -12, -7, 1, 0, 0, 0, 0, -9, 4, 0, -7, 13, 3, 7, -9, -20, -36, -17, -14, + -25, -25, -12, -17, -31, -14, -18, -13, -37, 11, 5, 2, 5, -6, 4, -14, -37, -5, -11, -11, -30, 0, + -2, 0, -4, -6, 3, -8, -13, -4, -12, -5, -3, -4, -8, -16, -9, -1, 5, 4, -4, -2, 2, -1, -4, -1, 0, + -7, -4, -6, -3, -6, 8, -9, -11, 9, 0, -5, 1, -15, -5, -8, -21, -21, -13, -25, -29, 9, 17, -8, -5, + -2, 13, 3, -7, 8, -10, 3, -3, -20, 0, 1, -3, -21, 16, -23, 17, 44, 50, -22, 4, 0, 7, 11, 8, 6, 7, + 3, 8, 13, 13, 4, 3, 2, 6, 8, 11, 14, 7, 4, 4, 13, 9, 19, 10, 12, 0, 20, 8, 35, 33, 28, 24, 27, 34, + 52, 39, 19, 31, 36, 24, 34, 30, 22, 20, 19, 27, 19, 15, 15, 21, 25, 30, 31, 27, 24, 0, 14, 15, 26, + -24, -24, -22, -9, 0, 0, -14, -13, -13, -11, -29, -21, -14, -12, -11, -10, -21, -16, -23, -15, -37, + -10, -8, -8, -8, -7, -14, -10, -9, -12, -18, -4, -22, -16, -2, 1, -13, -9, -4, -13, -13, -6, -13, + -6, -2, -16, -10, -13, -9, -10, 0, -22, -25, -25, -27, -19, -23, -25, -26, -24, -28, -31, -37, -10, + -15, -10, -13, -50, -5, 17, -5, -13, -12, -2, 0, -1, 4, -7, 5, 15, 1, 0, -10, 1, 0, 2, 0, -5, 7, 5, + 2, 14, 15, 5, 2, -2, -18, 12, 5, -12, 11, 5, 0, 2, -6, 5, 7, -6, -11, -2, -2, 25, 17, 16, 17, 27, + 37, 41, 42, 48, 39, 46, 52, 46, 52, 43, 32, 61, 56, 62, 81, 45, 35, 28, 34, 39, 30, 20, 18, 15, 0, + 36, 37, 37, 32, 34, 29, 24, 34, 31, 35, 31, 33, 36, 27, 21, 18, 19, 36, 24, 27, 24, 31, 22, 22, 16, + 15, 14, 3, -16, 21, 22, 25, 21, 19, 17, -3, -8, -9, -10, -18, -12, -11, -5, -17, -14, -16, -8, -14, + -9, -11, 9, 30, 31, 33, 33, 31, 37, 31, 20, -9, -7, -8, -11, -10, -12, -8, -9, -6, -10, -3, -8, -9, + -10, -18, -12, -11, -5, -17, -14, -16, -8, -14, -9, -11, 9, 30, 31, 33, 33, 31, 37, 31, 20, 11, 5, + 2, 5, -6, 4, -14, -37, -5, -11, -11, -30, 11, 5, 2, 5, -6, 4, -14, -37, -5, -11, -11, -30, -4, -12, + -5, -3, -4, -8, -16, -9, -1, 5, 4, -4, -2, 2, -1, -4, -1, 0, -7, -4, -6, -3, -6, 8, -9, -11, 9, 0, + -5, 1, -15, -5, -8, -21, -21, -13, -25, -29, 9, 17, -8, -5, -2, 13, -4, -12, -5, -3, -4, -8, -16, + -9, -1, 5, 4, -4, -2, 2, -1, -4, -1, 0, -7, -4, -6, -3, -6, 8, -9, -11, 9, 0, -5, 1, -15, -5, -8, + -21, -21, -13, -25, -29, 9, 17, -8, -5, -2, 13, 4, 0, 7, 11, 8, 6, 7, 3, 8, 13, 13, 4, 3, 2, 6, 8, + 11, 14, 7, 4, 4, 13, 9, 19, 10, 12, 0, 20, 8, 35, 33, 28, 24, 27, 34, 52, 39, 19, 31, 36, 24, 34, + 30, 22, 4, 0, 7, 11, 8, 6, 7, 3, 8, 13, 13, 4, 3, 2, 6, 8, 11, 14, 7, 4, 4, 13, 9, 19, 10, 12, 0, + 20, 8, 35, 33, 28, 24, 27, 34, 52, 39, 19, 31, 36, 24, 34, 30, 22, -3, -8, -9, -10, -18, -12, -11, + -5, -17, -14, -16, -8, -14, -9, -11, -3, -8, -9, -10, -18, -12, -11, -5, -17, -14, -16, -8, -14, + -9, -11, 9, 30, 31, 33, 33, 31, 37, 31, 20, 9, 30, 31, 33, 33, 31, 37, 31, 20, -9, -7, -8, -11, + -10, -12, -8, -9, -6, -10, -3, -8, -9, -10, -18, -12, -11, -5, -17, -14, -16, -8, -14, -9, -11, -3, + -8, -9, -10, -18, -12, -11, -5, -17, -14, -16, -8, -14, -9, -11, 9, 30, 31, 33, 33, 31, 37, 31, 20, + 9, 30, 31, 33, 33, 31, 37, 31, 20, -9, -7, -8, -11, -10, -12, -8, -9, -6, -10, -22, -25, -25, -27, + -19, -23, -25, -26, -24, -28, -31, -37, -10, -15, -10, -13, -50, -5, 17, -5, -13, -12, -2, 0, -1, + 4, -7, 5, 15, 1, 0, -10, 1, 0, 2, 0, -5, 7, 5, 2, 14, 15, 5, 2, -22, -25, -25, -27, -19, -23, -25, + -26, -24, -28, -31, -37, -10, -15, -10, -13, -50, -5, 17, -5, -13, -12, -2, 0, -1, 4, -7, 5, 15, 1, + 0, -10, 1, 0, 2, 0, -5, 7, 5, 2, 14, 15, 5, 2, 17, 16, 17, 27, 37, 41, 42, 48, 39, 46, 52, 46, 52, + 43, 32, 61, 56, 62, 81, 45, 35, 28, 34, 39, 30, 20, 18, 15, 0, 36, 37, 37, 32, 34, 29, 24, 34, 31, + 35, 31, 33, 36, 27, 21, 17, 16, 17, 27, 37, 41, 42, 48, 39, 46, 52, 46, 52, 43, 32, 61, 56, 62, 81, + 45, 35, 28, 34, 39, 30, 20, 18, 15, 0, 36, 37, 37, 32, 34, 29, 24, 34, 31, 35, 31, 33, 36, 27, 21, + -24, -22, -9, 0, 0, -14, -13, -13, -11, -29, -21, -14, -12, -11, -10, -21, -16, -23, -15, -37, -10, + -8, -8, -8, -7, -14, -10, -9, -12, -18, -24, -22, -9, 0, 0, -14, -13, -13, -11, -29, -21, -14, -12, + -11, -10, -21, -16, -23, -15, -37, -10, -8, -8, -8, -7, -14, -10, -9, -12, -18, -5, -11, -11, -30, + -5, -11, -11, -30, -5, -11, -11, -30}}; + public static int[][] cabac_context_init_PB_B = { + {-15, 54, 74, -15, 54, 74, 127, 104, 53, 54, 51, 33, 2, 0, 9, 49, 118, 57, 78, 65, 62, 49, 73, 50, 64, 43, + 0, 67, 90, 104, 127, 104, 67, 78, 65, 62, 86, 95, 61, 45, 69, 81, 96, 55, 67, 86, 88, 58, 76, 94, + 54, 69, 81, 88, 67, 74, 74, 80, 72, 58, 41, 63, 63, 63, 83, 86, 97, 72, 41, 62, 45, 78, 96, 126, + 98, 101, 67, 82, 94, 83, 110, 91, 102, 93, 127, 92, 89, 96, 108, 46, 65, 57, 93, 74, 92, 87, 126, + 54, 60, 59, 69, 48, 68, 69, 88, 85, 78, 75, 77, 54, 50, 68, 50, 42, 81, 63, 70, 67, 57, 76, 35, 64, + 61, 35, 25, 24, 29, 36, 93, 73, 73, 46, 49, 100, 53, 53, 53, 61, 56, 56, 63, 60, 62, 57, 69, 57, + 39, 51, 68, 64, 61, 63, 50, 39, 44, 52, 48, 60, 59, 59, 33, 44, 43, 78, 60, 69, 28, 40, 44, 49, 46, + 44, 51, 47, 39, 62, 46, 54, 54, 58, 63, 51, 57, 53, 52, 55, 45, 36, 53, 82, 55, 78, 46, 31, 84, 7, + -7, 3, 4, 0, -1, 6, 6, 9, 19, 27, 30, 20, 34, 70, 67, 59, 67, 30, 32, 35, 29, 31, 38, 43, 41, 63, + 59, 64, 94, 89, 108, 76, 44, 45, 52, 64, 59, 70, 75, 82, 102, 77, 24, 42, 48, 55, 59, 71, 83, 87, + 119, 58, 29, 36, 38, 43, 55, 58, 64, 74, 90, 70, 29, 31, 42, 59, 58, 72, 81, 97, 58, 5, 14, 18, 27, + 40, 58, 70, 79, 85, 0, 106, 106, 87, 114, 110, 98, 110, 106, 103, 107, 108, 112, 96, 95, 91, 93, + 94, 86, 67, 80, 85, 70, 60, 58, 61, 50, 50, 49, 54, 41, 46, 51, 49, 52, 41, 47, 55, 41, 44, 50, 53, + 49, 63, 64, 69, 59, 70, 44, 31, 43, 53, 34, 38, 52, 40, 32, 44, 38, 50, 57, 43, 11, 14, 11, 11, 9, + -2, -15, -15, -21, -23, -33, -31, -28, -12, 29, -24, -45, -26, -43, -19, -10, 9, 26, 27, 57, 82, + 75, 97, 125, 0, 0, -4, 6, 8, 10, 22, 19, 32, 31, 41, 44, 47, 65, 71, 60, 63, 65, 24, 20, 23, 32, + 23, 24, 40, 32, 29, 42, 57, 53, 61, 86, 40, 51, 59, 79, 71, 69, 70, 66, 68, 73, 69, 70, 67, 62, 70, + 66, 65, 63, -2, -9, -9, -7, -2, 3, 9, 27, 59, 66, 35, 42, 45, 48, 56, 60, 62, 66, 76, 85, 81, 77, + 81, 80, 73, 74, 83, 71, 67, 61, 66, 66, 59, 59, -13, -14, -7, -2, 2, 6, 17, 34, 62, 92, 89, 96, + 108, 46, 65, 57, 93, 74, 92, 87, 126, 92, 89, 96, 108, 46, 65, 57, 93, 74, 92, 87, 126, 85, 78, 75, + 77, 54, 50, 68, 50, 42, 81, 63, 70, 67, 57, 76, 35, 64, 61, 35, 25, 24, 29, 36, 93, 73, 73, 46, 49, + 100, 53, 53, 53, 61, 56, 56, 63, 60, 62, 57, 69, 57, 39, 51, 68, 85, 78, 75, 77, 54, 50, 68, 50, + 42, 81, 63, 70, 67, 57, 76, 35, 64, 61, 35, 25, 24, 29, 36, 93, 73, 73, 46, 49, 100, 53, 53, 53, + 61, 56, 56, 63, 60, 62, 57, 69, 57, 39, 51, 68, 28, 40, 44, 49, 46, 44, 51, 47, 39, 62, 46, 54, 54, + 58, 63, 51, 57, 53, 52, 55, 45, 36, 53, 82, 55, 78, 46, 31, 84, 7, -7, 3, 4, 0, -1, 6, 6, 9, 19, + 27, 30, 20, 34, 70, 28, 40, 44, 49, 46, 44, 51, 47, 39, 62, 46, 54, 54, 58, 63, 51, 57, 53, 52, 55, + 45, 36, 53, 82, 55, 78, 46, 31, 84, 7, -7, 3, 4, 0, -1, 6, 6, 9, 19, 27, 30, 20, 34, 70, 79, 71, + 69, 70, 66, 68, 73, 69, 70, 67, 62, 70, 66, 65, 63, 85, 81, 77, 81, 80, 73, 74, 83, 71, 67, 61, 66, + 66, 59, 59, -2, -9, -9, -7, -2, 3, 9, 27, 59, -13, -14, -7, -2, 2, 6, 17, 34, 62, 66, 35, 42, 45, + 48, 56, 60, 62, 66, 76, 79, 71, 69, 70, 66, 68, 73, 69, 70, 67, 62, 70, 66, 65, 63, 85, 81, 77, 81, + 80, 73, 74, 83, 71, 67, 61, 66, 66, 59, 59, -2, -9, -9, -7, -2, 3, 9, 27, 59, -13, -14, -7, -2, 2, + 6, 17, 34, 62, 66, 35, 42, 45, 48, 56, 60, 62, 66, 76, 106, 106, 87, 114, 110, 98, 110, 106, 103, + 107, 108, 112, 96, 95, 91, 93, 94, 86, 67, 80, 85, 70, 60, 58, 61, 50, 50, 49, 54, 41, 46, 51, 49, + 52, 41, 47, 55, 41, 44, 50, 53, 49, 63, 64, 106, 106, 87, 114, 110, 98, 110, 106, 103, 107, 108, + 112, 96, 95, 91, 93, 94, 86, 67, 80, 85, 70, 60, 58, 61, 50, 50, 49, 54, 41, 46, 51, 49, 52, 41, + 47, 55, 41, 44, 50, 53, 49, 63, 64, 11, 14, 11, 11, 9, -2, -15, -15, -21, -23, -33, -31, -28, -12, + 29, -24, -45, -26, -43, -19, -10, 9, 26, 27, 57, 82, 75, 97, 125, 0, 0, -4, 6, 8, 10, 22, 19, 32, + 31, 41, 44, 47, 65, 71, 11, 14, 11, 11, 9, -2, -15, -15, -21, -23, -33, -31, -28, -12, 29, -24, + -45, -26, -43, -19, -10, 9, 26, 27, 57, 82, 75, 97, 125, 0, 0, -4, 6, 8, 10, 22, 19, 32, 31, 41, + 44, 47, 65, 71, 76, 44, 45, 52, 64, 59, 70, 75, 82, 102, 77, 24, 42, 48, 55, 59, 71, 83, 87, 119, + 58, 29, 36, 38, 43, 55, 58, 64, 74, 90, 76, 44, 45, 52, 64, 59, 70, 75, 82, 102, 77, 24, 42, 48, + 55, 59, 71, 83, 87, 119, 58, 29, 36, 38, 43, 55, 58, 64, 74, 90, 74, 92, 87, 126, 74, 92, 87, 126, + 74, 92, 87, 126}, + + {-15, 54, 74, -15, 54, 74, 127, 104, 53, 54, 51, 25, 0, 0, 9, 41, 118, 65, 71, 79, 52, 50, 70, 54, 34, 22, + 0, 2, 36, 69, 127, 101, 76, 71, 79, 52, 69, 90, 52, 43, 69, 82, 96, 59, 75, 87, 100, 56, 74, 85, + 59, 81, 86, 95, 66, 77, 70, 86, 72, 61, 41, 63, 63, 63, 83, 86, 97, 72, 41, 62, 15, 51, 80, 127, + 91, 96, 81, 98, 102, 97, 119, 99, 110, 102, 127, 80, 89, 94, 92, 39, 65, 84, 127, 73, 104, 91, 127, + 55, 56, 55, 61, 53, 68, 74, 88, 103, 91, 89, 92, 76, 87, 110, 105, 78, 112, 99, 127, 127, 127, 127, + 66, 78, 71, 72, 59, 55, 70, 75, 89, 119, 75, 20, 22, 127, 54, 61, 58, 60, 61, 67, 84, 74, 65, 52, + 57, 61, 69, 70, 55, 71, 58, 61, 41, 25, 32, 43, 47, 44, 51, 46, 38, 66, 38, 42, 34, 89, 45, 28, 31, + -11, -43, 15, 0, -22, -25, 0, -18, -12, -94, 0, -15, -4, 10, -5, -29, -9, -34, -58, -63, -5, 7, + -29, 1, 0, 36, -25, -30, -28, -28, -27, -18, -16, -14, -8, -6, 0, 10, 18, 25, 41, 75, 72, 77, 35, + 31, 35, 30, 45, 42, 45, 26, 54, 66, 56, 73, 67, 116, 112, 71, 61, 53, 66, 77, 80, 84, 87, 127, 101, + 39, 53, 61, 75, 77, 91, 107, 111, 122, 76, 44, 52, 57, 58, 72, 69, 69, 74, 86, 66, 34, 32, 31, 52, + 55, 67, 73, 89, 52, 4, 8, 8, 19, 37, 61, 73, 70, 78, 0, 126, 124, 110, 126, 124, 105, 121, 117, + 102, 117, 116, 122, 95, 100, 95, 111, 114, 89, 80, 82, 85, 81, 72, 64, 67, 56, 69, 69, 69, 69, 67, + 77, 64, 61, 67, 64, 57, 65, 66, 62, 51, 66, 71, 75, 70, 72, 60, 37, 47, 35, 37, 41, 41, 48, 41, 41, + 59, 50, 40, 66, 50, -6, -6, 0, -12, -16, -25, -22, -28, -30, -30, -42, -36, -34, -17, 9, -71, -63, + -64, -74, -39, -35, -10, 0, -1, 14, 26, 37, 55, 65, -33, -36, -37, -30, -33, -30, -24, -29, -12, + -10, -3, -5, 20, 30, 44, 48, 49, 45, 22, 22, 21, 20, 28, 24, 34, 42, 39, 50, 70, 54, 71, 83, 32, + 49, 54, 85, 81, 77, 81, 80, 73, 74, 83, 71, 67, 61, 66, 66, 59, 59, -10, -13, -9, -5, 0, 3, 10, 27, + 57, 71, 24, 36, 42, 52, 57, 63, 65, 67, 82, 81, 76, 72, 78, 72, 68, 70, 76, 66, 62, 57, 61, 60, 54, + 58, -10, -13, -9, -5, 0, 3, 10, 27, 57, 80, 89, 94, 92, 39, 65, 84, 127, 73, 104, 91, 127, 80, 89, + 94, 92, 39, 65, 84, 127, 73, 104, 91, 127, 103, 91, 89, 92, 76, 87, 110, 105, 78, 112, 99, 127, + 127, 127, 127, 66, 78, 71, 72, 59, 55, 70, 75, 89, 119, 75, 20, 22, 127, 54, 61, 58, 60, 61, 67, + 84, 74, 65, 52, 57, 61, 69, 70, 55, 103, 91, 89, 92, 76, 87, 110, 105, 78, 112, 99, 127, 127, 127, + 127, 66, 78, 71, 72, 59, 55, 70, 75, 89, 119, 75, 20, 22, 127, 54, 61, 58, 60, 61, 67, 84, 74, 65, + 52, 57, 61, 69, 70, 55, 45, 28, 31, -11, -43, 15, 0, -22, -25, 0, -18, -12, -94, 0, -15, -4, 10, + -5, -29, -9, -34, -58, -63, -5, 7, -29, 1, 0, 36, -25, -30, -28, -28, -27, -18, -16, -14, -8, -6, + 0, 10, 18, 25, 41, 45, 28, 31, -11, -43, 15, 0, -22, -25, 0, -18, -12, -94, 0, -15, -4, 10, -5, + -29, -9, -34, -58, -63, -5, 7, -29, 1, 0, 36, -25, -30, -28, -28, -27, -18, -16, -14, -8, -6, 0, + 10, 18, 25, 41, 85, 81, 77, 81, 80, 73, 74, 83, 71, 67, 61, 66, 66, 59, 59, 81, 76, 72, 78, 72, 68, + 70, 76, 66, 62, 57, 61, 60, 54, 58, -10, -13, -9, -5, 0, 3, 10, 27, 57, -10, -13, -9, -5, 0, 3, 10, + 27, 57, 71, 24, 36, 42, 52, 57, 63, 65, 67, 82, 85, 81, 77, 81, 80, 73, 74, 83, 71, 67, 61, 66, 66, + 59, 59, 81, 76, 72, 78, 72, 68, 70, 76, 66, 62, 57, 61, 60, 54, 58, -10, -13, -9, -5, 0, 3, 10, 27, + 57, -10, -13, -9, -5, 0, 3, 10, 27, 57, 71, 24, 36, 42, 52, 57, 63, 65, 67, 82, 126, 124, 110, 126, + 124, 105, 121, 117, 102, 117, 116, 122, 95, 100, 95, 111, 114, 89, 80, 82, 85, 81, 72, 64, 67, 56, + 69, 69, 69, 69, 67, 77, 64, 61, 67, 64, 57, 65, 66, 62, 51, 66, 71, 75, 126, 124, 110, 126, 124, + 105, 121, 117, 102, 117, 116, 122, 95, 100, 95, 111, 114, 89, 80, 82, 85, 81, 72, 64, 67, 56, 69, + 69, 69, 69, 67, 77, 64, 61, 67, 64, 57, 65, 66, 62, 51, 66, 71, 75, -6, -6, 0, -12, -16, -25, -22, + -28, -30, -30, -42, -36, -34, -17, 9, -71, -63, -64, -74, -39, -35, -10, 0, -1, 14, 26, 37, 55, 65, + -33, -36, -37, -30, -33, -30, -24, -29, -12, -10, -3, -5, 20, 30, 44, -6, -6, 0, -12, -16, -25, + -22, -28, -30, -30, -42, -36, -34, -17, 9, -71, -63, -64, -74, -39, -35, -10, 0, -1, 14, 26, 37, + 55, 65, -33, -36, -37, -30, -33, -30, -24, -29, -12, -10, -3, -5, 20, 30, 44, 112, 71, 61, 53, 66, + 77, 80, 84, 87, 127, 101, 39, 53, 61, 75, 77, 91, 107, 111, 122, 76, 44, 52, 57, 58, 72, 69, 69, + 74, 86, 112, 71, 61, 53, 66, 77, 80, 84, 87, 127, 101, 39, 53, 61, 75, 77, 91, 107, 111, 122, 76, + 44, 52, 57, 58, 72, 69, 69, 74, 86, 73, 104, 91, 127, 73, 104, 91, 127, 73, 104, 91, 127}, + + {-15, 54, 74, -15, 54, 74, 127, 104, 53, 54, 51, 16, 0, 0, 51, 62, 99, 16, 85, 102, 57, 57, 73, 57, 40, + 10, 0, 0, 42, 97, 127, 117, 74, 85, 102, 57, 93, 88, 44, 55, 89, 103, 116, 57, 58, 84, 96, 63, 85, + 106, 63, 75, 90, 101, 55, 79, 75, 97, 50, 60, 41, 63, 63, 63, 83, 86, 97, 72, 41, 62, 34, 88, 127, + 127, 91, 95, 84, 86, 89, 91, 127, 76, 103, 90, 127, 80, 76, 84, 78, 55, 61, 83, 127, 79, 104, 91, + 127, 65, 79, 72, 92, 56, 68, 71, 98, 86, 88, 82, 72, 67, 72, 89, 69, 59, 66, 57, 71, 71, 58, 74, + 44, 69, 62, 51, 47, 42, 41, 53, 76, 78, 83, 52, 67, 90, 67, 72, 75, 80, 83, 64, 31, 64, 94, 75, 63, + 74, 35, 27, 91, 65, 69, 77, 66, 62, 68, 81, 30, 7, 23, 74, 66, 124, 37, -18, -34, 127, 39, 42, 34, + 29, 31, 37, 42, 40, 33, 43, 36, 47, 55, 58, 60, 44, 44, 42, 48, 56, 52, 37, 49, 58, 48, 45, 69, 33, + 63, -18, -25, -3, 10, 0, -14, -44, -24, 17, 25, 29, 33, 15, 20, 73, 34, 31, 44, 16, 36, 36, 28, 21, + 20, 12, 16, 42, 93, 56, 57, 38, 127, 115, 82, 62, 53, 59, 85, 89, 94, 92, 127, 100, 57, 67, 71, 77, + 85, 88, 104, 98, 127, 82, 48, 61, 66, 70, 75, 79, 83, 92, 108, 79, 69, 75, 58, 58, 78, 83, 81, 99, + 81, 38, 62, 58, 59, 73, 76, 86, 83, 87, 0, 127, 127, 120, 127, 114, 117, 118, 117, 113, 118, 120, + 124, 94, 102, 99, 106, 127, 92, 57, 86, 94, 91, 77, 71, 73, 64, 81, 64, 57, 67, 68, 67, 68, 77, 64, + 68, 78, 55, 59, 65, 54, 44, 60, 70, 76, 86, 70, 64, 70, 55, 56, 69, 65, 74, 54, 54, 76, 82, 77, 77, + 42, -13, -9, -12, -21, -30, -40, -41, -47, -32, -40, -51, -41, -39, -19, 11, -55, -46, -50, -67, + -20, -2, 15, 1, 1, 17, 38, 45, 54, 79, -16, -14, -17, 1, 15, 15, 25, 22, 16, 18, 28, 41, 28, 47, + 62, 31, 26, 24, 23, 16, 30, 29, 41, 42, 60, 52, 60, 78, 123, 53, 56, 61, 33, 50, 61, 78, 74, 72, + 72, 75, 71, 63, 70, 75, 72, 67, 53, 59, 52, 68, -2, -10, -4, -1, 7, 12, 23, 38, 64, 71, 37, 44, 49, + 56, 59, 63, 67, 68, 79, 78, 74, 72, 72, 75, 71, 63, 70, 75, 72, 67, 53, 59, 52, 68, -2, -10, -4, + -1, 7, 12, 23, 38, 64, 80, 76, 84, 78, 55, 61, 83, 127, 79, 104, 91, 127, 80, 76, 84, 78, 55, 61, + 83, 127, 79, 104, 91, 127, 86, 88, 82, 72, 67, 72, 89, 69, 59, 66, 57, 71, 71, 58, 74, 44, 69, 62, + 51, 47, 42, 41, 53, 76, 78, 83, 52, 67, 90, 67, 72, 75, 80, 83, 64, 31, 64, 94, 75, 63, 74, 35, 27, + 91, 86, 88, 82, 72, 67, 72, 89, 69, 59, 66, 57, 71, 71, 58, 74, 44, 69, 62, 51, 47, 42, 41, 53, 76, + 78, 83, 52, 67, 90, 67, 72, 75, 80, 83, 64, 31, 64, 94, 75, 63, 74, 35, 27, 91, 39, 42, 34, 29, 31, + 37, 42, 40, 33, 43, 36, 47, 55, 58, 60, 44, 44, 42, 48, 56, 52, 37, 49, 58, 48, 45, 69, 33, 63, + -18, -25, -3, 10, 0, -14, -44, -24, 17, 25, 29, 33, 15, 20, 73, 39, 42, 34, 29, 31, 37, 42, 40, 33, + 43, 36, 47, 55, 58, 60, 44, 44, 42, 48, 56, 52, 37, 49, 58, 48, 45, 69, 33, 63, -18, -25, -3, 10, + 0, -14, -44, -24, 17, 25, 29, 33, 15, 20, 73, 78, 74, 72, 72, 75, 71, 63, 70, 75, 72, 67, 53, 59, + 52, 68, 78, 74, 72, 72, 75, 71, 63, 70, 75, 72, 67, 53, 59, 52, 68, -2, -10, -4, -1, 7, 12, 23, 38, + 64, -2, -10, -4, -1, 7, 12, 23, 38, 64, 71, 37, 44, 49, 56, 59, 63, 67, 68, 79, 78, 74, 72, 72, 75, + 71, 63, 70, 75, 72, 67, 53, 59, 52, 68, 78, 74, 72, 72, 75, 71, 63, 70, 75, 72, 67, 53, 59, 52, 68, + -2, -10, -4, -1, 7, 12, 23, 38, 64, -2, -10, -4, -1, 7, 12, 23, 38, 64, 71, 37, 44, 49, 56, 59, 63, + 67, 68, 79, 127, 127, 120, 127, 114, 117, 118, 117, 113, 118, 120, 124, 94, 102, 99, 106, 127, 92, + 57, 86, 94, 91, 77, 71, 73, 64, 81, 64, 57, 67, 68, 67, 68, 77, 64, 68, 78, 55, 59, 65, 54, 44, 60, + 70, 127, 127, 120, 127, 114, 117, 118, 117, 113, 118, 120, 124, 94, 102, 99, 106, 127, 92, 57, 86, + 94, 91, 77, 71, 73, 64, 81, 64, 57, 67, 68, 67, 68, 77, 64, 68, 78, 55, 59, 65, 54, 44, 60, 70, + -13, -9, -12, -21, -30, -40, -41, -47, -32, -40, -51, -41, -39, -19, 11, -55, -46, -50, -67, -20, + -2, 15, 1, 1, 17, 38, 45, 54, 79, -16, -14, -17, 1, 15, 15, 25, 22, 16, 18, 28, 41, 28, 47, 62, + -13, -9, -12, -21, -30, -40, -41, -47, -32, -40, -51, -41, -39, -19, 11, -55, -46, -50, -67, -20, + -2, 15, 1, 1, 17, 38, 45, 54, 79, -16, -14, -17, 1, 15, 15, 25, 22, 16, 18, 28, 41, 28, 47, 62, + 115, 82, 62, 53, 59, 85, 89, 94, 92, 127, 100, 57, 67, 71, 77, 85, 88, 104, 98, 127, 82, 48, 61, + 66, 70, 75, 79, 83, 92, 108, 115, 82, 62, 53, 59, 85, 89, 94, 92, 127, 100, 57, 67, 71, 77, 85, 88, + 104, 98, 127, 82, 48, 61, 66, 70, 75, 79, 83, 92, 108, 79, 104, 91, 127, 79, 104, 91, 127, 79, 104, + 91, 127}}; +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/CAVLCReader.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/CAVLCReader.java new file mode 100644 index 0000000..3ac8f83 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/CAVLCReader.java @@ -0,0 +1,99 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Utils2; +import org.monte.media.impl.jcodec.common.io.BitReader; +import org.monte.media.impl.jcodec.common.tools.Debug; + +import static org.monte.media.impl.jcodec.common.tools.Debug.trace; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class CAVLCReader { + + private CAVLCReader() { + + } + + public static int readNBit(BitReader bits, int n, String message) { + int val = bits.readNBit(n); + + trace(message, val); + + return val; + } + + public static int readUE(BitReader bits) { + int cnt = 0; + while (bits.read1Bit() == 0 && cnt < 32) + cnt++; + + int res = 0; + if (cnt > 0) { + long val = bits.readNBit(cnt); + + res = (int) ((1 << cnt) - 1 + val); + } + + return res; + } + + public static int readUEtrace(BitReader bits, String message) { + int res = readUE(bits); + + trace(message, res); + + return res; + } + + public static int readSE(BitReader bits, String message) { + int val = readUE(bits); + + val = H264Utils2.golomb2Signed(val); + + trace(message, val); + + return val; + } + + public static boolean readBool(BitReader bits, String message) { + + boolean res = bits.read1Bit() == 0 ? false : true; + + trace(message, res ? 1 : 0); + + return res; + } + + public static int readU(BitReader bits, int i, String string) { + return (int) readNBit(bits, i, string); + } + + public static int readTE(BitReader bits, int max) { + if (max > 1) + return readUE(bits); + return ~bits.read1Bit() & 0x1; + } + + public static int readME(BitReader bits, String string) { + return readUEtrace(bits, string); + } + + public static int readZeroBitCount(BitReader bits, String message) { + int count = 0; + while (bits.read1Bit() == 0 && count < 32) + count++; + + if (Debug.debug) + trace(message, String.valueOf(count)); + + return count; + } + + public static boolean moreRBSPData(BitReader bits) { + return !(bits.remaining() < 32 && bits.checkNBit(1) == 1 && (bits.checkNBit(24) << 9) == 0); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/ChromaPredictionBuilder.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/ChromaPredictionBuilder.java new file mode 100644 index 0000000..e800109 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/ChromaPredictionBuilder.java @@ -0,0 +1,495 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.CHROMA_BLOCK_LUT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.CHROMA_POS_LUT; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Prediction builder for chroma samples + * + * @author The JCodec project + */ +public class ChromaPredictionBuilder { + + public static void predictWithMode(int[][] residual, int chromaMode, int mbX, boolean leftAvailable, + boolean topAvailable, byte[] leftRow, byte[] topLine, byte[] topLeft, byte[] pixOut) { + + switch (chromaMode) { + case 0: + predictDC(residual, mbX, leftAvailable, topAvailable, leftRow, topLine, pixOut); + break; + case 1: + predictHorizontal(residual, mbX, leftAvailable, leftRow, pixOut); + break; + case 2: + predictVertical(residual, mbX, topAvailable, topLine, pixOut); + break; + case 3: + predictPlane(residual, mbX, leftAvailable, topAvailable, leftRow, topLine, topLeft, pixOut); + break; + } + + } + + public static void buildPred(int chromaMode, int mbX, boolean leftAvailable, + boolean topAvailable, byte[] leftRow, byte[] topLine, byte topLeft, byte[][] pixOut) { + + switch (chromaMode) { + case 0: + buildPredDC(mbX, leftAvailable, topAvailable, leftRow, topLine, pixOut); + break; + case 1: + buildPredHorz(mbX, leftAvailable, leftRow, pixOut); + break; + case 2: + buildPredVert(mbX, topAvailable, topLine, pixOut); + break; + case 3: + buildPredPlane(mbX, leftAvailable, topAvailable, leftRow, topLine, topLeft, pixOut); + break; + } + + } + + public static int predSAD(int chromaMode, int mbX, boolean leftAvailable, boolean topAvailable, byte[] leftRow, + byte[] topLine, byte topLeft, byte[] pix) { + switch (chromaMode) { + default: + case 0: + return predDCSAD(mbX, leftAvailable, topAvailable, leftRow, topLine, pix); + case 1: + return predHorizontalSAD(mbX, leftAvailable, leftRow, pix); + case 2: + return predVerticalSAD(mbX, topAvailable, topLine, pix); + case 3: + return predPlaneSAD(mbX, leftAvailable, topAvailable, leftRow, topLine, topLeft, pix); + } + } + + public static boolean predAvb(int chromaMode, boolean leftAvailable, boolean topAvailable) { + switch (chromaMode) { + default: + case 0: + return true; + case 1: + return leftAvailable; + case 2: + return topAvailable; + case 3: + return leftAvailable && topAvailable; + } + } + + public static void predictDC(int[][] planeData, int mbX, boolean leftAvailable, boolean topAvailable, + byte[] leftRow, byte[] topLine, byte[] pixOut) { + predictDCInside(planeData, 0, 0, mbX, leftAvailable, topAvailable, leftRow, topLine, pixOut); + predictDCTopBorder(planeData, 1, 0, mbX, leftAvailable, topAvailable, leftRow, topLine, pixOut); + predictDCLeftBorder(planeData, 0, 1, mbX, leftAvailable, topAvailable, leftRow, topLine, pixOut); + predictDCInside(planeData, 1, 1, mbX, leftAvailable, topAvailable, leftRow, topLine, pixOut); + } + + public static int predDCSAD(int mbX, boolean leftAvailable, boolean topAvailable, byte[] leftRow, byte[] topLine, + byte[] pixOut) { + return predictDCInsideSAD(0, 0, mbX, leftAvailable, topAvailable, leftRow, topLine, pixOut) + + predictDCTopBorderSAD(1, 0, mbX, leftAvailable, topAvailable, leftRow, topLine, pixOut) + + predictDCLeftBorderSAD(0, 1, mbX, leftAvailable, topAvailable, leftRow, topLine, pixOut) + + predictDCInsideSAD(1, 1, mbX, leftAvailable, topAvailable, leftRow, topLine, pixOut); + } + + public static void buildPredDC(int mbX, boolean leftAvailable, boolean topAvailable, byte[] leftRow, byte[] topLine, + byte[][] pixOut) { + buildPredDCIns(0, 0, mbX, leftAvailable, topAvailable, leftRow, topLine, pixOut[0]); + buildPredDCTop(1, 0, mbX, leftAvailable, topAvailable, leftRow, topLine, pixOut[1]); + buildPredDCLft(0, 1, mbX, leftAvailable, topAvailable, leftRow, topLine, pixOut[2]); + buildPredDCIns(1, 1, mbX, leftAvailable, topAvailable, leftRow, topLine, pixOut[3]); + } + + public static void predictVertical(int[][] residual, int mbX, boolean topAvailable, byte[] topLine, byte[] pixOut) { + for (int off = 0, j = 0; j < 8; j++) { + for (int i = 0; i < 8; i++, off++) + pixOut[off] = (byte) clip( + residual[CHROMA_BLOCK_LUT[off]][CHROMA_POS_LUT[off]] + topLine[(mbX << 3) + i], -128, 127); + } + } + + public static int predVerticalSAD(int mbX, boolean topAvailable, byte[] topLine, byte[] pixOut) { + int sad = 0; + for (int off = 0, j = 0; j < 8; j++) { + for (int i = 0; i < 8; i++, off++) + sad += MathUtil.abs(pixOut[off] - topLine[(mbX << 3) + i]); + } + return sad; + } + + public static void buildPredVert(int mbX, boolean topAvailable, byte[] topLine, byte[][] pixOut) { + for (int off = 0, j = 0; j < 8; j++) { + for (int i = 0; i < 8; i++, off++) + pixOut[CHROMA_BLOCK_LUT[off]][CHROMA_POS_LUT[off]] = topLine[(mbX << 3) + i]; + } + } + + public static void predictHorizontal(int[][] residual, int mbX, boolean leftAvailable, byte[] leftRow, + byte[] pixOut) { + for (int off = 0, j = 0; j < 8; j++) { + for (int i = 0; i < 8; i++, off++) + pixOut[off] = (byte) clip(residual[CHROMA_BLOCK_LUT[off]][CHROMA_POS_LUT[off]] + leftRow[j], -128, 127); + } + } + + public static int predHorizontalSAD(int mbX, boolean leftAvailable, byte[] leftRow, byte[] pixOut) { + int sad = 0; + for (int off = 0, j = 0; j < 8; j++) { + for (int i = 0; i < 8; i++, off++) + sad += MathUtil.abs(pixOut[off] - leftRow[j]); + } + return sad; + } + + public static void buildPredHorz(int mbX, boolean leftAvailable, byte[] leftRow, byte[][] pixOut) { + for (int off = 0, j = 0; j < 8; j++) { + for (int i = 0; i < 8; i++, off++) + pixOut[CHROMA_BLOCK_LUT[off]][CHROMA_POS_LUT[off]] = leftRow[j]; + } + } + + public static void predictDCInside(int[][] residual, int blkX, int blkY, int mbX, boolean leftAvailable, + boolean topAvailable, byte[] leftRow, byte[] topLine, byte[] pixOut) { + + int s0, blkOffX = (blkX << 2) + (mbX << 3), blkOffY = blkY << 2; + + if (leftAvailable && topAvailable) { + s0 = 0; + for (int i = 0; i < 4; i++) + s0 += leftRow[i + blkOffY]; + for (int i = 0; i < 4; i++) + s0 += topLine[blkOffX + i]; + + s0 = (s0 + 4) >> 3; + } else if (leftAvailable) { + s0 = 0; + for (int i = 0; i < 4; i++) + s0 += leftRow[blkOffY + i]; + s0 = (s0 + 2) >> 2; + } else if (topAvailable) { + s0 = 0; + for (int i = 0; i < 4; i++) + s0 += topLine[blkOffX + i]; + s0 = (s0 + 2) >> 2; + } else { + s0 = 0; + } + for (int off = (blkY << 5) + (blkX << 2), j = 0; j < 4; j++, off += 8) { + pixOut[off] = (byte) clip(residual[CHROMA_BLOCK_LUT[off]][CHROMA_POS_LUT[off]] + s0, -128, 127); + pixOut[off + 1] = (byte) clip(residual[CHROMA_BLOCK_LUT[off + 1]][CHROMA_POS_LUT[off + 1]] + s0, -128, 127); + pixOut[off + 2] = (byte) clip(residual[CHROMA_BLOCK_LUT[off + 2]][CHROMA_POS_LUT[off + 2]] + s0, -128, 127); + pixOut[off + 3] = (byte) clip(residual[CHROMA_BLOCK_LUT[off + 3]][CHROMA_POS_LUT[off + 3]] + s0, -128, 127); + } + } + + public static int predictDCInsideSAD(int blkX, int blkY, int mbX, boolean leftAvailable, boolean topAvailable, + byte[] leftRow, byte[] topLine, byte[] pixOut) { + + int s0, blkOffX = (blkX << 2) + (mbX << 3), blkOffY = blkY << 2; + + if (leftAvailable && topAvailable) { + s0 = 0; + for (int i = 0; i < 4; i++) + s0 += leftRow[i + blkOffY]; + for (int i = 0; i < 4; i++) + s0 += topLine[blkOffX + i]; + + s0 = (s0 + 4) >> 3; + } else if (leftAvailable) { + s0 = 0; + for (int i = 0; i < 4; i++) + s0 += leftRow[blkOffY + i]; + s0 = (s0 + 2) >> 2; + } else if (topAvailable) { + s0 = 0; + for (int i = 0; i < 4; i++) + s0 += topLine[blkOffX + i]; + s0 = (s0 + 2) >> 2; + } else { + s0 = 0; + } + int sad = 0; + for (int off = (blkY << 5) + (blkX << 2), j = 0; j < 4; j++, off += 8) { + sad += MathUtil.abs(pixOut[off + 0] - s0); + sad += MathUtil.abs(pixOut[off + 1] - s0); + sad += MathUtil.abs(pixOut[off + 2] - s0); + sad += MathUtil.abs(pixOut[off + 3] - s0); + } + return sad; + } + + public static void buildPredDCIns(int blkX, int blkY, int mbX, boolean leftAvailable, boolean topAvailable, + byte[] leftRow, byte[] topLine, byte[] pixOut) { + + int s0, blkOffX = (blkX << 2) + (mbX << 3), blkOffY = blkY << 2; + + if (leftAvailable && topAvailable) { + s0 = 0; + for (int i = 0; i < 4; i++) + s0 += leftRow[i + blkOffY]; + for (int i = 0; i < 4; i++) + s0 += topLine[blkOffX + i]; + + s0 = (s0 + 4) >> 3; + } else if (leftAvailable) { + s0 = 0; + for (int i = 0; i < 4; i++) + s0 += leftRow[blkOffY + i]; + s0 = (s0 + 2) >> 2; + } else if (topAvailable) { + s0 = 0; + for (int i = 0; i < 4; i++) + s0 += topLine[blkOffX + i]; + s0 = (s0 + 2) >> 2; + } else { + s0 = 0; + } + for (int j = 0; j < 16; j++) { + pixOut[j] = (byte) s0; + } + } + + public static void predictDCTopBorder(int[][] residual, int blkX, int blkY, int mbX, boolean leftAvailable, + boolean topAvailable, byte[] leftRow, byte[] topLine, byte[] pixOut) { + + int s1, blkOffX = (blkX << 2) + (mbX << 3), blkOffY = blkY << 2; + if (topAvailable) { + s1 = 0; + for (int i = 0; i < 4; i++) + s1 += topLine[blkOffX + i]; + + s1 = (s1 + 2) >> 2; + } else if (leftAvailable) { + s1 = 0; + for (int i = 0; i < 4; i++) + s1 += leftRow[blkOffY + i]; + s1 = (s1 + 2) >> 2; + } else { + s1 = 0; + } + for (int off = (blkY << 5) + (blkX << 2), j = 0; j < 4; j++, off += 8) { + pixOut[off] = (byte) clip(residual[CHROMA_BLOCK_LUT[off]][CHROMA_POS_LUT[off]] + s1, -128, 127); + pixOut[off + 1] = (byte) clip(residual[CHROMA_BLOCK_LUT[off + 1]][CHROMA_POS_LUT[off + 1]] + s1, -128, 127); + pixOut[off + 2] = (byte) clip(residual[CHROMA_BLOCK_LUT[off + 2]][CHROMA_POS_LUT[off + 2]] + s1, -128, 127); + pixOut[off + 3] = (byte) clip(residual[CHROMA_BLOCK_LUT[off + 3]][CHROMA_POS_LUT[off + 3]] + s1, -128, 127); + } + } + + public static int predictDCTopBorderSAD(int blkX, int blkY, int mbX, boolean leftAvailable, boolean topAvailable, + byte[] leftRow, byte[] topLine, byte[] pixOut) { + + int s1, blkOffX = (blkX << 2) + (mbX << 3), blkOffY = blkY << 2; + if (topAvailable) { + s1 = 0; + for (int i = 0; i < 4; i++) + s1 += topLine[blkOffX + i]; + + s1 = (s1 + 2) >> 2; + } else if (leftAvailable) { + s1 = 0; + for (int i = 0; i < 4; i++) + s1 += leftRow[blkOffY + i]; + s1 = (s1 + 2) >> 2; + } else { + s1 = 0; + } + int sad = 0; + for (int off = (blkY << 5) + (blkX << 2), j = 0; j < 4; j++, off += 8) { + sad += MathUtil.abs(pixOut[off + 0] - s1); + sad += MathUtil.abs(pixOut[off + 1] - s1); + sad += MathUtil.abs(pixOut[off + 2] - s1); + sad += MathUtil.abs(pixOut[off + 3] - s1); + } + return sad; + } + + public static void buildPredDCTop(int blkX, int blkY, int mbX, boolean leftAvailable, boolean topAvailable, + byte[] leftRow, byte[] topLine, byte[] pixOut) { + + int s1, blkOffX = (blkX << 2) + (mbX << 3), blkOffY = blkY << 2; + if (topAvailable) { + s1 = 0; + for (int i = 0; i < 4; i++) + s1 += topLine[blkOffX + i]; + + s1 = (s1 + 2) >> 2; + } else if (leftAvailable) { + s1 = 0; + for (int i = 0; i < 4; i++) + s1 += leftRow[blkOffY + i]; + s1 = (s1 + 2) >> 2; + } else { + s1 = 0; + } + for (int j = 0; j < 16; j++) { + pixOut[j] = (byte) s1; + } + } + + public static void predictDCLeftBorder(int[][] residual, int blkX, int blkY, int mbX, boolean leftAvailable, + boolean topAvailable, byte[] leftRow, byte[] topLine, byte[] pixOut) { + + int s2, blkOffX = (blkX << 2) + (mbX << 3), blkOffY = blkY << 2; + if (leftAvailable) { + s2 = 0; + for (int i = 0; i < 4; i++) + s2 += leftRow[blkOffY + i]; + s2 = (s2 + 2) >> 2; + } else if (topAvailable) { + s2 = 0; + for (int i = 0; i < 4; i++) + s2 += topLine[blkOffX + i]; + s2 = (s2 + 2) >> 2; + } else { + s2 = 0; + } + for (int off = (blkY << 5) + (blkX << 2), j = 0; j < 4; j++, off += 8) { + pixOut[off] = (byte) clip(residual[CHROMA_BLOCK_LUT[off]][CHROMA_POS_LUT[off]] + s2, -128, 127); + pixOut[off + 1] = (byte) clip(residual[CHROMA_BLOCK_LUT[off + 1]][CHROMA_POS_LUT[off + 1]] + s2, -128, 127); + pixOut[off + 2] = (byte) clip(residual[CHROMA_BLOCK_LUT[off + 2]][CHROMA_POS_LUT[off + 2]] + s2, -128, 127); + pixOut[off + 3] = (byte) clip(residual[CHROMA_BLOCK_LUT[off + 3]][CHROMA_POS_LUT[off + 3]] + s2, -128, 127); + } + } + + public static int predictDCLeftBorderSAD(int blkX, int blkY, int mbX, boolean leftAvailable, boolean topAvailable, + byte[] leftRow, byte[] topLine, byte[] pixOut) { + + int s2, blkOffX = (blkX << 2) + (mbX << 3), blkOffY = blkY << 2; + if (leftAvailable) { + s2 = 0; + for (int i = 0; i < 4; i++) + s2 += leftRow[blkOffY + i]; + s2 = (s2 + 2) >> 2; + } else if (topAvailable) { + s2 = 0; + for (int i = 0; i < 4; i++) + s2 += topLine[blkOffX + i]; + s2 = (s2 + 2) >> 2; + } else { + s2 = 0; + } + int sad = 0; + for (int off = (blkY << 5) + (blkX << 2), j = 0; j < 4; j++, off += 8) { + sad += MathUtil.abs(pixOut[off] - s2); + sad += MathUtil.abs(pixOut[off + 1] - s2); + sad += MathUtil.abs(pixOut[off + 2] - s2); + sad += MathUtil.abs(pixOut[off + 3] - s2); + } + return sad; + } + + public static void buildPredDCLft(int blkX, int blkY, int mbX, boolean leftAvailable, boolean topAvailable, + byte[] leftRow, byte[] topLine, byte[] pixOut) { + + int s2, blkOffX = (blkX << 2) + (mbX << 3), blkOffY = blkY << 2; + if (leftAvailable) { + s2 = 0; + for (int i = 0; i < 4; i++) + s2 += leftRow[blkOffY + i]; + s2 = (s2 + 2) >> 2; + } else if (topAvailable) { + s2 = 0; + for (int i = 0; i < 4; i++) + s2 += topLine[blkOffX + i]; + s2 = (s2 + 2) >> 2; + } else { + s2 = 0; + } + for (int j = 0; j < 16; j++) { + pixOut[j] = (byte) s2; + } + } + + public static void predictPlane(int[][] residual, int mbX, boolean leftAvailable, boolean topAvailable, + byte[] leftRow, byte[] topLine, byte[] topLeft, byte[] pixOut) { + int H = 0, blkOffX = (mbX << 3); + + for (int i = 0; i < 3; i++) { + H += (i + 1) * (topLine[blkOffX + 4 + i] - topLine[blkOffX + 2 - i]); + } + H += 4 * (topLine[blkOffX + 7] - topLeft[0]); + + int V = 0; + for (int j = 0; j < 3; j++) { + V += (j + 1) * (leftRow[4 + j] - leftRow[2 - j]); + } + V += 4 * (leftRow[7] - topLeft[0]); + + int c = (34 * V + 32) >> 6; + int b = (34 * H + 32) >> 6; + int a = 16 * (leftRow[7] + topLine[blkOffX + 7]); + + for (int off = 0, j = 0; j < 8; j++) { + for (int i = 0; i < 8; i++, off++) { + int val = (a + b * (i - 3) + c * (j - 3) + 16) >> 5; + pixOut[off] = (byte) clip(residual[CHROMA_BLOCK_LUT[off]][CHROMA_POS_LUT[off]] + clip(val, -128, 127), + -128, 127); + } + } + } + + public static int predPlaneSAD(int mbX, boolean leftAvailable, boolean topAvailable, byte[] leftRow, byte[] topLine, + byte topLeft, byte[] pixOut) { + int sad = 0; + int H = 0, blkOffX = (mbX << 3); + + for (int i = 0; i < 3; i++) { + H += (i + 1) * (topLine[blkOffX + 4 + i] - topLine[blkOffX + 2 - i]); + } + H += 4 * (topLine[blkOffX + 7] - topLeft); + + int V = 0; + for (int j = 0; j < 3; j++) { + V += (j + 1) * (leftRow[4 + j] - leftRow[2 - j]); + } + V += 4 * (leftRow[7] - topLeft); + + int c = (34 * V + 32) >> 6; + int b = (34 * H + 32) >> 6; + int a = 16 * (leftRow[7] + topLine[blkOffX + 7]); + + for (int off = 0, j = 0; j < 8; j++) { + for (int i = 0; i < 8; i++, off++) { + int val = (a + b * (i - 3) + c * (j - 3) + 16) >> 5; + sad += MathUtil.abs(pixOut[off] - clip(val, -128, 127)); + } + } + return sad; + } + + public static void buildPredPlane(int mbX, boolean leftAvailable, boolean topAvailable, byte[] leftRow, byte[] topLine, + byte topLeft, byte[][] pixOut) { + int H = 0, blkOffX = (mbX << 3); + + for (int i = 0; i < 3; i++) { + H += (i + 1) * (topLine[blkOffX + 4 + i] - topLine[blkOffX + 2 - i]); + } + H += 4 * (topLine[blkOffX + 7] - topLeft); + + int V = 0; + for (int j = 0; j < 3; j++) { + V += (j + 1) * (leftRow[4 + j] - leftRow[2 - j]); + } + V += 4 * (leftRow[7] - topLeft); + + int c = (34 * V + 32) >> 6; + int b = (34 * H + 32) >> 6; + int a = 16 * (leftRow[7] + topLine[blkOffX + 7]); + + for (int off = 0, j = 0; j < 8; j++) { + for (int i = 0; i < 8; i++, off++) { + int val = (a + b * (i - 3) + c * (j - 3) + 16) >> 5; + pixOut[CHROMA_BLOCK_LUT[off]][CHROMA_POS_LUT[off]] = (byte) clip(val, -128, 127); + } + } + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/CoeffTransformer.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/CoeffTransformer.java new file mode 100644 index 0000000..0d6acc3 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/CoeffTransformer.java @@ -0,0 +1,508 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.common.ArrayUtil; + +import java.util.Arrays; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Integer DCT 4x4 base implementation + * + * @author The JCodec project + */ +public class CoeffTransformer { + + public static int[] zigzag4x4 = {0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15}; + public static int[] invZigzag4x4 = new int[16]; + + static int[][] dequantCoef = { + {10, 13, 10, 13, 13, 16, 13, 16, 10, 13, 10, 13, 13, 16, 13, 16}, + {11, 14, 11, 14, 14, 18, 14, 18, 11, 14, 11, 14, 14, 18, 14, 18}, + {13, 16, 13, 16, 16, 20, 16, 20, 13, 16, 13, 16, 16, 20, 16, 20}, + {14, 18, 14, 18, 18, 23, 18, 23, 14, 18, 14, 18, 18, 23, 18, 23}, + {16, 20, 16, 20, 20, 25, 20, 25, 16, 20, 16, 20, 20, 25, 20, 25}, + {18, 23, 18, 23, 23, 29, 23, 29, 18, 23, 18, 23, 23, 29, 23, 29} + }; + + static int[][] dequantCoef8x8 = new int[6][64]; + + static int[][] initDequantCoeff8x8 = {{20, 18, 32, 19, 25, 24}, {22, 19, 35, 21, 28, 26}, + {26, 23, 42, 24, 33, 31}, {28, 25, 45, 26, 35, 33}, {32, 28, 51, 30, 40, 38}, + {36, 32, 58, 34, 46, 43}}; + + public static int[] zigzag8x8 = new int[]{0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, + 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, + 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63}; + public static int[] invZigzag8x8 = new int[64]; + + static { + for (int i = 0; i < 16; i++) + invZigzag4x4[zigzag4x4[i]] = i; + for (int i = 0; i < 64; i++) + invZigzag8x8[zigzag8x8[i]] = i; + } + + private static final int quantCoeff[][] = { + {13107, 8066, 13107, 8066, 8066, 5243, 8066, 5243, 13107, 8066, 13107, 8066, 8066, 5243, 8066, 5243}, + {11916, 7490, 11916, 7490, 7490, 4660, 7490, 4660, 11916, 7490, 11916, 7490, 7490, 4660, 7490, 4660}, + {10082, 6554, 10082, 6554, 6554, 4194, 6554, 4194, 10082, 6554, 10082, 6554, 6554, 4194, 6554, 4194}, + {9362, 5825, 9362, 5825, 5825, 3647, 5825, 3647, 9362, 5825, 9362, 5825, 5825, 3647, 5825, 3647}, + {8192, 5243, 8192, 5243, 5243, 3355, 5243, 3355, 8192, 5243, 8192, 5243, 5243, 3355, 5243, 3355}, + {7282, 4559, 7282, 4559, 4559, 2893, 4559, 2893, 7282, 4559, 7282, 4559, 4559, 2893, 4559, 2893}}; + + static { + for (int g = 0; g < 6; g++) { + Arrays.fill(dequantCoef8x8[g], initDequantCoeff8x8[g][5]); + for (int i = 0; i < 8; i += 4) + for (int j = 0; j < 8; j += 4) + dequantCoef8x8[g][(i << 3) + j] = initDequantCoeff8x8[g][0]; + for (int i = 1; i < 8; i += 2) + for (int j = 1; j < 8; j += 2) + dequantCoef8x8[g][(i << 3) + j] = initDequantCoeff8x8[g][1]; + for (int i = 2; i < 8; i += 4) + for (int j = 2; j < 8; j += 4) + dequantCoef8x8[g][(i << 3) + j] = initDequantCoeff8x8[g][2]; + for (int i = 0; i < 8; i += 4) + for (int j = 1; j < 8; j += 2) + dequantCoef8x8[g][(i << 3) + j] = initDequantCoeff8x8[g][3]; + for (int i = 1; i < 8; i += 2) + for (int j = 0; j < 8; j += 4) + dequantCoef8x8[g][(i << 3) + j] = initDequantCoeff8x8[g][3]; + for (int i = 0; i < 8; i += 4) + for (int j = 2; j < 8; j += 4) + dequantCoef8x8[g][(i << 3) + j] = initDequantCoeff8x8[g][4]; + for (int i = 2; i < 8; i += 4) + for (int j = 0; j < 8; j += 4) + dequantCoef8x8[g][(i << 3) + j] = initDequantCoeff8x8[g][4]; + } + } + + /** + * Inverce integer DCT transform for 4x4 block + * + * @param block + * @return + */ + public final static void idct4x4(int[] block) { + _idct4x4(block, block); + } + + public static final void _idct4x4(int[] block, int[] out) { + // Horisontal + for (int i = 0; i < 16; i += 4) { + int e0 = block[i] + block[i + 2]; + int e1 = block[i] - block[i + 2]; + int e2 = (block[i + 1] >> 1) - block[i + 3]; + int e3 = block[i + 1] + (block[i + 3] >> 1); + + out[i] = e0 + e3; + out[i + 1] = e1 + e2; + out[i + 2] = e1 - e2; + out[i + 3] = e0 - e3; + } + + // Vertical + for (int i = 0; i < 4; i++) { + int g0 = out[i] + out[i + 8]; + int g1 = out[i] - out[i + 8]; + int g2 = (out[i + 4] >> 1) - out[i + 12]; + int g3 = out[i + 4] + (out[i + 12] >> 1); + out[i] = g0 + g3; + out[i + 4] = g1 + g2; + out[i + 8] = g1 - g2; + out[i + 12] = g0 - g3; + } + + // scale down + for (int i = 0; i < 16; i++) { + out[i] = (out[i] + 32) >> 6; + } + } + + public static void fdct4x4(int[] block) { + // Horizontal + for (int i = 0; i < 16; i += 4) { + int t0 = block[i] + block[i + 3]; + int t1 = block[i + 1] + block[i + 2]; + int t2 = block[i + 1] - block[i + 2]; + int t3 = block[i] - block[i + 3]; + + block[i] = t0 + t1; + block[i + 1] = (t3 << 1) + t2; + block[i + 2] = t0 - t1; + block[i + 3] = t3 - (t2 << 1); + } + + // Vertical + for (int i = 0; i < 4; i++) { + int t0 = block[i] + block[i + 12]; + int t1 = block[i + 4] + block[i + 8]; + int t2 = block[i + 4] - block[i + 8]; + int t3 = block[i] - block[i + 12]; + + block[i] = t0 + t1; + block[i + 4] = t2 + (t3 << 1); + block[i + 8] = t0 - t1; + block[i + 12] = t3 - (t2 << 1); + } + } + + /** + * Inverse Hadamard transform + * + * @param scaled + */ + public static void invDC4x4(int[] scaled) { + // Horisontal + for (int i = 0; i < 16; i += 4) { + int e0 = scaled[i] + scaled[i + 2]; + int e1 = scaled[i] - scaled[i + 2]; + int e2 = scaled[i + 1] - scaled[i + 3]; + int e3 = scaled[i + 1] + scaled[i + 3]; + + scaled[i] = e0 + e3; + scaled[i + 1] = e1 + e2; + scaled[i + 2] = e1 - e2; + scaled[i + 3] = e0 - e3; + } + + // Vertical + for (int i = 0; i < 4; i++) { + int g0 = scaled[i] + scaled[i + 8]; + int g1 = scaled[i] - scaled[i + 8]; + int g2 = scaled[i + 4] - scaled[i + 12]; + int g3 = scaled[i + 4] + scaled[i + 12]; + scaled[i] = g0 + g3; + scaled[i + 4] = g1 + g2; + scaled[i + 8] = g1 - g2; + scaled[i + 12] = g0 - g3; + } + } + + /** + * Forward Hadamard transform + * + * @param scaled + */ + public static void fvdDC4x4(int[] scaled) { + // Horizontal + for (int i = 0; i < 16; i += 4) { + int t0 = scaled[i] + scaled[i + 3]; + int t1 = scaled[i + 1] + scaled[i + 2]; + int t2 = scaled[i + 1] - scaled[i + 2]; + int t3 = scaled[i] - scaled[i + 3]; + + scaled[i] = t0 + t1; + scaled[i + 1] = t3 + t2; + scaled[i + 2] = t0 - t1; + scaled[i + 3] = t3 - t2; + } + + // Vertical + for (int i = 0; i < 4; i++) { + int t0 = scaled[i] + scaled[i + 12]; + int t1 = scaled[i + 4] + scaled[i + 8]; + int t2 = scaled[i + 4] - scaled[i + 8]; + int t3 = scaled[i] - scaled[i + 12]; + + scaled[i] = (t0 + t1) >> 1; + scaled[i + 4] = (t2 + t3) >> 1; + scaled[i + 8] = (t0 - t1) >> 1; + scaled[i + 12] = (t3 - t2) >> 1; + } + } + + public static void dequantizeAC(int[] coeffs, int qp, int[] scalingList) { + int group = qp % 6; + + if (scalingList == null) { + int qbits = qp / 6; + for (int i = 0; i < 16; i++) + coeffs[i] = (coeffs[i] * dequantCoef[group][i]) << qbits; + } else { + if (qp >= 24) { + int qbits = qp / 6 - 4; + for (int i = 0; i < 16; i++) + coeffs[i] = (coeffs[i] * dequantCoef[group][i] * scalingList[invZigzag4x4[i]]) << qbits; + } else { + int qbits = 4 - qp / 6; + int addition = 1 << (3 - qp / 6); + for (int i = 0; i < 16; i++) + coeffs[i] = (coeffs[i] * scalingList[invZigzag4x4[i]] * dequantCoef[group][i] + addition) >> qbits; + } + } + } + + public static void quantizeAC(int[] coeffs, int qp) { + int level = qp / 6; + int offset = qp % 6; + + int addition = 682 << (qp / 6 + 4); + int qbits = 15 + level; + + if (qp < 10) { + for (int i = 0; i < 16; i++) { + int sign = (coeffs[i] >> 31); + coeffs[i] = (Math.min((((coeffs[i] ^ sign) - sign) * quantCoeff[offset][i] + addition) >> qbits, 2063) ^ sign) + - sign; + } + } else { + for (int i = 0; i < 16; i++) { + int sign = (coeffs[i] >> 31); + coeffs[i] = (((((coeffs[i] ^ sign) - sign) * quantCoeff[offset][i] + addition) >> qbits) ^ sign) - sign; + } + } + } + + public static void dequantizeDC4x4(int[] coeffs, int qp, int[] scalingList) { + int group = qp % 6; + + if (qp >= 36) { + int qbits = qp / 6 - 6; + if (scalingList == null) { + for (int i = 0; i < 16; i++) + coeffs[i] = (coeffs[i] * (dequantCoef[group][0] << 4)) << qbits; + } else { + for (int i = 0; i < 16; i++) + coeffs[i] = (coeffs[i] * dequantCoef[group][0] * scalingList[0]) << qbits; + } + } else { + int qbits = 6 - qp / 6; + int addition = 1 << (5 - qp / 6); + if (scalingList == null) { + for (int i = 0; i < 16; i++) + coeffs[i] = (coeffs[i] * (dequantCoef[group][0] << 4) + addition) >> qbits; + } else { + for (int i = 0; i < 16; i++) + coeffs[i] = (coeffs[i] * dequantCoef[group][0] * scalingList[0] + addition) >> qbits; + } + } + } + + public static void quantizeDC4x4(int[] coeffs, int qp) { + + int level = qp / 6; + int offset = qp % 6; + + int addition = 682 << (qp / 6 + 5); + int qbits = 16 + level; + + if (qp < 10) { + for (int i = 0; i < 16; i++) { + int sign = (coeffs[i] >> 31); + coeffs[i] = (Math.min((((coeffs[i] ^ sign) - sign) * quantCoeff[offset][0] + addition) >> qbits, 2063) ^ sign) + - sign; + } + } else { + for (int i = 0; i < 16; i++) { + int sign = (coeffs[i] >> 31); + coeffs[i] = (((((coeffs[i] ^ sign) - sign) * quantCoeff[offset][0] + addition) >> qbits) ^ sign) - sign; + } + } + } + + /** + * Inverse Hadamard 2x2 + * + * @param block + */ + public static void invDC2x2(int[] block) { + int t0, t1, t2, t3; + + t0 = block[0] + block[1]; + t1 = block[0] - block[1]; + t2 = block[2] + block[3]; + t3 = block[2] - block[3]; + + block[0] = (t0 + t2); + block[1] = (t1 + t3); + block[2] = (t0 - t2); + block[3] = (t1 - t3); + } + + /** + * Forward Hadamard 2x2 + * + * @param dc2 + */ + public static void fvdDC2x2(int[] block) { + invDC2x2(block); + } + + public static void dequantizeDC2x2(int[] transformed, int qp, int[] scalingList) { + + int group = qp % 6; + + if (scalingList == null) { + int shift = qp / 6; + + for (int i = 0; i < 4; i++) { + transformed[i] = ((transformed[i] * dequantCoef[group][0]) << shift) >> 1; + } + } else { + if (qp >= 24) { + int qbits = qp / 6 - 4; + for (int i = 0; i < 4; i++) { + transformed[i] = ((transformed[i] * dequantCoef[group][0] * scalingList[0]) << qbits) >> 1; + } + } else { + int qbits = 4 - qp / 6; + int addition = 1 << (3 - qp / 6); + for (int i = 0; i < 4; i++) { + transformed[i] = ((transformed[i] * dequantCoef[group][0] * scalingList[0] + addition) >> qbits) >> 1; + } + } + } + } + + public static void quantizeDC2x2(int[] coeffs, int qp) { + + int level = qp / 6; + int offset = qp % 6; + + int addition = 682 << (qp / 6 + 5); + int qbits = 16 + level; + + if (qp < 4) { + for (int i = 0; i < 4; i++) { + int sign = (coeffs[i] >> 31); + coeffs[i] = (Math.min((((coeffs[i] ^ sign) - sign) * quantCoeff[offset][0] + addition) >> qbits, 2063) ^ sign) + - sign; + } + } else { + for (int i = 0; i < 4; i++) { + int sign = (coeffs[i] >> 31); + coeffs[i] = (((((coeffs[i] ^ sign) - sign) * quantCoeff[offset][0] + addition) >> qbits) ^ sign) - sign; + } + } + } + + public static void reorderDC4x4(int[] dc) { + ArrayUtil.swap(dc, 2, 4); + ArrayUtil.swap(dc, 3, 5); + ArrayUtil.swap(dc, 10, 12); + ArrayUtil.swap(dc, 11, 13); + } + + public static void fvdDC4x2(int[] dc) { + + } + + public static void quantizeDC4x2(int[] dc, int qp) { + + } + + public static void invDC4x2(int[] dc) { + // TODO Auto-generated method stub + + } + + public static void dequantizeDC4x2(int[] dc, int qp) { + // TODO Auto-generated method stub + + } + + /** + * Coefficients are <<4 on exit + * + * @param coeffs + * @param qp + */ + public static void dequantizeAC8x8(int[] coeffs, int qp, int[] scalingList) { + int group = qp % 6; + + if (qp >= 36) { + int qbits = qp / 6 - 6; + if (scalingList == null) { + for (int i = 0; i < 64; i++) + coeffs[i] = (coeffs[i] * (dequantCoef8x8[group][i]) << 4) << qbits; + } else { + for (int i = 0; i < 64; i++) + coeffs[i] = (coeffs[i] * dequantCoef8x8[group][i] * scalingList[invZigzag8x8[i]]) << qbits; + } + } else { + int qbits = 6 - qp / 6; + int addition = 1 << (5 - qp / 6); + if (scalingList == null) { + for (int i = 0; i < 64; i++) + coeffs[i] = (coeffs[i] * (dequantCoef8x8[group][i] << 4) + addition) >> qbits; + } else { + for (int i = 0; i < 64; i++) + coeffs[i] = (coeffs[i] * dequantCoef8x8[group][i] * scalingList[invZigzag8x8[i]] + addition) >> qbits; + } + } + } + + public static void idct8x8(int[] ac) { + int off = 0; + + // Horizontal + for (int row = 0; row < 8; row++) { + int e0 = ac[off] + ac[off + 4]; + int e1 = -ac[off + 3] + ac[off + 5] - ac[off + 7] - (ac[off + 7] >> 1); + int e2 = ac[off] - ac[off + 4]; + int e3 = ac[off + 1] + ac[off + 7] - ac[off + 3] - (ac[off + 3] >> 1); + int e4 = (ac[off + 2] >> 1) - ac[off + 6]; + int e5 = -ac[off + 1] + ac[off + 7] + ac[off + 5] + (ac[off + 5] >> 1); + int e6 = ac[off + 2] + (ac[off + 6] >> 1); + int e7 = ac[off + 3] + ac[off + 5] + ac[off + 1] + (ac[off + 1] >> 1); + + int f0 = e0 + e6; + int f1 = e1 + (e7 >> 2); + int f2 = e2 + e4; + int f3 = e3 + (e5 >> 2); + int f4 = e2 - e4; + int f5 = (e3 >> 2) - e5; + int f6 = e0 - e6; + int f7 = e7 - (e1 >> 2); + + ac[off] = f0 + f7; + ac[off + 1] = f2 + f5; + ac[off + 2] = f4 + f3; + ac[off + 3] = f6 + f1; + ac[off + 4] = f6 - f1; + ac[off + 5] = f4 - f3; + ac[off + 6] = f2 - f5; + ac[off + 7] = f0 - f7; + + off += 8; + } + + // Vertical + for (int col = 0; col < 8; col++) { + int e0 = ac[col] + ac[col + 32]; + int e1 = -ac[col + 24] + ac[col + 40] - ac[col + 56] - (ac[col + 56] >> 1); + int e2 = ac[col] - ac[col + 32]; + int e3 = ac[col + 8] + ac[col + 56] - ac[col + 24] - (ac[col + 24] >> 1); + int e4 = (ac[col + 16] >> 1) - ac[col + 48]; + int e5 = -ac[col + 8] + ac[col + 56] + ac[col + 40] + (ac[col + 40] >> 1); + int e6 = ac[col + 16] + (ac[col + 48] >> 1); + int e7 = ac[col + 24] + ac[col + 40] + ac[col + 8] + (ac[col + 8] >> 1); + + int f0 = e0 + e6; + int f1 = e1 + (e7 >> 2); + int f2 = e2 + e4; + int f3 = e3 + (e5 >> 2); + int f4 = e2 - e4; + int f5 = (e3 >> 2) - e5; + int f6 = e0 - e6; + int f7 = e7 - (e1 >> 2); + + ac[col] = f0 + f7; + ac[col + 8] = f2 + f5; + ac[col + 16] = f4 + f3; + ac[col + 24] = f6 + f1; + ac[col + 32] = f6 - f1; + ac[col + 40] = f4 - f3; + ac[col + 48] = f2 - f5; + ac[col + 56] = f0 - f7; + } + + // scale down + for (int i = 0; i < 64; i++) { + ac[i] = (ac[i] + 32) >> 6; + } + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/DeblockerInput.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/DeblockerInput.java new file mode 100644 index 0000000..b1df86a --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/DeblockerInput.java @@ -0,0 +1,40 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Utils; +import org.monte.media.impl.jcodec.codecs.h264.io.model.Frame; +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; + +import static org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet.getPicHeightInMbs; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Contains an input for deblocking filter + * + * @author The JCodec project + */ +public class DeblockerInput { + public int[][] nCoeff; + public H264Utils.MvList2D mvs; + public MBType[] mbTypes; + public int[][] mbQps; + public boolean[] tr8x8Used; + public Frame[][][] refsUsed; + public SliceHeader[] shs; + + public DeblockerInput(SeqParameterSet activeSps) { + int picWidthInMbs = activeSps.picWidthInMbsMinus1 + 1; + int picHeightInMbs = getPicHeightInMbs(activeSps); + + nCoeff = new int[picHeightInMbs << 2][picWidthInMbs << 2]; + mvs = new H264Utils.MvList2D(picWidthInMbs << 2, picHeightInMbs << 2); + mbTypes = new MBType[picHeightInMbs * picWidthInMbs]; + tr8x8Used = new boolean[picHeightInMbs * picWidthInMbs]; + mbQps = new int[3][picHeightInMbs * picWidthInMbs]; + shs = new SliceHeader[picHeightInMbs * picWidthInMbs]; + refsUsed = new Frame[picHeightInMbs * picWidthInMbs][][]; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/DecoderState.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/DecoderState.java new file mode 100644 index 0000000..5a86987 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/DecoderState.java @@ -0,0 +1,45 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Utils; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.common.model.ColorSpace; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Current state of the decoder, this data is accessed from many methods + * + * @author The JCodec project + */ +public class DecoderState { + public int[] chromaQpOffset; + public int qp; + public byte[][] leftRow; + public byte[][] topLine; + public byte[][] topLeft; + + ColorSpace chromaFormat; + + H264Utils.MvList mvTop; + H264Utils.MvList mvLeft; + H264Utils.MvList mvTopLeft; + + public DecoderState(SliceHeader sh) { + int mbWidth = sh.sps.picWidthInMbsMinus1 + 1; + chromaQpOffset = new int[]{sh.pps.chromaQpIndexOffset, + sh.pps.extended != null ? sh.pps.extended.secondChromaQpIndexOffset : sh.pps.chromaQpIndexOffset}; + + chromaFormat = sh.sps.chromaFormatIdc; + + mvTop = new H264Utils.MvList((mbWidth << 2) + 1); + mvLeft = new H264Utils.MvList(4); + mvTopLeft = new H264Utils.MvList(1); + + leftRow = new byte[3][16]; + topLeft = new byte[3][4]; + topLine = new byte[3][mbWidth << 4]; + + qp = sh.pps.picInitQpMinus26 + 26 + sh.sliceQpDelta; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/FrameReader.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/FrameReader.java new file mode 100644 index 0000000..d3df5f5 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/FrameReader.java @@ -0,0 +1,123 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.common.biari.MDecoder; +import org.monte.media.impl.jcodec.codecs.h264.decode.aso.MapManager; +import org.monte.media.impl.jcodec.codecs.h264.decode.aso.Mapper; +import org.monte.media.impl.jcodec.codecs.h264.io.CABAC; +import org.monte.media.impl.jcodec.codecs.h264.io.CAVLC; +import org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnit; +import org.monte.media.impl.jcodec.codecs.h264.io.model.PictureParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.common.IntObjectMap; +import org.monte.media.impl.jcodec.common.io.BitReader; +import org.monte.media.impl.jcodec.common.io.NIOUtils; +import org.monte.media.impl.jcodec.common.logging.Logger; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.unescapeNAL; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnitType.IDR_SLICE; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnitType.NON_IDR_SLICE; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnitType.PPS; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnitType.SPS; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * MPEG 4 AVC ( H.264 ) Frame reader + *

+ * Conforms to H.264 ( ISO/IEC 14496-10 ) specifications + * + * @author The JCodec project + */ +public class FrameReader { + private IntObjectMap sps; + private IntObjectMap pps; + + public FrameReader() { + this.sps = new IntObjectMap(); + this.pps = new IntObjectMap(); + } + + public List readFrame(List nalUnits) { + List result = new ArrayList(); + + for (ByteBuffer nalData : nalUnits) { + NALUnit nalUnit = NALUnit.read(nalData); + + unescapeNAL(nalData); + if (SPS == nalUnit.type) { + SeqParameterSet _sps = SeqParameterSet.read(nalData); + sps.put(_sps.seqParameterSetId, _sps); + } else if (PPS == nalUnit.type) { + PictureParameterSet _pps = PictureParameterSet.read(nalData); + pps.put(_pps.picParameterSetId, _pps); + } else if (IDR_SLICE == nalUnit.type || NON_IDR_SLICE == nalUnit.type) { + if (sps.size() == 0 || pps.size() == 0) { + Logger.warn("Skipping frame as no SPS/PPS have been seen so far..."); + return null; + } + result.add(createSliceReader(nalData, nalUnit)); + } + } + + return result; + } + + private SliceReader createSliceReader(ByteBuffer segment, NALUnit nalUnit) { + BitReader _in = BitReader.createBitReader(segment); + SliceHeader sh = SliceHeaderReader.readPart1(_in); + sh.pps = pps.get(sh.picParameterSetId); + sh.sps = sps.get(sh.pps.seqParameterSetId); + SliceHeaderReader.readPart2(sh, nalUnit, sh.sps, sh.pps, _in); + + Mapper mapper = new MapManager(sh.sps, sh.pps).getMapper(sh); + + CAVLC[] cavlc = new CAVLC[]{new CAVLC(sh.sps, sh.pps, 2, 2), new CAVLC(sh.sps, sh.pps, 1, 1), + new CAVLC(sh.sps, sh.pps, 1, 1)}; + + int mbWidth = sh.sps.picWidthInMbsMinus1 + 1; + CABAC cabac = new CABAC(mbWidth); + + MDecoder mDecoder = null; + if (sh.pps.entropyCodingModeFlag) { + _in.terminate(); + int[][] cm = new int[2][1024]; + int qp = sh.pps.picInitQpMinus26 + 26 + sh.sliceQpDelta; + cabac.initModels(cm, sh.sliceType, sh.cabacInitIdc, qp); + mDecoder = new MDecoder(segment, cm); + } + + return new SliceReader(sh.pps, cabac, cavlc, mDecoder, _in, mapper, sh, nalUnit); + } + + public void addSpsList(List spsList) { + for (ByteBuffer byteBuffer : spsList) { + addSps(byteBuffer); + } + } + + public void addSps(ByteBuffer byteBuffer) { + ByteBuffer clone = NIOUtils.clone(byteBuffer); + unescapeNAL(clone); + SeqParameterSet s = SeqParameterSet.read(clone); + sps.put(s.seqParameterSetId, s); + } + + public void addPpsList(List ppsList) { + for (ByteBuffer byteBuffer : ppsList) { + addPps(byteBuffer); + } + } + + public void addPps(ByteBuffer byteBuffer) { + ByteBuffer clone = NIOUtils.clone(byteBuffer); + unescapeNAL(clone); + PictureParameterSet p = PictureParameterSet.read(clone); + pps.put(p.picParameterSetId, p); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/Intra16x16PredictionBuilder.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/Intra16x16PredictionBuilder.java new file mode 100644 index 0000000..69c4865 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/Intra16x16PredictionBuilder.java @@ -0,0 +1,254 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.common.ArrayUtil; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.LUMA_4x4_BLOCK_LUT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.LUMA_4x4_POS_LUT; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Prediction builder class for intra 16x16 coded macroblocks + * + * @author The JCodec project + */ +public class Intra16x16PredictionBuilder { + public static void predictWithMode(int predMode, int[][] residual, boolean leftAvailable, boolean topAvailable, + byte[] leftRow, byte[] topLine, byte[] topLeft, int x, byte[] pixOut) { + switch (predMode) { + case 0: + predictVertical(residual, topAvailable, topLine, x, pixOut); + break; + case 1: + predictHorizontal(residual, leftAvailable, leftRow, x, pixOut); + break; + case 2: + predictDC(residual, leftAvailable, topAvailable, leftRow, topLine, x, pixOut); + break; + case 3: + predictPlane(residual, leftAvailable, topAvailable, leftRow, topLine, topLeft, x, pixOut); + break; + } + + } + + public static void lumaPred(int predMode, boolean leftAvailable, boolean topAvailable, byte[] leftRow, + byte[] topLine, byte topLeft, int x, byte[][] pred) { + switch (predMode) { + case 0: + lumaVerticalPred(topAvailable, topLine, x, pred); + break; + case 1: + lumaHorizontalPred(leftAvailable, leftRow, x, pred); + break; + case 2: + lumaDCPred(leftAvailable, topAvailable, leftRow, topLine, x, pred); + break; + case 3: + lumaPlanePred(leftRow, topLine, topLeft, x, pred); + break; + } + } + + public static int lumaPredSAD(int predMode, boolean leftAvailable, boolean topAvailable, byte[] leftRow, + byte[] topLine, byte topLeft, int x, byte[] pred) { + switch (predMode) { + case 0: + return lumaVerticalPredSAD(topAvailable, topLine, x, pred); + case 1: + return lumaHorizontalPredSAD(leftAvailable, leftRow, x, pred); + default: + case 2: + return lumaDCPredSAD(leftAvailable, topAvailable, leftRow, topLine, x, pred); + case 3: + return lumaPlanePredSAD(leftAvailable, topAvailable, leftRow, topLine, topLeft, x, pred); + } + } + + public static void predictVertical(int[][] residual, boolean topAvailable, byte[] topLine, int x, byte[] pixOut) { + int off = 0; + for (int j = 0; j < 16; j++) { + for (int i = 0; i < 16; i++, off++) + pixOut[off] = (byte) clip(residual[LUMA_4x4_BLOCK_LUT[off]][LUMA_4x4_POS_LUT[off]] + topLine[x + i], + -128, 127); + } + } + + public static void lumaVerticalPred(boolean topAvailable, byte[] topLine, int x, byte[][] pred) { + int off = 0; + for (int j = 0; j < 16; j++) { + for (int i = 0; i < 16; i++, off++) + pred[LUMA_4x4_BLOCK_LUT[off]][LUMA_4x4_POS_LUT[off]] = topLine[x + i]; + } + } + + public static int lumaVerticalPredSAD(boolean topAvailable, byte[] topLine, int x, byte[] pred) { + if (!topAvailable) + return Integer.MAX_VALUE; + int sad = 0; + for (int j = 0; j < 16; j++) { + for (int i = 0; i < 16; i++) + sad += MathUtil.abs(pred[(j << 4) + i] - topLine[x + i]); + } + return sad; + } + + public static void predictHorizontal(int[][] residual, boolean leftAvailable, byte[] leftRow, int x, + byte[] pixOut) { + int off = 0; + for (int j = 0; j < 16; j++) { + for (int i = 0; i < 16; i++, off++) + pixOut[off] = (byte) clip(residual[LUMA_4x4_BLOCK_LUT[off]][LUMA_4x4_POS_LUT[off]] + leftRow[j], -128, + 127); + } + } + + public static void lumaHorizontalPred(boolean leftAvailable, byte[] leftRow, int x, byte[][] pred) { + int off = 0; + for (int j = 0; j < 16; j++) { + for (int i = 0; i < 16; i++, off++) + pred[LUMA_4x4_BLOCK_LUT[off]][LUMA_4x4_POS_LUT[off]] = leftRow[j]; + } + } + + public static int lumaHorizontalPredSAD(boolean leftAvailable, byte[] leftRow, int x, byte[] pred) { + if (!leftAvailable) + return Integer.MAX_VALUE; + int sad = 0; + for (int j = 0; j < 16; j++) { + for (int i = 0; i < 16; i++) + sad += MathUtil.abs(pred[(j << 4) + i] - leftRow[j]); + } + return sad; + } + + public static void predictDC(int[][] residual, boolean leftAvailable, boolean topAvailable, byte[] leftRow, + byte[] topLine, int x, byte[] pixOut) { + int s0 = getDC(leftAvailable, topAvailable, leftRow, topLine, x); + + for (int i = 0; i < 256; i++) + pixOut[i] = (byte) clip(residual[LUMA_4x4_BLOCK_LUT[i]][LUMA_4x4_POS_LUT[i]] + s0, -128, 127); + } + + public static void lumaDCPred(boolean leftAvailable, boolean topAvailable, byte[] leftRow, byte[] topLine, int x, + byte[][] pred) { + int s0 = getDC(leftAvailable, topAvailable, leftRow, topLine, x); + + for (int i = 0; i < pred.length; i++) + for (int j = 0; j < pred[i].length; j++) + pred[i][j] += s0; + } + + public static int lumaDCPredSAD(boolean leftAvailable, boolean topAvailable, byte[] leftRow, byte[] topLine, int x, + byte[] pred) { + int s0 = getDC(leftAvailable, topAvailable, leftRow, topLine, x); + + int sad = 0; + for (int i = 0; i < pred.length; i++) + sad += MathUtil.abs(pred[i] - s0); + return sad; + } + + private static int getDC(boolean leftAvailable, boolean topAvailable, byte[] leftRow, byte[] topLine, int x) { + int s0; + if (leftAvailable && topAvailable) { + s0 = (ArrayUtil.sumByte(leftRow) + ArrayUtil.sumByte3(topLine, x, 16) + 16) >> 5; + } else if (leftAvailable) { + s0 = (ArrayUtil.sumByte(leftRow) + 8) >> 4; + } else if (topAvailable) { + s0 = (ArrayUtil.sumByte3(topLine, x, 16) + 8) >> 4; + } else { + s0 = 0; + } + return s0; + } + + public static void predictPlane(int[][] residual, boolean leftAvailable, boolean topAvailable, byte[] leftRow, + byte[] topLine, byte[] topLeft, int x, byte[] pixOut) { + int H = 0; + + for (int i = 0; i < 7; i++) { + H += (i + 1) * (topLine[x + 8 + i] - topLine[x + 6 - i]); + } + H += 8 * (topLine[x + 15] - topLeft[0]); + + int V = 0; + for (int j = 0; j < 7; j++) { + V += (j + 1) * (leftRow[8 + j] - leftRow[6 - j]); + } + V += 8 * (leftRow[15] - topLeft[0]); + + int c = (5 * V + 32) >> 6; + int b = (5 * H + 32) >> 6; + int a = 16 * (leftRow[15] + topLine[x + 15]); + + int off = 0; + for (int j = 0; j < 16; j++) { + for (int i = 0; i < 16; i++, off++) { + int val = clip((a + b * (i - 7) + c * (j - 7) + 16) >> 5, -128, 127); + pixOut[off] = (byte) clip(residual[LUMA_4x4_BLOCK_LUT[off]][LUMA_4x4_POS_LUT[off]] + val, -128, 127); + } + } + } + + public static void lumaPlanePred(byte[] leftRow, byte[] topLine, byte topLeft, int x, byte[][] pred) { + int H = 0; + + for (int i = 0; i < 7; i++) { + H += (i + 1) * (topLine[x + 8 + i] - topLine[x + 6 - i]); + } + H += 8 * (topLine[x + 15] - topLeft); + + int V = 0; + for (int j = 0; j < 7; j++) { + V += (j + 1) * (leftRow[8 + j] - leftRow[6 - j]); + } + V += 8 * (leftRow[15] - topLeft); + + int c = (5 * V + 32) >> 6; + int b = (5 * H + 32) >> 6; + int a = 16 * (leftRow[15] + topLine[x + 15]); + + int off = 0; + for (int j = 0; j < 16; j++) { + for (int i = 0; i < 16; i++, off++) { + int val = clip((a + b * (i - 7) + c * (j - 7) + 16) >> 5, -128, 127); + pred[LUMA_4x4_BLOCK_LUT[off]][LUMA_4x4_POS_LUT[off]] = (byte) val; + } + } + } + + public static int lumaPlanePredSAD(boolean leftAvailable, boolean topAvailable, byte[] leftRow, byte[] topLine, byte topLeft, int x, byte[] pred) { + if (!leftAvailable || !topAvailable) + return Integer.MAX_VALUE; + int H = 0; + + for (int i = 0; i < 7; i++) { + H += (i + 1) * (topLine[x + 8 + i] - topLine[x + 6 - i]); + } + H += 8 * (topLine[x + 15] - topLeft); + + int V = 0; + for (int j = 0; j < 7; j++) { + V += (j + 1) * (leftRow[8 + j] - leftRow[6 - j]); + } + V += 8 * (leftRow[15] - topLeft); + + int c = (5 * V + 32) >> 6; + int b = (5 * H + 32) >> 6; + int a = 16 * (leftRow[15] + topLine[x + 15]); + + int sad = 0; + int off = 0; + for (int j = 0; j < 16; j++) { + for (int i = 0; i < 16; i++, off++) { + int val = clip((a + b * (i - 7) + c * (j - 7) + 16) >> 5, -128, 127); + sad += MathUtil.abs(pred[off] - val); + } + } + return sad; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/Intra4x4PredictionBuilder.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/Intra4x4PredictionBuilder.java new file mode 100644 index 0000000..145ca78 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/Intra4x4PredictionBuilder.java @@ -0,0 +1,661 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Builds intra prediction for intra 4x4 coded macroblocks + * + * @author The JCodec project + */ +public class Intra4x4PredictionBuilder { + + public static void predictWithMode(int mode, int[] residual, boolean leftAvailable, boolean topAvailable, + boolean topRightAvailable, byte[] leftRow, byte[] topLine, byte topLeft[], int mbOffX, int blkX, int blkY, + byte[] pixOut) { + switch (mode) { + case 0: + predictVertical(residual, topAvailable, topLine, mbOffX, blkX, blkY, pixOut); + break; + case 1: + predictHorizontal(residual, leftAvailable, leftRow, mbOffX, blkX, blkY, pixOut); + break; + case 2: + predictDC(residual, leftAvailable, topAvailable, leftRow, topLine, mbOffX, blkX, blkY, pixOut); + break; + case 3: + predictDiagonalDownLeft(residual, topAvailable, topRightAvailable, topLine, mbOffX, blkX, blkY, pixOut); + break; + case 4: + predictDiagonalDownRight(residual, leftAvailable, topAvailable, leftRow, topLine, topLeft, mbOffX, blkX, + blkY, pixOut); + break; + case 5: + predictVerticalRight(residual, leftAvailable, topAvailable, leftRow, topLine, topLeft, mbOffX, blkX, blkY, + pixOut); + break; + case 6: + predictHorizontalDown(residual, leftAvailable, topAvailable, leftRow, topLine, topLeft, mbOffX, blkX, blkY, + pixOut); + break; + case 7: + predictVerticalLeft(residual, topAvailable, topRightAvailable, topLine, mbOffX, blkX, blkY, pixOut); + break; + case 8: + predictHorizontalUp(residual, leftAvailable, leftRow, mbOffX, blkX, blkY, pixOut); + break; + } + + int oo1 = mbOffX + blkX; + int off1 = (blkY << 4) + blkX + 3; + + topLeft[blkY >> 2] = topLine[oo1 + 3]; + + leftRow[blkY] = pixOut[off1]; + leftRow[blkY + 1] = pixOut[off1 + 16]; + leftRow[blkY + 2] = pixOut[off1 + 32]; + leftRow[blkY + 3] = pixOut[off1 + 48]; + + int off2 = (blkY << 4) + blkX + 48; + topLine[oo1] = pixOut[off2]; + topLine[oo1 + 1] = pixOut[off2 + 1]; + topLine[oo1 + 2] = pixOut[off2 + 2]; + topLine[oo1 + 3] = pixOut[off2 + 3]; + } + + public static boolean lumaPred(int predType, boolean hasLeft, boolean hasTop, boolean hasTr, byte[] predLeft, + byte[] predTop, byte predTopLeft, int blkX, int blkY, byte[] pred) { + switch (predType) { + case 0: + return lumaPredVertical(hasLeft, hasTop, predLeft, predTop, predTopLeft, blkX, blkY, pred); + case 1: + return lumaPredHorizontal(hasLeft, hasTop, predLeft, predTop, predTopLeft, blkX, blkY, pred); + default: + case 2: + return lumaPredDC(hasLeft, hasTop, predLeft, predTop, predTopLeft, blkX, blkY, pred); + case 3: + return lumaPredDiagonalDownLeft(hasLeft, hasTop, hasTr, predLeft, predTop, predTopLeft, blkX, blkY, pred); + case 4: + return lumaPredDiagonalDownRight(hasLeft, hasTop, predLeft, predTop, predTopLeft, blkX, blkY, pred); + case 5: + return lumaPredVerticalRight(hasLeft, hasTop, predLeft, predTop, predTopLeft, blkX, blkY, pred); + case 6: + return lumaPredHorizontalDown(hasLeft, hasTop, predLeft, predTop, predTopLeft, blkX, blkY, pred); + case 7: + return lumaPredVerticalLeft(hasLeft, hasTop, hasTr, predLeft, predTop, predTopLeft, blkX, blkY, pred); + case 8: + return lumaPredHorizontalUp(hasLeft, hasTop, predLeft, predTop, predTopLeft, blkX, blkY, pred); + } + } + + public static boolean available(int predType, boolean hasLeft, boolean hasTop) { + switch (predType) { + case 0: + return hasTop; + case 1: + return hasLeft; + default: + case 2: + return true; + case 3: + return hasTop; + case 4: + return hasLeft && hasTop; + case 5: + return hasLeft && hasTop; + case 6: + return hasLeft && hasTop; + case 7: + return hasTop; + case 8: + return hasLeft; + } + } + + private static boolean lumaPredVertical(boolean leftAvailable, boolean topAvailable, byte[] predLeft, byte[] predTop, + byte predTopLeft, int blkX, int blkY, byte[] resi) { + for (int j = 0, rOff = 0; j < 4; ++j, rOff += 4) { + resi[rOff + 0] = predTop[blkX + 0]; + resi[rOff + 1] = predTop[blkX + 1]; + resi[rOff + 2] = predTop[blkX + 2]; + resi[rOff + 3] = predTop[blkX + 3]; + } + return topAvailable; + } + + public static void predictVertical(int[] residual, boolean topAvailable, byte[] topLine, int mbOffX, int blkX, + int blkY, byte[] pixOut) { + + int pixOff = (blkY << 4) + blkX; + int toff = mbOffX + blkX; + int rOff = 0; + for (int j = 0; j < 4; ++j) { + pixOut[pixOff] = (byte) clip(residual[rOff] + topLine[toff], -128, 127); + pixOut[pixOff + 1] = (byte) clip(residual[rOff + 1] + topLine[toff + 1], -128, 127); + pixOut[pixOff + 2] = (byte) clip(residual[rOff + 2] + topLine[toff + 2], -128, 127); + pixOut[pixOff + 3] = (byte) clip(residual[rOff + 3] + topLine[toff + 3], -128, 127); + rOff += 4; + pixOff += 16; + } + } + + public static void predictHorizontal(int[] residual, boolean leftAvailable, byte[] leftRow, int mbOffX, int blkX, + int blkY, byte[] pixOut) { + + int pixOff = (blkY << 4) + blkX; + int rOff = 0; + for (int j = 0; j < 4; j++) { + int l = leftRow[blkY + j]; + pixOut[pixOff + 0] = (byte) clip(residual[rOff + 0] + l, -128, 127); + pixOut[pixOff + 1] = (byte) clip(residual[rOff + 1] + l, -128, 127); + pixOut[pixOff + 2] = (byte) clip(residual[rOff + 2] + l, -128, 127); + pixOut[pixOff + 3] = (byte) clip(residual[rOff + 3] + l, -128, 127); + rOff += 4; + pixOff += 16; + } + } + + private static boolean lumaPredHorizontal(boolean hasLeft, boolean hasTop, byte[] predLeft, byte[] predTop, + byte predTopLeft, int blkX, int blkY, byte[] resi) { + for (int j = 0, rOff = 0; j < 4; ++j, rOff += 4) { + resi[rOff + 0] = predLeft[blkY + j]; + resi[rOff + 1] = predLeft[blkY + j]; + resi[rOff + 2] = predLeft[blkY + j]; + resi[rOff + 3] = predLeft[blkY + j]; + } + return hasLeft; + } + + public static void predictDC(int[] residual, boolean leftAvailable, boolean topAvailable, byte[] leftRow, + byte[] topLine, int mbOffX, int blkX, int blkY, byte[] pixOut) { + + int val = calcDC(leftAvailable, topAvailable, leftRow, topLine, mbOffX, blkX, blkY); + + int pixOff = (blkY << 4) + blkX; + int rOff = 0; + for (int j = 0; j < 4; j++) { + pixOut[pixOff] = (byte) clip(residual[rOff] + val, -128, 127); + pixOut[pixOff + 1] = (byte) clip(residual[rOff + 1] + val, -128, 127); + pixOut[pixOff + 2] = (byte) clip(residual[rOff + 2] + val, -128, 127); + pixOut[pixOff + 3] = (byte) clip(residual[rOff + 3] + val, -128, 127); + pixOff += 16; + rOff += 4; + } + } + + private static int calcDC(boolean leftAvailable, boolean topAvailable, byte[] leftRow, byte[] topLine, int mbOffX, + int blkX, int blkY) { + int val; + if (leftAvailable && topAvailable) { + val = (leftRow[blkY] + leftRow[blkY + 1] + leftRow[blkY + 2] + leftRow[blkY + 3] + topLine[mbOffX + blkX] + + topLine[mbOffX + blkX + 1] + topLine[mbOffX + blkX + 2] + topLine[mbOffX + blkX + 3] + 4) >> 3; + } else if (leftAvailable) { + val = (leftRow[blkY] + leftRow[blkY + 1] + leftRow[blkY + 2] + leftRow[blkY + 3] + 2) >> 2; + } else if (topAvailable) { + val = (topLine[mbOffX + blkX] + topLine[mbOffX + blkX + 1] + topLine[mbOffX + blkX + 2] + + topLine[mbOffX + blkX + 3] + 2) >> 2; + } else { + val = 0; + } + return val; + } + + private static boolean lumaPredDC(boolean hasLeft, boolean hasTop, byte[] predLeft, byte[] predTop, byte predTopLeft, + int blkX, int blkY, byte[] resi) { + int val = calcDC(hasLeft, hasTop, predLeft, predTop, 0, blkX, blkY); + + for (int j = 0, rOff = 0; j < 4; ++j, rOff += 4) { + resi[rOff + 0] = (byte) val; + resi[rOff + 1] = (byte) val; + resi[rOff + 2] = (byte) val; + resi[rOff + 3] = (byte) val; + } + + return true; + } + + public static void predictDiagonalDownLeft(int[] residual, boolean topAvailable, boolean topRightAvailable, + byte[] topLine, int mbOffX, int blkX, int blkY, byte[] pixOut) { + int to = mbOffX + blkX; + int tr0 = topLine[to + 3], tr1 = topLine[to + 3], tr2 = topLine[to + 3], tr3 = topLine[to + 3]; + if (topRightAvailable) { + tr0 = topLine[to + 4]; + tr1 = topLine[to + 5]; + tr2 = topLine[to + 6]; + tr3 = topLine[to + 7]; + } + + int c0 = ((topLine[to] + topLine[to + 2] + (topLine[to + 1] << 1) + 2) >> 2); + int c1 = ((topLine[to + 1] + topLine[to + 3] + (topLine[to + 2] << 1) + 2) >> 2); + int c2 = ((topLine[to + 2] + tr0 + (topLine[to + 3] << 1) + 2) >> 2); + int c3 = ((topLine[to + 3] + tr1 + (tr0 << 1) + 2) >> 2); + int c4 = ((tr0 + tr2 + (tr1 << 1) + 2) >> 2); + int c5 = ((tr1 + tr3 + (tr2 << 1) + 2) >> 2); + int c6 = ((tr2 + 3 * (tr3) + 2) >> 2); + + int off = (blkY << 4) + blkX; + pixOut[off] = (byte) clip(residual[0] + c0, -128, 127); + pixOut[off + 1] = (byte) clip(residual[1] + c1, -128, 127); + pixOut[off + 2] = (byte) clip(residual[2] + c2, -128, 127); + pixOut[off + 3] = (byte) clip(residual[3] + c3, -128, 127); + + pixOut[off + 16] = (byte) clip(residual[4] + c1, -128, 127); + pixOut[off + 17] = (byte) clip(residual[5] + c2, -128, 127); + pixOut[off + 18] = (byte) clip(residual[6] + c3, -128, 127); + pixOut[off + 19] = (byte) clip(residual[7] + c4, -128, 127); + + pixOut[off + 32] = (byte) clip(residual[8] + c2, -128, 127); + pixOut[off + 33] = (byte) clip(residual[9] + c3, -128, 127); + pixOut[off + 34] = (byte) clip(residual[10] + c4, -128, 127); + pixOut[off + 35] = (byte) clip(residual[11] + c5, -128, 127); + + pixOut[off + 48] = (byte) clip(residual[12] + c3, -128, 127); + pixOut[off + 49] = (byte) clip(residual[13] + c4, -128, 127); + pixOut[off + 50] = (byte) clip(residual[14] + c5, -128, 127); + pixOut[off + 51] = (byte) clip(residual[15] + c6, -128, 127); + } + + private static boolean lumaPredDiagonalDownLeft(boolean hasLeft, boolean hasTop, boolean hasTr, byte[] leftRow, + byte[] topLine, byte predTopLeft, int blkX, int blkY, byte[] resi) { + int to = blkX; + int tr0 = topLine[to + 3], tr1 = topLine[to + 3], tr2 = topLine[to + 3], tr3 = topLine[to + 3]; + if (hasTr) { + tr0 = topLine[to + 4]; + tr1 = topLine[to + 5]; + tr2 = topLine[to + 6]; + tr3 = topLine[to + 7]; + } + + int c0 = ((topLine[to] + topLine[to + 2] + (topLine[to + 1] << 1) + 2) >> 2); + int c1 = ((topLine[to + 1] + topLine[to + 3] + (topLine[to + 2] << 1) + 2) >> 2); + int c2 = ((topLine[to + 2] + tr0 + (topLine[to + 3] << 1) + 2) >> 2); + int c3 = ((topLine[to + 3] + tr1 + (tr0 << 1) + 2) >> 2); + int c4 = ((tr0 + tr2 + (tr1 << 1) + 2) >> 2); + int c5 = ((tr1 + tr3 + (tr2 << 1) + 2) >> 2); + int c6 = ((tr2 + 3 * (tr3) + 2) >> 2); + + int off = (blkY << 4) + blkX; + resi[0] = (byte) c0; + resi[1] = (byte) c1; + resi[2] = (byte) c2; + resi[3] = (byte) c3; + resi[4] = (byte) c1; + resi[5] = (byte) c2; + resi[6] = (byte) c3; + resi[7] = (byte) c4; + resi[8] = (byte) c2; + resi[9] = (byte) c3; + resi[10] = (byte) c4; + resi[11] = (byte) c5; + resi[12] = (byte) c3; + resi[13] = (byte) c4; + resi[14] = (byte) c5; + resi[15] = (byte) c6; + + return hasTop; + } + + public static void predictDiagonalDownRight(int[] residual, boolean leftAvailable, boolean topAvailable, + byte[] leftRow, byte[] topLine, byte[] topLeft, int mbOffX, int blkX, int blkY, byte[] pixOut) { + + int off = (blkY << 4) + blkX; + int c0 = ((topLine[mbOffX + blkX] + 2 * topLeft[blkY >> 2] + leftRow[blkY] + 2) >> 2); + + int c1 = ((topLeft[blkY >> 2] + (topLine[mbOffX + blkX + 0] << 1) + topLine[mbOffX + blkX + 1] + 2) >> 2); + int c2 = ((topLine[mbOffX + blkX] + (topLine[mbOffX + blkX + 1] << 1) + topLine[mbOffX + blkX + 2] + 2) >> 2); + int c3 = ((topLine[mbOffX + blkX + 1] + (topLine[mbOffX + blkX + 2] << 1) + topLine[mbOffX + blkX + 3] + + 2) >> 2); + + pixOut[off] = (byte) clip(residual[0] + c0, -128, 127); + pixOut[off + 1] = (byte) clip(residual[1] + c1, -128, 127); + pixOut[off + 2] = (byte) clip(residual[2] + c2, -128, 127); + pixOut[off + 3] = (byte) clip(residual[3] + c3, -128, 127); + + int c4 = ((topLeft[blkY >> 2] + (leftRow[blkY] << 1) + leftRow[blkY + 1] + 2) >> 2); + int c6 = ((topLeft[blkY >> 2] + (topLine[mbOffX + blkX] << 1) + topLine[mbOffX + blkX + 1] + 2) >> 2); + int c7 = ((topLine[mbOffX + blkX] + (topLine[mbOffX + blkX + 1] << 1) + topLine[mbOffX + blkX + 2] + 2) >> 2); + + pixOut[off + 16] = (byte) clip(residual[4] + c4, -128, 127); + pixOut[off + 17] = (byte) clip(residual[5] + c0, -128, 127); + pixOut[off + 18] = (byte) clip(residual[6] + c6, -128, 127); + pixOut[off + 19] = (byte) clip(residual[7] + c7, -128, 127); + + int c8 = ((leftRow[blkY + 0] + (leftRow[blkY + 1] << 1) + leftRow[blkY + 2] + 2) >> 2); + int c9 = ((topLeft[blkY >> 2] + (leftRow[blkY] << 1) + leftRow[blkY + 1] + 2) >> 2); + int c11 = ((topLeft[blkY >> 2] + (topLine[mbOffX + blkX] << 1) + topLine[mbOffX + blkX + 1] + 2) >> 2); + + pixOut[off + 32] = (byte) clip(residual[8] + c8, -128, 127); + pixOut[off + 33] = (byte) clip(residual[9] + c9, -128, 127); + pixOut[off + 34] = (byte) clip(residual[10] + c0, -128, 127); + pixOut[off + 35] = (byte) clip(residual[11] + c11, -128, 127); + + int c12 = ((leftRow[blkY + 1] + (leftRow[blkY + 2] << 1) + leftRow[blkY + 3] + 2) >> 2); + int c13 = ((leftRow[blkY] + (leftRow[blkY + 1] << 1) + leftRow[blkY + 2] + 2) >> 2); + int c14 = ((topLeft[blkY >> 2] + (leftRow[blkY] << 1) + leftRow[blkY + 1] + 2) >> 2); + + pixOut[off + 48] = (byte) clip(residual[12] + c12, -128, 127); + pixOut[off + 49] = (byte) clip(residual[13] + c13, -128, 127); + pixOut[off + 50] = (byte) clip(residual[14] + c14, -128, 127); + pixOut[off + 51] = (byte) clip(residual[15] + c0, -128, 127); + } + + private static boolean lumaPredDiagonalDownRight(boolean hasLeft, boolean hasTop, byte[] leftRow, byte[] topLine, + byte topLeft, int blkX, int blkY, byte[] resi) { + int off = (blkY << 4) + blkX; + int c0 = ((topLine[blkX] + 2 * topLeft + leftRow[blkY] + 2) >> 2); + + int c1 = ((topLeft + (topLine[blkX + 0] << 1) + topLine[blkX + 1] + 2) >> 2); + int c2 = ((topLine[blkX] + (topLine[blkX + 1] << 1) + topLine[blkX + 2] + 2) >> 2); + int c3 = ((topLine[blkX + 1] + (topLine[blkX + 2] << 1) + topLine[blkX + 3] + 2) >> 2); + int c4 = ((topLeft + (leftRow[blkY] << 1) + leftRow[blkY + 1] + 2) >> 2); + int c6 = ((topLeft + (topLine[blkX] << 1) + topLine[blkX + 1] + 2) >> 2); + int c7 = ((topLine[blkX] + (topLine[blkX + 1] << 1) + topLine[blkX + 2] + 2) >> 2); + int c8 = ((leftRow[blkY + 0] + (leftRow[blkY + 1] << 1) + leftRow[blkY + 2] + 2) >> 2); + int c9 = ((topLeft + (leftRow[blkY] << 1) + leftRow[blkY + 1] + 2) >> 2); + int c11 = ((topLeft + (topLine[blkX] << 1) + topLine[blkX + 1] + 2) >> 2); + int c12 = ((leftRow[blkY + 1] + (leftRow[blkY + 2] << 1) + leftRow[blkY + 3] + 2) >> 2); + int c13 = ((leftRow[blkY] + (leftRow[blkY + 1] << 1) + leftRow[blkY + 2] + 2) >> 2); + int c14 = ((topLeft + (leftRow[blkY] << 1) + leftRow[blkY + 1] + 2) >> 2); + + resi[0] = (byte) c0; + resi[1] = (byte) c1; + resi[2] = (byte) c2; + resi[3] = (byte) c3; + resi[4] = (byte) c4; + resi[5] = (byte) c0; + resi[6] = (byte) c6; + resi[7] = (byte) c7; + resi[8] = (byte) c8; + resi[9] = (byte) c9; + resi[10] = (byte) c0; + resi[11] = (byte) c11; + resi[12] = (byte) c12; + resi[13] = (byte) c13; + resi[14] = (byte) c14; + resi[15] = (byte) c0; + + return hasLeft && hasTop; + } + + public static void predictVerticalRight(int[] residual, boolean leftAvailable, boolean topAvailable, byte[] leftRow, + byte[] topLine, byte[] topLeft, int mbOffX, int blkX, int blkY, byte[] pixOut) { + + int v1 = (topLeft[blkY >> 2] + topLine[mbOffX + blkX + 0] + 1) >> 1; + int v2 = (topLine[mbOffX + blkX + 0] + topLine[mbOffX + blkX + 1] + 1) >> 1; + int v3 = (topLine[mbOffX + blkX + 1] + topLine[mbOffX + blkX + 2] + 1) >> 1; + int v4 = (topLine[mbOffX + blkX + 2] + topLine[mbOffX + blkX + 3] + 1) >> 1; + int v5 = (leftRow[blkY] + 2 * topLeft[blkY >> 2] + topLine[mbOffX + blkX + 0] + 2) >> 2; + int v6 = (topLeft[blkY >> 2] + 2 * topLine[mbOffX + blkX + 0] + topLine[mbOffX + blkX + 1] + 2) >> 2; + int v7 = (topLine[mbOffX + blkX + 0] + 2 * topLine[mbOffX + blkX + 1] + topLine[mbOffX + blkX + 2] + 2) >> 2; + int v8 = (topLine[mbOffX + blkX + 1] + 2 * topLine[mbOffX + blkX + 2] + topLine[mbOffX + blkX + 3] + 2) >> 2; + int v9 = (topLeft[blkY >> 2] + 2 * leftRow[blkY] + leftRow[blkY + 1] + 2) >> 2; + int v10 = (leftRow[blkY] + 2 * leftRow[blkY + 1] + leftRow[blkY + 2] + 2) >> 2; + + int off = (blkY << 4) + blkX; + pixOut[off + 0] = (byte) clip(residual[0] + v1, -128, 127); + pixOut[off + 1] = (byte) clip(residual[1] + v2, -128, 127); + pixOut[off + 2] = (byte) clip(residual[2] + v3, -128, 127); + pixOut[off + 3] = (byte) clip(residual[3] + v4, -128, 127); + pixOut[off + 16] = (byte) clip(residual[4] + v5, -128, 127); + pixOut[off + 17] = (byte) clip(residual[5] + v6, -128, 127); + pixOut[off + 18] = (byte) clip(residual[6] + v7, -128, 127); + pixOut[off + 19] = (byte) clip(residual[7] + v8, -128, 127); + pixOut[off + 32] = (byte) clip(residual[8] + v9, -128, 127); + pixOut[off + 33] = (byte) clip(residual[9] + v1, -128, 127); + pixOut[off + 34] = (byte) clip(residual[10] + v2, -128, 127); + pixOut[off + 35] = (byte) clip(residual[11] + v3, -128, 127); + pixOut[off + 48] = (byte) clip(residual[12] + v10, -128, 127); + pixOut[off + 49] = (byte) clip(residual[13] + v5, -128, 127); + pixOut[off + 50] = (byte) clip(residual[14] + v6, -128, 127); + pixOut[off + 51] = (byte) clip(residual[15] + v7, -128, 127); + } + + private static boolean lumaPredVerticalRight(boolean hasLeft, boolean hasTop, byte[] predLeft, byte[] predTop, + byte predTopLeft, int blkX, int blkY, byte[] resi) { + int v1 = (predTopLeft + predTop[blkX + 0] + 1) >> 1; + int v2 = (predTop[blkX + 0] + predTop[blkX + 1] + 1) >> 1; + int v3 = (predTop[blkX + 1] + predTop[blkX + 2] + 1) >> 1; + int v4 = (predTop[blkX + 2] + predTop[blkX + 3] + 1) >> 1; + int v5 = (predLeft[blkY] + 2 * predTopLeft + predTop[blkX + 0] + 2) >> 2; + int v6 = (predTopLeft + 2 * predTop[blkX + 0] + predTop[blkX + 1] + 2) >> 2; + int v7 = (predTop[blkX + 0] + 2 * predTop[blkX + 1] + predTop[blkX + 2] + 2) >> 2; + int v8 = (predTop[blkX + 1] + 2 * predTop[blkX + 2] + predTop[blkX + 3] + 2) >> 2; + int v9 = (predTopLeft + 2 * predLeft[blkY] + predLeft[blkY + 1] + 2) >> 2; + int v10 = (predLeft[blkY] + 2 * predLeft[blkY + 1] + predLeft[blkY + 2] + 2) >> 2; + + resi[0] = (byte) v1; + resi[1] = (byte) v2; + resi[2] = (byte) v3; + resi[3] = (byte) v4; + resi[4] = (byte) v5; + resi[5] = (byte) v6; + resi[6] = (byte) v7; + resi[7] = (byte) v8; + resi[8] = (byte) v9; + resi[9] = (byte) v1; + resi[10] = (byte) v2; + resi[11] = (byte) v3; + resi[12] = (byte) v10; + resi[13] = (byte) v5; + resi[14] = (byte) v6; + resi[15] = (byte) v7; + + return hasLeft && hasTop; + } + + public static void predictHorizontalDown(int[] residual, boolean leftAvailable, boolean topAvailable, + byte[] leftRow, byte[] topLine, byte[] topLeft, int mbOffX, int blkX, int blkY, byte[] pixOut) { + + int c0 = (topLeft[blkY >> 2] + leftRow[blkY] + 1) >> 1; + int c1 = (leftRow[blkY] + 2 * topLeft[blkY >> 2] + topLine[mbOffX + blkX + 0] + 2) >> 2; + int c2 = (topLeft[blkY >> 2] + 2 * topLine[mbOffX + blkX + 0] + topLine[mbOffX + blkX + 1] + 2) >> 2; + int c3 = (topLine[mbOffX + blkX + 0] + 2 * topLine[mbOffX + blkX + 1] + topLine[mbOffX + blkX + 2] + 2) >> 2; + int c4 = (leftRow[blkY] + leftRow[blkY + 1] + 1) >> 1; + int c5 = (topLeft[blkY >> 2] + 2 * leftRow[blkY] + leftRow[blkY + 1] + 2) >> 2; + int c6 = (leftRow[blkY + 1] + leftRow[blkY + 2] + 1) >> 1; + int c7 = (leftRow[blkY] + 2 * leftRow[blkY + 1] + leftRow[blkY + 2] + 2) >> 2; + int c8 = (leftRow[blkY + 2] + leftRow[blkY + 3] + 1) >> 1; + int c9 = (leftRow[blkY + 1] + 2 * leftRow[blkY + 2] + leftRow[blkY + 3] + 2) >> 2; + + int off = (blkY << 4) + blkX; + pixOut[off] = (byte) clip(residual[0] + c0, -128, 127); + pixOut[off + 1] = (byte) clip(residual[1] + c1, -128, 127); + pixOut[off + 2] = (byte) clip(residual[2] + c2, -128, 127); + pixOut[off + 3] = (byte) clip(residual[3] + c3, -128, 127); + pixOut[off + 16] = (byte) clip(residual[4] + c4, -128, 127); + pixOut[off + 17] = (byte) clip(residual[5] + c5, -128, 127); + pixOut[off + 18] = (byte) clip(residual[6] + c0, -128, 127); + pixOut[off + 19] = (byte) clip(residual[7] + c1, -128, 127); + pixOut[off + 32] = (byte) clip(residual[8] + c6, -128, 127); + pixOut[off + 33] = (byte) clip(residual[9] + c7, -128, 127); + pixOut[off + 34] = (byte) clip(residual[10] + c4, -128, 127); + pixOut[off + 35] = (byte) clip(residual[11] + c5, -128, 127); + pixOut[off + 48] = (byte) clip(residual[12] + c8, -128, 127); + pixOut[off + 49] = (byte) clip(residual[13] + c9, -128, 127); + pixOut[off + 50] = (byte) clip(residual[14] + c6, -128, 127); + pixOut[off + 51] = (byte) clip(residual[15] + c7, -128, 127); + } + + private static boolean lumaPredHorizontalDown(boolean hasLeft, boolean hasTop, byte[] predLeft, byte[] predTop, + byte predTopLeft, int blkX, int blkY, byte[] resi) { + int c0 = (predTopLeft + predLeft[blkY] + 1) >> 1; + int c1 = (predLeft[blkY] + 2 * predTopLeft + predTop[blkX + 0] + 2) >> 2; + int c2 = (predTopLeft + 2 * predTop[blkX + 0] + predTop[blkX + 1] + 2) >> 2; + int c3 = (predTop[blkX + 0] + 2 * predTop[blkX + 1] + predTop[blkX + 2] + 2) >> 2; + int c4 = (predLeft[blkY] + predLeft[blkY + 1] + 1) >> 1; + int c5 = (predTopLeft + 2 * predLeft[blkY] + predLeft[blkY + 1] + 2) >> 2; + int c6 = (predLeft[blkY + 1] + predLeft[blkY + 2] + 1) >> 1; + int c7 = (predLeft[blkY] + 2 * predLeft[blkY + 1] + predLeft[blkY + 2] + 2) >> 2; + int c8 = (predLeft[blkY + 2] + predLeft[blkY + 3] + 1) >> 1; + int c9 = (predLeft[blkY + 1] + 2 * predLeft[blkY + 2] + predLeft[blkY + 3] + 2) >> 2; + + resi[0] = (byte) c0; + resi[1] = (byte) c1; + resi[2] = (byte) c2; + resi[3] = (byte) c3; + resi[4] = (byte) c4; + resi[5] = (byte) c5; + resi[6] = (byte) c0; + resi[7] = (byte) c1; + resi[8] = (byte) c6; + resi[9] = (byte) c7; + resi[10] = (byte) c4; + resi[11] = (byte) c5; + resi[12] = (byte) c8; + resi[13] = (byte) c9; + resi[14] = (byte) c6; + resi[15] = (byte) c7; + + return hasTop && hasLeft; + } + + public static void predictVerticalLeft(int[] residual, boolean topAvailable, boolean topRightAvailable, + byte[] topLine, int mbOffX, int blkX, int blkY, byte[] pixOut) { + + int to = mbOffX + blkX; + int tr0 = topLine[to + 3], tr1 = topLine[to + 3], tr2 = topLine[to + 3]; + if (topRightAvailable) { + tr0 = topLine[to + 4]; + tr1 = topLine[to + 5]; + tr2 = topLine[to + 6]; + } + + int c0 = ((topLine[to] + topLine[to + 1] + 1) >> 1); + int c1 = ((topLine[to + 1] + topLine[to + 2] + 1) >> 1); + int c2 = ((topLine[to + 2] + topLine[to + 3] + 1) >> 1); + int c3 = ((topLine[to + 3] + tr0 + 1) >> 1); + int c4 = ((tr0 + tr1 + 1) >> 1); + int c5 = ((topLine[to] + 2 * topLine[to + 1] + topLine[to + 2] + 2) >> 2); + int c6 = ((topLine[to + 1] + 2 * topLine[to + 2] + topLine[to + 3] + 2) >> 2); + int c7 = ((topLine[to + 2] + 2 * topLine[to + 3] + tr0 + 2) >> 2); + int c8 = ((topLine[to + 3] + 2 * tr0 + tr1 + 2) >> 2); + int c9 = ((tr0 + 2 * tr1 + tr2 + 2) >> 2); + + int off = (blkY << 4) + blkX; + pixOut[off] = (byte) clip(residual[0] + c0, -128, 127); + pixOut[off + 1] = (byte) clip(residual[1] + c1, -128, 127); + pixOut[off + 2] = (byte) clip(residual[2] + c2, -128, 127); + pixOut[off + 3] = (byte) clip(residual[3] + c3, -128, 127); + + pixOut[off + 16] = (byte) clip(residual[4] + c5, -128, 127); + pixOut[off + 17] = (byte) clip(residual[5] + c6, -128, 127); + pixOut[off + 18] = (byte) clip(residual[6] + c7, -128, 127); + pixOut[off + 19] = (byte) clip(residual[7] + c8, -128, 127); + + pixOut[off + 32] = (byte) clip(residual[8] + c1, -128, 127); + pixOut[off + 33] = (byte) clip(residual[9] + c2, -128, 127); + pixOut[off + 34] = (byte) clip(residual[10] + c3, -128, 127); + pixOut[off + 35] = (byte) clip(residual[11] + c4, -128, 127); + + pixOut[off + 48] = (byte) clip(residual[12] + c6, -128, 127); + pixOut[off + 49] = (byte) clip(residual[13] + c7, -128, 127); + pixOut[off + 50] = (byte) clip(residual[14] + c8, -128, 127); + pixOut[off + 51] = (byte) clip(residual[15] + c9, -128, 127); + } + + private static boolean lumaPredVerticalLeft(boolean hasLeft, boolean hasTop, boolean hasTr, byte[] predLeft, + byte[] predTop, byte predTopLeft, int blkX, int blkY, byte[] resi) { + int to = blkX; + int tr0 = predTop[to + 3], tr1 = predTop[to + 3], tr2 = predTop[to + 3]; + if (hasTr) { + tr0 = predTop[to + 4]; + tr1 = predTop[to + 5]; + tr2 = predTop[to + 6]; + } + + int c0 = ((predTop[to] + predTop[to + 1] + 1) >> 1); + int c1 = ((predTop[to + 1] + predTop[to + 2] + 1) >> 1); + int c2 = ((predTop[to + 2] + predTop[to + 3] + 1) >> 1); + int c3 = ((predTop[to + 3] + tr0 + 1) >> 1); + int c4 = ((tr0 + tr1 + 1) >> 1); + int c5 = ((predTop[to] + 2 * predTop[to + 1] + predTop[to + 2] + 2) >> 2); + int c6 = ((predTop[to + 1] + 2 * predTop[to + 2] + predTop[to + 3] + 2) >> 2); + int c7 = ((predTop[to + 2] + 2 * predTop[to + 3] + tr0 + 2) >> 2); + int c8 = ((predTop[to + 3] + 2 * tr0 + tr1 + 2) >> 2); + int c9 = ((tr0 + 2 * tr1 + tr2 + 2) >> 2); + + resi[0] = (byte) c0; + resi[1] = (byte) c1; + resi[2] = (byte) c2; + resi[3] = (byte) c3; + resi[4] = (byte) c5; + resi[5] = (byte) c6; + resi[6] = (byte) c7; + resi[7] = (byte) c8; + resi[8] = (byte) c1; + resi[9] = (byte) c2; + resi[10] = (byte) c3; + resi[11] = (byte) c4; + resi[12] = (byte) c6; + resi[13] = (byte) c7; + resi[14] = (byte) c8; + resi[15] = (byte) c9; + + return hasTop; + } + + public static void predictHorizontalUp(int[] residual, boolean leftAvailable, byte[] leftRow, int mbOffX, int blkX, + int blkY, byte[] pixOut) { + + int c0 = ((leftRow[blkY] + leftRow[blkY + 1] + 1) >> 1); + int c1 = ((leftRow[blkY] + (leftRow[blkY + 1] << 1) + leftRow[blkY + 2] + 2) >> 2); + int c2 = ((leftRow[blkY + 1] + leftRow[blkY + 2] + 1) >> 1); + int c3 = ((leftRow[blkY + 1] + (leftRow[blkY + 2] << 1) + leftRow[blkY + 3] + 2) >> 2); + int c4 = ((leftRow[blkY + 2] + leftRow[blkY + 3] + 1) >> 1); + int c5 = ((leftRow[blkY + 2] + (leftRow[blkY + 3] << 1) + leftRow[blkY + 3] + 2) >> 2); + int c6 = leftRow[blkY + 3]; + + int off = (blkY << 4) + blkX; + pixOut[off] = (byte) clip(residual[0] + c0, -128, 127); + pixOut[off + 1] = (byte) clip(residual[1] + c1, -128, 127); + pixOut[off + 2] = (byte) clip(residual[2] + c2, -128, 127); + pixOut[off + 3] = (byte) clip(residual[3] + c3, -128, 127); + + pixOut[off + 16] = (byte) clip(residual[4] + c2, -128, 127); + pixOut[off + 17] = (byte) clip(residual[5] + c3, -128, 127); + pixOut[off + 18] = (byte) clip(residual[6] + c4, -128, 127); + pixOut[off + 19] = (byte) clip(residual[7] + c5, -128, 127); + + pixOut[off + 32] = (byte) clip(residual[8] + c4, -128, 127); + pixOut[off + 33] = (byte) clip(residual[9] + c5, -128, 127); + pixOut[off + 34] = (byte) clip(residual[10] + c6, -128, 127); + pixOut[off + 35] = (byte) clip(residual[11] + c6, -128, 127); + + pixOut[off + 48] = (byte) clip(residual[12] + c6, -128, 127); + pixOut[off + 49] = (byte) clip(residual[13] + c6, -128, 127); + pixOut[off + 50] = (byte) clip(residual[14] + c6, -128, 127); + pixOut[off + 51] = (byte) clip(residual[15] + c6, -128, 127); + } + + private static boolean lumaPredHorizontalUp(boolean hasLeft, boolean hasTop, byte[] predLeft, byte[] predTop, + byte predTopLeft, int blkX, int blkY, byte[] resi) { + int c0 = ((predLeft[blkY] + predLeft[blkY + 1] + 1) >> 1); + int c1 = ((predLeft[blkY] + (predLeft[blkY + 1] << 1) + predLeft[blkY + 2] + 2) >> 2); + int c2 = ((predLeft[blkY + 1] + predLeft[blkY + 2] + 1) >> 1); + int c3 = ((predLeft[blkY + 1] + (predLeft[blkY + 2] << 1) + predLeft[blkY + 3] + 2) >> 2); + int c4 = ((predLeft[blkY + 2] + predLeft[blkY + 3] + 1) >> 1); + int c5 = ((predLeft[blkY + 2] + (predLeft[blkY + 3] << 1) + predLeft[blkY + 3] + 2) >> 2); + int c6 = predLeft[blkY + 3]; + + resi[0] = (byte) c0; + resi[1] = (byte) c1; + resi[2] = (byte) c2; + resi[3] = (byte) c3; + resi[4] = (byte) c2; + resi[5] = (byte) c3; + resi[6] = (byte) c4; + resi[7] = (byte) c5; + resi[8] = (byte) c4; + resi[9] = (byte) c5; + resi[10] = (byte) c6; + resi[11] = (byte) c6; + resi[12] = (byte) c6; + resi[13] = (byte) c6; + resi[14] = (byte) c6; + resi[15] = (byte) c6; + + return hasLeft; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/Intra8x8PredictionBuilder.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/Intra8x8PredictionBuilder.java new file mode 100644 index 0000000..c5abf1f --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/Intra8x8PredictionBuilder.java @@ -0,0 +1,428 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import static org.monte.media.impl.jcodec.common.Preconditions.checkState; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Builds intra prediction for intra 8x8 coded macroblocks + * + * @author The JCodec project + */ +public class Intra8x8PredictionBuilder { + + byte[] topBuf; + byte[] leftBuf; + byte[] genBuf; + + public Intra8x8PredictionBuilder() { + this.topBuf = new byte[16]; + this.leftBuf = new byte[8]; + this.genBuf = new byte[24]; + } + + public void predictWithMode(int mode, int[] residual, boolean leftAvailable, boolean topAvailable, + boolean topLeftAvailable, boolean topRightAvailable, byte[] leftRow, byte[] topLine, byte[] topLeft, + int mbOffX, int blkX, int blkY, byte[] pixOut) { + switch (mode) { + case 0: + checkState(topAvailable, ""); + predictVertical(residual, topLeftAvailable, topRightAvailable, topLeft, topLine, mbOffX, blkX, blkY, pixOut); + break; + case 1: + checkState(leftAvailable, ""); + predictHorizontal(residual, topLeftAvailable, topLeft, leftRow, mbOffX, blkX, blkY, pixOut); + break; + case 2: + predictDC(residual, topLeftAvailable, topRightAvailable, leftAvailable, topAvailable, topLeft, leftRow, + topLine, mbOffX, blkX, blkY, pixOut); + break; + case 3: + checkState(topAvailable, ""); + predictDiagonalDownLeft(residual, topLeftAvailable, topAvailable, topRightAvailable, topLeft, topLine, + mbOffX, blkX, blkY, pixOut); + break; + case 4: + checkState(topAvailable && leftAvailable && topLeftAvailable, ""); + predictDiagonalDownRight(residual, topRightAvailable, topLeft, leftRow, topLine, mbOffX, blkX, blkY, pixOut); + break; + case 5: + checkState(topAvailable && leftAvailable && topLeftAvailable, ""); + predictVerticalRight(residual, topRightAvailable, topLeft, leftRow, topLine, mbOffX, blkX, blkY, pixOut); + break; + case 6: + checkState(topAvailable && leftAvailable && topLeftAvailable, ""); + predictHorizontalDown(residual, topRightAvailable, topLeft, leftRow, topLine, mbOffX, blkX, blkY, pixOut); + break; + case 7: + checkState(topAvailable, ""); + predictVerticalLeft(residual, topLeftAvailable, topRightAvailable, topLeft, topLine, mbOffX, blkX, blkY, + pixOut); + break; + case 8: + checkState(leftAvailable, ""); + predictHorizontalUp(residual, topLeftAvailable, topLeft, leftRow, mbOffX, blkX, blkY, pixOut); + break; + } + + int oo1 = mbOffX + blkX; + int off1 = (blkY << 4) + blkX + 7; + + topLeft[blkY >> 2] = topLine[oo1 + 7]; + + for (int i = 0; i < 8; i++) + leftRow[blkY + i] = pixOut[off1 + (i << 4)]; + + int off2 = (blkY << 4) + blkX + 112; + for (int i = 0; i < 8; i++) + topLine[oo1 + i] = pixOut[off2 + i]; + + topLeft[(blkY >> 2) + 1] = leftRow[blkY + 3]; + } + + private void interpolateTop(boolean topLeftAvailable, boolean topRightAvailable, byte[] topLeft, byte[] topLine, + int blkX, int blkY, byte[] out) { + int a = topLeftAvailable ? topLeft[blkY >> 2] : topLine[blkX]; + + out[0] = (byte) ((a + (topLine[blkX] << 1) + topLine[blkX + 1] + 2) >> 2); + int i; + for (i = 1; i < 7; i++) + out[i] = (byte) ((topLine[blkX + i - 1] + (topLine[blkX + i] << 1) + topLine[blkX + i + 1] + 2) >> 2); + + if (topRightAvailable) { + for (; i < 15; i++) + out[i] = (byte) ((topLine[blkX + i - 1] + (topLine[blkX + i] << 1) + topLine[blkX + i + 1] + 2) >> 2); + out[15] = (byte) ((topLine[blkX + 14] + (topLine[blkX + 15] << 1) + topLine[blkX + 15] + 2) >> 2); + } else { + out[7] = (byte) ((topLine[blkX + 6] + (topLine[blkX + 7] << 1) + topLine[blkX + 7] + 2) >> 2); + for (i = 8; i < 16; i++) + out[i] = topLine[blkX + 7]; + } + } + + private void interpolateLeft(boolean topLeftAvailable, byte[] topLeft, byte[] leftRow, int blkY, byte[] out) { + int a = topLeftAvailable ? topLeft[blkY >> 2] : leftRow[0]; + + out[0] = (byte) ((a + (leftRow[blkY] << 1) + leftRow[blkY + 1] + 2) >> 2); + for (int i = 1; i < 7; i++) + out[i] = (byte) ((leftRow[blkY + i - 1] + (leftRow[blkY + i] << 1) + leftRow[blkY + i + 1] + 2) >> 2); + + out[7] = (byte) ((leftRow[blkY + 6] + (leftRow[blkY + 7] << 1) + leftRow[blkY + 7] + 2) >> 2); + } + + private int interpolateTopLeft(boolean topAvailable, boolean leftAvailable, byte[] topLeft, byte[] topLine, + byte[] leftRow, int mbOffX, int blkX, int blkY) { + int a = topLeft[blkY >> 2]; + int b = topAvailable ? topLine[mbOffX + blkX] : a; + int c = leftAvailable ? leftRow[blkY] : a; + int aa = a << 1; + + return (aa + b + c + 2) >> 2; + } + + public void copyAdd(byte[] pred, int srcOff, int[] residual, int pixOff, int rOff, byte[] out) { + out[pixOff] = (byte) clip(residual[rOff] + pred[srcOff], -128, 127); + out[pixOff + 1] = (byte) clip(residual[rOff + 1] + pred[srcOff + 1], -128, 127); + out[pixOff + 2] = (byte) clip(residual[rOff + 2] + pred[srcOff + 2], -128, 127); + out[pixOff + 3] = (byte) clip(residual[rOff + 3] + pred[srcOff + 3], -128, 127); + out[pixOff + 4] = (byte) clip(residual[rOff + 4] + pred[srcOff + 4], -128, 127); + out[pixOff + 5] = (byte) clip(residual[rOff + 5] + pred[srcOff + 5], -128, 127); + out[pixOff + 6] = (byte) clip(residual[rOff + 6] + pred[srcOff + 6], -128, 127); + out[pixOff + 7] = (byte) clip(residual[rOff + 7] + pred[srcOff + 7], -128, 127); + } + + public void fillAdd(int[] residual, int pixOff, int val, byte[] pixOut) { + int rOff = 0; + for (int i = 0; i < 8; i++) { + pixOut[pixOff] = (byte) clip(residual[rOff] + val, -128, 127); + pixOut[pixOff + 1] = (byte) clip(residual[rOff + 1] + val, -128, 127); + pixOut[pixOff + 2] = (byte) clip(residual[rOff + 2] + val, -128, 127); + pixOut[pixOff + 3] = (byte) clip(residual[rOff + 3] + val, -128, 127); + pixOut[pixOff + 4] = (byte) clip(residual[rOff + 4] + val, -128, 127); + pixOut[pixOff + 5] = (byte) clip(residual[rOff + 5] + val, -128, 127); + pixOut[pixOff + 6] = (byte) clip(residual[rOff + 6] + val, -128, 127); + pixOut[pixOff + 7] = (byte) clip(residual[rOff + 7] + val, -128, 127); + pixOff += 16; + rOff += 8; + } + } + + public void predictVertical(int[] residual, boolean topLeftAvailable, boolean topRightAvailable, byte[] topLeft, + byte[] topLine, int mbOffX, int blkX, int blkY, byte[] pixOut) { + interpolateTop(topLeftAvailable, topRightAvailable, topLeft, topLine, mbOffX + blkX, blkY, topBuf); + int pixOff = (blkY << 4) + blkX; + int rOff = 0; + for (int i = 0; i < 8; i++) { + pixOut[pixOff] = (byte) clip(residual[rOff] + topBuf[0], -128, 127); + pixOut[pixOff + 1] = (byte) clip(residual[rOff + 1] + topBuf[1], -128, 127); + pixOut[pixOff + 2] = (byte) clip(residual[rOff + 2] + topBuf[2], -128, 127); + pixOut[pixOff + 3] = (byte) clip(residual[rOff + 3] + topBuf[3], -128, 127); + pixOut[pixOff + 4] = (byte) clip(residual[rOff + 4] + topBuf[4], -128, 127); + pixOut[pixOff + 5] = (byte) clip(residual[rOff + 5] + topBuf[5], -128, 127); + pixOut[pixOff + 6] = (byte) clip(residual[rOff + 6] + topBuf[6], -128, 127); + pixOut[pixOff + 7] = (byte) clip(residual[rOff + 7] + topBuf[7], -128, 127); + pixOff += 16; + rOff += 8; + } + } + + public void predictHorizontal(int[] residual, boolean topLeftAvailable, byte[] topLeft, byte[] leftRow, int mbOffX, + int blkX, int blkY, byte[] pixOut) { + interpolateLeft(topLeftAvailable, topLeft, leftRow, blkY, leftBuf); + int pixOff = (blkY << 4) + blkX; + int rOff = 0; + for (int i = 0; i < 8; i++) { + pixOut[pixOff] = (byte) clip(residual[rOff] + leftBuf[i], -128, 127); + pixOut[pixOff + 1] = (byte) clip(residual[rOff + 1] + leftBuf[i], -128, 127); + pixOut[pixOff + 2] = (byte) clip(residual[rOff + 2] + leftBuf[i], -128, 127); + pixOut[pixOff + 3] = (byte) clip(residual[rOff + 3] + leftBuf[i], -128, 127); + pixOut[pixOff + 4] = (byte) clip(residual[rOff + 4] + leftBuf[i], -128, 127); + pixOut[pixOff + 5] = (byte) clip(residual[rOff + 5] + leftBuf[i], -128, 127); + pixOut[pixOff + 6] = (byte) clip(residual[rOff + 6] + leftBuf[i], -128, 127); + pixOut[pixOff + 7] = (byte) clip(residual[rOff + 7] + leftBuf[i], -128, 127); + pixOff += 16; + rOff += 8; + } + } + + public void predictDC(int[] residual, boolean topLeftAvailable, boolean topRightAvailable, boolean leftAvailable, + boolean topAvailable, byte[] topLeft, byte[] leftRow, byte[] topLine, int mbOffX, int blkX, int blkY, + byte[] pixOut) { + if (topAvailable && leftAvailable) { + interpolateTop(topLeftAvailable, topRightAvailable, topLeft, topLine, mbOffX + blkX, blkY, topBuf); + interpolateLeft(topLeftAvailable, topLeft, leftRow, blkY, leftBuf); + int sum1 = topBuf[0] + topBuf[1] + topBuf[2] + topBuf[3]; + int sum2 = topBuf[4] + topBuf[5] + topBuf[6] + topBuf[7]; + int sum3 = leftBuf[0] + leftBuf[1] + leftBuf[2] + leftBuf[3]; + int sum4 = leftBuf[4] + leftBuf[5] + leftBuf[6] + leftBuf[7]; + fillAdd(residual, (blkY << 4) + blkX, (sum1 + sum2 + sum3 + sum4 + 8) >> 4, pixOut); + } else if (leftAvailable) { + interpolateLeft(topLeftAvailable, topLeft, leftRow, blkY, leftBuf); + int sum3 = leftBuf[0] + leftBuf[1] + leftBuf[2] + leftBuf[3]; + int sum4 = leftBuf[4] + leftBuf[5] + leftBuf[6] + leftBuf[7]; + fillAdd(residual, (blkY << 4) + blkX, (sum3 + sum4 + 4) >> 3, pixOut); + } else if (topAvailable) { + interpolateTop(topLeftAvailable, topRightAvailable, topLeft, topLine, mbOffX + blkX, blkY, topBuf); + int sum1 = topBuf[0] + topBuf[1] + topBuf[2] + topBuf[3]; + int sum2 = topBuf[4] + topBuf[5] + topBuf[6] + topBuf[7]; + fillAdd(residual, (blkY << 4) + blkX, (sum1 + sum2 + 4) >> 3, pixOut); + } else { + fillAdd(residual, (blkY << 4) + blkX, 0, pixOut); + } + } + + public void predictDiagonalDownLeft(int[] residual, boolean topLeftAvailable, boolean topAvailable, + boolean topRightAvailable, byte[] topLeft, byte[] topLine, int mbOffX, int blkX, int blkY, byte[] pixOut) { + interpolateTop(topLeftAvailable, topRightAvailable, topLeft, topLine, mbOffX + blkX, blkY, topBuf); + + genBuf[0] = (byte) ((topBuf[0] + topBuf[2] + ((topBuf[1]) << 1) + 2) >> 2); + genBuf[1] = (byte) ((topBuf[1] + topBuf[3] + ((topBuf[2]) << 1) + 2) >> 2); + genBuf[2] = (byte) ((topBuf[2] + topBuf[4] + ((topBuf[3]) << 1) + 2) >> 2); + genBuf[3] = (byte) ((topBuf[3] + topBuf[5] + ((topBuf[4]) << 1) + 2) >> 2); + genBuf[4] = (byte) ((topBuf[4] + topBuf[6] + ((topBuf[5]) << 1) + 2) >> 2); + genBuf[5] = (byte) ((topBuf[5] + topBuf[7] + ((topBuf[6]) << 1) + 2) >> 2); + genBuf[6] = (byte) ((topBuf[6] + topBuf[8] + ((topBuf[7]) << 1) + 2) >> 2); + genBuf[7] = (byte) ((topBuf[7] + topBuf[9] + ((topBuf[8]) << 1) + 2) >> 2); + genBuf[8] = (byte) ((topBuf[8] + topBuf[10] + ((topBuf[9]) << 1) + 2) >> 2); + genBuf[9] = (byte) ((topBuf[9] + topBuf[11] + ((topBuf[10]) << 1) + 2) >> 2); + genBuf[10] = (byte) ((topBuf[10] + topBuf[12] + ((topBuf[11]) << 1) + 2) >> 2); + genBuf[11] = (byte) ((topBuf[11] + topBuf[13] + ((topBuf[12]) << 1) + 2) >> 2); + genBuf[12] = (byte) ((topBuf[12] + topBuf[14] + ((topBuf[13]) << 1) + 2) >> 2); + genBuf[13] = (byte) ((topBuf[13] + topBuf[15] + ((topBuf[14]) << 1) + 2) >> 2); + genBuf[14] = (byte) ((topBuf[14] + topBuf[15] + ((topBuf[15]) << 1) + 2) >> 2); + + int off = (blkY << 4) + blkX; + copyAdd(genBuf, 0, residual, off, 0, pixOut); + copyAdd(genBuf, 1, residual, off + 16, 8, pixOut); + copyAdd(genBuf, 2, residual, off + 32, 16, pixOut); + copyAdd(genBuf, 3, residual, off + 48, 24, pixOut); + copyAdd(genBuf, 4, residual, off + 64, 32, pixOut); + copyAdd(genBuf, 5, residual, off + 80, 40, pixOut); + copyAdd(genBuf, 6, residual, off + 96, 48, pixOut); + copyAdd(genBuf, 7, residual, off + 112, 56, pixOut); + } + + public void predictDiagonalDownRight(int[] residual, boolean topRightAvailable, byte[] topLeft, byte[] leftRow, + byte[] topLine, int mbOffX, int blkX, int blkY, byte[] pixOut) { + interpolateTop(true, topRightAvailable, topLeft, topLine, mbOffX + blkX, blkY, topBuf); + interpolateLeft(true, topLeft, leftRow, blkY, leftBuf); + int tl = interpolateTopLeft(true, true, topLeft, topLine, leftRow, mbOffX, blkX, blkY); + + genBuf[0] = (byte) ((leftBuf[7] + leftBuf[5] + ((leftBuf[6]) << 1) + 2) >> 2); + genBuf[1] = (byte) ((leftBuf[6] + leftBuf[4] + ((leftBuf[5]) << 1) + 2) >> 2); + genBuf[2] = (byte) ((leftBuf[5] + leftBuf[3] + ((leftBuf[4]) << 1) + 2) >> 2); + genBuf[3] = (byte) ((leftBuf[4] + leftBuf[2] + ((leftBuf[3]) << 1) + 2) >> 2); + genBuf[4] = (byte) ((leftBuf[3] + leftBuf[1] + ((leftBuf[2]) << 1) + 2) >> 2); + genBuf[5] = (byte) ((leftBuf[2] + leftBuf[0] + ((leftBuf[1]) << 1) + 2) >> 2); + genBuf[6] = (byte) ((leftBuf[1] + tl + ((leftBuf[0]) << 1) + 2) >> 2); + genBuf[7] = (byte) ((leftBuf[0] + topBuf[0] + ((tl) << 1) + 2) >> 2); + genBuf[8] = (byte) ((tl + topBuf[1] + ((topBuf[0]) << 1) + 2) >> 2); + genBuf[9] = (byte) ((topBuf[0] + topBuf[2] + ((topBuf[1]) << 1) + 2) >> 2); + genBuf[10] = (byte) ((topBuf[1] + topBuf[3] + ((topBuf[2]) << 1) + 2) >> 2); + genBuf[11] = (byte) ((topBuf[2] + topBuf[4] + ((topBuf[3]) << 1) + 2) >> 2); + genBuf[12] = (byte) ((topBuf[3] + topBuf[5] + ((topBuf[4]) << 1) + 2) >> 2); + genBuf[13] = (byte) ((topBuf[4] + topBuf[6] + ((topBuf[5]) << 1) + 2) >> 2); + genBuf[14] = (byte) ((topBuf[5] + topBuf[7] + ((topBuf[6]) << 1) + 2) >> 2); + + int off = (blkY << 4) + blkX; + copyAdd(genBuf, 7, residual, off, 0, pixOut); + copyAdd(genBuf, 6, residual, off + 16, 8, pixOut); + copyAdd(genBuf, 5, residual, off + 32, 16, pixOut); + copyAdd(genBuf, 4, residual, off + 48, 24, pixOut); + copyAdd(genBuf, 3, residual, off + 64, 32, pixOut); + copyAdd(genBuf, 2, residual, off + 80, 40, pixOut); + copyAdd(genBuf, 1, residual, off + 96, 48, pixOut); + copyAdd(genBuf, 0, residual, off + 112, 56, pixOut); + } + + public void predictVerticalRight(int[] residual, boolean topRightAvailable, byte[] topLeft, byte[] leftRow, + byte[] topLine, int mbOffX, int blkX, int blkY, byte[] pixOut) { + interpolateTop(true, topRightAvailable, topLeft, topLine, mbOffX + blkX, blkY, topBuf); + interpolateLeft(true, topLeft, leftRow, blkY, leftBuf); + int tl = interpolateTopLeft(true, true, topLeft, topLine, leftRow, mbOffX, blkX, blkY); + + genBuf[0] = (byte) ((leftBuf[5] + leftBuf[3] + ((leftBuf[4]) << 1) + 2) >> 2); + genBuf[1] = (byte) ((leftBuf[3] + leftBuf[1] + ((leftBuf[2]) << 1) + 2) >> 2); + genBuf[2] = (byte) ((leftBuf[1] + tl + ((leftBuf[0]) << 1) + 2) >> 2); + genBuf[3] = (byte) ((tl + topBuf[0] + 1) >> 1); + genBuf[4] = (byte) ((topBuf[0] + topBuf[1] + 1) >> 1); + genBuf[5] = (byte) ((topBuf[1] + topBuf[2] + 1) >> 1); + genBuf[6] = (byte) ((topBuf[2] + topBuf[3] + 1) >> 1); + genBuf[7] = (byte) ((topBuf[3] + topBuf[4] + 1) >> 1); + genBuf[8] = (byte) ((topBuf[4] + topBuf[5] + 1) >> 1); + genBuf[9] = (byte) ((topBuf[5] + topBuf[6] + 1) >> 1); + genBuf[10] = (byte) ((topBuf[6] + topBuf[7] + 1) >> 1); + genBuf[11] = (byte) ((leftBuf[6] + leftBuf[4] + ((leftBuf[5]) << 1) + 2) >> 2); + genBuf[12] = (byte) ((leftBuf[4] + leftBuf[2] + ((leftBuf[3]) << 1) + 2) >> 2); + genBuf[13] = (byte) ((leftBuf[2] + leftBuf[0] + ((leftBuf[1]) << 1) + 2) >> 2); + genBuf[14] = (byte) ((leftBuf[0] + topBuf[0] + ((tl) << 1) + 2) >> 2); + genBuf[15] = (byte) ((tl + topBuf[1] + ((topBuf[0]) << 1) + 2) >> 2); + genBuf[16] = (byte) ((topBuf[0] + topBuf[2] + ((topBuf[1]) << 1) + 2) >> 2); + genBuf[17] = (byte) ((topBuf[1] + topBuf[3] + ((topBuf[2]) << 1) + 2) >> 2); + genBuf[18] = (byte) ((topBuf[2] + topBuf[4] + ((topBuf[3]) << 1) + 2) >> 2); + genBuf[19] = (byte) ((topBuf[3] + topBuf[5] + ((topBuf[4]) << 1) + 2) >> 2); + genBuf[20] = (byte) ((topBuf[4] + topBuf[6] + ((topBuf[5]) << 1) + 2) >> 2); + genBuf[21] = (byte) ((topBuf[5] + topBuf[7] + ((topBuf[6]) << 1) + 2) >> 2); + + int off = (blkY << 4) + blkX; + copyAdd(genBuf, 3, residual, off, 0, pixOut); + copyAdd(genBuf, 14, residual, off + 16, 8, pixOut); + copyAdd(genBuf, 2, residual, off + 32, 16, pixOut); + copyAdd(genBuf, 13, residual, off + 48, 24, pixOut); + copyAdd(genBuf, 1, residual, off + 64, 32, pixOut); + copyAdd(genBuf, 12, residual, off + 80, 40, pixOut); + copyAdd(genBuf, 0, residual, off + 96, 48, pixOut); + copyAdd(genBuf, 11, residual, off + 112, 56, pixOut); + } + + public void predictHorizontalDown(int[] residual, boolean topRightAvailable, byte[] topLeft, byte[] leftRow, + byte[] topLine, int mbOffX, int blkX, int blkY, byte[] pixOut) { + interpolateTop(true, topRightAvailable, topLeft, topLine, mbOffX + blkX, blkY, topBuf); + interpolateLeft(true, topLeft, leftRow, blkY, leftBuf); + int tl = interpolateTopLeft(true, true, topLeft, topLine, leftRow, mbOffX, blkX, blkY); + + genBuf[0] = (byte) ((leftBuf[7] + leftBuf[6] + 1) >> 1); + genBuf[1] = (byte) ((leftBuf[5] + leftBuf[7] + (leftBuf[6] << 1) + 2) >> 2); + genBuf[2] = (byte) ((leftBuf[6] + leftBuf[5] + 1) >> 1); + genBuf[3] = (byte) ((leftBuf[4] + leftBuf[6] + ((leftBuf[5]) << 1) + 2) >> 2); + genBuf[4] = (byte) ((leftBuf[5] + leftBuf[4] + 1) >> 1); + genBuf[5] = (byte) ((leftBuf[3] + leftBuf[5] + ((leftBuf[4]) << 1) + 2) >> 2); + genBuf[6] = (byte) ((leftBuf[4] + leftBuf[3] + 1) >> 1); + genBuf[7] = (byte) ((leftBuf[2] + leftBuf[4] + ((leftBuf[3]) << 1) + 2) >> 2); + genBuf[8] = (byte) ((leftBuf[3] + leftBuf[2] + 1) >> 1); + genBuf[9] = (byte) ((leftBuf[1] + leftBuf[3] + ((leftBuf[2]) << 1) + 2) >> 2); + genBuf[10] = (byte) ((leftBuf[2] + leftBuf[1] + 1) >> 1); + genBuf[11] = (byte) ((leftBuf[0] + leftBuf[2] + ((leftBuf[1]) << 1) + 2) >> 2); + genBuf[12] = (byte) ((leftBuf[1] + leftBuf[0] + 1) >> 1); + genBuf[13] = (byte) ((tl + leftBuf[1] + ((leftBuf[0]) << 1) + 2) >> 2); + genBuf[14] = (byte) ((leftBuf[0] + tl + 1) >> 1); + genBuf[15] = (byte) ((leftBuf[0] + topBuf[0] + ((tl) << 1) + 2) >> 2); + genBuf[16] = (byte) ((tl + topBuf[1] + ((topBuf[0]) << 1) + 2) >> 2); + genBuf[17] = (byte) ((topBuf[0] + topBuf[2] + ((topBuf[1]) << 1) + 2) >> 2); + genBuf[18] = (byte) ((topBuf[1] + topBuf[3] + ((topBuf[2]) << 1) + 2) >> 2); + genBuf[19] = (byte) ((topBuf[2] + topBuf[4] + ((topBuf[3]) << 1) + 2) >> 2); + genBuf[20] = (byte) ((topBuf[3] + topBuf[5] + ((topBuf[4]) << 1) + 2) >> 2); + genBuf[21] = (byte) ((topBuf[4] + topBuf[6] + ((topBuf[5]) << 1) + 2) >> 2); + + int off = (blkY << 4) + blkX; + copyAdd(genBuf, 14, residual, off, 0, pixOut); + copyAdd(genBuf, 12, residual, off + 16, 8, pixOut); + copyAdd(genBuf, 10, residual, off + 32, 16, pixOut); + copyAdd(genBuf, 8, residual, off + 48, 24, pixOut); + copyAdd(genBuf, 6, residual, off + 64, 32, pixOut); + copyAdd(genBuf, 4, residual, off + 80, 40, pixOut); + copyAdd(genBuf, 2, residual, off + 96, 48, pixOut); + copyAdd(genBuf, 0, residual, off + 112, 56, pixOut); + } + + public void predictVerticalLeft(int[] residual, boolean topLeftAvailable, boolean topRightAvailable, + byte[] topLeft, byte[] topLine, int mbOffX, int blkX, int blkY, byte[] pixOut) { + interpolateTop(topLeftAvailable, topRightAvailable, topLeft, topLine, mbOffX + blkX, blkY, topBuf); + + genBuf[0] = (byte) ((topBuf[0] + topBuf[1] + 1) >> 1); + genBuf[1] = (byte) ((topBuf[1] + topBuf[2] + 1) >> 1); + genBuf[2] = (byte) ((topBuf[2] + topBuf[3] + 1) >> 1); + genBuf[3] = (byte) ((topBuf[3] + topBuf[4] + 1) >> 1); + genBuf[4] = (byte) ((topBuf[4] + topBuf[5] + 1) >> 1); + genBuf[5] = (byte) ((topBuf[5] + topBuf[6] + 1) >> 1); + genBuf[6] = (byte) ((topBuf[6] + topBuf[7] + 1) >> 1); + genBuf[7] = (byte) ((topBuf[7] + topBuf[8] + 1) >> 1); + genBuf[8] = (byte) ((topBuf[8] + topBuf[9] + 1) >> 1); + genBuf[9] = (byte) ((topBuf[9] + topBuf[10] + 1) >> 1); + genBuf[10] = (byte) ((topBuf[10] + topBuf[11] + 1) >> 1); + genBuf[11] = (byte) ((topBuf[0] + topBuf[2] + ((topBuf[1]) << 1) + 2) >> 2); + genBuf[12] = (byte) ((topBuf[1] + topBuf[3] + ((topBuf[2]) << 1) + 2) >> 2); + genBuf[13] = (byte) ((topBuf[2] + topBuf[4] + ((topBuf[3]) << 1) + 2) >> 2); + genBuf[14] = (byte) ((topBuf[3] + topBuf[5] + ((topBuf[4]) << 1) + 2) >> 2); + genBuf[15] = (byte) ((topBuf[4] + topBuf[6] + ((topBuf[5]) << 1) + 2) >> 2); + genBuf[16] = (byte) ((topBuf[5] + topBuf[7] + ((topBuf[6]) << 1) + 2) >> 2); + genBuf[17] = (byte) ((topBuf[6] + topBuf[8] + ((topBuf[7]) << 1) + 2) >> 2); + genBuf[18] = (byte) ((topBuf[7] + topBuf[9] + ((topBuf[8]) << 1) + 2) >> 2); + genBuf[19] = (byte) ((topBuf[8] + topBuf[10] + ((topBuf[9]) << 1) + 2) >> 2); + genBuf[20] = (byte) ((topBuf[9] + topBuf[11] + ((topBuf[10]) << 1) + 2) >> 2); + genBuf[21] = (byte) ((topBuf[10] + topBuf[12] + ((topBuf[11]) << 1) + 2) >> 2); + + int off = (blkY << 4) + blkX; + copyAdd(genBuf, 0, residual, off, 0, pixOut); + copyAdd(genBuf, 11, residual, off + 16, 8, pixOut); + copyAdd(genBuf, 1, residual, off + 32, 16, pixOut); + copyAdd(genBuf, 12, residual, off + 48, 24, pixOut); + copyAdd(genBuf, 2, residual, off + 64, 32, pixOut); + copyAdd(genBuf, 13, residual, off + 80, 40, pixOut); + copyAdd(genBuf, 3, residual, off + 96, 48, pixOut); + copyAdd(genBuf, 14, residual, off + 112, 56, pixOut); + } + + public void predictHorizontalUp(int[] residual, boolean topLeftAvailable, byte[] topLeft, byte[] leftRow, + int mbOffX, int blkX, int blkY, byte[] pixOut) { + interpolateLeft(topLeftAvailable, topLeft, leftRow, blkY, leftBuf); + + genBuf[0] = (byte) ((leftBuf[0] + leftBuf[1] + 1) >> 1); + genBuf[1] = (byte) ((leftBuf[2] + leftBuf[0] + ((leftBuf[1]) << 1) + 2) >> 2); + genBuf[2] = (byte) ((leftBuf[1] + leftBuf[2] + 1) >> 1); + genBuf[3] = (byte) ((leftBuf[3] + leftBuf[1] + ((leftBuf[2]) << 1) + 2) >> 2); + genBuf[4] = (byte) ((leftBuf[2] + leftBuf[3] + 1) >> 1); + genBuf[5] = (byte) ((leftBuf[4] + leftBuf[2] + ((leftBuf[3]) << 1) + 2) >> 2); + genBuf[6] = (byte) ((leftBuf[3] + leftBuf[4] + 1) >> 1); + genBuf[7] = (byte) ((leftBuf[5] + leftBuf[3] + ((leftBuf[4]) << 1) + 2) >> 2); + genBuf[8] = (byte) ((leftBuf[4] + leftBuf[5] + 1) >> 1); + genBuf[9] = (byte) ((leftBuf[6] + leftBuf[4] + ((leftBuf[5]) << 1) + 2) >> 2); + genBuf[10] = (byte) ((leftBuf[5] + leftBuf[6] + 1) >> 1); + genBuf[11] = (byte) ((leftBuf[7] + leftBuf[5] + ((leftBuf[6]) << 1) + 2) >> 2); + genBuf[12] = (byte) ((leftBuf[6] + leftBuf[7] + 1) >> 1); + genBuf[13] = (byte) ((leftBuf[6] + leftBuf[7] + ((leftBuf[7]) << 1) + 2) >> 2); + genBuf[14] = genBuf[15] = genBuf[16] = genBuf[17] = genBuf[18] = genBuf[19] = genBuf[20] = genBuf[21] = leftBuf[7]; + + int off = (blkY << 4) + blkX; + copyAdd(genBuf, 0, residual, off, 0, pixOut); + copyAdd(genBuf, 2, residual, off + 16, 8, pixOut); + copyAdd(genBuf, 4, residual, off + 32, 16, pixOut); + copyAdd(genBuf, 6, residual, off + 48, 24, pixOut); + copyAdd(genBuf, 8, residual, off + 64, 32, pixOut); + copyAdd(genBuf, 10, residual, off + 80, 40, pixOut); + copyAdd(genBuf, 12, residual, off + 96, 48, pixOut); + copyAdd(genBuf, 14, residual, off + 112, 56, pixOut); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlock.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlock.java new file mode 100644 index 0000000..71481fd --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlock.java @@ -0,0 +1,222 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred; +import org.monte.media.impl.jcodec.codecs.h264.H264Utils; +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; +import org.monte.media.impl.jcodec.common.model.ColorSpace; + +import java.util.Arrays; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * A codec macroblock + * + * @author The JCodec project + */ +public class MBlock { + + public int chromaPredictionMode; + public int mbQPDelta; + public int[] dc; + public int[][][] ac; + public boolean transform8x8Used; + public int[] lumaModes; + public int[] dc1; + public int[] dc2; + public int _cbp; + public int mbType; + public MBType curMbType; + public PB16x16 pb16x16; + public PB168x168 pb168x168; + public PB8x8 pb8x8; + public IPCM ipcm; + public int mbIdx; + public boolean fieldDecoding; + public MBType prevMbType; + public int luma16x16Mode; + public H264Utils.MvList x; + public PartPred[] partPreds; + + public boolean skipped; + // Number of coefficients in AC blocks, stored in 8x8 encoding order: 0 1 4 5 2 3 6 7 8 9 12 13 10 11 14 15 + public int[] nCoeff; + + public MBlock(ColorSpace chromaFormat) { + this.pb8x8 = new PB8x8(); + this.pb16x16 = new PB16x16(); + this.pb168x168 = new PB168x168(); + + dc = new int[16]; + ac = new int[][][]{new int[16][64], new int[4][16], new int[4][16]}; + lumaModes = new int[16]; + nCoeff = new int[16]; + dc1 = new int[(16 >> chromaFormat.compWidth[1]) >> chromaFormat.compHeight[1]]; + dc2 = new int[(16 >> chromaFormat.compWidth[2]) >> chromaFormat.compHeight[2]]; + ipcm = new IPCM(chromaFormat); + x = new H264Utils.MvList(16); + partPreds = new PartPred[4]; + } + + public int cbpLuma() { + return _cbp & 0xf; + } + + public int cbpChroma() { + return _cbp >> 4; + } + + public void cbp(int cbpLuma, int cbpChroma) { + _cbp = (cbpLuma & 0xf) | (cbpChroma << 4); + } + + static class PB16x16 { + public int[] refIdx; + public int[] mvdX; + public int[] mvdY; + + public PB16x16() { + this.refIdx = new int[2]; + this.mvdX = new int[2]; + this.mvdY = new int[2]; + } + + public void clean() { + refIdx[0] = refIdx[1] = 0; + mvdX[0] = mvdX[1] = 0; + mvdY[0] = mvdY[1] = 0; + } + } + + static class PB168x168 { + public int[] refIdx1; + public int[] refIdx2; + public int[] mvdX1; + public int[] mvdY1; + public int[] mvdX2; + public int[] mvdY2; + + public PB168x168() { + this.refIdx1 = new int[2]; + this.refIdx2 = new int[2]; + this.mvdX1 = new int[2]; + this.mvdY1 = new int[2]; + this.mvdX2 = new int[2]; + this.mvdY2 = new int[2]; + } + + public void clean() { + refIdx1[0] = refIdx1[1] = 0; + refIdx2[0] = refIdx2[1] = 0; + + mvdX1[0] = mvdX1[1] = 0; + mvdY1[0] = mvdY1[1] = 0; + mvdX2[0] = mvdX2[1] = 0; + mvdY2[0] = mvdY2[1] = 0; + } + } + + static class PB8x8 { + public int[][] refIdx; + public int[] subMbTypes; + public int[][] mvdX1; + public int[][] mvdY1; + public int[][] mvdX2; + public int[][] mvdY2; + public int[][] mvdX3; + public int[][] mvdY3; + public int[][] mvdX4; + public int[][] mvdY4; + + public PB8x8() { + this.refIdx = new int[2][4]; + this.subMbTypes = new int[4]; + this.mvdX1 = new int[2][4]; + this.mvdY1 = new int[2][4]; + this.mvdX2 = new int[2][4]; + this.mvdY2 = new int[2][4]; + this.mvdX3 = new int[2][4]; + this.mvdY3 = new int[2][4]; + this.mvdX4 = new int[2][4]; + this.mvdY4 = new int[2][4]; + } + + public void clean() { + mvdX1[0][0] = mvdX1[0][1] = mvdX1[0][2] = mvdX1[0][3] = 0; + mvdX2[0][0] = mvdX2[0][1] = mvdX2[0][2] = mvdX2[0][3] = 0; + mvdX3[0][0] = mvdX3[0][1] = mvdX3[0][2] = mvdX3[0][3] = 0; + mvdX4[0][0] = mvdX4[0][1] = mvdX4[0][2] = mvdX4[0][3] = 0; + + mvdY1[0][0] = mvdY1[0][1] = mvdY1[0][2] = mvdY1[0][3] = 0; + mvdY2[0][0] = mvdY2[0][1] = mvdY2[0][2] = mvdY2[0][3] = 0; + mvdY3[0][0] = mvdY3[0][1] = mvdY3[0][2] = mvdY3[0][3] = 0; + mvdY4[0][0] = mvdY4[0][1] = mvdY4[0][2] = mvdY4[0][3] = 0; + + mvdX1[1][0] = mvdX1[1][1] = mvdX1[1][2] = mvdX1[1][3] = 0; + mvdX2[1][0] = mvdX2[1][1] = mvdX2[1][2] = mvdX2[1][3] = 0; + mvdX3[1][0] = mvdX3[1][1] = mvdX3[1][2] = mvdX3[1][3] = 0; + mvdX4[1][0] = mvdX4[1][1] = mvdX4[1][2] = mvdX4[1][3] = 0; + + mvdY1[1][0] = mvdY1[1][1] = mvdY1[1][2] = mvdY1[1][3] = 0; + mvdY2[1][0] = mvdY2[1][1] = mvdY2[1][2] = mvdY2[1][3] = 0; + mvdY3[1][0] = mvdY3[1][1] = mvdY3[1][2] = mvdY3[1][3] = 0; + mvdY4[1][0] = mvdY4[1][1] = mvdY4[1][2] = mvdY4[1][3] = 0; + + subMbTypes[0] = subMbTypes[1] = subMbTypes[2] = subMbTypes[3] = 0; + refIdx[0][0] = refIdx[0][1] = refIdx[0][2] = refIdx[0][3] = 0; + refIdx[1][0] = refIdx[1][1] = refIdx[1][2] = refIdx[1][3] = 0; + } + } + + static class IPCM { + + public int[] samplesLuma; + public int[] samplesChroma; + + public IPCM(ColorSpace chromaFormat) { + this.samplesLuma = new int[256]; + int MbWidthC = 16 >> chromaFormat.compWidth[1]; + int MbHeightC = 16 >> chromaFormat.compHeight[1]; + + samplesChroma = new int[2 * MbWidthC * MbHeightC]; + } + + public void clean() { + Arrays.fill(samplesLuma, 0); + Arrays.fill(samplesChroma, 0); + } + } + + public void clear() { + chromaPredictionMode = 0; + mbQPDelta = 0; + Arrays.fill(dc, 0); + for (int i = 0; i < ac.length; i++) { + int[][] aci = ac[i]; + for (int j = 0; j < aci.length; j++) { + Arrays.fill(aci[j], 0); + } + } + transform8x8Used = false; + Arrays.fill(lumaModes, 0); + Arrays.fill(dc1, 0); + Arrays.fill(dc2, 0); + Arrays.fill(nCoeff, 0); + _cbp = 0; + mbType = 0; + pb16x16.clean(); + pb168x168.clean(); + pb8x8.clean(); + if (curMbType == MBType.I_PCM) + ipcm.clean(); + mbIdx = 0; + fieldDecoding = false; + prevMbType = null; + luma16x16Mode = 0; + skipped = false; + curMbType = null; + x.clear(); + partPreds[0] = partPreds[1] = partPreds[2] = partPreds[3] = null; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderBDirect.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderBDirect.java new file mode 100644 index 0000000..b20ad0b --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderBDirect.java @@ -0,0 +1,342 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const; +import org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred; +import org.monte.media.impl.jcodec.codecs.h264.H264Utils.MvList; +import org.monte.media.impl.jcodec.codecs.h264.decode.aso.Mapper; +import org.monte.media.impl.jcodec.codecs.h264.io.model.Frame; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.common.logging.Logger; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK8x8_BLOCKS; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK_4x4_MB_OFF_LUMA; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK_8x8_IND; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK_8x8_MB_OFF_LUMA; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK_DISP_MAP; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.COMP_BLOCK_4x4_LUT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.COMP_BLOCK_8x8_LUT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.COMP_POS_4x4_LUT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.COMP_POS_8x8_LUT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.Bi; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.L0; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.L1; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.identityMapping4; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvRef; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvX; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvY; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.packMv; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.calcMVPredictionMedian; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.collectPredictors; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.debugPrint; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.mergeResidual; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.saveMvs; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.savePrediction8x8; +import static org.monte.media.impl.jcodec.codecs.h264.decode.PredictionMerger.mergePrediction; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.abs; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * A decoder for B direct macroblocks + * + * @author The JCodec project + */ +public class MBlockDecoderBDirect extends MBlockDecoderBase { + private Mapper mapper; + + public MBlockDecoderBDirect(Mapper mapper, SliceHeader sh, DeblockerInput di, int poc, DecoderState decoderState) { + super(sh, di, poc, decoderState); + this.mapper = mapper; + } + + public void decode(MBlock mBlock, Picture mb, Frame[][] references) { + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + boolean lAvb = mapper.leftAvailable(mBlock.mbIdx); + boolean tAvb = mapper.topAvailable(mBlock.mbIdx); + int mbAddr = mapper.getAddress(mBlock.mbIdx); + boolean tlAvb = mapper.topLeftAvailable(mBlock.mbIdx); + boolean trAvb = mapper.topRightAvailable(mBlock.mbIdx); + + predictBDirect(references, mbX, mbY, lAvb, tAvb, tlAvb, trAvb, mBlock.x, mBlock.partPreds, mb, identityMapping4); + + predictChromaInter(references, mBlock.x, mbX << 3, mbY << 3, 1, mb, mBlock.partPreds); + predictChromaInter(references, mBlock.x, mbX << 3, mbY << 3, 2, mb, mBlock.partPreds); + + if (mBlock.cbpLuma() > 0 || mBlock.cbpChroma() > 0) { + s.qp = (s.qp + mBlock.mbQPDelta + 52) % 52; + } + di.mbQps[0][mbAddr] = s.qp; + + residualLuma(mBlock, lAvb, tAvb, mbX, mbY); + + savePrediction8x8(s, mbX, mBlock.x); + saveMvs(di, mBlock.x, mbX, mbY); + + int qp1 = calcQpChroma(s.qp, s.chromaQpOffset[0]); + int qp2 = calcQpChroma(s.qp, s.chromaQpOffset[1]); + + decodeChromaResidual(mBlock, lAvb, tAvb, mbX, mbY, qp1, qp2); + + di.mbQps[1][mbAddr] = qp1; + di.mbQps[2][mbAddr] = qp2; + + mergeResidual(mb, mBlock.ac, mBlock.transform8x8Used ? COMP_BLOCK_8x8_LUT : COMP_BLOCK_4x4_LUT, + mBlock.transform8x8Used ? COMP_POS_8x8_LUT : COMP_POS_4x4_LUT); + + collectPredictors(s, mb, mbX); + + di.mbTypes[mbAddr] = mBlock.curMbType; + di.tr8x8Used[mbAddr] = mBlock.transform8x8Used; + } + + public void predictBDirect(Frame[][] refs, int mbX, int mbY, boolean lAvb, boolean tAvb, boolean tlAvb, + boolean trAvb, MvList x, PartPred[] pp, Picture mb, int[] blocks) { + if (sh.directSpatialMvPredFlag) + predictBSpatialDirect(refs, mbX, mbY, lAvb, tAvb, tlAvb, trAvb, x, pp, mb, blocks); + else + predictBTemporalDirect(refs, mbX, mbY, lAvb, tAvb, tlAvb, trAvb, x, pp, mb, blocks); + } + + private void predictBTemporalDirect(Frame[][] refs, int mbX, int mbY, boolean lAvb, boolean tAvb, boolean tlAvb, + boolean trAvb, MvList x, PartPred[] pp, Picture mb, int[] blocks8x8) { + + for (int i = 0; i < blocks8x8.length; i++) { + int blk8x8 = blocks8x8[i]; + int blk4x4_0 = H264Const.BLK8x8_BLOCKS[blk8x8][0]; + pp[blk8x8] = Bi; + + if (!sh.sps.direct8x8InferenceFlag) { + int[] js = BLK8x8_BLOCKS[blk8x8]; + for (int j = 0; j < js.length; j++) { + int blk4x4 = js[j]; + predTemp4x4(refs, mbX, mbY, x, blk4x4); + + int blkIndX = blk4x4 & 3; + int blkIndY = blk4x4 >> 2; + + debugPrint("DIRECT_4x4 [%d, %d]: (%d,%d,%d), (%d,%d,%d)", blkIndY, blkIndX, x.mv0X(blk4x4), + x.mv0Y(blk4x4), x.mv0R(blk4x4), x.mv1X(blk4x4), x.mv1Y(blk4x4), x.mv1R(blk4x4)); + + int blkPredX = (mbX << 6) + (blkIndX << 4); + int blkPredY = (mbY << 6) + (blkIndY << 4); + + interpolator.getBlockLuma(refs[0][x.mv0R(blk4x4)], mbb[0], BLK_4x4_MB_OFF_LUMA[blk4x4], blkPredX + + x.mv0X(blk4x4), blkPredY + x.mv0Y(blk4x4), 4, 4); + interpolator.getBlockLuma(refs[1][0], mbb[1], BLK_4x4_MB_OFF_LUMA[blk4x4], blkPredX + + x.mv1X(blk4x4), blkPredY + x.mv1Y(blk4x4), 4, 4); + } + } else { + int blk4x4Pred = BLK_DISP_MAP[blk8x8 * 5]; + predTemp4x4(refs, mbX, mbY, x, blk4x4Pred); + propagatePred(x, blk8x8, blk4x4Pred); + + int blkIndX = blk4x4_0 & 3; + int blkIndY = blk4x4_0 >> 2; + + debugPrint("DIRECT_8x8 [%d, %d]: (%d,%d,%d), (%d,%d)", blkIndY, blkIndX, x.mv0X(blk4x4_0), + x.mv0Y(blk4x4_0), x.mv0R(blk4x4_0), x.mv1X(blk4x4_0), x.mv1Y(blk4x4_0), x.mv1R(blk4x4_0)); + + int blkPredX = (mbX << 6) + (blkIndX << 4); + int blkPredY = (mbY << 6) + (blkIndY << 4); + + interpolator.getBlockLuma(refs[0][x.mv0R(blk4x4_0)], mbb[0], BLK_4x4_MB_OFF_LUMA[blk4x4_0], blkPredX + + x.mv0X(blk4x4_0), blkPredY + x.mv0Y(blk4x4_0), 8, 8); + interpolator.getBlockLuma(refs[1][0], mbb[1], BLK_4x4_MB_OFF_LUMA[blk4x4_0], blkPredX + + x.mv1X(blk4x4_0), blkPredY + x.mv1Y(blk4x4_0), 8, 8); + } + mergePrediction(sh, x.mv0R(blk4x4_0), x.mv1R(blk4x4_0), Bi, 0, mbb[0].getPlaneData(0), mbb[1].getPlaneData(0), + BLK_4x4_MB_OFF_LUMA[blk4x4_0], 16, 8, 8, mb.getPlaneData(0), refs, poc); + } + } + + private void predTemp4x4(Frame[][] refs, int mbX, int mbY, MvList x, int blk4x4) { + int mbWidth = sh.sps.picWidthInMbsMinus1 + 1; + + Frame picCol = refs[1][0]; + int blkIndX = blk4x4 & 3; + int blkIndY = blk4x4 >> 2; + + int blkPosX = (mbX << 2) + blkIndX; + int blkPosY = (mbY << 2) + blkIndY; + + int mvCol = picCol.getMvs().getMv(blkPosX, blkPosY, 0); + Frame refL0; + int refIdxL0; + if (mvRef(mvCol) == -1) { + mvCol = picCol.getMvs().getMv(blkPosX, blkPosY, 1); + if (mvRef(mvCol) == -1) { + refIdxL0 = 0; + refL0 = refs[0][0]; + } else { + refL0 = picCol.getRefsUsed()[mbY * mbWidth + mbX][1][mvRef(mvCol)]; + refIdxL0 = findPic(refs[0], refL0); + } + } else { + refL0 = picCol.getRefsUsed()[mbY * mbWidth + mbX][0][mvRef(mvCol)]; + refIdxL0 = findPic(refs[0], refL0); + } + + int td = MathUtil.clip(picCol.getPOC() - refL0.getPOC(), -128, 127); + if (!refL0.isShortTerm() || td == 0) { + x.setPair(blk4x4, packMv(mvX(mvCol), mvY(mvCol), refIdxL0), 0); + } else { + int tb = MathUtil.clip(poc - refL0.getPOC(), -128, 127); + int tx = (16384 + Math.abs(td / 2)) / td; + int dsf = clip((tb * tx + 32) >> 6, -1024, 1023); + + x.setPair(blk4x4, packMv((dsf * mvX(mvCol) + 128) >> 8, (dsf * mvY(mvCol) + 128) >> 8, refIdxL0), + packMv((x.mv0X(blk4x4) - mvX(mvCol)), (x.mv0Y(blk4x4) - mvY(mvCol)), 0)); + } + } + + private int findPic(Frame[] frames, Frame refL0) { + for (int i = 0; i < frames.length; i++) + if (frames[i] == refL0) + return i; + Logger.error("RefPicList0 shall contain refPicCol"); + return 0; + } + + private void predictBSpatialDirect(Frame[][] refs, int mbX, int mbY, boolean lAvb, boolean tAvb, boolean tlAvb, + boolean trAvb, MvList x, PartPred[] pp, Picture mb, int[] blocks8x8) { + + int a0 = s.mvLeft.getMv(0, 0), a1 = s.mvLeft.getMv(0, 1); + int b0 = s.mvTop.getMv(mbX << 2, 0), b1 = s.mvTop.getMv(mbX << 2, 1); + int c0 = s.mvTop.getMv((mbX << 2) + 4, 0), c1 = s.mvTop.getMv((mbX << 2) + 4, 1); + int d0 = s.mvTopLeft.getMv(0, 0); + int d1 = s.mvTopLeft.getMv(0, 1); + + int refIdxL0 = calcRef(a0, b0, c0, d0, lAvb, tAvb, tlAvb, trAvb, mbX); + int refIdxL1 = calcRef(a1, b1, c1, d1, lAvb, tAvb, tlAvb, trAvb, mbX); + + if (refIdxL0 < 0 && refIdxL1 < 0) { + for (int i = 0; i < blocks8x8.length; i++) { + int blk8x8 = blocks8x8[i]; + int[] js = BLK8x8_BLOCKS[blk8x8]; + for (int j = 0; j < js.length; j++) { + int blk4x4 = js[j]; + x.setPair(blk4x4, 0, 0); + } + pp[blk8x8] = Bi; + + int blkOffX = (blk8x8 & 1) << 5; + int blkOffY = (blk8x8 >> 1) << 5; + interpolator.getBlockLuma(refs[0][0], mbb[0], BLK_8x8_MB_OFF_LUMA[blk8x8], (mbX << 6) + blkOffX, + (mbY << 6) + blkOffY, 8, 8); + interpolator.getBlockLuma(refs[1][0], mbb[1], BLK_8x8_MB_OFF_LUMA[blk8x8], (mbX << 6) + blkOffX, + (mbY << 6) + blkOffY, 8, 8); + PredictionMerger.mergePrediction(sh, 0, 0, PartPred.Bi, 0, mbb[0].getPlaneData(0), mbb[1].getPlaneData(0), + BLK_8x8_MB_OFF_LUMA[blk8x8], 16, 8, 8, mb.getPlaneData(0), refs, poc); + debugPrint("DIRECT_8x8 [%d, %d]: (0,0,0), (0,0,0)", (blk8x8 & 2), ((blk8x8 << 1) & 2)); + } + return; + } + int mvX0 = calcMVPredictionMedian(a0, b0, c0, d0, lAvb, tAvb, trAvb, tlAvb, refIdxL0, 0); + int mvY0 = calcMVPredictionMedian(a0, b0, c0, d0, lAvb, tAvb, trAvb, tlAvb, refIdxL0, 1); + int mvX1 = calcMVPredictionMedian(a1, b1, c1, d1, lAvb, tAvb, trAvb, tlAvb, refIdxL1, 0); + int mvY1 = calcMVPredictionMedian(a1, b1, c1, d1, lAvb, tAvb, trAvb, tlAvb, refIdxL1, 1); + + Frame col = refs[1][0]; + PartPred partPred = refIdxL0 >= 0 && refIdxL1 >= 0 ? Bi : (refIdxL0 >= 0 ? L0 : L1); + for (int i = 0; i < blocks8x8.length; i++) { + int blk8x8 = blocks8x8[i]; + int blk4x4_0 = H264Const.BLK8x8_BLOCKS[blk8x8][0]; + + if (!sh.sps.direct8x8InferenceFlag) { + int[] js = BLK8x8_BLOCKS[blk8x8]; + for (int j = 0; j < js.length; j++) { + int blk4x4 = js[j]; + pred4x4(mbX, mbY, x, pp, refIdxL0, refIdxL1, mvX0, mvY0, mvX1, mvY1, col, partPred, blk4x4); + + int blkIndX = blk4x4 & 3; + int blkIndY = blk4x4 >> 2; + + debugPrint("DIRECT_4x4 [%d, %d]: (%d,%d,%d), (%d,%d," + refIdxL1 + ")", blkIndY, blkIndX, + x.mv0X(blk4x4), x.mv0Y(blk4x4), refIdxL0, x.mv1X(blk4x4), x.mv1Y(blk4x4)); + + int blkPredX = (mbX << 6) + (blkIndX << 4); + int blkPredY = (mbY << 6) + (blkIndY << 4); + + if (refIdxL0 >= 0) + interpolator.getBlockLuma(refs[0][refIdxL0], mbb[0], BLK_4x4_MB_OFF_LUMA[blk4x4], blkPredX + + x.mv0X(blk4x4), blkPredY + x.mv0Y(blk4x4), 4, 4); + if (refIdxL1 >= 0) + interpolator.getBlockLuma(refs[1][refIdxL1], mbb[1], BLK_4x4_MB_OFF_LUMA[blk4x4], blkPredX + + x.mv1X(blk4x4), blkPredY + x.mv1Y(blk4x4), 4, 4); + } + } else { + int blk4x4Pred = BLK_DISP_MAP[blk8x8 * 5]; + pred4x4(mbX, mbY, x, pp, refIdxL0, refIdxL1, mvX0, mvY0, mvX1, mvY1, col, partPred, blk4x4Pred); + propagatePred(x, blk8x8, blk4x4Pred); + + int blkIndX = blk4x4_0 & 3; + int blkIndY = blk4x4_0 >> 2; + + debugPrint("DIRECT_8x8 [%d, %d]: (%d,%d,%d), (%d,%d,%d)", blkIndY, blkIndX, x.mv0X(blk4x4_0), + x.mv0Y(blk4x4_0), refIdxL0, x.mv1X(blk4x4_0), x.mv1Y(blk4x4_0), refIdxL1); + + int blkPredX = (mbX << 6) + (blkIndX << 4); + int blkPredY = (mbY << 6) + (blkIndY << 4); + + if (refIdxL0 >= 0) + interpolator.getBlockLuma(refs[0][refIdxL0], mbb[0], BLK_4x4_MB_OFF_LUMA[blk4x4_0], blkPredX + + x.mv0X(blk4x4_0), blkPredY + x.mv0Y(blk4x4_0), 8, 8); + if (refIdxL1 >= 0) + interpolator.getBlockLuma(refs[1][refIdxL1], mbb[1], BLK_4x4_MB_OFF_LUMA[blk4x4_0], blkPredX + + x.mv1X(blk4x4_0), blkPredY + x.mv1Y(blk4x4_0), 8, 8); + } + PredictionMerger.mergePrediction(sh, x.mv0R(blk4x4_0), x.mv1R(blk4x4_0), + refIdxL0 >= 0 ? (refIdxL1 >= 0 ? Bi : L0) : L1, 0, mbb[0].getPlaneData(0), mbb[1].getPlaneData(0), + BLK_4x4_MB_OFF_LUMA[blk4x4_0], 16, 8, 8, mb.getPlaneData(0), refs, poc); + } + } + + private int calcRef(int a0, int b0, int c0, int d0, boolean lAvb, boolean tAvb, boolean tlAvb, boolean trAvb, + int mbX) { + return minPos(minPos(lAvb ? mvRef(a0) : -1, tAvb ? mvRef(b0) : -1), trAvb ? mvRef(c0) : (tlAvb ? mvRef(d0) : -1)); + } + + private void propagatePred(MvList x, int blk8x8, int blk4x4Pred) { + int b0 = BLK8x8_BLOCKS[blk8x8][0]; + int b1 = BLK8x8_BLOCKS[blk8x8][1]; + int b2 = BLK8x8_BLOCKS[blk8x8][2]; + int b3 = BLK8x8_BLOCKS[blk8x8][3]; + x.copyPair(b0, x, blk4x4Pred); + x.copyPair(b1, x, blk4x4Pred); + x.copyPair(b2, x, blk4x4Pred); + x.copyPair(b3, x, blk4x4Pred); + } + + private void pred4x4(int mbX, int mbY, MvList x, PartPred[] pp, int refL0, int refL1, int mvX0, int mvY0, + int mvX1, int mvY1, Frame col, PartPred partPred, int blk4x4) { + int blkIndX = blk4x4 & 3; + int blkIndY = blk4x4 >> 2; + + int blkPosX = (mbX << 2) + blkIndX; + int blkPosY = (mbY << 2) + blkIndY; + + int mvCol = col.getMvs().getMv(blkPosX, blkPosY, 0); + if (mvRef(mvCol) == -1) + mvCol = col.getMvs().getMv(blkPosX, blkPosY, 1); + + boolean colZero = col.isShortTerm() && mvRef(mvCol) == 0 && (abs(mvX(mvCol)) >> 1) == 0 + && (abs(mvY(mvCol)) >> 1) == 0; + + int x0 = packMv(0, 0, refL0), x1 = packMv(0, 0, refL1); + if (refL0 > 0 || !colZero) { + x0 = packMv(mvX0, mvY0, refL0); + } + if (refL1 > 0 || !colZero) { + x1 = packMv(mvX1, mvY1, refL1); + } + x.setPair(blk4x4, x0, x1); + + pp[BLK_8x8_IND[blk4x4]] = partPred; + } + + private int minPos(int a, int b) { + return a >= 0 && b >= 0 ? Math.min(a, b) : Math.max(a, b); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderBase.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderBase.java new file mode 100644 index 0000000..7778c09 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderBase.java @@ -0,0 +1,229 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const; +import org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred; +import org.monte.media.impl.jcodec.codecs.h264.H264Utils.MvList; +import org.monte.media.impl.jcodec.codecs.h264.io.model.Frame; +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import java.util.Arrays; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK8x8_BLOCKS; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK_8x8_MB_OFF_CHROMA; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK_DISP_MAP; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.QP_SCALE_CR; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvRef; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvX; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvY; +import static org.monte.media.impl.jcodec.codecs.h264.decode.PredictionMerger.mergePrediction; +import static org.monte.media.impl.jcodec.common.model.ColorSpace.MONO; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Base macroblock decoder that contains routines shared by many decoders + * + * @author The JCodec project + */ +public class MBlockDecoderBase { + protected DecoderState s; + protected SliceHeader sh; + protected DeblockerInput di; + protected int poc; + protected BlockInterpolator interpolator; + protected Picture[] mbb; + protected int[][] scalingMatrix; + + public MBlockDecoderBase(SliceHeader sh, DeblockerInput di, int poc, DecoderState decoderState) { + this.interpolator = new BlockInterpolator(); + this.s = decoderState; + this.sh = sh; + this.di = di; + this.poc = poc; + this.mbb = new Picture[]{Picture.create(16, 16, s.chromaFormat), Picture.create(16, 16, s.chromaFormat)}; + scalingMatrix = initScalingMatrix(sh); + } + + void residualLuma(MBlock mBlock, boolean leftAvailable, boolean topAvailable, int mbX, int mbY) { + if (!mBlock.transform8x8Used) { + residualLuma4x4(mBlock); + } else if (sh.pps.entropyCodingModeFlag) { + residualLuma8x8CABAC(mBlock); + } else { + residualLuma8x8CAVLC(mBlock); + } + } + + private void residualLuma4x4(MBlock mBlock) { + + for (int i = 0; i < 16; i++) { + if ((mBlock.cbpLuma() & (1 << (i >> 2))) == 0) { + continue; + } + + CoeffTransformer.dequantizeAC(mBlock.ac[0][i], s.qp, getScalingList(mBlock.curMbType.intra ? 0 : 3)); + CoeffTransformer.idct4x4(mBlock.ac[0][i]); + } + } + + protected int[] getScalingList(int which) { + if (scalingMatrix == null) + return null; + return scalingMatrix[which]; + } + + protected static int[][] initScalingMatrix(SliceHeader sh2) { + if (sh2.sps.scalingMatrix == null && (sh2.pps.extended == null || sh2.pps.extended.scalingMatrix == null)) + return null; + int[][] merged = new int[][]{H264Const.defaultScalingList4x4Intra, null, null, + H264Const.defaultScalingList4x4Inter, null, null, H264Const.defaultScalingList8x8Intra, + H264Const.defaultScalingList8x8Inter, null, null, null, null}; + for (int i = 0; i < 8; i++) { + if (sh2.sps.scalingMatrix != null && sh2.sps.scalingMatrix[i] != null) + merged[i] = sh2.sps.scalingMatrix[i]; + if (sh2.pps.extended != null && sh2.pps.extended.scalingMatrix != null + && sh2.pps.extended.scalingMatrix[i] != null) + merged[i] = sh2.pps.extended.scalingMatrix[i]; + } + if (merged[1] == null) + merged[1] = merged[0]; + if (merged[2] == null) + merged[2] = merged[0]; + if (merged[4] == null) + merged[4] = merged[3]; + if (merged[5] == null) + merged[5] = merged[3]; + if (merged[8] == null) + merged[8] = merged[6]; + if (merged[10] == null) + merged[10] = merged[6]; + if (merged[9] == null) + merged[9] = merged[7]; + if (merged[11] == null) + merged[11] = merged[7]; + return merged; + } + + private void residualLuma8x8CABAC(MBlock mBlock) { + + for (int i = 0; i < 4; i++) { + if ((mBlock.cbpLuma() & (1 << i)) == 0) { + continue; + } + + CoeffTransformer.dequantizeAC8x8(mBlock.ac[0][i], s.qp, getScalingList(mBlock.curMbType.intra ? 6 : 7)); + CoeffTransformer.idct8x8(mBlock.ac[0][i]); + } + } + + private void residualLuma8x8CAVLC(MBlock mBlock) { + + for (int i = 0; i < 4; i++) { + if ((mBlock.cbpLuma() & (1 << i)) == 0) { + continue; + } + + CoeffTransformer.dequantizeAC8x8(mBlock.ac[0][i], s.qp, getScalingList(mBlock.curMbType.intra ? 6 : 7)); + CoeffTransformer.idct8x8(mBlock.ac[0][i]); + } + } + + public void decodeChroma(MBlock mBlock, int mbX, int mbY, boolean leftAvailable, boolean topAvailable, + Picture mb, int qp) { + + if (s.chromaFormat == MONO) { + Arrays.fill(mb.getPlaneData(1), (byte) 0); + Arrays.fill(mb.getPlaneData(2), (byte) 0); + return; + } + + int qp1 = calcQpChroma(qp, s.chromaQpOffset[0]); + int qp2 = calcQpChroma(qp, s.chromaQpOffset[1]); + + if (mBlock.cbpChroma() != 0) { + decodeChromaResidual(mBlock, leftAvailable, topAvailable, mbX, mbY, qp1, qp2); + } + + int addr = mbY * (sh.sps.picWidthInMbsMinus1 + 1) + mbX; + di.mbQps[1][addr] = qp1; + di.mbQps[2][addr] = qp2; + ChromaPredictionBuilder.predictWithMode(mBlock.ac[1], mBlock.chromaPredictionMode, mbX, leftAvailable, + topAvailable, s.leftRow[1], s.topLine[1], s.topLeft[1], mb.getPlaneData(1)); + ChromaPredictionBuilder.predictWithMode(mBlock.ac[2], mBlock.chromaPredictionMode, mbX, leftAvailable, + topAvailable, s.leftRow[2], s.topLine[2], s.topLeft[2], mb.getPlaneData(2)); + } + + void decodeChromaResidual(MBlock mBlock, boolean leftAvailable, boolean topAvailable, int mbX, int mbY, int crQp1, + int crQp2) { + if (mBlock.cbpChroma() != 0) { + if ((mBlock.cbpChroma() & 3) > 0) { + chromaDC(mbX, leftAvailable, topAvailable, mBlock.dc1, 1, crQp1, mBlock.curMbType); + chromaDC(mbX, leftAvailable, topAvailable, mBlock.dc2, 2, crQp2, mBlock.curMbType); + } + + chromaAC(leftAvailable, topAvailable, mbX, mbY, mBlock.dc1, 1, crQp1, mBlock.curMbType, + (mBlock.cbpChroma() & 2) > 0, mBlock.ac[1]); + chromaAC(leftAvailable, topAvailable, mbX, mbY, mBlock.dc2, 2, crQp2, mBlock.curMbType, + (mBlock.cbpChroma() & 2) > 0, mBlock.ac[2]); + } + } + + private void chromaDC(int mbX, boolean leftAvailable, boolean topAvailable, int[] dc, int comp, int crQp, + MBType curMbType) { + CoeffTransformer.invDC2x2(dc); + CoeffTransformer.dequantizeDC2x2(dc, crQp, getScalingList((curMbType.intra ? 6 : 7) + comp * 2)); + } + + private void chromaAC(boolean leftAvailable, boolean topAvailable, int mbX, int mbY, int[] dc, int comp, int crQp, + MBType curMbType, boolean codedAC, int[][] residualOut) { + for (int i = 0; i < dc.length; i++) { + int[] ac = residualOut[i]; + + if (codedAC) { + CoeffTransformer.dequantizeAC(ac, crQp, getScalingList((curMbType.intra ? 0 : 3) + comp)); + } + ac[0] = dc[i]; + + CoeffTransformer.idct4x4(ac); + } + } + + static int calcQpChroma(int qp, int crQpOffset) { + return QP_SCALE_CR[MathUtil.clip(qp + crQpOffset, 0, 51)]; + } + + public void predictChromaInter(Frame[][] refs, MvList vectors, int x, int y, int comp, Picture mb, + PartPred[] predType) { + + for (int blk8x8 = 0; blk8x8 < 4; blk8x8++) { + for (int list = 0; list < 2; list++) { + if (!H264Const.usesList(predType[blk8x8], list)) + continue; + for (int blk4x4 = 0; blk4x4 < 4; blk4x4++) { + int i = BLK_DISP_MAP[(blk8x8 << 2) + blk4x4]; + int mv = vectors.getMv(i, list); + Picture ref = refs[list][mvRef(mv)]; + + int blkPox = (i & 3) << 1; + int blkPoy = (i >> 2) << 1; + + int xx = ((x + blkPox) << 3) + mvX(mv); + int yy = ((y + blkPoy) << 3) + mvY(mv); + + BlockInterpolator.getBlockChroma(ref.getPlaneData(comp), ref.getPlaneWidth(comp), + ref.getPlaneHeight(comp), mbb[list].getPlaneData(comp), blkPoy * mb.getPlaneWidth(comp) + + blkPox, mb.getPlaneWidth(comp), xx, yy, 2, 2); + } + } + + int blk4x4 = BLK8x8_BLOCKS[blk8x8][0]; + mergePrediction(sh, vectors.mv0R(blk4x4), vectors.mv1R(blk4x4), predType[blk8x8], comp, + mbb[0].getPlaneData(comp), mbb[1].getPlaneData(comp), BLK_8x8_MB_OFF_CHROMA[blk8x8], + mb.getPlaneWidth(comp), 4, 4, mb.getPlaneData(comp), refs, poc); + } + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderIPCM.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderIPCM.java new file mode 100644 index 0000000..bd973bb --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderIPCM.java @@ -0,0 +1,28 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.decode.aso.Mapper; +import org.monte.media.impl.jcodec.common.model.Picture; + +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.collectPredictors; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.saveVectIntra; + +/** + * A decoder for Intra PCM macroblocks + * + * @author The JCodec project + */ +public class MBlockDecoderIPCM { + private Mapper mapper; + private DecoderState s; + + public MBlockDecoderIPCM(Mapper mapper, DecoderState decoderState) { + this.mapper = mapper; + this.s = decoderState; + } + + public void decode(MBlock mBlock, Picture mb) { + int mbX = mapper.getMbX(mBlock.mbIdx); + collectPredictors(s, mb, mbX); + saveVectIntra(s, mapper.getMbX(mBlock.mbIdx)); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderInter.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderInter.java new file mode 100644 index 0000000..dfa39af --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderInter.java @@ -0,0 +1,361 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const; +import org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred; +import org.monte.media.impl.jcodec.codecs.h264.H264Utils.MvList; +import org.monte.media.impl.jcodec.codecs.h264.decode.aso.Mapper; +import org.monte.media.impl.jcodec.codecs.h264.io.model.Frame; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.common.model.Picture; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.COMP_BLOCK_4x4_LUT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.COMP_BLOCK_8x8_LUT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.COMP_POS_4x4_LUT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.COMP_POS_8x8_LUT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvC; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvRef; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.packMv; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.NULL_VECTOR; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.calcMVPredictionMedian; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.collectPredictors; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.debugPrint; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.mergeResidual; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.saveMvs; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.saveVect; +import static org.monte.media.impl.jcodec.codecs.h264.decode.PredictionMerger.mergePrediction; +import static org.monte.media.impl.jcodec.common.model.ColorSpace.MONO; + +/** + * A decoder for Inter 16x16, 16x8 and 8x16 macroblocks + * + * @author The JCodec project + */ +public class MBlockDecoderInter extends MBlockDecoderBase { + private Mapper mapper; + + public MBlockDecoderInter(Mapper mapper, SliceHeader sh, DeblockerInput di, int poc, DecoderState decoderState) { + super(sh, di, poc, decoderState); + this.mapper = mapper; + } + + public void decode16x16(MBlock mBlock, Picture mb, Frame[][] refs, PartPred p0) { + + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + boolean leftAvailable = mapper.leftAvailable(mBlock.mbIdx); + boolean topAvailable = mapper.topAvailable(mBlock.mbIdx); + + boolean topLeftAvailable = mapper.topLeftAvailable(mBlock.mbIdx); + boolean topRightAvailable = mapper.topRightAvailable(mBlock.mbIdx); + int address = mapper.getAddress(mBlock.mbIdx); + int xx = mbX << 2; + + for (int list = 0; list < 2; list++) { + predictInter16x16(mBlock, mbb[list], refs, mbX, mbY, leftAvailable, topAvailable, topLeftAvailable, + topRightAvailable, mBlock.x, xx, list, p0); + } + + PredictionMerger.mergePrediction(sh, mBlock.x.mv0R(0), mBlock.x.mv1R(0), p0, 0, mbb[0].getPlaneData(0), + mbb[1].getPlaneData(0), 0, 16, 16, 16, mb.getPlaneData(0), refs, poc); + + mBlock.partPreds[0] = mBlock.partPreds[1] = mBlock.partPreds[2] = mBlock.partPreds[3] = p0; + predictChromaInter(refs, mBlock.x, mbX << 3, mbY << 3, 1, mb, mBlock.partPreds); + predictChromaInter(refs, mBlock.x, mbX << 3, mbY << 3, 2, mb, mBlock.partPreds); + + residualInter(mBlock, refs, leftAvailable, topAvailable, mbX, mbY, mapper.getAddress(mBlock.mbIdx)); + + saveMvs(di, mBlock.x, mbX, mbY); + + mergeResidual(mb, mBlock.ac, mBlock.transform8x8Used ? COMP_BLOCK_8x8_LUT : COMP_BLOCK_4x4_LUT, + mBlock.transform8x8Used ? COMP_POS_8x8_LUT : COMP_POS_4x4_LUT); + + collectPredictors(s, mb, mbX); + + di.mbTypes[address] = mBlock.curMbType; + } + + private void predictInter8x16(MBlock mBlock, Picture mb, Picture[][] references, int mbX, int mbY, + boolean leftAvailable, boolean topAvailable, boolean tlAvailable, boolean trAvailable, MvList x, + int list, PartPred p0, PartPred p1) { + int xx = mbX << 2; + + int mvX1 = 0, mvY1 = 0, r1 = -1, mvX2 = 0, mvY2 = 0, r2 = -1; + if (H264Const.usesList(p0, list)) { + int mvpX1 = calcMVPrediction8x16Left(s.mvLeft.getMv(0, list), s.mvTop.getMv(mbX << 2, list), + s.mvTop.getMv((mbX << 2) + 2, list), s.mvTopLeft.getMv(0, list), leftAvailable, topAvailable, + topAvailable, tlAvailable, mBlock.pb168x168.refIdx1[list], 0); + int mvpY1 = calcMVPrediction8x16Left(s.mvLeft.getMv(0, list), s.mvTop.getMv(mbX << 2, list), + s.mvTop.getMv((mbX << 2) + 2, list), s.mvTopLeft.getMv(0, list), leftAvailable, topAvailable, + topAvailable, tlAvailable, mBlock.pb168x168.refIdx1[list], 1); + + mvX1 = mBlock.pb168x168.mvdX1[list] + mvpX1; + mvY1 = mBlock.pb168x168.mvdY1[list] + mvpY1; + + debugPrint("MVP: (%d, %d), MVD: (%d, %d), MV: (%d,%d,%d)", mvpX1, mvpY1, mBlock.pb168x168.mvdX1[list], + mBlock.pb168x168.mvdY1[list], mvX1, mvY1, mBlock.pb168x168.refIdx1[list]); + + interpolator.getBlockLuma(references[list][mBlock.pb168x168.refIdx1[list]], mb, 0, (mbX << 6) + mvX1, + (mbY << 6) + mvY1, 8, 16); + r1 = mBlock.pb168x168.refIdx1[list]; + } + + // Horizontal motion vector range does not exceed the range of -2048 to 2047.75, inclusive, in units of luma + // samples. Vertical MV [-512,+511.75]. I.e. 14 + 12 bits = 26 bits. Ref Idx 6 bit ? + int v1 = packMv(mvX1, mvY1, r1); + if (H264Const.usesList(p1, list)) { + int mvpX2 = calcMVPrediction8x16Right(v1, s.mvTop.getMv((mbX << 2) + 2, list), + s.mvTop.getMv((mbX << 2) + 4, list), s.mvTop.getMv((mbX << 2) + 1, list), true, topAvailable, + trAvailable, topAvailable, mBlock.pb168x168.refIdx2[list], 0); + int mvpY2 = calcMVPrediction8x16Right(v1, s.mvTop.getMv((mbX << 2) + 2, list), + s.mvTop.getMv((mbX << 2) + 4, list), s.mvTop.getMv((mbX << 2) + 1, list), true, topAvailable, + trAvailable, topAvailable, mBlock.pb168x168.refIdx2[list], 1); + + mvX2 = mBlock.pb168x168.mvdX2[list] + mvpX2; + mvY2 = mBlock.pb168x168.mvdY2[list] + mvpY2; + + debugPrint("MVP: (" + mvpX2 + ", " + mvpY2 + "), MVD: (" + mBlock.pb168x168.mvdX2[list] + ", " + + mBlock.pb168x168.mvdY2[list] + "), MV: (" + mvX2 + "," + mvY2 + "," + + mBlock.pb168x168.refIdx2[list] + ")"); + + interpolator.getBlockLuma(references[list][mBlock.pb168x168.refIdx2[list]], mb, 8, (mbX << 6) + 32 + + mvX2, (mbY << 6) + mvY2, 8, 16); + r2 = mBlock.pb168x168.refIdx2[list]; + } + int v2 = packMv(mvX2, mvY2, r2); + + s.mvTopLeft.setMv(0, list, s.mvTop.getMv(xx + 3, list)); + saveVect(s.mvTop, list, xx, xx + 2, v1); + saveVect(s.mvTop, list, xx + 2, xx + 4, v2); + saveVect(s.mvLeft, list, 0, 4, v2); + + for (int i = 0; i < 16; i += 4) { + x.setMv(i, list, v1); + x.setMv(i + 1, list, v1); + x.setMv(i + 2, list, v2); + x.setMv(i + 3, list, v2); + } + } + + private void predictInter16x8(MBlock mBlock, Picture mb, Picture[][] references, int mbX, int mbY, + boolean leftAvailable, boolean topAvailable, boolean tlAvailable, boolean trAvailable, int xx, MvList x, + PartPred p0, PartPred p1, int list) { + + int mvX1 = 0, mvY1 = 0, mvX2 = 0, mvY2 = 0, r1 = -1, r2 = -1; + if (H264Const.usesList(p0, list)) { + + int mvpX1 = calcMVPrediction16x8Top(s.mvLeft.getMv(0, list), s.mvTop.getMv(mbX << 2, list), + s.mvTop.getMv((mbX << 2) + 4, list), s.mvTopLeft.getMv(0, list), leftAvailable, topAvailable, + trAvailable, tlAvailable, mBlock.pb168x168.refIdx1[list], 0); + int mvpY1 = calcMVPrediction16x8Top(s.mvLeft.getMv(0, list), s.mvTop.getMv(mbX << 2, list), + s.mvTop.getMv((mbX << 2) + 4, list), s.mvTopLeft.getMv(0, list), leftAvailable, topAvailable, + trAvailable, tlAvailable, mBlock.pb168x168.refIdx1[list], 1); + + mvX1 = mBlock.pb168x168.mvdX1[list] + mvpX1; + mvY1 = mBlock.pb168x168.mvdY1[list] + mvpY1; + + debugPrint("MVP: (%d, %d), MVD: (%d, %d), MV: (%d,%d,%d)", mvpX1, mvpY1, mBlock.pb168x168.mvdX1[list], + mBlock.pb168x168.mvdY1[list], mvX1, mvY1, mBlock.pb168x168.refIdx1[list]); + + interpolator.getBlockLuma(references[list][mBlock.pb168x168.refIdx1[list]], mb, 0, (mbX << 6) + mvX1, + (mbY << 6) + mvY1, 16, 8); + r1 = mBlock.pb168x168.refIdx1[list]; + } + int v1 = packMv(mvX1, mvY1, r1); + + if (H264Const.usesList(p1, list)) { + int mvpX2 = calcMVPrediction16x8Bottom(s.mvLeft.getMv(2, list), v1, NULL_VECTOR, s.mvLeft.getMv(1, list), + leftAvailable, true, false, leftAvailable, mBlock.pb168x168.refIdx2[list], 0); + int mvpY2 = calcMVPrediction16x8Bottom(s.mvLeft.getMv(2, list), v1, NULL_VECTOR, s.mvLeft.getMv(1, list), + leftAvailable, true, false, leftAvailable, mBlock.pb168x168.refIdx2[list], 1); + + mvX2 = mBlock.pb168x168.mvdX2[list] + mvpX2; + mvY2 = mBlock.pb168x168.mvdY2[list] + mvpY2; + + debugPrint("MVP: (%d, %d), MVD: (%d, %d), MV: (%d,%d,%d)", mvpX2, mvpY2, mBlock.pb168x168.mvdX2[list], + mBlock.pb168x168.mvdY2[list], mvX2, mvY2, mBlock.pb168x168.refIdx2[list]); + + interpolator.getBlockLuma(references[list][mBlock.pb168x168.refIdx2[list]], mb, 128, + (mbX << 6) + mvX2, (mbY << 6) + 32 + mvY2, 16, 8); + r2 = mBlock.pb168x168.refIdx2[list]; + } + int v2 = packMv(mvX2, mvY2, r2); + + s.mvTopLeft.setMv(0, list, s.mvTop.getMv(xx + 3, list)); + saveVect(s.mvLeft, list, 0, 2, v1); + saveVect(s.mvLeft, list, 2, 4, v2); + saveVect(s.mvTop, list, xx, xx + 4, v2); + + for (int i = 0; i < 8; i++) { + x.setMv(i, list, v1); + } + for (int i = 8; i < 16; i++) { + x.setMv(i, list, v2); + } + } + + public void decode16x8(MBlock mBlock, Picture mb, Frame[][] refs, PartPred p0, PartPred p1) { + + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + boolean leftAvailable = mapper.leftAvailable(mBlock.mbIdx); + boolean topAvailable = mapper.topAvailable(mBlock.mbIdx); + boolean topLeftAvailable = mapper.topLeftAvailable(mBlock.mbIdx); + boolean topRightAvailable = mapper.topRightAvailable(mBlock.mbIdx); + int address = mapper.getAddress(mBlock.mbIdx); + int xx = mbX << 2; + + for (int list = 0; list < 2; list++) { + predictInter16x8(mBlock, mbb[list], refs, mbX, mbY, leftAvailable, topAvailable, topLeftAvailable, + topRightAvailable, xx, mBlock.x, p0, p1, list); + } + + mergePrediction(sh, mBlock.x.mv0R(0), mBlock.x.mv1R(0), p0, 0, mbb[0].getPlaneData(0), mbb[1].getPlaneData(0), + 0, 16, 16, 8, mb.getPlaneData(0), refs, poc); + mergePrediction(sh, mBlock.x.mv0R(8), mBlock.x.mv1R(8), p1, 0, mbb[0].getPlaneData(0), mbb[1].getPlaneData(0), + 128, 16, 16, 8, mb.getPlaneData(0), refs, poc); + + mBlock.partPreds[0] = mBlock.partPreds[1] = p0; + mBlock.partPreds[2] = mBlock.partPreds[3] = p1; + predictChromaInter(refs, mBlock.x, mbX << 3, mbY << 3, 1, mb, mBlock.partPreds); + predictChromaInter(refs, mBlock.x, mbX << 3, mbY << 3, 2, mb, mBlock.partPreds); + + residualInter(mBlock, refs, leftAvailable, topAvailable, mbX, mbY, mapper.getAddress(mBlock.mbIdx)); + + saveMvs(di, mBlock.x, mbX, mbY); + + mergeResidual(mb, mBlock.ac, mBlock.transform8x8Used ? COMP_BLOCK_8x8_LUT : COMP_BLOCK_4x4_LUT, + mBlock.transform8x8Used ? COMP_POS_8x8_LUT : COMP_POS_4x4_LUT); + + collectPredictors(s, mb, mbX); + + di.mbTypes[address] = mBlock.curMbType; + } + + public void decode8x16(MBlock mBlock, Picture mb, Frame[][] refs, PartPred p0, PartPred p1) { + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + boolean leftAvailable = mapper.leftAvailable(mBlock.mbIdx); + boolean topAvailable = mapper.topAvailable(mBlock.mbIdx); + boolean topLeftAvailable = mapper.topLeftAvailable(mBlock.mbIdx); + boolean topRightAvailable = mapper.topRightAvailable(mBlock.mbIdx); + int address = mapper.getAddress(mBlock.mbIdx); + + for (int list = 0; list < 2; list++) { + predictInter8x16(mBlock, mbb[list], refs, mbX, mbY, leftAvailable, topAvailable, topLeftAvailable, + topRightAvailable, mBlock.x, list, p0, p1); + } + + mergePrediction(sh, mBlock.x.mv0R(0), mBlock.x.mv1R(0), p0, 0, mbb[0].getPlaneData(0), mbb[1].getPlaneData(0), + 0, 16, 8, 16, mb.getPlaneData(0), refs, poc); + mergePrediction(sh, mBlock.x.mv0R(2), mBlock.x.mv1R(2), p1, 0, mbb[0].getPlaneData(0), mbb[1].getPlaneData(0), + 8, 16, 8, 16, mb.getPlaneData(0), refs, poc); + + mBlock.partPreds[0] = mBlock.partPreds[2] = p0; + mBlock.partPreds[1] = mBlock.partPreds[3] = p1; + + predictChromaInter(refs, mBlock.x, mbX << 3, mbY << 3, 1, mb, mBlock.partPreds); + predictChromaInter(refs, mBlock.x, mbX << 3, mbY << 3, 2, mb, mBlock.partPreds); + + residualInter(mBlock, refs, leftAvailable, topAvailable, mbX, mbY, mapper.getAddress(mBlock.mbIdx)); + + saveMvs(di, mBlock.x, mbX, mbY); + + mergeResidual(mb, mBlock.ac, mBlock.transform8x8Used ? COMP_BLOCK_8x8_LUT : COMP_BLOCK_4x4_LUT, + mBlock.transform8x8Used ? COMP_POS_8x8_LUT : COMP_POS_4x4_LUT); + + collectPredictors(s, mb, mbX); + + di.mbTypes[address] = mBlock.curMbType; + } + + void predictInter16x16(MBlock mBlock, Picture mb, Picture[][] references, int mbX, int mbY, + boolean leftAvailable, boolean topAvailable, boolean tlAvailable, boolean trAvailable, MvList x, int xx, + int list, PartPred curPred) { + + int mvX = 0, mvY = 0, r = -1; + if (H264Const.usesList(curPred, list)) { + int mvpX = calcMVPredictionMedian(s.mvLeft.getMv(0, list), s.mvTop.getMv(mbX << 2, list), + s.mvTop.getMv((mbX << 2) + 4, list), s.mvTopLeft.getMv(0, list), leftAvailable, topAvailable, + trAvailable, tlAvailable, mBlock.pb16x16.refIdx[list], 0); + int mvpY = calcMVPredictionMedian(s.mvLeft.getMv(0, list), s.mvTop.getMv(mbX << 2, list), + s.mvTop.getMv((mbX << 2) + 4, list), s.mvTopLeft.getMv(0, list), leftAvailable, topAvailable, + trAvailable, tlAvailable, mBlock.pb16x16.refIdx[list], 1); + mvX = mBlock.pb16x16.mvdX[list] + mvpX; + mvY = mBlock.pb16x16.mvdY[list] + mvpY; + + debugPrint("MVP: (%d, %d), MVD: (%d, %d), MV: (%d,%d,%d)", mvpX, mvpY, mBlock.pb16x16.mvdX[list], + mBlock.pb16x16.mvdY[list], mvX, mvY, mBlock.pb16x16.refIdx[list]); + r = mBlock.pb16x16.refIdx[list]; + + interpolator.getBlockLuma(references[list][r], mb, 0, (mbX << 6) + mvX, (mbY << 6) + mvY, 16, 16); + } + + int v = packMv(mvX, mvY, r); + s.mvTopLeft.setMv(0, list, s.mvTop.getMv(xx + 3, list)); + saveVect(s.mvTop, list, xx, xx + 4, v); + saveVect(s.mvLeft, list, 0, 4, v); + + for (int i = 0; i < 16; i++) { + x.setMv(i, list, v); + } + } + + private void residualInter(MBlock mBlock, Frame[][] refs, boolean leftAvailable, boolean topAvailable, int mbX, + int mbY, int mbAddr) { + + if (mBlock.cbpLuma() > 0 || mBlock.cbpChroma() > 0) { + s.qp = (s.qp + mBlock.mbQPDelta + 52) % 52; + } + di.mbQps[0][mbAddr] = s.qp; + + residualLuma(mBlock, leftAvailable, topAvailable, mbX, mbY); + + if (s.chromaFormat != MONO) { + int qp1 = calcQpChroma(s.qp, s.chromaQpOffset[0]); + int qp2 = calcQpChroma(s.qp, s.chromaQpOffset[1]); + + decodeChromaResidual(mBlock, leftAvailable, topAvailable, mbX, mbY, qp1, qp2); + + di.mbQps[1][mbAddr] = qp1; + di.mbQps[2][mbAddr] = qp2; + } + + di.tr8x8Used[mbAddr] = mBlock.transform8x8Used; + } + + public int calcMVPrediction16x8Top(int a, int b, int c, int d, boolean aAvb, boolean bAvb, boolean cAvb, + boolean dAvb, int refIdx, int comp) { + if (bAvb && mvRef(b) == refIdx) + return mvC(b, comp); + else + return calcMVPredictionMedian(a, b, c, d, aAvb, bAvb, cAvb, dAvb, refIdx, comp); + } + + public int calcMVPrediction16x8Bottom(int a, int b, int c, int d, boolean aAvb, boolean bAvb, boolean cAvb, + boolean dAvb, int refIdx, int comp) { + + if (aAvb && mvRef(a) == refIdx) + return mvC(a, comp); + else + return calcMVPredictionMedian(a, b, c, d, aAvb, bAvb, cAvb, dAvb, refIdx, comp); + } + + public int calcMVPrediction8x16Left(int a, int b, int c, int d, boolean aAvb, boolean bAvb, boolean cAvb, + boolean dAvb, int refIdx, int comp) { + + if (aAvb && mvRef(a) == refIdx) + return mvC(a, comp); + else + return calcMVPredictionMedian(a, b, c, d, aAvb, bAvb, cAvb, dAvb, refIdx, comp); + } + + public int calcMVPrediction8x16Right(int a, int b, int c, int d, boolean aAvb, boolean bAvb, boolean cAvb, + boolean dAvb, int refIdx, int comp) { + int lc = cAvb ? c : (dAvb ? d : NULL_VECTOR); + + if (mvRef(lc) == refIdx) + return mvC(lc, comp); + else + return calcMVPredictionMedian(a, b, c, d, aAvb, bAvb, cAvb, dAvb, refIdx, comp); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderInter8x8.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderInter8x8.java new file mode 100644 index 0000000..44d7f2a --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderInter8x8.java @@ -0,0 +1,338 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const; +import org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred; +import org.monte.media.impl.jcodec.codecs.h264.H264Utils.MvList; +import org.monte.media.impl.jcodec.codecs.h264.decode.aso.Mapper; +import org.monte.media.impl.jcodec.codecs.h264.io.model.Frame; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.common.model.Picture; + +import java.util.Arrays; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.ARRAY; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK8x8_BLOCKS; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK_8x8_MB_OFF_LUMA; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.COMP_BLOCK_4x4_LUT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.COMP_BLOCK_8x8_LUT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.COMP_POS_4x4_LUT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.COMP_POS_8x8_LUT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.Direct; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.L0; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.bPartPredModes; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.bSubMbTypes; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvX; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvY; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.packMv; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.NULL_VECTOR; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.calcMVPredictionMedian; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.collectPredictors; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.debugPrint; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.mergeResidual; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.saveMvs; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.savePrediction8x8; +import static org.monte.media.impl.jcodec.codecs.h264.decode.PredictionMerger.mergePrediction; +import static org.monte.media.impl.jcodec.codecs.h264.decode.PredictionMerger.weightPrediction; + +/** + * A decoder for Inter 16x16, 16x8 and 8x16 macroblocks + * + * @author The JCodec project + */ +public class MBlockDecoderInter8x8 extends MBlockDecoderBase { + private Mapper mapper; + private MBlockDecoderBDirect bDirectDecoder; + + public MBlockDecoderInter8x8(Mapper mapper, MBlockDecoderBDirect bDirectDecoder, SliceHeader sh, DeblockerInput di, + int poc, DecoderState decoderState) { + super(sh, di, poc, decoderState); + this.mapper = mapper; + this.bDirectDecoder = bDirectDecoder; + } + + public void decode(MBlock mBlock, Frame[][] references, Picture mb, SliceType sliceType, boolean ref0) { + + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + boolean leftAvailable = mapper.leftAvailable(mBlock.mbIdx); + boolean topAvailable = mapper.topAvailable(mBlock.mbIdx); + int mbAddr = mapper.getAddress(mBlock.mbIdx); + boolean topLeftAvailable = mapper.topLeftAvailable(mBlock.mbIdx); + boolean topRightAvailable = mapper.topRightAvailable(mBlock.mbIdx); + + if (sliceType == SliceType.P) { + predict8x8P(mBlock, references[0], mb, ref0, mbX, mbY, leftAvailable, topAvailable, topLeftAvailable, + topRightAvailable, mBlock.x, mBlock.partPreds); + } else { + predict8x8B(mBlock, references, mb, ref0, mbX, mbY, leftAvailable, topAvailable, topLeftAvailable, + topRightAvailable, mBlock.x, mBlock.partPreds); + } + + predictChromaInter(references, mBlock.x, mbX << 3, mbY << 3, 1, mb, mBlock.partPreds); + predictChromaInter(references, mBlock.x, mbX << 3, mbY << 3, 2, mb, mBlock.partPreds); + + if (mBlock.cbpLuma() > 0 || mBlock.cbpChroma() > 0) { + s.qp = (s.qp + mBlock.mbQPDelta + 52) % 52; + } + di.mbQps[0][mbAddr] = s.qp; + + residualLuma(mBlock, leftAvailable, topAvailable, mbX, mbY); + + saveMvs(di, mBlock.x, mbX, mbY); + + int qp1 = calcQpChroma(s.qp, s.chromaQpOffset[0]); + int qp2 = calcQpChroma(s.qp, s.chromaQpOffset[1]); + + decodeChromaResidual(mBlock, leftAvailable, topAvailable, mbX, mbY, qp1, qp2); + + di.mbQps[1][mbAddr] = qp1; + di.mbQps[2][mbAddr] = qp2; + + mergeResidual(mb, mBlock.ac, mBlock.transform8x8Used ? COMP_BLOCK_8x8_LUT : COMP_BLOCK_4x4_LUT, + mBlock.transform8x8Used ? COMP_POS_8x8_LUT : COMP_POS_4x4_LUT); + + collectPredictors(s, mb, mbX); + + di.mbTypes[mbAddr] = mBlock.curMbType; + di.tr8x8Used[mbAddr] = mBlock.transform8x8Used; + } + + private void predict8x8P(MBlock mBlock, Picture[] references, Picture mb, boolean ref0, int mbX, int mbY, + boolean leftAvailable, boolean topAvailable, boolean tlAvailable, boolean topRightAvailable, MvList x, + PartPred[] pp) { + + decodeSubMb8x8(mBlock, 0, mBlock.pb8x8.subMbTypes[0], references, mbX << 6, mbY << 6, s.mvTopLeft.getMv(0, 0), + s.mvTop.getMv(mbX << 2, 0), s.mvTop.getMv((mbX << 2) + 1, 0), s.mvTop.getMv((mbX << 2) + 2, 0), + s.mvLeft.getMv(0, 0), s.mvLeft.getMv(1, 0), tlAvailable, topAvailable, topAvailable, leftAvailable, + mBlock.x, 0, 1, 4, 5, mBlock.pb8x8.refIdx[0][0], mb, 0, 0); + + decodeSubMb8x8(mBlock, 1, mBlock.pb8x8.subMbTypes[1], references, (mbX << 6) + 32, mbY << 6, + s.mvTop.getMv((mbX << 2) + 1, 0), s.mvTop.getMv((mbX << 2) + 2, 0), s.mvTop.getMv((mbX << 2) + 3, 0), + s.mvTop.getMv((mbX << 2) + 4, 0), x.getMv(1, 0), x.getMv(5, 0), topAvailable, topAvailable, + topRightAvailable, true, x, 2, 3, 6, 7, mBlock.pb8x8.refIdx[0][1], mb, 8, 0); + + decodeSubMb8x8(mBlock, 2, mBlock.pb8x8.subMbTypes[2], references, mbX << 6, (mbY << 6) + 32, + s.mvLeft.getMv(1, 0), x.getMv(4, 0), x.getMv(5, 0), x.getMv(6, 0), s.mvLeft.getMv(2, 0), + s.mvLeft.getMv(3, 0), leftAvailable, true, true, leftAvailable, x, 8, 9, 12, 13, + mBlock.pb8x8.refIdx[0][2], mb, 128, 0); + + decodeSubMb8x8(mBlock, 3, mBlock.pb8x8.subMbTypes[3], references, (mbX << 6) + 32, (mbY << 6) + 32, + x.getMv(5, 0), x.getMv(6, 0), x.getMv(7, 0), NULL_VECTOR, x.getMv(9, 0), x.getMv(13, 0), true, true, + false, true, x, 10, 11, 14, 15, mBlock.pb8x8.refIdx[0][3], mb, 136, 0); + + for (int i = 0; i < 4; i++) { + // TODO(stan): refactor this + int blk4x4 = BLK8x8_BLOCKS[i][0]; + weightPrediction(sh, x.mv0R(blk4x4), 0, mb.getPlaneData(0), BLK_8x8_MB_OFF_LUMA[i], 16, 8, + 8, mb.getPlaneData(0)); + } + + savePrediction8x8(s, mbX, x); + Arrays.fill(pp, L0); + } + + private void predict8x8B(MBlock mBlock, Frame[][] refs, Picture mb, boolean ref0, int mbX, int mbY, + boolean leftAvailable, boolean topAvailable, boolean tlAvailable, boolean topRightAvailable, MvList x, + PartPred[] p) { + + for (int i = 0; i < 4; i++) { + p[i] = bPartPredModes[mBlock.pb8x8.subMbTypes[i]]; + } + + for (int i = 0; i < 4; i++) { + if (p[i] == Direct) + bDirectDecoder.predictBDirect(refs, mbX, mbY, leftAvailable, topAvailable, tlAvailable, + topRightAvailable, x, p, mb, ARRAY[i]); + } + + for (int list = 0; list < 2; list++) { + if (H264Const.usesList(bPartPredModes[mBlock.pb8x8.subMbTypes[0]], list)) { + decodeSubMb8x8(mBlock, 0, bSubMbTypes[mBlock.pb8x8.subMbTypes[0]], refs[list], mbX << 6, mbY << 6, + s.mvTopLeft.getMv(0, list), s.mvTop.getMv(mbX << 2, list), s.mvTop.getMv((mbX << 2) + 1, list), + s.mvTop.getMv((mbX << 2) + 2, list), s.mvLeft.getMv(0, list), s.mvLeft.getMv(1, list), + tlAvailable, topAvailable, topAvailable, leftAvailable, x, 0, 1, 4, 5, + mBlock.pb8x8.refIdx[list][0], mbb[list], 0, list); + } + if (H264Const.usesList(bPartPredModes[mBlock.pb8x8.subMbTypes[1]], list)) { + decodeSubMb8x8(mBlock, 1, bSubMbTypes[mBlock.pb8x8.subMbTypes[1]], refs[list], (mbX << 6) + 32, + mbY << 6, s.mvTop.getMv((mbX << 2) + 1, list), s.mvTop.getMv((mbX << 2) + 2, list), + s.mvTop.getMv((mbX << 2) + 3, list), s.mvTop.getMv((mbX << 2) + 4, list), x.getMv(1, list), + x.getMv(5, list), topAvailable, topAvailable, topRightAvailable, true, x, 2, 3, 6, 7, + mBlock.pb8x8.refIdx[list][1], mbb[list], 8, list); + } + + if (H264Const.usesList(bPartPredModes[mBlock.pb8x8.subMbTypes[2]], list)) { + decodeSubMb8x8(mBlock, 2, bSubMbTypes[mBlock.pb8x8.subMbTypes[2]], refs[list], mbX << 6, + (mbY << 6) + 32, s.mvLeft.getMv(1, list), x.getMv(4, list), x.getMv(5, list), x.getMv(6, list), + s.mvLeft.getMv(2, list), s.mvLeft.getMv(3, list), leftAvailable, true, true, leftAvailable, x, + 8, 9, 12, 13, mBlock.pb8x8.refIdx[list][2], mbb[list], 128, list); + } + + if (H264Const.usesList(bPartPredModes[mBlock.pb8x8.subMbTypes[3]], list)) { + decodeSubMb8x8(mBlock, 3, bSubMbTypes[mBlock.pb8x8.subMbTypes[3]], refs[list], (mbX << 6) + 32, + (mbY << 6) + 32, x.getMv(5, list), x.getMv(6, list), x.getMv(7, list), NULL_VECTOR, + x.getMv(9, list), x.getMv(13, list), true, true, false, true, x, 10, 11, 14, 15, + mBlock.pb8x8.refIdx[list][3], mbb[list], 136, list); + } + } + + for (int i = 0; i < 4; i++) { + int blk4x4 = BLK8x8_BLOCKS[i][0]; + mergePrediction(sh, x.mv0R(blk4x4), x.mv1R(blk4x4), bPartPredModes[mBlock.pb8x8.subMbTypes[i]], 0, + mbb[0].getPlaneData(0), mbb[1].getPlaneData(0), BLK_8x8_MB_OFF_LUMA[i], 16, 8, 8, + mb.getPlaneData(0), refs, poc); + } + + savePrediction8x8(s, mbX, x); + } + + private void decodeSubMb8x8(MBlock mBlock, int partNo, int subMbType, Picture[] references, int offX, int offY, + int tl, int t0, int t1, int tr, int l0, int l1, boolean tlAvb, boolean tAvb, boolean trAvb, boolean lAvb, + MvList x, int i00, int i01, int i10, int i11, int refIdx, Picture mb, int off, int list) { + + switch (subMbType) { + case 3: + decodeSub4x4(mBlock, partNo, references, offX, offY, tl, t0, t1, tr, l0, l1, tlAvb, tAvb, trAvb, lAvb, x, + i00, i01, i10, i11, refIdx, mb, off, list); + break; + case 2: + decodeSub4x8(mBlock, partNo, references, offX, offY, tl, t0, t1, tr, l0, tlAvb, tAvb, trAvb, lAvb, x, i00, + i01, i10, i11, refIdx, mb, off, list); + break; + case 1: + decodeSub8x4(mBlock, partNo, references, offX, offY, tl, t0, tr, l0, l1, tlAvb, tAvb, trAvb, lAvb, x, i00, + i01, i10, i11, refIdx, mb, off, list); + break; + case 0: + decodeSub8x8(mBlock, partNo, references, offX, offY, tl, t0, tr, l0, tlAvb, tAvb, trAvb, lAvb, x, i00, i01, + i10, i11, refIdx, mb, off, list); + } + } + + private void decodeSub8x8(MBlock mBlock, int partNo, Picture[] references, int offX, int offY, int tl, + int t0, int tr, int l0, boolean tlAvb, boolean tAvb, boolean trAvb, boolean lAvb, MvList x, int i00, + int i01, int i10, int i11, int refIdx, Picture mb, int off, int list) { + + int mvpX = calcMVPredictionMedian(l0, t0, tr, tl, lAvb, tAvb, trAvb, tlAvb, refIdx, 0); + int mvpY = calcMVPredictionMedian(l0, t0, tr, tl, lAvb, tAvb, trAvb, tlAvb, refIdx, 1); + + int mv = packMv(mBlock.pb8x8.mvdX1[list][partNo] + mvpX, mBlock.pb8x8.mvdY1[list][partNo] + mvpY, refIdx); + + x.setMv(i00, list, mv); + x.setMv(i01, list, mv); + x.setMv(i10, list, mv); + x.setMv(i11, list, mv); + + debugPrint("MVP: (%d, %d), MVD: (%d, %d), MV: (%d,%d,%d)", mvpX, mvpY, mBlock.pb8x8.mvdX1[list][partNo], + mBlock.pb8x8.mvdY1[list][partNo], mvX(mv), mvY(mv), refIdx); + + interpolator.getBlockLuma(references[refIdx], mb, off, offX + mvX(mv), offY + mvY(mv), 8, 8); + } + + private void decodeSub8x4(MBlock mBlock, int partNo, Picture[] references, int offX, int offY, int tl, + int t0, int tr, int l0, int l1, boolean tlAvb, boolean tAvb, boolean trAvb, boolean lAvb, MvList x, + int i00, int i01, int i10, int i11, int refIdx, Picture mb, int off, int list) { + + int mvpX1 = calcMVPredictionMedian(l0, t0, tr, tl, lAvb, tAvb, trAvb, tlAvb, refIdx, 0); + int mvpY1 = calcMVPredictionMedian(l0, t0, tr, tl, lAvb, tAvb, trAvb, tlAvb, refIdx, 1); + + // TODO(stan): check if MVs need to be clipped + int mv1 = packMv(mBlock.pb8x8.mvdX1[list][partNo] + mvpX1, mBlock.pb8x8.mvdY1[list][partNo] + mvpY1, refIdx); + x.setMv(i00, list, mv1); + x.setMv(i01, list, mv1); + + debugPrint("MVP: (%d, %d), MVD: (%d, %d), MV: (%d,%d,%d)", mvpX1, mvpY1, mBlock.pb8x8.mvdX1[list][partNo], + mBlock.pb8x8.mvdY1[list][partNo], mvX(mv1), mvY(mv1), refIdx); + + int mvpX2 = calcMVPredictionMedian(l1, mv1, NULL_VECTOR, l0, lAvb, true, false, lAvb, refIdx, 0); + int mvpY2 = calcMVPredictionMedian(l1, mv1, NULL_VECTOR, l0, lAvb, true, false, lAvb, refIdx, 1); + + int mv2 = packMv(mBlock.pb8x8.mvdX2[list][partNo] + mvpX2, mBlock.pb8x8.mvdY2[list][partNo] + mvpY2, refIdx); + x.setMv(i10, list, mv2); + x.setMv(i11, list, mv2); + + debugPrint("MVP: (%d, %d), MVD: (%d, %d), MV: (%d,%d,%d)", mvpX2, mvpY2, mBlock.pb8x8.mvdX2[list][partNo], + mBlock.pb8x8.mvdY2[list][partNo], mvX(mv2), mvY(mv2), refIdx); + + interpolator.getBlockLuma(references[refIdx], mb, off, offX + mvX(mv1), offY + mvY(mv1), 8, 4); + interpolator.getBlockLuma(references[refIdx], mb, off + mb.getWidth() * 4, offX + mvX(mv2), + offY + mvY(mv2) + 16, 8, 4); + } + + private void decodeSub4x8(MBlock mBlock, int partNo, Picture[] references, int offX, int offY, int tl, int t0, + int t1, int tr, int l0, boolean tlAvb, boolean tAvb, boolean trAvb, boolean lAvb, MvList x, int i00, + int i01, int i10, int i11, int refIdx, Picture mb, int off, int list) { + + int mvpX1 = calcMVPredictionMedian(l0, t0, t1, tl, lAvb, tAvb, tAvb, tlAvb, refIdx, 0); + int mvpY1 = calcMVPredictionMedian(l0, t0, t1, tl, lAvb, tAvb, tAvb, tlAvb, refIdx, 1); + + int mv1 = packMv(mBlock.pb8x8.mvdX1[list][partNo] + mvpX1, mBlock.pb8x8.mvdY1[list][partNo] + mvpY1, refIdx); + x.setMv(i00, list, mv1); + x.setMv(i10, list, mv1); + + debugPrint("MVP: (%d, %d), MVD: (%d, %d), MV: (%d,%d,%d)", mvpX1, mvpY1, mBlock.pb8x8.mvdX1[list][partNo], + mBlock.pb8x8.mvdY1[list][partNo], mvX(mv1), mvY(mv1), refIdx); + + int mvpX2 = calcMVPredictionMedian(mv1, t1, tr, t0, true, tAvb, trAvb, tAvb, refIdx, 0); + int mvpY2 = calcMVPredictionMedian(mv1, t1, tr, t0, true, tAvb, trAvb, tAvb, refIdx, 1); + + int mv2 = packMv(mBlock.pb8x8.mvdX2[list][partNo] + mvpX2, mBlock.pb8x8.mvdY2[list][partNo] + mvpY2, refIdx); + + x.setMv(i01, list, mv2); + x.setMv(i11, list, mv2); + + debugPrint("MVP: (%d, %d), MVD: (%d, %d), MV: (%d,%d,%d)", mvpX2, mvpY2, mBlock.pb8x8.mvdX2[list][partNo], + mBlock.pb8x8.mvdY2[list][partNo], mvX(mv2), mvY(mv2), refIdx); + + interpolator.getBlockLuma(references[refIdx], mb, off, offX + mvX(mv1), offY + mvY(mv1), 4, 8); + interpolator.getBlockLuma(references[refIdx], mb, off + 4, offX + mvX(mv2) + 16, offY + mvY(mv2), 4, 8); + } + + private void decodeSub4x4(MBlock mBlock, int partNo, Picture[] references, int offX, int offY, int tl, + int t0, int t1, int tr, int l0, int l1, boolean tlAvb, boolean tAvb, boolean trAvb, boolean lAvb, MvList x, + int i00, int i01, int i10, int i11, int refIdx, Picture mb, int off, int list) { + + int mvpX1 = calcMVPredictionMedian(l0, t0, t1, tl, lAvb, tAvb, tAvb, tlAvb, refIdx, 0); + int mvpY1 = calcMVPredictionMedian(l0, t0, t1, tl, lAvb, tAvb, tAvb, tlAvb, refIdx, 1); + + int mv1 = packMv(mBlock.pb8x8.mvdX1[list][partNo] + mvpX1, mBlock.pb8x8.mvdY1[list][partNo] + mvpY1, refIdx); + x.setMv(i00, list, mv1); + debugPrint("MVP: (%d, %d), MVD: (%d, %d), MV: (%d,%d,%d)", mvpX1, mvpY1, mBlock.pb8x8.mvdX1[list][partNo], + mBlock.pb8x8.mvdY1[list][partNo], mvX(mv1), mvY(mv1), refIdx); + + int mvpX2 = calcMVPredictionMedian(mv1, t1, tr, t0, true, tAvb, trAvb, tAvb, refIdx, 0); + int mvpY2 = calcMVPredictionMedian(mv1, t1, tr, t0, true, tAvb, trAvb, tAvb, refIdx, 1); + + int mv2 = packMv(mBlock.pb8x8.mvdX2[list][partNo] + mvpX2, mBlock.pb8x8.mvdY2[list][partNo] + mvpY2, refIdx); + x.setMv(i01, list, mv2); + + debugPrint("MVP: (%d, %d), MVD: (%d, %d), MV: (%d,%d,%d)", mvpX2, mvpY2, mBlock.pb8x8.mvdX2[list][partNo], + mBlock.pb8x8.mvdY2[list][partNo], mvX(mv2), mvY(mv2), refIdx); + + int mvpX3 = calcMVPredictionMedian(l1, mv1, mv2, l0, lAvb, true, true, lAvb, refIdx, 0); + int mvpY3 = calcMVPredictionMedian(l1, mv1, mv2, l0, lAvb, true, true, lAvb, refIdx, 1); + + int mv3 = packMv(mBlock.pb8x8.mvdX3[list][partNo] + mvpX3, mBlock.pb8x8.mvdY3[list][partNo] + mvpY3, refIdx); + x.setMv(i10, list, mv3); + + debugPrint("MVP: (%d, %d), MVD: (%d, %d), MV: (%d,%d,%d)", mvpX3, mvpY3, mBlock.pb8x8.mvdX3[list][partNo], + mBlock.pb8x8.mvdY3[list][partNo], mvX(mv3), mvY(mv3), refIdx); + + int mvpX4 = calcMVPredictionMedian(mv3, mv2, NULL_VECTOR, mv1, true, true, false, true, refIdx, 0); + int mvpY4 = calcMVPredictionMedian(mv3, mv2, NULL_VECTOR, mv1, true, true, false, true, refIdx, 1); + + int mv4 = packMv(mBlock.pb8x8.mvdX4[list][partNo] + mvpX4, mBlock.pb8x8.mvdY4[list][partNo] + mvpY4, refIdx); + x.setMv(i11, list, mv4); + + debugPrint("MVP: (%d, %d), MVD: (%d, %d), MV: (%d,%d,%d)", mvpX4, mvpY4, mBlock.pb8x8.mvdX4[list][partNo], + mBlock.pb8x8.mvdY4[list][partNo], mvX(mv4), mvY(mv4), refIdx); + + interpolator.getBlockLuma(references[refIdx], mb, off, offX + mvX(mv1), offY + mvY(mv1), 4, 4); + interpolator.getBlockLuma(references[refIdx], mb, off + 4, offX + mvX(mv2) + 16, offY + mvY(mv2), 4, 4); + interpolator.getBlockLuma(references[refIdx], mb, off + mb.getWidth() * 4, offX + mvX(mv3), offY + mvY(mv3) + + 16, 4, 4); + interpolator.getBlockLuma(references[refIdx], mb, off + mb.getWidth() * 4 + 4, offX + mvX(mv4) + 16, offY + + mvY(mv4) + 16, 4, 4); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderIntra16x16.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderIntra16x16.java new file mode 100644 index 0000000..29bcf10 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderIntra16x16.java @@ -0,0 +1,65 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.decode.aso.Mapper; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.common.model.Picture; + +import static org.monte.media.impl.jcodec.codecs.h264.decode.CoeffTransformer.reorderDC4x4; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.collectPredictors; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.saveMvsIntra; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.saveVectIntra; + +/** + * A decoder for I16x16 macroblocks + * + * @author The JCodec project + */ +public class MBlockDecoderIntra16x16 extends MBlockDecoderBase { + + private Mapper mapper; + + public MBlockDecoderIntra16x16(Mapper mapper, SliceHeader sh, DeblockerInput di, int poc, + DecoderState decoderState) { + super(sh, di, poc, decoderState); + this.mapper = mapper; + } + + public void decode(MBlock mBlock, Picture mb) { + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + int address = mapper.getAddress(mBlock.mbIdx); + boolean leftAvailable = mapper.leftAvailable(mBlock.mbIdx); + boolean topAvailable = mapper.topAvailable(mBlock.mbIdx); + s.qp = (s.qp + mBlock.mbQPDelta + 52) % 52; + di.mbQps[0][address] = s.qp; + + residualLumaI16x16(mBlock, leftAvailable, topAvailable, mbX, mbY); + + Intra16x16PredictionBuilder.predictWithMode(mBlock.luma16x16Mode, mBlock.ac[0], leftAvailable, topAvailable, + s.leftRow[0], s.topLine[0], s.topLeft[0], mbX << 4, mb.getPlaneData(0)); + + decodeChroma(mBlock, mbX, mbY, leftAvailable, topAvailable, mb, s.qp); + di.mbTypes[address] = mBlock.curMbType; + + collectPredictors(s, mb, mbX); + saveMvsIntra(di, mbX, mbY); + saveVectIntra(s, mapper.getMbX(mBlock.mbIdx)); + } + + private void residualLumaI16x16(MBlock mBlock, boolean leftAvailable, boolean topAvailable, int mbX, int mbY) { + CoeffTransformer.invDC4x4(mBlock.dc); + int[] scalingList = getScalingList(0); + CoeffTransformer.dequantizeDC4x4(mBlock.dc, s.qp, scalingList); + reorderDC4x4(mBlock.dc); + + for (int bInd = 0; bInd < 16; bInd++) { + int ind8x8 = bInd >> 2; + int mask = 1 << ind8x8; + if ((mBlock.cbpLuma() & mask) != 0) { + CoeffTransformer.dequantizeAC(mBlock.ac[0][bInd], s.qp, scalingList); + } + mBlock.ac[0][bInd][0] = mBlock.dc[bInd]; + CoeffTransformer.idct4x4(mBlock.ac[0][bInd]); + } + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderIntraNxN.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderIntraNxN.java new file mode 100644 index 0000000..f37ef11 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderIntraNxN.java @@ -0,0 +1,81 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const; +import org.monte.media.impl.jcodec.codecs.h264.decode.aso.Mapper; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.common.model.Picture; + +/** + * A decoder for I16x16 macroblocks + * + * @author The JCodec project + */ +public class MBlockDecoderIntraNxN extends MBlockDecoderBase { + private Mapper mapper; + private Intra8x8PredictionBuilder prediction8x8Builder; + + public MBlockDecoderIntraNxN(Mapper mapper, SliceHeader sh, DeblockerInput di, int poc, + DecoderState decoderState) { + super(sh, di, poc, decoderState); + this.mapper = mapper; + this.prediction8x8Builder = new Intra8x8PredictionBuilder(); + } + + public void decode(MBlock mBlock, Picture mb) { + + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + + int mbAddr = mapper.getAddress(mBlock.mbIdx); + boolean leftAvailable = mapper.leftAvailable(mBlock.mbIdx); + boolean topAvailable = mapper.topAvailable(mBlock.mbIdx); + boolean topLeftAvailable = mapper.topLeftAvailable(mBlock.mbIdx); + boolean topRightAvailable = mapper.topRightAvailable(mBlock.mbIdx); + + if (mBlock.cbpLuma() > 0 || mBlock.cbpChroma() > 0) { + s.qp = (s.qp + mBlock.mbQPDelta + 52) % 52; + } + di.mbQps[0][mbAddr] = s.qp; + + residualLuma(mBlock, leftAvailable, topAvailable, mbX, mbY); + + if (!mBlock.transform8x8Used) { + for (int bInd = 0; bInd < 16; bInd++) { + int dInd = H264Const.BLK_DISP_MAP[bInd]; + int blkX = (dInd & 3) << 2; + int blkY = dInd & ~3; + + boolean trAvailable = ((bInd == 0 || bInd == 1 || bInd == 4) && topAvailable) + || (bInd == 5 && topRightAvailable) || bInd == 2 || bInd == 6 || bInd == 8 || bInd == 9 || bInd == 10 + || bInd == 12 || bInd == 14; + + Intra4x4PredictionBuilder.predictWithMode(mBlock.lumaModes[bInd], mBlock.ac[0][bInd], + blkX == 0 ? leftAvailable : true, blkY == 0 ? topAvailable : true, trAvailable, s.leftRow[0], + s.topLine[0], s.topLeft[0], (mbX << 4), blkX, blkY, mb.getPlaneData(0)); + } + } else { + for (int i = 0; i < 4; i++) { + int blkX = (i & 1) << 1; + int blkY = i & 2; + + boolean trAvailable = (i == 0 && topAvailable) || (i == 1 && topRightAvailable) || i == 2; + boolean tlAvailable = i == 0 ? topLeftAvailable : (i == 1 ? topAvailable : (i == 2 ? leftAvailable + : true)); + + prediction8x8Builder.predictWithMode(mBlock.lumaModes[i], mBlock.ac[0][i], + blkX == 0 ? leftAvailable : true, blkY == 0 ? topAvailable : true, tlAvailable, trAvailable, + s.leftRow[0], s.topLine[0], s.topLeft[0], (mbX << 4), blkX << 2, blkY << 2, mb.getPlaneData(0)); + } + } + + decodeChroma(mBlock, mbX, mbY, leftAvailable, topAvailable, mb, s.qp); + + di.mbTypes[mbAddr] = mBlock.curMbType; + di.tr8x8Used[mbAddr] = mBlock.transform8x8Used; + + MBlockDecoderUtils.collectChromaPredictors(s, mb, mbX); + + MBlockDecoderUtils.saveMvsIntra(di, mbX, mbY); + MBlockDecoderUtils.saveVectIntra(s, mapper.getMbX(mBlock.mbIdx)); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderUtils.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderUtils.java new file mode 100644 index 0000000..12f4896 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockDecoderUtils.java @@ -0,0 +1,154 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Utils.MvList; +import org.monte.media.impl.jcodec.common.logging.Logger; +import org.monte.media.impl.jcodec.common.model.Picture; + +import static java.lang.System.arraycopy; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvC; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvRef; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.packMv; +import static org.monte.media.impl.jcodec.common.ArrayUtil.shiftLeft1; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +public class MBlockDecoderUtils { + private static boolean debug; + public static final int NULL_VECTOR = packMv(0, 0, -1); + + public static void debugPrint(Object... arguments) { + if (debug && arguments.length > 0) { + if (arguments.length == 1) { + Logger.debug("" + arguments[0]); + } else { + String fmt = (String) arguments[0]; + shiftLeft1(arguments); + Logger.debug(String.format(fmt, arguments)); + } + } + } + + static void collectPredictors(DecoderState sharedState, Picture outMB, int mbX) { + sharedState.topLeft[0][0] = sharedState.topLine[0][(mbX << 4) + 15]; + sharedState.topLeft[0][1] = outMB.getPlaneData(0)[63]; + sharedState.topLeft[0][2] = outMB.getPlaneData(0)[127]; + sharedState.topLeft[0][3] = outMB.getPlaneData(0)[191]; + arraycopy(outMB.getPlaneData(0), 240, sharedState.topLine[0], mbX << 4, 16); + copyCol(outMB.getPlaneData(0), 16, 15, 16, sharedState.leftRow[0]); + + collectChromaPredictors(sharedState, outMB, mbX); + } + + static void collectChromaPredictors(DecoderState sharedState, Picture outMB, int mbX) { + sharedState.topLeft[1][0] = sharedState.topLine[1][(mbX << 3) + 7]; + sharedState.topLeft[2][0] = sharedState.topLine[2][(mbX << 3) + 7]; + + arraycopy(outMB.getPlaneData(1), 56, sharedState.topLine[1], mbX << 3, 8); + arraycopy(outMB.getPlaneData(2), 56, sharedState.topLine[2], mbX << 3, 8); + + copyCol(outMB.getPlaneData(1), 8, 7, 8, sharedState.leftRow[1]); + copyCol(outMB.getPlaneData(2), 8, 7, 8, sharedState.leftRow[2]); + } + + private static void copyCol(byte[] planeData, int n, int off, int stride, byte[] out) { + for (int i = 0; i < n; i++, off += stride) { + out[i] = planeData[off]; + } + } + + static void saveMvsIntra(DeblockerInput di, int mbX, int mbY) { + for (int j = 0, blkOffY = mbY << 2, blkInd = 0; j < 4; j++, blkOffY++) { + for (int i = 0, blkOffX = mbX << 2; i < 4; i++, blkOffX++, blkInd++) { + di.mvs.setMv(blkOffX, blkOffY, 0, NULL_VECTOR); + di.mvs.setMv(blkOffX, blkOffY, 1, NULL_VECTOR); + } + } + } + + static void mergeResidual(Picture mb, int[][][] residual, int[][] blockLUT, int[][] posLUT) { + for (int comp = 0; comp < 3; comp++) { + byte[] to = mb.getPlaneData(comp); + for (int i = 0; i < to.length; i++) { + to[i] = (byte) clip(to[i] + residual[comp][blockLUT[comp][i]][posLUT[comp][i]], -128, 127); + } + } + } + + static void saveVect(MvList mv, int list, int from, int to, int vect) { + for (int i = from; i < to; i++) { + mv.setMv(i, list, vect); + } + } + + /** + * Calculates median prediction + * + * @param a, b, c and d are packed motion vectors + */ + public static int calcMVPredictionMedian(int a, int b, int c, int d, boolean aAvb, boolean bAvb, boolean cAvb, + boolean dAvb, int ref, int comp) { + + if (!cAvb) { + c = d; + cAvb = dAvb; + } + + if (aAvb && !bAvb && !cAvb) { + b = c = a; + bAvb = cAvb = aAvb; + } + + a = aAvb ? a : NULL_VECTOR; + b = bAvb ? b : NULL_VECTOR; + c = cAvb ? c : NULL_VECTOR; + + if (mvRef(a) == ref && mvRef(b) != ref && mvRef(c) != ref) + return mvC(a, comp); + else if (mvRef(b) == ref && mvRef(a) != ref && mvRef(c) != ref) + return mvC(b, comp); + else if (mvRef(c) == ref && mvRef(a) != ref && mvRef(b) != ref) + return mvC(c, comp); + + return mvC(a, comp) + mvC(b, comp) + mvC(c, comp) - min(mvC(a, comp), mvC(b, comp), mvC(c, comp)) + - max(mvC(a, comp), mvC(b, comp), mvC(c, comp)); + } + + public static int max(int x, int x2, int x3) { + return x > x2 ? (x > x3 ? x : x3) : (x2 > x3 ? x2 : x3); + } + + public static int min(int x, int x2, int x3) { + return x < x2 ? (x < x3 ? x : x3) : (x2 < x3 ? x2 : x3); + } + + static void saveMvs(DeblockerInput di, MvList x, int mbX, int mbY) { + for (int j = 0, blkOffY = mbY << 2, blkInd = 0; j < 4; j++, blkOffY++) { + for (int i = 0, blkOffX = mbX << 2; i < 4; i++, blkOffX++, blkInd++) { + di.mvs.setMv(blkOffX, blkOffY, 0, x.getMv(blkInd, 0)); + di.mvs.setMv(blkOffX, blkOffY, 1, x.getMv(blkInd, 1)); + } + } + } + + static void savePrediction8x8(DecoderState sharedState, int mbX, MvList x) { + sharedState.mvTopLeft.copyPair(0, sharedState.mvTop, (mbX << 2) + 3); + sharedState.mvLeft.copyPair(0, x, 3); + sharedState.mvLeft.copyPair(1, x, 7); + sharedState.mvLeft.copyPair(2, x, 11); + sharedState.mvLeft.copyPair(3, x, 15); + sharedState.mvTop.copyPair(mbX << 2, x, 12); + sharedState.mvTop.copyPair((mbX << 2) + 1, x, 13); + sharedState.mvTop.copyPair((mbX << 2) + 2, x, 14); + sharedState.mvTop.copyPair((mbX << 2) + 3, x, 15); + } + + public static void saveVectIntra(DecoderState sharedState, int mbX) { + int xx = mbX << 2; + + sharedState.mvTopLeft.copyPair(0, sharedState.mvTop, xx + 3); + + saveVect(sharedState.mvTop, 0, xx, xx + 4, NULL_VECTOR); + saveVect(sharedState.mvLeft, 0, 0, 4, NULL_VECTOR); + saveVect(sharedState.mvTop, 1, xx, xx + 4, NULL_VECTOR); + saveVect(sharedState.mvLeft, 1, 0, 4, NULL_VECTOR); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockReader.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockReader.java new file mode 100644 index 0000000..5613629 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockReader.java @@ -0,0 +1,10 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +/** + * A reader for H.264 macroblocks + * + * @author The JCodec project + */ +public class MBlockReader { + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockSkipDecoder.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockSkipDecoder.java new file mode 100644 index 0000000..d11e994 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/MBlockSkipDecoder.java @@ -0,0 +1,102 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred; +import org.monte.media.impl.jcodec.codecs.h264.H264Utils.MvList; +import org.monte.media.impl.jcodec.codecs.h264.decode.aso.Mapper; +import org.monte.media.impl.jcodec.codecs.h264.io.model.Frame; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.common.model.Picture; + +import java.util.Arrays; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.L0; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.identityMapping4; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.packMv; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.NULL_VECTOR; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.calcMVPredictionMedian; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.collectPredictors; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.saveMvs; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.savePrediction8x8; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.saveVect; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType.P; + +/** + * A decoder for P skip macroblocks + * + * @author The JCodec project + */ +public class MBlockSkipDecoder extends MBlockDecoderBase { + private Mapper mapper; + private MBlockDecoderBDirect bDirectDecoder; + + public MBlockSkipDecoder(Mapper mapper, MBlockDecoderBDirect bDirectDecoder, + SliceHeader sh, DeblockerInput di, int poc, DecoderState sharedState) { + super(sh, di, poc, sharedState); + this.mapper = mapper; + this.bDirectDecoder = bDirectDecoder; + } + + public void decodeSkip(MBlock mBlock, Frame[][] refs, Picture mb, SliceType sliceType) { + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + int mbAddr = mapper.getAddress(mBlock.mbIdx); + + if (sliceType == P) { + predictPSkip(refs, mbX, mbY, mapper.leftAvailable(mBlock.mbIdx), mapper.topAvailable(mBlock.mbIdx), + mapper.topLeftAvailable(mBlock.mbIdx), mapper.topRightAvailable(mBlock.mbIdx), mBlock.x, mb); + Arrays.fill(mBlock.partPreds, PartPred.L0); + } else { + bDirectDecoder.predictBDirect(refs, mbX, mbY, mapper.leftAvailable(mBlock.mbIdx), + mapper.topAvailable(mBlock.mbIdx), mapper.topLeftAvailable(mBlock.mbIdx), + mapper.topRightAvailable(mBlock.mbIdx), mBlock.x, mBlock.partPreds, mb, identityMapping4); + savePrediction8x8(s, mbX, mBlock.x); + } + + decodeChromaSkip(refs, mBlock.x, mBlock.partPreds, mbX, mbY, mb); + + collectPredictors(s, mb, mbX); + + saveMvs(di, mBlock.x, mbX, mbY); + di.mbTypes[mbAddr] = mBlock.curMbType; + di.mbQps[0][mbAddr] = s.qp; + di.mbQps[1][mbAddr] = calcQpChroma(s.qp, s.chromaQpOffset[0]); + di.mbQps[2][mbAddr] = calcQpChroma(s.qp, s.chromaQpOffset[1]); + } + + public void predictPSkip(Frame[][] refs, int mbX, int mbY, boolean lAvb, boolean tAvb, boolean tlAvb, + boolean trAvb, MvList x, Picture mb) { + int mvX = 0, mvY = 0; + if (lAvb && tAvb) { + int b = s.mvTop.getMv(mbX << 2, 0); + int a = s.mvLeft.getMv(0, 0); + + if ((a != 0) && (b != 0)) { + mvX = calcMVPredictionMedian(a, b, s.mvTop.getMv((mbX << 2) + 4, 0), s.mvTopLeft.getMv(0, 0), lAvb, + tAvb, trAvb, tlAvb, 0, 0); + mvY = calcMVPredictionMedian(a, b, s.mvTop.getMv((mbX << 2) + 4, 0), s.mvTopLeft.getMv(0, 0), lAvb, + tAvb, trAvb, tlAvb, 0, 1); + } + } + + int xx = mbX << 2; + s.mvTopLeft.copyPair(0, s.mvTop, xx + 3); + saveVect(s.mvTop, 0, xx, xx + 4, packMv(mvX, mvY, 0)); + saveVect(s.mvLeft, 0, 0, 4, packMv(mvX, mvY, 0)); + saveVect(s.mvTop, 1, xx, xx + 4, NULL_VECTOR); + saveVect(s.mvLeft, 1, 0, 4, NULL_VECTOR); + + for (int i = 0; i < 16; i++) { + x.setMv(i, 0, packMv(mvX, mvY, 0)); + } + interpolator.getBlockLuma(refs[0][0], mb, 0, (mbX << 6) + mvX, (mbY << 6) + mvY, 16, 16); + + PredictionMerger.mergePrediction(sh, 0, 0, L0, 0, mb.getPlaneData(0), null, 0, 16, 16, 16, mb.getPlaneData(0), + refs, poc); + } + + public void decodeChromaSkip(Frame[][] reference, MvList vectors, PartPred[] pp, int mbX, int mbY, Picture mb) { + predictChromaInter(reference, vectors, mbX << 3, mbY << 3, 1, mb, pp); + predictChromaInter(reference, vectors, mbX << 3, mbY << 3, 2, mb, pp); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/PredictionMerger.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/PredictionMerger.java new file mode 100644 index 0000000..94b372e --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/PredictionMerger.java @@ -0,0 +1,150 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred; +import org.monte.media.impl.jcodec.codecs.h264.io.model.Frame; +import org.monte.media.impl.jcodec.codecs.h264.io.model.PictureParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.PredictionWeightTable; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.Bi; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.L0; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.L1; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Prediction merge and weight routines + * + * @author The JCodec project + */ +public class PredictionMerger { + + public static void mergePrediction(SliceHeader sh, int refIdxL0, int refIdxL1, PartPred predType, int comp, + byte[] pred0, byte[] pred1, int off, int stride, int blkW, int blkH, byte[] out, Frame[][] refs, int thisPoc) { + + PictureParameterSet pps = sh.pps; + if (sh.sliceType == SliceType.P) { + weightPrediction(sh, refIdxL0, comp, pred0, off, stride, blkW, blkH, out); + } else { + if (!pps.weightedPredFlag || sh.pps.weightedBipredIdc == 0 + || (sh.pps.weightedBipredIdc == 2 && predType != Bi)) { + mergeAvg(pred0, pred1, stride, predType, off, blkW, blkH, out); + } else if (sh.pps.weightedBipredIdc == 1) { + PredictionWeightTable w = sh.predWeightTable; + int w0 = refIdxL0 == -1 ? 0 : (comp == 0 ? w.lumaWeight[0][refIdxL0] + : w.chromaWeight[0][comp - 1][refIdxL0]); + int w1 = refIdxL1 == -1 ? 0 : (comp == 0 ? w.lumaWeight[1][refIdxL1] + : w.chromaWeight[1][comp - 1][refIdxL1]); + int o0 = refIdxL0 == -1 ? 0 : (comp == 0 ? w.lumaOffset[0][refIdxL0] + : w.chromaOffset[0][comp - 1][refIdxL0]); + int o1 = refIdxL1 == -1 ? 0 : (comp == 0 ? w.lumaOffset[1][refIdxL1] + : w.chromaOffset[1][comp - 1][refIdxL1]); + mergeWeight(pred0, pred1, stride, predType, off, blkW, blkH, comp == 0 ? w.lumaLog2WeightDenom + : w.chromaLog2WeightDenom, w0, w1, o0, o1, out); + } else { + int tb = MathUtil.clip(thisPoc - refs[0][refIdxL0].getPOC(), -128, 127); + int td = MathUtil.clip(refs[1][refIdxL1].getPOC() - refs[0][refIdxL0].getPOC(), -128, 127); + int w0 = 32, w1 = 32; + if (td != 0 && refs[0][refIdxL0].isShortTerm() && refs[1][refIdxL1].isShortTerm()) { + int tx = (16384 + Math.abs(td / 2)) / td; + int dsf = clip((tb * tx + 32) >> 6, -1024, 1023) >> 2; + + if (dsf >= -64 && dsf <= 128) { + w1 = dsf; + w0 = 64 - dsf; + } + } + + mergeWeight(pred0, pred1, stride, predType, off, blkW, blkH, 5, w0, w1, 0, 0, out); + } + } + } + + public static void weightPrediction(SliceHeader sh, int refIdxL0, int comp, byte[] pred0, int off, int stride, + int blkW, int blkH, byte[] out) { + PictureParameterSet pps = sh.pps; + if (pps.weightedPredFlag && sh.predWeightTable != null) { + PredictionWeightTable w = sh.predWeightTable; + weight(pred0, stride, off, blkW, blkH, comp == 0 ? w.lumaLog2WeightDenom + : w.chromaLog2WeightDenom, comp == 0 ? w.lumaWeight[0][refIdxL0] + : w.chromaWeight[0][comp - 1][refIdxL0], comp == 0 ? w.lumaOffset[0][refIdxL0] + : w.chromaOffset[0][comp - 1][refIdxL0], out); + } else { + copyPrediction(pred0, stride, off, blkW, blkH, out); + } + } + + private static void mergeAvg(byte[] blk0, byte[] blk1, int stride, PartPred p0, int off, int blkW, int blkH, + byte[] out) { + if (p0 == Bi) + _mergePrediction(blk0, blk1, stride, p0, off, blkW, blkH, out); + else if (p0 == L0) + copyPrediction(blk0, stride, off, blkW, blkH, out); + else if (p0 == L1) + copyPrediction(blk1, stride, off, blkW, blkH, out); + + } + + private static void mergeWeight(byte[] blk0, byte[] blk1, int stride, PartPred partPred, int off, int blkW, + int blkH, int logWD, int w0, int w1, int o0, int o1, byte[] out) { + if (partPred == L0) { + weight(blk0, stride, off, blkW, blkH, logWD, w0, o0, out); + } else if (partPred == L1) { + weight(blk1, stride, off, blkW, blkH, logWD, w1, o1, out); + } else if (partPred == Bi) { + _weightPrediction(blk0, blk1, stride, off, blkW, blkH, logWD, w0, w1, o0, o1, out); + } + } + + private static void copyPrediction(byte[] _in, int stride, int off, int blkW, int blkH, byte[] out) { + + for (int i = 0; i < blkH; i++, off += stride - blkW) + for (int j = 0; j < blkW; j++, off++) + out[off] = _in[off]; + } + + private static void _mergePrediction(byte[] blk0, byte[] blk1, int stride, PartPred p0, int off, int blkW, int blkH, + byte[] out) { + + for (int i = 0; i < blkH; i++, off += stride - blkW) + for (int j = 0; j < blkW; j++, off++) + out[off] = (byte) ((blk0[off] + blk1[off] + 1) >> 1); + } + + private static void _weightPrediction(byte[] blk0, byte[] blk1, int stride, int off, int blkW, int blkH, int logWD, + int w0, int w1, int o0, int o1, byte[] out) { + // Necessary to correctly scale in [-128, 127] range + int round = (1 << logWD) + ((w0 + w1) << 7); + int sum = ((o0 + o1 + 1) >> 1) - 128; + int logWDCP1 = logWD + 1; + for (int i = 0; i < blkH; i++, off += stride - blkW) + for (int j = 0; j < blkW; j++, off++) { + out[off] = (byte) clip(((blk0[off] * w0 + blk1[off] * w1 + round) >> logWDCP1) + sum, -128, 127); + } + } + + private static void weight(byte[] blk0, int stride, int off, int blkW, int blkH, int logWD, int w, int o, byte[] out) { + int round = 1 << (logWD - 1); + + if (logWD >= 1) { + // Necessary to correctly scale _in [-128, 127] range, + // i.e. x = ay / b; x,y _in [0, 255] is + // x = a * (y + 128) / b - 128; x,y _in [-128, 127] + o -= 128; + round += w << 7; + for (int i = 0; i < blkH; i++, off += stride - blkW) + for (int j = 0; j < blkW; j++, off++) + out[off] = (byte) clip(((blk0[off] * w + round) >> logWD) + o, -128, 127); + } else { + // Necessary to correctly scale in [-128, 127] range + o += (w << 7) - 128; + for (int i = 0; i < blkH; i++, off += stride - blkW) + for (int j = 0; j < blkW; j++, off++) + out[off] = (byte) clip(blk0[off] * w + o, -128, 127); + } + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/RefListManager.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/RefListManager.java new file mode 100644 index 0000000..e44b2a3 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/RefListManager.java @@ -0,0 +1,167 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const; +import org.monte.media.impl.jcodec.codecs.h264.io.model.Frame; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.common.IntObjectMap; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.platform.Platform; + +import java.util.Arrays; +import java.util.Comparator; + +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.debugPrint; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.wrap; + +/** + * Contains reference picture list management logic + * + * @author The JCodec Project + */ +public class RefListManager { + private SliceHeader sh; + private int[] numRef; + private Frame[] sRefs; + private IntObjectMap lRefs; + private Frame frameOut; + + public RefListManager(SliceHeader sh, Frame[] sRefs, IntObjectMap lRefs, Frame frameOut) { + this.sh = sh; + this.sRefs = sRefs; + this.lRefs = lRefs; + if (sh.numRefIdxActiveOverrideFlag) + numRef = new int[]{sh.numRefIdxActiveMinus1[0] + 1, sh.numRefIdxActiveMinus1[1] + 1}; + else + numRef = new int[]{sh.pps.numRefIdxActiveMinus1[0] + 1, sh.pps.numRefIdxActiveMinus1[1] + 1}; + this.frameOut = frameOut; + } + + public Frame[][] getRefList() { + Frame[][] refList = null; + if (sh.sliceType == SliceType.P) { + refList = new Frame[][]{buildRefListP(), null}; + } else if (sh.sliceType == SliceType.B) { + refList = buildRefListB(); + } + + debugPrint("------"); + if (refList != null) { + for (int l = 0; l < 2; l++) { + if (refList[l] != null) + for (int i = 0; i < refList[l].length; i++) + if (refList[l][i] != null) + debugPrint("REF[%d][%d]: ", l, i, ((Frame) refList[l][i]).getPOC()); + } + } + return refList; + } + + private Frame[] buildRefListP() { + int frame_num = sh.frameNum; + int maxFrames = 1 << (sh.sps.log2MaxFrameNumMinus4 + 4); + // int nLongTerm = Math.min(lRefs.size(), numRef[0] - 1); + Frame[] result = new Frame[numRef[0]]; + + int refs = 0; + for (int i = frame_num - 1; i >= frame_num - maxFrames && refs < numRef[0]; i--) { + int fn = i < 0 ? i + maxFrames : i; + if (sRefs[fn] != null) { + result[refs] = sRefs[fn] == H264Const.NO_PIC ? null : sRefs[fn]; + ++refs; + } + } + int[] keys = lRefs.keys(); + Arrays.sort(keys); + for (int i = 0; i < keys.length && refs < numRef[0]; i++) { + result[refs++] = lRefs.get(keys[i]); + } + + reorder(result, 0); + + return result; + } + + private Frame[][] buildRefListB() { + + Frame[] l0 = buildList(Frame.POCDesc, Frame.POCAsc); + Frame[] l1 = buildList(Frame.POCAsc, Frame.POCDesc); + + if (Platform.arrayEqualsObj(l0, l1) && count(l1) > 1) { + Frame frame = l1[1]; + l1[1] = l1[0]; + l1[0] = frame; + } + + Frame[][] result = {Platform.copyOfObj(l0, numRef[0]), Platform.copyOfObj(l1, numRef[1])}; + + reorder(result[0], 0); + reorder(result[1], 1); + + return result; + } + + private Frame[] buildList(Comparator cmpFwd, Comparator cmpInv) { + Frame[] refs = new Frame[sRefs.length + lRefs.size()]; + Frame[] fwd = copySort(cmpFwd, frameOut); + Frame[] inv = copySort(cmpInv, frameOut); + int nFwd = count(fwd); + int nInv = count(inv); + + int ref = 0; + for (int i = 0; i < nFwd; i++, ref++) + refs[ref] = fwd[i]; + for (int i = 0; i < nInv; i++, ref++) + refs[ref] = inv[i]; + + int[] keys = lRefs.keys(); + Arrays.sort(keys); + for (int i = 0; i < keys.length; i++, ref++) + refs[ref] = lRefs.get(keys[i]); + + return refs; + } + + private int count(Frame[] arr) { + for (int nn = 0; nn < arr.length; nn++) + if (arr[nn] == null) + return nn; + return arr.length; + } + + private Frame[] copySort(Comparator fwd, Frame dummy) { + Frame[] copyOf = Platform.copyOfObj(sRefs, sRefs.length); + for (int i = 0; i < copyOf.length; i++) + if (fwd.compare(dummy, copyOf[i]) > 0) + copyOf[i] = null; + Arrays.sort(copyOf, fwd); + return copyOf; + } + + private void reorder(Picture[] result, int list) { + if (sh.refPicReordering[list] == null) + return; + + int predict = sh.frameNum; + int maxFrames = 1 << (sh.sps.log2MaxFrameNumMinus4 + 4); + + for (int ind = 0; ind < sh.refPicReordering[list][0].length; ind++) { + int refType = sh.refPicReordering[list][0][ind]; + int refIdx = sh.refPicReordering[list][1][ind]; + + for (int i = numRef[list] - 1; i > ind; i--) + result[i] = result[i - 1]; + if (refType == 2) { + result[ind] = lRefs.get(refIdx); + } else { + predict = refType == 0 ? wrap(predict - refIdx - 1, maxFrames) : wrap(predict + refIdx + 1, maxFrames); + result[ind] = sRefs[predict]; + } + for (int i = ind + 1, j = i; i < numRef[list] && result[i] != null; i++) { + if (result[i] != sRefs[predict]) + result[j++] = result[i]; + } + } + } + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/SliceDecoder.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/SliceDecoder.java new file mode 100644 index 0000000..bcb7ce0 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/SliceDecoder.java @@ -0,0 +1,215 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const; +import org.monte.media.impl.jcodec.codecs.h264.decode.aso.MapManager; +import org.monte.media.impl.jcodec.codecs.h264.decode.aso.Mapper; +import org.monte.media.impl.jcodec.codecs.h264.io.model.Frame; +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.common.IntObjectMap; +import org.monte.media.impl.jcodec.common.logging.Logger; +import org.monte.media.impl.jcodec.common.model.Picture; + +import static java.lang.System.arraycopy; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.L0; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.debugPrint; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.B_Direct_16x16; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.P_16x16; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.P_16x8; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.P_8x16; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.P_8x8; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.P_8x8ref0; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType.P; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * A decoder for an individual slice + * + * @author The JCodec project + */ +public class SliceDecoder { + + private Mapper mapper; + + private MBlockDecoderIntra16x16 decoderIntra16x16; + private MBlockDecoderIntraNxN decoderIntraNxN; + private MBlockDecoderInter decoderInter; + private MBlockDecoderInter8x8 decoderInter8x8; + private MBlockSkipDecoder skipDecoder; + private MBlockDecoderBDirect decoderBDirect; + private RefListManager refListManager; + private MBlockDecoderIPCM decoderIPCM; + private SliceReader parser; + private SeqParameterSet activeSps; + private Frame frameOut; + private DecoderState decoderState; + private DeblockerInput di; + private IntObjectMap lRefs; + private Frame[] sRefs; + + public SliceDecoder(SeqParameterSet activeSps, Frame[] sRefs, + IntObjectMap lRefs, DeblockerInput di, Frame result) { + this.di = di; + this.activeSps = activeSps; + this.frameOut = result; + this.sRefs = sRefs; + this.lRefs = lRefs; + } + + public void decodeFromReader(SliceReader sliceReader) { + + parser = sliceReader; + + initContext(); + + debugPrint("============%d============= ", frameOut.getPOC()); + + Frame[][] refList = refListManager.getRefList(); + + decodeMacroblocks(refList); + } + + private void initContext() { + + SliceHeader sh = parser.getSliceHeader(); + + decoderState = new DecoderState(sh); + mapper = new MapManager(sh.sps, sh.pps).getMapper(sh); + + decoderIntra16x16 = new MBlockDecoderIntra16x16(mapper, sh, di, frameOut.getPOC(), decoderState); + decoderIntraNxN = new MBlockDecoderIntraNxN(mapper, sh, di, frameOut.getPOC(), decoderState); + decoderInter = new MBlockDecoderInter(mapper, sh, di, frameOut.getPOC(), decoderState); + decoderBDirect = new MBlockDecoderBDirect(mapper, sh, di, frameOut.getPOC(), decoderState); + decoderInter8x8 = new MBlockDecoderInter8x8(mapper, decoderBDirect, sh, di, frameOut.getPOC(), decoderState); + skipDecoder = new MBlockSkipDecoder(mapper, decoderBDirect, sh, di, frameOut.getPOC(), decoderState); + decoderIPCM = new MBlockDecoderIPCM(mapper, decoderState); + + refListManager = new RefListManager(sh, sRefs, lRefs, frameOut); + } + + private void decodeMacroblocks(Frame[][] refList) { + Picture mb = Picture.create(16, 16, activeSps.chromaFormatIdc); + int mbWidth = activeSps.picWidthInMbsMinus1 + 1; + + MBlock mBlock = new MBlock(activeSps.chromaFormatIdc); + while (parser.readMacroblock(mBlock)) { + int mbAddr = mapper.getAddress(mBlock.mbIdx); + int mbX = mbAddr % mbWidth; + int mbY = mbAddr / mbWidth; + decode(mBlock, parser.getSliceHeader().sliceType, mb, refList); + putMacroblock(frameOut, mb, mbX, mbY); + di.shs[mbAddr] = parser.getSliceHeader(); + di.refsUsed[mbAddr] = refList; + fillCoeff(mBlock, mbX, mbY); + mb.fill(0); + mBlock.clear(); + } + } + + private void fillCoeff(MBlock mBlock, int mbX, int mbY) { + for (int i = 0; i < 16; i++) { + int blkOffLeft = H264Const.MB_DISP_OFF_LEFT[i]; + int blkOffTop = H264Const.MB_DISP_OFF_TOP[i]; + int blkX = (mbX << 2) + blkOffLeft; + int blkY = (mbY << 2) + blkOffTop; + + di.nCoeff[blkY][blkX] = mBlock.nCoeff[i]; + } + } + + public void decode(MBlock mBlock, SliceType sliceType, Picture mb, Frame[][] references) { + if (mBlock.skipped) { + skipDecoder.decodeSkip(mBlock, references, mb, sliceType); + } else if (sliceType == SliceType.I) { + decodeMBlockI(mBlock, mb); + } else if (sliceType == SliceType.P) { + decodeMBlockP(mBlock, mb, references); + } else { + decodeMBlockB(mBlock, mb, references); + } + } + + private void decodeMBlockI(MBlock mBlock, Picture mb) { + decodeMBlockIInt(mBlock, mb); + } + + private void decodeMBlockIInt(MBlock mBlock, Picture mb) { + if (mBlock.curMbType == MBType.I_NxN) { + decoderIntraNxN.decode(mBlock, mb); + } else if (mBlock.curMbType == MBType.I_16x16) { + decoderIntra16x16.decode(mBlock, mb); + } else { + Logger.warn("IPCM macroblock found. Not tested, may cause unpredictable behavior."); + decoderIPCM.decode(mBlock, mb); + } + } + + private void decodeMBlockP(MBlock mBlock, Picture mb, Frame[][] references) { + if (P_16x16 == mBlock.curMbType) { + decoderInter.decode16x16(mBlock, mb, references, L0); + } else if (P_16x8 == mBlock.curMbType) { + decoderInter.decode16x8(mBlock, mb, references, L0, L0); + } else if (P_8x16 == mBlock.curMbType) { + decoderInter.decode8x16(mBlock, mb, references, L0, L0); + } else if (P_8x8 == mBlock.curMbType) { + decoderInter8x8.decode(mBlock, references, mb, P, false); + } else if (P_8x8ref0 == mBlock.curMbType) { + decoderInter8x8.decode(mBlock, references, mb, P, true); + } else { + decodeMBlockIInt(mBlock, mb); + } + } + + private void decodeMBlockB(MBlock mBlock, Picture mb, Frame[][] references) { + if (mBlock.curMbType.isIntra()) { + decodeMBlockIInt(mBlock, mb); + } else { + if (mBlock.curMbType == B_Direct_16x16) { + decoderBDirect.decode(mBlock, mb, references); + } else if (mBlock.mbType <= 3) { + decoderInter.decode16x16(mBlock, mb, references, H264Const.bPredModes[mBlock.mbType][0]); + } else if (mBlock.mbType == 22) { + decoderInter8x8.decode(mBlock, references, mb, SliceType.B, false); + } else if ((mBlock.mbType & 1) == 0) { + decoderInter.decode16x8(mBlock, mb, references, H264Const.bPredModes[mBlock.mbType][0], + H264Const.bPredModes[mBlock.mbType][1]); + } else { + decoderInter.decode8x16(mBlock, mb, references, H264Const.bPredModes[mBlock.mbType][0], + H264Const.bPredModes[mBlock.mbType][1]); + } + } + } + + private static void putMacroblock(Picture tgt, Picture decoded, final int mbX, final int mbY) { + byte[] luma = tgt.getPlaneData(0); + int stride = tgt.getPlaneWidth(0); + + byte[] cb = tgt.getPlaneData(1); + byte[] cr = tgt.getPlaneData(2); + int strideChroma = tgt.getPlaneWidth(1); + + int dOff = 0; + final int mbx16 = mbX * 16; + final int mby16 = mbY * 16; + final byte[] decodedY = decoded.getPlaneData(0); + for (int i = 0; i < 16; i++) { + arraycopy(decodedY, dOff, luma, (mby16 + i) * stride + mbx16, 16); + dOff += 16; + } + + final int mbx8 = mbX * 8; + final int mby8 = mbY * 8; + final byte[] decodedCb = decoded.getPlaneData(1); + final byte[] decodedCr = decoded.getPlaneData(2); + for (int i = 0; i < 8; i++) { + int decodePos = i << 3; + int chromaPos = (mby8 + i) * strideChroma + mbx8; + arraycopy(decodedCb, decodePos, cb, chromaPos, 8); + arraycopy(decodedCr, decodePos, cr, chromaPos, 8); + } + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/SliceHeaderReader.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/SliceHeaderReader.java new file mode 100644 index 0000000..f191b24 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/SliceHeaderReader.java @@ -0,0 +1,275 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnit; +import org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnitType; +import org.monte.media.impl.jcodec.codecs.h264.io.model.PictureParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.PredictionWeightTable; +import org.monte.media.impl.jcodec.codecs.h264.io.model.RefPicMarking; +import org.monte.media.impl.jcodec.codecs.h264.io.model.RefPicMarking.InstrType; +import org.monte.media.impl.jcodec.codecs.h264.io.model.RefPicMarking.Instruction; +import org.monte.media.impl.jcodec.codecs.h264.io.model.RefPicMarkingIDR; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.common.IntArrayList; +import org.monte.media.impl.jcodec.common.io.BitReader; + +import java.util.ArrayList; + +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readBool; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readSE; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readU; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readUEtrace; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet.getPicHeightInMbs; +import static org.monte.media.impl.jcodec.common.model.ColorSpace.MONO; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Reads header of the coded slice + * + * @author The JCodec project + */ +public class SliceHeaderReader { + private SliceHeaderReader() { + } + + public static SliceHeader readPart1(BitReader _in) { + + SliceHeader sh = new SliceHeader(); + sh.firstMbInSlice = readUEtrace(_in, "SH: first_mb_in_slice"); + int shType = readUEtrace(_in, "SH: slice_type"); + sh.sliceType = SliceType.fromValue(shType % 5); + sh.sliceTypeRestr = (shType / 5) > 0; + + sh.picParameterSetId = readUEtrace(_in, "SH: pic_parameter_set_id"); + + return sh; + } + + public static SliceHeader readPart2(SliceHeader sh, NALUnit nalUnit, SeqParameterSet sps, PictureParameterSet pps, + BitReader _in) { + sh.pps = pps; + sh.sps = sps; + + sh.frameNum = readU(_in, sps.log2MaxFrameNumMinus4 + 4, "SH: frame_num"); + if (!sps.frameMbsOnlyFlag) { + sh.fieldPicFlag = readBool(_in, "SH: field_pic_flag"); + if (sh.fieldPicFlag) { + sh.bottomFieldFlag = readBool(_in, "SH: bottom_field_flag"); + } + } + if (nalUnit.type == NALUnitType.IDR_SLICE) { + sh.idrPicId = readUEtrace(_in, "SH: idr_pic_id"); + } + if (sps.picOrderCntType == 0) { + sh.picOrderCntLsb = readU(_in, sps.log2MaxPicOrderCntLsbMinus4 + 4, "SH: pic_order_cnt_lsb"); + if (pps.picOrderPresentFlag && !sps.fieldPicFlag) { + sh.deltaPicOrderCntBottom = readSE(_in, "SH: delta_pic_order_cnt_bottom"); + } + } + sh.deltaPicOrderCnt = new int[2]; + if (sps.picOrderCntType == 1 && !sps.deltaPicOrderAlwaysZeroFlag) { + sh.deltaPicOrderCnt[0] = readSE(_in, "SH: delta_pic_order_cnt[0]"); + if (pps.picOrderPresentFlag && !sps.fieldPicFlag) + sh.deltaPicOrderCnt[1] = readSE(_in, "SH: delta_pic_order_cnt[1]"); + } + if (pps.redundantPicCntPresentFlag) { + sh.redundantPicCnt = readUEtrace(_in, "SH: redundant_pic_cnt"); + } + if (sh.sliceType == SliceType.B) { + sh.directSpatialMvPredFlag = readBool(_in, "SH: direct_spatial_mv_pred_flag"); + } + if (sh.sliceType == SliceType.P || sh.sliceType == SliceType.SP || sh.sliceType == SliceType.B) { + sh.numRefIdxActiveOverrideFlag = readBool(_in, "SH: num_ref_idx_active_override_flag"); + if (sh.numRefIdxActiveOverrideFlag) { + sh.numRefIdxActiveMinus1[0] = readUEtrace(_in, "SH: num_ref_idx_l0_active_minus1"); + if (sh.sliceType == SliceType.B) { + sh.numRefIdxActiveMinus1[1] = readUEtrace(_in, "SH: num_ref_idx_l1_active_minus1"); + } + } + } + readRefPicListReordering(sh, _in); + if ((pps.weightedPredFlag && (sh.sliceType == SliceType.P || sh.sliceType == SliceType.SP)) + || (pps.weightedBipredIdc == 1 && sh.sliceType == SliceType.B)) + readPredWeightTable(sps, pps, sh, _in); + if (nalUnit.nal_ref_idc != 0) + readDecoderPicMarking(nalUnit, sh, _in); + if (pps.entropyCodingModeFlag && sh.sliceType.isInter()) { + sh.cabacInitIdc = readUEtrace(_in, "SH: cabac_init_idc"); + } + sh.sliceQpDelta = readSE(_in, "SH: slice_qp_delta"); + if (sh.sliceType == SliceType.SP || sh.sliceType == SliceType.SI) { + if (sh.sliceType == SliceType.SP) { + sh.spForSwitchFlag = readBool(_in, "SH: sp_for_switch_flag"); + } + sh.sliceQsDelta = readSE(_in, "SH: slice_qs_delta"); + } + if (pps.deblockingFilterControlPresentFlag) { + sh.disableDeblockingFilterIdc = readUEtrace(_in, "SH: disable_deblocking_filter_idc"); + if (sh.disableDeblockingFilterIdc != 1) { + sh.sliceAlphaC0OffsetDiv2 = readSE(_in, "SH: slice_alpha_c0_offset_div2"); + sh.sliceBetaOffsetDiv2 = readSE(_in, "SH: slice_beta_offset_div2"); + } + } + if (pps.numSliceGroupsMinus1 > 0 && pps.sliceGroupMapType >= 3 && pps.sliceGroupMapType <= 5) { + int len = getPicHeightInMbs(sps) * (sps.picWidthInMbsMinus1 + 1) + / (pps.sliceGroupChangeRateMinus1 + 1); + if ((getPicHeightInMbs(sps) * (sps.picWidthInMbsMinus1 + 1)) + % (pps.sliceGroupChangeRateMinus1 + 1) > 0) + len += 1; + + len = CeilLog2(len + 1); + sh.sliceGroupChangeCycle = readU(_in, len, "SH: slice_group_change_cycle"); + } + + return sh; + } + + private static int CeilLog2(int uiVal) { + int uiTmp = uiVal - 1; + int uiRet = 0; + + while (uiTmp != 0) { + uiTmp >>= 1; + uiRet++; + } + return uiRet; + } + + // static int i = 0; + + private static void readDecoderPicMarking(NALUnit nalUnit, SliceHeader sh, BitReader _in) { + if (nalUnit.type == NALUnitType.IDR_SLICE) { + boolean noOutputOfPriorPicsFlag = readBool(_in, "SH: no_output_of_prior_pics_flag"); + boolean longTermReferenceFlag = readBool(_in, "SH: long_term_reference_flag"); + sh.refPicMarkingIDR = new RefPicMarkingIDR(noOutputOfPriorPicsFlag, longTermReferenceFlag); + } else { + boolean adaptiveRefPicMarkingModeFlag = readBool(_in, "SH: adaptive_ref_pic_marking_mode_flag"); + if (adaptiveRefPicMarkingModeFlag) { + ArrayList mmops = new ArrayList(); + int memoryManagementControlOperation; + do { + memoryManagementControlOperation = readUEtrace(_in, "SH: memory_management_control_operation"); + + Instruction instr = null; + + switch (memoryManagementControlOperation) { + case 1: + instr = new RefPicMarking.Instruction(InstrType.REMOVE_SHORT, readUEtrace(_in, + "SH: difference_of_pic_nums_minus1") + 1, 0); + break; + case 2: + instr = new RefPicMarking.Instruction(InstrType.REMOVE_LONG, + readUEtrace(_in, "SH: long_term_pic_num"), 0); + break; + case 3: + instr = new RefPicMarking.Instruction(InstrType.CONVERT_INTO_LONG, readUEtrace(_in, + "SH: difference_of_pic_nums_minus1") + 1, readUEtrace(_in, "SH: long_term_frame_idx")); + break; + case 4: + instr = new RefPicMarking.Instruction(InstrType.TRUNK_LONG, readUEtrace(_in, + "SH: max_long_term_frame_idx_plus1") - 1, 0); + break; + case 5: + instr = new RefPicMarking.Instruction(InstrType.CLEAR, 0, 0); + break; + case 6: + instr = new RefPicMarking.Instruction(InstrType.MARK_LONG, + readUEtrace(_in, "SH: long_term_frame_idx"), 0); + break; + } + if (instr != null) + mmops.add(instr); + } while (memoryManagementControlOperation != 0); + sh.refPicMarkingNonIDR = new RefPicMarking(mmops.toArray(new Instruction[]{})); + } + } + } + + private static void readPredWeightTable(SeqParameterSet sps, PictureParameterSet pps, SliceHeader sh, BitReader _in) { + sh.predWeightTable = new PredictionWeightTable(); + int[] numRefsMinus1 = sh.numRefIdxActiveOverrideFlag ? sh.numRefIdxActiveMinus1 + : pps.numRefIdxActiveMinus1; + int[] nr = new int[]{numRefsMinus1[0] + 1, numRefsMinus1[1] + 1}; + + sh.predWeightTable.lumaLog2WeightDenom = readUEtrace(_in, "SH: luma_log2_weight_denom"); + if (sps.chromaFormatIdc != MONO) { + sh.predWeightTable.chromaLog2WeightDenom = readUEtrace(_in, "SH: chroma_log2_weight_denom"); + } + int defaultLW = 1 << sh.predWeightTable.lumaLog2WeightDenom; + int defaultCW = 1 << sh.predWeightTable.chromaLog2WeightDenom; + + for (int list = 0; list < 2; list++) { + sh.predWeightTable.lumaWeight[list] = new int[nr[list]]; + sh.predWeightTable.lumaOffset[list] = new int[nr[list]]; + sh.predWeightTable.chromaWeight[list] = new int[2][nr[list]]; + sh.predWeightTable.chromaOffset[list] = new int[2][nr[list]]; + for (int i = 0; i < nr[list]; i++) { + sh.predWeightTable.lumaWeight[list][i] = defaultLW; + sh.predWeightTable.lumaOffset[list][i] = 0; + sh.predWeightTable.chromaWeight[list][0][i] = defaultCW; + sh.predWeightTable.chromaOffset[list][0][i] = 0; + sh.predWeightTable.chromaWeight[list][1][i] = defaultCW; + sh.predWeightTable.chromaOffset[list][1][i] = 0; + } + } + + readWeightOffset(sps, pps, sh, _in, nr, 0); + if (sh.sliceType == SliceType.B) { + readWeightOffset(sps, pps, sh, _in, nr, 1); + } + } + + private static void readWeightOffset(SeqParameterSet sps, PictureParameterSet pps, SliceHeader sh, BitReader _in, + int[] numRefs, int list) { + + for (int i = 0; i < numRefs[list]; i++) { + boolean lumaWeightL0Flag = readBool(_in, "SH: luma_weight_l0_flag"); + if (lumaWeightL0Flag) { + sh.predWeightTable.lumaWeight[list][i] = readSE(_in, "SH: weight"); + sh.predWeightTable.lumaOffset[list][i] = readSE(_in, "SH: offset"); + } + if (sps.chromaFormatIdc != MONO) { + boolean chromaWeightL0Flag = readBool(_in, "SH: chroma_weight_l0_flag"); + if (chromaWeightL0Flag) { + sh.predWeightTable.chromaWeight[list][0][i] = readSE(_in, "SH: weight"); + sh.predWeightTable.chromaOffset[list][0][i] = readSE(_in, "SH: offset"); + sh.predWeightTable.chromaWeight[list][1][i] = readSE(_in, "SH: weight"); + sh.predWeightTable.chromaOffset[list][1][i] = readSE(_in, "SH: offset"); + } + } + } + } + + private static void readRefPicListReordering(SliceHeader sh, BitReader _in) { + sh.refPicReordering = new int[2][][]; + // System.out.println(i++); + if (sh.sliceType.isInter()) { + boolean refPicListReorderingFlagL0 = readBool(_in, "SH: ref_pic_list_reordering_flag_l0"); + if (refPicListReorderingFlagL0) { + sh.refPicReordering[0] = readReorderingEntries(_in); + } + } + if (sh.sliceType == SliceType.B) { + boolean refPicListReorderingFlagL1 = readBool(_in, "SH: ref_pic_list_reordering_flag_l1"); + if (refPicListReorderingFlagL1) { + sh.refPicReordering[1] = readReorderingEntries(_in); + } + } + } + + private static int[][] readReorderingEntries(BitReader _in) { + IntArrayList ops = IntArrayList.createIntArrayList(); + IntArrayList args = IntArrayList.createIntArrayList(); + do { + int idc = readUEtrace(_in, "SH: reordering_of_pic_nums_idc"); + if (idc == 3) + break; + ops.add(idc); + args.add(readUEtrace(_in, "SH: abs_diff_pic_num_minus1")); + } while (true); + return new int[][]{ops.toArray(), args.toArray()}; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/SliceReader.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/SliceReader.java new file mode 100644 index 0000000..18c8fb3 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/SliceReader.java @@ -0,0 +1,1090 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode; + +import org.monte.media.impl.jcodec.codecs.common.biari.MDecoder; +import org.monte.media.impl.jcodec.codecs.h264.H264Const; +import org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred; +import org.monte.media.impl.jcodec.codecs.h264.decode.aso.Mapper; +import org.monte.media.impl.jcodec.codecs.h264.io.CABAC; +import org.monte.media.impl.jcodec.codecs.h264.io.CABAC.BlockType; +import org.monte.media.impl.jcodec.codecs.h264.io.CAVLC; +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; +import org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnit; +import org.monte.media.impl.jcodec.codecs.h264.io.model.PictureParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.common.io.BitReader; +import org.monte.media.impl.jcodec.common.logging.Logger; +import org.monte.media.impl.jcodec.common.model.ColorSpace; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.Direct; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.L0; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.bPartPredModes; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.bSubMbTypes; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.identityMapping16; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.last_sig_coeff_map_8x8; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.sig_coeff_map_8x8; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.moreRBSPData; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readBool; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readNBit; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readSE; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readTE; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readUEtrace; +import static org.monte.media.impl.jcodec.codecs.h264.decode.MBlockDecoderUtils.debugPrint; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.B_8x8; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.I_16x16; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.P_8x8; +import static org.monte.media.impl.jcodec.common.model.ColorSpace.MONO; + +/** + * Contains methods for reading high-level symbols out of H.264 bitstream + * + * @author The JCodec Project + */ +public class SliceReader { + + private PictureParameterSet activePps; + private CABAC cabac; + private MDecoder mDecoder; + private CAVLC[] cavlc; + private BitReader reader; + private Mapper mapper; + private SliceHeader sh; + private NALUnit nalUnit; + + private boolean prevMbSkipped = false; + private int mbIdx; + private MBType prevMBType = null; + private int mbSkipRun; + private boolean endOfData; + + // State + MBType[] topMBType; + MBType leftMBType; + int leftCBPLuma; + int[] topCBPLuma; + int leftCBPChroma; + int[] topCBPChroma; + ColorSpace chromaFormat; + boolean transform8x8; + int[] numRef; + boolean tf8x8Left; + boolean[] tf8x8Top; + int[] i4x4PredTop; + int[] i4x4PredLeft; + PartPred[] predModeLeft; + PartPred[] predModeTop; + + public SliceReader(PictureParameterSet activePps, CABAC cabac, CAVLC[] cavlc, MDecoder mDecoder, BitReader reader, + Mapper mapper, SliceHeader sh, NALUnit nalUnit) { + this.activePps = activePps; + this.cabac = cabac; + this.mDecoder = mDecoder; + this.cavlc = cavlc; + this.reader = reader; + this.mapper = mapper; + this.sh = sh; + this.nalUnit = nalUnit; + + int mbWidth = sh.sps.picWidthInMbsMinus1 + 1; + topMBType = new MBType[mbWidth]; + topCBPLuma = new int[mbWidth]; + topCBPChroma = new int[mbWidth]; + chromaFormat = sh.sps.chromaFormatIdc; + transform8x8 = sh.pps.extended == null ? false : sh.pps.extended.transform8x8ModeFlag; + if (sh.numRefIdxActiveOverrideFlag) + numRef = new int[]{sh.numRefIdxActiveMinus1[0] + 1, sh.numRefIdxActiveMinus1[1] + 1}; + else + numRef = new int[]{sh.pps.numRefIdxActiveMinus1[0] + 1, sh.pps.numRefIdxActiveMinus1[1] + 1}; + + tf8x8Top = new boolean[mbWidth]; + predModeLeft = new PartPred[2]; + predModeTop = new PartPred[mbWidth << 1]; + + i4x4PredLeft = new int[4]; + i4x4PredTop = new int[mbWidth << 2]; + } + + public boolean readMacroblock(MBlock mBlock) { + int mbWidth = sh.sps.picWidthInMbsMinus1 + 1; + int mbHeight = sh.sps.picHeightInMapUnitsMinus1 + 1; + if (endOfData && mbSkipRun == 0 || mbIdx >= mbWidth * mbHeight) + return false; + + mBlock.mbIdx = mbIdx; + mBlock.prevMbType = prevMBType; + + boolean mbaffFrameFlag = (sh.sps.mbAdaptiveFrameFieldFlag && !sh.fieldPicFlag); + + if (sh.sliceType.isInter() && !activePps.entropyCodingModeFlag) { + if (!prevMbSkipped && mbSkipRun == 0) { + mbSkipRun = readUEtrace(reader, "mb_skip_run"); + if (!moreRBSPData(reader)) { + endOfData = true; + } + } + + if (mbSkipRun > 0) { + --mbSkipRun; + int mbAddr = mapper.getAddress(mbIdx); + prevMbSkipped = true; + prevMBType = null; + debugPrint("---------------------- MB (%d,%d) ---------------------", (mbAddr % mbWidth), + (mbAddr / mbWidth)); + mBlock.skipped = true; + int mbX = mapper.getMbX(mBlock.mbIdx); + topMBType[mbX] = leftMBType = null; + int blk8x8X = mbX << 1; + predModeLeft[0] = predModeLeft[1] = predModeTop[blk8x8X] = predModeTop[blk8x8X + 1] = L0; + ++mbIdx; + return true; + } else { + prevMbSkipped = false; + } + } + + int mbAddr = mapper.getAddress(mbIdx); + int mbX = mbAddr % mbWidth; + int mbY = mbAddr / mbWidth; + debugPrint("---------------------- MB (%d,%d) ---------------------", mbX, mbY); + + if (sh.sliceType.isIntra() + || (!activePps.entropyCodingModeFlag || !readMBSkipFlag(sh.sliceType, mapper.leftAvailable(mbIdx), + mapper.topAvailable(mbIdx), mbX))) { + + boolean mb_field_decoding_flag = false; + if (mbaffFrameFlag && (mbIdx % 2 == 0 || (mbIdx % 2 == 1 && prevMbSkipped))) { + mb_field_decoding_flag = readBool(reader, "mb_field_decoding_flag"); + } + + mBlock.fieldDecoding = mb_field_decoding_flag; + readMBlock(mBlock, sh.sliceType); + + prevMBType = mBlock.curMbType; + + } else { + prevMBType = null; + prevMbSkipped = true; + mBlock.skipped = true; + int blk8x8X = mbX << 1; + predModeLeft[0] = predModeLeft[1] = predModeTop[blk8x8X] = predModeTop[blk8x8X + 1] = L0; + } + + endOfData = (activePps.entropyCodingModeFlag && mDecoder.decodeFinalBin() == 1) + || (!activePps.entropyCodingModeFlag && !moreRBSPData(reader)); + + ++mbIdx; + + topMBType[mapper.getMbX(mBlock.mbIdx)] = leftMBType = mBlock.curMbType; + + return true; + } + + int readMBQpDelta(MBType prevMbType) { + int mbQPDelta; + if (!activePps.entropyCodingModeFlag) { + mbQPDelta = readSE(reader, "mb_qp_delta"); + } else { + mbQPDelta = cabac.readMBQpDelta(mDecoder, prevMbType); + } + return mbQPDelta; + } + + int readChromaPredMode(int mbX, boolean leftAvailable, boolean topAvailable) { + int chromaPredictionMode; + if (!activePps.entropyCodingModeFlag) { + chromaPredictionMode = readUEtrace(reader, "MBP: intra_chroma_pred_mode"); + } else { + chromaPredictionMode = cabac.readIntraChromaPredMode(mDecoder, mbX, leftMBType, topMBType[mbX], + leftAvailable, topAvailable); + } + return chromaPredictionMode; + } + + boolean readTransform8x8Flag(boolean leftAvailable, boolean topAvailable, MBType leftType, MBType topType, + boolean is8x8Left, boolean is8x8Top) { + if (!activePps.entropyCodingModeFlag) + return readBool(reader, "transform_size_8x8_flag"); + else + return cabac.readTransform8x8Flag(mDecoder, leftAvailable, topAvailable, leftType, topType, is8x8Left, + is8x8Top); + } + + protected int readCodedBlockPatternIntra(boolean leftAvailable, boolean topAvailable, int leftCBP, int topCBP, + MBType leftMB, MBType topMB) { + + if (!activePps.entropyCodingModeFlag) + return H264Const.CODED_BLOCK_PATTERN_INTRA_COLOR[readUEtrace(reader, "coded_block_pattern")]; + else + return cabac.codedBlockPatternIntra(mDecoder, leftAvailable, topAvailable, leftCBP, topCBP, leftMB, topMB); + } + + protected int readCodedBlockPatternInter(boolean leftAvailable, boolean topAvailable, int leftCBP, int topCBP, + MBType leftMB, MBType topMB) { + if (!activePps.entropyCodingModeFlag) { + int code = readUEtrace(reader, "coded_block_pattern"); + return H264Const.CODED_BLOCK_PATTERN_INTER_COLOR[code]; + } else + return cabac.codedBlockPatternIntra(mDecoder, leftAvailable, topAvailable, leftCBP, topCBP, leftMB, topMB); + } + + int readRefIdx(boolean leftAvailable, boolean topAvailable, MBType leftType, MBType topType, PartPred leftPred, + PartPred topPred, PartPred curPred, int mbX, int partX, int partY, int partW, int partH, int list) { + if (!activePps.entropyCodingModeFlag) + return readTE(reader, numRef[list] - 1); + else + return cabac.readRefIdx(mDecoder, leftAvailable, topAvailable, leftType, topType, leftPred, topPred, + curPred, mbX, partX, partY, partW, partH, list); + } + + int readMVD(int comp, boolean leftAvailable, boolean topAvailable, MBType leftType, MBType topType, + PartPred leftPred, PartPred topPred, PartPred curPred, int mbX, int partX, int partY, int partW, int partH, + int list) { + if (!activePps.entropyCodingModeFlag) + return readSE(reader, "mvd_l0_x"); + else + return cabac.readMVD(mDecoder, comp, leftAvailable, topAvailable, leftType, topType, leftPred, topPred, + curPred, mbX, partX, partY, partW, partH, list); + } + + int readPredictionI4x4Block(boolean leftAvailable, boolean topAvailable, MBType leftMBType, MBType topMBType, + int blkX, int blkY, int mbX) { + int mode = 2; + if ((leftAvailable || blkX > 0) && (topAvailable || blkY > 0)) { + int predModeB = topMBType == MBType.I_NxN || blkY > 0 ? i4x4PredTop[(mbX << 2) + blkX] : 2; + int predModeA = leftMBType == MBType.I_NxN || blkX > 0 ? i4x4PredLeft[blkY] : 2; + mode = Math.min(predModeB, predModeA); + } + if (!prev4x4PredMode()) { + int rem_intra4x4_pred_mode = rem4x4PredMode(); + mode = rem_intra4x4_pred_mode + (rem_intra4x4_pred_mode < mode ? 0 : 1); + } + i4x4PredTop[(mbX << 2) + blkX] = i4x4PredLeft[blkY] = mode; + return mode; + } + + int rem4x4PredMode() { + if (!activePps.entropyCodingModeFlag) + return readNBit(reader, 3, "MB: rem_intra4x4_pred_mode"); + else + return cabac.rem4x4PredMode(mDecoder); + } + + boolean prev4x4PredMode() { + if (!activePps.entropyCodingModeFlag) + return readBool(reader, "MBP: prev_intra4x4_pred_mode_flag"); + else + return cabac.prev4x4PredModeFlag(mDecoder); + } + + void read16x16DC(boolean leftAvailable, boolean topAvailable, int mbX, int[] dc) { + if (!activePps.entropyCodingModeFlag) + cavlc[0].readLumaDCBlock(reader, dc, mbX, leftAvailable, leftMBType, topAvailable, topMBType[mbX], + CoeffTransformer.zigzag4x4); + else { + if (cabac.readCodedBlockFlagLumaDC(mDecoder, mbX, leftMBType, topMBType[mbX], leftAvailable, topAvailable, + MBType.I_16x16) == 1) + cabac.readCoeffs(mDecoder, BlockType.LUMA_16_DC, dc, 0, 16, CoeffTransformer.zigzag4x4, + identityMapping16, identityMapping16); + } + } + + /** + * Reads AC block of macroblock encoded as I_16x16, returns number of + * non-zero coefficients + * + * @return + */ + int read16x16AC(boolean leftAvailable, boolean topAvailable, int mbX, int cbpLuma, int[] ac, int blkOffLeft, + int blkOffTop, int blkX, int blkY) { + if (!activePps.entropyCodingModeFlag) { + return cavlc[0].readACBlock(reader, ac, blkX, blkOffTop, blkOffLeft != 0 || leftAvailable, + blkOffLeft == 0 ? leftMBType : I_16x16, blkOffTop != 0 || topAvailable, + blkOffTop == 0 ? topMBType[mbX] : I_16x16, 1, 15, CoeffTransformer.zigzag4x4); + } else { + if (cabac.readCodedBlockFlagLumaAC(mDecoder, BlockType.LUMA_15_AC, blkX, blkOffTop, 0, leftMBType, + topMBType[mbX], leftAvailable, topAvailable, leftCBPLuma, topCBPLuma[mbX], cbpLuma, MBType.I_16x16) == 1) + return cabac.readCoeffs(mDecoder, BlockType.LUMA_15_AC, ac, 1, 15, CoeffTransformer.zigzag4x4, + identityMapping16, identityMapping16); + } + return 0; + } + + /** + * Reads AC block of a macroblock, return number of non-zero coefficients + * + * @return + */ + int readResidualAC(boolean leftAvailable, boolean topAvailable, int mbX, MBType curMbType, int cbpLuma, + int blkOffLeft, int blkOffTop, int blkX, int blkY, int[] ac) { + if (!activePps.entropyCodingModeFlag) { + if (reader.remaining() <= 0) + return 0; + return cavlc[0].readACBlock(reader, ac, blkX, blkOffTop, blkOffLeft != 0 || leftAvailable, + blkOffLeft == 0 ? leftMBType : curMbType, blkOffTop != 0 || topAvailable, + blkOffTop == 0 ? topMBType[mbX] : curMbType, 0, 16, CoeffTransformer.zigzag4x4); + } else { + if (cabac.readCodedBlockFlagLumaAC(mDecoder, BlockType.LUMA_16, blkX, blkOffTop, 0, leftMBType, + topMBType[mbX], leftAvailable, topAvailable, leftCBPLuma, topCBPLuma[mbX], cbpLuma, curMbType) == 1) + return cabac.readCoeffs(mDecoder, BlockType.LUMA_16, ac, 0, 16, CoeffTransformer.zigzag4x4, + identityMapping16, identityMapping16); + } + return 0; + } + + public void setZeroCoeff(int comp, int blkX, int blkOffTop) { + cavlc[comp].setZeroCoeff(blkX, blkOffTop); + } + + public void savePrevCBP(int codedBlockPattern) { + if (activePps.entropyCodingModeFlag) + cabac.setPrevCBP(codedBlockPattern); + } + + public int readLumaAC(boolean leftAvailable, boolean topAvailable, int mbX, MBType curMbType, int blkX, int j, + int[] ac16, int blkOffLeft, int blkOffTop) { + return cavlc[0].readACBlock(reader, ac16, blkX + (j & 1), blkOffTop, blkOffLeft != 0 || leftAvailable, + blkOffLeft == 0 ? leftMBType : curMbType, blkOffTop != 0 || topAvailable, + blkOffTop == 0 ? topMBType[mbX] : curMbType, 0, 16, H264Const.identityMapping16); + } + + /** + * Reads luma AC coeffiecients for 8x8 blocks, returns number of non-zero + * coefficients + * + * @return + */ + public int readLumaAC8x8(int blkX, int blkY, int[] ac) { + int readCoeffs = cabac.readCoeffs(mDecoder, BlockType.LUMA_64, ac, 0, 64, CoeffTransformer.zigzag8x8, + sig_coeff_map_8x8, last_sig_coeff_map_8x8); + cabac.setCodedBlock(blkX, blkY); + cabac.setCodedBlock(blkX + 1, blkY); + cabac.setCodedBlock(blkX, blkY + 1); + cabac.setCodedBlock(blkX + 1, blkY + 1); + + return readCoeffs; + } + + public int readSubMBTypeP() { + if (!activePps.entropyCodingModeFlag) + return readUEtrace(reader, "SUB: sub_mb_type"); + else + return cabac.readSubMbTypeP(mDecoder); + } + + public int readSubMBTypeB() { + if (!activePps.entropyCodingModeFlag) + return readUEtrace(reader, "SUB: sub_mb_type"); + else + return cabac.readSubMbTypeB(mDecoder); + } + + public void readChromaDC(int mbX, boolean leftAvailable, boolean topAvailable, int[] dc, int comp, MBType curMbType) { + if (!activePps.entropyCodingModeFlag) + cavlc[comp].readChromaDCBlock(reader, dc, leftAvailable, topAvailable); + else { + if (cabac.readCodedBlockFlagChromaDC(mDecoder, mbX, comp, leftMBType, topMBType[mbX], leftAvailable, + topAvailable, leftCBPChroma, topCBPChroma[mbX], curMbType) == 1) + cabac.readCoeffs(mDecoder, BlockType.CHROMA_DC, dc, 0, 4, identityMapping16, identityMapping16, + identityMapping16); + } + } + + public void readChromaAC(boolean leftAvailable, boolean topAvailable, int mbX, int comp, MBType curMbType, + int[] ac, int blkOffLeft, int blkOffTop, int blkX) { + if (!activePps.entropyCodingModeFlag) { + if (reader.remaining() <= 0) + return; + cavlc[comp].readACBlock(reader, ac, blkX, blkOffTop, blkOffLeft != 0 || leftAvailable, + blkOffLeft == 0 ? leftMBType : curMbType, blkOffTop != 0 || topAvailable, + blkOffTop == 0 ? topMBType[mbX] : curMbType, 1, 15, CoeffTransformer.zigzag4x4); + } else { + if (cabac.readCodedBlockFlagChromaAC(mDecoder, blkX, blkOffTop, comp, leftMBType, topMBType[mbX], + leftAvailable, topAvailable, leftCBPChroma, topCBPChroma[mbX], curMbType) == 1) + cabac.readCoeffs(mDecoder, BlockType.CHROMA_AC, ac, 1, 15, CoeffTransformer.zigzag4x4, + identityMapping16, identityMapping16); + } + } + + public int decodeMBTypeI(int mbIdx, boolean leftAvailable, boolean topAvailable, MBType leftMBType, MBType topMBType) { + int mbType; + if (!activePps.entropyCodingModeFlag) + mbType = readUEtrace(reader, "MB: mb_type"); + else + mbType = cabac.readMBTypeI(mDecoder, leftMBType, topMBType, leftAvailable, topAvailable); + return mbType; + } + + public int readMBTypeP() { + int mbType; + if (!activePps.entropyCodingModeFlag) + mbType = readUEtrace(reader, "MB: mb_type"); + else + mbType = cabac.readMBTypeP(mDecoder); + return mbType; + } + + public int readMBTypeB(int mbIdx, boolean leftAvailable, boolean topAvailable, MBType leftMBType, MBType topMBType) { + int mbType; + if (!activePps.entropyCodingModeFlag) + mbType = readUEtrace(reader, "MB: mb_type"); + else + mbType = cabac.readMBTypeB(mDecoder, leftMBType, topMBType, leftAvailable, topAvailable); + return mbType; + } + + public boolean readMBSkipFlag(SliceType slType, boolean leftAvailable, boolean topAvailable, int mbX) { + return cabac.readMBSkipFlag(mDecoder, slType, leftAvailable, topAvailable, mbX); + } + + public void readIntra16x16(int mbType, MBlock mBlock) { + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + boolean leftAvailable = mapper.leftAvailable(mBlock.mbIdx); + boolean topAvailable = mapper.topAvailable(mBlock.mbIdx); + mBlock.cbp((mbType / 12) * 15, (mbType / 4) % 3); + mBlock.luma16x16Mode = mbType % 4; + mBlock.chromaPredictionMode = readChromaPredMode(mbX, leftAvailable, topAvailable); + mBlock.mbQPDelta = readMBQpDelta(mBlock.prevMbType); + read16x16DC(leftAvailable, topAvailable, mbX, mBlock.dc); + for (int i = 0; i < 16; i++) { + int blkOffLeft = H264Const.MB_DISP_OFF_LEFT[i]; + int blkOffTop = H264Const.MB_DISP_OFF_TOP[i]; + int blkX = (mbX << 2) + blkOffLeft; + int blkY = (mbY << 2) + blkOffTop; + + if ((mBlock.cbpLuma() & (1 << (i >> 2))) != 0) { + mBlock.nCoeff[i] = read16x16AC(leftAvailable, topAvailable, mbX, mBlock.cbpLuma(), mBlock.ac[0][i], + blkOffLeft, blkOffTop, blkX, blkY); + } else { + if (!sh.pps.entropyCodingModeFlag) + setZeroCoeff(0, blkX, blkOffTop); + } + } + + if (chromaFormat != ColorSpace.MONO) { + readChromaResidual(mBlock, leftAvailable, topAvailable, mbX); + } + } + + public void readMBlockBDirect(MBlock mBlock) { + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + boolean lAvb = mapper.leftAvailable(mBlock.mbIdx); + boolean tAvb = mapper.topAvailable(mBlock.mbIdx); + mBlock._cbp = readCodedBlockPatternInter(lAvb, tAvb, leftCBPLuma | (leftCBPChroma << 4), topCBPLuma[mbX] + | (topCBPChroma[mbX] << 4), leftMBType, topMBType[mbX]); + + mBlock.transform8x8Used = false; + if (transform8x8 && mBlock.cbpLuma() != 0 && sh.sps.direct8x8InferenceFlag) { + mBlock.transform8x8Used = readTransform8x8Flag(lAvb, tAvb, leftMBType, topMBType[mbX], tf8x8Left, + tf8x8Top[mbX]); + } + + if (mBlock.cbpLuma() > 0 || mBlock.cbpChroma() > 0) { + mBlock.mbQPDelta = readMBQpDelta(mBlock.prevMbType); + } + readResidualLuma(mBlock, lAvb, tAvb, mbX, mbY); + readChromaResidual(mBlock, lAvb, tAvb, mbX); + + predModeTop[mbX << 1] = predModeTop[(mbX << 1) + 1] = predModeLeft[0] = predModeLeft[1] = Direct; + } + + public void readInter16x16(PartPred p0, MBlock mBlock) { + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + boolean leftAvailable = mapper.leftAvailable(mBlock.mbIdx); + boolean topAvailable = mapper.topAvailable(mBlock.mbIdx); + + for (int list = 0; list < 2; list++) { + if (H264Const.usesList(p0, list) && numRef[list] > 1) + mBlock.pb16x16.refIdx[list] = readRefIdx(leftAvailable, topAvailable, leftMBType, topMBType[mbX], + predModeLeft[0], predModeTop[(mbX << 1)], p0, mbX, 0, 0, 4, 4, list); + } + for (int list = 0; list < 2; list++) { + readPredictionInter16x16(mBlock, mbX, leftAvailable, topAvailable, list, p0); + } + readResidualInter(mBlock, leftAvailable, topAvailable, mbX, mbY); + + predModeLeft[0] = predModeLeft[1] = predModeTop[mbX << 1] = predModeTop[(mbX << 1) + 1] = p0; + } + + private void readPredInter8x16(MBlock mBlock, int mbX, boolean leftAvailable, boolean topAvailable, int list, + PartPred p0, PartPred p1) { + int blk8x8X = (mbX << 1); + + if (H264Const.usesList(p0, list)) { + mBlock.pb168x168.mvdX1[list] = readMVD(0, leftAvailable, topAvailable, leftMBType, topMBType[mbX], + predModeLeft[0], predModeTop[blk8x8X], p0, mbX, 0, 0, 2, 4, list); + mBlock.pb168x168.mvdY1[list] = readMVD(1, leftAvailable, topAvailable, leftMBType, topMBType[mbX], + predModeLeft[0], predModeTop[blk8x8X], p0, mbX, 0, 0, 2, 4, list); + + } + + if (H264Const.usesList(p1, list)) { + mBlock.pb168x168.mvdX2[list] = readMVD(0, true, topAvailable, MBType.P_8x16, topMBType[mbX], p0, + predModeTop[blk8x8X + 1], p1, mbX, 2, 0, 2, 4, list); + mBlock.pb168x168.mvdY2[list] = readMVD(1, true, topAvailable, MBType.P_8x16, topMBType[mbX], p0, + predModeTop[blk8x8X + 1], p1, mbX, 2, 0, 2, 4, list); + + } + } + + private void readPredictionInter16x8(MBlock mBlock, int mbX, boolean leftAvailable, boolean topAvailable, + PartPred p0, PartPred p1, int list) { + int blk8x8X = mbX << 1; + if (H264Const.usesList(p0, list)) { + + mBlock.pb168x168.mvdX1[list] = readMVD(0, leftAvailable, topAvailable, leftMBType, topMBType[mbX], + predModeLeft[0], predModeTop[blk8x8X], p0, mbX, 0, 0, 4, 2, list); + mBlock.pb168x168.mvdY1[list] = readMVD(1, leftAvailable, topAvailable, leftMBType, topMBType[mbX], + predModeLeft[0], predModeTop[blk8x8X], p0, mbX, 0, 0, 4, 2, list); + } + + if (H264Const.usesList(p1, list)) { + mBlock.pb168x168.mvdX2[list] = readMVD(0, leftAvailable, true, leftMBType, MBType.P_16x8, predModeLeft[1], + p0, p1, mbX, 0, 2, 4, 2, list); + mBlock.pb168x168.mvdY2[list] = readMVD(1, leftAvailable, true, leftMBType, MBType.P_16x8, predModeLeft[1], + p0, p1, mbX, 0, 2, 4, 2, list); + } + } + + public void readInter16x8(PartPred p0, PartPred p1, MBlock mBlock) { + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + boolean leftAvailable = mapper.leftAvailable(mBlock.mbIdx); + boolean topAvailable = mapper.topAvailable(mBlock.mbIdx); + + for (int list = 0; list < 2; list++) { + if (H264Const.usesList(p0, list) && numRef[list] > 1) + mBlock.pb168x168.refIdx1[list] = readRefIdx(leftAvailable, topAvailable, leftMBType, topMBType[mbX], + predModeLeft[0], predModeTop[(mbX << 1)], p0, mbX, 0, 0, 4, 2, list); + if (H264Const.usesList(p1, list) && numRef[list] > 1) + mBlock.pb168x168.refIdx2[list] = readRefIdx(leftAvailable, true, leftMBType, mBlock.curMbType, + predModeLeft[1], p0, p1, mbX, 0, 2, 4, 2, list); + } + + for (int list = 0; list < 2; list++) { + readPredictionInter16x8(mBlock, mbX, leftAvailable, topAvailable, p0, p1, list); + } + readResidualInter(mBlock, leftAvailable, topAvailable, mbX, mbY); + + predModeLeft[0] = p0; + predModeLeft[1] = predModeTop[mbX << 1] = predModeTop[(mbX << 1) + 1] = p1; + } + + public void readIntra8x16(PartPred p0, PartPred p1, MBlock mBlock) { + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + boolean leftAvailable = mapper.leftAvailable(mBlock.mbIdx); + boolean topAvailable = mapper.topAvailable(mBlock.mbIdx); + for (int list = 0; list < 2; list++) { + if (H264Const.usesList(p0, list) && numRef[list] > 1) + mBlock.pb168x168.refIdx1[list] = readRefIdx(leftAvailable, topAvailable, leftMBType, topMBType[mbX], + predModeLeft[0], predModeTop[mbX << 1], p0, mbX, 0, 0, 2, 4, list); + if (H264Const.usesList(p1, list) && numRef[list] > 1) + mBlock.pb168x168.refIdx2[list] = readRefIdx(true, topAvailable, mBlock.curMbType, topMBType[mbX], p0, + predModeTop[(mbX << 1) + 1], p1, mbX, 2, 0, 2, 4, list); + } + + for (int list = 0; list < 2; list++) { + readPredInter8x16(mBlock, mbX, leftAvailable, topAvailable, list, p0, p1); + } + readResidualInter(mBlock, leftAvailable, topAvailable, mbX, mbY); + predModeTop[mbX << 1] = p0; + predModeTop[(mbX << 1) + 1] = predModeLeft[0] = predModeLeft[1] = p1; + } + + private void readPredictionInter16x16(MBlock mBlock, int mbX, boolean leftAvailable, boolean topAvailable, + int list, PartPred curPred) { + int blk8x8X = (mbX << 1); + + if (H264Const.usesList(curPred, list)) { + mBlock.pb16x16.mvdX[list] = readMVD(0, leftAvailable, topAvailable, leftMBType, topMBType[mbX], + predModeLeft[0], predModeTop[blk8x8X], curPred, mbX, 0, 0, 4, 4, list); + mBlock.pb16x16.mvdY[list] = readMVD(1, leftAvailable, topAvailable, leftMBType, topMBType[mbX], + predModeLeft[0], predModeTop[blk8x8X], curPred, mbX, 0, 0, 4, 4, list); + } + } + + private void readResidualInter(MBlock mBlock, boolean leftAvailable, boolean topAvailable, int mbX, int mbY) { + mBlock._cbp = readCodedBlockPatternInter(leftAvailable, topAvailable, leftCBPLuma | (leftCBPChroma << 4), + topCBPLuma[mbX] | (topCBPChroma[mbX] << 4), leftMBType, topMBType[mbX]); + + mBlock.transform8x8Used = false; + if (mBlock.cbpLuma() != 0 && transform8x8) { + mBlock.transform8x8Used = readTransform8x8Flag(leftAvailable, topAvailable, leftMBType, topMBType[mbX], + tf8x8Left, tf8x8Top[mbX]); + } + + if (mBlock.cbpLuma() > 0 || mBlock.cbpChroma() > 0) { + mBlock.mbQPDelta = readMBQpDelta(mBlock.prevMbType); + } + + readResidualLuma(mBlock, leftAvailable, topAvailable, mbX, mbY); + + if (chromaFormat != MONO) { + readChromaResidual(mBlock, leftAvailable, topAvailable, mbX); + } + } + + public void readMBlock8x8(MBlock mBlock) { + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + boolean leftAvailable = mapper.leftAvailable(mBlock.mbIdx); + boolean topAvailable = mapper.topAvailable(mBlock.mbIdx); + + boolean noSubMBLessThen8x8; + if (mBlock.curMbType == P_8x8 || mBlock.curMbType == MBType.P_8x8ref0) { + readPrediction8x8P(mBlock, mbX, leftAvailable, topAvailable); + + noSubMBLessThen8x8 = mBlock.pb8x8.subMbTypes[0] == 0 && mBlock.pb8x8.subMbTypes[1] == 0 + && mBlock.pb8x8.subMbTypes[2] == 0 && mBlock.pb8x8.subMbTypes[3] == 0; + } else { + readPrediction8x8B(mBlock, mbX, leftAvailable, topAvailable); + noSubMBLessThen8x8 = bSubMbTypes[mBlock.pb8x8.subMbTypes[0]] == 0 + && bSubMbTypes[mBlock.pb8x8.subMbTypes[1]] == 0 && bSubMbTypes[mBlock.pb8x8.subMbTypes[2]] == 0 + && bSubMbTypes[mBlock.pb8x8.subMbTypes[3]] == 0; + } + + mBlock._cbp = readCodedBlockPatternInter(leftAvailable, topAvailable, leftCBPLuma | (leftCBPChroma << 4), + topCBPLuma[mbX] | (topCBPChroma[mbX] << 4), leftMBType, topMBType[mbX]); + + mBlock.transform8x8Used = false; + if (transform8x8 && mBlock.cbpLuma() != 0 && noSubMBLessThen8x8) { + mBlock.transform8x8Used = readTransform8x8Flag(leftAvailable, topAvailable, leftMBType, topMBType[mbX], + tf8x8Left, tf8x8Top[mbX]); + } + + if (mBlock.cbpLuma() > 0 || mBlock.cbpChroma() > 0) { + mBlock.mbQPDelta = readMBQpDelta(mBlock.prevMbType); + } + readResidualLuma(mBlock, leftAvailable, topAvailable, mbX, mbY); + readChromaResidual(mBlock, leftAvailable, topAvailable, mbX); + } + + private void readPrediction8x8P(MBlock mBlock, int mbX, boolean leftAvailable, boolean topAvailable) { + for (int i = 0; i < 4; i++) { + mBlock.pb8x8.subMbTypes[i] = readSubMBTypeP(); + } + + if (numRef[0] > 1 && mBlock.curMbType != MBType.P_8x8ref0) { + mBlock.pb8x8.refIdx[0][0] = readRefIdx(leftAvailable, topAvailable, leftMBType, topMBType[mbX], L0, L0, L0, + mbX, 0, 0, 2, 2, 0); + mBlock.pb8x8.refIdx[0][1] = readRefIdx(true, topAvailable, P_8x8, topMBType[mbX], L0, L0, L0, mbX, 2, 0, 2, + 2, 0); + mBlock.pb8x8.refIdx[0][2] = readRefIdx(leftAvailable, true, leftMBType, P_8x8, L0, L0, L0, mbX, 0, 2, 2, 2, + 0); + mBlock.pb8x8.refIdx[0][3] = readRefIdx(true, true, P_8x8, P_8x8, L0, L0, L0, mbX, 2, 2, 2, 2, 0); + } + + readSubMb8x8(mBlock, 0, mBlock.pb8x8.subMbTypes[0], topAvailable, leftAvailable, 0, 0, mbX, leftMBType, + topMBType[mbX], P_8x8, L0, L0, L0, 0); + readSubMb8x8(mBlock, 1, mBlock.pb8x8.subMbTypes[1], topAvailable, true, 2, 0, mbX, P_8x8, topMBType[mbX], + P_8x8, L0, L0, L0, 0); + readSubMb8x8(mBlock, 2, mBlock.pb8x8.subMbTypes[2], true, leftAvailable, 0, 2, mbX, leftMBType, P_8x8, P_8x8, + L0, L0, L0, 0); + readSubMb8x8(mBlock, 3, mBlock.pb8x8.subMbTypes[3], true, true, 2, 2, mbX, P_8x8, P_8x8, P_8x8, L0, L0, L0, 0); + + int blk8x8X = mbX << 1; + + predModeLeft[0] = predModeLeft[1] = predModeTop[blk8x8X] = predModeTop[blk8x8X + 1] = L0; + } + + private void readPrediction8x8B(MBlock mBlock, int mbX, boolean leftAvailable, boolean topAvailable) { + PartPred[] p = new PartPred[4]; + for (int i = 0; i < 4; i++) { + mBlock.pb8x8.subMbTypes[i] = readSubMBTypeB(); + p[i] = bPartPredModes[mBlock.pb8x8.subMbTypes[i]]; + } + + for (int list = 0; list < 2; list++) { + if (numRef[list] <= 1) + continue; + if (H264Const.usesList(p[0], list)) + mBlock.pb8x8.refIdx[list][0] = readRefIdx(leftAvailable, topAvailable, leftMBType, topMBType[mbX], + predModeLeft[0], predModeTop[mbX << 1], p[0], mbX, 0, 0, 2, 2, list); + if (H264Const.usesList(p[1], list)) + mBlock.pb8x8.refIdx[list][1] = readRefIdx(true, topAvailable, B_8x8, topMBType[mbX], p[0], + predModeTop[(mbX << 1) + 1], p[1], mbX, 2, 0, 2, 2, list); + if (H264Const.usesList(p[2], list)) + mBlock.pb8x8.refIdx[list][2] = readRefIdx(leftAvailable, true, leftMBType, B_8x8, predModeLeft[1], + p[0], p[2], mbX, 0, 2, 2, 2, list); + if (H264Const.usesList(p[3], list)) + mBlock.pb8x8.refIdx[list][3] = readRefIdx(true, true, B_8x8, B_8x8, p[2], p[1], p[3], mbX, 2, 2, 2, 2, + list); + } + + debugPrint("Pred: " + p[0] + ", " + p[1] + ", " + p[2] + ", " + p[3]); + + int blk8x8X = mbX << 1; + for (int list = 0; list < 2; list++) { + if (H264Const.usesList(p[0], list)) { + readSubMb8x8(mBlock, 0, bSubMbTypes[mBlock.pb8x8.subMbTypes[0]], topAvailable, leftAvailable, 0, 0, + mbX, leftMBType, topMBType[mbX], B_8x8, predModeLeft[0], predModeTop[blk8x8X], p[0], list); + } + if (H264Const.usesList(p[1], list)) { + readSubMb8x8(mBlock, 1, bSubMbTypes[mBlock.pb8x8.subMbTypes[1]], topAvailable, true, 2, 0, mbX, B_8x8, + topMBType[mbX], B_8x8, p[0], predModeTop[blk8x8X + 1], p[1], list); + } + + if (H264Const.usesList(p[2], list)) { + readSubMb8x8(mBlock, 2, bSubMbTypes[mBlock.pb8x8.subMbTypes[2]], true, leftAvailable, 0, 2, mbX, + leftMBType, B_8x8, B_8x8, predModeLeft[1], p[0], p[2], list); + } + + if (H264Const.usesList(p[3], list)) { + readSubMb8x8(mBlock, 3, bSubMbTypes[mBlock.pb8x8.subMbTypes[3]], true, true, 2, 2, mbX, B_8x8, B_8x8, + B_8x8, p[2], p[1], p[3], list); + } + } + + predModeLeft[0] = p[1]; + predModeTop[blk8x8X] = p[2]; + predModeLeft[1] = predModeTop[blk8x8X + 1] = p[3]; + } + + private void readSubMb8x8(MBlock mBlock, int partNo, int subMbType, boolean tAvb, boolean lAvb, int blk8x8X, + int blk8x8Y, int mbX, MBType leftMBType, MBType topMBType, MBType curMBType, PartPred leftPred, + PartPred topPred, PartPred partPred, int list) { + switch (subMbType) { + case 3: + readSub4x4(mBlock, partNo, tAvb, lAvb, blk8x8X, blk8x8Y, mbX, leftMBType, topMBType, curMBType, leftPred, + topPred, partPred, list); + break; + case 2: + readSub4x8(mBlock, partNo, tAvb, lAvb, blk8x8X, blk8x8Y, mbX, leftMBType, topMBType, curMBType, leftPred, + topPred, partPred, list); + break; + case 1: + readSub8x4(mBlock, partNo, tAvb, lAvb, blk8x8X, blk8x8Y, mbX, leftMBType, topMBType, curMBType, leftPred, + topPred, partPred, list); + break; + case 0: + readSub8x8(mBlock, partNo, tAvb, lAvb, blk8x8X, blk8x8Y, mbX, leftMBType, topMBType, leftPred, topPred, + partPred, list); + } + } + + private void readSub8x8(MBlock mBlock, int partNo, boolean tAvb, boolean lAvb, int blk8x8X, int blk8x8Y, int mbX, + MBType leftMBType, MBType topMBType, PartPred leftPred, PartPred topPred, PartPred partPred, int list) { + mBlock.pb8x8.mvdX1[list][partNo] = readMVD(0, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, + mbX, blk8x8X, blk8x8Y, 2, 2, list); + mBlock.pb8x8.mvdY1[list][partNo] = readMVD(1, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, + mbX, blk8x8X, blk8x8Y, 2, 2, list); + debugPrint("mvd: (%d, %d)", mBlock.pb8x8.mvdX1[list][partNo], mBlock.pb8x8.mvdY1[list][partNo]); + } + + private void readSub8x4(MBlock mBlock, int partNo, boolean tAvb, boolean lAvb, int blk8x8X, int blk8x8Y, int mbX, + MBType leftMBType, MBType topMBType, MBType curMBType, PartPred leftPred, PartPred topPred, + PartPred partPred, int list) { + mBlock.pb8x8.mvdX1[list][partNo] = readMVD(0, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, + mbX, blk8x8X, blk8x8Y, 2, 1, list); + mBlock.pb8x8.mvdY1[list][partNo] = readMVD(1, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, + mbX, blk8x8X, blk8x8Y, 2, 1, list); + + mBlock.pb8x8.mvdX2[list][partNo] = readMVD(0, lAvb, true, leftMBType, curMBType, leftPred, partPred, partPred, + mbX, blk8x8X, blk8x8Y + 1, 2, 1, list); + mBlock.pb8x8.mvdY2[list][partNo] = readMVD(1, lAvb, true, leftMBType, curMBType, leftPred, partPred, partPred, + mbX, blk8x8X, blk8x8Y + 1, 2, 1, list); + } + + private void readSub4x8(MBlock mBlock, int partNo, boolean tAvb, boolean lAvb, int blk8x8X, int blk8x8Y, int mbX, + MBType leftMBType, MBType topMBType, MBType curMBType, PartPred leftPred, PartPred topPred, + PartPred partPred, int list) { + mBlock.pb8x8.mvdX1[list][partNo] = readMVD(0, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, + mbX, blk8x8X, blk8x8Y, 1, 2, list); + mBlock.pb8x8.mvdY1[list][partNo] = readMVD(1, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, + mbX, blk8x8X, blk8x8Y, 1, 2, list); + mBlock.pb8x8.mvdX2[list][partNo] = readMVD(0, true, tAvb, curMBType, topMBType, partPred, topPred, partPred, + mbX, blk8x8X + 1, blk8x8Y, 1, 2, list); + mBlock.pb8x8.mvdY2[list][partNo] = readMVD(1, true, tAvb, curMBType, topMBType, partPred, topPred, partPred, + mbX, blk8x8X + 1, blk8x8Y, 1, 2, list); + } + + private void readSub4x4(MBlock mBlock, int partNo, boolean tAvb, boolean lAvb, int blk8x8X, int blk8x8Y, int mbX, + MBType leftMBType, MBType topMBType, MBType curMBType, PartPred leftPred, PartPred topPred, + PartPred partPred, int list) { + mBlock.pb8x8.mvdX1[list][partNo] = readMVD(0, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, + mbX, blk8x8X, blk8x8Y, 1, 1, list); + mBlock.pb8x8.mvdY1[list][partNo] = readMVD(1, lAvb, tAvb, leftMBType, topMBType, leftPred, topPred, partPred, + mbX, blk8x8X, blk8x8Y, 1, 1, list); + mBlock.pb8x8.mvdX2[list][partNo] = readMVD(0, true, tAvb, curMBType, topMBType, partPred, topPred, partPred, + mbX, blk8x8X + 1, blk8x8Y, 1, 1, list); + mBlock.pb8x8.mvdY2[list][partNo] = readMVD(1, true, tAvb, curMBType, topMBType, partPred, topPred, partPred, + mbX, blk8x8X + 1, blk8x8Y, 1, 1, list); + mBlock.pb8x8.mvdX3[list][partNo] = readMVD(0, lAvb, true, leftMBType, curMBType, leftPred, partPred, partPred, + mbX, blk8x8X, blk8x8Y + 1, 1, 1, list); + mBlock.pb8x8.mvdY3[list][partNo] = readMVD(1, lAvb, true, leftMBType, curMBType, leftPred, partPred, partPred, + mbX, blk8x8X, blk8x8Y + 1, 1, 1, list); + mBlock.pb8x8.mvdX4[list][partNo] = readMVD(0, true, true, curMBType, curMBType, partPred, partPred, partPred, + mbX, blk8x8X + 1, blk8x8Y + 1, 1, 1, list); + mBlock.pb8x8.mvdY4[list][partNo] = readMVD(1, true, true, curMBType, curMBType, partPred, partPred, partPred, + mbX, blk8x8X + 1, blk8x8Y + 1, 1, 1, list); + } + + public void readIntraNxN(MBlock mBlock) { + int mbX = mapper.getMbX(mBlock.mbIdx); + int mbY = mapper.getMbY(mBlock.mbIdx); + boolean leftAvailable = mapper.leftAvailable(mBlock.mbIdx); + boolean topAvailable = mapper.topAvailable(mBlock.mbIdx); + + mBlock.transform8x8Used = false; + if (transform8x8) { + mBlock.transform8x8Used = readTransform8x8Flag(leftAvailable, topAvailable, leftMBType, topMBType[mbX], + tf8x8Left, tf8x8Top[mbX]); + } + + if (!mBlock.transform8x8Used) { + for (int bInd = 0; bInd < 16; bInd++) { + int blkX = H264Const.MB_DISP_OFF_LEFT[bInd]; + int blkY = H264Const.MB_DISP_OFF_TOP[bInd]; + mBlock.lumaModes[bInd] = readPredictionI4x4Block(leftAvailable, topAvailable, leftMBType, topMBType[mbX], + blkX, blkY, mbX); + } + } else { + for (int i = 0; i < 4; i++) { + int blkX = (i & 1) << 1; + int blkY = i & 2; + mBlock.lumaModes[i] = readPredictionI4x4Block(leftAvailable, topAvailable, leftMBType, topMBType[mbX], + blkX, blkY, mbX); + i4x4PredLeft[blkY + 1] = i4x4PredLeft[blkY]; + i4x4PredTop[(mbX << 2) + blkX + 1] = i4x4PredTop[(mbX << 2) + blkX]; + } + } + mBlock.chromaPredictionMode = readChromaPredMode(mbX, leftAvailable, topAvailable); + + mBlock._cbp = readCodedBlockPatternIntra(leftAvailable, topAvailable, leftCBPLuma | (leftCBPChroma << 4), + topCBPLuma[mbX] | (topCBPChroma[mbX] << 4), leftMBType, topMBType[mbX]); + + if (mBlock.cbpLuma() > 0 || mBlock.cbpChroma() > 0) { + mBlock.mbQPDelta = readMBQpDelta(mBlock.prevMbType); + } + readResidualLuma(mBlock, leftAvailable, topAvailable, mbX, mbY); + if (chromaFormat != ColorSpace.MONO) { + readChromaResidual(mBlock, leftAvailable, topAvailable, mbX); + } + } + + public void readResidualLuma(MBlock mBlock, boolean leftAvailable, boolean topAvailable, int mbX, int mbY) { + if (!mBlock.transform8x8Used) { + readLuma(mBlock, leftAvailable, topAvailable, mbX, mbY); + } else if (sh.pps.entropyCodingModeFlag) { + readLuma8x8CABAC(mBlock, mbX, mbY); + } else { + readLuma8x8CAVLC(mBlock, leftAvailable, topAvailable, mbX, mbY); + } + } + + private void readLuma(MBlock mBlock, boolean leftAvailable, boolean topAvailable, int mbX, int mbY) { + for (int i = 0; i < 16; i++) { + int blkOffLeft = H264Const.MB_DISP_OFF_LEFT[i]; + int blkOffTop = H264Const.MB_DISP_OFF_TOP[i]; + int blkX = (mbX << 2) + blkOffLeft; + int blkY = (mbY << 2) + blkOffTop; + + if ((mBlock.cbpLuma() & (1 << (i >> 2))) == 0) { + if (!sh.pps.entropyCodingModeFlag) + setZeroCoeff(0, blkX, blkOffTop); + continue; + } + + mBlock.nCoeff[i] = readResidualAC(leftAvailable, topAvailable, mbX, mBlock.curMbType, mBlock.cbpLuma(), + blkOffLeft, blkOffTop, blkX, blkY, mBlock.ac[0][i]); + } + + savePrevCBP(mBlock._cbp); + } + + private void readLuma8x8CABAC(MBlock mBlock, int mbX, int mbY) { + for (int i = 0; i < 4; i++) { + int blkOffLeft = (i & 1) << 1; + int blkOffTop = i & 2; + int blkX = (mbX << 2) + blkOffLeft; + int blkY = (mbY << 2) + blkOffTop; + + if ((mBlock.cbpLuma() & (1 << i)) == 0) { + continue; + } + + int nCoeff = readLumaAC8x8(blkX, blkY, mBlock.ac[0][i]); + + int blk4x4Offset = i << 2; + mBlock.nCoeff[blk4x4Offset] = mBlock.nCoeff[blk4x4Offset + 1] = mBlock.nCoeff[blk4x4Offset + 2] = mBlock.nCoeff[blk4x4Offset + 3] = nCoeff; + } + savePrevCBP(mBlock._cbp); + } + + private void readLuma8x8CAVLC(MBlock mBlock, boolean leftAvailable, boolean topAvailable, int mbX, int mbY) { + for (int i = 0; i < 4; i++) { + int blk8x8OffLeft = (i & 1) << 1; + int blk8x8OffTop = i & 2; + int blkX = (mbX << 2) + blk8x8OffLeft; + int blkY = (mbY << 2) + blk8x8OffTop; + + if ((mBlock.cbpLuma() & (1 << i)) == 0) { + setZeroCoeff(0, blkX, blk8x8OffTop); + setZeroCoeff(0, blkX + 1, blk8x8OffTop); + setZeroCoeff(0, blkX, blk8x8OffTop + 1); + setZeroCoeff(0, blkX + 1, blk8x8OffTop + 1); + continue; + } + int coeffs = 0; + for (int j = 0; j < 4; j++) { + int[] ac16 = new int[16]; + int blkOffLeft = blk8x8OffLeft + (j & 1); + int blkOffTop = blk8x8OffTop + (j >> 1); + coeffs += readLumaAC(leftAvailable, topAvailable, mbX, mBlock.curMbType, blkX, j, ac16, blkOffLeft, + blkOffTop); + for (int k = 0; k < 16; k++) + mBlock.ac[0][i][CoeffTransformer.zigzag8x8[(k << 2) + j]] = ac16[k]; + } + int blk4x4Offset = i << 2; + mBlock.nCoeff[blk4x4Offset] = mBlock.nCoeff[blk4x4Offset + 1] = mBlock.nCoeff[blk4x4Offset + 2] = mBlock.nCoeff[blk4x4Offset + 3] = coeffs; + } + } + + public void readChromaResidual(MBlock mBlock, boolean leftAvailable, boolean topAvailable, int mbX) { + if (mBlock.cbpChroma() != 0) { + if ((mBlock.cbpChroma() & 3) > 0) { + readChromaDC(mbX, leftAvailable, topAvailable, mBlock.dc1, 1, mBlock.curMbType); + readChromaDC(mbX, leftAvailable, topAvailable, mBlock.dc2, 2, mBlock.curMbType); + } + _readChromaAC(leftAvailable, topAvailable, mbX, mBlock.dc1, 1, mBlock.curMbType, + (mBlock.cbpChroma() & 2) > 0, mBlock.ac[1]); + _readChromaAC(leftAvailable, topAvailable, mbX, mBlock.dc2, 2, mBlock.curMbType, + (mBlock.cbpChroma() & 2) > 0, mBlock.ac[2]); + } else if (!sh.pps.entropyCodingModeFlag) { + setZeroCoeff(1, mbX << 1, 0); + setZeroCoeff(1, (mbX << 1) + 1, 1); + setZeroCoeff(2, mbX << 1, 0); + setZeroCoeff(2, (mbX << 1) + 1, 1); + } + } + + private void _readChromaAC(boolean leftAvailable, boolean topAvailable, int mbX, int[] dc, int comp, + MBType curMbType, boolean codedAC, int[][] residualOut) { + for (int i = 0; i < dc.length; i++) { + int[] ac = residualOut[i]; + int blkOffLeft = H264Const.MB_DISP_OFF_LEFT[i]; + int blkOffTop = H264Const.MB_DISP_OFF_TOP[i]; + + int blkX = (mbX << 1) + blkOffLeft; + + if (codedAC) { + readChromaAC(leftAvailable, topAvailable, mbX, comp, curMbType, ac, blkOffLeft, blkOffTop, blkX); + } else { + if (!sh.pps.entropyCodingModeFlag) + setZeroCoeff(comp, blkX, blkOffTop); + } + } + } + + private void readIPCM(MBlock mBlock) { + reader.align(); + + for (int i = 0; i < 256; i++) { + mBlock.ipcm.samplesLuma[i] = reader.readNBit(8); + } + int MbWidthC = 16 >> chromaFormat.compWidth[1]; + int MbHeightC = 16 >> chromaFormat.compHeight[1]; + + for (int i = 0; i < 2 * MbWidthC * MbHeightC; i++) { + mBlock.ipcm.samplesChroma[i] = reader.readNBit(8); + } + } + + public void readMBlock(MBlock mBlock, SliceType sliceType) { + + if (sliceType == SliceType.I) { + readMBlockI(mBlock); + } else if (sliceType == SliceType.P) { + readMBlockP(mBlock); + } else { + readMBlockB(mBlock); + } + int mbX = mapper.getMbX(mBlock.mbIdx); + topCBPLuma[mbX] = leftCBPLuma = mBlock.cbpLuma(); + topCBPChroma[mbX] = leftCBPChroma = mBlock.cbpChroma(); + tf8x8Left = tf8x8Top[mbX] = mBlock.transform8x8Used; + } + + private void readMBlockI(MBlock mBlock) { + + mBlock.mbType = decodeMBTypeI(mBlock.mbIdx, mapper.leftAvailable(mBlock.mbIdx), + mapper.topAvailable(mBlock.mbIdx), leftMBType, topMBType[mapper.getMbX(mBlock.mbIdx)]); + readMBlockIInt(mBlock, mBlock.mbType); + } + + private void readMBlockIInt(MBlock mBlock, int mbType) { + if (mbType == 0) { + mBlock.curMbType = MBType.I_NxN; + readIntraNxN(mBlock); + } else if (mbType >= 1 && mbType <= 24) { + mBlock.curMbType = MBType.I_16x16; + readIntra16x16(mbType - 1, mBlock); + } else { + Logger.warn("IPCM macroblock found. Not tested, may cause unpredictable behavior."); + mBlock.curMbType = MBType.I_PCM; + readIPCM(mBlock); + } + } + + private void readMBlockP(MBlock mBlock) { + mBlock.mbType = readMBTypeP(); + + switch (mBlock.mbType) { + case 0: + mBlock.curMbType = MBType.P_16x16; + readInter16x16(L0, mBlock); + break; + case 1: + mBlock.curMbType = MBType.P_16x8; + readInter16x8(L0, L0, mBlock); + break; + case 2: + mBlock.curMbType = MBType.P_8x16; + readIntra8x16(L0, L0, mBlock); + break; + case 3: + mBlock.curMbType = MBType.P_8x8; + readMBlock8x8(mBlock); + break; + case 4: + mBlock.curMbType = MBType.P_8x8ref0; + readMBlock8x8(mBlock); + break; + default: + readMBlockIInt(mBlock, mBlock.mbType - 5); + } + } + + private void readMBlockB(MBlock mBlock) { + mBlock.mbType = readMBTypeB(mBlock.mbIdx, mapper.leftAvailable(mBlock.mbIdx), + mapper.topAvailable(mBlock.mbIdx), leftMBType, topMBType[mapper.getMbX(mBlock.mbIdx)]); + if (mBlock.mbType >= 23) { + readMBlockIInt(mBlock, mBlock.mbType - 23); + } else { + mBlock.curMbType = H264Const.bMbTypes[mBlock.mbType]; + + if (mBlock.mbType == 0) { + readMBlockBDirect(mBlock); + } else if (mBlock.mbType <= 3) { + readInter16x16(H264Const.bPredModes[mBlock.mbType][0], mBlock); + } else if (mBlock.mbType == 22) { + readMBlock8x8(mBlock); + } else if ((mBlock.mbType & 1) == 0) { + readInter16x8(H264Const.bPredModes[mBlock.mbType][0], H264Const.bPredModes[mBlock.mbType][1], mBlock); + } else { + readIntra8x16(H264Const.bPredModes[mBlock.mbType][0], H264Const.bPredModes[mBlock.mbType][1], mBlock); + } + } + } + + public SliceHeader getSliceHeader() { + return sh; + } + + public NALUnit getNALUnit() { + return nalUnit; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/FlatMBlockMapper.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/FlatMBlockMapper.java new file mode 100644 index 0000000..cd94426 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/FlatMBlockMapper.java @@ -0,0 +1,54 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode.aso; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * A block map that that maps macroblocks sequentially in scan order + * + * @author The JCodec project + */ +public class FlatMBlockMapper implements Mapper { + private int frameWidthInMbs; + private int firstMBAddr; + + public FlatMBlockMapper(int frameWidthInMbs, int firstMBAddr) { + this.frameWidthInMbs = frameWidthInMbs; + this.firstMBAddr = firstMBAddr; + } + + public boolean leftAvailable(int index) { + int mbAddr = index + firstMBAddr; + boolean atTheBorder = mbAddr % frameWidthInMbs == 0; + return !atTheBorder && (mbAddr > firstMBAddr); + } + + public boolean topAvailable(int index) { + int mbAddr = index + firstMBAddr; + return mbAddr - frameWidthInMbs >= firstMBAddr; + } + + public int getAddress(int index) { + return firstMBAddr + index; + } + + public int getMbX(int index) { + return getAddress(index) % frameWidthInMbs; + } + + public int getMbY(int index) { + return getAddress(index) / frameWidthInMbs; + } + + public boolean topRightAvailable(int index) { + int mbAddr = index + firstMBAddr; + boolean atTheBorder = (mbAddr + 1) % frameWidthInMbs == 0; + return !atTheBorder && mbAddr - frameWidthInMbs + 1 >= firstMBAddr; + } + + public boolean topLeftAvailable(int index) { + int mbAddr = index + firstMBAddr; + boolean atTheBorder = mbAddr % frameWidthInMbs == 0; + return !atTheBorder && mbAddr - frameWidthInMbs - 1 >= firstMBAddr; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/MBToSliceGroupMap.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/MBToSliceGroupMap.java new file mode 100644 index 0000000..464e5c7 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/MBToSliceGroupMap.java @@ -0,0 +1,34 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode.aso; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Contains a mapping of macroblocks to slice groups. Groups is an array of + * group slice group indices having a dimension picWidthInMbs x picHeightInMbs + * + * @author The JCodec project + */ +public class MBToSliceGroupMap { + private int[] groups; + private int[] indices; + private int[][] inverse; + + public MBToSliceGroupMap(int[] groups, int[] indices, int[][] inverse) { + this.groups = groups; + this.indices = indices; + this.inverse = inverse; + } + + public int[] getGroups() { + return groups; + } + + public int[] getIndices() { + return indices; + } + + public int[][] getInverse() { + return inverse; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/MapManager.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/MapManager.java new file mode 100644 index 0000000..5aee851 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/MapManager.java @@ -0,0 +1,124 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode.aso; + +import org.monte.media.impl.jcodec.codecs.h264.io.model.PictureParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class MapManager { + private SeqParameterSet sps; + private PictureParameterSet pps; + private MBToSliceGroupMap mbToSliceGroupMap; + private int prevSliceGroupChangeCycle; + + public MapManager(SeqParameterSet sps, PictureParameterSet pps) { + this.sps = sps; + this.pps = pps; + this.mbToSliceGroupMap = buildMap(sps, pps); + } + + private MBToSliceGroupMap buildMap(SeqParameterSet sps, PictureParameterSet pps) { + int numGroups = pps.numSliceGroupsMinus1 + 1; + + if (numGroups > 1) { + int[] map; + int picWidthInMbs = sps.picWidthInMbsMinus1 + 1; + int picHeightInMbs = SeqParameterSet.getPicHeightInMbs(sps); + + if (pps.sliceGroupMapType == 0) { + int[] runLength = new int[numGroups]; + for (int i = 0; i < numGroups; i++) { + runLength[i] = pps.runLengthMinus1[i] + 1; + } + map = SliceGroupMapBuilder.buildInterleavedMap(picWidthInMbs, picHeightInMbs, runLength); + } else if (pps.sliceGroupMapType == 1) { + map = SliceGroupMapBuilder.buildDispersedMap(picWidthInMbs, picHeightInMbs, numGroups); + } else if (pps.sliceGroupMapType == 2) { + map = SliceGroupMapBuilder.buildForegroundMap(picWidthInMbs, picHeightInMbs, numGroups, pps.topLeft, + pps.bottomRight); + } else if (pps.sliceGroupMapType >= 3 && pps.sliceGroupMapType <= 5) { + return null; + } else if (pps.sliceGroupMapType == 6) { + map = pps.sliceGroupId; + } else { + throw new RuntimeException("Unsupported slice group map type"); + } + + return buildMapIndices(map, numGroups); + } + + return null; + } + + private MBToSliceGroupMap buildMapIndices(int[] map, int numGroups) { + int[] ind = new int[numGroups]; + int[] indices = new int[map.length]; + + for (int i = 0; i < map.length; i++) { + indices[i] = ind[map[i]]++; + } + + int[][] inverse = new int[numGroups][]; + for (int i = 0; i < numGroups; i++) { + inverse[i] = new int[ind[i]]; + } + ind = new int[numGroups]; + for (int i = 0; i < map.length; i++) { + int sliceGroup = map[i]; + inverse[sliceGroup][ind[sliceGroup]++] = i; + } + + return new MBToSliceGroupMap(map, indices, inverse); + } + + private void updateMap(SliceHeader sh) { + int mapType = pps.sliceGroupMapType; + int numGroups = pps.numSliceGroupsMinus1 + 1; + + if (numGroups > 1 && mapType >= 3 && mapType <= 5 + && (sh.sliceGroupChangeCycle != prevSliceGroupChangeCycle || mbToSliceGroupMap == null)) { + + prevSliceGroupChangeCycle = sh.sliceGroupChangeCycle; + + int picWidthInMbs = sps.picWidthInMbsMinus1 + 1; + int picHeightInMbs = SeqParameterSet.getPicHeightInMbs(sps); + int picSizeInMapUnits = picWidthInMbs * picHeightInMbs; + int mapUnitsInSliceGroup0 = sh.sliceGroupChangeCycle * (pps.sliceGroupChangeRateMinus1 + 1); + mapUnitsInSliceGroup0 = mapUnitsInSliceGroup0 > picSizeInMapUnits ? picSizeInMapUnits + : mapUnitsInSliceGroup0; + + int sizeOfUpperLeftGroup = (pps.sliceGroupChangeDirectionFlag ? (picSizeInMapUnits - mapUnitsInSliceGroup0) + : mapUnitsInSliceGroup0); + + int[] map; + if (mapType == 3) { + map = SliceGroupMapBuilder.buildBoxOutMap(picWidthInMbs, picHeightInMbs, + pps.sliceGroupChangeDirectionFlag, mapUnitsInSliceGroup0); + } else if (mapType == 4) { + map = SliceGroupMapBuilder.buildRasterScanMap(picWidthInMbs, picHeightInMbs, sizeOfUpperLeftGroup, + pps.sliceGroupChangeDirectionFlag); + } else { + map = SliceGroupMapBuilder.buildWipeMap(picWidthInMbs, picHeightInMbs, sizeOfUpperLeftGroup, + pps.sliceGroupChangeDirectionFlag); + } + + this.mbToSliceGroupMap = buildMapIndices(map, numGroups); + } + } + + public Mapper getMapper(SliceHeader sh) { + updateMap(sh); + int firstMBInSlice = sh.firstMbInSlice; + if (pps.numSliceGroupsMinus1 > 0) { + + return new PrebuiltMBlockMapper(mbToSliceGroupMap, firstMBInSlice, sps.picWidthInMbsMinus1 + 1); + } else { + return new FlatMBlockMapper(sps.picWidthInMbsMinus1 + 1, firstMBInSlice); + } + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/Mapper.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/Mapper.java new file mode 100644 index 0000000..3f75955 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/Mapper.java @@ -0,0 +1,23 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode.aso; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public interface Mapper { + boolean leftAvailable(int index); + + boolean topAvailable(int index); + + int getAddress(int index); + + int getMbX(int mbIndex); + + int getMbY(int mbIndex); + + boolean topRightAvailable(int mbIndex); + + boolean topLeftAvailable(int mbIdx); +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/PrebuiltMBlockMapper.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/PrebuiltMBlockMapper.java new file mode 100644 index 0000000..9bee1bc --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/PrebuiltMBlockMapper.java @@ -0,0 +1,67 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode.aso; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * A macrboblock to slice group mapper that operates on prebuilt map, passed to + * it in the constructor + * + * @author The JCodec project + */ +public class PrebuiltMBlockMapper implements Mapper { + + private MBToSliceGroupMap map; + private int firstMBInSlice; + private int groupId; + private int picWidthInMbs; + private int indexOfFirstMb; + + public PrebuiltMBlockMapper(MBToSliceGroupMap map, int firstMBInSlice, int picWidthInMbs) { + this.map = map; + this.firstMBInSlice = firstMBInSlice; + this.groupId = map.getGroups()[firstMBInSlice]; + this.picWidthInMbs = picWidthInMbs; + this.indexOfFirstMb = map.getIndices()[firstMBInSlice]; + } + + public int getAddress(int mbIndex) { + return map.getInverse()[groupId][mbIndex + indexOfFirstMb]; + } + + public boolean leftAvailable(int mbIndex) { + int mbAddr = map.getInverse()[groupId][mbIndex + indexOfFirstMb]; + int leftMBAddr = mbAddr - 1; + + return !((leftMBAddr < firstMBInSlice) || ((mbAddr % picWidthInMbs) == 0) || (map.getGroups()[leftMBAddr] != groupId)); + } + + public boolean topAvailable(int mbIndex) { + int mbAddr = map.getInverse()[groupId][mbIndex + indexOfFirstMb]; + int topMBAddr = mbAddr - picWidthInMbs; + + return !((topMBAddr < firstMBInSlice) || (map.getGroups()[topMBAddr] != groupId)); + } + + public int getMbX(int index) { + return getAddress(index) % picWidthInMbs; + } + + public int getMbY(int index) { + return getAddress(index) / picWidthInMbs; + } + + public boolean topRightAvailable(int mbIndex) { + int mbAddr = map.getInverse()[groupId][mbIndex + indexOfFirstMb]; + int topRMBAddr = mbAddr - picWidthInMbs + 1; + + return !((topRMBAddr < firstMBInSlice) || (((mbAddr + 1) % picWidthInMbs) == 0) || (map.getGroups()[topRMBAddr] != groupId)); + } + + public boolean topLeftAvailable(int mbIndex) { + int mbAddr = map.getInverse()[groupId][mbIndex + indexOfFirstMb]; + int topLMBAddr = mbAddr - picWidthInMbs - 1; + + return !((topLMBAddr < firstMBInSlice) || ((mbAddr % picWidthInMbs) == 0) || (map.getGroups()[topLMBAddr] != groupId)); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/SliceGroupMapBuilder.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/SliceGroupMapBuilder.java new file mode 100644 index 0000000..5a16442 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/aso/SliceGroupMapBuilder.java @@ -0,0 +1,242 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode.aso; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * A helper class that builds macroblock to slice group maps needed by ASO + * (Arbitrary Slice Order) + * + * @author The JCodec project + */ +public class SliceGroupMapBuilder { + + /** + * Interleaved slice group map. Each slice group fills a number of cells + * equal to the appropriate run length, then followed by the next slice + * group. + *

+ * Example: + *

+ * 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, + */ + public static int[] buildInterleavedMap(int picWidthInMbs, int picHeightInMbs, int[] runLength) { + + int numSliceGroups = runLength.length; + int picSizeInMbs = picWidthInMbs * picHeightInMbs; + int[] groups = new int[picSizeInMbs]; + + int i = 0; + do { + for (int iGroup = 0; iGroup < numSliceGroups && i < picSizeInMbs; i += runLength[iGroup++]) { + for (int j = 0; j < runLength[iGroup] && i + j < picSizeInMbs; j++) { + groups[i + j] = iGroup; + } + } + } while (i < picSizeInMbs); + + return groups; + } + + /** + * A dispersed map. Every odd line starts from the (N / 2)th group + *

+ * Example: + *

+ * 0, 1, 2, 3, 0, 1, 2, 3 2, 3, 0, 1, 2, 3, 0, 1 0, 1, 2, 3, 0, 1, 2, 3 2, + * 3, 0, 1, 2, 3, 0, 1 0, 1, 2, 3, 0, 1, 2, 3 2, 3, 0, 1, 2, 3, 0, 1 0, 1, + * 2, 3, 0, 1, 2, 3 2, 3, 0, 1, 2, 3, 0, 1 0, 1, 2, 3, 0, 1, 2, 3 2, 3, 0, + * 1, 2, 3, 0, 1 + */ + public static int[] buildDispersedMap(int picWidthInMbs, int picHeightInMbs, int numSliceGroups) { + + int picSizeInMbs = picWidthInMbs * picHeightInMbs; + int[] groups = new int[picSizeInMbs]; + + for (int i = 0; i < picSizeInMbs; i++) { + int group = ((i % picWidthInMbs) + (((i / picWidthInMbs) * numSliceGroups) / 2)) % numSliceGroups; + groups[i] = group; + } + + return groups; + } + + /** + * A foreground macroblock to slice group map. Macroblocks of the last slice + * group are the background, all the others represent rectangles covering + * areas with top-left corner specified by topLeftAddr[group] and bottom + * right corner specified by bottomRightAddr[group]. + * + * @param picWidthInMbs + * @param picHeightInMbs + * @param numSliceGroups Total number of slice groups + * @param topLeftAddr Addresses of macroblocks that are top-left corners of + * respective slice groups + * @param bottomRightAddr Addresses macroblocks that are bottom-right corners of + * respective slice groups + * @return + */ + public static int[] buildForegroundMap(int picWidthInMbs, int picHeightInMbs, int numSliceGroups, + int[] topLeftAddr, int[] bottomRightAddr) { + + int picSizeInMbs = picWidthInMbs * picHeightInMbs; + int[] groups = new int[picSizeInMbs]; + + for (int i = 0; i < picSizeInMbs; i++) + groups[i] = numSliceGroups - 1; + + int tot = 0; + for (int iGroup = numSliceGroups - 2; iGroup >= 0; iGroup--) { + + int yTopLeft = topLeftAddr[iGroup] / picWidthInMbs; + int xTopLeft = topLeftAddr[iGroup] % picWidthInMbs; + int yBottomRight = bottomRightAddr[iGroup] / picWidthInMbs; + int xBottomRight = bottomRightAddr[iGroup] % picWidthInMbs; + + int sz = (yBottomRight - yTopLeft + 1) * (xBottomRight - xTopLeft + 1); + tot += sz; + + int ind = 0; + for (int y = yTopLeft; y <= yBottomRight; y++) + for (int x = xTopLeft; x <= xBottomRight; x++) { + int mbAddr = y * picWidthInMbs + x; + groups[mbAddr] = iGroup; + } + } + + return groups; + } + + /** + * A boxout macroblock to slice group mapping. Only applicable when there's + * exactly 2 slice groups. Slice group 1 is a background, while slice group + * 0 is a box in the middle of the frame. + * + * @param picWidthInMbs + * @param picHeightInMbs + * @param changeDirection + * @param numberOfMbsInBox number of macroblocks in slice group 0 + * @return + */ + public static int[] buildBoxOutMap(int picWidthInMbs, int picHeightInMbs, boolean changeDirection, + int numberOfMbsInBox) { + + int picSizeInMbs = picWidthInMbs * picHeightInMbs; + int[] groups = new int[picSizeInMbs]; + int changeDirectionInt = changeDirection ? 1 : 0; + + for (int i = 0; i < picSizeInMbs; i++) + groups[i] = 1; + + int x = (picWidthInMbs - changeDirectionInt) / 2; + int y = (picHeightInMbs - changeDirectionInt) / 2; + int leftBound = x; + int topBound = y; + int rightBound = x; + int bottomBound = y; + int xDir = changeDirectionInt - 1; + int yDir = changeDirectionInt; + + boolean mapUnitVacant = false; + for (int k = 0; k < numberOfMbsInBox; k += (mapUnitVacant ? 1 : 0)) { + int mbAddr = y * picWidthInMbs + x; + mapUnitVacant = (groups[mbAddr] == 1); + if (mapUnitVacant) { + groups[mbAddr] = 0; + } + if (xDir == -1 && x == leftBound) { + leftBound = Max(leftBound - 1, 0); + x = leftBound; + xDir = 0; + yDir = 2 * changeDirectionInt - 1; + } else if (xDir == 1 && x == rightBound) { + rightBound = Min(rightBound + 1, picWidthInMbs - 1); + x = rightBound; + xDir = 0; + yDir = 1 - 2 * changeDirectionInt; + } else if (yDir == -1 && y == topBound) { + topBound = Max(topBound - 1, 0); + y = topBound; + xDir = 1 - 2 * changeDirectionInt; + yDir = 0; + } else if (yDir == 1 && y == bottomBound) { + bottomBound = Min(bottomBound + 1, picHeightInMbs - 1); + y = bottomBound; + xDir = 2 * changeDirectionInt - 1; + yDir = 0; + } else { + x += xDir; + y += yDir; + } + } + + return groups; + } + + private static int Min(int i, int j) { + return i < j ? i : j; + } + + private static int Max(int i, int j) { + return i > j ? i : j; + } + + /** + * A macroblock to slice group map that fills frame in raster scan. + * + * @param picWidthInMbs + * @param picHeightInMbs + * @param sizeOfUpperLeftGroup + * @param changeDirection + * @return + */ + public static int[] buildRasterScanMap(int picWidthInMbs, int picHeightInMbs, int sizeOfUpperLeftGroup, + boolean changeDirection) { + + int picSizeInMbs = picWidthInMbs * picHeightInMbs; + int[] groups = new int[picSizeInMbs]; + int changeDirectionInt = changeDirection ? 1 : 0; + + int i; + for (i = 0; i < sizeOfUpperLeftGroup; i++) { + groups[i] = changeDirectionInt; + } + + for (; i < picSizeInMbs; i++) { + groups[i] = 1 - changeDirectionInt; + } + + return groups; + } + + /** + * A macroblock to slice group map that fills frame column by column + * + * @param picWidthInMbs + * @param picHeightInMbs + * @param sizeOfUpperLeftGroup + * @param changeDirection + * @return + */ + public static int[] buildWipeMap(int picWidthInMbs, int picHeightInMbs, int sizeOfUpperLeftGroup, + boolean changeDirection) { + + int picSizeInMbs = picWidthInMbs * picHeightInMbs; + int[] groups = new int[picSizeInMbs]; + int changeDirectionInt = changeDirection ? 1 : 0; + + int k = 0; + for (int j = 0; j < picWidthInMbs; j++) { + for (int i = 0; i < picHeightInMbs; i++) { + int mbAddr = i * picWidthInMbs + j; + if (k++ < sizeOfUpperLeftGroup) { + groups[mbAddr] = changeDirectionInt; + } else { + groups[mbAddr] = 1 - changeDirectionInt; + } + } + } + + return groups; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/deblock/DeblockingFilter.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/deblock/DeblockingFilter.java new file mode 100644 index 0000000..52f8974 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/decode/deblock/DeblockingFilter.java @@ -0,0 +1,461 @@ +package org.monte.media.impl.jcodec.codecs.h264.decode.deblock; + +import org.monte.media.impl.jcodec.codecs.h264.decode.DeblockerInput; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.common.model.ColorSpace; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import static java.lang.Math.abs; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvRef; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvX; +import static org.monte.media.impl.jcodec.codecs.h264.H264Utils.Mv.mvY; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * A filter that removes DCT artifacts on block boundaries. + *

+ * It's operation is dependant on QP and is designed the way that the strenth is + * adjusted to the likelyhood of appearence of blocking artifacts on the + * specific edges. + *

+ * Builds a parameter for deblocking filter based on the properties of specific + * macroblocks. + *

+ * A parameter specifies the behavior of deblocking filter on each of 8 edges + * that need to filtered for a macroblock. + *

+ * For each edge the following things are evaluated on it's both sides: presence + * of DCT coded residual; motion vector difference; spatial location. + * + * @author The JCodec project + */ +public class DeblockingFilter { + + public static int[] alphaTab = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 5, 6, 7, 8, 9, 10, 12, + 13, 15, 17, 20, 22, 25, 28, 32, 36, 40, 45, 50, 56, 63, 71, 80, 90, 101, 113, 127, 144, 162, 182, 203, 226, + 255, 255}; + public static int[] betaTab = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 6, + 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18}; + + public static int[][] tcs = new int[][]{ + new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 6, 6, 7, 8, 9, 10, 11, 13}, + + new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, + 2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 6, 7, 8, 8, 10, 11, 12, 13, 15, 17}, + + new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, + 3, 3, 4, 4, 4, 5, 6, 6, 7, 8, 9, 10, 11, 13, 14, 16, 18, 20, 23, 25}}; + + private DeblockerInput di; + + public DeblockingFilter(int bitDepthLuma, int bitDepthChroma, DeblockerInput di) { + this.di = di; + } + + public void deblockFrame(Picture result) { + ColorSpace color = result.getColor(); + int[][] bsV = new int[4][4], bsH = new int[4][4]; + for (int i = 0; i < di.shs.length; i++) { + calcBsH(result, i, bsH); + calcBsV(result, i, bsV); + + for (int c = 0; c < color.nComp; c++) { + fillVerticalEdge(result, c, i, bsV); + fillHorizontalEdge(result, c, i, bsH); + } + } + } + + static int[] inverse = new int[]{0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 12, 13, 10, 11, 14, 15}; + + private int calcBoundaryStrenth(boolean atMbBoundary, boolean leftIntra, boolean rightIntra, int leftCoeff, + int rightCoeff, int mvA0, int mvB0, int mvA1, int mvB1, int mbAddrA, int mbAddrB) { + + if (atMbBoundary && (leftIntra || rightIntra)) + return 4; + else if (leftIntra || rightIntra) + return 3; + else { + + if (leftCoeff > 0 || rightCoeff > 0) + return 2; + + int nA = (mvRef(mvA0) == -1 ? 0 : 1) + (mvRef(mvA1) == -1 ? 0 : 1); + int nB = (mvRef(mvB0) == -1 ? 0 : 1) + (mvRef(mvB1) == -1 ? 0 : 1); + + if (nA != nB) + return 1; + + Picture ra0 = mvRef(mvA0) < 0 ? null : di.refsUsed[mbAddrA][0][mvRef(mvA0)]; + Picture ra1 = mvRef(mvA1) < 0 ? null : di.refsUsed[mbAddrA][1][mvRef(mvA1)]; + + Picture rb0 = mvRef(mvB0) < 0 ? null : di.refsUsed[mbAddrB][0][mvRef(mvB0)]; + Picture rb1 = mvRef(mvB1) < 0 ? null : di.refsUsed[mbAddrB][1][mvRef(mvB1)]; + + if (ra0 != rb0 && ra0 != rb1 || ra1 != rb0 && ra1 != rb1 || rb0 != ra0 && rb0 != ra1 || rb1 != ra0 + && rb1 != ra1) + return 1; + + if (ra0 == ra1 && ra1 == rb0 && rb0 == rb1) { + return ra0 != null + && (mvThresh(mvA0, mvB0) || mvThresh(mvA1, mvB0) || mvThresh(mvA0, mvB1) || mvThresh(mvA1, mvB1)) ? 1 + : 0; + } else if (ra0 == rb0 && ra1 == rb1) { + return ra0 != null && mvThresh(mvA0, mvB0) || ra1 != null && mvThresh(mvA1, mvB1) ? 1 : 0; + } else if (ra0 == rb1 && ra1 == rb0) { + return ra0 != null && mvThresh(mvA0, mvB1) || ra1 != null && mvThresh(mvA1, mvB0) ? 1 : 0; + } + } + + return 0; + } + + private boolean mvThresh(int v0, int v1) { + return abs(mvX(v0) - mvX(v1)) >= 4 || abs(mvY(v0) - mvY(v1)) >= 4; + } + + private static int getIdxBeta(int sliceBetaOffset, int avgQp) { + return MathUtil.clip(avgQp + sliceBetaOffset, 0, 51); + } + + private static int getIdxAlpha(int sliceAlphaC0Offset, int avgQp) { + return MathUtil.clip(avgQp + sliceAlphaC0Offset, 0, 51); + } + + private void calcBsH(Picture pic, int mbAddr, int[][] bs) { + SliceHeader sh = di.shs[mbAddr]; + int mbWidth = sh.sps.picWidthInMbsMinus1 + 1; + + int mbX = mbAddr % mbWidth; + int mbY = mbAddr / mbWidth; + + boolean topAvailable = mbY > 0 && (sh.disableDeblockingFilterIdc != 2 || di.shs[mbAddr - mbWidth] == sh); + boolean thisIntra = di.mbTypes[mbAddr] != null && di.mbTypes[mbAddr].isIntra(); + + if (topAvailable) { + boolean topIntra = di.mbTypes[mbAddr - mbWidth] != null && di.mbTypes[mbAddr - mbWidth].isIntra(); + for (int blkX = 0; blkX < 4; blkX++) { + int thisBlkX = (mbX << 2) + blkX; + int thisBlkY = (mbY << 2); + + bs[0][blkX] = calcBoundaryStrenth(true, topIntra, thisIntra, di.nCoeff[thisBlkY][thisBlkX], + di.nCoeff[thisBlkY - 1][thisBlkX], di.mvs.getMv(thisBlkX, thisBlkY, 0), + di.mvs.getMv(thisBlkX, thisBlkY - 1, 0), di.mvs.getMv(thisBlkX, thisBlkY, 1), + di.mvs.getMv(thisBlkX, thisBlkY - 1, 1), mbAddr, mbAddr - mbWidth); + + } + } + + for (int blkY = 1; blkY < 4; blkY++) { + for (int blkX = 0; blkX < 4; blkX++) { + int thisBlkX = (mbX << 2) + blkX; + int thisBlkY = (mbY << 2) + blkY; + + bs[blkY][blkX] = calcBoundaryStrenth(false, thisIntra, thisIntra, di.nCoeff[thisBlkY][thisBlkX], + di.nCoeff[thisBlkY - 1][thisBlkX], di.mvs.getMv(thisBlkX, thisBlkY, 0), + di.mvs.getMv(thisBlkX, thisBlkY - 1, 0), di.mvs.getMv(thisBlkX, thisBlkY, 1), + di.mvs.getMv(thisBlkX, thisBlkY - 1, 1), mbAddr, mbAddr); + } + } + } + + private void fillHorizontalEdge(Picture pic, int comp, int mbAddr, int[][] bs) { + SliceHeader sh = di.shs[mbAddr]; + int mbWidth = sh.sps.picWidthInMbsMinus1 + 1; + + int alpha = sh.sliceAlphaC0OffsetDiv2 << 1; + int beta = sh.sliceBetaOffsetDiv2 << 1; + + int mbX = mbAddr % mbWidth; + int mbY = mbAddr / mbWidth; + + boolean topAvailable = mbY > 0 && (sh.disableDeblockingFilterIdc != 2 || di.shs[mbAddr - mbWidth] == sh); + int curQp = di.mbQps[comp][mbAddr]; + + int cW = 2 - pic.getColor().compWidth[comp]; + int cH = 2 - pic.getColor().compHeight[comp]; + if (topAvailable) { + int topQp = di.mbQps[comp][mbAddr - mbWidth]; + int avgQp = (topQp + curQp + 1) >> 1; + for (int blkX = 0; blkX < 4; blkX++) { + int thisBlkX = (mbX << 2) + blkX; + int thisBlkY = (mbY << 2); + + filterBlockEdgeHoris(pic, comp, thisBlkX << cW, thisBlkY << cH, getIdxAlpha(alpha, avgQp), + getIdxBeta(beta, avgQp), bs[0][blkX], 1 << cW); + } + } + + boolean skip4x4 = comp == 0 && di.tr8x8Used[mbAddr] || cH == 1; + + for (int blkY = 1; blkY < 4; blkY++) { + if (skip4x4 && (blkY & 1) == 1) + continue; + + for (int blkX = 0; blkX < 4; blkX++) { + int thisBlkX = (mbX << 2) + blkX; + int thisBlkY = (mbY << 2) + blkY; + + filterBlockEdgeHoris(pic, comp, thisBlkX << cW, thisBlkY << cH, getIdxAlpha(alpha, curQp), + getIdxBeta(beta, curQp), bs[blkY][blkX], 1 << cW); + } + } + } + + private void calcBsV(Picture pic, int mbAddr, int[][] bs) { + + SliceHeader sh = di.shs[mbAddr]; + int mbWidth = sh.sps.picWidthInMbsMinus1 + 1; + + int mbX = mbAddr % mbWidth; + int mbY = mbAddr / mbWidth; + + boolean leftAvailable = mbX > 0 && (sh.disableDeblockingFilterIdc != 2 || di.shs[mbAddr - 1] == sh); + boolean thisIntra = di.mbTypes[mbAddr] != null && di.mbTypes[mbAddr].isIntra(); + + if (leftAvailable) { + boolean leftIntra = di.mbTypes[mbAddr - 1] != null && di.mbTypes[mbAddr - 1].isIntra(); + for (int blkY = 0; blkY < 4; blkY++) { + int thisBlkX = (mbX << 2); + int thisBlkY = (mbY << 2) + blkY; + bs[blkY][0] = calcBoundaryStrenth(true, leftIntra, thisIntra, di.nCoeff[thisBlkY][thisBlkX], + di.nCoeff[thisBlkY][thisBlkX - 1], di.mvs.getMv(thisBlkX, thisBlkY, 0), + di.mvs.getMv(thisBlkX - 1, thisBlkY, 0), di.mvs.getMv(thisBlkX, thisBlkY, 1), + di.mvs.getMv(thisBlkX - 1, thisBlkY, 1), mbAddr, mbAddr - 1); + } + } + + for (int blkX = 1; blkX < 4; blkX++) { + for (int blkY = 0; blkY < (1 << 2); blkY++) { + int thisBlkX = (mbX << 2) + blkX; + int thisBlkY = (mbY << 2) + blkY; + bs[blkY][blkX] = calcBoundaryStrenth(false, thisIntra, thisIntra, di.nCoeff[thisBlkY][thisBlkX], + di.nCoeff[thisBlkY][thisBlkX - 1], di.mvs.getMv(thisBlkX, thisBlkY, 0), + di.mvs.getMv(thisBlkX - 1, thisBlkY, 0), di.mvs.getMv(thisBlkX, thisBlkY, 1), + di.mvs.getMv(thisBlkX - 1, thisBlkY, 1), mbAddr, mbAddr); + } + } + } + + private void fillVerticalEdge(Picture pic, int comp, int mbAddr, int[][] bs) { + + SliceHeader sh = di.shs[mbAddr]; + int mbWidth = sh.sps.picWidthInMbsMinus1 + 1; + + int alpha = sh.sliceAlphaC0OffsetDiv2 << 1; + int beta = sh.sliceBetaOffsetDiv2 << 1; + + int mbX = mbAddr % mbWidth; + int mbY = mbAddr / mbWidth; + + boolean leftAvailable = mbX > 0 && (sh.disableDeblockingFilterIdc != 2 || di.shs[mbAddr - 1] == sh); + int curQp = di.mbQps[comp][mbAddr]; + + int cW = 2 - pic.getColor().compWidth[comp]; + int cH = 2 - pic.getColor().compHeight[comp]; + if (leftAvailable) { + int leftQp = di.mbQps[comp][mbAddr - 1]; + int avgQpV = (leftQp + curQp + 1) >> 1; + for (int blkY = 0; blkY < 4; blkY++) { + int thisBlkX = (mbX << 2); + int thisBlkY = (mbY << 2) + blkY; + filterBlockEdgeVert(pic, comp, thisBlkX << cW, thisBlkY << cH, getIdxAlpha(alpha, avgQpV), + getIdxBeta(beta, avgQpV), bs[blkY][0], 1 << cH); + } + } + boolean skip4x4 = comp == 0 && di.tr8x8Used[mbAddr] || cW == 1; + + for (int blkX = 1; blkX < 4; blkX++) { + if (skip4x4 && (blkX & 1) == 1) + continue; + for (int blkY = 0; blkY < 4; blkY++) { + int thisBlkX = (mbX << 2) + blkX; + int thisBlkY = (mbY << 2) + blkY; + filterBlockEdgeVert(pic, comp, thisBlkX << cW, thisBlkY << cH, getIdxAlpha(alpha, curQp), + getIdxBeta(beta, curQp), bs[blkY][blkX], 1 << cH); + } + } + } + + private void filterBlockEdgeHoris(Picture pic, int comp, int x, int y, int indexAlpha, int indexBeta, int bs, + int blkW) { + + int stride = pic.getPlaneWidth(comp); + int offset = y * stride + x; + + for (int pixOff = 0; pixOff < blkW; pixOff++) { + int p2Idx = offset - 3 * stride + pixOff; + int p1Idx = offset - 2 * stride + pixOff; + int p0Idx = offset - stride + pixOff; + int q0Idx = offset + pixOff; + int q1Idx = offset + stride + pixOff; + int q2Idx = offset + 2 * stride + pixOff; + + if (bs == 4) { + int p3Idx = offset - 4 * stride + pixOff; + int q3Idx = offset + 3 * stride + pixOff; + + filterBs4(indexAlpha, indexBeta, pic.getPlaneData(comp), pic.getPlaneData(comp), p3Idx, p2Idx, p1Idx, + p0Idx, q0Idx, q1Idx, q2Idx, q3Idx, comp != 0); + } else if (bs > 0) { + + filterBs(bs, indexAlpha, indexBeta, pic.getPlaneData(comp), pic.getPlaneData(comp), p2Idx, p1Idx, + p0Idx, q0Idx, q1Idx, q2Idx, comp != 0); + } + } + } + + private void filterBlockEdgeVert(Picture pic, int comp, int x, int y, int indexAlpha, int indexBeta, int bs, + int blkH) { + + int stride = pic.getPlaneWidth(comp); + for (int i = 0; i < blkH; i++) { + int offsetQ = (y + i) * stride + x; + int p2Idx = offsetQ - 3; + int p1Idx = offsetQ - 2; + int p0Idx = offsetQ - 1; + int q0Idx = offsetQ; + int q1Idx = offsetQ + 1; + int q2Idx = offsetQ + 2; + + if (bs == 4) { + int p3Idx = offsetQ - 4; + int q3Idx = offsetQ + 3; + filterBs4(indexAlpha, indexBeta, pic.getPlaneData(comp), pic.getPlaneData(comp), p3Idx, p2Idx, p1Idx, + p0Idx, q0Idx, q1Idx, q2Idx, q3Idx, comp != 0); + } else if (bs > 0) { + filterBs(bs, indexAlpha, indexBeta, pic.getPlaneData(comp), pic.getPlaneData(comp), p2Idx, p1Idx, + p0Idx, q0Idx, q1Idx, q2Idx, comp != 0); + } + } + } + + public static void filterBs(int bs, int indexAlpha, int indexBeta, byte[] pelsP, byte[] pelsQ, int p2Idx, int p1Idx, + int p0Idx, int q0Idx, int q1Idx, int q2Idx, boolean isChroma) { + + int p1 = pelsP[p1Idx]; + int p0 = pelsP[p0Idx]; + int q0 = pelsQ[q0Idx]; + int q1 = pelsQ[q1Idx]; + + int alphaThresh = alphaTab[indexAlpha]; + int betaThresh = betaTab[indexBeta]; + + boolean filterEnabled = abs(p0 - q0) < alphaThresh && abs(p1 - p0) < betaThresh && abs(q1 - q0) < betaThresh; + + if (!filterEnabled) + return; + + // System.out.printf("%h %h %h %h %h %h %h %h\n", q3, q2, q1, q0, p0, + // p1, p2, p3); + + int tC0 = tcs[bs - 1][indexAlpha]; + + boolean conditionP, conditionQ; + int tC; + if (!isChroma) { + int ap = abs(pelsP[p2Idx] - p0); + int aq = abs(pelsQ[q2Idx] - q0); + tC = tC0 + ((ap < betaThresh) ? 1 : 0) + ((aq < betaThresh) ? 1 : 0); + conditionP = ap < betaThresh; + conditionQ = aq < betaThresh; + } else { + tC = tC0 + 1; + conditionP = false; + conditionQ = false; + } + + int sigma = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3); + sigma = sigma < -tC ? -tC : (sigma > tC ? tC : sigma); + + int p0n = p0 + sigma; + p0n = p0n < -128 ? -128 : p0n; + int q0n = q0 - sigma; + q0n = q0n < -128 ? -128 : q0n; + + if (conditionP) { + int p2 = pelsP[p2Idx]; + + int diff = (p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1; + diff = diff < -tC0 ? -tC0 : (diff > tC0 ? tC0 : diff); + int p1n = p1 + diff; + pelsP[p1Idx] = (byte) clip(p1n, -128, 127); + } + + if (conditionQ) { + int q2 = pelsQ[q2Idx]; + int diff = (q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1; + diff = diff < -tC0 ? -tC0 : (diff > tC0 ? tC0 : diff); + int q1n = q1 + diff; + pelsQ[q1Idx] = (byte) clip(q1n, -128, 127); + } + + pelsQ[q0Idx] = (byte) clip(q0n, -128, 127); + pelsP[p0Idx] = (byte) clip(p0n, -128, 127); + + } + + public static void filterBs4(int indexAlpha, int indexBeta, byte[] pelsP, byte[] pelsQ, int p3Idx, int p2Idx, + int p1Idx, int p0Idx, int q0Idx, int q1Idx, int q2Idx, int q3Idx, boolean isChroma) { + int p0 = pelsP[p0Idx]; + int q0 = pelsQ[q0Idx]; + int p1 = pelsP[p1Idx]; + int q1 = pelsQ[q1Idx]; + + int alphaThresh = alphaTab[indexAlpha]; + int betaThresh = betaTab[indexBeta]; + + boolean filterEnabled = abs(p0 - q0) < alphaThresh && abs(p1 - p0) < betaThresh && abs(q1 - q0) < betaThresh; + + if (!filterEnabled) + return; + + boolean conditionP, conditionQ; + + if (isChroma) { + conditionP = false; + conditionQ = false; + } else { + int ap = abs(pelsP[p2Idx] - p0); + int aq = abs(pelsQ[q2Idx] - q0); + + conditionP = ap < betaThresh && abs(p0 - q0) < ((alphaThresh >> 2) + 2); + conditionQ = aq < betaThresh && abs(p0 - q0) < ((alphaThresh >> 2) + 2); + + } + + if (conditionP) { + int p3 = pelsP[p3Idx]; + int p2 = pelsP[p2Idx]; + + int p0n = (p2 + 2 * p1 + 2 * p0 + 2 * q0 + q1 + 4) >> 3; + int p1n = (p2 + p1 + p0 + q0 + 2) >> 2; + int p2n = (2 * p3 + 3 * p2 + p1 + p0 + q0 + 4) >> 3; + pelsP[p0Idx] = (byte) clip(p0n, -128, 127); + pelsP[p1Idx] = (byte) clip(p1n, -128, 127); + pelsP[p2Idx] = (byte) clip(p2n, -128, 127); + } else { + int p0n = (2 * p1 + p0 + q1 + 2) >> 2; + pelsP[p0Idx] = (byte) clip(p0n, -128, 127); + } + + if (conditionQ && !isChroma) { + int q2 = pelsQ[q2Idx]; + int q3 = pelsQ[q3Idx]; + int q0n = (p1 + 2 * p0 + 2 * q0 + 2 * q1 + q2 + 4) >> 3; + int q1n = (p0 + q0 + q1 + q2 + 2) >> 2; + int q2n = (2 * q3 + 3 * q2 + q1 + q0 + p0 + 4) >> 3; + pelsQ[q0Idx] = (byte) clip(q0n, -128, 127); + pelsQ[q1Idx] = (byte) clip(q1n, -128, 127); + pelsQ[q2Idx] = (byte) clip(q2n, -128, 127); + } else { + int q0n = (2 * q1 + q0 + p1 + 2) >> 2; + pelsQ[q0Idx] = (byte) clip(q0n, -128, 127); + } + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/CAVLCRate.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/CAVLCRate.java new file mode 100644 index 0000000..d650da2 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/CAVLCRate.java @@ -0,0 +1,45 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; +import org.monte.media.impl.jcodec.common.io.VLC; + +/** + * Returns the number of bits it would take to write certain types of CAVLC + * symbols + * + * @author Stanislav Vitvitskyy + */ +public class CAVLCRate { + + public static int rateTE(int refIdx, int i) { + // TODO Auto-generated method stub + return 0; + } + + public static int rateSE(int i) { + // TODO Auto-generated method stub + return 0; + } + + public static int rateUE(int i) { + // TODO Auto-generated method stub + return 0; + } + + public int rateACBlock(int i, int j, MBType p16x16, MBType p16x162, int[] ks, VLC[] totalzeros16, int k, int l, + int[] zigzag4x4) { + // TODO Auto-generated method stub + return 0; + } + + public int rateChrDCBlock(int[] dc, VLC[] totalzeros4, int i, int length, int[] js) { + // TODO Auto-generated method stub + return 0; + } + + public int rateLumaDCBlock(int mbLeftBlk, int mbTopBlk, MBType leftMBType, MBType topMBType, + int[] dc, VLC[] totalzeros16, int i, int j, int[] zigzag4x4) { + // TODO Auto-generated method stub + return 0; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/CQPRateControl.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/CQPRateControl.java new file mode 100644 index 0000000..3a55dc3 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/CQPRateControl.java @@ -0,0 +1,92 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.common.model.Size; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +/** + * Constant QP with psyvisual adjustments + * + * @author Stanislav Vitvitskiy + */ +public class CQPRateControl implements RateControl { + + private static final int MINQP = 12; + private int qp; + private int initialQp; + private int oldQp; + private SliceType sliceType; + + public CQPRateControl(int qp) { + this.initialQp = qp; + } + + @Override + public int startPicture(Size sz, int maxSize, SliceType sliceType) { + this.qp = initialQp; + this.oldQp = initialQp; + this.sliceType = sliceType; + return qp; + } + + @Override + public int accept(int bits) { + return 0; + } + + @Override + public int initialQpDelta(Picture pic, int mbX, int mbY) { + if (initialQp <= MINQP) { + return 0; + } + byte[] patch = new byte[256]; + MBEncoderHelper.take(pic.getPlaneData(0), pic.getPlaneWidth(0), pic.getPlaneHeight(0), mbX << 4, mbY << 4, + patch, 16, 16); + int avg = calcAvg(patch); + double var = calcVar(patch, avg); + double bright = calcBright(avg); + int newQp = initialQp; + int range = (initialQp - MINQP) / 2; + // Brightness + double delta = var * 0.1 * Math.max(0, bright - 2); + var += delta; + + // Variance + if (var < 4) { + newQp = sliceType == SliceType.I ? Math.max(initialQp / 2, 12) : Math.max(2 * initialQp / 3, 18); + } else if (var < 8) { + newQp = initialQp - range / 2; + } else if (var < 16) { + newQp = initialQp - range / 4; + } else if (var < 32) { + newQp = initialQp - range / 8; + } else if (var < 64) { + newQp = initialQp - range / 16; + } + int qpDelta = newQp - oldQp; + oldQp = newQp; + return qpDelta; + } + + private int calcAvg(byte[] patch) { + int sum = 0; + for (int i = 0; i < 256; i++) + sum += patch[i]; + return sum >> 8; + } + + private double calcVar(byte[] patch, int avg) { + long sum1 = 0; + for (int i = 0; i < 256; i++) { + int diff = patch[i] - avg; + sum1 += diff * diff; + } + + return Math.sqrt(sum1 >> 8); + } + + private double calcBright(int avg) { + return MathUtil.log2(avg + 128); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/DumbRateControl.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/DumbRateControl.java new file mode 100644 index 0000000..7e9c308 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/DumbRateControl.java @@ -0,0 +1,53 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.common.model.Size; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Dumb rate control policy, always maintains the same QP for the whole video + * + * @author The JCodec project + */ +public class DumbRateControl implements RateControl { + private static final int QP = 20; + private int bitsPerMb; + private int totalQpDelta; + private boolean justSwitched; + + @Override + public int accept(int bits) { + if (bits >= bitsPerMb) { + totalQpDelta++; + justSwitched = true; + return 1; + } else { + // Only decrease qp if we got too few bits (more then 12.5%) + if (totalQpDelta > 0 && !justSwitched && (bitsPerMb - bits > (bitsPerMb >> 3))) { + --totalQpDelta; + justSwitched = true; + return -1; + } else { + justSwitched = false; + } + return 0; + } + } + + @Override + public int startPicture(Size sz, int maxSize, SliceType sliceType) { + int totalMb = ((sz.getWidth() + 15) >> 4) * ((sz.getHeight() + 15) >> 4); + bitsPerMb = (maxSize << 3) / totalMb; + totalQpDelta = 0; + justSwitched = false; + return QP + (sliceType == SliceType.P ? 6 : 0); + } + + @Override + public int initialQpDelta(Picture pic, int mbX, int mbY) { + return 0; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/EncodedMB.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/EncodedMB.java new file mode 100644 index 0000000..2631d48 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/EncodedMB.java @@ -0,0 +1,72 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; +import org.monte.media.impl.jcodec.common.model.ColorSpace; +import org.monte.media.impl.jcodec.common.model.Picture; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class EncodedMB { + public Picture pixels; + public MBType type; + public int qp; + public int[] nc; + public int[] mx; + public int[] my; + public int[] mr; + public int mbX; + public int mbY; + + public EncodedMB() { + pixels = Picture.create(16, 16, ColorSpace.YUV420J); + nc = new int[16]; + mx = new int[16]; + my = new int[16]; + mr = new int[16]; + } + + public Picture getPixels() { + return pixels; + } + + public MBType getType() { + return type; + } + + public void setType(MBType type) { + this.type = type; + } + + public int getQp() { + return qp; + } + + public void setQp(int qp) { + this.qp = qp; + } + + public int[] getNc() { + return nc; + } + + public int[] getMx() { + return mx; + } + + public int[] getMy() { + return my; + } + + public void setPos(int mbX, int mbY) { + this.mbX = mbX; + this.mbY = mbY; + } + + public int[] getMr() { + return mr; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/EncodingContext.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/EncodingContext.java new file mode 100644 index 0000000..ecd51c4 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/EncodingContext.java @@ -0,0 +1,112 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import org.monte.media.impl.jcodec.codecs.h264.io.CAVLC; +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; + +import static java.lang.System.arraycopy; + +public class EncodingContext { + public CAVLC[] cavlc; + public byte[][] leftRow; + public byte[][] topLine; + public byte[] topLeft; + public int[] mvTopX; + public int[] mvTopY; + public int[] mvTopR; + public int[] mvLeftX; + public int[] mvLeftY; + public int[] mvLeftR; + public int mvTopLeftX; + public int mvTopLeftY; + public int mvTopLeftR; + public int mbHeight; + public int mbWidth; + public int prevQp; + + public int[] i4x4PredTop; + public int[] i4x4PredLeft; + public MBType leftMBType; + public MBType[] topMBType; + + public EncodingContext(int mbWidth, int mbHeight) { + this.mbWidth = mbWidth; + this.mbHeight = mbHeight; + leftRow = new byte[][]{new byte[16], new byte[8], new byte[8]}; + topLine = new byte[][]{new byte[mbWidth << 4], new byte[mbWidth << 3], new byte[mbWidth << 3]}; + topLeft = new byte[4]; + + mvTopX = new int[mbWidth << 2]; + mvTopY = new int[mbWidth << 2]; + mvTopR = new int[mbWidth << 2]; + mvLeftX = new int[4]; + mvLeftY = new int[4]; + mvLeftR = new int[4]; + i4x4PredTop = new int[mbWidth << 2]; + i4x4PredLeft = new int[4]; + topMBType = new MBType[mbWidth]; + } + + public void update(EncodedMB mb) { + if (mb.getType() != MBType.I_NxN) { + topLeft[0] = topLine[0][(mb.mbX << 4) + 15]; + arraycopy(mb.pixels.getPlaneData(0), 240, topLine[0], mb.mbX << 4, 16); + copyCol(mb.pixels.getPlaneData(0), 15, 16, leftRow[0]); + } + topLeft[1] = topLine[1][(mb.mbX << 3) + 7]; + topLeft[2] = topLine[2][(mb.mbX << 3) + 7]; + arraycopy(mb.pixels.getPlaneData(1), 56, topLine[1], mb.mbX << 3, 8); + arraycopy(mb.pixels.getPlaneData(2), 56, topLine[2], mb.mbX << 3, 8); + + copyCol(mb.pixels.getPlaneData(1), 7, 8, leftRow[1]); + copyCol(mb.pixels.getPlaneData(2), 7, 8, leftRow[2]); + + mvTopLeftX = mvTopX[mb.mbX << 2]; + mvTopLeftY = mvTopY[mb.mbX << 2]; + mvTopLeftR = mvTopR[mb.mbX << 2]; + for (int i = 0; i < 4; i++) { + mvTopX[(mb.mbX << 2) + i] = mb.mx[12 + i]; + mvTopY[(mb.mbX << 2) + i] = mb.my[12 + i]; + mvTopR[(mb.mbX << 2) + i] = mb.mr[12 + i]; + mvLeftX[i] = mb.mx[(i << 2)]; + mvLeftY[i] = mb.my[(i << 2)]; + mvLeftR[i] = mb.mr[(i << 2)]; + } + topMBType[mb.mbX] = leftMBType = mb.getType(); + } + + private void copyCol(byte[] planeData, int off, int stride, byte[] out) { + for (int i = 0; i < out.length; i++) { + out[i] = planeData[off]; + off += stride; + } + } + + public EncodingContext fork() { + EncodingContext ret = new EncodingContext(mbWidth, mbHeight); + ret.cavlc = new CAVLC[3]; + for (int i = 0; i < 3; i++) { + System.arraycopy(leftRow[i], 0, ret.leftRow[i], 0, leftRow[i].length); + System.arraycopy(topLine[i], 0, ret.topLine[i], 0, topLine[i].length); + ret.topLeft[i] = topLeft[i]; + ret.cavlc[i] = cavlc[i].fork(); + } + System.arraycopy(mvTopX, 0, ret.mvTopX, 0, ret.mvTopX.length); + System.arraycopy(mvTopY, 0, ret.mvTopY, 0, ret.mvTopY.length); + System.arraycopy(mvTopR, 0, ret.mvTopR, 0, ret.mvTopR.length); + System.arraycopy(mvLeftX, 0, ret.mvLeftX, 0, ret.mvLeftX.length); + System.arraycopy(mvLeftY, 0, ret.mvLeftY, 0, ret.mvLeftY.length); + System.arraycopy(mvLeftR, 0, ret.mvLeftR, 0, ret.mvLeftR.length); + ret.mvTopLeftX = mvTopLeftX; + ret.mvTopLeftY = mvTopLeftY; + ret.mvTopLeftR = mvTopLeftR; + ret.prevQp = prevQp; + + for (int i = 0; i < mbWidth; i++) + ret.topMBType[i] = topMBType[i]; + ret.leftMBType = leftMBType; + + System.arraycopy(i4x4PredTop, 0, ret.i4x4PredTop, 0, mbWidth << 2); + System.arraycopy(i4x4PredLeft, 0, ret.i4x4PredLeft, 0, 4); + return ret; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/H264EncoderUtils.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/H264EncoderUtils.java new file mode 100644 index 0000000..3efe178 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/H264EncoderUtils.java @@ -0,0 +1,53 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +/** + * Contains utility functions commonly used in H264 encoder + * + * @author Stanislav Vitvitskyy + */ +public class H264EncoderUtils { + public static int median(int a, boolean ar, int b, boolean br, int c, boolean cr, int d, boolean dr, boolean aAvb, + boolean bAvb, boolean cAvb, boolean dAvb) { + ar &= aAvb; + br &= bAvb; + cr &= cAvb; + + if (!cAvb) { + c = d; + cr = dr; + cAvb = dAvb; + } + + if (aAvb && !bAvb && !cAvb) { + b = c = a; + bAvb = cAvb = aAvb; + } + + a = aAvb ? a : 0; + b = bAvb ? b : 0; + c = cAvb ? c : 0; + + if (ar && !br && !cr) + return a; + else if (br && !ar && !cr) + return b; + else if (cr && !ar && !br) + return c; + + return a + b + c - min(min(a, b), c) - max(max(a, b), c); + } + + public static int mse(int[] orig, int[] enc, int w, int h) { + int sum = 0; + for (int i = 0, off = 0; i < h; i++) { + for (int j = 0; j < w; j++, off++) { + int diff = orig[off] - enc[off]; + sum += diff * diff; + } + } + return sum / (w * h); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/H264FixedRateControl.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/H264FixedRateControl.java new file mode 100644 index 0000000..4356490 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/H264FixedRateControl.java @@ -0,0 +1,62 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.common.model.Size; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * H.264 rate control policy that would produce frames of exactly equal size + * + * @author The JCodec project + */ +public class H264FixedRateControl implements RateControl { + private static final int INIT_QP = 26; + private int balance; + private int perMb; + private int curQp; + + public H264FixedRateControl(int bitsPer256) { + perMb = bitsPer256; + curQp = INIT_QP; + } + + @Override + public int startPicture(Size sz, int maxSize, SliceType sliceType) { + return INIT_QP + (sliceType == SliceType.P ? 4 : 0); + } + + @Override + public int accept(int bits) { + + balance += perMb - bits; + + return 0; + } + + public void reset() { + balance = 0; + curQp = INIT_QP; + } + + public int calcFrameSize(int nMB) { + return ((256 + nMB * (perMb + 9)) >> 3) + (nMB >> 6); + } + + public void setRate(int rate) { + perMb = rate; + } + + @Override + public int initialQpDelta(Picture pic, int mbX, int mbY) { + int qpDelta = balance < 0 ? (balance < -(perMb >> 1) ? 2 : 1) + : (balance > perMb ? (balance > (perMb << 2) ? -2 : -1) : 0); + int prevQp = curQp; + curQp = MathUtil.clip(curQp + qpDelta, 12, 30); + + return curQp - prevQp; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/H264RDO.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/H264RDO.java new file mode 100644 index 0000000..0936f94 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/H264RDO.java @@ -0,0 +1,5 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +public class H264RDO { + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/IntraPredEstimator.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/IntraPredEstimator.java new file mode 100644 index 0000000..7cda577 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/IntraPredEstimator.java @@ -0,0 +1,124 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import org.monte.media.impl.jcodec.codecs.h264.decode.ChromaPredictionBuilder; +import org.monte.media.impl.jcodec.codecs.h264.decode.CoeffTransformer; +import org.monte.media.impl.jcodec.codecs.h264.decode.Intra16x16PredictionBuilder; +import org.monte.media.impl.jcodec.codecs.h264.decode.Intra4x4PredictionBuilder; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import java.util.Arrays; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK_DISP_MAP; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class IntraPredEstimator { + public static int[] getLumaPred4x4(Picture pic, EncodingContext ctx, int mbX, int mbY, int qp) { + byte[] patch = new byte[256]; + MBEncoderHelper.take(pic.getPlaneData(0), pic.getPlaneWidth(0), pic.getPlaneHeight(0), mbX << 4, mbY << 4, + patch, 16, 16); + int[] predModes = new int[16]; + byte[] predLeft = Arrays.copyOf(ctx.leftRow[0], 16); + byte[] predTop = Arrays.copyOfRange(ctx.topLine[0], mbX << 4, + (mbX << 4) + 16 + (mbX < ctx.mbWidth - 1 ? 4 : 0)); + byte[] predTopLeft = new byte[]{ctx.topLeft[0], ctx.leftRow[0][3], ctx.leftRow[0][7], ctx.leftRow[0][11]}; + int[] resi = new int[16]; + byte[] pred = new byte[16]; + int[] bresi = new int[16]; + byte[] bpred = new byte[16]; + + for (int bInd = 0; bInd < 16; bInd++) { + int minSad = Integer.MAX_VALUE; + + int dInd = BLK_DISP_MAP[bInd]; + boolean hasLeft = (dInd & 0x3) != 0 || mbX != 0; + boolean hasTop = dInd >= 4 || mbY != 0; + boolean hasTr = ((bInd == 0 || bInd == 1 || bInd == 4) && mbY != 0) || (bInd == 5 && mbX < ctx.mbWidth - 1) + || bInd == 2 || bInd == 6 || bInd == 8 || bInd == 9 || bInd == 10 || bInd == 12 || bInd == 14; + predModes[bInd] = 2; + int blkX = (dInd & 0x3) << 2; + int blkY = (dInd >> 2) << 2; + + for (int predType = 0; predType < 9; predType++) { + boolean available = Intra4x4PredictionBuilder.lumaPred(predType, hasLeft, hasTop, hasTr, predLeft, + predTop, predTopLeft[dInd >> 2], blkX, blkY, pred); + + if (available) { + int sad = 0; + for (int i = 0; i < 16; i++) { + int x = blkX + (i & 0x3); + int y = blkY + (i >> 2); + resi[i] = patch[(y << 4) + x] - pred[i]; + sad += MathUtil.abs(resi[i]); + } + + if (sad < minSad) { + minSad = sad; + predModes[bInd] = predType; + + // Distort coeffs + CoeffTransformer.fdct4x4(resi); + CoeffTransformer.quantizeAC(resi, qp); + CoeffTransformer.dequantizeAC(resi, qp, null); + CoeffTransformer.idct4x4(resi); + System.arraycopy(pred, 0, bpred, 0, 16); + System.arraycopy(resi, 0, bresi, 0, 16); + } + } + } + predTopLeft[dInd >> 2] = predTop[blkX + 3]; + for (int p = 0; p < 4; p++) { + predLeft[blkY + p] = (byte) clip(bresi[3 + (p << 2)] + bpred[3 + (p << 2)], -128, 127); + predTop[blkX + p] = (byte) clip(bresi[12 + p] + bpred[12 + p], -128, 127); + } + } + return predModes; + } + + public static int getLumaMode(Picture pic, EncodingContext ctx, int mbX, int mbY) { + byte[] patch = new byte[256]; + MBEncoderHelper.take(pic.getPlaneData(0), pic.getPlaneWidth(0), pic.getPlaneHeight(0), mbX << 4, mbY << 4, + patch, 16, 16); + int minSad = Integer.MAX_VALUE; + int predMode = -1; + for (int predType = 0; predType < 4; predType++) { + int sad = Intra16x16PredictionBuilder.lumaPredSAD(predType, mbX != 0, mbY != 0, ctx.leftRow[0], + ctx.topLine[0], ctx.topLeft[0], mbX << 4, patch); + if (sad < minSad) { + minSad = sad; + predMode = predType; + } + } + return predMode; + } + + public static int getChromaMode(Picture pic, EncodingContext ctx, int mbX, int mbY) { + byte[] patch0 = new byte[64]; + byte[] patch1 = new byte[64]; + MBEncoderHelper.take(pic.getPlaneData(1), pic.getPlaneWidth(1), pic.getPlaneHeight(1), mbX << 3, mbY << 3, + patch0, 8, 8); + MBEncoderHelper.take(pic.getPlaneData(2), pic.getPlaneWidth(2), pic.getPlaneHeight(2), mbX << 3, mbY << 3, + patch1, 8, 8); + int minSad = Integer.MAX_VALUE; + int predMode = -1; + for (int predType = 0; predType < 4; predType++) { + if (!ChromaPredictionBuilder.predAvb(predType, mbX != 0, mbY != 0)) + continue; + int sad0 = ChromaPredictionBuilder.predSAD(predType, mbX, mbX != 0, mbY != 0, ctx.leftRow[1], + ctx.topLine[1], ctx.topLeft[1], patch0); + int sad1 = ChromaPredictionBuilder.predSAD(predType, mbX, mbX != 0, mbY != 0, ctx.leftRow[2], + ctx.topLine[2], ctx.topLeft[2], patch1); + if (sad0 + sad1 < minSad) { + minSad = sad0 + sad1; + predMode = predType; + } + } + return predMode; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBDeblocker.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBDeblocker.java new file mode 100644 index 0000000..274c195 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBDeblocker.java @@ -0,0 +1,418 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import org.monte.media.impl.jcodec.codecs.h264.decode.deblock.DeblockingFilter; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import static java.lang.Math.abs; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.QP_SCALE_CR; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Contains various deblocking filter routines for deblocking on MB bases + * + * @author Stan Vitvitskyy + */ +public class MBDeblocker { + + static int[][] LOOKUP_IDX_P_V = new int[][]{{3, 7, 11, 15}, {0, 4, 8, 12}, {1, 5, 9, 13}, + {2, 6, 10, 14}}; + static int[][] LOOKUP_IDX_Q_V = new int[][]{{0, 4, 8, 12}, {1, 5, 9, 13}, {2, 6, 10, 14}, + {3, 7, 11, 15}}; + static int[][] LOOKUP_IDX_P_H = new int[][]{{12, 13, 14, 15}, {0, 1, 2, 3}, {4, 5, 6, 7}, + {8, 9, 10, 11}}; + static int[][] LOOKUP_IDX_Q_H = new int[][]{{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}, + {12, 13, 14, 15}}; + + static int calcQpChroma(int qp, int crQpOffset) { + return QP_SCALE_CR[MathUtil.clip(qp + crQpOffset, 0, 51)]; + } + + /** + * Deblocks bottom edge of topOutMB, right edge of leftOutMB and left/top and + * inner block edges of outMB + *

+ * //@param curPix Pixels of the current MB + * //@param leftPix Pixels of the leftMB + * //@param topPix Pixels of the tipMB + * // + * //@param vertStrength Border strengths for vertical edges (filtered first) + * //@param horizStrength Border strengths for the horizontal edges + * //@param lastH + * //@param lastW + * // + * //@param curQp Current MB's qp + * //@param leftQp Left MB's qp + * //@param topQp Top MB's qp + */ + public void deblockMBGeneric(EncodedMB curMB, EncodedMB leftMB, EncodedMB topMB, int[][] vertStrength, + int horizStrength[][]) { + Picture curPix = curMB.getPixels(); + + int crQpOffset = 0; + int curChQp = calcQpChroma(curMB.getQp(), crQpOffset); + if (leftMB != null) { + Picture leftPix = leftMB.getPixels(); + + int leftChQp = calcQpChroma(leftMB.getQp(), crQpOffset); + int avgQp = MathUtil.clip((leftMB.getQp() + curMB.getQp() + 1) >> 1, 0, 51); + int avgChQp = MathUtil.clip((leftChQp + curChQp + 1) >> 1, 0, 51); + + deblockBorder(vertStrength[0], avgQp, leftPix.getPlaneData(0), 3, curPix.getPlaneData(0), 0, P_POS_V, + Q_POS_V, false); + deblockBorderChroma(vertStrength[0], avgChQp, leftPix.getPlaneData(1), 3, curPix.getPlaneData(1), 0, + P_POS_V_CHR, Q_POS_V_CHR, false); + deblockBorderChroma(vertStrength[0], avgChQp, leftPix.getPlaneData(2), 3, curPix.getPlaneData(2), 0, + P_POS_V_CHR, Q_POS_V_CHR, false); + } + for (int i = 1; i < 4; i++) { + deblockBorder(vertStrength[i], curMB.getQp(), curPix.getPlaneData(0), i - 1, curPix.getPlaneData(0), i, + P_POS_V, Q_POS_V, false); + } + deblockBorderChroma(vertStrength[2], curChQp, curPix.getPlaneData(1), 1, curPix.getPlaneData(1), 2, P_POS_V_CHR, + Q_POS_V_CHR, false); + deblockBorderChroma(vertStrength[2], curChQp, curPix.getPlaneData(2), 1, curPix.getPlaneData(2), 2, P_POS_V_CHR, + Q_POS_V_CHR, false); + + if (topMB != null) { + Picture topPix = topMB.getPixels(); + + int topChQp = calcQpChroma(topMB.getQp(), crQpOffset); + int avgQp = MathUtil.clip((topMB.getQp() + curMB.getQp() + 1) >> 1, 0, 51); + int avgChQp = MathUtil.clip((topChQp + curChQp + 1) >> 1, 0, 51); + + deblockBorder(horizStrength[0], avgQp, topPix.getPlaneData(0), 3, curPix.getPlaneData(0), 0, P_POS_H, + Q_POS_H, true); + deblockBorderChroma(horizStrength[0], avgChQp, topPix.getPlaneData(1), 3, curPix.getPlaneData(1), 0, + P_POS_H_CHR, Q_POS_H_CHR, true); + deblockBorderChroma(horizStrength[0], avgChQp, topPix.getPlaneData(2), 3, curPix.getPlaneData(2), 0, + P_POS_H_CHR, Q_POS_H_CHR, true); + } + for (int i = 1; i < 4; i++) { + deblockBorder(horizStrength[i], curMB.getQp(), curPix.getPlaneData(0), i - 1, curPix.getPlaneData(0), i, + P_POS_H, Q_POS_H, true); + } + deblockBorderChroma(horizStrength[2], curChQp, curPix.getPlaneData(1), 1, curPix.getPlaneData(1), 2, + P_POS_H_CHR, Q_POS_H_CHR, true); + deblockBorderChroma(horizStrength[2], curChQp, curPix.getPlaneData(2), 1, curPix.getPlaneData(2), 2, + P_POS_H_CHR, Q_POS_H_CHR, true); + } + + /** + * @param cur Pixels and parameters of encoded and reconstructed current + * macroblock + * @param left Pixels and parameters of encoded and reconstructed left + * macroblock + * @param top Pixels and parameters of encoded and reconstructed top macroblock + * //@param c + * //@param b + */ + public void deblockMBP(EncodedMB cur, EncodedMB left, EncodedMB top) { + int[][] vertStrength = new int[4][4]; + int[][] horizStrength = new int[4][4]; + + calcStrengthForBlocks(cur, left, vertStrength, LOOKUP_IDX_P_V, LOOKUP_IDX_Q_V); + calcStrengthForBlocks(cur, top, horizStrength, LOOKUP_IDX_P_H, LOOKUP_IDX_Q_H); + + deblockMBGeneric(cur, left, top, vertStrength, horizStrength); + } + + private void deblockBorder(int[] boundary, int qp, byte[] p, int pi, byte[] q, int qi, int[][] pTab, int[][] qTab, + boolean horiz) { + int inc1 = horiz ? 16 : 1, inc2 = inc1 * 2, inc3 = inc1 * 3; + for (int b = 0; b < 4; b++) { + if (boundary[b] == 4) { + for (int i = 0, ii = b << 2; i < 4; ++i, ++ii) + filterBs4(qp, qp, p, q, pTab[pi][ii] - inc3, pTab[pi][ii] - inc2, pTab[pi][ii] - inc1, pTab[pi][ii], + qTab[qi][ii], qTab[qi][ii] + inc1, qTab[qi][ii] + inc2, qTab[qi][ii] + inc3); + } else if (boundary[b] > 0) { + for (int i = 0, ii = b << 2; i < 4; ++i, ++ii) + filterBs(boundary[b], qp, qp, p, q, pTab[pi][ii] - inc2, pTab[pi][ii] - inc1, pTab[pi][ii], + qTab[qi][ii], qTab[qi][ii] + inc1, qTab[qi][ii] + inc2); + + } + } + } + + protected void filterBs4Chr(int indexAlpha, int indexBeta, byte[] pelsP, byte[] pelsQ, int p1Idx, int p0Idx, + int q0Idx, int q1Idx) { + _filterBs4(indexAlpha, indexBeta, pelsP, pelsQ, -1, -1, p1Idx, p0Idx, q0Idx, q1Idx, -1, -1, true); + } + + protected void filterBsChr(int bs, int indexAlpha, int indexBeta, byte[] pelsP, byte[] pelsQ, int p1Idx, int p0Idx, + int q0Idx, int q1Idx) { + _filterBs(bs, indexAlpha, indexBeta, pelsP, pelsQ, -1, p1Idx, p0Idx, q0Idx, q1Idx, -1, true); + } + + protected void filterBs4(int indexAlpha, int indexBeta, byte[] pelsP, byte[] pelsQ, int p3Idx, int p2Idx, int p1Idx, + int p0Idx, int q0Idx, int q1Idx, int q2Idx, int q3Idx) { + _filterBs4(indexAlpha, indexBeta, pelsP, pelsQ, p3Idx, p2Idx, p1Idx, p0Idx, q0Idx, q1Idx, q2Idx, q3Idx, false); + } + + protected void filterBs(int bs, int indexAlpha, int indexBeta, byte[] pelsP, byte[] pelsQ, int p2Idx, int p1Idx, + int p0Idx, int q0Idx, int q1Idx, int q2Idx) { + _filterBs(bs, indexAlpha, indexBeta, pelsP, pelsQ, p2Idx, p1Idx, p0Idx, q0Idx, q1Idx, q2Idx, false); + } + + protected void _filterBs4(int indexAlpha, int indexBeta, byte[] pelsP, byte[] pelsQ, int p3Idx, int p2Idx, + int p1Idx, int p0Idx, int q0Idx, int q1Idx, int q2Idx, int q3Idx, boolean isChroma) { + int p0 = pelsP[p0Idx]; + int q0 = pelsQ[q0Idx]; + int p1 = pelsP[p1Idx]; + int q1 = pelsQ[q1Idx]; + + int alphaThresh = DeblockingFilter.alphaTab[indexAlpha]; + int betaThresh = DeblockingFilter.betaTab[indexBeta]; + + boolean filterEnabled = abs(p0 - q0) < alphaThresh && abs(p1 - p0) < betaThresh && abs(q1 - q0) < betaThresh; + + if (!filterEnabled) + return; + + boolean conditionP, conditionQ; + + if (isChroma) { + conditionP = false; + conditionQ = false; + } else { + int ap = abs(pelsP[p2Idx] - p0); + int aq = abs(pelsQ[q2Idx] - q0); + + conditionP = ap < betaThresh && abs(p0 - q0) < ((alphaThresh >> 2) + 2); + conditionQ = aq < betaThresh && abs(p0 - q0) < ((alphaThresh >> 2) + 2); + + } + + if (conditionP) { + int p3 = pelsP[p3Idx]; + int p2 = pelsP[p2Idx]; + + int p0n = (p2 + 2 * p1 + 2 * p0 + 2 * q0 + q1 + 4) >> 3; + int p1n = (p2 + p1 + p0 + q0 + 2) >> 2; + int p2n = (2 * p3 + 3 * p2 + p1 + p0 + q0 + 4) >> 3; + pelsP[p0Idx] = (byte) clip(p0n, -128, 127); + pelsP[p1Idx] = (byte) clip(p1n, -128, 127); + pelsP[p2Idx] = (byte) clip(p2n, -128, 127); + } else { + int p0n = (2 * p1 + p0 + q1 + 2) >> 2; + pelsP[p0Idx] = (byte) clip(p0n, -128, 127); + } + + if (conditionQ && !isChroma) { + int q2 = pelsQ[q2Idx]; + int q3 = pelsQ[q3Idx]; + int q0n = (p1 + 2 * p0 + 2 * q0 + 2 * q1 + q2 + 4) >> 3; + int q1n = (p0 + q0 + q1 + q2 + 2) >> 2; + int q2n = (2 * q3 + 3 * q2 + q1 + q0 + p0 + 4) >> 3; + pelsQ[q0Idx] = (byte) clip(q0n, -128, 127); + pelsQ[q1Idx] = (byte) clip(q1n, -128, 127); + pelsQ[q2Idx] = (byte) clip(q2n, -128, 127); + } else { + int q0n = (2 * q1 + q0 + p1 + 2) >> 2; + pelsQ[q0Idx] = (byte) clip(q0n, -128, 127); + } + } + + protected void _filterBs(int bs, int indexAlpha, int indexBeta, byte[] pelsP, byte[] pelsQ, int p2Idx, int p1Idx, + int p0Idx, int q0Idx, int q1Idx, int q2Idx, boolean isChroma) { + int p1 = pelsP[p1Idx]; + int p0 = pelsP[p0Idx]; + int q0 = pelsQ[q0Idx]; + int q1 = pelsQ[q1Idx]; + + int alphaThresh = DeblockingFilter.alphaTab[indexAlpha]; + int betaThresh = DeblockingFilter.betaTab[indexBeta]; + + boolean filterEnabled = abs(p0 - q0) < alphaThresh && abs(p1 - p0) < betaThresh && abs(q1 - q0) < betaThresh; + + if (!filterEnabled) + return; + + int tC0 = DeblockingFilter.tcs[bs - 1][indexAlpha]; + + boolean conditionP, conditionQ; + int tC; + if (!isChroma) { + int ap = abs(pelsP[p2Idx] - p0); + int aq = abs(pelsQ[q2Idx] - q0); + tC = tC0 + ((ap < betaThresh) ? 1 : 0) + ((aq < betaThresh) ? 1 : 0); + conditionP = ap < betaThresh; + conditionQ = aq < betaThresh; + } else { + tC = tC0 + 1; + conditionP = false; + conditionQ = false; + } + + int sigma = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3); + sigma = sigma < -tC ? -tC : (sigma > tC ? tC : sigma); + + int p0n = p0 + sigma; + p0n = p0n < -128 ? -128 : p0n; + int q0n = q0 - sigma; + q0n = q0n < -128 ? -128 : q0n; + + if (conditionP) { + int p2 = pelsP[p2Idx]; + + int diff = (p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1; + diff = diff < -tC0 ? -tC0 : (diff > tC0 ? tC0 : diff); + int p1n = p1 + diff; + pelsP[p1Idx] = (byte) clip(p1n, -128, 127); + } + + if (conditionQ) { + int q2 = pelsQ[q2Idx]; + int diff = (q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1; + diff = diff < -tC0 ? -tC0 : (diff > tC0 ? tC0 : diff); + int q1n = q1 + diff; + pelsQ[q1Idx] = (byte) clip(q1n, -128, 127); + } + + pelsQ[q0Idx] = (byte) clip(q0n, -128, 127); + pelsP[p0Idx] = (byte) clip(p0n, -128, 127); + } + + private void deblockBorderChroma(int[] boundary, int qp, byte[] p, int pi, byte[] q, int qi, int[][] pTab, + int[][] qTab, boolean horiz) { + int inc1 = horiz ? 8 : 1; + for (int b = 0; b < 4; b++) { + if (boundary[b] == 4) { + for (int i = 0, ii = b << 1; i < 2; ++i, ++ii) + filterBs4Chr(qp, qp, p, q, pTab[pi][ii] - inc1, pTab[pi][ii], qTab[qi][ii], qTab[qi][ii] + inc1); + } else if (boundary[b] > 0) { + for (int i = 0, ii = b << 1; i < 2; ++i, ++ii) + filterBsChr(boundary[b], qp, qp, p, q, pTab[pi][ii] - inc1, pTab[pi][ii], qTab[qi][ii], + qTab[qi][ii] + inc1); + } + } + } + + private static int[][] buildPPosH() { + int[][] qPos = new int[4][16]; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 16; j++) { + qPos[i][j] = j + (i << 6) + 48; + } + } + return qPos; + } + + private static int[][] buildQPosH() { + int[][] pPos = new int[4][16]; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 16; j++) { + pPos[i][j] = j + (i << 6); + } + } + return pPos; + } + + private static int[][] buildPPosV() { + int[][] qPos = new int[4][16]; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 16; j++) { + qPos[i][j] = (j << 4) + (i << 2) + 3; + } + } + return qPos; + } + + private static int[][] buildQPosV() { + int[][] pPos = new int[4][16]; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 16; j++) { + pPos[i][j] = (j << 4) + (i << 2); + } + } + return pPos; + } + + private static int[][] buildPPosHChr() { + int[][] qPos = new int[4][8]; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 8; j++) { + qPos[i][j] = j + (i << 4) + 8; + } + } + return qPos; + } + + private static int[][] buildQPosHChr() { + int[][] pPos = new int[4][8]; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 8; j++) { + pPos[i][j] = j + (i << 4); + } + } + return pPos; + } + + private static int[][] buildPPosVChr() { + int[][] qPos = new int[4][8]; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 8; j++) { + qPos[i][j] = (j << 3) + (i << 1) + 1; + } + } + return qPos; + } + + private static int[][] buildQPosVChr() { + int[][] pPos = new int[4][8]; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 8; j++) { + pPos[i][j] = (j << 3) + (i << 1); + } + } + return pPos; + } + + static void calcStrengthForBlocks(EncodedMB cur, EncodedMB other, int[][] outStrength, int[][] LOOKUP_IDX_P, + int[][] LOOKUP_IDX_Q) { + boolean thisIntra = cur.getType().isIntra(); + if (other != null) { + boolean otherIntra = other.getType().isIntra(); + for (int i = 0; i < 4; ++i) { + int bsMvx = strengthMv(other.getMx()[LOOKUP_IDX_P[0][i]], cur.getMx()[LOOKUP_IDX_Q[0][i]]); + int bsMvy = strengthMv(other.getMy()[LOOKUP_IDX_P[0][i]], cur.getMy()[LOOKUP_IDX_Q[0][i]]); + int bsNc = strengthNc(other.getNc()[LOOKUP_IDX_P[0][i]], cur.getNc()[LOOKUP_IDX_Q[0][i]]); + int max3 = MathUtil.max3(bsMvx, bsMvy, bsNc); + outStrength[0][i] = (otherIntra || thisIntra) ? 4 : max3; + } + } + + for (int i = 1; i < 4; i++) { + for (int j = 0; j < 4; ++j) { + int bsMvx = strengthMv(cur.getMx()[LOOKUP_IDX_P[i][j]], cur.getMx()[LOOKUP_IDX_Q[i][j]]); + int bsMvy = strengthMv(cur.getMy()[LOOKUP_IDX_P[i][j]], cur.getMy()[LOOKUP_IDX_Q[i][j]]); + int bsNc = strengthNc(cur.getNc()[LOOKUP_IDX_P[i][j]], cur.getNc()[LOOKUP_IDX_Q[i][j]]); + int max3 = MathUtil.max3(bsMvx, bsMvy, bsNc); + outStrength[i][j] = thisIntra ? 3 : max3; + } + } + } + + private static int strengthNc(int ncA, int ncB) { + return ncA > 0 || ncB > 0 ? 2 : 0; + } + + private static int strengthMv(int v0, int v1) { + return abs(v0 - v1) >= 4 ? 1 : 0; + } + + private static int[][] P_POS_V = buildPPosV(); + private static int[][] Q_POS_V = buildQPosV(); + private static int[][] P_POS_H = buildPPosH(); + private static int[][] Q_POS_H = buildQPosH(); + + private static int[][] P_POS_V_CHR = buildPPosVChr(); + private static int[][] Q_POS_V_CHR = buildQPosVChr(); + + private static int[][] P_POS_H_CHR = buildPPosHChr(); + private static int[][] Q_POS_H_CHR = buildQPosHChr(); + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBEncoderHelper.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBEncoderHelper.java new file mode 100644 index 0000000..d52eb99 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBEncoderHelper.java @@ -0,0 +1,156 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import org.monte.media.impl.jcodec.common.model.Picture; + +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class MBEncoderHelper { + + public static final void takeSubtract(byte[] planeData, int planeWidth, int planeHeight, int x, int y, int[] coeff, + byte[] pred, int blkW, int blkH) { + if (x + blkW < planeWidth && y + blkH < planeHeight) + takeSubtractSafe(planeData, planeWidth, planeHeight, x, y, coeff, pred, blkW, blkH); + else + takeSubtractUnsafe(planeData, planeWidth, planeHeight, x, y, coeff, pred, blkW, blkH); + + } + + public static final void takeSubtractSafe(byte[] planeData, int planeWidth, int planeHeight, int x, int y, + int[] coeff, byte[] pred, int blkW, int blkH) { + for (int i = 0, srcOff = y * planeWidth + x, dstOff = 0; i < blkH; i++, srcOff += planeWidth) { + for (int j = 0, srcOff1 = srcOff; j < blkW; j += 4, dstOff += 4, srcOff1 += 4) { + coeff[dstOff] = planeData[srcOff1] - pred[dstOff]; + coeff[dstOff + 1] = planeData[srcOff1 + 1] - pred[dstOff + 1]; + coeff[dstOff + 2] = planeData[srcOff1 + 2] - pred[dstOff + 2]; + coeff[dstOff + 3] = planeData[srcOff1 + 3] - pred[dstOff + 3]; + } + } + } + + public static final void take(byte[] planeData, int planeWidth, int planeHeight, int x, int y, byte[] patch, + int blkW, int blkH) { + if (x + blkW < planeWidth && y + blkH < planeHeight) + takeSafe(planeData, planeWidth, planeHeight, x, y, patch, blkW, blkH); + else + takeExtendBorder(planeData, planeWidth, planeHeight, x, y, patch, blkW, blkH); + + } + + public static final void takeSafe(byte[] planeData, int planeWidth, int planeHeight, int x, int y, byte[] patch, + int blkW, int blkH) { + for (int i = 0, srcOff = y * planeWidth + x, dstOff = 0; i < blkH; i++, srcOff += planeWidth) { + for (int j = 0, srcOff1 = srcOff; j < blkW; ++j, ++dstOff, ++srcOff1) { + patch[dstOff] = planeData[srcOff1]; + } + } + } + + public static final void takeExtendBorder(byte[] planeData, int planeWidth, int planeHeight, int x, int y, byte[] patch, + int blkW, int blkH) { + int outOff = 0; + + int i; + for (i = y; i < Math.min(y + blkH, planeHeight); i++) { + int off = i * planeWidth + Math.min(x, planeWidth); + int j; + for (j = x; j < Math.min(x + blkW, planeWidth); j++, outOff++, off++) { + patch[outOff] = planeData[off]; + } + --off; + for (; j < x + blkW; j++, outOff++) { + patch[outOff] = planeData[off]; + } + } + for (; i < y + blkH; i++) { + int off = planeHeight * planeWidth - planeWidth + Math.min(x, planeWidth); + int j; + for (j = x; j < Math.min(x + blkW, planeWidth); j++, outOff++, off++) { + patch[outOff] = planeData[off]; + } + --off; + for (; j < x + blkW; j++, outOff++) { + patch[outOff] = planeData[off]; + } + } + } + + public static final void takeSafe2(byte[] planeData, int planeWidth, int planeHeight, int x, int y, int[] coeff, + int blkW, int blkH) { + for (int i = 0, srcOff = y * planeWidth + x, dstOff = 0; i < blkH; i++, srcOff += planeWidth) { + for (int j = 0, srcOff1 = srcOff; j < blkW; ++j, ++dstOff, ++srcOff1) { + coeff[dstOff] = planeData[srcOff1]; + } + } + } + + public static final void takeSubtractUnsafe(byte[] planeData, int planeWidth, int planeHeight, int x, int y, + int[] coeff, byte[] pred, int blkW, int blkH) { + int outOff = 0; + + int i; + for (i = y; i < Math.min(y + blkH, planeHeight); i++) { + int off = i * planeWidth + Math.min(x, planeWidth); + int j; + for (j = x; j < Math.min(x + blkW, planeWidth); j++, outOff++, off++) { + coeff[outOff] = planeData[off] - pred[outOff]; + } + --off; + for (; j < x + blkW; j++, outOff++) { + coeff[outOff] = planeData[off] - pred[outOff]; + } + } + for (; i < y + blkH; i++) { + int off = planeHeight * planeWidth - planeWidth + Math.min(x, planeWidth); + int j; + for (j = x; j < Math.min(x + blkW, planeWidth); j++, outOff++, off++) { + coeff[outOff] = planeData[off] - pred[outOff]; + } + --off; + for (; j < x + blkW; j++, outOff++) { + coeff[outOff] = planeData[off] - pred[outOff]; + } + } + } + + public static final void putBlk(byte[] planeData, int[] block, byte[] pred, int log2stride, int blkX, int blkY, + int blkW, int blkH) { + int stride = 1 << log2stride; + for (int line = 0, srcOff = 0, dstOff = (blkY << log2stride) + blkX; line < blkH; line++) { + int dstOff1 = dstOff; + for (int row = 0; row < blkW; row += 4) { + planeData[dstOff1] = (byte) clip(block[srcOff] + pred[srcOff], -128, 127); + planeData[dstOff1 + 1] = (byte) clip(block[srcOff + 1] + pred[srcOff + 1], -128, 127); + planeData[dstOff1 + 2] = (byte) clip(block[srcOff + 2] + pred[srcOff + 2], -128, 127); + planeData[dstOff1 + 3] = (byte) clip(block[srcOff + 3] + pred[srcOff + 3], -128, 127); + srcOff += 4; + dstOff1 += 4; + } + dstOff += stride; + } + } + + public static final void putBlkPic(Picture dest, Picture src, int x, int y) { + if (dest.getColor() != src.getColor()) + throw new RuntimeException("Incompatible color"); + for (int c = 0; c < dest.getColor().nComp; c++) { + pubBlkOnePlane(dest.getPlaneData(c), dest.getPlaneWidth(c), src.getPlaneData(c), src.getPlaneWidth(c), + src.getPlaneHeight(c), x >> dest.getColor().compWidth[c], y >> dest.getColor().compHeight[c]); + } + } + + private static void pubBlkOnePlane(byte[] dest, int destWidth, byte[] src, int srcWidth, int srcHeight, int x, int y) { + int destOff = y * destWidth + x; + int srcOff = 0; + for (int i = 0; i < srcHeight; i++) { + for (int j = 0; j < srcWidth; j++, ++destOff, ++srcOff) + dest[destOff] = src[srcOff]; + destOff += destWidth - srcWidth; + } + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBWriterI16x16.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBWriterI16x16.java new file mode 100644 index 0000000..1ba3566 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBWriterI16x16.java @@ -0,0 +1,259 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const; +import org.monte.media.impl.jcodec.codecs.h264.H264Encoder.NonRdVector; +import org.monte.media.impl.jcodec.codecs.h264.decode.ChromaPredictionBuilder; +import org.monte.media.impl.jcodec.codecs.h264.decode.CoeffTransformer; +import org.monte.media.impl.jcodec.codecs.h264.decode.Intra16x16PredictionBuilder; +import org.monte.media.impl.jcodec.codecs.h264.io.CAVLC; +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; +import org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter; +import org.monte.media.impl.jcodec.common.io.BitWriter; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK_DISP_MAP; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK_X; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK_Y; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.MB_DISP_OFF_LEFT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.MB_DISP_OFF_TOP; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.QP_SCALE_CR; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CoeffTransformer.reorderDC4x4; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.I_16x16; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Encodes macroblock as I16x16 + * + * @author Stanislav Vitvitskyy + */ +public class MBWriterI16x16 { + public boolean encodeMacroblock(EncodingContext ctx, Picture pic, int mbX, int mbY, BitWriter out, EncodedMB outMB, + int qp, NonRdVector params) { + CAVLCWriter.writeUE(out, params.chrPred); + CAVLCWriter.writeSE(out, qp - ctx.prevQp); // MB QP delta + + outMB.setType(MBType.I_16x16); + outMB.setQp(qp); + + boolean cbp = false; + int[] nc = new int[16]; + luma(ctx, pic, mbX, mbY, out, qp, outMB.getPixels(), params.lumaPred16x16, nc); + for (int dInd = 0; dInd < 16; dInd++) { + cbp |= nc[dInd] != 0; + } + chroma(ctx, pic, mbX, mbY, I_16x16, out, qp, outMB.getPixels(), params.chrPred); + ctx.prevQp = qp; + return cbp; + } + + private static int DUMMY[] = new int[16]; + + static int calcQpChroma(int qp, int crQpOffset) { + return QP_SCALE_CR[MathUtil.clip(qp + crQpOffset, 0, 51)]; + } + + public static void chroma(EncodingContext ctx, Picture pic, int mbX, int mbY, MBType curMBType, BitWriter out, + int qp, Picture outMB, int chrPred) { + int x = mbX << 3; + int y = mbY << 3; + int[][] ac1 = new int[4][16]; + int[][] ac2 = new int[4][16]; + byte[][] pred1 = new byte[4][16]; + byte[][] pred2 = new byte[4][16]; + + predictChroma(ctx, pic, ac1, pred1, 1, x, y, chrPred); + predictChroma(ctx, pic, ac2, pred2, 2, x, y, chrPred); + + chromaResidual(mbX, mbY, out, qp, ac1, ac2, ctx.cavlc[1], ctx.cavlc[2], ctx.leftMBType, ctx.topMBType[mbX], + curMBType); + + putChroma(outMB.getData()[1], 1, x, y, ac1, pred1); + putChroma(outMB.getData()[2], 2, x, y, ac2, pred2); + } + + public static void chromaResidual(int mbX, int mbY, BitWriter out, int qp, int[][] ac1, int[][] ac2, CAVLC cavlc1, + CAVLC cavlc2, MBType leftMBType, MBType topMBType, MBType curMBType) { + int crQpOffset = 0; + int chrQp = calcQpChroma(qp, crQpOffset); + + transformChroma(ac1); + transformChroma(ac2); + + int[] dc1 = extractDC(ac1); + int[] dc2 = extractDC(ac2); + + writeDC(cavlc1, mbX, mbY, out, chrQp, mbX << 1, mbY << 1, dc1, leftMBType, topMBType); + writeDC(cavlc2, mbX, mbY, out, chrQp, mbX << 1, mbY << 1, dc2, leftMBType, topMBType); + + writeAC(cavlc1, mbX, mbY, out, mbX << 1, mbY << 1, ac1, chrQp, leftMBType, topMBType, curMBType, DUMMY); + writeAC(cavlc2, mbX, mbY, out, mbX << 1, mbY << 1, ac2, chrQp, leftMBType, topMBType, curMBType, DUMMY); + + restorePlane(dc1, ac1, chrQp); + restorePlane(dc2, ac2, chrQp); + } + + private void luma(EncodingContext ctx, Picture pic, int mbX, int mbY, BitWriter out, int qp, Picture outMB, + int predType, int[] nc) { + int x = mbX << 4; + int y = mbY << 4; + int[][] ac = new int[16][16]; + byte[][] pred = new byte[16][16]; + + Intra16x16PredictionBuilder.lumaPred(predType, x != 0, y != 0, ctx.leftRow[0], ctx.topLine[0], ctx.topLeft[0], + x, pred); + + transform(pic, 0, ac, pred, x, y); + int[] dc = extractDC(ac); + writeDC(ctx.cavlc[0], mbX, mbY, out, qp, mbX << 2, mbY << 2, dc, ctx.leftMBType, ctx.topMBType[mbX]); + writeACLum(ctx.cavlc[0], mbX, mbY, out, mbX << 2, mbY << 2, ac, qp, ctx.leftMBType, ctx.topMBType[mbX], I_16x16, + nc); + + restorePlane(dc, ac, qp); + + for (int blk = 0; blk < ac.length; blk++) { + MBEncoderHelper.putBlk(outMB.getPlaneData(0), ac[blk], pred[blk], 4, BLK_X[blk], BLK_Y[blk], 4, 4); + } + } + + private static void putChroma(byte[] mb, int comp, int x, int y, int[][] ac, byte[][] pred) { + MBEncoderHelper.putBlk(mb, ac[0], pred[0], 3, 0, 0, 4, 4); + + MBEncoderHelper.putBlk(mb, ac[1], pred[1], 3, 4, 0, 4, 4); + + MBEncoderHelper.putBlk(mb, ac[2], pred[2], 3, 0, 4, 4, 4); + + MBEncoderHelper.putBlk(mb, ac[3], pred[3], 3, 4, 4, 4, 4); + } + + private static void restorePlane(int[] dc, int[][] ac, int qp) { + if (dc.length == 4) { + CoeffTransformer.invDC2x2(dc); + CoeffTransformer.dequantizeDC2x2(dc, qp, null); + } else if (dc.length == 8) { + CoeffTransformer.invDC4x2(dc); + CoeffTransformer.dequantizeDC4x2(dc, qp); + } else { + CoeffTransformer.invDC4x4(dc); + CoeffTransformer.dequantizeDC4x4(dc, qp, null); + reorderDC4x4(dc); + } + for (int i = 0; i < ac.length; i++) { + CoeffTransformer.dequantizeAC(ac[i], qp, null); + ac[i][0] = dc[i]; + CoeffTransformer.idct4x4(ac[i]); + } + } + + private static int[] extractDC(int[][] ac) { + int[] dc = new int[ac.length]; + for (int i = 0; i < ac.length; i++) { + dc[i] = ac[i][0]; + ac[i][0] = 0; + } + return dc; + } + + private static void writeAC(CAVLC cavlc, int mbX, int mbY, BitWriter out, int mbLeftBlk, int mbTopBlk, int[][] ac, + int qp, MBType leftMBType, MBType topMBType, MBType curMBType, int[] nc) { + for (int bInd = 0; bInd < ac.length; bInd++) { + CoeffTransformer.quantizeAC(ac[bInd], qp); + int blkOffLeft = MB_DISP_OFF_LEFT[bInd]; + int blkOffTop = MB_DISP_OFF_TOP[bInd]; + nc[BLK_DISP_MAP[bInd]] = CAVLC + .totalCoeff(cavlc.writeACBlock(out, mbLeftBlk + blkOffLeft, mbTopBlk + blkOffTop, + blkOffLeft == 0 ? leftMBType : curMBType, blkOffTop == 0 ? topMBType : curMBType, ac[bInd], + H264Const.totalZeros16, 1, 15, CoeffTransformer.zigzag4x4)); + } + } + + private static void writeACLum(CAVLC cavlc, int mbX, int mbY, BitWriter out, int mbLeftBlk, int mbTopBlk, + int[][] ac, int qp, MBType leftMBType, MBType topMBType, MBType curMBType, int[] nc) { + boolean code = false; + for (int bInd = 0; bInd < 16; bInd++) { + CoeffTransformer.quantizeAC(ac[bInd], qp); + code |= hasNz(ac[bInd]); + } + if (code) { + for (int bInd = 0; bInd < 16; bInd++) { + int blkOffLeft = MB_DISP_OFF_LEFT[bInd]; + int blkOffTop = MB_DISP_OFF_TOP[bInd]; + nc[BLK_DISP_MAP[bInd]] = CAVLC + .totalCoeff(cavlc.writeACBlock(out, mbLeftBlk + blkOffLeft, mbTopBlk + blkOffTop, + blkOffLeft == 0 ? leftMBType : curMBType, blkOffTop == 0 ? topMBType : curMBType, + ac[bInd], H264Const.totalZeros16, 1, 15, CoeffTransformer.zigzag4x4)); + } + } else { + for (int bInd = 0; bInd < 16; bInd++) { + int blkOffLeft = MB_DISP_OFF_LEFT[bInd]; + int blkOffTop = MB_DISP_OFF_TOP[bInd]; + cavlc.setZeroCoeff(mbLeftBlk + blkOffLeft, mbTopBlk + blkOffTop); + } + } + } + + public static boolean hasNz(int[] ac) { + int val = 0; + for (int i = 0; i < 16; i++) + val |= ac[i]; + return val != 0; + } + + private static void writeDC(CAVLC cavlc, int mbX, int mbY, BitWriter out, int qp, int mbLeftBlk, int mbTopBlk, + int[] dc, MBType leftMBType, MBType topMBType) { + if (dc.length == 4) { + CoeffTransformer.quantizeDC2x2(dc, qp); + CoeffTransformer.fvdDC2x2(dc); + cavlc.writeChrDCBlock(out, dc, H264Const.totalZeros4, 0, dc.length, new int[]{0, 1, 2, 3}); + } else if (dc.length == 8) { + CoeffTransformer.quantizeDC4x2(dc, qp); + CoeffTransformer.fvdDC4x2(dc); + cavlc.writeChrDCBlock(out, dc, H264Const.totalZeros8, 0, dc.length, new int[]{0, 1, 2, 3, 4, 5, 6, 7}); + } else { + reorderDC4x4(dc); + CoeffTransformer.quantizeDC4x4(dc, qp); + CoeffTransformer.fvdDC4x4(dc); + // TODO: calc here + cavlc.writeLumaDCBlock(out, mbLeftBlk, mbTopBlk, leftMBType, topMBType, dc, H264Const.totalZeros16, 0, 16, + CoeffTransformer.zigzag4x4); + } + } + + private static void transformChroma(int[][] ac) { + for (int i = 0; i < 4; i++) { + CoeffTransformer.fdct4x4(ac[i]); + } + } + + private static void predictChroma(EncodingContext ctx, Picture pic, int[][] ac, byte[][] pred, int comp, int x, + int y, int mode) { + ChromaPredictionBuilder.buildPred(mode, x >> 3, x != 0, y != 0, ctx.leftRow[comp], ctx.topLine[comp], ctx.topLeft[comp], pred); + + MBEncoderHelper.takeSubtract(pic.getPlaneData(comp), pic.getPlaneWidth(comp), pic.getPlaneHeight(comp), x, y, + ac[0], pred[0], 4, 4); + + MBEncoderHelper.takeSubtract(pic.getPlaneData(comp), pic.getPlaneWidth(comp), pic.getPlaneHeight(comp), x + 4, + y, ac[1], pred[1], 4, 4); + + MBEncoderHelper.takeSubtract(pic.getPlaneData(comp), pic.getPlaneWidth(comp), pic.getPlaneHeight(comp), x, + y + 4, ac[2], pred[2], 4, 4); + + MBEncoderHelper.takeSubtract(pic.getPlaneData(comp), pic.getPlaneWidth(comp), pic.getPlaneHeight(comp), x + 4, + y + 4, ac[3], pred[3], 4, 4); + } + + private void transform(Picture pic, int comp, int[][] ac, byte[][] pred, int x, int y) { + for (int i = 0; i < ac.length; i++) { + int[] coeff = ac[i]; + MBEncoderHelper.takeSubtract(pic.getPlaneData(comp), pic.getPlaneWidth(comp), pic.getPlaneHeight(comp), + x + BLK_X[i], y + BLK_Y[i], coeff, pred[i], 4, 4); + CoeffTransformer.fdct4x4(coeff); + } + } + + public int getCbpChroma(Picture pic, int mbX, int mbY) { + return 2; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBWriterINxN.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBWriterINxN.java new file mode 100644 index 0000000..9eb58a5 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBWriterINxN.java @@ -0,0 +1,148 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const; +import org.monte.media.impl.jcodec.codecs.h264.H264Encoder.NonRdVector; +import org.monte.media.impl.jcodec.codecs.h264.decode.CoeffTransformer; +import org.monte.media.impl.jcodec.codecs.h264.decode.Intra4x4PredictionBuilder; +import org.monte.media.impl.jcodec.codecs.h264.io.CAVLC; +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; +import org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter; +import org.monte.media.impl.jcodec.common.io.BitWriter; +import org.monte.media.impl.jcodec.common.model.Picture; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.BLK_DISP_MAP; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Encodes macroblock as I16x16 + * + * @author Stanislav Vitvitskyy + */ +public class MBWriterINxN { + public void encodeMacroblock(EncodingContext ctx, Picture pic, int mbX, int mbY, BitWriter out, EncodedMB outMB, + int qp, NonRdVector params) { + for (int bInd = 0; bInd < 16; bInd++) { + int blkX = H264Const.MB_DISP_OFF_LEFT[bInd]; + int blkY = H264Const.MB_DISP_OFF_TOP[bInd]; + writePredictionI4x4Block(out, mbX > 0, mbY > 0, ctx.leftMBType, ctx.topMBType[mbX], blkX, blkY, mbX, + ctx.i4x4PredTop, ctx.i4x4PredLeft, params.lumaPred4x4[bInd]); + } + + int[][] coeff = new int[16][16]; + int cbpLuma = lumaAnal(ctx, pic, mbX, mbY, out, qp, outMB, params.lumaPred4x4, coeff); + int cbpChroma = 2; + int cbp = cbpLuma | (cbpChroma << 4); + CAVLCWriter.writeUE(out, params.chrPred); + CAVLCWriter.writeUE(out, H264Const.CODED_BLOCK_PATTERN_INTRA_COLOR_INV[cbp]); + if (cbp != 0) + CAVLCWriter.writeSE(out, qp - ctx.prevQp); // MB QP delta + + outMB.setType(MBType.I_NxN); + outMB.setQp(qp); + + lumaCode(ctx, pic, mbX, mbY, out, qp, outMB, params.lumaPred4x4, coeff, cbpLuma); + MBWriterI16x16.chroma(ctx, pic, mbX, mbY, MBType.I_NxN, out, qp, outMB.getPixels(), params.chrPred); + ctx.prevQp = qp; + } + + private void writePredictionI4x4Block(BitWriter out, boolean leftAvailable, boolean topAvailable, MBType leftMBType, + MBType topMBType, int blkX, int blkY, int mbX, int[] i4x4PredTop, int[] i4x4PredLeft, int mode) { + int predMode = 2; + if ((leftAvailable || blkX > 0) && (topAvailable || blkY > 0)) { + int predModeB = topMBType == MBType.I_NxN || blkY > 0 ? i4x4PredTop[(mbX << 2) + blkX] : 2; + int predModeA = leftMBType == MBType.I_NxN || blkX > 0 ? i4x4PredLeft[blkY] : 2; + predMode = Math.min(predModeB, predModeA); + } + boolean prev4x4PredMode = mode == predMode; + out.write1Bit(prev4x4PredMode ? 1 : 0); + if (!prev4x4PredMode) { + int wrMode = mode - (mode > predMode ? 1 : 0); + out.writeNBit(wrMode, 3); + } + i4x4PredTop[(mbX << 2) + blkX] = i4x4PredLeft[blkY] = mode; + } + + private int lumaAnal(EncodingContext ctx, Picture pic, int mbX, int mbY, BitWriter out, int qp, EncodedMB outMB, + int predType[], int[][] _coeff) { + int cbp = 0; + byte[] pred = new byte[16]; + int[] coeff = new int[16]; + byte[] tl = new byte[]{ctx.topLeft[0], ctx.leftRow[0][3], ctx.leftRow[0][7], ctx.leftRow[0][11]}; + for (int i8x8 = 0; i8x8 < 4; i8x8++) { + boolean hasNz = false; + for (int i4x4 = 0; i4x4 < 4; i4x4++) { + int bIdx = (i8x8 << 2) + i4x4; + int blkOffLeft = H264Const.MB_DISP_OFF_LEFT[bIdx]; + int blkOffTop = H264Const.MB_DISP_OFF_TOP[bIdx]; + int blkX = (mbX << 2) + blkOffLeft; + int blkY = (mbY << 2) + blkOffTop; + + int dIdx = BLK_DISP_MAP[bIdx]; + boolean hasLeft = (dIdx & 0x3) != 0 || mbX != 0; + boolean hasTop = dIdx >= 4 || mbY != 0; + boolean hasTr = ((bIdx == 0 || bIdx == 1 || bIdx == 4) && mbY != 0) + || (bIdx == 5 && mbX < ctx.mbWidth - 1) || bIdx == 2 || bIdx == 6 || bIdx == 8 || bIdx == 9 + || bIdx == 10 || bIdx == 12 || bIdx == 14; + + Intra4x4PredictionBuilder.lumaPred(predType[bIdx], hasLeft, hasTop, hasTr, ctx.leftRow[0], + ctx.topLine[0], tl[blkOffTop], blkX << 2, blkOffTop << 2, pred); + MBEncoderHelper.takeSubtract(pic.getPlaneData(0), pic.getPlaneWidth(0), pic.getPlaneHeight(0), + blkX << 2, blkY << 2, coeff, pred, 4, 4); + CoeffTransformer.fdct4x4(coeff); + CoeffTransformer.quantizeAC(coeff, qp); + System.arraycopy(coeff, 0, _coeff[bIdx], 0, 16); + hasNz |= MBWriterI16x16.hasNz(coeff); + CoeffTransformer.dequantizeAC(coeff, qp, null); + CoeffTransformer.idct4x4(coeff); + MBEncoderHelper.putBlk(outMB.pixels.getPlaneData(0), coeff, pred, 4, blkOffLeft << 2, blkOffTop << 2, 4, + 4); + + tl[blkOffTop] = ctx.topLine[0][(blkX << 2) + 3]; + for (int p = 0; p < 4; p++) { + ctx.leftRow[0][(blkOffTop << 2) + p] = (byte) clip(coeff[3 + (p << 2)] + pred[3 + (p << 2)], -128, + 127); + ctx.topLine[0][(blkX << 2) + p] = (byte) clip(coeff[12 + p] + pred[12 + p], -128, 127); + } + } + cbp |= ((hasNz ? 1 : 0) << i8x8); + } + ctx.topLeft[0] = tl[0]; + return cbp; + } + + private void lumaCode(EncodingContext ctx, Picture pic, int mbX, int mbY, BitWriter out, int qp, EncodedMB outMB, + int predType[], int[][] _coeff, int cbpLuma) { + int cbp = 0; + for (int i8x8 = 0; i8x8 < 4; i8x8++) { + int bx = (mbX << 2) | ((i8x8 << 1) & 2); + int by = (mbY << 2) | (i8x8 & 2); + + if ((cbpLuma & (1 << i8x8)) != 0) { + cbp |= (1 << i8x8); + for (int i4x4 = 0; i4x4 < 4; i4x4++) { + int blkX = bx | (i4x4 & 1); + int blkY = by | ((i4x4 >> 1) & 1); + int blkOffLeft = blkX & 0x3; + int blkOffTop = blkY & 0x3; + int bIdx = (i8x8 << 2) + i4x4; + int dIdx = BLK_DISP_MAP[bIdx]; + + outMB.nc[dIdx] = CAVLC.totalCoeff( + ctx.cavlc[0].writeACBlock(out, blkX, blkY, blkOffLeft == 0 ? ctx.leftMBType : MBType.I_NxN, + blkOffTop == 0 ? ctx.topMBType[mbX] : MBType.I_NxN, _coeff[bIdx], H264Const.totalZeros16, 0, + 16, CoeffTransformer.zigzag4x4)); + + } + } else { + for (int i4x4 = 0; i4x4 < 4; i4x4++) { + int blkX = bx | (i4x4 & 1); + int blkY = by | ((i4x4 >> 1) & 1); + ctx.cavlc[0].setZeroCoeff(blkX, blkY); + } + } + } + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBWriterP16x16.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBWriterP16x16.java new file mode 100644 index 0000000..d10f717 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MBWriterP16x16.java @@ -0,0 +1,182 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const; +import org.monte.media.impl.jcodec.codecs.h264.H264Encoder.NonRdVector; +import org.monte.media.impl.jcodec.codecs.h264.decode.BlockInterpolator; +import org.monte.media.impl.jcodec.codecs.h264.decode.CoeffTransformer; +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter; +import org.monte.media.impl.jcodec.common.io.BitWriter; +import org.monte.media.impl.jcodec.common.model.Picture; + +import java.util.Arrays; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.MB_DISP_OFF_LEFT; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.MB_DISP_OFF_TOP; +import static org.monte.media.impl.jcodec.codecs.h264.encode.H264EncoderUtils.median; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.P_16x16; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Encodes macroblock as P16x16 + * + * @author Stanislav Vitvitskyy + */ +public class MBWriterP16x16 { + private SeqParameterSet sps; + private Picture ref; + + private BlockInterpolator interpolator; + + public MBWriterP16x16(SeqParameterSet sps, Picture ref) { + this.sps = sps; + this.ref = ref; + interpolator = new BlockInterpolator(); + } + + public void encodeMacroblock(EncodingContext ctx, Picture pic, int mbX, int mbY, BitWriter out, EncodedMB outMB, + int qp, NonRdVector params) { + if (sps.numRefFrames > 1) { + int refIdx = decideRef(); + CAVLCWriter.writeTE(out, refIdx, sps.numRefFrames - 1); + } + int partBlkSize = 4; // 16x16 + int refIdx = 1; + + boolean trAvb = mbY > 0 && mbX < sps.picWidthInMbsMinus1; + boolean tlAvb = mbX > 0 && mbY > 0; + int ax = ctx.mvLeftX[0]; + int ay = ctx.mvLeftY[0]; + boolean ar = ctx.mvLeftR[0] == refIdx; + + int bx = ctx.mvTopX[mbX << 2]; + int by = ctx.mvTopY[mbX << 2]; + boolean br = ctx.mvTopR[mbX << 2] == refIdx; + + int cx = trAvb ? ctx.mvTopX[(mbX << 2) + partBlkSize] : 0; + int cy = trAvb ? ctx.mvTopY[(mbX << 2) + partBlkSize] : 0; + boolean cr = trAvb ? ctx.mvTopR[(mbX << 2) + partBlkSize] == refIdx : false; + + int dx = tlAvb ? ctx.mvTopLeftX : 0; + int dy = tlAvb ? ctx.mvTopLeftY : 0; + boolean dr = tlAvb ? (ctx.mvTopLeftR == refIdx) : false; + + int mvpx = median(ax, ar, bx, br, cx, cr, dx, dr, mbX > 0, mbY > 0, trAvb, tlAvb); + int mvpy = median(ay, ar, by, br, cy, cr, dy, dr, mbX > 0, mbY > 0, trAvb, tlAvb); + + // Motion estimation for the current macroblock + CAVLCWriter.writeSE(out, params.mv[0] - mvpx); // mvdx + CAVLCWriter.writeSE(out, params.mv[1] - mvpy); // mvdy + + Picture mbRef = Picture.create(16, 16, sps.chromaFormatIdc); + int[][] mb = new int[][]{new int[256], new int[64], new int[64]}; + + interpolator.getBlockLuma(ref, mbRef, 0, (mbX << 6) + params.mv[0], (mbY << 6) + params.mv[1], 16, 16); + + BlockInterpolator.getBlockChroma(ref.getPlaneData(1), ref.getPlaneWidth(1), ref.getPlaneHeight(1), + mbRef.getPlaneData(1), 0, mbRef.getPlaneWidth(1), (mbX << 6) + params.mv[0], (mbY << 6) + params.mv[1], + 8, 8); + BlockInterpolator.getBlockChroma(ref.getPlaneData(2), ref.getPlaneWidth(2), ref.getPlaneHeight(2), + mbRef.getPlaneData(2), 0, mbRef.getPlaneWidth(2), (mbX << 6) + params.mv[0], (mbY << 6) + params.mv[1], + 8, 8); + + MBEncoderHelper.takeSubtract(pic.getPlaneData(0), pic.getPlaneWidth(0), pic.getPlaneHeight(0), mbX << 4, + mbY << 4, mb[0], mbRef.getPlaneData(0), 16, 16); + MBEncoderHelper.takeSubtract(pic.getPlaneData(1), pic.getPlaneWidth(1), pic.getPlaneHeight(1), mbX << 3, + mbY << 3, mb[1], mbRef.getPlaneData(1), 8, 8); + MBEncoderHelper.takeSubtract(pic.getPlaneData(2), pic.getPlaneWidth(2), pic.getPlaneHeight(2), mbX << 3, + mbY << 3, mb[2], mbRef.getPlaneData(2), 8, 8); + + int codedBlockPattern = getCodedBlockPattern(); + CAVLCWriter.writeUE(out, H264Const.CODED_BLOCK_PATTERN_INTER_COLOR_INV[codedBlockPattern]); + + CAVLCWriter.writeSE(out, qp - ctx.prevQp); + + luma(ctx, mb[0], mbX, mbY, out, qp, outMB.getNc()); + chroma(ctx, mb[1], mb[2], mbX, mbY, out, qp); + + MBEncoderHelper.putBlk(outMB.getPixels().getPlaneData(0), mb[0], mbRef.getPlaneData(0), 4, 0, 0, 16, 16); + MBEncoderHelper.putBlk(outMB.getPixels().getPlaneData(1), mb[1], mbRef.getPlaneData(1), 3, 0, 0, 8, 8); + MBEncoderHelper.putBlk(outMB.getPixels().getPlaneData(2), mb[2], mbRef.getPlaneData(2), 3, 0, 0, 8, 8); + + Arrays.fill(outMB.getMx(), params.mv[0]); + Arrays.fill(outMB.getMy(), params.mv[1]); + Arrays.fill(outMB.getMr(), refIdx); + outMB.setType(MBType.P_16x16); + outMB.setQp(qp); + ctx.prevQp = qp; + } + + private int getCodedBlockPattern() { + return 47; + } + + /** + * Decides which reference to use + * + * @return + */ + private int decideRef() { + return 0; + } + + private static void luma(EncodingContext ctx, int[] pix, int mbX, int mbY, BitWriter out, int qp, int[] nc) { + int[][] ac = new int[16][16]; + for (int i = 0; i < ac.length; i++) { + for (int j = 0; j < H264Const.PIX_MAP_SPLIT_4x4[i].length; j++) { + ac[i][j] = pix[H264Const.PIX_MAP_SPLIT_4x4[i][j]]; + } + CoeffTransformer.fdct4x4(ac[i]); + } + + writeAC(ctx, 0, mbX, out, mbX << 2, mbY << 2, ac, qp, nc); + + for (int i = 0; i < ac.length; i++) { + CoeffTransformer.dequantizeAC(ac[i], qp, null); + CoeffTransformer.idct4x4(ac[i]); + for (int j = 0; j < H264Const.PIX_MAP_SPLIT_4x4[i].length; j++) + pix[H264Const.PIX_MAP_SPLIT_4x4[i][j]] = ac[i][j]; + } + } + + private static void chroma(EncodingContext ctx, int[] pix1, int[] pix2, int mbX, int mbY, BitWriter out, + int qp) { + int[][] ac1 = new int[4][16]; + int[][] ac2 = new int[4][16]; + for (int i = 0; i < ac1.length; i++) { + for (int j = 0; j < H264Const.PIX_MAP_SPLIT_2x2[i].length; j++) + ac1[i][j] = pix1[H264Const.PIX_MAP_SPLIT_2x2[i][j]]; + } + for (int i = 0; i < ac2.length; i++) { + for (int j = 0; j < H264Const.PIX_MAP_SPLIT_2x2[i].length; j++) + ac2[i][j] = pix2[H264Const.PIX_MAP_SPLIT_2x2[i][j]]; + } + MBWriterI16x16.chromaResidual(mbX, mbY, out, qp, ac1, ac2, ctx.cavlc[1], ctx.cavlc[2], ctx.leftMBType, ctx.topMBType[mbX], P_16x16); + + for (int i = 0; i < ac1.length; i++) { + for (int j = 0; j < H264Const.PIX_MAP_SPLIT_2x2[i].length; j++) + pix1[H264Const.PIX_MAP_SPLIT_2x2[i][j]] = ac1[i][j]; + } + for (int i = 0; i < ac2.length; i++) { + for (int j = 0; j < H264Const.PIX_MAP_SPLIT_2x2[i].length; j++) + pix2[H264Const.PIX_MAP_SPLIT_2x2[i][j]] = ac2[i][j]; + } + } + + private static void writeAC(EncodingContext ctx, int comp, int mbX, + BitWriter out, int mbLeftBlk, int mbTopBlk, int[][] ac, int qp, int[] nc) { + for (int bIndx = 0; bIndx < ac.length; bIndx++) { + int dIdx = H264Const.BLK_DISP_MAP[bIndx]; + CoeffTransformer.quantizeAC(ac[dIdx], qp); + int blkOffLeft = MB_DISP_OFF_LEFT[bIndx]; + int blkOffTop = MB_DISP_OFF_TOP[bIndx]; + int coeffToken = ctx.cavlc[comp].writeACBlock(out, mbLeftBlk + blkOffLeft, mbTopBlk + blkOffTop, + blkOffLeft == 0 ? ctx.leftMBType : P_16x16, blkOffTop == 0 ? ctx.topMBType[mbX] : P_16x16, ac[dIdx], + H264Const.totalZeros16, 0, 16, CoeffTransformer.zigzag4x4); + nc[dIdx] = coeffToken >> 4; // total coeff + } + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MotionEstimator.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MotionEstimator.java new file mode 100644 index 0000000..3c790f5 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/MotionEstimator.java @@ -0,0 +1,341 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import static java.lang.Math.min; +import static org.monte.media.impl.jcodec.codecs.h264.encode.H264EncoderUtils.median; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Estimates motion using diagonal search + * + * @author Stanislav Vitvitskyy + */ +public class MotionEstimator { + private int maxSearchRange; + private int[] mvTopX; + private int[] mvTopY; + private int[] mvTopR; + private int mvLeftX; + private int mvLeftY; + private int mvLeftR; + private int mvTopLeftX; + private int mvTopLeftY; + private int mvTopLeftR; + private SeqParameterSet sps; + private Picture ref; + + public MotionEstimator(Picture ref, SeqParameterSet sps, int maxSearchRange) { + this.sps = sps; + this.ref = ref; + mvTopX = new int[sps.picWidthInMbsMinus1 + 1]; + mvTopY = new int[sps.picWidthInMbsMinus1 + 1]; + mvTopR = new int[sps.picWidthInMbsMinus1 + 1]; + this.maxSearchRange = maxSearchRange; + } + + public int[] mvEstimate(Picture pic, int mbX, int mbY) { + int refIdx = 1; + byte[] patch = new byte[256]; + boolean trAvb = mbY > 0 && mbX < sps.picWidthInMbsMinus1; + boolean tlAvb = mbX > 0 && mbY > 0; + + int ax = mvLeftX; + int ay = mvLeftY; + boolean ar = mvLeftR == refIdx; + + int bx = mvTopX[mbX]; + int by = mvTopY[mbX]; + boolean br = mvTopR[mbX] == refIdx; + + int cx = trAvb ? mvTopX[mbX + 1] : 0; + int cy = trAvb ? mvTopY[mbX + 1] : 0; + boolean cr = trAvb ? mvTopR[mbX + 1] == refIdx : false; + + int dx = tlAvb ? mvTopLeftX : 0; + int dy = tlAvb ? mvTopLeftY : 0; + boolean dr = tlAvb && mvTopLeftR == refIdx; + + int mvpx = median(ax, ar, bx, br, cx, cr, dx, dr, mbX > 0, mbY > 0, trAvb, tlAvb); + int mvpy = median(ay, ar, by, br, cy, cr, dy, dr, mbX > 0, mbY > 0, trAvb, tlAvb); + MBEncoderHelper.take(pic.getPlaneData(0), pic.getPlaneWidth(0), pic.getPlaneHeight(0), mbX << 4, mbY << 4, + patch, 16, 16); + int[] fullPix = estimateFullPix(ref, patch, mbX, mbY, mvpx, mvpy); + return estimateQPix(ref, patch, fullPix, mbX, mbY); + } + + private static final int[] SUB_X_OFF = {0, -2, 2, 0, 0, -2, -2, 2, 2, -1, 1, 0, 0, -1, -2, -1, -2, 1, 2, 1, 2, -1, 1, -1, 1}; + private static final int[] SUB_Y_OFF = {0, 0, 0, -2, 2, -2, 2, -2, 2, 0, 0, -1, 1, -2, -1, 2, 1, -2, -1, 2, 1, -1, -1, 1, 1}; + + public static int[] estimateQPix(Picture ref, byte[] patch, int[] fullPix, int mbX, int mbY) { + int fullX = (mbX << 4) + (fullPix[0] >> 2); + int fullY = (mbY << 4) + (fullPix[1] >> 2); + if (fullX < 3 || fullY < 3) + return fullPix; + byte[] sp = new byte[22 * 22]; + MBEncoderHelper.take(ref.getPlaneData(0), ref.getPlaneWidth(0), ref.getPlaneHeight(0), fullX - 3, fullY - 3, sp, + 22, 22); + // Calculating half pen + int[] pp = new int[352]; + int[] pn = new int[352]; + int scores[] = new int[25]; + for (int j = 0, dOff = 0, sOff = 0; j < 22; j++) { + for (int i = 0; i < 16; i++, dOff++, sOff++) { + { + int a = sp[sOff + 0] + sp[sOff + 5]; + int b = sp[sOff + 1] + sp[sOff + 4]; + int c = sp[sOff + 2] + sp[sOff + 3]; + + pn[dOff] = a + 5 * ((c << 2) - b); + } + { + int a = sp[sOff + 1] + sp[sOff + 6]; + int b = sp[sOff + 2] + sp[sOff + 5]; + int c = sp[sOff + 3] + sp[sOff + 4]; + pp[dOff] = a + 5 * ((c << 2) - b); + } + } + sOff += 6; + } + for (int j = 0, sof = 0, off = 0; j < 16; j++) { + for (int i = 0; i < 16; i++, off++, sof++) { + scores[0] += MathUtil.abs(patch[off] - sp[sof + 69]); + int horN20 = clip((pn[off + 48] + 16) >> 5, -128, 127); + int horP20 = clip((pp[off + 48] + 16) >> 5, -128, 127); + scores[1] += MathUtil.abs(patch[off] - horN20); + scores[2] += MathUtil.abs(patch[off] - horP20); + int horN10 = (horN20 + sp[sof + 69] + 1) >> 1; + int horP10 = (horP20 + sp[sof + 69] + 1) >> 1; + scores[9] += MathUtil.abs(patch[off] - horN10); + scores[10] += MathUtil.abs(patch[off] - horP10); + int verN20; + { + int a = sp[3 + sof + 0] + sp[3 + sof + 110]; + int b = sp[3 + sof + 22] + sp[3 + sof + 88]; + int c = sp[3 + sof + 44] + sp[3 + sof + 66]; + + int verNeg = a + 5 * ((c << 2) - b); + verN20 = clip((verNeg + 16) >> 5, -128, 127); + int verN10 = (verN20 + sp[sof + 69] + 1) >> 1; + int dnn = (verN20 + horN20 + 1) >> 1; + int dpn = (verN20 + horP20 + 1) >> 1; + scores[3] += MathUtil.abs(patch[off] - verN20); + scores[11] += MathUtil.abs(patch[off] - verN10); + scores[21] += MathUtil.abs(patch[off] - dnn); + scores[22] += MathUtil.abs(patch[off] - dpn); + } + int verP20; + { + int a = sp[3 + sof + 22] + sp[3 + sof + 132]; + int b = sp[3 + sof + 44] + sp[3 + sof + 110]; + int c = sp[3 + sof + 66] + sp[3 + sof + 88]; + int verPos = a + 5 * ((c << 2) - b); + + verP20 = clip((verPos + 16) >> 5, -128, 127); + int verP10 = (verP20 + sp[sof + 69] + 1) >> 1; + int dnp = (verP20 + horN20 + 1) >> 1; + int dpp = (verP20 + horP20 + 1) >> 1; + + scores[4] += MathUtil.abs(patch[off] - verP20); + scores[12] += MathUtil.abs(patch[off] - verP10); + scores[23] += MathUtil.abs(patch[off] - dnp); + scores[24] += MathUtil.abs(patch[off] - dpp); + } + { + int a = pn[off + 0] + pn[off + 80]; + int b = pn[off + 16] + pn[off + 64]; + int c = pn[off + 32] + pn[off + 48]; + + int interpNeg = a + 5 * ((c << 2) - b); + int diagNN = clip((interpNeg + 512) >> 10, -128, 127); + int ver = (diagNN + verN20 + 1) >> 1; + int hor = (diagNN + horN20 + 1) >> 1; + + scores[5] += MathUtil.abs(patch[off] - diagNN); + scores[13] += MathUtil.abs(patch[off] - ver); + scores[14] += MathUtil.abs(patch[off] - hor); + } + { + int a = pn[off + 16] + pn[off + 96]; + int b = pn[off + 32] + pn[off + 80]; + int c = pn[off + 48] + pn[off + 64]; + int interpPos = a + 5 * ((c << 2) - b); + + int diagNP = clip((interpPos + 512) >> 10, -128, 127); + int ver = (diagNP + verP20 + 1) >> 1; + int hor = (diagNP + horN20 + 1) >> 1; + scores[6] += MathUtil.abs(patch[off] - diagNP); + scores[15] += MathUtil.abs(patch[off] - ver); + scores[16] += MathUtil.abs(patch[off] - hor); + } + { + int a = pp[off + 0] + pp[off + 80]; + int b = pp[off + 16] + pp[off + 64]; + int c = pp[off + 32] + pp[off + 48]; + + int interpNeg = a + 5 * ((c << 2) - b); + int diagPN = clip((interpNeg + 512) >> 10, -128, 127); + int ver = (diagPN + verN20 + 1) >> 1; + int hor = (diagPN + horP20 + 1) >> 1; + scores[7] += MathUtil.abs(patch[off] - diagPN); + scores[17] += MathUtil.abs(patch[off] - ver); + scores[18] += MathUtil.abs(patch[off] - hor); + } + { + int a = pp[off + 16] + pp[off + 96]; + int b = pp[off + 32] + pp[off + 80]; + int c = pp[off + 48] + pp[off + 64]; + int interpPos = a + 5 * ((c << 2) - b); + + int diagPP = clip((interpPos + 512) >> 10, -128, 127); + int ver = (diagPP + verP20 + 1) >> 1; + int hor = (diagPP + horP20 + 1) >> 1; + scores[8] += MathUtil.abs(patch[off] - diagPP); + scores[19] += MathUtil.abs(patch[off] - ver); + scores[20] += MathUtil.abs(patch[off] - hor); + } + } + sof += 6; + } + + + int m0 = Math.min(scores[1], scores[2]); + int m1 = Math.min(scores[3], scores[4]); + int m2 = Math.min(scores[5], scores[6]); + + int m3 = Math.min(scores[7], scores[8]); + int m4 = Math.min(scores[9], scores[10]); + int m5 = Math.min(scores[11], scores[12]); + + int m6 = Math.min(scores[13], scores[14]); + int m7 = Math.min(scores[15], scores[16]); + int m8 = Math.min(scores[17], scores[18]); + + int m9 = Math.min(scores[19], scores[20]); + int m10 = Math.min(scores[21], scores[22]); + int m11 = Math.min(scores[23], scores[24]); + + m0 = Math.min(m0, m1); + m2 = Math.min(m2, m3); + m4 = Math.min(m4, m5); + m6 = Math.min(m6, m7); + m8 = Math.min(m8, m9); + m10 = Math.min(m10, m11); + + m0 = Math.min(m0, m2); + m4 = Math.min(m4, m6); + m8 = Math.min(m8, m10); + + int mf0 = Math.min(scores[0], m0); + int mf1 = Math.min(m4, m8); + int mf2 = Math.min(mf0, mf1); + + int sel = 0; + for (int i = 0; i < 25; i++) { + if (mf2 == scores[i]) { + sel = i; + break; + } + } + + return new int[]{fullPix[0] + SUB_X_OFF[sel], fullPix[1] + SUB_Y_OFF[sel]}; + } + + public void mvSave(int mbX, int mbY, int[] mv) { + mvTopLeftX = mvTopX[mbX]; + mvTopLeftY = mvTopY[mbX]; + mvTopLeftR = mvTopR[mbX]; + mvTopX[mbX] = mv[0]; + mvTopY[mbX] = mv[1]; + mvTopR[mbX] = mv[2]; + mvLeftX = mv[0]; + mvLeftY = mv[1]; + mvLeftR = mv[2]; + } + + private int[] estimateFullPix(Picture ref, byte[] patch, int mbX, int mbY, int mvpx, int mvpy) { + byte[] searchPatch = new byte[(maxSearchRange * 2 + 16) * (maxSearchRange * 2 + 16)]; + + int mvX0 = 0; + int mvX1 = 0; + int mvY0 = 0; + int mvY1 = 0; + int mvS0 = Integer.MAX_VALUE; + int mvS1 = Integer.MAX_VALUE; + // Search area 0: mb position + int startX = (mbX << 4); + int startY = (mbY << 4); + for (int area = 0; area < 2; area++) { + int patchTlX = Math.max(startX - maxSearchRange, 0); + int patchTlY = Math.max(startY - maxSearchRange, 0); + int patchBrX = Math.min(startX + maxSearchRange + 16, ref.getPlaneWidth(0)); + int patchBrY = Math.min(startY + maxSearchRange + 16, ref.getPlaneHeight(0)); + + int inPatchX = startX - patchTlX; + int inPatchY = startY - patchTlY; + if (inPatchX < 0 || inPatchY < 0) + continue; + int patchW = patchBrX - patchTlX; + int patchH = patchBrY - patchTlY; + // TODO: border fill? + MBEncoderHelper.takeSafe(ref.getPlaneData(0), ref.getPlaneWidth(0), ref.getPlaneHeight(0), patchTlX, patchTlY, + searchPatch, patchW, patchH); + + int bestMvX = inPatchX; + int bestMvY = inPatchY; + int bestScore = sad(searchPatch, patchW, patch, bestMvX, bestMvY); + // Diagonal search + for (int i = 0; i < maxSearchRange; i++) { + int score1 = bestMvX > 0 ? sad(searchPatch, patchW, patch, bestMvX - 1, bestMvY) : Integer.MAX_VALUE; + int score2 = bestMvX < patchW - 1 ? sad(searchPatch, patchW, patch, bestMvX + 1, bestMvY) + : Integer.MAX_VALUE; + int score3 = bestMvY > 0 ? sad(searchPatch, patchW, patch, bestMvX, bestMvY - 1) : Integer.MAX_VALUE; + int score4 = bestMvY < patchH - 1 ? sad(searchPatch, patchW, patch, bestMvX, bestMvY + 1) + : Integer.MAX_VALUE; + int min = min(min(min(score1, score2), score3), score4); + if (min > bestScore) + break; + bestScore = min; + if (score1 == min) { + --bestMvX; + } else if (score2 == min) { + ++bestMvX; + } else if (score3 == min) { + --bestMvY; + } else { + ++bestMvY; + } + } + if (area == 0) { + mvX0 = ((bestMvX - inPatchX) << 2); + mvY0 = ((bestMvY - inPatchY) << 2); + mvS0 = bestScore; + // Search area 1: mb predictor + startX = (mbX << 4) + (mvpx >> 2); + startY = (mbY << 4) + (mvpy >> 2); + } else { + mvX1 = (bestMvX - inPatchX + startX - (mbX << 4)) << 2; + mvY1 = (bestMvY - inPatchY + startY - (mbY << 4)) << 2; + mvS1 = bestScore; + } + } + + return new int[]{mvS0 < mvS1 ? mvX0 : mvX1, mvS0 < mvS1 ? mvY0 : mvY1}; + } + + private int sad(byte[] big, int bigStride, byte[] small, int offX, int offY) { + int score = 0, bigOff = offY * bigStride + offX, smallOff = 0; + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++, ++bigOff, ++smallOff) { + score += MathUtil.abs(big[bigOff] - small[smallOff]); + } + bigOff += bigStride - 16; + } + return score; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/RateControl.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/RateControl.java new file mode 100644 index 0000000..bb6a2b5 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/encode/RateControl.java @@ -0,0 +1,22 @@ +package org.monte.media.impl.jcodec.codecs.h264.encode; + +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.common.model.Size; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * MPEG 4 AVC ( H.264 ) Encoder pluggable rate control mechanism + * + * @author The JCodec project + */ +public interface RateControl { + + int startPicture(Size sz, int maxSize, SliceType sliceType); + + int initialQpDelta(Picture pic, int mbX, int mbY); + + int accept(int bits); +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/CABAC.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/CABAC.java new file mode 100644 index 0000000..f358abc --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/CABAC.java @@ -0,0 +1,749 @@ +package org.monte.media.impl.jcodec.codecs.h264.io; + +import org.monte.media.impl.jcodec.codecs.common.biari.MDecoder; +import org.monte.media.impl.jcodec.codecs.common.biari.MEncoder; +import org.monte.media.impl.jcodec.codecs.h264.H264Const; +import org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred; +import org.monte.media.impl.jcodec.codecs.h264.H264Utils2; +import org.monte.media.impl.jcodec.codecs.h264.decode.CABACContst; +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.Bi; +import static org.monte.media.impl.jcodec.codecs.h264.H264Const.PartPred.Direct; +import static org.monte.media.impl.jcodec.codecs.h264.io.CABAC.BlockType.CHROMA_AC; +import static org.monte.media.impl.jcodec.codecs.h264.io.CABAC.BlockType.CHROMA_DC; +import static org.monte.media.impl.jcodec.codecs.h264.io.CABAC.BlockType.LUMA_16_DC; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.B_Direct_16x16; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.I_16x16; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.I_NxN; +import static org.monte.media.impl.jcodec.codecs.h264.io.model.MBType.I_PCM; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; +import static org.monte.media.impl.jcodec.common.tools.MathUtil.sign; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author JCodec project + */ +public class CABAC { + + public final static class BlockType { + public final static BlockType LUMA_16_DC = new BlockType(85, 105, 166, 277, 338, 227, 0); + public final static BlockType LUMA_15_AC = new BlockType(89, 120, 181, 292, 353, 237, 0); + public final static BlockType LUMA_16 = new BlockType(93, 134, 195, 306, 367, 247, 0); + public final static BlockType CHROMA_DC = new BlockType(97, 149, 210, 321, 382, 257, 1); + public final static BlockType CHROMA_AC = new BlockType(101, 152, 213, 324, 385, 266, 0); + public final static BlockType LUMA_64 = new BlockType(1012, 402, 417, 436, 451, 426, 0); + public final static BlockType CB_16_DC = new BlockType(460, 484, 572, 776, 864, 952, 0); + public final static BlockType CB_15x16_AC = new BlockType(464, 499, 587, 791, 879, 962, 0); + public final static BlockType CB_16 = new BlockType(468, 513, 601, 805, 893, 972, 0); + public final static BlockType CB_64 = new BlockType(1016, 660, 690, 675, 699, 708, 0); + public final static BlockType CR_16_DC = new BlockType(472, 528, 616, 820, 908, 982, 0); + public final static BlockType CR_15x16_AC = new BlockType(476, 543, 631, 835, 923, 992, 0); + public final static BlockType CR_16 = new BlockType(480, 557, 645, 849, 937, 1002, 0); + public final static BlockType CR_64 = new BlockType(1020, 718, 748, 733, 757, 766, 0); + + public int codedBlockCtxOff; + public int sigCoeffFlagCtxOff; + public int lastSigCoeffCtxOff; + public int sigCoeffFlagFldCtxOff; + public int lastSigCoeffFldCtxOff; + public int coeffAbsLevelCtxOff; + public int coeffAbsLevelAdjust; + + private BlockType(int codecBlockCtxOff, int sigCoeffCtxOff, int lastSigCoeffCtxOff, int sigCoeffFlagFldCtxOff, + int lastSigCoeffFldCtxOff, int coeffAbsLevelCtxOff, int coeffAbsLevelAdjust) { + this.codedBlockCtxOff = codecBlockCtxOff; + this.sigCoeffFlagCtxOff = sigCoeffCtxOff; + this.lastSigCoeffCtxOff = lastSigCoeffCtxOff; + this.sigCoeffFlagFldCtxOff = sigCoeffFlagFldCtxOff; + this.lastSigCoeffFldCtxOff = sigCoeffFlagFldCtxOff; + this.coeffAbsLevelCtxOff = coeffAbsLevelCtxOff; + this.coeffAbsLevelAdjust = coeffAbsLevelAdjust; + } + } + + private int chromaPredModeLeft; + private int[] chromaPredModeTop; + private int prevMbQpDelta; + private int prevCBP; + + private int[][] codedBlkLeft; + private int[][] codedBlkTop; + + private int[] codedBlkDCLeft; + private int[][] codedBlkDCTop; + + private int[][] refIdxLeft; + private int[][] refIdxTop; + + private boolean skipFlagLeft; + private boolean[] skipFlagsTop; + + private int[][][] mvdTop; + private int[][][] mvdLeft; + + public int[] tmp; + + public CABAC(int mbWidth) { + this.tmp = new int[16]; + this.chromaPredModeLeft = 0; + this.chromaPredModeTop = new int[mbWidth]; + this.codedBlkLeft = new int[][]{new int[4], new int[2], new int[2]}; + this.codedBlkTop = new int[][]{new int[mbWidth << 2], new int[mbWidth << 1], new int[mbWidth << 1]}; + + this.codedBlkDCLeft = new int[3]; + this.codedBlkDCTop = new int[3][mbWidth]; + + this.refIdxLeft = new int[2][4]; + this.refIdxTop = new int[2][mbWidth << 2]; + + this.skipFlagsTop = new boolean[mbWidth]; + + this.mvdTop = new int[2][2][mbWidth << 2]; + this.mvdLeft = new int[2][2][4]; + } + + public int readCoeffs(MDecoder decoder, BlockType blockType, int[] out, int first, int num, int[] reorder, + int[] scMapping, int[] lscMapping) { + boolean sigCoeff[] = new boolean[num]; + int numCoeff; + for (numCoeff = 0; numCoeff < num - 1; numCoeff++) { + sigCoeff[numCoeff] = decoder.decodeBin(blockType.sigCoeffFlagCtxOff + scMapping[numCoeff]) == 1; + if (sigCoeff[numCoeff] && decoder.decodeBin(blockType.lastSigCoeffCtxOff + lscMapping[numCoeff]) == 1) + break; + } + sigCoeff[numCoeff++] = true; + + int numGt1 = 0, numEq1 = 0; + for (int j = numCoeff - 1; j >= 0; j--) { + if (!sigCoeff[j]) + continue; + int absLev = readCoeffAbsLevel(decoder, blockType, numGt1, numEq1); + if (absLev == 0) + ++numEq1; + else + ++numGt1; + out[reorder[j + first]] = MathUtil.toSigned(absLev + 1, -decoder.decodeBinBypass()); + } + // System.out.print("["); + // for (int i = 0; i < out.length; i++) + // System.out.print(out[i] + ","); + // System.out.println("]"); + + return numGt1 + numEq1; + } + + private int readCoeffAbsLevel(MDecoder decoder, BlockType blockType, int numDecodAbsLevelGt1, + int numDecodAbsLevelEq1) { + int incB0 = ((numDecodAbsLevelGt1 != 0) ? 0 : Math.min(4, 1 + numDecodAbsLevelEq1)); + int incBN = 5 + Math.min(4 - blockType.coeffAbsLevelAdjust, numDecodAbsLevelGt1); + + int val, b = decoder.decodeBin(blockType.coeffAbsLevelCtxOff + incB0); + for (val = 0; b != 0 && val < 13; val++) + b = decoder.decodeBin(blockType.coeffAbsLevelCtxOff + incBN); + val += b; + + if (val == 14) { + int log = -2, add = 0, sum = 0; + do { + log++; + b = decoder.decodeBinBypass(); + } while (b != 0); + + for (; log >= 0; log--) { + add |= decoder.decodeBinBypass() << log; + sum += 1 << log; + } + + val += add + sum; + } + + return val; + } + + public void writeCoeffs(MEncoder encoder, BlockType blockType, int[] _out, int first, int num, int[] reorder) { + + for (int i = 0; i < num; i++) + tmp[i] = _out[reorder[first + i]]; + + int numCoeff = 0; + for (int i = 0; i < num; i++) { + if (tmp[i] != 0) + numCoeff = i + 1; + } + for (int i = 0; i < Math.min(numCoeff, num - 1); i++) { + if (tmp[i] != 0) { + encoder.encodeBin(blockType.sigCoeffFlagCtxOff + i, 1); + encoder.encodeBin(blockType.lastSigCoeffCtxOff + i, i == numCoeff - 1 ? 1 : 0); + } else { + encoder.encodeBin(blockType.sigCoeffFlagCtxOff + i, 0); + } + } + + int numGt1 = 0, numEq1 = 0; + for (int j = numCoeff - 1; j >= 0; j--) { + if (tmp[j] == 0) + continue; + int absLev = MathUtil.abs(tmp[j]) - 1; + writeCoeffAbsLevel(encoder, blockType, numGt1, numEq1, absLev); + if (absLev == 0) + ++numEq1; + else + ++numGt1; + encoder.encodeBinBypass(sign(tmp[j])); + } + } + + private void writeCoeffAbsLevel(MEncoder encoder, BlockType blockType, int numDecodAbsLevelGt1, + int numDecodAbsLevelEq1, int absLev) { + int incB0 = ((numDecodAbsLevelGt1 != 0) ? 0 : Math.min(4, 1 + numDecodAbsLevelEq1)); + int incBN = 5 + Math.min(4 - blockType.coeffAbsLevelAdjust, numDecodAbsLevelGt1); + + if (absLev == 0) { + encoder.encodeBin(blockType.coeffAbsLevelCtxOff + incB0, 0); + } else { + encoder.encodeBin(blockType.coeffAbsLevelCtxOff + incB0, 1); + if (absLev < 14) { + for (int i = 1; i < absLev; i++) + encoder.encodeBin(blockType.coeffAbsLevelCtxOff + incBN, 1); + encoder.encodeBin(blockType.coeffAbsLevelCtxOff + incBN, 0); + } else { + for (int i = 1; i < 14; i++) + encoder.encodeBin(blockType.coeffAbsLevelCtxOff + incBN, 1); + absLev -= 14; + int sufLen, pow; + for (sufLen = 0, pow = 1; absLev >= pow; sufLen++, pow = (1 << sufLen)) { + encoder.encodeBinBypass(1); + absLev -= pow; + } + encoder.encodeBinBypass(0); + for (sufLen--; sufLen >= 0; sufLen--) + encoder.encodeBinBypass((absLev >> sufLen) & 1); + } + } + } + + public void initModels(int[][] cm, SliceType sliceType, int cabacIdc, int sliceQp) { + // System.out.println("INIT slice qp: "+sliceQp+", cabac init ids: "+cabacIdc+", slicetype : " + // + sliceType); + int[] tabA = sliceType.isIntra() ? CABACContst.cabac_context_init_I_A + : CABACContst.cabac_context_init_PB_A[cabacIdc]; + int[] tabB = sliceType.isIntra() ? CABACContst.cabac_context_init_I_B + : CABACContst.cabac_context_init_PB_B[cabacIdc]; + + for (int i = 0; i < 1024; i++) { + int preCtxState = clip(((tabA[i] * clip(sliceQp, 0, 51)) >> 4) + tabB[i], 1, 126); + if (preCtxState <= 63) { + cm[0][i] = 63 - preCtxState; + cm[1][i] = 0; + } else { + cm[0][i] = preCtxState - 64; + cm[1][i] = 1; + } + } + } + + public int readMBTypeI(MDecoder decoder, MBType left, MBType top, boolean leftAvailable, boolean topAvailable) { + int ctx = 3; + ctx += !leftAvailable || left == I_NxN ? 0 : 1; + ctx += !topAvailable || top == I_NxN ? 0 : 1; + + if (decoder.decodeBin(ctx) == 0) { + return 0; + } else { + return decoder.decodeFinalBin() == 1 ? 25 : 1 + readMBType16x16(decoder); + } + } + + private int readMBType16x16(MDecoder decoder) { + int type = decoder.decodeBin(6) * 12; + if (decoder.decodeBin(7) == 0) { + return type + (decoder.decodeBin(9) << 1) + decoder.decodeBin(10); + } else { + return type + (decoder.decodeBin(8) << 2) + (decoder.decodeBin(9) << 1) + decoder.decodeBin(10) + 4; + } + } + + public int readMBTypeP(MDecoder decoder) { + + if (decoder.decodeBin(14) == 1) { + return 5 + readIntraP(decoder, 17); + } else { + if (decoder.decodeBin(15) == 0) { + return decoder.decodeBin(16) == 0 ? 0 : 3; + } else { + return decoder.decodeBin(17) == 0 ? 2 : 1; + } + } + } + + private int readIntraP(MDecoder decoder, int ctxOff) { + if (decoder.decodeBin(ctxOff) == 0) { + return 0; + } else { + return decoder.decodeFinalBin() == 1 ? 25 : 1 + readMBType16x16P(decoder, ctxOff); + } + } + + private int readMBType16x16P(MDecoder decoder, int ctxOff) { + ctxOff++; + int type = decoder.decodeBin(ctxOff) * 12; + ctxOff++; + if (decoder.decodeBin(ctxOff) == 0) { + ctxOff++; + return type + (decoder.decodeBin(ctxOff) << 1) + decoder.decodeBin(ctxOff); + } else { + return type + (decoder.decodeBin(ctxOff) << 2) + (decoder.decodeBin(ctxOff + 1) << 1) + + decoder.decodeBin(ctxOff + 1) + 4; + } + } + + public int readMBTypeB(MDecoder mDecoder, MBType left, MBType top, boolean leftAvailable, boolean topAvailable) { + int ctx = 27; + ctx += !leftAvailable || left == null || left == B_Direct_16x16 ? 0 : 1; + ctx += !topAvailable || top == null || top == B_Direct_16x16 ? 0 : 1; + + if (mDecoder.decodeBin(ctx) == 0) + return 0; // B Direct + if (mDecoder.decodeBin(30) == 0) + return 1 + mDecoder.decodeBin(32); + + int b1 = mDecoder.decodeBin(31); + if (b1 == 0) { + return 3 + ((mDecoder.decodeBin(32) << 2) | (mDecoder.decodeBin(32) << 1) | mDecoder.decodeBin(32)); + } else { + if (mDecoder.decodeBin(32) == 0) { + return 12 + ((mDecoder.decodeBin(32) << 2) | (mDecoder.decodeBin(32) << 1) | mDecoder.decodeBin(32)); + } else { + switch ((mDecoder.decodeBin(32) << 1) + mDecoder.decodeBin(32)) { + case 0: + return 20 + mDecoder.decodeBin(32); + case 1: + return 23 + readIntraP(mDecoder, 32); + case 2: + return 11; + case 3: + return 22; + } + } + } + + return 0; + } + + public void writeMBTypeI(MEncoder encoder, MBType left, MBType top, boolean leftAvailable, boolean topAvailable, + int mbType) { + int ctx = 3; + ctx += !leftAvailable || left == I_NxN ? 0 : 1; + ctx += !topAvailable || top == I_NxN ? 0 : 1; + + if (mbType == 0) + encoder.encodeBin(ctx, 0); + else { + encoder.encodeBin(ctx, 1); + if (mbType == 25) + encoder.encodeBinFinal(1); + else { + encoder.encodeBinFinal(0); + writeMBType16x16(encoder, mbType - 1); + } + } + } + + private void writeMBType16x16(MEncoder encoder, int mbType) { + if (mbType < 12) { + encoder.encodeBin(6, 0); + } else { + encoder.encodeBin(6, 1); + mbType -= 12; + } + if (mbType < 4) { + encoder.encodeBin(7, 0); + encoder.encodeBin(9, mbType >> 1); + encoder.encodeBin(10, mbType & 1); + } else { + mbType -= 4; + encoder.encodeBin(7, 1); + encoder.encodeBin(8, mbType >> 2); + encoder.encodeBin(9, (mbType >> 1) & 1); + encoder.encodeBin(10, mbType & 1); + } + } + + public int readMBQpDelta(MDecoder decoder, MBType prevMbType) { + int ctx = 60; + ctx += prevMbType == null || prevMbType == I_PCM || (prevMbType != I_16x16 && prevCBP == 0) + || prevMbQpDelta == 0 ? 0 : 1; + + int val = 0; + if (decoder.decodeBin(ctx) == 1) { + val++; + if (decoder.decodeBin(62) == 1) { + val++; + while (decoder.decodeBin(63) == 1) + val++; + } + } + prevMbQpDelta = H264Utils2.golomb2Signed(val); + + return prevMbQpDelta; + } + + public void writeMBQpDelta(MEncoder encoder, MBType prevMbType, int mbQpDelta) { + int ctx = 60; + ctx += prevMbType == null || prevMbType == I_PCM || (prevMbType != I_16x16 && prevCBP == 0) + || prevMbQpDelta == 0 ? 0 : 1; + + prevMbQpDelta = mbQpDelta; + if (mbQpDelta-- == 0) + encoder.encodeBin(ctx, 0); + else { + encoder.encodeBin(ctx, 1); + if (mbQpDelta-- == 0) + encoder.encodeBin(62, 0); + else { + while (mbQpDelta-- > 0) + encoder.encodeBin(63, 1); + encoder.encodeBin(63, 0); + } + } + } + + public int readIntraChromaPredMode(MDecoder decoder, int mbX, MBType left, MBType top, boolean leftAvailable, + boolean topAvailable) { + int ctx = 64; + ctx += !leftAvailable || left == null || !left.isIntra() || chromaPredModeLeft == 0 ? 0 : 1; + ctx += !topAvailable || top == null || !top.isIntra() || chromaPredModeTop[mbX] == 0 ? 0 : 1; + int mode; + if (decoder.decodeBin(ctx) == 0) + mode = 0; + else if (decoder.decodeBin(67) == 0) + mode = 1; + else if (decoder.decodeBin(67) == 0) + mode = 2; + else + mode = 3; + chromaPredModeLeft = chromaPredModeTop[mbX] = mode; + + return mode; + } + + public void writeIntraChromaPredMode(MEncoder encoder, int mbX, MBType left, MBType top, boolean leftAvailable, + boolean topAvailable, int mode) { + int ctx = 64; + ctx += !leftAvailable || !left.isIntra() || chromaPredModeLeft == 0 ? 0 : 1; + ctx += !topAvailable || !top.isIntra() || chromaPredModeTop[mbX] == 0 ? 0 : 1; + encoder.encodeBin(ctx, mode-- == 0 ? 0 : 1); + for (int i = 0; mode >= 0 && i < 2; i++) + encoder.encodeBin(67, mode-- == 0 ? 0 : 1); + chromaPredModeLeft = chromaPredModeTop[mbX] = mode; + } + + public int condTerm(MBType mbCur, boolean nAvb, MBType mbN, boolean nBlkAvb, int cbpN) { + if (!nAvb) + return mbCur.isIntra() ? 1 : 0; + if (mbN == I_PCM) + return 1; + if (!nBlkAvb) + return 0; + return cbpN; + } + + public int readCodedBlockFlagLumaDC(MDecoder decoder, int mbX, MBType left, MBType top, boolean leftAvailable, + boolean topAvailable, MBType cur) { + int tLeft = condTerm(cur, leftAvailable, left, left == I_16x16, codedBlkDCLeft[0]); + int tTop = condTerm(cur, topAvailable, top, top == I_16x16, codedBlkDCTop[0][mbX]); + + int decoded = decoder.decodeBin(LUMA_16_DC.codedBlockCtxOff + tLeft + 2 * tTop); + + codedBlkDCLeft[0] = decoded; + codedBlkDCTop[0][mbX] = decoded; + + return decoded; + } + + public int readCodedBlockFlagChromaDC(MDecoder decoder, int mbX, int comp, MBType left, MBType top, + boolean leftAvailable, boolean topAvailable, int leftCBPChroma, int topCBPChroma, MBType cur) { + int tLeft = condTerm(cur, leftAvailable, left, left != null && leftCBPChroma != 0, codedBlkDCLeft[comp]); + int tTop = condTerm(cur, topAvailable, top, top != null && topCBPChroma != 0, codedBlkDCTop[comp][mbX]); + + int decoded = decoder.decodeBin(CHROMA_DC.codedBlockCtxOff + tLeft + 2 * tTop); + + codedBlkDCLeft[comp] = decoded; + codedBlkDCTop[comp][mbX] = decoded; + + return decoded; + } + + public int readCodedBlockFlagLumaAC(MDecoder decoder, BlockType blkType, int blkX, int blkY, int comp, MBType left, + MBType top, boolean leftAvailable, boolean topAvailable, int leftCBPLuma, int topCBPLuma, int curCBPLuma, + MBType cur) { + int blkOffLeft = blkX & 3, blkOffTop = blkY & 3; + + int tLeft; + if (blkOffLeft == 0) + tLeft = condTerm(cur, leftAvailable, left, left != null && left != I_PCM && cbp(leftCBPLuma, 3, blkOffTop), + codedBlkLeft[comp][blkOffTop]); + else + tLeft = condTerm(cur, true, cur, cbp(curCBPLuma, blkOffLeft - 1, blkOffTop), codedBlkLeft[comp][blkOffTop]); + + int tTop; + if (blkOffTop == 0) + tTop = condTerm(cur, topAvailable, top, top != null && top != I_PCM && cbp(topCBPLuma, blkOffLeft, 3), + codedBlkTop[comp][blkX]); + else + tTop = condTerm(cur, true, cur, cbp(curCBPLuma, blkOffLeft, blkOffTop - 1), codedBlkTop[comp][blkX]); + + int decoded = decoder.decodeBin(blkType.codedBlockCtxOff + tLeft + 2 * tTop); + + codedBlkLeft[comp][blkOffTop] = decoded; + codedBlkTop[comp][blkX] = decoded; + + return decoded; + } + + public int readCodedBlockFlagLuma64(MDecoder decoder, int blkX, int blkY, int comp, MBType left, MBType top, + boolean leftAvailable, boolean topAvailable, int leftCBPLuma, int topCBPLuma, int curCBPLuma, MBType cur, + boolean is8x8Left, boolean is8x8Top) { + + int blkOffLeft = blkX & 3, blkOffTop = blkY & 3; + + int tLeft; + if (blkOffLeft == 0) + tLeft = condTerm(cur, leftAvailable, left, + left != null && left != I_PCM && is8x8Left && cbp(leftCBPLuma, 3, blkOffTop), + codedBlkLeft[comp][blkOffTop]); + else + tLeft = condTerm(cur, true, cur, cbp(curCBPLuma, blkOffLeft - 1, blkOffTop), codedBlkLeft[comp][blkOffTop]); + + int tTop; + if (blkOffTop == 0) + tTop = condTerm(cur, topAvailable, top, + top != null && top != I_PCM && is8x8Top && cbp(topCBPLuma, blkOffLeft, 3), codedBlkTop[comp][blkX]); + else + tTop = condTerm(cur, true, cur, cbp(curCBPLuma, blkOffLeft, blkOffTop - 1), codedBlkTop[comp][blkX]); + + int decoded = decoder.decodeBin(BlockType.LUMA_64.codedBlockCtxOff + tLeft + 2 * tTop); + + codedBlkLeft[comp][blkOffTop] = decoded; + codedBlkTop[comp][blkX] = decoded; + + return decoded; + } + + private boolean cbp(int cbpLuma, int blkX, int blkY) { + int x8x8 = (blkY & 2) + (blkX >> 1); + + return ((cbpLuma >> x8x8) & 1) == 1; + } + + public int readCodedBlockFlagChromaAC(MDecoder decoder, int blkX, int blkY, int comp, MBType left, MBType top, + boolean leftAvailable, boolean topAvailable, int leftCBPChroma, int topCBPChroma, MBType cur) { + int blkOffLeft = blkX & 1, blkOffTop = blkY & 1; + + int tLeft; + if (blkOffLeft == 0) + tLeft = condTerm(cur, leftAvailable, left, left != null && left != I_PCM && (leftCBPChroma & 2) != 0, + codedBlkLeft[comp][blkOffTop]); + else + tLeft = condTerm(cur, true, cur, true, codedBlkLeft[comp][blkOffTop]); + int tTop; + if (blkOffTop == 0) + tTop = condTerm(cur, topAvailable, top, top != null && top != I_PCM && (topCBPChroma & 2) != 0, + codedBlkTop[comp][blkX]); + else + tTop = condTerm(cur, true, cur, true, codedBlkTop[comp][blkX]); + + int decoded = decoder.decodeBin(CHROMA_AC.codedBlockCtxOff + tLeft + 2 * tTop); + + codedBlkLeft[comp][blkOffTop] = decoded; + codedBlkTop[comp][blkX] = decoded; + + return decoded; + } + + public boolean prev4x4PredModeFlag(MDecoder decoder) { + return decoder.decodeBin(68) == 1; + } + + public int rem4x4PredMode(MDecoder decoder) { + return decoder.decodeBin(69) | (decoder.decodeBin(69) << 1) | (decoder.decodeBin(69) << 2); + } + + public int codedBlockPatternIntra(MDecoder mDecoder, boolean leftAvailable, boolean topAvailable, int cbpLeft, + int cbpTop, MBType mbLeft, MBType mbTop) { + int cbp0 = mDecoder.decodeBin(73 + _condTerm(leftAvailable, mbLeft, (cbpLeft >> 1) & 1) + 2 + * _condTerm(topAvailable, mbTop, (cbpTop >> 2) & 1)); + int cbp1 = mDecoder.decodeBin(73 + (1 - cbp0) + 2 * _condTerm(topAvailable, mbTop, (cbpTop >> 3) & 1)); + int cbp2 = mDecoder.decodeBin(73 + _condTerm(leftAvailable, mbLeft, (cbpLeft >> 3) & 1) + 2 * (1 - cbp0)); + int cbp3 = mDecoder.decodeBin(73 + (1 - cbp2) + 2 * (1 - cbp1)); + + int cr0 = mDecoder.decodeBin(77 + condTermCr0(leftAvailable, mbLeft, cbpLeft >> 4) + 2 + * condTermCr0(topAvailable, mbTop, cbpTop >> 4)); + int cr1 = cr0 != 0 ? mDecoder.decodeBin(81 + condTermCr1(leftAvailable, mbLeft, cbpLeft >> 4) + 2 + * condTermCr1(topAvailable, mbTop, cbpTop >> 4)) : 0; + + return cbp0 | (cbp1 << 1) | (cbp2 << 2) | (cbp3 << 3) | (cr0 << 4) | (cr1 << 5); + } + + private int condTermCr0(boolean avb, MBType mbt, int cbpChroma) { + return avb && (mbt == I_PCM || mbt != null && cbpChroma != 0) ? 1 : 0; + } + + private int condTermCr1(boolean avb, MBType mbt, int cbpChroma) { + return avb && (mbt == I_PCM || mbt != null && (cbpChroma & 2) != 0) ? 1 : 0; + } + + private int _condTerm(boolean avb, MBType mbt, int cbp) { + return !avb || mbt == I_PCM || (mbt != null && cbp == 1) ? 0 : 1; + } + + public void setPrevCBP(int prevCBP) { + this.prevCBP = prevCBP; + } + + public int readMVD(MDecoder decoder, int comp, boolean leftAvailable, boolean topAvailable, MBType leftType, + MBType topType, PartPred leftPred, PartPred topPred, PartPred curPred, int mbX, int partX, int partY, + int partW, int partH, int list) { + int ctx = comp == 0 ? 40 : 47; + + int partAbsX = (mbX << 2) + partX; + + boolean predEqA = leftPred != null && leftPred != Direct + && (leftPred == Bi || leftPred == curPred || (curPred == Bi && H264Const.usesList(leftPred, list))); + boolean predEqB = topPred != null && topPred != Direct + && (topPred == Bi || topPred == curPred || (curPred == Bi && H264Const.usesList(topPred, list))); + + // prefix and suffix as given by UEG3 with signedValFlag=1, uCoff=9 + int absMvdComp = !leftAvailable || leftType == null || leftType.isIntra() || !predEqA ? 0 : Math + .abs(mvdLeft[list][comp][partY]); + absMvdComp += !topAvailable || topType == null || topType.isIntra() || !predEqB ? 0 : Math + .abs(mvdTop[list][comp][partAbsX]); + + int val, b = decoder.decodeBin(ctx + (absMvdComp < 3 ? 0 : (absMvdComp > 32 ? 2 : 1))); + for (val = 0; b != 0 && val < 8; val++) + b = decoder.decodeBin(Math.min(ctx + val + 3, ctx + 6)); + val += b; + + if (val != 0) { + if (val == 9) { + int log = 2, add = 0, sum = 0, leftover = 0; + do { + sum += leftover; + log++; + b = decoder.decodeBinBypass(); + leftover = 1 << log; + } while (b != 0); + + --log; + + for (; log >= 0; log--) { + add |= decoder.decodeBinBypass() << log; + } + val += add + sum; + } + + val = MathUtil.toSigned(val, -decoder.decodeBinBypass()); + } + + for (int i = 0; i < partW; i++) { + mvdTop[list][comp][partAbsX + i] = val; + } + for (int i = 0; i < partH; i++) { + mvdLeft[list][comp][partY + i] = val; + } + + return val; + } + + public int readRefIdx(MDecoder mDecoder, boolean leftAvailable, boolean topAvailable, MBType leftType, + MBType topType, PartPred leftPred, PartPred topPred, PartPred curPred, int mbX, int partX, int partY, + int partW, int partH, int list) { + int partAbsX = (mbX << 2) + partX; + + boolean predEqA = leftPred != null && leftPred != Direct + && (leftPred == Bi || leftPred == curPred || (curPred == Bi && H264Const.usesList(leftPred, list))); + boolean predEqB = topPred != null && topPred != Direct + && (topPred == Bi || topPred == curPred || (curPred == Bi && H264Const.usesList(topPred, list))); + + int ctA = !leftAvailable || leftType == null || leftType.isIntra() || !predEqA || refIdxLeft[list][partY] == 0 ? 0 + : 1; + int ctB = !topAvailable || topType == null || topType.isIntra() || !predEqB || refIdxTop[list][partAbsX] == 0 ? 0 + : 1; + int b0 = mDecoder.decodeBin(54 + ctA + 2 * ctB); + int val; + if (b0 == 0) + val = 0; + else { + int b1 = mDecoder.decodeBin(58); + if (b1 == 0) + val = 1; + else { + for (val = 2; mDecoder.decodeBin(59) == 1; val++) + ; + } + } + + for (int i = 0; i < partW; i++) { + refIdxTop[list][partAbsX + i] = val; + } + + for (int i = 0; i < partH; i++) { + refIdxLeft[list][partY + i] = val; + } + + return val; + } + + public boolean readMBSkipFlag(MDecoder mDecoder, SliceType slType, boolean leftAvailable, boolean topAvailable, + int mbX) { + int base = slType == SliceType.P ? 11 : 24; + + boolean ret = mDecoder.decodeBin(base + (leftAvailable && !skipFlagLeft ? 1 : 0) + + (topAvailable && !skipFlagsTop[mbX] ? 1 : 0)) == 1; + + skipFlagLeft = skipFlagsTop[mbX] = ret; + + return ret; + } + + public int readSubMbTypeP(MDecoder mDecoder) { + if (mDecoder.decodeBin(21) == 1) + return 0; + else if (mDecoder.decodeBin(22) == 0) + return 1; + else if (mDecoder.decodeBin(23) == 1) + return 2; + else + return 3; + } + + public int readSubMbTypeB(MDecoder mDecoder) { + if (mDecoder.decodeBin(36) == 0) + return 0; // direct + if (mDecoder.decodeBin(37) == 0) + return 1 + mDecoder.decodeBin(39); + if (mDecoder.decodeBin(38) == 0) + return 3 + (mDecoder.decodeBin(39) << 1) + mDecoder.decodeBin(39); + + if (mDecoder.decodeBin(39) == 0) + return 7 + (mDecoder.decodeBin(39) << 1) + mDecoder.decodeBin(39); + + return 11 + mDecoder.decodeBin(39); + } + + public boolean readTransform8x8Flag(MDecoder mDecoder, boolean leftAvailable, boolean topAvailable, + MBType leftType, MBType topType, boolean is8x8Left, boolean is8x8Top) { + int ctx = 399 + (leftAvailable && leftType != null && is8x8Left ? 1 : 0) + + (topAvailable && topType != null && is8x8Top ? 1 : 0); + return mDecoder.decodeBin(ctx) == 1; + } + + public void setCodedBlock(int blkX, int blkY) { + codedBlkLeft[0][blkY & 0x3] = codedBlkTop[0][blkX] = 1; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/CAVLC.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/CAVLC.java new file mode 100644 index 0000000..5041cec --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/CAVLC.java @@ -0,0 +1,341 @@ +package org.monte.media.impl.jcodec.codecs.h264.io; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const; +import org.monte.media.impl.jcodec.codecs.h264.io.model.MBType; +import org.monte.media.impl.jcodec.codecs.h264.io.model.PictureParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet; +import org.monte.media.impl.jcodec.common.io.BitReader; +import org.monte.media.impl.jcodec.common.io.BitWriter; +import org.monte.media.impl.jcodec.common.io.VLC; +import org.monte.media.impl.jcodec.common.model.ColorSpace; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readU; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readZeroBitCount; +import static org.monte.media.impl.jcodec.common.model.ColorSpace.YUV422; +import static org.monte.media.impl.jcodec.common.model.ColorSpace.YUV444; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Non-CABAC H.264 symbols read/write routines + * + * @author Jay Codec + */ +public class CAVLC { + + private ColorSpace color; + private VLC chromaDCVLC; + + private int[] tokensLeft; + private int[] tokensTop; + + private int mbWidth; + private int mbMask; + private int mbW; + private int mbH; + + public CAVLC(SeqParameterSet sps, PictureParameterSet pps, int mbW, int mbH) { + this(sps.chromaFormatIdc, sps.picWidthInMbsMinus1 + 1, mbW, mbH); + } + + private CAVLC(ColorSpace color, int mbWidth, int mbW, int mbH) { + this.color = color; + this.chromaDCVLC = codeTableChromaDC(); + this.mbWidth = mbWidth; + this.mbMask = (1 << mbH) - 1; + this.mbW = mbW; + this.mbH = mbH; + + tokensLeft = new int[4]; + tokensTop = new int[mbWidth << mbW]; + } + + public CAVLC fork() { + CAVLC ret = new CAVLC(color, mbWidth, mbW, mbH); + System.arraycopy(tokensLeft, 0, ret.tokensLeft, 0, tokensLeft.length); + System.arraycopy(tokensTop, 0, ret.tokensTop, 0, tokensTop.length); + return ret; + } + + public int writeACBlock(BitWriter out, int blkIndX, int blkIndY, MBType leftMBType, MBType topMBType, int[] coeff, + VLC[] totalZerosTab, int firstCoeff, int maxCoeff, int[] scan) { + VLC coeffTokenTab = getCoeffTokenVLCForLuma(blkIndX != 0, leftMBType, tokensLeft[blkIndY & mbMask], + blkIndY != 0, topMBType, tokensTop[blkIndX]); + + int coeffToken = writeBlockGen(out, coeff, totalZerosTab, firstCoeff, maxCoeff, scan, coeffTokenTab); + + tokensLeft[blkIndY & mbMask] = coeffToken; + tokensTop[blkIndX] = coeffToken; + + return coeffToken; + } + + public void writeChrDCBlock(BitWriter out, int[] coeff, VLC[] totalZerosTab, int firstCoeff, int maxCoeff, + int[] scan) { + writeBlockGen(out, coeff, totalZerosTab, firstCoeff, maxCoeff, scan, getCoeffTokenVLCForChromaDC()); + } + + public void writeLumaDCBlock(BitWriter out, int blkIndX, int blkIndY, MBType leftMBType, MBType topMBType, + int[] coeff, VLC[] totalZerosTab, int firstCoeff, int maxCoeff, int[] scan) { + VLC coeffTokenTab = getCoeffTokenVLCForLuma(blkIndX != 0, leftMBType, tokensLeft[blkIndY & mbMask], + blkIndY != 0, topMBType, tokensTop[blkIndX]); + + writeBlockGen(out, coeff, totalZerosTab, firstCoeff, maxCoeff, scan, coeffTokenTab); + } + + private int writeBlockGen(BitWriter out, int[] coeff, VLC[] totalZerosTab, int firstCoeff, int maxCoeff, + int[] scan, VLC coeffTokenTab) { + int trailingOnes = 0, totalCoeff = 0, totalZeros = 0; + int[] runBefore = new int[maxCoeff]; + int[] levels = new int[maxCoeff]; + for (int i = 0; i < maxCoeff; i++) { + int c = coeff[scan[i + firstCoeff]]; + if (c == 0) { + runBefore[totalCoeff]++; + totalZeros++; + } else { + levels[totalCoeff++] = c; + } + } + if (totalCoeff < maxCoeff) + totalZeros -= runBefore[totalCoeff]; + + for (trailingOnes = 0; trailingOnes < totalCoeff && trailingOnes < 3 + && Math.abs(levels[totalCoeff - trailingOnes - 1]) == 1; trailingOnes++) + ; + + int coeffToken = H264Const.coeffToken(totalCoeff, trailingOnes); + + coeffTokenTab.writeVLC(out, coeffToken); + + if (totalCoeff > 0) { + writeTrailingOnes(out, levels, totalCoeff, trailingOnes); + writeLevels(out, levels, totalCoeff, trailingOnes); + + if (totalCoeff < maxCoeff) { + totalZerosTab[totalCoeff - 1].writeVLC(out, totalZeros); + writeRuns(out, runBefore, totalCoeff, totalZeros); + } + } + return coeffToken; + } + + private void writeTrailingOnes(BitWriter out, int[] levels, int totalCoeff, int trailingOne) { + for (int i = totalCoeff - 1; i >= totalCoeff - trailingOne; i--) { + out.write1Bit(levels[i] >>> 31); + } + } + + private void writeLevels(BitWriter out, int[] levels, int totalCoeff, int trailingOnes) { + + int suffixLen = totalCoeff > 10 && trailingOnes < 3 ? 1 : 0; + for (int i = totalCoeff - trailingOnes - 1; i >= 0; i--) { + int absLev = unsigned(levels[i]); + if (i == totalCoeff - trailingOnes - 1 && trailingOnes < 3) + absLev -= 2; + + int prefix = absLev >> suffixLen; + if (suffixLen == 0 && prefix < 14 || suffixLen > 0 && prefix < 15) { + out.writeNBit(1, prefix + 1); + out.writeNBit(absLev, suffixLen); + } else if (suffixLen == 0 && absLev < 30) { + out.writeNBit(1, 15); + out.writeNBit(absLev - 14, 4); + } else { + if (suffixLen == 0) + absLev -= 15; + int len, code; + for (len = 12; (code = absLev - (len + 3 << suffixLen) - (1 << len) + 4096) >= (1 << len); len++) + ; + out.writeNBit(1, len + 4); + out.writeNBit(code, len); + int mask = (1 << len) - 1; + } + if (suffixLen == 0) + suffixLen = 1; + if (MathUtil.abs(levels[i]) > (3 << (suffixLen - 1)) && suffixLen < 6) + suffixLen++; + } + } + + private final int unsigned(int signed) { + int sign = signed >>> 31; + int s = signed >> 31; + + return (((signed ^ s) - s) << 1) + sign - 2; + } + + private void writeRuns(BitWriter out, int[] run, int totalCoeff, int totalZeros) { + for (int i = totalCoeff - 1; i > 0 && totalZeros > 0; i--) { + H264Const.run[Math.min(6, totalZeros - 1)].writeVLC(out, run[i]); + totalZeros -= run[i]; + } + } + + public VLC getCoeffTokenVLCForLuma(boolean leftAvailable, MBType leftMBType, int leftToken, boolean topAvailable, + MBType topMBType, int topToken) { + + int nc = codeTableLuma(leftAvailable, leftMBType, leftToken, topAvailable, topMBType, topToken); + + return H264Const.CoeffToken[Math.min(nc, 8)]; + } + + public VLC getCoeffTokenVLCForChromaDC() { + return chromaDCVLC; + } + + protected int codeTableLuma(boolean leftAvailable, MBType leftMBType, int leftToken, boolean topAvailable, + MBType topMBType, int topToken) { + + int nA = leftMBType == null ? 0 : totalCoeff(leftToken); + int nB = topMBType == null ? 0 : totalCoeff(topToken); + + if (leftAvailable && topAvailable) + return (nA + nB + 1) >> 1; + else if (leftAvailable) + return nA; + else if (topAvailable) + return nB; + else + return 0; + } + + protected VLC codeTableChromaDC() { + if (color == ColorSpace.YUV420J) { + return H264Const.coeffTokenChromaDCY420; + } else if (color == YUV422) { + return H264Const.coeffTokenChromaDCY422; + } else if (color == YUV444) { + return H264Const.CoeffToken[0]; + } + return null; + } + + public int readCoeffs(BitReader _in, VLC coeffTokenTab, VLC[] totalZerosTab, int[] coeffLevel, int firstCoeff, + int nCoeff, int[] zigzag) { + int coeffToken = coeffTokenTab.readVLC(_in); + int totalCoeff = totalCoeff(coeffToken); + int trailingOnes = trailingOnes(coeffToken); + + if (totalCoeff > 0) { + int suffixLength = totalCoeff > 10 && trailingOnes < 3 ? 1 : 0; + + int[] level = new int[totalCoeff]; + int i; + for (i = 0; i < trailingOnes; i++) { + int read1Bit = _in.read1Bit(); + level[i] = 1 - 2 * read1Bit; + } + + for (; i < totalCoeff; i++) { + int level_prefix = readZeroBitCount(_in, ""); + int levelSuffixSize = suffixLength; + if (level_prefix == 14 && suffixLength == 0) + levelSuffixSize = 4; + if (level_prefix >= 15) + levelSuffixSize = level_prefix - 3; + + int levelCode = (Min(15, level_prefix) << suffixLength); + if (levelSuffixSize > 0) { + int level_suffix = readU(_in, levelSuffixSize, "RB: level_suffix"); + levelCode += level_suffix; + } + if (level_prefix >= 15 && suffixLength == 0) + levelCode += 15; + if (level_prefix >= 16) + levelCode += (1 << (level_prefix - 3)) - 4096; + if (i == trailingOnes && trailingOnes < 3) + levelCode += 2; + + if (levelCode % 2 == 0) + level[i] = (levelCode + 2) >> 1; + else + level[i] = (-levelCode - 1) >> 1; + + if (suffixLength == 0) + suffixLength = 1; + if (Abs(level[i]) > (3 << (suffixLength - 1)) && suffixLength < 6) + suffixLength++; + } + + int zerosLeft; + if (totalCoeff < nCoeff) { + if (coeffLevel.length == 4) { + zerosLeft = H264Const.totalZeros4[totalCoeff - 1].readVLC(_in); + } else if (coeffLevel.length == 8) { + zerosLeft = H264Const.totalZeros8[totalCoeff - 1].readVLC(_in); + } else { + zerosLeft = H264Const.totalZeros16[totalCoeff - 1].readVLC(_in); + } + } else + zerosLeft = 0; + + int[] runs = new int[totalCoeff]; + int r; + for (r = 0; r < totalCoeff - 1 && zerosLeft > 0; r++) { + int run = H264Const.run[Math.min(6, zerosLeft - 1)].readVLC(_in); + zerosLeft -= run; + runs[r] = run; + } + runs[r] = zerosLeft; + + for (int j = totalCoeff - 1, cn = 0; j >= 0 && cn < nCoeff; j--, cn++) { + cn += runs[j]; + coeffLevel[zigzag[cn + firstCoeff]] = level[j]; + } + } + + return coeffToken; + } + + private static int Min(int i, int level_prefix) { + return i < level_prefix ? i : level_prefix; + } + + private static int Abs(int i) { + return i < 0 ? -i : i; + } + + public static final int totalCoeff(int coeffToken) { + return coeffToken >> 4; + } + + public static final int trailingOnes(int coeffToken) { + return coeffToken & 0xf; + } + + public static int[] NO_ZIGZAG = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + + public void readChromaDCBlock(BitReader reader, int[] coeff, boolean leftAvailable, boolean topAvailable) { + VLC coeffTokenTab = getCoeffTokenVLCForChromaDC(); + + readCoeffs(reader, coeffTokenTab, coeff.length == 16 ? H264Const.totalZeros16 + : (coeff.length == 8 ? H264Const.totalZeros8 : H264Const.totalZeros4), coeff, 0, coeff.length, + NO_ZIGZAG); + } + + public void readLumaDCBlock(BitReader reader, int[] coeff, int mbX, boolean leftAvailable, MBType leftMbType, + boolean topAvailable, MBType topMbType, int[] zigzag4x4) { + VLC coeffTokenTab = getCoeffTokenVLCForLuma(leftAvailable, leftMbType, tokensLeft[0], topAvailable, topMbType, + tokensTop[mbX << 2]); + + readCoeffs(reader, coeffTokenTab, H264Const.totalZeros16, coeff, 0, 16, zigzag4x4); + } + + public int readACBlock(BitReader reader, int[] coeff, int blkIndX, int blkIndY, boolean leftAvailable, + MBType leftMbType, boolean topAvailable, MBType topMbType, int firstCoeff, int nCoeff, int[] zigzag4x4) { + VLC coeffTokenTab = getCoeffTokenVLCForLuma(leftAvailable, leftMbType, tokensLeft[blkIndY & mbMask], + topAvailable, topMbType, tokensTop[blkIndX]); + + int readCoeffs = readCoeffs(reader, coeffTokenTab, H264Const.totalZeros16, coeff, firstCoeff, nCoeff, zigzag4x4); + tokensLeft[blkIndY & mbMask] = tokensTop[blkIndX] = readCoeffs; + + return totalCoeff(readCoeffs); + } + + public void setZeroCoeff(int blkIndX, int blkIndY) { + tokensLeft[blkIndY & mbMask] = tokensTop[blkIndX] = 0; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/AspectRatio.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/AspectRatio.java new file mode 100644 index 0000000..bca2cb9 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/AspectRatio.java @@ -0,0 +1,33 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Aspect ratio + *

+ * dynamic enum + * + * @author The JCodec project + */ +public class AspectRatio { + + public static final AspectRatio Extended_SAR = new AspectRatio(255); + + private int value; + + private AspectRatio(int value) { + this.value = value; + } + + public static AspectRatio fromValue(int value) { + if (value == Extended_SAR.value) { + return Extended_SAR; + } + return new AspectRatio(value); + } + + public int getValue() { + return value; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/Frame.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/Frame.java new file mode 100644 index 0000000..4912062 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/Frame.java @@ -0,0 +1,125 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +import org.monte.media.impl.jcodec.codecs.h264.H264Utils.MvList2D; +import org.monte.media.impl.jcodec.common.model.ColorSpace; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.common.model.Rect; + +import java.util.Comparator; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Picture extension with frame number, makes it easier to debug reordering + * + * @author The JCodec project + */ +public class Frame extends Picture { + private int frameNo; + private SliceType frameType; + private MvList2D mvs; + private Frame[][][] refsUsed; + private boolean shortTerm; + private int poc; + + public Frame(int width, int height, byte[][] data, ColorSpace color, Rect crop, int frameNo, SliceType frameType, + MvList2D mvs, Frame[][][] refsUsed, int poc) { + super(width, height, data, null, color, 0, crop); + this.frameNo = frameNo; + this.mvs = mvs; + this.refsUsed = refsUsed; + this.poc = poc; + shortTerm = true; + } + + public static Frame createFrame(Frame pic) { + Picture comp = pic.createCompatible(); + return new Frame(comp.getWidth(), comp.getHeight(), comp.getData(), comp.getColor(), pic.getCrop(), + pic.frameNo, pic.frameType, pic.mvs, pic.refsUsed, pic.poc); + } + + public Frame cropped() { + Picture cropped = super.cropped(); + return new Frame(cropped.getWidth(), cropped.getHeight(), cropped.getData(), cropped.getColor(), null, frameNo, + frameType, mvs, refsUsed, poc); + } + + public void copyFromFrame(Frame src) { + super.copyFrom(src); + this.frameNo = src.frameNo; + this.mvs = src.mvs; + this.shortTerm = src.shortTerm; + this.refsUsed = src.refsUsed; + this.poc = src.poc; + } + + /** + * Creates a cropped clone of this picture. + * + * @return + */ + public Frame cloneCropped() { + if (cropNeeded()) { + return cropped(); + } else { + Frame clone = createFrame(this); + clone.copyFrom(this); + return clone; + } + } + + public int getFrameNo() { + return frameNo; + } + + public MvList2D getMvs() { + return mvs; + } + + public boolean isShortTerm() { + return shortTerm; + } + + public void setShortTerm(boolean shortTerm) { + this.shortTerm = shortTerm; + } + + public int getPOC() { + return poc; + } + + public static Comparator POCAsc = new Comparator() { + public int compare(Frame o1, Frame o2) { + if (o1 == null && o2 == null) + return 0; + else if (o1 == null) + return 1; + else if (o2 == null) + return -1; + else + return o1.poc > o2.poc ? 1 : (o1.poc == o2.poc ? 0 : -1); + } + }; + + public static Comparator POCDesc = new Comparator() { + public int compare(Frame o1, Frame o2) { + if (o1 == null && o2 == null) + return 0; + else if (o1 == null) + return 1; + else if (o2 == null) + return -1; + else + return o1.poc < o2.poc ? 1 : (o1.poc == o2.poc ? 0 : -1); + } + }; + + public Frame[][][] getRefsUsed() { + return refsUsed; + } + + public SliceType getFrameType() { + return frameType; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/HRDParameters.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/HRDParameters.java new file mode 100644 index 0000000..c26d66a --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/HRDParameters.java @@ -0,0 +1,32 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +/** + * This class is part of JCodec ( www.jcodec.org ) + * This software is distributed under FreeBSD License + * + * @author The JCodec project + */ +public class HRDParameters { + + // cpb_cnt_minus1 + public int cpbCntMinus1; + // bit_rate_scale + public int bitRateScale; + // cpb_size_scale + public int cpbSizeScale; + // bit_rate_value_minus1 + public int[] bitRateValueMinus1; + // cpb_size_value_minus1 + public int[] cpbSizeValueMinus1; + // cbr_flag + public boolean[] cbrFlag; + // initial_cpb_removal_delay_length_minus1 + public int initialCpbRemovalDelayLengthMinus1; + // cpb_removal_delay_length_minus1 + public int cpbRemovalDelayLengthMinus1; + // dpb_output_delay_length_minus1 + public int dpbOutputDelayLengthMinus1; + // time_offset_length + public int timeOffsetLength; + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/MBType.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/MBType.java new file mode 100644 index 0000000..4f2e0b4 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/MBType.java @@ -0,0 +1,58 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public final class MBType { + + public final static MBType I_NxN = new MBType(true, 0); + public final static MBType I_16x16 = new MBType(true, 1); + public final static MBType I_PCM = new MBType(true, 25); + public final static MBType P_16x16 = new MBType(false, 0); + public final static MBType P_16x8 = new MBType(false, 1); + public final static MBType P_8x16 = new MBType(false, 2); + public final static MBType P_8x8 = new MBType(false, 3); + public final static MBType P_8x8ref0 = new MBType(false, 4); + public final static MBType B_Direct_16x16 = new MBType(false, 0); + public final static MBType B_L0_16x16 = new MBType(false, 1); + public final static MBType B_L1_16x16 = new MBType(false, 2); + public final static MBType B_Bi_16x16 = new MBType(false, 3); + public final static MBType B_L0_L0_16x8 = new MBType(false, 4); + public final static MBType B_L0_L0_8x16 = new MBType(false, 5); + public final static MBType B_L1_L1_16x8 = new MBType(false, 6); + public final static MBType B_L1_L1_8x16 = new MBType(false, 7); + public final static MBType B_L0_L1_16x8 = new MBType(false, 8); + public final static MBType B_L0_L1_8x16 = new MBType(false, 9); + public final static MBType B_L1_L0_16x8 = new MBType(false, 10); + public final static MBType B_L1_L0_8x16 = new MBType(false, 11); + public final static MBType B_L0_Bi_16x8 = new MBType(false, 12); + public final static MBType B_L0_Bi_8x16 = new MBType(false, 13); + public final static MBType B_L1_Bi_16x8 = new MBType(false, 14); + public final static MBType B_L1_Bi_8x16 = new MBType(false, 15); + public final static MBType B_Bi_L0_16x8 = new MBType(false, 16); + public final static MBType B_Bi_L0_8x16 = new MBType(false, 17); + public final static MBType B_Bi_L1_16x8 = new MBType(false, 18); + public final static MBType B_Bi_L1_8x16 = new MBType(false, 19); + public final static MBType B_Bi_Bi_16x8 = new MBType(false, 20); + public final static MBType B_Bi_Bi_8x16 = new MBType(false, 21); + public final static MBType B_8x8 = new MBType(false, 22); + + public boolean intra; + public int _code; + + private MBType(boolean intra, int code) { + this.intra = intra; + this._code = code; + } + + public boolean isIntra() { + return intra; + } + + public int code() { + return _code; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/NALReasemble.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/NALReasemble.java new file mode 100644 index 0000000..d0bfb8b --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/NALReasemble.java @@ -0,0 +1,202 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.List; + +public class NALReasemble { + + /** + * The mask for NAL header type. + */ + private static final int FU_TYPE_MASK = 0x1F; + + /** + * The mask for FU start & end bits. + */ + private static final int FU_STARTEND_MASK = 0xC0; + + /** + * The mask for FU start bit. + */ + private static final int FU_START_MASK = 0x80; + + /** + * The mask for FU end bit. + */ + private static final int FU_END_MASK = 0x40; + + /** + * The mask for NAL NRI. + */ + private static final int NRI_MASK = 0x60; + + /** + * The mask for NAL forbidden bit. + */ + private static final int FORBIDDEN_MASK = 0x80; + + // Important Notes for FU-A/B fragments: + // + // - A FU payload MAY have any number of octets and MAY be empty. [RFC3984-p30] + // + // - A fragmented NAL unit MUST NOT be transmitted in one FU; i.e., the + // Start bit and End bit MUST NOT both be set to one in the same FU + // header. [RFC3984-p30] + // + // - If a fragmentation unit is lost, the receiver SHOULD discard all + // following fragmentation units in transmission order corresponding to + // the same fragmented NAL unit. [RFC3984-p31] + // + // - A receiver in an endpoint or in a MANE MAY aggregate the first n-1 + // fragments of a NAL unit to an (incomplete) NAL unit, even if fragment + // n of that NAL unit is not received. In this case, the + // forbidden_zero_bit of the NAL unit MUST be set to one to indicate a + // syntax violation. [RFC3984-p31] + + + /** + * Defragment FU-A NALs into a single NAL. + *

+ * This method assumes the following are true: + *

+ * 1. The NALs presented in the list are CONTIGUOUS - i.e. + * any FU packets received have been reordered based on + * RTP sequence number AND any dropped packets been detected + * and the rest of the fragment NALs have NOT been sent on this + * list - i.e. there is no END packet. + * e.g. + * RTP:seq1 RTP:seq3 RTP:seq2 RTP:seq4 RTP:seq5 ---> { [FU-A-S], [FU-A:1], [FU-A:2] } + * [FU-A-S] [FU-A:2] [FU-A:1] X [FU-A-E] + *

+ * NB: If an issue is detected, the F bit will be set to 1 to indicate + * to the decoder that this NAL is suspicious. + *

+ * 2. The list of buffers passed into this method are ONLY related to the + * same Fragmentation Unit, i.e. ALL buffers will be used to build + * the resulting NAL. + *

+ * 3. Each buffer in the list is at position 0, with limit set the the + * length of the data that should be extracted - i.e. payload length. + *

+ * For FU-A, the following 2 bytes are seen in the payload of each NAL: + *

+ * | FU indicator | FU header | + * +---------------+---------------+ + * |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7| + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |F|NRI| TypeX |S|E|R| TypeY | + * +---------------+---------------+ + *

+ * The defragmented NAL takes it's NAL header data from the first FU-A payload + * as follows: + *

+ * | NAL header | + * +---------------+ + * |0|1|2|3|4|5|6|7| + * +-+-+-+-+-+-+-+-+ + * |F|NRI| TypeY | NB: F bit is SET if no end marker OR + * +---------------+ start and end marker seen in a packet. + *

+ * NB: If NO start element is seen, this method will return byte[0] + * THE RTP interface layer should have stripped missing data as per note 1 + * already. In this case, the data would never be sent to this method, as + * a missing FU-A-START would mean dropping all fragments, same as passing + * empty list. + * + * @param nals The list of ByteBuffer-based NALs that meet the requirements above. + * @return The (optional) byte[] represented the defragmented NAL - NB: data will be empty + * if the defragmentation failed due to bad data, BUT will be present if some data + * could be decoded but was not 'perfect' - in this instance the forbidden bit is set. + */ + public static byte[] defragmentFUANals(final List nals) { // JAVA 8 - Optional + + // Require a valid list of NALs to work on. + if (nals == null || nals.isEmpty()) { + return new byte[0]; + } + + // Peek at the first two bytes of the first NAL. + final ByteBuffer headNAL = nals.get(0); + final NALUnit nalUnit = NALUnit.read(headNAL); + headNAL.rewind(); + + final byte headFuIndicator = headNAL.get(); + final byte headFuHeader = headNAL.get(); + headNAL.rewind(); + + // Ensure that the first NAL is an FU-A-START ONLY (i.e. not STARt, or START & END are not allowed) + if (NALUnitType.FU_A != nalUnit.type || + (headFuHeader & FU_START_MASK) == 0 || (headFuHeader & FU_STARTEND_MASK) == FU_STARTEND_MASK) { + return new byte[0]; + } + + // The required data byte[] is the sum of (1 + (each buffer remaining - 2)) + // i.e. add 1 byte for NAL header, then add each FU length - FU Indicator and FU Header. + + // JAVA 8 -> byte[] data = new byte[1 + nals.stream().flatMapToInt(n -> IntStream.of(n.remaining() - 2)).sum()]; + int datasize = 1; + for (int i = 0; i < nals.size(); i++) { + datasize += nals.get(i).remaining() - 2; + } + + byte[] data = new byte[datasize]; + final ByteBuffer out = ByteBuffer.wrap(data); + + // Set the NAL header based on parts from the first FU indicator and header. + out.put((byte) ((headFuIndicator & NRI_MASK) | (headFuHeader & FU_TYPE_MASK))); + + int validUntilPosition = -1; + boolean shouldSetFbit = false; + for (int i = 0; i < nals.size(); i++) { + final ByteBuffer nal = nals.get(i); + nal.get(); // Skip first byte. + final byte fuHeader = nal.get(); + + // Detect any errors in the ordering. + if ((i > 0 && ((fuHeader & FU_START_MASK) == FU_START_MASK)) || + (i == nals.size() - 1 && ((fuHeader & FU_END_MASK) == 0))) { + // Found a start indicator AFTER first entry OR + // DID NOT find end indicator at last entry. + // NOT IDEAL, but carry on with defragment. + shouldSetFbit = true; + } + + if (nal.hasRemaining()) { + final byte[] buffer = new byte[nal.remaining()]; + nal.get(buffer); + out.put(buffer); + } + + if ((i != nals.size() - 1 && ((fuHeader & FU_END_MASK) == FU_END_MASK))) { + // Found end indicator NOT at last entry. + // Need to STOP defragmenting. + shouldSetFbit = true; + validUntilPosition = out.position(); + break; + } + } + + // If there was an issue with the data, set the F bit - this packet is probably bad. + // i.e. no FU-END, or FU-END seen but more packets in the list. + if (shouldSetFbit) { + if (validUntilPosition > -1) { + // If this is a special case for truncating the data, do that first. + data = Arrays.copyOf(data, data.length - (data.length - validUntilPosition)); + } + + data[0] = (byte) (FORBIDDEN_MASK | data[0]); + } + + return data; + } + + + /** + * Private constructor to prevent general instance creation. + */ + private NALReasemble() { + // Do nothing. + } + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/NALUnit.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/NALUnit.java new file mode 100644 index 0000000..65836f1 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/NALUnit.java @@ -0,0 +1,36 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +import java.nio.ByteBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Network abstraction layer (NAL) unit + * + * @author The JCodec project + */ +public class NALUnit { + + public NALUnitType type; + public int nal_ref_idc; + + public NALUnit(NALUnitType type, int nal_ref_idc) { + this.type = type; + this.nal_ref_idc = nal_ref_idc; + } + + public static NALUnit read(ByteBuffer _in) { + int nalu = _in.get() & 0xff; + int nal_ref_idc = (nalu >> 5) & 0x3; + int nb = nalu & 0x1f; + + NALUnitType type = NALUnitType.fromValue(nb); + return new NALUnit(type, nal_ref_idc); + } + + public void write(ByteBuffer out) { + int nalu = type.getValue() | (nal_ref_idc << 5); + out.put((byte) nalu); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/NALUnitType.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/NALUnitType.java new file mode 100644 index 0000000..8f49c78 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/NALUnitType.java @@ -0,0 +1,67 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * NAL unit type + * + * @author The JCodec project + */ +public final class NALUnitType { + public final static NALUnitType NON_IDR_SLICE = new NALUnitType(1, "NON_IDR_SLICE", "non IDR slice"); + public final static NALUnitType SLICE_PART_A = new NALUnitType(2, "SLICE_PART_A", "slice part a"); + public final static NALUnitType SLICE_PART_B = new NALUnitType(3, "SLICE_PART_B", "slice part b"); + public final static NALUnitType SLICE_PART_C = new NALUnitType(4, "SLICE_PART_C", "slice part c"); + public final static NALUnitType IDR_SLICE = new NALUnitType(5, "IDR_SLICE", "idr slice"); + public final static NALUnitType SEI = new NALUnitType(6, "SEI", "sei"); + public final static NALUnitType SPS = new NALUnitType(7, "SPS", "sequence parameter set"); + public final static NALUnitType PPS = new NALUnitType(8, "PPS", "picture parameter set"); + public final static NALUnitType ACC_UNIT_DELIM = new NALUnitType(9, "ACC_UNIT_DELIM", "access unit delimiter"); + public final static NALUnitType END_OF_SEQ = new NALUnitType(10, "END_OF_SEQ", "end of sequence"); + public final static NALUnitType END_OF_STREAM = new NALUnitType(11, "END_OF_STREAM", "end of stream"); + public final static NALUnitType FILLER_DATA = new NALUnitType(12, "FILLER_DATA", "filler data"); + public final static NALUnitType SEQ_PAR_SET_EXT = new NALUnitType(13, "SEQ_PAR_SET_EXT", "sequence parameter set extension"); + public final static NALUnitType AUX_SLICE = new NALUnitType(19, "AUX_SLICE", "auxilary slice"); + public final static NALUnitType FU_A = new NALUnitType(28, "FU_A", "fragmented unit a"); + + private final static NALUnitType[] lut; + private final static NALUnitType[] _values; + + static { + _values = new NALUnitType[]{NON_IDR_SLICE, SLICE_PART_A, SLICE_PART_B, SLICE_PART_C, IDR_SLICE, SEI, SPS, PPS, + ACC_UNIT_DELIM, END_OF_SEQ, END_OF_STREAM, FILLER_DATA, SEQ_PAR_SET_EXT, AUX_SLICE, FU_A}; + lut = new NALUnitType[256]; + for (int i = 0; i < _values.length; i++) { + NALUnitType nalUnitType = _values[i]; + lut[nalUnitType.value] = nalUnitType; + } + } + + private final int value; + private final String displayName; + private String _name; + + private NALUnitType(int value, String name, String displayName) { + this.value = value; + this._name = name; + this.displayName = displayName; + } + + public String getName() { + return displayName; + } + + public int getValue() { + return value; + } + + public static NALUnitType fromValue(int value) { + return value > 0 && value < lut.length ? lut[value] : null; + } + + @Override + public String toString() { + return _name; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/PictureParameterSet.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/PictureParameterSet.java new file mode 100644 index 0000000..a25c724 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/PictureParameterSet.java @@ -0,0 +1,419 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +import org.monte.media.impl.jcodec.common.io.BitReader; +import org.monte.media.impl.jcodec.common.io.BitWriter; +import org.monte.media.impl.jcodec.platform.Platform; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.moreRBSPData; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readBool; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readNBit; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readSE; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readU; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readUEtrace; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeBool; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeNBit; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeSEtrace; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeTrailingBits; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeU; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeUEtrace; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Picture Parameter Set entity of H264 bitstream + *

+ * capable to serialize / deserialize with CAVLC bitstream + * + * @author The JCodec project + */ +public class PictureParameterSet { + + public static class PPSExt { + public boolean transform8x8ModeFlag; + public int[][] scalingMatrix; + public int secondChromaQpIndexOffset; + + public boolean isTransform8x8ModeFlag() { + // transform_8x8_mode_flag + return transform8x8ModeFlag; + } + + public int[][] getScalingMatrix() { + return scalingMatrix; + } + + public int getSecondChromaQpIndexOffset() { + // second_chroma_qp_index_offset + return secondChromaQpIndexOffset; + } + } + + // entropy_coding_mode_flag + public boolean entropyCodingModeFlag; + // num_ref_idx_active_minus1 + public int[] numRefIdxActiveMinus1; + // slice_group_change_rate_minus1 + public int sliceGroupChangeRateMinus1; + // pic_parameter_set_id + public int picParameterSetId; + // seq_parameter_set_id + public int seqParameterSetId; + // pic_order_present_flag + public boolean picOrderPresentFlag; + // num_slice_groups_minus1 + public int numSliceGroupsMinus1; + // slice_group_map_type + public int sliceGroupMapType; + // weighted_pred_flag + public boolean weightedPredFlag; + // weighted_bipred_idc + public int weightedBipredIdc; + // pic_init_qp_minus26 + public int picInitQpMinus26; + // pic_init_qs_minus26 + public int picInitQsMinus26; + // chroma_qp_index_offset + public int chromaQpIndexOffset; + // deblocking_filter_control_present_flag + public boolean deblockingFilterControlPresentFlag; + // constrained_intra_pred_flag + public boolean constrainedIntraPredFlag; + // redundant_pic_cnt_present_flag + public boolean redundantPicCntPresentFlag; + // top_left + public int[] topLeft; + // bottom_right + public int[] bottomRight; + // run_length_minus1 + public int[] runLengthMinus1; + // slice_group_change_direction_flag + public boolean sliceGroupChangeDirectionFlag; + // slice_group_id + public int[] sliceGroupId; + public PPSExt extended; + + public PictureParameterSet() { + this.numRefIdxActiveMinus1 = new int[2]; + } + + public static PictureParameterSet read(ByteBuffer is) { + BitReader _in = BitReader.createBitReader(is); + PictureParameterSet pps = new PictureParameterSet(); + + pps.picParameterSetId = readUEtrace(_in, "PPS: pic_parameter_set_id"); + pps.seqParameterSetId = readUEtrace(_in, "PPS: seq_parameter_set_id"); + pps.entropyCodingModeFlag = readBool(_in, "PPS: entropy_coding_mode_flag"); + pps.picOrderPresentFlag = readBool(_in, "PPS: pic_order_present_flag"); + pps.numSliceGroupsMinus1 = readUEtrace(_in, "PPS: num_slice_groups_minus1"); + if (pps.numSliceGroupsMinus1 > 0) { + pps.sliceGroupMapType = readUEtrace(_in, "PPS: slice_group_map_type"); + pps.topLeft = new int[pps.numSliceGroupsMinus1 + 1]; + pps.bottomRight = new int[pps.numSliceGroupsMinus1 + 1]; + pps.runLengthMinus1 = new int[pps.numSliceGroupsMinus1 + 1]; + if (pps.sliceGroupMapType == 0) + for (int iGroup = 0; iGroup <= pps.numSliceGroupsMinus1; iGroup++) + pps.runLengthMinus1[iGroup] = readUEtrace(_in, "PPS: run_length_minus1"); + else if (pps.sliceGroupMapType == 2) + for (int iGroup = 0; iGroup < pps.numSliceGroupsMinus1; iGroup++) { + pps.topLeft[iGroup] = readUEtrace(_in, "PPS: top_left"); + pps.bottomRight[iGroup] = readUEtrace(_in, "PPS: bottom_right"); + } + else if (pps.sliceGroupMapType == 3 || pps.sliceGroupMapType == 4 || pps.sliceGroupMapType == 5) { + pps.sliceGroupChangeDirectionFlag = readBool(_in, "PPS: slice_group_change_direction_flag"); + pps.sliceGroupChangeRateMinus1 = readUEtrace(_in, "PPS: slice_group_change_rate_minus1"); + } else if (pps.sliceGroupMapType == 6) { + int NumberBitsPerSliceGroupId; + if (pps.numSliceGroupsMinus1 + 1 > 4) + NumberBitsPerSliceGroupId = 3; + else if (pps.numSliceGroupsMinus1 + 1 > 2) + NumberBitsPerSliceGroupId = 2; + else + NumberBitsPerSliceGroupId = 1; + int pic_size_in_map_units_minus1 = readUEtrace(_in, "PPS: pic_size_in_map_units_minus1"); + pps.sliceGroupId = new int[pic_size_in_map_units_minus1 + 1]; + for (int i = 0; i <= pic_size_in_map_units_minus1; i++) { + pps.sliceGroupId[i] = readU(_in, NumberBitsPerSliceGroupId, "PPS: slice_group_id [" + i + "]f"); + } + } + } + pps.numRefIdxActiveMinus1 = new int[]{readUEtrace(_in, "PPS: num_ref_idx_l0_active_minus1"), readUEtrace(_in, "PPS: num_ref_idx_l1_active_minus1")}; + pps.weightedPredFlag = readBool(_in, "PPS: weighted_pred_flag"); + pps.weightedBipredIdc = readNBit(_in, 2, "PPS: weighted_bipred_idc"); + pps.picInitQpMinus26 = readSE(_in, "PPS: pic_init_qp_minus26"); + pps.picInitQsMinus26 = readSE(_in, "PPS: pic_init_qs_minus26"); + pps.chromaQpIndexOffset = readSE(_in, "PPS: chroma_qp_index_offset"); + pps.deblockingFilterControlPresentFlag = readBool(_in, "PPS: deblocking_filter_control_present_flag"); + pps.constrainedIntraPredFlag = readBool(_in, "PPS: constrained_intra_pred_flag"); + pps.redundantPicCntPresentFlag = readBool(_in, "PPS: redundant_pic_cnt_present_flag"); + if (moreRBSPData(_in)) { + pps.extended = new PictureParameterSet.PPSExt(); + pps.extended.transform8x8ModeFlag = readBool(_in, "PPS: transform_8x8_mode_flag"); + boolean pic_scaling_matrix_present_flag = readBool(_in, "PPS: pic_scaling_matrix_present_flag"); + if (pic_scaling_matrix_present_flag) { + pps.extended.scalingMatrix = new int[8][]; + for (int i = 0; i < 6 + 2 * (pps.extended.transform8x8ModeFlag ? 1 : 0); i++) { + int scalingListSize = i < 6 ? 16 : 64; + if (readBool(_in, "PPS: pic_scaling_list_present_flag")) { + pps.extended.scalingMatrix[i] = SeqParameterSet.readScalingList(_in, scalingListSize); + } + } + } + pps.extended.secondChromaQpIndexOffset = readSE(_in, "PPS: second_chroma_qp_index_offset"); + } + + return pps; + } + + public void write(ByteBuffer out) { + BitWriter writer = new BitWriter(out); + + writeUEtrace(writer, picParameterSetId, "PPS: pic_parameter_set_id"); + writeUEtrace(writer, seqParameterSetId, "PPS: seq_parameter_set_id"); + writeBool(writer, entropyCodingModeFlag, "PPS: entropy_coding_mode_flag"); + writeBool(writer, picOrderPresentFlag, "PPS: pic_order_present_flag"); + writeUEtrace(writer, numSliceGroupsMinus1, "PPS: num_slice_groups_minus1"); + if (numSliceGroupsMinus1 > 0) { + writeUEtrace(writer, sliceGroupMapType, "PPS: slice_group_map_type"); + if (sliceGroupMapType == 0) { + for (int iGroup = 0; iGroup <= numSliceGroupsMinus1; iGroup++) { + writeUEtrace(writer, runLengthMinus1[iGroup], "PPS: "); + } + } else if (sliceGroupMapType == 2) { + for (int iGroup = 0; iGroup < numSliceGroupsMinus1; iGroup++) { + writeUEtrace(writer, topLeft[iGroup], "PPS: "); + writeUEtrace(writer, bottomRight[iGroup], "PPS: "); + } + } else if (sliceGroupMapType == 3 || sliceGroupMapType == 4 || sliceGroupMapType == 5) { + writeBool(writer, sliceGroupChangeDirectionFlag, "PPS: slice_group_change_direction_flag"); + writeUEtrace(writer, sliceGroupChangeRateMinus1, "PPS: slice_group_change_rate_minus1"); + } else if (sliceGroupMapType == 6) { + int NumberBitsPerSliceGroupId; + if (numSliceGroupsMinus1 + 1 > 4) + NumberBitsPerSliceGroupId = 3; + else if (numSliceGroupsMinus1 + 1 > 2) + NumberBitsPerSliceGroupId = 2; + else + NumberBitsPerSliceGroupId = 1; + writeUEtrace(writer, sliceGroupId.length, "PPS: "); + for (int i = 0; i <= sliceGroupId.length; i++) { + writeU(writer, sliceGroupId[i], NumberBitsPerSliceGroupId); + } + } + } + writeUEtrace(writer, numRefIdxActiveMinus1[0], "PPS: num_ref_idx_l0_active_minus1"); + writeUEtrace(writer, numRefIdxActiveMinus1[1], "PPS: num_ref_idx_l1_active_minus1"); + writeBool(writer, weightedPredFlag, "PPS: weighted_pred_flag"); + writeNBit(writer, weightedBipredIdc, 2, "PPS: weighted_bipred_idc"); + writeSEtrace(writer, picInitQpMinus26, "PPS: pic_init_qp_minus26"); + writeSEtrace(writer, picInitQsMinus26, "PPS: pic_init_qs_minus26"); + writeSEtrace(writer, chromaQpIndexOffset, "PPS: chroma_qp_index_offset"); + writeBool(writer, deblockingFilterControlPresentFlag, "PPS: deblocking_filter_control_present_flag"); + writeBool(writer, constrainedIntraPredFlag, "PPS: constrained_intra_pred_flag"); + writeBool(writer, redundantPicCntPresentFlag, "PPS: redundant_pic_cnt_present_flag"); + if (extended != null) { + writeBool(writer, extended.transform8x8ModeFlag, "PPS: transform_8x8_mode_flag"); + writeBool(writer, extended.scalingMatrix != null, "PPS: scalindMatrix"); + if (extended.scalingMatrix != null) { + for (int i = 0; i < 6 + 2 * (extended.transform8x8ModeFlag ? 1 : 0); i++) { + writeBool(writer, extended.scalingMatrix[i] != null, "PPS: "); + if (extended.scalingMatrix[i] != null) { + SeqParameterSet.writeScalingList(writer, extended.scalingMatrix, i); + } + } + } + writeSEtrace(writer, extended.secondChromaQpIndexOffset, "PPS: "); + } + + writeTrailingBits(writer); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(bottomRight); + result = prime * result + chromaQpIndexOffset; + result = prime * result + (constrainedIntraPredFlag ? 1231 : 1237); + result = prime * result + (deblockingFilterControlPresentFlag ? 1231 : 1237); + result = prime * result + (entropyCodingModeFlag ? 1231 : 1237); + result = prime * result + ((extended == null) ? 0 : extended.hashCode()); + result = prime * result + numRefIdxActiveMinus1[0]; + result = prime * result + numRefIdxActiveMinus1[1]; + result = prime * result + numSliceGroupsMinus1; + result = prime * result + picInitQpMinus26; + result = prime * result + picInitQsMinus26; + result = prime * result + (picOrderPresentFlag ? 1231 : 1237); + result = prime * result + picParameterSetId; + result = prime * result + (redundantPicCntPresentFlag ? 1231 : 1237); + result = prime * result + Arrays.hashCode(runLengthMinus1); + result = prime * result + seqParameterSetId; + result = prime * result + (sliceGroupChangeDirectionFlag ? 1231 : 1237); + result = prime * result + sliceGroupChangeRateMinus1; + result = prime * result + Arrays.hashCode(sliceGroupId); + result = prime * result + sliceGroupMapType; + result = prime * result + Arrays.hashCode(topLeft); + result = prime * result + weightedBipredIdc; + result = prime * result + (weightedPredFlag ? 1231 : 1237); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PictureParameterSet other = (PictureParameterSet) obj; + if (!Platform.arrayEqualsInt(bottomRight, other.bottomRight)) + return false; + if (chromaQpIndexOffset != other.chromaQpIndexOffset) + return false; + if (constrainedIntraPredFlag != other.constrainedIntraPredFlag) + return false; + if (deblockingFilterControlPresentFlag != other.deblockingFilterControlPresentFlag) + return false; + if (entropyCodingModeFlag != other.entropyCodingModeFlag) + return false; + if (extended == null) { + if (other.extended != null) + return false; + } else if (!extended.equals(other.extended)) + return false; + if (numRefIdxActiveMinus1[0] != other.numRefIdxActiveMinus1[0]) + return false; + if (numRefIdxActiveMinus1[1] != other.numRefIdxActiveMinus1[1]) + return false; + if (numSliceGroupsMinus1 != other.numSliceGroupsMinus1) + return false; + if (picInitQpMinus26 != other.picInitQpMinus26) + return false; + if (picInitQsMinus26 != other.picInitQsMinus26) + return false; + if (picOrderPresentFlag != other.picOrderPresentFlag) + return false; + if (picParameterSetId != other.picParameterSetId) + return false; + if (redundantPicCntPresentFlag != other.redundantPicCntPresentFlag) + return false; + if (!Platform.arrayEqualsInt(runLengthMinus1, other.runLengthMinus1)) + return false; + if (seqParameterSetId != other.seqParameterSetId) + return false; + if (sliceGroupChangeDirectionFlag != other.sliceGroupChangeDirectionFlag) + return false; + if (sliceGroupChangeRateMinus1 != other.sliceGroupChangeRateMinus1) + return false; + if (!Platform.arrayEqualsInt(sliceGroupId, other.sliceGroupId)) + return false; + if (sliceGroupMapType != other.sliceGroupMapType) + return false; + if (!Platform.arrayEqualsInt(topLeft, other.topLeft)) + return false; + if (weightedBipredIdc != other.weightedBipredIdc) + return false; + if (weightedPredFlag != other.weightedPredFlag) + return false; + return true; + } + + public PictureParameterSet copy() { + ByteBuffer buf = ByteBuffer.allocate(2048); + write(buf); + buf.flip(); + return read(buf); + } + + public boolean isEntropyCodingModeFlag() { + return entropyCodingModeFlag; + } + + public int[] getNumRefIdxActiveMinus1() { + return numRefIdxActiveMinus1; + } + + public int getSliceGroupChangeRateMinus1() { + return sliceGroupChangeRateMinus1; + } + + public int getPicParameterSetId() { + return picParameterSetId; + } + + public int getSeqParameterSetId() { + return seqParameterSetId; + } + + public boolean isPicOrderPresentFlag() { + return picOrderPresentFlag; + } + + public int getNumSliceGroupsMinus1() { + return numSliceGroupsMinus1; + } + + public int getSliceGroupMapType() { + return sliceGroupMapType; + } + + public boolean isWeightedPredFlag() { + return weightedPredFlag; + } + + public int getWeightedBipredIdc() { + return weightedBipredIdc; + } + + public int getPicInitQpMinus26() { + return picInitQpMinus26; + } + + public int getPicInitQsMinus26() { + return picInitQsMinus26; + } + + public int getChromaQpIndexOffset() { + return chromaQpIndexOffset; + } + + public boolean isDeblockingFilterControlPresentFlag() { + return deblockingFilterControlPresentFlag; + } + + public boolean isConstrainedIntraPredFlag() { + return constrainedIntraPredFlag; + } + + public boolean isRedundantPicCntPresentFlag() { + return redundantPicCntPresentFlag; + } + + public int[] getTopLeft() { + return topLeft; + } + + public int[] getBottomRight() { + return bottomRight; + } + + public int[] getRunLengthMinus1() { + return runLengthMinus1; + } + + public boolean isSliceGroupChangeDirectionFlag() { + return sliceGroupChangeDirectionFlag; + } + + public int[] getSliceGroupId() { + return sliceGroupId; + } + + public PPSExt getExtended() { + return extended; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/PredictionWeightTable.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/PredictionWeightTable.java new file mode 100644 index 0000000..1c940bf --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/PredictionWeightTable.java @@ -0,0 +1,32 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class PredictionWeightTable { + // luma_log2_weight_denom + public int lumaLog2WeightDenom; + // chroma_log2_weight_denom + public int chromaLog2WeightDenom; + + // luma_weight + public int[][] lumaWeight; + // chroma_weight + public int[][][] chromaWeight; + + // luma_offset + public int[][] lumaOffset; + // chroma_offset + public int[][][] chromaOffset; + + public PredictionWeightTable() { + this.lumaWeight = new int[2][]; + this.chromaWeight = new int[2][][]; + + this.lumaOffset = new int[2][]; + this.chromaOffset = new int[2][][]; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/RefPicMarking.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/RefPicMarking.java new file mode 100644 index 0000000..187028f --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/RefPicMarking.java @@ -0,0 +1,54 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * A script of instructions applied to reference picture list + * + * @author The JCodec project + */ +public class RefPicMarking { + + public static enum InstrType { + REMOVE_SHORT, REMOVE_LONG, CONVERT_INTO_LONG, TRUNK_LONG, CLEAR, MARK_LONG + } + + ; + + public static class Instruction { + private InstrType type; + private int arg1; + private int arg2; + + public Instruction(InstrType type, int arg1, int arg2) { + this.type = type; + this.arg1 = arg1; + this.arg2 = arg2; + } + + public InstrType getType() { + return type; + } + + public int getArg1() { + return arg1; + } + + public int getArg2() { + return arg2; + } + } + + private Instruction[] instructions; + + public RefPicMarking(Instruction[] instructions) { + this.instructions = instructions; + } + + public Instruction[] getInstructions() { + return instructions; + } + + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/RefPicMarkingIDR.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/RefPicMarkingIDR.java new file mode 100644 index 0000000..4671ea0 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/RefPicMarkingIDR.java @@ -0,0 +1,29 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Reference picture marking used for IDR frames + * + * @author The JCodec project + */ +public class RefPicMarkingIDR { + boolean discardDecodedPics; + boolean useForlongTerm; + + public RefPicMarkingIDR(boolean discardDecodedPics, boolean useForlongTerm) { + this.discardDecodedPics = discardDecodedPics; + this.useForlongTerm = useForlongTerm; + } + + public boolean isDiscardDecodedPics() { + return discardDecodedPics; + } + + public boolean isUseForlongTerm() { + return useForlongTerm; + } + + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SEI.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SEI.java new file mode 100644 index 0000000..fb02c93 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SEI.java @@ -0,0 +1,91 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +import org.monte.media.impl.jcodec.common.io.BitWriter; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeTrailingBits; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Supplementary Enhanced Information entity of H264 bitstream + *

+ * capable to serialize and deserialize with CAVLC bitstream + * + * @author The JCodec project + */ +public class SEI { + + public static class SEIMessage { + public int payloadType; + public int payloadSize; + public byte[] payload; + + public SEIMessage(int payloadType2, int payloadSize2, byte[] payload2) { + this.payload = payload2; + this.payloadType = payloadType2; + this.payloadSize = payloadSize2; + } + + } + + public SEIMessage[] messages; + + public SEI(SEIMessage[] messages) { + this.messages = messages; + } + + public static SEI read(ByteBuffer is) { + + List messages = new ArrayList(); + SEIMessage msg; + do { + msg = sei_message(is); + if (msg != null) + messages.add(msg); + } while (msg != null); + + return new SEI((SEIMessage[]) messages.toArray(new SEIMessage[]{})); + } + + private static SEIMessage sei_message(ByteBuffer is) { + int payloadType = 0; + int b = 0; + while (is.hasRemaining() && (b = (is.get() & 0xff)) == 0xff) { + payloadType += 255; + } + if (!is.hasRemaining()) + return null; + payloadType += b; + int payloadSize = 0; + while (is.hasRemaining() && (b = (is.get() & 0xff)) == 0xff) { + payloadSize += 255; + } + if (!is.hasRemaining()) + return null; + payloadSize += b; + byte[] payload = sei_payload(payloadType, payloadSize, is); + if (payload.length != payloadSize) + return null; + + return new SEIMessage(payloadType, payloadSize, payload); + + } + + private static byte[] sei_payload(int payloadType, int payloadSize, ByteBuffer is) { + byte[] res = new byte[payloadSize]; + is.get(res); + return res; + } + + public void write(ByteBuffer out) { + BitWriter writer = new BitWriter(out); + // TODO Auto-generated method stub + + writeTrailingBits(writer); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SeqParameterSet.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SeqParameterSet.java new file mode 100644 index 0000000..96ffad7 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SeqParameterSet.java @@ -0,0 +1,708 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +import org.monte.media.impl.jcodec.codecs.h264.H264Const; +import org.monte.media.impl.jcodec.common.io.BitReader; +import org.monte.media.impl.jcodec.common.io.BitWriter; +import org.monte.media.impl.jcodec.common.model.ColorSpace; + +import java.nio.ByteBuffer; + +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readBool; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readNBit; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readSE; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readUEtrace; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeBool; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeNBit; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeSEtrace; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeTrailingBits; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeUEtrace; +import static org.monte.media.impl.jcodec.common.model.ColorSpace.MONO; +import static org.monte.media.impl.jcodec.common.model.ColorSpace.YUV420J; +import static org.monte.media.impl.jcodec.common.model.ColorSpace.YUV422; +import static org.monte.media.impl.jcodec.common.model.ColorSpace.YUV444; +import static org.monte.media.impl.jcodec.platform.Platform.arrayEqualsInt; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Sequence Parameter Set structure of h264 bitstream + *

+ * capable to serialize and deserialize with CAVLC bitstream + * + * @author The JCodec project + */ +public class SeqParameterSet { + // pic_order_cnt_type + public int picOrderCntType; + // field_pic_flag + public boolean fieldPicFlag; + // delta_pic_order_always_zero_flag + public boolean deltaPicOrderAlwaysZeroFlag; + // mb_adaptive_frame_field_flag + public boolean mbAdaptiveFrameFieldFlag; + // direct_8x8_inference_flag + public boolean direct8x8InferenceFlag; + // chroma_format_idc + public ColorSpace chromaFormatIdc; + // log2_max_frame_num_minus4 + public int log2MaxFrameNumMinus4; + // log2_max_pic_order_cnt_lsb_minus4 + public int log2MaxPicOrderCntLsbMinus4; + // pic_height_in_map_units_minus1 + public int picHeightInMapUnitsMinus1; + // pic_width_in_mbs_minus1 + public int picWidthInMbsMinus1; + // bit_depth_luma_minus8 + public int bitDepthLumaMinus8; + // bit_depth_chroma_minus8 + public int bitDepthChromaMinus8; + // qpprime_y_zero_transform_bypass_flag + public boolean qpprimeYZeroTransformBypassFlag; + // profile_idc + public int profileIdc; + // constraint_set0_flag + public boolean constraintSet0Flag; + // constraint_set1_flag + public boolean constraintSet1Flag; + // constraint_set2_flag + public boolean constraintSet2Flag; + // constraint_set_3_flag + public boolean constraintSet3Flag; + // constraint_set_4_flag + public boolean constraintSet4Flag; + // constraint_set_5_flag + public boolean constraintSet5Flag; + // level_idc + public int levelIdc; + // seq_parameter_set_id + public int seqParameterSetId; + /** + * separate_colour_plane_flag. When a picture is coded using three separate + * colour planes (separate_colour_plane_flag is equal to 1), a slice + * contains only macroblocks of one colour component being identified by the + * corresponding value of colour_plane_id, and each colour component array + * of a picture consists of slices having the same colour_plane_id value. + * Coded slices with different values of colour_plane_id within an access + * unit can be interleaved with each other under the constraint that for + * each value of colour_plane_id, the coded slice NAL units with that value + * colour_plane_id shall be in the order of increasing macroblock address + * for the first macroblock of each coded slice NAL unit. + */ + public boolean separateColourPlaneFlag; + + /** + * offset_for_non_ref_pic is used to calculate the picture order count of a + * non-reference picture as specified in 8.2.1. The value of + * offset_for_non_ref_pic shall be in the range of -231 to 231 - 1, + * inclusive. + */ + public int offsetForNonRefPic; + + /** + * offset_for_top_to_bottom_field is used to calculate the picture order + * count of a bottom field as specified in subclause 8.2.1. The value of + * offset_for_top_to_bottom_field shall be in the range of -231 to 231 - 1, + * inclusive. + */ + public int offsetForTopToBottomField; + + // num_ref_frames + public int numRefFrames; + + /** + * gaps_in_frame_num_value_allowed_flag specifies the allowed values of + * frame_num as specified in subclause 7.4.3 and the decoding process in + * case of an inferred gap between values of frame_num as specified in + * subclause 8.2.5.2. + */ + public boolean gapsInFrameNumValueAllowedFlag; + + /** + * frame_mbs_only_flag equal to 0 specifies that coded pictures of the coded + * video sequence may either be coded fields or coded frames. + * frame_mbs_only_flag equal to 1 specifies that every coded picture of the + * coded video sequence is a coded frame containing only frame macroblocks. + */ + public boolean frameMbsOnlyFlag; + + // frame_cropping_flag + public boolean frameCroppingFlag; + + // frame_crop_left_offset + public int frameCropLeftOffset; + + // frame_crop_right_offset + public int frameCropRightOffset; + + // frame_crop_top_offset + public int frameCropTopOffset; + + // frame_crop_bottom_offset + public int frameCropBottomOffset; + + public int[] offsetForRefFrame; + public VUIParameters vuiParams; + public int[][] scalingMatrix; + + // num_ref_frames_in_pic_order_cnt_cycle + public int numRefFramesInPicOrderCntCycle; + + public static ColorSpace getColor(int id) { + switch (id) { + case 0: + return MONO; + case 1: + return YUV420J; + case 2: + return YUV422; + case 3: + return YUV444; + } + throw new RuntimeException("Colorspace not supported"); + } + + public static int fromColor(ColorSpace color) { + if (color == MONO) { + return 0; + } else if (color == YUV420J) { + return 1; + } else if (color == YUV422) { + return 2; + } else if (color == YUV444) { + return 3; + } + throw new RuntimeException("Colorspace not supported"); + } + + public static SeqParameterSet read(ByteBuffer is) { + BitReader _in = BitReader.createBitReader(is); + SeqParameterSet sps = new SeqParameterSet(); + + sps.profileIdc = readNBit(_in, 8, "SPS: profile_idc"); + sps.constraintSet0Flag = readBool(_in, "SPS: constraint_set_0_flag"); + sps.constraintSet1Flag = readBool(_in, "SPS: constraint_set_1_flag"); + sps.constraintSet2Flag = readBool(_in, "SPS: constraint_set_2_flag"); + sps.constraintSet3Flag = readBool(_in, "SPS: constraint_set_3_flag"); + sps.constraintSet4Flag = readBool(_in, "SPS: constraint_set_4_flag"); + sps.constraintSet5Flag = readBool(_in, "SPS: constraint_set_5_flag"); + readNBit(_in, 2, "SPS: reserved_zero_2bits"); + sps.levelIdc = (int) readNBit(_in, 8, "SPS: level_idc"); + sps.seqParameterSetId = readUEtrace(_in, "SPS: seq_parameter_set_id"); + + if (sps.profileIdc == 100 || sps.profileIdc == 110 || sps.profileIdc == 122 || sps.profileIdc == 144) { + sps.chromaFormatIdc = getColor(readUEtrace(_in, "SPS: chroma_format_idc")); + if (sps.chromaFormatIdc == YUV444) { + sps.separateColourPlaneFlag = readBool(_in, "SPS: separate_colour_plane_flag"); + } + sps.bitDepthLumaMinus8 = readUEtrace(_in, "SPS: bit_depth_luma_minus8"); + sps.bitDepthChromaMinus8 = readUEtrace(_in, "SPS: bit_depth_chroma_minus8"); + sps.qpprimeYZeroTransformBypassFlag = readBool(_in, "SPS: qpprime_y_zero_transform_bypass_flag"); + boolean seqScalingMatrixPresent = readBool(_in, "SPS: seq_scaling_matrix_present_lag"); + if (seqScalingMatrixPresent) { + readScalingListMatrix(_in, sps); + } + } else { + sps.chromaFormatIdc = YUV420J; + } + sps.log2MaxFrameNumMinus4 = readUEtrace(_in, "SPS: log2_max_frame_num_minus4"); + sps.picOrderCntType = readUEtrace(_in, "SPS: pic_order_cnt_type"); + if (sps.picOrderCntType == 0) { + sps.log2MaxPicOrderCntLsbMinus4 = readUEtrace(_in, "SPS: log2_max_pic_order_cnt_lsb_minus4"); + } else if (sps.picOrderCntType == 1) { + sps.deltaPicOrderAlwaysZeroFlag = readBool(_in, "SPS: delta_pic_order_always_zero_flag"); + sps.offsetForNonRefPic = readSE(_in, "SPS: offset_for_non_ref_pic"); + sps.offsetForTopToBottomField = readSE(_in, "SPS: offset_for_top_to_bottom_field"); + sps.numRefFramesInPicOrderCntCycle = readUEtrace(_in, "SPS: num_ref_frames_in_pic_order_cnt_cycle"); + sps.offsetForRefFrame = new int[sps.numRefFramesInPicOrderCntCycle]; + for (int i = 0; i < sps.numRefFramesInPicOrderCntCycle; i++) { + sps.offsetForRefFrame[i] = readSE(_in, "SPS: offsetForRefFrame [" + i + "]"); + } + } + sps.numRefFrames = readUEtrace(_in, "SPS: num_ref_frames"); + sps.gapsInFrameNumValueAllowedFlag = readBool(_in, "SPS: gaps_in_frame_num_value_allowed_flag"); + sps.picWidthInMbsMinus1 = readUEtrace(_in, "SPS: pic_width_in_mbs_minus1"); + sps.picHeightInMapUnitsMinus1 = readUEtrace(_in, "SPS: pic_height_in_map_units_minus1"); + sps.frameMbsOnlyFlag = readBool(_in, "SPS: frame_mbs_only_flag"); + if (!sps.frameMbsOnlyFlag) { + sps.mbAdaptiveFrameFieldFlag = readBool(_in, "SPS: mb_adaptive_frame_field_flag"); + } + sps.direct8x8InferenceFlag = readBool(_in, "SPS: direct_8x8_inference_flag"); + sps.frameCroppingFlag = readBool(_in, "SPS: frame_cropping_flag"); + if (sps.frameCroppingFlag) { + sps.frameCropLeftOffset = readUEtrace(_in, "SPS: frame_crop_left_offset"); + sps.frameCropRightOffset = readUEtrace(_in, "SPS: frame_crop_right_offset"); + sps.frameCropTopOffset = readUEtrace(_in, "SPS: frame_crop_top_offset"); + sps.frameCropBottomOffset = readUEtrace(_in, "SPS: frame_crop_bottom_offset"); + } + boolean vuiParametersPresentFlag = readBool(_in, "SPS: vui_parameters_present_flag"); + if (vuiParametersPresentFlag) + sps.vuiParams = readVUIParameters(_in); + + return sps; + } + + public static void writeScalingList(BitWriter out, int[][] scalingMatrix, int which) { + // Want to find out if the default scaling list is actually used + boolean useDefaultScalingMatrixFlag = false; + switch (which) { + case 0: // 4x4 intra y + useDefaultScalingMatrixFlag = arrayEqualsInt(scalingMatrix[which], H264Const.defaultScalingList4x4Intra); + break; + case 1: + case 2: + useDefaultScalingMatrixFlag = arrayEqualsInt(scalingMatrix[which], scalingMatrix[0]); + break; + case 3: + useDefaultScalingMatrixFlag = arrayEqualsInt(scalingMatrix[which], H264Const.defaultScalingList4x4Inter); + break; + case 4: + case 5: + useDefaultScalingMatrixFlag = arrayEqualsInt(scalingMatrix[which], scalingMatrix[3]); + break; + case 6: + useDefaultScalingMatrixFlag = arrayEqualsInt(scalingMatrix[which], H264Const.defaultScalingList8x8Intra); + break; + case 7: + useDefaultScalingMatrixFlag = arrayEqualsInt(scalingMatrix[which], H264Const.defaultScalingList8x8Inter); + break; + } + int[] scalingList = scalingMatrix[which]; + + if (useDefaultScalingMatrixFlag) { + writeSEtrace(out, -8, "SPS: "); + return; + } + + int lastScale = 8; + int nextScale = 8; + for (int j = 0; j < scalingList.length; j++) { + if (nextScale != 0) { + int deltaScale = scalingList[j] - lastScale - 256; + writeSEtrace(out, deltaScale, "SPS: "); + } + lastScale = scalingList[j]; + } + } + + //Wrong usage of Javascript keyword:in + public static int[] readScalingList(BitReader src, int sizeOfScalingList) { + + int[] scalingList = new int[sizeOfScalingList]; + int lastScale = 8; + int nextScale = 8; + for (int j = 0; j < sizeOfScalingList; j++) { + if (nextScale != 0) { + int deltaScale = readSE(src, "deltaScale"); + nextScale = (lastScale + deltaScale + 256) % 256; + if (j == 0 && nextScale == 0) + return null; + } + scalingList[j] = nextScale == 0 ? lastScale : nextScale; + lastScale = scalingList[j]; + } + return scalingList; + } + + //Wrong usage of Javascript keyword:in + private static void readScalingListMatrix(BitReader src, SeqParameterSet sps) { + sps.scalingMatrix = new int[8][]; + for (int i = 0; i < 8; i++) { + boolean seqScalingListPresentFlag = readBool(src, "SPS: seqScalingListPresentFlag"); + if (seqScalingListPresentFlag) { + int scalingListSize = i < 6 ? 16 : 64; + sps.scalingMatrix[i] = readScalingList(src, scalingListSize); + } + } + } + + private static VUIParameters readVUIParameters(BitReader _in) { + VUIParameters vuip = new VUIParameters(); + vuip.aspectRatioInfoPresentFlag = readBool(_in, "VUI: aspect_ratio_info_present_flag"); + if (vuip.aspectRatioInfoPresentFlag) { + vuip.aspectRatio = AspectRatio.fromValue((int) readNBit(_in, 8, "VUI: aspect_ratio")); + if (vuip.aspectRatio == AspectRatio.Extended_SAR) { + vuip.sarWidth = (int) readNBit(_in, 16, "VUI: sar_width"); + vuip.sarHeight = (int) readNBit(_in, 16, "VUI: sar_height"); + } + } + vuip.overscanInfoPresentFlag = readBool(_in, "VUI: overscan_info_present_flag"); + if (vuip.overscanInfoPresentFlag) { + vuip.overscanAppropriateFlag = readBool(_in, "VUI: overscan_appropriate_flag"); + } + vuip.videoSignalTypePresentFlag = readBool(_in, "VUI: video_signal_type_present_flag"); + if (vuip.videoSignalTypePresentFlag) { + vuip.videoFormat = (int) readNBit(_in, 3, "VUI: video_format"); + vuip.videoFullRangeFlag = readBool(_in, "VUI: video_full_range_flag"); + vuip.colourDescriptionPresentFlag = readBool(_in, "VUI: colour_description_present_flag"); + if (vuip.colourDescriptionPresentFlag) { + vuip.colourPrimaries = (int) readNBit(_in, 8, "VUI: colour_primaries"); + vuip.transferCharacteristics = (int) readNBit(_in, 8, "VUI: transfer_characteristics"); + vuip.matrixCoefficients = (int) readNBit(_in, 8, "VUI: matrix_coefficients"); + } + } + vuip.chromaLocInfoPresentFlag = readBool(_in, "VUI: chroma_loc_info_present_flag"); + if (vuip.chromaLocInfoPresentFlag) { + vuip.chromaSampleLocTypeTopField = readUEtrace(_in, "VUI chroma_sample_loc_type_top_field"); + vuip.chromaSampleLocTypeBottomField = readUEtrace(_in, "VUI chroma_sample_loc_type_bottom_field"); + } + vuip.timingInfoPresentFlag = readBool(_in, "VUI: timing_info_present_flag"); + if (vuip.timingInfoPresentFlag) { + vuip.numUnitsInTick = (int) readNBit(_in, 32, "VUI: num_units_in_tick"); + vuip.timeScale = (int) readNBit(_in, 32, "VUI: time_scale"); + vuip.fixedFrameRateFlag = readBool(_in, "VUI: fixed_frame_rate_flag"); + } + boolean nalHRDParametersPresentFlag = readBool(_in, "VUI: nal_hrd_parameters_present_flag"); + if (nalHRDParametersPresentFlag) + vuip.nalHRDParams = readHRDParameters(_in); + boolean vclHRDParametersPresentFlag = readBool(_in, "VUI: vcl_hrd_parameters_present_flag"); + if (vclHRDParametersPresentFlag) + vuip.vclHRDParams = readHRDParameters(_in); + if (nalHRDParametersPresentFlag || vclHRDParametersPresentFlag) { + vuip.lowDelayHrdFlag = readBool(_in, "VUI: low_delay_hrd_flag"); + } + vuip.picStructPresentFlag = readBool(_in, "VUI: pic_struct_present_flag"); + boolean bitstreamRestrictionFlag = readBool(_in, "VUI: bitstream_restriction_flag"); + if (bitstreamRestrictionFlag) { + vuip.bitstreamRestriction = new VUIParameters.BitstreamRestriction(); + vuip.bitstreamRestriction.motionVectorsOverPicBoundariesFlag = readBool(_in, + "VUI: motion_vectors_over_pic_boundaries_flag"); + vuip.bitstreamRestriction.maxBytesPerPicDenom = readUEtrace(_in, "VUI max_bytes_per_pic_denom"); + vuip.bitstreamRestriction.maxBitsPerMbDenom = readUEtrace(_in, "VUI max_bits_per_mb_denom"); + vuip.bitstreamRestriction.log2MaxMvLengthHorizontal = readUEtrace(_in, "VUI log2_max_mv_length_horizontal"); + vuip.bitstreamRestriction.log2MaxMvLengthVertical = readUEtrace(_in, "VUI log2_max_mv_length_vertical"); + vuip.bitstreamRestriction.numReorderFrames = readUEtrace(_in, "VUI num_reorder_frames"); + vuip.bitstreamRestriction.maxDecFrameBuffering = readUEtrace(_in, "VUI max_dec_frame_buffering"); + } + + return vuip; + } + + private static HRDParameters readHRDParameters(BitReader _in) { + HRDParameters hrd = new HRDParameters(); + hrd.cpbCntMinus1 = readUEtrace(_in, "SPS: cpb_cnt_minus1"); + hrd.bitRateScale = (int) readNBit(_in, 4, "HRD: bit_rate_scale"); + hrd.cpbSizeScale = (int) readNBit(_in, 4, "HRD: cpb_size_scale"); + hrd.bitRateValueMinus1 = new int[hrd.cpbCntMinus1 + 1]; + hrd.cpbSizeValueMinus1 = new int[hrd.cpbCntMinus1 + 1]; + hrd.cbrFlag = new boolean[hrd.cpbCntMinus1 + 1]; + + for (int SchedSelIdx = 0; SchedSelIdx <= hrd.cpbCntMinus1; SchedSelIdx++) { + hrd.bitRateValueMinus1[SchedSelIdx] = readUEtrace(_in, "HRD: bit_rate_value_minus1"); + hrd.cpbSizeValueMinus1[SchedSelIdx] = readUEtrace(_in, "HRD: cpb_size_value_minus1"); + hrd.cbrFlag[SchedSelIdx] = readBool(_in, "HRD: cbr_flag"); + } + hrd.initialCpbRemovalDelayLengthMinus1 = (int) readNBit(_in, 5, + "HRD: initial_cpb_removal_delay_length_minus1"); + hrd.cpbRemovalDelayLengthMinus1 = (int) readNBit(_in, 5, "HRD: cpb_removal_delay_length_minus1"); + hrd.dpbOutputDelayLengthMinus1 = (int) readNBit(_in, 5, "HRD: dpb_output_delay_length_minus1"); + hrd.timeOffsetLength = (int) readNBit(_in, 5, "HRD: time_offset_length"); + return hrd; + } + + public void write(ByteBuffer out) { + BitWriter writer = new BitWriter(out); + + writeNBit(writer, profileIdc, 8, "SPS: profile_idc"); + writeBool(writer, constraintSet0Flag, "SPS: constraint_set_0_flag"); + writeBool(writer, constraintSet1Flag, "SPS: constraint_set_1_flag"); + writeBool(writer, constraintSet2Flag, "SPS: constraint_set_2_flag"); + writeBool(writer, constraintSet3Flag, "SPS: constraint_set_3_flag"); + writeBool(writer, constraintSet4Flag, "SPS: constraint_set_4_flag"); + writeBool(writer, constraintSet5Flag, "SPS: constraint_set_5_flag"); + writeNBit(writer, 0, 2, "SPS: reserved"); + writeNBit(writer, levelIdc, 8, "SPS: level_idc"); + writeUEtrace(writer, seqParameterSetId, "SPS: seq_parameter_set_id"); + + if (profileIdc == 100 || profileIdc == 110 || profileIdc == 122 || profileIdc == 144) { + writeUEtrace(writer, fromColor(chromaFormatIdc), "SPS: chroma_format_idc"); + if (chromaFormatIdc == YUV444) { + writeBool(writer, separateColourPlaneFlag, "SPS: residual_color_transform_flag"); + } + writeUEtrace(writer, bitDepthLumaMinus8, "SPS: "); + writeUEtrace(writer, bitDepthChromaMinus8, "SPS: "); + writeBool(writer, qpprimeYZeroTransformBypassFlag, "SPS: qpprime_y_zero_transform_bypass_flag"); + writeBool(writer, scalingMatrix != null, "SPS: "); + if (scalingMatrix != null) { + for (int i = 0; i < 8; i++) { + writeBool(writer, scalingMatrix[i] != null, "SPS: "); + if (scalingMatrix[i] != null) + writeScalingList(writer, scalingMatrix, i); + } + } + } + writeUEtrace(writer, log2MaxFrameNumMinus4, "SPS: log2_max_frame_num_minus4"); + writeUEtrace(writer, picOrderCntType, "SPS: pic_order_cnt_type"); + if (picOrderCntType == 0) { + writeUEtrace(writer, log2MaxPicOrderCntLsbMinus4, "SPS: log2_max_pic_order_cnt_lsb_minus4"); + } else if (picOrderCntType == 1) { + writeBool(writer, deltaPicOrderAlwaysZeroFlag, "SPS: delta_pic_order_always_zero_flag"); + writeSEtrace(writer, offsetForNonRefPic, "SPS: offset_for_non_ref_pic"); + writeSEtrace(writer, offsetForTopToBottomField, "SPS: offset_for_top_to_bottom_field"); + writeUEtrace(writer, offsetForRefFrame.length, "SPS: "); + for (int i = 0; i < offsetForRefFrame.length; i++) + writeSEtrace(writer, offsetForRefFrame[i], "SPS: "); + } + writeUEtrace(writer, numRefFrames, "SPS: num_ref_frames"); + writeBool(writer, gapsInFrameNumValueAllowedFlag, "SPS: gaps_in_frame_num_value_allowed_flag"); + writeUEtrace(writer, picWidthInMbsMinus1, "SPS: pic_width_in_mbs_minus1"); + writeUEtrace(writer, picHeightInMapUnitsMinus1, "SPS: pic_height_in_map_units_minus1"); + writeBool(writer, frameMbsOnlyFlag, "SPS: frame_mbs_only_flag"); + if (!frameMbsOnlyFlag) { + writeBool(writer, mbAdaptiveFrameFieldFlag, "SPS: mb_adaptive_frame_field_flag"); + } + writeBool(writer, direct8x8InferenceFlag, "SPS: direct_8x8_inference_flag"); + writeBool(writer, frameCroppingFlag, "SPS: frame_cropping_flag"); + if (frameCroppingFlag) { + writeUEtrace(writer, frameCropLeftOffset, "SPS: frame_crop_left_offset"); + writeUEtrace(writer, frameCropRightOffset, "SPS: frame_crop_right_offset"); + writeUEtrace(writer, frameCropTopOffset, "SPS: frame_crop_top_offset"); + writeUEtrace(writer, frameCropBottomOffset, "SPS: frame_crop_bottom_offset"); + } + writeBool(writer, vuiParams != null, "SPS: "); + if (vuiParams != null) + writeVUIParameters(vuiParams, writer); + + writeTrailingBits(writer); + } + + private void writeVUIParameters(VUIParameters vuip, BitWriter writer) { + writeBool(writer, vuip.aspectRatioInfoPresentFlag, "VUI: aspect_ratio_info_present_flag"); + if (vuip.aspectRatioInfoPresentFlag) { + writeNBit(writer, vuip.aspectRatio.getValue(), 8, "VUI: aspect_ratio"); + if (vuip.aspectRatio == AspectRatio.Extended_SAR) { + writeNBit(writer, vuip.sarWidth, 16, "VUI: sar_width"); + writeNBit(writer, vuip.sarHeight, 16, "VUI: sar_height"); + } + } + writeBool(writer, vuip.overscanInfoPresentFlag, "VUI: overscan_info_present_flag"); + if (vuip.overscanInfoPresentFlag) { + writeBool(writer, vuip.overscanAppropriateFlag, "VUI: overscan_appropriate_flag"); + } + writeBool(writer, vuip.videoSignalTypePresentFlag, "VUI: video_signal_type_present_flag"); + if (vuip.videoSignalTypePresentFlag) { + writeNBit(writer, vuip.videoFormat, 3, "VUI: video_format"); + writeBool(writer, vuip.videoFullRangeFlag, "VUI: video_full_range_flag"); + writeBool(writer, vuip.colourDescriptionPresentFlag, "VUI: colour_description_present_flag"); + if (vuip.colourDescriptionPresentFlag) { + writeNBit(writer, vuip.colourPrimaries, 8, "VUI: colour_primaries"); + writeNBit(writer, vuip.transferCharacteristics, 8, "VUI: transfer_characteristics"); + writeNBit(writer, vuip.matrixCoefficients, 8, "VUI: matrix_coefficients"); + } + } + writeBool(writer, vuip.chromaLocInfoPresentFlag, "VUI: chroma_loc_info_present_flag"); + if (vuip.chromaLocInfoPresentFlag) { + writeUEtrace(writer, vuip.chromaSampleLocTypeTopField, "VUI: chroma_sample_loc_type_top_field"); + writeUEtrace(writer, vuip.chromaSampleLocTypeBottomField, "VUI: chroma_sample_loc_type_bottom_field"); + } + writeBool(writer, vuip.timingInfoPresentFlag, "VUI: timing_info_present_flag"); + if (vuip.timingInfoPresentFlag) { + writeNBit(writer, vuip.numUnitsInTick, 32, "VUI: num_units_in_tick"); + writeNBit(writer, vuip.timeScale, 32, "VUI: time_scale"); + writeBool(writer, vuip.fixedFrameRateFlag, "VUI: fixed_frame_rate_flag"); + } + writeBool(writer, vuip.nalHRDParams != null, "VUI: "); + if (vuip.nalHRDParams != null) { + writeHRDParameters(vuip.nalHRDParams, writer); + } + writeBool(writer, vuip.vclHRDParams != null, "VUI: "); + if (vuip.vclHRDParams != null) { + writeHRDParameters(vuip.vclHRDParams, writer); + } + + if (vuip.nalHRDParams != null || vuip.vclHRDParams != null) { + writeBool(writer, vuip.lowDelayHrdFlag, "VUI: low_delay_hrd_flag"); + } + writeBool(writer, vuip.picStructPresentFlag, "VUI: pic_struct_present_flag"); + writeBool(writer, vuip.bitstreamRestriction != null, "VUI: "); + if (vuip.bitstreamRestriction != null) { + writeBool(writer, vuip.bitstreamRestriction.motionVectorsOverPicBoundariesFlag, + "VUI: motion_vectors_over_pic_boundaries_flag"); + writeUEtrace(writer, vuip.bitstreamRestriction.maxBytesPerPicDenom, "VUI: max_bytes_per_pic_denom"); + writeUEtrace(writer, vuip.bitstreamRestriction.maxBitsPerMbDenom, "VUI: max_bits_per_mb_denom"); + writeUEtrace(writer, vuip.bitstreamRestriction.log2MaxMvLengthHorizontal, + "VUI: log2_max_mv_length_horizontal"); + writeUEtrace(writer, vuip.bitstreamRestriction.log2MaxMvLengthVertical, "VUI: log2_max_mv_length_vertical"); + writeUEtrace(writer, vuip.bitstreamRestriction.numReorderFrames, "VUI: num_reorder_frames"); + writeUEtrace(writer, vuip.bitstreamRestriction.maxDecFrameBuffering, "VUI: max_dec_frame_buffering"); + } + + } + + private void writeHRDParameters(HRDParameters hrd, BitWriter writer) { + writeUEtrace(writer, hrd.cpbCntMinus1, "HRD: cpb_cnt_minus1"); + writeNBit(writer, hrd.bitRateScale, 4, "HRD: bit_rate_scale"); + writeNBit(writer, hrd.cpbSizeScale, 4, "HRD: cpb_size_scale"); + + for (int SchedSelIdx = 0; SchedSelIdx <= hrd.cpbCntMinus1; SchedSelIdx++) { + writeUEtrace(writer, hrd.bitRateValueMinus1[SchedSelIdx], "HRD: "); + writeUEtrace(writer, hrd.cpbSizeValueMinus1[SchedSelIdx], "HRD: "); + writeBool(writer, hrd.cbrFlag[SchedSelIdx], "HRD: "); + } + writeNBit(writer, hrd.initialCpbRemovalDelayLengthMinus1, 5, + "HRD: initial_cpb_removal_delay_length_minus1"); + writeNBit(writer, hrd.cpbRemovalDelayLengthMinus1, 5, "HRD: cpb_removal_delay_length_minus1"); + writeNBit(writer, hrd.dpbOutputDelayLengthMinus1, 5, "HRD: dpb_output_delay_length_minus1"); + writeNBit(writer, hrd.timeOffsetLength, 5, "HRD: time_offset_length"); + } + + public SeqParameterSet copy() { + ByteBuffer buf = ByteBuffer.allocate(2048); + write(buf); + buf.flip(); + return read(buf); + } + + public int getPicOrderCntType() { + return picOrderCntType; + } + + public boolean isFieldPicFlag() { + return fieldPicFlag; + } + + public boolean isDeltaPicOrderAlwaysZeroFlag() { + return deltaPicOrderAlwaysZeroFlag; + } + + public boolean isMbAdaptiveFrameFieldFlag() { + return mbAdaptiveFrameFieldFlag; + } + + public boolean isDirect8x8InferenceFlag() { + return direct8x8InferenceFlag; + } + + public ColorSpace getChromaFormatIdc() { + return chromaFormatIdc; + } + + public int getLog2MaxFrameNumMinus4() { + return log2MaxFrameNumMinus4; + } + + public int getLog2MaxPicOrderCntLsbMinus4() { + return log2MaxPicOrderCntLsbMinus4; + } + + public int getPicHeightInMapUnitsMinus1() { + return picHeightInMapUnitsMinus1; + } + + public int getPicWidthInMbsMinus1() { + return picWidthInMbsMinus1; + } + + public int getBitDepthLumaMinus8() { + return bitDepthLumaMinus8; + } + + public int getBitDepthChromaMinus8() { + return bitDepthChromaMinus8; + } + + public boolean isQpprimeYZeroTransformBypassFlag() { + return qpprimeYZeroTransformBypassFlag; + } + + public int getProfileIdc() { + return profileIdc; + } + + public boolean isConstraintSet0Flag() { + return constraintSet0Flag; + } + + public boolean isConstraintSet1Flag() { + return constraintSet1Flag; + } + + public boolean isConstraintSet2Flag() { + return constraintSet2Flag; + } + + public boolean isConstraintSet3Flag() { + return constraintSet3Flag; + } + + public boolean isConstraintSet4Flag() { + return constraintSet4Flag; + } + + public boolean isConstraintSet5Flag() { + return constraintSet5Flag; + } + + public int getLevelIdc() { + return levelIdc; + } + + public int getSeqParameterSetId() { + return seqParameterSetId; + } + + public boolean isResidualColorTransformFlag() { + return separateColourPlaneFlag; + } + + public int getOffsetForNonRefPic() { + return offsetForNonRefPic; + } + + public int getOffsetForTopToBottomField() { + return offsetForTopToBottomField; + } + + public int getNumRefFrames() { + return numRefFrames; + } + + public boolean isGapsInFrameNumValueAllowedFlag() { + return gapsInFrameNumValueAllowedFlag; + } + + public boolean isFrameMbsOnlyFlag() { + return frameMbsOnlyFlag; + } + + public boolean isFrameCroppingFlag() { + return frameCroppingFlag; + } + + public int getFrameCropLeftOffset() { + return frameCropLeftOffset; + } + + public int getFrameCropRightOffset() { + return frameCropRightOffset; + } + + public int getFrameCropTopOffset() { + return frameCropTopOffset; + } + + public int getFrameCropBottomOffset() { + return frameCropBottomOffset; + } + + public int[] getOffsetForRefFrame() { + return offsetForRefFrame; + } + + public VUIParameters getVuiParams() { + return vuiParams; + } + + public int[][] getScalingMatrix() { + return scalingMatrix; + } + + public int getNumRefFramesInPicOrderCntCycle() { + return numRefFramesInPicOrderCntCycle; + } + + public static int getPicHeightInMbs(SeqParameterSet sps) { + int picHeightInMbs = (sps.picHeightInMapUnitsMinus1 + 1) << (sps.frameMbsOnlyFlag ? 0 : 1); + return picHeightInMbs; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SeqParameterSetExt.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SeqParameterSetExt.java new file mode 100644 index 0000000..8510c37 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SeqParameterSetExt.java @@ -0,0 +1,54 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +import org.monte.media.impl.jcodec.common.io.BitReader; +import org.monte.media.impl.jcodec.common.io.BitWriter; + +import java.nio.ByteBuffer; + +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readBool; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readU; +import static org.monte.media.impl.jcodec.codecs.h264.decode.CAVLCReader.readUEtrace; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeTrailingBits; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Sequence Parameter Set Extension entity of H264 bitstream + *

+ * Capable to serialize / deserialize itself with CAVLC bit stream + * + * @author The JCodec project + */ +public class SeqParameterSetExt { + + public int seq_parameter_set_id; + public int aux_format_idc; + public int bit_depth_aux_minus8; + public boolean alpha_incr_flag; + public boolean additional_extension_flag; + public int alpha_opaque_value; + public int alpha_transparent_value; + + public static SeqParameterSetExt read(ByteBuffer is) { + BitReader _in = BitReader.createBitReader(is); + + SeqParameterSetExt spse = new SeqParameterSetExt(); + spse.seq_parameter_set_id = readUEtrace(_in, "SPSE: seq_parameter_set_id"); + spse.aux_format_idc = readUEtrace(_in, "SPSE: aux_format_idc"); + if (spse.aux_format_idc != 0) { + spse.bit_depth_aux_minus8 = readUEtrace(_in, "SPSE: bit_depth_aux_minus8"); + spse.alpha_incr_flag = readBool(_in, "SPSE: alpha_incr_flag"); + spse.alpha_opaque_value = readU(_in, spse.bit_depth_aux_minus8 + 9, "SPSE: alpha_opaque_value"); + spse.alpha_transparent_value = readU(_in, spse.bit_depth_aux_minus8 + 9, "SPSE: alpha_transparent_value"); + } + spse.additional_extension_flag = readBool(_in, "SPSE: additional_extension_flag"); + + return spse; + } + + public void write(ByteBuffer out) { + BitWriter writer = new BitWriter(out); + writeTrailingBits(writer); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SliceHeader.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SliceHeader.java new file mode 100644 index 0000000..04f51b5 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SliceHeader.java @@ -0,0 +1,99 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Slice header H264 bitstream entity + *

+ * capable to serialize / deserialize with CAVLC bitstream + * + * @author The JCodec project + */ +public class SliceHeader { + + public SeqParameterSet sps; + public PictureParameterSet pps; + + public RefPicMarking refPicMarkingNonIDR; + public RefPicMarkingIDR refPicMarkingIDR; + + public int[][][] refPicReordering; + + // pred_weight_table + public PredictionWeightTable predWeightTable; + // first_mb_in_slice + public int firstMbInSlice; + + // field_pic_flag + public boolean fieldPicFlag; + + // slice_type + public SliceType sliceType; + + // slice_type_restr + public boolean sliceTypeRestr; + + // pic_parameter_set_id + public int picParameterSetId; + + // frame_num + public int frameNum; + + // bottom_field_flag + public boolean bottomFieldFlag; + + // idr_pic_id + public int idrPicId; + + // pic_order_cnt_lsb + public int picOrderCntLsb; + + // delta_pic_order_cnt_bottom + public int deltaPicOrderCntBottom; + + // delta_pic_order_cnt + public int[] deltaPicOrderCnt; + + // redundant_pic_cnt + public int redundantPicCnt; + + // direct_spatial_mv_pred_flag + public boolean directSpatialMvPredFlag; + + // num_ref_idx_active_override_flag + public boolean numRefIdxActiveOverrideFlag; + + // num_ref_idx_active_minus1 + public int[] numRefIdxActiveMinus1; + + // cabac_init_idc + public int cabacInitIdc; + + // slice_qp_delta + public int sliceQpDelta; + + // sp_for_switch_flag + public boolean spForSwitchFlag; + + // slice_qs_delta + public int sliceQsDelta; + + // disable_deblocking_filter_idc + public int disableDeblockingFilterIdc; + + // slice_alpha_c0_offset_div2 + public int sliceAlphaC0OffsetDiv2; + + // slice_beta_offset_div2 + public int sliceBetaOffsetDiv2; + + // slice_group_change_cycle + public int sliceGroupChangeCycle; + + public SliceHeader() { + this.numRefIdxActiveMinus1 = new int[2]; + } + + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SliceType.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SliceType.java new file mode 100644 index 0000000..f33fb17 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/SliceType.java @@ -0,0 +1,53 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public final class SliceType { + private final static SliceType _values[] = new SliceType[5]; + public final static SliceType P = new SliceType("P", 0); + public final static SliceType B = new SliceType("B", 1); + public final static SliceType I = new SliceType("I", 2); + public final static SliceType SP = new SliceType("SP", 3); + public final static SliceType SI = new SliceType("SI", 4); + private String _name; + private int _ordinal; + + private SliceType(String name, int ordinal) { + this._name = name; + this._ordinal = ordinal; + _values[ordinal] = this; + } + + public boolean isIntra() { + return this == I || this == SI; + } + + public boolean isInter() { + return this != I && this != SI; + } + + public static SliceType[] values() { + return _values; + } + + public int ordinal() { + return _ordinal; + } + + @Override + public String toString() { + return _name; + } + + public String name() { + return _name; + } + + public static SliceType fromValue(int j) { + return values()[j]; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/VUIParameters.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/VUIParameters.java new file mode 100644 index 0000000..b3c6b3e --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/model/VUIParameters.java @@ -0,0 +1,77 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.model; + +/** + * This class is part of JCodec ( www.jcodec.org ) + * This software is distributed under FreeBSD License + * + * @author The JCodec project + */ +public class VUIParameters { + + public static class BitstreamRestriction { + // motion_vectors_over_pic_boundaries_flag + public boolean motionVectorsOverPicBoundariesFlag; + // max_bytes_per_pic_denom + public int maxBytesPerPicDenom; + // max_bits_per_mb_denom + public int maxBitsPerMbDenom; + // log2_max_mv_length_horizontal + public int log2MaxMvLengthHorizontal; + // log2_max_mv_length_vertical + public int log2MaxMvLengthVertical; + // num_reorder_frames + public int numReorderFrames; + // max_dec_frame_buffering + public int maxDecFrameBuffering; + } + + // boolean aspect_ratio_info_present_flag + public boolean aspectRatioInfoPresentFlag; + // int sar_width + public int sarWidth; + // int sar_height + public int sarHeight; + // overscan_info_present_flag + public boolean overscanInfoPresentFlag; + // overscan_appropriate_flag; + public boolean overscanAppropriateFlag; + // video_signal_type_present_flag; + public boolean videoSignalTypePresentFlag; + // video_format + public int videoFormat; + // video_full_range_flag + public boolean videoFullRangeFlag; + // colour_description_present_flag + public boolean colourDescriptionPresentFlag; + // colour_primaries + public int colourPrimaries; + // transfer_characteristics + public int transferCharacteristics; + // matrix_coefficients + public int matrixCoefficients; + // chroma_loc_info_present_flag + public boolean chromaLocInfoPresentFlag; + // chroma_sample_loc_type_top_field + public int chromaSampleLocTypeTopField; + // chroma_sample_loc_type_bottom_field + public int chromaSampleLocTypeBottomField; + // timing_info_present_flag + public boolean timingInfoPresentFlag; + // num_units_in_tick + public int numUnitsInTick; + // time_scale + public int timeScale; + // fixed_frame_rate_flag + public boolean fixedFrameRateFlag; + // low_delay_hrd_flag + public boolean lowDelayHrdFlag; + // pic_struct_present_flag + public boolean picStructPresentFlag; + public HRDParameters nalHRDParams; + public HRDParameters vclHRDParams; + + public BitstreamRestriction bitstreamRestriction; + // aspect_ratio + public AspectRatio aspectRatio; + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/write/CAVLCWriter.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/write/CAVLCWriter.java new file mode 100644 index 0000000..4ee68bd --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/write/CAVLCWriter.java @@ -0,0 +1,87 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.write; + +import org.monte.media.impl.jcodec.api.NotImplementedException; +import org.monte.media.impl.jcodec.common.io.BitWriter; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import static org.monte.media.impl.jcodec.common.tools.Debug.trace; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * A class responsible for outputting exp-Golomb values into binary stream + * + * @author The JCodec project + */ +public class CAVLCWriter { + + private CAVLCWriter() { + } + + public static void writeUtrace(BitWriter out, int value, int n, String message) { + out.writeNBit(value, n); + trace(message, value); + } + + public static void writeUE(BitWriter out, int value) { + int bits = 0; + int cumul = 0; + for (int i = 0; i < 15; i++) { + if (value < cumul + (1 << i)) { + bits = i; + break; + } + cumul += (1 << i); + } + out.writeNBit(0, bits); + out.write1Bit(1); + out.writeNBit(value - cumul, bits); + } + + public static void writeSE(BitWriter out, int value) { + writeUE(out, MathUtil.golomb(value)); + } + + public static void writeUEtrace(BitWriter out, int value, String message) { + writeUE(out, value); + trace(message, value); + } + + public static void writeSEtrace(BitWriter out, int value, String message) { + writeUE(out, MathUtil.golomb(value)); + trace(message, value); + } + + public static void writeTE(BitWriter out, int value, int max) { + if (max > 1) + writeUE(out, value); + else + out.write1Bit(~value & 0x1); + } + + public static void writeBool(BitWriter out, boolean value, String message) { + out.write1Bit(value ? 1 : 0); + trace(message, value ? 1 : 0); + } + + public static void writeU(BitWriter out, int i, int n) { + out.writeNBit(i, n); + } + + public static void writeNBit(BitWriter out, long value, int n, String message) { + for (int i = 0; i < n; i++) { + out.write1Bit((int) (value >> (n - i - 1)) & 0x1); + } + trace(message, value); + } + + public static void writeTrailingBits(BitWriter out) { + out.write1Bit(1); + out.flush(); + } + + public static void writeSliceTrailingBits() { + throw new NotImplementedException("todo"); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/write/NALUnitWriter.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/write/NALUnitWriter.java new file mode 100644 index 0000000..7b169f5 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/write/NALUnitWriter.java @@ -0,0 +1,54 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.write; + +import org.monte.media.impl.jcodec.codecs.h264.io.model.NALUnit; +import org.monte.media.impl.jcodec.common.io.NIOUtils; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class NALUnitWriter { + private final WritableByteChannel to; + private static ByteBuffer _MARKER = ByteBuffer.allocate(4); + + static { + _MARKER.putInt(1); + _MARKER.flip(); + } + + public NALUnitWriter(WritableByteChannel to) { + this.to = to; + } + + public void writeUnit(NALUnit nal, ByteBuffer data) throws IOException { + ByteBuffer emprev = ByteBuffer.allocate(data.remaining() + 1024); + NIOUtils.write(emprev, _MARKER); + nal.write(emprev); + emprev(emprev, data); + emprev.flip(); + to.write(emprev); + } + + private void emprev(ByteBuffer emprev, ByteBuffer data) { + ByteBuffer dd = data.duplicate(); + int prev1 = 1, prev2 = 1; + while (dd.hasRemaining()) { + byte b = dd.get(); + if (prev1 == 0 && prev2 == 0 && ((b & 0x3) == b)) { + prev2 = prev1; + prev1 = 3; + emprev.put((byte) 3); + } + + prev2 = prev1; + prev1 = b; + emprev.put((byte) b); + } + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/write/SliceHeaderWriter.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/write/SliceHeaderWriter.java new file mode 100644 index 0000000..51eff6c --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/io/write/SliceHeaderWriter.java @@ -0,0 +1,239 @@ +package org.monte.media.impl.jcodec.codecs.h264.io.write; + +import org.monte.media.impl.jcodec.codecs.h264.io.model.PictureParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.RefPicMarking; +import org.monte.media.impl.jcodec.codecs.h264.io.model.RefPicMarking.Instruction; +import org.monte.media.impl.jcodec.codecs.h264.io.model.RefPicMarkingIDR; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceHeader; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SliceType; +import org.monte.media.impl.jcodec.common.io.BitWriter; + +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeBool; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeSEtrace; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeU; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeUEtrace; +import static org.monte.media.impl.jcodec.codecs.h264.io.write.CAVLCWriter.writeUtrace; +import static org.monte.media.impl.jcodec.common.model.ColorSpace.MONO; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * A writer for slice header data structure + * + * @author The JCodec project + */ +public class SliceHeaderWriter { + private SliceHeaderWriter() { + } + + public static void write(SliceHeader sliceHeader, boolean idrSlice, int nalRefIdc, BitWriter writer) { + SeqParameterSet sps = sliceHeader.sps; + PictureParameterSet pps = sliceHeader.pps; + writeUEtrace(writer, sliceHeader.firstMbInSlice, "SH: first_mb_in_slice"); + writeUEtrace(writer, sliceHeader.sliceType.ordinal() + (sliceHeader.sliceTypeRestr ? 5 : 0), "SH: slice_type"); + writeUEtrace(writer, sliceHeader.picParameterSetId, "SH: pic_parameter_set_id"); + if (sliceHeader.frameNum > (1 << (sps.log2MaxFrameNumMinus4 + 4))) { + throw new IllegalArgumentException("frame_num > " + (1 << (sps.log2MaxFrameNumMinus4 + 4))); + } + writeUtrace(writer, sliceHeader.frameNum, sps.log2MaxFrameNumMinus4 + 4, "SH: frame_num"); + if (!sps.frameMbsOnlyFlag) { + writeBool(writer, sliceHeader.fieldPicFlag, "SH: field_pic_flag"); + if (sliceHeader.fieldPicFlag) { + writeBool(writer, sliceHeader.bottomFieldFlag, "SH: bottom_field_flag"); + } + } + if (idrSlice) { + writeUEtrace(writer, sliceHeader.idrPicId, "SH: idr_pic_id"); + } + if (sps.picOrderCntType == 0) { + if (sliceHeader.picOrderCntLsb > (1 << (sps.log2MaxPicOrderCntLsbMinus4 + 4))) { + throw new IllegalArgumentException("pic_order_cnt_lsb > " + (1 << (sps.log2MaxPicOrderCntLsbMinus4 + 4))); + } + writeU(writer, sliceHeader.picOrderCntLsb, sps.log2MaxPicOrderCntLsbMinus4 + 4); + if (pps.picOrderPresentFlag && !sps.fieldPicFlag) { + writeSEtrace(writer, sliceHeader.deltaPicOrderCntBottom, "SH: delta_pic_order_cnt_bottom"); + } + } + if (sps.picOrderCntType == 1 && !sps.deltaPicOrderAlwaysZeroFlag) { + writeSEtrace(writer, sliceHeader.deltaPicOrderCnt[0], "SH: delta_pic_order_cnt"); + if (pps.picOrderPresentFlag && !sps.fieldPicFlag) + writeSEtrace(writer, sliceHeader.deltaPicOrderCnt[1], "SH: delta_pic_order_cnt"); + } + if (pps.redundantPicCntPresentFlag) { + writeUEtrace(writer, sliceHeader.redundantPicCnt, "SH: redundant_pic_cnt"); + } + if (sliceHeader.sliceType == SliceType.B) { + writeBool(writer, sliceHeader.directSpatialMvPredFlag, "SH: direct_spatial_mv_pred_flag"); + } + if (sliceHeader.sliceType == SliceType.P || sliceHeader.sliceType == SliceType.SP + || sliceHeader.sliceType == SliceType.B) { + writeBool(writer, sliceHeader.numRefIdxActiveOverrideFlag, "SH: num_ref_idx_active_override_flag"); + if (sliceHeader.numRefIdxActiveOverrideFlag) { + writeUEtrace(writer, sliceHeader.numRefIdxActiveMinus1[0], "SH: num_ref_idx_l0_active_minus1"); + if (sliceHeader.sliceType == SliceType.B) { + writeUEtrace(writer, sliceHeader.numRefIdxActiveMinus1[1], "SH: num_ref_idx_l1_active_minus1"); + } + } + } + writeRefPicListReordering(sliceHeader, writer); + if ((pps.weightedPredFlag && (sliceHeader.sliceType == SliceType.P || sliceHeader.sliceType == SliceType.SP)) + || (pps.weightedBipredIdc == 1 && sliceHeader.sliceType == SliceType.B)) + writePredWeightTable(sliceHeader, writer); + if (nalRefIdc != 0) + writeDecRefPicMarking(sliceHeader, idrSlice, writer); + if (pps.entropyCodingModeFlag && sliceHeader.sliceType.isInter()) { + writeUEtrace(writer, sliceHeader.cabacInitIdc, "SH: cabac_init_idc"); + } + writeSEtrace(writer, sliceHeader.sliceQpDelta, "SH: slice_qp_delta"); + if (sliceHeader.sliceType == SliceType.SP || sliceHeader.sliceType == SliceType.SI) { + if (sliceHeader.sliceType == SliceType.SP) { + writeBool(writer, sliceHeader.spForSwitchFlag, "SH: sp_for_switch_flag"); + } + writeSEtrace(writer, sliceHeader.sliceQsDelta, "SH: slice_qs_delta"); + } + if (pps.deblockingFilterControlPresentFlag) { + writeUEtrace(writer, sliceHeader.disableDeblockingFilterIdc, "SH: disable_deblocking_filter_idc"); + if (sliceHeader.disableDeblockingFilterIdc != 1) { + writeSEtrace(writer, sliceHeader.sliceAlphaC0OffsetDiv2, "SH: slice_alpha_c0_offset_div2"); + writeSEtrace(writer, sliceHeader.sliceBetaOffsetDiv2, "SH: slice_beta_offset_div2"); + } + } + if (pps.numSliceGroupsMinus1 > 0 && pps.sliceGroupMapType >= 3 && pps.sliceGroupMapType <= 5) { + int len = (sps.picHeightInMapUnitsMinus1 + 1) * (sps.picWidthInMbsMinus1 + 1) + / (pps.sliceGroupChangeRateMinus1 + 1); + if (((sps.picHeightInMapUnitsMinus1 + 1) * (sps.picWidthInMbsMinus1 + 1)) + % (pps.sliceGroupChangeRateMinus1 + 1) > 0) + len += 1; + + len = CeilLog2(len + 1); + writeU(writer, sliceHeader.sliceGroupChangeCycle, len); + } + + } + + private static int CeilLog2(int uiVal) { + int uiTmp = uiVal - 1; + int uiRet = 0; + + while (uiTmp != 0) { + uiTmp >>= 1; + uiRet++; + } + return uiRet; + } + + private static void writeDecRefPicMarking(SliceHeader sliceHeader, boolean idrSlice, BitWriter writer) { + if (idrSlice) { + RefPicMarkingIDR drpmidr = sliceHeader.refPicMarkingIDR; + writeBool(writer, drpmidr.isDiscardDecodedPics(), "SH: no_output_of_prior_pics_flag"); + writeBool(writer, drpmidr.isUseForlongTerm(), "SH: long_term_reference_flag"); + } else { + writeBool(writer, sliceHeader.refPicMarkingNonIDR != null, "SH: adaptive_ref_pic_marking_mode_flag"); + if (sliceHeader.refPicMarkingNonIDR != null) { + RefPicMarking drpmidr = sliceHeader.refPicMarkingNonIDR; + Instruction[] instructions = drpmidr.getInstructions(); + for (int i = 0; i < instructions.length; i++) { + Instruction mmop = instructions[i]; + switch (mmop.getType()) { + case REMOVE_SHORT: + writeUEtrace(writer, 1, "SH: memory_management_control_operation"); + writeUEtrace(writer, mmop.getArg1() - 1, "SH: difference_of_pic_nums_minus1"); + break; + case REMOVE_LONG: + writeUEtrace(writer, 2, "SH: memory_management_control_operation"); + writeUEtrace(writer, mmop.getArg1(), "SH: long_term_pic_num"); + break; + case CONVERT_INTO_LONG: + writeUEtrace(writer, 3, "SH: memory_management_control_operation"); + writeUEtrace(writer, mmop.getArg1() - 1, "SH: difference_of_pic_nums_minus1"); + writeUEtrace(writer, mmop.getArg2(), "SH: long_term_frame_idx"); + break; + case TRUNK_LONG: + writeUEtrace(writer, 4, "SH: memory_management_control_operation"); + writeUEtrace(writer, mmop.getArg1() + 1, "SH: max_long_term_frame_idx_plus1"); + break; + case CLEAR: + writeUEtrace(writer, 5, "SH: memory_management_control_operation"); + break; + case MARK_LONG: + writeUEtrace(writer, 6, "SH: memory_management_control_operation"); + writeUEtrace(writer, mmop.getArg1(), "SH: long_term_frame_idx"); + break; + } + } + writeUEtrace(writer, 0, "SH: memory_management_control_operation"); + } + } + } + + private static void writePredWeightTable(SliceHeader sliceHeader, BitWriter writer) { + SeqParameterSet sps = sliceHeader.sps; + writeUEtrace(writer, sliceHeader.predWeightTable.lumaLog2WeightDenom, "SH: luma_log2_weight_denom"); + if (sps.chromaFormatIdc != MONO) { + writeUEtrace(writer, sliceHeader.predWeightTable.chromaLog2WeightDenom, "SH: chroma_log2_weight_denom"); + } + + writeOffsetWeight(sliceHeader, writer, 0); + if (sliceHeader.sliceType == SliceType.B) { + writeOffsetWeight(sliceHeader, writer, 1); + } + } + + private static void writeOffsetWeight(SliceHeader sliceHeader, BitWriter writer, int list) { + SeqParameterSet sps = sliceHeader.sps; + int defaultLW = 1 << sliceHeader.predWeightTable.lumaLog2WeightDenom; + int defaultCW = 1 << sliceHeader.predWeightTable.chromaLog2WeightDenom; + + for (int i = 0; i < sliceHeader.predWeightTable.lumaWeight[list].length; i++) { + boolean flagLuma = sliceHeader.predWeightTable.lumaWeight[list][i] != defaultLW + || sliceHeader.predWeightTable.lumaOffset[list][i] != 0; + writeBool(writer, flagLuma, "SH: luma_weight_l0_flag"); + if (flagLuma) { + writeSEtrace(writer, sliceHeader.predWeightTable.lumaWeight[list][i], "SH: luma_weight_l" + list); + writeSEtrace(writer, sliceHeader.predWeightTable.lumaOffset[list][i], "SH: luma_offset_l" + list); + } + if (sps.chromaFormatIdc != MONO) { + boolean flagChroma = sliceHeader.predWeightTable.chromaWeight[list][0][i] != defaultCW + || sliceHeader.predWeightTable.chromaOffset[list][0][i] != 0 + || sliceHeader.predWeightTable.chromaWeight[list][1][i] != defaultCW + || sliceHeader.predWeightTable.chromaOffset[list][1][i] != 0; + writeBool(writer, flagChroma, "SH: chroma_weight_l0_flag"); + if (flagChroma) + for (int j = 0; j < 2; j++) { + writeSEtrace(writer, sliceHeader.predWeightTable.chromaWeight[list][j][i], "SH: chroma_weight_l" + list); + writeSEtrace(writer, sliceHeader.predWeightTable.chromaOffset[list][j][i], "SH: chroma_offset_l" + list); + } + } + } + } + + private static void writeRefPicListReordering(SliceHeader sliceHeader, BitWriter writer) { + if (sliceHeader.sliceType.isInter()) { + boolean l0ReorderingPresent = sliceHeader.refPicReordering != null + && sliceHeader.refPicReordering[0] != null; + writeBool(writer, l0ReorderingPresent, "SH: ref_pic_list_reordering_flag_l0"); + if (l0ReorderingPresent) + writeReorderingList(sliceHeader.refPicReordering[0], writer); + } + if (sliceHeader.sliceType == SliceType.B) { + boolean l1ReorderingPresent = sliceHeader.refPicReordering != null + && sliceHeader.refPicReordering[1] != null; + writeBool(writer, l1ReorderingPresent, "SH: ref_pic_list_reordering_flag_l1"); + if (l1ReorderingPresent) + writeReorderingList(sliceHeader.refPicReordering[1], writer); + } + } + + private static void writeReorderingList(int[][] reordering, BitWriter writer) { + if (reordering == null) + return; + + for (int i = 0; i < reordering[0].length; i++) { + writeUEtrace(writer, reordering[0][i], "SH: reordering_of_pic_nums_idc"); + writeUEtrace(writer, reordering[1][i], "SH: abs_diff_pic_num_minus1"); + } + writeUEtrace(writer, 3, "SH: reordering_of_pic_nums_idc"); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/mp4/AvcCBox.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/mp4/AvcCBox.java new file mode 100644 index 0000000..033f721 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/codecs/h264/mp4/AvcCBox.java @@ -0,0 +1,183 @@ +package org.monte.media.impl.jcodec.codecs.h264.mp4; + +import org.monte.media.impl.jcodec.common.io.NIOUtils; +import org.monte.media.impl.jcodec.containers.mp4.boxes.Box; +import org.monte.media.impl.jcodec.containers.mp4.boxes.Header; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import static org.monte.media.impl.jcodec.common.Preconditions.checkState; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Creates MP4 file out of a set of samples + * + * @author The JCodec project + */ +public class AvcCBox extends Box { + + private int profile; + private int profileCompat; + private int level; + private int nalLengthSize; + + private List spsList; + private List ppsList; + + public AvcCBox(Header header) { + super(header); + this.spsList = new ArrayList(); + this.ppsList = new ArrayList(); + } + + public static String fourcc() { + return "avcC"; + } + + public static AvcCBox parseAvcCBox(ByteBuffer buf) { + AvcCBox avcCBox = new AvcCBox(new Header(fourcc())); + avcCBox.parse(buf); + return avcCBox; + } + + public static AvcCBox createEmpty() { + return new AvcCBox(new Header(fourcc())); + } + + public static AvcCBox createAvcCBox(int profile, int profileCompat, int level, int nalLengthSize, + List spsList, List ppsList) { + AvcCBox avcc = new AvcCBox(new Header(fourcc())); + avcc.profile = profile; + avcc.profileCompat = profileCompat; + avcc.level = level; + avcc.nalLengthSize = nalLengthSize; + avcc.spsList = spsList; + avcc.ppsList = ppsList; + return avcc; + } + + @Override + public void parse(ByteBuffer input) { + NIOUtils.skip(input, 1); + profile = input.get() & 0xff; + profileCompat = input.get() & 0xff; + level = input.get() & 0xff; + int flags = input.get() & 0xff; + nalLengthSize = (flags & 0x03) + 1; + + int nSPS = input.get() & 0x1f; // 3 bits reserved + 5 bits number of + // sps + for (int i = 0; i < nSPS; i++) { + int spsSize = input.getShort(); + checkState(0x27 == (input.get() & 0x3f)); + spsList.add(NIOUtils.read(input, spsSize - 1)); + } + + int nPPS = input.get() & 0xff; + for (int i = 0; i < nPPS; i++) { + int ppsSize = input.getShort(); + checkState(0x28 == (input.get() & 0x3f)); + ppsList.add(NIOUtils.read(input, ppsSize - 1)); + } + } + + @Override + public void doWrite(ByteBuffer out) { + out.put((byte) 0x1); // version + out.put((byte) profile); + out.put((byte) profileCompat); + out.put((byte) level); + out.put((byte) ((nalLengthSize - 1) | 0xf8)); + + out.put((byte) (spsList.size() | 0xe0)); + for (ByteBuffer sps : spsList) { + out.putShort((short) (sps.remaining() + 1)); + out.put((byte) 0x67); + NIOUtils.write(out, sps); + } + + out.put((byte) ppsList.size()); + for (ByteBuffer pps : ppsList) { + out.putShort((byte) (pps.remaining() + 1)); + out.put((byte) 0x68); + NIOUtils.write(out, pps); + } + } + + @Override + public int estimateSize() { + int sz = 17; + for (ByteBuffer sps : spsList) { + sz += 3 + sps.remaining(); + } + + for (ByteBuffer pps : ppsList) { + sz += 3 + pps.remaining(); + } + return sz; + } + + public int getProfile() { + return profile; + } + + public int getProfileCompat() { + return profileCompat; + } + + public int getLevel() { + return level; + } + + public List getSpsList() { + return spsList; + } + + public List getPpsList() { + return ppsList; + } + + public int getNalLengthSize() { + return nalLengthSize; + } + + // public void toNAL(ByteBuffer codecPrivate) { + // H264Utils.toNAL(codecPrivate, getSpsList(), getPpsList()); + // } + // + // public ByteBuffer toNAL() { + // ByteBuffer bb = ByteBuffer.allocate(2048); + // H264Utils.toNAL(bb, getSpsList(), getPpsList()); + // bb.flip(); + // return bb; + // } + // + // public static AvcCBox fromNAL(ByteBuffer codecPrivate) { + // List spsList = new ArrayList(); + // List ppsList = new ArrayList(); + // + // ByteBuffer dup = codecPrivate.duplicate(); + // + // ByteBuffer buf; + // SeqParameterSet sps = null; + // while ((buf = H264Utils.nextNALUnit(dup)) != null) { + // NALUnit nu = NALUnit.read(buf); + // + // H264Utils.unescapeNAL(buf); + // + // if (nu.type == NALUnitType.PPS) { + // ppsList.add(buf); + // } else if (nu.type == NALUnitType.SPS) { + // spsList.add(buf); + // sps = SeqParameterSet.read(buf.duplicate()); + // } + // } + // if (spsList.size() == 0 || ppsList.size() == 0) + // return null; + // return new AvcCBox(sps.profile_idc, 0, sps.level_idc, spsList, ppsList); + // } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/ArrayUtil.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/ArrayUtil.java new file mode 100644 index 0000000..79e1923 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/ArrayUtil.java @@ -0,0 +1,565 @@ +package org.monte.media.impl.jcodec.common; + +import static java.lang.System.arraycopy; + +import java.lang.StringBuilder; +import java.lang.System; +import java.lang.reflect.Array; +import java.util.List; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author Jay Codec + */ +public class ArrayUtil { + + public static void shiftRight1(T[] array) { + for (int i = 1; i < array.length; i++) { + array[i] = array[i - 1]; + } + array[0] = null; + } + + public static void shiftLeft1(T[] array) { + for (int i = 0; i < array.length - 1; i++) { + array[i] = array[i + 1]; + } + array[array.length - 1] = null; + } + + public static void shiftRight3(T[] array, int from, int to) { + for (int i = to - 1; i > from; i--) { + array[i] = array[i - 1]; + } + array[from] = null; + } + + public static void shiftLeft3(T[] array, int from, int to) { + for (int i = from; i < to - 1; i++) { + array[i] = array[i + 1]; + } + array[to - 1] = null; + } + + public static void shiftLeft2(T[] array, int from) { + shiftLeft3(array, from, array.length); + } + + public static void shiftRight2(T[] array, int to) { + shiftRight3(array, 0, to); + } + + public static final void swap(int[] arr, int ind1, int ind2) { + if (ind1 == ind2) + return; + int tmp = arr[ind1]; + arr[ind1] = arr[ind2]; + arr[ind2] = tmp; + } + + public static final int sumInt(int[] array) { + int result = 0; + for (int i = 0; i < array.length; i++) { + result += array[i]; + } + return result; + } + + public static final int sumByte(byte[] array) { + int result = 0; + for (int i = 0; i < array.length; i++) { + result += array[i]; + } + return result; + } + + public static int sumInt3(int[] array, int from, int count) { + int result = 0; + for (int i = from; i < from + count; i++) { + result += array[i]; + } + return result; + } + + public static int sumByte3(byte[] array, int from, int count) { + int result = 0; + for (int i = from; i < from + count; i++) { + result += array[i]; + } + return result; + } + + public static void addInt(int[] array, int val) { + for (int i = 0; i < array.length; i++) + array[i] += val; + } + + public static int[] addAllInt(int[] array1, int[] array2) { + if (array1 == null) { + return cloneInt(array2); + } else if (array2 == null) { + return cloneInt(array1); + } + int[] joinedArray = new int[array1.length + array2.length]; + arraycopy(array1, 0, joinedArray, 0, array1.length); + arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } + + public static long[] addAllLong(long[] array1, long[] array2) { + if (array1 == null) { + return cloneLong(array2); + } else if (array2 == null) { + return cloneLong(array1); + } + long[] joinedArray = new long[array1.length + array2.length]; + arraycopy(array1, 0, joinedArray, 0, array1.length); + arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } + + public static Object[] addAllObj(Object[] array1, Object[] array2) { + if (array1 == null) { + return cloneObj(array2); + } else if (array2 == null) { + return cloneObj(array1); + } + Object[] joinedArray = (Object[]) Array.newInstance(array1.getClass().getComponentType(), array1.length + + array2.length); + arraycopy(array1, 0, joinedArray, 0, array1.length); + arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } + + public static int[] cloneInt(int[] array) { + if (array == null) { + return null; + } + return (int[]) array.clone(); + } + + public static long[] cloneLong(long[] array) { + if (array == null) { + return null; + } + return (long[]) array.clone(); + } + + public static Object[] cloneObj(Object[] array) { + if (array == null) { + return null; + } + return (Object[]) array.clone(); + } + + public static byte[] toByteArrayShifted(int[] array) { + byte[] result = new byte[array.length]; + for (int i = 0; i < array.length; i++) + result[i] = (byte) (array[i] - 128); + return result; + } + + public static byte[][] toByteArrayShifted2(int[][] intArray) { + byte[][] result = new byte[intArray.length][]; + for (int i = 0; i < intArray.length; i++) { + result[i] = toByteArrayShifted(intArray[i]); + } + return result; + } + + public static int[] toIntArrayUnshifted(byte[] bytes) { + int[] result = new int[bytes.length]; + for (int i = 0; i < result.length; i++) + result[i] = (byte) (bytes[i] + 128); + + return result; + } + + public static byte[] toByteArray(int[] ints) { + byte[] result = new byte[ints.length]; + for (int i = 0; i < ints.length; i++) + result[i] = (byte) ints[i]; + return result; + } + + public static int[] toIntArray(byte[] bytes) { + int[] result = new int[bytes.length]; + for (int i = 0; i < result.length; i++) + result[i] = bytes[i]; + + return result; + } + + public static int[] toUnsignedIntArray(byte[] bytes) { + int[] result = new int[bytes.length]; + for (int i = 0; i < bytes.length; i++) + result[i] = bytes[i] & 0xff; + return result; + } + + public static void reverse(T[] frames) { + for (int i = 0, j = frames.length - 1; i < frames.length >> 1; ++i, --j) { + T tmp = frames[i]; + frames[i] = frames[j]; + frames[j] = tmp; + } + } + + public static int max(int[] array) { + int max = Integer.MIN_VALUE; + for (int i = 0; i < array.length; i++) { + if (array[i] > max) { + max = array[i]; + } + } + + return max; + } + + public static int[][] rotate(int[][] src) { + int[][] dst = new int[src[0].length][src.length]; + for (int i = 0; i < src.length; i++) { + for (int j = 0; j < src[0].length; j++) { + dst[j][i] = src[i][j]; + } + } + return dst; + } + + public static byte[][] create2D(int width, int height) { + byte[][] result = new byte[height][]; + for (int i = 0; i < height; i++) + result[i] = new byte[width]; + return result; + } + + public static void printMatrixBytes(byte[] array, String format, int width) { + String[] strings = new String[array.length]; + int maxLen = 0; + for (int i = 0; i < array.length; i++) { + strings[i] = String.format(format, array[i]); + maxLen = Math.max(maxLen, strings[i].length()); + } + for (int ind = 0; ind < strings.length; ) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < width && ind < strings.length; i++, ind++) { + for (int j = 0; j < maxLen - strings[ind].length() + 1; j++) + builder.append(" "); + builder.append(strings[ind]); + } + System.out.println(builder); + } + } + + public static void printMatrix(int[] array, String format, int width) { + String[] strings = new String[array.length]; + int maxLen = 0; + for (int i = 0; i < array.length; i++) { + strings[i] = String.format(format, array[i]); + maxLen = Math.max(maxLen, strings[i].length()); + } + for (int ind = 0; ind < strings.length; ) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < width && ind < strings.length; i++, ind++) { + for (int j = 0; j < maxLen - strings[ind].length() + 1; j++) + builder.append(" "); + builder.append(strings[ind]); + } + System.out.println(builder); + } + } + + public static byte[] padLeft(byte[] array, int padLength) { + byte[] result = new byte[array.length + padLength]; + for (int i = padLength; i < result.length; i++) + result[i] = array[i - padLength]; + return result; + } + + public static int[] randomIntArray(int size, int from, int to) { + int width = to - from; + int[] result = new int[size]; + for (int i = 0; i < size; i++) + result[i] = (int) ((Math.random() * width) % width) + from; + return result; + } + + public static byte[] randomByteArray(int size, byte from, byte to) { + byte width = (byte) (to - from); + byte[] result = new byte[size]; + for (int i = 0; i < size; i++) + result[i] = (byte) (((Math.random() * width) % width) + from); + return result; + } + + /** + * Implements a quicksort algorithm + */ + public static void quickSort(int[] a, int start, int end, int[] p) { + int len = end - start; + if (len < 2) { + return; + } else { + int startPlus1 = start + 1; + if (len == 2) { + if (a[start] > a[startPlus1]) { + swap(a, start, startPlus1); + if (p != null) + swap(p, start, startPlus1); + } + return; + } else if (len == 3) { + if (a[start] > a[startPlus1]) { + swap(a, start, startPlus1); + if (p != null) + swap(p, start, startPlus1); + } + int startPlus2 = start + 2; + if (a[startPlus1] > a[startPlus2]) { + swap(a, startPlus1, startPlus2); + if (p != null) + swap(p, startPlus1, startPlus2); + } + if (a[start] > a[startPlus1]) { + swap(a, start, startPlus1); + if (p != null) + swap(p, start, startPlus1); + } + } + } + int pivot = a[0]; + + // partially sort + int p_large = end - 1; + for (int i = end - 1; i >= start; i--) { + if (a[i] > pivot) { + swap(a, i, p_large); + if (p != null) + swap(p, i, p_large); + p_large--; + } + } + swap(a, start, p_large); + if (p != null) + swap(p, start, p_large); + + quickSort(a, start, p_large, p); + quickSort(a, p_large + 1, end, p); + } + + public static int[] flatten5D(int[][][][][] is) { + IntArrayList list = new IntArrayList(128); + flatten5DL(is, list); + return list.toArray(); + } + + public static int[] flatten4D(int[][][][] is) { + IntArrayList list = new IntArrayList(128); + flatten4DL(is, list); + return list.toArray(); + } + + public static int[] flatten3D(int[][][] is) { + IntArrayList list = new IntArrayList(128); + flatten3DL(is, list); + return list.toArray(); + } + + public static int[] flatten2D(int[][] is) { + IntArrayList list = new IntArrayList(128); + flatten2DL(is, list); + return list.toArray(); + } + + private static void flatten5DL(int[][][][][] is, IntArrayList list) { + for (int i = 0; i < is.length; i++) { + flatten4DL(is[i], list); + } + } + + private static void flatten4DL(int[][][][] is, IntArrayList list) { + for (int i = 0; i < is.length; i++) { + flatten3DL(is[i], list); + } + } + + private static void flatten3DL(int[][][] is, IntArrayList list) { + for (int i = 0; i < is.length; i++) { + flatten2DL(is[i], list); + } + } + + private static void flatten2DL(int[][] is, IntArrayList list) { + for (int i = 0; i < is.length; i++) { + flatten1DL(is[i], list); + } + } + + private static void flatten1DL(int[] is, IntArrayList list) { + for (int i = 0; i < is.length; i++) { + list.add(is[i]); + } + } + + public static void copy6D(int[][][][][][] to, int[][][][][][] from) { + for (int i = 0; i < Math.min(to.length, from.length); i++) { + copy5D(to[i], from[i]); + } + } + + public static void copy5D(int[][][][][] to, int[][][][][] from) { + for (int i = 0; i < Math.min(to.length, from.length); i++) { + copy4D(to[i], from[i]); + } + } + + public static void copy4D(int[][][][] to, int[][][][] from) { + for (int i = 0; i < Math.min(to.length, from.length); i++) { + copy3D(to[i], from[i]); + } + } + + public static void copy3D(int[][][] to, int[][][] from) { + for (int i = 0; i < Math.min(to.length, from.length); i++) { + copy2D(to[i], from[i]); + } + } + + public static void copy2D(int[][] to, int[][] from) { + for (int i = 0; i < Math.min(to.length, from.length); i++) { + copy1D(to[i], from[i]); + } + } + + public static void copy1D(int[] to, int[] from) { + for (int i = 0; i < Math.min(to.length, from.length); i++) { + to[i] = from[i]; + } + } + + public static int fill6D(int[][][][][][] to, int[] from, int index) { + for (int i = 0; i < to.length; i++) { + index = fill5D(to[i], from, index); + } + return index; + } + + public static int fill5D(int[][][][][] to, int[] from, int index) { + for (int i = 0; i < to.length; i++) { + index = fill4D(to[i], from, index); + } + return index; + } + + public static int fill4D(int[][][][] to, int[] from, int index) { + for (int i = 0; i < to.length; i++) { + index = fill3D(to[i], from, index); + } + return index; + } + + public static int fill3D(int[][][] to, int[] from, int index) { + for (int i = 0; i < to.length; i++) { + index = fill2D(to[i], from, index); + } + return index; + } + + public static int fill2D(int[][] to, int[] from, int index) { + for (int i = 0; i < to.length; i++) { + index = fill1D(to[i], from, index); + } + return index; + } + + public static int fill1D(int[] to, int[] from, int index) { + for (int i = 0; i < to.length; i++) { + to[i] = from[index++]; + } + return index; + } + + public static int fill6D(short[][][][][][] to, short[] from, int index) { + for (int i = 0; i < to.length; i++) { + index = fill5D(to[i], from, index); + } + return index; + } + + public static int fill5D(short[][][][][] to, short[] from, int index) { + for (int i = 0; i < to.length; i++) { + index = fill4D(to[i], from, index); + } + return index; + } + + public static int fill4D(short[][][][] to, short[] from, int index) { + for (int i = 0; i < to.length; i++) { + index = fill3D(to[i], from, index); + } + return index; + } + + public static int fill3D(short[][][] to, short[] from, int index) { + for (int i = 0; i < to.length; i++) { + index = fill2D(to[i], from, index); + } + return index; + } + + public static int fill2D(short[][] to, short[] from, int index) { + for (int i = 0; i < to.length; i++) { + index = fill1D(to[i], from, index); + } + return index; + } + + public static int fill1D(short[] to, short[] from, int index) { + for (int i = 0; i < to.length; i++) { + to[i] = from[index++]; + } + return index; + } + + + public static int[] flatten2DL(List sampleSizes) { + int size = 0; + for (int[] is : sampleSizes) { + size += is.length; + } + int idx = 0; + int[] ret = new int[size]; + for (int[] is : sampleSizes) { + for (int i = 0; i < is.length; i++, idx++) { + ret[idx] = is[i]; + } + } + return ret; + } + + // 116, 114, 97, 107 + public static int find(byte[] bytes, int off, byte[] needle) { + for (int i = off; i < bytes.length; i++) { + boolean eq = true; + int j = 0; + int min = Math.min(needle.length, bytes.length - i); + for (; j < min; j++) { + eq &= bytes[i + j] == needle[j]; + } + if (eq && j == min) + return i; + } + return -1; + } + + public static int instances(byte[] bytes, byte[] bs) { + int idx = 0; + int count = 0; + while ((idx = find(bytes, idx + bs.length, bs)) != -1) { + count++; + } + return count; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/AutoFileChannelWrapper.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/AutoFileChannelWrapper.java new file mode 100644 index 0000000..3492e23 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/AutoFileChannelWrapper.java @@ -0,0 +1,115 @@ +package org.monte.media.impl.jcodec.common; + +import org.monte.media.impl.jcodec.common.io.AutoPool; +import org.monte.media.impl.jcodec.common.io.AutoResource; +import org.monte.media.impl.jcodec.common.io.SeekableByteChannel; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +import static java.lang.System.currentTimeMillis; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class AutoFileChannelWrapper implements SeekableByteChannel, AutoResource { + + private static final long THRESHOLD = 5000; // five seconds + + private FileChannel ch; + private File file; + private long savedPos; + private long curTime; + private long accessTime; + + public AutoFileChannelWrapper(File file) throws IOException { + this.file = file; + this.curTime = currentTimeMillis(); + AutoPool.getInstance().add(this); + ensureOpen(); + } + + private void ensureOpen() throws IOException { + accessTime = curTime; + if (ch == null || !ch.isOpen()) { + ch = new FileInputStream(file).getChannel(); + ch.position(savedPos); + } + } + + @Override + public int read(ByteBuffer arg0) throws IOException { + ensureOpen(); + int r = ch.read(arg0); + savedPos = ch.position(); + return r; + } + + @Override + public void close() throws IOException { + if (ch != null && ch.isOpen()) { + savedPos = ch.position(); + ch.close(); + ch = null; + } + } + + @Override + public boolean isOpen() { + return ch != null && ch.isOpen(); + } + + @Override + public int write(ByteBuffer arg0) throws IOException { + ensureOpen(); + int w = ch.write(arg0); + savedPos = ch.position(); + return w; + } + + @Override + public long position() throws IOException { + ensureOpen(); + return ch.position(); + } + + @Override + public SeekableByteChannel setPosition(long newPosition) throws IOException { + ensureOpen(); + ch.position(newPosition); + savedPos = newPosition; + return this; + } + + @Override + public long size() throws IOException { + ensureOpen(); + return ch.size(); + } + + @Override + public SeekableByteChannel truncate(long size) throws IOException { + ensureOpen(); + ch.truncate(size); + savedPos = ch.position(); + return this; + } + + @Override + public void setCurTime(long curTime) { + this.curTime = curTime; + if (ch != null && ch.isOpen() && curTime - accessTime > THRESHOLD) { + try { + close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/CodecMeta.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/CodecMeta.java new file mode 100644 index 0000000..92f027f --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/CodecMeta.java @@ -0,0 +1,27 @@ +package org.monte.media.impl.jcodec.common; + +import java.nio.ByteBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class CodecMeta { + private String fourcc; + private ByteBuffer codecPrivate; + + public CodecMeta(String fourcc, ByteBuffer codecPrivate) { + this.fourcc = fourcc; + this.codecPrivate = codecPrivate; + } + + public String getFourcc() { + return fourcc; + } + + public ByteBuffer getCodecPrivate() { + return codecPrivate; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/IntArrayList.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/IntArrayList.java new file mode 100644 index 0000000..5b7a988 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/IntArrayList.java @@ -0,0 +1,179 @@ +package org.monte.media.impl.jcodec.common; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static java.lang.System.arraycopy; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Internal storage is a list of primitive arrays + * + * @author The JCodec project + */ +public class IntArrayList { + private static final int DEFAULT_GROW_AMOUNT = 1 << 8; + + private int[] storage; + private int _start; + private int _size; + private List chunks; + private int growAmount; + + public static IntArrayList createIntArrayList() { + return new IntArrayList(DEFAULT_GROW_AMOUNT); + } + + public IntArrayList(int growAmount) { + this.chunks = new ArrayList(); + this.growAmount = growAmount; + this.storage = new int[growAmount]; + } + + public int[] toArray() { + int[] result = new int[_size + chunks.size() * growAmount - _start]; + int off = 0; + for (int i = 0; i < chunks.size(); i++) { + int[] chunk = chunks.get(i); + int aoff = i == 0 ? _start : 0; + arraycopy(chunk, aoff, result, off, growAmount - aoff); + off += growAmount; + } + int aoff = chunks.size() == 0 ? _start : 0; + arraycopy(storage, aoff, result, off, _size - aoff); + return result; + } + + public void add(int val) { + if (_size >= storage.length) { + chunks.add(storage); + storage = new int[growAmount]; + _size = 0; + } + storage[_size++] = val; + } + + public void push(int id) { + this.add(id); + } + + public void pop() { + if (_size == 0) { + if (chunks.size() == 0) + return; + storage = chunks.remove(chunks.size() - 1); + _size = growAmount; + } + if (chunks.size() == 0 && _size == _start) + return; + _size--; + } + + public void set(int index, int value) { + index += _start; + int chunk = index / growAmount; + int off = index % growAmount; + + if (chunk < chunks.size()) + chunks.get(chunk)[off] = value; + else + storage[off] = value; + } + + public int get(int index) { + index += _start; + int chunk = index / growAmount; + int off = index % growAmount; + return chunk < chunks.size() ? chunks.get(chunk)[off] : storage[off]; + } + + public int shift() { + if (chunks.size() == 0 && _start >= _size) { + throw new IllegalStateException(); + } + int ret = get(0); + ++_start; + if (chunks.size() != 0 && _start >= growAmount) { + chunks.remove(0); + _start = 0; + } + return ret; + } + + public void fill(int start, int end, int val) { + start += _start; + end += _start; + while (start < end) { + int chunk = start / growAmount; + int off = start % growAmount; + if (chunk < chunks.size()) { + int toFill = Math.min(end - start, growAmount - off); + Arrays.fill(chunks.get(chunk), off, off + toFill, val); + start += toFill; + } else if (chunk == chunks.size()) { + int toFill = Math.min(end - start, growAmount - off); + Arrays.fill(storage, off, off + toFill, val); + _size = Math.max(_size, off + toFill); + start += toFill; + if (_size == growAmount) { + chunks.add(storage); + _size = 0; + storage = new int[growAmount]; + } + } else { + chunks.add(storage); + _size = 0; + storage = new int[growAmount]; + } + } + } + + public int size() { + return chunks.size() * growAmount + _size - _start; + } + + public void addAll(int[] other) { + int otherOff = 0; + while (otherOff < other.length) { + int copyAmount = Math.min(other.length - otherOff, growAmount - _size); + if (copyAmount < 32) { + for (int i = 0; i < copyAmount; i++) + storage[_size++] = other[otherOff++]; + } else { + arraycopy(other, otherOff, storage, _size, copyAmount); + _size += copyAmount; + otherOff += copyAmount; + } + if (otherOff < other.length) { + chunks.add(storage); + storage = new int[growAmount]; + _size = 0; + } + } + } + + public void clear() { + chunks.clear(); + _size = 0; + _start = 0; + } + + public boolean contains(int needle) { + for (int c = 0; c < chunks.size(); c++) { + int[] chunk = chunks.get(c); + int coff = c == 0 ? _start : 0; + for (int i = coff; i < growAmount; i++) { + if (chunk[i] == needle) + return true; + } + } + int coff = chunks.size() == 0 ? _start : 0; + for (int i = coff; i < _size; i++) + if (storage[i] == needle) + return true; + return false; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/IntIntMap.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/IntIntMap.java new file mode 100644 index 0000000..66da402 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/IntIntMap.java @@ -0,0 +1,85 @@ +package org.monte.media.impl.jcodec.common; + +import java.util.Arrays; + +import static java.lang.System.arraycopy; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class IntIntMap { + + private static final int GROW_BY = 128; + private static final int MIN_VALUE = 0x80000000; + private int[] storage; + private int _size; + + public IntIntMap() { + this.storage = createArray(GROW_BY); + Arrays.fill(this.storage, MIN_VALUE); + } + + public void put(int key, int val) { + if (val == MIN_VALUE) + throw new IllegalArgumentException("This implementation can not store " + MIN_VALUE); + + if (storage.length <= key) { + int[] ns = createArray(key + GROW_BY); + arraycopy(storage, 0, ns, 0, storage.length); + Arrays.fill(ns, storage.length, ns.length, MIN_VALUE); + storage = ns; + } + if (storage[key] == MIN_VALUE) + _size++; + storage[key] = val; + } + + public int get(int key) { + return key >= storage.length ? MIN_VALUE : storage[key]; + } + + public boolean contains(int key) { + return key >= 0 && key < storage.length; + } + + public int[] keys() { + int[] result = new int[_size]; + for (int i = 0, r = 0; i < storage.length; i++) { + if (storage[i] != MIN_VALUE) + result[r++] = i; + } + return result; + } + + public void clear() { + for (int i = 0; i < storage.length; i++) + storage[i] = MIN_VALUE; + _size = 0; + } + + public int size() { + return _size; + } + + public void remove(int key) { + if (storage[key] != MIN_VALUE) + _size--; + storage[key] = MIN_VALUE; + } + + public int[] values() { + int[] result = createArray(_size); + for (int i = 0, r = 0; i < storage.length; i++) { + if (storage[i] != MIN_VALUE) + result[r++] = storage[i]; + } + return result; + } + + private static int[] createArray(int size) { + return new int[size]; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/IntObjectMap.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/IntObjectMap.java new file mode 100644 index 0000000..6136a95 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/IntObjectMap.java @@ -0,0 +1,74 @@ +package org.monte.media.impl.jcodec.common; + +import org.monte.media.impl.jcodec.platform.Platform; + +import java.lang.reflect.Array; + +import static java.lang.System.arraycopy; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class IntObjectMap { + private static final int GROW_BY = 128; + private Object[] storage; + private int _size; + + public IntObjectMap() { + this.storage = new Object[GROW_BY]; + } + + public void put(int key, T val) { + if (storage.length <= key) { + Object[] ns = new Object[key + GROW_BY]; + arraycopy(storage, 0, ns, 0, storage.length); + storage = ns; + } + if (storage[key] == null) + _size++; + storage[key] = val; + } + + @SuppressWarnings("unchecked") + public T get(int key) { + return key >= storage.length ? null : (T) storage[key]; + } + + public int[] keys() { + int[] result = new int[_size]; + for (int i = 0, r = 0; i < storage.length; i++) { + if (storage[i] != null) + result[r++] = i; + } + return result; + } + + public void clear() { + for (int i = 0; i < storage.length; i++) + storage[i] = null; + _size = 0; + } + + public int size() { + return _size; + } + + public void remove(int key) { + if (storage[key] != null) + _size--; + storage[key] = null; + } + + @SuppressWarnings("unchecked") + public T[] values(T[] runtime) { + T[] result = (T[]) Array.newInstance(Platform.arrayComponentType(runtime), _size); + for (int i = 0, r = 0; i < storage.length; i++) { + if (storage[i] != null) + result[r++] = (T) storage[i]; + } + return result; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/JCodecUtil2.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/JCodecUtil2.java new file mode 100644 index 0000000..a8611b3 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/JCodecUtil2.java @@ -0,0 +1,56 @@ +package org.monte.media.impl.jcodec.common; + +import org.monte.media.impl.jcodec.common.tools.MathUtil; +import org.monte.media.impl.jcodec.platform.Platform; + +import java.nio.ByteBuffer; + +public class JCodecUtil2 { + public static void writeBER32(ByteBuffer buffer, int value) { + buffer.put((byte) ((value >> 21) | 0x80)); + buffer.put((byte) ((value >> 14) | 0x80)); + buffer.put((byte) ((value >> 7) | 0x80)); + buffer.put((byte) (value & 0x7F)); + } + + public static int readBER32(ByteBuffer input) { + int size = 0; + for (int i = 0; i < 4; i++) { + byte b = input.get(); + size = (size << 7) | (b & 0x7f); + if (((b & 0xff) >> 7) == 0) + break; + } + return size; + } + + public static void writeBER32Var(ByteBuffer bb, int value) { + for (int i = 0, bits = MathUtil.log2(value); i < 4 && bits > 0; i++) { + bits -= 7; + int out = value >> bits; + if (bits > 0) + out |= 0x80; + bb.put((byte) out); + } + } + + public static byte[] asciiString(String fourcc) { + return Platform.getBytes(fourcc); + } + + public static int[] getAsIntArray(ByteBuffer yuv, int size) { + byte[] b = new byte[size]; + int[] result = new int[size]; + yuv.get(b); + for (int i = 0; i < b.length; i++) { + result[i] = b[i] & 0xff; + } + return result; + } + + public static String removeExtension(String name) { + if (name == null) + return null; + return name.replaceAll("\\.[^\\.]+$", ""); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/Preconditions.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/Preconditions.java new file mode 100644 index 0000000..7a55cd2 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/Preconditions.java @@ -0,0 +1,1308 @@ +/* + * Copyright (C) 2007 The Guava Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.monte.media.impl.jcodec.common; + +/** + * Static convenience methods that help a method or constructor check whether it was invoked + * correctly (whether its preconditions have been met). These methods generally accept a + * {@code boolean} expression which is expected to be {@code true} (or in the case of {@code + * checkNotNull}, an object reference which is expected to be non-null). When {@code false} (or + * {@code null}) is passed instead, the {@code Preconditions} method throws an unchecked exception, + * which helps the calling method communicate to its caller that that caller has made + * a mistake. Example:

   {@code
+ *
+ *   /**
+ *    * Returns the positive square root of the given value.
+ *    *
+ *    * @throws IllegalArgumentException if the value is negative
+ *    *}{@code /
+ *   public static double sqrt(double value) {
+ *     Preconditions.checkArgument(value >= 0.0, "negative value: %s", value);
+ *     // calculate the square root
+ *   }
+ *
+ *   void exampleBadCaller() {
+ *     double d = sqrt(-1.0);
+ *   }}
+ *

+ * In this example, {@code checkArgument} throws an {@code IllegalArgumentException} to indicate + * that {@code exampleBadCaller} made an error in its call to {@code sqrt}. + * + *

Warning about performance

+ * + *

The goal of this class is to improve readability of code, but in some circumstances this may + * come at a significant performance cost. Remember that parameter values for message construction + * must all be computed eagerly, and autoboxing and varargs array creation may happen as well, even + * when the precondition check then succeeds (as it should almost always do in production). In some + * circumstances these wasted CPU cycles and allocations can add up to a real problem. + * Performance-sensitive precondition checks can always be converted to the customary form: + *

   {@code
+ *
+ *   if (value < 0.0) {
+ *     throw new IllegalArgumentException("negative value: " + value);
+ *   }}
+ * + *

Other types of preconditions

+ * + *

Not every type of precondition failure is supported by these methods. Continue to throw + * standard JDK exceptions such as {@link java.util.NoSuchElementException} or + * {@link UnsupportedOperationException} in the situations they are intended for. + * + *

Non-preconditions

+ * + *

It is of course possible to use the methods of this class to check for invalid conditions + * which are not the caller's fault. Doing so is not recommended because it is + * misleading to future readers of the code and of stack traces. See + * Conditional failures + * explained in the Guava User Guide for more advice. + * + *

{@code java.util.Objects.requireNonNull()}

+ * + *

Projects which use {@code com.google.common} should generally avoid the use of + * {@link java.util.Objects#requireNonNull(Object)}. Instead, use whichever of + * {@link #checkNotNull(Object)} or {@code Verify#verifyNotNull(Object)} is appropriate to the + * situation. (The same goes for the message-accepting overloads.) + * + *

Only {@code %s} is supported

+ * + *

In {@code Preconditions} error message template strings, only the {@code "%s"} specifier is + * supported, not the full range of {@link java.util.Formatter} specifiers. + * + *

More information

+ * + *

See the Guava User Guide on + * using {@code + * Preconditions}. + * + * @author Kevin Bourrillion + * @since 2.0 + */ +public final class Preconditions { + private Preconditions() { + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + * @param expression a boolean expression + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression) { + if (!expression) { + throw new IllegalArgumentException(); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessage the exception message to use if the check fails; will be converted to a + * string using {@link String#valueOf(Object)} + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalArgumentException(String.valueOf(errorMessage)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the check fail. The + * message is formed by replacing each {@code %s} placeholder in the template with an + * argument. These are matched by position - the first {@code %s} gets {@code + * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in + * square braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message template. Arguments + * are converted to strings using {@link String#valueOf(Object)}. + * @throws IllegalArgumentException if {@code expression} is false + * @throws NullPointerException if the check fails and either {@code errorMessageTemplate} or + * {@code errorMessageArgs} is null (don't let this happen) + */ + public static void checkArgument( + boolean expression, + String errorMessageTemplate, + Object... errorMessageArgs) { + if (!expression) { + throw new IllegalArgumentException(format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument(boolean b, String errorMessageTemplate, char p1) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument(boolean b, String errorMessageTemplate, int p1) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument(boolean b, String errorMessageTemplate, long p1) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, Object p1) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, char p1, char p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, char p1, int p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, char p1, long p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, char p1, Object p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, int p1, char p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, int p1, int p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, int p1, long p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, int p1, Object p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, long p1, char p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, long p1, int p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, long p1, long p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, long p1, Object p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, Object p1, char p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, Object p1, int p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, Object p1, long p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, String errorMessageTemplate, Object p1, Object p2) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, + String errorMessageTemplate, + Object p1, + Object p2, + Object p3) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2, p3)); + } + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + *

See {@link #checkArgument(boolean, String, Object...)} for details. + */ + public static void checkArgument( + boolean b, + String errorMessageTemplate, + Object p1, + Object p2, + Object p3, + Object p4) { + if (!b) { + throw new IllegalArgumentException(format(errorMessageTemplate, p1, p2, p3, p4)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + * @param expression a boolean expression + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(boolean expression) { + if (!expression) { + throw new IllegalStateException(); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessage the exception message to use if the check fails; will be converted to a + * string using {@link String#valueOf(Object)} + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(boolean expression, Object errorMessage) { + if (!expression) { + throw new IllegalStateException(String.valueOf(errorMessage)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param errorMessageTemplate a template for the exception message should the check fail. The + * message is formed by replacing each {@code %s} placeholder in the template with an + * argument. These are matched by position - the first {@code %s} gets {@code + * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in + * square braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message template. Arguments + * are converted to strings using {@link String#valueOf(Object)}. + * @throws IllegalStateException if {@code expression} is false + * @throws NullPointerException if the check fails and either {@code errorMessageTemplate} or + * {@code errorMessageArgs} is null (don't let this happen) + */ + public static void checkState( + boolean expression, + String errorMessageTemplate, + Object... errorMessageArgs) { + if (!expression) { + throw new IllegalStateException(format(errorMessageTemplate, errorMessageArgs)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState(boolean b, String errorMessageTemplate, char p1) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState(boolean b, String errorMessageTemplate, int p1) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState(boolean b, String errorMessageTemplate, long p1) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState( + boolean b, String errorMessageTemplate, Object p1) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState( + boolean b, String errorMessageTemplate, char p1, char p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState(boolean b, String errorMessageTemplate, char p1, int p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState( + boolean b, String errorMessageTemplate, char p1, long p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState( + boolean b, String errorMessageTemplate, char p1, Object p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState(boolean b, String errorMessageTemplate, int p1, char p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState(boolean b, String errorMessageTemplate, int p1, int p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState(boolean b, String errorMessageTemplate, int p1, long p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState( + boolean b, String errorMessageTemplate, int p1, Object p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState( + boolean b, String errorMessageTemplate, long p1, char p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState(boolean b, String errorMessageTemplate, long p1, int p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState( + boolean b, String errorMessageTemplate, long p1, long p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState( + boolean b, String errorMessageTemplate, long p1, Object p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState( + boolean b, String errorMessageTemplate, Object p1, char p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState( + boolean b, String errorMessageTemplate, Object p1, int p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState( + boolean b, String errorMessageTemplate, Object p1, long p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState( + boolean b, String errorMessageTemplate, Object p1, Object p2) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState( + boolean b, + String errorMessageTemplate, + Object p1, + Object p2, + Object p3) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2, p3)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + *

See {@link #checkState(boolean, String, Object...)} for details. + */ + public static void checkState( + boolean b, + String errorMessageTemplate, + Object p1, + Object p2, + Object p3, + Object p4) { + if (!b) { + throw new IllegalStateException(format(errorMessageTemplate, p1, p2, p3, p4)); + } + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + * @param reference an object reference + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + + public static T checkNotNull(T reference) { + if (reference == null) { + throw new NullPointerException(); + } + return reference; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + * @param reference an object reference + * @param errorMessage the exception message to use if the check fails; will be converted to a + * string using {@link String#valueOf(Object)} + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + + public static T checkNotNull(T reference, Object errorMessage) { + if (reference == null) { + throw new NullPointerException(String.valueOf(errorMessage)); + } + return reference; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + * @param reference an object reference + * @param errorMessageTemplate a template for the exception message should the check fail. The + * message is formed by replacing each {@code %s} placeholder in the template with an + * argument. These are matched by position - the first {@code %s} gets {@code + * errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in + * square braces. Unmatched placeholders will be left as-is. + * @param errorMessageArgs the arguments to be substituted into the message template. Arguments + * are converted to strings using {@link String#valueOf(Object)}. + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + + public static T checkNotNull( + T reference, String errorMessageTemplate, Object... errorMessageArgs) { + if (reference == null) { + // If either of these parameters is null, the right thing happens anyway + throw new NullPointerException(format(errorMessageTemplate, errorMessageArgs)); + } + return reference; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull(T obj, String errorMessageTemplate, char p1) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull(T obj, String errorMessageTemplate, int p1) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull(T obj, String errorMessageTemplate, long p1) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull( + T obj, String errorMessageTemplate, Object p1) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull(T obj, String errorMessageTemplate, char p1, char p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull(T obj, String errorMessageTemplate, char p1, int p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull(T obj, String errorMessageTemplate, char p1, long p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull( + T obj, String errorMessageTemplate, char p1, Object p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull(T obj, String errorMessageTemplate, int p1, char p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull(T obj, String errorMessageTemplate, int p1, int p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull(T obj, String errorMessageTemplate, int p1, long p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull( + T obj, String errorMessageTemplate, int p1, Object p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull(T obj, String errorMessageTemplate, long p1, char p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull(T obj, String errorMessageTemplate, long p1, int p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull(T obj, String errorMessageTemplate, long p1, long p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull( + T obj, String errorMessageTemplate, long p1, Object p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull( + T obj, String errorMessageTemplate, Object p1, char p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull( + T obj, String errorMessageTemplate, Object p1, int p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull( + T obj, String errorMessageTemplate, Object p1, long p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull( + T obj, String errorMessageTemplate, Object p1, Object p2) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull( + T obj, + String errorMessageTemplate, + Object p1, + Object p2, + Object p3) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3)); + } + return obj; + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + *

See {@link #checkNotNull(Object, String, Object...)} for details. + */ + + public static T checkNotNull( + T obj, + String errorMessageTemplate, + Object p1, + Object p2, + Object p3, + Object p4) { + if (obj == null) { + throw new NullPointerException(format(errorMessageTemplate, p1, p2, p3, p4)); + } + return obj; + } + + /* + * All recent hotspots (as of 2009) *really* like to have the natural code + * + * if (guardExpression) { + * throw new BadException(messageExpression); + * } + * + * refactored so that messageExpression is moved to a separate String-returning method. + * + * if (guardExpression) { + * throw new BadException(badMsg(...)); + * } + * + * The alternative natural refactorings into void or Exception-returning methods are much slower. + * This is a big deal - we're talking factors of 2-8 in microbenchmarks, not just 10-20%. (This is + * a hotspot optimizer bug, which should be fixed, but that's a separate, big project). + * + * The coding pattern above is heavily used in java.util, e.g. in ArrayList. There is a + * RangeCheckMicroBenchmark in the JDK that was used to test this. + * + * But the methods in this class want to throw different exceptions, depending on the args, so it + * appears that this pattern is not directly applicable. But we can use the ridiculous, devious + * trick of throwing an exception in the middle of the construction of another exception. Hotspot + * is fine with that. + */ + + /** + * Ensures that {@code index} specifies a valid element in an array, list or string of size + * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list or string + * @param size the size of that array, list or string + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + + public static int checkElementIndex(int index, int size) { + return checkElementIndex(index, size, "index"); + } + + /** + * Ensures that {@code index} specifies a valid element in an array, list or string of size + * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive. + * + * @param index a user-supplied index identifying an element of an array, list or string + * @param size the size of that array, list or string + * @param desc the text to use to describe this index in an error message + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + + public static int checkElementIndex(int index, int size, String desc) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException(badElementIndex(index, size, desc)); + } + return index; + } + + private static String badElementIndex(int index, int size, String desc) { + if (index < 0) { + return format("%s (%s) must not be negative", desc, index); + } else if (size < 0) { + throw new IllegalArgumentException("negative size: " + size); + } else { // index >= size + return format("%s (%s) must be less than size (%s)", desc, index, size); + } + } + + /** + * Ensures that {@code index} specifies a valid position in an array, list or string of + * size {@code size}. A position index may range from zero to {@code size}, inclusive. + * + * @param index a user-supplied index identifying a position in an array, list or string + * @param size the size of that array, list or string + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is greater than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + + public static int checkPositionIndex(int index, int size) { + return checkPositionIndex(index, size, "index"); + } + + /** + * Ensures that {@code index} specifies a valid position in an array, list or string of + * size {@code size}. A position index may range from zero to {@code size}, inclusive. + * + * @param index a user-supplied index identifying a position in an array, list or string + * @param size the size of that array, list or string + * @param desc the text to use to describe this index in an error message + * @return the value of {@code index} + * @throws IndexOutOfBoundsException if {@code index} is negative or is greater than {@code size} + * @throws IllegalArgumentException if {@code size} is negative + */ + + public static int checkPositionIndex(int index, int size, String desc) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc)); + } + return index; + } + + private static String badPositionIndex(int index, int size, String desc) { + if (index < 0) { + return format("%s (%s) must not be negative", desc, index); + } else if (size < 0) { + throw new IllegalArgumentException("negative size: " + size); + } else { // index > size + return format("%s (%s) must not be greater than size (%s)", desc, index, size); + } + } + + /** + * Ensures that {@code start} and {@code end} specify a valid positions in an array, list + * or string of size {@code size}, and are in order. A position index may range from zero to + * {@code size}, inclusive. + * + * @param start a user-supplied index identifying a starting position in an array, list or string + * @param end a user-supplied index identifying a ending position in an array, list or string + * @param size the size of that array, list or string + * @throws IndexOutOfBoundsException if either index is negative or is greater than {@code size}, + * or if {@code end} is less than {@code start} + * @throws IllegalArgumentException if {@code size} is negative + */ + public static void checkPositionIndexes(int start, int end, int size) { + // Carefully optimized for execution by hotspot (explanatory comment above) + if (start < 0 || end < start || end > size) { + throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size)); + } + } + + private static String badPositionIndexes(int start, int end, int size) { + if (start < 0 || start > size) { + return badPositionIndex(start, size, "start index"); + } + if (end < 0 || end > size) { + return badPositionIndex(end, size, "end index"); + } + // end < start + return format("end index (%s) must not be less than start index (%s)", end, start); + } + + /** + * Substitutes each {@code %s} in {@code template} with an argument. These are matched by + * position: the first {@code %s} gets {@code args[0]}, etc. If there are more arguments than + * placeholders, the unmatched arguments will be appended to the end of the formatted message in + * square braces. + * + * @param template a non-null string containing 0 or more {@code %s} placeholders. + * @param args the arguments to be substituted into the message template. Arguments are converted + * to strings using {@link String#valueOf(Object)}. Arguments can be null. + */ + // Note that this is somewhat-improperly used from Verify.java as well. + static String format(String template, Object... args) { + if (args == null) { + throw new NullPointerException(); + } + template = String.valueOf(template); // null -> "null" + + // start substituting the arguments into the '%s' placeholders + StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); + int templateStart = 0; + int i = 0; + while (i < args.length) { + int placeholderStart = template.indexOf("%s", templateStart); + if (placeholderStart == -1) { + break; + } + builder.append(template, templateStart, placeholderStart); + builder.append(args[i++]); + templateStart = placeholderStart + 2; + } + builder.append(template, templateStart, template.length()); + + // if we run out of placeholders, append the extra args in square braces + if (i < args.length) { + builder.append(" ["); + builder.append(args[i++]); + while (i < args.length) { + builder.append(", "); + builder.append(args[i++]); + } + builder.append(']'); + } + + return builder.toString(); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/StringUtils.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/StringUtils.java new file mode 100644 index 0000000..39d67f2 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/StringUtils.java @@ -0,0 +1,290 @@ +package org.monte.media.impl.jcodec.common; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * This code is borrowed from Apache Commons String Utils + * + * @author The JCodec project + */ +public class StringUtils { + + public final static String[] zeroPad00 = {"00", "01", "02", "03", "04", "05", "06", "07", "08", "09"}; + + private static String[] splitWorker4(String str, String separatorChars, int max, boolean preserveAllTokens) { + // Performance tuned for 2.0 (JDK1.4) + // Direct code is quicker than StringTokenizer. + // Also, StringTokenizer uses isSpace() not isWhitespace() + + if (str == null) { + return null; + } + int len = str.length(); + if (len == 0) { + return new String[0]; + } + List list = new ArrayList(); + int sizePlus1 = 1; + int i = 0, start = 0; + boolean match = false; + boolean lastMatch = false; + if (separatorChars == null) { + // Null separator means use whitespace + while (i < len) { + if (Character.isWhitespace(str.charAt(i))) { + if (match || preserveAllTokens) { + lastMatch = true; + if (sizePlus1++ == max) { + i = len; + lastMatch = false; + } + list.add(str.substring(start, i)); + match = false; + } + start = ++i; + continue; + } + lastMatch = false; + match = true; + i++; + } + } else if (separatorChars.length() == 1) { + // Optimise 1 character case + char sep = separatorChars.charAt(0); + while (i < len) { + if (str.charAt(i) == sep) { + if (match || preserveAllTokens) { + lastMatch = true; + if (sizePlus1++ == max) { + i = len; + lastMatch = false; + } + list.add(str.substring(start, i)); + match = false; + } + start = ++i; + continue; + } + lastMatch = false; + match = true; + i++; + } + } else { + // standard case + while (i < len) { + if (separatorChars.indexOf(str.charAt(i)) >= 0) { + if (match || preserveAllTokens) { + lastMatch = true; + if (sizePlus1++ == max) { + i = len; + lastMatch = false; + } + list.add(str.substring(start, i)); + match = false; + } + start = ++i; + continue; + } + lastMatch = false; + match = true; + i++; + } + } + if (match || (preserveAllTokens && lastMatch)) { + list.add(str.substring(start, i)); + } + return (String[]) list.toArray(new String[list.size()]); + } + + private static String[] splitWorker(String str, char separatorChar, boolean preserveAllTokens) { + // Performance tuned for 2.0 (JDK1.4) + + if (str == null) { + return null; + } + int len = str.length(); + if (len == 0) { + return new String[0]; + } + List list = new ArrayList(); + int i = 0, start = 0; + boolean match = false; + boolean lastMatch = false; + while (i < len) { + if (str.charAt(i) == separatorChar) { + if (match || preserveAllTokens) { + list.add(str.substring(start, i)); + match = false; + lastMatch = true; + } + start = ++i; + continue; + } + lastMatch = false; + match = true; + i++; + } + if (match || (preserveAllTokens && lastMatch)) { + list.add(str.substring(start, i)); + } + return (String[]) list.toArray(new String[list.size()]); + } + + public static String[] split(String str) { + return splitS3(str, null, -1); + } + + public static String[] splitS(String str, String separatorChars) { + return splitWorker4(str, separatorChars, -1, false); + } + + public static String[] splitS3(String str, String separatorChars, int max) { + return splitWorker4(str, separatorChars, max, false); + } + + public static String[] splitC(String str, char separatorChar) { + return splitWorker(str, separatorChar, false); + } + + public static boolean isEmpty(String str) { + return str == null || str.length() == 0; + } + + private static boolean isDelimiter(char ch, char[] delimiters) { + if (delimiters == null) { + return Character.isWhitespace(ch); + } + for (int i = 0, isize = delimiters.length; i < isize; i++) { + if (ch == delimiters[i]) { + return true; + } + } + return false; + } + + public static String capitaliseAllWords(String str) { + return capitalize(str); + } + + public static String capitalize(String str) { + return capitalizeD(str, null); + } + + public static String capitalizeD(String str, char[] delimiters) { + int delimLen = (delimiters == null ? -1 : delimiters.length); + if (str == null || str.length() == 0 || delimLen == 0) { + return str; + } + int strLen = str.length(); + StringBuilder buffer = new StringBuilder(strLen); + boolean capitalizeNext = true; + for (int i = 0; i < strLen; i++) { + char ch = str.charAt(i); + + if (isDelimiter(ch, delimiters)) { + buffer.append(ch); + capitalizeNext = true; + } else if (capitalizeNext) { + buffer.append(Character.toTitleCase(ch)); + capitalizeNext = false; + } else { + buffer.append(ch); + } + } + return buffer.toString(); + } + + + public static String join(Object[] array) { + return joinS(array, null); + } + + + public static String join2(Object[] array, char separator) { + if (array == null) { + return null; + } + + return join4(array, separator, 0, array.length); + } + + + public static String join4(Object[] array, char separator, int startIndex, int endIndex) { + if (array == null) { + return null; + } + int bufSize = (endIndex - startIndex); + if (bufSize <= 0) { + return ""; + } + + bufSize *= ((array[startIndex] == null ? 16 : array[startIndex].toString().length()) + 1); + StringBuilder buf = new StringBuilder(bufSize); + + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + if (array[i] != null) { + buf.append(array[i]); + } + } + return buf.toString(); + } + + + public static String joinS(Object[] array, String separator) { + if (array == null) { + return null; + } + return joinS4(array, separator, 0, array.length); + } + + + public static String joinS4(Object[] array, String separator, int startIndex, int endIndex) { + if (array == null) { + return null; + } + if (separator == null) { + separator = ""; + } + + // endIndex - startIndex > 0: Len = NofStrings *(len(firstString) + len(separator)) + // (Assuming that all Strings are roughly equally long) + int bufSize = (endIndex - startIndex); + if (bufSize <= 0) { + return ""; + } + + bufSize *= ((array[startIndex] == null ? 16 : array[startIndex].toString().length()) + + separator.length()); + + StringBuilder buf = new StringBuilder(bufSize); + + for (int i = startIndex; i < endIndex; i++) { + if (i > startIndex) { + buf.append(separator); + } + if (array[i] != null) { + buf.append(array[i]); + } + } + return buf.toString(); + } + + public static String zeroPad2(int x) { + if (x >= 0 && x < 10) { + return zeroPad00[x]; + } + return "" + x; + } + + public static String zeroPad3(int n) { + String s1 = zeroPad2(n); + return s1.length() == 2 ? "0" + s1 : s1; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/Tuple.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/Tuple.java new file mode 100644 index 0000000..dc29f24 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/Tuple.java @@ -0,0 +1,267 @@ +package org.monte.media.impl.jcodec.common; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class Tuple { + + public static class _1 { + public final T0 v0; + + public _1(T0 v0) { + this.v0 = v0; + } + } + + public static class _2 { + public final T0 v0; + public final T1 v1; + + public _2(T0 v0, T1 v1) { + this.v0 = v0; + this.v1 = v1; + } + } + + public static class _3 { + public final T0 v0; + public final T1 v1; + public final T2 v2; + + public _3(T0 v0, T1 v1, T2 v2) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + } + } + + public static class _4 { + public final T0 v0; + public final T1 v1; + public final T2 v2; + public final T3 v3; + + public _4(T0 v0, T1 v1, T2 v2, T3 v3) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.v3 = v3; + } + } + + public static _1 single(T0 v0) { + return new _1(v0); + } + + public static _2 pair(T0 v0, T1 v1) { + return new _2(v0, v1); + } + + public static _3 triple(T0 v0, T1 v1, T2 v2) { + return new _3(v0, v1, v2); + } + + public static _4 quad(T0 v0, T1 v1, T2 v2, T3 v3) { + return new _4(v0, v1, v2, v3); + } + + public static Map asMap(Iterable<_2> it) { + HashMap result = new HashMap(); + for (_2 el : it) { + result.put(el.v0, el.v1); + } + return result; + } + + public static Map arrayAsMap(_2[] arr) { + HashMap result = new HashMap(); + for (int i = 0; i < arr.length; i++) { + _2 el = arr[i]; + result.put(el.v0, el.v1); + } + return result; + } + + public static List<_2> asList(Map m) { + LinkedList<_2> result = new LinkedList<_2>(); + Set> entrySet = m.entrySet(); + for (Entry entry : entrySet) { + result.add(pair(entry.getKey(), entry.getValue())); + } + return result; + } + + public static List _1_project0(List<_1> temp) { + List result = new LinkedList(); + for (_1 _1 : temp) { + result.add(_1.v0); + } + return result; + } + + public static List _2_project0(List<_2> temp) { + List result = new LinkedList(); + for (_2 _2 : temp) { + result.add(_2.v0); + } + return result; + } + + public static List _2_project1(List<_2> temp) { + List result = new LinkedList(); + for (_2 _2 : temp) { + result.add(_2.v1); + } + return result; + } + + public static List _3_project0(List<_3> temp) { + List result = new LinkedList(); + for (_3 _3 : temp) { + result.add(_3.v0); + } + return result; + } + + public static List _3_project1(List<_3> temp) { + List result = new LinkedList(); + for (_3 _3 : temp) { + result.add(_3.v1); + } + return result; + } + + public static List _3_project2(List<_3> temp) { + List result = new LinkedList(); + for (_3 _3 : temp) { + result.add(_3.v2); + } + return result; + } + + public static List _4_project0(List<_4> temp) { + List result = new LinkedList(); + for (_4 _4 : temp) { + result.add(_4.v0); + } + return result; + } + + public static List _4_project1(List<_4> temp) { + List result = new LinkedList(); + for (_4 _4 : temp) { + result.add(_4.v1); + } + return result; + } + + public static List _4_project2(List<_4> temp) { + List result = new LinkedList(); + for (_4 _4 : temp) { + result.add(_4.v2); + } + return result; + } + + public static List _4_project3(List<_4> temp) { + List result = new LinkedList(); + for (_4 _4 : temp) { + result.add(_4.v3); + } + return result; + } + + public static interface Mapper { + U map(T t); + } + + public static List<_1> _1map0(List<_1> l, Mapper mapper) { + LinkedList<_1> result = new LinkedList<_1>(); + for (_1 _1 : l) { + result.add(single(mapper.map(_1.v0))); + } + return result; + } + + public static List<_2> _2map0(List<_2> l, Mapper mapper) { + LinkedList<_2> result = new LinkedList<_2>(); + for (_2 _2 : l) { + result.add(pair(mapper.map(_2.v0), _2.v1)); + } + return result; + } + + public static List<_2> _2map1(List<_2> l, Mapper mapper) { + LinkedList<_2> result = new LinkedList<_2>(); + for (_2 _2 : l) { + result.add(pair(_2.v0, mapper.map(_2.v1))); + } + return result; + } + + public static List<_3> _3map0(List<_3> l, Mapper mapper) { + LinkedList<_3> result = new LinkedList<_3>(); + for (_3 _3 : l) { + result.add(triple(mapper.map(_3.v0), _3.v1, _3.v2)); + } + return result; + } + + public static List<_3> _3map1(List<_3> l, Mapper mapper) { + LinkedList<_3> result = new LinkedList<_3>(); + for (_3 _3 : l) { + result.add(triple(_3.v0, mapper.map(_3.v1), _3.v2)); + } + return result; + } + + public static List<_3> _3map3(List<_3> l, Mapper mapper) { + LinkedList<_3> result = new LinkedList<_3>(); + for (_3 _3 : l) { + result.add(triple(_3.v0, _3.v1, mapper.map(_3.v2))); + } + return result; + } + + public static List<_4> _4map0(List<_4> l, Mapper mapper) { + LinkedList<_4> result = new LinkedList<_4>(); + for (_4 _4 : l) { + result.add(quad(mapper.map(_4.v0), _4.v1, _4.v2, _4.v3)); + } + return result; + } + + public static List<_4> _4map1(List<_4> l, Mapper mapper) { + LinkedList<_4> result = new LinkedList<_4>(); + for (_4 _4 : l) { + result.add(quad(_4.v0, mapper.map(_4.v1), _4.v2, _4.v3)); + } + return result; + } + + public static List<_4> _4map3(List<_4> l, Mapper mapper) { + LinkedList<_4> result = new LinkedList<_4>(); + for (_4 _4 : l) { + result.add(quad(_4.v0, _4.v1, mapper.map(_4.v2), _4.v3)); + } + return result; + } + + public static List<_4> _4map4(List<_4> l, Mapper mapper) { + LinkedList<_4> result = new LinkedList<_4>(); + for (_4 _4 : l) { + result.add(quad(_4.v0, _4.v1, _4.v2, mapper.map(_4.v3))); + } + return result; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/UsedViaReflection.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/UsedViaReflection.java new file mode 100644 index 0000000..497189a --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/UsedViaReflection.java @@ -0,0 +1,9 @@ +package org.monte.media.impl.jcodec.common; + +/** + * this annotation warns not to remove methods/constructors/fields if their + * usages are not found via normal means + */ +public @interface UsedViaReflection { + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/VideoCodecMeta.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/VideoCodecMeta.java new file mode 100644 index 0000000..42aaf3f --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/VideoCodecMeta.java @@ -0,0 +1,79 @@ +package org.monte.media.impl.jcodec.common; + +import org.monte.media.impl.jcodec.common.model.ColorSpace; +import org.monte.media.impl.jcodec.common.model.Rational; +import org.monte.media.impl.jcodec.common.model.Size; + +import java.nio.ByteBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class VideoCodecMeta extends CodecMeta { + + public static VideoCodecMeta createVideoCodecMeta(String fourcc, ByteBuffer codecPrivate, Size size, + Rational pasp) { + VideoCodecMeta self = new VideoCodecMeta(fourcc, codecPrivate); + self.size = size; + self.pasp = pasp; + return self; + } + + public static VideoCodecMeta createVideoCodecMeta2(String fourcc, ByteBuffer codecPrivate, Size size, Rational pasp, + boolean interlaced, boolean topFieldFirst) { + VideoCodecMeta self = new VideoCodecMeta(fourcc, codecPrivate); + self.size = size; + self.pasp = pasp; + self.interlaced = interlaced; + self.topFieldFirst = topFieldFirst; + return self; + } + + public VideoCodecMeta(String fourcc, ByteBuffer codecPrivate) { + super(fourcc, codecPrivate); + } + + private Size size; + private Rational pasp; + private boolean interlaced; + private boolean topFieldFirst; + private ColorSpace color; + + public Size getSize() { + return size; + } + + public Rational getPasp() { + return pasp; + } + + public Rational getPixelAspectRatio() { + return pasp; + } + + public boolean isInterlaced() { + return interlaced; + } + + public boolean isTopFieldFirst() { + return topFieldFirst; + } + + public ColorSpace getColor() { + return color; + } + + public static VideoCodecMeta createSimpleVideoCodecMeta(Size size, ColorSpace color) { + VideoCodecMeta self = new VideoCodecMeta(null, null); + self.size = size; + self.color = color; + return self; + } + + public void setPixelAspectRatio(Rational pasp) { + this.pasp = pasp; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/VideoDecoder.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/VideoDecoder.java new file mode 100644 index 0000000..e9f2fc4 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/VideoDecoder.java @@ -0,0 +1,46 @@ +package org.monte.media.impl.jcodec.common; + +import org.monte.media.impl.jcodec.common.model.Picture; + +import java.io.IOException; +import java.nio.ByteBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +abstract public class VideoDecoder { + private byte[][] byteBuffer; + + /** + * Decodes a video frame to an uncompressed picture in codec native + * colorspace + * + * @param data Compressed frame data + * @throws IOException + */ + public abstract Picture decodeFrame(ByteBuffer data, byte[][] buffer); + + public abstract VideoCodecMeta getCodecMeta(ByteBuffer data); + + + protected byte[][] getSameSizeBuffer(int[][] buffer) { + if (byteBuffer == null || byteBuffer.length != buffer.length || byteBuffer[0].length != buffer[0].length) + byteBuffer = ArrayUtil.create2D(buffer[0].length, buffer.length); + return byteBuffer; + } + + /** + * Returns a downscaled version of this decoder + * + * @param ratio + * @return + */ + public VideoDecoder downscaled(int ratio) { + if (ratio == 1) + return this; + return null; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/VideoEncoder.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/VideoEncoder.java new file mode 100644 index 0000000..9722c61 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/VideoEncoder.java @@ -0,0 +1,83 @@ +package org.monte.media.impl.jcodec.common; + +import org.monte.media.impl.jcodec.common.model.ColorSpace; +import org.monte.media.impl.jcodec.common.model.Picture; + +import java.nio.ByteBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * A generic interface for the video encoder. + * + * @author The JCodec project + */ +public abstract class VideoEncoder { + /** + * The bytes of this video frame with associated metadata. + * + * @author The JCodec project + */ + public static class EncodedFrame { + private ByteBuffer data; + private boolean keyFrame; + + public EncodedFrame(ByteBuffer data, boolean keyFrame) { + this.data = data; + this.keyFrame = keyFrame; + } + + public ByteBuffer getData() { + return data; + } + + public boolean isKeyFrame() { + return keyFrame; + } + } + + /** + * Encode one video frame. + * + * @param pic The video frame to be encoded. Must be in one of the encoder's + * native color spaces. + * @param buffer The buffer to store the encoded frame into. Note, only the + * storage of this buffer is used, the position and limit are + * kept untouched. Instead the returned value contains a + * duplicate of this buffer with the position and limit set + * correctly to the boundaries of the encoded frame. This buffer + * must be large enough to hold the encoded frame. It is + * undefined what will happen if the buffer is not large enough. + * Most commonly some exception will be thrown. + * @return The bytes of an encoded frame with additional metadata. The bytes + * are located in the storage supplied by the buffer argument, no + * allocation happens. + */ + public abstract EncodedFrame encodeFrame(Picture pic, ByteBuffer buffer); + + /** + * Native color spaces of this video encoder, i.e. the color space the video + * encoder uses internally to represent color. For example the native color + * space of an h.264 encoder is most commonly yuv420. This information is + * used to find the correct color transform. The encoder may return null + * which means that ANY color space will be taken. This is useful in some + * cases for instance for the raw video encoder. + * + * @return The array of this encoder's native color spaces. + */ + public abstract ColorSpace[] getSupportedColorSpaces(); + + /** + * Estimate the output buffer size that will likely be needed for the + * current instance of encoder to encode a given frame. Note: expect a very + * coarse estimate that reflects the settings the encoder has been created + * with as well as the input frame size. + * + * @param frame A frame in question. + * @return The number of bytes the encoded frame will likely take. + */ + public abstract int estimateBufferSize(Picture frame); + + public abstract void finish(); +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/AutoPool.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/AutoPool.java new file mode 100644 index 0000000..25b256d --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/AutoPool.java @@ -0,0 +1,58 @@ +package org.monte.media.impl.jcodec.common.io; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import static java.lang.System.currentTimeMillis; + +/** + * This class is part of JCodec ( www.jcodec.org ) + * This software is distributed under FreeBSD License + * + * @author The JCodec project + */ +public class AutoPool { + private final List resources; + private ScheduledExecutorService scheduler; + + private AutoPool() { + this.resources = Collections.synchronizedList(new ArrayList()); + scheduler = Executors.newScheduledThreadPool(1, daemonThreadFactory()); + final List res = resources; + scheduler.scheduleAtFixedRate(new Runnable() { + public void run() { + long curTime = currentTimeMillis(); + for (AutoResource autoResource : res) { + autoResource.setCurTime(curTime); + } + } + }, 0, 100, TimeUnit.MILLISECONDS); + } + + private ThreadFactory daemonThreadFactory() { + return new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r); + t.setDaemon(true); + t.setName(AutoPool.class.getName()); + return t; + } + }; + } + + public static AutoPool getInstance() { + return instance; + } + + public void add(AutoResource res) { + resources.add(res); + } + + private static AutoPool instance = new AutoPool(); +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/AutoResource.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/AutoResource.java new file mode 100644 index 0000000..46fa24e --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/AutoResource.java @@ -0,0 +1,13 @@ +package org.monte.media.impl.jcodec.common.io; + +/** + * This class is part of JCodec ( www.jcodec.org ) + * This software is distributed under FreeBSD License + * + * @author The JCodec project + */ +public interface AutoResource { + + void setCurTime(long curTime); + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/BitReader.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/BitReader.java new file mode 100644 index 0000000..8cfefa9 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/BitReader.java @@ -0,0 +1,244 @@ +package org.monte.media.impl.jcodec.common.io; + +import java.nio.ByteBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class BitReader { + public static BitReader createBitReader(ByteBuffer bb) { + BitReader r = new BitReader(bb); + r.curInt = r.readInt(); + r.deficit = 0; + return r; + } + + private int deficit = -1; + private int curInt = -1; + private ByteBuffer bb; + private int initPos; + + private BitReader(ByteBuffer bb) { + this.bb = bb; + this.initPos = bb.position(); + } + + public BitReader fork() { + BitReader fork = new BitReader(this.bb.duplicate()); + fork.initPos = 0; + fork.curInt = this.curInt; + fork.deficit = this.deficit; + return fork; + } + + public final int readInt() { + if (((java.nio.Buffer) bb).remaining() >= 4) { + deficit -= 32; + return ((bb.get() & 0xff) << 24) | ((bb.get() & 0xff) << 16) | ((bb.get() & 0xff) << 8) | (bb.get() & 0xff); + } else + return readIntSafe(); + } + + private int readIntSafe() { + deficit -= (((java.nio.Buffer) bb).remaining() << 3); + int res = 0; + if (bb.hasRemaining()) + res |= bb.get() & 0xff; + res <<= 8; + if (bb.hasRemaining()) + res |= bb.get() & 0xff; + res <<= 8; + if (bb.hasRemaining()) + res |= bb.get() & 0xff; + res <<= 8; + if (bb.hasRemaining()) + res |= bb.get() & 0xff; + return res; + } + + public int read1Bit() { + + int ret = curInt >>> 31; + curInt <<= 1; + ++deficit; + if (deficit == 32) { + curInt = readInt(); + } + // System.out.println(ret); + + return ret; + } + + public int readNBitSigned(int n) { + int v = readNBit(n); + return read1Bit() == 0 ? v : -v; + } + + public int readNBit(int n) { + if (n > 32) + throw new IllegalArgumentException("Can not read more then 32 bit"); + + int ret = 0; + if (n + deficit > 31) { + ret |= (curInt >>> deficit); + n -= 32 - deficit; + ret <<= n; + deficit = 32; + curInt = readInt(); + } + + if (n != 0) { + ret |= curInt >>> (32 - n); + curInt <<= n; + deficit += n; + } + + return ret; + } + + public boolean moreData() { + int remaining = ((java.nio.Buffer) bb).remaining() + 4 - ((deficit + 7) >> 3); + return remaining > 1 || (remaining == 1 && curInt != 0); + } + + public int remaining() { + return (((java.nio.Buffer) bb).remaining() << 3) + 32 - deficit; + } + + public final boolean isByteAligned() { + return (deficit & 0x7) == 0; + } + + public int skip(int bits) { + int left = bits; + + if (left + deficit > 31) { + left -= 32 - deficit; + deficit = 32; + if (left > 31) { + int skip = Math.min(left >> 3, ((java.nio.Buffer) bb).remaining()); + ((java.nio.Buffer) bb).position(((java.nio.Buffer) bb).position() + skip); + left -= skip << 3; + } + curInt = readInt(); + } + + deficit += left; + curInt <<= left; + + return bits; + } + + public int skipFast(int bits) { + deficit += bits; + curInt <<= bits; + + return bits; + } + + public int bitsToAlign() { + return (deficit & 0x7) > 0 ? 8 - (deficit & 0x7) : 0; + } + + public int align() { + return (deficit & 0x7) > 0 ? skip(8 - (deficit & 0x7)) : 0; + } + + public int check24Bits() { + if (deficit > 16) { + deficit -= 16; + curInt |= nextIgnore16() << deficit; + } + + if (deficit > 8) { + deficit -= 8; + curInt |= nextIgnore() << deficit; + } + + return curInt >>> 8; + } + + public int check16Bits() { + if (deficit > 16) { + deficit -= 16; + curInt |= nextIgnore16() << deficit; + } + return curInt >>> 16; + } + + public int readFast16(int n) { + if (n == 0) + return 0; + if (deficit > 16) { + deficit -= 16; + curInt |= nextIgnore16() << deficit; + } + + int ret = curInt >>> (32 - n); + deficit += n; + curInt <<= n; + + return ret; + } + + public int checkNBit(int n) { + if (n > 24) { + throw new IllegalArgumentException("Can not check more then 24 bit"); + } + + return checkNBitDontCare(n); + } + + public int checkNBitDontCare(int n) { + while (deficit + n > 32) { + deficit -= 8; + curInt |= nextIgnore() << deficit; + } + int res = curInt >>> (32 - n); + return res; + } + + private int nextIgnore16() { + return ((java.nio.Buffer) bb).remaining() > 1 ? bb.getShort() & 0xffff : (((java.nio.Buffer) bb).hasRemaining() ? ((bb.get() & 0xff) << 8) : 0); + } + + private int nextIgnore() { + return ((java.nio.Buffer) bb).hasRemaining() ? bb.get() & 0xff : 0; + } + + public int curBit() { + return deficit & 0x7; + } + + public boolean lastByte() { + return ((java.nio.Buffer) bb).remaining() + 4 - (deficit >> 3) <= 1; + } + + public void terminate() { + int putBack = (32 - deficit) >> 3; + ((java.nio.Buffer) bb).position(((java.nio.Buffer) bb).position() - putBack); + } + + public int position() { + return ((((java.nio.Buffer) bb).position() - initPos - 4) << 3) + deficit; + } + + /** + * Stops this bit reader. Returns underlying ByteBuffer pointer to the next + * byte unread byte + */ + public void stop() { + ((java.nio.Buffer) bb).position(((java.nio.Buffer) bb).position() - ((32 - deficit) >> 3)); + } + + public int checkAllBits() { + return curInt; + } + + public boolean readBool() { + return read1Bit() == 1; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/BitWriter.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/BitWriter.java new file mode 100644 index 0000000..59c16d5 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/BitWriter.java @@ -0,0 +1,111 @@ +package org.monte.media.impl.jcodec.common.io; + +import java.nio.ByteBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Bitstream writer + * + * @author The JCodec project + */ +public class BitWriter { + + private final ByteBuffer buf; + private int curInt; + private int _curBit; + private int initPos; + + public BitWriter(ByteBuffer buf) { + this.buf = buf; + initPos = buf.position(); + } + + public BitWriter fork() { + BitWriter fork = new BitWriter(buf.duplicate()); + fork._curBit = this._curBit; + fork.curInt = this.curInt; + fork.initPos = this.initPos; + return fork; + } + + public void writeOther(BitWriter bw) { + if (_curBit >= 8) { + int shift = 32 - _curBit; + for (int i = initPos; i < bw.buf.position(); i++) { + buf.put((byte) (curInt >> 24)); + curInt <<= 8; + curInt |= (bw.buf.get(i) & 0xff) << shift; + } + } else { + int shift = 24 - _curBit; + for (int i = initPos; i < bw.buf.position(); i++) { + curInt |= (bw.buf.get(i) & 0xff) << shift; + buf.put((byte) (curInt >> 24)); + curInt <<= 8; + } + } + writeNBit(bw.curInt >> (32 - bw._curBit), bw._curBit); + } + + public void flush() { + int toWrite = (_curBit + 7) >> 3; + for (int i = 0; i < toWrite; i++) { + buf.put((byte) (curInt >>> 24)); + curInt <<= 8; + } + } + + private final void putInt(int i) { + buf.put((byte) (i >>> 24)); + buf.put((byte) (i >> 16)); + buf.put((byte) (i >> 8)); + buf.put((byte) i); + } + + public final void writeNBit(int value, int n) { + if (n > 32) + throw new IllegalArgumentException("Max 32 bit to write"); + if (n == 0) + return; + value &= -1 >>> (32 - n); + if (32 - _curBit >= n) { + curInt |= value << (32 - _curBit - n); + _curBit += n; + if (_curBit == 32) { + putInt(curInt); + _curBit = 0; + curInt = 0; + } + } else { + int secPart = n - (32 - _curBit); + curInt |= value >>> secPart; + putInt(curInt); + curInt = value << (32 - secPart); + _curBit = secPart; + } + } + + public void write1Bit(int bit) { + curInt |= bit << (32 - _curBit - 1); + ++_curBit; + if (_curBit == 32) { + putInt(curInt); + _curBit = 0; + curInt = 0; + } + } + + public int curBit() { + return _curBit & 0x7; + } + + public int position() { + return ((buf.position() - initPos) << 3) + _curBit; + } + + public ByteBuffer getBuffer() { + return buf; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/FileChannelWrapper.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/FileChannelWrapper.java new file mode 100644 index 0000000..0228f0e --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/FileChannelWrapper.java @@ -0,0 +1,63 @@ +package org.monte.media.impl.jcodec.common.io; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class FileChannelWrapper implements SeekableByteChannel { + + private FileChannel ch; + + public FileChannelWrapper(FileChannel ch) throws FileNotFoundException { + this.ch = ch; + } + + @Override + public int read(ByteBuffer arg0) throws IOException { + return ch.read(arg0); + } + + @Override + public void close() throws IOException { + ch.close(); + } + + @Override + public boolean isOpen() { + return ch.isOpen(); + } + + @Override + public int write(ByteBuffer arg0) throws IOException { + return ch.write(arg0); + } + + @Override + public long position() throws IOException { + return ch.position(); + } + + @Override + public SeekableByteChannel setPosition(long newPosition) throws IOException { + ch.position(newPosition); + return this; + } + + @Override + public long size() throws IOException { + return ch.size(); + } + + @Override + public SeekableByteChannel truncate(long size) throws IOException { + ch.truncate(size); + return this; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/IOUtils.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/IOUtils.java new file mode 100644 index 0000000..80b176b --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/IOUtils.java @@ -0,0 +1,102 @@ +package org.monte.media.impl.jcodec.common.io; + +import org.monte.media.impl.jcodec.platform.Platform; + +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class IOUtils { + + public static final int DEFAULT_BUFFER_SIZE = 4096; + + public static void closeQuietly(Closeable c) { + if (c == null) + return; + try { + c.close(); + } catch (IOException e) { + } + } + + public static byte[] toByteArray(InputStream input) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + copy(input, output); + return output.toByteArray(); + } + + public static int copy(InputStream input, OutputStream output) throws IOException { + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + int count = 0; + int n = 0; + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + public static int copyDumb(InputStream input, OutputStream output) throws IOException { + int count = 0; + int n = 0; + while (-1 != (n = input.read())) { + output.write(n); + count++; + } + return count; + } + + public static byte[] readFileToByteArray(File file) throws IOException { + return NIOUtils.toArray(NIOUtils.fetchFromFile(file)); + } + + public static String readToString(InputStream is) throws IOException { + return Platform.stringFromBytes(toByteArray(is)); + } + + public static void writeStringToFile(File file, String str) throws IOException { + NIOUtils.writeTo(ByteBuffer.wrap(str.getBytes()), file); + } + + public static void forceMkdir(File directory) throws IOException { + if (directory.exists()) { + if (!directory.isDirectory()) { + String message = "File " + directory + " exists and is " + + "not a directory. Unable to create directory."; + throw new IOException(message); + } + } else { + if (!directory.mkdirs()) { + // Double-check that some other thread or process hasn't made + // the directory in the background + if (!directory.isDirectory()) { + String message = "Unable to create directory " + directory; + throw new IOException(message); + } + } + } + } + + public static void copyFile(File src, File dst) throws IOException { + FileChannelWrapper _in = null; + FileChannelWrapper out = null; + try { + _in = NIOUtils.readableChannel(src); + out = NIOUtils.writableChannel(dst); + NIOUtils.copy(_in, out, Long.MAX_VALUE); + } finally { + NIOUtils.closeQuietly(_in); + NIOUtils.closeQuietly(out); + } + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/NIOUtils.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/NIOUtils.java new file mode 100644 index 0000000..bebf53b --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/NIOUtils.java @@ -0,0 +1,531 @@ +package org.monte.media.impl.jcodec.common.io; + +import org.monte.media.impl.jcodec.common.ArrayUtil; +import org.monte.media.impl.jcodec.common.AutoFileChannelWrapper; +import org.monte.media.impl.jcodec.platform.Platform; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.MappedByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; +import java.util.ArrayList; +import java.util.List; + +import static java.lang.Math.min; +import static org.monte.media.impl.jcodec.platform.Platform.stringFromBytes; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class NIOUtils { + + public static ByteBuffer search(ByteBuffer buffer, int n, byte[] param) { + ByteBuffer result = buffer.duplicate(); + int step = 0, rem = buffer.position(); + while (buffer.hasRemaining()) { + int b = buffer.get(); + if (b == param[step]) { + ++step; + if (step == param.length) { + if (n == 0) { + buffer.position(rem); + result.limit(buffer.position()); + break; + } + n--; + step = 0; + } + } else { + if (step != 0) { + step = 0; + ++rem; + buffer.position(rem); + } else + rem = buffer.position(); + } + } + return result; + } + + public static final ByteBuffer read(ByteBuffer buffer, int count) { + ByteBuffer slice = buffer.duplicate(); + int limit = buffer.position() + count; + ((java.nio.Buffer) slice).limit(limit); + ((java.nio.Buffer) buffer).position(limit); + return slice; + } + + public static ByteBuffer fetchFromFile(File file) throws IOException { + return NIOUtils.fetchFromFileL(file, (int) file.length()); + } + + public static ByteBuffer fetchFromChannel(ReadableByteChannel ch, int size) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(size); + NIOUtils.readFromChannel(ch, buf); + ((java.nio.Buffer) buf).flip(); + return buf; + } + + public static ByteBuffer fetchAllFromChannel(SeekableByteChannel ch) throws IOException { + List buffers = new ArrayList(); + ByteBuffer buf; + do { + buf = fetchFromChannel(ch, 1 << 20); + buffers.add(buf); + } while (buf.hasRemaining()); + + return combineBuffers(buffers); + } + + /** + * Reads size amount of bytes from ch into a new ByteBuffer allocated from a + * buffer buf + * + * @param buf + * @param ch + * @param size + * @return + * @throws IOException + */ + public static ByteBuffer fetchFrom(ByteBuffer buf, ReadableByteChannel ch, int size) throws IOException { + ByteBuffer result = buf.duplicate(); + result.limit(size); + NIOUtils.readFromChannel(ch, result); + result.flip(); + return result; + } + + public static ByteBuffer fetchFromFileOL(File file, int off, int length) throws IOException { + FileChannel is = null; + try { + is = new FileInputStream(file).getChannel(); + is.position(off); + return fetchFromChannel(is, length); + } finally { + closeQuietly(is); + } + } + + public static ByteBuffer fetchFromFileL(File file, int length) throws IOException { + FileChannel is = null; + try { + is = new FileInputStream(file).getChannel(); + return fetchFromChannel(is, length); + } finally { + closeQuietly(is); + } + } + + public static void writeTo(ByteBuffer buffer, File file) throws IOException { + FileChannel out = null; + try { + out = new FileOutputStream(file).getChannel(); + out.write(buffer); + } finally { + closeQuietly(out); + } + } + + public static byte[] toArray(ByteBuffer buffer) { + byte[] result = new byte[buffer.remaining()]; + buffer.duplicate().get(result); + return result; + } + + public static byte[] toArrayL(ByteBuffer buffer, int count) { + byte[] result = new byte[Math.min(buffer.remaining(), count)]; + buffer.duplicate().get(result); + return result; + } + + public static int readL(ReadableByteChannel channel, ByteBuffer buffer, int length) throws IOException { + ByteBuffer fork = buffer.duplicate(); + ((java.nio.Buffer) fork).limit(min(fork.position() + length, fork.limit())); + while (channel.read(fork) != -1 && fork.hasRemaining()) + ; + ((java.nio.Buffer) buffer).position(fork.position()); + return buffer.position() == 0 ? -1 : buffer.position(); + } + + public static int readFromChannel(ReadableByteChannel channel, ByteBuffer buffer) throws IOException { + int rem = buffer.position(); + while (channel.read(buffer) != -1 && buffer.hasRemaining()) + ; + return buffer.position() - rem; + } + + public static void write(ByteBuffer to, ByteBuffer from) { + if (from.hasArray()) { + to.put(from.array(), from.arrayOffset() + from.position(), Math.min(to.remaining(), from.remaining())); + } else { + to.put(toArrayL(from, to.remaining())); + } + } + + public static void writeL(ByteBuffer to, ByteBuffer from, int count) { + if (from.hasArray()) { + to.put(from.array(), from.arrayOffset() + from.position(), Math.min(from.remaining(), count)); + } else { + to.put(toArrayL(from, count)); + } + } + + public static void fill(ByteBuffer buffer, byte val) { + while (buffer.hasRemaining()) + buffer.put(val); + } + + public static final MappedByteBuffer map(String fileName) throws IOException { + return mapFile(new File(fileName)); + } + + public static final MappedByteBuffer mapFile(File file) throws IOException { + FileInputStream is = new FileInputStream(file); + MappedByteBuffer map = is.getChannel().map(MapMode.READ_ONLY, 0, file.length()); + is.close(); + return map; + } + + public static int skip(ByteBuffer buffer, int count) { + int toSkip = Math.min(buffer.remaining(), count); + ((Buffer) buffer).position(buffer.position() + toSkip); + return toSkip; + } + + public static ByteBuffer from(ByteBuffer buffer, int offset) { + ByteBuffer dup = buffer.duplicate(); + dup.position(dup.position() + offset); + return dup; + } + + public static ByteBuffer combineBuffers(Iterable picture) { + int size = 0; + for (ByteBuffer byteBuffer : picture) { + size += byteBuffer.remaining(); + } + ByteBuffer result = ByteBuffer.allocate(size); + for (ByteBuffer byteBuffer : picture) { + write(result, byteBuffer); + } + ((java.nio.Buffer) result).flip(); + return result; + } + + public static boolean combineBuffersInto(ByteBuffer dup, List buffers) { + throw new RuntimeException("Stan"); + } + + public static String readString(ByteBuffer buffer, int len) { + return stringFromBytes(toArray(read(buffer, len))); + } + + public static String readPascalStringL(ByteBuffer buffer, int maxLen) { + ByteBuffer sub = read(buffer, maxLen + 1); + return stringFromBytes(toArray(NIOUtils.read(sub, Math.min(sub.get() & 0xff, maxLen)))); + } + + public static void writePascalStringL(ByteBuffer buffer, String string, int maxLen) { + buffer.put((byte) string.length()); + buffer.put(asciiString(string)); + skip(buffer, maxLen - string.length()); + } + + public static byte[] asciiString(String fourcc) { + return Platform.getBytes(fourcc); + } + + public static void writePascalString(ByteBuffer buffer, String name) { + buffer.put((byte) name.length()); + buffer.put(asciiString(name)); + } + + public static String readPascalString(ByteBuffer buffer) { + return readString(buffer, buffer.get() & 0xff); + } + + public static String readNullTermString(ByteBuffer buffer) { + return readNullTermStringCharset(buffer, Platform.UTF_8); + } + + public static String readNullTermStringCharset(ByteBuffer buffer, String charset) { + ByteBuffer fork = buffer.duplicate(); + while (buffer.hasRemaining() && buffer.get() != 0) { + } + ((java.nio.Buffer) fork).limit(buffer.position() - 1); + return Platform.stringFromCharset(toArray(fork), charset); + } + + public static void writeNullTermString(ByteBuffer buffer, String str) { + writeNullTermStringCharset(buffer, str, Platform.UTF_8); + } + + public static void writeNullTermStringCharset(ByteBuffer buffer, String str, String charset) { + try { + byte[] bytes = str.getBytes(charset); + buffer.put(bytes); + buffer.put((byte) 0); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + public static ByteBuffer readBuf(ByteBuffer buffer) { + ByteBuffer result = buffer.duplicate(); + ((java.nio.Buffer) buffer).position(buffer.limit()); + return result; + } + + public static void copy(ReadableByteChannel _in, WritableByteChannel out, long amount) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(0x10000); + int read; + do { + buf.position(0); + buf.limit((int) Math.min(amount, buf.capacity())); + read = _in.read(buf); + if (read != -1) { + buf.flip(); + out.write(buf); + amount -= read; + } + } while (read != -1 && amount > 0); + } + + public static void copyAll(ReadableByteChannel in, WritableByteChannel out) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(0x10000); + int read; + do { + ((java.nio.Buffer) buf).position(0); + read = in.read(buf); + if (read != -1) { + ((java.nio.Buffer) buf).flip(); + out.write(buf); + } + } while (read != -1); + } + + public static void closeQuietly(Closeable channel) { + if (channel == null) + return; + try { + channel.close(); + } catch (IOException e) { + } + } + + public static byte readByte(ReadableByteChannel channel) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(1); + channel.read(buf); + buf.flip(); + return buf.get(); + } + + public static byte[] readNByte(ReadableByteChannel channel, int n) throws IOException { + byte[] result = new byte[n]; + channel.read(ByteBuffer.wrap(result)); + return result; + } + + public static int readInt(ReadableByteChannel channel) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(4); + channel.read(buf); + buf.flip(); + return buf.getInt(); + } + + public static int readIntOrder(ReadableByteChannel channel, ByteOrder order) throws IOException { + ByteBuffer buf = (ByteBuffer) ByteBuffer.allocate(4).order(order); + channel.read(buf); + buf.flip(); + return buf.getInt(); + } + + public static void writeByte(WritableByteChannel channel, byte value) throws IOException { + channel.write((ByteBuffer) ByteBuffer.allocate(1).put(value).flip()); + } + + public static void writeIntOrder(WritableByteChannel channel, int value, ByteOrder order) throws IOException { + ByteBuffer order2 = (ByteBuffer) ByteBuffer.allocate(4).order(order); + channel.write((ByteBuffer) order2.putInt(value).flip()); + } + + public static void writeIntLE(WritableByteChannel channel, int value) throws IOException { + ByteBuffer allocate = ByteBuffer.allocate(4); + allocate.order(ByteOrder.LITTLE_ENDIAN); + channel.write((ByteBuffer) allocate.putInt(value).flip()); + } + + public static void writeInt(WritableByteChannel channel, int value) throws IOException { + ByteBuffer putInt = ByteBuffer.allocate(4).putInt(value); + channel.write((ByteBuffer) ((java.nio.Buffer) putInt).flip()); + } + + public static void writeLong(WritableByteChannel channel, long value) throws IOException { + ByteBuffer putLong = ByteBuffer.allocate(8).putLong(value); + channel.write((ByteBuffer) ((java.nio.Buffer) putLong).flip()); + } + + public static FileChannelWrapper readableChannel(File file) throws FileNotFoundException { + return new FileChannelWrapper(new FileInputStream(file).getChannel()); + } + + public static FileChannelWrapper writableChannel(File file) throws FileNotFoundException { + return new FileChannelWrapper(new FileOutputStream(file).getChannel()); + } + + public static FileChannelWrapper rwChannel(File file) throws FileNotFoundException { + return new FileChannelWrapper(new RandomAccessFile(file, "rw").getChannel()); + } + + public static FileChannelWrapper readableFileChannel(String file) throws FileNotFoundException { + return new FileChannelWrapper(new FileInputStream(file).getChannel()); + } + + public static FileChannelWrapper writableFileChannel(String file) throws FileNotFoundException { + return new FileChannelWrapper(new FileOutputStream(file).getChannel()); + } + + public static FileChannelWrapper rwFileChannel(String file) throws FileNotFoundException { + return new FileChannelWrapper(new RandomAccessFile(file, "rw").getChannel()); + } + + public static AutoFileChannelWrapper autoChannel(File file) throws IOException { + return new AutoFileChannelWrapper(file); + } + + public static ByteBuffer duplicate(ByteBuffer bb) { + ByteBuffer out = ByteBuffer.allocate(bb.remaining()); + out.put(bb.duplicate()); + ((java.nio.Buffer) out).flip(); + return out; + } + + public static int find(List catalog, ByteBuffer key) { + byte[] keyA = toArray(key); + for (int i = 0; i < catalog.size(); i++) { + if (Platform.arrayEqualsByte(toArray(catalog.get(i)), keyA)) + return i; + } + return -1; + } + + public static interface FileReaderListener { + void progress(int percentDone); + } + + public static abstract class FileReader { + private int oldPd; + + protected abstract void data(ByteBuffer data, long filePos); + + protected abstract void done(); + + public void readChannel(SeekableByteChannel ch, int bufferSize, FileReaderListener listener) + throws IOException { + ByteBuffer buf = ByteBuffer.allocate(bufferSize); + long size = ch.size(); + for (long pos = ch.position(); ch.read(buf) != -1; pos = ch.position()) { + buf.flip(); + data(buf, pos); + buf.flip(); + if (listener != null) { + int newPd = (int) (100 * pos / size); + if (newPd != oldPd) + listener.progress(newPd); + oldPd = newPd; + } + } + done(); + } + + public void readFile(File source, int bufferSize, FileReaderListener listener) throws IOException { + SeekableByteChannel ch = null; + try { + ch = NIOUtils.readableChannel(source); + readChannel(ch, bufferSize, listener); + } finally { + NIOUtils.closeQuietly(ch); + } + } + } + + public static byte getRel(ByteBuffer bb, int rel) { + return bb.get(bb.position() + rel); + } + + public static ByteBuffer cloneBuffer(ByteBuffer pesBuffer) { + ByteBuffer res = ByteBuffer.allocate(pesBuffer.remaining()); + res.put(pesBuffer.duplicate()); + res.clear(); + return res; + } + + public static ByteBuffer clone(ByteBuffer byteBuffer) { + ByteBuffer result = ByteBuffer.allocate(byteBuffer.remaining()); + result.put(byteBuffer.duplicate()); + result.flip(); + return result; + } + + public static ByteBuffer asByteBuffer(byte[] bytes) { + return ByteBuffer.wrap(bytes); + } + + public static ByteBuffer asByteBufferInt(int[] ints) { + return asByteBuffer(ArrayUtil.toByteArray(ints)); + } + + public static void relocateLeftover(ByteBuffer bb) { + int pos; + for (pos = 0; bb.hasRemaining(); pos++) { + bb.put(pos, bb.get()); + } + bb.position(pos); + bb.limit(bb.capacity()); + } + + public static boolean fetchUrl(URL urlInit, File fm) throws IOException { + ReadableByteChannel in = null; + SeekableByteChannel out = null; + try { + in = Channels.newChannel(urlInit.openConnection().getInputStream()); + out = NIOUtils.writableChannel(fm); + NIOUtils.copyAll(in, out); + } catch (FileNotFoundException e) { + return false; + } finally { + NIOUtils.closeQuietly(in); + NIOUtils.closeQuietly(out); + } + return true; + } + + public static void copyFile(File src, File dst) throws IOException { + FileChannelWrapper in = null; + FileChannelWrapper out = null; + try { + in = readableChannel(src); + out = writableChannel(dst); + copyAll(in, out); + } finally { + closeQuietly(in); + closeQuietly(out); + } + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/SeekableByteChannel.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/SeekableByteChannel.java new file mode 100644 index 0000000..f4e4b93 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/SeekableByteChannel.java @@ -0,0 +1,24 @@ +package org.monte.media.impl.jcodec.common.io; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.channels.ByteChannel; +import java.nio.channels.Channel; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public interface SeekableByteChannel extends ByteChannel, Channel, Closeable, ReadableByteChannel, WritableByteChannel { + long position() throws IOException; + + SeekableByteChannel setPosition(long newPosition) throws IOException; + + long size() throws IOException; + + SeekableByteChannel truncate(long size) throws IOException; +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/StringReader.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/StringReader.java new file mode 100644 index 0000000..dc882f0 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/StringReader.java @@ -0,0 +1,42 @@ +package org.monte.media.impl.jcodec.common.io; + +import org.monte.media.impl.jcodec.platform.Platform; + +import java.io.IOException; +import java.io.InputStream; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public abstract class StringReader { + public static String readString(InputStream input, int len) throws IOException { + byte[] bs = _sureRead(input, len); + return bs == null ? null : Platform.stringFromBytes(bs); + } + + public static byte[] _sureRead(InputStream input, int len) throws IOException { + byte[] res = new byte[len]; + if (sureRead(input, res, res.length) == len) + return res; + return null; + } + + public static int sureRead(InputStream input, byte[] buf, int len) throws IOException { + int read = 0; + while (read < len) { + int tmp = input.read(buf, read, len - read); + if (tmp == -1) + break; + read += tmp; + } + return read; + } + + public static void sureSkip(InputStream is, long l) throws IOException { + while (l > 0) + l -= is.skip(l); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/VLC.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/VLC.java new file mode 100644 index 0000000..90f0bf4 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/VLC.java @@ -0,0 +1,171 @@ +package org.monte.media.impl.jcodec.common.io; + +import org.monte.media.impl.jcodec.common.IntArrayList; +import org.monte.media.impl.jcodec.platform.Platform; + +import java.io.PrintStream; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Table-based prefix VLC reader + * + * @author The JCodec project + */ +public class VLC { + + /** + * @param codes vlc codes + * @return + */ + public static VLC createVLC(String[] codes) { + IntArrayList _codes = IntArrayList.createIntArrayList(); + IntArrayList _codeSizes = IntArrayList.createIntArrayList(); + for (int i = 0; i < codes.length; i++) { + String string = codes[i]; + _codes.add(Integer.parseInt(string, 2) << (32 - string.length())); + _codeSizes.add(string.length()); + } + VLC vlc = new VLC(_codes.toArray(), _codeSizes.toArray()); + return vlc; + } + + private int[] codes; + private int[] codeSizes; + + private int[] values; + private int[] valueSizes; + + public VLC(int[] codes, int[] codeSizes) { + this.codes = codes; + this.codeSizes = codeSizes; + + _invert(); + } + + public static VLC fromMixed(int[][] codesAndSizes) { + int[] codes = new int[codesAndSizes.length]; + int[] sizes = new int[codesAndSizes.length]; + for (int i = 0; i < codesAndSizes.length; i++) { + sizes[i] = codesAndSizes[i][0]; + codes[i] = codesAndSizes[i][1] << (32 - sizes[i]); + } + return new VLC(codes, sizes); + } + + private void _invert() { + IntArrayList values = IntArrayList.createIntArrayList(); + IntArrayList valueSizes = IntArrayList.createIntArrayList(); + invert(0, 0, 0, values, valueSizes); + this.values = values.toArray(); + this.valueSizes = valueSizes.toArray(); + } + + private int invert(int startOff, int level, int prefix, IntArrayList values, IntArrayList valueSizes) { + + int tableEnd = startOff + 256; + values.fill(startOff, tableEnd, -1); + valueSizes.fill(startOff, tableEnd, 0); + + int prefLen = level << 3; + for (int i = 0; i < codeSizes.length; i++) { + if ((codeSizes[i] <= prefLen) || (level > 0 && (codes[i] >>> (32 - prefLen)) != prefix)) + continue; + + int pref = codes[i] >>> (32 - prefLen - 8); + int code = pref & 0xff; + int len = codeSizes[i] - prefLen; + if (len <= 8) { + for (int k = 0; k < (1 << (8 - len)); k++) { + values.set(startOff + code + k, i); + valueSizes.set(startOff + code + k, len); + } + } else { + if (values.get(startOff + code) == -1) { + values.set(startOff + code, tableEnd); + tableEnd = invert(tableEnd, level + 1, pref, values, valueSizes); + } + } + } + + return tableEnd; + } + + public int readVLC16(BitReader _in) { + + int string = _in.check16Bits(); + int b = string >>> 8; + int code = values[b]; + int len = valueSizes[b]; + + if (len == 0) { + b = (string & 0xff) + code; + code = values[b]; + _in.skipFast(8 + valueSizes[b]); + } else + _in.skipFast(len); + + return code; + } + + public int readVLC(BitReader _in) { + + int code = 0, len = 0, overall = 0, total = 0; + for (int i = 0; len == 0; i++) { + int string = _in.checkNBit(8); + int ind = string + code; + code = values[ind]; + len = valueSizes[ind]; + + int bits = len != 0 ? len : 8; + total += bits; + overall = (overall << bits) | (string >> (8 - bits)); + _in.skip(bits); + + if (code == -1) + throw new RuntimeException("Invalid code prefix " + binary(overall, (i << 3) + bits)); + } + + // System.out.println("VLC: " + binary(overall, total)); + + return code; + } + + private static String binary(int string, int len) { + char[] symb = new char[len]; + for (int i = 0; i < len; i++) { + symb[i] = (string & (1 << (len - i - 1))) != 0 ? '1' : '0'; + } + return Platform.stringFromChars(symb); + } + + public void writeVLC(BitWriter out, int code) { + out.writeNBit(codes[code] >>> (32 - codeSizes[code]), codeSizes[code]); + } + + public void printTable(PrintStream ps) { + for (int i = 0; i < values.length; i++) { + ps.println(i + ": " + extracted(i) + " (" + valueSizes[i] + ") -> " + values[i]); + + } + } + + private static String extracted(int num) { + + String str = Integer.toString(num & 0xff, 2); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < 8 - str.length(); i++) + builder.append("0"); + builder.append(str); + return builder.toString(); + } + + public int[] getCodes() { + return codes; + } + + public int[] getCodeSizes() { + return codeSizes; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/VLCBuilder.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/VLCBuilder.java new file mode 100644 index 0000000..440ecc5 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/io/VLCBuilder.java @@ -0,0 +1,68 @@ +package org.monte.media.impl.jcodec.common.io; + +import org.monte.media.impl.jcodec.common.IntArrayList; +import org.monte.media.impl.jcodec.common.IntIntMap; + + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * prefix VLC reader builder + * + * @author The JCodec project + */ +public class VLCBuilder { + + public static VLCBuilder createVLCBuilder(int[] codes, int[] lens, int[] vals) { + VLCBuilder b = new VLCBuilder(); + for (int i = 0; i < codes.length; i++) { + b.setInt(codes[i], lens[i], vals[i]); + } + return b; + } + + private IntIntMap forward; + private IntIntMap inverse; + private IntArrayList codes; + private IntArrayList codesSizes; + + public VLCBuilder() { + this.forward = new IntIntMap(); + this.inverse = new IntIntMap(); + this.codes = IntArrayList.createIntArrayList(); + this.codesSizes = IntArrayList.createIntArrayList(); + } + + public VLCBuilder set(int val, String code) { + setInt(Integer.parseInt(code, 2), code.length(), val); + + return this; + } + + public VLCBuilder setInt(int code, int len, int val) { + codes.add(code << (32 - len)); + codesSizes.add(len); + forward.put(val, codes.size() - 1); + inverse.put(codes.size() - 1, val); + + return this; + } + + public VLC getVLC() { + final VLCBuilder self = this; + return new VLC(codes.toArray(), codesSizes.toArray()) { + public int readVLC(BitReader _in) { + return self.inverse.get(super.readVLC(_in)); + } + + public int readVLC16(BitReader _in) { + return self.inverse.get(super.readVLC16(_in)); + } + + public void writeVLC(BitWriter out, int code) { + super.writeVLC(out, self.forward.get(code)); + } + }; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/BufferLogSink.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/BufferLogSink.java new file mode 100644 index 0000000..a7f5ea4 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/BufferLogSink.java @@ -0,0 +1,30 @@ +package org.monte.media.impl.jcodec.common.logging; + +import java.util.LinkedList; +import java.util.List; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Just stores log messages to be extracted at later point + * + * @author The JCodec project + */ +public class BufferLogSink implements LogSink { + + private List messages; + + public BufferLogSink() { + this.messages = new LinkedList(); + } + + @Override + public void postMessage(Message msg) { + messages.add(msg); + } + + public List getMessages() { + return messages; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/LogLevel.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/LogLevel.java new file mode 100644 index 0000000..10d5245 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/LogLevel.java @@ -0,0 +1,5 @@ +package org.monte.media.impl.jcodec.common.logging; + +public enum LogLevel { + DEBUG, INFO, WARN, ERROR +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/LogSink.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/LogSink.java new file mode 100644 index 0000000..a16260d --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/LogSink.java @@ -0,0 +1,5 @@ +package org.monte.media.impl.jcodec.common.logging; + +public interface LogSink { + void postMessage(Message msg); +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/Logger.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/Logger.java new file mode 100644 index 0000000..ca16163 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/Logger.java @@ -0,0 +1,98 @@ +package org.monte.media.impl.jcodec.common.logging; + +import java.util.LinkedList; +import java.util.List; + +import static org.monte.media.impl.jcodec.common.logging.LogLevel.DEBUG; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * JCodec has to be dependancy free, so it can run both on Java SE and Android + * hence defining here our small logger that can be plugged into the logging + * framework of choice on the target platform + * + * @author The JCodec project + */ +public class Logger { + + private static List stageSinks = new LinkedList(); + private static List sinks; + + public static void debug(String message) { + message(LogLevel.DEBUG, message, null); + } + + public static void debug(String message, Object... args) { + message(LogLevel.DEBUG, message, args); + } + + public static void info(String message) { + message(LogLevel.INFO, message, null); + } + + public static void info(String message, Object... args) { + message(LogLevel.INFO, message, args); + } + + public static void warn(String message) { + message(LogLevel.WARN, message, null); + } + + public static void warn(String message, Object... args) { + message(LogLevel.WARN, message, args); + } + + public static void error(String message) { + message(LogLevel.ERROR, message, null); + } + + public static void error(String message, Object... args) { + message(LogLevel.ERROR, message, args); + } + + private static void message(LogLevel level, String message, Object[] args) { + if (Logger.globalLogLevel.ordinal() > level.ordinal()) { + return; + } + if (sinks == null) { + synchronized (Logger.class) { + if (sinks == null) { + sinks = stageSinks; + stageSinks = null; + if (sinks.isEmpty()) + sinks.add(OutLogSink.createOutLogSink()); + } + } + } + Message msg; + if (DEBUG.equals(globalLogLevel)) { + StackTraceElement tr = Thread.currentThread().getStackTrace()[3]; + msg = new Message(level, tr.getFileName(), tr.getClassName(), tr.getMethodName(), tr.getLineNumber(), + message, args); + } else { + msg = new Message(level, "", "", "", 0, message, args); + } + for (LogSink logSink : sinks) { + logSink.postMessage(msg); + } + } + + private static LogLevel globalLogLevel = LogLevel.INFO; + + public synchronized static void setLevel(LogLevel level) { + globalLogLevel = level; + } + + public synchronized static LogLevel getLevel() { + return globalLogLevel; + } + + + public static void addSink(LogSink sink) { + if (stageSinks == null) + throw new IllegalStateException("Logger already started"); + stageSinks.add(sink); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/Message.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/Message.java new file mode 100644 index 0000000..16795a4 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/Message.java @@ -0,0 +1,50 @@ +package org.monte.media.impl.jcodec.common.logging; + +public class Message { + private LogLevel level; + private String fileName; + private String className; + private int lineNumber; + private String message; + private String methodName; + private Object[] args; + + public Message(LogLevel level, String fileName, String className, String methodName, int lineNumber, String message, Object[] args) { + this.level = level; + this.fileName = fileName; + this.className = className; + this.methodName = methodName; + this.message = methodName; + this.lineNumber = lineNumber; + this.message = message; + this.args = args; + } + + public LogLevel getLevel() { + return level; + } + + public String getFileName() { + return fileName; + } + + public String getClassName() { + return className; + } + + public String getMethodName() { + return methodName; + } + + public int getLineNumber() { + return lineNumber; + } + + public String getMessage() { + return message; + } + + public Object[] getArgs() { + return args; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/OutLogSink.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/OutLogSink.java new file mode 100644 index 0000000..bf1f8ea --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/logging/OutLogSink.java @@ -0,0 +1,82 @@ +package org.monte.media.impl.jcodec.common.logging; + +import org.monte.media.impl.jcodec.common.tools.MainUtils; +import org.monte.media.impl.jcodec.common.tools.MainUtils.ANSIColor; + +import java.io.PrintStream; +import java.util.HashMap; +import java.util.Map; + +import static org.monte.media.impl.jcodec.common.tools.MainUtils.colorString; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Outputs messages to standard output + * + * @author The JCodec project + */ +public class OutLogSink implements LogSink { + + private static String empty = " "; + + public static class SimpleFormat implements MessageFormat { + private String fmt; + private static Map colorMap = new HashMap(); + + static { + colorMap.put(LogLevel.DEBUG, ANSIColor.BROWN); + colorMap.put(LogLevel.INFO, ANSIColor.GREEN); + colorMap.put(LogLevel.WARN, ANSIColor.MAGENTA); + colorMap.put(LogLevel.ERROR, ANSIColor.RED); + } + + ; + + public SimpleFormat(String fmt) { + this.fmt = fmt; + } + + @Override + public String formatMessage(Message msg) { + String str = fmt.replace("#level", String.valueOf(msg.getLevel())) + .replace("#color_code", String.valueOf(30 + colorMap.get(msg.getLevel()).ordinal())) + .replace("#class", msg.getClassName()).replace("#method", msg.getMethodName()) + .replace("#file", msg.getFileName()).replace("#line", String.valueOf(msg.getLineNumber())) + .replace("#message", msg.getMessage()); + return str; + } + } + + ; + + public static SimpleFormat DEFAULT_FORMAT = new SimpleFormat( + colorString("[#level]", "#color_code") + MainUtils.bold("\t#class.#method (#file:#line):") + "\t#message"); + + public static OutLogSink createOutLogSink() { + return new OutLogSink(System.out, DEFAULT_FORMAT, LogLevel.INFO); + } + + private PrintStream out; + private MessageFormat fmt; + private LogLevel minLevel; + + public OutLogSink(PrintStream out, MessageFormat fmt, LogLevel minLevel) { + this.out = out; + this.fmt = fmt; + this.minLevel = minLevel; + } + + @Override + public void postMessage(Message msg) { + if (msg.getLevel().ordinal() < minLevel.ordinal()) + return; + String str = fmt.formatMessage(msg); + out.println(str); + } + + public static interface MessageFormat { + String formatMessage(Message msg); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/ColorSpace.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/ColorSpace.java new file mode 100644 index 0000000..fa2c8d8 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/ColorSpace.java @@ -0,0 +1,124 @@ +package org.monte.media.impl.jcodec.common.model; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public final class ColorSpace { + public static final int MAX_PLANES = 4; + + public int nComp; + + public int[] compPlane; + + public int[] compWidth; + + public int[] compHeight; + + public boolean planar; + + private String _name; + + public int bitsPerPixel; + + private ColorSpace(String name, int nComp, int[] compPlane, int[] compWidth, int[] compHeight, boolean planar) { + this._name = name; + this.nComp = nComp; + this.compPlane = compPlane; + this.compWidth = compWidth; + this.compHeight = compHeight; + this.planar = planar; + this.bitsPerPixel = calcBitsPerPixel(nComp, compWidth, compHeight); + } + + @Override + public String toString() { + return _name; + } + + private static int calcBitsPerPixel(int nComp, int[] compWidth, int[] compHeight) { + int bitsPerPixel = 0; + for (int i = 0; i < nComp; i++) { + bitsPerPixel += ((8 >> compWidth[i]) >> compHeight[i]); + } + return bitsPerPixel; + } + + public int getWidthMask() { + return ~(nComp > 1 ? compWidth[1] : 0); + } + + public int getHeightMask() { + return ~(nComp > 1 ? compHeight[1] : 0); + } + + /** + * Determines if two colors match. Aside from simply comparing the objects + * this function also takes into account lables ANY, ANY_INTERLEAVED, ANY + * PLANAR. + * + * @param inputColor + * @return True if the color is the same or matches the label. + */ + public boolean matches(ColorSpace inputColor) { + if (inputColor == this) + return true; + if (inputColor == ANY || this == ANY) + return true; + if ((inputColor == ANY_INTERLEAVED || this == ANY_INTERLEAVED || inputColor == ANY_PLANAR || this == ANY_PLANAR) + && inputColor.planar == this.planar) + return true; + return false; + } + + /** + * Calculates the component size based on the fullt size and color subsampling of the given component index. + * + * @param size + * @return Component size + */ + public Size compSize(Size size, int comp) { + if (compWidth[comp] == 0 && compHeight[comp] == 0) + return size; + return new Size(size.getWidth() >> compWidth[comp], size.getHeight() >> compHeight[comp]); + } + + private static final int[] _000 = new int[]{0, 0, 0}; + private static final int[] _011 = new int[]{0, 1, 1}; + private static final int[] _012 = new int[]{0, 1, 2}; + public final static ColorSpace BGR = new ColorSpace("BGR", 3, _000, _000, _000, false); + public final static ColorSpace RGB = new ColorSpace("RGB", 3, _000, _000, _000, false); + public final static ColorSpace YUV420 = new ColorSpace("YUV420", 3, _012, _011, _011, true); + public final static ColorSpace YUV420J = new ColorSpace("YUV420J", 3, _012, _011, _011, true); + public final static ColorSpace YUV422 = new ColorSpace("YUV422", 3, _012, _011, _000, true); + public final static ColorSpace YUV422J = new ColorSpace("YUV422J", 3, _012, _011, _000, true); + public final static ColorSpace YUV444 = new ColorSpace("YUV444", 3, _012, _000, _000, true); + public final static ColorSpace YUV444J = new ColorSpace("YUV444J", 3, _012, _000, _000, true); + public final static ColorSpace YUV422_10 = new ColorSpace("YUV422_10", 3, _012, _011, _000, true); + public final static ColorSpace GREY = new ColorSpace("GREY", 1, new int[]{0}, new int[]{0}, new int[]{0}, true); + public final static ColorSpace MONO = new ColorSpace("MONO", 1, _000, _000, _000, true); + public final static ColorSpace YUV444_10 = new ColorSpace("YUV444_10", 3, _012, _000, _000, true); + + /** + * Any color space, used in the cases where any color space will do. + */ + public final static ColorSpace ANY = new ColorSpace("ANY", 0, null, null, null, true); + + /** + * Any planar color space, used in the cases where any planar color space will do. + */ + public final static ColorSpace ANY_PLANAR = new ColorSpace("ANY_PLANAR", 0, null, null, null, true); + + /** + * Any interleaved color space, used in the cases where any interleaved color space will do. + */ + public final static ColorSpace ANY_INTERLEAVED = new ColorSpace("ANY_INTERLEAVED", 0, null, null, null, false); + + /** + * Same color, used in filters to declare that the color stays unchanged. + */ + public final static ColorSpace SAME = new ColorSpace("SAME", 0, null, null, null, false); + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Packet.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Packet.java new file mode 100644 index 0000000..3262792 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Packet.java @@ -0,0 +1,140 @@ +package org.monte.media.impl.jcodec.common.model; + +import java.nio.ByteBuffer; +import java.util.Comparator; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Encoded stream packet + * + * @author The JCodec project + */ +public class Packet { + + public enum FrameType { + KEY, INTER, UNKNOWN + } + + ; + + public ByteBuffer data; + public long pts; + public int timescale; + public long duration; + public long frameNo; + public FrameType frameType; + public TapeTimecode tapeTimecode; + public int displayOrder; + + public static Packet createPacket(ByteBuffer data, long pts, int timescale, long duration, long frameNo, + FrameType frameType, TapeTimecode tapeTimecode) { + return new Packet(data, pts, timescale, duration, frameNo, frameType, tapeTimecode, 0); + } + + public static Packet createPacketWithData(Packet other, ByteBuffer data) { + return new Packet(data, other.pts, other.timescale, other.duration, other.frameNo, other.frameType, + other.tapeTimecode, other.displayOrder); + } + + public Packet(ByteBuffer data, long pts, int timescale, long duration, long frameNo, FrameType frameType, + TapeTimecode tapeTimecode, int displayOrder) { + this.data = data; + this.pts = pts; + this.timescale = timescale; + this.duration = duration; + this.frameNo = frameNo; + this.frameType = frameType; + this.tapeTimecode = tapeTimecode; + this.displayOrder = displayOrder; + } + + public ByteBuffer getData() { + return data.duplicate(); + } + + public long getPts() { + return pts; + } + + public int getTimescale() { + return timescale; + } + + public long getDuration() { + return duration; + } + + public long getFrameNo() { + return frameNo; + } + + public void setTimescale(int timescale) { + this.timescale = timescale; + } + + public TapeTimecode getTapeTimecode() { + return tapeTimecode; + } + + public void setTapeTimecode(TapeTimecode tapeTimecode) { + this.tapeTimecode = tapeTimecode; + } + + public int getDisplayOrder() { + return displayOrder; + } + + public void setDisplayOrder(int displayOrder) { + this.displayOrder = displayOrder; + } + + public FrameType getFrameType() { + return frameType; + } + + public void setFrameType(FrameType frameType) { + this.frameType = frameType; + } + + public RationalLarge getPtsR() { + return RationalLarge.R(pts, timescale); + } + + public double getPtsD() { + return ((double) pts) / timescale; + } + + public double getDurationD() { + return ((double) duration) / timescale; + } + + public void setData(ByteBuffer data) { + this.data = data; + } + + public void setPts(long pts) { + this.pts = pts; + } + + public static final Comparator FRAME_ASC = new Comparator() { + public int compare(Packet o1, Packet o2) { + if (o1 == null && o2 == null) + return 0; + if (o1 == null) + return -1; + if (o2 == null) + return 1; + return o1.frameNo < o2.frameNo ? -1 : (o1.frameNo == o2.frameNo ? 0 : 1); + } + }; + + public void setDuration(long duration) { + this.duration = duration; + } + + public boolean isKeyFrame() { + return frameType == FrameType.KEY; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Picture.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Picture.java new file mode 100644 index 0000000..f290c46 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Picture.java @@ -0,0 +1,390 @@ +package org.monte.media.impl.jcodec.common.model; + +import org.monte.media.impl.jcodec.common.Tuple._2; +import org.monte.media.impl.jcodec.common.Tuple._3; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import java.util.Arrays; + +import static java.lang.System.arraycopy; +import static org.monte.media.impl.jcodec.common.model.ColorSpace.MAX_PLANES; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * The data is -128 shifted, so 0 is represented by -128 and 255 is represented + * by +127 + * + * @author The JCodec project + */ +public class Picture { + private ColorSpace color; + + private int width; + private int height; + + private byte[][] data; + private byte[][] lowBits; + private int lowBitsNum; + + private Rect crop; + + public static Picture createPicture(int width, int height, byte[][] data, ColorSpace color) { + return new Picture(width, height, data, null, color, 0, new Rect(0, 0, width, height)); + } + + public static Picture createPictureHiBD(int width, int height, byte[][] data, byte[][] lowBits, ColorSpace color, + int lowBitsNum) { + return new Picture(width, height, data, lowBits, color, lowBitsNum, new Rect(0, 0, width, height)); + } + + public Picture(int width, int height, byte[][] data, byte[][] lowBits, ColorSpace color, int lowBitsNum, Rect crop) { + this.width = width; + this.height = height; + this.data = data; + this.lowBits = lowBits; + this.color = color; + this.lowBitsNum = lowBitsNum; + this.crop = crop; + + if (color != null) { + for (int i = 0; i < color.nComp; i++) { + int mask = 0xff >> (8 - color.compWidth[i]); + if ((width & mask) != 0) + throw new IllegalArgumentException("Component " + i + " width should be a multiple of " + + (1 << color.compWidth[i]) + " for colorspace: " + color); + if (crop != null && (crop.getWidth() & mask) != 0) + throw new IllegalArgumentException("Component " + i + " cropped width should be a multiple of " + + (1 << color.compWidth[i]) + " for colorspace: " + color); + mask = 0xff >> (8 - color.compHeight[i]); + if ((height & mask) != 0) + throw new IllegalArgumentException("Component " + i + " height should be a multiple of " + + (1 << color.compHeight[i]) + " for colorspace: " + color); + if (crop != null && (crop.getHeight() & mask) != 0) + throw new IllegalArgumentException("Component " + i + " cropped height should be a multiple of " + + (1 << color.compHeight[i]) + " for colorspace: " + color); + } + } + } + + public static Picture copyPicture(Picture other) { + return new Picture(other.width, other.height, other.data, other.lowBits, other.color, 0, other.crop); + } + + public static Picture create(int width, int height, ColorSpace colorSpace) { + return createCropped(width, height, colorSpace, null); + } + + public static Picture createCropped(int width, int height, ColorSpace colorSpace, Rect crop) { + int[] planeSizes = new int[MAX_PLANES]; + for (int i = 0; i < colorSpace.nComp; i++) { + planeSizes[colorSpace.compPlane[i]] += (width >> colorSpace.compWidth[i]) + * (height >> colorSpace.compHeight[i]); + } + int nPlanes = 0; + for (int i = 0; i < MAX_PLANES; i++) + nPlanes += planeSizes[i] != 0 ? 1 : 0; + + byte[][] data = new byte[nPlanes][]; + for (int i = 0, plane = 0; i < MAX_PLANES; i++) { + if (planeSizes[i] != 0) { + data[plane++] = new byte[planeSizes[i]]; + } + } + return new Picture(width, height, data, null, colorSpace, 0, crop); + } + + public static Picture createCroppedHiBD(int width, int height, int lowBitsNum, ColorSpace colorSpace, Rect crop) { + Picture result = createCropped(width, height, colorSpace, crop); + if (lowBitsNum <= 0) + return result; + byte[][] data = result.getData(); + int nPlanes = data.length; + + byte[][] lowBits = new byte[nPlanes][]; + for (int i = 0, plane = 0; i < nPlanes; i++) { + lowBits[plane++] = new byte[data[i].length]; + } + result.setLowBits(lowBits); + result.setLowBitsNum(lowBitsNum); + + return result; + } + + private void setLowBitsNum(int lowBitsNum) { + this.lowBitsNum = lowBitsNum; + } + + private void setLowBits(byte[][] lowBits) { + this.lowBits = lowBits; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public byte[] getPlaneData(int plane) { + return data[plane]; + } + + public ColorSpace getColor() { + return color; + } + + public void setColor(ColorSpace color) { + this.color = color; + } + + public byte[][] getData() { + return data; + } + + public byte[][] getLowBits() { + return lowBits; + } + + + public Rect getCrop() { + return crop; + } + + public int getPlaneWidth(int plane) { + return width >> color.compWidth[plane]; + } + + public int getPlaneHeight(int plane) { + return height >> color.compHeight[plane]; + } + + public boolean compatible(Picture src) { + return src.color == color && src.width == width && src.height == height; + } + + public Picture createCompatible() { + return Picture.create(width, height, color); + } + + public void copyFrom(Picture src) { + if (!compatible(src)) + throw new IllegalArgumentException("Can not copy to incompatible picture"); + for (int plane = 0; plane < color.nComp; plane++) { + if (data[plane] == null) + continue; + arraycopy(src.data[plane], 0, data[plane], 0, (width >> color.compWidth[plane]) + * (height >> color.compHeight[plane])); + } + } + + /** + * Creates a cropped clone of this picture. + * + * @return + */ + public Picture cloneCropped() { + if (cropNeeded()) { + return cropped(); + } else { + Picture clone = createCompatible(); + clone.copyFrom(this); + return clone; + } + } + + public Picture cropped() { + if (!cropNeeded()) + return this; + Picture result = Picture.create(crop.getWidth(), crop.getHeight(), color); + + if (color.planar) { + for (int plane = 0; plane < data.length; plane++) { + if (data[plane] == null) + continue; + cropSub(data[plane], crop.getX() >> color.compWidth[plane], crop.getY() >> color.compHeight[plane], + crop.getWidth() >> color.compWidth[plane], crop.getHeight() >> color.compHeight[plane], + width >> color.compWidth[plane], crop.getWidth() >> color.compWidth[plane], result.data[plane]); + } + } else { + cropSub(data[0], crop.getX(), crop.getY(), crop.getWidth(), + crop.getHeight(), width * color.nComp, crop.getWidth() * color.nComp, result.data[0]); + } + + return result; + } + + protected boolean cropNeeded() { + return crop != null + && (crop.getX() != 0 || crop.getY() != 0 || crop.getWidth() != width || crop.getHeight() != height); + } + + private void cropSub(byte[] src, int x, int y, int w, int h, int srcStride, int dstStride, byte[] tgt) { + int srcOff = y * srcStride + x, dstOff = 0; + for (int i = 0; i < h; i++) { + for (int j = 0; j < dstStride; j++) + tgt[dstOff + j] = src[srcOff + j]; + + srcOff += srcStride; + dstOff += dstStride; + } + } + + public void setCrop(Rect crop) { + this.crop = crop; + } + + public int getCroppedWidth() { + return crop == null ? width : crop.getWidth(); + } + + public int getCroppedHeight() { + return crop == null ? height : crop.getHeight(); + } + + public int getLowBitsNum() { + return lowBitsNum; + } + + public static Picture fromPictureHiBD(PictureHiBD pic) { + int lowBitsNum = pic.getBitDepth() - 8; + int lowBitsRound = (1 << lowBitsNum) >> 1; + + Picture result = Picture.createCroppedHiBD(pic.getWidth(), pic.getHeight(), lowBitsNum, pic.getColor(), + pic.getCrop()); + + for (int i = 0; i < Math.min(pic.getData().length, result.getData().length); i++) { + for (int j = 0; j < Math.min(pic.getData()[i].length, result.getData()[i].length); j++) { + int val = pic.getData()[i][j]; + int round = MathUtil.clip((val + lowBitsRound) >> lowBitsNum, 0, 255); + result.getData()[i][j] = (byte) (round - 128); + } + } + + byte[][] lowBits = result.getLowBits(); + if (lowBits != null) { + for (int i = 0; i < Math.min(pic.getData().length, result.getData().length); i++) { + for (int j = 0; j < Math.min(pic.getData()[i].length, result.getData()[i].length); j++) { + int val = pic.getData()[i][j]; + int round = MathUtil.clip((val + lowBitsRound) >> lowBitsNum, 0, 255); + lowBits[i][j] = (byte) (val - (round << 2)); + } + } + } + + return result; + } + + public PictureHiBD toPictureHiBD() { + PictureHiBD create = PictureHiBD.doCreate(width, height, color, lowBitsNum + 8, crop); + + return toPictureHiBDInternal(create); + } + + public PictureHiBD toPictureHiBDWithBuffer(int[][] buffer) { + PictureHiBD create = new PictureHiBD(width, height, buffer, color, lowBitsNum + 8, crop); + + return toPictureHiBDInternal(create); + } + + private PictureHiBD toPictureHiBDInternal(PictureHiBD pic) { + int[][] dstData = pic.getData(); + + for (int i = 0; i < data.length; i++) { + int planeSize = getPlaneWidth(i) * getPlaneHeight(i); + for (int j = 0; j < planeSize; j++) { + dstData[i][j] = (data[i][j] + 128) << lowBitsNum; + } + } + + if (lowBits != null) { + for (int i = 0; i < lowBits.length; i++) { + int planeSize = getPlaneWidth(i) * getPlaneHeight(i); + for (int j = 0; j < planeSize; j++) { + dstData[i][j] += lowBits[i][j]; + } + } + } + + return pic; + } + + public void fill(int val) { + for (int i = 0; i < data.length; i++) { + Arrays.fill(data[i], (byte) val); + } + } + + private boolean planeEquals(Picture other, int plane) { + int cw = color.compWidth[plane]; + int ch = color.compHeight[plane]; + int offA = other.getCrop() == null ? 0 : ((other.getCrop().getX() >> cw) + (other.getCrop().getY() >> ch) + * (other.getWidth() >> cw)); + int offB = crop == null ? 0 : ((crop.getX() >> cw) + (crop.getY() >> ch) * (width >> cw)); + + byte[] planeData = other.getPlaneData(plane); + for (int i = 0; i < getCroppedHeight() >> ch; i++, offA += (other.getWidth() >> cw), offB += (width >> cw)) { + for (int j = 0; j < getCroppedWidth() >> cw; j++) { + if (planeData[offA + j] != data[plane][offB + j]) + return false; + } + } + return true; + } + + public _3 firstMismatch(Picture other) { + if (other.getCroppedWidth() != getCroppedWidth() || other.getCroppedHeight() != getCroppedHeight() + || other.getColor() != color) + throw new RuntimeException("w,h,c should be same"); + + for (int i = 0; i < getData().length; i++) { + _2 mm = firstMismatchPlane(other, i); + if (mm != null) + return new _3(mm.v0, mm.v1, i); + } + return null; + } + + private _2 firstMismatchPlane(Picture other, int plane) { + int cw = color.compWidth[plane]; + int ch = color.compHeight[plane]; + int offA = other.getCrop() == null ? 0 : ((other.getCrop().getX() >> cw) + (other.getCrop().getY() >> ch) + * (other.getWidth() >> cw)); + int offB = crop == null ? 0 : ((crop.getX() >> cw) + (crop.getY() >> ch) * (width >> cw)); + + byte[] planeData = other.getPlaneData(plane); + for (int i = 0; i < getCroppedHeight() >> ch; i++, offA += (other.getWidth() >> cw), offB += (width >> cw)) { + for (int j = 0; j < getCroppedWidth() >> cw; j++) { + if (planeData[offA + j] != data[plane][offB + j]) + return new _2(j, i); + } + } + return null; + } + + public int getStartX() { + return crop == null ? 0 : crop.getX(); + } + + public int getStartY() { + return crop == null ? 0 : crop.getY(); + } + + public boolean isHiBD() { + return lowBits != null; + } + + public Size getSize() { + return new Size(width, height); + } + + public int pixAt(int x, int y, int pl) { + int cw = color.compWidth[pl]; + int ch = color.compHeight[pl]; + int stride = width >> cw; + + return data[pl][y * stride + x]; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/PictureHiBD.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/PictureHiBD.java new file mode 100644 index 0000000..5d780e3 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/PictureHiBD.java @@ -0,0 +1,199 @@ +package org.monte.media.impl.jcodec.common.model; + +import static java.lang.System.arraycopy; +import static org.monte.media.impl.jcodec.common.model.ColorSpace.MAX_PLANES; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * A YUV picture + * + * @author The JCodec project + */ +public class PictureHiBD { + private ColorSpace color; + + private int width; + private int height; + + private int[][] data; + + private Rect crop; + + private int bitDepth; + + public static PictureHiBD createPicture(int width, int height, int[][] data, ColorSpace color) { + return new PictureHiBD(width, height, data, color, 8, new Rect(0, 0, width, height)); + } + + public static PictureHiBD createPictureWithDepth(int width, int height, int[][] data, ColorSpace color, int bitDepth) { + return new PictureHiBD(width, height, data, color, bitDepth, new Rect(0, 0, width, height)); + } + + public static PictureHiBD createPictureCropped(int width, int height, int[][] data, ColorSpace color, Rect crop) { + return new PictureHiBD(width, height, data, color, 8, crop); + } + + public PictureHiBD(int width, int height, int[][] data, ColorSpace color, int bitDepth, Rect crop) { + this.width = width; + this.height = height; + this.data = data; + this.color = color; + this.crop = crop; + this.bitDepth = bitDepth; + } + + public static PictureHiBD clonePicture(PictureHiBD other) { + return new PictureHiBD(other.width, other.height, other.data, other.color, other.bitDepth, other.crop); + } + + public static PictureHiBD create(int width, int height, ColorSpace colorSpace) { + return doCreate(width, height, colorSpace, 8, null); + } + + public static PictureHiBD createWithDepth(int width, int height, ColorSpace colorSpace, int bitDepth) { + return doCreate(width, height, colorSpace, bitDepth, null); + } + + public static PictureHiBD createCropped(int width, int height, ColorSpace colorSpace, Rect crop) { + return doCreate(width, height, colorSpace, 8, crop); + } + + public static PictureHiBD doCreate(int width, int height, ColorSpace colorSpace, int bitDepth, Rect crop) { + int[] planeSizes = new int[MAX_PLANES]; + for (int i = 0; i < colorSpace.nComp; i++) { + planeSizes[colorSpace.compPlane[i]] += (width >> colorSpace.compWidth[i]) + * (height >> colorSpace.compHeight[i]); + } + int nPlanes = 0; + for (int i = 0; i < MAX_PLANES; i++) + nPlanes += planeSizes[i] != 0 ? 1 : 0; + + int[][] data = new int[nPlanes][]; + for (int i = 0, plane = 0; i < MAX_PLANES; i++) { + if (planeSizes[i] != 0) { + data[plane++] = new int[planeSizes[i]]; + } + } + + return new PictureHiBD(width, height, data, colorSpace, 8, crop); + } + + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int[] getPlaneData(int plane) { + return data[plane]; + } + + public ColorSpace getColor() { + return color; + } + + public int[][] getData() { + return data; + } + + public Rect getCrop() { + return crop; + } + + public int getPlaneWidth(int plane) { + return width >> color.compWidth[plane]; + } + + public int getPlaneHeight(int plane) { + return height >> color.compHeight[plane]; + } + + public boolean compatible(PictureHiBD src) { + return src.color == color && src.width == width && src.height == height; + } + + public PictureHiBD createCompatible() { + return PictureHiBD.create(width, height, color); + } + + public void copyFrom(PictureHiBD src) { + if (!compatible(src)) + throw new IllegalArgumentException("Can not copy to incompatible picture"); + for (int plane = 0; plane < color.nComp; plane++) { + if (data[plane] == null) + continue; + arraycopy(src.data[plane], 0, data[plane], 0, + (width >> color.compWidth[plane]) * (height >> color.compHeight[plane])); + } + } + + public PictureHiBD cropped() { + if (crop == null + || (crop.getX() == 0 && crop.getY() == 0 && crop.getWidth() == width && crop.getHeight() == height)) + return this; + PictureHiBD result = PictureHiBD.create(crop.getWidth(), crop.getHeight(), color); + + for (int plane = 0; plane < color.nComp; plane++) { + if (data[plane] == null) + continue; + cropSub(data[plane], crop.getX() >> color.compWidth[plane], crop.getY() >> color.compHeight[plane], + crop.getWidth() >> color.compWidth[plane], crop.getHeight() >> color.compHeight[plane], + width >> color.compWidth[plane], result.data[plane]); + } + + return result; + } + + private void cropSub(int[] src, int x, int y, int w, int h, int srcStride, int[] tgt) { + int srcOff = y * srcStride + x, dstOff = 0; + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) + tgt[dstOff + j] = src[srcOff + j]; + + srcOff += srcStride; + dstOff += w; + } + } + + public void setCrop(Rect crop) { + this.crop = crop; + } + + public int getCroppedWidth() { + return crop == null ? width : crop.getWidth(); + } + + public int getCroppedHeight() { + return crop == null ? height : crop.getHeight(); + } + + public void setBitDepth(int bitDepth) { + this.bitDepth = bitDepth; + } + + public int getBitDepth() { + return bitDepth; + } + + private boolean planeEquals(PictureHiBD other, int plane) { + int cw = color.compWidth[plane]; + int ch = color.compHeight[plane]; + int offA = other.getCrop() == null ? 0 + : ((other.getCrop().getX() >> cw) + (other.getCrop().getY() >> ch) * (other.getWidth() >> cw)); + int offB = crop == null ? 0 : ((crop.getX() >> cw) + (crop.getY() >> ch) * (width >> cw)); + + int[] planeData = other.getPlaneData(plane); + for (int i = 0; i < getCroppedHeight() >> ch; i++, offA += (other.getWidth() >> cw), offB += (width >> cw)) { + for (int j = 0; j < getCroppedWidth() >> cw; j++) { + if (planeData[offA + j] != data[plane][offB + j]) + return false; + } + } + return true; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Rational.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Rational.java new file mode 100644 index 0000000..28f84c4 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Rational.java @@ -0,0 +1,215 @@ +package org.monte.media.impl.jcodec.common.model; + +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import static java.lang.Integer.parseInt; +import static org.monte.media.impl.jcodec.common.model.RationalLarge.reduceLong; + + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class Rational { + + public static final Rational ONE = new Rational(1, 1); + public static final Rational HALF = new Rational(1, 2); + public static final Rational ZERO = new Rational(0, 1); + public final int num; + public final int den; + + public static Rational R(int num, int den) { + return new Rational(num, den); + } + + public static Rational R1(int num) { + return R(num, 1); + } + + public Rational(int num, int den) { + this.num = num; + this.den = den; + } + + public int getNum() { + return num; + } + + public int getDen() { + return den; + } + + public static Rational parseRational(String string) { + return parse(string); + } + + public static Rational parse(String string) { + int idx = string.indexOf(":"); + if (idx < 0) { + idx = string.indexOf("/"); + } + if (idx > 0) { + String num = string.substring(0, idx); + String den = string.substring(idx + 1); + return new Rational(parseInt(num), parseInt(den)); + } + return R(parseInt(string), 1); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + den; + result = prime * result + num; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Rational other = (Rational) obj; + if (den != other.den) + return false; + if (num != other.num) + return false; + return true; + } + + public int multiplyS(int val) { + return (int) (((long) num * val) / den); + } + + public int divideS(int val) { + return (int) (((long) den * val) / num); + } + + public int divideByS(int val) { + return num / (den * val); + } + + public long multiplyLong(long val) { + return (num * val) / den; + } + + public long divideLong(long val) { + return (den * val) / num; + } + + public Rational flip() { + return new Rational(den, num); + } + + public boolean smallerThen(Rational sec) { + return num * sec.den < sec.num * den; + } + + public boolean greaterThen(Rational sec) { + return num * sec.den > sec.num * den; + } + + public boolean smallerOrEqualTo(Rational sec) { + return num * sec.den <= sec.num * den; + } + + public boolean greaterOrEqualTo(Rational sec) { + return num * sec.den >= sec.num * den; + } + + public boolean equalsRational(Rational other) { + return num * other.den == other.num * den; + } + + public Rational plus(Rational other) { + return reduce(num * other.den + other.num * den, den * other.den); + } + + public RationalLarge plusLarge(RationalLarge other) { + return reduceLong(num * other.den + other.num * den, den * other.den); + } + + public Rational minus(Rational other) { + return reduce(num * other.den - other.num * den, den * other.den); + } + + public RationalLarge minusLarge(RationalLarge other) { + return reduceLong(num * other.den - other.num * den, den * other.den); + } + + public Rational plusInt(int scalar) { + return new Rational(num + scalar * den, den); + } + + public Rational minusInt(int scalar) { + return new Rational(num - scalar * den, den); + } + + public Rational multiplyInt(int scalar) { + return new Rational(num * scalar, den); + } + + public Rational divideInt(int scalar) { + return new Rational(den * scalar, num); + } + + public Rational divideByInt(int scalar) { + return new Rational(num, den * scalar); + } + + public Rational multiply(Rational other) { + return reduce(num * other.num, den * other.den); + } + + public RationalLarge multiplyLarge(RationalLarge other) { + return reduceLong(num * other.num, den * other.den); + } + + public Rational divide(Rational other) { + return reduce(other.num * den, other.den * num); + } + + public RationalLarge divideLarge(RationalLarge other) { + return reduceLong(other.num * den, other.den * num); + } + + public Rational divideBy(Rational other) { + return reduce(num * other.den, den * other.num); + } + + public RationalLarge divideByLarge(RationalLarge other) { + return reduceLong(num * other.den, den * other.num); + } + + public float scalar() { + return (float) num / den; + } + + public int scalarClip() { + return num / den; + } + + public static Rational reduce(int num, int den) { + int gcd = MathUtil.gcd(num, den); + return new Rational(num / gcd, den / gcd); + } + + public static Rational reduceRational(Rational r) { + return reduce(r.getNum(), r.getDen()); + } + + @Override + public String toString() { + return num + "/" + den; + } + + public double toDouble() { + return (double) num / den; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/RationalLarge.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/RationalLarge.java new file mode 100644 index 0000000..4537d5d --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/RationalLarge.java @@ -0,0 +1,187 @@ +package org.monte.media.impl.jcodec.common.model; + +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +import static org.monte.media.impl.jcodec.common.StringUtils.splitS; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class RationalLarge { + + public static final RationalLarge ONE = new RationalLarge(1, 1); + public static final RationalLarge HALF = new RationalLarge(1, 2); + public static final RationalLarge ZERO = new RationalLarge(0, 1); + + final long num; + final long den; + + public RationalLarge(long num, long den) { + this.num = num; + this.den = den; + } + + public long getNum() { + return num; + } + + public long getDen() { + return den; + } + + public static RationalLarge parse(String string) { + String[] split = splitS(string, ":"); + return split.length > 1 ? RationalLarge.R(Long.parseLong(split[0]), Long.parseLong(split[1])) : RationalLarge + .R(Long.parseLong(string), 1); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (int) (den ^ (den >>> 32)); + result = prime * result + (int) (num ^ (num >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + RationalLarge other = (RationalLarge) obj; + if (den != other.den) + return false; + if (num != other.num) + return false; + return true; + } + + public long multiplyS(long scalar) { + return (num * scalar) / den; + } + + public long divideS(long scalar) { + return (den * scalar) / num; + } + + public long divideByS(long scalar) { + return num / (den * scalar); + } + + public RationalLarge flip() { + return new RationalLarge(den, num); + } + + public static RationalLarge R(long num, long den) { + return new RationalLarge(num, den); + } + + public static RationalLarge R1(long num) { + return R(num, 1); + } + + public boolean lessThen(RationalLarge sec) { + return num * sec.den < sec.num * den; + } + + public boolean greaterThen(RationalLarge sec) { + return num * sec.den > sec.num * den; + } + + public boolean smallerOrEqualTo(RationalLarge sec) { + return num * sec.den <= sec.num * den; + } + + public boolean greaterOrEqualTo(RationalLarge sec) { + return num * sec.den >= sec.num * den; + } + + public boolean equalsLarge(RationalLarge other) { + return num * other.den == other.num * den; + } + + public RationalLarge plus(RationalLarge other) { + return reduceLong(num * other.den + other.num * den, den * other.den); + } + + public RationalLarge plusR(Rational other) { + return reduceLong(num * other.den + other.num * den, den * other.den); + } + + public RationalLarge minus(RationalLarge other) { + return reduceLong(num * other.den - other.num * den, den * other.den); + } + + public RationalLarge minusR(Rational other) { + return reduceLong(num * other.den - other.num * den, den * other.den); + } + + public RationalLarge plusLong(long scalar) { + return new RationalLarge(num + scalar * den, den); + } + + public RationalLarge minusLong(long scalar) { + return new RationalLarge(num - scalar * den, den); + } + + public RationalLarge multiplyLong(long scalar) { + return new RationalLarge(num * scalar, den); + } + + public RationalLarge divideLong(long scalar) { + return new RationalLarge(den * scalar, num); + } + + public RationalLarge divideByLong(long scalar) { + return new RationalLarge(num, den * scalar); + } + + public RationalLarge multiply(RationalLarge other) { + return reduceLong(num * other.num, den * other.den); + } + + public RationalLarge multiplyR(Rational other) { + return reduceLong(num * other.num, den * other.den); + } + + public RationalLarge divideRL(RationalLarge other) { + return reduceLong(other.num * den, other.den * num); + } + + public RationalLarge divideR(Rational other) { + return reduceLong(other.num * den, other.den * num); + } + + public RationalLarge divideBy(RationalLarge other) { + return reduceLong(num * other.den, den * other.num); + } + + public RationalLarge divideByR(Rational other) { + return reduceLong(num * other.den, den * other.num); + } + + public double scalar() { + return ((double) num) / den; + } + + public long scalarClip() { + return num / den; + } + + @Override + public String toString() { + return num + ":" + den; + } + + public static RationalLarge reduceLong(long num, long den) { + long gcd = MathUtil.gcdLong(num, den); + return new RationalLarge(num / gcd, den / gcd); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Rect.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Rect.java new file mode 100644 index 0000000..3eb59d4 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Rect.java @@ -0,0 +1,73 @@ +package org.monte.media.impl.jcodec.common.model; + +/** + * This class is part of JCodec ( www.jcodec.org ) + * This software is distributed under FreeBSD License + * + * @author The JCodec project + */ +public class Rect { + private int x; + private int y; + private int width; + private int height; + + public Rect(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + height; + result = prime * result + width; + result = prime * result + x; + result = prime * result + y; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Rect other = (Rect) obj; + if (height != other.height) + return false; + if (width != other.width) + return false; + if (x != other.x) + return false; + if (y != other.y) + return false; + return true; + } + + @Override + public String toString() { + return "Rect [x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + "]"; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Size.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Size.java new file mode 100644 index 0000000..361b37e --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/Size.java @@ -0,0 +1,56 @@ +package org.monte.media.impl.jcodec.common.model; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class Size { + private int width; + private int height; + + public Size(int width, int height) { + this.width = width; + this.height = height; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + height; + result = prime * result + width; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Size other = (Size) obj; + if (height != other.height) + return false; + if (width != other.width) + return false; + return true; + } + + @Override + public String toString() { + return "Size [width=" + width + ", height=" + height + "]"; + } + +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/TapeTimecode.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/TapeTimecode.java new file mode 100644 index 0000000..68d5e60 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/model/TapeTimecode.java @@ -0,0 +1,74 @@ +package org.monte.media.impl.jcodec.common.model; + +import static org.monte.media.impl.jcodec.common.StringUtils.zeroPad2; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Tape timecode + * + * @author The JCodec project + */ +public class TapeTimecode { + public static final TapeTimecode ZERO_TAPE_TIMECODE = new TapeTimecode((short) 0, (byte) 0, (byte) 0, (byte) 0, false, 0); + + private final short hour; + private final byte minute; + private final byte second; + private final byte frame; + private final boolean dropFrame; + private final int tapeFps; + + public TapeTimecode(short hour, byte minute, byte second, byte frame, boolean dropFrame, int tapeFps) { + this.hour = hour; + this.minute = minute; + this.second = second; + this.frame = frame; + this.dropFrame = dropFrame; + this.tapeFps = tapeFps; + } + + public short getHour() { + return hour; + } + + public byte getMinute() { + return minute; + } + + public byte getSecond() { + return second; + } + + public byte getFrame() { + return frame; + } + + public boolean isDropFrame() { + return dropFrame; + } + + public int getTapeFps() { + return tapeFps; + } + + @Override + public String toString() { + return zeroPad2(hour) + ":" + + zeroPad2(minute) + ":" + + zeroPad2(second) + (dropFrame ? ";" : ":") + + zeroPad2(frame); + } + + public static TapeTimecode tapeTimecode(long frame, boolean dropFrame, int tapeFps) { + if (dropFrame) { + long D = frame / 17982; + long M = frame % 17982; + frame += 18 * D + 2 * ((M - 2) / 1798); + } + long sec = frame / tapeFps; + return new TapeTimecode((short) (sec / 3600), (byte) ((sec / 60) % 60), (byte) (sec % 60), + (byte) (frame % tapeFps), dropFrame, tapeFps); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/tools/Debug.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/tools/Debug.java new file mode 100644 index 0000000..d4790a0 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/tools/Debug.java @@ -0,0 +1,80 @@ +package org.monte.media.impl.jcodec.common.tools; + +import org.monte.media.impl.jcodec.common.ArrayUtil; + +import java.nio.ShortBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class Debug { + public final static void print8x8i(int[] output) { + int i = 0; + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + System.out.printf("%3d, ", output[i]); + i++; + } + System.out.println(); + } + } + + public final static void print8x8s(short[] output) { + int i = 0; + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + System.out.printf("%3d, ", output[i]); + i++; + } + System.out.println(); + } + } + + public final static void print8x8sb(ShortBuffer output) { + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + System.out.printf("%3d, ", output.get()); + } + System.out.println(); + } + } + + public static void prints(short[] table) { + int i = 0; + for (int x = 0; x < 8; x++) { + for (int y = 0; y < 8; y++) { + System.out.printf("%3d, ", table[i]); + i++; + } + System.out.println(); + } + } + + public static void trace(Object... arguments) { + if (debug && arguments.length > 0) { + String format = (String) arguments[0]; + ArrayUtil.shiftLeft1(arguments); + System.out.printf(format + ": %d\n", arguments); + } + } + + public static boolean debug = false; + + public static void printInt(int i) { + if (debug) + System.out.print(i); + } + + public static void print(String string) { + if (debug) + System.out.print(string); + } + + public static void println(String string) { + if (debug) + System.out.println(string); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/tools/MainUtils.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/tools/MainUtils.java new file mode 100644 index 0000000..0f405bf --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/tools/MainUtils.java @@ -0,0 +1,498 @@ +package org.monte.media.impl.jcodec.common.tools; + +import org.monte.media.impl.jcodec.common.StringUtils; +import org.monte.media.impl.jcodec.common.io.IOUtils; +import org.monte.media.impl.jcodec.platform.Platform; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class MainUtils { + + private static final String KEY_GIT_REVISION = "git.commit.id.abbrev"; + private static final String JCODEC_LOG_SINK_COLOR = "jcodec.colorPrint"; + private static final String GIT_PROPERTIES = "git.properties"; + + public static boolean isColorSupported = System.console() != null + || Boolean.parseBoolean(System.getProperty(JCODEC_LOG_SINK_COLOR)); + + public static enum FlagType { + VOID, STRING, INT, LONG, DOUBLE, MULT, ENUM, ANY + } + + public static class Flag { + private String longName; + private String shortName; + private String description; + private FlagType type; + + public Flag(String longName, String shortName, String description, FlagType type) { + this.longName = longName; + this.shortName = shortName; + this.description = description; + this.type = type; + } + + public static Flag flag(String longName, String shortName, String description) { + return new Flag(longName, shortName, description, FlagType.ANY); + } + + public static Flag flagVoid(String longName, String shortName, String description) { + return new Flag(longName, shortName, description, FlagType.VOID); + } + + public String getLongName() { + return longName; + } + + public String getDescription() { + return description; + } + + public String getShortName() { + return shortName; + } + + public FlagType getType() { + return type; + } + } + + public static class Cmd { + public Map longFlags; + public Map shortFlags; + public String[] args; + private Map[] longArgFlags; + private Map[] shortArgFlags; + + public Cmd(Map longFlags, Map shortFlags, String[] args, + Map[] longArgFlags, Map[] shortArgFlags) { + this.args = args; + this.longFlags = longFlags; + this.shortFlags = shortFlags; + this.longArgFlags = longArgFlags; + this.shortArgFlags = shortArgFlags; + } + + private Long getLongFlagInternal(Map longFlags, Map shortFlags, Flag flag, + Long defaultValue) { + return longFlags.containsKey(flag.getLongName()) ? Long.valueOf(longFlags.get(flag.getLongName())) + : (shortFlags.containsKey(flag.getShortName()) ? Long.valueOf(shortFlags.get(flag.getShortName())) + : defaultValue); + } + + private Integer getIntegerFlagInternal(Map longFlags, Map shortFlags, Flag flag, + Integer defaultValue) { + return longFlags.containsKey(flag.getLongName()) ? Integer.valueOf(longFlags.get(flag.getLongName())) + : (shortFlags.containsKey(flag.getShortName()) ? Integer.valueOf(shortFlags.get(flag.getShortName())) + : defaultValue); + } + + private Boolean getBooleanFlagInternal(Map longFlags, Map shortFlags, Flag flag, + Boolean defaultValue) { + return longFlags.containsKey(flag.getLongName()) + ? !"false".equalsIgnoreCase(longFlags.get(flag.getLongName())) + : (shortFlags.containsKey(flag.getShortName()) + ? !"false".equalsIgnoreCase(shortFlags.get(flag.getShortName())) : defaultValue); + } + + private Double getDoubleFlagInternal(Map longFlags, Map shortFlags, Flag flag, + Double defaultValue) { + return longFlags.containsKey(flag.getLongName()) ? Double.valueOf(longFlags.get(flag.getLongName())) + : (shortFlags.containsKey(flag.getShortName()) ? Double.valueOf(shortFlags.get(flag.getShortName())) + : defaultValue); + } + + private String getStringFlagInternal(Map longFlags, Map shortFlags, Flag flag, + String defaultValue) { + return longFlags.containsKey(flag.getLongName()) ? longFlags.get(flag.getLongName()) + : (shortFlags.containsKey(flag.getShortName()) ? shortFlags.get(flag.getShortName()) + : defaultValue); + } + + private int[] getMultiIntegerFlagInternal(Map longFlags, Map shortFlags, + Flag flag, int[] defaultValue) { + String flagValue; + if (longFlags.containsKey(flag.getLongName())) + flagValue = longFlags.get(flag.getLongName()); + else if (shortFlags.containsKey(flag.getShortName())) + flagValue = shortFlags.get(flag.getShortName()); + else + return defaultValue; + String[] split = StringUtils.splitS(flagValue, ","); + int[] result = new int[split.length]; + for (int i = 0; i < split.length; i++) + result[i] = Integer.parseInt(split[i]); + return result; + } + + private double[] getMultiDoubleFlagInternal(Map longFlags, Map shortFlags, + Flag flag, double[] defaultValue) { + String flagValue; + if (longFlags.containsKey(flag.getLongName())) + flagValue = longFlags.get(flag.getLongName()); + else if (shortFlags.containsKey(flag.getShortName())) + flagValue = shortFlags.get(flag.getShortName()); + else + return defaultValue; + String[] split = StringUtils.splitS(flagValue, ","); + double[] result = new double[split.length]; + for (int i = 0; i < split.length; i++) + result[i] = Double.parseDouble(split[i]); + return result; + } + + private > T getEnumFlagInternal(Map longFlags, Map shortFlags, + Flag flag, T defaultValue, Class class1) { + String flagValue; + if (longFlags.containsKey(flag.getLongName())) + flagValue = longFlags.get(flag.getLongName()); + else if (shortFlags.containsKey(flag.getShortName())) + flagValue = shortFlags.get(flag.getShortName()); + else + return defaultValue; + + String strVal = flagValue.toLowerCase(); + EnumSet allOf = EnumSet.allOf(class1); + for (T val : allOf) { + if (val.name().toLowerCase().equals(strVal)) + return val; + } + return null; + } + + public boolean hasVoidFlag(Flag flag) { + return longFlags.containsKey(flag.getLongName()) || shortFlags.containsKey(flag.getShortName()); + } + + public Long getLongFlagD(Flag flagName, Long defaultValue) { + return this.getLongFlagInternal(longFlags, shortFlags, flagName, defaultValue); + } + + public Long getLongFlag(Flag flagName) { + return this.getLongFlagInternal(longFlags, shortFlags, flagName, null); + } + + public Long getLongFlagID(int arg, Flag flagName, Long defaultValue) { + return this.getLongFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, defaultValue); + } + + public Long getLongFlagI(int arg, Flag flagName) { + return this.getLongFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, null); + } + + public Integer getIntegerFlagD(Flag flagName, Integer defaultValue) { + return getIntegerFlagInternal(longFlags, shortFlags, flagName, defaultValue); + } + + public Integer getIntegerFlag(Flag flagName) { + return getIntegerFlagInternal(longFlags, shortFlags, flagName, null); + } + + public Integer getIntegerFlagID(int arg, Flag flagName, Integer defaultValue) { + return getIntegerFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, defaultValue); + } + + public Integer getIntegerFlagI(int arg, Flag flagName) { + return getIntegerFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, null); + } + + public Boolean getBooleanFlagD(Flag flagName, Boolean defaultValue) { + return getBooleanFlagInternal(longFlags, shortFlags, flagName, defaultValue); + } + + public Boolean getBooleanFlag(Flag flagName) { + return getBooleanFlagInternal(longFlags, shortFlags, flagName, false); + } + + public Boolean getBooleanFlagID(int arg, Flag flagName, Boolean defaultValue) { + return getBooleanFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, defaultValue); + } + + public Boolean getBooleanFlagI(int arg, Flag flagName) { + return getBooleanFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, false); + } + + public Double getDoubleFlagD(Flag flagName, Double defaultValue) { + return getDoubleFlagInternal(longFlags, shortFlags, flagName, defaultValue); + } + + public Double getDoubleFlag(Flag flagName) { + return getDoubleFlagInternal(longFlags, shortFlags, flagName, null); + } + + public Double getDoubleFlagID(int arg, Flag flagName, Double defaultValue) { + return getDoubleFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, defaultValue); + } + + public Double getDoubleFlagI(int arg, Flag flagName) { + return getDoubleFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, null); + } + + public String getStringFlagD(Flag flagName, String defaultValue) { + return getStringFlagInternal(longFlags, shortFlags, flagName, defaultValue); + } + + public String getStringFlag(Flag flagName) { + return getStringFlagInternal(longFlags, shortFlags, flagName, null); + } + + public String getStringFlagID(int arg, Flag flagName, String defaultValue) { + return getStringFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, defaultValue); + } + + public String getStringFlagI(int arg, Flag flagName) { + return getStringFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, null); + } + + public int[] getMultiIntegerFlagD(Flag flagName, int[] defaultValue) { + return getMultiIntegerFlagInternal(longFlags, shortFlags, flagName, defaultValue); + } + + public int[] getMultiIntegerFlag(Flag flagName) { + return getMultiIntegerFlagInternal(longFlags, shortFlags, flagName, new int[0]); + } + + public int[] getMultiIntegerFlagID(int arg, Flag flagName, int[] defaultValue) { + return getMultiIntegerFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, defaultValue); + } + + public int[] getMultiIntegerFlagI(int arg, Flag flagName) { + return getMultiIntegerFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, new int[0]); + } + + public double[] getMultiDoubleFlagD(Flag flagName, double[] defaultValue) { + return getMultiDoubleFlagInternal(longFlags, shortFlags, flagName, defaultValue); + } + + public double[] getMultiDoubleFlag(Flag flagName) { + return getMultiDoubleFlagInternal(longFlags, shortFlags, flagName, new double[0]); + } + + public double[] getMultiDoubleFlagID(int arg, Flag flagName, double[] defaultValue) { + return getMultiDoubleFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, defaultValue); + } + + public double[] getMultiDoubleFlagI(int arg, Flag flagName) { + return getMultiDoubleFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, new double[0]); + } + + public > T getEnumFlagD(Flag flagName, T defaultValue, Class class1) { + return getEnumFlagInternal(longFlags, shortFlags, flagName, defaultValue, class1); + } + + public > T getEnumFlag(Flag flagName, Class class1) { + return getEnumFlagInternal(longFlags, shortFlags, flagName, null, class1); + } + + public > T getEnumFlagID(int arg, Flag flagName, T defaultValue, Class class1) { + return getEnumFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, defaultValue, class1); + } + + public > T getEnumFlagI(int arg, Flag flagName, Class class1) { + return getEnumFlagInternal(longArgFlags[arg], shortArgFlags[arg], flagName, null, class1); + } + + public String getArg(int i) { + return i < args.length ? args[i] : null; + } + + public int argsLength() { + return args.length; + } + + public void popArg() { + args = Platform.copyOfRangeO(args, 1, args.length); + + } + } + + private static Pattern flagPattern = Pattern.compile("^--([^=]+)=(.*)$"); + + public static Cmd parseArguments(String[] args, Flag[] flags) { + Map longFlags = new HashMap(); + Map shortFlags = new HashMap(); + Map allLongFlags = new HashMap(); + Map allShortFlags = new HashMap(); + List outArgs = new ArrayList(); + List> argLongFlags = new ArrayList>(); + List> argShortFlags = new ArrayList>(); + int arg = 0; + for (; arg < args.length; arg++) { + if (args[arg].startsWith("--")) { + Matcher matcher = flagPattern.matcher(args[arg]); + if (matcher.matches()) { + longFlags.put(matcher.group(1), matcher.group(2)); + } else { + longFlags.put(args[arg].substring(2), "true"); + } + } else if (args[arg].startsWith("-")) { + String shortName = args[arg].substring(1); + boolean found = false; + for (Flag flag : flags) { + if (shortName.equals(flag.getShortName())) { + found = true; + if (flag.getType() != FlagType.VOID) + shortFlags.put(shortName, args[++arg]); + else + shortFlags.put(shortName, "true"); + } + } + if (!found) + ++arg; + } else { + allLongFlags.putAll(longFlags); + allShortFlags.putAll(shortFlags); + outArgs.add(args[arg]); + argLongFlags.add(longFlags); + argShortFlags.add(shortFlags); + longFlags = new HashMap(); + shortFlags = new HashMap(); + } + } + + return new Cmd(allLongFlags, allShortFlags, outArgs.toArray(new String[0]), + argLongFlags.toArray((Map[]) Array.newInstance(longFlags.getClass(), 0)), + argShortFlags.toArray((Map[]) Array.newInstance(shortFlags.getClass(), 0))); + } + + public static void printHelpArgs(Flag[] flags, String[] args) { + printHelpOut(System.out, "", flags, Arrays.asList(args)); + } + + public static void printHelp(Flag[] flags, List params) { + printHelpOut(System.out, "", flags, params); + } + + public static void printHelpNoFlags(String... arguments) { + printHelpOut(System.out, "", new Flag[]{}, Arrays.asList(arguments)); + } + + public static void printHelpCmdVa(String command, Flag[] flags, String args) { + printHelpOut(System.out, command, flags, Collections.singletonList(args)); + } + + public static void printHelpCmd(String command, Flag[] flags, List params) { + printHelpOut(System.out, command, flags, params); + } + + private static String getGitRevision() { + InputStream is = null; + try { + is = Thread.currentThread().getContextClassLoader().getResourceAsStream(GIT_PROPERTIES); + if (is == null) + return null; + Properties properties = new Properties(); + properties.load(is); + return (String) properties.get(KEY_GIT_REVISION); + } catch (IOException e) { + } finally { + IOUtils.closeQuietly(is); + } + return null; + } + + public static void printHelpOut(PrintStream out, String command, Flag[] flags, List params) { + String version = MainUtils.class.getPackage().getImplementationVersion(); + String gitRevision = getGitRevision(); + if (command == null || command.isEmpty()) + command = "jcodec"; + if (gitRevision != null || version != null) { + out.println(command + bold((version != null ? " v." + version : "") + + (gitRevision != null ? " rev. " + gitRevision : ""))); + out.println(); + } + out.print(bold("Syntax: " + command)); + StringBuilder sample = new StringBuilder(); + StringBuilder detail = new StringBuilder(); + for (Flag flag : flags) { + sample.append(" ["); + detail.append("\t"); + if (flag.getLongName() != null) { + sample.append(bold(color("--" + flag.getLongName() + (flag.type != FlagType.VOID ? "=" : ""), ANSIColor.MAGENTA))); + detail.append(bold(color("--" + flag.getLongName(), ANSIColor.MAGENTA))); + } + if (flag.getShortName() != null) { + if (flag.getLongName() != null) { + sample.append(" ("); + detail.append(" ("); + } + sample.append(bold(color("-" + flag.getShortName() + (flag.type != FlagType.VOID ? " " : ""), ANSIColor.MAGENTA))); + detail.append(bold(color("-" + flag.getShortName(), ANSIColor.MAGENTA))); + if (flag.getLongName() != null) { + sample.append(")"); + detail.append(")"); + } + } + sample.append("]"); + detail.append("\t\t" + flag.getDescription() + "\n"); + } + for (String param : params) { + if (param.charAt(0) != '?') + sample.append(bold(" <" + param + ">")); + else + sample.append(bold(" [" + param.substring(1) + "]")); + } + out.println(sample); + out.println(bold("Where:")); + out.println(detail); + } + + public enum ANSIColor { + BLACK, RED, GREEN, BROWN, BLUE, MAGENTA, CYAN, GREY + } + + public static String bold(String str) { + return isColorSupported ? "\033[1m" + str + "\033[0m" : str; + } + + public static String colorString(String str, String placeholder) { + return isColorSupported ? "\033[" + placeholder + "m" + str + "\033[0m" : str; + } + + public static String color(String str, ANSIColor fg) { + return isColorSupported ? "\033[" + (30 + (fg.ordinal() & 0x7)) + "m" + str + "\033[0m" : str; + } + + public static String colorBright(String str, ANSIColor fg, boolean bright) { + return isColorSupported ? "\033[" + (30 + (fg.ordinal() & 0x7)) + ";" + (bright ? 1 : 2) + "m" + str + "\033[0m" + : str; + } + + public static String color3(String str, ANSIColor fg, ANSIColor bg) { + return isColorSupported + ? "\033[" + (30 + (fg.ordinal() & 0x7)) + ";" + (40 + (bg.ordinal() & 0x7)) + ";1m" + str + "\033[0m" + : str; + } + + public static String color4(String str, ANSIColor fg, ANSIColor bg, boolean bright) { + return isColorSupported ? "\033[" + (30 + (fg.ordinal() & 0x7)) + ";" + (40 + (bg.ordinal() & 0x7)) + ";" + + (bright ? 1 : 2) + "m" + str + "\033[0m" : str; + } + + public static File tildeExpand(String path) { + if (path.startsWith("~")) { + path = path.replaceFirst("~", System.getProperty("user.home")); + } + return new File(path); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/tools/MathUtil.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/tools/MathUtil.java new file mode 100644 index 0000000..c0dfcd2 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/common/tools/MathUtil.java @@ -0,0 +1,151 @@ +package org.monte.media.impl.jcodec.common.tools; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class MathUtil { + + private static final int[] logTab = new int[]{0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}; + + private static final int[] reverseTab = new int[]{0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, + 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, + 0x38, 0xB8, 0x78, 0xF8, 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, + 0x74, 0xF4, 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, + 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, 0x0A, 0x8A, + 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, 0x06, 0x86, 0x46, 0xC6, + 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, + 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, + 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, + 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, + 0x35, 0xB5, 0x75, 0xF5, 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, + 0x7D, 0xFD, 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, + 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, 0x07, 0x87, + 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, 0x0F, 0x8F, 0x4F, 0xCF, + 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF}; + + public static int log2(int v) { + int n = 0; + if ((v & 0xffff0000) != 0) { + v >>= 16; + n += 16; + } + if ((v & 0xff00) != 0) { + v >>= 8; + n += 8; + } + n += logTab[v]; + + return n; + } + + public static int log2l(long v) { + int n = 0; + if ((v & 0xffffffff00000000L) != 0) { + v >>= 32; + n += 32; + } + if ((v & 0xffff0000L) != 0) { + v >>= 16; + n += 16; + } + if ((v & 0xff00L) != 0) { + v >>= 8; + n += 8; + } + n += logTab[(int) v]; + + return n; + } + + public static int log2Slow(int val) { + int i = 0; + while ((val & 0x80000000) == 0) { + val <<= 1; + i++; + } + return 31 - i; + } + + public static int gcd(int a, int b) { + if (b != 0) + return gcd(b, a % b); + else + return a; + } + + public static long gcdLong(long a, long b) { + if (b != 0) + return gcdLong(b, a % b); + else + return a; + } + + public static final int clip(int val, int from, int to) { + return val < from ? from : (val > to ? to : val); + } + + public static final int clipMax(int val, int max) { + return val < max ? val : max; + } + + public static int cubeRoot(int n) { + // TODO Auto-generated method stub + return 0; + } + + public static final int reverse(int b) { + return reverseTab[b & 0xff]; + } + + public static int nextPowerOfTwo(int n) { + n = n - 1; + n = n | (n >> 1); + n = n | (n >> 2); + n = n | (n >> 4); + n = n | (n >> 8); + n = n | (n >> 16); + n = n + 1; + return n; + } + + public static final int abs(int val) { + int sign = (val >> 31); + return (val ^ sign) - sign; + } + + public static final int golomb(int signedLevel) { + if (signedLevel == 0) + return 0; + return (abs(signedLevel) << 1) - (~signedLevel >>> 31); + } + + public static final int toSigned(int val, int sign) { + return (val ^ sign) - sign; + } + + public static final int sign(int val) { + return -(val >> 31); + } + + public static int wrap(int picNo, int maxFrames) { + return picNo < 0 ? picNo + maxFrames : (picNo >= maxFrames ? picNo - maxFrames : picNo); + } + + public static int max3(int a, int b, int c) { + return Math.max(Math.max(a, b), c); + } + + public static long max3L(long a, long b, long c) { + return Math.max(Math.max(a, b), c); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/IBoxFactory.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/IBoxFactory.java new file mode 100644 index 0000000..b408f5a --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/IBoxFactory.java @@ -0,0 +1,9 @@ +package org.monte.media.impl.jcodec.containers.mp4; + +import org.monte.media.impl.jcodec.containers.mp4.boxes.Box; +import org.monte.media.impl.jcodec.containers.mp4.boxes.Header; + +public interface IBoxFactory { + + Box newBox(Header header); +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/TimeUtil.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/TimeUtil.java new file mode 100644 index 0000000..ca946c9 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/TimeUtil.java @@ -0,0 +1,35 @@ +package org.monte.media.impl.jcodec.containers.mp4; + +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class TimeUtil { + + public final static long MOV_TIME_OFFSET; + + static { + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + calendar.set(1904, 0, 1, 0, 0, 0); + calendar.set(Calendar.MILLISECOND, 0); + MOV_TIME_OFFSET = calendar.getTimeInMillis(); + } + + public static Date macTimeToDate(int movSec) { + return new Date(fromMovTime(movSec)); + } + + public static long fromMovTime(int movSec) { + return ((long) movSec) * 1000L + MOV_TIME_OFFSET; + } + + public static int toMovTime(long millis) { + return (int) ((millis - MOV_TIME_OFFSET) / 1000L); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/Box.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/Box.java new file mode 100644 index 0000000..e3ef4d8 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/Box.java @@ -0,0 +1,144 @@ +package org.monte.media.impl.jcodec.containers.mp4.boxes; + +import org.monte.media.impl.jcodec.common.StringUtils; +import org.monte.media.impl.jcodec.common.UsedViaReflection; +import org.monte.media.impl.jcodec.common.io.NIOUtils; +import org.monte.media.impl.jcodec.containers.mp4.IBoxFactory; +import org.monte.media.impl.jcodec.platform.Platform; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.nio.ByteBuffer; + +import static org.monte.media.impl.jcodec.common.Preconditions.checkState; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * An MP4 file struncture (box). + * + * @author The JCodec project + */ +public abstract class Box { + public Header header; + public static final int MAX_BOX_SIZE = 128 * 1024 * 1024; + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public static @interface AtomField { + int idx(); + } + + @UsedViaReflection + public Box(Header header) { + this.header = header; + } + + public Header getHeader() { + return header; + } + + public abstract void parse(ByteBuffer buf); + + public void write(ByteBuffer buf) { + ByteBuffer dup = buf.duplicate(); + NIOUtils.skip(buf, 8); + doWrite(buf); + + header.setBodySize(buf.position() - dup.position() - 8); + checkState(header.headerSize() == (long) 8); + header.write(dup); + } + + protected abstract void doWrite(ByteBuffer out); + + public abstract int estimateSize(); + + public String getFourcc() { + return header.getFourcc(); + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + dump(sb); + return sb.toString(); + + } + + protected void dump(StringBuilder sb) { + sb.append("{\"tag\":\"" + header.getFourcc() + "\"}"); + } + + public static Box terminatorAtom() { + return createLeafBox(new Header(Platform.stringFromBytes(new byte[4])), ByteBuffer.allocate(0)); + } + + public static String[] path(String path) { + return StringUtils.splitC(path, '.'); + } + + public static LeafBox createLeafBox(Header atom, ByteBuffer data) { + LeafBox leaf = new LeafBox(atom); + leaf.data = data; + return leaf; + } + + public static Box parseBox(ByteBuffer input, Header childAtom, IBoxFactory factory) { + Box box = factory.newBox(childAtom); + + if (childAtom.getBodySize() < Box.MAX_BOX_SIZE) { + box.parse(input); + return box; + } else { + return new LeafBox(Header.createHeader("free", 8)); + } + } + + public static T asBox(Class class1, Box box) { + try { + T res = Platform.newInstance(class1, new Object[]{box.getHeader()}); + ByteBuffer buffer = ByteBuffer.allocate((int) box.getHeader().getBodySize()); + box.doWrite(buffer); + ((java.nio.Buffer) buffer).flip(); + res.parse(buffer); + return res; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static class LeafBox extends Box { + ByteBuffer data; + + public LeafBox(Header atom) { + super(atom); + } + + public LeafBox(Header atom, ByteBuffer data) { + super(atom); + this.data = data; + } + + public void parse(ByteBuffer input) { + data = NIOUtils.read(input, (int) header.getBodySize()); + } + + public ByteBuffer getData() { + return data.duplicate(); + } + + @Override + protected void doWrite(ByteBuffer out) { + NIOUtils.write(out, data); + } + + @Override + public int estimateSize() { + return data.remaining() + Header.estimateHeaderSize(data.remaining()); + } + } + +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/FullBox.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/FullBox.java new file mode 100644 index 0000000..f37b18d --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/FullBox.java @@ -0,0 +1,45 @@ +package org.monte.media.impl.jcodec.containers.mp4.boxes; + +import java.nio.ByteBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public abstract class FullBox extends Box { + + public FullBox(Header atom) { + super(atom); + } + + protected byte version; + protected int flags; + + public void parse(ByteBuffer input) { + int vf = input.getInt(); + version = (byte) ((vf >> 24) & 0xff); + flags = vf & 0xffffff; + } + + protected void doWrite(ByteBuffer out) { + out.putInt((version << 24) | (flags & 0xffffff)); + } + + public byte getVersion() { + return version; + } + + public int getFlags() { + return flags; + } + + public void setVersion(byte version) { + this.version = version; + } + + public void setFlags(int flags) { + this.flags = flags; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/Header.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/Header.java new file mode 100644 index 0000000..a358a1a --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/Header.java @@ -0,0 +1,159 @@ +package org.monte.media.impl.jcodec.containers.mp4.boxes; + +import org.monte.media.impl.jcodec.common.JCodecUtil2; +import org.monte.media.impl.jcodec.common.io.NIOUtils; +import org.monte.media.impl.jcodec.common.io.SeekableByteChannel; +import org.monte.media.impl.jcodec.common.io.StringReader; +import org.monte.media.impl.jcodec.common.logging.Logger; +import org.monte.media.impl.jcodec.platform.Platform; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * An MP4 file structure (atom) + * + * @author The JCodec project + */ +public class Header { + + public static final byte[] FOURCC_FREE = new byte[]{'f', 'r', 'e', 'e'}; + private static final long MAX_UNSIGNED_INT = 0x100000000L; + private String fourcc; + private long size; + private boolean lng; + + public Header(String fourcc) { + this.fourcc = fourcc; + } + + public static Header createHeader(String fourcc, long size) { + Header header = new Header(fourcc); + header.size = size; + return header; + } + + public static Header newHeader(String fourcc, long size, boolean lng) { + Header header = new Header(fourcc); + header.size = size; + header.lng = lng; + return header; + } + + public static Header read(ByteBuffer input) { + long size = 0; + while (input.remaining() >= 4 && (size = Platform.unsignedInt(input.getInt())) == 0) + ; + if (input.remaining() < 4 || size < 8 && size != 1) { + Logger.error("Broken atom of size " + size); + return null; + } + + String fourcc = NIOUtils.readString(input, 4); + boolean lng = false; + if (size == 1) { + if (input.remaining() >= 8) { + lng = true; + size = input.getLong(); + } else { + Logger.error("Broken atom of size " + size); + return null; + } + } + + return newHeader(fourcc, size, lng); + } + + + public void skip(InputStream di) throws IOException { + StringReader.sureSkip(di, size - headerSize()); + } + + public long headerSize() { + return lng || (size > MAX_UNSIGNED_INT) ? 16 : 8; + } + + public static int estimateHeaderSize(int size) { + return size + 8 > MAX_UNSIGNED_INT ? 16 : 8; + } + + public byte[] readContents(InputStream di) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + for (int i = 0; i < size - headerSize(); i++) { + baos.write(di.read()); + } + return baos.toByteArray(); + } + + public String getFourcc() { + return fourcc; + } + + public long getBodySize() { + return size - headerSize(); + } + + public void setBodySize(int length) { + size = length + headerSize(); + } + + public void write(ByteBuffer out) { + if (size > MAX_UNSIGNED_INT) + out.putInt(1); + else + out.putInt((int) size); + byte[] bt = JCodecUtil2.asciiString(fourcc); + if (bt != null && bt.length == 4) + out.put(bt); + else + out.put(FOURCC_FREE); + if (size > MAX_UNSIGNED_INT) { + out.putLong(size); + } + } + + public void writeChannel(SeekableByteChannel output) throws IOException { + ByteBuffer bb = ByteBuffer.allocate(16); + write(bb); + bb.flip(); + output.write(bb); + } + + public long getSize() { + return size; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((fourcc == null) ? 0 : fourcc.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Header other = (Header) obj; + if (fourcc == null) { + if (other.fourcc != null) + return false; + } else if (!fourcc.equals(other.fourcc)) + return false; + return true; + } + + public void setFourcc(String fourcc) { + this.fourcc = fourcc; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/MovieHeaderBox.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/MovieHeaderBox.java new file mode 100644 index 0000000..84787ca --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/MovieHeaderBox.java @@ -0,0 +1,173 @@ +package org.monte.media.impl.jcodec.containers.mp4.boxes; + +import org.monte.media.impl.jcodec.common.io.NIOUtils; + +import java.nio.ByteBuffer; + +import static org.monte.media.impl.jcodec.containers.mp4.TimeUtil.fromMovTime; +import static org.monte.media.impl.jcodec.containers.mp4.TimeUtil.toMovTime; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * A movie header box + * + * @author The JCodec project + */ +public class MovieHeaderBox extends FullBox { + private int timescale; + private long duration; + private float rate; + private float volume; + private long created; + private long modified; + private int[] matrix; + private int nextTrackId; + + public static String fourcc() { + return "mvhd"; + } + + public static MovieHeaderBox createMovieHeaderBox(int timescale, long duration, float rate, float volume, + long created, long modified, int[] matrix, int nextTrackId) { + MovieHeaderBox mvhd = new MovieHeaderBox(new Header(fourcc())); + mvhd.timescale = timescale; + mvhd.duration = duration; + mvhd.rate = rate; + mvhd.volume = volume; + mvhd.created = created; + mvhd.modified = modified; + mvhd.matrix = matrix; + mvhd.nextTrackId = nextTrackId; + return mvhd; + } + + public MovieHeaderBox(Header header) { + super(header); + } + + @AtomField(idx = 0) + public int getTimescale() { + return timescale; + } + + @AtomField(idx = 1) + public long getDuration() { + return duration; + } + + @AtomField(idx = 7) + public int getNextTrackId() { + return nextTrackId; + } + + @AtomField(idx = 2) + public float getRate() { + return rate; + } + + @AtomField(idx = 3) + public float getVolume() { + return volume; + } + + @AtomField(idx = 4) + public long getCreated() { + return created; + } + + @AtomField(idx = 5) + public long getModified() { + return modified; + } + + @AtomField(idx = 6) + public int[] getMatrix() { + return matrix; + } + + public void setTimescale(int newTs) { + this.timescale = newTs; + } + + public void setDuration(long duration) { + this.duration = duration; + } + + public void setNextTrackId(int nextTrackId) { + this.nextTrackId = nextTrackId; + } + + private int[] readMatrix(ByteBuffer input) { + int[] matrix = new int[9]; + for (int i = 0; i < 9; i++) + matrix[i] = input.getInt(); + return matrix; + } + + private float readVolume(ByteBuffer input) { + return (float) input.getShort() / 256f; + } + + private float readRate(ByteBuffer input) { + return (float) input.getInt() / 65536f; + } + + public void parse(ByteBuffer input) { + super.parse(input); + if (version == 0) { + created = fromMovTime(input.getInt()); + modified = fromMovTime(input.getInt()); + timescale = input.getInt(); + duration = input.getInt(); + } else if (version == 1) { + created = fromMovTime((int) input.getLong()); + modified = fromMovTime((int) input.getLong()); + timescale = input.getInt(); + duration = input.getLong(); + } else { + throw new RuntimeException("Unsupported version"); + } + rate = readRate(input); + volume = readVolume(input); + NIOUtils.skip(input, 10); + matrix = readMatrix(input); + NIOUtils.skip(input, 24); + nextTrackId = input.getInt(); + } + + public void doWrite(ByteBuffer out) { + super.doWrite(out); + out.putInt(toMovTime(created)); + out.putInt(toMovTime(modified)); + out.putInt(timescale); + out.putInt((int) duration); + writeFixed1616(out, rate); + writeFixed88(out, volume); + out.put(new byte[10]); + writeMatrix(out); + out.put(new byte[24]); + out.putInt(nextTrackId); + } + + @Override + public int estimateSize() { + return 144; + } + + private void writeMatrix(ByteBuffer out) { + for (int i = 0; i < Math.min(9, matrix.length); i++) + out.putInt(matrix[i]); + for (int i = Math.min(9, matrix.length); i < 9; i++) + out.putInt(0); + } + + private void writeFixed88(ByteBuffer out, float volume) { + out.putShort((short) (volume * 256.)); + } + + private void writeFixed1616(ByteBuffer out, float rate) { + out.putInt((int) (rate * 65536.)); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/NodeBox.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/NodeBox.java new file mode 100644 index 0000000..58fad9f --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/NodeBox.java @@ -0,0 +1,224 @@ +package org.monte.media.impl.jcodec.containers.mp4.boxes; + +import org.monte.media.impl.jcodec.common.io.NIOUtils; +import org.monte.media.impl.jcodec.common.logging.Logger; +import org.monte.media.impl.jcodec.containers.mp4.IBoxFactory; +import org.monte.media.impl.jcodec.platform.Platform; + +import java.lang.reflect.Array; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * A node box + *

+ * A box containing children, no data + * + * @author The JCodec project + */ +public class NodeBox extends Box { + protected List boxes; + protected IBoxFactory factory; + + public NodeBox(Header atom) { + super(atom); + this.boxes = new LinkedList(); + } + + public void setFactory(IBoxFactory factory) { + this.factory = factory; + } + + public void parse(ByteBuffer input) { + + while (input.remaining() >= 8) { + Box child = parseChildBox(input, factory); + if (child != null) + boxes.add(child); + } + } + + public static Box parseChildBox(ByteBuffer input, IBoxFactory factory) { + ByteBuffer fork = input.duplicate(); + while (input.remaining() >= 4 && fork.getInt() == 0) + input.getInt(); + if (input.remaining() < 4) + return null; + + Box ret = null; + Header childAtom = Header.read(input); + if (childAtom != null && input.remaining() >= childAtom.getBodySize()) { + ret = Box.parseBox(NIOUtils.read(input, (int) childAtom.getBodySize()), childAtom, factory); + } + return ret; + } + + public List getBoxes() { + return boxes; + } + + public void add(Box box) { + boxes.add(box); + } + + protected void doWrite(ByteBuffer out) { + for (Box box : boxes) { + box.write(out); + } + } + + @Override + public int estimateSize() { + int total = 0; + for (Box box : boxes) { + total += box.estimateSize(); + } + return total + Header.estimateHeaderSize(total); + } + + public void addFirst(MovieHeaderBox box) { + boxes.add(0, box); + } + + public void replace(String fourcc, Box box) { + removeChildren(new String[]{fourcc}); + add(box); + } + + public void replaceBox(Box box) { + removeChildren(new String[]{box.getFourcc()}); + add(box); + } + + public void replaceBoxWith(LeafBox was, Box now) { + for (ListIterator it = boxes.listIterator(); it.hasNext(); ) { + Box box = it.next(); + if (box == was) + it.set(now); + } + } + + protected void dump(StringBuilder sb) { + sb.append("{\"tag\":\"" + header.getFourcc() + "\","); + sb.append("\"boxes\": ["); + dumpBoxes(sb); + sb.append("]"); + sb.append("}"); + } + + protected void dumpBoxes(StringBuilder sb) { + for (int i = 0; i < boxes.size(); i++) { + boxes.get(i).dump(sb); + if (i < boxes.size() - 1) + sb.append(","); + } + } + + public void removeChildren(String[] fourcc) { + for (Iterator it = boxes.iterator(); it.hasNext(); ) { + Box box = it.next(); + String fcc = box.getFourcc(); + for (int i = 0; i < fourcc.length; i++) { + String cand = fourcc[i]; + if (cand.equals(fcc)) { + it.remove(); + break; + } + } + } + } + + public static Box doCloneBox(Box box, int approxSize, IBoxFactory bf) { + ByteBuffer buf = ByteBuffer.allocate(approxSize); + box.write(buf); + ((java.nio.Buffer) buf).flip(); + return parseChildBox(buf, bf); + } + + public static Box cloneBox(Box box, int approxSize, IBoxFactory bf) { + return NodeBox.doCloneBox(box, approxSize, bf); + } + + public static T[] findDeep(Box box, Class class1, String name) { + List storage = new ArrayList(); + findDeepInner(box, class1, name, storage); + return storage.toArray((T[]) Array.newInstance(class1, 0)); + } + + public static void findDeepInner(Box box, Class class1, String name, List storage) { + if (box == null) + return; + if (name.equals(box.getHeader().getFourcc())) { + storage.add((T) box); + return; + } + if (box instanceof NodeBox) { + NodeBox nb = (NodeBox) box; + for (Box candidate : nb.getBoxes()) { + findDeepInner(candidate, class1, name, storage); + } + } + } + + public static T[] findAll(Box box, Class class1, String path) { + return findAllPath(box, class1, new String[]{path}); + } + + public static T findFirst(NodeBox box, Class clazz, String path) { + return findFirstPath(box, clazz, new String[]{path}); + } + + public static T findFirstPath(NodeBox box, Class clazz, String[] path) { + T[] result = (T[]) findAllPath(box, clazz, path); + return result.length > 0 ? result[0] : null; + } + + public static T[] findAllPath(Box box, Class class1, String[] path) { + List result = new LinkedList(); + findBox(box, new ArrayList(Arrays.asList(path)), result); + + for (ListIterator it = result.listIterator(); it.hasNext(); ) { + Box next = it.next(); + if (next == null) { + it.remove(); + } else if (!Platform.isAssignableFrom(class1, next.getClass())) { + // Trying to reinterpret one box as the other + try { + it.set(Box.asBox(class1, next)); + } catch (Exception e) { + Logger.warn("Failed to reinterpret box: " + next.getFourcc() + " as: " + class1.getName() + "." + + e.getMessage()); + it.remove(); + } + } + } + return result.toArray((T[]) Array.newInstance(class1, 0)); + } + + public static void findBox(Box root, List path, Collection result) { + + if (path.size() > 0) { + String head = path.remove(0); + if (root instanceof NodeBox) { + NodeBox nb = (NodeBox) root; + for (Box candidate : nb.getBoxes()) { + if (head == null || head.equals(candidate.header.getFourcc())) { + findBox(candidate, path, result); + } + } + } + path.add(0, head); + } else { + result.add(root); + } + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/SampleEntry.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/SampleEntry.java new file mode 100644 index 0000000..25dbfaa --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/SampleEntry.java @@ -0,0 +1,57 @@ +package org.monte.media.impl.jcodec.containers.mp4.boxes; + +import java.nio.ByteBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Creates MP4 file out of a set of samples + * + * @author The JCodec project + */ +public class SampleEntry extends NodeBox { + + protected short drefInd; + + public SampleEntry(Header header) { + super(header); + } + + public void parse(ByteBuffer input) { + input.getInt(); + input.getShort(); + + drefInd = input.getShort(); + } + + protected void parseExtensions(ByteBuffer input) { + super.parse(input); + } + + protected void doWrite(ByteBuffer out) { + out.put(new byte[]{0, 0, 0, 0, 0, 0}); + out.putShort(drefInd); // data ref index + } + + protected void writeExtensions(ByteBuffer out) { + super.doWrite(out); + } + + public short getDrefInd() { + return drefInd; + } + + public void setDrefInd(short ind) { + this.drefInd = ind; + } + + public void setMediaType(String mediaType) { + header = new Header(mediaType); + } + + @Override + public int estimateSize() { + return 8 + super.estimateSize(); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/VideoSampleEntry.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/VideoSampleEntry.java new file mode 100644 index 0000000..edbcfad --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/containers/mp4/boxes/VideoSampleEntry.java @@ -0,0 +1,172 @@ +package org.monte.media.impl.jcodec.containers.mp4.boxes; + +import org.monte.media.impl.jcodec.common.JCodecUtil2; +import org.monte.media.impl.jcodec.common.io.NIOUtils; +import org.monte.media.impl.jcodec.common.model.Size; + +import java.nio.ByteBuffer; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Describes video payload sample + * + * @author The JCodec project + */ +public class VideoSampleEntry extends SampleEntry { + public static VideoSampleEntry videoSampleEntry(String fourcc, Size size, String encoderName) { + return createVideoSampleEntry(new Header(fourcc), (short) 0, (short) 0, "jcod", 0, 768, + (short) size.getWidth(), (short) size.getHeight(), 72, 72, (short) 1, + encoderName != null ? encoderName : "jcodec", (short) 24, (short) 1, (short) -1); + } + + public static VideoSampleEntry createVideoSampleEntry(Header atom, short version, short revision, String vendor, + int temporalQual, int spacialQual, short width, short height, long hRes, long vRes, short frameCount, + String compressorName, short depth, short drefInd, short clrTbl) { + VideoSampleEntry e = new VideoSampleEntry(atom); + e.drefInd = drefInd; + e.version = version; + e.revision = revision; + e.vendor = vendor; + e.temporalQual = temporalQual; + e.spacialQual = spacialQual; + e.width = width; + e.height = height; + e.hRes = hRes; + e.vRes = vRes; + e.frameCount = frameCount; + e.compressorName = compressorName; + e.depth = depth; + e.clrTbl = clrTbl; + return e; + } + + private short version; + private short revision; + private String vendor; + private int temporalQual; + private int spacialQual; + private short width; + private short height; + private float hRes; + private float vRes; + private short frameCount; + private String compressorName; + private short depth; + private short clrTbl; + + public VideoSampleEntry(Header atom) { + super(atom); + } + + public void parse(ByteBuffer input) { + super.parse(input); + + version = input.getShort(); + revision = input.getShort(); + vendor = NIOUtils.readString(input, 4); + temporalQual = input.getInt(); + spacialQual = input.getInt(); + + width = input.getShort(); + height = input.getShort(); + + hRes = (float) input.getInt() / 65536f; + vRes = (float) input.getInt() / 65536f; + + input.getInt(); // Reserved + + frameCount = input.getShort(); + + compressorName = NIOUtils.readPascalStringL(input, 31); + + depth = input.getShort(); + + clrTbl = input.getShort(); + + parseExtensions(input); + } + + @Override + public void doWrite(ByteBuffer out) { + + super.doWrite(out); + + out.putShort(version); + out.putShort(revision); + out.put(JCodecUtil2.asciiString(vendor), 0, 4); + out.putInt(temporalQual); + out.putInt(spacialQual); + + out.putShort((short) width); + out.putShort((short) height); + + out.putInt((int) (hRes * 65536)); + out.putInt((int) (vRes * 65536)); + + out.putInt(0); // data size + + out.putShort(frameCount); + + NIOUtils.writePascalStringL(out, compressorName, 31); + + out.putShort(depth); + + out.putShort(clrTbl); + + writeExtensions(out); + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public float gethRes() { + return hRes; + } + + public float getvRes() { + return vRes; + } + + public long getFrameCount() { + return frameCount; + } + + public String getCompressorName() { + return compressorName; + } + + public long getDepth() { + return depth; + } + + public String getVendor() { + return vendor; + } + + public short getVersion() { + return version; + } + + public short getRevision() { + return revision; + } + + public int getTemporalQual() { + return temporalQual; + } + + public int getSpacialQual() { + return spacialQual; + } + + public short getClrTbl() { + return clrTbl; + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/impl/AWTUtil.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/impl/AWTUtil.java new file mode 100644 index 0000000..c3e211a --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/impl/AWTUtil.java @@ -0,0 +1,109 @@ +/* + * @(#)AWTUtil.java + * Copyright © 2024 Werner Randelshofer, Switzerland. MIT License. + */ + +package org.monte.media.impl.jcodec.impl; + +import org.monte.media.impl.jcodec.common.model.ColorSpace; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.scale.ColorUtil; +import org.monte.media.impl.jcodec.scale.RgbToBgr; +import org.monte.media.impl.jcodec.scale.Transform; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; + +import static org.monte.media.impl.jcodec.common.model.ColorSpace.RGB; + +public class AWTUtil { + private static final int alphaR = 0xff; + private static final int alphaG = 0xff; + private static final int alphaB = 0xff; + + public static void toBufferedImage2(Picture src, BufferedImage dst) { + byte[] data = ((DataBufferByte) dst.getRaster().getDataBuffer()).getData(); + byte[] srcData = src.getPlaneData(0); + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (srcData[i] + 128); + } + } + + public static BufferedImage toBufferedImage(Picture src) { + if (src.getColor() != ColorSpace.BGR) { + Picture bgr = Picture.createCropped(src.getWidth(), src.getHeight(), ColorSpace.BGR, src.getCrop()); + if (src.getColor() == ColorSpace.RGB) { + new RgbToBgr().transform(src, bgr); + } else { + Transform transform = ColorUtil.getTransform(src.getColor(), ColorSpace.RGB); + transform.transform(src, bgr); + new RgbToBgr().transform(bgr, bgr); + } + src = bgr; + } + BufferedImage dst = new BufferedImage(src.getCroppedWidth(), src.getCroppedHeight(), + BufferedImage.TYPE_3BYTE_BGR); + + if (src.getCrop() == null) + toBufferedImage2(src, dst); + else + toBufferedImageCropped(src, dst); + + return dst; + } + + private static void toBufferedImageCropped(Picture src, BufferedImage dst) { + byte[] data = ((DataBufferByte) dst.getRaster().getDataBuffer()).getData(); + byte[] srcData = src.getPlaneData(0); + int dstStride = dst.getWidth() * 3; + int srcStride = src.getWidth() * 3; + for (int line = 0, srcOff = 0, dstOff = 0; line < dst.getHeight(); line++) { + for (int id = dstOff, is = srcOff; id < dstOff + dstStride; id += 3, is += 3) { + data[id] = (byte) (srcData[is] + 128); + data[id + 1] = (byte) (srcData[is + 1] + 128); + data[id + 2] = (byte) (srcData[is + 2] + 128); + } + srcOff += srcStride; + dstOff += dstStride; + } + } + + public static Picture convertColorSpace(Picture pic, ColorSpace tgtColor) { + Transform tr = ColorUtil.getTransform(pic.getColor(), tgtColor); + Picture res = Picture.create(pic.getWidth(), pic.getHeight(), tgtColor); + tr.transform(pic, res); + return res; + } + + public static Picture fromBufferedImage(BufferedImage src, ColorSpace tgtColor) { + return convertColorSpace(fromBufferedImageRGB(src), tgtColor); + } + + public static Picture fromBufferedImageRGB(BufferedImage src) { + Picture dst = Picture.create(src.getWidth(), src.getHeight(), RGB); + bufImgToPicture(src, dst); + return dst; + } + + public static void bufImgToPicture(BufferedImage src, Picture dst) { + byte[] dstData = dst.getPlaneData(0); + + int off = 0; + for (int i = 0; i < src.getHeight(); i++) { + for (int j = 0; j < src.getWidth(); j++) { + int rgb1 = src.getRGB(j, i); + int alpha = (rgb1 >> 24) & 0xff; + if (alpha == 0xff) { + dstData[off++] = (byte) (((rgb1 >> 16) & 0xff) - 128); + dstData[off++] = (byte) (((rgb1 >> 8) & 0xff) - 128); + dstData[off++] = (byte) ((rgb1 & 0xff) - 128); + } else { + int nalpha = 255 - alpha; + dstData[off++] = (byte) (((((rgb1 >> 16) & 0xff) * alpha + alphaR * nalpha) >> 8) - 128); + dstData[off++] = (byte) (((((rgb1 >> 8) & 0xff) * alpha + alphaG * nalpha) >> 8) - 128); + dstData[off++] = (byte) ((((rgb1 & 0xff) * alpha + alphaB * nalpha) >> 8) - 128); + } + } + } + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/platform/Platform.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/platform/Platform.java new file mode 100644 index 0000000..ff783ed --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/platform/Platform.java @@ -0,0 +1,183 @@ +package org.monte.media.impl.jcodec.platform; + +import java.io.File; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class Platform { + + public final static String UTF_8 = "UTF-8"; + public final static String UTF_16 = "UTF-16"; + public final static String UTF_16BE = "UTF-16BE"; + public final static String ISO8859_1 = "iso8859-1"; + + private final static Map boxed2primitive = new HashMap(); + + static { + boxed2primitive.put(Void.class, void.class); + boxed2primitive.put(Byte.class, byte.class); + boxed2primitive.put(Short.class, short.class); + boxed2primitive.put(Character.class, char.class); + boxed2primitive.put(Integer.class, int.class); + boxed2primitive.put(Long.class, long.class); + boxed2primitive.put(Float.class, float.class); + boxed2primitive.put(Double.class, double.class); + } + + public static T newInstance(Class clazz, Object[] params) { + try { + return clazz.getConstructor(classes(params)).newInstance(params); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static Class[] classes(Object[] params) { + Class[] classes = new Class[params.length]; + for (int i = 0; i < params.length; i++) { + Class cls = params[i].getClass(); + if (boxed2primitive.containsKey(cls)) { + classes[i] = boxed2primitive.get(cls); + } else { + classes[i] = cls; + } + } + return classes; + } + + public static Field[] getDeclaredFields(Class class1) { + return class1.getDeclaredFields(); + } + + public static Field[] getFields(Class class1) { + return class1.getFields(); + } + + public static String stringFromCharset(byte[] data, String charset) { + return new String(data, Charset.forName(charset)); + } + + public static byte[] getBytesForCharset(String url, String charset) { + return url.getBytes(Charset.forName(charset)); + } + + public static String stringFromCharset4(byte[] data, int offset, int len, String charset) { + return new String(data, offset, len, Charset.forName(charset)); + } + + public static boolean arrayEqualsInt(int[] a, int[] a2) { + return Arrays.equals(a, a2); + } + + public static boolean arrayEqualsByte(byte[] a, byte[] a2) { + return Arrays.equals(a, a2); + } + + public static boolean arrayEqualsObj(Object[] a, Object[] a2) { + return Arrays.equals(a, a2); + } + + public static T[] copyOfRangeO(T[] original, int from, int to) { + return Arrays.copyOfRange(original, from, to); + } + + public static long[] copyOfRangeL(long[] original, int from, int to) { + return Arrays.copyOfRange(original, from, to); + } + + public static int[] copyOfRangeI(int[] original, int from, int to) { + return Arrays.copyOfRange(original, from, to); + } + + public static byte[] copyOfRangeB(byte[] original, int from, int to) { + return Arrays.copyOfRange(original, from, to); + } + + public static T[] copyOfObj(T[] original, int newLength) { + return Arrays.copyOf(original, newLength); + } + + public static long[] copyOfLong(long[] original, int newLength) { + return Arrays.copyOf(original, newLength); + } + + public static int[] copyOfInt(int[] original, int newLength) { + return Arrays.copyOf(original, newLength); + } + + public static boolean[] copyOfBool(boolean[] original, int newLength) { + return Arrays.copyOf(original, newLength); + } + + public static byte[] copyOfByte(byte[] original, int newLength) { + return Arrays.copyOf(original, newLength); + } + + public static String arrayToString(Object[] a) { + return Arrays.toString(a); + } + + public static boolean deleteFile(File file) { + return file.delete(); + } + + public static byte[] getBytes(String fourcc) { + if (fourcc == null) return null; + try { + return fourcc.getBytes("iso8859-1"); + } catch (UnsupportedEncodingException e) { + return null; + } + } + + public static String stringFromBytes(byte[] bytes) { + try { + return new String(bytes, "iso8859-1"); + } catch (UnsupportedEncodingException e) { + return null; + } + } + + public static boolean isAssignableFrom(Class class1, Class class2) { + if (class1 == class2 || class1.equals(class2)) { + return true; + } + return class1.isAssignableFrom(class2); + } + + public static InputStream stdin() { + return System.in; + } + + + public static T invokeStaticMethod(Class cls, String methodName, Object[] params) { + try { + for (Method method : cls.getDeclaredMethods()) { + if (method.getName().equals(methodName)) { + return (T) method.invoke(null, params); + } + } + throw new NoSuchMethodException(cls + "." + methodName); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static long unsignedInt(int signed) { + return (long) signed & 0xffffffffL; + } + + public static String stringFromChars(char[] symb) { + return new String(symb); + } + + public static Class arrayComponentType(Object[] array) { + return array.getClass().getComponentType(); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/ColorUtil.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/ColorUtil.java new file mode 100644 index 0000000..1003055 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/ColorUtil.java @@ -0,0 +1,81 @@ +package org.monte.media.impl.jcodec.scale; + +import org.monte.media.impl.jcodec.common.model.ColorSpace; +import org.monte.media.impl.jcodec.common.model.Picture; + +import java.util.HashMap; +import java.util.Map; + +import static java.lang.System.arraycopy; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class ColorUtil { + + private static Map> map = new HashMap>(); + + static { + Map rgb = new HashMap(); + rgb.put(ColorSpace.RGB, new Idential()); + rgb.put(ColorSpace.YUV420J, new RgbToYuv420j()); + rgb.put(ColorSpace.YUV420, new RgbToYuv420p()); + rgb.put(ColorSpace.YUV422, new RgbToYuv422p()); + map.put(ColorSpace.RGB, rgb); + + Map yuv420 = new HashMap(); + yuv420.put(ColorSpace.YUV420, new Idential()); + yuv420.put(ColorSpace.YUV422, new Yuv420pToYuv422p()); + yuv420.put(ColorSpace.RGB, new Yuv420pToRgb()); + yuv420.put(ColorSpace.YUV420J, new Idential()); + map.put(ColorSpace.YUV420, yuv420); + + Map yuv422 = new HashMap(); + yuv422.put(ColorSpace.YUV422, new Idential()); + yuv422.put(ColorSpace.YUV420, new Yuv422pToYuv420p()); + yuv422.put(ColorSpace.YUV420J, new Yuv422pToYuv420p()); + yuv422.put(ColorSpace.RGB, new Yuv422pToRgb()); + map.put(ColorSpace.YUV422, yuv422); + + Map yuv444 = new HashMap(); + yuv444.put(ColorSpace.YUV444, new Idential()); + map.put(ColorSpace.YUV444, yuv444); + + Map yuv444j = new HashMap(); + yuv444j.put(ColorSpace.YUV444J, new Idential()); + yuv444j.put(ColorSpace.YUV420J, new Yuv444jToYuv420j()); + map.put(ColorSpace.YUV444J, yuv444j); + + Map yuv420j = new HashMap(); + yuv420j.put(ColorSpace.YUV420J, new Idential()); + yuv420j.put(ColorSpace.YUV422, new Yuv420pToYuv422p()); + yuv420j.put(ColorSpace.RGB, new Yuv420jToRgb()); + yuv420j.put(ColorSpace.YUV420, new Idential()); + map.put(ColorSpace.YUV420J, yuv420j); + } + + public static Transform getTransform(ColorSpace from, ColorSpace to) { + Map map2 = map.get(from); + + return map2 == null ? null : map2.get(to); + } + + public static class Idential implements Transform { + @Override + public void transform(Picture src, Picture dst) { + for (int i = 0; i < Math.min(src.getData().length, dst.getData().length); i++) + arraycopy(src.getPlaneData(i), 0, dst.getPlaneData(i), 0, + Math.min(src.getPlaneData(i).length, dst.getPlaneData(i).length)); + byte[][] srcLowBits = src.getLowBits(); + byte[][] dstLowBits = dst.getLowBits(); + if (srcLowBits != null && dstLowBits != null) { + for (int i = 0; i < Math.min(src.getData().length, dst.getData().length); i++) + arraycopy(srcLowBits[i], 0, dstLowBits[i], 0, + Math.min(src.getPlaneData(i).length, dst.getPlaneData(i).length)); + } + } + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToBgr.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToBgr.java new file mode 100644 index 0000000..bdec314 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToBgr.java @@ -0,0 +1,31 @@ +package org.monte.media.impl.jcodec.scale; + +import org.monte.media.impl.jcodec.common.model.ColorSpace; +import org.monte.media.impl.jcodec.common.model.Picture; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class RgbToBgr implements Transform { + + @Override + public void transform(Picture src, Picture dst) { + if (src.getColor() != ColorSpace.RGB && src.getColor() != ColorSpace.BGR + || dst.getColor() != ColorSpace.RGB && dst.getColor() != ColorSpace.BGR) { + throw new IllegalArgumentException( + "Expected RGB or BGR inputs, was: " + src.getColor() + ", " + dst.getColor()); + } + + byte[] dataSrc = src.getPlaneData(0); + byte[] dataDst = dst.getPlaneData(0); + for (int i = 0; i < dataSrc.length; i += 3) { + byte tmp = dataSrc[i + 2]; + dataDst[i + 2] = dataSrc[i]; + dataDst[i] = tmp; + dataDst[i + 1] = dataSrc[i + 1]; + } + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToYuv420j.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToYuv420j.java new file mode 100644 index 0000000..79cb70f --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToYuv420j.java @@ -0,0 +1,78 @@ +package org.monte.media.impl.jcodec.scale; + +import org.monte.media.impl.jcodec.common.model.Picture; + +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Transforms Picture in RGB colorspace ( one plane, 3 integers per pixel ) to + * Yuv420 colorspace output picture ( 3 planes, luma - 0th plane, cb - 1th + * plane, cr - 2nd plane; cb and cr planes are half width and half haight ) + *

+ * TODO: implement jpeg colorspace instead of NTSC + * + * @author The JCodec project + */ +public class RgbToYuv420j implements Transform { + + public RgbToYuv420j() { + } + + @Override + public void transform(Picture img, Picture dst) { + + byte[] y = img.getData()[0]; + byte[][] dstData = dst.getData(); + int[][] out = new int[4][3]; + + int offChr = 0, offLuma = 0, offSrc = 0, strideSrc = img.getWidth() * 3, strideDst = dst.getWidth(); + for (int i = 0; i < img.getHeight() >> 1; i++) { + for (int j = 0; j < img.getWidth() >> 1; j++) { + dstData[1][offChr] = 0; + dstData[2][offChr] = 0; + + rgb2yuv(y[offSrc], y[offSrc + 1], y[offSrc + 2], out[0]); + dstData[0][offLuma] = (byte) out[0][0]; + + rgb2yuv(y[offSrc + strideSrc], y[offSrc + strideSrc + 1], y[offSrc + strideSrc + 2], out[1]); + dstData[0][offLuma + strideDst] = (byte) out[1][0]; + + ++offLuma; + + rgb2yuv(y[offSrc + 3], y[offSrc + 4], y[offSrc + 5], out[2]); + dstData[0][offLuma] = (byte) out[2][0]; + + rgb2yuv(y[offSrc + strideSrc + 3], y[offSrc + strideSrc + 4], y[offSrc + strideSrc + 5], out[3]); + dstData[0][offLuma + strideDst] = (byte) out[3][0]; + ++offLuma; + + dstData[1][offChr] = (byte) ((out[0][1] + out[1][1] + out[2][1] + out[3][1] + 2) >> 2); + dstData[2][offChr] = (byte) ((out[0][2] + out[1][2] + out[2][2] + out[3][2] + 2) >> 2); + + ++offChr; + offSrc += 6; + } + offLuma += strideDst; + offSrc += strideSrc; + } + } + + public static final void rgb2yuv(byte r, byte g, byte b, int[] out) { + int rS = r + 128; + int gS = g + 128; + int bS = b + 128; + int y = 77 * rS + 150 * gS + 15 * bS; + int u = -43 * rS - 85 * gS + 128 * bS; + int v = 128 * rS - 107 * gS - 21 * bS; + y = (y + 128) >> 8; + u = (u + 128) >> 8; + v = (v + 128) >> 8; + + out[0] = clip(y - 128, -128, 127); + out[1] = clip(u, -128, 127); + out[2] = clip(v, -128, 127); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToYuv420p.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToYuv420p.java new file mode 100644 index 0000000..49afa97 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToYuv420p.java @@ -0,0 +1,78 @@ +package org.monte.media.impl.jcodec.scale; + +import org.monte.media.impl.jcodec.common.model.Picture; + +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + *

+ * Transforms Picture in RGB colorspace ( one plane, 3 integers per pixel ) to + * Yuv420 colorspace output picture ( 3 planes, luma - 0th plane, cb - 1th + * plane, cr - 2nd plane; cb and cr planes are half width and half haight ) + *

+ * TODO: implement jpeg colorspace instead of NTSC + * + * @author The JCodec project + */ +public class RgbToYuv420p implements Transform { + + public RgbToYuv420p() { + } + + @Override + public void transform(Picture img, Picture dst) { + + byte[] y = img.getData()[0]; + byte[][] dstData = dst.getData(); + byte[][] out = new byte[4][3]; + + int offChr = 0, offLuma = 0, offSrc = 0, strideSrc = img.getWidth() * 3, strideDst = dst.getWidth(); + for (int i = 0; i < img.getHeight() >> 1; i++) { + for (int j = 0; j < img.getWidth() >> 1; j++) { + dstData[1][offChr] = 0; + dstData[2][offChr] = 0; + + rgb2yuv(y[offSrc], y[offSrc + 1], y[offSrc + 2], out[0]); + dstData[0][offLuma] = out[0][0]; + + rgb2yuv(y[offSrc + strideSrc], y[offSrc + strideSrc + 1], y[offSrc + strideSrc + 2], out[1]); + dstData[0][offLuma + strideDst] = out[1][0]; + + ++offLuma; + + rgb2yuv(y[offSrc + 3], y[offSrc + 4], y[offSrc + 5], out[2]); + dstData[0][offLuma] = out[2][0]; + + rgb2yuv(y[offSrc + strideSrc + 3], y[offSrc + strideSrc + 4], y[offSrc + strideSrc + 5], out[3]); + dstData[0][offLuma + strideDst] = out[3][0]; + ++offLuma; + + dstData[1][offChr] = (byte) ((out[0][1] + out[1][1] + out[2][1] + out[3][1] + 2) >> 2); + dstData[2][offChr] = (byte) ((out[0][2] + out[1][2] + out[2][2] + out[3][2] + 2) >> 2); + + ++offChr; + offSrc += 6; + } + offLuma += strideDst; + offSrc += strideSrc; + } + } + + public static final void rgb2yuv(byte r, byte g, byte b, byte[] out) { + int rS = r + 128; + int gS = g + 128; + int bS = b + 128; + int y = 66 * rS + 129 * gS + 25 * bS; + int u = -38 * rS - 74 * gS + 112 * bS; + int v = 112 * rS - 94 * gS - 18 * bS; + y = (y + 128) >> 8; + u = (u + 128) >> 8; + v = (v + 128) >> 8; + + out[0] = (byte) clip(y - 112, -128, 127); + out[1] = (byte) clip(u, -128, 127); + out[2] = (byte) clip(v, -128, 127); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToYuv422p.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToYuv422p.java new file mode 100644 index 0000000..897b39a --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/RgbToYuv422p.java @@ -0,0 +1,38 @@ +package org.monte.media.impl.jcodec.scale; + +import org.monte.media.impl.jcodec.common.model.Picture; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class RgbToYuv422p implements Transform { + + @Override + public void transform(Picture img, Picture dst) { + + byte[] y = img.getData()[0]; + byte[] out1 = new byte[3]; + byte[] out2 = new byte[3]; + byte[][] dstData = dst.getData(); + + int off = 0, offSrc = 0; + for (int i = 0; i < img.getHeight(); i++) { + for (int j = 0; j < img.getWidth() >> 1; j++) { + int offY = off << 1; + + RgbToYuv420p.rgb2yuv(y[offSrc++], y[offSrc++], y[offSrc++], out1); + dstData[0][offY] = out1[0]; + + RgbToYuv420p.rgb2yuv(y[offSrc++], y[offSrc++], y[offSrc++], out2); + dstData[0][offY + 1] = out2[0]; + + dstData[1][off] = (byte) ((out1[1] + out2[1] + 1) >> 1); + dstData[2][off] = (byte) ((out1[2] + out2[2] + 1) >> 1); + ++off; + } + } + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Transform.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Transform.java new file mode 100644 index 0000000..5ee5884 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Transform.java @@ -0,0 +1,20 @@ +package org.monte.media.impl.jcodec.scale; + +import org.monte.media.impl.jcodec.common.model.Picture; + + +/** + * This class is part of JCodec ( www.jcodec.org ) + * This software is distributed under FreeBSD License + * + * @author The JCodec project + */ +public interface Transform { + public static enum Levels { + STUDIO, PC + } + + ; + + public void transform(Picture src, Picture dst); +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv420jToRgb.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv420jToRgb.java new file mode 100644 index 0000000..6a0553b --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv420jToRgb.java @@ -0,0 +1,92 @@ +package org.monte.media.impl.jcodec.scale; + +import org.monte.media.impl.jcodec.common.model.Picture; + +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class Yuv420jToRgb implements Transform { + + public Yuv420jToRgb() { + } + + public final void transform(Picture src, Picture dst) { + byte[] y = src.getPlaneData(0); + byte[] u = src.getPlaneData(1); + byte[] v = src.getPlaneData(2); + byte[] data = dst.getPlaneData(0); + + int offLuma = 0, offChroma = 0; + int stride = dst.getWidth(); + for (int i = 0; i < (dst.getHeight() >> 1); i++) { + for (int k = 0; k < (dst.getWidth() >> 1); k++) { + int j = k << 1; + YUVJtoRGB(y[offLuma + j], u[offChroma], v[offChroma], data, (offLuma + j) * 3); + YUVJtoRGB(y[offLuma + j + 1], u[offChroma], v[offChroma], data, (offLuma + j + 1) * 3); + + YUVJtoRGB(y[offLuma + j + stride], u[offChroma], v[offChroma], data, (offLuma + j + stride) * 3); + YUVJtoRGB(y[offLuma + j + stride + 1], u[offChroma], v[offChroma], data, + (offLuma + j + stride + 1) * 3); + + ++offChroma; + } + if ((dst.getWidth() & 0x1) != 0) { + int j = dst.getWidth() - 1; + + YUVJtoRGB(y[offLuma + j], u[offChroma], v[offChroma], data, (offLuma + j) * 3); + YUVJtoRGB(y[offLuma + j + stride], u[offChroma], v[offChroma], data, (offLuma + j + stride) * 3); + + ++offChroma; + } + + offLuma += 2 * stride; + } + if ((dst.getHeight() & 0x1) != 0) { + for (int k = 0; k < (dst.getWidth() >> 1); k++) { + int j = k << 1; + YUVJtoRGB(y[offLuma + j], u[offChroma], v[offChroma], data, (offLuma + j) * 3); + YUVJtoRGB(y[offLuma + j + 1], u[offChroma], v[offChroma], data, (offLuma + j + 1) * 3); + + ++offChroma; + } + if ((dst.getWidth() & 0x1) != 0) { + int j = dst.getWidth() - 1; + + YUVJtoRGB(y[offLuma + j], u[offChroma], v[offChroma], data, (offLuma + j) * 3); + + ++offChroma; + } + } + } + + private static final int SCALEBITS = 10; + private static final int ONE_HALF = (1 << (SCALEBITS - 1)); + + private final static int FIX(double x) { + return ((int) ((x) * (1 << SCALEBITS) + 0.5)); + } + + private static final int FIX_0_71414 = FIX(0.71414); + private static final int FIX_1_772 = FIX(1.77200); + private static final int _FIX_0_34414 = -FIX(0.34414); + private static final int FIX_1_402 = FIX(1.40200); + + public final static void YUVJtoRGB(byte y, byte cb, byte cr, byte[] data, int off) { + int y_ = (y + 128) << SCALEBITS; + int add_r = FIX_1_402 * cr + ONE_HALF; + int add_g = _FIX_0_34414 * cb - FIX_0_71414 * cr + ONE_HALF; + int add_b = FIX_1_772 * cb + ONE_HALF; + + int r = (y_ + add_r) >> SCALEBITS; + int g = (y_ + add_g) >> SCALEBITS; + int b = (y_ + add_b) >> SCALEBITS; + data[off] = (byte) clip(r - 128, -128, 127); + data[off + 1] = (byte) clip(g - 128, -128, 127); + data[off + 2] = (byte) clip(b - 128, -128, 127); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv420pToRgb.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv420pToRgb.java new file mode 100644 index 0000000..6214a7c --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv420pToRgb.java @@ -0,0 +1,131 @@ +package org.monte.media.impl.jcodec.scale; + +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.common.tools.MathUtil; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class Yuv420pToRgb implements Transform { + + public Yuv420pToRgb() { + } + + @Override + public final void transform(Picture src, Picture dst) { + byte[] yh = src.getPlaneData(0); + byte[] uh = src.getPlaneData(1); + byte[] vh = src.getPlaneData(2); + byte[] yl = null; + byte[] ul = null; + byte[] vl = null; + byte[][] low = src.getLowBits(); + + if (low != null) { + yl = low[0]; + ul = low[1]; + vl = low[2]; + } + byte[] data = dst.getPlaneData(0); + byte[] lowBits = dst.getLowBits() == null ? null : dst.getLowBits()[0]; + boolean hbd = src.isHiBD() && dst.isHiBD(); + int lowBitsNumSrc = src.getLowBitsNum(); + int lowBitsNumDst = dst.getLowBitsNum(); + + int offLuma = 0, offChroma = 0; + int stride = dst.getWidth(); + for (int i = 0; i < (dst.getHeight() >> 1); i++) { + for (int k = 0; k < (dst.getWidth() >> 1); k++) { + int j = k << 1; + if (hbd) { + YUV420pToRGBH2H(yh[offLuma + j], yl[offLuma + j], uh[offChroma], ul[offChroma], vh[offChroma], + vl[offChroma], lowBitsNumSrc, data, lowBits, lowBitsNumDst, (offLuma + j) * 3); + YUV420pToRGBH2H(yh[offLuma + j + 1], yl[offLuma + j + 1], uh[offChroma], ul[offChroma], + vh[offChroma], vl[offChroma], lowBitsNumSrc, data, lowBits, lowBitsNumDst, + (offLuma + j + 1) * 3); + YUV420pToRGBH2H(yh[offLuma + j + stride], yl[offLuma + j + stride], uh[offChroma], ul[offChroma], + vh[offChroma], vl[offChroma], lowBitsNumSrc, data, lowBits, lowBitsNumDst, + (offLuma + j + stride) * 3); + YUV420pToRGBH2H(yh[offLuma + j + stride + 1], yl[offLuma + j + stride + 1], uh[offChroma], + ul[offChroma], vh[offChroma], vl[offChroma], lowBitsNumSrc, data, lowBits, lowBitsNumDst, + (offLuma + j + stride + 1) * 3); + } else { + YUV420pToRGBN2N(yh[offLuma + j], uh[offChroma], vh[offChroma], data, (offLuma + j) * 3); + YUV420pToRGBN2N(yh[offLuma + j + 1], uh[offChroma], vh[offChroma], data, (offLuma + j + 1) * 3); + + YUV420pToRGBN2N(yh[offLuma + j + stride], uh[offChroma], vh[offChroma], data, + (offLuma + j + stride) * 3); + YUV420pToRGBN2N(yh[offLuma + j + stride + 1], uh[offChroma], vh[offChroma], data, (offLuma + j + + stride + 1) * 3); + } + + ++offChroma; + } + if ((dst.getWidth() & 0x1) != 0) { + int j = dst.getWidth() - 1; + + YUV420pToRGBN2N(yh[offLuma + j], uh[offChroma], vh[offChroma], data, (offLuma + j) * 3); + YUV420pToRGBN2N(yh[offLuma + j + stride], uh[offChroma], vh[offChroma], data, (offLuma + j + stride) * 3); + + ++offChroma; + } + + offLuma += 2 * stride; + } + if ((dst.getHeight() & 0x1) != 0) { + for (int k = 0; k < (dst.getWidth() >> 1); k++) { + int j = k << 1; + YUV420pToRGBN2N(yh[offLuma + j], uh[offChroma], vh[offChroma], data, (offLuma + j) * 3); + YUV420pToRGBN2N(yh[offLuma + j + 1], uh[offChroma], vh[offChroma], data, (offLuma + j + 1) * 3); + + ++offChroma; + } + if ((dst.getWidth() & 0x1) != 0) { + int j = dst.getWidth() - 1; + + YUV420pToRGBN2N(yh[offLuma + j], uh[offChroma], vh[offChroma], data, (offLuma + j) * 3); + + ++offChroma; + } + } + } + + public static void YUV420pToRGBN2N(byte y, byte u, byte v, byte[] data, int off) { + int c = y + 112; + int r = (298 * c + 409 * v + 128) >> 8; + int g = (298 * c - 100 * u - 208 * v + 128) >> 8; + int b = (298 * c + 516 * u + 128) >> 8; + data[off] = (byte) (MathUtil.clip(r, 0, 255) - 128); + data[off + 1] = (byte) (MathUtil.clip(g, 0, 255) - 128); + data[off + 2] = (byte) (MathUtil.clip(b, 0, 255) - 128); + } + + public static void YUV420pToRGBH2H(byte yh, byte yl, byte uh, byte ul, byte vh, byte vl, int nlbi, + byte[] data, byte[] lowBits, int nlbo, int off) { + int clipMax = ((1 << nlbo) << 8) - 1; + int round = (1 << nlbo) >> 1; + + int c = ((yh + 128) << nlbi) + yl - 64; + int d = ((uh + 128) << nlbi) + ul - 512; + int e = ((vh + 128) << nlbi) + vl - 512; + + int r = MathUtil.clip((298 * c + 409 * e + 128) >> 8, 0, clipMax); + int g = MathUtil.clip((298 * c - 100 * d - 208 * e + 128) >> 8, 0, clipMax); + int b = MathUtil.clip((298 * c + 516 * d + 128) >> 8, 0, clipMax); + + int valR = MathUtil.clip((r + round) >> nlbo, 0, 255); + data[off] = (byte) (valR - 128); + lowBits[off] = (byte) (r - (valR << nlbo)); + + int valG = MathUtil.clip((g + round) >> nlbo, 0, 255); + data[off + 1] = (byte) (valG - 128); + lowBits[off + 1] = (byte) (g - (valG << nlbo)); + + int valB = MathUtil.clip((b + round) >> nlbo, 0, 255); + data[off + 2] = (byte) (valB - 128); + lowBits[off + 2] = (byte) (b - (valB << nlbo)); + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv420pToYuv422p.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv420pToYuv422p.java new file mode 100644 index 0000000..cb74020 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv420pToYuv422p.java @@ -0,0 +1,72 @@ +package org.monte.media.impl.jcodec.scale; + +import org.monte.media.impl.jcodec.common.model.Picture; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class Yuv420pToYuv422p implements Transform { + + public Yuv420pToYuv422p() { + } + + @Override + public void transform(Picture src, Picture dst) { + copy(src.getPlaneData(0), dst.getPlaneData(0), src.getWidth(), dst.getWidth(), dst.getHeight()); + + _copy(src.getPlaneData(1), dst.getPlaneData(1), 0, 0, 1, 2, src.getWidth() >> 1, dst.getWidth() >> 1, + src.getHeight() >> 1, dst.getHeight()); + _copy(src.getPlaneData(1), dst.getPlaneData(1), 0, 1, 1, 2, src.getWidth() >> 1, dst.getWidth() >> 1, + src.getHeight() >> 1, dst.getHeight()); + _copy(src.getPlaneData(2), dst.getPlaneData(2), 0, 0, 1, 2, src.getWidth() >> 1, dst.getWidth() >> 1, + src.getHeight() >> 1, dst.getHeight()); + _copy(src.getPlaneData(2), dst.getPlaneData(2), 0, 1, 1, 2, src.getWidth() >> 1, dst.getWidth() >> 1, + src.getHeight() >> 1, dst.getHeight()); + } + + private static final void _copy(byte[] src, byte[] dest, int offX, int offY, int stepX, int stepY, int strideSrc, + int strideDest, int heightSrc, int heightDst) { + int offD = offX + offY * strideDest, srcOff = 0; + for (int i = 0; i < heightSrc; i++) { + for (int j = 0; j < strideSrc; j++) { + dest[offD] = src[srcOff++]; + offD += stepX; + } + int lastOff = offD - stepX; + for (int j = strideSrc * stepX; j < strideDest; j += stepX) { + dest[offD] = dest[lastOff]; + offD += stepX; + } + offD += (stepY - 1) * strideDest; + } + int lastLine = offD - stepY * strideDest; + for (int i = heightSrc * stepY; i < heightDst; i += stepY) { + for (int j = 0; j < strideDest; j += stepX) { + dest[offD] = dest[lastLine + j]; + offD += stepX; + } + offD += (stepY - 1) * strideDest; + } + } + + private static void copy(byte[] src, byte[] dest, int srcWidth, int dstWidth, int dstHeight) { + int height = src.length / srcWidth; + int dstOff = 0, srcOff = 0; + for (int i = 0; i < height; i++) { + for (int j = 0; j < srcWidth; j++) { + dest[dstOff++] = src[srcOff++]; + } + for (int j = srcWidth; j < dstWidth; j++) + dest[dstOff++] = dest[srcWidth - 1]; + } + int lastLine = (height - 1) * dstWidth; + for (int i = height; i < dstHeight; i++) { + for (int j = 0; j < dstWidth; j++) { + dest[dstOff++] = dest[lastLine + j]; + } + } + } +} \ No newline at end of file diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv422pToRgb.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv422pToRgb.java new file mode 100644 index 0000000..e2397d8 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv422pToRgb.java @@ -0,0 +1,47 @@ +package org.monte.media.impl.jcodec.scale; + +import org.monte.media.impl.jcodec.common.model.Picture; + +import static org.monte.media.impl.jcodec.common.tools.MathUtil.clip; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class Yuv422pToRgb implements Transform { + + @Override + public void transform(Picture src, Picture dst) { + byte[] y = src.getPlaneData(0); + byte[] u = src.getPlaneData(1); + byte[] v = src.getPlaneData(2); + + byte[] data = dst.getPlaneData(0); + + int offLuma = 0, offChroma = 0; + for (int i = 0; i < dst.getHeight(); i++) { + for (int j = 0; j < dst.getWidth(); j += 2) { + YUV444toRGB888(y[offLuma], u[offChroma], v[offChroma], data, offLuma * 3); + YUV444toRGB888(y[offLuma + 1], u[offChroma], v[offChroma], data, (offLuma + 1) * 3); + offLuma += 2; + ++offChroma; + } + } + + } + + public static final void YUV444toRGB888(final byte y, final byte u, final byte v, byte[] data, int off) { + final int c = y + 112; + final int d = u; + final int e = v; + + final int r = (298 * c + 409 * e + 128) >> 8; + final int g = (298 * c - 100 * d - 208 * e + 128) >> 8; + final int b = (298 * c + 516 * d + 128) >> 8; + data[off] = (byte) (clip(r, 0, 255) - 128); + data[off + 1] = (byte) (clip(g, 0, 255) - 128); + data[off + 2] = (byte) (clip(b, 0, 255) - 128); + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv422pToYuv420p.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv422pToYuv420p.java new file mode 100644 index 0000000..60462ac --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv422pToYuv420p.java @@ -0,0 +1,32 @@ +package org.monte.media.impl.jcodec.scale; + +import org.monte.media.impl.jcodec.common.model.Picture; + +import static java.lang.System.arraycopy; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author The JCodec project + */ +public class Yuv422pToYuv420p implements Transform { + + @Override + public void transform(Picture src, Picture dst) { + int lumaSize = src.getWidth() * src.getHeight(); + arraycopy(src.getPlaneData(0), 0, dst.getPlaneData(0), 0, lumaSize); + copyAvg(src.getPlaneData(1), dst.getPlaneData(1), src.getPlaneWidth(1), src.getPlaneHeight(1)); + copyAvg(src.getPlaneData(2), dst.getPlaneData(2), src.getPlaneWidth(2), src.getPlaneHeight(2)); + } + + private void copyAvg(byte[] src, byte[] dst, int width, int height) { + int offSrc = 0, offDst = 0; + for (int y = 0; y < height / 2; y++) { + for (int x = 0; x < width; x++, offDst++, offSrc++) { + dst[offDst] = (byte) ((src[offSrc] + src[offSrc + width] + 1) >> 1); + } + offSrc += width; + } + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv444jToYuv420j.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv444jToYuv420j.java new file mode 100644 index 0000000..d14ad3e --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/impl/jcodec/scale/Yuv444jToYuv420j.java @@ -0,0 +1,30 @@ +package org.monte.media.impl.jcodec.scale; + +import org.monte.media.impl.jcodec.common.model.Picture; + +/** + * This class is part of JCodec ( www.jcodec.org ) This software is distributed + * under FreeBSD License + * + * @author Stanislav Vitvitskyy + */ +public class Yuv444jToYuv420j implements Transform { + + @Override + public void transform(Picture src, Picture dst) { + int size = src.getWidth() * src.getHeight(); + System.arraycopy(src.getPlaneData(0), 0, dst.getPlaneData(0), 0, size); + + for (int plane = 1; plane < 3; plane++) { + byte[] srcPl = src.getPlaneData(plane); + byte[] dstPl = dst.getPlaneData(plane); + int srcStride = src.getPlaneWidth(plane); + for (int y = 0, srcOff = 0, dstOff = 0; y < src.getHeight(); y += 2, srcOff += srcStride) { + for (int x = 0; x < src.getWidth(); x += 2, srcOff += 2, dstOff++) { + dstPl[dstOff] = (byte) ((srcPl[srcOff] + srcPl[srcOff + 1] + srcPl[srcOff + srcStride] + + srcPl[srcOff + srcStride + 1] + 2) >> 2); + } + } + } + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/io/ByteArrayImageOutputStream.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/io/ByteArrayImageOutputStream.java index b2b0351..d525a47 100755 --- a/org.monte.media/src/main/java/org.monte.media/org/monte/media/io/ByteArrayImageOutputStream.java +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/io/ByteArrayImageOutputStream.java @@ -42,7 +42,7 @@ public class ByteArrayImageOutputStream extends ImageOutputStreamImpl { */ protected byte[] buf; /** - * The index one greater than the last valid character in the input + * The index one greater than the last valid byte in the input * stream buffer. * This value should always be nonnegative * and not larger than the length of buf. @@ -61,7 +61,7 @@ public ByteArrayImageOutputStream() { } public ByteArrayImageOutputStream(int initialCapacity) { - this(new byte[initialCapacity]); + this(new byte[initialCapacity], 0, 0, ByteOrder.BIG_ENDIAN); } public ByteArrayImageOutputStream(byte[] buf) { diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/io/ImageInputStreams.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/io/ImageInputStreams.java new file mode 100644 index 0000000..ef83b48 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/io/ImageInputStreams.java @@ -0,0 +1,26 @@ +/* + * @(#)ImageInputStreams.java + * Copyright © 2024 Werner Randelshofer, Switzerland. MIT License. + */ + +package org.monte.media.io; + +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; + +public class ImageInputStreams { + /** + * Don't let anyone instantiate this class. + */ + private ImageInputStreams() { + + } + + public static int read(ImageInputStream in, int[] b, int off, int len) throws IOException { + int nbytes = 0; + while (nbytes < len && in.getStreamPosition() < in.length() - 4) { + b[off + nbytes] = in.readInt(); + } + return nbytes; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/MP4Writer.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/MP4Writer.java index 37012b8..0dba78a 100755 --- a/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/MP4Writer.java +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/MP4Writer.java @@ -28,6 +28,7 @@ import static org.monte.media.av.BufferFlag.KEYFRAME; import static org.monte.media.av.FormatKeys.EncodingKey; import static org.monte.media.av.FormatKeys.FrameRateKey; +import static org.monte.media.av.FormatKeys.KeyFrameIntervalKey; import static org.monte.media.av.FormatKeys.MIME_JAVA; import static org.monte.media.av.FormatKeys.MIME_MP4; import static org.monte.media.av.FormatKeys.MIME_QUICKTIME; @@ -112,7 +113,7 @@ public int addTrack(Format fmt) throws IOException { fmt.get(CompressorNameKey, AbstractQTFFMovieStream.DEFAULT_COMPONENT_NAME), Math.min(6000, fmt.get(FrameRateKey).getNumerator() * fmt.get(FrameRateKey).getDenominator()), fmt.get(WidthKey), fmt.get(HeightKey), fmt.get(DepthKey, 24), - (int) fmt.get(FrameRateKey).getDenominator(), fmt); + fmt.get(KeyFrameIntervalKey, fmt.get(FrameRateKey).floor(1).intValue()), fmt); setCompressionQuality(t, fmt.get(QualityKey, 1.0f)); return t; } else if (fmt.get(MediaTypeKey) == MediaType.AUDIO) { diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/H264Codec.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/H264Codec.java new file mode 100644 index 0000000..068a515 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/H264Codec.java @@ -0,0 +1,206 @@ +/* + * @(#)H264Codec.java + * Copyright © 2024 Werner Randelshofer, Switzerland. MIT License. + */ + +package org.monte.media.mp4.codec.video; + +import org.monte.media.av.Buffer; +import org.monte.media.av.Format; +import org.monte.media.av.FormatKeys; +import org.monte.media.av.codec.video.AbstractVideoCodec; +import org.monte.media.impl.jcodec.api.transcode.PixelStore; +import org.monte.media.impl.jcodec.api.transcode.VideoFrameWithPacket; +import org.monte.media.impl.jcodec.codecs.h264.H264Encoder; +import org.monte.media.impl.jcodec.codecs.h264.H264Utils; +import org.monte.media.impl.jcodec.codecs.h264.io.model.SeqParameterSet; +import org.monte.media.impl.jcodec.common.VideoEncoder; +import org.monte.media.impl.jcodec.common.io.NIOUtils; +import org.monte.media.impl.jcodec.common.model.ColorSpace; +import org.monte.media.impl.jcodec.common.model.Packet; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.impl.AWTUtil; +import org.monte.media.qtff.AvcDecoderConfigurationRecord; +import org.monte.media.util.ArrayUtil; +import org.monte.media.util.ByteArray; + +import java.awt.image.BufferedImage; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.monte.media.av.BufferFlag.DISCARD; +import static org.monte.media.av.BufferFlag.KEYFRAME; +import static org.monte.media.av.FormatKeys.EncodingKey; +import static org.monte.media.av.FormatKeys.FrameRateKey; +import static org.monte.media.av.FormatKeys.KeyFrameIntervalKey; +import static org.monte.media.av.FormatKeys.MIME_AVI; +import static org.monte.media.av.FormatKeys.MIME_QUICKTIME; +import static org.monte.media.av.FormatKeys.MediaTypeKey; +import static org.monte.media.av.FormatKeys.MimeTypeKey; +import static org.monte.media.av.codec.video.VideoFormatKeys.DataClassKey; +import static org.monte.media.av.codec.video.VideoFormatKeys.DepthKey; +import static org.monte.media.av.codec.video.VideoFormatKeys.ENCODING_AVC1; +import static org.monte.media.av.codec.video.VideoFormatKeys.ENCODING_BUFFERED_IMAGE; +import static org.monte.media.av.codec.video.VideoFormatKeys.HeightKey; +import static org.monte.media.av.codec.video.VideoFormatKeys.MotionSearchRangeKey; +import static org.monte.media.av.codec.video.VideoFormatKeys.WidthKey; +import static org.monte.media.mp4.codec.video.JCodecPictureCodec.ENCODING_PICTURE; + +/** + * Codec for {@link Picture} to {@code H264} byte array. + */ +public class H264Codec extends AbstractVideoCodec { + private VideoEncoder videoEncoder = null; + private ByteBuffer byteBuffer; + + public H264Codec() { + super(new Format[]{ + new Format(MediaTypeKey, FormatKeys.MediaType.VIDEO, + EncodingKey, ENCODING_BUFFERED_IMAGE, + DataClassKey, BufferedImage.class), // + new Format(MediaTypeKey, FormatKeys.MediaType.VIDEO, + EncodingKey, ENCODING_PICTURE, + DataClassKey, Picture.class), // + }, + new Format[]{ + new Format(MediaTypeKey, FormatKeys.MediaType.VIDEO, + MimeTypeKey, MIME_QUICKTIME, + DepthKey, 24, + EncodingKey, ENCODING_AVC1, + DataClassKey, byte[].class), // + new Format(MediaTypeKey, FormatKeys.MediaType.VIDEO, + MimeTypeKey, MIME_AVI, + DepthKey, 24, + EncodingKey, ENCODING_AVC1, + DataClassKey, byte[].class), // + }// + ); + name = "JCodec H264 Codec"; + } + + @Override + public Format setOutputFormat(Format f) { + super.setOutputFormat(f); + // This codec can not scale an image. + // Enforce these properties + if (outputFormat != null) { + if (inputFormat != null) { + outputFormat = outputFormat.prepend(inputFormat.intersectKeys(WidthKey, HeightKey, DepthKey)); + } + // Suggest a keyframe rate and motion compensation + outputFormat = outputFormat.append(KeyFrameIntervalKey, 60, MotionSearchRangeKey, 16); + } + return this.outputFormat; + } + + @Override + public int process(Buffer in, Buffer out) { + if (ENCODING_PICTURE.equals(outputFormat.get(EncodingKey))) { + return decode(in, out); + } else { + return encode(in, out); + } + } + + public int decode(Buffer in, Buffer out) { + out.setMetaTo(in); + out.format = outputFormat; + if (in.isFlag(DISCARD)) { + return CODEC_OK; + } + + out.setException(new UnsupportedOperationException("decoding is not supported")); + out.setFlag(DISCARD); + return CODEC_FAILED; + + } + + public int encode(Buffer in, Buffer out) { + out.setMetaTo(in); + out.format = outputFormat; + if (in.isFlag(DISCARD)) { + return CODEC_OK; + } + Picture picture = getPicture(in); + if (picture == null) { + out.setFlag(DISCARD); + return CODEC_FAILED; + } + var enc = getEncoder(outputFormat); + + PixelStore.LoanerPicture toEncode = new PixelStore.LoanerPicture(picture, 0); + + Packet pkt = Packet.createPacket(null, 0, outputFormat.get(FrameRateKey).intValue(), + out.sampleDuration.divide(outputFormat.get(FrameRateKey)).intValue(), + out.sequenceNumber, + out.sequenceNumber % outputFormat.get(KeyFrameIntervalKey) == 0 ? Packet.FrameType.KEY : Packet.FrameType.INTER, + null); + VideoFrameWithPacket videoFrame = new VideoFrameWithPacket(pkt, toEncode); + Packet outputVideoPacket; + int bufferSize = enc.estimateBufferSize(picture); + if (byteBuffer == null || bufferSize < byteBuffer.capacity()) { + byteBuffer = ByteBuffer.allocate(bufferSize); + } + byteBuffer.clear(); + VideoEncoder.EncodedFrame encodedFrame = enc.encodeFrame(picture, byteBuffer); + outputVideoPacket = Packet.createPacketWithData(videoFrame.getPacket(), NIOUtils.clone(encodedFrame.getData())); + outputVideoPacket.setFrameType(encodedFrame.isKeyFrame() ? Packet.FrameType.KEY : Packet.FrameType.INTER); + + // compute header + out.header = null; + if (encodedFrame.isKeyFrame()) { + List spsList = new ArrayList<>(); + List ppsList = new ArrayList<>(); + H264Utils.wipePSinplace(outputVideoPacket.data, spsList, ppsList); + if (!spsList.isEmpty()) { + SeqParameterSet p = H264Utils.readSPS(spsList.get(0)); + Function byteBufferFunction = b -> new ByteArray(ArrayUtil.copyOf(b.array(), b.arrayOffset(), b.remaining())); + out.header = new AvcDecoderConfigurationRecord(p.profileIdc, 0, p.levelIdc, 4, + spsList.stream().map(byteBufferFunction).collect(Collectors.toCollection(LinkedHashSet::new)), + ppsList.stream().map(byteBufferFunction).collect(Collectors.toCollection(LinkedHashSet::new))); + } + } + + out.setFlag(KEYFRAME, encodedFrame.isKeyFrame()); + ByteBuffer packetBuf = outputVideoPacket.data; + if (out.data instanceof byte[] && ((byte[]) out.data).length >= packetBuf.remaining()) { + byte[] byteArray = (byte[]) out.data; + System.arraycopy(packetBuf.array(), packetBuf.position(), byteArray, 0, packetBuf.remaining()); + out.offset = 0; + out.length = packetBuf.remaining(); + } else { + out.data = packetBuf.array().clone(); + out.offset = packetBuf.position(); + out.length = packetBuf.remaining(); + } + + return CODEC_OK; + } + + private VideoEncoder getEncoder(Format outputFormat) { + if (videoEncoder == null) { + H264Encoder enc = H264Encoder.createH264Encoder(); + enc.setMotionSearchRange(outputFormat.get(MotionSearchRangeKey)); + enc.setKeyInterval(outputFormat.get(KeyFrameIntervalKey)); + videoEncoder = enc; + } + return videoEncoder; + } + + private Picture getPicture(Buffer buf) { + if (buf.data instanceof BufferedImage) { + BufferedImage img = (BufferedImage) buf.data; + return AWTUtil.fromBufferedImage(img, ColorSpace.YUV420J); + } else if (buf.data instanceof Picture) { + Picture picture = (Picture) buf.data; + return picture; + } + return null; + } + + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/H264CodecSpi.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/H264CodecSpi.java new file mode 100755 index 0000000..616f665 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/H264CodecSpi.java @@ -0,0 +1,22 @@ +/* + * @(#)JPEGCodecSpi.java + * Copyright © 2023 Werner Randelshofer, Switzerland. MIT License. + */ + +package org.monte.media.mp4.codec.video; + +import org.monte.media.av.CodecSpi; + +/** + * JPEGCodecSpi. + * + * @author Werner Randelshofer + */ +public class H264CodecSpi implements CodecSpi { + + @Override + public H264Codec create() { + return new H264Codec(); + } + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/JCodecPictureCodec.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/JCodecPictureCodec.java new file mode 100644 index 0000000..d574921 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/JCodecPictureCodec.java @@ -0,0 +1,58 @@ +/* + * @(#)JCodecPictureCodec.java + * Copyright © 2024 Werner Randelshofer, Switzerland. MIT License. + */ + +package org.monte.media.mp4.codec.video; + +import org.monte.media.av.AbstractCodec; +import org.monte.media.av.Buffer; +import org.monte.media.av.BufferFlag; +import org.monte.media.av.Format; +import org.monte.media.av.FormatKeys; +import org.monte.media.impl.jcodec.common.model.ColorSpace; +import org.monte.media.impl.jcodec.common.model.Picture; +import org.monte.media.impl.jcodec.impl.AWTUtil; + +import java.awt.image.BufferedImage; + +import static org.monte.media.av.FormatKeys.EncodingKey; +import static org.monte.media.av.FormatKeys.MediaTypeKey; +import static org.monte.media.av.codec.video.VideoFormatKeys.DataClassKey; +import static org.monte.media.av.codec.video.VideoFormatKeys.ENCODING_BUFFERED_IMAGE; + +/** + * Codec for {@link BufferedImage} to/from {@link Picture}. + */ +public class JCodecPictureCodec extends AbstractCodec { + public static final String ENCODING_PICTURE = "picture"; + + public JCodecPictureCodec() { + super(new Format[]{ + new Format(MediaTypeKey, FormatKeys.MediaType.VIDEO, + EncodingKey, ENCODING_BUFFERED_IMAGE, + DataClassKey, BufferedImage.class), // + }, + new Format[]{ + new Format(MediaTypeKey, FormatKeys.MediaType.VIDEO, + EncodingKey, ENCODING_PICTURE, + DataClassKey, Picture.class), // + }// + ); + name = "JCodec Picture Codec"; + } + + @Override + public int process(Buffer in, Buffer out) { + out.setMetaTo(in); + if (in.isFlag(BufferFlag.DISCARD)) { + return CODEC_OK; + } + if (in.data instanceof BufferedImage) { + BufferedImage img = (BufferedImage) in.data; + out.data = AWTUtil.fromBufferedImage(img, ColorSpace.YUV420J); + return CODEC_OK; + } + return CODEC_FAILED; + } +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/JCodecPictureCodecSpi.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/JCodecPictureCodecSpi.java new file mode 100755 index 0000000..27dd186 --- /dev/null +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/mp4/codec/video/JCodecPictureCodecSpi.java @@ -0,0 +1,22 @@ +/* + * @(#)JPEGCodecSpi.java + * Copyright © 2023 Werner Randelshofer, Switzerland. MIT License. + */ + +package org.monte.media.mp4.codec.video; + +import org.monte.media.av.CodecSpi; + +/** + * JPEGCodecSpi. + * + * @author Werner Randelshofer + */ +public class JCodecPictureCodecSpi implements CodecSpi { + + @Override + public JCodecPictureCodec create() { + return new JCodecPictureCodec(); + } + +} diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/quicktime/QuickTimeWriter.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/quicktime/QuickTimeWriter.java index 0159081..117e35a 100755 --- a/org.monte.media/src/main/java/org.monte.media/org/monte/media/quicktime/QuickTimeWriter.java +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/quicktime/QuickTimeWriter.java @@ -28,6 +28,7 @@ import static org.monte.media.av.BufferFlag.KEYFRAME; import static org.monte.media.av.FormatKeys.EncodingKey; import static org.monte.media.av.FormatKeys.FrameRateKey; +import static org.monte.media.av.FormatKeys.KeyFrameIntervalKey; import static org.monte.media.av.FormatKeys.MIME_JAVA; import static org.monte.media.av.FormatKeys.MIME_QUICKTIME; import static org.monte.media.av.FormatKeys.MediaTypeKey; @@ -229,7 +230,7 @@ public int addTrack(Format fmt) throws IOException { fmt.get(CompressorNameKey, AbstractQTFFMovieStream.DEFAULT_COMPONENT_NAME), Math.min(6000, fmt.get(FrameRateKey).getNumerator() * fmt.get(FrameRateKey).getDenominator()), fmt.get(WidthKey), fmt.get(HeightKey), fmt.get(DepthKey, 24), - (int) fmt.get(FrameRateKey).getDenominator(), fmt); + fmt.get(KeyFrameIntervalKey, fmt.get(FrameRateKey).floor(1).intValue()), fmt); setCompressionQuality(t, fmt.get(QualityKey, 1.0f)); return t; } else if (fmt.get(MediaTypeKey) == MediaType.AUDIO) { @@ -298,7 +299,8 @@ public int addVideoTrack(Format format, long timeScale, int width, int height) t * than 1. */ public int addVideoTrack(Format format, int width, int height, int depth, int syncInterval) throws IOException { - int tr = addVideoTrack(format.get(EncodingKey), format.get(CompressorNameKey), format.get(FrameRateKey).getDenominator() * format.get(FrameRateKey).getNumerator(), width, height, depth, syncInterval, format); + int tr = addVideoTrack(format.get(EncodingKey), format.get(CompressorNameKey), + format.get(FrameRateKey).getDenominator() * format.get(FrameRateKey).getNumerator(), width, height, depth, syncInterval, format); setVideoColorTable(tr, format.get(PaletteKey)); return tr; } diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/quicktime/codec/video/AnimationCodec.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/quicktime/codec/video/AnimationCodec.java index d9d4242..1c09988 100755 --- a/org.monte.media/src/main/java/org.monte.media/org/monte/media/quicktime/codec/video/AnimationCodec.java +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/quicktime/codec/video/AnimationCodec.java @@ -200,6 +200,7 @@ public int process(Buffer in, Buffer out) { } out.format = outputFormat; ByteArrayImageOutputStream tmp = new ByteArrayImageOutputStream(ArrayUtil.reuseByteArray(out.data, 32)); + tmp.clear(); Format vf = outputFormat; diff --git a/org.monte.media/src/main/java/org.monte.media/org/monte/media/quicktime/codec/video/RawCodec.java b/org.monte.media/src/main/java/org.monte.media/org/monte/media/quicktime/codec/video/RawCodec.java index f73b93e..6d2f51b 100755 --- a/org.monte.media/src/main/java/org.monte.media/org/monte/media/quicktime/codec/video/RawCodec.java +++ b/org.monte.media/src/main/java/org.monte.media/org/monte/media/quicktime/codec/video/RawCodec.java @@ -218,6 +218,8 @@ public int process(Buffer in, Buffer out) { out.format = outputFormat; ByteArrayImageOutputStream tmp = new ByteArrayImageOutputStream(ArrayUtil.reuseByteArray(out.data, 32)); + tmp.clear(); + Format vf = outputFormat; // Handle sub-image diff --git a/org.monte.media/src/test/java/org.monte.media/org/monte/media/av/codec/video/RgbImageBuilder.java b/org.monte.media/src/test/java/org.monte.media/org/monte/media/av/codec/video/RgbImageBuilder.java new file mode 100644 index 0000000..e9b76a5 --- /dev/null +++ b/org.monte.media/src/test/java/org.monte.media/org/monte/media/av/codec/video/RgbImageBuilder.java @@ -0,0 +1,49 @@ +/* + * @(#)RgbImageBuilder.java + * Copyright © 2024 Werner Randelshofer, Switzerland. MIT License. + */ + +package org.monte.media.av.codec.video; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.util.Arrays; + +/** + * Helper for creating test images. + */ +public class RgbImageBuilder { + private int width; + private int height; + public final static int WHITE = 0xffffff; + public final static int BLACK = 0x000000; + public final static int RED = 0xff0000; + public final static int GREEN = 0x00ff00; + public final static int BLUE = 0x0000ff; + + public RgbImageBuilder(int width, int height) { + this.height = height; + this.width = width; + } + + public BufferedImage createBlack() { + return new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + } + + public BufferedImage createWhite() { + BufferedImage img = createBlack(); + return fillRectangle(img, 0, 0, img.getWidth(), img.getHeight(), WHITE); + } + + public BufferedImage fillRectangle(BufferedImage img, int x, int y, int w, int h, int color) { + int imgW = img.getWidth(); + DataBufferInt buf = (DataBufferInt) img.getRaster().getDataBuffer(); + int[] data = buf.getData(); + for (int i = 0; i < h; i++) { + int lineIndex = (y + i) * imgW; + Arrays.fill(data, lineIndex + x, lineIndex + x + w, color); + + } + return img; + } +} diff --git a/org.monte.media/src/test/java/org.monte.media/org/monte/media/av/codec/video/TechSmithCodecTest.java b/org.monte.media/src/test/java/org.monte.media/org/monte/media/av/codec/video/TechSmithCodecTest.java index 0777a43..822bb4a 100644 --- a/org.monte.media/src/test/java/org.monte.media/org/monte/media/av/codec/video/TechSmithCodecTest.java +++ b/org.monte.media/src/test/java/org.monte.media/org/monte/media/av/codec/video/TechSmithCodecTest.java @@ -9,13 +9,21 @@ import org.monte.media.av.Buffer; import org.monte.media.av.Format; import org.monte.media.io.ByteArrayImageOutputStream; +import org.monte.media.io.UncachedImageInputStream; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics2D; import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; import java.awt.image.IndexColorModel; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.zip.InflaterInputStream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; @@ -69,6 +77,110 @@ public void shouldEncodeDecode8BitKeyFrame() throws IOException { assertArrayEquals(rgb24, actualPixels); } + private static final byte ESCAPE_OP = (byte) 0x00; + private static final byte PADDING_OP = (byte) 0x00; + private static final byte END_OF_LINE_OP = (byte) 0x00; + private static final byte END_OF_BITMAP_OP = (byte) 0x01; + private static final byte SKIP_OP = (byte) 0x02; + + private static byte repeatOp(int times) { + return (byte) times; + } + + private static byte horizontalOffsetOp(int times) { + return (byte) times; + } + + private static byte verticalOffsetOp(int times) { + return (byte) times; + } + + private static byte endOfLineOp() { + return END_OF_LINE_OP; + } + + private static byte endOFileOp() { + return END_OF_BITMAP_OP; + } + + private static byte skipOp() { + return SKIP_OP; + } + + private static byte escapeOp() { + return ESCAPE_OP; + } + + @Test + public void shouldEncode24BitKeyFrameAndDeltas() throws IOException { + List images = new ArrayList<>(); + int width = 8; + int height = 5; + RgbImageBuilder b = new RgbImageBuilder(width, height); + images.add(b.createBlack()); + images.add(b.createBlack()); + images.add(b.fillRectangle(b.createBlack(), 1, 2, 6, 1, RgbImageBuilder.WHITE)); + images.add(b.fillRectangle(b.createBlack(), 1, 1, 6, 2, RgbImageBuilder.WHITE)); + + List expected = new ArrayList<>(); + expected.add(new byte[]{ + repeatOp(8), 0, 0, 0, escapeOp(), endOfLineOp(), + repeatOp(8), 0, 0, 0, escapeOp(), endOfLineOp(), + repeatOp(8), 0, 0, 0, escapeOp(), endOfLineOp(), + repeatOp(8), 0, 0, 0, escapeOp(), endOfLineOp(), + repeatOp(8), 0, 0, 0, escapeOp(), endOfLineOp(), + escapeOp(), endOFileOp() + }); + expected.add(new byte[]{ + escapeOp(), endOFileOp() + }); + expected.add(new byte[]{ + escapeOp(), skipOp(), horizontalOffsetOp(1), verticalOffsetOp(2), + repeatOp(6), -1, -1, -1, escapeOp(), endOfLineOp(), + escapeOp(), endOFileOp() + }); + expected.add(new byte[]{ + escapeOp(), skipOp(), horizontalOffsetOp(1), verticalOffsetOp(3), + repeatOp(6), -1, -1, -1, escapeOp(), endOfLineOp(), + escapeOp(), endOFileOp() + }); + + + TechSmithCodecCore codec = new TechSmithCodecCore(); + ByteArrayImageOutputStream encoded = new ByteArrayImageOutputStream(); + int[] prev = null; + int frame = 0; + for (BufferedImage img : images) { + encoded.clear(); + int[] current = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); + if (prev == null) { + codec.encodeKey24(encoded, current, width, height, 0, width); + } else { + codec.encodeDelta24(encoded, current, prev, width, height, 0, width); + } + var in = inflate(encoded); + byte[] actual = new byte[width * height * 4]; + int off = 0; + int len = 0; + while (true) { + int readLen = in.read(actual, off, actual.length - off); + if (readLen <= 0) break; + off += readLen; + } + actual = Arrays.copyOf(actual, off); + assertArrayEquals(expected.get(frame), actual, "frame " + frame); + frame++; + prev = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); + } + } + + private static UncachedImageInputStream inflate(ByteArrayImageOutputStream encoded) { + if (encoded.size() == 2) { + return new UncachedImageInputStream(new ByteArrayInputStream(encoded.getBuffer(), 0, encoded.size()), ByteOrder.LITTLE_ENDIAN); + } + return new UncachedImageInputStream(new InflaterInputStream(new ByteArrayInputStream(encoded.getBuffer(), 0, encoded.size())), ByteOrder.LITTLE_ENDIAN); + } + /** * Creates a video frame at the specified time. *