double Oggeyman::get_packet_time() { if (ogg_theora_packet.granulepos >= 0) { theora_control(&theo_state, TH_DECCTL_SET_GRANPOS, &(ogg_theora_packet.granulepos), sizeof(ogg_theora_packet.granulepos)); } ogg_int64_t videobuf_granulepos = theo_state.granulepos; return theora_granule_time(&theo_state, videobuf_granulepos); }
/* * We're forced to use a dirty hack here, due to Theora's idiotic API * Theora needs three separate pieces of data, called headers to initialize * its internal decoder structure. After all three pieces have been received, * we can call theora_decode_init. * We use a counter and a flag to make sure we have decoded our three headers and then * we call theora_decode_init so we can initialize a theora_state structure. * We use the ts structure to convert a granule position into an actual timestamp. * There are many ways in which this can fail, but we rely on having all three headers * at the beginning of the ogg video bitstream. * * To whoever came up with this convoluted scheme: please consider a change of careers. */ int read_theora_cb(OGGZ *oggz, ogg_packet *op, long serialno, void *data) { struct op_node *node; struct theora_headers *th; long timestamp = 0; //mylog("Got theora packet, serialno=%d, size=%d, packetno=%lld, granulepos=%lld\n", // serialno, op->bytes, op->packetno, op->granulepos); th = (struct theora_headers *)video_stream->data; if ( theora_packet_isheader(op) ) { theora_decode_header(&(th->ti), &(th->tc), op); th->header_count++; } if ( th->header_count >= 3 && !th->have_headers ) { theora_decode_init(&(th->ts), &(th->ti)); th->have_headers = 1; } if ( th->have_headers ) { double d; d = theora_granule_time(&(th->ts), op->granulepos); timestamp = (long)(d * 1000); } if ( timestamp < 0 ) { timestamp = video_stream->page_ts + video_stream->page_count * THEORA_FRAME_DURATION; video_stream->page_count++; } else { video_stream->page_ts = timestamp; video_stream->page_count = 0; } if ( !theora_packet_isheader(op) ) { node = create_node(op, serialno, timestamp); append_node(video_stream, node); } return 0; }
int DGVideo::_prepareFrame() { while (_state == DGVideoPlaying) { while (_theoraInfo->theora_p && !_theoraInfo->videobuf_ready) { if (ogg_stream_packetout(&_theoraInfo->to, &_theoraInfo->op) > 0) { theora_decode_packetin(&_theoraInfo->td, &_theoraInfo->op); _theoraInfo->videobuf_granulepos = _theoraInfo->td.granulepos; _theoraInfo->videobuf_time = theora_granule_time(&_theoraInfo->td, _theoraInfo->videobuf_granulepos); _theoraInfo->videobuf_ready = 1; if (!_theoraInfo->bos) { _theoraInfo->bos = _theoraInfo->td.granulepos; } } else break; } if (!_theoraInfo->videobuf_ready && feof(_handle)) { if (_isLoopable) { // This is the begin of stream (granule position) * 8 bits (in bytes) fseek(_handle, (long)_theoraInfo->bos * 8, SEEK_SET); ogg_stream_reset(&_theoraInfo->to); } else { this->stop(); } break; } if (!_theoraInfo->videobuf_ready) { _bufferData(&_theoraInfo->oy); while (ogg_sync_pageout(&_theoraInfo->oy, &_theoraInfo->og) > 0) { _queuePage(_theoraInfo, &_theoraInfo->og); } } else { _theoraInfo->videobuf_ready = 0; return 1; } _theoraInfo->videobuf_ready = 0; } return 0; }
static GstClockTime theora_enc_get_ogg_packet_end_time (GstTheoraEnc * enc, ogg_packet * op) { ogg_int64_t end_granule; /* FIXME: remove this hack once we depend on libtheora >= 1.0beta1 */ if (G_UNLIKELY (use_old_granulepos)) { /* This is where we hack around theora's broken idea of what granulepos * is -- normally we wouldn't need to add the 1, because granulepos * should be the presentation time of the last sample in the packet, but * theora starts with 0 instead of 1... (update: this only applies to old * bitstream/theora versions, this is fixed with bitstream version 3.2.1) */ end_granule = granulepos_add (op->granulepos, 1, enc->granule_shift); } else { end_granule = op->granulepos; } return theora_granule_time (&enc->state, end_granule) * GST_SECOND; }
/** ** Play a video file. ** ** @param name Filename of movie file. ** ** @return Non-zero if file isn't a supported movie. */ int PlayMovie(const std::string &name) { OggData data; CFile f; SDL_Rect rect; SDL_Overlay *yuv_overlay; CSample *sample; const EventCallback *old_callbacks; EventCallback callbacks; unsigned int start_ticks; int need_data; int diff; char buffer[PATH_MAX]; LibraryFileName(name.c_str(), buffer, sizeof(buffer)); if (f.open(buffer, CL_OPEN_READ) == -1) { fprintf(stderr, "Can't open file `%s'\n", name.c_str()); return -1; } memset(&data, 0, sizeof(data)); if (OggInit(&f, &data) || !data.video) { OggFree(&data); f.close(); return -1; } data.File = &f; if (data.tinfo.frame_width * 300 / 4 > data.tinfo.frame_height * 100) { rect.w = Video.Width; rect.h = Video.Width * data.tinfo.frame_height / data.tinfo.frame_width; rect.x = 0; rect.y = (Video.Height - rect.h) / 2; } else { rect.w = Video.Height * data.tinfo.frame_width / data.tinfo.frame_height; rect.h = Video.Height; rect.x = (Video.Width - rect.w) / 2; rect.y = 0; } yuv_overlay = SDL_CreateYUVOverlay(data.tinfo.frame_width, data.tinfo.frame_height, SDL_YV12_OVERLAY, TheScreen); if (yuv_overlay == NULL) { fprintf(stderr, "SDL_CreateYUVOverlay: %s\n", SDL_GetError()); OggFree(&data); f.close(); return 0; } StopMusic(); if ((sample = LoadVorbis(buffer, PlayAudioStream))) { if ((sample->Channels != 1 && sample->Channels != 2) || sample->SampleSize != 16) { fprintf(stderr, "Unsupported sound format in movie\n"); delete sample; SDL_FreeYUVOverlay(yuv_overlay); OggFree(&data); f.close(); return 0; } PlayMusic(sample); } callbacks.ButtonPressed = MovieCallbackButtonPressed; callbacks.ButtonReleased = MovieCallbackButtonReleased; callbacks.MouseMoved = MovieCallbackMouseMove; callbacks.MouseExit = MovieCallbackMouseExit; callbacks.KeyPressed = MovieCallbackKeyPressed; callbacks.KeyReleased = MovieCallbackKeyReleased; callbacks.KeyRepeated = MovieCallbackKeyRepeated; callbacks.NetworkEvent = NetworkEvent; old_callbacks = GetCallbacks(); SetCallbacks(&callbacks); Invalidate(); RealizeVideoMemory(); MovieStop = false; start_ticks = SDL_GetTicks(); need_data = 1; while (!MovieStop) { if (need_data) { if (TheoraProcessData(&data)) { break; } need_data = 0; } diff = SDL_GetTicks() - start_ticks - static_cast<int>( theora_granule_time(&data.tstate, data.tstate.granulepos) * 1000); if (diff > 100) { // too far behind, skip some frames need_data = 1; continue; } if (diff > 0) { OutputTheora(&data, yuv_overlay, &rect); need_data = 1; } WaitEventsOneFrame(); } StopMusic(); SDL_FreeYUVOverlay(yuv_overlay); OggFree(&data); f.close(); SetCallbacks(old_callbacks); return 0; }
static int granulepos_test_encode (int frequency, int auto_p) { theora_info ti; theora_state th; int result; int frame, tframe, keyframe, keydist; int shift; double rate, ttime; yuv_buffer yuv; unsigned char *framedata; ogg_packet op; long long int last_granule = -1; /* INFO ("+ Initializing theora_info struct"); */ theora_info_init (&ti); ti.width = 32; ti.height = 32; ti.frame_width = ti.width; ti.frame_height = ti.frame_height; ti.offset_x = 0; ti.offset_y = 0; ti.fps_numerator = 16; ti.fps_denominator = 1; ti.aspect_numerator = 1; ti.aspect_denominator = 1; ti.colorspace = OC_CS_UNSPECIFIED; ti.pixelformat = OC_PF_420; ti.target_bitrate = 0; ti.quality = 16; ti.dropframes_p = 0; ti.quick_p = 1; /* check variations of automatic or forced keyframe choice */ ti.keyframe_auto_p = auto_p; /* check with variations of the maximum gap */ ti.keyframe_frequency = frequency; ti.keyframe_frequency_force = frequency; ti.keyframe_data_target_bitrate = ti.target_bitrate * 1.5; ti.keyframe_auto_threshold = 80; ti.keyframe_mindistance = MIN(8, frequency); ti.noise_sensitivity = 1; /* INFO ("+ Initializing theora_state for encoding"); */ result = theora_encode_init (&th, &ti); if (result == OC_DISABLED) { INFO ("+ Clearing theora_state"); theora_clear (&th); } else if (result < 0) { FAIL ("negative return code initializing encoder"); } /* INFO ("+ Setting up dummy 4:2:0 frame data"); */ framedata = calloc(ti.height, ti.width); yuv.y_width = ti.width; yuv.y_height = ti.height; yuv.y_stride = ti.width; yuv.y = framedata; yuv.uv_width = ti.width / 2; yuv.uv_height = ti.width / 2; yuv.uv_stride = ti.width; yuv.u = framedata; yuv.v = framedata; INFO ("+ Checking granulepos generation"); shift = theora_granule_shift(&ti); rate = (double)ti.fps_denominator/ti.fps_numerator; for (frame = 0; frame < frequency * 2 + 1; frame++) { result = theora_encode_YUVin (&th, &yuv); if (result < 0) { printf("theora_encode_YUVin() returned %d\n", result); FAIL ("negative error code submitting frame for compression"); } theora_encode_packetout (&th, frame >= frequency * 2, &op); if ((long long int)op.granulepos < last_granule) FAIL ("encoder returned a decreasing granulepos value"); last_granule = op.granulepos; keyframe = op.granulepos >> shift; keydist = op.granulepos - (keyframe << shift); tframe = theora_granule_frame (&th, op.granulepos); ttime = theora_granule_time(&th, op.granulepos); #if DEBUG printf("++ frame %d granulepos %lld %d:%d %d %.3lfs\n", frame, (long long int)op.granulepos, keyframe, keydist, tframe, theora_granule_time (&th, op.granulepos)); #endif if ((keyframe + keydist) != frame + 1) FAIL ("encoder granulepos does not map to the correct frame number"); if (tframe != frame) FAIL ("theora_granule_frame returned incorrect results"); if (fabs(rate*(frame+1) - ttime) > 1.0e-6) FAIL ("theora_granule_time returned incorrect results"); } /* clean up */ /* INFO ("+ Freeing dummy frame data"); */ free (framedata); /* INFO ("+ Clearing theora_info struct"); */ theora_info_clear (&ti); /* INFO ("+ Clearing theora_state"); */ theora_clear (&th); return 0; }
int main(int argc,char *argv[]){ int i,j; ogg_packet op; FILE *infile = stdin; #ifdef _WIN32 /* We need to set stdin/stdout to binary mode. Damn windows. */ /* Beware the evil ifdef. We avoid these where we can, but this one we cannot. Don't add any more, you'll probably go to hell if you do. */ _setmode( _fileno( stdin ), _O_BINARY ); #endif /* open the input file if any */ if(argc==2){ infile=fopen(argv[1],"rb"); if(infile==NULL){ fprintf(stderr,"Unable to open '%s' for playback.\n", argv[1]); exit(1); } } if(argc>2){ usage(); exit(1); } /* start up Ogg stream synchronization layer */ ogg_sync_init(&oy); /* init supporting Vorbis structures needed in header parsing */ vorbis_info_init(&vi); vorbis_comment_init(&vc); /* init supporting Theora structures needed in header parsing */ theora_comment_init(&tc); theora_info_init(&ti); /* Ogg file open; parse the headers */ /* Only interested in Vorbis/Theora streams */ while(!stateflag){ int ret=buffer_data(infile,&oy); if(ret==0)break; 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 */ queue_page(&og); stateflag=1; break; } 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 && theora_decode_header(&ti,&tc,&op)>=0){ /* it is theora */ memcpy(&to,&test,sizeof(test)); theora_p=1; }else if(!vorbis_p && vorbis_synthesis_headerin(&vi,&vc,&op)>=0){ /* it is vorbis */ memcpy(&vo,&test,sizeof(test)); vorbis_p=1; }else{ /* whatever it is, we don't care about it */ ogg_stream_clear(&test); } } /* fall through to non-bos page parsing */ } /* we're expecting more header packets. */ while((theora_p && theora_p<3) || (vorbis_p && vorbis_p<3)){ int ret; /* look for further theora headers */ while(theora_p && (theora_p<3) && (ret=ogg_stream_packetout(&to,&op))){ if(ret<0){ fprintf(stderr,"Error parsing Theora stream headers; corrupt stream?\n"); exit(1); } if(theora_decode_header(&ti,&tc,&op)){ printf("Error parsing Theora stream headers; corrupt stream?\n"); exit(1); } theora_p++; if(theora_p==3)break; } /* look for more vorbis header packets */ while(vorbis_p && (vorbis_p<3) && (ret=ogg_stream_packetout(&vo,&op))){ if(ret<0){ fprintf(stderr,"Error parsing Vorbis stream headers; corrupt stream?\n"); exit(1); } if(vorbis_synthesis_headerin(&vi,&vc,&op)){ fprintf(stderr,"Error parsing Vorbis stream headers; corrupt stream?\n"); exit(1); } vorbis_p++; if(vorbis_p==3)break; } /* 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){ queue_page(&og); /* demux into the appropriate stream */ }else{ int ret=buffer_data(infile,&oy); /* someone needs more data */ if(ret==0){ fprintf(stderr,"End of file while searching for codec headers.\n"); exit(1); } } } /* and now we have it all. initialize decoders */ if(theora_p){ theora_decode_init(&td,&ti); printf("Ogg logical stream %x is Theora %dx%d %.02f fps video\n", (unsigned int)to.serialno,ti.width,ti.height, (double)ti.fps_numerator/ti.fps_denominator); if(ti.width!=ti.frame_width || ti.height!=ti.frame_height) printf(" Frame content is %dx%d with offset (%d,%d).\n", ti.frame_width, ti.frame_height, ti.offset_x, ti.offset_y); report_colorspace(&ti); dump_comments(&tc); }else{ /* tear down the partial theora setup */ theora_info_clear(&ti); theora_comment_clear(&tc); } if(vorbis_p){ vorbis_synthesis_init(&vd,&vi); vorbis_block_init(&vd,&vb); fprintf(stderr,"Ogg logical stream %x is Vorbis %d channel %d Hz audio.\n", (unsigned int)vo.serialno,vi.channels,(int)vi.rate); }else{ /* tear down the partial vorbis setup */ vorbis_info_clear(&vi); vorbis_comment_clear(&vc); } /* open audio */ if(vorbis_p)open_audio(); /* open video */ if(theora_p)open_video(); /* install signal handler as SDL clobbered the default */ signal (SIGINT, sigint_handler); /* on to the main decode loop. We assume in this example that audio and video start roughly together, and don't begin playback until we have a start frame for both. This is not necessarily a valid assumption in Ogg A/V streams! It will always be true of the example_encoder (and most streams) though. */ stateflag=0; /* playback has not begun */ while(!got_sigint){ /* we want a video and audio frame ready to go at all times. If we have to buffer incoming, buffer the compressed data (ie, let ogg do the buffering) */ while(vorbis_p && !audiobuf_ready){ int ret; float **pcm; /* if there's pending, decoded audio, grab it */ if((ret=vorbis_synthesis_pcmout(&vd,&pcm))>0){ int count=audiobuf_fill/2; int maxsamples=(audiofd_fragsize-audiobuf_fill)/2/vi.channels; for(i=0;i<ret && i<maxsamples;i++) for(j=0;j<vi.channels;j++){ int val=rint(pcm[j][i]*32767.f); if(val>32767)val=32767; if(val<-32768)val=-32768; audiobuf[count++]=val; } vorbis_synthesis_read(&vd,i); audiobuf_fill+=i*vi.channels*2; if(audiobuf_fill==audiofd_fragsize)audiobuf_ready=1; if(vd.granulepos>=0) audiobuf_granulepos=vd.granulepos-ret+i; else audiobuf_granulepos+=i; }else{ /* no pending audio; is there a pending packet to decode? */ if(ogg_stream_packetout(&vo,&op)>0){ if(vorbis_synthesis(&vb,&op)==0) /* test for success! */ vorbis_synthesis_blockin(&vd,&vb); }else /* we need more data; break out to suck in another page */ break; } } while(theora_p && !videobuf_ready){ /* theora is one in, one out... */ if(ogg_stream_packetout(&to,&op)>0){ theora_decode_packetin(&td,&op); videobuf_granulepos=td.granulepos; videobuf_time=theora_granule_time(&td,videobuf_granulepos); /* is it already too old to be useful? This is only actually useful cosmetically after a SIGSTOP. Note that we have to decode the frame even if we don't show it (for now) due to keyframing. Soon enough libtheora will be able to deal with non-keyframe seeks. */ if(videobuf_time>=get_time()) videobuf_ready=1; }else break; } if(!videobuf_ready && !audiobuf_ready && feof(infile))break; if(!videobuf_ready || !audiobuf_ready){ /* no data yet for somebody. Grab another page */ int bytes=buffer_data(infile,&oy); while(ogg_sync_pageout(&oy,&og)>0){ queue_page(&og); } } /* If playback has begun, top audio buffer off immediately. */ if(stateflag) audio_write_nonblocking(); /* are we at or past time for this video frame? */ if(stateflag && videobuf_ready && videobuf_time<=get_time()){ video_write(); videobuf_ready=0; } if(stateflag && (audiobuf_ready || !vorbis_p) && (videobuf_ready || !theora_p) && !got_sigint){ /* we have an audio frame ready (which means the audio buffer is full), it's not time to play video, so wait until one of the audio buffer is ready or it's near time to play video */ /* set up select wait on the audiobuffer and a timeout for video */ struct timeval timeout; fd_set writefs; fd_set empty; int n=0; FD_ZERO(&writefs); FD_ZERO(&empty); if(audiofd>=0){ FD_SET(audiofd,&writefs); n=audiofd+1; } if(theora_p){ long milliseconds=(videobuf_time-get_time())*1000-5; if(milliseconds>500)milliseconds=500; if(milliseconds>0){ timeout.tv_sec=milliseconds/1000; timeout.tv_usec=(milliseconds%1000)*1000; n=select(n,&empty,&writefs,&empty,&timeout); if(n)audio_calibrate_timer(0); } }else{ select(n,&empty,&writefs,&empty,NULL); } } /* if our buffers either don't exist or are ready to go, we can begin playback */ if((!theora_p || videobuf_ready) && (!vorbis_p || audiobuf_ready))stateflag=1; /* same if we've run out of input */ if(feof(infile))stateflag=1; } /* tear it all down */ audio_close(); SDL_Quit(); if(vorbis_p){ ogg_stream_clear(&vo); vorbis_block_clear(&vb); vorbis_dsp_clear(&vd); vorbis_comment_clear(&vc); vorbis_info_clear(&vi); } if(theora_p){ ogg_stream_clear(&to); theora_clear(&td); theora_comment_clear(&tc); theora_info_clear(&ti); } ogg_sync_clear(&oy); if(infile && infile!=stdin)fclose(infile); fprintf(stderr, "\r " "\nDone.\n"); return(0); }
/** ** Play a video file. ** ** @param name Filename of movie file. ** ** @return Non-zero if file isn't a supported movie. */ int PlayMovie(const std::string &name) { int videoWidth, videoHeight; #if defined(USE_OPENGL) || defined(USE_GLES) videoWidth = Video.ViewportWidth; videoHeight = Video.ViewportHeight; #else videoWidth = Video.Width; videoHeight = Video.Height; #endif const std::string filename = LibraryFileName(name.c_str()); CFile f; if (f.open(filename.c_str(), CL_OPEN_READ) == -1) { fprintf(stderr, "Can't open file '%s'\n", name.c_str()); return 0; } OggData data; memset(&data, 0, sizeof(data)); if (OggInit(&f, &data) || !data.video) { OggFree(&data); f.close(); return -1; } data.File = &f; SDL_Rect rect; if (data.tinfo.frame_width * 300 / 4 > data.tinfo.frame_height * 100) { rect.w = videoWidth; rect.h = videoWidth * data.tinfo.frame_height / data.tinfo.frame_width; rect.x = 0; rect.y = (videoHeight - rect.h) / 2; } else { rect.w = videoHeight * data.tinfo.frame_width / data.tinfo.frame_height; rect.h = videoHeight; rect.x = (videoWidth - rect.w) / 2; rect.y = 0; } #ifdef USE_OPENGL // When SDL_OPENGL is used, it is not possible to call SDL_CreateYUVOverlay, so turn temporary OpenGL off // With GLES is all ok if (UseOpenGL) { SDL_SetVideoMode(Video.ViewportWidth, Video.ViewportHeight, Video.Depth, SDL_GetVideoSurface()->flags & ~SDL_OPENGL); } #endif SDL_FillRect(SDL_GetVideoSurface(), NULL, 0); Video.ClearScreen(); SDL_Overlay *yuv_overlay = SDL_CreateYUVOverlay(data.tinfo.frame_width, data.tinfo.frame_height, SDL_YV12_OVERLAY, TheScreen); if (yuv_overlay == NULL) { fprintf(stderr, "SDL_CreateYUVOverlay: %s\n", SDL_GetError()); OggFree(&data); f.close(); return 0; } StopMusic(); CSample *sample = LoadVorbis(filename.c_str(), PlayAudioStream); if (sample) { if ((sample->Channels != 1 && sample->Channels != 2) || sample->SampleSize != 16) { fprintf(stderr, "Unsupported sound format in movie\n"); delete sample; SDL_FreeYUVOverlay(yuv_overlay); OggFree(&data); f.close(); return 0; } PlayMusic(sample); } EventCallback callbacks; callbacks.ButtonPressed = MovieCallbackButtonPressed; callbacks.ButtonReleased = MovieCallbackButtonReleased; callbacks.MouseMoved = MovieCallbackMouseMove; callbacks.MouseExit = MovieCallbackMouseExit; callbacks.KeyPressed = MovieCallbackKeyPressed; callbacks.KeyReleased = MovieCallbackKeyReleased; callbacks.KeyRepeated = MovieCallbackKeyRepeated; callbacks.NetworkEvent = NetworkEvent; const EventCallback *old_callbacks = GetCallbacks(); SetCallbacks(&callbacks); Invalidate(); RealizeVideoMemory(); MovieStop = false; const unsigned int start_ticks = SDL_GetTicks(); bool need_data = true; while (!MovieStop) { if (need_data) { if (TheoraProcessData(&data)) { break; } need_data = false; } const int diff = SDL_GetTicks() - start_ticks - static_cast<int>(theora_granule_time(&data.tstate, data.tstate.granulepos) * 1000); if (diff > 100) { // too far behind, skip some frames need_data = true; continue; } if (diff > 0) { OutputTheora(&data, yuv_overlay, &rect); need_data = true; } WaitEventsOneFrame(); } StopMusic(); SDL_FreeYUVOverlay(yuv_overlay); OggFree(&data); f.close(); #ifdef USE_OPENGL if (UseOpenGL) { SDL_SetVideoMode(Video.ViewportWidth, Video.ViewportHeight, Video.Depth, SDL_GetVideoSurface()->flags | SDL_OPENGL); ReloadOpenGL(); } #endif SetCallbacks(old_callbacks); return 0; }
bool CTheoraPlayer::playVideo( //Plays specified OGG Theora file to screen surface. //If screen == NULL, then this method will test that the file is playable //by decoding it as fast as possible but not displaying anything. // //Returns: whether playback was successful CStretchyBuffer& buffer, SDL_Surface *screen, const int x, const int y) //[default=(0,0)] { //init theora_p = vorbis_p = 0; startticks = 0; bool bSkippedLastFrame = false; // start up Ogg stream synchronization layer ogg_sync_init(&oy); // init supporting Vorbis structures needed in header parsing vorbis_info_init(&vi); vorbis_comment_init(&vc); // init supporting Theora structures needed in header parsing theora_comment_init(&tc); theora_info_init(&ti); if (!screen) ti.quick_p = 1; ti.quality = 63; if (!parseHeaders(buffer)) return false; // force audio off vorbis_p = 0; // initialize decoders if (theora_p) { theora_decode_init(&td,&ti); #if 0 printf("Ogg logical stream %x is Theora %dx%d %.02f fps video\n" " Frame content is %dx%d with offset (%d,%d).\n", to.serialno,ti.width,ti.height, (double)ti.fps_numerator/ti.fps_denominator, ti.frame_width, ti.frame_height, ti.offset_x, ti.offset_y); //report_colorspace(&ti); //we're not using this info for anything dump_comments(&tc); #endif } else { // tear down the partial theora setup theora_info_clear(&ti); theora_comment_clear(&tc); } if(vorbis_p) { vorbis_synthesis_init(&vd,&vi); vorbis_block_init(&vd,&vb); printf("Ogg logical stream %lx is Vorbis %d channel %ld Hz audio.\n", vo.serialno,vi.channels,vi.rate); } else { // tear down the partial vorbis setup vorbis_info_clear(&vi); vorbis_comment_clear(&vc); } // open audio if (vorbis_p) open_audio(); // open video SDL_Overlay *yuv_overlay = NULL; if (theora_p && screen) yuv_overlay = open_video(screen); // single frame video buffering ogg_packet op; ogg_int64_t videobuf_granulepos=-1; double videobuf_time=0; double last_frame_time = 0; bool hasdatatobuffer = true; // Main loop bool audiobuf_ready=false; bool videobuf_ready=false; bool playbackdone = (yuv_overlay == NULL); bool isPlaying = false; bool bBreakout = false; while (!playbackdone) { // break out on SDL quit event SDL_Event event; if (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: playbackdone = bBreakout = true; break; case SDL_KEYDOWN: if (event.key.keysym.sym == SDLK_ESCAPE) playbackdone = bBreakout = true; break; default: break; } } while (theora_p && !videobuf_ready) { // get one video packet... if (ogg_stream_packetout(&to,&op)>0) { theora_decode_packetin(&td,&op); videobuf_granulepos=td.granulepos; videobuf_time=theora_granule_time(&td,videobuf_granulepos); #if 0 //Without sound channels to synch to, don't need to worry about skipping frames when slow. // update the frame counter //++frameNum; // check if this frame time has not passed yet. // If the frame is late we need to decode additional // ones and keep looping, since theora at this stage // needs to decode all frames. const double now=get_time(); const double delay=videobuf_time-now; if(delay>=0.0){ /// got a good frame, not late, ready to break out videobuf_ready=true; } else if(now-last_frame_time>=1.0) { // display at least one frame per second, regardless videobuf_ready=true; } else { //Need to catch up -- no time to display frame. if (bSkippedLastFrame) //only allow skipping one frame in a row videobuf_ready = true; //show anyway else bSkippedLastFrame = true; //printf("dropping frame %d (%.3fs behind)\n", frameNum, -delay); } #else videobuf_ready = true; //show every frame #endif } else { // need more data break; } } if (!hasdatatobuffer && !videobuf_ready && !audiobuf_ready) { isPlaying = false; playbackdone = true; } //If we're set for the next frame, sleep. //In other words, don't show frames too rapidly. if((!theora_p || videobuf_ready) && (!vorbis_p || audiobuf_ready)) { const int ticks = (int)(1000*(videobuf_time-get_time())); if(ticks>0 && screen) //don't need to sleep if only testing file SDL_Delay(ticks); } if (videobuf_ready) { // time to write our cached frame if (screen) { const bool bRes = video_write(screen, yuv_overlay, x, y); if (!bRes) //couldn't display image playbackdone = bBreakout = true; } videobuf_ready=false; last_frame_time=get_time(); bSkippedLastFrame = false; // if audio has not started (first frame) then start it if ((!isPlaying)&&(vorbis_p)) { start_audio(); isPlaying = true; } } // HACK: always look for more audio data audiobuf_ready=false; // buffer compressed data every loop if (hasdatatobuffer) { hasdatatobuffer = buffer_data(&oy, buffer) > 0; if (!hasdatatobuffer) { //printf("Ogg buffering stopped, end of file reached.\n"); } } if (ogg_sync_pageout(&oy,&og)>0) queue_page(&og); } // playbackdone // show number of video frames decoded //printf("\nFrames decoded: %d\n", frameNum); // deinit if (vorbis_p) { audio_close(); ogg_stream_clear(&vo); vorbis_block_clear(&vb); vorbis_dsp_clear(&vd); vorbis_comment_clear(&vc); vorbis_info_clear(&vi); } if (theora_p) { if (yuv_overlay) SDL_FreeYUVOverlay(yuv_overlay); ogg_stream_clear(&to); theora_clear(&td); theora_comment_clear(&tc); theora_info_clear(&ti); } ogg_sync_clear(&oy); //If broken out of testing, return false since entire file was not verified. return !bBreakout || screen != NULL; }
gboolean ucil_theora_combine_av_file( const char *path, const char *codec, gboolean remove, ucil_processing_info_func_t procfunc, void *func_data, GError **error ) { FILE *f; FILE *af; FILE *vf; gchar *apath; gchar *vpath; long len; long filepos = 0; ogg_sync_state oy; ogg_stream_state tins; theora_comment tc; theora_info ti; theora_state ts; ogg_stream_state vs; vorbis_info vi; vorbis_comment vc; vorbis_dsp_state vd; vorbis_block vb; ogg_page og; ogg_packet op; ogg_packet header_comm; ogg_packet header_code; int pkt; double videopos, audiopos; f = fopen( path, "w" ); if( !f ) { gchar *name = g_filename_display_name( path ); g_set_error( error, G_FILE_ERROR, g_file_error_from_errno( errno ), _("Could not open '%s' for writing: %s"), name, g_strerror( errno ) ); g_free( name ); return FALSE; } apath = g_strconcat( path, ".atmp", NULL ); af = fopen( apath, "r" ); if( !af ) { gchar *name = g_filename_display_name( apath ); g_set_error( error, G_FILE_ERROR, g_file_error_from_errno( errno ), _("Could not open '%s' for reading: %s"), name, g_strerror( errno ) ); g_free( name ); g_free( apath ); fclose( f ); return FALSE; } vpath = g_strconcat( path, ".vtmp", NULL ); vf = fopen( vpath, "r" ); if( !vf ) { gchar *name = g_filename_display_name( vpath ); g_set_error( error, G_FILE_ERROR, g_file_error_from_errno( errno ), _("Could not open '%s' for reading: %s"), name, g_strerror( errno ) ); g_free( name ); g_free( apath ); g_free( vpath ); fclose( f ); fclose( af ); return FALSE; } fseek( af, 0L, SEEK_END ); len = ftell( af ); fseek( af, 0L, SEEK_SET ); ogg_sync_init( &oy ); theora_comment_init( &tc ); theora_info_init( &ti ); if( ogg_stream_init( &vs, 2 ) != 0 ) { g_free( apath ); g_free( vpath ); fclose( f ); fclose( af ); fclose( vf ); g_set_error( error, UCIL_ERROR, UCIL_ERROR_INVALID_STREAM, _("Failed to initialize audio stream") ); return FALSE; } vorbis_info_init( &vi ); if( vorbis_encode_init( &vi, 2, 44100, -1, 128000, -1 ) ) { g_free( apath ); g_free( vpath ); fclose( f ); fclose( af ); fclose( vf ); g_set_error( error, UCIL_ERROR, UCIL_ERROR_INVALID_STREAM, _("Failed to initialize audio encoder") ); return FALSE; } vorbis_comment_init( &vc ); vorbis_analysis_init( &vd, &vi ); vorbis_block_init( &vd, &vb ); // // write theora header page // { ogg_page og; while( ogg_sync_pageout( &oy, &og ) == 0 ) { int bytes; bytes = buffer_data( vf, &oy ); if( bytes == 0 ) { g_free( apath ); g_free( vpath ); fclose( f ); fclose( af ); fclose( vf ); g_set_error( error, UCIL_ERROR, UCIL_ERROR_INVALID_STREAM, _("Invalid video stream") ); return FALSE; } } if( !ogg_page_bos( &og ) ) { g_free( apath ); g_free( vpath ); fclose( f ); fclose( af ); fclose( vf ); g_set_error( error, UCIL_ERROR, UCIL_ERROR_INVALID_STREAM, _("Invalid video stream") ); return FALSE; } ogg_stream_init( &tins, ogg_page_serialno( &og ) ); fwrite( og.header, 1, og.header_len, f ); fwrite( og.body, 1, og.body_len, f ); if( ogg_stream_pagein( &tins, &og ) < 0 ) { g_free( apath ); g_free( vpath ); fclose( f ); fclose( af ); fclose( vf ); g_set_error( error, UCIL_ERROR, UCIL_ERROR_INVALID_STREAM, _("Invalid video stream") ); return FALSE; } pkt = 0; while( ( pkt < 3 ) && ( ogg_stream_packetout( &tins, &op ) > 0 ) ) { if( theora_decode_header( &ti, &tc, &op ) != 0 ) { g_free( apath ); g_free( vpath ); fclose( f ); fclose( af ); fclose( vf ); g_set_error( error, UCIL_ERROR, UCIL_ERROR_INVALID_STREAM, _("Invalid video stream") ); return FALSE; } pkt++; } } // // Write vorbis header page // vorbis_analysis_headerout( &vd, &vc, &op, &header_comm, &header_code ); ogg_stream_packetin( &vs, &op ); if( ogg_stream_pageout( &vs, &og ) != 1 ) { g_free( apath ); g_free( vpath ); fclose( f ); fclose( af ); fclose( vf ); g_set_error( error, UCIL_ERROR, UCIL_ERROR_INVALID_STREAM, _("Could not create audio stream") ); return FALSE; } fwrite( og.header, 1, og.header_len, f ); fwrite( og.body, 1, og.body_len, f ); ogg_stream_packetin( &vs, &header_comm ); ogg_stream_packetin( &vs, &header_code ); // write remaining theora headers for( pkt = 1; pkt < 3; ) { ogg_page og; if( ogg_sync_pageout( &oy, &og ) > 0 ) { fwrite( og.header, 1, og.header_len, f ); fwrite( og.body, 1, og.body_len, f ); if( ogg_stream_pagein( &tins, &og ) < 0 ) { g_free( apath ); g_free( vpath ); fclose( f ); fclose( af ); fclose( vf ); g_set_error( error, UCIL_ERROR, UCIL_ERROR_INVALID_STREAM, _("Invalid video stream") ); return FALSE; } while( ( pkt < 3 ) && ( ogg_stream_packetout( &tins, &op ) > 0 ) ) { if( theora_decode_header( &ti, &tc, &op ) != 0 ) { g_free( apath ); g_free( vpath ); fclose( f ); fclose( af ); fclose( vf ); g_set_error( error, UCIL_ERROR, UCIL_ERROR_INVALID_STREAM, _("Invalid video stream") ); return FALSE; } pkt++; } } else { int bytes; bytes = buffer_data( vf, &oy ); if( bytes == 0 ) { g_free( apath ); g_free( vpath ); fclose( f ); fclose( af ); fclose( vf ); g_set_error( error, UCIL_ERROR, UCIL_ERROR_INVALID_STREAM, _("Invalid video stream") ); return FALSE; } } } theora_decode_init( &ts, &ti ); // // Write remaining vorbis headers // while( ogg_stream_flush( &vs, &og ) > 0 ) { fwrite( og.header, 1, og.header_len, f ); fwrite( og.body, 1, og.body_len, f ); } audiopos = 0.0; videopos = 0.0; while( !feof( af ) ) { signed char abuf[512 * 4]; int n_samples; if( feof( vf ) || ( audiopos <= videopos ) ) { n_samples = fread( abuf, 4, 512, af ); audiopos = encode_vorbis( f, &vd, &vs, &vb, 44100, audiopos, abuf, n_samples ); filepos += n_samples * 4; } else { buffer_data( vf, &oy ); if( ogg_sync_pageout( &oy, &og ) > 0 ) { double tmppos; fwrite( og.header, 1, og.header_len, f ); fwrite( og.body, 1, og.body_len, f ); if( ogg_stream_pagein( &tins, &og ) < 0 ) { TRACE( "failed to decode theora\n" ); } ogg_stream_packetout( &tins, &op ); tmppos = theora_granule_time( &ts, ogg_page_granulepos( &og ) ); if( tmppos > 0 ) { videopos = tmppos; } } } if( procfunc ) { procfunc( func_data, (double)filepos/(double)len ); } } while( !feof( vf ) && buffer_data( vf, &oy ) ) { if( ogg_sync_pageout( &oy, &og ) > 0 ) { fwrite( og.header, 1, og.header_len, f ); fwrite( og.body, 1, og.body_len, f ); } } if( remove ) { unlink( vpath ); unlink( apath ); } g_free( vpath ); g_free( apath ); fclose( f ); fclose( af ); fclose( vf ); return TRUE; }
static void *ucil_theora_encode_thread( ucil_theora_video_file_object_t *vobj ) { yuv_buffer yuv; double videopos = 0; double audiopos = 0; int gotpage = 0; unsigned char *ds_y_buffer = NULL; unsigned char *ds_u_buffer = NULL; unsigned char *ds_v_buffer = NULL; yuv.y_width = vobj->ti.width; yuv.y_height = vobj->ti.height; yuv.y_stride = vobj->ti.width; yuv.uv_width = vobj->ti.width / 2; yuv.uv_height = vobj->ti.height / 2; yuv.uv_stride = vobj->ti.width / 2; if( vobj->downsize > 1 || vobj->requires_resizing_frames ) { ds_y_buffer = malloc( yuv.y_width * yuv.y_height ); ds_u_buffer = malloc( yuv.uv_width * yuv.uv_height ); ds_v_buffer = malloc( yuv.uv_width * yuv.uv_height ); } vobj->last_frame = NULL; while( !vobj->quit_thread ) { unicap_data_buffer_t *data_buffer; ogg_page og; sem_wait( &vobj->lock ); data_buffer = ( unicap_data_buffer_t *)g_queue_pop_head( vobj->full_queue ); sem_post( &vobj->lock ); if( !data_buffer ) { audiopos = fetch_and_process_audio( vobj, audiopos ); usleep( 1000 ); continue; } if( vobj->frame_count == 0 ) { memcpy( &vobj->recording_start_time, &data_buffer->fill_time, sizeof( struct timeval ) ); } audiopos = fetch_and_process_audio( vobj, audiopos ); /* printf( "v: %f a: %f\n", videopos, audiopos ); */ if( vobj->audio && ( videopos > audiopos ) ) { data_buffer->flags &= ~UNICAP_FLAGS_BUFFER_LOCKED; sem_wait( &vobj->lock ); g_queue_push_head( vobj->empty_queue, data_buffer ); sem_post( &vobj->lock ); continue; } if( vobj->fill_frames ) { if( vobj->audio ) { if( vobj->last_frame ) { unicap_data_buffer_t *last_data_buffer; double streampos; struct timeval streamtime; last_data_buffer = vobj->last_frame; if( vobj->downsize > 1 || vobj->requires_resizing_frames ) { yuv.y = ds_y_buffer; yuv.u = ds_u_buffer; yuv.v = ds_v_buffer; } else { yuv.y = last_data_buffer->data; yuv.u = last_data_buffer->data + ( yuv.y_stride * yuv.y_height ); yuv.v = yuv.u + ( yuv.uv_stride * yuv.uv_height ); } streamtime.tv_sec = data_buffer->fill_time.tv_sec - vobj->recording_start_time.tv_sec; streamtime.tv_usec = data_buffer->fill_time.tv_usec; if( data_buffer->fill_time.tv_usec < vobj->recording_start_time.tv_usec ) { streamtime.tv_sec--; streamtime.tv_usec += 1000000; } streamtime.tv_usec -= vobj->recording_start_time.tv_usec; streampos = streamtime.tv_sec + ( (double)streamtime.tv_usec / 1000000.0f ); // If the streampos is ahead, this means that we get // less than 30 frames per seconds. // --> Fill up the stream while( streampos > videopos ) { /* printf( "s v: %f a: %f\n", videopos, audiopos ); */ gotpage = 0; if( theora_encode_YUVin( &vobj->th, &yuv ) ) { TRACE( "theora_encode_YUVin FAILED!\n" ); } theora_encode_packetout( &vobj->th, 0, &vobj->op ); ogg_stream_packetin( &vobj->os, &vobj->op ); while( ogg_stream_pageout( &vobj->os, &og ) ) { double gt; fwrite( og.header, og.header_len, 1, vobj->f ); fwrite( og.body, og.body_len, 1, vobj->f ); gt = theora_granule_time( &vobj->th, ogg_page_granulepos( &og ) ); if( gt < 0 ) { continue; } gotpage = 1; videopos = gt; /* printf( "THEORA: %f\n", videopos ); */ } if( !gotpage ) { videopos += vobj->frame_interval / 1000000.0f; } vobj->frame_count++; audiopos = fetch_and_process_audio( vobj, audiopos ); } // while( ( videopos + ( vobj->frame_interval / 1000000.0f ) ) < audiopos ) { /* printf( "a v: %f a: %f\n", videopos, audiopos ); */ gotpage = 0; if( theora_encode_YUVin( &vobj->th, &yuv ) ) { TRACE( "theora_encode_YUVin FAILED!\n" ); } theora_encode_packetout( &vobj->th, 0, &vobj->op ); ogg_stream_packetin( &vobj->os, &vobj->op ); while( ogg_stream_pageout( &vobj->os, &og ) ) { double gt; fwrite( og.header, og.header_len, 1, vobj->f ); fwrite( og.body, og.body_len, 1, vobj->f ); gt = theora_granule_time( &vobj->th, ogg_page_granulepos( &og ) ); if( gt < 0 ) { continue; } gotpage = 1; videopos = gt; /* printf( "THEORA: %f\n", videopos ); */ } if( !gotpage ) { videopos += vobj->frame_interval / 1000000.0f; } vobj->frame_count++; audiopos = fetch_and_process_audio( vobj, audiopos ); } last_data_buffer->flags &= ~UNICAP_FLAGS_BUFFER_LOCKED; sem_wait( &vobj->lock ); g_queue_push_head( vobj->empty_queue, vobj->last_frame ); sem_post( &vobj->lock ); vobj->last_frame = NULL; } } else { fill_frames( vobj, data_buffer, &yuv, ds_y_buffer, ds_u_buffer, ds_v_buffer ); } } else// ( !vobj->fill_frames ) { if( vobj->last_frame ) { unicap_data_buffer_t *last_data_buffer; last_data_buffer = vobj->last_frame; last_data_buffer->flags &= ~UNICAP_FLAGS_BUFFER_LOCKED; sem_wait( &vobj->lock ); g_queue_push_head( vobj->empty_queue, vobj->last_frame ); sem_post( &vobj->lock ); } vobj->last_frame = NULL; } // // Encode the new buffer // if( vobj->encode_frame_cb ) { vobj->encode_frame_cb( UNICAP_EVENT_NEW_FRAME, NULL, data_buffer, vobj->encode_frame_cb_data ); } vobj->frame_count++; if( vobj->downsize > 1 || vobj->requires_resizing_frames ) { downsize_yuv420p( vobj->format.size.width, vobj->format.size.height, vobj->downsize, vobj->ti.width, vobj->ti.height, ds_y_buffer, ds_u_buffer, ds_v_buffer, data_buffer->data, data_buffer->data + ( vobj->format.size.width * vobj->format.size.height ), data_buffer->data + ( vobj->format.size.width * vobj->format.size.height ) + ( ( vobj->format.size.width * vobj->format.size.height ) / 4 ) ); yuv.y = ds_y_buffer; yuv.u = ds_u_buffer; yuv.v = ds_v_buffer; } else { yuv.y = data_buffer->data; yuv.u = data_buffer->data + ( yuv.y_stride * yuv.y_height ); yuv.v = yuv.u + ( yuv.uv_stride * yuv.uv_height ); } if( theora_encode_YUVin( &vobj->th, &yuv ) ) { TRACE( "theora_encode_YUVin FAILED!\n" ); } memcpy( &vobj->last_frame_time, &data_buffer->fill_time, sizeof( struct timeval ) ); vobj->last_frame = data_buffer; theora_encode_packetout( &vobj->th, 0, &vobj->op ); ogg_stream_packetin( &vobj->os, &vobj->op ); /* printf( "= v: %f a: %f\n", videopos, audiopos ); */ gotpage = 0; while( ogg_stream_pageout( &vobj->os, &og ) ) { double gt; fwrite( og.header, og.header_len, 1, vobj->f ); fwrite( og.body, og.body_len, 1, vobj->f ); gt = theora_granule_time( &vobj->th, ogg_page_granulepos( &og ) ); if( gt < 0 ) { continue; } gotpage = 1; videopos = gt; /* printf( "THEORA: %f\n", videopos ); */ } if( !gotpage ) { videopos += vobj->frame_interval / 1000000.0f; } } if( vobj->last_frame ) { // encode again to set eos unicap_data_buffer_t *last_data_buffer; ogg_page og; ogg_packet op; #if HAVE_ALSA if( vobj->audio && !vobj->async_audio_encoding ) { audiopos = fetch_and_process_audio( vobj, audiopos ); vorbis_analysis_wrote( &vobj->vd, 0 ); while( vorbis_analysis_blockout( &vobj->vd, &vobj->vb ) == 1 ) { vorbis_analysis( &vobj->vb, NULL ); vorbis_bitrate_addblock( &vobj->vb ); while( vorbis_bitrate_flushpacket( &vobj->vd, &op ) ) { ogg_stream_packetin( &vobj->vo, &op ); } } while( ogg_stream_pageout( &vobj->vo, &og ) ) { fwrite( og.header, og.header_len, 1, vobj->f ); fwrite( og.body, og.body_len, 1, vobj->f ); } } else if( vobj->audio ) { audiopos = fetch_and_process_audio( vobj, audiopos ); } #endif last_data_buffer = vobj->last_frame; if( vobj->downsize > 1 || vobj->requires_resizing_frames ) { yuv.y = ds_y_buffer; yuv.u = ds_u_buffer; yuv.v = ds_v_buffer; } else { yuv.y = last_data_buffer->data; yuv.u = last_data_buffer->data + ( yuv.y_stride * yuv.y_height ); yuv.v = yuv.u + ( yuv.uv_stride * yuv.uv_height ); } if( theora_encode_YUVin( &vobj->th, &yuv ) ) { TRACE( "theora_encode_YUVin FAILED!\n" ); } theora_encode_packetout( &vobj->th, 1, &vobj->op ); ogg_stream_packetin( &vobj->os, &vobj->op ); while( ogg_stream_pageout( &vobj->os, &og ) ) { /* printf( "THEORA: %f\n", theora_granule_time( &vobj->th, ogg_page_granulepos( &og ) ) ); */ fwrite( og.header, og.header_len, 1, vobj->f ); fwrite( og.body, og.body_len, 1, vobj->f ); } last_data_buffer->flags &= ~UNICAP_FLAGS_BUFFER_LOCKED; sem_wait( &vobj->lock ); g_queue_push_head( vobj->empty_queue, vobj->last_frame ); sem_post( &vobj->lock ); vobj->last_frame = NULL; } return NULL; }
int main(int argc,char *argv[]){ ogg_packet op; int long_option_index; int c; FILE *infile = stdin; outfile = stdout; #ifdef _WIN32 /* We need to set stdin/stdout to binary mode on windows. */ /* Beware the evil ifdef. We avoid these where we can, but this one we cannot. Don't add any more, you'll probably go to hell if you do. */ _setmode( _fileno( stdin ), _O_BINARY ); _setmode( _fileno( stdout ), _O_BINARY ); #endif /* Process option arguments. */ while((c=getopt_long(argc,argv,optstring,options,&long_option_index))!=EOF){ switch(c){ case 'o': outfile=fopen(optarg,"wb"); if(outfile==NULL){ fprintf(stderr,"Unable to open output file '%s'\n", optarg); exit(1); } break; default: usage(); } } if(optind<argc){ infile=fopen(argv[optind],"rb"); if(infile==NULL){ fprintf(stderr,"Unable to open '%s' for extraction.\n", argv[optind]); exit(1); } if(++optind<argc){ usage(); exit(1); } } /* Ok, Ogg parsing. The idea here is we have a bitstream that is made up of Ogg pages. The libogg sync layer will find them for us. There may be pages from several logical streams interleaved; we find the first theora stream and ignore any others. Then we pass the pages for our stream to the libogg stream layer which assembles our original set of packets out of them. It's the packets that libtheora actually knows how to handle. */ /* start up Ogg stream synchronization layer */ ogg_sync_init(&oy); /* init supporting Theora structures needed in header parsing */ theora_comment_init(&tc); theora_info_init(&ti); /* Ogg file open; parse the headers */ /* Vorbis and Theora both depend on some initial header packets for decoder setup and initialization. We retrieve these first before entering the main decode loop. */ /* Only interested in Theora streams */ while(!stateflag){ int ret=buffer_data(infile,&oy); if(ret==0)break; 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 */ queue_page(&og); stateflag=1; break; } 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 && theora_decode_header(&ti,&tc,&op)>=0){ /* it is theora -- save this stream state */ memcpy(&to,&test,sizeof(test)); theora_p=1; }else{ /* whatever it is, we don't care about it */ ogg_stream_clear(&test); } } /* fall through to non-initial page parsing */ } /* we're expecting more header packets. */ while(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){ fprintf(stderr,"Error parsing Theora stream headers; corrupt stream?\n"); exit(1); } if(theora_decode_header(&ti,&tc,&op)){ printf("Error parsing Theora stream headers; corrupt stream?\n"); exit(1); } theora_p++; if(theora_p==3)break; } /* 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){ queue_page(&og); /* demux into the stream state */ }else{ int ret=buffer_data(infile,&oy); /* need more data */ if(ret==0){ fprintf(stderr,"End of file while searching for codec headers.\n"); exit(1); } } } /* Now we have all the required headers. initialize the decoder. */ if(theora_p){ theora_decode_init(&td,&ti); fprintf(stderr,"Ogg logical stream %x is Theora %dx%d %.02f fps video\nEncoded frame content is %dx%d with %dx%d offset\n", to.serialno,ti.width,ti.height, (double)ti.fps_numerator/ti.fps_denominator, ti.frame_width, ti.frame_height, ti.offset_x, ti.offset_y); }else{ /* tear down the partial theora setup */ theora_info_clear(&ti); theora_comment_clear(&tc); } /* open video */ if(theora_p)open_video(); /* install signal handler */ signal (SIGINT, sigint_handler); /* Finally the main decode loop. It's one Theora packet per frame, so this is pretty straightforward if we're not trying to maintain sync with other multiplexed streams. the videobuf_ready flag is used to maintain the input buffer in the libogg stream state. If there's no output frame available at the end of the decode step, we must need more input data. We could simplify this by just using the return code on ogg_page_packetout(), but the flag system extends easily to the case were you care about more than one multiplexed stream (like with audio playback). In that case, just maintain a flag for each decoder you care about, and pull data when any one of them stalls. videobuf_time holds the presentation time of the currently buffered video frame. We ignore this value. */ stateflag=0; /* playback has not begun */ /* queue any remaining pages from data we buffered but that did not contain headers */ while(ogg_sync_pageout(&oy,&og)>0){ queue_page(&og); } while(!got_sigint){ while(theora_p && !videobuf_ready){ /* theora is one in, one out... */ if(ogg_stream_packetout(&to,&op)>0){ theora_decode_packetin(&td,&op); videobuf_granulepos=td.granulepos; videobuf_time=theora_granule_time(&td,videobuf_granulepos); videobuf_ready=1; }else break; } if(!videobuf_ready && feof(infile))break; if(!videobuf_ready){ /* no data yet for somebody. Grab another page */ int ret=buffer_data(infile,&oy); while(ogg_sync_pageout(&oy,&og)>0){ queue_page(&og); } } /* dumpvideo frame, and get new one */ else video_write(); videobuf_ready=0; } /* end of decoder loop -- close everything */ if(theora_p){ ogg_stream_clear(&to); theora_clear(&td); theora_comment_clear(&tc); theora_info_clear(&ti); } ogg_sync_clear(&oy); if(infile && infile!=stdin)fclose(infile); fprintf(stderr, "\r " "\nDone.\n"); return(0); }
int main( int argc, char* argv[] ){ int i,j; ogg_packet op; SDL_Event event; int hasdatatobuffer = 1; int playbackdone = 0; double now, delay, last_frame_time = 0; int frameNum=0; int skipNum=0; /* takes first argument as file to play */ /* this works better on Windows and is more convenient for drag and drop ogg files over the .exe */ if( argc != 2 ) { usage(); exit(0); } infile = fopen( argv[1], "rb" ); /* start up Ogg stream synchronization layer */ ogg_sync_init(&oy); /* init supporting Vorbis structures needed in header parsing */ vorbis_info_init(&vi); vorbis_comment_init(&vc); /* init supporting Theora structures needed in header parsing */ theora_comment_init(&tc); theora_info_init(&ti); parseHeaders(); /* force audio off */ /* vorbis_p = 0; */ /* initialize decoders */ if(theora_p){ theora_decode_init(&td,&ti); printf("Ogg logical stream %x is Theora %dx%d %.02f fps video\n" " Frame content is %dx%d with offset (%d,%d).\n", to.serialno,ti.width,ti.height, (double)ti.fps_numerator/ti.fps_denominator, ti.frame_width, ti.frame_height, ti.offset_x, ti.offset_y); report_colorspace(&ti); dump_comments(&tc); }else{ /* tear down the partial theora setup */ theora_info_clear(&ti); theora_comment_clear(&tc); } if(vorbis_p){ vorbis_synthesis_init(&vd,&vi); vorbis_block_init(&vd,&vb); printf("Ogg logical stream %x is Vorbis %d channel %d Hz audio.\n", vo.serialno,vi.channels,vi.rate); }else{ /* tear down the partial vorbis setup */ vorbis_info_clear(&vi); vorbis_comment_clear(&vc); } /* open audio */ if(vorbis_p)open_audio(); /* open video */ if(theora_p)open_video(); /* our main loop */ while(!playbackdone){ /* break out on SDL quit event */ if ( SDL_PollEvent ( &event ) ) { if ( event.type == SDL_QUIT ) break ; } /* get some audio data */ while(vorbis_p && !audiobuf_ready){ int ret; float **pcm; int count = 0; int maxBytesToWrite; /* is there pending audio? does it fit our circular buffer without blocking? */ ret=vorbis_synthesis_pcmout(&vd,&pcm); maxBytesToWrite = GetAudioStreamWriteable(aOutStream); if (maxBytesToWrite<=FRAMES_PER_BUFFER){ /* break out until there is a significant amount of data to avoid a series of small write operations. */ break; } /* if there's pending, decoded audio, grab it */ if((ret>0)&&(maxBytesToWrite>0)){ for(i=0;i<ret && i<(maxBytesToWrite/vi.channels);i++) for(j=0;j<vi.channels;j++){ int val=(int)(pcm[j][i]*32767.f); if(val>32767)val=32767; if(val<-32768)val=-32768; samples[count]=val; count++; } if(WriteAudioStream( aOutStream, samples, i )) { if(count==maxBytesToWrite){ audiobuf_ready=1; } } vorbis_synthesis_read(&vd,i); if(vd.granulepos>=0) audiobuf_granulepos=vd.granulepos-ret+i; else audiobuf_granulepos+=i; }else{ /* no pending audio; is there a pending packet to decode? */ if(ogg_stream_packetout(&vo,&op)>0){ if(vorbis_synthesis(&vb,&op)==0) /* test for success! */ vorbis_synthesis_blockin(&vd,&vb); }else /* we need more data; break out to suck in another page */ break; } } /* end audio cycle */ while(theora_p && !videobuf_ready){ /* get one video packet... */ if(ogg_stream_packetout(&to,&op)>0){ theora_decode_packetin(&td,&op); videobuf_granulepos=td.granulepos; videobuf_time=theora_granule_time(&td,videobuf_granulepos); /* update the frame counter */ frameNum++; /* check if this frame time has not passed yet. If the frame is late we need to decode additonal ones and keep looping, since theora at this stage needs to decode all frames */ now=get_time(); delay=videobuf_time-now; if(delay>=0.0){ /* got a good frame, not late, ready to break out */ videobuf_ready=1; }else if(now-last_frame_time>=1.0){ /* display at least one frame per second, regardless */ videobuf_ready=1; }else{ fprintf(stderr, "dropping frame %d (%.3fs behind)\n", frameNum, -delay); } }else{ /* need more data */ break; } } if(!hasdatatobuffer && !videobuf_ready && !audiobuf_ready){ isPlaying = 0; playbackdone = 1; } /* if we're set for the next frame, sleep */ if((!theora_p || videobuf_ready) && (!vorbis_p || audiobuf_ready)){ int ticks = 1.0e3*(videobuf_time-get_time()); if(ticks>0) SDL_Delay(ticks); } if(videobuf_ready){ /* time to write our cached frame */ video_write(); videobuf_ready=0; last_frame_time=get_time(); /* if audio has not started (first frame) then start it */ if ((!isPlaying)&&(vorbis_p)){ start_audio(); isPlaying = 1; } } /* HACK: always look for more audio data */ audiobuf_ready=0; /* buffer compressed data every loop */ if(hasdatatobuffer){ hasdatatobuffer=buffer_data(&oy); if(hasdatatobuffer==0){ printf("Ogg buffering stopped, end of file reached.\n"); } } if (ogg_sync_pageout(&oy,&og)>0){ queue_page(&og); } } /* playbackdone */ /* show number of video frames decoded */ printf( "\n"); printf( "Frames decoded: %d", frameNum ); if(skipNum) printf( " (only %d shown)", frameNum-skipNum); printf( "\n" ); /* tear it all down */ fclose( infile ); if(vorbis_p){ audio_close(); ogg_stream_clear(&vo); vorbis_block_clear(&vb); vorbis_dsp_clear(&vd); vorbis_comment_clear(&vc); vorbis_info_clear(&vi); } if(theora_p){ ogg_stream_clear(&to); theora_clear(&td); theora_comment_clear(&tc); theora_info_clear(&ti); } ogg_sync_clear(&oy); printf("\r " "\nDone.\n"); SDL_Quit(); return(0); }
int main(int argc,char *argv[]){ int c,long_option_index,ret; ogg_stream_state to; /* take physical pages, weld into a logical stream of packets */ ogg_stream_state vo; /* take physical pages, weld into a logical stream of packets */ ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */ ogg_packet op; /* one raw packet of data for decode */ theora_state td; theora_info ti; theora_comment tc; vorbis_info vi; /* struct that stores all the static vorbis bitstream settings */ vorbis_comment vc; /* struct that stores all the user comments */ vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */ vorbis_block vb; /* local working space for packet->PCM decode */ int audioflag=0; int videoflag=0; int akbps=0; int vkbps=0; ogg_int64_t audio_bytesout=0; ogg_int64_t video_bytesout=0; double timebase; FILE* outfile = stdout; #ifdef _WIN32 # ifdef THEORA_PERF_DATA LARGE_INTEGER start_time; LARGE_INTEGER final_time; LONGLONG elapsed_ticks; LARGE_INTEGER ticks_per_second; LONGLONG elapsed_secs; LONGLONG elapsed_sec_mod; double elapsed_secs_dbl ; # endif /* We need to set stdin/stdout to binary mode. Damn windows. */ /* if we were reading/writing a file, it would also need to in binary mode, eg, fopen("file.wav","wb"); */ /* Beware the evil ifdef. We avoid these where we can, but this one we cannot. Don't add any more, you'll probably go to hell if you do. */ _setmode( _fileno( stdin ), _O_BINARY ); _setmode( _fileno( stdout ), _O_BINARY ); #endif while((c=getopt_long(argc,argv,optstring,options,&long_option_index))!=EOF){ switch(c){ case 'o': outfile=fopen(optarg,"wb"); if(outfile==NULL){ fprintf(stderr,"Unable to open output file '%s'\n", optarg); exit(1); } break;; case 'a': audio_q=atof(optarg)*.099; if(audio_q<-.1 || audio_q>1){ fprintf(stderr,"Illegal audio quality (choose -1 through 10)\n"); exit(1); } audio_r=-1; break; case 'v': video_q=rint(atof(optarg)*6.3); if(video_q<0 || video_q>63){ fprintf(stderr,"Illegal video quality (choose 0 through 10)\n"); exit(1); } video_r=0; break; case 'A': audio_r=atof(optarg)*1000; if(audio_q<0){ fprintf(stderr,"Illegal audio quality (choose > 0 please)\n"); exit(1); } audio_q=-99; break; case 'V': video_r=rint(atof(optarg)*1000); if(video_r<45000 || video_r>2000000){ fprintf(stderr,"Illegal video bitrate (choose 45kbps through 2000kbps)\n"); exit(1); } video_q=0; break; case 's': video_an=rint(atof(optarg)); break; case 'S': video_ad=rint(atof(optarg)); break; case 'f': video_hzn=rint(atof(optarg)); break; case 'F': video_hzd=rint(atof(optarg)); break; default: usage(); } } while(optind<argc){ /* assume that anything following the options must be a filename */ id_file(argv[optind]); optind++; } #ifdef THEORA_PERF_DATA # ifdef WIN32 QueryPerformanceCounter(&start_time); # endif #endif /* yayness. Set up Ogg output stream */ srand(time(NULL)); { /* need two inequal serial numbers */ int serial1, serial2; serial1 = rand(); serial2 = rand(); if (serial1 == serial2) serial2++; ogg_stream_init(&to,serial1); ogg_stream_init(&vo,serial2); } /* Set up Theora encoder */ if(!video){ fprintf(stderr,"No video files submitted for compression?\n"); exit(1); } /* Theora has a divisible-by-sixteen restriction for the encoded video size */ /* scale the frame size up to the nearest /16 and calculate offsets */ video_x=((frame_x + 15) >>4)<<4; video_y=((frame_y + 15) >>4)<<4; /* We force the offset to be even. This ensures that the chroma samples align properly with the luma samples. */ frame_x_offset=((video_x-frame_x)/2)&~1; frame_y_offset=((video_y-frame_y)/2)&~1; theora_info_init(&ti); ti.width=video_x; ti.height=video_y; ti.frame_width=frame_x; ti.frame_height=frame_y; ti.offset_x=frame_x_offset; ti.offset_y=frame_y_offset; ti.fps_numerator=video_hzn; ti.fps_denominator=video_hzd; ti.aspect_numerator=video_an; ti.aspect_denominator=video_ad; ti.colorspace=OC_CS_UNSPECIFIED; ti.pixelformat=OC_PF_420; ti.target_bitrate=video_r; ti.quality=video_q; ti.dropframes_p=0; ti.quick_p=1; ti.keyframe_auto_p=1; ti.keyframe_frequency=64; ti.keyframe_frequency_force=64; ti.keyframe_data_target_bitrate=video_r*1.5; ti.keyframe_auto_threshold=80; ti.keyframe_mindistance=8; ti.noise_sensitivity=1; theora_encode_init(&td,&ti); theora_info_clear(&ti); /* initialize Vorbis too, assuming we have audio to compress. */ if(audio){ vorbis_info_init(&vi); if(audio_q>-99) ret = vorbis_encode_init_vbr(&vi,audio_ch,audio_hz,audio_q); else ret = vorbis_encode_init(&vi,audio_ch,audio_hz,-1,audio_r,-1); if(ret){ fprintf(stderr,"The Vorbis encoder could not set up a mode according to\n" "the requested quality or bitrate.\n\n"); exit(1); } vorbis_comment_init(&vc); vorbis_analysis_init(&vd,&vi); vorbis_block_init(&vd,&vb); } /* write the bitstream header packets with proper page interleave */ /* first packet will get its own page automatically */ theora_encode_header(&td,&op); ogg_stream_packetin(&to,&op); if(ogg_stream_pageout(&to,&og)!=1){ fprintf(stderr,"Internal Ogg library error.\n"); exit(1); } fwrite(og.header,1,og.header_len,outfile); fwrite(og.body,1,og.body_len,outfile); /* create the remaining theora headers */ theora_comment_init(&tc); theora_encode_comment(&tc,&op); ogg_stream_packetin(&to,&op); /*theora_encode_comment() doesn't take a theora_state parameter, so it has to allocate its own buffer to pass back the packet data. If we don't free it here, we'll leak. libogg2 makes this much cleaner: the stream owns the buffer after you call packetin in libogg2, but this is not true in libogg1.*/ free(op.packet); theora_encode_tables(&td,&op); ogg_stream_packetin(&to,&op); if(audio){ ogg_packet header; ogg_packet header_comm; ogg_packet header_code; vorbis_analysis_headerout(&vd,&vc,&header,&header_comm,&header_code); ogg_stream_packetin(&vo,&header); /* automatically placed in its own page */ if(ogg_stream_pageout(&vo,&og)!=1){ fprintf(stderr,"Internal Ogg library error.\n"); exit(1); } fwrite(og.header,1,og.header_len,outfile); fwrite(og.body,1,og.body_len,outfile); /* remaining vorbis header packets */ ogg_stream_packetin(&vo,&header_comm); ogg_stream_packetin(&vo,&header_code); } /* Flush the rest of our headers. This ensures the actual data in each stream will start on a new page, as per spec. */ while(1){ int result = ogg_stream_flush(&to,&og); if(result<0){ /* can't get here */ fprintf(stderr,"Internal Ogg library error.\n"); exit(1); } if(result==0)break; fwrite(og.header,1,og.header_len,outfile); fwrite(og.body,1,og.body_len,outfile); } if(audio){ while(1){ int result=ogg_stream_flush(&vo,&og); if(result<0){ /* can't get here */ fprintf(stderr,"Internal Ogg library error.\n"); exit(1); } if(result==0)break; fwrite(og.header,1,og.header_len,outfile); fwrite(og.body,1,og.body_len,outfile); } } /* setup complete. Raw processing loop */ fprintf(stderr,"Compressing....\n"); while(1){ ogg_page audiopage; ogg_page videopage; /* is there an audio page flushed? If not, fetch one if possible */ audioflag=fetch_and_process_audio(audio,&audiopage,&vo,&vd,&vb,audioflag); /* is there a video page flushed? If not, fetch one if possible */ videoflag=fetch_and_process_video(video,&videopage,&to,&td,videoflag); /* no pages of either? Must be end of stream. */ if(!audioflag && !videoflag)break; /* which is earlier; the end of the audio page or the end of the video page? Flush the earlier to stream */ { int audio_or_video=-1; double audiotime= audioflag?vorbis_granule_time(&vd,ogg_page_granulepos(&audiopage)):-1; double videotime= videoflag?theora_granule_time(&td,ogg_page_granulepos(&videopage)):-1; if(!audioflag){ audio_or_video=1; } else if(!videoflag) { audio_or_video=0; } else { if(audiotime<videotime) audio_or_video=0; else audio_or_video=1; } if(audio_or_video==1){ /* flush a video page */ video_bytesout+=fwrite(videopage.header,1,videopage.header_len,outfile); video_bytesout+=fwrite(videopage.body,1,videopage.body_len,outfile); videoflag=0; timebase=videotime; }else{ /* flush an audio page */ audio_bytesout+=fwrite(audiopage.header,1,audiopage.header_len,outfile); audio_bytesout+=fwrite(audiopage.body,1,audiopage.body_len,outfile); audioflag=0; timebase=audiotime; } { int hundredths=timebase*100-(long)timebase*100; int seconds=(long)timebase%60; int minutes=((long)timebase/60)%60; int hours=(long)timebase/3600; if(audio_or_video) vkbps=rint(video_bytesout*8./timebase*.001); else akbps=rint(audio_bytesout*8./timebase*.001); fprintf(stderr, "\r %d:%02d:%02d.%02d audio: %dkbps video: %dkbps ", hours,minutes,seconds,hundredths,akbps,vkbps); } } } /* clear out state */ if(audio){ ogg_stream_clear(&vo); vorbis_block_clear(&vb); vorbis_dsp_clear(&vd); vorbis_comment_clear(&vc); vorbis_info_clear(&vi); } if(video){ ogg_stream_clear(&to); theora_clear(&td); } if(outfile && outfile!=stdout)fclose(outfile); fprintf(stderr,"\r \ndone.\n\n"); #ifdef THEORA_PERF_DATA # ifdef WIN32 QueryPerformanceCounter(&final_time); elapsed_ticks = final_time.QuadPart - start_time.QuadPart; ticks_per_second; QueryPerformanceFrequency(&ticks_per_second); elapsed_secs = elapsed_ticks / ticks_per_second.QuadPart; elapsed_sec_mod = elapsed_ticks % ticks_per_second.QuadPart; elapsed_secs_dbl = elapsed_secs; elapsed_secs_dbl += ((double)elapsed_sec_mod / (double)ticks_per_second.QuadPart); printf("Encode time = %lld ticks\n", elapsed_ticks); printf("~%lld and %lld / %lld seconds\n", elapsed_secs, elapsed_sec_mod, ticks_per_second.QuadPart); printf("~%Lf seconds\n", elapsed_secs_dbl); # endif #endif return(0); }