tags:

views:

236

answers:

2

How to write bitmaps as frames to Ogg Theora in C\C++?

Some Examples with source would be grate!)

+1  A: 

Here's the libtheora API and example code.

Here's a micro howto that shows how to use the theora binaries. As the encoder reads raw, uncompressed 'yuv4mpeg' data for video you could use that from your app, too by piping the video frames to the encoder.

lothar
+1  A: 

The entire solution is a little lengthy to post on here as a code sample, but if you download libtheora from Xiph.org, there is an example png2theora. All of the library functions I am about to mention can be found in the documentation on Xiph.org for theora and ogg.

  1. Call th_info_init() to initialise a th_info structure, then set up you output parameters by assigning the appropriate members in that.
  2. Use that structure in a call to th_encode_alloc() to get an encoder context
  3. Initialise an ogg stream, with ogg_stream_init()
  4. Initialise a blank th_comment structure using th_comment_init

Iterate through the following:

  1. Call th_encode_flushheader with the the encoder context, the blank comment structure and an ogg_packet.
  2. Send the resulting packet to the ogg stream with ogg_stream_packetin()

Until th_encode_flushheader returns 0 (or an error code)

Now, repeatedly call ogg_stream_pageout(), every time writing the page.header and then page.body to an output file, until it returns 0. Now call ogg_stream_flush and write the resulting page to the file.

You can now write frames to the encoder. Here is how I did it:

int theora_write_frame(int outputFd, unsigned long w, unsigned long h, unsigned char *yuv_y, unsigned char *yuv_u, unsigned char *yuv_v, int last)
{
  th_ycbcr_buffer ycbcr;
  ogg_packet op;
  ogg_page og;

  unsigned long yuv_w;
  unsigned long yuv_h;

  /* Must hold: yuv_w >= w */
  yuv_w = (w + 15) & ~15;
  /* Must hold: yuv_h >= h */
  yuv_h = (h + 15) & ~15;

  //Fill out the ycbcr buffer
  ycbcr[0].width = yuv_w;
  ycbcr[0].height = yuv_h;
  ycbcr[0].stride = yuv_w;
  ycbcr[1].width = yuv_w;
  ycbcr[1].stride = ycbcr[1].width;
  ycbcr[1].height = yuv_h;
  ycbcr[2].width = ycbcr[1].width;
  ycbcr[2].stride = ycbcr[1].stride;
  ycbcr[2].height = ycbcr[1].height;

  if(encoderInfo->pixel_fmt == TH_PF_420)
  {
    //Chroma is decimated by 2 in both directions
    ycbcr[1].width = yuv_w >> 1;
    ycbcr[2].width = yuv_w >> 1;
    ycbcr[1].height = yuv_h >> 1;
    ycbcr[2].height = yuv_h >> 1;
  }else if(encoderInfo->pixel_fmt == TH_PF_422)
  {
    ycbcr[1].width = yuv_w >> 1;
    ycbcr[2].width = yuv_w >> 1;
  }else if(encoderInfo->pixel_fmt != TH_PF_422)
  {
    //Then we have an unknown pixel format
    //We don't know how long the arrays are!
    fprintf(stderr, "[theora_write_frame] Unknown pixel format in writeFrame!\n");
    return -1;
  }

  ycbcr[0].data = yuv_y;
  ycbcr[1].data = yuv_u;
  ycbcr[2].data = yuv_v;

  /* Theora is a one-frame-in,one-frame-out system; submit a frame
     for compression and pull out the packet */
  if(th_encode_ycbcr_in(encoderContext, ycbcr)) {
    fprintf(stderr, "[theora_write_frame] Error: could not encode frame\n");
    return -1;
  }

  if(!th_encode_packetout(encoderContext, last, &op)) {
    fprintf(stderr, "[theora_write_frame] Error: could not read packets\n");
    return -1;
  }

  ogg_stream_packetin(&theoraStreamState, &op);
  ssize_t bytesWritten = 0;
  int pagesOut = 0;
  while(ogg_stream_pageout(&theoraStreamState, &og)) {
    pagesOut ++;
    bytesWritten = write(outputFd, og.header, og.header_len);
    if(bytesWritten != og.header_len)
    {
      fprintf(stderr, "[theora_write_frame] Error: Could not write to file\n");
      return -1;
    }
    bytesWritten = write(outputFd, og.body, og.body_len);
    if(bytesWritten != og.body_len)
    {
      bytesWritten = fprintf(stderr, "[theora_write_frame] Error: Could not write to file\n");
      return -1;
    }
  }
  return pagesOut;
}

Where encoderInfo is the th_info structure used to initialise the encoder (static in the data section for me).

On your last frame, setting the last frame on th_encode_packetout() will make sure the stream terminates properly.

Once your done, just make sure to clean up (closing fds mainly). th_info_clear() will clear the th_info structure, and th_encode_free() will free your encoder context.

Obviously, you'll need to convert your bitmap into YUV planes before you can pass them to theora_write_frame().

Hope this is of some help. Good luck!

Thalex