godot/drivers/theoraplayer/video_stream_theoraplayer.cpp

557 lines
13 KiB
C++

/*************************************************************************/
/* video_stream.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* http://www.godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2015 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "video_stream_theoraplayer.h"
#include "core/os/file_access.h"
#include "include/theoraplayer/TheoraPlayer.h"
#include "include/theoraplayer/TheoraTimer.h"
#include "include/theoraplayer/TheoraAudioInterface.h"
#include "include/theoraplayer/TheoraDataSource.h"
#include "include/theoraplayer/TheoraException.h"
#include "core/ring_buffer.h"
#include "core/os/thread_safe.h"
#include "core/globals.h"
static TheoraVideoManager* mgr = NULL;
class TPDataFA : public TheoraDataSource {
FileAccess* fa;
String data_name;
public:
int read(void* output,int nBytes) {
if (!fa)
return -1;
return fa->get_buffer((uint8_t*)output, nBytes);
};
//! returns a string representation of the DataSource, eg 'File: source.ogg'
virtual std::string repr() {
return data_name.utf8().get_data();
};
//! position the source pointer to byte_index from the start of the source
virtual void seek(unsigned long byte_index) {
if (!fa)
return;
fa->seek(byte_index);
};
//! return the size of the stream in bytes
virtual unsigned long size() {
if (!fa)
return 0;
return fa->get_len();
};
//! return the current position of the source pointer
virtual unsigned long tell() {
if (!fa)
return 0;
return fa->get_pos();
};
TPDataFA(const String& p_path) {
fa = FileAccess::open(p_path, FileAccess::READ);
data_name = "File: " + p_path;
};
TPDataFA(FileAccess* p_fa, const String& p_path) {
fa = p_fa;
data_name = "File: " + p_path;
};
~TPDataFA() {
if (fa)
memdelete(fa);
};
};
class AudioStreamInput : public AudioStreamResampled {
_THREAD_SAFE_CLASS_;
int channels;
int freq;
RID stream_rid;
mutable RingBuffer<float> rb;
int rb_power;
int total_wrote;
bool playing;
bool paused;
public:
virtual void play() {
_THREAD_SAFE_METHOD_
_setup(channels, freq, 256);
stream_rid=AudioServer::get_singleton()->audio_stream_create(get_audio_stream());
AudioServer::get_singleton()->stream_set_active(stream_rid,true);
AudioServer::get_singleton()->stream_set_volume_scale(stream_rid,1);
playing = true;
paused = false;
};
virtual void stop() {
_THREAD_SAFE_METHOD_
AudioServer::get_singleton()->stream_set_active(stream_rid,false);
//_clear_stream();
playing=false;
_clear();
};
virtual bool is_playing() const { return true; };
virtual void set_paused(bool p_paused) { paused = p_paused; };
virtual bool is_paused(bool p_paused) const { return paused; };
virtual void set_loop(bool p_enable) {};
virtual bool has_loop() const { return false; };
virtual float get_length() const { return 0; };
virtual String get_stream_name() const { return "Theora Audio Stream"; };
virtual int get_loop_count() const { return 1; };
virtual float get_pos() const { return 0; };
virtual void seek_pos(float p_time) {};
virtual UpdateMode get_update_mode() const { return UPDATE_THREAD; };
virtual bool _can_mix() const { return true; };
void input(float* p_data, int p_samples) {
_THREAD_SAFE_METHOD_;
//printf("input %i samples from %p\n", p_samples, p_data);
if (rb.space_left() < p_samples) {
rb_power += 1;
rb.resize(rb_power);
}
rb.write(p_data, p_samples);
update(); //update too here for less latency
};
void update() {
_THREAD_SAFE_METHOD_;
int todo = get_todo();
int16_t* buffer = get_write_buffer();
int frames = rb.data_left()/channels;
const int to_write = MIN(todo, frames);
for (int i=0; i<to_write*channels; i++) {
int v = rb.read() * 32767;
int16_t sample = CLAMP(v,-32768,32767);
buffer[i] = sample;
};
write(to_write);
total_wrote += to_write;
};
int get_pending() const {
return rb.data_left();
};
int get_total_wrote() {
return total_wrote - (get_total() - get_todo());
};
AudioStreamInput(int p_channels, int p_freq) {
playing = false;
paused = true;
channels = p_channels;
freq = p_freq;
total_wrote = 0;
rb_power = 22;
rb.resize(rb_power);
};
~AudioStreamInput() {
stop();
};
};
class TPAudioGodot : public TheoraAudioInterface, TheoraTimer {
Ref<AudioStreamInput> stream;
int sample_count;
int channels;
int freq;
public:
void insertData(float* data, int nSamples) {
stream->input(data, nSamples);
};
TPAudioGodot(TheoraVideoClip* owner, int nChannels, int p_freq)
: TheoraAudioInterface(owner, nChannels, p_freq), TheoraTimer() {
printf("***************** audio interface constructor freq %i\n", p_freq);
channels = nChannels;
freq = p_freq;
stream = Ref<AudioStreamInput>(memnew(AudioStreamInput(nChannels, p_freq)));
stream->play();
sample_count = 0;
owner->setTimer(this);
};
void stop() {
stream->stop();
};
void update(float time_increase)
{
float prev_time = mTime;
//mTime = (float)(stream->get_total_wrote()) / freq;
//mTime = MAX(0,mTime-AudioServer::get_singleton()->get_output_delay());
//mTime = (float)sample_count / channels / freq;
mTime += time_increase;
if (mTime - prev_time > .02) printf("time increase %f secs\n", mTime - prev_time);
//float duration=mClip->getDuration();
//if (mTime > duration) mTime=duration;
//printf("time at timer is %f, %f, samples %i\n", mTime, time_increase, sample_count);
}
};
class TPAudioGodotFactory : public TheoraAudioInterfaceFactory {
public:
TheoraAudioInterface* createInstance(TheoraVideoClip* owner, int nChannels, int freq) {
printf("************** creating audio output\n");
TheoraAudioInterface* ta = new TPAudioGodot(owner, nChannels, freq);
return ta;
};
};
static TPAudioGodotFactory* audio_factory = NULL;
void VideoStreamTheoraplayer::stop() {
playing = false;
if (clip) {
clip->stop();
clip->seek(0);
};
started = true;
};
void VideoStreamTheoraplayer::play() {
if (clip)
playing = true;
};
bool VideoStreamTheoraplayer::is_playing() const {
return playing;
};
void VideoStreamTheoraplayer::set_paused(bool p_paused) {
paused = p_paused;
if (paused) {
clip->pause();
} else {
if (clip && playing && !started)
clip->play();
}
};
bool VideoStreamTheoraplayer::is_paused(bool p_paused) const {
return !playing;
};
void VideoStreamTheoraplayer::set_loop(bool p_enable) {
loop = p_enable;
};
bool VideoStreamTheoraplayer::has_loop() const {
return loop;
};
float VideoStreamTheoraplayer::get_length() const {
if (!clip)
return 0;
return clip->getDuration();
};
float VideoStreamTheoraplayer::get_pos() const {
if (!clip)
return 0;
return clip->getTimer()->getTime();
};
void VideoStreamTheoraplayer::seek_pos(float p_time) {
if (!clip)
return;
clip->seek(p_time);
};
int VideoStreamTheoraplayer::get_pending_frame_count() const {
if (!clip)
return 0;
TheoraVideoFrame* f = clip->getNextFrame();
return f ? 1 : 0;
};
void VideoStreamTheoraplayer::pop_frame(Ref<ImageTexture> p_tex) {
if (!clip)
return;
TheoraVideoFrame* f = clip->getNextFrame();
if (!f) {
return;
};
#ifdef GLES2_ENABLED
// RasterizerGLES2* r = RasterizerGLES2::get_singleton();
// r->_texture_set_data(p_tex, f->mBpp == 3 ? Image::Format_RGB : Image::Format_RGBA, f->mBpp, w, h, f->getBuffer());
#endif
float w=clip->getWidth(),h=clip->getHeight();
int imgsize = w * h * f->mBpp;
int size = f->getStride() * f->getHeight() * f->mBpp;
data.resize(imgsize);
{
DVector<uint8_t>::Write wr = data.write();
uint8_t* ptr = wr.ptr();
copymem(ptr, f->getBuffer(), imgsize);
}
/*
for (int i=0; i<h; i++) {
int dstofs = i * w * f->mBpp;
int srcofs = i * f->getStride() * f->mBpp;
copymem(ptr + dstofs, f->getBuffer() + dstofs, w * f->mBpp);
};
*/
Image frame = Image();
frame.create(w, h, 0, f->mBpp == 3 ? Image::FORMAT_RGB : Image::FORMAT_RGBA, data);
clip->popFrame();
if (p_tex->get_width() == 0) {
p_tex->create(frame.get_width(),frame.get_height(),frame.get_format(),Texture::FLAG_VIDEO_SURFACE|Texture::FLAG_FILTER);
p_tex->set_data(frame);
} else {
p_tex->set_data(frame);
};
};
/*
Image VideoStreamTheoraplayer::pop_frame() {
Image ret = frame;
frame = Image();
return ret;
};
*/
Image VideoStreamTheoraplayer::peek_frame() const {
return Image();
};
void VideoStreamTheoraplayer::update(float p_time) {
if (!mgr)
return;
if (!clip)
return;
if (!playing || paused)
return;
//printf("video update!\n");
if (started) {
if (clip->getNumReadyFrames() < 2) {
printf("frames not ready, returning!\n");
return;
};
started = false;
//printf("playing clip!\n");
clip->play();
} else if (clip->isDone()) {
playing = false;
};
mgr->update(p_time);
};
void VideoStreamTheoraplayer::set_audio_track(int p_idx) {
audio_track=p_idx;
if (clip)
clip->set_audio_track(audio_track);
}
void VideoStreamTheoraplayer::set_file(const String& p_file) {
FileAccess* f = FileAccess::open(p_file, FileAccess::READ);
if (!f || !f->is_open())
return;
if (!audio_factory) {
audio_factory = memnew(TPAudioGodotFactory);
};
if (mgr == NULL) {
mgr = memnew(TheoraVideoManager);
mgr->setAudioInterfaceFactory(audio_factory);
};
int track = GLOBAL_DEF("theora/audio_track", 0); // hack
if (p_file.find(".mp4") != -1) {
std::string file = p_file.replace("res://", "").utf8().get_data();
clip = mgr->createVideoClip(file, TH_RGBX, 2, false, track);
//clip->set_audio_track(audio_track);
memdelete(f);
} else {
TheoraDataSource* ds = memnew(TPDataFA(f, p_file));
try {
clip = mgr->createVideoClip(ds);
clip->set_audio_track(audio_track);
} catch (_TheoraGenericException e) {
printf("exception ocurred! %s\n", e.repr().c_str());
clip = NULL;
};
};
clip->pause();
started = true;
};
VideoStreamTheoraplayer::~VideoStreamTheoraplayer() {
stop();
//if (mgr) { // this should be a singleton or static or something
// memdelete(mgr);
//};
//mgr = NULL;
if (clip) {
mgr->destroyVideoClip(clip);
clip = NULL;
};
};
VideoStreamTheoraplayer::VideoStreamTheoraplayer() {
//mgr = NULL;
clip = NULL;
started = false;
playing = false;
paused = false;
loop = false;
audio_track=0;
};
RES ResourceFormatLoaderVideoStreamTheoraplayer::load(const String &p_path, const String& p_original_path, Error *r_error) {
if (r_error)
*r_error=OK;
VideoStreamTheoraplayer *stream = memnew(VideoStreamTheoraplayer);
stream->set_file(p_path);
return Ref<VideoStreamTheoraplayer>(stream);
}
void ResourceFormatLoaderVideoStreamTheoraplayer::get_recognized_extensions(List<String> *p_extensions) const {
p_extensions->push_back("ogm");
p_extensions->push_back("ogv");
p_extensions->push_back("mp4");
}
bool ResourceFormatLoaderVideoStreamTheoraplayer::handles_type(const String& p_type) const {
return p_type=="VideoStream" || p_type == "VideoStreamTheoraplayer";
}
String ResourceFormatLoaderVideoStreamTheoraplayer::get_resource_type(const String &p_path) const {
String exl=p_path.extension().to_lower();
if (exl=="ogm" || exl=="ogv" || exl=="mp4")
return "VideoStream";
return "";
}