| /* |
| * 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 |
| */ |
| |
| #include "libavutil/avassert.h" |
| #include "libavutil/frame.h" |
| #include "libavutil/imgutils.h" |
| #include "libavutil/log.h" |
| #include "libavutil/mem.h" |
| #include "libavutil/refstruct.h" |
| |
| #include "decode.h" |
| #include "lcevcdec.h" |
| |
| static LCEVC_ColorFormat map_format(int format) |
| { |
| switch (format) { |
| case AV_PIX_FMT_YUV420P: |
| return LCEVC_I420_8; |
| case AV_PIX_FMT_YUV420P10: |
| return LCEVC_I420_10_LE; |
| case AV_PIX_FMT_NV12: |
| return LCEVC_NV12_8; |
| case AV_PIX_FMT_NV21: |
| return LCEVC_NV21_8; |
| case AV_PIX_FMT_GRAY8: |
| return LCEVC_GRAY_8; |
| } |
| |
| return LCEVC_ColorFormat_Unknown; |
| } |
| |
| static int alloc_base_frame(void *logctx, FFLCEVCContext *lcevc, |
| const AVFrame *frame, LCEVC_PictureHandle *picture) |
| { |
| LCEVC_PictureDesc desc; |
| LCEVC_ColorFormat fmt = map_format(frame->format); |
| LCEVC_PicturePlaneDesc planes[AV_VIDEO_MAX_PLANES] = { 0 }; |
| int width = frame->width - frame->crop_left - frame->crop_right; |
| int height = frame->height - frame->crop_top - frame->crop_bottom; |
| LCEVC_ReturnCode res; |
| |
| res = LCEVC_DefaultPictureDesc(&desc, fmt, width, height); |
| if (res != LCEVC_Success) |
| return AVERROR_EXTERNAL; |
| |
| desc.cropTop = frame->crop_top; |
| desc.cropBottom = frame->crop_bottom; |
| desc.cropLeft = frame->crop_left; |
| desc.cropRight = frame->crop_right; |
| desc.sampleAspectRatioNum = frame->sample_aspect_ratio.num; |
| desc.sampleAspectRatioDen = frame->sample_aspect_ratio.den; |
| |
| for (int i = 0; i < AV_VIDEO_MAX_PLANES; i++) { |
| planes[i].firstSample = frame->data[i]; |
| planes[i].rowByteStride = frame->linesize[i]; |
| } |
| |
| /* Allocate LCEVC Picture */ |
| res = LCEVC_AllocPictureExternal(lcevc->decoder, &desc, NULL, planes, picture); |
| if (res != LCEVC_Success) { |
| return AVERROR_EXTERNAL; |
| } |
| |
| return 0; |
| } |
| |
| static int alloc_enhanced_frame(void *logctx, FFLCEVCFrame *frame_ctx, |
| LCEVC_PictureHandle *picture) |
| { |
| FFLCEVCContext *lcevc = frame_ctx->lcevc; |
| LCEVC_PictureDesc desc ; |
| LCEVC_ColorFormat fmt = map_format(frame_ctx->frame->format); |
| LCEVC_PicturePlaneDesc planes[4] = { 0 }; |
| LCEVC_ReturnCode res; |
| |
| res = LCEVC_DefaultPictureDesc(&desc, fmt, frame_ctx->frame->width, frame_ctx->frame->height); |
| if (res != LCEVC_Success) |
| return AVERROR_EXTERNAL; |
| |
| /* Set plane description */ |
| for (int i = 0; i < 4; i++) { |
| planes[i].firstSample = frame_ctx->frame->data[i]; |
| planes[i].rowByteStride = frame_ctx->frame->linesize[i]; |
| } |
| |
| /* Allocate LCEVC Picture */ |
| res = LCEVC_AllocPictureExternal(lcevc->decoder, &desc, NULL, planes, picture); |
| if (res != LCEVC_Success) { |
| return AVERROR_EXTERNAL; |
| } |
| return 0; |
| } |
| |
| static int lcevc_send_frame(void *logctx, FFLCEVCFrame *frame_ctx, const AVFrame *in) |
| { |
| FFLCEVCContext *lcevc = frame_ctx->lcevc; |
| const AVFrameSideData *sd = av_frame_get_side_data(in, AV_FRAME_DATA_LCEVC); |
| AVFrame *opaque; |
| LCEVC_PictureHandle picture; |
| LCEVC_ReturnCode res; |
| int ret = 0; |
| |
| if (!sd) |
| return 1; |
| |
| res = LCEVC_SendDecoderEnhancementData(lcevc->decoder, in->pts, sd->data, sd->size); |
| if (res != LCEVC_Success) |
| return AVERROR_EXTERNAL; |
| |
| ret = alloc_base_frame(logctx, lcevc, in, &picture); |
| if (ret < 0) |
| return ret; |
| |
| opaque = av_frame_clone(in); |
| if (!opaque) { |
| LCEVC_FreePicture(lcevc->decoder, picture); |
| return AVERROR(ENOMEM); |
| } |
| |
| res = LCEVC_SetPictureUserData(lcevc->decoder, picture, opaque); |
| if (res != LCEVC_Success) { |
| LCEVC_FreePicture(lcevc->decoder, picture); |
| av_frame_free(&opaque); |
| return AVERROR_EXTERNAL; |
| } |
| |
| res = LCEVC_SendDecoderBase(lcevc->decoder, in->pts, picture, -1, opaque); |
| if (res != LCEVC_Success) { |
| LCEVC_FreePicture(lcevc->decoder, picture); |
| av_frame_free(&opaque); |
| return AVERROR_EXTERNAL; |
| } |
| |
| memset(&picture, 0, sizeof(picture)); |
| ret = alloc_enhanced_frame(logctx, frame_ctx, &picture); |
| if (ret < 0) |
| return ret; |
| |
| res = LCEVC_SendDecoderPicture(lcevc->decoder, picture); |
| if (res != LCEVC_Success) { |
| LCEVC_FreePicture(lcevc->decoder, picture); |
| return AVERROR_EXTERNAL; |
| } |
| |
| return 0; |
| } |
| |
| static int generate_output(void *logctx, FFLCEVCFrame *frame_ctx, AVFrame *out) |
| { |
| FFLCEVCContext *lcevc = frame_ctx->lcevc; |
| LCEVC_PictureDesc desc; |
| LCEVC_DecodeInformation info; |
| LCEVC_PictureHandle picture; |
| LCEVC_ReturnCode res; |
| |
| res = LCEVC_ReceiveDecoderPicture(lcevc->decoder, &picture, &info); |
| if (res != LCEVC_Success) |
| return AVERROR_EXTERNAL; |
| |
| res = LCEVC_GetPictureDesc(lcevc->decoder, picture, &desc); |
| if (res != LCEVC_Success) { |
| LCEVC_FreePicture(lcevc->decoder, picture); |
| return AVERROR_EXTERNAL; |
| } |
| |
| av_frame_unref(out); |
| av_frame_copy_props(frame_ctx->frame, (AVFrame *)info.baseUserData); |
| av_frame_move_ref(out, frame_ctx->frame); |
| |
| out->crop_top = desc.cropTop; |
| out->crop_bottom = desc.cropBottom; |
| out->crop_left = desc.cropLeft; |
| out->crop_right = desc.cropRight; |
| out->sample_aspect_ratio.num = desc.sampleAspectRatioNum; |
| out->sample_aspect_ratio.den = desc.sampleAspectRatioDen; |
| out->width = desc.width + out->crop_left + out->crop_right; |
| out->height = desc.height + out->crop_top + out->crop_bottom; |
| |
| res = LCEVC_FreePicture(lcevc->decoder, picture); |
| if (res != LCEVC_Success) |
| return AVERROR_EXTERNAL; |
| |
| return 0; |
| } |
| |
| static int lcevc_flush_pictures(FFLCEVCContext *lcevc) |
| { |
| LCEVC_PictureHandle picture; |
| LCEVC_ReturnCode res; |
| |
| while (1) { |
| AVFrame *base = NULL; |
| res = LCEVC_ReceiveDecoderBase (lcevc->decoder, &picture); |
| if (res != LCEVC_Success && res != LCEVC_Again) |
| return AVERROR_EXTERNAL; |
| |
| if (res == LCEVC_Again) |
| break; |
| |
| LCEVC_GetPictureUserData(lcevc->decoder, picture, (void **)&base); |
| av_frame_free(&base); |
| |
| res = LCEVC_FreePicture(lcevc->decoder, picture); |
| if (res != LCEVC_Success) |
| return AVERROR_EXTERNAL; |
| } |
| |
| return 0; |
| } |
| |
| static int lcevc_receive_frame(void *logctx, FFLCEVCFrame *frame_ctx, AVFrame *out) |
| { |
| FFLCEVCContext *lcevc = frame_ctx->lcevc; |
| int ret; |
| |
| ret = generate_output(logctx, frame_ctx, out); |
| if (ret < 0) |
| return ret; |
| |
| return lcevc_flush_pictures(lcevc); |
| } |
| |
| static void event_callback(LCEVC_DecoderHandle dec, LCEVC_Event event, |
| LCEVC_PictureHandle pic, const LCEVC_DecodeInformation *info, |
| const uint8_t *data, uint32_t size, void *logctx) |
| { |
| switch (event) { |
| case LCEVC_Log: |
| av_log(logctx, AV_LOG_INFO, "%s\n", data); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void lcevc_free(AVRefStructOpaque unused, void *obj) |
| { |
| FFLCEVCContext *lcevc = obj; |
| if (lcevc->initialized) { |
| LCEVC_FlushDecoder(lcevc->decoder); |
| lcevc_flush_pictures(lcevc); |
| LCEVC_DestroyDecoder(lcevc->decoder); |
| } |
| memset(lcevc, 0, sizeof(*lcevc)); |
| } |
| |
| static int lcevc_init(FFLCEVCContext *lcevc, void *logctx) |
| { |
| LCEVC_AccelContextHandle dummy = { 0 }; |
| const int32_t event = LCEVC_Log; |
| |
| if (LCEVC_CreateDecoder(&lcevc->decoder, dummy) != LCEVC_Success) { |
| av_log(logctx, AV_LOG_ERROR, "Failed to create LCEVC decoder\n"); |
| return AVERROR_EXTERNAL; |
| } |
| |
| LCEVC_ConfigureDecoderInt(lcevc->decoder, "log_level", 4); |
| LCEVC_ConfigureDecoderIntArray(lcevc->decoder, "events", 1, &event); |
| LCEVC_SetDecoderEventCallback(lcevc->decoder, event_callback, logctx); |
| |
| if (LCEVC_InitializeDecoder(lcevc->decoder) != LCEVC_Success) { |
| av_log(logctx, AV_LOG_ERROR, "Failed to initialize LCEVC decoder\n"); |
| LCEVC_DestroyDecoder(lcevc->decoder); |
| return AVERROR_EXTERNAL; |
| } |
| |
| lcevc->initialized = 1; |
| |
| return 0; |
| } |
| |
| int ff_lcevc_process(void *logctx, AVFrame *frame) |
| { |
| FrameDecodeData *fdd = frame->private_ref; |
| FFLCEVCFrame *frame_ctx = fdd->post_process_opaque; |
| FFLCEVCContext *lcevc = frame_ctx->lcevc; |
| int ret; |
| |
| if (!lcevc->initialized) { |
| ret = lcevc_init(lcevc, logctx); |
| if (ret < 0) |
| return ret; |
| } |
| |
| av_assert0(frame_ctx->frame); |
| |
| |
| ret = lcevc_send_frame(logctx, frame_ctx, frame); |
| if (ret) |
| return ret < 0 ? ret : 0; |
| |
| ret = lcevc_receive_frame(logctx, frame_ctx, frame); |
| if (ret < 0) |
| return ret; |
| |
| av_frame_remove_side_data(frame, AV_FRAME_DATA_LCEVC); |
| |
| return 0; |
| } |
| |
| int ff_lcevc_alloc(FFLCEVCContext **plcevc) |
| { |
| FFLCEVCContext *lcevc = NULL; |
| lcevc = av_refstruct_alloc_ext(sizeof(*lcevc), 0, NULL, lcevc_free); |
| if (!lcevc) |
| return AVERROR(ENOMEM); |
| *plcevc = lcevc; |
| return 0; |
| } |
| |
| void ff_lcevc_unref(void *opaque) |
| { |
| FFLCEVCFrame *lcevc = opaque; |
| av_refstruct_unref(&lcevc->lcevc); |
| av_frame_free(&lcevc->frame); |
| av_free(opaque); |
| } |