All of lore.kernel.org
 help / color / mirror / Atom feed
From: Fam Zheng <famz@redhat.com>
To: Alex K <vip-ak47@yandex.ru>
Cc: qemu-devel@nongnu.org, kraxel@redhat.com, aliguori@amazon.com
Subject: Re: [Qemu-devel] [PATCH] Video and sound capture to a videofile through ffmpeg
Date: Thu, 4 May 2017 19:35:22 +0800	[thread overview]
Message-ID: <20170504113522.GB6865@lemon.lan> (raw)
In-Reply-To: <1493894491-26130-1-git-send-email-vip-ak47@yandex.ru>

On Thu, 05/04 13:41, Alex K wrote:
> Hello everyone,

Hi Alex, thanks for posting the patch! I don't know much about display and sound
in QEMU, so just making some superficial points.
> 
> I've made a patch that adds ability to record video of what's going on
> inside qemu. It uses ffmpeg libraries. Basically, the patch adds
> 2 new commands to the console:
> capture_start path framerate
> capture_stop

Sounds like a good idea. In addition, I think it's worth to be added to QMP too.

> 
> path is required
> framerate could be 24, 25, 30 or 60. Default is 60
> video codec is always h264
> 
> The patch uses ffmpeg so you will need to install these packages:
> ffmpeg libavformat-dev libavcodec-dev libavutil-dev libswscale-dev
> 
> This is my first time posting here, so please correct me if I'm doing
> something wrong

This line can be moved after a '---' line following the 'signed-off-by' line,
taht way it doesn't go into the git history when your patch is applied by
maintainers, as it is useless there.

> 
> Signed-off-by: Alex K <vip-ak47@yandex.ru>
> ---
>  configure                          |  20 +
>  default-configs/i386-softmmu.mak   |   1 +
>  default-configs/x86_64-softmmu.mak |   1 +
>  hmp-commands.hx                    |  34 ++
>  hmp.h                              |   2 +
>  hw/display/Makefile.objs           |   2 +
>  hw/display/capture.c               | 761 +++++++++++++++++++++++++++++++++++++
>  hw/display/capture.h               |  78 ++++
>  8 files changed, 899 insertions(+)
>  create mode 100644 hw/display/capture.c
>  create mode 100644 hw/display/capture.h
> 
> diff --git a/configure b/configure
> index 48a9370..a6ddbf0 100755
> --- a/configure
> +++ b/configure
> @@ -281,6 +281,7 @@ opengl=""
>  opengl_dmabuf="no"
>  avx2_opt="no"
>  zlib="yes"
> +libav="yes"
>  lzo=""
>  snappy=""
>  bzip2=""
> @@ -1993,6 +1994,25 @@ if test "$seccomp" != "no" ; then
>          seccomp="no"
>      fi
>  fi
> +#########################################
> +# libav check
> +

Use $pkg_config?

> +if test "$libav" != "no" ; then
> +    cat > $TMPC << EOF
> +#include <libavcodec/avcodec.h>
> +#include <libavformat/avformat.h>
> +
> +int main(void){ av_register_all(); avcodec_register_all(); return 0; }
> +EOF
> +    if compile_prog "" "-lm -lpthread -lavformat -lavcodec -lavutil -lswscale -lswresample" ; then
> +        :
> +    else
> +        error_exit "libav check failed" \
> +            "Make sure to have the libav libs and headers installed."

Please don't make this an error - the dependency should be optional.

> +    fi
> +fi
> +LIBS="$LIBS -lm -lpthread -lavformat -lavcodec -lavutil -lswscale -lswresample"
> +

Please don't extend $LIBS, use something like AV_LIBS and only add it to the
object that uses the library, with per object flags.

Also, add AV_CFLAGS if necessary.

See also how $curl_libs and friends are created and used in configure and
Makefile.


>  ##########################################
>  # xen probe
>  
> diff --git a/default-configs/i386-softmmu.mak b/default-configs/i386-softmmu.mak
> index d2ab2f6..35ce513 100644
> --- a/default-configs/i386-softmmu.mak
> +++ b/default-configs/i386-softmmu.mak
> @@ -59,3 +59,4 @@ CONFIG_SMBIOS=y
>  CONFIG_HYPERV_TESTDEV=$(CONFIG_KVM)
>  CONFIG_PXB=y
>  CONFIG_ACPI_VMGENID=y
> +CONFIG_CAPTURE=y
> diff --git a/default-configs/x86_64-softmmu.mak b/default-configs/x86_64-softmmu.mak
> index 9bde2f1..b9a7175 100644
> --- a/default-configs/x86_64-softmmu.mak
> +++ b/default-configs/x86_64-softmmu.mak
> @@ -59,3 +59,4 @@ CONFIG_SMBIOS=y
>  CONFIG_HYPERV_TESTDEV=$(CONFIG_KVM)
>  CONFIG_PXB=y
>  CONFIG_ACPI_VMGENID=y
> +CONFIG_CAPTURE=y

These two hunks are wrong. Probe and generate it in configure, and config.h.

See how CONFIG_CURL is handled in configure and Makefiles.

For more information, see docs/build-system.txt. But I believe following the
libcurl example is enough.

> diff --git a/hmp-commands.hx b/hmp-commands.hx
> index 0aca984..9066aac 100644
> --- a/hmp-commands.hx
> +++ b/hmp-commands.hx
> @@ -1809,3 +1809,37 @@ ETEXI
>  STEXI
>  @end table
>  ETEXI
> +
> +    {
> +        .name       = "capture_start",
> +        .args_type  = "filename:F,fps:i?",
> +        .params     = "filename [framerate]",
> +        .help       = "Start video capture",
> +        .cmd        = hmp_capture_start,
> +    },
> +
> +STEXI
> +@item capture_start @var{filename} [@var{framerate}]
> +@findex capture_start
> +Start video capture.
> +Capture video into @var{filename} with framerate @var{framerate}.

Is there an fps limit? Is it possible to do 0.5 fps?

> +
> +Defaults:
> +@itemize @minus
> +@item framerate = 60
> +@end itemize
> +ETEXI
> +
> +    {
> +        .name       = "capture_stop",
> +        .args_type  = "",
> +        .params     = "",
> +        .help       = "Stop video capture",
> +        .cmd        = hmp_capture_stop,
> +    },
> +
> +STEXI
> +@item capture_stop
> +@findex capture_stop
> +Stop video capture.
> +ETEXI
> diff --git a/hmp.h b/hmp.h
> index 799fd37..36c7a4d 100644
> --- a/hmp.h
> +++ b/hmp.h
> @@ -138,5 +138,7 @@ void hmp_rocker_of_dpa_groups(Monitor *mon, const QDict *qdict);
>  void hmp_info_dump(Monitor *mon, const QDict *qdict);
>  void hmp_hotpluggable_cpus(Monitor *mon, const QDict *qdict);
>  void hmp_info_vm_generation_id(Monitor *mon, const QDict *qdict);
> +void hmp_capture_start(Monitor *mon, const QDict *qdict);
> +void hmp_capture_stop(Monitor *mon, const QDict *qdict);
>  
>  #endif
> diff --git a/hw/display/Makefile.objs b/hw/display/Makefile.objs
> index 551c050..a918896 100644
> --- a/hw/display/Makefile.objs
> +++ b/hw/display/Makefile.objs
> @@ -20,6 +20,8 @@ common-obj-$(CONFIG_ZAURUS) += tc6393xb.o
>  
>  common-obj-$(CONFIG_MILKYMIST_TMU2) += milkymist-tmu2.o
>  
> +obj-$(CONFIG_CAPTURE) += capture.o
> +
>  obj-$(CONFIG_OMAP) += omap_dss.o
>  obj-$(CONFIG_OMAP) += omap_lcdc.o
>  obj-$(CONFIG_PXA2XX) += pxa2xx_lcd.o
> diff --git a/hw/display/capture.c b/hw/display/capture.c
> new file mode 100644
> index 0000000..c89aaa0
> --- /dev/null
> +++ b/hw/display/capture.c
> @@ -0,0 +1,761 @@

A copy right header is mandatory for this source file.

Please also include "qemu/osdep.h" before any other headers.

> +#include "capture.h"
> +
> +static void sound_capture_notify(void *opaque, audcnotification_e cmd)
> +{
> +    (void) opaque;
> +    (void) cmd;

These lines are not needed per QEMU's compiler warning level.

> +}
> +
> +static void sound_capture_destroy(void *opaque)
> +{
> +    (void) opaque;

This one too.

> +}
> +
> +static void write_empty_sound(void *opaque, struct CaptureThreadWorkerData *data)
> +{
> +    AVFormatContext *oc = data->oc;
> +    OutputStream *ost = &data->audio_stream;
> +
> +    AVFrame *tmp = ost->tmp_frame;
> +    ost->tmp_frame = ost->empty_frame;
> +    double newlen = write_audio_frame(oc, ost);
> +    ost->tmp_frame = tmp;
> +
> +    if (newlen >= 0.0) {
> +        data->video_len = newlen;
> +    }
> +}
> +
> +static void sound_capture_capture(void *opaque, void *buf, int size)
> +{
> +    int bufsize = size;
> +    SoundCapture *wav = opaque;
> +    AVFrame *frame;
> +    int sampleCount;
> +    double len1, len2, delta;
> +    int8_t *q;
> +    int buffpos;
> +
> +    /*int32_t n = 0;
> +    int i = 0;
> +    for(i=0;i<size;i++) {
> +        int8_t a = ((int8_t*)buf)[i];
> +        n+=a;
> +    }
> +    wav->bytes += size;
> +    if(n==0)
> +        return;
> +    printf("%d\n",n);*/
> +    frame = wav->data->audio_stream.tmp_frame;
> +    sampleCount = frame->nb_samples * 4;
> +
> +    len1 = wav->data->video_len;
> +    len2 = wav->data->video_len2;
> +    delta = len1 - len2;
> +
> +    while (delta < 0.0) {
> +        write_empty_sound(opaque, wav->data);
> +
> +        len1 = wav->data->video_len;
> +        len2 = wav->data->video_len2;
> +        delta = len1 - len2;
> +    }
> +
> +    q = (int8_t *)frame->data[0];
> +
> +    buffpos = 0;
> +    while (bufsize > 0) {
> +        int start = wav->bufferPos;
> +        int freeSpace = sampleCount - start;
> +
> +        int willWrite = freeSpace;
> +        if (willWrite > bufsize) {
> +            willWrite = bufsize;
> +        }
> +
> +        memcpy(q + start, buf + buffpos, willWrite);
> +        bufsize -= willWrite;
> +        buffpos += willWrite;
> +
> +        freeSpace = sampleCount - start - willWrite;
> +
> +        if (freeSpace == 0) {
> +            double newlen = write_audio_frame(wav->data->oc, &wav->data->audio_stream);
> +
> +            if (newlen >= 0.0) {
> +                wav->data->video_len = newlen;
> +            }
> +            wav->bufferPos = 0;
> +        } else {
> +            wav->bufferPos = start + willWrite;
> +        }
> +    }
> +}
> +
> +static void sound_capture_capture_destroy(void *opaque)
> +{
> +    SoundCapture *wav = opaque;
> +
> +    AUD_del_capture (wav->cap, wav);
> +}
> +
> +static int sound_capture_start_capture(struct CaptureThreadWorkerData *data)
> +{
> +    Monitor *mon = cur_mon;
> +    SoundCapture *wav;
> +    struct audsettings as;
> +    struct audio_capture_ops ops;
> +    CaptureVoiceOut *cap;
> +
> +    as.freq = 44100;
> +    as.nchannels = 2;
> +    as.fmt = AUD_FMT_S16;
> +    as.endianness = 0;
> +
> +    ops.notify = sound_capture_notify;
> +    ops.capture = sound_capture_capture;
> +    ops.destroy = sound_capture_destroy;
> +
> +    wav = g_malloc0(sizeof(*wav));
> +
> +
> +    cap = AUD_add_capture(&as, &ops, wav);
> +    if (!cap) {
> +        monitor_printf(mon, "Failed to add audio capture\n");
> +        goto error_free;
> +    }
> +
> +    wav->bufferPos = 0;
> +    wav->data = data;
> +    wav->cap = cap;
> +    data->soundCapture = wav;
> +    return 0;
> +
> +error_free:
> +    g_free(wav);
> +    return -1;
> +}
> +
> +static int write_frame(AVFormatContext *fmt_ctx, const AVRational *time_base,
> +                        AVStream *st, AVPacket *pkt)

Parameter list is off by one column.

> +{
> +    /* rescale output packet timestamp values from codec to stream timebase */
> +    av_packet_rescale_ts(pkt, *time_base, st->time_base);
> +    pkt->stream_index = st->index;
> +    /* Write the compressed frame to the media file. */
> +    return av_interleaved_write_frame(fmt_ctx, pkt);
> +}
> +
> +/* Add an output stream. */
> +static void add_video_stream(OutputStream *ost, AVFormatContext *oc,
> +                       AVCodec **codec,
> +                       enum AVCodecID codec_id,
> +                       int w, int h, int bit_rate, int framerate)

Parameter list indentation is off.
> +{
> +    AVCodecContext *c;
> +    /* find the encoder */
> +    *codec = avcodec_find_encoder(codec_id);
> +    if (!(*codec)) {
> +        fprintf(stderr, "Could not find encoder for '%s'\n",
> +                avcodec_get_name(codec_id));
> +        exit(1);

Can this be handled more gracefully instead of exit? (The same question to all
other 'exits' in this patch).

> +    }
> +    ost->st = avformat_new_stream(oc, *codec);
> +    if (!ost->st) {
> +        fprintf(stderr, "Could not allocate stream\n");
> +        exit(1);
> +    }
> +    ost->st->id = oc->nb_streams - 1;
> +    c = ost->st->codec;
> +    if ((*codec)->type == AVMEDIA_TYPE_VIDEO) {
> +        c->codec_id = codec_id;
> +        c->bit_rate = bit_rate;
> +        /* Resolution must be a multiple of two. */
> +        c->width    = w;
> +        c->height   = h;
> +        /* timebase: This is the fundamental unit of time (in seconds) in terms
> +         * of which frame timestamps are represented. For fixed-fps content,
> +         * timebase should be 1/framerate and timestamp increments should be
> +         * identical to 1. */
> +        ost->st->time_base = (AVRational){ 1, framerate };
> +        c->time_base = ost->st->time_base;
> +        c->gop_size  = 12; /* emit one intra frame every 12 frames at most */
> +        c->pix_fmt   = AV_PIX_FMT_YUV420P;
> +        if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) {
> +            /* just for testing, we also add B frames */
> +            c->max_b_frames = 2;
> +        }
> +        if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) {
> +            /* Needed to avoid using macroblocks in which some coeffs overflow.
> +             * This does not happen with normal video, it just happens here as
> +             * the motion of the chroma plane does not match the luma plane. */
> +            c->mb_decision = 2;
> +        }
> +    } else {
> +        fprintf(stderr, "Wrong stream type\n");
> +        exit(1);
> +    }
> +    /* Some formats want stream headers to be separate. */
> +    if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
> +        c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
> +    }
> +}
> +
> +static void add_audio_stream(OutputStream *ost, AVFormatContext *oc,
> +                             AVCodec **codec,
> +                             enum AVCodecID codec_id)
> +{
> +    AVCodecContext *c;
> +    int i;
> +    /* find the encoder */
> +    *codec = avcodec_find_encoder(codec_id);
> +    if (!(*codec)) {
> +        fprintf(stderr, "Could not find encoder for '%s'\n",
> +                avcodec_get_name(codec_id));
> +        exit(1);
> +    }
> +    ost->st = avformat_new_stream(oc, *codec);
> +    if (!ost->st) {
> +        fprintf(stderr, "Could not allocate stream\n");
> +        exit(1);
> +    }
> +    ost->st->id = oc->nb_streams - 1;
> +    c = ost->st->codec;
> +    if ((*codec)->type == AVMEDIA_TYPE_AUDIO) {
> +        c->sample_fmt = AV_SAMPLE_FMT_FLTP;
> +        c->bit_rate    = 128000;
> +        c->sample_rate = 44100;
> +        c->channels    = av_get_channel_layout_nb_channels(c->channel_layout);
> +        c->channel_layout = AV_CH_LAYOUT_STEREO;
> +        if ((*codec)->channel_layouts) {
> +            c->channel_layout = (*codec)->channel_layouts[0];
> +            for (i = 0; (*codec)->channel_layouts[i]; i++) {
> +                if ((*codec)->channel_layouts[i] == AV_CH_LAYOUT_STEREO) {
> +                    c->channel_layout = AV_CH_LAYOUT_STEREO;
> +                }
> +            }
> +        }
> +        c->channels   = av_get_channel_layout_nb_channels(c->channel_layout);
> +        ost->st->time_base = (AVRational){ 1, c->sample_rate };
> +    } else {
> +        fprintf(stderr, "Wrong stream type\n");
> +        exit(1);
> +    }
> +    /* Some formats want stream headers to be separate. */
> +    if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
> +        c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
> +    }
> +}
> +/**************************************************************/
> +/* audio output */
> +static AVFrame *alloc_audio_frame(enum AVSampleFormat sample_fmt,
> +                                  uint64_t channel_layout,
> +                                  int sample_rate, int nb_samples)
> +{
> +    AVFrame *frame = av_frame_alloc();
> +    int ret;
> +    if (!frame) {
> +        fprintf(stderr, "Error allocating an audio frame\n");
> +        exit(1);
> +    }
> +    frame->format = sample_fmt;
> +    frame->channel_layout = channel_layout;
> +    frame->sample_rate = sample_rate;
> +    frame->nb_samples = nb_samples;
> +    if (nb_samples) {
> +        ret = av_frame_get_buffer(frame, 0);
> +        if (ret < 0) {
> +            fprintf(stderr, "Error allocating an audio buffer\n");
> +            exit(1);
> +        }
> +    }
> +    return frame;
> +}
> +
> +static void open_audio(AVFormatContext *oc, AVCodec *codec,
> +                       OutputStream *ost, AVDictionary *opt_arg)
> +{
> +    AVCodecContext *c;
> +    int nb_samples;
> +    int ret;
> +    AVDictionary *opt = NULL;
> +    c = ost->st->codec;
> +    /* open it */
> +    av_dict_copy(&opt, opt_arg, 0);
> +    ret = avcodec_open2(c, codec, &opt);
> +    av_dict_free(&opt);
> +    if (ret < 0) {
> +        fprintf(stderr, "Could not open audio codec: %s\n", av_err2str(ret));
> +        exit(1);
> +    }
> +    nb_samples = c->frame_size;
> +    ost->frame     = alloc_audio_frame(c->sample_fmt, c->channel_layout,
> +                                       c->sample_rate, nb_samples);
> +
> +    ost->tmp_frame = alloc_audio_frame(AV_SAMPLE_FMT_S16, c->channel_layout,
> +                                       c->sample_rate, nb_samples);
> +    ost->tmp_frame->pts = 0;
> +
> +    ost->empty_frame = alloc_audio_frame(AV_SAMPLE_FMT_S16, c->channel_layout,
> +                                       c->sample_rate, nb_samples);
> +    int sampleCount = nb_samples * 4;
> +    int8_t *q = (int8_t *)ost->empty_frame->data[0];
> +    memset(q, 0, sampleCount);
> +
> +    /* create resampler context */
> +    ost->swr_ctx = swr_alloc();
> +    if (!ost->swr_ctx) {
> +        fprintf(stderr, "Could not allocate resampler context\n");
> +        exit(1);
> +    }
> +    /* set options */
> +    av_opt_set_int       (ost->swr_ctx, "in_channel_count",  c->channels,       0);
> +    av_opt_set_int       (ost->swr_ctx, "in_sample_rate",    c->sample_rate,    0);
> +    av_opt_set_sample_fmt(ost->swr_ctx, "in_sample_fmt",     AV_SAMPLE_FMT_S16, 0);
> +    av_opt_set_int       (ost->swr_ctx, "out_channel_count", c->channels,       0);
> +    av_opt_set_int       (ost->swr_ctx, "out_sample_rate",   c->sample_rate,    0);
> +    av_opt_set_sample_fmt(ost->swr_ctx, "out_sample_fmt",    c->sample_fmt,     0);
> +    /* initialize the resampling context */
> +    if (swr_init(ost->swr_ctx) < 0) {
> +        fprintf(stderr, "Failed to initialize the resampling context\n");
> +        exit(1);
> +    }
> +}
> +
> +/*
> + * encode one audio frame and send it to the muxer
> + */
> +static double write_audio_frame(AVFormatContext *oc, OutputStream *ost)
> +{
> +    AVCodecContext *c;
> +    AVPacket pkt = { 0 };
> +    AVFrame *frame;
> +    int ret;
> +    int got_packet;
> +    int dst_nb_samples;
> +    av_init_packet(&pkt);
> +    c = ost->st->codec;
> +    frame = ost->tmp_frame;
> +    frame->pts = frame->pts + 1;
> +    double videolen = -1.0;
> +    if (frame) {
> +        /* convert samples from native format to destination codec format,
> +         * using the resampler */
> +        /* compute destination number of samples */
> +        dst_nb_samples = av_rescale_rnd(
> +            swr_get_delay(ost->swr_ctx, c->sample_rate) + frame->nb_samples,
> +            c->sample_rate, c->sample_rate, AV_ROUND_UP);
> +        av_assert0(dst_nb_samples == frame->nb_samples);
> +        /* when we pass a frame to the encoder, it may keep a reference to it
> +         * internally;
> +         * make sure we do not overwrite it here
> +         */
> +        ret = av_frame_make_writable(ost->frame);
> +        if (ret < 0) {
> +            exit(1);
> +        }
> +        /* convert to destination format */
> +        ret = swr_convert(ost->swr_ctx,
> +                          ost->frame->data, dst_nb_samples,
> +                          (const uint8_t **)frame->data, frame->nb_samples);
> +        if (ret < 0) {
> +            fprintf(stderr, "Error while converting\n");
> +            exit(1);
> +        }
> +        frame = ost->frame;
> +        frame->pts = av_rescale_q(ost->samples_count,
> +                                  (AVRational){1, c->sample_rate},
> +                                  c->time_base);
> +
> +        videolen = (double)frame->pts / c->sample_rate;
> +        ost->samples_count += dst_nb_samples;
> +    }
> +    ret = avcodec_encode_audio2(c, &pkt, frame, &got_packet);
> +    if (ret < 0) {
> +        fprintf(stderr, "Error encoding audio frame: %s\n", av_err2str(ret));
> +        exit(1);
> +    }
> +    if (got_packet) {
> +        ret = write_frame(oc, &c->time_base, ost->st, &pkt);
> +        if (ret < 0) {
> +            fprintf(stderr, "Error while writing audio frame: %s\n",
> +                    av_err2str(ret));
> +            exit(1);
> +        }
> +    }
> +    return videolen;
> +}

Add a blank line?

> +static void write_delayed_audio_frames(void)
> +{
> +    struct CaptureThreadWorkerData *data = capture_get_data();
> +    AVFormatContext *oc = data->oc;
> +    OutputStream *ost = &data->audio_stream;
> +    AVCodecContext *c = ost->st->codec;
> +
> +    AVPacket pkt = { 0 };
> +    pkt.data = NULL;
> +    pkt.size = 0;
> +    av_init_packet(&pkt);
> +    int got_output = 1;
> +    int ret;
> +    while (got_output) {
> +
> +        ret = avcodec_encode_audio2(c, &pkt, NULL, &got_output);
> +        if (ret < 0) {
> +            fprintf(stderr, "Error encoding frame\n");
> +            exit(1);
> +        }
> +
> +        if (got_output) {
> +            ret = write_frame(oc, &c->time_base, ost->st, &pkt);
> +            av_free_packet(&pkt);
> +        }
> +    }
> +}

Blank line.

> +/**************************************************************/
> +/* video output */
> +static AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height)
> +{
> +    AVFrame *picture;
> +    int ret;
> +    picture = av_frame_alloc();
> +    if (!picture) {
> +        return NULL;
> +    }
> +    picture->format = pix_fmt;
> +    picture->width  = width;
> +    picture->height = height;
> +    /* allocate the buffers for the frame data */
> +    ret = av_frame_get_buffer(picture, 32);
> +    if (ret < 0) {
> +        fprintf(stderr, "Could not allocate frame data.\n");
> +        exit(1);
> +    }
> +    return picture;
> +}
> +
> +static void open_video(AVFormatContext *oc, AVCodec *codec,
> +                       OutputStream *ost, AVDictionary *opt_arg)
> +{
> +    int ret;
> +    AVCodecContext *c = ost->st->codec;
> +    AVDictionary *opt = NULL;
> +    av_dict_copy(&opt, opt_arg, 0);
> +    /* open the codec */
> +    ret = avcodec_open2(c, codec, &opt);
> +    av_dict_free(&opt);
> +    if (ret < 0) {
> +        fprintf(stderr, "Could not open video codec: %s\n", av_err2str(ret));
> +        exit(1);
> +    }
> +    /* allocate and init a re-usable frame */
> +    ost->frame = alloc_picture(c->pix_fmt, c->width, c->height);
> +    if (!ost->frame) {
> +        fprintf(stderr, "Could not allocate video frame\n");
> +        exit(1);
> +    }
> +}
> +
> +static AVFrame *get_filled_image(void)
> +{
> +    QemuConsole *con = qemu_console_lookup_by_index(0);
> +    DisplaySurface *surface;
> +    surface = qemu_console_surface(con);
> +
> +    if (con == NULL) {
> +        fprintf(stderr, "There is no QemuConsole I can screendump from.\n");
> +        return NULL;
> +    }
> +
> +    int ourW = pixman_image_get_width(surface->image);
> +    int ourH = pixman_image_get_height(surface->image);
> +
> +    AVFrame *pict = alloc_picture(AV_PIX_FMT_RGB32, ourW, ourH);
> +    av_frame_make_writable(pict);
> +
> +    uint8_t* picdata = (uint8_t *)pixman_image_get_data(surface->image);
> +
> +    memcpy(pict->data[0], picdata, ourW * ourH * 4);
> +    return pict;
> +}
> +
> +static AVFrame *get_video_frame(OutputStream *ost, int64_t frame)
> +{
> +    AVCodecContext *c = ost->st->codec;
> +
> +    AVFrame *pict = get_filled_image();
> +    if (pict == NULL) {
> +        return NULL;
> +    }
> +
> +    struct SwsContext *swsContext = sws_getContext(
> +        pict->width, pict->height, pict->format,
> +        ost->frame->width, ost->frame->height,
> +        ost->frame->format, SWS_BICUBIC, NULL, NULL, NULL);
> +    sws_scale(swsContext, (const uint8_t * const *)pict->data,
> +              pict->linesize , 0, c->height, ost->frame->data,
> +              ost->frame->linesize);
> +
> +    av_frame_free(&pict);
> +    sws_freeContext(swsContext);
> +
> +    if (frame <= ost->frame->pts) {
> +        ost->frame->pts = ost->frame->pts + 1;
> +    } else {
> +        ost->frame->pts = frame;
> +    }
> +
> +    return ost->frame;
> +}

Blank line.

> +/*
> + * encode one video frame and send it to the muxer
> + */
> +static void write_video_frame(AVFormatContext *oc,
> +                              OutputStream *ost, int frameNumber)
> +{
> +    int ret;
> +    AVCodecContext *c;
> +    AVFrame *frame;
> +    int got_packet = 0;
> +    AVPacket pkt = { 0 };
> +
> +    frame = get_video_frame(ost, frameNumber);
> +    if (frame == NULL) {
> +        return;
> +    }
> +
> +    c = ost->st->codec;
> +    av_init_packet(&pkt);
> +    /* encode the image */
> +    ret = avcodec_encode_video2(c, &pkt, frame, &got_packet);
> +    if (ret < 0) {
> +        fprintf(stderr, "Error encoding video frame: %s\n", av_err2str(ret));
> +        exit(1);
> +    }
> +    if (got_packet) {
> +        ret = write_frame(oc, &c->time_base, ost->st, &pkt);
> +    } else {
> +        ret = 0;
> +    }
> +    if (ret < 0) {
> +        fprintf(stderr, "Error while writing video frame: %s\n", av_err2str(ret));
> +        return;
> +    }
> +}
> +
> +static void write_delayed_video_frames(void)
> +{
> +    struct CaptureThreadWorkerData *data = capture_get_data();
> +    AVFormatContext *oc = data->oc;
> +    OutputStream *ost = &data->stream;
> +    AVCodecContext *c = ost->st->codec;
> +
> +    AVPacket pkt = { 0 };
> +    pkt.data = NULL;
> +    pkt.size = 0;
> +    av_init_packet(&pkt);
> +    int got_output = 1;
> +    int ret;
> +    while (got_output) {
> +        ret = avcodec_encode_video2(c, &pkt, NULL, &got_output);
> +        if (ret < 0) {
> +            fprintf(stderr, "Error encoding frame\n");
> +            exit(1);
> +        }
> +
> +        if (got_output) {
> +            ret = write_frame(oc, &c->time_base, ost->st, &pkt);
> +            av_free_packet(&pkt);
> +        }
> +    }
> +}
> +
> +static void close_stream(AVFormatContext *oc, OutputStream *ost)
> +{
> +    avcodec_close(ost->st->codec);
> +    av_frame_free(&ost->frame);
> +    av_frame_free(&ost->tmp_frame);
> +    sws_freeContext(ost->sws_ctx);
> +    swr_free(&ost->swr_ctx);
> +}
> +
> +static int ends_with(const char *str, const char *suffix)
> +{
> +    if (!str || !suffix) {
> +        return 0;
> +    }
> +    size_t lenstr = strlen(str);
> +    size_t lensuffix = strlen(suffix);
> +    if (lensuffix >  lenstr) {
> +        return 0;
> +    }
> +    return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0;
> +}
> +
> +static struct CaptureThreadWorkerData *capture_get_data(void)
> +{
> +    static struct CaptureThreadWorkerData data = {0};
> +    return &data;
> +}
> +
> +static void capture_timer(void *p)
> +{
> +    struct CaptureThreadWorkerData *data = (struct CaptureThreadWorkerData *)p;
> +    if (!data->is_capturing) {
> +        return;
> +    }
> +
> +    int64_t n = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
> +    int64_t intdelta = (n - data->time) / 100000;
> +    double delta = (double)intdelta / 10000;
> +    data->delta += delta;
> +    data->time   = n;
> +
> +    while (data->delta > (1.0 / data->framerate)) {
> +        data->delta -= 1.0 / data->framerate;
> +
> +        av_frame_make_writable(data->stream.frame);
> +        write_video_frame(data->oc, &data->stream,
> +            (int)(floor(data->video_len * (double)data->framerate + 0.5)));
> +    }
> +    data->video_len2 = data->video_len2 + delta;
> +
> +    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
> +    if (data->is_capturing) {
> +        timer_mod_ns(data->timer, now + 10000000);
> +    }
> +}
> +
> +static void capture_powerdown_req(void)
> +{
> +    if (capture_stop()) {
> +        printf("Capture stoped\n");
> +    }
> +}
> +
> +void hmp_capture_start(Monitor *mon, const QDict *qdict)
> +{
> +    const char *filename = qdict_get_str(qdict, "filename");
> +    int framerate = qdict_get_try_int(qdict, "fps", 60);
> +
> +    struct CaptureThreadWorkerData *data = capture_get_data();
> +    if (!data->is_loaded) {
> +        av_register_all();
> +        avcodec_register_all();
> +        data->codec = avcodec_find_encoder(AV_CODEC_ID_H264);
> +        if (!data->codec) {
> +            fprintf(stderr, "codec not found\n");
> +            return;
> +        }
> +        data->c = NULL;
> +        data->is_loaded = 1;
> +        atexit(capture_powerdown_req);
> +    }
> +
> +    if (data->is_capturing == 0) {
> +        if (!ends_with(filename, ".mp4")
> +         && !ends_with(filename, ".mpg")
> +         && !ends_with(filename, ".avi")) {
> +            monitor_printf(mon, "Invalid file format, use .mp4 or .mpg\n");
> +            return;
> +        }
> +        if (framerate != 60 && framerate != 30
> +         && framerate != 24 && framerate != 25) {
> +            monitor_printf(mon, "Invalid framerate, valid values are: 24, 25, 30, 60\n");
> +            return;
> +        }
> +        monitor_printf(mon, "Capture started to file: %s\n", filename);
> +
> +        data->framerate = framerate;
> +        data->frame = 0;
> +
> +        data->delta = 0.0;
> +        data->time = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
> +
> +        data->video_len = 0.0;
> +        data->video_len2 = 0.0;
> +
> +        QemuConsole *con = qemu_console_lookup_by_index(0);
> +        DisplaySurface *surface;
> +        surface = qemu_console_surface(con);
> +        int resW = pixman_image_get_width(surface->image);
> +        int resH = pixman_image_get_height(surface->image);
> +
> +        OutputStream video_st = { 0 };
> +        data->stream = video_st;
> +        OutputStream audio_st = { 0 };
> +        data->audio_stream = audio_st;
> +
> +        avformat_alloc_output_context2(&data->oc, NULL, "avi", filename);
> +        AVOutputFormat *fmt;
> +        fmt = data->oc->oformat;
> +
> +        add_video_stream(&data->stream, data->oc, &data->codec,
> +                         fmt->video_codec, resW, resH, 4000000, framerate);
> +        add_audio_stream(&data->audio_stream, data->oc, &data->audio_codec,
> +                         fmt->audio_codec);
> +
> +        open_video(data->oc, data->codec, &data->stream, NULL);
> +        open_audio(data->oc, data->audio_codec, &data->audio_stream, NULL);
> +
> +        int ret = avio_open(&data->oc->pb, filename, AVIO_FLAG_WRITE);
> +        if (ret < 0) {
> +            fprintf(stderr, "Could not open '%s': %s\n", filename,
> +                    av_err2str(ret));
> +            return;
> +        }
> +        ret = avformat_write_header(data->oc, NULL);
> +        if (ret < 0) {
> +            fprintf(stderr, "Error occurred when opening output file: %s\n",
> +                    av_err2str(ret));
> +            return;
> +        }
> +
> +        data->is_capturing = 1;
> +
> +        if (data->timer) {
> +            timer_free(data->timer);
> +        }
> +        data->timer = timer_new_ns(QEMU_CLOCK_REALTIME, capture_timer, data);
> +        int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
> +        timer_mod_ns(data->timer, now + 1000000000 / data->framerate);
> +
> +        sound_capture_start_capture(data);
> +    } else {
> +        monitor_printf(mon, "Already capturing\n");
> +    }
> +}
> +
> +static int capture_stop(void)
> +{
> +    struct CaptureThreadWorkerData *data = capture_get_data();
> +    if (!data->is_loaded) {
> +        return 0;
> +    }
> +
> +    if (data->is_capturing) {
> +        data->is_capturing = 0;
> +
> +        write_delayed_video_frames();
> +        write_delayed_audio_frames();
> +
> +        av_write_trailer(data->oc);
> +        close_stream(data->oc, &data->stream);
> +        close_stream(data->oc, &data->audio_stream);
> +        avio_closep(&data->oc->pb);
> +        avformat_free_context(data->oc);
> +
> +        sound_capture_capture_destroy(data->soundCapture);
> +        return 1;
> +    }
> +    return 0;
> +}
> +
> +void hmp_capture_stop(Monitor *mon, const QDict *qdict)
> +{
> +    if (capture_stop()) {
> +        monitor_printf(mon, "Capture stopped\n");
> +    } else {
> +        monitor_printf(mon, "Not capturing\n");
> +    }
> +}
> diff --git a/hw/display/capture.h b/hw/display/capture.h
> new file mode 100644
> index 0000000..73c79f1
> --- /dev/null
> +++ b/hw/display/capture.h
> @@ -0,0 +1,78 @@
> +#ifndef CAPTURE_H
> +#define CAPTURE_H
> +
> +#include "qemu/osdep.h"
> +#include "monitor/monitor.h"
> +#include "ui/console.h"
> +#include "qemu/timer.h"
> +#include "audio/audio.h"
> +
> +#include <libavcodec/avcodec.h>
> +#include <libavformat/avformat.h>
> +#include "libavutil/frame.h"
> +#include "libavutil/imgutils.h"
> +#include <libswscale/swscale.h>
> +#include <libavutil/avassert.h>
> +#include <libavutil/channel_layout.h>
> +#include <libavutil/opt.h>
> +#include <libavutil/mathematics.h>
> +#include <libavutil/timestamp.h>
> +#include <libswresample/swresample.h>
> +
> +void hmp_capture_start(Monitor *mon, const QDict *qdict);
> +void hmp_capture_stop(Monitor *mon, const QDict *qdict);
> +
> +typedef struct OutputStream {
> +    AVStream *st;
> +    int samples_count;
> +    AVFrame *frame;
> +    AVFrame *tmp_frame;
> +    AVFrame *empty_frame;
> +    struct SwsContext *sws_ctx;
> +    struct SwrContext *swr_ctx;
> +} OutputStream;
> +
> +struct CaptureThreadWorkerData {
> +    QEMUTimer *timer;
> +    int frame;
> +    int is_loaded;
> +    int is_capturing;
> +    int framerate;
> +    double video_len;
> +    double video_len2;
> +    CaptureState *wavCapture;
> +
> +    AVCodec *codec;
> +    AVCodecContext *c;
> +
> +    AVFrame *picture;
> +    AVPacket pkt;
> +
> +    AVCodec *audio_codec;
> +    OutputStream stream;
> +    OutputStream audio_stream;
> +    AVFormatContext *oc;
> +
> +    int64_t time;
> +    double delta;
> +
> +    void *soundCapture;
> +};
> +
> +typedef struct {
> +    int bytes;
> +    CaptureVoiceOut *cap;
> +    struct CaptureThreadWorkerData *data;
> +    int bufferPos;
> +} SoundCapture;
> +
> +static int sound_capture_start_capture(struct CaptureThreadWorkerData *data);
> +static int ends_with(const char *str, const char *suffix);
> +static struct CaptureThreadWorkerData *capture_get_data(void);
> +static void write_delayed_audio_frames(void);
> +static void write_delayed_video_frames(void);
> +static int capture_stop(void);
> +static double write_audio_frame(AVFormatContext *oc, OutputStream *ost);
> +static void write_empty_sound(void *opaque, struct CaptureThreadWorkerData* data);

What's the point of these prototypes? 'static' means they are private functions
so they needn't be listed in the header.

> +
> +#endif
> -- 
> 2.7.4
> 
> 

Fam

  parent reply	other threads:[~2017-05-04 11:35 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-05-04 10:41 [Qemu-devel] [PATCH] Video and sound capture to a videofile through ffmpeg Alex K
2017-05-04 10:45 ` no-reply
2017-05-04 10:48 ` no-reply
2017-05-04 10:50 ` Marc-André Lureau
2017-05-04 10:51 ` Daniel P. Berrange
2017-05-04 11:35 ` Fam Zheng [this message]
2017-05-04 14:01 ` Gerd Hoffmann
  -- strict thread matches above, loose matches on Subject: below --
2017-04-23 14:10 Alex K
2017-04-23 14:16 ` no-reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20170504113522.GB6865@lemon.lan \
    --to=famz@redhat.com \
    --cc=aliguori@amazon.com \
    --cc=kraxel@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=vip-ak47@yandex.ru \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.