Torque3D Documentation / _generateds / videoEncoderTheora.cpp

videoEncoderTheora.cpp

Engine/source/gfx/video/videoEncoderTheora.cpp

More...

Detailed Description

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#ifdef TORQUE_OGGTHEORA
 25
 26#include "videoCapture.h"
 27#include "core/stream/fileStream.h"
 28#include "console/console.h"
 29#include "gfx/bitmap/gBitmap.h"
 30#include "gfx/bitmap/bitmapUtils.h"
 31
 32#include "platform/threads/thread.h"
 33#include "platform/threads/threadSafeDeque.h"
 34#include "platform/threads/semaphore.h"
 35
 36#include "theora/theoraenc.h"
 37#include "vorbis/codec.h"
 38#include "vorbis/vorbisenc.h"
 39
 40#include "platform/profiler.h"
 41
 42//#define THEORA_ENCODER_SINGLE_THREAD
 43
 44// These are the coefficient lookup tables, so we don't need to multiply
 45dALIGN( static S32 sYRGB[ 256 ][ 4 ] );
 46dALIGN( static S32 sURGB[ 256 ][ 4 ] );
 47dALIGN( static S32 sVRGB[ 256 ][ 4 ] );
 48
 49/// Initialize the lookup tables used in the RGB-YUV transcoding
 50static void initLookupTables()
 51{
 52   static bool sGenerated = false;
 53   if( !sGenerated )
 54   {
 55      for( S32 i = 0; i < 256; ++ i )
 56      {
 57         //Y = ( (  66 * R + 129 * G +  25 * B + 128) >> 8) +  16
 58         //U = ( ( -38 * R -  74 * G + 112 * B + 128) >> 8) + 128
 59         //V = ( ( 112 * R -  94 * G -  18 * B + 128) >> 8) + 128
 60
 61         // Y coefficients
 62         sYRGB[ i ][ 0 ] =  66 * i;
 63         sYRGB[ i ][ 1 ] = 129 * i;
 64         sYRGB[ i ][ 2 ] =  25 * i + 128 + (16 << 8);
 65
 66         // U coefficients
 67         sURGB[ i ][ 0 ] = -38 * i;
 68         sURGB[ i ][ 1 ] = -74 * i;
 69         sURGB[ i ][ 2 ] = 112 * i + 128 + (128 << 8);
 70
 71         // V coefficients
 72         sVRGB[ i ][ 0 ] = 112 * i;
 73         sVRGB[ i ][ 1 ] = -94 * i;
 74         sVRGB[ i ][ 2 ] = -18 * i + 128 + (128 << 8);
 75      }
 76      sGenerated = true;
 77   }
 78}
 79
 80/// Theora video capture encoder
 81class VideoEncoderTheora : public VideoEncoder, public Thread
 82{
 83   U32 mCurrentFrame;
 84   
 85   ogg_stream_state to; // take physical pages, weld into a logical stream of packets   
 86   th_enc_ctx      *td; // Theora encoder context
 87   th_info          ti; // Theora info structure
 88   th_comment       tc; // Theora comment structure
 89   
 90   FileStream mFile;    // Output file
 91
 92   th_ycbcr_buffer mBuffer; // YCbCr buffer
 93      
 94   GBitmap* mLastFrame;
 95
 96   ThreadSafeDeque< GBitmap*> mFrameBitmapList; // List with unprocessed frame bitmaps   
 97   Semaphore  mSemaphore;    //Semaphore for preventing the encoder from being lagged behind the game
 98
 99   bool mErrorStatus; //Status flag, true if OK, false if an error ocurred
100   
101   /// Sets our error status
102   bool setStatus(bool status)
103   {
104      mErrorStatus = status;
105      return mErrorStatus;
106   }
107   bool getStatus() { return mErrorStatus; }
108
109   /// Encodes one frame
110   void encodeFrame( GBitmap* bitmap, bool isLast=false )
111   {        
112      PROFILE_SCOPE(Theora_encodeFrame);
113      
114      //Copy bitmap to YUV buffer
115      copyBitmapToYUV420(bitmap);      
116            
117      PROFILE_START(th_encode_ycbcr_in);
118      //Submit frame for encoding
119      if (th_encode_ycbcr_in(td, mBuffer))
120      {
121         Platform::outputDebugString("VideoEncoderTheora::encodeFrame() - The buffer size does not match the frame size the encoder was initialized with, or encoding has already completed. .");
122         setStatus(false);
123         PROFILE_END();
124         return;
125      }      
126      PROFILE_END();
127            
128      //Fetch the encoded packets
129      ogg_packet packet;
130      if (!th_encode_packetout(td, isLast, &packet))
131      {
132         Platform::outputDebugString("VideoEncoderTheora::encodeFrame() - Internal Theora library error.");
133         setStatus(false);
134         return;
135      }
136
137      //Insert packet into vorbis stream page
138      ogg_stream_packetin(&to,&packet);
139
140      //Increment
141      mCurrentFrame++;
142
143      //Is there a video page flushed? 
144      ogg_page videopage;        
145      while (ogg_stream_pageout(&to,&videopage))
146      {
147         //Write the video page to disk
148         mFile.write(videopage.header_len, videopage.header);
149         mFile.write(videopage.body_len, videopage.body);
150
151         F64 videotime = th_granule_time(td,ogg_page_granulepos(&videopage));
152         if (videotime > 0)
153         {            
154            S32 hundredths=(int)(videotime*100-(long)videotime*100);
155            S32 seconds=(long)videotime%60;
156            Platform::outputDebugString("Encoding time %g %02i.%02i", videotime, seconds, hundredths);
157         }
158      }
159
160      mSemaphore.release();
161   }
162   
163   bool process(bool ending)
164   {
165      if (!getStatus())
166         return false;
167
168      //Try getting a bitmap for encoding         
169      GBitmap* bitmap = NULL;      
170      if (mFrameBitmapList.tryPopFront(bitmap))   
171      {         
172         encodeFrame(bitmap, false);
173      }
174            
175      //Delete previous bitmap
176      if (!ending && bitmap)
177      {
178         if (mLastFrame)
179            pushProcessedBitmap(mLastFrame);
180         mLastFrame = bitmap;
181      }
182
183      //If we're stopping encoding, but didn't have a frame, re-encode the last frame
184      if (ending && !bitmap && mLastFrame)
185      {
186         encodeFrame(mLastFrame, true);         
187         pushProcessedBitmap(mLastFrame);
188         mLastFrame = NULL;
189      }
190
191      // We'll live while we have a last frame
192      return (mLastFrame != NULL);
193   }
194   
195public:
196   VideoEncoderTheora() :
197      mCurrentFrame(0), td(NULL), mLastFrame(NULL)
198   {
199      dMemset(&to, 0, sizeof(to));
200      dMemset(&ti, 0, sizeof(ti));
201      dMemset(&tc, 0, sizeof(tc));
202      dMemset(&mBuffer, 0, sizeof(mBuffer));
203
204      setStatus(false);      
205   }
206      
207   virtual void run( void* arg )
208   {      
209      _setName( "TheoraEncoderThread" );
210      while (!checkForStop())
211         process(false);
212
213      // Encode all pending frames and close the last one
214      while (process(true));
215   }   
216
217   /// Begins accepting frames for encoding
218   bool begin()
219   {
220      mPath += ".ogv";
221      mCurrentFrame = 0;
222            
223      //Try opening the file for writing
224      if ( !mFile.open( mPath, Torque::FS::File::Write ) )
225      {
226         Platform::outputDebugString( "VideoEncoderTheora::begin() - Failed to open output file '%s'!", mPath.c_str() );         
227         return setStatus(false);
228      }
229
230      ogg_stream_init(&to, Platform::getRandom() * S32_MAX);
231
232      ogg_packet op;
233
234      th_info_init(&ti);
235      ti.frame_width  = mResolution.x;
236      ti.frame_height = mResolution.y;
237      ti.pic_width    = mResolution.x;
238      ti.pic_height   = mResolution.y;
239      ti.pic_x        = 0;
240      ti.pic_y        = 0;
241
242      ti.fps_numerator      = (int)(mFramerate * 1000.0f);
243      ti.fps_denominator    = 1000;
244
245      ti.aspect_numerator   = 0;
246      ti.aspect_denominator = 0;
247      ti.colorspace = TH_CS_UNSPECIFIED;
248      
249      ti.target_bitrate = 0;
250      ti.quality        = 63;
251      ti.pixel_fmt      = TH_PF_420;
252            
253      td = th_encode_alloc(&ti);
254      if (td == NULL)
255      {
256         Platform::outputDebugString("VideoEncoderTheora::begin() - Theora initialization error.");   
257         return setStatus(false);
258      }
259
260      th_info_clear(&ti);              
261
262      // This is needed for youtube compatibility
263      S32 vp3_compatible = 1;
264      th_encode_ctl(td, TH_ENCCTL_SET_VP3_COMPATIBLE, &vp3_compatible, sizeof(vp3_compatible));      
265      
266      // Set the encoder to max speed
267      S32 speed_max;      
268      S32 ret;
269      ret = th_encode_ctl(td, TH_ENCCTL_GET_SPLEVEL_MAX, &speed_max, sizeof(speed_max));
270      if(ret<0){
271         Platform::outputDebugString("VideoEncoderTheora::begin() - could not determine maximum speed level.");
272         speed_max = 0;
273      }
274      ret = th_encode_ctl(td, TH_ENCCTL_SET_SPLEVEL, &speed_max, sizeof(speed_max));
275
276      // write the bitstream header packets with proper page interleave
277      th_comment_init(&tc);
278
279      // first packet will get its own page automatically
280      if(th_encode_flushheader(td,&tc,&op) <= 0)
281      {
282         Platform::outputDebugString("VideoEncoderTheora::begin() - Internal Theora library error.");
283         return setStatus(false);
284      }
285
286      ogg_page og;
287      ogg_stream_packetin(&to,&op);
288      if(ogg_stream_pageout(&to,&og) != 1)
289      {         
290         Platform::outputDebugString("VideoEncoderTheora::begin() - Internal Ogg library error.");
291         return setStatus(false);
292      }
293      mFile.write(og.header_len, og.header);
294      mFile.write(og.body_len, og.body);
295
296      // create the remaining theora headers    
297      while((ret = th_encode_flushheader(td,&tc,&op)) != 0)
298      {         
299         if(ret < 0)
300         {
301            Platform::outputDebugString("VideoEncoderTheora::begin() - Internal Theora library error.");
302            return setStatus(false);
303         }
304         ogg_stream_packetin(&to,&op);
305      }
306
307      // Flush the rest of our headers. This ensures
308      // the actual data in each stream will start
309      // on a new page, as per spec.
310      while((ret = ogg_stream_flush(&to,&og)) != 0)
311      {         
312         if(ret < 0)
313         {
314            Platform::outputDebugString("VideoEncoderTheora::begin() - Internal Ogg library error.");
315            return setStatus(false);
316         }
317         mFile.write(og.header_len, og.header);
318         mFile.write(og.body_len, og.body);
319      }
320
321      //Initialize the YUV buffer 
322      S32 decimation[] = {0,1,1};
323      for (U32 i=0; i<3; i++)
324      {
325         mBuffer[i].width  = mResolution.x >> decimation[i];
326         mBuffer[i].height = mResolution.y >> decimation[i];
327         mBuffer[i].stride = mBuffer[i].width * sizeof(U8);
328         mBuffer[i].data = new U8[mBuffer[i].width*mBuffer[i].height*sizeof(U8)];
329      }      
330
331      //Initialize the YUV coefficient lookup tables
332      initLookupTables();
333
334      setStatus(true);
335
336#ifndef THEORA_ENCODER_SINGLE_THREAD
337      start();
338#endif
339      
340      return getStatus();
341   }
342
343   /// Pushes a new frame into the video stream
344   bool pushFrame( GBitmap * bitmap )
345   {      
346                  
347      // Push the bitmap into the frame list
348      mFrameBitmapList.pushBack( bitmap );
349
350      mSemaphore.acquire();
351
352#ifdef THEORA_ENCODER_SINGLE_THREAD
353      process(false);
354#endif
355      
356      return getStatus();
357   }
358
359   /// Finishes the encoding and closes the video
360   bool end()
361   {  
362      //Let's wait the thread stop doing whatever it needs to do
363      stop();
364      join();
365
366#ifdef THEORA_ENCODER_SINGLE_THREAD
367      while (process(true));
368#endif
369            
370      th_encode_free(td);
371      ogg_stream_clear(&to);
372      th_comment_clear(&tc);
373
374      mFile.close();
375      return true;
376   }
377   
378
379   void setResolution( Point2I* resolution ) 
380   { 
381      /* Theora has a divisible-by-sixteen restriction for the encoded frame size */
382      /* scale the picture size up to the nearest /16 and calculate offsets */
383      resolution->x = (resolution->x) + 15 & ~0xF;
384      resolution->y = (resolution->y) + 15 & ~0xF;
385
386      mResolution = *resolution; 
387   }
388   
389   /// Converts the bitmap to YUV420 and copies it into our internal buffer
390   void copyBitmapToYUV420( GBitmap* bitmap )
391   {
392      PROFILE_SCOPE(copyBitmapToYUV420);
393
394      // Convert luma
395      const U8* rgb = bitmap->getBits();           
396      
397      // Chroma planes are half width and half height
398      U32 w = mResolution.x / 2;
399      U32 h = mResolution.y / 2;      
400
401      // We'll update two luminance rows at once
402      U8* yuv_y0 = mBuffer[0].data;
403      U8* yuv_y1 = mBuffer[0].data + mBuffer[0].stride;
404
405      // Get pointers to chroma planes
406      U8* yuv_u = mBuffer[1].data;
407      U8* yuv_v = mBuffer[2].data;
408
409      // We'll also need to read two RGB rows at once
410      U32 rgbStride = mResolution.x * bitmap->getBytesPerPixel();
411      const U8* row0 = rgb;
412      const U8* row1 = row0 + rgbStride;
413
414      for(U32 y = 0; y < h; y++) 
415      {         
416         for(U32 x = 0; x < w; x++) 
417         {
418            // Fetch two RGB samples from each RGB row (for downsampling the chroma)
419            U8 r0 = *row0++;
420            U8 g0 = *row0++;
421            U8 b0 = *row0++;
422            U8 r1 = *row0++;
423            U8 g1 = *row0++;
424            U8 b1 = *row0++;
425            U8 r2 = *row1++;
426            U8 g2 = *row1++;
427            U8 b2 = *row1++;
428            U8 r3 = *row1++;
429            U8 g3 = *row1++;
430            U8 b3 = *row1++;
431
432            // Convert the four RGB samples into four luminance samples            
433            *yuv_y0 = ( (sYRGB[r0][0] + sYRGB[g0][1] + sYRGB[b0][2]) >> 8);
434            yuv_y0++;
435            *yuv_y0 = ( (sYRGB[r1][0] + sYRGB[g1][1] + sYRGB[b1][2]) >> 8);
436            yuv_y0++;
437            *yuv_y1 = ( (sYRGB[r2][0] + sYRGB[g2][1] + sYRGB[b2][2]) >> 8);
438            yuv_y1++;
439            *yuv_y1 = ( (sYRGB[r3][0] + sYRGB[g3][1] + sYRGB[b3][2]) >> 8);
440            yuv_y1++;
441
442            // Downsample the four RGB samples
443            U8 r = (r0 + r1 + r2 + r3) >> 2;
444            U8 g = (g0 + g1 + g2 + g3) >> 2;
445            U8 b = (b0 + b1 + b2 + b3) >> 2;
446
447            // Convert downsampled RGB into chroma
448            *yuv_u = ( (sURGB[r][0] + sURGB[g][1] + sURGB[b][2]) >> 8);
449            *yuv_v = ( (sVRGB[r][0] + sVRGB[g][1] + sVRGB[b][2]) >> 8);
450            yuv_u++;
451            yuv_v++;
452         }
453
454         //Next RGB rows
455         row0 += rgbStride;
456         row1 += rgbStride;
457
458         //Next luminance rows
459         yuv_y0 += mBuffer[0].stride;
460         yuv_y1 += mBuffer[0].stride;
461      }      
462   }
463};
464
465REGISTER_VIDEO_ENCODER(VideoEncoderTheora, THEORA)
466
467#endif
468