Added pausing, rewind, fast forward, and playing (and different speeds). Still a bit buggy and experimental with fast forward and rewinding (due to seek bugs in readers).

This commit is contained in:
Jonathan Thomas
2014-03-23 01:12:29 -05:00
parent 2ba83486c9
commit a77de842e4
13 changed files with 346 additions and 54 deletions

View File

@@ -58,6 +58,7 @@ namespace openshot
bool repeat; /// Repeat the audio source when finished
int size; /// The size of the internal buffer
AudioSampleBuffer *buffer; /// The audio sample buffer
int speed; /// The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...)
ReaderBase *reader; /// The reader to pull samples from
int64 original_frame_number; /// The current frame to read from
@@ -70,6 +71,9 @@ namespace openshot
/// Get more samples from the reader
void GetMoreSamplesFromReader();
/// Reverse an audio buffer (for backwards audio)
juce::AudioSampleBuffer* reverse_buffer(juce::AudioSampleBuffer* buffer);
public:
/// @brief Constructor that reads samples from a reader
@@ -111,9 +115,26 @@ namespace openshot
void setBuffer (AudioSampleBuffer *audio_buffer);
const ReaderInfo & getReaderInfo() const { return reader->info; }
/// Return the current frame object
tr1::shared_ptr<Frame> getFrame() const { return frame; }
int getFramePosition() const { return frame_position; }
/// Get the estimate frame that is playing at this moment
int getEstimatedFrame() const { return int(estimated_frame); }
/// Set Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...)
void setSpeed(int new_speed) { speed = new_speed; }
/// Get Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...)
int getSpeed() const { return speed; }
/// Set Reader
void Reader(ReaderBase *audio_reader) { reader = audio_reader; }
/// Get Reader
ReaderBase* Reader() const { return reader; }
/// Seek to a specific frame
int Seek(int64 new_position) { frame_number = new_position; }
};
}

View File

@@ -68,6 +68,9 @@ namespace openshot
/// Display a loading animation
virtual void Loading() = 0;
/// Get the current mode
virtual PlaybackMode Mode() = 0;
/// Play the video
virtual void Play() = 0;
@@ -81,25 +84,25 @@ namespace openshot
virtual void Seek(int new_frame) = 0;
/// Get the Playback speed
float Speed();
virtual float Speed() = 0;
/// Set the Playback speed (1.0 = normal speed, <1.0 = slower, >1.0 faster)
void Speed(float new_speed);
virtual void Speed(float new_speed) = 0;
/// Stop the video player and clear the cached frames
virtual void Stop() = 0;
/// Get the current reader, such as a FFmpegReader
ReaderBase* Reader();
virtual ReaderBase* Reader() = 0;
/// Set the current reader, such as a FFmpegReader
void Reader(ReaderBase *new_reader);
virtual void Reader(ReaderBase *new_reader) = 0;
/// Get the Volume
float Volume();
virtual float Volume() = 0;
/// Set the Volume (1.0 = normal volume, <1.0 = quieter, >1.0 louder)
void Volume(float new_volume);
virtual void Volume(float new_volume) = 0;
};

View File

@@ -9,6 +9,7 @@
#include <QtWidgets/QBoxLayout>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QMenu>
#include <QtGui/qevent.h>
namespace openshot
{
@@ -26,6 +27,9 @@ public:
PlayerDemo(QWidget *parent = 0);
~PlayerDemo();
protected:
void keyPressEvent(QKeyEvent *event);
private slots:
void open(bool checked);

View File

@@ -47,6 +47,7 @@ namespace openshot
class QtPlayer : public PlayerBase
{
PlayerPrivate *p;
bool threads_started;
public:
/// Default constructor
@@ -64,6 +65,9 @@ namespace openshot
/// Display a loading animation
void Loading();
/// Get the current mode
PlaybackMode Mode();
/// Pause the video
void Pause();
@@ -73,8 +77,26 @@ namespace openshot
/// Seek to a specific frame in the player
void Seek(int new_frame);
/// Get the Playback speed
float Speed();
/// Set the Playback speed (1.0 = normal speed, <1.0 = slower, >1.0 faster)
void Speed(float new_speed);
/// Stop the video player and clear the cached frames
void Stop();
/// Set the current reader
void Reader(ReaderBase *new_reader);
/// Get the current reader, such as a FFmpegReader
ReaderBase* Reader();
/// Get the Volume
float Volume();
/// Set the Volume (1.0 = normal volume, <1.0 = quieter, >1.0 louder)
void Volume(float new_volume);
};
}

View File

@@ -33,7 +33,7 @@ using namespace openshot;
// Constructor that reads samples from a reader
AudioReaderSource::AudioReaderSource(ReaderBase *audio_reader, int64 starting_frame_number, int buffer_size)
: reader(audio_reader), frame_number(starting_frame_number), original_frame_number(starting_frame_number),
size(buffer_size), position(0), frame_position(0), estimated_frame(0) {
size(buffer_size), position(0), frame_position(0), estimated_frame(0), speed(1) {
// Initialize an audio buffer (based on reader)
buffer = new juce::AudioSampleBuffer(reader->info.channels, size);
@@ -79,13 +79,14 @@ void AudioReaderSource::GetMoreSamplesFromReader() {
position = 0;
// Loop through frames until buffer filled
while (amount_needed > 0) {
while (amount_needed > 0 && speed != 0) {
// Get the next frame (if position is zero)
if (frame_position == 0) {
try {
// Get frame object
frame = reader->GetFrameSafe(frame_number++);
frame = reader->GetFrameSafe(frame_number);
frame_number = frame_number + speed;
} catch (const ReaderClosed & e) {
break;
@@ -110,7 +111,12 @@ void AudioReaderSource::GetMoreSamplesFromReader() {
// Load all of its samples into the buffer
for (int channel = 0; channel < new_buffer->getNumChannels(); channel++)
new_buffer->addFrom(channel, position, *frame->GetAudioSampleBuffer(), channel, frame_position, amount_to_copy);
if (speed >= 0)
// playback normal
new_buffer->addFrom(channel, position, *frame->GetAudioSampleBuffer(), channel, frame_position, amount_to_copy);
else
// reverse playback
new_buffer->addFrom(channel, position, *reverse_buffer(frame->GetAudioSampleBuffer()), channel, frame_position, amount_to_copy);
// Adjust remaining samples
position += amount_to_copy;
@@ -131,6 +137,37 @@ void AudioReaderSource::GetMoreSamplesFromReader() {
position = 0;
}
// Reverse an audio buffer
juce::AudioSampleBuffer* AudioReaderSource::reverse_buffer(juce::AudioSampleBuffer* buffer)
{
int number_of_samples = buffer->getNumSamples();
int channels = buffer->getNumChannels();
// Reverse array (create new buffer to hold the reversed version)
AudioSampleBuffer *reversed = new juce::AudioSampleBuffer(channels, number_of_samples);
reversed->clear();
for (int channel = 0; channel < channels; channel++)
{
int n=0;
for (int s = number_of_samples - 1; s >= 0; s--, n++)
reversed->getSampleData(channel)[n] = buffer->getSampleData(channel)[s];
}
// Copy the samples back to the original array
buffer->clear();
// Loop through channels, and get audio samples
for (int channel = 0; channel < channels; channel++)
// Get the audio samples for this channel
buffer->addFrom(channel, 0, reversed->getSampleData(channel), number_of_samples, 1.0f);
delete reversed;
reversed = NULL;
// return pointer or passed in object (so this method can be chained together)
return buffer;
}
// Get the next block of audio samples
void AudioReaderSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
{
@@ -182,7 +219,8 @@ void AudioReaderSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
// Adjust estimate frame number (the estimated frame number that is being played)
estimated_samples_per_frame = Frame::GetSamplesPerFrame(estimated_frame, reader->info.fps, reader->info.sample_rate);
estimated_frame += double(info.numSamples) / double(estimated_samples_per_frame);
if (speed != 0)
estimated_frame += double(info.numSamples) / double(estimated_samples_per_frame);
}
}

View File

@@ -2,6 +2,7 @@
* @file
* @brief Source file for AudioPlaybackThread class
* @author Duzy Chan <code@duzy.info>
* @author Jonathan Thomas <jonathan@openshot.org> *
*
* @section LICENSE
*
@@ -44,6 +45,7 @@ namespace openshot
}
};
// Construtor
AudioPlaybackThread::AudioPlaybackThread()
: Thread("audio-playback")
, audioDeviceManager()
@@ -57,28 +59,41 @@ namespace openshot
{
}
// Destructor
AudioPlaybackThread::~AudioPlaybackThread()
{
}
void AudioPlaybackThread::setReader(ReaderBase *reader)
// Set the reader object
void AudioPlaybackThread::Reader(ReaderBase *reader)
{
sampleRate = reader->info.sample_rate;
numChannels = reader->info.channels;
source = new AudioReaderSource(reader, 1, buffer_size);
if (!source) {
sampleRate = reader->info.sample_rate;
numChannels = reader->info.channels;
source = new AudioReaderSource(reader, 1, buffer_size);
}
}
// Get the current frame object (which is filling the buffer)
tr1::shared_ptr<Frame> AudioPlaybackThread::getFrame()
{
if (source) return source->getFrame();
return tr1::shared_ptr<Frame>();
}
// Get the currently playing frame number
int AudioPlaybackThread::getCurrentFramePosition()
{
return source ? source->getEstimatedFrame() : 0;
}
// Seek the audio thread
void AudioPlaybackThread::Seek(int new_position)
{
source->Seek(new_position);
}
// Start audio thread
void AudioPlaybackThread::run()
{
// Init audio device
@@ -113,7 +128,7 @@ namespace openshot
transport.start();
while (!threadShouldExit() && transport.isPlaying()) {
sleep(1);
sleep(100);
}
transport.stop();
@@ -124,5 +139,9 @@ namespace openshot
audioDeviceManager.closeAudioDevice();
audioDeviceManager.removeAllChangeListeners();
audioDeviceManager.dispatchPendingMessages();
// Remove source
delete source;
source = NULL;
}
}

View File

@@ -2,6 +2,7 @@
* @file
* @brief Source file for AudioPlaybackThread class
* @author Duzy Chan <code@duzy.info>
* @author Jonathan Thomas <jonathan@openshot.org>
*
* @section LICENSE
*
@@ -31,7 +32,7 @@ namespace openshot
using juce::WaitableEvent;
/**
* @brief The audio playback class.
* @brief The audio playback thread
*/
class AudioPlaybackThread : Thread
{
@@ -47,15 +48,32 @@ namespace openshot
WaitableEvent played;
int buffer_size;
/// Constructor
AudioPlaybackThread();
/// Destructor
~AudioPlaybackThread();
void setReader(ReaderBase *reader);
/// Set the current thread's reader
void Reader(ReaderBase *reader);
/// Get the current frame object (which is filling the buffer)
tr1::shared_ptr<Frame> getFrame();
/// Get the current frame number being played
int getCurrentFramePosition();
/// Seek the audio thread
void Seek(int new_position);
/// Start thread
void run();
/// Set Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...)
void setSpeed(int new_speed) { if (source) source->setSpeed(new_speed); }
/// Get Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...)
int getSpeed() const { if (source) return source->getSpeed(); else return 1; }
friend class PlayerPrivate;
};

View File

@@ -2,6 +2,8 @@
* @file player.cpp
*/
#include "stdio.h"
#include "string.h"
#include "../../../include/QtPlayer.h"
#include "../../../include/Qt/PlayerDemo.h"
#include "../../../include/Qt/VideoRenderWidget.h"
@@ -30,6 +32,10 @@ PlayerDemo::PlayerDemo(QWidget *parent)
vbox->setMargin(0);
vbox->setSpacing(0);
resize(600, 480);
// Accept keyboard event
setFocusPolicy(Qt::StrongFocus);
}
PlayerDemo::~PlayerDemo()
@@ -37,6 +43,42 @@ PlayerDemo::~PlayerDemo()
delete player;
}
void PlayerDemo::keyPressEvent(QKeyEvent *event)
{
string key = event->text().toStdString();
if (key == " ") {
cout << "START / STOP: " << player->Mode() << endl;
if (player->Mode() == openshot::PLAYBACK_PLAY)
player->Pause();
else if (player->Mode() == openshot::PLAYBACK_PAUSED)
player->Play();
}
else if (key == "j") {
cout << "BACKWARD" << player->Speed() - 1 << endl;
int current_speed = player->Speed();
player->Speed(current_speed - 1); // backwards
}
else if (key == "k") {
cout << "PAUSE" << endl;
if (player->Mode() == openshot::PLAYBACK_PLAY)
player->Pause();
else if (player->Mode() == openshot::PLAYBACK_PAUSED)
player->Play();
}
else if (key == "l") {
cout << "FORWARD" << player->Speed() + 1 << endl;
int current_speed = player->Speed();
player->Speed(current_speed + 1); // backwards
}
event->accept();
QWidget::keyPressEvent(event);
}
void PlayerDemo::open(bool checked)
{
const QString filename = QFileDialog::getOpenFileName(this, "Open Video File");

View File

@@ -25,22 +25,20 @@
* You should have received a copy of the GNU General Public License
* along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../include/ReaderBase.h"
#include "../include/RendererBase.h"
#include "../include/AudioReaderSource.h"
#include "PlayerPrivate.h"
#include "AudioPlaybackThread.h"
#include "VideoPlaybackThread.h"
namespace openshot
{
// Constructor
PlayerPrivate::PlayerPrivate(RendererBase *rb)
: Thread("player"), video_position(0), audio_position(0)
, audioPlayback(new AudioPlaybackThread())
, videoPlayback(new VideoPlaybackThread(rb))
{
}
, speed(1), reader(NULL)
{ }
// Destructor
PlayerPrivate::~PlayerPrivate()
{
if (isThreadRunning()) stopThread(500);
@@ -50,13 +48,15 @@ namespace openshot
delete videoPlayback;
}
// Start thread
void PlayerPrivate::run()
{
// Kill audio and video threads (if they are currently running)
if (audioPlayback->isThreadRunning() && reader->info.has_audio) audioPlayback->stopThread(-1);
if (videoPlayback->isThreadRunning() && reader->info.has_video) videoPlayback->stopThread(-1);
// Set the reader for the Audio thread
audioPlayback->setReader(reader);
audioPlayback->Reader(reader);
// Start the threads
if (reader->info.has_audio)
@@ -67,6 +67,15 @@ namespace openshot
tr1::shared_ptr<Frame> frame;
while (!threadShouldExit()) {
// Calculate the milliseconds a single frame should stay on the screen
double frame_time = (1000.0 / reader->info.fps.ToDouble());
// Experimental Pausing Code
if (speed == 0) {
sleep(frame_time);
continue;
}
// Get the start time (to track how long a frame takes to render)
const Time t1 = Time::getCurrentTime();
@@ -88,9 +97,6 @@ namespace openshot
// Get the end time (to track how long a frame takes to render)
const Time t2 = Time::getCurrentTime();
// Calculate the milliseconds a single frame should stay on the screen
double frame_time = (1000.0 / reader->info.fps.ToDouble());
// Determine how many milliseconds it took to render the frame
int64 render_time = t2.toMilliseconds() - t1.toMilliseconds();
@@ -112,25 +118,24 @@ namespace openshot
// Debug output
std::cout << "video frame diff: " << video_frame_diff << std::endl;
// Determine if the next frame will be the end of stream
//if ((video_position + 1) > reader->info.video_length)
//{
// End threads at END OF STREAM
//if (reader->info.has_audio)
// audioPlayback->stopThread(1);
//if (reader->info.has_video)
// videoPlayback->stopThread(2);
//}
}
std::cout << "stopped thread" << endl;
// Kill audio and video threads (if they are still running)
if (audioPlayback->isThreadRunning() && reader->info.has_audio) audioPlayback->stopThread(-1);
if (videoPlayback->isThreadRunning() && reader->info.has_video) videoPlayback->stopThread(-1);
}
// Get the next displayed frame (based on speed and direction)
tr1::shared_ptr<Frame> PlayerPrivate::getFrame()
{
try {
return reader->GetFrameSafe(video_position++);
// Get the next frame (based on speed)
video_position = video_position + speed;
return reader->GetFrameSafe(video_position);
} catch (const ReaderClosed & e) {
// ...
} catch (const TooManySeeks & e) {
@@ -141,6 +146,7 @@ namespace openshot
return tr1::shared_ptr<Frame>();
}
// Start video/audio playback
bool PlayerPrivate::startPlayback()
{
if (video_position < 0) return false;
@@ -149,9 +155,39 @@ namespace openshot
return true;
}
// Stop video/audio playback
void PlayerPrivate::stopPlayback(int timeOutMilliseconds)
{
std::cout << "stop playback!!!" << std::endl;
if (isThreadRunning()) stopThread(timeOutMilliseconds);
}
// Seek to a frame
void PlayerPrivate::Seek(int new_position)
{
// Check for seek
if (new_position > 0) {
// Update current position
video_position = new_position;
// Notify audio thread that seek has occured
audioPlayback->Seek(video_position);
}
}
// Set Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...)
void PlayerPrivate::Speed(int new_speed)
{
speed = new_speed;
if (reader->info.has_audio)
audioPlayback->setSpeed(new_speed);
}
// Set the reader object
void PlayerPrivate::Reader(ReaderBase *new_reader)
{
reader = new_reader;
audioPlayback->Reader(new_reader);
}
}

View File

@@ -26,6 +26,12 @@
* along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../include/ReaderBase.h"
#include "../include/RendererBase.h"
#include "../include/AudioReaderSource.h"
#include "AudioPlaybackThread.h"
#include "VideoPlaybackThread.h"
namespace openshot
{
class AudioPlaybackThread;
@@ -33,26 +39,47 @@ namespace openshot
using juce::Thread;
/**
* @brief The private part of QtPlayer class.
* @brief The private part of QtPlayer class, which contains an audio thread and video thread,
* and controls the video timing and audio synchronization code.
*/
class PlayerPrivate : Thread
{
int video_position; /// The current frame position.
int audio_position; /// The current frame position.
ReaderBase *reader;
AudioPlaybackThread *audioPlayback;
VideoPlaybackThread *videoPlayback;
ReaderBase *reader; /// The reader which powers this player
AudioPlaybackThread *audioPlayback; /// The audio thread
VideoPlaybackThread *videoPlayback; /// The video thread
int speed; /// The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...)
/// Constructor
PlayerPrivate(RendererBase *rb);
/// Destructor
virtual ~PlayerPrivate();
/// Start thread
void run();
/// Seek to a frame
void Seek(int new_position);
/// Start the video/audio playback
bool startPlayback();
/// Stop the video/audio playback
void stopPlayback(int timeOutMilliseconds = -1);
/// Get the next frame (based on speed and direction)
tr1::shared_ptr<Frame> getFrame();
/// Set Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...)
void Speed(int new_speed);
/// Get Speed (The speed and direction to playback a reader (1=normal, 2=fast, 3=faster, -1=rewind, etc...)
int Speed() const { return speed; }
/// Set the current reader
void Reader(ReaderBase *new_reader);
/// The parent class of PlayerPrivate
friend class QtPlayer;
};

View File

@@ -31,16 +31,19 @@
namespace openshot
{
// Constructor
VideoPlaybackThread::VideoPlaybackThread(RendererBase *rb)
: Thread("video-playback"), renderer(rb)
, render(), reset(false)
{
}
// Destructor
VideoPlaybackThread::~VideoPlaybackThread()
{
}
// Get the currently playing frame number (if any)
int VideoPlaybackThread::getCurrentFramePosition()
{
if (frame)
@@ -49,11 +52,15 @@ namespace openshot
return 0;
}
// Start the thread
void VideoPlaybackThread::run()
{
while (!threadShouldExit()) {
render.wait();
// Make other threads wait on the render event
render.wait();
// Render the frame to the screen
renderer->paint(frame);
// Signal to other threads that the rendered event has completed
rendered.signal();
}
}

View File

@@ -42,12 +42,18 @@ namespace openshot
WaitableEvent rendered;
bool reset;
/// Constructor
VideoPlaybackThread(RendererBase *rb);
/// Destructor
~VideoPlaybackThread();
/// Get the currently playing frame number (if any)
int getCurrentFramePosition();
/// Start the thread
void run();
/// Parent class of VideoPlaybackThread
friend class PlayerPrivate;
};

View File

@@ -32,15 +32,15 @@
using namespace openshot;
QtPlayer::QtPlayer(RendererBase *rb) : PlayerBase(), p(new PlayerPrivate(rb))
QtPlayer::QtPlayer(RendererBase *rb) : PlayerBase(), p(new PlayerPrivate(rb)), threads_started(false)
{
reader = NULL;
reader = NULL;
}
QtPlayer::~QtPlayer()
{
if (mode != PLAYBACK_STOPPED) {
//p->stop();
Stop();
}
delete p;
}
@@ -49,15 +49,21 @@ void QtPlayer::SetSource(const std::string &source)
{
reader = new FFmpegReader(source);
reader->Open();
p->Reader(reader);
}
void QtPlayer::Play()
{
mode = PLAYBACK_PLAY;
p->stopPlayback();
p->video_position = 0;
p->reader = reader;
p->startPlayback();
cout << "PLAY() on QTPlayer" << endl;
if (reader && !threads_started) {
mode = PLAYBACK_PLAY;
p->Reader(reader);
p->startPlayback();
threads_started = true;
} else {
mode = PLAYBACK_PLAY;
Speed(1);
}
}
void QtPlayer::Loading()
@@ -65,9 +71,16 @@ void QtPlayer::Loading()
mode = PLAYBACK_LOADING;
}
/// Get the current mode
PlaybackMode QtPlayer::Mode()
{
return mode;
}
void QtPlayer::Pause()
{
mode = PLAYBACK_PAUSED;
Speed(0);
}
int QtPlayer::Position()
@@ -78,10 +91,46 @@ int QtPlayer::Position()
void QtPlayer::Seek(int new_frame)
{
// Seek the reader to a new position
p->video_position = new_frame;
p->Seek(new_frame);
}
void QtPlayer::Stop()
{
mode = PLAYBACK_STOPPED;
p->stopPlayback();
p->video_position = 0;
threads_started = false;
}
// Set the reader object
void QtPlayer::Reader(ReaderBase *new_reader)
{
reader = new_reader;
p->Reader(new_reader);
}
// Get the current reader, such as a FFmpegReader
ReaderBase* QtPlayer::Reader() {
return reader;
}
// Get the Playback speed
float QtPlayer::Speed() {
return speed;
}
// Set the Playback speed multiplier (1.0 = normal speed, <1.0 = slower, >1.0 faster)
void QtPlayer::Speed(float new_speed) {
speed = new_speed;
p->Speed(new_speed);
}
// Get the Volume
float QtPlayer::Volume() {
return volume;
}
// Set the Volume multiplier (1.0 = normal volume, <1.0 = quieter, >1.0 louder)
void QtPlayer::Volume(float new_volume) {
volume = new_volume;
}