/*! \file gsd_img_mpeg.c \brief OGSF library - FFMPEG stuff GRASS OpenGL gsurf OGSF Library (C) 1999-2008, 2012 by the GRASS Development Team This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. \author Bill Brown USACERL, GMSL/University of Illinois \author Doxygenized (May 2008) and update for FFMPEG >= 0.7 (November 2012) by Martin Landa */ #include #include #include #include #include /* FFMPEG stuff */ #ifdef HAVE_FFMPEG #include #include #if LIBAVUTIL_VERSION_MAJOR < 51 #include #else /* libavutil 51.22.1's avutil.h doesn't include libavutil/mathematics.h */ #include #endif /* 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)) #define STREAM_PIX_FMT PIX_FMT_YUV420P /* default pix_fmt */ AVFrame *picture, *tmp_picture; uint8_t *video_outbuf; int frame_count, video_outbuf_size; AVOutputFormat *fmt; AVFormatContext *oc; AVStream *video_st; /*! \brief Add a video output stream \param oc \param codec_id \param w \param h \return */ static AVStream *add_video_stream(AVFormatContext * oc, int codec_id, int w, int h) { AVCodecContext *c; AVStream *st; #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52, 112, 0) st = av_new_stream(oc, 0); #else st = avformat_new_stream(oc, NULL); #endif if (!st) { G_warning(_("Unable to allocate stream")); return NULL; } c = st->codec; c->codec_id = codec_id; #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(52, 123, 0) c->codec_type = CODEC_TYPE_VIDEO; #else c->codec_type = AVMEDIA_TYPE_VIDEO; #endif /* put sample parameters */ c->bit_rate = 400000; /* resolution must be a multiple of two */ c->width = w; c->height = h; /* time base: 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 identically 1. */ c->time_base.den = STREAM_FRAME_RATE; c->time_base.num = 1; c->gop_size = 12; /* emit one intra frame every twelve frames at most */ c->pix_fmt = STREAM_PIX_FMT; 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 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; } /* some formats want stream headers to be separate */ if (!strcmp(oc->oformat->name, "mp4") || !strcmp(oc->oformat->name, "mov") || !strcmp(oc->oformat->name, "3gp")) c->flags |= CODEC_FLAG_GLOBAL_HEADER; c->flags |= CODEC_FLAG_QSCALE; /* Quality, as it has been removed from AVCodecContext and put in AVVideoFrame. */ #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52, 100, 0) c->global_quality = st->quality = FF_QP2LAMBDA * 10; #else c->global_quality = FF_QP2LAMBDA * 10; #endif return st; } /*! \brief Allocate picture \param pix_fmt \param width picture width \param height picture height \return pointer to AVFrame struct \return NULL on failure */ static 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 = av_malloc(size); if (!picture_buf) { av_free(picture); return NULL; } avpicture_fill((AVPicture *) picture, picture_buf, pix_fmt, width, height); return picture; } /*! \brief Open video \param oc \param st */ static 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) { G_warning(_("Video codec not found")); return; } /* open the codec */ #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52, 100, 0) if (avcodec_open(c, codec) < 0) { #else if (avcodec_open2(c, codec, NULL) < 0) { #endif G_warning(_("Unable to open codec")); return; } video_outbuf = NULL; if (!(oc->oformat->flags & AVFMT_RAWPICTURE)) { /* allocate output buffer */ /* XXX: API change will be done */ /* buffers passed into lav* can be allocated any way you prefer, as long as they're aligned enough for the architecture, and they're freed appropriately (such as using av_free for buffers allocated with av_malloc) */ video_outbuf_size = 200000; video_outbuf = av_malloc(video_outbuf_size); } /* allocate the encoded raw picture */ picture = alloc_picture(c->pix_fmt, c->width, c->height); if (!picture) { G_warning(_("Unable to allocate picture")); return; } /* if the output format is not YUV420P, then a temporary YUV420P picture is needed too. It is then converted to the required output format */ tmp_picture = NULL; if (c->pix_fmt != PIX_FMT_YUV420P) { tmp_picture = alloc_picture(PIX_FMT_YUV420P, c->width, c->height); if (!tmp_picture) { G_warning(_("Unable to allocate temporary picture")); return; } } } /*! \brief Write video frame \param oc \param st */ static void write_video_frame(AVFormatContext * oc, AVStream * st) { int out_size, ret; AVCodecContext *c; c = st->codec; if (oc->oformat->flags & AVFMT_RAWPICTURE) { /* raw video case. The API will change slightly in the near future for that */ AVPacket pkt; av_init_packet(&pkt); #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52, 32, 0) pkt.flags |= PKT_FLAG_KEY; #else pkt.flags |= AV_PKT_FLAG_KEY; #endif pkt.stream_index = st->index; pkt.data = (uint8_t *) picture; 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); /* if zero size, it means the image was buffered */ if (out_size > 0) { AVPacket pkt; av_init_packet(&pkt); pkt.pts = av_rescale_q(c->coded_frame->pts, c->time_base, st->time_base); if (c->coded_frame->key_frame) #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52, 32, 0) pkt.flags |= PKT_FLAG_KEY; #else pkt.flags |= AV_PKT_FLAG_KEY; #endif 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) { G_warning(_("Error while writing video frame")); return; } frame_count++; } /*! \brief Close video \param oc [unused] \param st */ static void close_video(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); } #endif /*! \brief Initialize FAME setup mpeg defaults and open file for writing \param filename file name \return -1 on failure \return 0 on success */ int gsd_init_mpeg(const char *filename) { #ifdef HAVE_FFMPEG GLuint l, r, b, t; GLint tmp[4]; glGetIntegerv(GL_VIEWPORT, tmp); l = tmp[0]; r = tmp[0] + tmp[2] - 1; b = tmp[1]; t = tmp[1] + tmp[3] - 1; G_verbose_message(_("Opening MPEG stream <%s>..."), filename); /* initialize libavcodec, and register all codecs and formats */ av_register_all(); /* auto detect the output format from the name. default is mpeg. */ #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52, 32, 0) fmt = guess_format(NULL, filename, NULL); #else fmt = av_guess_format(NULL, filename, NULL); #endif if (!fmt) { G_warning(_("Unable to deduce output format from file extension: using MPEG")); #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52, 32, 0) fmt = guess_format("mpeg", NULL, NULL); #else fmt = av_guess_format("mpeg", NULL, NULL); #endif } if (!fmt) { G_warning(_("Unable to find suitable output format")); return (-1); } /* allocate the output media context */ oc = avformat_alloc_context(); if (!oc) { G_warning(_("Out of memory")); return (-1); } oc->oformat = fmt; snprintf(oc->filename, sizeof(oc->filename), "%s", filename); /* if you want to hardcode the codec (eg #ifdef USE_XVID) this may be the place to do it (?????) */ #ifdef USE_XVID fmt->video_codec = CODEC_ID_XVID; #endif video_st = NULL; if (fmt->video_codec != CODEC_ID_NONE) { video_st = add_video_stream(oc, fmt->video_codec, (r - l + 1), (t - b + 1)); } /* set the output parameters (must be done even if no parameters). */ #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52, 100, 0) if (av_set_parameters(oc, NULL) < 0) { #else if (avformat_write_header(oc, NULL) < 0) { #endif G_warning(_("Invalid output format parameters")); return -1; } #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52, 100, 0) dump_format(oc, 0, filename, 1); #else av_dump_format(oc, 0, filename, 1); #endif /* 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 LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52, 112, 0) if (url_fopen(&oc->pb, filename, URL_WRONLY) < 0) { #else if (avio_open(&oc->pb, filename, AVIO_FLAG_WRITE) < 0) { #endif G_warning(_("Unable to open <%s>"), filename); return (-1); } } /* write the stream header, if any */ #if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52, 100, 0) av_write_header(oc); #else avformat_write_header(oc, NULL); #endif #else G_warning(_("OGSF library has not been built with MPEG output support")); return -1; #endif return 0; } /*! \brief Get RGB pixbuf and convert to YUV 4:2:0 Image and write to mpeg stream \return 0 */ int gsd_write_mpegframe(void) { #ifdef HAVE_FFMPEG unsigned int xsize, ysize, x; int y, xy, xy_uv; int yy, uu, vv; unsigned char *pixbuf; gsd_getimage(&pixbuf, &xsize, &ysize); xy = xy_uv = 0; for (y = ysize - 1; y >= 0; y--) { for (x = 0; x < xsize; x++) { unsigned char r = pixbuf[(y * xsize + x) * 4 + 0]; unsigned char g = pixbuf[(y * xsize + x) * 4 + 1]; unsigned char b = pixbuf[(y * xsize + x) * 4 + 2]; yy = (0.257 * r) + (0.504 * g) + (0.098 * b) + 16;; vv = (0.439 * r) - (0.368 * g) - (0.071 * b) + 128; uu = -(0.148 * r) - (0.291 * g) + (0.439 * b) + 128; fflush(stdout); picture->data[0][xy] = yy; if ((x % 2) && (y % 2)) { picture->data[1][xy_uv] = uu; picture->data[2][xy_uv] = vv; xy_uv++; } xy++; } } G_free(pixbuf); write_video_frame(oc, video_st); #endif return (0); } /*! \brief Close the mpeg, free buffer, and close file \return 0 */ int gsd_close_mpeg(void) { #ifdef HAVE_FFMPEG unsigned int i; close_video(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]->codec); av_freep(&oc->streams[i]); } if (!(fmt->flags & AVFMT_NOFILE)) { /* close the output file */ #if (LIBAVFORMAT_VERSION_INT>>16) < 52 url_fclose(&oc->pb); #elif LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52, 100, 0) url_fclose(oc->pb); #else avio_close(oc->pb); #endif } /* free the stream */ av_free(oc); G_debug(3, "Closed MPEG stream"); #endif return 0; }