From 70340520422bd57265c125c9114bf0804a0fcfb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Wed, 11 Apr 2007 22:47:14 +0200 Subject: Improve MPEG2 detection to have it ready for correct aspect ratio determination. The current code detects MPEG2 when parsing the sequence extention which appears only in MPEG2 streams. But this is to late for correct aspect ratio determination which happens earlier and therefore assumes MPEG1. The result is a totally wrong aspect ratio. To fix this issue, the next start code (which is already available at that time) is passed to parse_chunk() too, which can then be used to detect a MPEG2 extension start code early enough before the aspect ratio is determined. --- src/libmpeg2/decode.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/libmpeg2/decode.c b/src/libmpeg2/decode.c index a2bb868df..3b19feda1 100644 --- a/src/libmpeg2/decode.c +++ b/src/libmpeg2/decode.c @@ -236,7 +236,7 @@ static void remember_metainfo (mpeg2dec_t *mpeg2dec) { } static inline int parse_chunk (mpeg2dec_t * mpeg2dec, int code, - uint8_t * buffer) + uint8_t * buffer, int next_code) { picture_t * picture; int is_frame_done; @@ -393,6 +393,11 @@ static inline int parse_chunk (mpeg2dec_t * mpeg2dec, int code, /* abort(); */ break; } + + /* according to ISO/IEC 13818-2, an extension start code will follow. + * Otherwise the stream follows ISO/IEC 11172-2 which means MPEG1 */ + picture->mpeg1 = (next_code != 0xb5); + if (mpeg2dec->force_aspect) picture->aspect_ratio_information = mpeg2dec->force_aspect; if (mpeg2dec->is_sequence_needed ) { @@ -638,7 +643,7 @@ int mpeg2_decode_data (mpeg2dec_t * mpeg2dec, uint8_t * current, uint8_t * end, current = copy_chunk (mpeg2dec, current, end); if (current == NULL) break; - ret += parse_chunk (mpeg2dec, code, mpeg2dec->chunk_buffer); + ret += parse_chunk (mpeg2dec, code, mpeg2dec->chunk_buffer, mpeg2dec->code); } libmpeg2_accel_frame_completion(&mpeg2dec->accel, mpeg2dec->frame_format, @@ -805,7 +810,7 @@ void mpeg2_close (mpeg2dec_t * mpeg2dec) void mpeg2_find_sequence_header (mpeg2dec_t * mpeg2dec, uint8_t * current, uint8_t * end){ - uint8_t code; + uint8_t code, next_code; picture_t *picture = mpeg2dec->picture; mpeg2dec->seek_mode = 1; @@ -815,6 +820,7 @@ void mpeg2_find_sequence_header (mpeg2dec_t * mpeg2dec, current = copy_chunk (mpeg2dec, current, end); if (current == NULL) return ; + next_code = mpeg2dec->code; /* printf ("looking for sequence header... %02x\n", code); */ @@ -825,6 +831,11 @@ void mpeg2_find_sequence_header (mpeg2dec_t * mpeg2dec, printf ("libmpeg2: bad sequence header\n"); continue; } + + /* according to ISO/IEC 13818-2, an extension start code will follow. + * Otherwise the stream follows ISO/IEC 11172-2 which means MPEG1 */ + picture->mpeg1 = (next_code != 0xb5); + if (mpeg2dec->force_aspect) picture->aspect_ratio_information = mpeg2dec->force_aspect; if (mpeg2dec->is_sequence_needed) { -- cgit v1.2.3 From 47d88e8447b5b86f7eca03d302f2d17d2a4c9c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Wed, 11 Apr 2007 23:04:11 +0200 Subject: Detect absence of AFD and report only changes. The current code cannot detect the absence of AFD once it has been seen in the stream. As AFD can appear in user data after sequence, group or picture start codes, the idea is to reset the stored AFD value when processing the sequence start code. In the case where AFD is seen in user data, it is stored internally, to have it ready when the first slice is processed. At least at that time, AFD data has been seen and can be analyzed for changes. At any change, the AFD value will then be stored into a stream property. Doing this only for changes avoids locks while writing the same value over and over to the stream's property. --- src/libmpeg2/decode.c | 25 ++++++++++++++++++++----- src/libmpeg2/mpeg2.h | 6 ++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/libmpeg2/decode.c b/src/libmpeg2/decode.c index 3b19feda1..3233fb9b4 100644 --- a/src/libmpeg2/decode.c +++ b/src/libmpeg2/decode.c @@ -87,6 +87,10 @@ void mpeg2_init (mpeg2dec_t * mpeg2dec, mpeg2dec->code = 0xb4; mpeg2dec->seek_mode = 0; + /* initialize AFD storage */ + mpeg2dec->afd_value_seen = XINE_VIDEO_AFD_NOT_PRESENT; + mpeg2dec->afd_value_reported = (XINE_VIDEO_AFD_NOT_PRESENT - 1); + memset (mpeg2dec->picture, 0, sizeof (picture_t)); /* initialize substructures */ @@ -394,6 +398,9 @@ static inline int parse_chunk (mpeg2dec_t * mpeg2dec, int code, break; } + /* reset AFD value to detect absence */ + mpeg2dec->afd_value_seen = XINE_VIDEO_AFD_NOT_PRESENT; + /* according to ISO/IEC 13818-2, an extension start code will follow. * Otherwise the stream follows ISO/IEC 11172-2 which means MPEG1 */ picture->mpeg1 = (next_code != 0xb5); @@ -469,6 +476,18 @@ static inline int parse_chunk (mpeg2dec_t * mpeg2dec, int code, if (code >= 0xb0) break; + /* check for AFD change once per picture */ + if (mpeg2dec->afd_value_reported != mpeg2dec->afd_value_seen) { + /* AFD data should better be stored in current_frame to have it */ + /* ready and synchronous with other data like width or height. */ + /* An AFD change should then be detected when a new frame is emitted */ + /* from the decoder to report the AFD change in display order and not */ + /* in decoding order like it happens below for now. */ + _x_stream_info_set(mpeg2dec->stream, XINE_STREAM_INFO_VIDEO_AFD, mpeg2dec->afd_value_seen); +fprintf(stderr, "AFD changed from %d to %d\n", mpeg2dec->afd_value_reported, mpeg2dec->afd_value_seen); + mpeg2dec->afd_value_reported = mpeg2dec->afd_value_seen; + } + if (!(mpeg2dec->in_slice)) { mpeg2dec->in_slice = 1; @@ -929,9 +948,5 @@ static void process_userdata(mpeg2dec_t *mpeg2dec, uint8_t *buffer) } /* check Active Format Description ETSI TS 101 154 V1.5.1 */ else if (buffer[0] == 0x44 && buffer[1] == 0x54 && buffer[2] == 0x47 && buffer[3] == 0x31) - { - int afd = (buffer[4] & 0x40) ? (buffer[5] & 0x0f) : -1; - _x_stream_info_set(mpeg2dec->stream, XINE_STREAM_INFO_VIDEO_AFD, afd); - - } + mpeg2dec->afd_value_seen = (buffer[4] & 0x40) ? (buffer[5] & 0x0f) : XINE_VIDEO_AFD_NOT_PRESENT; } diff --git a/src/libmpeg2/mpeg2.h b/src/libmpeg2/mpeg2.h index 788fa823c..253f300a2 100644 --- a/src/libmpeg2/mpeg2.h +++ b/src/libmpeg2/mpeg2.h @@ -57,6 +57,12 @@ typedef struct mpeg2dec_s { int force_aspect; int force_pan_scan; + /* AFD data can be found after a sequence, group or picture start code */ + /* and will be stored in afd_value_seen. Later it will be transfered to */ + /* a stream property and stored into afd_value_reported to detect changes */ + int afd_value_seen; + int afd_value_reported; + xine_stream_t *stream; /* a spu decoder for possible closed captions */ -- cgit v1.2.3 From 5e58a3c2f6a02dbd198369d1132366c49f5345fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Wed, 11 Apr 2007 23:27:25 +0200 Subject: Special handling of sequence end code to improve still frames. The current code emits a frame when a non slice start code is seen. For still frames, this is typically a sequence end code. But the current code doesn't call parse_chunk() immediately because it waits for a further start code to determine the chunk of data to pass to parse_chunk(). But there isn't such a further start code for still frames after the sequence end code and thus, the still frame will not be emitted. As sequence end code is the only start code which has no data according to the MPEG specification, let's use this information to call parse_chunk() immediately. --- src/libmpeg2/decode.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/libmpeg2/decode.c b/src/libmpeg2/decode.c index 3233fb9b4..c3ba6ea4d 100644 --- a/src/libmpeg2/decode.c +++ b/src/libmpeg2/decode.c @@ -606,6 +606,18 @@ static inline uint8_t * copy_chunk (mpeg2dec_t * mpeg2dec, uint8_t * limit; uint8_t byte; + /* sequence end code 0xb7 doesn't have any data and there might be the case + * that no start code will follow this code for quite some time (e. g. in case + * of a still image. + * Therefore, return immediately with a chunk_size of 0. Setting code to 0xb4 + * will eat up any trailing garbage next time. + */ + if (mpeg2dec->code == 0xb7) { + mpeg2dec->code = 0xb4; + mpeg2dec->chunk_size = 0; + return current; + } + shift = mpeg2dec->shift; chunk_ptr = mpeg2dec->chunk_ptr; limit = current + (mpeg2dec->chunk_buffer + BUFFER_SIZE - chunk_ptr); @@ -657,7 +669,7 @@ int mpeg2_decode_data (mpeg2dec_t * mpeg2dec, uint8_t * current, uint8_t * end, if (pts) mpeg2dec->pts = pts; - while (current != end) { + while (current != end || mpeg2dec->code == 0xb7) { code = mpeg2dec->code; current = copy_chunk (mpeg2dec, current, end); if (current == NULL) -- cgit v1.2.3 From a76aacd69fa3512c3c566c93248bc6c41bb00e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Thu, 12 Apr 2007 00:02:45 +0200 Subject: Speed up start code scanning. The current code implements hardware (a shift register) in software just to find the byte pattern 00 00 01 xx, which causes remarkable CPU load on less powerful machines. The new approach uses memchr() to find the 01 in the buffer, which most often hits a start code. memchr() seems to be even faster then implementing a real pattern search (i. e. by just looking at every third byte to find 01). The new implementation causes significantly fewer CPU load on less powerful machines. --- src/libmpeg2/decode.c | 99 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 27 deletions(-) diff --git a/src/libmpeg2/decode.c b/src/libmpeg2/decode.c index c3ba6ea4d..2b2426950 100644 --- a/src/libmpeg2/decode.c +++ b/src/libmpeg2/decode.c @@ -598,13 +598,63 @@ fprintf(stderr, "AFD changed from %d to %d\n", mpeg2dec->afd_value_reported, mpe return is_frame_done; } +static inline int find_start_code (mpeg2dec_t * mpeg2dec, + uint8_t ** current, uint8_t * limit) +{ + uint8_t * p; + + if (*current >= limit) + return 0; + if (mpeg2dec->shift == 0x00000100) + return 1; + + mpeg2dec->shift = (mpeg2dec->shift | *(*current)++) << 8; + + if (*current >= limit) + return 0; + if (mpeg2dec->shift == 0x00000100) + return 1; + + mpeg2dec->shift = (mpeg2dec->shift | *(*current)++) << 8; + + if (*current >= limit) + return 0; + if (mpeg2dec->shift == 0x00000100) + return 1; + + limit--; + + if (*current >= limit) { + mpeg2dec->shift = (mpeg2dec->shift | *(*current)++) << 8; + return 0; + } + + p = *current; + + while (p < limit && (p = (uint8_t *)memchr(p, 0x01, limit - p))) { + if (p[-2] || p[-1]) + p += 3; + else { + *current = ++p; + return 1; + } + } + + *current = ++limit; + p = limit - 3; + mpeg2dec->shift = (mpeg2dec->shift | *p++) << 8; + mpeg2dec->shift = (mpeg2dec->shift | *p++) << 8; + mpeg2dec->shift = (mpeg2dec->shift | *p++) << 8; + + return 0; +} + static inline uint8_t * copy_chunk (mpeg2dec_t * mpeg2dec, uint8_t * current, uint8_t * end) { - uint32_t shift; - uint8_t * chunk_ptr; uint8_t * limit; - uint8_t byte; + uint8_t * data = current; + int found, bite; /* sequence end code 0xb7 doesn't have any data and there might be the case * that no start code will follow this code for quite some time (e. g. in case @@ -618,37 +668,32 @@ static inline uint8_t * copy_chunk (mpeg2dec_t * mpeg2dec, return current; } - shift = mpeg2dec->shift; - chunk_ptr = mpeg2dec->chunk_ptr; - limit = current + (mpeg2dec->chunk_buffer + BUFFER_SIZE - chunk_ptr); + limit = current + (mpeg2dec->chunk_buffer + BUFFER_SIZE - mpeg2dec->chunk_ptr); if (limit > end) limit = end; - while (1) { - - byte = *current++; - if (shift != 0x00000100) { - shift = (shift | byte) << 8; - *chunk_ptr++ = byte; - if (current < limit) - continue; - if (current == end) { - mpeg2dec->chunk_ptr = chunk_ptr; - mpeg2dec->shift = shift; - return NULL; - } else { - /* we filled the chunk buffer without finding a start code */ - mpeg2dec->code = 0xb4; /* sequence_error_code */ - mpeg2dec->chunk_ptr = mpeg2dec->chunk_buffer; - return current; - } - } - mpeg2dec->code = byte; - mpeg2dec->chunk_size = chunk_ptr - mpeg2dec->chunk_buffer - 3; + found = find_start_code(mpeg2dec, ¤t, limit); + bite = current - data; + if (bite) { + xine_fast_memcpy(mpeg2dec->chunk_ptr, data, bite); + mpeg2dec->chunk_ptr += bite; + } + + if (found) { + mpeg2dec->code = *current++; + mpeg2dec->chunk_size = mpeg2dec->chunk_ptr - mpeg2dec->chunk_buffer - 3; mpeg2dec->chunk_ptr = mpeg2dec->chunk_buffer; mpeg2dec->shift = 0xffffff00; return current; } + + if (current == end) + return NULL; + + /* we filled the chunk buffer without finding a start code */ + mpeg2dec->code = 0xb4; /* sequence_error_code */ + mpeg2dec->chunk_ptr = mpeg2dec->chunk_buffer; + return current; } int mpeg2_decode_data (mpeg2dec_t * mpeg2dec, uint8_t * current, uint8_t * end, -- cgit v1.2.3 From b4304f243c6006eec3ca4b4ce2b19cfca703861a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Thu, 12 Apr 2007 22:14:06 +0200 Subject: Extend ticket system for nonblocking ticket acquiries. The current code has a race condition which can block arbitrary threads that call for example xine_get_current_frame() until the stream gets unpaused again. This can happen when the internal ticket acquiration collides with a ticket revokation for example when another thread is going to pause the stream. There are a few situations where a port ticket needs to be acquired for calling a port function but where it is absolutely undesireable to get blocked for an undetermined period of time. Therefore the ticket system should be extended by nonblocking functions which allow ticket acquiration even when a ticket revokation is in progress. And in the case where blocking is not avoidable, it should simply be indicated that no ticket was acquired. The caller can then choose to repeat the call at a later point in time. --- src/xine-engine/xine.c | 49 ++++++++++++++++++++++++++++++++--------- src/xine-engine/xine_internal.h | 9 ++++++++ 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/xine-engine/xine.c b/src/xine-engine/xine.c index f49a988c9..612bf8dcc 100644 --- a/src/xine-engine/xine.c +++ b/src/xine-engine/xine.c @@ -127,23 +127,42 @@ void _x_extra_info_merge( extra_info_t *dst, extra_info_t *src ) { } } -static void ticket_acquire(xine_ticket_t *this, int irrevocable) { +static int ticket_acquire_internal(xine_ticket_t *this, int irrevocable, int nonblocking) { + int must_wait = 0; pthread_mutex_lock(&this->lock); if (this->ticket_revoked && !this->irrevocable_tickets) - pthread_cond_wait(&this->issued, &this->lock); + must_wait = !nonblocking; else if (this->atomic_revoke && !pthread_equal(this->atomic_revoker_thread, pthread_self())) + must_wait = 1; + + if (must_wait) { + if (nonblocking) { + pthread_mutex_unlock(&this->lock); + return 0; + } + pthread_cond_wait(&this->issued, &this->lock); + } this->tickets_granted++; if (irrevocable) this->irrevocable_tickets++; pthread_mutex_unlock(&this->lock); + return 1; } -static void ticket_release(xine_ticket_t *this, int irrevocable) { +static int ticket_acquire_nonblocking(xine_ticket_t *this, int irrevocable) { + return ticket_acquire_internal(this, irrevocable, 1); +} + +static void ticket_acquire(xine_ticket_t *this, int irrevocable) { + ticket_acquire_internal(this, irrevocable, 0); +} + +static void ticket_release_internal(xine_ticket_t *this, int irrevocable, int nonblocking) { pthread_mutex_lock(&this->lock); @@ -153,12 +172,20 @@ static void ticket_release(xine_ticket_t *this, int irrevocable) { if (this->ticket_revoked && !this->tickets_granted) pthread_cond_broadcast(&this->revoked); - if (this->ticket_revoked && !this->irrevocable_tickets) + if (this->ticket_revoked && !this->irrevocable_tickets && !nonblocking) pthread_cond_wait(&this->issued, &this->lock); pthread_mutex_unlock(&this->lock); } +static void ticket_release_nonblocking(xine_ticket_t *this, int irrevocable) { + ticket_release_internal(this, irrevocable, 1); +} + +static void ticket_release(xine_ticket_t *this, int irrevocable) { + ticket_release_internal(this, irrevocable, 0); +} + static void ticket_renew(xine_ticket_t *this, int irrevocable) { pthread_mutex_lock(&this->lock); @@ -227,12 +254,14 @@ static xine_ticket_t *ticket_init(void) { port_ticket = (xine_ticket_t *) xine_xmalloc(sizeof(xine_ticket_t)); - port_ticket->acquire = ticket_acquire; - port_ticket->release = ticket_release; - port_ticket->renew = ticket_renew; - port_ticket->issue = ticket_issue; - port_ticket->revoke = ticket_revoke; - port_ticket->dispose = ticket_dispose; + port_ticket->acquire_nonblocking = ticket_acquire_nonblocking; + port_ticket->acquire = ticket_acquire; + port_ticket->release_nonblocking = ticket_release_nonblocking; + port_ticket->release = ticket_release; + port_ticket->renew = ticket_renew; + port_ticket->issue = ticket_issue; + port_ticket->revoke = ticket_revoke; + port_ticket->dispose = ticket_dispose; pthread_mutex_init(&port_ticket->lock, NULL); pthread_mutex_init(&port_ticket->revoke_lock, NULL); diff --git a/src/xine-engine/xine_internal.h b/src/xine-engine/xine_internal.h index 30899a4b3..da6f88a7f 100644 --- a/src/xine-engine/xine_internal.h +++ b/src/xine-engine/xine_internal.h @@ -159,6 +159,15 @@ struct xine_ticket_s { * revocation or by other threads acquiring tickets */ void (*revoke)(xine_ticket_t *self, int atomic); + /* behaves like acquire() but doesn't block the calling thread; when + * the thread would have been blocked, 0 is returned otherwise 1 + * this function acquires a ticket even if ticket revocation is active */ + int (*acquire_nonblocking)(xine_ticket_t *self, int irrevocable); + + /* behaves like release() but doesn't block the calling thread; should + * be used in combination with acquire_nonblocking() */ + void (*release_nonblocking)(xine_ticket_t *self, int irrevocable); + void (*dispose)(xine_ticket_t *self); pthread_mutex_t lock; -- cgit v1.2.3 From bd88a5c94af0af727680606a22ec9414fba68366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Thu, 12 Apr 2007 22:33:26 +0200 Subject: Provide a function to query buffer usage. This function shall be used to poll the number of remaining frames from a certain point in time on until the reported numbers are all 0. At that point in time, the content on screen is identical to a certain state of the stream, at which for example, a hardcopy may be taken. --- src/xine-engine/xine.c | 28 ++++++++++++++++++++++++++++ src/xine-engine/xine_internal.h | 2 ++ 2 files changed, 30 insertions(+) diff --git a/src/xine-engine/xine.c b/src/xine-engine/xine.c index 612bf8dcc..9623668dc 100644 --- a/src/xine-engine/xine.c +++ b/src/xine-engine/xine.c @@ -2069,3 +2069,31 @@ int xine_stream_master_slave(xine_stream_t *master, xine_stream_t *slave, slave->master = master->master; return 1; } + +int _x_query_buffer_usage(xine_stream_t *stream, int *num_video_buffers, int *num_audio_buffers, int *num_video_frames, int *num_audio_frames) +{ + int ticket_acquired = -1; + + if (num_video_buffers) + *num_video_buffers = (stream->video_fifo ? stream->video_fifo->size(stream->video_fifo) : 0); + + if (num_audio_buffers) + *num_audio_buffers = (stream->audio_fifo ? stream->audio_fifo->size(stream->audio_fifo) : 0); + + if ((num_video_frames && stream->video_out) + || (num_audio_frames && stream->audio_out)) { + + ticket_acquired = stream->xine->port_ticket->acquire_nonblocking(stream->xine->port_ticket, 1); + } + + if (num_video_frames) + *num_video_frames = ((ticket_acquired && stream->video_out) ? stream->video_out->get_property(stream->video_out, VO_PROP_BUFS_IN_FIFO) : 0); + + if (num_audio_frames) + *num_audio_frames = ((ticket_acquired && stream->audio_out) ? stream->audio_out->get_property(stream->audio_out, AO_PROP_BUFS_IN_FIFO) : 0); + + if (ticket_acquired > 0) + stream->xine->port_ticket->release_nonblocking(stream->xine->port_ticket, 1); + + return ticket_acquired != 0; +} diff --git a/src/xine-engine/xine_internal.h b/src/xine-engine/xine_internal.h index da6f88a7f..c88bcc904 100644 --- a/src/xine-engine/xine_internal.h +++ b/src/xine-engine/xine_internal.h @@ -366,6 +366,8 @@ struct xine_stream_s { * private function prototypes: */ +int _x_query_buffer_usage(xine_stream_t *stream, int *num_video_buffers, int *num_audio_buffers, int *num_video_frames, int *num_audio_frames) XINE_PROTECTED; + void _x_handle_stream_end (xine_stream_t *stream, int non_user) XINE_PROTECTED; /* report message to UI. usually these are async errors */ -- cgit v1.2.3 From f01ea9ec4d506bf4080a569129cafe4a966b0724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Thu, 12 Apr 2007 22:59:54 +0200 Subject: Let xxmc switch back to an unaccelerated context. The current code misses the ability to switch back to an unaccelerated context, e. g. when previously MPEG2 material was displayed which is then followed by H.264 material. As the latter is not handled as XINE_IMGFMT_XXMC there was no way to leave the accelerated context and therefore the images did not appear on screen. --- src/video_out/video_out_xxmc.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/video_out/video_out_xxmc.c b/src/video_out/video_out_xxmc.c index fd19f391b..498eaab33 100644 --- a/src/video_out/video_out_xxmc.c +++ b/src/video_out/video_out_xxmc.c @@ -977,7 +977,7 @@ static void xvmc_check_colorkey_properties(xxmc_driver_t *driver) static int xxmc_xvmc_update_context(xxmc_driver_t *driver, xxmc_frame_t *frame, - uint32_t width, uint32_t height) + uint32_t width, uint32_t height, int frame_format_xxmc) { xine_xxmc_t *xxmc = &frame->xxmc_data; @@ -991,8 +991,12 @@ static int xxmc_xvmc_update_context(xxmc_driver_t *driver, xxmc_frame_t *frame, xprintf(driver->xine, XINE_VERBOSITY_LOG, "video_out_xxmc: New format. Need to change XvMC Context.\n" - "width: %d height: %d mpeg: %d acceleration: %d\n", width, height, - xxmc->mpeg, xxmc->acceleration); + "width: %d height: %d", width, height); + if (frame_format_xxmc) { + xprintf(driver->xine, XINE_VERBOSITY_LOG, + " mpeg: %d acceleration: %d", xxmc->mpeg, xxmc->acceleration); + } + xprintf(driver->xine, XINE_VERBOSITY_LOG, "\n"); if (frame->xvmc_surf) xxmc_xvmc_free_surface( driver , frame->xvmc_surf); @@ -1000,7 +1004,7 @@ static int xxmc_xvmc_update_context(xxmc_driver_t *driver, xxmc_frame_t *frame, xxmc_dispose_context( driver ); - if (xxmc_find_context( driver, xxmc, width, height )) { + if (frame_format_xxmc && xxmc_find_context( driver, xxmc, width, height )) { xxmc_create_context( driver, width, height); xvmc_check_colorkey_properties( driver ); xxmc_setup_subpictures(driver, width, height); @@ -1231,7 +1235,7 @@ static void xxmc_do_update_frame(vo_driver_t *this_gen, (this->xvmc_width != width) || (this->xvmc_height != height)) { this->last_accel_request = xxmc->acceleration; - xxmc_xvmc_update_context(this, frame, width, height); + xxmc_xvmc_update_context(this, frame, width, height, 1); } else { this->last_accel_request = xxmc->acceleration; } @@ -1254,6 +1258,11 @@ static void xxmc_do_update_frame(vo_driver_t *this_gen, xvmc_context_writer_unlock( &this->xvmc_lock); } else { + /* switch back to an unaccelerated context */ + if (this->last_accel_request != 0xFFFFFFFF) { + this->last_accel_request = 0xFFFFFFFF; + xxmc_xvmc_update_context(this, frame, width, height, 0); + } frame->vo_frame.proc_duplicate_frame_data = NULL; xxmc_do_update_frame_xv(this_gen, frame_gen, width, height, ratio, format, flags); -- cgit v1.2.3 From e8aa9780585fcec85b2a019af6e248687189e97c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Thu, 12 Apr 2007 23:12:28 +0200 Subject: Avoid keeping frames referenced which are nolonger used. The current implementation keeps references to VO_NUM_RECENT_FRAMES frames (for deinterlacing), but doesn't make any use of them. As many XXMC capable devices only supply 8 frames at all, keeping fewer frames referenced makes more available for decoding and thus avoids frame drops by keeping the number of frames which are ready for display more often above frame_drop_limit. --- src/video_out/video_out_xxmc.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/video_out/video_out_xxmc.c b/src/video_out/video_out_xxmc.c index 498eaab33..53e4b7995 100644 --- a/src/video_out/video_out_xxmc.c +++ b/src/video_out/video_out_xxmc.c @@ -1598,6 +1598,20 @@ static void xxmc_display_frame (vo_driver_t *this_gen, vo_frame_t *frame_gen) xxmc_add_recent_frame (this, frame); /* deinterlacing */ + /* + * the current implementation doesn't need recent frames for deinterlacing, + * but as most of the time we only have a little number of frames available + * per device, we only hold references to the most recent frame by filling + * the whole buffer with the same frame + */ + { + int i; + for (i = 1; i < VO_NUM_RECENT_FRAMES; i++) { + frame->vo_frame.lock(&frame->vo_frame); + xxmc_add_recent_frame (this, frame); /* deinterlacing */ + } + } + if ((frame->format == XINE_IMGFMT_XXMC) && (!xxmc->decoded || !xxmc_xvmc_surface_valid(this, frame->xvmc_surf))) { xvmc_context_reader_unlock( &this->xvmc_lock ); @@ -1943,7 +1957,7 @@ static void xxmc_dispose (vo_driver_t *this_gen) { for( i=0; i < VO_NUM_RECENT_FRAMES; i++ ) { if( this->recent_frames[i] ) - this->recent_frames[i]->vo_frame.dispose + this->recent_frames[i]->vo_frame.free (&this->recent_frames[i]->vo_frame); this->recent_frames[i] = NULL; } -- cgit v1.2.3 From 7b50cbef25c8e2769bfbdd801b7318bb86047f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Thu, 12 Apr 2007 23:36:51 +0200 Subject: Make bob deinterlacing more precisely and skip it on demand. Bob deinterlacing is implemented as showing the top field, sleeping for half the frame duration and showing the bottom field. Most drivers tend to synchronize displaying a field on the VBI and thus displaying a field may take up to half the frame duration in certain cases. According to the original code, the sleep took always half the frame duration and therefore the second field could get displayed too late. As a result, the driver was syncing to VBI most often, so that things got even worse. The changed code now calculates the sleep time in a way that the second field gets displayed half the frame duration after the first field. Moreover, it monitors how much time was spent to display the first field and when this time exceeds 75 % of the field time (= half the frame time), it skips displaying the second field, as usually this is an indicator that the driver has no more frame buffers left. So displaying the second field would just make things go worse. --- src/video_out/video_out_xxmc.c | 53 +++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/src/video_out/video_out_xxmc.c b/src/video_out/video_out_xxmc.c index 53e4b7995..37dad7a99 100644 --- a/src/video_out/video_out_xxmc.c +++ b/src/video_out/video_out_xxmc.c @@ -1588,6 +1588,12 @@ static void xxmc_display_frame (vo_driver_t *this_gen, vo_frame_t *frame_gen) xxmc_frame_t *frame = (xxmc_frame_t *) frame_gen; xine_xxmc_t *xxmc = &frame->xxmc_data; int first_field; + struct timeval tv_top; + + /* + * take time to calculate the time to sleep for the bottom field + */ + gettimeofday(&tv_top, 0); /* * queue frames (deinterlacing) @@ -1653,20 +1659,41 @@ static void xxmc_display_frame (vo_driver_t *this_gen, vo_frame_t *frame_gen) this->cur_field); XVMCUNLOCKDISPLAY( this->display ); if (this->deinterlace_enabled && this->bob) { - unsigned - ms_per_field = 500 * frame->vo_frame.duration / 90000 - 2; - - usleep(ms_per_field*1000); - this->cur_field = (frame->vo_frame.top_field_first) ? XVMC_BOTTOM_FIELD : XVMC_TOP_FIELD; + struct timeval tv_middle; + long us_spent_so_far, us_per_field = frame->vo_frame.duration * 50 / 9; - XVMCLOCKDISPLAY( this->display ); - XvMCPutSurface( this->display, frame->xvmc_surf , this->drawable, - this->sc.displayed_xoffset, this->sc.displayed_yoffset, - this->sc.displayed_width, this->sc.displayed_height, - this->sc.output_xoffset, this->sc.output_yoffset, - this->sc.output_width, this->sc.output_height, - this->cur_field); - XVMCUNLOCKDISPLAY( this->display ); + gettimeofday(&tv_middle, 0); + us_spent_so_far = (tv_middle.tv_sec - tv_top.tv_sec) * 1000000 + (tv_middle.tv_usec - tv_top.tv_usec); + if (us_spent_so_far < 0) + us_spent_so_far = 0; + + /* + * typically, the operations above take just a few milliseconds, but when the + * driver actively waits to sync on the next field, we better skip showing the + * other field as it would lead to further busy waiting + * so display the other field only if we've spent less than 75 % of the per + * field time so far + */ + if (4 * us_spent_so_far < 3 * us_per_field) { + long us_delay = (us_per_field - 2000) - us_spent_so_far; + if (us_delay > 0) { + xvmc_context_reader_unlock( &this->xvmc_lock ); + xine_usec_sleep(us_delay); + LOCK_AND_SURFACE_VALID( this, frame->xvmc_surf ); + } + + this->cur_field = (frame->vo_frame.top_field_first) ? XVMC_BOTTOM_FIELD : XVMC_TOP_FIELD; + + XVMCLOCKDISPLAY( this->display ); + XvMCPutSurface( this->display, frame->xvmc_surf , this->drawable, + this->sc.displayed_xoffset, this->sc.displayed_yoffset, + this->sc.displayed_width, this->sc.displayed_height, + this->sc.output_xoffset, this->sc.output_yoffset, + this->sc.output_width, this->sc.output_height, + this->cur_field); + + XVMCUNLOCKDISPLAY( this->display ); + } } } else { XLockDisplay (this->display); -- cgit v1.2.3 From 60871f034457fb6727df480c311b76e81a4ca6fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Thu, 12 Apr 2007 23:51:04 +0200 Subject: Disable bob deinterlacing on a per frame decision. There are some situations where bob deinterlacing doesn't make much sense, as the effect doesn't work for example for slow motion. And in fast motion mode, the sleep between displaying the two fields lowers the reachable fast motion frame rate. Another annoying effect is the reduction in vertical resolution for still images. The changed code tries to detect such issues and disables bob deinterlacing for such frames. The detection of still images is still to come. --- src/video_out/video_out_xxmc.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/video_out/video_out_xxmc.c b/src/video_out/video_out_xxmc.c index 37dad7a99..20ce00a5e 100644 --- a/src/video_out/video_out_xxmc.c +++ b/src/video_out/video_out_xxmc.c @@ -1588,6 +1588,7 @@ static void xxmc_display_frame (vo_driver_t *this_gen, vo_frame_t *frame_gen) xxmc_frame_t *frame = (xxmc_frame_t *) frame_gen; xine_xxmc_t *xxmc = &frame->xxmc_data; int first_field; + int disable_deinterlace = 0; struct timeval tv_top; /* @@ -1595,6 +1596,19 @@ static void xxmc_display_frame (vo_driver_t *this_gen, vo_frame_t *frame_gen) */ gettimeofday(&tv_top, 0); + /* + * bob deinterlacing doesn't make much sense for still images or at replay speeds + * other than 100 %, so let's disable deinterlacing at all for this frame + */ + if (this->deinterlace_enabled && this->bob) { + disable_deinterlace = frame->vo_frame.progressive_frame + || !frame->vo_frame.stream + || xine_get_param(frame->vo_frame.stream, XINE_PARAM_FINE_SPEED) != XINE_FINE_SPEED_NORMAL; + if (!disable_deinterlace) { + /* TODO: still frame detection */ + } + } + /* * queue frames (deinterlacing) * free old frames @@ -1645,7 +1659,7 @@ static void xxmc_display_frame (vo_driver_t *this_gen, vo_frame_t *frame_gen) first_field = (frame->vo_frame.top_field_first) ? XVMC_TOP_FIELD : XVMC_BOTTOM_FIELD; first_field = (this->bob) ? first_field : XVMC_TOP_FIELD; - this->cur_field = (this->deinterlace_enabled) ? first_field : XVMC_FRAME_PICTURE; + this->cur_field = (this->deinterlace_enabled && !disable_deinterlace) ? first_field : XVMC_FRAME_PICTURE; xxmc_redraw_needed (this_gen); if (frame->format == XINE_IMGFMT_XXMC) { @@ -1658,7 +1672,7 @@ static void xxmc_display_frame (vo_driver_t *this_gen, vo_frame_t *frame_gen) this->sc.output_width, this->sc.output_height, this->cur_field); XVMCUNLOCKDISPLAY( this->display ); - if (this->deinterlace_enabled && this->bob) { + if (this->deinterlace_enabled && !disable_deinterlace && this->bob) { struct timeval tv_middle; long us_spent_so_far, us_per_field = frame->vo_frame.duration * 50 / 9; -- cgit v1.2.3 From 42db183b596601354643b6e8f4997109b2324f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Fri, 13 Apr 2007 00:01:57 +0200 Subject: Disable bob deinterlacing on a per frame decision (continued). One can assume that a still frame is to show when there are no more frames left to display. The changed code uses _x_query_buffer_usage() to retrieve the number of frames waiting to be displayed to integrate this information into the decision. --- src/video_out/video_out_xxmc.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/video_out/video_out_xxmc.c b/src/video_out/video_out_xxmc.c index 20ce00a5e..11bfc7e1a 100644 --- a/src/video_out/video_out_xxmc.c +++ b/src/video_out/video_out_xxmc.c @@ -1605,7 +1605,9 @@ static void xxmc_display_frame (vo_driver_t *this_gen, vo_frame_t *frame_gen) || !frame->vo_frame.stream || xine_get_param(frame->vo_frame.stream, XINE_PARAM_FINE_SPEED) != XINE_FINE_SPEED_NORMAL; if (!disable_deinterlace) { - /* TODO: still frame detection */ + int vo_bufs_in_fifo = 0; + _x_query_buffer_usage(frame->vo_frame.stream, NULL, NULL, &vo_bufs_in_fifo, NULL); + disable_deinterlace = (vo_bufs_in_fifo <= 0); } } -- cgit v1.2.3 From 9a3aa1d854a3c8dcdf22cbe5db289425321b2499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Sun, 15 Apr 2007 19:06:31 +0200 Subject: Initialize image size in bmiheader with data from AVCodecContext, if still uninitalized. --- src/libffmpeg/ff_video_decoder.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libffmpeg/ff_video_decoder.c b/src/libffmpeg/ff_video_decoder.c index e04c680b7..3943fd05d 100644 --- a/src/libffmpeg/ff_video_decoder.c +++ b/src/libffmpeg/ff_video_decoder.c @@ -1186,6 +1186,11 @@ static void ff_handle_buffer (ff_video_decoder_t *this, buf_element_t *buf) { if ((this->aspect_ratio_prio < 2) && av_cmp_q(this->context->sample_aspect_ratio, avr00)) { + if (!this->bih.biWidth || !this->bih.biHeight) { + this->bih.biWidth = this->context->width; + this->bih.biHeight = this->context->height; + } + this->aspect_ratio = av_q2d(this->context->sample_aspect_ratio) * (double)this->bih.biWidth / (double)this->bih.biHeight; this->aspect_ratio_prio = 2; -- cgit v1.2.3 From 09bffb7aef6adc609c235ab43a80b7c0a4cc3150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Reinhard=20Ni=C3=9Fl?= Date: Sun, 15 Apr 2007 23:14:51 +0200 Subject: Avoid sending BUF_FLAG_FRAME_END before the first frame. When BUF_FLAG_FRAME_END is sent before the first frame, decoding fails as there is no data and a "bad" frame of size 0x0 will be allocated, which is really bad as such as frame is simply invalid. --- src/demuxers/demux_mpeg_pes.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/demuxers/demux_mpeg_pes.c b/src/demuxers/demux_mpeg_pes.c index c5769e3e3..fbfde4e60 100644 --- a/src/demuxers/demux_mpeg_pes.c +++ b/src/demuxers/demux_mpeg_pes.c @@ -1135,17 +1135,20 @@ static int32_t parse_video_stream(demux_mpeg_pes_t *this, uint8_t *p, buf_elemen */ if (this->mpeg12_h264_detected & 1) { buf_type = BUF_VIDEO_H264; - int nal_type_code = -1; - if (payload_size >= 4 && p[2] == 0x01 && p[1] == 0x00 && p[0] == 0x00) - nal_type_code = p[3] & 0x1f; - if (nal_type_code == 9) { /* access unit delimiter */ - buf_element_t *b = this->video_fifo->buffer_pool_alloc (this->video_fifo); - b->content = b->mem; - b->size = 0; - b->pts = 0; - b->type = buf_type; - b->decoder_flags = BUF_FLAG_FRAME_END; - this->video_fifo->put (this->video_fifo, b); + /* omit sending BUF_FLAG_FRAME_END for the first AUD occurence */ + if (this->mpeg12_h264_detected > 2) { + int nal_type_code = -1; + if (payload_size >= 4 && p[2] == 0x01 && p[1] == 0x00 && p[0] == 0x00) + nal_type_code = p[3] & 0x1f; + if (nal_type_code == 9) { /* access unit delimiter */ + buf_element_t *b = this->video_fifo->buffer_pool_alloc (this->video_fifo); + b->content = b->mem; + b->size = 0; + b->pts = 0; + b->type = buf_type; + b->decoder_flags = BUF_FLAG_FRAME_END; + this->video_fifo->put (this->video_fifo, b); + } } } -- cgit v1.2.3 From 643c083ce09d285ad3a93187a5224472c159542a Mon Sep 17 00:00:00 2001 From: "hadess@cookie.hadess.net" Date: Sat, 5 May 2007 00:41:54 +0100 Subject: - Fix _x_io_select exiting when the select has been interrupted (EINTR errno) when we want to run until the timeout has occurred, partially fixes Totem's browser plugin playing back browser streams with the xine-lib backend See http://bugzilla.gnome.org/show_bug.cgi?id=375866 for details --- src/xine-engine/io_helper.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xine-engine/io_helper.c b/src/xine-engine/io_helper.c index ad36c565e..67dddcfdc 100644 --- a/src/xine-engine/io_helper.c +++ b/src/xine-engine/io_helper.c @@ -276,7 +276,7 @@ int _x_io_select (xine_stream_t *stream, int fd, int state, int timeout_msec) { wset = (state & XIO_WRITE_READY) ? &fdset : NULL; ret = select (fd + 1, rset, wset, NULL, &select_timeout); - if (ret == -1) { + if (ret == -1 && errno != EINTR) { /* select error */ return XIO_ERROR; } else if (ret == 1) { -- cgit v1.2.3 From 89a988361909cc345663d8f7d777a006a605e9c9 Mon Sep 17 00:00:00 2001 From: "hadess@cookie.hadess.net" Date: Sat, 5 May 2007 01:03:47 +0100 Subject: Up the version number in HG so we can check for the _x_io_select fix --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index b43bd1b8f..003af6722 100644 --- a/configure.ac +++ b/configure.ac @@ -16,7 +16,7 @@ dnl XINE_SUB += 1; continue with XINE_LT_* values below dnl XINE_MAJOR=1 XINE_MINOR=1 -XINE_SUB=6 +XINE_SUB=7 #if test $XINE_SUB -eq 0 ; then # XINE_SUBPART=""; -- cgit v1.2.3 From 111fe67b6395937ba487602d3bb28a809dceff71 Mon Sep 17 00:00:00 2001 From: Darren Salt Date: Sun, 6 May 2007 15:48:24 +0100 Subject: Changelog entries. --- ChangeLog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ChangeLog b/ChangeLog index 5ae4e8a77..c92214f15 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,12 @@ xine-lib (1.1.7) (unreleased) [Bug 1707526] * Fix proxy usage when the hostnames cannot be resolved. Thanks to Jeff Mitchell for reporting and testing the fix. + * Avoid zero-sized frames when demuxing MPEG PES. + * Improved MPEG2 detection and optimised processing. + * Extract AFD information (commonly used in UK DVB-T) from the MPEG stream. + * Ensure that the ffmpeg video image size is properly initialised. + * Allow XxMC to switch back to software decoding; don't deinterlace if it's + not needed for any given frame. xine-lib (1.1.6) * Split the DirectFB plugin into X11 and non-X versions. -- cgit v1.2.3 From 213eac661441a9c482f280c5cb5ec63240c8d29e Mon Sep 17 00:00:00 2001 From: Simon Farnsworth Date: Fri, 4 May 2007 18:25:14 +0100 Subject: [PATCH] Fix deinterlacing in video_out_xv.c Xv drivers are permitted to return a bigger image than we asked for, to work around hardware constraints. If this happens, 1.1.6's video_out_xv cannot deinterlace properly. Fix this by deinterlacing based on the width of the Xv image; the Xv driver will discard the extra horizontal data. Without this patch, you get a barber-pole effect if the input video is interlaced *and* the Xv driver chooses to round up the width of the requested image. -- Simon Farnsworth --- src/video_out/video_out_xv.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/video_out/video_out_xv.c b/src/video_out/video_out_xv.c index 2cba3c2b6..dde15fb0f 100644 --- a/src/video_out/video_out_xv.c +++ b/src/video_out/video_out_xv.c @@ -512,17 +512,17 @@ static void xv_deinterlace_frame (xv_driver_t *this) { else recent_bitmaps[i] = NULL; - deinterlace_yuv( this->deinterlace_frame.image->data+frame->width*frame->height, - recent_bitmaps, frame->width/2, frame->height/2, this->deinterlace_method ); + deinterlace_yuv( this->deinterlace_frame.image->data+this->deinterlace_frame.image->width*frame->height, + recent_bitmaps, this->deinterlace_frame.image->width/2, frame->height/2, this->deinterlace_method ); for( i = 0; i < VO_NUM_RECENT_FRAMES; i++ ) if( this->recent_frames[i] && this->recent_frames[i]->width == frame->width && this->recent_frames[i]->height == frame->height ) - recent_bitmaps[i] = this->recent_frames[i]->image->data + frame->width*frame->height*5/4; + recent_bitmaps[i] = this->recent_frames[i]->image->data + this->deinterlace_frame.image->width*frame->height*5/4; else recent_bitmaps[i] = NULL; - deinterlace_yuv( this->deinterlace_frame.image->data+frame->width*frame->height*5/4, - recent_bitmaps, frame->width/2, frame->height/2, this->deinterlace_method ); + deinterlace_yuv( this->deinterlace_frame.image->data+this->deinterlace_frame.image->width*frame->height*5/4, + recent_bitmaps, this->deinterlace_frame.image->width/2, frame->height/2, this->deinterlace_method ); #else @@ -541,7 +541,7 @@ static void xv_deinterlace_frame (xv_driver_t *this) { recent_bitmaps[i] = NULL; deinterlace_yuv( this->deinterlace_frame.image->data, recent_bitmaps, - frame->width, frame->height, this->deinterlace_method ); + this->deinterlace_frame.image->width, frame->height, this->deinterlace_method ); } else { /* -- cgit v1.2.3 From efbef33b133ee728285f6bfb5f5014041dc991d4 Mon Sep 17 00:00:00 2001 From: Darren Salt Date: Tue, 8 May 2007 14:27:35 +0100 Subject: Internal libpostproc.a needs libavutil.a, so link planar post-plugin with both. --- src/post/planar/Makefile.am | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/post/planar/Makefile.am b/src/post/planar/Makefile.am index 6d239202b..4461f8314 100644 --- a/src/post/planar/Makefile.am +++ b/src/post/planar/Makefile.am @@ -7,10 +7,12 @@ postproc_lib = $(FFMPEG_POSTPROC_LIBS) ff_cflags = $(FFMPEG_POSTPROC_CFLAGS) else ff_cflags = -I$(top_srcdir)/contrib/ffmpeg/libpostproc -postproc_lib = $(top_builddir)/contrib/ffmpeg/libpostproc/libpostproc.a - +postproc_lib = $(top_builddir)/contrib/ffmpeg/libpostproc/libpostproc.a \ + $(top_builddir)/contrib/ffmpeg/libavutil/libavutil.a $(top_builddir)/contrib/ffmpeg/libpostproc/libpostproc.a: $(MAKE) -C $(top_builddir)/contrib/ffmpeg/ -f makefile.xine libpostproc/libpostproc.a +$(top_builddir)/contrib/ffmpeg/libavutil/libavutil.a: + $(MAKE) -C $(top_builddir)/contrib/ffmpeg/ -f makefile.xine libpostproc/libpostproc.a endif # -fomit-frame-pointer is always needed. it might cause debug to not -- cgit v1.2.3