videoEncoderTheora.cpp
Engine/source/gfx/video/videoEncoderTheora.cpp
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