//============================================================================= // // File : KviOggTheoraDecoder.cpp // Creation date : Sat Nov 21 2009 22:53:21 CEST by Fabio Bas // // This file is part of the KVIrc IRC client distribution // Copyright (C) 2009 Fabio Bas (ctrlaltca at libero dot it) // // This program is FREE software. You can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the HOPE that it will be USEFUL, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, write to the Free Software Foundation, // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // Based on the example encoder/player from Xiph.Org Foundation: //============================================================================= // // function: example encoder application; makes an Ogg Theora/Vorbis // file from YUV4MPEG2 and WAV input // last mod: $Id: encoder_example.c 16517 2009-08-25 19:48:57Z giles $ // // // function: example SDL player application; plays Ogg Theora files (with // optional Vorbis audio second stream) // last mod: $Id: player_example.c 16628 2009-10-10 05:50:52Z gmaxwell $ // #ifndef COMPILE_DISABLE_OGG_THEORA #include #include #include "KviMemory.h" #include "KviOggTheoraDecoder.h" #include "KviOggIrcText.h" #include "KviDataBuffer.h" using namespace KviOggIrcText; #define OC_CLAMP255(_x) ((unsigned char)((((_x) < 0) - 1) & ((_x) | -((_x) > 255)))) KviOggTheoraDecoder::KviOggTheoraDecoder(KviDataBuffer * videoSignal, KviDataBuffer * textSignal) { m_pVideoSignal = videoSignal; m_pTextSignal = textSignal; theora_p = 0; stateflag = 0; ts = nullptr; thda = false; thtic = false; irct_p = 0; /* single frame video buffering */ videobuf_ready = 0; videobuf_granulepos = -1; videobuf_time = 0; /* start up Ogg stream synchronization layer */ ogg_sync_init(&oy); /* init supporting Theora structures needed in header parsing */ th_comment_init(&tc); th_info_init(&ti); for(int i = 0; i < 256; i++) { lu_Y[i] = 2441889 * (i - 16) + 1048576; lu_R[i] = 3347111 * (i - 128); lu_GV[i] = -1704917 * (i - 128); lu_GU[i] = -821585 * (i - 128); lu_B[i] = 4230442 * (i - 128); } } KviOggTheoraDecoder::~KviOggTheoraDecoder() { if(theora_p) { ogg_stream_clear(&to); th_decode_free(td); th_comment_clear(&tc); th_info_clear(&ti); } if(irct_p) { ogg_stream_clear(&zo); irct_encode_clear(); } ogg_sync_clear(&oy); } void KviOggTheoraDecoder::addData(KviDataBuffer * stream) { //qDebug("adddata signal %p stream size %d",m_pVideoSignal, stream->size() ); if(stream->size() == 0) return; /* Ogg file open; parse the headers */ if(!stateflag) { char * buffer = ogg_sync_buffer(&oy, stream->size()); memcpy(buffer, stream->data(), stream->size()); ogg_sync_wrote(&oy, stream->size()); stream->clear(); while(ogg_sync_pageout(&oy, &og) > 0) { ogg_stream_state test; /* is this a mandated initial header? If not, stop parsing */ if(!ogg_page_bos(&og)) { /* don't leak the page; get it into the appropriate stream */ //qDebug("queue_page && return"); queue_page(&og); stateflag = 1; return; } ogg_stream_init(&test, ogg_page_serialno(&og)); ogg_stream_pagein(&test, &og); ogg_stream_packetout(&test, &op); /* identify the codec: try theora */ if(!theora_p && th_decode_headerin(&ti, &tc, &ts, &op) >= 0) { qDebug("is theora, ts=%p &ts=%p", ts, &ts); /* it is theora */ memcpy(&to, &test, sizeof(test)); theora_p = 1; } else if(!irct_p && irct_decode_headerin(&op) >= 0) { qDebug("is irct"); /* it is irct */ memcpy(&zo, &test, sizeof(test)); irct_p = 1; } else { //qDebug("is other"); /* whatever it is, we don't care about it */ ogg_stream_clear(&test); } } /* fall through to non-bos page parsing */ } //qDebug("checkpoint 2, theora_p=%d ",theora_p); /* we're expecting more header packets. */ if((theora_p && theora_p < 3)) { int ret; /* look for further theora headers */ while(theora_p && (theora_p < 3) && (ret = ogg_stream_packetout(&to, &op))) { if(ret < 0) { qDebug("Error parsing Theora stream headers; corrupt stream?"); return; } if(!th_decode_headerin(&ti, &tc, &ts, &op)) { qDebug("Error parsing Theora stream headers; corrupt stream?"); return; } theora_p++; } /* The header pages/packets will arrive before anything else we care about, or the stream is not obeying spec */ if(ogg_sync_pageout(&oy, &og) > 0) { qDebug("queue_page"); queue_page(&og); /* demux into the appropriate stream */ } else { qDebug("TheoraDecoder: need more data!"); //need more data return; } } //qDebug("checkpoint 3"); /* and now we have it all. initialize decoders */ if(theora_p && !thda) { thda = true; td = th_decode_alloc(&ti, ts); qDebug("Ogg logical stream %lx is Theora %dx%d %.02f fps", to.serialno, ti.pic_width, ti.pic_height, (double)ti.fps_numerator / ti.fps_denominator); geometry.pic_w = ti.pic_width; geometry.pic_h = ti.pic_height; geometry.frame_w = ti.frame_width; geometry.frame_h = ti.frame_height; geometry.pic_x = ti.pic_x; geometry.pic_y = ti.pic_y; px_fmt = ti.pixel_fmt; switch(ti.pixel_fmt) { case TH_PF_444: qDebug(" 4:4:4 video"); break; default: qDebug(" video (UNSUPPORTED Chroma sampling!)"); return; break; } th_decode_ctl(td, TH_DECCTL_GET_PPLEVEL_MAX, &pp_level_max, sizeof(pp_level_max)); pp_level = pp_level_max; th_decode_ctl(td, TH_DECCTL_SET_PPLEVEL, &pp_level, sizeof(pp_level)); pp_inc = 0; int w = ((ti.pic_x + ti.pic_width + 1) & ~1) - (ti.pic_x & ~1); int h = ((ti.pic_y + ti.pic_height + 1) & ~1) - (ti.pic_y & ~1); RGBbuffer = (unsigned char *)calloc(sizeof(char), w * h * 4); } else if(!thtic) { thtic = true; /* tear down the partial theora setup */ th_info_clear(&ti); th_comment_clear(&tc); th_setup_free(ts); } // stateflag=0; /* playback has not begun */ if(irct_p) { if(ogg_stream_packetout(&zo, &op) > 0) { char * textPkt = nullptr; int textSize = 0; if(irct_decode_packetin(&textPkt, &textSize, &op) == 0) { m_pTextSignal->append((unsigned char *)textPkt, textSize); } } } if(theora_p && !videobuf_ready) { if(stream->size() > 0) { char * buffer = ogg_sync_buffer(&oy, stream->size()); memcpy(buffer, stream->data(), stream->size()); ogg_sync_wrote(&oy, stream->size()); stream->clear(); while(ogg_sync_pageout(&oy, &og) > 0) queue_page(&og); } //qDebug("loop4 theora_p=%d videobuf_ready=%d",theora_p, videobuf_ready); // theora is one in, one out... if(ogg_stream_packetout(&to, &op) > 0) { if(th_decode_packetin(td, &op, &videobuf_granulepos) == 0) videobuf_ready = 1; } else { //qDebug("ogg_stream_packetout <=0"); //need more data :) return; } } if(!videobuf_ready) return; if(stateflag && videobuf_ready) { video_write(); videobuf_ready = 0; } // if our buffers either don't exist or are ready to go,we can begin playback if(!theora_p || videobuf_ready) stateflag = 1; } /* helper: push a page into the appropriate steam */ /* this can be done blindly; a stream won't accept a page that doesn't belong to it */ int KviOggTheoraDecoder::queue_page(ogg_page * page) { if(theora_p) ogg_stream_pagein(&to, page); if(irct_p) ogg_stream_pagein(&zo, page); return 0; } void KviOggTheoraDecoder::video_write(void) { th_ycbcr_buffer yuv; int y_offset; th_decode_ycbcr_out(td, yuv); y_offset = (geometry.pic_x & ~1) + yuv[0].stride * (geometry.pic_y & ~1); for(int i = 0; i < geometry.pic_h; i++) { unsigned char * in_y = (unsigned char *)yuv[0].data + y_offset + yuv[0].stride * i; unsigned char * in_u = (unsigned char *)yuv[1].data + y_offset + yuv[1].stride * i; unsigned char * in_v = (unsigned char *)yuv[2].data + y_offset + yuv[2].stride * i; unsigned char * out = RGBbuffer + (geometry.pic_w * i * ARGB32_BPP); for(int j = 0; j < geometry.pic_w; j++) { int r, g, b; int y = lu_Y[in_y[j]]; b = (y + lu_B[in_u[j]]) >> 21; out[j * 4] = OC_CLAMP255(b); g = (y + lu_GV[in_v[j]] + lu_GU[in_u[j]]) >> 21; out[j * 4 + 1] = OC_CLAMP255(g); r = (y + lu_R[in_v[j]]) >> 21; out[j * 4 + 2] = OC_CLAMP255(r); out[j * 4 + 3] = 255; //alpha } } int size = sizeof(char) * geometry.pic_w * geometry.pic_h * ARGB32_BPP; //qDebug("VIDEO WRITE size=%d",size); m_pVideoSignal->clear(); m_pVideoSignal->append(RGBbuffer, size); } #endif // COMPILE_DISABLE_OGG_THEORA