diff options
Diffstat (limited to 'libavcodec/mpegvideo_xvmc.c')
-rw-r--r-- | libavcodec/mpegvideo_xvmc.c | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/libavcodec/mpegvideo_xvmc.c b/libavcodec/mpegvideo_xvmc.c new file mode 100644 index 0000000..f065837 --- /dev/null +++ b/libavcodec/mpegvideo_xvmc.c @@ -0,0 +1,376 @@ +/* + * XVideo Motion Compensation + * Copyright (c) 2003 Ivan Kalvachev + * + * 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 <limits.h> +#include <X11/extensions/XvMC.h> + +#include "avcodec.h" +#include "mpegutils.h" +#include "mpegvideo.h" + +#undef NDEBUG +#include <assert.h> + +#include "xvmc.h" +#include "xvmc_internal.h" +#include "version.h" + +/** + * Initialize the block field of the MpegEncContext pointer passed as + * parameter after making sure that the data is not corrupted. + * In order to implement something like direct rendering instead of decoding + * coefficients in s->blocks and then copying them, copy them directly + * into the data_blocks array provided by xvmc. + */ +void ff_xvmc_init_block(MpegEncContext *s) +{ + struct xvmc_pix_fmt *render = (struct xvmc_pix_fmt*)s->current_picture.f->data[2]; + assert(render && render->xvmc_id == AV_XVMC_ID); + + s->block = (int16_t (*)[64])(render->data_blocks + render->next_free_data_block_num * 64); +} + +static void exchange_uv(MpegEncContext *s) +{ + int16_t (*tmp)[64]; + + tmp = s->pblocks[4]; + s->pblocks[4] = s->pblocks[5]; + s->pblocks[5] = tmp; +} + +/** + * Fill individual block pointers, so there are no gaps in the data_block array + * in case not all blocks in the macroblock are coded. + */ +void ff_xvmc_pack_pblocks(MpegEncContext *s, int cbp) +{ + int i, j = 0; + const int mb_block_count = 4 + (1 << s->chroma_format); + + cbp <<= 12-mb_block_count; + for (i = 0; i < mb_block_count; i++) { + if (cbp & (1 << 11)) + s->pblocks[i] = &s->block[j++]; + else + s->pblocks[i] = NULL; + cbp += cbp; + } + if (s->swap_uv) { + exchange_uv(s); + } +} + +/** + * Find and store the surfaces that are used as reference frames. + * This function should be called for every new field and/or frame. + * It should be safe to call the function a few times for the same field. + */ +static int ff_xvmc_field_start(AVCodecContext *avctx, const uint8_t *buf, uint32_t buf_size) +{ + struct MpegEncContext *s = avctx->priv_data; + struct xvmc_pix_fmt *last, *next, *render = (struct xvmc_pix_fmt*)s->current_picture.f->data[2]; + const int mb_block_count = 4 + (1 << s->chroma_format); + + assert(avctx); + if (!render || render->xvmc_id != AV_XVMC_ID || + !render->data_blocks || !render->mv_blocks || + (unsigned int)render->allocated_mv_blocks > INT_MAX/(64*6) || + (unsigned int)render->allocated_data_blocks > INT_MAX/64 || + !render->p_surface) { + av_log(avctx, AV_LOG_ERROR, + "Render token doesn't look as expected.\n"); + return -1; // make sure that this is a render packet + } + + if (render->filled_mv_blocks_num) { + av_log(avctx, AV_LOG_ERROR, + "Rendering surface contains %i unprocessed blocks.\n", + render->filled_mv_blocks_num); + return -1; + } + if (render->allocated_mv_blocks < 1 || + render->allocated_data_blocks < render->allocated_mv_blocks*mb_block_count || + render->start_mv_blocks_num >= render->allocated_mv_blocks || + render->next_free_data_block_num > + render->allocated_data_blocks - + mb_block_count*(render->allocated_mv_blocks-render->start_mv_blocks_num)) { + av_log(avctx, AV_LOG_ERROR, + "Rendering surface doesn't provide enough block structures to work with.\n"); + return -1; + } + + render->picture_structure = s->picture_structure; + render->flags = s->first_field ? 0 : XVMC_SECOND_FIELD; + render->p_future_surface = NULL; + render->p_past_surface = NULL; + + switch(s->pict_type) { + case AV_PICTURE_TYPE_I: + return 0; // no prediction from other frames + case AV_PICTURE_TYPE_B: + next = (struct xvmc_pix_fmt*)s->next_picture.f->data[2]; + if (!next) + return -1; + if (next->xvmc_id != AV_XVMC_ID) + return -1; + render->p_future_surface = next->p_surface; + // no return here, going to set forward prediction + case AV_PICTURE_TYPE_P: + last = (struct xvmc_pix_fmt*)s->last_picture.f->data[2]; + if (!last) + last = render; // predict second field from the first + if (last->xvmc_id != AV_XVMC_ID) + return -1; + render->p_past_surface = last->p_surface; + return 0; + } + +return -1; +} + +/** + * Complete frame/field rendering by passing any remaining blocks. + * Normally ff_draw_horiz_band() is called for each slice, however, + * some leftover blocks, for example from error_resilience(), may remain. + * It should be safe to call the function a few times for the same field. + */ +static int ff_xvmc_field_end(AVCodecContext *avctx) +{ + struct MpegEncContext *s = avctx->priv_data; + struct xvmc_pix_fmt *render = (struct xvmc_pix_fmt*)s->current_picture.f->data[2]; + assert(render); + + if (render->filled_mv_blocks_num > 0) + ff_mpeg_draw_horiz_band(s, 0, 0); + return 0; +} + +/** + * Synthesize the data needed by XvMC to render one macroblock of data. + * Fill all relevant fields, if necessary do IDCT. + */ +static void ff_xvmc_decode_mb(struct MpegEncContext *s) +{ + XvMCMacroBlock *mv_block; + struct xvmc_pix_fmt *render; + int i, cbp, blocks_per_mb; + + const int mb_xy = s->mb_y * s->mb_stride + s->mb_x; + + + if (s->encoding) { + av_log(s->avctx, AV_LOG_ERROR, "XVMC doesn't support encoding!!!\n"); + return; + } + + // from ff_mpv_reconstruct_mb(), update DC predictors for P macroblocks + if (!s->mb_intra) { + s->last_dc[0] = + s->last_dc[1] = + s->last_dc[2] = 128 << s->intra_dc_precision; + } + + // MC doesn't skip blocks + s->mb_skipped = 0; + + + // Do I need to export quant when I could not perform postprocessing? + // Anyway, it doesn't hurt. + s->current_picture.qscale_table[mb_xy] = s->qscale; + + // start of XVMC-specific code + render = (struct xvmc_pix_fmt*)s->current_picture.f->data[2]; + assert(render); + assert(render->xvmc_id == AV_XVMC_ID); + assert(render->mv_blocks); + + // take the next free macroblock + mv_block = &render->mv_blocks[render->start_mv_blocks_num + + render->filled_mv_blocks_num]; + + mv_block->x = s->mb_x; + mv_block->y = s->mb_y; + mv_block->dct_type = s->interlaced_dct; // XVMC_DCT_TYPE_FRAME/FIELD; + if (s->mb_intra) { + mv_block->macroblock_type = XVMC_MB_TYPE_INTRA; // no MC, all done + } else { + mv_block->macroblock_type = XVMC_MB_TYPE_PATTERN; + + if (s->mv_dir & MV_DIR_FORWARD) { + mv_block->macroblock_type |= XVMC_MB_TYPE_MOTION_FORWARD; + // PMV[n][dir][xy] = mv[dir][n][xy] + mv_block->PMV[0][0][0] = s->mv[0][0][0]; + mv_block->PMV[0][0][1] = s->mv[0][0][1]; + mv_block->PMV[1][0][0] = s->mv[0][1][0]; + mv_block->PMV[1][0][1] = s->mv[0][1][1]; + } + if (s->mv_dir & MV_DIR_BACKWARD) { + mv_block->macroblock_type |= XVMC_MB_TYPE_MOTION_BACKWARD; + mv_block->PMV[0][1][0] = s->mv[1][0][0]; + mv_block->PMV[0][1][1] = s->mv[1][0][1]; + mv_block->PMV[1][1][0] = s->mv[1][1][0]; + mv_block->PMV[1][1][1] = s->mv[1][1][1]; + } + + switch(s->mv_type) { + case MV_TYPE_16X16: + mv_block->motion_type = XVMC_PREDICTION_FRAME; + break; + case MV_TYPE_16X8: + mv_block->motion_type = XVMC_PREDICTION_16x8; + break; + case MV_TYPE_FIELD: + mv_block->motion_type = XVMC_PREDICTION_FIELD; + if (s->picture_structure == PICT_FRAME) { + mv_block->PMV[0][0][1] <<= 1; + mv_block->PMV[1][0][1] <<= 1; + mv_block->PMV[0][1][1] <<= 1; + mv_block->PMV[1][1][1] <<= 1; + } + break; + case MV_TYPE_DMV: + mv_block->motion_type = XVMC_PREDICTION_DUAL_PRIME; + if (s->picture_structure == PICT_FRAME) { + + mv_block->PMV[0][0][0] = s->mv[0][0][0]; // top from top + mv_block->PMV[0][0][1] = s->mv[0][0][1] << 1; + + mv_block->PMV[0][1][0] = s->mv[0][0][0]; // bottom from bottom + mv_block->PMV[0][1][1] = s->mv[0][0][1] << 1; + + mv_block->PMV[1][0][0] = s->mv[0][2][0]; // dmv00, top from bottom + mv_block->PMV[1][0][1] = s->mv[0][2][1] << 1; // dmv01 + + mv_block->PMV[1][1][0] = s->mv[0][3][0]; // dmv10, bottom from top + mv_block->PMV[1][1][1] = s->mv[0][3][1] << 1; // dmv11 + + } else { + mv_block->PMV[0][1][0] = s->mv[0][2][0]; // dmv00 + mv_block->PMV[0][1][1] = s->mv[0][2][1]; // dmv01 + } + break; + default: + assert(0); + } + + mv_block->motion_vertical_field_select = 0; + + // set correct field references + if (s->mv_type == MV_TYPE_FIELD || s->mv_type == MV_TYPE_16X8) { + mv_block->motion_vertical_field_select |= s->field_select[0][0]; + mv_block->motion_vertical_field_select |= s->field_select[1][0] << 1; + mv_block->motion_vertical_field_select |= s->field_select[0][1] << 2; + mv_block->motion_vertical_field_select |= s->field_select[1][1] << 3; + } + } // !intra + // time to handle data blocks + mv_block->index = render->next_free_data_block_num; + + blocks_per_mb = 6; + if (s->chroma_format >= 2) { + blocks_per_mb = 4 + (1 << s->chroma_format); + } + + // calculate cbp + cbp = 0; + for (i = 0; i < blocks_per_mb; i++) { + cbp += cbp; + if (s->block_last_index[i] >= 0) + cbp++; + } + + if (s->avctx->flags & AV_CODEC_FLAG_GRAY) { + if (s->mb_intra) { // intra frames are always full chroma blocks + for (i = 4; i < blocks_per_mb; i++) { + memset(s->pblocks[i], 0, sizeof(*s->pblocks[i])); // so we need to clear them + if (!render->unsigned_intra) + *s->pblocks[i][0] = 1 << 10; + } + } else { + cbp &= 0xf << (blocks_per_mb - 4); + blocks_per_mb = 4; // luminance blocks only + } + } + mv_block->coded_block_pattern = cbp; + if (cbp == 0) + mv_block->macroblock_type &= ~XVMC_MB_TYPE_PATTERN; + + for (i = 0; i < blocks_per_mb; i++) { + if (s->block_last_index[i] >= 0) { + // I do not have unsigned_intra MOCO to test, hope it is OK. + if (s->mb_intra && (render->idct || !render->unsigned_intra)) + *s->pblocks[i][0] -= 1 << 10; + if (!render->idct) { + s->idsp.idct(*s->pblocks[i]); + /* It is unclear if MC hardware requires pixel diff values to be + * in the range [-255;255]. TODO: Clipping if such hardware is + * ever found. As of now it would only be an unnecessary + * slowdown. */ + } + // copy blocks only if the codec doesn't support pblocks reordering + if (!s->pack_pblocks) { + memcpy(&render->data_blocks[render->next_free_data_block_num*64], + s->pblocks[i], sizeof(*s->pblocks[i])); + } + render->next_free_data_block_num++; + } + } + render->filled_mv_blocks_num++; + + assert(render->filled_mv_blocks_num <= render->allocated_mv_blocks); + assert(render->next_free_data_block_num <= render->allocated_data_blocks); + /* The above conditions should not be able to fail as long as this function + * is used and the following 'if ()' automatically calls a callback to free + * blocks. */ + + + if (render->filled_mv_blocks_num == render->allocated_mv_blocks) + ff_mpeg_draw_horiz_band(s, 0, 0); +} + +#if CONFIG_MPEG1_XVMC_HWACCEL +const AVHWAccel ff_mpeg1_xvmc_hwaccel = { + .name = "mpeg1_xvmc", + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_MPEG1VIDEO, + .pix_fmt = AV_PIX_FMT_XVMC, + .start_frame = ff_xvmc_field_start, + .end_frame = ff_xvmc_field_end, + .decode_slice = NULL, + .decode_mb = ff_xvmc_decode_mb, + .priv_data_size = 0, +}; +#endif + +#if CONFIG_MPEG2_XVMC_HWACCEL +const AVHWAccel ff_mpeg2_xvmc_hwaccel = { + .name = "mpeg2_xvmc", + .type = AVMEDIA_TYPE_VIDEO, + .id = AV_CODEC_ID_MPEG2VIDEO, + .pix_fmt = AV_PIX_FMT_XVMC, + .start_frame = ff_xvmc_field_start, + .end_frame = ff_xvmc_field_end, + .decode_slice = NULL, + .decode_mb = ff_xvmc_decode_mb, + .priv_data_size = 0, +}; +#endif |