Index: menu.c =================================================================== --- menu.c (revision 2516) +++ menu.c (working copy) @@ -90,9 +90,14 @@ getyx (menu->win, y, x); if (title_width <= title_space || mi->align == MENU_ALIGN_LEFT) xwaddnstr (menu->win, mi->title, title_space); - else - xwaddstr (menu->win, mi->title + title_width - title_space); + else { + char *ptr; + ptr = xstrtail (mi->title, title_space); + xwaddstr (menu->win, ptr); + free (ptr); + } + /* Fill the remainder of the title field with spaces. */ if (mi == menu->selected) { getyx (menu->win, y, ix); Index: utf8.c =================================================================== --- utf8.c (revision 2516) +++ utf8.c (working copy) @@ -199,47 +199,50 @@ int xwaddnstr (WINDOW *win, const char *str, const int n) { - int res; + int res, width, inv_char; + wchar_t *ucs; + char *mstr, *lstr; + size_t size, num_chars; assert (n > 0); assert (str != NULL); - if (using_utf8) { + mstr = iconv_str (iconv_desc, str); - /* This nasty hack is because we need to count n in chars, but - * [w]addnstr() takes arguments in bytes (in UTF-8 a char can be - * longer than 1 byte). There are also problems with [w]addnwstr() - * (screen garbled). I have no better idea. */ + size = xmbstowcs (NULL, mstr, -1, NULL) + 1; + ucs = (wchar_t *)xmalloc (sizeof(wchar_t) * size); + xmbstowcs (ucs, mstr, size, &inv_char); + width = wcswidth (ucs, WIDTH_MAX); - wchar_t *ucs; - size_t size; - size_t utf_num_chars; - int inv_char; - - size = xmbstowcs (NULL, str, -1, NULL) + 1; - ucs = (wchar_t *)xmalloc (sizeof(wchar_t) * size); - xmbstowcs (ucs, str, size, &inv_char); - if ((size_t)n < size - 1) - ucs[n] = L'\0'; - utf_num_chars = wcstombs (NULL, ucs, 0); - if (inv_char) { - char *utf8 = (char *)xmalloc (utf_num_chars + 1); - - wcstombs (utf8, ucs, utf_num_chars + 1); - res = waddstr (win, utf8); - free (utf8); + if (width == -1) { + size_t clidx; + for (clidx = 0; clidx < size - 1; clidx++) { + if (wcwidth (ucs[clidx]) == -1) + ucs[clidx] = L'?'; } - else - res = waddnstr (win, str, utf_num_chars); - free (ucs); + width = wcswidth (ucs, WIDTH_MAX); + inv_char = 1; } - else { - char *lstr = iconv_str (iconv_desc, str); - res = waddnstr (win, lstr, n); - free (lstr); + if (width > n) { + while (width > n) + width -= wcwidth (ucs[--size]); + ucs[size] = L'\0'; } + num_chars = wcstombs (NULL, ucs, 0); + lstr = (char *)xmalloc (num_chars + 1); + + if (inv_char) + wcstombs (lstr, ucs, num_chars + 1); + else + snprintf (lstr, num_chars + 1, "%s", mstr); + + res = waddstr (win, lstr); + + free (ucs); + free (lstr); + free (mstr); return res; } Index: decoder_plugins/mp3/mp3.c =================================================================== --- decoder_plugins/mp3/mp3.c (revision 2516) +++ decoder_plugins/mp3/mp3.c (working copy) @@ -696,17 +696,19 @@ { char *ext; + strcpy (buf, "MPx"); + ext = ext_pos (file); - if (!strcasecmp (ext, "mp3")) - strcpy (buf, "MP3"); - else if (!strcasecmp (ext, "mp2")) - strcpy (buf, "MP2"); - else if (!strcasecmp (ext, "mp1")) - strcpy (buf, "MP1"); - else if (!strcasecmp (ext, "mpga")) - strcpy (buf, "MPG"); - else - strcpy (buf, "MPx"); + if (ext) { + if (!strcasecmp (ext, "mp3")) + strcpy (buf, "MP3"); + else if (!strcasecmp (ext, "mp2")) + strcpy (buf, "MP2"); + else if (!strcasecmp (ext, "mp1")) + strcpy (buf, "MP1"); + else if (!strcasecmp (ext, "mpga")) + strcpy (buf, "MPG"); + } } static int mp3_our_format_ext (const char *ext) Index: decoder_plugins/ffmpeg/ffmpeg.m4 =================================================================== --- decoder_plugins/ffmpeg/ffmpeg.m4 (revision 2516) +++ decoder_plugins/ffmpeg/ffmpeg.m4 (working copy) @@ -88,6 +88,25 @@ [#include ]) AC_CHECK_DECLS([AV_CODEC_ID_OPUS], , , [#include ]) + AC_SEARCH_LIBS(avcodec_free_frame, avcodec, + [AC_DEFINE([HAVE_AVCODEC_FREE_FRAME], 1, + [Define to 1 if you have the `avcodec_free_frame' function.])]) + AC_CHECK_DECLS([CODEC_ID_PCM_S8_PLANAR], , , + [#include ]) + AC_CHECK_DECLS([AV_SAMPLE_FMT_U8P], , , + [#include ]) + AC_CHECK_DECLS([AV_SAMPLE_FMT_S16P], , , + [#include ]) + AC_CHECK_DECLS([AV_SAMPLE_FMT_S32P], , , + [#include ]) + AC_CHECK_DECLS([AV_SAMPLE_FMT_FLTP], , , + [#include ]) + AC_SEARCH_LIBS(av_get_sample_fmt_name, avutil, + [AC_DEFINE([HAVE_AV_GET_SAMPLE_FMT_NAME], 1, + [Define to 1 if you have the `av_get_sample_fmt_name' function.])]) + AC_SEARCH_LIBS(av_lockmgr_register, avcodec, + [AC_DEFINE([HAVE_LOCKMGR_REGISTER], 1, + [Define to 1 if you have the `av_lockmgr_register' function.])]) CPPFLAGS="$save_CPPFLAGS" CFLAGS="$save_CFLAGS" LIBS="$save_LIBS" Index: decoder_plugins/ffmpeg/ffmpeg.c =================================================================== --- decoder_plugins/ffmpeg/ffmpeg.c (revision 2516) +++ decoder_plugins/ffmpeg/ffmpeg.c (working copy) @@ -99,6 +99,7 @@ bool okay; /* was this stream successfully opened? */ struct decoder_error error; long fmt; + int sample_width; int bitrate; /* in bits per second */ int avg_bitrate; /* in bits per second */ #if SEEK_IN_DECODER @@ -106,6 +107,7 @@ int seek_sec; /* second to which to seek */ #endif bool seek_broken; /* FFmpeg seeking is broken */ + bool timing_broken; /* FFmpeg trashes duration and bit_rate */ #if SEEK_IN_DECODER && defined(DEBUG) pthread_t thread_id; #endif @@ -277,8 +279,72 @@ } } +/* Handle FFmpeg's locking requirements. */ +#ifdef HAVE_LOCKMGR_REGISTER +static int locking_cb (void **mutex, enum AVLockOp op) +{ + int result; + + switch (op) { + case AV_LOCK_CREATE: + *mutex = xmalloc (sizeof (pthread_mutex_t)); + result = pthread_mutex_init (*mutex, NULL); + break; + case AV_LOCK_OBTAIN: + result = pthread_mutex_lock (*mutex); + break; + case AV_LOCK_RELEASE: + result = pthread_mutex_unlock (*mutex); + break; + case AV_LOCK_DESTROY: + result = pthread_mutex_destroy (*mutex); + free (*mutex); + *mutex = NULL; + break; + } + + return result; +} +#endif + +/* Here we attempt to determine if FFmpeg/LibAV has trashed the 'duration' + * and 'bit_rate' fields in AVFormatContext for large files. Determining + * whether or not they are likely to be valid is imprecise and will vary + * depending (at least) on: + * + * - The file's size, + * - The file's codec, + * - The number and size of tags, + * - The version of FFmpeg/LibAV, and + * - Whether it's FFmpeg or LibAV. + * + * This function represents a best guess. +*/ +static bool is_timing_broken (AVFormatContext *ic) +{ + int64_t file_size; + + if (ic->duration < 0 || ic->bit_rate < 0) + return true; + +#ifdef HAVE_AVIO_SIZE + file_size = avio_size (ic->pb); +#else + file_size = ic->file_size; +#endif + + if (file_size < UINT32_MAX) + return false; + + return true; +} + static void ffmpeg_init () { +#ifdef HAVE_LOCKMGR_REGISTER + int rc; +#endif + #ifdef DEBUG av_log_set_level (AV_LOG_INFO); #else @@ -291,10 +357,20 @@ supported_extns = lists_strs_new (16); load_audio_extns (supported_extns); load_video_extns (supported_extns); + +#ifdef HAVE_LOCKMGR_REGISTER + rc = av_lockmgr_register (locking_cb); + if (rc < 0) + fatal ("Lock manager initialisation failed"); +#endif } static void ffmpeg_destroy () { +#ifdef HAVE_LOCKMGR_REGISTER + av_lockmgr_register (NULL); +#endif + av_log_set_level (AV_LOG_QUIET); ffmpeg_log_repeats (NULL); @@ -341,9 +417,9 @@ } #endif - if (tags_sel & TAGS_TIME) { + if (!is_timing_broken (ic) && tags_sel & TAGS_TIME) { info->time = -1; - if (ic->duration >= 0) + if (ic->duration != (int64_t)AV_NOPTS_VALUE && ic->duration >= 0) info->time = ic->duration / AV_TIME_BASE; } @@ -426,12 +502,6 @@ #endif - if (tags_sel & TAGS_TIME) { - info->time = -1; - if (ic->duration != (int64_t)AV_NOPTS_VALUE && ic->duration >= 0) - info->time = ic->duration / AV_TIME_BASE; - } - end: #ifdef HAVE_AVFORMAT_CLOSE_INPUT avformat_close_input (&ic); @@ -450,12 +520,16 @@ if (!strcmp (data->ic->iformat->name, "wav")) { switch (data->enc->codec_id) { case CODEC_ID_PCM_S8: +#if HAVE_DECL_CODEC_ID_PCM_S8_PLANAR + case CODEC_ID_PCM_S8_PLANAR: +#endif result = SFMT_S8; break; case CODEC_ID_PCM_U8: result = SFMT_U8; break; case CODEC_ID_PCM_S16LE: + case CODEC_ID_PCM_S16LE_PLANAR: case CODEC_ID_PCM_S16BE: result = SFMT_S16; break; @@ -490,15 +564,27 @@ switch (data->enc->sample_fmt) { case AV_SAMPLE_FMT_U8: +#if HAVE_DECL_AV_SAMPLE_FMT_U8P + case AV_SAMPLE_FMT_U8P: +#endif result = SFMT_U8; break; case AV_SAMPLE_FMT_S16: +#if HAVE_DECL_AV_SAMPLE_FMT_S16P + case AV_SAMPLE_FMT_S16P: +#endif result = SFMT_S16; break; case AV_SAMPLE_FMT_S32: +#if HAVE_DECL_AV_SAMPLE_FMT_S32P + case AV_SAMPLE_FMT_S32P: +#endif result = SFMT_S32; break; case AV_SAMPLE_FMT_FLT: +#if HAVE_DECL_AV_SAMPLE_FMT_FLTP + case AV_SAMPLE_FMT_FLTP: +#endif result = SFMT_FLOAT; break; default: @@ -597,6 +683,7 @@ data->stream = NULL; data->enc = NULL; data->codec = NULL; + data->sample_width = 0; data->bitrate = 0; data->avg_bitrate = 0; @@ -624,6 +711,7 @@ data->seek_sec = 0; #endif data->seek_broken = false; + data->timing_broken = false; decoder_error_init (&data->error); @@ -702,27 +790,38 @@ if (data->fmt == 0) data->fmt = fmt_from_sample_fmt (data); if (data->fmt == 0) { +#ifdef HAVE_AV_GET_SAMPLE_FMT_NAME decoder_error (&data->error, ERROR_FATAL, 0, + "Cannot get sample size from unknown sample format: %s", + av_get_sample_fmt_name (data->enc->sample_fmt)); +#else + decoder_error (&data->error, ERROR_FATAL, 0, "Unsupported sample size!"); +#endif + avcodec_close (data->enc); goto end; } + data->sample_width = sfmt_Bps (data->fmt); if (data->codec->capabilities & CODEC_CAP_DELAY) data->delay = true; data->seek_broken = is_seek_broken (data); + data->timing_broken = is_timing_broken (data->ic); data->okay = true; - if (data->ic->duration >= AV_TIME_BASE) { + if (!data->timing_broken && data->ic->duration >= AV_TIME_BASE) { #ifdef HAVE_AVIO_SIZE data->avg_bitrate = (int) (avio_size (data->ic->pb) / - (data->ic->duration / AV_TIME_BASE) * 8); + (data->ic->duration / AV_TIME_BASE) * 8); #else data->avg_bitrate = (int) (data->ic->file_size / - (data->ic->duration / AV_TIME_BASE) * 8); + (data->ic->duration / AV_TIME_BASE) * 8); #endif } - data->bitrate = data->ic->bit_rate; + if (!data->timing_broken && data->ic->bit_rate > 0) + data->bitrate = data->ic->bit_rate; + return data; end: @@ -943,12 +1042,14 @@ char *buf, int buf_len) { int filled = 0; + AVFrame *frame; + frame = avcodec_alloc_frame (); + do { int len, got_frame, is_planar, plane_size, data_size, copied; - AVFrame frame; - len = avcodec_decode_audio4 (data->enc, &frame, &got_frame, pkt); + len = avcodec_decode_audio4 (data->enc, frame, &got_frame, pkt); if (len < 0) { /* skip frame */ @@ -968,33 +1069,46 @@ is_planar = av_sample_fmt_is_planar (data->enc->sample_fmt); data_size = av_samples_get_buffer_size (&plane_size, - data->enc->channels, frame.nb_samples, + data->enc->channels, + frame->nb_samples, data->enc->sample_fmt, 1); if (data_size == 0) continue; - copied = copy_or_buffer (data, (char *)frame.extended_data[0], - plane_size, buf, buf_len); - buf += copied; - filled += copied; - buf_len -= copied; + if (is_planar && data->enc->channels > 1) { + int offset, ch; - if (is_planar && data->enc->channels > 1) { - int ch; + for (offset = 0; offset < plane_size; offset += data->sample_width) { + for (ch = 0; ch < data->enc->channels; ch += 1) { + copied = copy_or_buffer (data, + (char *)frame->extended_data[ch] + + offset, + data->sample_width, buf, buf_len); + buf += copied; + filled += copied; + buf_len -= copied; + } + } + } + else { + copied = copy_or_buffer (data, (char *)frame->extended_data[0], + plane_size, buf, buf_len); + buf += copied; + filled += copied; + buf_len -= copied; + } - for (ch = 1; ch < data->enc->channels; ch += 1) { - copied = copy_or_buffer (data, (char *)frame.extended_data[ch], - plane_size, buf, buf_len); - buf += copied; - filled += copied; - buf_len -= copied; - } - } - debug ("Copying %dB (%dB filled)", data_size, filled); } while (pkt->size > 0); + avcodec_get_frame_defaults (frame); +#ifdef HAVE_AVCODEC_FREE_FRAME + avcodec_free_frame (&frame); +#else + av_freep (&frame); +#endif + return filled; } #endif @@ -1122,9 +1236,10 @@ free_packet (pkt); } while (!bytes_produced && !data->eos); - data->bitrate = compute_bitrate (sound_params, bytes_used, - bytes_produced + data->remain_buf_len, - data->bitrate); + if (!data->timing_broken) + data->bitrate = compute_bitrate (sound_params, bytes_used, + bytes_produced + data->remain_buf_len, + data->bitrate); return bytes_produced; } @@ -1185,20 +1300,23 @@ { struct ffmpeg_data *data = (struct ffmpeg_data *)prv_data; - return data->bitrate / 1000; + return data->timing_broken ? -1 : data->bitrate / 1000; } static int ffmpeg_get_avg_bitrate (void *prv_data) { struct ffmpeg_data *data = (struct ffmpeg_data *)prv_data; - return data->avg_bitrate / 1000; + return data->timing_broken ? -1 : data->avg_bitrate / 1000; } static int ffmpeg_get_duration (void *prv_data) { struct ffmpeg_data *data = (struct ffmpeg_data *)prv_data; + if (data->timing_broken) + return -1; + if (!data->stream) return -1; Index: configure.in =================================================================== --- configure.in (revision 2518) +++ configure.in (working copy) @@ -293,7 +293,6 @@ dnl optional functions AC_CHECK_FUNCS([strcasestr strerror_r syslog]) -AC_CHECK_FUNCS([getrlimit pthread_attr_getstacksize]) AX_CHECK_UNAME_SYSCALL dnl MIME magic @@ -322,6 +321,7 @@ CC="$PTHREAD_CC" CFLAGS="$PTHREAD_CFLAGS $CFLAGS" EXTRA_LIBS="$EXTRA_LIBS $PTHREAD_LIBS" +AC_CHECK_FUNCS([getrlimit pthread_attr_getstacksize]) dnl __FUNCTION__ AC_TRY_COMPILE(,[printf(__FUNCTION__);], [AC_DEFINE([HAVE__FUNCTION__], 1,