2009-10-31 21:03:48 +00:00
|
|
|
/*
|
|
|
|
* av_log to mp_msg converter
|
|
|
|
* Copyright (C) 2006 Michael Niedermayer
|
|
|
|
* Copyright (C) 2009 Uoti Urpala
|
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* This file is part of mpv.
|
2009-10-31 21:03:48 +00:00
|
|
|
*
|
2017-09-21 11:50:18 +00:00
|
|
|
* mpv is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
2009-10-31 21:03:48 +00:00
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* mpv is distributed in the hope that it will be useful,
|
2009-10-31 21:03:48 +00:00
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2017-09-21 11:50:18 +00:00
|
|
|
* GNU Lesser General Public License for more details.
|
2009-10-31 21:03:48 +00:00
|
|
|
*
|
2017-09-21 11:50:18 +00:00
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
2009-10-31 21:03:48 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
2013-12-21 19:06:36 +00:00
|
|
|
#include <stdio.h>
|
2009-10-31 21:03:48 +00:00
|
|
|
#include <stdbool.h>
|
|
|
|
|
2011-03-07 20:50:45 +00:00
|
|
|
#include "av_log.h"
|
2013-12-21 19:06:36 +00:00
|
|
|
#include "common/common.h"
|
|
|
|
#include "common/global.h"
|
2013-12-17 01:39:45 +00:00
|
|
|
#include "common/msg.h"
|
2023-10-21 02:55:41 +00:00
|
|
|
#include "config.h"
|
2023-11-24 04:02:30 +00:00
|
|
|
#include "misc/bstr.h"
|
2023-10-21 02:55:41 +00:00
|
|
|
#include "osdep/threads.h"
|
2013-12-21 19:06:36 +00:00
|
|
|
|
2011-10-01 17:03:49 +00:00
|
|
|
#include <libavutil/avutil.h>
|
2009-10-31 21:03:48 +00:00
|
|
|
#include <libavutil/log.h>
|
2022-03-17 12:34:35 +00:00
|
|
|
#include <libavutil/version.h>
|
2009-10-31 21:03:48 +00:00
|
|
|
|
|
|
|
#include <libavcodec/avcodec.h>
|
2022-03-17 12:34:35 +00:00
|
|
|
#include <libavcodec/version.h>
|
2009-10-31 21:03:48 +00:00
|
|
|
#include <libavformat/avformat.h>
|
2022-03-17 12:34:35 +00:00
|
|
|
#include <libavformat/version.h>
|
Remove remains of Libav compatibility
Libav seems rather dead: no release for 2 years, no new git commits in
master for almost a year (with one exception ~6 months ago). From what I
can tell, some developers resigned themselves to the horrifying idea to
post patches to ffmpeg-devel instead, while the rest of the developers
went on to greener pastures.
Libav was a better project than FFmpeg. Unfortunately, FFmpeg won,
because it managed to keep the name and website. Libav was pushed more
and more into obscurity: while there was initially a big push for Libav,
FFmpeg just remained "in place" and visible for most people. FFmpeg was
slowly draining all manpower and energy from Libav. A big part of this
was that FFmpeg stole code from Libav (regular merges of the entire
Libav git tree), making it some sort of Frankenstein mirror of Libav,
think decaying zombie with additional legs ("features") nailed to it.
"Stealing" surely is the wrong word; I'm just aping the language that
some of the FFmpeg members used to use. All that is in the past now, I'm
probably the only person left who is annoyed by this, and with this
commit I'm putting this decade long problem finally to an end. I just
thought I'd express my annoyance about this fucking shitshow one last
time.
The most intrusive change in this commit is the resample filter, which
originally used libavresample. Since the FFmpeg developer refused to
enable libavresample by default for drama reasons, and the API was
slightly different, so the filter used some big preprocessor mess to
make it compatible to libswresample. All that falls away now. The
simplification to the build system is also significant.
2020-02-16 14:14:55 +00:00
|
|
|
#include <libswresample/swresample.h>
|
2022-03-17 12:34:35 +00:00
|
|
|
#include <libswresample/version.h>
|
2011-10-01 17:03:49 +00:00
|
|
|
#include <libswscale/swscale.h>
|
2022-03-17 12:34:35 +00:00
|
|
|
#include <libswscale/version.h>
|
2016-02-05 22:17:33 +00:00
|
|
|
#include <libavfilter/avfilter.h>
|
2022-03-17 12:34:35 +00:00
|
|
|
#include <libavfilter/version.h>
|
2009-10-31 21:03:48 +00:00
|
|
|
|
2013-07-16 11:28:28 +00:00
|
|
|
#if HAVE_LIBAVDEVICE
|
demux_lavf: add support for libavdevice
libavdevice supports various "special" video and audio inputs, such
as screen-capture or libavfilter filter graphs.
libavdevice inputs are implemented as demuxers. They don't use the
custom stream callbacks (in AVFormatContext.pb). Instead, input
parameters are passed as filename. This means the mpv stream layer has
to be disabled. Do this by adding the pseudo stream handler avdevice://,
whose only purpose is passing the filename to demux_lavf, without
actually doing anything.
Change the logic how the filename is passed to libavformat. Remove
handling of the filename from demux_open_lavf() and move it to
lavf_check_file(). (This also fixes a possible bug when skipping the
"lavf://" prefix.)
libavdevice now can be invoked by specifying demuxer and args as in:
mpv avdevice://demuxer:args
The args are passed as filename to libavformat. When using libavdevice
demuxers, their actual meaning is highly implementation specific. They
don't refer to actual filenames.
Note:
libavdevice is disabled by default. There is one problem: libavdevice
pulls in libavfilter, which in turn causes symbol clashes with mpv
internals. The problem is that libavfilter includes a mplayer filter
bridge, which is used to interface with a set of nearly unmodified
mplayer filters copied into libavfilter. This filter bridge uses the
same symbol names as mplayer/mpv's filter chain, which results in symbol
clashes at link-time.
This can be prevented by building ffmpeg with --disable-filter=mp, but
unfortunately this is not the default.
This means linking to libavdevice (which in turn forces linking with
libavfilter by default) must be disabled. We try doing this by compiling
a test file that defines one of the clashing symbols (vf_mpi_clear).
To enable libavdevice input, ffmpeg should be built with the options:
--disable-filter=mp
and mpv with:
--enable-libavdevice
Originally, I tried to auto-detect it. But the resulting complications
in configure did't seem worth the trouble.
2012-11-30 17:41:04 +00:00
|
|
|
#include <libavdevice/avdevice.h>
|
|
|
|
#endif
|
|
|
|
|
2013-12-21 19:06:36 +00:00
|
|
|
// Needed because the av_log callback does not provide a library-safe message
|
|
|
|
// callback.
|
2023-10-21 02:55:41 +00:00
|
|
|
static mp_static_mutex log_lock = MP_STATIC_MUTEX_INITIALIZER;
|
2013-12-21 19:06:36 +00:00
|
|
|
static struct mpv_global *log_mpv_instance;
|
|
|
|
static struct mp_log *log_root, *log_decaudio, *log_decvideo, *log_demuxer;
|
|
|
|
static bool log_print_prefix = true;
|
2023-11-24 04:02:30 +00:00
|
|
|
static bstr log_buffer;
|
2013-12-21 19:06:36 +00:00
|
|
|
|
2009-10-31 21:03:48 +00:00
|
|
|
static int av_log_level_to_mp_level(int av_level)
|
|
|
|
{
|
2011-06-26 02:10:12 +00:00
|
|
|
if (av_level > AV_LOG_VERBOSE)
|
2017-09-28 09:53:57 +00:00
|
|
|
return MSGL_TRACE;
|
2009-10-31 21:03:48 +00:00
|
|
|
if (av_level > AV_LOG_INFO)
|
2017-09-28 09:53:57 +00:00
|
|
|
return MSGL_DEBUG;
|
2009-10-31 21:03:48 +00:00
|
|
|
if (av_level > AV_LOG_WARNING)
|
2013-09-14 22:43:43 +00:00
|
|
|
return MSGL_V;
|
2009-10-31 21:03:48 +00:00
|
|
|
if (av_level > AV_LOG_ERROR)
|
|
|
|
return MSGL_WARN;
|
|
|
|
if (av_level > AV_LOG_FATAL)
|
|
|
|
return MSGL_ERR;
|
|
|
|
return MSGL_FATAL;
|
|
|
|
}
|
|
|
|
|
2013-12-21 19:06:36 +00:00
|
|
|
static struct mp_log *get_av_log(void *ptr)
|
2009-10-31 21:03:48 +00:00
|
|
|
{
|
|
|
|
if (!ptr)
|
2013-12-21 19:06:36 +00:00
|
|
|
return log_root;
|
2009-10-31 21:03:48 +00:00
|
|
|
|
|
|
|
AVClass *avc = *(AVClass **)ptr;
|
2011-08-14 23:58:51 +00:00
|
|
|
if (!avc) {
|
2013-12-21 19:06:36 +00:00
|
|
|
mp_warn(log_root,
|
2011-08-14 23:58:51 +00:00
|
|
|
"av_log callback called with bad parameters (NULL AVClass).\n"
|
2024-01-19 18:45:30 +00:00
|
|
|
"This is a bug in one of FFmpeg libraries used.\n");
|
2013-12-21 19:06:36 +00:00
|
|
|
return log_root;
|
2011-08-14 23:58:51 +00:00
|
|
|
}
|
2009-10-31 21:03:48 +00:00
|
|
|
|
|
|
|
if (!strcmp(avc->class_name, "AVCodecContext")) {
|
|
|
|
AVCodecContext *s = ptr;
|
|
|
|
if (s->codec) {
|
2011-04-19 23:59:45 +00:00
|
|
|
if (s->codec->type == AVMEDIA_TYPE_AUDIO) {
|
2018-01-03 21:26:37 +00:00
|
|
|
if (av_codec_is_decoder(s->codec))
|
2013-12-21 19:06:36 +00:00
|
|
|
return log_decaudio;
|
2011-04-19 23:59:45 +00:00
|
|
|
} else if (s->codec->type == AVMEDIA_TYPE_VIDEO) {
|
2018-01-03 21:26:37 +00:00
|
|
|
if (av_codec_is_decoder(s->codec))
|
2013-12-21 19:06:36 +00:00
|
|
|
return log_decvideo;
|
2009-10-31 21:03:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strcmp(avc->class_name, "AVFormatContext")) {
|
|
|
|
AVFormatContext *s = ptr;
|
|
|
|
if (s->iformat)
|
2013-12-21 19:06:36 +00:00
|
|
|
return log_demuxer;
|
2009-10-31 21:03:48 +00:00
|
|
|
}
|
|
|
|
|
2013-12-21 19:06:36 +00:00
|
|
|
return log_root;
|
2009-10-31 21:03:48 +00:00
|
|
|
}
|
|
|
|
|
2023-12-23 21:42:26 +00:00
|
|
|
static const char *avclass_item_name(void *obj, const AVClass *avc)
|
|
|
|
{
|
|
|
|
return (avc->item_name ? avc->item_name : av_default_item_name)(obj);
|
|
|
|
}
|
|
|
|
|
2009-10-31 21:03:48 +00:00
|
|
|
static void mp_msg_av_log_callback(void *ptr, int level, const char *fmt,
|
|
|
|
va_list vl)
|
|
|
|
{
|
|
|
|
AVClass *avc = ptr ? *(AVClass **)ptr : NULL;
|
|
|
|
int mp_level = av_log_level_to_mp_level(level);
|
|
|
|
|
2013-12-21 19:06:36 +00:00
|
|
|
// Note: mp_log is thread-safe, but destruction of the log instances is not.
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&log_lock);
|
2013-12-21 19:06:36 +00:00
|
|
|
|
|
|
|
if (!log_mpv_instance) {
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&log_lock);
|
2013-12-21 19:06:36 +00:00
|
|
|
// Fallback to stderr
|
|
|
|
vfprintf(stderr, fmt, vl);
|
2009-10-31 21:03:48 +00:00
|
|
|
return;
|
2013-12-21 19:06:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct mp_log *log = get_av_log(ptr);
|
|
|
|
|
2013-12-21 20:49:13 +00:00
|
|
|
if (mp_msg_test(log, mp_level)) {
|
2023-11-24 04:02:30 +00:00
|
|
|
log_buffer.len = 0;
|
|
|
|
bstr_xappend_vasprintf(log_root, &log_buffer, fmt, vl);
|
|
|
|
if (!log_buffer.len)
|
|
|
|
goto done;
|
2023-12-23 21:42:26 +00:00
|
|
|
const char *prefix = avc ? avclass_item_name(ptr, avc) : NULL;
|
2023-11-24 04:02:30 +00:00
|
|
|
if (log_print_prefix && prefix) {
|
|
|
|
mp_msg(log, mp_level, "%s: %.*s", prefix, BSTR_P(log_buffer));
|
|
|
|
} else {
|
|
|
|
mp_msg(log, mp_level, "%.*s", BSTR_P(log_buffer));
|
|
|
|
}
|
|
|
|
log_print_prefix = log_buffer.start[log_buffer.len - 1] == '\n';
|
2013-07-22 12:48:51 +00:00
|
|
|
}
|
2009-10-31 21:03:48 +00:00
|
|
|
|
2023-11-24 04:02:30 +00:00
|
|
|
done:
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&log_lock);
|
2009-10-31 21:03:48 +00:00
|
|
|
}
|
|
|
|
|
2013-12-21 19:06:36 +00:00
|
|
|
void init_libav(struct mpv_global *global)
|
2009-10-31 21:03:48 +00:00
|
|
|
{
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&log_lock);
|
2013-12-21 19:06:36 +00:00
|
|
|
if (!log_mpv_instance) {
|
|
|
|
log_mpv_instance = global;
|
Remove remains of Libav compatibility
Libav seems rather dead: no release for 2 years, no new git commits in
master for almost a year (with one exception ~6 months ago). From what I
can tell, some developers resigned themselves to the horrifying idea to
post patches to ffmpeg-devel instead, while the rest of the developers
went on to greener pastures.
Libav was a better project than FFmpeg. Unfortunately, FFmpeg won,
because it managed to keep the name and website. Libav was pushed more
and more into obscurity: while there was initially a big push for Libav,
FFmpeg just remained "in place" and visible for most people. FFmpeg was
slowly draining all manpower and energy from Libav. A big part of this
was that FFmpeg stole code from Libav (regular merges of the entire
Libav git tree), making it some sort of Frankenstein mirror of Libav,
think decaying zombie with additional legs ("features") nailed to it.
"Stealing" surely is the wrong word; I'm just aping the language that
some of the FFmpeg members used to use. All that is in the past now, I'm
probably the only person left who is annoyed by this, and with this
commit I'm putting this decade long problem finally to an end. I just
thought I'd express my annoyance about this fucking shitshow one last
time.
The most intrusive change in this commit is the resample filter, which
originally used libavresample. Since the FFmpeg developer refused to
enable libavresample by default for drama reasons, and the API was
slightly different, so the filter used some big preprocessor mess to
make it compatible to libswresample. All that falls away now. The
simplification to the build system is also significant.
2020-02-16 14:14:55 +00:00
|
|
|
log_root = mp_log_new(NULL, global->log, "ffmpeg");
|
2013-12-21 19:06:36 +00:00
|
|
|
log_decaudio = mp_log_new(log_root, log_root, "audio");
|
|
|
|
log_decvideo = mp_log_new(log_root, log_root, "video");
|
|
|
|
log_demuxer = mp_log_new(log_root, log_root, "demuxer");
|
2023-11-24 04:02:30 +00:00
|
|
|
log_buffer = (bstr){0};
|
2013-12-21 19:06:36 +00:00
|
|
|
av_log_set_callback(mp_msg_av_log_callback);
|
|
|
|
}
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&log_lock);
|
2013-12-21 19:06:36 +00:00
|
|
|
|
2012-04-17 22:27:55 +00:00
|
|
|
avformat_network_init();
|
2016-02-05 22:17:33 +00:00
|
|
|
|
2013-07-16 11:28:28 +00:00
|
|
|
#if HAVE_LIBAVDEVICE
|
demux_lavf: add support for libavdevice
libavdevice supports various "special" video and audio inputs, such
as screen-capture or libavfilter filter graphs.
libavdevice inputs are implemented as demuxers. They don't use the
custom stream callbacks (in AVFormatContext.pb). Instead, input
parameters are passed as filename. This means the mpv stream layer has
to be disabled. Do this by adding the pseudo stream handler avdevice://,
whose only purpose is passing the filename to demux_lavf, without
actually doing anything.
Change the logic how the filename is passed to libavformat. Remove
handling of the filename from demux_open_lavf() and move it to
lavf_check_file(). (This also fixes a possible bug when skipping the
"lavf://" prefix.)
libavdevice now can be invoked by specifying demuxer and args as in:
mpv avdevice://demuxer:args
The args are passed as filename to libavformat. When using libavdevice
demuxers, their actual meaning is highly implementation specific. They
don't refer to actual filenames.
Note:
libavdevice is disabled by default. There is one problem: libavdevice
pulls in libavfilter, which in turn causes symbol clashes with mpv
internals. The problem is that libavfilter includes a mplayer filter
bridge, which is used to interface with a set of nearly unmodified
mplayer filters copied into libavfilter. This filter bridge uses the
same symbol names as mplayer/mpv's filter chain, which results in symbol
clashes at link-time.
This can be prevented by building ffmpeg with --disable-filter=mp, but
unfortunately this is not the default.
This means linking to libavdevice (which in turn forces linking with
libavfilter by default) must be disabled. We try doing this by compiling
a test file that defines one of the clashing symbols (vf_mpi_clear).
To enable libavdevice input, ffmpeg should be built with the options:
--disable-filter=mp
and mpv with:
--enable-libavdevice
Originally, I tried to auto-detect it. But the resulting complications
in configure did't seem worth the trouble.
2012-11-30 17:41:04 +00:00
|
|
|
avdevice_register_all();
|
|
|
|
#endif
|
2009-10-31 21:03:48 +00:00
|
|
|
}
|
2011-10-01 17:03:49 +00:00
|
|
|
|
2013-12-21 19:06:36 +00:00
|
|
|
void uninit_libav(struct mpv_global *global)
|
|
|
|
{
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_lock(&log_lock);
|
2013-12-21 19:06:36 +00:00
|
|
|
if (log_mpv_instance == global) {
|
2015-06-17 12:52:31 +00:00
|
|
|
av_log_set_callback(av_log_default_callback);
|
2013-12-21 19:06:36 +00:00
|
|
|
log_mpv_instance = NULL;
|
|
|
|
talloc_free(log_root);
|
|
|
|
}
|
2023-10-21 02:55:41 +00:00
|
|
|
mp_mutex_unlock(&log_lock);
|
2013-12-21 19:06:36 +00:00
|
|
|
}
|
|
|
|
|
2019-10-11 19:21:51 +00:00
|
|
|
#define V(x) AV_VERSION_MAJOR(x), \
|
|
|
|
AV_VERSION_MINOR(x), \
|
|
|
|
AV_VERSION_MICRO(x)
|
2014-02-10 22:11:30 +00:00
|
|
|
|
|
|
|
struct lib {
|
|
|
|
const char *name;
|
|
|
|
unsigned buildv;
|
|
|
|
unsigned runv;
|
|
|
|
};
|
2011-10-01 17:03:49 +00:00
|
|
|
|
2020-03-08 17:54:01 +00:00
|
|
|
void check_library_versions(struct mp_log *log, int v)
|
2011-10-01 17:03:49 +00:00
|
|
|
{
|
2014-02-10 22:11:30 +00:00
|
|
|
const struct lib libs[] = {
|
|
|
|
{"libavcodec", LIBAVCODEC_VERSION_INT, avcodec_version()},
|
2024-06-22 11:25:07 +00:00
|
|
|
#if HAVE_LIBAVDEVICE
|
|
|
|
{"libavdevice", LIBAVDEVICE_VERSION_INT, avdevice_version()},
|
|
|
|
#endif
|
2014-02-10 22:11:30 +00:00
|
|
|
{"libavfilter", LIBAVFILTER_VERSION_INT, avfilter_version()},
|
2024-06-22 11:25:07 +00:00
|
|
|
{"libavformat", LIBAVFORMAT_VERSION_INT, avformat_version()},
|
|
|
|
{"libavutil", LIBAVUTIL_VERSION_INT, avutil_version()},
|
2014-02-10 22:11:30 +00:00
|
|
|
{"libswresample", LIBSWRESAMPLE_VERSION_INT, swresample_version()},
|
2024-06-22 11:25:07 +00:00
|
|
|
{"libswscale", LIBSWSCALE_VERSION_INT, swscale_version()},
|
2014-02-10 22:11:30 +00:00
|
|
|
};
|
|
|
|
|
2022-11-13 12:48:27 +00:00
|
|
|
mp_msg(log, v, "FFmpeg version: %s\n", av_version_info());
|
Remove remains of Libav compatibility
Libav seems rather dead: no release for 2 years, no new git commits in
master for almost a year (with one exception ~6 months ago). From what I
can tell, some developers resigned themselves to the horrifying idea to
post patches to ffmpeg-devel instead, while the rest of the developers
went on to greener pastures.
Libav was a better project than FFmpeg. Unfortunately, FFmpeg won,
because it managed to keep the name and website. Libav was pushed more
and more into obscurity: while there was initially a big push for Libav,
FFmpeg just remained "in place" and visible for most people. FFmpeg was
slowly draining all manpower and energy from Libav. A big part of this
was that FFmpeg stole code from Libav (regular merges of the entire
Libav git tree), making it some sort of Frankenstein mirror of Libav,
think decaying zombie with additional legs ("features") nailed to it.
"Stealing" surely is the wrong word; I'm just aping the language that
some of the FFmpeg members used to use. All that is in the past now, I'm
probably the only person left who is annoyed by this, and with this
commit I'm putting this decade long problem finally to an end. I just
thought I'd express my annoyance about this fucking shitshow one last
time.
The most intrusive change in this commit is the resample filter, which
originally used libavresample. Since the FFmpeg developer refused to
enable libavresample by default for drama reasons, and the API was
slightly different, so the filter used some big preprocessor mess to
make it compatible to libswresample. All that falls away now. The
simplification to the build system is also significant.
2020-02-16 14:14:55 +00:00
|
|
|
mp_msg(log, v, "FFmpeg library versions:\n");
|
2014-02-10 22:11:30 +00:00
|
|
|
|
|
|
|
for (int n = 0; n < MP_ARRAY_SIZE(libs); n++) {
|
|
|
|
const struct lib *l = &libs[n];
|
|
|
|
mp_msg(log, v, " %-15s %d.%d.%d", l->name, V(l->buildv));
|
2020-03-08 17:54:01 +00:00
|
|
|
if (l->buildv != l->runv)
|
2014-02-10 22:11:30 +00:00
|
|
|
mp_msg(log, v, " (runtime %d.%d.%d)", V(l->runv));
|
|
|
|
mp_msg(log, v, "\n");
|
2020-03-08 17:54:01 +00:00
|
|
|
if (l->buildv > l->runv ||
|
|
|
|
AV_VERSION_MAJOR(l->buildv) != AV_VERSION_MAJOR(l->runv))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: %d.%d.%d -> %d.%d.%d\n",
|
|
|
|
l->name, V(l->buildv), V(l->runv));
|
|
|
|
abort();
|
|
|
|
}
|
2014-02-10 22:11:30 +00:00
|
|
|
}
|
2011-10-01 17:03:49 +00:00
|
|
|
}
|
2014-02-10 22:11:30 +00:00
|
|
|
|
|
|
|
#undef V
|