2012-08-06 15:52:17 +00:00
|
|
|
/*
|
2015-04-13 07:36:54 +00:00
|
|
|
* This file is part of mpv.
|
2012-08-06 15:52:17 +00:00
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* mpv is free software; you can redistribute it and/or modify
|
2012-08-06 15:52:17 +00:00
|
|
|
* 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.
|
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* mpv is distributed in the hope that it will be useful,
|
2012-08-06 15:52:17 +00:00
|
|
|
* 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
|
2015-04-13 07:36:54 +00:00
|
|
|
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
2012-08-06 15:52:17 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <math.h>
|
|
|
|
#include <stdbool.h>
|
2012-08-06 15:52:47 +00:00
|
|
|
#include <sys/stat.h>
|
2012-08-06 15:52:17 +00:00
|
|
|
|
|
|
|
#include <libswscale/swscale.h>
|
|
|
|
|
|
|
|
#include "config.h"
|
2014-08-29 10:09:04 +00:00
|
|
|
#include "misc/bstr.h"
|
2012-08-06 15:52:47 +00:00
|
|
|
#include "osdep/io.h"
|
2016-09-05 19:04:55 +00:00
|
|
|
#include "options/m_config.h"
|
2013-12-17 01:02:25 +00:00
|
|
|
#include "options/path.h"
|
2016-01-11 18:03:40 +00:00
|
|
|
#include "mpv_talloc.h"
|
Do not call strerror()
...because everything is terrible.
strerror() is not documented as having to be thread-safe by POSIX and
C11. (Which is pretty much bullshit, because both mandate threads and
some form of thread-local storage - so there's no excuse why
implementation couldn't implement this in a thread-safe way. Especially
with C11 this is ridiculous, because there is no way to use threads and
convert error numbers to strings at the same time!)
Since we heavily use threads now, we should avoid unsafe functions like
strerror().
strerror_r() is in POSIX, but GNU/glibc deliberately fucks it up and
gives the function different semantics than the POSIX one. It's a bit of
work to convince this piece of shit to expose the POSIX standard
function, and not the messed up GNU one.
strerror_l() is also in POSIX, but only since the 2008 standard, and
thus is not widespread.
The solution is using avlibc (libavutil, by its official name), which
handles the unportable details for us, mostly. We avoid some pain.
2014-11-26 20:21:56 +00:00
|
|
|
#include "common/common.h"
|
2013-12-17 01:39:45 +00:00
|
|
|
#include "common/msg.h"
|
2012-11-09 00:06:43 +00:00
|
|
|
#include "video/out/vo.h"
|
|
|
|
#include "video/csputils.h"
|
|
|
|
#include "video/mp_image.h"
|
|
|
|
#include "video/fmt-conversion.h"
|
|
|
|
#include "video/image_writer.h"
|
2012-12-22 18:11:53 +00:00
|
|
|
#include "video/sws_utils.h"
|
2013-11-24 11:58:06 +00:00
|
|
|
#include "sub/osd.h"
|
2013-12-17 01:02:25 +00:00
|
|
|
#include "options/m_option.h"
|
2012-08-06 15:52:17 +00:00
|
|
|
|
2016-09-05 19:04:55 +00:00
|
|
|
static const struct m_sub_options image_writer_conf = {
|
|
|
|
.opts = image_writer_opts,
|
|
|
|
.size = sizeof(struct image_writer_opts),
|
|
|
|
.defaults = &image_writer_opts_defaults,
|
|
|
|
};
|
|
|
|
|
|
|
|
struct vo_image_opts {
|
2012-08-06 15:52:17 +00:00
|
|
|
struct image_writer_opts *opts;
|
2012-08-06 15:52:47 +00:00
|
|
|
char *outdir;
|
2016-09-05 19:04:55 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
#define OPT_BASE_STRUCT struct vo_image_opts
|
|
|
|
|
|
|
|
static const struct m_sub_options vo_image_conf = {
|
|
|
|
.opts = (const struct m_option[]) {
|
|
|
|
OPT_SUBSTRUCT("vo-image", opts, image_writer_conf, 0),
|
|
|
|
OPT_STRING("vo-image-outdir", outdir, 0),
|
|
|
|
{0},
|
|
|
|
},
|
|
|
|
.size = sizeof(struct vo_image_opts),
|
|
|
|
};
|
|
|
|
|
|
|
|
struct priv {
|
|
|
|
struct vo_image_opts *opts;
|
2012-08-06 15:52:17 +00:00
|
|
|
|
2012-12-22 18:11:53 +00:00
|
|
|
struct mp_image *current;
|
2012-08-06 15:52:17 +00:00
|
|
|
int frame;
|
|
|
|
};
|
|
|
|
|
2013-08-23 21:30:09 +00:00
|
|
|
static bool checked_mkdir(struct vo *vo, const char *buf)
|
2012-08-06 15:52:47 +00:00
|
|
|
{
|
2013-08-23 21:30:09 +00:00
|
|
|
MP_INFO(vo, "Creating output directory '%s'...\n", buf);
|
2012-08-06 15:52:47 +00:00
|
|
|
if (mkdir(buf, 0755) < 0) {
|
Do not call strerror()
...because everything is terrible.
strerror() is not documented as having to be thread-safe by POSIX and
C11. (Which is pretty much bullshit, because both mandate threads and
some form of thread-local storage - so there's no excuse why
implementation couldn't implement this in a thread-safe way. Especially
with C11 this is ridiculous, because there is no way to use threads and
convert error numbers to strings at the same time!)
Since we heavily use threads now, we should avoid unsafe functions like
strerror().
strerror_r() is in POSIX, but GNU/glibc deliberately fucks it up and
gives the function different semantics than the POSIX one. It's a bit of
work to convince this piece of shit to expose the POSIX standard
function, and not the messed up GNU one.
strerror_l() is also in POSIX, but only since the 2008 standard, and
thus is not widespread.
The solution is using avlibc (libavutil, by its official name), which
handles the unportable details for us, mostly. We avoid some pain.
2014-11-26 20:21:56 +00:00
|
|
|
char *errstr = mp_strerror(errno);
|
2012-08-06 15:52:47 +00:00
|
|
|
if (errno == EEXIST) {
|
|
|
|
struct stat stat_p;
|
2014-10-17 19:46:08 +00:00
|
|
|
if (stat(buf, &stat_p ) == 0 && S_ISDIR(stat_p.st_mode))
|
2012-08-06 15:52:47 +00:00
|
|
|
return true;
|
|
|
|
}
|
2013-08-23 21:30:09 +00:00
|
|
|
MP_ERR(vo, "Error creating output directory: %s\n", errstr);
|
2012-08-06 15:52:47 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-10-03 16:20:16 +00:00
|
|
|
static int reconfig(struct vo *vo, struct mp_image_params *params)
|
2012-08-06 15:52:17 +00:00
|
|
|
{
|
|
|
|
struct priv *p = vo->priv;
|
2012-12-22 18:11:53 +00:00
|
|
|
mp_image_unrefp(&p->current);
|
|
|
|
|
2012-08-06 15:52:17 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-12-22 18:11:53 +00:00
|
|
|
static void draw_image(struct vo *vo, mp_image_t *mpi)
|
2012-08-06 15:52:17 +00:00
|
|
|
{
|
2012-12-22 18:11:53 +00:00
|
|
|
struct priv *p = vo->priv;
|
|
|
|
|
2014-06-17 21:05:50 +00:00
|
|
|
p->current = mpi;
|
2012-12-22 18:11:53 +00:00
|
|
|
|
2014-01-21 22:43:54 +00:00
|
|
|
struct mp_osd_res dim = osd_res_from_image_params(vo->params);
|
2014-06-15 18:46:57 +00:00
|
|
|
osd_draw_on_image(vo->osd, dim, mpi->pts, OSD_DRAW_SUB_ONLY, p->current);
|
2012-08-06 15:52:17 +00:00
|
|
|
}
|
|
|
|
|
2012-12-22 18:11:53 +00:00
|
|
|
static void flip_page(struct vo *vo)
|
2012-08-06 15:52:17 +00:00
|
|
|
{
|
|
|
|
struct priv *p = vo->priv;
|
video: introduce failure path for image allocations
Until now, failure to allocate image data resulted in a crash (i.e.
abort() was called). This was intentional, because it's pretty silly to
degrade playback, and in almost all situations, the OOM will probably
kill you anyway. (And then there's the standard Linux overcommit
behavior, which also will kill you at some point.)
But I changed my opinion, so here we go. This change does not affect
_all_ memory allocations, just image data. Now in most failure cases,
the output will just be skipped. For video filters, this coincidentally
means that failure is treated as EOF (because the playback core assumes
EOF if nothing comes out of the video filter chain). In other
situations, output might be in some way degraded, like skipping frames,
not scaling OSD, and such.
Functions whose return values changed semantics:
mp_image_alloc
mp_image_new_copy
mp_image_new_ref
mp_image_make_writeable
mp_image_setrefp
mp_image_to_av_frame_and_unref
mp_image_from_av_frame
mp_image_new_external_ref
mp_image_new_custom_ref
mp_image_pool_make_writeable
mp_image_pool_get
mp_image_pool_new_copy
mp_vdpau_mixed_frame_create
vf_alloc_out_image
vf_make_out_image_writeable
glGetWindowScreenshot
2014-06-17 20:43:43 +00:00
|
|
|
if (!p->current)
|
|
|
|
return;
|
2012-08-06 15:52:17 +00:00
|
|
|
|
2013-05-18 11:19:22 +00:00
|
|
|
(p->frame)++;
|
|
|
|
|
2012-08-06 15:52:47 +00:00
|
|
|
void *t = talloc_new(NULL);
|
|
|
|
char *filename = talloc_asprintf(t, "%08d.%s", p->frame,
|
2016-09-05 19:04:55 +00:00
|
|
|
image_writer_file_ext(p->opts->opts));
|
2012-08-06 15:52:47 +00:00
|
|
|
|
2016-09-05 19:04:55 +00:00
|
|
|
if (p->opts->outdir && strlen(p->opts->outdir))
|
|
|
|
filename = mp_path_join(t, p->opts->outdir, filename);
|
2012-08-06 15:52:47 +00:00
|
|
|
|
2013-08-23 21:30:09 +00:00
|
|
|
MP_INFO(vo, "Saving %s\n", filename);
|
2016-09-05 19:04:55 +00:00
|
|
|
write_image(p->current, p->opts->opts, filename, vo->log);
|
2012-08-06 15:52:17 +00:00
|
|
|
|
2012-08-06 15:52:47 +00:00
|
|
|
talloc_free(t);
|
2012-12-22 18:11:53 +00:00
|
|
|
mp_image_unrefp(&p->current);
|
2012-08-06 15:52:17 +00:00
|
|
|
}
|
|
|
|
|
2015-01-21 21:08:24 +00:00
|
|
|
static int query_format(struct vo *vo, int fmt)
|
2012-08-06 15:52:17 +00:00
|
|
|
{
|
2012-12-22 18:11:53 +00:00
|
|
|
if (mp_sws_supported_format(fmt))
|
2015-01-21 21:08:24 +00:00
|
|
|
return 1;
|
2012-08-06 15:52:17 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void uninit(struct vo *vo)
|
|
|
|
{
|
2012-12-22 18:11:53 +00:00
|
|
|
struct priv *p = vo->priv;
|
|
|
|
|
|
|
|
mp_image_unrefp(&p->current);
|
2012-08-06 15:52:17 +00:00
|
|
|
}
|
|
|
|
|
2013-07-22 20:52:42 +00:00
|
|
|
static int preinit(struct vo *vo)
|
2012-08-06 15:52:17 +00:00
|
|
|
{
|
2014-05-05 22:21:15 +00:00
|
|
|
struct priv *p = vo->priv;
|
2016-09-05 19:04:55 +00:00
|
|
|
p->opts = mp_get_config_group(vo, vo->global, &vo_image_conf);
|
|
|
|
if (p->opts->outdir && !checked_mkdir(vo, p->opts->outdir))
|
2014-05-05 22:21:15 +00:00
|
|
|
return -1;
|
2012-08-06 15:52:17 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int control(struct vo *vo, uint32_t request, void *data)
|
|
|
|
{
|
|
|
|
return VO_NOTIMPL;
|
|
|
|
}
|
|
|
|
|
|
|
|
const struct vo_driver video_out_image =
|
|
|
|
{
|
2013-10-23 17:06:14 +00:00
|
|
|
.description = "Write video frames to image files",
|
|
|
|
.name = "image",
|
video: move display and timing to a separate thread
The VO is run inside its own thread. It also does most of video timing.
The playloop hands the image data and a realtime timestamp to the VO,
and the VO does the rest.
In particular, this allows the playloop to do other things, instead of
blocking for video redraw. But if anything accesses the VO during video
timing, it will block.
This also fixes vo_sdl.c event handling; but that is only a side-effect,
since reimplementing the broken way would require more effort.
Also drop --softsleep. In theory, this option helps if the kernel's
sleeping mechanism is too inaccurate for video timing. In practice, I
haven't ever encountered a situation where it helps, and it just burns
CPU cycles. On the other hand it's probably actively harmful, because
it prevents the libavcodec decoder threads from doing real work.
Side note:
Originally, I intended that multiple frames can be queued to the VO. But
this is not done, due to problems with OSD and other certain features.
OSD in particular is simply designed in a way that it can be neither
timed nor copied, so you do have to render it into the video frame
before you can draw the next frame. (Subtitles have no such restriction.
sd_lavc was even updated to fix this.) It seems the right solution to
queuing multiple VO frames is rendering on VO-backed framebuffers, like
vo_vdpau.c does. This requires VO driver support, and is out of scope
of this commit.
As consequence, the VO has a queue size of 1. The existing video queue
is just needed to compute frame duration, and will be moved out in the
next commit.
2014-08-12 21:02:08 +00:00
|
|
|
.untimed = true,
|
2012-08-06 15:52:17 +00:00
|
|
|
.priv_size = sizeof(struct priv),
|
|
|
|
.options = (const struct m_option[]) {
|
2016-09-05 19:04:55 +00:00
|
|
|
OPT_SUBOPT_LEGACY("jpeg-quality", "vo-image-jpeg-quality"),
|
|
|
|
OPT_SUBOPT_LEGACY("jpeg-smooth", "vo-image-jpeg-smooth"),
|
|
|
|
OPT_SUBOPT_LEGACY("jpeg-source-chroma", "vo-image-jpeg-source-chroma"),
|
|
|
|
OPT_SUBOPT_LEGACY("png-compression", "vo-image-png-compression"),
|
|
|
|
OPT_SUBOPT_LEGACY("png-filter", "vo-image-png-filter"),
|
|
|
|
OPT_SUBOPT_LEGACY("format", "vo-image-format"),
|
|
|
|
OPT_SUBOPT_LEGACY("high-bit-depth", "vo-image-high-bit-depth"),
|
|
|
|
OPT_SUBOPT_LEGACY("tag-colorspace", "vo-image-tag-colorspace"),
|
|
|
|
OPT_SUBOPT_LEGACY("outdir", "vo-image-outdir"),
|
2012-08-06 15:52:17 +00:00
|
|
|
{0},
|
|
|
|
},
|
|
|
|
.preinit = preinit,
|
2012-11-04 15:24:18 +00:00
|
|
|
.query_format = query_format,
|
2013-08-24 15:01:42 +00:00
|
|
|
.reconfig = reconfig,
|
2012-08-06 15:52:17 +00:00
|
|
|
.control = control,
|
2012-11-04 14:56:04 +00:00
|
|
|
.draw_image = draw_image,
|
2012-08-06 15:52:17 +00:00
|
|
|
.flip_page = flip_page,
|
|
|
|
.uninit = uninit,
|
2016-09-05 19:04:55 +00:00
|
|
|
.global_opts = &vo_image_conf,
|
2012-08-06 15:52:17 +00:00
|
|
|
};
|