/* * Copyright (C) 2000-2003 the xine project * * This file is part of xine, a free video player. * * xine 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. * * xine 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * * network buffering control */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include "net_buf_ctrl.h" #define DEFAULT_LOW_WATER_MARK 1 #define DEFAULT_HIGH_WATER_MARK 5000 /* in 1/1000 s */ #define WRAP_THRESHOLD 5*90000 /* from the asf demuxer */ #define FIFO_PUT 0 #define FIFO_GET 1 /* #define LOG */ struct nbc_s { xine_stream_t *stream; int buffering; int enabled; int progress; fifo_buffer_t *video_fifo; fifo_buffer_t *audio_fifo; int video_fifo_fill; int audio_fifo_fill; int video_fifo_free; int audio_fifo_free; int64_t video_fifo_length; /* in ms */ int64_t audio_fifo_length; /* in ms */ int64_t low_water_mark; int64_t high_water_mark; /* bitrate */ int64_t video_last_pts; int64_t audio_last_pts; int64_t video_first_pts; int64_t audio_first_pts; int64_t video_fifo_size; int64_t audio_fifo_size; int64_t video_br; int64_t audio_br; pthread_mutex_t mutex; }; static void report_progress (xine_stream_t *stream, int p) { xine_event_t event; xine_progress_data_t prg; prg.description = _("Buffering..."); prg.percent = (p>100)?100:p; event.type = XINE_EVENT_PROGRESS; event.data = &prg; event.data_length = sizeof (xine_progress_data_t); xine_event_send (stream, &event); } static void nbc_set_speed_pause (xine_stream_t *stream) { #ifdef LOG printf("\nnet_buf_ctrl: nbc_put_cb: set_speed_pause\n"); #endif stream->xine->clock->set_speed (stream->xine->clock, XINE_SPEED_PAUSE); stream->xine->clock->set_option (stream->xine->clock, CLOCK_SCR_ADJUSTABLE, 0); if (stream->audio_out) stream->audio_out->set_property(stream->audio_out,AO_PROP_PAUSED,2); } static void nbc_set_speed_normal (xine_stream_t *stream) { #ifdef LOG printf("\nnet_buf_ctrl: nbc_put_cb: set_speed_normal\n"); #endif stream->xine->clock->set_speed (stream->xine->clock, XINE_SPEED_NORMAL); stream->xine->clock->set_option (stream->xine->clock, CLOCK_SCR_ADJUSTABLE, 1); if (stream->audio_out) stream->audio_out->set_property(stream->audio_out,AO_PROP_PAUSED,0); } void nbc_check_buffers (nbc_t *this) { /* Deprecated */ } static void display_stats (nbc_t *this) { if (this->stream->xine->verbosity >= XINE_VERBOSITY_DEBUG) { printf("net_buf_ctrl: vff=%3d%% aff=%3d%% vf=%4.1fs af=%4.1fs vbr=%4lld abr=%4lld b=%1d e=%1d\r", this->video_fifo_fill, this->audio_fifo_fill, (float)(this->video_fifo_length / 1000), (float)(this->audio_fifo_length / 1000), this->video_br / 1000, this->audio_br / 1000, this->buffering, this->enabled ); fflush(stdout); } } /* Try to compute the length of the fifo in 1/1000 s * 2 methods : * if the bitrate is known * use the size of the fifo * else * use the the first and the last pts of the fifo */ static void nbc_compute_fifo_length(nbc_t *this, fifo_buffer_t *fifo, buf_element_t *buf, int action) { int fifo_free, fifo_fill; int64_t video_br, audio_br; int has_video, has_audio; int64_t pts_diff = 0; has_video = this->stream->stream_info[XINE_STREAM_INFO_HAS_VIDEO]; has_audio = this->stream->stream_info[XINE_STREAM_INFO_HAS_AUDIO]; video_br = this->stream->stream_info[XINE_STREAM_INFO_VIDEO_BITRATE]; audio_br = this->stream->stream_info[XINE_STREAM_INFO_AUDIO_BITRATE]; fifo_free = fifo->buffer_pool_num_free; fifo_fill = fifo->fifo_size; if (fifo == this->video_fifo) { this->video_fifo_free = fifo_free; this->video_fifo_fill = (100 * fifo_fill) / (fifo_fill + fifo_free - 1); this->video_fifo_size = fifo->fifo_data_size; if (video_br) { this->video_br = video_br; this->video_fifo_length = (8000 * this->video_fifo_size) / this->video_br; } else { if (buf->pts) { if (action == FIFO_PUT) { pts_diff = buf->pts - this->video_last_pts; if ((pts_diff >= 0) && (pts_diff < WRAP_THRESHOLD)) { this->video_last_pts = buf->pts; } else { /* discontinuity detected */ #ifdef LOG printf("\nnet_buf_ctrl: nbc_compute_fifo_length: video discontinuity: %lld\n", pts_diff); #endif this->video_last_pts = buf->pts; /* smooth the discontinuity */ this->video_first_pts = buf->pts - 90 * this->video_fifo_length; } if (this->video_first_pts == 0) { this->video_first_pts = buf->pts; } } else { this->video_first_pts = buf->pts; } this->video_fifo_length = (this->video_last_pts - this->video_first_pts) / 90; if (this->video_fifo_length) this->video_br = 8000 * (this->video_fifo_size / this->video_fifo_length); else this->video_br = 0; } } } else { this->audio_fifo_free = fifo_free; this->audio_fifo_fill = (100 * fifo_fill) / (fifo_fill + fifo_free - 1); this->audio_fifo_size = fifo->fifo_data_size; if (audio_br) { this->audio_br = audio_br; this->audio_fifo_length = (8000 * this->audio_fifo_size) / this->audio_br; } else { if (buf->pts) { if (action == FIFO_PUT) { pts_diff = buf->pts - this->audio_last_pts; if ((pts_diff >= 0) && (pts_diff < WRAP_THRESHOLD)) { this->audio_last_pts = buf->pts; } else { /* discontinuity detected */ #ifdef LOG printf("\nnet_buf_ctrl: nbc_compute_fifo_length: audio discontinuity: %lld\n", pts_diff); #endif this->audio_last_pts = buf->pts; /* smooth the discontinuity */ this->audio_first_pts = buf->pts - 90 * this->audio_fifo_length; } if (!this->audio_first_pts) { this->audio_first_pts = buf->pts; } } else { this->audio_first_pts = buf->pts; } this->audio_fifo_length = (this->audio_last_pts - this->audio_first_pts) / 90; if (this->audio_fifo_length) this->audio_br = 8000 * (this->audio_fifo_size / this->audio_fifo_length); else this->audio_br = 0; } } } } /* Put callback * the fifo mutex is locked */ static void nbc_put_cb (fifo_buffer_t *fifo, buf_element_t *buf, void *this_gen) { nbc_t *this = (nbc_t*)this_gen; int64_t progress = 0; int64_t video_p = 0; int64_t audio_p = 0; int has_video, has_audio; uint32_t buf_major_mask; pthread_mutex_lock(&this->mutex); /* stop buffering at the end of the stream */ if ((buf->decoder_flags & BUF_FLAG_END_USER) || (buf->decoder_flags & BUF_FLAG_END_STREAM)) { this->enabled = 0; if (this->buffering) { this->buffering = 0; nbc_set_speed_normal(this->stream); } } /* do nothing if we are at the end of the stream */ if (!this->enabled) { buf_major_mask = buf->type & BUF_MAJOR_MASK; if ((buf_major_mask == BUF_VIDEO_BASE) || (buf_major_mask == BUF_AUDIO_BASE)) { /* a new stream starts */ if (this->stream->xine->verbosity >= XINE_VERBOSITY_DEBUG) printf("\nnet_buf_ctrl: nbc_put_cb: starts buffering\n"); this->enabled = 1; this->buffering = 1; this->video_first_pts = 0; this->video_last_pts = 0; this->audio_first_pts = 0; this->audio_last_pts = 0; this->video_fifo_length = 0; this->audio_fifo_length = 0; nbc_set_speed_pause(this->stream); this->progress = 0; report_progress (this->stream, progress); } else { display_stats(this); pthread_mutex_unlock(&this->mutex); return; } } nbc_compute_fifo_length(this, fifo, buf, FIFO_PUT); if (this->buffering) { has_video = this->stream->stream_info[XINE_STREAM_INFO_HAS_VIDEO]; has_audio = this->stream->stream_info[XINE_STREAM_INFO_HAS_AUDIO]; /* restart playing if : * - one fifo is full (to avoid deadlock) * - high_water_mark is reached by all fifos * do not restart if has_video and has_audio are false to avoid * a yoyo effect at the beginning of the stream when these values * are not yet known. * * be sure that the next buffer_pool_alloc() call will not deadlock, * we need at least 2 buffers (see buffer.c) */ if ((fifo->buffer_pool_num_free <= 2) || (((!has_video) || (this->video_fifo_length > this->high_water_mark)) && ((!has_audio) || (this->audio_fifo_length > this->high_water_mark)) && (has_video || has_audio))) { this->progress = 100; report_progress (this->stream, 100); this->buffering = 0; if (this->stream->xine->verbosity >= XINE_VERBOSITY_DEBUG) printf("\nnet_buf_ctrl: nbc_put_cb: stops buffering\n"); nbc_set_speed_normal(this->stream); } else { /* compute the buffering progress * 50%: video * 50%: audio */ video_p = ((this->video_fifo_length * 50) / this->high_water_mark); if (video_p > 50) video_p = 50; audio_p = ((this->audio_fifo_length * 50) / this->high_water_mark); if (audio_p > 50) audio_p = 50; if ((has_video) && (has_audio)) { progress = video_p + audio_p; } else if (has_video) { progress = 2 * video_p; } else { progress = 2 * audio_p; } /* if the progress can't be computed using the fifo length, use the number of buffers */ if (!progress) { video_p = this->video_fifo_fill; audio_p = this->audio_fifo_fill; progress = (video_p > audio_p) ? video_p : audio_p; } if (progress > this->progress) { report_progress (this->stream, progress); this->progress = progress; } } } display_stats(this); pthread_mutex_unlock(&this->mutex); } /* Get callback * the fifo mutex is locked */ static void nbc_get_cb (fifo_buffer_t *fifo, buf_element_t *buf, void *this_gen) { nbc_t *this = (nbc_t*)this_gen; int other_fifo_free; pthread_mutex_lock(&this->mutex); nbc_compute_fifo_length(this, fifo, buf, FIFO_GET); if (!this->enabled) { display_stats(this); pthread_mutex_unlock(&this->mutex); return; } if (!this->buffering) { /* start buffering if one fifo is empty */ if (fifo->fifo_size == 0) { if (fifo == this->video_fifo) { other_fifo_free = this->audio_fifo_free; } else { other_fifo_free = this->video_fifo_free; } /* Don't pause if the other fifo is full because the next put() will restart the engine */ if (other_fifo_free > 2) { this->buffering = 1; this->progress = 0; report_progress (this->stream, 0); if (this->stream->xine->verbosity >= XINE_VERBOSITY_DEBUG) printf("\nnet_buf_ctrl: nbc_put_cb: starts buffering\n"); nbc_set_speed_pause(this->stream); } } } else { nbc_set_speed_pause(this->stream); } display_stats(this); pthread_mutex_unlock(&this->mutex); } nbc_t *nbc_init (xine_stream_t *stream) { nbc_t *this = (nbc_t *) malloc (sizeof (nbc_t)); fifo_buffer_t *video_fifo = stream->video_fifo; fifo_buffer_t *audio_fifo = stream->audio_fifo; #ifdef LOG printf("net_buf_ctrl: nbc_init\n"); #endif pthread_mutex_init (&this->mutex, NULL); this->stream = stream; this->buffering = 0; this->enabled = 0; this->low_water_mark = DEFAULT_LOW_WATER_MARK; this->high_water_mark = DEFAULT_HIGH_WATER_MARK; this->progress = 0; this->video_fifo = video_fifo; this->audio_fifo = audio_fifo; this->video_fifo_fill = 0; this->audio_fifo_fill = 0; this->video_fifo_free = 0; this->audio_fifo_free = 0; this->video_fifo_length = 0; this->audio_fifo_length = 0; this->video_last_pts = 0; this->audio_last_pts = 0; this->video_first_pts = 0; this->audio_first_pts = 0; this->video_fifo_size = 0; this->audio_fifo_size = 0; this->video_br = 0; this->audio_br = 0; video_fifo->register_put_cb(video_fifo, nbc_put_cb, this); video_fifo->register_get_cb(video_fifo, nbc_get_cb, this); if (audio_fifo) { audio_fifo->register_put_cb(audio_fifo, nbc_put_cb, this); audio_fifo->register_get_cb(audio_fifo, nbc_get_cb, this); } return this; } void nbc_close (nbc_t *this) { fifo_buffer_t *video_fifo = this->stream->video_fifo; fifo_buffer_t *audio_fifo = this->stream->audio_fifo; #ifdef LOG printf("\nnet_buf_ctrl: nbc_close\n"); #endif video_fifo->unregister_put_cb(video_fifo, nbc_put_cb); video_fifo->unregister_get_cb(video_fifo, nbc_get_cb); if (audio_fifo) { audio_fifo->unregister_put_cb(audio_fifo, nbc_put_cb); audio_fifo->unregister_get_cb(audio_fifo, nbc_get_cb); } pthread_mutex_lock(&this->mutex); this->stream->xine->clock->set_option (this->stream->xine->clock, CLOCK_SCR_ADJUSTABLE, 1); if (this->buffering) { this->buffering = 0; nbc_set_speed_normal(this->stream); } pthread_mutex_unlock(&this->mutex); free (this); #ifdef LOG printf("\nnet_buf_ctrl: nbc_close: done\n"); #endif } void nbc_set_high_water_mark(nbc_t *this, int value) { /* Deprecated this->high_water_mark = value; */ } void nbc_set_low_water_mark(nbc_t *this, int value) { /* Deprecated this->low_water_mark = value; */ }