X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=synfig-core%2Fsrc%2Fmodules%2Fmod_libavcodec%2Ftrgt_av.cpp;fp=synfig-core%2Fsrc%2Fmodules%2Fmod_libavcodec%2Ftrgt_av.cpp;h=5ad733aba2220061eee63975a0e280c3115e2217;hb=a095981e18cc37a8ecc7cd237cc22b9c10329264;hp=0000000000000000000000000000000000000000;hpb=9459638ad6797b8139f1e9f0715c96076dbf0890;p=synfig.git diff --git a/synfig-core/src/modules/mod_libavcodec/trgt_av.cpp b/synfig-core/src/modules/mod_libavcodec/trgt_av.cpp new file mode 100644 index 0000000..5ad733a --- /dev/null +++ b/synfig-core/src/modules/mod_libavcodec/trgt_av.cpp @@ -0,0 +1,983 @@ +/* === S Y N F I G ========================================================= */ +/*! \file trgt_av.cpp +** \brief \writeme +** +** $Id$ +** +** \legal +** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley +** Copyright (c) 2008 Paul Wise +** Copyright (c) 2008 Gerco Ballintijn +** +** This package is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License as +** published by the Free Software Foundation; either version 2 of +** the License, or (at your option) any later version. +** +** This package is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +** General Public License for more details. +** \endlegal +*/ +/* ========================================================================= */ + +/* === H E A D E R S ======================================================= */ + +#define SYNFIG_NO_ANGLE + +#ifdef USING_PCH +# include "pch.h" +#else +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "trgt_av.h" + +extern "C" +{ + +/* + ffmpeg library headers have historically had multiple locations. + We should check all of the locations to be more portable. +*/ + +#ifdef HAVE_LIBAVFORMAT_AVFORMAT_H +# include +#elif defined(HAVE_AVFORMAT_H) +# include +#elif defined(HAVE_FFMPEG_AVFORMAT_H) +# include +#endif + +#ifdef WITH_LIBSWSCALE +#ifdef HAVE_LIBSWSCALE_SWSCALE_H +# include +#elif defined(HAVE_SWSCALE_H) +# include +#elif defined(HAVE_FFMPEG_SWSCALE_H) +# include +#endif +#endif + +} + +#include + +#include +#include +#include +#endif + +#ifdef WIN32 +#define snprintf _snprintf +#endif + +/* === U S I N G =========================================================== */ + +using namespace synfig; +using namespace std; +using namespace etl; + +/* === I N F O ============================================================= */ + +SYNFIG_TARGET_INIT(Target_LibAVCodec); +SYNFIG_TARGET_SET_NAME(Target_LibAVCodec,"libav"); +SYNFIG_TARGET_SET_EXT(Target_LibAVCodec,"avi"); +SYNFIG_TARGET_SET_VERSION(Target_LibAVCodec,"0.1"); +SYNFIG_TARGET_SET_CVS_ID(Target_LibAVCodec,"$Id$"); + +/* === C L A S S E S & S T R U C T S ======================================= */ + +bool Target_LibAVCodec::registered = false; + +//for now compilation +//float STREAM_DURATION = 5.0f; + +struct VideoInfo +{ + int w,h; + int fps; + + int bitrate; +}; + +struct AudioInfo +{ + int samplerate; //in HZ + int samplesize; //in bytes +}; + +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 = (uint8_t *)malloc(size); + if (!picture_buf) { + av_free(picture); + return NULL; + } + avpicture_fill((AVPicture *)picture, picture_buf, + pix_fmt, width, height); + return picture; +} + +void free_picture(AVFrame *pic) +{ + av_free(pic->data[0]); + av_free(pic); +} + +//the frame must be RGB24 +static void convert_surface_frame(AVFrame *pic, const Surface &s, const Gamma &gamma) +{ + unsigned int j; + Surface::const_pen p = s.begin(); + unsigned int w,h; + Color c; + + uint8_t *ptr; + int stride; + + w = s.size().x; + h = s.size().y; + + ptr = pic->data[0]; + stride = pic->linesize[0]; + + for(j = 0; j < h; j++, p.inc_y(), ptr += stride) + { + uint8_t *tptr = ptr; + + //use convert_color_format instead... + #if 0 + const int channels = 3; + + for(int i = 0; i < w; i++, p.inc_x(), tptr += channels) + { + c = p.get_value(); + + Color::value_type r = c.get_r(); + Color::value_type g = c.get_g(); + Color::value_type b = c.get_b(); + Color::value_type a = c.get_a(); + + //premultiply alpha + r *= a; + g *= a; + b *= a; + + //essentially treats it as if it has a background color of black + + //we must also clamp the rgb values [0,1] + r = min(1.0f,r); + g = min(1.0f,g); + b = min(1.0f,b); + + r = max(0.0f,r); + g = max(0.0f,g); + b = max(0.0f,b); + + //now scale to range of char [0,255] + tptr[0] = (int)(r*255); + tptr[1] = (int)(g*255); + tptr[2] = (int)(b*255); + } + + p.dec_x(w); + #else + + convert_color_format((unsigned char *)tptr,&p.get_value(),w,PF_RGB,gamma); + + #endif + } +} + +//Audio Streamer (abstracts the open, write and close operations for audio streams) +#if 0 +class AudioEncoder +{ +public: + + void *samples; + + vectoraudiobuffer; + + int audio_input_frame_size; + + bool open(AVFormatContext *formatc, AVStream *stream) + { + AVCodecContext *context; + AVCodec *codec; + + context = &stream->codec; + + //find audio encoder + codec = avcodec_find_encoder(context->codec_id); + if(!codec) + { + synfig::warning("audio-open: could not find codec"); + return 0; + } + + //open the codec + if(avcodec_open(context, codec) < 0) + { + synfig::warning("audio-open: could not open codec"); + return 0; + } + + /* hardcoded example for generating samples array*/ + /* + t = 0; + tincr = 2 * M_PI * 110.0 / c->sample_rate; + tincr2 = 2 * M_PI * 110.0 / c->sample_rate / c->sample_rate;*/ + + audiobuffer.resize(10000); + + /* ugly hack for PCM codecs (will be removed ASAP with new PCM + support to compute the input frame size in samples */ + if (context->frame_size <= 1) { + audio_input_frame_size = audiobuffer.size() / context->channels; + switch(stream->codec.codec_id) { + case CODEC_ID_PCM_S16LE: + case CODEC_ID_PCM_S16BE: + case CODEC_ID_PCM_U16LE: + case CODEC_ID_PCM_U16BE: + audio_input_frame_size >>= 1; + break; + default: + break; + } + } else { + audio_input_frame_size = context->frame_size; + } + + //hardcoded array + //samples = (int16_t *)malloc(audio_input_frame_size * 2 * c->channels); + + return true; + } + + bool write_frame(AVFormatContext *formatc, AVStream *stream, void *samples) + { + int size; + AVCodecContext *context; + + context = &stream->codec; + + //hardcoded in example + //must read in from somewhere... + //get_audio_frame(samples, audio_input_frame_size, c->channels); + + //encode the audio + const short int*samps=(const short int *)samples; //assuming it's set from somewhere right now + + size = avcodec_encode_audio(context, &audiobuffer[0], audiobuffer.size(), samps); + + //write the compressed audio to a file + if(av_write_frame(formatc, stream->index, &audiobuffer[0], size) != 0) + { + synfig::warning("audio-write_frame: unable to write the entire audio frame"); + return 0; + } + + return true; + } + + void close(AVFormatContext *formatc, AVStream *stream) + { + //we may also want to catch delayed frames from here (don't for now) + + if(stream) + { + avcodec_close(&stream->codec); + } + + //if(samples)av_free(samples); + audiobuffer.resize(0); + } +}; + +#endif + +class VideoEncoder +{ +public: + AVFrame *encodable; //for compression and output to a file (in compatible pixel format) + + vector videobuffer; + + bool startedencoding; + + //int stream_nb_frames; + + bool open(AVFormatContext *formatc, AVStream *stream) + { + if(!formatc || !stream) + { + synfig::warning("Attempt to open a video codec with a bad format or stream"); + return false; + } + + //codec and context + AVCodec *codec; + AVCodecContext *context; + + //get from inside stream + context = stream->codec; + + //search for desired codec (contained in the stream) + codec = avcodec_find_encoder(context->codec_id); + if(!codec) + { + synfig::warning("Open_video: could not find desired codec"); + return 0; + } + + //try to open the codec + if(avcodec_open(context, codec) < 0) + { + synfig::warning("open_video: could not open desired codec"); + return 0; + } + + videobuffer.resize(0); + if(!(formatc->oformat->flags & AVFMT_RAWPICTURE)) + { + //resize buffer to desired buffersize + videobuffer.resize(200000); //TODO: need to figure out a good size + } + + //allocate the base picture which will be used to encode + /*picture = alloc_picture(PIX_FMT_RGBA32, context->width, context->height); + if(!picture) + { + synfig::warning("open_video: could not allocate the picture to be encoded"); + return 0; + }*/ + + //if our output (rgb) needs to be translated to a different coordinate system, need a temporary picture in that color space + + /* Should use defaults of RGB + Possible formats: + PIX_FMT_RGB24 + PIX_FMT_BGR24 + PIX_FMT_RGBA32 //stored in cpu endianness (!!!!) + + (possibly translate directly to required coordinate systems later on... less error) + */ + encodable = NULL; + if(context->pix_fmt != PIX_FMT_RGB24) + { + encodable = alloc_picture(context->pix_fmt, context->width, context->height); + if(!encodable) + { + synfig::warning("open_video: could not allocate encodable picture"); + return 0; + } + } + + return true; + } + + //write a frame with the frame passed in + bool write_frame(AVFormatContext *formatc, AVStream *stream, AVFrame *pict) + { + if(!formatc || !stream) + { + synfig::warning("Attempt to open a video codec with a bad format or stream"); + return false; + } + + int size, + ret = 0; + AVCodecContext *context = stream->codec; + + /* + If pict is invalid (NULL), then we are done compressing frames and we are trying to get + the buffer cleared out (or if it's already in the right format) so no transform necessary + */ + if ( pict ) + { + startedencoding = true; + } + + + if ( pict && context->pix_fmt != PIX_FMT_RGB24 ) + { + //We're using RGBA at the moment, write custom conversion code later (get less accuracy errors) +#ifdef WITH_LIBSWSCALE + struct SwsContext* img_convert_ctx = + sws_getContext(context->width, context->height, PIX_FMT_RGB24, + context->width, context->height, context->pix_fmt, + SWS_BICUBIC, NULL, NULL, NULL); + + sws_scale(img_convert_ctx, pict->data, pict->linesize, + + 0, context->height, encodable->data, + encodable->linesize); + + sws_freeContext (img_convert_ctx); +#else + img_convert((AVPicture *)encodable, context->pix_fmt, + (AVPicture *)pict, PIX_FMT_RGB24, + context->width, context->height); +#endif + + pict = encodable; + } + + AVPacket pkt; + av_init_packet(&pkt); + pkt.stream_index = stream->index; + pkt.data = (uint8_t *)pict; + pkt.size = sizeof(AVPicture); + if( context->coded_frame ) + pkt.pts = context->coded_frame->pts; + if( context->coded_frame && context->coded_frame->key_frame) + pkt.flags |= PKT_FLAG_KEY; + + //kluge for raw picture format (they said they'd fix) + if (formatc->oformat->flags & AVFMT_RAWPICTURE) + { + ret = av_write_frame(formatc, &pkt); + } + else + { + //encode our given image + size = avcodec_encode_video(context, &videobuffer[0], videobuffer.size(), pict); + + //if greater than zero we've got stuff to write + if (size > 0) + { + av_init_packet(&pkt); + pkt.stream_index = stream->index; + pkt.data = &videobuffer[0]; + pkt.size = size; + if( context->coded_frame ) + pkt.pts = context->coded_frame->pts; + if( context->coded_frame && context->coded_frame->key_frame) + pkt.flags |= PKT_FLAG_KEY; + + ret = av_write_frame(formatc, &pkt); + + //error detect - possibly throw later... + if(ret < 0) + { + synfig::warning("write_frame: error while writing video frame"); + return false; + } + } + //if 0, it was buffered (if invalid picture we don't have ANY data left) + else + { + //if we're clearing the buffers and there was no stuff to be written, we're done (like in codec example) + if(pict == NULL) + { + return false; + startedencoding = false; + } + + //buffered picture + } + } + + return true; + } + + void close(AVFormatContext */*formatc*/, AVStream *stream) + { + if(stream) + avcodec_close(stream->codec); + + if (encodable) + { + free_picture(encodable); + encodable = 0; + } + + videobuffer.resize(0); + } +}; + +class Target_LibAVCodec::LibAVEncoder +{ +public: + + bool initialized; + + AVOutputFormat *format; //reference to global, do not delete + + AVFormatContext *formatc; + + AVStream *video_st; + //AVStream *audio_st; + + double video_pts; + //double audio_pts; + + VideoEncoder vid; + VideoInfo vInfo; + + /*AudioEncoder aud; + AudioInfo aInfo;*/ + + AVFrame *picture; //for encoding to RGB24 (perhaps RGBA later) + + int frame_count; + int num_frames; + + LibAVEncoder() + { + format = 0; + formatc = 0; + + //video settings + video_st = 0; + video_pts = 0; + + vid.encodable = 0; + //vid.stream_nb_frames = 2; //reasonable default + + initialized = false; + picture = 0; + + frame_count = 0; + num_frames = 0; + + //aud.samples = 0; + //audio_st = 0; + //audio_pts = 0; + } + + ~LibAVEncoder() + { + CleanUp(); + } + + bool Initialize(const char *filename, const char *typestring) + { + //guess if we have a type string, otherwise use filename + if (typestring) + { + //formatptr guess_format(type, filename, MIME type) + format = guess_format(typestring,NULL,NULL); + } + else + { + format = guess_format(NULL, filename, NULL); + } + + if(!format) + { + synfig::warning("Unable to Guess the output, defaulting to mpeg"); + format = guess_format("mpeg", NULL, NULL); + } + + if(!format) + { + synfig::warning("Unable to find output format"); + return 0; + } + + //allocate the output context + formatc = (AVFormatContext *)av_mallocz(sizeof(AVFormatContext)); + if(!formatc) + { + synfig::warning("Memory error\n"); + return 0; + } + //set the output format to the one we found + formatc->oformat = format; + + //print the output filename + snprintf(formatc->filename, sizeof(formatc->filename), "%s", filename); + + //initial + video_st = NULL; + //audio_st = NULL; + + //video stream + if(format->video_codec != CODEC_ID_NONE) + { + video_st = add_video_stream(format->video_codec,vInfo); + if(!video_st) + { + av_free(formatc); + } + } + + //audio stream + /*if(format->audio_codec != CODEC_ID_NONE) + { + audio_st = add_audio_stream(format->audio_codec,aInfo); + }*/ + + //set output parameters: required in ALL cases + + video_st->codec->time_base= (AVRational){1,vInfo.fps}; + video_st->codec->width = vInfo.w; + video_st->codec->height = vInfo.h; + video_st->codec->pix_fmt = PIX_FMT_YUV420P; + + //dump the formatting information as the file header + dump_format(formatc, 0, filename, 1); + + //open codecs and allocate buffers + if(video_st) + { + if(!vid.open(formatc, video_st)) + { + synfig::warning("Could not open video encoder"); + return 0; + } + } + /*if(audio_st) + { + if(!aud.open(formatc, audio_st)) + { + synfig::warning("Could not open audio encoder"); + return 0; + } + }*/ + + //open output file + if(!(format->flags & AVFMT_NOFILE)) + { + //use libav's file open function (what does it do differently????) + if(url_fopen(&formatc->pb, filename, URL_WRONLY) < 0) + { + synfig::warning("Unable to open file: %s", filename); + return 0; + } + } + + //allocate the picture to render to + //may have to retrieve the width, height from the codec... for resizing... + picture = alloc_picture(PIX_FMT_RGB24,vInfo.w,vInfo.h);//video_st->codec.width, video_st->codec.height); + if(!picture) + { + synfig::warning("Unable to allocate the temporary AVFrame surface"); + return 0; + } + + initialized = true; + //vInfo.w = video_st->codec.width; + //vInfo.h = video_st->codec.height; + + //write the stream header + av_write_header(formatc); + + return true; + } + + void CleanUp() + { + unsigned int i; + + if(picture) free_picture(picture); + + //do all the clean up file rendering + if(formatc && video_st) + { + //want to scan in delayed frames until no longer needed (TODO) + if(vid.startedencoding) while( vid.write_frame(formatc, video_st, 0) ); + + //may want to move this... probably to the end of the last frame... + av_write_trailer(formatc); + } + + //close codecs + if (video_st) + vid.close(formatc,video_st); + /*if (audio_st) + aud.close(formatc,audio_st);*/ + + /* write the trailer, if any */ + if(formatc) + { + /* free the streams */ + for(i = 0; i < formatc->nb_streams; i++) + { + av_freep(&formatc->streams[i]); + } + + if(!(format->flags & AVFMT_NOFILE)) + { + /* close the output file */ +#if LIBAVFORMAT_VERSION_INT >= (52<<16) + url_fclose(formatc->pb); +#else + url_fclose(&formatc->pb); +#endif + } + + /* free the stream */ + av_free(formatc); + } + + initialized = false; + + format = 0; + formatc = 0; + + //video settings + video_st = 0; + video_pts = 0; + + vid.encodable = 0; + //vid.stream_nb_frames = 2; //reasonable default + + initialized = false; + picture = 0; + } + + //create a video output stream + AVStream *add_video_stream(int codec_id, const VideoInfo &info) + { + AVCodecContext *context; + AVStream *st; + + st = av_new_stream(formatc, 0); + if(!st) + { + synfig::warning("video-add_stream: Unable to allocate stream"); + return 0; + } + + context = st->codec; + context->codec_id = (CodecID)codec_id; + context->codec_type = CODEC_TYPE_VIDEO; + + //PARAMETERS MUST BE PASSED IN SOMEHOW (ANOTHER FUNCTION PARAMETER???) + + /* resolution must be a multiple of two */ + context->width = info.w; + context->height = info.h; + + //have another way to input these + context->bit_rate = info.bitrate; //TODO: Make dependant on the quality + + /* frames per second */ + // FIXME: Port next two lines to recent libavcodec versions + //context->frame_rate = info.fps; + //context->frame_rate_base = 1; + + /* "High Quality" */ + context->mb_decision=FF_MB_DECISION_BITS; + + context->gop_size = info.fps/4; /* emit one intra frame every twelve frames at most */ + + //HACK: MPEG requires b frames be set... any better way to do this? + if (context->codec_id == CODEC_ID_MPEG1VIDEO || + context->codec_id == CODEC_ID_MPEG2VIDEO) + { + /* just for testing, we also add B frames */ + context->max_b_frames = 2; + } + + return st; + } + + // add an audio output stream + AVStream *add_audio_stream(int codec_id,const AudioInfo &/*aInfo*/) + { + AVCodecContext *context; + AVStream *stream; + + stream = av_new_stream(formatc, 1); + if(!stream) + { + synfig::warning("could not alloc stream"); + return 0; + } + + context = stream->codec; + context->codec_id = (CodecID)codec_id; + context->codec_type = CODEC_TYPE_AUDIO; + + /* put sample parameters */ + context->bit_rate = 64000; + context->sample_rate = 44100; + context->channels = 2; + + return stream; + } +}; + +/* === M E T H O D S ======================================================= */ + +Target_LibAVCodec::Target_LibAVCodec(const char *Filename): + filename(Filename) +{ + if(!registered) + { + registered = true; + av_register_all(); + } + set_remove_alpha(); + + data = new LibAVEncoder; +} + +Target_LibAVCodec::~Target_LibAVCodec() +{ + data->CleanUp(); +} + +bool +Target_LibAVCodec::set_rend_desc(RendDesc *given_desc) +{ + // This is where you can determine how you want stuff + // to be rendered! given_desc is the suggestion, and + // you need to modify it to suit the needs of the codec. + // ie: Making the pixel dimensions divisible by 8, etc... + + desc=*given_desc; + + //resize surface (round even) + int w = desc.get_w(); + int h = desc.get_h(); + Point tl = desc.get_tl(); + Point br = desc.get_br(); + Real pw = desc.get_pw(); + Real ph = desc.get_ph(); + + //resize to the size it should be... + //desc.set_subwindow(-offx/2,-offy/2, desc.get_w() - offx?(offx + 8):0, desc.get_h() - offy?(offy + 8):0); + + //if resolution is broken, change the size... or something + //budge to nearest pixel values + if(w&1) + { + w += 1; + tl[0] -= pw/2; + br[0] += pw/2; + } + + if(h&1) + { + h += 1; + tl[1] -= ph/2; + br[1] += ph/2; + } + + desc.set_w(w); + desc.set_h(h); + desc.set_tl(tl); + desc.set_br(br); + + data->vInfo.w = w; + data->vInfo.h = h; + + //may want to round frame rate + data->vInfo.fps = (int)floor(desc.get_frame_rate()+0.5); +#define MEGABYTES_PER_HOUR(x) (((x)*1024/3600*1024*8)/*/640*w/480*h*/) + data->vInfo.bitrate = MEGABYTES_PER_HOUR(400); + //data->vInfo.bitrate = 800000; //make customizable somehow + + desc.set_frame_rate(data->vInfo.fps); + + data->frame_count = desc.get_frame_start(); + data->num_frames = desc.get_frame_end()+1; //number of frames should be 1 greater than the last one + + surface.set_wh(data->vInfo.w,data->vInfo.h); + + return true; +} + +void +Target_LibAVCodec::end_frame() +{ + //AVStream *audio_st = data->audio_st; + AVStream *video_st = data->video_st; + + AVFormatContext *formatc = data->formatc; + + //double &audio_pts = data->audio_pts; + //double &video_pts = data->video_pts; + + //ignore audio for now + /*if (audio_st) + audio_pts = (double)audio_st->pts.val * formatc->pts_num / formatc->pts_den; + else + audio_pts = 0.0; + + if (video_st) + video_pts = (double)video_st->pts.val * formatc->pts_num / formatc->pts_den; + else + video_pts = 0.0;*/ + + //hardcoded crappiness + /*if ((!audio_st || audio_pts >= STREAM_DURATION) && + (!video_st || video_pts >= STREAM_DURATION)) + break;*/ + + if(data->frame_count >= data->num_frames) return; + + //copy the current surface to the buffer + if(data->picture)convert_surface_frame(data->picture,surface,gamma()); + + //encode the frame and write it to the file + if(!data->vid.write_frame(formatc,video_st,data->picture)) + { + synfig::warning("Unable to write a frame"); + } + + data->frame_count++; + + if(data->frame_count >= data->num_frames) + { + data->CleanUp(); + } + + /* write interleaved audio and video frames */ + /*if (!video_st || (video_st && audio_st && audio_pts < video_pts)) { + data->aud.write_frame(formatc,audio_st); + } else { + data->vid.write_frame(formatc,video_st); + }*/ +} + +bool +Target_LibAVCodec::start_frame(synfig::ProgressCallback */*callback*/) +{ + //prepare all the color buffer stuff, etc. + + return true; +} + +Color * +Target_LibAVCodec::start_scanline(int scanline) +{ + return surface[scanline]; + + return 0; // This should kill the render process +} + +bool +Target_LibAVCodec::end_scanline() +{ + //don't need to do anything until the whole frame is done + return true; +} + +bool Target_LibAVCodec::init() +{ + //hardcoded test for mpeg + if(!data->Initialize(filename.c_str(),NULL)) + { + synfig::warning("Unable to Initialize the audio video encoders"); + return 0; + } + + return true; +}