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
40static 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 */
53typedef 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. */
64int 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
125AVFrame *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
146int 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 */
195int 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 */
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 */
268AVFormatContext *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
330void 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 */
360char *filename = NULL;
361AVFormatContext *oc = NULL;
362int bitrate = 1000000;
363int framerate = 5;
364long max_time = 0;
365struct timespec start_time, cur_time;
366
367/* Signal handling */
368void signal_handler(int signal) {
369 quit=TRUE;
370}
371
372/* returns time since start in pts units */
373int64_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 */
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. */
400 else
402 return TRUE;
403}
404
405void vnc_update(rfbClient* client,int x,int y,int w,int h) {
406}
407
408/**************************************************************/
409/* media file output */
410int 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
int WaitForMessage(rfbClient *client, unsigned int usecs)
Waits for an RFB message to arrive from the server.
rfbClient * rfbGetClient(int bitsPerSample, int samplesPerPixel, int bytesPerPixel)
Allocates and returns a pointer to an rfbClient structure.
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:244
GotFrameBufferUpdateProc GotFrameBufferUpdate
Definition: rfbclient.h:355
uint8_t * frameBuffer
Definition: rfbclient.h:243
int height
Definition: rfbclient.h:244
MallocFrameBufferProc MallocFrameBuffer
Definition: rfbclient.h:358
rfbPixelFormat format
Definition: rfbclient.h:274
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
AVFormatContext * movie_open(char *filename, VideoOutputStream *video_st, int br, int fr, int w, int h)
Definition: vnc2mpg.c:268
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
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
#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
AVFrame * alloc_picture(enum AVPixelFormat pix_fmt, int width, int height)
Definition: vnc2mpg.c:125
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