diff options
Diffstat (limited to 'sub/spudec.c')
-rw-r--r-- | sub/spudec.c | 1390 |
1 files changed, 1390 insertions, 0 deletions
diff --git a/sub/spudec.c b/sub/spudec.c new file mode 100644 index 0000000000..f48d47fd2a --- /dev/null +++ b/sub/spudec.c @@ -0,0 +1,1390 @@ +/* + * Skeleton of function spudec_process_controll() is from xine sources. + * Further works: + * LGB,... (yeah, try to improve it and insert your name here! ;-) + * + * Kim Minh Kaplan + * implement fragments reassembly, RLE decoding. + * read brightness from the IFO. + * + * For information on SPU format see <URL:http://sam.zoy.org/doc/dvd/subtitles/> + * and <URL:http://members.aol.com/mpucoder/DVD/spu.html> + * + * 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 "mp_msg.h" + +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <math.h> +#include "libvo/video_out.h" +#include "spudec.h" +#include "vobsub.h" +#include "libavutil/avutil.h" +#include "ffmpeg_files/intreadwrite.h" +#include "libswscale/swscale.h" +#include "mpcommon.h" + +/* Valid values for spu_aamode: + 0: none (fastest, most ugly) + 1: approximate + 2: full (slowest) + 3: bilinear (similiar to vobsub, fast and not too bad) + 4: uses swscaler gaussian (this is the only one that looks good) + */ + +int spu_aamode = 3; +int spu_alignment = -1; +float spu_gaussvar = 1.0; +extern int sub_pos; + +typedef struct packet_t packet_t; +struct packet_t { + int is_decoded; + unsigned char *packet; + int data_len; + unsigned int palette[4]; + unsigned int alpha[4]; + unsigned int control_start; /* index of start of control data */ + unsigned int current_nibble[2]; /* next data nibble (4 bits) to be + processed (for RLE decoding) for + even and odd lines */ + int deinterlace_oddness; /* 0 or 1, index into current_nibble */ + unsigned int start_col; + unsigned int start_row; + unsigned int width, height, stride; + unsigned int start_pts, end_pts; + packet_t *next; +}; + +struct palette_crop_cache { + int valid; + uint32_t palette; + int sx, sy, ex, ey; + int result; +}; + +typedef struct { + packet_t *queue_head; + packet_t *queue_tail; + unsigned int global_palette[16]; + unsigned int orig_frame_width, orig_frame_height; + unsigned char* packet; + size_t packet_reserve; /* size of the memory pointed to by packet */ + unsigned int packet_offset; /* end of the currently assembled fragment */ + unsigned int packet_size; /* size of the packet once all fragments are assembled */ + int packet_pts; /* PTS for this packet */ + unsigned int palette[4]; + unsigned int alpha[4]; + unsigned int cuspal[4]; + unsigned int custom; + unsigned int now_pts; + unsigned int start_pts, end_pts; + unsigned int start_col; + unsigned int start_row; + unsigned int width, height, stride; + size_t image_size; /* Size of the image buffer */ + unsigned char *image; /* Grayscale value */ + unsigned char *aimage; /* Alpha value */ + unsigned int pal_start_col, pal_start_row; + unsigned int pal_width, pal_height; + unsigned char *pal_image; /* palette entry value */ + unsigned int scaled_frame_width, scaled_frame_height; + unsigned int scaled_start_col, scaled_start_row; + unsigned int scaled_width, scaled_height, scaled_stride; + size_t scaled_image_size; + unsigned char *scaled_image; + unsigned char *scaled_aimage; + int auto_palette; /* 1 if we lack a palette and must use an heuristic. */ + int font_start_level; /* Darkest value used for the computed font */ + struct vo *hw_spu; + int spu_changed; + unsigned int forced_subs_only; /* flag: 0=display all subtitle, !0 display only forced subtitles */ + unsigned int is_forced_sub; /* true if current subtitle is a forced subtitle */ + + struct palette_crop_cache palette_crop_cache; +} spudec_handle_t; + +static void spudec_queue_packet(spudec_handle_t *this, packet_t *packet) +{ + if (this->queue_head == NULL) + this->queue_head = packet; + else + this->queue_tail->next = packet; + this->queue_tail = packet; +} + +static packet_t *spudec_dequeue_packet(spudec_handle_t *this) +{ + packet_t *retval = this->queue_head; + + this->queue_head = retval->next; + if (this->queue_head == NULL) + this->queue_tail = NULL; + + return retval; +} + +static void spudec_free_packet(packet_t *packet) +{ + free(packet->packet); + free(packet); +} + +static inline unsigned int get_be16(const unsigned char *p) +{ + return (p[0] << 8) + p[1]; +} + +static inline unsigned int get_be24(const unsigned char *p) +{ + return (get_be16(p) << 8) + p[2]; +} + +static void next_line(packet_t *packet) +{ + if (packet->current_nibble[packet->deinterlace_oddness] % 2) + packet->current_nibble[packet->deinterlace_oddness]++; + packet->deinterlace_oddness = (packet->deinterlace_oddness + 1) % 2; +} + +static inline unsigned char get_nibble(packet_t *packet) +{ + unsigned char nib; + unsigned int *nibblep = packet->current_nibble + packet->deinterlace_oddness; + if (*nibblep / 2 >= packet->control_start) { + mp_msg(MSGT_SPUDEC,MSGL_WARN, "SPUdec: ERROR: get_nibble past end of packet\n"); + return 0; + } + nib = packet->packet[*nibblep / 2]; + if (*nibblep % 2) + nib &= 0xf; + else + nib >>= 4; + ++*nibblep; + return nib; +} + +/* Cut the sub to visible part */ +static inline void spudec_cut_image(spudec_handle_t *this) +{ + unsigned int fy, ly; + unsigned int first_y, last_y; + + if (this->stride == 0 || this->height == 0) { + return; + } + + for (fy = 0; fy < this->image_size && !this->aimage[fy]; fy++); + for (ly = this->stride * this->height-1; ly && !this->aimage[ly]; ly--); + first_y = fy / this->stride; + last_y = ly / this->stride; + //printf("first_y: %d, last_y: %d\n", first_y, last_y); + this->start_row += first_y; + + // Some subtitles trigger this condition + if (last_y + 1 > first_y ) { + this->height = last_y - first_y +1; + } else { + this->height = 0; + return; + } + +// printf("new h %d new start %d (sz %d st %d)---\n\n", this->height, this->start_row, this->image_size, this->stride); + + if (first_y > 0) { + memmove(this->image, this->image + this->stride * first_y, this->stride * this->height); + memmove(this->aimage, this->aimage + this->stride * first_y, this->stride * this->height); + } +} + + +static int spudec_alloc_image(spudec_handle_t *this, int stride, int height) +{ + if (this->width > stride) // just a safeguard + this->width = stride; + this->stride = stride; + this->height = height; + if (this->image_size < this->stride * this->height) { + if (this->image != NULL) { + free(this->image); + free(this->pal_image); + this->image_size = 0; + this->pal_width = this->pal_height = 0; + } + this->image = malloc(2 * this->stride * this->height); + if (this->image) { + this->image_size = this->stride * this->height; + this->aimage = this->image + this->image_size; + // use stride here as well to simplify reallocation checks + this->pal_image = malloc(this->stride * this->height); + } + } + return this->image != NULL; +} + +/** + * \param pal palette in MPlayer-style gray-alpha values, i.e. + * alpha == 0 means transparent, 1 fully opaque, + * gray value <= 256 - alpha. + */ +static void pal2gray_alpha(const uint16_t *pal, + const uint8_t *src, int src_stride, + uint8_t *dst, uint8_t *dsta, + int dst_stride, int w, int h) +{ + int x, y; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + uint16_t pixel = pal[src[x]]; + *dst++ = pixel; + *dsta++ = pixel >> 8; + } + for (; x < dst_stride; x++) + *dsta++ = *dst++ = 0; + src += src_stride; + } +} + +static int apply_palette_crop(spudec_handle_t *this, + unsigned crop_x, unsigned crop_y, + unsigned crop_w, unsigned crop_h) +{ + int i; + uint8_t *src; + uint16_t pal[4]; + unsigned stride = (crop_w + 7) & ~7; + if (crop_x > this->pal_width || crop_y > this->pal_height || + crop_w > this->pal_width - crop_x || crop_h > this->pal_width - crop_y || + crop_w > 0x8000 || crop_h > 0x8000 || + stride * crop_h > this->image_size) { + return 0; + } + for (i = 0; i < 4; ++i) { + int color; + int alpha = this->alpha[i]; + // extend 4 -> 8 bit + alpha |= alpha << 4; + if (this->custom && (this->cuspal[i] >> 31) != 0) + alpha = 0; + color = this->custom ? this->cuspal[i] : + this->global_palette[this->palette[i]]; + color = (color >> 16) & 0xff; + // convert to MPlayer-style gray/alpha palette + color = FFMIN(color, alpha); + pal[i] = (-alpha << 8) | color; + } + src = this->pal_image + crop_y * this->pal_width + crop_x; + pal2gray_alpha(pal, src, this->pal_width, + this->image, this->aimage, stride, + crop_w, crop_h); + this->width = crop_w; + this->height = crop_h; + this->stride = stride; + this->start_col = this->pal_start_col + crop_x; + this->start_row = this->pal_start_row + crop_y; + spudec_cut_image(this); + + // reset scaled image + this->scaled_frame_width = 0; + this->scaled_frame_height = 0; + this->palette_crop_cache.valid = 0; + return 1; +} + +int spudec_apply_palette_crop(void *this, uint32_t palette, + int sx, int sy, int ex, int ey) +{ + spudec_handle_t *spu = this; + struct palette_crop_cache *c = &spu->palette_crop_cache; + if (c->valid && c->palette == palette && + c->sx == sx && c->sy == sy && c->ex == ex && c->ey == ey) + return c->result; + spu->palette[0] = (palette >> 28) & 0xf; + spu->palette[1] = (palette >> 24) & 0xf; + spu->palette[2] = (palette >> 20) & 0xf; + spu->palette[3] = (palette >> 16) & 0xf; + spu->alpha[0] = (palette >> 12) & 0xf; + spu->alpha[1] = (palette >> 8) & 0xf; + spu->alpha[2] = (palette >> 4) & 0xf; + spu->alpha[3] = palette & 0xf; + spu->spu_changed = 1; + c->result = apply_palette_crop(spu, + sx - spu->pal_start_col, sy - spu->pal_start_row, + ex - sx, ey - sy); + c->palette = palette; + c->sx = sx; c->sy = sy; + c->ex = ex; c->ey = ey; + c->valid = 1; + return c->result; +} + +static void spudec_process_data(spudec_handle_t *this, packet_t *packet) +{ + unsigned int i, x, y; + uint8_t *dst; + + if (!spudec_alloc_image(this, packet->stride, packet->height)) + return; + + this->pal_start_col = packet->start_col; + this->pal_start_row = packet->start_row; + this->pal_height = packet->height; + this->pal_width = packet->width; + this->stride = packet->stride; + memcpy(this->palette, packet->palette, sizeof(this->palette)); + memcpy(this->alpha, packet->alpha, sizeof(this->alpha)); + + i = packet->current_nibble[1]; + x = 0; + y = 0; + dst = this->pal_image; + while (packet->current_nibble[0] < i + && packet->current_nibble[1] / 2 < packet->control_start + && y < this->pal_height) { + unsigned int len, color; + unsigned int rle = 0; + rle = get_nibble(packet); + if (rle < 0x04) { + if (rle == 0) { + rle = (rle << 4) | get_nibble(packet); + if (rle < 0x04) + rle = (rle << 4) | get_nibble(packet); + } + rle = (rle << 4) | get_nibble(packet); + } + color = 3 - (rle & 0x3); + len = rle >> 2; + x += len; + if (len == 0 || x >= this->pal_width) { + len += this->pal_width - x; + next_line(packet); + x = 0; + ++y; + } + memset(dst, color, len); + dst += len; + } + apply_palette_crop(this, 0, 0, this->pal_width, this->pal_height); +} + + +/* + This function tries to create a usable palette. + It determines how many non-transparent colors are used, and assigns different +gray scale values to each color. + I tested it with four streams and even got something readable. Half of the +times I got black characters with white around and half the reverse. +*/ +static void compute_palette(spudec_handle_t *this, packet_t *packet) +{ + int used[16],i,cused,start,step,color; + + memset(used, 0, sizeof(used)); + for (i=0; i<4; i++) + if (packet->alpha[i]) /* !Transparent? */ + used[packet->palette[i]] = 1; + for (cused=0, i=0; i<16; i++) + if (used[i]) cused++; + if (!cused) return; + if (cused == 1) { + start = 0x80; + step = 0; + } else { + start = this->font_start_level; + step = (0xF0-this->font_start_level)/(cused-1); + } + memset(used, 0, sizeof(used)); + for (i=0; i<4; i++) { + color = packet->palette[i]; + if (packet->alpha[i] && !used[color]) { /* not assigned? */ + used[color] = 1; + this->global_palette[color] = start<<16; + start += step; + } + } +} + +static void spudec_process_control(spudec_handle_t *this, int pts100) +{ + int a,b,c,d; /* Temporary vars */ + unsigned int date, type; + unsigned int off; + unsigned int start_off = 0; + unsigned int next_off; + unsigned int start_pts = 0; + unsigned int end_pts = 0; + unsigned int current_nibble[2] = {0, 0}; + unsigned int control_start; + unsigned int display = 0; + unsigned int start_col = 0; + unsigned int end_col = 0; + unsigned int start_row = 0; + unsigned int end_row = 0; + unsigned int width = 0; + unsigned int height = 0; + unsigned int stride = 0; + + control_start = get_be16(this->packet + 2); + next_off = control_start; + while (start_off != next_off) { + start_off = next_off; + date = get_be16(this->packet + start_off) * 1024; + next_off = get_be16(this->packet + start_off + 2); + mp_msg(MSGT_SPUDEC,MSGL_DBG2, "date=%d\n", date); + off = start_off + 4; + for (type = this->packet[off++]; type != 0xff; type = this->packet[off++]) { + mp_msg(MSGT_SPUDEC,MSGL_DBG2, "cmd=%d ",type); + switch(type) { + case 0x00: + /* Menu ID, 1 byte */ + mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Menu ID\n"); + /* shouldn't a Menu ID type force display start? */ + start_pts = pts100 < 0 && -pts100 >= date ? 0 : pts100 + date; + end_pts = UINT_MAX; + display = 1; + this->is_forced_sub=~0; // current subtitle is forced + break; + case 0x01: + /* Start display */ + mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Start display!\n"); + start_pts = pts100 < 0 && -pts100 >= date ? 0 : pts100 + date; + end_pts = UINT_MAX; + display = 1; + this->is_forced_sub=0; + break; + case 0x02: + /* Stop display */ + mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Stop display!\n"); + end_pts = pts100 < 0 && -pts100 >= date ? 0 : pts100 + date; + break; + case 0x03: + /* Palette */ + this->palette[0] = this->packet[off] >> 4; + this->palette[1] = this->packet[off] & 0xf; + this->palette[2] = this->packet[off + 1] >> 4; + this->palette[3] = this->packet[off + 1] & 0xf; + mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Palette %d, %d, %d, %d\n", + this->palette[0], this->palette[1], this->palette[2], this->palette[3]); + off+=2; + break; + case 0x04: + /* Alpha */ + a = this->packet[off] >> 4; + b = this->packet[off] & 0xf; + c = this->packet[off + 1] >> 4; + d = this->packet[off + 1] & 0xf; + // Note: some DVDs change these values to create a fade-in/fade-out effect + // We can not handle this, so just keep the highest value during the display time. + if (display) { + a = FFMAX(a, this->alpha[0]); + b = FFMAX(b, this->alpha[1]); + c = FFMAX(c, this->alpha[2]); + d = FFMAX(d, this->alpha[3]); + } + this->alpha[0] = a; + this->alpha[1] = b; + this->alpha[2] = c; + this->alpha[3] = d; + mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Alpha %d, %d, %d, %d\n", + this->alpha[0], this->alpha[1], this->alpha[2], this->alpha[3]); + off+=2; + break; + case 0x05: + /* Co-ords */ + a = get_be24(this->packet + off); + b = get_be24(this->packet + off + 3); + start_col = a >> 12; + end_col = a & 0xfff; + width = (end_col < start_col) ? 0 : end_col - start_col + 1; + stride = (width + 7) & ~7; /* Kludge: draw_alpha needs width multiple of 8 */ + start_row = b >> 12; + end_row = b & 0xfff; + height = (end_row < start_row) ? 0 : end_row - start_row /* + 1 */; + mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Coords col: %d - %d row: %d - %d (%dx%d)\n", + start_col, end_col, start_row, end_row, + width, height); + off+=6; + break; + case 0x06: + /* Graphic lines */ + current_nibble[0] = 2 * get_be16(this->packet + off); + current_nibble[1] = 2 * get_be16(this->packet + off + 2); + mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Graphic offset 1: %d offset 2: %d\n", + current_nibble[0] / 2, current_nibble[1] / 2); + off+=4; + break; + case 0xff: + /* All done, bye-bye */ + mp_msg(MSGT_SPUDEC,MSGL_DBG2,"Done!\n"); + return; +// break; + default: + mp_msg(MSGT_SPUDEC,MSGL_WARN,"spudec: Error determining control type 0x%02x. Skipping %d bytes.\n", + type, next_off - off); + goto next_control; + } + } + next_control: + if (!display) + continue; + if (end_pts == UINT_MAX && start_off != next_off) { + end_pts = get_be16(this->packet + next_off) * 1024; + end_pts = 1 - pts100 >= end_pts ? 0 : pts100 + end_pts - 1; + } + if (end_pts > 0) { + packet_t *packet = calloc(1, sizeof(packet_t)); + int i; + packet->start_pts = start_pts; + packet->end_pts = end_pts; + packet->current_nibble[0] = current_nibble[0]; + packet->current_nibble[1] = current_nibble[1]; + packet->start_row = start_row; + packet->start_col = start_col; + packet->width = width; + packet->height = height; + packet->stride = stride; + packet->control_start = control_start; + for (i=0; i<4; i++) { + packet->alpha[i] = this->alpha[i]; + packet->palette[i] = this->palette[i]; + } + packet->packet = malloc(this->packet_size); + memcpy(packet->packet, this->packet, this->packet_size); + spudec_queue_packet(this, packet); + } + } +} + +static void spudec_decode(spudec_handle_t *this, int pts100) +{ + if (!this->hw_spu) + spudec_process_control(this, pts100); + else if (pts100 >= 0) { + static vo_mpegpes_t packet = { NULL, 0, 0x20, 0 }; + static vo_mpegpes_t *pkg=&packet; + packet.data = this->packet; + packet.size = this->packet_size; + packet.timestamp = pts100; + vo_draw_frame(this->hw_spu, (uint8_t**)&pkg); + } +} + +int spudec_changed(void * this) +{ + spudec_handle_t * spu = this; + return spu->spu_changed || spu->now_pts > spu->end_pts; +} + +void spudec_assemble(void *this, unsigned char *packet, unsigned int len, int pts100) +{ + spudec_handle_t *spu = this; +// spudec_heartbeat(this, pts100); + if (len < 2) { + mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUasm: packet too short\n"); + return; + } + spu->packet_pts = pts100; + if (spu->packet_offset == 0) { + unsigned int len2 = get_be16(packet); + // Start new fragment + if (spu->packet_reserve < len2) { + free(spu->packet); + spu->packet = malloc(len2); + spu->packet_reserve = spu->packet != NULL ? len2 : 0; + } + if (spu->packet != NULL) { + spu->packet_size = len2; + if (len > len2) { + mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUasm: invalid frag len / len2: %d / %d \n", len, len2); + return; + } + memcpy(spu->packet, packet, len); + spu->packet_offset = len; + spu->packet_pts = pts100; + } + } else { + // Continue current fragment + if (spu->packet_size < spu->packet_offset + len){ + mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUasm: invalid fragment\n"); + spu->packet_size = spu->packet_offset = 0; + return; + } else { + memcpy(spu->packet + spu->packet_offset, packet, len); + spu->packet_offset += len; + } + } +#if 1 + // check if we have a complete packet (unfortunatelly packet_size is bad + // for some disks) + // [cb] packet_size is padded to be even -> may be one byte too long + if ((spu->packet_offset == spu->packet_size) || + ((spu->packet_offset + 1) == spu->packet_size)){ + unsigned int x=0,y; + while(x+4<=spu->packet_offset){ + y=get_be16(spu->packet+x+2); // next control pointer + mp_msg(MSGT_SPUDEC,MSGL_DBG2,"SPUtest: x=%d y=%d off=%d size=%d\n",x,y,spu->packet_offset,spu->packet_size); + if(x>=4 && x==y){ // if it points to self - we're done! + // we got it! + mp_msg(MSGT_SPUDEC,MSGL_DBG2,"SPUgot: off=%d size=%d \n",spu->packet_offset,spu->packet_size); + spudec_decode(spu, pts100); + spu->packet_offset = 0; + break; + } + if(y<=x || y>=spu->packet_size){ // invalid? + mp_msg(MSGT_SPUDEC,MSGL_WARN,"SPUtest: broken packet!!!!! y=%d < x=%d\n",y,x); + spu->packet_size = spu->packet_offset = 0; + break; + } + x=y; + } + // [cb] packet is done; start new packet + spu->packet_offset = 0; + } +#else + if (spu->packet_offset == spu->packet_size) { + spudec_decode(spu, pts100); + spu->packet_offset = 0; + } +#endif +} + +void spudec_reset(void *this) // called after seek +{ + spudec_handle_t *spu = this; + while (spu->queue_head) + spudec_free_packet(spudec_dequeue_packet(spu)); + spu->now_pts = 0; + spu->end_pts = 0; + spu->packet_size = spu->packet_offset = 0; +} + +void spudec_heartbeat(void *this, unsigned int pts100) +{ + spudec_handle_t *spu = this; + spu->now_pts = pts100; + + // TODO: detect and handle broken timestamps (e.g. due to wrapping) + while (spu->queue_head != NULL && pts100 >= spu->queue_head->start_pts) { + packet_t *packet = spudec_dequeue_packet(spu); + spu->start_pts = packet->start_pts; + spu->end_pts = packet->end_pts; + if (packet->is_decoded) { + free(spu->image); + spu->image_size = packet->data_len; + spu->image = packet->packet; + spu->aimage = packet->packet + packet->stride * packet->height; + packet->packet = NULL; + spu->width = packet->width; + spu->height = packet->height; + spu->stride = packet->stride; + spu->start_col = packet->start_col; + spu->start_row = packet->start_row; + + // reset scaled image + spu->scaled_frame_width = 0; + spu->scaled_frame_height = 0; + } else { + if (spu->auto_palette) + compute_palette(spu, packet); + spudec_process_data(spu, packet); + } + spudec_free_packet(packet); + spu->spu_changed = 1; + } +} + +int spudec_visible(void *this){ + spudec_handle_t *spu = this; + int ret=(spu->start_pts <= spu->now_pts && + spu->now_pts < spu->end_pts && + spu->height > 0); +// printf("spu visible: %d \n",ret); + return ret; +} + +void spudec_set_forced_subs_only(void * const this, const unsigned int flag) +{ + if(this){ + ((spudec_handle_t *)this)->forced_subs_only=flag; + mp_msg(MSGT_SPUDEC,MSGL_DBG2,"SPU: Display only forced subs now %s\n", flag ? "enabled": "disabled"); + } +} + +void spudec_draw(void *this, void (*draw_alpha)(void *ctx, int x0,int y0, int w,int h, unsigned char* src, unsigned char *srca, int stride), void *ctx) +{ + spudec_handle_t *spu = this; + if (spudec_visible(spu)) + { + draw_alpha(ctx, spu->start_col, spu->start_row, spu->width, spu->height, + spu->image, spu->aimage, spu->stride); + spu->spu_changed = 0; + } +} + +/* calc the bbox for spudec subs */ +void spudec_calc_bbox(void *me, unsigned int dxs, unsigned int dys, unsigned int* bbox) +{ + spudec_handle_t *spu = me; + if (spu->orig_frame_width == 0 || spu->orig_frame_height == 0 + || (spu->orig_frame_width == dxs && spu->orig_frame_height == dys)) { + // unscaled + bbox[0] = spu->start_col; + bbox[1] = spu->start_col + spu->width; + bbox[2] = spu->start_row; + bbox[3] = spu->start_row + spu->height; + } + else { + // scaled + unsigned int scalex = 0x100 * dxs / spu->orig_frame_width; + unsigned int scaley = 0x100 * dys / spu->orig_frame_height; + bbox[0] = spu->start_col * scalex / 0x100; + bbox[1] = spu->start_col * scalex / 0x100 + spu->width * scalex / 0x100; + switch (spu_alignment) { + case 0: + bbox[3] = dys*sub_pos/100 + spu->height * scaley / 0x100; + if (bbox[3] > dys) bbox[3] = dys; + bbox[2] = bbox[3] - spu->height * scaley / 0x100; + break; + case 1: + if (sub_pos < 50) { + bbox[2] = dys*sub_pos/100 - spu->height * scaley / 0x200; + bbox[3] = bbox[2] + spu->height; + } else { + bbox[3] = dys*sub_pos/100 + spu->height * scaley / 0x200; + if (bbox[3] > dys) bbox[3] = dys; + bbox[2] = bbox[3] - spu->height * scaley / 0x100; + } + break; + case 2: + bbox[2] = dys*sub_pos/100 - spu->height * scaley / 0x100; + bbox[3] = bbox[2] + spu->height; + break; + default: /* -1 */ + bbox[2] = spu->start_row * scaley / 0x100; + bbox[3] = spu->start_row * scaley / 0x100 + spu->height * scaley / 0x100; + break; + } + } +} +/* transform mplayer's alpha value into an opacity value that is linear */ +static inline int canon_alpha(int alpha) +{ + return (uint8_t)-alpha; +} + +typedef struct { + unsigned position; + unsigned left_up; + unsigned right_down; +}scale_pixel; + + +static void scale_table(unsigned int start_src, unsigned int start_tar, unsigned int end_src, unsigned int end_tar, scale_pixel * table) +{ + unsigned int t; + unsigned int delta_src = end_src - start_src; + unsigned int delta_tar = end_tar - start_tar; + int src = 0; + int src_step; + if (delta_src == 0 || delta_tar == 0) { + return; + } + src_step = (delta_src << 16) / delta_tar >>1; + for (t = 0; t<=delta_tar; src += (src_step << 1), t++){ + table[t].position= FFMIN(src >> 16, end_src - 1); + table[t].right_down = src & 0xffff; + table[t].left_up = 0x10000 - table[t].right_down; + } +} + +/* bilinear scale, similar to vobsub's code */ +static void scale_image(int x, int y, scale_pixel* table_x, scale_pixel* table_y, spudec_handle_t * spu) +{ + int alpha[4]; + int color[4]; + unsigned int scale[4]; + int base = table_y[y].position * spu->stride + table_x[x].position; + int scaled = y * spu->scaled_stride + x; + alpha[0] = canon_alpha(spu->aimage[base]); + alpha[1] = canon_alpha(spu->aimage[base + 1]); + alpha[2] = canon_alpha(spu->aimage[base + spu->stride]); + alpha[3] = canon_alpha(spu->aimage[base + spu->stride + 1]); + color[0] = spu->image[base]; + color[1] = spu->image[base + 1]; + color[2] = spu->image[base + spu->stride]; + color[3] = spu->image[base + spu->stride + 1]; + scale[0] = (table_x[x].left_up * table_y[y].left_up >> 16) * alpha[0]; + if (table_y[y].left_up == 0x10000) // necessary to avoid overflow-case + scale[0] = table_x[x].left_up * alpha[0]; + scale[1] = (table_x[x].right_down * table_y[y].left_up >>16) * alpha[1]; + scale[2] = (table_x[x].left_up * table_y[y].right_down >> 16) * alpha[2]; + scale[3] = (table_x[x].right_down * table_y[y].right_down >> 16) * alpha[3]; + spu->scaled_image[scaled] = (color[0] * scale[0] + color[1] * scale[1] + color[2] * scale[2] + color[3] * scale[3])>>24; + spu->scaled_aimage[scaled] = (scale[0] + scale[1] + scale[2] + scale[3]) >> 16; + if (spu->scaled_aimage[scaled]){ + // ensure that MPlayer's simplified alpha-blending can not overflow + spu->scaled_image[scaled] = FFMIN(spu->scaled_image[scaled], spu->scaled_aimage[scaled]); + // convert to MPlayer-style alpha + spu->scaled_aimage[scaled] = -spu->scaled_aimage[scaled]; + } +} + +static void sws_spu_image(unsigned char *d1, unsigned char *d2, int dw, int dh, + int ds, const unsigned char* s1, unsigned char* s2, + int sw, int sh, int ss) +{ + struct SwsContext *ctx; + static SwsFilter filter; + static int firsttime = 1; + static float oldvar; + int i; + + if (!firsttime && oldvar != spu_gaussvar) sws_freeVec(filter.lumH); + if (firsttime) { + filter.lumH = filter.lumV = + filter.chrH = filter.chrV = sws_getGaussianVec(spu_gaussvar, 3.0); + sws_normalizeVec(filter.lumH, 1.0); + firsttime = 0; + oldvar = spu_gaussvar; + } + + ctx=sws_getContext(sw, sh, PIX_FMT_GRAY8, dw, dh, PIX_FMT_GRAY8, SWS_GAUSS, &filter, NULL, NULL); + sws_scale(ctx,&s1,&ss,0,sh,&d1,&ds); + for (i=ss*sh-1; i>=0; i--) if (!s2[i]) s2[i] = 255; //else s2[i] = 1; + sws_scale(ctx,&s2,&ss,0,sh,&d2,&ds); + for (i=ds*dh-1; i>=0; i--) if (d2[i]==0) d2[i] = 1; else if (d2[i]==255) d2[i] = 0; + sws_freeContext(ctx); +} + +void spudec_draw_scaled(void *me, unsigned int dxs, unsigned int dys, void (*draw_alpha)(void *ctx, int x0,int y0, int w,int h, unsigned char* src, unsigned char *srca, int stride), void *ctx) +{ + spudec_handle_t *spu = me; + scale_pixel *table_x; + scale_pixel *table_y; + + if (spudec_visible(spu)) { + + // check if only forced subtitles are requested + if( (spu->forced_subs_only) && !(spu->is_forced_sub) ){ + return; + } + + if (!(spu_aamode&16) && (spu->orig_frame_width == 0 || spu->orig_frame_height == 0 + || (spu->orig_frame_width == dxs && spu->orig_frame_height == dys))) { + spudec_draw(spu, draw_alpha, ctx); + } + else { + if (spu->scaled_frame_width != dxs || spu->scaled_frame_height != dys) { /* Resizing is needed */ + /* scaled_x = scalex * x / 0x100 + scaled_y = scaley * y / 0x100 + order of operations is important because of rounding. */ + unsigned int scalex = 0x100 * dxs / spu->orig_frame_width; + unsigned int scaley = 0x100 * dys / spu->orig_frame_height; + spu->scaled_start_col = spu->start_col * scalex / 0x100; + spu->scaled_start_row = spu->start_row * scaley / 0x100; + spu->scaled_width = spu->width * scalex / 0x100; + spu->scaled_height = spu->height * scaley / 0x100; + /* Kludge: draw_alpha needs width multiple of 8 */ + spu->scaled_stride = (spu->scaled_width + 7) & ~7; + if (spu->scaled_image_size < spu->scaled_stride * spu->scaled_height) { + if (spu->scaled_image) { + free(spu->scaled_image); + spu->scaled_image_size = 0; + } + spu->scaled_image = malloc(2 * spu->scaled_stride * spu->scaled_height); + if (spu->scaled_image) { + spu->scaled_image_size = spu->scaled_stride * spu->scaled_height; + spu->scaled_aimage = spu->scaled_image + spu->scaled_image_size; + } + } + if (spu->scaled_image) { + unsigned int x, y; + if (spu->scaled_width <= 1 || spu->scaled_height <= 1) { + goto nothing_to_do; + } + switch(spu_aamode&15) { + case 4: + sws_spu_image(spu->scaled_image, spu->scaled_aimage, + spu->scaled_width, spu->scaled_height, spu->scaled_stride, + spu->image, spu->aimage, spu->width, spu->height, spu->stride); + break; + case 3: + table_x = calloc(spu->scaled_width, sizeof(scale_pixel)); + table_y = calloc(spu->scaled_height, sizeof(scale_pixel)); + if (!table_x || !table_y) { + mp_msg(MSGT_SPUDEC, MSGL_FATAL, "Fatal: spudec_draw_scaled: calloc failed\n"); + } + scale_table(0, 0, spu->width - 1, spu->scaled_width - 1, table_x); + scale_table(0, 0, spu->height - 1, spu->scaled_height - 1, table_y); + for (y = 0; y < spu->scaled_height; y++) + for (x = 0; x < spu->scaled_width; x++) + scale_image(x, y, table_x, table_y, spu); + free(table_x); + free(table_y); + break; + case 0: + /* no antialiasing */ + for (y = 0; y < spu->scaled_height; ++y) { + int unscaled_y = y * 0x100 / scaley; + int strides = spu->stride * unscaled_y; + int scaled_strides = spu->scaled_stride * y; + for (x = 0; x < spu->scaled_width; ++x) { + int unscaled_x = x * 0x100 / scalex; + spu->scaled_image[scaled_strides + x] = spu->image[strides + unscaled_x]; + spu->scaled_aimage[scaled_strides + x] = spu->aimage[strides + unscaled_x]; + } + } + break; + case 1: + { + /* Intermediate antialiasing. */ + for (y = 0; y < spu->scaled_height; ++y) { + const unsigned int unscaled_top = y * spu->orig_frame_height / dys; + unsigned int unscaled_bottom = (y + 1) * spu->orig_frame_height / dys; + if (unscaled_bottom >= spu->height) + unscaled_bottom = spu->height - 1; + for (x = 0; x < spu->scaled_width; ++x) { + const unsigned int unscaled_left = x * spu->orig_frame_width / dxs; + unsigned int unscaled_right = (x + 1) * spu->orig_frame_width / dxs; + unsigned int color = 0; + unsigned int alpha = 0; + unsigned int walkx, walky; + unsigned int base, tmp; + if (unscaled_right >= spu->width) + unscaled_right = spu->width - 1; + for (walky = unscaled_top; walky <= unscaled_bottom; ++walky) + for (walkx = unscaled_left; walkx <= unscaled_right; ++walkx) { + base = walky * spu->stride + walkx; + tmp = canon_alpha(spu->aimage[base]); + alpha += tmp; + color += tmp * spu->image[base]; + } + base = y * spu->scaled_stride + x; + spu->scaled_image[base] = alpha ? color / alpha : 0; + spu->scaled_aimage[base] = + alpha * (1 + unscaled_bottom - unscaled_top) * (1 + unscaled_right - unscaled_left); + /* spu->scaled_aimage[base] = + alpha * dxs * dys / spu->orig_frame_width / spu->orig_frame_height; */ + if (spu->scaled_aimage[base]) { + spu->scaled_aimage[base] = 256 - spu->scaled_aimage[base]; + if (spu->scaled_aimage[base] + spu->scaled_image[base] > 255) + spu->scaled_image[base] = 256 - spu->scaled_aimage[base]; + } + } + } + } + break; + case 2: + { + /* Best antialiasing. Very slow. */ + /* Any pix |