diff mbox series

[FFmpeg-devel,v6,4/5] libavcodec/dnxucdec: DNxUncompressed decoder

Message ID 20240912055238.965068-5-ms+git@mur.at
State New
Headers show
Series [FFmpeg-devel,v6,1/5] libavcodec/: Add ID and desc entries for DNxUncompressed | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Martin Schitter Sept. 12, 2024, 5:52 a.m. UTC
---
 libavcodec/Makefile    |   1 +
 libavcodec/allcodecs.c |   1 +
 libavcodec/dnxucdec.c  | 391 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 393 insertions(+)
 create mode 100644 libavcodec/dnxucdec.c

Comments

Marton Balint Sept. 22, 2024, 10:24 p.m. UTC | #1
On Thu, 12 Sep 2024, Martin Schitter wrote:

> ---
> libavcodec/Makefile    |   1 +
> libavcodec/allcodecs.c |   1 +
> libavcodec/dnxucdec.c  | 391 +++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 393 insertions(+)
> create mode 100644 libavcodec/dnxucdec.c
>
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index 55444cb..b2f00af 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -325,6 +325,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 d773ac3..1a3c084 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..8fffd0a
> --- /dev/null
> +++ b/libavcodec/dnxucdec.c
> @@ -0,0 +1,391 @@
> +/*
> + * 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/12bit
> +  - RGB 8/10/12bit/half/float
> +
> +*/
> +
> +#include "avcodec.h"
> +#include "codec_internal.h"
> +#include "decode.h"
> +#include "libavutil/imgutils.h"
> +#include "thread.h"
> +
> +static av_cold int dnxuc_decode_init(AVCodecContext *avctx)
> +{
> +    return 0;
> +}
> +
> +
> +static int pass_though(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);
> +}
> +
> +static int float2planes(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt)
> +{
> +    int x, y, lw;
> +    const size_t sof = 4;
> +
> +    lw = frame->width;
> +
> +    for(y = 0; y < frame->height; y++){
> +        for(x = 0; x < frame->width; x++){

You might want to push variable declarations to the loop initializer 
expression, like "for (int y = 0; ..."

This is true for all similar cases.

> +            memcpy(&frame->data[2][sof*(lw*y + x)], &pkt->data[sof* 3*(lw*y + x)], sof);
> +            memcpy(&frame->data[0][sof*(lw*y + x)], &pkt->data[sof*(3*(lw*y + x) + 1)], sof);
> +            memcpy(&frame->data[1][sof*(lw*y + x)], &pkt->data[sof*(3*(lw*y + x) + 2)], sof);
> +        }
> +    }
> +    return pkt->size;
> +}
> +
> +static int half_add_alpha(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt)
> +{
> +    /* ffmpeg doesn't provide RGB half bit depth without alpha channel right now
> +     * we simply add an opaque alpha layer as workaround */
> +
> +    int x, y, lw;
> +    const size_t soh = 2;
> +    const uint16_t opaque = 0x3c00;
> +
> +    lw = frame->width;
> +
> +    for(y = 0; y < frame->height; y++){
> +        for(x = 0; x < frame->width; x++){
> +            memcpy(&frame->data[0][soh*4*(lw*y + x)], &pkt->data[soh*3*(lw*y + x)], soh*3);
> +            memcpy(&frame->data[0][soh*(4*(lw*y + x) + 3)], &opaque, soh);
> +        }
> +    }
> +    return pkt->size;
> +}
> +
> +/* DNxUncompressed utilizes a very dense bitpack representation of 10bit and 12bit pixel data.
> +
> +Lines of Image data, which look like in their ordinary 8bit counterpart, contain the most
> +significant upper bits of the pixel data. These sections alternate with shorter segments in
> +which the complementary least significant bits of information get packed in a gapless sequence.
> +
> ++----------------------+ +----------------------+ +------------------------+ +----------~
> +|  8 m.s.bits of R[1]  | |  8 m.s.bits of G[1]  | |  8 m.s.bits of B[1]    | | msb R[2]    ... one line
> ++----------------------+ +----------------------+ +------------------------+ +----------~
> +
> ++---------------------------------------------------------------+  +-----------~
> +| +------------+ +------------+ +------------+ +--------------+ |  | +--------~
> +| | 2 lsb R[2] | | 2 lsb B[1] | | 2 lsb G[1] | | 2 lsb R[1]   | |  | | G[3]lsb    ... LSB bits for line
> +| +------------+ +------------+ +------------+ +--------------+ |  | +--------~
> ++---------------------------- one byte ------------------------ +  +-----------~
> +
> +next line of MSB bytes...   */
> +
> +static int unpack_rg10(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt)
> +{
> +    int x, y, lw, msp, pack, lsp, p_off;
> +    uint16_t r,g,b;
> +
> +    lw = frame->width;
> +
> +    for(y = 0; y < frame->height; y++){
> +        for(x = 0; x < frame->width; x++){
> +            msp = pkt->data[y*3*(lw + lw/4) + 3*x];
> +            p_off = y*(3*(lw + lw/4)) + 3*lw + 3*x/4;
> +            pack = pkt->data[p_off];
> +            lsp = (pack >> (3*x%4)*2) & 0x3;
> +            r = (msp << 2) + lsp;
> +            // av_log(0, AV_LOG_DEBUG, "r: %04x, %02x, %02x, %02x, %d\n",
> +            //    r, msp, lsp, pack, p_off);
> +
> +            msp = pkt->data[y*3*(lw + lw/4) + 3*x + 1];
> +            p_off = y*(3*(lw + lw/4)) + 3*lw + (3*x+1)/4;
> +            pack = pkt->data[p_off];
> +            lsp = (pack >> ((3*x+1)%4)*2) & 0x3;
> +            g = (msp << 2) + lsp;
> +            // av_log(0, AV_LOG_DEBUG, "g: %04x, %02x, %02x, %02x, %d\n",
> +            //    g, msp, lsp, pack, p_off);
> +
> +            msp = pkt->data[y*3*(lw + lw/4) + 3*x + 2];
> +            p_off = y*(3*(lw + lw/4)) + 3*lw + (3*x+2)/4;
> +            pack = pkt->data[p_off];
> +            lsp = (pack >> ((3*x+2)%4)*2) & 0x3;
> +            b = (msp << 2) + lsp;
> +            // av_log(0, AV_LOG_DEBUG, "b: %04x, %02x, %02x, %02x, %d\n\n",
> +            //    b, msp, lsp, pack, p_off);
> +
> +            memcpy(&frame->data[2][2*(y*lw + x)], &r, 2);
> +            memcpy(&frame->data[0][2*(y*lw + x)], &g, 2);
> +            memcpy(&frame->data[1][2*(y*lw + x)], &b, 2);
> +        }
> +    }
> +    return pkt->size;
> +}
> +
> +static int unpack_rg12(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt)
> +{
> +    int x, y, lw, msp, pack, lsp, p_off;
> +    uint16_t r,g,b;
> +
> +    lw = frame->width;
> +
> +    for(y = 0; y < frame->height; y++){
> +        for(x = 0; x < frame->width; x++){
> +            msp = pkt->data[y*3*(lw + lw/2) + 3*x];
> +            p_off = y*(3*(lw + lw/2)) + 3*lw + 3*x/2;
> +            pack = pkt->data[p_off];
> +            lsp = (pack >> (3*x%2)*4) & 0xf;
> +            r = (msp << 4) + lsp;
> +            // av_log(0, AV_LOG_DEBUG, "r: %04x, %02x, %02x, %02x, %d\n",
> +            //    r, msp, lsp, pack, p_off);
> +
> +            msp = pkt->data[y*3*(lw + lw/2) + 3*x + 1];
> +            p_off =y*(3*(lw + lw/2)) + 3*lw + (3*x+1)/2;
> +            pack = pkt->data[p_off];
> +            lsp = (pack >> ((3*x+1)%2)*4) & 0xf;
> +            g = (msp << 4) + lsp;
> +            // av_log(0, AV_LOG_DEBUG, "g: %04x, %02x, %02x, %02x, %d\n",
> +            //    g, msp, lsp, pack, p_off);
> +
> +            msp = pkt->data[y*3*(lw + lw/2) + 3*x + 2];
> +            p_off = y*(3*(lw + lw/2)) + 3*lw + (3*x+2)/2;
> +            pack = pkt->data[p_off];
> +            lsp = (pack >> ((3*x+2)%2)*4) & 0xf;
> +            b = (msp << 4) + lsp;
> +            // av_log(0, AV_LOG_DEBUG, "b: %04x, %02x, %02x, %02x, %d\n\n",
> +            //    b, msp, lsp, pack, p_off);
> +
> +            memcpy(&frame->data[2][2*(y*lw + x)], &r, 2);
> +            memcpy(&frame->data[0][2*(y*lw + x)], &g, 2);
> +            memcpy(&frame->data[1][2*(y*lw + x)], &b, 2);
> +        }
> +    }
> +    return pkt->size;
> +}
> +
> +
> +static int unpack_y210(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt)
> +{
> +    int x, y, lw, msp, pack, lsp, p_off;
> +    uint16_t y1, y2, u, v;
> +
> +    lw = frame->width;
> +
> +    for(y = 0; y < frame->height; y++){
> +        for(x = 0; x < frame->width; x += 2){
> +
> +            p_off = y*(2*(lw + lw/4)) + 2*lw + x/2;
> +            pack = pkt->data[p_off];
> +
> +            msp = pkt->data[y*2*(lw + lw/4) + 2*x];
> +            lsp = pack & 0x3;
> +            u = (msp << 2) + lsp;
> +            // av_log(0, AV_LOG_DEBUG, " u: %04x, %02x, %02x, %02x, %d\n",
> +            //    u, msp, lsp, pack, p_off);
> +
> +            msp = pkt->data[y*2*(lw + lw/4) + 2*x + 1];
> +            lsp = (pack >> 2) & 0x3;
> +            y1 = (msp << 2) + lsp;
> +            // av_log(0, AV_LOG_DEBUG, "y1: %04x, %02x, %02x, %02x, %d\n",
> +            //    y1, msp, lsp, pack, p_off);
> +
> +            msp = pkt->data[y*2*(lw + lw/4) + 2*x + 2];
> +            lsp = (pack >> 4) & 0x3;
> +            v = (msp << 2) + lsp;
> +            // av_log(0, AV_LOG_DEBUG, " v: %04x, %02x, %02x, %02x, %d\n",
> +            //    v, msp, lsp, pack, p_off);
> +
> +            msp = pkt->data[y*2*(lw + lw/4) + 2*x + 3];
> +            lsp = (pack >> 6) & 0x3;
> +            y2 = (msp << 2) + lsp;
> +            // av_log(0, AV_LOG_DEBUG, "y2: %04x, %02x, %02x, %02x, %d\n\n",
> +            //    y2, msp, lsp, pack, p_off);
> +
> +            memcpy(&frame->data[0][2*(y*lw + x)], &y1, 2);
> +            memcpy(&frame->data[0][2*(y*lw + x+1)], &y2, 2);
> +            memcpy(&frame->data[1][2*(y*lw/2 + x/2)], &u, 2);
> +            memcpy(&frame->data[2][2*(y*lw/2 + x/2)], &v, 2);
> +        }
> +    }
> +    return pkt->size;
> +}
> +
> +
> +static int unpack_y212(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt)
> +{
> +    int x, y, lw, msp, pack, lsp, p_off;
> +    uint16_t y1, y2, u, v;
> +
> +    lw = frame->width;
> +
> +    for(y = 0; y < frame->height; y++){
> +        for(x = 0; x < frame->width; x += 2){
> +
> +            p_off = y*(2*(lw + lw/2)) + 2*lw + x;
> +            pack = pkt->data[p_off];
> +
> +            msp = pkt->data[y*2*(lw + lw/2) + 2*x];
> +            lsp = pack & 0xf;
> +            u = (msp << 4) + lsp;
> +            // av_log(0, AV_LOG_DEBUG, " u: %04x, %02x, %02x, %02x, %d\n",
> +            //    u, msp, lsp, pack, p_off);
> +
> +            msp = pkt->data[y*2*(lw + lw/2) + 2*x + 1];
> +            lsp = (pack >> 4) & 0xf;
> +            y1 = (msp << 4) + lsp;
> +            // av_log(0, AV_LOG_DEBUG, "y1: %04x, %02x, %02x, %02x, %d\n",
> +            //    y1, msp, lsp, pack, p_off);
> +
> +            p_off = y*(2*(lw + lw/2)) + 2*lw + x+1;
> +            pack = pkt->data[p_off];
> +
> +            msp = pkt->data[y*2*(lw + lw/2) + 2*x + 2];
> +            lsp = pack & 0xf;
> +            v = (msp << 4) + lsp;
> +            // av_log(0, AV_LOG_DEBUG, " v: %04x, %02x, %02x, %02x, %d\n",
> +            //    v, msp, lsp, pack, p_off);
> +
> +            msp = pkt->data[y*2*(lw + lw/2) + 2*x + 3];
> +            lsp = (pack >> 4) & 0xf;
> +            y2 = (msp << 4) + lsp;
> +            // av_log(0, AV_LOG_DEBUG, "y2: %04x, %02x, %02x, %02x, %d\n\n",
> +            //    y2, msp, lsp, pack, p_off);
> +
> +            memcpy(&frame->data[0][2*(y*lw + x)], &y1, 2);
> +            memcpy(&frame->data[0][2*(y*lw + x+1)], &y2, 2);
> +            memcpy(&frame->data[1][2*(y*lw/2 + x/2)], &u, 2);
> +            memcpy(&frame->data[2][2*(y*lw/2 + x/2)], &v, 2);
> +        }
> +    }
> +    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_EXIT;

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[5];
> +    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_EXIT;

AVERROR_INVALIDDATA

> +    }
> +
> +    switch (avctx->codec_tag) {
> +        case MKTAG('r','g','0','8'):
> +            ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_RGB24, 24, pass_though);
> +            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','h'):
> +            ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_RGBAF16LE, 48, half_add_alpha);
> +            break;
> +        case MKTAG(' ','r','g','f'):
> +            ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_GBRPF32LE, 96, float2planes);
> +            break;
> +
> +        case MKTAG('y','2','0','8'):
> +            ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_UYVY422, 16, pass_though);
> +            break;
> +        case MKTAG('y','2','1','0'):
> +            ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_YUV422P10LE, 20, unpack_y210);
> +            break;
> +        case MKTAG('y','2','1','2'):
> +            ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_YUV422P12LE, 24, unpack_y212);
> +            break;
> +
> +        default:
> +            av_log(avctx, AV_LOG_ERROR,
> +            "Unsupported DNxUncompressed pixel format variant: '%s'\n",
> +            fourcc_buf);
> +            return AVERROR(ENOSYS);

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,
> +    .init           = dnxuc_decode_init,
> +    FF_CODEC_DECODE_CB(dnxuc_decode_frame),
> +    .p.capabilities = AV_CODEC_CAP_FRAME_THREADS,
> +};
> -- 
> 2.45.2
>

Regards,
Marton
Martin Schitter Sept. 23, 2024, 9:14 a.m. UTC | #2
All the mentioned issues are fixed in v9.

Thanks for your accurate review!

martin

On 23.09.24 00:24, Marton Balint wrote:
>> +    for(y = 0; y < frame->height; y++){
>> +        for(x = 0; x < frame->width; x++){
> 
> You might want to push variable declarations to the loop initializer 
> expression, like "for (int y = 0; ..."
> 
> This is true for all similar cases.

>> +        av_log(avctx, AV_LOG_ERROR,
>> +        "Insufficient size of AVPacket data (pkg size: %d needed: 
>> %d)\n", avpkt->size, needed);
>> +        return AVERROR_EXIT;
> 
> AVERROR_INVALIDDATA

>> +        av_log(avctx, AV_LOG_ERROR,
>> +        "Image width must be a multiple of 2 for YUV 4:2:2 
>> DNxUncompressed!\n");
>> +        return AVERROR_EXIT;
> 
> AVERROR_INVALIDDATA

>> +            av_log(avctx, AV_LOG_ERROR,
>> +            "Unsupported DNxUncompressed pixel format variant: '%s'\n",
>> +            fourcc_buf);
>> +            return AVERROR(ENOSYS);
> 
> AVERROR_PATCHWELCOME
Martin Schitter Sept. 28, 2024, 10:35 a.m. UTC | #3
I know, you are all busy with 7.1 release cleanup, but could you please 
take another look at my DNxUncompressed patches and merge them.

I always fixed the change requests reported by reviewers within 24 
hours, and most of them were in fact really trivial and of cosmetic 
nature anyway, but in the meanwhile a few weeks passed by and my 
contribution is still not merged! :(

For the changes from Monday (v9) I still have not got any reaction.
(https://ffmpeg.org//pipermail/ffmpeg-devel/2024-September/333806.html)

It would be really glad if you could finally accept the code or at lest 
report all the needed improvements in a finally solvable manner.

thanks
martin
Michael Niedermayer Sept. 28, 2024, 10:41 p.m. UTC | #4
On Sat, Sep 28, 2024 at 12:35:13PM +0200, martin schitter wrote:
> I know, you are all busy with 7.1 release cleanup, but could you please take
> another look at my DNxUncompressed patches and merge them.
> 
> I always fixed the change requests reported by reviewers within 24 hours,
> and most of them were in fact really trivial and of cosmetic nature anyway,
> but in the meanwhile a few weeks passed by and my contribution is still not
> merged! :(

make sure you add yourself to the MAINTAINER file, so once it is merged
you would then be less dependant on other people having time

thx

[...]
diff mbox series

Patch

diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 55444cb..b2f00af 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -325,6 +325,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 d773ac3..1a3c084 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..8fffd0a
--- /dev/null
+++ b/libavcodec/dnxucdec.c
@@ -0,0 +1,391 @@ 
+/*
+ * 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/12bit
+  - RGB 8/10/12bit/half/float
+
+*/
+
+#include "avcodec.h"
+#include "codec_internal.h"
+#include "decode.h"
+#include "libavutil/imgutils.h"
+#include "thread.h"
+
+static av_cold int dnxuc_decode_init(AVCodecContext *avctx)
+{
+    return 0;
+}
+
+
+static int pass_though(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);
+}
+
+static int float2planes(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt)
+{
+    int x, y, lw;
+    const size_t sof = 4;
+
+    lw = frame->width;
+
+    for(y = 0; y < frame->height; y++){
+        for(x = 0; x < frame->width; x++){
+            memcpy(&frame->data[2][sof*(lw*y + x)], &pkt->data[sof* 3*(lw*y + x)], sof);
+            memcpy(&frame->data[0][sof*(lw*y + x)], &pkt->data[sof*(3*(lw*y + x) + 1)], sof);
+            memcpy(&frame->data[1][sof*(lw*y + x)], &pkt->data[sof*(3*(lw*y + x) + 2)], sof);
+        }
+    }
+    return pkt->size;
+}
+
+static int half_add_alpha(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt)
+{
+    /* ffmpeg doesn't provide RGB half bit depth without alpha channel right now
+     * we simply add an opaque alpha layer as workaround */
+
+    int x, y, lw;
+    const size_t soh = 2;
+    const uint16_t opaque = 0x3c00;
+
+    lw = frame->width;
+
+    for(y = 0; y < frame->height; y++){
+        for(x = 0; x < frame->width; x++){
+            memcpy(&frame->data[0][soh*4*(lw*y + x)], &pkt->data[soh*3*(lw*y + x)], soh*3);
+            memcpy(&frame->data[0][soh*(4*(lw*y + x) + 3)], &opaque, soh);
+        }
+    }
+    return pkt->size;
+}
+
+/* DNxUncompressed utilizes a very dense bitpack representation of 10bit and 12bit pixel data.
+
+Lines of Image data, which look like in their ordinary 8bit counterpart, contain the most
+significant upper bits of the pixel data. These sections alternate with shorter segments in
+which the complementary least significant bits of information get packed in a gapless sequence.
+
++----------------------+ +----------------------+ +------------------------+ +----------~
+|  8 m.s.bits of R[1]  | |  8 m.s.bits of G[1]  | |  8 m.s.bits of B[1]    | | msb R[2]    ... one line
++----------------------+ +----------------------+ +------------------------+ +----------~
+
++---------------------------------------------------------------+  +-----------~
+| +------------+ +------------+ +------------+ +--------------+ |  | +--------~
+| | 2 lsb R[2] | | 2 lsb B[1] | | 2 lsb G[1] | | 2 lsb R[1]   | |  | | G[3]lsb    ... LSB bits for line
+| +------------+ +------------+ +------------+ +--------------+ |  | +--------~
++---------------------------- one byte ------------------------ +  +-----------~
+
+next line of MSB bytes...   */
+
+static int unpack_rg10(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt)
+{
+    int x, y, lw, msp, pack, lsp, p_off;
+    uint16_t r,g,b;
+
+    lw = frame->width;
+
+    for(y = 0; y < frame->height; y++){
+        for(x = 0; x < frame->width; x++){
+            msp = pkt->data[y*3*(lw + lw/4) + 3*x];
+            p_off = y*(3*(lw + lw/4)) + 3*lw + 3*x/4;
+            pack = pkt->data[p_off];
+            lsp = (pack >> (3*x%4)*2) & 0x3;
+            r = (msp << 2) + lsp;
+            // av_log(0, AV_LOG_DEBUG, "r: %04x, %02x, %02x, %02x, %d\n",
+            //    r, msp, lsp, pack, p_off);
+
+            msp = pkt->data[y*3*(lw + lw/4) + 3*x + 1];
+            p_off = y*(3*(lw + lw/4)) + 3*lw + (3*x+1)/4;
+            pack = pkt->data[p_off];
+            lsp = (pack >> ((3*x+1)%4)*2) & 0x3;
+            g = (msp << 2) + lsp;
+            // av_log(0, AV_LOG_DEBUG, "g: %04x, %02x, %02x, %02x, %d\n",
+            //    g, msp, lsp, pack, p_off);
+
+            msp = pkt->data[y*3*(lw + lw/4) + 3*x + 2];
+            p_off = y*(3*(lw + lw/4)) + 3*lw + (3*x+2)/4;
+            pack = pkt->data[p_off];
+            lsp = (pack >> ((3*x+2)%4)*2) & 0x3;
+            b = (msp << 2) + lsp;
+            // av_log(0, AV_LOG_DEBUG, "b: %04x, %02x, %02x, %02x, %d\n\n",
+            //    b, msp, lsp, pack, p_off);
+
+            memcpy(&frame->data[2][2*(y*lw + x)], &r, 2);
+            memcpy(&frame->data[0][2*(y*lw + x)], &g, 2);
+            memcpy(&frame->data[1][2*(y*lw + x)], &b, 2);
+        }
+    }
+    return pkt->size;
+}
+
+static int unpack_rg12(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt)
+{
+    int x, y, lw, msp, pack, lsp, p_off;
+    uint16_t r,g,b;
+
+    lw = frame->width;
+
+    for(y = 0; y < frame->height; y++){
+        for(x = 0; x < frame->width; x++){
+            msp = pkt->data[y*3*(lw + lw/2) + 3*x];
+            p_off = y*(3*(lw + lw/2)) + 3*lw + 3*x/2;
+            pack = pkt->data[p_off];
+            lsp = (pack >> (3*x%2)*4) & 0xf;
+            r = (msp << 4) + lsp;
+            // av_log(0, AV_LOG_DEBUG, "r: %04x, %02x, %02x, %02x, %d\n",
+            //    r, msp, lsp, pack, p_off);
+
+            msp = pkt->data[y*3*(lw + lw/2) + 3*x + 1];
+            p_off =y*(3*(lw + lw/2)) + 3*lw + (3*x+1)/2;
+            pack = pkt->data[p_off];
+            lsp = (pack >> ((3*x+1)%2)*4) & 0xf;
+            g = (msp << 4) + lsp;
+            // av_log(0, AV_LOG_DEBUG, "g: %04x, %02x, %02x, %02x, %d\n",
+            //    g, msp, lsp, pack, p_off);
+
+            msp = pkt->data[y*3*(lw + lw/2) + 3*x + 2];
+            p_off = y*(3*(lw + lw/2)) + 3*lw + (3*x+2)/2;
+            pack = pkt->data[p_off];
+            lsp = (pack >> ((3*x+2)%2)*4) & 0xf;
+            b = (msp << 4) + lsp;
+            // av_log(0, AV_LOG_DEBUG, "b: %04x, %02x, %02x, %02x, %d\n\n",
+            //    b, msp, lsp, pack, p_off);
+
+            memcpy(&frame->data[2][2*(y*lw + x)], &r, 2);
+            memcpy(&frame->data[0][2*(y*lw + x)], &g, 2);
+            memcpy(&frame->data[1][2*(y*lw + x)], &b, 2);
+        }
+    }
+    return pkt->size;
+}
+
+
+static int unpack_y210(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt)
+{
+    int x, y, lw, msp, pack, lsp, p_off;
+    uint16_t y1, y2, u, v;
+
+    lw = frame->width;
+
+    for(y = 0; y < frame->height; y++){
+        for(x = 0; x < frame->width; x += 2){
+
+            p_off = y*(2*(lw + lw/4)) + 2*lw + x/2;
+            pack = pkt->data[p_off];
+
+            msp = pkt->data[y*2*(lw + lw/4) + 2*x];
+            lsp = pack & 0x3;
+            u = (msp << 2) + lsp;
+            // av_log(0, AV_LOG_DEBUG, " u: %04x, %02x, %02x, %02x, %d\n",
+            //    u, msp, lsp, pack, p_off);
+
+            msp = pkt->data[y*2*(lw + lw/4) + 2*x + 1];
+            lsp = (pack >> 2) & 0x3;
+            y1 = (msp << 2) + lsp;
+            // av_log(0, AV_LOG_DEBUG, "y1: %04x, %02x, %02x, %02x, %d\n",
+            //    y1, msp, lsp, pack, p_off);
+
+            msp = pkt->data[y*2*(lw + lw/4) + 2*x + 2];
+            lsp = (pack >> 4) & 0x3;
+            v = (msp << 2) + lsp;
+            // av_log(0, AV_LOG_DEBUG, " v: %04x, %02x, %02x, %02x, %d\n",
+            //    v, msp, lsp, pack, p_off);
+
+            msp = pkt->data[y*2*(lw + lw/4) + 2*x + 3];
+            lsp = (pack >> 6) & 0x3;
+            y2 = (msp << 2) + lsp;
+            // av_log(0, AV_LOG_DEBUG, "y2: %04x, %02x, %02x, %02x, %d\n\n",
+            //    y2, msp, lsp, pack, p_off);
+
+            memcpy(&frame->data[0][2*(y*lw + x)], &y1, 2);
+            memcpy(&frame->data[0][2*(y*lw + x+1)], &y2, 2);
+            memcpy(&frame->data[1][2*(y*lw/2 + x/2)], &u, 2);
+            memcpy(&frame->data[2][2*(y*lw/2 + x/2)], &v, 2);
+        }
+    }
+    return pkt->size;
+}
+
+
+static int unpack_y212(AVCodecContext *avctx, AVFrame *frame, const AVPacket *pkt)
+{
+    int x, y, lw, msp, pack, lsp, p_off;
+    uint16_t y1, y2, u, v;
+
+    lw = frame->width;
+
+    for(y = 0; y < frame->height; y++){
+        for(x = 0; x < frame->width; x += 2){
+
+            p_off = y*(2*(lw + lw/2)) + 2*lw + x;
+            pack = pkt->data[p_off];
+
+            msp = pkt->data[y*2*(lw + lw/2) + 2*x];
+            lsp = pack & 0xf;
+            u = (msp << 4) + lsp;
+            // av_log(0, AV_LOG_DEBUG, " u: %04x, %02x, %02x, %02x, %d\n",
+            //    u, msp, lsp, pack, p_off);
+
+            msp = pkt->data[y*2*(lw + lw/2) + 2*x + 1];
+            lsp = (pack >> 4) & 0xf;
+            y1 = (msp << 4) + lsp;
+            // av_log(0, AV_LOG_DEBUG, "y1: %04x, %02x, %02x, %02x, %d\n",
+            //    y1, msp, lsp, pack, p_off);
+
+            p_off = y*(2*(lw + lw/2)) + 2*lw + x+1;
+            pack = pkt->data[p_off];
+
+            msp = pkt->data[y*2*(lw + lw/2) + 2*x + 2];
+            lsp = pack & 0xf;
+            v = (msp << 4) + lsp;
+            // av_log(0, AV_LOG_DEBUG, " v: %04x, %02x, %02x, %02x, %d\n",
+            //    v, msp, lsp, pack, p_off);
+
+            msp = pkt->data[y*2*(lw + lw/2) + 2*x + 3];
+            lsp = (pack >> 4) & 0xf;
+            y2 = (msp << 4) + lsp;
+            // av_log(0, AV_LOG_DEBUG, "y2: %04x, %02x, %02x, %02x, %d\n\n",
+            //    y2, msp, lsp, pack, p_off);
+
+            memcpy(&frame->data[0][2*(y*lw + x)], &y1, 2);
+            memcpy(&frame->data[0][2*(y*lw + x+1)], &y2, 2);
+            memcpy(&frame->data[1][2*(y*lw/2 + x/2)], &u, 2);
+            memcpy(&frame->data[2][2*(y*lw/2 + x/2)], &v, 2);
+        }
+    }
+    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_EXIT;
+    }
+    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[5];
+    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_EXIT;
+    }
+
+    switch (avctx->codec_tag) {
+        case MKTAG('r','g','0','8'):
+            ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_RGB24, 24, pass_though);
+            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','h'):
+            ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_RGBAF16LE, 48, half_add_alpha);
+            break;
+        case MKTAG(' ','r','g','f'):
+            ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_GBRPF32LE, 96, float2planes);
+            break;
+
+        case MKTAG('y','2','0','8'):
+            ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_UYVY422, 16, pass_though);
+            break;
+        case MKTAG('y','2','1','0'):
+            ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_YUV422P10LE, 20, unpack_y210);
+            break;
+        case MKTAG('y','2','1','2'):
+            ret = fmt_frame(avctx, frame, avpkt, AV_PIX_FMT_YUV422P12LE, 24, unpack_y212);
+            break;
+
+        default:
+            av_log(avctx, AV_LOG_ERROR,
+            "Unsupported DNxUncompressed pixel format variant: '%s'\n",
+            fourcc_buf);
+            return AVERROR(ENOSYS);
+    }
+
+    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,
+    .init           = dnxuc_decode_init,
+    FF_CODEC_DECODE_CB(dnxuc_decode_frame),
+    .p.capabilities = AV_CODEC_CAP_FRAME_THREADS,
+};