// Used by the main thread to wakeup the cache thread, and to wait for the // cache thread. The cache mutex has to be locked when calling this function. // *retry_time should be set to 0 on the first call. // Returns CACHE_INTERRUPTED if the caller is supposed to abort. static int cache_wakeup_and_wait(struct priv *s, double *retry_time) { if (stream_check_interrupt(s->cache)) return CACHE_INTERRUPTED; double start = mp_time_sec(); if (!s->last_warn_time || start - s->last_warn_time >= CACHE_NO_SPAM) { // Print a "more severe" warning after waiting 1 second and no new data if ((*retry_time) >= 1.0) { MP_ERR(s, "Cache keeps not responding.\n"); s->last_warn_time = start; } else if (*retry_time > 0.1) { MP_WARN(s, "Cache is not responding - slow/stuck network connection?\n"); s->last_warn_time = start; } } pthread_cond_signal(&s->wakeup); mpthread_cond_timedwait_rel(&s->wakeup, &s->mutex, CACHE_WAIT_TIME); *retry_time += mp_time_sec() - start; return 0; }
static int cache_read(cache_vars_t *s, unsigned char *buf, int size) { int total=0; int sleep_count = 0; int last_max = s->max_filepos; while(size>0){ int pos,newb,len; //printf("CACHE2_READ: 0x%X <= 0x%X <= 0x%X \n",s->min_filepos,s->read_filepos,s->max_filepos); if(s->read_filepos>=s->max_filepos || s->read_filepos<s->min_filepos){ // eof? if(s->eof) break; if (s->max_filepos == last_max) { if (sleep_count++ == 10) {// mp_msg(MSGT_CACHE, MSGL_WARN, "Cache not filling, consider increasing -cache and/or -cache-min!\n"); } } else { last_max = s->max_filepos; sleep_count = 0; } // waiting for buffer fill... if (stream_check_interrupt(READ_SLEEP_TIME)) { s->eof = 1; break; } continue; // try again... } sleep_count = 0; newb=s->max_filepos-s->read_filepos; // new bytes in the buffer if(newb<min_fill) min_fill=newb; // statistics... // printf("*** newb: %d bytes ***\n",newb); pos=s->read_filepos - s->offset; if(pos<0) pos+=s->buffer_size; else if(pos>=s->buffer_size) pos-=s->buffer_size; if(newb>s->buffer_size-pos) newb=s->buffer_size-pos; // handle wrap... if(newb>size) newb=size; // check: if(s->read_filepos<s->min_filepos) mp_msg(MSGT_CACHE,MSGL_ERR,"Ehh. s->read_filepos<s->min_filepos !!! Report bug...\n"); // len=write(mem,newb) //printf("Buffer read: %d bytes\n",newb); memcpy(buf,&s->buffer[pos],newb); buf+=newb; len=newb; // ... s->read_filepos+=len; size-=len; total+=len; } return total; }
int cache_do_control(stream_t *stream, int cmd, void *arg) { int sleep_count = 0; cache_vars_t* s = stream->cache_data; switch (cmd) { case STREAM_CTRL_SEEK_TO_TIME: s->control_double_arg = *(double *)arg; s->control = cmd; break; case STREAM_CTRL_SEEK_TO_CHAPTER: case STREAM_CTRL_SET_ANGLE: s->control_uint_arg = *(unsigned *)arg; s->control = cmd; break; // the core might call these every frame, they are too slow for this... case STREAM_CTRL_GET_TIME_LENGTH: // case STREAM_CTRL_GET_CURRENT_TIME: *(double *)arg = s->stream_time_length; return s->stream_time_length ? STREAM_OK : STREAM_UNSUPPORTED; case STREAM_CTRL_GET_NUM_CHAPTERS: case STREAM_CTRL_GET_CURRENT_CHAPTER: case STREAM_CTRL_GET_ASPECT_RATIO: case STREAM_CTRL_GET_NUM_ANGLES: case STREAM_CTRL_GET_ANGLE: case -2: s->control = cmd; break; default: return STREAM_UNSUPPORTED; } cache_wakeup(stream); while (s->control != -1) { if (sleep_count++ == 1000) mp_msg(MSGT_CACHE, MSGL_WARN, "Cache not responding!\n"); if (stream_check_interrupt(CONTROL_SLEEP_TIME)) { s->eof = 1; return STREAM_UNSUPPORTED; } } switch (cmd) { case STREAM_CTRL_GET_TIME_LENGTH: case STREAM_CTRL_GET_CURRENT_TIME: case STREAM_CTRL_GET_ASPECT_RATIO: *(double *)arg = s->control_double_arg; break; case STREAM_CTRL_GET_NUM_CHAPTERS: case STREAM_CTRL_GET_CURRENT_CHAPTER: case STREAM_CTRL_GET_NUM_ANGLES: case STREAM_CTRL_GET_ANGLE: *(unsigned *)arg = s->control_uint_arg; break; case STREAM_CTRL_SEEK_TO_CHAPTER: case STREAM_CTRL_SEEK_TO_TIME: case STREAM_CTRL_SET_ANGLE: if (s->control_res != STREAM_UNSUPPORTED) stream->pos = s->read_filepos = s->control_new_pos; break; } return s->control_res; }
// Used by the main thread to wakeup the cache thread, and to wait for the // cache thread. The cache mutex has to be locked when calling this function. // *retry_time should be set to 0 on the first call. // Returns CACHE_INTERRUPTED if the caller is supposed to abort. static int cache_wakeup_and_wait(struct priv *s, double *retry_time) { if (stream_check_interrupt(0)) return CACHE_INTERRUPTED; // Print a "more severe" warning after waiting 1 second and no new data if ((*retry_time) >= 1.0) { mp_msg(MSGT_CACHE, MSGL_ERR, "Cache keeps not responding.\n"); } else if (*retry_time > 0.1) { mp_msg(MSGT_CACHE, MSGL_WARN, "Cache is not responding - slow/stuck network connection?\n"); } double start = mp_time_sec(); pthread_cond_signal(&s->wakeup); mpthread_cond_timed_wait(&s->wakeup, &s->mutex, CACHE_WAIT_TIME); *retry_time += mp_time_sec() - start; return 0; }
/** * \return 1 on success, 0 if the function was interrupted and -1 on error */ int stream_enable_cache(stream_t *stream,int size,int min,int seek_limit){ int ss = stream->sector_size ? stream->sector_size : STREAM_BUFFER_SIZE; int res = -1; cache_vars_t* s; if (stream->flags & STREAM_NON_CACHEABLE) { mp_msg(MSGT_CACHE,MSGL_STATUS,"\rThis stream is non-cacheable\n"); return 1; } s=cache_init(size,ss); if(s == NULL) return -1; stream->cache_data=s; s->stream=stream; // callback s->seek_limit=seek_limit; //make sure that we won't wait from cache_fill //more data than it is allowed to fill if (s->seek_limit > s->buffer_size - s->fill_limit ){ s->seek_limit = s->buffer_size - s->fill_limit; } if (min > s->buffer_size - s->fill_limit) { min = s->buffer_size - s->fill_limit; } // to make sure we wait for the cache process/thread to be active // before continuing if (min <= 0) min = 1; #if FORKED_CACHE if((stream->cache_pid=fork())){ if ((pid_t)stream->cache_pid == -1) stream->cache_pid = 0; #else { stream_t* stream2=malloc(sizeof(stream_t)); memcpy(stream2,s->stream,sizeof(stream_t)); s->stream=stream2; #if defined(__MINGW32__) stream->cache_pid = _beginthread( ThreadProc, 0, s ); #elif defined(__OS2__) stream->cache_pid = _beginthread( ThreadProc, NULL, 256 * 1024, s ); #else { pthread_t tid; pthread_create(&tid, NULL, ThreadProc, s); stream->cache_pid = 1; } #endif #endif if (!stream->cache_pid) { mp_msg(MSGT_CACHE, MSGL_ERR, "Starting cache process/thread failed: %s.\n", strerror(errno)); goto err_out; } // wait until cache is filled at least prefill_init % /* mp_msg(MSGT_CACHE,MSGL_V,"CACHE_PRE_INIT: %"PRId64" [%"PRId64"] %"PRId64" pre:%d eof:%d \n", (int64_t)s->min_filepos,(int64_t)s->read_filepos,(int64_t)s->max_filepos,min,s->eof);*/ while(s->read_filepos<s->min_filepos || s->max_filepos-s->read_filepos<min){ /* mp_msg(MSGT_CACHE,MSGL_STATUS,MSGTR_CacheFill, 100.0*(float)(s->max_filepos-s->read_filepos)/(float)(s->buffer_size), (int64_t)s->max_filepos-s->read_filepos );*/ if(s->eof) break; // file is smaller than prefill size if(stream_check_interrupt(PREFILL_SLEEP_TIME)) { res = 0; goto err_out; } } //mp_msg(MSGT_CACHE,MSGL_STATUS,"\n"); return 1; // parent exits err_out: cache_uninit(stream); return res; } #if FORKED_CACHE signal(SIGTERM,exit_sighandler); // kill cache_mainloop(s); // make sure forked code never leaves this function exit(0); #endif } #if !FORKED_CACHE #if defined(__MINGW32__) || defined(__OS2__) static void ThreadProc( void *s ){ cache_mainloop(s); _endthread(); }
/** * \return 1 on success, 0 if the function was interrupted and -1 on error */ int stream_enable_cache(stream_t *stream,int size,int min,int seek_limit){ int ss = stream->sector_size ? stream->sector_size : STREAM_BUFFER_SIZE; int res = -1; cache_vars_t* s; if (stream->flags & STREAM_NON_CACHEABLE) { mp_msg(MSGT_CACHE,MSGL_STATUS,"\rThis stream is non-cacheable\n"); return 1; } s=cache_init(size,ss); if(s == NULL) return -1; stream->cache_data=s; s->stream=stream; // callback s->seek_limit=seek_limit; //make sure that we won't wait from cache_fill //more data than it is alowed to fill if (s->seek_limit > s->buffer_size - s->fill_limit ){ s->seek_limit = s->buffer_size - s->fill_limit; } if (min > s->buffer_size - s->fill_limit) { min = s->buffer_size - s->fill_limit; } #if !defined(__MINGW32__) && !defined(PTHREAD_CACHE) && !defined(__OS2__) if((stream->cache_pid=fork())){ if ((pid_t)stream->cache_pid == -1) stream->cache_pid = 0; #else { stream_t* stream2=malloc(sizeof(stream_t)); memcpy(stream2,s->stream,sizeof(stream_t)); s->stream=stream2; #if defined(__MINGW32__) stream->cache_pid = _beginthread( ThreadProc, 0, s ); #elif defined(__OS2__) stream->cache_pid = _beginthread( ThreadProc, NULL, 256 * 1024, s ); #else { pthread_t tid; pthread_create(&tid, NULL, ThreadProc, s); stream->cache_pid = 1; } #endif #endif if (!stream->cache_pid) { mp_msg(MSGT_CACHE, MSGL_ERR, "Starting cache process/thread failed: %s.\n", strerror(errno)); goto err_out; } // wait until cache is filled at least prefill_init % mp_msg(MSGT_CACHE,MSGL_V,"CACHE_PRE_INIT: %"PRId64" [%"PRId64"] %"PRId64" pre:%d eof:%d \n", (int64_t)s->min_filepos,(int64_t)s->read_filepos,(int64_t)s->max_filepos,min,s->eof); while(s->read_filepos<s->min_filepos || s->max_filepos-s->read_filepos<min){ mp_msg(MSGT_CACHE,MSGL_STATUS,MSGTR_CacheFill, 100.0*(float)(s->max_filepos-s->read_filepos)/(float)(s->buffer_size), (int64_t)s->max_filepos-s->read_filepos ); if(s->eof) break; // file is smaller than prefill size if(stream_check_interrupt(PREFILL_SLEEP_TIME)) { res = 0; goto err_out; } } mp_msg(MSGT_CACHE,MSGL_STATUS,"\n"); return 1; // parent exits err_out: cache_uninit(stream); return res; } #if defined(__MINGW32__) || defined(PTHREAD_CACHE) || defined(__OS2__) } #ifdef PTHREAD_CACHE static void *ThreadProc( void *s ){ #else static void ThreadProc( void *s ){ #endif #endif #ifdef CONFIG_GUI use_gui = 0; // mp_msg may not use gui stuff in forked code #endif // cache thread mainloop: signal(SIGTERM,exit_sighandler); // kill do { if(!cache_fill(s)){ usec_sleep(FILL_USLEEP_TIME); // idle } // cache_stats(s->cache_data); } while (cache_execute_control(s)); #if defined(__MINGW32__) || defined(__OS2__) _endthread(); #elif defined(PTHREAD_CACHE) return NULL; #else // make sure forked code never leaves this function exit(0); #endif } int cache_stream_fill_buffer(stream_t *s){ int len; if(s->eof){ s->buf_pos=s->buf_len=0; return 0; } if(!s->cache_pid) return stream_fill_buffer(s); // cache_stats(s->cache_data); if(s->pos!=((cache_vars_t*)s->cache_data)->read_filepos) mp_msg(MSGT_CACHE,MSGL_ERR,"!!! read_filepos differs!!! report this bug...\n"); len=cache_read(s->cache_data,s->buffer, ((cache_vars_t*)s->cache_data)->sector_size); //printf("cache_stream_fill_buffer->read -> %d\n",len); if(len<=0){ s->eof=1; s->buf_pos=s->buf_len=0; return 0; } s->buf_pos=0; s->buf_len=len; s->pos+=len; // printf("[%d]",len);fflush(stdout); return len; }
// return 1 on success, 0 if the function was interrupted and -1 on error, or // if the cache is disabled int stream_cache_init(stream_t *cache, stream_t *stream, int64_t size, int64_t min, int64_t seek_limit) { if (size < 1) return -1; mp_tmsg(MSGT_NETWORK, MSGL_INFO, "Cache size set to %" PRId64 " KiB\n", size / 1024); if (size > SIZE_MAX) { mp_msg(MSGT_CACHE, MSGL_FATAL, "Cache size larger than max. allocation size\n"); return -1; } struct priv *s = talloc_zero(NULL, struct priv); //64kb min_size s->fill_limit = FFMAX(16 * 1024, BYTE_META_CHUNK_SIZE * 2); s->buffer_size = FFMAX(size, s->fill_limit * 4); s->back_size = s->buffer_size / 2; s->buffer = malloc(s->buffer_size); s->bm = malloc((s->buffer_size / BYTE_META_CHUNK_SIZE + 2) * sizeof(struct byte_meta)); if (!s->buffer || !s->bm) { mp_msg(MSGT_CACHE, MSGL_ERR, "Failed to allocate cache buffer.\n"); free(s->buffer); free(s->bm); talloc_free(s); return -1; } pthread_mutex_init(&s->mutex, NULL); pthread_cond_init(&s->wakeup, NULL); cache->priv = s; s->cache = cache; s->stream = stream; cache->seek = cache_seek; cache->fill_buffer = cache_fill_buffer; cache->control = cache_control; cache->close = cache_uninit; s->seek_limit = seek_limit; //make sure that we won't wait from cache_fill //more data than it is allowed to fill if (s->seek_limit > s->buffer_size - s->fill_limit) s->seek_limit = s->buffer_size - s->fill_limit; if (min > s->buffer_size - s->fill_limit) min = s->buffer_size - s->fill_limit; if (pthread_create(&s->cache_thread, NULL, cache_thread, s) != 0) { mp_msg(MSGT_CACHE, MSGL_ERR, "Starting cache process/thread failed: %s.\n", strerror(errno)); return -1; } s->cache_thread_running = true; // wait until cache is filled at least prefill_init % for (;;) { if (stream_check_interrupt(0)) return 0; int64_t fill; int idle; if (stream_control(s->cache, STREAM_CTRL_GET_CACHE_FILL, &fill) < 0) break; if (stream_control(s->cache, STREAM_CTRL_GET_CACHE_IDLE, &idle) < 0) break; mp_tmsg(MSGT_CACHE, MSGL_STATUS, "\rCache fill: %5.2f%% " "(%" PRId64 " bytes) ", 100.0 * fill / s->buffer_size, fill); if (fill >= min) break; if (idle) break; // file is smaller than prefill size // Wake up if the cache is done reading some data (or on timeout/abort) pthread_mutex_lock(&s->mutex); s->control = CACHE_CTRL_PING; pthread_cond_signal(&s->wakeup); cache_wakeup_and_wait(s, &(double){0}); pthread_mutex_unlock(&s->mutex); }
static int connect2Server_with_af(char *host, int port, int af,int verb) { int socket_server_fd; int err; socklen_t err_len; int ret,count = 0; fd_set set; struct timeval tv; union { struct sockaddr_in four; #ifdef HAVE_AF_INET6 struct sockaddr_in6 six; #endif } server_address; size_t server_address_size; void *our_s_addr; // Pointer to sin_addr or sin6_addr struct hostent *hp=NULL; char buf[255]; #if HAVE_WINSOCK2_H unsigned long val; int to; #else struct timeval to; #endif #if HAVE_WINSOCK2_H && defined(HAVE_AF_INET6) // our winsock name resolution code can not handle IPv6 if (af == AF_INET6) { mp_msg(MSGT_NETWORK, MSGL_WARN, "IPv6 not supported for winsock2\n"); return TCP_ERROR_FATAL; } #endif socket_server_fd = socket(af, SOCK_STREAM, 0); if( socket_server_fd==-1 ) { // mp_msg(MSGT_NETWORK,MSGL_ERR,"Failed to create %s socket:\n", af2String(af)); return TCP_ERROR_FATAL; } #if defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO) #if HAVE_WINSOCK2_H /* timeout in milliseconds */ to = 10 * 1000; #else to.tv_sec = 10; to.tv_usec = 0; #endif setsockopt(socket_server_fd, SOL_SOCKET, SO_RCVTIMEO, &to, sizeof(to)); setsockopt(socket_server_fd, SOL_SOCKET, SO_SNDTIMEO, &to, sizeof(to)); #endif switch (af) { case AF_INET: our_s_addr = &server_address.four.sin_addr; break; #ifdef HAVE_AF_INET6 case AF_INET6: our_s_addr = &server_address.six.sin6_addr; break; #endif default: mp_msg(MSGT_NETWORK,MSGL_ERR, MSGTR_MPDEMUX_NW_UnknownAF, af); return TCP_ERROR_FATAL; } memset(&server_address, 0, sizeof(server_address)); #if HAVE_INET_PTON if (inet_pton(af, host, our_s_addr)!=1) #elif HAVE_INET_ATON if (inet_aton(host, our_s_addr)!=1) #elif HAVE_WINSOCK2_H if ( inet_addr(host)==INADDR_NONE ) #endif { if(verb) mp_msg(MSGT_NETWORK,MSGL_STATUS,MSGTR_MPDEMUX_NW_ResolvingHostForAF, host, af2String(af)); #ifdef HAVE_GETHOSTBYNAME2 hp=gethostbyname2( host, af ); #else hp=gethostbyname( host ); #endif if( hp==NULL ) { if(verb) mp_msg(MSGT_NETWORK,MSGL_ERR,MSGTR_MPDEMUX_NW_CantResolv, af2String(af), host); return TCP_ERROR_FATAL; } if (af != hp->h_addrtype) return TCP_ERROR_FATAL; memcpy( our_s_addr, hp->h_addr_list[0], hp->h_length ); } #if HAVE_WINSOCK2_H else { unsigned long addr = inet_addr(host); memcpy( our_s_addr, &addr, sizeof(addr) ); } #endif switch (af) { case AF_INET: server_address.four.sin_family=af; server_address.four.sin_port=htons(port); server_address_size = sizeof(server_address.four); break; #ifdef HAVE_AF_INET6 case AF_INET6: server_address.six.sin6_family=af; server_address.six.sin6_port=htons(port); server_address_size = sizeof(server_address.six); break; #endif default: mp_msg(MSGT_NETWORK,MSGL_ERR, MSGTR_MPDEMUX_NW_UnknownAF, af); return TCP_ERROR_FATAL; } #if HAVE_INET_PTON inet_ntop(af, our_s_addr, buf, 255); #elif HAVE_INET_ATON || defined(HAVE_WINSOCK2_H) av_strlcpy( buf, inet_ntoa( *((struct in_addr*)our_s_addr) ), 255); #endif if(verb) mp_msg(MSGT_NETWORK,MSGL_STATUS,MSGTR_MPDEMUX_NW_ConnectingToServer, host, buf , port ); // Turn the socket as non blocking so we can timeout on the connection #if !HAVE_WINSOCK2_H fcntl( socket_server_fd, F_SETFL, fcntl(socket_server_fd, F_GETFL) | O_NONBLOCK ); #else val = 1; ioctlsocket( socket_server_fd, FIONBIO, &val ); #endif if( connect( socket_server_fd, (struct sockaddr*)&server_address, server_address_size )==-1 ) { #if !HAVE_WINSOCK2_H if( errno!=EINPROGRESS ) { #else if( (WSAGetLastError() != WSAEINPROGRESS) && (WSAGetLastError() != WSAEWOULDBLOCK) ) { #endif if(verb) mp_msg(MSGT_NETWORK,MSGL_ERR,MSGTR_MPDEMUX_NW_CantConnect2Server, af2String(af)); closesocket(socket_server_fd); return TCP_ERROR_PORT; } } tv.tv_sec = 0; tv.tv_usec = 500000; FD_ZERO( &set ); FD_SET( socket_server_fd, &set ); // When the connection will be made, we will have a writeable fd while((ret = select(socket_server_fd+1, NULL, &set, NULL, &tv)) == 0) { if(count > 30 || stream_check_interrupt(500)) { if(count > 30) mp_msg(MSGT_NETWORK,MSGL_ERR,MSGTR_MPDEMUX_NW_ConnTimeout); else mp_msg(MSGT_NETWORK,MSGL_V,"Connection interrupted by user\n"); return TCP_ERROR_TIMEOUT; } count++; FD_ZERO( &set ); FD_SET( socket_server_fd, &set ); tv.tv_sec = 0; tv.tv_usec = 500000; } if (ret < 0) mp_msg(MSGT_NETWORK,MSGL_ERR,MSGTR_MPDEMUX_NW_SelectFailed); // Turn back the socket as blocking #if !HAVE_WINSOCK2_H fcntl( socket_server_fd, F_SETFL, fcntl(socket_server_fd, F_GETFL) & ~O_NONBLOCK ); #else val = 0; ioctlsocket( socket_server_fd, FIONBIO, &val ); #endif // Check if there were any errors err_len = sizeof(int); ret = getsockopt(socket_server_fd,SOL_SOCKET,SO_ERROR,&err,&err_len); if(ret < 0) { mp_msg(MSGT_NETWORK,MSGL_ERR,MSGTR_MPDEMUX_NW_GetSockOptFailed,strerror(errno)); return TCP_ERROR_FATAL; } if(err > 0) { mp_msg(MSGT_NETWORK,MSGL_ERR,MSGTR_MPDEMUX_NW_ConnectError,strerror(err)); return TCP_ERROR_PORT; } return socket_server_fd; } // Connect to a server using a TCP connection // return -2 for fatal error, like unable to resolve name, connection timeout... // return -1 is unable to connect to a particular port int connect2Server(char *host, int port, int verb) { #ifdef HAVE_AF_INET6 int r; int s = TCP_ERROR_FATAL; r = connect2Server_with_af(host, port, network_prefer_ipv4 ? AF_INET:AF_INET6,verb); if (r >= 0) return r; s = connect2Server_with_af(host, port, network_prefer_ipv4 ? AF_INET6:AF_INET,verb); if (s == TCP_ERROR_FATAL) return r; return s; #else return connect2Server_with_af(host, port, AF_INET,verb); #endif }
int cache_do_control(stream_t *stream, int cmd, void *arg) { int sleep_count = 0; int pos_change = 0; cache_vars_t* s = stream->cache_data; switch (cmd) { case STREAM_CTRL_SEEK_TO_TIME: s->control_double_arg = *(double *)arg; s->control = cmd; pos_change = 1; break; case STREAM_CTRL_SEEK_TO_CHAPTER: case STREAM_CTRL_SET_ANGLE: s->control_uint_arg = *(unsigned *)arg; s->control = cmd; pos_change = 1; break; // the core might call these every frame, so cache them... case STREAM_CTRL_GET_TIME_LENGTH: *(double *)arg = s->stream_time_length; return s->stream_time_length ? STREAM_OK : STREAM_UNSUPPORTED; case STREAM_CTRL_GET_CURRENT_TIME: *(double *)arg = s->stream_time_pos; return s->stream_time_pos != MP_NOPTS_VALUE ? STREAM_OK : STREAM_UNSUPPORTED; case STREAM_CTRL_GET_NUM_CHAPTERS: case STREAM_CTRL_GET_CURRENT_CHAPTER: case STREAM_CTRL_GET_ASPECT_RATIO: case STREAM_CTRL_GET_NUM_ANGLES: case STREAM_CTRL_GET_ANGLE: case -2: s->control = cmd; break; default: return STREAM_UNSUPPORTED; } cache_wakeup(stream); while (s->control != -1) { if (sleep_count++ == 1000) mp_msg(MSGT_CACHE, MSGL_WARN, "Cache not responding! [performance issue]\n"); if (stream_check_interrupt(CONTROL_SLEEP_TIME)) { s->eof = 1; return STREAM_UNSUPPORTED; } } // to avoid unnecessary differences with non-cache behaviour, // do this also on failure. if (pos_change) { stream->pos = s->read_filepos; stream->eof = s->eof; } if (s->control_res != STREAM_OK) return s->control_res; switch (cmd) { case STREAM_CTRL_GET_TIME_LENGTH: case STREAM_CTRL_GET_CURRENT_TIME: case STREAM_CTRL_GET_ASPECT_RATIO: *(double *)arg = s->control_double_arg; break; case STREAM_CTRL_GET_NUM_CHAPTERS: case STREAM_CTRL_GET_CURRENT_CHAPTER: case STREAM_CTRL_GET_NUM_ANGLES: case STREAM_CTRL_GET_ANGLE: *(unsigned *)arg = s->control_uint_arg; break; } return s->control_res; }
int cache_do_control(stream_t *stream, int cmd, void *arg) { int sleep_count = 0; int pos_change = 0; cache_vars_t* s = stream->cache_data; switch (cmd) { case STREAM_CTRL_SEEK_TO_TIME: s->control_double_arg = *(double *)arg; s->control = cmd; pos_change = 1; break; case STREAM_CTRL_SEEK_TO_CHAPTER: case STREAM_CTRL_SET_ANGLE: s->control_uint_arg = *(unsigned *)arg; s->control = cmd; pos_change = 1; break; // the core might call these every frame, so cache them... case STREAM_CTRL_GET_TIME_LENGTH: *(double *)arg = s->stream_time_length; return s->stream_time_length ? STREAM_OK : STREAM_UNSUPPORTED; case STREAM_CTRL_GET_CURRENT_TIME: *(double *)arg = s->stream_time_pos; return s->stream_time_pos != MP_NOPTS_VALUE ? STREAM_OK : STREAM_UNSUPPORTED; case STREAM_CTRL_GET_LANG: s->control_lang_arg = *(struct stream_lang_req *)arg; case STREAM_CTRL_GET_NUM_CHAPTERS: case STREAM_CTRL_GET_CURRENT_CHAPTER: case STREAM_CTRL_GET_ASPECT_RATIO: case STREAM_CTRL_GET_NUM_ANGLES: case STREAM_CTRL_GET_ANGLE: case STREAM_CTRL_GET_SIZE: case -2: s->control = cmd; break; default: return STREAM_UNSUPPORTED; } cache_wakeup(stream); while (s->control != -1) { if (sleep_count++ == 1000) mp_msg(MSGT_CACHE, MSGL_WARN, "Cache not responding! [performance issue]\n"); if (stream_check_interrupt(CONTROL_SLEEP_TIME)) { s->eof = 1; return STREAM_UNSUPPORTED; } } if (s->control_res != STREAM_OK) return s->control_res; // We cannot do this on failure, since this would cause the // stream position to jump when e.g. STREAM_CTRL_SEEK_TO_TIME // is unsupported - but in that case we need the old value // to do the fallback seek. // This unfortunately can lead to slightly different behaviour // with and without cache if the protocol changes pos even // when an error happened. if (pos_change) { stream->pos = s->read_filepos; stream->eof = s->eof; } switch (cmd) { case STREAM_CTRL_GET_TIME_LENGTH: case STREAM_CTRL_GET_CURRENT_TIME: case STREAM_CTRL_GET_ASPECT_RATIO: *(double *)arg = s->control_double_arg; break; case STREAM_CTRL_GET_NUM_CHAPTERS: case STREAM_CTRL_GET_CURRENT_CHAPTER: case STREAM_CTRL_GET_NUM_ANGLES: case STREAM_CTRL_GET_ANGLE: *(unsigned *)arg = s->control_uint_arg; break; case STREAM_CTRL_GET_SIZE: *(off_t *)arg = s->control_uint_arg; break; case STREAM_CTRL_GET_LANG: *(struct stream_lang_req *)arg = s->control_lang_arg; break; } return s->control_res; }
// return 1 on success, 0 if the function was interrupted and -1 on error, or // if the cache is disabled int stream_cache_init(stream_t *cache, stream_t *stream, struct mp_cache_opts *opts) { if (opts->size < 1) return -1; struct priv *s = talloc_zero(NULL, struct priv); s->log = cache->log; s->seek_limit = opts->seek_min * 1024ULL; if (resize_cache(s, opts->size * 1024ULL) != STREAM_OK) { MP_ERR(s, "Failed to allocate cache buffer.\n"); talloc_free(s); return -1; } MP_VERBOSE(cache, "Cache size set to %" PRId64 " KiB\n", s->buffer_size / 1024); pthread_mutex_init(&s->mutex, NULL); pthread_cond_init(&s->wakeup, NULL); cache->priv = s; s->cache = cache; s->stream = stream; cache->seek = cache_seek; cache->fill_buffer = cache_fill_buffer; cache->control = cache_control; cache->close = cache_uninit; int64_t min = opts->initial * 1024ULL; if (min > s->buffer_size - FILL_LIMIT) min = s->buffer_size - FILL_LIMIT; s->seekable = stream->seekable; if (pthread_create(&s->cache_thread, NULL, cache_thread, s) != 0) { MP_ERR(s, "Starting cache process/thread failed: %s.\n", strerror(errno)); return -1; } s->cache_thread_running = true; // wait until cache is filled with at least min bytes if (min < 1) return 1; for (;;) { if (stream_check_interrupt(cache)) return 0; int64_t fill; int idle; if (stream_control(s->cache, STREAM_CTRL_GET_CACHE_FILL, &fill) < 0) break; if (stream_control(s->cache, STREAM_CTRL_GET_CACHE_IDLE, &idle) < 0) break; MP_INFO(s, "\rCache fill: %5.2f%% " "(%" PRId64 " bytes) ", 100.0 * fill / s->buffer_size, fill); if (fill >= min) break; if (idle) break; // file is smaller than prefill size // Wake up if the cache is done reading some data (or on timeout/abort) pthread_mutex_lock(&s->mutex); s->control = CACHE_CTRL_PING; pthread_cond_signal(&s->wakeup); cache_wakeup_and_wait(s, &(double){0}); pthread_mutex_unlock(&s->mutex); }