2013-09-28 15 views
9

Attualmente sto cercando di salvare l'animazione creata in openGL in un file video. Ho provato a utilizzare openCV 's videowriter ma senza alcun vantaggio. Sono riuscito a generare uno snapshot con successo ea salvarlo come bmp utilizzando la libreria SDL. Se salvo tutte le istantanee e poi generi il video usando ffmpeg, è come raccogliere 4 GB di immagini. Non pratico. Come posso scrivere direttamente i fotogrammi video durante il rendering? Ecco il codice che uso per scattare istantanee quando ho bisogno:Salvataggio del contesto openGL come output video

void snapshot(){ 
SDL_Surface* snap = SDL_CreateRGBSurface(SDL_SWSURFACE,WIDTH,HEIGHT,24, 0x000000FF, 0x0000FF00, 0x00FF0000, 0); 
char * pixels = new char [3 *WIDTH * HEIGHT]; 
glReadPixels(0, 0,WIDTH, HEIGHT, GL_RGB, GL_UNSIGNED_BYTE, pixels); 

for (int i = 0 ; i <HEIGHT ; i++) 
    std::memcpy(((char *) snap->pixels) + snap->pitch * i, pixels + 3 * WIDTH * (HEIGHT-i - 1), WIDTH*3); 

delete [] pixels; 
SDL_SaveBMP(snap, "snapshot.bmp"); 
SDL_FreeSurface(snap); 
} 

ho bisogno l'uscita video. Ho scoperto che ffmpeg può essere utilizzato per creare video dal codice C++ ma non sono stato in grado di capire il processo. Per favore aiuto!

EDIT: Ho provato con openCVCvVideoWriter classe, ma il programma si blocca ("segmentation fault") nel momento in cui è declared.Compilation mostra nessun errore naturalmente. Qualche suggerimento?

soluzione per gli utenti PITONE (richiede Python2.7, python-imaging, python-opengl, python-opencv, codec di formato che si desidera scrivere, io sono su Ubuntu 14.04 64-bit):

def snap(): 
    pixels=[] 
    screenshot = glReadPixels(0,0,W,H,GL_RGBA,GL_UNSIGNED_BYTE) 
    snapshot = Image.frombuffer("RGBA",W,H),screenshot,"raw","RGBA",0,0) 
    snapshot.save(os.path.dirname(videoPath) + "/temp.jpg") 
    load = cv2.cv.LoadImage(os.path.dirname(videoPath) + "/temp.jpg") 
    cv2.cv.WriteFrame(videoWriter,load) 

Qui W e H sono le dimensioni della finestra (larghezza altezza). Quello che sta accadendo è che sto usando PIL per convertire i pixel grezzi letti dal comando glReadPixels in un'immagine JPEG. Sto caricando quel file JPEG nell'immagine openCV e scrivo sul videowriter. Stavo riscontrando alcuni problemi utilizzando direttamente l'immagine PIL nel videowriter (che farebbe risparmiare milioni di cicli di clock di I/O), ma al momento non ci sto lavorando. Image è un modulo PILcv2 è un modulo python-opencv.

+0

Anziché utilizzare 'ffmpeg', che è un'utilità della riga di comando per codificare il video, è necessario utilizzare' libavcodec' e 'libavformat'. Queste sono le librerie su cui 'ffmpeg' è effettivamente costruito, e ti permetteranno di codificare il video e memorizzarlo in un formato standard di streaming/interscambio (ad esempio RIFF/AVI) senza usare un programma separato. –

+0

@ AndonM.Coleman posso avere qualche riferimento su come utilizzare queste librerie perché ho anche controllato questi, non ho trovato abbastanza dati da comprendere su come lavorare – activatedgeek

risposta

7

Sembra che si stia utilizzando l'utilità della riga di comando: ffmpeg. Anziché utilizzare la riga di comando per codificare il video da una raccolta di immagini fisse, è necessario utilizzare libavcodec e libavformat. Queste sono le librerie su cui è effettivamente costruito lo standard ffmpeg e consentono di codificare il video e memorizzarlo in un formato standard di streaming/interscambio (ad esempio RIFF/AVI) senza utilizzare un programma separato.

Probabilmente non troverai molti tutorial sull'implementazione di questo perché è sempre stato il caso che le persone volessero usare ffmpeg per andare dall'altra parte; cioè, decodifica vari formati video per la visualizzazione in OpenGL. Penso che questo cambierà molto presto con l'introduzione della codifica video di gameplay sulle console PS4 e Xbox One, improvvisamente la richiesta di questa funzionalità salirà alle stelle.

Il processo generale è questo, però:

  1. selezionamento un formato contenitore e CODEC
    • Spesso si deciderà l'altro, (ad esempioMPEG-2 + MPEG Program Stream)
  2. Inizia il riempimento di un buffer con le cornici ancora
  3. periodicamente codificare il buffer di fotogrammi ancora e scrivere la vostra uscita (scrittura a pacchetto in termini MPEG)
    • È lo farà sia quando il buffer diventa pieno, sia ogni n-molti ms; potresti preferire l'uno sull'altro a seconda che tu voglia trasmettere il tuo video dal vivo o meno.
  4. Quando il programma termina svuotare il buffer e chiudere il flusso di

Una cosa bella di questo è che non effettivamente bisogno di scrivere in un file. Dato che periodicamente codifichi pacchetti di dati dal tuo buffer di fotogrammi fissi, puoi trasmettere il tuo video codificato su una rete, se vuoi; per questo motivo il formato di codec e di contenitore (di interscambio) è separato.

Un'altra cosa bella è che non è necessario sincronizzare CPU e GPU, è possibile impostare un oggetto buffer di pixel e disporre di dati OpenGL nella memoria della CPU un paio di frame dietro la GPU. Ciò rende la codifica video in tempo reale molto meno impegnativa, basta codificare e scaricare il video su disco o in rete periodicamente se le richieste di latenza video non sono irragionevoli. Funziona molto bene con il rendering in tempo reale, dal momento che si dispone di un pool di dati abbastanza grande da mantenere una codifica occupata dal thread della CPU in ogni momento.

I frame di codifica possono essere eseguiti anche in tempo reale sulla GPU e hanno una memoria sufficiente per un ampio buffer di frame (dal momento che in definitiva i dati codificati devono essere copiati dalla GPU alla CPU e si desidera farlo il più raramente possibile). Ovviamente questo non viene fatto usando ffmpeg, ci sono librerie specializzate usando gli shader CUDA/OpenCL/compute per questo scopo. Non li ho mai usati, ma esistono.

Per motivi di portabilità, è necessario attenersi a libavcodec e Pixel Buffer Objects per GPU asincrona-> copia CPU. Al giorno d'oggi le CPU hanno abbastanza core che è possibile ottenere senza codifica assistita da GPU se si bufferano abbastanza frame e si codificano in più thread simultanei (questo crea sovraccarico di sincronizzazione aggiunto e latenza aumentata durante l'output del video codificato) o semplicemente drop frame/risoluzione inferiore (la soluzione del povero uomo).

Ci sono molti concetti qui trattati che vanno ben oltre lo scopo di SDL, ma hai chiesto come fare questo con prestazioni migliori rispetto alla tua attuale soluzione. In breve, utilizzare OpenGL Pixel Buffer Objects per trasferire i dati e libavcodec per la codifica. Un file example application che codifica il video può essere trovato nella pagina ffmpeg libavcodec examples.

+0

Grazie! E sì, per il momento la mia priorità era far funzionare il video writer, lo cambierò più tardi in PBO. – activatedgeek

+0

Se senti che Andon ti ha aiutato, dovresti ringraziarlo segnando la sua risposta come risposta. –

+0

Pensavo che l'up-voting non bastasse a questa risposta ben scritta. Grazie mille Andon! @activatedgeek: Raccolgo dalla tua risposta che questa risposta è accettabile per te, dovresti contrassegnarla come tale! – alok

4

Per alcuni test rapidi, qualcosa come il seguente codice funziona (verificato), le finestre ridimensionabili non sono gestite.

#include <stdio.h> 
FILE *avconv = NULL; 
... 
/* initialize */ 
avconv = popen("avconv -y -f rawvideo -s 800x600 -pix_fmt rgb24 -r 25 -i - -vf vflip -an -b:v 1000k test.mp4", "w"); 
... 
/* save */ 
glReadPixels(0, 0, 800, 600, GL_RGB, GL_UNSIGNED_BYTE, pixels); 
if (avconv) 
    fwrite(pixels ,800*600*3 , 1, avconv); 
... 
/* term */ 
if (avconv) 
    pclose(avconv); 
+0

Avevo bisogno di aprire il file come binario usando '" wb "' affinché funzioni. –

+0

cos'è la variabile 'pixels'? puoi dare un esempio completo? –

+0

@AlirezaAfzalaghaei 'pixels' è un puntatore a un buffer per un frame, 800 pixel di larghezza, 600 pixel di altezza, 3 byte per pixel,' glReadPixels' lo popola con il contenuto del buffer di rendering corrente. 'pixel di pixel non firmati [800 * 600 * 3]' o 'void * pixel = malloc (800 * 600 * 3)' dovrebbe fare – Alex

2

esempio mpg Runnable con FFmpeg 2,7

Spiegazione ed un esempio di superset a: https://stackoverflow.com/a/14324292/895245

Considerare https://github.com/FFmpeg/FFmpeg/blob/n3.0/doc/examples/muxing.c per generare un formato contenuto.

#include <assert.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#define GL_GLEXT_PROTOTYPES 1 
#include <GL/gl.h> 
#include <GL/glu.h> 
#include <GL/glut.h> 
#include <GL/glext.h> 

#include <libavcodec/avcodec.h> 
#include <libavutil/imgutils.h> 
#include <libavutil/opt.h> 
#include <libswscale/swscale.h> 

enum Constants { SCREENSHOT_MAX_FILENAME = 256 }; 
static GLubyte *pixels = NULL; 
static GLuint fbo; 
static GLuint rbo_color; 
static GLuint rbo_depth; 
static const unsigned int HEIGHT = 100; 
static const unsigned int WIDTH = 100; 
static int offscreen = 1; 
static unsigned int max_nframes = 100; 
static unsigned int nframes = 0; 
static unsigned int time0; 

/* Model. */ 
static double angle; 
static double delta_angle; 

/* Adapted from: https://github.com/cirosantilli/cpp-cheat/blob/19044698f91fefa9cb75328c44f7a487d336b541/ffmpeg/encode.c */ 
static AVCodecContext *c = NULL; 
static AVFrame *frame; 
static AVPacket pkt; 
static FILE *file; 
static struct SwsContext *sws_context = NULL; 
static uint8_t *rgb = NULL; 

static void ffmpeg_encoder_set_frame_yuv_from_rgb(uint8_t *rgb) { 
    const int in_linesize[1] = { 4 * c->width }; 
    sws_context = sws_getCachedContext(sws_context, 
      c->width, c->height, AV_PIX_FMT_RGB32, 
      c->width, c->height, AV_PIX_FMT_YUV420P, 
      0, NULL, NULL, NULL); 
    sws_scale(sws_context, (const uint8_t * const *)&rgb, in_linesize, 0, 
      c->height, frame->data, frame->linesize); 
} 

void ffmpeg_encoder_start(const char *filename, int codec_id, int fps, int width, int height) { 
    AVCodec *codec; 
    int ret; 
    avcodec_register_all(); 
    codec = avcodec_find_encoder(codec_id); 
    if (!codec) { 
     fprintf(stderr, "Codec not found\n"); 
     exit(1); 
    } 
    c = avcodec_alloc_context3(codec); 
    if (!c) { 
     fprintf(stderr, "Could not allocate video codec context\n"); 
     exit(1); 
    } 
    c->bit_rate = 400000; 
    c->width = width; 
    c->height = height; 
    c->time_base.num = 1; 
    c->time_base.den = fps; 
    c->gop_size = 10; 
    c->max_b_frames = 1; 
    c->pix_fmt = AV_PIX_FMT_YUV420P; 
    if (codec_id == AV_CODEC_ID_H264) 
     av_opt_set(c->priv_data, "preset", "slow", 0); 
    if (avcodec_open2(c, codec, NULL) < 0) { 
     fprintf(stderr, "Could not open codec\n"); 
     exit(1); 
    } 
    file = fopen(filename, "wb"); 
    if (!file) { 
     fprintf(stderr, "Could not open %s\n", filename); 
     exit(1); 
    } 
    frame = av_frame_alloc(); 
    if (!frame) { 
     fprintf(stderr, "Could not allocate video frame\n"); 
     exit(1); 
    } 
    frame->format = c->pix_fmt; 
    frame->width = c->width; 
    frame->height = c->height; 
    ret = av_image_alloc(frame->data, frame->linesize, c->width, c->height, c->pix_fmt, 32); 
    if (ret < 0) { 
     fprintf(stderr, "Could not allocate raw picture buffer\n"); 
     exit(1); 
    } 
} 

void ffmpeg_encoder_finish(void) { 
    uint8_t endcode[] = { 0, 0, 1, 0xb7 }; 
    int got_output, ret; 
    do { 
     fflush(stdout); 
     ret = avcodec_encode_video2(c, &pkt, NULL, &got_output); 
     if (ret < 0) { 
      fprintf(stderr, "Error encoding frame\n"); 
      exit(1); 
     } 
     if (got_output) { 
      fwrite(pkt.data, 1, pkt.size, file); 
      av_packet_unref(&pkt); 
     } 
    } while (got_output); 
    fwrite(endcode, 1, sizeof(endcode), file); 
    fclose(file); 
    avcodec_close(c); 
    av_free(c); 
    av_freep(&frame->data[0]); 
    av_frame_free(&frame); 
} 

void ffmpeg_encoder_encode_frame(uint8_t *rgb) { 
    int ret, got_output; 
    ffmpeg_encoder_set_frame_yuv_from_rgb(rgb); 
    av_init_packet(&pkt); 
    pkt.data = NULL; 
    pkt.size = 0; 
    ret = avcodec_encode_video2(c, &pkt, frame, &got_output); 
    if (ret < 0) { 
     fprintf(stderr, "Error encoding frame\n"); 
     exit(1); 
    } 
    if (got_output) { 
     fwrite(pkt.data, 1, pkt.size, file); 
     av_packet_unref(&pkt); 
    } 
} 

void ffmpeg_encoder_glread_rgb(uint8_t **rgb, GLubyte **pixels, unsigned int width, unsigned int height) { 
    size_t i, j, k, cur_gl, cur_rgb, nvals; 
    const size_t format_nchannels = 4; 
    nvals = format_nchannels * width * height; 
    *pixels = realloc(*pixels, nvals * sizeof(GLubyte)); 
    *rgb = realloc(*rgb, nvals * sizeof(uint8_t)); 
    /* Get RGBA to align to 32 bits instead of just 24 for RGB. May be faster for FFmpeg. */ 
    glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, *pixels); 
    for (i = 0; i < height; i++) { 
     for (j = 0; j < width; j++) { 
      cur_gl = format_nchannels * (width * (height - i - 1) + j); 
      cur_rgb = format_nchannels * (width * i + j); 
      for (k = 0; k < format_nchannels; k++) 
       (*rgb)[cur_rgb + k] = (*pixels)[cur_gl + k]; 
     } 
    } 
} 

static int model_init(void) { 
    angle = 0; 
    delta_angle = 1; 
} 

static int model_update(void) { 
    angle += delta_angle; 
    return 0; 
} 

static int model_finished(void) { 
    return nframes >= max_nframes; 
} 

static void init(void) { 
    int glget; 

    if (offscreen) { 
     /* Framebuffer */ 
     glGenFramebuffers(1, &fbo); 
     glBindFramebuffer(GL_FRAMEBUFFER, fbo); 

     /* Color renderbuffer. */ 
     glGenRenderbuffers(1, &rbo_color); 
     glBindRenderbuffer(GL_RENDERBUFFER, rbo_color); 
     /* Storage must be one of: */ 
     /* GL_RGBA4, GL_RGB565, GL_RGB5_A1, GL_DEPTH_COMPONENT16, GL_STENCIL_INDEX8. */ 
     glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB565, WIDTH, HEIGHT); 
     glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo_color); 

     /* Depth renderbuffer. */ 
     glGenRenderbuffers(1, &rbo_depth); 
     glBindRenderbuffer(GL_RENDERBUFFER, rbo_depth); 
     glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, WIDTH, HEIGHT); 
     glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rbo_depth); 

     glReadBuffer(GL_COLOR_ATTACHMENT0); 

     /* Sanity check. */ 
     assert(glCheckFramebufferStatus(GL_FRAMEBUFFER)); 
     glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &glget); 
     assert(WIDTH * HEIGHT < (unsigned int)glget); 
    } else { 
     glReadBuffer(GL_BACK); 
    } 

    glClearColor(0.0, 0.0, 0.0, 0.0); 
    glEnable(GL_DEPTH_TEST); 
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 
    glViewport(0, 0, WIDTH, HEIGHT); 
    glMatrixMode(GL_PROJECTION); 
    glLoadIdentity(); 
    glMatrixMode(GL_MODELVIEW); 

    time0 = glutGet(GLUT_ELAPSED_TIME); 
    model_init(); 
    ffmpeg_encoder_start("tmp.mpg", AV_CODEC_ID_MPEG1VIDEO, 25, WIDTH, HEIGHT); 
} 

static void deinit(void) { 
    printf("FPS = %f\n", 1000.0 * nframes/(double)(glutGet(GLUT_ELAPSED_TIME) - time0)); 
    free(pixels); 
    ffmpeg_encoder_finish(); 
    free(rgb); 
    if (offscreen) { 
     glDeleteFramebuffers(1, &fbo); 
     glDeleteRenderbuffers(1, &rbo_color); 
     glDeleteRenderbuffers(1, &rbo_depth); 
    } 
} 

static void draw_scene(void) { 
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
    glLoadIdentity(); 
    glRotatef(angle, 0.0f, 0.0f, -1.0f); 
    glBegin(GL_TRIANGLES); 
    glColor3f(1.0f, 0.0f, 0.0f); 
    glVertex3f(0.0f, 0.5f, 0.0f); 
    glColor3f(0.0f, 1.0f, 0.0f); 
    glVertex3f(-0.5f, -0.5f, 0.0f); 
    glColor3f(0.0f, 0.0f, 1.0f); 
    glVertex3f(0.5f, -0.5f, 0.0f); 
    glEnd(); 
} 

static void display(void) { 
    char extension[SCREENSHOT_MAX_FILENAME]; 
    char filename[SCREENSHOT_MAX_FILENAME]; 
    draw_scene(); 
    if (offscreen) { 
     glFlush(); 
    } else { 
     glutSwapBuffers(); 
    } 
    frame->pts = nframes; 
    ffmpeg_encoder_glread_rgb(&rgb, &pixels, WIDTH, HEIGHT); 
    ffmpeg_encoder_encode_frame(rgb); 
    nframes++; 
    if (model_finished()) 
     exit(EXIT_SUCCESS); 
} 

static void idle(void) { 
    while (model_update()); 
    glutPostRedisplay(); 
} 

int main(int argc, char **argv) { 
    GLint glut_display; 
    glutInit(&argc, argv); 
    if (argc > 1) 
     offscreen = 0; 
    if (offscreen) { 
     /* TODO: if we use anything smaller than the window, it only renders a smaller version of things. */ 
     /*glutInitWindowSize(50, 50);*/ 
     glutInitWindowSize(WIDTH, HEIGHT); 
     glut_display = GLUT_SINGLE; 
    } else { 
     glutInitWindowSize(WIDTH, HEIGHT); 
     glutInitWindowPosition(100, 100); 
     glut_display = GLUT_DOUBLE; 
    } 
    glutInitDisplayMode(glut_display | GLUT_RGBA | GLUT_DEPTH); 
    glutCreateWindow(argv[0]); 
    if (offscreen) { 
     /* TODO: if we hide the window the program blocks. */ 
     /*glutHideWindow();*/ 
    } 
    init(); 
    glutDisplayFunc(display); 
    glutIdleFunc(idle); 
    atexit(deinit); 
    glutMainLoop(); 
    return EXIT_SUCCESS; 
} 
+0

Per qualche motivo, quando uso la tua soluzione, salva solo in basso parte sinistra della finestra. –

+0

@EugeneKolesnikov ringrazia per la segnalazione. Ho appena eseguito il suo sul mio Ubuntu 17.10 e ha funzionato. Fammi sapere se riesci a eseguire il debug. –

Problemi correlati