diff options
author | wm4 <wm4@nowhere> | 2012-11-05 17:02:04 +0100 |
---|---|---|
committer | wm4 <wm4@nowhere> | 2012-11-12 20:06:14 +0100 |
commit | d4bdd0473d6f43132257c9fb3848d829755167a3 (patch) | |
tree | 8021c2f7da1841393c8c832105e20cd527826d6c /video | |
parent | bd48deba77bd5582c5829d6fe73a7d2571088aba (diff) |
Rename directories, move files (step 1 of 2) (does not compile)
Tis drops the silly lib prefixes, and attempts to organize the tree in
a more logical way. Make the top-level directory less cluttered as
well.
Renames the following directories:
libaf -> audio/filter
libao2 -> audio/out
libvo -> video/out
libmpdemux -> demux
Split libmpcodecs:
vf* -> video/filter
vd*, dec_video.* -> video/decode
mp_image*, img_format*, ... -> video/
ad*, dec_audio.* -> audio/decode
libaf/format.* is moved to audio/ - this is similar to how mp_image.*
is located in video/.
Move most top-level .c/.h files to core. (talloc.c/.h is left on top-
level, because it's external.) Park some of the more annoying files
in compat/. Some of these are relicts from the time mplayer used
ffmpeg internals.
sub/ is not split, because it's too much of a mess (subtitle code is
mixed with OSD display and rendering).
Maybe the organization of core is not ideal: it mixes playback core
(like mplayer.c) and utility helpers (like bstr.c/h). Should the need
arise, the playback core will be moved somewhere else, while core
contains all helper and common code.
Diffstat (limited to 'video')
94 files changed, 36526 insertions, 0 deletions
diff --git a/video/csputils.c b/video/csputils.c new file mode 100644 index 0000000000..23eb099f69 --- /dev/null +++ b/video/csputils.c @@ -0,0 +1,391 @@ +/* + * Common code related to colorspaces and conversion + * + * Copyleft (C) 2009 Reimar Döffinger <Reimar.Doeffinger@gmx.de> + * + * mp_invert_yuv2rgb based on DarkPlaces engine, original code (GPL2 or later) + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can alternatively redistribute this file 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. + */ + +#include <stdint.h> +#include <math.h> +#include <assert.h> +#include <libavutil/common.h> + +#include "csputils.h" + +char * const mp_csp_names[MP_CSP_COUNT] = { + "Autoselect", + "BT.601 (SD)", + "BT.709 (HD)", + "SMPTE-240M", + "RGB", +}; + +char * const mp_csp_equalizer_names[MP_CSP_EQ_COUNT] = { + "brightness", + "contrast", + "hue", + "saturation", + "gamma", +}; + +enum mp_csp avcol_spc_to_mp_csp(enum AVColorSpace colorspace) +{ + switch (colorspace) { + case AVCOL_SPC_BT709: return MP_CSP_BT_709; + case AVCOL_SPC_BT470BG: return MP_CSP_BT_601; + case AVCOL_SPC_SMPTE170M: return MP_CSP_BT_601; + case AVCOL_SPC_SMPTE240M: return MP_CSP_SMPTE_240M; + case AVCOL_SPC_RGB: return MP_CSP_RGB; + default: return MP_CSP_AUTO; + } +} + +enum mp_csp_levels avcol_range_to_mp_csp_levels(enum AVColorRange range) +{ + switch (range) { + case AVCOL_RANGE_MPEG: return MP_CSP_LEVELS_TV; + case AVCOL_RANGE_JPEG: return MP_CSP_LEVELS_PC; + default: return MP_CSP_LEVELS_AUTO; + } +} + +enum AVColorSpace mp_csp_to_avcol_spc(enum mp_csp colorspace) +{ + switch (colorspace) { + case MP_CSP_BT_709: return AVCOL_SPC_BT709; + case MP_CSP_BT_601: return AVCOL_SPC_BT470BG; + case MP_CSP_SMPTE_240M: return AVCOL_SPC_SMPTE240M; + case MP_CSP_RGB: return AVCOL_SPC_RGB; + default: return AVCOL_SPC_UNSPECIFIED; + } +} + +enum AVColorRange mp_csp_levels_to_avcol_range(enum mp_csp_levels range) +{ + switch (range) { + case MP_CSP_LEVELS_TV: return AVCOL_RANGE_MPEG; + case MP_CSP_LEVELS_PC: return AVCOL_RANGE_JPEG; + default: return AVCOL_RANGE_UNSPECIFIED; + } +} + +enum mp_csp mp_csp_guess_colorspace(int width, int height) +{ + return width >= 1280 || height > 576 ? MP_CSP_BT_709 : MP_CSP_BT_601; +} + +/** + * \brief little helper function to create a lookup table for gamma + * \param map buffer to create map into + * \param size size of buffer + * \param gamma gamma value + */ +void mp_gen_gamma_map(uint8_t *map, int size, float gamma) +{ + if (gamma == 1.0) { + for (int i = 0; i < size; i++) + map[i] = 255 * i / (size - 1); + return; + } + gamma = 1.0 / gamma; + for (int i = 0; i < size; i++) { + float tmp = (float)i / (size - 1.0); + tmp = pow(tmp, gamma); + if (tmp > 1.0) + tmp = 1.0; + if (tmp < 0.0) + tmp = 0.0; + map[i] = 255 * tmp; + } +} + +/* Fill in the Y, U, V vectors of a yuv2rgb conversion matrix + * based on the given luma weights of the R, G and B components (lr, lg, lb). + * lr+lg+lb is assumed to equal 1. + * This function is meant for colorspaces satisfying the following + * conditions (which are true for common YUV colorspaces): + * - The mapping from input [Y, U, V] to output [R, G, B] is linear. + * - Y is the vector [1, 1, 1]. (meaning input Y component maps to 1R+1G+1B) + * - U maps to a value with zero R and positive B ([0, x, y], y > 0; + * i.e. blue and green only). + * - V maps to a value with zero B and positive R ([x, y, 0], x > 0; + * i.e. red and green only). + * - U and V are orthogonal to the luma vector [lr, lg, lb]. + * - The magnitudes of the vectors U and V are the minimal ones for which + * the image of the set Y=[0...1],U=[-0.5...0.5],V=[-0.5...0.5] under the + * conversion function will cover the set R=[0...1],G=[0...1],B=[0...1] + * (the resulting matrix can be converted for other input/output ranges + * outside this function). + * Under these conditions the given parameters lr, lg, lb uniquely + * determine the mapping of Y, U, V to R, G, B. + */ +static void luma_coeffs(float m[3][4], float lr, float lg, float lb) +{ + assert(fabs(lr+lg+lb - 1) < 1e-6); + m[0][0] = m[1][0] = m[2][0] = 1; + m[0][1] = 0; + m[1][1] = -2 * (1-lb) * lb/lg; + m[2][1] = 2 * (1-lb); + m[0][2] = 2 * (1-lr); + m[1][2] = -2 * (1-lr) * lr/lg; + m[2][2] = 0; + // Constant coefficients (m[x][3]) not set here +} + +/** + * \brief get the coefficients of the yuv -> rgb conversion matrix + * \param params struct specifying the properties of the conversion like + * brightness, ... + * \param m array to store coefficients into + */ +void mp_get_yuv2rgb_coeffs(struct mp_csp_params *params, float m[3][4]) +{ + int format = params->colorspace.format; + if (format <= MP_CSP_AUTO || format >= MP_CSP_COUNT) + format = MP_CSP_BT_601; + switch (format) { + case MP_CSP_BT_601: luma_coeffs(m, 0.299, 0.587, 0.114 ); break; + case MP_CSP_BT_709: luma_coeffs(m, 0.2126, 0.7152, 0.0722); break; + case MP_CSP_SMPTE_240M: luma_coeffs(m, 0.2122, 0.7013, 0.0865); break; + default: + abort(); + }; + + // Hue is equivalent to rotating input [U, V] subvector around the origin. + // Saturation scales [U, V]. + float huecos = params->saturation * cos(params->hue); + float huesin = params->saturation * sin(params->hue); + for (int i = 0; i < 3; i++) { + float u = m[i][COL_U]; + m[i][COL_U] = huecos * u - huesin * m[i][COL_V]; + m[i][COL_V] = huesin * u + huecos * m[i][COL_V]; + } + + int levels_in = params->colorspace.levels_in; + if (levels_in <= MP_CSP_LEVELS_AUTO || levels_in >= MP_CSP_LEVELS_COUNT) + levels_in = MP_CSP_LEVELS_TV; + assert(params->input_bits >= 8); + assert(params->texture_bits >= params->input_bits); + double s = (1 << params->input_bits-8) / ((1<<params->texture_bits)-1.); + // The values below are written in 0-255 scale + struct yuvlevels { double ymin, ymax, cmin, cmid; } + yuvlim = { 16*s, 235*s, 16*s, 128*s }, + yuvfull = { 0*s, 255*s, 1*s, 128*s }, // '1' for symmetry around 128 + yuvlev; + switch (levels_in) { + case MP_CSP_LEVELS_TV: yuvlev = yuvlim; break; + case MP_CSP_LEVELS_PC: yuvlev = yuvfull; break; + default: + abort(); + } + + int levels_out = params->colorspace.levels_out; + if (levels_out <= MP_CSP_LEVELS_AUTO || levels_out >= MP_CSP_LEVELS_COUNT) + levels_out = MP_CSP_LEVELS_PC; + struct rgblevels { double min, max; } + rgblim = { 16/255., 235/255. }, + rgbfull = { 0, 1 }, + rgblev; + switch (levels_out) { + case MP_CSP_LEVELS_TV: rgblev = rgblim; break; + case MP_CSP_LEVELS_PC: rgblev = rgbfull; break; + default: + abort(); + } + + double ymul = (rgblev.max - rgblev.min) / (yuvlev.ymax - yuvlev.ymin); + double cmul = (rgblev.max - rgblev.min) / (yuvlev.cmid - yuvlev.cmin) / 2; + for (int i = 0; i < 3; i++) { + m[i][COL_Y] *= ymul; + m[i][COL_U] *= cmul; + m[i][COL_V] *= cmul; + // Set COL_C so that Y=umin,UV=cmid maps to RGB=min (black to black) + m[i][COL_C] = rgblev.min - m[i][COL_Y] * yuvlev.ymin + -(m[i][COL_U] + m[i][COL_V]) * yuvlev.cmid; + } + + // Brightness adds a constant to output R,G,B. + // Contrast scales Y around 1/2 (not 0 in this implementation). + for (int i = 0; i < 3; i++) { + m[i][COL_C] += params->brightness; + m[i][COL_Y] *= params->contrast; + m[i][COL_C] += (rgblev.max-rgblev.min) * (1 - params->contrast)/2; + } + + int in_bits = FFMAX(params->int_bits_in, 1); + int out_bits = FFMAX(params->int_bits_out, 1); + double in_scale = (1 << in_bits) - 1.0; + double out_scale = (1 << out_bits) - 1.0; + for (int i = 0; i < 3; i++) { + m[i][COL_C] *= out_scale; // constant is 1.0 + for (int x = 0; x < 3; x++) + m[i][x] *= out_scale / in_scale; + } +} + +//! size of gamma map use to avoid slow exp function in gen_yuv2rgb_map +#define GMAP_SIZE (1024) +/** + * \brief generate a 3D YUV -> RGB map + * \param params struct containing parameters like brightness, gamma, ... + * \param map where to store map. Must provide space for (size + 2)^3 elements + * \param size size of the map, excluding border + */ +void mp_gen_yuv2rgb_map(struct mp_csp_params *params, unsigned char *map, int size) +{ + int i, j, k, l; + float step = 1.0 / size; + float y, u, v; + float yuv2rgb[3][4]; + unsigned char gmaps[3][GMAP_SIZE]; + mp_gen_gamma_map(gmaps[0], GMAP_SIZE, params->rgamma); + mp_gen_gamma_map(gmaps[1], GMAP_SIZE, params->ggamma); + mp_gen_gamma_map(gmaps[2], GMAP_SIZE, params->bgamma); + mp_get_yuv2rgb_coeffs(params, yuv2rgb); + for (i = 0; i < 3; i++) + for (j = 0; j < 4; j++) + yuv2rgb[i][j] *= GMAP_SIZE - 1; + v = 0; + for (i = -1; i <= size; i++) { + u = 0; + for (j = -1; j <= size; j++) { + y = 0; + for (k = -1; k <= size; k++) { + for (l = 0; l < 3; l++) { + float rgb = yuv2rgb[l][COL_Y] * y + yuv2rgb[l][COL_U] * u + + yuv2rgb[l][COL_V] * v + yuv2rgb[l][COL_C]; + *map++ = gmaps[l][av_clip(rgb, 0, GMAP_SIZE - 1)]; + } + y += (k == -1 || k == size - 1) ? step / 2 : step; + } + u += (j == -1 || j == size - 1) ? step / 2 : step; + } + v += (i == -1 || i == size - 1) ? step / 2 : step; + } +} + +// Copy settings from eq into params. +void mp_csp_copy_equalizer_values(struct mp_csp_params *params, + const struct mp_csp_equalizer *eq) +{ + params->brightness = eq->values[MP_CSP_EQ_BRIGHTNESS] / 100.0; + params->contrast = (eq->values[MP_CSP_EQ_CONTRAST] + 100) / 100.0; + params->hue = eq->values[MP_CSP_EQ_HUE] / 100.0 * 3.1415927; + params->saturation = (eq->values[MP_CSP_EQ_SATURATION] + 100) / 100.0; + float gamma = exp(log(8.0) * eq->values[MP_CSP_EQ_GAMMA] / 100.0); + params->rgamma = gamma; + params->ggamma = gamma; + params->bgamma = gamma; +} + +static int find_eq(int capabilities, const char *name) +{ + for (int i = 0; i < MP_CSP_EQ_COUNT; i++) { + if (strcmp(name, mp_csp_equalizer_names[i]) == 0) + return ((1 << i) & capabilities) ? i : -1; + } + return -1; +} + +int mp_csp_equalizer_get(struct mp_csp_equalizer *eq, const char *property, + int *out_value) +{ + int index = find_eq(eq->capabilities, property); + if (index < 0) + return -1; + + *out_value = eq->values[index]; + + return 0; +} + +int mp_csp_equalizer_set(struct mp_csp_equalizer *eq, const char *property, + int value) +{ + int index = find_eq(eq->capabilities, property); + if (index < 0) + return 0; + + eq->values[index] = value; + + return 1; +} + +void mp_invert_yuv2rgb(float out[3][4], float in[3][4]) +{ + float m00 = in[0][0], m01 = in[0][1], m02 = in[0][2], m03 = in[0][3], + m10 = in[1][0], m11 = in[1][1], m12 = in[1][2], m13 = in[1][3], + m20 = in[2][0], m21 = in[2][1], m22 = in[2][2], m23 = in[2][3]; + + // calculate the adjoint + out[0][0] = (m11 * m22 - m21 * m12); + out[0][1] = -(m01 * m22 - m21 * m02); + out[0][2] = (m01 * m12 - m11 * m02); + out[1][0] = -(m10 * m22 - m20 * m12); + out[1][1] = (m00 * m22 - m20 * m02); + out[1][2] = -(m00 * m12 - m10 * m02); + out[2][0] = (m10 * m21 - m20 * m11); + out[2][1] = -(m00 * m21 - m20 * m01); + out[2][2] = (m00 * m11 - m10 * m01); + + // calculate the determinant (as inverse == 1/det * adjoint, + // adjoint * m == identity * det, so this calculates the det) + float det = m00 * out[0][0] + m10 * out[0][1] + m20 * out[0][2]; + det = 1.0f / det; + + out[0][0] *= det; + out[0][1] *= det; + out[0][2] *= det; + out[1][0] *= det; + out[1][1] *= det; + out[1][2] *= det; + out[2][0] *= det; + out[2][1] *= det; + out[2][2] *= det; + + // fix the constant coefficient + // rgb = M * yuv + C + // M^-1 * rgb = yuv + M^-1 * C + // yuv = M^-1 * rgb - M^-1 * C + // ^^^^^^^^^^ + out[0][3] = -(out[0][0] * m03 + out[0][1] * m13 + out[0][2] * m23); + out[1][3] = -(out[1][0] * m03 + out[1][1] * m13 + out[1][2] * m23); + out[2][3] = -(out[2][0] * m03 + out[2][1] * m13 + out[2][2] * m23); +} + +// Multiply the color in c with the given matrix. +// c is {R, G, B} or {Y, U, V} (depending on input/output and matrix). +// Output is clipped to the given number of bits. +void mp_map_int_color(float matrix[3][4], int clip_bits, int c[3]) +{ + int in[3] = {c[0], c[1], c[2]}; + for (int i = 0; i < 3; i++) { + double val = matrix[i][3]; + for (int x = 0; x < 3; x++) + val += matrix[i][x] * in[x]; + int ival = lrint(val); + c[i] = av_clip(ival, 0, (1 << clip_bits) - 1); + } +} diff --git a/video/csputils.h b/video/csputils.h new file mode 100644 index 0000000000..d66bb86fa3 --- /dev/null +++ b/video/csputils.h @@ -0,0 +1,151 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can alternatively redistribute this file 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. + */ + +#ifndef MPLAYER_CSPUTILS_H +#define MPLAYER_CSPUTILS_H + +#include <stdbool.h> +#include <stdint.h> + +#include "libavcodec/avcodec.h" + +/* NOTE: the csp and levels AUTO values are converted to specific ones + * above vf/vo level. At least vf_scale relies on all valid settings being + * nonzero at vf/vo level. + */ + +enum mp_csp { + MP_CSP_AUTO, + MP_CSP_BT_601, + MP_CSP_BT_709, + MP_CSP_SMPTE_240M, + MP_CSP_RGB, + MP_CSP_COUNT +}; + +// Any enum mp_csp value is a valid index (except MP_CSP_COUNT) +extern char * const mp_csp_names[MP_CSP_COUNT]; + +enum mp_csp_levels { + MP_CSP_LEVELS_AUTO, + MP_CSP_LEVELS_TV, + MP_CSP_LEVELS_PC, + MP_CSP_LEVELS_COUNT, +}; + +struct mp_csp_details { + enum mp_csp format; + enum mp_csp_levels levels_in; // encoded video + enum mp_csp_levels levels_out; // output device +}; + +// initializer for struct mp_csp_details that contains reasonable defaults +#define MP_CSP_DETAILS_DEFAULTS {MP_CSP_BT_601, MP_CSP_LEVELS_TV, MP_CSP_LEVELS_PC} + +struct mp_csp_params { + struct mp_csp_details colorspace; + float brightness; + float contrast; + float hue; + float saturation; + float rgamma; + float ggamma; + float bgamma; + // texture_bits/input_bits is for rescaling fixed point input to range [0,1] + int texture_bits; + int input_bits; + // for scaling integer input and output (if 0, assume range [0,1]) + int int_bits_in; + int int_bits_out; +}; + +#define MP_CSP_PARAMS_DEFAULTS { \ + .colorspace = MP_CSP_DETAILS_DEFAULTS, \ + .brightness = 0, .contrast = 1, .hue = 0, .saturation = 1, \ + .rgamma = 1, .ggamma = 1, .bgamma = 1, \ + .texture_bits = 8, .input_bits = 8} + +enum mp_csp_equalizer_param { + MP_CSP_EQ_BRIGHTNESS, + MP_CSP_EQ_CONTRAST, + MP_CSP_EQ_HUE, + MP_CSP_EQ_SATURATION, + MP_CSP_EQ_GAMMA, + MP_CSP_EQ_COUNT, +}; + +#define MP_CSP_EQ_CAPS_COLORMATRIX \ + ( (1 << MP_CSP_EQ_BRIGHTNESS) \ + | (1 << MP_CSP_EQ_CONTRAST) \ + | (1 << MP_CSP_EQ_HUE) \ + | (1 << MP_CSP_EQ_SATURATION) ) + +#define MP_CSP_EQ_CAPS_GAMMA (1 << MP_CSP_EQ_GAMMA) + +extern char * const mp_csp_equalizer_names[MP_CSP_EQ_COUNT]; + +// Default initialization with 0 is enough, except for the capabilities field +struct mp_csp_equalizer { + // Bit field of capabilities. For example (1 << MP_CSP_EQ_HUE) means hue + // support is available. + int capabilities; + // Value for each property is in the range [-100, 100]. + // 0 is default, meaning neutral or no change. + int values[MP_CSP_EQ_COUNT]; +}; + + +void mp_csp_copy_equalizer_values(struct mp_csp_params *params, + const struct mp_csp_equalizer *eq); + +int mp_csp_equalizer_set(struct mp_csp_equalizer *eq, const char *property, + int value); + +int mp_csp_equalizer_get(struct mp_csp_equalizer *eq, const char *property, + int *out_value); + +enum mp_csp avcol_spc_to_mp_csp(enum AVColorSpace colorspace); + +enum mp_csp_levels avcol_range_to_mp_csp_levels(enum AVColorRange range); + +enum AVColorSpace mp_csp_to_avcol_spc(enum mp_csp colorspace); + +enum AVColorRange mp_csp_levels_to_avcol_range(enum mp_csp_levels range); + +enum mp_csp mp_csp_guess_colorspace(int width, int height); + +void mp_gen_gamma_map(unsigned char *map, int size, float gamma); +#define ROW_R 0 +#define ROW_G 1 +#define ROW_B 2 +#define COL_Y 0 +#define COL_U 1 +#define COL_V 2 +#define COL_C 3 +void mp_get_yuv2rgb_coeffs(struct mp_csp_params *params, float yuv2rgb[3][4]); +void mp_gen_yuv2rgb_map(struct mp_csp_params *params, uint8_t *map, int size); + +void mp_invert_yuv2rgb(float out[3][4], float in[3][4]); +void mp_map_int_color(float matrix[3][4], int clip_bits, int c[3]); + +#endif /* MPLAYER_CSPUTILS_H */ diff --git a/video/decode/dec_video.c b/video/decode/dec_video.c new file mode 100644 index 0000000000..5c6d3113da --- /dev/null +++ b/video/decode/dec_video.c @@ -0,0 +1,448 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "options.h" + +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <unistd.h> + +#include "mp_msg.h" + +#include "osdep/timer.h" +#include "osdep/shmem.h" + +#include "stream/stream.h" +#include "libmpdemux/demuxer.h" + +#include "codec-cfg.h" + +#include "libvo/video_out.h" +#include "libvo/csputils.h" + +#include "libmpdemux/stheader.h" +#include "vd.h" +#include "vf.h" + +#include "dec_video.h" + +// =================================================================== + +#include "cpudetect.h" + +int field_dominance = -1; + +int divx_quality = 0; + +int get_video_quality_max(sh_video_t *sh_video) +{ + vf_instance_t *vf = sh_video->vfilter; + if (vf) { + int ret = vf->control(vf, VFCTRL_QUERY_MAX_PP_LEVEL, NULL); + if (ret > 0) { + mp_tmsg(MSGT_DECVIDEO, MSGL_INFO, "[PP] Using external postprocessing filter, max q = %d.\n", ret); + return ret; + } + } + return 0; +} + +int set_video_colors(sh_video_t *sh_video, const char *item, int value) +{ + vf_instance_t *vf = sh_video->vfilter; + vf_equalizer_t data; + + data.item = item; + data.value = value; + + mp_dbg(MSGT_DECVIDEO, MSGL_V, "set video colors %s=%d \n", item, value); + if (vf) { + int ret = vf->control(vf, VFCTRL_SET_EQUALIZER, &data); + if (ret == CONTROL_TRUE) + return 1; + } + mp_tmsg(MSGT_DECVIDEO, MSGL_V, "Video attribute '%s' is not supported by selected vo.\n", + item); + return 0; +} + +int get_video_colors(sh_video_t *sh_video, const char *item, int *value) +{ + vf_instance_t *vf = sh_video->vfilter; + vf_equalizer_t data; + + data.item = item; + + mp_dbg(MSGT_DECVIDEO, MSGL_V, "get video colors %s \n", item); + if (vf) { + int ret = vf->control(vf, VFCTRL_GET_EQUALIZER, &data); + if (ret == CONTROL_TRUE) { + *value = data.value; + return 1; + } + } + return 0; +} + +void get_detected_video_colorspace(struct sh_video *sh, struct mp_csp_details *csp) +{ + struct MPOpts *opts = sh->opts; + struct vf_instance *vf = sh->vfilter; + + csp->format = opts->requested_colorspace; + csp->levels_in = opts->requested_input_range; + csp->levels_out = opts->requested_output_range; + + if (csp->format == MP_CSP_AUTO) + csp->format = sh->colorspace; + if (csp->format == MP_CSP_AUTO) + csp->format = mp_csp_guess_colorspace(vf->w, vf->h); + + if (csp->levels_in == MP_CSP_LEVELS_AUTO) + csp->levels_in = sh->color_range; + if (csp->levels_in == MP_CSP_LEVELS_AUTO) + csp->levels_in = MP_CSP_LEVELS_TV; + + if (csp->levels_out == MP_CSP_LEVELS_AUTO) + csp->levels_out = MP_CSP_LEVELS_PC; +} + +void set_video_colorspace(struct sh_video *sh) +{ + struct vf_instance *vf = sh->vfilter; + + struct mp_csp_details requested; + get_detected_video_colorspace(sh, &requested); + vf->control(vf, VFCTRL_SET_YUV_COLORSPACE, &requested); + + struct mp_csp_details actual = MP_CSP_DETAILS_DEFAULTS; + vf->control(vf, VFCTRL_GET_YUV_COLORSPACE, &actual); + + int success = actual.format == requested.format + && actual.levels_in == requested.levels_in + && actual.levels_out == requested.levels_out; + + if (!success) + mp_tmsg(MSGT_DECVIDEO, MSGL_WARN, + "Colorspace details not fully supported by selected vo.\n"); + + if (actual.format != requested.format + && requested.format == MP_CSP_SMPTE_240M) { + // BT.709 is pretty close, much better than BT.601 + requested.format = MP_CSP_BT_709; + vf->control(vf, VFCTRL_SET_YUV_COLORSPACE, &requested); + } + +} + +void resync_video_stream(sh_video_t *sh_video) +{ + const struct vd_functions *vd = sh_video->vd_driver; + if (vd) + vd->control(sh_video, VDCTRL_RESYNC_STREAM, NULL); + sh_video->prev_codec_reordered_pts = MP_NOPTS_VALUE; + sh_video->prev_sorted_pts = MP_NOPTS_VALUE; +} + +void video_reset_aspect(struct sh_video *sh_video) +{ + sh_video->vd_driver->control(sh_video, VDCTRL_RESET_ASPECT, NULL); +} + +int get_current_video_decoder_lag(sh_video_t *sh_video) +{ + const struct vd_functions *vd = sh_video->vd_driver; + if (!vd) + return -1; + int ret = vd->control(sh_video, VDCTRL_QUERY_UNSEEN_FRAMES, NULL); + if (ret >= 10) + return ret - 10; + return -1; +} + +void uninit_video(sh_video_t *sh_video) +{ + if (!sh_video->initialized) + return; + mp_tmsg(MSGT_DECVIDEO, MSGL_V, "Uninit video: %s\n", sh_video->codec->drv); + sh_video->vd_driver->uninit(sh_video); + vf_uninit_filter_chain(sh_video->vfilter); + sh_video->initialized = 0; +} + +void vfm_help(void) +{ + int i; + mp_tmsg(MSGT_DECVIDEO, MSGL_INFO, "Available (compiled-in) video codec families/drivers:\n"); + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_VIDEO_DRIVERS\n"); + mp_msg(MSGT_DECVIDEO, MSGL_INFO, " vfm: info: (comment)\n"); + for (i = 0; mpcodecs_vd_drivers[i] != NULL; i++) + mp_msg(MSGT_DECVIDEO, MSGL_INFO, "%8s %s (%s)\n", + mpcodecs_vd_drivers[i]->info->short_name, + mpcodecs_vd_drivers[i]->info->name, + mpcodecs_vd_drivers[i]->info->comment); +} + +static int init_video(sh_video_t *sh_video, char *codecname, char *vfm, + int status, stringset_t *selected) +{ + int force = 0; + unsigned int orig_fourcc = + sh_video->bih ? sh_video->bih->biCompression : 0; + sh_video->codec = NULL; + sh_video->vf_initialized = 0; + if (codecname && codecname[0] == '+') { + codecname = &codecname[1]; + force = 1; + } + + while (1) { + int i; + int orig_w, orig_h; + // restore original fourcc: + if (sh_video->bih) + sh_video->bih->biCompression = orig_fourcc; + if (! + (sh_video->codec = + find_video_codec(sh_video->format, + sh_video->bih ? ((unsigned int *) &sh_video-> + bih->biCompression) : NULL, + sh_video->codec, force))) + break; + // ok we found one codec + if (stringset_test(selected, sh_video->codec->name)) + continue; // already tried & failed + if (codecname && strcmp(sh_video->codec->name, codecname)) + continue; // -vc + if (vfm && strcmp(sh_video->codec->drv, vfm)) + continue; // vfm doesn't match + if (!force && sh_video->codec->status < status) + continue; // too unstable + stringset_add(selected, sh_video->codec->name); // tagging it + // ok, it matches all rules, let's find the driver! + for (i = 0; mpcodecs_vd_drivers[i] != NULL; i++) + if (!strcmp(mpcodecs_vd_drivers[i]->info->short_name, + sh_video->codec->drv)) + break; + sh_video->vd_driver = mpcodecs_vd_drivers[i]; + if (!sh_video->vd_driver) { // driver not available (==compiled in) + mp_tmsg(MSGT_DECVIDEO, MSGL_WARN, + _("Requested video codec family [%s] (vfm=%s) not available.\nEnable it at compilation.\n"), + sh_video->codec->name, sh_video->codec->drv); + continue; + } + orig_w = sh_video->bih ? sh_video->bih->biWidth : sh_video->disp_w; + orig_h = sh_video->bih ? sh_video->bih->biHeight : sh_video->disp_h; + sh_video->disp_w = orig_w; + sh_video->disp_h = orig_h; + // it's available, let's try to init! + if (sh_video->codec->flags & CODECS_FLAG_ALIGN16) { + // align width/height to n*16 + sh_video->disp_w = (sh_video->disp_w + 15) & (~15); + sh_video->disp_h = (sh_video->disp_h + 15) & (~15); + } + if (sh_video->bih) { + sh_video->bih->biWidth = sh_video->disp_w; + sh_video->bih->biHeight = sh_video->disp_h; + } + + // init() + const struct vd_functions *vd = sh_video->vd_driver; + mp_tmsg(MSGT_DECVIDEO, MSGL_V, "Opening video decoder: [%s] %s\n", + vd->info->short_name, vd->info->name); + // clear vf init error, it is no longer relevant + if (sh_video->vf_initialized < 0) + sh_video->vf_initialized = 0; + if (!vd->init(sh_video)) { + mp_tmsg(MSGT_DECVIDEO, MSGL_INFO, "Video decoder init failed for " + "codecs.conf entry \"%s\".\n", sh_video->codec->name); + sh_video->disp_w = orig_w; + sh_video->disp_h = orig_h; + if (sh_video->bih) { + sh_video->bih->biWidth = sh_video->disp_w; + sh_video->bih->biHeight = sh_video->disp_h; + } + continue; // try next... + } + // Yeah! We got it! + sh_video->initialized = 1; + sh_video->prev_codec_reordered_pts = MP_NOPTS_VALUE; + sh_video->prev_sorted_pts = MP_NOPTS_VALUE; + return 1; + } + return 0; +} + +int init_best_video_codec(sh_video_t *sh_video, char **video_codec_list, + char **video_fm_list) +{ + char *vc_l_default[2] = { "", (char *) NULL }; + stringset_t selected; + // hack: + if (!video_codec_list) + video_codec_list = vc_l_default; + // Go through the codec.conf and find the best codec... + sh_video->initialized = 0; + stringset_init(&selected); + while (!sh_video->initialized && *video_codec_list) { + char *video_codec = *(video_codec_list++); + if (video_codec[0]) { + if (video_codec[0] == '-') { + // disable this codec: + stringset_add(&selected, video_codec + 1); + } else { + // forced codec by name: + mp_tmsg(MSGT_DECVIDEO, MSGL_INFO, "Forced video codec: %s\n", + video_codec); + init_video(sh_video, video_codec, NULL, -1, &selected); + } + } else { + int status; + // try in stability order: UNTESTED, WORKING, BUGGY. never try CRASHING. + if (video_fm_list) { + char **fmlist = video_fm_list; + // try first the preferred codec families: + while (!sh_video->initialized && *fmlist) { + char *video_fm = *(fmlist++); + mp_tmsg(MSGT_DECVIDEO, MSGL_INFO, "Trying to force video codec driver family %s...\n", + video_fm); + for (status = CODECS_STATUS__MAX; + status >= CODECS_STATUS__MIN; --status) + if (init_video + (sh_video, NULL, video_fm, status, &selected)) + break; + } + } + if (!sh_video->initialized) + for (status = CODECS_STATUS__MAX; status >= CODECS_STATUS__MIN; + --status) + if (init_video(sh_video, NULL, NULL, status, &selected)) + break; + } + } + stringset_free(&selected); + + if (!sh_video->initialized) { + mp_tmsg(MSGT_DECVIDEO, MSGL_ERR, "Cannot find codec matching selected -vo and video format 0x%X.\n", + sh_video->format); + return 0; // failed + } + + mp_tmsg(MSGT_DECVIDEO, MSGL_INFO, "Selected video codec: %s [%s]\n", + sh_video->codecname ? sh_video->codecname : sh_video->codec->info, + sh_video->vd_driver->info->print_name ? + sh_video->vd_driver->info->print_name : + sh_video->vd_driver->info->short_name); + mp_tmsg(MSGT_DECVIDEO, MSGL_V, + "Video codecs.conf entry: %s (%s) vfm: %s\n", + sh_video->codec->name, sh_video->codec->info, sh_video->codec->drv); + return 1; // success +} + +void *decode_video(sh_video_t *sh_video, struct demux_packet *packet, + unsigned char *start, int in_size, + int drop_frame, double pts) +{ + mp_image_t *mpi = NULL; + struct MPOpts *opts = sh_video->opts; + + if (opts->correct_pts && pts != MP_NOPTS_VALUE) { + int delay = get_current_video_decoder_lag(sh_video); + if (delay >= 0) { + if (delay > sh_video->num_buffered_pts) +#if 0 + // this is disabled because vd_ffmpeg reports the same lag + // after seek even when there are no buffered frames, + // leading to incorrect error messages + mp_msg(MSGT_DECVIDEO, MSGL_ERR, "Not enough buffered pts\n"); +#else + ; +#endif + else + sh_video->num_buffered_pts = delay; + } + if (sh_video->num_buffered_pts == + sizeof(sh_video->buffered_pts) / sizeof(double)) + mp_msg(MSGT_DECVIDEO, MSGL_ERR, "Too many buffered pts\n"); + else { + int i, j; + for (i = 0; i < sh_video->num_buffered_pts; i++) + if (sh_video->buffered_pts[i] < pts) + break; + for (j = sh_video->num_buffered_pts; j > i; j--) + sh_video->buffered_pts[j] = sh_video->buffered_pts[j - 1]; + sh_video->buffered_pts[i] = pts; + sh_video->num_buffered_pts++; + } + } + + mpi = sh_video->vd_driver->decode(sh_video, packet, start, in_size, + drop_frame, &pts); + + //------------------------ frame decoded. -------------------- + +#if HAVE_MMX + // some codecs are broken, and doesn't restore MMX state :( + // it happens usually with broken/damaged files. + if (gCpuCaps.hasMMX) { + __asm__ volatile("emms\n\t":::"memory"); + } +#endif + + if (!mpi || drop_frame) + return NULL; // error / skipped frame + + if (field_dominance == 0) + mpi->fields |= MP_IMGFIELD_TOP_FIRST; + else if (field_dominance == 1) + mpi->fields &= ~MP_IMGFIELD_TOP_FIRST; + + double prevpts = sh_video->codec_reordered_pts; + sh_video->prev_codec_reordered_pts = prevpts; + sh_video->codec_reordered_pts = pts; + if (prevpts != MP_NOPTS_VALUE && pts <= prevpts + || pts == MP_NOPTS_VALUE) + sh_video->num_reordered_pts_problems++; + prevpts = sh_video->sorted_pts; + if (opts->correct_pts) { + if (sh_video->num_buffered_pts) { + sh_video->num_buffered_pts--; + sh_video->sorted_pts = + sh_video->buffered_pts[sh_video->num_buffered_pts]; + } else { + mp_msg(MSGT_CPLAYER, MSGL_ERR, + "No pts value from demuxer to use for frame!\n"); + sh_video->sorted_pts = MP_NOPTS_VALUE; + } + } + pts = sh_video->sorted_pts; + if (prevpts != MP_NOPTS_VALUE && pts <= prevpts + || pts == MP_NOPTS_VALUE) + sh_video->num_sorted_pts_problems++; + return mpi; +} + +int filter_video(sh_video_t *sh_video, void *frame, double pts) +{ + mp_image_t *mpi = frame; + vf_instance_t *vf = sh_video->vfilter; + // apply video filters and call the leaf vo/ve + return vf->put_image(vf, mpi, pts); +} diff --git a/video/decode/dec_video.h b/video/decode/dec_video.h new file mode 100644 index 0000000000..f871198988 --- /dev/null +++ b/video/decode/dec_video.h @@ -0,0 +1,51 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_DEC_VIDEO_H +#define MPLAYER_DEC_VIDEO_H + +#include "libmpdemux/stheader.h" + +struct osd_state; + +// dec_video.c: +void vfm_help(void); + +int init_best_video_codec(sh_video_t *sh_video, char** video_codec_list, char** video_fm_list); +void uninit_video(sh_video_t *sh_video); + +struct demux_packet; +void *decode_video(sh_video_t *sh_video, struct demux_packet *packet, + unsigned char *start, int in_size, int drop_frame, + double pts); +int filter_video(sh_video_t *sh_video, void *frame, double pts); + +int get_video_quality_max(sh_video_t *sh_video); + +int get_video_colors(sh_video_t *sh_video, const char *item, int *value); +int set_video_colors(sh_video_t *sh_video, const char *item, int value); +struct mp_csp_details; +void get_detected_video_colorspace(struct sh_video *sh, struct mp_csp_details *csp); +void set_video_colorspace(struct sh_video *sh); +void resync_video_stream(sh_video_t *sh_video); +void video_reset_aspect(struct sh_video *sh_video); +int get_current_video_decoder_lag(sh_video_t *sh_video); + +extern int divx_quality; + +#endif /* MPLAYER_DEC_VIDEO_H */ diff --git a/video/decode/vd.c b/video/decode/vd.c new file mode 100644 index 0000000000..3bfc17c895 --- /dev/null +++ b/video/decode/vd.c @@ -0,0 +1,273 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "config.h" +#include "mp_msg.h" +#include "options.h" + +#include "codec-cfg.h" + +#include "img_format.h" + +#include "stream/stream.h" +#include "libmpdemux/demuxer.h" +#include "libmpdemux/stheader.h" +#include "dec_video.h" + +#include "vd.h" +#include "vf.h" +#include "libvo/video_out.h" + +extern const vd_functions_t mpcodecs_vd_ffmpeg; + +/* Please do not add any new decoders here. If you want to implement a new + * decoder, add it to libavcodec, except for wrappers around external + * libraries and decoders requiring binary support. */ + +const vd_functions_t * const mpcodecs_vd_drivers[] = { + &mpcodecs_vd_ffmpeg, + /* Please do not add any new decoders here. If you want to implement a new + * decoder, add it to libavcodec, except for wrappers around external + * libraries and decoders requiring binary support. */ + NULL +}; + +int mpcodecs_config_vo(sh_video_t *sh, int w, int h, + const unsigned int *outfmts, + unsigned int preferred_outfmt) +{ + struct MPOpts *opts = sh->opts; + int j; + unsigned int out_fmt = 0; + int screen_size_x = 0; + int screen_size_y = 0; + vf_instance_t *vf = sh->vfilter; + int vocfg_flags = 0; + + if (w) + sh->disp_w = w; + if (h) + sh->disp_h = h; + + mp_msg(MSGT_DECVIDEO, MSGL_V, + "VIDEO: %dx%d %5.3f fps %5.1f kbps (%4.1f kB/s)\n", + sh->disp_w, sh->disp_h, sh->fps, sh->i_bps * 0.008, + sh->i_bps / 1000.0); + + if (!sh->disp_w || !sh->disp_h) + return 0; + + mp_msg(MSGT_DECVIDEO, MSGL_V, + "VDec: vo config request - %d x %d (preferred colorspace: %s)\n", + w, h, vo_format_name(preferred_outfmt)); + + if (get_video_quality_max(sh) <= 0 && divx_quality) { + // user wants postprocess but no pp filter yet: + sh->vfilter = vf = vf_open_filter(opts, vf, "pp", NULL); + } + + if (!outfmts || sh->codec->outfmt[0] != 0xffffffff) + outfmts = sh->codec->outfmt; + + // check if libvo and codec has common outfmt (no conversion): + csp_again: + + if (mp_msg_test(MSGT_DECVIDEO, MSGL_V)) { + mp_msg(MSGT_DECVIDEO, MSGL_V, "Trying filter chain:"); + for (vf_instance_t *f = vf; f; f = f->next) + mp_msg(MSGT_DECVIDEO, MSGL_V, " %s", f->info->name); + mp_msg(MSGT_DECVIDEO, MSGL_V, "\n"); + } + + j = -1; + for (int i = 0; i < CODECS_MAX_OUTFMT; i++) { + int flags; + out_fmt = outfmts[i]; + if (out_fmt == (unsigned int) 0xFFFFFFFF) + break; + flags = vf->query_format(vf, out_fmt); + mp_msg(MSGT_CPLAYER, MSGL_DBG2, + "vo_debug: query(%s) returned 0x%X (i=%d) \n", + vo_format_name(out_fmt), flags, i); + if ((flags & VFCAP_CSP_SUPPORTED_BY_HW) + || (flags & VFCAP_CSP_SUPPORTED && j < 0)) { + // check (query) if codec really support this outfmt... + sh->outfmtidx = j; // pass index to the control() function this way + if (sh->vd_driver->control(sh, VDCTRL_QUERY_FORMAT, &out_fmt) == + CONTROL_FALSE) { + mp_msg(MSGT_CPLAYER, MSGL_DBG2, + "vo_debug: codec query_format(%s) returned FALSE\n", + vo_format_name(out_fmt)); + continue; + } + j = i; + sh->output_flags = flags; + if (flags & VFCAP_CSP_SUPPORTED_BY_HW) + break; + } + } + if (j < 0) { + // TODO: no match - we should use conversion... + if (strcmp(vf->info->name, "scale")) { + mp_tmsg(MSGT_DECVIDEO, MSGL_INFO, "Could not find matching colorspace - retrying with -vf scale...\n"); + vf = vf_open_filter(opts, vf, "scale", NULL); + goto csp_again; + } + mp_tmsg(MSGT_CPLAYER, MSGL_WARN, + "The selected video_out device is incompatible with this codec.\n"\ + "Try appending the scale filter to your filter list,\n"\ + "e.g. -vf spp,scale instead of -vf spp.\n"); + sh->vf_initialized = -1; + return 0; // failed + } + out_fmt = outfmts[j]; + sh->outfmt = out_fmt; + mp_msg(MSGT_CPLAYER, MSGL_V, "VDec: using %s as output csp (no %d)\n", + vo_format_name(out_fmt), j); + sh->outfmtidx = j; + sh->vfilter = vf; + + // autodetect flipping + if (opts->flip == -1) { + opts->flip = 0; + if (sh->codec->outflags[j] & CODECS_FLAG_FLIP) + if (!(sh->codec->outflags[j] & CODECS_FLAG_NOFLIP)) + opts->flip = 1; + } + if (opts->flip && !(sh->output_flags & VFCAP_FLIP)) { + // we need to flip, but no flipping filter avail. + vf_add_before_vo(&vf, "flip", NULL); + sh->vfilter = vf; + } + // time to do aspect ratio corrections... + + if (opts->movie_aspect > -1.0) + sh->aspect = opts->movie_aspect; // cmdline overrides autodetect + else if (sh->stream_aspect != 0.0) + sh->aspect = sh->stream_aspect; + + if (opts->screen_size_x || opts->screen_size_y) { + screen_size_x = opts->screen_size_x; + screen_size_y = opts->screen_size_y; + if (!opts->vidmode) { + if (!screen_size_x) + screen_size_x = 1; + if (!screen_size_y) + screen_size_y = 1; + if (screen_size_x <= 8) + screen_size_x *= sh->disp_w; + if (screen_size_y <= 8) + screen_size_y *= sh->disp_h; + } + } else { + // check source format aspect, calculate prescale ::atmos + screen_size_x = sh->disp_w; + screen_size_y = sh->disp_h; + if (opts->screen_size_xy >= 0.001) { + if (opts->screen_size_xy <= 8) { + // -xy means x+y scale + screen_size_x *= opts->screen_size_xy; + screen_size_y *= opts->screen_size_xy; + } else { + // -xy means forced width while keeping correct aspect + screen_size_x = opts->screen_size_xy; + screen_size_y = opts->screen_size_xy * sh->disp_h / sh->disp_w; + } + } + if (sh->aspect > 0.01) { + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_VIDEO_ASPECT=%1.4f\n", + sh->aspect); + int w = screen_size_y * sh->aspect; + int h = screen_size_y; + // we don't like horizontal downscale || user forced width: + if (w < screen_size_x || opts->screen_size_xy > 8) { + w = screen_size_x; + h = screen_size_x / sh->aspect; + } + if (abs(screen_size_x - w) >= 4 || abs(screen_size_y - h) >= 4) { + screen_size_x = w; + screen_size_y = h; + mp_tmsg(MSGT_CPLAYER, MSGL_V, "Aspect ratio is %.2f:1 - " + "scaling to correct movie aspect.\n", sh->aspect); + } + } else { + mp_tmsg(MSGT_CPLAYER, MSGL_V, "Movie-Aspect is undefined - no prescaling applied.\n"); + } + } + + vocfg_flags = (opts->fullscreen ? VOFLAG_FULLSCREEN : 0) + | (opts->vidmode ? VOFLAG_MODESWITCHING : 0) + | (opts->softzoom ? VOFLAG_SWSCALE : 0) + | (opts->flip ? VOFLAG_FLIPPING : 0); + + // Time to config libvo! + mp_msg(MSGT_CPLAYER, MSGL_V, + "VO Config (%dx%d->%dx%d,flags=%d,0x%X)\n", sh->disp_w, + sh->disp_h, screen_size_x, screen_size_y, vocfg_flags, out_fmt); + + vf->w = sh->disp_w; + vf->h = sh->disp_h; + + if (vf_config_wrapper + (vf, sh->disp_w, sh->disp_h, screen_size_x, screen_size_y, vocfg_flags, + out_fmt) == 0) { + mp_tmsg(MSGT_CPLAYER, MSGL_WARN, "FATAL: Cannot initialize video driver.\n"); + sh->vf_initialized = -1; + return 0; + } + + sh->vf_initialized = 1; + + set_video_colorspace(sh); + + if (opts->vo_gamma_gamma != 1000) + set_video_colors(sh, "gamma", opts->vo_gamma_gamma); + if (opts->vo_gamma_brightness != 1000) + set_video_colors(sh, "brightness", opts->vo_gamma_brightness); + if (opts->vo_gamma_contrast != 1000) + set_video_colors(sh, "contrast", opts->vo_gamma_contrast); + if (opts->vo_gamma_saturation != 1000) + set_video_colors(sh, "saturation", opts->vo_gamma_saturation); + if (opts->vo_gamma_hue != 1000) + set_video_colors(sh, "hue", opts->vo_gamma_hue); + + return 1; +} + +// mp_imgtype: buffering type, see mp_image.h +// mp_imgflag: buffer requirements (read/write, preserve, stride limits), see mp_image.h +// returns NULL or allocated mp_image_t* +// Note: buffer allocation may be moved to mpcodecs_config_vo() later... +mp_image_t *mpcodecs_get_image(sh_video_t *sh, int mp_imgtype, int mp_imgflag, + int w, int h) +{ + return vf_get_image(sh->vfilter, sh->outfmt, mp_imgtype, mp_imgflag, w, h); +} + +void mpcodecs_draw_slice(sh_video_t *sh, unsigned char **src, int *stride, + int w, int h, int x, int y) +{ + struct vf_instance *vf = sh->vfilter; + + if (vf->draw_slice) + vf->draw_slice(vf, src, stride, w, h, x, y); +} diff --git a/video/decode/vd.h b/video/decode/vd.h new file mode 100644 index 0000000000..6b9803a611 --- /dev/null +++ b/video/decode/vd.h @@ -0,0 +1,60 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_VD_H +#define MPLAYER_VD_H + +#include "mp_image.h" +#include "mpc_info.h" +#include "libmpdemux/stheader.h" + +typedef struct mp_codec_info vd_info_t; + +struct demux_packet; + +/* interface of video decoder drivers */ +typedef struct vd_functions +{ + const vd_info_t *info; + int (*init)(sh_video_t *sh); + void (*uninit)(sh_video_t *sh); + int (*control)(sh_video_t *sh, int cmd, void *arg); + struct mp_image *(*decode)(struct sh_video *sh, struct demux_packet *pkt, + void *data, int len, int flags, + double *reordered_pts); +} vd_functions_t; + +// NULL terminated array of all drivers +extern const vd_functions_t *const mpcodecs_vd_drivers[]; + +#define VDCTRL_QUERY_FORMAT 3 // test for availabilty of a format +#define VDCTRL_RESYNC_STREAM 8 // reset decode state after seeking +#define VDCTRL_QUERY_UNSEEN_FRAMES 9 // current decoder lag +#define VDCTRL_RESET_ASPECT 10 // reinit filter/VO chain for new aspect ratio + +// callbacks: +int mpcodecs_config_vo(sh_video_t *sh, int w, int h, + const unsigned int *outfmts, + unsigned int preferred_outfmt); + +mp_image_t *mpcodecs_get_image(sh_video_t *sh, int mp_imgtype, int mp_imgflag, + int w, int h); +void mpcodecs_draw_slice(sh_video_t *sh, unsigned char **src, int *stride, + int w, int h, int x, int y); + +#endif /* MPLAYER_VD_H */ diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c new file mode 100644 index 0000000000..e078de4419 --- /dev/null +++ b/video/decode/vd_lavc.c @@ -0,0 +1,857 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <time.h> +#include <stdbool.h> +#include <sys/types.h> + +#include <libavutil/common.h> +#include <libavutil/opt.h> +#include <libavutil/intreadwrite.h> + +#include "talloc.h" +#include "config.h" +#include "mp_msg.h" +#include "options.h" +#include "av_opts.h" + +#include "mpbswap.h" +#include "fmt-conversion.h" + +#include "vd.h" +#include "img_format.h" +#include "libmpdemux/stheader.h" +#include "libmpdemux/demux_packet.h" +#include "codec-cfg.h" +#include "osdep/numcores.h" +#include "libvo/csputils.h" + +static const vd_info_t info = { + "libavcodec video codecs", + "ffmpeg", + "", + "", + "native codecs", + .print_name = "libavcodec", +}; + +#include "libavcodec/avcodec.h" + +#if AVPALETTE_SIZE > 1024 +#error palette too large, adapt libmpcodecs/vf.c:vf_get_image +#endif + +typedef struct { + AVCodecContext *avctx; + AVFrame *pic; + enum PixelFormat pix_fmt; + int do_slices; + int do_dr1; + int vo_initialized; + int best_csp; + int qp_stat[32]; + double qp_sum; + double inv_qp_sum; + int ip_count; + int b_count; + AVRational last_sample_aspect_ratio; + enum AVDiscard skip_frame; +} vd_ffmpeg_ctx; + +#include "m_option.h" + +static int get_buffer(AVCodecContext *avctx, AVFrame *pic); +static void release_buffer(AVCodecContext *avctx, AVFrame *pic); +static void draw_slice(struct AVCodecContext *s, const AVFrame *src, + int offset[4], int y, int type, int height); + +static enum PixelFormat get_format(struct AVCodecContext *avctx, + const enum PixelFormat *pix_fmt); +static void uninit(struct sh_video *sh); + +const m_option_t lavc_decode_opts_conf[] = { + OPT_INTRANGE("bug", lavc_param.workaround_bugs, 0, -1, 999999), + OPT_FLAG_ON("gray", lavc_param.gray, 0), + OPT_INTRANGE("idct", lavc_param.idct_algo, 0, 0, 99), + OPT_INTRANGE("ec", lavc_param.error_concealment, 0, 0, 99), + OPT_FLAG_ON("vstats", lavc_param.vstats, 0), + OPT_INTRANGE("debug", lavc_param.debug, 0, 0, 9999999), + OPT_INTRANGE("vismv", lavc_param.vismv, 0, 0, 9999999), + OPT_INTRANGE("st", lavc_param.skip_top, 0, 0, 999), + OPT_INTRANGE("sb", lavc_param.skip_bottom, 0, 0, 999), + OPT_FLAG_CONSTANTS("fast", lavc_param.fast, 0, 0, CODEC_FLAG2_FAST), + OPT_STRING("lowres", lavc_param.lowres_str, 0), + OPT_STRING("skiploopfilter", lavc_param.skip_loop_filter_str, 0), + OPT_STRING("skipidct", lavc_param.skip_idct_str, 0), + OPT_STRING("skipframe", lavc_param.skip_frame_str, 0), + OPT_INTRANGE("threads", lavc_param.threads, 0, 0, 16), + OPT_FLAG_CONSTANTS("bitexact", lavc_param.bitexact, 0, 0, CODEC_FLAG_BITEXACT), + OPT_STRING("o", lavc_param.avopt, 0), + {NULL, NULL, 0, 0, 0, 0, NULL} +}; + +static enum AVDiscard str2AVDiscard(char *str) +{ + if (!str) return AVDISCARD_DEFAULT; + if (strcasecmp(str, "none" ) == 0) return AVDISCARD_NONE; + if (strcasecmp(str, "default") == 0) return AVDISCARD_DEFAULT; + if (strcasecmp(str, "nonref" ) == 0) return AVDISCARD_NONREF; + if (strcasecmp(str, "bidir" ) == 0) return AVDISCARD_BIDIR; + if (strcasecmp(str, "nonkey" ) == 0) return AVDISCARD_NONKEY; + if (strcasecmp(str, "all" ) == 0) return AVDISCARD_ALL; + mp_msg(MSGT_DECVIDEO, MSGL_ERR, "Unknown discard value %s\n", str); + return AVDISCARD_DEFAULT; +} + +static int init(sh_video_t *sh) +{ + struct lavc_param *lavc_param = &sh->opts->lavc_param; + AVCodecContext *avctx; + vd_ffmpeg_ctx *ctx; + AVCodec *lavc_codec = NULL; + enum PixelFormat rawfmt = PIX_FMT_NONE; + int do_vis_debug = lavc_param->vismv || + (lavc_param->debug & (FF_DEBUG_VIS_MB_TYPE | FF_DEBUG_VIS_QP)); + + ctx = sh->context = talloc_zero(NULL, vd_ffmpeg_ctx); + + if (sh->codec->dll) { + lavc_codec = avcodec_find_decoder_by_name(sh->codec->dll); + if (!lavc_codec) { + mp_tmsg(MSGT_DECVIDEO, MSGL_ERR, + "Cannot find codec '%s' in libavcodec...\n", + sh->codec->dll); + uninit(sh); + return 0; + } + } else if (sh->libav_codec_id) { + lavc_codec = avcodec_find_decoder(sh->libav_codec_id); + if (!lavc_codec) { + mp_tmsg(MSGT_DECVIDEO, MSGL_INFO, "Libavcodec has no decoder " + "for this codec\n"); + uninit(sh); + return 0; + } + } else if (!IMGFMT_IS_HWACCEL(sh->format)) { + rawfmt = imgfmt2pixfmt(sh->format); + if (rawfmt != PIX_FMT_NONE) + lavc_codec = avcodec_find_decoder_by_name("rawvideo"); + } + if (!lavc_codec) { + uninit(sh); + return 0; + } + + sh->codecname = lavc_codec->long_name; + if (!sh->codecname) + sh->codecname = lavc_codec->name; + + if (sh->opts->vd_use_slices + && (lavc_codec->capabilities & CODEC_CAP_DRAW_HORIZ_BAND) + && !do_vis_debug) + ctx->do_slices = 1; + + if (lavc_codec->capabilities & CODEC_CAP_DR1 && !do_vis_debug + && lavc_codec->id != CODEC_ID_H264 + && lavc_codec->id != CODEC_ID_INTERPLAY_VIDEO + && lavc_codec->id != CODEC_ID_ROQ && lavc_codec->id != CODEC_ID_VP8 + && lavc_codec->id != CODEC_ID_LAGARITH) + ctx->do_dr1 = sh->opts->vd_use_dr1; + ctx->ip_count = ctx->b_count = 0; + + ctx->pic = avcodec_alloc_frame(); + ctx->avctx = avcodec_alloc_context3(lavc_codec); + avctx = ctx->avctx; + avctx->opaque = sh; + avctx->codec_type = AVMEDIA_TYPE_VIDEO; + avctx->codec_id = lavc_codec->id; + + if (lavc_codec->capabilities & CODEC_CAP_HWACCEL // XvMC + || lavc_codec->capabilities & CODEC_CAP_HWACCEL_VDPAU) { + ctx->do_dr1 = true; + ctx->do_slices = true; + lavc_param->threads = 1; + avctx->get_format = get_format; + avctx->get_buffer = get_buffer; + avctx->release_buffer = release_buffer; + avctx->reget_buffer = get_buffer; + avctx->draw_horiz_band = draw_slice; + if (lavc_codec->capabilities & CODEC_CAP_HWACCEL_VDPAU) + mp_msg(MSGT_DECVIDEO, MSGL_V, "[VD_FFMPEG] VDPAU hardware " + "decoding.\n"); + avctx->slice_flags = SLICE_FLAG_CODED_ORDER | SLICE_FLAG_ALLOW_FIELD; + } + + if (lavc_param->threads == 0) { + int threads = default_thread_count(); + if (threads < 1) { + mp_msg(MSGT_DECVIDEO, MSGL_WARN, "[VD_FFMPEG] Could not determine " + "thread count to use, defaulting to 1.\n"); + threads = 1; + } + threads = FFMIN(threads, 16); + lavc_param->threads = threads; + } + /* Our get_buffer and draw_horiz_band callbacks are not safe to call + * from other threads. */ + if (lavc_param->threads > 1) { + ctx->do_dr1 = false; + ctx->do_slices = false; + mp_tmsg(MSGT_DECVIDEO, MSGL_V, "Asking decoder to use " + "%d threads if supported.\n", lavc_param->threads); + } + + if (ctx->do_dr1) { + avctx->flags |= CODEC_FLAG_EMU_EDGE; + avctx->get_buffer = get_buffer; + avctx->release_buffer = release_buffer; + avctx->reget_buffer = get_buffer; + } + + avctx->flags |= lavc_param->bitexact; + + avctx->coded_width = sh->disp_w; + avctx->coded_height = sh->disp_h; + avctx->workaround_bugs = lavc_param->workaround_bugs; + if (lavc_param->gray) + avctx->flags |= CODEC_FLAG_GRAY; + avctx->flags2 |= lavc_param->fast; + if (rawfmt == PIX_FMT_NONE) { + avctx->codec_tag = sh->format; + } else { + avctx->pix_fmt = rawfmt; + } + if (sh->gsh->lavf_codec_tag) + avctx->codec_tag = sh->gsh->lavf_codec_tag; + avctx->stream_codec_tag = sh->video.fccHandler; + avctx->idct_algo = lavc_param->idct_algo; + avctx->error_concealment = lavc_param->error_concealment; + avctx->debug = lavc_param->debug; + if (lavc_param->debug) + av_log_set_level(AV_LOG_DEBUG); + avctx->debug_mv = lavc_param->vismv; + avctx->skip_top = lavc_param->skip_top; + avctx->skip_bottom = lavc_param->skip_bottom; + if (lavc_param->lowres_str != NULL) { + int lowres, lowres_w; + sscanf(lavc_param->lowres_str, "%d,%d", &lowres, &lowres_w); + if (lowres < 1 || lowres > 16 || + lowres_w > 0 && avctx->width < lowres_w) + lowres = 0; + avctx->lowres = lowres; + } + avctx->skip_loop_filter = str2AVDiscard(lavc_param->skip_loop_filter_str); + avctx->skip_idct = str2AVDiscard(lavc_param->skip_idct_str); + avctx->skip_frame = str2AVDiscard(lavc_param->skip_frame_str); + + if (lavc_param->avopt) { + if (parse_avopts(avctx, lavc_param->avopt) < 0) { + mp_msg(MSGT_DECVIDEO, MSGL_ERR, + "Your options /%s/ look like gibberish to me pal\n", + lavc_param->avopt); + uninit(sh); + return 0; + } + } + + // Do this after the above avopt handling in case it changes values + ctx->skip_frame = avctx->skip_frame; + + mp_dbg(MSGT_DECVIDEO, MSGL_DBG2, + "libavcodec.size: %d x %d\n", avctx->width, avctx->height); + switch (sh->format) { + case mmioFOURCC('S','V','Q','3'): + case mmioFOURCC('A','V','R','n'): + case mmioFOURCC('M','J','P','G'): + /* AVRn stores huffman table in AVI header */ + /* Pegasus MJPEG stores it also in AVI header, but it uses the common + * MJPG fourcc :( */ + if (!sh->bih || sh->bih->biSize <= sizeof(*sh->bih)) + break; + av_opt_set_int(avctx, "extern_huff", 1, AV_OPT_SEARCH_CHILDREN); + avctx->extradata_size = sh->bih->biSize - sizeof(*sh->bih); + avctx->extradata = av_mallocz(avctx->extradata_size + + FF_INPUT_BUFFER_PADDING_SIZE); + memcpy(avctx->extradata, sh->bih + 1, avctx->extradata_size); + break; + + case mmioFOURCC('R','V','1','0'): + case mmioFOURCC('R','V','1','3'): + case mmioFOURCC('R','V','2','0'): + case mmioFOURCC('R','V','3','0'): + case mmioFOURCC('R','V','4','0'): + if (sh->bih->biSize < sizeof(*sh->bih) + 8) { + // only 1 packet per frame & sub_id from fourcc + avctx->extradata_size = 8; + avctx->extradata = av_mallocz(avctx->extradata_size + + FF_INPUT_BUFFER_PADDING_SIZE); + ((uint32_t *)avctx->extradata)[0] = 0; + ((uint32_t *)avctx->extradata)[1] = + sh->format == mmioFOURCC('R','V','1','3') ? + 0x10003001 : 0x10000000; + } else { + // has extra slice header (demux_rm or rm->avi streamcopy) + avctx->extradata_size = sh->bih->biSize - sizeof(*sh->bih); + avctx->extradata = av_mallocz(avctx->extradata_size + + FF_INPUT_BUFFER_PADDING_SIZE); + memcpy(avctx->extradata, sh->bih + 1, avctx->extradata_size); + } + break; + + default: + if (!sh->bih || sh->bih->biSize <= sizeof(*sh->bih)) + break; + avctx->extradata_size = sh->bih->biSize - sizeof(*sh->bih); + avctx->extradata = av_mallocz(avctx->extradata_size + + FF_INPUT_BUFFER_PADDING_SIZE); + memcpy(avctx->extradata, sh->bih + 1, avctx->extradata_size); + break; + } + + if (sh->bih) + avctx->bits_per_coded_sample = sh->bih->biBitCount; + + avctx->thread_count = lavc_param->threads; + + /* open it */ + if (avcodec_open2(avctx, lavc_codec, NULL) < 0) { + mp_tmsg(MSGT_DECVIDEO, MSGL_ERR, "Could not open codec.\n"); + uninit(sh); + return 0; + } + return 1; +} + +static void uninit(sh_video_t *sh) +{ + vd_ffmpeg_ctx *ctx = sh->context; + AVCodecContext *avctx = ctx->avctx; + + sh->codecname = NULL; + if (sh->opts->lavc_param.vstats && avctx->coded_frame) { + for (int i = 1; i < 32; i++) + mp_msg(MSGT_DECVIDEO, MSGL_INFO, + "QP: %d, count: %d\n", i, ctx->qp_stat[i]); + mp_tmsg(MSGT_DECVIDEO, MSGL_INFO, "[VD_FFMPEG] Arithmetic mean of QP: " + "%2.4f, Harmonic mean of QP: %2.4f\n", + ctx->qp_sum / avctx->coded_frame->coded_picture_number, + 1.0 / (ctx->inv_qp_sum / avctx->coded_frame->coded_picture_number)); + } + + if (avctx) { + if (avctx->codec && avcodec_close(avctx) < 0) + mp_tmsg(MSGT_DECVIDEO, MSGL_ERR, "Could not close codec.\n"); + + av_freep(&avctx->extradata); + av_freep(&avctx->slice_offset); + } + + av_freep(&avctx); + avcodec_free_frame(&ctx->pic); + talloc_free(ctx); +} + +static void draw_slice(struct AVCodecContext *s, + const AVFrame *src, int offset[4], + int y, int type, int height) +{ + sh_video_t *sh = s->opaque; + uint8_t *source[MP_MAX_PLANES] = { + src->data[0] + offset[0], src->data[1] + offset[1], + src->data[2] + offset[2] + }; + int strides[MP_MAX_PLANES] = { + src->linesize[0], src->linesize[1], src->linesize[2] + }; + if (height < 0) { + int i; + height = -height; + y -= height; + for (i = 0; i < MP_MAX_PLANES; i++) { + strides[i] = -strides[i]; + source[i] -= strides[i]; + } + } + if (y < sh->disp_h) { + height = FFMIN(height, sh->disp_h - y); + mpcodecs_draw_slice(sh, source, strides, sh->disp_w, height, 0, y); + } +} + + +static int init_vo(sh_video_t *sh, enum PixelFormat pix_fmt) +{ + vd_ffmpeg_ctx *ctx = sh->context; + AVCodecContext *avctx = ctx->avctx; + float aspect = av_q2d(avctx->sample_aspect_ratio) * + avctx->width / avctx->height; + int width, height; + + width = avctx->width; + height = avctx->height; + + /* Reconfiguring filter/VO chain may invalidate direct rendering buffers + * we have allocated for libavcodec (including the VDPAU HW decoding + * case). Is it guaranteed that the code below only triggers in a situation + * with no busy direct rendering buffers for reference frames? + */ + if (av_cmp_q(avctx->sample_aspect_ratio, ctx->last_sample_aspect_ratio) || + width != sh->disp_w || height != sh->disp_h || + pix_fmt != ctx->pix_fmt || !ctx->vo_initialized) { + ctx->vo_initialized = 0; + mp_msg(MSGT_DECVIDEO, MSGL_V, "[ffmpeg] aspect_ratio: %f\n", aspect); + + // Do not overwrite s->aspect on the first call, so that a container + // aspect if available is preferred. + // But set it even if the sample aspect did not change, since a + // resolution change can cause an aspect change even if the + // _sample_ aspect is unchanged. + if (sh->aspect == 0 || ctx->last_sample_aspect_ratio.den) + sh->aspect = aspect; + ctx->last_sample_aspect_ratio = avctx->sample_aspect_ratio; + sh->disp_w = width; + sh->disp_h = height; + ctx->pix_fmt = pix_fmt; + ctx->best_csp = pixfmt2imgfmt(pix_fmt); + const unsigned int *supported_fmts; + if (ctx->best_csp == IMGFMT_YV12) + supported_fmts = (const unsigned int[]){ + IMGFMT_YV12, IMGFMT_I420, IMGFMT_IYUV, 0xffffffff + }; + else if (ctx->best_csp == IMGFMT_422P) + supported_fmts = (const unsigned int[]){ + IMGFMT_422P, IMGFMT_YV12, IMGFMT_I420, IMGFMT_IYUV, 0xffffffff + }; + else + supported_fmts = (const unsigned int[]){ctx->best_csp, 0xffffffff}; + + sh->colorspace = avcol_spc_to_mp_csp(avctx->colorspace); + sh->color_range = avcol_range_to_mp_csp_levels(avctx->color_range); + + if (!mpcodecs_config_vo(sh, sh->disp_w, sh->disp_h, supported_fmts, + ctx->best_csp)) + return -1; + ctx->vo_initialized = 1; + } + return 0; +} + +static int get_buffer(AVCodecContext *avctx, AVFrame *pic) +{ + sh_video_t *sh = avctx->opaque; + vd_ffmpeg_ctx *ctx = sh->context; + mp_image_t *mpi = NULL; + int flags = MP_IMGFLAG_ACCEPT_ALIGNED_STRIDE | + MP_IMGFLAG_PREFER_ALIGNED_STRIDE; + int type = MP_IMGTYPE_IPB; + int width = avctx->width; + int height = avctx->height; + // special case to handle reget_buffer without buffer hints + if (pic->opaque && pic->data[0] && !pic->buffer_hints) + return 0; + avcodec_align_dimensions(avctx, &width, &height); + + if (pic->buffer_hints) { + mp_msg(MSGT_DECVIDEO, MSGL_DBG2, "Buffer hints: %u\n", + pic->buffer_hints); + type = MP_IMGTYPE_TEMP; + if (pic->buffer_hints & FF_BUFFER_HINTS_READABLE) + flags |= MP_IMGFLAG_READABLE; + if (pic->buffer_hints & FF_BUFFER_HINTS_PRESERVE) { + type = MP_IMGTYPE_STATIC; + flags |= MP_IMGFLAG_PRESERVE; + } + if (pic->buffer_hints & FF_BUFFER_HINTS_REUSABLE) { + type = MP_IMGTYPE_STATIC; + flags |= MP_IMGFLAG_PRESERVE; + } + flags |= ctx->do_slices ? MP_IMGFLAG_DRAW_CALLBACK : 0; + mp_msg(MSGT_DECVIDEO, MSGL_DBG2, + type == MP_IMGTYPE_STATIC ? "using STATIC\n" : "using TEMP\n"); + } else { + if (!pic->reference) { + ctx->b_count++; + flags |= ctx->do_slices ? MP_IMGFLAG_DRAW_CALLBACK : 0; + } else { + ctx->ip_count++; + flags |= MP_IMGFLAG_PRESERVE | MP_IMGFLAG_READABLE + | (ctx->do_slices ? MP_IMGFLAG_DRAW_CALLBACK : 0); + } + } + + if (init_vo(sh, avctx->pix_fmt) < 0) { + avctx->release_buffer = avcodec_default_release_buffer; + avctx->get_buffer = avcodec_default_get_buffer; + avctx->reget_buffer = avcodec_default_reget_buffer; + if (pic->data[0]) + release_buffer(avctx, pic); + return avctx->get_buffer(avctx, pic); + } + + if (IMGFMT_IS_HWACCEL(ctx->best_csp)) + type = MP_IMGTYPE_NUMBERED | (0xffff << 16); + else if (!pic->buffer_hints) { + if (ctx->b_count > 1 || ctx->ip_count > 2) { + mp_tmsg(MSGT_DECVIDEO, MSGL_WARN, "[VD_FFMPEG] DRI failure.\n"); + + ctx->do_dr1 = 0; //FIXME + avctx->get_buffer = avcodec_default_get_buffer; + avctx->reget_buffer = avcodec_default_reget_buffer; + if (pic->data[0]) + release_buffer(avctx, pic); + return avctx->get_buffer(avctx, pic); + } + + if (avctx->has_b_frames || ctx->b_count) + type = MP_IMGTYPE_IPB; + else + type = MP_IMGTYPE_IP; + mp_msg(MSGT_DECVIDEO, MSGL_DBG2, + type == MP_IMGTYPE_IPB ? "using IPB\n" : "using IP\n"); + } + + if (ctx->best_csp == IMGFMT_RGB8 || ctx->best_csp == IMGFMT_BGR8) + flags |= MP_IMGFLAG_RGB_PALETTE; + mpi = mpcodecs_get_image(sh, type, flags, width, height); + if (!mpi) + return -1; + + // ok, let's see what did we get: + if (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK && + !(mpi->flags & MP_IMGFLAG_DIRECT)) { + // nice, filter/vo likes draw_callback :) + avctx->draw_horiz_band = draw_slice; + } else + avctx->draw_horiz_band = NULL; + if (IMGFMT_IS_HWACCEL(mpi->imgfmt)) + avctx->draw_horiz_band = draw_slice; + + pic->data[0] = mpi->planes[0]; + pic->data[1] = mpi->planes[1]; + pic->data[2] = mpi->planes[2]; + pic->data[3] = mpi->planes[3]; + + /* Note: some (many) codecs in libavcodec require + * linesize[1] == linesize[2] and no changes between frames. + * Lavc will check that and die with an error message if it's not true. + */ + pic->linesize[0] = mpi->stride[0]; + pic->linesize[1] = mpi->stride[1]; + pic->linesize[2] = mpi->stride[2]; + pic->linesize[3] = mpi->stride[3]; + + pic->opaque = mpi; + + pic->type = FF_BUFFER_TYPE_USER; + + /* The libavcodec reordered_opaque functionality is implemented by + * a similar copy in avcodec_default_get_buffer() and without a + * workaround like this it'd stop working when a custom buffer + * callback is used. + */ + pic->reordered_opaque = avctx->reordered_opaque; + return 0; +} + +static void release_buffer(struct AVCodecContext *avctx, AVFrame *pic) +{ + mp_image_t *mpi = pic->opaque; + sh_video_t *sh = avctx->opaque; + vd_ffmpeg_ctx *ctx = sh->context; + + if (ctx->ip_count <= 2 && ctx->b_count <= 1) { + if (mpi->flags & MP_IMGFLAG_PRESERVE) + ctx->ip_count--; + else + ctx->b_count--; + } + + if (mpi) { + // release mpi (in case MPI_IMGTYPE_NUMBERED is used, e.g. for VDPAU) + mpi->usage_count--; + if (mpi->usage_count < 0) { + mp_msg(MSGT_DECVIDEO, MSGL_ERR, "Bad mp_image usage count, please report!\n"); + mpi->usage_count = 0; + } + } + + if (pic->type != FF_BUFFER_TYPE_USER) { + avcodec_default_release_buffer(avctx, pic); + return; + } + + for (int i = 0; i < 4; i++) + pic->data[i] = NULL; +} + +static av_unused void swap_palette(void *pal) +{ + int i; + uint32_t *p = pal; + for (i = 0; i < AVPALETTE_COUNT; i++) + p[i] = le2me_32(p[i]); +} + +static struct mp_image *decode(struct sh_video *sh, struct demux_packet *packet, + void *data, int len, int flags, + double *reordered_pts) +{ + int got_picture = 0; + int ret; + vd_ffmpeg_ctx *ctx = sh->context; + AVFrame *pic = ctx->pic; + AVCodecContext *avctx = ctx->avctx; + struct lavc_param *lavc_param = &sh->opts->lavc_param; + mp_image_t *mpi = NULL; + int dr1 = ctx->do_dr1; + AVPacket pkt; + + if (!dr1) + avctx->draw_horiz_band = NULL; + + if (flags & 2) + avctx->skip_frame = AVDISCARD_ALL; + else if (flags & 1) + avctx->skip_frame = AVDISCARD_NONREF; + else + avctx->skip_frame = ctx->skip_frame; + + av_init_packet(&pkt); + pkt.data = data; + pkt.size = len; + /* Some codecs (ZeroCodec, some cases of PNG) may want keyframe info + * from demuxer. */ + if (packet && packet->keyframe) + pkt.flags |= AV_PKT_FLAG_KEY; + if (packet && packet->avpacket) { + pkt.side_data = packet->avpacket->side_data; + pkt.side_data_elems = packet->avpacket->side_data_elems; + } + // The avcodec opaque field stupidly supports only int64_t type + union pts { int64_t i; double d; }; + avctx->reordered_opaque = (union pts){.d = *reordered_pts}.i; + ret = avcodec_decode_video2(avctx, pic, &got_picture, &pkt); + *reordered_pts = (union pts){.i = pic->reordered_opaque}.d; + + dr1 = ctx->do_dr1; + if (ret < 0) + mp_msg(MSGT_DECVIDEO, MSGL_WARN, "Error while decoding frame!\n"); + //-- vstats generation + while (lavc_param->vstats) { // always one time loop + static FILE *fvstats = NULL; + char filename[20]; + static long long int all_len = 0; + static int frame_number = 0; + static double all_frametime = 0.0; + AVFrame *pic = avctx->coded_frame; + double quality = 0.0; + + if (!pic) + break; + + if (!fvstats) { + time_t today2; + struct tm *today; + today2 = time(NULL); + today = localtime(&today2); + sprintf(filename, "vstats_%02d%02d%02d.log", today->tm_hour, + today->tm_min, today->tm_sec); + fvstats = fopen(filename, "w"); + if (!fvstats) { + perror("fopen"); + lavc_param->vstats = 0; // disable block + break; + /*exit(1);*/ + } + } + + // average MB quantizer + { + int x, y; + int w = ((avctx->width << avctx->lowres) + 15) >> 4; + int h = ((avctx->height << avctx->lowres) + 15) >> 4; + int8_t *q = pic->qscale_table; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) + quality += (double)*(q + x); + q += pic->qstride; + } + quality /= w * h; + } + + all_len += len; + all_frametime += sh->frametime; + fprintf(fvstats, "frame= %5d q= %2.2f f_size= %6d s_size= %8.0fkB ", + ++frame_number, quality, len, (double)all_len / 1024); + fprintf(fvstats, "time= %0.3f br= %7.1fkbits/s avg_br= %7.1fkbits/s ", + all_frametime, (double)(len * 8) / sh->frametime / 1000.0, + (double)(all_len * 8) / all_frametime / 1000.0); + switch (pic->pict_type) { + case AV_PICTURE_TYPE_I: + fprintf(fvstats, "type= I\n"); + break; + case AV_PICTURE_TYPE_P: + fprintf(fvstats, "type= P\n"); + break; + case AV_PICTURE_TYPE_S: + fprintf(fvstats, "type= S\n"); + break; + case AV_PICTURE_TYPE_B: + fprintf(fvstats, "type= B\n"); + break; + default: + fprintf(fvstats, "type= ? (%d)\n", pic->pict_type); + break; + } + + ctx->qp_stat[(int)(quality + 0.5)]++; + ctx->qp_sum += quality; + ctx->inv_qp_sum += 1.0 / (double)quality; + + break; + } + //-- + + if (!got_picture) + return NULL; // skipped image + + if (init_vo(sh, avctx->pix_fmt) < 0) + return NULL; + + if (dr1 && pic->opaque) + mpi = (mp_image_t *)pic->opaque; + + if (!mpi) + mpi = mpcodecs_get_image(sh, MP_IMGTYPE_EXPORT, MP_IMGFLAG_PRESERVE, + avctx->width, avctx->height); + if (!mpi) { // temporary error? + mp_tmsg(MSGT_DECVIDEO, MSGL_WARN, + "[VD_FFMPEG] Couldn't allocate image for codec.\n"); + return NULL; + } + + if (!dr1) { + mpi->planes[0] = pic->data[0]; + mpi->planes[1] = pic->data[1]; + mpi->planes[2] = pic->data[2]; + mpi->planes[3] = pic->data[3]; + mpi->stride[0] = pic->linesize[0]; + mpi->stride[1] = pic->linesize[1]; + mpi->stride[2] = pic->linesize[2]; + mpi->stride[3] = pic->linesize[3]; + } + + if (!mpi->planes[0]) + return NULL; + + if (ctx->best_csp == IMGFMT_422P && mpi->chroma_y_shift == 1) { + // we have 422p but user wants 420p + mpi->stride[1] *= 2; + mpi->stride[2] *= 2; + } + +#if BYTE_ORDER == BIG_ENDIAN + // FIXME: this might cause problems for buffers with FF_BUFFER_HINTS_PRESERVE + if (mpi->bpp == 8) + swap_palette(mpi->planes[1]); +#endif + + mpi->colorspace = sh->colorspace; + mpi->levels = sh->color_range; + mpi->qscale = pic->qscale_table; + mpi->qstride = pic->qstride; + mpi->pict_type = pic->pict_type; + mpi->qscale_type = pic->qscale_type; + mpi->fields = MP_IMGFIELD_ORDERED; + if (pic->interlaced_frame) + mpi->fields |= MP_IMGFIELD_INTERLACED; + if (pic->top_field_first) + mpi->fields |= MP_IMGFIELD_TOP_FIRST; + if (pic->repeat_pict == 1) + mpi->fields |= MP_IMGFIELD_REPEAT_FIRST; + + return mpi; +} + +static enum PixelFormat get_format(struct AVCodecContext *avctx, + const enum PixelFormat *fmt) +{ + sh_video_t *sh = avctx->opaque; + int i; + + for (i = 0; fmt[i] != PIX_FMT_NONE; i++) { + int imgfmt = pixfmt2imgfmt(fmt[i]); + if (!IMGFMT_IS_HWACCEL(imgfmt)) + continue; + mp_msg(MSGT_DECVIDEO, MSGL_V, "[VD_FFMPEG] Trying pixfmt=%d.\n", i); + if (init_vo(sh, fmt[i]) >= 0) + break; + } + return fmt[i]; +} + +static int control(sh_video_t *sh, int cmd, void *arg) +{ + vd_ffmpeg_ctx *ctx = sh->context; + AVCodecContext *avctx = ctx->avctx; + switch (cmd) { + case VDCTRL_QUERY_FORMAT: { + int format = (*((int *)arg)); + if (format == ctx->best_csp) + return CONTROL_TRUE; + // possible conversions: + switch (format) { + case IMGFMT_YV12: + case IMGFMT_IYUV: + case IMGFMT_I420: + // "converted" using pointer/stride modification + if (ctx->best_csp == IMGFMT_YV12) + return CONTROL_TRUE; // u/v swap + if (ctx->best_csp == IMGFMT_422P && !ctx->do_dr1) + return CONTROL_TRUE; // half stride + break; + } + return CONTROL_FALSE; + } + case VDCTRL_RESYNC_STREAM: + avcodec_flush_buffers(avctx); + return CONTROL_TRUE; + case VDCTRL_QUERY_UNSEEN_FRAMES:; + int delay = avctx->has_b_frames; + if (avctx->active_thread_type & FF_THREAD_FRAME) + delay += avctx->thread_count - 1; + return delay + 10; + case VDCTRL_RESET_ASPECT: + if (ctx->vo_initialized) + ctx->vo_initialized = false; + init_vo(sh, avctx->pix_fmt); + return true; + } + return CONTROL_UNKNOWN; +} + +const struct vd_functions mpcodecs_vd_ffmpeg = { + .info = &info, + .init = init, + .uninit = uninit, + .control = control, + .decode = decode, +}; diff --git a/video/filter/pullup.c b/video/filter/pullup.c new file mode 100644 index 0000000000..bd25c187d6 --- /dev/null +++ b/video/filter/pullup.c @@ -0,0 +1,817 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "config.h" +#include "pullup.h" +#include "cpudetect.h" +#include "mpcommon.h" + + + +#if ARCH_X86 +#if HAVE_MMX +static int diff_y_mmx(unsigned char *a, unsigned char *b, int s) +{ + int ret; + __asm__ volatile ( + "movl $4, %%ecx \n\t" + "pxor %%mm4, %%mm4 \n\t" + "pxor %%mm7, %%mm7 \n\t" + + "1: \n\t" + + "movq (%%"REG_S"), %%mm0 \n\t" + "movq (%%"REG_S"), %%mm2 \n\t" + "add %%"REG_a", %%"REG_S" \n\t" + "movq (%%"REG_D"), %%mm1 \n\t" + "add %%"REG_a", %%"REG_D" \n\t" + "psubusb %%mm1, %%mm2 \n\t" + "psubusb %%mm0, %%mm1 \n\t" + "movq %%mm2, %%mm0 \n\t" + "movq %%mm1, %%mm3 \n\t" + "punpcklbw %%mm7, %%mm0 \n\t" + "punpcklbw %%mm7, %%mm1 \n\t" + "punpckhbw %%mm7, %%mm2 \n\t" + "punpckhbw %%mm7, %%mm3 \n\t" + "paddw %%mm0, %%mm4 \n\t" + "paddw %%mm1, %%mm4 \n\t" + "paddw %%mm2, %%mm4 \n\t" + "paddw %%mm3, %%mm4 \n\t" + + "decl %%ecx \n\t" + "jnz 1b \n\t" + + "movq %%mm4, %%mm3 \n\t" + "punpcklwd %%mm7, %%mm4 \n\t" + "punpckhwd %%mm7, %%mm3 \n\t" + "paddd %%mm4, %%mm3 \n\t" + "movd %%mm3, %%eax \n\t" + "psrlq $32, %%mm3 \n\t" + "movd %%mm3, %%edx \n\t" + "addl %%edx, %%eax \n\t" + "emms \n\t" + : "=a" (ret) + : "S" (a), "D" (b), "a" (s) + : "%ecx", "%edx" + ); + return ret; +} + +static int licomb_y_mmx(unsigned char *a, unsigned char *b, int s) +{ + int ret; + __asm__ volatile ( + "movl $4, %%ecx \n\t" + "pxor %%mm6, %%mm6 \n\t" + "pxor %%mm7, %%mm7 \n\t" + "sub %%"REG_a", %%"REG_D" \n\t" + + "2: \n\t" + + "movq (%%"REG_D"), %%mm0 \n\t" + "movq (%%"REG_D"), %%mm1 \n\t" + "punpcklbw %%mm7, %%mm0 \n\t" + "movq (%%"REG_D",%%"REG_a"), %%mm2 \n\t" + "punpcklbw %%mm7, %%mm1 \n\t" + "punpcklbw %%mm7, %%mm2 \n\t" + "paddw %%mm0, %%mm0 \n\t" + "paddw %%mm2, %%mm1 \n\t" + "movq %%mm0, %%mm2 \n\t" + "psubusw %%mm1, %%mm0 \n\t" + "psubusw %%mm2, %%mm1 \n\t" + "paddw %%mm0, %%mm6 \n\t" + "paddw %%mm1, %%mm6 \n\t" + + "movq (%%"REG_S"), %%mm0 \n\t" + "movq (%%"REG_D"), %%mm1 \n\t" + "punpckhbw %%mm7, %%mm0 \n\t" + "movq (%%"REG_D",%%"REG_a"), %%mm2 \n\t" + "punpckhbw %%mm7, %%mm1 \n\t" + "punpckhbw %%mm7, %%mm2 \n\t" + "paddw %%mm0, %%mm0 \n\t" + "paddw %%mm2, %%mm1 \n\t" + "movq %%mm0, %%mm2 \n\t" + "psubusw %%mm1, %%mm0 \n\t" + "psubusw %%mm2, %%mm1 \n\t" + "paddw %%mm0, %%mm6 \n\t" + "paddw %%mm1, %%mm6 \n\t" + + "movq (%%"REG_D",%%"REG_a"), %%mm0 \n\t" + "movq (%%"REG_S"), %%mm1 \n\t" + "punpcklbw %%mm7, %%mm0 \n\t" + "movq (%%"REG_S",%%"REG_a"), %%mm2 \n\t" + "punpcklbw %%mm7, %%mm1 \n\t" + "punpcklbw %%mm7, %%mm2 \n\t" + "paddw %%mm0, %%mm0 \n\t" + "paddw %%mm2, %%mm1 \n\t" + "movq %%mm0, %%mm2 \n\t" + "psubusw %%mm1, %%mm0 \n\t" + "psubusw %%mm2, %%mm1 \n\t" + "paddw %%mm0, %%mm6 \n\t" + "paddw %%mm1, %%mm6 \n\t" + + "movq (%%"REG_D",%%"REG_a"), %%mm0 \n\t" + "movq (%%"REG_S"), %%mm1 \n\t" + "punpckhbw %%mm7, %%mm0 \n\t" + "movq (%%"REG_S",%%"REG_a"), %%mm2 \n\t" + "punpckhbw %%mm7, %%mm1 \n\t" + "punpckhbw %%mm7, %%mm2 \n\t" + "paddw %%mm0, %%mm0 \n\t" + "paddw %%mm2, %%mm1 \n\t" + "movq %%mm0, %%mm2 \n\t" + "psubusw %%mm1, %%mm0 \n\t" + "psubusw %%mm2, %%mm1 \n\t" + "paddw %%mm0, %%mm6 \n\t" + "paddw %%mm1, %%mm6 \n\t" + + "add %%"REG_a", %%"REG_S" \n\t" + "add %%"REG_a", %%"REG_D" \n\t" + "decl %%ecx \n\t" + "jnz 2b \n\t" + + "movq %%mm6, %%mm5 \n\t" + "punpcklwd %%mm7, %%mm6 \n\t" + "punpckhwd %%mm7, %%mm5 \n\t" + "paddd %%mm6, %%mm5 \n\t" + "movd %%mm5, %%eax \n\t" + "psrlq $32, %%mm5 \n\t" + "movd %%mm5, %%edx \n\t" + "addl %%edx, %%eax \n\t" + + "emms \n\t" + : "=a" (ret) + : "S" (a), "D" (b), "a" (s) + : "%ecx", "%edx" + ); + return ret; +} + +static int var_y_mmx(unsigned char *a, unsigned char *b, int s) +{ + int ret; + __asm__ volatile ( + "movl $3, %%ecx \n\t" + "pxor %%mm4, %%mm4 \n\t" + "pxor %%mm7, %%mm7 \n\t" + + "1: \n\t" + + "movq (%%"REG_S"), %%mm0 \n\t" + "movq (%%"REG_S"), %%mm2 \n\t" + "movq (%%"REG_S",%%"REG_a"), %%mm1 \n\t" + "add %%"REG_a", %%"REG_S" \n\t" + "psubusb %%mm1, %%mm2 \n\t" + "psubusb %%mm0, %%mm1 \n\t" + "movq %%mm2, %%mm0 \n\t" + "movq %%mm1, %%mm3 \n\t" + "punpcklbw %%mm7, %%mm0 \n\t" + "punpcklbw %%mm7, %%mm1 \n\t" + "punpckhbw %%mm7, %%mm2 \n\t" + "punpckhbw %%mm7, %%mm3 \n\t" + "paddw %%mm0, %%mm4 \n\t" + "paddw %%mm1, %%mm4 \n\t" + "paddw %%mm2, %%mm4 \n\t" + "paddw %%mm3, %%mm4 \n\t" + + "decl %%ecx \n\t" + "jnz 1b \n\t" + + "movq %%mm4, %%mm3 \n\t" + "punpcklwd %%mm7, %%mm4 \n\t" + "punpckhwd %%mm7, %%mm3 \n\t" + "paddd %%mm4, %%mm3 \n\t" + "movd %%mm3, %%eax \n\t" + "psrlq $32, %%mm3 \n\t" + "movd %%mm3, %%edx \n\t" + "addl %%edx, %%eax \n\t" + "emms \n\t" + : "=a" (ret) + : "S" (a), "a" (s) + : "%ecx", "%edx" + ); + return 4*ret; +} +#endif +#endif + +#define ABS(a) (((a)^((a)>>31))-((a)>>31)) + +static int diff_y(unsigned char *a, unsigned char *b, int s) +{ + int i, j, diff=0; + for (i=4; i; i--) { + for (j=0; j<8; j++) diff += ABS(a[j]-b[j]); + a+=s; b+=s; + } + return diff; +} + +static int licomb_y(unsigned char *a, unsigned char *b, int s) +{ + int i, j, diff=0; + for (i=4; i; i--) { + for (j=0; j<8; j++) + diff += ABS((a[j]<<1) - b[j-s] - b[j]) + + ABS((b[j]<<1) - a[j] - a[j+s]); + a+=s; b+=s; + } + return diff; +} + +#if 0 +static int qpcomb_y(unsigned char *a, unsigned char *b, int s) +{ + int i, j, diff=0; + for (i=4; i; i--) { + for (j=0; j<8; j++) + diff += ABS(a[j] - 3*b[j-s] + 3*a[j+s] - b[j]); + a+=s; b+=s; + } + return diff; +} + +static int licomb_y_test(unsigned char *a, unsigned char *b, int s) +{ + int c = licomb_y(a,b,s); + int m = licomb_y_mmx(a,b,s); + if (c != m) printf("%d != %d\n", c, m); + return m; +} +#endif + +static int var_y(unsigned char *a, unsigned char *b, int s) +{ + int i, j, var=0; + for (i=3; i; i--) { + for (j=0; j<8; j++) { + var += ABS(a[j]-a[j+s]); + } + a+=s; b+=s; + } + return 4*var; /* match comb scaling */ +} + + + + + + + + + +static void alloc_buffer(struct pullup_context *c, struct pullup_buffer *b) +{ + int i; + if (b->planes) return; + b->planes = calloc(c->nplanes, sizeof(unsigned char *)); + for (i = 0; i < c->nplanes; i++) { + b->planes[i] = malloc(c->h[i]*c->stride[i]); + /* Deal with idiotic 128=0 for chroma: */ + memset(b->planes[i], c->background[i], c->h[i]*c->stride[i]); + } +} + +struct pullup_buffer *pullup_lock_buffer(struct pullup_buffer *b, int parity) +{ + if (!b) return 0; + if ((parity+1) & 1) b->lock[0]++; + if ((parity+1) & 2) b->lock[1]++; + return b; +} + +void pullup_release_buffer(struct pullup_buffer *b, int parity) +{ + if (!b) return; + if ((parity+1) & 1) b->lock[0]--; + if ((parity+1) & 2) b->lock[1]--; +} + +struct pullup_buffer *pullup_get_buffer(struct pullup_context *c, int parity) +{ + int i; + + /* Try first to get the sister buffer for the previous field */ + if (parity < 2 && c->last && parity != c->last->parity + && !c->last->buffer->lock[parity]) { + alloc_buffer(c, c->last->buffer); + return pullup_lock_buffer(c->last->buffer, parity); + } + + /* Prefer a buffer with both fields open */ + for (i = 0; i < c->nbuffers; i++) { + if (c->buffers[i].lock[0]) continue; + if (c->buffers[i].lock[1]) continue; + alloc_buffer(c, &c->buffers[i]); + return pullup_lock_buffer(&c->buffers[i], parity); + } + + if (parity == 2) return 0; + + /* Search for any half-free buffer */ + for (i = 0; i < c->nbuffers; i++) { + if (((parity+1) & 1) && c->buffers[i].lock[0]) continue; + if (((parity+1) & 2) && c->buffers[i].lock[1]) continue; + alloc_buffer(c, &c->buffers[i]); + return pullup_lock_buffer(&c->buffers[i], parity); + } + + return 0; +} + + + + + + +static void compute_metric(struct pullup_context *c, + struct pullup_field *fa, int pa, + struct pullup_field *fb, int pb, + int (*func)(unsigned char *, unsigned char *, int), int *dest) +{ + unsigned char *a, *b; + int x, y; + int mp = c->metric_plane; + int xstep = c->bpp[mp]; + int ystep = c->stride[mp]<<3; + int s = c->stride[mp]<<1; /* field stride */ + int w = c->metric_w*xstep; + + if (!fa->buffer || !fb->buffer) return; + + /* Shortcut for duplicate fields (e.g. from RFF flag) */ + if (fa->buffer == fb->buffer && pa == pb) { + memset(dest, 0, c->metric_len * sizeof(int)); + return; + } + + a = fa->buffer->planes[mp] + pa * c->stride[mp] + c->metric_offset; + b = fb->buffer->planes[mp] + pb * c->stride[mp] + c->metric_offset; + + for (y = c->metric_h; y; y--) { + for (x = 0; x < w; x += xstep) { + *dest++ = func(a + x, b + x, s); + } + a += ystep; b += ystep; + } +} + + + + + +static void alloc_metrics(struct pullup_context *c, struct pullup_field *f) +{ + f->diffs = calloc(c->metric_len, sizeof(int)); + f->comb = calloc(c->metric_len, sizeof(int)); + f->var = calloc(c->metric_len, sizeof(int)); + /* add more metrics here as needed */ +} + +static struct pullup_field *make_field_queue(struct pullup_context *c, int len) +{ + struct pullup_field *head, *f; + f = head = calloc(1, sizeof(struct pullup_field)); + alloc_metrics(c, f); + for (; len > 0; len--) { + f->next = calloc(1, sizeof(struct pullup_field)); + f->next->prev = f; + f = f->next; + alloc_metrics(c, f); + } + f->next = head; + head->prev = f; + return head; +} + +static void check_field_queue(struct pullup_context *c) +{ + if (c->head->next == c->first) { + struct pullup_field *f = calloc(1, sizeof(struct pullup_field)); + alloc_metrics(c, f); + f->prev = c->head; + f->next = c->first; + c->head->next = f; + c->first->prev = f; + } +} + +void pullup_submit_field(struct pullup_context *c, struct pullup_buffer *b, + int parity, double pts) +{ + struct pullup_field *f; + + /* Grow the circular list if needed */ + check_field_queue(c); + + /* Cannot have two fields of same parity in a row; drop the new one */ + if (c->last && c->last->parity == parity) return; + + f = c->head; + f->parity = parity; + f->buffer = pullup_lock_buffer(b, parity); + f->flags = 0; + f->breaks = 0; + f->affinity = 0; + f->pts = pts; + + compute_metric(c, f, parity, f->prev->prev, parity, c->diff, f->diffs); + compute_metric(c, parity?f->prev:f, 0, parity?f:f->prev, 1, c->comb, f->comb); + compute_metric(c, f, parity, f, -1, c->var, f->var); + + /* Advance the circular list */ + if (!c->first) c->first = c->head; + c->last = c->head; + c->head = c->head->next; +} + + + + +#define F_HAVE_BREAKS 1 +#define F_HAVE_AFFINITY 2 + + +#define BREAK_LEFT 1 +#define BREAK_RIGHT 2 + + + + +static int queue_length(struct pullup_field *begin, struct pullup_field *end) +{ + int count = 1; + struct pullup_field *f; + + if (!begin || !end) return 0; + for (f = begin; f != end; f = f->next) count++; + return count; +} + +static int find_first_break(struct pullup_field *f, int max) +{ + int i; + for (i = 0; i < max; i++) { + if (f->breaks & BREAK_RIGHT || f->next->breaks & BREAK_LEFT) + return i+1; + f = f->next; + } + return 0; +} + +static void compute_breaks(struct pullup_context *c, struct pullup_field *f0) +{ + int i; + struct pullup_field *f1 = f0->next; + struct pullup_field *f2 = f1->next; + struct pullup_field *f3 = f2->next; + int l, max_l=0, max_r=0; + //struct pullup_field *ff; + //for (i=0, ff=c->first; ff != f0; i++, ff=ff->next); + + if (f0->flags & F_HAVE_BREAKS) return; + //printf("\n%d: ", i); + f0->flags |= F_HAVE_BREAKS; + + /* Special case when fields are 100% identical */ + if (f0->buffer == f2->buffer && f1->buffer != f3->buffer) { + f2->breaks |= BREAK_RIGHT; + return; + } + if (f0->buffer != f2->buffer && f1->buffer == f3->buffer) { + f1->breaks |= BREAK_LEFT; + return; + } + + for (i = 0; i < c->metric_len; i++) { + l = f2->diffs[i] - f3->diffs[i]; + if (l > max_l) max_l = l; + if (-l > max_r) max_r = -l; + } + /* Don't get tripped up when differences are mostly quant error */ + //printf("%d %d\n", max_l, max_r); + if (max_l + max_r < 128) return; + if (max_l > 4*max_r) f1->breaks |= BREAK_LEFT; + if (max_r > 4*max_l) f2->breaks |= BREAK_RIGHT; +} + +static void compute_affinity(struct pullup_context *c, struct pullup_field *f) +{ + int i; + int max_l=0, max_r=0, l; + if (f->flags & F_HAVE_AFFINITY) return; + f->flags |= F_HAVE_AFFINITY; + if (f->buffer == f->next->next->buffer) { + f->affinity = 1; + f->next->affinity = 0; + f->next->next->affinity = -1; + f->next->flags |= F_HAVE_AFFINITY; + f->next->next->flags |= F_HAVE_AFFINITY; + return; + } + if (1) { + for (i = 0; i < c->metric_len; i++) { + int lv = f->prev->var[i]; + int rv = f->next->var[i]; + int v = f->var[i]; + int lc = f->comb[i] - (v+lv) + ABS(v-lv); + int rc = f->next->comb[i] - (v+rv) + ABS(v-rv); + lc = lc>0 ? lc : 0; + rc = rc>0 ? rc : 0; + l = lc - rc; + if (l > max_l) max_l = l; + if (-l > max_r) max_r = -l; + } + if (max_l + max_r < 64) return; + if (max_r > 6*max_l) f->affinity = -1; + else if (max_l > 6*max_r) f->affinity = 1; + } else { + for (i = 0; i < c->metric_len; i++) { + l = f->comb[i] - f->next->comb[i]; + if (l > max_l) max_l = l; + if (-l > max_r) max_r = -l; + } + if (max_l + max_r < 64) return; + if (max_r > 2*max_l) f->affinity = -1; + else if (max_l > 2*max_r) f->affinity = 1; + } +} + +static void foo(struct pullup_context *c) +{ + struct pullup_field *f = c->first; + int i, n = queue_length(f, c->last); + for (i = 0; i < n-1; i++) { + if (i < n-3) compute_breaks(c, f); + compute_affinity(c, f); + f = f->next; + } +} + +static int decide_frame_length(struct pullup_context *c) +{ + struct pullup_field *f0 = c->first; + struct pullup_field *f1 = f0->next; + struct pullup_field *f2 = f1->next; + int l; + + if (queue_length(c->first, c->last) < 4) return 0; + foo(c); + + if (f0->affinity == -1) return 1; + + l = find_first_break(f0, 3); + if (l == 1 && c->strict_breaks < 0) l = 0; + + switch (l) { + case 1: + if (c->strict_breaks < 1 && f0->affinity == 1 && f1->affinity == -1) + return 2; + else return 1; + case 2: + /* FIXME: strictly speaking, f0->prev is no longer valid... :) */ + if (c->strict_pairs + && (f0->prev->breaks & BREAK_RIGHT) && (f2->breaks & BREAK_LEFT) + && (f0->affinity != 1 || f1->affinity != -1) ) + return 1; + if (f1->affinity == 1) return 1; + else return 2; + case 3: + if (f2->affinity == 1) return 2; + else return 3; + default: + /* 9 possibilities covered before switch */ + if (f1->affinity == 1) return 1; /* covers 6 */ + else if (f1->affinity == -1) return 2; /* covers 6 */ + else if (f2->affinity == -1) { /* covers 2 */ + if (f0->affinity == 1) return 3; + else return 1; + } + else return 2; /* the remaining 6 */ + } +} + + +static void print_aff_and_breaks(struct pullup_context *c, struct pullup_field *f) +{ + int i; + struct pullup_field *f0 = f; + const char aff_l[] = "+..", aff_r[] = "..+"; + printf("\naffinity: "); + for (i = 0; i < 4; i++) { + printf("%c%d%c", aff_l[1+f->affinity], i, aff_r[1+f->affinity]); + f = f->next; + } + f = f0; + printf("\nbreaks: "); + for (i=0; i<4; i++) { + printf("%c%d%c", f->breaks & BREAK_LEFT ? '|' : '.', i, f->breaks & BREAK_RIGHT ? '|' : '.'); + f = f->next; + } + printf("\n"); +} + + + + + +struct pullup_frame *pullup_get_frame(struct pullup_context *c) +{ + int i; + struct pullup_frame *fr = c->frame; + int n = decide_frame_length(c); + int aff = c->first->next->affinity; + + if (!n) return 0; + if (fr->lock) return 0; + + if (c->verbose) { + print_aff_and_breaks(c, c->first); + printf("duration: %d \n", n); + } + + fr->lock++; + fr->length = n; + fr->parity = c->first->parity; + fr->buffer = 0; + fr->pts = 0; + for (i = 0; i < n; i++) { + /* We cheat and steal the buffer without release+relock */ + fr->ifields[i] = c->first->buffer; + c->first->buffer = 0; + if (c->first->pts == MP_NOPTS_VALUE || fr->pts == MP_NOPTS_VALUE) + fr->pts = MP_NOPTS_VALUE; + else + fr->pts += c->first->pts; + c->first = c->first->next; + } + if (fr->pts != MP_NOPTS_VALUE) + fr->pts /= n; + + if (n == 1) { + fr->ofields[fr->parity] = fr->ifields[0]; + fr->ofields[fr->parity^1] = 0; + } else if (n == 2) { + fr->ofields[fr->parity] = fr->ifields[0]; + fr->ofields[fr->parity^1] = fr->ifields[1]; + } else if (n == 3) { + if (aff == 0) + aff = (fr->ifields[0] == fr->ifields[1]) ? -1 : 1; + /* else if (c->verbose) printf("forced aff: %d \n", aff); */ + fr->ofields[fr->parity] = fr->ifields[1+aff]; + fr->ofields[fr->parity^1] = fr->ifields[1]; + } + pullup_lock_buffer(fr->ofields[0], 0); + pullup_lock_buffer(fr->ofields[1], 1); + + if (fr->ofields[0] == fr->ofields[1]) { + fr->buffer = fr->ofields[0]; + pullup_lock_buffer(fr->buffer, 2); + return fr; + } + return fr; +} + +static void copy_field(struct pullup_context *c, struct pullup_buffer *dest, + struct pullup_buffer *src, int parity) +{ + int i, j; + unsigned char *d, *s; + for (i = 0; i < c->nplanes; i++) { + s = src->planes[i] + parity*c->stride[i]; + d = dest->planes[i] + parity*c->stride[i]; + for (j = c->h[i]>>1; j; j--) { + memcpy(d, s, c->stride[i]); + s += c->stride[i]<<1; + d += c->stride[i]<<1; + } + } +} + +void pullup_pack_frame(struct pullup_context *c, struct pullup_frame *fr) +{ + int i; + if (fr->buffer) return; + if (fr->length < 2) return; /* FIXME: deal with this */ + for (i = 0; i < 2; i++) + { + if (fr->ofields[i]->lock[i^1]) continue; + fr->buffer = fr->ofields[i]; + pullup_lock_buffer(fr->buffer, 2); + copy_field(c, fr->buffer, fr->ofields[i^1], i^1); + return; + } + fr->buffer = pullup_get_buffer(c, 2); + copy_field(c, fr->buffer, fr->ofields[0], 0); + copy_field(c, fr->buffer, fr->ofields[1], 1); +} + +void pullup_release_frame(struct pullup_frame *fr) +{ + int i; + for (i = 0; i < fr->length; i++) + pullup_release_buffer(fr->ifields[i], fr->parity ^ (i&1)); + pullup_release_buffer(fr->ofields[0], 0); + pullup_release_buffer(fr->ofields[1], 1); + if (fr->buffer) pullup_release_buffer(fr->buffer, 2); + fr->lock--; +} + + + + + + +struct pullup_context *pullup_alloc_context(void) +{ + struct pullup_context *c; + + c = calloc(1, sizeof(struct pullup_context)); + + return c; +} + +void pullup_preinit_context(struct pullup_context *c) +{ + c->bpp = calloc(c->nplanes, sizeof(int)); + c->w = calloc(c->nplanes, sizeof(int)); + c->h = calloc(c->nplanes, sizeof(int)); + c->stride = calloc(c->nplanes, sizeof(int)); + c->background = calloc(c->nplanes, sizeof(int)); +} + +void pullup_init_context(struct pullup_context *c) +{ + int mp = c->metric_plane; + if (c->nbuffers < 10) c->nbuffers = 10; + c->buffers = calloc(c->nbuffers, sizeof (struct pullup_buffer)); + + c->metric_w = (c->w[mp] - ((c->junk_left + c->junk_right) << 3)) >> 3; + c->metric_h = (c->h[mp] - ((c->junk_top + c->junk_bottom) << 1)) >> 3; + c->metric_offset = c->junk_left*c->bpp[mp] + (c->junk_top<<1)*c->stride[mp]; + c->metric_len = c->metric_w * c->metric_h; + + c->head = make_field_queue(c, 8); + + c->frame = calloc(1, sizeof (struct pullup_frame)); + c->frame->ifields = calloc(3, sizeof (struct pullup_buffer *)); + + switch(c->format) { + case PULLUP_FMT_Y: + c->diff = diff_y; + c->comb = licomb_y; + c->var = var_y; +#if ARCH_X86 +#if HAVE_MMX + if (c->cpu & PULLUP_CPU_MMX) { + c->diff = diff_y_mmx; + c->comb = licomb_y_mmx; + c->var = var_y_mmx; + } +#endif +#endif + /* c->comb = qpcomb_y; */ + break; +#if 0 + case PULLUP_FMT_YUY2: + c->diff = diff_yuy2; + break; + case PULLUP_FMT_RGB32: + c->diff = diff_rgb32; + break; +#endif + } +} + +void pullup_free_context(struct pullup_context *c) +{ + struct pullup_field *f; + free(c->buffers); + f = c->head; + do { + if (!f) break; + free(f->diffs); + free(f->comb); + f = f->next; + free(f->prev); + } while (f != c->head); + free(c->frame); + free(c); +} diff --git a/video/filter/pullup.h b/video/filter/pullup.h new file mode 100644 index 0000000000..0948737919 --- /dev/null +++ b/video/filter/pullup.h @@ -0,0 +1,102 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_PULLUP_H +#define MPLAYER_PULLUP_H + +#define PULLUP_CPU_MMX 1 +#define PULLUP_CPU_MMX2 2 +#define PULLUP_CPU_SSE 16 +#define PULLUP_CPU_SSE2 32 + +#define PULLUP_FMT_Y 1 +#define PULLUP_FMT_YUY2 2 +#define PULLUP_FMT_UYVY 3 +#define PULLUP_FMT_RGB32 4 + +struct pullup_buffer +{ + int lock[2]; + unsigned char **planes; +}; + +struct pullup_field +{ + int parity; + double pts; + struct pullup_buffer *buffer; + unsigned int flags; + int breaks; + int affinity; + int *diffs; + int *comb; + int *var; + struct pullup_field *prev, *next; +}; + +struct pullup_frame +{ + int lock; + int length; + int parity; + double pts; + struct pullup_buffer **ifields, *ofields[2]; + struct pullup_buffer *buffer; +}; + +struct pullup_context +{ + /* Public interface */ + int format; + int nplanes; + int *bpp, *w, *h, *stride, *background; + unsigned int cpu; + int junk_left, junk_right, junk_top, junk_bottom; + int verbose; + int metric_plane; + int strict_breaks; + int strict_pairs; + /* Internal data */ + struct pullup_field *first, *last, *head; + struct pullup_buffer *buffers; + int nbuffers; + int (*diff)(unsigned char *, unsigned char *, int); + int (*comb)(unsigned char *, unsigned char *, int); + int (*var)(unsigned char *, unsigned char *, int); + int metric_w, metric_h, metric_len, metric_offset; + struct pullup_frame *frame; +}; + + +struct pullup_buffer *pullup_lock_buffer(struct pullup_buffer *b, int parity); +void pullup_release_buffer(struct pullup_buffer *b, int parity); +struct pullup_buffer *pullup_get_buffer(struct pullup_context *c, int parity); + +void pullup_submit_field(struct pullup_context *c, struct pullup_buffer *b, + int parity, double pts); + +struct pullup_frame *pullup_get_frame(struct pullup_context *c); +void pullup_pack_frame(struct pullup_context *c, struct pullup_frame *fr); +void pullup_release_frame(struct pullup_frame *fr); + +struct pullup_context *pullup_alloc_context(void); +void pullup_preinit_context(struct pullup_context *c); +void pullup_init_context(struct pullup_context *c); +void pullup_free_context(struct pullup_context *c); + +#endif /* MPLAYER_PULLUP_H */ diff --git a/video/filter/vf.c b/video/filter/vf.c new file mode 100644 index 0000000000..10b9fa546f --- /dev/null +++ b/video/filter/vf.c @@ -0,0 +1,830 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <sys/types.h> +#include <libavutil/common.h> +#include <libavutil/mem.h> + +#include "config.h" + +#include "mp_msg.h" +#include "m_option.h" +#include "m_struct.h" + + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +#include "libvo/fastmemcpy.h" + +extern const vf_info_t vf_info_vo; +extern const vf_info_t vf_info_crop; +extern const vf_info_t vf_info_expand; +extern const vf_info_t vf_info_pp; +extern const vf_info_t vf_info_scale; +extern const vf_info_t vf_info_format; +extern const vf_info_t vf_info_noformat; +extern const vf_info_t vf_info_flip; +extern const vf_info_t vf_info_rotate; +extern const vf_info_t vf_info_mirror; +extern const vf_info_t vf_info_noise; +extern const vf_info_t vf_info_eq2; +extern const vf_info_t vf_info_gradfun; +extern const vf_info_t vf_info_unsharp; +extern const vf_info_t vf_info_swapuv; +extern const vf_info_t vf_info_down3dright; +extern const vf_info_t vf_info_hqdn3d; +extern const vf_info_t vf_info_ilpack; +extern const vf_info_t vf_info_dsize; +extern const vf_info_t vf_info_softpulldown; +extern const vf_info_t vf_info_pullup; +extern const vf_info_t vf_info_delogo; +extern const vf_info_t vf_info_phase; +extern const vf_info_t vf_info_divtc; +extern const vf_info_t vf_info_softskip; +extern const vf_info_t vf_info_screenshot; +extern const vf_info_t vf_info_screenshot_force; +extern const vf_info_t vf_info_sub; +extern const vf_info_t vf_info_yadif; +extern const vf_info_t vf_info_stereo3d; +extern const vf_info_t vf_info_dlopen; + +// list of available filters: +static const vf_info_t *const filter_list[] = { + &vf_info_crop, + &vf_info_expand, + &vf_info_scale, + &vf_info_vo, + &vf_info_format, + &vf_info_noformat, + &vf_info_flip, + &vf_info_rotate, + &vf_info_mirror, + +#ifdef CONFIG_LIBPOSTPROC + &vf_info_pp, +#endif + + &vf_info_screenshot, + &vf_info_screenshot_force, + + &vf_info_noise, + &vf_info_eq2, + &vf_info_gradfun, + &vf_info_unsharp, + &vf_info_swapuv, + &vf_info_down3dright, + &vf_info_hqdn3d, + &vf_info_ilpack, + &vf_info_dsize, + &vf_info_softpulldown, + &vf_info_pullup, + &vf_info_delogo, + &vf_info_phase, + &vf_info_divtc, + &vf_info_sub, + &vf_info_yadif, + &vf_info_stereo3d, + &vf_info_dlopen, + NULL +}; + +// For the vf option +const m_obj_list_t vf_obj_list = { + (void **)filter_list, + M_ST_OFF(vf_info_t, name), + M_ST_OFF(vf_info_t, info), + M_ST_OFF(vf_info_t, opts) +}; + +//============================================================================ +// mpi stuff: + +void vf_mpi_clear(mp_image_t *mpi, int x0, int y0, int w, int h) +{ + int y; + if (mpi->flags & MP_IMGFLAG_PLANAR) { + y0 &= ~1; + h += h & 1; + if (x0 == 0 && w == mpi->width) { + // full width clear: + memset(mpi->planes[0] + mpi->stride[0] * y0, 0, mpi->stride[0] * h); + memset(mpi->planes[1] + mpi->stride[1] *(y0 >> mpi->chroma_y_shift), + 128, mpi->stride[1] * (h >> mpi->chroma_y_shift)); + memset(mpi->planes[2] + mpi->stride[2] *(y0 >> mpi->chroma_y_shift), + 128, mpi->stride[2] * (h >> mpi->chroma_y_shift)); + } else + for (y = y0; y < y0 + h; y += 2) { + memset(mpi->planes[0] + x0 + mpi->stride[0] * y, 0, w); + memset(mpi->planes[0] + x0 + mpi->stride[0] * (y + 1), 0, w); + memset(mpi->planes[1] + (x0 >> mpi->chroma_x_shift) + + mpi->stride[1] * (y >> mpi->chroma_y_shift), + 128, (w >> mpi->chroma_x_shift)); + memset(mpi->planes[2] + (x0 >> mpi->chroma_x_shift) + + mpi->stride[2] * (y >> mpi->chroma_y_shift), + 128, (w >> mpi->chroma_x_shift)); + } + return; + } + // packed: + for (y = y0; y < y0 + h; y++) { + unsigned char *dst = mpi->planes[0] + mpi->stride[0] * y + + (mpi->bpp >> 3) * x0; + if (mpi->flags & MP_IMGFLAG_YUV) { + unsigned int *p = (unsigned int *) dst; + int size = (mpi->bpp >> 3) * w / 4; + int i; +#ifdef BIG_ENDIAN +#define CLEAR_PACKEDYUV_PATTERN 0x00800080 +#define CLEAR_PACKEDYUV_PATTERN_SWAPPED 0x80008000 +#else +#define CLEAR_PACKEDYUV_PATTERN 0x80008000 +#define CLEAR_PACKEDYUV_PATTERN_SWAPPED 0x00800080 +#endif + if (mpi->flags & MP_IMGFLAG_SWAPPED) { + for (i = 0; i < size - 3; i += 4) + p[i] = p[i + 1] = p[i + 2] = p[i + 3] = CLEAR_PACKEDYUV_PATTERN_SWAPPED; + for (; i < size; i++) + p[i] = CLEAR_PACKEDYUV_PATTERN_SWAPPED; + } else { + for (i = 0; i < size - 3; i += 4) + p[i] = p[i + 1] = p[i + 2] = p[i + 3] = CLEAR_PACKEDYUV_PATTERN; + for (; i < size; i++) + p[i] = CLEAR_PACKEDYUV_PATTERN; + } + } else + memset(dst, 0, (mpi->bpp >> 3) * w); + } +} + +mp_image_t *vf_get_image(vf_instance_t *vf, unsigned int outfmt, + int mp_imgtype, int mp_imgflag, int w, int h) +{ + mp_image_t *mpi = NULL; + int w2; + int number = mp_imgtype >> 16; + + assert(w == -1 || w >= vf->w); + assert(h == -1 || h >= vf->h); + assert(vf->w > 0); + assert(vf->h > 0); + + if (w == -1) + w = vf->w; + if (h == -1) + h = vf->h; + + w2 = (mp_imgflag & MP_IMGFLAG_ACCEPT_ALIGNED_STRIDE) ? FFALIGN(w, 32) : w; + + if (vf->put_image == vf_next_put_image) { + // passthru mode, if the filter uses the fallback/default put_image() + mpi = vf_get_image(vf->next,outfmt,mp_imgtype,mp_imgflag,w,h); + mpi->usage_count++; + return mpi; + } + + // Note: we should call libvo first to check if it supports direct rendering + // and if not, then fallback to software buffers: + switch (mp_imgtype & 0xff) { + case MP_IMGTYPE_EXPORT: + if (!vf->imgctx.export_images[0]) + vf->imgctx.export_images[0] = new_mp_image(w2, h); + mpi = vf->imgctx.export_images[0]; + break; + case MP_IMGTYPE_STATIC: + if (!vf->imgctx.static_images[0]) + vf->imgctx.static_images[0] = new_mp_image(w2, h); + mpi = vf->imgctx.static_images[0]; + break; + case MP_IMGTYPE_TEMP: + if (!vf->imgctx.temp_images[0]) + vf->imgctx.temp_images[0] = new_mp_image(w2, h); + mpi = vf->imgctx.temp_images[0]; + break; + case MP_IMGTYPE_IPB: + if (!(mp_imgflag & MP_IMGFLAG_READABLE)) { // B frame: + if (!vf->imgctx.temp_images[0]) + vf->imgctx.temp_images[0] = new_mp_image(w2, h); + mpi = vf->imgctx.temp_images[0]; + break; + } + case MP_IMGTYPE_IP: + if (!vf->imgctx.static_images[vf->imgctx.static_idx]) + vf->imgctx.static_images[vf->imgctx.static_idx] = new_mp_image(w2, h); + mpi = vf->imgctx.static_images[vf->imgctx.static_idx]; + vf->imgctx.static_idx ^= 1; + break; + case MP_IMGTYPE_NUMBERED: + if (number == -1) { + int i; + for (i = 0; i < NUM_NUMBERED_MPI; i++) + if (!vf->imgctx.numbered_images[i] || + !vf->imgctx.numbered_images[i]->usage_count) + break; + number = i; + } + if (number < 0 || number >= NUM_NUMBERED_MPI) + return NULL; + if (!vf->imgctx.numbered_images[number]) + vf->imgctx.numbered_images[number] = new_mp_image(w2, h); + mpi = vf->imgctx.numbered_images[number]; + mpi->number = number; + break; + } + if (mpi) { + int missing_palette = !(mpi->flags & MP_IMGFLAG_RGB_PALETTE) && + (mp_imgflag & MP_IMGFLAG_RGB_PALETTE); + mpi->type = mp_imgtype; + mpi->w = vf->w; + mpi->h = vf->h; + // keep buffer allocation status & color flags only: + mpi->flags &= MP_IMGFLAG_ALLOCATED | MP_IMGFLAG_TYPE_DISPLAYED | + MP_IMGFLAGMASK_COLORS; + // accept restrictions, draw_slice and palette flags only: + mpi->flags |= mp_imgflag & (MP_IMGFLAGMASK_RESTRICTIONS | + MP_IMGFLAG_DRAW_CALLBACK | MP_IMGFLAG_RGB_PALETTE); + if (!vf->draw_slice) + mpi->flags &= ~MP_IMGFLAG_DRAW_CALLBACK; + if (mpi->width != w2 || mpi->height != h || missing_palette) { + if (mpi->flags & MP_IMGFLAG_ALLOCATED) { + if (mpi->width < w2 || mpi->height < h || missing_palette) { + // need to re-allocate buffer memory: + av_free(mpi->planes[0]); + if (mpi->flags & MP_IMGFLAG_RGB_PALETTE) + av_free(mpi->planes[1]); + for (int n = 0; n < MP_MAX_PLANES; n++) + mpi->planes[n] = NULL; + mpi->flags &= ~MP_IMGFLAG_ALLOCATED; + mp_msg(MSGT_VFILTER, MSGL_V, + "vf.c: have to REALLOCATE buffer memory :(\n"); + } + } + mpi->width = w2; + mpi->chroma_width = (w2 + (1 << mpi->chroma_x_shift) - 1) >> + mpi->chroma_x_shift; + mpi->height = h; + mpi->chroma_height = (h + (1 << mpi->chroma_y_shift) - 1) >> + mpi->chroma_y_shift; + } + if (!mpi->bpp) + mp_image_setfmt(mpi, outfmt); + if (!(mpi->flags & MP_IMGFLAG_ALLOCATED) && + mpi->type > MP_IMGTYPE_EXPORT) { + // check libvo first! + if (vf->get_image) + vf->get_image(vf, mpi); + + if (!(mpi->flags & MP_IMGFLAG_DIRECT)) { + // non-direct and not yet allocated image. allocate it! + if (!mpi->bpp) { // no way we can allocate this + mp_msg(MSGT_DECVIDEO, MSGL_FATAL, + "vf_get_image: Tried to allocate a format that " + "can not be allocated!\n"); + return NULL; + } + + // check if codec prefer aligned stride: + if (mp_imgflag & MP_IMGFLAG_PREFER_ALIGNED_STRIDE) { + int align = (mpi->flags & MP_IMGFLAG_PLANAR && + mpi->flags & MP_IMGFLAG_YUV) ? + (16 << mpi->chroma_x_shift) - 1 : 32; // OK? + w2 = FFALIGN(w, align); + if (mpi->width != w2) { + // we have to change width... check if we CAN co it: + int flags = vf->query_format(vf, outfmt); + // should not fail + if (!(flags & (VFCAP_CSP_SUPPORTED | + VFCAP_CSP_SUPPORTED_BY_HW))) + mp_msg(MSGT_DECVIDEO, MSGL_WARN, + "??? vf_get_image{vf->query_format(outfmt)} " + "failed!\n"); + if (flags & VFCAP_ACCEPT_STRIDE) { + mpi->width = w2; + mpi->chroma_width = + (w2 + (1 << mpi->chroma_x_shift) - 1) >> + mpi->chroma_x_shift; + } + } + } + + mp_image_alloc_planes(mpi); + vf_mpi_clear(mpi, 0, 0, mpi->width, mpi->height); + } + } + if (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK) + if (vf->start_slice) + vf->start_slice(vf, mpi); + if (!(mpi->flags & MP_IMGFLAG_TYPE_DISPLAYED)) { + mp_msg(MSGT_DECVIDEO, MSGL_V, + "*** [%s] %s%s mp_image_t, %dx%dx%dbpp %s %s, %d bytes\n", + vf->info->name, + (mpi->type == MP_IMGTYPE_EXPORT) ? "Exporting" : + ((mpi->flags & MP_IMGFLAG_DIRECT) ? + "Direct Rendering" : "Allocating"), + (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK) ? " (slices)" : "", + mpi->width, mpi->height, mpi->bpp, + (mpi->flags & MP_IMGFLAG_YUV) ? "YUV" : + ((mpi->flags & MP_IMGFLAG_SWAPPED) ? "BGR" : "RGB"), + (mpi->flags & MP_IMGFLAG_PLANAR) ? "planar" : "packed", + mpi->bpp * mpi->width * mpi->height / 8); + mp_msg(MSGT_DECVIDEO, MSGL_DBG2, "(imgfmt: %x, planes: %p,%p,%p " + "strides: %d,%d,%d, chroma: %dx%d, shift: h:%d,v:%d)\n", + mpi->imgfmt, mpi->planes[0], mpi->planes[1], mpi->planes[2], + mpi->stride[0], mpi->stride[1], mpi->stride[2], + mpi->chroma_width, mpi->chroma_height, + mpi->chroma_x_shift, mpi->chroma_y_shift); + mpi->flags |= MP_IMGFLAG_TYPE_DISPLAYED; + } + mpi->qscale = NULL; + mpi->usage_count++; + } + return mpi; +} + +//============================================================================ + +static int vf_default_query_format(struct vf_instance *vf, unsigned int fmt) +{ + return vf_next_query_format(vf, fmt); +} + +struct vf_instance *vf_open_plugin_noerr(struct MPOpts *opts, + const vf_info_t *const *filter_list, + vf_instance_t *next, const char *name, + char **args, int *retcode) +{ + vf_instance_t *vf; + int i; + for (i = 0;; i++) { + if (!filter_list[i]) { + mp_tmsg(MSGT_VFILTER, MSGL_ERR, + "Couldn't find video filter '%s'.\n", name); + return NULL; // no such filter! + } + if (!strcmp(filter_list[i]->name, name)) + break; + } + vf = calloc(1, sizeof *vf); + vf->opts = opts; + vf->info = filter_list[i]; + vf->next = next; + vf->config = vf_next_config; + vf->control = vf_next_control; + vf->query_format = vf_default_query_format; + vf->put_image = vf_next_put_image; + vf->default_caps = VFCAP_ACCEPT_STRIDE; + vf->default_reqs = 0; + if (vf->info->opts) { // vf_vo get some special argument + const m_struct_t *st = vf->info->opts; + void *vf_priv = m_struct_alloc(st); + int n; + for (n = 0; args && args[2 * n]; n++) + m_struct_set(st, vf_priv, args[2 * n], bstr0(args[2 * n + 1])); + vf->priv = vf_priv; + args = NULL; + } else // Otherwise we should have the '_oldargs_' + if (args && !strcmp(args[0], "_oldargs_")) + args = (char **)args[1]; + else + args = NULL; + *retcode = vf->info->vf_open(vf, (char *)args); + if (*retcode > 0) + return vf; + free(vf); + return NULL; +} + +struct vf_instance *vf_open_plugin(struct MPOpts *opts, + const vf_info_t *const *filter_list, + vf_instance_t *next, const char *name, + char **args) +{ + struct vf_instance *vf = vf_open_plugin_noerr(opts, filter_list, next, + name, args, &(int){0}); + if (!vf) + mp_tmsg(MSGT_VFILTER, MSGL_ERR, "Couldn't open video filter '%s'.\n", + name); + return vf; +} + +vf_instance_t *vf_open_filter(struct MPOpts *opts, vf_instance_t *next, + const char *name, char **args) +{ + if (args && strcmp(args[0], "_oldargs_")) { + int i, l = 0; + for (i = 0; args && args[2 * i]; i++) + l += 1 + strlen(args[2 * i]) + 1 + strlen(args[2 * i + 1]); + l += strlen(name); + { + char str[l + 1]; + char *p = str; + p += sprintf(str, "%s", name); + for (i = 0; args && args[2 * i]; i++) + p += sprintf(p, " %s=%s", args[2 * i], args[2 * i + 1]); + mp_msg(MSGT_VFILTER, MSGL_INFO, "%s[%s]\n", + mp_gtext("Opening video filter: "), str); + } + } else if (strcmp(name, "vo")) { + if (args && strcmp(args[0], "_oldargs_") == 0) + mp_msg(MSGT_VFILTER, MSGL_INFO, "%s[%s=%s]\n", + mp_gtext("Opening video filter: "), name, args[1]); + else + mp_msg(MSGT_VFILTER, MSGL_INFO, "%s[%s]\n", + mp_gtext("Opening video filter: "), name); + } + return vf_open_plugin(opts, filter_list, next, name, args); +} + +/** + * \brief adds a filter before the last one (which should be the vo filter). + * \param vf start of the filter chain. + * \param name name of the filter to add. + * \param args argument list for the filter. + * \return pointer to the filter instance that was created. + */ +vf_instance_t *vf_add_before_vo(vf_instance_t **vf, char *name, char **args) +{ + struct MPOpts *opts = (*vf)->opts; + vf_instance_t *vo, *prev = NULL, *new; + // Find the last filter (should be vf_vo) + for (vo = *vf; vo->next; vo = vo->next) + prev = vo; + new = vf_open_filter(opts, vo, name, args); + if (prev) + prev->next = new; + else + *vf = new; + return new; +} + +//============================================================================ + +unsigned int vf_match_csp(vf_instance_t **vfp, const unsigned int *list, + unsigned int preferred) +{ + vf_instance_t *vf = *vfp; + struct MPOpts *opts = vf->opts; + const unsigned int *p; + unsigned int best = 0; + int ret; + if ((p = list)) + while (*p) { + ret = vf->query_format(vf, *p); + mp_msg(MSGT_VFILTER, MSGL_V, "[%s] query(%s) -> %x\n", + vf->info->name, vo_format_name(*p), ret); + if (ret & VFCAP_CSP_SUPPORTED_BY_HW) { + best = *p; + break; + } + if (ret & VFCAP_CSP_SUPPORTED && !best) + best = *p; + ++p; + } + if (best) + return best; // bingo, they have common csp! + // ok, then try with scale: + if (vf->info == &vf_info_scale) + return 0; // avoid infinite recursion! + vf = vf_open_filter(opts, vf, "scale", NULL); + if (!vf) + return 0; // failed to init "scale" + // try the preferred csp first: + if (preferred && vf->query_format(vf, preferred)) + best = preferred; + else + // try the list again, now with "scaler" : + if ((p = list)) + while (*p) { + ret = vf->query_format(vf, *p); + mp_msg(MSGT_VFILTER, MSGL_V, "[%s] query(%s) -> %x\n", + vf->info->name, vo_format_name(*p), ret); + if (ret & VFCAP_CSP_SUPPORTED_BY_HW) { + best = *p; + break; + } + if (ret & VFCAP_CSP_SUPPORTED && !best) + best = *p; + ++p; + } + if (best) + *vfp = vf; // else uninit vf !FIXME! + return best; +} + +void vf_clone_mpi_attributes(mp_image_t *dst, mp_image_t *src) +{ + dst->pict_type = src->pict_type; + dst->fields = src->fields; + dst->qscale_type = src->qscale_type; + if (dst->width == src->width && dst->height == src->height) { + dst->qstride = src->qstride; + dst->qscale = src->qscale; + dst->display_w = src->display_w; + dst->display_h = src->display_h; + } + if ((dst->flags & MP_IMGFLAG_YUV) == (src->flags & MP_IMGFLAG_YUV)) { + dst->colorspace = src->colorspace; + dst->levels = src->levels; + } +} + +void vf_queue_frame(vf_instance_t *vf, int (*func)(vf_instance_t *)) +{ + vf->continue_buffered_image = func; +} + +// Output the next buffered image (if any) from the filter chain. +// The queue could be kept as a simple stack/list instead avoiding the +// looping here, but there's currently no good context variable where +// that could be stored so this was easier to implement. + +int vf_output_queued_frame(vf_instance_t *vf) +{ + while (1) { + int ret; + vf_instance_t *current; + vf_instance_t *last = NULL; + int (*tmp)(vf_instance_t *); + for (current = vf; current; current = current->next) + if (current->continue_buffered_image) + last = current; + if (!last) + return 0; + tmp = last->continue_buffered_image; + last->continue_buffered_image = NULL; + ret = tmp(last); + if (ret) + return ret; + } +} + + +/** + * \brief Video config() function wrapper + * + * Blocks config() calls with different size or format for filters + * with VFCAP_CONSTANT + * + * First call is redirected to vf->config. + * + * In following calls, it verifies that the configuration parameters + * are unchanged, and returns either success or error. + * + */ +int vf_config_wrapper(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt) +{ + vf->fmt.have_configured = 1; + vf->fmt.orig_height = height; + vf->fmt.orig_width = width; + vf->fmt.orig_fmt = outfmt; + int r = vf->config(vf, width, height, d_width, d_height, flags, outfmt); + if (!r) + vf->fmt.have_configured = 0; + return r; +} + +int vf_next_config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int voflags, unsigned int outfmt) +{ + struct MPOpts *opts = vf->opts; + int miss; + int flags = vf->next->query_format(vf->next, outfmt); + if (!flags) { + // hmm. colorspace mismatch!!! + // let's insert the 'scale' filter, it does the job for us: + vf_instance_t *vf2; + if (vf->next->info == &vf_info_scale) + return 0; // scale->scale + vf2 = vf_open_filter(opts, vf->next, "scale", NULL); + if (!vf2) + return 0; // shouldn't happen! + vf->next = vf2; + flags = vf->next->query_format(vf->next, outfmt); + if (!flags) { + mp_tmsg(MSGT_VFILTER, MSGL_ERR, "Cannot find matching colorspace, " + "even by inserting 'scale' :(\n"); + return 0; // FAIL + } + } + mp_msg(MSGT_VFILTER, MSGL_V, "REQ: flags=0x%X req=0x%X \n", + flags, vf->default_reqs); + miss = vf->default_reqs - (flags & vf->default_reqs); + if (miss & VFCAP_ACCEPT_STRIDE) { + // vf requires stride support but vf->next doesn't support it! + // let's insert the 'expand' filter, it does the job for us: + vf_instance_t *vf2 = vf_open_filter(opts, vf->next, "expand", NULL); + if (!vf2) + return 0; // shouldn't happen! + vf->next = vf2; + } + vf->next->w = width; + vf->next->h = height; + return vf_config_wrapper(vf->next, width, height, d_width, d_height, + voflags, outfmt); +} + +int vf_next_control(struct vf_instance *vf, int request, void *data) +{ + return vf->next->control(vf->next, request, data); +} + +int vf_next_query_format(struct vf_instance *vf, unsigned int fmt) +{ + int flags = vf->next->query_format(vf->next, fmt); + if (flags) + flags |= vf->default_caps; + return flags; +} + +int vf_next_put_image(struct vf_instance *vf, mp_image_t *mpi, double pts) +{ + return vf->next->put_image(vf->next, mpi, pts); +} + +void vf_next_draw_slice(struct vf_instance *vf, unsigned char **src, + int *stride, int w, int h, int x, int y) +{ + if (vf->next->draw_slice) { + vf->next->draw_slice(vf->next, src, stride, w, h, x, y); + return; + } + if (!vf->dmpi) { + mp_msg(MSGT_VFILTER, MSGL_ERR, + "draw_slice: dmpi not stored by vf_%s\n", vf->info->name); + return; + } + if (!(vf->dmpi->flags & MP_IMGFLAG_PLANAR)) { + memcpy_pic(vf->dmpi->planes[0] + y * vf->dmpi->stride[0] + + vf->dmpi->bpp / 8 * x, + src[0], vf->dmpi->bpp / 8 * w, h, vf->dmpi->stride[0], + stride[0]); + return; + } + memcpy_pic(vf->dmpi->planes[0] + y * vf->dmpi->stride[0] + x, src[0], + w, h, vf->dmpi->stride[0], stride[0]); + memcpy_pic(vf->dmpi->planes[1] + + (y >> vf->dmpi->chroma_y_shift) * vf->dmpi->stride[1] + + (x >> vf->dmpi->chroma_x_shift), + src[1], w >> vf->dmpi->chroma_x_shift, + h >> vf->dmpi->chroma_y_shift, vf->dmpi->stride[1], stride[1]); + memcpy_pic(vf->dmpi->planes[2] + + (y >> vf->dmpi->chroma_y_shift) * vf->dmpi->stride[2] + + (x >> vf->dmpi->chroma_x_shift), + src[2], w >> vf->dmpi->chroma_x_shift, + h >> vf->dmpi->chroma_y_shift, vf->dmpi->stride[2], stride[2]); +} + +//============================================================================ + +vf_instance_t *append_filters(vf_instance_t *last, + struct m_obj_settings *vf_settings) +{ + struct MPOpts *opts = last->opts; + vf_instance_t *vf; + int i; + + if (vf_settings) { + // We want to add them in the 'right order' + for (i = 0; vf_settings[i].name; i++) + /* NOP */; + for (i--; i >= 0; i--) { + //printf("Open filter %s\n",vf_settings[i].name); + vf = vf_open_filter(opts, last, vf_settings[i].name, + vf_settings[i].attribs); + if (vf) + last = vf; + } + } + return last; +} + +//============================================================================ + +void vf_uninit_filter(vf_instance_t *vf) +{ + if (vf->uninit) + vf->uninit(vf); + free_mp_image(vf->imgctx.static_images[0]); + free_mp_image(vf->imgctx.static_images[1]); + free_mp_image(vf->imgctx.temp_images[0]); + free_mp_image(vf->imgctx.export_images[0]); + for (int i = 0; i < NUM_NUMBERED_MPI; i++) + free_mp_image(vf->imgctx.numbered_images[i]); + free(vf); +} + +void vf_uninit_filter_chain(vf_instance_t *vf) +{ + while (vf) { + vf_instance_t *next = vf->next; + vf_uninit_filter(vf); + vf = next; + } +} + +void vf_detc_init_pts_buf(struct vf_detc_pts_buf *p) +{ + p->inpts_prev = MP_NOPTS_VALUE; + p->outpts_prev = MP_NOPTS_VALUE; + p->lastdelta = 0; +} + +static double vf_detc_adjust_pts_internal(struct vf_detc_pts_buf *p, + double pts, bool reset_pattern, + bool skip_frame, double delta, + double boundfactor_minus, + double increasefactor, + double boundfactor_plus) +{ + double newpts; + + if (pts == MP_NOPTS_VALUE) + return pts; + + if (delta <= 0) { + if (p->inpts_prev == MP_NOPTS_VALUE) + delta = 0; + else if (pts == p->inpts_prev) + delta = p->lastdelta; + else + delta = pts - p->inpts_prev; + } + p->inpts_prev = pts; + p->lastdelta = delta; + + if (skip_frame) + return MP_NOPTS_VALUE; + + /* detect bogus deltas and then passthru pts (possibly caused by seeking, + * or bad input) */ + if (p->outpts_prev == MP_NOPTS_VALUE || reset_pattern || delta <= 0.0 || + delta >= 0.5) + newpts = pts; + else { + // turn 5 frames into 4 + newpts = p->outpts_prev + delta * increasefactor; + + // bound to input pts in a sensible way; these numbers come because we + // map frames the following way when ivtc'ing: + // 0/30 -> 0/24 diff=0 + // 1/30 -> 1/24 diff=1/120 + // 2/30 -> - + // 3/30 -> 2/24 diff=-1/60 + // 4/30 -> 3/24 diff=-1/120 + if (newpts < pts - delta * boundfactor_minus) + newpts = pts - delta * boundfactor_minus; + if (newpts > pts + delta * boundfactor_plus) + newpts = pts + delta * boundfactor_plus; + if (newpts < p->outpts_prev) + newpts = p->outpts_prev; // damage control + } + p->outpts_prev = newpts; + + return newpts; +} + +double vf_detc_adjust_pts(struct vf_detc_pts_buf *p, double pts, + bool reset_pattern, bool skip_frame) +{ + // standard telecine (see above) + return vf_detc_adjust_pts_internal(p, pts, reset_pattern, skip_frame, + 0, 0.5, 1.25, 0.25); +} + +double vf_softpulldown_adjust_pts(struct vf_detc_pts_buf *p, double pts, + bool reset_pattern, bool skip_frame, + int last_frame_duration) +{ + // for the softpulldown filter we get: + // 0/60 -> 0/30 + // 2/60 -> 1/30 + // 5/60 -> 2/30 + // 7/60 -> 3/30, 4/30 + return vf_detc_adjust_pts_internal(p, pts, reset_pattern, skip_frame, + 0, 1.0 / last_frame_duration, + 2.0 / last_frame_duration, + 1.0 / last_frame_duration); +} diff --git a/video/filter/vf.h b/video/filter/vf.h new file mode 100644 index 0000000000..4c50f0e9cc --- /dev/null +++ b/video/filter/vf.h @@ -0,0 +1,189 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_VF_H +#define MPLAYER_VF_H + +#include "mp_image.h" +#include "mpcommon.h" +#include "stdbool.h" + +#include "mpc_info.h" +#include "vfcap.h" + +struct MPOpts; +struct vf_instance; +struct vf_priv_s; + +typedef struct vf_info { + const char *info; + const char *name; + const char *author; + const char *comment; + int (*vf_open)(struct vf_instance *vf, char *args); + // Ptr to a struct dscribing the options + const void *opts; +} vf_info_t; + +#define NUM_NUMBERED_MPI 50 + +struct vf_image_context { + mp_image_t *static_images[2]; + mp_image_t *temp_images[1]; + mp_image_t *export_images[1]; + mp_image_t *numbered_images[NUM_NUMBERED_MPI]; + int static_idx; +}; + +struct vf_format_context { + int have_configured; + int orig_width, orig_height, orig_fmt; +}; + +typedef struct vf_instance { + const vf_info_t *info; + // funcs: + int (*config)(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt); + int (*control)(struct vf_instance *vf, int request, void *data); + int (*query_format)(struct vf_instance *vf, unsigned int fmt); + void (*get_image)(struct vf_instance *vf, mp_image_t *mpi); + int (*put_image)(struct vf_instance *vf, mp_image_t *mpi, double pts); + void (*start_slice)(struct vf_instance *vf, mp_image_t *mpi); + void (*draw_slice)(struct vf_instance *vf, unsigned char **src, + int *stride, int w, int h, int x, int y); + void (*uninit)(struct vf_instance *vf); + + int (*continue_buffered_image)(struct vf_instance *vf); + // caps: + unsigned int default_caps; // used by default query_format() + unsigned int default_reqs; // used by default config() + // data: + int w, h; + struct vf_image_context imgctx; + struct vf_format_context fmt; + struct vf_instance *next; + mp_image_t *dmpi; + struct vf_priv_s *priv; + struct MPOpts *opts; +} vf_instance_t; + +typedef struct vf_seteq { + const char *item; + int value; +} vf_equalizer_t; + +struct vf_ctrl_screenshot { + // When the screenshot is complete, pass it to this callback. + void (*image_callback)(void *, mp_image_t *); + void *image_callback_ctx; +}; + +#define VFCTRL_QUERY_MAX_PP_LEVEL 4 // query max postprocessing level (if any) +#define VFCTRL_SET_PP_LEVEL 5 // set postprocessing level +#define VFCTRL_SET_EQUALIZER 6 // set color options (brightness,contrast etc) +#define VFCTRL_GET_EQUALIZER 8 // get color options (brightness,contrast etc) +#define VFCTRL_DUPLICATE_FRAME 11 // For encoding - encode zero-change frame +#define VFCTRL_SKIP_NEXT_FRAME 12 // For encoding - drop the next frame that passes thru +#define VFCTRL_FLUSH_FRAMES 13 // For encoding - flush delayed frames +#define VFCTRL_SCREENSHOT 14 // Take screenshot, arg is vf_ctrl_screenshot +#define VFCTRL_INIT_OSD 15 // Filter OSD renderer present? +#define VFCTRL_SET_DEINTERLACE 18 // Set deinterlacing status +#define VFCTRL_GET_DEINTERLACE 19 // Get deinterlacing status +/* Hack to make the OSD state object available to vf_sub which + * access OSD/subtitle state outside of normal OSD draw time. */ +#define VFCTRL_SET_OSD_OBJ 20 +#define VFCTRL_SET_YUV_COLORSPACE 22 // arg is struct mp_csp_details* +#define VFCTRL_GET_YUV_COLORSPACE 23 // arg is struct mp_csp_details* + +// functions: +void vf_mpi_clear(mp_image_t *mpi, int x0, int y0, int w, int h); +mp_image_t *vf_get_image(vf_instance_t *vf, unsigned int outfmt, + int mp_imgtype, int mp_imgflag, int w, int h); + +vf_instance_t *vf_open_plugin(struct MPOpts *opts, + const vf_info_t * const *filter_list, vf_instance_t *next, + const char *name, char **args); +struct vf_instance *vf_open_plugin_noerr(struct MPOpts *opts, + const vf_info_t *const *filter_list, vf_instance_t *next, + const char *name, char **args, int *retcode); +vf_instance_t *vf_open_filter(struct MPOpts *opts, vf_instance_t *next, + const char *name, char **args); +vf_instance_t *vf_add_before_vo(vf_instance_t **vf, char *name, char **args); +vf_instance_t *vf_open_encoder(struct MPOpts *opts, vf_instance_t *next, + const char *name, char *args); + +unsigned int vf_match_csp(vf_instance_t **vfp, const unsigned int *list, + unsigned int preferred); +void vf_clone_mpi_attributes(mp_image_t *dst, mp_image_t *src); +void vf_queue_frame(vf_instance_t *vf, int (*)(vf_instance_t *)); +int vf_output_queued_frame(vf_instance_t *vf); + +// default wrappers: +int vf_next_config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt); +int vf_next_control(struct vf_instance *vf, int request, void *data); +int vf_next_query_format(struct vf_instance *vf, unsigned int fmt); +int vf_next_put_image(struct vf_instance *vf, mp_image_t *mpi, double pts); +void vf_next_draw_slice(struct vf_instance *vf, unsigned char **src, + int *stride, int w, int h, int x, int y); + +struct m_obj_settings; +vf_instance_t *append_filters(vf_instance_t *last, + struct m_obj_settings *vf_settings); + +void vf_uninit_filter(vf_instance_t *vf); +void vf_uninit_filter_chain(vf_instance_t *vf); + +int vf_config_wrapper(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt); + +static inline int norm_qscale(int qscale, int type) +{ + switch (type) { + case 0: // MPEG-1 + return qscale; + case 1: // MPEG-2 + return qscale >> 1; + case 2: // H264 + return qscale >> 2; + case 3: // VP56 + return (63 - qscale + 2) >> 2; + } + return qscale; +} + +struct vf_detc_pts_buf { + double inpts_prev, outpts_prev; + double lastdelta; +}; +void vf_detc_init_pts_buf(struct vf_detc_pts_buf *p); +/* Adjust pts when detelecining. + * skip_frame: do not render this frame + * reset_pattern: set to 1 if the telecine pattern has reset due to scene cut + */ +double vf_detc_adjust_pts(struct vf_detc_pts_buf *p, double pts, + bool reset_pattern, bool skip_frame); +double vf_softpulldown_adjust_pts(struct vf_detc_pts_buf *p, double pts, + bool reset_pattern, bool skip_frame, + int last_frame_duration); + +#endif /* MPLAYER_VF_H */ diff --git a/video/filter/vf_crop.c b/video/filter/vf_crop.c new file mode 100644 index 0000000000..c4e0b4253b --- /dev/null +++ b/video/filter/vf_crop.c @@ -0,0 +1,196 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "config.h" +#include "mp_msg.h" +#include "options.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +#include "m_option.h" +#include "m_struct.h" + +static const struct vf_priv_s { + int crop_w,crop_h; + int crop_x,crop_y; +} vf_priv_dflt = { + -1,-1, + -1,-1 +}; + +//===========================================================================// + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt){ + struct MPOpts *opts = vf->opts; + // calculate the missing parameters: + if(vf->priv->crop_w<=0 || vf->priv->crop_w>width) vf->priv->crop_w=width; + if(vf->priv->crop_h<=0 || vf->priv->crop_h>height) vf->priv->crop_h=height; + if(vf->priv->crop_x<0) vf->priv->crop_x=(width-vf->priv->crop_w)/2; + if(vf->priv->crop_y<0) vf->priv->crop_y=(height-vf->priv->crop_h)/2; + // rounding: + if(!IMGFMT_IS_RGB(outfmt) && !IMGFMT_IS_BGR(outfmt)){ + switch(outfmt){ + case IMGFMT_444P: + case IMGFMT_Y800: + case IMGFMT_Y8: + break; + case IMGFMT_YVU9: + case IMGFMT_IF09: + vf->priv->crop_y&=~3; + case IMGFMT_411P: + vf->priv->crop_x&=~3; + break; + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + vf->priv->crop_y&=~1; + default: + vf->priv->crop_x&=~1; + } + } + // check: + if(vf->priv->crop_w+vf->priv->crop_x>width || + vf->priv->crop_h+vf->priv->crop_y>height){ + mp_tmsg(MSGT_VFILTER, MSGL_WARN, "[CROP] Bad position/width/height - cropped area outside of the original!\n"); + return 0; + } + if(!opts->screen_size_x && !opts->screen_size_y){ + d_width=d_width*vf->priv->crop_w/width; + d_height=d_height*vf->priv->crop_h/height; + } + return vf_next_config(vf,vf->priv->crop_w,vf->priv->crop_h,d_width,d_height,flags,outfmt); +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts){ + mp_image_t *dmpi; + if (mpi->flags&MP_IMGFLAG_DRAW_CALLBACK) + return vf_next_put_image(vf,vf->dmpi, pts); + dmpi=vf_get_image(vf->next,mpi->imgfmt, + MP_IMGTYPE_EXPORT, 0, + vf->priv->crop_w, vf->priv->crop_h); + if(mpi->flags&MP_IMGFLAG_PLANAR){ + dmpi->planes[0]=mpi->planes[0]+ + vf->priv->crop_y*mpi->stride[0]+vf->priv->crop_x; + dmpi->planes[1]=mpi->planes[1]+ + (vf->priv->crop_y>>mpi->chroma_y_shift)*mpi->stride[1]+(vf->priv->crop_x>>mpi->chroma_x_shift); + dmpi->planes[2]=mpi->planes[2]+ + (vf->priv->crop_y>>mpi->chroma_y_shift)*mpi->stride[2]+(vf->priv->crop_x>>mpi->chroma_x_shift); + dmpi->stride[1]=mpi->stride[1]; + dmpi->stride[2]=mpi->stride[2]; + } else { + dmpi->planes[0]=mpi->planes[0]+ + vf->priv->crop_y*mpi->stride[0]+ + vf->priv->crop_x*(mpi->bpp/8); + dmpi->planes[1]=mpi->planes[1]; // passthrough rgb8 palette + } + dmpi->stride[0]=mpi->stride[0]; + dmpi->width=mpi->width; + return vf_next_put_image(vf,dmpi, pts); +} + +static void start_slice(struct vf_instance *vf, mp_image_t *mpi){ + vf->dmpi = vf_get_image(vf->next, mpi->imgfmt, mpi->type, mpi->flags, + vf->priv->crop_w, vf->priv->crop_h); +} + +static void draw_slice(struct vf_instance *vf, + unsigned char** src, int* stride, int w,int h, int x, int y){ + unsigned char *src2[3]; + src2[0] = src[0]; + if (vf->dmpi->flags & MP_IMGFLAG_PLANAR) { + src2[1] = src[1]; + src2[2] = src[2]; + } + //mp_msg(MSGT_VFILTER, MSGL_V, "crop slice %d %d %d %d ->", w,h,x,y); + if ((x -= vf->priv->crop_x) < 0) { + x = -x; + src2[0] += x; + if (vf->dmpi->flags & MP_IMGFLAG_PLANAR) { + src2[1] += x>>vf->dmpi->chroma_x_shift; + src2[2] += x>>vf->dmpi->chroma_x_shift; + } + w -= x; + x = 0; + } + if ((y -= vf->priv->crop_y) < 0) { + y = -y; + src2[0] += y*stride[0]; + if (vf->dmpi->flags & MP_IMGFLAG_PLANAR) { + src2[1] += (y>>vf->dmpi->chroma_y_shift)*stride[1]; + src2[2] += (y>>vf->dmpi->chroma_y_shift)*stride[2]; + } + h -= y; + y = 0; + } + if (x+w > vf->priv->crop_w) w = vf->priv->crop_w-x; + if (y+h > vf->priv->crop_h) h = vf->priv->crop_h-y; + //mp_msg(MSGT_VFILTER, MSGL_V, "%d %d %d %d\n", w,h,x,y); + if (w <= 0 || h <= 0) return; + vf_next_draw_slice(vf,src2,stride,w,h,x,y); +} + +//===========================================================================// + +static int vf_open(vf_instance_t *vf, char *args){ + vf->config=config; + vf->put_image=put_image; + vf->start_slice=start_slice; + vf->draw_slice=draw_slice; + vf->default_reqs=VFCAP_ACCEPT_STRIDE; + mp_msg(MSGT_VFILTER, MSGL_INFO, "Crop: %d x %d, %d ; %d\n", + vf->priv->crop_w, + vf->priv->crop_h, + vf->priv->crop_x, + vf->priv->crop_y); + return 1; +} + +#define ST_OFF(f) M_ST_OFF(struct vf_priv_s,f) +static const m_option_t vf_opts_fields[] = { + {"w", ST_OFF(crop_w), CONF_TYPE_INT, M_OPT_MIN,0 ,0, NULL}, + {"h", ST_OFF(crop_h), CONF_TYPE_INT, M_OPT_MIN,0 ,0, NULL}, + {"x", ST_OFF(crop_x), CONF_TYPE_INT, M_OPT_MIN,-1 ,0, NULL}, + {"y", ST_OFF(crop_y), CONF_TYPE_INT, M_OPT_MIN,-1 ,0, NULL}, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; + +static const m_struct_t vf_opts = { + "crop", + sizeof(struct vf_priv_s), + &vf_priv_dflt, + vf_opts_fields +}; + +const vf_info_t vf_info_crop = { + "cropping", + "crop", + "A'rpi", + "", + vf_open, + &vf_opts +}; + +//===========================================================================// diff --git a/video/filter/vf_delogo.c b/video/filter/vf_delogo.c new file mode 100644 index 0000000000..add6dc6b0c --- /dev/null +++ b/video/filter/vf_delogo.c @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2002 Jindrich Makovicka <makovick@gmail.com> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* A very simple tv station logo remover */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <limits.h> +#include <errno.h> +#include <math.h> + +#include "mp_msg.h" +#include "cpudetect.h" +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" +#include "libvo/fastmemcpy.h" + +#include "m_option.h" +#include "m_struct.h" + +//===========================================================================// + +static struct vf_priv_s { + unsigned int outfmt; + int xoff, yoff, lw, lh, band, show; + const char *file; + struct timed_rectangle { + int ts, x, y, w, h, b; + } *timed_rect; + int n_timed_rect; + int cur_timed_rect; +} const vf_priv_dflt = { + 0, + 0, 0, 0, 0, 0, 0, + NULL, NULL, 0, 0, +}; + +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) + +/** + * Adjust the coordinates to suit the band width + * Also print a notice in verbose mode + */ +static void fix_band(struct vf_priv_s *p) +{ + p->show = 0; + if (p->band < 0) { + p->band = 4; + p->show = 1; + } + p->lw += p->band*2; + p->lh += p->band*2; + p->xoff -= p->band; + p->yoff -= p->band; + mp_msg(MSGT_VFILTER, MSGL_V, "delogo: %d x %d, %d x %d, band = %d\n", + p->xoff, p->yoff, p->lw, p->lh, p->band); +} + +static void update_sub(struct vf_priv_s *p, double pts) +{ + int ipts = pts * 1000; + int tr = p->cur_timed_rect; + while (tr < p->n_timed_rect - 1 && ipts >= p->timed_rect[tr + 1].ts) + tr++; + while (tr >= 0 && ipts < p->timed_rect[tr].ts) + tr--; + if (tr == p->cur_timed_rect) + return; + p->cur_timed_rect = tr; + if (tr >= 0) { + p->xoff = p->timed_rect[tr].x; + p->yoff = p->timed_rect[tr].y; + p->lw = p->timed_rect[tr].w; + p->lh = p->timed_rect[tr].h; + p->band = p->timed_rect[tr].b; + } else { + p->xoff = p->yoff = p->lw = p->lh = p->band = 0; + } + fix_band(p); +} + +static void delogo(uint8_t *dst, uint8_t *src, int dstStride, int srcStride, int width, int height, + int logo_x, int logo_y, int logo_w, int logo_h, int band, int show, int direct) { + int y, x; + int interp, dist; + uint8_t *xdst, *xsrc; + + uint8_t *topleft, *botleft, *topright; + int xclipl, xclipr, yclipt, yclipb; + int logo_x1, logo_x2, logo_y1, logo_y2; + + xclipl = MAX(-logo_x, 0); + xclipr = MAX(logo_x+logo_w-width, 0); + yclipt = MAX(-logo_y, 0); + yclipb = MAX(logo_y+logo_h-height, 0); + + logo_x1 = logo_x + xclipl; + logo_x2 = logo_x + logo_w - xclipr; + logo_y1 = logo_y + yclipt; + logo_y2 = logo_y + logo_h - yclipb; + + topleft = src+logo_y1*srcStride+logo_x1; + topright = src+logo_y1*srcStride+logo_x2-1; + botleft = src+(logo_y2-1)*srcStride+logo_x1; + + if (!direct) memcpy_pic(dst, src, width, height, dstStride, srcStride); + + dst += (logo_y1+1)*dstStride; + src += (logo_y1+1)*srcStride; + + for(y = logo_y1+1; y < logo_y2-1; y++) + { + for (x = logo_x1+1, xdst = dst+logo_x1+1, xsrc = src+logo_x1+1; x < logo_x2-1; x++, xdst++, xsrc++) { + interp = ((topleft[srcStride*(y-logo_y-yclipt)] + + topleft[srcStride*(y-logo_y-1-yclipt)] + + topleft[srcStride*(y-logo_y+1-yclipt)])*(logo_w-(x-logo_x))/logo_w + + (topright[srcStride*(y-logo_y-yclipt)] + + topright[srcStride*(y-logo_y-1-yclipt)] + + topright[srcStride*(y-logo_y+1-yclipt)])*(x-logo_x)/logo_w + + (topleft[x-logo_x-xclipl] + + topleft[x-logo_x-1-xclipl] + + topleft[x-logo_x+1-xclipl])*(logo_h-(y-logo_y))/logo_h + + (botleft[x-logo_x-xclipl] + + botleft[x-logo_x-1-xclipl] + + botleft[x-logo_x+1-xclipl])*(y-logo_y)/logo_h + )/6; +/* interp = (topleft[srcStride*(y-logo_y)]*(logo_w-(x-logo_x))/logo_w + + topright[srcStride*(y-logo_y)]*(x-logo_x)/logo_w + + topleft[x-logo_x]*(logo_h-(y-logo_y))/logo_h + + botleft[x-logo_x]*(y-logo_y)/logo_h + )/2;*/ + if (y >= logo_y+band && y < logo_y+logo_h-band && x >= logo_x+band && x < logo_x+logo_w-band) { + *xdst = interp; + } else { + dist = 0; + if (x < logo_x+band) dist = MAX(dist, logo_x-x+band); + else if (x >= logo_x+logo_w-band) dist = MAX(dist, x-(logo_x+logo_w-1-band)); + if (y < logo_y+band) dist = MAX(dist, logo_y-y+band); + else if (y >= logo_y+logo_h-band) dist = MAX(dist, y-(logo_y+logo_h-1-band)); + *xdst = (*xsrc*dist + interp*(band-dist))/band; + if (show && (dist == band-1)) *xdst = 0; + } + } + + dst+= dstStride; + src+= srcStride; + } +} + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt){ + + return vf_next_config(vf,width,height,d_width,d_height,flags,outfmt); +} + + +static void get_image(struct vf_instance *vf, mp_image_t *mpi){ + if(mpi->flags&MP_IMGFLAG_PRESERVE) return; // don't change + if(mpi->imgfmt!=vf->priv->outfmt) return; // colorspace differ + // ok, we can do pp in-place (or pp disabled): + mpi->priv = + vf->dmpi=vf_get_image(vf->next,mpi->imgfmt, + mpi->type, mpi->flags, mpi->w, mpi->h); + mpi->planes[0]=vf->dmpi->planes[0]; + mpi->stride[0]=vf->dmpi->stride[0]; + mpi->width=vf->dmpi->width; + if(mpi->flags&MP_IMGFLAG_PLANAR){ + mpi->planes[1]=vf->dmpi->planes[1]; + mpi->planes[2]=vf->dmpi->planes[2]; + mpi->stride[1]=vf->dmpi->stride[1]; + mpi->stride[2]=vf->dmpi->stride[2]; + } + mpi->flags|=MP_IMGFLAG_DIRECT; +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts){ + mp_image_t *dmpi; + + if(mpi->flags&MP_IMGFLAG_DIRECT) { + vf->dmpi = mpi->priv; + mpi->priv = NULL; + } else { + // no DR, so get a new image! hope we'll get DR buffer: + vf->dmpi=vf_get_image(vf->next,vf->priv->outfmt, + MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE, + mpi->w,mpi->h); + } + dmpi= vf->dmpi; + + if (vf->priv->timed_rect) + update_sub(vf->priv, pts); + delogo(dmpi->planes[0], mpi->planes[0], dmpi->stride[0], mpi->stride[0], mpi->w, mpi->h, + vf->priv->xoff, vf->priv->yoff, vf->priv->lw, vf->priv->lh, vf->priv->band, vf->priv->show, + mpi->flags&MP_IMGFLAG_DIRECT); + delogo(dmpi->planes[1], mpi->planes[1], dmpi->stride[1], mpi->stride[1], mpi->w/2, mpi->h/2, + vf->priv->xoff/2, vf->priv->yoff/2, vf->priv->lw/2, vf->priv->lh/2, vf->priv->band/2, vf->priv->show, + mpi->flags&MP_IMGFLAG_DIRECT); + delogo(dmpi->planes[2], mpi->planes[2], dmpi->stride[2], mpi->stride[2], mpi->w/2, mpi->h/2, + vf->priv->xoff/2, vf->priv->yoff/2, vf->priv->lw/2, vf->priv->lh/2, vf->priv->band/2, vf->priv->show, + mpi->flags&MP_IMGFLAG_DIRECT); + + vf_clone_mpi_attributes(dmpi, mpi); + + return vf_next_put_image(vf,dmpi, pts); +} + +static void uninit(struct vf_instance *vf){ + if(!vf->priv) return; + + free(vf->priv); + vf->priv=NULL; +} + +//===========================================================================// + +static int query_format(struct vf_instance *vf, unsigned int fmt){ + switch(fmt) + { + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + return vf_next_query_format(vf,vf->priv->outfmt); + } + return 0; +} + +static const unsigned int fmt_list[]={ + IMGFMT_YV12, + IMGFMT_I420, + IMGFMT_IYUV, + 0 +}; + +static int load_timed_rectangles(struct vf_priv_s *delogo) +{ + FILE *f; + char line[2048]; + int lineno = 0, p; + double ts, last_ts = 0; + struct timed_rectangle *rect = NULL, *nr; + int n_rect = 0, alloc_rect = 0; + + f = fopen(delogo->file, "r"); + if (!f) { + mp_msg(MSGT_VFILTER, MSGL_ERR, "delogo: unable to load %s: %s\n", + delogo->file, strerror(errno)); + return -1; + } + while (fgets(line, sizeof(line), f)) { + lineno++; + if (*line == '#' || *line == '\n') + continue; + if (n_rect == alloc_rect) { + if (alloc_rect > INT_MAX / 2 / (int)sizeof(*rect)) { + mp_msg(MSGT_VFILTER, MSGL_WARN, + "delogo: too many rectangles\n"); + goto load_error; + } + alloc_rect = alloc_rect ? 2 * alloc_rect : 256; + nr = realloc(rect, alloc_rect * sizeof(*rect)); + if (!nr) { + mp_msg(MSGT_VFILTER, MSGL_WARN, "delogo: out of memory\n"); + goto load_error; + } + rect = nr; + } + nr = rect + n_rect; + memset(nr, 0, sizeof(*nr)); + p = sscanf(line, "%lf %d:%d:%d:%d:%d", + &ts, &nr->x, &nr->y, &nr->w, &nr->h, &nr->b); + if ((p == 2 && !nr->x) || p == 5 || p == 6) { + if (ts <= last_ts) + mp_msg(MSGT_VFILTER, MSGL_WARN, "delogo: %s:%d: wrong time\n", + delogo->file, lineno); + nr->ts = 1000 * ts + 0.5; + n_rect++; + } else { + mp_msg(MSGT_VFILTER, MSGL_WARN, "delogo: %s:%d: syntax error\n", + delogo->file, lineno); + } + } + fclose(f); + if (!n_rect) { + mp_msg(MSGT_VFILTER, MSGL_ERR, "delogo: %s: no rectangles found\n", + delogo->file); + free(rect); + return -1; + } + nr = realloc(rect, n_rect * sizeof(*rect)); + if (nr) + rect = nr; + delogo->timed_rect = rect; + delogo->n_timed_rect = n_rect; + return 0; + +load_error: + free(rect); + fclose(f); + return -1; +} + +static int vf_open(vf_instance_t *vf, char *args){ + vf->config=config; + vf->put_image=put_image; + vf->get_image=get_image; + vf->query_format=query_format; + vf->uninit=uninit; + + if (vf->priv->file) { + if (load_timed_rectangles(vf->priv)) + return 0; + mp_msg(MSGT_VFILTER, MSGL_V, "delogo: %d from %s\n", + vf->priv->n_timed_rect, vf->priv->file); + vf->priv->cur_timed_rect = -1; + } + fix_band(vf->priv); + + // check csp: + vf->priv->outfmt=vf_match_csp(&vf->next,fmt_list,IMGFMT_YV12); + if(!vf->priv->outfmt) + { + uninit(vf); + return 0; // no csp match :( + } + + return 1; +} + +#define ST_OFF(f) M_ST_OFF(struct vf_priv_s,f) +static const m_option_t vf_opts_fields[] = { + { "x", ST_OFF(xoff), CONF_TYPE_INT, 0, 0, 0, NULL }, + { "y", ST_OFF(yoff), CONF_TYPE_INT, 0, 0, 0, NULL }, + { "w", ST_OFF(lw), CONF_TYPE_INT, 0, 0, 0, NULL }, + { "h", ST_OFF(lh), CONF_TYPE_INT, 0, 0, 0, NULL }, + { "t", ST_OFF(band), CONF_TYPE_INT, 0, 0, 0, NULL }, + { "band", ST_OFF(band), CONF_TYPE_INT, 0, 0, 0, NULL }, // alias + { "file", ST_OFF(file), CONF_TYPE_STRING, 0, 0, 0, NULL }, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; + +static const m_struct_t vf_opts = { + "delogo", + sizeof(struct vf_priv_s), + &vf_priv_dflt, + vf_opts_fields +}; + +const vf_info_t vf_info_delogo = { + "simple logo remover", + "delogo", + "Jindrich Makovicka, Alex Beregszaszi", + "", + vf_open, + &vf_opts +}; + +//===========================================================================// diff --git a/video/filter/vf_divtc.c b/video/filter/vf_divtc.c new file mode 100644 index 0000000000..6faeb7c411 --- /dev/null +++ b/video/filter/vf_divtc.c @@ -0,0 +1,723 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#include <math.h> + +#include "config.h" +#include "mp_msg.h" +#include "cpudetect.h" +#include "libavutil/common.h" +#include "mpbswap.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +#include "libvo/fastmemcpy.h" + +const vf_info_t vf_info_divtc; + +struct vf_priv_s + { + int deghost, pass, phase, window, fcount, bcount, frameno, misscount, + ocount, sum[5]; + double threshold; + FILE *file; + int8_t *bdata; + unsigned int *csdata; + int *history; + struct vf_detc_pts_buf ptsbuf; + }; + +/* + * diff_MMX and diff_C stolen from vf_decimate.c + */ + +#if HAVE_MMX && HAVE_EBX_AVAILABLE +static int diff_MMX(unsigned char *old, unsigned char *new, int os, int ns) + { + volatile short out[4]; + __asm__ ( + "movl $8, %%ecx \n\t" + "pxor %%mm4, %%mm4 \n\t" + "pxor %%mm7, %%mm7 \n\t" + + ASMALIGN(4) + "1: \n\t" + + "movq (%%"REG_S"), %%mm0 \n\t" + "movq (%%"REG_S"), %%mm2 \n\t" + "add %%"REG_a", %%"REG_S" \n\t" + "movq (%%"REG_D"), %%mm1 \n\t" + "add %%"REG_b", %%"REG_D" \n\t" + "psubusb %%mm1, %%mm2 \n\t" + "psubusb %%mm0, %%mm1 \n\t" + "movq %%mm2, %%mm0 \n\t" + "movq %%mm1, %%mm3 \n\t" + "punpcklbw %%mm7, %%mm0 \n\t" + "punpcklbw %%mm7, %%mm1 \n\t" + "punpckhbw %%mm7, %%mm2 \n\t" + "punpckhbw %%mm7, %%mm3 \n\t" + "paddw %%mm0, %%mm4 \n\t" + "paddw %%mm1, %%mm4 \n\t" + "paddw %%mm2, %%mm4 \n\t" + "paddw %%mm3, %%mm4 \n\t" + + "decl %%ecx \n\t" + "jnz 1b \n\t" + "movq %%mm4, (%%"REG_d") \n\t" + "emms \n\t" + : + : "S" (old), "D" (new), "a" ((long)os), "b" ((long)ns), "d" (out) + : "%ecx", "memory" + ); + return out[0]+out[1]+out[2]+out[3]; + } +#endif + +static int diff_C(unsigned char *old, unsigned char *new, int os, int ns) + { + int x, y, d=0; + + for(y=8; y; y--, new+=ns, old+=os) + for(x=8; x; x--) + d+=abs(new[x]-old[x]); + + return d; + } + +static int (*diff)(unsigned char *, unsigned char *, int, int); + +static int diff_plane(unsigned char *old, unsigned char *new, + int w, int h, int os, int ns, int arg) + { + int x, y, d, max=0, sum=0, n=0; + + for(y=0; y<h-7; y+=8) + { + for(x=0; x<w-7; x+=8) + { + d=diff(old+x+y*os, new+x+y*ns, os, ns); + if(d>max) max=d; + sum+=d; + n++; + } + } + + return (sum+n*max)/2; + } + +/* +static unsigned int checksum_plane(unsigned char *p, unsigned char *z, + int w, int h, int s, int zs, int arg) + { + unsigned int shift, sum; + unsigned char *e; + + for(sum=0; h; h--, p+=s-w) + for(e=p+w, shift=32; p<e;) + sum^=(*p++)<<(shift=(shift-8)&31); + + return sum; + } +*/ + +static unsigned int checksum_plane(unsigned char *p, unsigned char *z, + int w, int h, int s, int zs, int arg) + { + unsigned int shift; + uint32_t sum, t; + unsigned char *e, *e2; +#if HAVE_FAST_64BIT + typedef uint64_t wsum_t; +#else + typedef uint32_t wsum_t; +#endif + wsum_t wsum; + + for(sum=0; h; h--, p+=s-w) + { + for(shift=0, e=p+w; (size_t)p&(sizeof(wsum_t)-1) && p<e;) + sum^=*p++<<(shift=(shift-8)&31); + + for(wsum=0, e2=e-sizeof(wsum_t)+1; p<e2; p+=sizeof(wsum_t)) + wsum^=*(wsum_t *)p; + +#if HAVE_FAST_64BIT + t=be2me_32((uint32_t)(wsum>>32^wsum)); +#else + t=be2me_32(wsum); +#endif + + for(sum^=(t<<shift|t>>(32-shift)); p<e;) + sum^=*p++<<(shift=(shift-8)&31); + } + + return sum; + } + +static int deghost_plane(unsigned char *d, unsigned char *s, + int w, int h, int ds, int ss, int threshold) + { + int t; + unsigned char *e; + + for(; h; h--, s+=ss-w, d+=ds-w) + for(e=d+w; d<e; d++, s++) + if(abs(*d-*s)>=threshold) + *d=(t=(*d<<1)-*s)<0?0:t>255?255:t; + + return 0; + } + +static int copyop(unsigned char *d, unsigned char *s, int bpl, int h, int dstride, int sstride, int dummy) { + memcpy_pic(d, s, bpl, h, dstride, sstride); + return 0; +} + +static int imgop(int(*planeop)(unsigned char *, unsigned char *, + int, int, int, int, int), + mp_image_t *dst, mp_image_t *src, int arg) + { + if(dst->flags&MP_IMGFLAG_PLANAR) + return planeop(dst->planes[0], src?src->planes[0]:0, + dst->w, dst->h, + dst->stride[0], src?src->stride[0]:0, arg)+ + planeop(dst->planes[1], src?src->planes[1]:0, + dst->chroma_width, dst->chroma_height, + dst->stride[1], src?src->stride[1]:0, arg)+ + planeop(dst->planes[2], src?src->planes[2]:0, + dst->chroma_width, dst->chroma_height, + dst->stride[2], src?src->stride[2]:0, arg); + + return planeop(dst->planes[0], src?src->planes[0]:0, + dst->w*(dst->bpp/8), dst->h, + dst->stride[0], src?src->stride[0]:0, arg); + } + +/* + * Find the phase in which the telecine pattern fits best to the + * given 5 frame slice of frame difference measurements. + * + * If phase1 and phase2 are not negative, only the two specified + * phases are tested. + */ + +static int match(struct vf_priv_s *p, int *diffs, + int phase1, int phase2, double *strength) + { + const int pattern1[]={ -4, 1, 1, 1, 1 }, + pattern2[]={ -2, -3, 4, 4, -3 }, *pattern; + int f, m, n, t[5]; + + pattern=p->deghost>0?pattern2:pattern1; + + for(f=0; f<5; f++) + { + if(phase1<0 || phase2<0 || f==phase1 || f==phase2) + { + for(n=t[f]=0; n<5; n++) + t[f]+=diffs[n]*pattern[(n-f+5)%5]; + } + else + t[f]=INT_MIN; + } + + /* find the best match */ + for(m=0, n=1; n<5; n++) + if(t[n]>t[m]) m=n; + + if(strength) + { + /* the second best match */ + for(f=m?0:1, n=f+1; n<5; n++) + if(n!=m && t[n]>t[f]) f=n; + + *strength=(t[m]>0?(double)(t[m]-t[f])/t[m]:0.0); + } + + return m; + } + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts) + { + mp_image_t *dmpi, *tmpi=0; + int n, m, f, newphase; + struct vf_priv_s *p=vf->priv; + unsigned int checksum; + double d; + + dmpi=vf_get_image(vf->next, mpi->imgfmt, + MP_IMGTYPE_STATIC, MP_IMGFLAG_ACCEPT_STRIDE | + MP_IMGFLAG_PRESERVE | MP_IMGFLAG_READABLE, + mpi->width, mpi->height); + vf_clone_mpi_attributes(dmpi, mpi); + + newphase=p->phase; + + switch(p->pass) + { + case 1: + fprintf(p->file, "%08x %d\n", + (unsigned int)imgop((void *)checksum_plane, mpi, 0, 0), + p->frameno?imgop(diff_plane, dmpi, mpi, 0):0); + break; + + case 2: + if(p->frameno/5>p->bcount) + { + mp_msg(MSGT_VFILTER, MSGL_ERR, + "\n%s: Log file ends prematurely! " + "Switching to one pass mode.\n", vf->info->name); + p->pass=0; + break; + } + + checksum=(unsigned int)imgop((void *)checksum_plane, mpi, 0, 0); + + if(checksum!=p->csdata[p->frameno]) + { + for(f=0; f<100; f++) + if(p->frameno+f<p->fcount && p->csdata[p->frameno+f]==checksum) + break; + else if(p->frameno-f>=0 && p->csdata[p->frameno-f]==checksum) + { + f=-f; + break; + } + + if(f<100) + { + mp_msg(MSGT_VFILTER, MSGL_INFO, + "\n%s: Mismatch with pass-1: %+d frame(s).\n", + vf->info->name, f); + + p->frameno+=f; + p->misscount=0; + } + else if(p->misscount++>=30) + { + mp_msg(MSGT_VFILTER, MSGL_ERR, + "\n%s: Sync with pass-1 lost! " + "Switching to one pass mode.\n", vf->info->name); + p->pass=0; + break; + } + } + + n=(p->frameno)/5; + if(n>=p->bcount) n=p->bcount-1; + + newphase=p->bdata[n]; + break; + + default: + if(p->frameno) + { + int *sump=p->sum+p->frameno%5, + *histp=p->history+p->frameno%p->window; + + *sump-=*histp; + *sump+=(*histp=imgop(diff_plane, dmpi, mpi, 0)); + } + + m=match(p, p->sum, -1, -1, &d); + + if(d>=p->threshold) + newphase=m; + } + + n=p->ocount++%5; + + if(newphase!=p->phase && ((p->phase+4)%5<n)==((newphase+4)%5<n)) + { + p->phase=newphase; + mp_msg(MSGT_VFILTER, MSGL_STATUS, + "\n%s: Telecine phase %d.\n", vf->info->name, p->phase); + } + + switch((p->frameno++-p->phase+10)%5) + { + case 0: + imgop(copyop, dmpi, mpi, 0); + vf_detc_adjust_pts(&p->ptsbuf, pts, 0, 1); + return 0; + + case 4: + if(p->deghost>0) + { + tmpi=vf_get_image(vf->next, mpi->imgfmt, + MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE | + MP_IMGFLAG_READABLE, + mpi->width, mpi->height); + vf_clone_mpi_attributes(tmpi, mpi); + + imgop(copyop, tmpi, mpi, 0); + imgop(deghost_plane, tmpi, dmpi, p->deghost); + imgop(copyop, dmpi, mpi, 0); + return vf_next_put_image(vf, tmpi, vf_detc_adjust_pts(&p->ptsbuf, pts, 0, 0)); + } + } + + imgop(copyop, dmpi, mpi, 0); + return vf_next_put_image(vf, dmpi, vf_detc_adjust_pts(&p->ptsbuf, pts, 0, 0)); + } + +static int analyze(struct vf_priv_s *p) + { + int *buf=0, *bp, bufsize=0, n, b, f, i, j, m, s; + unsigned int *cbuf=0, *cp; + int8_t *pbuf; + int8_t lbuf[256]; + int sum[5]; + double d; + + /* read the file */ + + n=15; + while(fgets(lbuf, 256, p->file)) + { + if(n>=bufsize-19) + { + bufsize=bufsize?bufsize*2:30000; + if((bp=realloc(buf, bufsize*sizeof *buf))) buf=bp; + if((cp=realloc(cbuf, bufsize*sizeof *cbuf))) cbuf=cp; + + if(!bp || !cp) + { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "%s: Not enough memory.\n", + vf_info_divtc.name); + free(buf); + free(cbuf); + return 0; + } + } + sscanf(lbuf, "%x %d", cbuf+n, buf+n); + n++; + } + + if(n <= 15) + { + mp_msg(MSGT_VFILTER, MSGL_FATAL, "%s: Empty 2-pass log file.\n", + vf_info_divtc.name); + free(buf); + free(cbuf); + return 0; + } + + /* generate some dummy data past the beginning and end of the array */ + + buf+=15, cbuf+=15; + n-=15; + + memcpy(buf-15, buf, 15*sizeof *buf); + memset(cbuf-15, 0, 15*sizeof *cbuf); + + while(n%5) + buf[n]=buf[n-5], cbuf[n]=0, n++; + + memcpy(buf+n, buf+n-15, 15*sizeof *buf); + memset(cbuf+n, 0, 15*sizeof *cbuf); + + p->csdata=cbuf; + p->fcount=n; + + /* array with one slot for each slice of 5 frames */ + + p->bdata=pbuf=malloc(p->bcount=b=(n/5)); + memset(pbuf, 255, b); + + /* resolve the automatic mode */ + + if(p->deghost<0) + { + int deghost=-p->deghost; + double s0=0.0, s1=0.0; + + for(f=0; f<n; f+=5) + { + p->deghost=0; match(p, buf+f, -1, -1, &d); s0+=d; + p->deghost=1; match(p, buf+f, -1, -1, &d); s1+=d; + } + + p->deghost=s1>s0?deghost:0; + + mp_msg(MSGT_VFILTER, MSGL_INFO, + "%s: Deghosting %-3s (relative pattern strength %+.2fdB).\n", + vf_info_divtc.name, + p->deghost?"ON":"OFF", + 10.0*log10(s1/s0)); + } + + /* analyze the data */ + + for(f=0; f<5; f++) + for(sum[f]=0, n=-15; n<20; n+=5) + sum[f]+=buf[n+f]; + + for(f=0; f<b; f++) + { + m=match(p, sum, -1, -1, &d); + + if(d>=p->threshold) + pbuf[f]=m; + + if(f<b-1) + for(n=0; n<5; n++) + sum[n]=sum[n]-buf[5*(f-3)+n]+buf[5*(f+4)+n]; + } + + /* fill in the gaps */ + + /* the beginning */ + for(f=0; f<b && pbuf[f]==-1; f++); + + if(f==b) + { + free(buf-15); + mp_msg(MSGT_VFILTER, MSGL_FATAL, "%s: No telecine pattern found!\n", + vf_info_divtc.name); + return 0; + } + + for(n=0; n<f; pbuf[n++]=pbuf[f]); + + /* the end */ + for(f=b-1; pbuf[f]==-1; f--); + for(n=f+1; n<b; pbuf[n++]=pbuf[f]); + + /* the rest */ + for(f=0;;) + { + while(f<b && pbuf[f]!=-1) f++; + if(f==b) break; + for(n=f; pbuf[n]==-1; n++); + + if(pbuf[f-1]==pbuf[n]) + { + /* just a gap */ + while(f<n) pbuf[f++]=pbuf[n]; + } + else + { + /* phase change, reanalyze the original data in the gap with zero + threshold for only the two phases that appear at the ends */ + + for(i=0; i<5; i++) + for(sum[i]=0, j=5*f-15; j<5*f; j+=5) + sum[i]+=buf[i+j]; + + for(i=f; i<n; i++) + { + pbuf[i]=match(p, sum, pbuf[f-1], pbuf[n], 0); + + for(j=0; j<5; j++) + sum[j]=sum[j]-buf[5*(i-3)+j]+buf[5*(i+4)+j]; + } + + /* estimate the transition point by dividing the gap + in the same proportion as the number of matches of each kind */ + + for(i=f, m=f; i<n; i++) + if(pbuf[i]==pbuf[f-1]) m++; + + /* find the transition of the right direction nearest to the + estimated point */ + + if(m>f && m<n) + { + for(j=m; j>f; j--) + if(pbuf[j-1]==pbuf[f-1] && pbuf[j]==pbuf[n]) break; + for(s=m; s<n; s++) + if(pbuf[s-1]==pbuf[f-1] && pbuf[s]==pbuf[n]) break; + + m=(s-m<m-j)?s:j; + } + + /* and rewrite the data to allow only this one transition */ + + for(i=f; i<m; i++) + pbuf[i]=pbuf[f-1]; + + for(; i<n; i++) + pbuf[i]=pbuf[n]; + + f=n; + } + } + + free(buf-15); + + return 1; + } + +static int query_format(struct vf_instance *vf, unsigned int fmt) + { + switch(fmt) + { + case IMGFMT_444P: case IMGFMT_IYUV: case IMGFMT_RGB24: + case IMGFMT_422P: case IMGFMT_UYVY: case IMGFMT_BGR24: + case IMGFMT_411P: case IMGFMT_YUY2: case IMGFMT_IF09: + case IMGFMT_YV12: case IMGFMT_I420: case IMGFMT_YVU9: + case IMGFMT_IUYV: case IMGFMT_Y800: case IMGFMT_Y8: + return vf_next_query_format(vf,fmt); + } + + return 0; + } + +static void uninit(struct vf_instance *vf) + { + if(vf->priv) + { + if(vf->priv->file) fclose(vf->priv->file); + if(vf->priv->csdata) free(vf->priv->csdata-15); + free(vf->priv->bdata); + free(vf->priv->history); + free(vf->priv); + } + } + +static int vf_open(vf_instance_t *vf, char *args) + { + struct vf_priv_s *p; + char *filename="framediff.log", *ap, *q, *a; + + if(args && !(args=strdup(args))) + { + nomem: + mp_msg(MSGT_VFILTER, MSGL_FATAL, + "%s: Not enough memory.\n", vf->info->name); + fail: + uninit(vf); + free(args); + return 0; + } + + vf->put_image=put_image; + vf->uninit=uninit; + vf->query_format=query_format; + vf->default_reqs=VFCAP_ACCEPT_STRIDE; + if(!(vf->priv=p=calloc(1, sizeof(struct vf_priv_s)))) + goto nomem; + + p->phase=5; + p->threshold=0.5; + p->window=30; + + if((ap=args)) + while(*ap) + { + q=ap; + if((ap=strchr(q, ':'))) *ap++=0; else ap=q+strlen(q); + if((a=strchr(q, '='))) *a++=0; else a=q+strlen(q); + + switch(*q) + { + case 0: break; + case 'f': filename=a; break; + case 't': p->threshold=atof(a); break; + case 'w': p->window=5*(atoi(a)+4)/5; break; + case 'd': p->deghost=atoi(a); break; + case 'p': + if(q[1]=='h') p->phase=atoi(a); + else p->pass=atoi(a); + break; + + case 'h': + mp_msg(MSGT_VFILTER, MSGL_INFO, + "\n%s options:\n\n" + "pass=1|2 - Use 2-pass mode.\n" + "file=filename - Set the 2-pass log file name " + "(default %s).\n" + "threshold=value - Set the pattern recognition " + "sensitivity (default %g).\n" + "deghost=value - Select deghosting threshold " + "(default %d).\n" + "window=numframes - Set the statistics window " + "for 1-pass mode (default %d).\n" + "phase=0|1|2|3|4 - Set the initial phase " + "for 1-pass mode (default %d).\n\n" + "The option names can be abbreviated to the shortest " + "unique prefix.\n\n", + vf->info->name, filename, p->threshold, p->deghost, + p->window, p->phase%5); + break; + + default: + mp_msg(MSGT_VFILTER, MSGL_FATAL, + "%s: Unknown argument %s.\n", vf->info->name, q); + goto fail; + } + } + + switch(p->pass) + { + case 1: + if(!(p->file=fopen(filename, "w"))) + { + mp_msg(MSGT_VFILTER, MSGL_FATAL, + "%s: Can't create file %s.\n", vf->info->name, filename); + goto fail; + } + + break; + + case 2: + if(!(p->file=fopen(filename, "r"))) + { + mp_msg(MSGT_VFILTER, MSGL_FATAL, + "%s: Can't open file %s.\n", vf->info->name, filename); + goto fail; + } + + if(!analyze(p)) + goto fail; + + fclose(p->file); + p->file=0; + break; + } + + if(p->window<5) p->window=5; + if(!(p->history=calloc(sizeof *p->history, p->window))) + goto nomem; + + diff = diff_C; +#if HAVE_MMX && HAVE_EBX_AVAILABLE + if(gCpuCaps.hasMMX) diff = diff_MMX; +#endif + + free(args); + vf_detc_init_pts_buf(&p->ptsbuf); + return 1; + } + +const vf_info_t vf_info_divtc = + { + "inverse telecine for deinterlaced video", + "divtc", + "Ville Saari", + "", + vf_open, + NULL + }; diff --git a/video/filter/vf_dlopen.c b/video/filter/vf_dlopen.c new file mode 100644 index 0000000000..183b31be84 --- /dev/null +++ b/video/filter/vf_dlopen.c @@ -0,0 +1,389 @@ +/* + * This file is part of mplayer. + * + * mplayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mplayer. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include "config.h" +#include "mp_msg.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +#include "m_option.h" +#include "m_struct.h" + +#include "vf_dlopen.h" + +#ifdef _WIN32 +# include <windows.h> +# define DLLOpen(name) LoadLibrary(name) +# define DLLClose(handle) FreeLibrary(handle) +# define DLLSymbol(handle, name) ((void *)GetProcAddress(handle, name)) +#else +# include <dlfcn.h> +# define DLLOpen(name) dlopen(name, RTLD_NOW) +# define DLLClose(handle) dlclose(handle) +# define DLLSymbol(handle, name) dlsym(handle, name) +#endif + +static struct vf_priv_s { + const char *cfg_dllname; + int cfg_argc; + const char *cfg_argv[4]; + void *dll; + struct vf_dlopen_context filter; + + // output mp_image_t stuff + mp_image_t *outpic[FILTER_MAX_OUTCNT]; + + // generic + unsigned int out_cnt, out_width, out_height; + + // multi frame output + unsigned int outbufferpos; + unsigned int outbufferlen; + mp_image_t *outbuffermpi; + + // qscale buffer + unsigned char *qbuffer; + size_t qbuffersize; + + unsigned int outfmt; + + int argc; +} const vf_priv_dflt = {}; + +//===========================================================================// + +static void set_imgprop(struct vf_dlopen_picdata *out, const mp_image_t *mpi) +{ + int i; + out->planes = mpi->num_planes; + for (i = 0; i < mpi->num_planes; ++i) { + out->plane[i] = mpi->planes[i]; + out->planestride[i] = mpi->stride[i]; + out->planewidth[i] = + i ? (/*mpi->chroma_width*/ mpi->w >> mpi->chroma_x_shift) : mpi->w; + out->planeheight[i] = + i ? (/*mpi->chroma_height*/ mpi->h >> mpi->chroma_y_shift) : mpi->h; + out->planexshift[i] = i ? mpi->chroma_x_shift : 0; + out->planeyshift[i] = i ? mpi->chroma_y_shift : 0; + } +} + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int fmt) +{ + vf->priv->filter.in_width = width; + vf->priv->filter.in_height = height; + vf->priv->filter.in_d_width = d_width; + vf->priv->filter.in_d_height = d_height; + vf->priv->filter.in_fmt = mp_imgfmt_to_name(fmt); + vf->priv->filter.out_width = width; + vf->priv->filter.out_height = height; + vf->priv->filter.out_d_width = d_width; + vf->priv->filter.out_d_height = d_height; + vf->priv->filter.out_fmt = NULL; + vf->priv->filter.out_cnt = 1; + + if (!vf->priv->filter.in_fmt) { + mp_msg(MSGT_VFILTER, MSGL_ERR, "invalid input/output format\n"); + return 0; + } + if (vf->priv->filter.config && vf->priv->filter.config(&vf->priv->filter) < 0) { + mp_msg(MSGT_VFILTER, MSGL_ERR, "filter config failed\n"); + return 0; + } + + // copy away stuff to sanity island + vf->priv->out_cnt = vf->priv->filter.out_cnt; + vf->priv->out_width = vf->priv->filter.out_width; + vf->priv->out_height = vf->priv->filter.out_height; + + if (vf->priv->filter.out_fmt) + vf->priv->outfmt = mp_imgfmt_from_name(bstr0(vf->priv->filter.out_fmt), + false); + else { + struct vf_dlopen_formatpair *p = vf->priv->filter.format_mapping; + vf->priv->outfmt = 0; + if (p) { + for (; p->from; ++p) { + // TODO support pixel format classes in matching + if (!strcmp(p->from, vf->priv->filter.in_fmt)) { + vf->priv->outfmt = mp_imgfmt_from_name(bstr0(p->to), false); + break; + } + } + } else + vf->priv->outfmt = fmt; + vf->priv->filter.out_fmt = mp_imgfmt_to_name(vf->priv->outfmt); + } + + if (!vf->priv->outfmt) { + mp_msg(MSGT_VFILTER, MSGL_ERR, + "filter config wants an unsupported output format\n"); + return 0; + } + if (!vf->priv->out_cnt || vf->priv->out_cnt > FILTER_MAX_OUTCNT) { + mp_msg(MSGT_VFILTER, MSGL_ERR, + "filter config wants to yield zero or too many output frames\n"); + return 0; + } + + if (vf->priv->out_cnt >= 2) { + int i; + for (i = 0; i < vf->priv->out_cnt; ++i) { + vf->priv->outpic[i] = + alloc_mpi(vf->priv->out_width, vf->priv->out_height, + vf->priv->outfmt); + set_imgprop(&vf->priv->filter.outpic[i], vf->priv->outpic[i]); + } + } + + return vf_next_config(vf, vf->priv->out_width, + vf->priv->out_height, + vf->priv->filter.out_d_width, + vf->priv->filter.out_d_height, + flags, vf->priv->outfmt); +} + +static void uninit(struct vf_instance *vf) +{ + if (vf->priv->filter.uninit) + vf->priv->filter.uninit(&vf->priv->filter); + memset(&vf->priv->filter, 0, sizeof(&vf->priv->filter)); + if (vf->priv->dll) { + DLLClose(vf->priv->dll); + vf->priv->dll = NULL; + } + if (vf->priv->out_cnt >= 2) { + int i; + for (i = 0; i < vf->priv->out_cnt; ++i) { + free_mp_image(vf->priv->outpic[i]); + vf->priv->outpic[i] = NULL; + } + } + if (vf->priv->qbuffer) { + free(vf->priv->qbuffer); + vf->priv->qbuffer = NULL; + } +} + +// NOTE: only called if (vf->priv->out_cnt >= 2) { +static int continue_put_image(struct vf_instance *vf) +{ + int k; + int ret = 0; + + mp_image_t *dmpi = + vf_get_image(vf->next, vf->priv->outfmt, MP_IMGTYPE_EXPORT, 0, + vf->priv->outpic[vf->priv->outbufferpos]->w, + vf->priv->outpic[vf->priv->outbufferpos]->h); + for (k = 0; k < vf->priv->outpic[vf->priv->outbufferpos]->num_planes; + ++k) { + dmpi->planes[k] = vf->priv->outpic[vf->priv->outbufferpos]->planes[k]; + dmpi->stride[k] = vf->priv->outpic[vf->priv->outbufferpos]->stride[k]; + } + + // pass through qscale if we can + vf_clone_mpi_attributes(dmpi, vf->priv->outbuffermpi); + + ret = + vf_next_put_image(vf, dmpi, + vf->priv->filter.outpic[vf->priv->outbufferpos].pts); + + ++vf->priv->outbufferpos; + + // more frames left? + if (vf->priv->outbufferpos < vf->priv->outbufferlen) + vf_queue_frame(vf, continue_put_image); + + return ret; +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts) +{ + int i, k; + + set_imgprop(&vf->priv->filter.inpic, mpi); + if (mpi->qscale) { + if (mpi->qscale_type != 0) { + k = mpi->qstride * ((mpi->height + 15) >> 4); + if (vf->priv->qbuffersize != k) { + vf->priv->qbuffer = realloc(vf->priv->qbuffer, k); + vf->priv->qbuffersize = k; + } + for (i = 0; i < k; ++i) + vf->priv->qbuffer[i] = norm_qscale(mpi->qscale[i], + mpi->qscale_type); + vf->priv->filter.inpic_qscale = vf->priv->qbuffer; + } else + vf->priv->filter.inpic_qscale = mpi->qscale; + vf->priv->filter.inpic_qscalestride = mpi->qstride; + vf->priv->filter.inpic_qscaleshift = 4; + } else { + vf->priv->filter.inpic_qscale = NULL; + vf->priv->filter.inpic_qscalestride = 0; + vf->priv->filter.inpic_qscaleshift = 0; + } + vf->priv->filter.inpic.pts = pts; + + if (vf->priv->out_cnt >= 2) { + // more than one out pic + int ret = vf->priv->filter.put_image(&vf->priv->filter); + if (ret <= 0) + return ret; + + vf->priv->outbuffermpi = mpi; + vf->priv->outbufferlen = ret; + vf->priv->outbufferpos = 0; + return continue_put_image(vf); + } else { + // efficient case: exactly one out pic + mp_image_t *dmpi = + vf_get_image(vf->next, vf->priv->outfmt, + MP_IMGTYPE_TEMP, + MP_IMGFLAG_ACCEPT_STRIDE | MP_IMGFLAG_PREFER_ALIGNED_STRIDE, + vf->priv->out_width, vf->priv->out_height); + set_imgprop(&vf->priv->filter.outpic[0], dmpi); + + int ret = vf->priv->filter.put_image(&vf->priv->filter); + if (ret <= 0) + return ret; + + // pass through qscale if we can + vf_clone_mpi_attributes(dmpi, mpi); + + return vf_next_put_image(vf, dmpi, vf->priv->filter.outpic[0].pts); + } +} + +//===========================================================================// + +static int query_format(struct vf_instance *vf, unsigned int fmt) +{ + if (IMGFMT_IS_HWACCEL(fmt)) + return 0; // these can't really be filtered + if (fmt == IMGFMT_RGB8 || fmt == IMGFMT_BGR8) + return 0; // we don't have palette support, sorry + const char *fmtname = mp_imgfmt_to_name(fmt); + if (!fmtname) + return 0; + struct vf_dlopen_formatpair *p = vf->priv->filter.format_mapping; + unsigned int outfmt = 0; + if (p) { + for (; p->from; ++p) { + // TODO support pixel format classes in matching + if (!strcmp(p->from, fmtname)) { + outfmt = mp_imgfmt_from_name(bstr0(p->to), false); + break; + } + } + } else + outfmt = fmt; + if (!outfmt) + return 0; + return vf_next_query_format(vf, outfmt); +} + +static int vf_open(vf_instance_t *vf, char *args) +{ + if (!vf->priv->cfg_dllname) { + mp_msg(MSGT_VFILTER, MSGL_ERR, + "usage: -vf dlopen=filename.so:function:args\n"); + return 0; + } + + vf->priv->dll = DLLOpen(vf->priv->cfg_dllname); + if (!vf->priv->dll) { + mp_msg(MSGT_VFILTER, MSGL_ERR, "library not found: %s\n", + vf->priv->cfg_dllname); + return 0; + } + + vf_dlopen_getcontext_func *func = + (vf_dlopen_getcontext_func *) DLLSymbol(vf->priv->dll, "vf_dlopen_getcontext"); + if (!func) { + mp_msg(MSGT_VFILTER, MSGL_ERR, "library is not a filter: %s\n", + vf->priv->cfg_dllname); + return 0; + } + + memset(&vf->priv->filter, 0, sizeof(vf->priv->filter)); + vf->priv->filter.major_version = VF_DLOPEN_MAJOR_VERSION; + vf->priv->filter.minor_version = VF_DLOPEN_MINOR_VERSION; + + // count arguments + for (vf->priv->cfg_argc = 0; + vf->priv->cfg_argc < sizeof(vf->priv->cfg_argv) / sizeof(vf->priv->cfg_argv[0]) && vf->priv->cfg_argv[vf->priv->cfg_argc]; + ++vf->priv->cfg_argc) + ; + + if (func(&vf->priv->filter, vf->priv->cfg_argc, vf->priv->cfg_argv) < 0) { + mp_msg(MSGT_VFILTER, MSGL_ERR, + "function did not create a filter: %s\n", + vf->priv->cfg_dllname); + return 0; + } + + if (!vf->priv->filter.put_image) { + mp_msg(MSGT_VFILTER, MSGL_ERR, + "function did not create a filter that can put images: %s\n", + vf->priv->cfg_dllname); + return 0; + } + + vf->put_image = put_image; + vf->query_format = query_format; + vf->config = config; + vf->uninit = uninit; + + return 1; +} + +#define ST_OFF(f) M_ST_OFF(struct vf_priv_s, f) +static m_option_t vf_opts_fields[] = { + {"dll", ST_OFF(cfg_dllname), CONF_TYPE_STRING, 0, 0, 0, NULL}, + {"a0", ST_OFF(cfg_argv[0]), CONF_TYPE_STRING, 0, 0, 0, NULL}, + {"a1", ST_OFF(cfg_argv[1]), CONF_TYPE_STRING, 0, 0, 0, NULL}, + {"a2", ST_OFF(cfg_argv[2]), CONF_TYPE_STRING, 0, 0, 0, NULL}, + {"a3", ST_OFF(cfg_argv[3]), CONF_TYPE_STRING, 0, 0, 0, NULL}, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; + +static const m_struct_t vf_opts = { + "dlopen", + sizeof(struct vf_priv_s), + &vf_priv_dflt, + vf_opts_fields +}; + +const vf_info_t vf_info_dlopen = { + "Dynamic library filter", + "dlopen", + "Rudolf Polzer", + "", + vf_open, + &vf_opts +}; + +//===========================================================================// diff --git a/video/filter/vf_dlopen.h b/video/filter/vf_dlopen.h new file mode 100644 index 0000000000..962605ca28 --- /dev/null +++ b/video/filter/vf_dlopen.h @@ -0,0 +1,88 @@ +#ifndef VF_DLOPEN_H +#define VF_DLOPEN_H + +// when doing a two-way compatible change, don't change these +// when doing a backwards compatible change, bump minor version +// when doing an incompatible change, bump major version and zero minor version +#define VF_DLOPEN_MAJOR_VERSION 1 +#define VF_DLOPEN_MINOR_VERSION 0 + +#if VF_DLOPEN_MINOR_VERSION > 0 +# define VF_DLOPEN_CHECK_VERSION(ctx) \ + do { \ + if (ctx->major_version != VF_DLOPEN_MAJOR_VERSION || \ + ctx->minor_version < VF_DLOPEN_MINOR_VERSION) \ + return -1; \ + } while (0) +#else +// workaround for "comparison is always false" warning +# define VF_DLOPEN_CHECK_VERSION(ctx) \ + do { \ + if (ctx->major_version != VF_DLOPEN_MAJOR_VERSION) \ + return -1; \ + } while (0) +#endif + +// valid pixel format names: +// "yv12": planar YUV, U and V planes have an xshift and yshift of 1 +// "rgb24": packed RGB24 +struct vf_dlopen_formatpair { + const char *from; // (LATER) can also be a name of a format class + const char *to; // if NULL, this means identical format as source +}; + +#define FILTER_MAX_OUTCNT 16 + +struct vf_dlopen_picdata { + unsigned int planes; + unsigned char *plane[4]; + signed int planestride[4]; + unsigned int planewidth[4]; + unsigned int planeheight[4]; + unsigned int planexshift[4]; + unsigned int planeyshift[4]; + double pts; +}; + +struct vf_dlopen_context { + unsigned short major_version; + unsigned short minor_version; + + void *priv; + + struct vf_dlopen_formatpair *format_mapping; + // {NULL, NULL} terminated list of supported format pairs + // if NULL, anything goes + + int (*config)(struct vf_dlopen_context *ctx); // -1 = error + // image config is put into the in_* members before calling this + // fills in the out_* members (which are preinitialized for an identity vf_dlopen_context) + + int (*put_image)(struct vf_dlopen_context *ctx); // returns number of images written, or negative on error + // before this is called, inpic_* and outpic_* are filled + + void (*uninit)(struct vf_dlopen_context *ctx); + + unsigned int in_width; + unsigned int in_height; + unsigned int in_d_width; + unsigned int in_d_height; + const char *in_fmt; + unsigned int out_width; + unsigned int out_height; + unsigned int out_d_width; + unsigned int out_d_height; + const char *out_fmt; + unsigned int out_cnt; + + struct vf_dlopen_picdata inpic; + char *inpic_qscale; + unsigned int inpic_qscalestride; + unsigned int inpic_qscaleshift; + + struct vf_dlopen_picdata outpic[FILTER_MAX_OUTCNT]; +}; +typedef int (vf_dlopen_getcontext_func)(struct vf_dlopen_context *ctx, int argc, const char **argv); // negative on error +vf_dlopen_getcontext_func vf_dlopen_getcontext; + +#endif diff --git a/video/filter/vf_down3dright.c b/video/filter/vf_down3dright.c new file mode 100644 index 0000000000..561bc898d0 --- /dev/null +++ b/video/filter/vf_down3dright.c @@ -0,0 +1,166 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include "config.h" +#include "mp_msg.h" +#include "cpudetect.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +#include "libvo/fastmemcpy.h" + +struct vf_priv_s { + int skipline; + int scalew; + int scaleh; +}; + +static void toright(unsigned char *dst[3], unsigned char *src[3], + int dststride[3], int srcstride[3], + int w, int h, struct vf_priv_s* p) +{ + int k; + + for (k = 0; k < 3; k++) { + unsigned char* fromL = src[k]; + unsigned char* fromR = src[k]; + unsigned char* to = dst[k]; + int src = srcstride[k]; + int dst = dststride[k]; + int ss; + unsigned int dd; + int i; + + if (k > 0) { + i = h / 4 - p->skipline / 2; + ss = src * (h / 4 + p->skipline / 2); + dd = w / 4; + } else { + i = h / 2 - p->skipline; + ss = src * (h / 2 + p->skipline); + dd = w / 2; + } + fromR += ss; + for ( ; i > 0; i--) { + int j; + unsigned char* t = to; + unsigned char* sL = fromL; + unsigned char* sR = fromR; + + if (p->scalew == 1) { + for (j = dd; j > 0; j--) { + *t++ = (sL[0] + sL[1]) / 2; + sL+=2; + } + for (j = dd ; j > 0; j--) { + *t++ = (sR[0] + sR[1]) / 2; + sR+=2; + } + } else { + for (j = dd * 2 ; j > 0; j--) + *t++ = *sL++; + for (j = dd * 2 ; j > 0; j--) + *t++ = *sR++; + } + if (p->scaleh == 1) { + memcpy(to + dst, to, dst); + to += dst; + } + to += dst; + fromL += src; + fromR += src; + } + //printf("K %d %d %d %d %d \n", k, w, h, src, dst); + } +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts) +{ + mp_image_t *dmpi; + + // hope we'll get DR buffer: + dmpi=vf_get_image(vf->next, IMGFMT_YV12, + MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE | + ((vf->priv->scaleh == 1) ? MP_IMGFLAG_READABLE : 0), + mpi->w * vf->priv->scalew, + mpi->h / vf->priv->scaleh - vf->priv->skipline); + + toright(dmpi->planes, mpi->planes, dmpi->stride, + mpi->stride, mpi->w, mpi->h, vf->priv); + + return vf_next_put_image(vf,dmpi, pts); +} + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt) +{ + /* FIXME - also support UYVY output? */ + return vf_next_config(vf, width * vf->priv->scalew, + height / vf->priv->scaleh - vf->priv->skipline, d_width, d_height, flags, IMGFMT_YV12); +} + + +static int query_format(struct vf_instance *vf, unsigned int fmt) +{ + /* FIXME - really any YUV 4:2:0 input format should work */ + switch (fmt) { + case IMGFMT_YV12: + case IMGFMT_IYUV: + case IMGFMT_I420: + return vf_next_query_format(vf, IMGFMT_YV12); + } + return 0; +} + +static void uninit(struct vf_instance *vf) +{ + free(vf->priv); +} + +static int vf_open(vf_instance_t *vf, char *args) +{ + vf->config=config; + vf->query_format=query_format; + vf->put_image=put_image; + vf->uninit=uninit; + + vf->priv = calloc(1, sizeof (struct vf_priv_s)); + vf->priv->skipline = 0; + vf->priv->scalew = 1; + vf->priv->scaleh = 2; + if (args) sscanf(args, "%d:%d:%d", &vf->priv->skipline, &vf->priv->scalew, &vf->priv->scaleh); + + return 1; +} + +const vf_info_t vf_info_down3dright = { + "convert stereo movie from top-bottom to left-right field", + "down3dright", + "Zdenek Kabelac", + "", + vf_open, + NULL +}; diff --git a/video/filter/vf_dsize.c b/video/filter/vf_dsize.c new file mode 100644 index 0000000000..d46d22ebb2 --- /dev/null +++ b/video/filter/vf_dsize.c @@ -0,0 +1,125 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include "config.h" +#include "mp_msg.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +struct vf_priv_s { + int w, h; + int method; // aspect method, 0 -> downscale, 1-> upscale. +2 -> original aspect. + int round; + float aspect; +}; + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt) +{ + int w = vf->priv->w; + int h = vf->priv->h; + if (vf->priv->aspect < 0.001) { // did the user input aspect or w,h params + if (w == 0) w = d_width; + if (h == 0) h = d_height; + if (w == -1) w = width; + if (h == -1) h = height; + if (w == -2) w = h * (double)d_width / d_height; + if (w == -3) w = h * (double)width / height; + if (h == -2) h = w * (double)d_height / d_width; + if (h == -3) h = w * (double)height / width; + if (vf->priv->method > -1) { + double aspect = (vf->priv->method & 2) ? ((double)height / width) : ((double)d_height / d_width); + if ((h > w * aspect) ^ (vf->priv->method & 1)) { + h = w * aspect; + } else { + w = h / aspect; + } + } + if (vf->priv->round > 1) { // round up + w += (vf->priv->round - 1 - (w - 1) % vf->priv->round); + h += (vf->priv->round - 1 - (h - 1) % vf->priv->round); + } + d_width = w; + d_height = h; + } else { + if (vf->priv->aspect * height > width) { + d_width = height * vf->priv->aspect + .5; + d_height = height; + } else { + d_height = width / vf->priv->aspect + .5; + d_width = width; + } + } + return vf_next_config(vf, width, height, d_width, d_height, flags, outfmt); +} + +static void uninit(vf_instance_t *vf) { + free(vf->priv); + vf->priv = NULL; +} + +static int vf_open(vf_instance_t *vf, char *args) +{ + vf->config = config; + vf->draw_slice = vf_next_draw_slice; + vf->uninit = uninit; + //vf->default_caps = 0; + vf->priv = calloc(sizeof(struct vf_priv_s), 1); + vf->priv->aspect = 0.; + vf->priv->w = -1; + vf->priv->h = -1; + vf->priv->method = -1; + vf->priv->round = 1; + if (args) { + if (strchr(args, '/')) { + int w, h; + sscanf(args, "%d/%d", &w, &h); + vf->priv->aspect = (float)w/h; + } else if (strchr(args, '.')) { + sscanf(args, "%f", &vf->priv->aspect); + } else { + sscanf(args, "%d:%d:%d:%d", &vf->priv->w, &vf->priv->h, &vf->priv->method, &vf->priv->round); + } + } + if ((vf->priv->aspect < 0.) || (vf->priv->w < -3) || (vf->priv->h < -3) || + ((vf->priv->w < -1) && (vf->priv->h < -1)) || + (vf->priv->method < -1) || (vf->priv->method > 3) || + (vf->priv->round < 0)) { + mp_msg(MSGT_VFILTER, MSGL_ERR, "[dsize] Illegal value(s): aspect: %f w: %d h: %d aspect_method: %d round: %d\n", vf->priv->aspect, vf->priv->w, vf->priv->h, vf->priv->method, vf->priv->round); + free(vf->priv); vf->priv = NULL; + return -1; + } + return 1; +} + +const vf_info_t vf_info_dsize = { + "reset displaysize/aspect", + "dsize", + "Rich Felker", + "", + vf_open, + NULL +}; diff --git a/video/filter/vf_eq2.c b/video/filter/vf_eq2.c new file mode 100644 index 0000000000..fe4a89fb13 --- /dev/null +++ b/video/filter/vf_eq2.c @@ -0,0 +1,519 @@ +/* + * Software equalizer (brightness, contrast, gamma, saturation) + * + * Hampa Hug <hampa@hampa.ch> (original LUT gamma/contrast/brightness filter) + * Daniel Moreno <comac@comac.darktech.org> (saturation, R/G/B gamma support) + * Richard Felker (original MMX contrast/brightness code (vf_eq.c)) + * Michael Niedermayer <michalni@gmx.at> (LUT16) + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <inttypes.h> + +#include "config.h" +#include "mp_msg.h" +#include "cpudetect.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +#define LUT16 + +/* Per channel parameters */ +typedef struct eq2_param_t { + unsigned char lut[256]; +#ifdef LUT16 + uint16_t lut16[256*256]; +#endif + int lut_clean; + + void (*adjust) (struct eq2_param_t *par, unsigned char *dst, unsigned char *src, + unsigned w, unsigned h, unsigned dstride, unsigned sstride); + + double c; + double b; + double g; + double w; +} eq2_param_t; + +typedef struct vf_priv_s { + eq2_param_t param[3]; + + double contrast; + double brightness; + double saturation; + + double gamma; + double gamma_weight; + double rgamma; + double ggamma; + double bgamma; + + unsigned buf_w[3]; + unsigned buf_h[3]; + unsigned char *buf[3]; +} vf_eq2_t; + + +static +void create_lut (eq2_param_t *par) +{ + unsigned i; + double g, v; + double lw, gw; + + g = par->g; + gw = par->w; + lw = 1.0 - gw; + + if ((g < 0.001) || (g > 1000.0)) { + g = 1.0; + } + + g = 1.0 / g; + + for (i = 0; i < 256; i++) { + v = (double) i / 255.0; + v = par->c * (v - 0.5) + 0.5 + par->b; + + if (v <= 0.0) { + par->lut[i] = 0; + } + else { + v = v*lw + pow(v, g)*gw; + + if (v >= 1.0) { + par->lut[i] = 255; + } + else { + par->lut[i] = (unsigned char) (256.0 * v); + } + } + } + +#ifdef LUT16 + for(i=0; i<256*256; i++){ + par->lut16[i]= par->lut[i&0xFF] + (par->lut[i>>8]<<8); + } +#endif + + par->lut_clean = 1; +} + +#if HAVE_MMX +static +void affine_1d_MMX (eq2_param_t *par, unsigned char *dst, unsigned char *src, + unsigned w, unsigned h, unsigned dstride, unsigned sstride) +{ + unsigned i; + int contrast, brightness; + unsigned dstep, sstep; + int pel; + short brvec[4]; + short contvec[4]; + +// printf("\nmmx: src=%p dst=%p w=%d h=%d ds=%d ss=%d\n",src,dst,w,h,dstride,sstride); + + contrast = (int) (par->c * 256 * 16); + brightness = ((int) (100.0 * par->b + 100.0) * 511) / 200 - 128 - contrast / 32; + + brvec[0] = brvec[1] = brvec[2] = brvec[3] = brightness; + contvec[0] = contvec[1] = contvec[2] = contvec[3] = contrast; + + sstep = sstride - w; + dstep = dstride - w; + + while (h-- > 0) { + __asm__ volatile ( + "movq (%5), %%mm3 \n\t" + "movq (%6), %%mm4 \n\t" + "pxor %%mm0, %%mm0 \n\t" + "movl %4, %%eax\n\t" + ASMALIGN(4) + "1: \n\t" + "movq (%0), %%mm1 \n\t" + "movq (%0), %%mm2 \n\t" + "punpcklbw %%mm0, %%mm1 \n\t" + "punpckhbw %%mm0, %%mm2 \n\t" + "psllw $4, %%mm1 \n\t" + "psllw $4, %%mm2 \n\t" + "pmulhw %%mm4, %%mm1 \n\t" + "pmulhw %%mm4, %%mm2 \n\t" + "paddw %%mm3, %%mm1 \n\t" + "paddw %%mm3, %%mm2 \n\t" + "packuswb %%mm2, %%mm1 \n\t" + "add $8, %0 \n\t" + "movq %%mm1, (%1) \n\t" + "add $8, %1 \n\t" + "decl %%eax \n\t" + "jnz 1b \n\t" + : "=r" (src), "=r" (dst) + : "0" (src), "1" (dst), "r" (w >> 3), "r" (brvec), "r" (contvec) + : "%eax" + ); + + for (i = w & 7; i > 0; i--) { + pel = ((*src++ * contrast) >> 12) + brightness; + if (pel & 768) { + pel = (-pel) >> 31; + } + *dst++ = pel; + } + + src += sstep; + dst += dstep; + } + + __asm__ volatile ( "emms \n\t" ::: "memory" ); +} +#endif + +static +void apply_lut (eq2_param_t *par, unsigned char *dst, unsigned char *src, + unsigned w, unsigned h, unsigned dstride, unsigned sstride) +{ + unsigned i, j, w2; + unsigned char *lut; + uint16_t *lut16; + + if (!par->lut_clean) { + create_lut (par); + } + + lut = par->lut; +#ifdef LUT16 + lut16 = par->lut16; + w2= (w>>3)<<2; + for (j = 0; j < h; j++) { + uint16_t *src16= (uint16_t*)src; + uint16_t *dst16= (uint16_t*)dst; + for (i = 0; i < w2; i+=4) { + dst16[i+0] = lut16[src16[i+0]]; + dst16[i+1] = lut16[src16[i+1]]; + dst16[i+2] = lut16[src16[i+2]]; + dst16[i+3] = lut16[src16[i+3]]; + } + i <<= 1; +#else + w2= (w>>3)<<3; + for (j = 0; j < h; j++) { + for (i = 0; i < w2; i+=8) { + dst[i+0] = lut[src[i+0]]; + dst[i+1] = lut[src[i+1]]; + dst[i+2] = lut[src[i+2]]; + dst[i+3] = lut[src[i+3]]; + dst[i+4] = lut[src[i+4]]; + dst[i+5] = lut[src[i+5]]; + dst[i+6] = lut[src[i+6]]; + dst[i+7] = lut[src[i+7]]; + } +#endif + for (; i < w; i++) { + dst[i] = lut[src[i]]; + } + + src += sstride; + dst += dstride; + } +} + +static +int put_image (vf_instance_t *vf, mp_image_t *src, double pts) +{ + unsigned i; + vf_eq2_t *eq2; + mp_image_t *dst; + unsigned long img_n,img_c; + + eq2 = vf->priv; + + if ((eq2->buf_w[0] != src->w) || (eq2->buf_h[0] != src->h)) { + eq2->buf_w[0] = src->w; + eq2->buf_h[0] = src->h; + eq2->buf_w[1] = eq2->buf_w[2] = src->w >> src->chroma_x_shift; + eq2->buf_h[1] = eq2->buf_h[2] = src->h >> src->chroma_y_shift; + img_n = eq2->buf_w[0]*eq2->buf_h[0]; + if(src->num_planes>1){ + img_c = eq2->buf_w[1]*eq2->buf_h[1]; + eq2->buf[0] = realloc (eq2->buf[0], img_n + 2*img_c); + eq2->buf[1] = eq2->buf[0] + img_n; + eq2->buf[2] = eq2->buf[1] + img_c; + } else + eq2->buf[0] = realloc (eq2->buf[0], img_n); + } + + dst = vf_get_image (vf->next, src->imgfmt, MP_IMGTYPE_EXPORT, 0, src->w, src->h); + + for (i = 0; i < ((src->num_planes>1)?3:1); i++) { + if (eq2->param[i].adjust != NULL) { + dst->planes[i] = eq2->buf[i]; + dst->stride[i] = eq2->buf_w[i]; + + eq2->param[i].adjust (&eq2->param[i], dst->planes[i], src->planes[i], + eq2->buf_w[i], eq2->buf_h[i], dst->stride[i], src->stride[i]); + } + else { + dst->planes[i] = src->planes[i]; + dst->stride[i] = src->stride[i]; + } + } + + return vf_next_put_image (vf, dst, pts); +} + +static +void check_values (eq2_param_t *par) +{ + /* yuck! floating point comparisons... */ + + if ((par->c == 1.0) && (par->b == 0.0) && (par->g == 1.0)) { + par->adjust = NULL; + } +#if HAVE_MMX + else if (par->g == 1.0 && gCpuCaps.hasMMX) { + par->adjust = &affine_1d_MMX; + } +#endif + else { + par->adjust = &apply_lut; + } +} + +static +void print_values (vf_eq2_t *eq2) +{ + mp_msg (MSGT_VFILTER, MSGL_V, "vf_eq2: c=%.2f b=%.2f g=%.4f s=%.2f \n", + eq2->contrast, eq2->brightness, eq2->gamma, eq2->saturation + ); +} + +static +void set_contrast (vf_eq2_t *eq2, double c) +{ + eq2->contrast = c; + eq2->param[0].c = c; + eq2->param[0].lut_clean = 0; + check_values (&eq2->param[0]); + print_values (eq2); +} + +static +void set_brightness (vf_eq2_t *eq2, double b) +{ + eq2->brightness = b; + eq2->param[0].b = b; + eq2->param[0].lut_clean = 0; + check_values (&eq2->param[0]); + print_values (eq2); +} + +static +void set_gamma (vf_eq2_t *eq2, double g) +{ + eq2->gamma = g; + + eq2->param[0].g = eq2->gamma * eq2->ggamma; + eq2->param[1].g = sqrt (eq2->bgamma / eq2->ggamma); + eq2->param[2].g = sqrt (eq2->rgamma / eq2->ggamma); + eq2->param[0].w = eq2->param[1].w = eq2->param[2].w = eq2->gamma_weight; + + eq2->param[0].lut_clean = 0; + eq2->param[1].lut_clean = 0; + eq2->param[2].lut_clean = 0; + + check_values (&eq2->param[0]); + check_values (&eq2->param[1]); + check_values (&eq2->param[2]); + + print_values (eq2); +} + +static +void set_saturation (vf_eq2_t *eq2, double s) +{ + eq2->saturation = s; + + eq2->param[1].c = s; + eq2->param[2].c = s; + + eq2->param[1].lut_clean = 0; + eq2->param[2].lut_clean = 0; + + check_values (&eq2->param[1]); + check_values (&eq2->param[2]); + + print_values (eq2); +} + +static +int control (vf_instance_t *vf, int request, void *data) +{ + vf_equalizer_t *eq; + + switch (request) { + case VFCTRL_SET_EQUALIZER: + eq = (vf_equalizer_t *) data; + + if (strcmp (eq->item, "gamma") == 0) { + set_gamma (vf->priv, exp (log (8.0) * eq->value / 100.0)); + return CONTROL_TRUE; + } + else if (strcmp (eq->item, "contrast") == 0) { + set_contrast (vf->priv, (1.0 / 100.0) * (eq->value + 100)); + return CONTROL_TRUE; + } + else if (strcmp (eq->item, "brightness") == 0) { + set_brightness (vf->priv, (1.0 / 100.0) * eq->value); + return CONTROL_TRUE; + } + else if (strcmp (eq->item, "saturation") == 0) { + set_saturation (vf->priv, (double) (eq->value + 100) / 100.0); + return CONTROL_TRUE; + } + break; + + case VFCTRL_GET_EQUALIZER: + eq = (vf_equalizer_t *) data; + if (strcmp (eq->item, "gamma") == 0) { + eq->value = (int) (100.0 * log (vf->priv->gamma) / log (8.0)); + return CONTROL_TRUE; + } + else if (strcmp (eq->item, "contrast") == 0) { + eq->value = (int) (100.0 * vf->priv->contrast) - 100; + return CONTROL_TRUE; + } + else if (strcmp (eq->item, "brightness") == 0) { + eq->value = (int) (100.0 * vf->priv->brightness); + return CONTROL_TRUE; + } + else if (strcmp (eq->item, "saturation") == 0) { + eq->value = (int) (100.0 * vf->priv->saturation) - 100; + return CONTROL_TRUE; + } + break; + } + + return vf_next_control (vf, request, data); +} + +static +int query_format (vf_instance_t *vf, unsigned fmt) +{ + switch (fmt) { + case IMGFMT_YVU9: + case IMGFMT_IF09: + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + case IMGFMT_Y800: + case IMGFMT_Y8: + case IMGFMT_444P: + case IMGFMT_422P: + case IMGFMT_411P: + return vf_next_query_format (vf, fmt); + } + + return 0; +} + +static +void uninit (vf_instance_t *vf) +{ + if (vf->priv != NULL) { + free (vf->priv->buf[0]); + free (vf->priv); + } +} + +static +int vf_open(vf_instance_t *vf, char *args) +{ + unsigned i; + vf_eq2_t *eq2; + double par[8]; + + vf->control = control; + vf->query_format = query_format; + vf->put_image = put_image; + vf->uninit = uninit; + + vf->priv = malloc (sizeof (vf_eq2_t)); + eq2 = vf->priv; + + for (i = 0; i < 3; i++) { + eq2->buf[i] = NULL; + eq2->buf_w[i] = 0; + eq2->buf_h[i] = 0; + + eq2->param[i].adjust = NULL; + eq2->param[i].c = 1.0; + eq2->param[i].b = 0.0; + eq2->param[i].g = 1.0; + eq2->param[i].lut_clean = 0; + } + + eq2->contrast = 1.0; + eq2->brightness = 0.0; + eq2->saturation = 1.0; + + eq2->gamma = 1.0; + eq2->gamma_weight = 1.0; + eq2->rgamma = 1.0; + eq2->ggamma = 1.0; + eq2->bgamma = 1.0; + + if (args != NULL) { + par[0] = 1.0; + par[1] = 1.0; + par[2] = 0.0; + par[3] = 1.0; + par[4] = 1.0; + par[5] = 1.0; + par[6] = 1.0; + par[7] = 1.0; + sscanf (args, "%lf:%lf:%lf:%lf:%lf:%lf:%lf:%lf", + par, par + 1, par + 2, par + 3, par + 4, par + 5, par + 6, par + 7 + ); + + eq2->rgamma = par[4]; + eq2->ggamma = par[5]; + eq2->bgamma = par[6]; + eq2->gamma_weight = par[7]; + + set_gamma (eq2, par[0]); + set_contrast (eq2, par[1]); + set_brightness (eq2, par[2]); + set_saturation (eq2, par[3]); + } + + return 1; +} + +const vf_info_t vf_info_eq2 = { + "Software equalizer", + "eq2", + "Hampa Hug, Daniel Moreno, Richard Felker", + "", + &vf_open, + NULL +}; diff --git a/video/filter/vf_expand.c b/video/filter/vf_expand.c new file mode 100644 index 0000000000..839820510d --- /dev/null +++ b/video/filter/vf_expand.c @@ -0,0 +1,350 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> + +#include <libavutil/common.h> + +#include "config.h" +#include "mp_msg.h" +#include "options.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +#include "libvo/fastmemcpy.h" + +#include "m_option.h" +#include "m_struct.h" + +static struct vf_priv_s { + // These four values are a backup of the values parsed from the command line. + // This is necessary so that we do not get a mess upon filter reinit due to + // e.g. aspect changes and with only aspect specified on the command line, + // where we would otherwise use the values calculated for a different aspect + // instead of recalculating them again. + int cfg_exp_w, cfg_exp_h; + int cfg_exp_x, cfg_exp_y; + int exp_w,exp_h; + int exp_x,exp_y; + double aspect; + int round; + int first_slice; +} const vf_priv_dflt = { + -1,-1, + -1,-1, + -1,-1, + -1,-1, + 0., + 1, + 0 +}; + +//===========================================================================// + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt) +{ + struct MPOpts *opts = vf->opts; + mp_image_t test_mpi; + mp_image_setfmt(&test_mpi, outfmt); + if (outfmt == IMGFMT_IF09 || !test_mpi.bpp) return 0; + vf->priv->exp_x = vf->priv->cfg_exp_x; + vf->priv->exp_y = vf->priv->cfg_exp_y; + vf->priv->exp_w = vf->priv->cfg_exp_w; + vf->priv->exp_h = vf->priv->cfg_exp_h; + // calculate the missing parameters: +#if 0 + if(vf->priv->exp_w<width) vf->priv->exp_w=width; + if(vf->priv->exp_h<height) vf->priv->exp_h=height; +#else + if ( vf->priv->exp_w == -1 ) vf->priv->exp_w=width; + else if (vf->priv->exp_w < -1 ) vf->priv->exp_w=width - vf->priv->exp_w; + else if ( vf->priv->exp_w<width ) vf->priv->exp_w=width; + if ( vf->priv->exp_h == -1 ) vf->priv->exp_h=height; + else if ( vf->priv->exp_h < -1 ) vf->priv->exp_h=height - vf->priv->exp_h; + else if( vf->priv->exp_h<height ) vf->priv->exp_h=height; +#endif + if (vf->priv->aspect) { + float adjusted_aspect = vf->priv->aspect; + adjusted_aspect *= ((double)width/height) / ((double)d_width/d_height); + if (vf->priv->exp_h < vf->priv->exp_w / adjusted_aspect) { + vf->priv->exp_h = vf->priv->exp_w / adjusted_aspect + 0.5; + } else { + vf->priv->exp_w = vf->priv->exp_h * adjusted_aspect + 0.5; + } + } + if (vf->priv->round > 1) { // round up. + vf->priv->exp_w = (1 + (vf->priv->exp_w - 1) / vf->priv->round) * vf->priv->round; + vf->priv->exp_h = (1 + (vf->priv->exp_h - 1) / vf->priv->round) * vf->priv->round; + } + + if(vf->priv->exp_x<0 || vf->priv->exp_x+width>vf->priv->exp_w) vf->priv->exp_x=(vf->priv->exp_w-width)/2; + if(vf->priv->exp_y<0 || vf->priv->exp_y+height>vf->priv->exp_h) vf->priv->exp_y=(vf->priv->exp_h-height)/2; + if(test_mpi.flags & MP_IMGFLAG_YUV) { + int x_align_mask = (1 << test_mpi.chroma_x_shift) - 1; + int y_align_mask = (1 << test_mpi.chroma_y_shift) - 1; + // For 1-plane format non-aligned offsets will completely + // destroy the colours, for planar it will break the chroma + // sampling position. + if (vf->priv->exp_x & x_align_mask) { + vf->priv->exp_x &= ~x_align_mask; + mp_msg(MSGT_VFILTER, MSGL_ERR, "Specified x offset not supported " + "for YUV, reduced to %i.\n", vf->priv->exp_x); + } + if (vf->priv->exp_y & y_align_mask) { + vf->priv->exp_y &= ~y_align_mask; + mp_msg(MSGT_VFILTER, MSGL_ERR, "Specified y offset not supported " + "for YUV, reduced to %i.\n", vf->priv->exp_y); + } + } + + if(!opts->screen_size_x && !opts->screen_size_y){ + d_width=d_width*vf->priv->exp_w/width; + d_height=d_height*vf->priv->exp_h/height; + } + return vf_next_config(vf,vf->priv->exp_w,vf->priv->exp_h,d_width,d_height,flags,outfmt); +} + +// there are 4 cases: +// codec --DR--> expand --DR--> vo +// codec --DR--> expand -copy-> vo +// codec -copy-> expand --DR--> vo +// codec -copy-> expand -copy-> vo (worst case) + +static void get_image(struct vf_instance *vf, mp_image_t *mpi){ +// if(mpi->type==MP_IMGTYPE_IPB) return; // not yet working + if(vf->priv->exp_w==mpi->width || + (mpi->flags&(MP_IMGFLAG_ACCEPT_STRIDE|MP_IMGFLAG_ACCEPT_WIDTH)) ){ + // try full DR ! + mpi->priv=vf->dmpi=vf_get_image(vf->next,mpi->imgfmt, + mpi->type, mpi->flags, + FFMAX(vf->priv->exp_w, mpi->width +vf->priv->exp_x), + FFMAX(vf->priv->exp_h, mpi->height+vf->priv->exp_y)); + if((vf->dmpi->flags & MP_IMGFLAG_DRAW_CALLBACK) && + !(vf->dmpi->flags & MP_IMGFLAG_DIRECT)){ + mp_tmsg(MSGT_VFILTER, MSGL_INFO, "Full DR not possible, trying SLICES instead!\n"); + return; + } + // set up mpi as a cropped-down image of dmpi: + if(mpi->flags&MP_IMGFLAG_PLANAR){ + mpi->planes[0]=vf->dmpi->planes[0]+ + vf->priv->exp_y*vf->dmpi->stride[0]+vf->priv->exp_x; + mpi->planes[1]=vf->dmpi->planes[1]+ + (vf->priv->exp_y>>mpi->chroma_y_shift)*vf->dmpi->stride[1]+(vf->priv->exp_x>>mpi->chroma_x_shift); + mpi->planes[2]=vf->dmpi->planes[2]+ + (vf->priv->exp_y>>mpi->chroma_y_shift)*vf->dmpi->stride[2]+(vf->priv->exp_x>>mpi->chroma_x_shift); + mpi->stride[1]=vf->dmpi->stride[1]; + mpi->stride[2]=vf->dmpi->stride[2]; + } else { + mpi->planes[0]=vf->dmpi->planes[0]+ + vf->priv->exp_y*vf->dmpi->stride[0]+ + vf->priv->exp_x*(vf->dmpi->bpp/8); + } + mpi->stride[0]=vf->dmpi->stride[0]; + mpi->width=vf->dmpi->width; + mpi->flags|=MP_IMGFLAG_DIRECT; + mpi->flags&=~MP_IMGFLAG_DRAW_CALLBACK; +// vf->dmpi->flags&=~MP_IMGFLAG_DRAW_CALLBACK; + } +} + +static void start_slice(struct vf_instance *vf, mp_image_t *mpi){ +// printf("start_slice called! flag=%d\n",mpi->flags&MP_IMGFLAG_DRAW_CALLBACK); + // they want slices!!! allocate the buffer. + if(!mpi->priv) + mpi->priv=vf->dmpi=vf_get_image(vf->next,mpi->imgfmt, +// MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE | MP_IMGFLAG_PREFER_ALIGNED_STRIDE, + MP_IMGTYPE_TEMP, mpi->flags, + FFMAX(vf->priv->exp_w, mpi->width +vf->priv->exp_x), + FFMAX(vf->priv->exp_h, mpi->height+vf->priv->exp_y)); + vf->priv->first_slice = 1; +} + +static void draw_top_blackbar_slice(struct vf_instance *vf, + unsigned char** src, int* stride, int w,int h, int x, int y){ + if(vf->priv->exp_y>0 && y == 0) { + vf_next_draw_slice(vf, vf->dmpi->planes, vf->dmpi->stride, + vf->dmpi->w,vf->priv->exp_y,0,0); + } + +} + +static void draw_bottom_blackbar_slice(struct vf_instance *vf, + unsigned char** src, int* stride, int w,int h, int x, int y){ + if(vf->priv->exp_y+vf->h<vf->dmpi->h && y+h == vf->h) { + unsigned char *src2[MP_MAX_PLANES]; + src2[0] = vf->dmpi->planes[0] + + (vf->priv->exp_y+vf->h)*vf->dmpi->stride[0]; + if(vf->dmpi->flags&MP_IMGFLAG_PLANAR){ + src2[1] = vf->dmpi->planes[1] + + ((vf->priv->exp_y+vf->h)>>vf->dmpi->chroma_y_shift)*vf->dmpi->stride[1]; + src2[2] = vf->dmpi->planes[2] + + ((vf->priv->exp_y+vf->h)>>vf->dmpi->chroma_y_shift)*vf->dmpi->stride[2]; + } else { + src2[1] = vf->dmpi->planes[1]; // passthrough rgb8 palette + } + vf_next_draw_slice(vf, src2, vf->dmpi->stride, + vf->dmpi->w,vf->dmpi->h-(vf->priv->exp_y+vf->h), + 0,vf->priv->exp_y+vf->h); + } +} + +static void draw_slice(struct vf_instance *vf, + unsigned char** src, int* stride, int w,int h, int x, int y){ +// printf("draw_slice() called %d at %d\n",h,y); + + if (y == 0 && y+h == vf->h) { + // special case - only one slice + draw_top_blackbar_slice(vf, src, stride, w, h, x, y); + vf_next_draw_slice(vf,src,stride,w,h,x+vf->priv->exp_x,y+vf->priv->exp_y); + draw_bottom_blackbar_slice(vf, src, stride, w, h, x, y); + return; + } + if (vf->priv->first_slice) { + draw_top_blackbar_slice(vf, src, stride, w, h, x, y); + draw_bottom_blackbar_slice(vf, src, stride, w, h, x, y); + } + vf_next_draw_slice(vf,src,stride,w,h,x+vf->priv->exp_x,y+vf->priv->exp_y); + if (!vf->priv->first_slice) { + draw_top_blackbar_slice(vf, src, stride, w, h, x, y); + draw_bottom_blackbar_slice(vf, src, stride, w, h, x, y); + } + vf->priv->first_slice = 0; +} + +// w, h = width and height of the actual video frame (located at exp_x/exp_y) +static void clear_borders(struct vf_instance *vf, int w, int h) +{ + // upper border (over the full width) + vf_mpi_clear(vf->dmpi, 0, 0, vf->priv->exp_w, vf->priv->exp_y); + // lower border + vf_mpi_clear(vf->dmpi, 0, vf->priv->exp_y + h, vf->priv->exp_w, + vf->priv->exp_h - (vf->priv->exp_y + h)); + // left + vf_mpi_clear(vf->dmpi, 0, vf->priv->exp_y, vf->priv->exp_x, h); + // right + vf_mpi_clear(vf->dmpi, vf->priv->exp_x + w, vf->priv->exp_y, + vf->priv->exp_w - (vf->priv->exp_x + w), h); +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts){ + if(mpi->flags&MP_IMGFLAG_DIRECT || mpi->flags&MP_IMGFLAG_DRAW_CALLBACK){ + vf->dmpi=mpi->priv; + if(!vf->dmpi) { mp_tmsg(MSGT_VFILTER, MSGL_WARN, "Why do we get NULL??\n"); return 0; } + mpi->priv=NULL; + clear_borders(vf,mpi->w,mpi->h); + // we've used DR, so we're ready... + if(!(mpi->flags&MP_IMGFLAG_PLANAR)) + vf->dmpi->planes[1] = mpi->planes[1]; // passthrough rgb8 palette + return vf_next_put_image(vf,vf->dmpi, pts); + } + + // hope we'll get DR buffer: + vf->dmpi=vf_get_image(vf->next,mpi->imgfmt, + MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE, + vf->priv->exp_w, vf->priv->exp_h); + + // copy mpi->dmpi... + if(mpi->flags&MP_IMGFLAG_PLANAR){ + memcpy_pic(vf->dmpi->planes[0]+ + vf->priv->exp_y*vf->dmpi->stride[0]+vf->priv->exp_x, + mpi->planes[0], mpi->w, mpi->h, + vf->dmpi->stride[0],mpi->stride[0]); + memcpy_pic(vf->dmpi->planes[1]+ + (vf->priv->exp_y>>mpi->chroma_y_shift)*vf->dmpi->stride[1]+(vf->priv->exp_x>>mpi->chroma_x_shift), + mpi->planes[1], (mpi->w>>mpi->chroma_x_shift), (mpi->h>>mpi->chroma_y_shift), + vf->dmpi->stride[1],mpi->stride[1]); + memcpy_pic(vf->dmpi->planes[2]+ + (vf->priv->exp_y>>mpi->chroma_y_shift)*vf->dmpi->stride[2]+(vf->priv->exp_x>>mpi->chroma_x_shift), + mpi->planes[2], (mpi->w>>mpi->chroma_x_shift), (mpi->h>>mpi->chroma_y_shift), + vf->dmpi->stride[2],mpi->stride[2]); + } else { + memcpy_pic(vf->dmpi->planes[0]+ + vf->priv->exp_y*vf->dmpi->stride[0]+vf->priv->exp_x*(vf->dmpi->bpp/8), + mpi->planes[0], mpi->w*(vf->dmpi->bpp/8), mpi->h, + vf->dmpi->stride[0],mpi->stride[0]); + vf->dmpi->planes[1] = mpi->planes[1]; // passthrough rgb8 palette + } + clear_borders(vf,mpi->w,mpi->h); + return vf_next_put_image(vf,vf->dmpi, pts); +} + +//===========================================================================// + +static int control(struct vf_instance *vf, int request, void* data){ + return vf_next_control(vf,request,data); +} + +static int query_format(struct vf_instance *vf, unsigned int fmt){ + return vf_next_query_format(vf,fmt); +} + +static int vf_open(vf_instance_t *vf, char *args){ + vf->config=config; + vf->control=control; + vf->query_format=query_format; + vf->start_slice=start_slice; + vf->draw_slice=draw_slice; + vf->get_image=get_image; + vf->put_image=put_image; + mp_msg(MSGT_VFILTER, MSGL_INFO, "Expand: %d x %d, %d ; %d, aspect: %f, round: %d\n", + vf->priv->cfg_exp_w, + vf->priv->cfg_exp_h, + vf->priv->cfg_exp_x, + vf->priv->cfg_exp_y, + vf->priv->aspect, + vf->priv->round); + return 1; +} + +#define ST_OFF(f) M_ST_OFF(struct vf_priv_s,f) +static m_option_t vf_opts_fields[] = { + {"w", ST_OFF(cfg_exp_w), CONF_TYPE_INT, 0, 0 ,0, NULL}, + {"h", ST_OFF(cfg_exp_h), CONF_TYPE_INT, 0, 0 ,0, NULL}, + {"x", ST_OFF(cfg_exp_x), CONF_TYPE_INT, M_OPT_MIN, -1, 0, NULL}, + {"y", ST_OFF(cfg_exp_y), CONF_TYPE_INT, M_OPT_MIN, -1, 0, NULL}, + {"aspect", ST_OFF(aspect), CONF_TYPE_DOUBLE, M_OPT_MIN, 0, 0, NULL}, + {"round", ST_OFF(round), CONF_TYPE_INT, M_OPT_MIN, 1, 0, NULL}, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; + +static const m_struct_t vf_opts = { + "expand", + sizeof(struct vf_priv_s), + &vf_priv_dflt, + vf_opts_fields +}; + + + +const vf_info_t vf_info_expand = { + "expanding", + "expand", + "A'rpi", + "", + vf_open, + &vf_opts +}; + +//===========================================================================// diff --git a/video/filter/vf_flip.c b/video/filter/vf_flip.c new file mode 100644 index 0000000000..e8660ceb51 --- /dev/null +++ b/video/filter/vf_flip.c @@ -0,0 +1,110 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "config.h" +#include "mp_msg.h" + +#include "mp_image.h" +#include "vf.h" + +#include "libvo/video_out.h" + +//===========================================================================// + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt){ + flags&=~VOFLAG_FLIPPING; // remove the FLIP flag + return vf_next_config(vf,width,height,d_width,d_height,flags,outfmt); +} + +static void get_image(struct vf_instance *vf, mp_image_t *mpi){ + if(mpi->flags&MP_IMGFLAG_ACCEPT_STRIDE){ + // try full DR ! + vf->dmpi=vf_get_image(vf->next,mpi->imgfmt, + mpi->type, mpi->flags, mpi->width, mpi->height); + // set up mpi as a upside-down image of dmpi: + mpi->planes[0]=vf->dmpi->planes[0]+ + vf->dmpi->stride[0]*(vf->dmpi->height-1); + mpi->stride[0]=-vf->dmpi->stride[0]; + if(mpi->flags&MP_IMGFLAG_PLANAR){ + mpi->planes[1]=vf->dmpi->planes[1]+ + vf->dmpi->stride[1]*((vf->dmpi->height>>mpi->chroma_y_shift)-1); + mpi->stride[1]=-vf->dmpi->stride[1]; + mpi->planes[2]=vf->dmpi->planes[2]+ + vf->dmpi->stride[2]*((vf->dmpi->height>>mpi->chroma_y_shift)-1); + mpi->stride[2]=-vf->dmpi->stride[2]; + } + mpi->flags|=MP_IMGFLAG_DIRECT; + mpi->priv=(void*)vf->dmpi; + } +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts){ + if(mpi->flags&MP_IMGFLAG_DIRECT){ + // we've used DR, so we're ready... + if(!(mpi->flags&MP_IMGFLAG_PLANAR)) + ((mp_image_t*)mpi->priv)->planes[1] = mpi->planes[1]; // passthrough rgb8 palette + return vf_next_put_image(vf,(mp_image_t*)mpi->priv, pts); + } + + vf->dmpi=vf_get_image(vf->next,mpi->imgfmt, + MP_IMGTYPE_EXPORT, MP_IMGFLAG_ACCEPT_STRIDE, + mpi->width, mpi->height); + + // set up mpi as a upside-down image of dmpi: + vf->dmpi->planes[0]=mpi->planes[0]+ + mpi->stride[0]*(mpi->height-1); + vf->dmpi->stride[0]=-mpi->stride[0]; + if(vf->dmpi->flags&MP_IMGFLAG_PLANAR){ + vf->dmpi->planes[1]=mpi->planes[1]+ + mpi->stride[1]*((mpi->height>>mpi->chroma_y_shift)-1); + vf->dmpi->stride[1]=-mpi->stride[1]; + vf->dmpi->planes[2]=mpi->planes[2]+ + mpi->stride[2]*((mpi->height>>mpi->chroma_y_shift)-1); + vf->dmpi->stride[2]=-mpi->stride[2]; + } else + vf->dmpi->planes[1]=mpi->planes[1]; // passthru bgr8 palette!!! + + return vf_next_put_image(vf,vf->dmpi, pts); +} + +//===========================================================================// + +static int vf_open(vf_instance_t *vf, char *args){ + vf->config=config; + vf->get_image=get_image; + vf->put_image=put_image; + vf->default_reqs=VFCAP_ACCEPT_STRIDE; + return 1; +} + +const vf_info_t vf_info_flip = { + "flip image upside-down", + "flip", + "A'rpi", + "", + vf_open, + NULL +}; + +//===========================================================================// diff --git a/video/filter/vf_format.c b/video/filter/vf_format.c new file mode 100644 index 0000000000..422956539b --- /dev/null +++ b/video/filter/vf_format.c @@ -0,0 +1,91 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include "config.h" +#include "mp_msg.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +#include "m_option.h" +#include "m_struct.h" + +static struct vf_priv_s { + unsigned int fmt; + unsigned int outfmt; +} const vf_priv_dflt = { + IMGFMT_YUY2, + 0 +}; + +//===========================================================================// + +static int query_format(struct vf_instance *vf, unsigned int fmt){ + if(fmt==vf->priv->fmt) { + if (vf->priv->outfmt) + fmt = vf->priv->outfmt; + return vf_next_query_format(vf,fmt); + } + return 0; +} + +static int config(struct vf_instance *vf, int width, int height, + int d_width, int d_height, + unsigned flags, unsigned outfmt){ + return vf_next_config(vf, width, height, d_width, d_height, flags, vf->priv->outfmt); +} + +static int vf_open(vf_instance_t *vf, char *args){ + vf->query_format=query_format; + vf->draw_slice=vf_next_draw_slice; + vf->default_caps=0; + if (vf->priv->outfmt) + vf->config=config; + return 1; +} + +#define ST_OFF(f) M_ST_OFF(struct vf_priv_s,f) +static const m_option_t vf_opts_fields[] = { + {"fmt", ST_OFF(fmt), CONF_TYPE_IMGFMT, 0,0 ,0, NULL}, + {"outfmt", ST_OFF(outfmt), CONF_TYPE_IMGFMT, 0,0 ,0, NULL}, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; + +static const m_struct_t vf_opts = { + "format", + sizeof(struct vf_priv_s), + &vf_priv_dflt, + vf_opts_fields +}; + +const vf_info_t vf_info_format = { + "force output format", + "format", + "A'rpi", + "FIXME! get_image()/put_image()", + vf_open, + &vf_opts +}; + +//===========================================================================// diff --git a/video/filter/vf_gradfun.c b/video/filter/vf_gradfun.c new file mode 100644 index 0000000000..eb73cfa2a4 --- /dev/null +++ b/video/filter/vf_gradfun.c @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2009 Loren Merritt <lorenm@u.washignton.edu> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Debanding algorithm (from gradfun2db by prunedtree): + * Boxblur. + * Foreach pixel, if it's within threshold of the blurred value, make it closer. + * So now we have a smoothed and higher bitdepth version of all the shallow + * gradients, while leaving detailed areas untouched. + * Dither it back to 8bit. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <math.h> + +#include <libavutil/common.h> + +#include "config.h" +#include "cpudetect.h" +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" +#include "libvo/fastmemcpy.h" +#include "ffmpeg_files/x86_cpu.h" + +#include "m_option.h" +#include "m_struct.h" + +struct vf_priv_s { + float cfg_thresh; + int cfg_radius; + float cfg_size; + int thresh; + int radius; + uint16_t *buf; + void (*filter_line)(uint8_t *dst, uint8_t *src, uint16_t *dc, + int width, int thresh, const uint16_t *dithers); + void (*blur_line)(uint16_t *dc, uint16_t *buf, uint16_t *buf1, + uint8_t *src, int sstride, int width); +} const vf_priv_dflt = { + .cfg_thresh = 1.5, + .cfg_radius = -1, + .cfg_size = -1, +}; + +static const uint16_t __attribute__((aligned(16))) pw_7f[8] = {127,127,127,127,127,127,127,127}; +static const uint16_t __attribute__((aligned(16))) pw_ff[8] = {255,255,255,255,255,255,255,255}; +static const uint16_t __attribute__((aligned(16))) dither[8][8] = { + { 0, 96, 24,120, 6,102, 30,126 }, + { 64, 32, 88, 56, 70, 38, 94, 62 }, + { 16,112, 8,104, 22,118, 14,110 }, + { 80, 48, 72, 40, 86, 54, 78, 46 }, + { 4,100, 28,124, 2, 98, 26,122 }, + { 68, 36, 92, 60, 66, 34, 90, 58 }, + { 20,116, 12,108, 18,114, 10,106 }, + { 84, 52, 76, 44, 82, 50, 74, 42 }, +}; + +static void filter_line_c(uint8_t *dst, uint8_t *src, uint16_t *dc, + int width, int thresh, const uint16_t *dithers) +{ + int x; + for (x=0; x<width; x++, dc+=x&1) { + int pix = src[x]<<7; + int delta = dc[0] - pix; + int m = abs(delta) * thresh >> 16; + m = FFMAX(0, 127-m); + m = m*m*delta >> 14; + pix += m + dithers[x&7]; + dst[x] = av_clip_uint8(pix>>7); + } +} + +static void blur_line_c(uint16_t *dc, uint16_t *buf, uint16_t *buf1, + uint8_t *src, int sstride, int width) +{ + int x, v, old; + for (x=0; x<width; x++) { + v = buf1[x] + src[2*x] + src[2*x+1] + src[2*x+sstride] + src[2*x+1+sstride]; + old = buf[x]; + buf[x] = v; + dc[x] = v - old; + } +} + +#if HAVE_MMX2 +static void filter_line_mmx2(uint8_t *dst, uint8_t *src, uint16_t *dc, + int width, int thresh, const uint16_t *dithers) +{ + intptr_t x; + if (width&3) { + x = width&~3; + filter_line_c(dst+x, src+x, dc+x/2, width-x, thresh, dithers); + width = x; + } + x = -width; + __asm__ volatile( + "movd %4, %%mm5 \n" + "pxor %%mm7, %%mm7 \n" + "pshufw $0, %%mm5, %%mm5 \n" + "movq %6, %%mm6 \n" + "movq %5, %%mm4 \n" + "1: \n" + "movd (%2,%0), %%mm0 \n" + "movd (%3,%0), %%mm1 \n" + "punpcklbw %%mm7, %%mm0 \n" + "punpcklwd %%mm1, %%mm1 \n" + "psllw $7, %%mm0 \n" + "pxor %%mm2, %%mm2 \n" + "psubw %%mm0, %%mm1 \n" // delta = dc - pix + "psubw %%mm1, %%mm2 \n" + "pmaxsw %%mm1, %%mm2 \n" + "pmulhuw %%mm5, %%mm2 \n" // m = abs(delta) * thresh >> 16 + "psubw %%mm6, %%mm2 \n" + "pminsw %%mm7, %%mm2 \n" // m = -max(0, 127-m) + "pmullw %%mm2, %%mm2 \n" + "paddw %%mm4, %%mm0 \n" // pix += dither + "pmulhw %%mm2, %%mm1 \n" + "psllw $2, %%mm1 \n" // m = m*m*delta >> 14 + "paddw %%mm1, %%mm0 \n" // pix += m + "psraw $7, %%mm0 \n" + "packuswb %%mm0, %%mm0 \n" + "movd %%mm0, (%1,%0) \n" // dst = clip(pix>>7) + "add $4, %0 \n" + "jl 1b \n" + "emms \n" + :"+r"(x) + :"r"(dst+width), "r"(src+width), "r"(dc+width/2), + "rm"(thresh), "m"(*dithers), "m"(*pw_7f) + :"memory" + ); +} +#endif + +#if HAVE_SSSE3 +static void filter_line_ssse3(uint8_t *dst, uint8_t *src, uint16_t *dc, + int width, int thresh, const uint16_t *dithers) +{ + intptr_t x; + if (width&7) { + // could be 10% faster if I somehow eliminated this + x = width&~7; + filter_line_c(dst+x, src+x, dc+x/2, width-x, thresh, dithers); + width = x; + } + x = -width; + __asm__ volatile( + "movd %4, %%xmm5 \n" + "pxor %%xmm7, %%xmm7 \n" + "pshuflw $0,%%xmm5, %%xmm5 \n" + "movdqa %6, %%xmm6 \n" + "punpcklqdq %%xmm5, %%xmm5 \n" + "movdqa %5, %%xmm4 \n" + "1: \n" + "movq (%2,%0), %%xmm0 \n" + "movq (%3,%0), %%xmm1 \n" + "punpcklbw %%xmm7, %%xmm0 \n" + "punpcklwd %%xmm1, %%xmm1 \n" + "psllw $7, %%xmm0 \n" + "psubw %%xmm0, %%xmm1 \n" // delta = dc - pix + "pabsw %%xmm1, %%xmm2 \n" + "pmulhuw %%xmm5, %%xmm2 \n" // m = abs(delta) * thresh >> 16 + "psubw %%xmm6, %%xmm2 \n" + "pminsw %%xmm7, %%xmm2 \n" // m = -max(0, 127-m) + "pmullw %%xmm2, %%xmm2 \n" + "psllw $1, %%xmm2 \n" + "paddw %%xmm4, %%xmm0 \n" // pix += dither + "pmulhrsw %%xmm2, %%xmm1 \n" // m = m*m*delta >> 14 + "paddw %%xmm1, %%xmm0 \n" // pix += m + "psraw $7, %%xmm0 \n" + "packuswb %%xmm0, %%xmm0 \n" + "movq %%xmm0, (%1,%0) \n" // dst = clip(pix>>7) + "add $8, %0 \n" + "jl 1b \n" + :"+&r"(x) + :"r"(dst+width), "r"(src+width), "r"(dc+width/2), + "rm"(thresh), "m"(*dithers), "m"(*pw_7f) + :"memory" + ); +} +#endif // HAVE_SSSE3 + +#if HAVE_SSE2 && HAVE_6REGS +#define BLURV(load)\ + intptr_t x = -2*width;\ + __asm__ volatile(\ + "movdqa %6, %%xmm7 \n"\ + "1: \n"\ + load" (%4,%0), %%xmm0 \n"\ + load" (%5,%0), %%xmm1 \n"\ + "movdqa %%xmm0, %%xmm2 \n"\ + "movdqa %%xmm1, %%xmm3 \n"\ + "psrlw $8, %%xmm0 \n"\ + "psrlw $8, %%xmm1 \n"\ + "pand %%xmm7, %%xmm2 \n"\ + "pand %%xmm7, %%xmm3 \n"\ + "paddw %%xmm1, %%xmm0 \n"\ + "paddw %%xmm3, %%xmm2 \n"\ + "paddw %%xmm2, %%xmm0 \n"\ + "paddw (%2,%0), %%xmm0 \n"\ + "movdqa (%1,%0), %%xmm1 \n"\ + "movdqa %%xmm0, (%1,%0) \n"\ + "psubw %%xmm1, %%xmm0 \n"\ + "movdqa %%xmm0, (%3,%0) \n"\ + "add $16, %0 \n"\ + "jl 1b \n"\ + :"+&r"(x)\ + :"r"(buf+width),\ + "r"(buf1+width),\ + "r"(dc+width),\ + "r"(src+width*2),\ + "r"(src+width*2+sstride),\ + "m"(*pw_ff)\ + :"memory"\ + ); + +static void blur_line_sse2(uint16_t *dc, uint16_t *buf, uint16_t *buf1, + uint8_t *src, int sstride, int width) +{ + if (((intptr_t)src|sstride)&15) { + BLURV("movdqu"); + } else { + BLURV("movdqa"); + } +} +#endif // HAVE_6REGS && HAVE_SSE2 + +static void filter(struct vf_priv_s *ctx, uint8_t *dst, uint8_t *src, + int width, int height, int dstride, int sstride, int r) +{ + int bstride = ((width+15)&~15)/2; + int y; + uint32_t dc_factor = (1<<21)/(r*r); + uint16_t *dc = ctx->buf+16; + uint16_t *buf = ctx->buf+bstride+32; + int thresh = ctx->thresh; + + memset(dc, 0, (bstride+16)*sizeof(*buf)); + for (y=0; y<r; y++) + ctx->blur_line(dc, buf+y*bstride, buf+(y-1)*bstride, src+2*y*sstride, sstride, width/2); + for (;;) { + if (y < height-r) { + int mod = ((y+r)/2)%r; + uint16_t *buf0 = buf+mod*bstride; + uint16_t *buf1 = buf+(mod?mod-1:r-1)*bstride; + int x, v; + ctx->blur_line(dc, buf0, buf1, src+(y+r)*sstride, sstride, width/2); + for (x=v=0; x<r; x++) + v += dc[x]; + for (; x<width/2; x++) { + v += dc[x] - dc[x-r]; + dc[x-r] = v * dc_factor >> 16; + } + for (; x<(width+r+1)/2; x++) + dc[x-r] = v * dc_factor >> 16; + for (x=-r/2; x<0; x++) + dc[x] = dc[0]; + } + if (y == r) { + for (y=0; y<r; y++) + ctx->filter_line(dst+y*dstride, src+y*sstride, dc-r/2, width, thresh, dither[y&7]); + } + ctx->filter_line(dst+y*dstride, src+y*sstride, dc-r/2, width, thresh, dither[y&7]); + if (++y >= height) break; + ctx->filter_line(dst+y*dstride, src+y*sstride, dc-r/2, width, thresh, dither[y&7]); + if (++y >= height) break; + } +} + +static void get_image(struct vf_instance *vf, mp_image_t *mpi) +{ + if (mpi->flags&MP_IMGFLAG_PRESERVE) return; // don't change + // ok, we can do pp in-place: + vf->dmpi = vf_get_image(vf->next, mpi->imgfmt, + mpi->type, mpi->flags, mpi->width, mpi->height); + mpi->planes[0] = vf->dmpi->planes[0]; + mpi->stride[0] = vf->dmpi->stride[0]; + mpi->width = vf->dmpi->width; + if (mpi->flags&MP_IMGFLAG_PLANAR){ + mpi->planes[1] = vf->dmpi->planes[1]; + mpi->planes[2] = vf->dmpi->planes[2]; + mpi->stride[1] = vf->dmpi->stride[1]; + mpi->stride[2] = vf->dmpi->stride[2]; + } + mpi->flags |= MP_IMGFLAG_DIRECT; +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts) +{ + mp_image_t *dmpi = vf->dmpi; + int p; + + if (!(mpi->flags&MP_IMGFLAG_DIRECT)) { + // no DR, so get a new image. hope we'll get DR buffer: + dmpi = vf_get_image(vf->next,mpi->imgfmt, MP_IMGTYPE_TEMP, + MP_IMGFLAG_ACCEPT_STRIDE|MP_IMGFLAG_PREFER_ALIGNED_STRIDE, + mpi->w, mpi->h); + } + vf_clone_mpi_attributes(dmpi, mpi); + + for (p=0; p<mpi->num_planes; p++) { + int w = mpi->w; + int h = mpi->h; + int r = vf->priv->radius; + if (p) { + w >>= mpi->chroma_x_shift; + h >>= mpi->chroma_y_shift; + r = ((r>>mpi->chroma_x_shift) + (r>>mpi->chroma_y_shift)) / 2; + r = av_clip((r+1)&~1,4,32); + } + if (FFMIN(w,h) > 2*r) + filter(vf->priv, dmpi->planes[p], mpi->planes[p], w, h, + dmpi->stride[p], mpi->stride[p], r); + else if (dmpi->planes[p] != mpi->planes[p]) + memcpy_pic(dmpi->planes[p], mpi->planes[p], w, h, + dmpi->stride[p], mpi->stride[p]); + } + + return vf_next_put_image(vf, dmpi, pts); +} + +static int query_format(struct vf_instance *vf, unsigned int fmt) +{ + switch (fmt){ + case IMGFMT_YVU9: + case IMGFMT_IF09: + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + case IMGFMT_CLPL: + case IMGFMT_Y800: + case IMGFMT_Y8: + case IMGFMT_NV12: + case IMGFMT_NV21: + case IMGFMT_444P: + case IMGFMT_422P: + case IMGFMT_411P: + case IMGFMT_HM12: + return vf_next_query_format(vf,fmt); + } + return 0; +} + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt) +{ + free(vf->priv->buf); + vf->priv->radius = vf->priv->cfg_radius; + if (vf->priv->cfg_size > -1) { + vf->priv->radius = (vf->priv->cfg_size / 100.0f) + * sqrtf(width * width + height * height); + } + vf->priv->radius = av_clip((vf->priv->radius+1)&~1, 4, 32); + vf->priv->buf = av_mallocz((((width+15)&~15)*(vf->priv->radius+1)/2+32)*sizeof(uint16_t)); + return vf_next_config(vf,width,height,d_width,d_height,flags,outfmt); +} + +static void uninit(struct vf_instance *vf) +{ + if (!vf->priv) return; + av_free(vf->priv->buf); + free(vf->priv); + vf->priv = NULL; +} + +static int vf_open(vf_instance_t *vf, char *args) +{ + vf->get_image=get_image; + vf->put_image=put_image; + vf->query_format=query_format; + vf->config=config; + vf->uninit=uninit; + + bool have_radius = vf->priv->cfg_radius > -1; + bool have_size = vf->priv->cfg_size > -1; + + if (have_radius && have_size) { + mp_msg(MSGT_VFILTER, MSGL_ERR, "scale: gradfun: only one of " + "radius/size parameters allowed at the same time!\n"); + return 0; + } + + if (!have_radius && !have_size) + vf->priv->cfg_size = 1.0; + + vf->priv->thresh = (1<<15)/av_clipf(vf->priv->cfg_thresh,0.51,255); + + vf->priv->blur_line = blur_line_c; + vf->priv->filter_line = filter_line_c; +#if HAVE_SSE2 && HAVE_6REGS + if (gCpuCaps.hasSSE2) + vf->priv->blur_line = blur_line_sse2; +#endif +#if HAVE_MMX2 + if (gCpuCaps.hasMMX2) + vf->priv->filter_line = filter_line_mmx2; +#endif +#if HAVE_SSSE3 + if (gCpuCaps.hasSSSE3) + vf->priv->filter_line = filter_line_ssse3; +#endif + + return 1; +} + +#undef ST_OFF +#define ST_OFF(f) M_ST_OFF(struct vf_priv_s,f) +static const m_option_t vf_opts_fields[] = { + {"strength", ST_OFF(cfg_thresh), CONF_TYPE_FLOAT, M_OPT_RANGE, 0.51, 255, NULL}, + {"radius", ST_OFF(cfg_radius), CONF_TYPE_INT, M_OPT_RANGE, 4, 32, NULL}, + {"size", ST_OFF(cfg_size), CONF_TYPE_FLOAT, M_OPT_RANGE, 0.1, 5.0, NULL}, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; + +static const m_struct_t vf_opts = { + "gradfun", + sizeof(struct vf_priv_s), + &vf_priv_dflt, + vf_opts_fields +}; + +const vf_info_t vf_info_gradfun = { + "gradient deband", + "gradfun", + "Loren Merritt", + "", + vf_open, + &vf_opts +}; diff --git a/video/filter/vf_hqdn3d.c b/video/filter/vf_hqdn3d.c new file mode 100644 index 0000000000..95cdba1ef5 --- /dev/null +++ b/video/filter/vf_hqdn3d.c @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2003 Daniel Moreno <comac@comac.darktech.org> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <math.h> + +#include "mp_msg.h" +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +#define PARAM1_DEFAULT 4.0 +#define PARAM2_DEFAULT 3.0 +#define PARAM3_DEFAULT 6.0 + +//===========================================================================// + +struct vf_priv_s { + int Coefs[4][512*16]; + unsigned int *Line; + unsigned short *Frame[3]; +}; + + +/***************************************************************************/ + +static void uninit(struct vf_instance *vf) +{ + free(vf->priv->Line); + free(vf->priv->Frame[0]); + free(vf->priv->Frame[1]); + free(vf->priv->Frame[2]); + + vf->priv->Line = NULL; + vf->priv->Frame[0] = NULL; + vf->priv->Frame[1] = NULL; + vf->priv->Frame[2] = NULL; +} + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt){ + + uninit(vf); + vf->priv->Line = malloc(width*sizeof(int)); + + return vf_next_config(vf,width,height,d_width,d_height,flags,outfmt); +} + +static inline unsigned int LowPassMul(unsigned int PrevMul, unsigned int CurrMul, int* Coef){ +// int dMul= (PrevMul&0xFFFFFF)-(CurrMul&0xFFFFFF); + int dMul= PrevMul-CurrMul; + unsigned int d=((dMul+0x10007FF)>>12); + return CurrMul + Coef[d]; +} + +static void deNoiseTemporal( + unsigned char *Frame, // mpi->planes[x] + unsigned char *FrameDest, // dmpi->planes[x] + unsigned short *FrameAnt, + int W, int H, int sStride, int dStride, + int *Temporal) +{ + long X, Y; + unsigned int PixelDst; + + for (Y = 0; Y < H; Y++){ + for (X = 0; X < W; X++){ + PixelDst = LowPassMul(FrameAnt[X]<<8, Frame[X]<<16, Temporal); + FrameAnt[X] = ((PixelDst+0x1000007F)>>8); + FrameDest[X]= ((PixelDst+0x10007FFF)>>16); + } + Frame += sStride; + FrameDest += dStride; + FrameAnt += W; + } +} + +static void deNoiseSpacial( + unsigned char *Frame, // mpi->planes[x] + unsigned char *FrameDest, // dmpi->planes[x] + unsigned int *LineAnt, // vf->priv->Line (width bytes) + int W, int H, int sStride, int dStride, + int *Horizontal, int *Vertical) +{ + long X, Y; + long sLineOffs = 0, dLineOffs = 0; + unsigned int PixelAnt; + unsigned int PixelDst; + + /* First pixel has no left nor top neighbor. */ + PixelDst = LineAnt[0] = PixelAnt = Frame[0]<<16; + FrameDest[0]= ((PixelDst+0x10007FFF)>>16); + + /* First line has no top neighbor, only left. */ + for (X = 1; X < W; X++){ + PixelDst = LineAnt[X] = LowPassMul(PixelAnt, Frame[X]<<16, Horizontal); + FrameDest[X]= ((PixelDst+0x10007FFF)>>16); + } + + for (Y = 1; Y < H; Y++){ + unsigned int PixelAnt; + sLineOffs += sStride, dLineOffs += dStride; + /* First pixel on each line doesn't have previous pixel */ + PixelAnt = Frame[sLineOffs]<<16; + PixelDst = LineAnt[0] = LowPassMul(LineAnt[0], PixelAnt, Vertical); + FrameDest[dLineOffs]= ((PixelDst+0x10007FFF)>>16); + + for (X = 1; X < W; X++){ + unsigned int PixelDst; + /* The rest are normal */ + PixelAnt = LowPassMul(PixelAnt, Frame[sLineOffs+X]<<16, Horizontal); + PixelDst = LineAnt[X] = LowPassMul(LineAnt[X], PixelAnt, Vertical); + FrameDest[dLineOffs+X]= ((PixelDst+0x10007FFF)>>16); + } + } +} + +static void deNoise(unsigned char *Frame, // mpi->planes[x] + unsigned char *FrameDest, // dmpi->planes[x] + unsigned int *LineAnt, // vf->priv->Line (width bytes) + unsigned short **FrameAntPtr, + int W, int H, int sStride, int dStride, + int *Horizontal, int *Vertical, int *Temporal) +{ + long X, Y; + long sLineOffs = 0, dLineOffs = 0; + unsigned int PixelAnt; + unsigned int PixelDst; + unsigned short* FrameAnt=(*FrameAntPtr); + + if(!FrameAnt){ + (*FrameAntPtr)=FrameAnt=malloc(W*H*sizeof(unsigned short)); + for (Y = 0; Y < H; Y++){ + unsigned short* dst=&FrameAnt[Y*W]; + unsigned char* src=Frame+Y*sStride; + for (X = 0; X < W; X++) dst[X]=src[X]<<8; + } + } + + if(!Horizontal[0] && !Vertical[0]){ + deNoiseTemporal(Frame, FrameDest, FrameAnt, + W, H, sStride, dStride, Temporal); + return; + } + if(!Temporal[0]){ + deNoiseSpacial(Frame, FrameDest, LineAnt, + W, H, sStride, dStride, Horizontal, Vertical); + return; + } + + /* First pixel has no left nor top neighbor. Only previous frame */ + LineAnt[0] = PixelAnt = Frame[0]<<16; + PixelDst = LowPassMul(FrameAnt[0]<<8, PixelAnt, Temporal); + FrameAnt[0] = ((PixelDst+0x1000007F)>>8); + FrameDest[0]= ((PixelDst+0x10007FFF)>>16); + + /* First line has no top neighbor. Only left one for each pixel and + * last frame */ + for (X = 1; X < W; X++){ + LineAnt[X] = PixelAnt = LowPassMul(PixelAnt, Frame[X]<<16, Horizontal); + PixelDst = LowPassMul(FrameAnt[X]<<8, PixelAnt, Temporal); + FrameAnt[X] = ((PixelDst+0x1000007F)>>8); + FrameDest[X]= ((PixelDst+0x10007FFF)>>16); + } + + for (Y = 1; Y < H; Y++){ + unsigned int PixelAnt; + unsigned short* LinePrev=&FrameAnt[Y*W]; + sLineOffs += sStride, dLineOffs += dStride; + /* First pixel on each line doesn't have previous pixel */ + PixelAnt = Frame[sLineOffs]<<16; + LineAnt[0] = LowPassMul(LineAnt[0], PixelAnt, Vertical); + PixelDst = LowPassMul(LinePrev[0]<<8, LineAnt[0], Temporal); + LinePrev[0] = ((PixelDst+0x1000007F)>>8); + FrameDest[dLineOffs]= ((PixelDst+0x10007FFF)>>16); + + for (X = 1; X < W; X++){ + unsigned int PixelDst; + /* The rest are normal */ + PixelAnt = LowPassMul(PixelAnt, Frame[sLineOffs+X]<<16, Horizontal); + LineAnt[X] = LowPassMul(LineAnt[X], PixelAnt, Vertical); + PixelDst = LowPassMul(LinePrev[X]<<8, LineAnt[X], Temporal); + LinePrev[X] = ((PixelDst+0x1000007F)>>8); + FrameDest[dLineOffs+X]= ((PixelDst+0x10007FFF)>>16); + } + } +} + + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts){ + int cw= mpi->w >> mpi->chroma_x_shift; + int ch= mpi->h >> mpi->chroma_y_shift; + int W = mpi->w, H = mpi->h; + + mp_image_t *dmpi=vf_get_image(vf->next,mpi->imgfmt, + MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE, + mpi->w,mpi->h); + + if(!dmpi) return 0; + + deNoise(mpi->planes[0], dmpi->planes[0], + vf->priv->Line, &vf->priv->Frame[0], W, H, + mpi->stride[0], dmpi->stride[0], + vf->priv->Coefs[0], + vf->priv->Coefs[0], + vf->priv->Coefs[1]); + deNoise(mpi->planes[1], dmpi->planes[1], + vf->priv->Line, &vf->priv->Frame[1], cw, ch, + mpi->stride[1], dmpi->stride[1], + vf->priv->Coefs[2], + vf->priv->Coefs[2], + vf->priv->Coefs[3]); + deNoise(mpi->planes[2], dmpi->planes[2], + vf->priv->Line, &vf->priv->Frame[2], cw, ch, + mpi->stride[2], dmpi->stride[2], + vf->priv->Coefs[2], + vf->priv->Coefs[2], + vf->priv->Coefs[3]); + + return vf_next_put_image(vf,dmpi, pts); +} + +//===========================================================================// + +static int query_format(struct vf_instance *vf, unsigned int fmt){ + switch(fmt) + { + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + case IMGFMT_YVU9: + case IMGFMT_444P: + case IMGFMT_422P: + case IMGFMT_411P: + return vf_next_query_format(vf, fmt); + } + return 0; +} + + +#define ABS(A) ( (A) > 0 ? (A) : -(A) ) + +static void PrecalcCoefs(int *Ct, double Dist25) +{ + int i; + double Gamma, Simil, C; + + Gamma = log(0.25) / log(1.0 - Dist25/255.0 - 0.00001); + + for (i = -255*16; i <= 255*16; i++) + { + Simil = 1.0 - ABS(i) / (16*255.0); + C = pow(Simil, Gamma) * 65536.0 * (double)i / 16.0; + Ct[16*256+i] = (C<0) ? (C-0.5) : (C+0.5); + } + + Ct[0] = (Dist25 != 0); +} + + +static int vf_open(vf_instance_t *vf, char *args){ + double LumSpac, LumTmp, ChromSpac, ChromTmp; + double Param1, Param2, Param3, Param4; + + vf->config=config; + vf->put_image=put_image; + vf->query_format=query_format; + vf->uninit=uninit; + vf->priv=malloc(sizeof(struct vf_priv_s)); + memset(vf->priv, 0, sizeof(struct vf_priv_s)); + + if (args) + { + switch(sscanf(args, "%lf:%lf:%lf:%lf", + &Param1, &Param2, &Param3, &Param4 + )) + { + case 0: + LumSpac = PARAM1_DEFAULT; + LumTmp = PARAM3_DEFAULT; + + ChromSpac = PARAM2_DEFAULT; + ChromTmp = LumTmp * ChromSpac / LumSpac; + break; + + case 1: + LumSpac = Param1; + LumTmp = PARAM3_DEFAULT * Param1 / PARAM1_DEFAULT; + + ChromSpac = PARAM2_DEFAULT * Param1 / PARAM1_DEFAULT; + ChromTmp = LumTmp * ChromSpac / LumSpac; + break; + + case 2: + LumSpac = Param1; + LumTmp = PARAM3_DEFAULT * Param1 / PARAM1_DEFAULT; + + ChromSpac = Param2; + ChromTmp = LumTmp * ChromSpac / LumSpac; + break; + + case 3: + LumSpac = Param1; + LumTmp = Param3; + + ChromSpac = Param2; + ChromTmp = LumTmp * ChromSpac / LumSpac; + break; + + case 4: + LumSpac = Param1; + LumTmp = Param3; + + ChromSpac = Param2; + ChromTmp = Param4; + break; + + default: + LumSpac = PARAM1_DEFAULT; + LumTmp = PARAM3_DEFAULT; + + ChromSpac = PARAM2_DEFAULT; + ChromTmp = LumTmp * ChromSpac / LumSpac; + } + } + else + { + LumSpac = PARAM1_DEFAULT; + LumTmp = PARAM3_DEFAULT; + + ChromSpac = PARAM2_DEFAULT; + ChromTmp = LumTmp * ChromSpac / LumSpac; + } + + PrecalcCoefs(vf->priv->Coefs[0], LumSpac); + PrecalcCoefs(vf->priv->Coefs[1], LumTmp); + PrecalcCoefs(vf->priv->Coefs[2], ChromSpac); + PrecalcCoefs(vf->priv->Coefs[3], ChromTmp); + + return 1; +} + +const vf_info_t vf_info_hqdn3d = { + "High Quality 3D Denoiser", + "hqdn3d", + "Daniel Moreno & A'rpi", + "", + vf_open, + NULL +}; + +//===========================================================================// diff --git a/video/filter/vf_ilpack.c b/video/filter/vf_ilpack.c new file mode 100644 index 0000000000..e98d70d85d --- /dev/null +++ b/video/filter/vf_ilpack.c @@ -0,0 +1,457 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include "config.h" +#include "mp_msg.h" +#include "cpudetect.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" +#include "libavutil/attributes.h" + +typedef void (pack_func_t)(unsigned char *dst, unsigned char *y, + unsigned char *u, unsigned char *v, int w, int us, int vs); + +struct vf_priv_s { + int mode; + pack_func_t *pack[2]; +}; + +static void pack_nn_C(unsigned char *dst, unsigned char *y, + unsigned char *u, unsigned char *v, int w, + int av_unused us, int av_unused vs) +{ + int j; + for (j = w/2; j; j--) { + *dst++ = *y++; + *dst++ = *u++; + *dst++ = *y++; + *dst++ = *v++; + } +} + +static void pack_li_0_C(unsigned char *dst, unsigned char *y, + unsigned char *u, unsigned char *v, int w, int us, int vs) +{ + int j; + for (j = w/2; j; j--) { + *dst++ = *y++; + *dst++ = (u[us+us] + 7*u[0])>>3; + *dst++ = *y++; + *dst++ = (v[vs+vs] + 7*v[0])>>3; + u++; v++; + } +} + +static void pack_li_1_C(unsigned char *dst, unsigned char *y, + unsigned char *u, unsigned char *v, int w, int us, int vs) +{ + int j; + for (j = w/2; j; j--) { + *dst++ = *y++; + *dst++ = (3*u[us+us] + 5*u[0])>>3; + *dst++ = *y++; + *dst++ = (3*v[vs+vs] + 5*v[0])>>3; + u++; v++; + } +} + +#if HAVE_MMX +static void pack_nn_MMX(unsigned char *dst, unsigned char *y, + unsigned char *u, unsigned char *v, int w, + int av_unused us, int av_unused vs) +{ + __asm__ volatile ("" + ASMALIGN(4) + "1: \n\t" + "movq (%0), %%mm1 \n\t" + "movq (%0), %%mm2 \n\t" + "movq (%1), %%mm4 \n\t" + "movq (%2), %%mm6 \n\t" + "punpcklbw %%mm6, %%mm4 \n\t" + "punpcklbw %%mm4, %%mm1 \n\t" + "punpckhbw %%mm4, %%mm2 \n\t" + + "add $8, %0 \n\t" + "add $4, %1 \n\t" + "add $4, %2 \n\t" + "movq %%mm1, (%3) \n\t" + "movq %%mm2, 8(%3) \n\t" + "add $16, %3 \n\t" + "decl %4 \n\t" + "jnz 1b \n\t" + "emms \n\t" + : + : "r" (y), "r" (u), "r" (v), "r" (dst), "r" (w/8) + : "memory" + ); + pack_nn_C(dst, y, u, v, (w&7), 0, 0); +} + +#if HAVE_EBX_AVAILABLE +static void pack_li_0_MMX(unsigned char *dst, unsigned char *y, + unsigned char *u, unsigned char *v, int w, int us, int vs) +{ + __asm__ volatile ("" + "push %%"REG_BP" \n\t" +#if ARCH_X86_64 + "mov %6, %%"REG_BP" \n\t" +#else + "movl 4(%%"REG_d"), %%"REG_BP" \n\t" + "movl (%%"REG_d"), %%"REG_d" \n\t" +#endif + "pxor %%mm0, %%mm0 \n\t" + + ASMALIGN(4) + ".Lli0: \n\t" + "movq (%%"REG_S"), %%mm1 \n\t" + "movq (%%"REG_S"), %%mm2 \n\t" + + "movq (%%"REG_a",%%"REG_d",2), %%mm4 \n\t" + "movq (%%"REG_b",%%"REG_BP",2), %%mm6 \n\t" + "punpcklbw %%mm0, %%mm4 \n\t" + "punpcklbw %%mm0, %%mm6 \n\t" + "movq (%%"REG_a"), %%mm3 \n\t" + "movq (%%"REG_b"), %%mm5 \n\t" + "punpcklbw %%mm0, %%mm3 \n\t" + "punpcklbw %%mm0, %%mm5 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "psrlw $3, %%mm4 \n\t" + "psrlw $3, %%mm6 \n\t" + "packuswb %%mm4, %%mm4 \n\t" + "packuswb %%mm6, %%mm6 \n\t" + "punpcklbw %%mm6, %%mm4 \n\t" + "punpcklbw %%mm4, %%mm1 \n\t" + "punpckhbw %%mm4, %%mm2 \n\t" + + "movq %%mm1, (%%"REG_D") \n\t" + "movq %%mm2, 8(%%"REG_D") \n\t" + + "movq 8(%%"REG_S"), %%mm1 \n\t" + "movq 8(%%"REG_S"), %%mm2 \n\t" + + "movq (%%"REG_a",%%"REG_d",2), %%mm4 \n\t" + "movq (%%"REG_b",%%"REG_BP",2), %%mm6 \n\t" + "punpckhbw %%mm0, %%mm4 \n\t" + "punpckhbw %%mm0, %%mm6 \n\t" + "movq (%%"REG_a"), %%mm3 \n\t" + "movq (%%"REG_b"), %%mm5 \n\t" + "punpckhbw %%mm0, %%mm3 \n\t" + "punpckhbw %%mm0, %%mm5 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "psrlw $3, %%mm4 \n\t" + "psrlw $3, %%mm6 \n\t" + "packuswb %%mm4, %%mm4 \n\t" + "packuswb %%mm6, %%mm6 \n\t" + "punpcklbw %%mm6, %%mm4 \n\t" + "punpcklbw %%mm4, %%mm1 \n\t" + "punpckhbw %%mm4, %%mm2 \n\t" + + "add $16, %%"REG_S" \n\t" + "add $8, %%"REG_a" \n\t" + "add $8, %%"REG_b" \n\t" + + "movq %%mm1, 16(%%"REG_D") \n\t" + "movq %%mm2, 24(%%"REG_D") \n\t" + "add $32, %%"REG_D" \n\t" + + "decl %%ecx \n\t" + "jnz .Lli0 \n\t" + "emms \n\t" + "pop %%"REG_BP" \n\t" + : + : "S" (y), "D" (dst), "a" (u), "b" (v), "c" (w/16), +#if ARCH_X86_64 + "d" ((x86_reg)us), "r" ((x86_reg)vs) +#else + "d" (&us) +#endif + : "memory" + ); + pack_li_0_C(dst, y, u, v, (w&15), us, vs); +} + +static void pack_li_1_MMX(unsigned char *dst, unsigned char *y, + unsigned char *u, unsigned char *v, int w, int us, int vs) +{ + __asm__ volatile ("" + "push %%"REG_BP" \n\t" +#if ARCH_X86_64 + "mov %6, %%"REG_BP" \n\t" +#else + "movl 4(%%"REG_d"), %%"REG_BP" \n\t" + "movl (%%"REG_d"), %%"REG_d" \n\t" +#endif + "pxor %%mm0, %%mm0 \n\t" + + ASMALIGN(4) + ".Lli1: \n\t" + "movq (%%"REG_S"), %%mm1 \n\t" + "movq (%%"REG_S"), %%mm2 \n\t" + + "movq (%%"REG_a",%%"REG_d",2), %%mm4 \n\t" + "movq (%%"REG_b",%%"REG_BP",2), %%mm6 \n\t" + "punpcklbw %%mm0, %%mm4 \n\t" + "punpcklbw %%mm0, %%mm6 \n\t" + "movq (%%"REG_a"), %%mm3 \n\t" + "movq (%%"REG_b"), %%mm5 \n\t" + "punpcklbw %%mm0, %%mm3 \n\t" + "punpcklbw %%mm0, %%mm5 \n\t" + "movq %%mm4, %%mm7 \n\t" + "paddw %%mm4, %%mm4 \n\t" + "paddw %%mm7, %%mm4 \n\t" + "movq %%mm6, %%mm7 \n\t" + "paddw %%mm6, %%mm6 \n\t" + "paddw %%mm7, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "psrlw $3, %%mm4 \n\t" + "psrlw $3, %%mm6 \n\t" + "packuswb %%mm4, %%mm4 \n\t" + "packuswb %%mm6, %%mm6 \n\t" + "punpcklbw %%mm6, %%mm4 \n\t" + "punpcklbw %%mm4, %%mm1 \n\t" + "punpckhbw %%mm4, %%mm2 \n\t" + + "movq %%mm1, (%%"REG_D") \n\t" + "movq %%mm2, 8(%%"REG_D") \n\t" + + "movq 8(%%"REG_S"), %%mm1 \n\t" + "movq 8(%%"REG_S"), %%mm2 \n\t" + + "movq (%%"REG_a",%%"REG_d",2), %%mm4 \n\t" + "movq (%%"REG_b",%%"REG_BP",2), %%mm6 \n\t" + "punpckhbw %%mm0, %%mm4 \n\t" + "punpckhbw %%mm0, %%mm6 \n\t" + "movq (%%"REG_a"), %%mm3 \n\t" + "movq (%%"REG_b"), %%mm5 \n\t" + "punpckhbw %%mm0, %%mm3 \n\t" + "punpckhbw %%mm0, %%mm5 \n\t" + "movq %%mm4, %%mm7 \n\t" + "paddw %%mm4, %%mm4 \n\t" + "paddw %%mm7, %%mm4 \n\t" + "movq %%mm6, %%mm7 \n\t" + "paddw %%mm6, %%mm6 \n\t" + "paddw %%mm7, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "paddw %%mm3, %%mm4 \n\t" + "paddw %%mm5, %%mm6 \n\t" + "psrlw $3, %%mm4 \n\t" + "psrlw $3, %%mm6 \n\t" + "packuswb %%mm4, %%mm4 \n\t" + "packuswb %%mm6, %%mm6 \n\t" + "punpcklbw %%mm6, %%mm4 \n\t" + "punpcklbw %%mm4, %%mm1 \n\t" + "punpckhbw %%mm4, %%mm2 \n\t" + + "add $16, %%"REG_S" \n\t" + "add $8, %%"REG_a" \n\t" + "add $8, %%"REG_b" \n\t" + + "movq %%mm1, 16(%%"REG_D") \n\t" + "movq %%mm2, 24(%%"REG_D") \n\t" + "add $32, %%"REG_D" \n\t" + + "decl %%ecx \n\t" + "jnz .Lli1 \n\t" + "emms \n\t" + "pop %%"REG_BP" \n\t" + : + : "S" (y), "D" (dst), "a" (u), "b" (v), "c" (w/16), +#if ARCH_X86_64 + "d" ((x86_reg)us), "r" ((x86_reg)vs) +#else + "d" (&us) +#endif + : "memory" + ); + pack_li_1_C(dst, y, u, v, (w&15), us, vs); +} +#endif /* HAVE_EBX_AVAILABLE */ +#endif + +static pack_func_t *pack_nn; +static pack_func_t *pack_li_0; +static pack_func_t *pack_li_1; + +static void ilpack(unsigned char *dst, unsigned char *src[3], + int dststride, int srcstride[3], int w, int h, pack_func_t *pack[2]) +{ + int i; + unsigned char *y, *u, *v; + int ys = srcstride[0], us = srcstride[1], vs = srcstride[2]; + int a, b; + + y = src[0]; + u = src[1]; + v = src[2]; + + pack_nn(dst, y, u, v, w, 0, 0); + y += ys; dst += dststride; + pack_nn(dst, y, u+us, v+vs, w, 0, 0); + y += ys; dst += dststride; + for (i=2; i<h-2; i++) { + a = (i&2) ? 1 : -1; + b = (i&1) ^ ((i&2)>>1); + pack[b](dst, y, u, v, w, us*a, vs*a); + y += ys; + if ((i&3) == 1) { + u -= us; + v -= vs; + } else { + u += us; + v += vs; + } + dst += dststride; + } + pack_nn(dst, y, u, v, w, 0, 0); + y += ys; dst += dststride; u += us; v += vs; + pack_nn(dst, y, u, v, w, 0, 0); +} + + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts) +{ + mp_image_t *dmpi; + + // hope we'll get DR buffer: + dmpi=vf_get_image(vf->next, IMGFMT_YUY2, + MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE, + mpi->w, mpi->h); + + ilpack(dmpi->planes[0], mpi->planes, dmpi->stride[0], mpi->stride, mpi->w, mpi->h, vf->priv->pack); + + return vf_next_put_image(vf,dmpi, pts); +} + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt) +{ + /* FIXME - also support UYVY output? */ + return vf_next_config(vf, width, height, d_width, d_height, flags, IMGFMT_YUY2); +} + + +static int query_format(struct vf_instance *vf, unsigned int fmt) +{ + /* FIXME - really any YUV 4:2:0 input format should work */ + switch (fmt) { + case IMGFMT_YV12: + case IMGFMT_IYUV: + case IMGFMT_I420: + return vf_next_query_format(vf,IMGFMT_YUY2); + } + return 0; +} + +static int vf_open(vf_instance_t *vf, char *args) +{ + vf->config=config; + vf->query_format=query_format; + vf->put_image=put_image; + vf->priv = calloc(1, sizeof(struct vf_priv_s)); + vf->priv->mode = 1; + if (args) sscanf(args, "%d", &vf->priv->mode); + + pack_nn = pack_nn_C; + pack_li_0 = pack_li_0_C; + pack_li_1 = pack_li_1_C; +#if HAVE_MMX + if(gCpuCaps.hasMMX) { + pack_nn = pack_nn_MMX; +#if HAVE_EBX_AVAILABLE + pack_li_0 = pack_li_0_MMX; + pack_li_1 = pack_li_1_MMX; +#endif + } +#endif + + switch(vf->priv->mode) { + case 0: + vf->priv->pack[0] = vf->priv->pack[1] = pack_nn; + break; + default: + mp_msg(MSGT_VFILTER, MSGL_WARN, + "ilpack: unknown mode %d (fallback to linear)\n", + vf->priv->mode); + /* Fallthrough */ + case 1: + vf->priv->pack[0] = pack_li_0; + vf->priv->pack[1] = pack_li_1; + break; + } + + return 1; +} + +const vf_info_t vf_info_ilpack = { + "4:2:0 planar -> 4:2:2 packed reinterlacer", + "ilpack", + "Richard Felker", + "", + vf_open, + NULL +}; diff --git a/video/filter/vf_mirror.c b/video/filter/vf_mirror.c new file mode 100644 index 0000000000..b48d9a2ef6 --- /dev/null +++ b/video/filter/vf_mirror.c @@ -0,0 +1,131 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include "config.h" +#include "mp_msg.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + + +static void mirror(unsigned char* dst,unsigned char* src,int dststride,int srcstride,int w,int h,int bpp,unsigned int fmt){ + int y; + for(y=0;y<h;y++){ + int x; + switch(bpp){ + case 1: + for(x=0;x<w;x++) dst[x]=src[w-x-1]; + break; + case 2: + switch(fmt){ + case IMGFMT_UYVY: { + // packed YUV is tricky. U,V are 32bpp while Y is 16bpp: + int w2=w>>1; + for(x=0;x<w2;x++){ + // TODO: optimize this... + dst[x*4+0]=src[0+(w2-x-1)*4]; + dst[x*4+1]=src[3+(w2-x-1)*4]; + dst[x*4+2]=src[2+(w2-x-1)*4]; + dst[x*4+3]=src[1+(w2-x-1)*4]; + } + break; } + case IMGFMT_YUY2: + case IMGFMT_YVYU: { + // packed YUV is tricky. U,V are 32bpp while Y is 16bpp: + int w2=w>>1; + for(x=0;x<w2;x++){ + // TODO: optimize this... + dst[x*4+0]=src[2+(w2-x-1)*4]; + dst[x*4+1]=src[1+(w2-x-1)*4]; + dst[x*4+2]=src[0+(w2-x-1)*4]; + dst[x*4+3]=src[3+(w2-x-1)*4]; + } + break; } + default: + for(x=0;x<w;x++) *((short*)(dst+x*2))=*((short*)(src+(w-x-1)*2)); + } + break; + case 3: + for(x=0;x<w;x++){ + dst[x*3+0]=src[0+(w-x-1)*3]; + dst[x*3+1]=src[1+(w-x-1)*3]; + dst[x*3+2]=src[2+(w-x-1)*3]; + } + break; + case 4: + for(x=0;x<w;x++) *((int*)(dst+x*4))=*((int*)(src+(w-x-1)*4)); + } + src+=srcstride; + dst+=dststride; + } +} + +//===========================================================================// + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts){ + mp_image_t *dmpi; + + // hope we'll get DR buffer: + dmpi=vf_get_image(vf->next,mpi->imgfmt, + MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE, + mpi->w, mpi->h); + + if(mpi->flags&MP_IMGFLAG_PLANAR){ + mirror(dmpi->planes[0],mpi->planes[0], + dmpi->stride[0],mpi->stride[0], + dmpi->w,dmpi->h,1,mpi->imgfmt); + mirror(dmpi->planes[1],mpi->planes[1], + dmpi->stride[1],mpi->stride[1], + dmpi->w>>mpi->chroma_x_shift,dmpi->h>>mpi->chroma_y_shift,1,mpi->imgfmt); + mirror(dmpi->planes[2],mpi->planes[2], + dmpi->stride[2],mpi->stride[2], + dmpi->w>>mpi->chroma_x_shift,dmpi->h>>mpi->chroma_y_shift,1,mpi->imgfmt); + } else { + mirror(dmpi->planes[0],mpi->planes[0], + dmpi->stride[0],mpi->stride[0], + dmpi->w,dmpi->h,dmpi->bpp>>3,mpi->imgfmt); + dmpi->planes[1]=mpi->planes[1]; // passthrough rgb8 palette + } + + return vf_next_put_image(vf,dmpi, pts); +} + +//===========================================================================// + +static int vf_open(vf_instance_t *vf, char *args){ + //vf->config=config; + vf->put_image=put_image; + return 1; +} + +const vf_info_t vf_info_mirror = { + "horizontal mirror", + "mirror", + "Eyck", + "", + vf_open, + NULL +}; + +//===========================================================================// diff --git a/video/filter/vf_noformat.c b/video/filter/vf_noformat.c new file mode 100644 index 0000000000..b143ac0005 --- /dev/null +++ b/video/filter/vf_noformat.c @@ -0,0 +1,77 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include "config.h" +#include "mp_msg.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +#include "m_option.h" +#include "m_struct.h" + +static struct vf_priv_s { + unsigned int fmt; +} const vf_priv_dflt = { + IMGFMT_YV12 +}; + +//===========================================================================// + +static int query_format(struct vf_instance *vf, unsigned int fmt){ + if(fmt!=vf->priv->fmt) + return vf_next_query_format(vf,fmt); + return 0; +} + +static int vf_open(vf_instance_t *vf, char *args){ + vf->query_format=query_format; + vf->draw_slice=vf_next_draw_slice; + vf->default_caps=0; + return 1; +} + +#define ST_OFF(f) M_ST_OFF(struct vf_priv_s,f) +static const m_option_t vf_opts_fields[] = { + {"fmt", ST_OFF(fmt), CONF_TYPE_IMGFMT, 0,0 ,0, NULL}, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; + +static const m_struct_t vf_opts = { + "noformat", + sizeof(struct vf_priv_s), + &vf_priv_dflt, + vf_opts_fields +}; + +const vf_info_t vf_info_noformat = { + "disallow one output format", + "noformat", + "Joey", + "", + vf_open, + &vf_opts +}; + +//===========================================================================// diff --git a/video/filter/vf_noise.c b/video/filter/vf_noise.c new file mode 100644 index 0000000000..87a480d655 --- /dev/null +++ b/video/filter/vf_noise.c @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2002 Michael Niedermayer <michaelni@gmx.at> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <math.h> + +#include "config.h" +#include "mp_msg.h" +#include "cpudetect.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" +#include "libvo/fastmemcpy.h" +#include "libavutil/mem.h" + +#define MAX_NOISE 4096 +#define MAX_SHIFT 1024 +#define MAX_RES (MAX_NOISE-MAX_SHIFT) + +//===========================================================================// + +static inline void lineNoise_C(uint8_t *dst, uint8_t *src, int8_t *noise, int len, int shift); +static inline void lineNoiseAvg_C(uint8_t *dst, uint8_t *src, int len, int8_t **shift); + +static void (*lineNoise)(uint8_t *dst, uint8_t *src, int8_t *noise, int len, int shift)= lineNoise_C; +static void (*lineNoiseAvg)(uint8_t *dst, uint8_t *src, int len, int8_t **shift)= lineNoiseAvg_C; + +typedef struct FilterParam{ + int strength; + int uniform; + int temporal; + int quality; + int averaged; + int pattern; + int shiftptr; + int8_t *noise; + int8_t *prev_shift[MAX_RES][3]; +}FilterParam; + +struct vf_priv_s { + FilterParam lumaParam; + FilterParam chromaParam; + unsigned int outfmt; +}; + +static int nonTempRandShift_init; +static int nonTempRandShift[MAX_RES]; + +static int patt[4] = { + -1,0,1,0 +}; + +#define RAND_N(range) ((int) ((double)range*rand()/(RAND_MAX+1.0))) +static int8_t *initNoise(FilterParam *fp){ + int strength= fp->strength; + int uniform= fp->uniform; + int averaged= fp->averaged; + int pattern= fp->pattern; + int8_t *noise= av_malloc(MAX_NOISE*sizeof(int8_t)); + int i, j; + + srand(123457); + + for(i=0,j=0; i<MAX_NOISE; i++,j++) + { + if(uniform) { + if (averaged) { + if (pattern) { + noise[i]= (RAND_N(strength) - strength/2)/6 + +patt[j%4]*strength*0.25/3; + } else { + noise[i]= (RAND_N(strength) - strength/2)/3; + } + } else { + if (pattern) { + noise[i]= (RAND_N(strength) - strength/2)/2 + + patt[j%4]*strength*0.25; + } else { + noise[i]= RAND_N(strength) - strength/2; + } + } + } else { + double x1, x2, w, y1; + do { + x1 = 2.0 * rand()/(float)RAND_MAX - 1.0; + x2 = 2.0 * rand()/(float)RAND_MAX - 1.0; + w = x1 * x1 + x2 * x2; + } while ( w >= 1.0 ); + + w = sqrt( (-2.0 * log( w ) ) / w ); + y1= x1 * w; + y1*= strength / sqrt(3.0); + if (pattern) { + y1 /= 2; + y1 += patt[j%4]*strength*0.35; + } + if (y1<-128) y1=-128; + else if(y1> 127) y1= 127; + if (averaged) y1 /= 3.0; + noise[i]= (int)y1; + } + if (RAND_N(6) == 0) j--; + } + + + for (i = 0; i < MAX_RES; i++) + for (j = 0; j < 3; j++) + fp->prev_shift[i][j] = noise + (rand()&(MAX_SHIFT-1)); + + if(!nonTempRandShift_init){ + for(i=0; i<MAX_RES; i++){ + nonTempRandShift[i]= rand()&(MAX_SHIFT-1); + } + nonTempRandShift_init = 1; + } + + fp->noise= noise; + fp->shiftptr= 0; + return noise; +} + +/***************************************************************************/ + +#if HAVE_MMX +static inline void lineNoise_MMX(uint8_t *dst, uint8_t *src, int8_t *noise, int len, int shift){ + x86_reg mmx_len= len&(~7); + noise+=shift; + + __asm__ volatile( + "mov %3, %%"REG_a" \n\t" + "pcmpeqb %%mm7, %%mm7 \n\t" + "psllw $15, %%mm7 \n\t" + "packsswb %%mm7, %%mm7 \n\t" + ASMALIGN(4) + "1: \n\t" + "movq (%0, %%"REG_a"), %%mm0 \n\t" + "movq (%1, %%"REG_a"), %%mm1 \n\t" + "pxor %%mm7, %%mm0 \n\t" + "paddsb %%mm1, %%mm0 \n\t" + "pxor %%mm7, %%mm0 \n\t" + "movq %%mm0, (%2, %%"REG_a") \n\t" + "add $8, %%"REG_a" \n\t" + " js 1b \n\t" + :: "r" (src+mmx_len), "r" (noise+mmx_len), "r" (dst+mmx_len), "g" (-mmx_len) + : "%"REG_a + ); + if(mmx_len!=len) + lineNoise_C(dst+mmx_len, src+mmx_len, noise+mmx_len, len-mmx_len, 0); +} +#endif + +//duplicate of previous except movntq +#if HAVE_MMX2 +static inline void lineNoise_MMX2(uint8_t *dst, uint8_t *src, int8_t *noise, int len, int shift){ + x86_reg mmx_len= len&(~7); + noise+=shift; + + __asm__ volatile( + "mov %3, %%"REG_a" \n\t" + "pcmpeqb %%mm7, %%mm7 \n\t" + "psllw $15, %%mm7 \n\t" + "packsswb %%mm7, %%mm7 \n\t" + ASMALIGN(4) + "1: \n\t" + "movq (%0, %%"REG_a"), %%mm0 \n\t" + "movq (%1, %%"REG_a"), %%mm1 \n\t" + "pxor %%mm7, %%mm0 \n\t" + "paddsb %%mm1, %%mm0 \n\t" + "pxor %%mm7, %%mm0 \n\t" + "movntq %%mm0, (%2, %%"REG_a") \n\t" + "add $8, %%"REG_a" \n\t" + " js 1b \n\t" + :: "r" (src+mmx_len), "r" (noise+mmx_len), "r" (dst+mmx_len), "g" (-mmx_len) + : "%"REG_a + ); + if(mmx_len!=len) + lineNoise_C(dst+mmx_len, src+mmx_len, noise+mmx_len, len-mmx_len, 0); +} +#endif + +static inline void lineNoise_C(uint8_t *dst, uint8_t *src, int8_t *noise, int len, int shift){ + int i; + noise+= shift; + for(i=0; i<len; i++) + { + int v= src[i]+ noise[i]; + if(v>255) dst[i]=255; //FIXME optimize + else if(v<0) dst[i]=0; + else dst[i]=v; + } +} + +/***************************************************************************/ + +#if HAVE_MMX +static inline void lineNoiseAvg_MMX(uint8_t *dst, uint8_t *src, int len, int8_t **shift){ + x86_reg mmx_len= len&(~7); + + __asm__ volatile( + "mov %5, %%"REG_a" \n\t" + ASMALIGN(4) + "1: \n\t" + "movq (%1, %%"REG_a"), %%mm1 \n\t" + "movq (%0, %%"REG_a"), %%mm0 \n\t" + "paddb (%2, %%"REG_a"), %%mm1 \n\t" + "paddb (%3, %%"REG_a"), %%mm1 \n\t" + "movq %%mm0, %%mm2 \n\t" + "movq %%mm1, %%mm3 \n\t" + "punpcklbw %%mm0, %%mm0 \n\t" + "punpckhbw %%mm2, %%mm2 \n\t" + "punpcklbw %%mm1, %%mm1 \n\t" + "punpckhbw %%mm3, %%mm3 \n\t" + "pmulhw %%mm0, %%mm1 \n\t" + "pmulhw %%mm2, %%mm3 \n\t" + "paddw %%mm1, %%mm1 \n\t" + "paddw %%mm3, %%mm3 \n\t" + "paddw %%mm0, %%mm1 \n\t" + "paddw %%mm2, %%mm3 \n\t" + "psrlw $8, %%mm1 \n\t" + "psrlw $8, %%mm3 \n\t" + "packuswb %%mm3, %%mm1 \n\t" + "movq %%mm1, (%4, %%"REG_a") \n\t" + "add $8, %%"REG_a" \n\t" + " js 1b \n\t" + :: "r" (src+mmx_len), "r" (shift[0]+mmx_len), "r" (shift[1]+mmx_len), "r" (shift[2]+mmx_len), + "r" (dst+mmx_len), "g" (-mmx_len) + : "%"REG_a + ); + + if(mmx_len!=len){ + int8_t *shift2[3]={shift[0]+mmx_len, shift[1]+mmx_len, shift[2]+mmx_len}; + lineNoiseAvg_C(dst+mmx_len, src+mmx_len, len-mmx_len, shift2); + } +} +#endif + +static inline void lineNoiseAvg_C(uint8_t *dst, uint8_t *src, int len, int8_t **shift){ + int i; + int8_t *src2= (int8_t*)src; + + for(i=0; i<len; i++) + { + const int n= shift[0][i] + shift[1][i] + shift[2][i]; + dst[i]= src2[i]+((n*src2[i])>>7); + } +} + +/***************************************************************************/ + +static void noise(uint8_t *dst, uint8_t *src, int dstStride, int srcStride, int width, int height, FilterParam *fp){ + int8_t *noise= fp->noise; + int y; + int shift=0; + + if(!noise) + { + if(src==dst) return; + + if(dstStride==srcStride) memcpy(dst, src, srcStride*height); + else + { + for(y=0; y<height; y++) + { + memcpy(dst, src, width); + dst+= dstStride; + src+= srcStride; + } + } + return; + } + + for(y=0; y<height; y++) + { + if(fp->temporal) shift= rand()&(MAX_SHIFT -1); + else shift= nonTempRandShift[y]; + + if(fp->quality==0) shift&= ~7; + if (fp->averaged) { + lineNoiseAvg(dst, src, width, fp->prev_shift[y]); + fp->prev_shift[y][fp->shiftptr] = noise + shift; + } else { + lineNoise(dst, src, noise, width, shift); + } + dst+= dstStride; + src+= srcStride; + } + fp->shiftptr++; + if (fp->shiftptr == 3) fp->shiftptr = 0; +} + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt){ + + return vf_next_config(vf,width,height,d_width,d_height,flags,outfmt); +} + +static void get_image(struct vf_instance *vf, mp_image_t *mpi){ + if(mpi->flags&MP_IMGFLAG_PRESERVE) return; // don't change + if(mpi->imgfmt!=vf->priv->outfmt) return; // colorspace differ + // ok, we can do pp in-place (or pp disabled): + vf->dmpi=vf_get_image(vf->next,mpi->imgfmt, + mpi->type, mpi->flags, mpi->w, mpi->h); + mpi->planes[0]=vf->dmpi->planes[0]; + mpi->stride[0]=vf->dmpi->stride[0]; + mpi->width=vf->dmpi->width; + if(mpi->flags&MP_IMGFLAG_PLANAR){ + mpi->planes[1]=vf->dmpi->planes[1]; + mpi->planes[2]=vf->dmpi->planes[2]; + mpi->stride[1]=vf->dmpi->stride[1]; + mpi->stride[2]=vf->dmpi->stride[2]; + } + mpi->flags|=MP_IMGFLAG_DIRECT; +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts){ + mp_image_t *dmpi; + + if(!(mpi->flags&MP_IMGFLAG_DIRECT)){ + // no DR, so get a new image! hope we'll get DR buffer: + vf->dmpi=vf_get_image(vf->next,vf->priv->outfmt, + MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE, + mpi->w,mpi->h); +//printf("nodr\n"); + } +//else printf("dr\n"); + dmpi= vf->dmpi; + + noise(dmpi->planes[0], mpi->planes[0], dmpi->stride[0], mpi->stride[0], mpi->w, mpi->h, &vf->priv->lumaParam); + noise(dmpi->planes[1], mpi->planes[1], dmpi->stride[1], mpi->stride[1], mpi->w/2, mpi->h/2, &vf->priv->chromaParam); + noise(dmpi->planes[2], mpi->planes[2], dmpi->stride[2], mpi->stride[2], mpi->w/2, mpi->h/2, &vf->priv->chromaParam); + + vf_clone_mpi_attributes(dmpi, mpi); + +#if HAVE_MMX + if(gCpuCaps.hasMMX) __asm__ volatile ("emms\n\t"); +#endif +#if HAVE_MMX2 + if(gCpuCaps.hasMMX2) __asm__ volatile ("sfence\n\t"); +#endif + + return vf_next_put_image(vf,dmpi, pts); +} + +static void uninit(struct vf_instance *vf){ + if(!vf->priv) return; + + av_free(vf->priv->chromaParam.noise); + vf->priv->chromaParam.noise= NULL; + + av_free(vf->priv->lumaParam.noise); + vf->priv->lumaParam.noise= NULL; + + free(vf->priv); + vf->priv=NULL; +} + +//===========================================================================// + +static int query_format(struct vf_instance *vf, unsigned int fmt){ + switch(fmt) + { + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + return vf_next_query_format(vf,vf->priv->outfmt); + } + return 0; +} + +static void parse(FilterParam *fp, char* args){ + char *pos; + char *max= strchr(args, ':'); + + if(!max) max= args + strlen(args); + + fp->strength= atoi(args); + pos= strchr(args, 'u'); + if(pos && pos<max) fp->uniform=1; + pos= strchr(args, 't'); + if(pos && pos<max) fp->temporal=1; + pos= strchr(args, 'h'); + if(pos && pos<max) fp->quality=1; + pos= strchr(args, 'p'); + if(pos && pos<max) fp->pattern=1; + pos= strchr(args, 'a'); + if(pos && pos<max) { + fp->temporal=1; + fp->averaged=1; + } + + if(fp->strength) initNoise(fp); +} + +static const unsigned int fmt_list[]={ + IMGFMT_YV12, + IMGFMT_I420, + IMGFMT_IYUV, + 0 +}; + +static int vf_open(vf_instance_t *vf, char *args){ + vf->config=config; + vf->put_image=put_image; + vf->get_image=get_image; + vf->query_format=query_format; + vf->uninit=uninit; + vf->priv=malloc(sizeof(struct vf_priv_s)); + memset(vf->priv, 0, sizeof(struct vf_priv_s)); + if(args) + { + char *arg2= strchr(args,':'); + if(arg2) parse(&vf->priv->chromaParam, arg2+1); + parse(&vf->priv->lumaParam, args); + } + + // check csp: + vf->priv->outfmt=vf_match_csp(&vf->next,fmt_list,IMGFMT_YV12); + if(!vf->priv->outfmt) + { + uninit(vf); + return 0; // no csp match :( + } + + +#if HAVE_MMX + if(gCpuCaps.hasMMX){ + lineNoise= lineNoise_MMX; + lineNoiseAvg= lineNoiseAvg_MMX; + } +#endif +#if HAVE_MMX2 + if(gCpuCaps.hasMMX2) lineNoise= lineNoise_MMX2; +// if(gCpuCaps.hasMMX) lineNoiseAvg= lineNoiseAvg_MMX2; +#endif + + return 1; +} + +const vf_info_t vf_info_noise = { + "noise generator", + "noise", + "Michael Niedermayer", + "", + vf_open, + NULL +}; + +//===========================================================================// diff --git a/video/filter/vf_phase.c b/video/filter/vf_phase.c new file mode 100644 index 0000000000..568c8f1f45 --- /dev/null +++ b/video/filter/vf_phase.c @@ -0,0 +1,303 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#include "config.h" +#include "mp_msg.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +#include "libvo/fastmemcpy.h" + +enum mode { PROGRESSIVE, TOP_FIRST, BOTTOM_FIRST, + TOP_FIRST_ANALYZE, BOTTOM_FIRST_ANALYZE, + ANALYZE, FULL_ANALYZE, AUTO, AUTO_ANALYZE }; + +#define fixed_mode(p) ((p)<=BOTTOM_FIRST) + +struct vf_priv_s + { + enum mode mode; + int verbose; + unsigned char *buf[3]; + }; + +/* + * Copy fields from either current or buffered previous frame to the + * output and store the current frame unmodified to the buffer. + */ + +static void do_plane(unsigned char *to, unsigned char *from, + int w, int h, int ts, int fs, + unsigned char **bufp, enum mode mode) + { + unsigned char *buf, *end; + int top; + + if(!*bufp) + { + mode=PROGRESSIVE; + if(!(*bufp=malloc(h*w))) return; + } + + for(end=to+h*ts, buf=*bufp, top=1; to<end; from+=fs, to+=ts, buf+=w, top^=1) + { + memcpy(to, mode==(top?BOTTOM_FIRST:TOP_FIRST)?buf:from, w); + memcpy(buf, from, w); + } + } + +/* + * This macro interpolates the value of both fields at a point halfway + * between lines and takes the squared difference. In field resolution + * the point is a quarter pixel below a line in one field and a quarter + * pixel above a line in other. + * + * (the result is actually multiplied by 25) + */ + +#define diff(a, as, b, bs) (t=((*a-b[bs])<<2)+a[as<<1]-b[-bs], t*t) + +/* + * Find which field combination has the smallest average squared difference + * between the fields. + */ + +static enum mode analyze_plane(unsigned char *old, unsigned char *new, + int w, int h, int os, int ns, enum mode mode, + int verbose, int fields) + { + double bdiff, pdiff, tdiff, scale; + int bdif, tdif, pdif; + int top, t; + unsigned char *end, *rend; + + if(mode==AUTO) + mode=fields&MP_IMGFIELD_ORDERED?fields&MP_IMGFIELD_TOP_FIRST? + TOP_FIRST:BOTTOM_FIRST:PROGRESSIVE; + else if(mode==AUTO_ANALYZE) + mode=fields&MP_IMGFIELD_ORDERED?fields&MP_IMGFIELD_TOP_FIRST? + TOP_FIRST_ANALYZE:BOTTOM_FIRST_ANALYZE:FULL_ANALYZE; + + if(fixed_mode(mode)) + bdiff=pdiff=tdiff=65536.0; + else + { + bdiff=pdiff=tdiff=0.0; + + for(end=new+(h-2)*ns, new+=ns, old+=os, top=0; + new<end; new+=ns-w, old+=os-w, top^=1) + { + pdif=tdif=bdif=0; + + switch(mode) + { + case TOP_FIRST_ANALYZE: + if(top) + for(rend=new+w; new<rend; new++, old++) + pdif+=diff(new, ns, new, ns), + tdif+=diff(new, ns, old, os); + else + for(rend=new+w; new<rend; new++, old++) + pdif+=diff(new, ns, new, ns), + tdif+=diff(old, os, new, ns); + break; + + case BOTTOM_FIRST_ANALYZE: + if(top) + for(rend=new+w; new<rend; new++, old++) + pdif+=diff(new, ns, new, ns), + bdif+=diff(old, os, new, ns); + else + for(rend=new+w; new<rend; new++, old++) + pdif+=diff(new, ns, new, ns), + bdif+=diff(new, ns, old, os); + break; + + case ANALYZE: + if(top) + for(rend=new+w; new<rend; new++, old++) + tdif+=diff(new, ns, old, os), + bdif+=diff(old, os, new, ns); + else + for(rend=new+w; new<rend; new++, old++) + bdif+=diff(new, ns, old, os), + tdif+=diff(old, os, new, ns); + break; + + default: /* FULL_ANALYZE */ + if(top) + for(rend=new+w; new<rend; new++, old++) + pdif+=diff(new, ns, new, ns), + tdif+=diff(new, ns, old, os), + bdif+=diff(old, os, new, ns); + else + for(rend=new+w; new<rend; new++, old++) + pdif+=diff(new, ns, new, ns), + bdif+=diff(new, ns, old, os), + tdif+=diff(old, os, new, ns); + } + + pdiff+=(double)pdif; + tdiff+=(double)tdif; + bdiff+=(double)bdif; + } + + scale=1.0/(w*(h-3))/25.0; + pdiff*=scale; + tdiff*=scale; + bdiff*=scale; + + if(mode==TOP_FIRST_ANALYZE) + bdiff=65536.0; + else if(mode==BOTTOM_FIRST_ANALYZE) + tdiff=65536.0; + else if(mode==ANALYZE) + pdiff=65536.0; + + if(bdiff<pdiff && bdiff<tdiff) + mode=BOTTOM_FIRST; + else if(tdiff<pdiff && tdiff<bdiff) + mode=TOP_FIRST; + else + mode=PROGRESSIVE; + } + + if( mp_msg_test(MSGT_VFILTER,MSGL_V) ) + { + mp_msg(MSGT_VFILTER, MSGL_INFO, "%c", mode==BOTTOM_FIRST?'b':mode==TOP_FIRST?'t':'p'); + if(tdiff==65536.0) mp_msg(MSGT_VFILTER, MSGL_INFO," N/A "); else mp_msg(MSGT_VFILTER, MSGL_INFO," %8.2f", tdiff); + if(bdiff==65536.0) mp_msg(MSGT_VFILTER, MSGL_INFO," N/A "); else mp_msg(MSGT_VFILTER, MSGL_INFO," %8.2f", bdiff); + if(pdiff==65536.0) mp_msg(MSGT_VFILTER, MSGL_INFO," N/A "); else mp_msg(MSGT_VFILTER, MSGL_INFO," %8.2f", pdiff); + mp_msg(MSGT_VFILTER, MSGL_INFO," \n"); + } + + return mode; + } + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts) + { + mp_image_t *dmpi; + int w; + enum mode mode; + + if(!(dmpi=vf_get_image(vf->next, mpi->imgfmt, + MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE, + mpi->w, mpi->h))) + return 0; + + w=dmpi->w; + if(!(dmpi->flags&MP_IMGFLAG_PLANAR)) + w*=dmpi->bpp/8; + + mode=vf->priv->mode; + + if(!vf->priv->buf[0]) + mode=PROGRESSIVE; + else + mode=analyze_plane(vf->priv->buf[0], mpi->planes[0], + w, dmpi->h, w, mpi->stride[0], mode, + vf->priv->verbose, mpi->fields); + + do_plane(dmpi->planes[0], mpi->planes[0], + w, dmpi->h, + dmpi->stride[0], mpi->stride[0], + &vf->priv->buf[0], mode); + + if(dmpi->flags&MP_IMGFLAG_PLANAR) + { + do_plane(dmpi->planes[1], mpi->planes[1], + dmpi->chroma_width, dmpi->chroma_height, + dmpi->stride[1], mpi->stride[1], + &vf->priv->buf[1], mode); + do_plane(dmpi->planes[2], mpi->planes[2], + dmpi->chroma_width, dmpi->chroma_height, + dmpi->stride[2], mpi->stride[2], + &vf->priv->buf[2], mode); + } + + return vf_next_put_image(vf, dmpi, pts); + } + +static void uninit(struct vf_instance *vf) + { + if (!vf->priv) + return; + free(vf->priv->buf[0]); + free(vf->priv->buf[1]); + free(vf->priv->buf[2]); + free(vf->priv); + } + +static int vf_open(vf_instance_t *vf, char *args) + { + vf->put_image = put_image; + vf->uninit = uninit; + vf->default_reqs = VFCAP_ACCEPT_STRIDE; + + if(!(vf->priv = calloc(1, sizeof(struct vf_priv_s)))) + { + uninit(vf); + return 0; + } + + vf->priv->mode=AUTO_ANALYZE; + vf->priv->verbose=0; + + while(args && *args) + { + switch(*args) + { + case 't': vf->priv->mode=TOP_FIRST; break; + case 'a': vf->priv->mode=AUTO; break; + case 'b': vf->priv->mode=BOTTOM_FIRST; break; + case 'u': vf->priv->mode=ANALYZE; break; + case 'T': vf->priv->mode=TOP_FIRST_ANALYZE; break; + case 'A': vf->priv->mode=AUTO_ANALYZE; break; + case 'B': vf->priv->mode=BOTTOM_FIRST_ANALYZE; break; + case 'U': vf->priv->mode=FULL_ANALYZE; break; + case 'p': vf->priv->mode=PROGRESSIVE; break; + case 'v': vf->priv->verbose=1; break; + case ':': break; + + default: + uninit(vf); + return 0; /* bad args */ + } + + if( (args=strchr(args, ':')) ) args++; + } + + return 1; + } + +const vf_info_t vf_info_phase = + { + "phase shift fields", + "phase", + "Ville Saari", + "", + vf_open, + NULL + }; diff --git a/video/filter/vf_pp.c b/video/filter/vf_pp.c new file mode 100644 index 0000000000..9647032002 --- /dev/null +++ b/video/filter/vf_pp.c @@ -0,0 +1,196 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <errno.h> + +#include "config.h" +#include "mp_msg.h" +#include "cpudetect.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" +#include "libpostproc/postprocess.h" + +struct vf_priv_s { + int pp; + pp_mode *ppMode[PP_QUALITY_MAX+1]; + void *context; + unsigned int outfmt; +}; + +//===========================================================================// + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int voflags, unsigned int outfmt){ + int flags= + (gCpuCaps.hasMMX ? PP_CPU_CAPS_MMX : 0) + | (gCpuCaps.hasMMX2 ? PP_CPU_CAPS_MMX2 : 0); + + switch(outfmt){ + case IMGFMT_444P: flags|= PP_FORMAT_444; break; + case IMGFMT_422P: flags|= PP_FORMAT_422; break; + case IMGFMT_411P: flags|= PP_FORMAT_411; break; + default: flags|= PP_FORMAT_420; break; + } + + if(vf->priv->context) pp_free_context(vf->priv->context); + vf->priv->context= pp_get_context(width, height, flags); + + return vf_next_config(vf,width,height,d_width,d_height,voflags,outfmt); +} + +static void uninit(struct vf_instance *vf){ + int i; + for(i=0; i<=PP_QUALITY_MAX; i++){ + if(vf->priv->ppMode[i]) + pp_free_mode(vf->priv->ppMode[i]); + } + if(vf->priv->context) pp_free_context(vf->priv->context); +} + +static int query_format(struct vf_instance *vf, unsigned int fmt){ + switch(fmt){ + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + case IMGFMT_444P: + case IMGFMT_422P: + case IMGFMT_411P: + return vf_next_query_format(vf,fmt); + } + return 0; +} + +static int control(struct vf_instance *vf, int request, void* data){ + switch(request){ + case VFCTRL_QUERY_MAX_PP_LEVEL: + return PP_QUALITY_MAX; + case VFCTRL_SET_PP_LEVEL: + vf->priv->pp= *((unsigned int*)data); + return CONTROL_TRUE; + } + return vf_next_control(vf,request,data); +} + +static void get_image(struct vf_instance *vf, mp_image_t *mpi){ + if(vf->priv->pp&0xFFFF) return; // non-local filters enabled + if((mpi->type==MP_IMGTYPE_IPB || vf->priv->pp) && + mpi->flags&MP_IMGFLAG_PRESERVE) return; // don't change + if(!(mpi->flags&MP_IMGFLAG_ACCEPT_STRIDE) && mpi->imgfmt!=vf->priv->outfmt) + return; // colorspace differ + // ok, we can do pp in-place (or pp disabled): + vf->dmpi=vf_get_image(vf->next,mpi->imgfmt, + mpi->type, mpi->flags | MP_IMGFLAG_READABLE, mpi->width, mpi->height); + mpi->planes[0]=vf->dmpi->planes[0]; + mpi->stride[0]=vf->dmpi->stride[0]; + mpi->width=vf->dmpi->width; + if(mpi->flags&MP_IMGFLAG_PLANAR){ + mpi->planes[1]=vf->dmpi->planes[1]; + mpi->planes[2]=vf->dmpi->planes[2]; + mpi->stride[1]=vf->dmpi->stride[1]; + mpi->stride[2]=vf->dmpi->stride[2]; + } + mpi->flags|=MP_IMGFLAG_DIRECT; +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts){ + if(!(mpi->flags&MP_IMGFLAG_DIRECT)){ + // no DR, so get a new image! hope we'll get DR buffer: + vf->dmpi=vf_get_image(vf->next,mpi->imgfmt, + MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE | + MP_IMGFLAG_PREFER_ALIGNED_STRIDE | MP_IMGFLAG_READABLE, +// MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE, +// mpi->w,mpi->h); + (mpi->width+7)&(~7),(mpi->height+7)&(~7)); + vf->dmpi->w=mpi->w; vf->dmpi->h=mpi->h; // display w;h + } + + if(vf->priv->pp || !(mpi->flags&MP_IMGFLAG_DIRECT)){ + // do the postprocessing! (or copy if no DR) + pp_postprocess((const uint8_t **)mpi->planes, mpi->stride, + vf->dmpi->planes,vf->dmpi->stride, + (mpi->w+7)&(~7),mpi->h, + mpi->qscale, mpi->qstride, + vf->priv->ppMode[ vf->priv->pp ], vf->priv->context, +#ifdef PP_PICT_TYPE_QP2 + mpi->pict_type | (mpi->qscale_type ? PP_PICT_TYPE_QP2 : 0)); +#else + mpi->pict_type); +#endif + } + return vf_next_put_image(vf,vf->dmpi, pts); +} + +//===========================================================================// + +extern int divx_quality; + +static const unsigned int fmt_list[]={ + IMGFMT_YV12, + IMGFMT_I420, + IMGFMT_IYUV, + IMGFMT_444P, + IMGFMT_422P, + IMGFMT_411P, + 0 +}; + +static int vf_open(vf_instance_t *vf, char *args){ + int i; + + vf->query_format=query_format; + vf->control=control; + vf->config=config; + vf->get_image=get_image; + vf->put_image=put_image; + vf->uninit=uninit; + vf->default_caps=VFCAP_ACCEPT_STRIDE|VFCAP_POSTPROC; + vf->priv=malloc(sizeof(struct vf_priv_s)); + vf->priv->context=NULL; + + // check csp: + vf->priv->outfmt=vf_match_csp(&vf->next,fmt_list,IMGFMT_YV12); + if(!vf->priv->outfmt) return 0; // no csp match :( + + char *name = args ? args : "de"; + + for(i=0; i<=PP_QUALITY_MAX; i++){ + vf->priv->ppMode[i]= pp_get_mode_by_name_and_quality(name, i); + if(vf->priv->ppMode[i]==NULL) return -1; + } + + vf->priv->pp=PP_QUALITY_MAX; + return 1; +} + +const vf_info_t vf_info_pp = { + "postprocessing", + "pp", + "A'rpi", + "", + vf_open, + NULL +}; + +//===========================================================================// diff --git a/video/filter/vf_pullup.c b/video/filter/vf_pullup.c new file mode 100644 index 0000000000..97f851f348 --- /dev/null +++ b/video/filter/vf_pullup.c @@ -0,0 +1,333 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "config.h" +#include "mp_msg.h" +#include "cpudetect.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +#include "libvo/fastmemcpy.h" + +#include "pullup.h" + +#undef MAX +#define MAX(a,b) ((a)>(b)?(a):(b)) + +struct vf_priv_s { + struct pullup_context *ctx; + int init; + int fakecount; + char *qbuf; + double lastpts; +}; + +static void init_pullup(struct vf_instance *vf, mp_image_t *mpi) +{ + struct pullup_context *c = vf->priv->ctx; + + if (mpi->flags & MP_IMGFLAG_PLANAR) { + c->format = PULLUP_FMT_Y; + c->nplanes = 4; + pullup_preinit_context(c); + c->bpp[0] = c->bpp[1] = c->bpp[2] = 8; + c->w[0] = mpi->w; + c->h[0] = mpi->h; + c->w[1] = c->w[2] = mpi->chroma_width; + c->h[1] = c->h[2] = mpi->chroma_height; + c->w[3] = ((mpi->w+15)/16) * ((mpi->h+15)/16); + c->h[3] = 2; + c->stride[0] = mpi->width; + c->stride[1] = c->stride[2] = mpi->chroma_width; + c->stride[3] = c->w[3]; + c->background[1] = c->background[2] = 128; + } + + if (gCpuCaps.hasMMX) c->cpu |= PULLUP_CPU_MMX; + if (gCpuCaps.hasMMX2) c->cpu |= PULLUP_CPU_MMX2; + if (gCpuCaps.hasSSE) c->cpu |= PULLUP_CPU_SSE; + if (gCpuCaps.hasSSE2) c->cpu |= PULLUP_CPU_SSE2; + + pullup_init_context(c); + + vf->priv->init = 1; + vf->priv->qbuf = malloc(c->w[3]); +} + + +#if 0 +static void get_image(struct vf_instance *vf, mp_image_t *mpi) +{ + struct pullup_context *c = vf->priv->ctx; + struct pullup_buffer *b; + + if (mpi->type == MP_IMGTYPE_STATIC) return; + + if (!vf->priv->init) init_pullup(vf, mpi); + + b = pullup_get_buffer(c, 2); + if (!b) return; /* shouldn't happen... */ + + mpi->priv = b; + + mpi->planes[0] = b->planes[0]; + mpi->planes[1] = b->planes[1]; + mpi->planes[2] = b->planes[2]; + mpi->stride[0] = c->stride[0]; + mpi->stride[1] = c->stride[1]; + mpi->stride[2] = c->stride[2]; + + mpi->flags |= MP_IMGFLAG_DIRECT; + mpi->flags &= ~MP_IMGFLAG_DRAW_CALLBACK; +} +#endif + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts) +{ + struct pullup_context *c = vf->priv->ctx; + struct pullup_buffer *b; + struct pullup_frame *f; + mp_image_t *dmpi; + int ret; + int p; + int i; + + if (!vf->priv->init) init_pullup(vf, mpi); + + if (mpi->flags & MP_IMGFLAG_DIRECT) { + b = mpi->priv; + mpi->priv = 0; + } else { + b = pullup_get_buffer(c, 2); + if (!b) { + mp_msg(MSGT_VFILTER,MSGL_ERR,"Could not get buffer from pullup!\n"); + f = pullup_get_frame(c); + pullup_release_frame(f); + return 0; + } + memcpy_pic(b->planes[0], mpi->planes[0], mpi->w, mpi->h, + c->stride[0], mpi->stride[0]); + if (mpi->flags & MP_IMGFLAG_PLANAR) { + memcpy_pic(b->planes[1], mpi->planes[1], + mpi->chroma_width, mpi->chroma_height, + c->stride[1], mpi->stride[1]); + memcpy_pic(b->planes[2], mpi->planes[2], + mpi->chroma_width, mpi->chroma_height, + c->stride[2], mpi->stride[2]); + } + } + if (mpi->qscale) { + memcpy(b->planes[3], mpi->qscale, c->w[3]); + memcpy(b->planes[3]+c->w[3], mpi->qscale, c->w[3]); + } + + p = mpi->fields & MP_IMGFIELD_TOP_FIRST ? 0 : + (mpi->fields & MP_IMGFIELD_ORDERED ? 1 : 0); + + if (pts == MP_NOPTS_VALUE) { + pullup_submit_field(c, b, p, MP_NOPTS_VALUE); + pullup_submit_field(c, b, p^1, MP_NOPTS_VALUE); + if (mpi->fields & MP_IMGFIELD_REPEAT_FIRST) + pullup_submit_field(c, b, p, MP_NOPTS_VALUE); + } else { + double delta; + if (vf->priv->lastpts == MP_NOPTS_VALUE) + delta = 1001.0/60000.0; // delta = field time distance + else + delta = (pts - vf->priv->lastpts) / 2; + if (delta <= 0.0 || delta >= 0.5) + delta = 0.0; + vf->priv->lastpts = pts; + if (mpi->fields & MP_IMGFIELD_REPEAT_FIRST) { + pullup_submit_field(c, b, p, pts - delta); + pullup_submit_field(c, b, p^1, pts); + pullup_submit_field(c, b, p, pts + delta); + } else { + pullup_submit_field(c, b, p, pts - delta * 0.5); + pullup_submit_field(c, b, p^1, pts + delta * 0.5); + } + } + + pullup_release_buffer(b, 2); + + f = pullup_get_frame(c); + + /* Fake yes for first few frames (buffer depth) to keep from + * breaking A/V sync with G1's bad architecture... */ + if (!f) return vf->priv->fakecount ? (--vf->priv->fakecount,1) : 0; + + if (f->length < 2) { + pullup_release_frame(f); + f = pullup_get_frame(c); + if (!f) return 0; + if (f->length < 2) { + pullup_release_frame(f); + if (!(mpi->fields & MP_IMGFIELD_REPEAT_FIRST)) + return 0; + f = pullup_get_frame(c); + if (!f) return 0; + if (f->length < 2) { + pullup_release_frame(f); + return 0; + } + } + } + +#if 0 + /* Average qscale tables from both frames. */ + if (mpi->qscale) { + for (i=0; i<c->w[3]; i++) { + vf->priv->qbuf[i] = (f->ofields[0]->planes[3][i] + + f->ofields[1]->planes[3][i+c->w[3]])>>1; + } + } +#else + /* Take worst of qscale tables from both frames. */ + if (mpi->qscale) { + for (i=0; i<c->w[3]; i++) { + vf->priv->qbuf[i] = MAX(f->ofields[0]->planes[3][i], f->ofields[1]->planes[3][i+c->w[3]]); + } + } +#endif + + /* If the frame isn't already exportable... */ + while (!f->buffer) { + dmpi = vf_get_image(vf->next, mpi->imgfmt, + MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE, + mpi->width, mpi->height); + /* FIXME: Is it ok to discard dmpi if it's not direct? */ + if (!(dmpi->flags & MP_IMGFLAG_DIRECT)) { + pullup_pack_frame(c, f); + break; + } + /* Direct render fields into output buffer */ + my_memcpy_pic(dmpi->planes[0], f->ofields[0]->planes[0], + mpi->w, mpi->h/2, dmpi->stride[0]*2, c->stride[0]*2); + my_memcpy_pic(dmpi->planes[0] + dmpi->stride[0], + f->ofields[1]->planes[0] + c->stride[0], + mpi->w, mpi->h/2, dmpi->stride[0]*2, c->stride[0]*2); + if (mpi->flags & MP_IMGFLAG_PLANAR) { + my_memcpy_pic(dmpi->planes[1], f->ofields[0]->planes[1], + mpi->chroma_width, mpi->chroma_height/2, + dmpi->stride[1]*2, c->stride[1]*2); + my_memcpy_pic(dmpi->planes[1] + dmpi->stride[1], + f->ofields[1]->planes[1] + c->stride[1], + mpi->chroma_width, mpi->chroma_height/2, + dmpi->stride[1]*2, c->stride[1]*2); + my_memcpy_pic(dmpi->planes[2], f->ofields[0]->planes[2], + mpi->chroma_width, mpi->chroma_height/2, + dmpi->stride[2]*2, c->stride[2]*2); + my_memcpy_pic(dmpi->planes[2] + dmpi->stride[2], + f->ofields[1]->planes[2] + c->stride[2], + mpi->chroma_width, mpi->chroma_height/2, + dmpi->stride[2]*2, c->stride[2]*2); + } + pullup_release_frame(f); + if (mpi->qscale) { + dmpi->qscale = vf->priv->qbuf; + dmpi->qstride = mpi->qstride; + dmpi->qscale_type = mpi->qscale_type; + } + return vf_next_put_image(vf, dmpi, f->pts); + } + dmpi = vf_get_image(vf->next, mpi->imgfmt, + MP_IMGTYPE_EXPORT, MP_IMGFLAG_ACCEPT_STRIDE, + mpi->width, mpi->height); + + dmpi->planes[0] = f->buffer->planes[0]; + dmpi->planes[1] = f->buffer->planes[1]; + dmpi->planes[2] = f->buffer->planes[2]; + + dmpi->stride[0] = c->stride[0]; + dmpi->stride[1] = c->stride[1]; + dmpi->stride[2] = c->stride[2]; + + if (mpi->qscale) { + dmpi->qscale = vf->priv->qbuf; + dmpi->qstride = mpi->qstride; + dmpi->qscale_type = mpi->qscale_type; + } + ret = vf_next_put_image(vf, dmpi, f->pts); + pullup_release_frame(f); + return ret; +} + +static int query_format(struct vf_instance *vf, unsigned int fmt) +{ + /* FIXME - support more formats */ + switch (fmt) { + case IMGFMT_YV12: + case IMGFMT_IYUV: + case IMGFMT_I420: + return vf_next_query_format(vf, fmt); + } + return 0; +} + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt) +{ + if (height&3) return 0; + return vf_next_config(vf, width, height, d_width, d_height, flags, outfmt); +} + +static void uninit(struct vf_instance *vf) +{ + pullup_free_context(vf->priv->ctx); + free(vf->priv); +} + +static int vf_open(vf_instance_t *vf, char *args) +{ + struct vf_priv_s *p; + struct pullup_context *c; + //vf->get_image = get_image; + vf->put_image = put_image; + vf->config = config; + vf->query_format = query_format; + vf->uninit = uninit; + vf->default_reqs = VFCAP_ACCEPT_STRIDE; + vf->priv = p = calloc(1, sizeof(struct vf_priv_s)); + p->ctx = c = pullup_alloc_context(); + p->fakecount = 1; + c->verbose = verbose>0; + c->junk_left = c->junk_right = 1; + c->junk_top = c->junk_bottom = 4; + c->strict_breaks = 0; + c->metric_plane = 0; + if (args) { + sscanf(args, "%d:%d:%d:%d:%d:%d", &c->junk_left, &c->junk_right, &c->junk_top, &c->junk_bottom, &c->strict_breaks, &c->metric_plane); + } + return 1; +} + +const vf_info_t vf_info_pullup = { + "pullup (from field sequence to frames)", + "pullup", + "Rich Felker", + "", + vf_open, + NULL +}; diff --git a/video/filter/vf_rotate.c b/video/filter/vf_rotate.c new file mode 100644 index 0000000000..19eeae6d35 --- /dev/null +++ b/video/filter/vf_rotate.c @@ -0,0 +1,152 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include "config.h" +#include "mp_msg.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +struct vf_priv_s { + int direction; +}; + +static void rotate(unsigned char* dst,unsigned char* src,int dststride,int srcstride,int w,int h,int bpp,int dir){ + int y; + if(dir&1){ + src+=srcstride*(w-1); + srcstride*=-1; + } + if(dir&2){ + dst+=dststride*(h-1); + dststride*=-1; + } + + for(y=0;y<h;y++){ + int x; + switch(bpp){ + case 1: + for(x=0;x<w;x++) dst[x]=src[y+x*srcstride]; + break; + case 2: + for(x=0;x<w;x++) *((short*)(dst+x*2))=*((short*)(src+y*2+x*srcstride)); + break; + case 3: + for(x=0;x<w;x++){ + dst[x*3+0]=src[0+y*3+x*srcstride]; + dst[x*3+1]=src[1+y*3+x*srcstride]; + dst[x*3+2]=src[2+y*3+x*srcstride]; + } + break; + case 4: + for(x=0;x<w;x++) *((int*)(dst+x*4))=*((int*)(src+y*4+x*srcstride)); + } + dst+=dststride; + } +} + +//===========================================================================// + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt){ + if (vf->priv->direction & 4) { + if (width<height) vf->priv->direction&=3; + } + if (vf->priv->direction & 4){ + vf->put_image=vf_next_put_image; // passthru mode! + if (vf->next->draw_slice) vf->draw_slice=vf_next_draw_slice; +/* FIXME: this should be in an other procedure in vf.c; that should always check + whether the filter after the passthrough one still (not)supports slices */ + return vf_next_config(vf,width,height,d_width,d_height,flags,outfmt); + } + return vf_next_config(vf,height,width,d_height,d_width,flags,outfmt); +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts){ + mp_image_t *dmpi; + + // hope we'll get DR buffer: + dmpi=vf_get_image(vf->next,mpi->imgfmt, + MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE, + mpi->h, mpi->w); + + if(mpi->flags&MP_IMGFLAG_PLANAR){ + rotate(dmpi->planes[0],mpi->planes[0], + dmpi->stride[0],mpi->stride[0], + dmpi->w,dmpi->h,1,vf->priv->direction); + rotate(dmpi->planes[1],mpi->planes[1], + dmpi->stride[1],mpi->stride[1], + dmpi->w>>mpi->chroma_x_shift,dmpi->h>>mpi->chroma_y_shift,1,vf->priv->direction); + rotate(dmpi->planes[2],mpi->planes[2], + dmpi->stride[2],mpi->stride[2], + dmpi->w>>mpi->chroma_x_shift,dmpi->h>>mpi->chroma_y_shift,1,vf->priv->direction); + } else { + rotate(dmpi->planes[0],mpi->planes[0], + dmpi->stride[0],mpi->stride[0], + dmpi->w,dmpi->h,dmpi->bpp>>3,vf->priv->direction); + dmpi->planes[1] = mpi->planes[1]; // passthrough rgb8 palette + } + + return vf_next_put_image(vf,dmpi, pts); +} + +//===========================================================================// + +static int query_format(struct vf_instance *vf, unsigned int fmt){ + if(IMGFMT_IS_RGB(fmt) || IMGFMT_IS_BGR(fmt)) return vf_next_query_format(vf, fmt); + // we can support only symmetric (chroma_x_shift==chroma_y_shift) YUV formats: + switch(fmt) { + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + case IMGFMT_YVU9: +// case IMGFMT_IF09: + case IMGFMT_Y8: + case IMGFMT_Y800: + case IMGFMT_444P: + return vf_next_query_format(vf, fmt); + } + return 0; +} + +static int vf_open(vf_instance_t *vf, char *args){ + vf->config=config; + vf->put_image=put_image; + vf->query_format=query_format; + vf->priv=malloc(sizeof(struct vf_priv_s)); + vf->priv->direction=args?atoi(args):0; + return 1; +} + +const vf_info_t vf_info_rotate = { + "rotate", + "rotate", + "A'rpi", + "", + vf_open, + NULL +}; + +//===========================================================================// diff --git a/video/filter/vf_scale.c b/video/filter/vf_scale.c new file mode 100644 index 0000000000..5ea62bacbd --- /dev/null +++ b/video/filter/vf_scale.c @@ -0,0 +1,718 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <sys/types.h> + +#include "config.h" +#include "mp_msg.h" +#include "options.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" +#include "fmt-conversion.h" +#include "mpbswap.h" + +#include "libmpcodecs/sws_utils.h" + +#include "libvo/csputils.h" +// VOFLAG_SWSCALE +#include "libvo/video_out.h" + +#include "m_option.h" +#include "m_struct.h" + +static struct vf_priv_s { + int w,h; + int cfg_w, cfg_h; + int v_chr_drop; + double param[2]; + unsigned int fmt; + struct SwsContext *ctx; + struct SwsContext *ctx2; //for interlaced slices only + unsigned char* palette; + int interlaced; + int noup; + int accurate_rnd; + struct mp_csp_details colorspace; +} const vf_priv_dflt = { + 0, 0, + -1,-1, + 0, + {SWS_PARAM_DEFAULT, SWS_PARAM_DEFAULT}, + 0, + NULL, + NULL, + NULL +}; + +//===========================================================================// + +static const unsigned int outfmt_list[]={ +// YUV: + IMGFMT_444P, + IMGFMT_444P16_LE, + IMGFMT_444P16_BE, + IMGFMT_444P10_LE, + IMGFMT_444P10_BE, + IMGFMT_444P9_LE, + IMGFMT_444P9_BE, + IMGFMT_422P, + IMGFMT_422P16_LE, + IMGFMT_422P16_BE, + IMGFMT_422P10_LE, + IMGFMT_422P10_BE, + IMGFMT_422P9_LE, + IMGFMT_422P9_BE, + IMGFMT_YV12, + IMGFMT_I420, + IMGFMT_420P16_LE, + IMGFMT_420P16_BE, + IMGFMT_420P10_LE, + IMGFMT_420P10_BE, + IMGFMT_420P9_LE, + IMGFMT_420P9_BE, + IMGFMT_420A, + IMGFMT_IYUV, + IMGFMT_YVU9, + IMGFMT_IF09, + IMGFMT_411P, + IMGFMT_NV12, + IMGFMT_NV21, + IMGFMT_YUY2, + IMGFMT_UYVY, + IMGFMT_440P, +// RGB and grayscale (Y8 and Y800): + IMGFMT_BGR32, + IMGFMT_RGB32, + IMGFMT_BGR24, + IMGFMT_RGB24, + IMGFMT_GBRP, + IMGFMT_RGB48LE, + IMGFMT_RGB48BE, + IMGFMT_BGR16, + IMGFMT_RGB16, + IMGFMT_BGR15, + IMGFMT_RGB15, + IMGFMT_BGR12, + IMGFMT_RGB12, + IMGFMT_Y800, + IMGFMT_Y8, + IMGFMT_BGR8, + IMGFMT_RGB8, + IMGFMT_BGR4, + IMGFMT_RGB4, + IMGFMT_BG4B, + IMGFMT_RG4B, + IMGFMT_BGR1, + IMGFMT_RGB1, + 0 +}; + +/** + * A list of preferred conversions, in order of preference. + * This should be used for conversions that e.g. involve no scaling + * or to stop vf_scale from choosing a conversion that has no + * fast assembler implementation. + */ +static int preferred_conversions[][2] = { + {IMGFMT_YUY2, IMGFMT_UYVY}, + {IMGFMT_YUY2, IMGFMT_422P}, + {IMGFMT_UYVY, IMGFMT_YUY2}, + {IMGFMT_UYVY, IMGFMT_422P}, + {IMGFMT_422P, IMGFMT_YUY2}, + {IMGFMT_422P, IMGFMT_UYVY}, + {IMGFMT_GBRP, IMGFMT_BGR24}, + {IMGFMT_GBRP, IMGFMT_RGB24}, + {IMGFMT_GBRP, IMGFMT_BGR32}, + {IMGFMT_GBRP, IMGFMT_RGB32}, + {0, 0} +}; + +static unsigned int find_best_out(vf_instance_t *vf, int in_format){ + unsigned int best=0; + int i = -1; + int j = -1; + int format = 0; + + // find the best outfmt: + while (1) { + int ret; + if (j < 0) { + format = in_format; + j = 0; + } else if (i < 0) { + while (preferred_conversions[j][0] && + preferred_conversions[j][0] != in_format) + j++; + format = preferred_conversions[j++][1]; + // switch to standard list + if (!format) + i = 0; + } + if (i >= 0) + format = outfmt_list[i++]; + if (!format) + break; + ret = vf_next_query_format(vf, format); + + mp_msg(MSGT_VFILTER,MSGL_DBG2,"scale: query(%s) -> %d\n",vo_format_name(format),ret&3); + if(ret&VFCAP_CSP_SUPPORTED_BY_HW){ + best=format; // no conversion -> bingo! + break; + } + if(ret&VFCAP_CSP_SUPPORTED && !best) + best=format; // best with conversion + } + return best; +} + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt){ + struct MPOpts *opts = vf->opts; + unsigned int best=find_best_out(vf, outfmt); + int vo_flags; + int int_sws_flags=0; + int round_w=0, round_h=0; + int i; + SwsFilter *srcFilter, *dstFilter; + enum PixelFormat dfmt, sfmt; + + vf->priv->colorspace = (struct mp_csp_details) {0}; + + if(!best){ + mp_msg(MSGT_VFILTER,MSGL_WARN,"SwScale: no supported outfmt found :(\n"); + return 0; + } + sfmt = imgfmt2pixfmt(outfmt); + if (outfmt == IMGFMT_RGB8 || outfmt == IMGFMT_BGR8) sfmt = PIX_FMT_PAL8; + dfmt = imgfmt2pixfmt(best); + + vo_flags=vf->next->query_format(vf->next,best); + + vf->priv->w = vf->priv->cfg_w; + vf->priv->h = vf->priv->cfg_h; + + // scaling to dwidth*d_height, if all these TRUE: + // - option -zoom + // - no other sw/hw up/down scaling avail. + // - we're after postproc + // - user didn't set w:h + if(!(vo_flags&VFCAP_POSTPROC) && (flags&VOFLAG_SWSCALE) && + vf->priv->w<0 && vf->priv->h<0){ // -zoom + int x=(vo_flags&VFCAP_SWSCALE) ? 0 : 1; + if(d_width<width || d_height<height){ + // downscale! + if(vo_flags&VFCAP_HWSCALE_DOWN) x=0; + } else { + // upscale: + if(vo_flags&VFCAP_HWSCALE_UP) x=0; + } + if(x){ + // user wants sw scaling! (-zoom) + vf->priv->w=d_width; + vf->priv->h=d_height; + } + } + + if (vf->priv->w <= -8) { + vf->priv->w += 8; + round_w = 1; + } + if (vf->priv->h <= -8) { + vf->priv->h += 8; + round_h = 1; + } + + if (vf->priv->w < -3 || vf->priv->h < -3 || + (vf->priv->w < -1 && vf->priv->h < -1)) { + // TODO: establish a direct connection to the user's brain + // and find out what the heck he thinks MPlayer should do + // with this nonsense. + mp_msg(MSGT_VFILTER, MSGL_ERR, "SwScale: EUSERBROKEN Check your parameters, they make no sense!\n"); + return 0; + } + + if (vf->priv->w == -1) + vf->priv->w = width; + if (vf->priv->w == 0) + vf->priv->w = d_width; + + if (vf->priv->h == -1) + vf->priv->h = height; + if (vf->priv->h == 0) + vf->priv->h = d_height; + + if (vf->priv->w == -3) + vf->priv->w = vf->priv->h * width / height; + if (vf->priv->w == -2) + vf->priv->w = vf->priv->h * d_width / d_height; + + if (vf->priv->h == -3) + vf->priv->h = vf->priv->w * height / width; + if (vf->priv->h == -2) + vf->priv->h = vf->priv->w * d_height / d_width; + + if (round_w) + vf->priv->w = ((vf->priv->w + 8) / 16) * 16; + if (round_h) + vf->priv->h = ((vf->priv->h + 8) / 16) * 16; + + // check for upscaling, now that all parameters had been applied + if(vf->priv->noup){ + if((vf->priv->w > width) + (vf->priv->h > height) >= vf->priv->noup){ + vf->priv->w= width; + vf->priv->h= height; + } + } + + // calculate the missing parameters: + switch(best) { + case IMGFMT_YV12: /* YV12 needs w & h rounded to 2 */ + case IMGFMT_I420: + case IMGFMT_IYUV: + case IMGFMT_NV12: + case IMGFMT_NV21: + vf->priv->h = (vf->priv->h + 1) & ~1; + case IMGFMT_YUY2: /* YUY2 needs w rounded to 2 */ + case IMGFMT_UYVY: + vf->priv->w = (vf->priv->w + 1) & ~1; + } + + mp_msg(MSGT_VFILTER,MSGL_DBG2,"SwScale: scaling %dx%d %s to %dx%d %s \n", + width,height,vo_format_name(outfmt), + vf->priv->w,vf->priv->h,vo_format_name(best)); + + // free old ctx: + if(vf->priv->ctx) sws_freeContext(vf->priv->ctx); + if(vf->priv->ctx2)sws_freeContext(vf->priv->ctx2); + + // new swscaler: + sws_getFlagsAndFilterFromCmdLine(&int_sws_flags, &srcFilter, &dstFilter); + int_sws_flags|= vf->priv->v_chr_drop << SWS_SRC_V_CHR_DROP_SHIFT; + int_sws_flags|= vf->priv->accurate_rnd * SWS_ACCURATE_RND; + vf->priv->ctx=sws_getContext(width, height >> vf->priv->interlaced, + sfmt, + vf->priv->w, vf->priv->h >> vf->priv->interlaced, + dfmt, + int_sws_flags, srcFilter, dstFilter, vf->priv->param); + if(vf->priv->interlaced){ + vf->priv->ctx2=sws_getContext(width, height >> 1, + sfmt, + vf->priv->w, vf->priv->h >> 1, + dfmt, + int_sws_flags, srcFilter, dstFilter, vf->priv->param); + } + if(!vf->priv->ctx){ + // error... + mp_msg(MSGT_VFILTER,MSGL_WARN,"Couldn't init SwScaler for this setup\n"); + return 0; + } + vf->priv->fmt=best; + + free(vf->priv->palette); + vf->priv->palette=NULL; + switch(best){ + case IMGFMT_RGB8: { + /* set 332 palette for 8 bpp */ + vf->priv->palette=malloc(4*256); + for(i=0; i<256; i++){ + vf->priv->palette[4*i+0]=4*(i>>6)*21; + vf->priv->palette[4*i+1]=4*((i>>3)&7)*9; + vf->priv->palette[4*i+2]=4*((i&7)&7)*9; + vf->priv->palette[4*i+3]=0; + } + break; } + case IMGFMT_BGR8: { + /* set 332 palette for 8 bpp */ + vf->priv->palette=malloc(4*256); + for(i=0; i<256; i++){ + vf->priv->palette[4*i+0]=4*(i&3)*21; + vf->priv->palette[4*i+1]=4*((i>>2)&7)*9; + vf->priv->palette[4*i+2]=4*((i>>5)&7)*9; + vf->priv->palette[4*i+3]=0; + } + break; } + case IMGFMT_BGR4: + case IMGFMT_BG4B: { + vf->priv->palette=malloc(4*16); + for(i=0; i<16; i++){ + vf->priv->palette[4*i+0]=4*(i&1)*63; + vf->priv->palette[4*i+1]=4*((i>>1)&3)*21; + vf->priv->palette[4*i+2]=4*((i>>3)&1)*63; + vf->priv->palette[4*i+3]=0; + } + break; } + case IMGFMT_RGB4: + case IMGFMT_RG4B: { + vf->priv->palette=malloc(4*16); + for(i=0; i<16; i++){ + vf->priv->palette[4*i+0]=4*(i>>3)*63; + vf->priv->palette[4*i+1]=4*((i>>1)&3)*21; + vf->priv->palette[4*i+2]=4*((i&1)&1)*63; + vf->priv->palette[4*i+3]=0; + } + break; } + } + + if (!opts->screen_size_x && !opts->screen_size_y + && !(opts->screen_size_xy >= 0.001)) { + // Compute new d_width and d_height, preserving aspect + // while ensuring that both are >= output size in pixels. + if (vf->priv->h * d_width > vf->priv->w * d_height) { + d_width = vf->priv->h * d_width / d_height; + d_height = vf->priv->h; + } else { + d_height = vf->priv->w * d_height / d_width; + d_width = vf->priv->w; + } + //d_width=d_width*vf->priv->w/width; + //d_height=d_height*vf->priv->h/height; + } + return vf_next_config(vf,vf->priv->w,vf->priv->h,d_width,d_height,flags,best); +} + +static void start_slice(struct vf_instance *vf, mp_image_t *mpi){ +// printf("start_slice called! flag=%d\n",mpi->flags&MP_IMGFLAG_DRAW_CALLBACK); + if(!(mpi->flags&MP_IMGFLAG_DRAW_CALLBACK)) return; // shouldn't happen + // they want slices!!! allocate the buffer. + mpi->priv=vf->dmpi=vf_get_image(vf->next,vf->priv->fmt, +// mpi->type, mpi->flags & (~MP_IMGFLAG_DRAW_CALLBACK), + MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE | MP_IMGFLAG_PREFER_ALIGNED_STRIDE, + vf->priv->w, vf->priv->h); +} + +static void scale(struct SwsContext *sws1, struct SwsContext *sws2, uint8_t *src[MP_MAX_PLANES], int src_stride[MP_MAX_PLANES], + int y, int h, uint8_t *dst[MP_MAX_PLANES], int dst_stride[MP_MAX_PLANES], int interlaced){ + const uint8_t *src2[MP_MAX_PLANES]={src[0], src[1], src[2], src[3]}; +#if BYTE_ORDER == BIG_ENDIAN + uint32_t pal2[256]; + if (src[1] && !src[2]){ + int i; + for(i=0; i<256; i++) + pal2[i]= bswap_32(((uint32_t*)src[1])[i]); + src2[1]= pal2; + } +#endif + + if(interlaced){ + int i; + uint8_t *dst2[MP_MAX_PLANES]={dst[0], dst[1], dst[2], dst[3]}; + int src_stride2[MP_MAX_PLANES]={2*src_stride[0], 2*src_stride[1], 2*src_stride[2], 2*src_stride[3]}; + int dst_stride2[MP_MAX_PLANES]={2*dst_stride[0], 2*dst_stride[1], 2*dst_stride[2], 2*dst_stride[3]}; + + sws_scale(sws1, src2, src_stride2, y>>1, h>>1, dst2, dst_stride2); + for(i=0; i<MP_MAX_PLANES; i++){ + src2[i] += src_stride[i]; + dst2[i] += dst_stride[i]; + } + sws_scale(sws2, src2, src_stride2, y>>1, h>>1, dst2, dst_stride2); + }else{ + sws_scale(sws1, src2, src_stride, y, h, dst, dst_stride); + } +} + +static void draw_slice(struct vf_instance *vf, + unsigned char** src, int* stride, int w,int h, int x, int y){ + mp_image_t *dmpi=vf->dmpi; + if(!dmpi){ + mp_msg(MSGT_VFILTER,MSGL_FATAL,"vf_scale: draw_slice() called with dmpi=NULL (no get_image?)\n"); + return; + } +// printf("vf_scale::draw_slice() y=%d h=%d\n",y,h); + scale(vf->priv->ctx, vf->priv->ctx2, src, stride, y, h, dmpi->planes, dmpi->stride, vf->priv->interlaced); +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts){ + mp_image_t *dmpi=mpi->priv; + +// printf("vf_scale::put_image(): processing whole frame! dmpi=%p flag=%d\n", +// dmpi, (mpi->flags&MP_IMGFLAG_DRAW_CALLBACK)); + + if(!(mpi->flags&MP_IMGFLAG_DRAW_CALLBACK && dmpi)){ + + // hope we'll get DR buffer: + dmpi=vf_get_image(vf->next,vf->priv->fmt, + MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE | MP_IMGFLAG_PREFER_ALIGNED_STRIDE, + vf->priv->w, vf->priv->h); + + scale(vf->priv->ctx, vf->priv->ctx, mpi->planes,mpi->stride,0,mpi->h,dmpi->planes,dmpi->stride, vf->priv->interlaced); + } + + if(vf->priv->w==mpi->w && vf->priv->h==mpi->h){ + // just conversion, no scaling -> keep postprocessing data + // this way we can apply pp filter to non-yv12 source using scaler + vf_clone_mpi_attributes(dmpi, mpi); + } + + if(vf->priv->palette) dmpi->planes[1]=vf->priv->palette; // export palette! + + return vf_next_put_image(vf,dmpi, pts); +} + +static int control(struct vf_instance *vf, int request, void* data){ + int *table; + int *inv_table; + int r; + int brightness, contrast, saturation, srcRange, dstRange; + vf_equalizer_t *eq; + + if(vf->priv->ctx) + switch(request){ + case VFCTRL_GET_EQUALIZER: + r= sws_getColorspaceDetails(vf->priv->ctx, &inv_table, &srcRange, &table, &dstRange, &brightness, &contrast, &saturation); + if(r<0) break; + + eq = data; + if (!strcmp(eq->item,"brightness")) { + eq->value = ((brightness*100) + (1<<15))>>16; + } + else if (!strcmp(eq->item,"contrast")) { + eq->value = (((contrast *100) + (1<<15))>>16) - 100; + } + else if (!strcmp(eq->item,"saturation")) { + eq->value = (((saturation*100) + (1<<15))>>16) - 100; + } + else + break; + return CONTROL_TRUE; + case VFCTRL_SET_EQUALIZER: + r= sws_getColorspaceDetails(vf->priv->ctx, &inv_table, &srcRange, &table, &dstRange, &brightness, &contrast, &saturation); + if(r<0) break; +//printf("set %f %f %f\n", brightness/(float)(1<<16), contrast/(float)(1<<16), saturation/(float)(1<<16)); + eq = data; + + if (!strcmp(eq->item,"brightness")) { + brightness = (( eq->value <<16) + 50)/100; + } + else if (!strcmp(eq->item,"contrast")) { + contrast = (((eq->value+100)<<16) + 50)/100; + } + else if (!strcmp(eq->item,"saturation")) { + saturation = (((eq->value+100)<<16) + 50)/100; + } + else + break; + + r= sws_setColorspaceDetails(vf->priv->ctx, inv_table, srcRange, table, dstRange, brightness, contrast, saturation); + if(r<0) break; + if(vf->priv->ctx2){ + r= sws_setColorspaceDetails(vf->priv->ctx2, inv_table, srcRange, table, dstRange, brightness, contrast, saturation); + if(r<0) break; + } + + return CONTROL_TRUE; + case VFCTRL_SET_YUV_COLORSPACE: { + struct mp_csp_details colorspace = *(struct mp_csp_details *)data; + if (mp_sws_set_colorspace(vf->priv->ctx, &colorspace) >= 0) { + if (vf->priv->ctx2) + mp_sws_set_colorspace(vf->priv->ctx2, &colorspace); + vf->priv->colorspace = colorspace; + return 1; + } + break; + } + case VFCTRL_GET_YUV_COLORSPACE: { + /* This scale filter should never react to colorspace commands if it + * doesn't do YUV->RGB conversion. But because finding out whether this + * is really YUV->RGB (and not YUV->YUV or anything else) is hard, + * react only if the colorspace has been set explicitly before. The + * trick is that mp_sws_set_colorspace does not succeed for YUV->YUV + * and RGB->YUV conversions, which makes this code correct in "most" + * cases. (This would be trivial to do correctly if libswscale exposed + * functionality like isYUV()). + */ + if (vf->priv->colorspace.format) { + *(struct mp_csp_details *)data = vf->priv->colorspace; + return CONTROL_TRUE; + } + break; + } + default: + break; + } + + return vf_next_control(vf,request,data); +} + +static const int mp_csp_to_swscale[MP_CSP_COUNT] = { + [MP_CSP_BT_601] = SWS_CS_ITU601, + [MP_CSP_BT_709] = SWS_CS_ITU709, + [MP_CSP_SMPTE_240M] = SWS_CS_SMPTE240M, +}; + +// Adjust the colorspace used for YUV->RGB conversion. On other conversions, +// do nothing or return an error. +// The csp argument is set to the supported values. +// Return 0 on success and -1 on error. +int mp_sws_set_colorspace(struct SwsContext *sws, struct mp_csp_details *csp) +{ + int *table, *inv_table; + int brightness, contrast, saturation, srcRange, dstRange; + + csp->levels_out = MP_CSP_LEVELS_PC; + + // NOTE: returns an error if the destination format is YUV + if (sws_getColorspaceDetails(sws, &inv_table, &srcRange, &table, &dstRange, + &brightness, &contrast, &saturation) == -1) + goto error_out; + + int sws_csp = mp_csp_to_swscale[csp->format]; + if (sws_csp == 0) { + // colorspace not supported, go with a reasonable default + csp->format = SWS_CS_ITU601; + sws_csp = MP_CSP_BT_601; + } + + /* The swscale API for these is hardly documented. + * Apparently table/range only apply to YUV. Thus dstRange has no effect + * for YUV->RGB conversions, and conversions to limited-range RGB are + * not supported. + */ + srcRange = csp->levels_in == MP_CSP_LEVELS_PC; + const int *new_inv_table = sws_getCoefficients(sws_csp); + + if (sws_setColorspaceDetails(sws, new_inv_table, srcRange, table, dstRange, + brightness, contrast, saturation) == -1) + goto error_out; + + return 0; + +error_out: + *csp = (struct mp_csp_details){0}; + return -1; +} + +//===========================================================================// + +// supported Input formats: YV12, I420, IYUV, YUY2, UYVY, BGR32, BGR24, BGR16, BGR15, RGB32, RGB24, Y8, Y800 + +static int query_format(struct vf_instance *vf, unsigned int fmt){ + if (!IMGFMT_IS_HWACCEL(fmt) && imgfmt2pixfmt(fmt) != PIX_FMT_NONE) { + unsigned int best=find_best_out(vf, fmt); + int flags; + if(!best) return 0; // no matching out-fmt + flags=vf_next_query_format(vf,best); + if(!(flags&(VFCAP_CSP_SUPPORTED|VFCAP_CSP_SUPPORTED_BY_HW))) return 0; // huh? + if(fmt!=best) flags&=~VFCAP_CSP_SUPPORTED_BY_HW; + // do not allow scaling, if we are before the PP fliter! + if(!(flags&VFCAP_POSTPROC)) flags|=VFCAP_SWSCALE; + return flags; + } + return 0; // nomatching in-fmt +} + +static void uninit(struct vf_instance *vf){ + if(vf->priv->ctx) sws_freeContext(vf->priv->ctx); + if(vf->priv->ctx2) sws_freeContext(vf->priv->ctx2); + free(vf->priv->palette); + free(vf->priv); +} + +static int vf_open(vf_instance_t *vf, char *args){ + vf->config=config; + vf->start_slice=start_slice; + vf->draw_slice=draw_slice; + vf->put_image=put_image; + vf->query_format=query_format; + vf->control= control; + vf->uninit=uninit; + mp_msg(MSGT_VFILTER,MSGL_V,"SwScale params: %d x %d (-1=no scaling)\n", + vf->priv->cfg_w, + vf->priv->cfg_h); + + return 1; +} + +/// An example of presets usage +static const struct size_preset { + char* name; + int w, h; +} vf_size_presets_defs[] = { + // TODO add more 'standard' resolutions + { "qntsc", 352, 240 }, + { "qpal", 352, 288 }, + { "ntsc", 720, 480 }, + { "pal", 720, 576 }, + { "sntsc", 640, 480 }, + { "spal", 768, 576 }, + { NULL, 0, 0} +}; + +#define ST_OFF(f) M_ST_OFF(struct size_preset,f) +static const m_option_t vf_size_preset_fields[] = { + {"w", ST_OFF(w), CONF_TYPE_INT, M_OPT_MIN,1 ,0, NULL}, + {"h", ST_OFF(h), CONF_TYPE_INT, M_OPT_MIN,1 ,0, NULL}, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; + +static const m_struct_t vf_size_preset = { + "scale_size_preset", + sizeof(struct size_preset), + NULL, + vf_size_preset_fields +}; + +static const m_struct_t vf_opts; +static const m_obj_presets_t size_preset = { + &vf_size_preset, // Input struct desc + &vf_opts, // Output struct desc + vf_size_presets_defs, // The list of presets + ST_OFF(name) // At wich offset is the name field in the preset struct +}; + +/// Now the options +#undef ST_OFF +#define ST_OFF(f) M_ST_OFF(struct vf_priv_s,f) +static const m_option_t vf_opts_fields[] = { + {"w", ST_OFF(cfg_w), CONF_TYPE_INT, M_OPT_MIN,-11,0, NULL}, + {"h", ST_OFF(cfg_h), CONF_TYPE_INT, M_OPT_MIN,-11,0, NULL}, + {"interlaced", ST_OFF(interlaced), CONF_TYPE_INT, M_OPT_RANGE, 0, 1, NULL}, + {"chr-drop", ST_OFF(v_chr_drop), CONF_TYPE_INT, M_OPT_RANGE, 0, 3, NULL}, + {"param" , ST_OFF(param[0]), CONF_TYPE_DOUBLE, M_OPT_RANGE, 0.0, 100.0, NULL}, + {"param2", ST_OFF(param[1]), CONF_TYPE_DOUBLE, M_OPT_RANGE, 0.0, 100.0, NULL}, + // Note that here the 2 field is NULL (ie 0) + // As we want this option to act on the option struct itself + {"presize", 0, CONF_TYPE_OBJ_PRESETS, 0, 0, 0, (void *)&size_preset}, + {"noup", ST_OFF(noup), CONF_TYPE_INT, M_OPT_RANGE, 0, 2, NULL}, + {"arnd", ST_OFF(accurate_rnd), CONF_TYPE_FLAG, 0, 0, 1, NULL}, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; + +static const m_struct_t vf_opts = { + "scale", + sizeof(struct vf_priv_s), + &vf_priv_dflt, + vf_opts_fields +}; + +const vf_info_t vf_info_scale = { + "software scaling", + "scale", + "A'rpi", + "", + vf_open, + &vf_opts +}; + +//===========================================================================// diff --git a/video/filter/vf_screenshot.c b/video/filter/vf_screenshot.c new file mode 100644 index 0000000000..693d871e5f --- /dev/null +++ b/video/filter/vf_screenshot.c @@ -0,0 +1,219 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> + +#include "mp_msg.h" +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" +#include "libmpcodecs/sws_utils.h" +#include "fmt-conversion.h" +#include "libvo/fastmemcpy.h" + +#include <libswscale/swscale.h> + +struct vf_priv_s { + mp_image_t *image; + void (*image_callback)(void *, mp_image_t *); + void *image_callback_ctx; + int shot, store_slices; +}; + +//===========================================================================// + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt) +{ + free_mp_image(vf->priv->image); + vf->priv->image = new_mp_image(width, height); + mp_image_setfmt(vf->priv->image, outfmt); + vf->priv->image->w = d_width; + vf->priv->image->h = d_height; + return vf_next_config(vf,width,height,d_width,d_height,flags,outfmt); +} + +static void start_slice(struct vf_instance *vf, mp_image_t *mpi) +{ + mpi->priv= + vf->dmpi=vf_get_image(vf->next,mpi->imgfmt, + mpi->type, mpi->flags, mpi->width, mpi->height); + if (vf->priv->shot) { + vf->priv->store_slices = 1; + if (!(vf->priv->image->flags & MP_IMGFLAG_ALLOCATED)) + mp_image_alloc_planes(vf->priv->image); + } + +} + +static void memcpy_pic_slice(unsigned char *dst, unsigned char *src, + int bytesPerLine, int y, int h, + int dstStride, int srcStride) +{ + memcpy_pic(dst + h * dstStride, src + h * srcStride, bytesPerLine, + h, dstStride, srcStride); +} + +static void draw_slice(struct vf_instance *vf, unsigned char** src, + int* stride, int w,int h, int x, int y) +{ + if (vf->priv->store_slices) { + mp_image_t *dst = vf->priv->image; + int bp = (dst->bpp + 7) / 8; + + if (dst->flags & MP_IMGFLAG_PLANAR) { + int bytes_per_line[3] = { w * bp, dst->chroma_width, dst->chroma_width }; + for (int n = 0; n < 3; n++) { + memcpy_pic_slice(dst->planes[n], src[n], bytes_per_line[n], + y, h, dst->stride[n], stride[n]); + } + } else { + memcpy_pic_slice(dst->planes[0], src[0], dst->w*bp, y, dst->h, + dst->stride[0], stride[0]); + } + } + vf_next_draw_slice(vf,src,stride,w,h,x,y); +} + +static void get_image(struct vf_instance *vf, mp_image_t *mpi) +{ + // FIXME: should vf.c really call get_image when using slices?? + if (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK) + return; + vf->dmpi= vf_get_image(vf->next, mpi->imgfmt, + mpi->type, mpi->flags/* | MP_IMGFLAG_READABLE*/, mpi->width, mpi->height); + + for (int i = 0; i < MP_MAX_PLANES; i++) { + mpi->planes[i]=vf->dmpi->planes[i]; + mpi->stride[i]=vf->dmpi->stride[i]; + } + mpi->width=vf->dmpi->width; + + mpi->flags|=MP_IMGFLAG_DIRECT; + + mpi->priv=(void*)vf->dmpi; +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts) +{ + mp_image_t *dmpi = (mp_image_t *)mpi->priv; + + if(!(mpi->flags&(MP_IMGFLAG_DIRECT|MP_IMGFLAG_DRAW_CALLBACK))){ + dmpi=vf_get_image(vf->next,mpi->imgfmt, + MP_IMGTYPE_EXPORT, 0, + mpi->width, mpi->height); + vf_clone_mpi_attributes(dmpi, mpi); + for (int i = 0; i < MP_MAX_PLANES; i++) { + dmpi->planes[i]=mpi->planes[i]; + dmpi->stride[i]=mpi->stride[i]; + } + dmpi->width=mpi->width; + dmpi->height=mpi->height; + } + + if(vf->priv->shot) { + vf->priv->shot=0; + mp_image_t image; + if (!vf->priv->store_slices) + image = *dmpi; + else + image = *vf->priv->image; + image.w = vf->priv->image->w; + image.h = vf->priv->image->h; + vf_clone_mpi_attributes(&image, mpi); + vf->priv->image_callback(vf->priv->image_callback_ctx, &image); + vf->priv->store_slices = 0; + } + + return vf_next_put_image(vf, dmpi, pts); +} + +static int control (vf_instance_t *vf, int request, void *data) +{ + if(request==VFCTRL_SCREENSHOT) { + struct vf_ctrl_screenshot *cmd = (struct vf_ctrl_screenshot *)data; + vf->priv->image_callback = cmd->image_callback; + vf->priv->image_callback_ctx = cmd->image_callback_ctx; + vf->priv->shot=1; + return CONTROL_TRUE; + } + return vf_next_control (vf, request, data); +} + + +//===========================================================================// + +static int query_format(struct vf_instance *vf, unsigned int fmt) +{ + enum PixelFormat av_format = imgfmt2pixfmt(fmt); + + if (av_format != PIX_FMT_NONE && sws_isSupportedInput(av_format)) + return vf_next_query_format(vf, fmt); + return 0; +} + +static void uninit(vf_instance_t *vf) +{ + free_mp_image(vf->priv->image); + free(vf->priv); +} + +static int vf_open(vf_instance_t *vf, char *args) +{ + vf->config=config; + vf->control=control; + vf->put_image=put_image; + vf->query_format=query_format; + vf->start_slice=start_slice; + vf->draw_slice=draw_slice; + vf->get_image=get_image; + vf->uninit=uninit; + vf->priv=malloc(sizeof(struct vf_priv_s)); + vf->priv->shot=0; + vf->priv->store_slices=0; + vf->priv->image=NULL; + return 1; +} + +const vf_info_t vf_info_screenshot = { + "screenshot to file", + "screenshot", + "A'rpi, Jindrich Makovicka", + "", + vf_open, + NULL +}; + +// screenshot.c will look for a filter named "screenshot_force", and not use +// the VO based screenshot code if it's in the filter chain. +const vf_info_t vf_info_screenshot_force = { + "screenshot to file (override VO based screenshot code)", + "screenshot_force", + "A'rpi, Jindrich Makovicka", + "", + vf_open, + NULL +}; + +//===========================================================================// diff --git a/video/filter/vf_softpulldown.c b/video/filter/vf_softpulldown.c new file mode 100644 index 0000000000..d07f9d6e26 --- /dev/null +++ b/video/filter/vf_softpulldown.c @@ -0,0 +1,182 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "config.h" +#include "mp_msg.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + +#include "libvo/fastmemcpy.h" + +struct vf_priv_s { + int state; + long long in; + long long out; + struct vf_detc_pts_buf ptsbuf; + int last_frame_duration; + double buffered_pts; + mp_image_t *buffered_mpi; + int buffered_last_frame_duration; +}; + +static int continue_buffered_image(struct vf_instance *vf) +{ + double pts = vf->priv->buffered_pts; + mp_image_t *mpi = vf->priv->buffered_mpi; + vf->priv->out++; + vf->priv->state=0; + return vf_next_put_image(vf, mpi, vf_softpulldown_adjust_pts(&vf->priv->ptsbuf, pts, 0, 0, vf->priv->buffered_last_frame_duration)); +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts) +{ + mp_image_t *dmpi; + int ret = 0; + int flags = mpi->fields; + int state = vf->priv->state; + + dmpi = vf_get_image(vf->next, mpi->imgfmt, + MP_IMGTYPE_STATIC, MP_IMGFLAG_ACCEPT_STRIDE | + MP_IMGFLAG_PRESERVE, mpi->width, mpi->height); + + vf->priv->in++; + + if ((state == 0 && + !(flags & MP_IMGFIELD_TOP_FIRST)) || + (state == 1 && + flags & MP_IMGFIELD_TOP_FIRST)) { + mp_msg(MSGT_VFILTER, MSGL_WARN, + "softpulldown: Unexpected field flags: state=%d top_field_first=%d repeat_first_field=%d\n", + state, + (flags & MP_IMGFIELD_TOP_FIRST) != 0, + (flags & MP_IMGFIELD_REPEAT_FIRST) != 0); + state ^= 1; + } + + if (state == 0) { + ret = vf_next_put_image(vf, mpi, vf_softpulldown_adjust_pts(&vf->priv->ptsbuf, pts, 0, 0, vf->priv->last_frame_duration)); + vf->priv->out++; + if (flags & MP_IMGFIELD_REPEAT_FIRST) { + my_memcpy_pic(dmpi->planes[0], + mpi->planes[0], mpi->w, mpi->h/2, + dmpi->stride[0]*2, mpi->stride[0]*2); + if (mpi->flags & MP_IMGFLAG_PLANAR) { + my_memcpy_pic(dmpi->planes[1], + mpi->planes[1], + mpi->chroma_width, + mpi->chroma_height/2, + dmpi->stride[1]*2, + mpi->stride[1]*2); + my_memcpy_pic(dmpi->planes[2], + mpi->planes[2], + mpi->chroma_width, + mpi->chroma_height/2, + dmpi->stride[2]*2, + mpi->stride[2]*2); + } + state=1; + } + } else { + my_memcpy_pic(dmpi->planes[0]+dmpi->stride[0], + mpi->planes[0]+mpi->stride[0], mpi->w, mpi->h/2, + dmpi->stride[0]*2, mpi->stride[0]*2); + if (mpi->flags & MP_IMGFLAG_PLANAR) { + my_memcpy_pic(dmpi->planes[1]+dmpi->stride[1], + mpi->planes[1]+mpi->stride[1], + mpi->chroma_width, mpi->chroma_height/2, + dmpi->stride[1]*2, mpi->stride[1]*2); + my_memcpy_pic(dmpi->planes[2]+dmpi->stride[2], + mpi->planes[2]+mpi->stride[2], + mpi->chroma_width, mpi->chroma_height/2, + dmpi->stride[2]*2, mpi->stride[2]*2); + } + ret = vf_next_put_image(vf, dmpi, vf_softpulldown_adjust_pts(&vf->priv->ptsbuf, pts, 0, 0, vf->priv->last_frame_duration)); + vf->priv->out++; + if (flags & MP_IMGFIELD_REPEAT_FIRST) { + vf->priv->buffered_mpi = mpi; + vf->priv->buffered_pts = pts; + vf->priv->buffered_last_frame_duration = vf->priv->last_frame_duration; + vf_queue_frame(vf, continue_buffered_image); + } else { + my_memcpy_pic(dmpi->planes[0], + mpi->planes[0], mpi->w, mpi->h/2, + dmpi->stride[0]*2, mpi->stride[0]*2); + if (mpi->flags & MP_IMGFLAG_PLANAR) { + my_memcpy_pic(dmpi->planes[1], + mpi->planes[1], + mpi->chroma_width, + mpi->chroma_height/2, + dmpi->stride[1]*2, + mpi->stride[1]*2); + my_memcpy_pic(dmpi->planes[2], + mpi->planes[2], + mpi->chroma_width, + mpi->chroma_height/2, + dmpi->stride[2]*2, + mpi->stride[2]*2); + } + } + } + + vf->priv->state = state; + if (flags & MP_IMGFIELD_REPEAT_FIRST) + vf->priv->last_frame_duration = 3; + else + vf->priv->last_frame_duration = 2; + + return ret; +} + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt) +{ + return vf_next_config(vf,width,height,d_width,d_height,flags,outfmt); +} + +static void uninit(struct vf_instance *vf) +{ + mp_msg(MSGT_VFILTER, MSGL_INFO, "softpulldown: %lld frames in, %lld frames out\n", vf->priv->in, vf->priv->out); + free(vf->priv); +} + +static int vf_open(vf_instance_t *vf, char *args) +{ + vf->config = config; + vf->put_image = put_image; + vf->uninit = uninit; + vf->default_reqs = VFCAP_ACCEPT_STRIDE; + vf->priv = calloc(1, sizeof(struct vf_priv_s)); + vf->priv->state = 0; + return 1; +} + +const vf_info_t vf_info_softpulldown = { + "mpeg2 soft 3:2 pulldown", + "softpulldown", + "Tobias Diedrich <ranma+mplayer@tdiedrich.de>", + "", + vf_open, + NULL +}; diff --git a/video/filter/vf_stereo3d.c b/video/filter/vf_stereo3d.c new file mode 100644 index 0000000000..60208cb3c6 --- /dev/null +++ b/video/filter/vf_stereo3d.c @@ -0,0 +1,527 @@ +/* + * Copyright (C) 2010 Gordon Schmidt <gordon.schmidt <at> s2000.tu-chemnitz.de> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +//==includes==// +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "config.h" +#include "mp_msg.h" +#include "options.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" +#include "m_struct.h" +#include "m_option.h" + +#include "libavutil/common.h" +#include "libvo/fastmemcpy.h" + +//==types==// +typedef enum stereo_code { + ANAGLYPH_RC_GRAY, //anaglyph red/cyan gray + ANAGLYPH_RC_HALF, //anaglyph red/cyan half colored + ANAGLYPH_RC_COLOR, //anaglyph red/cyan colored + ANAGLYPH_RC_DUBOIS, //anaglyph red/cyan dubois + ANAGLYPH_GM_GRAY, //anaglyph green/magenta gray + ANAGLYPH_GM_HALF, //anaglyph green/magenta half colored + ANAGLYPH_GM_COLOR, //anaglyph green/magenta colored + ANAGLYPH_YB_GRAY, //anaglyph yellow/blue gray + ANAGLYPH_YB_HALF, //anaglyph yellow/blue half colored + ANAGLYPH_YB_COLOR, //anaglyph yellow/blue colored + ANAGLYPH_YB_DUBOIS, //anaglyph yellow/blue dubois + MONO_L, //mono output for debugging (left eye only) + MONO_R, //mono output for debugging (right eye only) + SIDE_BY_SIDE_LR, //side by side parallel (left eye left, right eye right) + SIDE_BY_SIDE_RL, //side by side crosseye (right eye left, left eye right) + SIDE_BY_SIDE_2_LR, //side by side parallel with half width resolution + SIDE_BY_SIDE_2_RL, //side by side crosseye with half width resolution + ABOVE_BELOW_LR, //above-below (left eye above, right eye below) + ABOVE_BELOW_RL, //above-below (right eye above, left eye below) + ABOVE_BELOW_2_LR, //above-below with half height resolution + ABOVE_BELOW_2_RL, //above-below with half height resolution + INTERLEAVE_ROWS_LR, //row-interleave (left eye has top row) + INTERLEAVE_ROWS_RL, //row-interleave (right eye has top row) + STEREO_CODE_COUNT //no value set - TODO: needs autodetection +} stereo_code; + +typedef struct component { + stereo_code fmt; + unsigned int width; + unsigned int height; + unsigned int off_left; + unsigned int off_right; + unsigned int row_left; + unsigned int row_right; +} component; + +//==global variables==// +static const int ana_coeff[][3][6] = { + [ANAGLYPH_RC_GRAY] = + {{19595, 38470, 7471, 0, 0, 0}, + { 0, 0, 0, 19595, 38470, 7471}, + { 0, 0, 0, 19595, 38470, 7471}}, + [ANAGLYPH_RC_HALF] = + {{19595, 38470, 7471, 0, 0, 0}, + { 0, 0, 0, 0, 65536, 0}, + { 0, 0, 0, 0, 0, 65536}}, + [ANAGLYPH_RC_COLOR] = + {{65536, 0, 0, 0, 0, 0}, + { 0, 0, 0, 0, 65536, 0}, + { 0, 0, 0, 0, 0, 65536}}, + [ANAGLYPH_RC_DUBOIS] = + {{29891, 32800, 11559, -2849, -5763, -102}, + {-2627, -2479, -1033, 24804, 48080, -1209}, + { -997, -1350, -358, -4729, -7403, 80373}}, + [ANAGLYPH_GM_GRAY] = + {{ 0, 0, 0, 19595, 38470, 7471}, + {19595, 38470, 7471, 0, 0, 0}, + { 0, 0, 0, 19595, 38470, 7471}}, + [ANAGLYPH_GM_HALF] = + {{ 0, 0, 0, 65536, 0, 0}, + {19595, 38470, 7471, 0, 0, 0}, + { 0, 0, 0, 0, 0, 65536}}, + [ANAGLYPH_GM_COLOR] = + {{ 0, 0, 0, 65536, 0, 0}, + { 0, 65536, 0, 0, 0, 0}, + { 0, 0, 0, 0, 0, 65536}}, + [ANAGLYPH_YB_GRAY] = + {{ 0, 0, 0, 19595, 38470, 7471}, + { 0, 0, 0, 19595, 38470, 7471}, + {19595, 38470, 7471, 0, 0, 0}}, + [ANAGLYPH_YB_HALF] = + {{ 0, 0, 0, 65536, 0, 0}, + { 0, 0, 0, 0, 65536, 0}, + {19595, 38470, 7471, 0, 0, 0}}, + [ANAGLYPH_YB_COLOR] = + {{ 0, 0, 0, 65536, 0, 0}, + { 0, 0, 0, 0, 65536, 0}, + { 0, 0, 65536, 0, 0, 0}}, + [ANAGLYPH_YB_DUBOIS] = + {{65535,-12650,18451, -987, -7590, -1049}, + {-1604, 56032, 4196, 370, 3826, -1049}, + {-2345,-10676, 1358, 5801, 11416, 56217}}, +}; + +struct vf_priv_s { + component in; + component out; + int ana_matrix[3][6]; + unsigned int width; + unsigned int height; + unsigned int row_step; +} const vf_priv_default = { + {SIDE_BY_SIDE_LR}, + {ANAGLYPH_RC_DUBOIS} +}; + +//==functions==// +static inline uint8_t ana_convert(int coeff[6], uint8_t left[3], uint8_t right[3]) +{ + int sum; + + sum = coeff[0] * left[0] + coeff[3] * right[0]; //red in + sum += coeff[1] * left[1] + coeff[4] * right[1]; //green in + sum += coeff[2] * left[2] + coeff[5] * right[2]; //blue in + return av_clip_uint8(sum >> 16); +} + +static int config(struct vf_instance *vf, int width, int height, int d_width, + int d_height, unsigned int flags, unsigned int outfmt) +{ + struct MPOpts *opts = vf->opts; + + if ((width & 1) || (height & 1)) { + mp_msg(MSGT_VFILTER, MSGL_WARN, "[stereo3d] invalid height or width\n"); + return 0; + } + //default input values + vf->priv->width = width; + vf->priv->height = height; + vf->priv->row_step = 1; + vf->priv->in.width = width; + vf->priv->in.height = height; + vf->priv->in.off_left = 0; + vf->priv->in.off_right = 0; + vf->priv->in.row_left = 0; + vf->priv->in.row_right = 0; + + //check input format + switch (vf->priv->in.fmt) { + case SIDE_BY_SIDE_2_LR: + d_width *= 2; + case SIDE_BY_SIDE_LR: + vf->priv->width = width / 2; + vf->priv->in.off_right = vf->priv->width * 3; + break; + case SIDE_BY_SIDE_2_RL: + d_width *= 2; + case SIDE_BY_SIDE_RL: + vf->priv->width = width / 2; + vf->priv->in.off_left = vf->priv->width * 3; + break; + case ABOVE_BELOW_2_LR: + d_height *= 2; + case ABOVE_BELOW_LR: + vf->priv->height = height / 2; + vf->priv->in.row_right = vf->priv->height; + break; + case ABOVE_BELOW_2_RL: + d_height *= 2; + case ABOVE_BELOW_RL: + vf->priv->height = height / 2; + vf->priv->in.row_left = vf->priv->height; + break; + default: + mp_msg(MSGT_VFILTER, MSGL_WARN, + "[stereo3d] stereo format of input is not supported\n"); + return 0; + break; + } + //default output values + vf->priv->out.width = vf->priv->width; + vf->priv->out.height = vf->priv->height; + vf->priv->out.off_left = 0; + vf->priv->out.off_right = 0; + vf->priv->out.row_left = 0; + vf->priv->out.row_right = 0; + + //check output format + switch (vf->priv->out.fmt) { + case ANAGLYPH_RC_GRAY: + case ANAGLYPH_RC_HALF: + case ANAGLYPH_RC_COLOR: + case ANAGLYPH_RC_DUBOIS: + case ANAGLYPH_GM_GRAY: + case ANAGLYPH_GM_HALF: + case ANAGLYPH_GM_COLOR: + case ANAGLYPH_YB_GRAY: + case ANAGLYPH_YB_HALF: + case ANAGLYPH_YB_COLOR: + case ANAGLYPH_YB_DUBOIS: + memcpy(vf->priv->ana_matrix, ana_coeff[vf->priv->out.fmt], + sizeof(vf->priv->ana_matrix)); + break; + case SIDE_BY_SIDE_2_LR: + d_width /= 2; + case SIDE_BY_SIDE_LR: + vf->priv->out.width = vf->priv->width * 2; + vf->priv->out.off_right = vf->priv->width * 3; + break; + case SIDE_BY_SIDE_2_RL: + d_width /= 2; + case SIDE_BY_SIDE_RL: + vf->priv->out.width = vf->priv->width * 2; + vf->priv->out.off_left = vf->priv->width * 3; + break; + case ABOVE_BELOW_2_LR: + d_height /= 2; + case ABOVE_BELOW_LR: + vf->priv->out.height = vf->priv->height * 2; + vf->priv->out.row_right = vf->priv->height; + break; + case ABOVE_BELOW_2_RL: + d_height /= 2; + case ABOVE_BELOW_RL: + vf->priv->out.height = vf->priv->height * 2; + vf->priv->out.row_left = vf->priv->height; + break; + case INTERLEAVE_ROWS_LR: + vf->priv->row_step = 2; + vf->priv->height = vf->priv->height / 2; + vf->priv->out.off_right = vf->priv->width * 3; + vf->priv->in.off_right += vf->priv->in.width * 3; + break; + case INTERLEAVE_ROWS_RL: + vf->priv->row_step = 2; + vf->priv->height = vf->priv->height / 2; + vf->priv->out.off_left = vf->priv->width * 3; + vf->priv->in.off_left += vf->priv->in.width * 3; + break; + case MONO_R: + //same as MONO_L only needs switching of input offsets + vf->priv->in.off_left = vf->priv->in.off_right; + vf->priv->in.row_left = vf->priv->in.row_right; + //nobreak; + case MONO_L: + //use default settings + break; + default: + mp_msg(MSGT_VFILTER, MSGL_WARN, + "[stereo3d] stereo format of output is not supported\n"); + return 0; + break; + } + if (!opts->screen_size_x && !opts->screen_size_y) { + d_width = d_width * vf->priv->out.width / width; + d_height = d_height * vf->priv->out.height / height; + } + return vf_next_config(vf, vf->priv->out.width, vf->priv->out.height, + d_width, d_height, flags, outfmt); +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts) +{ + mp_image_t *dmpi; + if (vf->priv->in.fmt == vf->priv->out.fmt) { //nothing to do + dmpi = mpi; + } else { + int out_off_left, out_off_right; + int in_off_left = vf->priv->in.row_left * mpi->stride[0] + + vf->priv->in.off_left; + int in_off_right = vf->priv->in.row_right * mpi->stride[0] + + vf->priv->in.off_right; + + dmpi = vf_get_image(vf->next, IMGFMT_RGB24, MP_IMGTYPE_TEMP, + MP_IMGFLAG_ACCEPT_STRIDE, + vf->priv->out.width, vf->priv->out.height); + out_off_left = vf->priv->out.row_left * dmpi->stride[0] + + vf->priv->out.off_left; + out_off_right = vf->priv->out.row_right * dmpi->stride[0] + + vf->priv->out.off_right; + + switch (vf->priv->out.fmt) { + case SIDE_BY_SIDE_LR: + case SIDE_BY_SIDE_RL: + case SIDE_BY_SIDE_2_LR: + case SIDE_BY_SIDE_2_RL: + case ABOVE_BELOW_LR: + case ABOVE_BELOW_RL: + case ABOVE_BELOW_2_LR: + case ABOVE_BELOW_2_RL: + case INTERLEAVE_ROWS_LR: + case INTERLEAVE_ROWS_RL: + memcpy_pic2(dmpi->planes[0] + out_off_left, + mpi->planes[0] + in_off_left, + 3 * vf->priv->width, + vf->priv->height, + dmpi->stride[0] * vf->priv->row_step, + mpi->stride[0] * vf->priv->row_step, + vf->priv->row_step != 1); + memcpy_pic2(dmpi->planes[0] + out_off_right, + mpi->planes[0] + in_off_right, + 3 * vf->priv->width, + vf->priv->height, + dmpi->stride[0] * vf->priv->row_step, + mpi->stride[0] * vf->priv->row_step, + vf->priv->row_step != 1); + break; + case MONO_L: + case MONO_R: + memcpy_pic(dmpi->planes[0], + mpi->planes[0] + in_off_left, + 3 * vf->priv->width, + vf->priv->height, + dmpi->stride[0], + mpi->stride[0]); + break; + case ANAGLYPH_RC_GRAY: + case ANAGLYPH_RC_HALF: + case ANAGLYPH_RC_COLOR: + case ANAGLYPH_RC_DUBOIS: + case ANAGLYPH_GM_GRAY: + case ANAGLYPH_GM_HALF: + case ANAGLYPH_GM_COLOR: + case ANAGLYPH_YB_GRAY: + case ANAGLYPH_YB_HALF: + case ANAGLYPH_YB_COLOR: { + int x,y,il,ir,o; + unsigned char *source = mpi->planes[0]; + unsigned char *dest = dmpi->planes[0]; + unsigned int out_width = vf->priv->out.width; + int *ana_matrix[3]; + + for(int i = 0; i < 3; i++) + ana_matrix[i] = vf->priv->ana_matrix[i]; + + for (y = 0; y < vf->priv->out.height; y++) { + o = dmpi->stride[0] * y; + il = in_off_left + y * mpi->stride[0]; + ir = in_off_right + y * mpi->stride[0]; + for (x = 0; x < out_width; x++) { + dest[o ] = ana_convert( + ana_matrix[0], source + il, source + ir); //red out + dest[o + 1] = ana_convert( + ana_matrix[1], source + il, source + ir); //green out + dest[o + 2] = ana_convert( + ana_matrix[2], source + il, source + ir); //blue out + il += 3; + ir += 3; + o += 3; + } + } + break; + } + default: + mp_msg(MSGT_VFILTER, MSGL_WARN, + "[stereo3d] stereo format of output is not supported\n"); + return 0; + break; + } + } + return vf_next_put_image(vf, dmpi, pts); +} + +static int query_format(struct vf_instance *vf, unsigned int fmt) +{ + switch (fmt) + case IMGFMT_RGB24: + return vf_next_query_format(vf, fmt); + return 0; +} + +static void uninit(vf_instance_t *vf) +{ + free(vf->priv); +} + +static int vf_open(vf_instance_t *vf, char *args) +{ + vf->config = config; + vf->uninit = uninit; + vf->put_image = put_image; + vf->query_format = query_format; + + return 1; +} + +///Presets usage +static const struct format_preset { + char* name; + stereo_code scode; +} vf_format_presets_defs[] = { + {"arcg", ANAGLYPH_RC_GRAY}, + {"anaglyph_red_cyan_gray", ANAGLYPH_RC_GRAY}, + {"arch", ANAGLYPH_RC_HALF}, + {"anaglyph_red_cyan_half_color", ANAGLYPH_RC_HALF}, + {"arcc", ANAGLYPH_RC_COLOR}, + {"anaglyph_red_cyan_color", ANAGLYPH_RC_COLOR}, + {"arcd", ANAGLYPH_RC_DUBOIS}, + {"anaglyph_red_cyan_dubios", ANAGLYPH_RC_DUBOIS}, + {"agmg", ANAGLYPH_GM_GRAY}, + {"anaglyph_green_magenta_gray", ANAGLYPH_GM_GRAY}, + {"agmh", ANAGLYPH_GM_HALF}, + {"anaglyph_green_magenta_half_color",ANAGLYPH_GM_HALF}, + {"agmc", ANAGLYPH_GM_COLOR}, + {"anaglyph_green_magenta_color", ANAGLYPH_GM_COLOR}, + {"aybg", ANAGLYPH_YB_GRAY}, + {"anaglyph_yellow_blue_gray", ANAGLYPH_YB_GRAY}, + {"aybh", ANAGLYPH_YB_HALF}, + {"anaglyph_yellow_blue_half_color", ANAGLYPH_YB_HALF}, + {"aybc", ANAGLYPH_YB_COLOR}, + {"anaglyph_yellow_blue_color", ANAGLYPH_YB_COLOR}, + {"aybd", ANAGLYPH_YB_DUBOIS}, + {"anaglyph_yellow_blue_dubois", ANAGLYPH_YB_DUBOIS}, + {"ml", MONO_L}, + {"mono_left", MONO_L}, + {"mr", MONO_R}, + {"mono_right", MONO_R}, + {"sbsl", SIDE_BY_SIDE_LR}, + {"side_by_side_left_first", SIDE_BY_SIDE_LR}, + {"sbsr", SIDE_BY_SIDE_RL}, + {"side_by_side_right_first", SIDE_BY_SIDE_RL}, + {"sbs2l", SIDE_BY_SIDE_2_LR}, + {"side_by_side_half_width_left_first", SIDE_BY_SIDE_2_LR}, + {"sbs2r", SIDE_BY_SIDE_2_RL}, + {"side_by_side_half_width_right_first",SIDE_BY_SIDE_2_RL}, + {"abl", ABOVE_BELOW_LR}, + {"above_below_left_first", ABOVE_BELOW_LR}, + {"abr", ABOVE_BELOW_RL}, + {"above_below_right_first", ABOVE_BELOW_RL}, + {"ab2l", ABOVE_BELOW_2_LR}, + {"above_below_half_height_left_first", ABOVE_BELOW_2_LR}, + {"ab2r", ABOVE_BELOW_2_RL}, + {"above_below_half_height_right_first",ABOVE_BELOW_2_RL}, + {"irl", INTERLEAVE_ROWS_LR}, + {"interleave_rows_left_first", INTERLEAVE_ROWS_LR}, + {"irr", INTERLEAVE_ROWS_RL}, + {"interleave_rows_right_first", INTERLEAVE_ROWS_RL}, + { NULL, 0} +}; + +#define ST_OFF(f) M_ST_OFF(struct format_preset,f) +static const m_option_t vf_format_preset_fields_in[] = { + {"in", ST_OFF(scode), CONF_TYPE_INT, 0,0,0, NULL}, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; +static const m_option_t vf_format_preset_fields_out[] = { + {"out", ST_OFF(scode), CONF_TYPE_INT, 0,0,0, NULL}, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; + +static const m_struct_t vf_format_preset_in = { + "stereo_format_preset_in", + sizeof(struct format_preset), + NULL, + vf_format_preset_fields_in +}; +static const m_struct_t vf_format_preset_out = { + "stereo_format_preset_out", + sizeof(struct format_preset), + NULL, + vf_format_preset_fields_out +}; + +static const m_struct_t vf_opts; +static const m_obj_presets_t format_preset_in = { + (struct m_struct_st*)&vf_format_preset_in, + (struct m_struct_st*)&vf_opts, + (struct format_preset*)vf_format_presets_defs, + ST_OFF(name) +}; +static const m_obj_presets_t format_preset_out = { + (struct m_struct_st*)&vf_format_preset_out, + (struct m_struct_st*)&vf_opts, + (struct format_preset*)vf_format_presets_defs, + ST_OFF(name) +}; + +/// Now the options +#undef ST_OFF +#define ST_OFF(f) M_ST_OFF(struct vf_priv_s,f) +static const m_option_t vf_opts_fields[] = { + {"stereo_in", 0, CONF_TYPE_OBJ_PRESETS, 0, 0, 0, + (m_obj_presets_t*)&format_preset_in}, + {"stereo_out", 0, CONF_TYPE_OBJ_PRESETS, 0, 0, 0, + (m_obj_presets_t*)&format_preset_out}, + {"in", ST_OFF(in.fmt), CONF_TYPE_INT, 0,0,0, NULL}, + {"out", ST_OFF(out.fmt), CONF_TYPE_INT, 0,0,0, NULL}, + { NULL, NULL, 0, 0, 0, 0, NULL } +}; + +static const m_struct_t vf_opts = { + "stereo3d", + sizeof(struct vf_priv_s), + &vf_priv_default, + vf_opts_fields +}; + + +//==info struct==// +const vf_info_t vf_info_stereo3d = { + "stereoscopic 3d view", + "stereo3d", + "Gordon Schmidt", + "view stereoscopic videos", + vf_open, + &vf_opts +}; diff --git a/video/filter/vf_sub.c b/video/filter/vf_sub.c new file mode 100644 index 0000000000..2d5de3a7ba --- /dev/null +++ b/video/filter/vf_sub.c @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <assert.h> +#include <libavutil/common.h> + +#include "config.h" +#include "mp_msg.h" +#include "options.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" +#include "sub/sub.h" +#include "sub/dec_sub.h" + +#include "libvo/fastmemcpy.h" +#include "libvo/csputils.h" + +#include "m_option.h" +#include "m_struct.h" + +static const struct vf_priv_s { + int outh, outw; + + unsigned int outfmt; + struct mp_csp_details csp; + + struct osd_state *osd; + struct mp_osd_res dim; +} vf_priv_dflt = { + .csp = MP_CSP_DETAILS_DEFAULTS, +}; + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt) +{ + struct MPOpts *opts = vf->opts; + if (outfmt == IMGFMT_IF09) + return 0; + + vf->priv->outh = height + opts->ass_top_margin + opts->ass_bottom_margin; + vf->priv->outw = width; + + if (!opts->screen_size_x && !opts->screen_size_y) { + d_width = d_width * vf->priv->outw / width; + d_height = d_height * vf->priv->outh / height; + } + + double dar = (double)d_width / d_height; + double sar = (double)width / height; + + vf->priv->dim = (struct mp_osd_res) { + .w = vf->priv->outw, + .h = vf->priv->outh, + .mt = opts->ass_top_margin, + .mb = opts->ass_bottom_margin, + .display_par = sar / dar, + .video_par = dar / sar, + }; + + return vf_next_config(vf, vf->priv->outw, vf->priv->outh, d_width, + d_height, flags, outfmt); +} + +static void get_image(struct vf_instance *vf, mp_image_t *mpi) +{ + if (mpi->type == MP_IMGTYPE_IPB) + return; + if (mpi->flags & MP_IMGFLAG_PRESERVE) + return; + if (mpi->imgfmt != vf->priv->outfmt) + return; // colorspace differ + + // width never changes, always try full DR + mpi->priv = vf->dmpi = vf_get_image(vf->next, mpi->imgfmt, mpi->type, + mpi->flags | MP_IMGFLAG_READABLE, + FFMAX(mpi->width, vf->priv->outw), + FFMAX(mpi->height, vf->priv->outh)); + + if ((vf->dmpi->flags & MP_IMGFLAG_DRAW_CALLBACK) && + !(vf->dmpi->flags & MP_IMGFLAG_DIRECT)) { + mp_tmsg(MSGT_ASS, MSGL_INFO, "Full DR not possible, trying SLICES instead!\n"); + return; + } + + int tmargin = vf->opts->ass_top_margin; + // set up mpi as a cropped-down image of dmpi: + if (mpi->flags & MP_IMGFLAG_PLANAR) { + mpi->planes[0] = vf->dmpi->planes[0] + tmargin * vf->dmpi->stride[0]; + mpi->planes[1] = vf->dmpi->planes[1] + (tmargin >> mpi->chroma_y_shift) * vf->dmpi->stride[1]; + mpi->planes[2] = vf->dmpi->planes[2] + (tmargin >> mpi->chroma_y_shift) * vf->dmpi->stride[2]; + mpi->stride[1] = vf->dmpi->stride[1]; + mpi->stride[2] = vf->dmpi->stride[2]; + } else { + mpi->planes[0] = vf->dmpi->planes[0] + tmargin * vf->dmpi->stride[0]; + } + mpi->stride[0] = vf->dmpi->stride[0]; + mpi->width = vf->dmpi->width; + mpi->flags |= MP_IMGFLAG_DIRECT; + mpi->flags &= ~MP_IMGFLAG_DRAW_CALLBACK; +// vf->dmpi->flags&=~MP_IMGFLAG_DRAW_CALLBACK; +} + +static void blank(mp_image_t *mpi, int y1, int y2) +{ + int color[3] = {16, 128, 128}; // black (YUV) + int y; + unsigned char *dst; + int chroma_rows = (y2 - y1) >> mpi->chroma_y_shift; + + dst = mpi->planes[0] + y1 * mpi->stride[0]; + for (y = 0; y < y2 - y1; ++y) { + memset(dst, color[0], mpi->w); + dst += mpi->stride[0]; + } + dst = mpi->planes[1] + (y1 >> mpi->chroma_y_shift) * mpi->stride[1]; + for (y = 0; y < chroma_rows; ++y) { + memset(dst, color[1], mpi->chroma_width); + dst += mpi->stride[1]; + } + dst = mpi->planes[2] + (y1 >> mpi->chroma_y_shift) * mpi->stride[2]; + for (y = 0; y < chroma_rows; ++y) { + memset(dst, color[2], mpi->chroma_width); + dst += mpi->stride[2]; + } +} + +static int prepare_image(struct vf_instance *vf, mp_image_t *mpi) +{ + struct MPOpts *opts = vf->opts; + int tmargin = opts->ass_top_margin; + if (mpi->flags & MP_IMGFLAG_DIRECT + || mpi->flags & MP_IMGFLAG_DRAW_CALLBACK) { + vf->dmpi = mpi->priv; + if (!vf->dmpi) { + mp_tmsg(MSGT_ASS, MSGL_WARN, "Why do we get NULL??\n"); + return 0; + } + mpi->priv = NULL; + // we've used DR, so we're ready... + if (tmargin) + blank(vf->dmpi, 0, tmargin); + if (opts->ass_bottom_margin) + blank(vf->dmpi, vf->priv->outh - opts->ass_bottom_margin, + vf->priv->outh); + if (!(mpi->flags & MP_IMGFLAG_PLANAR)) + vf->dmpi->planes[1] = mpi->planes[1]; // passthrough rgb8 palette + return 0; + } + + // hope we'll get DR buffer: + vf->dmpi = vf_get_image(vf->next, vf->priv->outfmt, MP_IMGTYPE_TEMP, + MP_IMGFLAG_ACCEPT_STRIDE | MP_IMGFLAG_READABLE, + vf->priv->outw, vf->priv->outh); + + // copy mpi->dmpi... + if (mpi->flags & MP_IMGFLAG_PLANAR) { + memcpy_pic(vf->dmpi->planes[0] + tmargin * vf->dmpi->stride[0], + mpi->planes[0], + mpi->w, + mpi->h, + vf->dmpi->stride[0], + mpi->stride[0]); + memcpy_pic(vf->dmpi->planes[1] + (tmargin >> mpi->chroma_y_shift) * vf->dmpi->stride[1], + mpi->planes[1], + mpi->w >> mpi->chroma_x_shift, + mpi->h >> mpi->chroma_y_shift, + vf->dmpi->stride[1], + mpi->stride[1]); + memcpy_pic(vf->dmpi->planes[2] + (tmargin >> mpi->chroma_y_shift) * vf->dmpi->stride[2], + mpi->planes[2], + mpi->w >> mpi->chroma_x_shift, + mpi->h >> mpi->chroma_y_shift, + vf->dmpi->stride[2], + mpi->stride[2]); + } else { + memcpy_pic(vf->dmpi->planes[0] + tmargin * vf->dmpi->stride[0], + mpi->planes[0], + mpi->w * (vf->dmpi->bpp / 8), + mpi->h, + vf->dmpi->stride[0], + mpi->stride[0]); + vf->dmpi->planes[1] = mpi->planes[1]; // passthrough rgb8 palette + } + if (tmargin) + blank(vf->dmpi, 0, tmargin); + if (opts->ass_bottom_margin) + blank(vf->dmpi, vf->priv->outh - opts->ass_bottom_margin, + vf->priv->outh); + return 0; +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts) +{ + struct vf_priv_s *priv = vf->priv; + struct osd_state *osd = priv->osd; + + prepare_image(vf, mpi); + mp_image_set_colorspace_details(mpi, &priv->csp); + + if (pts != MP_NOPTS_VALUE) + osd_draw_on_image(osd, priv->dim, pts, OSD_DRAW_SUB_FILTER, vf->dmpi); + + return vf_next_put_image(vf, vf->dmpi, pts); +} + +static int query_format(struct vf_instance *vf, unsigned int fmt) +{ + switch (fmt) { + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + return vf_next_query_format(vf, vf->priv->outfmt); + } + return 0; +} + +static int control(vf_instance_t *vf, int request, void *data) +{ + switch (request) { + case VFCTRL_SET_OSD_OBJ: + vf->priv->osd = data; + break; + case VFCTRL_INIT_OSD: + return CONTROL_TRUE; + case VFCTRL_SET_YUV_COLORSPACE: { + struct mp_csp_details colorspace = *(struct mp_csp_details *)data; + vf->priv->csp = colorspace; + break; + } + } + return vf_next_control(vf, request, data); +} + +static void uninit(struct vf_instance *vf) +{ + free(vf->priv); +} + +static const unsigned int fmt_list[] = { + IMGFMT_YV12, + IMGFMT_I420, + IMGFMT_IYUV, + 0 +}; + +static int vf_open(vf_instance_t *vf, char *args) +{ + vf->priv->outfmt = vf_match_csp(&vf->next, fmt_list, IMGFMT_YV12); + if (!vf->priv->outfmt) { + uninit(vf); + return 0; + } + + vf->config = config; + vf->query_format = query_format; + vf->uninit = uninit; + vf->control = control; + vf->get_image = get_image; + vf->put_image = put_image; + vf->default_caps = VFCAP_OSD; + return 1; +} + +#define ST_OFF(f) M_ST_OFF(struct vf_priv_s, f) +static const m_option_t vf_opts_fields[] = { + {NULL, NULL, 0, 0, 0, 0, NULL} +}; + +static const m_struct_t vf_opts = { + "sub", + sizeof(struct vf_priv_s), + &vf_priv_dflt, + vf_opts_fields +}; + +const vf_info_t vf_info_sub = { + "Render subtitles", + "sub", + "Evgeniy Stepanov", + "", + vf_open, + &vf_opts +}; diff --git a/video/filter/vf_swapuv.c b/video/filter/vf_swapuv.c new file mode 100644 index 0000000000..6edb256759 --- /dev/null +++ b/video/filter/vf_swapuv.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2002 Michael Niedermayer <michaelni@gmx.at> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <assert.h> + +#include "mp_msg.h" +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" + + +//===========================================================================// + +static void get_image(struct vf_instance *vf, mp_image_t *mpi){ + mp_image_t *dmpi= vf_get_image(vf->next, mpi->imgfmt, + mpi->type, mpi->flags, mpi->w, mpi->h); + + mpi->planes[0]=dmpi->planes[0]; + mpi->planes[1]=dmpi->planes[2]; + mpi->planes[2]=dmpi->planes[1]; + mpi->stride[0]=dmpi->stride[0]; + mpi->stride[1]=dmpi->stride[2]; + mpi->stride[2]=dmpi->stride[1]; + mpi->width=dmpi->width; + + mpi->flags|=MP_IMGFLAG_DIRECT; + mpi->priv=(void*)dmpi; +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts){ + mp_image_t *dmpi; + + if(mpi->flags&MP_IMGFLAG_DIRECT){ + dmpi=(mp_image_t*)mpi->priv; + } else { + dmpi=vf_get_image(vf->next, mpi->imgfmt, MP_IMGTYPE_EXPORT, 0, mpi->w, mpi->h); + assert(mpi->flags&MP_IMGFLAG_PLANAR); + dmpi->planes[0]=mpi->planes[0]; + dmpi->planes[1]=mpi->planes[2]; + dmpi->planes[2]=mpi->planes[1]; + dmpi->stride[0]=mpi->stride[0]; + dmpi->stride[1]=mpi->stride[2]; + dmpi->stride[2]=mpi->stride[1]; + dmpi->width=mpi->width; + } + + vf_clone_mpi_attributes(dmpi, mpi); + + return vf_next_put_image(vf,dmpi, pts); +} + +//===========================================================================// + +static int query_format(struct vf_instance *vf, unsigned int fmt){ + switch(fmt) + { + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + case IMGFMT_YVU9: + case IMGFMT_444P: + case IMGFMT_422P: + case IMGFMT_411P: + return vf_next_query_format(vf, fmt); + } + return 0; +} + +static int vf_open(vf_instance_t *vf, char *args){ + vf->put_image=put_image; + vf->get_image=get_image; + vf->query_format=query_format; + return 1; +} + +const vf_info_t vf_info_swapuv = { + "UV swapper", + "swapuv", + "Michael Niedermayer", + "", + vf_open, + NULL +}; + +//===========================================================================// diff --git a/video/filter/vf_unsharp.c b/video/filter/vf_unsharp.c new file mode 100644 index 0000000000..69368d6bf5 --- /dev/null +++ b/video/filter/vf_unsharp.c @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2002 Remi Guyomarch <rguyom@pobox.com> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <math.h> + +#include "config.h" +#include "mp_msg.h" +#include "cpudetect.h" + +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" +#include "libvo/fastmemcpy.h" +#include "libavutil/common.h" + +//===========================================================================// + +#define MIN_MATRIX_SIZE 3 +#define MAX_MATRIX_SIZE 63 + +typedef struct FilterParam { + int msizeX, msizeY; + double amount; + uint32_t *SC[MAX_MATRIX_SIZE-1]; +} FilterParam; + +struct vf_priv_s { + FilterParam lumaParam; + FilterParam chromaParam; + unsigned int outfmt; +}; + + +//===========================================================================// + +/* This code is based on : + +An Efficient algorithm for Gaussian blur using finite-state machines +Frederick M. Waltz and John W. V. Miller + +SPIE Conf. on Machine Vision Systems for Inspection and Metrology VII +Originally published Boston, Nov 98 + +*/ + +static void unsharp( uint8_t *dst, uint8_t *src, int dstStride, int srcStride, int width, int height, FilterParam *fp ) { + + uint32_t **SC = fp->SC; + uint32_t SR[MAX_MATRIX_SIZE-1], Tmp1, Tmp2; + uint8_t* src2 = src; // avoid gcc warning + + int32_t res; + int x, y, z; + int amount = fp->amount * 65536.0; + int stepsX = fp->msizeX/2; + int stepsY = fp->msizeY/2; + int scalebits = (stepsX+stepsY)*2; + int32_t halfscale = 1 << ((stepsX+stepsY)*2-1); + + if( !fp->amount ) { + if( src == dst ) + return; + if( dstStride == srcStride ) + memcpy( dst, src, srcStride*height ); + else + for( y=0; y<height; y++, dst+=dstStride, src+=srcStride ) + memcpy( dst, src, width ); + return; + } + + for( y=0; y<2*stepsY; y++ ) + memset( SC[y], 0, sizeof(SC[y][0]) * (width+2*stepsX) ); + + for( y=-stepsY; y<height+stepsY; y++ ) { + if( y < height ) src2 = src; + memset( SR, 0, sizeof(SR[0]) * (2*stepsX-1) ); + for( x=-stepsX; x<width+stepsX; x++ ) { + Tmp1 = x<=0 ? src2[0] : x>=width ? src2[width-1] : src2[x]; + for( z=0; z<stepsX*2; z+=2 ) { + Tmp2 = SR[z+0] + Tmp1; SR[z+0] = Tmp1; + Tmp1 = SR[z+1] + Tmp2; SR[z+1] = Tmp2; + } + for( z=0; z<stepsY*2; z+=2 ) { + Tmp2 = SC[z+0][x+stepsX] + Tmp1; SC[z+0][x+stepsX] = Tmp1; + Tmp1 = SC[z+1][x+stepsX] + Tmp2; SC[z+1][x+stepsX] = Tmp2; + } + if( x>=stepsX && y>=stepsY ) { + uint8_t* srx = src - stepsY*srcStride + x - stepsX; + uint8_t* dsx = dst - stepsY*dstStride + x - stepsX; + + res = (int32_t)*srx + ( ( ( (int32_t)*srx - (int32_t)((Tmp1+halfscale) >> scalebits) ) * amount ) >> 16 ); + *dsx = res>255 ? 255 : res<0 ? 0 : (uint8_t)res; + } + } + if( y >= 0 ) { + dst += dstStride; + src += srcStride; + } + } +} + +//===========================================================================// + +static int config( struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt ) { + + int z, stepsX, stepsY; + FilterParam *fp; + char *effect; + + // allocate buffers + + fp = &vf->priv->lumaParam; + effect = fp->amount == 0 ? "don't touch" : fp->amount < 0 ? "blur" : "sharpen"; + mp_msg( MSGT_VFILTER, MSGL_INFO, "unsharp: %dx%d:%0.2f (%s luma) \n", fp->msizeX, fp->msizeY, fp->amount, effect ); + memset( fp->SC, 0, sizeof( fp->SC ) ); + stepsX = fp->msizeX/2; + stepsY = fp->msizeY/2; + for( z=0; z<2*stepsY; z++ ) + fp->SC[z] = av_malloc(sizeof(*(fp->SC[z])) * (width+2*stepsX)); + + fp = &vf->priv->chromaParam; + effect = fp->amount == 0 ? "don't touch" : fp->amount < 0 ? "blur" : "sharpen"; + mp_msg( MSGT_VFILTER, MSGL_INFO, "unsharp: %dx%d:%0.2f (%s chroma)\n", fp->msizeX, fp->msizeY, fp->amount, effect ); + memset( fp->SC, 0, sizeof( fp->SC ) ); + stepsX = fp->msizeX/2; + stepsY = fp->msizeY/2; + for( z=0; z<2*stepsY; z++ ) + fp->SC[z] = av_malloc(sizeof(*(fp->SC[z])) * (width+2*stepsX)); + + return vf_next_config( vf, width, height, d_width, d_height, flags, outfmt ); +} + +//===========================================================================// + +static void get_image( struct vf_instance *vf, mp_image_t *mpi ) { + if( mpi->flags & MP_IMGFLAG_PRESERVE ) + return; // don't change + if( mpi->imgfmt!=vf->priv->outfmt ) + return; // colorspace differ + + mpi->priv = + vf->dmpi = vf_get_image( vf->next, mpi->imgfmt, mpi->type, mpi->flags, mpi->width, mpi->height ); + mpi->planes[0] = vf->dmpi->planes[0]; + mpi->stride[0] = vf->dmpi->stride[0]; + mpi->width = vf->dmpi->width; + if( mpi->flags & MP_IMGFLAG_PLANAR ) { + mpi->planes[1] = vf->dmpi->planes[1]; + mpi->planes[2] = vf->dmpi->planes[2]; + mpi->stride[1] = vf->dmpi->stride[1]; + mpi->stride[2] = vf->dmpi->stride[2]; + } + mpi->flags |= MP_IMGFLAG_DIRECT; +} + +static int put_image( struct vf_instance *vf, mp_image_t *mpi, double pts) { + mp_image_t *dmpi = mpi->priv; + mpi->priv = NULL; + + if( !(mpi->flags & MP_IMGFLAG_DIRECT) ) + // no DR, so get a new image! hope we'll get DR buffer: + dmpi = vf->dmpi = vf_get_image( vf->next,vf->priv->outfmt, MP_IMGTYPE_TEMP, MP_IMGFLAG_ACCEPT_STRIDE, mpi->width, mpi->height); + + unsharp( dmpi->planes[0], mpi->planes[0], dmpi->stride[0], mpi->stride[0], mpi->w, mpi->h, &vf->priv->lumaParam ); + unsharp( dmpi->planes[1], mpi->planes[1], dmpi->stride[1], mpi->stride[1], mpi->w/2, mpi->h/2, &vf->priv->chromaParam ); + unsharp( dmpi->planes[2], mpi->planes[2], dmpi->stride[2], mpi->stride[2], mpi->w/2, mpi->h/2, &vf->priv->chromaParam ); + + vf_clone_mpi_attributes(dmpi, mpi); + +#if HAVE_MMX + if(gCpuCaps.hasMMX) + __asm__ volatile ("emms\n\t"); +#endif +#if HAVE_MMX2 + if(gCpuCaps.hasMMX2) + __asm__ volatile ("sfence\n\t"); +#endif + + return vf_next_put_image( vf, dmpi, pts); +} + +static void uninit( struct vf_instance *vf ) { + unsigned int z; + FilterParam *fp; + + if( !vf->priv ) return; + + fp = &vf->priv->lumaParam; + for( z=0; z<sizeof(fp->SC)/sizeof(fp->SC[0]); z++ ) { + av_free( fp->SC[z] ); + fp->SC[z] = NULL; + } + fp = &vf->priv->chromaParam; + for( z=0; z<sizeof(fp->SC)/sizeof(fp->SC[0]); z++ ) { + av_free( fp->SC[z] ); + fp->SC[z] = NULL; + } + + free( vf->priv ); + vf->priv = NULL; +} + +//===========================================================================// + +static int query_format( struct vf_instance *vf, unsigned int fmt ) { + switch(fmt) { + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + return vf_next_query_format( vf, vf->priv->outfmt ); + } + return 0; +} + +//===========================================================================// + +static void parse( FilterParam *fp, char* args ) { + + // l7x5:0.8:c3x3:-0.2 + + char *z; + char *pos = args; + char *max = args + strlen(args); + + // parse matrix sizes + fp->msizeX = ( pos && pos+1<max ) ? atoi( pos+1 ) : 0; + z = strchr( pos+1, 'x' ); + fp->msizeY = ( z && z+1<max ) ? atoi( pos=z+1 ) : fp->msizeX; + + // min/max & odd + fp->msizeX = 1 | av_clip(fp->msizeX, MIN_MATRIX_SIZE, MAX_MATRIX_SIZE); + fp->msizeY = 1 | av_clip(fp->msizeY, MIN_MATRIX_SIZE, MAX_MATRIX_SIZE); + + // parse amount + pos = strchr( pos+1, ':' ); + fp->amount = ( pos && pos+1<max ) ? atof( pos+1 ) : 0; +} + +//===========================================================================// + +static const unsigned int fmt_list[] = { + IMGFMT_YV12, + IMGFMT_I420, + IMGFMT_IYUV, + 0 +}; + +static int vf_open( vf_instance_t *vf, char *args ) { + vf->config = config; + vf->put_image = put_image; + vf->get_image = get_image; + vf->query_format = query_format; + vf->uninit = uninit; + vf->priv = malloc( sizeof(struct vf_priv_s) ); + memset( vf->priv, 0, sizeof(struct vf_priv_s) ); + + if( args ) { + char *args2 = strchr( args, 'l' ); + if( args2 ) + parse( &vf->priv->lumaParam, args2 ); + else { + vf->priv->lumaParam.amount = + vf->priv->lumaParam.msizeX = + vf->priv->lumaParam.msizeY = 0; + } + + args2 = strchr( args, 'c' ); + if( args2 ) + parse( &vf->priv->chromaParam, args2 ); + else { + vf->priv->chromaParam.amount = + vf->priv->chromaParam.msizeX = + vf->priv->chromaParam.msizeY = 0; + } + + if( !vf->priv->lumaParam.msizeX && !vf->priv->chromaParam.msizeX ) + return 0; // nothing to do + } + + // check csp: + vf->priv->outfmt = vf_match_csp( &vf->next, fmt_list, IMGFMT_YV12 ); + if( !vf->priv->outfmt ) { + uninit( vf ); + return 0; // no csp match :( + } + + return 1; +} + +const vf_info_t vf_info_unsharp = { + "unsharp mask & gaussian blur", + "unsharp", + "Remi Guyomarch", + "", + vf_open, + NULL +}; + +//===========================================================================// diff --git a/video/filter/vf_vo.c b/video/filter/vf_vo.c new file mode 100644 index 0000000000..d11724f881 --- /dev/null +++ b/video/filter/vf_vo.c @@ -0,0 +1,202 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> + +#include "config.h" +#include "mp_msg.h" +#include "options.h" + +#include "mp_image.h" +#include "vf.h" + +#include "libvo/video_out.h" + +#include "sub/sub.h" + +struct vf_priv_s { + struct vo *vo; +}; +#define video_out (vf->priv->vo) + +static void draw_slice(struct vf_instance *vf, unsigned char **src, + int *stride, int w, int h, int x, int y); + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt) +{ + + if ((width <= 0) || (height <= 0) || (d_width <= 0) || (d_height <= 0)) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, "VO: invalid dimensions!\n"); + return 0; + } + + const vo_info_t *info = video_out->driver->info; + mp_msg(MSGT_CPLAYER, MSGL_INFO, "VO: [%s] %dx%d => %dx%d %s %s%s%s%s\n", + info->short_name, + width, height, + d_width, d_height, + vo_format_name(outfmt), + (flags & VOFLAG_FULLSCREEN) ? " [fs]" : "", + (flags & VOFLAG_MODESWITCHING) ? " [vm]" : "", + (flags & VOFLAG_SWSCALE) ? " [zoom]" : "", + (flags & VOFLAG_FLIPPING) ? " [flip]" : ""); + mp_msg(MSGT_CPLAYER, MSGL_V, "VO: Description: %s\n", info->name); + mp_msg(MSGT_CPLAYER, MSGL_V, "VO: Author: %s\n", info->author); + if (info->comment && strlen(info->comment) > 0) + mp_msg(MSGT_CPLAYER, MSGL_V, "VO: Comment: %s\n", info->comment); + + if (vo_config(video_out, width, height, d_width, d_height, flags, outfmt)) + return 0; + + // save vo's stride capability for the wanted colorspace: + vf->default_caps = video_out->default_caps; + vf->draw_slice = (vf->default_caps & VOCAP_NOSLICES) ? NULL : draw_slice; + + return 1; +} + +static int control(struct vf_instance *vf, int request, void *data) +{ + switch (request) { + case VFCTRL_GET_DEINTERLACE: + if (!video_out) + return CONTROL_FALSE; // vo not configured? + return vo_control(video_out, VOCTRL_GET_DEINTERLACE, data) == VO_TRUE; + case VFCTRL_SET_DEINTERLACE: + if (!video_out) + return CONTROL_FALSE; // vo not configured? + return vo_control(video_out, VOCTRL_SET_DEINTERLACE, data) == VO_TRUE; + case VFCTRL_GET_YUV_COLORSPACE: + return vo_control(video_out, VOCTRL_GET_YUV_COLORSPACE, data) == true; + case VFCTRL_SET_YUV_COLORSPACE: + return vo_control(video_out, VOCTRL_SET_YUV_COLORSPACE, data) == true; + case VFCTRL_SET_EQUALIZER: { + vf_equalizer_t *eq = data; + if (!video_out->config_ok) + return CONTROL_FALSE; // vo not configured? + struct voctrl_set_equalizer_args param = { + eq->item, eq->value + }; + return vo_control(video_out, VOCTRL_SET_EQUALIZER, ¶m) == VO_TRUE; + } + case VFCTRL_GET_EQUALIZER: { + vf_equalizer_t *eq = data; + if (!video_out->config_ok) + return CONTROL_FALSE; // vo not configured? + struct voctrl_get_equalizer_args param = { + eq->item, &eq->value + }; + return vo_control(video_out, VOCTRL_GET_EQUALIZER, ¶m) == VO_TRUE; + } + } + return CONTROL_UNKNOWN; +} + +static int query_format(struct vf_instance *vf, unsigned int fmt) +{ + int flags = vo_control(video_out, VOCTRL_QUERY_FORMAT, &fmt); + // draw_slice() accepts stride, draw_frame() doesn't: + if (flags) + if (fmt == IMGFMT_YV12 || fmt == IMGFMT_I420 || fmt == IMGFMT_IYUV) + flags |= VFCAP_ACCEPT_STRIDE; + return flags; +} + +static void get_image(struct vf_instance *vf, + mp_image_t *mpi) +{ + if (!video_out->config_ok) + return; + // GET_IMAGE is required for hardware-accelerated formats + if (IMGFMT_IS_HWACCEL(mpi->imgfmt)) + vo_control(video_out, VOCTRL_GET_IMAGE, mpi); +} + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts) +{ + if (!video_out->config_ok) + return 0; + // first check, maybe the vo/vf plugin implements draw_image using mpi: + if (vo_draw_image(video_out, mpi, pts) >= 0) + return 1; + // nope, fallback to old draw_frame/draw_slice: + if (!(mpi->flags & (MP_IMGFLAG_DIRECT | MP_IMGFLAG_DRAW_CALLBACK))) { + // blit frame: + if (vf->default_caps & VFCAP_ACCEPT_STRIDE) + vo_draw_slice(video_out, mpi->planes, mpi->stride, mpi->w, mpi->h, + 0, 0); + // else: out of luck + } + return 1; +} + +static void start_slice(struct vf_instance *vf, mp_image_t *mpi) +{ + if (!video_out->config_ok) + return; + vo_control(video_out, VOCTRL_START_SLICE, mpi); +} + +static void draw_slice(struct vf_instance *vf, unsigned char **src, + int *stride, int w, int h, int x, int y) +{ + if (!video_out->config_ok) + return; + vo_draw_slice(video_out, src, stride, w, h, x, y); +} + +static void uninit(struct vf_instance *vf) +{ + if (vf->priv) { + /* Allow VO (which may live on to work with another instance of vf_vo) + * to get rid of numbered-mpi references that will now be invalid. */ + vo_seek_reset(video_out); + free(vf->priv); + } +} + +static int vf_open(vf_instance_t *vf, char *args) +{ + vf->config = config; + vf->control = control; + vf->query_format = query_format; + vf->get_image = get_image; + vf->put_image = put_image; + vf->draw_slice = draw_slice; + vf->start_slice = start_slice; + vf->uninit = uninit; + vf->priv = calloc(1, sizeof(struct vf_priv_s)); + vf->priv->vo = (struct vo *)args; + if (!video_out) + return 0; + return 1; +} + +const vf_info_t vf_info_vo = { + "libvo wrapper", + "vo", + "A'rpi", + "for internal use", + vf_open, + NULL +}; diff --git a/video/filter/vf_yadif.c b/video/filter/vf_yadif.c new file mode 100644 index 0000000000..bb6595cdcd --- /dev/null +++ b/video/filter/vf_yadif.c @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2006 Michael Niedermayer <michaelni@gmx.at> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <math.h> + +#include "config.h" +#include "cpudetect.h" +#include "options.h" + +#include "mp_msg.h" +#include "img_format.h" +#include "mp_image.h" +#include "vf.h" +#include "libvo/fastmemcpy.h" +#include "libavutil/common.h" + +//===========================================================================// + +struct vf_priv_s { + int mode; + int parity; + int buffered_i; + int buffered_tff; + double buffered_pts; + double buffered_pts_delta; + mp_image_t *buffered_mpi; + int stride[3]; + uint8_t *ref[4][3]; + int do_deinterlace; +}; + +static void (*filter_line)(struct vf_priv_s *p, uint8_t *dst, uint8_t *prev, uint8_t *cur, uint8_t *next, int w, int refs, int parity); + +static void store_ref(struct vf_priv_s *p, uint8_t *src[3], int src_stride[3], int width, int height){ + int i; + + memcpy (p->ref[3], p->ref[0], sizeof(uint8_t *)*3); + memmove(p->ref[0], p->ref[1], sizeof(uint8_t *)*3*3); + + for(i=0; i<3; i++){ + int is_chroma= !!i; + int pn_width = width >>is_chroma; + int pn_height = height>>is_chroma; + + + memcpy_pic(p->ref[2][i], src[i], pn_width, pn_height, p->stride[i], src_stride[i]); + + memcpy(p->ref[2][i] + pn_height * p->stride[i], + src[i] + (pn_height-1)*src_stride[i], pn_width); + memcpy(p->ref[2][i] + (pn_height+1)* p->stride[i], + src[i] + (pn_height-1)*src_stride[i], pn_width); + + memcpy(p->ref[2][i] - p->stride[i], src[i], pn_width); + memcpy(p->ref[2][i] - 2*p->stride[i], src[i], pn_width); + } +} + +#if HAVE_MMX + +#define LOAD4(mem,dst) \ + "movd "mem", "#dst" \n\t"\ + "punpcklbw %%mm7, "#dst" \n\t" + +#define PABS(tmp,dst) \ + "pxor "#tmp", "#tmp" \n\t"\ + "psubw "#dst", "#tmp" \n\t"\ + "pmaxsw "#tmp", "#dst" \n\t" + +#define CHECK(pj,mj) \ + "movq "#pj"(%[cur],%[mrefs]), %%mm2 \n\t" /* cur[x-refs-1+j] */\ + "movq "#mj"(%[cur],%[prefs]), %%mm3 \n\t" /* cur[x+refs-1-j] */\ + "movq %%mm2, %%mm4 \n\t"\ + "movq %%mm2, %%mm5 \n\t"\ + "pxor %%mm3, %%mm4 \n\t"\ + "pavgb %%mm3, %%mm5 \n\t"\ + "pand %[pb1], %%mm4 \n\t"\ + "psubusb %%mm4, %%mm5 \n\t"\ + "psrlq $8, %%mm5 \n\t"\ + "punpcklbw %%mm7, %%mm5 \n\t" /* (cur[x-refs+j] + cur[x+refs-j])>>1 */\ + "movq %%mm2, %%mm4 \n\t"\ + "psubusb %%mm3, %%mm2 \n\t"\ + "psubusb %%mm4, %%mm3 \n\t"\ + "pmaxub %%mm3, %%mm2 \n\t"\ + "movq %%mm2, %%mm3 \n\t"\ + "movq %%mm2, %%mm4 \n\t" /* ABS(cur[x-refs-1+j] - cur[x+refs-1-j]) */\ + "psrlq $8, %%mm3 \n\t" /* ABS(cur[x-refs +j] - cur[x+refs -j]) */\ + "psrlq $16, %%mm4 \n\t" /* ABS(cur[x-refs+1+j] - cur[x+refs+1-j]) */\ + "punpcklbw %%mm7, %%mm2 \n\t"\ + "punpcklbw %%mm7, %%mm3 \n\t"\ + "punpcklbw %%mm7, %%mm4 \n\t"\ + "paddw %%mm3, %%mm2 \n\t"\ + "paddw %%mm4, %%mm2 \n\t" /* score */ + +#define CHECK1 \ + "movq %%mm0, %%mm3 \n\t"\ + "pcmpgtw %%mm2, %%mm3 \n\t" /* if(score < spatial_score) */\ + "pminsw %%mm2, %%mm0 \n\t" /* spatial_score= score; */\ + "movq %%mm3, %%mm6 \n\t"\ + "pand %%mm3, %%mm5 \n\t"\ + "pandn %%mm1, %%mm3 \n\t"\ + "por %%mm5, %%mm3 \n\t"\ + "movq %%mm3, %%mm1 \n\t" /* spatial_pred= (cur[x-refs+j] + cur[x+refs-j])>>1; */ + +#define CHECK2 /* pretend not to have checked dir=2 if dir=1 was bad.\ + hurts both quality and speed, but matches the C version. */\ + "paddw %[pw1], %%mm6 \n\t"\ + "psllw $14, %%mm6 \n\t"\ + "paddsw %%mm6, %%mm2 \n\t"\ + "movq %%mm0, %%mm3 \n\t"\ + "pcmpgtw %%mm2, %%mm3 \n\t"\ + "pminsw %%mm2, %%mm0 \n\t"\ + "pand %%mm3, %%mm5 \n\t"\ + "pandn %%mm1, %%mm3 \n\t"\ + "por %%mm5, %%mm3 \n\t"\ + "movq %%mm3, %%mm1 \n\t" + +static void filter_line_mmx2(struct vf_priv_s *p, uint8_t *dst, uint8_t *prev, uint8_t *cur, uint8_t *next, int w, int refs, int parity){ + static const uint64_t pw_1 = 0x0001000100010001ULL; + static const uint64_t pb_1 = 0x0101010101010101ULL; + const int mode = p->mode; + uint64_t tmp0, tmp1, tmp2, tmp3; + int x; + +#define FILTER\ + for(x=0; x<w; x+=4){\ + __asm__ volatile(\ + "pxor %%mm7, %%mm7 \n\t"\ + LOAD4("(%[cur],%[mrefs])", %%mm0) /* c = cur[x-refs] */\ + LOAD4("(%[cur],%[prefs])", %%mm1) /* e = cur[x+refs] */\ + LOAD4("(%["prev2"])", %%mm2) /* prev2[x] */\ + LOAD4("(%["next2"])", %%mm3) /* next2[x] */\ + "movq %%mm3, %%mm4 \n\t"\ + "paddw %%mm2, %%mm3 \n\t"\ + "psraw $1, %%mm3 \n\t" /* d = (prev2[x] + next2[x])>>1 */\ + "movq %%mm0, %[tmp0] \n\t" /* c */\ + "movq %%mm3, %[tmp1] \n\t" /* d */\ + "movq %%mm1, %[tmp2] \n\t" /* e */\ + "psubw %%mm4, %%mm2 \n\t"\ + PABS( %%mm4, %%mm2) /* temporal_diff0 */\ + LOAD4("(%[prev],%[mrefs])", %%mm3) /* prev[x-refs] */\ + LOAD4("(%[prev],%[prefs])", %%mm4) /* prev[x+refs] */\ + "psubw %%mm0, %%mm3 \n\t"\ + "psubw %%mm1, %%mm4 \n\t"\ + PABS( %%mm5, %%mm3)\ + PABS( %%mm5, %%mm4)\ + "paddw %%mm4, %%mm3 \n\t" /* temporal_diff1 */\ + "psrlw $1, %%mm2 \n\t"\ + "psrlw $1, %%mm3 \n\t"\ + "pmaxsw %%mm3, %%mm2 \n\t"\ + LOAD4("(%[next],%[mrefs])", %%mm3) /* next[x-refs] */\ + LOAD4("(%[next],%[prefs])", %%mm4) /* next[x+refs] */\ + "psubw %%mm0, %%mm3 \n\t"\ + "psubw %%mm1, %%mm4 \n\t"\ + PABS( %%mm5, %%mm3)\ + PABS( %%mm5, %%mm4)\ + "paddw %%mm4, %%mm3 \n\t" /* temporal_diff2 */\ + "psrlw $1, %%mm3 \n\t"\ + "pmaxsw %%mm3, %%mm2 \n\t"\ + "movq %%mm2, %[tmp3] \n\t" /* diff */\ +\ + "paddw %%mm0, %%mm1 \n\t"\ + "paddw %%mm0, %%mm0 \n\t"\ + "psubw %%mm1, %%mm0 \n\t"\ + "psrlw $1, %%mm1 \n\t" /* spatial_pred */\ + PABS( %%mm2, %%mm0) /* ABS(c-e) */\ +\ + "movq -1(%[cur],%[mrefs]), %%mm2 \n\t" /* cur[x-refs-1] */\ + "movq -1(%[cur],%[prefs]), %%mm3 \n\t" /* cur[x+refs-1] */\ + "movq %%mm2, %%mm4 \n\t"\ + "psubusb %%mm3, %%mm2 \n\t"\ + "psubusb %%mm4, %%mm3 \n\t"\ + "pmaxub %%mm3, %%mm2 \n\t"\ + "pshufw $9,%%mm2, %%mm3 \n\t"\ + "punpcklbw %%mm7, %%mm2 \n\t" /* ABS(cur[x-refs-1] - cur[x+refs-1]) */\ + "punpcklbw %%mm7, %%mm3 \n\t" /* ABS(cur[x-refs+1] - cur[x+refs+1]) */\ + "paddw %%mm2, %%mm0 \n\t"\ + "paddw %%mm3, %%mm0 \n\t"\ + "psubw %[pw1], %%mm0 \n\t" /* spatial_score */\ +\ + CHECK(-2,0)\ + CHECK1\ + CHECK(-3,1)\ + CHECK2\ + CHECK(0,-2)\ + CHECK1\ + CHECK(1,-3)\ + CHECK2\ +\ + /* if(p->mode<2) ... */\ + "movq %[tmp3], %%mm6 \n\t" /* diff */\ + "cmpl $2, %[mode] \n\t"\ + "jge 1f \n\t"\ + LOAD4("(%["prev2"],%[mrefs],2)", %%mm2) /* prev2[x-2*refs] */\ + LOAD4("(%["next2"],%[mrefs],2)", %%mm4) /* next2[x-2*refs] */\ + LOAD4("(%["prev2"],%[prefs],2)", %%mm3) /* prev2[x+2*refs] */\ + LOAD4("(%["next2"],%[prefs],2)", %%mm5) /* next2[x+2*refs] */\ + "paddw %%mm4, %%mm2 \n\t"\ + "paddw %%mm5, %%mm3 \n\t"\ + "psrlw $1, %%mm2 \n\t" /* b */\ + "psrlw $1, %%mm3 \n\t" /* f */\ + "movq %[tmp0], %%mm4 \n\t" /* c */\ + "movq %[tmp1], %%mm5 \n\t" /* d */\ + "movq %[tmp2], %%mm7 \n\t" /* e */\ + "psubw %%mm4, %%mm2 \n\t" /* b-c */\ + "psubw %%mm7, %%mm3 \n\t" /* f-e */\ + "movq %%mm5, %%mm0 \n\t"\ + "psubw %%mm4, %%mm5 \n\t" /* d-c */\ + "psubw %%mm7, %%mm0 \n\t" /* d-e */\ + "movq %%mm2, %%mm4 \n\t"\ + "pminsw %%mm3, %%mm2 \n\t"\ + "pmaxsw %%mm4, %%mm3 \n\t"\ + "pmaxsw %%mm5, %%mm2 \n\t"\ + "pminsw %%mm5, %%mm3 \n\t"\ + "pmaxsw %%mm0, %%mm2 \n\t" /* max */\ + "pminsw %%mm0, %%mm3 \n\t" /* min */\ + "pxor %%mm4, %%mm4 \n\t"\ + "pmaxsw %%mm3, %%mm6 \n\t"\ + "psubw %%mm2, %%mm4 \n\t" /* -max */\ + "pmaxsw %%mm4, %%mm6 \n\t" /* diff= MAX3(diff, min, -max); */\ + "1: \n\t"\ +\ + "movq %[tmp1], %%mm2 \n\t" /* d */\ + "movq %%mm2, %%mm3 \n\t"\ + "psubw %%mm6, %%mm2 \n\t" /* d-diff */\ + "paddw %%mm6, %%mm3 \n\t" /* d+diff */\ + "pmaxsw %%mm2, %%mm1 \n\t"\ + "pminsw %%mm3, %%mm1 \n\t" /* d = clip(spatial_pred, d-diff, d+diff); */\ + "packuswb %%mm1, %%mm1 \n\t"\ +\ + :[tmp0]"=m"(tmp0),\ + [tmp1]"=m"(tmp1),\ + [tmp2]"=m"(tmp2),\ + [tmp3]"=m"(tmp3)\ + :[prev] "r"(prev),\ + [cur] "r"(cur),\ + [next] "r"(next),\ + [prefs]"r"((x86_reg)refs),\ + [mrefs]"r"((x86_reg)-refs),\ + [pw1] "m"(pw_1),\ + [pb1] "m"(pb_1),\ + [mode] "g"(mode)\ + );\ + __asm__ volatile("movd %%mm1, %0" :"=m"(*dst));\ + dst += 4;\ + prev+= 4;\ + cur += 4;\ + next+= 4;\ + } + + if(parity){ +#define prev2 "prev" +#define next2 "cur" + FILTER +#undef prev2 +#undef next2 + }else{ +#define prev2 "cur" +#define next2 "next" + FILTER +#undef prev2 +#undef next2 + } +} +#undef LOAD4 +#undef PABS +#undef CHECK +#undef CHECK1 +#undef CHECK2 +#undef FILTER + +#endif /* HAVE_MMX */ + +static void filter_line_c(struct vf_priv_s *p, uint8_t *dst, uint8_t *prev, uint8_t *cur, uint8_t *next, int w, int refs, int parity){ + int x; + uint8_t *prev2= parity ? prev : cur ; + uint8_t *next2= parity ? cur : next; + for(x=0; x<w; x++){ + int c= cur[-refs]; + int d= (prev2[0] + next2[0])>>1; + int e= cur[+refs]; + int temporal_diff0= FFABS(prev2[0] - next2[0]); + int temporal_diff1=( FFABS(prev[-refs] - c) + FFABS(prev[+refs] - e) )>>1; + int temporal_diff2=( FFABS(next[-refs] - c) + FFABS(next[+refs] - e) )>>1; + int diff= FFMAX3(temporal_diff0>>1, temporal_diff1, temporal_diff2); + int spatial_pred= (c+e)>>1; + int spatial_score= FFABS(cur[-refs-1] - cur[+refs-1]) + FFABS(c-e) + + FFABS(cur[-refs+1] - cur[+refs+1]) - 1; + +#define CHECK(j)\ + { int score= FFABS(cur[-refs-1+j] - cur[+refs-1-j])\ + + FFABS(cur[-refs +j] - cur[+refs -j])\ + + FFABS(cur[-refs+1+j] - cur[+refs+1-j]);\ + if(score < spatial_score){\ + spatial_score= score;\ + spatial_pred= (cur[-refs +j] + cur[+refs -j])>>1;\ + + CHECK(-1) CHECK(-2) }} }} + CHECK( 1) CHECK( 2) }} }} + + if(p->mode<2){ + int b= (prev2[-2*refs] + next2[-2*refs])>>1; + int f= (prev2[+2*refs] + next2[+2*refs])>>1; +#if 0 + int a= cur[-3*refs]; + int g= cur[+3*refs]; + int max= FFMAX3(d-e, d-c, FFMIN3(FFMAX(b-c,f-e),FFMAX(b-c,b-a),FFMAX(f-g,f-e)) ); + int min= FFMIN3(d-e, d-c, FFMAX3(FFMIN(b-c,f-e),FFMIN(b-c,b-a),FFMIN(f-g,f-e)) ); +#else + int max= FFMAX3(d-e, d-c, FFMIN(b-c, f-e)); + int min= FFMIN3(d-e, d-c, FFMAX(b-c, f-e)); +#endif + + diff= FFMAX3(diff, min, -max); + } + + if(spatial_pred > d + diff) + spatial_pred = d + diff; + else if(spatial_pred < d - diff) + spatial_pred = d - diff; + + dst[0] = spatial_pred; + + dst++; + cur++; + prev++; + next++; + prev2++; + next2++; + } +} + +static void filter(struct vf_priv_s *p, uint8_t *dst[3], int dst_stride[3], int width, int height, int parity, int tff){ + int y, i; + + for(i=0; i<3; i++){ + int is_chroma= !!i; + int w= width >>is_chroma; + int h= height>>is_chroma; + int refs= p->stride[i]; + + for(y=0; y<h; y++){ + if((y ^ parity) & 1){ + uint8_t *prev= &p->ref[0][i][y*refs]; + uint8_t *cur = &p->ref[1][i][y*refs]; + uint8_t *next= &p->ref[2][i][y*refs]; + uint8_t *dst2= &dst[i][y*dst_stride[i]]; + filter_line(p, dst2, prev, cur, next, w, refs, parity ^ tff); + }else{ + memcpy(&dst[i][y*dst_stride[i]], &p->ref[1][i][y*refs], w); + } + } + } +#if HAVE_MMX + if(gCpuCaps.hasMMX2) __asm__ volatile("emms \n\t" : : : "memory"); +#endif +} + +static int config(struct vf_instance *vf, + int width, int height, int d_width, int d_height, + unsigned int flags, unsigned int outfmt){ + int i, j; + + for(i=0; i<3; i++){ + int is_chroma= !!i; + int w= ((width + 31) & (~31))>>is_chroma; + int h=(((height + 1) & ( ~1))>>is_chroma) + 6; + + vf->priv->stride[i]= w; + for(j=0; j<3; j++) + vf->priv->ref[j][i]= (char *)malloc(w*h*sizeof(uint8_t))+3*w; + } + + return vf_next_config(vf,width,height,d_width,d_height,flags,outfmt); +} + +static int continue_buffered_image(struct vf_instance *vf); + +static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts){ + int tff; + + if(vf->priv->parity < 0) { + if (mpi->fields & MP_IMGFIELD_ORDERED) + tff = !!(mpi->fields & MP_IMGFIELD_TOP_FIRST); + else + tff = 1; + } + else tff = (vf->priv->parity&1)^1; + + store_ref(vf->priv, mpi->planes, mpi->stride, mpi->w, mpi->h); + + { + double delta; + if (vf->priv->buffered_pts == MP_NOPTS_VALUE) + delta = 1001.0/60000.0; // delta = field time distance + else + delta = (pts - vf->priv->buffered_pts) / 2; + if (delta <= 0.0 || delta >= 0.5) + delta = 0.0; + vf->priv->buffered_pts_delta = delta; + } + + vf->priv->buffered_mpi = mpi; + vf->priv->buffered_tff = tff; + vf->priv->buffered_i = 0; + vf->priv->buffered_pts = pts; + + if(vf->priv->do_deinterlace == 0) + return vf_next_put_image(vf, mpi, pts); + else if(vf->priv->do_deinterlace == 1){ + vf->priv->do_deinterlace= 2; + return 0; + }else + return continue_buffered_image(vf); +} + +static int continue_buffered_image(struct vf_instance *vf) +{ + mp_image_t *mpi = vf->priv->buffered_mpi; + int tff = vf->priv->buffered_tff; + double pts = vf->priv->buffered_pts; + int i; + int ret=0; + mp_image_t *dmpi; + + pts += (vf->priv->buffered_i - 0.5 * (vf->priv->mode&1)) * vf->priv->buffered_pts_delta; + + for(i = vf->priv->buffered_i; i<=(vf->priv->mode&1); i++){ + dmpi=vf_get_image(vf->next,mpi->imgfmt, + MP_IMGTYPE_TEMP, + MP_IMGFLAG_ACCEPT_STRIDE|MP_IMGFLAG_PREFER_ALIGNED_STRIDE, + mpi->width,mpi->height); + vf_clone_mpi_attributes(dmpi, mpi); + filter(vf->priv, dmpi->planes, dmpi->stride, mpi->w, mpi->h, i ^ tff ^ 1, tff); + if (i < (vf->priv->mode & 1)) + vf_queue_frame(vf, continue_buffered_image); + ret |= vf_next_put_image(vf, dmpi, pts); + break; + } + vf->priv->buffered_i = 1; + return ret; +} + +static void uninit(struct vf_instance *vf){ + int i; + if(!vf->priv) return; + + for(i=0; i<3*3; i++){ + uint8_t **p= &vf->priv->ref[i%3][i/3]; + if(*p) free(*p - 3*vf->priv->stride[i/3]); + *p= NULL; + } + free(vf->priv); + vf->priv=NULL; +} + +//===========================================================================// +static int query_format(struct vf_instance *vf, unsigned int fmt){ + switch(fmt){ + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + case IMGFMT_Y800: + case IMGFMT_Y8: + return vf_next_query_format(vf,fmt); + } + return 0; +} + +static int control(struct vf_instance *vf, int request, void* data){ + switch (request){ + case VFCTRL_GET_DEINTERLACE: + *(int*)data = vf->priv->do_deinterlace; + return CONTROL_OK; + case VFCTRL_SET_DEINTERLACE: + vf->priv->do_deinterlace = 2*!!*(int*)data; + return CONTROL_OK; + } + return vf_next_control (vf, request, data); +} + +static int vf_open(vf_instance_t *vf, char *args){ + + vf->config=config; + vf->put_image=put_image; + vf->query_format=query_format; + vf->uninit=uninit; + vf->priv=malloc(sizeof(struct vf_priv_s)); + vf->control=control; + memset(vf->priv, 0, sizeof(struct vf_priv_s)); + + vf->priv->mode=0; + vf->priv->parity= -1; + vf->priv->do_deinterlace=1; + + if (args) sscanf(args, "%d:%d", &vf->priv->mode, &vf->priv->parity); + + filter_line = filter_line_c; +#if HAVE_MMX + if(gCpuCaps.hasMMX2) filter_line = filter_line_mmx2; +#endif + + return 1; +} + +const vf_info_t vf_info_yadif = { + "Yet Another DeInterlacing Filter", + "yadif", + "Michael Niedermayer", + "", + vf_open, + NULL +}; diff --git a/video/fmt-conversion.c b/video/fmt-conversion.c new file mode 100644 index 0000000000..81ab7a45fb --- /dev/null +++ b/video/fmt-conversion.c @@ -0,0 +1,144 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "mp_msg.h" +#include "libavutil/avutil.h" +#include <libavutil/pixdesc.h> +#include "libmpcodecs/img_format.h" +#include "fmt-conversion.h" + +static const struct { + int fmt; + enum PixelFormat pix_fmt; +} conversion_map[] = { + {IMGFMT_ARGB, PIX_FMT_ARGB}, + {IMGFMT_BGRA, PIX_FMT_BGRA}, + {IMGFMT_BGR24, PIX_FMT_BGR24}, + {IMGFMT_BGR16BE, PIX_FMT_RGB565BE}, + {IMGFMT_BGR16LE, PIX_FMT_RGB565LE}, + {IMGFMT_BGR15BE, PIX_FMT_RGB555BE}, + {IMGFMT_BGR15LE, PIX_FMT_RGB555LE}, + {IMGFMT_BGR12BE, PIX_FMT_RGB444BE}, + {IMGFMT_BGR12LE, PIX_FMT_RGB444LE}, + {IMGFMT_BGR8, PIX_FMT_RGB8}, + {IMGFMT_BGR4, PIX_FMT_RGB4}, + {IMGFMT_BGR1, PIX_FMT_MONOBLACK}, + {IMGFMT_RGB1, PIX_FMT_MONOBLACK}, + {IMGFMT_RG4B, PIX_FMT_BGR4_BYTE}, + {IMGFMT_BG4B, PIX_FMT_RGB4_BYTE}, + {IMGFMT_RGB48LE, PIX_FMT_RGB48LE}, + {IMGFMT_RGB48BE, PIX_FMT_RGB48BE}, + {IMGFMT_ABGR, PIX_FMT_ABGR}, + {IMGFMT_RGBA, PIX_FMT_RGBA}, + {IMGFMT_RGB24, PIX_FMT_RGB24}, + {IMGFMT_RGB16BE, PIX_FMT_BGR565BE}, + {IMGFMT_RGB16LE, PIX_FMT_BGR565LE}, + {IMGFMT_RGB15BE, PIX_FMT_BGR555BE}, + {IMGFMT_RGB15LE, PIX_FMT_BGR555LE}, + {IMGFMT_RGB12BE, PIX_FMT_BGR444BE}, + {IMGFMT_RGB12LE, PIX_FMT_BGR444LE}, + {IMGFMT_RGB8, PIX_FMT_BGR8}, + {IMGFMT_RGB4, PIX_FMT_BGR4}, + {IMGFMT_BGR8, PIX_FMT_PAL8}, + {IMGFMT_GBRP, PIX_FMT_GBRP}, + {IMGFMT_YUY2, PIX_FMT_YUYV422}, + {IMGFMT_UYVY, PIX_FMT_UYVY422}, + {IMGFMT_NV12, PIX_FMT_NV12}, + {IMGFMT_NV21, PIX_FMT_NV21}, + {IMGFMT_Y800, PIX_FMT_GRAY8}, + {IMGFMT_Y8, PIX_FMT_GRAY8}, + {IMGFMT_YVU9, PIX_FMT_YUV410P}, + {IMGFMT_IF09, PIX_FMT_YUV410P}, + {IMGFMT_YV12, PIX_FMT_YUV420P}, + {IMGFMT_I420, PIX_FMT_YUV420P}, + {IMGFMT_IYUV, PIX_FMT_YUV420P}, + {IMGFMT_411P, PIX_FMT_YUV411P}, + {IMGFMT_422P, PIX_FMT_YUV422P}, + {IMGFMT_444P, PIX_FMT_YUV444P}, + {IMGFMT_440P, PIX_FMT_YUV440P}, + + {IMGFMT_420A, PIX_FMT_YUVA420P}, + + {IMGFMT_420P16_LE, PIX_FMT_YUV420P16LE}, + {IMGFMT_420P16_BE, PIX_FMT_YUV420P16BE}, + {IMGFMT_420P9_LE, PIX_FMT_YUV420P9LE}, + {IMGFMT_420P9_BE, PIX_FMT_YUV420P9BE}, + {IMGFMT_420P10_LE, PIX_FMT_YUV420P10LE}, + {IMGFMT_420P10_BE, PIX_FMT_YUV420P10BE}, + {IMGFMT_422P10_LE, PIX_FMT_YUV422P10LE}, + {IMGFMT_422P10_BE, PIX_FMT_YUV422P10BE}, + {IMGFMT_444P9_BE , PIX_FMT_YUV444P9BE}, + {IMGFMT_444P9_LE , PIX_FMT_YUV444P9LE}, + {IMGFMT_444P10_BE, PIX_FMT_YUV444P10BE}, + {IMGFMT_444P10_LE, PIX_FMT_YUV444P10LE}, + {IMGFMT_422P16_LE, PIX_FMT_YUV422P16LE}, + {IMGFMT_422P16_BE, PIX_FMT_YUV422P16BE}, + {IMGFMT_422P9_LE, PIX_FMT_YUV422P9LE}, + {IMGFMT_422P9_BE, PIX_FMT_YUV422P9BE}, + {IMGFMT_444P16_LE, PIX_FMT_YUV444P16LE}, + {IMGFMT_444P16_BE, PIX_FMT_YUV444P16BE}, + + // YUVJ are YUV formats that use the full Y range and not just + // 16 - 235 (see colorspaces.txt). + // Currently they are all treated the same way. + {IMGFMT_YV12, PIX_FMT_YUVJ420P}, + {IMGFMT_422P, PIX_FMT_YUVJ422P}, + {IMGFMT_444P, PIX_FMT_YUVJ444P}, + {IMGFMT_440P, PIX_FMT_YUVJ440P}, + + // ffmpeg only +#if LIBAVUTIL_VERSION_MICRO >= 100 + {IMGFMT_BGR0, PIX_FMT_BGR0}, +#endif + + {IMGFMT_VDPAU_MPEG1, PIX_FMT_VDPAU_MPEG1}, + {IMGFMT_VDPAU_MPEG2, PIX_FMT_VDPAU_MPEG2}, + {IMGFMT_VDPAU_H264, PIX_FMT_VDPAU_H264}, + {IMGFMT_VDPAU_WMV3, PIX_FMT_VDPAU_WMV3}, + {IMGFMT_VDPAU_VC1, PIX_FMT_VDPAU_VC1}, + {IMGFMT_VDPAU_MPEG4, PIX_FMT_VDPAU_MPEG4}, + {0, PIX_FMT_NONE} +}; + +enum PixelFormat imgfmt2pixfmt(int fmt) +{ + int i; + enum PixelFormat pix_fmt; + for (i = 0; conversion_map[i].fmt; i++) + if (conversion_map[i].fmt == fmt) + break; + pix_fmt = conversion_map[i].pix_fmt; + if (pix_fmt == PIX_FMT_NONE) + mp_msg(MSGT_GLOBAL, MSGL_ERR, "Unsupported format %s\n", vo_format_name(fmt)); + return pix_fmt; +} + +int pixfmt2imgfmt(enum PixelFormat pix_fmt) +{ + int i; + for (i = 0; conversion_map[i].pix_fmt != PIX_FMT_NONE; i++) + if (conversion_map[i].pix_fmt == pix_fmt) + break; + int fmt = conversion_map[i].fmt; + if (!fmt) { + const char *fmtname = av_get_pix_fmt_name(pix_fmt); + mp_msg(MSGT_GLOBAL, MSGL_ERR, "Unsupported PixelFormat %s (%d)\n", + fmtname ? fmtname : "INVALID", pix_fmt); + } + return fmt; +} diff --git a/video/fmt-conversion.h b/video/fmt-conversion.h new file mode 100644 index 0000000000..f7114b0aef --- /dev/null +++ b/video/fmt-conversion.h @@ -0,0 +1,27 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_FMT_CONVERSION_H +#define MPLAYER_FMT_CONVERSION_H + +#include <libavutil/pixfmt.h> + +enum PixelFormat imgfmt2pixfmt(int fmt); +int pixfmt2imgfmt(enum PixelFormat pix_fmt); + +#endif /* MPLAYER_FMT_CONVERSION_H */ diff --git a/video/image_writer.c b/video/image_writer.c new file mode 100644 index 0000000000..877c89e700 --- /dev/null +++ b/video/image_writer.c @@ -0,0 +1,327 @@ +/* + * This file is part of mplayer. + * + * mplayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mplayer. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <setjmp.h> + +#include <libswscale/swscale.h> +#include <libavcodec/avcodec.h> + +#include "config.h" + +#ifdef CONFIG_JPEG +#include <jpeglib.h> +#endif + +#include "osdep/io.h" + +#include "image_writer.h" +#include "talloc.h" +#include "libmpcodecs/img_format.h" +#include "libmpcodecs/mp_image.h" +#include "libmpcodecs/dec_video.h" +#include "libmpcodecs/vf.h" +#include "fmt-conversion.h" + +#include "libmpcodecs/sws_utils.h" +#include "libmpcodecs/vf.h" + +#include "m_option.h" + +const struct image_writer_opts image_writer_opts_defaults = { + .format = "jpg", + .png_compression = 7, + .jpeg_quality = 90, + .jpeg_optimize = 100, + .jpeg_smooth = 0, + .jpeg_dpi = 72, + .jpeg_progressive = 0, + .jpeg_baseline = 1, +}; + +#undef OPT_BASE_STRUCT +#define OPT_BASE_STRUCT struct image_writer_opts + +const struct m_sub_options image_writer_conf = { + .opts = (m_option_t[]) { + OPT_INTRANGE("jpeg-quality", jpeg_quality, 0, 0, 100), + OPT_INTRANGE("jpeg-optimize", jpeg_optimize, 0, 0, 100), + OPT_INTRANGE("jpeg-smooth", jpeg_smooth, 0, 0, 100), + OPT_INTRANGE("jpeg-dpi", jpeg_dpi, M_OPT_MIN, 1, 99999), + OPT_MAKE_FLAGS("jpeg-progressive", jpeg_progressive, 0), + OPT_MAKE_FLAGS("jpeg-baseline", jpeg_baseline, 0), + OPT_INTRANGE("png-compression", png_compression, 0, 0, 9), + OPT_STRING("format", format, 0), + {0}, + }, + .size = sizeof(struct image_writer_opts), + .defaults = &image_writer_opts_defaults, +}; + +struct image_writer_ctx { + const struct image_writer_opts *opts; + const struct img_writer *writer; +}; + +struct img_writer { + const char *file_ext; + int (*write)(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp); + int *pixfmts; + int lavc_codec; +}; + +static int write_lavc(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp) +{ + void *outbuffer = NULL; + int success = 0; + AVFrame *pic = NULL; + + struct AVCodec *codec = avcodec_find_encoder(ctx->writer->lavc_codec); + AVCodecContext *avctx = NULL; + if (!codec) + goto print_open_fail; + avctx = avcodec_alloc_context3(codec); + if (!avctx) + goto print_open_fail; + + avctx->time_base = AV_TIME_BASE_Q; + avctx->width = image->width; + avctx->height = image->height; + avctx->pix_fmt = imgfmt2pixfmt(image->imgfmt); + if (ctx->writer->lavc_codec == CODEC_ID_PNG) + avctx->compression_level = ctx->opts->png_compression; + + if (avcodec_open2(avctx, codec, NULL) < 0) { + print_open_fail: + mp_msg(MSGT_CPLAYER, MSGL_INFO, "Could not open libavcodec encoder" + " for saving images\n"); + goto error_exit; + } + + size_t outbuffer_size = image->width * image->height * 3 * 2; + outbuffer = malloc(outbuffer_size); + if (!outbuffer) + goto error_exit; + + pic = avcodec_alloc_frame(); + if (!pic) + goto error_exit; + avcodec_get_frame_defaults(pic); + for (int n = 0; n < 4; n++) { + pic->data[n] = image->planes[n]; + pic->linesize[n] = image->stride[n]; + } + int size = avcodec_encode_video(avctx, outbuffer, outbuffer_size, pic); + if (size < 1) + goto error_exit; + + fwrite(outbuffer, size, 1, fp); + + success = 1; +error_exit: + if (avctx) + avcodec_close(avctx); + av_free(avctx); + avcodec_free_frame(&pic); + free(outbuffer); + return success; +} + +#ifdef CONFIG_JPEG + +static void write_jpeg_error_exit(j_common_ptr cinfo) +{ + // NOTE: do not write error message, too much effort to connect the libjpeg + // log callbacks with mplayer's log function mp_msp() + + // Return control to the setjmp point + longjmp(*(jmp_buf*)cinfo->client_data, 1); +} + +static int write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp) +{ + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr); + jerr.error_exit = write_jpeg_error_exit; + + jmp_buf error_return_jmpbuf; + cinfo.client_data = &error_return_jmpbuf; + if (setjmp(cinfo.client_data)) { + jpeg_destroy_compress(&cinfo); + return 0; + } + + jpeg_create_compress(&cinfo); + jpeg_stdio_dest(&cinfo, fp); + + cinfo.image_width = image->width; + cinfo.image_height = image->height; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + + cinfo.write_JFIF_header = TRUE; + cinfo.JFIF_major_version = 1; + cinfo.JFIF_minor_version = 2; + cinfo.density_unit = 1; /* 0=unknown, 1=dpi, 2=dpcm */ + /* Image DPI is determined by Y_density, so we leave that at + jpeg_dpi if possible and crunch X_density instead (PAR > 1) */ + // NOTE: write_image never passes anamorphic images currently + cinfo.X_density = ctx->opts->jpeg_dpi*image->width/image->w; + cinfo.Y_density = ctx->opts->jpeg_dpi*image->height/image->h; + cinfo.write_Adobe_marker = TRUE; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, ctx->opts->jpeg_quality, ctx->opts->jpeg_baseline); + cinfo.optimize_coding = ctx->opts->jpeg_optimize; + cinfo.smoothing_factor = ctx->opts->jpeg_smooth; + + if (ctx->opts->jpeg_progressive) + jpeg_simple_progression(&cinfo); + + jpeg_start_compress(&cinfo, TRUE); + + while (cinfo.next_scanline < cinfo.image_height) { + JSAMPROW row_pointer[1]; + row_pointer[0] = image->planes[0] + + cinfo.next_scanline * image->stride[0]; + jpeg_write_scanlines(&cinfo, row_pointer,1); + } + + jpeg_finish_compress(&cinfo); + + jpeg_destroy_compress(&cinfo); + + return 1; +} + +#endif + +static const struct img_writer img_writers[] = { + { "png", write_lavc, .lavc_codec = CODEC_ID_PNG }, + { "ppm", write_lavc, .lavc_codec = CODEC_ID_PPM }, + { "pgm", write_lavc, + .lavc_codec = CODEC_ID_PGM, + .pixfmts = (int[]) { IMGFMT_Y800, 0 }, + }, + { "pgmyuv", write_lavc, + .lavc_codec = CODEC_ID_PGMYUV, + .pixfmts = (int[]) { IMGFMT_YV12, 0 }, + }, + { "tga", write_lavc, + .lavc_codec = CODEC_ID_TARGA, + .pixfmts = (int[]) { IMGFMT_BGR24, IMGFMT_BGRA, IMGFMT_BGR15LE, + IMGFMT_Y800, 0}, + }, +#ifdef CONFIG_JPEG + { "jpg", write_jpeg }, + { "jpeg", write_jpeg }, +#endif +}; + +static const struct img_writer *get_writer(const struct image_writer_opts *opts) +{ + const char *type = opts->format; + + for (size_t n = 0; n < sizeof(img_writers) / sizeof(img_writers[0]); n++) { + const struct img_writer *writer = &img_writers[n]; + if (type && strcmp(type, writer->file_ext) == 0) + return writer; + } + + return &img_writers[0]; +} + +const char *image_writer_file_ext(const struct image_writer_opts *opts) +{ + struct image_writer_opts defs = image_writer_opts_defaults; + + if (!opts) + opts = &defs; + + return get_writer(opts)->file_ext; +} + +int write_image(struct mp_image *image, const struct image_writer_opts *opts, + const char *filename) +{ + struct mp_image *allocated_image = NULL; + struct image_writer_opts defs = image_writer_opts_defaults; + int d_w = image->display_w ? image->display_w : image->w; + int d_h = image->display_h ? image->display_h : image->h; + bool is_anamorphic = image->w != d_w || image->h != d_h; + + if (!opts) + opts = &defs; + + const struct img_writer *writer = get_writer(opts); + struct image_writer_ctx ctx = { opts, writer }; + int destfmt = IMGFMT_RGB24; + + if (writer->pixfmts) { + destfmt = writer->pixfmts[0]; // default to first pixel format + for (int *fmt = writer->pixfmts; *fmt; fmt++) { + if (*fmt == image->imgfmt) { + destfmt = *fmt; + break; + } + } + } + + // Caveat: - no colorspace/levels conversion done if pixel formats equal + // - RGB->YUV assumes BT.601 + // - color levels broken in various ways thanks to libswscale + if (image->imgfmt != destfmt || is_anamorphic) { + struct mp_image *dst = alloc_mpi(d_w, d_h, destfmt); + vf_clone_mpi_attributes(dst, image); + + int flags = SWS_LANCZOS | SWS_FULL_CHR_H_INT | SWS_FULL_CHR_H_INP | + SWS_ACCURATE_RND | SWS_BITEXACT; + + mp_image_swscale(dst, image, flags); + + allocated_image = dst; + image = dst; + } + + FILE *fp = fopen(filename, "wb"); + int success = 0; + if (fp == NULL) { + mp_msg(MSGT_CPLAYER, MSGL_ERR, + "Error opening '%s' for writing!\n", filename); + } else { + success = writer->write(&ctx, image, fp); + success = !fclose(fp) && success; + if (!success) + mp_msg(MSGT_CPLAYER, MSGL_ERR, "Error writing file '%s'!\n", + filename); + } + + free_mp_image(allocated_image); + + return success; +} + +void dump_png(struct mp_image *image, const char *filename) +{ + struct image_writer_opts opts = image_writer_opts_defaults; + opts.format = "png"; + write_image(image, &opts, filename); +} diff --git a/video/image_writer.h b/video/image_writer.h new file mode 100644 index 0000000000..e73b526c7e --- /dev/null +++ b/video/image_writer.h @@ -0,0 +1,53 @@ +/* + * This file is part of mplayer. + * + * mplayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mplayer. If not, see <http://www.gnu.org/licenses/>. + */ + +struct mp_image; +struct mp_csp_details; + +struct image_writer_opts { + char *format; + int png_compression; + int jpeg_quality; + int jpeg_optimize; + int jpeg_smooth; + int jpeg_dpi; + int jpeg_progressive; + int jpeg_baseline; +}; + +extern const struct image_writer_opts image_writer_opts_defaults; + +extern const struct m_sub_options image_writer_conf; + +// Return the file extension that will be used, e.g. "png". +const char *image_writer_file_ext(const struct image_writer_opts *opts); + +/* + * Save the given image under the given filename. The parameters csp and opts + * are optional. All pixel formats supported by swscale are supported. + * + * File format and compression settings are controlled via the opts parameter. + * + * NOTE: The fields w/h/width/height of the passed mp_image must be all set + * accordingly. Setting w and width or h and height to different values + * can be used to store snapshots of anamorphic video. + */ +int write_image(struct mp_image *image, const struct image_writer_opts *opts, + const char *filename); + +// Debugging helper. +void dump_png(struct mp_image *image, const char *filename); diff --git a/video/img_format.c b/video/img_format.c new file mode 100644 index 0000000000..1084a8f9a1 --- /dev/null +++ b/video/img_format.c @@ -0,0 +1,233 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "img_format.h" +#include "stdio.h" +#include "mpbswap.h" + +#include <string.h> + +const char *vo_format_name(int format) +{ + const char *name = mp_imgfmt_to_name(format); + if (name) + return name; + static char unknown_format[20]; + snprintf(unknown_format, 20, "Unknown 0x%04x", format); + return unknown_format; +} + +int mp_get_chroma_shift(int format, int *x_shift, int *y_shift, + int *component_bits) +{ + int xs = 0, ys = 0; + int bpp; + int err = 0; + int bits = 8; + if ((format & 0xff0000f0) == 0x34000050) + format = bswap_32(format); + if ((format & 0xf00000ff) == 0x50000034) { + switch (format >> 24) { + case 0x50: + break; + case 0x51: + bits = 16; + break; + case 0x52: + bits = 10; + break; + case 0x53: + bits = 9; + break; + default: + err = 1; + break; + } + switch (format & 0x00ffffff) { + case 0x00343434: // 444 + xs = 0; + ys = 0; + break; + case 0x00323234: // 422 + xs = 1; + ys = 0; + break; + case 0x00303234: // 420 + xs = 1; + ys = 1; + break; + case 0x00313134: // 411 + xs = 2; + ys = 0; + break; + case 0x00303434: // 440 + xs = 0; + ys = 1; + break; + default: + err = 1; + break; + } + } else + switch (format) { + case IMGFMT_420A: + case IMGFMT_I420: + case IMGFMT_IYUV: + case IMGFMT_YV12: + xs = 1; + ys = 1; + break; + case IMGFMT_IF09: + case IMGFMT_YVU9: + xs = 2; + ys = 2; + break; + case IMGFMT_Y8: + case IMGFMT_Y800: + xs = 31; + ys = 31; + break; + default: + err = 1; + break; + } + if (x_shift) + *x_shift = xs; + if (y_shift) + *y_shift = ys; + if (component_bits) + *component_bits = bits; + bpp = 8 + ((16 >> xs) >> ys); + if (format == IMGFMT_420A) + bpp += 8; + bpp *= (bits + 7) >> 3; + return err ? 0 : bpp; +} + +struct mp_imgfmt_entry mp_imgfmt_list[] = { + {"444p16le", IMGFMT_444P16_LE}, + {"444p16be", IMGFMT_444P16_BE}, + {"444p10le", IMGFMT_444P10_LE}, + {"444p10be", IMGFMT_444P10_BE}, + {"444p9le", IMGFMT_444P9_LE}, + {"444p9be", IMGFMT_444P9_BE}, + {"422p16le", IMGFMT_422P16_LE}, + {"422p16be", IMGFMT_422P16_BE}, + {"422p10le", IMGFMT_422P10_LE}, + {"422p10be", IMGFMT_422P10_BE}, + {"422p9le", IMGFMT_422P9_LE}, + {"422p9be", IMGFMT_422P9_BE}, + {"420p16le", IMGFMT_420P16_LE}, + {"420p16be", IMGFMT_420P16_BE}, + {"420p10le", IMGFMT_420P10_LE}, + {"420p10be", IMGFMT_420P10_BE}, + {"420p9le", IMGFMT_420P9_LE}, + {"420p9be", IMGFMT_420P9_BE}, + {"444p16", IMGFMT_444P16}, + {"444p10", IMGFMT_444P10}, + {"444p9", IMGFMT_444P9}, + {"422p16", IMGFMT_422P16}, + {"422p10", IMGFMT_422P10}, + {"420p10", IMGFMT_420P10}, + {"420p9", IMGFMT_420P9}, + {"420p16", IMGFMT_420P16}, + {"420a", IMGFMT_420A}, + {"444p", IMGFMT_444P}, + {"422p", IMGFMT_422P}, + {"411p", IMGFMT_411P}, + {"440p", IMGFMT_440P}, + {"yuy2", IMGFMT_YUY2}, + {"yvyu", IMGFMT_YVYU}, + {"uyvy", IMGFMT_UYVY}, + {"yvu9", IMGFMT_YVU9}, + {"if09", IMGFMT_IF09}, + {"yv12", IMGFMT_YV12}, + {"i420", IMGFMT_I420}, + {"iyuv", IMGFMT_IYUV}, + {"clpl", IMGFMT_CLPL}, + {"hm12", IMGFMT_HM12}, + {"y800", IMGFMT_Y800}, + {"y8", IMGFMT_Y8}, + {"nv12", IMGFMT_NV12}, + {"nv21", IMGFMT_NV21}, + {"bgr24", IMGFMT_BGR24}, + {"bgr32", IMGFMT_BGR32}, + {"bgr16", IMGFMT_BGR16}, + {"bgr15", IMGFMT_BGR15}, + {"bgr12", IMGFMT_BGR12}, + {"bgr8", IMGFMT_BGR8}, + {"bgr4", IMGFMT_BGR4}, + {"bg4b", IMGFMT_BG4B}, + {"bgr1", IMGFMT_BGR1}, + {"rgb48be", IMGFMT_RGB48BE}, + {"rgb48le", IMGFMT_RGB48LE}, + {"rgb48ne", IMGFMT_RGB48NE}, + {"rgb24", IMGFMT_RGB24}, + {"rgb32", IMGFMT_RGB32}, + {"rgb16", IMGFMT_RGB16}, + {"rgb15", IMGFMT_RGB15}, + {"rgb12", IMGFMT_RGB12}, + {"rgb8", IMGFMT_RGB8}, + {"rgb4", IMGFMT_RGB4}, + {"rg4b", IMGFMT_RG4B}, + {"rgb1", IMGFMT_RGB1}, + {"rgba", IMGFMT_RGBA}, + {"argb", IMGFMT_ARGB}, + {"bgra", IMGFMT_BGRA}, + {"abgr", IMGFMT_ABGR}, + {"bgr0", IMGFMT_BGR0}, + {"gbrp", IMGFMT_GBRP}, + {"mjpeg", IMGFMT_MJPEG}, + {"mjpg", IMGFMT_MJPEG}, + {"vdpau_h264", IMGFMT_VDPAU_H264}, + {"vdpau_mpeg1", IMGFMT_VDPAU_MPEG1}, + {"vdpau_mpeg2", IMGFMT_VDPAU_MPEG2}, + {"vdpau_mpeg4", IMGFMT_VDPAU_MPEG4}, + {"vdpau_wmv3", IMGFMT_VDPAU_WMV3}, + {"vdpau_vc1", IMGFMT_VDPAU_VC1}, + {0} +}; + +unsigned int mp_imgfmt_from_name(bstr name, bool allow_hwaccel) +{ + if (bstr_startswith0(name, "0x")) { + bstr rest; + unsigned int fmt = bstrtoll(name, &rest, 16); + if (rest.len == 0) + return fmt; + } + for(struct mp_imgfmt_entry *p = mp_imgfmt_list; p->name; ++p) { + if(!bstrcasecmp0(name, p->name)) { + if (!allow_hwaccel && IMGFMT_IS_HWACCEL(p->fmt)) + return 0; + return p->fmt; + } + } + return 0; +} + +const char *mp_imgfmt_to_name(unsigned int fmt) +{ + struct mp_imgfmt_entry *p = mp_imgfmt_list; + for(; p->name; ++p) { + if(p->fmt == fmt) + return p->name; + } + return NULL; +} diff --git a/video/img_format.h b/video/img_format.h new file mode 100644 index 0000000000..b488734f02 --- /dev/null +++ b/video/img_format.h @@ -0,0 +1,243 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_IMG_FORMAT_H +#define MPLAYER_IMG_FORMAT_H + +#include <sys/types.h> +#include "config.h" +#include "bstr.h" + +/* RGB/BGR Formats */ + +#define IMGFMT_RGB_MASK 0xFFFFFF00 +#define IMGFMT_RGB (('R'<<24)|('G'<<16)|('B'<<8)) +#define IMGFMT_RGB1 (IMGFMT_RGB|1) +#define IMGFMT_RGB4 (IMGFMT_RGB|4) +#define IMGFMT_RGB4_CHAR (IMGFMT_RGB|4|128) // RGB4 with 1 pixel per byte +#define IMGFMT_RGB8 (IMGFMT_RGB|8) +#define IMGFMT_RGB12 (IMGFMT_RGB|12) +#define IMGFMT_RGB15 (IMGFMT_RGB|15) +#define IMGFMT_RGB16 (IMGFMT_RGB|16) +#define IMGFMT_RGB24 (IMGFMT_RGB|24) +#define IMGFMT_RGB32 (IMGFMT_RGB|32) +#define IMGFMT_RGB48LE (IMGFMT_RGB|48) +#define IMGFMT_RGB48BE (IMGFMT_RGB|48|128) + +#define IMGFMT_BGR_MASK 0xFFFFFF00 +#define IMGFMT_BGR (('B'<<24)|('G'<<16)|('R'<<8)) +#define IMGFMT_BGR1 (IMGFMT_BGR|1) +#define IMGFMT_BGR4 (IMGFMT_BGR|4) +#define IMGFMT_BGR4_CHAR (IMGFMT_BGR|4|128) // BGR4 with 1 pixel per byte +#define IMGFMT_BGR8 (IMGFMT_BGR|8) +#define IMGFMT_BGR12 (IMGFMT_BGR|12) +#define IMGFMT_BGR15 (IMGFMT_BGR|15) +#define IMGFMT_BGR16 (IMGFMT_BGR|16) +#define IMGFMT_BGR24 (IMGFMT_BGR|24) +#define IMGFMT_BGR32 (IMGFMT_BGR|32) + +#define IMGFMT_GBRP (('G'<<24)|('B'<<16)|('R'<<8)|24) + +#if BYTE_ORDER == BIG_ENDIAN +#define IMGFMT_ABGR IMGFMT_RGB32 +#define IMGFMT_BGRA (IMGFMT_RGB32|128) +#define IMGFMT_ARGB IMGFMT_BGR32 +#define IMGFMT_RGBA (IMGFMT_BGR32|128) +#define IMGFMT_RGB48NE IMGFMT_RGB48BE +#define IMGFMT_RGB12BE IMGFMT_RGB12 +#define IMGFMT_RGB12LE (IMGFMT_RGB12|128) +#define IMGFMT_RGB15BE IMGFMT_RGB15 +#define IMGFMT_RGB15LE (IMGFMT_RGB15|128) +#define IMGFMT_RGB16BE IMGFMT_RGB16 +#define IMGFMT_RGB16LE (IMGFMT_RGB16|128) +#define IMGFMT_BGR12BE IMGFMT_BGR12 +#define IMGFMT_BGR12LE (IMGFMT_BGR12|128) +#define IMGFMT_BGR15BE IMGFMT_BGR15 +#define IMGFMT_BGR15LE (IMGFMT_BGR15|128) +#define IMGFMT_BGR16BE IMGFMT_BGR16 +#define IMGFMT_BGR16LE (IMGFMT_BGR16|128) +#else +#define IMGFMT_ABGR (IMGFMT_BGR32|128) +#define IMGFMT_BGRA IMGFMT_BGR32 +#define IMGFMT_ARGB (IMGFMT_RGB32|128) +#define IMGFMT_RGBA IMGFMT_RGB32 +#define IMGFMT_RGB48NE IMGFMT_RGB48LE +#define IMGFMT_RGB12BE (IMGFMT_RGB12|128) +#define IMGFMT_RGB12LE IMGFMT_RGB12 +#define IMGFMT_RGB15BE (IMGFMT_RGB15|128) +#define IMGFMT_RGB15LE IMGFMT_RGB15 +#define IMGFMT_RGB16BE (IMGFMT_RGB16|128) +#define IMGFMT_RGB16LE IMGFMT_RGB16 +#define IMGFMT_BGR12BE (IMGFMT_BGR12|128) +#define IMGFMT_BGR12LE IMGFMT_BGR12 +#define IMGFMT_BGR15BE (IMGFMT_BGR15|128) +#define IMGFMT_BGR15LE IMGFMT_BGR15 +#define IMGFMT_BGR16BE (IMGFMT_BGR16|128) +#define IMGFMT_BGR16LE IMGFMT_BGR16 +#endif + +/* old names for compatibility */ +#define IMGFMT_RG4B IMGFMT_RGB4_CHAR +#define IMGFMT_BG4B IMGFMT_BGR4_CHAR + +#define IMGFMT_IS_RGB(fmt) (((fmt)&IMGFMT_RGB_MASK)==IMGFMT_RGB) +#define IMGFMT_IS_BGR(fmt) (((fmt)&IMGFMT_BGR_MASK)==IMGFMT_BGR) + +#define IMGFMT_RGB_DEPTH(fmt) ((fmt)&0x3F) +#define IMGFMT_BGR_DEPTH(fmt) ((fmt)&0x3F) + +// AV_PIX_FMT_BGR0 +#define IMGFMT_BGR0 0x1DC70000 + +/* Planar YUV Formats */ + +#define IMGFMT_YVU9 0x39555659 +#define IMGFMT_IF09 0x39304649 +#define IMGFMT_YV12 0x32315659 +#define IMGFMT_I420 0x30323449 +#define IMGFMT_IYUV 0x56555949 +#define IMGFMT_CLPL 0x4C504C43 +#define IMGFMT_Y800 0x30303859 +#define IMGFMT_Y8 0x20203859 +#define IMGFMT_NV12 0x3231564E +#define IMGFMT_NV21 0x3132564E + +/* unofficial Planar Formats, FIXME if official 4CC exists */ +#define IMGFMT_444P 0x50343434 +#define IMGFMT_422P 0x50323234 +#define IMGFMT_411P 0x50313134 +#define IMGFMT_440P 0x50303434 +#define IMGFMT_HM12 0x32314D48 + +// 4:2:0 planar with alpha +#define IMGFMT_420A 0x41303234 + +#define IMGFMT_444P16_LE 0x51343434 +#define IMGFMT_444P16_BE 0x34343451 +#define IMGFMT_444P10_LE 0x52343434 +#define IMGFMT_444P10_BE 0x34343452 +#define IMGFMT_444P9_LE 0x53343434 +#define IMGFMT_444P9_BE 0x34343453 +#define IMGFMT_422P16_LE 0x51323234 +#define IMGFMT_422P16_BE 0x34323251 +#define IMGFMT_422P10_LE 0x52323234 +#define IMGFMT_422P10_BE 0x34323252 +#define IMGFMT_422P9_LE 0x53323234 +#define IMGFMT_422P9_BE 0x34323253 +#define IMGFMT_420P16_LE 0x51303234 +#define IMGFMT_420P16_BE 0x34323051 +#define IMGFMT_420P10_LE 0x52303234 +#define IMGFMT_420P10_BE 0x34323052 +#define IMGFMT_420P9_LE 0x53303234 +#define IMGFMT_420P9_BE 0x34323053 +#if BYTE_ORDER == BIG_ENDIAN +#define IMGFMT_444P16 IMGFMT_444P16_BE +#define IMGFMT_444P10 IMGFMT_444P10_BE +#define IMGFMT_444P9 IMGFMT_444P9_BE +#define IMGFMT_422P16 IMGFMT_422P16_BE +#define IMGFMT_422P10 IMGFMT_422P10_BE +#define IMGFMT_422P9 IMGFMT_422P9_BE +#define IMGFMT_420P16 IMGFMT_420P16_BE +#define IMGFMT_420P10 IMGFMT_420P10_BE +#define IMGFMT_420P9 IMGFMT_420P9_BE +#define IMGFMT_IS_YUVP16_NE(fmt) IMGFMT_IS_YUVP16_BE(fmt) +#else +#define IMGFMT_444P16 IMGFMT_444P16_LE +#define IMGFMT_444P10 IMGFMT_444P10_LE +#define IMGFMT_444P9 IMGFMT_444P9_LE +#define IMGFMT_422P16 IMGFMT_422P16_LE +#define IMGFMT_422P10 IMGFMT_422P10_LE +#define IMGFMT_422P9 IMGFMT_422P9_LE +#define IMGFMT_420P16 IMGFMT_420P16_LE +#define IMGFMT_420P10 IMGFMT_420P10_LE +#define IMGFMT_420P9 IMGFMT_420P9_LE +#define IMGFMT_IS_YUVP16_NE(fmt) IMGFMT_IS_YUVP16_LE(fmt) +#endif + +// These macros are misnamed - they actually match 9, 10 or 16 bits +#define IMGFMT_IS_YUVP16_LE(fmt) (((fmt - 0x51000034) & 0xfc0000ff) == 0) +#define IMGFMT_IS_YUVP16_BE(fmt) (((fmt - 0x34000051) & 0xff0000fc) == 0) +#define IMGFMT_IS_YUVP16(fmt) (IMGFMT_IS_YUVP16_LE(fmt) || IMGFMT_IS_YUVP16_BE(fmt)) + +/* Packed YUV Formats */ + +#define IMGFMT_IUYV 0x56595549 // Interlaced UYVY +#define IMGFMT_IY41 0x31435949 // Interlaced Y41P +#define IMGFMT_IYU1 0x31555949 +#define IMGFMT_IYU2 0x32555949 +#define IMGFMT_UYVY 0x59565955 +#define IMGFMT_UYNV 0x564E5955 // Exactly same as UYVY +#define IMGFMT_cyuv 0x76757963 // upside-down UYVY +#define IMGFMT_Y422 0x32323459 // Exactly same as UYVY +#define IMGFMT_YUY2 0x32595559 +#define IMGFMT_YUNV 0x564E5559 // Exactly same as YUY2 +#define IMGFMT_YVYU 0x55595659 +#define IMGFMT_Y41P 0x50313459 +#define IMGFMT_Y211 0x31313259 +#define IMGFMT_Y41T 0x54313459 // Y41P, Y lsb = transparency +#define IMGFMT_Y42T 0x54323459 // UYVY, Y lsb = transparency +#define IMGFMT_V422 0x32323456 // upside-down UYVY? +#define IMGFMT_V655 0x35353656 +#define IMGFMT_CLJR 0x524A4C43 +#define IMGFMT_YUVP 0x50565559 // 10-bit YUYV +#define IMGFMT_UYVP 0x50565955 // 10-bit UYVY + +/* Compressed Formats */ +#define IMGFMT_MJPEG (('M')|('J'<<8)|('P'<<16)|('G'<<24)) + +// VDPAU specific format. +#define IMGFMT_VDPAU 0x1DC80000 +#define IMGFMT_VDPAU_MASK 0xFFFF0000 +#define IMGFMT_IS_VDPAU(fmt) (((fmt)&IMGFMT_VDPAU_MASK)==IMGFMT_VDPAU) +#define IMGFMT_VDPAU_MPEG1 (IMGFMT_VDPAU|0x01) +#define IMGFMT_VDPAU_MPEG2 (IMGFMT_VDPAU|0x02) +#define IMGFMT_VDPAU_H264 (IMGFMT_VDPAU|0x03) +#define IMGFMT_VDPAU_WMV3 (IMGFMT_VDPAU|0x04) +#define IMGFMT_VDPAU_VC1 (IMGFMT_VDPAU|0x05) +#define IMGFMT_VDPAU_MPEG4 (IMGFMT_VDPAU|0x06) + +#define IMGFMT_IS_HWACCEL(fmt) IMGFMT_IS_VDPAU(fmt) + +typedef struct { + void* data; + int size; + int id; // stream id. usually 0x1E0 + int timestamp; // pts, 90000 Hz counter based +} vo_mpegpes_t; + +const char *vo_format_name(int format); + +/** + * Calculates the scale shifts for the chroma planes for planar YUV + * + * \param component_bits bits per component + * \return bits-per-pixel for format if successful (i.e. format is 3 or 4-planes planar YUV), 0 otherwise + */ +int mp_get_chroma_shift(int format, int *x_shift, int *y_shift, int *component_bits); + +struct mp_imgfmt_entry { + const char *name; + unsigned int fmt; +}; + +extern struct mp_imgfmt_entry mp_imgfmt_list[]; + +unsigned int mp_imgfmt_from_name(bstr name, bool allow_hwaccel); +const char *mp_imgfmt_to_name(unsigned int fmt); + +#endif /* MPLAYER_IMG_FORMAT_H */ diff --git a/video/memcpy_pic.h b/video/memcpy_pic.h new file mode 100644 index 0000000000..c2cd79314f --- /dev/null +++ b/video/memcpy_pic.h @@ -0,0 +1,77 @@ +/* + * This file is part of MPlayer. + * + * MPlayer 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. + * + * MPlayer 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 MPlayer; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MPLAYER_FASTMEMCPY_H +#define MPLAYER_FASTMEMCPY_H + +#include "config.h" +#include <inttypes.h> +#include <string.h> +#include <stddef.h> + +#define memcpy_pic(d, s, b, h, ds, ss) memcpy_pic2(d, s, b, h, ds, ss, 0) +#define my_memcpy_pic(d, s, b, h, ds, ss) memcpy_pic2(d, s, b, h, ds, ss, 1) + +/** + * \param limit2width always skip data between end of line and start of next + * instead of copying the full block when strides are the same + */ +static inline void * memcpy_pic2(void * dst, const void * src, + int bytesPerLine, int height, + int dstStride, int srcStride, int limit2width) +{ + int i; + void *retval=dst; + + if(!limit2width && dstStride == srcStride) + { + if (srcStride < 0) { + src = (uint8_t*)src + (height-1)*srcStride; + dst = (uint8_t*)dst + (height-1)*dstStride; + srcStride = -srcStride; + } + + memcpy(dst, src, srcStride*height); + } + else + { + for(i=0; i<height; i++) + { + memcpy(dst, src, bytesPerLine); + src = (uint8_t*)src + srcStride; + dst = (uint8_t*)dst + dstStride; + } + } + + return retval; +} + +static inline void memset_pic(void *dst, int fill, int bytesPerLine, int height, + int stride) +{ + if (bytesPerLine == stride) { + memset(dst, fill, stride * height); + } else { + for (int i = 0; i < height; i++) { + memset(dst, fill, bytesPerLine); + dst = (uint8_t *)dst + stride; + } + } +} + +#endif /* MPLAYER_FASTMEMCPY_H */ diff --git a/video/mp_image.c b/video/mp_image.c new file mode 100644 index 0000000000..c0227e4b1d --- /dev/null +++ b/video/mp_image.c @@ -0,0 +1,280 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "talloc.h" + +#include "libmpcodecs/img_format.h" +#include "libmpcodecs/mp_image.h" +#include "libmpcodecs/sws_utils.h" + +#include "libvo/fastmemcpy.h" +#include "libavutil/mem.h" +#include "libavutil/common.h" + +void mp_image_alloc_planes(mp_image_t *mpi) { + if (mpi->imgfmt == IMGFMT_BGRA) { + mpi->stride[0]=FFALIGN(mpi->width*4,SWS_MIN_BYTE_ALIGN); + mpi->planes[0]=av_malloc(mpi->stride[0]*mpi->height); + mpi->flags|=MP_IMGFLAG_ALLOCATED; + return; + } + if (mpi->imgfmt == IMGFMT_444P16 || mpi->imgfmt == IMGFMT_444P) { + int bp = mpi->imgfmt == IMGFMT_444P16 ? 2 : 1; + mpi->stride[0]=FFALIGN(mpi->width*bp,SWS_MIN_BYTE_ALIGN); + mpi->stride[1]=mpi->stride[2]=mpi->stride[0]; + int imgsize = mpi->stride[0] * mpi->height; + mpi->planes[0]=av_malloc(imgsize*3); + mpi->planes[1]=mpi->planes[0]+imgsize; + mpi->planes[2]=mpi->planes[1]+imgsize; + mpi->flags|=MP_IMGFLAG_ALLOCATED; + return; + } + // IF09 - allocate space for 4. plane delta info - unused + if (mpi->imgfmt == IMGFMT_IF09) { + mpi->planes[0]=av_malloc(mpi->bpp*mpi->width*(mpi->height+2)/8+ + mpi->chroma_width*mpi->chroma_height); + } else + mpi->planes[0]=av_malloc(mpi->bpp*mpi->width*(mpi->height+2)/8); + if (!mpi->planes[0]) + abort(); //out of memory + if (mpi->flags&MP_IMGFLAG_PLANAR) { + // FIXME this code only supports same bpp for all planes, and bpp divisible + // by 8. Currently the case for all planar formats. + int bpp = MP_IMAGE_PLANAR_BITS_PER_PIXEL_ON_PLANE(mpi, 0) / 8; + // YV12/I420/YVU9/IF09. feel free to add other planar formats here... + mpi->stride[0]=mpi->stride[3]=bpp*mpi->width; + if(mpi->num_planes > 2){ + mpi->stride[1]=mpi->stride[2]=bpp*mpi->chroma_width; + if(mpi->flags&MP_IMGFLAG_SWAPPED){ + // I420/IYUV (Y,U,V) + mpi->planes[1]=mpi->planes[0]+mpi->stride[0]*mpi->height; + mpi->planes[2]=mpi->planes[1]+mpi->stride[1]*mpi->chroma_height; + if (mpi->num_planes > 3) + mpi->planes[3]=mpi->planes[2]+mpi->stride[2]*mpi->chroma_height; + } else { + // YV12,YVU9,IF09 (Y,V,U) + mpi->planes[2]=mpi->planes[0]+mpi->stride[0]*mpi->height; + mpi->planes[1]=mpi->planes[2]+mpi->stride[1]*mpi->chroma_height; + if (mpi->num_planes > 3) + mpi->planes[3]=mpi->planes[1]+mpi->stride[1]*mpi->chroma_height; + } + } else { + // NV12/NV21 + mpi->stride[1]=mpi->chroma_width; + mpi->planes[1]=mpi->planes[0]+mpi->stride[0]*mpi->height; + } + } else { + mpi->stride[0]=mpi->width*mpi->bpp/8; + if (mpi->flags & MP_IMGFLAG_RGB_PALETTE) + mpi->planes[1] = av_malloc(1024); + } + mpi->flags|=MP_IMGFLAG_ALLOCATED; +} + +mp_image_t* alloc_mpi(int w, int h, unsigned long int fmt) { + mp_image_t* mpi = new_mp_image(w,h); + + mp_image_setfmt(mpi,fmt); + mp_image_alloc_planes(mpi); + + return mpi; +} + +void copy_mpi(mp_image_t *dmpi, mp_image_t *mpi) { + if(mpi->flags&MP_IMGFLAG_PLANAR){ + memcpy_pic(dmpi->planes[0],mpi->planes[0], MP_IMAGE_BYTES_PER_ROW_ON_PLANE(mpi, 0), mpi->h, + dmpi->stride[0],mpi->stride[0]); + memcpy_pic(dmpi->planes[1],mpi->planes[1], MP_IMAGE_BYTES_PER_ROW_ON_PLANE(mpi, 1), mpi->chroma_height, + dmpi->stride[1],mpi->stride[1]); + memcpy_pic(dmpi->planes[2], mpi->planes[2], MP_IMAGE_BYTES_PER_ROW_ON_PLANE(mpi, 2), mpi->chroma_height, + dmpi->stride[2],mpi->stride[2]); + } else { + memcpy_pic(dmpi->planes[0],mpi->planes[0], + MP_IMAGE_BYTES_PER_ROW_ON_PLANE(mpi, 0), mpi->h, + dmpi->stride[0],mpi->stride[0]); + } +} + +void mp_image_setfmt(mp_image_t* mpi,unsigned int out_fmt){ + mpi->flags&=~(MP_IMGFLAG_PLANAR|MP_IMGFLAG_YUV|MP_IMGFLAG_SWAPPED); + mpi->imgfmt=out_fmt; + // compressed formats + if(IMGFMT_IS_HWACCEL(out_fmt)){ + mpi->bpp=0; + return; + } + mpi->num_planes=1; + if (IMGFMT_IS_RGB(out_fmt)) { + if (IMGFMT_RGB_DEPTH(out_fmt) < 8 && !(out_fmt&128)) + mpi->bpp = IMGFMT_RGB_DEPTH(out_fmt); + else + mpi->bpp=(IMGFMT_RGB_DEPTH(out_fmt)+7)&(~7); + return; + } + if (IMGFMT_IS_BGR(out_fmt)) { + if (IMGFMT_BGR_DEPTH(out_fmt) < 8 && !(out_fmt&128)) + mpi->bpp = IMGFMT_BGR_DEPTH(out_fmt); + else + mpi->bpp=(IMGFMT_BGR_DEPTH(out_fmt)+7)&(~7); + mpi->flags|=MP_IMGFLAG_SWAPPED; + return; + } + switch (out_fmt) { + case IMGFMT_BGR0: + mpi->bpp = 32; + return; + } + mpi->num_planes=3; + if (out_fmt == IMGFMT_GBRP) { + mpi->bpp=24; + mpi->flags|=MP_IMGFLAG_PLANAR; + return; + } + mpi->flags|=MP_IMGFLAG_YUV; + if (mp_get_chroma_shift(out_fmt, NULL, NULL, NULL)) { + mpi->flags|=MP_IMGFLAG_PLANAR; + mpi->bpp = mp_get_chroma_shift(out_fmt, &mpi->chroma_x_shift, &mpi->chroma_y_shift, NULL); + mpi->chroma_width = mpi->width >> mpi->chroma_x_shift; + mpi->chroma_height = mpi->height >> mpi->chroma_y_shift; + } + switch(out_fmt){ + case IMGFMT_I420: + case IMGFMT_IYUV: + mpi->flags|=MP_IMGFLAG_SWAPPED; + case IMGFMT_YV12: + return; + case IMGFMT_420A: + case IMGFMT_IF09: + mpi->num_planes=4; + case IMGFMT_YVU9: + case IMGFMT_444P: + case IMGFMT_422P: + case IMGFMT_411P: + case IMGFMT_440P: + case IMGFMT_444P16_LE: + case IMGFMT_444P16_BE: + case IMGFMT_444P10_LE: + case IMGFMT_444P10_BE: + case IMGFMT_444P9_LE: + case IMGFMT_444P9_BE: + case IMGFMT_422P16_LE: + case IMGFMT_422P16_BE: + case IMGFMT_422P10_LE: + case IMGFMT_422P10_BE: + case IMGFMT_422P9_LE: + case IMGFMT_422P9_BE: + case IMGFMT_420P16_LE: + case IMGFMT_420P16_BE: + case IMGFMT_420P10_LE: + case IMGFMT_420P10_BE: + case IMGFMT_420P9_LE: + case IMGFMT_420P9_BE: + return; + case IMGFMT_Y800: + case IMGFMT_Y8: + /* they're planar ones, but for easier handling use them as packed */ + mpi->flags&=~MP_IMGFLAG_PLANAR; + mpi->num_planes=1; + return; + case IMGFMT_UYVY: + mpi->flags|=MP_IMGFLAG_SWAPPED; + case IMGFMT_YUY2: + mpi->chroma_x_shift = 1; + mpi->chroma_y_shift = 1; + mpi->chroma_width=(mpi->width>>1); + mpi->chroma_height=(mpi->height>>1); + mpi->bpp=16; + mpi->num_planes=1; + return; + case IMGFMT_NV12: + mpi->flags|=MP_IMGFLAG_SWAPPED; + case IMGFMT_NV21: + mpi->flags|=MP_IMGFLAG_PLANAR; + mpi->bpp=12; + mpi->num_planes=2; + mpi->chroma_width=(mpi->width>>0); + mpi->chroma_height=(mpi->height>>1); + mpi->chroma_x_shift=0; + mpi->chroma_y_shift=1; + return; + } + mp_msg(MSGT_DECVIDEO,MSGL_WARN,"mp_image: unknown out_fmt: 0x%X\n",out_fmt); + mpi->bpp=0; +} + +static int mp_image_destructor(void *ptr) +{ + mp_image_t *mpi = ptr; + + if(mpi->flags&MP_IMGFLAG_ALLOCATED){ + /* because we allocate the whole image at once */ + av_free(mpi->planes[0]); + if (mpi->flags & MP_IMGFLAG_RGB_PALETTE) + av_free(mpi->planes[1]); + } + + return 0; +} + +mp_image_t* new_mp_image(int w,int h){ + mp_image_t* mpi = talloc_zero(NULL, mp_image_t); + talloc_set_destructor(mpi, mp_image_destructor); + mpi->width=mpi->w=w; + mpi->height=mpi->h=h; + return mpi; +} + +void free_mp_image(mp_image_t* mpi){ + talloc_free(mpi); +} + +enum mp_csp mp_image_csp(struct mp_image *img) +{ + if (img->colorspace != MP_CSP_AUTO) + return img->colorspace; + return (img->flags & MP_IMGFLAG_YUV) ? MP_CSP_BT_601 : MP_CSP_RGB; +} + +enum mp_csp_levels mp_image_levels(struct mp_image *img) +{ + if (img->levels != MP_CSP_LEVELS_AUTO) + return img->levels; + return (img->flags & MP_IMGFLAG_YUV) ? MP_CSP_LEVELS_TV : MP_CSP_LEVELS_PC; +} + +void mp_image_set_colorspace_details(struct mp_image *image, + struct mp_csp_details *csp) +{ + if (image->flags & MP_IMGFLAG_YUV) { + image->colorspace = csp->format; + if (image->colorspace == MP_CSP_AUTO) + image->colorspace = MP_CSP_BT_601; + image->levels = csp->levels_in; + if (image->levels == MP_CSP_LEVELS_AUTO) + image->levels = MP_CSP_LEVELS_TV; + } else { + image->colorspace = MP_CSP_RGB; + image->levels = MP_CSP_LEVELS_PC; + } +} diff --git a/video/mp_image.h b/video/mp_image.h new file mode 100644 index 0000000000..f2d149bc9d --- /dev/null +++ b/video/mp_image.h @@ -0,0 +1,162 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_MP_IMAGE_H +#define MPLAYER_MP_IMAGE_H + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include "mp_msg.h" +#include "libvo/csputils.h" + +//--------- codec's requirements (filled by the codec/vf) --------- + +//--- buffer content restrictions: +// set if buffer content shouldn't be modified: +#define MP_IMGFLAG_PRESERVE 0x01 +// set if buffer content will be READ. +// This can be e.g. for next frame's MC: (I/P mpeg frames) - +// then in combination with MP_IMGFLAG_PRESERVE - or it +// can be because a video filter or codec will read a significant +// amount of data while processing that frame (e.g. blending something +// onto the frame, MV based intra prediction). +// A frame marked like this should not be placed in to uncachable +// video RAM for example. +#define MP_IMGFLAG_READABLE 0x02 + +//--- buffer width/stride/plane restrictions: (used for direct rendering) +// stride _have_to_ be aligned to MB boundary: [for DR restrictions] +#define MP_IMGFLAG_ACCEPT_ALIGNED_STRIDE 0x4 +// stride should be aligned to MB boundary: [for buffer allocation] +#define MP_IMGFLAG_PREFER_ALIGNED_STRIDE 0x8 +// codec accept any stride (>=width): +#define MP_IMGFLAG_ACCEPT_STRIDE 0x10 +// codec accept any width (width*bpp=stride -> stride%bpp==0) (>=width): +#define MP_IMGFLAG_ACCEPT_WIDTH 0x20 +//--- for planar formats only: +// uses only stride[0], and stride[1]=stride[2]=stride[0]>>mpi->chroma_x_shift +#define MP_IMGFLAG_COMMON_STRIDE 0x40 +// uses only planes[0], and calculates planes[1,2] from width,height,imgfmt +#define MP_IMGFLAG_COMMON_PLANE 0x80 + +#define MP_IMGFLAGMASK_RESTRICTIONS 0xFF + +//--------- color info (filled by mp_image_setfmt() ) ----------- +// set if number of planes > 1 +#define MP_IMGFLAG_PLANAR 0x100 +// set if it's YUV colorspace +#define MP_IMGFLAG_YUV 0x200 +// set if it's swapped (BGR or YVU) plane/byteorder +#define MP_IMGFLAG_SWAPPED 0x400 +// set if you want memory for palette allocated and managed by vf_get_image etc. +#define MP_IMGFLAG_RGB_PALETTE 0x800 + +#define MP_IMGFLAGMASK_COLORS 0xF00 + +// codec uses drawing/rendering callbacks (draw_slice()-like thing, DR method 2) +// [the codec will set this flag if it supports callbacks, and the vo _may_ +// clear it in get_image() if draw_slice() not implemented] +#define MP_IMGFLAG_DRAW_CALLBACK 0x1000 +// set if it's in video buffer/memory: [set by vo/vf's get_image() !!!] +#define MP_IMGFLAG_DIRECT 0x2000 +// set if buffer is allocated (used in destination images): +#define MP_IMGFLAG_ALLOCATED 0x4000 + +// buffer type was printed (do NOT set this flag - it's for INTERNAL USE!!!) +#define MP_IMGFLAG_TYPE_DISPLAYED 0x8000 + +// codec doesn't support any form of direct rendering - it has own buffer +// allocation. so we just export its buffer pointers: +#define MP_IMGTYPE_EXPORT 0 +// codec requires a static WO buffer, but it does only partial updates later: +#define MP_IMGTYPE_STATIC 1 +// codec just needs some WO memory, where it writes/copies the whole frame to: +#define MP_IMGTYPE_TEMP 2 +// I+P type, requires 2+ independent static R/W buffers +#define MP_IMGTYPE_IP 3 +// I+P+B type, requires 2+ independent static R/W and 1+ temp WO buffers +#define MP_IMGTYPE_IPB 4 +// Upper 16 bits give desired buffer number, -1 means get next available +#define MP_IMGTYPE_NUMBERED 5 + +#define MP_MAX_PLANES 4 + +#define MP_IMGFIELD_ORDERED 0x01 +#define MP_IMGFIELD_TOP_FIRST 0x02 +#define MP_IMGFIELD_REPEAT_FIRST 0x04 +#define MP_IMGFIELD_TOP 0x08 +#define MP_IMGFIELD_BOTTOM 0x10 +#define MP_IMGFIELD_INTERLACED 0x20 + +typedef struct mp_image { + unsigned int flags; + unsigned char type; + int number; + unsigned char bpp; // bits/pixel. NOT depth! for RGB it will be n*8 + unsigned int imgfmt; + int width,height; // internal to vf.c, do not use (stored dimensions) + int w,h; // visible dimensions + int display_w,display_h; // if set (!= 0), anamorphic size + uint8_t *planes[MP_MAX_PLANES]; + int stride[MP_MAX_PLANES]; + char * qscale; + int qstride; + int pict_type; // 0->unknown, 1->I, 2->P, 3->B + int fields; + int qscale_type; // 0->mpeg1/4/h263, 1->mpeg2 + int num_planes; + /* these are only used by planar formats Y,U(Cb),V(Cr) */ + int chroma_width; + int chroma_height; + int chroma_x_shift; // horizontal + int chroma_y_shift; // vertical + enum mp_csp colorspace; + enum mp_csp_levels levels; + int usage_count; + /* for private use by filter or vo driver (to store buffer id or dmpi) */ + void* priv; +} mp_image_t; + +void mp_image_setfmt(mp_image_t* mpi,unsigned int out_fmt); +mp_image_t* new_mp_image(int w,int h); +void free_mp_image(mp_image_t* mpi); + +mp_image_t* alloc_mpi(int w, int h, unsigned long int fmt); +void mp_image_alloc_planes(mp_image_t *mpi); +void copy_mpi(mp_image_t *dmpi, mp_image_t *mpi); + +enum mp_csp mp_image_csp(struct mp_image *img); +enum mp_csp_levels mp_image_levels(struct mp_image *img); + +struct mp_csp_details; +void mp_image_set_colorspace_details(struct mp_image *image, + struct mp_csp_details *csp); + +// this macro requires img_format.h to be included too: +#define MP_IMAGE_PLANAR_BITS_PER_PIXEL_ON_PLANE(mpi, p) \ + (IMGFMT_IS_YUVP16((mpi)->imgfmt) ? 16 : 8) +#define MP_IMAGE_BITS_PER_PIXEL_ON_PLANE(mpi, p) \ + (((mpi)->flags & MP_IMGFLAG_PLANAR) \ + ? MP_IMAGE_PLANAR_BITS_PER_PIXEL_ON_PLANE(mpi, p) \ + : (mpi)->bpp) +#define MP_IMAGE_BYTES_PER_ROW_ON_PLANE(mpi, p) \ + ((MP_IMAGE_BITS_PER_PIXEL_ON_PLANE(mpi, p) * ((mpi)->w >> (p ? mpi->chroma_x_shift : 0)) + 7) / 8) + +#endif /* MPLAYER_MP_IMAGE_H */ diff --git a/video/out/aspect.c b/video/out/aspect.c new file mode 100644 index 0000000000..f3cd00a5e5 --- /dev/null +++ b/video/out/aspect.c @@ -0,0 +1,148 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* Stuff for correct aspect scaling. */ +#include "aspect.h" +#include "geometry.h" +#include "video_out.h" +#include "mp_msg.h" +#include "options.h" + +#include "video_out.h" + +void aspect_save_videores(struct vo *vo, int w, int h, int d_w, int d_h) +{ + vo->aspdat.orgw = w; + vo->aspdat.orgh = h; + vo->aspdat.prew = d_w; + vo->aspdat.preh = d_h; + vo->aspdat.par = (double)d_w / d_h * h / w; +} + +void aspect_save_screenres(struct vo *vo, int scrw, int scrh) +{ + mp_msg(MSGT_VO, MSGL_DBG2, "aspect_save_screenres %dx%d\n", scrw, scrh); + struct MPOpts *opts = vo->opts; + if (scrw <= 0 && scrh <= 0) + scrw = 1024; + if (scrh <= 0) + scrh = (scrw * 3 + 3) / 4; + if (scrw <= 0) + scrw = (scrh * 4 + 2) / 3; + vo->aspdat.scrw = scrw; + vo->aspdat.scrh = scrh; + if (opts->force_monitor_aspect) + vo->monitor_par = opts->force_monitor_aspect * scrh / scrw; + else + vo->monitor_par = 1.0 / opts->monitor_pixel_aspect; +} + +/* aspect is called with the source resolution and the + * resolution, that the scaled image should fit into + */ + +void aspect_fit(struct vo *vo, int *srcw, int *srch, int fitw, int fith) +{ + struct aspect_data *aspdat = &vo->aspdat; + float pixelaspect = vo->monitor_par; + + mp_msg(MSGT_VO, MSGL_DBG2, "aspect(0) fitin: %dx%d monitor_par: %.2f\n", + fitw, fith, vo->monitor_par); + *srcw = fitw; + *srch = (float)fitw / aspdat->prew * aspdat->preh / pixelaspect; + *srch += *srch % 2; // round + mp_msg(MSGT_VO, MSGL_DBG2, "aspect(1) wh: %dx%d (org: %dx%d)\n", + *srcw, *srch, aspdat->prew, aspdat->preh); + if (*srch > fith || *srch < aspdat->orgh) { + int tmpw = (float)fith / aspdat->preh * aspdat->prew * pixelaspect; + tmpw += tmpw % 2; // round + if (tmpw <= fitw) { + *srch = fith; + *srcw = tmpw; + } else if (*srch > fith) { + mp_tmsg(MSGT_VO, MSGL_WARN, + "[ASPECT] Warning: No suitable new res found!\n"); + } + } + aspdat->asp = *srcw / (float)*srch; + mp_msg(MSGT_VO, MSGL_DBG2, "aspect(2) wh: %dx%d (org: %dx%d)\n", + *srcw, *srch, aspdat->prew, aspdat->preh); +} + +static void get_max_dims(struct vo *vo, int *w, int *h, int zoom) +{ + struct aspect_data *aspdat = &vo->aspdat; + *w = zoom ? aspdat->scrw : aspdat->prew; + *h = zoom ? aspdat->scrh : aspdat->preh; + if (zoom && WinID >= 0) + zoom = A_WINZOOM; + if (zoom == A_WINZOOM) { + *w = vo->dwidth; + *h = vo->dheight; + } +} + +void aspect(struct vo *vo, int *srcw, int *srch, int zoom) +{ + int fitw; + int fith; + get_max_dims(vo, &fitw, &fith, zoom); + if (!zoom && geometry_wh_changed) { + mp_msg(MSGT_VO, MSGL_DBG2, "aspect(0) no aspect forced!\n"); + return; // the user doesn't want to fix aspect + } + aspect_fit(vo, srcw, srch, fitw, fith); +} + +void panscan_init(struct vo *vo) +{ + vo->panscan_x = 0; + vo->panscan_y = 0; + vo->panscan_amount = 0.0f; +} + +static void panscan_calc_internal(struct vo *vo, int zoom) +{ + int fwidth, fheight; + int vo_panscan_area; + int max_w, max_h; + get_max_dims(vo, &max_w, &max_h, zoom); + struct MPOpts *opts = vo->opts; + + if (opts->vo_panscanrange > 0) { + aspect(vo, &fwidth, &fheight, zoom); + vo_panscan_area = max_h - fheight; + if (!vo_panscan_area) + vo_panscan_area = max_w - fwidth; + vo_panscan_area *= opts->vo_panscanrange; + } else + vo_panscan_area = -opts->vo_panscanrange * max_h; + + vo->panscan_amount = vo_fs || zoom == A_WINZOOM ? vo_panscan : 0; + vo->panscan_x = vo_panscan_area * vo->panscan_amount * vo->aspdat.asp; + vo->panscan_y = vo_panscan_area * vo->panscan_amount; +} + +/** + * vos that set vo_dwidth and v_dheight correctly should call this to update + * vo_panscan_x and vo_panscan_y + */ +void panscan_calc_windowed(struct vo *vo) +{ + panscan_calc_internal(vo, A_WINZOOM); +} diff --git a/video/out/aspect.h b/video/out/aspect.h new file mode 100644 index 0000000000..c5247421d2 --- /dev/null +++ b/video/out/aspect.h @@ -0,0 +1,37 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_ASPECT_H +#define MPLAYER_ASPECT_H +/* Stuff for correct aspect scaling. */ + +struct vo; +void panscan_init(struct vo *vo); +void panscan_calc_windowed(struct vo *vo); + +void aspect_save_videores(struct vo *vo, int w, int h, int d_w, int d_h); +void aspect_save_screenres(struct vo *vo, int scrw, int scrh); + +#define A_WINZOOM 2 ///< zoom to fill window size +#define A_ZOOM 1 +#define A_NOZOOM 0 + +void aspect(struct vo *vo, int *srcw, int *srch, int zoom); +void aspect_fit(struct vo *vo, int *srcw, int *srch, int fitw, int fith); + +#endif /* MPLAYER_ASPECT_H */ diff --git a/video/out/bitmap_packer.c b/video/out/bitmap_packer.c new file mode 100644 index 0000000000..603a6ce410 --- /dev/null +++ b/video/out/bitmap_packer.c @@ -0,0 +1,227 @@ +/* + * Calculate how to pack bitmap rectangles into a larger surface + * + * Copyright 2009, 2012 Uoti Urpala + * + * This file is part of mplayer2. + * + * mplayer2 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mplayer2. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <assert.h> + +#include <libavutil/common.h> + +#include "talloc.h" +#include "bitmap_packer.h" +#include "mp_msg.h" +#include "mpcommon.h" +#include "sub/dec_sub.h" +#include "fastmemcpy.h" + +#define IS_POWER_OF_2(x) (((x) > 0) && !(((x) - 1) & (x))) + +void packer_reset(struct bitmap_packer *packer) +{ + struct bitmap_packer old = *packer; + *packer = (struct bitmap_packer) { + .w_max = old.w_max, + .h_max = old.h_max, + }; + talloc_free_children(packer); +} + +void packer_get_bb(struct bitmap_packer *packer, struct pos out_bb[2]) +{ + out_bb[0] = (struct pos) {0}; + out_bb[1] = (struct pos) { + FFMIN(packer->used_width + packer->padding, packer->w), + FFMIN(packer->used_height + packer->padding, packer->h), + }; +} + +#define HEIGHT_SORT_BITS 4 +static int size_index(int s) +{ + int n = av_log2_16bit(s); + return (n << HEIGHT_SORT_BITS) + + (- 1 - (s << HEIGHT_SORT_BITS >> n) & (1 << HEIGHT_SORT_BITS) - 1); +} + +/* Pack the given rectangles into an area of size w * h. + * The size of each rectangle is read from in[i].x / in[i].y. + * The height of each rectangle must be less than 65536. + * 'scratch' must point to work memory for num_rects+16 ints. + * The packed position for rectangle number i is set in out[i]. + * Return 0 on success, -1 if the rectangles did not fit in w*h. + * + * The rectangles are placed in rows in order approximately sorted by + * height (the approximate sorting is simpler than a full one would be, + * and allows the algorithm to work in linear time). Additionally, to + * reduce wasted space when there are a few tall rectangles, empty + * lower-right parts of rows are filled recursively when the size of + * rectangles in the row drops past a power-of-two threshold. So if a + * row starts with rectangles of size 3x50, 10x40 and 5x20 then the + * free rectangle with corners (13, 20)-(w, 50) is filled recursively. + */ +static int pack_rectangles(struct pos *in, struct pos *out, int num_rects, + int w, int h, int *scratch, int *used_width) +{ + int bins[16 << HEIGHT_SORT_BITS]; + int sizes[16 << HEIGHT_SORT_BITS] = { 0 }; + for (int i = 0; i < num_rects; i++) + sizes[size_index(in[i].y)]++; + int idx = 0; + for (int i = 0; i < 16 << HEIGHT_SORT_BITS; i += 1 << HEIGHT_SORT_BITS) { + for (int j = 0; j < 1 << HEIGHT_SORT_BITS; j++) { + bins[i + j] = idx; + idx += sizes[i + j]; + } + scratch[idx++] = -1; + } + for (int i = 0; i < num_rects; i++) + scratch[bins[size_index(in[i].y)]++] = i; + for (int i = 0; i < 16; i++) + bins[i] = bins[i << HEIGHT_SORT_BITS] - sizes[i << HEIGHT_SORT_BITS]; + struct { + int size, x, bottom; + } stack[16] = {{15, 0, h}}, s = {}; + int stackpos = 1; + int y; + while (stackpos) { + y = s.bottom; + s = stack[--stackpos]; + s.size++; + while (s.size--) { + int maxy = -1; + int obj; + while ((obj = scratch[bins[s.size]]) >= 0) { + int bottom = y + in[obj].y; + if (bottom > s.bottom) + break; + int right = s.x + in[obj].x; + if (right > w) + break; + bins[s.size]++; + out[obj] = (struct pos){s.x, y}; + num_rects--; + if (maxy < 0) + stack[stackpos++] = s; + s.x = right; + maxy = FFMAX(maxy, bottom); + } + *used_width = FFMAX(*used_width, s.x); + if (maxy > 0) + s.bottom = maxy; + } + } + return num_rects ? -1 : y; +} + +int packer_pack(struct bitmap_packer *packer) +{ + if (packer->count == 0) + return 0; + int w_orig = packer->w, h_orig = packer->h; + struct pos *in = packer->in; + int xmax = 0, ymax = 0; + for (int i = 0; i < packer->count; i++) { + if (in[i].x <= packer->padding || in[i].y <= packer->padding) + in[i] = (struct pos){0, 0}; + if (in[i].x < 0 || in [i].x > 65535 || in[i].y < 0 || in[i].y > 65535) { + mp_msg(MSGT_VO, MSGL_FATAL, "Invalid OSD / subtitle bitmap size\n"); + abort(); + } + xmax = FFMAX(xmax, in[i].x); + ymax = FFMAX(ymax, in[i].y); + } + xmax = FFMAX(0, xmax - packer->padding); + ymax = FFMAX(0, ymax - packer->padding); + if (xmax > packer->w) + packer->w = 1 << av_log2(xmax - 1) + 1; + if (ymax > packer->h) + packer->h = 1 << av_log2(ymax - 1) + 1; + while (1) { + int used_width = 0; + int y = pack_rectangles(in, packer->result, packer->count, + packer->w + packer->padding, + packer->h + packer->padding, + packer->scratch, &used_width); + if (y >= 0) { + // No padding at edges + packer->used_width = FFMIN(used_width, packer->w); + packer->used_height = FFMIN(y, packer->h); + assert(packer->w == 0 || IS_POWER_OF_2(packer->w)); + assert(packer->h == 0 || IS_POWER_OF_2(packer->h)); + return packer->w != w_orig || packer->h != h_orig; + } + if (packer->w <= packer->h && packer->w != packer->w_max) + packer->w = FFMIN(packer->w * 2, packer->w_max); + else if (packer->h != packer->h_max) + packer->h = FFMIN(packer->h * 2, packer->h_max); + else { + packer->w = w_orig; + packer->h = h_orig; + return -1; + } + } +} + +void packer_set_size(struct bitmap_packer *packer, int size) +{ + packer->count = size; + if (size <= packer->asize) + return; + packer->asize = FFMAX(packer->asize * 2, size); + talloc_free(packer->result); + talloc_free(packer->scratch); + packer->in = talloc_realloc(packer, packer->in, struct pos, packer->asize); + packer->result = talloc_array_ptrtype(packer, packer->result, + packer->asize); + packer->scratch = talloc_array_ptrtype(packer, packer->scratch, + packer->asize + 16); +} + +int packer_pack_from_subbitmaps(struct bitmap_packer *packer, + struct sub_bitmaps *b) +{ + packer->count = 0; + if (b->format == SUBBITMAP_EMPTY) + return 0; + packer_set_size(packer, b->num_parts); + int a = packer->padding; + for (int i = 0; i < b->num_parts; i++) + packer->in[i] = (struct pos){b->parts[i].w + a, b->parts[i].h + a}; + return packer_pack(packer); +} + +void packer_copy_subbitmaps(struct bitmap_packer *packer, struct sub_bitmaps *b, + void *data, int pixel_stride, int stride) +{ + assert(packer->count == b->num_parts); + if (packer->padding) { + struct pos bb[2]; + packer_get_bb(packer, bb); + memset_pic(data, 0, bb[1].x * pixel_stride, bb[1].y, stride); + } + for (int n = 0; n < packer->count; n++) { + struct sub_bitmap *s = &b->parts[n]; + struct pos p = packer->result[n]; + + void *pdata = (uint8_t *)data + p.y * stride + p.x * pixel_stride; + memcpy_pic(pdata, s->bitmap, s->w * pixel_stride, s->h, + stride, s->stride); + } +} diff --git a/video/out/bitmap_packer.h b/video/out/bitmap_packer.h new file mode 100644 index 0000000000..b86c3ec4f9 --- /dev/null +++ b/video/out/bitmap_packer.h @@ -0,0 +1,68 @@ +#ifndef MPLAYER_PACK_RECTANGLES_H +#define MPLAYER_PACK_RECTANGLES_H + +struct pos { + int x; + int y; +}; + +struct bitmap_packer { + int w; + int h; + int w_max; + int h_max; + int padding; + int count; + struct pos *in; + struct pos *result; + int used_width; + int used_height; + + // internal + int *scratch; + int asize; +}; + +struct ass_image; +struct sub_bitmaps; + +// Clear all internal state. Leave the following fields: w_max, h_max +void packer_reset(struct bitmap_packer *packer); + +// Get the bounding box used for bitmap data (including padding). +// The bounding box doesn't exceed (0,0)-(packer->w,packer->h). +void packer_get_bb(struct bitmap_packer *packer, struct pos out_bb[2]); + +/* Reallocate packer->in for at least to desired number of items. + * Also sets packer->count to the same value. + */ +void packer_set_size(struct bitmap_packer *packer, int size); + +/* To use this, set packer->count to number of rectangles, w_max and h_max + * to maximum output rectangle size, and w and h to start size (may be 0). + * Write input sizes in packer->in. + * Resulting packing will be written in packer->result. + * w and h will be increased if necessary for successful packing. + * There is a strong guarantee that w and h will be powers of 2 (or set to 0). + * Return value is -1 if packing failed because w and h were set to max + * values but that wasn't enough, 1 if w or h was increased, and 0 otherwise. + */ +int packer_pack(struct bitmap_packer *packer); + +/* Like above, but packer->count will be automatically set and + * packer->in will be reallocated if needed and filled from the + * given image list. + */ +int packer_pack_from_subbitmaps(struct bitmap_packer *packer, + struct sub_bitmaps *b); + +// Copy the (already packed) sub-bitmaps from b to the image in data. +// data must point to an image that is at least (packer->w, packer->h) big. +// The image has the given stride (bytes between (x, y) to (x, y + 1)), and the +// pixel format used by both the sub-bitmaps and the image uses pixel_stride +// bytes per pixel (bytes between (x, y) to (x + 1, y)). +// If packer->padding is set, the padding borders are cleared with 0. +void packer_copy_subbitmaps(struct bitmap_packer *packer, struct sub_bitmaps *b, + void *data, int pixel_stride, int stride); + +#endif diff --git a/video/out/cocoa_common.h b/video/out/cocoa_common.h new file mode 100644 index 0000000000..079e497441 --- /dev/null +++ b/video/out/cocoa_common.h @@ -0,0 +1,55 @@ +/* + * Cocoa OpenGL Backend + * + * This file is part of mplayer2. + * + * mplayer2 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mplayer2. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MPLAYER_COCOA_COMMON_H +#define MPLAYER_COCOA_COMMON_H + +#include "video_out.h" + +struct vo_cocoa_state; + +bool vo_cocoa_gui_running(void); +void *vo_cocoa_glgetaddr(const char *s); + +int vo_cocoa_init(struct vo *vo); +void vo_cocoa_uninit(struct vo *vo); + +void vo_cocoa_update_xinerama_info(struct vo *vo); + +int vo_cocoa_change_attributes(struct vo *vo); +int vo_cocoa_create_window(struct vo *vo, uint32_t d_width, + uint32_t d_height, uint32_t flags, + int gl3profile); + +void vo_cocoa_swap_buffers(struct vo *vo); +int vo_cocoa_check_events(struct vo *vo); +void vo_cocoa_fullscreen(struct vo *vo); +void vo_cocoa_ontop(struct vo *vo); +void vo_cocoa_pause(struct vo *vo); +void vo_cocoa_resume(struct vo *vo); + +// returns an int to conform to the gl extensions from other platforms +int vo_cocoa_swap_interval(int enabled); + +void *vo_cocoa_cgl_context(struct vo *vo); +void *vo_cocoa_cgl_pixel_format(struct vo *vo); + +int vo_cocoa_cgl_color_size(struct vo *vo); + +#endif /* MPLAYER_COCOA_COMMON_H */ diff --git a/video/out/cocoa_common.m b/video/out/cocoa_common.m new file mode 100644 index 0000000000..337e0a32be --- /dev/null +++ b/video/out/cocoa_common.m @@ -0,0 +1,865 @@ +/* + * Cocoa OpenGL Backend + * + * This file is part of mplayer2. + * + * mplayer2 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mplayer2. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Cocoa/Cocoa.h> +#import <CoreServices/CoreServices.h> // for CGDisplayHideCursor +#import <IOKit/pwr_mgt/IOPMLib.h> +#include <dlfcn.h> + +#include "cocoa_common.h" + +#include "config.h" + +#include "options.h" +#include "video_out.h" +#include "aspect.h" + +#include "mp_fifo.h" +#include "talloc.h" + +#include "input/input.h" +#include "input/keycodes.h" +#include "osx_common.h" +#include "mp_msg.h" + +#ifndef NSOpenGLPFAOpenGLProfile +#define NSOpenGLPFAOpenGLProfile 99 +#endif + +#ifndef NSOpenGLProfileVersionLegacy +#define NSOpenGLProfileVersionLegacy 0x1000 +#endif + +#ifndef NSOpenGLProfileVersion3_2Core +#define NSOpenGLProfileVersion3_2Core 0x3200 +#endif + +#define NSLeftAlternateKeyMask (0x000020 | NSAlternateKeyMask) +#define NSRightAlternateKeyMask (0x000040 | NSAlternateKeyMask) + +// add methods not available on OSX versions prior to 10.7 +#ifndef MAC_OS_X_VERSION_10_7 +@interface NSView (IntroducedInLion) +- (NSRect)convertRectToBacking:(NSRect)aRect; +- (void)setWantsBestResolutionOpenGLSurface:(BOOL)aBool; +@end +#endif + +// add power management assertion not available on OSX versions prior to 10.7 +#ifndef kIOPMAssertionTypePreventUserIdleDisplaySleep +#define kIOPMAssertionTypePreventUserIdleDisplaySleep \ + CFSTR("PreventUserIdleDisplaySleep") +#endif + +@interface GLMPlayerWindow : NSWindow <NSWindowDelegate> { + struct vo *_vo; +} +- (void)setVideoOutput:(struct vo *)vo; +- (BOOL)canBecomeKeyWindow; +- (BOOL)canBecomeMainWindow; +- (void)fullscreen; +- (void)mouseEvent:(NSEvent *)theEvent; +- (void)mulSize:(float)multiplier; +- (void)setContentSize:(NSSize)newSize keepCentered:(BOOL)keepCentered; +@end + +@interface GLMPlayerOpenGLView : NSView +@end + +struct vo_cocoa_state { + NSAutoreleasePool *pool; + GLMPlayerWindow *window; + NSOpenGLContext *glContext; + NSOpenGLPixelFormat *pixelFormat; + + NSSize current_video_size; + NSSize previous_video_size; + + NSRect screen_frame; + NSScreen *screen_handle; + NSArray *screen_array; + + NSInteger windowed_mask; + NSInteger fullscreen_mask; + + NSRect windowed_frame; + + NSString *window_title; + + NSInteger window_level; + NSInteger fullscreen_window_level; + + int display_cursor; + int cursor_timer; + int cursor_autohide_delay; + + bool did_resize; + bool out_fs_resize; + + IOPMAssertionID power_mgmt_assertion; +}; + +static int _instances = 0; + +static void create_menu(void); + +static struct vo_cocoa_state *vo_cocoa_init_state(struct vo *vo) +{ + struct vo_cocoa_state *s = talloc_ptrtype(vo, s); + *s = (struct vo_cocoa_state){ + .pool = [[NSAutoreleasePool alloc] init], + .did_resize = NO, + .current_video_size = {0,0}, + .previous_video_size = {0,0}, + .windowed_mask = NSTitledWindowMask|NSClosableWindowMask| + NSMiniaturizableWindowMask|NSResizableWindowMask, + .fullscreen_mask = NSBorderlessWindowMask, + .windowed_frame = {{0,0},{0,0}}, + .out_fs_resize = NO, + .display_cursor = 1, + .cursor_autohide_delay = vo->opts->cursor_autohide_delay, + .power_mgmt_assertion = kIOPMNullAssertionID, + }; + return s; +} + +static bool supports_hidpi(NSView *view) +{ + SEL hdpi_selector = @selector(setWantsBestResolutionOpenGLSurface:); + return is_osx_version_at_least(10, 7, 0) && view && + [view respondsToSelector:hdpi_selector]; +} + +bool vo_cocoa_gui_running(void) +{ + return _instances > 0; +} + +void *vo_cocoa_glgetaddr(const char *s) +{ + void *ret = NULL; + void *handle = dlopen( + "/System/Library/Frameworks/OpenGL.framework/OpenGL", + RTLD_LAZY | RTLD_LOCAL); + if (!handle) + return NULL; + ret = dlsym(handle, s); + dlclose(handle); + return ret; +} + +static void enable_power_management(struct vo *vo) +{ + struct vo_cocoa_state *s = vo->cocoa; + if (!s->power_mgmt_assertion) return; + IOPMAssertionRelease(s->power_mgmt_assertion); + s->power_mgmt_assertion = kIOPMNullAssertionID; +} + +static void disable_power_management(struct vo *vo) +{ + struct vo_cocoa_state *s = vo->cocoa; + if (s->power_mgmt_assertion) return; + + CFStringRef assertion_type = kIOPMAssertionTypeNoDisplaySleep; + if (is_osx_version_at_least(10, 7, 0)) + assertion_type = kIOPMAssertionTypePreventUserIdleDisplaySleep; + + IOPMAssertionCreateWithName(assertion_type, kIOPMAssertionLevelOn, + CFSTR("org.mplayer2.power_mgmt"), &s->power_mgmt_assertion); +} + +int vo_cocoa_init(struct vo *vo) +{ + vo->cocoa = vo_cocoa_init_state(vo); + _instances++; + + NSApplicationLoad(); + NSApp = [NSApplication sharedApplication]; + [NSApp setActivationPolicy: NSApplicationActivationPolicyRegular]; + disable_power_management(vo); + + return 1; +} + +void vo_cocoa_uninit(struct vo *vo) +{ + struct vo_cocoa_state *s = vo->cocoa; + CGDisplayShowCursor(kCGDirectMainDisplay); + enable_power_management(vo); + [NSApp setPresentationOptions:NSApplicationPresentationDefault]; + + [s->window release]; + s->window = nil; + [s->glContext release]; + s->glContext = nil; + [s->pool release]; + s->pool = nil; + + _instances--; +} + +void vo_cocoa_pause(struct vo *vo) +{ + enable_power_management(vo); +} + +void vo_cocoa_resume(struct vo *vo) +{ + disable_power_management(vo); +} + +static int current_screen_has_dock_or_menubar(struct vo *vo) +{ + struct vo_cocoa_state *s = vo->cocoa; + NSRect f = s->screen_frame; + NSRect vf = [s->screen_handle visibleFrame]; + return f.size.height > vf.size.height || f.size.width > vf.size.width; +} + +static void update_screen_info(struct vo *vo) +{ + struct vo_cocoa_state *s = vo->cocoa; + s->screen_array = [NSScreen screens]; + if (xinerama_screen >= (int)[s->screen_array count]) { + mp_msg(MSGT_VO, MSGL_INFO, "[cocoa] Device ID %d does not exist, " + "falling back to main device\n", xinerama_screen); + xinerama_screen = -1; + } + + if (xinerama_screen < 0) { // default behaviour + if (! (s->screen_handle = [s->window screen]) ) + s->screen_handle = [s->screen_array objectAtIndex:0]; + } else { + s->screen_handle = [s->screen_array objectAtIndex:(xinerama_screen)]; + } + + s->screen_frame = [s->screen_handle frame]; +} + +void vo_cocoa_update_xinerama_info(struct vo *vo) +{ + struct vo_cocoa_state *s = vo->cocoa; + struct MPOpts *opts = vo->opts; + + update_screen_info(vo); + aspect_save_screenres(vo, s->screen_frame.size.width, + s->screen_frame.size.height); + opts->vo_screenwidth = s->screen_frame.size.width; + opts->vo_screenheight = s->screen_frame.size.height; + xinerama_x = s->screen_frame.origin.x; + xinerama_y = s->screen_frame.origin.y; +} + +int vo_cocoa_change_attributes(struct vo *vo) +{ + return 0; +} + +static void resize_window(struct vo *vo) +{ + struct vo_cocoa_state *s = vo->cocoa; + NSView *view = [s->window contentView]; + NSRect frame; + + if (supports_hidpi(view)) { + frame = [view convertRectToBacking: [view frame]]; + } else { + frame = [view frame]; + } + + vo->dwidth = frame.size.width; + vo->dheight = frame.size.height; + [s->glContext update]; +} + +static void vo_set_level(struct vo *vo, int ontop) +{ + struct vo_cocoa_state *s = vo->cocoa; + if (ontop) { + s->window_level = NSNormalWindowLevel + 1; + } else { + s->window_level = NSNormalWindowLevel; + } + + if (!vo_fs) + [s->window setLevel:s->window_level]; +} + +void vo_cocoa_ontop(struct vo *vo) +{ + struct MPOpts *opts = vo->opts; + opts->vo_ontop = !opts->vo_ontop; + vo_set_level(vo, opts->vo_ontop); +} + +static void update_state_sizes(struct vo_cocoa_state *s, + uint32_t d_width, uint32_t d_height) +{ + if (s->current_video_size.width > 0 || s->current_video_size.height > 0) + s->previous_video_size = s->current_video_size; + s->current_video_size = NSMakeSize(d_width, d_height); +} + +static int create_window(struct vo *vo, uint32_t d_width, uint32_t d_height, + uint32_t flags, int gl3profile) +{ + struct vo_cocoa_state *s = vo->cocoa; + struct MPOpts *opts = vo->opts; + + const NSRect window_rect = NSMakeRect(0, 0, d_width, d_height); + const NSRect glview_rect = NSMakeRect(0, 0, 100, 100); + + s->window = + [[GLMPlayerWindow alloc] initWithContentRect:window_rect + styleMask:s->windowed_mask + backing:NSBackingStoreBuffered + defer:NO]; + + GLMPlayerOpenGLView *glView = + [[GLMPlayerOpenGLView alloc] initWithFrame:glview_rect]; + + // check for HiDPI support and enable it (available on 10.7 +) + if (supports_hidpi(glView)) + [glView setWantsBestResolutionOpenGLSurface:YES]; + + int i = 0; + NSOpenGLPixelFormatAttribute attr[32]; + if (is_osx_version_at_least(10, 7, 0)) { + attr[i++] = NSOpenGLPFAOpenGLProfile; + if (gl3profile) { + attr[i++] = NSOpenGLProfileVersion3_2Core; + } else { + attr[i++] = NSOpenGLProfileVersionLegacy; + } + } else if(gl3profile) { + mp_msg(MSGT_VO, MSGL_ERR, + "[cocoa] Invalid pixel format attribute " + "(GL3 is not supported on OSX versions prior to 10.7)\n"); + return -1; + } + attr[i++] = NSOpenGLPFADoubleBuffer; // double buffered + attr[i] = (NSOpenGLPixelFormatAttribute)0; + + s->pixelFormat = + [[[NSOpenGLPixelFormat alloc] initWithAttributes:attr] autorelease]; + if (!s->pixelFormat) { + mp_msg(MSGT_VO, MSGL_ERR, + "[cocoa] Invalid pixel format attribute " + "(GL3 not supported?)\n"); + return -1; + } + s->glContext = + [[NSOpenGLContext alloc] initWithFormat:s->pixelFormat + shareContext:nil]; + + create_menu(); + + [s->window setContentView:glView]; + [glView release]; + [s->window setAcceptsMouseMovedEvents:YES]; + [s->glContext setView:glView]; + [s->glContext makeCurrentContext]; + [s->window setVideoOutput:vo]; + + [NSApp setDelegate:s->window]; + [s->window setDelegate:s->window]; + [s->window setContentSize:s->current_video_size]; + [s->window setContentAspectRatio:s->current_video_size]; + [s->window setFrameOrigin:NSMakePoint(vo->dx, vo->dy)]; + + if (flags & VOFLAG_HIDDEN) { + [s->window orderOut:nil]; + } else { + [s->window makeKeyAndOrderFront:nil]; + [NSApp activateIgnoringOtherApps:YES]; + } + + if (flags & VOFLAG_FULLSCREEN) + vo_cocoa_fullscreen(vo); + + vo_set_level(vo, opts->vo_ontop); + + return 0; +} + +static void update_window(struct vo *vo) +{ + struct vo_cocoa_state *s = vo->cocoa; + + if (s->current_video_size.width != s->previous_video_size.width || + s->current_video_size.height != s->previous_video_size.height) { + if (vo_fs) { + // we will resize as soon as we get out of fullscreen + s->out_fs_resize = YES; + } else { + // only if we are not in fullscreen and the video size did + // change we resize the window and set a new aspect ratio + [s->window setContentSize:s->current_video_size + keepCentered:YES]; + [s->window setContentAspectRatio:s->current_video_size]; + } + } +} + +int vo_cocoa_create_window(struct vo *vo, uint32_t d_width, + uint32_t d_height, uint32_t flags, + int gl3profile) +{ + struct vo_cocoa_state *s = vo->cocoa; + + update_state_sizes(s, d_width, d_height); + + if (!(s->window || s->glContext)) { + if (create_window(vo, d_width, d_height, flags, gl3profile) < 0) + return -1; + } else { + update_window(vo); + } + + resize_window(vo); + + if (s->window_title) + [s->window_title release]; + + s->window_title = + [[NSString alloc] initWithUTF8String:vo_get_window_title(vo)]; + [s->window setTitle: s->window_title]; + + return 0; +} + +void vo_cocoa_swap_buffers(struct vo *vo) +{ + struct vo_cocoa_state *s = vo->cocoa; + [s->glContext flushBuffer]; +} + +static void vo_cocoa_display_cursor(struct vo *vo, int requested_state) +{ + struct vo_cocoa_state *s = vo->cocoa; + if (requested_state) { + if (!vo_fs || s->cursor_autohide_delay > -2) { + s->display_cursor = requested_state; + CGDisplayShowCursor(kCGDirectMainDisplay); + } + } else { + if (s->cursor_autohide_delay != -1) { + s->display_cursor = requested_state; + CGDisplayHideCursor(kCGDirectMainDisplay); + } + } +} + +int vo_cocoa_check_events(struct vo *vo) +{ + struct vo_cocoa_state *s = vo->cocoa; + NSEvent *event; + int ms_time = (int) ([[NSProcessInfo processInfo] systemUptime] * 1000); + + // automatically hide mouse cursor + if (vo_fs && s->display_cursor && + (ms_time - s->cursor_timer >= s->cursor_autohide_delay)) { + vo_cocoa_display_cursor(vo, 0); + s->cursor_timer = ms_time; + } + + event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:nil + inMode:NSEventTrackingRunLoopMode dequeue:YES]; + if (event == nil) + return 0; + [NSApp sendEvent:event]; + + if (s->did_resize) { + s->did_resize = NO; + resize_window(vo); + return VO_EVENT_RESIZE; + } + // Without SDL's bootstrap code (include SDL.h in mplayer.c), + // on Leopard, we have trouble to get the play window automatically focused + // when the app is actived. The Following code fix this problem. + if ([event type] == NSAppKitDefined + && [event subtype] == NSApplicationActivatedEventType) { + [s->window makeMainWindow]; + [s->window makeKeyAndOrderFront:nil]; + } + return 0; +} + +void vo_cocoa_fullscreen(struct vo *vo) +{ + struct vo_cocoa_state *s = vo->cocoa; + [s->window fullscreen]; + resize_window(vo); +} + +int vo_cocoa_swap_interval(int enabled) +{ + [[NSOpenGLContext currentContext] setValues:&enabled + forParameter:NSOpenGLCPSwapInterval]; + return 0; +} + +void *vo_cocoa_cgl_context(struct vo *vo) +{ + struct vo_cocoa_state *s = vo->cocoa; + return [s->glContext CGLContextObj]; +} + +void *vo_cocoa_cgl_pixel_format(struct vo *vo) +{ + return CGLGetPixelFormat(vo_cocoa_cgl_context(vo)); +} + +int vo_cocoa_cgl_color_size(struct vo *vo) +{ + GLint value; + CGLDescribePixelFormat(vo_cocoa_cgl_pixel_format(vo), 0, + kCGLPFAColorSize, &value); + switch (value) { + case 32: + case 24: + return 8; + case 16: + return 5; + } + + return 8; +} + +static NSMenuItem *new_menu_item(NSMenu *parent_menu, NSString *title, + SEL action, NSString *key_equivalent) +{ + NSMenuItem *new_item = + [[NSMenuItem alloc] initWithTitle:title action:action + keyEquivalent:key_equivalent]; + [parent_menu addItem:new_item]; + return [new_item autorelease]; +} + +static NSMenuItem *new_main_menu_item(NSMenu *parent_menu, NSMenu *child_menu, + NSString *title) +{ + NSMenuItem *new_item = + [[NSMenuItem alloc] initWithTitle:title action:nil + keyEquivalent:@""]; + [new_item setSubmenu:child_menu]; + [parent_menu addItem:new_item]; + return [new_item autorelease]; +} + +void create_menu() +{ + NSAutoreleasePool *pool = [NSAutoreleasePool new]; + NSMenu *main_menu, *m_menu, *w_menu; + NSMenuItem *app_menu_item; + + main_menu = [[NSMenu new] autorelease]; + app_menu_item = [[NSMenuItem new] autorelease]; + [main_menu addItem:app_menu_item]; + [NSApp setMainMenu: main_menu]; + + m_menu = [[[NSMenu alloc] initWithTitle:@"Movie"] autorelease]; + new_menu_item(m_menu, @"Half Size", @selector(halfSize), @"0"); + new_menu_item(m_menu, @"Normal Size", @selector(normalSize), @"1"); + new_menu_item(m_menu, @"Double Size", @selector(doubleSize), @"2"); + + new_main_menu_item(main_menu, m_menu, @"Movie"); + + w_menu = [[[NSMenu alloc] initWithTitle:@"Window"] autorelease]; + new_menu_item(w_menu, @"Minimize", @selector(performMiniaturize:), @"m"); + new_menu_item(w_menu, @"Zoom", @selector(performZoom:), @"z"); + + new_main_menu_item(main_menu, w_menu, @"Window"); + [pool release]; +} + +@implementation GLMPlayerWindow +- (void)setVideoOutput:(struct vo *)vo +{ + _vo = vo; +} + +- (void)windowDidResize:(NSNotification *) notification +{ + if (_vo) { + struct vo_cocoa_state *s = _vo->cocoa; + s->did_resize = YES; + } +} + +- (void)fullscreen +{ + struct vo_cocoa_state *s = _vo->cocoa; + if (!vo_fs) { + update_screen_info(_vo); + if (current_screen_has_dock_or_menubar(_vo)) + [NSApp setPresentationOptions:NSApplicationPresentationHideDock| + NSApplicationPresentationHideMenuBar]; + s->windowed_frame = [self frame]; + [self setHasShadow:NO]; + [self setStyleMask:s->fullscreen_mask]; + [self setFrame:s->screen_frame display:YES animate:NO]; + vo_fs = VO_TRUE; + vo_cocoa_display_cursor(_vo, 0); + [self setMovableByWindowBackground: NO]; + } else { + [NSApp setPresentationOptions:NSApplicationPresentationDefault]; + [self setHasShadow:YES]; + [self setStyleMask:s->windowed_mask]; + [self setTitle:s->window_title]; + [self setFrame:s->windowed_frame display:YES animate:NO]; + if (s->out_fs_resize) { + [self setContentSize:s->current_video_size keepCentered:YES]; + s->out_fs_resize = NO; + } + [self setContentAspectRatio:s->current_video_size]; + vo_fs = VO_FALSE; + vo_cocoa_display_cursor(_vo, 1); + [self setMovableByWindowBackground: YES]; + } +} + +- (BOOL)canBecomeMainWindow { return YES; } +- (BOOL)canBecomeKeyWindow { return YES; } +- (BOOL)acceptsFirstResponder { return YES; } +- (BOOL)becomeFirstResponder { return YES; } +- (BOOL)resignFirstResponder { return YES; } +- (BOOL)windowShouldClose:(id)sender +{ + mplayer_put_key(_vo->key_fifo, KEY_CLOSE_WIN); + // We have to wait for MPlayer to handle this, + // otherwise we are in trouble if the + // KEY_CLOSE_WIN handler is disabled + return NO; +} + +- (BOOL)isMovableByWindowBackground +{ + // this is only valid as a starting value. it will be rewritten in the + // -fullscreen method. + return !vo_fs; +} + +- (void)handleQuitEvent:(NSAppleEventDescriptor*)e + withReplyEvent:(NSAppleEventDescriptor*)r +{ + mplayer_put_key(_vo->key_fifo, KEY_CLOSE_WIN); +} + +- (void)keyDown:(NSEvent *)theEvent +{ + unsigned char charcode; + if (([theEvent modifierFlags] & NSRightAlternateKeyMask) == + NSRightAlternateKeyMask) + charcode = *[[theEvent characters] UTF8String]; + else + charcode = [[theEvent charactersIgnoringModifiers] characterAtIndex:0]; + + int key = convert_key([theEvent keyCode], charcode); + + if (key > -1) { + if ([theEvent modifierFlags] & NSShiftKeyMask) + key |= KEY_MODIFIER_SHIFT; + if ([theEvent modifierFlags] & NSControlKeyMask) + key |= KEY_MODIFIER_CTRL; + if (([theEvent modifierFlags] & NSLeftAlternateKeyMask) == + NSLeftAlternateKeyMask) + key |= KEY_MODIFIER_ALT; + if ([theEvent modifierFlags] & NSCommandKeyMask) + key |= KEY_MODIFIER_META; + mplayer_put_key(_vo->key_fifo, key); + } +} + +- (void)mouseMoved: (NSEvent *) theEvent +{ + if (vo_fs) + vo_cocoa_display_cursor(_vo, 1); +} + +- (void)mouseDragged:(NSEvent *)theEvent +{ + [self mouseEvent: theEvent]; +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + [self mouseEvent: theEvent]; +} + +- (void)mouseUp:(NSEvent *)theEvent +{ + [self mouseEvent: theEvent]; +} + +- (void)rightMouseDown:(NSEvent *)theEvent +{ + [self mouseEvent: theEvent]; +} + +- (void)rightMouseUp:(NSEvent *)theEvent +{ + [self mouseEvent: theEvent]; +} + +- (void)otherMouseDown:(NSEvent *)theEvent +{ + [self mouseEvent: theEvent]; +} + +- (void)otherMouseUp:(NSEvent *)theEvent +{ + [self mouseEvent: theEvent]; +} + +- (void)scrollWheel:(NSEvent *)theEvent +{ + if ([theEvent deltaY] > 0) + mplayer_put_key(_vo->key_fifo, MOUSE_BTN3); + else + mplayer_put_key(_vo->key_fifo, MOUSE_BTN4); +} + +- (void)mouseEvent:(NSEvent *)theEvent +{ + if ([theEvent buttonNumber] >= 0 && [theEvent buttonNumber] <= 9) { + int buttonNumber = [theEvent buttonNumber]; + // Fix to mplayer defined button order: left, middle, right + if (buttonNumber == 1) buttonNumber = 2; + else if (buttonNumber == 2) buttonNumber = 1; + switch ([theEvent type]) { + case NSLeftMouseDown: + case NSRightMouseDown: + case NSOtherMouseDown: + mplayer_put_key(_vo->key_fifo, + (MOUSE_BTN0 + buttonNumber) | MP_KEY_DOWN); + // Looks like Cocoa doesn't create MouseUp events when we are + // doing the second click in a double click. Put in the key_fifo + // the key that would be put from the MouseUp handling code. + if([theEvent clickCount] == 2) + mplayer_put_key(_vo->key_fifo, MOUSE_BTN0 + buttonNumber); + break; + case NSLeftMouseUp: + case NSRightMouseUp: + case NSOtherMouseUp: + mplayer_put_key(_vo->key_fifo, MOUSE_BTN0 + buttonNumber); + break; + } + } +} + +- (void)applicationWillBecomeActive:(NSNotification *)aNotification +{ + if (vo_fs && current_screen_has_dock_or_menubar(_vo)) { + [NSApp setPresentationOptions:NSApplicationPresentationHideDock| + NSApplicationPresentationHideMenuBar]; + } +} + +- (void)applicationWillResignActive:(NSNotification *)aNotification +{ + if (vo_fs) { + [NSApp setPresentationOptions:NSApplicationPresentationDefault]; + } +} + +- (void)applicationDidFinishLaunching:(NSNotification*)notification +{ + // Install an event handler so the Quit menu entry works + // The proper way using NSApp setDelegate: and + // applicationShouldTerminate: does not work, + // probably NSApplication never installs its handler. + [[NSAppleEventManager sharedAppleEventManager] + setEventHandler:self + andSelector:@selector(handleQuitEvent:withReplyEvent:) + forEventClass:kCoreEventClass + andEventID:kAEQuitApplication]; +} + +- (void)normalSize +{ + struct vo_cocoa_state *s = _vo->cocoa; + if (!vo_fs) + [self setContentSize:s->current_video_size keepCentered:YES]; +} + +- (void)halfSize { [self mulSize:0.5f];} + +- (void)doubleSize { [self mulSize:2.0f];} + +- (void)mulSize:(float)multiplier +{ + if (!vo_fs) { + struct vo_cocoa_state *s = _vo->cocoa; + NSSize size = [[self contentView] frame].size; + size.width = s->current_video_size.width * (multiplier); + size.height = s->current_video_size.height * (multiplier); + [self setContentSize:size keepCentered:YES]; + } +} + +- (void)setCenteredContentSize:(NSSize)ns +{ + NSRect nf = [self frame]; + NSRect vf = [[self screen] visibleFrame]; + NSRect cb = [[self contentView] bounds]; + int title_height = nf.size.height - cb.size.height; + double ratio = (double)ns.width / (double)ns.height; + + // clip the new size to the visibleFrame's size if needed + if (ns.width > vf.size.width || ns.height + title_height > vf.size.height) { + ns = vf.size; + ns.height -= title_height; // make space for the title bar + + if (ns.width > ns.height) { + ns.height = ((double)ns.width * 1/ratio + 0.5); + } else { + ns.width = ((double)ns.height * ratio + 0.5); + } + } + + int dw = nf.size.width - ns.width; + int dh = nf.size.height - ns.height - title_height; + + nf.origin.x += dw / 2; + nf.origin.y += dh / 2; + + NSRect new_frame = + NSMakeRect(nf.origin.x, nf.origin.y, ns.width, ns.height + title_height); + [self setFrame:new_frame display:YES animate:NO]; +} + +- (void)setContentSize:(NSSize)ns keepCentered:(BOOL)keepCentered +{ + if (keepCentered) { + [self setCenteredContentSize:ns]; + } else { + [self setContentSize:ns]; + } +} +@end + +@implementation GLMPlayerOpenGLView +- (void)drawRect: (NSRect)rect +{ + [[NSColor clearColor] set]; + NSRectFill([self bounds]); +} +@end diff --git a/video/out/d3d_shader_yuv.h b/video/out/d3d_shader_yuv.h new file mode 100644 index 0000000000..49ef753b4c --- /dev/null +++ b/video/out/d3d_shader_yuv.h @@ -0,0 +1,142 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.27.952.3022 +// +// fxc /Tps_2_0 /Fhz:\tmp\mplayer\libvo\d3d_shader_yuv.h +// z:\tmp\mplayer\libvo\d3d_shader_yuv.hlsl /Vnd3d_shader_yuv +// +// +// Parameters: +// +// float4x4 colormatrix; +// sampler2D tex0; +// sampler2D tex1; +// sampler2D tex2; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// colormatrix c0 4 +// tex0 s0 1 +// tex1 s1 1 +// tex2 s2 1 +// + + ps_2_0 + def c4, 1, 0, 0, 0 + dcl t0.xy + dcl t1.xy + dcl t2.xy + dcl_2d s0 + dcl_2d s1 + dcl_2d s2 + texld r0, t0, s0 + texld r1, t1, s1 + texld r2, t2, s2 + mov r0.y, r1.x + mov r0.z, r2.x + mov r0.w, c4.x + dp4 r1.x, r0, c0 + dp4 r1.y, r0, c1 + dp4 r1.z, r0, c2 + dp4 r1.w, r0, c3 + mov oC0, r1 + +// approximately 11 instruction slots used (3 texture, 8 arithmetic) +#endif + +const BYTE d3d_shader_yuv[] = +{ + 0, 2, 255, 255, 254, 255, + 67, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 215, 0, + 0, 0, 0, 2, 255, 255, + 4, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 208, 0, 0, 0, 108, 0, + 0, 0, 2, 0, 0, 0, + 4, 0, 2, 0, 120, 0, + 0, 0, 0, 0, 0, 0, + 136, 0, 0, 0, 3, 0, + 0, 0, 1, 0, 2, 0, + 144, 0, 0, 0, 0, 0, + 0, 0, 160, 0, 0, 0, + 3, 0, 1, 0, 1, 0, + 6, 0, 168, 0, 0, 0, + 0, 0, 0, 0, 184, 0, + 0, 0, 3, 0, 2, 0, + 1, 0, 10, 0, 192, 0, + 0, 0, 0, 0, 0, 0, + 99, 111, 108, 111, 114, 109, + 97, 116, 114, 105, 120, 0, + 3, 0, 3, 0, 4, 0, + 4, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 116, 101, + 120, 48, 0, 171, 171, 171, + 4, 0, 12, 0, 1, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 116, 101, + 120, 49, 0, 171, 171, 171, + 4, 0, 12, 0, 1, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 116, 101, + 120, 50, 0, 171, 171, 171, + 4, 0, 12, 0, 1, 0, + 1, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 112, 115, + 95, 50, 95, 48, 0, 77, + 105, 99, 114, 111, 115, 111, + 102, 116, 32, 40, 82, 41, + 32, 72, 76, 83, 76, 32, + 83, 104, 97, 100, 101, 114, + 32, 67, 111, 109, 112, 105, + 108, 101, 114, 32, 57, 46, + 50, 55, 46, 57, 53, 50, + 46, 51, 48, 50, 50, 0, + 81, 0, 0, 5, 4, 0, + 15, 160, 0, 0, 128, 63, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 31, 0, 0, 2, 0, 0, + 0, 128, 0, 0, 3, 176, + 31, 0, 0, 2, 0, 0, + 0, 128, 1, 0, 3, 176, + 31, 0, 0, 2, 0, 0, + 0, 128, 2, 0, 3, 176, + 31, 0, 0, 2, 0, 0, + 0, 144, 0, 8, 15, 160, + 31, 0, 0, 2, 0, 0, + 0, 144, 1, 8, 15, 160, + 31, 0, 0, 2, 0, 0, + 0, 144, 2, 8, 15, 160, + 66, 0, 0, 3, 0, 0, + 15, 128, 0, 0, 228, 176, + 0, 8, 228, 160, 66, 0, + 0, 3, 1, 0, 15, 128, + 1, 0, 228, 176, 1, 8, + 228, 160, 66, 0, 0, 3, + 2, 0, 15, 128, 2, 0, + 228, 176, 2, 8, 228, 160, + 1, 0, 0, 2, 0, 0, + 2, 128, 1, 0, 0, 128, + 1, 0, 0, 2, 0, 0, + 4, 128, 2, 0, 0, 128, + 1, 0, 0, 2, 0, 0, + 8, 128, 4, 0, 0, 160, + 9, 0, 0, 3, 1, 0, + 1, 128, 0, 0, 228, 128, + 0, 0, 228, 160, 9, 0, + 0, 3, 1, 0, 2, 128, + 0, 0, 228, 128, 1, 0, + 228, 160, 9, 0, 0, 3, + 1, 0, 4, 128, 0, 0, + 228, 128, 2, 0, 228, 160, + 9, 0, 0, 3, 1, 0, + 8, 128, 0, 0, 228, 128, + 3, 0, 228, 160, 1, 0, + 0, 2, 0, 8, 15, 128, + 1, 0, 228, 128, 255, 255, + 0, 0 +}; diff --git a/video/out/d3d_shader_yuv.hlsl b/video/out/d3d_shader_yuv.hlsl new file mode 100644 index 0000000000..b17e257210 --- /dev/null +++ b/video/out/d3d_shader_yuv.hlsl @@ -0,0 +1,44 @@ +// Compile with: +// fxc.exe /Tps_2_0 /Fhd3d_shader_yuv.h d3d_shader_yuv.hlsl /Vnd3d_shader_yuv +// fxc.exe /Tps_2_0 /Fhd3d_shader_yuv_2ch.h d3d_shader_yuv.hlsl /Vnd3d_shader_yuv_2ch /DUSE_2CH=1 + +// Be careful with this shader. You can't use constant slots, since we don't +// load the shader with D3DX. All uniform variables are mapped to hardcoded +// constant slots. + +sampler2D tex0 : register(s0); +sampler2D tex1 : register(s1); +sampler2D tex2 : register(s2); + +uniform float4x4 colormatrix : register(c0); +uniform float2 depth : register(c5); + +#ifdef USE_2CH + +float1 sample(sampler2D tex, float2 t) +{ + // Sample from A8L8 format as if we sampled a single value from L16. + // We compute the 2 channel values back into one. + return dot(tex2D(tex, t).xw, depth); +} + +#else + +float1 sample(sampler2D tex, float2 t) +{ + return tex2D(tex, t).x; +} + +#endif + +float4 main(float2 t0 : TEXCOORD0, + float2 t1 : TEXCOORD1, + float2 t2 : TEXCOORD2) + : COLOR +{ + float4 c = float4(sample(tex0, t0), + sample(tex1, t1), + sample(tex2, t2), + 1); + return mul(c, colormatrix); +} diff --git a/video/out/d3d_shader_yuv_2ch.h b/video/out/d3d_shader_yuv_2ch.h new file mode 100644 index 0000000000..45dcc73992 --- /dev/null +++ b/video/out/d3d_shader_yuv_2ch.h @@ -0,0 +1,170 @@ +#if 0 +// +// Generated by Microsoft (R) HLSL Shader Compiler 9.27.952.3022 +// +// fxc /Tps_2_0 /Fhz:\tmp\mplayer\libvo\d3d_shader_yuv_2ch.h +// z:\tmp\mplayer\libvo\d3d_shader_yuv.hlsl /Vnd3d_shader_yuv_2ch +// /DUSE_2CH=1 +// +// +// Parameters: +// +// float4x4 colormatrix; +// float2 depth; +// sampler2D tex0; +// sampler2D tex1; +// sampler2D tex2; +// +// +// Registers: +// +// Name Reg Size +// ------------ ----- ---- +// colormatrix c0 4 +// depth c5 1 +// tex0 s0 1 +// tex1 s1 1 +// tex2 s2 1 +// + + ps_2_0 + def c4, 1, 0, 0, 0 + dcl t0.xy + dcl t1.xy + dcl t2.xy + dcl_2d s0 + dcl_2d s1 + dcl_2d s2 + texld r0, t0, s0 + texld r1, t1, s1 + texld r2, t2, s2 + mul r0.x, r0.x, c5.x + mad r0.x, r0.w, c5.y, r0.x + mul r1.x, r1.x, c5.x + mad r0.y, r1.w, c5.y, r1.x + mul r1.x, r2.x, c5.x + mad r0.z, r2.w, c5.y, r1.x + mov r0.w, c4.x + dp4 r1.x, r0, c0 + dp4 r1.y, r0, c1 + dp4 r1.z, r0, c2 + dp4 r1.w, r0, c3 + mov oC0, r1 + +// approximately 15 instruction slots used (3 texture, 12 arithmetic) +#endif + +const BYTE d3d_shader_yuv_2ch[] = +{ + 0, 2, 255, 255, 254, 255, + 78, 0, 67, 84, 65, 66, + 28, 0, 0, 0, 3, 1, + 0, 0, 0, 2, 255, 255, + 5, 0, 0, 0, 28, 0, + 0, 0, 0, 1, 0, 0, + 252, 0, 0, 0, 128, 0, + 0, 0, 2, 0, 0, 0, + 4, 0, 2, 0, 140, 0, + 0, 0, 0, 0, 0, 0, + 156, 0, 0, 0, 2, 0, + 5, 0, 1, 0, 22, 0, + 164, 0, 0, 0, 0, 0, + 0, 0, 180, 0, 0, 0, + 3, 0, 0, 0, 1, 0, + 2, 0, 188, 0, 0, 0, + 0, 0, 0, 0, 204, 0, + 0, 0, 3, 0, 1, 0, + 1, 0, 6, 0, 212, 0, + 0, 0, 0, 0, 0, 0, + 228, 0, 0, 0, 3, 0, + 2, 0, 1, 0, 10, 0, + 236, 0, 0, 0, 0, 0, + 0, 0, 99, 111, 108, 111, + 114, 109, 97, 116, 114, 105, + 120, 0, 3, 0, 3, 0, + 4, 0, 4, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 100, 101, 112, 116, 104, 0, + 171, 171, 1, 0, 3, 0, + 1, 0, 2, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 116, 101, 120, 48, 0, 171, + 171, 171, 4, 0, 12, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 116, 101, 120, 49, 0, 171, + 171, 171, 4, 0, 12, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 116, 101, 120, 50, 0, 171, + 171, 171, 4, 0, 12, 0, + 1, 0, 1, 0, 1, 0, + 0, 0, 0, 0, 0, 0, + 112, 115, 95, 50, 95, 48, + 0, 77, 105, 99, 114, 111, + 115, 111, 102, 116, 32, 40, + 82, 41, 32, 72, 76, 83, + 76, 32, 83, 104, 97, 100, + 101, 114, 32, 67, 111, 109, + 112, 105, 108, 101, 114, 32, + 57, 46, 50, 55, 46, 57, + 53, 50, 46, 51, 48, 50, + 50, 0, 81, 0, 0, 5, + 4, 0, 15, 160, 0, 0, + 128, 63, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 31, 0, 0, 2, + 0, 0, 0, 128, 0, 0, + 3, 176, 31, 0, 0, 2, + 0, 0, 0, 128, 1, 0, + 3, 176, 31, 0, 0, 2, + 0, 0, 0, 128, 2, 0, + 3, 176, 31, 0, 0, 2, + 0, 0, 0, 144, 0, 8, + 15, 160, 31, 0, 0, 2, + 0, 0, 0, 144, 1, 8, + 15, 160, 31, 0, 0, 2, + 0, 0, 0, 144, 2, 8, + 15, 160, 66, 0, 0, 3, + 0, 0, 15, 128, 0, 0, + 228, 176, 0, 8, 228, 160, + 66, 0, 0, 3, 1, 0, + 15, 128, 1, 0, 228, 176, + 1, 8, 228, 160, 66, 0, + 0, 3, 2, 0, 15, 128, + 2, 0, 228, 176, 2, 8, + 228, 160, 5, 0, 0, 3, + 0, 0, 1, 128, 0, 0, + 0, 128, 5, 0, 0, 160, + 4, 0, 0, 4, 0, 0, + 1, 128, 0, 0, 255, 128, + 5, 0, 85, 160, 0, 0, + 0, 128, 5, 0, 0, 3, + 1, 0, 1, 128, 1, 0, + 0, 128, 5, 0, 0, 160, + 4, 0, 0, 4, 0, 0, + 2, 128, 1, 0, 255, 128, + 5, 0, 85, 160, 1, 0, + 0, 128, 5, 0, 0, 3, + 1, 0, 1, 128, 2, 0, + 0, 128, 5, 0, 0, 160, + 4, 0, 0, 4, 0, 0, + 4, 128, 2, 0, 255, 128, + 5, 0, 85, 160, 1, 0, + 0, 128, 1, 0, 0, 2, + 0, 0, 8, 128, 4, 0, + 0, 160, 9, 0, 0, 3, + 1, 0, 1, 128, 0, 0, + 228, 128, 0, 0, 228, 160, + 9, 0, 0, 3, 1, 0, + 2, 128, 0, 0, 228, 128, + 1, 0, 228, 160, 9, 0, + 0, 3, 1, 0, 4, 128, + 0, 0, 228, 128, 2, 0, + 228, 160, 9, 0, 0, 3, + 1, 0, 8, 128, 0, 0, + 228, 128, 3, 0, 228, 160, + 1, 0, 0, 2, 0, 8, + 15, 128, 1, 0, 228, 128, + 255, 255, 0, 0 +}; diff --git a/video/out/filter_kernels.c b/video/out/filter_kernels.c new file mode 100644 index 0000000000..2c2f56ee51 --- /dev/null +++ b/video/out/filter_kernels.c @@ -0,0 +1,279 @@ +/* + * This file is part of mplayer2. + * + * Most code for computing the weights is taken from Anti-Grain Geometry (AGG) + * (licensed under GPL 2 or later), with modifications. + * Copyright (C) 2002-2006 Maxim Shemanarev + * http://vector-agg.cvs.sourceforge.net/viewvc/vector-agg/agg-2.5/include/agg_image_filters.h?view=markup + * + * Also see glumpy (BSD licensed), contains the same code in Python: + * http://code.google.com/p/glumpy/source/browse/glumpy/image/filter.py + * + * Also see: Paul Heckbert's "zoom" + * + * Also see XBMC: ConvolutionKernels.cpp etc. + * + * mplayer2 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mplayer2; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stddef.h> +#include <string.h> +#include <math.h> +#include <assert.h> + +#include "filter_kernels.h" + +// NOTE: all filters are separable, symmetric, and are intended for use with +// a lookup table/texture. + +const struct filter_kernel *mp_find_filter_kernel(const char *name) +{ + for (const struct filter_kernel *k = mp_filter_kernels; k->name; k++) { + if (strcmp(k->name, name) == 0) + return k; + } + return NULL; +} + +// sizes = sorted list of available filter sizes, terminated with size 0 +// inv_scale = source_size / dest_size +bool mp_init_filter(struct filter_kernel *filter, const int *sizes, + double inv_scale) +{ + // only downscaling requires widening the filter + filter->inv_scale = inv_scale >= 1.0 ? inv_scale : 1.0; + double support = filter->radius * filter->inv_scale; + int size = ceil(2.0 * support); + // round up to smallest available size that's still large enough + if (size < sizes[0]) + size = sizes[0]; + const int *cursize = sizes; + while (size > *cursize && *cursize) + cursize++; + if (*cursize) { + filter->size = *cursize; + return true; + } else { + // The filter doesn't fit - instead of failing completely, use the + // largest filter available. This is incorrect, but better than refusing + // to do anything. + filter->size = cursize[-1]; + filter->inv_scale = filter->size / 2.0 / filter->radius; + return false; + } +} + +// Calculate the 1D filtering kernel for N sample points. +// N = number of samples, which is filter->size +// The weights will be stored in out_w[0] to out_w[N - 1] +// f = x0 - abs(x0), subpixel position in the range [0,1) or [0,1]. +void mp_compute_weights(struct filter_kernel *filter, double f, float *out_w) +{ + assert(filter->size > 0); + double sum = 0; + for (int n = 0; n < filter->size; n++) { + double x = f - (n - filter->size / 2 + 1); + double w = filter->weight(filter, fabs(x) / filter->inv_scale); + out_w[n] = w; + sum += w; + } + //normalize + for (int n = 0; n < filter->size; n++) + out_w[n] /= sum; +} + +// Fill the given array with weights for the range [0.0, 1.0]. The array is +// interpreted as rectangular array of count * filter->size items. +void mp_compute_lut(struct filter_kernel *filter, int count, float *out_array) +{ + for (int n = 0; n < count; n++) { + mp_compute_weights(filter, n / (double)(count - 1), + out_array + filter->size * n); + } +} + +typedef struct filter_kernel kernel; + +static double bilinear(kernel *k, double x) +{ + return 1.0 - x; +} + +static double hanning(kernel *k, double x) +{ + return 0.5 + 0.5 * cos(M_PI * x); +} + +static double hamming(kernel *k, double x) +{ + return 0.54 + 0.46 * cos(M_PI * x); +} + +static double hermite(kernel *k, double x) +{ + return (2.0 * x - 3.0) * x * x + 1.0; +} + +static double quadric(kernel *k, double x) +{ + // NOTE: glumpy uses 0.75, AGG uses 0.5 + if (x < 0.5) + return 0.75 - x * x; + if (x < 1.5) + return 0.5 * (x - 1.5) * (x - 1.5); + return 0; +} + +static double bc_pow3(double x) +{ + return (x <= 0) ? 0 : x * x * x; +} + +static double bicubic(kernel *k, double x) +{ + return (1.0/6.0) * ( bc_pow3(x + 2) + - 4 * bc_pow3(x + 1) + + 6 * bc_pow3(x) + - 4 * bc_pow3(x - 1)); +} + +static double bessel_i0(double epsilon, double x) +{ + double sum = 1; + double y = x * x / 4; + double t = y; + for (int i = 2; t > epsilon; i++) { + sum += t; + t *= y / (i * i); + } + return sum; +} + +static double kaiser(kernel *k, double x) +{ + double a = k->params[0]; + double b = k->params[1]; + double epsilon = 1e-12; + double i0a = 1 / bessel_i0(epsilon, b); + return bessel_i0(epsilon, a * sqrt(1 - x * x)) * i0a; +} + +static double catmull_rom(kernel *k, double x) +{ + if (x < 1.0) + return 0.5 * (2.0 + x * x * (-5.0 + x * 3.0)); + if (x < 2.0) + return 0.5 * (4.0 + x * (-8.0 + x * (5.0 - x))); + return 0; +} + +// Mitchell-Netravali +static double mitchell(kernel *k, double x) +{ + double b = k->params[0]; + double c = k->params[1]; + double + p0 = (6.0 - 2.0 * b) / 6.0, + p2 = (-18.0 + 12.0 * b + 6.0 * c) / 6.0, + p3 = (12.0 - 9.0 * b - 6.0 * c) / 6.0, + q0 = (8.0 * b + 24.0 * c) / 6.0, + q1 = (-12.0 * b - 48.0 * c) / 6.0, + q2 = (6.0 * b + 30.0 * c) / 6.0, + q3 = (-b - 6.0 * c) / 6.0; + if (x < 1.0) + return p0 + x * x * (p2 + x * p3); + if (x < 2.0) + return q0 + x * (q1 + x * (q2 + x * q3)); + return 0; +} + +static double spline16(kernel *k, double x) +{ + if (x < 1.0) + return ((x - 9.0/5.0 ) * x - 1.0/5.0 ) * x + 1.0; + return ((-1.0/3.0 * (x-1) + 4.0/5.0) * (x-1) - 7.0/15.0 ) * (x-1); +} + +static double spline36(kernel *k, double x) +{ + if(x < 1.0) + return ((13.0/11.0 * x - 453.0/209.0) * x - 3.0/209.0) * x + 1.0; + if(x < 2.0) + return ((-6.0/11.0 * (x - 1) + 270.0/209.0) * (x - 1) - 156.0/209.0) + * (x - 1); + return ((1.0/11.0 * (x - 2) - 45.0/209.0) * (x - 2) + 26.0/209.0) + * (x - 2); +} + +static double gaussian(kernel *k, double x) +{ + return exp(-2.0 * x * x) * sqrt(2.0 / M_PI); +} + +static double sinc(kernel *k, double x) +{ + if (x == 0.0) + return 1.0; + double pix = M_PI * x; + return sin(pix) / pix; +} + +static double lanczos(kernel *k, double x) +{ + double radius = k->size / 2; + if (x < -radius || x > radius) + return 0; + if (x == 0) + return 1; + double pix = M_PI * x; + return radius * sin(pix) * sin(pix / radius) / (pix * pix); +} + +static double blackman(kernel *k, double x) +{ + double radius = k->size / 2; + if (x == 0.0) + return 1.0; + if (x > radius) + return 0.0; + x *= M_PI; + double xr = x / radius; + return (sin(x) / x) * (0.42 + 0.5 * cos(xr) + 0.08 * cos(2 * xr)); +} + +const struct filter_kernel mp_filter_kernels[] = { + {"bilinear_slow", 1, bilinear}, + {"hanning", 1, hanning}, + {"hamming", 1, hamming}, + {"hermite", 1, hermite}, + {"quadric", 1.5, quadric}, + {"bicubic", 2, bicubic}, + {"kaiser", 1, kaiser, .params = {6.33, 6.33} }, + {"catmull_rom", 2, catmull_rom}, + {"mitchell", 2, mitchell, .params = {1.0/3.0, 1.0/3.0} }, + {"spline16", 2, spline16}, + {"spline36", 3, spline36}, + {"gaussian", 2, gaussian}, + {"sinc2", 2, sinc}, + {"sinc3", 3, sinc}, + {"sinc4", 4, sinc}, + {"lanczos2", 2, lanczos}, + {"lanczos3", 3, lanczos}, + {"lanczos4", 4, lanczos}, + {"blackman2", 2, blackman}, + {"blackman3", 3, blackman}, + {"blackman4", 4, blackman}, + {0} +}; diff --git a/video/out/filter_kernels.h b/video/out/filter_kernels.h new file mode 100644 index 0000000000..46a392c40a --- /dev/null +++ b/video/out/filter_kernels.h @@ -0,0 +1,45 @@ +/* + * This file is part of mplayer2. + * + * mplayer2 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mplayer2; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_FILTER_KERNELS_H +#define MPLAYER_FILTER_KERNELS_H + +#include <stdbool.h> + +struct filter_kernel { + const char *name; + double radius; + double (*weight)(struct filter_kernel *kernel, double x); + + // The filter params can be changed at runtime. Only used by some filters. + float params[2]; + // The following values are set by mp_init_filter() at runtime. + // Number of coefficients; equals the rounded up radius multiplied with 2. + int size; + double inv_scale; +}; + +extern const struct filter_kernel mp_filter_kernels[]; + +const struct filter_kernel *mp_find_filter_kernel(const char *name); +bool mp_init_filter(struct filter_kernel *filter, const int *sizes, + double scale); +void mp_compute_weights(struct filter_kernel *filter, double f, float *out_w); +void mp_compute_lut(struct filter_kernel *filter, int count, float *out_array); + +#endif /* MPLAYER_FILTER_KERNELS_H */ diff --git a/video/out/geometry.c b/video/out/geometry.c new file mode 100644 index 0000000000..941528aea9 --- /dev/null +++ b/video/out/geometry.c @@ -0,0 +1,112 @@ +/* + * copyright (C) 2002 Mark Zealey <mark@zealos.org> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <string.h> +#include <limits.h> +#include "geometry.h" +#include "mp_msg.h" + +/* A string of the form [WxH][+X+Y] or xpos[%]:ypos[%] */ +char *vo_geometry; +// set when either width or height is changed +int geometry_wh_changed; +int geometry_xy_changed; + +// xpos,ypos: position of the left upper corner +// widw,widh: width and height of the window +// scrw,scrh: width and height of the current screen +int geometry(int *xpos, int *ypos, int *widw, int *widh, int scrw, int scrh) +{ + if(vo_geometry != NULL) { + char xsign[2], ysign[2], dummy[2]; + int width, height, xoff, yoff, xper, yper; + int i; + int ok = 0; + for (i = 0; !ok && i < 9; i++) { + width = height = xoff = yoff = xper = yper = INT_MIN; + strcpy(xsign, "+"); + strcpy(ysign, "+"); + switch (i) { + case 0: + ok = sscanf(vo_geometry, "%ix%i%1[+-]%i%1[+-]%i%c", + &width, &height, xsign, &xoff, ysign, + &yoff, dummy) == 6; + break; + case 1: + ok = sscanf(vo_geometry, "%ix%i%c", &width, &height, dummy) == 2; + break; + case 2: + ok = sscanf(vo_geometry, "%1[+-]%i%1[+-]%i%c", + xsign, &xoff, ysign, &yoff, dummy) == 4; + break; + case 3: + ok = sscanf(vo_geometry, "%i%%:%i%1[%]%c", &xper, &yper, dummy, dummy) == 3; + break; + case 4: + ok = sscanf(vo_geometry, "%i:%i%1[%]%c", &xoff, &yper, dummy, dummy) == 3; + break; + case 5: + ok = sscanf(vo_geometry, "%i%%:%i%c", &xper, &yoff, dummy) == 2; + break; + case 6: + ok = sscanf(vo_geometry, "%i:%i%c", &xoff, &yoff, dummy) == 2; + break; + case 7: + ok = sscanf(vo_geometry, "%i%1[%]%c", &xper, dummy, dummy) == 2; + break; + case 8: + ok = sscanf(vo_geometry, "%i%c", &xoff, dummy) == 1; + break; + } + } + if (!ok) { + mp_msg(MSGT_VO, MSGL_ERR, + "-geometry must be in [WxH][[+-]X[+-]Y] | [X[%%]:[Y[%%]]] format, incorrect (%s)\n", vo_geometry); + return 0; + } + + mp_msg(MSGT_VO, MSGL_V,"geometry window parameter: widw: %i," + " widh: %i, scrw: %i, scrh: %i\n",*widw, *widh, scrw, scrh); + + mp_msg(MSGT_VO, MSGL_V,"geometry set to width: %i," + "height: %i, xoff: %s%i, yoff: %s%i, xper: %i, yper: %i\n", + width, height, xsign, xoff, ysign, yoff, xper, yper); + + if (width > 0) *widw = width; + if (height > 0) *widh = height; + + if(xoff != INT_MIN && xsign[0] == '-') xoff = scrw - *widw - xoff; + if(yoff != INT_MIN && ysign[0] == '-') yoff = scrh - *widh - yoff; + if(xper >= 0 && xper <= 100) xoff = (scrw - *widw) * ((float)xper / 100.0); + if(yper >= 0 && yper <= 100) yoff = (scrh - *widh) * ((float)yper / 100.0); + + mp_msg(MSGT_VO, MSGL_V,"geometry set to width: %i," + "height: %i, xoff: %i, yoff: %i, xper: %i, yper: %i\n", + width, height, xoff, yoff, xper, yper); + + if (xoff != INT_MIN && xpos) *xpos = xoff; + if (yoff != INT_MIN && ypos) *ypos = yoff; + + geometry_wh_changed = width > 0 || height > 0; + geometry_xy_changed = xoff != INT_MIN || yoff != INT_MIN; + } + return 1; +} diff --git a/video/out/geometry.h b/video/out/geometry.h new file mode 100644 index 0000000000..77868a5aad --- /dev/null +++ b/video/out/geometry.h @@ -0,0 +1,29 @@ +/* + * copyright (C) 2002 Mark Zealey <mark@zealos.org> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_GEOMETRY_H +#define MPLAYER_GEOMETRY_H + +extern char *vo_geometry; +extern int geometry_wh_changed; +extern int geometry_xy_changed; +int geometry(int *xpos, int *ypos, int *widw, int *widh, int scrw, int scrh); + +#endif /* MPLAYER_GEOMETRY_H */ diff --git a/video/out/gl_common.c b/video/out/gl_common.c new file mode 100644 index 0000000000..80db2eacc4 --- /dev/null +++ b/video/out/gl_common.c @@ -0,0 +1,2654 @@ +/* + * common OpenGL routines + * + * copyleft (C) 2005-2010 Reimar Döffinger <Reimar.Doeffinger@gmx.de> + * Special thanks go to the xine team and Matthias Hopf, whose video_out_opengl.c + * gave me lots of good ideas. + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can alternatively redistribute this file 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. + */ + +/** + * \file gl_common.c + * \brief OpenGL helper functions used by vo_gl.c and vo_gl2.c + */ + +#include <stddef.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <stdbool.h> +#include <math.h> +#include <assert.h> +#include "talloc.h" +#include "gl_common.h" +#include "csputils.h" +#include "aspect.h" +#include "pnm_loader.h" +#include "options.h" +#include "sub/sub.h" +#include "bitmap_packer.h" + +//! \defgroup glgeneral OpenGL general helper functions + +// GLU has this as gluErrorString (we don't use GLU, as it is legacy-OpenGL) +static const char *gl_error_to_string(GLenum error) +{ + switch (error) { + case GL_INVALID_ENUM: return "INVALID_ENUM"; + case GL_INVALID_VALUE: return "INVALID_VALUE"; + case GL_INVALID_OPERATION: return "INVALID_OPERATION"; + case GL_INVALID_FRAMEBUFFER_OPERATION: + return "INVALID_FRAMEBUFFER_OPERATION"; + case GL_OUT_OF_MEMORY: return "OUT_OF_MEMORY"; + default: return "unknown"; + } +} + +void glCheckError(GL *gl, const char *info) +{ + for (;;) { + GLenum error = gl->GetError(); + if (error == GL_NO_ERROR) + break; + mp_msg(MSGT_VO, MSGL_ERR, "[gl] %s: OpenGL error %s.\n", info, + gl_error_to_string(error)); + } +} + +//! \defgroup glcontext OpenGL context management helper functions + +//! \defgroup gltexture OpenGL texture handling helper functions + +//! \defgroup glconversion OpenGL conversion helper functions + +/** + * \brief adjusts the GL_UNPACK_ALIGNMENT to fit the stride. + * \param stride number of bytes per line for which alignment should fit. + * \ingroup glgeneral + */ +void glAdjustAlignment(GL *gl, int stride) +{ + GLint gl_alignment; + if (stride % 8 == 0) + gl_alignment = 8; + else if (stride % 4 == 0) + gl_alignment = 4; + else if (stride % 2 == 0) + gl_alignment = 2; + else + gl_alignment = 1; + gl->PixelStorei(GL_UNPACK_ALIGNMENT, gl_alignment); + gl->PixelStorei(GL_PACK_ALIGNMENT, gl_alignment); +} + +//! always return this format as internal texture format in glFindFormat +#define TEXTUREFORMAT_ALWAYS GL_RGB8 +#undef TEXTUREFORMAT_ALWAYS + +/** + * \brief find the OpenGL settings coresponding to format. + * + * All parameters may be NULL. + * \param fmt MPlayer format to analyze. + * \param bpp [OUT] bits per pixel of that format. + * \param gl_texfmt [OUT] internal texture format that fits the + * image format, not necessarily the best for performance. + * \param gl_format [OUT] OpenGL format for this image format. + * \param gl_type [OUT] OpenGL type for this image format. + * \return 1 if format is supported by OpenGL, 0 if not. + * \ingroup gltexture + */ +int glFindFormat(uint32_t fmt, int have_texture_rg, int *bpp, GLint *gl_texfmt, + GLenum *gl_format, GLenum *gl_type) +{ + int supported = 1; + int dummy1; + GLenum dummy2; + GLint dummy3; + if (!bpp) + bpp = &dummy1; + if (!gl_texfmt) + gl_texfmt = &dummy3; + if (!gl_format) + gl_format = &dummy2; + if (!gl_type) + gl_type = &dummy2; + + if (mp_get_chroma_shift(fmt, NULL, NULL, NULL)) { + // reduce the possible cases a bit + if (IMGFMT_IS_YUVP16_LE(fmt)) + fmt = IMGFMT_420P16_LE; + else if (IMGFMT_IS_YUVP16_BE(fmt)) + fmt = IMGFMT_420P16_BE; + else + fmt = IMGFMT_YV12; + } + + *bpp = IMGFMT_IS_BGR(fmt) ? IMGFMT_BGR_DEPTH(fmt) : IMGFMT_RGB_DEPTH(fmt); + *gl_texfmt = 3; + switch (fmt) { + case IMGFMT_RGB48NE: + *gl_format = GL_RGB; + *gl_type = GL_UNSIGNED_SHORT; + break; + case IMGFMT_RGB24: + *gl_format = GL_RGB; + *gl_type = GL_UNSIGNED_BYTE; + break; + case IMGFMT_RGBA: + *gl_texfmt = 4; + *gl_format = GL_RGBA; + *gl_type = GL_UNSIGNED_BYTE; + break; + case IMGFMT_420P16: + supported = 0; // no native YUV support + *gl_texfmt = have_texture_rg ? GL_R16 : GL_LUMINANCE16; + *bpp = 16; + *gl_format = have_texture_rg ? GL_RED : GL_LUMINANCE; + *gl_type = GL_UNSIGNED_SHORT; + break; + case IMGFMT_YV12: + supported = 0; // no native YV12 support + case IMGFMT_Y800: + case IMGFMT_Y8: + *gl_texfmt = 1; + *bpp = 8; + *gl_format = GL_LUMINANCE; + *gl_type = GL_UNSIGNED_BYTE; + break; + case IMGFMT_UYVY: + // IMGFMT_YUY2 would be more logical for the _REV format, + // but gives clearly swapped colors. + case IMGFMT_YVYU: + *gl_texfmt = GL_YCBCR_MESA; + *bpp = 16; + *gl_format = GL_YCBCR_MESA; + *gl_type = fmt == IMGFMT_UYVY ? GL_UNSIGNED_SHORT_8_8 : GL_UNSIGNED_SHORT_8_8_REV; + break; +#if 0 + // we do not support palettized formats, although the format the + // swscale produces works + case IMGFMT_RGB8: + *gl_format = GL_RGB; + *gl_type = GL_UNSIGNED_BYTE_2_3_3_REV; + break; +#endif + case IMGFMT_RGB15: + *gl_format = GL_RGBA; + *gl_type = GL_UNSIGNED_SHORT_1_5_5_5_REV; + break; + case IMGFMT_RGB16: + *gl_format = GL_RGB; + *gl_type = GL_UNSIGNED_SHORT_5_6_5_REV; + break; +#if 0 + case IMGFMT_BGR8: + // special case as red and blue have a different number of bits. + // GL_BGR and GL_UNSIGNED_BYTE_3_3_2 isn't supported at least + // by nVidia drivers, and in addition would give more bits to + // blue than to red, which isn't wanted + *gl_format = GL_RGB; + *gl_type = GL_UNSIGNED_BYTE_3_3_2; + break; +#endif + case IMGFMT_BGR15: + *gl_format = GL_BGRA; + *gl_type = GL_UNSIGNED_SHORT_1_5_5_5_REV; + break; + case IMGFMT_BGR16: + *gl_format = GL_RGB; + *gl_type = GL_UNSIGNED_SHORT_5_6_5; + break; + case IMGFMT_BGR24: + *gl_format = GL_BGR; + *gl_type = GL_UNSIGNED_BYTE; + break; + case IMGFMT_BGRA: + *gl_texfmt = 4; + *gl_format = GL_BGRA; + *gl_type = GL_UNSIGNED_BYTE; + break; + default: + *gl_texfmt = 4; + *gl_format = GL_RGBA; + *gl_type = GL_UNSIGNED_BYTE; + supported = 0; + } +#ifdef TEXTUREFORMAT_ALWAYS + *gl_texfmt = TEXTUREFORMAT_ALWAYS; +#endif + return supported; +} + +struct feature { + int id; + const char *name; +}; + +static const struct feature features[] = { + {MPGL_CAP_GL, "Basic OpenGL"}, + {MPGL_CAP_GL_LEGACY, "Legacy OpenGL"}, + {MPGL_CAP_GL2, "OpenGL 2.0"}, + {MPGL_CAP_GL21, "OpenGL 2.1"}, + {MPGL_CAP_GL3, "OpenGL 3.0"}, + {MPGL_CAP_FB, "Framebuffers"}, + {MPGL_CAP_VAO, "VAOs"}, + {MPGL_CAP_SRGB_TEX, "sRGB textures"}, + {MPGL_CAP_SRGB_FB, "sRGB framebuffers"}, + {MPGL_CAP_FLOAT_TEX, "Float textures"}, + {MPGL_CAP_TEX_RG, "RG textures"}, + {MPGL_CAP_NO_SW, "NO_SW"}, + {0}, +}; + +static void list_features(int set, int msgl, bool invert) +{ + for (const struct feature *f = &features[0]; f->id; f++) { + if (invert == !(f->id & set)) + mp_msg(MSGT_VO, msgl, " [%s]", f->name); + } + mp_msg(MSGT_VO, msgl, "\n"); +} + +// This guesses if the current GL context is a suspected software renderer. +static bool is_software_gl(GL *gl) +{ + const char *renderer = gl->GetString(GL_RENDERER); + const char *vendor = gl->GetString(GL_VENDOR); + return !(renderer && vendor) || + strcmp(renderer, "Software Rasterizer") == 0 || + strstr(renderer, "llvmpipe") || + strcmp(vendor, "Microsoft Corporation") == 0 || + strcmp(renderer, "Mesa X11") == 0; +} + +#ifdef HAVE_LIBDL +#include <dlfcn.h> +#endif +/** + * \brief find address of a linked function + * \param s name of function to find + * \return address of function or NULL if not found + */ +static void *getdladdr(const char *s) +{ + void *ret = NULL; +#ifdef HAVE_LIBDL + void *handle = dlopen(NULL, RTLD_LAZY); + if (!handle) + return NULL; + ret = dlsym(handle, s); + dlclose(handle); +#endif + return ret; +} + +#define FN_OFFS(name) offsetof(GL, name) + +// Define the function with a "hard" reference to the function as fallback. +// (This requires linking with a compatible OpenGL library.) +#define DEF_FN_HARD(name) {FN_OFFS(name), {"gl" # name}, gl ## name} + +#define DEF_FN(name) {FN_OFFS(name), {"gl" # name}} +#define DEF_FN_NAMES(name, ...) {FN_OFFS(name), {__VA_ARGS__}} + +struct gl_function { + ptrdiff_t offset; + char *funcnames[7]; + void *fallback; +}; + +struct gl_functions { + const char *extension; // introduced with this extension in any version + int provides; // bitfield of MPGL_CAP_* constants + int ver_core; // introduced as required function + int ver_removed; // removed as required function (no replacement) + bool partial_ok; // loading only some functions is ok + struct gl_function *functions; +}; + +#define MAX_FN_COUNT 50 // max functions per gl_functions section + +struct gl_functions gl_functions[] = { + // GL functions which are always available anywhere at least since 1.1 + { + .ver_core = MPGL_VER(1, 1), + .provides = MPGL_CAP_GL, + .functions = (struct gl_function[]) { + DEF_FN_HARD(Viewport), + DEF_FN_HARD(Clear), + DEF_FN_HARD(GenTextures), + DEF_FN_HARD(DeleteTextures), + DEF_FN_HARD(TexEnvi), + DEF_FN_HARD(ClearColor), + DEF_FN_HARD(Enable), + DEF_FN_HARD(Disable), + DEF_FN_HARD(DrawBuffer), + DEF_FN_HARD(DepthMask), + DEF_FN_HARD(BlendFunc), + DEF_FN_HARD(Flush), + DEF_FN_HARD(Finish), + DEF_FN_HARD(PixelStorei), + DEF_FN_HARD(TexImage1D), + DEF_FN_HARD(TexImage2D), + DEF_FN_HARD(TexSubImage2D), + DEF_FN_HARD(GetTexImage), + DEF_FN_HARD(TexParameteri), + DEF_FN_HARD(TexParameterf), + DEF_FN_HARD(TexParameterfv), + DEF_FN_HARD(GetIntegerv), + DEF_FN_HARD(GetBooleanv), + DEF_FN_HARD(ColorMask), + DEF_FN_HARD(ReadPixels), + DEF_FN_HARD(ReadBuffer), + DEF_FN_HARD(DrawArrays), + DEF_FN_HARD(GetString), + DEF_FN_HARD(GetError), + {0} + }, + }, + // GL 2.0-3.x functions + { + .ver_core = MPGL_VER(2, 0), + .provides = MPGL_CAP_GL2, + .functions = (struct gl_function[]) { + DEF_FN(GenBuffers), + DEF_FN(DeleteBuffers), + DEF_FN(BindBuffer), + DEF_FN(MapBuffer), + DEF_FN(UnmapBuffer), + DEF_FN(BufferData), + DEF_FN(ActiveTexture), + DEF_FN(BindTexture), + DEF_FN(GetAttribLocation), + DEF_FN(EnableVertexAttribArray), + DEF_FN(DisableVertexAttribArray), + DEF_FN(VertexAttribPointer), + DEF_FN(UseProgram), + DEF_FN(GetUniformLocation), + DEF_FN(CompileShader), + DEF_FN(CreateProgram), + DEF_FN(CreateShader), + DEF_FN(ShaderSource), + DEF_FN(LinkProgram), + DEF_FN(AttachShader), + DEF_FN(DeleteShader), + DEF_FN(DeleteProgram), + DEF_FN(GetShaderInfoLog), + DEF_FN(GetShaderiv), + DEF_FN(GetProgramInfoLog), + DEF_FN(GetProgramiv), + DEF_FN(BindAttribLocation), + DEF_FN(Uniform1f), + DEF_FN(Uniform2f), + DEF_FN(Uniform3f), + DEF_FN(Uniform1i), + DEF_FN(UniformMatrix3fv), + DEF_FN(TexImage3D), + {0}, + }, + }, + // GL 2.1-3.x functions (also: GLSL 120 shaders) + { + .ver_core = MPGL_VER(2, 1), + .provides = MPGL_CAP_GL21, + .functions = (struct gl_function[]) { + DEF_FN(UniformMatrix4x3fv), + {0} + }, + }, + // GL 3.x core only functions. + { + .ver_core = MPGL_VER(3, 0), + .provides = MPGL_CAP_GL3 | MPGL_CAP_SRGB_TEX | MPGL_CAP_SRGB_FB, + .functions = (struct gl_function[]) { + DEF_FN(GetStringi), + {0} + }, + }, + // Framebuffers, extension in GL 2.x, core in GL 3.x core. + { + .ver_core = MPGL_VER(3, 0), + .extension = "GL_ARB_framebuffer_object", + .provides = MPGL_CAP_FB, + .functions = (struct gl_function[]) { + DEF_FN(BindFramebuffer), + DEF_FN(GenFramebuffers), + DEF_FN(DeleteFramebuffers), + DEF_FN(CheckFramebufferStatus), + DEF_FN(FramebufferTexture2D), + {0} + }, + }, + // Framebuffers, alternative extension name. + { + .ver_removed = MPGL_VER(3, 0), // don't touch these fn names in 3.x + .extension = "GL_EXT_framebuffer_object", + .provides = MPGL_CAP_FB, + .functions = (struct gl_function[]) { + DEF_FN_NAMES(BindFramebuffer, "glBindFramebufferEXT"), + DEF_FN_NAMES(GenFramebuffers, "glGenFramebuffersEXT"), + DEF_FN_NAMES(DeleteFramebuffers, "glDeleteFramebuffersEXT"), + DEF_FN_NAMES(CheckFramebufferStatus, "glCheckFramebufferStatusEXT"), + DEF_FN_NAMES(FramebufferTexture2D, "glFramebufferTexture2DEXT"), + {0} + }, + }, + // VAOs, extension in GL 2.x, core in GL 3.x core. + { + .ver_core = MPGL_VER(3, 0), + .extension = "GL_ARB_vertex_array_object", + .provides = MPGL_CAP_VAO, + .functions = (struct gl_function[]) { + DEF_FN(GenVertexArrays), + DEF_FN(BindVertexArray), + DEF_FN(DeleteVertexArrays), + {0} + } + }, + // sRGB textures, extension in GL 2.x, core in GL 3.x core. + { + .ver_core = MPGL_VER(3, 0), + .extension = "GL_EXT_texture_sRGB", + .provides = MPGL_CAP_SRGB_TEX, + .functions = (struct gl_function[]) {{0}}, + }, + // sRGB framebuffers, extension in GL 2.x, core in GL 3.x core. + { + .ver_core = MPGL_VER(3, 0), + .extension = "GL_EXT_framebuffer_sRGB", + .provides = MPGL_CAP_SRGB_FB, + .functions = (struct gl_function[]) {{0}}, + }, + // Float textures, extension in GL 2.x, core in GL 3.x core. + { + .ver_core = MPGL_VER(3, 0), + .extension = "GL_ARB_texture_float", + .provides = MPGL_CAP_FLOAT_TEX, + .functions = (struct gl_function[]) {{0}}, + }, + // GL_RED / GL_RG textures, extension in GL 2.x, core in GL 3.x core. + { + .ver_core = MPGL_VER(3, 0), + .extension = "GL_ARB_texture_rg", + .provides = MPGL_CAP_TEX_RG, + .functions = (struct gl_function[]) {{0}}, + }, + // Swap control, always an OS specific extension + { + .extension = "_swap_control", + .functions = (struct gl_function[]) { + DEF_FN_NAMES(SwapInterval, "glXSwapIntervalSGI", "glXSwapInterval", + "wglSwapIntervalSGI", "wglSwapInterval", + "wglSwapIntervalEXT"), + {0} + }, + }, + // GL legacy functions in GL 1.x - 2.x, removed from GL 3.x + { + .ver_core = MPGL_VER(1, 1), + .ver_removed = MPGL_VER(3, 0), + .provides = MPGL_CAP_GL_LEGACY, + .functions = (struct gl_function[]) { + DEF_FN_HARD(Begin), + DEF_FN_HARD(End), + DEF_FN_HARD(MatrixMode), + DEF_FN_HARD(LoadIdentity), + DEF_FN_HARD(Translated), + DEF_FN_HARD(Scaled), + DEF_FN_HARD(Ortho), + DEF_FN_HARD(PushMatrix), + DEF_FN_HARD(PopMatrix), + DEF_FN_HARD(GenLists), + DEF_FN_HARD(DeleteLists), + DEF_FN_HARD(NewList), + DEF_FN_HARD(EndList), + DEF_FN_HARD(CallList), + DEF_FN_HARD(CallLists), + DEF_FN_HARD(Color4ub), + DEF_FN_HARD(Color4f), + DEF_FN_HARD(TexCoord2f), + DEF_FN_HARD(TexCoord2fv), + DEF_FN_HARD(Vertex2f), + DEF_FN_HARD(VertexPointer), + DEF_FN_HARD(ColorPointer), + DEF_FN_HARD(TexCoordPointer), + DEF_FN_HARD(EnableClientState), + DEF_FN_HARD(DisableClientState), + {0} + }, + }, + // Loading of old extensions, which are later added to GL 2.0. + // NOTE: actually we should be checking the extension strings: the OpenGL + // library could provide an entry point, but not implement it. + // But the previous code didn't do that, and nobody ever complained. + { + .ver_removed = MPGL_VER(2, 1), + .partial_ok = true, + .functions = (struct gl_function[]) { + DEF_FN_NAMES(GenBuffers, "glGenBuffers", "glGenBuffersARB"), + DEF_FN_NAMES(DeleteBuffers, "glDeleteBuffers", "glDeleteBuffersARB"), + DEF_FN_NAMES(BindBuffer, "glBindBuffer", "glBindBufferARB"), + DEF_FN_NAMES(MapBuffer, "glMapBuffer", "glMapBufferARB"), + DEF_FN_NAMES(UnmapBuffer, "glUnmapBuffer", "glUnmapBufferARB"), + DEF_FN_NAMES(BufferData, "glBufferData", "glBufferDataARB"), + DEF_FN_NAMES(ActiveTexture, "glActiveTexture", "glActiveTextureARB"), + DEF_FN_NAMES(BindTexture, "glBindTexture", "glBindTextureARB", "glBindTextureEXT"), + DEF_FN_NAMES(MultiTexCoord2f, "glMultiTexCoord2f", "glMultiTexCoord2fARB"), + DEF_FN_NAMES(TexImage3D, "glTexImage3D"), + {0} + }, + }, + // Ancient ARB shaders. + { + .extension = "_program", + .ver_removed = MPGL_VER(3, 0), + .functions = (struct gl_function[]) { + DEF_FN_NAMES(GenPrograms, "glGenProgramsARB"), + DEF_FN_NAMES(DeletePrograms, "glDeleteProgramsARB"), + DEF_FN_NAMES(BindProgram, "glBindProgramARB"), + DEF_FN_NAMES(ProgramString, "glProgramStringARB"), + DEF_FN_NAMES(GetProgramivARB, "glGetProgramivARB"), + DEF_FN_NAMES(ProgramEnvParameter4f, "glProgramEnvParameter4fARB"), + {0} + }, + }, + // Ancient ATI extensions. + { + .extension = "ATI_fragment_shader", + .ver_removed = MPGL_VER(3, 0), + .functions = (struct gl_function[]) { + DEF_FN_NAMES(BeginFragmentShader, "glBeginFragmentShaderATI"), + DEF_FN_NAMES(EndFragmentShader, "glEndFragmentShaderATI"), + DEF_FN_NAMES(SampleMap, "glSampleMapATI"), + DEF_FN_NAMES(ColorFragmentOp2, "glColorFragmentOp2ATI"), + DEF_FN_NAMES(ColorFragmentOp3, "glColorFragmentOp3ATI"), + DEF_FN_NAMES(SetFragmentShaderConstant, "glSetFragmentShaderConstantATI"), + {0} + }, + }, +}; + +#undef FN_OFFS +#undef DEF_FN_HARD +#undef DEF_FN +#undef DEF_FN_NAMES + + +/** + * \brief find the function pointers of some useful OpenGL extensions + * \param getProcAddress function to resolve function names, may be NULL + * \param ext2 an extra extension string + */ +static void getFunctions(GL *gl, void *(*getProcAddress)(const GLubyte *), + const char *ext2, bool gl3) +{ + talloc_free_children(gl); + *gl = (GL) { + .extensions = talloc_strdup(gl, ext2 ? ext2 : ""), + }; + + if (!getProcAddress) + getProcAddress = (void *)getdladdr; + + GLint major = 0, minor = 0; + if (gl3) { + gl->GetStringi = getProcAddress("glGetStringi"); + gl->GetIntegerv = getProcAddress("glGetIntegerv"); + + if (!(gl->GetStringi && gl->GetIntegerv)) + return; + + gl->GetIntegerv(GL_MAJOR_VERSION, &major); + gl->GetIntegerv(GL_MINOR_VERSION, &minor); + + GLint exts; + gl->GetIntegerv(GL_NUM_EXTENSIONS, &exts); + for (int n = 0; n < exts; n++) { + gl->extensions + = talloc_asprintf_append(gl->extensions, " %s", + gl->GetStringi(GL_EXTENSIONS, n)); + } + } else { + gl->GetString = getProcAddress("glGetString"); + if (!gl->GetString) + gl->GetString = glGetString; + + const char *ext = (char*)gl->GetString(GL_EXTENSIONS); + gl->extensions = talloc_asprintf_append(gl->extensions, " %s", ext); + + const char *version = gl->GetString(GL_VERSION); + sscanf(version, "%d.%d", &major, &minor); + } + gl->version = MPGL_VER(major, minor); + + mp_msg(MSGT_VO, MSGL_V, "[gl] Detected OpenGL %d.%d.\n", major, minor); + mp_msg(MSGT_VO, MSGL_DBG2, "[gl] Combined OpenGL extensions string:\n%s\n", + gl->extensions); + + for (int n = 0; n < sizeof(gl_functions) / sizeof(gl_functions[0]); n++) { + struct gl_functions *section = &gl_functions[n]; + + // With gl3=false, we could have a legacy context, where functionality + // is never removed. (E.g. the context could be at version >= 3.0, but + // legacy functions like glBegin still exist and work.) + if (gl3 && section->ver_removed && gl->version >= section->ver_removed) + continue; + + bool must_exist = section->ver_core && gl->version >= section->ver_core + && !section->partial_ok; + + if (!must_exist && section->extension && + !strstr(gl->extensions, section->extension)) + continue; + + void *loaded[MAX_FN_COUNT] = {0}; + bool all_loaded = true; + + for (int i = 0; section->functions[i].funcnames[0]; i++) { + struct gl_function *fn = §ion->functions[i]; + void *ptr = NULL; + for (int x = 0; fn->funcnames[x]; x++) { + ptr = getProcAddress((const GLubyte *)fn->funcnames[x]); + if (ptr) + break; + } + if (!ptr) + ptr = fn->fallback; + if (!ptr) { + all_loaded = false; + if (must_exist) { + // Either we or the driver are not conforming to OpenGL. + mp_msg(MSGT_VO, MSGL_ERR, "[gl] Required function '%s' not " + "found.\n", fn->funcnames[0]); + talloc_free_children(gl); + *gl = (GL) {0}; + return; + } + } + assert(i < MAX_FN_COUNT); + loaded[i] = ptr; + } + + if (all_loaded || section->partial_ok) { + gl->mpgl_caps |= section->provides; + for (int i = 0; section->functions[i].funcnames[0]; i++) { + struct gl_function *fn = §ion->functions[i]; + void **funcptr = (void**)(((char*)gl) + fn->offset); + if (loaded[i]) + *funcptr = loaded[i]; + } + } + } + + gl->glsl_version = 0; + if (gl->version >= MPGL_VER(2, 0)) + gl->glsl_version = 110; + if (gl->version >= MPGL_VER(2, 1)) + gl->glsl_version = 120; + if (gl->version >= MPGL_VER(3, 0)) + gl->glsl_version = 130; + // Specifically needed for OSX (normally we request 3.0 contexts only, but + // OSX always creates 3.2 contexts when requesting a core context). + if (gl->version >= MPGL_VER(3, 2)) + gl->glsl_version = 150; + + if (!is_software_gl(gl)) + gl->mpgl_caps |= MPGL_CAP_NO_SW; + + mp_msg(MSGT_VO, MSGL_V, "[gl] Detected OpenGL features:"); + list_features(gl->mpgl_caps, MSGL_V, false); +} + +/** + * \brief create a texture and set some defaults + * \param target texture taget, usually GL_TEXTURE_2D + * \param fmt internal texture format + * \param format texture host data format + * \param type texture host data type + * \param filter filter used for scaling, e.g. GL_LINEAR + * \param w texture width + * \param h texture height + * \param val luminance value to fill texture with + * \ingroup gltexture + */ +void glCreateClearTex(GL *gl, GLenum target, GLenum fmt, GLenum format, + GLenum type, GLint filter, int w, int h, + unsigned char val) +{ + GLfloat fval = (GLfloat)val / 255.0; + GLfloat border[4] = { + fval, fval, fval, fval + }; + int stride; + char *init; + if (w == 0) + w = 1; + if (h == 0) + h = 1; + stride = w * glFmt2bpp(format, type); + if (!stride) + return; + init = malloc(stride * h); + memset(init, val, stride * h); + glAdjustAlignment(gl, stride); + gl->PixelStorei(GL_UNPACK_ROW_LENGTH, w); + gl->TexImage2D(target, 0, fmt, w, h, 0, format, type, init); + gl->TexParameterf(target, GL_TEXTURE_PRIORITY, 1.0); + gl->TexParameteri(target, GL_TEXTURE_MIN_FILTER, filter); + gl->TexParameteri(target, GL_TEXTURE_MAG_FILTER, filter); + gl->TexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl->TexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + // Border texels should not be used with CLAMP_TO_EDGE + // We set a sane default anyway. + gl->TexParameterfv(target, GL_TEXTURE_BORDER_COLOR, border); + free(init); +} + +static GLint detect_hqtexfmt(GL *gl) +{ + const char *extensions = (const char *)gl->GetString(GL_EXTENSIONS); + if (strstr(extensions, "_texture_float")) + return GL_RGB32F; + else if (strstr(extensions, "NV_float_buffer")) + return GL_FLOAT_RGB32_NV; + return GL_RGB16; +} + +/** + * \brief creates a texture from a PPM file + * \param target texture taget, usually GL_TEXTURE_2D + * \param fmt internal texture format, 0 for default + * \param filter filter used for scaling, e.g. GL_LINEAR + * \param f file to read PPM from + * \param width [out] width of texture + * \param height [out] height of texture + * \param maxval [out] maxval value from PPM file + * \return 0 on error, 1 otherwise + * \ingroup gltexture + */ +int glCreatePPMTex(GL *gl, GLenum target, GLenum fmt, GLint filter, + FILE *f, int *width, int *height, int *maxval) +{ + int w, h, m, bpp; + GLenum type; + uint8_t *data = read_pnm(f, &w, &h, &bpp, &m); + GLint hqtexfmt = detect_hqtexfmt(gl); + if (!data || (bpp != 3 && bpp != 6)) { + free(data); + return 0; + } + if (!fmt) { + fmt = bpp == 6 ? hqtexfmt : 3; + if (fmt == GL_FLOAT_RGB32_NV && target != GL_TEXTURE_RECTANGLE) + fmt = GL_RGB16; + } + type = bpp == 6 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE; + glCreateClearTex(gl, target, fmt, GL_RGB, type, filter, w, h, 0); + glUploadTex(gl, target, GL_RGB, type, + data, w * bpp, 0, 0, w, h, 0); + free(data); + if (width) + *width = w; + if (height) + *height = h; + if (maxval) + *maxval = m; + return 1; +} + +/** + * \brief return the number of bytes per pixel for the given format + * \param format OpenGL format + * \param type OpenGL type + * \return bytes per pixel + * \ingroup glgeneral + * + * Does not handle all possible variants, just those used by MPlayer + */ +int glFmt2bpp(GLenum format, GLenum type) +{ + int component_size = 0; + switch (type) { + case GL_UNSIGNED_BYTE_3_3_2: + case GL_UNSIGNED_BYTE_2_3_3_REV: + return 1; + case GL_UNSIGNED_SHORT_5_5_5_1: + case GL_UNSIGNED_SHORT_1_5_5_5_REV: + case GL_UNSIGNED_SHORT_5_6_5: + case GL_UNSIGNED_SHORT_5_6_5_REV: + return 2; + case GL_UNSIGNED_BYTE: + component_size = 1; + break; + case GL_UNSIGNED_SHORT: + component_size = 2; + break; + } + switch (format) { + case GL_LUMINANCE: + case GL_ALPHA: + return component_size; + case GL_YCBCR_MESA: + return 2; + case GL_RGB: + case GL_BGR: + return 3 * component_size; + case GL_RGBA: + case GL_BGRA: + return 4 * component_size; + case GL_RED: + return component_size; + case GL_RG: + case GL_LUMINANCE_ALPHA: + return 2 * component_size; + } + abort(); // unknown +} + +/** + * \brief upload a texture, handling things like stride and slices + * \param target texture target, usually GL_TEXTURE_2D + * \param format OpenGL format of data + * \param type OpenGL type of data + * \param dataptr data to upload + * \param stride data stride + * \param x x offset in texture + * \param y y offset in texture + * \param w width of the texture part to upload + * \param h height of the texture part to upload + * \param slice height of an upload slice, 0 for all at once + * \ingroup gltexture + */ +void glUploadTex(GL *gl, GLenum target, GLenum format, GLenum type, + const void *dataptr, int stride, + int x, int y, int w, int h, int slice) +{ + const uint8_t *data = dataptr; + int y_max = y + h; + if (w <= 0 || h <= 0) + return; + if (slice <= 0) + slice = h; + if (stride < 0) { + data += (h - 1) * stride; + stride = -stride; + } + // this is not always correct, but should work for MPlayer + glAdjustAlignment(gl, stride); + gl->PixelStorei(GL_UNPACK_ROW_LENGTH, stride / glFmt2bpp(format, type)); + for (; y + slice <= y_max; y += slice) { + gl->TexSubImage2D(target, 0, x, y, w, slice, format, type, data); + data += stride * slice; + } + if (y < y_max) + gl->TexSubImage2D(target, 0, x, y, w, y_max - y, format, type, data); +} + +// Like glUploadTex, but upload a byte array with all elements set to val. +// If scratch is not NULL, points to a resizeable talloc memory block than can +// be freely used by the function (for avoiding temporary memory allocations). +void glClearTex(GL *gl, GLenum target, GLenum format, GLenum type, + int x, int y, int w, int h, uint8_t val, void **scratch) +{ + int bpp = glFmt2bpp(format, type); + int stride = w * bpp; + int size = h * stride; + if (size < 1) + return; + void *data = scratch ? *scratch : NULL; + if (talloc_get_size(data) < size) + data = talloc_realloc(NULL, data, char *, size); + memset(data, val, size); + glAdjustAlignment(gl, stride); + gl->PixelStorei(GL_UNPACK_ROW_LENGTH, w); + gl->TexSubImage2D(target, 0, x, y, w, h, format, type, data); + if (scratch) { + *scratch = data; + } else { + talloc_free(data); + } +} + +/** + * \brief download a texture, handling things like stride and slices + * \param target texture target, usually GL_TEXTURE_2D + * \param format OpenGL format of data + * \param type OpenGL type of data + * \param dataptr destination memory for download + * \param stride data stride (must be positive) + * \ingroup gltexture + */ +void glDownloadTex(GL *gl, GLenum target, GLenum format, GLenum type, + void *dataptr, int stride) +{ + // this is not always correct, but should work for MPlayer + glAdjustAlignment(gl, stride); + gl->PixelStorei(GL_PACK_ROW_LENGTH, stride / glFmt2bpp(format, type)); + gl->GetTexImage(target, 0, format, type, dataptr); +} + +/** + * \brief Setup ATI version of register combiners for YUV to RGB conversion. + * \param csp_params parameters used for colorspace conversion + * \param text if set use the GL_ATI_text_fragment_shader API as + * used on OS X. + */ +static void glSetupYUVFragmentATI(GL *gl, struct mp_csp_params *csp_params, + int text) +{ + GLint i; + float yuv2rgb[3][4]; + + gl->GetIntegerv(GL_MAX_TEXTURE_UNITS, &i); + if (i < 3) + mp_msg(MSGT_VO, MSGL_ERR, + "[gl] 3 texture units needed for YUV combiner (ATI) support (found %i)\n", i); + + mp_get_yuv2rgb_coeffs(csp_params, yuv2rgb); + for (i = 0; i < 3; i++) { + int j; + yuv2rgb[i][3] -= -0.5 * (yuv2rgb[i][1] + yuv2rgb[i][2]); + for (j = 0; j < 4; j++) { + yuv2rgb[i][j] *= 0.125; + yuv2rgb[i][j] += 0.5; + if (yuv2rgb[i][j] > 1) + yuv2rgb[i][j] = 1; + if (yuv2rgb[i][j] < 0) + yuv2rgb[i][j] = 0; + } + } + if (text == 0) { + GLfloat c0[4] = { yuv2rgb[0][0], yuv2rgb[1][0], yuv2rgb[2][0] }; + GLfloat c1[4] = { yuv2rgb[0][1], yuv2rgb[1][1], yuv2rgb[2][1] }; + GLfloat c2[4] = { yuv2rgb[0][2], yuv2rgb[1][2], yuv2rgb[2][2] }; + GLfloat c3[4] = { yuv2rgb[0][3], yuv2rgb[1][3], yuv2rgb[2][3] }; + if (!gl->BeginFragmentShader || !gl->EndFragmentShader || + !gl->SetFragmentShaderConstant || !gl->SampleMap || + !gl->ColorFragmentOp2 || !gl->ColorFragmentOp3) { + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Combiner (ATI) functions missing!\n"); + return; + } + gl->GetIntegerv(GL_NUM_FRAGMENT_REGISTERS_ATI, &i); + if (i < 3) + mp_msg(MSGT_VO, MSGL_ERR, + "[gl] 3 registers needed for YUV combiner (ATI) support (found %i)\n", i); + gl->BeginFragmentShader(); + gl->SetFragmentShaderConstant(GL_CON_0_ATI, c0); + gl->SetFragmentShaderConstant(GL_CON_1_ATI, c1); + gl->SetFragmentShaderConstant(GL_CON_2_ATI, c2); + gl->SetFragmentShaderConstant(GL_CON_3_ATI, c3); + gl->SampleMap(GL_REG_0_ATI, GL_TEXTURE0, GL_SWIZZLE_STR_ATI); + gl->SampleMap(GL_REG_1_ATI, GL_TEXTURE1, GL_SWIZZLE_STR_ATI); + gl->SampleMap(GL_REG_2_ATI, GL_TEXTURE2, GL_SWIZZLE_STR_ATI); + gl->ColorFragmentOp2(GL_MUL_ATI, GL_REG_1_ATI, GL_NONE, GL_NONE, + GL_REG_1_ATI, GL_NONE, GL_BIAS_BIT_ATI, + GL_CON_1_ATI, GL_NONE, GL_BIAS_BIT_ATI); + gl->ColorFragmentOp3(GL_MAD_ATI, GL_REG_2_ATI, GL_NONE, GL_NONE, + GL_REG_2_ATI, GL_NONE, GL_BIAS_BIT_ATI, + GL_CON_2_ATI, GL_NONE, GL_BIAS_BIT_ATI, + GL_REG_1_ATI, GL_NONE, GL_NONE); + gl->ColorFragmentOp3(GL_MAD_ATI, GL_REG_0_ATI, GL_NONE, GL_NONE, + GL_REG_0_ATI, GL_NONE, GL_NONE, + GL_CON_0_ATI, GL_NONE, GL_BIAS_BIT_ATI, + GL_REG_2_ATI, GL_NONE, GL_NONE); + gl->ColorFragmentOp2(GL_ADD_ATI, GL_REG_0_ATI, GL_NONE, GL_8X_BIT_ATI, + GL_REG_0_ATI, GL_NONE, GL_NONE, + GL_CON_3_ATI, GL_NONE, GL_BIAS_BIT_ATI); + gl->EndFragmentShader(); + } else { + static const char template[] = + "!!ATIfs1.0\n" + "StartConstants;\n" + " CONSTANT c0 = {%e, %e, %e};\n" + " CONSTANT c1 = {%e, %e, %e};\n" + " CONSTANT c2 = {%e, %e, %e};\n" + " CONSTANT c3 = {%e, %e, %e};\n" + "EndConstants;\n" + "StartOutputPass;\n" + " SampleMap r0, t0.str;\n" + " SampleMap r1, t1.str;\n" + " SampleMap r2, t2.str;\n" + " MUL r1.rgb, r1.bias, c1.bias;\n" + " MAD r2.rgb, r2.bias, c2.bias, r1;\n" + " MAD r0.rgb, r0, c0.bias, r2;\n" + " ADD r0.rgb.8x, r0, c3.bias;\n" + "EndPass;\n"; + char buffer[512]; + snprintf(buffer, sizeof(buffer), template, + yuv2rgb[0][0], yuv2rgb[1][0], yuv2rgb[2][0], + yuv2rgb[0][1], yuv2rgb[1][1], yuv2rgb[2][1], + yuv2rgb[0][2], yuv2rgb[1][2], yuv2rgb[2][2], + yuv2rgb[0][3], yuv2rgb[1][3], yuv2rgb[2][3]); + mp_msg(MSGT_VO, MSGL_DBG2, "[gl] generated fragment program:\n%s\n", + buffer); + loadGPUProgram(gl, GL_TEXT_FRAGMENT_SHADER_ATI, buffer); + } +} + +// Replace all occurances of variables named "$"+name (e.g. $foo) in *text with +// replace, and return the result. *text must have been allocated with talloc. +static void replace_var_str(char **text, const char *name, const char *replace) +{ + size_t namelen = strlen(name); + char *nextvar = *text; + void *parent = talloc_parent(*text); + for (;;) { + nextvar = strchr(nextvar, '$'); + if (!nextvar) + break; + char *until = nextvar; + nextvar++; + if (strncmp(nextvar, name, namelen) != 0) + continue; + nextvar += namelen; + // try not to replace prefixes of other vars (e.g. $foo vs. $foo_bar) + char term = nextvar[0]; + if (isalnum(term) || term == '_') + continue; + int prelength = until - *text; + int postlength = nextvar - *text; + char *n = talloc_asprintf(parent, "%.*s%s%s", prelength, *text, replace, + nextvar); + talloc_free(*text); + *text = n; + nextvar = *text + postlength; + } +} + +static void replace_var_float(char **text, const char *name, float replace) +{ + char *s = talloc_asprintf(NULL, "%e", replace); + replace_var_str(text, name, s); + talloc_free(s); +} + +static void replace_var_char(char **text, const char *name, char replace) +{ + char s[2] = { replace, '\0' }; + replace_var_str(text, name, s); +} + +// Append template to *text. Possibly initialize *text if it's NULL. +static void append_template(char **text, const char* template) +{ + if (!text) + *text = talloc_strdup(NULL, template); + else + *text = talloc_strdup_append(*text, template); +} + +/** + * \brief helper function for gen_spline_lookup_tex + * \param x subpixel-position ((0,1) range) to calculate weights for + * \param dst where to store transformed weights, must provide space for 4 GLfloats + * + * calculates the weights and stores them after appropriate transformation + * for the scaler fragment program. + */ +static void store_weights(float x, GLfloat *dst) +{ + float w0 = (((-1 * x + 3) * x - 3) * x + 1) / 6; + float w1 = (((3 * x - 6) * x + 0) * x + 4) / 6; + float w2 = (((-3 * x + 3) * x + 3) * x + 1) / 6; + float w3 = (((1 * x + 0) * x + 0) * x + 0) / 6; + *dst++ = 1 + x - w1 / (w0 + w1); + *dst++ = 1 - x + w3 / (w2 + w3); + *dst++ = w0 + w1; + *dst++ = 0; +} + +//! to avoid artefacts this should be rather large +#define LOOKUP_BSPLINE_RES (2 * 1024) +/** + * \brief creates the 1D lookup texture needed for fast higher-order filtering + * \param unit texture unit to attach texture to + */ +static void gen_spline_lookup_tex(GL *gl, GLenum unit) +{ + GLfloat *tex = calloc(4 * LOOKUP_BSPLINE_RES, sizeof(*tex)); + GLfloat *tp = tex; + int i; + for (i = 0; i < LOOKUP_BSPLINE_RES; i++) { + float x = (float)(i + 0.5) / LOOKUP_BSPLINE_RES; + store_weights(x, tp); + tp += 4; + } + store_weights(0, tex); + store_weights(1, &tex[4 * (LOOKUP_BSPLINE_RES - 1)]); + gl->ActiveTexture(unit); + gl->TexImage1D(GL_TEXTURE_1D, 0, GL_RGBA16, LOOKUP_BSPLINE_RES, 0, GL_RGBA, + GL_FLOAT, tex); + gl->TexParameterf(GL_TEXTURE_1D, GL_TEXTURE_PRIORITY, 1.0); + gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT); + gl->ActiveTexture(GL_TEXTURE0); + free(tex); +} + +#define NOISE_RES 2048 + +/** + * \brief creates the 1D lookup texture needed to generate pseudo-random numbers. + * \param unit texture unit to attach texture to + */ +static void gen_noise_lookup_tex(GL *gl, GLenum unit) { + GLfloat *tex = calloc(NOISE_RES, sizeof(*tex)); + uint32_t lcg = 0x79381c11; + int i; + for (i = 0; i < NOISE_RES; i++) + tex[i] = (double)i / (NOISE_RES - 1); + for (i = 0; i < NOISE_RES - 1; i++) { + int remain = NOISE_RES - i; + int idx = i + (lcg >> 16) % remain; + GLfloat tmp = tex[i]; + tex[i] = tex[idx]; + tex[idx] = tmp; + lcg = lcg * 1664525 + 1013904223; + } + gl->ActiveTexture(unit); + gl->TexImage1D(GL_TEXTURE_1D, 0, 1, NOISE_RES, 0, GL_RED, GL_FLOAT, tex); + gl->TexParameterf(GL_TEXTURE_1D, GL_TEXTURE_PRIORITY, 1.0); + gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + gl->TexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT); + gl->ActiveTexture(GL_TEXTURE0); + free(tex); +} + +#define SAMPLE(dest, coord, texture) \ + "TEX textemp, " coord ", " texture ", $tex_type;\n" \ + "MOV " dest ", textemp.r;\n" + +static const char bilin_filt_template[] = + SAMPLE("yuv.$out_comp","fragment.texcoord[$in_tex]","texture[$in_tex]"); + +#define BICUB_FILT_MAIN \ + /* first y-interpolation */ \ + "ADD coord, fragment.texcoord[$in_tex].xyxy, cdelta.xyxw;\n" \ + "ADD coord2, fragment.texcoord[$in_tex].xyxy, cdelta.zyzw;\n" \ + SAMPLE("a.r","coord.xyxy","texture[$in_tex]") \ + SAMPLE("a.g","coord.zwzw","texture[$in_tex]") \ + /* second y-interpolation */ \ + SAMPLE("b.r","coord2.xyxy","texture[$in_tex]") \ + SAMPLE("b.g","coord2.zwzw","texture[$in_tex]") \ + "LRP a.b, parmy.b, a.rrrr, a.gggg;\n" \ + "LRP a.a, parmy.b, b.rrrr, b.gggg;\n" \ + /* x-interpolation */ \ + "LRP yuv.$out_comp, parmx.b, a.bbbb, a.aaaa;\n" + +static const char bicub_filt_template_2D[] = + "MAD coord.xy, fragment.texcoord[$in_tex], {$texw, $texh}, {0.5, 0.5};\n" + "TEX parmx, coord.x, texture[$texs], 1D;\n" + "MUL cdelta.xz, parmx.rrgg, {-$ptw, 0, $ptw, 0};\n" + "TEX parmy, coord.y, texture[$texs], 1D;\n" + "MUL cdelta.yw, parmy.rrgg, {0, -$pth, 0, $pth};\n" + BICUB_FILT_MAIN; + +static const char bicub_filt_template_RECT[] = + "ADD coord, fragment.texcoord[$in_tex], {0.5, 0.5};\n" + "TEX parmx, coord.x, texture[$texs], 1D;\n" + "MUL cdelta.xz, parmx.rrgg, {-1, 0, 1, 0};\n" + "TEX parmy, coord.y, texture[$texs], 1D;\n" + "MUL cdelta.yw, parmy.rrgg, {0, -1, 0, 1};\n" + BICUB_FILT_MAIN; + +#define CALCWEIGHTS(t, s) \ + "MAD "t ", {-0.5, 0.1666, 0.3333, -0.3333}, "s ", {1, 0, -0.5, 0.5};\n" \ + "MAD "t ", "t ", "s ", {0, 0, -0.5, 0.5};\n" \ + "MAD "t ", "t ", "s ", {-0.6666, 0, 0.8333, 0.1666};\n" \ + "RCP a.x, "t ".z;\n" \ + "RCP a.y, "t ".w;\n" \ + "MAD "t ".xy, "t ".xyxy, a.xyxy, {1, 1, 0, 0};\n" \ + "ADD "t ".x, "t ".xxxx, "s ";\n" \ + "SUB "t ".y, "t ".yyyy, "s ";\n" + +static const char bicub_notex_filt_template_2D[] = + "MAD coord.xy, fragment.texcoord[$in_tex], {$texw, $texh}, {0.5, 0.5};\n" + "FRC coord.xy, coord.xyxy;\n" + CALCWEIGHTS("parmx", "coord.xxxx") + "MUL cdelta.xz, parmx.rrgg, {-$ptw, 0, $ptw, 0};\n" + CALCWEIGHTS("parmy", "coord.yyyy") + "MUL cdelta.yw, parmy.rrgg, {0, -$pth, 0, $pth};\n" + BICUB_FILT_MAIN; + +static const char bicub_notex_filt_template_RECT[] = + "ADD coord, fragment.texcoord[$in_tex], {0.5, 0.5};\n" + "FRC coord.xy, coord.xyxy;\n" + CALCWEIGHTS("parmx", "coord.xxxx") + "MUL cdelta.xz, parmx.rrgg, {-1, 0, 1, 0};\n" + CALCWEIGHTS("parmy", "coord.yyyy") + "MUL cdelta.yw, parmy.rrgg, {0, -1, 0, 1};\n" + BICUB_FILT_MAIN; + +#define BICUB_X_FILT_MAIN \ + "ADD coord.xy, fragment.texcoord[$in_tex].xyxy, cdelta.xyxy;\n" \ + "ADD coord2.xy, fragment.texcoord[$in_tex].xyxy, cdelta.zyzy;\n" \ + SAMPLE("a.r","coord","texture[$in_tex]") \ + SAMPLE("b.r","coord2","texture[$in_tex]") \ + /* x-interpolation */ \ + "LRP yuv.$out_comp, parmx.b, a.rrrr, b.rrrr;\n" + +static const char bicub_x_filt_template_2D[] = + "MAD coord.x, fragment.texcoord[$in_tex], {$texw}, {0.5};\n" + "TEX parmx, coord, texture[$texs], 1D;\n" + "MUL cdelta.xyz, parmx.rrgg, {-$ptw, 0, $ptw};\n" + BICUB_X_FILT_MAIN; + +static const char bicub_x_filt_template_RECT[] = + "ADD coord.x, fragment.texcoord[$in_tex], {0.5};\n" + "TEX parmx, coord, texture[$texs], 1D;\n" + "MUL cdelta.xyz, parmx.rrgg, {-1, 0, 1};\n" + BICUB_X_FILT_MAIN; + +static const char unsharp_filt_template[] = + "PARAM dcoord$out_comp = {$ptw_05, $pth_05, $ptw_05, -$pth_05};\n" + "ADD coord, fragment.texcoord[$in_tex].xyxy, dcoord$out_comp;\n" + "SUB coord2, fragment.texcoord[$in_tex].xyxy, dcoord$out_comp;\n" + SAMPLE("a.r","fragment.texcoord[$in_tex]","texture[$in_tex]") + SAMPLE("b.r","coord.xyxy","texture[$in_tex]") + SAMPLE("b.g","coord.zwzw","texture[$in_tex]") + "ADD b.r, b.r, b.g;\n" + SAMPLE("b.b","coord2.xyxy","texture[$in_tex]") + SAMPLE("b.g","coord2.zwzw","texture[$in_tex]") + "DP3 b, b, {0.25, 0.25, 0.25};\n" + "SUB b.r, a.r, b.r;\n" + "MAD textemp.r, b.r, {$strength}, a.r;\n" + "MOV yuv.$out_comp, textemp.r;\n"; + +static const char unsharp_filt_template2[] = + "PARAM dcoord$out_comp = {$ptw_12, $pth_12, $ptw_12, -$pth_12};\n" + "PARAM dcoord2$out_comp = {$ptw_15, 0, 0, $pth_15};\n" + "ADD coord, fragment.texcoord[$in_tex].xyxy, dcoord$out_comp;\n" + "SUB coord2, fragment.texcoord[$in_tex].xyxy, dcoord$out_comp;\n" + SAMPLE("a.r","fragment.texcoord[$in_tex]","texture[$in_tex]") + SAMPLE("b.r","coord.xyxy","texture[$in_tex]") + SAMPLE("b.g","coord.zwzw","texture[$in_tex]") + "ADD b.r, b.r, b.g;\n" + SAMPLE("b.b","coord2.xyxy","texture[$in_tex]") + SAMPLE("b.g","coord2.zwzw","texture[$in_tex]") + "ADD b.r, b.r, b.b;\n" + "ADD b.a, b.r, b.g;\n" + "ADD coord, fragment.texcoord[$in_tex].xyxy, dcoord2$out_comp;\n" + "SUB coord2, fragment.texcoord[$in_tex].xyxy, dcoord2$out_comp;\n" + SAMPLE("b.r","coord.xyxy","texture[$in_tex]") + SAMPLE("b.g","coord.zwzw","texture[$in_tex]") + "ADD b.r, b.r, b.g;\n" + SAMPLE("b.b","coord2.xyxy","texture[$in_tex]") + SAMPLE("b.g","coord2.zwzw","texture[$in_tex]") + "DP4 b.r, b, {-0.1171875, -0.1171875, -0.1171875, -0.09765625};\n" + "MAD b.r, a.r, {0.859375}, b.r;\n" + "MAD textemp.r, b.r, {$strength}, a.r;\n" + "MOV yuv.$out_comp, textemp.r;\n"; + +static const char yuv_prog_template[] = + "PARAM ycoef = {$cm11, $cm21, $cm31};\n" + "PARAM ucoef = {$cm12, $cm22, $cm32};\n" + "PARAM vcoef = {$cm13, $cm23, $cm33};\n" + "PARAM offsets = {$cm14, $cm24, $cm34};\n" + "TEMP res;\n" + "MAD res.rgb, yuv.rrrr, ycoef, offsets;\n" + "MAD res.rgb, yuv.gggg, ucoef, res;\n" + "MAD res.rgb, yuv.bbbb, vcoef, res;\n"; + +static const char yuv_pow_prog_template[] = + "PARAM ycoef = {$cm11, $cm21, $cm31};\n" + "PARAM ucoef = {$cm12, $cm22, $cm32};\n" + "PARAM vcoef = {$cm13, $cm23, $cm33};\n" + "PARAM offsets = {$cm14, $cm24, $cm34};\n" + "PARAM gamma = {$gamma_r, $gamma_g, $gamma_b};\n" + "TEMP res;\n" + "MAD res.rgb, yuv.rrrr, ycoef, offsets;\n" + "MAD res.rgb, yuv.gggg, ucoef, res;\n" + "MAD_SAT res.rgb, yuv.bbbb, vcoef, res;\n" + "POW res.r, res.r, gamma.r;\n" + "POW res.g, res.g, gamma.g;\n" + "POW res.b, res.b, gamma.b;\n"; + +static const char yuv_lookup_prog_template[] = + "PARAM ycoef = {$cm11, $cm21, $cm31, 0};\n" + "PARAM ucoef = {$cm12, $cm22, $cm32, 0};\n" + "PARAM vcoef = {$cm13, $cm23, $cm33, 0};\n" + "PARAM offsets = {$cm14, $cm24, $cm34, 0.125};\n" + "TEMP res;\n" + "MAD res, yuv.rrrr, ycoef, offsets;\n" + "MAD res.rgb, yuv.gggg, ucoef, res;\n" + "MAD res.rgb, yuv.bbbb, vcoef, res;\n" + "TEX res.r, res.raaa, texture[$conv_tex0], 2D;\n" + "ADD res.a, res.a, 0.25;\n" + "TEX res.g, res.gaaa, texture[$conv_tex0], 2D;\n" + "ADD res.a, res.a, 0.25;\n" + "TEX res.b, res.baaa, texture[$conv_tex0], 2D;\n"; + +static const char yuv_lookup3d_prog_template[] = + "TEMP res;\n" + "TEX res, yuv, texture[$conv_tex0], 3D;\n"; + +static const char noise_filt_template[] = + "MUL coord.xy, fragment.texcoord[0], {$noise_sx, $noise_sy};\n" + "TEMP rand;\n" + "TEX rand.r, coord.x, texture[$noise_filt_tex], 1D;\n" + "ADD rand.r, rand.r, coord.y;\n" + "TEX rand.r, rand.r, texture[$noise_filt_tex], 1D;\n" + "MAD res.rgb, rand.rrrr, {$noise_str, $noise_str, $noise_str}, res;\n"; + +/** + * \brief creates and initializes helper textures needed for scaling texture read + * \param scaler scaler type to create texture for + * \param texu contains next free texture unit number + * \param texs texture unit ids for the scaler are stored in this array + */ +static void create_scaler_textures(GL *gl, int scaler, int *texu, char *texs) +{ + switch (scaler) { + case YUV_SCALER_BILIN: + case YUV_SCALER_BICUB_NOTEX: + case YUV_SCALER_UNSHARP: + case YUV_SCALER_UNSHARP2: + break; + case YUV_SCALER_BICUB: + case YUV_SCALER_BICUB_X: + texs[0] = (*texu)++; + gen_spline_lookup_tex(gl, GL_TEXTURE0 + texs[0]); + texs[0] += '0'; + break; + default: + mp_msg(MSGT_VO, MSGL_ERR, "[gl] unknown scaler type %i\n", scaler); + } +} + +//! resolution of texture for gamma lookup table +#define LOOKUP_RES 512 +//! resolution for 3D yuv->rgb conversion lookup table +#define LOOKUP_3DRES 32 +/** + * \brief creates and initializes helper textures needed for yuv conversion + * \param params struct containing parameters like brightness, gamma, ... + * \param texu contains next free texture unit number + * \param texs texture unit ids for the conversion are stored in this array + */ +static void create_conv_textures(GL *gl, gl_conversion_params_t *params, + int *texu, char *texs) +{ + unsigned char *lookup_data = NULL; + int conv = YUV_CONVERSION(params->type); + switch (conv) { + case YUV_CONVERSION_FRAGMENT: + case YUV_CONVERSION_FRAGMENT_POW: + break; + case YUV_CONVERSION_FRAGMENT_LOOKUP: + texs[0] = (*texu)++; + gl->ActiveTexture(GL_TEXTURE0 + texs[0]); + lookup_data = malloc(4 * LOOKUP_RES); + mp_gen_gamma_map(lookup_data, LOOKUP_RES, params->csp_params.rgamma); + mp_gen_gamma_map(&lookup_data[LOOKUP_RES], LOOKUP_RES, + params->csp_params.ggamma); + mp_gen_gamma_map(&lookup_data[2 * LOOKUP_RES], LOOKUP_RES, + params->csp_params.bgamma); + glCreateClearTex(gl, GL_TEXTURE_2D, GL_LUMINANCE8, GL_LUMINANCE, + GL_UNSIGNED_BYTE, GL_LINEAR, LOOKUP_RES, 4, 0); + glUploadTex(gl, GL_TEXTURE_2D, GL_LUMINANCE, GL_UNSIGNED_BYTE, + lookup_data, LOOKUP_RES, 0, 0, LOOKUP_RES, 4, 0); + gl->ActiveTexture(GL_TEXTURE0); + texs[0] += '0'; + break; + case YUV_CONVERSION_FRAGMENT_LOOKUP3D: + { + int sz = LOOKUP_3DRES + 2; // texture size including borders + if (!gl->TexImage3D) { + mp_msg(MSGT_VO, MSGL_ERR, "[gl] Missing 3D texture function!\n"); + break; + } + texs[0] = (*texu)++; + gl->ActiveTexture(GL_TEXTURE0 + texs[0]); + lookup_data = malloc(3 * sz * sz * sz); + mp_gen_yuv2rgb_map(¶ms->csp_params, lookup_data, LOOKUP_3DRES); + glAdjustAlignment(gl, sz); + gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0); + gl->TexImage3D(GL_TEXTURE_3D, 0, 3, sz, sz, sz, 1, + GL_RGB, GL_UNSIGNED_BYTE, lookup_data); + gl->TexParameterf(GL_TEXTURE_3D, GL_TEXTURE_PRIORITY, 1.0); + gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP); + gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP); + gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP); + gl->ActiveTexture(GL_TEXTURE0); + texs[0] += '0'; + } + break; + default: + mp_msg(MSGT_VO, MSGL_ERR, "[gl] unknown conversion type %i\n", conv); + } + free(lookup_data); +} + +/** + * \brief adds a scaling texture read at the current fragment program position + * \param scaler type of scaler to insert + * \param prog pointer to fragment program so far + * \param texs array containing the texture unit identifiers for this scaler + * \param in_tex texture unit the scaler should read from + * \param out_comp component of the yuv variable the scaler stores the result in + * \param rect if rectangular (pixel) adressing should be used for in_tex + * \param texw width of the in_tex texture + * \param texh height of the in_tex texture + * \param strength strength of filter effect if the scaler does some kind of filtering + */ +static void add_scaler(int scaler, char **prog, char *texs, + char in_tex, char out_comp, int rect, int texw, int texh, + double strength) +{ + const char *ttype = rect ? "RECT" : "2D"; + const float ptw = rect ? 1.0 : 1.0 / texw; + const float pth = rect ? 1.0 : 1.0 / texh; + switch (scaler) { + case YUV_SCALER_BILIN: + append_template(prog, bilin_filt_template); + break; + case YUV_SCALER_BICUB: + if (rect) + append_template(prog, bicub_filt_template_RECT); + else + append_template(prog, bicub_filt_template_2D); + break; + case YUV_SCALER_BICUB_X: + if (rect) + append_template(prog, bicub_x_filt_template_RECT); + else + append_template(prog, bicub_x_filt_template_2D); + break; + case YUV_SCALER_BICUB_NOTEX: + if (rect) + append_template(prog, bicub_notex_filt_template_RECT); + else + append_template(prog, bicub_notex_filt_template_2D); + break; + case YUV_SCALER_UNSHARP: + append_template(prog, unsharp_filt_template); + break; + case YUV_SCALER_UNSHARP2: + append_template(prog, unsharp_filt_template2); + break; + } + + replace_var_char(prog, "texs", texs[0]); + replace_var_char(prog, "in_tex", in_tex); + replace_var_char(prog, "out_comp", out_comp); + replace_var_str(prog, "tex_type", ttype); + replace_var_float(prog, "texw", texw); + replace_var_float(prog, "texh", texh); + replace_var_float(prog, "ptw", ptw); + replace_var_float(prog, "pth", pth); + + // this is silly, not sure if that couldn't be in the shader source instead + replace_var_float(prog, "ptw_05", ptw * 0.5); + replace_var_float(prog, "pth_05", pth * 0.5); + replace_var_float(prog, "ptw_15", ptw * 1.5); + replace_var_float(prog, "pth_15", pth * 1.5); + replace_var_float(prog, "ptw_12", ptw * 1.2); + replace_var_float(prog, "pth_12", pth * 1.2); + + replace_var_float(prog, "strength", strength); +} + +static const struct { + const char *name; + GLenum cur; + GLenum max; +} progstats[] = { + {"instructions", 0x88A0, 0x88A1}, + {"native instructions", 0x88A2, 0x88A3}, + {"temporaries", 0x88A4, 0x88A5}, + {"native temporaries", 0x88A6, 0x88A7}, + {"parameters", 0x88A8, 0x88A9}, + {"native parameters", 0x88AA, 0x88AB}, + {"attribs", 0x88AC, 0x88AD}, + {"native attribs", 0x88AE, 0x88AF}, + {"ALU instructions", 0x8805, 0x880B}, + {"TEX instructions", 0x8806, 0x880C}, + {"TEX indirections", 0x8807, 0x880D}, + {"native ALU instructions", 0x8808, 0x880E}, + {"native TEX instructions", 0x8809, 0x880F}, + {"native TEX indirections", 0x880A, 0x8810}, + {NULL, 0, 0} +}; + +/** + * \brief load the specified GPU Program + * \param target program target to load into, only GL_FRAGMENT_PROGRAM is tested + * \param prog program string + * \return 1 on success, 0 otherwise + */ +int loadGPUProgram(GL *gl, GLenum target, char *prog) +{ + int i; + GLint cur = 0, max = 0, err = 0; + if (!gl->ProgramString) { + mp_msg(MSGT_VO, MSGL_ERR, "[gl] Missing GPU program function\n"); + return 0; + } + gl->ProgramString(target, GL_PROGRAM_FORMAT_ASCII, strlen(prog), prog); + gl->GetIntegerv(GL_PROGRAM_ERROR_POSITION, &err); + if (err != -1) { + mp_msg(MSGT_VO, MSGL_ERR, + "[gl] Error compiling fragment program, make sure your card supports\n" + "[gl] GL_ARB_fragment_program (use glxinfo to check).\n" + "[gl] Error message:\n %s at %.10s\n", + gl->GetString(GL_PROGRAM_ERROR_STRING), &prog[err]); + return 0; + } + if (!gl->GetProgramivARB || !mp_msg_test(MSGT_VO, MSGL_DBG2)) + return 1; + mp_msg(MSGT_VO, MSGL_V, "[gl] Program statistics:\n"); + for (i = 0; progstats[i].name; i++) { + gl->GetProgramivARB(target, progstats[i].cur, &cur); + gl->GetProgramivARB(target, progstats[i].max, &max); + mp_msg(MSGT_VO, MSGL_V, "[gl] %s: %i/%i\n", progstats[i].name, cur, + max); + } + return 1; +} + +#define MAX_PROGSZ (1024 * 1024) + +/** + * \brief setup a fragment program that will do YUV->RGB conversion + * \param parms struct containing parameters like conversion and scaler type, + * brightness, ... + */ +static void glSetupYUVFragprog(GL *gl, gl_conversion_params_t *params) +{ + int type = params->type; + int texw = params->texw; + int texh = params->texh; + int rect = params->target == GL_TEXTURE_RECTANGLE; + static const char prog_hdr[] = + "!!ARBfp1.0\n" + "OPTION ARB_precision_hint_fastest;\n" + // all scaler variables must go here so they aren't defined + // multiple times when the same scaler is used more than once + "TEMP coord, coord2, cdelta, parmx, parmy, a, b, yuv, textemp;\n"; + char *yuv_prog = NULL; + char **prog = &yuv_prog; + int cur_texu = 3; + char lum_scale_texs[1] = {0}; + char chrom_scale_texs[1] = {0}; + char conv_texs[1]; + char filt_texs[1] = {0}; + GLint i; + // this is the conversion matrix, with y, u, v factors + // for red, green, blue and the constant offsets + float yuv2rgb[3][4]; + int noise = params->noise_strength != 0; + create_conv_textures(gl, params, &cur_texu, conv_texs); + create_scaler_textures(gl, YUV_LUM_SCALER(type), &cur_texu, lum_scale_texs); + if (YUV_CHROM_SCALER(type) == YUV_LUM_SCALER(type)) + memcpy(chrom_scale_texs, lum_scale_texs, sizeof(chrom_scale_texs)); + else + create_scaler_textures(gl, YUV_CHROM_SCALER(type), &cur_texu, + chrom_scale_texs); + + if (noise) { + gen_noise_lookup_tex(gl, cur_texu); + filt_texs[0] = '0' + cur_texu++; + } + + gl->GetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &i); + if (i < cur_texu) + mp_msg(MSGT_VO, MSGL_ERR, + "[gl] %i texture units needed for this type of YUV fragment support (found %i)\n", + cur_texu, i); + if (!gl->ProgramString) { + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] ProgramString function missing!\n"); + return; + } + append_template(prog, prog_hdr); + add_scaler(YUV_LUM_SCALER(type), prog, lum_scale_texs, + '0', 'r', rect, texw, texh, params->filter_strength); + add_scaler(YUV_CHROM_SCALER(type), prog, + chrom_scale_texs, '1', 'g', rect, params->chrom_texw, + params->chrom_texh, params->filter_strength); + add_scaler(YUV_CHROM_SCALER(type), prog, + chrom_scale_texs, '2', 'b', rect, params->chrom_texw, + params->chrom_texh, params->filter_strength); + mp_get_yuv2rgb_coeffs(¶ms->csp_params, yuv2rgb); + switch (YUV_CONVERSION(type)) { + case YUV_CONVERSION_FRAGMENT: + append_template(prog, yuv_prog_template); + break; + case YUV_CONVERSION_FRAGMENT_POW: + append_template(prog, yuv_pow_prog_template); + break; + case YUV_CONVERSION_FRAGMENT_LOOKUP: + append_template(prog, yuv_lookup_prog_template); + break; + case YUV_CONVERSION_FRAGMENT_LOOKUP3D: + append_template(prog, yuv_lookup3d_prog_template); + break; + default: + mp_msg(MSGT_VO, MSGL_ERR, "[gl] unknown conversion type %i\n", + YUV_CONVERSION(type)); + break; + } + for (int r = 0; r < 3; r++) { + for (int c = 0; c < 4; c++) { + // "cmRC" + char var[] = { 'c', 'm', '1' + r, '1' + c, '\0' }; + replace_var_float(prog, var, yuv2rgb[r][c]); + } + } + replace_var_float(prog, "gamma_r", (float)1.0 / params->csp_params.rgamma); + replace_var_float(prog, "gamma_g", (float)1.0 / params->csp_params.ggamma); + replace_var_float(prog, "gamma_b", (float)1.0 / params->csp_params.bgamma); + replace_var_char(prog, "conv_tex0", conv_texs[0]); + + if (noise) { + // 1.0 strength is suitable for dithering 8 to 6 bit + double str = params->noise_strength * (1.0 / 64); + double scale_x = (double)NOISE_RES / texw; + double scale_y = (double)NOISE_RES / texh; + if (rect) { + scale_x /= texw; + scale_y /= texh; + } + append_template(prog, noise_filt_template); + replace_var_float(prog, "noise_sx", scale_x); + replace_var_float(prog, "noise_sy", scale_y); + replace_var_char(prog, "noise_filt_tex", filt_texs[0]); + replace_var_float(prog, "noise_str", str); + } + + append_template(prog, "MOV result.color.rgb, res;\nEND"); + + mp_msg(MSGT_VO, MSGL_DBG2, "[gl] generated fragment program:\n%s\n", + yuv_prog); + loadGPUProgram(gl, GL_FRAGMENT_PROGRAM, yuv_prog); + talloc_free(yuv_prog); +} + +/** + * \brief detect the best YUV->RGB conversion method available + */ +int glAutodetectYUVConversion(GL *gl) +{ + const char *extensions = gl->GetString(GL_EXTENSIONS); + if (!extensions || !gl->MultiTexCoord2f) + return YUV_CONVERSION_NONE; + if (strstr(extensions, "GL_ARB_fragment_program")) + return YUV_CONVERSION_FRAGMENT; + if (strstr(extensions, "GL_ATI_text_fragment_shader")) + return YUV_CONVERSION_TEXT_FRAGMENT; + if (strstr(extensions, "GL_ATI_fragment_shader")) + return YUV_CONVERSION_COMBINERS_ATI; + return YUV_CONVERSION_NONE; +} + +/** + * \brief setup YUV->RGB conversion + * \param parms struct containing parameters like conversion and scaler type, + * brightness, ... + * \ingroup glconversion + */ +void glSetupYUVConversion(GL *gl, gl_conversion_params_t *params) +{ + if (params->chrom_texw == 0) + params->chrom_texw = 1; + if (params->chrom_texh == 0) + params->chrom_texh = 1; + switch (YUV_CONVERSION(params->type)) { + case YUV_CONVERSION_COMBINERS_ATI: + glSetupYUVFragmentATI(gl, ¶ms->csp_params, 0); + break; + case YUV_CONVERSION_TEXT_FRAGMENT: + glSetupYUVFragmentATI(gl, ¶ms->csp_params, 1); + break; + case YUV_CONVERSION_FRAGMENT_LOOKUP: + case YUV_CONVERSION_FRAGMENT_LOOKUP3D: + case YUV_CONVERSION_FRAGMENT: + case YUV_CONVERSION_FRAGMENT_POW: + glSetupYUVFragprog(gl, params); + break; + case YUV_CONVERSION_NONE: + break; + default: + mp_msg(MSGT_VO, MSGL_ERR, "[gl] unknown conversion type %i\n", + YUV_CONVERSION(params->type)); + } +} + +/** + * \brief enable the specified YUV conversion + * \param target texture target for Y, U and V textures (e.g. GL_TEXTURE_2D) + * \param type type of YUV conversion + * \ingroup glconversion + */ +void glEnableYUVConversion(GL *gl, GLenum target, int type) +{ + switch (YUV_CONVERSION(type)) { + case YUV_CONVERSION_COMBINERS_ATI: + gl->ActiveTexture(GL_TEXTURE1); + gl->Enable(target); + gl->ActiveTexture(GL_TEXTURE2); + gl->Enable(target); + gl->ActiveTexture(GL_TEXTURE0); + gl->Enable(GL_FRAGMENT_SHADER_ATI); + break; + case YUV_CONVERSION_TEXT_FRAGMENT: + gl->ActiveTexture(GL_TEXTURE1); + gl->Enable(target); + gl->ActiveTexture(GL_TEXTURE2); + gl->Enable(target); + gl->ActiveTexture(GL_TEXTURE0); + gl->Enable(GL_TEXT_FRAGMENT_SHADER_ATI); + break; + case YUV_CONVERSION_FRAGMENT_LOOKUP3D: + case YUV_CONVERSION_FRAGMENT_LOOKUP: + case YUV_CONVERSION_FRAGMENT_POW: + case YUV_CONVERSION_FRAGMENT: + case YUV_CONVERSION_NONE: + gl->Enable(GL_FRAGMENT_PROGRAM); + break; + } +} + +/** + * \brief disable the specified YUV conversion + * \param target texture target for Y, U and V textures (e.g. GL_TEXTURE_2D) + * \param type type of YUV conversion + * \ingroup glconversion + */ +void glDisableYUVConversion(GL *gl, GLenum target, int type) +{ + switch (YUV_CONVERSION(type)) { + case YUV_CONVERSION_COMBINERS_ATI: + gl->ActiveTexture(GL_TEXTURE1); + gl->Disable(target); + gl->ActiveTexture(GL_TEXTURE2); + gl->Disable(target); + gl->ActiveTexture(GL_TEXTURE0); + gl->Disable(GL_FRAGMENT_SHADER_ATI); + break; + case YUV_CONVERSION_TEXT_FRAGMENT: + gl->Disable(GL_TEXT_FRAGMENT_SHADER_ATI); + // HACK: at least the Mac OS X 10.5 PPC Radeon drivers are broken and + // without this disable the texture units while the program is still + // running (10.4 PPC seems to work without this though). + gl->Flush(); + gl->ActiveTexture(GL_TEXTURE1); + gl->Disable(target); + gl->ActiveTexture(GL_TEXTURE2); + gl->Disable(target); + gl->ActiveTexture(GL_TEXTURE0); + break; + case YUV_CONVERSION_FRAGMENT_LOOKUP3D: + case YUV_CONVERSION_FRAGMENT_LOOKUP: + case YUV_CONVERSION_FRAGMENT_POW: + case YUV_CONVERSION_FRAGMENT: + case YUV_CONVERSION_NONE: + gl->Disable(GL_FRAGMENT_PROGRAM); + break; + } +} + +void glEnable3DLeft(GL *gl, int type) +{ + GLint buffer; + switch (type) { + case GL_3D_RED_CYAN: + gl->ColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_FALSE); + break; + case GL_3D_GREEN_MAGENTA: + gl->ColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE); + break; + case GL_3D_QUADBUFFER: + gl->GetIntegerv(GL_DRAW_BUFFER, &buffer); + switch (buffer) { + case GL_FRONT: + case GL_FRONT_LEFT: + case GL_FRONT_RIGHT: + buffer = GL_FRONT_LEFT; + break; + case GL_BACK: + case GL_BACK_LEFT: + case GL_BACK_RIGHT: + buffer = GL_BACK_LEFT; + break; + } + gl->DrawBuffer(buffer); + break; + } +} + +void glEnable3DRight(GL *gl, int type) +{ + GLint buffer; + switch (type) { + case GL_3D_RED_CYAN: + gl->ColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE); + break; + case GL_3D_GREEN_MAGENTA: + gl->ColorMask(GL_TRUE, GL_FALSE, GL_TRUE, GL_FALSE); + break; + case GL_3D_QUADBUFFER: + gl->GetIntegerv(GL_DRAW_BUFFER, &buffer); + switch (buffer) { + case GL_FRONT: + case GL_FRONT_LEFT: + case GL_FRONT_RIGHT: + buffer = GL_FRONT_RIGHT; + break; + case GL_BACK: + case GL_BACK_LEFT: + case GL_BACK_RIGHT: + buffer = GL_BACK_RIGHT; + break; + } + gl->DrawBuffer(buffer); + break; + } +} + +void glDisable3D(GL *gl, int type) +{ + GLint buffer; + switch (type) { + case GL_3D_RED_CYAN: + case GL_3D_GREEN_MAGENTA: + gl->ColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + break; + case GL_3D_QUADBUFFER: + gl->DrawBuffer(GL_BACK); + gl->GetIntegerv(GL_DRAW_BUFFER, &buffer); + switch (buffer) { + case GL_FRONT: + case GL_FRONT_LEFT: + case GL_FRONT_RIGHT: + buffer = GL_FRONT; + break; + case GL_BACK: + case GL_BACK_LEFT: + case GL_BACK_RIGHT: + buffer = GL_BACK; + break; + } + gl->DrawBuffer(buffer); + break; + } +} + +/** + * \brief draw a texture part at given 2D coordinates + * \param x screen top coordinate + * \param y screen left coordinate + * \param w screen width coordinate + * \param h screen height coordinate + * \param tx texture top coordinate in pixels + * \param ty texture left coordinate in pixels + * \param tw texture part width in pixels + * \param th texture part height in pixels + * \param sx width of texture in pixels + * \param sy height of texture in pixels + * \param rect_tex whether this texture uses texture_rectangle extension + * \param is_yv12 if != 0, also draw the textures from units 1 and 2, + * bits 8 - 15 and 16 - 23 specify the x and y scaling of those textures + * \param flip flip the texture upside down + * \ingroup gltexture + */ +void glDrawTex(GL *gl, GLfloat x, GLfloat y, GLfloat w, GLfloat h, + GLfloat tx, GLfloat ty, GLfloat tw, GLfloat th, + int sx, int sy, int rect_tex, int is_yv12, int flip) +{ + int chroma_x_shift = (is_yv12 >> 8) & 31; + int chroma_y_shift = (is_yv12 >> 16) & 31; + GLfloat xscale = 1 << chroma_x_shift; + GLfloat yscale = 1 << chroma_y_shift; + GLfloat tx2 = tx / xscale, ty2 = ty / yscale, tw2 = tw / xscale, th2 = th / yscale; + if (!rect_tex) { + tx /= sx; + ty /= sy; + tw /= sx; + th /= sy; + tx2 = tx, ty2 = ty, tw2 = tw, th2 = th; + } + if (flip) { + y += h; + h = -h; + } + gl->Begin(GL_QUADS); + gl->TexCoord2f(tx, ty); + if (is_yv12) { + gl->MultiTexCoord2f(GL_TEXTURE1, tx2, ty2); + gl->MultiTexCoord2f(GL_TEXTURE2, tx2, ty2); + } + gl->Vertex2f(x, y); + gl->TexCoord2f(tx, ty + th); + if (is_yv12) { + gl->MultiTexCoord2f(GL_TEXTURE1, tx2, ty2 + th2); + gl->MultiTexCoord2f(GL_TEXTURE2, tx2, ty2 + th2); + } + gl->Vertex2f(x, y + h); + gl->TexCoord2f(tx + tw, ty + th); + if (is_yv12) { + gl->MultiTexCoord2f(GL_TEXTURE1, tx2 + tw2, ty2 + th2); + gl->MultiTexCoord2f(GL_TEXTURE2, tx2 + tw2, ty2 + th2); + } + gl->Vertex2f(x + w, y + h); + gl->TexCoord2f(tx + tw, ty); + if (is_yv12) { + gl->MultiTexCoord2f(GL_TEXTURE1, tx2 + tw2, ty2); + gl->MultiTexCoord2f(GL_TEXTURE2, tx2 + tw2, ty2); + } + gl->Vertex2f(x + w, y); + gl->End(); +} + +mp_image_t *glGetWindowScreenshot(GL *gl) +{ + GLint vp[4]; //x, y, w, h + gl->GetIntegerv(GL_VIEWPORT, vp); + mp_image_t *image = alloc_mpi(vp[2], vp[3], IMGFMT_RGB24); + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + gl->PixelStorei(GL_PACK_ALIGNMENT, 0); + gl->PixelStorei(GL_PACK_ROW_LENGTH, 0); + gl->ReadBuffer(GL_FRONT); + //flip image while reading + for (int y = 0; y < vp[3]; y++) { + gl->ReadPixels(vp[0], vp[1] + vp[3] - y - 1, vp[2], 1, + GL_RGB, GL_UNSIGNED_BYTE, + image->planes[0] + y * image->stride[0]); + } + return image; +} + +#ifdef CONFIG_GL_COCOA +#include "cocoa_common.h" + +static bool create_window_cocoa(struct MPGLContext *ctx, uint32_t d_width, + uint32_t d_height, uint32_t flags, bool gl3) +{ + int rv = vo_cocoa_create_window(ctx->vo, d_width, d_height, flags, gl3); + if (rv != 0) + return false; + + getFunctions(ctx->gl, (void *)vo_cocoa_glgetaddr, NULL, gl3); + + if (gl3) { + ctx->depth_r = vo_cocoa_cgl_color_size(ctx->vo); + ctx->depth_g = vo_cocoa_cgl_color_size(ctx->vo); + ctx->depth_b = vo_cocoa_cgl_color_size(ctx->vo); + } + + if (!ctx->gl->SwapInterval) + ctx->gl->SwapInterval = vo_cocoa_swap_interval; + + return true; +} + +static bool create_window_cocoa_old(struct MPGLContext *ctx, uint32_t d_width, + uint32_t d_height, uint32_t flags) +{ + return create_window_cocoa(ctx, d_width, d_height, flags, false); +} + +static bool create_window_cocoa_gl3(struct MPGLContext *ctx, uint32_t d_width, + uint32_t d_height, uint32_t flags) +{ + return create_window_cocoa(ctx, d_width, d_height, flags, true); +} + +static void releaseGlContext_cocoa(MPGLContext *ctx) +{ +} + +static void swapGlBuffers_cocoa(MPGLContext *ctx) +{ + vo_cocoa_swap_buffers(ctx->vo); +} +#endif + +#ifdef CONFIG_GL_WIN32 +#include <windows.h> +#include "w32_common.h" + +struct w32_context { + HGLRC context; +}; + +static void *w32gpa(const GLubyte *procName) +{ + HMODULE oglmod; + void *res = wglGetProcAddress(procName); + if (res) + return res; + oglmod = GetModuleHandle("opengl32.dll"); + return GetProcAddress(oglmod, procName); +} + +static bool create_window_w32_old(struct MPGLContext *ctx, uint32_t d_width, + uint32_t d_height, uint32_t flags) +{ + GL *gl = ctx->gl; + + if (!vo_w32_config(ctx->vo, d_width, d_height, flags)) + return false; + + struct w32_context *w32_ctx = ctx->priv; + HGLRC *context = &w32_ctx->context; + + if (*context) { + gl->Finish(); // supposedly to prevent flickering + return true; + } + + HWND win = ctx->vo->w32->window; + HDC windc = vo_w32_get_dc(ctx->vo, win); + bool res = false; + + HGLRC new_context = wglCreateContext(windc); + if (!new_context) { + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not create GL context!\n"); + goto out; + } + + if (!wglMakeCurrent(windc, new_context)) { + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not set GL context!\n"); + wglDeleteContext(new_context); + goto out; + } + + *context = new_context; + + getFunctions(ctx->gl, w32gpa, NULL, false); + res = true; + +out: + vo_w32_release_dc(ctx->vo, win, windc); + return res; +} + +static bool create_window_w32_gl3(struct MPGLContext *ctx, uint32_t d_width, + uint32_t d_height, uint32_t flags) +{ + if (!vo_w32_config(ctx->vo, d_width, d_height, flags)) + return false; + + struct w32_context *w32_ctx = ctx->priv; + HGLRC *context = &w32_ctx->context; + + if (*context) // reuse existing context + return true; // not reusing it breaks gl3! + + HWND win = ctx->vo->w32->window; + HDC windc = vo_w32_get_dc(ctx->vo, win); + HGLRC new_context = 0; + + new_context = wglCreateContext(windc); + if (!new_context) { + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not create GL context!\n"); + return false; + } + + // set context + if (!wglMakeCurrent(windc, new_context)) { + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not set GL context!\n"); + goto out; + } + + const char *(GLAPIENTRY *wglGetExtensionsStringARB)(HDC hdc) + = w32gpa((const GLubyte*)"wglGetExtensionsStringARB"); + + if (!wglGetExtensionsStringARB) + goto unsupported; + + const char *wgl_exts = wglGetExtensionsStringARB(windc); + if (!strstr(wgl_exts, "WGL_ARB_create_context")) + goto unsupported; + + HGLRC (GLAPIENTRY *wglCreateContextAttribsARB)(HDC hDC, HGLRC hShareContext, + const int *attribList) + = w32gpa((const GLubyte*)"wglCreateContextAttribsARB"); + + if (!wglCreateContextAttribsARB) + goto unsupported; + + int gl_version = ctx->requested_gl_version; + int attribs[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, MPGL_VER_GET_MAJOR(gl_version), + WGL_CONTEXT_MINOR_VERSION_ARB, MPGL_VER_GET_MINOR(gl_version), + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + 0 + }; + + *context = wglCreateContextAttribsARB(windc, 0, attribs); + if (! *context) { + // NVidia, instead of ignoring WGL_CONTEXT_FLAGS_ARB, will error out if + // it's present on pre-3.2 contexts. + // Remove it from attribs and retry the context creation. + attribs[6] = attribs[7] = 0; + *context = wglCreateContextAttribsARB(windc, 0, attribs); + } + if (! *context) { + int err = GetLastError(); + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not create an OpenGL 3.x" + " context: error 0x%x\n", err); + goto out; + } + + wglMakeCurrent(NULL, NULL); + wglDeleteContext(new_context); + + if (!wglMakeCurrent(windc, *context)) { + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not set GL3 context!\n"); + wglDeleteContext(*context); + return false; + } + + /* update function pointers */ + getFunctions(ctx->gl, w32gpa, NULL, true); + + int pfmt = GetPixelFormat(windc); + PIXELFORMATDESCRIPTOR pfd; + if (DescribePixelFormat(windc, pfmt, sizeof(PIXELFORMATDESCRIPTOR), &pfd)) { + ctx->depth_r = pfd.cRedBits; + ctx->depth_g = pfd.cGreenBits; + ctx->depth_b = pfd.cBlueBits; + } + + return true; + +unsupported: + mp_msg(MSGT_VO, MSGL_ERR, "[gl] The current OpenGL implementation does" + " not support OpenGL 3.x \n"); +out: + wglDeleteContext(new_context); + return false; +} + +static void releaseGlContext_w32(MPGLContext *ctx) +{ + struct w32_context *w32_ctx = ctx->priv; + HGLRC *context = &w32_ctx->context; + if (*context) { + wglMakeCurrent(0, 0); + wglDeleteContext(*context); + } + *context = 0; +} + +static void swapGlBuffers_w32(MPGLContext *ctx) +{ + HDC vo_hdc = vo_w32_get_dc(ctx->vo, ctx->vo->w32->window); + SwapBuffers(vo_hdc); + vo_w32_release_dc(ctx->vo, ctx->vo->w32->window, vo_hdc); +} +#endif + +#ifdef CONFIG_GL_X11 +#include <X11/Xlib.h> +#include <GL/glx.h> +#include "x11_common.h" + +struct glx_context { + XVisualInfo *vinfo; + GLXContext context; + GLXFBConfig fbc; +}; + +// The GL3/FBC initialization code roughly follows/copies from: +// http://www.opengl.org/wiki/Tutorial:_OpenGL_3.0_Context_Creation_(GLX) +// but also uses some of the old code. + +static GLXFBConfig select_fb_config(struct vo *vo, const int *attribs) +{ + int fbcount; + GLXFBConfig *fbc = glXChooseFBConfig(vo->x11->display, vo->x11->screen, + attribs, &fbcount); + if (!fbc) + return NULL; + + // The list in fbc is sorted (so that the first element is the best). + GLXFBConfig fbconfig = fbc[0]; + + XFree(fbc); + + return fbconfig; +} + +static bool create_glx_window(struct MPGLContext *ctx, uint32_t d_width, + uint32_t d_height, uint32_t flags) +{ + struct vo *vo = ctx->vo; + struct glx_context *glx_ctx = ctx->priv; + + if (glx_ctx->context) { + // GL context and window already exist. + // Only update window geometry etc. + Colormap colormap = XCreateColormap(vo->x11->display, vo->x11->rootwin, + glx_ctx->vinfo->visual, AllocNone); + vo_x11_create_vo_window(vo, glx_ctx->vinfo, vo->dx, vo->dy, d_width, + d_height, flags, colormap, "gl"); + XFreeColormap(vo->x11->display, colormap); + return true; + } + + int glx_major, glx_minor; + + // FBConfigs were added in GLX version 1.3. + if (!glXQueryVersion(vo->x11->display, &glx_major, &glx_minor) || + (MPGL_VER(glx_major, glx_minor) < MPGL_VER(1, 3))) + { + mp_msg(MSGT_VO, MSGL_ERR, "[gl] GLX version older than 1.3.\n"); + return false; + } + + const int glx_attribs_stereo_value_idx = 1; // index of GLX_STEREO + 1 + int glx_attribs[] = { + GLX_STEREO, False, + GLX_X_RENDERABLE, True, + GLX_RED_SIZE, 1, + GLX_GREEN_SIZE, 1, + GLX_BLUE_SIZE, 1, + GLX_DOUBLEBUFFER, True, + None + }; + GLXFBConfig fbc = NULL; + if (flags & VOFLAG_STEREO) { + glx_attribs[glx_attribs_stereo_value_idx] = True; + fbc = select_fb_config(vo, glx_attribs); + if (!fbc) { + mp_msg(MSGT_VO, MSGL_ERR, "[gl] Could not find a stereo visual," + " 3D will probably not work!\n"); + glx_attribs[glx_attribs_stereo_value_idx] = False; + } + } + if (!fbc) + fbc = select_fb_config(vo, glx_attribs); + if (!fbc) { + mp_msg(MSGT_VO, MSGL_ERR, "[gl] no GLX support present\n"); + return false; + } + + glx_ctx->fbc = fbc; + glx_ctx->vinfo = glXGetVisualFromFBConfig(vo->x11->display, fbc); + + mp_msg(MSGT_VO, MSGL_V, "[gl] GLX chose visual with ID 0x%x\n", + (int)glx_ctx->vinfo->visualid); + + glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_RED_SIZE, &ctx->depth_r); + glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_GREEN_SIZE, &ctx->depth_g); + glXGetFBConfigAttrib(vo->x11->display, fbc, GLX_BLUE_SIZE, &ctx->depth_b); + + Colormap colormap = XCreateColormap(vo->x11->display, vo->x11->rootwin, + glx_ctx->vinfo->visual, AllocNone); + vo_x11_create_vo_window(vo, glx_ctx->vinfo, vo->dx, vo->dy, d_width, + d_height, flags, colormap, "gl"); + XFreeColormap(vo->x11->display, colormap); + + return true; +} + +static bool create_window_x11_old(struct MPGLContext *ctx, uint32_t d_width, + uint32_t d_height, uint32_t flags) +{ + struct glx_context *glx_ctx = ctx->priv; + Display *display = ctx->vo->x11->display; + struct vo *vo = ctx->vo; + GL *gl = ctx->gl; + + if (!create_glx_window(ctx, d_width, d_height, flags)) + return false; + + if (glx_ctx->context) + return true; + + GLXContext new_context = glXCreateContext(display, glx_ctx->vinfo, NULL, + True); + if (!new_context) { + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not create GLX context!\n"); + return false; + } + + if (!glXMakeCurrent(display, ctx->vo->x11->window, new_context)) { + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not set GLX context!\n"); + glXDestroyContext(display, new_context); + return false; + } + + void *(*getProcAddress)(const GLubyte *); + getProcAddress = getdladdr("glXGetProcAddress"); + if (!getProcAddress) + getProcAddress = getdladdr("glXGetProcAddressARB"); + + const char *glxstr = ""; + const char *(*glXExtStr)(Display *, int) + = getdladdr("glXQueryExtensionsString"); + if (glXExtStr) + glxstr = glXExtStr(display, ctx->vo->x11->screen); + + getFunctions(gl, getProcAddress, glxstr, false); + if (!gl->GenPrograms && gl->GetString && + gl->version < MPGL_VER(3, 0) && + getProcAddress && + strstr(gl->GetString(GL_EXTENSIONS), "GL_ARB_vertex_program")) + { + mp_msg(MSGT_VO, MSGL_WARN, + "Broken glXGetProcAddress detected, trying workaround\n"); + getFunctions(gl, NULL, glxstr, false); + } + + glx_ctx->context = new_context; + + if (!glXIsDirect(vo->x11->display, new_context)) + ctx->gl->mpgl_caps &= ~MPGL_CAP_NO_SW; + + return true; +} + +typedef GLXContext (*glXCreateContextAttribsARBProc) + (Display*, GLXFBConfig, GLXContext, Bool, const int*); + +static bool create_window_x11_gl3(struct MPGLContext *ctx, uint32_t d_width, + uint32_t d_height, uint32_t flags) +{ + struct glx_context *glx_ctx = ctx->priv; + struct vo *vo = ctx->vo; + + if (!create_glx_window(ctx, d_width, d_height, flags)) + return false; + + if (glx_ctx->context) + return true; + + glXCreateContextAttribsARBProc glXCreateContextAttribsARB = + (glXCreateContextAttribsARBProc) + glXGetProcAddressARB((const GLubyte *)"glXCreateContextAttribsARB"); + + const char *glxstr = ""; + const char *(*glXExtStr)(Display *, int) + = getdladdr("glXQueryExtensionsString"); + if (glXExtStr) + glxstr = glXExtStr(vo->x11->display, vo->x11->screen); + bool have_ctx_ext = glxstr && !!strstr(glxstr, "GLX_ARB_create_context"); + + if (!(have_ctx_ext && glXCreateContextAttribsARB)) { + return false; + } + + int gl_version = ctx->requested_gl_version; + int context_attribs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, MPGL_VER_GET_MAJOR(gl_version), + GLX_CONTEXT_MINOR_VERSION_ARB, MPGL_VER_GET_MINOR(gl_version), + GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, + GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB + | (flags & VOFLAG_GL_DEBUG ? GLX_CONTEXT_DEBUG_BIT_ARB : 0), + None + }; + GLXContext context = glXCreateContextAttribsARB(vo->x11->display, + glx_ctx->fbc, 0, True, + context_attribs); + if (!context) { + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not create GLX context!\n"); + return false; + } + + // set context + if (!glXMakeCurrent(vo->x11->display, vo->x11->window, context)) { + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Could not set GLX context!\n"); + glXDestroyContext(vo->x11->display, context); + return false; + } + + glx_ctx->context = context; + + getFunctions(ctx->gl, (void *)glXGetProcAddress, glxstr, true); + + if (!glXIsDirect(vo->x11->display, context)) + ctx->gl->mpgl_caps &= ~MPGL_CAP_NO_SW; + + return true; +} + +/** + * \brief free the VisualInfo and GLXContext of an OpenGL context. + * \ingroup glcontext + */ +static void releaseGlContext_x11(MPGLContext *ctx) +{ + struct glx_context *glx_ctx = ctx->priv; + XVisualInfo **vinfo = &glx_ctx->vinfo; + GLXContext *context = &glx_ctx->context; + Display *display = ctx->vo->x11->display; + GL *gl = ctx->gl; + if (*vinfo) + XFree(*vinfo); + *vinfo = NULL; + if (*context) { + if (gl->Finish) + gl->Finish(); + glXMakeCurrent(display, None, NULL); + glXDestroyContext(display, *context); + } + *context = 0; +} + +static void swapGlBuffers_x11(MPGLContext *ctx) +{ + glXSwapBuffers(ctx->vo->x11->display, ctx->vo->x11->window); +} +#endif + + +struct backend { + const char *name; + enum MPGLType type; +}; + +static struct backend backends[] = { + {"auto", GLTYPE_AUTO}, + {"cocoa", GLTYPE_COCOA}, + {"win", GLTYPE_W32}, + {"x11", GLTYPE_X11}, + // mplayer-svn aliases (note that mplayer-svn couples these with the numeric + // values of the internal GLTYPE_* constants) + {"-1", GLTYPE_AUTO}, + { "0", GLTYPE_W32}, + { "1", GLTYPE_X11}, + + {0} +}; + +int mpgl_find_backend(const char *name) +{ + for (const struct backend *entry = backends; entry->name; entry++) { + if (strcmp(entry->name, name) == 0) + return entry->type; + } + return -1; +} + +MPGLContext *mpgl_init(enum MPGLType type, struct vo *vo) +{ + MPGLContext *ctx; + if (type == GLTYPE_AUTO) { + ctx = mpgl_init(GLTYPE_COCOA, vo); + if (ctx) + return ctx; + ctx = mpgl_init(GLTYPE_W32, vo); + if (ctx) + return ctx; + return mpgl_init(GLTYPE_X11, vo); + } + ctx = talloc_zero(NULL, MPGLContext); + *ctx = (MPGLContext) { + .gl = talloc_zero(ctx, GL), + .type = type, + .vo = vo, + .requested_gl_version = MPGL_VER(3, 0), + .vo_init_ok = true, + }; + switch (ctx->type) { +#ifdef CONFIG_GL_COCOA + case GLTYPE_COCOA: + ctx->create_window_old = create_window_cocoa_old; + ctx->create_window_gl3 = create_window_cocoa_gl3; + ctx->releaseGlContext = releaseGlContext_cocoa; + ctx->swapGlBuffers = swapGlBuffers_cocoa; + ctx->check_events = vo_cocoa_check_events; + ctx->update_xinerama_info = vo_cocoa_update_xinerama_info; + ctx->fullscreen = vo_cocoa_fullscreen; + ctx->ontop = vo_cocoa_ontop; + ctx->vo_init = vo_cocoa_init; + ctx->pause = vo_cocoa_pause; + ctx->resume = vo_cocoa_resume; + ctx->vo_uninit = vo_cocoa_uninit; + break; +#endif +#ifdef CONFIG_GL_WIN32 + case GLTYPE_W32: + ctx->priv = talloc_zero(ctx, struct w32_context); + ctx->create_window_old = create_window_w32_old; + ctx->create_window_gl3 = create_window_w32_gl3; + ctx->releaseGlContext = releaseGlContext_w32; + ctx->swapGlBuffers = swapGlBuffers_w32; + ctx->update_xinerama_info = w32_update_xinerama_info; + ctx->border = vo_w32_border; + ctx->check_events = vo_w32_check_events; + ctx->fullscreen = vo_w32_fullscreen; + ctx->ontop = vo_w32_ontop; + ctx->vo_init = vo_w32_init; + ctx->vo_uninit = vo_w32_uninit; + break; +#endif +#ifdef CONFIG_GL_X11 + case GLTYPE_X11: + ctx->priv = talloc_zero(ctx, struct glx_context); + ctx->create_window_old = create_window_x11_old; + ctx->create_window_gl3 = create_window_x11_gl3; + ctx->releaseGlContext = releaseGlContext_x11; + ctx->swapGlBuffers = swapGlBuffers_x11; + ctx->update_xinerama_info = update_xinerama_info; + ctx->border = vo_x11_border; + ctx->check_events = vo_x11_check_events; + ctx->fullscreen = vo_x11_fullscreen; + ctx->ontop = vo_x11_ontop; + ctx->vo_init = vo_init; + ctx->vo_uninit = vo_x11_uninit; + break; +#endif + } + if (ctx->vo_init && ctx->vo_init(vo)) + return ctx; + talloc_free(ctx); + return NULL; +} + +bool mpgl_destroy_window(struct MPGLContext *ctx) +{ + ctx->releaseGlContext(ctx); + *ctx->gl = (GL) {0}; + // This is a caveat. At least on X11, this will recreate the X display + // connection. Also, if vo_init() fails, unspecified things will happen. + ctx->vo_uninit(ctx->vo); + ctx->vo_init_ok = ctx->vo_init(ctx->vo); + return ctx->vo_init_ok; +} + +static bool create_window(struct MPGLContext *ctx, int gl_caps, + bool (*create)(struct MPGLContext *, uint32_t, + uint32_t, uint32_t), + uint32_t d_width, uint32_t d_height, uint32_t flags) +{ + if (!create || !ctx->vo_init_ok) + return false; + if (create(ctx, d_width, d_height, flags)) { + int missing = (ctx->gl->mpgl_caps & gl_caps) ^ gl_caps; + if (!missing) { + ctx->selected_create_window = create; + return true; + } + mp_msg(MSGT_VO, MSGL_WARN, "[gl] Missing OpenGL features:"); + list_features(missing, MSGL_WARN, false); + if (missing & MPGL_CAP_NO_SW) { + mp_msg(MSGT_VO, MSGL_WARN, "[gl] Rejecting suspected software " + "OpenGL renderer.\n"); + } + } + // If we tried to create a GL 3 context, and we're going to create a legacy + // context after this, the window should be recreated at least on X11. + mpgl_destroy_window(ctx); + return false; +} + +bool mpgl_create_window(struct MPGLContext *ctx, int gl_caps, uint32_t d_width, + uint32_t d_height, uint32_t flags) +{ + assert(ctx->vo_init_ok); + if (ctx->selected_create_window) + return ctx->selected_create_window(ctx, d_width, d_height, flags); + + bool allow_gl3 = !(gl_caps & MPGL_CAP_GL_LEGACY); + bool allow_legacy = !(gl_caps & MPGL_CAP_GL3); + gl_caps |= MPGL_CAP_GL; + + if (allow_gl3 && create_window(ctx, gl_caps, ctx->create_window_gl3, + d_width, d_height, flags)) + return true; + + if (allow_legacy && create_window(ctx, gl_caps, ctx->create_window_old, + d_width, d_height, flags)) + return true; + + mp_msg(MSGT_VO, MSGL_ERR, "[gl] OpenGL context creation failed!\n"); + return false; +} + +void mpgl_uninit(MPGLContext *ctx) +{ + if (!ctx) + return; + if (ctx->vo_init_ok) { + ctx->releaseGlContext(ctx); + ctx->vo_uninit(ctx->vo); + } + talloc_free(ctx); +} + +void mp_log_source(int mod, int lev, const char *src) +{ + int line = 1; + if (!src) + return; + while (*src) { + const char *end = strchr(src, '\n'); + const char *next = end + 1; + if (!end) + next = end = src + strlen(src); + mp_msg(mod, lev, "[%3d] %.*s\n", line, (int)(end - src), src); + line++; + src = next; + } +} diff --git a/video/out/gl_common.h b/video/out/gl_common.h new file mode 100644 index 0000000000..9816566097 --- /dev/null +++ b/video/out/gl_common.h @@ -0,0 +1,396 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can alternatively redistribute this file 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. + */ + +#ifndef MPLAYER_GL_COMMON_H +#define MPLAYER_GL_COMMON_H + +#include <stdio.h> +#include <stdint.h> + +#include "config.h" +#include "mp_msg.h" + +#include "video_out.h" +#include "csputils.h" + +#include "libmpcodecs/mp_image.h" + +#if defined(CONFIG_GL_COCOA) && !defined(CONFIG_GL_X11) +#ifdef GL_VERSION_3_0 +#include <OpenGL/gl3.h> +#else +#include <OpenGL/gl.h> +#endif +#include <OpenGL/glext.h> +#else +#include <GL/gl.h> +#include <GL/glext.h> +#endif + +#include "libvo/gl_header_fixes.h" + +struct GL; +typedef struct GL GL; + +void glAdjustAlignment(GL *gl, int stride); + +int glFindFormat(uint32_t format, int have_texture_rg, int *bpp, + GLint *gl_texfmt, GLenum *gl_format, GLenum *gl_type); +int glFmt2bpp(GLenum format, GLenum type); +void glCreateClearTex(GL *gl, GLenum target, GLenum fmt, GLenum format, + GLenum type, GLint filter, int w, int h, + unsigned char val); +int glCreatePPMTex(GL *gl, GLenum target, GLenum fmt, GLint filter, + FILE *f, int *width, int *height, int *maxval); +void glUploadTex(GL *gl, GLenum target, GLenum format, GLenum type, + const void *dataptr, int stride, + int x, int y, int w, int h, int slice); +void glClearTex(GL *gl, GLenum target, GLenum format, GLenum type, + int x, int y, int w, int h, uint8_t val, void **scratch); +void glDownloadTex(GL *gl, GLenum target, GLenum format, GLenum type, + void *dataptr, int stride); +void glDrawTex(GL *gl, GLfloat x, GLfloat y, GLfloat w, GLfloat h, + GLfloat tx, GLfloat ty, GLfloat tw, GLfloat th, + int sx, int sy, int rect_tex, int is_yv12, int flip); +int loadGPUProgram(GL *gl, GLenum target, char *prog); +void glCheckError(GL *gl, const char *info); +mp_image_t *glGetWindowScreenshot(GL *gl); + +/** \addtogroup glconversion + * \{ */ +//! do not use YUV conversion, this should always stay 0 +#define YUV_CONVERSION_NONE 0 +//! use nVidia specific register combiners for YUV conversion +//! implementation has been removed +#define YUV_CONVERSION_COMBINERS 1 +//! use a fragment program for YUV conversion +#define YUV_CONVERSION_FRAGMENT 2 +//! use a fragment program for YUV conversion with gamma using POW +#define YUV_CONVERSION_FRAGMENT_POW 3 +//! use a fragment program with additional table lookup for YUV conversion +#define YUV_CONVERSION_FRAGMENT_LOOKUP 4 +//! use ATI specific register combiners ("fragment program") +#define YUV_CONVERSION_COMBINERS_ATI 5 +//! use a fragment program with 3D table lookup for YUV conversion +#define YUV_CONVERSION_FRAGMENT_LOOKUP3D 6 +//! use ATI specific "text" register combiners ("fragment program") +#define YUV_CONVERSION_TEXT_FRAGMENT 7 +//! use normal bilinear scaling for textures +#define YUV_SCALER_BILIN 0 +//! use higher quality bicubic scaling for textures +#define YUV_SCALER_BICUB 1 +//! use cubic scaling in X and normal linear scaling in Y direction +#define YUV_SCALER_BICUB_X 2 +//! use cubic scaling without additional lookup texture +#define YUV_SCALER_BICUB_NOTEX 3 +#define YUV_SCALER_UNSHARP 4 +#define YUV_SCALER_UNSHARP2 5 +//! mask for conversion type +#define YUV_CONVERSION_MASK 0xF +//! mask for scaler type +#define YUV_SCALER_MASK 0xF +//! shift value for luminance scaler type +#define YUV_LUM_SCALER_SHIFT 8 +//! shift value for chrominance scaler type +#define YUV_CHROM_SCALER_SHIFT 12 +//! extract conversion out of type +#define YUV_CONVERSION(t) ((t) & YUV_CONVERSION_MASK) +//! extract luminance scaler out of type +#define YUV_LUM_SCALER(t) (((t) >> YUV_LUM_SCALER_SHIFT) & YUV_SCALER_MASK) +//! extract chrominance scaler out of type +#define YUV_CHROM_SCALER(t) (((t) >> YUV_CHROM_SCALER_SHIFT) & YUV_SCALER_MASK) +#define SET_YUV_CONVERSION(c) ((c) & YUV_CONVERSION_MASK) +#define SET_YUV_LUM_SCALER(s) (((s) & YUV_SCALER_MASK) << YUV_LUM_SCALER_SHIFT) +#define SET_YUV_CHROM_SCALER(s) (((s) & YUV_SCALER_MASK) << YUV_CHROM_SCALER_SHIFT) +//! returns whether the yuv conversion supports large brightness range etc. +static inline int glYUVLargeRange(int conv) +{ + switch (conv) { + case YUV_CONVERSION_NONE: + case YUV_CONVERSION_COMBINERS_ATI: + case YUV_CONVERSION_FRAGMENT_LOOKUP3D: + case YUV_CONVERSION_TEXT_FRAGMENT: + return 0; + } + return 1; +} +/** \} */ + +typedef struct { + GLenum target; + int type; + struct mp_csp_params csp_params; + int texw; + int texh; + int chrom_texw; + int chrom_texh; + float filter_strength; + float noise_strength; +} gl_conversion_params_t; + +int glAutodetectYUVConversion(GL *gl); +void glSetupYUVConversion(GL *gl, gl_conversion_params_t *params); +void glEnableYUVConversion(GL *gl, GLenum target, int type); +void glDisableYUVConversion(GL *gl, GLenum target, int type); + +#define GL_3D_RED_CYAN 1 +#define GL_3D_GREEN_MAGENTA 2 +#define GL_3D_QUADBUFFER 3 + +void glEnable3DLeft(GL *gl, int type); +void glEnable3DRight(GL *gl, int type); +void glDisable3D(GL *gl, int type); + +enum MPGLType { + GLTYPE_AUTO, + GLTYPE_COCOA, + GLTYPE_W32, + GLTYPE_X11, +}; + +enum { + MPGL_CAP_GL = (1 << 0), // GL was successfully loaded + MPGL_CAP_GL_LEGACY = (1 << 1), // GL 1.1 (but not 3.x) + MPGL_CAP_GL2 = (1 << 2), // GL 2.0 (3.x core subset) + MPGL_CAP_GL21 = (1 << 3), // GL 2.1 (3.x core subset) + MPGL_CAP_GL3 = (1 << 4), // GL 3.x core + MPGL_CAP_FB = (1 << 5), + MPGL_CAP_VAO = (1 << 6), + MPGL_CAP_SRGB_TEX = (1 << 7), + MPGL_CAP_SRGB_FB = (1 << 8), + MPGL_CAP_FLOAT_TEX = (1 << 9), + MPGL_CAP_TEX_RG = (1 << 10), // GL_ARB_texture_rg / GL 3.x + MPGL_CAP_NO_SW = (1 << 30), // used to block sw. renderers +}; + +#define MPGL_VER(major, minor) (((major) << 16) | (minor)) +#define MPGL_VER_GET_MAJOR(ver) ((ver) >> 16) +#define MPGL_VER_GET_MINOR(ver) ((ver) & ((1 << 16) - 1)) + +#define MPGL_VER_P(ver) MPGL_VER_GET_MAJOR(ver), MPGL_VER_GET_MINOR(ver) + +typedef struct MPGLContext { + GL *gl; + enum MPGLType type; + struct vo *vo; + + // Bit size of each component in the created framebuffer. 0 if unknown. + int depth_r, depth_g, depth_b; + + // GL version requested from create_window_gl3 backend. + // (Might be different from the actual version in gl->version.) + int requested_gl_version; + + void (*swapGlBuffers)(struct MPGLContext *); + int (*check_events)(struct vo *vo); + void (*fullscreen)(struct vo *vo); + int (*vo_init)(struct vo *vo); + void (*vo_uninit)(struct vo *vo); + void (*releaseGlContext)(struct MPGLContext *); + + // Creates GL 1.x/2.x legacy context. + bool (*create_window_old)(struct MPGLContext *ctx, uint32_t d_width, + uint32_t d_height, uint32_t flags); + + // Creates GL 3.x core context. + bool (*create_window_gl3)(struct MPGLContext *ctx, uint32_t d_width, + uint32_t d_height, uint32_t flags); + + // optional + void (*pause)(struct vo *vo); + void (*resume)(struct vo *vo); + void (*ontop)(struct vo *vo); + void (*border)(struct vo *vo); + void (*update_xinerama_info)(struct vo *vo); + + // For free use by the backend. + void *priv; + // Internal to gl_common.c. + bool (*selected_create_window)(struct MPGLContext *ctx, uint32_t d_width, + uint32_t d_height, uint32_t flags); + bool vo_init_ok; +} MPGLContext; + +int mpgl_find_backend(const char *name); + +MPGLContext *mpgl_init(enum MPGLType type, struct vo *vo); +void mpgl_uninit(MPGLContext *ctx); + +// Create a VO window and create a GL context on it. +// (Calls create_window_gl3 or create_window+setGlWindow.) +// gl_caps: bitfield of MPGL_CAP_* (required GL version and feature set) +// flags: passed to the backend's create window function +// Returns success. +bool mpgl_create_window(struct MPGLContext *ctx, int gl_caps, uint32_t d_width, + uint32_t d_height, uint32_t flags); + +// Destroy the window, without resetting GL3 vs. GL2 context choice. +// If this fails (false), mpgl_uninit(ctx) must be called. +bool mpgl_destroy_window(struct MPGLContext *ctx); + +// print a multi line string with line numbers (e.g. for shader sources) +// mod, lev: module and log level, as in mp_msg() +void mp_log_source(int mod, int lev, const char *src); + +//function pointers loaded from the OpenGL library +struct GL { + int version; // MPGL_VER() mangled + int glsl_version; // e.g. 130 for GLSL 1.30 + char *extensions; // Equivalent to GL_EXTENSIONS + int mpgl_caps; // Bitfield of MPGL_CAP_* constants + + void (GLAPIENTRY *Begin)(GLenum); + void (GLAPIENTRY *End)(void); + void (GLAPIENTRY *Viewport)(GLint, GLint, GLsizei, GLsizei); + void (GLAPIENTRY *MatrixMode)(GLenum); + void (GLAPIENTRY *LoadIdentity)(void); + void (GLAPIENTRY *Translated)(double, double, double); + void (GLAPIENTRY *Scaled)(double, double, double); + void (GLAPIENTRY *Ortho)(double, double, double, double, double,double); + void (GLAPIENTRY *PushMatrix)(void); + void (GLAPIENTRY *PopMatrix)(void); + void (GLAPIENTRY *Clear)(GLbitfield); + GLuint (GLAPIENTRY *GenLists)(GLsizei); + void (GLAPIENTRY *DeleteLists)(GLuint, GLsizei); + void (GLAPIENTRY *NewList)(GLuint, GLenum); + void (GLAPIENTRY *EndList)(void); + void (GLAPIENTRY *CallList)(GLuint); + void (GLAPIENTRY *CallLists)(GLsizei, GLenum, const GLvoid *); + void (GLAPIENTRY *GenTextures)(GLsizei, GLuint *); + void (GLAPIENTRY *DeleteTextures)(GLsizei, const GLuint *); + void (GLAPIENTRY *TexEnvi)(GLenum, GLenum, GLint); + void (GLAPIENTRY *Color4ub)(GLubyte, GLubyte, GLubyte, GLubyte); + void (GLAPIENTRY *Color4f)(GLfloat, GLfloat, GLfloat, GLfloat); + void (GLAPIENTRY *ClearColor)(GLclampf, GLclampf, GLclampf, GLclampf); + void (GLAPIENTRY *Enable)(GLenum); + void (GLAPIENTRY *Disable)(GLenum); + const GLubyte *(GLAPIENTRY * GetString)(GLenum); + void (GLAPIENTRY *DrawBuffer)(GLenum); + void (GLAPIENTRY *DepthMask)(GLboolean); + void (GLAPIENTRY *BlendFunc)(GLenum, GLenum); + void (GLAPIENTRY *Flush)(void); + void (GLAPIENTRY *Finish)(void); + void (GLAPIENTRY *PixelStorei)(GLenum, GLint); + void (GLAPIENTRY *TexImage1D)(GLenum, GLint, GLint, GLsizei, GLint, + GLenum, GLenum, const GLvoid *); + void (GLAPIENTRY *TexImage2D)(GLenum, GLint, GLint, GLsizei, GLsizei, + GLint, GLenum, GLenum, const GLvoid *); + void (GLAPIENTRY *TexSubImage2D)(GLenum, GLint, GLint, GLint, + GLsizei, GLsizei, GLenum, GLenum, + const GLvoid *); + void (GLAPIENTRY *GetTexImage)(GLenum, GLint, GLenum, GLenum, GLvoid *); + void (GLAPIENTRY *TexParameteri)(GLenum, GLenum, GLint); + void (GLAPIENTRY *TexParameterf)(GLenum, GLenum, GLfloat); + void (GLAPIENTRY *TexParameterfv)(GLenum, GLenum, const GLfloat *); + void (GLAPIENTRY *TexCoord2f)(GLfloat, GLfloat); + void (GLAPIENTRY *TexCoord2fv)(const GLfloat *); + void (GLAPIENTRY *Vertex2f)(GLfloat, GLfloat); + void (GLAPIENTRY *GetIntegerv)(GLenum, GLint *); + void (GLAPIENTRY *GetBooleanv)(GLenum, GLboolean *); + void (GLAPIENTRY *ColorMask)(GLboolean, GLboolean, GLboolean, GLboolean); + void (GLAPIENTRY *ReadPixels)(GLint, GLint, GLsizei, GLsizei, GLenum, + GLenum, GLvoid *); + void (GLAPIENTRY *ReadBuffer)(GLenum); + void (GLAPIENTRY *VertexPointer)(GLint, GLenum, GLsizei, const GLvoid *); + void (GLAPIENTRY *ColorPointer)(GLint, GLenum, GLsizei, const GLvoid *); + void (GLAPIENTRY *TexCoordPointer)(GLint, GLenum, GLsizei, const GLvoid *); + void (GLAPIENTRY *DrawArrays)(GLenum, GLint, GLsizei); + void (GLAPIENTRY *EnableClientState)(GLenum); + void (GLAPIENTRY *DisableClientState)(GLenum); + GLenum (GLAPIENTRY *GetError)(void); + + void (GLAPIENTRY *GenBuffers)(GLsizei, GLuint *); + void (GLAPIENTRY *DeleteBuffers)(GLsizei, const GLuint *); + void (GLAPIENTRY *BindBuffer)(GLenum, GLuint); + GLvoid * (GLAPIENTRY * MapBuffer)(GLenum, GLenum); + GLboolean (GLAPIENTRY *UnmapBuffer)(GLenum); + void (GLAPIENTRY *BufferData)(GLenum, intptr_t, const GLvoid *, GLenum); + void (GLAPIENTRY *ActiveTexture)(GLenum); + void (GLAPIENTRY *BindTexture)(GLenum, GLuint); + void (GLAPIENTRY *MultiTexCoord2f)(GLenum, GLfloat, GLfloat); + void (GLAPIENTRY *GenPrograms)(GLsizei, GLuint *); + void (GLAPIENTRY *DeletePrograms)(GLsizei, const GLuint *); + void (GLAPIENTRY *BindProgram)(GLenum, GLuint); + void (GLAPIENTRY *ProgramString)(GLenum, GLenum, GLsizei, const GLvoid *); + void (GLAPIENTRY *GetProgramivARB)(GLenum, GLenum, GLint *); + void (GLAPIENTRY *ProgramEnvParameter4f)(GLenum, GLuint, GLfloat, GLfloat, + GLfloat, GLfloat); + int (GLAPIENTRY *SwapInterval)(int); + void (GLAPIENTRY *TexImage3D)(GLenum, GLint, GLenum, GLsizei, GLsizei, + GLsizei, GLint, GLenum, GLenum, + const GLvoid *); + + void (GLAPIENTRY *BeginFragmentShader)(void); + void (GLAPIENTRY *EndFragmentShader)(void); + void (GLAPIENTRY *SampleMap)(GLuint, GLuint, GLenum); + void (GLAPIENTRY *ColorFragmentOp2)(GLenum, GLuint, GLuint, GLuint, GLuint, + GLuint, GLuint, GLuint, GLuint, GLuint); + void (GLAPIENTRY *ColorFragmentOp3)(GLenum, GLuint, GLuint, GLuint, GLuint, + GLuint, GLuint, GLuint, GLuint, GLuint, + GLuint, GLuint, GLuint); + void (GLAPIENTRY *SetFragmentShaderConstant)(GLuint, const GLfloat *); + + void (GLAPIENTRY *GenVertexArrays)(GLsizei, GLuint *); + void (GLAPIENTRY *BindVertexArray)(GLuint); + GLint (GLAPIENTRY *GetAttribLocation)(GLuint, const GLchar *); + void (GLAPIENTRY *EnableVertexAttribArray)(GLuint); + void (GLAPIENTRY *DisableVertexAttribArray)(GLuint); + void (GLAPIENTRY *VertexAttribPointer)(GLuint, GLint, GLenum, GLboolean, + GLsizei, const GLvoid *); + void (GLAPIENTRY *DeleteVertexArrays)(GLsizei, const GLuint *); + void (GLAPIENTRY *UseProgram)(GLuint); + GLint (GLAPIENTRY *GetUniformLocation)(GLuint, const GLchar *); + void (GLAPIENTRY *CompileShader)(GLuint); + GLuint (GLAPIENTRY *CreateProgram)(void); + GLuint (GLAPIENTRY *CreateShader)(GLenum); + void (GLAPIENTRY *ShaderSource)(GLuint, GLsizei, const GLchar **, + const GLint *); + void (GLAPIENTRY *LinkProgram)(GLuint); + void (GLAPIENTRY *AttachShader)(GLuint, GLuint); + void (GLAPIENTRY *DeleteShader)(GLuint); + void (GLAPIENTRY *DeleteProgram)(GLuint); + void (GLAPIENTRY *GetShaderInfoLog)(GLuint, GLsizei, GLsizei *, GLchar *); + void (GLAPIENTRY *GetShaderiv)(GLuint, GLenum, GLint *); + void (GLAPIENTRY *GetProgramInfoLog)(GLuint, GLsizei, GLsizei *, GLchar *); + void (GLAPIENTRY *GetProgramiv)(GLenum, GLenum, GLint *); + const GLubyte* (GLAPIENTRY *GetStringi)(GLenum, GLuint); + void (GLAPIENTRY *BindAttribLocation)(GLuint, GLuint, const GLchar *); + void (GLAPIENTRY *BindFramebuffer)(GLenum, GLuint); + void (GLAPIENTRY *GenFramebuffers)(GLsizei, GLuint *); + void (GLAPIENTRY *DeleteFramebuffers)(GLsizei, const GLuint *); + GLenum (GLAPIENTRY *CheckFramebufferStatus)(GLenum); + void (GLAPIENTRY *FramebufferTexture2D)(GLenum, GLenum, GLenum, GLuint, + GLint); + + void (GLAPIENTRY *Uniform1f)(GLint, GLfloat); + void (GLAPIENTRY *Uniform2f)(GLint, GLfloat, GLfloat); + void (GLAPIENTRY *Uniform3f)(GLint, GLfloat, GLfloat, GLfloat); + void (GLAPIENTRY *Uniform4f)(GLint, GLfloat, GLfloat, GLfloat, GLfloat); + void (GLAPIENTRY *Uniform1i)(GLint, GLint); + void (GLAPIENTRY *UniformMatrix3fv)(GLint, GLsizei, GLboolean, + const GLfloat *); + void (GLAPIENTRY *UniformMatrix4x3fv)(GLint, GLsizei, GLboolean, + const GLfloat *); +}; + +#endif /* MPLAYER_GL_COMMON_H */ diff --git a/video/out/gl_header_fixes.h b/video/out/gl_header_fixes.h new file mode 100644 index 0000000000..d149a9970a --- /dev/null +++ b/video/out/gl_header_fixes.h @@ -0,0 +1,257 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can alternatively redistribute this file 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. + */ + +// workaround for some gl.h headers +#ifndef GLAPIENTRY +#ifdef APIENTRY +#define GLAPIENTRY APIENTRY +#elif defined(CONFIG_GL_WIN32) +#define GLAPIENTRY __stdcall +#else +#define GLAPIENTRY +#endif +#endif + +/** + * \defgroup glextdefines OpenGL extension defines + * + * conditionally define all extension defines used. + * vendor specific extensions should be marked as such + * (e.g. _NV), _ARB is not used to ease readability. + * \{ + */ +#ifndef GL_TEXTURE_3D +#define GL_TEXTURE_3D 0x806F +#endif +#ifndef GL_TEXTURE_WRAP_R +#define GL_TEXTURE_WRAP_R 0x8072 +#endif +#ifndef GL_CLAMP_TO_EDGE +#define GL_CLAMP_TO_EDGE 0x812F +#endif +#ifndef GL_GENERATE_MIPMAP +#define GL_GENERATE_MIPMAP 0x8191 +#endif +#ifndef GL_TEXT_FRAGMENT_SHADER_ATI +#define GL_TEXT_FRAGMENT_SHADER_ATI 0x8200 +#endif +#ifndef GL_FRAGMENT_SHADER_ATI +#define GL_FRAGMENT_SHADER_ATI 0x8920 +#endif +#ifndef GL_NUM_FRAGMENT_REGISTERS_ATI +#define GL_NUM_FRAGMENT_REGISTERS_ATI 0x896E +#endif +#ifndef GL_REG_0_ATI +#define GL_REG_0_ATI 0x8921 +#endif +#ifndef GL_REG_1_ATI +#define GL_REG_1_ATI 0x8922 +#endif +#ifndef GL_REG_2_ATI +#define GL_REG_2_ATI 0x8923 +#endif +#ifndef GL_CON_0_ATI +#define GL_CON_0_ATI 0x8941 +#endif +#ifndef GL_CON_1_ATI +#define GL_CON_1_ATI 0x8942 +#endif +#ifndef GL_CON_2_ATI +#define GL_CON_2_ATI 0x8943 +#endif +#ifndef GL_CON_3_ATI +#define GL_CON_3_ATI 0x8944 +#endif +#ifndef GL_ADD_ATI +#define GL_ADD_ATI 0x8963 +#endif +#ifndef GL_MUL_ATI +#define GL_MUL_ATI 0x8964 +#endif +#ifndef GL_MAD_ATI +#define GL_MAD_ATI 0x8968 +#endif +#ifndef GL_SWIZZLE_STR_ATI +#define GL_SWIZZLE_STR_ATI 0x8976 +#endif +#ifndef GL_4X_BIT_ATI +#define GL_4X_BIT_ATI 2 +#endif +#ifndef GL_8X_BIT_ATI +#define GL_8X_BIT_ATI 4 +#endif +#ifndef GL_BIAS_BIT_ATI +#define GL_BIAS_BIT_ATI 8 +#endif +#ifndef GL_MAX_TEXTURE_UNITS +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#endif +#ifndef GL_TEXTURE0 +#define GL_TEXTURE0 0x84C0 +#endif +#ifndef GL_TEXTURE1 +#define GL_TEXTURE1 0x84C1 +#endif +#ifndef GL_TEXTURE2 +#define GL_TEXTURE2 0x84C2 +#endif +#ifndef GL_TEXTURE3 +#define GL_TEXTURE3 0x84C3 +#endif +#ifndef GL_TEXTURE_RECTANGLE +#define GL_TEXTURE_RECTANGLE 0x84F5 +#endif +#ifndef GL_PIXEL_UNPACK_BUFFER +#define GL_PIXEL_UNPACK_BUFFER 0x88EC +#endif +#ifndef GL_STREAM_DRAW +#define GL_STREAM_DRAW 0x88E0 +#endif +#ifndef GL_DYNAMIC_DRAW +#define GL_DYNAMIC_DRAW 0x88E8 +#endif +#ifndef GL_WRITE_ONLY +#define GL_WRITE_ONLY 0x88B9 +#endif +#ifndef GL_BGR +#define GL_BGR 0x80E0 +#endif +#ifndef GL_BGRA +#define GL_BGRA 0x80E1 +#endif +#ifndef GL_UNSIGNED_BYTE_3_3_2 +#define GL_UNSIGNED_BYTE_3_3_2 0x8032 +#endif +#ifndef GL_UNSIGNED_BYTE_2_3_3_REV +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#endif +#ifndef GL_UNSIGNED_SHORT_4_4_4_4 +#define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#endif +#ifndef GL_UNSIGNED_SHORT_4_4_4_4_REV +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#endif +#ifndef GL_UNSIGNED_SHORT_5_6_5 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#endif +#ifndef GL_UNSIGNED_INT_8_8_8_8 +#define GL_UNSIGNED_INT_8_8_8_8 0x8035 +#endif +#ifndef GL_UNSIGNED_INT_8_8_8_8_REV +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#endif +#ifndef GL_UNSIGNED_SHORT_5_6_5_REV +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#endif +#ifndef GL_UNSIGNED_INT_10_10_10_2 +#define GL_UNSIGNED_INT_10_10_10_2 0x8036 +#endif +#ifndef GL_UNSIGNED_INT_2_10_10_10_REV +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#endif +#ifndef GL_UNSIGNED_SHORT_5_5_5_1 +#define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 +#endif +#ifndef GL_UNSIGNED_SHORT_1_5_5_5_REV +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#endif +#ifndef GL_UNSIGNED_SHORT_8_8 +#define GL_UNSIGNED_SHORT_8_8 0x85BA +#endif +#ifndef GL_UNSIGNED_SHORT_8_8_REV +#define GL_UNSIGNED_SHORT_8_8_REV 0x85BB +#endif +#ifndef GL_YCBCR_MESA +#define GL_YCBCR_MESA 0x8757 +#endif +#ifndef GL_RGB32F +#define GL_RGB32F 0x8815 +#endif +#ifndef GL_FLOAT_RGB32_NV +#define GL_FLOAT_RGB32_NV 0x8889 +#endif +#ifndef GL_LUMINANCE16 +#define GL_LUMINANCE16 0x8042 +#endif +#ifndef GL_R16 +#define GL_R16 0x822A +#endif +#ifndef GL_UNPACK_CLIENT_STORAGE_APPLE +#define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2 +#endif +#ifndef GL_FRAGMENT_PROGRAM +#define GL_FRAGMENT_PROGRAM 0x8804 +#endif +#ifndef GL_PROGRAM_FORMAT_ASCII +#define GL_PROGRAM_FORMAT_ASCII 0x8875 +#endif +#ifndef GL_PROGRAM_ERROR_POSITION +#define GL_PROGRAM_ERROR_POSITION 0x864B +#endif +#ifndef GL_MAX_TEXTURE_IMAGE_UNITS +#define GL_MAX_TEXTURE_IMAGE_UNITS 0x8872 +#endif +#ifndef GL_PROGRAM_ERROR_STRING +#define GL_PROGRAM_ERROR_STRING 0x8874 +#endif +/** \} */ // end of glextdefines group + + +#if defined(CONFIG_GL_WIN32) && !defined(WGL_CONTEXT_MAJOR_VERSION_ARB) +/* these are supposed to be defined in wingdi.h but mingw's is too old */ +/* only the bits actually used by mplayer are defined */ +/* reference: http://www.opengl.org/registry/specs/ARB/wgl_create_context.txt */ + +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#endif + +// Define just enough constants to make the OpenGL 3 code compile against +// older SDKs. Values are taken straight from OpenGL/gl3.h +#if defined __APPLE__ && !(defined GL_VERSION_3_0) +#define GL_RGBA16F 0x881A +#define GL_RGB16F 0x881B +#define GL_MAJOR_VERSION 0x821B +#define GL_MINOR_VERSION 0x821C +#define GL_NUM_EXTENSIONS 0x821D + +#ifndef GL_ARB_framebuffer_sRGB +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#endif +#endif + +// FreeBSD 10.0-CURRENT lacks the GLX_ARB_create_context extension completely +#ifndef GLX_CONTEXT_MAJOR_VERSION_ARB +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define GLX_CONTEXT_FLAGS_ARB 0x2094 +#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define GLX_CONTEXT_DEBUG_BIT_ARB 0x0001 +#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 +#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#endif diff --git a/video/out/gl_osd.c b/video/out/gl_osd.c new file mode 100644 index 0000000000..81485cabe9 --- /dev/null +++ b/video/out/gl_osd.c @@ -0,0 +1,324 @@ +/* + * This file is part of mplayer. + * + * mplayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mplayer. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdlib.h> +#include <assert.h> +#include <libavutil/common.h> + +#include "bitmap_packer.h" + +#include "gl_osd.h" + +struct osd_fmt_entry { + GLint internal_format; + GLint format; + GLenum type; +}; + +// glBlendFunc() arguments +static const int blend_factors[SUBBITMAP_COUNT][2] = { + [SUBBITMAP_LIBASS] = {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA}, + [SUBBITMAP_RGBA] = {GL_ONE, GL_ONE_MINUS_SRC_ALPHA}, +}; + +static const struct osd_fmt_entry osd_to_gl3_formats[SUBBITMAP_COUNT] = { + [SUBBITMAP_LIBASS] = {GL_RED, GL_RED, GL_UNSIGNED_BYTE}, + [SUBBITMAP_RGBA] = {GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE}, +}; + +static const struct osd_fmt_entry osd_to_gl_legacy_formats[SUBBITMAP_COUNT] = { + [SUBBITMAP_LIBASS] = {GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE}, + [SUBBITMAP_RGBA] = {GL_RGBA, GL_BGRA, GL_UNSIGNED_BYTE}, +}; + +struct mpgl_osd *mpgl_osd_init(GL *gl, bool legacy) +{ + GLint max_texture_size; + gl->GetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size); + + struct mpgl_osd *ctx = talloc_ptrtype(NULL, ctx); + *ctx = (struct mpgl_osd) { + .gl = gl, + .fmt_table = legacy ? osd_to_gl_legacy_formats : osd_to_gl3_formats, + .scratch = talloc_zero_size(ctx, 1), + }; + + for (int n = 0; n < MAX_OSD_PARTS; n++) { + struct mpgl_osd_part *p = talloc_ptrtype(ctx, p); + *p = (struct mpgl_osd_part) { + .packer = talloc_struct(p, struct bitmap_packer, { + .w_max = max_texture_size, + .h_max = max_texture_size, + }), + }; + ctx->parts[n] = p; + } + + for (int n = 0; n < SUBBITMAP_COUNT; n++) + ctx->formats[n] = ctx->fmt_table[n].type != 0; + + return ctx; +} + +void mpgl_osd_destroy(struct mpgl_osd *ctx) +{ + GL *gl = ctx->gl; + + for (int n = 0; n < MAX_OSD_PARTS; n++) { + struct mpgl_osd_part *p = ctx->parts[n]; + gl->DeleteTextures(1, &p->texture); + if (gl->DeleteBuffers) + gl->DeleteBuffers(1, &p->buffer); + } + talloc_free(ctx); +} + +static bool upload_pbo(struct mpgl_osd *ctx, struct mpgl_osd_part *osd, + struct sub_bitmaps *imgs) +{ + GL *gl = ctx->gl; + bool success = true; + struct osd_fmt_entry fmt = ctx->fmt_table[imgs->format]; + int pix_stride = glFmt2bpp(fmt.format, fmt.type); + + if (!osd->buffer) { + gl->GenBuffers(1, &osd->buffer); + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, osd->buffer); + gl->BufferData(GL_PIXEL_UNPACK_BUFFER, osd->w * osd->h * pix_stride, + NULL, GL_DYNAMIC_COPY); + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, osd->buffer); + char *data = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); + if (!data) { + success = false; + } else { + struct pos bb[2]; + packer_get_bb(osd->packer, bb); + size_t stride = osd->w * pix_stride; + packer_copy_subbitmaps(osd->packer, imgs, data, pix_stride, stride); + if (!gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER)) + success = false; + glUploadTex(gl, GL_TEXTURE_2D, fmt.format, fmt.type, NULL, stride, + bb[0].x, bb[0].y, bb[1].x - bb[0].x, bb[1].y - bb[0].y, + 0); + } + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + if (!success) { + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Error: can't upload subtitles! " + "Remove the 'pbo' suboption.\n"); + } + + return success; +} + +static void upload_tex(struct mpgl_osd *ctx, struct mpgl_osd_part *osd, + struct sub_bitmaps *imgs) +{ + struct osd_fmt_entry fmt = ctx->fmt_table[imgs->format]; + if (osd->packer->padding) { + struct pos bb[2]; + packer_get_bb(osd->packer, bb); + glClearTex(ctx->gl, GL_TEXTURE_2D, fmt.format, fmt.type, + bb[0].x, bb[0].y, bb[1].x - bb[0].y, bb[1].y - bb[0].y, + 0, &ctx->scratch); + } + for (int n = 0; n < osd->packer->count; n++) { + struct sub_bitmap *s = &imgs->parts[n]; + struct pos p = osd->packer->result[n]; + + glUploadTex(ctx->gl, GL_TEXTURE_2D, fmt.format, fmt.type, + s->bitmap, s->stride, p.x, p.y, s->w, s->h, 0); + } +} + +static bool upload_osd(struct mpgl_osd *ctx, struct mpgl_osd_part *osd, + struct sub_bitmaps *imgs) +{ + GL *gl = ctx->gl; + + // assume 2x2 filter on scaling + osd->packer->padding = ctx->scaled || imgs->scaled; + int r = packer_pack_from_subbitmaps(osd->packer, imgs); + if (r < 0) { + mp_msg(MSGT_VO, MSGL_ERR, "[gl] OSD bitmaps do not fit on " + "a surface with the maximum supported size %dx%d.\n", + osd->packer->w_max, osd->packer->h_max); + return false; + } + + struct osd_fmt_entry fmt = ctx->fmt_table[imgs->format]; + assert(fmt.type != 0); + + if (!osd->texture) + gl->GenTextures(1, &osd->texture); + + gl->BindTexture(GL_TEXTURE_2D, osd->texture); + + if (osd->packer->w > osd->w || osd->packer->h > osd->h + || osd->format != imgs->format) + { + osd->format = imgs->format; + osd->w = FFMAX(32, osd->packer->w); + osd->h = FFMAX(32, osd->packer->h); + + gl->TexImage2D(GL_TEXTURE_2D, 0, fmt.internal_format, osd->w, osd->h, + 0, fmt.format, fmt.type, NULL); + + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + if (gl->DeleteBuffers) + gl->DeleteBuffers(1, &osd->buffer); + osd->buffer = 0; + } + + bool uploaded = false; + if (ctx->use_pbo) + uploaded = upload_pbo(ctx, osd, imgs); + if (!uploaded) + upload_tex(ctx, osd, imgs); + + gl->BindTexture(GL_TEXTURE_2D, 0); + + return true; +} + +struct mpgl_osd_part *mpgl_osd_generate(struct mpgl_osd *ctx, + struct sub_bitmaps *imgs) +{ + if (imgs->num_parts == 0 || !ctx->formats[imgs->format]) + return NULL; + + struct mpgl_osd_part *osd = ctx->parts[imgs->render_index]; + + if (imgs->bitmap_pos_id != osd->bitmap_pos_id) { + if (imgs->bitmap_id != osd->bitmap_id) { + if (!upload_osd(ctx, osd, imgs)) + osd->packer->count = 0; + } + + osd->bitmap_id = imgs->bitmap_id; + osd->bitmap_pos_id = imgs->bitmap_pos_id; + osd->num_vertices = 0; + } + + return osd->packer->count ? osd : NULL; +} + +void mpgl_osd_set_gl_state(struct mpgl_osd *ctx, struct mpgl_osd_part *p) +{ + GL *gl = ctx->gl; + + gl->BindTexture(GL_TEXTURE_2D, p->texture); + gl->Enable(GL_BLEND); + gl->BlendFunc(blend_factors[p->format][0], blend_factors[p->format][1]); +} + +void mpgl_osd_unset_gl_state(struct mpgl_osd *ctx, struct mpgl_osd_part *p) +{ + GL *gl = ctx->gl; + + gl->Disable(GL_BLEND); + gl->BindTexture(GL_TEXTURE_2D, 0); +} + +struct vertex { + float position[2]; + uint8_t color[4]; + float texcoord[2]; +}; + +static void draw_legacy_cb(void *pctx, struct sub_bitmaps *imgs) +{ + struct mpgl_osd *ctx = pctx; + struct mpgl_osd_part *osd = mpgl_osd_generate(ctx, imgs); + if (!osd) + return; + + if (!osd->num_vertices) { + // 2 triangles primitives per quad = 6 vertices per quad + // not using GL_QUADS, as it is deprecated in OpenGL 3.x and later + osd->vertices = talloc_realloc(osd, osd->vertices, struct vertex, + osd->packer->count * 6); + + struct vertex *va = osd->vertices; + float tex_w = osd->w; + float tex_h = osd->h; + + for (int n = 0; n < osd->packer->count; n++) { + struct sub_bitmap *b = &imgs->parts[n]; + struct pos p = osd->packer->result[n]; + + uint32_t c = imgs->format == SUBBITMAP_LIBASS + ? b->libass.color : 0xFFFFFF00; + uint8_t color[4] = { c >> 24, (c >> 16) & 0xff, + (c >> 8) & 0xff, 255 - (c & 0xff) }; + + float x0 = b->x; + float y0 = b->y; + float x1 = b->x + b->dw; + float y1 = b->y + b->dh; + float tx0 = p.x / tex_w; + float ty0 = p.y / tex_h; + float tx1 = (p.x + b->w) / tex_w; + float ty1 = (p.y + b->h) / tex_h; + +#define COLOR_INIT {color[0], color[1], color[2], color[3]} + struct vertex *v = &va[osd->num_vertices]; + v[0] = (struct vertex) { {x0, y0}, COLOR_INIT, {tx0, ty0} }; + v[1] = (struct vertex) { {x0, y1}, COLOR_INIT, {tx0, ty1} }; + v[2] = (struct vertex) { {x1, y0}, COLOR_INIT, {tx1, ty0} }; + v[3] = (struct vertex) { {x1, y1}, COLOR_INIT, {tx1, ty1} }; + v[4] = v[2]; + v[5] = v[1]; +#undef COLOR_INIT + osd->num_vertices += 6; + } + } + + GL *gl = ctx->gl; + + struct vertex *va = osd->vertices; + size_t stride = sizeof(va[0]); + + gl->VertexPointer(2, GL_FLOAT, stride, &va[0].position[0]); + gl->ColorPointer(4, GL_UNSIGNED_BYTE, stride, &va[0].color[0]); + gl->TexCoordPointer(2, GL_FLOAT, stride, &va[0].texcoord[0]); + + gl->EnableClientState(GL_VERTEX_ARRAY); + gl->EnableClientState(GL_TEXTURE_COORD_ARRAY); + gl->EnableClientState(GL_COLOR_ARRAY); + + mpgl_osd_set_gl_state(ctx, osd); + gl->DrawArrays(GL_TRIANGLES, 0, osd->num_vertices); + mpgl_osd_unset_gl_state(ctx, osd); + + gl->DisableClientState(GL_VERTEX_ARRAY); + gl->DisableClientState(GL_TEXTURE_COORD_ARRAY); + gl->DisableClientState(GL_COLOR_ARRAY); +} + +void mpgl_osd_draw_legacy(struct mpgl_osd *ctx, struct osd_state *osd, + struct mp_osd_res res) +{ + osd_draw(osd, res, osd->vo_pts, 0, ctx->formats, draw_legacy_cb, ctx); +} diff --git a/video/out/gl_osd.h b/video/out/gl_osd.h new file mode 100644 index 0000000000..cf3182ffb2 --- /dev/null +++ b/video/out/gl_osd.h @@ -0,0 +1,43 @@ +#ifndef MPLAYER_GL_OSD_H +#define MPLAYER_GL_OSD_H + +#include <stdbool.h> +#include <inttypes.h> + +#include "gl_common.h" +#include "sub/sub.h" + +struct mpgl_osd_part { + enum sub_bitmap_format format; + int bitmap_id, bitmap_pos_id; + GLuint texture; + int w, h; + GLuint buffer; + int num_vertices; + void *vertices; + struct bitmap_packer *packer; +}; + +struct mpgl_osd { + GL *gl; + bool use_pbo; + bool scaled; + struct mpgl_osd_part *parts[MAX_OSD_PARTS]; + const struct osd_fmt_entry *fmt_table; + bool formats[SUBBITMAP_COUNT]; + void *scratch; +}; + +struct mpgl_osd *mpgl_osd_init(GL *gl, bool legacy); +void mpgl_osd_destroy(struct mpgl_osd *ctx); + +struct mpgl_osd_part *mpgl_osd_generate(struct mpgl_osd *ctx, + struct sub_bitmaps *b); + +void mpgl_osd_set_gl_state(struct mpgl_osd *ctx, struct mpgl_osd_part *p); +void mpgl_osd_unset_gl_state(struct mpgl_osd *ctx, struct mpgl_osd_part *p); + +void mpgl_osd_draw_legacy(struct mpgl_osd *ctx, struct osd_state *osd, + struct mp_osd_res res); + +#endif diff --git a/video/out/osx_common.c b/video/out/osx_common.c new file mode 100644 index 0000000000..2aa0a28126 --- /dev/null +++ b/video/out/osx_common.c @@ -0,0 +1,145 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "config.h" + +// only to get keycode definitions from HIToolbox/Events.h +#include <Carbon/Carbon.h> +#include <CoreServices/CoreServices.h> +#include "config.h" +#include "osx_common.h" +#include "video_out.h" +#include "input/keycodes.h" +#include "input/input.h" +#include "mp_msg.h" + +/* + * Define keycodes only found in OSX >= 10.5 for older versions + */ +#if MAC_OS_X_VERSION_MAX_ALLOWED <= 1040 +#define kVK_ANSI_Keypad0 0x52 +#define kVK_ANSI_Keypad1 0x53 +#define kVK_ANSI_Keypad2 0x54 +#define kVK_ANSI_Keypad3 0x55 +#define kVK_ANSI_Keypad4 0x56 +#define kVK_ANSI_Keypad5 0x57 +#define kVK_ANSI_Keypad6 0x58 +#define kVK_ANSI_Keypad7 0x59 +#define kVK_ANSI_Keypad8 0x5b +#define kVK_ANSI_Keypad9 0x5c +#define kVK_ANSI_KeypadDecimal 0x41 +#define kVK_ANSI_KeypadDivide 0x4b +#define kVK_ANSI_KeypadEnter 0x4c +#define kVK_ANSI_KeypadMinus 0x4e +#define kVK_ANSI_KeypadMultiply 0x43 +#define kVK_ANSI_KeypadPlus 0x45 +#define kVK_Control 0x3b +#define kVK_Delete 0x33 +#define kVK_DownArrow 0x7d +#define kVK_End 0x77 +#define kVK_Escape 0x35 +#define kVK_F1 0x7a +#define kVK_F10 0x6d +#define kVK_F11 0x67 +#define kVK_F12 0x6f +#define kVK_F2 0x78 +#define kVK_F3 0x63 +#define kVK_F4 0x76 +#define kVK_F5 0x60 +#define kVK_F6 0x61 +#define kVK_F7 0x62 +#define kVK_F8 0x64 +#define kVK_F9 0x65 +#define kVK_ForwardDelete 0x75 +#define kVK_Help 0x72 +#define kVK_Home 0x73 +#define kVK_LeftArrow 0x7b +#define kVK_Option 0x3a +#define kVK_PageDown 0x79 +#define kVK_PageUp 0x74 +#define kVK_Return 0x24 +#define kVK_RightArrow 0x7c +#define kVK_Shift 0x38 +#define kVK_Tab 0x30 +#define kVK_UpArrow 0x7e +#endif /* MAC_OS_X_VERSION_MAX_ALLOWED <= 1040 */ + +static const struct mp_keymap keymap[] = { + // special keys + {0x34, KEY_ENTER}, // Enter key on some iBooks? + {kVK_Return, KEY_ENTER}, + {kVK_Escape, KEY_ESC}, + {kVK_Delete, KEY_BACKSPACE}, {kVK_Option, KEY_BACKSPACE}, {kVK_Control, KEY_BACKSPACE}, {kVK_Shift, KEY_BACKSPACE}, + {kVK_Tab, KEY_TAB}, + + // cursor keys + {kVK_UpArrow, KEY_UP}, {kVK_DownArrow, KEY_DOWN}, {kVK_LeftArrow, KEY_LEFT}, {kVK_RightArrow, KEY_RIGHT}, + + // navigation block + {kVK_Help, KEY_INSERT}, {kVK_ForwardDelete, KEY_DELETE}, {kVK_Home, KEY_HOME}, + {kVK_End, KEY_END}, {kVK_PageUp, KEY_PAGE_UP}, {kVK_PageDown, KEY_PAGE_DOWN}, + + // F-keys + {kVK_F1, KEY_F + 1}, {kVK_F2, KEY_F + 2}, {kVK_F3, KEY_F + 3}, {kVK_F4, KEY_F + 4}, + {kVK_F5, KEY_F + 5}, {kVK_F6, KEY_F + 6}, {kVK_F7, KEY_F + 7}, {kVK_F8, KEY_F + 8}, + {kVK_F9, KEY_F + 9}, {kVK_F10, KEY_F + 10}, {kVK_F11, KEY_F + 11}, {kVK_F12, KEY_F + 12}, + + // numpad + {kVK_ANSI_KeypadPlus, '+'}, {kVK_ANSI_KeypadMinus, '-'}, {kVK_ANSI_KeypadMultiply, '*'}, + {kVK_ANSI_KeypadDivide, '/'}, {kVK_ANSI_KeypadEnter, KEY_KPENTER}, {kVK_ANSI_KeypadDecimal, KEY_KPDEC}, + {kVK_ANSI_Keypad0, KEY_KP0}, {kVK_ANSI_Keypad1, KEY_KP1}, {kVK_ANSI_Keypad2, KEY_KP2}, {kVK_ANSI_Keypad3, KEY_KP3}, + {kVK_ANSI_Keypad4, KEY_KP4}, {kVK_ANSI_Keypad5, KEY_KP5}, {kVK_ANSI_Keypad6, KEY_KP6}, {kVK_ANSI_Keypad7, KEY_KP7}, + {kVK_ANSI_Keypad8, KEY_KP8}, {kVK_ANSI_Keypad9, KEY_KP9}, + + {0, 0} +}; + +int convert_key(unsigned key, unsigned charcode) +{ + int mpkey = lookup_keymap_table(keymap, key); + if (mpkey) + return mpkey; + return charcode; +} + +/** + * Checks at runtime that OSX version is the same or newer than the one + * provided as input. + */ +int is_osx_version_at_least(int majorv, int minorv, int bugfixv) +{ + OSErr err; + SInt32 major, minor, bugfix; + if ((err = Gestalt(gestaltSystemVersionMajor, &major)) != noErr) + goto fail; + if ((err = Gestalt(gestaltSystemVersionMinor, &minor)) != noErr) + goto fail; + if ((err = Gestalt(gestaltSystemVersionBugFix, &bugfix)) != noErr) + goto fail; + + if(major > majorv || + (major == majorv && (minor > minorv || + (minor == minorv && bugfix >= bugfixv)))) + return 1; + else + return 0; +fail: + // There's no reason the Gestalt system call should fail on OSX. + mp_msg(MSGT_VO, MSGL_FATAL, "[osx] Failed to get system version number. " + "Please contact the developers. Error code: %ld\n", (long)err); + return 0; +} diff --git a/video/out/osx_common.h b/video/out/osx_common.h new file mode 100644 index 0000000000..ae31a6353d --- /dev/null +++ b/video/out/osx_common.h @@ -0,0 +1,27 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_OSX_COMMON_H +#define MPLAYER_OSX_COMMON_H + +struct vo; + +int convert_key(unsigned key, unsigned charcode); +int is_osx_version_at_least(int majorv, int minorv, int bugfixv); + +#endif /* MPLAYER_OSX_COMMON_H */ diff --git a/video/out/pnm_loader.c b/video/out/pnm_loader.c new file mode 100644 index 0000000000..048461e51f --- /dev/null +++ b/video/out/pnm_loader.c @@ -0,0 +1,97 @@ +/* + * PNM image files loader + * + * copyleft (C) 2005-2010 Reimar Döffinger <Reimar.Doeffinger@gmx.de> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can alternatively redistribute this file 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. + */ + +/** + * \file pnm_loader.c + * \brief PNM image files loader + */ + +#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> +#include <ctype.h> +#include "pnm_loader.h" + +/** + * \brief skips whitespace and comments + * \param f file to read from + */ +static void ppm_skip(FILE *f) { + int c, comment = 0; + do { + c = fgetc(f); + if (c == '#') + comment = 1; + if (c == '\n') + comment = 0; + } while (c != EOF && (isspace(c) || comment)); + if (c != EOF) + ungetc(c, f); +} + +#define MAXDIM (16 * 1024) + +uint8_t *read_pnm(FILE *f, int *width, int *height, + int *bytes_per_pixel, int *maxval) { + uint8_t *data; + int type; + unsigned w, h, m, val, bpp; + *width = *height = *bytes_per_pixel = *maxval = 0; + ppm_skip(f); + if (fgetc(f) != 'P') + return NULL; + type = fgetc(f); + if (type != '5' && type != '6') + return NULL; + ppm_skip(f); + if (fscanf(f, "%u", &w) != 1) + return NULL; + ppm_skip(f); + if (fscanf(f, "%u", &h) != 1) + return NULL; + ppm_skip(f); + if (fscanf(f, "%u", &m) != 1) + return NULL; + val = fgetc(f); + if (!isspace(val)) + return NULL; + if (w > MAXDIM || h > MAXDIM) + return NULL; + bpp = (m > 255) ? 2 : 1; + if (type == '6') + bpp *= 3; + data = malloc(w * h * bpp); + if (fread(data, w * bpp, h, f) != h) { + free(data); + return NULL; + } + *width = w; + *height = h; + *bytes_per_pixel = bpp; + *maxval = m; + return data; +} diff --git a/video/out/pnm_loader.h b/video/out/pnm_loader.h new file mode 100644 index 0000000000..e00cce2e63 --- /dev/null +++ b/video/out/pnm_loader.h @@ -0,0 +1,52 @@ +/* + * PNM image files loader + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can alternatively redistribute this file 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. + */ + +#ifndef MPLAYER_PNM_LOADER_H +#define MPLAYER_PNM_LOADER_H + +#include <stdio.h> +#include <stdint.h> + +/** + * Read a "portable anymap" image. + * Supports raw PGM (P5) and PNM (P6). + * + * @param[in] f input stream. + * @param[out] width width of the loaded image. + * @param[out] height height of the loaded image. + * @param[out] bytes_per_pixel format of the loaded image. + * @param[out] maxval maximum pixel value; possible values are: + * 1 for 8 bits gray, + * 2 for 16 bits gray, + * 3 for 8 bits per component RGB, + * 6 for 16 bits per component RGB. + * @return a newly allocated array of + * width*height*bytes_per_pixel bytes, + * or NULL in case of error. + */ +uint8_t *read_pnm(FILE *f, int *width, int *height, + int *bytes_per_pixel, int *maxval); + +#endif /* MPLAYER_PNM_LOADER_H */ diff --git a/video/out/vo.c b/video/out/vo.c new file mode 100644 index 0000000000..571f00da4d --- /dev/null +++ b/video/out/vo.c @@ -0,0 +1,530 @@ +/* + * libvo common functions, variables used by many/all drivers. + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <stdbool.h> + +#include <unistd.h> +//#include <sys/mman.h> + +#include "config.h" +#include "options.h" +#include "talloc.h" +#include "bstr.h" +#include "video_out.h" +#include "aspect.h" +#include "geometry.h" +#include "input/input.h" +#include "mp_fifo.h" +#include "m_config.h" +#include "mp_msg.h" +#include "libmpcodecs/vfcap.h" +#include "sub/sub.h" + +#include "osdep/shmem.h" +#ifdef CONFIG_X11 +#include "x11_common.h" +#endif + +int xinerama_screen = -1; +int xinerama_x; +int xinerama_y; + +int vo_nomouse_input = 0; +int vo_grabpointer = 1; +int vo_vsync = 1; +int vo_fs = 0; +int vo_fsmode = 0; +float vo_panscan = 0.0f; +int vo_refresh_rate=0; +int vo_keepaspect=1; +int vo_rootwin=0; +int vo_border=1; +int64_t WinID = -1; + +int vo_pts=0; // for hw decoding +float vo_fps=0; + +int vo_colorkey = 0x0000ff00; // default colorkey is green + // (0xff000000 means that colorkey has been disabled) + +// +// Externally visible list of all vo drivers +// +extern struct vo_driver video_out_x11; +extern struct vo_driver video_out_vdpau; +extern struct vo_driver video_out_xv; +extern struct vo_driver video_out_opengl; +extern struct vo_driver video_out_opengl_hq; +extern struct vo_driver video_out_opengl_old; +extern struct vo_driver video_out_null; +extern struct vo_driver video_out_image; +extern struct vo_driver video_out_lavc; +extern struct vo_driver video_out_caca; +extern struct vo_driver video_out_direct3d; +extern struct vo_driver video_out_direct3d_shaders; +extern struct vo_driver video_out_corevideo; + +const struct vo_driver *video_out_drivers[] = +{ +#ifdef CONFIG_DIRECT3D + &video_out_direct3d_shaders, + &video_out_direct3d, +#endif +#ifdef CONFIG_GL_COCOA + &video_out_opengl, + &video_out_opengl_old, +#endif +#ifdef CONFIG_COREVIDEO + &video_out_corevideo, +#endif +#if CONFIG_VDPAU + &video_out_vdpau, +#endif +#ifdef CONFIG_XV + &video_out_xv, +#endif +#ifdef CONFIG_GL +#if !defined CONFIG_GL_COCOA + &video_out_opengl, + &video_out_opengl_old, +#endif +#endif +#ifdef CONFIG_X11 + &video_out_x11, +#endif +#ifdef CONFIG_CACA + &video_out_caca, +#endif + &video_out_null, + // should not be auto-selected + &video_out_image, +#ifdef CONFIG_ENCODING + &video_out_lavc, +#endif +#ifdef CONFIG_GL + &video_out_opengl_hq, +#endif + NULL +}; + + +static int vo_preinit(struct vo *vo, char *arg) +{ + if (vo->driver->priv_size) { + vo->priv = talloc_zero_size(vo, vo->driver->priv_size); + if (vo->driver->priv_defaults) + memcpy(vo->priv, vo->driver->priv_defaults, vo->driver->priv_size); + } + if (vo->driver->options) { + struct m_config *cfg = m_config_simple(vo->priv); + talloc_steal(vo->priv, cfg); + m_config_register_options(cfg, vo->driver->options); + char n[50]; + int l = snprintf(n, sizeof(n), "vo/%s", vo->driver->info->short_name); + assert(l < sizeof(n)); + int r = m_config_parse_suboptions(cfg, n, arg); + if (r < 0) + return r; + } + return vo->driver->preinit(vo, arg); +} + +int vo_control(struct vo *vo, uint32_t request, void *data) +{ + return vo->driver->control(vo, request, data); +} + +// Return -1 if driver appears not to support a draw_image interface, +// 0 otherwise (whether the driver actually drew something or not). +int vo_draw_image(struct vo *vo, struct mp_image *mpi, double pts) +{ + if (!vo->config_ok) + return 0; + if (vo->driver->buffer_frames) { + vo->driver->draw_image(vo, mpi, pts); + return 0; + } + vo->frame_loaded = true; + vo->next_pts = pts; + // Guaranteed to support at least DRAW_IMAGE later + if (vo->driver->is_new) { + vo->waiting_mpi = mpi; + return 0; + } + if (vo_control(vo, VOCTRL_DRAW_IMAGE, mpi) == VO_NOTIMPL) + return -1; + return 0; +} + +int vo_redraw_frame(struct vo *vo) +{ + if (!vo->config_ok || !vo->hasframe) + return -1; + if (vo_control(vo, VOCTRL_REDRAW_FRAME, NULL) == true) { + vo->redrawing = true; + return 0; + } + return -1; +} + +int vo_get_buffered_frame(struct vo *vo, bool eof) +{ + if (!vo->config_ok) + return -1; + if (vo->frame_loaded) + return 0; + if (!vo->driver->buffer_frames) + return -1; + vo->driver->get_buffered_frame(vo, eof); + return vo->frame_loaded ? 0 : -1; +} + +void vo_skip_frame(struct vo *vo) +{ + vo_control(vo, VOCTRL_SKIPFRAME, NULL); + vo->frame_loaded = false; +} + +int vo_draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h, int x, int y) +{ + return vo->driver->draw_slice(vo, src, stride, w, h, x, y); +} + +void vo_new_frame_imminent(struct vo *vo) +{ + if (!vo->driver->is_new) + return; + if (vo->driver->buffer_frames) + vo_control(vo, VOCTRL_NEWFRAME, NULL); + else { + vo_control(vo, VOCTRL_DRAW_IMAGE, vo->waiting_mpi); + vo->waiting_mpi = NULL; + } +} + +void vo_draw_osd(struct vo *vo, struct osd_state *osd) +{ + if (vo->config_ok && (vo->default_caps & VFCAP_OSD)) + vo->driver->draw_osd(vo, osd); +} + +void vo_flip_page(struct vo *vo, unsigned int pts_us, int duration) +{ + if (!vo->config_ok) + return; + if (!vo->redrawing) { + vo->frame_loaded = false; + vo->next_pts = MP_NOPTS_VALUE; + } + vo->want_redraw = false; + vo->redrawing = false; + if (vo->driver->flip_page_timed) + vo->driver->flip_page_timed(vo, pts_us, duration); + else + vo->driver->flip_page(vo); + vo->hasframe = true; +} + +void vo_check_events(struct vo *vo) +{ + if (!vo->config_ok) { + if (vo->registered_fd != -1) + mp_input_rm_key_fd(vo->input_ctx, vo->registered_fd); + vo->registered_fd = -1; + return; + } + vo->driver->check_events(vo); +} + +void vo_seek_reset(struct vo *vo) +{ + vo_control(vo, VOCTRL_RESET, NULL); + vo->frame_loaded = false; + vo->hasframe = false; +} + +void vo_destroy(struct vo *vo) +{ + if (vo->registered_fd != -1) + mp_input_rm_key_fd(vo->input_ctx, vo->registered_fd); + vo->driver->uninit(vo); + talloc_free(vo); +} + +void list_video_out(void) +{ + int i = 0; + mp_tmsg(MSGT_CPLAYER, MSGL_INFO, "Available video output drivers:\n"); + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_VIDEO_OUTPUTS\n"); + while (video_out_drivers[i]) { + const vo_info_t *info = video_out_drivers[i++]->info; + mp_msg(MSGT_GLOBAL, MSGL_INFO,"\t%s\t%s\n", info->short_name, info->name); + } + mp_msg(MSGT_GLOBAL, MSGL_INFO,"\n"); +} + +static void replace_legacy_vo_name(bstr *name) +{ + bstr new = *name; + if (bstr_equals0(*name, "gl")) + new = bstr0("opengl"); + if (bstr_equals0(*name, "gl3")) + new = bstr0("opengl-hq"); + if (!bstr_equals(*name, new)) { + mp_tmsg(MSGT_CPLAYER, MSGL_ERR, "VO driver '%.*s' has been replaced " + "with '%.*s'!\n", BSTR_P(*name), BSTR_P(new)); + } + *name = new; +} + +struct vo *init_best_video_out(struct MPOpts *opts, + struct mp_fifo *key_fifo, + struct input_ctx *input_ctx, + struct encode_lavc_context *encode_lavc_ctx) +{ + char **vo_list = opts->video_driver_list; + int i; + struct vo *vo = talloc_ptrtype(NULL, vo); + struct vo initial_values = { + .opts = opts, + .key_fifo = key_fifo, + .encode_lavc_ctx = encode_lavc_ctx, + .input_ctx = input_ctx, + .event_fd = -1, + .registered_fd = -1, + }; + // first try the preferred drivers, with their optional subdevice param: + if (vo_list && vo_list[0]) + while (vo_list[0][0]) { + char *arg = vo_list[0]; + bstr name = bstr0(arg); + char *params = strchr(arg, ':'); + if (params) { + name = bstr_splice(name, 0, params - arg); + params++; + } + replace_legacy_vo_name(&name); + for (i = 0; video_out_drivers[i]; i++) { + const struct vo_driver *video_driver = video_out_drivers[i]; + const vo_info_t *info = video_driver->info; + if (bstr_equals0(name, info->short_name)) { + // name matches, try it + *vo = initial_values; + vo->driver = video_driver; + if (!vo_preinit(vo, params)) + return vo; // success! + talloc_free_children(vo); + } + } + // continue... + ++vo_list; + if (!(vo_list[0])) { + talloc_free(vo); + return NULL; // do NOT fallback to others + } + } + // now try the rest... + for (i = 0; video_out_drivers[i]; i++) { + const struct vo_driver *video_driver = video_out_drivers[i]; + *vo = initial_values; + vo->driver = video_driver; + if (!vo_preinit(vo, NULL)) + return vo; // success! + talloc_free_children(vo); + } + talloc_free(vo); + return NULL; +} + +static int event_fd_callback(void *ctx, int fd) +{ + struct vo *vo = ctx; + vo_check_events(vo); + return MP_INPUT_NOTHING; +} + +int vo_config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t flags, + uint32_t format) +{ + struct MPOpts *opts = vo->opts; + panscan_init(vo); + aspect_save_videores(vo, width, height, d_width, d_height); + + if (vo_control(vo, VOCTRL_UPDATE_SCREENINFO, NULL) == VO_TRUE) { + aspect(vo, &d_width, &d_height, A_NOZOOM); + vo->dx = (int)(opts->vo_screenwidth - d_width) / 2; + vo->dy = (int)(opts->vo_screenheight - d_height) / 2; + geometry(&vo->dx, &vo->dy, &d_width, &d_height, + opts->vo_screenwidth, opts->vo_screenheight); + geometry_xy_changed |= xinerama_screen >= 0; + vo->dx += xinerama_x; + vo->dy += xinerama_y; + vo->dwidth = d_width; + vo->dheight = d_height; + } + + vo->default_caps = vo_control(vo, VOCTRL_QUERY_FORMAT, &format); + + int ret = vo->driver->config(vo, width, height, d_width, d_height, flags, + format); + vo->config_ok = (ret == 0); + vo->config_count += vo->config_ok; + if (!vo->config_ok) + vo->default_caps = 0; + if (vo->registered_fd == -1 && vo->event_fd != -1 && vo->config_ok) { + mp_input_add_key_fd(vo->input_ctx, vo->event_fd, 1, event_fd_callback, + NULL, vo); + vo->registered_fd = vo->event_fd; + } + vo->frame_loaded = false; + vo->waiting_mpi = NULL; + vo->redrawing = false; + vo->hasframe = false; + return ret; +} + +/** + * \brief lookup an integer in a table, table must have 0 as the last key + * \param key key to search for + * \result translation corresponding to key or "to" value of last mapping + * if not found. + */ +int lookup_keymap_table(const struct mp_keymap *map, int key) { + while (map->from && map->from != key) map++; + return map->to; +} + +static void print_video_rect(struct vo *vo, struct mp_rect src, + struct mp_rect dst, struct mp_osd_res osd) +{ + int lv = MSGL_V; + + int sw = src.x1 - src.x0, sh = src.y1 - src.y0; + int dw = dst.x1 - dst.x0, dh = dst.y1 - dst.y0; + + mp_msg(MSGT_VO, lv, "[vo] Window size: %dx%d\n", + vo->dwidth, vo->dheight); + mp_msg(MSGT_VO, lv, "[vo] Video source: %dx%d (%dx%d)\n", + vo->aspdat.orgw, vo->aspdat.orgh, + vo->aspdat.prew, vo->aspdat.preh); + mp_msg(MSGT_VO, lv, "[vo] Video display: (%d, %d) %dx%d -> (%d, %d) %dx%d\n", + src.x0, src.y0, sw, sh, dst.x0, dst.y0, dw, dh); + mp_msg(MSGT_VO, lv, "[vo] Video scale: %f/%f\n", + (double)dw / sw, (double)dh / sh); + mp_msg(MSGT_VO, lv, "[vo] OSD borders: l=%d t=%d r=%d b=%d\n", + osd.ml, osd.mt, osd.mr, osd.mb); + mp_msg(MSGT_VO, lv, "[vo] Video borders: l=%d t=%d r=%d b=%d\n", + dst.x0, dst.y0, vo->dwidth - dst.x1, vo->dheight - dst.y1); +} + +static void src_dst_split_scaling(int src_size, int dst_size, + int scaled_src_size, int *src_start, + int *src_end, int *dst_start, int *dst_end) +{ + if (scaled_src_size > dst_size) { + int border = src_size * (scaled_src_size - dst_size) / scaled_src_size; + // round to a multiple of 2, this is at least needed for vo_direct3d + // and ATI cards + border = (border / 2 + 1) & ~1; + *src_start = border; + *src_end = src_size - border; + *dst_start = 0; + *dst_end = dst_size; + } else { + *src_start = 0; + *src_end = src_size; + *dst_start = (dst_size - scaled_src_size) / 2; + *dst_end = *dst_start + scaled_src_size; + } +} + +// Calculate the appropriate source and destination rectangle to +// get a correctly scaled picture, including pan-scan. +// out_src: visible part of the video +// out_dst: area of screen covered by the video source rectangle +// out_osd: OSD size, OSD margins, etc. +void vo_get_src_dst_rects(struct vo *vo, struct mp_rect *out_src, + struct mp_rect *out_dst, struct mp_osd_res *out_osd) +{ + int src_w = vo->aspdat.orgw; + int src_h = vo->aspdat.orgh; + struct mp_rect dst = {0, 0, vo->dwidth, vo->dheight}; + struct mp_rect src = {0, 0, src_w, src_h}; + struct mp_osd_res osd = { + .w = vo->dwidth, + .h = vo->dheight, + .display_par = vo->monitor_par, + .video_par = vo->aspdat.par, + }; + if (aspect_scaling()) { + int scaled_width = 0, scaled_height = 0; + aspect(vo, &scaled_width, &scaled_height, A_WINZOOM); + panscan_calc_windowed(vo); + scaled_width += vo->panscan_x; + scaled_height += vo->panscan_y; + int border_w = vo->dwidth - scaled_width; + int border_h = vo->dheight - scaled_height; + osd.ml = border_w / 2; + osd.mt = border_h / 2; + osd.mr = border_w - osd.ml; + osd.mb = border_h - osd.mt; + src_dst_split_scaling(src_w, vo->dwidth, scaled_width, + &src.x0, &src.x1, &dst.x0, &dst.x1); + src_dst_split_scaling(src_h, vo->dheight, scaled_height, + &src.y0, &src.y1, &dst.y0, &dst.y1); + } + + *out_src = src; + *out_dst = dst; + *out_osd = osd; + + print_video_rect(vo, src, dst, osd); +} + +// Return the window title the VO should set. Always returns a null terminated +// string. The string is valid until frontend code is invoked again. Copy it if +// you need to keep the string for an extended period of time. +const char *vo_get_window_title(struct vo *vo) +{ + if (!vo->window_title) + vo->window_title = talloc_strdup(vo, ""); + return vo->window_title; +} + +/** + * Generates a mouse movement message if those are enable and sends it + * to the "main" MPlayer. + * + * \param posx new x position of mouse + * \param posy new y position of mouse + */ +void vo_mouse_movement(struct vo *vo, int posx, int posy) +{ + char cmd_str[40]; + if (!enable_mouse_movements) + return; + snprintf(cmd_str, sizeof(cmd_str), "set_mouse_pos %i %i", posx, posy); + mp_input_queue_cmd(vo->input_ctx, mp_input_parse_cmd(bstr0(cmd_str), "")); +} diff --git a/video/out/vo.h b/video/out/vo.h new file mode 100644 index 0000000000..ac2ded9b3c --- /dev/null +++ b/video/out/vo.h @@ -0,0 +1,352 @@ +/* + * Copyright (C) Aaron Holtzman - Aug 1999 + * Strongly modified, most parts rewritten: A'rpi/ESP-team - 2000-2001 + * (C) MPlayer developers + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_VIDEO_OUT_H +#define MPLAYER_VIDEO_OUT_H + +#include <inttypes.h> +#include <stdbool.h> + +#include "libmpcodecs/img_format.h" +#include "mpcommon.h" + +#define VO_EVENT_EXPOSE 1 +#define VO_EVENT_RESIZE 2 +#define VO_EVENT_KEYPRESS 4 +#define VO_EVENT_REINIT 8 +#define VO_EVENT_MOVE 16 + +enum mp_voctrl { + /* does the device support the required format */ + VOCTRL_QUERY_FORMAT = 1, + /* signal a device reset seek */ + VOCTRL_RESET, + /* used to switch to fullscreen */ + VOCTRL_FULLSCREEN, + /* signal a device pause */ + VOCTRL_PAUSE, + /* start/resume playback */ + VOCTRL_RESUME, + /* libmpcodecs direct rendering */ + VOCTRL_GET_IMAGE, + VOCTRL_DRAW_IMAGE, + VOCTRL_GET_PANSCAN, + VOCTRL_SET_PANSCAN, + VOCTRL_SET_EQUALIZER, // struct voctrl_set_equalizer_args + VOCTRL_GET_EQUALIZER, // struct voctrl_get_equalizer_args + VOCTRL_DUPLICATE_FRAME, + + VOCTRL_START_SLICE, + + VOCTRL_NEWFRAME, + VOCTRL_SKIPFRAME, + VOCTRL_REDRAW_FRAME, + + VOCTRL_ONTOP, + VOCTRL_ROOTWIN, + VOCTRL_BORDER, + + VOCTRL_SET_DEINTERLACE, + VOCTRL_GET_DEINTERLACE, + + VOCTRL_UPDATE_SCREENINFO, + + VOCTRL_SET_YUV_COLORSPACE, // struct mp_csp_details + VOCTRL_GET_YUV_COLORSPACE, // struct mp_csp_details + + VOCTRL_SCREENSHOT, // struct voctrl_screenshot_args + + VOCTRL_SET_COMMAND_LINE, // char* +}; + +// VOCTRL_SET_EQUALIZER +struct voctrl_set_equalizer_args { + const char *name; + int value; +}; + +// VOCTRL_GET_EQUALIZER +struct voctrl_get_equalizer_args { + const char *name; + int *valueptr; +}; + +// VOCTRL_SCREENSHOT +struct voctrl_screenshot_args { + // 0: Save image of the currently displayed video frame, in original + // resolution. + // 1: Save full screenshot of the window. Should contain OSD, EOSD, and the + // scaled video. + // The value of this variable can be ignored if only a single method is + // implemented. + int full_window; + // Will be set to a newly allocated image, that contains the screenshot. + // The caller has to free the pointer with free_mp_image(). + // It is not specified whether the image data is a copy or references the + // image data directly. + // Is never NULL. (Failure has to be indicated by returning VO_FALSE.) + struct mp_image *out_image; + // Whether the VO rendered OSD/subtitles into out_image + bool has_osd; +}; + +typedef struct { + int x,y; + int w,h; +} mp_win_t; + +#define VO_TRUE 1 +#define VO_FALSE 0 +#define VO_ERROR -1 +#define VO_NOTAVAIL -2 +#define VO_NOTIMPL -3 + +#define VOFLAG_FULLSCREEN 0x01 +#define VOFLAG_MODESWITCHING 0x02 +#define VOFLAG_SWSCALE 0x04 +#define VOFLAG_FLIPPING 0x08 +#define VOFLAG_HIDDEN 0x10 //< Use to create a hidden window +#define VOFLAG_STEREO 0x20 //< Use to create a stereo-capable window +#define VOFLAG_GL_DEBUG 0x40 // Hint to request debug OpenGL context + +typedef struct vo_info_s +{ + /* driver name ("Matrox Millennium G200/G400" */ + const char *name; + /* short name (for config strings) ("vdpau") */ + const char *short_name; + /* author ("Aaron Holtzman <aholtzma@ess.engr.uvic.ca>") */ + const char *author; + /* any additional comments */ + const char *comment; +} vo_info_t; + +struct vo; +struct osd_state; +struct mp_image; + +struct vo_driver { + // Driver uses new API + bool is_new; + // Driver buffers or adds (deinterlace) frames and will keep track + // of pts values itself + bool buffer_frames; + + const vo_info_t *info; + /* + * Preinitializes driver (real INITIALIZATION) + * arg - currently it's vo_subdevice + * returns: zero on successful initialization, non-zero on error. + */ + int (*preinit)(struct vo *vo, const char *arg); + /* + * Initialize (means CONFIGURE) the display driver. + * params: + * width,height: image source size + * d_width,d_height: size of the requested window size, just a hint + * fullscreen: flag, 0=windowd 1=fullscreen, just a hint + * title: window title, if available + * format: fourcc of pixel format + * returns : zero on successful initialization, non-zero on error. + */ + int (*config)(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t fullscreen, + uint32_t format); + + /* + * Control interface + */ + int (*control)(struct vo *vo, uint32_t request, void *data); + + void (*draw_image)(struct vo *vo, struct mp_image *mpi, double pts); + + /* + * Get extra frames from the VO, such as those added by VDPAU + * deinterlace. Preparing the next such frame if any could be done + * automatically by the VO after a previous flip_page(), but having + * it as a separate step seems to allow making code more robust. + */ + void (*get_buffered_frame)(struct vo *vo, bool eof); + + /* + * Draw a planar YUV slice to the buffer: + * params: + * src[3] = source image planes (Y,U,V) + * stride[3] = source image planes line widths (in bytes) + * w,h = width*height of area to be copied (in Y pixels) + * x,y = position at the destination image (in Y pixels) + */ + int (*draw_slice)(struct vo *vo, uint8_t *src[], int stride[], int w, + int h, int x, int y); + + /* + * Draws OSD to the screen buffer + */ + void (*draw_osd)(struct vo *vo, struct osd_state *osd); + + /* + * Blit/Flip buffer to the screen. Must be called after each frame! + */ + void (*flip_page)(struct vo *vo); + void (*flip_page_timed)(struct vo *vo, unsigned int pts_us, int duration); + + /* + * This func is called after every frames to handle keyboard and + * other events. It's called in PAUSE mode too! + */ + void (*check_events)(struct vo *vo); + + /* + * Closes driver. Should restore the original state of the system. + */ + void (*uninit)(struct vo *vo); + + // Size of private struct for automatic allocation (0 doesn't allocate) + int priv_size; + + // If not NULL, it's copied into the newly allocated private struct. + const void *priv_defaults; + + // List of options to parse into priv struct (requires privsize to be set) + const struct m_option *options; +}; + +struct vo { + int config_ok; // Last config call was successful? + int config_count; // Total number of successful config calls + int default_caps; // query_format() result for configured video format + + bool frame_loaded; // Is there a next frame the VO could flip to? + struct mp_image *waiting_mpi; + double next_pts; // pts value of the next frame if any + double next_pts2; // optional pts of frame after that + bool want_redraw; // visible frame wrong (window resize), needs refresh + bool redrawing; // between redrawing frame and flipping it + bool hasframe; // >= 1 frame has been drawn, so redraw is possible + + double flip_queue_offset; // queue flip events at most this much in advance + + const struct vo_driver *driver; + void *priv; + struct MPOpts *opts; + struct vo_x11_state *x11; + struct vo_w32_state *w32; + struct vo_cocoa_state *cocoa; + struct mp_fifo *key_fifo; + struct encode_lavc_context *encode_lavc_ctx; + struct input_ctx *input_ctx; + int event_fd; // check_events() should be called when this has input + int registered_fd; // set to event_fd when registered in input system + + // requested position/resolution (usually window position/window size) + int dx; + int dy; + int dwidth; + int dheight; + + int panscan_x; + int panscan_y; + float panscan_amount; + float monitor_par; + struct aspect_data { + int orgw; // real width + int orgh; // real height + int prew; // prescaled width + int preh; // prescaled height + float par; // pixel aspect ratio out of orgw/orgh and prew/preh + int scrw; // horizontal resolution + int scrh; // vertical resolution + float asp; + } aspdat; + + char *window_title; +}; + +struct vo *init_best_video_out(struct MPOpts *opts, + struct mp_fifo *key_fifo, + struct input_ctx *input_ctx, + struct encode_lavc_context *encode_lavc_ctx); +int vo_config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t flags, + uint32_t format); +void list_video_out(void); + +int vo_control(struct vo *vo, uint32_t request, void *data); +int vo_draw_image(struct vo *vo, struct mp_image *mpi, double pts); +int vo_redraw_frame(struct vo *vo); +int vo_get_buffered_frame(struct vo *vo, bool eof); +void vo_skip_frame(struct vo *vo); +int vo_draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h, int x, int y); +void vo_new_frame_imminent(struct vo *vo); +void vo_draw_osd(struct vo *vo, struct osd_state *osd); +void vo_flip_page(struct vo *vo, unsigned int pts_us, int duration); +void vo_check_events(struct vo *vo); +void vo_seek_reset(struct vo *vo); +void vo_destroy(struct vo *vo); + +const char *vo_get_window_title(struct vo *vo); + +// NULL terminated array of all drivers +extern const struct vo_driver *video_out_drivers[]; + +extern int xinerama_screen; +extern int xinerama_x; +extern int xinerama_y; + +extern int vo_grabpointer; +extern int vo_vsync; +extern int vo_fs; +extern int vo_fsmode; +extern float vo_panscan; +extern int vo_refresh_rate; +extern int vo_keepaspect; +extern int vo_rootwin; +extern int vo_border; + +extern int vo_nomouse_input; +extern int enable_mouse_movements; + +extern int vo_pts; +extern float vo_fps; + +extern int vo_colorkey; + +extern int64_t WinID; + +struct mp_keymap { + int from; + int to; +}; +int lookup_keymap_table(const struct mp_keymap *map, int key); + +void vo_mouse_movement(struct vo *vo, int posx, int posy); + +struct mp_osd_res; +void vo_get_src_dst_rects(struct vo *vo, struct mp_rect *out_src, + struct mp_rect *out_dst, struct mp_osd_res *out_osd); + +static inline int aspect_scaling(void) +{ + return vo_keepaspect || vo_fs; +} + +#endif /* MPLAYER_VIDEO_OUT_H */ diff --git a/video/out/vo_caca.c b/video/out/vo_caca.c new file mode 100644 index 0000000000..2d998bf91d --- /dev/null +++ b/video/out/vo_caca.c @@ -0,0 +1,395 @@ +/* + * video output driver for libcaca + * + * by Pigeon <pigeon@pigeond.net> + * + * Some functions/codes/ideas are from x11 and aalib vo + * + * TODO: support draw_alpha? + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <time.h> +#include <errno.h> +#include <assert.h> +#include <caca.h> + +#include "config.h" +#include "video_out.h" +#include "sub/sub.h" +#include "libmpcodecs/mp_image.h" +#include "libmpcodecs/vfcap.h" + +#include "input/keycodes.h" +#include "input/input.h" +#include "mp_msg.h" +#include "mp_fifo.h" + +/* caca stuff */ +static caca_canvas_t *canvas; +static caca_display_t *display; +static caca_dither_t *dither = NULL; +static const char *dither_antialias = "default"; +static const char *dither_charset = "default"; +static const char *dither_color = "default"; +static const char *dither_algo = "none"; + +/* image infos */ +static int image_format; +static int image_width; +static int image_height; + +static int screen_w, screen_h; + +/* We want 24bpp always for now */ +static unsigned int bpp = 24; +static unsigned int depth = 3; +static unsigned int rmask = 0xff0000; +static unsigned int gmask = 0x00ff00; +static unsigned int bmask = 0x0000ff; +static unsigned int amask = 0; + +#define MESSAGE_SIZE 512 +#define MESSAGE_DURATION 5 + +static time_t stoposd = 0; +static int showosdmessage = 0; +static char osdmessagetext[MESSAGE_SIZE]; +static char posbar[MESSAGE_SIZE]; + +static int osdx = 0, osdy = 0; +static int posbary = 2; + +static void osdmessage(int duration, const char *fmt, ...) +{ + /* for outputting a centered string at the window bottom for a while */ + va_list ar; + char m[MESSAGE_SIZE]; + + va_start(ar, fmt); + vsprintf(m, fmt, ar); + va_end(ar); + strcpy(osdmessagetext, m); + + showosdmessage = 1; + stoposd = time(NULL) + duration; + osdx = (screen_w - strlen(osdmessagetext)) / 2; + posbar[0] = '\0'; +} + +static void osdpercent(int duration, int min, int max, int val, + const char *desc, const char *unit) +{ + /* prints a bar for setting values */ + float step; + int where, i; + + step = (float)screen_w / (float)(max - min); + where = (val - min) * step; + osdmessage(duration, "%s: %i%s", desc, val, unit); + posbar[0] = '|'; + posbar[screen_w - 1] = '|'; + + for (i = 0; i < screen_w; i++) { + if (i == where) + posbar[i] = '#'; + else + posbar[i] = '-'; + } + + if (where != 0) + posbar[0] = '|'; + + if (where != (screen_w - 1)) + posbar[screen_w - 1] = '|'; + + posbar[screen_w] = '\0'; +} + +static int resize(void) +{ + screen_w = caca_get_canvas_width(canvas); + screen_h = caca_get_canvas_height(canvas); + + caca_free_dither(dither); + + dither = caca_create_dither(bpp, image_width, image_height, + depth * image_width, + rmask, gmask, bmask, amask); + if (dither == NULL) { + mp_msg(MSGT_VO, MSGL_FATAL, "vo_caca: caca_create_dither failed!\n"); + return ENOSYS; + } + + /* Default libcaca features */ + caca_set_dither_antialias(dither, dither_antialias); + caca_set_dither_charset(dither, dither_charset); + caca_set_dither_color(dither, dither_color); + caca_set_dither_algorithm(dither, dither_algo); + + return 0; +} + +static int config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t flags, + uint32_t format) +{ + image_height = height; + image_width = width; + image_format = format; + + showosdmessage = 0; + posbar[0] = '\0'; + + return resize(); +} + +static uint32_t draw_image(struct vo *vo, mp_image_t *mpi) +{ + assert(mpi->stride[0] == image_width * 3); + caca_dither_bitmap(canvas, 0, 0, screen_w, screen_h, dither, + mpi->planes[0]); + return true; +} + +static int draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h, + int x, int y) +{ + return 0; +} + +static void flip_page(struct vo *vo) +{ + if (showosdmessage) { + if (time(NULL) >= stoposd) { + showosdmessage = 0; + if (*posbar) + posbar[0] = '\0'; + } else { + caca_put_str(canvas, osdx, osdy, osdmessagetext); + if (*posbar) + caca_put_str(canvas, 0, posbary, posbar); + } + } + + caca_refresh_display(display); +} + +static void set_next_str(const char * const *list, const char **str, + const char **msg) +{ + int ind; + for (ind = 0; list[ind]; ind += 2) { + if (strcmp(list[ind], *str) == 0) { + if (list[ind + 2] == NULL) + ind = -2; + *str = list[ind + 2]; + *msg = list[ind + 3]; + return; + } + } + + *str = list[0]; + *msg = list[1]; +} + +static const struct mp_keymap keysym_map[] = { + {CACA_KEY_RETURN, KEY_ENTER}, {CACA_KEY_ESCAPE, KEY_ESC}, + {CACA_KEY_UP, KEY_DOWN}, {CACA_KEY_DOWN, KEY_DOWN}, + {CACA_KEY_LEFT, KEY_LEFT}, {CACA_KEY_RIGHT, KEY_RIGHT}, + {CACA_KEY_PAGEUP, KEY_PAGE_UP}, {CACA_KEY_PAGEDOWN, KEY_PAGE_DOWN}, + {CACA_KEY_HOME, KEY_HOME}, {CACA_KEY_END, KEY_END}, + {CACA_KEY_INSERT, KEY_INSERT}, {CACA_KEY_DELETE, KEY_DELETE}, + {CACA_KEY_BACKSPACE, KEY_BACKSPACE}, {CACA_KEY_TAB, KEY_TAB}, + {CACA_KEY_PAUSE, KEY_PAUSE}, + {CACA_KEY_F1, KEY_F+1}, {CACA_KEY_F2, KEY_F+2}, + {CACA_KEY_F3, KEY_F+3}, {CACA_KEY_F4, KEY_F+4}, + {CACA_KEY_F5, KEY_F+5}, {CACA_KEY_F6, KEY_F+6}, + {CACA_KEY_F7, KEY_F+7}, {CACA_KEY_F8, KEY_F+8}, + {CACA_KEY_F9, KEY_F+9}, {CACA_KEY_F10, KEY_F+10}, + {CACA_KEY_F11, KEY_F+11}, {CACA_KEY_F12, KEY_F+12}, + {CACA_KEY_F13, KEY_F+13}, {CACA_KEY_F14, KEY_F+14}, + {CACA_KEY_F15, KEY_F+15}, + {0, 0} +}; + +static void check_events(struct vo *vo) +{ + caca_event_t cev; + while (caca_get_event(display, CACA_EVENT_ANY, &cev, 0)) { + + switch (cev.type) { + case CACA_EVENT_RESIZE: + caca_refresh_display(display); + resize(); + break; + case CACA_EVENT_QUIT: + mplayer_put_key(vo->key_fifo, KEY_CLOSE_WIN); + break; + case CACA_EVENT_MOUSE_MOTION: + vo_mouse_movement(vo, cev.data.mouse.x, cev.data.mouse.y); + break; + case CACA_EVENT_MOUSE_PRESS: + if (!vo_nomouse_input) + mplayer_put_key(vo->key_fifo, + (MOUSE_BTN0 + cev.data.mouse.button - 1) | MP_KEY_DOWN); + break; + case CACA_EVENT_MOUSE_RELEASE: + if (!vo_nomouse_input) + mplayer_put_key(vo->key_fifo, + MOUSE_BTN0 + cev.data.mouse.button - 1); + break; + case CACA_EVENT_KEY_PRESS: + { + int key = cev.data.key.ch; + int mpkey = lookup_keymap_table(keysym_map, key); + const char *msg_name; + + if (mpkey) + mplayer_put_key(vo->key_fifo, mpkey); + else + switch (key) { + case 'd': + case 'D': + /* Toggle dithering algorithm */ + set_next_str(caca_get_dither_algorithm_list(dither), + &dither_algo, &msg_name); + caca_set_dither_algorithm(dither, dither_algo); + osdmessage(MESSAGE_DURATION, "Using %s", msg_name); + break; + + case 'a': + case 'A': + /* Toggle antialiasing method */ + set_next_str(caca_get_dither_antialias_list(dither), + &dither_antialias, &msg_name); + caca_set_dither_antialias(dither, dither_antialias); + osdmessage(MESSAGE_DURATION, "Using %s", msg_name); + break; + + case 'h': + case 'H': + /* Toggle charset method */ + set_next_str(caca_get_dither_charset_list(dither), + &dither_charset, &msg_name); + caca_set_dither_charset(dither, dither_charset); + osdmessage(MESSAGE_DURATION, "Using %s", msg_name); + break; + + case 'c': + case 'C': + /* Toggle color method */ + set_next_str(caca_get_dither_color_list(dither), + &dither_color, &msg_name); + caca_set_dither_color(dither, dither_color); + osdmessage(MESSAGE_DURATION, "Using %s", msg_name); + break; + + default: + if (key <= 255) + mplayer_put_key(vo->key_fifo, key); + break; + } + } + } + } +} + +static void uninit(struct vo *vo) +{ + caca_free_dither(dither); + dither = NULL; + caca_free_display(display); + caca_free_canvas(canvas); +} + +static void draw_osd(struct vo *vo, struct osd_state *osd) +{ + if (osd->progbar_type != -1) + osdpercent(MESSAGE_DURATION, 0, 255, osd->progbar_value, + sub_osd_names[osd->progbar_type], ""); +} + +static int preinit(struct vo *vo, const char *arg) +{ + if (arg) { + mp_msg(MSGT_VO, MSGL_ERR, "vo_caca: Unknown subdevice: %s\n", arg); + return ENOSYS; + } + + canvas = caca_create_canvas(0, 0); + if (canvas == NULL) { + mp_msg(MSGT_VO, MSGL_ERR, "vo_caca: failed to create canvas\n"); + return ENOSYS; + } + + display = caca_create_display(canvas); + + if (display == NULL) { + mp_msg(MSGT_VO, MSGL_ERR, "vo_caca: failed to create display\n"); + caca_free_canvas(canvas); + return ENOSYS; + } + + caca_set_display_title(display, "mpv"); + + return 0; +} + +static int query_format(uint32_t format) +{ + if (format == IMGFMT_BGR24) + return VFCAP_OSD | VFCAP_CSP_SUPPORTED; + + return 0; +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + switch (request) { + case VOCTRL_QUERY_FORMAT: + return query_format(*((uint32_t *)data)); + case VOCTRL_DRAW_IMAGE: + return draw_image(vo, data); + default: + return VO_NOTIMPL; + } +} + +const struct vo_driver video_out_caca = { + .is_new = false, + .info = &(const vo_info_t) { + "libcaca", + "caca", + "Pigeon <pigeon@pigeond.net>", + "" + }, + .preinit = preinit, + .config = config, + .control = control, + .draw_slice = draw_slice, + .draw_osd = draw_osd, + .flip_page = flip_page, + .check_events = check_events, + .uninit = uninit, +}; diff --git a/video/out/vo_corevideo.h b/video/out/vo_corevideo.h new file mode 100644 index 0000000000..cfb86621bc --- /dev/null +++ b/video/out/vo_corevideo.h @@ -0,0 +1,28 @@ +/* + * CoreVideo video output driver + * + * Copyright (c) 2005 Nicolas Plourde <nicolasplourde@gmail.com> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_VO_COREVIDEO_H +#define MPLAYER_VO_COREVIDEO_H + +#import <QuartzCore/QuartzCore.h> + +#endif /* MPLAYER_VO_COREVIDEO_H */ diff --git a/video/out/vo_corevideo.m b/video/out/vo_corevideo.m new file mode 100644 index 0000000000..5116ab653c --- /dev/null +++ b/video/out/vo_corevideo.m @@ -0,0 +1,457 @@ +/* + * CoreVideo video output driver + * Copyright (c) 2005 Nicolas Plourde <nicolasplourde@gmail.com> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <assert.h> + +#import "vo_corevideo.h" + +// mplayer includes +#import "fastmemcpy.h" +#import "talloc.h" +#import "video_out.h" +#import "aspect.h" +#import "sub/sub.h" +#import "subopt-helper.h" + +#import "csputils.h" +#import "libmpcodecs/vfcap.h" +#import "libmpcodecs/mp_image.h" + +#import "gl_common.h" +#import "gl_osd.h" +#import "cocoa_common.h" + +struct quad { + GLfloat lowerLeft[2]; + GLfloat lowerRight[2]; + GLfloat upperRight[2]; + GLfloat upperLeft[2]; +}; + +struct priv { + MPGLContext *mpglctx; + OSType pixelFormat; + unsigned int image_width; + unsigned int image_height; + struct mp_csp_details colorspace; + int ass_border_x, ass_border_y; + + CVPixelBufferRef pixelBuffer; + CVOpenGLTextureCacheRef textureCache; + CVOpenGLTextureRef texture; + struct quad *quad; + + struct mpgl_osd *osd; +}; + +static void resize(struct vo *vo, int width, int height) +{ + struct priv *p = vo->priv; + GL *gl = p->mpglctx->gl; + + gl->Viewport(0, 0, width, height); + gl->MatrixMode(GL_PROJECTION); + gl->LoadIdentity(); + p->ass_border_x = p->ass_border_y = 0; + if (aspect_scaling()) { + int new_w, new_h; + GLdouble scale_x, scale_y; + + aspect(vo, &new_w, &new_h, A_WINZOOM); + panscan_calc_windowed(vo); + new_w += vo->panscan_x; + new_h += vo->panscan_y; + scale_x = (GLdouble)new_w / (GLdouble)width; + scale_y = (GLdouble)new_h / (GLdouble)height; + gl->Scaled(scale_x, scale_y, 1); + p->ass_border_x = (vo->dwidth - new_w) / 2; + p->ass_border_y = (vo->dheight - new_h) / 2; + } + + gl->Ortho(0, p->image_width, p->image_height, 0, -1.0, 1.0); + gl->MatrixMode(GL_MODELVIEW); + gl->LoadIdentity(); + + gl->Clear(GL_COLOR_BUFFER_BIT); + vo->want_redraw = true; +} + +static int init_gl(struct vo *vo, uint32_t d_width, uint32_t d_height) +{ + struct priv *p = vo->priv; + GL *gl = p->mpglctx->gl; + + const char *vendor = gl->GetString(GL_VENDOR); + const char *version = gl->GetString(GL_VERSION); + const char *renderer = gl->GetString(GL_RENDERER); + + mp_msg(MSGT_VO, MSGL_V, "[vo_corevideo] Running on OpenGL '%s' by '%s'," + " version '%s'\n", renderer, vendor, version); + + gl->Disable(GL_BLEND); + gl->Disable(GL_DEPTH_TEST); + gl->DepthMask(GL_FALSE); + gl->Disable(GL_CULL_FACE); + gl->Enable(GL_TEXTURE_2D); + gl->DrawBuffer(GL_BACK); + gl->TexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + if (!p->osd) + p->osd = mpgl_osd_init(gl, true); + + resize(vo, d_width, d_height); + + gl->ClearColor(0.0f, 0.0f, 0.0f, 0.0f); + gl->Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + if (gl->SwapInterval) + gl->SwapInterval(1); + return 1; +} + +static void release_cv_entities(struct vo *vo) { + struct priv *p = vo->priv; + CVPixelBufferRelease(p->pixelBuffer); + p->pixelBuffer = NULL; + CVOpenGLTextureRelease(p->texture); + p->texture = NULL; + CVOpenGLTextureCacheRelease(p->textureCache); + p->textureCache = NULL; +} + +static int config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t flags, + uint32_t format) +{ + struct priv *p = vo->priv; + release_cv_entities(vo); + p->image_width = width; + p->image_height = height; + + int mpgl_caps = MPGL_CAP_GL_LEGACY; + if (!mpgl_create_window(p->mpglctx, mpgl_caps, d_width, d_height, flags)) + return -1; + + init_gl(vo, vo->dwidth, vo->dheight); + + return 0; +} + +static void check_events(struct vo *vo) +{ + struct priv *p = vo->priv; + int e = p->mpglctx->check_events(vo); + if (e & VO_EVENT_RESIZE) + resize(vo, vo->dwidth, vo->dheight); +} + +static void prepare_texture(struct vo *vo) +{ + struct priv *p = vo->priv; + struct quad *q = p->quad; + CVReturn error; + + CVOpenGLTextureRelease(p->texture); + error = CVOpenGLTextureCacheCreateTextureFromImage(NULL, + p->textureCache, p->pixelBuffer, 0, &p->texture); + if(error != kCVReturnSuccess) + mp_msg(MSGT_VO, MSGL_ERR,"[vo_corevideo] Failed to create OpenGL" + " texture(%d)\n", error); + + CVOpenGLTextureGetCleanTexCoords(p->texture, q->lowerLeft, q->lowerRight, + q->upperRight, q->upperLeft); +} + +static void do_render(struct vo *vo) +{ + struct priv *p = vo->priv; + struct quad *q = p->quad; + GL *gl = p->mpglctx->gl; + prepare_texture(vo); + + float x0 = 0; + float y0 = 0; + float w = p->image_width; + float h = p->image_height; + + // vertically flips the image + y0 += h; + h = -h; + + float xm = x0 + w; + float ym = y0 + h; + + gl->Enable(CVOpenGLTextureGetTarget(p->texture)); + gl->BindTexture( + CVOpenGLTextureGetTarget(p->texture), + CVOpenGLTextureGetName(p->texture)); + + gl->Begin(GL_QUADS); + gl->TexCoord2fv(q->lowerLeft); gl->Vertex2f(x0, y0); + gl->TexCoord2fv(q->upperLeft); gl->Vertex2f(x0, ym); + gl->TexCoord2fv(q->upperRight); gl->Vertex2f(xm, ym); + gl->TexCoord2fv(q->lowerRight); gl->Vertex2f(xm, y0); + gl->End(); + + gl->Disable(CVOpenGLTextureGetTarget(p->texture)); +} + +static void flip_page(struct vo *vo) +{ + struct priv *p = vo->priv; + p->mpglctx->swapGlBuffers(p->mpglctx); + p->mpglctx->gl->Clear(GL_COLOR_BUFFER_BIT); +} + +static uint32_t draw_image(struct vo *vo, mp_image_t *mpi) +{ + struct priv *p = vo->priv; + CVReturn error; + + if (!p->textureCache || !p->pixelBuffer) { + error = CVOpenGLTextureCacheCreate(NULL, 0, vo_cocoa_cgl_context(vo), + vo_cocoa_cgl_pixel_format(vo), 0, &p->textureCache); + if(error != kCVReturnSuccess) + mp_msg(MSGT_VO, MSGL_ERR,"[vo_corevideo] Failed to create OpenGL" + " texture Cache(%d)\n", error); + + error = CVPixelBufferCreateWithBytes(NULL, mpi->width, mpi->height, + p->pixelFormat, mpi->planes[0], mpi->width * mpi->bpp / 8, + NULL, NULL, NULL, &p->pixelBuffer); + if(error != kCVReturnSuccess) + mp_msg(MSGT_VO, MSGL_ERR,"[vo_corevideo] Failed to create Pixel" + "Buffer(%d)\n", error); + } + + do_render(vo); + return VO_TRUE; +} + +static int query_format(struct vo *vo, uint32_t format) +{ + struct priv *p = vo->priv; + const int flags = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | + VFCAP_OSD | VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | + VOCAP_NOSLICES; + switch (format) { + case IMGFMT_YUY2: + p->pixelFormat = kYUVSPixelFormat; + return flags; + + case IMGFMT_RGB24: + p->pixelFormat = k24RGBPixelFormat; + return flags; + + case IMGFMT_ARGB: + p->pixelFormat = k32ARGBPixelFormat; + return flags; + + case IMGFMT_BGRA: + p->pixelFormat = k32BGRAPixelFormat; + return flags; + } + return 0; +} + +static void uninit(struct vo *vo) +{ + struct priv *p = vo->priv; + if (p->osd) + mpgl_osd_destroy(p->osd); + release_cv_entities(vo); + mpgl_uninit(p->mpglctx); +} + + +static int preinit(struct vo *vo, const char *arg) +{ + struct priv *p = vo->priv; + + *p = (struct priv) { + .mpglctx = mpgl_init(GLTYPE_COCOA, vo), + .colorspace = MP_CSP_DETAILS_DEFAULTS, + .quad = talloc_ptrtype(p, p->quad), + }; + + return 0; +} + +static void draw_osd(struct vo *vo, struct osd_state *osd) +{ + struct priv *p = vo->priv; + GL *gl = p->mpglctx->gl; + assert(p->osd); + + gl->MatrixMode(GL_PROJECTION); + gl->PushMatrix(); + gl->LoadIdentity(); + gl->Ortho(0, vo->dwidth, vo->dheight, 0, -1, 1); + + struct mp_osd_res res = { + .w = vo->dwidth, + .h = vo->dheight, + .display_par = vo->monitor_par, + .video_par = vo->aspdat.par, + }; + + if (aspect_scaling()) { + res.ml = res.mr = p->ass_border_x; + res.mt = res.mb = p->ass_border_y; + } + + mpgl_osd_draw_legacy(p->osd, osd, res); + + gl->PopMatrix(); +} + +static CFStringRef get_cv_csp_matrix(struct vo *vo) +{ + struct priv *p = vo->priv; + switch (p->colorspace.format) { + case MP_CSP_BT_601: + return kCVImageBufferYCbCrMatrix_ITU_R_601_4; + case MP_CSP_BT_709: + return kCVImageBufferYCbCrMatrix_ITU_R_709_2; + case MP_CSP_SMPTE_240M: + return kCVImageBufferYCbCrMatrix_SMPTE_240M_1995; + } + return kCVImageBufferYCbCrMatrix_ITU_R_601_4; +} + +static void set_yuv_colorspace(struct vo *vo) +{ + struct priv *p = vo->priv; + CVBufferSetAttachment(p->pixelBuffer, + kCVImageBufferYCbCrMatrixKey, get_cv_csp_matrix(vo), + kCVAttachmentMode_ShouldPropagate); + vo->want_redraw = true; +} + +static int get_image_fmt(struct vo *vo) +{ + struct priv *p = vo->priv; + switch (p->pixelFormat) { + case kYUVSPixelFormat: return IMGFMT_YUY2; + case k24RGBPixelFormat: return IMGFMT_RGB24; + case k32ARGBPixelFormat: return IMGFMT_ARGB; + case k32BGRAPixelFormat: return IMGFMT_BGRA; + } + mp_msg(MSGT_VO, MSGL_ERR, "[vo_corevideo] Failed to convert pixel format. " + "Please contact the developers. PixelFormat: %d\n", p->pixelFormat); + return -1; +} + +static mp_image_t *get_screenshot(struct vo *vo) +{ + int img_fmt = get_image_fmt(vo); + if (img_fmt < 0) return NULL; + + struct priv *p = vo->priv; + void *base = CVPixelBufferGetBaseAddress(p->pixelBuffer); + + size_t width = CVPixelBufferGetWidth(p->pixelBuffer); + size_t height = CVPixelBufferGetHeight(p->pixelBuffer); + size_t stride = CVPixelBufferGetBytesPerRow(p->pixelBuffer); + size_t image_size = stride * height; + + mp_image_t *image = alloc_mpi(width, height, img_fmt); + memcpy(image->planes[0], base, image_size); + image->stride[0] = stride; + + image->display_w = vo->aspdat.prew; + image->display_h = vo->aspdat.preh; + + mp_image_set_colorspace_details(image, &p->colorspace); + + return image; +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + struct priv *p = vo->priv; + switch (request) { + case VOCTRL_DRAW_IMAGE: + return draw_image(vo, data); + case VOCTRL_QUERY_FORMAT: + return query_format(vo, *(uint32_t*)data); + case VOCTRL_ONTOP: + p->mpglctx->ontop(vo); + return VO_TRUE; + case VOCTRL_PAUSE: + if (!p->mpglctx->pause) + break; + p->mpglctx->pause(vo); + return VO_TRUE; + case VOCTRL_RESUME: + if (!p->mpglctx->resume) + break; + p->mpglctx->resume(vo); + return VO_TRUE; + case VOCTRL_FULLSCREEN: + p->mpglctx->fullscreen(vo); + resize(vo, vo->dwidth, vo->dheight); + return VO_TRUE; + case VOCTRL_GET_PANSCAN: + return VO_TRUE; + case VOCTRL_SET_PANSCAN: + resize(vo, vo->dwidth, vo->dheight); + return VO_TRUE; + case VOCTRL_UPDATE_SCREENINFO: + p->mpglctx->update_xinerama_info(vo); + return VO_TRUE; + case VOCTRL_REDRAW_FRAME: + do_render(vo); + return VO_TRUE; + case VOCTRL_SET_YUV_COLORSPACE: + p->colorspace.format = ((struct mp_csp_details *)data)->format; + set_yuv_colorspace(vo); + return VO_TRUE; + case VOCTRL_GET_YUV_COLORSPACE: + *(struct mp_csp_details *)data = p->colorspace; + return VO_TRUE; + case VOCTRL_SCREENSHOT: { + struct voctrl_screenshot_args *args = data; + if (args->full_window) + args->out_image = glGetWindowScreenshot(p->mpglctx->gl); + else + args->out_image = get_screenshot(vo); + return VO_TRUE; + } + } + return VO_NOTIMPL; +} + +const struct vo_driver video_out_corevideo = { + .is_new = true, + .info = &(const vo_info_t) { + "Mac OS X Core Video", + "corevideo", + "Nicolas Plourde <nicolas.plourde@gmail.com> and others", + "" + }, + .preinit = preinit, + .config = config, + .control = control, + .draw_osd = draw_osd, + .flip_page = flip_page, + .check_events = check_events, + .uninit = uninit, + .priv_size = sizeof(struct priv), +}; diff --git a/video/out/vo_direct3d.c b/video/out/vo_direct3d.c new file mode 100644 index 0000000000..294a101ffe --- /dev/null +++ b/video/out/vo_direct3d.c @@ -0,0 +1,2101 @@ +/* + * Copyright (c) 2008 Georgi Petrov (gogothebee) <gogothebee@gmail.com> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <windows.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <stdbool.h> +#include <assert.h> +#include <d3d9.h> +#include "config.h" +#include "options.h" +#include "subopt-helper.h" +#include "talloc.h" +#include "video_out.h" +#include "libmpcodecs/vfcap.h" +#include "csputils.h" +#include "libmpcodecs/mp_image.h" +#include "libmpcodecs/img_format.h" +#include "fastmemcpy.h" +#include "mp_msg.h" +#include "aspect.h" +#include "w32_common.h" +#include "libavutil/common.h" +#include "sub/sub.h" +#include "bitmap_packer.h" + +// shaders generated by fxc.exe from d3d_shader_yuv.hlsl +#include "d3d_shader_yuv.h" +#include "d3d_shader_yuv_2ch.h" + + +// TODO: beg someone to add this (there is already IMGFMT_Y8) +// equals MAKEFOURCC('Y', '1', '6', ' ') +#define IMGFMT_Y16 0x20363159 +#define IMGFMT_A8Y8 MAKEFOURCC('A', '8', 'Y', '8') + +#define IMGFMT_IS_Y(x) ((x) == IMGFMT_Y8 || (x) == IMGFMT_Y16 || (x) == IMGFMT_A8Y8) +#define IMGFMT_Y_DEPTH(x) ((x) == IMGFMT_Y8 ? 8 : 16) + +#define DEVTYPE D3DDEVTYPE_HAL +//#define DEVTYPE D3DDEVTYPE_REF + +#define D3DFVF_OSD_VERTEX (D3DFVF_XYZ | D3DFVF_TEX1 | D3DFVF_DIFFUSE) + +typedef struct { + float x, y, z; + D3DCOLOR color; + float tu, tv; +} vertex_osd; + +#define D3DFVF_VIDEO_VERTEX (D3DFVF_XYZ | D3DFVF_TEX3) + +typedef struct { + float x, y, z; + // pairs of texture coordinates for up to 3 planes + float t[3][2]; +} vertex_video; + + +struct d3dtex { + // user-requested size + int w, h; + // allocated texture size + int tex_w, tex_h; + // D3DPOOL_SYSTEMMEM (or others) texture: + // - can be locked in order to write (and even read) data + // - can _not_ (probably) be used as texture for rendering + // This is always non-NULL if d3dtex_allocate succeeds. + IDirect3DTexture9 *system; + // D3DPOOL_DEFAULT texture: + // - can't be locked (Probably.) + // - must be used for rendering + // This will be NULL on systems with device_texture_sys != 0. + IDirect3DTexture9 *device; +}; + +struct texplane { + int bytes_per_pixel; + int bits_per_pixel; + // chroma shifts + // e.g. get the plane's width in pixels with (priv->src_width >> shift_x) + int shift_x, shift_y; + D3DFORMAT d3d_format; + struct d3dtex texture; + // temporary locking during uploading the frame (e.g. for draw_slice) + D3DLOCKED_RECT locked_rect; + // value used to clear the image with memset (YUV chroma planes do not use + // the value 0 for this) + uint8_t clearval; +}; + +struct osdpart { + enum sub_bitmap_format format; + int bitmap_id, bitmap_pos_id; + struct d3dtex texture; + int num_vertices; + vertex_osd *vertices; + struct bitmap_packer *packer; +}; + +/* Global variables "priv" structure. I try to keep their count low. + */ +typedef struct d3d_priv { + int opt_prefer_stretchrect; + int opt_disable_textures; + int opt_disable_stretchrect; + int opt_disable_shaders; + int opt_only_8bit; + int opt_disable_osd; + int opt_disable_texture_align; + // debugging + int opt_force_power_of_2; + int opt_texture_memory; + int opt_swap_discard; + int opt_exact_backbuffer; + int opt_16bit_textures; + + struct vo *vo; + + int is_clear_needed; /**< 1 = Clear the backbuffer before StretchRect + 0 = (default) Don't clear it */ + D3DLOCKED_RECT locked_rect; /**< The locked offscreen surface */ + RECT fs_movie_rect; /**< Rect (upscaled) of the movie when displayed + in fullscreen */ + RECT fs_panscan_rect; /**< PanScan source surface cropping in + fullscreen */ + int src_width; /**< Source (movie) width */ + int src_height; /**< Source (movie) heigth */ + struct mp_osd_res osd_res; + int image_format; /**< mplayer image format */ + bool use_textures; /**< use 3D texture rendering, instead of + StretchRect */ + bool use_shaders; /**< use shader for YUV color conversion + (or possibly for RGB video equalizers) */ + bool use_2ch_hack; /**< 2 byte YUV formats use 2 channel hack */ + + int plane_count; + struct texplane planes[3]; + + IDirect3DPixelShader9 *pixel_shader; + const BYTE *pixel_shader_data; + + D3DFORMAT movie_src_fmt; /**< Movie colorspace format (depends on + the movie's codec) */ + D3DFORMAT desktop_fmt; /**< Desktop (screen) colorspace format. + Usually XRGB */ + + HANDLE d3d9_dll; /**< d3d9 Library HANDLE */ + IDirect3D9 * (WINAPI *pDirect3DCreate9)(UINT); /**< pointer to Direct3DCreate9 function */ + + LPDIRECT3D9 d3d_handle; /**< Direct3D Handle */ + LPDIRECT3DDEVICE9 d3d_device; /**< The Direct3D Adapter */ + bool d3d_in_scene; /**< BeginScene was called, EndScene not */ + IDirect3DSurface9 *d3d_surface; /**< Offscreen Direct3D Surface. MPlayer + renders inside it. Uses colorspace + priv->movie_src_fmt */ + IDirect3DSurface9 *d3d_backbuf; /**< Video card's back buffer (used to + display next frame) */ + int cur_backbuf_width; /**< Current backbuffer width */ + int cur_backbuf_height; /**< Current backbuffer height */ + int device_caps_power2_only; /**< 1 = texture sizes have to be power 2 + 0 = texture sizes can be anything */ + int device_caps_square_only; /**< 1 = textures have to be square + 0 = textures do not have to be square */ + int device_texture_sys; /**< 1 = device can texture from system memory + 0 = device requires shadow */ + int max_texture_width; /**< from the device capabilities */ + int max_texture_height; /**< from the device capabilities */ + + D3DMATRIX d3d_colormatrix; + float d3d_depth_vector[4]; + struct mp_csp_details colorspace; + struct mp_csp_equalizer video_eq; + + struct osdpart *osd[MAX_OSD_PARTS]; +} d3d_priv; + +struct fmt_entry { + const unsigned int mplayer_fmt; /**< Given by MPlayer */ + const D3DFORMAT fourcc; /**< Required by D3D's test function */ +}; + +/* Map table from reported MPlayer format to the required + fourcc. This is needed to perform the format query. */ + +static const struct fmt_entry fmt_table[] = { + // planar YUV + {IMGFMT_YV12, MAKEFOURCC('Y','V','1','2')}, + {IMGFMT_I420, MAKEFOURCC('I','4','2','0')}, + {IMGFMT_IYUV, MAKEFOURCC('I','Y','U','V')}, + {IMGFMT_YVU9, MAKEFOURCC('Y','V','U','9')}, + // packed YUV + {IMGFMT_YUY2, D3DFMT_YUY2}, + {IMGFMT_UYVY, D3DFMT_UYVY}, + // packed RGB + {IMGFMT_BGR32, D3DFMT_X8R8G8B8}, + {IMGFMT_RGB32, D3DFMT_X8B8G8R8}, + {IMGFMT_BGR24, D3DFMT_R8G8B8}, //untested + {IMGFMT_BGR16, D3DFMT_R5G6B5}, + {IMGFMT_BGR15, D3DFMT_X1R5G5B5}, + {IMGFMT_BGR8 , D3DFMT_R3G3B2}, //untested + // grayscale (can be considered both packed and planar) + {IMGFMT_Y8, D3DFMT_L8}, + {IMGFMT_Y16, D3DFMT_L16}, + {IMGFMT_A8Y8, D3DFMT_A8L8}, + {0}, +}; + +static const D3DFORMAT osd_fmt_table[SUBBITMAP_COUNT] = { + [SUBBITMAP_LIBASS] = D3DFMT_A8, + [SUBBITMAP_RGBA] = D3DFMT_A8R8G8B8, +}; +static const bool osd_fmt_supported[SUBBITMAP_COUNT] = { + [SUBBITMAP_LIBASS] = true, + [SUBBITMAP_RGBA] = true, +}; + + +static void update_colorspace(d3d_priv *priv); +static void d3d_clear_video_textures(d3d_priv *priv); +static bool resize_d3d(d3d_priv *priv); +static uint32_t d3d_draw_frame(d3d_priv *priv); +static int draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h, + int x, int y); +static void uninit(struct vo *vo); +static void flip_page(struct vo *vo); +static mp_image_t *get_screenshot(d3d_priv *priv); +static mp_image_t *get_window_screenshot(d3d_priv *priv); + + +static void d3d_matrix_identity(D3DMATRIX *m) +{ + memset(m, 0, sizeof(D3DMATRIX)); + m->_11 = m->_22 = m->_33 = m->_44 = 1.0f; +} + +static void d3d_matrix_ortho(D3DMATRIX *m, float left, float right, + float bottom, float top) +{ + d3d_matrix_identity(m); + m->_11 = 2.0f / (right - left); + m->_22 = 2.0f / (top - bottom); + m->_33 = 1.0f; + m->_41 = -(right + left) / (right - left); + m->_42 = -(top + bottom) / (top - bottom); + m->_43 = 0; + m->_44 = 1.0f; +} + +/**************************************************************************** + * * + * * + * * + * Direct3D specific implementation functions * + * * + * * + * * + ****************************************************************************/ + +static bool d3d_begin_scene(d3d_priv *priv) +{ + if (!priv->d3d_in_scene) { + if (FAILED(IDirect3DDevice9_BeginScene(priv->d3d_device))) { + mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>BeginScene failed.\n"); + return false; + } + priv->d3d_in_scene = true; + } + return true; +} + +/** @brief Calculate scaled fullscreen movie rectangle with + * preserved aspect ratio. + */ +static void calc_fs_rect(d3d_priv *priv) +{ + struct mp_rect src_rect; + struct mp_rect dst_rect; + vo_get_src_dst_rects(priv->vo, &src_rect, &dst_rect, &priv->osd_res); + + priv->fs_movie_rect.left = dst_rect.x0; + priv->fs_movie_rect.right = dst_rect.x1; + priv->fs_movie_rect.top = dst_rect.y0; + priv->fs_movie_rect.bottom = dst_rect.y1; + priv->fs_panscan_rect.left = src_rect.x0; + priv->fs_panscan_rect.right = src_rect.x1; + priv->fs_panscan_rect.top = src_rect.y0; + priv->fs_panscan_rect.bottom = src_rect.y1; + + mp_msg(MSGT_VO, MSGL_V, + "<vo_direct3d>Video rectangle: t: %ld, l: %ld, r: %ld, b:%ld\n", + priv->fs_movie_rect.top, priv->fs_movie_rect.left, + priv->fs_movie_rect.right, priv->fs_movie_rect.bottom); + + /* The backbuffer should be cleared before next StretchRect. This is + * necessary because our new draw area could be smaller than the + * previous one used by StretchRect and without it, leftovers from the + * previous frame will be left. */ + priv->is_clear_needed = 1; +} + +// Adjust the texture size *width/*height to fit the requirements of the D3D +// device. The texture size is only increased. +static void d3d_fix_texture_size(d3d_priv *priv, int *width, int *height) +{ + int tex_width = *width; + int tex_height = *height; + + // avoid nasty special cases with 0-sized textures and texture sizes + tex_width = FFMAX(tex_width, 1); + tex_height = FFMAX(tex_height, 1); + + if (priv->device_caps_power2_only) { + tex_width = 1; + tex_height = 1; + while (tex_width < *width) tex_width <<= 1; + while (tex_height < *height) tex_height <<= 1; + } + if (priv->device_caps_square_only) + /* device only supports square textures */ + tex_width = tex_height = FFMAX(tex_width, tex_height); + // better round up to a multiple of 16 + if (!priv->opt_disable_texture_align) { + tex_width = (tex_width + 15) & ~15; + tex_height = (tex_height + 15) & ~15; + } + + *width = tex_width; + *height = tex_height; +} + +static void d3dtex_release(d3d_priv *priv, struct d3dtex *tex) +{ + if (tex->system) + IDirect3DTexture9_Release(tex->system); + tex->system = NULL; + + if (tex->device) + IDirect3DTexture9_Release(tex->device); + tex->device = NULL; + + tex->tex_w = tex->tex_h = 0; +} + +static bool d3dtex_allocate(d3d_priv *priv, struct d3dtex *tex, D3DFORMAT fmt, + int w, int h) +{ + d3dtex_release(priv, tex); + + tex->w = w; + tex->h = h; + + int tw = w, th = h; + d3d_fix_texture_size(priv, &tw, &th); + + int memtype = D3DPOOL_SYSTEMMEM; + switch (priv->opt_texture_memory) { + case 1: memtype = D3DPOOL_MANAGED; break; + case 2: memtype = D3DPOOL_DEFAULT; break; + } + + if (FAILED(IDirect3DDevice9_CreateTexture(priv->d3d_device, tw, th, 1, + D3DUSAGE_DYNAMIC, fmt, memtype, &tex->system, NULL))) + { + mp_msg(MSGT_VO, MSGL_ERR, + "<vo_direct3d>Allocating %dx%d texture in system RAM failed.\n", + w, h); + goto error_exit; + } + + if (!priv->device_texture_sys && !priv->opt_texture_memory) { + if (FAILED(IDirect3DDevice9_CreateTexture(priv->d3d_device, tw, th, 1, + D3DUSAGE_DYNAMIC, fmt, D3DPOOL_DEFAULT, &tex->device, NULL))) + { + mp_msg(MSGT_VO, MSGL_ERR, + "<vo_direct3d>Allocating %dx%d texture in video RAM failed.\n", + w, h); + goto error_exit; + } + } + + tex->tex_w = tw; + tex->tex_h = th; + + return true; + +error_exit: + d3dtex_release(priv, tex); + return false; +} + +static IDirect3DBaseTexture9 *d3dtex_get_render_texture(d3d_priv *priv, + struct d3dtex *tex) +{ + return (IDirect3DBaseTexture9 *)(tex->device ? tex->device : tex->system); +} + +// Copy system texture contents to device texture. +static bool d3dtex_update(d3d_priv *priv, struct d3dtex *tex) +{ + if (!tex->device) + return true; + return !FAILED(IDirect3DDevice9_UpdateTexture(priv->d3d_device, + (IDirect3DBaseTexture9 *)tex->system, + (IDirect3DBaseTexture9 *)tex->device)); +} + +static void d3d_unlock_video_objects(d3d_priv *priv) +{ + bool any_failed = false; + + if (priv->locked_rect.pBits) { + if (FAILED(IDirect3DSurface9_UnlockRect(priv->d3d_surface))) + any_failed = true; + } + priv->locked_rect.pBits = NULL; + + for (int n = 0; n < priv->plane_count; n++) { + struct texplane *plane = &priv->planes[n]; + if (plane->locked_rect.pBits) { + if (FAILED(IDirect3DTexture9_UnlockRect(plane->texture.system, 0))) + any_failed = true; + } + plane->locked_rect.pBits = NULL; + } + + if (any_failed) { + mp_msg(MSGT_VO, MSGL_V, + "<vo_direct3d>Unlocking video objects failed.\n"); + } +} + +// Free video surface/textures, shaders, etc. +static void d3d_destroy_video_objects(d3d_priv *priv) +{ + d3d_unlock_video_objects(priv); + + if (priv->d3d_surface) + IDirect3DSurface9_Release(priv->d3d_surface); + priv->d3d_surface = NULL; + + for (int n = 0; n < priv->plane_count; n++) { + d3dtex_release(priv, &priv->planes[n].texture); + } + + if (priv->pixel_shader) + IDirect3DPixelShader9_Release(priv->pixel_shader); + priv->pixel_shader = NULL; +} + +/** @brief Destroy D3D Offscreen and Backbuffer surfaces. + */ +static void destroy_d3d_surfaces(d3d_priv *priv) +{ + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>destroy_d3d_surfaces called.\n"); + + d3d_destroy_video_objects(priv); + + for (int n = 0; n < MAX_OSD_PARTS; n++) { + struct osdpart *osd = priv->osd[n]; + d3dtex_release(priv, &osd->texture); + osd->bitmap_id = osd->bitmap_pos_id = -1; + } + + if (priv->d3d_backbuf) + IDirect3DSurface9_Release(priv->d3d_backbuf); + priv->d3d_backbuf = NULL; + + priv->d3d_in_scene = false; +} + +// Allocate video surface or textures, and create shaders if needed. +static bool d3d_configure_video_objects(d3d_priv *priv) +{ + int n; + bool need_clear = false; + + assert(priv->image_format != 0); + + if (priv->use_textures) { + for (n = 0; n < priv->plane_count; n++) { + struct texplane *plane = &priv->planes[n]; + + if (!plane->texture.system) { + if (!d3dtex_allocate(priv, + &plane->texture, + plane->d3d_format, + priv->src_width >> plane->shift_x, + priv->src_height >> plane->shift_y)) + { + mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>Allocating plane %d" + " failed.\n", n); + return false; + } + + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Allocated plane %d:" + " %d bit, shift=%d/%d size=%d/%d (%d/%d).\n", n, + plane->bits_per_pixel, + plane->shift_x, plane->shift_y, + plane->texture.w, plane->texture.h, + plane->texture.tex_w, plane->texture.tex_h); + + need_clear = true; + } + } + + if (need_clear) + d3d_clear_video_textures(priv); + + if (priv->pixel_shader_data) { + if (!priv->pixel_shader && + FAILED(IDirect3DDevice9_CreatePixelShader(priv->d3d_device, + (DWORD *)priv->pixel_shader_data, &priv->pixel_shader))) + { + mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>Failed to create " + "YUV conversion pixel shader.\n"); + return false; + } + } + + } else { + + if (!priv->d3d_surface && + FAILED(IDirect3DDevice9_CreateOffscreenPlainSurface( + priv->d3d_device, priv->src_width, priv->src_height, + priv->movie_src_fmt, D3DPOOL_DEFAULT, &priv->d3d_surface, NULL))) + { + mp_msg(MSGT_VO, MSGL_ERR, + "<vo_direct3d>Allocating offscreen surface failed.\n"); + return false; + } + } + + return true; +} + +static bool d3d_lock_video_textures(d3d_priv *priv) +{ + for (int n = 0; n < priv->plane_count; n++) { + struct texplane *plane = &priv->planes[n]; + + if (!plane->locked_rect.pBits) { + if (FAILED(IDirect3DTexture9_LockRect(plane->texture.system, 0, + &plane->locked_rect, NULL, 0))) + { + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Texture lock failure.\n"); + d3d_unlock_video_objects(priv); + return false; + } + } + } + + return true; +} + +static void d3d_clear_video_textures(d3d_priv *priv) +{ + if (!d3d_lock_video_textures(priv)) + return; + + for (int n = 0; n < priv->plane_count; n++) { + struct texplane *plane = &priv->planes[n]; + memset(plane->locked_rect.pBits, plane->clearval, + plane->locked_rect.Pitch * plane->texture.tex_h); + } + + d3d_unlock_video_objects(priv); +} + +// Recreate and initialize D3D objects if necessary. The amount of work that +// needs to be done can be quite different: it could be that full initialization +// is required, or that some objects need to be created, or that nothing is +// done. +static bool create_d3d_surfaces(d3d_priv *priv) +{ + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>create_d3d_surfaces called.\n"); + + if (!priv->d3d_backbuf && + FAILED(IDirect3DDevice9_GetBackBuffer(priv->d3d_device, 0, 0, + D3DBACKBUFFER_TYPE_MONO, + &priv->d3d_backbuf))) { + mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>Allocating backbuffer failed.\n"); + return 0; + } + + if (!d3d_configure_video_objects(priv)) + return 0; + + /* setup default renderstate */ + IDirect3DDevice9_SetRenderState(priv->d3d_device, + D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + IDirect3DDevice9_SetRenderState(priv->d3d_device, + D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + IDirect3DDevice9_SetRenderState(priv->d3d_device, + D3DRS_ALPHAFUNC, D3DCMP_GREATER); + IDirect3DDevice9_SetRenderState(priv->d3d_device, + D3DRS_ALPHAREF, (DWORD)0x0); + IDirect3DDevice9_SetRenderState(priv->d3d_device, + D3DRS_LIGHTING, FALSE); + + // we use up to 3 samplers for up to 3 YUV planes + for (int n = 0; n < 3; n++) { + IDirect3DDevice9_SetSamplerState(priv->d3d_device, n, D3DSAMP_MINFILTER, + D3DTEXF_LINEAR); + IDirect3DDevice9_SetSamplerState(priv->d3d_device, n, D3DSAMP_MAGFILTER, + D3DTEXF_LINEAR); + IDirect3DDevice9_SetSamplerState(priv->d3d_device, n, D3DSAMP_ADDRESSU, + D3DTADDRESS_CLAMP); + IDirect3DDevice9_SetSamplerState(priv->d3d_device, n, D3DSAMP_ADDRESSV, + D3DTADDRESS_CLAMP); + } + + return 1; +} + +static bool init_d3d(d3d_priv *priv) +{ + D3DDISPLAYMODE disp_mode; + D3DCAPS9 disp_caps; + DWORD texture_caps; + DWORD dev_caps; + + priv->d3d_handle = priv->pDirect3DCreate9(D3D_SDK_VERSION); + if (!priv->d3d_handle) { + mp_msg(MSGT_VO, MSGL_ERR, + "<vo_direct3d>Initializing Direct3D failed.\n"); + return false; + } + + if (FAILED(IDirect3D9_GetAdapterDisplayMode(priv->d3d_handle, + D3DADAPTER_DEFAULT, + &disp_mode))) { + mp_msg(MSGT_VO, MSGL_ERR, + "<vo_direct3d>Reading display mode failed.\n"); + return false; + } + + priv->desktop_fmt = disp_mode.Format; + priv->cur_backbuf_width = disp_mode.Width; + priv->cur_backbuf_height = disp_mode.Height; + + mp_msg(MSGT_VO, MSGL_V, + "<vo_direct3d>Setting backbuffer dimensions to (%dx%d).\n", + disp_mode.Width, disp_mode.Height); + + if (FAILED(IDirect3D9_GetDeviceCaps(priv->d3d_handle, + D3DADAPTER_DEFAULT, + DEVTYPE, + &disp_caps))) + { + mp_msg(MSGT_VO, MSGL_ERR, + "<vo_direct3d>Reading display capabilities failed.\n"); + return false; + } + + /* Store relevant information reguarding caps of device */ + texture_caps = disp_caps.TextureCaps; + dev_caps = disp_caps.DevCaps; + priv->device_caps_power2_only = (texture_caps & D3DPTEXTURECAPS_POW2) && + !(texture_caps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL); + priv->device_caps_square_only = texture_caps & D3DPTEXTURECAPS_SQUAREONLY; + priv->device_texture_sys = dev_caps & D3DDEVCAPS_TEXTURESYSTEMMEMORY; + priv->max_texture_width = disp_caps.MaxTextureWidth; + priv->max_texture_height = disp_caps.MaxTextureHeight; + + if (priv->opt_force_power_of_2) + priv->device_caps_power2_only = 1; + + mp_msg(MSGT_VO, MSGL_V, + "<vo_direct3d>device_caps_power2_only %d, device_caps_square_only %d\n" + "<vo_direct3d>device_texture_sys %d\n" + "<vo_direct3d>max_texture_width %d, max_texture_height %d\n", + priv->device_caps_power2_only, priv->device_caps_square_only, + priv->device_texture_sys, priv->max_texture_width, + priv->max_texture_height); + + return true; +} + +/** @brief Fill D3D Presentation parameters + */ +static void fill_d3d_presentparams(d3d_priv *priv, + D3DPRESENT_PARAMETERS *present_params) +{ + /* Prepare Direct3D initialization parameters. */ + memset(present_params, 0, sizeof(D3DPRESENT_PARAMETERS)); + present_params->Windowed = TRUE; + present_params->SwapEffect = + priv->opt_swap_discard ? D3DSWAPEFFECT_DISCARD : D3DSWAPEFFECT_COPY; + present_params->Flags = D3DPRESENTFLAG_VIDEO; + present_params->hDeviceWindow = priv->vo->w32->window; + present_params->BackBufferWidth = priv->cur_backbuf_width; + present_params->BackBufferHeight = priv->cur_backbuf_height; + present_params->MultiSampleType = D3DMULTISAMPLE_NONE; + present_params->PresentationInterval = D3DPRESENT_INTERVAL_ONE; + present_params->BackBufferFormat = priv->desktop_fmt; + present_params->BackBufferCount = 1; + present_params->EnableAutoDepthStencil = FALSE; +} + + +// Create a new backbuffer. Create or Reset the D3D device. +static bool change_d3d_backbuffer(d3d_priv *priv) +{ + D3DPRESENT_PARAMETERS present_params; + + int window_w = priv->vo->dwidth; + int window_h = priv->vo->dheight; + + /* Grow the backbuffer in the required dimension. */ + if (window_w > priv->cur_backbuf_width) + priv->cur_backbuf_width = window_w; + + if (window_h > priv->cur_backbuf_height) + priv->cur_backbuf_height = window_h; + + if (priv->opt_exact_backbuffer) { + priv->cur_backbuf_width = window_w; + priv->cur_backbuf_height = window_h; + } + + /* The grown backbuffer dimensions are ready and fill_d3d_presentparams + * will use them, so we can reset the device. + */ + fill_d3d_presentparams(priv, &present_params); + + if (!priv->d3d_device) { + if (FAILED(IDirect3D9_CreateDevice(priv->d3d_handle, + D3DADAPTER_DEFAULT, + DEVTYPE, priv->vo->w32->window, + D3DCREATE_SOFTWARE_VERTEXPROCESSING + | D3DCREATE_FPU_PRESERVE, + &present_params, &priv->d3d_device))) + { + mp_msg(MSGT_VO, MSGL_V, + "<vo_direct3d>Creating Direct3D device failed.\n"); + return 0; + } + } else { + if (FAILED(IDirect3DDevice9_Reset(priv->d3d_device, &present_params))) { + mp_msg(MSGT_VO, MSGL_ERR, + "<vo_direct3d>Reseting Direct3D device failed.\n"); + return 0; + } + } + + mp_msg(MSGT_VO, MSGL_V, + "<vo_direct3d>New backbuffer (%dx%d), VO (%dx%d)\n", + present_params.BackBufferWidth, present_params.BackBufferHeight, + window_w, window_h); + + return 1; +} + +/** @brief Reconfigure the whole Direct3D. Called only + * when the video adapter becomes uncooperative. ("Lost" devices) + * @return 1 on success, 0 on failure + */ +static int reconfigure_d3d(d3d_priv *priv) +{ + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>reconfigure_d3d called.\n"); + + destroy_d3d_surfaces(priv); + + if (priv->d3d_device) + IDirect3DDevice9_Release(priv->d3d_device); + priv->d3d_device = NULL; + + // Force complete destruction of the D3D state. + // Note: this step could be omitted. The resize_d3d call below would detect + // that d3d_device is NULL, and would properly recreate it. I'm not sure why + // the following code to release and recreate the d3d_handle exists. + if (priv->d3d_handle) + IDirect3D9_Release(priv->d3d_handle); + priv->d3d_handle = NULL; + if (!init_d3d(priv)) + return 0; + + // Proper re-initialization. + if (!resize_d3d(priv)) + return 0; + + return 1; +} + +// Resize Direct3D context on window resize. +// This function also is called when major initializations need to be done. +static bool resize_d3d(d3d_priv *priv) +{ + D3DVIEWPORT9 vp = {0, 0, priv->vo->dwidth, priv->vo->dheight, 0, 1}; + + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>resize_d3d called.\n"); + + /* Make sure that backbuffer is large enough to accomodate the new + viewport dimensions. Grow it if necessary. */ + + bool backbuf_resize = priv->vo->dwidth > priv->cur_backbuf_width || + priv->vo->dheight > priv->cur_backbuf_height; + + if (priv->opt_exact_backbuffer) { + backbuf_resize = priv->vo->dwidth != priv->cur_backbuf_width || + priv->vo->dheight != priv->cur_backbuf_height; + } + + if (backbuf_resize || !priv->d3d_device) + { + destroy_d3d_surfaces(priv); + if (!change_d3d_backbuffer(priv)) + return 0; + } + + if (!priv->d3d_device) + return 1; + + if (!create_d3d_surfaces(priv)) + return 0; + + if (FAILED(IDirect3DDevice9_SetViewport(priv->d3d_device, &vp))) { + mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>Setting viewport failed.\n"); + return 0; + } + + // so that screen coordinates map to D3D ones + D3DMATRIX view; + d3d_matrix_ortho(&view, 0.5f, vp.Width + 0.5f, vp.Height + 0.5f, 0.5f); + IDirect3DDevice9_SetTransform(priv->d3d_device, D3DTS_VIEW, &view); + + calc_fs_rect(priv); + + priv->vo->want_redraw = true; + + return 1; +} + +/** @brief Uninitialize Direct3D and close the window. + */ +static void uninit_d3d(d3d_priv *priv) +{ + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>uninit_d3d called.\n"); + + destroy_d3d_surfaces(priv); + + if (priv->d3d_device) + IDirect3DDevice9_Release(priv->d3d_device); + priv->d3d_device = NULL; + + if (priv->d3d_handle) { + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Stopping Direct3D.\n"); + IDirect3D9_Release(priv->d3d_handle); + } + priv->d3d_handle = NULL; +} + +static uint32_t d3d_upload_and_render_frame_texture(d3d_priv *priv, + mp_image_t *mpi) +{ + if (!(mpi->flags & MP_IMGFLAG_DRAW_CALLBACK)) + draw_slice(priv->vo, mpi->planes, mpi->stride, mpi->w, mpi->h, 0, 0); + + d3d_unlock_video_objects(priv); + + for (int n = 0; n < priv->plane_count; n++) { + d3dtex_update(priv, &priv->planes[n].texture); + } + + return d3d_draw_frame(priv); +} + +/** @brief Render a frame on the screen. + * @param mpi mpi structure with the decoded frame inside + * @return VO_TRUE on success, VO_ERROR on failure + */ +static uint32_t d3d_upload_and_render_frame(d3d_priv *priv, mp_image_t *mpi) +{ + if (!priv->d3d_device) + return VO_TRUE; + + if (priv->use_textures) + return d3d_upload_and_render_frame_texture(priv, mpi); + + if (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK) + goto skip_upload; + + if (mpi->flags & MP_IMGFLAG_PLANAR) { /* Copy a planar frame. */ + draw_slice(priv->vo, mpi->planes, mpi->stride, mpi->w, mpi->h, 0, 0); + goto skip_upload; + } + + /* If we're here, then we should lock the rect and copy a packed frame */ + if (!priv->locked_rect.pBits) { + if (FAILED(IDirect3DSurface9_LockRect(priv->d3d_surface, + &priv->locked_rect, NULL, 0))) { + mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>Surface lock failed.\n"); + return VO_ERROR; + } + } + + memcpy_pic(priv->locked_rect.pBits, mpi->planes[0], mpi->stride[0], + mpi->height, priv->locked_rect.Pitch, mpi->stride[0]); + +skip_upload: + d3d_unlock_video_objects(priv); + + return d3d_draw_frame(priv); +} + +static uint32_t d3d_draw_frame(d3d_priv *priv) +{ + int n; + + if (!priv->d3d_device) + return VO_TRUE; + + if (!d3d_begin_scene(priv)) + return VO_ERROR; + + if (priv->is_clear_needed || priv->opt_swap_discard) { + IDirect3DDevice9_Clear(priv->d3d_device, 0, NULL, + D3DCLEAR_TARGET, 0, 0, 0); + priv->is_clear_needed = 0; + } + + if (priv->use_textures) { + + for (n = 0; n < priv->plane_count; n++) { + IDirect3DDevice9_SetTexture(priv->d3d_device, n, + d3dtex_get_render_texture(priv, &priv->planes[n].texture)); + } + + RECT rm = priv->fs_movie_rect; + RECT rs = priv->fs_panscan_rect; + + vertex_video vb[] = { + { rm.left, rm.top, 0.0f}, + { rm.right, rm.top, 0.0f}, + { rm.left, rm.bottom, 0.0f}, + { rm.right, rm.bottom, 0.0f} + }; + + float texc[4][2] = { + { rs.left, rs.top}, + { rs.right, rs.top}, + { rs.left, rs.bottom}, + { rs.right, rs.bottom} + }; + + for (n = 0; n < priv->plane_count; n++) { + float s_x = (1.0f / (1 << priv->planes[n].shift_x)) + / priv->planes[n].texture.tex_w; + float s_y = (1.0f / (1 << priv->planes[n].shift_y)) + / priv->planes[n].texture.tex_h; + for (int i = 0; i < 4; i++) { + vb[i].t[n][0] = texc[i][0] * s_x; + vb[i].t[n][1] = texc[i][1] * s_y; + } + } + + if (priv->pixel_shader) { + IDirect3DDevice9_SetPixelShader(priv->d3d_device, priv->pixel_shader); + IDirect3DDevice9_SetPixelShaderConstantF(priv->d3d_device, 0, + &priv->d3d_colormatrix._11, + 4); + IDirect3DDevice9_SetPixelShaderConstantF(priv->d3d_device, 5, + priv->d3d_depth_vector, + 1); + } + + IDirect3DDevice9_SetFVF(priv->d3d_device, D3DFVF_VIDEO_VERTEX); + IDirect3DDevice9_DrawPrimitiveUP(priv->d3d_device, D3DPT_TRIANGLESTRIP, + 2, &vb[0], sizeof(vertex_video)); + + IDirect3DDevice9_SetPixelShader(priv->d3d_device, NULL); + + for (n = 0; n < priv->plane_count; n++) { + IDirect3DDevice9_SetTexture(priv->d3d_device, n, NULL); + } + + } else { + if (FAILED(IDirect3DDevice9_StretchRect(priv->d3d_device, + priv->d3d_surface, + &priv->fs_panscan_rect, + priv->d3d_backbuf, + &priv->fs_movie_rect, + D3DTEXF_LINEAR))) { + mp_msg(MSGT_VO, MSGL_ERR, + "<vo_direct3d>Copying frame to the backbuffer failed.\n"); + return VO_ERROR; + } + } + + return VO_TRUE; +} + +// Return the high byte of the value that represents white in chroma (U/V) +static int get_chroma_clear_val(int bit_depth) +{ + return 1 << (bit_depth - 1 & 7); +} + +// this macro is supposed to work on all formats supported by 3D rendering, and +// that produce "reasonable" output (i.e. no mixed up colors) +#define IMGFMT_IS_ANY_RND(x) \ + (IMGFMT_IS_BGR(x) || IMGFMT_IS_RGB(x) || IMGFMT_IS_Y(x)) + +// pixel size in bit for any IMGFMT_IS_ANY_RND(x)==true +// we assume that the actual pixel strides are always aligned on bytes +static int imgfmt_any_rnd_depth(int fmt) +{ + if (IMGFMT_IS_BGR(fmt)) + return IMGFMT_BGR_DEPTH(fmt); + if (IMGFMT_IS_RGB(fmt)) + return IMGFMT_RGB_DEPTH(fmt); + if (IMGFMT_IS_Y(fmt)) + return IMGFMT_Y_DEPTH(fmt); + assert(false); + return 0; +} + +static D3DFORMAT check_format(d3d_priv *priv, uint32_t movie_fmt, + bool as_texture) +{ + const char *type = as_texture ? "texture rendering" : "StretchRect"; + const struct fmt_entry *cur = &fmt_table[0]; + + // Don't try to handle weird packed texture formats (although I don't know + // if D3D9 would even accept any such format for 3D rendering; and we + // certainly don't try any tricks like matching it to RGB formats and + // applying a YUV conversion matrix) + if (as_texture && !IMGFMT_IS_ANY_RND(movie_fmt)) + return 0; + + while (cur->mplayer_fmt) { + if (cur->mplayer_fmt == movie_fmt) { + HRESULT res; + if (as_texture) { + res = IDirect3D9_CheckDeviceFormat(priv->d3d_handle, + D3DADAPTER_DEFAULT, + DEVTYPE, + priv->desktop_fmt, + D3DUSAGE_DYNAMIC | D3DUSAGE_QUERY_FILTER, + D3DRTYPE_TEXTURE, + cur->fourcc); + } else { + /* Test conversion from Movie colorspace to + * display's target colorspace. */ + res = IDirect3D9_CheckDeviceFormatConversion(priv->d3d_handle, + D3DADAPTER_DEFAULT, + DEVTYPE, + cur->fourcc, + priv->desktop_fmt); + } + if (FAILED(res)) { + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Rejected image format " + "(%s): %s\n", type, vo_format_name(cur->mplayer_fmt)); + return 0; + } + + mp_msg(MSGT_VO, MSGL_DBG2, "<vo_direct3d>Accepted image format " + "(%s): %s\n", type, vo_format_name(cur->mplayer_fmt)); + + return cur->fourcc; + } + cur++; + } + + return 0; +} + +// Check whether YUV conversion with shaders can be done. +// Returns the format the individual planes should use (or 0 on failure) +static D3DFORMAT check_shader_conversion(d3d_priv *priv, uint32_t fmt) +{ + if (priv->opt_disable_shaders) + return 0; + int component_bits; + if (!mp_get_chroma_shift(fmt, NULL, NULL, &component_bits)) + return 0; + if (component_bits < 8 || component_bits > 16) + return 0; + bool is_8bit = component_bits == 8; + if (!is_8bit && priv->opt_only_8bit) + return 0; + int texfmt = IMGFMT_Y8; + if (!is_8bit) { + if (priv->opt_16bit_textures) + texfmt = IMGFMT_Y16; + else + texfmt = IMGFMT_A8Y8; + } + return check_format(priv, texfmt, true); +} + +// Return if the image format can be used. If it can, decide which rendering +// and conversion mode to use. +// If initialize is true, actually setup all variables to use the picked +// rendering mode. +static bool init_rendering_mode(d3d_priv *priv, uint32_t fmt, bool initialize) +{ + int n; + int blit_d3dfmt = check_format(priv, fmt, false); + int texture_d3dfmt = check_format(priv, fmt, true); + int shader_d3dfmt = check_shader_conversion(priv, fmt); + + if (priv->opt_disable_textures) + texture_d3dfmt = 0; + if (priv->opt_disable_shaders) + shader_d3dfmt = 0; + if (priv->opt_disable_stretchrect) + blit_d3dfmt = 0; + + if (!(blit_d3dfmt || shader_d3dfmt || texture_d3dfmt)) + return false; + + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Accepted rendering methods for " + "format='%s': StretchRect=%#x, Texture=%#x, Texture+Shader=%#x.\n", + vo_format_name(fmt), blit_d3dfmt, texture_d3dfmt, shader_d3dfmt); + + if (!initialize) + return true; + + // initialization doesn't fail beyond this point + + priv->use_shaders = false; + priv->use_textures = false; + priv->use_2ch_hack = false; + priv->movie_src_fmt = 0; + priv->pixel_shader_data = NULL; + priv->plane_count = 0; + priv->image_format = fmt; + + if (blit_d3dfmt && priv->opt_prefer_stretchrect) + texture_d3dfmt = shader_d3dfmt = 0; + + if (texture_d3dfmt) { + priv->use_textures = true; + } else if (shader_d3dfmt) { + priv->use_textures = true; + priv->use_shaders = true; + } else { + assert(!!blit_d3dfmt); + } + + if (priv->use_textures) { + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Using 3D rendering.\n"); + + struct texplane *planes = &priv->planes[0]; + planes[0].shift_x = planes[0].shift_y = 0; + planes[0].clearval = 0; + + if (!priv->use_shaders) { + assert(IMGFMT_IS_ANY_RND(priv->image_format)); + priv->plane_count = 1; + planes[0].bits_per_pixel = imgfmt_any_rnd_depth(priv->image_format); + planes[0].d3d_format = texture_d3dfmt; + } else { + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Using YUV shaders.\n"); + + int sx, sy, component_bits; + mp_get_chroma_shift(priv->image_format, &sx, &sy, &component_bits); + priv->plane_count = 3; + for (n = 0; n < 3; n++) { + planes[n].d3d_format = shader_d3dfmt; + planes[n].bits_per_pixel = component_bits; + if (n > 0) { + planes[n].shift_x = sx; + planes[n].shift_y = sy; + planes[n].clearval = get_chroma_clear_val(component_bits); + } + } + if (shader_d3dfmt != D3DFMT_A8L8) { + priv->pixel_shader_data = d3d_shader_yuv; + } else { + mp_msg(MSGT_VO, MSGL_WARN, "<vo_direct3d>Using YUV 2ch hack.\n"); + + priv->pixel_shader_data = d3d_shader_yuv_2ch; + priv->use_2ch_hack = true; + } + } + + for (n = 0; n < priv->plane_count; n++) { + planes[n].bytes_per_pixel = (planes[n].bits_per_pixel + 7) / 8; + } + + } else { + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Using StretchRect.\n"); + + priv->movie_src_fmt = blit_d3dfmt; + } + + update_colorspace(priv); + + return true; +} + +/** @brief Query if movie colorspace is supported by the HW. + * @return 0 on failure, device capabilities (not probed + * currently) on success. + */ +static int query_format(d3d_priv *priv, uint32_t movie_fmt) +{ + if (!init_rendering_mode(priv, movie_fmt, false)) + return 0; + + int osd_caps = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW + | VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN; + if (!priv->opt_disable_osd) + osd_caps |= VFCAP_OSD; + return osd_caps; +} + +/**************************************************************************** + * * + * * + * * + * libvo Control / Callback functions * + * * + * * + * * + ****************************************************************************/ + +static void get_2ch_depth_multiplier(int depth, float *out_f1, float *out_f2) { + // How to get these values: + // The suffix i8 and i16 is for values with 8/16 bit fixed point numbers. + // The suffix f is for float, ideally in the range 0.0-1.0. + // c_i8 is a two component vector, sampled from a two channel texture. + // (c_i8.x is the low byte, c_i8.y is the high byte) + // r_f is the resulting color scalar value. + // + // c_i8 = c_f * (2^8-1) + // r_i16 = c_i8.x + c_i8.y * 2^8 + // r_f = r_i16 / (2^16-1) + // = c_f.x * (2^8-1) / (2^16-1) + c_f.y * (2^8-1) * 2^8 / (2^16-1) + // = c_f.x * ((2^8-1) / (2^16-1)) + c_f.y * (2^8 * ((2^8-1) / (2^16-1))) + // out = ((2^8-1) / (2^16-1), 2^8 * ((2^8-1) / (2^16-1))) + // The result color is r_f = dot(c_f, out). + // Same goes for other bit depth, such as 10 bit. Assuming (2^depth-1) is + // the maximum possible value at that depth, you have to scale the value + // r_i16 with it, the factor (2^16-1) in the formula above has to be + // replaced with (2^depth-1). + float factor = (float)((1 << 8) - 1) / (float)((1 << depth) - 1); + *out_f1 = factor; + *out_f2 = 256.0 * factor; +} + +static void update_colorspace(d3d_priv *priv) +{ + float coeff[3][4]; + struct mp_csp_params csp = { .colorspace = priv->colorspace }; + mp_csp_copy_equalizer_values(&csp, &priv->video_eq); + + if (priv->use_shaders) { + if (!priv->use_2ch_hack) { + csp.input_bits = priv->planes[0].bits_per_pixel; + csp.texture_bits = (csp.input_bits + 7) & ~7; + } else { + float f1, f2; + get_2ch_depth_multiplier(priv->planes[0].bits_per_pixel, &f1, &f2); + priv->d3d_depth_vector[0] = f1; + priv->d3d_depth_vector[1] = f2; + priv->d3d_depth_vector[2] = priv->d3d_depth_vector[3] = 0; + // no change + csp.input_bits = 8; + csp.texture_bits = 8; + } + + mp_get_yuv2rgb_coeffs(&csp, coeff); + for (int row = 0; row < 3; row++) { + for (int col = 0; col < 4; col++) { + priv->d3d_colormatrix.m[row][col] = coeff[row][col]; + } + } + } +} + +const char *options_help_text = "-vo direct3d command line help:\n" +"Example: -vo direct3d:disable-osd:disable-textures\n" +"Options:\n" +" prefer-stretchrect\n" +" Use IDirect3DDevice9::StretchRect over other methods if possible.\n" +" disable-stretchrect\n" +" Never render the video using IDirect3DDevice9::StretchRect.\n" +" disable-textures\n" +" Never render the video using D3D texture rendering. (Rendering with\n" +" textures + shader will still be allowed. Add disable-shaders to\n" +" completely disable video rendering with textures.)\n" +" disable-shaders\n" +" Never use shaders when rendering video.\n" +" only-8bit\n" +" Never render YUV video with more than 8 bits per component.\n" +" (Using this flag will force software conversion to 8 bit.)\n" +" disable-osd\n" +" Disable OSD rendering.\n" +" (Using this flag might force the insertion of the 'ass' video filter,\n" +" which will render the subtitles in software.)\n" +" disable-texture-align\n" +" Normally texture sizes are always aligned to 16. With this option\n" +" enabled, the video texture will always have exactly the same size as\n" +" the video itself.\n" +"Debug options. These might be incorrect, might be removed in the future, might\n" +"crash, might cause slow downs, etc. Contact the developers if you actually need\n" +"any of these for performance or proper operation.\n" +" force-power-of-2\n" +" Always force textures to power of 2, even if the device reports\n" +" non-power-of-2 texture sizes as supported.\n" +" texture-memory=N\n" +" Only affects operation with shaders/texturing enabled, and (E)OSD.\n" +" Values for N:\n" +" 0 default, will often use an additional shadow texture + copy\n" +" 1 use D3DPOOL_MANAGED\n" +" 2 use D3DPOOL_DEFAULT\n" +" 3 use D3DPOOL_SYSTEMMEM, but without shadow texture\n" +" swap-discard\n" +" Use D3DSWAPEFFECT_DISCARD, which might be faster.\n" +" Might be slower too, as it must (?) clear every frame.\n" +" exact-backbuffer\n" +" Always resize the backbuffer to window size.\n" +" no16bit-textures\n" +" Don't use textures with a 16 bit color channel for YUV formats that\n" +" use more than 8 bits per component. Instead, use D3DFMT_A8L8 textures\n" +" and compute the values sampled from the 2 channels back into one.\n" +" Might be slower, since the shader becomes slightly more complicated.\n" +" Might work better, if your drivers either don't support D3DFMT_L16,\n" +" or if either the texture unit or the shaders don't operate in at least\n" +" 16 bit precision.\n" +""; + +/** @brief libvo Callback: Preinitialize the video card. + * Preinit the hardware just enough to be queried about + * supported formats. + * + * @return 0 on success, -1 on failure + */ + +static int preinit_internal(struct vo *vo, const char *arg, bool allow_shaders) +{ + d3d_priv *priv = talloc_zero(vo, d3d_priv); + vo->priv = priv; + + *priv = (d3d_priv) { + .vo = vo, + + .opt_16bit_textures = true, + + .colorspace = MP_CSP_DETAILS_DEFAULTS, + .video_eq = { MP_CSP_EQ_CAPS_COLORMATRIX }, + }; + + for (int n = 0; n < MAX_OSD_PARTS; n++) { + struct osdpart *osd = talloc_ptrtype(priv, osd); + *osd = (struct osdpart) { + .packer = talloc_zero(osd, struct bitmap_packer), + }; + priv->osd[n] = osd; + } + + if (!allow_shaders) { + priv->opt_disable_shaders = priv->opt_disable_textures = true; + } + + const opt_t subopts[] = { + {"prefer-stretchrect", OPT_ARG_BOOL, &priv->opt_prefer_stretchrect}, + {"disable-textures", OPT_ARG_BOOL, &priv->opt_disable_textures}, + {"disable-stretchrect", OPT_ARG_BOOL, &priv->opt_disable_stretchrect}, + {"disable-shaders", OPT_ARG_BOOL, &priv->opt_disable_shaders}, + {"only-8bit", OPT_ARG_BOOL, &priv->opt_only_8bit}, + {"disable-osd", OPT_ARG_BOOL, &priv->opt_disable_osd}, + {"force-power-of-2", OPT_ARG_BOOL, &priv->opt_force_power_of_2}, + {"disable-texture-align", OPT_ARG_BOOL, &priv->opt_disable_texture_align}, + {"texture-memory", OPT_ARG_INT, &priv->opt_texture_memory}, + {"swap-discard", OPT_ARG_BOOL, &priv->opt_swap_discard}, + {"exact-backbuffer", OPT_ARG_BOOL, &priv->opt_exact_backbuffer}, + {"16bit-textures", OPT_ARG_BOOL, &priv->opt_16bit_textures}, + {NULL} + }; + if (subopt_parse(arg, subopts) != 0) { + mp_msg(MSGT_VO, MSGL_FATAL, options_help_text); + return -1; + } + + priv->d3d9_dll = LoadLibraryA("d3d9.dll"); + if (!priv->d3d9_dll) { + mp_msg(MSGT_VO, MSGL_ERR, + "<vo_direct3d>Unable to dynamically load d3d9.dll\n"); + goto err_out; + } + + priv->pDirect3DCreate9 = (void *)GetProcAddress(priv->d3d9_dll, + "Direct3DCreate9"); + if (!priv->pDirect3DCreate9) { + mp_msg(MSGT_VO, MSGL_ERR, + "<vo_direct3d>Unable to find entry point of Direct3DCreate9\n"); + goto err_out; + } + + if (!init_d3d(priv)) + goto err_out; + + /* w32_common framework call. Configures window on the screen, gets + * fullscreen dimensions and does other useful stuff. + */ + if (!vo_w32_init(vo)) { + mp_msg(MSGT_VO, MSGL_V, + "<vo_direct3d>Configuring onscreen window failed.\n"); + goto err_out; + } + + return 0; + +err_out: + uninit(vo); + return -1; +} + +static int preinit_standard(struct vo *vo, const char *arg) +{ + return preinit_internal(vo, arg, false); +} + +static int preinit_shaders(struct vo *vo, const char *arg) +{ + return preinit_internal(vo, arg, true); +} + +/** @brief libvo Callback: Handle control requests. + * @return VO_TRUE on success, VO_NOTIMPL when not implemented + */ +static int control(struct vo *vo, uint32_t request, void *data) +{ + d3d_priv *priv = vo->priv; + + switch (request) { + case VOCTRL_QUERY_FORMAT: + return query_format(priv, *(uint32_t*) data); + case VOCTRL_DRAW_IMAGE: + return d3d_upload_and_render_frame(priv, data); + case VOCTRL_FULLSCREEN: + vo_w32_fullscreen(vo); + resize_d3d(priv); + return VO_TRUE; + case VOCTRL_RESET: + return VO_NOTIMPL; + case VOCTRL_REDRAW_FRAME: + priv->is_clear_needed = 1; + d3d_draw_frame(priv); + return VO_TRUE; + case VOCTRL_SET_YUV_COLORSPACE: + priv->colorspace = *(struct mp_csp_details *)data; + update_colorspace(priv); + vo->want_redraw = true; + return VO_TRUE; + case VOCTRL_GET_YUV_COLORSPACE: + if (!priv->use_shaders) + break; // no idea what the heck D3D YUV uses + *(struct mp_csp_details *)data = priv->colorspace; + return VO_TRUE; + case VOCTRL_SET_EQUALIZER: { + if (!priv->use_shaders) + break; + struct voctrl_set_equalizer_args *args = data; + if (mp_csp_equalizer_set(&priv->video_eq, args->name, args->value) < 0) + return VO_NOTIMPL; + update_colorspace(priv); + vo->want_redraw = true; + return VO_TRUE; + } + case VOCTRL_GET_EQUALIZER: { + if (!priv->use_shaders) + break; + struct voctrl_get_equalizer_args *args = data; + return mp_csp_equalizer_get(&priv->video_eq, args->name, args->valueptr) + >= 0 ? VO_TRUE : VO_NOTIMPL; + } + case VOCTRL_ONTOP: + vo_w32_ontop(vo); + return VO_TRUE; + case VOCTRL_BORDER: + vo_w32_border(vo); + return VO_TRUE; + case VOCTRL_UPDATE_SCREENINFO: + w32_update_xinerama_info(vo); + return VO_TRUE; + case VOCTRL_SET_PANSCAN: + calc_fs_rect(priv); + return VO_TRUE; + case VOCTRL_GET_PANSCAN: + return VO_TRUE; + case VOCTRL_SCREENSHOT: { + struct voctrl_screenshot_args *args = data; + if (args->full_window) + args->out_image = get_window_screenshot(priv); + else + args->out_image = get_screenshot(priv); + return !!args->out_image; + } + } + return VO_NOTIMPL; +} + +/** @brief libvo Callback: Configre the Direct3D adapter. + * @param width Movie source width + * @param height Movie source height + * @param d_width Screen (destination) width + * @param d_height Screen (destination) height + * @param options Options bitmap + * @param format Movie colorspace format (using MPlayer's + * defines, e.g. IMGFMT_YUY2) + * @return 0 on success, VO_ERROR on failure + */ +static int config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t options, + uint32_t format) +{ + d3d_priv *priv = vo->priv; + + /* w32_common framework call. Creates window on the screen with + * the given coordinates. + */ + if (!vo_w32_config(vo, d_width, d_height, options)) { + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Creating window failed.\n"); + return VO_ERROR; + } + + if ((priv->image_format != format) + || (priv->src_width != width) + || (priv->src_height != height)) + { + d3d_destroy_video_objects(priv); + + priv->src_width = width; + priv->src_height = height; + init_rendering_mode(priv, format, true); + } + + if (!resize_d3d(priv)) + return VO_ERROR; + + return 0; /* Success */ +} + +/** @brief libvo Callback: Flip next already drawn frame on the + * screen. + */ +static void flip_page(struct vo *vo) +{ + d3d_priv *priv = vo->priv; + + if (priv->d3d_device && priv->d3d_in_scene) { + if (FAILED(IDirect3DDevice9_EndScene(priv->d3d_device))) { + mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>EndScene failed.\n"); + } + } + priv->d3d_in_scene = false; + + RECT rect = {0, 0, vo->dwidth, vo->dheight}; + if (!priv->d3d_device || + FAILED(IDirect3DDevice9_Present(priv->d3d_device, &rect, 0, 0, 0))) { + mp_msg(MSGT_VO, MSGL_V, + "<vo_direct3d>Trying to reinitialize uncooperative video adapter.\n"); + if (!reconfigure_d3d(priv)) { + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Reinitialization failed.\n"); + return; + } else { + mp_msg(MSGT_VO, MSGL_V, + "<vo_direct3d>Video adapter reinitialized.\n"); + } + } +} + +/** @brief libvo Callback: Uninitializes all pointers and closes + * all D3D related stuff, + */ +static void uninit(struct vo *vo) +{ + d3d_priv *priv = vo->priv; + + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>uninit called.\n"); + + uninit_d3d(priv); + vo_w32_uninit(vo); + if (priv->d3d9_dll) + FreeLibrary(priv->d3d9_dll); + priv->d3d9_dll = NULL; +} + +/** @brief libvo Callback: Handles video window events. + */ +static void check_events(struct vo *vo) +{ + d3d_priv *priv = vo->priv; + + int flags = vo_w32_check_events(vo); + if (flags & VO_EVENT_RESIZE) + resize_d3d(priv); + + if (flags & VO_EVENT_EXPOSE) + vo->want_redraw = true; +} + +static int draw_slice_textures(d3d_priv *priv, uint8_t *src[], int stride[], + int w, int h, int x, int y) +{ + if (!d3d_lock_video_textures(priv)) + return VO_FALSE; + + for (int n = 0; n < priv->plane_count; n++) { + struct texplane *plane = &priv->planes[n]; + + int dst_stride = plane->locked_rect.Pitch; + uint8_t *pdst = (uint8_t*)plane->locked_rect.pBits + + (y >> plane->shift_y) * dst_stride + + (x >> plane->shift_x) * plane->bytes_per_pixel; + + memcpy_pic(pdst, src[n], (w >> plane->shift_x) * plane->bytes_per_pixel, + h >> plane->shift_y, dst_stride, stride[n]); + } + + return 0; +} + +/** @brief libvo Callback: Draw slice + * @return 0 on success + */ +static int draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h, + int x, int y) +{ + d3d_priv *priv = vo->priv; + + if (!priv->d3d_device) + return 0; + + char *my_src; /**< Pointer to the source image */ + char *dst; /**< Pointer to the destination image */ + int uv_stride; /**< Stride of the U/V planes */ + + if (priv->use_textures) + return draw_slice_textures(priv, src, stride, w, h, x, y); + + /* Lock the offscreen surface if it's not already locked. */ + if (!priv->locked_rect.pBits) { + if (FAILED(IDirect3DSurface9_LockRect(priv->d3d_surface, + &priv->locked_rect, NULL, 0))) { + mp_msg(MSGT_VO, MSGL_V, "<vo_direct3d>Surface lock failure.\n"); + return VO_FALSE; + } + } + + uv_stride = priv->locked_rect.Pitch / 2; + + /* Copy Y */ + dst = priv->locked_rect.pBits; + dst = dst + priv->locked_rect.Pitch * y + x; + my_src = src[0]; + memcpy_pic(dst, my_src, w, h, priv->locked_rect.Pitch, stride[0]); + + w /= 2; + h /= 2; + x /= 2; + y /= 2; + + /* Copy U */ + dst = priv->locked_rect.pBits; + dst = dst + priv->locked_rect.Pitch * priv->src_height + + uv_stride * y + x; + if (priv->movie_src_fmt == MAKEFOURCC('Y','V','1','2')) + my_src = src[2]; + else + my_src = src[1]; + + memcpy_pic(dst, my_src, w, h, uv_stride, stride[1]); + + /* Copy V */ + dst = priv->locked_rect.pBits; + dst = dst + priv->locked_rect.Pitch * priv->src_height + + uv_stride * (priv->src_height / 2) + uv_stride * y + x; + if (priv->movie_src_fmt == MAKEFOURCC('Y','V','1','2')) + my_src=src[1]; + else + my_src=src[2]; + + memcpy_pic(dst, my_src, w, h, uv_stride, stride[2]); + + return 0; /* Success */ +} + +static bool get_screenshot_from_surface(d3d_priv *priv, mp_image_t *image) +{ + if (!priv->locked_rect.pBits) { + if (FAILED(IDirect3DSurface9_LockRect(priv->d3d_surface, + &priv->locked_rect, NULL, 0))) { + mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>Surface lock failed.\n"); + return false; + } + } + + if (image->flags & MP_IMGFLAG_PLANAR) { + char *src; + int w = priv->src_width; + int h = priv->src_height; + int swapped = priv->movie_src_fmt == MAKEFOURCC('Y','V','1','2'); + int plane1 = swapped ? 2 : 1; + int plane2 = swapped ? 1 : 2; + + int uv_stride = priv->locked_rect.Pitch / 2; + + /* Copy Y */ + src = priv->locked_rect.pBits; + memcpy_pic(image->planes[0], src, w, h, priv->locked_rect.Pitch, + image->stride[0]); + + w /= 2; + h /= 2; + + /* Copy U */ + src = priv->locked_rect.pBits; + src = src + priv->locked_rect.Pitch * priv->src_height; + + memcpy_pic(image->planes[plane1], src, w, h, uv_stride, + image->stride[1]); + + /* Copy V */ + src = priv->locked_rect.pBits; + src = src + priv->locked_rect.Pitch * priv->src_height + + uv_stride * (priv->src_height / 2); + + memcpy_pic(image->planes[plane2], src, w, h, uv_stride, + image->stride[2]); + } else { + // packed YUV or RGB + memcpy_pic(image->planes[0], priv->locked_rect.pBits, image->stride[0], + image->height, priv->locked_rect.Pitch, image->stride[0]); + } + + d3d_unlock_video_objects(priv); + return true; +} + +static bool get_screenshot_from_texture(d3d_priv *priv, mp_image_t *image) +{ + if (!d3d_lock_video_textures(priv)) { + d3d_unlock_video_objects(priv); + return false; + } + + assert(image->num_planes == priv->plane_count); + + for (int n = 0; n < priv->plane_count; n++) { + struct texplane *plane = &priv->planes[n]; + + int width = priv->src_width >> plane->shift_x; + int height = priv->src_height >> plane->shift_y; + + memcpy_pic(image->planes[n], plane->locked_rect.pBits, + width * plane->bytes_per_pixel, height, + image->stride[n], plane->locked_rect.Pitch); + } + + return true; +} + +static mp_image_t *get_screenshot(d3d_priv *priv) +{ + if (!priv->d3d_device) + return NULL; + + mp_image_t *image = alloc_mpi(priv->src_width, priv->src_height, + priv->image_format); + + bool res; + + if (priv->use_textures) + res = get_screenshot_from_texture(priv, image); + else + res = get_screenshot_from_surface(priv, image); + + if (!res) { + free_mp_image(image); + return NULL; + } + + image->display_w = priv->vo->aspdat.prew; + image->display_h = priv->vo->aspdat.preh; + + mp_image_set_colorspace_details(image, &priv->colorspace); + + return image; +} + +static mp_image_t *get_window_screenshot(d3d_priv *priv) +{ + D3DDISPLAYMODE mode; + mp_image_t *image = NULL; + RECT window_rc; + RECT screen_rc; + RECT visible; + POINT pt; + D3DLOCKED_RECT locked_rect; + int width, height; + + if (FAILED(IDirect3DDevice9_GetDisplayMode(priv->d3d_device, 0, &mode))) { + mp_msg(MSGT_VO,MSGL_ERR, "<vo_direct3d>GetDisplayMode failed.\n"); + goto error_exit; + } + + IDirect3DSurface9 *surface = NULL; + if (FAILED(IDirect3DDevice9_CreateOffscreenPlainSurface(priv->d3d_device, + mode.Width, mode.Height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &surface, + NULL))) + { + mp_msg(MSGT_VO,MSGL_ERR, "<vo_direct3d>Couldn't create surface.\n"); + goto error_exit; + } + + if (FAILED(IDirect3DDevice9_GetFrontBufferData(priv->d3d_device, 0, + surface))) + { + mp_msg(MSGT_VO,MSGL_ERR, "<vo_direct3d>Couldn't copy frontbuffer.\n"); + goto error_exit; + } + + GetClientRect(priv->vo->w32->window, &window_rc); + pt = (POINT) { 0, 0 }; + ClientToScreen(priv->vo->w32->window, &pt); + window_rc.left = pt.x; + window_rc.top = pt.y; + window_rc.right += window_rc.left; + window_rc.bottom += window_rc.top; + + screen_rc = (RECT) { 0, 0, mode.Width, mode.Height }; + + if (!IntersectRect(&visible, &screen_rc, &window_rc)) + goto error_exit; + width = visible.right - visible.left; + height = visible.bottom - visible.top; + if (width < 1 || height < 1) + goto error_exit; + + image = alloc_mpi(width, height, IMGFMT_BGR32); + + IDirect3DSurface9_LockRect(surface, &locked_rect, NULL, 0); + + memcpy_pic(image->planes[0], (char*)locked_rect.pBits + visible.top * + locked_rect.Pitch + visible.left * 4, width * 4, height, + image->stride[0], locked_rect.Pitch); + + IDirect3DSurface9_UnlockRect(surface); + IDirect3DSurface9_Release(surface); + + return image; + +error_exit: + if (image) + free_mp_image(image); + if (surface) + IDirect3DSurface9_Release(surface); + return NULL; +} + +static bool upload_osd(d3d_priv *priv, struct osdpart *osd, + struct sub_bitmaps *imgs) +{ + D3DFORMAT fmt = osd_fmt_table[imgs->format]; + + osd->packer->w_max = priv->max_texture_width; + osd->packer->h_max = priv->max_texture_height; + + osd->packer->padding = imgs->scaled; // assume 2x2 filter on scaling + int r = packer_pack_from_subbitmaps(osd->packer, imgs); + if (r < 0) { + mp_msg(MSGT_VO, MSGL_ERR, "<vo_direct3d>OSD bitmaps do not fit on " + "a surface with the maximum supported size %dx%d.\n", + osd->packer->w_max, osd->packer->h_max); + return false; + } + + if (osd->packer->w > osd->texture.tex_w + || osd->packer->h > osd->texture.tex_h + || osd->format != imgs->format) + { + osd->format = imgs->format; + + int new_w = osd->packer->w; + int new_h = osd->packer->h; + d3d_fix_texture_size(priv, &new_w, &new_h); + + mp_msg(MSGT_VO, MSGL_DBG2, "<vo_direct3d>reallocate OSD surface.\n"); + + d3dtex_release(priv, &osd->texture); + d3dtex_allocate(priv, &osd->texture, fmt, new_w, new_h); + + if (!osd->texture.system) + return false; // failed to allocate + } + + struct pos bb[2]; + packer_get_bb(osd->packer, bb); + RECT dirty_rc = { bb[0].x, bb[0].y, bb[1].x, bb[1].y }; + + D3DLOCKED_RECT locked_rect; + + if (FAILED(IDirect3DTexture9_LockRect(osd->texture.system, 0, &locked_rect, + &dirty_rc, 0))) + { + mp_msg(MSGT_VO,MSGL_ERR, "<vo_direct3d>OSD texture lock failed.\n"); + return false; + } + + int ps = fmt == D3DFMT_A8 ? 1 : 4; + packer_copy_subbitmaps(osd->packer, imgs, locked_rect.pBits, ps, + locked_rect.Pitch); + + if (FAILED(IDirect3DTexture9_UnlockRect(osd->texture.system, 0))) { + mp_msg(MSGT_VO,MSGL_ERR, "<vo_direct3d>OSD texture unlock failed.\n"); + return false; + } + + return d3dtex_update(priv, &osd->texture); +} + +static struct osdpart *generate_osd(d3d_priv *priv, struct sub_bitmaps *imgs) +{ + if (imgs->num_parts == 0 || !osd_fmt_table[imgs->format]) + return NULL; + + struct osdpart *osd = priv->osd[imgs->render_index]; + + if (imgs->bitmap_pos_id != osd->bitmap_pos_id) { + if (imgs->bitmap_id != osd->bitmap_id) { + if (!upload_osd(priv, osd, imgs)) + osd->packer->count = 0; + } + + osd->bitmap_id = imgs->bitmap_id; + osd->bitmap_pos_id = imgs->bitmap_pos_id; + osd->num_vertices = 0; + } + + return osd->packer->count ? osd : NULL; +} + +static D3DCOLOR ass_to_d3d_color(uint32_t color) +{ + uint32_t r = (color >> 24) & 0xff; + uint32_t g = (color >> 16) & 0xff; + uint32_t b = (color >> 8) & 0xff; + uint32_t a = 0xff - (color & 0xff); + return D3DCOLOR_ARGB(a, r, g, b); +} + +static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs) +{ + d3d_priv *priv = ctx; + + struct osdpart *osd = generate_osd(priv, imgs); + if (!osd) + return; + + if (osd->packer->count && !osd->num_vertices) { + // We need 2 primitives per quad which makes 6 vertices (we could reduce + // the number of vertices by using an indexed vertex array, but it's + // probably not worth doing) + osd->num_vertices = osd->packer->count * 6; + osd->vertices = talloc_realloc(osd, osd->vertices, vertex_osd, + osd->num_vertices); + + float tex_w = osd->texture.tex_w; + float tex_h = osd->texture.tex_h; + + for (int n = 0; n < osd->packer->count; n++) { + struct sub_bitmap *b = &imgs->parts[n]; + struct pos p = osd->packer->result[n]; + + D3DCOLOR color = imgs->format == SUBBITMAP_LIBASS + ? ass_to_d3d_color(b->libass.color) + : D3DCOLOR_ARGB(255, 255, 255, 255); + + float x0 = b->x; + float y0 = b->y; + float x1 = b->x + b->dw; + float y1 = b->y + b->dh; + float tx0 = p.x / tex_w; + float ty0 = p.y / tex_h; + float tx1 = (p.x + b->w) / tex_w; + float ty1 = (p.y + b->h) / tex_h; + + vertex_osd *v = &osd->vertices[n * 6]; + v[0] = (vertex_osd) { x0, y0, 0, color, tx0, ty0 }; + v[1] = (vertex_osd) { x1, y0, 0, color, tx1, ty0 }; + v[2] = (vertex_osd) { x0, y1, 0, color, tx0, ty1 }; + v[3] = (vertex_osd) { x1, y1, 0, color, tx1, ty1 }; + v[4] = v[2]; + v[5] = v[1]; + } + } + + d3d_begin_scene(priv); + + IDirect3DDevice9_SetRenderState(priv->d3d_device, + D3DRS_ALPHABLENDENABLE, TRUE); + + IDirect3DDevice9_SetTexture(priv->d3d_device, 0, + d3dtex_get_render_texture(priv, &osd->texture)); + + if (imgs->format == SUBBITMAP_LIBASS) { + // do not use the color value from the A8 texture, because that is black + IDirect3DDevice9_SetRenderState(priv->d3d_device,D3DRS_TEXTUREFACTOR, + 0xFFFFFFFF); + IDirect3DDevice9_SetTextureStageState(priv->d3d_device,0, + D3DTSS_COLORARG1, D3DTA_TFACTOR); + + IDirect3DDevice9_SetTextureStageState(priv->d3d_device, 0, + D3DTSS_ALPHAOP, D3DTOP_MODULATE); + } else { + IDirect3DDevice9_SetRenderState(priv->d3d_device, D3DRS_SRCBLEND, + D3DBLEND_ONE); + } + + IDirect3DDevice9_SetFVF(priv->d3d_device, D3DFVF_OSD_VERTEX); + IDirect3DDevice9_DrawPrimitiveUP(priv->d3d_device, D3DPT_TRIANGLELIST, + osd->num_vertices / 3, + osd->vertices, sizeof(vertex_osd)); + + IDirect3DDevice9_SetTextureStageState(priv->d3d_device,0, + D3DTSS_COLORARG1, D3DTA_TEXTURE); + IDirect3DDevice9_SetTextureStageState(priv->d3d_device, 0, + D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + IDirect3DDevice9_SetRenderState(priv->d3d_device, + D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + + IDirect3DDevice9_SetTexture(priv->d3d_device, 0, NULL); + + IDirect3DDevice9_SetRenderState(priv->d3d_device, + D3DRS_ALPHABLENDENABLE, FALSE); +} + + +static void draw_osd(struct vo *vo, struct osd_state *osd) +{ + d3d_priv *priv = vo->priv; + if (!priv->d3d_device) + return; + + osd_draw(osd, priv->osd_res, osd->vo_pts, 0, osd_fmt_supported, + draw_osd_cb, priv); +} + +#define AUTHOR "Georgi Petrov (gogothebee) <gogothebee@gmail.com> and others" + +const struct vo_driver video_out_direct3d = { + .is_new = true, + .info = &(const vo_info_t) { + "Direct3D 9 Renderer", + "direct3d", + AUTHOR, + "" + }, + .preinit = preinit_standard, + .config = config, + .control = control, + .draw_slice = draw_slice, + .draw_osd = draw_osd, + .flip_page = flip_page, + .check_events = check_events, + .uninit = uninit, +}; + +const struct vo_driver video_out_direct3d_shaders = { + .is_new = true, + .info = &(const vo_info_t) { + "Direct3D 9 Renderer (using shaders for YUV conversion)", + "direct3d_shaders", + AUTHOR, + "" + }, + .preinit = preinit_shaders, + .config = config, + .control = control, + .draw_slice = draw_slice, + .draw_osd = draw_osd, + .flip_page = flip_page, + .check_events = check_events, + .uninit = uninit, +}; diff --git a/video/out/vo_image.c b/video/out/vo_image.c new file mode 100644 index 0000000000..7f80a16f35 --- /dev/null +++ b/video/out/vo_image.c @@ -0,0 +1,198 @@ +/* + * This file is part of mplayer. + * + * mplayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mplayer. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <stdbool.h> +#include <sys/stat.h> + +#include <libswscale/swscale.h> + +#include "config.h" +#include "bstr.h" +#include "osdep/io.h" +#include "path.h" +#include "talloc.h" +#include "mp_msg.h" +#include "libvo/video_out.h" +#include "libvo/csputils.h" +#include "libmpcodecs/vfcap.h" +#include "libmpcodecs/mp_image.h" +#include "fmt-conversion.h" +#include "image_writer.h" +#include "m_config.h" +#include "m_option.h" + +struct priv { + struct image_writer_opts *opts; + char *outdir; + + int frame; + + uint32_t d_width; + uint32_t d_height; + + struct mp_csp_details colorspace; +}; + +static bool checked_mkdir(const char *buf) +{ + mp_msg(MSGT_VO, MSGL_INFO, "[vo_image] Creating output directory '%s'...\n", + buf); + if (mkdir(buf, 0755) < 0) { + char *errstr = strerror(errno); + if (errno == EEXIST) { + struct stat stat_p; + if (mp_stat(buf, &stat_p ) == 0 && S_ISDIR(stat_p.st_mode)) + return true; + } + mp_msg(MSGT_VO, MSGL_ERR, "[vo_image] Error creating output directory" + ": %s\n", errstr); + return false; + } + return true; +} + +static int config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t flags, + uint32_t format) +{ + struct priv *p = vo->priv; + + p->d_width = d_width; + p->d_height = d_height; + + if (p->outdir && vo->config_count < 1) + if (!checked_mkdir(p->outdir)) + return -1; + + return 0; +} + +static void check_events(struct vo *vo) +{ +} + +static void draw_osd(struct vo *vo, struct osd_state *osd) +{ +} + +static void flip_page(struct vo *vo) +{ +} + +static uint32_t draw_image(struct vo *vo, mp_image_t *mpi) +{ + struct priv *p = vo->priv; + + mp_image_t img = *mpi; + img.width = p->d_width; + img.height = p->d_height; + mp_image_set_colorspace_details(&img, &p->colorspace); + + void *t = talloc_new(NULL); + char *filename = talloc_asprintf(t, "%08d.%s", p->frame, + image_writer_file_ext(p->opts)); + + if (p->outdir && strlen(p->outdir)) + filename = mp_path_join(t, bstr0(p->outdir), bstr0(filename)); + + mp_msg(MSGT_VO, MSGL_STATUS, "\nSaving %s\n", filename); + write_image(&img, p->opts, filename); + + talloc_free(t); + + (p->frame)++; + + return VO_TRUE; +} + +static int query_format(struct vo *vo, uint32_t fmt) +{ + enum PixelFormat av_format = imgfmt2pixfmt(fmt); + + // NOTE: accept everything that can be converted by swscale. screenshot.c + // always wants RGB (at least for now), but it probably doesn't matter + // whether we or screenshot.c do the conversion. + if (av_format != PIX_FMT_NONE && sws_isSupportedInput(av_format)) + return VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | + VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN; + return 0; +} + +static void uninit(struct vo *vo) +{ +} + +static int preinit(struct vo *vo, const char *arg) +{ + return 0; +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + struct priv *p = vo->priv; + + switch (request) { + case VOCTRL_QUERY_FORMAT: + return query_format(vo, *(uint32_t *)data); + case VOCTRL_DRAW_IMAGE: + return draw_image(vo, data); + case VOCTRL_SET_YUV_COLORSPACE: + p->colorspace = *(struct mp_csp_details *)data; + return true; + case VOCTRL_GET_YUV_COLORSPACE: + *(struct mp_csp_details *)data = p->colorspace; + return true; + // prevent random frame stepping by frontend + case VOCTRL_REDRAW_FRAME: + return true; + } + return VO_NOTIMPL; +} + +#undef OPT_BASE_STRUCT +#define OPT_BASE_STRUCT struct priv + +const struct vo_driver video_out_image = +{ + .is_new = true, + .info = &(const vo_info_t) { + "Write video frames to image files", + "image", + "wm4", + "" + }, + .priv_size = sizeof(struct priv), + .priv_defaults = &(const struct priv) { + .colorspace = MP_CSP_DETAILS_DEFAULTS, + }, + .options = (const struct m_option[]) { + OPT_SUBSTRUCT(opts, image_writer_conf, M_OPT_MERGE), + OPT_STRING("outdir", outdir, 0), + {0}, + }, + .preinit = preinit, + .config = config, + .control = control, + .draw_osd = draw_osd, + .flip_page = flip_page, + .check_events = check_events, + .uninit = uninit, +}; diff --git a/video/out/vo_lavc.c b/video/out/vo_lavc.c new file mode 100644 index 0000000000..b86cd76509 --- /dev/null +++ b/video/out/vo_lavc.c @@ -0,0 +1,553 @@ +/* + * video encoding using libavformat + * Copyright (C) 2010 Nicolas George <george@nsup.org> + * Copyright (C) 2011 Rudolf Polzer <divVerent@xonotic.org> + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include "mpcommon.h" +#include "options.h" +#include "fmt-conversion.h" +#include "libmpcodecs/mp_image.h" +#include "libmpcodecs/vfcap.h" +#include "subopt-helper.h" +#include "talloc.h" +#include "video_out.h" + +#include "encode_lavc.h" + +#include "sub/sub.h" +#include "sub/dec_sub.h" + +struct priv { + uint8_t *buffer; + size_t buffer_size; + AVStream *stream; + int have_first_packet; + + int harddup; + + double lastpts; + int64_t lastipts; + int64_t lastframeipts; + double expected_next_pts; + mp_image_t *lastimg; + int lastimg_wants_osd; + int lastdisplaycount; + + AVRational worst_time_base; + int worst_time_base_is_stream; + + struct mp_csp_details colorspace; +}; + +static int preinit(struct vo *vo, const char *arg) +{ + struct priv *vc; + if (!encode_lavc_available(vo->encode_lavc_ctx)) { + mp_msg(MSGT_ENCODE, MSGL_ERR, + "vo-lavc: the option -o (output file) must be specified\n"); + return -1; + } + vo->priv = talloc_zero(vo, struct priv); + vc = vo->priv; + vc->harddup = vo->encode_lavc_ctx->options->harddup; + vc->colorspace = (struct mp_csp_details) MP_CSP_DETAILS_DEFAULTS; + return 0; +} + +static void draw_image(struct vo *vo, mp_image_t *mpi, double pts); +static void uninit(struct vo *vo) +{ + struct priv *vc = vo->priv; + if (!vc) + return; + + if (vc->lastipts >= 0 && vc->stream) + draw_image(vo, NULL, MP_NOPTS_VALUE); + + if (vc->lastimg) { + // palette hack + if (vc->lastimg->imgfmt == IMGFMT_RGB8 + || vc->lastimg->imgfmt == IMGFMT_BGR8) + vc->lastimg->planes[1] = NULL; + free_mp_image(vc->lastimg); + vc->lastimg = NULL; + } + + vo->priv = NULL; +} + +static int config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t flags, + uint32_t format) +{ + struct priv *vc = vo->priv; + enum PixelFormat pix_fmt = imgfmt2pixfmt(format); + AVRational display_aspect_ratio, image_aspect_ratio; + AVRational aspect; + + if (!vc) + return -1; + + display_aspect_ratio.num = d_width; + display_aspect_ratio.den = d_height; + image_aspect_ratio.num = width; + image_aspect_ratio.den = height; + aspect = av_div_q(display_aspect_ratio, image_aspect_ratio); + + if (vc->stream) { + /* NOTE: + * in debug builds we get a "comparison between signed and unsigned" + * warning here. We choose to ignore that; just because ffmpeg currently + * uses a plain 'int' for these struct fields, it doesn't mean it always + * will */ + if (width == vc->stream->codec->width && + height == vc->stream->codec->height) { + if (aspect.num != vc->stream->codec->sample_aspect_ratio.num || + aspect.den != vc->stream->codec->sample_aspect_ratio.den) { + /* aspect-only changes are not critical */ + mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: unsupported pixel aspect " + "ratio change from %d:%d to %d:%d\n", + vc->stream->codec->sample_aspect_ratio.num, + vc->stream->codec->sample_aspect_ratio.den, + aspect.num, aspect.den); + } + return 0; + } + + /* FIXME Is it possible with raw video? */ + mp_msg(MSGT_ENCODE, MSGL_ERR, + "vo-lavc: resolution changes not supported.\n"); + goto error; + } + + vc->lastipts = MP_NOPTS_VALUE; + vc->lastframeipts = MP_NOPTS_VALUE; + + if (pix_fmt == PIX_FMT_NONE) + goto error; /* imgfmt2pixfmt already prints something */ + + vc->stream = encode_lavc_alloc_stream(vo->encode_lavc_ctx, + AVMEDIA_TYPE_VIDEO); + vc->stream->sample_aspect_ratio = vc->stream->codec->sample_aspect_ratio = + aspect; + vc->stream->codec->width = width; + vc->stream->codec->height = height; + vc->stream->codec->pix_fmt = pix_fmt; + + encode_lavc_set_csp(vo->encode_lavc_ctx, vc->stream, vc->colorspace.format); + encode_lavc_set_csp_levels(vo->encode_lavc_ctx, vc->stream, vc->colorspace.levels_out); + + if (encode_lavc_open_codec(vo->encode_lavc_ctx, vc->stream) < 0) + goto error; + + vc->colorspace.format = encode_lavc_get_csp(vo->encode_lavc_ctx, vc->stream); + vc->colorspace.levels_out = encode_lavc_get_csp_levels(vo->encode_lavc_ctx, vc->stream); + + vc->buffer_size = 6 * width * height + 200; + if (vc->buffer_size < FF_MIN_BUFFER_SIZE) + vc->buffer_size = FF_MIN_BUFFER_SIZE; + if (vc->buffer_size < sizeof(AVPicture)) + vc->buffer_size = sizeof(AVPicture); + + vc->buffer = talloc_size(vc, vc->buffer_size); + + vc->lastimg = alloc_mpi(width, height, format); + + // palette hack + if (vc->lastimg->imgfmt == IMGFMT_RGB8 || + vc->lastimg->imgfmt == IMGFMT_BGR8) + vc->lastimg->planes[1] = talloc_zero_size(vc, 1024); + + return 0; + +error: + uninit(vo); + return -1; +} + +static int query_format(struct vo *vo, uint32_t format) +{ + enum PixelFormat pix_fmt = imgfmt2pixfmt(format); + + if (!vo->encode_lavc_ctx) + return 0; + + if (!encode_lavc_supports_pixfmt(vo->encode_lavc_ctx, pix_fmt)) + return 0; + + return + VFCAP_CSP_SUPPORTED | + // we can do it + VFCAP_CSP_SUPPORTED_BY_HW | + // we don't convert colorspaces here + VFCAP_OSD | + // we have OSD + VOCAP_NOSLICES; + // we don't use slices +} + +static void write_packet(struct vo *vo, int size, AVPacket *packet) +{ + struct priv *vc = vo->priv; + + if (size < 0) { + mp_msg(MSGT_ENCODE, MSGL_ERR, "vo-lavc: error encoding\n"); + return; + } + + if (size > 0) { + packet->stream_index = vc->stream->index; + if (packet->pts != AV_NOPTS_VALUE) { + packet->pts = av_rescale_q(packet->pts, + vc->stream->codec->time_base, + vc->stream->time_base); + } else { + mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: codec did not provide pts\n"); + packet->pts = av_rescale_q(vc->lastipts, vc->worst_time_base, + vc->stream->time_base); + } + if (packet->dts != AV_NOPTS_VALUE) { + packet->dts = av_rescale_q(packet->dts, + vc->stream->codec->time_base, + vc->stream->time_base); + } + if (packet->duration > 0) { + packet->duration = av_rescale_q(packet->duration, + vc->stream->codec->time_base, + vc->stream->time_base); + } else { + // HACK: libavformat calculates dts wrong if the initial packet + // duration is not set, but ONLY if the time base is "high" and if we + // have b-frames! + if (!packet->duration) + if (!vc->have_first_packet) + if (vc->stream->codec->has_b_frames + || vc->stream->codec->max_b_frames) + if (vc->stream->time_base.num * 1000LL <= + vc->stream->time_base.den) + packet->duration = FFMAX(1, av_rescale_q(1, + vc->stream->codec->time_base, vc->stream->time_base)); + } + + if (encode_lavc_write_frame(vo->encode_lavc_ctx, packet) < 0) { + mp_msg(MSGT_ENCODE, MSGL_ERR, "vo-lavc: error writing\n"); + return; + } + + vc->have_first_packet = 1; + } +} + +static int encode_video(struct vo *vo, AVFrame *frame, AVPacket *packet) +{ + struct priv *vc = vo->priv; + if (encode_lavc_oformat_flags(vo->encode_lavc_ctx) & AVFMT_RAWPICTURE) { + if (!frame) + return 0; + memcpy(vc->buffer, frame, sizeof(AVPicture)); + mp_msg(MSGT_ENCODE, MSGL_DBG2, "vo-lavc: got pts %f\n", + frame->pts * (double) vc->stream->codec->time_base.num / + (double) vc->stream->codec->time_base.den); + packet->size = sizeof(AVPicture); + return packet->size; + } else { + int got_packet = 0; + int status = avcodec_encode_video2(vc->stream->codec, packet, + frame, &got_packet); + int size = (status < 0) ? status : got_packet ? packet->size : 0; + + if (frame) + mp_msg(MSGT_ENCODE, MSGL_DBG2, "vo-lavc: got pts %f; out size: %d\n", + frame->pts * (double) vc->stream->codec->time_base.num / + (double) vc->stream->codec->time_base.den, size); + + encode_lavc_write_stats(vo->encode_lavc_ctx, vc->stream); + return size; + } +} + +static void draw_image(struct vo *vo, mp_image_t *mpi, double pts) +{ + struct priv *vc = vo->priv; + struct encode_lavc_context *ectx = vo->encode_lavc_ctx; + int i, size; + AVFrame *frame; + AVCodecContext *avc; + int64_t frameipts; + double nextpts; + + if (!vc) + return; + if (!encode_lavc_start(ectx)) { + mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: NOTE: skipped initial video frame (probably because audio is not there yet)\n"); + return; + } + if (pts == MP_NOPTS_VALUE) { + if (mpi) + mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: frame without pts, please report; synthesizing pts instead\n"); + pts = vc->expected_next_pts; + } + + avc = vc->stream->codec; + + if (vc->worst_time_base.den == 0) { + //if (avc->time_base.num / avc->time_base.den >= vc->stream->time_base.num / vc->stream->time_base.den) + if (avc->time_base.num * (double) vc->stream->time_base.den >= + vc->stream->time_base.num * (double) avc->time_base.den) { + mp_msg(MSGT_ENCODE, MSGL_V, "vo-lavc: NOTE: using codec time base " + "(%d/%d) for frame dropping; the stream base (%d/%d) is " + "not worse.\n", (int)avc->time_base.num, + (int)avc->time_base.den, (int)vc->stream->time_base.num, + (int)vc->stream->time_base.den); + vc->worst_time_base = avc->time_base; + vc->worst_time_base_is_stream = 0; + } else { + mp_msg(MSGT_ENCODE, MSGL_WARN, "vo-lavc: NOTE: not using codec time " + "base (%d/%d) for frame dropping; the stream base (%d/%d) " + "is worse.\n", (int)avc->time_base.num, + (int)avc->time_base.den, (int)vc->stream->time_base.num, + (int)vc->stream->time_base.den); + vc->worst_time_base = vc->stream->time_base; + vc->worst_time_base_is_stream = 1; + } + + // NOTE: we use the following "axiom" of av_rescale_q: + // if time base A is worse than time base B, then + // av_rescale_q(av_rescale_q(x, A, B), B, A) == x + // this can be proven as long as av_rescale_q rounds to nearest, which + // it currently does + + // av_rescale_q(x, A, B) * B = "round x*A to nearest multiple of B" + // and: + // av_rescale_q(av_rescale_q(x, A, B), B, A) * A + // == "round av_rescale_q(x, A, B)*B to nearest multiple of A" + // == "round 'round x*A to nearest multiple of B' to nearest multiple of A" + // + // assume this fails. Then there is a value of x*A, for which the + // nearest multiple of B is outside the range [(x-0.5)*A, (x+0.5)*A[. + // Absurd, as this range MUST contain at least one multiple of B. + } + + double timeunit = (double)vc->worst_time_base.num / vc->worst_time_base.den; + + double outpts; + if (ectx->options->rawts) + outpts = pts; + else if (ectx->options->copyts) { + // fix the discontinuity pts offset + nextpts = pts; + if (ectx->discontinuity_pts_offset == MP_NOPTS_VALUE) { + ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts; + } + else if (fabs(nextpts + ectx->discontinuity_pts_offset - ectx->next_in_pts) > 30) { + mp_msg(MSGT_ENCODE, MSGL_WARN, + "vo-lavc: detected an unexpected discontinuity (pts jumped by " + "%f seconds)\n", + nextpts + ectx->discontinuity_pts_offset - ectx->next_in_pts); + ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts; + } + + outpts = pts + ectx->discontinuity_pts_offset; + } + else { + // adjust pts by knowledge of audio pts vs audio playback time + double duration = 0; + if (ectx->last_video_in_pts != MP_NOPTS_VALUE) + duration = pts - ectx->last_video_in_pts; + if (duration < 0) + duration = timeunit; // XXX warn about discontinuity? + outpts = vc->lastpts + duration; + if (ectx->audio_pts_offset != MP_NOPTS_VALUE) { + double adj = outpts - pts - ectx->audio_pts_offset; + adj = FFMIN(adj, duration * 0.1); + adj = FFMAX(adj, -duration * 0.1); + outpts -= adj; + } + } + vc->lastpts = outpts; + ectx->last_video_in_pts = pts; + frameipts = floor((outpts + encode_lavc_getoffset(ectx, vc->stream)) + / timeunit + 0.5); + + // calculate expected pts of next video frame + vc->expected_next_pts = pts + timeunit; + + if (!ectx->options->rawts && ectx->options->copyts) { + // set next allowed output pts value + nextpts = vc->expected_next_pts + ectx->discontinuity_pts_offset; + if (nextpts > ectx->next_in_pts) + ectx->next_in_pts = nextpts; + } + + // never-drop mode + if (ectx->options->neverdrop && frameipts <= vc->lastipts) { + mp_msg(MSGT_ENCODE, MSGL_INFO, "vo-lavc: -oneverdrop increased pts by %d\n", + (int) (vc->lastipts - frameipts + 1)); + frameipts = vc->lastipts + 1; + vc->lastpts = frameipts * timeunit - encode_lavc_getoffset(ectx, vc->stream); + } + + if (vc->lastipts != MP_NOPTS_VALUE) { + frame = avcodec_alloc_frame(); + + // we have a valid image in lastimg + while (vc->lastipts < frameipts) { + int64_t thisduration = vc->harddup ? 1 : (frameipts - vc->lastipts); + AVPacket packet; + + avcodec_get_frame_defaults(frame); + + // this is a nop, unless the worst time base is the STREAM time base + frame->pts = av_rescale_q(vc->lastipts, vc->worst_time_base, + avc->time_base); + + for (i = 0; i < 4; i++) { + frame->data[i] = vc->lastimg->planes[i]; + frame->linesize[i] = vc->lastimg->stride[i]; + } + frame->quality = avc->global_quality; + + av_init_packet(&packet); + packet.data = vc->buffer; + packet.size = vc->buffer_size; + size = encode_video(vo, frame, &packet); + write_packet(vo, size, &packet); + + vc->lastipts += thisduration; + ++vc->lastdisplaycount; + } + + avcodec_free_frame(&frame); + } + + if (!mpi) { + // finish encoding + do { + AVPacket packet; + av_init_packet(&packet); + packet.data = vc->buffer; + packet.size = vc->buffer_size; + size = encode_video(vo, NULL, &packet); + write_packet(vo, size, &packet); + } while (size > 0); + } else { + if (frameipts >= vc->lastframeipts) { + if (vc->lastframeipts != MP_NOPTS_VALUE && vc->lastdisplaycount != 1) + mp_msg(MSGT_ENCODE, MSGL_INFO, + "vo-lavc: Frame at pts %d got displayed %d times\n", + (int) vc->lastframeipts, vc->lastdisplaycount); + copy_mpi(vc->lastimg, mpi); + vc->lastimg_wants_osd = true; + + // palette hack + if (vc->lastimg->imgfmt == IMGFMT_RGB8 || + vc->lastimg->imgfmt == IMGFMT_BGR8) + memcpy(vc->lastimg->planes[1], mpi->planes[1], 1024); + + vc->lastframeipts = vc->lastipts = frameipts; + if (ectx->options->rawts && vc->lastipts < 0) { + mp_msg(MSGT_ENCODE, MSGL_ERR, "vo-lavc: why does this happen? DEBUG THIS! vc->lastipts = %lld\n", (long long) vc->lastipts); + vc->lastipts = -1; + } + vc->lastdisplaycount = 0; + } else { + mp_msg(MSGT_ENCODE, MSGL_INFO, "vo-lavc: Frame at pts %d got dropped " + "entirely because pts went backwards\n", (int) frameipts); + vc->lastimg_wants_osd = false; + } + } +} + +static void flip_page_timed(struct vo *vo, unsigned int pts_us, int duration) +{ +} + +static void check_events(struct vo *vo) +{ +} + +static void draw_osd(struct vo *vo, struct osd_state *osd) +{ + struct priv *vc = vo->priv; + + if (vc->lastimg && vc->lastimg_wants_osd) { + struct aspect_data asp = vo->aspdat; + double sar = (double)asp.orgw / asp.orgh; + double dar = (double)asp.prew / asp.preh; + + struct mp_osd_res dim = { + .w = asp.orgw, + .h = asp.orgh, + .display_par = sar / dar, + .video_par = dar / sar, + }; + + mp_image_set_colorspace_details(vc->lastimg, &vc->colorspace); + + osd_draw_on_image(osd, dim, osd->vo_pts, OSD_DRAW_SUB_ONLY, vc->lastimg); + } +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + struct priv *vc = vo->priv; + switch (request) { + case VOCTRL_QUERY_FORMAT: + return query_format(vo, *((uint32_t *)data)); + case VOCTRL_DRAW_IMAGE: + draw_image(vo, (mp_image_t *)data, vo->next_pts); + return 0; + case VOCTRL_SET_YUV_COLORSPACE: + vc->colorspace = *(struct mp_csp_details *)data; + if (vc->stream) { + encode_lavc_set_csp(vo->encode_lavc_ctx, vc->stream, vc->colorspace.format); + encode_lavc_set_csp_levels(vo->encode_lavc_ctx, vc->stream, vc->colorspace.levels_out); + vc->colorspace.format = encode_lavc_get_csp(vo->encode_lavc_ctx, vc->stream); + vc->colorspace.levels_out = encode_lavc_get_csp_levels(vo->encode_lavc_ctx, vc->stream); + } + return 1; + case VOCTRL_GET_YUV_COLORSPACE: + *(struct mp_csp_details *)data = vc->colorspace; + return 1; + } + return VO_NOTIMPL; +} + +const struct vo_driver video_out_lavc = { + .is_new = true, + .buffer_frames = false, + .info = &(const struct vo_info_s){ + "video encoding using libavcodec", + "lavc", + "Nicolas George <george@nsup.org>, Rudolf Polzer <divVerent@xonotic.org>", + "" + }, + .preinit = preinit, + .config = config, + .control = control, + .uninit = uninit, + .check_events = check_events, + .draw_osd = draw_osd, + .flip_page_timed = flip_page_timed, +}; + +// vim: sw=4 ts=4 et tw=80 diff --git a/video/out/vo_null.c b/video/out/vo_null.c new file mode 100644 index 0000000000..1f307f7f5b --- /dev/null +++ b/video/out/vo_null.c @@ -0,0 +1,103 @@ +/* + * based on video_out_null.c from mpeg2dec + * + * Copyright (C) Aaron Holtzman - June 2000 + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include "config.h" +#include "mp_msg.h" +#include "video_out.h" +#include "libmpcodecs/vfcap.h" +#include "libmpcodecs/mp_image.h" + +static int draw_slice(struct vo *vo, uint8_t *image[], int stride[], + int w, int h, int x, int y) +{ + return 0; +} + +static void draw_osd(struct vo *vo, struct osd_state *osd) +{ +} + +static void flip_page(struct vo *vo) +{ +} + +static int query_format(struct vo *vo, uint32_t format) +{ + if (IMGFMT_IS_HWACCEL(format)) + return 0; + return VFCAP_CSP_SUPPORTED; +} + +static int config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t flags, + uint32_t format) +{ + return 0; +} + +static void uninit(struct vo *vo) +{ +} + +static void check_events(struct vo *vo) +{ +} + +static int preinit(struct vo *vo, const char *arg) +{ + if (arg) { + mp_tmsg(MSGT_VO, MSGL_WARN, "[VO_NULL] Unknown subdevice: %s.\n", arg); + return ENOSYS; + } + return 0; +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + switch (request) { + case VOCTRL_QUERY_FORMAT: + return query_format(vo, *((uint32_t *)data)); + } + return VO_NOTIMPL; +} + +const struct vo_driver video_out_null = { + .is_new = false, + .info = &(const vo_info_t) { + "Null video output", + "null", + "Aaron Holtzman <aholtzma@ess.engr.uvic.ca>", + "" + }, + .preinit = preinit, + .config = config, + .control = control, + .draw_slice = draw_slice, + .draw_osd = draw_osd, + .flip_page = flip_page, + .check_events = check_events, + .uninit = uninit, +}; diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c new file mode 100644 index 0000000000..7b5289838f --- /dev/null +++ b/video/out/vo_opengl.c @@ -0,0 +1,2419 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can alternatively redistribute this file 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <stdbool.h> +#include <assert.h> +#include "config.h" + +#include <libavutil/common.h> + +#ifdef CONFIG_LCMS2 +#include <lcms2.h> +#include "stream/stream.h" +#endif + +#include "talloc.h" +#include "mpcommon.h" +#include "bstr.h" +#include "mp_msg.h" +#include "subopt-helper.h" +#include "video_out.h" +#include "libmpcodecs/vfcap.h" +#include "libmpcodecs/mp_image.h" +#include "geometry.h" +#include "sub/sub.h" +#include "bitmap_packer.h" + +#include "gl_common.h" +#include "gl_osd.h" +#include "filter_kernels.h" +#include "aspect.h" +#include "fastmemcpy.h" + +static const char vo_opengl_shaders[] = +// Generated from libvo/vo_opengl_shaders.glsl +#include "libvo/vo_opengl_shaders.h" +; + +// Pixel width of 1D lookup textures. +#define LOOKUP_TEXTURE_SIZE 256 + +// Texture units 0-2 are used by the video, with unit 0 for free use. +// Units 3-4 are used for scaler LUTs. +#define TEXUNIT_SCALERS 3 +#define TEXUNIT_3DLUT 5 +#define TEXUNIT_DITHER 6 + +// lscale/cscale arguments that map directly to shader filter routines. +// Note that the convolution filters are not included in this list. +static const char *fixed_scale_filters[] = { + "bilinear", + "bicubic_fast", + "sharpen3", + "sharpen5", + NULL +}; + +struct lut_tex_format { + int pixels; + GLint internal_format; + GLenum format; +}; + +// Indexed with filter_kernel->size. +// This must match the weightsN functions in the shader. +// Each entry uses (size+3)/4 pixels per LUT entry, and size/pixels components +// per pixel. +struct lut_tex_format lut_tex_formats[] = { + [2] = {1, GL_RG16F, GL_RG}, + [4] = {1, GL_RGBA16F, GL_RGBA}, + [6] = {2, GL_RGB16F, GL_RGB}, + [8] = {2, GL_RGBA16F, GL_RGBA}, + [12] = {3, GL_RGBA16F, GL_RGBA}, + [16] = {4, GL_RGBA16F, GL_RGBA}, +}; + +// must be sorted, and terminated with 0 +static const int filter_sizes[] = {2, 4, 6, 8, 12, 16, 0}; + +struct vertex { + float position[2]; + uint8_t color[4]; + float texcoord[2]; +}; + +#define VERTEX_ATTRIB_POSITION 0 +#define VERTEX_ATTRIB_COLOR 1 +#define VERTEX_ATTRIB_TEXCOORD 2 + +// 2 triangles primitives per quad = 6 vertices per quad +// (GL_QUAD is deprecated, strips can't be used with OSD image lists) +#define VERTICES_PER_QUAD 6 + +struct texplane { + int shift_x, shift_y; + GLuint gl_texture; + int gl_buffer; + int buffer_size; + void *buffer_ptr; +}; + +struct scaler { + int index; + const char *name; + float params[2]; + struct filter_kernel *kernel; + GLuint gl_lut; + const char *lut_name; + + // kernel points here + struct filter_kernel kernel_storage; +}; + +struct fbotex { + GLuint fbo; + GLuint texture; + int tex_w, tex_h; // size of .texture + int vp_w, vp_h; // viewport of fbo / used part of the texture +}; + +struct gl_priv { + struct vo *vo; + MPGLContext *glctx; + GL *gl; + + int use_indirect; + int use_gamma; + int use_srgb; + int use_scale_sep; + int use_fancy_downscaling; + int use_lut_3d; + int use_npot; + int use_pbo; + int use_glFinish; + int use_gl_debug; + int allow_sw; + + int dither_depth; + int swap_interval; + GLint fbo_format; + int stereo_mode; + int osd_color; + + struct gl_priv *defaults; + struct gl_priv *orig_cmdline; + + GLuint vertex_buffer; + GLuint vao; + + GLuint osd_programs[SUBBITMAP_COUNT]; + GLuint indirect_program, scale_sep_program, final_program; + + struct mpgl_osd *osd; + + GLuint lut_3d_texture; + int lut_3d_w, lut_3d_h, lut_3d_d; + void *lut_3d_data; + + GLuint dither_texture; + float dither_quantization; + float dither_multiply; + int dither_size; + + uint32_t image_width; + uint32_t image_height; + uint32_t image_format; + int texture_width; + int texture_height; + + bool is_yuv; + bool is_linear_rgb; + + // per pixel (full pixel when packed, each component when planar) + int plane_bytes; + int plane_bits; + int component_bits; + + GLint gl_internal_format; + GLenum gl_format; + GLenum gl_type; + + int plane_count; + struct texplane planes[3]; + + struct fbotex indirect_fbo; // RGB target + struct fbotex scale_sep_fbo; // first pass when doing 2 pass scaling + + // state for luma (0) and chroma (1) scalers + struct scaler scalers[2]; + // luma scaler parameters (the same are used for chroma) + float scaler_params[2]; + + struct mp_csp_details colorspace; + struct mp_csp_equalizer video_eq; + + int mpi_flipped; + int vo_flipped; + + struct mp_rect src_rect; // displayed part of the source video + struct mp_rect dst_rect; // video rectangle on output window + struct mp_osd_res osd_rect; // OSD size/margins + int vp_x, vp_y, vp_w, vp_h; // GL viewport + + int frames_rendered; + + void *scratch; +}; + +struct fmt_entry { + int mp_format; + GLint internal_format; + GLenum format; + int component_bits; + GLenum type; +}; + +static const struct fmt_entry mp_to_gl_formats[] = { + {IMGFMT_RGB48NE, GL_RGB16, GL_RGB, 16, GL_UNSIGNED_SHORT}, + {IMGFMT_RGB24, GL_RGB, GL_RGB, 8, GL_UNSIGNED_BYTE}, + {IMGFMT_RGBA, GL_RGBA, GL_RGBA, 8, GL_UNSIGNED_BYTE}, + {IMGFMT_RGB15, GL_RGBA, GL_RGBA, 5, GL_UNSIGNED_SHORT_1_5_5_5_REV}, + {IMGFMT_RGB16, GL_RGB, GL_RGB, 6, GL_UNSIGNED_SHORT_5_6_5_REV}, + {IMGFMT_BGR15, GL_RGBA, GL_BGRA, 5, GL_UNSIGNED_SHORT_1_5_5_5_REV}, + {IMGFMT_BGR16, GL_RGB, GL_RGB, 6, GL_UNSIGNED_SHORT_5_6_5}, + {IMGFMT_BGR24, GL_RGB, GL_BGR, 8, GL_UNSIGNED_BYTE}, + {IMGFMT_BGRA, GL_RGBA, GL_BGRA, 8, GL_UNSIGNED_BYTE}, + {0}, +}; + +static const char *osd_shaders[SUBBITMAP_COUNT] = { + [SUBBITMAP_LIBASS] = "frag_osd_libass", + [SUBBITMAP_RGBA] = "frag_osd_rgba", +}; + + +static const char help_text[]; + +static void uninit_rendering(struct gl_priv *p); +static void delete_shaders(struct gl_priv *p); +static bool reparse_cmdline(struct gl_priv *p, char *arg); + + +static void default_tex_params(struct GL *gl, GLenum target, GLint filter) +{ + gl->TexParameteri(target, GL_TEXTURE_MIN_FILTER, filter); + gl->TexParameteri(target, GL_TEXTURE_MAG_FILTER, filter); + gl->TexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl->TexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +} + +static void debug_check_gl(struct gl_priv *p, const char *msg) +{ + if (p->use_gl_debug || p->frames_rendered < 5) + glCheckError(p->gl, msg); +} + +static void tex_size(struct gl_priv *p, int w, int h, int *texw, int *texh) +{ + if (p->use_npot) { + *texw = w; + *texh = h; + } else { + *texw = 32; + while (*texw < w) + *texw *= 2; + *texh = 32; + while (*texh < h) + *texh *= 2; + } +} + +static void draw_triangles(struct gl_priv *p, struct vertex *vb, int vert_count) +{ + GL *gl = p->gl; + + assert(vert_count % 3 == 0); + + gl->BindBuffer(GL_ARRAY_BUFFER, p->vertex_buffer); + gl->BufferData(GL_ARRAY_BUFFER, vert_count * sizeof(struct vertex), vb, + GL_DYNAMIC_DRAW); + gl->BindBuffer(GL_ARRAY_BUFFER, 0); + + if (gl->BindVertexArray) + gl->BindVertexArray(p->vao); + + gl->DrawArrays(GL_TRIANGLES, 0, vert_count); + + if (gl->BindVertexArray) + gl->BindVertexArray(0); + + debug_check_gl(p, "after rendering"); +} + +// Write a textured quad to a vertex array. +// va = destination vertex array, VERTICES_PER_QUAD entries will be overwritten +// x0, y0, x1, y1 = destination coordinates of the quad +// tx0, ty0, tx1, ty1 = source texture coordinates (usually in pixels) +// texture_w, texture_h = size of the texture, or an inverse factor +// color = optional color for all vertices, NULL for opaque white +// flip = flip vertically +static void write_quad(struct vertex *va, + float x0, float y0, float x1, float y1, + float tx0, float ty0, float tx1, float ty1, + float texture_w, float texture_h, + const uint8_t color[4], bool flip) +{ + static const uint8_t white[4] = { 255, 255, 255, 255 }; + + if (!color) + color = white; + + tx0 /= texture_w; + ty0 /= texture_h; + tx1 /= texture_w; + ty1 /= texture_h; + + if (flip) { + float tmp = ty0; + ty0 = ty1; + ty1 = tmp; + } + +#define COLOR_INIT {color[0], color[1], color[2], color[3]} + va[0] = (struct vertex) { {x0, y0}, COLOR_INIT, {tx0, ty0} }; + va[1] = (struct vertex) { {x0, y1}, COLOR_INIT, {tx0, ty1} }; + va[2] = (struct vertex) { {x1, y0}, COLOR_INIT, {tx1, ty0} }; + va[3] = (struct vertex) { {x1, y1}, COLOR_INIT, {tx1, ty1} }; + va[4] = va[2]; + va[5] = va[1]; +#undef COLOR_INIT +} + +static bool fbotex_init(struct gl_priv *p, struct fbotex *fbo, int w, int h) +{ + GL *gl = p->gl; + bool res = true; + + assert(gl->mpgl_caps & MPGL_CAP_FB); + assert(!fbo->fbo); + assert(!fbo->texture); + + tex_size(p, w, h, &fbo->tex_w, &fbo->tex_h); + + fbo->vp_w = w; + fbo->vp_h = h; + + mp_msg(MSGT_VO, MSGL_V, "[gl] Create FBO: %dx%d\n", fbo->tex_w, fbo->tex_h); + + gl->GenFramebuffers(1, &fbo->fbo); + gl->GenTextures(1, &fbo->texture); + gl->BindTexture(GL_TEXTURE_2D, fbo->texture); + gl->TexImage2D(GL_TEXTURE_2D, 0, p->fbo_format, fbo->tex_w, fbo->tex_h, 0, + GL_RGB, GL_UNSIGNED_BYTE, NULL); + default_tex_params(gl, GL_TEXTURE_2D, GL_LINEAR); + gl->BindFramebuffer(GL_FRAMEBUFFER, fbo->fbo); + gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, fbo->texture, 0); + + if (gl->CheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + mp_msg(MSGT_VO, MSGL_ERR, "[gl] Error: framebuffer completeness " + "check failed!\n"); + res = false; + } + + gl->BindFramebuffer(GL_FRAMEBUFFER, 0); + + debug_check_gl(p, "after creating framebuffer & associated texture"); + + return res; +} + +static void fbotex_uninit(struct gl_priv *p, struct fbotex *fbo) +{ + GL *gl = p->gl; + + if (gl->mpgl_caps & MPGL_CAP_FB) { + gl->DeleteFramebuffers(1, &fbo->fbo); + gl->DeleteTextures(1, &fbo->texture); + *fbo = (struct fbotex) {0}; + } +} + +static void matrix_ortho2d(float m[3][3], float x0, float x1, + float y0, float y1) +{ + memset(m, 0, 9 * sizeof(float)); + m[0][0] = 2.0f / (x1 - x0); + m[1][1] = 2.0f / (y1 - y0); + m[2][0] = -(x1 + x0) / (x1 - x0); + m[2][1] = -(y1 + y0) / (y1 - y0); + m[2][2] = 1.0f; +} + +static void update_uniforms(struct gl_priv *p, GLuint program) +{ + GL *gl = p->gl; + GLint loc; + + if (program == 0) + return; + + gl->UseProgram(program); + + struct mp_csp_params cparams = { + .colorspace = p->colorspace, + .input_bits = p->plane_bits, + .texture_bits = (p->plane_bits + 7) & ~7, + }; + mp_csp_copy_equalizer_values(&cparams, &p->video_eq); + + loc = gl->GetUniformLocation(program, "transform"); + if (loc >= 0) { + float matrix[3][3]; + matrix_ortho2d(matrix, 0, p->vp_w, p->vp_h, 0); + gl->UniformMatrix3fv(loc, 1, GL_FALSE, &matrix[0][0]); + } + + loc = gl->GetUniformLocation(program, "colormatrix"); + if (loc >= 0) { + float yuv2rgb[3][4] = {{0}}; + if (p->is_yuv) + mp_get_yuv2rgb_coeffs(&cparams, yuv2rgb); + gl->UniformMatrix4x3fv(loc, 1, GL_TRUE, &yuv2rgb[0][0]); + } + + gl->Uniform3f(gl->GetUniformLocation(program, "inv_gamma"), + 1.0 / cparams.rgamma, + 1.0 / cparams.ggamma, + 1.0 / cparams.bgamma); + + for (int n = 0; n < p->plane_count; n++) { + char textures_n[32]; + char textures_size_n[32]; + snprintf(textures_n, sizeof(textures_n), "textures[%d]", n); + snprintf(textures_size_n, sizeof(textures_size_n), "textures_size[%d]", n); + + gl->Uniform1i(gl->GetUniformLocation(program, textures_n), n); + gl->Uniform2f(gl->GetUniformLocation(program, textures_size_n), + p->texture_width >> p->planes[n].shift_x, + p->texture_height >> p->planes[n].shift_y); + } + + gl->Uniform2f(gl->GetUniformLocation(program, "dither_size"), + p->dither_size, p->dither_size); + + gl->Uniform1i(gl->GetUniformLocation(program, "lut_3d"), TEXUNIT_3DLUT); + + for (int n = 0; n < 2; n++) { + const char *lut = p->scalers[n].lut_name; + if (lut) + gl->Uniform1i(gl->GetUniformLocation(program, lut), + TEXUNIT_SCALERS + n); + } + + gl->Uniform1i(gl->GetUniformLocation(program, "dither"), TEXUNIT_DITHER); + gl->Uniform1f(gl->GetUniformLocation(program, "dither_quantization"), + p->dither_quantization); + gl->Uniform1f(gl->GetUniformLocation(program, "dither_multiply"), + p->dither_multiply); + + float sparam1 = p->scaler_params[0]; + gl->Uniform1f(gl->GetUniformLocation(program, "filter_param1"), + isnan(sparam1) ? 0.5f : sparam1); + + loc = gl->GetUniformLocation(program, "osd_color"); + if (loc >= 0) { + int r = (p->osd_color >> 16) & 0xff; + int g = (p->osd_color >> 8) & 0xff; + int b = p->osd_color & 0xff; + int a = 0xff - (p->osd_color >> 24); + gl->Uniform4f(loc, r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f); + } + + gl->UseProgram(0); + + debug_check_gl(p, "update_uniforms()"); +} + +static void update_all_uniforms(struct gl_priv *p) +{ + for (int n = 0; n < SUBBITMAP_COUNT; n++) + update_uniforms(p, p->osd_programs[n]); + update_uniforms(p, p->indirect_program); + update_uniforms(p, p->scale_sep_program); + update_uniforms(p, p->final_program); +} + +#define SECTION_HEADER "#!section " + +static char *get_section(void *talloc_ctx, struct bstr source, + const char *section) +{ + char *res = talloc_strdup(talloc_ctx, ""); + bool copy = false; + while (source.len) { + struct bstr line = bstr_strip_linebreaks(bstr_getline(source, &source)); + if (bstr_eatstart(&line, bstr0(SECTION_HEADER))) { + copy = bstrcmp0(line, section) == 0; + } else if (copy) { + res = talloc_asprintf_append_buffer(res, "%.*s\n", BSTR_P(line)); + } + } + return res; +} + +static char *t_concat(void *talloc_ctx, const char *s1, const char *s2) +{ + return talloc_asprintf(talloc_ctx, "%s%s", s1, s2); +} + +static GLuint create_shader(GL *gl, GLenum type, const char *header, + const char *source) +{ + void *tmp = talloc_new(NULL); + const char *full_source = t_concat(tmp, header, source); + + GLuint shader = gl->CreateShader(type); + gl->ShaderSource(shader, 1, &full_source, NULL); + gl->CompileShader(shader); + GLint status; + gl->GetShaderiv(shader, GL_COMPILE_STATUS, &status); + GLint log_length; + gl->GetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length); + + int pri = status ? (log_length > 1 ? MSGL_V : MSGL_DBG2) : MSGL_ERR; + const char *typestr = type == GL_VERTEX_SHADER ? "vertex" : "fragment"; + if (mp_msg_test(MSGT_VO, pri)) { + mp_msg(MSGT_VO, pri, "[gl] %s shader source:\n", typestr); + mp_log_source(MSGT_VO, pri, full_source); + } + if (log_length > 1) { + GLchar *log = talloc_zero_size(tmp, log_length + 1); + gl->GetShaderInfoLog(shader, log_length, NULL, log); + mp_msg(MSGT_VO, pri, "[gl] %s shader compile log (status=%d):\n%s\n", + typestr, status, log); + } + + talloc_free(tmp); + + return shader; +} + +static void prog_create_shader(GL *gl, GLuint program, GLenum type, + const char *header, const char *source) +{ + GLuint shader = create_shader(gl, type, header, source); + gl->AttachShader(program, shader); + gl->DeleteShader(shader); +} + +static void link_shader(GL *gl, GLuint program) +{ + gl->LinkProgram(program); + GLint status; + gl->GetProgramiv(program, GL_LINK_STATUS, &status); + GLint log_length; + gl->GetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length); + + int pri = status ? (log_length > 1 ? MSGL_V : MSGL_DBG2) : MSGL_ERR; + if (mp_msg_test(MSGT_VO, pri)) { + GLchar *log = talloc_zero_size(NULL, log_length + 1); + gl->GetProgramInfoLog(program, log_length, NULL, log); + mp_msg(MSGT_VO, pri, "[gl] shader link log (status=%d): %s\n", + status, log); + talloc_free(log); + } +} + +static void bind_attrib_locs(GL *gl, GLuint program) +{ + gl->BindAttribLocation(program, VERTEX_ATTRIB_POSITION, "vertex_position"); + gl->BindAttribLocation(program, VERTEX_ATTRIB_COLOR, "vertex_color"); + gl->BindAttribLocation(program, VERTEX_ATTRIB_TEXCOORD, "vertex_texcoord"); +} + +static GLuint create_program(GL *gl, const char *name, const char *header, + const char *vertex, const char *frag) +{ + mp_msg(MSGT_VO, MSGL_V, "[gl] compiling shader program '%s'\n", name); + mp_msg(MSGT_VO, MSGL_V, "[gl] header:\n"); + mp_log_source(MSGT_VO, MSGL_V, header); + GLuint prog = gl->CreateProgram(); + prog_create_shader(gl, prog, GL_VERTEX_SHADER, header, vertex); + prog_create_shader(gl, prog, GL_FRAGMENT_SHADER, header, frag); + bind_attrib_locs(gl, prog); + link_shader(gl, prog); + return prog; +} + +static void shader_def(char **shader, const char *name, + const char *value) +{ + *shader = talloc_asprintf_append(*shader, "#define %s %s\n", name, value); +} + +static void shader_def_opt(char **shader, const char *name, bool b) +{ + if (b) + shader_def(shader, name, "1"); +} + +static void shader_setup_scaler(char **shader, struct scaler *scaler, int pass) +{ + const char *target = scaler->index == 0 ? "SAMPLE_L" : "SAMPLE_C"; + if (!scaler->kernel) { + *shader = talloc_asprintf_append(*shader, "#define %s sample_%s\n", + target, scaler->name); + } else { + int size = scaler->kernel->size; + if (pass != -1) { + // The direction/pass assignment is rather arbitrary, but fixed in + // other parts of the code (like FBO setup). + const char *direction = pass == 0 ? "0, 1" : "1, 0"; + *shader = talloc_asprintf_append(*shader, "#define %s(p0, p1, p2) " + "sample_convolution_sep%d(vec2(%s), %s, p0, p1, p2)\n", + target, size, direction, scaler->lut_name); + } else { + *shader = talloc_asprintf_append(*shader, "#define %s(p0, p1, p2) " + "sample_convolution%d(%s, p0, p1, p2)\n", + target, size, scaler->lut_name); + } + } +} + +// return false if RGB or 4:4:4 YUV +static bool input_is_subsampled(struct gl_priv *p) +{ + for (int i = 0; i < p->plane_count; i++) + if (p->planes[i].shift_x || p->planes[i].shift_y) + return true; + return false; +} + +static void compile_shaders(struct gl_priv *p) +{ + GL *gl = p->gl; + + delete_shaders(p); + + void *tmp = talloc_new(NULL); + + struct bstr src = bstr0(vo_opengl_shaders); + char *vertex_shader = get_section(tmp, src, "vertex_all"); + char *shader_prelude = get_section(tmp, src, "prelude"); + char *s_video = get_section(tmp, src, "frag_video"); + + char *header = talloc_asprintf(tmp, "#version %d\n%s", gl->glsl_version, + shader_prelude); + + char *header_osd = talloc_strdup(tmp, header); + shader_def_opt(&header_osd, "USE_OSD_LINEAR_CONV", p->use_srgb && + !p->use_lut_3d); + shader_def_opt(&header_osd, "USE_OSD_3DLUT", p->use_lut_3d); + shader_def_opt(&header_osd, "USE_OSD_SRGB", p->use_srgb); + + for (int n = 0; n < SUBBITMAP_COUNT; n++) { + const char *name = osd_shaders[n]; + if (name) { + char *s_osd = get_section(tmp, src, name); + p->osd_programs[n] = + create_program(gl, name, header_osd, vertex_shader, s_osd); + } + } + + char *header_conv = talloc_strdup(tmp, ""); + char *header_final = talloc_strdup(tmp, ""); + char *header_sep = NULL; + + bool convert_input_to_linear = !p->is_linear_rgb + && (p->use_srgb || p->use_lut_3d); + + shader_def_opt(&header_conv, "USE_PLANAR", p->plane_count > 1); + shader_def_opt(&header_conv, "USE_GBRP", p->image_format == IMGFMT_GBRP); + shader_def_opt(&header_conv, "USE_YGRAY", p->is_yuv && p->plane_count == 1); + shader_def_opt(&header_conv, "USE_COLORMATRIX", p->is_yuv); + shader_def_opt(&header_conv, "USE_LINEAR_CONV", convert_input_to_linear); + + shader_def_opt(&header_final, "USE_LINEAR_CONV_INV", p->use_lut_3d); + shader_def_opt(&header_final, "USE_GAMMA_POW", p->use_gamma); + shader_def_opt(&header_final, "USE_3DLUT", p->use_lut_3d); + shader_def_opt(&header_final, "USE_SRGB", p->use_srgb); + shader_def_opt(&header_final, "USE_DITHER", p->dither_texture != 0); + + if (p->use_scale_sep && p->scalers[0].kernel) { + header_sep = talloc_strdup(tmp, ""); + shader_def_opt(&header_sep, "FIXED_SCALE", true); + shader_setup_scaler(&header_sep, &p->scalers[0], 0); + shader_setup_scaler(&header_final, &p->scalers[0], 1); + } else { + shader_setup_scaler(&header_final, &p->scalers[0], -1); + } + + // We want to do scaling in linear light. Scaling is closely connected to + // texture sampling due to how the shader is structured (or if GL bilinear + // scaling is used). The purpose of the "indirect" pass is to convert the + // input video to linear RGB. + // Another purpose is reducing input to a single texture for scaling. + bool use_indirect = p->use_indirect; + + // Don't sample from input video textures before converting the input to + // linear light. (Unneeded when sRGB textures are used.) + if (convert_input_to_linear) + use_indirect = true; + + // It doesn't make sense to scale the chroma with cscale in the 1. scale + // step and with lscale in the 2. step. If the chroma is subsampled, a + // convolution filter wouldn't even work entirely correctly, because the + // luma scaler would sample two texels instead of one per tap for chroma. + // Also, even with 4:4:4 YUV or planar RGB, the indirection might be faster, + // because the shader can't use one scaler for sampling from 3 textures. It + // has to fetch the coefficients for each texture separately, even though + // they're the same (this is not an inherent restriction, but would require + // to restructure the shader). + if (header_sep && p->plane_count > 1) + use_indirect = true; + + if (input_is_subsampled(p)) { + shader_setup_scaler(&header_conv, &p->scalers[1], -1); + } else { + // Force using the luma scaler on chroma. If the "indirect" stage is + // used, the actual scaling will happen in the next stage. + shader_def(&header_conv, "SAMPLE_C", + use_indirect ? "sample_bilinear" : "SAMPLE_L"); + } + + if (use_indirect) { + // We don't use filtering for the Y-plane (luma), because it's never + // scaled in this scenario. + shader_def(&header_conv, "SAMPLE_L", "sample_bilinear"); + shader_def_opt(&header_conv, "FIXED_SCALE", true); + header_conv = t_concat(tmp, header, header_conv); + p->indirect_program = + create_program(gl, "indirect", header_conv, vertex_shader, s_video); + } else if (header_sep) { + header_sep = t_concat(tmp, header_sep, header_conv); + } else { + header_final = t_concat(tmp, header_final, header_conv); + } + + if (header_sep) { + header_sep = t_concat(tmp, header, header_sep); + p->scale_sep_program = + create_program(gl, "scale_sep", header_sep, vertex_shader, s_video); + } + + header_final = t_concat(tmp, header, header_final); + p->final_program = + create_program(gl, "final", header_final, vertex_shader, s_video); + + debug_check_gl(p, "shader compilation"); + + talloc_free(tmp); +} + +static void delete_program(GL *gl, GLuint *prog) +{ + gl->DeleteProgram(*prog); + *prog = 0; +} + +static void delete_shaders(struct gl_priv *p) +{ + GL *gl = p->gl; + + for (int n = 0; n < SUBBITMAP_COUNT; n++) + delete_program(gl, &p->osd_programs[n]); + delete_program(gl, &p->indirect_program); + delete_program(gl, &p->scale_sep_program); + delete_program(gl, &p->final_program); +} + +static double get_scale_factor(struct gl_priv *p) +{ + double sx = (p->dst_rect.x1 - p->dst_rect.x0) / + (double)(p->src_rect.x1 - p->src_rect.x0); + double sy = (p->dst_rect.y1 - p->dst_rect.y0) / + (double)(p->src_rect.y1 - p->src_rect.y0); + // xxx: actually we should use different scalers in X/Y directions if the + // scale factors are different due to anamorphic content + return FFMIN(sx, sy); +} + +static bool update_scale_factor(struct gl_priv *p, struct filter_kernel *kernel) +{ + double scale = get_scale_factor(p); + if (!p->use_fancy_downscaling && scale < 1.0) + scale = 1.0; + return mp_init_filter(kernel, filter_sizes, FFMAX(1.0, 1.0 / scale)); +} + +static void init_scaler(struct gl_priv *p, struct scaler *scaler) +{ + GL *gl = p->gl; + + assert(scaler->name); + + scaler->kernel = NULL; + + const struct filter_kernel *t_kernel = mp_find_filter_kernel(scaler->name); + if (!t_kernel) + return; + + scaler->kernel_storage = *t_kernel; + scaler->kernel = &scaler->kernel_storage; + + for (int n = 0; n < 2; n++) { + if (!isnan(p->scaler_params[n])) + scaler->kernel->params[n] = p->scaler_params[n]; + } + + update_scale_factor(p, scaler->kernel); + + int size = scaler->kernel->size; + assert(size < FF_ARRAY_ELEMS(lut_tex_formats)); + struct lut_tex_format *fmt = &lut_tex_formats[size]; + bool use_2d = fmt->pixels > 1; + bool is_luma = scaler->index == 0; + scaler->lut_name = use_2d + ? (is_luma ? "lut_l_2d" : "lut_c_2d") + : (is_luma ? "lut_l_1d" : "lut_c_1d"); + + gl->ActiveTexture(GL_TEXTURE0 + TEXUNIT_SCALERS + scaler->index); + GLenum target = use_2d ? GL_TEXTURE_2D : GL_TEXTURE_1D; + + if (!scaler->gl_lut) + gl->GenTextures(1, &scaler->gl_lut); + + gl->BindTexture(target, scaler->gl_lut); + gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4); + gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + float *weights = talloc_array(NULL, float, LOOKUP_TEXTURE_SIZE * size); + mp_compute_lut(scaler->kernel, LOOKUP_TEXTURE_SIZE, weights); + if (use_2d) { + gl->TexImage2D(GL_TEXTURE_2D, 0, fmt->internal_format, fmt->pixels, + LOOKUP_TEXTURE_SIZE, 0, fmt->format, GL_FLOAT, + weights); + } else { + gl->TexImage1D(GL_TEXTURE_1D, 0, fmt->internal_format, + LOOKUP_TEXTURE_SIZE, 0, fmt->format, GL_FLOAT, + weights); + } + talloc_free(weights); + + gl->TexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl->TexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl->TexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl->TexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + gl->ActiveTexture(GL_TEXTURE0); + + debug_check_gl(p, "after initializing scaler"); +} + +static void make_dither_matrix(unsigned char *m, int size) +{ + m[0] = 0; + for (int sz = 1; sz < size; sz *= 2) { + int offset[] = {sz*size, sz, sz * (size+1), 0}; + for (int i = 0; i < 4; i++) + for (int y = 0; y < sz * size; y += size) + for (int x = 0; x < sz; x++) + m[x+y+offset[i]] = m[x+y] * 4 + (3-i) * 256/size/size; + } +} + +static void init_dither(struct gl_priv *p) +{ + GL *gl = p->gl; + + // Assume 8 bits per component if unknown. + int dst_depth = p->glctx->depth_g ? p->glctx->depth_g : 8; + if (p->dither_depth > 0) + dst_depth = p->dither_depth; + + int src_depth = p->component_bits; + if (p->use_lut_3d) + src_depth = 16; + + if (dst_depth >= src_depth || p->dither_depth < 0 || src_depth < 0) + return; + + mp_msg(MSGT_VO, MSGL_V, "[gl] Dither %d->%d.\n", src_depth, dst_depth); + + // This defines how many bits are considered significant for output on + // screen. The superfluous bits will be used for rounded according to the + // dither matrix. The precision of the source implicitly decides how many + // dither patterns can be visible. + p->dither_quantization = (1 << dst_depth) - 1; + int size = 8; + p->dither_multiply = p->dither_quantization + 1.0 / (size*size); + unsigned char dither[256]; + make_dither_matrix(dither, size); + + p->dither_size = size; + + gl->ActiveTexture(GL_TEXTURE0 + TEXUNIT_DITHER); + gl->GenTextures(1, &p->dither_texture); + gl->BindTexture(GL_TEXTURE_2D, p->dither_texture); + gl->PixelStorei(GL_UNPACK_ALIGNMENT, 1); + gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0); + gl->TexImage2D(GL_TEXTURE_2D, 0, GL_RED, size, size, 0, GL_RED, + GL_UNSIGNED_BYTE, dither); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + gl->ActiveTexture(GL_TEXTURE0); +} + +static void reinit_rendering(struct gl_priv *p) +{ + mp_msg(MSGT_VO, MSGL_V, "[gl] Reinit rendering.\n"); + + if (p->gl->SwapInterval && p->swap_interval >= 0) + p->gl->SwapInterval(p->swap_interval); + + debug_check_gl(p, "before scaler initialization"); + + uninit_rendering(p); + + init_dither(p); + + init_scaler(p, &p->scalers[0]); + init_scaler(p, &p->scalers[1]); + + compile_shaders(p); + + if (p->indirect_program && !p->indirect_fbo.fbo) + fbotex_init(p, &p->indirect_fbo, p->texture_width, p->texture_height); + + if (!p->osd) { + p->osd = mpgl_osd_init(p->gl, false); + p->osd->use_pbo = p->use_pbo; + } +} + +static void uninit_rendering(struct gl_priv *p) +{ + GL *gl = p->gl; + + delete_shaders(p); + + for (int n = 0; n < 2; n++) { + gl->DeleteTextures(1, &p->scalers[n].gl_lut); + p->scalers[n].gl_lut = 0; + p->scalers[n].lut_name = NULL; + p->scalers[n].kernel = NULL; + } + + gl->DeleteTextures(1, &p->dither_texture); + p->dither_texture = 0; + + if (p->osd) + mpgl_osd_destroy(p->osd); + p->osd = NULL; +} + +static void init_lut_3d(struct gl_priv *p) +{ + GL *gl = p->gl; + + gl->GenTextures(1, &p->lut_3d_texture); + gl->ActiveTexture(GL_TEXTURE0 + TEXUNIT_3DLUT); + gl->BindTexture(GL_TEXTURE_3D, p->lut_3d_texture); + gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4); + gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0); + gl->TexImage3D(GL_TEXTURE_3D, 0, GL_RGB16, p->lut_3d_w, p->lut_3d_h, + p->lut_3d_d, 0, GL_RGB, GL_UNSIGNED_SHORT, p->lut_3d_data); + gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + gl->ActiveTexture(GL_TEXTURE0); + + debug_check_gl(p, "after 3d lut creation"); +} + +static void init_video(struct gl_priv *p) +{ + GL *gl = p->gl; + + if (p->use_lut_3d && !p->lut_3d_texture) + init_lut_3d(p); + + if (!p->is_yuv && (p->use_srgb || p->use_lut_3d)) { + p->is_linear_rgb = true; + p->gl_internal_format = GL_SRGB; + } + + int eq_caps = MP_CSP_EQ_CAPS_GAMMA; + if (p->is_yuv) + eq_caps |= MP_CSP_EQ_CAPS_COLORMATRIX; + p->video_eq.capabilities = eq_caps; + + debug_check_gl(p, "before video texture creation"); + + tex_size(p, p->image_width, p->image_height, + &p->texture_width, &p->texture_height); + + for (int n = 0; n < p->plane_count; n++) { + struct texplane *plane = &p->planes[n]; + + int w = p->texture_width >> plane->shift_x; + int h = p->texture_height >> plane->shift_y; + + mp_msg(MSGT_VO, MSGL_V, "[gl] Texture for plane %d: %dx%d\n", n, w, h); + + gl->ActiveTexture(GL_TEXTURE0 + n); + gl->GenTextures(1, &plane->gl_texture); + gl->BindTexture(GL_TEXTURE_2D, plane->gl_texture); + + gl->TexImage2D(GL_TEXTURE_2D, 0, p->gl_internal_format, w, h, 0, + p->gl_format, p->gl_type, NULL); + default_tex_params(gl, GL_TEXTURE_2D, GL_LINEAR); + } + gl->ActiveTexture(GL_TEXTURE0); + + debug_check_gl(p, "after video texture creation"); + + reinit_rendering(p); +} + +static void uninit_video(struct gl_priv *p) +{ + GL *gl = p->gl; + + uninit_rendering(p); + + for (int n = 0; n < 3; n++) { + struct texplane *plane = &p->planes[n]; + + gl->DeleteTextures(1, &plane->gl_texture); + plane->gl_texture = 0; + gl->DeleteBuffers(1, &plane->gl_buffer); + plane->gl_buffer = 0; + plane->buffer_ptr = NULL; + plane->buffer_size = 0; + } + + fbotex_uninit(p, &p->indirect_fbo); + fbotex_uninit(p, &p->scale_sep_fbo); +} + +static void render_to_fbo(struct gl_priv *p, struct fbotex *fbo, int w, int h, + int tex_w, int tex_h) +{ + GL *gl = p->gl; + + gl->Viewport(0, 0, fbo->vp_w, fbo->vp_h); + gl->BindFramebuffer(GL_FRAMEBUFFER, fbo->fbo); + + struct vertex vb[VERTICES_PER_QUAD]; + write_quad(vb, -1, -1, 1, 1, + 0, 0, w, h, + tex_w, tex_h, + NULL, false); + draw_triangles(p, vb, VERTICES_PER_QUAD); + + gl->BindFramebuffer(GL_FRAMEBUFFER, 0); + gl->Viewport(p->vp_x, p->vp_y, p->vp_w, p->vp_h); + +} + +static void handle_pass(struct gl_priv *p, struct fbotex **source, + struct fbotex *fbo, GLuint program) +{ + GL *gl = p->gl; + + if (!program) + return; + + gl->BindTexture(GL_TEXTURE_2D, (*source)->texture); + gl->UseProgram(program); + render_to_fbo(p, fbo, (*source)->vp_w, (*source)->vp_h, + (*source)->tex_w, (*source)->tex_h); + *source = fbo; +} + +static void do_render(struct gl_priv *p) +{ + GL *gl = p->gl; + struct vertex vb[VERTICES_PER_QUAD]; + bool is_flipped = p->mpi_flipped ^ p->vo_flipped; + + // Order of processing: + // [indirect -> [scale_sep ->]] final + + struct fbotex dummy = { + .vp_w = p->image_width, .vp_h = p->image_height, + .tex_w = p->texture_width, .tex_h = p->texture_height, + .texture = p->planes[0].gl_texture, + }; + struct fbotex *source = &dummy; + + handle_pass(p, &source, &p->indirect_fbo, p->indirect_program); + handle_pass(p, &source, &p->scale_sep_fbo, p->scale_sep_program); + + gl->BindTexture(GL_TEXTURE_2D, source->texture); + gl->UseProgram(p->final_program); + + float final_texw = p->image_width * source->tex_w / (float)source->vp_w; + float final_texh = p->image_height * source->tex_h / (float)source->vp_h; + + if (p->stereo_mode) { + int w = p->src_rect.x1 - p->src_rect.x0; + int imgw = p->image_width; + + glEnable3DLeft(gl, p->stereo_mode); + + write_quad(vb, + p->dst_rect.x0, p->dst_rect.y0, + p->dst_rect.x1, p->dst_rect.y1, + p->src_rect.x0 / 2, p->src_rect.y0, + p->src_rect.x0 / 2 + w / 2, p->src_rect.y1, + final_texw, final_texh, + NULL, is_flipped); + draw_triangles(p, vb, VERTICES_PER_QUAD); + + glEnable3DRight(gl, p->stereo_mode); + + write_quad(vb, + p->dst_rect.x0, p->dst_rect.y0, + p->dst_rect.x1, p->dst_rect.y1, + p->src_rect.x0 / 2 + imgw / 2, p->src_rect.y0, + p->src_rect.x0 / 2 + imgw / 2 + w / 2, p->src_rect.y1, + final_texw, final_texh, + NULL, is_flipped); + draw_triangles(p, vb, VERTICES_PER_QUAD); + + glDisable3D(gl, p->stereo_mode); + } else { + write_quad(vb, + p->dst_rect.x0, p->dst_rect.y0, + p->dst_rect.x1, p->dst_rect.y1, + p->src_rect.x0, p->src_rect.y0, + p->src_rect.x1, p->src_rect.y1, + final_texw, final_texh, + NULL, is_flipped); + draw_triangles(p, vb, VERTICES_PER_QUAD); + } + + gl->UseProgram(0); + + debug_check_gl(p, "after video rendering"); +} + +static void update_window_sized_objects(struct gl_priv *p) +{ + if (p->scale_sep_program) { + int h = p->dst_rect.y1 - p->dst_rect.y0; + if (h > p->scale_sep_fbo.tex_h) { + fbotex_uninit(p, &p->scale_sep_fbo); + // Round up to an arbitrary alignment to make window resizing or + // panscan controls smoother (less texture reallocations). + int height = FFALIGN(h, 256); + fbotex_init(p, &p->scale_sep_fbo, p->image_width, height); + } + p->scale_sep_fbo.vp_w = p->image_width; + p->scale_sep_fbo.vp_h = h; + } +} + +static void resize(struct gl_priv *p) +{ + GL *gl = p->gl; + struct vo *vo = p->vo; + + mp_msg(MSGT_VO, MSGL_V, "[gl] Resize: %dx%d\n", vo->dwidth, vo->dheight); + p->vp_x = 0, p->vp_y = 0; + p->vp_w = vo->dwidth, p->vp_h = vo->dheight; + gl->Viewport(p->vp_x, p->vp_y, p->vp_w, p->vp_h); + + vo_get_src_dst_rects(vo, &p->src_rect, &p->dst_rect, &p->osd_rect); + + bool need_scaler_reinit = false; // filter size change needed + bool need_scaler_update = false; // filter LUT change needed + bool too_small = false; + for (int n = 0; n < 2; n++) { + if (p->scalers[n].kernel) { + struct filter_kernel tkernel = *p->scalers[n].kernel; + struct filter_kernel old = tkernel; + bool ok = update_scale_factor(p, &tkernel); + too_small |= !ok; + need_scaler_reinit |= (tkernel.size != old.size); + need_scaler_update |= (tkernel.inv_scale != old.inv_scale); + } + } + if (need_scaler_reinit) { + reinit_rendering(p); + } else if (need_scaler_update) { + init_scaler(p, &p->scalers[0]); + init_scaler(p, &p->scalers[1]); + } + if (too_small) + mp_msg(MSGT_VO, MSGL_WARN, "[gl] Can't downscale that much, window " + "output may look suboptimal.\n"); + + update_window_sized_objects(p); + update_all_uniforms(p); + + gl->Clear(GL_COLOR_BUFFER_BIT); + vo->want_redraw = true; +} + +static void flip_page(struct vo *vo) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + + if (p->use_glFinish) + gl->Finish(); + + p->glctx->swapGlBuffers(p->glctx); + + if (p->dst_rect.x0 > p->vp_x || p->dst_rect.y0 > p->vp_y + || p->dst_rect.x1 < p->vp_x + p->vp_w + || p->dst_rect.y1 < p->vp_y + p->vp_h) + { + gl->Clear(GL_COLOR_BUFFER_BIT); + } + + p->frames_rendered++; +} + +static int draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h, + int x, int y) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + + p->mpi_flipped = stride[0] < 0; + + for (int n = 0; n < p->plane_count; n++) { + gl->ActiveTexture(GL_TEXTURE0 + n); + gl->BindTexture(GL_TEXTURE_2D, p->planes[n].gl_texture); + int xs = p->planes[n].shift_x, ys = p->planes[n].shift_y; + glUploadTex(gl, GL_TEXTURE_2D, p->gl_format, p->gl_type, src[n], + stride[n], x >> xs, y >> ys, w >> xs, h >> ys, 0); + } + gl->ActiveTexture(GL_TEXTURE0); + + return 0; +} + +static uint32_t get_image(struct vo *vo, mp_image_t *mpi) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + + if (!p->use_pbo) + return VO_FALSE; + + // We don't support alpha planes. (Disabling PBOs with normal draw calls is + // an undesired, but harmless side-effect.) + if (mpi->num_planes != p->plane_count) + return VO_FALSE; + + if (mpi->flags & MP_IMGFLAG_READABLE) + return VO_FALSE; + if (mpi->type != MP_IMGTYPE_STATIC && mpi->type != MP_IMGTYPE_TEMP && + (mpi->type != MP_IMGTYPE_NUMBERED || mpi->number)) + return VO_FALSE; + mpi->flags &= ~MP_IMGFLAG_COMMON_PLANE; + for (int n = 0; n < p->plane_count; n++) { + struct texplane *plane = &p->planes[n]; + mpi->stride[n] = (mpi->width >> plane->shift_x) * p->plane_bytes; + int needed_size = (mpi->height >> plane->shift_y) * mpi->stride[n]; + if (!plane->gl_buffer) + gl->GenBuffers(1, &plane->gl_buffer); + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer); + if (needed_size > plane->buffer_size) { + plane->buffer_size = needed_size; + gl->BufferData(GL_PIXEL_UNPACK_BUFFER, plane->buffer_size, + NULL, GL_DYNAMIC_DRAW); + } + if (!plane->buffer_ptr) + plane->buffer_ptr = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER, + GL_WRITE_ONLY); + mpi->planes[n] = plane->buffer_ptr; + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + mpi->flags |= MP_IMGFLAG_DIRECT; + return VO_TRUE; +} + +static uint32_t draw_image(struct gl_priv *p, mp_image_t *mpi) +{ + GL *gl = p->gl; + int n; + + assert(mpi->num_planes >= p->plane_count); + + mp_image_t mpi2 = *mpi; + int w = mpi->w, h = mpi->h; + if (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK) + goto skip_upload; + mpi2.flags = 0; + mpi2.type = MP_IMGTYPE_TEMP; + mpi2.width = mpi2.w; + mpi2.height = mpi2.h; + if (!(mpi->flags & MP_IMGFLAG_DIRECT) + && !p->planes[0].buffer_ptr + && get_image(p->vo, &mpi2) == VO_TRUE) + { + for (n = 0; n < p->plane_count; n++) { + struct texplane *plane = &p->planes[n]; + int xs = plane->shift_x, ys = plane->shift_y; + int line_bytes = (mpi->w >> xs) * p->plane_bytes; + memcpy_pic(mpi2.planes[n], mpi->planes[n], line_bytes, mpi->h >> ys, + mpi2.stride[n], mpi->stride[n]); + } + mpi = &mpi2; + } + p->mpi_flipped = mpi->stride[0] < 0; + for (n = 0; n < p->plane_count; n++) { + struct texplane *plane = &p->planes[n]; + int xs = plane->shift_x, ys = plane->shift_y; + void *plane_ptr = mpi->planes[n]; + if (mpi->flags & MP_IMGFLAG_DIRECT) { + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, plane->gl_buffer); + if (!gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER)) + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Video PBO upload failed. " + "Remove the 'pbo' suboption.\n"); + plane->buffer_ptr = NULL; + plane_ptr = NULL; // PBO offset 0 + } + gl->ActiveTexture(GL_TEXTURE0 + n); + gl->BindTexture(GL_TEXTURE_2D, plane->gl_texture); + glUploadTex(gl, GL_TEXTURE_2D, p->gl_format, p->gl_type, plane_ptr, + mpi->stride[n], 0, 0, w >> xs, h >> ys, 0); + } + gl->ActiveTexture(GL_TEXTURE0); + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +skip_upload: + do_render(p); + return VO_TRUE; +} + +static mp_image_t *get_screenshot(struct gl_priv *p) +{ + GL *gl = p->gl; + + mp_image_t *image = alloc_mpi(p->texture_width, p->texture_height, + p->image_format); + + // NOTE about image formats with alpha plane: we don't even have the alpha + // anymore. We never upload it to any texture, as it would be a waste of + // time. On the other hand, we can't find a "similar", non-alpha image + // format easily. So we just leave the alpha plane of the newly allocated + // image as-is, and hope that the alpha is ignored by the receiver of the + // screenshot. (If not, code should be added to make it fully opaque.) + + for (int n = 0; n < p->plane_count; n++) { + gl->ActiveTexture(GL_TEXTURE0 + n); + gl->BindTexture(GL_TEXTURE_2D, p->planes[n].gl_texture); + glDownloadTex(gl, GL_TEXTURE_2D, p->gl_format, p->gl_type, + image->planes[n], image->stride[n]); + } + gl->ActiveTexture(GL_TEXTURE0); + + image->w = p->image_width; + image->h = p->image_height; + image->display_w = p->vo->aspdat.prew; + image->display_h = p->vo->aspdat.preh; + + mp_image_set_colorspace_details(image, &p->colorspace); + + return image; +} + +static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs) +{ + struct gl_priv *p = ctx; + GL *gl = p->gl; + + struct mpgl_osd_part *osd = mpgl_osd_generate(p->osd, imgs); + if (!osd) + return; + + assert(osd->format != SUBBITMAP_EMPTY); + + if (!osd->num_vertices) { + osd->vertices = talloc_realloc(osd, osd->vertices, struct vertex, + osd->packer->count * VERTICES_PER_QUAD); + + struct vertex *va = osd->vertices; + + for (int n = 0; n < osd->packer->count; n++) { + struct sub_bitmap *b = &imgs->parts[n]; + struct pos p = osd->packer->result[n]; + + // NOTE: the blend color is used with SUBBITMAP_LIBASS only, so it + // doesn't matter that we upload garbage for the other formats + uint32_t c = b->libass.color; + uint8_t color[4] = { c >> 24, (c >> 16) & 0xff, + (c >> 8) & 0xff, 255 - (c & 0xff) }; + + write_quad(&va[osd->num_vertices], + b->x, b->y, b->x + b->dw, b->y + b->dh, + p.x, p.y, p.x + b->w, p.y + b->h, + osd->w, osd->h, color, false); + osd->num_vertices += VERTICES_PER_QUAD; + } + } + + debug_check_gl(p, "before drawing osd"); + + gl->UseProgram(p->osd_programs[osd->format]); + mpgl_osd_set_gl_state(p->osd, osd); + draw_triangles(p, osd->vertices, osd->num_vertices); + mpgl_osd_unset_gl_state(p->osd, osd); + gl->UseProgram(0); + + debug_check_gl(p, "after drawing osd"); +} + +static void draw_osd(struct vo *vo, struct osd_state *osd) +{ + struct gl_priv *p = vo->priv; + assert(p->osd); + + osd_draw(osd, p->osd_rect, osd->vo_pts, 0, p->osd->formats, draw_osd_cb, p); +} + +// Disable features that are not supported with the current OpenGL version. +static void check_gl_features(struct gl_priv *p) +{ + GL *gl = p->gl; + bool have_float_tex = gl->mpgl_caps & MPGL_CAP_FLOAT_TEX; + bool have_fbo = gl->mpgl_caps & MPGL_CAP_FB; + bool have_srgb = gl->mpgl_caps & MPGL_CAP_SRGB_TEX; + + // srgb_compand() not available + if (gl->glsl_version < 130) + have_srgb = false; + + char *disabled[10]; + int n_disabled = 0; + + if (have_fbo) { + struct fbotex fbo = {0}; + have_fbo = fbotex_init(p, &fbo, 16, 16); + fbotex_uninit(p, &fbo); + } + + // Disable these only if the user didn't disable scale-sep on the command + // line, so convolution filter can still be forced to be run. + // Normally, we want to disable them by default if FBOs are unavailable, + // because they will be slow (not critically slow, but still slower). + // Without FP textures, we must always disable them. + if (!have_float_tex || (!have_fbo && p->use_scale_sep)) { + for (int n = 0; n < 2; n++) { + struct scaler *scaler = &p->scalers[n]; + if (mp_find_filter_kernel(scaler->name)) { + scaler->name = "bilinear"; + disabled[n_disabled++] + = have_float_tex ? "scaler (FBO)" : "scaler (float tex.)"; + } + } + } + + if (!have_srgb && p->use_srgb) { + p->use_srgb = false; + disabled[n_disabled++] = "sRGB"; + } + if (!have_fbo && p->use_lut_3d) { + p->use_lut_3d = false; + disabled[n_disabled++] = "color management (FBO)"; + } + if (!have_srgb && p->use_lut_3d) { + p->use_lut_3d = false; + disabled[n_disabled++] = "color management (sRGB)"; + } + + if (!have_fbo) { + p->use_scale_sep = false; + p->use_indirect = false; + } + + if (n_disabled) { + mp_msg(MSGT_VO, MSGL_ERR, "[gl] Some OpenGL extensions not detected, " + "disabling: "); + for (int n = 0; n < n_disabled; n++) { + if (n) + mp_msg(MSGT_VO, MSGL_ERR, ", "); + mp_msg(MSGT_VO, MSGL_ERR, "%s", disabled[n]); + } + mp_msg(MSGT_VO, MSGL_ERR, ".\n"); + } +} + +static void setup_vertex_array(GL *gl) +{ + size_t stride = sizeof(struct vertex); + + gl->EnableVertexAttribArray(VERTEX_ATTRIB_POSITION); + gl->VertexAttribPointer(VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, + stride, (void*)offsetof(struct vertex, position)); + + gl->EnableVertexAttribArray(VERTEX_ATTRIB_COLOR); + gl->VertexAttribPointer(VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, + stride, (void*)offsetof(struct vertex, color)); + + gl->EnableVertexAttribArray(VERTEX_ATTRIB_TEXCOORD); + gl->VertexAttribPointer(VERTEX_ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, + stride, (void*)offsetof(struct vertex, texcoord)); +} + +static int init_gl(struct gl_priv *p) +{ + GL *gl = p->gl; + + debug_check_gl(p, "before init_gl"); + + const char *vendor = gl->GetString(GL_VENDOR); + const char *version = gl->GetString(GL_VERSION); + const char *renderer = gl->GetString(GL_RENDERER); + const char *glsl = gl->GetString(GL_SHADING_LANGUAGE_VERSION); + mp_msg(MSGT_VO, MSGL_V, "[gl] GL_RENDERER='%s', GL_VENDOR='%s', " + "GL_VERSION='%s', GL_SHADING_LANGUAGE_VERSION='%s'" + "\n", renderer, vendor, version, glsl); + mp_msg(MSGT_VO, MSGL_V, "[gl] Display depth: R=%d, G=%d, B=%d\n", + p->glctx->depth_r, p->glctx->depth_g, p->glctx->depth_b); + + check_gl_features(p); + + gl->Disable(GL_DITHER); + gl->Disable(GL_BLEND); + gl->Disable(GL_DEPTH_TEST); + gl->DepthMask(GL_FALSE); + gl->Disable(GL_CULL_FACE); + gl->DrawBuffer(GL_BACK); + + gl->GenBuffers(1, &p->vertex_buffer); + gl->BindBuffer(GL_ARRAY_BUFFER, p->vertex_buffer); + + if (gl->BindVertexArray) { + gl->GenVertexArrays(1, &p->vao); + gl->BindVertexArray(p->vao); + setup_vertex_array(gl); + gl->BindVertexArray(0); + } else { + setup_vertex_array(gl); + } + + gl->BindBuffer(GL_ARRAY_BUFFER, 0); + + gl->ClearColor(0.0f, 0.0f, 0.0f, 0.0f); + gl->Clear(GL_COLOR_BUFFER_BIT); + + debug_check_gl(p, "after init_gl"); + + return 1; +} + +static void uninit_gl(struct gl_priv *p) +{ + GL *gl = p->gl; + + // NOTE: GL functions might not be loaded yet + if (!(p->glctx && p->gl->DeleteTextures)) + return; + + uninit_video(p); + + if (gl->DeleteVertexArrays) + gl->DeleteVertexArrays(1, &p->vao); + p->vao = 0; + gl->DeleteBuffers(1, &p->vertex_buffer); + p->vertex_buffer = 0; + + gl->DeleteTextures(1, &p->lut_3d_texture); + p->lut_3d_texture = 0; +} + +static bool init_format(int fmt, struct gl_priv *init) +{ + bool supported = false; + struct gl_priv dummy; + if (!init) + init = &dummy; + + mp_image_t dummy_img = {0}; + mp_image_setfmt(&dummy_img, fmt); + + init->image_format = fmt; + init->component_bits = -1; + + // RGB/packed formats + for (const struct fmt_entry *e = mp_to_gl_formats; e->mp_format; e++) { + if (e->mp_format == fmt) { + supported = true; + init->plane_bits = dummy_img.bpp; + init->gl_format = e->format; + init->gl_internal_format = e->internal_format; + init->component_bits = e->component_bits; + init->gl_type = e->type; + break; + } + } + + // YUV/planar formats + if (!supported && mp_get_chroma_shift(fmt, NULL, NULL, &init->plane_bits)) { + init->gl_format = GL_RED; + init->component_bits = init->plane_bits; + if (init->plane_bits == 8) { + supported = true; + init->gl_internal_format = GL_RED; + init->gl_type = GL_UNSIGNED_BYTE; + } else if (IMGFMT_IS_YUVP16_NE(fmt)) { + supported = true; + init->gl_internal_format = GL_R16; + init->gl_type = GL_UNSIGNED_SHORT; + } + } + + // RGB/planar + if (!supported && fmt == IMGFMT_GBRP) { + supported = true; + init->plane_bits = init->component_bits = 8; + init->gl_format = GL_RED; + init->gl_internal_format = GL_RED; + init->gl_type = GL_UNSIGNED_BYTE; + } + + if (!supported) + return false; + + init->plane_bytes = (init->plane_bits + 7) / 8; + init->is_yuv = dummy_img.flags & MP_IMGFLAG_YUV; + init->is_linear_rgb = false; + + // NOTE: we throw away the additional alpha plane, if one exists. + init->plane_count = dummy_img.num_planes > 2 ? 3 : 1; + assert(dummy_img.num_planes >= init->plane_count); + assert(dummy_img.num_planes <= init->plane_count + 1); + + for (int n = 0; n < init->plane_count; n++) { + struct texplane *plane = &init->planes[n]; + + plane->shift_x = n > 0 ? dummy_img.chroma_x_shift : 0; + plane->shift_y = n > 0 ? dummy_img.chroma_y_shift : 0; + } + + return true; +} + +static int query_format(uint32_t format) +{ + int caps = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | VFCAP_FLIP | + VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | VFCAP_ACCEPT_STRIDE | + VFCAP_OSD; + if (!init_format(format, NULL)) + return 0; + return caps; +} + +static bool create_window(struct gl_priv *p, uint32_t d_width, + uint32_t d_height, uint32_t flags) +{ + if (p->stereo_mode == GL_3D_QUADBUFFER) + flags |= VOFLAG_STEREO; + + if (p->use_gl_debug) + flags |= VOFLAG_GL_DEBUG; + + int mpgl_caps = MPGL_CAP_GL21 | MPGL_CAP_TEX_RG; + if (!p->allow_sw) + mpgl_caps |= MPGL_CAP_NO_SW; + return mpgl_create_window(p->glctx, mpgl_caps, d_width, d_height, flags); +} + +static int config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t flags, + uint32_t format) +{ + struct gl_priv *p = vo->priv; + + if (!create_window(p, d_width, d_height, flags)) + return -1; + + if (!p->vertex_buffer) + init_gl(p); + + p->vo_flipped = !!(flags & VOFLAG_FLIPPING); + + if (p->image_format != format || p->image_width != width + || p->image_height != height) + { + uninit_video(p); + p->image_height = height; + p->image_width = width; + init_format(format, p); + init_video(p); + } + + resize(p); + + return 0; +} + +static void check_events(struct vo *vo) +{ + struct gl_priv *p = vo->priv; + + int e = p->glctx->check_events(vo); + if (e & VO_EVENT_REINIT) { + uninit_gl(p); + init_gl(p); + init_video(p); + resize(p); + } + if (e & VO_EVENT_RESIZE) + resize(p); + if (e & VO_EVENT_EXPOSE) + vo->want_redraw = true; +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + struct gl_priv *p = vo->priv; + + switch (request) { + case VOCTRL_QUERY_FORMAT: + return query_format(*(uint32_t *)data); + case VOCTRL_DRAW_IMAGE: + return draw_image(p, data); + case VOCTRL_ONTOP: + if (!p->glctx->ontop) + break; + p->glctx->ontop(vo); + return VO_TRUE; + case VOCTRL_PAUSE: + if (!p->glctx->pause) + break; + p->glctx->pause(vo); + return VO_TRUE; + case VOCTRL_RESUME: + if (!p->glctx->resume) + break; + p->glctx->resume(vo); + return VO_TRUE; + case VOCTRL_FULLSCREEN: + p->glctx->fullscreen(vo); + resize(p); + return VO_TRUE; + case VOCTRL_BORDER: + if (!p->glctx->border) + break; + p->glctx->border(vo); + resize(p); + return VO_TRUE; + case VOCTRL_GET_PANSCAN: + return VO_TRUE; + case VOCTRL_SET_PANSCAN: + resize(p); + return VO_TRUE; + case VOCTRL_GET_EQUALIZER: { + struct voctrl_get_equalizer_args *args = data; + return mp_csp_equalizer_get(&p->video_eq, args->name, args->valueptr) + >= 0 ? VO_TRUE : VO_NOTIMPL; + } + case VOCTRL_SET_EQUALIZER: { + struct voctrl_set_equalizer_args *args = data; + if (mp_csp_equalizer_set(&p->video_eq, args->name, args->value) < 0) + return VO_NOTIMPL; + if (!p->use_gamma && p->video_eq.values[MP_CSP_EQ_GAMMA] != 0) { + mp_msg(MSGT_VO, MSGL_V, "[gl] Auto-enabling gamma.\n"); + p->use_gamma = true; + compile_shaders(p); + } + update_all_uniforms(p); + vo->want_redraw = true; + return VO_TRUE; + } + case VOCTRL_SET_YUV_COLORSPACE: { + if (p->is_yuv) { + p->colorspace = *(struct mp_csp_details *)data; + update_all_uniforms(p); + vo->want_redraw = true; + } + return VO_TRUE; + } + case VOCTRL_GET_YUV_COLORSPACE: + *(struct mp_csp_details *)data = p->colorspace; + return VO_TRUE; + case VOCTRL_UPDATE_SCREENINFO: + if (!p->glctx->update_xinerama_info) + break; + p->glctx->update_xinerama_info(vo); + return VO_TRUE; + case VOCTRL_SCREENSHOT: { + struct voctrl_screenshot_args *args = data; + if (args->full_window) + args->out_image = glGetWindowScreenshot(p->gl); + else + args->out_image = get_screenshot(p); + return true; + } + case VOCTRL_REDRAW_FRAME: + do_render(p); + return true; + case VOCTRL_SET_COMMAND_LINE: { + char *arg = data; + if (!reparse_cmdline(p, arg)) + return false; + check_gl_features(p); + reinit_rendering(p); + resize(p); + vo->want_redraw = true; + return true; + } + } + return VO_NOTIMPL; +} + +static void uninit(struct vo *vo) +{ + struct gl_priv *p = vo->priv; + + uninit_gl(p); + mpgl_uninit(p->glctx); + p->glctx = NULL; + p->gl = NULL; +} + +#ifdef CONFIG_LCMS2 + +static void lcms2_error_handler(cmsContext ctx, cmsUInt32Number code, + const char *msg) +{ + mp_msg(MSGT_VO, MSGL_ERR, "[gl] lcms2: %s\n", msg); +} + +static struct bstr load_file(struct gl_priv *p, void *talloc_ctx, + const char *filename) +{ + struct bstr res = {0}; + stream_t *s = open_stream(filename, p->vo->opts, NULL); + if (s) { + res = stream_read_complete(s, talloc_ctx, 1000000000, 0); + free_stream(s); + } + return res; +} + +#define LUT3D_CACHE_HEADER "mpv 3dlut cache 1.0\n" + +static bool load_icc(struct gl_priv *p, const char *icc_file, + const char *icc_cache, int icc_intent, + int s_r, int s_g, int s_b) +{ + void *tmp = talloc_new(p); + uint16_t *output = talloc_array(tmp, uint16_t, s_r * s_g * s_b * 3); + + if (icc_intent == -1) + icc_intent = INTENT_ABSOLUTE_COLORIMETRIC; + + mp_msg(MSGT_VO, MSGL_INFO, "[gl] Opening ICC profile '%s'\n", icc_file); + struct bstr iccdata = load_file(p, tmp, icc_file); + if (!iccdata.len) + goto error_exit; + + char *cache_info = talloc_asprintf(tmp, "intent=%d, size=%dx%dx%d\n", + icc_intent, s_r, s_g, s_b); + + // check cache + if (icc_cache) { + mp_msg(MSGT_VO, MSGL_INFO, "[gl] Opening 3D LUT cache in file '%s'.\n", + icc_cache); + struct bstr cachedata = load_file(p, tmp, icc_cache); + if (bstr_eatstart(&cachedata, bstr0(LUT3D_CACHE_HEADER)) + && bstr_eatstart(&cachedata, bstr0(cache_info)) + && bstr_eatstart(&cachedata, iccdata) + && cachedata.len == talloc_get_size(output)) + { + memcpy(output, cachedata.start, cachedata.len); + goto done; + } else { + mp_msg(MSGT_VO, MSGL_WARN, "[gl] 3D LUT cache invalid!\n"); + } + } + + cmsSetLogErrorHandler(lcms2_error_handler); + + cmsHPROFILE profile = cmsOpenProfileFromMem(iccdata.start, iccdata.len); + if (!profile) + goto error_exit; + + cmsCIExyY d65; + cmsWhitePointFromTemp(&d65, 6504); + static const cmsCIExyYTRIPLE bt709prim = { + .Red = {0.64, 0.33, 1.0}, + .Green = {0.30, 0.60, 1.0}, + .Blue = {0.15, 0.06, 1.0}, + }; + cmsToneCurve *tonecurve = cmsBuildGamma(NULL, 2.2); + cmsHPROFILE vid_profile = cmsCreateRGBProfile(&d65, &bt709prim, + (cmsToneCurve*[3]){tonecurve, tonecurve, tonecurve}); + cmsFreeToneCurve(tonecurve); + cmsHTRANSFORM trafo = cmsCreateTransform(vid_profile, TYPE_RGB_16, + profile, TYPE_RGB_16, + icc_intent, + cmsFLAGS_HIGHRESPRECALC); + cmsCloseProfile(profile); + cmsCloseProfile(vid_profile); + + if (!trafo) + goto error_exit; + + // transform a (s_r)x(s_g)x(s_b) cube, with 3 components per channel + uint16_t *input = talloc_array(tmp, uint16_t, s_r * 3); + for (int b = 0; b < s_b; b++) { + for (int g = 0; g < s_g; g++) { + for (int r = 0; r < s_r; r++) { + input[r * 3 + 0] = r * 65535 / (s_r - 1); + input[r * 3 + 1] = g * 65535 / (s_g - 1); + input[r * 3 + 2] = b * 65535 / (s_b - 1); + } + size_t base = (b * s_r * s_g + g * s_r) * 3; + cmsDoTransform(trafo, input, output + base, s_r); + } + } + + cmsDeleteTransform(trafo); + + if (icc_cache) { + FILE *out = fopen(icc_cache, "wb"); + if (out) { + fprintf(out, "%s%s", LUT3D_CACHE_HEADER, cache_info); + fwrite(iccdata.start, iccdata.len, 1, out); + fwrite(output, talloc_get_size(output), 1, out); + fclose(out); + } + } + +done: + + p->lut_3d_data = talloc_steal(p, output); + p->lut_3d_w = s_r, p->lut_3d_h = s_g, p->lut_3d_d = s_b; + p->use_lut_3d = true; + + talloc_free(tmp); + return true; + +error_exit: + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] Error loading ICC profile.\n"); + talloc_free(tmp); + return false; +} + +#else /* CONFIG_LCMS2 */ + +static bool load_icc(struct gl_priv *p, ...) +{ + mp_msg(MSGT_VO, MSGL_FATAL, "[gl] LCMS2 support not compiled.\n"); + return false; +} + +#endif /* CONFIG_LCMS2 */ + +static bool parse_3dlut_size(const char *s, int *p1, int *p2, int *p3) +{ + if (sscanf(s, "%dx%dx%d", p1, p2, p3) != 3) + return false; + for (int n = 0; n < 3; n++) { + int s = ((int[]) { *p1, *p2, *p3 })[n]; + if (s < 2 || s > 256 || ((s - 1) & s)) + return false; + } + return true; +} + +static int lut3d_size_valid(void *arg) +{ + char *s = *(char **)arg; + int p1, p2, p3; + return parse_3dlut_size(s, &p1, &p2, &p3); +} + +static int backend_valid(void *arg) +{ + return mpgl_find_backend(*(const char **)arg) >= 0; +} + +struct fbo_format { + const char *name; + GLint format; +}; + +const struct fbo_format fbo_formats[] = { + {"rgb", GL_RGB}, + {"rgba", GL_RGBA}, + {"rgb8", GL_RGB8}, + {"rgb10", GL_RGB10}, + {"rgb16", GL_RGB16}, + {"rgb16f", GL_RGB16F}, + {"rgb32f", GL_RGB32F}, + {0} +}; + +static GLint find_fbo_format(const char *name) +{ + for (const struct fbo_format *fmt = fbo_formats; fmt->name; fmt++) { + if (strcmp(fmt->name, name) == 0) + return fmt->format; + } + return -1; +} + +static int fbo_format_valid(void *arg) +{ + return find_fbo_format(*(const char **)arg) >= 0; +} + +static bool can_use_filter_kernel(const struct filter_kernel *kernel) +{ + if (!kernel) + return false; + struct filter_kernel k = *kernel; + return mp_init_filter(&k, filter_sizes, 1); +} + +static const char* handle_scaler_opt(const char *name) +{ + const struct filter_kernel *kernel = mp_find_filter_kernel(name); + if (can_use_filter_kernel(kernel)) + return kernel->name; + + for (const char **filter = fixed_scale_filters; *filter; filter++) { + if (strcmp(*filter, name) == 0) + return *filter; + } + + return NULL; +} + +static int scaler_valid(void *arg) +{ + return handle_scaler_opt(*(const char **)arg) != NULL; +} + +#if 0 +static void print_scalers(void) +{ + mp_msg(MSGT_VO, MSGL_INFO, "Available scalers:\n"); + for (const char **e = fixed_scale_filters; *e; e++) { + mp_msg(MSGT_VO, MSGL_INFO, " %s\n", *e); + } + for (const struct filter_kernel *e = mp_filter_kernels; e->name; e++) { + if (can_use_filter_kernel(e)) + mp_msg(MSGT_VO, MSGL_INFO, " %s\n", e->name); + } +} +#endif + +static bool reparse_cmdline(struct gl_priv *p, char *arg) +{ + struct gl_priv tmp = *p->defaults; + struct gl_priv *opt = &tmp; + + if (strcmp(arg, "-") == 0) { + tmp = *p->orig_cmdline; + arg = ""; + } + + char *scalers[2] = {0}; + char *fbo_format = NULL; + + const opt_t subopts[] = { + {"srgb", OPT_ARG_BOOL, &opt->use_srgb}, + {"pbo", OPT_ARG_BOOL, &opt->use_pbo}, + {"glfinish", OPT_ARG_BOOL, &opt->use_glFinish}, + {"swapinterval", OPT_ARG_INT, &opt->swap_interval}, + {"osdcolor", OPT_ARG_INT, &opt->osd_color}, + {"lscale", OPT_ARG_MSTRZ, &scalers[0], scaler_valid}, + {"cscale", OPT_ARG_MSTRZ, &scalers[1], scaler_valid}, + {"lparam1", OPT_ARG_FLOAT, &opt->scaler_params[0]}, + {"lparam2", OPT_ARG_FLOAT, &opt->scaler_params[1]}, + {"fancy-downscaling", OPT_ARG_BOOL, &opt->use_fancy_downscaling}, + {"indirect", OPT_ARG_BOOL, &opt->use_indirect}, + {"scale-sep", OPT_ARG_BOOL, &opt->use_scale_sep}, + {"fbo-format", OPT_ARG_MSTRZ, &fbo_format, fbo_format_valid}, + {"dither-depth", OPT_ARG_INT, &opt->dither_depth}, + {NULL} + }; + + if (subopt_parse(arg, subopts) != 0) + return false; + + p->fbo_format = opt->fbo_format; + if (fbo_format) + p->fbo_format = find_fbo_format(fbo_format); + free(fbo_format); + + for (int n = 0; n < 2; n++) { + p->scalers[n].name = opt->scalers[n].name; + if (scalers[n]) + p->scalers[n].name = handle_scaler_opt(scalers[n]); + free(scalers[n]); + } + + // xxx ideally we'd put all options into an option struct, and just copy + p->use_srgb = opt->use_srgb; //xxx changing srgb will be wrong on RGB input! + p->use_pbo = opt->use_pbo; + p->use_glFinish = opt->use_glFinish; + p->swap_interval = opt->swap_interval; + p->osd_color = opt->osd_color; + memcpy(p->scaler_params, opt->scaler_params, sizeof(p->scaler_params)); + p->use_fancy_downscaling = opt->use_fancy_downscaling; + p->use_indirect = opt->use_indirect; + p->use_scale_sep = opt->use_scale_sep; + p->dither_depth = opt->dither_depth; + + check_gl_features(p); + + return true; +} + +static int preinit(struct vo *vo, const char *arg) +{ + struct gl_priv *p = talloc_zero(vo, struct gl_priv); + vo->priv = p; + + bool hq = strcmp(vo->driver->info->short_name, "opengl-hq") == 0; + + *p = (struct gl_priv) { + .vo = vo, + .colorspace = MP_CSP_DETAILS_DEFAULTS, + .use_npot = 1, + .use_pbo = hq, + .swap_interval = vo_vsync, + .osd_color = 0xffffff, + .dither_depth = hq ? 0 : -1, + .fbo_format = hq ? GL_RGB16 : GL_RGB, + .use_scale_sep = 1, + .scalers = { + { .index = 0, .name = hq ? "lanczos2" : "bilinear" }, + { .index = 1, .name = "bilinear" }, + }, + .scaler_params = {NAN, NAN}, + .scratch = talloc_zero_array(p, char *, 1), + }; + + p->defaults = talloc(p, struct gl_priv); + *p->defaults = *p; + + char *scalers[2] = {0}; + char *backend_arg = NULL; + char *fbo_format = NULL; + char *icc_profile = NULL; + char *icc_cache = NULL; + int icc_intent = -1; + char *icc_size_str = NULL; + + const opt_t subopts[] = { + {"gamma", OPT_ARG_BOOL, &p->use_gamma}, + {"srgb", OPT_ARG_BOOL, &p->use_srgb}, + {"npot", OPT_ARG_BOOL, &p->use_npot}, + {"pbo", OPT_ARG_BOOL, &p->use_pbo}, + {"glfinish", OPT_ARG_BOOL, &p->use_glFinish}, + {"swapinterval", OPT_ARG_INT, &p->swap_interval}, + {"osdcolor", OPT_ARG_INT, &p->osd_color}, + {"stereo", OPT_ARG_INT, &p->stereo_mode}, + {"lscale", OPT_ARG_MSTRZ, &scalers[0], scaler_valid}, + {"cscale", OPT_ARG_MSTRZ, &scalers[1], scaler_valid}, + {"lparam1", OPT_ARG_FLOAT, &p->scaler_params[0]}, + {"lparam2", OPT_ARG_FLOAT, &p->scaler_params[1]}, + {"fancy-downscaling", OPT_ARG_BOOL, &p->use_fancy_downscaling}, + {"debug", OPT_ARG_BOOL, &p->use_gl_debug}, + {"indirect", OPT_ARG_BOOL, &p->use_indirect}, + {"scale-sep", OPT_ARG_BOOL, &p->use_scale_sep}, + {"fbo-format", OPT_ARG_MSTRZ, &fbo_format, fbo_format_valid}, + {"backend", OPT_ARG_MSTRZ, &backend_arg, backend_valid}, + {"sw", OPT_ARG_BOOL, &p->allow_sw}, + {"icc-profile", OPT_ARG_MSTRZ, &icc_profile}, + {"icc-cache", OPT_ARG_MSTRZ, &icc_cache}, + {"icc-intent", OPT_ARG_INT, &icc_intent}, + {"3dlut-size", OPT_ARG_MSTRZ, &icc_size_str, + lut3d_size_valid}, + {"dither-depth", OPT_ARG_INT, &p->dither_depth}, + {NULL} + }; + + if (subopt_parse(arg, subopts) != 0) { + mp_msg(MSGT_VO, MSGL_FATAL, help_text); + goto err_out; + } + + int backend = backend_arg ? mpgl_find_backend(backend_arg) : GLTYPE_AUTO; + free(backend_arg); + + if (fbo_format) + p->fbo_format = find_fbo_format(fbo_format); + free(fbo_format); + + for (int n = 0; n < 2; n++) { + if (scalers[n]) + p->scalers[n].name = handle_scaler_opt(scalers[n]); + free(scalers[n]); + } + + int s_r = 128, s_g = 256, s_b = 64; + if (icc_size_str) + parse_3dlut_size(icc_size_str, &s_r, &s_g, &s_b); + free(icc_size_str); + + bool success = true; + if (icc_profile) { + success = load_icc(p, icc_profile, icc_cache, icc_intent, + s_r, s_g, s_b); + } + free(icc_profile); + free(icc_cache); + + if (!success) + goto err_out; + + p->orig_cmdline = talloc(p, struct gl_priv); + *p->orig_cmdline = *p; + + p->glctx = mpgl_init(backend, vo); + if (!p->glctx) + goto err_out; + p->gl = p->glctx->gl; + + if (!create_window(p, 320, 200, VOFLAG_HIDDEN)) + goto err_out; + check_gl_features(p); + // We created a window to test whether the GL context could be + // created and so on. Destroy that window to make sure all state + // associated with it is lost. + uninit_gl(p); + if (!mpgl_destroy_window(p->glctx)) + goto err_out; + + return 0; + +err_out: + uninit(vo); + return -1; +} + +const struct vo_driver video_out_opengl = { + .is_new = true, + .info = &(const vo_info_t) { + "Extended OpenGL Renderer", + "opengl", + "Based on vo_gl.c by Reimar Doeffinger", + "" + }, + .preinit = preinit, + .config = config, + .control = control, + .draw_slice = draw_slice, + .draw_osd = draw_osd, + .flip_page = flip_page, + .check_events = check_events, + .uninit = uninit, +}; + +const struct vo_driver video_out_opengl_hq = { + .is_new = true, + .info = &(const vo_info_t) { + "Extended OpenGL Renderer (high quality rendering preset)", + "opengl-hq", + "Based on vo_gl.c by Reimar Doeffinger", + "" + }, + .preinit = preinit, + .config = config, + .control = control, + .draw_slice = draw_slice, + .draw_osd = draw_osd, + .flip_page = flip_page, + .check_events = check_events, + .uninit = uninit, +}; + +static const char help_text[] = +"\n--vo=opengl command line help:\n" +"Example: mpv --vo=opengl:scale-sep:lscale=lanczos2\n" +"\nOptions:\n" +" lscale=<filter>\n" +" Set the scaling filter. Possible choices:\n" +" bilinear: bilinear texture filtering (fastest).\n" +" bicubic_fast: bicubic filter (without lookup texture).\n" +" sharpen3: unsharp masking (sharpening) with radius=3.\n" +" sharpen5: unsharp masking (sharpening) with radius=5.\n" +" lanczos2: Lanczos with radius=2 (recommended).\n" +" lanczos3: Lanczos with radius=3 (not recommended).\n" +" mitchell: Mitchell-Netravali.\n" +" Default: bilinear\n" +" lparam1=<value> / lparam2=<value>\n" +" Set parameters for configurable filters. Affects chroma scaler\n" +" as well.\n" +" Filters which use this:\n" +" mitchell: b and c params (defaults: b=1/3 c=1/3)\n" +" kaiser: (defaults: 6.33 6.33)\n" +" sharpen3: lparam1 sets sharpening strength (default: 0.5)\n" +" sharpen5: as with sharpen3\n" +" osdcolor=<0xAARRGGBB>\n" +" Use the given color for the OSD.\n" +" stereo=<n>\n" +" 0: normal display\n" +" 1: side-by-side to red-cyan stereo\n" +" 2: side-by-side to green-magenta stereo\n" +" 3: side-by-side to quadbuffer stereo\n" +" srgb\n" +" Enable gamma-correct scaling by working in linear light. This\n" +" makes use of sRGB textures and framebuffers.\n" +" This option forces the options 'indirect' and 'gamma'.\n" +" NOTE: For YUV colorspaces, gamma 2.2 is assumed. RGB input is always\n" +" assumed to be in sRGB.\n" +" pbo\n" +" Enable use of PBOs. This is faster, but can sometimes lead to\n" +" sporadic and temporary image corruption.\n" +" dither-depth=<n>\n" +" Positive non-zero values select the target bit depth.\n" +" -1: Disable any dithering done by mpv.\n" +" 0: Automatic selection. If output bit depth can't be detected,\n" +" 8 bits per component are assumed.\n" +" 8: Dither to 8 bit output.\n" +" Default: -1.\n" +" Note that dithering will always be disabled if the bit depth\n" +" of the video is lower or qual to the detected dither-depth.\n" +" If color management is enabled, input depth is assumed to be\n" +" 16 bits, because the 3D LUT output is 16 bit wide.\n" +" debug\n" +" Check for OpenGL errors, i.e. call glGetError(). Also request a\n" +" debug OpenGL context.\n" +"Less useful options:\n" +" swapinterval=<n>\n" +" Interval in displayed frames between to buffer swaps.\n" +" 1 is equivalent to enable VSYNC, 0 to disable VSYNC.\n" +" no-scale-sep\n" +" When using a separable scale filter for luma, usually two filter\n" +" passes are done. This is often faster. However, it forces\n" +" conversion to RGB in an extra pass, so it can actually be slower\n" +" if used with fast filters on small screen resolutions. Using\n" +" this options will make rendering a single operation.\n" +" Note that chroma scalers are always done as 1-pass filters.\n" +" cscale=<n>\n" +" As lscale but for chroma (2x slower with little visible effect).\n" +" Note that with some scaling filters, upscaling is always done in\n" +" RGB. If chroma is not subsampled, this option is ignored, and the\n" +" luma scaler is used instead. Setting this option is often useless.\n" +" fancy-downscaling\n" +" When using convolution based filters, extend the filter size\n" +" when downscaling. Trades quality for reduced downscaling performance.\n" +" no-npot\n" +" Force use of power-of-2 texture sizes. For debugging only.\n" +" Borders will look discolored due to filtering.\n" +" glfinish\n" +" Call glFinish() before swapping buffers\n" +" backend=<sys>\n" +" auto: auto-select (default)\n" +" cocoa: Cocoa/OSX\n" +" win: Win32/WGL\n" +" x11: X11/GLX\n" +" indirect\n" +" Do YUV conversion and scaling as separate passes. This will\n" +" first render the video into a video-sized RGB texture, and\n" +" draw the result on screen. The luma scaler is used to scale\n" +" the RGB image when rendering to screen. The chroma scaler\n" +" is used only on YUV conversion, and only if the video uses\n" +" chroma-subsampling.\n" +" This mechanism is disabled on RGB input.\n" +" fbo-format=<fmt>\n" +" Selects the internal format of any FBO textures used.\n" +" fmt can be one of: rgb, rgba, rgb8, rgb10, rgb16, rgb16f, rgb32f\n" +" Default: rgb.\n" +" gamma\n" +" Always enable gamma control. (Disables delayed enabling.)\n" +"Color management:\n" +" icc-profile=<file>\n" +" Load an ICC profile and use it to transform linear RGB to\n" +" screen output. Needs LittleCMS2 support compiled in.\n" +" icc-cache=<file>\n" +" Store and load the 3D LUT created from the ICC profile in\n" +" this file. This can be used to speed up loading, since\n" +" LittleCMS2 can take a while to create the 3D LUT.\n" +" Note that this file will be up to ~100 MB big.\n" +" icc-intent=<value>\n" +" 0: perceptual\n" +" 1: relative colorimetric\n" +" 2: saturation\n" +" 3: absolute colorimetric (default)\n" +" 3dlut-size=<r>x<g>x<b>\n" +" Size of the 3D LUT generated from the ICC profile in each\n" +" dimension. Default is 128x256x64.\n" +" Sizes must be a power of two, and 256 at most.\n" +"Note: all defaults mentioned are for 'opengl', not 'opengl-hq'.\n" +"\n"; diff --git a/video/out/vo_opengl_old.c b/video/out/vo_opengl_old.c new file mode 100644 index 0000000000..b8b1fd4813 --- /dev/null +++ b/video/out/vo_opengl_old.c @@ -0,0 +1,1166 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * You can alternatively redistribute this file 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <stdbool.h> +#include <assert.h> + +#include "config.h" +#include "talloc.h" +#include "mp_msg.h" +#include "subopt-helper.h" +#include "video_out.h" +#include "libmpcodecs/vfcap.h" +#include "libmpcodecs/mp_image.h" +#include "geometry.h" +#include "sub/sub.h" + +#include "gl_common.h" +#include "gl_osd.h" +#include "aspect.h" +#include "fastmemcpy.h" + +//for gl_priv.use_yuv +#define MASK_ALL_YUV (~(1 << YUV_CONVERSION_NONE)) +#define MASK_NOT_COMBINERS (~((1 << YUV_CONVERSION_NONE) | (1 << YUV_CONVERSION_COMBINERS))) +#define MASK_GAMMA_SUPPORT (MASK_NOT_COMBINERS & ~(1 << YUV_CONVERSION_FRAGMENT)) + +struct gl_priv { + MPGLContext *glctx; + GL *gl; + + int allow_sw; + + int use_osd; + int scaled_osd; + struct mpgl_osd *osd; + int osd_color; + + int use_ycbcr; + int use_yuv; + struct mp_csp_details colorspace; + int is_yuv; + int lscale; + int cscale; + float filter_strength; + float noise_strength; + int yuvconvtype; + int use_rectangle; + int err_shown; + uint32_t image_width; + uint32_t image_height; + uint32_t image_format; + int many_fmts; + int have_texture_rg; + int ati_hack; + int force_pbo; + int use_glFinish; + int swap_interval; + GLenum target; + GLint texfmt; + GLenum gl_format; + GLenum gl_type; + GLuint buffer; + GLuint buffer_uv[2]; + int buffersize; + int buffersize_uv; + void *bufferptr; + void *bufferptr_uv[2]; + GLuint fragprog; + GLuint default_texs[22]; + char *custom_prog; + char *custom_tex; + int custom_tlin; + int custom_trect; + int mipmap_gen; + int stereo_mode; + + struct mp_csp_equalizer video_eq; + + int texture_width; + int texture_height; + int mpi_flipped; + int vo_flipped; + int ass_border_x, ass_border_y; + + unsigned int slice_height; +}; + +static void resize(struct vo *vo, int x, int y) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + + mp_msg(MSGT_VO, MSGL_V, "[gl] Resize: %dx%d\n", x, y); + gl->Viewport(0, 0, x, y); + + gl->MatrixMode(GL_PROJECTION); + gl->LoadIdentity(); + p->ass_border_x = p->ass_border_y = 0; + if (aspect_scaling()) { + int new_w, new_h; + GLdouble scale_x, scale_y; + aspect(vo, &new_w, &new_h, A_WINZOOM); + panscan_calc_windowed(vo); + new_w += vo->panscan_x; + new_h += vo->panscan_y; + scale_x = (GLdouble)new_w / (GLdouble)x; + scale_y = (GLdouble)new_h / (GLdouble)y; + gl->Scaled(scale_x, scale_y, 1); + p->ass_border_x = (vo->dwidth - new_w) / 2; + p->ass_border_y = (vo->dheight - new_h) / 2; + } + gl->Ortho(0, p->image_width, p->image_height, 0, -1, 1); + + gl->MatrixMode(GL_MODELVIEW); + gl->LoadIdentity(); + + gl->Clear(GL_COLOR_BUFFER_BIT); + vo->want_redraw = true; +} + +static void texSize(struct vo *vo, int w, int h, int *texw, int *texh) +{ + struct gl_priv *p = vo->priv; + + if (p->use_rectangle) { + *texw = w; + *texh = h; + } else { + *texw = 32; + while (*texw < w) + *texw *= 2; + *texh = 32; + while (*texh < h) + *texh *= 2; + } + if (p->ati_hack) + *texw = (*texw + 511) & ~511; +} + +//! maximum size of custom fragment program +#define MAX_CUSTOM_PROG_SIZE (1024 * 1024) +static void update_yuvconv(struct vo *vo) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + + int xs, ys, depth; + struct mp_csp_params cparams = { .colorspace = p->colorspace }; + mp_csp_copy_equalizer_values(&cparams, &p->video_eq); + gl_conversion_params_t params = { + p->target, p->yuvconvtype, cparams, + p->texture_width, p->texture_height, 0, 0, p->filter_strength, + p->noise_strength + }; + mp_get_chroma_shift(p->image_format, &xs, &ys, &depth); + params.chrom_texw = params.texw >> xs; + params.chrom_texh = params.texh >> ys; + params.csp_params.input_bits = depth; + params.csp_params.texture_bits = depth+7 & ~7; + glSetupYUVConversion(gl, ¶ms); + if (p->custom_prog) { + FILE *f = fopen(p->custom_prog, "rb"); + if (!f) { + mp_msg(MSGT_VO, MSGL_WARN, + "[gl] Could not read customprog %s\n", p->custom_prog); + } else { + char *prog = calloc(1, MAX_CUSTOM_PROG_SIZE + 1); + fread(prog, 1, MAX_CUSTOM_PROG_SIZE, f); + fclose(f); + loadGPUProgram(gl, GL_FRAGMENT_PROGRAM, prog); + free(prog); + } + gl->ProgramEnvParameter4f(GL_FRAGMENT_PROGRAM, 0, + 1.0 / p->texture_width, + 1.0 / p->texture_height, + p->texture_width, p->texture_height); + } + if (p->custom_tex) { + FILE *f = fopen(p->custom_tex, "rb"); + if (!f) { + mp_msg(MSGT_VO, MSGL_WARN, + "[gl] Could not read customtex %s\n", p->custom_tex); + } else { + int width, height, maxval; + gl->ActiveTexture(GL_TEXTURE3); + if (glCreatePPMTex(gl, p->custom_trect ? GL_TEXTURE_RECTANGLE : GL_TEXTURE_2D, + 0, p->custom_tlin ? GL_LINEAR : GL_NEAREST, + f, &width, &height, &maxval)) { + gl->ProgramEnvParameter4f(GL_FRAGMENT_PROGRAM, 1, + 1.0 / width, 1.0 / height, + width, height); + } else + mp_msg(MSGT_VO, MSGL_WARN, + "[gl] Error parsing customtex %s\n", p->custom_tex); + fclose(f); + gl->ActiveTexture(GL_TEXTURE0); + } + } +} + +static void draw_osd(struct vo *vo, struct osd_state *osd) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + assert(p->osd); + + if (!p->use_osd) + return; + + if (!p->scaled_osd) { + gl->MatrixMode(GL_PROJECTION); + gl->PushMatrix(); + gl->LoadIdentity(); + gl->Ortho(0, vo->dwidth, vo->dheight, 0, -1, 1); + } + + gl->Color4ub((p->osd_color >> 16) & 0xff, (p->osd_color >> 8) & 0xff, + p->osd_color & 0xff, 0xff - (p->osd_color >> 24)); + + struct mp_osd_res res = { + .w = vo->dwidth, + .h = vo->dheight, + .display_par = vo->monitor_par, + .video_par = vo->aspdat.par, + }; + if (p->scaled_osd) { + res.w = p->image_width; + res.h = p->image_height; + } else if (aspect_scaling()) { + res.ml = res.mr = p->ass_border_x; + res.mt = res.mb = p->ass_border_y; + } + + mpgl_osd_draw_legacy(p->osd, osd, res); + + if (!p->scaled_osd) + gl->PopMatrix(); +} + +/** + * \brief uninitialize OpenGL context, freeing textures, buffers etc. + */ +static void uninitGl(struct vo *vo) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + + if (!gl) + return; + + int i = 0; + if (gl->DeletePrograms && p->fragprog) + gl->DeletePrograms(1, &p->fragprog); + p->fragprog = 0; + while (p->default_texs[i] != 0) + i++; + if (i) + gl->DeleteTextures(i, p->default_texs); + p->default_texs[0] = 0; + if (p->osd) + mpgl_osd_destroy(p->osd); + p->osd = NULL; + p->buffer = 0; + p->buffersize = 0; + p->bufferptr = NULL; + if (gl->DeleteBuffers && p->buffer_uv[0]) + gl->DeleteBuffers(2, p->buffer_uv); + p->buffer_uv[0] = p->buffer_uv[1] = 0; + p->buffersize_uv = 0; + p->bufferptr_uv[0] = p->bufferptr_uv[1] = 0; + p->err_shown = 0; +} + +static void autodetectGlExtensions(struct vo *vo) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + + const char *extensions = gl->GetString(GL_EXTENSIONS); + const char *vendor = gl->GetString(GL_VENDOR); + const char *version = gl->GetString(GL_VERSION); + const char *renderer = gl->GetString(GL_RENDERER); + int is_ati = vendor && strstr(vendor, "ATI") != NULL; + int ati_broken_pbo = 0; + mp_msg(MSGT_VO, MSGL_V, "[gl] Running on OpenGL '%s' by '%s', version '%s'\n", + renderer, vendor, version); + if (is_ati && strncmp(version, "2.1.", 4) == 0) { + int ver = atoi(version + 4); + mp_msg(MSGT_VO, MSGL_V, "[gl] Detected ATI driver version: %i\n", ver); + ati_broken_pbo = ver && ver < 8395; + } + if (p->ati_hack == -1) + p->ati_hack = ati_broken_pbo; + if (p->force_pbo == -1) { + p->force_pbo = 0; + if (extensions && strstr(extensions, "_pixel_buffer_object")) + p->force_pbo = is_ati; + } + p->have_texture_rg = extensions && strstr(extensions, "GL_ARB_texture_rg"); + if (p->use_rectangle == -1) { + p->use_rectangle = 0; + if (extensions) { +// if (strstr(extensions, "_texture_non_power_of_two")) + if (strstr(extensions, "_texture_rectangle")) + p->use_rectangle = renderer + && strstr(renderer, "Mesa DRI R200") ? 1 : 0; + } + } + if (p->use_osd == -1) + p->use_osd = gl->BindTexture != NULL; + if (p->use_yuv == -1) + p->use_yuv = glAutodetectYUVConversion(gl); + + int eq_caps = 0; + int yuv_mask = (1 << p->use_yuv); + if (!(yuv_mask & MASK_NOT_COMBINERS)) { + // combiners + eq_caps = (1 << MP_CSP_EQ_HUE) | (1 << MP_CSP_EQ_SATURATION); + } else if (yuv_mask & MASK_ALL_YUV) { + eq_caps = MP_CSP_EQ_CAPS_COLORMATRIX; + if (yuv_mask & MASK_GAMMA_SUPPORT) + eq_caps |= MP_CSP_EQ_CAPS_GAMMA; + } + p->video_eq.capabilities = eq_caps; + + if (is_ati && (p->lscale == 1 || p->lscale == 2 || p->cscale == 1 || p->cscale == 2)) + mp_msg(MSGT_VO, MSGL_WARN, "[gl] Selected scaling mode may be broken on" + " ATI cards.\n" + "Tell _them_ to fix GL_REPEAT if you have issues.\n"); + mp_msg(MSGT_VO, MSGL_V, "[gl] Settings after autodetection: ati-hack = %i, " + "force-pbo = %i, rectangle = %i, yuv = %i\n", + p->ati_hack, p->force_pbo, p->use_rectangle, p->use_yuv); +} + +static GLint get_scale_type(struct vo *vo, int chroma) +{ + struct gl_priv *p = vo->priv; + + int nearest = (chroma ? p->cscale : p->lscale) & 64; + if (nearest) + return p->mipmap_gen ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST; + return p->mipmap_gen ? GL_LINEAR_MIPMAP_NEAREST : GL_LINEAR; +} + +// Return the high byte of the value that represents white in chroma (U/V) +static int get_chroma_clear_val(int bit_depth) +{ + return 1 << (bit_depth - 1 & 7); +} + +/** + * \brief Initialize a (new or reused) OpenGL context. + * set global gl-related variables to their default values + */ +static int initGl(struct vo *vo, uint32_t d_width, uint32_t d_height) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + + GLint scale_type = get_scale_type(vo, 0); + autodetectGlExtensions(vo); + p->target = p->use_rectangle == 1 ? GL_TEXTURE_RECTANGLE : GL_TEXTURE_2D; + p->yuvconvtype = SET_YUV_CONVERSION(p->use_yuv) | + SET_YUV_LUM_SCALER(p->lscale) | + SET_YUV_CHROM_SCALER(p->cscale); + + texSize(vo, p->image_width, p->image_height, + &p->texture_width, &p->texture_height); + + gl->Disable(GL_BLEND); + gl->Disable(GL_DEPTH_TEST); + gl->DepthMask(GL_FALSE); + gl->Disable(GL_CULL_FACE); + gl->Enable(p->target); + gl->DrawBuffer(GL_BACK); + gl->TexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + + mp_msg(MSGT_VO, MSGL_V, "[gl] Creating %dx%d texture...\n", + p->texture_width, p->texture_height); + + glCreateClearTex(gl, p->target, p->texfmt, p->gl_format, + p->gl_type, scale_type, + p->texture_width, p->texture_height, 0); + + if (p->mipmap_gen) + gl->TexParameteri(p->target, GL_GENERATE_MIPMAP, GL_TRUE); + + if (p->is_yuv) { + int i; + int xs, ys, depth; + scale_type = get_scale_type(vo, 1); + mp_get_chroma_shift(p->image_format, &xs, &ys, &depth); + int clear = get_chroma_clear_val(depth); + gl->GenTextures(21, p->default_texs); + p->default_texs[21] = 0; + for (i = 0; i < 7; i++) { + gl->ActiveTexture(GL_TEXTURE1 + i); + gl->BindTexture(GL_TEXTURE_2D, p->default_texs[i]); + gl->BindTexture(GL_TEXTURE_RECTANGLE, p->default_texs[i + 7]); + gl->BindTexture(GL_TEXTURE_3D, p->default_texs[i + 14]); + } + gl->ActiveTexture(GL_TEXTURE1); + glCreateClearTex(gl, p->target, p->texfmt, p->gl_format, + p->gl_type, scale_type, + p->texture_width >> xs, p->texture_height >> ys, + clear); + if (p->mipmap_gen) + gl->TexParameteri(p->target, GL_GENERATE_MIPMAP, GL_TRUE); + gl->ActiveTexture(GL_TEXTURE2); + glCreateClearTex(gl, p->target, p->texfmt, p->gl_format, + p->gl_type, scale_type, + p->texture_width >> xs, p->texture_height >> ys, + clear); + if (p->mipmap_gen) + gl->TexParameteri(p->target, GL_GENERATE_MIPMAP, GL_TRUE); + gl->ActiveTexture(GL_TEXTURE0); + gl->BindTexture(p->target, 0); + } + if (p->is_yuv || p->custom_prog) { + if ((MASK_NOT_COMBINERS & (1 << p->use_yuv)) || p->custom_prog) { + if (!gl->GenPrograms || !gl->BindProgram) + mp_msg(MSGT_VO, MSGL_ERR, + "[gl] fragment program functions missing!\n"); + else { + gl->GenPrograms(1, &p->fragprog); + gl->BindProgram(GL_FRAGMENT_PROGRAM, p->fragprog); + } + } + update_yuvconv(vo); + } + + p->osd = mpgl_osd_init(gl, true); + p->osd->scaled = p->scaled_osd; + + resize(vo, d_width, d_height); + + gl->ClearColor(0.0f, 0.0f, 0.0f, 0.0f); + gl->Clear(GL_COLOR_BUFFER_BIT); + if (gl->SwapInterval && p->swap_interval >= 0) + gl->SwapInterval(p->swap_interval); + return 1; +} + +static bool create_window(struct vo *vo, uint32_t d_width, uint32_t d_height, + uint32_t flags) +{ + struct gl_priv *p = vo->priv; + + if (p->stereo_mode == GL_3D_QUADBUFFER) + flags |= VOFLAG_STEREO; + + int mpgl_caps = MPGL_CAP_GL_LEGACY; + if (!p->allow_sw) + mpgl_caps |= MPGL_CAP_NO_SW; + return mpgl_create_window(p->glctx, mpgl_caps, d_width, d_height, flags); +} + +static int config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t flags, + uint32_t format) +{ + struct gl_priv *p = vo->priv; + + int xs, ys; + p->image_height = height; + p->image_width = width; + p->image_format = format; + p->is_yuv = mp_get_chroma_shift(p->image_format, &xs, &ys, NULL) > 0; + p->is_yuv |= (xs << 8) | (ys << 16); + glFindFormat(format, p->have_texture_rg, NULL, &p->texfmt, &p->gl_format, + &p->gl_type); + + p->vo_flipped = !!(flags & VOFLAG_FLIPPING); + + if (vo->config_count) + uninitGl(vo); + + if (!create_window(vo, d_width, d_height, flags)) + return -1; + + initGl(vo, vo->dwidth, vo->dheight); + + return 0; +} + +static void check_events(struct vo *vo) +{ + struct gl_priv *p = vo->priv; + + int e = p->glctx->check_events(vo); + if (e & VO_EVENT_REINIT) { + uninitGl(vo); + initGl(vo, vo->dwidth, vo->dheight); + } + if (e & VO_EVENT_RESIZE) + resize(vo, vo->dwidth, vo->dheight); + if (e & VO_EVENT_EXPOSE) + vo->want_redraw = true; +} + +static void do_render(struct vo *vo) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + +// Enable(GL_TEXTURE_2D); +// BindTexture(GL_TEXTURE_2D, texture_id); + + gl->Color4f(1, 1, 1, 1); + if (p->is_yuv || p->custom_prog) + glEnableYUVConversion(gl, p->target, p->yuvconvtype); + if (p->stereo_mode) { + glEnable3DLeft(gl, p->stereo_mode); + glDrawTex(gl, 0, 0, p->image_width, p->image_height, + 0, 0, p->image_width >> 1, p->image_height, + p->texture_width, p->texture_height, + p->use_rectangle == 1, p->is_yuv, + p->mpi_flipped ^ p->vo_flipped); + glEnable3DRight(gl, p->stereo_mode); + glDrawTex(gl, 0, 0, p->image_width, p->image_height, + p->image_width >> 1, 0, p->image_width >> 1, + p->image_height, p->texture_width, p->texture_height, + p->use_rectangle == 1, p->is_yuv, + p->mpi_flipped ^ p->vo_flipped); + glDisable3D(gl, p->stereo_mode); + } else { + glDrawTex(gl, 0, 0, p->image_width, p->image_height, + 0, 0, p->image_width, p->image_height, + p->texture_width, p->texture_height, + p->use_rectangle == 1, p->is_yuv, + p->mpi_flipped ^ p->vo_flipped); + } + if (p->is_yuv || p->custom_prog) + glDisableYUVConversion(gl, p->target, p->yuvconvtype); +} + +static void flip_page(struct vo *vo) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + + if (p->use_glFinish) + gl->Finish(); + p->glctx->swapGlBuffers(p->glctx); + if (aspect_scaling()) + gl->Clear(GL_COLOR_BUFFER_BIT); +} + +static int draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h, + int x, int y) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + + p->mpi_flipped = stride[0] < 0; + glUploadTex(gl, p->target, p->gl_format, p->gl_type, src[0], stride[0], + x, y, w, h, p->slice_height); + if (p->is_yuv) { + int xs, ys; + mp_get_chroma_shift(p->image_format, &xs, &ys, NULL); + gl->ActiveTexture(GL_TEXTURE1); + glUploadTex(gl, p->target, p->gl_format, p->gl_type, src[1], stride[1], + x >> xs, y >> ys, w >> xs, h >> ys, p->slice_height); + gl->ActiveTexture(GL_TEXTURE2); + glUploadTex(gl, p->target, p->gl_format, p->gl_type, src[2], stride[2], + x >> xs, y >> ys, w >> xs, h >> ys, p->slice_height); + gl->ActiveTexture(GL_TEXTURE0); + } + return 0; +} + +static uint32_t get_image(struct vo *vo, mp_image_t *mpi) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + + int needed_size; + if (!gl->GenBuffers || !gl->BindBuffer || !gl->BufferData || !gl->MapBuffer) { + if (!p->err_shown) + mp_msg(MSGT_VO, MSGL_ERR, "[gl] extensions missing for dr\n" + "Expect a _major_ speed penalty\n"); + p->err_shown = 1; + return VO_FALSE; + } + if (mpi->flags & MP_IMGFLAG_READABLE) + return VO_FALSE; + if (mpi->type != MP_IMGTYPE_STATIC && mpi->type != MP_IMGTYPE_TEMP && + (mpi->type != MP_IMGTYPE_NUMBERED || mpi->number)) + return VO_FALSE; + if (p->ati_hack) { + mpi->width = p->texture_width; + mpi->height = p->texture_height; + } + mpi->stride[0] = mpi->width * mpi->bpp / 8; + needed_size = mpi->stride[0] * mpi->height; + if (!p->buffer) + gl->GenBuffers(1, &p->buffer); + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer); + if (needed_size > p->buffersize) { + p->buffersize = needed_size; + gl->BufferData(GL_PIXEL_UNPACK_BUFFER, p->buffersize, + NULL, GL_DYNAMIC_DRAW); + } + if (!p->bufferptr) + p->bufferptr = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); + mpi->planes[0] = p->bufferptr; + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + if (!mpi->planes[0]) { + if (!p->err_shown) + mp_msg(MSGT_VO, MSGL_ERR, "[gl] could not acquire buffer for dr\n" + "Expect a _major_ speed penalty\n"); + p->err_shown = 1; + return VO_FALSE; + } + if (p->is_yuv) { + // planar YUV + int xs, ys, component_bits; + mp_get_chroma_shift(p->image_format, &xs, &ys, &component_bits); + int bp = (component_bits + 7) / 8; + mpi->flags |= MP_IMGFLAG_COMMON_STRIDE | MP_IMGFLAG_COMMON_PLANE; + mpi->stride[0] = mpi->width * bp; + mpi->planes[1] = mpi->planes[0] + mpi->stride[0] * mpi->height; + mpi->stride[1] = (mpi->width >> xs) * bp; + mpi->planes[2] = mpi->planes[1] + mpi->stride[1] * (mpi->height >> ys); + mpi->stride[2] = (mpi->width >> xs) * bp; + if (p->ati_hack) { + mpi->flags &= ~MP_IMGFLAG_COMMON_PLANE; + if (!p->buffer_uv[0]) + gl->GenBuffers(2, p->buffer_uv); + int buffer_size = mpi->stride[1] * mpi->height; + if (buffer_size > p->buffersize_uv) { + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer_uv[0]); + gl->BufferData(GL_PIXEL_UNPACK_BUFFER, buffer_size, NULL, + GL_DYNAMIC_DRAW); + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer_uv[1]); + gl->BufferData(GL_PIXEL_UNPACK_BUFFER, buffer_size, NULL, + GL_DYNAMIC_DRAW); + p->buffersize_uv = buffer_size; + } + if (!p->bufferptr_uv[0]) { + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer_uv[0]); + p->bufferptr_uv[0] = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER, + GL_WRITE_ONLY); + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer_uv[1]); + p->bufferptr_uv[1] = gl->MapBuffer(GL_PIXEL_UNPACK_BUFFER, + GL_WRITE_ONLY); + } + mpi->planes[1] = p->bufferptr_uv[0]; + mpi->planes[2] = p->bufferptr_uv[1]; + } + } + mpi->flags |= MP_IMGFLAG_DIRECT; + return VO_TRUE; +} + +static void clear_border(struct vo *vo, uint8_t *dst, int start, int stride, + int height, int full_height, int value) +{ + int right_border = stride - start; + int bottom_border = full_height - height; + while (height > 0) { + if (right_border > 0) + memset(dst + start, value, right_border); + dst += stride; + height--; + } + if (bottom_border > 0) + memset(dst, value, stride * bottom_border); +} + +static uint32_t draw_image(struct vo *vo, mp_image_t *mpi) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + + int slice = p->slice_height; + int stride[3]; + unsigned char *planes[3]; + mp_image_t mpi2 = *mpi; + int w = mpi->w, h = mpi->h; + if (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK) + goto skip_upload; + mpi2.flags = 0; + mpi2.type = MP_IMGTYPE_TEMP; + mpi2.width = mpi2.w; + mpi2.height = mpi2.h; + if (p->force_pbo && !(mpi->flags & MP_IMGFLAG_DIRECT) && !p->bufferptr + && get_image(vo, &mpi2) == VO_TRUE) + { + int bp = mpi->bpp / 8; + int xs, ys, component_bits; + mp_get_chroma_shift(p->image_format, &xs, &ys, &component_bits); + if (p->is_yuv) + bp = (component_bits + 7) / 8; + memcpy_pic(mpi2.planes[0], mpi->planes[0], mpi->w * bp, mpi->h, + mpi2.stride[0], mpi->stride[0]); + int uv_bytes = (mpi->w >> xs) * bp; + if (p->is_yuv) { + memcpy_pic(mpi2.planes[1], mpi->planes[1], uv_bytes, mpi->h >> ys, + mpi2.stride[1], mpi->stride[1]); + memcpy_pic(mpi2.planes[2], mpi->planes[2], uv_bytes, mpi->h >> ys, + mpi2.stride[2], mpi->stride[2]); + } + if (p->ati_hack) { + // since we have to do a full upload we need to clear the borders + clear_border(vo, mpi2.planes[0], mpi->w * bp, mpi2.stride[0], + mpi->h, mpi2.height, 0); + if (p->is_yuv) { + int clear = get_chroma_clear_val(component_bits); + clear_border(vo, mpi2.planes[1], uv_bytes, mpi2.stride[1], + mpi->h >> ys, mpi2.height >> ys, clear); + clear_border(vo, mpi2.planes[2], uv_bytes, mpi2.stride[2], + mpi->h >> ys, mpi2.height >> ys, clear); + } + } + mpi = &mpi2; + } + stride[0] = mpi->stride[0]; + stride[1] = mpi->stride[1]; + stride[2] = mpi->stride[2]; + planes[0] = mpi->planes[0]; + planes[1] = mpi->planes[1]; + planes[2] = mpi->planes[2]; + p->mpi_flipped = stride[0] < 0; + if (mpi->flags & MP_IMGFLAG_DIRECT) { + intptr_t base = (intptr_t)planes[0]; + if (p->ati_hack) { + w = p->texture_width; + h = p->texture_height; + } + if (p->mpi_flipped) + base += (mpi->h - 1) * stride[0]; + planes[0] -= base; + planes[1] -= base; + planes[2] -= base; + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer); + gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + p->bufferptr = NULL; + if (!(mpi->flags & MP_IMGFLAG_COMMON_PLANE)) + planes[0] = planes[1] = planes[2] = NULL; + slice = 0; // always "upload" full texture + } + glUploadTex(gl, p->target, p->gl_format, p->gl_type, planes[0], + stride[0], 0, 0, w, h, slice); + if (p->is_yuv) { + int xs, ys; + mp_get_chroma_shift(p->image_format, &xs, &ys, NULL); + if ((mpi->flags & MP_IMGFLAG_DIRECT) && !(mpi->flags & MP_IMGFLAG_COMMON_PLANE)) { + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer_uv[0]); + gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + p->bufferptr_uv[0] = NULL; + } + gl->ActiveTexture(GL_TEXTURE1); + glUploadTex(gl, p->target, p->gl_format, p->gl_type, planes[1], + stride[1], 0, 0, w >> xs, h >> ys, slice); + if ((mpi->flags & MP_IMGFLAG_DIRECT) && !(mpi->flags & MP_IMGFLAG_COMMON_PLANE)) { + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, p->buffer_uv[1]); + gl->UnmapBuffer(GL_PIXEL_UNPACK_BUFFER); + p->bufferptr_uv[1] = NULL; + } + gl->ActiveTexture(GL_TEXTURE2); + glUploadTex(gl, p->target, p->gl_format, p->gl_type, planes[2], + stride[2], 0, 0, w >> xs, h >> ys, slice); + gl->ActiveTexture(GL_TEXTURE0); + } + if (mpi->flags & MP_IMGFLAG_DIRECT) { + gl->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } +skip_upload: + do_render(vo); + return VO_TRUE; +} + +static mp_image_t *get_screenshot(struct vo *vo) +{ + struct gl_priv *p = vo->priv; + GL *gl = p->gl; + + mp_image_t *image = alloc_mpi(p->texture_width, p->texture_height, + p->image_format); + + glDownloadTex(gl, p->target, p->gl_format, p->gl_type, image->planes[0], + image->stride[0]); + + if (p->is_yuv) { + gl->ActiveTexture(GL_TEXTURE1); + glDownloadTex(gl, p->target, p->gl_format, p->gl_type, image->planes[1], + image->stride[1]); + gl->ActiveTexture(GL_TEXTURE2); + glDownloadTex(gl, p->target, p->gl_format, p->gl_type, image->planes[2], + image->stride[2]); + gl->ActiveTexture(GL_TEXTURE0); + } + + image->w = p->image_width; + image->h = p->image_height; + image->display_w = vo->aspdat.prew; + image->display_h = vo->aspdat.preh; + + mp_image_set_colorspace_details(image, &p->colorspace); + + return image; +} + +static int query_format(struct vo *vo, uint32_t format) +{ + struct gl_priv *p = vo->priv; + + int depth; + int caps = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | VFCAP_FLIP | + VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | VFCAP_ACCEPT_STRIDE; + if (p->use_osd) + caps |= VFCAP_OSD; + if (format == IMGFMT_RGB24 || format == IMGFMT_RGBA) + return caps; + if (p->use_yuv && mp_get_chroma_shift(format, NULL, NULL, &depth) && + (depth == 8 || depth == 16 || glYUVLargeRange(p->use_yuv)) && + (IMGFMT_IS_YUVP16_NE(format) || !IMGFMT_IS_YUVP16(format))) + return caps; + // HACK, otherwise we get only b&w with some filters (e.g. -vf eq) + // ideally MPlayer should be fixed instead not to use Y800 when it has the choice + if (!p->use_yuv && (format == IMGFMT_Y8 || format == IMGFMT_Y800)) + return 0; + if (!p->use_ycbcr && (format == IMGFMT_UYVY || format == IMGFMT_YVYU)) + return 0; + if (p->many_fmts && + glFindFormat(format, p->have_texture_rg, NULL, NULL, NULL, NULL)) + return caps; + return 0; +} + +static void uninit(struct vo *vo) +{ + struct gl_priv *p = vo->priv; + + uninitGl(vo); + free(p->custom_prog); + p->custom_prog = NULL; + free(p->custom_tex); + p->custom_tex = NULL; + mpgl_uninit(p->glctx); + p->glctx = NULL; + p->gl = NULL; +} + +static int backend_valid(void *arg) +{ + return mpgl_find_backend(*(const char **)arg) >= 0; +} + +static int preinit(struct vo *vo, const char *arg) +{ + struct gl_priv *p = talloc_zero(vo, struct gl_priv); + vo->priv = p; + + *p = (struct gl_priv) { + .many_fmts = 1, + .use_osd = -1, + .use_yuv = -1, + .colorspace = MP_CSP_DETAILS_DEFAULTS, + .filter_strength = 0.5, + .use_rectangle = -1, + .ati_hack = -1, + .force_pbo = -1, + .swap_interval = vo_vsync, + .custom_prog = NULL, + .custom_tex = NULL, + .custom_tlin = 1, + .osd_color = 0xffffff, + }; + + char *backend_arg = NULL; + + //essentially unused; for legacy warnings only + int user_colorspace = 0; + int levelconv = -1; + int aspect = -1; + + const opt_t subopts[] = { + {"manyfmts", OPT_ARG_BOOL, &p->many_fmts, NULL}, + {"osd", OPT_ARG_BOOL, &p->use_osd, NULL}, + {"scaled-osd", OPT_ARG_BOOL, &p->scaled_osd, NULL}, + {"ycbcr", OPT_ARG_BOOL, &p->use_ycbcr, NULL}, + {"slice-height", OPT_ARG_INT, &p->slice_height, int_non_neg}, + {"rectangle", OPT_ARG_INT, &p->use_rectangle,int_non_neg}, + {"yuv", OPT_ARG_INT, &p->use_yuv, int_non_neg}, + {"lscale", OPT_ARG_INT, &p->lscale, int_non_neg}, + {"cscale", OPT_ARG_INT, &p->cscale, int_non_neg}, + {"filter-strength", OPT_ARG_FLOAT, &p->filter_strength, NULL}, + {"noise-strength", OPT_ARG_FLOAT, &p->noise_strength, NULL}, + {"ati-hack", OPT_ARG_BOOL, &p->ati_hack, NULL}, + {"force-pbo", OPT_ARG_BOOL, &p->force_pbo, NULL}, + {"glfinish", OPT_ARG_BOOL, &p->use_glFinish, NULL}, + {"swapinterval", OPT_ARG_INT, &p->swap_interval,NULL}, + {"customprog", OPT_ARG_MSTRZ,&p->custom_prog, NULL}, + {"customtex", OPT_ARG_MSTRZ,&p->custom_tex, NULL}, + {"customtlin", OPT_ARG_BOOL, &p->custom_tlin, NULL}, + {"customtrect", OPT_ARG_BOOL, &p->custom_trect, NULL}, + {"mipmapgen", OPT_ARG_BOOL, &p->mipmap_gen, NULL}, + {"osdcolor", OPT_ARG_INT, &p->osd_color, NULL}, + {"stereo", OPT_ARG_INT, &p->stereo_mode, NULL}, + {"sw", OPT_ARG_BOOL, &p->allow_sw, NULL}, + {"backend", OPT_ARG_MSTRZ,&backend_arg, backend_valid}, + // Removed options. + // They are only parsed to notify the user about the replacements. + {"aspect", OPT_ARG_BOOL, &aspect, NULL}, + {"colorspace", OPT_ARG_INT, &user_colorspace, NULL}, + {"levelconv", OPT_ARG_INT, &levelconv, NULL}, + {NULL} + }; + + if (subopt_parse(arg, subopts) != 0) { + mp_msg(MSGT_VO, MSGL_FATAL, + "\n-vo opengl_old command line help:\n" + "Example: mpv -vo opengl_old:slice-height=4\n" + "\nOptions:\n" + " nomanyfmts\n" + " Disable extended color formats for OpenGL 1.2 and later\n" + " slice-height=<0-...>\n" + " Slice size for texture transfer, 0 for whole image\n" + " noosd\n" + " Do not use OpenGL OSD code\n" + " scaled-osd\n" + " Render OSD at movie resolution and scale it\n" + " rectangle=<0,1,2>\n" + " 0: use power-of-two textures\n" + " 1: use texture_rectangle\n" + " 2: use texture_non_power_of_two\n" + " ati-hack\n" + " Workaround ATI bug with PBOs\n" + " force-pbo\n" + " Force use of PBO even if this involves an extra memcpy\n" + " glfinish\n" + " Call glFinish() before swapping buffers\n" + " swapinterval=<n>\n" + " Interval in displayed frames between to buffer swaps.\n" + " 1 is equivalent to enable VSYNC, 0 to disable VSYNC.\n" + " Requires GLX_SGI_swap_control support to work.\n" + " ycbcr\n" + " also try to use the GL_MESA_ycbcr_texture extension\n" + " yuv=<n>\n" + " 0: use software YUV to RGB conversion.\n" + " 1: deprecated, will use yuv=2 (used to be nVidia register combiners).\n" + " 2: use fragment program.\n" + " 3: use fragment program with gamma correction.\n" + " 4: use fragment program with gamma correction via lookup.\n" + " 5: use ATI-specific method (for older cards).\n" + " 6: use lookup via 3D texture.\n" + " lscale=<n>\n" + " 0: use standard bilinear scaling for luma.\n" + " 1: use improved bicubic scaling for luma.\n" + " 2: use cubic in X, linear in Y direction scaling for luma.\n" + " 3: as 1 but without using a lookup texture.\n" + " 4: experimental unsharp masking (sharpening).\n" + " 5: experimental unsharp masking (sharpening) with larger radius.\n" + " cscale=<n>\n" + " as lscale but for chroma (2x slower with little visible effect).\n" + " filter-strength=<value>\n" + " set the effect strength for some lscale/cscale filters\n" + " noise-strength=<value>\n" + " set how much noise to add. 1.0 is suitable for dithering to 6 bit.\n" + " customprog=<filename>\n" + " use a custom YUV conversion program\n" + " customtex=<filename>\n" + " use a custom YUV conversion lookup texture\n" + " nocustomtlin\n" + " use GL_NEAREST scaling for customtex texture\n" + " customtrect\n" + " use texture_rectangle for customtex texture\n" + " mipmapgen\n" + " generate mipmaps for the video image (use with TXB in customprog)\n" + " osdcolor=<0xAARRGGBB>\n" + " use the given color for the OSD\n" + " stereo=<n>\n" + " 0: normal display\n" + " 1: side-by-side to red-cyan stereo\n" + " 2: side-by-side to green-magenta stereo\n" + " 3: side-by-side to quadbuffer stereo\n" + " sw\n" + " allow using a software renderer, if such is detected\n" + " backend=<sys>\n" + " auto: auto-select (default)\n" + " cocoa: Cocoa/OSX\n" + " win: Win32/WGL\n" + " x11: X11/GLX\n" + "\n"); + return -1; + } + if (user_colorspace != 0 || levelconv != -1) { + mp_msg(MSGT_VO, MSGL_ERR, "[gl] \"colorspace\" and \"levelconv\" " + "suboptions have been removed. Use options --colormatrix and" + " --colormatrix-input-range/--colormatrix-output-range instead.\n"); + return -1; + } + if (aspect != -1) { + mp_msg(MSGT_VO, MSGL_ERR, "[gl] \"noaspect\" suboption has been " + "removed. Use --noaspect instead.\n"); + return -1; + } + if (p->use_yuv == 1) { + mp_msg(MSGT_VO, MSGL_WARN, "[gl] yuv=1 (nVidia register combiners) have" + " been removed, using yuv=2 instead.\n"); + p->use_yuv = 2; + } + + int backend = backend_arg ? mpgl_find_backend(backend_arg) : GLTYPE_AUTO; + free(backend_arg); + + p->glctx = mpgl_init(backend, vo); + if (!p->glctx) + goto err_out; + p->gl = p->glctx->gl; + + if (p->use_yuv == -1) { + if (!create_window(vo, 320, 200, VOFLAG_HIDDEN)) + goto err_out; + autodetectGlExtensions(vo); + // We created a window to test whether the GL context supports hardware + // acceleration and so on. Destroy that window to make sure all state + // associated with it is lost. + uninitGl(vo); + if (!mpgl_destroy_window(p->glctx)) + goto err_out; + } + if (p->many_fmts) + mp_msg(MSGT_VO, MSGL_INFO, "[gl] using extended formats. " + "Use -vo gl:nomanyfmts if playback fails.\n"); + mp_msg(MSGT_VO, MSGL_V, "[gl] Using %d as slice height " + "(0 means image height).\n", p->slice_height); + + return 0; + +err_out: + uninit(vo); + return -1; +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + struct gl_priv *p = vo->priv; + + switch (request) { + case VOCTRL_QUERY_FORMAT: + return query_format(vo, *(uint32_t *)data); + case VOCTRL_DRAW_IMAGE: + return draw_image(vo, data); + case VOCTRL_ONTOP: + if (!p->glctx->ontop) + break; + p->glctx->ontop(vo); + return VO_TRUE; + case VOCTRL_FULLSCREEN: + p->glctx->fullscreen(vo); + resize(vo, vo->dwidth, vo->dheight); + return VO_TRUE; + case VOCTRL_BORDER: + if (!p->glctx->border) + break; + p->glctx->border(vo); + resize(vo, vo->dwidth, vo->dheight); + return VO_TRUE; + case VOCTRL_GET_PANSCAN: + return VO_TRUE; + case VOCTRL_SET_PANSCAN: + resize(vo, vo->dwidth, vo->dheight); + return VO_TRUE; + case VOCTRL_GET_EQUALIZER: + if (p->is_yuv) { + struct voctrl_get_equalizer_args *args = data; + return mp_csp_equalizer_get(&p->video_eq, args->name, args->valueptr) + >= 0 ? VO_TRUE : VO_NOTIMPL; + } + break; + case VOCTRL_SET_EQUALIZER: + if (p->is_yuv) { + struct voctrl_set_equalizer_args *args = data; + if (mp_csp_equalizer_set(&p->video_eq, args->name, args->value) < 0) + return VO_NOTIMPL; + update_yuvconv(vo); + vo->want_redraw = true; + return VO_TRUE; + } + break; + case VOCTRL_SET_YUV_COLORSPACE: { + bool supports_csp = (1 << p->use_yuv) & MASK_NOT_COMBINERS; + if (vo->config_count && supports_csp) { + p->colorspace = *(struct mp_csp_details *)data; + update_yuvconv(vo); + vo->want_redraw = true; + } + return VO_TRUE; + } + case VOCTRL_GET_YUV_COLORSPACE: + *(struct mp_csp_details *)data = p->colorspace; + return VO_TRUE; + case VOCTRL_UPDATE_SCREENINFO: + if (!p->glctx->update_xinerama_info) + break; + p->glctx->update_xinerama_info(vo); + return VO_TRUE; + case VOCTRL_REDRAW_FRAME: + do_render(vo); + return true; + case VOCTRL_PAUSE: + if (!p->glctx->pause) + break; + p->glctx->pause(vo); + return VO_TRUE; + case VOCTRL_RESUME: + if (!p->glctx->resume) + break; + p->glctx->resume(vo); + return VO_TRUE; + case VOCTRL_SCREENSHOT: { + struct voctrl_screenshot_args *args = data; + if (args->full_window) + args->out_image = glGetWindowScreenshot(p->gl); + else + args->out_image = get_screenshot(vo); + return true; + } + } + return VO_NOTIMPL; +} + +const struct vo_driver video_out_opengl_old = { + .is_new = true, + .info = &(const vo_info_t) { + "OpenGL", + "opengl-old", + "Reimar Doeffinger <Reimar.Doeffinger@gmx.de>", + "" + }, + .preinit = preinit, + .config = config, + .control = control, + .draw_slice = draw_slice, + .draw_osd = draw_osd, + .flip_page = flip_page, + .check_events = check_events, + .uninit = uninit, +}; diff --git a/video/out/vo_opengl_shaders.glsl b/video/out/vo_opengl_shaders.glsl new file mode 100644 index 0000000000..1f302889e4 --- /dev/null +++ b/video/out/vo_opengl_shaders.glsl @@ -0,0 +1,355 @@ +/* + * This file is part of mplayer2. + * + * mplayer2 is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with mplayer2; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +// Note that this file is not directly passed as shader, but run through some +// text processing functions, and in fact contains multiple vertex and fragment +// shaders. + +// inserted at the beginning of all shaders +#!section prelude + +// GLSL 1.20 compatibility layer +// texture() should be assumed to always map to texture2D() +#if __VERSION__ >= 130 +# define texture1D texture +# define texture3D texture +# define DECLARE_FRAGPARMS \ + out vec4 out_color; +#else +# define texture texture2D +# define DECLARE_FRAGPARMS +# define out_color gl_FragColor +# define in varying +#endif + +// Earlier GLSL doesn't support mix() with bvec +#if __VERSION__ >= 130 +vec3 srgb_compand(vec3 v) +{ + return mix(1.055 * pow(v, vec3(1.0/2.4)) - vec3(0.055), v * 12.92, + lessThanEqual(v, vec3(0.0031308))); +} +#endif + +#!section vertex_all + +#if __VERSION__ < 130 +# undef in +# define in attribute +# define out varying +#endif + +uniform mat3 transform; +uniform sampler3D lut_3d; + +in vec2 vertex_position; +in vec4 vertex_color; +out vec4 color; +in vec2 vertex_texcoord; +out vec2 texcoord; + +void main() { + vec3 position = vec3(vertex_position, 1); +#ifndef FIXED_SCALE + position = transform * position; +#endif + gl_Position = vec4(position, 1); + color = vertex_color; + +#ifdef USE_OSD_LINEAR_CONV + // If no 3dlut is being used, we need to pull up to linear light for + // the sRGB function. *IF* 3dlut is used, we do not. + color.rgb = pow(color.rgb, vec3(2.2)); +#endif +#ifdef USE_OSD_3DLUT + color = vec4(texture3D(lut_3d, color.rgb).rgb, color.a); +#endif +#ifdef USE_OSD_SRGB + color.rgb = srgb_compand(color.rgb); +#endif + + texcoord = vertex_texcoord; +} + +#!section frag_osd_libass +uniform sampler2D textures[3]; + +in vec2 texcoord; +in vec4 color; +DECLARE_FRAGPARMS + +void main() { + out_color = vec4(color.rgb, color.a * texture(textures[0], texcoord).r); +} + +#!section frag_osd_rgba +uniform sampler2D textures[3]; + +in vec2 texcoord; +DECLARE_FRAGPARMS + +void main() { + out_color = texture(textures[0], texcoord); +} + +#!section frag_video +uniform sampler2D textures[3]; +uniform vec2 textures_size[3]; +uniform sampler1D lut_c_1d; +uniform sampler1D lut_l_1d; +uniform sampler2D lut_c_2d; +uniform sampler2D lut_l_2d; +uniform sampler3D lut_3d; +uniform sampler2D dither; +uniform mat4x3 colormatrix; +uniform vec3 inv_gamma; +uniform float conv_gamma; +uniform float dither_quantization; +uniform float dither_multiply; +uniform float filter_param1; +uniform vec2 dither_size; + +in vec2 texcoord; +DECLARE_FRAGPARMS + +vec4 sample_bilinear(sampler2D tex, vec2 texsize, vec2 texcoord) { + return texture(tex, texcoord); +} + +// Explanation how bicubic scaling with only 4 texel fetches is done: +// http://www.mate.tue.nl/mate/pdfs/10318.pdf +// 'Efficient GPU-Based Texture Interpolation using Uniform B-Splines' +// Explanation why this algorithm normally always blurs, even with unit scaling: +// http://bigwww.epfl.ch/preprints/ruijters1001p.pdf +// 'GPU Prefilter for Accurate Cubic B-spline Interpolation' +vec4 calcweights(float s) { + vec4 t = vec4(-0.5, 0.1666, 0.3333, -0.3333) * s + vec4(1, 0, -0.5, 0.5); + t = t * s + vec4(0, 0, -0.5, 0.5); + t = t * s + vec4(-0.6666, 0, 0.8333, 0.1666); + vec2 a = vec2(1 / t.z, 1 / t.w); + t.xy = t.xy * a + vec2(1, 1); + t.x = t.x + s; + t.y = t.y - s; + return t; +} + +vec4 sample_bicubic_fast(sampler2D tex, vec2 texsize, vec2 texcoord) { + vec2 pt = 1 / texsize; + vec2 fcoord = fract(texcoord * texsize + vec2(0.5, 0.5)); + vec4 parmx = calcweights(fcoord.x); + vec4 parmy = calcweights(fcoord.y); + vec4 cdelta; + cdelta.xz = parmx.rg * vec2(-pt.x, pt.x); + cdelta.yw = parmy.rg * vec2(-pt.y, pt.y); + // first y-interpolation + vec4 ar = texture(tex, texcoord + cdelta.xy); + vec4 ag = texture(tex, texcoord + cdelta.xw); + vec4 ab = mix(ag, ar, parmy.b); + // second y-interpolation + vec4 br = texture(tex, texcoord + cdelta.zy); + vec4 bg = texture(tex, texcoord + cdelta.zw); + vec4 aa = mix(bg, br, parmy.b); + // x-interpolation + return mix(aa, ab, parmx.b); +} + +float[2] weights2(sampler1D lookup, float f) { + vec4 c = texture1D(lookup, f); + return float[2](c.r, c.g); +} + +float[4] weights4(sampler1D lookup, float f) { + vec4 c = texture1D(lookup, f); + return float[4](c.r, c.g, c.b, c.a); +} + +float[6] weights6(sampler2D lookup, float f) { + vec4 c1 = texture(lookup, vec2(0.25, f)); + vec4 c2 = texture(lookup, vec2(0.75, f)); + return float[6](c1.r, c1.g, c1.b, c2.r, c2.g, c2.b); +} + +float[8] weights8(sampler2D lookup, float f) { + vec4 c1 = texture(lookup, vec2(0.25, f)); + vec4 c2 = texture(lookup, vec2(0.75, f)); + return float[8](c1.r, c1.g, c1.b, c1.a, c2.r, c2.g, c2.b, c2.a); +} + +float[12] weights12(sampler2D lookup, float f) { + vec4 c1 = texture(lookup, vec2(1.0/6.0, f)); + vec4 c2 = texture(lookup, vec2(0.5, f)); + vec4 c3 = texture(lookup, vec2(5.0/6.0, f)); + return float[12](c1.r, c1.g, c1.b, c1.a, + c2.r, c2.g, c2.b, c2.a, + c3.r, c3.g, c3.b, c3.a); +} + +float[16] weights16(sampler2D lookup, float f) { + vec4 c1 = texture(lookup, vec2(0.125, f)); + vec4 c2 = texture(lookup, vec2(0.375, f)); + vec4 c3 = texture(lookup, vec2(0.625, f)); + vec4 c4 = texture(lookup, vec2(0.875, f)); + return float[16](c1.r, c1.g, c1.b, c1.a, c2.r, c2.g, c2.b, c2.a, + c3.r, c3.g, c3.b, c3.a, c4.r, c4.g, c4.b, c4.a); +} + +#define CONVOLUTION_SEP_N(NAME, N) \ + vec4 NAME(sampler2D tex, vec2 texcoord, vec2 pt, float weights[N]) { \ + vec4 res = vec4(0); \ + for (int n = 0; n < N; n++) { \ + res += weights[n] * texture(tex, texcoord + pt * n); \ + } \ + return res; \ + } + +CONVOLUTION_SEP_N(convolution_sep2, 2) +CONVOLUTION_SEP_N(convolution_sep4, 4) +CONVOLUTION_SEP_N(convolution_sep6, 6) +CONVOLUTION_SEP_N(convolution_sep8, 8) +CONVOLUTION_SEP_N(convolution_sep12, 12) +CONVOLUTION_SEP_N(convolution_sep16, 16) + +// The dir parameter is (0, 1) or (1, 0), and we expect the shader compiler to +// remove all the redundant multiplications and additions. +#define SAMPLE_CONVOLUTION_SEP_N(NAME, N, SAMPLERT, CONV_FUNC, WEIGHTS_FUNC)\ + vec4 NAME(vec2 dir, SAMPLERT lookup, sampler2D tex, vec2 texsize, \ + vec2 texcoord) { \ + vec2 pt = (1 / texsize) * dir; \ + float fcoord = dot(fract(texcoord * texsize - 0.5), dir); \ + vec2 base = texcoord - fcoord * pt; \ + return CONV_FUNC(tex, base - pt * (N / 2 - 1), pt, \ + WEIGHTS_FUNC(lookup, fcoord)); \ + } + +SAMPLE_CONVOLUTION_SEP_N(sample_convolution_sep2, 2, sampler1D, convolution_sep2, weights2) +SAMPLE_CONVOLUTION_SEP_N(sample_convolution_sep4, 4, sampler1D, convolution_sep4, weights4) +SAMPLE_CONVOLUTION_SEP_N(sample_convolution_sep6, 6, sampler2D, convolution_sep6, weights6) +SAMPLE_CONVOLUTION_SEP_N(sample_convolution_sep8, 8, sampler2D, convolution_sep8, weights8) +SAMPLE_CONVOLUTION_SEP_N(sample_convolution_sep12, 12, sampler2D, convolution_sep12, weights12) +SAMPLE_CONVOLUTION_SEP_N(sample_convolution_sep16, 16, sampler2D, convolution_sep16, weights16) + + +#define CONVOLUTION_N(NAME, N) \ + vec4 NAME(sampler2D tex, vec2 texcoord, vec2 pt, float taps_x[N], \ + float taps_y[N]) { \ + vec4 res = vec4(0); \ + for (int y = 0; y < N; y++) { \ + vec4 line = vec4(0); \ + for (int x = 0; x < N; x++) \ + line += taps_x[x] * texture(tex, texcoord + pt * vec2(x, y));\ + res += taps_y[y] * line; \ + } \ + return res; \ + } + +CONVOLUTION_N(convolution2, 2) +CONVOLUTION_N(convolution4, 4) +CONVOLUTION_N(convolution6, 6) +CONVOLUTION_N(convolution8, 8) +CONVOLUTION_N(convolution12, 12) +CONVOLUTION_N(convolution16, 16) + +#define SAMPLE_CONVOLUTION_N(NAME, N, SAMPLERT, CONV_FUNC, WEIGHTS_FUNC) \ + vec4 NAME(SAMPLERT lookup, sampler2D tex, vec2 texsize, vec2 texcoord) {\ + vec2 pt = 1 / texsize; \ + vec2 fcoord = fract(texcoord * texsize - 0.5); \ + vec2 base = texcoord - fcoord * pt; \ + return CONV_FUNC(tex, base - pt * (N / 2 - 1), pt, \ + WEIGHTS_FUNC(lookup, fcoord.x), \ + WEIGHTS_FUNC(lookup, fcoord.y)); \ + } + +SAMPLE_CONVOLUTION_N(sample_convolution2, 2, sampler1D, convolution2, weights2) +SAMPLE_CONVOLUTION_N(sample_convolution4, 4, sampler1D, convolution4, weights4) +SAMPLE_CONVOLUTION_N(sample_convolution6, 6, sampler2D, convolution6, weights6) +SAMPLE_CONVOLUTION_N(sample_convolution8, 8, sampler2D, convolution8, weights8) +SAMPLE_CONVOLUTION_N(sample_convolution12, 12, sampler2D, convolution12, weights12) +SAMPLE_CONVOLUTION_N(sample_convolution16, 16, sampler2D, convolution16, weights16) + + +// Unsharp masking +vec4 sample_sharpen3(sampler2D tex, vec2 texsize, vec2 texcoord) { + vec2 pt = 1 / texsize; + vec2 st = pt * 0.5; + vec4 p = texture(tex, texcoord); + vec4 sum = texture(tex, texcoord + st * vec2(+1, +1)) + + texture(tex, texcoord + st * vec2(+1, -1)) + + texture(tex, texcoord + st * vec2(-1, +1)) + + texture(tex, texcoord + st * vec2(-1, -1)); + return p + (p - 0.25 * sum) * filter_param1; +} + +vec4 sample_sharpen5(sampler2D tex, vec2 texsize, vec2 texcoord) { + vec2 pt = 1 / texsize; + vec2 st1 = pt * 1.2; + vec4 p = texture(tex, texcoord); + vec4 sum1 = texture(tex, texcoord + st1 * vec2(+1, +1)) + + texture(tex, texcoord + st1 * vec2(+1, -1)) + + texture(tex, texcoord + st1 * vec2(-1, +1)) + + texture(tex, texcoord + st1 * vec2(-1, -1)); + vec2 st2 = pt * 1.5; + vec4 sum2 = texture(tex, texcoord + st2 * vec2(+1, 0)) + + texture(tex, texcoord + st2 * vec2( 0, +1)) + + texture(tex, texcoord + st2 * vec2(-1, 0)) + + texture(tex, texcoord + st2 * vec2( 0, -1)); + vec4 t = p * 0.859375 + sum2 * -0.1171875 + sum1 * -0.09765625; + return p + t * filter_param1; +} + +void main() { +#ifdef USE_PLANAR + vec3 color = vec3(SAMPLE_L(textures[0], textures_size[0], texcoord).r, + SAMPLE_C(textures[1], textures_size[1], texcoord).r, + SAMPLE_C(textures[2], textures_size[2], texcoord).r); +#else + vec3 color = SAMPLE_L(textures[0], textures_size[0], texcoord).rgb; +#endif +#ifdef USE_GBRP + color.gbr = color; +#endif +#ifdef USE_YGRAY + // NOTE: actually slightly wrong for 16 bit input video, and completely + // wrong for 9/10 bit input + color.gb = vec2(128.0/255.0); +#endif +#ifdef USE_COLORMATRIX + color = mat3(colormatrix) * color + colormatrix[3]; +#endif +#ifdef USE_LINEAR_CONV + color = pow(color, vec3(2.2)); +#endif +#ifdef USE_LINEAR_CONV_INV + // Convert from linear RGB to gamma RGB before putting it through the 3D-LUT + // in the final stage. + color = pow(color, vec3(1.0/2.2)); +#endif +#ifdef USE_GAMMA_POW + color = pow(color, inv_gamma); +#endif +#ifdef USE_3DLUT + color = texture3D(lut_3d, color).rgb; +#endif +#ifdef USE_SRGB + color.rgb = srgb_compand(color.rgb); +#endif +#ifdef USE_DITHER + float dither_value = texture(dither, gl_FragCoord.xy / dither_size).r; + color = floor(color * dither_multiply + dither_value ) / dither_quantization; +#endif + out_color = vec4(color, 1); +} diff --git a/video/out/vo_vdpau.c b/video/out/vo_vdpau.c new file mode 100644 index 0000000000..a523ea5815 --- /dev/null +++ b/video/out/vo_vdpau.c @@ -0,0 +1,1718 @@ +/* + * VDPAU video output driver + * + * Copyright (C) 2008 NVIDIA + * Copyright (C) 2009 Uoti Urpala + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Actual decoding and presentation are implemented here. + * All necessary frame information is collected through + * the "vdpau_render_state" structure after parsing all headers + * etc. in libavcodec for different codecs. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <limits.h> +#include <assert.h> + +#include <libavutil/common.h> +#include <libavcodec/vdpau.h> + +#include "config.h" +#include "mp_msg.h" +#include "options.h" +#include "talloc.h" +#include "video_out.h" +#include "x11_common.h" +#include "aspect.h" +#include "csputils.h" +#include "sub/sub.h" +#include "m_option.h" +#include "libmpcodecs/vfcap.h" +#include "libmpcodecs/mp_image.h" +#include "osdep/timer.h" +#include "bitmap_packer.h" + +#define WRAP_ADD(x, a, m) ((a) < 0 \ + ? ((x)+(a)+(m) < (m) ? (x)+(a)+(m) : (x)+(a)) \ + : ((x)+(a) < (m) ? (x)+(a) : (x)+(a)-(m))) + +#define CHECK_ST_ERROR(message) \ + do { \ + if (vdp_st != VDP_STATUS_OK) { \ + mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] %s: %s\n", \ + message, vdp->get_error_string(vdp_st)); \ + return -1; \ + } \ + } while (0) + +#define CHECK_ST_WARNING(message) \ + do { \ + if (vdp_st != VDP_STATUS_OK) \ + mp_msg(MSGT_VO, MSGL_WARN, "[ vdpau] %s: %s\n", \ + message, vdp->get_error_string(vdp_st)); \ + } while (0) + +/* number of video and output surfaces */ +#define MAX_OUTPUT_SURFACES 15 +#define MAX_VIDEO_SURFACES 50 +#define NUM_BUFFERED_VIDEO 5 + +/* Pixelformat used for output surfaces */ +#define OUTPUT_RGBA_FORMAT VDP_RGBA_FORMAT_B8G8R8A8 + +/* + * Global variable declaration - VDPAU specific + */ + +struct vdp_functions { +#define VDP_FUNCTION(vdp_type, _, mp_name) vdp_type *mp_name; +#include "vdpau_template.c" +#undef VDP_FUNCTION +}; + +struct vdpctx { + struct vdp_functions *vdp; + + VdpDevice vdp_device; + bool is_preempted; + bool preemption_acked; + bool preemption_user_notified; + unsigned int last_preemption_retry_fail; + VdpGetProcAddress *vdp_get_proc_address; + + VdpPresentationQueueTarget flip_target; + VdpPresentationQueue flip_queue; + uint64_t last_vdp_time; + unsigned int last_sync_update; + + VdpOutputSurface output_surfaces[MAX_OUTPUT_SURFACES]; + VdpOutputSurface screenshot_surface; + int num_output_surfaces; + struct buffered_video_surface { + VdpVideoSurface surface; + double pts; + mp_image_t *mpi; + } buffered_video[NUM_BUFFERED_VIDEO]; + int deint_queue_pos; + int output_surface_width, output_surface_height; + + VdpVideoMixer video_mixer; + struct mp_csp_details colorspace; + int deint; + int deint_type; + int deint_counter; + int pullup; + float denoise; + float sharpen; + int hqscaling; + int chroma_deint; + int flip_offset_window; + int flip_offset_fs; + int top_field_first; + bool flip; + + VdpDecoder decoder; + int decoder_max_refs; + + VdpRect src_rect_vid; + VdpRect out_rect_vid; + struct mp_osd_res osd_rect; + + struct vdpau_render_state surface_render[MAX_VIDEO_SURFACES]; + int surface_num; + int query_surface_num; + VdpTime recent_vsync_time; + float user_fps; + int composite_detect; + unsigned int vsync_interval; + uint64_t last_queue_time; + uint64_t queue_time[MAX_OUTPUT_SURFACES]; + uint64_t last_ideal_time; + bool dropped_frame; + uint64_t dropped_time; + uint32_t vid_width, vid_height; + uint32_t image_format; + VdpChromaType vdp_chroma_type; + VdpYCbCrFormat vdp_pixel_format; + + // OSD + struct osd_bitmap_surface { + VdpRGBAFormat format; + VdpBitmapSurface surface; + uint32_t max_width; + uint32_t max_height; + struct bitmap_packer *packer; + // List of surfaces to be rendered + struct osd_target { + VdpRect source; + VdpRect dest; + VdpColor color; + } *targets; + int targets_size; + int render_count; + int bitmap_id; + int bitmap_pos_id; + } osd_surfaces[MAX_OSD_PARTS]; + + // Video equalizer + struct mp_csp_equalizer video_eq; + + // These tell what's been initialized and uninit() should free/uninitialize + bool mode_switched; +}; + +static bool status_ok(struct vo *vo); + +static int change_vdptime_sync(struct vdpctx *vc, unsigned int *t) +{ + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + VdpTime vdp_time; + vdp_st = vdp->presentation_queue_get_time(vc->flip_queue, &vdp_time); + CHECK_ST_ERROR("Error when calling vdp_presentation_queue_get_time"); + unsigned int t1 = *t; + unsigned int t2 = GetTimer(); + uint64_t old = vc->last_vdp_time + (t1 - vc->last_sync_update) * 1000ULL; + if (vdp_time > old) + if (vdp_time > old + (t2 - t1) * 1000ULL) + vdp_time -= (t2 - t1) * 1000ULL; + else + vdp_time = old; + mp_msg(MSGT_VO, MSGL_DBG2, "[vdpau] adjusting VdpTime offset by %f µs\n", + (int64_t)(vdp_time - old) / 1000.); + vc->last_vdp_time = vdp_time; + vc->last_sync_update = t1; + *t = t2; + return 0; +} + +static uint64_t sync_vdptime(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + + unsigned int t = GetTimer(); + if (t - vc->last_sync_update > 5000000) + change_vdptime_sync(vc, &t); + uint64_t now = (t - vc->last_sync_update) * 1000ULL + vc->last_vdp_time; + // Make sure nanosecond inaccuracies don't make things inconsistent + now = FFMAX(now, vc->recent_vsync_time); + return now; +} + +static uint64_t convert_to_vdptime(struct vo *vo, unsigned int t) +{ + struct vdpctx *vc = vo->priv; + return (int)(t - vc->last_sync_update) * 1000LL + vc->last_vdp_time; +} + +static int render_video_to_output_surface(struct vo *vo, + VdpOutputSurface output_surface, + VdpRect *output_rect) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpTime dummy; + VdpStatus vdp_st; + if (vc->deint_queue_pos < 0) + return -1; + + struct buffered_video_surface *bv = vc->buffered_video; + int field = VDP_VIDEO_MIXER_PICTURE_STRUCTURE_FRAME; + unsigned int dp = vc->deint_queue_pos; + // dp==0 means last field of latest frame, 1 earlier field of latest frame, + // 2 last field of previous frame and so on + if (vc->deint) { + field = vc->top_field_first ^ (dp & 1) ? + VDP_VIDEO_MIXER_PICTURE_STRUCTURE_BOTTOM_FIELD: + VDP_VIDEO_MIXER_PICTURE_STRUCTURE_TOP_FIELD; + } + const VdpVideoSurface *past_fields = (const VdpVideoSurface []){ + bv[(dp+1)/2].surface, bv[(dp+2)/2].surface}; + const VdpVideoSurface *future_fields = (const VdpVideoSurface []){ + dp >= 1 ? bv[(dp-1)/2].surface : VDP_INVALID_HANDLE}; + vdp_st = vdp->presentation_queue_block_until_surface_idle(vc->flip_queue, + output_surface, + &dummy); + CHECK_ST_WARNING("Error when calling " + "vdp_presentation_queue_block_until_surface_idle"); + + vdp_st = vdp->video_mixer_render(vc->video_mixer, VDP_INVALID_HANDLE, + 0, field, 2, past_fields, + bv[dp/2].surface, 1, future_fields, + &vc->src_rect_vid, output_surface, + NULL, output_rect, 0, NULL); + CHECK_ST_WARNING("Error when calling vdp_video_mixer_render"); + return 0; +} + +static int video_to_output_surface(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + + return render_video_to_output_surface(vo, + vc->output_surfaces[vc->surface_num], + &vc->out_rect_vid); +} + +static int next_deint_queue_pos(struct vo *vo, bool eof) +{ + struct vdpctx *vc = vo->priv; + + int dqp = vc->deint_queue_pos; + if (dqp < 0) + dqp += 1000; + else + dqp = vc->deint >= 2 ? dqp - 1 : dqp - 2 | 1; + if (dqp < (eof ? 0 : 3)) + return -1; + return dqp; +} + +static void set_next_frame_info(struct vo *vo, bool eof) +{ + struct vdpctx *vc = vo->priv; + + vo->frame_loaded = false; + int dqp = next_deint_queue_pos(vo, eof); + if (dqp < 0) + return; + vo->frame_loaded = true; + + // Set pts values + struct buffered_video_surface *bv = vc->buffered_video; + int idx = dqp >> 1; + if (idx == 0) { // no future frame/pts available + vo->next_pts = bv[0].pts; + vo->next_pts2 = MP_NOPTS_VALUE; + } else if (!(vc->deint >= 2)) { // no field-splitting deinterlace + vo->next_pts = bv[idx].pts; + vo->next_pts2 = bv[idx - 1].pts; + } else { // deinterlace with separate fields + double intermediate_pts; + double diff = bv[idx - 1].pts - bv[idx].pts; + if (diff > 0 && diff < 0.5) + intermediate_pts = (bv[idx].pts + bv[idx - 1].pts) / 2; + else + intermediate_pts = bv[idx].pts; + if (dqp & 1) { // first field + vo->next_pts = bv[idx].pts; + vo->next_pts2 = intermediate_pts; + } else { + vo->next_pts = intermediate_pts; + vo->next_pts2 = bv[idx - 1].pts; + } + } +} + +static void add_new_video_surface(struct vo *vo, VdpVideoSurface surface, + struct mp_image *reserved_mpi, double pts) +{ + struct vdpctx *vc = vo->priv; + struct buffered_video_surface *bv = vc->buffered_video; + + if (reserved_mpi) + reserved_mpi->usage_count++; + if (bv[NUM_BUFFERED_VIDEO - 1].mpi) + bv[NUM_BUFFERED_VIDEO - 1].mpi->usage_count--; + + for (int i = NUM_BUFFERED_VIDEO - 1; i > 0; i--) + bv[i] = bv[i - 1]; + bv[0] = (struct buffered_video_surface){ + .mpi = reserved_mpi, + .surface = surface, + .pts = pts, + }; + + vc->deint_queue_pos = FFMIN(vc->deint_queue_pos + 2, + NUM_BUFFERED_VIDEO * 2 - 3); + set_next_frame_info(vo, false); +} + +static void forget_frames(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + + vc->deint_queue_pos = -1001; + vc->dropped_frame = false; + for (int i = 0; i < NUM_BUFFERED_VIDEO; i++) { + struct buffered_video_surface *p = vc->buffered_video + i; + if (p->mpi) + p->mpi->usage_count--; + *p = (struct buffered_video_surface){ + .surface = VDP_INVALID_HANDLE, + }; + } +} + +static void resize(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + struct mp_rect src_rect; + struct mp_rect dst_rect; + vo_get_src_dst_rects(vo, &src_rect, &dst_rect, &vc->osd_rect); + vc->out_rect_vid.x0 = dst_rect.x0; + vc->out_rect_vid.x1 = dst_rect.x1; + vc->out_rect_vid.y0 = dst_rect.y0; + vc->out_rect_vid.y1 = dst_rect.y1; + vc->src_rect_vid.x0 = src_rect.x0; + vc->src_rect_vid.x1 = src_rect.x1; + vc->src_rect_vid.y0 = vc->flip ? src_rect.y1 : src_rect.y0; + vc->src_rect_vid.y1 = vc->flip ? src_rect.y0 : src_rect.y1; + + int flip_offset_ms = vo_fs ? vc->flip_offset_fs : vc->flip_offset_window; + vo->flip_queue_offset = flip_offset_ms / 1000.; + + if (vc->output_surface_width < vo->dwidth + || vc->output_surface_height < vo->dheight) { + if (vc->output_surface_width < vo->dwidth) { + vc->output_surface_width += vc->output_surface_width >> 1; + vc->output_surface_width = FFMAX(vc->output_surface_width, + vo->dwidth); + } + if (vc->output_surface_height < vo->dheight) { + vc->output_surface_height += vc->output_surface_height >> 1; + vc->output_surface_height = FFMAX(vc->output_surface_height, + vo->dheight); + } + // Creation of output_surfaces + for (int i = 0; i < vc->num_output_surfaces; i++) + if (vc->output_surfaces[i] != VDP_INVALID_HANDLE) { + vdp_st = vdp->output_surface_destroy(vc->output_surfaces[i]); + CHECK_ST_WARNING("Error when calling " + "vdp_output_surface_destroy"); + } + for (int i = 0; i < vc->num_output_surfaces; i++) { + vdp_st = vdp->output_surface_create(vc->vdp_device, + OUTPUT_RGBA_FORMAT, + vc->output_surface_width, + vc->output_surface_height, + &vc->output_surfaces[i]); + CHECK_ST_WARNING("Error when calling vdp_output_surface_create"); + mp_msg(MSGT_VO, MSGL_DBG2, "vdpau out create: %u\n", + vc->output_surfaces[i]); + } + } + vo->want_redraw = true; +} + +static void preemption_callback(VdpDevice device, void *context) +{ + struct vdpctx *vc = context; + vc->is_preempted = true; + vc->preemption_acked = false; +} + +/* Initialize vdp_get_proc_address, called from preinit() */ +static int win_x11_init_vdpau_procs(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + struct vdpctx *vc = vo->priv; + if (vc->vdp) // reinitialization after preemption + memset(vc->vdp, 0, sizeof(*vc->vdp)); + else + vc->vdp = talloc_zero(vc, struct vdp_functions); + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + + struct vdp_function { + const int id; + int offset; + }; + + const struct vdp_function *dsc; + + static const struct vdp_function vdp_func[] = { +#define VDP_FUNCTION(_, macro_name, mp_name) {macro_name, offsetof(struct vdp_functions, mp_name)}, +#include "vdpau_template.c" +#undef VDP_FUNCTION + {0, -1} + }; + + vdp_st = vdp_device_create_x11(x11->display, x11->screen, &vc->vdp_device, + &vc->vdp_get_proc_address); + if (vdp_st != VDP_STATUS_OK) { + if (vc->is_preempted) + mp_msg(MSGT_VO, MSGL_DBG2, "[vdpau] Error calling " + "vdp_device_create_x11 while preempted: %d\n", vdp_st); + else + mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Error when calling " + "vdp_device_create_x11: %d\n", vdp_st); + return -1; + } + + vdp->get_error_string = NULL; + for (dsc = vdp_func; dsc->offset >= 0; dsc++) { + vdp_st = vc->vdp_get_proc_address(vc->vdp_device, dsc->id, + (void **)((char *)vdp + dsc->offset)); + if (vdp_st != VDP_STATUS_OK) { + mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Error when calling " + "vdp_get_proc_address(function id %d): %s\n", dsc->id, + vdp->get_error_string ? vdp->get_error_string(vdp_st) : "?"); + return -1; + } + } + vdp_st = vdp->preemption_callback_register(vc->vdp_device, + preemption_callback, vc); + return 0; +} + +static int win_x11_init_vdpau_flip_queue(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + struct vo_x11_state *x11 = vo->x11; + VdpStatus vdp_st; + + if (vc->flip_target == VDP_INVALID_HANDLE) { + vdp_st = vdp->presentation_queue_target_create_x11(vc->vdp_device, + x11->window, + &vc->flip_target); + CHECK_ST_ERROR("Error when calling " + "vdp_presentation_queue_target_create_x11"); + } + + /* Emperically this seems to be the first call which fails when we + * try to reinit after preemption while the user is still switched + * from X to a virtual terminal (creating the vdp_device initially + * succeeds, as does creating the flip_target above). This is + * probably not guaranteed behavior, but we'll assume it as a simple + * way to reduce warnings while trying to recover from preemption. + */ + if (vc->flip_queue == VDP_INVALID_HANDLE) { + vdp_st = vdp->presentation_queue_create(vc->vdp_device, vc->flip_target, + &vc->flip_queue); + if (vc->is_preempted && vdp_st != VDP_STATUS_OK) { + mp_msg(MSGT_VO, MSGL_DBG2, "[vdpau] Failed to create flip queue " + "while preempted: %s\n", vdp->get_error_string(vdp_st)); + return -1; + } else + CHECK_ST_ERROR("Error when calling vdp_presentation_queue_create"); + } + + VdpTime vdp_time; + vdp_st = vdp->presentation_queue_get_time(vc->flip_queue, &vdp_time); + CHECK_ST_ERROR("Error when calling vdp_presentation_queue_get_time"); + vc->last_vdp_time = vdp_time; + vc->last_sync_update = GetTimer(); + + vc->vsync_interval = 1; + if (vc->composite_detect && vo_x11_screen_is_composited(vo)) { + mp_msg(MSGT_VO, MSGL_INFO, "[vdpau] Compositing window manager " + "detected. Assuming timing info is inaccurate.\n"); + } else if (vc->user_fps > 0) { + vc->vsync_interval = 1e9 / vc->user_fps; + mp_msg(MSGT_VO, MSGL_INFO, "[vdpau] Assuming user-specified display " + "refresh rate of %.3f Hz.\n", vc->user_fps); + } else if (vc->user_fps == 0) { +#ifdef CONFIG_XF86VM + double fps = vo_vm_get_fps(vo); + if (!fps) + mp_msg(MSGT_VO, MSGL_WARN, "[vdpau] Failed to get display FPS\n"); + else { + vc->vsync_interval = 1e9 / fps; + // This is verbose, but I'm not yet sure how common wrong values are + mp_msg(MSGT_VO, MSGL_INFO, + "[vdpau] Got display refresh rate %.3f Hz.\n" + "[vdpau] If that value looks wrong give the " + "-vo vdpau:fps=X suboption manually.\n", fps); + } +#else + mp_msg(MSGT_VO, MSGL_INFO, "[vdpau] This binary has been compiled " + "without XF86VidMode support.\n"); + mp_msg(MSGT_VO, MSGL_INFO, "[vdpau] Can't use vsync-aware timing " + "without manually provided -vo vdpau:fps=X suboption.\n"); +#endif + } else + mp_msg(MSGT_VO, MSGL_V, "[vdpau] framedrop/timing logic disabled by " + "user.\n"); + + return 0; +} + +static int set_video_attribute(struct vdpctx *vc, VdpVideoMixerAttribute attr, + const void *value, char *attr_name) +{ + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + + vdp_st = vdp->video_mixer_set_attribute_values(vc->video_mixer, 1, &attr, + &value); + if (vdp_st != VDP_STATUS_OK) { + mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Error setting video mixer " + "attribute %s: %s\n", attr_name, vdp->get_error_string(vdp_st)); + return -1; + } + return 0; +} + +static void update_csc_matrix(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + + mp_msg(MSGT_VO, MSGL_V, "[vdpau] Updating CSC matrix\n"); + + // VdpCSCMatrix happens to be compatible with mplayer's CSC matrix type + // both are float[3][4] + VdpCSCMatrix matrix; + + struct mp_csp_params cparams = { + .colorspace = vc->colorspace, .input_bits = 8, .texture_bits = 8 }; + mp_csp_copy_equalizer_values(&cparams, &vc->video_eq); + mp_get_yuv2rgb_coeffs(&cparams, matrix); + + set_video_attribute(vc, VDP_VIDEO_MIXER_ATTRIBUTE_CSC_MATRIX, + &matrix, "CSC matrix"); +} + +#define SET_VIDEO_ATTR(attr_name, attr_type, value) set_video_attribute(vc, \ + VDP_VIDEO_MIXER_ATTRIBUTE_ ## attr_name, &(attr_type){value},\ + # attr_name) +static int create_vdp_mixer(struct vo *vo, VdpChromaType vdp_chroma_type) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; +#define VDP_NUM_MIXER_PARAMETER 3 +#define MAX_NUM_FEATURES 6 + int i; + VdpStatus vdp_st; + + if (vc->video_mixer != VDP_INVALID_HANDLE) + return 0; + + int feature_count = 0; + VdpVideoMixerFeature features[MAX_NUM_FEATURES]; + VdpBool feature_enables[MAX_NUM_FEATURES]; + static const VdpVideoMixerParameter parameters[VDP_NUM_MIXER_PARAMETER] = { + VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_WIDTH, + VDP_VIDEO_MIXER_PARAMETER_VIDEO_SURFACE_HEIGHT, + VDP_VIDEO_MIXER_PARAMETER_CHROMA_TYPE, + }; + const void *const parameter_values[VDP_NUM_MIXER_PARAMETER] = { + &vc->vid_width, + &vc->vid_height, + &vdp_chroma_type, + }; + features[feature_count++] = VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL; + if (vc->deint_type == 4) + features[feature_count++] = + VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL; + if (vc->pullup) + features[feature_count++] = VDP_VIDEO_MIXER_FEATURE_INVERSE_TELECINE; + if (vc->denoise) + features[feature_count++] = VDP_VIDEO_MIXER_FEATURE_NOISE_REDUCTION; + if (vc->sharpen) + features[feature_count++] = VDP_VIDEO_MIXER_FEATURE_SHARPNESS; + if (vc->hqscaling) { + VdpVideoMixerFeature hqscaling_feature = + VDP_VIDEO_MIXER_FEATURE_HIGH_QUALITY_SCALING_L1 + vc->hqscaling-1; + VdpBool hqscaling_available; + vdp_st = vdp->video_mixer_query_feature_support(vc->vdp_device, + hqscaling_feature, + &hqscaling_available); + CHECK_ST_ERROR("Error when calling video_mixer_query_feature_support"); + if (hqscaling_available) + features[feature_count++] = hqscaling_feature; + else + mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Your hardware or VDPAU " + "library does not support requested hqscaling.\n"); + } + + vdp_st = vdp->video_mixer_create(vc->vdp_device, feature_count, features, + VDP_NUM_MIXER_PARAMETER, + parameters, parameter_values, + &vc->video_mixer); + CHECK_ST_ERROR("Error when calling vdp_video_mixer_create"); + + for (i = 0; i < feature_count; i++) + feature_enables[i] = VDP_TRUE; + if (vc->deint < 3) + feature_enables[0] = VDP_FALSE; + if (vc->deint_type == 4 && vc->deint < 4) + feature_enables[1] = VDP_FALSE; + if (feature_count) { + vdp_st = vdp->video_mixer_set_feature_enables(vc->video_mixer, + feature_count, features, + feature_enables); + CHECK_ST_WARNING("Error calling vdp_video_mixer_set_feature_enables"); + } + if (vc->denoise) + SET_VIDEO_ATTR(NOISE_REDUCTION_LEVEL, float, vc->denoise); + if (vc->sharpen) + SET_VIDEO_ATTR(SHARPNESS_LEVEL, float, vc->sharpen); + if (!vc->chroma_deint) + SET_VIDEO_ATTR(SKIP_CHROMA_DEINTERLACE, uint8_t, 1); + + update_csc_matrix(vo); + return 0; +} + +// Free everything specific to a certain video file +static void free_video_specific(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + int i; + VdpStatus vdp_st; + + if (vc->decoder != VDP_INVALID_HANDLE) + vdp->decoder_destroy(vc->decoder); + vc->decoder = VDP_INVALID_HANDLE; + vc->decoder_max_refs = -1; + + forget_frames(vo); + + for (i = 0; i < MAX_VIDEO_SURFACES; i++) { + if (vc->surface_render[i].surface != VDP_INVALID_HANDLE) { + vdp_st = vdp->video_surface_destroy(vc->surface_render[i].surface); + CHECK_ST_WARNING("Error when calling vdp_video_surface_destroy"); + } + vc->surface_render[i].surface = VDP_INVALID_HANDLE; + } + + if (vc->video_mixer != VDP_INVALID_HANDLE) { + vdp_st = vdp->video_mixer_destroy(vc->video_mixer); + CHECK_ST_WARNING("Error when calling vdp_video_mixer_destroy"); + } + vc->video_mixer = VDP_INVALID_HANDLE; + + if (vc->screenshot_surface != VDP_INVALID_HANDLE) { + vdp_st = vdp->output_surface_destroy(vc->screenshot_surface); + CHECK_ST_WARNING("Error when calling vdp_output_surface_destroy"); + } + vc->screenshot_surface = VDP_INVALID_HANDLE; +} + +static int create_vdp_decoder(struct vo *vo, int max_refs) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + VdpDecoderProfile vdp_decoder_profile; + if (vc->decoder != VDP_INVALID_HANDLE) + vdp->decoder_destroy(vc->decoder); + switch (vc->image_format) { + case IMGFMT_VDPAU_MPEG1: + vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG1; + break; + case IMGFMT_VDPAU_MPEG2: + vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG2_MAIN; + break; + case IMGFMT_VDPAU_H264: + vdp_decoder_profile = VDP_DECODER_PROFILE_H264_HIGH; + mp_msg(MSGT_VO, MSGL_V, "[vdpau] Creating H264 hardware decoder " + "for %d reference frames.\n", max_refs); + break; + case IMGFMT_VDPAU_WMV3: + vdp_decoder_profile = VDP_DECODER_PROFILE_VC1_MAIN; + break; + case IMGFMT_VDPAU_VC1: + vdp_decoder_profile = VDP_DECODER_PROFILE_VC1_ADVANCED; + break; + case IMGFMT_VDPAU_MPEG4: + vdp_decoder_profile = VDP_DECODER_PROFILE_MPEG4_PART2_ASP; + break; + default: + mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] Unknown image format!\n"); + goto fail; + } + vdp_st = vdp->decoder_create(vc->vdp_device, vdp_decoder_profile, + vc->vid_width, vc->vid_height, max_refs, + &vc->decoder); + CHECK_ST_WARNING("Failed creating VDPAU decoder"); + if (vdp_st != VDP_STATUS_OK) { + fail: + vc->decoder = VDP_INVALID_HANDLE; + vc->decoder_max_refs = 0; + return 0; + } + vc->decoder_max_refs = max_refs; + return 1; +} + +static int initialize_vdpau_objects(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + + vc->vdp_chroma_type = VDP_CHROMA_TYPE_420; + switch (vc->image_format) { + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + vc->vdp_pixel_format = VDP_YCBCR_FORMAT_YV12; + break; + case IMGFMT_NV12: + vc->vdp_pixel_format = VDP_YCBCR_FORMAT_NV12; + break; + case IMGFMT_YUY2: + vc->vdp_pixel_format = VDP_YCBCR_FORMAT_YUYV; + vc->vdp_chroma_type = VDP_CHROMA_TYPE_422; + break; + case IMGFMT_UYVY: + vc->vdp_pixel_format = VDP_YCBCR_FORMAT_UYVY; + vc->vdp_chroma_type = VDP_CHROMA_TYPE_422; + } + if (win_x11_init_vdpau_flip_queue(vo) < 0) + return -1; + + if (create_vdp_mixer(vo, vc->vdp_chroma_type) < 0) + return -1; + + forget_frames(vo); + resize(vo); + return 0; +} + +static void mark_vdpau_objects_uninitialized(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + + vc->decoder = VDP_INVALID_HANDLE; + for (int i = 0; i < MAX_VIDEO_SURFACES; i++) + vc->surface_render[i].surface = VDP_INVALID_HANDLE; + forget_frames(vo); + vc->video_mixer = VDP_INVALID_HANDLE; + vc->flip_queue = VDP_INVALID_HANDLE; + vc->flip_target = VDP_INVALID_HANDLE; + for (int i = 0; i < MAX_OUTPUT_SURFACES; i++) + vc->output_surfaces[i] = VDP_INVALID_HANDLE; + vc->screenshot_surface = VDP_INVALID_HANDLE; + vc->vdp_device = VDP_INVALID_HANDLE; + for (int i = 0; i < MAX_OSD_PARTS; i++) { + struct osd_bitmap_surface *sfc = &vc->osd_surfaces[i]; + talloc_free(sfc->packer); + sfc->bitmap_id = sfc->bitmap_pos_id = 0; + *sfc = (struct osd_bitmap_surface){ + .surface = VDP_INVALID_HANDLE, + }; + } + vc->output_surface_width = vc->output_surface_height = -1; +} + +static int handle_preemption(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + + if (!vc->is_preempted) + return 0; + if (!vc->preemption_acked) + mark_vdpau_objects_uninitialized(vo); + vc->preemption_acked = true; + if (!vc->preemption_user_notified) { + mp_tmsg(MSGT_VO, MSGL_ERR, "[vdpau] Got display preemption notice! " + "Will attempt to recover.\n"); + vc->preemption_user_notified = true; + } + /* Trying to initialize seems to be quite slow, so only try once a + * second to avoid using 100% CPU. */ + if (vc->last_preemption_retry_fail + && GetTimerMS() - vc->last_preemption_retry_fail < 1000) + return -1; + if (win_x11_init_vdpau_procs(vo) < 0 || initialize_vdpau_objects(vo) < 0) { + vc->last_preemption_retry_fail = GetTimerMS() | 1; + return -1; + } + vc->last_preemption_retry_fail = 0; + vc->is_preempted = false; + vc->preemption_user_notified = false; + mp_tmsg(MSGT_VO, MSGL_INFO, "[vdpau] Recovered from display preemption.\n"); + return 1; +} + +/* + * connect to X server, create and map window, initialize all + * VDPAU objects, create different surfaces etc. + */ +static int config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t flags, + uint32_t format) +{ + struct vdpctx *vc = vo->priv; + struct vo_x11_state *x11 = vo->x11; + XVisualInfo vinfo; + XSetWindowAttributes xswa; + XWindowAttributes attribs; + unsigned long xswamask; + int depth; + +#ifdef CONFIG_XF86VM + int vm = flags & VOFLAG_MODESWITCHING; +#endif + + if (handle_preemption(vo) < 0) + return -1; + + vc->flip = flags & VOFLAG_FLIPPING; + vc->image_format = format; + vc->vid_width = width; + vc->vid_height = height; + + free_video_specific(vo); + if (IMGFMT_IS_VDPAU(vc->image_format) && !create_vdp_decoder(vo, 2)) + return -1; + +#ifdef CONFIG_XF86VM + if (vm) { + vo_vm_switch(vo); + vc->mode_switched = true; + } +#endif + XGetWindowAttributes(x11->display, DefaultRootWindow(x11->display), + &attribs); + depth = attribs.depth; + if (depth != 15 && depth != 16 && depth != 24 && depth != 32) + depth = 24; + XMatchVisualInfo(x11->display, x11->screen, depth, TrueColor, &vinfo); + + xswa.background_pixel = 0; + xswa.border_pixel = 0; + /* Do not use CWBackPixel: It leads to VDPAU errors after + * aspect ratio changes. */ + xswamask = CWBorderPixel; + + vo_x11_create_vo_window(vo, &vinfo, vo->dx, vo->dy, d_width, d_height, + flags, CopyFromParent, "vdpau"); + XChangeWindowAttributes(x11->display, x11->window, xswamask, &xswa); + +#ifdef CONFIG_XF86VM + if (vm) { + /* Grab the mouse pointer in our window */ + if (vo_grabpointer) + XGrabPointer(x11->display, x11->window, True, 0, + GrabModeAsync, GrabModeAsync, + x11->window, None, CurrentTime); + XSetInputFocus(x11->display, x11->window, RevertToNone, CurrentTime); + } +#endif + + if (initialize_vdpau_objects(vo) < 0) + return -1; + + return 0; +} + +static void check_events(struct vo *vo) +{ + if (handle_preemption(vo) < 0) + return; + + int e = vo_x11_check_events(vo); + + if (e & VO_EVENT_RESIZE) + resize(vo); + else if (e & VO_EVENT_EXPOSE) { + vo->want_redraw = true; + } +} + +static struct bitmap_packer *make_packer(struct vo *vo, VdpRGBAFormat format) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + + struct bitmap_packer *packer = talloc_zero(vo, struct bitmap_packer); + uint32_t w_max = 0, h_max = 0; + VdpStatus vdp_st = vdp-> + bitmap_surface_query_capabilities(vc->vdp_device, format, + &(VdpBool){0}, &w_max, &h_max); + CHECK_ST_WARNING("Query to get max OSD surface size failed"); + packer->w_max = w_max; + packer->h_max = h_max; + return packer; +} + +static void draw_osd_part(struct vo *vo, int index) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + struct osd_bitmap_surface *sfc = &vc->osd_surfaces[index]; + VdpOutputSurface output_surface = vc->output_surfaces[vc->surface_num]; + int i; + + VdpOutputSurfaceRenderBlendState blend_state = { + .struct_version = VDP_OUTPUT_SURFACE_RENDER_BLEND_STATE_VERSION, + .blend_factor_source_color = + VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_SRC_ALPHA, + .blend_factor_source_alpha = + VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE, + .blend_factor_destination_color = + VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .blend_factor_destination_alpha = + VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_SRC_ALPHA, + .blend_equation_color = VDP_OUTPUT_SURFACE_RENDER_BLEND_EQUATION_ADD, + .blend_equation_alpha = VDP_OUTPUT_SURFACE_RENDER_BLEND_EQUATION_ADD, + }; + + VdpOutputSurfaceRenderBlendState blend_state_premultiplied = blend_state; + blend_state_premultiplied.blend_factor_source_color = + VDP_OUTPUT_SURFACE_RENDER_BLEND_FACTOR_ONE; + + for (i = 0; i < sfc->render_count; i++) { + VdpOutputSurfaceRenderBlendState *blend = &blend_state; + if (sfc->format == VDP_RGBA_FORMAT_B8G8R8A8) + blend = &blend_state_premultiplied; + vdp_st = vdp-> + output_surface_render_bitmap_surface(output_surface, + &sfc->targets[i].dest, + sfc->surface, + &sfc->targets[i].source, + &sfc->targets[i].color, + blend, + VDP_OUTPUT_SURFACE_RENDER_ROTATE_0); + CHECK_ST_WARNING("OSD: Error when rendering"); + } +} + +static void generate_osd_part(struct vo *vo, struct sub_bitmaps *imgs) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + struct osd_bitmap_surface *sfc = &vc->osd_surfaces[imgs->render_index]; + bool need_upload = false; + + if (imgs->bitmap_pos_id == sfc->bitmap_pos_id) + return; // Nothing changed and we still have the old data + + sfc->render_count = 0; + + if (imgs->format == SUBBITMAP_EMPTY || imgs->num_parts == 0) + return; + + if (imgs->bitmap_id == sfc->bitmap_id) + goto osd_skip_upload; + + need_upload = true; + VdpRGBAFormat format; + int format_size; + switch (imgs->format) { + case SUBBITMAP_LIBASS: + format = VDP_RGBA_FORMAT_A8; + format_size = 1; + break; + case SUBBITMAP_RGBA: + format = VDP_RGBA_FORMAT_B8G8R8A8; + format_size = 4; + break; + default: + abort(); + }; + if (sfc->format != format) { + talloc_free(sfc->packer); + sfc->packer = NULL; + }; + sfc->format = format; + if (!sfc->packer) + sfc->packer = make_packer(vo, format); + sfc->packer->padding = imgs->scaled; // assume 2x2 filter on scaling + int r = packer_pack_from_subbitmaps(sfc->packer, imgs); + if (r < 0) { + mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] OSD bitmaps do not fit on " + "a surface with the maximum supported size\n"); + return; + } else if (r == 1) { + if (sfc->surface != VDP_INVALID_HANDLE) { + vdp_st = vdp->bitmap_surface_destroy(sfc->surface); + CHECK_ST_WARNING("Error when calling vdp_bitmap_surface_destroy"); + } + mp_msg(MSGT_VO, MSGL_V, "[vdpau] Allocating a %dx%d surface for " + "OSD bitmaps.\n", sfc->packer->w, sfc->packer->h); + vdp_st = vdp->bitmap_surface_create(vc->vdp_device, format, + sfc->packer->w, sfc->packer->h, + true, &sfc->surface); + if (vdp_st != VDP_STATUS_OK) + sfc->surface = VDP_INVALID_HANDLE; + CHECK_ST_WARNING("OSD: error when creating surface"); + } + if (imgs->scaled) { + char zeros[sfc->packer->used_width * format_size]; + memset(zeros, 0, sizeof(zeros)); + vdp_st = vdp->bitmap_surface_put_bits_native(sfc->surface, + &(const void *){zeros}, &(uint32_t){0}, + &(VdpRect){0, 0, sfc->packer->used_width, + sfc->packer->used_height}); + } + +osd_skip_upload: + if (sfc->surface == VDP_INVALID_HANDLE) + return; + if (sfc->packer->count > sfc->targets_size) { + talloc_free(sfc->targets); + sfc->targets_size = sfc->packer->count; + sfc->targets = talloc_size(vc, sfc->targets_size + * sizeof(*sfc->targets)); + } + + for (int i = 0 ;i < sfc->packer->count; i++) { + struct sub_bitmap *b = &imgs->parts[i]; + struct osd_target *target = sfc->targets + sfc->render_count; + int x = sfc->packer->result[i].x; + int y = sfc->packer->result[i].y; + target->source = (VdpRect){x, y, x + b->w, y + b->h}; + target->dest = (VdpRect){b->x, b->y, b->x + b->dw, b->y + b->dh}; + target->color = (VdpColor){1, 1, 1, 1}; + if (imgs->format == SUBBITMAP_LIBASS) { + uint32_t color = b->libass.color; + target->color.alpha = 1.0 - ((color >> 0) & 0xff) / 255.0; + target->color.blue = ((color >> 8) & 0xff) / 255.0; + target->color.green = ((color >> 16) & 0xff) / 255.0; + target->color.red = ((color >> 24) & 0xff) / 255.0; + } + if (need_upload) { + vdp_st = vdp-> + bitmap_surface_put_bits_native(sfc->surface, + &(const void *){b->bitmap}, + &(uint32_t){b->stride}, + &target->source); + CHECK_ST_WARNING("OSD: putbits failed"); + } + sfc->render_count++; + } + + sfc->bitmap_id = imgs->bitmap_id; + sfc->bitmap_pos_id = imgs->bitmap_pos_id; +} + +static void draw_osd_cb(void *ctx, struct sub_bitmaps *imgs) +{ + struct vo *vo = ctx; + generate_osd_part(vo, imgs); + draw_osd_part(vo, imgs->render_index); +} + +static void draw_osd(struct vo *vo, struct osd_state *osd) +{ + struct vdpctx *vc = vo->priv; + + if (!status_ok(vo)) + return; + + static const bool formats[SUBBITMAP_COUNT] = { + [SUBBITMAP_LIBASS] = true, + [SUBBITMAP_RGBA] = true, + }; + + osd_draw(osd, vc->osd_rect, osd->vo_pts, 0, formats, draw_osd_cb, vo); +} + +static int update_presentation_queue_status(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + + while (vc->query_surface_num != vc->surface_num) { + VdpTime vtime; + VdpPresentationQueueStatus status; + VdpOutputSurface surface = vc->output_surfaces[vc->query_surface_num]; + vdp_st = vdp->presentation_queue_query_surface_status(vc->flip_queue, + surface, + &status, &vtime); + CHECK_ST_WARNING("Error calling " + "presentation_queue_query_surface_status"); + if (status == VDP_PRESENTATION_QUEUE_STATUS_QUEUED) + break; + if (vc->vsync_interval > 1) { + uint64_t qtime = vc->queue_time[vc->query_surface_num]; + if (vtime < qtime + vc->vsync_interval / 2) + mp_msg(MSGT_VO, MSGL_V, "[vdpau] Frame shown too early\n"); + if (vtime > qtime + vc->vsync_interval) + mp_msg(MSGT_VO, MSGL_V, "[vdpau] Frame shown late\n"); + } + vc->query_surface_num = WRAP_ADD(vc->query_surface_num, 1, + vc->num_output_surfaces); + vc->recent_vsync_time = vtime; + } + int num_queued = WRAP_ADD(vc->surface_num, -vc->query_surface_num, + vc->num_output_surfaces); + mp_msg(MSGT_VO, MSGL_DBG3, "[vdpau] Queued surface count (before add): " + "%d\n", num_queued); + return num_queued; +} + +static inline uint64_t prev_vs2(struct vdpctx *vc, uint64_t ts, int shift) +{ + uint64_t offset = ts - vc->recent_vsync_time; + // Fix negative values for 1<<shift vsyncs before vc->recent_vsync_time + offset += (uint64_t)vc->vsync_interval << shift; + offset %= vc->vsync_interval; + return ts - offset; +} + +static void flip_page_timed(struct vo *vo, unsigned int pts_us, int duration) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + uint32_t vsync_interval = vc->vsync_interval; + + if (handle_preemption(vo) < 0) + return; + + if (duration > INT_MAX / 1000) + duration = -1; + else + duration *= 1000; + + if (vc->vsync_interval == 1) + duration = -1; // Make sure drop logic is disabled + + uint64_t now = sync_vdptime(vo); + uint64_t pts = pts_us ? convert_to_vdptime(vo, pts_us) : now; + uint64_t ideal_pts = pts; + uint64_t npts = duration >= 0 ? pts + duration : UINT64_MAX; + +#define PREV_VS2(ts, shift) prev_vs2(vc, ts, shift) + // Only gives accurate results for ts >= vc->recent_vsync_time +#define PREV_VSYNC(ts) PREV_VS2(ts, 0) + + /* We hope to be here at least one vsync before the frame should be shown. + * If we are running late then don't drop the frame unless there is + * already one queued for the next vsync; even if we _hope_ to show the + * next frame soon enough to mean this one should be dropped we might + * not make the target time in reality. Without this check we could drop + * every frame, freezing the display completely if video lags behind. + */ + if (now > PREV_VSYNC(FFMAX(pts, vc->last_queue_time + vsync_interval))) + npts = UINT64_MAX; + + /* Allow flipping a frame at a vsync if its presentation time is a + * bit after that vsync and the change makes the flip time delta + * from previous frame better match the target timestamp delta. + * This avoids instability with frame timestamps falling near vsyncs. + * For example if the frame timestamps were (with vsyncs at + * integer values) 0.01, 1.99, 4.01, 5.99, 8.01, ... then + * straightforward timing at next vsync would flip the frames at + * 1, 2, 5, 6, 9; this changes it to 1, 2, 4, 6, 8 and so on with + * regular 2-vsync intervals. + * + * Also allow moving the frame forward if it looks like we dropped + * the previous frame incorrectly (now that we know better after + * having final exact timestamp information for this frame) and + * there would unnecessarily be a vsync without a frame change. + */ + uint64_t vsync = PREV_VSYNC(pts); + if (pts < vsync + vsync_interval / 4 + && (vsync - PREV_VS2(vc->last_queue_time, 16) + > pts - vc->last_ideal_time + vsync_interval / 2 + || vc->dropped_frame && vsync > vc->dropped_time)) + pts -= vsync_interval / 2; + + vc->dropped_frame = true; // changed at end if false + vc->dropped_time = ideal_pts; + + pts = FFMAX(pts, vc->last_queue_time + vsync_interval); + pts = FFMAX(pts, now); + if (npts < PREV_VSYNC(pts) + vsync_interval) + return; + + int num_flips = update_presentation_queue_status(vo); + vsync = vc->recent_vsync_time + num_flips * vc->vsync_interval; + now = sync_vdptime(vo); + pts = FFMAX(pts, now); + pts = FFMAX(pts, vsync + (vsync_interval >> 2)); + vsync = PREV_VSYNC(pts); + if (npts < vsync + vsync_interval) + return; + pts = vsync + (vsync_interval >> 2); + vdp_st = + vdp->presentation_queue_display(vc->flip_queue, + vc->output_surfaces[vc->surface_num], + vo->dwidth, vo->dheight, pts); + CHECK_ST_WARNING("Error when calling vdp_presentation_queue_display"); + + vc->last_queue_time = pts; + vc->queue_time[vc->surface_num] = pts; + vc->last_ideal_time = ideal_pts; + vc->dropped_frame = false; + vc->surface_num = WRAP_ADD(vc->surface_num, 1, vc->num_output_surfaces); +} + +static int draw_slice(struct vo *vo, uint8_t *image[], int stride[], int w, + int h, int x, int y) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + VdpStatus vdp_st; + + if (handle_preemption(vo) < 0) + return VO_TRUE; + + struct vdpau_render_state *rndr = (struct vdpau_render_state *)image[0]; + int max_refs = vc->image_format == IMGFMT_VDPAU_H264 ? + rndr->info.h264.num_ref_frames : 2; + if (!IMGFMT_IS_VDPAU(vc->image_format)) + return VO_FALSE; + if ((vc->decoder == VDP_INVALID_HANDLE || vc->decoder_max_refs < max_refs) + && !create_vdp_decoder(vo, max_refs)) + return VO_FALSE; + + vdp_st = vdp->decoder_render(vc->decoder, rndr->surface, + (void *)&rndr->info, + rndr->bitstream_buffers_used, + rndr->bitstream_buffers); + CHECK_ST_WARNING("Failed VDPAU decoder rendering"); + return VO_TRUE; +} + + +static struct vdpau_render_state *get_surface(struct vo *vo, int number) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + + if (number >= MAX_VIDEO_SURFACES) + return NULL; + if (vc->surface_render[number].surface == VDP_INVALID_HANDLE + && !vc->is_preempted) { + VdpStatus vdp_st; + vdp_st = vdp->video_surface_create(vc->vdp_device, vc->vdp_chroma_type, + vc->vid_width, vc->vid_height, + &vc->surface_render[number].surface); + CHECK_ST_WARNING("Error when calling vdp_video_surface_create"); + } + mp_msg(MSGT_VO, MSGL_DBG3, "vdpau vid create: %u\n", + vc->surface_render[number].surface); + return &vc->surface_render[number]; +} + +static void draw_image(struct vo *vo, mp_image_t *mpi, double pts) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + struct mp_image *reserved_mpi = NULL; + struct vdpau_render_state *rndr; + + if (IMGFMT_IS_VDPAU(vc->image_format)) { + rndr = mpi->priv; + reserved_mpi = mpi; + } else if (!(mpi->flags & MP_IMGFLAG_DRAW_CALLBACK)) { + rndr = get_surface(vo, vc->deint_counter); + vc->deint_counter = WRAP_ADD(vc->deint_counter, 1, NUM_BUFFERED_VIDEO); + if (handle_preemption(vo) >= 0) { + VdpStatus vdp_st; + const void *destdata[3] = {mpi->planes[0], mpi->planes[2], + mpi->planes[1]}; + if (vc->image_format == IMGFMT_NV12) + destdata[1] = destdata[2]; + vdp_st = vdp->video_surface_put_bits_y_cb_cr(rndr->surface, + vc->vdp_pixel_format, destdata, mpi->stride); + CHECK_ST_WARNING("Error when calling " + "vdp_video_surface_put_bits_y_cb_cr"); + } + } else + // We don't support slice callbacks so this shouldn't occur - + // I think the flags test above in pointless, but I'm adding + // this instead of removing it just in case. + abort(); + if (mpi->fields & MP_IMGFIELD_ORDERED) + vc->top_field_first = !!(mpi->fields & MP_IMGFIELD_TOP_FIRST); + else + vc->top_field_first = 1; + + add_new_video_surface(vo, rndr->surface, reserved_mpi, pts); + + return; +} + +// warning: the size and pixel format of surface must match that of the +// surfaces in vc->output_surfaces +static struct mp_image *read_output_surface(struct vdpctx *vc, + VdpOutputSurface surface, + int width, int height) +{ + VdpStatus vdp_st; + struct vdp_functions *vdp = vc->vdp; + struct mp_image *image = alloc_mpi(width, height, IMGFMT_BGR32); + image->colorspace = MP_CSP_RGB; + image->levels = vc->colorspace.levels_out; // hardcoded with conv. matrix + + void *dst_planes[] = { image->planes[0] }; + uint32_t dst_pitches[] = { image->stride[0] }; + vdp_st = vdp->output_surface_get_bits_native(surface, NULL, dst_planes, + dst_pitches); + CHECK_ST_WARNING("Error when calling vdp_output_surface_get_bits_native"); + + return image; +} + +static struct mp_image *get_screenshot(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + VdpStatus vdp_st; + struct vdp_functions *vdp = vc->vdp; + + if (vc->screenshot_surface == VDP_INVALID_HANDLE) { + vdp_st = vdp->output_surface_create(vc->vdp_device, + OUTPUT_RGBA_FORMAT, + vc->vid_width, vc->vid_height, + &vc->screenshot_surface); + CHECK_ST_WARNING("Error when calling vdp_output_surface_create"); + } + + VdpRect rc = { .x1 = vc->vid_width, .y1 = vc->vid_height }; + render_video_to_output_surface(vo, vc->screenshot_surface, &rc); + + struct mp_image *image = read_output_surface(vc, vc->screenshot_surface, + vc->vid_width, vc->vid_height); + + image->display_w = vo->aspdat.prew; + image->display_h = vo->aspdat.preh; + + return image; +} + +static struct mp_image *get_window_screenshot(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + int last_surface = WRAP_ADD(vc->surface_num, -1, vc->num_output_surfaces); + VdpOutputSurface screen = vc->output_surfaces[last_surface]; + struct mp_image *image = read_output_surface(vo->priv, screen, + vc->output_surface_width, + vc->output_surface_height); + image->width = image->w = vo->dwidth; + image->height = image->h = vo->dheight; + return image; +} + +static uint32_t get_image(struct vo *vo, mp_image_t *mpi) +{ + struct vdpctx *vc = vo->priv; + struct vdpau_render_state *rndr; + + // no dr for non-decoding for now + if (!IMGFMT_IS_VDPAU(vc->image_format)) + return VO_FALSE; + if (mpi->type != MP_IMGTYPE_NUMBERED) + return VO_FALSE; + + rndr = get_surface(vo, mpi->number); + if (!rndr) { + mp_msg(MSGT_VO, MSGL_ERR, "[vdpau] no surfaces available in " + "get_image\n"); + // TODO: this probably breaks things forever, provide a dummy buffer? + return VO_FALSE; + } + mpi->flags |= MP_IMGFLAG_DIRECT; + mpi->stride[0] = mpi->stride[1] = mpi->stride[2] = 0; + mpi->planes[0] = mpi->planes[1] = mpi->planes[2] = NULL; + // hack to get around a check and to avoid a special-case in vd_ffmpeg.c + mpi->planes[0] = (void *)rndr; + mpi->num_planes = 1; + mpi->priv = rndr; + return VO_TRUE; +} + +static int query_format(uint32_t format) +{ + int default_flags = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW + | VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | VFCAP_OSD + | VFCAP_FLIP; + switch (format) { + case IMGFMT_YV12: + case IMGFMT_I420: + case IMGFMT_IYUV: + case IMGFMT_NV12: + case IMGFMT_YUY2: + case IMGFMT_UYVY: + return default_flags | VOCAP_NOSLICES; + case IMGFMT_VDPAU_MPEG1: + case IMGFMT_VDPAU_MPEG2: + case IMGFMT_VDPAU_H264: + case IMGFMT_VDPAU_WMV3: + case IMGFMT_VDPAU_VC1: + case IMGFMT_VDPAU_MPEG4: + return default_flags; + } + return 0; +} + +static void destroy_vdpau_objects(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + + int i; + VdpStatus vdp_st; + + free_video_specific(vo); + + if (vc->flip_queue != VDP_INVALID_HANDLE) { + vdp_st = vdp->presentation_queue_destroy(vc->flip_queue); + CHECK_ST_WARNING("Error when calling vdp_presentation_queue_destroy"); + } + + if (vc->flip_target != VDP_INVALID_HANDLE) { + vdp_st = vdp->presentation_queue_target_destroy(vc->flip_target); + CHECK_ST_WARNING("Error when calling " + "vdp_presentation_queue_target_destroy"); + } + + for (i = 0; i < vc->num_output_surfaces; i++) { + if (vc->output_surfaces[i] == VDP_INVALID_HANDLE) + continue; + vdp_st = vdp->output_surface_destroy(vc->output_surfaces[i]); + CHECK_ST_WARNING("Error when calling vdp_output_surface_destroy"); + } + + for (int i = 0; i < MAX_OSD_PARTS; i++) { + struct osd_bitmap_surface *sfc = &vc->osd_surfaces[i]; + if (sfc->surface != VDP_INVALID_HANDLE) { + vdp_st = vdp->bitmap_surface_destroy(sfc->surface); + CHECK_ST_WARNING("Error when calling vdp_bitmap_surface_destroy"); + } + } + + vdp_st = vdp->device_destroy(vc->vdp_device); + CHECK_ST_WARNING("Error when calling vdp_device_destroy"); +} + +static void uninit(struct vo *vo) +{ + struct vdpctx *vc = vo->priv; + + /* Destroy all vdpau objects */ + destroy_vdpau_objects(vo); + +#ifdef CONFIG_XF86VM + if (vc->mode_switched) + vo_vm_close(vo); +#endif + vo_x11_uninit(vo); + + // Free bitstream buffers allocated by FFmpeg + for (int i = 0; i < MAX_VIDEO_SURFACES; i++) + av_freep(&vc->surface_render[i].bitstream_buffers); +} + +static int preinit(struct vo *vo, const char *arg) +{ + struct vdpctx *vc = vo->priv; + + // Mark everything as invalid first so uninit() can tell what has been + // allocated + mark_vdpau_objects_uninitialized(vo); + + vc->colorspace = (struct mp_csp_details) MP_CSP_DETAILS_DEFAULTS; + vc->video_eq.capabilities = MP_CSP_EQ_CAPS_COLORMATRIX; + + vc->deint_type = vc->deint ? FFABS(vc->deint) : 3; + if (vc->deint < 0) + vc->deint = 0; + + if (!vo_init(vo)) + return -1; + + // After this calling uninit() should work to free resources + + if (win_x11_init_vdpau_procs(vo) < 0) { + if (vc->vdp && vc->vdp->device_destroy) + vc->vdp->device_destroy(vc->vdp_device); + vo_x11_uninit(vo); + return -1; + } + + return 0; +} + +static int get_equalizer(struct vo *vo, const char *name, int *value) +{ + struct vdpctx *vc = vo->priv; + return mp_csp_equalizer_get(&vc->video_eq, name, value) >= 0 ? + VO_TRUE : VO_NOTIMPL; +} + +static bool status_ok(struct vo *vo) +{ + if (!vo->config_ok || handle_preemption(vo) < 0) + return false; + return true; +} + +static int set_equalizer(struct vo *vo, const char *name, int value) +{ + struct vdpctx *vc = vo->priv; + + if (mp_csp_equalizer_set(&vc->video_eq, name, value) < 0) + return VO_NOTIMPL; + + if (status_ok(vo)) + update_csc_matrix(vo); + return true; +} + +static void checked_resize(struct vo *vo) +{ + if (!status_ok(vo)) + return; + resize(vo); +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + struct vdpctx *vc = vo->priv; + struct vdp_functions *vdp = vc->vdp; + + handle_preemption(vo); + + switch (request) { + case VOCTRL_GET_DEINTERLACE: + *(int *)data = vc->deint; + return VO_TRUE; + case VOCTRL_SET_DEINTERLACE: + vc->deint = *(int *)data; + if (vc->deint) + vc->deint = vc->deint_type; + if (vc->deint_type > 2 && status_ok(vo)) { + VdpStatus vdp_st; + VdpVideoMixerFeature features[1] = + {vc->deint_type == 3 ? + VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL : + VDP_VIDEO_MIXER_FEATURE_DEINTERLACE_TEMPORAL_SPATIAL}; + VdpBool feature_enables[1] = {vc->deint ? VDP_TRUE : VDP_FALSE}; + vdp_st = vdp->video_mixer_set_feature_enables(vc->video_mixer, + 1, features, + feature_enables); + CHECK_ST_WARNING("Error changing deinterlacing settings"); + } + vo->want_redraw = true; + return VO_TRUE; + case VOCTRL_PAUSE: + if (vc->dropped_frame) + vo->want_redraw = true; + return true; + case VOCTRL_QUERY_FORMAT: + return query_format(*(uint32_t *)data); + case VOCTRL_GET_IMAGE: + return get_image(vo, data); + case VOCTRL_DRAW_IMAGE: + abort(); // draw_image() should get called directly + case VOCTRL_BORDER: + vo_x11_border(vo); + checked_resize(vo); + return VO_TRUE; + case VOCTRL_FULLSCREEN: + vo_x11_fullscreen(vo); + checked_resize(vo); + return VO_TRUE; + case VOCTRL_GET_PANSCAN: + return VO_TRUE; + case VOCTRL_SET_PANSCAN: + checked_resize(vo); + return VO_TRUE; + case VOCTRL_SET_EQUALIZER: { + vo->want_redraw = true; + struct voctrl_set_equalizer_args *args = data; + return set_equalizer(vo, args->name, args->value); + } + case VOCTRL_GET_EQUALIZER: { + struct voctrl_get_equalizer_args *args = data; + return get_equalizer(vo, args->name, args->valueptr); + } + case VOCTRL_SET_YUV_COLORSPACE: + vc->colorspace = *(struct mp_csp_details *)data; + if (status_ok(vo)) + update_csc_matrix(vo); + vo->want_redraw = true; + return true; + case VOCTRL_GET_YUV_COLORSPACE: + *(struct mp_csp_details *)data = vc->colorspace; + return true; + case VOCTRL_ONTOP: + vo_x11_ontop(vo); + return VO_TRUE; + case VOCTRL_UPDATE_SCREENINFO: + update_xinerama_info(vo); + return VO_TRUE; + case VOCTRL_NEWFRAME: + vc->deint_queue_pos = next_deint_queue_pos(vo, true); + if (status_ok(vo)) + video_to_output_surface(vo); + return true; + case VOCTRL_SKIPFRAME: + vc->deint_queue_pos = next_deint_queue_pos(vo, true); + return true; + case VOCTRL_REDRAW_FRAME: + if (status_ok(vo)) + video_to_output_surface(vo); + return true; + case VOCTRL_RESET: + forget_frames(vo); + return true; + case VOCTRL_SCREENSHOT: { + if (!status_ok(vo)) + return false; + struct voctrl_screenshot_args *args = data; + if (args->full_window) + args->out_image = get_window_screenshot(vo); + else + args->out_image = get_screenshot(vo); + return true; + } + } + return VO_NOTIMPL; +} + +#undef OPT_BASE_STRUCT +#define OPT_BASE_STRUCT struct vdpctx + +const struct vo_driver video_out_vdpau = { + .is_new = true, + .buffer_frames = true, + .info = &(const struct vo_info_s){ + "VDPAU with X11", + "vdpau", + "Rajib Mahapatra <rmahapatra@nvidia.com> and others", + "" + }, + .preinit = preinit, + .config = config, + .control = control, + .draw_image = draw_image, + .get_buffered_frame = set_next_frame_info, + .draw_slice = draw_slice, + .draw_osd = draw_osd, + .flip_page_timed = flip_page_timed, + .check_events = check_events, + .uninit = uninit, + .priv_size = sizeof(struct vdpctx), + .options = (const struct m_option []){ + OPT_INTRANGE("deint", deint, 0, -4, 4), + OPT_FLAG_ON("chroma-deint", chroma_deint, 0, OPTDEF_INT(1)), + OPT_FLAG_OFF("nochroma-deint", chroma_deint, 0), + OPT_MAKE_FLAGS("pullup", pullup, 0), + OPT_FLOATRANGE("denoise", denoise, 0, 0, 1), + OPT_FLOATRANGE("sharpen", sharpen, 0, -1, 1), + OPT_INTRANGE("hqscaling", hqscaling, 0, 0, 9), + OPT_FLOAT("fps", user_fps, 0), + OPT_FLAG_ON("composite-detect", composite_detect, 0, OPTDEF_INT(1)), + OPT_INT("queuetime_windowed", flip_offset_window, 0, OPTDEF_INT(50)), + OPT_INT("queuetime_fs", flip_offset_fs, 0, OPTDEF_INT(50)), + OPT_INTRANGE("output_surfaces", num_output_surfaces, 0, + 2, MAX_OUTPUT_SURFACES, OPTDEF_INT(3)), + {NULL}, + } +}; diff --git a/video/out/vo_x11.c b/video/out/vo_x11.c new file mode 100644 index 0000000000..2358b0a295 --- /dev/null +++ b/video/out/vo_x11.c @@ -0,0 +1,620 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "config.h" +#include "video_out.h" +#include "aspect.h" +#include "csputils.h" +#include "libmpcodecs/mp_image.h" +#include "libmpcodecs/vfcap.h" + +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include <errno.h> + +#include "x11_common.h" + +#ifdef HAVE_SHM +#include <sys/ipc.h> +#include <sys/shm.h> +#include <X11/extensions/XShm.h> +#endif + +#include "sub/sub.h" + +#include "libmpcodecs/sws_utils.h" +#define MODE_RGB 0x1 +#define MODE_BGR 0x2 + +#include "mp_msg.h" + +extern int sws_flags; + +struct priv { + struct vo *vo; + + /* local data */ + unsigned char *ImageData; + //! original unaligned pointer for free + unsigned char *ImageDataOrig; + + /* X11 related variables */ + XImage *myximage; + int depth, bpp; + XWindowAttributes attribs; + + int int_pause; + + int Flip_Flag; + int zoomFlag; + + uint32_t image_width; + uint32_t image_height; + uint32_t in_format; + uint32_t out_format; + int out_offset; + int srcW; + int srcH; + + int old_vo_dwidth; + int old_vo_dheight; + + struct SwsContext *swsContext; + int dst_width; + + XVisualInfo vinfo; + + int firstTime; + +#ifdef HAVE_SHM + int Shmem_Flag; + + XShmSegmentInfo Shminfo[1]; + int gXErrorFlag; + int CompletionType; +#endif +}; + +static void flip_page(struct vo *vo); + +static void check_events(struct vo *vo) +{ + struct priv *p = vo->priv; + + int ret = vo_x11_check_events(vo); + + if (ret & VO_EVENT_RESIZE) + vo_x11_clearwindow(vo, vo->x11->window); + else if (ret & VO_EVENT_EXPOSE) + vo_x11_clearwindow_part(vo, vo->x11->window, p->myximage->width, + p->myximage->height); + if (ret & VO_EVENT_EXPOSE && p->int_pause) + flip_page(vo); +} + +static void getMyXImage(struct priv *p) +{ + struct vo *vo = p->vo; +#ifdef HAVE_SHM + if (vo->x11->display_is_local && XShmQueryExtension(vo->x11->display)) + p->Shmem_Flag = 1; + else { + p->Shmem_Flag = 0; + mp_msg(MSGT_VO, MSGL_WARN, + "Shared memory not supported\nReverting to normal Xlib\n"); + } + if (p->Shmem_Flag) + p->CompletionType = XShmGetEventBase(vo->x11->display) + ShmCompletion; + + if (p->Shmem_Flag) { + p->myximage = + XShmCreateImage(vo->x11->display, p->vinfo.visual, p->depth, + ZPixmap, NULL, &p->Shminfo[0], p->image_width, + p->image_height); + if (p->myximage == NULL) { + mp_msg(MSGT_VO, MSGL_WARN, + "Shared memory error,disabling ( Ximage error )\n"); + goto shmemerror; + } + p->Shminfo[0].shmid = shmget(IPC_PRIVATE, + p->myximage->bytes_per_line * + p->myximage->height, + IPC_CREAT | 0777); + if (p->Shminfo[0].shmid < 0) { + XDestroyImage(p->myximage); + mp_msg(MSGT_VO, MSGL_V, "%s\n", strerror(errno)); + //perror( strerror( errno ) ); + mp_msg(MSGT_VO, MSGL_WARN, + "Shared memory error,disabling ( seg id error )\n"); + goto shmemerror; + } + p->Shminfo[0].shmaddr = (char *) shmat(p->Shminfo[0].shmid, 0, 0); + + if (p->Shminfo[0].shmaddr == ((char *) -1)) { + XDestroyImage(p->myximage); + if (p->Shminfo[0].shmaddr != ((char *) -1)) + shmdt(p->Shminfo[0].shmaddr); + mp_msg(MSGT_VO, MSGL_WARN, + "Shared memory error,disabling ( address error )\n"); + goto shmemerror; + } + p->myximage->data = p->Shminfo[0].shmaddr; + p->ImageData = (unsigned char *) p->myximage->data; + p->Shminfo[0].readOnly = False; + XShmAttach(vo->x11->display, &p->Shminfo[0]); + + XSync(vo->x11->display, False); + + if (p->gXErrorFlag) { + XDestroyImage(p->myximage); + shmdt(p->Shminfo[0].shmaddr); + mp_msg(MSGT_VO, MSGL_WARN, "Shared memory error,disabling.\n"); + p->gXErrorFlag = 0; + goto shmemerror; + } else + shmctl(p->Shminfo[0].shmid, IPC_RMID, 0); + + if (!p->firstTime) { + mp_msg(MSGT_VO, MSGL_V, "Sharing memory.\n"); + p->firstTime = 1; + } + } else { +shmemerror: + p->Shmem_Flag = 0; +#endif + p->myximage = + XCreateImage(vo->x11->display, p->vinfo.visual, p->depth, ZPixmap, + 0, NULL, p->image_width, p->image_height, 8, 0); + p->ImageDataOrig = + malloc(p->myximage->bytes_per_line * p->image_height + 32); + p->myximage->data = p->ImageDataOrig + 16 - ((long)p->ImageDataOrig & 15); + memset(p->myximage->data, 0, p->myximage->bytes_per_line * p->image_height); + p->ImageData = p->myximage->data; +#ifdef HAVE_SHM +} +#endif +} + +static void freeMyXImage(struct priv *p) +{ + struct vo *vo = p->vo; +#ifdef HAVE_SHM + if (p->Shmem_Flag) { + XShmDetach(vo->x11->display, &p->Shminfo[0]); + XDestroyImage(p->myximage); + shmdt(p->Shminfo[0].shmaddr); + } else +#endif + { + p->myximage->data = p->ImageDataOrig; + XDestroyImage(p->myximage); + p->ImageDataOrig = NULL; + } + p->myximage = NULL; + p->ImageData = NULL; +} + +#if BYTE_ORDER == BIG_ENDIAN +#define BO_NATIVE MSBFirst +#define BO_NONNATIVE LSBFirst +#else +#define BO_NATIVE LSBFirst +#define BO_NONNATIVE MSBFirst +#endif +const struct fmt2Xfmtentry_s { + uint32_t mpfmt; + int byte_order; + unsigned red_mask; + unsigned green_mask; + unsigned blue_mask; +} fmt2Xfmt[] = { + {IMGFMT_RGB8, BO_NATIVE, 0x00000007, 0x00000038, 0x000000C0}, + {IMGFMT_RGB8, BO_NONNATIVE, 0x00000007, 0x00000038, 0x000000C0}, + {IMGFMT_BGR8, BO_NATIVE, 0x000000E0, 0x0000001C, 0x00000003}, + {IMGFMT_BGR8, BO_NONNATIVE, 0x000000E0, 0x0000001C, 0x00000003}, + {IMGFMT_RGB15, BO_NATIVE, 0x0000001F, 0x000003E0, 0x00007C00}, + {IMGFMT_BGR15, BO_NATIVE, 0x00007C00, 0x000003E0, 0x0000001F}, + {IMGFMT_RGB16, BO_NATIVE, 0x0000001F, 0x000007E0, 0x0000F800}, + {IMGFMT_BGR16, BO_NATIVE, 0x0000F800, 0x000007E0, 0x0000001F}, + {IMGFMT_RGB24, MSBFirst, 0x00FF0000, 0x0000FF00, 0x000000FF}, + {IMGFMT_RGB24, LSBFirst, 0x000000FF, 0x0000FF00, 0x00FF0000}, + {IMGFMT_BGR24, MSBFirst, 0x000000FF, 0x0000FF00, 0x00FF0000}, + {IMGFMT_BGR24, LSBFirst, 0x00FF0000, 0x0000FF00, 0x000000FF}, + {IMGFMT_RGB32, BO_NATIVE, 0x000000FF, 0x0000FF00, 0x00FF0000}, + {IMGFMT_RGB32, BO_NONNATIVE, 0xFF000000, 0x00FF0000, 0x0000FF00}, + {IMGFMT_BGR32, BO_NATIVE, 0x00FF0000, 0x0000FF00, 0x000000FF}, + {IMGFMT_BGR32, BO_NONNATIVE, 0x0000FF00, 0x00FF0000, 0xFF000000}, + {IMGFMT_ARGB, MSBFirst, 0x00FF0000, 0x0000FF00, 0x000000FF}, + {IMGFMT_ARGB, LSBFirst, 0x0000FF00, 0x00FF0000, 0xFF000000}, + {IMGFMT_ABGR, MSBFirst, 0x000000FF, 0x0000FF00, 0x00FF0000}, + {IMGFMT_ABGR, LSBFirst, 0xFF000000, 0x00FF0000, 0x0000FF00}, + {IMGFMT_RGBA, MSBFirst, 0xFF000000, 0x00FF0000, 0x0000FF00}, + {IMGFMT_RGBA, LSBFirst, 0x000000FF, 0x0000FF00, 0x00FF0000}, + {IMGFMT_BGRA, MSBFirst, 0x0000FF00, 0x00FF0000, 0xFF000000}, + {IMGFMT_BGRA, LSBFirst, 0x00FF0000, 0x0000FF00, 0x000000FF}, + {0} +}; + +static int config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t flags, + uint32_t format) +{ + struct priv *p = vo->priv; + + Colormap theCmap; + const struct fmt2Xfmtentry_s *fmte = fmt2Xfmt; + +#ifdef CONFIG_XF86VM + int vm = flags & VOFLAG_MODESWITCHING; +#endif + p->Flip_Flag = flags & VOFLAG_FLIPPING; + p->zoomFlag = flags & VOFLAG_SWSCALE; + + p->old_vo_dwidth = -1; + p->old_vo_dheight = -1; + + p->in_format = format; + p->srcW = width; + p->srcH = height; + + XGetWindowAttributes(vo->x11->display, vo->x11->rootwin, &p->attribs); + p->depth = p->attribs.depth; + + if (p->depth != 15 && p->depth != 16 && p->depth != 24 && p->depth != 32) { + Visual *visual; + + p->depth = vo_find_depth_from_visuals(vo->x11->display, vo->x11->screen, + &visual); + } + if (!XMatchVisualInfo(vo->x11->display, vo->x11->screen, p->depth, + DirectColor, &p->vinfo) + || (WinID > 0 + && p->vinfo.visualid != XVisualIDFromVisual(p->attribs.visual))) + { + XMatchVisualInfo(vo->x11->display, vo->x11->screen, p->depth, TrueColor, + &p->vinfo); + } + + /* set image size (which is indeed neither the input nor output size), + if zoom is on it will be changed during draw_slice anyway so we don't + duplicate the aspect code here + */ + p->image_width = (width + 7) & (~7); + p->image_height = height; + + { +#ifdef CONFIG_XF86VM + if (vm) + vo_vm_switch(vo); + +#endif + theCmap = vo_x11_create_colormap(vo, &p->vinfo); + + vo_x11_create_vo_window(vo, &p->vinfo, vo->dx, vo->dy, vo->dwidth, + vo->dheight, flags, theCmap, "x11"); + if (WinID > 0) + p->depth = vo_x11_update_geometry(vo, true); + +#ifdef CONFIG_XF86VM + if (vm) { + /* Grab the mouse pointer in our window */ + if (vo_grabpointer) + XGrabPointer(vo->x11->display, vo->x11->window, True, 0, + GrabModeAsync, GrabModeAsync, + vo->x11->window, None, CurrentTime); + XSetInputFocus(vo->x11->display, vo->x11->window, RevertToNone, + CurrentTime); + } +#endif + } + + if (p->myximage) { + freeMyXImage(p); + sws_freeContext(p->swsContext); + } + getMyXImage(p); + + while (fmte->mpfmt) { + int depth = IMGFMT_RGB_DEPTH(fmte->mpfmt); + /* bits_per_pixel in X seems to be set to 16 for 15 bit formats + => force depth to 16 so that only the color masks are used for the format check */ + if (depth == 15) + depth = 16; + + if (depth == p->myximage->bits_per_pixel && + fmte->byte_order == p->myximage->byte_order && + fmte->red_mask == p->myximage->red_mask && + fmte->green_mask == p->myximage->green_mask && + fmte->blue_mask == p->myximage->blue_mask) + break; + fmte++; + } + if (!fmte->mpfmt) { + mp_msg( + MSGT_VO, MSGL_ERR, + "X server image format not supported, please contact the developers\n"); + return -1; + } + p->out_format = fmte->mpfmt; + p->bpp = p->myximage->bits_per_pixel; + p->out_offset = 0; + // We can easily "emulate" non-native RGB32 and BGR32 + if (p->out_format == (IMGFMT_BGR32 | 128) + || p->out_format == (IMGFMT_RGB32 | 128)) + { + p->out_format &= ~128; +#if BYTE_ORDER == BIG_ENDIAN + p->out_offset = 1; +#else + p->out_offset = -1; +#endif + } + + /* always allocate swsContext as size could change between frames */ + p->swsContext = sws_getContextFromCmdLine(width, height, p->in_format, + width, height, p->out_format); + if (!p->swsContext) + return -1; + + p->dst_width = width; + + return 0; +} + +static void Display_Image(struct priv *p, XImage *myximage, uint8_t *ImageData) +{ + struct vo *vo = p->vo; + + int x = (vo->dwidth - p->dst_width) / 2; + int y = (vo->dheight - p->myximage->height) / 2; + + // do not draw if the image needs rescaling + if ((p->old_vo_dwidth != vo->dwidth || + p->old_vo_dheight != vo->dheight) && p->zoomFlag) + return; + + if (WinID == 0) { + x = vo->dx; + y = vo->dy; + } + p->myximage->data += p->out_offset; +#ifdef HAVE_SHM + if (p->Shmem_Flag) { + XShmPutImage(vo->x11->display, vo->x11->window, vo->x11->vo_gc, + p->myximage, 0, 0, x, y, p->dst_width, p->myximage->height, + True); + } else +#endif + { + XPutImage(vo->x11->display, vo->x11->window, vo->x11->vo_gc, + p->myximage, 0, 0, x, y, p->dst_width, p->myximage->height); + } + p->myximage->data -= p->out_offset; +} + +static struct mp_image get_x_buffer(struct priv *p) +{ + struct mp_image img = {0}; + img.w = img.width = p->image_width; + img.h = img.height = p->image_height; + mp_image_setfmt(&img, p->out_format); + + img.planes[0] = p->ImageData; + img.stride[0] = p->image_width * ((p->bpp + 7) / 8); + + return img; +} + +static void draw_osd(struct vo *vo, struct osd_state *osd) +{ + struct priv *p = vo->priv; + + struct mp_image img = get_x_buffer(p); + + struct mp_osd_res res = { + .w = img.w, + .h = img.h, + .display_par = vo->monitor_par, + .video_par = vo->aspdat.par, + }; + + osd_draw_on_image(osd, res, osd->vo_pts, 0, &img); +} + +static void flip_page(struct vo *vo) +{ + struct priv *p = vo->priv; + Display_Image(p, p->myximage, p->ImageData); + XSync(vo->x11->display, False); +} + +static int draw_slice(struct vo *vo, uint8_t *src[], int stride[], int w, int h, + int x, int y) +{ + struct priv *p = vo->priv; + uint8_t *dst[MP_MAX_PLANES] = {NULL}; + int dstStride[MP_MAX_PLANES] = {0}; + + if ((p->old_vo_dwidth != vo->dwidth || p->old_vo_dheight != vo->dheight) + /*&& y==0 */ && p->zoomFlag) + { + int newW = vo->dwidth; + int newH = vo->dheight; + struct SwsContext *oldContext = p->swsContext; + + p->old_vo_dwidth = vo->dwidth; + p->old_vo_dheight = vo->dheight; + + if (vo_fs) + aspect(vo, &newW, &newH, A_ZOOM); + if (sws_flags == 0) + newW &= (~31); // not needed but, if the user wants the FAST_BILINEAR SCALER, then its needed + + p->swsContext + = sws_getContextFromCmdLine(p->srcW, p->srcH, p->in_format, newW, + newH, p->out_format); + if (p->swsContext) { + p->image_width = (newW + 7) & (~7); + p->image_height = newH; + + freeMyXImage(p); + getMyXImage(p); + sws_freeContext(oldContext); + } else + p->swsContext = oldContext; + p->dst_width = newW; + } + + dstStride[0] = p->image_width * ((p->bpp + 7) / 8); + dst[0] = p->ImageData; + if (p->Flip_Flag) { + dst[0] += dstStride[0] * (p->image_height - 1); + dstStride[0] = -dstStride[0]; + } + sws_scale(p->swsContext, (const uint8_t **)src, stride, y, h, dst, + dstStride); + return 0; +} + +static int query_format(struct vo *vo, uint32_t format) +{ + mp_msg(MSGT_VO, MSGL_DBG2, + "vo_x11: query_format was called: %x (%s)\n", format, + vo_format_name(format)); + if (IMGFMT_IS_BGR(format)) { + if (IMGFMT_BGR_DEPTH(format) <= 8) + return 0; // TODO 8bpp not yet fully implemented + if (IMGFMT_BGR_DEPTH(format) == vo->x11->depthonscreen) + return VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | + VFCAP_OSD | VFCAP_SWSCALE | VFCAP_FLIP | + VFCAP_ACCEPT_STRIDE; + else + return VFCAP_CSP_SUPPORTED | VFCAP_OSD | VFCAP_SWSCALE | + VFCAP_FLIP | + VFCAP_ACCEPT_STRIDE; + } + + switch (format) { + case IMGFMT_I420: + case IMGFMT_IYUV: + case IMGFMT_YV12: + return VFCAP_CSP_SUPPORTED | VFCAP_OSD | VFCAP_SWSCALE | + VFCAP_ACCEPT_STRIDE; + } + return 0; +} + + +static void uninit(struct vo *vo) +{ + struct priv *p = vo->priv; + if (p->myximage) + freeMyXImage(p); + +#ifdef CONFIG_XF86VM + vo_vm_close(vo); +#endif + + p->zoomFlag = 0; + vo_x11_uninit(vo); + + sws_freeContext(p->swsContext); +} + +static int preinit(struct vo *vo, const char *arg) +{ + struct priv *p = vo->priv; + p->vo = vo; + + if (arg) { + mp_msg(MSGT_VO, MSGL_ERR, "vo_x11: Unknown subdevice: %s\n", arg); + return ENOSYS; + } + + if (!vo_init(vo)) + return -1; // Can't open X11 + return 0; +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + struct priv *p = vo->priv; + switch (request) { + case VOCTRL_PAUSE: + return p->int_pause = 1; + case VOCTRL_RESUME: + return p->int_pause = 0; + case VOCTRL_QUERY_FORMAT: + return query_format(vo, *((uint32_t *) data)); + case VOCTRL_FULLSCREEN: + vo_x11_fullscreen(vo); + vo_x11_clearwindow(vo, vo->x11->window); + return VO_TRUE; + case VOCTRL_SET_EQUALIZER: + { + struct voctrl_set_equalizer_args *args = data; + return vo_x11_set_equalizer(vo, args->name, args->value); + } + case VOCTRL_GET_EQUALIZER: + { + struct voctrl_get_equalizer_args *args = data; + return vo_x11_get_equalizer(args->name, args->valueptr); + } + case VOCTRL_ONTOP: + vo_x11_ontop(vo); + return VO_TRUE; + case VOCTRL_UPDATE_SCREENINFO: + update_xinerama_info(vo); + return VO_TRUE; + } + return VO_NOTIMPL; +} + +const struct vo_driver video_out_x11 = { + .is_new = false, + .info = &(const vo_info_t) { + "X11 ( XImage/Shm )", + "x11", + "Aaron Holtzman <aholtzma@ess.engr.uvic.ca>", + "" + }, + .priv_size = sizeof(struct priv), + .priv_defaults = &(const struct priv) { + .srcW = -1, + .srcH = -1, + .old_vo_dwidth = -1, + .old_vo_dheight = -1, +#ifdef HAVE_SHM + .CompletionType = -1, +#endif + }, + .preinit = preinit, + .config = config, + .control = control, + .draw_slice = draw_slice, + .draw_osd = draw_osd, + .flip_page = flip_page, + .check_events = check_events, + .uninit = uninit, +}; diff --git a/video/out/vo_xv.c b/video/out/vo_xv.c new file mode 100644 index 0000000000..3673764ed4 --- /dev/null +++ b/video/out/vo_xv.c @@ -0,0 +1,716 @@ +/* + * X11 Xv interface + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <errno.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include <libavutil/common.h> + +#include "config.h" + +#ifdef HAVE_SHM +#include <sys/ipc.h> +#include <sys/shm.h> +#include <X11/extensions/XShm.h> +#endif + +// Note: depends on the inclusion of X11/extensions/XShm.h +#include <X11/extensions/Xv.h> +#include <X11/extensions/Xvlib.h> + +#include "options.h" +#include "talloc.h" +#include "mp_msg.h" +#include "video_out.h" +#include "libmpcodecs/vfcap.h" +#include "libmpcodecs/mp_image.h" +#include "x11_common.h" +#include "fastmemcpy.h" +#include "sub/sub.h" +#include "aspect.h" +#include "csputils.h" +#include "subopt-helper.h" + +static const vo_info_t info = { + "X11/Xv", + "xv", + "Gerd Knorr <kraxel@goldbach.in-berlin.de> and others", + "" +}; + +struct xvctx { + XvAdaptorInfo *ai; + XvImageFormatValues *fo; + unsigned int formats, adaptors, xv_format; + int current_buf; + int current_ip_buf; + int num_buffers; + int total_buffers; + bool have_image_copy; + bool unchanged_image; + int visible_buf; + XvImage *xvimage[2 + 1]; + uint32_t image_width; + uint32_t image_height; + uint32_t image_format; + struct mp_csp_details cached_csp; + int is_paused; + struct mp_rect src_rect; + struct mp_rect dst_rect; + uint32_t max_width, max_height; // zero means: not set + int mode_switched; +#ifdef HAVE_SHM + XShmSegmentInfo Shminfo[2 + 1]; + int Shmem_Flag; +#endif +}; + +static void allocate_xvimage(struct vo *, int); +static void deallocate_xvimage(struct vo *vo, int foo); + +static void read_xv_csp(struct vo *vo) +{ + struct xvctx *ctx = vo->priv; + struct vo_x11_state *x11 = vo->x11; + struct mp_csp_details *cspc = &ctx->cached_csp; + *cspc = (struct mp_csp_details) MP_CSP_DETAILS_DEFAULTS; + int bt709_enabled; + if (vo_xv_get_eq(vo, x11->xv_port, "bt_709", &bt709_enabled)) + cspc->format = bt709_enabled == 100 ? MP_CSP_BT_709 : MP_CSP_BT_601; +} + +static void resize(struct vo *vo) +{ + struct xvctx *ctx = vo->priv; + + // Can't be used, because the function calculates screen-space coordinates, + // while we need video-space. + struct mp_osd_res unused; + + vo_get_src_dst_rects(vo, &ctx->src_rect, &ctx->dst_rect, &unused); + + struct mp_rect *dst = &ctx->dst_rect; + int dw = dst->x1 - dst->x0, dh = dst->y1 - dst->y0; + vo_x11_clearwindow_part(vo, vo->x11->window, dw, dh); + vo_xv_draw_colorkey(vo, dst->x0, dst->y0, dw, dh); + read_xv_csp(vo); +} + +/* + * connect to server, create and map window, + * allocate colors and (shared) memory + */ +static int config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t d_width, uint32_t d_height, uint32_t flags, + uint32_t format) +{ + struct vo_x11_state *x11 = vo->x11; + XVisualInfo vinfo; + XSetWindowAttributes xswa; + XWindowAttributes attribs; + unsigned long xswamask; + int depth; + struct xvctx *ctx = vo->priv; + int i; + + ctx->image_height = height; + ctx->image_width = width; + ctx->image_format = format; + + if ((ctx->max_width != 0 && ctx->max_height != 0) + && (ctx->image_width > ctx->max_width + || ctx->image_height > ctx->max_height)) { + mp_tmsg(MSGT_VO, MSGL_ERR, "Source image dimensions are too high: %ux%u (maximum is %ux%u)\n", + ctx->image_width, ctx->image_height, ctx->max_width, + ctx->max_height); + return -1; + } + + ctx->visible_buf = -1; + ctx->have_image_copy = false; + + /* check image formats */ + ctx->xv_format = 0; + for (i = 0; i < ctx->formats; i++) { + mp_msg(MSGT_VO, MSGL_V, "Xvideo image format: 0x%x (%4.4s) %s\n", + ctx->fo[i].id, (char *) &ctx->fo[i].id, + (ctx->fo[i].format == XvPacked) ? "packed" : "planar"); + if (ctx->fo[i].id == format) + ctx->xv_format = ctx->fo[i].id; + } + if (!ctx->xv_format) + return -1; + + { +#ifdef CONFIG_XF86VM + int vm = flags & VOFLAG_MODESWITCHING; + if (vm) { + vo_vm_switch(vo); + ctx->mode_switched = 1; + } +#endif + XGetWindowAttributes(x11->display, DefaultRootWindow(x11->display), + &attribs); + depth = attribs.depth; + if (depth != 15 && depth != 16 && depth != 24 && depth != 32) + depth = 24; + XMatchVisualInfo(x11->display, x11->screen, depth, TrueColor, &vinfo); + + xswa.border_pixel = 0; + xswamask = CWBorderPixel; + if (x11->xv_ck_info.method == CK_METHOD_BACKGROUND) { + xswa.background_pixel = x11->xv_colorkey; + xswamask |= CWBackPixel; + } + + vo_x11_create_vo_window(vo, &vinfo, vo->dx, vo->dy, vo->dwidth, + vo->dheight, flags, CopyFromParent, "xv"); + XChangeWindowAttributes(x11->display, x11->window, xswamask, &xswa); + +#ifdef CONFIG_XF86VM + if (vm) { + /* Grab the mouse pointer in our window */ + if (vo_grabpointer) + XGrabPointer(x11->display, x11->window, True, 0, GrabModeAsync, + GrabModeAsync, x11->window, None, CurrentTime); + XSetInputFocus(x11->display, x11->window, RevertToNone, + CurrentTime); + } +#endif + } + + mp_msg(MSGT_VO, MSGL_V, "using Xvideo port %d for hw scaling\n", + x11->xv_port); + + // In case config has been called before + for (i = 0; i < ctx->total_buffers; i++) + deallocate_xvimage(vo, i); + + ctx->num_buffers = 2; + ctx->total_buffers = ctx->num_buffers + 1; + + for (i = 0; i < ctx->total_buffers; i++) + allocate_xvimage(vo, i); + + ctx->current_buf = 0; + ctx->current_ip_buf = 0; + + + resize(vo); + + return 0; +} + +static void allocate_xvimage(struct vo *vo, int foo) +{ + struct xvctx *ctx = vo->priv; + struct vo_x11_state *x11 = vo->x11; + /* + * allocate XvImages. FIXME: no error checking, without + * mit-shm this will bomb... trzing to fix ::atmos + */ +#ifdef HAVE_SHM + if (x11->display_is_local && XShmQueryExtension(x11->display)) + ctx->Shmem_Flag = 1; + else { + ctx->Shmem_Flag = 0; + mp_tmsg(MSGT_VO, MSGL_INFO, "[VO_XV] Shared memory not supported\nReverting to normal Xv.\n"); + } + if (ctx->Shmem_Flag) { + ctx->xvimage[foo] = + (XvImage *) XvShmCreateImage(x11->display, x11->xv_port, + ctx->xv_format, NULL, + ctx->image_width, ctx->image_height, + &ctx->Shminfo[foo]); + + ctx->Shminfo[foo].shmid = shmget(IPC_PRIVATE, + ctx->xvimage[foo]->data_size, + IPC_CREAT | 0777); + ctx->Shminfo[foo].shmaddr = (char *) shmat(ctx->Shminfo[foo].shmid, 0, + 0); + ctx->Shminfo[foo].readOnly = False; + + ctx->xvimage[foo]->data = ctx->Shminfo[foo].shmaddr; + XShmAttach(x11->display, &ctx->Shminfo[foo]); + XSync(x11->display, False); + shmctl(ctx->Shminfo[foo].shmid, IPC_RMID, 0); + } else +#endif + { + ctx->xvimage[foo] = + (XvImage *) XvCreateImage(x11->display, x11->xv_port, + ctx->xv_format, NULL, ctx->image_width, + ctx->image_height); + ctx->xvimage[foo]->data = malloc(ctx->xvimage[foo]->data_size); + XSync(x11->display, False); + } + memset(ctx->xvimage[foo]->data, 128, ctx->xvimage[foo]->data_size); + return; +} + +static void deallocate_xvimage(struct vo *vo, int foo) +{ + struct xvctx *ctx = vo->priv; +#ifdef HAVE_SHM + if (ctx->Shmem_Flag) { + XShmDetach(vo->x11->display, &ctx->Shminfo[foo]); + shmdt(ctx->Shminfo[foo].shmaddr); + } else +#endif + { + free(ctx->xvimage[foo]->data); + } + XFree(ctx->xvimage[foo]); + + XSync(vo->x11->display, False); + return; +} + +static inline void put_xvimage(struct vo *vo, XvImage *xvi) +{ + struct xvctx *ctx = vo->priv; + struct vo_x11_state *x11 = vo->x11; + struct mp_rect *src = &ctx->src_rect; + struct mp_rect *dst = &ctx->dst_rect; + int dw = dst->x1 - dst->x0, dh = dst->y1 - dst->y0; + int sw = src->x1 - src->x0, sh = src->y1 - src->y0; +#ifdef HAVE_SHM + if (ctx->Shmem_Flag) { + XvShmPutImage(x11->display, x11->xv_port, x11->window, x11->vo_gc, xvi, + src->x0, src->y0, sw, sh, + dst->x0, dst->y0, dw, dh, + False); + } else +#endif + { + XvPutImage(x11->display, x11->xv_port, x11->window, x11->vo_gc, xvi, + src->x0, src->y0, sw, sh, + dst->x0, dst->y0, dw, dh); + } +} + +static struct mp_image get_xv_buffer(struct vo *vo, int buf_index) +{ + struct xvctx *ctx = vo->priv; + XvImage *xv_image = ctx->xvimage[buf_index]; + + struct mp_image img = {0}; + img.w = img.width = xv_image->width; + img.h = img.height = xv_image->height; + mp_image_setfmt(&img, ctx->image_format); + + bool swapuv = ctx->image_format == IMGFMT_YV12; + for (int n = 0; n < img.num_planes; n++) { + int sn = n > 0 && swapuv ? (n == 1 ? 2 : 1) : n; + img.planes[n] = xv_image->data + xv_image->offsets[sn]; + img.stride[n] = xv_image->pitches[sn]; + } + + mp_image_set_colorspace_details(&img, &ctx->cached_csp); + + return img; +} + +static void copy_backup_image(struct vo *vo, int dest, int src) +{ + struct mp_image img_dest = get_xv_buffer(vo, dest); + struct mp_image img_src = get_xv_buffer(vo, src); + + copy_mpi(&img_dest, &img_src); +} + +static void check_events(struct vo *vo) +{ + int e = vo_x11_check_events(vo); + + if (e & VO_EVENT_EXPOSE || e & VO_EVENT_RESIZE) { + resize(vo); + vo->want_redraw = true; + } +} + +static void draw_osd(struct vo *vo, struct osd_state *osd) +{ + struct xvctx *ctx = vo->priv; + + struct mp_image img = get_xv_buffer(vo, ctx->current_buf); + + struct mp_rect *src = &ctx->src_rect; + struct mp_rect *dst = &ctx->dst_rect; + int dw = dst->x1 - dst->x0, dh = dst->y1 - dst->y0; + int sw = src->x1 - src->x0, sh = src->y1 - src->y0; + double xvpar = (double)dw / dh * sh / sw; + + struct mp_osd_res res = { + .w = ctx->image_width, + .h = ctx->image_height, + .display_par = vo->monitor_par / xvpar, + .video_par = vo->aspdat.par, + }; + + if (osd_draw_on_image(osd, res, osd->vo_pts, 0, &img)) + ctx->unchanged_image = false; +} + +static int redraw_frame(struct vo *vo) +{ + struct xvctx *ctx = vo->priv; + + if (ctx->have_image_copy) + copy_backup_image(vo, ctx->visible_buf, ctx->num_buffers); + else if (ctx->unchanged_image) { + copy_backup_image(vo, ctx->num_buffers, ctx->visible_buf); + ctx->have_image_copy = true; + } else + return false; + ctx->current_buf = ctx->visible_buf; + return true; +} + +static void flip_page(struct vo *vo) +{ + struct xvctx *ctx = vo->priv; + put_xvimage(vo, ctx->xvimage[ctx->current_buf]); + + /* remember the currently visible buffer */ + ctx->visible_buf = ctx->current_buf; + + ctx->current_buf = (ctx->current_buf + 1) % ctx->num_buffers; + XFlush(vo->x11->display); + return; +} + +static int draw_slice(struct vo *vo, uint8_t *image[], int stride[], int w, + int h, int x, int y) +{ + struct xvctx *ctx = vo->priv; + uint8_t *dst; + XvImage *current_image = ctx->xvimage[ctx->current_buf]; + + dst = current_image->data + current_image->offsets[0] + + current_image->pitches[0] * y + x; + memcpy_pic(dst, image[0], w, h, current_image->pitches[0], stride[0]); + + x /= 2; + y /= 2; + w /= 2; + h /= 2; + + dst = current_image->data + current_image->offsets[1] + + current_image->pitches[1] * y + x; + if (ctx->image_format != IMGFMT_YV12) + memcpy_pic(dst, image[1], w, h, current_image->pitches[1], stride[1]); + else + memcpy_pic(dst, image[2], w, h, current_image->pitches[1], stride[2]); + + dst = current_image->data + current_image->offsets[2] + + current_image->pitches[2] * y + x; + if (ctx->image_format == IMGFMT_YV12) + memcpy_pic(dst, image[1], w, h, current_image->pitches[1], stride[1]); + else + memcpy_pic(dst, image[2], w, h, current_image->pitches[1], stride[2]); + + return 0; +} + +static mp_image_t *get_screenshot(struct vo *vo) +{ + struct xvctx *ctx = vo->priv; + + // try to get an image without OSD + int id = ctx->have_image_copy ? ctx->num_buffers : ctx->visible_buf; + struct mp_image img = get_xv_buffer(vo, id); + img.display_w = vo->aspdat.prew; + img.display_h = vo->aspdat.preh; + + return talloc_memdup(NULL, &img, sizeof(img)); +} + +static uint32_t draw_image(struct vo *vo, mp_image_t *mpi) +{ + struct xvctx *ctx = vo->priv; + + ctx->have_image_copy = false; + + if (mpi->flags & MP_IMGFLAG_DRAW_CALLBACK) + ; // done + else if (mpi->flags & MP_IMGFLAG_PLANAR) + draw_slice(vo, mpi->planes, mpi->stride, mpi->w, mpi->h, 0, 0); + else if (mpi->flags & MP_IMGFLAG_YUV) + // packed YUV: + memcpy_pic(ctx->xvimage[ctx->current_buf]->data + + ctx->xvimage[ctx->current_buf]->offsets[0], mpi->planes[0], + mpi->w * (mpi->bpp / 8), mpi->h, + ctx->xvimage[ctx->current_buf]->pitches[0], mpi->stride[0]); + else + return false; + + if (ctx->is_paused) { + copy_backup_image(vo, ctx->num_buffers, ctx->current_buf); + ctx->have_image_copy = true; + } + ctx->unchanged_image = true; + return true; +} + +static int query_format(struct xvctx *ctx, uint32_t format) +{ + uint32_t i; + int flag = VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW | VFCAP_HWSCALE_UP | VFCAP_HWSCALE_DOWN | VFCAP_OSD | VFCAP_ACCEPT_STRIDE; // FIXME! check for DOWN + + /* check image formats */ + for (i = 0; i < ctx->formats; i++) { + if (ctx->fo[i].id == format) + return flag; //xv_format = fo[i].id; + } + return 0; +} + +static void uninit(struct vo *vo) +{ + struct xvctx *ctx = vo->priv; + int i; + + ctx->visible_buf = -1; + if (ctx->ai) + XvFreeAdaptorInfo(ctx->ai); + ctx->ai = NULL; + if (ctx->fo) { + XFree(ctx->fo); + ctx->fo = NULL; + } + for (i = 0; i < ctx->total_buffers; i++) + deallocate_xvimage(vo, i); +#ifdef CONFIG_XF86VM + if (ctx->mode_switched) + vo_vm_close(vo); +#endif + // uninit() shouldn't get called unless initialization went past vo_init() + vo_x11_uninit(vo); +} + +static int preinit(struct vo *vo, const char *arg) +{ + XvPortID xv_p; + int busy_ports = 0; + unsigned int i; + strarg_t ck_src_arg = { 0, NULL }; + strarg_t ck_method_arg = { 0, NULL }; + struct xvctx *ctx = talloc_zero(vo, struct xvctx); + vo->priv = ctx; + int xv_adaptor = -1; + + if (!vo_init(vo)) + return -1; + + struct vo_x11_state *x11 = vo->x11; + + const opt_t subopts[] = + { + /* name arg type arg var test */ + { "port", OPT_ARG_INT, &x11->xv_port, int_pos }, + { "adaptor", OPT_ARG_INT, &xv_adaptor, int_non_neg }, + { "ck", OPT_ARG_STR, &ck_src_arg, xv_test_ck }, + { "ck-method", OPT_ARG_STR, &ck_method_arg, xv_test_ckm }, + { NULL } + }; + + x11->xv_port = 0; + + /* parse suboptions */ + if (subopt_parse(arg, subopts) != 0) { + return -1; + } + + /* modify colorkey settings according to the given options */ + xv_setup_colorkeyhandling(vo, ck_method_arg.str, ck_src_arg.str); + + /* check for Xvideo extension */ + unsigned int ver, rel, req, ev, err; + if (Success != XvQueryExtension(x11->display, &ver, &rel, &req, &ev, &err)) { + mp_tmsg(MSGT_VO, MSGL_ERR, "[VO_XV] Sorry, Xv not supported by this X11 version/driver\n[VO_XV] ******** Try with -vo x11 *********\n"); + goto error; + } + + /* check for Xvideo support */ + if (Success != + XvQueryAdaptors(x11->display, DefaultRootWindow(x11->display), + &ctx->adaptors, &ctx->ai)) { + mp_tmsg(MSGT_VO, MSGL_ERR, "[VO_XV] XvQueryAdaptors failed.\n"); + goto error; + } + + /* check adaptors */ + if (x11->xv_port) { + int port_found; + + for (port_found = 0, i = 0; !port_found && i < ctx->adaptors; i++) { + if ((ctx->ai[i].type & XvInputMask) + && (ctx->ai[i].type & XvImageMask)) { + for (xv_p = ctx->ai[i].base_id; + xv_p < ctx->ai[i].base_id + ctx->ai[i].num_ports; + ++xv_p) { + if (xv_p == x11->xv_port) { + port_found = 1; + break; + } + } + } + } + if (port_found) { + if (XvGrabPort(x11->display, x11->xv_port, CurrentTime)) + x11->xv_port = 0; + } else { + mp_tmsg(MSGT_VO, MSGL_WARN, "[VO_XV] Invalid port parameter, overriding with port 0.\n"); + x11->xv_port = 0; + } + } + + for (i = 0; i < ctx->adaptors && x11->xv_port == 0; i++) { + /* check if adaptor number has been specified */ + if (xv_adaptor != -1 && xv_adaptor != i) + continue; + + if ((ctx->ai[i].type & XvInputMask) && (ctx->ai[i].type & XvImageMask)) { + for (xv_p = ctx->ai[i].base_id; + xv_p < ctx->ai[i].base_id + ctx->ai[i].num_ports; ++xv_p) + if (!XvGrabPort(x11->display, xv_p, CurrentTime)) { + x11->xv_port = xv_p; + mp_msg(MSGT_VO, MSGL_V, + "[VO_XV] Using Xv Adapter #%d (%s)\n", + i, ctx->ai[i].name); + break; + } else { + mp_tmsg(MSGT_VO, MSGL_WARN, "[VO_XV] Could not grab port %i.\n", + (int) xv_p); + ++busy_ports; + } + } + } + if (!x11->xv_port) { + if (busy_ports) + mp_tmsg(MSGT_VO, MSGL_ERR, + "[VO_XV] Could not find free Xvideo port - maybe another process is already\n"\ + "[VO_XV] using it. Close all video applications, and try again. If that does\n"\ + "[VO_XV] not help, see 'mpv -vo help' for other (non-xv) video out drivers.\n"); + else + mp_tmsg(MSGT_VO, MSGL_ERR, + "[VO_XV] It seems there is no Xvideo support for your video card available.\n"\ + "[VO_XV] Run 'xvinfo' to verify its Xv support and read\n"\ + "[VO_XV] DOCS/HTML/en/video.html#xv!\n"\ + "[VO_XV] See 'mpv -vo help' for other (non-xv) video out drivers.\n"\ + "[VO_XV] Try -vo x11.\n"); + goto error; + } + + if (!vo_xv_init_colorkey(vo)) { + goto error; // bail out, colorkey setup failed + } + vo_xv_enable_vsync(vo); + vo_xv_get_max_img_dim(vo, &ctx->max_width, &ctx->max_height); + + ctx->fo = XvListImageFormats(x11->display, x11->xv_port, + (int *) &ctx->formats); + + return 0; + + error: + uninit(vo); // free resources + return -1; +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + struct xvctx *ctx = vo->priv; + struct vo_x11_state *x11 = vo->x11; + switch (request) { + case VOCTRL_PAUSE: + return (ctx->is_paused = 1); + case VOCTRL_RESUME: + return (ctx->is_paused = 0); + case VOCTRL_QUERY_FORMAT: + return query_format(ctx, *((uint32_t *) data)); + case VOCTRL_DRAW_IMAGE: + return draw_image(vo, data); + case VOCTRL_GET_PANSCAN: + return VO_TRUE; + case VOCTRL_FULLSCREEN: + vo_x11_fullscreen(vo); + /* indended, fallthrough to update panscan on fullscreen/windowed switch */ + case VOCTRL_SET_PANSCAN: + resize(vo); + return VO_TRUE; + case VOCTRL_SET_EQUALIZER: { + vo->want_redraw = true; + struct voctrl_set_equalizer_args *args = data; + return vo_xv_set_eq(vo, x11->xv_port, args->name, args->value); + } + case VOCTRL_GET_EQUALIZER: { + struct voctrl_get_equalizer_args *args = data; + return vo_xv_get_eq(vo, x11->xv_port, args->name, args->valueptr); + } + case VOCTRL_SET_YUV_COLORSPACE:; + struct mp_csp_details* given_cspc = data; + int is_709 = given_cspc->format == MP_CSP_BT_709; + vo_xv_set_eq(vo, x11->xv_port, "bt_709", is_709 * 200 - 100); + read_xv_csp(vo); + vo->want_redraw = true; + return true; + case VOCTRL_GET_YUV_COLORSPACE:; + struct mp_csp_details* cspc = data; + read_xv_csp(vo); + *cspc = ctx->cached_csp; + return true; + case VOCTRL_ONTOP: + vo_x11_ontop(vo); + return VO_TRUE; + case VOCTRL_UPDATE_SCREENINFO: + update_xinerama_info(vo); + return VO_TRUE; + case VOCTRL_REDRAW_FRAME: + return redraw_frame(vo); + case VOCTRL_SCREENSHOT: { + struct voctrl_screenshot_args *args = data; + args->out_image = get_screenshot(vo); + args->has_osd = !ctx->have_image_copy; + return true; + } + } + return VO_NOTIMPL; +} + +const struct vo_driver video_out_xv = { + .is_new = 1, + .info = &info, + .preinit = preinit, + .config = config, + .control = control, + .draw_slice = draw_slice, + .draw_osd = draw_osd, + .flip_page = flip_page, + .check_events = check_events, + .uninit = uninit +}; diff --git a/video/out/w32_common.c b/video/out/w32_common.c new file mode 100644 index 0000000000..f60f5328de --- /dev/null +++ b/video/out/w32_common.c @@ -0,0 +1,757 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <limits.h> +#include <assert.h> +#include <windows.h> +#include <windowsx.h> + +#include "options.h" +#include "input/keycodes.h" +#include "input/input.h" +#include "mp_msg.h" +#include "video_out.h" +#include "aspect.h" +#include "w32_common.h" +#include "mp_fifo.h" +#include "osdep/io.h" +#include "talloc.h" + +#define WIN_ID_TO_HWND(x) ((HWND)(uint32_t)(x)) + +static const wchar_t classname[] = L"mpv"; + +static const struct mp_keymap vk_map[] = { + // special keys + {VK_ESCAPE, KEY_ESC}, {VK_BACK, KEY_BS}, {VK_TAB, KEY_TAB}, + {VK_RETURN, KEY_ENTER}, {VK_PAUSE, KEY_PAUSE}, {VK_SNAPSHOT, KEY_PRINT}, + + // cursor keys + {VK_LEFT, KEY_LEFT}, {VK_UP, KEY_UP}, {VK_RIGHT, KEY_RIGHT}, {VK_DOWN, KEY_DOWN}, + + // navigation block + {VK_INSERT, KEY_INSERT}, {VK_DELETE, KEY_DELETE}, {VK_HOME, KEY_HOME}, {VK_END, KEY_END}, + {VK_PRIOR, KEY_PAGE_UP}, {VK_NEXT, KEY_PAGE_DOWN}, + + // F-keys + {VK_F1, KEY_F+1}, {VK_F2, KEY_F+2}, {VK_F3, KEY_F+3}, {VK_F4, KEY_F+4}, + {VK_F5, KEY_F+5}, {VK_F6, KEY_F+6}, {VK_F7, KEY_F+7}, {VK_F8, KEY_F+8}, + {VK_F9, KEY_F+9}, {VK_F10, KEY_F+10}, {VK_F11, KEY_F+11}, {VK_F12, KEY_F+12}, + // numpad + {VK_NUMPAD0, KEY_KP0}, {VK_NUMPAD1, KEY_KP1}, {VK_NUMPAD2, KEY_KP2}, + {VK_NUMPAD3, KEY_KP3}, {VK_NUMPAD4, KEY_KP4}, {VK_NUMPAD5, KEY_KP5}, + {VK_NUMPAD6, KEY_KP6}, {VK_NUMPAD7, KEY_KP7}, {VK_NUMPAD8, KEY_KP8}, + {VK_NUMPAD9, KEY_KP9}, {VK_DECIMAL, KEY_KPDEC}, + + {0, 0} +}; + +static void add_window_borders(HWND hwnd, RECT *rc) +{ + AdjustWindowRect(rc, GetWindowLong(hwnd, GWL_STYLE), 0); +} + +// basically a reverse AdjustWindowRect (win32 doesn't appear to have this) +static void subtract_window_borders(HWND hwnd, RECT *rc) +{ + RECT b = { 0, 0, 0, 0 }; + add_window_borders(hwnd, &b); + rc->left -= b.left; + rc->top -= b.top; + rc->right -= b.right; + rc->bottom -= b.bottom; +} + +// turn a WMSZ_* input value in v into the border that should be resized +// returns: 0=left, 1=top, 2=right, 3=bottom, -1=undefined +static int get_resize_border(int v) { + switch (v) { + case WMSZ_LEFT: return 3; + case WMSZ_TOP: return 2; + case WMSZ_RIGHT: return 3; + case WMSZ_BOTTOM: return 2; + case WMSZ_TOPLEFT: return 1; + case WMSZ_TOPRIGHT: return 1; + case WMSZ_BOTTOMLEFT: return 3; + case WMSZ_BOTTOMRIGHT: return 3; + default: return -1; + } +} + +static bool key_state(struct vo *vo, int vk) +{ + return GetKeyState(vk) & 0x8000; +} + +static int mod_state(struct vo *vo) +{ + int res = 0; + if (key_state(vo, VK_CONTROL)) + res |= KEY_MODIFIER_CTRL; + if (key_state(vo, VK_SHIFT)) + res |= KEY_MODIFIER_SHIFT; + if (key_state(vo, VK_MENU)) + res |= KEY_MODIFIER_ALT; + return res; +} + +static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, + LPARAM lParam) +{ + if (message == WM_NCCREATE) { + CREATESTRUCT *cs = (void*)lParam; + SetWindowLongPtrW(hWnd, GWLP_USERDATA, (LONG_PTR)cs->lpCreateParams); + } + struct vo *vo = (void*)GetWindowLongPtrW(hWnd, GWLP_USERDATA); + // message before WM_NCCREATE, pray to Raymond Chen that it's not important + if (!vo) + return DefWindowProcW(hWnd, message, wParam, lParam); + struct vo_w32_state *w32 = vo->w32; + + switch (message) { + case WM_ERASEBKGND: // no need to erase background seperately + return 1; + case WM_PAINT: + w32->event_flags |= VO_EVENT_EXPOSE; + break; + case WM_MOVE: { + w32->event_flags |= VO_EVENT_MOVE; + POINT p = {0}; + ClientToScreen(w32->window, &p); + w32->window_x = p.x; + w32->window_y = p.y; + mp_msg(MSGT_VO, MSGL_V, "[vo] move window: %d:%d\n", + w32->window_x, w32->window_y); + break; + } + case WM_SIZE: { + w32->event_flags |= VO_EVENT_RESIZE; + RECT r; + GetClientRect(w32->window, &r); + vo->dwidth = r.right; + vo->dheight = r.bottom; + mp_msg(MSGT_VO, MSGL_V, "[vo] resize window: %d:%d\n", + vo->dwidth, vo->dheight); + break; + } + case WM_SIZING: + if (vo_keepaspect && !vo_fs && WinID < 0) { + RECT *rc = (RECT*)lParam; + // get client area of the windows if it had the rect rc + // (subtracting the window borders) + RECT r = *rc; + subtract_window_borders(w32->window, &r); + int c_w = r.right - r.left, c_h = r.bottom - r.top; + float aspect = vo->aspdat.asp; + int d_w = c_h * aspect - c_w; + int d_h = c_w / aspect - c_h; + int d_corners[4] = { d_w, d_h, -d_w, -d_h }; + int corners[4] = { rc->left, rc->top, rc->right, rc->bottom }; + int corner = get_resize_border(wParam); + if (corner >= 0) + corners[corner] -= d_corners[corner]; + *rc = (RECT) { corners[0], corners[1], corners[2], corners[3] }; + return TRUE; + } + break; + case WM_CLOSE: + mplayer_put_key(vo->key_fifo, KEY_CLOSE_WIN); + break; + case WM_SYSCOMMAND: + switch (wParam) { + case SC_SCREENSAVE: + case SC_MONITORPOWER: + mp_msg(MSGT_VO, MSGL_V, "vo: win32: killing screensaver\n"); + return 0; + } + break; + case WM_KEYDOWN: + case WM_SYSKEYDOWN: { + int mpkey = lookup_keymap_table(vk_map, wParam); + if (mpkey) + mplayer_put_key(vo->key_fifo, mpkey | mod_state(vo)); + if (wParam == VK_F10) + return 0; + break; + } + case WM_CHAR: + case WM_SYSCHAR: { + int mods = mod_state(vo); + int code = wParam; + // Windows enables Ctrl+Alt when AltGr (VK_RMENU) is pressed. + // E.g. AltGr+9 on a German keyboard would yield Ctrl+Alt+[ + // Warning: wine handles this differently. Don't test this on wine! + if (key_state(vo, VK_RMENU)) + mods &= ~(KEY_MODIFIER_CTRL | KEY_MODIFIER_ALT); + // Apparently Ctrl+A to Ctrl+Z is special cased, and produces + // character codes from 1-26. Work it around. + // Also, enter/return (including the keypad variant) and CTRL+J both + // map to wParam==10. As a workaround, check VK_RETURN to + // distinguish these two key combinations. + if ((mods & KEY_MODIFIER_CTRL) && code >= 1 && code <= 26 + && !key_state(vo, VK_RETURN)) + code = code - 1 + (mods & KEY_MODIFIER_SHIFT ? 'A' : 'a'); + if (code >= 32 && code < (1<<21)) { + mplayer_put_key(vo->key_fifo, code | mods); + // At least with Alt+char, not calling DefWindowProcW stops + // Windows from emitting a beep. + return 0; + } + break; + } + case WM_LBUTTONDOWN: + if (!vo_nomouse_input && (vo_fs || (wParam & MK_CONTROL))) { + mplayer_put_key(vo->key_fifo, MOUSE_BTN0 | mod_state(vo)); + break; + } + if (!vo_fs) { + ReleaseCapture(); + SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0); + return 0; + } + break; + case WM_MBUTTONDOWN: + if (!vo_nomouse_input) + mplayer_put_key(vo->key_fifo, MOUSE_BTN1 | mod_state(vo)); + break; + case WM_RBUTTONDOWN: + if (!vo_nomouse_input) + mplayer_put_key(vo->key_fifo, MOUSE_BTN2 | mod_state(vo)); + break; + case WM_MOUSEMOVE: + vo_mouse_movement(vo, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); + break; + case WM_MOUSEWHEEL: + if (!vo_nomouse_input) { + int x = GET_WHEEL_DELTA_WPARAM(wParam); + if (x > 0) + mplayer_put_key(vo->key_fifo, MOUSE_BTN3 | mod_state(vo)); + else + mplayer_put_key(vo->key_fifo, MOUSE_BTN4 | mod_state(vo)); + } + break; + case WM_XBUTTONDOWN: + if (!vo_nomouse_input) { + int x = HIWORD(wParam); + if (x == 1) + mplayer_put_key(vo->key_fifo, MOUSE_BTN5 | mod_state(vo)); + else // if (x == 2) + mplayer_put_key(vo->key_fifo, MOUSE_BTN6 | mod_state(vo)); + } + break; + } + + return DefWindowProcW(hWnd, message, wParam, lParam); +} + +/** + * \brief Dispatch incoming window events and handle them. + * + * This function should be placed inside libvo's function "check_events". + * + * \return int with these flags possibly set, take care to handle in the right order + * if it matters in your driver: + * + * VO_EVENT_RESIZE = The window was resized. If necessary reinit your + * driver render context accordingly. + * VO_EVENT_EXPOSE = The window was exposed. Call e.g. flip_frame() to redraw + * the window if the movie is paused. + */ +int vo_w32_check_events(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + MSG msg; + w32->event_flags = 0; + while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + if (WinID >= 0) { + BOOL res; + RECT r; + POINT p; + res = GetClientRect(w32->window, &r); + if (res && (r.right != vo->dwidth || r.bottom != vo->dheight)) { + vo->dwidth = r.right; vo->dheight = r.bottom; + w32->event_flags |= VO_EVENT_RESIZE; + } + p.x = 0; p.y = 0; + ClientToScreen(w32->window, &p); + if (p.x != w32->window_x || p.y != w32->window_y) { + w32->window_x = p.x; w32->window_y = p.y; + w32->event_flags |= VO_EVENT_MOVE; + } + res = GetClientRect(WIN_ID_TO_HWND(WinID), &r); + if (res && (r.right != vo->dwidth || r.bottom != vo->dheight)) + MoveWindow(w32->window, 0, 0, r.right, r.bottom, FALSE); + if (!IsWindow(WIN_ID_TO_HWND(WinID))) + // Window has probably been closed, e.g. due to program crash + mplayer_put_key(vo->key_fifo, KEY_CLOSE_WIN); + } + + return w32->event_flags; +} + +static BOOL CALLBACK mon_enum(HMONITOR hmon, HDC hdc, LPRECT r, LPARAM p) +{ + struct vo *vo = (void*)p; + struct vo_w32_state *w32 = vo->w32; + // this defaults to the last screen if specified number does not exist + xinerama_x = r->left; + xinerama_y = r->top; + vo->opts->vo_screenwidth = r->right - r->left; + vo->opts->vo_screenheight = r->bottom - r->top; + if (w32->mon_cnt == xinerama_screen) + return FALSE; + w32->mon_cnt++; + return TRUE; +} + +/** + * \brief Update screen information. + * + * This function should be called in libvo's "control" callback + * with parameter VOCTRL_UPDATE_SCREENINFO. + * Note that this also enables the new API where geometry and aspect + * calculations are done in video_out.c:config_video_out + * + * Global libvo variables changed: + * xinerama_x + * xinerama_y + * vo_screenwidth + * vo_screenheight + */ +void w32_update_xinerama_info(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + xinerama_x = xinerama_y = 0; + if (xinerama_screen < -1) { + int tmp; + xinerama_x = GetSystemMetrics(SM_XVIRTUALSCREEN); + xinerama_y = GetSystemMetrics(SM_YVIRTUALSCREEN); + tmp = GetSystemMetrics(SM_CXVIRTUALSCREEN); + if (tmp) vo->opts->vo_screenwidth = tmp; + tmp = GetSystemMetrics(SM_CYVIRTUALSCREEN); + if (tmp) vo->opts->vo_screenheight = tmp; + } else if (xinerama_screen == -1) { + MONITORINFO mi; + HMONITOR m = MonitorFromWindow(w32->window, MONITOR_DEFAULTTOPRIMARY); + mi.cbSize = sizeof(mi); + GetMonitorInfoW(m, &mi); + xinerama_x = mi.rcMonitor.left; + xinerama_y = mi.rcMonitor.top; + vo->opts->vo_screenwidth = mi.rcMonitor.right - mi.rcMonitor.left; + vo->opts->vo_screenheight = mi.rcMonitor.bottom - mi.rcMonitor.top; + } else if (xinerama_screen > 0) { + w32->mon_cnt = 0; + EnumDisplayMonitors(NULL, NULL, mon_enum, (LONG_PTR)vo); + } + aspect_save_screenres(vo, vo->opts->vo_screenwidth, + vo->opts->vo_screenheight); +} + +static void updateScreenProperties(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + DEVMODE dm; + dm.dmSize = sizeof dm; + dm.dmDriverExtra = 0; + dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; + if (!EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &dm)) { + mp_msg(MSGT_VO, MSGL_ERR, + "vo: win32: unable to enumerate display settings!\n"); + return; + } + + vo->opts->vo_screenwidth = dm.dmPelsWidth; + vo->opts->vo_screenheight = dm.dmPelsHeight; + w32->depthonscreen = dm.dmBitsPerPel; + w32_update_xinerama_info(vo); +} + +static void changeMode(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + DEVMODE dm; + dm.dmSize = sizeof dm; + dm.dmDriverExtra = 0; + + dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; + dm.dmBitsPerPel = w32->depthonscreen; + dm.dmPelsWidth = vo->opts->vo_screenwidth; + dm.dmPelsHeight = vo->opts->vo_screenheight; + + if (w32->vm) { + int bestMode = -1; + int bestScore = INT_MAX; + int i; + for (i = 0; EnumDisplaySettings(0, i, &dm); ++i) { + int score = (dm.dmPelsWidth - w32->o_dwidth) + * (dm.dmPelsHeight - w32->o_dheight); + if (dm.dmBitsPerPel != w32->depthonscreen + || dm.dmPelsWidth < w32->o_dwidth + || dm.dmPelsHeight < w32->o_dheight) + continue; + + if (score < bestScore) { + bestScore = score; + bestMode = i; + } + } + + if (bestMode != -1) + EnumDisplaySettings(0, bestMode, &dm); + + ChangeDisplaySettings(&dm, CDS_FULLSCREEN); + } +} + +static void resetMode(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + if (w32->vm) + ChangeDisplaySettings(0, 0); +} + +static DWORD update_style(struct vo *vo, DWORD style) +{ + const DWORD NO_FRAME = WS_POPUP; + const DWORD FRAME = WS_OVERLAPPEDWINDOW | WS_SIZEBOX; + style &= ~(NO_FRAME | FRAME); + style |= (vo_border && !vo_fs) ? FRAME : NO_FRAME; + return style; +} + +// Update the window title, position, size, and border style from vo_* values. +static int reinit_window_state(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + HWND layer = HWND_NOTOPMOST; + RECT r; + + if (WinID >= 0) + return 1; + + wchar_t *title = mp_from_utf8(NULL, vo_get_window_title(vo)); + SetWindowTextW(w32->window, title); + talloc_free(title); + + bool toggle_fs = w32->current_fs != vo_fs; + w32->current_fs = vo_fs; + + DWORD style = update_style(vo, GetWindowLong(w32->window, GWL_STYLE)); + + if (vo_fs || vo->opts->vo_ontop) + layer = HWND_TOPMOST; + + // xxx not sure if this can trigger any unwanted messages (WM_MOVE/WM_SIZE) + if (vo_fs) { + changeMode(vo); + while (ShowCursor(0) >= 0) /**/ ; + } else { + resetMode(vo); + while (ShowCursor(1) < 0) /**/ ; + } + updateScreenProperties(vo); + + if (vo_fs) { + // Save window position and size when switching to fullscreen. + if (toggle_fs) { + w32->prev_width = vo->dwidth; + w32->prev_height = vo->dheight; + w32->prev_x = w32->window_x; + w32->prev_y = w32->window_y; + mp_msg(MSGT_VO, MSGL_V, "[vo] save window bounds: %d:%d:%d:%d\n", + w32->prev_x, w32->prev_y, w32->prev_width, w32->prev_height); + } + vo->dwidth = vo->opts->vo_screenwidth; + vo->dheight = vo->opts->vo_screenheight; + w32->window_x = xinerama_x; + w32->window_y = xinerama_y; + } else { + if (toggle_fs) { + // Restore window position and size when switching from fullscreen. + mp_msg(MSGT_VO, MSGL_V, "[vo] restore window bounds: %d:%d:%d:%d\n", + w32->prev_x, w32->prev_y, w32->prev_width, w32->prev_height); + vo->dwidth = w32->prev_width; + vo->dheight = w32->prev_height; + w32->window_x = w32->prev_x; + w32->window_y = w32->prev_y; + } + } + + r.left = w32->window_x; + r.right = r.left + vo->dwidth; + r.top = w32->window_y; + r.bottom = r.top + vo->dheight; + + SetWindowLong(w32->window, GWL_STYLE, style); + add_window_borders(w32->window, &r); + + mp_msg(MSGT_VO, MSGL_V, "[vo] reset window bounds: %ld:%ld:%ld:%ld\n", + r.left, r.top, r.right - r.left, r.bottom - r.top); + + SetWindowPos(w32->window, layer, r.left, r.top, r.right - r.left, + r.bottom - r.top, SWP_FRAMECHANGED); + // For some reason, moving SWP_SHOWWINDOW to a second call works better + // with wine: returning from fullscreen doesn't cause a bogus resize to + // screen size. + // It's not needed on Windows XP or wine with a virtual desktop. + // It doesn't seem to have any negative effects. + SetWindowPos(w32->window, NULL, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW); + + return 1; +} + +/** + * \brief Configure and show window on the screen. + * + * This function should be called in libvo's "config" callback. + * It configures a window and shows it on the screen. + * + * \return 1 - Success, 0 - Failure + */ +int vo_w32_config(struct vo *vo, uint32_t width, uint32_t height, + uint32_t flags) +{ + struct vo_w32_state *w32 = vo->w32; + PIXELFORMATDESCRIPTOR pfd; + int pf; + HDC vo_hdc = vo_w32_get_dc(vo, w32->window); + + memset(&pfd, 0, sizeof pfd); + pfd.nSize = sizeof pfd; + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + if (flags & VOFLAG_STEREO) + pfd.dwFlags |= PFD_STEREO; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 24; + pfd.iLayerType = PFD_MAIN_PLANE; + pf = ChoosePixelFormat(vo_hdc, &pfd); + if (!pf) { + mp_msg(MSGT_VO, MSGL_ERR, "vo: win32: unable to select a valid pixel format!\n"); + vo_w32_release_dc(vo, w32->window, vo_hdc); + return 0; + } + + SetPixelFormat(vo_hdc, pf, &pfd); + vo_w32_release_dc(vo, w32->window, vo_hdc); + + // we already have a fully initialized window, so nothing needs to be done + if (flags & VOFLAG_HIDDEN) + return 1; + + bool reset_size = !(w32->o_dwidth == width && w32->o_dheight == height); + + w32->o_dwidth = width; + w32->o_dheight = height; + + // the desired size is ignored in wid mode, it always matches the window size. + if (WinID < 0) { + if (w32->window_bounds_initialized) { + // restore vo_dwidth/vo_dheight, which are reset against our will + // in vo_config() + RECT r; + GetClientRect(w32->window, &r); + vo->dwidth = r.right; + vo->dheight = r.bottom; + } else { + // first vo_config call; vo_config() will always set vo_dx/dy so + // that the window is centered on the screen, and this is the only + // time we actually want to use vo_dy/dy (this is not sane, and + // video_out.h should provide a function to query the initial + // window position instead) + w32->window_bounds_initialized = true; + reset_size = true; + w32->window_x = w32->prev_x = vo->dx; + w32->window_y = w32->prev_y = vo->dy; + } + if (reset_size) { + w32->prev_width = vo->dwidth = width; + w32->prev_height = vo->dheight = height; + } + } + + vo_fs = flags & VOFLAG_FULLSCREEN; + w32->vm = flags & VOFLAG_MODESWITCHING; + return reinit_window_state(vo); +} + +/** + * \brief Initialize w32_common framework. + * + * The first function that should be called from the w32_common framework. + * It handles window creation on the screen with proper title and attributes. + * It also initializes the framework's internal variables. The function should + * be called after your own preinit initialization and you shouldn't do any + * window management on your own. + * + * Global libvo variables changed: + * vo_w32_window + * vo_screenwidth + * vo_screenheight + * + * \return 1 = Success, 0 = Failure + */ +int vo_w32_init(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + if (w32 && w32->window) + return 1; + + if (!w32) + w32 = vo->w32 = talloc_zero(vo, struct vo_w32_state); + + HINSTANCE hInstance = GetModuleHandleW(NULL); + + HICON mplayerIcon = LoadIconW(hInstance, L"IDI_ICON1"); + + WNDCLASSEXW wcex = { + .cbSize = sizeof wcex, + .style = CS_OWNDC | CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW, + .lpfnWndProc = WndProc, + .hInstance = hInstance, + .hIcon = mplayerIcon, + .hCursor = LoadCursor(0, IDC_ARROW), + .lpszClassName = classname, + .hIconSm = mplayerIcon, + }; + + if (!RegisterClassExW(&wcex)) { + mp_msg(MSGT_VO, MSGL_ERR, + "vo: win32: unable to register window class!\n"); + return 0; + } + + if (WinID >= 0) { + RECT r; + GetClientRect(WIN_ID_TO_HWND(WinID), &r); + vo->dwidth = r.right; vo->dheight = r.bottom; + w32->window = CreateWindowExW(WS_EX_NOPARENTNOTIFY, classname, + classname, + WS_CHILD | WS_VISIBLE, + 0, 0, vo->dwidth, vo->dheight, + WIN_ID_TO_HWND(WinID), 0, hInstance, vo); + } else { + w32->window = CreateWindowExW(0, classname, + classname, + update_style(vo, 0), + CW_USEDEFAULT, 0, 100, 100, + 0, 0, hInstance, vo); + } + + if (!w32->window) { + mp_msg(MSGT_VO, MSGL_ERR, "vo: win32: unable to create window!\n"); + return 0; + } + + if (WinID >= 0) + EnableWindow(w32->window, 0); + + updateScreenProperties(vo); + + mp_msg(MSGT_VO, MSGL_V, "vo: win32: running at %dx%d with depth %d\n", + vo->opts->vo_screenwidth, vo->opts->vo_screenheight, + w32->depthonscreen); + + return 1; +} + +/** + * \brief Toogle fullscreen / windowed mode. + * + * Should be called on VOCTRL_FULLSCREEN event. The window is + * always resized during this call, so the rendering context + * should be reinitialized with the new dimensions. + * It is unspecified if vo_check_events will create a resize + * event in addition or not. + */ + +void vo_w32_fullscreen(struct vo *vo) +{ + vo_fs = !vo_fs; + reinit_window_state(vo); +} + +/** + * \brief Toogle window border attribute. + * + * Should be called on VOCTRL_BORDER event. + */ +void vo_w32_border(struct vo *vo) +{ + vo_border = !vo_border; + reinit_window_state(vo); +} + +/** + * \brief Toogle window ontop attribute. + * + * Should be called on VOCTRL_ONTOP event. + */ +void vo_w32_ontop(struct vo *vo) +{ + vo->opts->vo_ontop = !vo->opts->vo_ontop; + reinit_window_state(vo); +} + +/** + * \brief Uninitialize w32_common framework. + * + * Should be called last in video driver's uninit function. First release + * anything built on top of the created window e.g. rendering context inside + * and call vo_w32_uninit at the end. + */ +void vo_w32_uninit(struct vo *vo) +{ + struct vo_w32_state *w32 = vo->w32; + mp_msg(MSGT_VO, MSGL_V, "vo: win32: uninit\n"); + if (!w32) + return; + resetMode(vo); + ShowCursor(1); + DestroyWindow(w32->window); + UnregisterClassW(classname, 0); + talloc_free(w32); + vo->w32 = NULL; +} + +/** + * \brief get a device context to draw in + * + * \param wnd window the DC should belong to if it makes sense + */ +HDC vo_w32_get_dc(struct vo *vo, HWND wnd) +{ + struct vo_w32_state *w32 = vo->w32; + return GetDC(wnd); +} + +/** + * \brief release a device context + * + * \param wnd window the DC probably belongs to + */ +void vo_w32_release_dc(struct vo *vo, HWND wnd, HDC dc) +{ + struct vo_w32_state *w32 = vo->w32; + ReleaseDC(wnd, dc); +} diff --git a/video/out/w32_common.h b/video/out/w32_common.h new file mode 100644 index 0000000000..c6d9fc7653 --- /dev/null +++ b/video/out/w32_common.h @@ -0,0 +1,66 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_W32_COMMON_H +#define MPLAYER_W32_COMMON_H + +#include <stdint.h> +#include <stdbool.h> +#include <windows.h> + +struct vo_w32_state { + HWND window; + + bool vm; + + int depthonscreen; + + // last non-fullscreen extends (updated only on fullscreen or on initialization) + int prev_width; + int prev_height; + int prev_x; + int prev_y; + + // whether the window position and size were intialized + bool window_bounds_initialized; + + bool current_fs; + + int window_x; + int window_y; + + // video size + uint32_t o_dwidth; + uint32_t o_dheight; + + int event_flags; + int mon_cnt; +}; + +int vo_w32_init(struct vo *vo); +void vo_w32_uninit(struct vo *vo); +void vo_w32_ontop(struct vo *vo); +void vo_w32_border(struct vo *vo); +void vo_w32_fullscreen(struct vo *vo); +int vo_w32_check_events(struct vo *vo); +int vo_w32_config(struct vo *vo, uint32_t, uint32_t, uint32_t); +void w32_update_xinerama_info(struct vo *vo); +HDC vo_w32_get_dc(struct vo *vo, HWND wnd); +void vo_w32_release_dc(struct vo *vo, HWND wnd, HDC dc); + +#endif /* MPLAYER_W32_COMMON_H */ diff --git a/video/out/x11_common.c b/video/out/x11_common.c new file mode 100644 index 0000000000..04d5c6880b --- /dev/null +++ b/video/out/x11_common.c @@ -0,0 +1,2404 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <inttypes.h> +#include <limits.h> + +#include "config.h" +#include "bstr.h" +#include "options.h" +#include "mp_msg.h" +#include "mp_fifo.h" +#include "libavutil/common.h" +#include "x11_common.h" +#include "talloc.h" + +#include <string.h> +#include <unistd.h> +#include <assert.h> + +#include "video_out.h" +#include "aspect.h" +#include "geometry.h" +#include "osdep/timer.h" + +#include <X11/Xmd.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> +#include <X11/keysym.h> + +#ifdef CONFIG_XSS +#include <X11/extensions/scrnsaver.h> +#endif + +#ifdef CONFIG_XDPMS +#include <X11/extensions/dpms.h> +#endif + +#ifdef CONFIG_XINERAMA +#include <X11/extensions/Xinerama.h> +#endif + +#ifdef CONFIG_XF86VM +#include <X11/extensions/xf86vmode.h> +#endif + +#ifdef CONFIG_XF86XK +#include <X11/XF86keysym.h> +#endif + +#ifdef CONFIG_XV +#include <X11/extensions/Xv.h> +#include <X11/extensions/Xvlib.h> + +#include "subopt-helper.h" +#endif + +#include "input/input.h" +#include "input/keycodes.h" + +#define WIN_LAYER_ONBOTTOM 2 +#define WIN_LAYER_NORMAL 4 +#define WIN_LAYER_ONTOP 6 +#define WIN_LAYER_ABOVE_DOCK 10 + +int fs_layer = WIN_LAYER_ABOVE_DOCK; + +int stop_xscreensaver = 1; + +static int dpms_disabled = 0; + +char *mDisplayName = NULL; + +char **vo_fstype_list; + +/* 1 means that the WM is metacity (broken as hell) */ +int metacity_hack = 0; + +#ifdef CONFIG_XF86VM +static int modecount; +static XF86VidModeModeInfo **vidmodes; +static XF86VidModeModeLine modeline; +#endif + +static int vo_x11_get_fs_type(int supported); +static void saver_off(Display *); +static void saver_on(Display *); + +/* + * Sends the EWMH fullscreen state event. + * + * action: could be one of _NET_WM_STATE_REMOVE -- remove state + * _NET_WM_STATE_ADD -- add state + * _NET_WM_STATE_TOGGLE -- toggle + */ +void vo_x11_ewmh_fullscreen(struct vo_x11_state *x11, int action) +{ + assert(action == _NET_WM_STATE_REMOVE || + action == _NET_WM_STATE_ADD || action == _NET_WM_STATE_TOGGLE); + + if (x11->fs_type & vo_wm_FULLSCREEN) + { + XEvent xev; + + /* init X event structure for _NET_WM_FULLSCREEN client message */ + xev.xclient.type = ClientMessage; + xev.xclient.serial = 0; + xev.xclient.send_event = True; + xev.xclient.message_type = x11->XA_NET_WM_STATE; + xev.xclient.window = x11->window; + xev.xclient.format = 32; + xev.xclient.data.l[0] = action; + xev.xclient.data.l[1] = x11->XA_NET_WM_STATE_FULLSCREEN; + xev.xclient.data.l[2] = 0; + xev.xclient.data.l[3] = 0; + xev.xclient.data.l[4] = 0; + + /* finally send that damn thing */ + if (!XSendEvent(x11->display, DefaultRootWindow(x11->display), False, + SubstructureRedirectMask | SubstructureNotifyMask, + &xev)) + { + mp_tmsg(MSGT_VO, MSGL_ERR, "\nX11: Couldn't send EWMH fullscreen event!\n"); + } + } +} + +static void vo_hidecursor(Display * disp, Window win) +{ + Cursor no_ptr; + Pixmap bm_no; + XColor black, dummy; + Colormap colormap; + const char bm_no_data[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + + if (WinID == 0) + return; // do not hide if playing on the root window + + colormap = DefaultColormap(disp, DefaultScreen(disp)); + if ( !XAllocNamedColor(disp, colormap, "black", &black, &dummy) ) + { + return; // color alloc failed, give up + } + bm_no = XCreateBitmapFromData(disp, win, bm_no_data, 8, 8); + no_ptr = XCreatePixmapCursor(disp, bm_no, bm_no, &black, &black, 0, 0); + XDefineCursor(disp, win, no_ptr); + XFreeCursor(disp, no_ptr); + if (bm_no != None) + XFreePixmap(disp, bm_no); + XFreeColors(disp,colormap,&black.pixel,1,0); +} + +static void vo_showcursor(Display * disp, Window win) +{ + if (WinID == 0) + return; + XDefineCursor(disp, win, 0); +} + +static int x11_errorhandler(Display * display, XErrorEvent * event) +{ +#define MSGLEN 60 + char msg[MSGLEN]; + + XGetErrorText(display, event->error_code, (char *) &msg, MSGLEN); + + mp_msg(MSGT_VO, MSGL_ERR, "X11 error: %s\n", msg); + + mp_msg(MSGT_VO, MSGL_V, + "Type: %x, display: %p, resourceid: %lx, serial: %lx\n", + event->type, event->display, event->resourceid, event->serial); + mp_msg(MSGT_VO, MSGL_V, + "Error code: %x, request code: %x, minor code: %x\n", + event->error_code, event->request_code, event->minor_code); + +// abort(); + return 0; +#undef MSGLEN +} + +void fstype_help(void) +{ + mp_tmsg(MSGT_VO, MSGL_INFO, "Available fullscreen layer change modes:\n"); + mp_msg(MSGT_IDENTIFY, MSGL_INFO, "ID_FULL_SCREEN_TYPES\n"); + + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "none", + "don't set fullscreen window layer"); + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "layer", + "use _WIN_LAYER hint with default layer"); + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "layer=<0..15>", + "use _WIN_LAYER hint with a given layer number"); + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "netwm", + "force NETWM style"); + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "above", + "use _NETWM_STATE_ABOVE hint if available"); + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "below", + "use _NETWM_STATE_BELOW hint if available"); + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "fullscreen", + "use _NETWM_STATE_FULLSCREEN hint if available"); + mp_msg(MSGT_VO, MSGL_INFO, " %-15s %s\n", "stays_on_top", + "use _NETWM_STATE_STAYS_ON_TOP hint if available"); + mp_msg(MSGT_VO, MSGL_INFO, + "You can also negate the settings with simply putting '-' in the beginning"); + mp_msg(MSGT_VO, MSGL_INFO, "\n"); +} + +static void fstype_dump(int fstype) +{ + if (fstype) + { + mp_msg(MSGT_VO, MSGL_V, "[x11] Current fstype setting honours"); + if (fstype & vo_wm_LAYER) + mp_msg(MSGT_VO, MSGL_V, " LAYER"); + if (fstype & vo_wm_FULLSCREEN) + mp_msg(MSGT_VO, MSGL_V, " FULLSCREEN"); + if (fstype & vo_wm_STAYS_ON_TOP) + mp_msg(MSGT_VO, MSGL_V, " STAYS_ON_TOP"); + if (fstype & vo_wm_ABOVE) + mp_msg(MSGT_VO, MSGL_V, " ABOVE"); + if (fstype & vo_wm_BELOW) + mp_msg(MSGT_VO, MSGL_V, " BELOW"); + mp_msg(MSGT_VO, MSGL_V, " X atoms\n"); + } else + mp_msg(MSGT_VO, MSGL_V, + "[x11] Current fstype setting doesn't honour any X atoms\n"); +} + +static int net_wm_support_state_test(struct vo_x11_state *x11, Atom atom) +{ +#define NET_WM_STATE_TEST(x) { if (atom == x11->XA_NET_WM_STATE_##x) { mp_msg( MSGT_VO,MSGL_V, "[x11] Detected wm supports " #x " state.\n" ); return vo_wm_##x; } } + + NET_WM_STATE_TEST(FULLSCREEN); + NET_WM_STATE_TEST(ABOVE); + NET_WM_STATE_TEST(STAYS_ON_TOP); + NET_WM_STATE_TEST(BELOW); + return 0; +} + +static int x11_get_property(struct vo_x11_state *x11, Atom type, Atom ** args, + unsigned long *nitems) +{ + int format; + unsigned long bytesafter; + + return Success == + XGetWindowProperty(x11->display, x11->rootwin, type, 0, 16384, False, + AnyPropertyType, &type, &format, nitems, + &bytesafter, (unsigned char **) args) + && *nitems > 0; +} + +static int vo_wm_detect(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + int i; + int wm = 0; + unsigned long nitems; + Atom *args = NULL; + + if (WinID >= 0) + return 0; + +// -- supports layers + if (x11_get_property(x11, x11->XA_WIN_PROTOCOLS, &args, &nitems)) + { + mp_msg(MSGT_VO, MSGL_V, "[x11] Detected wm supports layers.\n"); + for (i = 0; i < nitems; i++) + { + if (args[i] == x11->XA_WIN_LAYER) + { + wm |= vo_wm_LAYER; + metacity_hack |= 1; + } else + /* metacity is the only window manager I know which reports + * supporting only the _WIN_LAYER hint in _WIN_PROTOCOLS. + * (what's more support for it is broken) */ + metacity_hack |= 2; + } + XFree(args); + if (wm && (metacity_hack == 1)) + { + // metacity claims to support layers, but it is not the truth :-) + wm ^= vo_wm_LAYER; + mp_msg(MSGT_VO, MSGL_V, + "[x11] Using workaround for Metacity bugs.\n"); + } + } +// --- netwm + if (x11_get_property(x11, x11->XA_NET_SUPPORTED, &args, &nitems)) + { + mp_msg(MSGT_VO, MSGL_V, "[x11] Detected wm supports NetWM.\n"); + for (i = 0; i < nitems; i++) + wm |= net_wm_support_state_test(vo->x11, args[i]); + XFree(args); + } + + if (wm == 0) + mp_msg(MSGT_VO, MSGL_V, "[x11] Unknown wm type...\n"); + return wm; +} + +#define XA_INIT(x) x11->XA##x = XInternAtom(x11->display, #x, False) +static void init_atoms(struct vo_x11_state *x11) +{ + XA_INIT(_NET_SUPPORTED); + XA_INIT(_NET_WM_STATE); + XA_INIT(_NET_WM_STATE_FULLSCREEN); + XA_INIT(_NET_WM_STATE_ABOVE); + XA_INIT(_NET_WM_STATE_STAYS_ON_TOP); + XA_INIT(_NET_WM_STATE_BELOW); + XA_INIT(_NET_WM_PID); + XA_INIT(_NET_WM_NAME); + XA_INIT(_NET_WM_ICON_NAME); + XA_INIT(_WIN_PROTOCOLS); + XA_INIT(_WIN_LAYER); + XA_INIT(_WIN_HINTS); + XA_INIT(WM_PROTOCOLS); + XA_INIT(WM_DELETE_WINDOW); + XA_INIT(UTF8_STRING); + char buf[50]; + sprintf(buf, "_NET_WM_CM_S%d", x11->screen); + x11->XA_NET_WM_CM = XInternAtom(x11->display, buf, False); +} + +void update_xinerama_info(struct vo *vo) { + struct MPOpts *opts = vo->opts; + xinerama_x = xinerama_y = 0; +#ifdef CONFIG_XINERAMA + if (xinerama_screen >= -1 && XineramaIsActive(vo->x11->display)) + { + int screen = xinerama_screen; + XineramaScreenInfo *screens; + int num_screens; + + screens = XineramaQueryScreens(vo->x11->display, &num_screens); + if (screen >= num_screens) + screen = num_screens - 1; + if (screen == -1) { + int x = vo->dx + vo->dwidth / 2; + int y = vo->dy + vo->dheight / 2; + for (screen = num_screens - 1; screen > 0; screen--) { + int left = screens[screen].x_org; + int right = left + screens[screen].width; + int top = screens[screen].y_org; + int bottom = top + screens[screen].height; + if (left <= x && x <= right && top <= y && y <= bottom) + break; + } + } + if (screen < 0) + screen = 0; + opts->vo_screenwidth = screens[screen].width; + opts->vo_screenheight = screens[screen].height; + xinerama_x = screens[screen].x_org; + xinerama_y = screens[screen].y_org; + + XFree(screens); + } +#endif + aspect_save_screenres(vo, opts->vo_screenwidth, opts->vo_screenheight); +} + +int vo_init(struct vo *vo) +{ + struct MPOpts *opts = vo->opts; +// int mScreen; + int depth, bpp; + unsigned int mask; + +// char * DisplayName = ":0.0"; +// Display * mDisplay; + XImage *mXImage = NULL; + +// Window mRootWin; + XWindowAttributes attribs; + char *dispName; + + if (vo->x11) + return 1; + + vo->x11 = vo_x11_init_state(); + struct vo_x11_state *x11 = vo->x11; + + if (vo_rootwin) + WinID = 0; // use root window + + if (x11->depthonscreen) + { + saver_off(x11->display); + return 1; // already called + } + + XSetErrorHandler(x11_errorhandler); + +#if 0 + if (!mDisplayName) + if (!(mDisplayName = getenv("DISPLAY"))) + mDisplayName = strdup(":0.0"); +#else + dispName = XDisplayName(mDisplayName); +#endif + + mp_msg(MSGT_VO, MSGL_V, "X11 opening display: %s\n", dispName); + + x11->display = XOpenDisplay(dispName); + if (!x11->display) + { + mp_msg(MSGT_VO, MSGL_ERR, + "vo: couldn't open the X11 display (%s)!\n", dispName); + talloc_free(x11); + vo->x11 = NULL; + return 0; + } + x11->screen = DefaultScreen(x11->display); // screen ID + x11->rootwin = RootWindow(x11->display, x11->screen); // root window ID + + x11->xim = XOpenIM(x11->display, NULL, NULL, NULL); + + init_atoms(vo->x11); + +#ifdef CONFIG_XF86VM + { + int clock; + + XF86VidModeGetModeLine(x11->display, x11->screen, &clock, &modeline); + if (!opts->vo_screenwidth) + opts->vo_screenwidth = modeline.hdisplay; + if (!opts->vo_screenheight) + opts->vo_screenheight = modeline.vdisplay; + } +#endif + { + if (!opts->vo_screenwidth) + opts->vo_screenwidth = DisplayWidth(x11->display, x11->screen); + if (!opts->vo_screenheight) + opts->vo_screenheight = DisplayHeight(x11->display, x11->screen); + } + // get color depth (from root window, or the best visual): + XGetWindowAttributes(x11->display, x11->rootwin, &attribs); + depth = attribs.depth; + + if (depth != 15 && depth != 16 && depth != 24 && depth != 32) + { + Visual *visual; + + depth = vo_find_depth_from_visuals(x11->display, x11->screen, &visual); + if (depth != -1) + mXImage = XCreateImage(x11->display, visual, depth, ZPixmap, + 0, NULL, 1, 1, 8, 1); + } else + mXImage = + XGetImage(x11->display, x11->rootwin, 0, 0, 1, 1, AllPlanes, ZPixmap); + + x11->depthonscreen = depth; // display depth on screen + + // get bits/pixel from XImage structure: + if (mXImage == NULL) + { + mask = 0; + } else + { + /* + * for the depth==24 case, the XImage structures might use + * 24 or 32 bits of data per pixel. The x11->depthonscreen + * field stores the amount of data per pixel in the + * XImage structure! + * + * Maybe we should rename vo_depthonscreen to (or add) vo_bpp? + */ + bpp = mXImage->bits_per_pixel; + if ((x11->depthonscreen + 7) / 8 != (bpp + 7) / 8) + x11->depthonscreen = bpp; // by A'rpi + mask = + mXImage->red_mask | mXImage->green_mask | mXImage->blue_mask; + mp_msg(MSGT_VO, MSGL_V, + "vo: X11 color mask: %X (R:%lX G:%lX B:%lX)\n", mask, + mXImage->red_mask, mXImage->green_mask, mXImage->blue_mask); + XDestroyImage(mXImage); + } + if (((x11->depthonscreen + 7) / 8) == 2) + { + if (mask == 0x7FFF) + x11->depthonscreen = 15; + else if (mask == 0xFFFF) + x11->depthonscreen = 16; + } +// XCloseDisplay( mDisplay ); +/* slightly improved local display detection AST */ + if (strncmp(dispName, "unix:", 5) == 0) + dispName += 4; + else if (strncmp(dispName, "localhost:", 10) == 0) + dispName += 9; + if (*dispName == ':' && atoi(dispName + 1) < 10) + x11->display_is_local = 1; + else + x11->display_is_local = 0; + mp_msg(MSGT_VO, MSGL_V, + "vo: X11 running at %dx%d with depth %d and %d bpp (\"%s\" => %s display)\n", + opts->vo_screenwidth, opts->vo_screenheight, depth, x11->depthonscreen, + dispName, x11->display_is_local ? "local" : "remote"); + + x11->wm_type = vo_wm_detect(vo); + + x11->fs_type = vo_x11_get_fs_type(x11->wm_type); + + fstype_dump(x11->fs_type); + + saver_off(x11->display); + return 1; +} + +void vo_uninit(struct vo_x11_state *x11) +{ + if (!x11) + return; + if (!x11->display) + { + mp_msg(MSGT_VO, MSGL_V, + "vo: x11 uninit called but X11 not initialized..\n"); + } else { + mp_msg(MSGT_VO, MSGL_V, "vo: uninit ...\n"); + if (x11->xim) + XCloseIM(x11->xim); + XSetErrorHandler(NULL); + XCloseDisplay(x11->display); + x11->depthonscreen = 0; + x11->display = NULL; + } + talloc_free(x11); +} + +static const struct mp_keymap keymap[] = { + // special keys + {XK_Pause, KEY_PAUSE}, {XK_Escape, KEY_ESC}, {XK_BackSpace, KEY_BS}, + {XK_Tab, KEY_TAB}, {XK_Return, KEY_ENTER}, + {XK_Menu, KEY_MENU}, {XK_Print, KEY_PRINT}, + + // cursor keys + {XK_Left, KEY_LEFT}, {XK_Right, KEY_RIGHT}, {XK_Up, KEY_UP}, {XK_Down, KEY_DOWN}, + + // navigation block + {XK_Insert, KEY_INSERT}, {XK_Delete, KEY_DELETE}, {XK_Home, KEY_HOME}, {XK_End, KEY_END}, + {XK_Page_Up, KEY_PAGE_UP}, {XK_Page_Down, KEY_PAGE_DOWN}, + + // F-keys + {XK_F1, KEY_F+1}, {XK_F2, KEY_F+2}, {XK_F3, KEY_F+3}, {XK_F4, KEY_F+4}, + {XK_F5, KEY_F+5}, {XK_F6, KEY_F+6}, {XK_F7, KEY_F+7}, {XK_F8, KEY_F+8}, + {XK_F9, KEY_F+9}, {XK_F10, KEY_F+10}, {XK_F11, KEY_F+11}, {XK_F12, KEY_F+12}, + + // numpad independent of numlock + {XK_KP_Subtract, '-'}, {XK_KP_Add, '+'}, {XK_KP_Multiply, '*'}, {XK_KP_Divide, '/'}, + {XK_KP_Enter, KEY_KPENTER}, + + // numpad with numlock + {XK_KP_0, KEY_KP0}, {XK_KP_1, KEY_KP1}, {XK_KP_2, KEY_KP2}, + {XK_KP_3, KEY_KP3}, {XK_KP_4, KEY_KP4}, {XK_KP_5, KEY_KP5}, + {XK_KP_6, KEY_KP6}, {XK_KP_7, KEY_KP7}, {XK_KP_8, KEY_KP8}, + {XK_KP_9, KEY_KP9}, {XK_KP_Decimal, KEY_KPDEC}, + {XK_KP_Separator, KEY_KPDEC}, + + // numpad without numlock + {XK_KP_Insert, KEY_KPINS}, {XK_KP_End, KEY_KP1}, {XK_KP_Down, KEY_KP2}, + {XK_KP_Page_Down, KEY_KP3}, {XK_KP_Left, KEY_KP4}, {XK_KP_Begin, KEY_KP5}, + {XK_KP_Right, KEY_KP6}, {XK_KP_Home, KEY_KP7}, {XK_KP_Up, KEY_KP8}, + {XK_KP_Page_Up, KEY_KP9}, {XK_KP_Delete, KEY_KPDEL}, + +#ifdef XF86XK_AudioPause + {XF86XK_MenuKB, KEY_MENU}, + {XF86XK_AudioPlay, KEY_PLAY}, {XF86XK_AudioPause, KEY_PAUSE}, {XF86XK_AudioStop, KEY_STOP}, + {XF86XK_AudioPrev, KEY_PREV}, {XF86XK_AudioNext, KEY_NEXT}, + {XF86XK_AudioMute, KEY_MUTE}, {XF86XK_AudioLowerVolume, KEY_VOLUME_DOWN}, {XF86XK_AudioRaiseVolume, KEY_VOLUME_UP}, +#endif + + {0, 0} +}; + +static int vo_x11_lookupkey(int key) +{ + static const char *passthrough_keys = " -+*/<>`~!@#$%^&()_{}:;\"\',.?\\|=[]"; + int mpkey = 0; + if ((key >= 'a' && key <= 'z') || + (key >= 'A' && key <= 'Z') || + (key >= '0' && key <= '9') || + (key > 0 && key < 256 && strchr(passthrough_keys, key))) + mpkey = key; + + if (!mpkey) + mpkey = lookup_keymap_table(keymap, key); + + return mpkey; +} + + +// ----- Motif header: ------- + +#define MWM_HINTS_FUNCTIONS (1L << 0) +#define MWM_HINTS_DECORATIONS (1L << 1) +#define MWM_HINTS_INPUT_MODE (1L << 2) +#define MWM_HINTS_STATUS (1L << 3) + +#define MWM_FUNC_ALL (1L << 0) +#define MWM_FUNC_RESIZE (1L << 1) +#define MWM_FUNC_MOVE (1L << 2) +#define MWM_FUNC_MINIMIZE (1L << 3) +#define MWM_FUNC_MAXIMIZE (1L << 4) +#define MWM_FUNC_CLOSE (1L << 5) + +#define MWM_DECOR_ALL (1L << 0) +#define MWM_DECOR_BORDER (1L << 1) +#define MWM_DECOR_RESIZEH (1L << 2) +#define MWM_DECOR_TITLE (1L << 3) +#define MWM_DECOR_MENU (1L << 4) +#define MWM_DECOR_MINIMIZE (1L << 5) +#define MWM_DECOR_MAXIMIZE (1L << 6) + +#define MWM_INPUT_MODELESS 0 +#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 +#define MWM_INPUT_SYSTEM_MODAL 2 +#define MWM_INPUT_FULL_APPLICATION_MODAL 3 +#define MWM_INPUT_APPLICATION_MODAL MWM_INPUT_PRIMARY_APPLICATION_MODAL + +#define MWM_TEAROFF_WINDOW (1L<<0) + +typedef struct +{ + long flags; + long functions; + long decorations; + long input_mode; + long state; +} MotifWmHints; + +static MotifWmHints vo_MotifWmHints; +static Atom vo_MotifHints = None; + +void vo_x11_decoration(struct vo *vo, int d) +{ + struct vo_x11_state *x11 = vo->x11; + Atom mtype; + int mformat; + unsigned long mn, mb; + + if (!WinID) + return; + + if (vo_fsmode & 8) + { + XSetTransientForHint(x11->display, x11->window, + RootWindow(x11->display, x11->screen)); + } + + vo_MotifHints = XInternAtom(x11->display, "_MOTIF_WM_HINTS", 0); + if (vo_MotifHints != None) + { + if (!d) + { + MotifWmHints *mhints = NULL; + + XGetWindowProperty(x11->display, x11->window, + vo_MotifHints, 0, 20, False, + vo_MotifHints, &mtype, &mformat, &mn, + &mb, (unsigned char **) &mhints); + if (mhints) + { + if (mhints->flags & MWM_HINTS_DECORATIONS) + x11->olddecor = mhints->decorations; + if (mhints->flags & MWM_HINTS_FUNCTIONS) + x11->oldfuncs = mhints->functions; + XFree(mhints); + } + } + + memset(&vo_MotifWmHints, 0, sizeof(MotifWmHints)); + vo_MotifWmHints.flags = + MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS; + if (d) + { + vo_MotifWmHints.functions = x11->oldfuncs; + d = x11->olddecor; + } +#if 0 + vo_MotifWmHints.decorations = + d | ((vo_fsmode & 2) ? 0 : MWM_DECOR_MENU); +#else + vo_MotifWmHints.decorations = + d | ((vo_fsmode & 2) ? MWM_DECOR_MENU : 0); +#endif + XChangeProperty(x11->display, x11->window, vo_MotifHints, + vo_MotifHints, 32, + PropModeReplace, + (unsigned char *) &vo_MotifWmHints, + (vo_fsmode & 4) ? 4 : 5); + } +} + +void vo_x11_classhint(struct vo *vo, Window window, const char *name) +{ + struct MPOpts *opts = vo->opts; + struct vo_x11_state *x11 = vo->x11; + XClassHint wmClass; + pid_t pid = getpid(); + + wmClass.res_name = opts->vo_winname ? opts->vo_winname : (char *)name; + wmClass.res_class = "mpv"; + XSetClassHint(x11->display, window, &wmClass); + XChangeProperty(x11->display, window, x11->XA_NET_WM_PID, XA_CARDINAL, + 32, PropModeReplace, (unsigned char *) &pid, 1); +} + +void vo_x11_uninit(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + saver_on(x11->display); + if (x11->window != None) + vo_showcursor(x11->display, x11->window); + + if (x11->f_gc != None) + { + XFreeGC(vo->x11->display, x11->f_gc); + x11->f_gc = None; + } + { + if (x11->vo_gc != None) + { + XFreeGC(vo->x11->display, x11->vo_gc); + x11->vo_gc = None; + } + if (x11->window != None) + { + XClearWindow(x11->display, x11->window); + if (WinID < 0) + { + XEvent xev; + + if (x11->xic) + XDestroyIC(x11->xic); + x11->xic = NULL; + + XUnmapWindow(x11->display, x11->window); + XSelectInput(x11->display, x11->window, StructureNotifyMask); + XDestroyWindow(x11->display, x11->window); + do + { + XNextEvent(x11->display, &xev); + } + while (xev.type != DestroyNotify + || xev.xdestroywindow.event != x11->window); + } + x11->window = None; + } + vo_fs = 0; + x11->vo_old_width = x11->vo_old_height = 0; + x11->last_video_width = 0; + x11->last_video_height = 0; + x11->size_changed_during_fs = false; + } + vo_uninit(x11); + vo->x11 = NULL; +} + +static int check_resize(struct vo *vo) +{ + int old_w = vo->dwidth, old_h = vo->dheight; + int old_x = vo->dx, old_y = vo->dy; + int rc = 0; + vo_x11_update_geometry(vo, true); + if (vo->dwidth != old_w || vo->dheight != old_h) + rc |= VO_EVENT_RESIZE; + if (vo->dx != old_x || vo->dy != old_y) + rc |= VO_EVENT_MOVE; + return rc; +} + +int vo_x11_check_events(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + struct MPOpts *opts = vo->opts; + Display *display = vo->x11->display; + int ret = 0; + XEvent Event; + + if (x11->mouse_waiting_hide && opts->cursor_autohide_delay != -1 && + (GetTimerMS() - x11->mouse_timer >= opts->cursor_autohide_delay)) { + vo_hidecursor(display, x11->window); + x11->mouse_waiting_hide = 0; + } + + if (WinID > 0) + ret |= check_resize(vo); + while (XPending(display)) + { + XNextEvent(display, &Event); +// printf("\rEvent.type=%X \n",Event.type); + switch (Event.type) + { + case Expose: + ret |= VO_EVENT_EXPOSE; + break; + case ConfigureNotify: + if (x11->window == None) + break; + ret |= check_resize(vo); + break; + case KeyPress: + { + char buf[100]; + KeySym keySym = 0; + int modifiers = 0; + if (Event.xkey.state & ShiftMask) + modifiers |= KEY_MODIFIER_SHIFT; + if (Event.xkey.state & ControlMask) + modifiers |= KEY_MODIFIER_CTRL; + if (Event.xkey.state & Mod1Mask) + modifiers |= KEY_MODIFIER_ALT; + if (Event.xkey.state & Mod4Mask) + modifiers |= KEY_MODIFIER_META; + if (x11->xic) { + Status status; + int len = Xutf8LookupString(x11->xic, &Event.xkey, buf, + sizeof(buf), &keySym, + &status); + int mpkey = vo_x11_lookupkey(keySym); + if (mpkey) { + mplayer_put_key(vo->key_fifo, mpkey | modifiers); + } else if (status == XLookupChars + || status == XLookupBoth) + { + struct bstr t = { buf, len }; + mplayer_put_key_utf8(vo->key_fifo, modifiers, t); + } + } else { + XLookupString(&Event.xkey, buf, sizeof(buf), &keySym, + &x11->compose_status); + int mpkey = vo_x11_lookupkey(keySym); + if (mpkey) + mplayer_put_key(vo->key_fifo, mpkey | modifiers); + } + ret |= VO_EVENT_KEYPRESS; + } + break; + case MotionNotify: + vo_mouse_movement(vo, Event.xmotion.x, Event.xmotion.y); + + if (opts->cursor_autohide_delay > -2) { + vo_showcursor(display, x11->window); + x11->mouse_waiting_hide = 1; + x11->mouse_timer = GetTimerMS(); + } + break; + case ButtonPress: + if (opts->cursor_autohide_delay > -2) { + vo_showcursor(display, x11->window); + x11->mouse_waiting_hide = 1; + x11->mouse_timer = GetTimerMS(); + } + mplayer_put_key(vo->key_fifo, + (MOUSE_BTN0 + Event.xbutton.button - 1) + | MP_KEY_DOWN); + break; + case ButtonRelease: + if (opts->cursor_autohide_delay > -2) { + vo_showcursor(display, x11->window); + x11->mouse_waiting_hide = 1; + x11->mouse_timer = GetTimerMS(); + } + mplayer_put_key(vo->key_fifo, + MOUSE_BTN0 + Event.xbutton.button - 1); + break; + case PropertyNotify: + { + char *name = + XGetAtomName(display, Event.xproperty.atom); + + if (!name) + break; + +// fprintf(stderr,"[ws] PropertyNotify ( 0x%x ) %s ( 0x%x )\n",vo_window,name,Event.xproperty.atom ); + + XFree(name); + } + break; + case MapNotify: + x11->vo_hint.win_gravity = x11->old_gravity; + XSetWMNormalHints(display, x11->window, &x11->vo_hint); + x11->fs_flip = 0; + break; + case DestroyNotify: + mp_msg(MSGT_VO, MSGL_WARN, "Our window was destroyed, exiting\n"); + mplayer_put_key(vo->key_fifo, KEY_CLOSE_WIN); + break; + case ClientMessage: + if (Event.xclient.message_type == x11->XAWM_PROTOCOLS && + Event.xclient.data.l[0] == x11->XAWM_DELETE_WINDOW) + mplayer_put_key(vo->key_fifo, KEY_CLOSE_WIN); + break; + } + } + return ret; +} + +/** + * \brief sets the size and position of the non-fullscreen window. + */ +static void vo_x11_nofs_sizepos(struct vo *vo, int x, int y, + int width, int height) +{ + struct vo_x11_state *x11 = vo->x11; + if (width == x11->last_video_width && height == x11->last_video_height) { + if (!vo->opts->force_window_position && !x11->size_changed_during_fs) + return; + } else if (vo_fs) + x11->size_changed_during_fs = true; + x11->last_video_height = height; + x11->last_video_width = width; + vo_x11_sizehint(vo, x, y, width, height, 0); + if (vo_fs) { + x11->vo_old_x = x; + x11->vo_old_y = y; + x11->vo_old_width = width; + x11->vo_old_height = height; + } + else + { + vo->dwidth = width; + vo->dheight = height; + if (vo->opts->force_window_position) + XMoveResizeWindow(vo->x11->display, vo->x11->window, x, y, width, + height); + else + XResizeWindow(vo->x11->display, vo->x11->window, width, height); + } +} + +void vo_x11_sizehint(struct vo *vo, int x, int y, int width, int height, int max) +{ + struct vo_x11_state *x11 = vo->x11; + x11->vo_hint.flags = 0; + if (vo_keepaspect) + { + x11->vo_hint.flags |= PAspect; + x11->vo_hint.min_aspect.x = width; + x11->vo_hint.min_aspect.y = height; + x11->vo_hint.max_aspect.x = width; + x11->vo_hint.max_aspect.y = height; + } + + x11->vo_hint.flags |= PPosition | PSize; + x11->vo_hint.x = x; + x11->vo_hint.y = y; + x11->vo_hint.width = width; + x11->vo_hint.height = height; + if (max) + { + x11->vo_hint.flags |= PMaxSize; + x11->vo_hint.max_width = width; + x11->vo_hint.max_height = height; + } else + { + x11->vo_hint.max_width = 0; + x11->vo_hint.max_height = 0; + } + + // Set minimum height/width to 4 to avoid off-by-one errors. + x11->vo_hint.flags |= PMinSize; + x11->vo_hint.min_width = x11->vo_hint.min_height = 4; + + // Set the base size. A window manager might display the window + // size to the user relative to this. + // Setting these to width/height might be nice, but e.g. fluxbox can't handle it. + x11->vo_hint.flags |= PBaseSize; + x11->vo_hint.base_width = 0 /*width*/; + x11->vo_hint.base_height = 0 /*height*/; + + x11->vo_hint.flags |= PWinGravity; + x11->vo_hint.win_gravity = StaticGravity; + XSetWMNormalHints(x11->display, x11->window, &x11->vo_hint); +} + +static int vo_x11_get_gnome_layer(struct vo_x11_state *x11, Window win) +{ + Atom type; + int format; + unsigned long nitems; + unsigned long bytesafter; + unsigned short *args = NULL; + + if (XGetWindowProperty(x11->display, win, x11->XA_WIN_LAYER, 0, 16384, + False, AnyPropertyType, &type, &format, &nitems, + &bytesafter, + (unsigned char **) &args) == Success + && nitems > 0 && args) + { + mp_msg(MSGT_VO, MSGL_V, "[x11] original window layer is %d.\n", + *args); + return *args; + } + return WIN_LAYER_NORMAL; +} + +// set a X text property that expects a UTF8_STRING type +static void vo_x11_set_property_utf8(struct vo *vo, Atom name, const char *t) +{ + struct vo_x11_state *x11 = vo->x11; + + XChangeProperty(x11->display, x11->window, name, x11->XAUTF8_STRING, 8, + PropModeReplace, t, strlen(t)); +} + +// set a X text property that expects a STRING or COMPOUND_TEXT type +static void vo_x11_set_property_string(struct vo *vo, Atom name, const char *t) +{ + struct vo_x11_state *x11 = vo->x11; + XTextProperty prop = {0}; + + if (Xutf8TextListToTextProperty(x11->display, (char **)&t, 1, + XStdICCTextStyle, &prop) == Success) + { + XSetTextProperty(x11->display, x11->window, &prop, name); + } else { + // Strictly speaking this violates the ICCCM, but there's no way we + // can do this correctly. + vo_x11_set_property_utf8(vo, name, t); + } + + if (prop.value) + XFree(prop.value); +} + +static void vo_x11_update_window_title(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + + const char *title = vo_get_window_title(vo); + vo_x11_set_property_string(vo, XA_WM_NAME, title); + vo_x11_set_property_string(vo, XA_WM_ICON_NAME, title); + vo_x11_set_property_utf8(vo, x11->XA_NET_WM_NAME, title); + vo_x11_set_property_utf8(vo, x11->XA_NET_WM_ICON_NAME, title); +} + +// +static Window vo_x11_create_smooth_window(struct vo_x11_state *x11, Window mRoot, + Visual * vis, int x, int y, + unsigned int width, unsigned int height, + int depth, Colormap col_map) +{ + unsigned long xswamask = CWBorderPixel; + XSetWindowAttributes xswa; + Window ret_win; + + if (col_map != CopyFromParent) + { + xswa.colormap = col_map; + xswamask |= CWColormap; + } + xswa.background_pixel = 0; + xswa.border_pixel = 0; + xswa.backing_store = NotUseful; + xswa.bit_gravity = StaticGravity; + + ret_win = + XCreateWindow(x11->display, x11->rootwin, x, y, width, height, 0, depth, + CopyFromParent, vis, xswamask, &xswa); + XSetWMProtocols(x11->display, ret_win, &x11->XAWM_DELETE_WINDOW, 1); + if (x11->f_gc == None) + x11->f_gc = XCreateGC(x11->display, ret_win, 0, 0); + XSetForeground(x11->display, x11->f_gc, 0); + + return ret_win; +} + +/** + * \brief create and setup a window suitable for display + * \param vis Visual to use for creating the window + * \param x x position of window + * \param y y position of window + * \param width width of window + * \param height height of window + * \param flags flags for window creation. + * Only VOFLAG_FULLSCREEN is supported so far. + * \param col_map Colourmap for window or CopyFromParent if a specific colormap isn't needed + * \param classname name to use for the classhint + * + * This also does the grunt-work like setting Window Manager hints etc. + * If vo_window is already set it just moves and resizes it. + */ +void vo_x11_create_vo_window(struct vo *vo, XVisualInfo *vis, int x, int y, + unsigned int width, unsigned int height, int flags, + Colormap col_map, const char *classname) +{ + struct MPOpts *opts = vo->opts; + struct vo_x11_state *x11 = vo->x11; + Display *mDisplay = vo->x11->display; + if (WinID >= 0) { + vo_fs = flags & VOFLAG_FULLSCREEN; + x11->window = WinID ? (Window)WinID : x11->rootwin; + if (col_map != CopyFromParent) { + unsigned long xswamask = CWColormap; + XSetWindowAttributes xswa; + xswa.colormap = col_map; + XChangeWindowAttributes(mDisplay, x11->window, xswamask, &xswa); + XInstallColormap(mDisplay, col_map); + } + if (WinID) { + // Expose events can only really be handled by us, so request them. + vo_x11_selectinput_witherr(mDisplay, x11->window, ExposureMask); + } else + // Do not capture events since it might break the parent application + // if it relies on events being forwarded to the parent of WinID. + // It also is consistent with the w32_common.c code. + vo_x11_selectinput_witherr(mDisplay, x11->window, + StructureNotifyMask | KeyPressMask | PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | ExposureMask); + + vo_x11_update_geometry(vo, true); + goto final; + } + if (x11->window == None) { + vo_fs = 0; + vo->dwidth = width; + vo->dheight = height; + x11->window = vo_x11_create_smooth_window(x11, x11->rootwin, vis->visual, + x, y, width, height, vis->depth, col_map); + x11->window_state = VOFLAG_HIDDEN; + } + if (flags & VOFLAG_HIDDEN) + goto final; + if (x11->window_state & VOFLAG_HIDDEN) { + XSizeHints hint; + x11->window_state &= ~VOFLAG_HIDDEN; + vo_x11_classhint(vo, x11->window, classname); + vo_hidecursor(mDisplay, x11->window); + XSelectInput(mDisplay, x11->window, StructureNotifyMask); + hint.x = x; hint.y = y; + hint.width = width; hint.height = height; + hint.flags = PSize; + if (geometry_xy_changed) + hint.flags |= PPosition; + XSetWMNormalHints(mDisplay, x11->window, &hint); + if (!vo_border) vo_x11_decoration(vo, 0); + // map window + x11->xic = XCreateIC(x11->xim, + XNInputStyle, XIMPreeditNone | XIMStatusNone, + XNClientWindow, x11->window, + XNFocusWindow, x11->window, + NULL); + XSelectInput(mDisplay, x11->window, NoEventMask); + vo_x11_selectinput_witherr(mDisplay, x11->window, + StructureNotifyMask | KeyPressMask | PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | ExposureMask); + XMapWindow(mDisplay, x11->window); + vo_x11_clearwindow(vo, x11->window); + } + vo_x11_update_window_title(vo); + if (opts->vo_ontop) vo_x11_setlayer(vo, x11->window, opts->vo_ontop); + vo_x11_update_geometry(vo, !geometry_xy_changed); + vo_x11_nofs_sizepos(vo, vo->dx, vo->dy, width, height); + if (!!vo_fs != !!(flags & VOFLAG_FULLSCREEN)) + vo_x11_fullscreen(vo); + else if (vo_fs) { + // if we are already in fullscreen do not switch back and forth, just + // set the size values right. + vo->dwidth = vo->opts->vo_screenwidth; + vo->dheight = vo->opts->vo_screenheight; + } +final: + if (x11->vo_gc != None) + XFreeGC(mDisplay, x11->vo_gc); + x11->vo_gc = XCreateGC(mDisplay, x11->window, 0, NULL); + + XSync(mDisplay, False); + vo->event_fd = ConnectionNumber(x11->display); +} + +void vo_x11_clearwindow_part(struct vo *vo, Window vo_window, + int img_width, int img_height) +{ + struct vo_x11_state *x11 = vo->x11; + Display *mDisplay = vo->x11->display; + int u_dheight, u_dwidth, left_ov, left_ov2; + + if (x11->f_gc == None) + return; + + u_dheight = vo->dheight; + u_dwidth = vo->dwidth; + if ((u_dheight <= img_height) && (u_dwidth <= img_width)) + return; + + left_ov = (u_dheight - img_height) / 2; + left_ov2 = (u_dwidth - img_width) / 2; + + XFillRectangle(mDisplay, vo_window, x11->f_gc, 0, 0, u_dwidth, left_ov); + XFillRectangle(mDisplay, vo_window, x11->f_gc, 0, u_dheight - left_ov - 1, + u_dwidth, left_ov + 1); + + if (u_dwidth > img_width) + { + XFillRectangle(mDisplay, vo_window, x11->f_gc, 0, left_ov, left_ov2, + img_height); + XFillRectangle(mDisplay, vo_window, x11->f_gc, u_dwidth - left_ov2 - 1, + left_ov, left_ov2 + 1, img_height); + } + + XFlush(mDisplay); +} + +void vo_x11_clearwindow(struct vo *vo, Window vo_window) +{ + struct vo_x11_state *x11 = vo->x11; + struct MPOpts *opts = vo->opts; + if (x11->f_gc == None) + return; + XFillRectangle(x11->display, vo_window, x11->f_gc, 0, 0, + opts->vo_screenwidth, opts->vo_screenheight); + // + XFlush(x11->display); +} + + +void vo_x11_setlayer(struct vo *vo, Window vo_window, int layer) +{ + struct vo_x11_state *x11 = vo->x11; + if (WinID >= 0) + return; + + if (x11->fs_type & vo_wm_LAYER) + { + XClientMessageEvent xev; + + if (!x11->orig_layer) + x11->orig_layer = vo_x11_get_gnome_layer(x11, vo_window); + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.display = x11->display; + xev.window = vo_window; + xev.message_type = x11->XA_WIN_LAYER; + xev.format = 32; + xev.data.l[0] = layer ? fs_layer : x11->orig_layer; // if not fullscreen, stay on default layer + xev.data.l[1] = CurrentTime; + mp_msg(MSGT_VO, MSGL_V, + "[x11] Layered style stay on top (layer %ld).\n", + xev.data.l[0]); + XSendEvent(x11->display, x11->rootwin, False, SubstructureNotifyMask, + (XEvent *) & xev); + } else if (x11->fs_type & vo_wm_NETWM) + { + XClientMessageEvent xev; + char *state; + + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.message_type = x11->XA_NET_WM_STATE; + xev.display = x11->display; + xev.window = vo_window; + xev.format = 32; + xev.data.l[0] = layer; + + if (x11->fs_type & vo_wm_STAYS_ON_TOP) + xev.data.l[1] = x11->XA_NET_WM_STATE_STAYS_ON_TOP; + else if (x11->fs_type & vo_wm_ABOVE) + xev.data.l[1] = x11->XA_NET_WM_STATE_ABOVE; + else if (x11->fs_type & vo_wm_FULLSCREEN) + xev.data.l[1] = x11->XA_NET_WM_STATE_FULLSCREEN; + else if (x11->fs_type & vo_wm_BELOW) + // This is not fallback. We can safely assume that the situation + // where only NETWM_STATE_BELOW is supported doesn't exist. + xev.data.l[1] = x11->XA_NET_WM_STATE_BELOW; + + XSendEvent(x11->display, x11->rootwin, False, SubstructureRedirectMask, + (XEvent *) & xev); + state = XGetAtomName(x11->display, xev.data.l[1]); + mp_msg(MSGT_VO, MSGL_V, + "[x11] NET style stay on top (layer %d). Using state %s.\n", + layer, state); + XFree(state); + } +} + +static int vo_x11_get_fs_type(int supported) +{ + int i; + int type = supported; + + if (vo_fstype_list) + { + for (i = 0; vo_fstype_list[i]; i++) + { + int neg = 0; + char *arg = vo_fstype_list[i]; + + if (vo_fstype_list[i][0] == '-') + { + neg = 1; + arg = vo_fstype_list[i] + 1; + } + + if (!strncmp(arg, "layer", 5)) + { + if (!neg && (arg[5] == '=')) + { + char *endptr = NULL; + int layer = strtol(vo_fstype_list[i] + 6, &endptr, 10); + + if (endptr && *endptr == '\0' && layer >= 0 + && layer <= 15) + fs_layer = layer; + } + if (neg) + type &= ~vo_wm_LAYER; + else + type |= vo_wm_LAYER; + } else if (!strcmp(arg, "above")) + { + if (neg) + type &= ~vo_wm_ABOVE; + else + type |= vo_wm_ABOVE; + } else if (!strcmp(arg, "fullscreen")) + { + if (neg) + type &= ~vo_wm_FULLSCREEN; + else + type |= vo_wm_FULLSCREEN; + } else if (!strcmp(arg, "stays_on_top")) + { + if (neg) + type &= ~vo_wm_STAYS_ON_TOP; + else + type |= vo_wm_STAYS_ON_TOP; + } else if (!strcmp(arg, "below")) + { + if (neg) + type &= ~vo_wm_BELOW; + else + type |= vo_wm_BELOW; + } else if (!strcmp(arg, "netwm")) + { + if (neg) + type &= ~vo_wm_NETWM; + else + type |= vo_wm_NETWM; + } else if (!strcmp(arg, "none")) + type = 0; // clear; keep parsing + } + } + + return type; +} + +/** + * \brief update vo->dx, vo->dy, vo->dwidth and vo->dheight with current values of vo->x11->window + * \return returns current color depth of vo->x11->window + */ +int vo_x11_update_geometry(struct vo *vo, bool update_pos) +{ + struct vo_x11_state *x11 = vo->x11; + unsigned depth, w, h; + int dummy_int; + Window dummy_win; + XGetGeometry(x11->display, x11->window, &dummy_win, &dummy_int, &dummy_int, + &w, &h, &dummy_int, &depth); + if (w <= INT_MAX && h <= INT_MAX) { + vo->dwidth = w; + vo->dheight = h; + } + if (update_pos) + XTranslateCoordinates(x11->display, x11->window, x11->rootwin, 0, 0, + &vo->dx, &vo->dy, &dummy_win); + + return depth <= INT_MAX ? depth : 0; +} + +void vo_x11_fullscreen(struct vo *vo) +{ + struct MPOpts *opts = vo->opts; + struct vo_x11_state *x11 = vo->x11; + int x, y, w, h; + x = x11->vo_old_x; + y = x11->vo_old_y; + w = x11->vo_old_width; + h = x11->vo_old_height; + + if (WinID >= 0) { + vo_fs = !vo_fs; + return; + } + if (x11->fs_flip) + return; + + if (vo_fs) + { + vo_x11_ewmh_fullscreen(x11, _NET_WM_STATE_REMOVE); // removes fullscreen state if wm supports EWMH + vo_fs = VO_FALSE; + if (x11->size_changed_during_fs && (x11->fs_type & vo_wm_FULLSCREEN)) + vo_x11_nofs_sizepos(vo, vo->dx, vo->dy, x11->last_video_width, + x11->last_video_height); + x11->size_changed_during_fs = false; + } else + { + // win->fs + vo_x11_ewmh_fullscreen(x11, _NET_WM_STATE_ADD); // sends fullscreen state to be added if wm supports EWMH + + vo_fs = VO_TRUE; + if ( ! (x11->fs_type & vo_wm_FULLSCREEN) ) // not needed with EWMH fs + { + x11->vo_old_x = vo->dx; + x11->vo_old_y = vo->dy; + x11->vo_old_width = vo->dwidth; + x11->vo_old_height = vo->dheight; + } + update_xinerama_info(vo); + x = xinerama_x; + y = xinerama_y; + w = opts->vo_screenwidth; + h = opts->vo_screenheight; + } + { + long dummy; + + XGetWMNormalHints(x11->display, x11->window, &x11->vo_hint, &dummy); + if (!(x11->vo_hint.flags & PWinGravity)) + x11->old_gravity = NorthWestGravity; + else + x11->old_gravity = x11->vo_hint.win_gravity; + } + if (x11->wm_type == 0 && !(vo_fsmode & 16)) + { + XUnmapWindow(x11->display, x11->window); // required for MWM + XWithdrawWindow(x11->display, x11->window, x11->screen); + x11->fs_flip = 1; + } + + if ( ! (x11->fs_type & vo_wm_FULLSCREEN) ) // not needed with EWMH fs + { + vo_x11_decoration(vo, vo_border && !vo_fs); + vo_x11_sizehint(vo, x, y, w, h, 0); + vo_x11_setlayer(vo, x11->window, vo_fs); + + + XMoveResizeWindow(x11->display, x11->window, x, y, w, h); + } + /* some WMs lose ontop after fullscreen */ + if ((!(vo_fs)) & opts->vo_ontop) + vo_x11_setlayer(vo, x11->window, opts->vo_ontop); + + XMapRaised(x11->display, x11->window); + if ( ! (x11->fs_type & vo_wm_FULLSCREEN) ) // some WMs change window pos on map + XMoveResizeWindow(x11->display, x11->window, x, y, w, h); + XRaiseWindow(x11->display, x11->window); + XFlush(x11->display); +} + +void vo_x11_ontop(struct vo *vo) +{ + struct MPOpts *opts = vo->opts; + opts->vo_ontop = !opts->vo_ontop; + + vo_x11_setlayer(vo, vo->x11->window, opts->vo_ontop); +} + +void vo_x11_border(struct vo *vo) +{ + vo_border = !vo_border; + vo_x11_decoration(vo, vo_border && !vo_fs); +} + +/* + * XScreensaver stuff + */ + +static int screensaver_off; +static unsigned int time_last; + +void xscreensaver_heartbeat(struct vo_x11_state *x11) +{ + unsigned int time = GetTimerMS(); + + if (x11->display && screensaver_off && (time - time_last) > 30000) + { + time_last = time; + + XResetScreenSaver(x11->display); + } +} + +static int xss_suspend(Display *mDisplay, Bool suspend) +{ +#ifndef CONFIG_XSS + return 0; +#else + int event, error, major, minor; + if (XScreenSaverQueryExtension(mDisplay, &event, &error) != True || + XScreenSaverQueryVersion(mDisplay, &major, &minor) != True) + return 0; + if (major < 1 || (major == 1 && minor < 1)) + return 0; + XScreenSaverSuspend(mDisplay, suspend); + return 1; +#endif +} + +/* + * End of XScreensaver stuff + */ + +static void saver_on(Display * mDisplay) +{ + + if (!screensaver_off) + return; + screensaver_off = 0; + if (xss_suspend(mDisplay, False)) + return; +#ifdef CONFIG_XDPMS + if (dpms_disabled) + { + int nothing; + if (DPMSQueryExtension(mDisplay, ¬hing, ¬hing)) + { + if (!DPMSEnable(mDisplay)) + { // restoring power saving settings + mp_msg(MSGT_VO, MSGL_WARN, "DPMS not available?\n"); + } else + { + // DPMS does not seem to be enabled unless we call DPMSInfo + BOOL onoff; + CARD16 state; + + DPMSForceLevel(mDisplay, DPMSModeOn); + DPMSInfo(mDisplay, &state, &onoff); + if (onoff) + { + mp_msg(MSGT_VO, MSGL_V, + "Successfully enabled DPMS\n"); + } else + { + mp_msg(MSGT_VO, MSGL_WARN, "Could not enable DPMS\n"); + } + } + } + dpms_disabled = 0; + } +#endif +} + +static void saver_off(Display * mDisplay) +{ + int nothing; + + if (!stop_xscreensaver || screensaver_off) + return; + screensaver_off = 1; + if (xss_suspend(mDisplay, True)) + return; +#ifdef CONFIG_XDPMS + if (DPMSQueryExtension(mDisplay, ¬hing, ¬hing)) + { + BOOL onoff; + CARD16 state; + + DPMSInfo(mDisplay, &state, &onoff); + if (onoff) + { + Status stat; + + mp_msg(MSGT_VO, MSGL_V, "Disabling DPMS\n"); + dpms_disabled = 1; + stat = DPMSDisable(mDisplay); // monitor powersave off + mp_msg(MSGT_VO, MSGL_V, "DPMSDisable stat: %d\n", stat); + } + } +#endif +} + +static XErrorHandler old_handler = NULL; +static int selectinput_err = 0; +static int x11_selectinput_errorhandler(Display * display, + XErrorEvent * event) +{ + if (event->error_code == BadAccess) + { + selectinput_err = 1; + mp_msg(MSGT_VO, MSGL_ERR, + "X11 error: BadAccess during XSelectInput Call\n"); + mp_msg(MSGT_VO, MSGL_ERR, + "X11 error: The 'ButtonPressMask' mask of specified window has probably already used by another appication (see man XSelectInput)\n"); + /* If you think MPlayer should shutdown with this error, + * comment out the following line */ + return 0; + } + if (old_handler != NULL) + old_handler(display, event); + else + x11_errorhandler(display, event); + return 0; +} + +void vo_x11_selectinput_witherr(Display * display, Window w, + long event_mask) +{ + XSync(display, False); + old_handler = XSetErrorHandler(x11_selectinput_errorhandler); + selectinput_err = 0; + if (vo_nomouse_input) + { + XSelectInput(display, w, + event_mask & + (~(ButtonPressMask | ButtonReleaseMask))); + } else + { + XSelectInput(display, w, event_mask); + } + XSync(display, False); + XSetErrorHandler(old_handler); + if (selectinput_err) + { + mp_msg(MSGT_VO, MSGL_ERR, + "X11 error: mpv discards mouse control (reconfiguring)\n"); + XSelectInput(display, w, + event_mask & + (~ + (ButtonPressMask | ButtonReleaseMask | + PointerMotionMask))); + } +} + +#ifdef CONFIG_XF86VM +void vo_vm_switch(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + struct MPOpts *opts = vo->opts; + Display *mDisplay = x11->display; + int vm_event, vm_error; + int vm_ver, vm_rev; + int i, j, have_vm = 0; + int X = vo->dwidth, Y = vo->dheight; + int modeline_width, modeline_height; + + if (XF86VidModeQueryExtension(mDisplay, &vm_event, &vm_error)) + { + XF86VidModeQueryVersion(mDisplay, &vm_ver, &vm_rev); + mp_msg(MSGT_VO, MSGL_V, "XF86VidMode extension v%i.%i\n", vm_ver, + vm_rev); + have_vm = 1; + } else { + mp_msg(MSGT_VO, MSGL_WARN, + "XF86VidMode extension not available.\n"); + } + + if (have_vm) + { + if (vidmodes == NULL) + XF86VidModeGetAllModeLines(mDisplay, x11->screen, &modecount, + &vidmodes); + j = 0; + modeline_width = vidmodes[0]->hdisplay; + modeline_height = vidmodes[0]->vdisplay; + + for (i = 1; i < modecount; i++) + if ((vidmodes[i]->hdisplay >= X) + && (vidmodes[i]->vdisplay >= Y)) + if ((vidmodes[i]->hdisplay <= modeline_width) + && (vidmodes[i]->vdisplay <= modeline_height)) + { + modeline_width = vidmodes[i]->hdisplay; + modeline_height = vidmodes[i]->vdisplay; + j = i; + } + + mp_tmsg(MSGT_VO, MSGL_INFO, "XF86VM: Selected video mode %dx%d for image size %dx%d.\n", + modeline_width, modeline_height, X, Y); + XF86VidModeLockModeSwitch(mDisplay, x11->screen, 0); + XF86VidModeSwitchToMode(mDisplay, x11->screen, vidmodes[j]); + XF86VidModeSwitchToMode(mDisplay, x11->screen, vidmodes[j]); + + // FIXME: all this is more of a hack than proper solution + X = (opts->vo_screenwidth - modeline_width) / 2; + Y = (opts->vo_screenheight - modeline_height) / 2; + XF86VidModeSetViewPort(mDisplay, x11->screen, X, Y); + vo->dx = X; + vo->dy = Y; + vo->dwidth = modeline_width; + vo->dheight = modeline_height; + aspect_save_screenres(vo, modeline_width, modeline_height); + } +} + +void vo_vm_close(struct vo *vo) +{ + Display *dpy = vo->x11->display; + struct MPOpts *opts = vo->opts; + if (vidmodes != NULL) + { + int i; + + free(vidmodes); + vidmodes = NULL; + XF86VidModeGetAllModeLines(dpy, vo->x11->screen, &modecount, + &vidmodes); + for (i = 0; i < modecount; i++) + if ((vidmodes[i]->hdisplay == opts->vo_screenwidth) + && (vidmodes[i]->vdisplay == opts->vo_screenheight)) + { + mp_msg(MSGT_VO, MSGL_INFO, + "Returning to original mode %dx%d\n", + opts->vo_screenwidth, opts->vo_screenheight); + break; + } + + XF86VidModeSwitchToMode(dpy, vo->x11->screen, vidmodes[i]); + XF86VidModeSwitchToMode(dpy, vo->x11->screen, vidmodes[i]); + free(vidmodes); + vidmodes = NULL; + modecount = 0; + } +} + +double vo_vm_get_fps(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + int clock; + XF86VidModeModeLine modeline; + if (!XF86VidModeGetModeLine(x11->display, x11->screen, &clock, &modeline)) + return 0; + if (modeline.privsize) + XFree(modeline.private); + return 1e3 * clock / modeline.htotal / modeline.vtotal; +} +#endif + + +/* + * Scan the available visuals on this Display/Screen. Try to find + * the 'best' available TrueColor visual that has a decent color + * depth (at least 15bit). If there are multiple visuals with depth + * >= 15bit, we prefer visuals with a smaller color depth. + */ +int vo_find_depth_from_visuals(Display * dpy, int screen, + Visual ** visual_return) +{ + XVisualInfo visual_tmpl; + XVisualInfo *visuals; + int nvisuals, i; + int bestvisual = -1; + int bestvisual_depth = -1; + + visual_tmpl.screen = screen; + visual_tmpl.class = TrueColor; + visuals = XGetVisualInfo(dpy, + VisualScreenMask | VisualClassMask, + &visual_tmpl, &nvisuals); + if (visuals != NULL) + { + for (i = 0; i < nvisuals; i++) + { + mp_msg(MSGT_VO, MSGL_V, + "vo: X11 truecolor visual %#lx, depth %d, R:%lX G:%lX B:%lX\n", + visuals[i].visualid, visuals[i].depth, + visuals[i].red_mask, visuals[i].green_mask, + visuals[i].blue_mask); + /* + * Save the visual index and its depth, if this is the first + * truecolor visul, or a visual that is 'preferred' over the + * previous 'best' visual. + */ + if (bestvisual_depth == -1 + || (visuals[i].depth >= 15 + && (visuals[i].depth < bestvisual_depth + || bestvisual_depth < 15))) + { + bestvisual = i; + bestvisual_depth = visuals[i].depth; + } + } + + if (bestvisual != -1 && visual_return != NULL) + *visual_return = visuals[bestvisual].visual; + + XFree(visuals); + } + return bestvisual_depth; +} + + +static Colormap cmap = None; +static XColor cols[256]; +static int cm_size, red_mask, green_mask, blue_mask; + + +Colormap vo_x11_create_colormap(struct vo *vo, XVisualInfo *vinfo) +{ + struct vo_x11_state *x11 = vo->x11; + unsigned k, r, g, b, ru, gu, bu, m, rv, gv, bv, rvu, gvu, bvu; + + if (vinfo->class != DirectColor) + return XCreateColormap(x11->display, x11->rootwin, vinfo->visual, + AllocNone); + + /* can this function get called twice or more? */ + if (cmap) + return cmap; + cm_size = vinfo->colormap_size; + red_mask = vinfo->red_mask; + green_mask = vinfo->green_mask; + blue_mask = vinfo->blue_mask; + ru = (red_mask & (red_mask - 1)) ^ red_mask; + gu = (green_mask & (green_mask - 1)) ^ green_mask; + bu = (blue_mask & (blue_mask - 1)) ^ blue_mask; + rvu = 65536ull * ru / (red_mask + ru); + gvu = 65536ull * gu / (green_mask + gu); + bvu = 65536ull * bu / (blue_mask + bu); + r = g = b = 0; + rv = gv = bv = 0; + m = DoRed | DoGreen | DoBlue; + for (k = 0; k < cm_size; k++) + { + int t; + + cols[k].pixel = r | g | b; + cols[k].red = rv; + cols[k].green = gv; + cols[k].blue = bv; + cols[k].flags = m; + t = (r + ru) & red_mask; + if (t < r) + m &= ~DoRed; + r = t; + t = (g + gu) & green_mask; + if (t < g) + m &= ~DoGreen; + g = t; + t = (b + bu) & blue_mask; + if (t < b) + m &= ~DoBlue; + b = t; + rv += rvu; + gv += gvu; + bv += bvu; + } + cmap = XCreateColormap(x11->display, x11->rootwin, vinfo->visual, AllocAll); + XStoreColors(x11->display, cmap, cols, cm_size); + return cmap; +} + +/* + * Via colormaps/gamma ramps we can do gamma, brightness, contrast, + * hue and red/green/blue intensity, but we cannot do saturation. + * Currently only gamma, brightness and contrast are implemented. + * Is there sufficient interest for hue and/or red/green/blue intensity? + */ +/* these values have range [-100,100] and are initially 0 */ +static int vo_gamma = 0; +static int vo_brightness = 0; +static int vo_contrast = 0; + +static int transform_color(float val, + float brightness, float contrast, float gamma) { + float s = pow(val, gamma); + s = (s - 0.5) * contrast + 0.5; + s += brightness; + if (s < 0) + s = 0; + if (s > 1) + s = 1; + return (unsigned short) (s * 65535); +} + +uint32_t vo_x11_set_equalizer(struct vo *vo, const char *name, int value) +{ + float gamma, brightness, contrast; + float rf, gf, bf; + int k; + + /* + * IMPLEMENTME: consider using XF86VidModeSetGammaRamp in the case + * of TrueColor-ed window but be careful: + * Unlike the colormaps, which are private for the X client + * who created them and thus automatically destroyed on client + * disconnect, this gamma ramp is a system-wide (X-server-wide) + * setting and _must_ be restored before the process exits. + * Unforunately when the process crashes (or gets killed + * for some reason) it is impossible to restore the setting, + * and such behaviour could be rather annoying for the users. + */ + if (cmap == None) + return VO_NOTAVAIL; + + if (!strcasecmp(name, "brightness")) + vo_brightness = value; + else if (!strcasecmp(name, "contrast")) + vo_contrast = value; + else if (!strcasecmp(name, "gamma")) + vo_gamma = value; + else + return VO_NOTIMPL; + + brightness = 0.01 * vo_brightness; + contrast = tan(0.0095 * (vo_contrast + 100) * M_PI / 4); + gamma = pow(2, -0.02 * vo_gamma); + + rf = (float) ((red_mask & (red_mask - 1)) ^ red_mask) / red_mask; + gf = (float) ((green_mask & (green_mask - 1)) ^ green_mask) / + green_mask; + bf = (float) ((blue_mask & (blue_mask - 1)) ^ blue_mask) / blue_mask; + + /* now recalculate the colormap using the newly set value */ + for (k = 0; k < cm_size; k++) + { + cols[k].red = transform_color(rf * k, brightness, contrast, gamma); + cols[k].green = transform_color(gf * k, brightness, contrast, gamma); + cols[k].blue = transform_color(bf * k, brightness, contrast, gamma); + } + + XStoreColors(vo->x11->display, cmap, cols, cm_size); + XFlush(vo->x11->display); + return VO_TRUE; +} + +uint32_t vo_x11_get_equalizer(const char *name, int *value) +{ + if (cmap == None) + return VO_NOTAVAIL; + if (!strcasecmp(name, "brightness")) + *value = vo_brightness; + else if (!strcasecmp(name, "contrast")) + *value = vo_contrast; + else if (!strcasecmp(name, "gamma")) + *value = vo_gamma; + else + return VO_NOTIMPL; + return VO_TRUE; +} + +bool vo_x11_screen_is_composited(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + return XGetSelectionOwner(x11->display, x11->XA_NET_WM_CM) != None; +} + +#ifdef CONFIG_XV + +static int xv_find_atom(struct vo *vo, uint32_t xv_port, const char *name, + bool get, int *min, int *max) +{ + Atom atom = None; + int howmany = 0; + XvAttribute *attributes = XvQueryPortAttributes(vo->x11->display, xv_port, + &howmany); + for (int i = 0; i < howmany && attributes; i++) { + int flag = get ? XvGettable : XvSettable; + if (attributes[i].flags & flag) { + atom = XInternAtom(vo->x11->display, attributes[i].name, True); + *min = attributes[i].min_value; + *max = attributes[i].max_value; +/* since we have SET_DEFAULTS first in our list, we can check if it's available + then trigger it if it's ok so that the other values are at default upon query */ + if (atom != None) { + if (!strcmp(attributes[i].name, "XV_BRIGHTNESS") && + (!strcasecmp(name, "brightness"))) + break; + else if (!strcmp(attributes[i].name, "XV_CONTRAST") && + (!strcasecmp(name, "contrast"))) + break; + else if (!strcmp(attributes[i].name, "XV_SATURATION") && + (!strcasecmp(name, "saturation"))) + break; + else if (!strcmp(attributes[i].name, "XV_HUE") && + (!strcasecmp(name, "hue"))) + break; + if (!strcmp(attributes[i].name, "XV_RED_INTENSITY") && + (!strcasecmp(name, "red_intensity"))) + break; + else if (!strcmp(attributes[i].name, "XV_GREEN_INTENSITY") + && (!strcasecmp(name, "green_intensity"))) + break; + else if (!strcmp(attributes[i].name, "XV_BLUE_INTENSITY") + && (!strcasecmp(name, "blue_intensity"))) + break; + else if ((!strcmp(attributes[i].name, "XV_ITURBT_709") //NVIDIA + || !strcmp(attributes[i].name, "XV_COLORSPACE"))//ATI + && (!strcasecmp(name, "bt_709"))) + break; + atom = None; + continue; + } + } + } + XFree(attributes); + return atom; +} + +int vo_xv_set_eq(struct vo *vo, uint32_t xv_port, const char *name, int value) +{ + mp_dbg(MSGT_VO, MSGL_V, "xv_set_eq called! (%s, %d)\n", name, value); + + int min, max; + int atom = xv_find_atom(vo, xv_port, name, false, &min, &max); + if (atom != None) { + // -100 -> min + // 0 -> (max+min)/2 + // +100 -> max + int port_value = (value + 100) * (max - min) / 200 + min; + XvSetPortAttribute(vo->x11->display, xv_port, atom, port_value); + return VO_TRUE; + } + return VO_FALSE; +} + +int vo_xv_get_eq(struct vo *vo, uint32_t xv_port, const char *name, int *value) +{ + int min, max; + int atom = xv_find_atom(vo, xv_port, name, true, &min, &max); + if (atom != None) { + int port_value = 0; + XvGetPortAttribute(vo->x11->display, xv_port, atom, &port_value); + + *value = (port_value - min) * 200 / (max - min) - 100; + mp_dbg(MSGT_VO, MSGL_V, "xv_get_eq called! (%s, %d)\n", + name, *value); + return VO_TRUE; + } + return VO_FALSE; +} + +/** + * \brief Interns the requested atom if it is available. + * + * \param atom_name String containing the name of the requested atom. + * + * \return Returns the atom if available, else None is returned. + * + */ +static Atom xv_intern_atom_if_exists(struct vo_x11_state *x11, + char const *atom_name) +{ + XvAttribute * attributes; + int attrib_count,i; + Atom xv_atom = None; + + attributes = XvQueryPortAttributes(x11->display, x11->xv_port, &attrib_count ); + if( attributes!=NULL ) + { + for ( i = 0; i < attrib_count; ++i ) + { + if ( strcmp(attributes[i].name, atom_name ) == 0 ) + { + xv_atom = XInternAtom(x11->display, atom_name, False ); + break; // found what we want, break out + } + } + XFree( attributes ); + } + + return xv_atom; +} + +/** + * \brief Try to enable vsync for xv. + * \return Returns -1 if not available, 0 on failure and 1 on success. + */ +int vo_xv_enable_vsync(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + Atom xv_atom = xv_intern_atom_if_exists(x11, "XV_SYNC_TO_VBLANK"); + if (xv_atom == None) + return -1; + return XvSetPortAttribute(x11->display, x11->xv_port, xv_atom, 1) == Success; +} + +/** + * \brief Get maximum supported source image dimensions. + * + * This function does not set the variables pointed to by + * width and height if the information could not be retrieved, + * so the caller is reponsible for properly initializing them. + * + * \param width [out] The maximum width gets stored here. + * \param height [out] The maximum height gets stored here. + * + */ +void vo_xv_get_max_img_dim(struct vo *vo, uint32_t * width, uint32_t * height) +{ + struct vo_x11_state *x11 = vo->x11; + XvEncodingInfo * encodings; + //unsigned long num_encodings, idx; to int or too long?! + unsigned int num_encodings, idx; + + XvQueryEncodings(x11->display, x11->xv_port, &num_encodings, &encodings); + + if ( encodings ) + { + for ( idx = 0; idx < num_encodings; ++idx ) + { + if ( strcmp( encodings[idx].name, "XV_IMAGE" ) == 0 ) + { + *width = encodings[idx].width; + *height = encodings[idx].height; + break; + } + } + } + + mp_msg( MSGT_VO, MSGL_V, + "[xv common] Maximum source image dimensions: %ux%u\n", + *width, *height ); + + XvFreeEncodingInfo( encodings ); +} + +/** + * \brief Print information about the colorkey method and source. + * + * \param ck_handling Integer value containing the information about + * colorkey handling (see x11_common.h). + * + * Outputs the content of |ck_handling| as a readable message. + * + */ +static void vo_xv_print_ck_info(struct vo_x11_state *x11) +{ + mp_msg( MSGT_VO, MSGL_V, "[xv common] " ); + + switch ( x11->xv_ck_info.method ) + { + case CK_METHOD_NONE: + mp_msg( MSGT_VO, MSGL_V, "Drawing no colorkey.\n" ); return; + case CK_METHOD_AUTOPAINT: + mp_msg( MSGT_VO, MSGL_V, "Colorkey is drawn by Xv." ); break; + case CK_METHOD_MANUALFILL: + mp_msg( MSGT_VO, MSGL_V, "Drawing colorkey manually." ); break; + case CK_METHOD_BACKGROUND: + mp_msg( MSGT_VO, MSGL_V, "Colorkey is drawn as window background." ); break; + } + + mp_msg( MSGT_VO, MSGL_V, "\n[xv common] " ); + + switch ( x11->xv_ck_info.source ) + { + case CK_SRC_CUR: + mp_msg( MSGT_VO, MSGL_V, "Using colorkey from Xv (0x%06lx).\n", + x11->xv_colorkey ); + break; + case CK_SRC_USE: + if ( x11->xv_ck_info.method == CK_METHOD_AUTOPAINT ) + { + mp_msg( MSGT_VO, MSGL_V, + "Ignoring colorkey from mpv (0x%06lx).\n", + x11->xv_colorkey ); + } + else + { + mp_msg( MSGT_VO, MSGL_V, + "Using colorkey from mpv (0x%06lx)." + " Use -colorkey to change.\n", + x11->xv_colorkey ); + } + break; + case CK_SRC_SET: + mp_msg( MSGT_VO, MSGL_V, + "Setting and using colorkey from mpv (0x%06lx)." + " Use -colorkey to change.\n", + x11->xv_colorkey ); + break; + } +} +/** + * \brief Init colorkey depending on the settings in xv_ck_info. + * + * \return Returns 0 on failure and 1 on success. + * + * Sets the colorkey variable according to the CK_SRC_* and CK_METHOD_* + * flags in xv_ck_info. + * + * Possiblilities: + * * Methods + * - manual colorkey drawing ( CK_METHOD_MANUALFILL ) + * - set colorkey as window background ( CK_METHOD_BACKGROUND ) + * - let Xv paint the colorkey ( CK_METHOD_AUTOPAINT ) + * * Sources + * - use currently set colorkey ( CK_SRC_CUR ) + * - use colorkey in vo_colorkey ( CK_SRC_USE ) + * - use and set colorkey in vo_colorkey ( CK_SRC_SET ) + * + * NOTE: If vo_colorkey has bits set after the first 3 low order bytes + * we don't draw anything as this means it was forced to off. + */ +int vo_xv_init_colorkey(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + Atom xv_atom; + int rez; + + /* check if colorkeying is needed */ + xv_atom = xv_intern_atom_if_exists(vo->x11, "XV_COLORKEY"); + + /* if we have to deal with colorkeying ... */ + if( xv_atom != None && !(vo_colorkey & 0xFF000000) ) + { + /* check if we should use the colorkey specified in vo_colorkey */ + if ( x11->xv_ck_info.source != CK_SRC_CUR ) + { + x11->xv_colorkey = vo_colorkey; + + /* check if we have to set the colorkey too */ + if ( x11->xv_ck_info.source == CK_SRC_SET ) + { + xv_atom = XInternAtom(x11->display, "XV_COLORKEY",False); + + rez = XvSetPortAttribute(x11->display, x11->xv_port, xv_atom, vo_colorkey); + if ( rez != Success ) + { + mp_msg( MSGT_VO, MSGL_FATAL, + "[xv common] Couldn't set colorkey!\n" ); + return 0; // error setting colorkey + } + } + } + else + { + int colorkey_ret; + + rez=XvGetPortAttribute(x11->display,x11->xv_port, xv_atom, &colorkey_ret); + if ( rez == Success ) + { + x11->xv_colorkey = colorkey_ret; + } + else + { + mp_msg( MSGT_VO, MSGL_FATAL, + "[xv common] Couldn't get colorkey!" + "Maybe the selected Xv port has no overlay.\n" ); + return 0; // error getting colorkey + } + } + + xv_atom = xv_intern_atom_if_exists(vo->x11, "XV_AUTOPAINT_COLORKEY"); + + /* should we draw the colorkey ourselves or activate autopainting? */ + if ( x11->xv_ck_info.method == CK_METHOD_AUTOPAINT ) + { + rez = !Success; // reset rez to something different than Success + + if ( xv_atom != None ) // autopaint is supported + { + rez = XvSetPortAttribute(x11->display, x11->xv_port, xv_atom, 1); + } + + if ( rez != Success ) + { + // fallback to manual colorkey drawing + x11->xv_ck_info.method = CK_METHOD_MANUALFILL; + } + } + else // disable colorkey autopainting if supported + { + if ( xv_atom != None ) // we have autopaint attribute + { + XvSetPortAttribute(x11->display, x11->xv_port, xv_atom, 0); + } + } + } + else // do no colorkey drawing at all + { + x11->xv_ck_info.method = CK_METHOD_NONE; + } /* end: should we draw colorkey */ + + /* output information about the current colorkey settings */ + vo_xv_print_ck_info(x11); + + return 1; // success +} + +/** + * \brief Draw the colorkey on the video window. + * + * Draws the colorkey depending on the set method ( colorkey_handling ). + * + * Also draws the black bars ( when the video doesn't fit the display in + * fullscreen ) separately, so they don't overlap with the video area. + * It doesn't call XFlush. + * + */ +void vo_xv_draw_colorkey(struct vo *vo, int32_t x, int32_t y, + int32_t w, int32_t h) +{ + struct vo_x11_state *x11 = vo->x11; + if( x11->xv_ck_info.method == CK_METHOD_MANUALFILL || + x11->xv_ck_info.method == CK_METHOD_BACKGROUND )//less tearing than XClearWindow() + { + XSetForeground(x11->display, x11->vo_gc, x11->xv_colorkey ); + XFillRectangle(x11->display, x11->window, x11->vo_gc, + x, y, + w, h ); + } +} + +/** \brief Tests if a valid argument for the ck suboption was given. */ +int xv_test_ck( void * arg ) +{ + strarg_t * strarg = (strarg_t *)arg; + + if ( strargcmp( strarg, "use" ) == 0 || + strargcmp( strarg, "set" ) == 0 || + strargcmp( strarg, "cur" ) == 0 ) + { + return 1; + } + + return 0; +} +/** \brief Tests if a valid arguments for the ck-method suboption was given. */ +int xv_test_ckm( void * arg ) +{ + strarg_t * strarg = (strarg_t *)arg; + + if ( strargcmp( strarg, "bg" ) == 0 || + strargcmp( strarg, "man" ) == 0 || + strargcmp( strarg, "auto" ) == 0 ) + { + return 1; + } + + return 0; +} + +/** + * \brief Modify the colorkey_handling var according to str + * + * Checks if a valid pointer ( not NULL ) to the string + * was given. And in that case modifies the colorkey_handling + * var to reflect the requested behaviour. + * If nothing happens the content of colorkey_handling stays + * the same. + * + * \param str Pointer to the string or NULL + * + */ +void xv_setup_colorkeyhandling(struct vo *vo, const char *ck_method_str, + const char *ck_str) +{ + struct vo_x11_state *x11 = vo->x11; + /* check if a valid pointer to the string was passed */ + if ( ck_str ) + { + if ( strncmp( ck_str, "use", 3 ) == 0 ) + { + x11->xv_ck_info.source = CK_SRC_USE; + } + else if ( strncmp( ck_str, "set", 3 ) == 0 ) + { + x11->xv_ck_info.source = CK_SRC_SET; + } + } + /* check if a valid pointer to the string was passed */ + if ( ck_method_str ) + { + if ( strncmp( ck_method_str, "bg", 2 ) == 0 ) + { + x11->xv_ck_info.method = CK_METHOD_BACKGROUND; + } + else if ( strncmp( ck_method_str, "man", 3 ) == 0 ) + { + x11->xv_ck_info.method = CK_METHOD_MANUALFILL; + } + else if ( strncmp( ck_method_str, "auto", 4 ) == 0 ) + { + x11->xv_ck_info.method = CK_METHOD_AUTOPAINT; + } + } +} + +#endif + +struct vo_x11_state *vo_x11_init_state(void) +{ + struct vo_x11_state *s = talloc_ptrtype(NULL, s); + *s = (struct vo_x11_state){ + .xv_ck_info = { CK_METHOD_MANUALFILL, CK_SRC_CUR }, + .olddecor = MWM_DECOR_ALL, + .oldfuncs = MWM_FUNC_MOVE | MWM_FUNC_CLOSE | MWM_FUNC_MINIMIZE | + MWM_FUNC_MAXIMIZE | MWM_FUNC_RESIZE, + .old_gravity = NorthWestGravity, + }; + return s; +} diff --git a/video/out/x11_common.h b/video/out/x11_common.h new file mode 100644 index 0000000000..cb8b39a3b1 --- /dev/null +++ b/video/out/x11_common.h @@ -0,0 +1,184 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_X11_COMMON_H +#define MPLAYER_X11_COMMON_H + +#include <stdint.h> +#include <stdbool.h> +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +#include "config.h" + +struct vo; + +struct vo_x11_state { + Display *display; + Window window; + Window rootwin; + int screen; + int display_is_local; + int depthonscreen; + + XIM xim; + XIC xic; + + GC vo_gc; + + struct xv_ck_info_s { + int method; ///< CK_METHOD_* constants + int source; ///< CK_SRC_* constants + } xv_ck_info; + unsigned long xv_colorkey; + unsigned int xv_port; + + int wm_type; + int fs_type; + int window_state; + int fs_flip; + + GC f_gc; + XSizeHints vo_hint; + unsigned int mouse_timer; + int mouse_waiting_hide; + int orig_layer; + int old_gravity; + int vo_old_x; + int vo_old_y; + int vo_old_width; + int vo_old_height; + + /* Keep track of original video width/height to determine when to + * resize window when reconfiguring. Resize window when video size + * changes, but don't force window size changes as long as video size + * stays the same (even if that size is different from the current + * window size after the user modified the latter). */ + int last_video_width; + int last_video_height; + /* Video size changed during fullscreen when we couldn't tell the new + * size to the window manager. Must set window size when turning + * fullscreen off. */ + bool size_changed_during_fs; + + unsigned int olddecor; + unsigned int oldfuncs; + XComposeStatus compose_status; + + Atom XA_NET_SUPPORTED; + Atom XA_NET_WM_STATE; + Atom XA_NET_WM_STATE_FULLSCREEN; + Atom XA_NET_WM_STATE_ABOVE; + Atom XA_NET_WM_STATE_STAYS_ON_TOP; + Atom XA_NET_WM_STATE_BELOW; + Atom XA_NET_WM_PID; + Atom XA_NET_WM_NAME; + Atom XA_NET_WM_ICON_NAME; + Atom XA_WIN_PROTOCOLS; + Atom XA_WIN_LAYER; + Atom XA_WIN_HINTS; + Atom XAWM_PROTOCOLS; + Atom XAWM_DELETE_WINDOW; + Atom XAUTF8_STRING; + Atom XA_NET_WM_CM; +}; + +#define vo_wm_LAYER 1 +#define vo_wm_FULLSCREEN 2 +#define vo_wm_STAYS_ON_TOP 4 +#define vo_wm_ABOVE 8 +#define vo_wm_BELOW 16 +#define vo_wm_NETWM (vo_wm_FULLSCREEN | vo_wm_STAYS_ON_TOP | vo_wm_ABOVE | vo_wm_BELOW) + +/* EWMH state actions, see + http://freedesktop.org/Standards/wm-spec/index.html#id2768769 */ +#define _NET_WM_STATE_REMOVE 0 /* remove/unset property */ +#define _NET_WM_STATE_ADD 1 /* add/set property */ +#define _NET_WM_STATE_TOGGLE 2 /* toggle property */ + +extern int metacity_hack; + +extern char** vo_fstype_list; + +extern char *mDisplayName; + +struct vo_x11_state *vo_x11_init_state(void); +int vo_init(struct vo *vo); +void vo_uninit(struct vo_x11_state *x11); +void vo_x11_decoration(struct vo *vo, int d ); +void vo_x11_classhint(struct vo *vo, Window window, const char *name); +void vo_x11_sizehint(struct vo *vo, int x, int y, int width, int height, int max); +int vo_x11_check_events(struct vo *vo); +void vo_x11_selectinput_witherr(Display *display, Window w, long event_mask); +void vo_x11_fullscreen(struct vo *vo); +int vo_x11_update_geometry(struct vo *vo, bool update_pos); +void vo_x11_setlayer(struct vo *vo, Window vo_window, int layer); +void vo_x11_uninit(struct vo *vo); +Colormap vo_x11_create_colormap(struct vo *vo, XVisualInfo *vinfo); +uint32_t vo_x11_set_equalizer(struct vo *vo, const char *name, int value); +uint32_t vo_x11_get_equalizer(const char *name, int *value); +bool vo_x11_screen_is_composited(struct vo *vo); +void fstype_help(void); +void vo_x11_create_vo_window(struct vo *vo, XVisualInfo *vis, + int x, int y, unsigned int width, unsigned int height, int flags, + Colormap col_map, const char *classname); +void vo_x11_clearwindow_part(struct vo *vo, Window vo_window, + int img_width, int img_height); +void vo_x11_clearwindow(struct vo *vo, Window vo_window); +void vo_x11_ontop(struct vo *vo); +void vo_x11_border(struct vo *vo); +void vo_x11_ewmh_fullscreen(struct vo_x11_state *x11, int action); + + +int vo_xv_set_eq(struct vo *vo, uint32_t xv_port, const char *name, int value); +int vo_xv_get_eq(struct vo *vo, uint32_t xv_port, const char *name, int *value); + +int vo_xv_enable_vsync(struct vo *vo); + +void vo_xv_get_max_img_dim(struct vo *vo, uint32_t * width, uint32_t * height); + +/*** colorkey handling ***/ + +#define CK_METHOD_NONE 0 ///< no colorkey drawing +#define CK_METHOD_BACKGROUND 1 ///< set colorkey as window background +#define CK_METHOD_AUTOPAINT 2 ///< let xv draw the colorkey +#define CK_METHOD_MANUALFILL 3 ///< manually draw the colorkey +#define CK_SRC_USE 0 ///< use specified / default colorkey +#define CK_SRC_SET 1 ///< use and set specified / default colorkey +#define CK_SRC_CUR 2 ///< use current colorkey ( get it from xv ) + +int vo_xv_init_colorkey(struct vo *vo); +void vo_xv_draw_colorkey(struct vo *vo, int32_t x, int32_t y, int32_t w, int32_t h); +void xv_setup_colorkeyhandling(struct vo *vo, const char *ck_method_str, const char *ck_str); + +/*** test functions for common suboptions ***/ +int xv_test_ck( void * arg ); +int xv_test_ckm( void * arg ); + +#ifdef CONFIG_XF86VM +void vo_vm_switch(struct vo *vo); +void vo_vm_close(struct vo *vo); +double vo_vm_get_fps(struct vo *vo); +#endif + +void update_xinerama_info(struct vo *vo); + +int vo_find_depth_from_visuals(Display *dpy, int screen, Visual **visual_return); +void xscreensaver_heartbeat(struct vo_x11_state *x11); + +#endif /* MPLAYER_X11_COMMON_H */ diff --git a/video/sws_utils.c b/video/sws_utils.c new file mode 100644 index 0000000000..4fc5639e55 --- /dev/null +++ b/video/sws_utils.c @@ -0,0 +1,199 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <assert.h> + +#include <libavutil/opt.h> + +#include "libmpcodecs/sws_utils.h" + +#include "libmpcodecs/mp_image.h" +#include "libmpcodecs/img_format.h" +#include "fmt-conversion.h" +#include "libvo/csputils.h" +#include "mp_msg.h" + +//global sws_flags from the command line +int sws_flags = 2; + +float sws_lum_gblur = 0.0; +float sws_chr_gblur = 0.0; +int sws_chr_vshift = 0; +int sws_chr_hshift = 0; +float sws_chr_sharpen = 0.0; +float sws_lum_sharpen = 0.0; + +//global srcFilter +static SwsFilter *src_filter = NULL; + +void sws_getFlagsAndFilterFromCmdLine(int *flags, SwsFilter **srcFilterParam, + SwsFilter **dstFilterParam) +{ + static int firstTime = 1; + *flags = 0; + + if (firstTime) { + firstTime = 0; + *flags = SWS_PRINT_INFO; + } else if (mp_msg_test(MSGT_VFILTER, MSGL_DBG2)) + *flags = SWS_PRINT_INFO; + + if (src_filter) + sws_freeFilter(src_filter); + + src_filter = sws_getDefaultFilter( + sws_lum_gblur, sws_chr_gblur, + sws_lum_sharpen, sws_chr_sharpen, + sws_chr_hshift, sws_chr_vshift, verbose > 1); + + switch (sws_flags) { + case 0: *flags |= SWS_FAST_BILINEAR; + break; + case 1: *flags |= SWS_BILINEAR; + break; + case 2: *flags |= SWS_BICUBIC; + break; + case 3: *flags |= SWS_X; + break; + case 4: *flags |= SWS_POINT; + break; + case 5: *flags |= SWS_AREA; + break; + case 6: *flags |= SWS_BICUBLIN; + break; + case 7: *flags |= SWS_GAUSS; + break; + case 8: *flags |= SWS_SINC; + break; + case 9: *flags |= SWS_LANCZOS; + break; + case 10: *flags |= SWS_SPLINE; + break; + default: *flags |= SWS_BILINEAR; + break; + } + + *srcFilterParam = src_filter; + *dstFilterParam = NULL; +} + +// will use sws_flags & src_filter (from cmd line) +static struct SwsContext *sws_getContextFromCmdLine2(int srcW, int srcH, + int srcFormat, int dstW, + int dstH, int dstFormat, + int extraflags) +{ + int flags; + SwsFilter *dstFilterParam, *srcFilterParam; + enum PixelFormat dfmt, sfmt; + + dfmt = imgfmt2pixfmt(dstFormat); + sfmt = imgfmt2pixfmt(srcFormat); + if (srcFormat == IMGFMT_RGB8 || srcFormat == IMGFMT_BGR8) + sfmt = PIX_FMT_PAL8; + sws_getFlagsAndFilterFromCmdLine(&flags, &srcFilterParam, &dstFilterParam); + + return sws_getContext(srcW, srcH, sfmt, dstW, dstH, dfmt, flags | + extraflags, srcFilterParam, dstFilterParam, + NULL); +} + +struct SwsContext *sws_getContextFromCmdLine(int srcW, int srcH, int srcFormat, + int dstW, int dstH, + int dstFormat) +{ + return sws_getContextFromCmdLine2(srcW, srcH, srcFormat, dstW, dstH, + dstFormat, + 0); +} + +struct SwsContext *sws_getContextFromCmdLine_hq(int srcW, int srcH, + int srcFormat, int dstW, + int dstH, + int dstFormat) +{ + return sws_getContextFromCmdLine2( + srcW, srcH, srcFormat, dstW, dstH, dstFormat, + SWS_FULL_CHR_H_INT | SWS_FULL_CHR_H_INP | + SWS_ACCURATE_RND | SWS_BITEXACT); +} + +bool mp_sws_supported_format(int imgfmt) +{ + enum PixelFormat av_format = imgfmt2pixfmt(imgfmt); + + return av_format != PIX_FMT_NONE && sws_isSupportedInput(av_format) + && sws_isSupportedOutput(av_format); +} + +static int mp_csp_to_sws_colorspace(enum mp_csp csp) +{ + switch (csp) { + case MP_CSP_BT_601: return SWS_CS_ITU601; + case MP_CSP_BT_709: return SWS_CS_ITU709; + case MP_CSP_SMPTE_240M: return SWS_CS_SMPTE240M; + default: return SWS_CS_DEFAULT; + } +} + +void mp_image_swscale(struct mp_image *dst, struct mp_image *src, + int my_sws_flags) +{ + enum PixelFormat s_fmt = imgfmt2pixfmt(src->imgfmt); + if (src->imgfmt == IMGFMT_RGB8 || src->imgfmt == IMGFMT_BGR8) + s_fmt = PIX_FMT_PAL8; + int s_csp = mp_csp_to_sws_colorspace(mp_image_csp(src)); + int s_range = mp_image_levels(src) == MP_CSP_LEVELS_PC; + + enum PixelFormat d_fmt = imgfmt2pixfmt(dst->imgfmt); + int d_csp = mp_csp_to_sws_colorspace(mp_image_csp(dst)); + int d_range = mp_image_levels(dst) == MP_CSP_LEVELS_PC; + + // Work around libswscale bug #1852 (fixed in ffmpeg commit 8edf9b1fa): + // setting range flags for RGB gives random bogus results. + // Newer libswscale always ignores range flags for RGB. + bool s_yuv = src->flags & MP_IMGFLAG_YUV; + bool d_yuv = dst->flags & MP_IMGFLAG_YUV; + s_range = s_range && s_yuv; + d_range = d_range && d_yuv; + + struct SwsContext *sws = sws_alloc_context(); + + av_opt_set_int(sws, "sws_flags", my_sws_flags, 0); + + av_opt_set_int(sws, "srcw", src->w, 0); + av_opt_set_int(sws, "srch", src->h, 0); + av_opt_set_int(sws, "src_format", s_fmt, 0); + + av_opt_set_int(sws, "dstw", dst->w, 0); + av_opt_set_int(sws, "dsth", dst->h, 0); + av_opt_set_int(sws, "dst_format", d_fmt, 0); + + sws_setColorspaceDetails(sws, sws_getCoefficients(s_csp), s_range, + sws_getCoefficients(d_csp), d_range, + 0, 1 << 16, 1 << 16); + + int res = sws_init_context(sws, NULL, NULL); + assert(res >= 0); + + sws_scale(sws, (const uint8_t *const *) src->planes, src->stride, + 0, src->h, dst->planes, dst->stride); + sws_freeContext(sws); +} + +// vim: ts=4 sw=4 et tw=80 diff --git a/video/sws_utils.h b/video/sws_utils.h new file mode 100644 index 0000000000..a0cc47d850 --- /dev/null +++ b/video/sws_utils.h @@ -0,0 +1,33 @@ +#ifndef MPLAYER_SWS_UTILS_H +#define MPLAYER_SWS_UTILS_H + +#include <stdbool.h> +#include <libswscale/swscale.h> + +struct mp_image; +struct mp_csp_details; + +// libswscale currently requires 16 bytes alignment for row pointers and +// strides. Otherwise, it will print warnings and use slow codepaths. +// Guaranteed to be a power of 2 and > 1. +#define SWS_MIN_BYTE_ALIGN 16 + +void sws_getFlagsAndFilterFromCmdLine(int *flags, SwsFilter **srcFilterParam, + SwsFilter **dstFilterParam); +struct SwsContext *sws_getContextFromCmdLine(int srcW, int srcH, int srcFormat, + int dstW, int dstH, + int dstFormat); +struct SwsContext *sws_getContextFromCmdLine_hq(int srcW, int srcH, + int srcFormat, int dstW, + int dstH, + int dstFormat); +int mp_sws_set_colorspace(struct SwsContext *sws, struct mp_csp_details *csp); + +bool mp_sws_supported_format(int imgfmt); + +void mp_image_swscale(struct mp_image *dst, struct mp_image *src, + int my_sws_flags); + +#endif /* MP_SWS_UTILS_H */ + +// vim: ts=4 sw=4 et tw=80 diff --git a/video/vfcap.h b/video/vfcap.h new file mode 100644 index 0000000000..acc7ce31c6 --- /dev/null +++ b/video/vfcap.h @@ -0,0 +1,46 @@ +/* VFCAP_* values: they are flags, returned by query_format(): + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_VFCAP_H +#define MPLAYER_VFCAP_H + +// set, if the given colorspace is supported (with or without conversion) +#define VFCAP_CSP_SUPPORTED 0x1 +// set, if the given colorspace is supported _without_ conversion +#define VFCAP_CSP_SUPPORTED_BY_HW 0x2 +// set if the driver/filter can draw OSD +#define VFCAP_OSD 0x4 +// scaling up/down by hardware, or software: +#define VFCAP_HWSCALE_UP 0x10 +#define VFCAP_HWSCALE_DOWN 0x20 +#define VFCAP_SWSCALE 0x40 +// driver/filter can do vertical flip (upside-down) +#define VFCAP_FLIP 0x80 + +// driver/hardware handles timing (blocking) +#define VFCAP_TIMER 0x100 +// vf filter: accepts stride (put_image) +// vo driver: has draw_slice() support for the given csp +#define VFCAP_ACCEPT_STRIDE 0x400 +// filter does postprocessing (so you shouldn't scale/filter image before it) +#define VFCAP_POSTPROC 0x800 +// used by libvo and vf_vo, indicates the VO does not support draw_slice for this format +#define VOCAP_NOSLICES 0x8000 + +#endif /* MPLAYER_VFCAP_H */ |