void Timer::removeObserver(Observer& anObserver) { std::set<Observer*>::iterator it = m_observers.find(&anObserver); if (it == m_observers.end()) { sfeLogWarning("Timer::removeObserver() - removing an unregistered observer. Ignored."); } else { m_observers.erase(it); } }
void Stream::flushBuffers() { sf::Lock l(m_readerMutex); if (getStatus() == Playing) { sfeLogWarning("packets flushed while the stream is still playing"); } if (m_formatCtx && m_stream) avcodec_flush_buffers(m_stream->codec); AVPacket* pkt = nullptr; while (m_packetList.size()) { pkt = m_packetList.front(); m_packetList.pop_front(); av_free_packet(pkt); av_free(pkt); } }
bool Demuxer::didSeek(const Timer &timer, sf::Time oldPosition) { resetEndOfFileStatus(); sf::Time newPosition = timer.getOffset(); std::set< std::shared_ptr<Stream> > connectedStreams; if (m_connectedVideoStream) connectedStreams.insert(m_connectedVideoStream); if (m_connectedAudioStream) connectedStreams.insert(m_connectedAudioStream); if (m_connectedSubtitleStream) connectedStreams.insert(m_connectedSubtitleStream); CHECK(!connectedStreams.empty(), "Inconcistency error: seeking with no active stream"); // Trivial seeking to beginning if (newPosition == sf::Time::Zero) { int64_t timestamp = 0; if (m_formatCtx->iformat->flags & AVFMT_SEEK_TO_PTS && m_formatCtx->start_time != AV_NOPTS_VALUE) timestamp += m_formatCtx->start_time; // Flush all streams for (std::shared_ptr<Stream> stream : connectedStreams) stream->flushBuffers(); flushBuffers(); // Seek to beginning int err = avformat_seek_file(m_formatCtx, -1, INT64_MIN, timestamp, INT64_MAX, AVSEEK_FLAG_BACKWARD); if (err < 0) { sfeLogError("Error while seeking at time " + s(newPosition.asMilliseconds()) + "ms"); return false; } } else // Seeking to some other position { // Initial target seek point int64_t timestamp = newPosition.asSeconds() * AV_TIME_BASE; // < 0 = before seek point // > 0 = after seek point std::map< std::shared_ptr<Stream>, sf::Time> seekingGaps; static const float brokenSeekingThreshold = 60.f; // seconds bool didReseekBackward = false; bool didReseekForward = false; int tooEarlyCount = 0; int tooLateCount = 0; int brokenSeekingCount = 0; int ffmpegSeekFlags = AVSEEK_FLAG_BACKWARD; do { // Flush all streams for (std::shared_ptr<Stream> stream : connectedStreams) stream->flushBuffers(); flushBuffers(); // Seek to new estimated target if (m_formatCtx->iformat->flags & AVFMT_SEEK_TO_PTS && m_formatCtx->start_time != AV_NOPTS_VALUE) timestamp += m_formatCtx->start_time; int err = avformat_seek_file(m_formatCtx, -1, timestamp - 10 * AV_TIME_BASE, timestamp, timestamp, ffmpegSeekFlags); CHECK0(err, "avformat_seek_file failure"); // Compute the new gap for (std::shared_ptr<Stream> stream : connectedStreams) { sf::Time gap = stream->computeEncodedPosition() - newPosition; seekingGaps[stream] = gap; } tooEarlyCount = 0; tooLateCount = 0; brokenSeekingCount = 0; // Check the current situation for (std::pair< std::shared_ptr<Stream>, sf::Time>&& gapByStream : seekingGaps) { // < 0 = before seek point // > 0 = after seek point const sf::Time& gap = gapByStream.second; float absoluteDiff = fabs(gap.asSeconds()); // Before seek point if (gap < sf::Time::Zero) { if (absoluteDiff > brokenSeekingThreshold) { brokenSeekingCount++; tooEarlyCount++; } // else: a bit early but not too much, this is the final situation we want } // After seek point else if (gap > sf::Time::Zero) { tooLateCount++; if (absoluteDiff > brokenSeekingThreshold) brokenSeekingCount++; // TODO: unhandled for now => should seek to non-key frame } if (brokenSeekingCount > 0) sfeLogWarning("Seeking on " + gapByStream.first->description() + " is broken! Gap: " + s(gap.asSeconds()) + "s"); } CHECK(false == (tooEarlyCount && tooLateCount), "Both too late and too early for different streams, unhandled situation!"); // Define what to do next if (tooEarlyCount) { // Go forward by 1 sec timestamp += AV_TIME_BASE; didReseekForward = true; } else if (tooLateCount) { // Go backward by 1 sec timestamp -= AV_TIME_BASE; didReseekBackward = true; } if (brokenSeekingCount) { if (ffmpegSeekFlags & AVSEEK_FLAG_ANY) { sfeLogError("Seeking is really broken in the media, giving up"); return false; } else { // Try to seek to non-key frame before giving up // Image may be wrong but it's better than nothing :) ffmpegSeekFlags |= AVSEEK_FLAG_ANY; sfeLogError("Media has broken seeking index, trying to seek to non-key frame"); } } CHECK(!(didReseekBackward && didReseekForward), "infinitely seeking backward and forward"); } while (tooEarlyCount != 0 || tooLateCount != 0); } return true; }
Demuxer::Demuxer(const std::string& sourceFile, std::shared_ptr<Timer> timer, VideoStream::Delegate& videoDelegate, SubtitleStream::Delegate& subtitleDelegate) : m_formatCtx(nullptr), m_eofReached(false), m_streams(), m_ignoredStreams(), m_synchronized(), m_timer(timer), m_connectedAudioStream(nullptr), m_connectedVideoStream(nullptr), m_connectedSubtitleStream(nullptr), m_duration(sf::Time::Zero) { CHECK(sourceFile.size(), "Demuxer::Demuxer() - invalid argument: sourceFile"); CHECK(timer, "Inconsistency error: null timer"); int err = 0; // Load all the decoders loadFFmpeg(); // Open the movie file err = avformat_open_input(&m_formatCtx, sourceFile.c_str(), nullptr, nullptr); CHECK0(err, "Demuxer::Demuxer() - error while opening media: " + sourceFile); CHECK(m_formatCtx, "Demuxer() - inconsistency: media context cannot be nullptr"); // Read the general movie informations err = avformat_find_stream_info(m_formatCtx, nullptr); CHECK0(err, "Demuxer::Demuxer() - error while retreiving media information"); // Get the media duration if possible (otherwise rely on the streams) if (m_formatCtx->duration != AV_NOPTS_VALUE) { int64_t secs, us; secs = m_formatCtx->duration / AV_TIME_BASE; us = m_formatCtx->duration % AV_TIME_BASE; m_duration = sf::seconds(secs + (float)us / AV_TIME_BASE); } // Find all interesting streams for (unsigned int i = 0; i < m_formatCtx->nb_streams; i++) { AVStream* & ffstream = m_formatCtx->streams[i]; try { std::shared_ptr<Stream> stream; switch (ffstream->codec->codec_type) { case AVMEDIA_TYPE_VIDEO: stream = std::make_shared<VideoStream>(m_formatCtx, ffstream, *this, timer, videoDelegate); if (m_duration == sf::Time::Zero) { extractDurationFromStream(ffstream); } sfeLogDebug("Loaded " + avcodec_get_name(ffstream->codec->codec_id) + " video stream"); break; case AVMEDIA_TYPE_AUDIO: stream = std::make_shared<AudioStream>(m_formatCtx, ffstream, *this, timer); if (m_duration == sf::Time::Zero) { extractDurationFromStream(ffstream); } sfeLogDebug("Loaded " + avcodec_get_name(ffstream->codec->codec_id) + " audio stream"); break; case AVMEDIA_TYPE_SUBTITLE: stream = std::make_shared<SubtitleStream>(m_formatCtx, ffstream, *this, timer, subtitleDelegate); sfeLogDebug("Loaded " + avcodec_get_name(ffstream->codec->codec_id) + " subtitle stream"); break; default: m_ignoredStreams[ffstream->index] = Stream::AVStreamDescription(ffstream); sfeLogDebug(m_ignoredStreams[ffstream->index] + " ignored"); break; } // Don't create an entry in the map unless everything went well and stream did not get ignored if (stream) m_streams[ffstream->index] = stream; } catch (std::runtime_error& e) { std::string streamDesc = Stream::AVStreamDescription(ffstream); sfeLogError("error while loading " + streamDesc + ": " + e.what()); CHECK(m_streams.find(ffstream->index) == m_streams.end(), "Internal inconcistency error: stream whose loading failed should not be stored"); } } if (m_duration == sf::Time::Zero) { sfeLogWarning("The media duration could not be retreived"); } m_timer->addObserver(*this, DemuxerTimerPriority); }