int64_t TheoraState::StartTime(int64_t granulepos) { if (granulepos < 0 || !mActive || mInfo.fps_numerator == 0) { return -1; } CheckedInt64 t = (CheckedInt64(th_granule_frame(mCtx, granulepos)) * USECS_PER_S) * mInfo.fps_denominator; if (!t.isValid()) return -1; return t.value() / mInfo.fps_numerator; }
PRInt64 nsTheoraState::StartTime(PRInt64 granulepos) { if (granulepos < 0 || !mActive || mInfo.fps_numerator == 0) { return -1; } PRInt64 t = 0; PRInt64 frameno = th_granule_frame(mCtx, granulepos); if (!MulOverflow(frameno, 1000, t)) return -1; if (!MulOverflow(t, mInfo.fps_denominator, t)) return -1; return t / mInfo.fps_numerator; }
static ogg_int64_t theora_decode_granule_frame(theora_state *_td, ogg_int64_t _gp){ return th_granule_frame(((th_api_wrapper *)_td->i->codec_setup)->decode,_gp); }
void TheoraState::ReconstructTheoraGranulepos() { if (mUnstamped.Length() == 0) { return; } ogg_int64_t lastGranulepos = mUnstamped[mUnstamped.Length() - 1]->granulepos; NS_ASSERTION(lastGranulepos != -1, "Must know last granulepos"); // Reconstruct the granulepos (and thus timestamps) of the decoded // frames. Granulepos are stored as ((keyframe<<shift)+offset). We // know the granulepos of the last frame in the list, so we can infer // the granulepos of the intermediate frames using their frame numbers. ogg_int64_t shift = mInfo.keyframe_granule_shift; ogg_int64_t version_3_2_1 = TheoraVersion(&mInfo,3,2,1); ogg_int64_t lastFrame = th_granule_frame(mCtx, lastGranulepos) + version_3_2_1; ogg_int64_t firstFrame = lastFrame - mUnstamped.Length() + 1; // Until we encounter a keyframe, we'll assume that the "keyframe" // segment of the granulepos is the first frame, or if that causes // the "offset" segment to overflow, we assume the required // keyframe is maximumally offset. Until we encounter a keyframe // the granulepos will probably be wrong, but we can't decode the // frame anyway (since we don't have its keyframe) so it doesn't really // matter. ogg_int64_t keyframe = lastGranulepos >> shift; // The lastFrame, firstFrame, keyframe variables, as well as the frame // variable in the loop below, store the frame number for Theora // version >= 3.2.1 streams, and store the frame index for Theora // version < 3.2.1 streams. for (uint32_t i = 0; i < mUnstamped.Length() - 1; ++i) { ogg_int64_t frame = firstFrame + i; ogg_int64_t granulepos; ogg_packet* packet = mUnstamped[i]; bool isKeyframe = th_packet_iskeyframe(packet) == 1; if (isKeyframe) { granulepos = frame << shift; keyframe = frame; } else if (frame >= keyframe && frame - keyframe < ((ogg_int64_t)1 << shift)) { // (frame - keyframe) won't overflow the "offset" segment of the // granulepos, so it's safe to calculate the granulepos. granulepos = (keyframe << shift) + (frame - keyframe); } else { // (frame - keyframeno) will overflow the "offset" segment of the // granulepos, so we take "keyframe" to be the max possible offset // frame instead. ogg_int64_t k = std::max(frame - (((ogg_int64_t)1 << shift) - 1), version_3_2_1); granulepos = (k << shift) + (frame - k); } // Theora 3.2.1+ granulepos store frame number [1..N], so granulepos // should be > 0. // Theora 3.2.0 granulepos store the frame index [0..(N-1)], so // granulepos should be >= 0. NS_ASSERTION(granulepos >= version_3_2_1, "Invalid granulepos for Theora version"); // Check that the frame's granule number is one more than the // previous frame's. NS_ASSERTION(i == 0 || th_granule_frame(mCtx, granulepos) == th_granule_frame(mCtx, mUnstamped[i-1]->granulepos) + 1, "Granulepos calculation is incorrect!"); packet->granulepos = granulepos; } // Check that the second to last frame's granule number is one less than // the last frame's (the known granule number). If not our granulepos // recovery missed a beat. NS_ASSERTION(mUnstamped.Length() < 2 || th_granule_frame(mCtx, mUnstamped[mUnstamped.Length()-2]->granulepos) + 1 == th_granule_frame(mCtx, lastGranulepos), "Granulepos recovery should catch up with packet->granulepos!"); }
void VideoClip_Theora::_load(DataSource* source) { #ifdef _DEBUG log("-----"); #endif this->stream = source; this->_readTheoraVorbisHeaders(); this->info.TheoraDecoder = th_decode_alloc(&this->info.TheoraInfo, this->info.TheoraSetup); this->width = this->info.TheoraInfo.frame_width; this->height = this->info.TheoraInfo.frame_height; this->subFrameWidth = this->info.TheoraInfo.pic_width; this->subFrameHeight = this->info.TheoraInfo.pic_height; this->subFrameX = this->info.TheoraInfo.pic_x; this->subFrameY = this->info.TheoraInfo.pic_y; this->stride = this->getWidth(); if (this->useStride) { this->stride = potCeil(this->stride); } this->fps = this->info.TheoraInfo.fps_numerator / (float)this->info.TheoraInfo.fps_denominator; #ifdef _DEBUG log("width: " + str(this->width) + ", height: " + str(this->height) + ", fps: " + str((int)this->getFps())); #endif this->frameQueue = new FrameQueue(this); this->frameQueue->setSize(this->precachedFramesCount); // find out the duration of the file by seeking to the end // having ogg decode pages, extract the granule pos from // the last theora page and seek back to beginning of the file char* buffer = NULL; int bytesRead = 0; uint64_t streamSize = this->stream->getSize(); uint64_t seekPos = 0; int result = 0; ogg_int64_t granule = 0; for (unsigned int i = 1; i <= 50; ++i) { ogg_sync_reset(&this->info.OggSyncState); seekPos = (BUFFER_SIZE * i > streamSize ? 0 : streamSize - BUFFER_SIZE * i); this->stream->seek(seekPos); buffer = ogg_sync_buffer(&this->info.OggSyncState, BUFFER_SIZE * i); bytesRead = this->stream->read(buffer, BUFFER_SIZE * i); ogg_sync_wrote(&this->info.OggSyncState, bytesRead); ogg_sync_pageseek(&this->info.OggSyncState, &this->info.OggPage); while (true) { result = ogg_sync_pageout(&this->info.OggSyncState, &this->info.OggPage); if (result == 0) { break; } // if page is not a theora page or page is unsynced(-1), skip it if (result == -1 || ogg_page_serialno(&this->info.OggPage) != this->info.TheoraStreamState.serialno) { continue; } granule = ogg_page_granulepos(&this->info.OggPage); if (granule >= 0) { this->framesCount = (int)th_granule_frame(this->info.TheoraDecoder, granule) + 1; } else if (this->framesCount > 0) { ++this->framesCount; // append delta frames at the end to get the exact number } } if (this->framesCount > 0 || streamSize < BUFFER_SIZE * i) { break; } } if (this->framesCount < 0) { log("unable to determine file duration!"); } else { this->duration = this->framesCount / this->fps; #ifdef _DEBUG log("duration: " + strf(this->duration) + " seconds"); #endif } // restore to beginning of stream. ogg_sync_reset(&this->info.OggSyncState); this->stream->seek(0); if (this->vorbisStreams > 0) // if there is no audio interface factory defined, even though the video clip might have audio, it will be ignored { vorbis_synthesis_init(&this->info.VorbisDSPState, &this->info.VorbisInfo); vorbis_block_init(&this->info.VorbisDSPState, &this->info.VorbisBlock); this->audioChannelsCount = this->info.VorbisInfo.channels; this->audioFrequency = (int) this->info.VorbisInfo.rate; // create an audio interface instance if available AudioInterfaceFactory* audioInterfaceFactory = theoraplayer::manager->getAudioInterfaceFactory(); if (audioInterfaceFactory != NULL) { this->setAudioInterface(audioInterfaceFactory->createInstance(this, this->audioChannelsCount, this->audioFrequency)); } } this->frameDuration = 1.0f / this->getFps(); #ifdef _DEBUG log("-----"); #endif }
long VideoClip_Theora::_seekPage(long targetFrame, bool returnKeyFrame) { uint64_t seekMin = 0; uint64_t seekMax = this->stream->getSize(); long frame = 0; ogg_int64_t granule = 0; if (targetFrame == 0) { this->stream->seek(0); } char* buffer = NULL; int bytesRead = 0; if (targetFrame != 0) { for (int i = 0; i < 100; ++i) { ogg_sync_reset(&this->info.OggSyncState); this->stream->seek(seekMin / 2 + seekMax / 2); // do a binary search memset(&this->info.OggPage, 0, sizeof(ogg_page)); ogg_sync_pageseek(&this->info.OggSyncState, &this->info.OggPage); while (i < 1000) { if (ogg_sync_pageout(&this->info.OggSyncState, &this->info.OggPage) == 1) { if (ogg_page_serialno(&this->info.OggPage) == this->info.TheoraStreamState.serialno) { granule = ogg_page_granulepos(&this->info.OggPage); if (granule >= 0) { frame = (long)th_granule_frame(this->info.TheoraDecoder, granule); if (frame < targetFrame && targetFrame - frame < 10) { // we're close enough, let's break this. i = 1000; break; } // we're not close enough, let's shorten the borders of the binary search if (targetFrame - 1 > frame) { seekMin = seekMin / 2 + seekMax / 2; } else { seekMax = seekMin / 2 + seekMax / 2; } break; } } } else { buffer = ogg_sync_buffer(&this->info.OggSyncState, BUFFER_SIZE); bytesRead = this->stream->read(buffer, BUFFER_SIZE); if (bytesRead == 0) { break; } ogg_sync_wrote(&this->info.OggSyncState, bytesRead); } } } } if (returnKeyFrame) { return (long)(granule >> this->info.TheoraInfo.keyframe_granule_shift); } ogg_sync_reset(&this->info.OggSyncState); memset(&this->info.OggPage, 0, sizeof(ogg_page)); ogg_sync_pageseek(&this->info.OggSyncState, &this->info.OggPage); if (targetFrame == 0) { return -1; } this->stream->seek((seekMin + seekMax) / 2); // do a binary search return -1; }
void VideoClip_Theora::_executeSeek() { #if _DEBUG log(this->name + " [seek]: seeking to frame " + str(this->seekFrame)); #endif int frame = 0; float time = this->seekFrame / getFps(); this->timer->seek(time); bool paused = this->timer->isPaused(); if (!paused) { this->timer->pause(); // pause until seeking is done } this->endOfFile = false; this->restarted = false; this->_resetFrameQueue(); // reset the video decoder. ogg_stream_reset(&this->info.TheoraStreamState); th_decode_free(this->info.TheoraDecoder); this->info.TheoraDecoder = th_decode_alloc(&this->info.TheoraInfo, this->info.TheoraSetup); Mutex::ScopeLock audioMutexLock; if (this->audioInterface != NULL) { audioMutexLock.acquire(this->audioMutex); ogg_stream_reset(&this->info.VorbisStreamState); vorbis_synthesis_restart(&this->info.VorbisDSPState); this->destroyAllAudioPackets(); } // first seek to desired frame, then figure out the location of the // previous key frame and seek to it. // then by setting the correct time, the decoder will skip N frames untill // we get the frame we want. frame = (int)this->_seekPage(this->seekFrame, 1); // find the key frame nearest to the target frame #ifdef _DEBUG // log(mName + " [seek]: nearest key frame for frame " + str(mSeekFrame) + " is frame: " + str(frame)); #endif this->_seekPage(std::max(0, frame - 1), 0); ogg_packet opTheora; ogg_int64_t granulePos; bool granuleSet = false; if (frame <= 1) { if (this->info.TheoraInfo.version_major == 3 && this->info.TheoraInfo.version_minor == 2 && this->info.TheoraInfo.version_subminor == 0) { granulePos = 0; } else { granulePos = 1; // because of difference in granule interpretation in theora streams 3.2.0 and newer ones } th_decode_ctl(this->info.TheoraDecoder, TH_DECCTL_SET_GRANPOS, &granulePos, sizeof(granulePos)); granuleSet = true; } // now that we've found the key frame that preceeds our desired frame, lets keep on decoding frames until we // reach our target frame. int status = 0; while (this->seekFrame != 0) { if (ogg_stream_packetout(&this->info.TheoraStreamState, &opTheora) > 0) { if (!granuleSet) { // theora decoder requires to set the granule pos after seek to be able to determine the current frame if (opTheora.granulepos < 0) { continue; // ignore prev delta frames until we hit a key frame } th_decode_ctl(this->info.TheoraDecoder, TH_DECCTL_SET_GRANPOS, &opTheora.granulepos, sizeof(opTheora.granulepos)); granuleSet = true; } status = th_decode_packetin(this->info.TheoraDecoder, &opTheora, &granulePos); if (status != 0 && status != TH_DUPFRAME) { continue; } frame = (int)th_granule_frame(this->info.TheoraDecoder, granulePos); if (frame >= this->seekFrame - 1) { break; } } else if (!this->_readData()) { log(this->name + " [seek]: fineseeking failed, _readData failed!"); return; } } #ifdef _DEBUG // log(mName + " [seek]: fineseeked to frame " + str(frame + 1) + ", requested: " + str(mSeekFrame)); #endif if (this->audioInterface != NULL) { // read audio data until we reach a timeStamp. this usually takes only one iteration, but just in case let's // wrap it in a loop float timeStamp = 0.0f; while (true) { timeStamp = this->_decodeAudio(); if (timeStamp >= 0) { break; } this->_readData(); } float rate = (float)this->audioFrequency * this->audioChannelsCount; float queuedTime = this->getAudioPacketQueueLength(); int trimmedCount = 0; // at this point there are only 2 possibilities: either we have too much packets and we have to delete // the first N ones, or we don't have enough, so let's fill the gap with silence. if (time > timeStamp - queuedTime) { while (this->audioPacketQueue != NULL) { if (time <= timeStamp - queuedTime + this->audioPacketQueue->samplesCount / rate) { trimmedCount = (int)((timeStamp - queuedTime + this->audioPacketQueue->samplesCount / rate - time) * rate); if (this->audioPacketQueue->samplesCount - trimmedCount <= 0) { this->destroyAudioPacket(this->popAudioPacket()); // if there's no data to be left, just destroy it } else { for (int i = trimmedCount, j = 0; i < this->audioPacketQueue->samplesCount; ++i, ++j) { this->audioPacketQueue->pcmData[j] = this->audioPacketQueue->pcmData[i]; } this->audioPacketQueue->samplesCount -= trimmedCount; } break; } queuedTime -= this->audioPacketQueue->samplesCount / rate; this->destroyAudioPacket(this->popAudioPacket()); } } // expand the first packet with silence. else if (this->audioPacketQueue != NULL) { int i = 0; int j = 0; int missingCount = (int)((timeStamp - queuedTime - time) * rate); if (missingCount > 0) { float* samples = new float[missingCount + this->audioPacketQueue->samplesCount]; if (missingCount > 0) { memset(samples, 0, missingCount * sizeof(float)); } for (j = 0; i < missingCount + this->audioPacketQueue->samplesCount; ++i, ++j) { samples[i] = this->audioPacketQueue->pcmData[j]; } delete[] this->audioPacketQueue->pcmData; this->audioPacketQueue->pcmData = samples; } } this->lastDecodedFrameNumber = this->seekFrame; this->readAudioSamples = (unsigned int)(timeStamp * this->audioFrequency); audioMutexLock.release(); } if (!paused) { this->timer->play(); } this->seekFrame = -1; }
bool VideoClip_Theora::_decodeNextFrame() { if (this->endOfFile) { return false; } VideoFrame* frame = this->frameQueue->requestEmptyFrame(); if (frame == NULL) { return false; // max number of precached frames reached } bool shouldRestart = false; ogg_packet opTheora; ogg_int64_t granulePos; th_ycbcr_buffer buff; int result = 0; int attempts = 0; int status = 0; float time = 0.0f; unsigned long frameNumber = 0; while (true) { // ogg_stream_packetout can return -1 and the official docs suggest to do subsequent calls until it succeeds // because the data is out of sync. still will limit the number of attempts just in case for (result = -1, attempts = 0; result < 0 && attempts < 100; ++attempts) { result = ogg_stream_packetout(&this->info.TheoraStreamState, &opTheora); } if (result > 0) { status = th_decode_packetin(this->info.TheoraDecoder, &opTheora, &granulePos); if (status != 0 && status != TH_DUPFRAME) // 0 means success { continue; } time = (float)th_granule_time(this->info.TheoraDecoder, granulePos); frameNumber = (unsigned long)th_granule_frame(this->info.TheoraDecoder, granulePos); // %16 operation is here to prevent a playback halt during video playback if the decoder can't keep up with demand. if (time < this->timer->getTime() && !this->restarted && frameNumber % 16 != 0) { #ifdef _DEBUG_FRAMEDROP log(mName + ": pre-dropped frame " + str((int)frameNumber)); #endif ++this->droppedFramesCount; continue; // drop frame } this->_setVideoFrameTimeToDisplay(frame, time - this->frameDuration); this->_setVideoFrameIteration(frame, this->iteration); this->_setVideoFrameFrameNumber(frame, (int)frameNumber); this->lastDecodedFrameNumber = frameNumber; th_decode_ycbcr_out(this->info.TheoraDecoder, buff); Theoraplayer_PixelTransform pixelTransform; memset(&pixelTransform, 0, sizeof(Theoraplayer_PixelTransform)); pixelTransform.y = buff[0].data; pixelTransform.yStride = buff[0].stride; pixelTransform.u = buff[1].data; pixelTransform.uStride = buff[1].stride; pixelTransform.v = buff[2].data; pixelTransform.vStride = buff[2].stride; frame->decode(&pixelTransform); break; } if (!this->_readData()) { this->_setVideoFrameInUse(frame, false); shouldRestart = this->autoRestart; break; } } if (this->audioInterface != NULL) { Mutex::ScopeLock lock(this->audioMutex); this->_decodeAudio(); } if (shouldRestart) { ++this->iteration; this->_executeRestart(); } return true; }