LibVNCServer/LibVNCClient
vnc2mpg.c
Go to the documentation of this file.
1 
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <math.h>
30 #include <signal.h>
31 #include <sys/time.h>
32 #include <libavcodec/avcodec.h>
33 #include <libavformat/avformat.h>
34 #include <libswscale/swscale.h>
35 #include <rfb/rfbclient.h>
36 
37 #define VNC_PIX_FMT AV_PIX_FMT_RGB565 /* pixel format generated by VNC client */
38 #define OUTPUT_PIX_FMT AV_PIX_FMT_YUV420P /* default pix_fmt */
39 
40 static int write_packet(AVFormatContext *oc, const AVRational *time_base, AVStream *st, AVPacket *pkt)
41 {
42  /* rescale output packet timestamp values from codec to stream timebase */
43  av_packet_rescale_ts(pkt, *time_base, st->time_base);
44  pkt->stream_index = st->index;
45  /* Write the compressed frame to the media file. */
46  return av_interleaved_write_frame(oc, pkt);
47 }
48 
49 /*************************************************/
50 /* video functions */
51 
52 /* a wrapper around a single output video stream */
53 typedef struct {
54  AVStream *st;
55  AVCodec *codec;
56  AVCodecContext *enc;
57  int64_t pts;
58  AVFrame *frame;
59  AVFrame *tmp_frame;
60  struct SwsContext *sws;
62 
63 /* Add an output video stream. */
64 int add_video_stream(VideoOutputStream *ost, AVFormatContext *oc,
65  enum AVCodecID codec_id, int64_t br, int sr, int w, int h)
66 {
67  int i;
68 
69  /* find the encoder */
70  ost->codec = avcodec_find_encoder(codec_id);
71  if (!(ost->codec)) {
72  fprintf(stderr, "Could not find encoder for '%s'\n",
73  avcodec_get_name(codec_id));
74  return -1;
75  } // no extra memory allocation from this call
76  if (ost->codec->type != AVMEDIA_TYPE_VIDEO) {
77  fprintf(stderr, "Encoder for '%s' does not seem to be for video.\n",
78  avcodec_get_name(codec_id));
79  return -2;
80  }
81  ost->enc = avcodec_alloc_context3(ost->codec);
82  if (!(ost->enc)) {
83  fprintf(stderr, "Could not alloc an encoding context\n");
84  return -3;
85  } // from now on need to call avcodec_free_context(&(ost->enc)) on error
86 
87  /* Set codec parameters */
88  ost->enc->codec_id = codec_id;
89  ost->enc->bit_rate = br;
90  /* Resolution must be a multiple of two (round up to avoid buffer overflow). */
91  ost->enc->width = w + (w % 2);
92  ost->enc->height = h + (h % 2);
93  /* timebase: This is the fundamental unit of time (in seconds) in terms
94  * of which frame timestamps are represented. For fixed-fps content,
95  * timebase should be 1/framerate and timestamp increments should be
96  * identical to 1. */
97  ost->enc->time_base = (AVRational){ 1, sr };
98  ost->enc->gop_size = 12; /* emit one intra frame every twelve frames at most */
99  ost->enc->pix_fmt = OUTPUT_PIX_FMT;
100  if (ost->enc->codec_id == AV_CODEC_ID_MPEG1VIDEO) {
101  /* Needed to avoid using macroblocks in which some coeffs overflow.
102  * This does not happen with normal video, it just happens here as
103  * the motion of the chroma plane does not match the luma plane. */
104  ost->enc->mb_decision = 2;
105  }
106 
107  ost->st = avformat_new_stream(oc, ost->codec);
108  if (!ost->st) {
109  fprintf(stderr, "Could not allocate stream\n");
110  avcodec_free_context(&(ost->enc));
111  return -4;
112  } // stream memory cleared up when oc is freed, so no need to do so later in this function on error
113  ost->st->id = oc->nb_streams-1;
114  ost->st->time_base = ost->enc->time_base;
115  ost->pts = 0;
116 
117  /* Some formats want stream headers to be separate. */
118  if (oc->oformat->flags & AVFMT_GLOBALHEADER)
119  ost->enc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
120 
121  // must wait to allocate frame buffers until codec is opened (in case codec changes the PIX_FMT)
122  return 0;
123 }
124 
125 AVFrame *alloc_picture(enum AVPixelFormat pix_fmt, int width, int height)
126 {
127  AVFrame *picture;
128  int ret;
129  picture = av_frame_alloc();
130  if (!picture)
131  return NULL;
132  // from now on need to call av_frame_free(&picture) on error
133  picture->format = pix_fmt;
134  picture->width = width;
135  picture->height = height;
136  /* allocate the buffers for the frame data */
137  ret = av_frame_get_buffer(picture, 64);
138  if (ret < 0) {
139  fprintf(stderr, "Could not allocate frame data.\n");
140  av_frame_free(&picture);
141  return NULL;
142  }
143  return picture;
144 } // use av_frame_free(&picture) to free memory from this call
145 
146 int open_video(AVFormatContext *oc, VideoOutputStream *ost)
147 {
148  int ret;
149  /* open the codec */
150  ret = avcodec_open2(ost->enc, ost->codec, NULL);
151  if (ret < 0) {
152  fprintf(stderr, "Could not open video codec: %s\n", av_err2str(ret));
153  return ret;
154  } // memory from this call freed when oc is freed, no need to do it on error in this call
155  /* copy the stream parameters to the muxer */
156  ret = avcodec_parameters_from_context(ost->st->codecpar, ost->enc);
157  if (ret < 0) {
158  fprintf(stderr, "Could not copy the stream parameters.\n");
159  return ret;
160  } // memory from this call is freed when oc (parent of ost->st) is freed, no need to do it on error in this call
161  /* allocate and init a re-usable frame */
162  ost->frame = alloc_picture(ost->enc->pix_fmt, ost->enc->width, ost->enc->height);
163  if (!(ost->frame)) {
164  fprintf(stderr, "Could not allocate video frame\n");
165  return -1;
166  } // from now on need to call av_frame_free(&(ost->frame)) on error
167  /* If the output format is not the same as the VNC format, then a temporary VNC format
168  * picture is needed too. It is then converted to the required
169  * output format. */
170  ost->tmp_frame = NULL;
171  ost->sws = NULL;
172  if (ost->enc->pix_fmt != VNC_PIX_FMT) {
173  ost->tmp_frame = alloc_picture(VNC_PIX_FMT, ost->enc->width, ost->enc->height);
174  if (!(ost->tmp_frame)) {
175  fprintf(stderr, "Could not allocate temporary picture\n");
176  av_frame_free(&(ost->frame));
177  return -2;
178  } // from now on need to call av_frame_free(&(ost->tmp_frame)) on error
179  ost->sws = sws_getCachedContext(ost->sws, ost->enc->width, ost->enc->height, VNC_PIX_FMT, ost->enc->width, ost->enc->height, ost->enc->pix_fmt, 0, NULL, NULL, NULL);
180  if (!(ost->sws)) {
181  fprintf(stderr, "Could not get sws context\n");
182  av_frame_free(&(ost->frame));
183  av_frame_free(&(ost->tmp_frame));
184  return -3;
185  } // from now on need to call sws_freeContext(ost->sws); ost->sws = NULL; on error
186  }
187 
188  return 0;
189 }
190 
191 /*
192  * encode current video frame and send it to the muxer
193  * return 0 on success, negative on error
194  */
195 int write_video_frame(AVFormatContext *oc, VideoOutputStream *ost, int64_t pts)
196 {
197  int ret, ret2;
198  AVPacket pkt = { 0 };
199  if (pts <= ost->pts) return 0; // nothing to do
200  /* convert format if needed */
201  if (ost->tmp_frame) {
202  sws_scale(ost->sws, (const uint8_t * const *)ost->tmp_frame->data,
203  ost->tmp_frame->linesize, 0, ost->enc->height, ost->frame->data, ost->frame->linesize);
204  }
205 
206  /* send the imager to encoder */
207  ost->pts = pts;
208  ost->frame->pts = ost->pts;
209  ret = avcodec_send_frame(ost->enc, ost->frame);
210  if (ret < 0) {
211  fprintf(stderr, "Error sending video frame to encoder: %s\n", av_err2str(ret));
212  return ret;
213  }
214  /* read all available packets */
215  ret2 = 0;
216  for (ret = avcodec_receive_packet(ost->enc, &pkt); ret == 0; ret = avcodec_receive_packet(ost->enc, &pkt)) {
217  ret2 = write_packet(oc, &(ost->enc->time_base), ost->st, &pkt);
218  if (ret2 < 0) {
219  fprintf(stderr, "Error while writing video frame: %s\n", av_err2str(ret2));
220  /* continue on this error to not gum up encoder */
221  }
222  }
223  if (ret2 < 0) return ret2;
224  if (!(ret == AVERROR(EAGAIN))) return ret; // if AVERROR(EAGAIN), means all available packets output, need more frames (i.e. success)
225  return 0;
226 }
227 
228 /*
229  * Write final video frame (i.e. drain codec).
230  */
231 int write_final_video_frame(AVFormatContext *oc, VideoOutputStream *ost)
232 {
233  int ret, ret2;
234  AVPacket pkt = { 0 };
235 
236  /* send NULL image to encoder */
237  ret = avcodec_send_frame(ost->enc, NULL);
238  if (ret < 0) {
239  fprintf(stderr, "Error sending final video frame to encoder: %s\n", av_err2str(ret));
240  return ret;
241  }
242  /* read all available packets */
243  ret2 = 0;
244  for (ret = avcodec_receive_packet(ost->enc, &pkt); ret == 0; ret = avcodec_receive_packet(ost->enc, &pkt)) {
245  ret2 = write_packet(oc, &(ost->enc->time_base), ost->st, &pkt);
246  if (ret2 < 0) {
247  fprintf(stderr, "Error while writing final video frame: %s\n", av_err2str(ret2));
248  /* continue on this error to not gum up encoder */
249  }
250  }
251  if (ret2 < 0) return ret2;
252  if (!(ret == AVERROR(EOF))) return ret;
253  return 0;
254 }
255 
257 {
258  avcodec_free_context(&(ost->enc));
259  av_frame_free(&(ost->frame));
260  av_frame_free(&(ost->tmp_frame));
261  sws_freeContext(ost->sws); ost->sws = NULL;
262  ost->codec = NULL; /* codec not an allocated item */
263  ost->st = NULL; /* freeing parent oc will free this memory */
264 }
265 
266 /**************************************************************/
267 /* Output movie handling */
268 AVFormatContext *movie_open(char *filename, VideoOutputStream *video_st, int br, int fr, int w, int h) {
269  int ret;
270  AVFormatContext *oc;
271 
272  /* allocate the output media context. */
273  ret = avformat_alloc_output_context2(&oc, NULL, NULL, filename);
274  if (ret < 0) {
275  fprintf(stderr, "Warning: Could not deduce output format from file extension: using MP4.\n");
276  ret = avformat_alloc_output_context2(&oc, NULL, "mp4", filename);
277  }
278  if (ret < 0) {
279  fprintf(stderr, "Error: Could not allocate media context: %s.\n", av_err2str(ret));
280  return NULL;
281  } // from now on, need to call avformat_free_context(oc); oc=NULL; to free memory on error
282 
283  /* Add the video stream using the default format codec and initialize the codec. */
284  if (oc->oformat->video_codec != AV_CODEC_ID_NONE) {
285  ret = add_video_stream(video_st, oc, oc->oformat->video_codec, br, fr, w, h);
286  } else {
287  ret = -1;
288  }
289  if (ret < 0) {
290  fprintf(stderr, "Error: chosen output format does not have a video codec, or error %i\n", ret);
291  avformat_free_context(oc); oc = NULL;
292  return NULL;
293  } // from now on, need to call close_video_stream(video_st) to free memory on error
294 
295  /* Now that all the parameters are set, we can open the codecs and allocate the necessary encode buffers. */
296  ret = open_video(oc, video_st);
297  if (ret < 0) {
298  fprintf(stderr, "Error: error opening video codec, error %i\n", ret);
300  avformat_free_context(oc); oc = NULL;
301  return NULL;
302  } // no additional calls required to free memory, as close_video_stream(video_st) will do it
303 
304  /* open the output file, if needed */
305  if (!(oc->oformat->flags & AVFMT_NOFILE)) {
306  ret = avio_open(&oc->pb, filename, AVIO_FLAG_WRITE);
307  if (ret < 0) {
308  fprintf(stderr, "Could not open '%s': %s\n", filename,
309  av_err2str(ret));
311  avformat_free_context(oc); oc = NULL;
312  return NULL;
313  }
314  } // will need to call avio_closep(&oc->pb) to free file handle on error
315 
316  /* Write the stream header, if any. */
317  ret = avformat_write_header(oc, NULL);
318  if (ret < 0) {
319  fprintf(stderr, "Error occurred when writing to output file: %s\n",
320  av_err2str(ret));
321  if (!(oc->oformat->flags & AVFMT_NOFILE))
322  avio_closep(&oc->pb);
324  avformat_free_context(oc); oc = NULL;
325  } // no additional items to free
326 
327  return oc;
328 }
329 
330 void movie_close(AVFormatContext **ocp, VideoOutputStream *video_st) {
331  AVFormatContext *oc = *ocp;
332  /* Write the trailer, if any. The trailer must be written before you
333  * close the CodecContexts open when you wrote the header; otherwise
334  * av_write_trailer() may try to use memory that was freed on
335  * av_codec_close(). */
336  if (oc) {
337  if (video_st)
339 
340  av_write_trailer(oc);
341 
342  /* Close the video codec. */
344 
345  if (!(oc->oformat->flags & AVFMT_NOFILE))
346  /* Close the output file. */
347  avio_closep(&oc->pb);
348 
349  /* free the stream */
350  avformat_free_context(oc);
351  ocp = NULL;
352  }
353 }
354 
355 /**************************************************************/
356 /* VNC globals */
360 char *filename = NULL;
361 AVFormatContext *oc = NULL;
362 int bitrate = 1000000;
363 int framerate = 5;
364 long max_time = 0;
365 struct timespec start_time, cur_time;
366 
367 /* Signal handling */
368 void signal_handler(int signal) {
369  quit=TRUE;
370 }
371 
372 /* returns time since start in pts units */
373 int64_t time_to_pts(int framerate, struct timespec *start_time, struct timespec *cur_time) {
374  time_t ds = cur_time->tv_sec - start_time->tv_sec;
375  long dns = cur_time->tv_nsec - start_time->tv_nsec;
376  /* use usecs */
377  int64_t dt = (int64_t)ds*(int64_t)1000000+(int64_t)dns/(int64_t)1000;
378  /* compute rv in units of frame number (rounding to nearest, not truncating) */
379  int64_t rv = (((int64_t)framerate)*dt + (int64_t)500000) / (int64_t)(1000000);
380 
381  return rv;
382 }
383 
384 /* VNC callback functions */
386  movie_close(&oc, &video_st);
388  if (!oc)
389  return FALSE;
390  signal(SIGINT,signal_handler);
391  signal(SIGTERM,signal_handler);
392  #ifdef SIGQUIT
393  signal(SIGQUIT,signal_handler);
394  #endif
395  signal(SIGABRT,signal_handler);
396  /* These assignments assumes the AVFrame buffer is contigous. This is true in current ffmpeg versions for
397  * most non-HW accelerated bits, but may not be true globally. */
398  if(video_st.tmp_frame)
400  else
401  client->frameBuffer=video_st.frame->data[0];
402  return TRUE;
403 }
404 
405 void vnc_update(rfbClient* client,int x,int y,int w,int h) {
406 }
407 
408 /**************************************************************/
409 /* media file output */
410 int main(int argc, char **argv)
411 {
412  int i,j;
413 
414  /* Initialize vnc client structure (don't connect yet). */
415  client = rfbGetClient(5,3,2);
419 
420  /* Initialize libavcodec, and register all codecs and formats. */
421 #if LIBAVUTIL_VERSION_MAJOR < 56 /* deprecrated in FFMPEG 4.0 */
422  av_register_all();
423 #endif
424 
425  /* Parse command line. */
426  for(i=1;i<argc;i++) {
427  j=i;
428  if(argc>i+1 && !strcmp("-o",argv[i])) {
429  filename=argv[i+1];
430  j+=2;
431  } else if(argc>i+1 && !strcmp("-t",argv[i])) {
432  max_time=atol(argv[i+1]);
433  if (max_time < 10 || max_time > 100000000) {
434  fprintf(stderr, "Warning: Nonsensical time-per-file %li, resetting to default.\n", max_time);
435  max_time = 0;
436  }
437  j+=2;
438  }
439  /* This is so that argc/argv are ready for passing to rfbInitClient */
440  if(j>i) {
441  argc-=j-i;
442  memmove(argv+i,argv+j,(argc-i)*sizeof(char*));
443  i--;
444  }
445  }
446 
447  /* default filename. */
448  if (!filename) {
449  fprintf(stderr, "Warning: No filename specified. Using output.mp4\n");
450  filename = "output.mp4";
451  }
452 
453  /* open VNC connection. */
456  if(!rfbInitClient(client,&argc,argv)) {
457  printf("usage: %s [-o output_file] [-t seconds-per-file] server:port\n", argv[0]);
458  return 1;
459  }
460 
461  /* main loop */
462  clock_gettime(CLOCK_MONOTONIC, &start_time);
463  while(!quit) {
464  int i=WaitForMessage(client,10000/framerate); /* useful for timeout to be no more than 10 msec per second (=10000/framerate usec) */
465  if (i>0) {
467  quit=TRUE;
468  } else if (i<0) {
469  quit=TRUE;
470  }
471  if (!quit) {
472  clock_gettime(CLOCK_MONOTONIC, &cur_time);
474  if ((cur_time.tv_sec - start_time.tv_sec) > max_time && max_time > 0) {
475  quit = TRUE;
476  }
477  }
478  }
480  return 0;
481 }
int y
Definition: SDLvncviewer.c:34
int x
Definition: SDLvncviewer.c:34
rfbClient * rfbGetClient(int bitsPerSample, int samplesPerPixel, int bytesPerPixel)
Allocates and returns a pointer to an rfbClient structure.
int WaitForMessage(rfbClient *client, unsigned int usecs)
Waits for an RFB message to arrive from the server.
rfbBool HandleRFBServerMessage(rfbClient *client)
Handles messages from the RFB server.
rfbBool rfbInitClient(rfbClient *client, int *argc, char **argv)
Initializes the client.
int8_t rfbBool
Definition: rfbproto.h:108
#define TRUE
Definition: rfbproto.h:112
#define FALSE
Definition: rfbproto.h:110
AVFrame * tmp_frame
Definition: vnc2mpg.c:59
struct SwsContext * sws
Definition: vnc2mpg.c:60
AVCodec * codec
Definition: vnc2mpg.c:55
int64_t pts
Definition: vnc2mpg.c:57
AVStream * st
Definition: vnc2mpg.c:54
AVCodecContext * enc
Definition: vnc2mpg.c:56
AVFrame * frame
Definition: vnc2mpg.c:58
int width
Definition: rfbclient.h:243
GotFrameBufferUpdateProc GotFrameBufferUpdate
Definition: rfbclient.h:354
uint8_t * frameBuffer
Definition: rfbclient.h:242
int height
Definition: rfbclient.h:243
MallocFrameBufferProc MallocFrameBuffer
Definition: rfbclient.h:357
rfbPixelFormat format
Definition: rfbclient.h:273
uint16_t redMax
Definition: rfbproto.h:180
uint16_t greenMax
Definition: rfbproto.h:184
uint16_t blueMax
Definition: rfbproto.h:186
uint8_t greenShift
Definition: rfbproto.h:200
uint8_t blueShift
Definition: rfbproto.h:202
uint8_t redShift
Definition: rfbproto.h:188
int write_video_frame(AVFormatContext *oc, VideoOutputStream *ost, int64_t pts)
Definition: vnc2mpg.c:195
int framerate
Definition: vnc2mpg.c:363
long max_time
Definition: vnc2mpg.c:364
VideoOutputStream video_st
Definition: vnc2mpg.c:357
AVFormatContext * oc
Definition: vnc2mpg.c:361
void signal_handler(int signal)
Definition: vnc2mpg.c:368
int main(int argc, char **argv)
Definition: vnc2mpg.c:410
#define OUTPUT_PIX_FMT
Definition: vnc2mpg.c:38
AVFrame * alloc_picture(enum AVPixelFormat pix_fmt, int width, int height)
Definition: vnc2mpg.c:125
struct timespec start_time cur_time
Definition: vnc2mpg.c:365
void movie_close(AVFormatContext **ocp, VideoOutputStream *video_st)
Definition: vnc2mpg.c:330
rfbBool quit
Definition: vnc2mpg.c:359
int open_video(AVFormatContext *oc, VideoOutputStream *ost)
Definition: vnc2mpg.c:146
AVFormatContext * movie_open(char *filename, VideoOutputStream *video_st, int br, int fr, int w, int h)
Definition: vnc2mpg.c:268
#define VNC_PIX_FMT
Definition: vnc2mpg.c:37
int64_t time_to_pts(int framerate, struct timespec *start_time, struct timespec *cur_time)
Definition: vnc2mpg.c:373
int write_final_video_frame(AVFormatContext *oc, VideoOutputStream *ost)
Definition: vnc2mpg.c:231
int bitrate
Definition: vnc2mpg.c:362
int add_video_stream(VideoOutputStream *ost, AVFormatContext *oc, enum AVCodecID codec_id, int64_t br, int sr, int w, int h)
Definition: vnc2mpg.c:64
rfbClient * client
Definition: vnc2mpg.c:358
void vnc_update(rfbClient *client, int x, int y, int w, int h)
Definition: vnc2mpg.c:405
char * filename
Definition: vnc2mpg.c:360
rfbBool vnc_malloc_fb(rfbClient *client)
Definition: vnc2mpg.c:385
void close_video_stream(VideoOutputStream *ost)
Definition: vnc2mpg.c:256
#define height
Definition: vncev.c:19
#define width
Definition: vncev.c:18