Index: server/asobj/NetStreamFfmpeg.cpp =================================================================== RCS file: /sources/gnash/gnash/server/asobj/NetStreamFfmpeg.cpp,v retrieving revision 1.133 diff -u -r1.133 NetStreamFfmpeg.cpp --- server/asobj/NetStreamFfmpeg.cpp 22 May 2008 11:30:17 -0000 1.133 +++ server/asobj/NetStreamFfmpeg.cpp 24 May 2008 10:38:45 -0000 @@ -32,7 +32,7 @@ #include "movie_root.h" #include "sound_handler.h" #include "VideoDecoderFfmpeg.h" -#include "ClockTime.h" // TODO: use the VirtualClock instead ? +#include "SystemClock.h" #include "FLVParser.h" @@ -48,24 +48,28 @@ #endif /// Define this to add debugging prints for locking -#define GNASH_DEBUG_THREADS +//#define GNASH_DEBUG_THREADS // Define the following macro to have status notification handling debugged //#define GNASH_DEBUG_STATUS +namespace { + // Used to free data in the AVPackets we create our self -static void avpacket_destruct(AVPacket* av) +void avpacket_destruct(AVPacket* av) { delete [] av->data; } +} // anonymous namespace + namespace gnash { -NetStreamFfmpeg::NetStreamFfmpeg(): +NetStreamFfmpeg::NetStreamFfmpeg() + : - _playback_state(PLAY_NONE), _decoding_state(DEC_NONE), m_video_index(-1), @@ -82,9 +86,11 @@ m_last_video_timestamp(0), m_last_audio_timestamp(0), - m_current_timestamp(0), + + _playbackClock(new InterruptableVirtualClock(new SystemClock)), + _playHead(_playbackClock.get()), + m_unqueued_data(NULL), - m_time_of_pause(0), _decoderBuffer(0), _soundHandler(get_sound_handler()) @@ -106,23 +112,21 @@ void NetStreamFfmpeg::pause( PauseMode mode ) { log_debug("::pause(%d) called ", mode); - switch ( mode ) { - case pauseModeToggle: - if ( playbackStatus() == PLAY_PAUSED ) { - unpausePlayback(); - } else { - pausePlayback(); - } + switch ( mode ) + { + case pauseModeToggle: + if ( _playHead.getState() == PlayHead::PLAY_PAUSED) unpausePlayback(); + else pausePlayback(); break; - case pauseModePause: + case pauseModePause: pausePlayback(); break; - case pauseModeUnPause: + case pauseModeUnPause: unpausePlayback(); break; - default: + default: break; - } + } } @@ -164,11 +168,6 @@ delete m_unqueued_data; m_unqueued_data = NULL; - boost::mutex::scoped_lock lock(_qMutex); - - m_qvideo.clear(); - m_qaudio.clear(); - delete [] ByteIOCxt.buffer; } @@ -226,12 +225,10 @@ void NetStreamFfmpeg::play(const std::string& c_url) { - // Is it already playing ? - if (playbackStatus() != PLAY_NONE && playbackStatus() != PLAY_STOPPED) + if ( m_parser.get() ) { - log_error("NetStream.play() called already playing ?"); // TODO: fix this case - //unpausePlayback(); // will check for playbackStatus itself.. + log_error("NetStream.play() called while already streaming ?"); // TODO: fix this case return; } @@ -258,15 +255,13 @@ return; } - //decodingStatus(DEC_BUFFERING); - // We need to restart the audio if (_soundHandler) _soundHandler->attach_aux_streamer(audio_streamer, this); // This starts the decoding thread - _decodeThread = new boost::thread(boost::bind(NetStreamFfmpeg::av_streamer, this)); - _decodeThreadBarrier.wait(); + //_decodeThread = new boost::thread(boost::bind(NetStreamFfmpeg::av_streamer, this)); + //_decodeThreadBarrier.wait(); return; } @@ -467,8 +462,6 @@ m_video_index = 0; m_audio_index = 1; - m_start_onbuffer = true; - // Allocate a frame to store the decoded frame in m_Frame = avcodec_alloc_frame(); } @@ -602,8 +595,16 @@ } } - playbackStatus(PLAY_PLAYING); - m_start_clock = clocktime::getTicks(); + _playHead.init(m_VCodecCtx!=0, false); // second arg should be m_ACodecCtx!=0, but we're testing video only for now + _playHead.setState(PlayHead::PLAY_PLAYING); + + decodingStatus(DEC_BUFFERING); + +//#ifdef GNASH_DEBUG_STATUS + log_debug("Setting playStart status"); +//#endif + setStatus(playStart); + return true; } @@ -638,106 +639,14 @@ ns->_decodeThreadBarrier.wait(); - //assert (ns->m_ACodecCtx); // is only set if audio decoder could be initialized - //assert (ns->m_VCodecCtx); // is only set if video decder could be initialized - //assert (ns->m_FormatCtx); // is only set for non-flv - - ns->setStatus(playStart); - - ns->m_last_video_timestamp = 0; - ns->m_last_audio_timestamp = 0; - ns->m_current_timestamp = 0; - - ns->m_start_clock = clocktime::getTicks(); - - ns->m_unqueued_data = NULL; - - // Loop until killed - while ( ! ns->decodeThreadKillRequested() ) // locks _qMutex + // Parse in a thread... + abort(); // has to be fixed to use mutex against parser + // FIXME: + while ( ! ns->m_parser->parsingCompleted() + && ! ns->decodeThreadKillRequested() ) { - unsigned long int sleepTime = 1000; - - { -#ifdef GNASH_DEBUG_THREADS - log_debug("qMutex: waiting for lock in av_streamer"); -#endif - boost::mutex::scoped_lock lock(ns->_qMutex); -#ifdef GNASH_DEBUG_THREADS - log_debug("qMutex: lock obtained in av_streamer"); -#endif - - if ( ns->decodingStatus() == DEC_STOPPED ) - { - log_debug("Dec stopped (eof), waiting on qNeedRefill condition"); - ns->_qFillerResume.wait(lock); - continue; // will release the lock for a moment - } - -#ifdef GNASH_DEBUG_THREADS - log_debug("Decoding iteration. bufferTime=%lu, bufferLen=%lu, videoFrames=%lu, audioFrames=%lu", - ns->bufferTime(), ns->bufferLength(), ns->m_qvideo.size(), ns->m_qaudio.size()); -#endif - - if (ns->m_isFLV) - { - // If any of the two queues are full don't bother fetching more - // (next consumer will wake us up) - // - if ( ns->m_qvideo.full() || ns->m_qaudio.full() ) - { - ns->decodingStatus(DEC_DECODING); // that's to say: not buffering anymore - - // Instead wait till waked up by short-queues event - log_debug("Queues full, waiting on qNeedRefill condition"); - ns->_qFillerResume.wait(lock); - } - else - { - log_debug("Calling decodeFLVFrame"); - bool successDecoding = ns->decodeFLVFrame(); - //log_debug("decodeFLVFrame returned %d", successDecoding); - if ( ! successDecoding ) - { - // Possible failures: - // 1. could not decode frame... lot's of possible - // reasons... - // 2. EOF reached - if ( ns->m_videoFrameFormat != render::NONE ) - { - log_error("Could not decode FLV frame"); - } - // else it's expected, we'll keep going anyway - } - - } - - } - else - { - - // If we have problems with decoding - break - if (ns->decodeMediaFrame() == false && ns->m_start_onbuffer == false && ns->m_qvideo.size() == 0 && ns->m_qaudio.size() == 0) - { - break; - } - - } - -#ifdef GNASH_DEBUG_THREADS - log_debug("qMutex: releasing lock in av_streamer"); -#endif - } - - //log_debug("Sleeping %d microseconds", sleepTime); - usleep(sleepTime); // Sleep 1ms to avoid busying the processor. - + ns->m_parser->parseNextTag(); } - -//#ifdef GNASH_DEBUG_THREADS - log_debug("Out of decoding loop. playbackStatus:%d, decodingStatus:%d", ns->playbackStatus(), ns->decodingStatus()); -//#endif - ns->decodingStatus(DEC_STOPPED); - } // audio callback is running in sound handler thread @@ -745,6 +654,10 @@ { //GNASH_REPORT_FUNCTION; + return false; + +#if 0 // no audio for now, needs proper mutex design first (SDL sound handler runs in a thread) + NetStreamFfmpeg* ns = static_cast(owner); PlaybackState pbStatus = ns->playbackStatus(); @@ -797,10 +710,100 @@ #endif } return true; +#endif +} + +media::raw_mediadata_t* +NetStreamFfmpeg::getDecodedVideoFrame(boost::uint32_t ts) +{ + if ( ! m_parser.get() ) + { + log_error("getDecodedVideoFrame: no parser available"); + return 0; // no parser, no party + } + + FLVVideoFrameInfo* info = m_parser->peekNextVideoFrameInfo(); + if ( ! info ) + { + log_error("getDecodedVideoFrame(%d): no more video frames in input (peekNextVideoFrameInfo returned false)"); + decodingStatus(DEC_STOPPED); + return 0; + } + + if ( info->timestamp > ts ) + { + log_error("getDecodedVideoFrame(%d): next video frame is in the future (%d)", ts, info->timestamp); + return 0; // next frame is in the future + } + + // Loop until a good frame is found + media::raw_mediadata_t* video = 0; + while ( 1 ) + { + video = decodeNextVideoFrame(); + if ( ! video ) + { + log_error("peekNextVideoFrameInfo returned some info, " + "but decodeNextVideoFrame returned null, " + "I don't think this should ever happen"); + break; + } + + FLVVideoFrameInfo* info = m_parser->peekNextVideoFrameInfo(); + if ( ! info ) + { + // the one we decoded was the last one + log_debug("last video frame decoded (should set playback status to STOP?)"); + break; + } + if ( info->timestamp > ts ) + { + // the next one is in the future, we'll return this one. + log_debug("next video frame is in the future, we'll return this one"); + break; // the one we decoded + } + } + + return video; +} + +media::raw_mediadata_t* +NetStreamFfmpeg::decodeNextVideoFrame() +{ + if ( ! m_parser.get() ) + { + log_error("decodeNextVideoFrame: no parser available"); + return 0; // no parser, no party + } + + FLVFrame* frame = m_parser->nextVideoFrame(); + if (frame == NULL) + { + log_debug("decodeNextVideoFrame: no more video frames in input"); + return 0; + } + assert (frame->type == videoFrame); + + AVPacket packet; + + packet.destruct = avpacket_destruct; // needed ? + packet.size = frame->dataSize; + packet.data = frame->data; + // FIXME: is this the right value for packet.dts? + packet.pts = packet.dts = static_cast(frame->timestamp); + assert (frame->type == videoFrame); + packet.stream_index = 0; + + return decodeVideo(&packet); } -bool NetStreamFfmpeg::decodeFLVFrame() +bool +NetStreamFfmpeg::decodeFLVFrame() { +#if 1 + abort(); + return false; +#else FLVFrame* frame = m_parser->nextMediaFrame(); // we don't care which one, do we ? if (frame == NULL) @@ -822,21 +825,40 @@ if (frame->type == videoFrame) { packet.stream_index = 0; - return decodeVideo(&packet); + media::raw_mediadata_t* video = decodeVideo(&packet); + assert (m_isFLV); + if (video) + { + // NOTE: Caller is assumed to have locked _qMutex already + if ( ! m_qvideo.push(video) ) + { + log_error("Video queue full !"); + } + } } else { assert(frame->type == audioFrame); packet.stream_index = 1; - return decodeAudio(&packet); + media::raw_mediadata_t* audio = decodeAudio(&packet); + if ( audio ) + { + if ( ! m_qaudio.push(audio) ) + { + log_error("Audio queue full!"); + } + } } + return true; +#endif } -bool NetStreamFfmpeg::decodeAudio( AVPacket* packet ) +media::raw_mediadata_t* +NetStreamFfmpeg::decodeAudio( AVPacket* packet ) { - if (!m_ACodecCtx) return false; + if (!m_ACodecCtx) return 0; int frame_size; //static const unsigned int bufsize = (AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2; @@ -944,26 +966,20 @@ m_last_audio_timestamp += frame_delay; - if (m_isFLV) - { - if ( ! m_qaudio.push(raw) ) - { - log_error("Audio queue full!"); - } - } - else m_unqueued_data = m_qaudio.push(raw) ? NULL : raw; + return raw; } - return true; + return 0; } -bool NetStreamFfmpeg::decodeVideo(AVPacket* packet) +media::raw_mediadata_t* +NetStreamFfmpeg::decodeVideo(AVPacket* packet) { - if (!m_VCodecCtx) return false; + if (!m_VCodecCtx) return NULL; int got = 0; avcodec_decode_video(m_VCodecCtx, m_Frame, &got, packet->data, packet->size); - if (!got) return false; + if (!got) return NULL; // This tmpImage is really only used to compute proper size of the video data... // stupid isn't it ? @@ -982,7 +998,7 @@ if (m_videoFrameFormat == render::NONE) { // NullGui? - return false; + return NULL; } else if (m_videoFrameFormat == render::YUV && m_VCodecCtx->pix_fmt != PIX_FMT_YUV420P) @@ -997,7 +1013,7 @@ rgbpicture = media::VideoDecoderFfmpeg::convertRGB24(m_VCodecCtx, *m_Frame); if (!rgbpicture.data[0]) { - return false; + return NULL; } } @@ -1088,21 +1104,14 @@ } - // NOTE: Caller is assumed to have locked _qMutex already - if (m_isFLV) - { - if ( ! m_qvideo.push(video) ) - { - log_error("Video queue full !"); - } - } - else m_unqueued_data = m_qvideo.push(video) ? NULL : video; - - return true; + return video; } bool NetStreamFfmpeg::decodeMediaFrame() { + return false; + +#if 0 // Only FLV for now (non-FLV should be threated the same as FLV, using a MediaParser in place of the FLVParser) if (m_unqueued_data) { @@ -1132,20 +1141,24 @@ { if (packet.stream_index == m_audio_index && _soundHandler) { - if (!decodeAudio(&packet)) + media::raw_mediadata_t* audio = decodeAudio(&packet); + if (!audio) { log_error(_("Problems decoding audio frame")); return false; } + m_unqueued_data = m_qaudio.push(audio) ? NULL : audio; } else if (packet.stream_index == m_video_index) { - if (!decodeVideo(&packet)) + media::raw_mediadata_t* video = decodeVideo(&packet); + if (!video) { log_error(_("Problems decoding video frame")); return false; } + m_unqueued_data = m_qvideo.push(video) ? NULL : video; } av_free_packet(&packet); } @@ -1156,15 +1169,24 @@ } return true; +#endif } void -NetStreamFfmpeg::seek(boost::uint32_t pos) +NetStreamFfmpeg::seek(boost::uint32_t posSeconds) { GNASH_REPORT_FUNCTION; - // We'll mess with the queues here - boost::mutex::scoped_lock lock(_qMutex); + // We'll mess with the input here + if ( ! m_parser.get() ) + { + log_debug("NetStreamFfmpeg::seek(%d): no parser, no party", posSeconds); + return; + } + + // Don't ask me why, but NetStream::seek() takes seconds... + boost::uint32_t pos = posSeconds*1000; + long newpos = 0; double timebase = 0; @@ -1172,18 +1194,11 @@ // Seek to new position if (m_isFLV) { - if (m_parser.get()) - { - newpos = m_parser->seek(pos); - } - else - { - newpos = 0; - } + newpos = m_parser->seek(pos); + log_debug("m_parser->seek(%d) returned %d", pos, newpos); } else if (m_FormatCtx) { - AVStream* videostream = m_FormatCtx->streams[m_video_index]; timebase = static_cast(videostream->time_base.num / videostream->time_base.den); newpos = static_cast(pos / timebase); @@ -1205,20 +1220,11 @@ { m_last_video_timestamp = 0; m_last_audio_timestamp = 0; - m_current_timestamp = 0; - - m_start_clock = clocktime::getTicks(); - } else if (m_isFLV) { - - if (m_VCodecCtx) m_start_clock += m_last_video_timestamp - newpos; - else m_start_clock += m_last_audio_timestamp - newpos; - if (m_ACodecCtx) m_last_audio_timestamp = newpos; if (m_VCodecCtx) m_last_video_timestamp = newpos; - m_current_timestamp = newpos; } else { @@ -1240,146 +1246,159 @@ av_free_packet( &Packet ); av_seek_frame(m_FormatCtx, m_video_index, newpos, 0); - boost::uint32_t newtime_ms = static_cast(newtime / 1000.0); - m_start_clock += m_last_audio_timestamp - newtime_ms; + newpos = static_cast(newtime / 1000.0); - m_last_audio_timestamp = newtime_ms; - m_last_video_timestamp = newtime_ms; - m_current_timestamp = newtime_ms; + m_last_audio_timestamp = newpos; + m_last_video_timestamp = newpos; } - // Flush the queues - m_qvideo.clear(); - m_qaudio.clear(); + // 'newpos' will always be on a keyframe (supposedly) + _playHead.seekTo(newpos); - decodingStatus(DEC_DECODING); // or ::refreshVideoFrame will send a STOPPED again - if ( playbackStatus() == PLAY_STOPPED ) - { - // restart playback (if not paused) - playbackStatus(PLAY_PLAYING); - } + decodingStatus(DEC_BUFFERING); // make sure we have enough things in buffer _qFillerResume.notify_all(); // wake it decoder is sleeping + refreshVideoFrame(true); } void -NetStreamFfmpeg::refreshVideoFrame() +NetStreamFfmpeg::parseNextChunk() { -#ifdef GNASH_DEBUG_THREADS - log_debug("qMutex: waiting for lock in refreshVideoFrame"); -#endif - boost::mutex::scoped_lock lock(_qMutex); -#ifdef GNASH_DEBUG_THREADS - log_debug("qMutex: lock obtained in refreshVideoFrame"); -#endif + // TODO: parse as much as possible w/out blocking + // (will always block currently..) + const int tagsPerChunk = 2; + for (int i=0; iparseNextTag(); +} - // If we're paused (and we got the first imageframe), there is no need to do this - if (playbackStatus() == PLAY_PAUSED && m_imageframe) +void +NetStreamFfmpeg::refreshVideoFrame(bool alsoIfPaused) +{ + + if ( ! m_parser.get() ) { - log_debug("refreshVideoFrame doing nothing as playback is paused and we have an image frame already"); -#ifdef GNASH_DEBUG_THREADS - log_debug("qMutex: releasing lock in refreshVideoFrame"); -#endif + log_debug("%p.refreshVideoFrame: no parser, no party", this); return; } - // Loop until a good frame is found - do + if ( decodingStatus() == DEC_DECODING && bufferLength() == 0) { - // Get video frame from queue, will have the lowest timestamp - // will return NULL if empty(). See multithread_queue::front - media::raw_mediadata_t* video = m_qvideo.front(); - - // If the queue is empty either we're waiting for more data - // to be decoded or we're out of data - if (!video) + if ( ! m_parser->parsingCompleted() ) { - log_debug("refreshVideoFrame:: No more video frames in queue"); - - if ( decodingStatus() == DEC_STOPPED ) - { - if ( playbackStatus() != PLAY_STOPPED ) - { - playbackStatus(PLAY_STOPPED); -//#ifdef GNASH_DEBUG_STATUS - log_debug("Setting playStop status"); -//#endif - setStatus(playStop); - } - } - else - { - // There no video but decoder is still running - // not much to do here except wait for next call - //assert(decodingStatus() == DEC_BUFFERING); - } - - break; + log_debug("%p.refreshVideoFrame: buffer empty while decoding," + " setting buffer to buffering and pausing playback clock", + this); + setStatus(bufferEmpty); + decodingStatus(DEC_BUFFERING); + _playbackClock->pause(); } - - // Caclulate the current time - boost::uint32_t current_clock; - if (m_ACodecCtx && _soundHandler) + else { - current_clock = m_current_timestamp; + // set playStop ? (will be done later for now) } - else + } + + if ( decodingStatus() == DEC_BUFFERING ) + { + if ( bufferLength() < m_bufferTime ) { - current_clock = clocktime::getTicks() - m_start_clock; - m_current_timestamp = current_clock; + log_debug("%p.refreshVideoFrame: buffering" + " - position=%d, buffer=%d/%d", + this, _playHead.getPosition(), bufferLength(), m_bufferTime); + return; } + log_debug("%p.refreshVideoFrame: buffer full, resuming playback clock" + " - position=%d, buffer=%d/%d", + this, _playHead.getPosition(), bufferLength(), m_bufferTime); + setStatus(bufferFull); + decodingStatus(DEC_DECODING); + _playbackClock->resume(); + } - boost::uint32_t video_clock = video->m_pts; + if ( ! alsoIfPaused && _playHead.getState() == PlayHead::PLAY_PAUSED ) + { + log_debug("%p.refreshVideoFrame: doing nothing as playhead is paused - " + "bufferLength=%d, bufferTime=%d", + this, bufferLength(), m_bufferTime); + return; + } - // If the timestamp on the videoframe is smaller than the - // current time, we put it in the output image. - if (current_clock >= video_clock) - { + if ( _playHead.isVideoConsumed() ) + { + log_debug("%p.refreshVideoFrame: doing nothing " + "as current position was already decoded - " + "bufferLength=%d, bufferTime=%d", + this, bufferLength(), m_bufferTime); + return; + } - if (m_videoFrameFormat == render::YUV) - { - if ( ! m_imageframe ) m_imageframe = new image::yuv(m_VCodecCtx->width, m_VCodecCtx->height); - // XXX m_imageframe might be a byte aligned buffer, while video is not! - static_cast(m_imageframe)->update(video->m_data); - } - else if (m_videoFrameFormat == render::RGB) - { - if ( ! m_imageframe ) m_imageframe = new image::rgb(m_VCodecCtx->width, m_VCodecCtx->height); - image::rgb* imgframe = static_cast(m_imageframe); - rgbcopy(imgframe, video, m_VCodecCtx->width * 3); - } + // Caclulate the current time + boost::uint64_t curPos = _playHead.getPosition(); - // Delete the frame from the queue - m_qvideo.pop(); - delete video; + log_debug("%p.refreshVideoFrame: currentPosition=%d, playHeadState=%d, bufferLength=%d, bufferTime=%d", + this, curPos, _playHead.getState(), bufferLength(), m_bufferTime); - // wake up filler (TODO: do only if decoder is running) - // TODO2: resume only at end of loop ? - _qFillerResume.notify_all(); - // A frame is ready for pickup - m_newFrameReady = true; + // Get next decoded video frame from parser, will have the lowest timestamp + media::raw_mediadata_t* video = getDecodedVideoFrame(curPos); + // to be decoded or we're out of data + if (!video) + { + if ( decodingStatus() == DEC_STOPPED ) + { + log_debug("%p.refreshVideoFrame(): no more video frames to decode, sending STOP event", this); +//#ifdef GNASH_DEBUG_STATUS + log_debug("Setting playStop status"); +//#endif + setStatus(playStop); } else { - // The timestamp on the first frame in the queue is greater - // than the current time, so no need to do anything. - break; + log_debug("%p.refreshVideoFrame(): last video frame was good enough for current position", this); + // There no video but decoder is still running + // not much to do here except wait for next call + //assert(decodingStatus() == DEC_BUFFERING); } - } while(!m_qvideo.empty()); + } + else + { + + if (m_videoFrameFormat == render::YUV) + { + if ( ! m_imageframe ) m_imageframe = new image::yuv(m_VCodecCtx->width, m_VCodecCtx->height); + // XXX m_imageframe might be a byte aligned buffer, while video is not! + static_cast(m_imageframe)->update(video->m_data); + } + else if (m_videoFrameFormat == render::RGB) + { + if ( ! m_imageframe ) m_imageframe = new image::rgb(m_VCodecCtx->width, m_VCodecCtx->height); + image::rgb* imgframe = static_cast(m_imageframe); + rgbcopy(imgframe, video, m_VCodecCtx->width * 3); + } + + // Delete the frame from the queue + delete video; + + // A frame is ready for pickup + m_newFrameReady = true; + } + + // We consumed video of current position, feel free to advance if needed + _playHead.setVideoConsumed(); + -#ifdef GNASH_DEBUG_THREADS - log_debug("qMutex: releasing lock in refreshVideoFrame"); -#endif } void NetStreamFfmpeg::advance() { - //log_debug("advance"); + log_debug("%p.advance : bufferLength=%d, bufferTime=%d", + this, bufferLength(), m_bufferTime); + + if ( m_parser.get() ) parseNextChunk(); // Check if there are any new status messages, and if we should // pass them to a event handler @@ -1388,109 +1407,83 @@ // Find video frame with the most suited timestamp in the video queue, // and put it in the output image frame. refreshVideoFrame(); + + // Refill audio buffer to consume all samples + // up to current playhead + //refreshAudioBuffer(); } boost::int32_t NetStreamFfmpeg::time() { - - if (m_FormatCtx && m_FormatCtx->nb_streams > 0) - { - double time = (double)m_FormatCtx->streams[0]->time_base.num / (double)m_FormatCtx->streams[0]->time_base.den * (double)m_FormatCtx->streams[0]->cur_dts; - return static_cast(time); - } - else if - (m_isFLV) - { - return m_current_timestamp; - } - else - { - return 0; - } + return _playHead.getPosition(); } void NetStreamFfmpeg::pausePlayback() { GNASH_REPORT_FUNCTION; - if (playbackStatus() == PLAY_PAUSED) return; - - playbackStatus(PLAY_PAUSED); - - // Save the current time so we later can tell how long the pause lasted - m_time_of_pause = clocktime::getTicks(); + PlayHead::PlaybackStatus oldStatus = _playHead.setState(PlayHead::PLAY_PAUSED); - // Disconnect the soundhandler so we don't play while paused - if ( _soundHandler ) _soundHandler->detach_aux_streamer((void*)this); + // Disconnect the soundhandler if we were playing before + if ( oldStatus == PlayHead::PLAY_PLAYING && _soundHandler ) + { + _soundHandler->detach_aux_streamer((void*)this); + } } void NetStreamFfmpeg::unpausePlayback() { GNASH_REPORT_FUNCTION; - if (playbackStatus() == PLAY_PLAYING) // already playing - { - log_debug("unpausePlayback: already playing"); - return; - } - - playbackStatus(PLAY_PLAYING); + PlayHead::PlaybackStatus oldStatus = _playHead.setState(PlayHead::PLAY_PLAYING); - if (m_current_timestamp == 0) + // Re-connect to the soundhandler if we were paused before + if ( oldStatus == PlayHead::PLAY_PAUSED && _soundHandler ) { - m_start_clock = clocktime::getTicks(); + _soundHandler->attach_aux_streamer(audio_streamer, (void*) this); } - else - { - // Add the paused time to the start time so that the playhead doesn't - // noticed that we have been paused - m_start_clock += clocktime::getTicks() - m_time_of_pause; - } - - // (re)-connect to the soundhandler. - // It was disconnected in ::pausePlayback to avoid to keep playing sound while paused - if ( _soundHandler ) _soundHandler->attach_aux_streamer(audio_streamer, (void*) this); } long NetStreamFfmpeg::bytesLoaded () { - long ret_val = 0; - - if ( _netCon ) + if ( ! m_parser.get() ) { - ret_val = _netCon->getBytesLoaded(); + log_debug("bytesLoaded: no parser, no party"); + return 0; } - return ret_val; + return m_parser->getBytesLoaded(); } - long -NetStreamFfmpeg::bytesTotal () +NetStreamFfmpeg::bufferLength () { - long ret_val = 0; - - if ( _netCon ) + if ( ! m_parser.get() ) { - ret_val = _netCon->getBytesTotal(); + log_debug("bytesTotal: no parser, no party"); + return 0; } - return ret_val; + boost::uint32_t maxTimeInBuffer = m_parser->getBufferLength(); + boost::uint64_t curPos = _playHead.getPosition(); + + if ( maxTimeInBuffer < curPos ) return 0; + return maxTimeInBuffer-curPos; } -NetStreamFfmpeg::PlaybackState -NetStreamFfmpeg::playbackStatus(PlaybackState newstate) +long +NetStreamFfmpeg::bytesTotal () { - boost::mutex::scoped_lock lock(_state_mutex); - - if (newstate != PLAY_NONE) { - _playback_state = newstate; - } + if ( ! m_parser.get() ) + { + log_debug("bytesTotal: no parser, no party"); + return 0; + } - return _playback_state; + return m_parser->getBytesTotal(); } NetStreamFfmpeg::DecodingState @@ -1511,14 +1504,6 @@ GNASH_REPORT_FUNCTION; { -#ifdef GNASH_DEBUG_THREADS - log_debug("qMutex: waiting for lock in killDecodeThread"); -#endif - boost::mutex::scoped_lock lock(_qMutex); -#ifdef GNASH_DEBUG_THREADS - log_debug("qMutex: lock obtained in killDecodeThread"); -#endif - _qFillerKillRequest = true; _qFillerResume.notify_all(); // wake it up if waiting.. } @@ -1536,7 +1521,6 @@ bool NetStreamFfmpeg::decodeThreadKillRequested() { - boost::mutex::scoped_lock lock(_qMutex); return _qFillerKillRequest; } Index: server/asobj/NetStreamFfmpeg.h =================================================================== RCS file: /sources/gnash/gnash/server/asobj/NetStreamFfmpeg.h,v retrieving revision 1.68 diff -u -r1.68 NetStreamFfmpeg.h --- server/asobj/NetStreamFfmpeg.h 23 May 2008 05:58:09 -0000 1.68 +++ server/asobj/NetStreamFfmpeg.h 24 May 2008 10:38:45 -0000 @@ -36,6 +36,9 @@ #include #include +#include +#include + #include "impl.h" #ifdef HAVE_FFMPEG_AVFORMAT_H @@ -53,6 +56,7 @@ #include "image.h" #include "StreamProvider.h" #include "NetStream.h" // for inheritance +#include "VirtualClock.h" #include "ffmpegNetStreamUtil.h" @@ -109,6 +113,7 @@ long bytesTotal(); + long bufferLength(); private: enum PlaybackState { @@ -125,7 +130,6 @@ DEC_BUFFERING, }; - PlaybackState _playback_state; DecodingState _decoding_state; // Mutex protecting _playback_state and _decoding_state @@ -163,7 +167,11 @@ /// is that refreshVideoFrame() is called right before get_video(). This is important /// to ensure timing is correct.. /// - void refreshVideoFrame(); + /// @param alsoIfPaused + /// If true, video is consumed/refreshed even if playhead is paused. + /// By default this is false, but will be used on ::seek (user-reguested) + /// + void refreshVideoFrame(bool alsoIfPaused=false); // Used to decode and push the next available (non-FLV) frame to the audio or video queue bool decodeMediaFrame(); @@ -197,35 +205,45 @@ /// bool decodeFLVFrame(); - /// Used to decode a video frame and push it on the videoqueue + /// Decode next video frame fetching it MediaParser cursor + // + /// @return 0 on EOF or error, a decoded video otherwise + /// + media::raw_mediadata_t* decodeNextVideoFrame(); + + /// Decode input frames up to the one with timestamp <= ts. // - /// Also updates m_imageframe (why !??) + /// Decoding starts from "next" element in the parser cursor. /// + /// Return 0 if: + /// 1. there's no parser active. + /// 2. parser cursor is already on last frame. + /// 3. next element in cursor has timestamp > tx + /// 4. there was an error decoding + /// + media::raw_mediadata_t* getDecodedVideoFrame(boost::uint32_t ts); + + /// Used to decode a video frame + // /// This is a blocking call. - /// If no Video decoding context exists (m_VCodecCtx), false is returned. - /// On decoding (or converting) error, false is returned. - /// If renderer requested video format is render::NONE, false is returned. - /// In any other case, true is returned. + /// If no Video decoding context exists (m_VCodecCtx), 0 is returned. + /// On decoding (or converting) error, 0 is returned. + /// If renderer requested video format is render::NONE, 0 is returned. + /// In any other case, a decoded video frame is returned. /// - /// NOTE: (FIXME) if video queue is full, - /// we'd still return true w/out pushing anything new there - /// /// TODO: return a more informative value to tell what happened. /// - bool decodeVideo( AVPacket* packet ); + media::raw_mediadata_t* decodeVideo( AVPacket* packet ); - /// Used to decode a audio frame and push it on the audioqueue + /// Used to decode an audio frame // /// This is a blocking call. - /// If no Video decoding context exists (m_ACodecCtx), false is returned. - /// In any other case, true is returned. + /// If no Video decoding context exists (m_ACodecCtx), 0 is returned. + /// In any other case, a decoded audio frame is returned. /// - /// NOTE: (FIXME) if audio queue is full, - /// we'd still return true w/out pushing anything new there - /// /// TODO: return a more informative value to tell what happened. /// - bool decodeAudio( AVPacket* packet ); + media::raw_mediadata_t* decodeAudio( AVPacket* packet ); // Used to calculate a decimal value from a ffmpeg fraction inline double as_double(AVRational time) @@ -233,7 +251,6 @@ return time.num / (double) time.den; } - PlaybackState playbackStatus(PlaybackState newstate = PLAY_NONE); DecodingState decodingStatus(DecodingState newstate = DEC_NONE); int m_video_index; @@ -286,24 +303,14 @@ // The timestamp of the last decoded audio frame, in seconds. volatile boost::uint32_t m_last_audio_timestamp; - // The timestamp of the last played audio (default) or video (if no audio) frame. - // Misured in seconds. - boost::uint32_t m_current_timestamp; - - /// The queues of audio and video data. - typedef media::ElementsOwningQueue MediaQueue; - - MediaQueue m_qaudio; - MediaQueue m_qvideo; - - /// Mutex protecting access to queues - boost::mutex _qMutex; - /// Queues filler will wait on this condition when queues are full boost::condition _qFillerResume; - // The time we started playing in seconds (since VM start ?) - volatile boost::uint64_t m_start_clock; + /// Virtual clock used as playback clock source + std::auto_ptr _playbackClock; + + /// Playback control device + PlayHead _playHead; // When the queues are full, this is where we keep the audio/video frame // there wasn't room for on its queue @@ -311,14 +318,16 @@ ByteIOContext ByteIOCxt; - // Time of when pause started, in seconds since VM started - volatile boost::uint64_t m_time_of_pause; - // Decoder buffer boost::uint8_t* _decoderBuffer; // Current sound handler media::sound_handler* _soundHandler; + + /// Parse a chunk of input + /// Currently blocks, ideally should parse as much + /// as possible w/out blocking + void parseNextChunk(); };