Update for new FFMPEG version, extra header for implementation

This commit is contained in:
Matthias Hochsteger 2017-02-03 15:28:59 +01:00
parent 515e68260d
commit 74cb50d5ce
4 changed files with 284 additions and 457 deletions

View File

@ -126,6 +126,7 @@ set_vars( NETGEN_CMAKE_ARGS
USE_CCACHE
USE_NATIVE_ARCH
USE_OCC
USE_MPEG
INSTALL_DIR
INSTALL_DEPENDENCIES
INTEL_MIC

250
ng/encoding.hpp Normal file
View File

@ -0,0 +1,250 @@
#ifndef ENCODING_HPP_INCLUDED__
#define ENCODING_HPP_INCLUDED__
#ifdef FFMPEG
extern "C" {
#include <libavutil/avassert.h>
#include <libavutil/channel_layout.h>
#include <libavutil/opt.h>
#include <libavutil/mathematics.h>
#include <libavutil/timestamp.h>
#include <libavutil/imgutils.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
}
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__

View File

@ -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

View File

@ -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 <ffmpeg/avcodec.h>
#include <ffmpeg/avformat.h>
#include <ffmpeg/swscale.h>
*/
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}
#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
// 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 int Ng_VideoClip(ClientData clientData, Tcl_Interp *interp, int argc, Tcl_Obj *const *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;
static Mpeg mpeg;
struct Togl *togl;
if (Togl_GetToglFromObj(interp, argv[1], &togl) != TCL_OK)
return TCL_ERROR;
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; y<ny/2; y++ ) {
uint8_t *r1 = buff.RGB + stride*y;
uint8_t *r2 = buff.RGB + stride*(ny-1-y);
memcpy( buff.ROW, r1, stride );
memcpy( r1, r2, stride );
memcpy( r2, buff.ROW, stride );
}
// Convert to YUV:
//----------------
if( img_convert_ctx == NULL )
img_convert_ctx = sws_getContext( nx, ny, PIX_FMT_RGB24,
nx, ny, PIX_FMT_YUV420P,
SWS_BICUBIC, NULL, NULL, NULL );
if( img_convert_ctx == NULL ) {
cout << "can't initialize scaler context" << endl;
return TCL_ERROR;
}
sws_scale( img_convert_ctx, RGBpicture->data, 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;