|
@@ -35,10 +35,13 @@
|
|
|
#include "mkvparser/mkvparser.h"
|
|
|
|
|
|
#include "os/file_access.h"
|
|
|
+#include "os/os.h"
|
|
|
#include "project_settings.h"
|
|
|
|
|
|
#include "thirdparty/misc/yuv2rgb.h"
|
|
|
|
|
|
+#include "servers/audio_server.h"
|
|
|
+
|
|
|
#include <string.h>
|
|
|
|
|
|
class MkvReader : public mkvparser::IMkvReader {
|
|
@@ -47,6 +50,8 @@ public:
|
|
|
MkvReader(const String &p_file) {
|
|
|
|
|
|
file = FileAccess::open(p_file, FileAccess::READ);
|
|
|
+
|
|
|
+ ERR_EXPLAIN("Failed loading resource: '" + p_file + "';");
|
|
|
ERR_FAIL_COND(!file);
|
|
|
}
|
|
|
~MkvReader() {
|
|
@@ -113,14 +118,14 @@ bool VideoStreamPlaybackWebm::open_file(const String &p_file) {
|
|
|
webm = memnew(WebMDemuxer(new MkvReader(file_name), 0, audio_track));
|
|
|
if (webm->isOpen()) {
|
|
|
|
|
|
- video = memnew(VPXDecoder(*webm, 8)); //TODO: Detect CPU threads
|
|
|
+ video = memnew(VPXDecoder(*webm, OS::get_singleton()->get_processor_count()));
|
|
|
if (video->isOpen()) {
|
|
|
|
|
|
audio = memnew(OpusVorbisDecoder(*webm));
|
|
|
if (audio->isOpen()) {
|
|
|
|
|
|
audio_frame = memnew(WebMFrame);
|
|
|
- pcm = (int16_t *)memalloc(sizeof(int16_t) * audio->getBufferSamples() * webm->getChannels());
|
|
|
+ pcm = (float *)memalloc(sizeof(float) * audio->getBufferSamples() * webm->getChannels());
|
|
|
} else {
|
|
|
|
|
|
memdelete(audio);
|
|
@@ -183,7 +188,7 @@ void VideoStreamPlaybackWebm::set_paused(bool p_paused) {
|
|
|
|
|
|
paused = p_paused;
|
|
|
}
|
|
|
-bool VideoStreamPlaybackWebm::is_paused(bool p_paused) const {
|
|
|
+bool VideoStreamPlaybackWebm::is_paused() const {
|
|
|
|
|
|
return paused;
|
|
|
}
|
|
@@ -222,11 +227,18 @@ Ref<Texture> VideoStreamPlaybackWebm::get_texture() {
|
|
|
|
|
|
return texture;
|
|
|
}
|
|
|
+
|
|
|
void VideoStreamPlaybackWebm::update(float p_delta) {
|
|
|
|
|
|
if ((!playing || paused) || !video)
|
|
|
return;
|
|
|
|
|
|
+ time += p_delta;
|
|
|
+
|
|
|
+ if (time < video_pos) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
bool audio_buffer_full = false;
|
|
|
|
|
|
if (samples_offset > -1) {
|
|
@@ -245,13 +257,15 @@ void VideoStreamPlaybackWebm::update(float p_delta) {
|
|
|
}
|
|
|
|
|
|
const bool hasAudio = (audio && mix_callback);
|
|
|
- while ((hasAudio && (!audio_buffer_full || !has_enough_video_frames())) || (!hasAudio && video_frames_pos == 0)) {
|
|
|
+ while ((hasAudio && !audio_buffer_full && !has_enough_video_frames()) ||
|
|
|
+ (!hasAudio && video_frames_pos == 0)) {
|
|
|
|
|
|
- if (hasAudio && !audio_buffer_full && audio_frame->isValid() && audio->getPCMS16(*audio_frame, pcm, num_decoded_samples) && num_decoded_samples > 0) {
|
|
|
+ if (hasAudio && !audio_buffer_full && audio_frame->isValid() &&
|
|
|
+ audio->getPCMF(*audio_frame, pcm, num_decoded_samples) && num_decoded_samples > 0) {
|
|
|
|
|
|
const int mixed = mix_callback(mix_udata, pcm, num_decoded_samples);
|
|
|
- if (mixed != num_decoded_samples) {
|
|
|
|
|
|
+ if (mixed != num_decoded_samples) {
|
|
|
samples_offset = mixed;
|
|
|
audio_buffer_full = true;
|
|
|
}
|
|
@@ -273,72 +287,61 @@ void VideoStreamPlaybackWebm::update(float p_delta) {
|
|
|
++video_frames_pos;
|
|
|
};
|
|
|
|
|
|
- const double video_delay = video->getFramesDelay() * video_frame_delay;
|
|
|
-
|
|
|
- bool want_this_frame = false;
|
|
|
- while (video_frames_pos > 0 && !want_this_frame) {
|
|
|
+ bool video_frame_done = false;
|
|
|
+ while (video_frames_pos > 0 && !video_frame_done) {
|
|
|
|
|
|
WebMFrame *video_frame = video_frames[0];
|
|
|
- if (video_frame->time <= time + video_delay) {
|
|
|
|
|
|
- if (video->decode(*video_frame)) {
|
|
|
+ // It seems VPXDecoder::decode has to be executed even though we might skip this frame
|
|
|
+ if (video->decode(*video_frame)) {
|
|
|
|
|
|
- VPXDecoder::IMAGE_ERROR err;
|
|
|
- VPXDecoder::Image image;
|
|
|
+ VPXDecoder::IMAGE_ERROR err;
|
|
|
+ VPXDecoder::Image image;
|
|
|
|
|
|
- while ((err = video->getImage(image)) != VPXDecoder::NO_FRAME) {
|
|
|
+ if (should_process(*video_frame)) {
|
|
|
|
|
|
- want_this_frame = (time - video_frame->time <= video_frame_delay);
|
|
|
+ if ((err = video->getImage(image)) != VPXDecoder::NO_FRAME) {
|
|
|
|
|
|
- if (want_this_frame) {
|
|
|
+ if (err == VPXDecoder::NO_ERROR && image.w == webm->getWidth() && image.h == webm->getHeight()) {
|
|
|
|
|
|
- if (err == VPXDecoder::NO_ERROR && image.w == webm->getWidth() && image.h == webm->getHeight()) {
|
|
|
+ PoolVector<uint8_t>::Write w = frame_data.write();
|
|
|
+ bool converted = false;
|
|
|
|
|
|
- PoolVector<uint8_t>::Write w = frame_data.write();
|
|
|
- bool converted = false;
|
|
|
+ if (image.chromaShiftW == 1 && image.chromaShiftH == 1) {
|
|
|
|
|
|
- if (image.chromaShiftW == 1 && image.chromaShiftH == 1) {
|
|
|
+ yuv420_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0);
|
|
|
+ // libyuv::I420ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
|
|
+ converted = true;
|
|
|
+ } else if (image.chromaShiftW == 1 && image.chromaShiftH == 0) {
|
|
|
|
|
|
- yuv420_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0);
|
|
|
- // libyuv::I420ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
|
|
- converted = true;
|
|
|
- } else if (image.chromaShiftW == 1 && image.chromaShiftH == 0) {
|
|
|
+ yuv422_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0);
|
|
|
+ // libyuv::I422ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
|
|
+ converted = true;
|
|
|
+ } else if (image.chromaShiftW == 0 && image.chromaShiftH == 0) {
|
|
|
|
|
|
- yuv422_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0);
|
|
|
- // libyuv::I422ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
|
|
- converted = true;
|
|
|
- } else if (image.chromaShiftW == 0 && image.chromaShiftH == 0) {
|
|
|
+ yuv444_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0);
|
|
|
+ // libyuv::I444ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
|
|
+ converted = true;
|
|
|
+ } else if (image.chromaShiftW == 2 && image.chromaShiftH == 0) {
|
|
|
|
|
|
- yuv444_2_rgb8888(w.ptr(), image.planes[0], image.planes[2], image.planes[1], image.w, image.h, image.linesize[0], image.linesize[1], image.w << 2, 0);
|
|
|
- // libyuv::I444ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
|
|
- converted = true;
|
|
|
- } else if (image.chromaShiftW == 2 && image.chromaShiftH == 0) {
|
|
|
-
|
|
|
- // libyuv::I411ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
|
|
- // converted = true;
|
|
|
- }
|
|
|
-
|
|
|
- if (converted)
|
|
|
- texture->set_data(Image(image.w, image.h, 0, Image::FORMAT_RGBA8, frame_data)); //Zero copy send to visual server
|
|
|
+ // libyuv::I411ToARGB(image.planes[0], image.linesize[0], image.planes[2], image.linesize[2], image.planes[1], image.linesize[1], w.ptr(), image.w << 2, image.w, image.h);
|
|
|
+ // converted = true;
|
|
|
}
|
|
|
|
|
|
- break;
|
|
|
+ if (converted) {
|
|
|
+ Ref<Image> img = memnew(Image(image.w, image.h, 0, Image::FORMAT_RGBA8, frame_data));
|
|
|
+ texture->set_data(img); //Zero copy send to visual server
|
|
|
+ video_frame_done = true;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- video_frame_delay = video_frame->time - video_pos;
|
|
|
- video_pos = video_frame->time;
|
|
|
-
|
|
|
- memmove(video_frames, video_frames + 1, (--video_frames_pos) * sizeof(void *));
|
|
|
- video_frames[video_frames_pos] = video_frame;
|
|
|
- } else {
|
|
|
-
|
|
|
- break;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- time += p_delta;
|
|
|
+ video_pos = video_frame->time;
|
|
|
+ memmove(video_frames, video_frames + 1, (--video_frames_pos) * sizeof(void *));
|
|
|
+ video_frames[video_frames_pos] = video_frame;
|
|
|
+ }
|
|
|
|
|
|
if (video_frames_pos == 0 && webm->isEOS())
|
|
|
stop();
|
|
@@ -372,6 +375,11 @@ inline bool VideoStreamPlaybackWebm::has_enough_video_frames() const {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
+bool VideoStreamPlaybackWebm::should_process(WebMFrame &video_frame) {
|
|
|
+ const double audio_delay = AudioServer::get_singleton()->get_output_delay();
|
|
|
+ return video_frame.time >= time + audio_delay + delay_compensation;
|
|
|
+}
|
|
|
+
|
|
|
void VideoStreamPlaybackWebm::delete_pointers() {
|
|
|
|
|
|
if (pcm)
|
|
@@ -395,34 +403,6 @@ void VideoStreamPlaybackWebm::delete_pointers() {
|
|
|
|
|
|
/**/
|
|
|
|
|
|
-RES ResourceFormatLoaderVideoStreamWebm::load(const String &p_path, const String &p_original_path, Error *r_error) {
|
|
|
-
|
|
|
- Ref<VideoStreamWebm> stream = memnew(VideoStreamWebm);
|
|
|
- stream->set_file(p_path);
|
|
|
- if (r_error)
|
|
|
- *r_error = OK;
|
|
|
- return stream;
|
|
|
-}
|
|
|
-
|
|
|
-void ResourceFormatLoaderVideoStreamWebm::get_recognized_extensions(List<String> *p_extensions) const {
|
|
|
-
|
|
|
- p_extensions->push_back("webm");
|
|
|
-}
|
|
|
-bool ResourceFormatLoaderVideoStreamWebm::handles_type(const String &p_type) const {
|
|
|
-
|
|
|
- return (p_type == "VideoStream" || p_type == "VideoStreamWebm");
|
|
|
-}
|
|
|
-
|
|
|
-String ResourceFormatLoaderVideoStreamWebm::get_resource_type(const String &p_path) const {
|
|
|
-
|
|
|
- const String exl = p_path.get_extension().to_lower();
|
|
|
- if (exl == "webm")
|
|
|
- return "VideoStreamWebm";
|
|
|
- return "";
|
|
|
-}
|
|
|
-
|
|
|
-/**/
|
|
|
-
|
|
|
VideoStreamWebm::VideoStreamWebm()
|
|
|
: audio_track(0) {}
|
|
|
|
|
@@ -439,6 +419,19 @@ void VideoStreamWebm::set_file(const String &p_file) {
|
|
|
|
|
|
file = p_file;
|
|
|
}
|
|
|
+String VideoStreamWebm::get_file() {
|
|
|
+
|
|
|
+ return file;
|
|
|
+}
|
|
|
+
|
|
|
+void VideoStreamWebm::_bind_methods() {
|
|
|
+
|
|
|
+ ClassDB::bind_method(D_METHOD("set_file", "file"), &VideoStreamWebm::set_file);
|
|
|
+ ClassDB::bind_method(D_METHOD("get_file"), &VideoStreamWebm::get_file);
|
|
|
+
|
|
|
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "file", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_file", "get_file");
|
|
|
+}
|
|
|
+
|
|
|
void VideoStreamWebm::set_audio_track(int p_track) {
|
|
|
|
|
|
audio_track = p_track;
|