From 00901a2eb9ec569157fad8a531a1de037ac0e2de Mon Sep 17 00:00:00 2001 From: dscho Date: Mon, 7 Jun 2004 17:47:54 +0000 Subject: add vnc2mpg, a program which makes a movie from a VNC desktop using FFMPEG --- client_examples/Makefile.am | 13 +- client_examples/vnc2mpg.c | 426 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 client_examples/vnc2mpg.c (limited to 'client_examples') diff --git a/client_examples/Makefile.am b/client_examples/Makefile.am index d2a3e29..83750ab 100644 --- a/client_examples/Makefile.am +++ b/client_examples/Makefile.am @@ -1,6 +1,15 @@ DEFINES=-I.. -g -Wall LDADD = ../libvncclient/libvncclient.a +if WITH_FFMPEG +FFMPEG_HOME=@with_ffmpeg@ + +vnc2mpg_CFLAGS=-I$(FFMPEG_HOME)/libavformat -I$(FFMPEG_HOME)/libavcodec +vnc2mpg_LDADD=$(LDADD) $(FFMPEG_HOME)/libavformat/libavformat.a $(FFMPEG_HOME)/libavcodec/libavcodec.a -lm + +FFMPEG_CLIENT=vnc2mpg +endif + if HAVE_LIBSDL SDLVIEWER=SDLvncviewer @@ -9,5 +18,7 @@ SDLvncviewer_CFLAGS=`sdl-config --cflags` SDLvncviewer_LDFLAGS=`sdl-config --libs` endif -noinst_PROGRAMS=ppmtest $(SDLVIEWER) +noinst_PROGRAMS=ppmtest $(SDLVIEWER) $(FFMPEG_CLIENT) + + diff --git a/client_examples/vnc2mpg.c b/client_examples/vnc2mpg.c new file mode 100644 index 0000000..d1deb9b --- /dev/null +++ b/client_examples/vnc2mpg.c @@ -0,0 +1,426 @@ +/* + * Simple movie writer for vnc; based on Libavformat API example from FFMPEG + * + * Copyright (c) 2003 Fabrice Bellard, 2004 Johannes E. Schindelin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include +#include +#include + +#ifndef M_PI +#define M_PI 3.1415926535897931 +#endif + +#include "avformat.h" +#include + +/* 5 seconds stream duration */ +//#define STREAM_DURATION 5.0 +#define STREAM_FRAME_RATE 25 /* 25 images/s */ +//#define STREAM_NB_FRAMES ((int)(STREAM_DURATION * STREAM_FRAME_RATE)) + +/**************************************************************/ +/* video output */ + +AVFrame *picture, *tmp_picture; +uint8_t *video_outbuf; +int frame_count, video_outbuf_size; + +/* add a video output stream */ +AVStream *add_video_stream(AVFormatContext *oc, int codec_id, int w, int h) +{ + AVCodecContext *c; + AVStream *st; + + st = av_new_stream(oc, 0); + if (!st) { + fprintf(stderr, "Could not alloc stream\n"); + exit(1); + } + + c = &st->codec; + c->codec_id = codec_id; + c->codec_type = CODEC_TYPE_VIDEO; + + /* put sample parameters */ + c->bit_rate = 400000; + /* resolution must be a multiple of two */ + c->width = w; + c->height = h; + /* frames per second */ + c->frame_rate = STREAM_FRAME_RATE; + c->frame_rate_base = 1; + c->gop_size = 12; /* emit one intra frame every twelve frames at most */ + if (c->codec_id == CODEC_ID_MPEG2VIDEO) { + /* just for testing, we also add B frames */ + c->max_b_frames = 2; + } + if (c->codec_id == CODEC_ID_MPEG1VIDEO){ + /* needed to avoid using macroblocks in which some coeffs overflow + this doesnt happen with normal video, it just happens here as the + motion of the chroma plane doesnt match the luma plane */ + c->mb_decision=2; + } + // some formats want stream headers to be seperate + if(!strcmp(oc->oformat->name, "mp4") || !strcmp(oc->oformat->name, "mov") || !strcmp(oc->oformat->name, "3gp")) + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + + return st; +} + +AVFrame *alloc_picture(int pix_fmt, int width, int height) +{ + AVFrame *picture; + uint8_t *picture_buf; + int size; + + picture = avcodec_alloc_frame(); + if (!picture) + return NULL; + size = avpicture_get_size(pix_fmt, width, height); + picture_buf = malloc(size); + if (!picture_buf) { + av_free(picture); + return NULL; + } + avpicture_fill((AVPicture *)picture, picture_buf, + pix_fmt, width, height); + return picture; +} + +void open_video(AVFormatContext *oc, AVStream *st) +{ + AVCodec *codec; + AVCodecContext *c; + + c = &st->codec; + + /* find the video encoder */ + codec = avcodec_find_encoder(c->codec_id); + if (!codec) { + fprintf(stderr, "codec not found\n"); + exit(1); + } + + /* open the codec */ + if (avcodec_open(c, codec) < 0) { + fprintf(stderr, "could not open codec\n"); + exit(1); + } + + video_outbuf = NULL; + if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) { + /* allocate output buffer */ + /* XXX: API change will be done */ + video_outbuf_size = 200000; + video_outbuf = malloc(video_outbuf_size); + } + + /* allocate the encoded raw picture */ + picture = alloc_picture(c->pix_fmt, c->width, c->height); + if (!picture) { + fprintf(stderr, "Could not allocate picture\n"); + exit(1); + } + + /* if the output format is not RGB565, then a temporary RGB565 + picture is needed too. It is then converted to the required + output format */ + tmp_picture = NULL; + if (c->pix_fmt != PIX_FMT_RGB565) { + tmp_picture = alloc_picture(PIX_FMT_RGB565, c->width, c->height); + if (!tmp_picture) { + fprintf(stderr, "Could not allocate temporary picture\n"); + exit(1); + } + } +} + +void write_video_frame(AVFormatContext *oc, AVStream *st) +{ + int out_size, ret; + AVCodecContext *c; + AVFrame *picture_ptr; + + c = &st->codec; + + if (c->pix_fmt != PIX_FMT_RGB565) { + /* as we only generate a RGB565 picture, we must convert it + to the codec pixel format if needed */ + // TODO + //fill_yuv_image(tmp_picture, frame_count, c->width, c->height); + img_convert((AVPicture *)picture, c->pix_fmt, + (AVPicture *)tmp_picture, PIX_FMT_RGB565, + c->width, c->height); + } else { + // TODO: fill_yuv_image(picture, frame_count, c->width, c->height); + } + picture_ptr = picture; + + + if (oc->oformat->flags & AVFMT_RAWPICTURE) { + /* raw video case. The API will change slightly in the near + futur for that */ + AVPacket pkt; + av_init_packet(&pkt); + + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index= st->index; + pkt.data= (uint8_t *)picture_ptr; + pkt.size= sizeof(AVPicture); + + ret = av_write_frame(oc, &pkt); + } else { + /* encode the image */ + out_size = avcodec_encode_video(c, video_outbuf, video_outbuf_size, picture_ptr); + /* if zero size, it means the image was buffered */ + if (out_size != 0) { + AVPacket pkt; + av_init_packet(&pkt); + + pkt.pts= c->coded_frame->pts; + if(c->coded_frame->key_frame) + pkt.flags |= PKT_FLAG_KEY; + pkt.stream_index= st->index; + pkt.data= video_outbuf; + pkt.size= out_size; + + /* write the compressed frame in the media file */ + ret = av_write_frame(oc, &pkt); + } else { + ret = 0; + } + } + if (ret != 0) { + fprintf(stderr, "Error while writing video frame\n"); + exit(1); + } + frame_count++; +} + +void close_video(AVFormatContext *oc, AVStream *st) +{ + avcodec_close(&st->codec); + av_free(picture->data[0]); + av_free(picture); + if (tmp_picture) { + av_free(tmp_picture->data[0]); + av_free(tmp_picture); + } + av_free(video_outbuf); +} + +static const char *filename; +static AVOutputFormat *fmt; +static AVFormatContext *oc; +static AVStream *video_st; +static double video_pts; + +static int movie_open(int w, int h) { + if (fmt->video_codec != CODEC_ID_NONE) { + video_st = add_video_stream(oc, fmt->video_codec, w, h); + } else + return 1; + + /* set the output parameters (must be done even if no + parameters). */ + if (av_set_parameters(oc, NULL) < 0) { + fprintf(stderr, "Invalid output format parameters\n"); + return 2; + } + + dump_format(oc, 0, filename, 1); + + /* now that all the parameters are set, we can open the audio and + video codecs and allocate the necessary encode buffers */ + if (video_st) + open_video(oc, video_st); + + /* open the output file, if needed */ + if (!(fmt->flags & AVFMT_NOFILE)) { + if (url_fopen(&oc->pb, filename, URL_WRONLY) < 0) { + fprintf(stderr, "Could not open '%s'\n", filename); + return 3; + } + } + + /* write the stream header, if any */ + av_write_header(oc); + + return 0; +} + +static int movie_close() { + int i; + + /* close each codec */ + close_video(oc, video_st); + + /* write the trailer, if any */ + av_write_trailer(oc); + + /* free the streams */ + for(i = 0; i < oc->nb_streams; i++) { + av_freep(&oc->streams[i]); + } + + if (!(fmt->flags & AVFMT_NOFILE)) { + /* close the output file */ + url_fclose(&oc->pb); + } + + /* free the stream */ + av_free(oc); + +} + +static rfbBool quit=FALSE; +static void signal_handler(int signal) { + fprintf(stderr,"Cleaning up.\n"); + quit=TRUE; +} + +/**************************************************************/ +/* VNC callback functions */ +static rfbBool resize(rfbClient* client) { + static rfbBool first=TRUE; + if(!first) { + movie_close(); + perror("I don't know yet how to change resolutions!\n"); + } + movie_open(client->width, client->height); + signal(SIGINT,signal_handler); + if(tmp_picture) + client->frameBuffer=tmp_picture->data[0]; + else + client->frameBuffer=picture->data[0]; + return TRUE; +} + +static void update(rfbClient* client,int x,int y,int w,int h) { +} + +/**************************************************************/ +/* media file output */ + +int main(int argc, char **argv) +{ + time_t stop=0; + rfbClient* client; + int i; + + /* get a vnc client structure (don't connect yet). */ + client = rfbGetClient(5,3,2); + client->format.redShift=11; client->format.redMax=31; + client->format.greenShift=5; client->format.greenMax=63; + client->format.blueShift=0; client->format.blueMax=31; + + /* initialize libavcodec, and register all codecs and formats */ + av_register_all(); + + if(!strncmp(argv[argc-1],":",1) || + !strncmp(argv[argc-1],"127.0.0.1",9) || + !strncmp(argv[argc-1],"localhost",9)) + client->appData.encodingsString="raw"; + + i=1; + filename=0; + while(ii+1 && !strcmp("-o",argv[i])) { + filename=argv[2]; + i+=2; + } else if(argc>i+1 && !strcmp("-t",argv[i])) { + stop=time(0)+atoi(argv[i+1]); + i+=2; + } else if(argc>i+1 && !strcmp("-encodings",argv[i])) { + client->appData.encodingsString=argv[i+1]; + i+=2; + } else + break; + } + + if (argc != i+1) { + printf("usage: %s [-o output_file] [-t seconds] server:port\n" + "Shoot a movie from a VNC server.\n", argv[0]); + exit(1); + } + + /* auto detect the output format from the name. default is + mpeg. */ + fmt = filename?guess_format(NULL, filename, NULL):0; + if (!fmt) { + printf("Could not deduce output format from file extension: using MPEG.\n"); + fmt = guess_format("mpeg", NULL, NULL); + } + if (!fmt) { + fprintf(stderr, "Could not find suitable output format\n"); + exit(1); + } + + /* allocate the output media context */ + oc = av_alloc_format_context(); + if (!oc) { + fprintf(stderr, "Memory error\n"); + exit(1); + } + oc->oformat = fmt; + snprintf(oc->filename, sizeof(oc->filename), "%s", filename); + + /* add the audio and video streams using the default format codecs + and initialize the codecs */ + video_st = NULL; + + /* purge the arguments not concerning VNC */ + argv[1]=argv[i]; + argc=2; + client->MallocFrameBuffer=resize; + client->GotFrameBufferUpdate=update; + if(!rfbInitClient(client,&argc,argv)) { + fprintf(stderr,"Could not connect to server!\n"); + return 1; + } + + /* main loop */ + + while(!quit) { + int i=WaitForMessage(client,2000); + if(i<0) { + movie_close(); + return 0; + } + if(i) + HandleRFBServerMessage(client); + else { + /* compute current audio and video time */ + video_pts = (double)video_st->pts.val * video_st->time_base.num / video_st->time_base.den; + + /* write interleaved audio and video frames */ + write_video_frame(oc, video_st); + } + if(stop!=0 && stop