core: add infrastructure to get screenshots from VOs

Add a VO command (VOCTRL_SCREENSHOT) which requests a screenshot
directly from the VO. If VO support is available, screenshots will be
taken instantly (no more 1 or 2 frames delay). Taking screenshots when
hardware decoding is in use will also work (vdpau). Additionally, the
screenshots will now use the same colorspace as the video display.
Change the central MPContext to be allocated with talloc so that it
can be used as a talloc parent context.

This commit does not yet implement the functionality for any VO (added
in subsequent commits).

The old screenshot video filter is not needed anymore if VO support is
present, and in that case will not be used even if it is present in
the filter chain. If VO support is not available then the filter is
used like before. Note that the filter still has some of the old
problems, such as delaying the screenshot by at least 1 frame.
This commit is contained in:
wm4 2011-10-06 20:46:01 +02:00 committed by Uoti Urpala
parent e3f5043233
commit 01cf896a2f
9 changed files with 347 additions and 144 deletions

View File

@ -504,6 +504,7 @@ SRCS_MPLAYER = command.c \
mp_fifo.c \
mplayer.c \
parser-mpcmd.c \
screenshot.c \
input/input.c \
libao2/ao_mpegpes.c \
libao2/ao_null.c \

View File

@ -66,6 +66,7 @@
#endif
#include "stream/stream_dvdnav.h"
#include "m_struct.h"
#include "screenshot.h"
#include "mp_core.h"
#include "mp_fifo.h"
@ -3380,15 +3381,7 @@ void run_command(MPContext *mpctx, mp_cmd_t *cmd)
break;
case MP_CMD_SCREENSHOT:
if (mpctx->video_out && mpctx->video_out->config_ok) {
mp_msg(MSGT_CPLAYER, MSGL_INFO, "sending VFCTRL_SCREENSHOT!\n");
if (CONTROL_OK !=
((vf_instance_t *) sh_video->vfilter)->
control(sh_video->vfilter, VFCTRL_SCREENSHOT,
&cmd->args[0].v.i))
mp_msg(MSGT_CPLAYER, MSGL_INFO,
"failed (forgot -vf screenshot?)\n");
}
screenshot_request(mpctx, cmd->args[0].v.i);
break;
case MP_CMD_VF_CHANGE_RECTANGLE:

View File

@ -95,6 +95,12 @@ typedef struct vf_seteq_s
int value;
} vf_equalizer_t;
struct vf_ctrl_screenshot {
// When the screenshot is complete, pass it to this callback.
void (*image_callback)(void *, mp_image_t *);
void *image_callback_ctx;
};
#define VFCTRL_QUERY_MAX_PP_LEVEL 4 /* test for postprocessing support (max level) */
#define VFCTRL_SET_PP_LEVEL 5 /* set postprocessing level */
#define VFCTRL_SET_EQUALIZER 6 /* set color options (brightness,contrast etc) */
@ -104,7 +110,7 @@ typedef struct vf_seteq_s
#define VFCTRL_DUPLICATE_FRAME 11 /* For encoding - encode zero-change frame */
#define VFCTRL_SKIP_NEXT_FRAME 12 /* For encoding - drop the next frame that passes thru */
#define VFCTRL_FLUSH_FRAMES 13 /* For encoding - flush delayed frames */
#define VFCTRL_SCREENSHOT 14 /* Make a screenshot */
#define VFCTRL_SCREENSHOT 14 // Take screenshot, arg is vf_ctrl_screenshot
#define VFCTRL_INIT_EOSD 15 /* Select EOSD renderer */
#define VFCTRL_DRAW_EOSD 16 /* Render EOSD */
#define VFCTRL_SET_DEINTERLACE 18 /* Set deinterlacing status */

View File

@ -23,35 +23,21 @@
#include <string.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libswscale/swscale.h>
#include <libavcodec/avcodec.h>
#include "mp_msg.h"
#include "img_format.h"
#include "mp_image.h"
#include "vf.h"
#include "vf_scale.h"
#include "fmt-conversion.h"
#include "libvo/fastmemcpy.h"
#include <libswscale/swscale.h>
struct vf_priv_s {
int frameno;
char fname[102];
/// shot stores current screenshot mode:
/// 0: don't take screenshots
/// 1: take single screenshot, reset to 0 afterwards
/// 2: take screenshots of each frame
mp_image_t *image;
void (*image_callback)(void *, mp_image_t *);
void *image_callback_ctx;
int shot, store_slices;
int dw, dh, stride;
uint8_t *buffer;
struct SwsContext *ctx;
AVCodecContext *avctx;
uint8_t *outbuffer;
int outbuffer_size;
};
//===========================================================================//
@ -60,102 +46,51 @@ static int config(struct vf_instance *vf,
int width, int height, int d_width, int d_height,
unsigned int flags, unsigned int outfmt)
{
vf->priv->ctx=sws_getContextFromCmdLine(width, height, outfmt,
d_width, d_height, IMGFMT_RGB24);
vf->priv->outbuffer_size = d_width * d_height * 3 * 2;
vf->priv->outbuffer = realloc(vf->priv->outbuffer, vf->priv->outbuffer_size);
vf->priv->avctx->width = d_width;
vf->priv->avctx->height = d_height;
vf->priv->avctx->pix_fmt = PIX_FMT_RGB24;
vf->priv->avctx->compression_level = 0;
vf->priv->dw = d_width;
vf->priv->dh = d_height;
vf->priv->stride = (3*vf->priv->dw+15)&~15;
free(vf->priv->buffer); // probably reconfigured
vf->priv->buffer = NULL;
free_mp_image(vf->priv->image);
vf->priv->image = new_mp_image(width, height);
mp_image_setfmt(vf->priv->image, outfmt);
vf->priv->image->w = d_width;
vf->priv->image->h = d_height;
return vf_next_config(vf,width,height,d_width,d_height,flags,outfmt);
}
static void write_png(struct vf_priv_s *priv)
{
char *fname = priv->fname;
FILE * fp;
AVFrame pic;
int size;
fp = fopen (fname, "wb");
if (fp == NULL) {
mp_msg(MSGT_VFILTER,MSGL_ERR,"\nPNG Error opening %s for writing!\n", fname);
return;
}
pic.data[0] = priv->buffer;
pic.linesize[0] = priv->stride;
size = avcodec_encode_video(priv->avctx, priv->outbuffer, priv->outbuffer_size, &pic);
if (size > 0)
fwrite(priv->outbuffer, size, 1, fp);
fclose (fp);
}
static int fexists(char *fname)
{
struct stat dummy;
if (stat(fname, &dummy) == 0) return 1;
else return 0;
}
static void gen_fname(struct vf_priv_s* priv)
{
do {
snprintf (priv->fname, 100, "shot%04d.png", ++priv->frameno);
} while (fexists(priv->fname) && priv->frameno < 100000);
if (fexists(priv->fname)) {
priv->fname[0] = '\0';
return;
}
mp_msg(MSGT_VFILTER,MSGL_INFO,"*** screenshot '%s' ***\n",priv->fname);
}
static void scale_image(struct vf_priv_s* priv, mp_image_t *mpi)
{
uint8_t *dst[MP_MAX_PLANES] = {NULL};
int dst_stride[MP_MAX_PLANES] = {0};
dst_stride[0] = priv->stride;
if (!priv->buffer)
priv->buffer = av_malloc(dst_stride[0]*priv->dh);
dst[0] = priv->buffer;
sws_scale(priv->ctx, (const uint8_t **)mpi->planes, mpi->stride, 0, priv->dh, dst, dst_stride);
}
static void start_slice(struct vf_instance *vf, mp_image_t *mpi)
{
vf->dmpi=vf_get_image(vf->next,mpi->imgfmt,
mpi->type, mpi->flags, mpi->width, mpi->height);
if (vf->priv->shot) {
vf->priv->store_slices = 1;
if (!vf->priv->buffer)
vf->priv->buffer = av_malloc(vf->priv->stride*vf->priv->dh);
if (!(vf->priv->image->flags & MP_IMGFLAG_ALLOCATED))
mp_image_alloc_planes(vf->priv->image);
}
}
static void memcpy_pic_slice(unsigned char *dst, unsigned char *src,
int bytesPerLine, int y, int h,
int dstStride, int srcStride)
{
memcpy_pic(dst + h * dstStride, src + h * srcStride, bytesPerLine,
h, dstStride, srcStride);
}
static void draw_slice(struct vf_instance *vf, unsigned char** src,
int* stride, int w,int h, int x, int y)
{
if (vf->priv->store_slices) {
uint8_t *dst[MP_MAX_PLANES] = {NULL};
int dst_stride[MP_MAX_PLANES] = {0};
dst_stride[0] = vf->priv->stride;
dst[0] = vf->priv->buffer;
sws_scale(vf->priv->ctx, (const uint8_t **)src, stride, y, h, dst, dst_stride);
mp_image_t *dst = vf->priv->image;
int bp = (dst->bpp + 7) / 8;
if (dst->flags & MP_IMGFLAG_PLANAR) {
int bytes_per_line[3] = { w * bp, dst->chroma_width, dst->chroma_width };
for (int n = 0; n < 3; n++) {
memcpy_pic_slice(dst->planes[n], src[n], bytes_per_line[n],
y, h, dst->stride[n], stride[n]);
}
} else {
memcpy_pic_slice(dst->planes[0], src[0], dst->w*bp, y, dst->h,
dst->stride[0], stride[0]);
}
}
vf_next_draw_slice(vf,src,stride,w,h,x,y);
}
@ -200,14 +135,15 @@ static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts)
}
if(vf->priv->shot) {
if (vf->priv->shot==1)
vf->priv->shot=0;
gen_fname(vf->priv);
if (vf->priv->fname[0]) {
if (!vf->priv->store_slices)
scale_image(vf->priv, dmpi);
write_png(vf->priv);
}
vf->priv->shot=0;
mp_image_t image;
if (!vf->priv->store_slices)
image = *dmpi;
else
image = *vf->priv->image;
image.w = vf->priv->image->w;
image.h = vf->priv->image->h;
vf->priv->image_callback(vf->priv->image_callback_ctx, &image);
vf->priv->store_slices = 0;
}
@ -216,20 +152,11 @@ static int put_image(struct vf_instance *vf, mp_image_t *mpi, double pts)
static int control (vf_instance_t *vf, int request, void *data)
{
/** data contains an integer argument
* 0: take screenshot with the next frame
* 1: take screenshots with each frame until the same command is given once again
**/
if(request==VFCTRL_SCREENSHOT) {
if (data && *(int*)data) { // repeated screenshot mode
if (vf->priv->shot==2)
vf->priv->shot=0;
else
vf->priv->shot=2;
} else { // single screenshot
if (!vf->priv->shot)
vf->priv->shot=1;
}
struct vf_ctrl_screenshot *cmd = (struct vf_ctrl_screenshot *)data;
vf->priv->image_callback = cmd->image_callback;
vf->priv->image_callback_ctx = cmd->image_callback_ctx;
vf->priv->shot=1;
return CONTROL_TRUE;
}
return vf_next_control (vf, request, data);
@ -249,11 +176,7 @@ static int query_format(struct vf_instance *vf, unsigned int fmt)
static void uninit(vf_instance_t *vf)
{
avcodec_close(vf->priv->avctx);
av_freep(&vf->priv->avctx);
if(vf->priv->ctx) sws_freeContext(vf->priv->ctx);
av_free(vf->priv->buffer);
free(vf->priv->outbuffer);
free_mp_image(vf->priv->image);
free(vf->priv);
}
@ -268,17 +191,9 @@ static int vf_open(vf_instance_t *vf, char *args)
vf->get_image=get_image;
vf->uninit=uninit;
vf->priv=malloc(sizeof(struct vf_priv_s));
vf->priv->frameno=0;
vf->priv->shot=0;
vf->priv->store_slices=0;
vf->priv->buffer=0;
vf->priv->outbuffer=0;
vf->priv->ctx=0;
vf->priv->avctx = avcodec_alloc_context();
if (avcodec_open(vf->priv->avctx, avcodec_find_encoder(CODEC_ID_PNG))) {
mp_msg(MSGT_VFILTER, MSGL_FATAL, "Could not open libavcodec PNG encoder\n");
return 0;
}
vf->priv->image=NULL;
return 1;
}

View File

@ -78,6 +78,8 @@ enum mp_voctrl {
VOCTRL_SET_YUV_COLORSPACE, // struct mp_csp_details
VOCTRL_GET_YUV_COLORSPACE, // struct mp_csp_details
VOCTRL_SCREENSHOT, // struct voctrl_screenshot_args
};
// VOCTRL_SET_EQUALIZER
@ -104,6 +106,16 @@ typedef struct mp_eosd_res {
int mt, mb, ml, mr; // borders (top, bottom, left, right)
} mp_eosd_res_t;
// VOCTRL_SCREENSHOT
struct voctrl_screenshot_args {
// Will be set to a newly allocated image, that contains the screenshot.
// The caller has to free the pointer with free_mp_image().
// It is not specified whether the image data is a copy or references the
// image data directly.
// Is never NULL. (Failure has to be indicated by returning VO_FALSE.)
struct mp_image *out_image;
};
typedef struct {
int x,y;
int w,h;

View File

@ -216,6 +216,8 @@ typedef struct MPContext {
// playback rate. Used to avoid showing it multiple times.
bool drop_message_shown;
struct screenshot_ctx *screenshot_ctx;
#ifdef CONFIG_DVDNAV
struct mp_image *nav_smpi; ///< last decoded dvdnav video image
unsigned char *nav_buffer; ///< last read dvdnav video frame

View File

@ -73,6 +73,7 @@
#include "mp_osd.h"
#include "libvo/video_out.h"
#include "screenshot.h"
#include "sub/font_load.h"
#include "sub/sub.h"
@ -791,6 +792,8 @@ void exit_player_with_rc(struct MPContext *mpctx, enum exit_reason how, int rc)
m_config_free(mpctx->mconfig);
mpctx->mconfig = NULL;
talloc_free(mpctx);
exit(rc);
}
@ -3711,6 +3714,7 @@ static void run_playloop(struct MPContext *mpctx)
get_relative_time(mpctx);
}
print_status(mpctx, MP_NOPTS_VALUE, true);
screenshot_flip(mpctx);
} else
print_status(mpctx, MP_NOPTS_VALUE, false);
@ -3943,7 +3947,8 @@ int main(int argc, char *argv[])
int opt_exit = 0;
int i;
struct MPContext *mpctx = &(struct MPContext){
struct MPContext *mpctx = talloc(NULL, MPContext);
*mpctx = (struct MPContext){
.osd_function = OSD_PLAY,
.begin_skip = MP_NOPTS_VALUE,
.play_tree_step = 1,

230
screenshot.c Normal file
View File

@ -0,0 +1,230 @@
/*
* This file is part of mplayer2.
*
* mplayer2 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.
*
* mplayer2 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 mplayer2; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <libswscale/swscale.h>
#include <libavcodec/avcodec.h>
#include "config.h"
#include "talloc.h"
#include "screenshot.h"
#include "mp_core.h"
#include "mp_msg.h"
#include "libmpcodecs/img_format.h"
#include "libmpcodecs/mp_image.h"
#include "libmpcodecs/dec_video.h"
#include "libmpcodecs/vf.h"
#include "libvo/video_out.h"
#include "fmt-conversion.h"
//for sws_getContextFromCmdLine and mp_sws_set_colorspace
#include "libmpcodecs/vf_scale.h"
#include "libvo/csputils.h"
typedef struct screenshot_ctx {
int each_frame;
int using_vf_screenshot;
int frameno;
char fname[102];
} screenshot_ctx;
static screenshot_ctx *screenshot_get_ctx(MPContext *mpctx)
{
if (!mpctx->screenshot_ctx)
mpctx->screenshot_ctx = talloc_zero(mpctx, screenshot_ctx);
return mpctx->screenshot_ctx;
}
static int write_png(screenshot_ctx *ctx, struct mp_image *image)
{
char *fname = ctx->fname;
FILE *fp = NULL;
void *outbuffer = NULL;
int success = 0;
AVCodecContext *avctx = avcodec_alloc_context();
if (!avctx)
goto error_exit;
if (avcodec_open(avctx, avcodec_find_encoder(CODEC_ID_PNG))) {
mp_msg(MSGT_CPLAYER, MSGL_INFO, "Could not open libavcodec PNG encoder"
" for saving screenshot\n");
goto error_exit;
}
avctx->width = image->width;
avctx->height = image->height;
avctx->pix_fmt = PIX_FMT_RGB24;
avctx->compression_level = 0;
size_t outbuffer_size = image->width * image->height * 3 * 2;
outbuffer = malloc(outbuffer_size);
if (!outbuffer)
goto error_exit;
AVFrame pic;
pic.data[0] = image->planes[0];
pic.linesize[0] = image->stride[0];
int size = avcodec_encode_video(avctx, outbuffer, outbuffer_size, &pic);
if (size < 1)
goto error_exit;
fp = fopen(fname, "wb");
if (fp == NULL) {
avcodec_close(avctx);
mp_msg(MSGT_CPLAYER, MSGL_ERR, "\nPNG Error opening %s for writing!\n",
fname);
goto error_exit;
}
fwrite(outbuffer, size, 1, fp);
fflush(fp);
if (ferror(fp))
goto error_exit;
success = 1;
error_exit:
if (avctx)
avcodec_close(avctx);
if (fp)
fclose(fp);
free(outbuffer);
return success;
}
static int fexists(char *fname)
{
struct stat dummy;
if (stat(fname, &dummy) == 0)
return 1;
else
return 0;
}
static void gen_fname(screenshot_ctx *ctx)
{
do {
snprintf(ctx->fname, 100, "shot%04d.png", ++ctx->frameno);
} while (fexists(ctx->fname) && ctx->frameno < 100000);
if (fexists(ctx->fname)) {
ctx->fname[0] = '\0';
return;
}
mp_msg(MSGT_CPLAYER, MSGL_INFO, "*** screenshot '%s' ***\n", ctx->fname);
}
void screenshot_save(struct MPContext *mpctx, struct mp_image *image)
{
screenshot_ctx *ctx = screenshot_get_ctx(mpctx);
struct mp_image *dst = alloc_mpi(image->w, image->h, IMGFMT_RGB24);
struct SwsContext *sws = sws_getContextFromCmdLine(image->width,
image->height,
image->imgfmt,
dst->width,
dst->height,
dst->imgfmt);
struct mp_csp_details colorspace;
get_detected_video_colorspace(mpctx->sh_video, &colorspace);
// this is a property of the output device; images always use full-range RGB
colorspace.levels_out = MP_CSP_LEVELS_PC;
mp_sws_set_colorspace(sws, &colorspace);
sws_scale(sws, (const uint8_t **)image->planes, image->stride, 0,
image->height, dst->planes, dst->stride);
gen_fname(ctx);
write_png(ctx, dst);
sws_freeContext(sws);
free_mp_image(dst);
}
static void vf_screenshot_callback(void *pctx, struct mp_image *image)
{
struct MPContext *mpctx = (struct MPContext *)pctx;
screenshot_ctx *ctx = screenshot_get_ctx(mpctx);
screenshot_save(mpctx, image);
if (ctx->each_frame)
screenshot_request(mpctx, 0);
}
void screenshot_request(struct MPContext *mpctx, bool each_frame)
{
if (mpctx->video_out && mpctx->video_out->config_ok) {
screenshot_ctx *ctx = screenshot_get_ctx(mpctx);
ctx->using_vf_screenshot = 0;
if (each_frame) {
ctx->each_frame = !ctx->each_frame;
if (!ctx->each_frame)
return;
}
struct voctrl_screenshot_args args;
if (vo_control(mpctx->video_out, VOCTRL_SCREENSHOT, &args) == true) {
screenshot_save(mpctx, args.out_image);
free_mp_image(args.out_image);
} else {
mp_msg(MSGT_CPLAYER, MSGL_INFO, "No VO support for taking"
" screenshots, trying VFCTRL_SCREENSHOT!\n");
ctx->using_vf_screenshot = 1;
struct vf_ctrl_screenshot cmd = {
.image_callback = vf_screenshot_callback,
.image_callback_ctx = mpctx,
};
struct vf_instance *vfilter = mpctx->sh_video->vfilter;
if (vfilter->control(vfilter, VFCTRL_SCREENSHOT, &cmd) !=
CONTROL_OK)
mp_msg(MSGT_CPLAYER, MSGL_INFO,
"...failed (need --vf=screenshot?)\n");
}
}
}
void screenshot_flip(struct MPContext *mpctx)
{
screenshot_ctx *ctx = screenshot_get_ctx(mpctx);
if (!ctx->each_frame)
return;
// screenshot_flip is called when the VO presents a new frame. vf_screenshot
// can behave completely different (consider filters inserted between
// vf_screenshot and vf_vo, that add or remove frames), so handle this case
// somewhere else.
if (ctx->using_vf_screenshot)
return;
screenshot_request(mpctx, 0);
}

39
screenshot.h Normal file
View File

@ -0,0 +1,39 @@
/*
* This file is part of mplayer2.
*
* mplayer2 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.
*
* mplayer2 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 mplayer2; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef MPLAYER_SCREENSHOT_H
#define MPLAYER_SCREENSHOT_H
#include <stdbool.h>
struct MPContext;
struct mp_image;
// Request a taking & saving a screenshot of the currently displayed frame.
// If each_frame is set, this toggles per-frame screenshots, exactly like the
// screenshot slave command (MP_CMD_SCREENSHOT).
void screenshot_request(struct MPContext *mpctx, bool each_frame);
// Save the screenshot contained in the image to disk.
// The image can be in any format supported by libswscale.
void screenshot_save(struct MPContext *mpctx, struct mp_image *image);
// Called by the playback core code when a new frame is displayed.
void screenshot_flip(struct MPContext *mpctx);
#endif /* MPLAYER_SCREENSHOT_H */