2013-09-28 48 views
9

我目前正試圖將openGL中製作的動畫保存爲視頻文件。我曾嘗試使用openCVvideowriter,但沒有任何優勢。我已成功地使用SDL庫生成快照並將其保存爲bmp。如果我保存所有快照,然後使用ffmpeg生成視頻,那就像是收集4 GB的圖像。不實際。 如何在渲染過程中直接寫視頻幀? 這裏我用的時候我需要拍攝快照代碼:將openGL上下文保存爲視頻輸出

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); 
} 

我需要的視頻輸出。我發現ffmpeg可以用來創建來自C++代碼的視頻,但一直沒能弄清楚這個過程。請幫忙!

編輯:我一直在使用openCVCvVideoWriter類嘗試,但程序崩潰(「segmentation fault」),目前它是declared.Compilation ofcourse顯示沒有錯誤。對此有何建議?

FOR Python用戶解決方案(需要Python2.7python-imagingpython-openglpython-opencv,你要寫入格式的編解碼器,我是在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) 

這裏WH是窗口尺寸(寬度,高度)。發生了什麼是我正在使用PIL將從glReadPixels命令讀取的原始像素轉換爲JPEG圖像。我將該JPEG加載到openCV圖像並寫入錄像機。我通過直接在錄像機中使用PIL圖像(這可以節省數百萬個時鐘週期的I/O),但是現在我沒有處理這些問題。 ImagePIL模塊cv2python-opencv模塊。

+0

而不是使用'ffmpeg',這是一個命令行工具來編碼視頻,你應該使用'libavcodec'和'libavformat'。這些是實際構建'ffmpeg'的庫,並且允許您對視頻進行編碼並將其以標準流/交換格式(例如RIFF/AVI)存儲,而無需使用單獨的程序。 –

+0

@ AndonM.Coleman我可以對如何使用這些庫有一些參考,因爲我甚至檢查了這些,找不到足夠的數據來了解如何工作 – activatedgeek

回答

7

聽起來好像您正在使用命令行實用程序:ffmpeg。您應該使用libavcodeclibavformat,而不是使用命令行對靜態圖像集合中的視頻進行編碼。這些是ffmpeg實際構建的庫,並且允許您對視頻進行編碼並將其以標準流/交換格式(例如RIFF/AVI)存儲,而無需使用單獨的程序。

你可能不會找到很多關於實現這個教程的教程,因爲傳統上人們想用ffmpeg去換個方法;即解碼各種視頻格式以在OpenGL中顯示。我認爲隨着將遊戲玩法視頻編碼引入到PS4和Xbox One遊戲機中,這種情況將很快發生變化,突然對這種功能的需求將會猛增。

的一般過程是這樣的,但是:

  1. 選擇一個容器格式和CODEC
    • 通常一個將決定,另外,(例如MPEG-2 + MPEG程序流)
  2. 開始填充你的靜幀
  3. 緩衝定期編碼您仍然幀緩衝區,並寫信給你輸出MPEG方面(數據包寫入)
    • 你將在緩衝區變滿或每隔n-ms時執行此操作;您可能更喜歡一個取決於您是否要直播您的視頻。
  4. 當你的程序終止刷新緩衝區,並關閉流

一個好處有關,這是你實際上並不需要寫一個文件。由於您定期編碼來自靜幀緩衝區的數據包,因此如果需要,您可以通過網絡流式傳輸已編碼的視頻 - 這就是爲什麼編解碼器和容器(互換)格式是分開的原因。

另一件好事是你不必同步CPU和GPU,你可以設置一個像素緩衝區對象,讓OpenGL將數據複製到CPU內存後面的GPU中。這使得對視頻的實時編碼要求更低,只有在視頻延遲要求不是不合理的情況下,您才需要定期對視頻進行編碼並將視頻刷新到磁盤或網絡。這在實時渲染中非常有效,因爲您擁有足夠大的數據池來隨時保持CPU線程繁忙的編碼。

編碼幀甚至可以在GPU上實時完成,爲大量幀緩衝提供足夠的存儲空間(因爲最終編碼數據必須從GPU複製到CPU,並且您希望儘可能不頻繁地執行此操作)。很顯然,這不是使用ffmpeg完成的,因此有專門的庫爲此使用CUDA/OpenCL /計算着色器。我從來沒有使用過它們,但它們確實存在。

爲便於使用,您應該使用libavcodec和像素緩衝區對象來進行異步GPU-> CPU複製。如果緩衝足夠的幀並在多個併發線程中進行編碼(這會增加同步開銷並增加輸出編碼視頻時的延遲),或者只是丟幀/降低分辨率窮人的解決方案)。

這裏介紹的很多概念遠遠超出了SDL的範圍,但您確實要求如何以比目前的解決方案更好的性能來實現這一點。總之,使用OpenGL像素緩衝區對象來傳輸數據,並使用libavcodec進行編碼。編碼視頻的example application可在ffmpeg libavcodec examples頁面找到。

+0

謝謝!是的,目前我的首要任務是讓視頻作家工作,後來我會將其改爲PBO。 – activatedgeek

+0

如果你覺得安東幫了你,你應該感謝他,並把他的答覆標記爲答案。 –

+0

我認爲投票不足以滿足這個很好的書面答案。非常感謝Andon! @activatedgeek:我從你的回答中收集到這個答案對你是可以接受的,你應該把它標記爲這樣! – alok

4

對於像下面的代碼工作(測試)一些快速測試,可調整大小的窗口是未處理的。

#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

我需要使用''wb「''''''''作爲二進制文件打開這個文件才能運行。 –

+0

什麼是像素變量?你能舉一個完整的例子嗎? –

+0

@AlirezaAfzalaghaei'像素'是指向幀緩衝區的指針,800像素寬,600像素高,每像素3字節,'glReadPixels'填充當前渲染緩衝區的內容。 '無符號字符像素[800 * 600 * 3]'或'void *像素= malloc(800 * 600 * 3)'應該做 – Alex

2

與FFmpeg的2.7

說明,並在一個超集例如可運行MPG例如:https://stackoverflow.com/a/14324292/895245

考慮https://github.com/FFmpeg/FFmpeg/blob/n3.0/doc/examples/muxing.c以生成包含格式。

#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

由於某種原因,當我使用你的解決方案時,窗戶的四分之一。 –

+0

@EugeneKolesnikov感謝他的報告。我剛剛在Ubunu 17.10上運行了它,並且它工作正常。讓我知道你是否設法調試它。 –