From patchwork Mon Oct 21 19:57:18 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: martin schitter X-Patchwork-Id: 52439 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a05:612c:143:b0:48e:c0f8:d0de with SMTP id h3csp2948476vqi; Mon, 21 Oct 2024 14:14:08 -0700 (PDT) X-Forwarded-Encrypted: i=2; AJvYcCWzxBmxWcmYOj3pz88Gf3PhgrM+Smo17h9+uUuAp3b/BDwwfjxqjOVN2joOuVIQbHCq7gppTt0xaTspjfXnYyy5@gmail.com X-Google-Smtp-Source: AGHT+IFlZ+4pnzlL3gj4q+nzqKlFa13I0TETsJuvK0YyN03i0SdYFRljoZjnrJ8WLeFRIMhbugSn X-Received: by 2002:a17:907:7d90:b0:a9a:2a56:91f with SMTP id a640c23a62f3a-a9a699699d7mr1254695766b.2.1729545247667; Mon, 21 Oct 2024 14:14:07 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1729545247; cv=none; d=google.com; s=arc-20240605; b=TBchtePDOikiN6P2xUU5IQxqE+lqIqqOdAKOHLK7Y8MlRGQfbCM15vNuZeL/ZZjQoE CofEbLbuhgRfmeRwKQeEQZmmGc0kv5/UJf9XKDRrIjzSbkWWzrTVZ4cRbS1BKEMBN5fH /xQ4K+OXe5kXHtLyqhMiGDmj9yj9UZMPLVa6D+xraEsBHxaPDIyidv/ihs3cPCOjj/ov 5Q4vJPRPH9/IA9AkgWT4m3Hq92AZksSWkgpZGdDEnMa0bQt9brTO2hows74z5k+Tq4IE UW771GXZC+KXSqGtwa+qazzvTm4RzWFr3/kM06xGoPPb1d625e2nkeT3j3awm0+VSE67 cIPA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=sender:errors-to:content-transfer-encoding:cc:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:mime-version:references:in-reply-to :message-id:date:to:from:dkim-signature:delivered-to; bh=RUtuCdykUUge41Fv7ukgJLVO85pB7QybUD/me9smhOM=; fh=igg3JDwkSkAbPTwY6V0OsEq0RUkG4DKWC8fVJg4GhMY=; b=F5tDKYUdWZS7+3rOVvz/AZpDxARmi+CrYQquUM13Xs7hkBV5y3urUvhAw+3OrsXd/o B2/lWcMe7UfIL4HyXDtdhczBrMvZzjg7U6Ra7S6U7YGMtg+YnB2AB9PMX30sgamnvmlv CBd4b7Jq2L85qxXZPZ1i+rE8UEbwmsl+mK9xLtzFyuqEGIF63mkHFCFaRbBNc036cMJL YlQ80rDXlz4ZIQLHCoJaYpQSagN74n8ogGbhTIQuJXVDjqOfyCCz23+6hDK3Q9e/euGv HnxTa6rM0ZxJV+D8SeLsdqzaJOAnzTGPFsUgTLzj7CMWRw0aaR0pwK359MwydPE19wPC K0ZA==; dara=google.com ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@mur.at header.s=dkim2 header.b=ttHMG3wY; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=mur.at Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id a640c23a62f3a-a9a91559db9si296146166b.827.2024.10.21.14.14.07; Mon, 21 Oct 2024 14:14:07 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@mur.at header.s=dkim2 header.b=ttHMG3wY; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=mur.at Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id BF7B268DE1C; Mon, 21 Oct 2024 23:18:16 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from efeu.mur.at (efeu.mur.at [89.106.208.42]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 69E0A68DCE4 for ; Mon, 21 Oct 2024 23:18:06 +0300 (EEST) Received: from localhost.localdomain (lan1.raspi.ma39.ffgraz.net [10.12.1.243]) by efeu.mur.at (Postfix) with ESMTPSA id E709B462E3; Mon, 21 Oct 2024 22:18:00 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=mur.at; s=dkim2; t=1729541880; bh=J3nYZbSWszYztQzE+LrSlhtsLH29KpkP8hCHu4znQNA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ttHMG3wYo4eUJHlaynYgWeEFACtvWOLDX9qNfEoPEl49e2CkTP9RpWAPCz8xrFDJU xf63FdJ/OIUruhxeayzoXFeaVNugPOkdKKCmHuJfzqHkZXa6wJUgUPGs9Q+CX1BLEn vfHWFZ7JtpNYsuh7EOliftsf3ZyICey5fqXQer96n8eXLsx2JMYJCMWgEfBNwZvJs5 D4Ydw8oZib+6CKavTsvMAjludFa+S/1UwKPrO8hnwqYvrCgcFZToCAGXEqfFWoUfZ6 OfJQATbNmZm6nwYwGME/oQ85+boRRjFkVnlZZJQ3IpCyEwWE6G3tboclO+l23TiWj7 TykZUZ04CfYNN+e3+k+4Oi/NFISAZbEOtwsEZCE0wIgYwwK+V3ohEpaFMRM+37wJND xhMvu97y/ge9DthEOWFoeQkgpAwNvWOdr3FvAYnQ5+wYb5AEYc0fyXw3XjM7MmiVtu CIU8fz+Lml06GQkkexuUp9SnMSxBajMYeKjHvpTxaXlxFVlCJwP From: Martin Schitter To: ffmpeg-devel@ffmpeg.org Date: Mon, 21 Oct 2024 21:57:18 +0200 Message-ID: <20241021195721.892544-7-ms+git@mur.at> X-Mailer: git-send-email 2.45.2 In-Reply-To: <20241021195721.892544-2-ms+git@mur.at> References: <20241021195721.892544-2-ms+git@mur.at> MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH v12 5/9] libavcodec/dnxucdec: DNxUncompressed decoder X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Martin Schitter Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: PT/6qOq0HrCV --- libavcodec/Makefile | 1 + libavcodec/allcodecs.c | 1 + libavcodec/dnxucdec.c | 338 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 340 insertions(+) create mode 100644 libavcodec/dnxucdec.c diff --git a/libavcodec/Makefile b/libavcodec/Makefile index dd5d0de..e13b127 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -328,6 +328,7 @@ OBJS-$(CONFIG_DFPWM_DECODER) += dfpwmdec.o OBJS-$(CONFIG_DFPWM_ENCODER) += dfpwmenc.o OBJS-$(CONFIG_DNXHD_DECODER) += dnxhddec.o dnxhddata.o OBJS-$(CONFIG_DNXHD_ENCODER) += dnxhdenc.o dnxhddata.o +OBJS-$(CONFIG_DNXUC_DECODER) += dnxucdec.o OBJS-$(CONFIG_DOLBY_E_DECODER) += dolby_e.o dolby_e_parse.o kbdwin.o OBJS-$(CONFIG_DPX_DECODER) += dpx.o OBJS-$(CONFIG_DPX_ENCODER) += dpxenc.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index c7e5f99..ccca2ad 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -93,6 +93,7 @@ extern const FFCodec ff_dfa_decoder; extern const FFCodec ff_dirac_decoder; extern const FFCodec ff_dnxhd_encoder; extern const FFCodec ff_dnxhd_decoder; +extern const FFCodec ff_dnxuc_decoder; extern const FFCodec ff_dpx_encoder; extern const FFCodec ff_dpx_decoder; extern const FFCodec ff_dsicinvideo_decoder; diff --git a/libavcodec/dnxucdec.c b/libavcodec/dnxucdec.c new file mode 100644 index 0000000..9d5847d --- /dev/null +++ b/libavcodec/dnxucdec.c @@ -0,0 +1,338 @@ +/* + * Avid DNxUncomressed / SMPTE RDD 50 decoder + * Copyright (c) 2024 Martin Schitter + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + This decoder for DNxUncompressed video data is mostly based on + reverse engineering of output generated by DaVinci Resolve 19 + but was later also checked against the SMPTE RDD 50 specification. + + Not all DNxUncompressed pixel format variants are supported, + but at least an elementary base set is already usable: + + - YUV 4:2:2 8/10/12/16bit/half/float (16bit untested) + YUV 4:4:4 8/16bit/half/float (all untested!) + - RGB 8/10/12/16bit/half/float (16bit untested) + Alpha/Y 8/16bit (all untested!) + + TODO: Compositions of multiple components aren't supportet until now. + TODO: This also hinders Image+Alpha use in one file. + +*/ + +#include "avcodec.h" +#include "codec_internal.h" +#include "decode.h" +#include "libavutil/imgutils.h" +#include "libavutil/intreadwrite.h" +#include "thread.h" + +static int pass_through(AVCodecContext *avctx, AVFrame *frame, const AVPacket *avpkt) +{ + /* there is no need to copy as the data already match + * a known pixel format */ + + frame->buf[0] = av_buffer_ref(avpkt->buf); + + if (!frame->buf[0]) { + return AVERROR(ENOMEM); + } + + return av_image_fill_arrays(frame->data, frame->linesize, avpkt->data, + avctx->pix_fmt, avctx->width, avctx->height, 1); +} + +/// Unpack 10bit value +static av_always_inline +uint16_t get10(uint8_t *line_data, uint32_t pos, int msb_bytes) +{ + return (line_data[pos] << 2) | + ((line_data[msb_bytes + (pos >> 2)] >> ((pos & 0x3u) << 1)) & 0x3u); +} + +/// Unpack 12bit value +static av_always_inline +uint16_t get12(uint8_t *line_data, uint32_t pos, int msb_bytes) +{ + return (line_data[pos] << 4) | + ((line_data[msb_bytes + (pos >> 1)] >> ((pos & 0x1u) << 2)) & 0xfu); +} + +static int unpack_rg10(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt) +{ + uint8_t *data = &pkt->data[0]; + int lw = frame->width; + int msb_bytes = lw * 3; + int line_offset = lw * 3 + (lw * 3 + 3) / 4; + int pos = 0, pos_in = 0; + for(int y = 0; y < frame->height; y++){ + for(int x = 0; x < lw; x++){ + AV_WL16(&frame->data[2][pos], get10(data, pos_in++, msb_bytes)); // r + AV_WL16(&frame->data[0][pos], get10(data, pos_in++, msb_bytes)); // g + AV_WL16(&frame->data[1][pos], get10(data, pos_in++, msb_bytes)); // b + pos += 2; + } + data += line_offset; + pos_in = 0; + } + return pkt->size; +} + +static int unpack_y410(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt) +{ + uint8_t *data = &pkt->data[0]; + int lw = frame->width; + int msb_bytes = lw * 3; + int line_offset = lw * 3 + (lw * 3 + 3) / 4; + int pos = 0, pos_in = 0; + for(int y = 0; y < frame->height; y++){ + for(int x = 0; x < lw; x++){ + AV_WL16(&frame->data[0][pos], get10(data, pos_in++, msb_bytes)); // y + AV_WL16(&frame->data[1][pos], get10(data, pos_in++, msb_bytes)); // u + AV_WL16(&frame->data[2][pos], get10(data, pos_in++, msb_bytes)); // v + pos += 2; + } + data += line_offset; + pos_in = 0; + } + return pkt->size; +} + +static int unpack_rg12(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt) +{ + uint8_t *data = &pkt->data[0]; + int lw = frame->width; + int msb_bytes = lw * 3; + int line_offset = lw * 3 + (lw * 3 + 1) / 2; + int pos = 0, pos_in = 0; + for(int y = 0; y < frame->height; y++){ + for(int x = 0; x < lw; x++){ + AV_WL16(&frame->data[2][pos], get12(data, pos_in++, msb_bytes)); // r + AV_WL16(&frame->data[0][pos], get12(data, pos_in++, msb_bytes)); // g + AV_WL16(&frame->data[1][pos], get12(data, pos_in++, msb_bytes)); // b + pos += 2; + } + data += line_offset; + pos_in = 0; + } + return pkt->size; +} + +static int unpack_y412(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt) +{ + uint8_t *data = &pkt->data[0]; + int lw = frame->width; + int msb_bytes = lw * 3; + int line_offset = lw * 3 + (lw * 3 + 1) / 2; + int pos = 0, pos_in = 0; + for(int y = 0; y < frame->height; y++){ + for(int x = 0; x < lw; x++){ + AV_WL16(&frame->data[0][pos], get12(data, pos_in++, msb_bytes)); // y + AV_WL16(&frame->data[1][pos], get12(data, pos_in++, msb_bytes)); // u + AV_WL16(&frame->data[2][pos], get12(data, pos_in++, msb_bytes)); // v + pos += 2; + } + data += line_offset; + pos_in = 0; + } + return pkt->size; +} + +static int unpack_y210(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt) +{ + uint8_t *data = &pkt->data[0]; + int lw = frame->width; + int msb_bytes = lw * 2; + int line_offset = lw/2 * 5; + int pos_uv = 0, pos_y = 0, pos_in = 0; + for(int y = 0; y < frame->height; y++){ + for(int x = 0; x < lw/2; x++){ + AV_WL16(&frame->data[1][pos_uv], get10(data, pos_in++, msb_bytes)); // u + AV_WL16(&frame->data[0][pos_y], get10(data, pos_in++, msb_bytes)); // y + AV_WL16(&frame->data[2][pos_uv], get10(data, pos_in++, msb_bytes)); // v + AV_WL16(&frame->data[0][pos_y + 2], get10(data, pos_in++, msb_bytes)); // y + pos_uv += 2; + pos_y += 4; + } + data += line_offset; + pos_in = 0; + } + return pkt->size; +} + +static int unpack_y212(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt) +{ + uint8_t *data = &pkt->data[0]; + int lw = frame->width; + int msb_bytes = lw * 2; + int line_offset = lw * 3; + int pos_uv = 0, pos_y = 0, pos_in = 0; + for(int y = 0; y < frame->height; y++){ + for(int x = 0; x < lw/2; x++){ + AV_WL16(&frame->data[1][pos_uv], get12(data, pos_in++, msb_bytes)); // u + AV_WL16(&frame->data[0][pos_y], get12(data, pos_in++, msb_bytes)); // y + AV_WL16(&frame->data[2][pos_uv], get12(data, pos_in++, msb_bytes)); // v + AV_WL16(&frame->data[0][pos_y + 2],get12(data, pos_in++, msb_bytes)); // y + pos_uv += 2; + pos_y += 4; + } + data += line_offset; + pos_in = 0; + } + return pkt->size; +} + +static int check_pkt_size(AVCodecContext *avctx, AVPacket *avpkt, int bpp) +{ + int needed = ((avctx->width * bpp + 7) / 8) * avctx->height; + if (avpkt->size < needed){ + av_log(avctx, AV_LOG_ERROR, + "Insufficient size of AVPacket data (pkg size: %d needed: %d)\n", avpkt->size, needed); + return AVERROR_INVALIDDATA; + } + return 0; +} + +static int fmt_frame(AVCodecContext *avctx, AVFrame *frame, AVPacket *avpkt, + enum AVPixelFormat pix_fmt, int src_bpp, + int (*frame_handler)(AVCodecContext *avctx, AVFrame *frame, const AVPacket *avpkt)) +{ + int ret; + avctx->pix_fmt = pix_fmt; + + ret = check_pkt_size(avctx, avpkt, src_bpp); + if (ret) + return ret; + + ret = ff_thread_get_buffer(avctx, frame, 0); + if (ret < 0) + return ret; + + return frame_handler(avctx, frame, avpkt); +} + +static int dnxuc_decode_frame(AVCodecContext *avctx, AVFrame *frame, + int *got_frame, AVPacket *avpkt) +{ + char fourcc_buf[AV_FOURCC_MAX_STRING_SIZE]; + int ret; + + av_fourcc_make_string(fourcc_buf, avctx->codec_tag); + if ((avctx->width % 2) && ((fourcc_buf[0] == 'y' && fourcc_buf[1] == '2') + ||(fourcc_buf[1] == 'y' && fourcc_buf[2] == '2'))){ + av_log(avctx, AV_LOG_ERROR, + "Image width must be a multiple of 2 for YUV 4:2:2 DNxUncompressed!\n"); + return AVERROR_INVALIDDATA; + } + + switch (avctx->codec_tag) { + case MKTAG('y','2','0','8'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_UYVY422, 16, pass_through); + break; + case MKTAG('y','4','0','8'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_YUV444, 24, pass_through); + break; + case MKTAG('y','2','1','0'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_YUV422P10LE, 20, unpack_y210); + break; + case MKTAG('y','4','1','0'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_YUV444P10LE, 20, unpack_y410); + break; + case MKTAG('y','2','1','2'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_YUV422P12LE, 24, unpack_y212); + break; + case MKTAG('y','4','1','2'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_YUV444P12LE, 24, unpack_y412); + break; + case MKTAG('y','2','1','6'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_UYVY422_16LE, 32, pass_through); + break; + case MKTAG('y','4','1','6'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_YUV444_16LE, 48, pass_through); + break; + case MKTAG(' ','y','2','h'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_UYVY422F16LE, 32, pass_through); + break; + case MKTAG(' ','y','4','h'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_YUV444F16LE, 48, pass_through); + break; + case MKTAG(' ','y','2','f'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_UYVY422F32LE, 64, pass_through); + break; + case MKTAG(' ','y','4','f'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_YUV444F32LE, 96, pass_through); + break; + + case MKTAG('r','g','0','8'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_RGB24, 24, pass_through); + break; + case MKTAG('r','g','1','0'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_GBRP10LE, 30, unpack_rg10); + break; + case MKTAG('r','g','1','2'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_GBRP12LE, 36, unpack_rg12); + break; + case MKTAG('r','g','1','6'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_RGB48LE, 48, pass_through); + break; + case MKTAG(' ','r','g','h'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_RGBF16LE, 48, pass_through); + break; + case MKTAG(' ','r','g','f'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_RGBF32LE, 96, pass_through); + break; + + case MKTAG(' ','a','0','8'): + case MKTAG(' ','y','0','8'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_GRAY8, 8, pass_through); + break; + case MKTAG(' ','a','1','6'): + case MKTAG(' ','y','1','6'): + ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_GRAY16LE, 16, pass_through); + break; + + // case MKTAG('r','l','0','8'): TODO: RLE encoded 8bit alpha + // case MKTAG('r','l','1','6'): TODO: RLE encoded 16bit alpha + + default: + av_log(avctx, AV_LOG_ERROR, + "Unsupported DNxUncompressed pixel format variant: '%s'\n", + fourcc_buf); + return AVERROR_PATCHWELCOME; + } + + if (ret < 0) { + av_buffer_unref(&frame->buf[0]); + return ret; + } + + *got_frame = 1; + + return avpkt->size; +} + +const FFCodec ff_dnxuc_decoder = { + .p.name = "dnxuc", + CODEC_LONG_NAME("DNxUncompressed (SMPTE RDD 50)"), + .p.type = AVMEDIA_TYPE_VIDEO, + .p.id = AV_CODEC_ID_DNXUC, + FF_CODEC_DECODE_CB(dnxuc_decode_frame), + .p.capabilities = AV_CODEC_CAP_FRAME_THREADS, +};