diff --git a/cmake_modules/SuperBuild.cmake b/cmake_modules/SuperBuild.cmake index fe9b4ef5..a9f5a5bb 100644 --- a/cmake_modules/SuperBuild.cmake +++ b/cmake_modules/SuperBuild.cmake @@ -126,6 +126,7 @@ set_vars( NETGEN_CMAKE_ARGS USE_CCACHE USE_NATIVE_ARCH USE_OCC + USE_MPEG INSTALL_DIR INSTALL_DEPENDENCIES INTEL_MIC diff --git a/ng/encoding.hpp b/ng/encoding.hpp new file mode 100644 index 00000000..c94d3408 --- /dev/null +++ b/ng/encoding.hpp @@ -0,0 +1,250 @@ +#ifndef ENCODING_HPP_INCLUDED__ +#define ENCODING_HPP_INCLUDED__ + +#ifdef FFMPEG + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +} + +constexpr int BITRATE = 50000000; + +class Mpeg { + private: + bool is_started =false; + int framerate = 25; + AVOutputFormat *fmt; + AVFormatContext *oc; + + AVStream *st; + AVCodecContext *enc; + + AVFrame *frame; + AVFrame *rgb_frame; + uint8_t *rgb_buffer; + + struct SwsContext *sws_ctx; + + AVFrame *alloc_picture(enum AVPixelFormat pix_fmt) + { + AVFrame *picture; + + picture = av_frame_alloc(); + if (!picture) + return NULL; + + picture->format = pix_fmt; + picture->width = width; + picture->height = height; + + av_frame_get_buffer(picture, 32); + return picture; + } + + public: + int width; + int height; + bool IsStarted() { return is_started; } + int AddFrame() { + int ret; + int got_packet = 0; + AVPacket pkt = { 0 }; + + glReadPixels (0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, rgb_buffer); + av_image_fill_arrays(rgb_frame->data, rgb_frame->linesize, rgb_buffer, AV_PIX_FMT_RGB24, width, height, 1); + + + if (av_frame_make_writable(frame) < 0) + return 1; + + // The picture is upside down - flip it: + auto data = rgb_frame->data[0] + 3*width*height; + uint8_t *flipped_data[4] = { data, data, data, data }; + int flipped_stride[4] = { -rgb_frame->linesize[0], -rgb_frame->linesize[1], -rgb_frame->linesize[2], -rgb_frame->linesize[3] }; + sws_scale(sws_ctx, flipped_data, flipped_stride, 0, enc->height, frame->data, frame->linesize); + + + av_init_packet(&pkt); + + got_packet = 0; + ret = avcodec_send_frame(enc, frame); + if (ret < 0) + { + cerr << "Error encoding video frame: " << endl; + return(1); + } + + ret = avcodec_receive_packet(enc, &pkt); + if (!ret) + got_packet = 1; + if (ret == AVERROR(EAGAIN)) + return 0; + + if (ret < 0) { + cerr << "Error encoding video frame: " << endl; + return 1; + } + + if (got_packet) { + /* rescale output packet timestamp values from codec to stream timebase */ + av_packet_rescale_ts(&pkt, enc->time_base, st->time_base); + pkt.stream_index = st->index; + + /* Write the compressed frame to the media file. */ + ret = av_interleaved_write_frame(oc, &pkt); + } else { + ret = 0; + } + + if (ret < 0) { + cerr << "Error while writing video frame: " << endl; + return(1); + } + + return 0; + } + + int Start(string filename) { + AVCodec *video_codec; + if(is_started) { + cerr << "Stream already started" << endl; + return 1; + } + is_started = true; + + GLint dims[4] = {0}; + glGetIntegerv(GL_VIEWPORT, dims); + width = dims[2]; + height= dims[3]; + + width = int((width+1)/4)*4+4; + height = 2 * (height/2); + + int ret; + int i; + + av_register_all(); + + avformat_alloc_output_context2(&oc, NULL, NULL, filename.c_str()); +// oc->preload= (int)(0.5*AV_TIME_BASE); + oc->max_delay= (int)(0.7*AV_TIME_BASE); + + fmt = oc->oformat; + + if (fmt->video_codec != AV_CODEC_ID_NONE) { + /* find the encoder */ + video_codec = avcodec_find_encoder(fmt->video_codec); + if (!(video_codec)) { + cerr << "Could not find encoder for '" << avcodec_get_name(fmt->video_codec) << "'" << endl; + return 1; + } + + st = avformat_new_stream(oc, NULL); + if (!st) { + cerr << "Could not allocate stream\n"; + return 1; + } + st->id = oc->nb_streams-1; + enc = avcodec_alloc_context3(video_codec); + if (!enc) { + cerr << "Could not alloc an encoding context\n"; + return 1; + } + + enc->codec_id = fmt->video_codec; + + enc->bit_rate = BITRATE; + enc->width = width; + enc->height = height; + st->time_base = (AVRational){ 1, framerate }; + enc->time_base = st->time_base; + + enc->gop_size = 200; + enc->pix_fmt = AV_PIX_FMT_YUV420P; + if (enc->codec_id == AV_CODEC_ID_MPEG2VIDEO) { + enc->max_b_frames = 3; + } + if (enc->codec_id == AV_CODEC_ID_MPEG1VIDEO) { + enc->mb_decision = 2; + } + + if (oc->oformat->flags & AVFMT_GLOBALHEADER) + enc->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; + +// enc->flags |= CODEC_FLAG_QSCALE; +// enc->global_quality = 1180; + } + else { + cerr << "could not init codecs!" << endl; + return 1; + } + + AVDictionary *opt = NULL; + + /* open the codec */ + ret = avcodec_open2(enc, video_codec, &opt); + av_dict_free(&opt); + if (ret < 0) { + cerr << "Could not open video codec" << endl; + return 1; + } + + /* allocate and init a re-usable frame */ + frame = alloc_picture(enc->pix_fmt); + if (!frame) { + cerr << "Could not allocate video frame\n"; + return 1; + } + + /* copy the stream parameters to the muxer */ + ret = avcodec_parameters_from_context(st->codecpar, enc); + if (ret < 0) { + cerr << "Could not copy the stream parameters\n"; + return 1; + } + + av_dump_format(oc, 0, filename.c_str(), 1); + + if (!(fmt->flags & AVFMT_NOFILE)) { + ret = avio_open(&oc->pb, filename.c_str(), AVIO_FLAG_WRITE); + if (ret < 0) { + cerr << "Could not open " << filename << " : " << endl; + return 1; + } + } + + ret = avformat_write_header(oc, &opt); + if (ret < 0) { + cerr << "Error occurred when opening output file: " << endl;; + return 1; + } + + rgb_frame = alloc_picture(AV_PIX_FMT_RGB24); + rgb_buffer = new uint8_t[width*height*4]; + sws_ctx = sws_getContext( width, height, AV_PIX_FMT_RGB24, + width, height, AV_PIX_FMT_YUV420P, + SWS_BICUBIC, NULL, NULL, NULL ); + } + + void Stop() { + av_write_trailer(oc); + avcodec_free_context(&enc); + av_frame_free(&frame); + sws_freeContext(sws_ctx); + if (!(fmt->flags & AVFMT_NOFILE)) + avio_closep(&oc->pb); + avformat_free_context(oc); + delete [] rgb_buffer; + is_started = false; + } +}; +#endif // FFMPEG +#endif // ENCODING_HPP_INCLUDED__ diff --git a/ng/menustat.tcl b/ng/menustat.tcl index bfafc763..610d6a6b 100644 --- a/ng/menustat.tcl +++ b/ng/menustat.tcl @@ -319,7 +319,7 @@ proc demoredraw { } { global videoactive if { $videoactive == 1 } { puts "addframe" - .ndraw Ng_VideoClip addframe + Ng_VideoClip .ndraw addframe } if { $result == 0 && $stopdemo == 0 } { after 1 { demoredraw } @@ -367,14 +367,14 @@ set videoactive 0 } set file [tk_getSaveFile -filetypes $types] if {$file != ""} { - .ndraw Ng_VideoClip init $file + Ng_VideoClip .ndraw init $file global videoactive set videoactive 1 } } .ngmenu.file.video add command -label "add frame..." \ - -command {.ndraw Ng_VideoClip addframe } + -command {Ng_VideoClip .ndraw addframe } .ngmenu.file.video add command -label "one cycle" \ -command { @@ -383,14 +383,14 @@ set videoactive 0 puts "j = $j" Ng_Vis_Set time [expr (1000 * $j / 100)] redraw - .ndraw Ng_VideoClip addframe + Ng_VideoClip .ndraw addframe after 200 } } .ngmenu.file.video add command -label "finalize..." \ -command { - .ndraw Ng_VideoClip finalize + Ng_VideoClip .ndraw finalize global videoactive set videoactive 0 } @@ -1017,7 +1017,7 @@ proc timer2 { } { global videoactive if { $videoactive == 1 } { puts "addframe" - .ndraw Ng_VideoClip addframe + Ng_VideoClip .ndraw addframe } } if { $multithread_redraw == 2 } { @@ -1028,7 +1028,7 @@ proc timer2 { } { global videoactive if { $videoactive == 1 } { puts "addframe" - .ndraw Ng_VideoClip addframe + Ng_VideoClip .ndraw addframe } after 1 { timer2 } return diff --git a/ng/ngpkg.cpp b/ng/ngpkg.cpp index 8a534211..76e2b1c9 100644 --- a/ng/ngpkg.cpp +++ b/ng/ngpkg.cpp @@ -24,11 +24,7 @@ The interface between the GUI and the netgen library // to be sure to include the 'right' togl-version -#ifdef USE_TOGL_2 #include "Togl2.1/togl.h" -#else // USE_TOGL_2 -#include "togl_1_7.h" -#endif // USE_TOGL_2 #include "fonts.hpp" extern bool nodisplay; @@ -56,16 +52,7 @@ namespace netgen #endif #ifdef FFMPEG -extern "C" { - /* -#include -#include -#include - */ -#include -#include -#include -} +#include "encoding.hpp" #endif #ifdef NGSOLVE @@ -1908,150 +1895,6 @@ namespace netgen - - - -#if TOGL_MAJOR_VERSION==1 - - - // Togl - - static int fontbase = 0; - - void MyOpenGLText_GUI (const char * text) - { - if (nodisplay) - return; - - glListBase (fontbase); - glCallLists (GLsizei(strlen(text)), GL_UNSIGNED_BYTE, text); - } - - static int - Ng_ToglVersion(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const *objv) - { - Tcl_SetResult (interp, (char*)"1", TCL_STATIC); - return TCL_OK; - } - - static void init( struct Togl *togl ) - { - if (nodisplay) - return; - - LoadOpenGLFunctionPointers(); - fontbase = Togl_LoadBitmapFont( togl, TOGL_BITMAP_8_BY_13 ); - Set_OpenGLText_Callback (&MyOpenGLText_GUI); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - glMatrixMode(GL_MODELVIEW); - - SetVisualScene (Togl_Interp(togl)); - vs->DrawScene(); - } - - static void zap( struct Togl *togl ) - { - ; - } - - static void draw( struct Togl *togl ) - { - if (nodisplay) - return; - - - int w = Togl_Width (togl); - int h = Togl_Height (togl); - - glViewport(0, 0, w, h); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - // OpenGL near and far clipping planes - double pnear = 0.1; - double pfar = 10; - - gluPerspective(20.0f, double(w) / h, pnear, pfar); - glMatrixMode(GL_MODELVIEW); - - - - Tcl_Interp * interp = Togl_Interp(togl); - - SetVisualScene (interp); - -#ifdef STEREO - if (1) // vispar.stereo) - { - glMatrixMode (GL_MODELVIEW); - glPushMatrix(); - - glLoadIdentity (); - - // glTranslatef (0.1, 0, 0); - gluLookAt (0.3, 0, 6, 0, 0, 0, 0, 1, 0); - Togl_StereoDrawBuffer(GL_BACK_RIGHT); - vs->DrawScene(); - - glLoadIdentity (); - // glTranslatef (-0.1, 0, 0); - gluLookAt (-0.3, 0, 6, 0, 0, 0, 0, 1, 0); - Togl_StereoDrawBuffer(GL_BACK_LEFT); - vs->DrawScene(); - glPopMatrix(); - - Togl_SwapBuffers(togl); - } - else -#endif - { - glPushMatrix(); - glLoadIdentity(); - // gluLookAt (0, 0, 6, 0, 0, 0, 0, 1, 0); - vs->DrawScene(); - glPopMatrix(); - Togl_SwapBuffers(togl); - } - } - - static void reshape( struct Togl *togl) - { - /* - if (nodisplay) - return; - - int w = Togl_Width (togl); - int h = Togl_Height (togl); - - glViewport(0, 0, w, h); - netgen::VisualScene::viewport[0]=0; - netgen::VisualScene::viewport[1]=0; - netgen::VisualScene::viewport[2]=w; - netgen::VisualScene::viewport[3]=h; - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - - // OpenGL near and far clipping planes - double pnear = 0.1; - double pfar = 10; - - gluPerspective(20.0f, double(w) / h, pnear, pfar); - glMatrixMode(GL_MODELVIEW); - - draw (togl); - */ - } - - - -#else - - - // Sorry, Togl 2.0 not supported - Font * font = nullptr; Togl * togl = NULL; @@ -2151,15 +1994,6 @@ namespace netgen } - - - -#endif - - - - - #if TOGL_MAJOR_VERSION==1 #ifndef JPEGLIB @@ -2292,302 +2126,63 @@ namespace netgen #endif +#else + +// TODO: JPEGLIB for Togl2 + + +#endif + #ifdef FFMPEG + static int Ng_VideoClip(ClientData clientData, Tcl_Interp *interp, int argc, Tcl_Obj *const *argv) + { + static Mpeg mpeg; + struct Togl *togl; + if (Togl_GetToglFromObj(interp, argv[1], &togl) != TCL_OK) + return TCL_ERROR; - // thanks to Mikko Lyly @ CSC, Helsinki - -#define STATE_READY 0 -#define STATE_STARTED 1 - - -#define INBUF_SIZE 4096 -#define DEFAULT_B_FRAMES 3 - // #define DEFAULT_B_FRAMES 0 -#define DEFAULT_GOP_SIZE 200 - // #define DEFAULT_GOP_SIZE 10 - // #define DEFAULT_BITRATE 500000 -#define DEFAULT_BITRATE 5000000 - // #define DEFAULT_MPG_BUFSIZE 500000 -#define DEFAULT_MPG_BUFSIZE 500000 - - typedef struct buffer_s { - uint8_t *MPG; - uint8_t *YUV; - uint8_t *RGB; - uint8_t *ROW; - } buffer_t; - - void free_buffers( buffer_t *buff ) { - free( buff->MPG ); - free( buff->YUV ); - free( buff->RGB ); - free( buff->ROW ); - } - - static double psnr( double d ) { - if( d==0 ) - return INFINITY; - return -10.0*log( d )/log( 10.0 ); - } - - void print_info( int count_frames, AVCodecContext *context, int bytes ) { - double tmp = context->width * context->height * 255.0 * 255.0; - double Ypsnr = psnr( context->coded_frame->error[0] / tmp ); - double quality = context->coded_frame->quality/(double)FF_QP2LAMBDA; - char pict_type = av_get_picture_type_char(context->coded_frame->pict_type); - cout << "video: frame=" << count_frames << " type=" << pict_type; - cout << " size=" << bytes << " PSNR(Y)=" << Ypsnr << " dB q=" << (float)quality << endl; - } - - - - static int Ng_VideoClip (struct Togl * togl, - int argc, tcl_const char *argv[]) - { - static AVCodec *codec = NULL; - static AVCodecContext *context = NULL; - static AVFrame *YUVpicture = NULL; - static AVFrame *RGBpicture = NULL; - static int bytes, PIXsize, stride; - static int y, nx, ny; - // static int ox, oy, viewp[4]; - static int i_state = STATE_READY; - static int initialized = 0; - static int count_frames = 0; - static int bitrate = DEFAULT_BITRATE; - static int gopsize = DEFAULT_GOP_SIZE; - static int bframes = DEFAULT_B_FRAMES; - static int MPGbufsize = DEFAULT_MPG_BUFSIZE; - static AVCodecID codec_id = CODEC_ID_MPEG1VIDEO; - static FILE *MPGfile; - static buffer_t buff; - static struct SwsContext *img_convert_ctx; - - - if (strcmp (argv[2], "init") == 0) + if (strcmp (Tcl_GetString(argv[2]), "init") == 0) { // Can't initialize when running: //------------------------------- - if( i_state != STATE_READY ) { + if( mpeg.IsStarted() ) { cout << "cannot initialize: already running" << endl; return TCL_ERROR; } - - - // Open output file: - //------------------- - const char * filename = argv[3]; - cout << "Saving videoclip to file '" << filename << "'" << endl; - MPGfile = fopen(filename, "wb"); - - // Determine picture size: - //------------------------ - nx = Togl_Width (togl); - nx = int((nx + 1) / 4) * 4 + 4; - ny = Togl_Height (togl); - ny = 2 * (ny/2); - cout << "Width=" << nx << ", height=" << ny << endl; - - // Allocate buffers: - //------------------ - PIXsize = nx*ny; - stride = 3*nx; - buff.RGB = (uint8_t*)malloc(stride*ny); - buff.ROW = (uint8_t*)malloc(stride); - buff.YUV = (uint8_t*)malloc(3*(PIXsize/2)); - buff.MPG = (uint8_t*)malloc(MPGbufsize); - - - // Initialize libavcodec: - //----------------------- - if( !initialized ) { - av_register_all(); - initialized = 1; - } - - // Choose codec: - //-------------- - codec = avcodec_find_encoder( codec_id ); - if( !codec ) { - free_buffers( &buff ); - fclose( MPGfile ); - cout << "can't find codec" << endl; - return TCL_ERROR; - } - - // Init codec context etc.: - //-------------------------- - // context = avcodec_alloc_context(); - context = avcodec_alloc_context3(codec); - - context->bit_rate = bitrate; - context->width = nx; - context->height = ny; - AVRational s; - s.num = 1; - s.den = 25; - context->time_base = s; - context->gop_size = gopsize; - context->max_b_frames = bframes; - context->pix_fmt = PIX_FMT_YUV420P; - context->flags |= CODEC_FLAG_PSNR; - - // if( avcodec_open( context, codec ) < 0 ) { - if( avcodec_open2( context, codec, NULL) < 0 ) { - cout << "can't open codec" << endl; - avcodec_close( context ); - av_free( context ); - free_buffers( &buff ); - fclose( MPGfile ); - return TCL_ERROR; - } - - YUVpicture = avcodec_alloc_frame(); - - YUVpicture->data[0] = buff.YUV; - YUVpicture->data[1] = buff.YUV + PIXsize; - YUVpicture->data[2] = buff.YUV + PIXsize + PIXsize / 4; - YUVpicture->linesize[0] = nx; - YUVpicture->linesize[1] = nx / 2; - YUVpicture->linesize[2] = nx / 2; - - RGBpicture = avcodec_alloc_frame(); - - RGBpicture->data[0] = buff.RGB; - RGBpicture->data[1] = buff.RGB; - RGBpicture->data[2] = buff.RGB; - RGBpicture->linesize[0] = stride; - RGBpicture->linesize[1] = stride; - RGBpicture->linesize[2] = stride; - - // Set state "started": - //---------------------- - i_state = STATE_STARTED; - cout << "savempg: state: started" << endl; + const char * filename = Tcl_GetString(argv[3]); + mpeg.Start(filename); return TCL_OK; } - else if (strcmp (argv[2], "addframe") == 0) + else if (strcmp (Tcl_GetString(argv[2]), "addframe") == 0) { - // Can't compress if status != started: - //------------------------------------- - if( i_state != STATE_STARTED ) { - cout << "cannot add frame: codec not initialized" << endl; + if(mpeg.AddFrame()) return TCL_ERROR; - } - - // Read RGB data: - //--------------- - glReadPixels (0, 0, nx, ny, GL_RGB, GL_UNSIGNED_BYTE, buff.RGB ); - - // The picture is upside down - flip it: - //--------------------------------------- - for( y=0; ydata, RGBpicture->linesize, - 0, ny, YUVpicture->data, YUVpicture->linesize ); - - - // Encode frame: - //-------------- - bytes = avcodec_encode_video( context, buff.MPG, - MPGbufsize, YUVpicture ); - count_frames++; - print_info( count_frames, context, bytes ); - fwrite( buff.MPG, 1, bytes, MPGfile ); - - return TCL_OK; } - else if (strcmp (argv[2], "finalize") == 0) + else if (strcmp (Tcl_GetString(argv[2]), "finalize") == 0) { - // Can't stop if status != started: - //--------------------------------- - if( i_state != STATE_STARTED ) { - cout << "cannot finalize: codec not initialized" << endl; - return TCL_ERROR; - } - - // Get the delayed frames, if any: - //-------------------------------- - for( ; bytes; ) { - bytes = avcodec_encode_video( context, buff.MPG, MPGbufsize, NULL ); - count_frames++; - print_info( count_frames, context, bytes ); - fwrite( buff.MPG, 1, bytes, MPGfile ); - } - - // Add sequence end code: - //----------------------- - if( codec_id == CODEC_ID_MPEG1VIDEO ) { - buff.MPG[0] = 0x00; - buff.MPG[1] = 0x00; - buff.MPG[2] = 0x01; - buff.MPG[3] = 0xb7; - fwrite( buff.MPG, 1, 4, MPGfile ); - } - - // Finalize: - //----------- - avcodec_close( context ); - av_free( context ); - av_free( YUVpicture ); - av_free( RGBpicture ); - free_buffers( &buff ); - fclose( MPGfile ); - - i_state = STATE_READY; - cout << "finalized" << endl; - - return TCL_OK; + mpeg.Stop(); } return TCL_OK; } - - -#else - static int Ng_VideoClip (struct Togl * togl, - int argc, tcl_const char *argv[]) +#else // FFMPEG + static int Ng_VideoClip(ClientData clientData, Tcl_Interp *interp, int argc, Tcl_Obj *const *argv) { Tcl_SetResult (Togl_Interp(togl), (char*)"Video not available, Netgen was not compiled with FFMPEG library", TCL_STATIC); return TCL_ERROR; } -#endif - -#endif - - - - - +#endif // FFMPEG @@ -3476,22 +3071,6 @@ void PlayAnimFile(const char* name, int speed, int maxcnt) */ Tcl_CreateObjCommand(interp, "Ng_GetToglVersion", Ng_ToglVersion, NULL, NULL); -#if TOGL_MAJOR_VERSION==1 - if (!nodisplay) - { - if (Togl_Init(interp) == TCL_ERROR) - return TCL_ERROR; - - Togl_CreateFunc( init ); - Togl_DestroyFunc( zap ); - Togl_DisplayFunc( draw ); - Togl_ReshapeFunc( reshape ); - // Togl_TimerFunc( idle ); - Togl_CreateCommand( (char*)"Ng_SnapShot", Ng_SnapShot); - Togl_CreateCommand( (char*)"Ng_VideoClip", Ng_VideoClip); - // Togl_CreateCommand("position",position); - } -#else if (!nodisplay) { if (Togl_Init(interp) == TCL_ERROR) @@ -3505,12 +3084,9 @@ void PlayAnimFile(const char* name, int speed, int maxcnt) // Togl_TimerFunc( idle ); // Togl_CreateCommand( (char*)"Ng_SnapShot", Ng_SnapShot); - // Togl_CreateCommand( (char*)"Ng_VideoClip", Ng_VideoClip); - // Togl_CreateCommand("position",position); + Tcl_CreateObjCommand(interp, "Ng_VideoClip", Ng_VideoClip, NULL, NULL); } -#endif - multithread.pause = 0; multithread.testmode = 0;