2013-09-28 46 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上运行了它,并且它工作正常。让我知道你是否设法调试它。 –