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 |
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 |
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
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
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
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 --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, +};