/** * @file * @brief Source file for PlayerPrivate class * @author Duzy Chan * @author Jonathan Thomas * * @ref License */ /* LICENSE * * Copyright (c) 2008-2019 OpenShot Studios, LLC * . This file is part of * OpenShot Library (libopenshot), an open-source project dedicated to * delivering high quality video editing and animation solutions to the * world. For more information visit . * * OpenShot Library (libopenshot) is free software: you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * OpenShot Library (libopenshot) is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with OpenShot Library. If not, see . */ #include "../../include/Qt/PlayerPrivate.h" #include // for std::this_thread::sleep_for #include // for std::chrono milliseconds, high_resolution_clock namespace openshot { // Constructor PlayerPrivate::PlayerPrivate(openshot::RendererBase *rb) : renderer(rb), Thread("player"), video_position(1), audio_position(0) , audioPlayback(new openshot::AudioPlaybackThread()) , videoPlayback(new openshot::VideoPlaybackThread(rb)) , videoCache(new openshot::VideoCacheThread()) , speed(1), reader(NULL), last_video_position(1) { } // Destructor PlayerPrivate::~PlayerPrivate() { stopPlayback(1000); delete audioPlayback; delete videoCache; delete videoPlayback; } // Start thread void PlayerPrivate::run() { // bail if no reader set if (!reader) return; // Start the threads if (reader->info.has_audio) audioPlayback->startThread(8); if (reader->info.has_video) { videoCache->startThread(2); videoPlayback->startThread(4); } using std::chrono::duration_cast; // Types for storing time durations in whole and fractional milliseconds using ms = std::chrono::milliseconds; using double_ms = std::chrono::duration; // Calculate on-screen time for a single frame in milliseconds const auto frame_duration = double_ms(1000.0 / reader->info.fps.ToDouble()); while (!threadShouldExit()) { // Get the start time (to track how long a frame takes to render) const auto time1 = std::chrono::high_resolution_clock::now(); // Get the current video frame (if it's different) frame = getFrame(); // Experimental Pausing Code (if frame has not changed) if ((speed == 0 && video_position == last_video_position) || (video_position > reader->info.video_length) ) { speed = 0; std::this_thread::sleep_for(frame_duration); continue; } // Set the video frame on the video thread and render frame videoPlayback->frame = frame; videoPlayback->render.signal(); // Keep track of the last displayed frame last_video_position = video_position; // How many frames ahead or behind is the video thread? int64_t video_frame_diff = 0; if (reader->info.has_audio && reader->info.has_video) { if (speed != 1) // Set audio frame again (since we are not in normal speed, and not paused) audioPlayback->Seek(video_position); // Only calculate this if a reader contains both an audio and video thread audio_position = audioPlayback->getCurrentFramePosition(); video_frame_diff = video_position - audio_position; } // Get the end time (to track how long a frame takes to render) const auto time2 = std::chrono::high_resolution_clock::now(); // Determine how many milliseconds it took to render the frame const auto render_time = double_ms(time2 - time1); // Calculate the amount of time to sleep (by subtracting the render time) auto sleep_time = duration_cast(frame_duration - render_time); // Debug ZmqLogger::Instance()->AppendDebugMethod("PlayerPrivate::run (determine sleep)", "video_frame_diff", video_frame_diff, "video_position", video_position, "audio_position", audio_position, "speed", speed, "render_time(ms)", render_time.count(), "sleep_time(ms)", sleep_time.count()); // Adjust drift (if more than a few frames off between audio and video) if (video_frame_diff > 0 && reader->info.has_audio && reader->info.has_video) { // Since the audio and video threads are running independently, // they will quickly get out of sync. To fix this, we calculate // how far ahead or behind the video frame is, and adjust the amount // of time the frame is displayed on the screen (i.e. the sleep time). // If a frame is ahead of the audio, we sleep for longer. // If a frame is behind the audio, we sleep less (or not at all), // in order for the video to catch up. sleep_time += duration_cast(video_frame_diff * frame_duration); } else if (video_frame_diff < -10 && reader->info.has_audio && reader->info.has_video) { // Skip frame(s) to catch up to the audio (if more than 10 frames behind) video_position += std::fabs(video_frame_diff) / 2; // Seek forward 1/2 the difference sleep_time = sleep_time.zero(); // Don't sleep now... immediately go to next position } // Sleep (leaving the video frame on the screen for the correct amount of time) if (sleep_time > sleep_time.zero()) { std::this_thread::sleep_for(sleep_time); } } } // Get the next displayed frame (based on speed and direction) std::shared_ptr PlayerPrivate::getFrame() { try { // Get the next frame (based on speed) if (video_position + speed >= 1 && video_position + speed <= reader->info.video_length) video_position = video_position + speed; if (frame && frame->number == video_position && video_position == last_video_position) { // return cached frame return frame; } else { // Update cache on which frame was retrieved videoCache->setCurrentFramePosition(video_position); // return frame from reader return reader->GetFrame(video_position); } } catch (const ReaderClosed & e) { // ... } catch (const TooManySeeks & e) { // ... } catch (const OutOfBoundsFrame & e) { // ... } return std::shared_ptr(); } // Start video/audio playback bool PlayerPrivate::startPlayback() { if (video_position < 0) return false; stopPlayback(-1); startThread(1); return true; } // Stop video/audio playback void PlayerPrivate::stopPlayback(int timeOutMilliseconds) { if (isThreadRunning()) stopThread(timeOutMilliseconds); if (audioPlayback->isThreadRunning() && reader->info.has_audio) audioPlayback->stopThread(timeOutMilliseconds); if (videoCache->isThreadRunning() && reader->info.has_video) videoCache->stopThread(timeOutMilliseconds); if (videoPlayback->isThreadRunning() && reader->info.has_video) videoPlayback->stopThread(timeOutMilliseconds); } }