]> Some of my projects - openlase.git/commitdiff
qplayvid: new Qt-based GUI video player
authorHector Martin <hector@marcansoft.com>
Sun, 20 Mar 2011 07:45:54 +0000 (08:45 +0100)
committerHector Martin <hector@marcansoft.com>
Sun, 20 Mar 2011 07:47:42 +0000 (08:47 +0100)
tools/CMakeLists.txt
tools/qplayvid/CMakeLists.txt [new file with mode: 0644]
tools/qplayvid/qplayvid.c [new file with mode: 0644]
tools/qplayvid/qplayvid.h [new file with mode: 0644]
tools/qplayvid/qplayvid_gui.cpp [new file with mode: 0644]
tools/qplayvid/qplayvid_gui.h [new file with mode: 0644]

index 991ed07b70790f244c558833efd1e72a7c3ac714..9a4b18bcc688258394c7fbc831eaaf2fc7f46f01 100644 (file)
@@ -36,3 +36,5 @@ if(OPENGL_FOUND AND GLUT_FOUND)
 else()
   message(STATUS "Will NOT build simulator (OpenGL or GLUT missing)")
 endif()
+
+add_subdirectory(qplayvid)
diff --git a/tools/qplayvid/CMakeLists.txt b/tools/qplayvid/CMakeLists.txt
new file mode 100644 (file)
index 0000000..3a1927c
--- /dev/null
@@ -0,0 +1,30 @@
+#         OpenLase - a realtime laser graphics toolkit
+#
+# Copyright (C) 2009-2011 Hector Martin "marcan" <hector@marcansoft.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 or version 3.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+
+if(QT4_FOUND AND FFMPEG_FOUND)
+  include(${QT_USE_FILE})
+
+  QT4_AUTOMOC(qplayvid_gui.cpp qplayvid.cpp)
+
+  include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+  add_executable(qplayvid qplayvid.c qplayvid_gui.cpp)
+  target_link_libraries(qplayvid openlase ${FFMPEG_LIBRARIES} ${QT_LIBRARIES})
+else()
+  message(STATUS "Will NOT build qplayvid (Qt4 or FFmpeg missing)")
+endif()
diff --git a/tools/qplayvid/qplayvid.c b/tools/qplayvid/qplayvid.c
new file mode 100644 (file)
index 0000000..cd21bf9
--- /dev/null
@@ -0,0 +1,955 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2011 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "libol.h"
+#include "trace.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <jack/jack.h>
+#include <math.h>
+#include <pthread.h>
+
+#include "qplayvid.h"
+
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+
+#define OL_FRAMES_BUF 3
+#define VIDEO_BUF 64
+
+#define SAMPLE_RATE 48000
+#define AUDIO_BUF 3
+
+#define dprintf printf
+
+typedef struct {
+       uint8_t *data;
+       size_t stride;
+       size_t data_size;
+       int32_t seekid;
+       double pts;
+} VideoFrame;
+
+typedef struct {
+       int16_t l, r;
+       int32_t seekid;
+       double pts;
+} AudioSample;
+
+typedef enum {
+       STOP,
+       PAUSE,
+       PLAY,
+} DisplayMode;
+
+struct PlayerCtx {
+       int exit;
+       DisplayMode display_mode;
+       pthread_t decoder_thread;
+       pthread_t display_thread;
+       pthread_mutex_t display_mode_mutex;
+       PlayerEventCb ev_cb;
+
+       PlayerSettings settings;
+       int settings_changed;
+       pthread_mutex_t settings_mutex;
+       int skip_frame;
+
+       AVFormatContext *fmt_ctx;
+       int audio_idx;
+       int video_idx;
+       pthread_mutex_t seek_mutex;
+       pthread_cond_t seek_cond;
+       int32_t cur_seekid;
+       double seek_pos;
+       double duration;
+
+       AVStream *a_stream;
+       AVCodecContext *a_codec_ctx;
+       AVCodec *a_codec;
+       ReSampleContext *a_resampler;
+       double a_ratio;
+
+       AVStream *v_stream;
+       AVCodecContext *v_codec_ctx;
+       AVCodec *v_codec;
+       int width, height;
+       int64_t v_pkt_pts;
+       int64_t v_faulty_dts, v_faulty_pts, v_last_pts, v_last_dts;
+
+       AVFrame *v_frame;
+       VideoFrame *v_bufs[VIDEO_BUF];
+       int v_buf_len;
+       int v_buf_get;
+       int v_buf_put;
+       VideoFrame *cur_frame;
+       double last_frame_pts;
+
+       int a_stride;
+       short *a_ibuf;
+       short *a_rbuf;
+       AudioSample *a_buf;
+       int a_buf_len;
+       int a_buf_get;
+       int a_buf_put;
+       double a_cur_pts;
+
+       pthread_mutex_t a_buf_mutex;
+       pthread_cond_t a_buf_not_full;
+       pthread_cond_t a_buf_not_empty;
+       pthread_mutex_t v_buf_mutex;
+       pthread_cond_t v_buf_not_full;
+       pthread_cond_t v_buf_not_empty;
+};
+
+size_t decode_audio(PlayerCtx *ctx, AVPacket *packet, int new_packet, int32_t seekid)
+{
+       int bytes = ctx->a_stride * AVCODEC_MAX_AUDIO_FRAME_SIZE;
+       int decoded;
+
+       decoded = avcodec_decode_audio3(ctx->a_codec_ctx, ctx->a_ibuf, &bytes, packet);
+       if (decoded < 0) {
+               fprintf(stderr, "Error while decoding audio frame\n");
+               return packet->size;
+       }
+       int in_samples = bytes / ctx->a_stride;
+       if (!in_samples)
+               return decoded;
+
+
+       int out_samples;
+       out_samples = audio_resample(ctx->a_resampler, ctx->a_rbuf, ctx->a_ibuf, in_samples);
+
+       pthread_mutex_lock(&ctx->a_buf_mutex);
+
+       int free_samples;
+       while (1) {
+               free_samples = ctx->a_buf_get - ctx->a_buf_put;
+               if (free_samples <= 0)
+                       free_samples += ctx->a_buf_len;
+
+               if (free_samples <= out_samples) {
+                       printf("Wait for space in audio buffer\n");
+                       pthread_cond_wait(&ctx->a_buf_not_full, &ctx->a_buf_mutex);
+               } else {
+                       break;
+               }
+       }
+       pthread_mutex_unlock(&ctx->a_buf_mutex);
+
+       if (new_packet && packet->pts != AV_NOPTS_VALUE)
+               ctx->a_cur_pts = av_q2d(ctx->a_stream->time_base) * packet->pts;
+
+       int put = ctx->a_buf_put;
+       short *rbuf = ctx->a_rbuf;
+       int i;
+       for (i = 0; i < out_samples; i++) {
+               ctx->a_buf[put].l = *rbuf++;
+               ctx->a_buf[put].r = *rbuf++;
+               ctx->a_buf[put].seekid = seekid;
+               ctx->a_buf[put].pts = ctx->a_cur_pts;
+               put++;
+               ctx->a_cur_pts += 1.0/SAMPLE_RATE;
+               if (put == ctx->a_buf_len)
+                       put = 0;
+       }
+
+       printf("Put %d audio samples\n", out_samples);
+
+       pthread_mutex_lock(&ctx->a_buf_mutex);
+       ctx->a_buf_put = put;
+       pthread_cond_signal(&ctx->a_buf_not_empty);
+       pthread_mutex_unlock(&ctx->a_buf_mutex);
+
+       return decoded;
+}
+
+size_t decode_video(PlayerCtx *ctx, AVPacket *packet, int new_packet, int32_t seekid)
+{
+       int decoded;
+       int got_frame;
+
+       if (!new_packet)
+               fprintf(stderr, "warn: multi-frame video packets, pts might be inaccurate\n");
+
+       ctx->v_pkt_pts = packet->pts;
+
+       decoded = avcodec_decode_video2(ctx->v_codec_ctx, ctx->v_frame, &got_frame, packet);
+       if (decoded < 0) {
+               fprintf(stderr, "Error while decoding video frame\n");
+               return packet->size;
+       }
+       if (!got_frame)
+               return decoded;
+
+       // The pts magic guesswork
+       int64_t pts = AV_NOPTS_VALUE;
+       int64_t frame_pts = *(int64_t*)ctx->v_frame->opaque;
+       if (packet->dts != AV_NOPTS_VALUE) {
+               ctx->v_faulty_dts += packet->dts <= ctx->v_last_dts;
+               ctx->v_last_dts = packet->dts;
+       }
+       if (frame_pts != AV_NOPTS_VALUE) {
+               ctx->v_faulty_pts += frame_pts <= ctx->v_last_pts;
+               ctx->v_last_pts = frame_pts;
+       }
+       if ((ctx->v_faulty_pts <= ctx->v_faulty_dts || packet->dts == AV_NOPTS_VALUE)
+               && frame_pts != AV_NOPTS_VALUE)
+               pts = frame_pts;
+       else
+               pts = packet->dts;
+
+       pthread_mutex_lock(&ctx->v_buf_mutex);
+       while (((ctx->v_buf_put + 1) % ctx->v_buf_len) == ctx->v_buf_get) {
+               printf("Wait for space in video buffer\n");
+               pthread_cond_wait(&ctx->v_buf_not_full, &ctx->v_buf_mutex);
+       }
+       pthread_mutex_unlock(&ctx->v_buf_mutex);
+
+       VideoFrame *frame = ctx->v_bufs[ctx->v_buf_put];
+       if (!frame) {
+               frame = malloc(sizeof(VideoFrame));
+               frame->stride = ctx->v_frame->linesize[0];
+               frame->data_size = frame->stride * ctx->height;
+               frame->data = malloc(frame->data_size);
+               ctx->v_bufs[ctx->v_buf_put] = frame;
+       }
+
+       if (frame->stride != ctx->v_frame->linesize[0]) {
+               fprintf(stderr, "stride mismatch: %d != %d\n", (int)frame->stride, ctx->v_frame->linesize[0]);
+               return decoded;
+       }
+
+       frame->pts = av_q2d(ctx->v_stream->time_base) * pts;
+       frame->seekid = seekid;
+       memcpy(frame->data, ctx->v_frame->data[0], frame->data_size);
+
+       printf("Put frame %d\n", ctx->v_buf_put);
+       pthread_mutex_lock(&ctx->v_buf_mutex);
+       if (++ctx->v_buf_put == ctx->v_buf_len)
+               ctx->v_buf_put = 0;
+       pthread_cond_signal(&ctx->v_buf_not_empty);
+       pthread_mutex_unlock(&ctx->v_buf_mutex);
+
+       return decoded;
+}
+
+void push_eof(PlayerCtx *ctx, int32_t seekid)
+{
+       pthread_mutex_lock(&ctx->a_buf_mutex);
+       while (((ctx->a_buf_put + 1) % ctx->a_buf_len) == ctx->a_buf_get) {
+               printf("Wait for space in audio buffer\n");
+               pthread_cond_wait(&ctx->a_buf_not_full, &ctx->a_buf_mutex);
+       }
+       ctx->a_buf[ctx->a_buf_put].l = 0;
+       ctx->a_buf[ctx->a_buf_put].r = 0;
+       ctx->a_buf[ctx->a_buf_put].pts = 0;
+       ctx->a_buf[ctx->a_buf_put].seekid = -seekid;
+       if (++ctx->a_buf_put == ctx->a_buf_len)
+               ctx->a_buf_put = 0;
+       pthread_cond_signal(&ctx->a_buf_not_empty);
+       pthread_mutex_unlock(&ctx->a_buf_mutex);
+
+       pthread_mutex_lock(&ctx->v_buf_mutex);
+       while (((ctx->v_buf_put + 1) % ctx->v_buf_len) == ctx->v_buf_get) {
+               printf("Wait for space in video buffer\n");
+               pthread_cond_wait(&ctx->v_buf_not_full, &ctx->v_buf_mutex);
+       }
+       ctx->v_bufs     [ctx->v_buf_put]->pts = 0;
+       ctx->v_bufs[ctx->v_buf_put]->seekid = -seekid;
+       if (++ctx->v_buf_put == ctx->v_buf_len)
+               ctx->v_buf_put = 0;
+       pthread_mutex_unlock(&ctx->v_buf_mutex);
+}
+
+void *decoder_thread(void *arg)
+{
+       PlayerCtx *ctx = arg;
+       AVPacket packet;
+       AVPacket cpacket;
+       size_t decoded_bytes;
+       int seekid = ctx->cur_seekid;
+
+       printf("Decoder thread started\n");
+
+       memset(&packet, 0, sizeof(packet));
+       memset(&cpacket, 0, sizeof(cpacket));
+
+       while (!ctx->exit) {
+               int new_packet = 0;
+               if (cpacket.size == 0) {
+                       if (packet.data)
+                               av_free_packet(&packet);
+                       pthread_mutex_lock(&ctx->seek_mutex);
+                       if (ctx->cur_seekid > seekid) {
+                               printf("Seek! %f\n", ctx->seek_pos);
+                               av_seek_frame(ctx->fmt_ctx, -1, (int)(ctx->seek_pos * AV_TIME_BASE), 0);
+                               seekid = ctx->cur_seekid;
+                               // HACK! Avoid deadlock by waking up the video waiter
+                               pthread_mutex_lock(&ctx->v_buf_mutex);
+                               pthread_cond_signal(&ctx->v_buf_not_empty);
+                               pthread_mutex_unlock(&ctx->v_buf_mutex);
+
+                       }
+                       if (av_read_frame(ctx->fmt_ctx, &packet) < 0) {
+                               fprintf(stderr, "EOF!\n");
+                               push_eof(ctx, seekid);
+                               pthread_cond_wait(&ctx->seek_cond, &ctx->seek_mutex);
+                               pthread_mutex_unlock(&ctx->seek_mutex);
+                               continue;
+                       }
+                       pthread_mutex_unlock(&ctx->seek_mutex);
+                       cpacket = packet;
+                       new_packet = 1;
+               }
+               if (cpacket.stream_index == ctx->audio_idx) {
+                       decoded_bytes = decode_audio(ctx, &cpacket, new_packet, seekid);
+               } else if (cpacket.stream_index == ctx->video_idx) {
+                       decoded_bytes = decode_video(ctx, &cpacket, new_packet, seekid);
+               } else {
+                       decoded_bytes = cpacket.size;
+               }
+
+               cpacket.data += decoded_bytes;
+               cpacket.size -= decoded_bytes;
+       }
+       return NULL;
+}
+
+/* apparently hacking the buffer functions to store the PTS value is *the
+ * normal thing to do* with ffmpeg. WTF? */
+
+int hack_get_buffer(struct AVCodecContext *c, AVFrame *pic) {
+       PlayerCtx *ctx = (PlayerCtx*) c->opaque;
+
+       int ret = avcodec_default_get_buffer(c, pic);
+       uint64_t *pts = av_malloc(sizeof(uint64_t));
+       *pts = ctx->v_pkt_pts;
+       pic->opaque = pts;
+       return ret;
+}
+
+void hack_release_buffer(struct AVCodecContext *c, AVFrame *pic) {
+       if(pic) av_freep(&pic->opaque);
+       avcodec_default_release_buffer(c, pic);
+}
+
+int decoder_init(PlayerCtx *ctx, const char *file)
+{
+       int i;
+
+       memset(ctx, 0, sizeof(*ctx));
+
+       ctx->video_idx = -1;
+       ctx->audio_idx = -1;
+       ctx->cur_seekid = 1;
+       ctx->a_cur_pts = 0;
+
+       if (av_open_input_file(&ctx->fmt_ctx, file, NULL, 0, NULL) != 0)
+               return -1;
+
+       if (av_find_stream_info(ctx->fmt_ctx) < 0)
+               return -1;
+
+       ctx->duration = ctx->fmt_ctx->duration/(double)AV_TIME_BASE;
+
+       pthread_mutex_init(&ctx->seek_mutex, NULL);
+       pthread_cond_init(&ctx->seek_cond, NULL);
+
+       for (i = 0; i < ctx->fmt_ctx->nb_streams; i++) {
+               switch (ctx->fmt_ctx->streams[i]->codec->codec_type) {
+                       case CODEC_TYPE_VIDEO:
+                               if (ctx->video_idx == -1)
+                                       ctx->video_idx = i;
+                               break;
+                       case CODEC_TYPE_AUDIO:
+                               if (ctx->audio_idx == -1)
+                                       ctx->audio_idx = i;
+                               break;
+                       default:
+                               break;
+               }
+       }
+
+       if (ctx->video_idx == -1 || ctx->audio_idx == -1)
+               return -1;
+
+       ctx->a_stream = ctx->fmt_ctx->streams[ctx->audio_idx];
+       ctx->v_stream = ctx->fmt_ctx->streams[ctx->video_idx];
+
+       ctx->a_codec_ctx = ctx->a_stream->codec;
+       ctx->v_codec_ctx = ctx->v_stream->codec;
+       ctx->width = ctx->v_codec_ctx->width;
+       ctx->height = ctx->v_codec_ctx->height;
+
+       ctx->a_codec = avcodec_find_decoder(ctx->a_codec_ctx->codec_id);
+       if (ctx->a_codec == NULL)
+               return -1;
+       ctx->v_codec = avcodec_find_decoder(ctx->v_codec_ctx->codec_id);
+       if (ctx->v_codec == NULL)
+               return -1;
+
+       if (avcodec_open(ctx->a_codec_ctx, ctx->a_codec) < 0)
+               return -1;
+       if (avcodec_open(ctx->v_codec_ctx, ctx->v_codec) < 0)
+               return -1;
+
+       ctx->v_pkt_pts = AV_NOPTS_VALUE;
+    ctx->v_faulty_pts = ctx->v_faulty_dts = 0;
+    ctx->v_last_pts = ctx->v_last_dts = INT64_MIN;
+
+       ctx->v_codec_ctx->get_buffer = hack_get_buffer;
+    ctx->v_codec_ctx->release_buffer = hack_release_buffer;
+       ctx->v_codec_ctx->opaque = ctx;
+
+       printf("Audio srate: %d\n", ctx->a_codec_ctx->sample_rate);
+
+       ctx->a_resampler = av_audio_resample_init(2, ctx->a_codec_ctx->channels,
+                                                       SAMPLE_RATE, ctx->a_codec_ctx->sample_rate,
+                                                       SAMPLE_FMT_S16, ctx->a_codec_ctx->sample_fmt,
+                                                       16, 10, 0, 0.8);
+       if (!ctx->a_resampler)
+               return -1;
+
+       ctx->a_ratio = SAMPLE_RATE/(double)ctx->a_codec_ctx->sample_rate;
+
+       ctx->a_stride = av_get_bits_per_sample_fmt(ctx->a_codec_ctx->sample_fmt) / 8;
+       ctx->a_stride *= ctx->a_codec_ctx->channels;
+
+       ctx->a_ibuf = malloc(ctx->a_stride * AVCODEC_MAX_AUDIO_FRAME_SIZE);
+       ctx->a_rbuf = malloc(2 * sizeof(short) * AVCODEC_MAX_AUDIO_FRAME_SIZE * (ctx->a_ratio * 1.1));
+       ctx->a_buf_len = AUDIO_BUF*SAMPLE_RATE;
+       ctx->a_buf = malloc(sizeof(*ctx->a_buf) * ctx->a_buf_len);
+
+       ctx->v_frame = avcodec_alloc_frame();
+       ctx->v_buf_len = VIDEO_BUF;
+
+       pthread_mutex_init(&ctx->a_buf_mutex, NULL);
+       pthread_cond_init(&ctx->a_buf_not_full, NULL);
+       pthread_cond_init(&ctx->a_buf_not_empty, NULL);
+       pthread_mutex_init(&ctx->v_buf_mutex, NULL);
+       pthread_cond_init(&ctx->v_buf_not_full, NULL);
+       pthread_cond_init(&ctx->v_buf_not_empty, NULL);
+
+       if (pthread_create(&ctx->decoder_thread, NULL, decoder_thread, ctx) != 0)
+               return -1;
+
+       return 0;
+}
+
+PlayerCtx *g_ctx;
+
+void drop_audio(PlayerCtx *ctx, int by_pts)
+{
+       if (!ctx->cur_frame)
+               return;
+
+       while (1) {
+               pthread_mutex_lock(&ctx->a_buf_mutex);
+               int get = ctx->a_buf_get;
+               int have_samples = ctx->a_buf_put - get;
+               if (!have_samples) {
+                       pthread_mutex_unlock(&ctx->a_buf_mutex);
+                       break;
+               }
+               if (have_samples < 0)
+                       have_samples += ctx->a_buf_len;
+               pthread_mutex_unlock(&ctx->a_buf_mutex);
+
+               int i;
+               for (i = 0; i < have_samples; i++) {
+                       if (ctx->a_buf[get].seekid == -1 ||
+                               (ctx->a_buf[get].seekid == ctx->cur_seekid &&
+                               (!by_pts || ctx->a_buf[get].pts >= ctx->cur_frame->pts)))
+                               break;
+                       if (++get == ctx->a_buf_len)
+                               get = 0;
+               }
+               printf("Dropped %d samples\n", i);
+
+               pthread_mutex_lock(&ctx->a_buf_mutex);
+               ctx->a_buf_get = get;
+               pthread_cond_signal(&ctx->a_buf_not_full);
+               pthread_mutex_unlock(&ctx->a_buf_mutex);
+               if (i == 0)
+                       break;
+       }
+}
+
+int next_video_frame(PlayerCtx *ctx)
+{
+       if (ctx->cur_frame && ctx->cur_frame->seekid == -ctx->cur_seekid) {
+               printf("No more video (EOF)\n");
+               return 0;
+       }
+       if (ctx->cur_frame)
+               ctx->last_frame_pts = ctx->cur_frame->pts;
+       pthread_mutex_lock(&ctx->v_buf_mutex);
+       while (ctx->v_buf_get == ctx->v_buf_put) {
+               printf("Wait for video (pts %f)\n", ctx->cur_frame?ctx->cur_frame->pts:-1);
+               pthread_cond_wait(&ctx->v_buf_not_empty, &ctx->v_buf_mutex);
+               // HACK! This makes sure to flush stale stuff from the audio queue to
+               // avoid deadlocks while seeking
+               pthread_mutex_unlock(&ctx->v_buf_mutex);
+               drop_audio(ctx, 0);
+               pthread_mutex_lock(&ctx->v_buf_mutex);
+       }
+       if (ctx->cur_frame && ctx->v_bufs[ctx->v_buf_get]->seekid > ctx->cur_frame->seekid)
+               ctx->last_frame_pts = -1;
+       ctx->cur_frame = ctx->v_bufs[ctx->v_buf_get];
+       printf("Get frame %d\n", ctx->v_buf_get);
+       ctx->v_buf_get++;
+       if (ctx->v_buf_get == ctx->v_buf_len)
+               ctx->v_buf_get = 0;
+       pthread_cond_signal(&ctx->v_buf_not_full);
+       pthread_mutex_unlock(&ctx->v_buf_mutex);
+       return 1;
+}
+
+void get_audio(float *lb, float *rb, int samples)
+{
+       PlayerCtx *ctx = g_ctx;
+
+       pthread_mutex_lock(&ctx->display_mode_mutex);
+       DisplayMode display_mode = ctx->display_mode;
+       pthread_mutex_unlock(&ctx->display_mode_mutex);
+
+       if (display_mode != PLAY)
+       {
+               if (display_mode == PAUSE) {
+                       printf("get_audio: paused\n");
+                       if (!ctx->cur_frame) {
+                               next_video_frame(ctx);
+                       }
+                       while (ctx->cur_frame->seekid != ctx->cur_seekid &&
+                                  ctx->cur_frame->seekid != -ctx->cur_seekid) {
+                               printf("Drop audio due to seek\n");
+                               drop_audio(ctx, 1);
+                               next_video_frame(ctx);
+                               drop_audio(ctx, 1);
+                       }
+                       if (ctx->skip_frame) {
+                               ctx->skip_frame = 0;
+                               drop_audio(ctx, 1);
+                               next_video_frame(ctx);
+                               drop_audio(ctx, 1);
+                       }
+                       printf("get_audio: pause complete\n");
+               }
+               memset(lb, 0, samples * sizeof(*lb));
+               memset(rb, 0, samples * sizeof(*rb));
+               return;
+       }
+       pthread_mutex_lock(&ctx->settings_mutex);
+       double volume = ctx->settings.volume / 100.0;
+       pthread_mutex_unlock(&ctx->settings_mutex);
+
+       while (samples) {
+               double pts = -1;
+
+               pthread_mutex_lock(&ctx->a_buf_mutex);
+               int have_samples = ctx->a_buf_put - ctx->a_buf_get;
+               if (!have_samples) {
+                       printf("Wait for audio\n");
+                       pthread_cond_wait(&ctx->a_buf_not_empty, &ctx->a_buf_mutex);
+                       pthread_mutex_unlock(&ctx->a_buf_mutex);
+                       continue;
+               }
+               if (have_samples < 0)
+                       have_samples += ctx->a_buf_len;
+               pthread_mutex_unlock(&ctx->a_buf_mutex);
+
+               int get = ctx->a_buf_get;
+               int played = 0;
+               while (samples && have_samples--) {
+                       if (ctx->a_buf[get].seekid == -ctx->cur_seekid) {
+                               memset(lb, 0, sizeof(*lb) * samples);
+                               memset(rb, 0, sizeof(*rb) * samples);
+                               samples = 0;
+                               break;
+                       }
+                       if (ctx->a_buf[get].seekid == ctx->cur_seekid) {
+                               pts = ctx->a_buf[get].pts;
+                               *lb++ = ctx->a_buf[get].l / 32768.0 * volume;
+                               *rb++ = ctx->a_buf[get].r / 32768.0 * volume;
+                               samples--;
+                               played++;
+                       }
+                       if (++get >= ctx->a_buf_len)
+                               get = 0;
+               }
+
+               pthread_mutex_lock(&ctx->a_buf_mutex);
+               ctx->a_buf_get = get;
+               pthread_cond_signal(&ctx->a_buf_not_full);
+               pthread_mutex_unlock(&ctx->a_buf_mutex);
+
+               printf("Played %d samples, next pts %f\n", played, pts);
+
+               while (1) {
+                       if (!ctx->cur_frame) {
+                               next_video_frame(ctx);
+                               continue;
+                       }
+                       if (ctx->cur_frame->seekid == -ctx->cur_seekid)
+                               break;
+                       double next_pts = ctx->cur_frame->pts;
+                       if (ctx->last_frame_pts != -1)
+                               next_pts = 2 * ctx->cur_frame->pts - ctx->last_frame_pts;
+                       if (pts > next_pts || ctx->cur_frame->seekid != ctx->cur_seekid) {
+                               if (next_video_frame(ctx))
+                                       continue;
+                       }
+                       break;
+               }
+       }
+}
+
+void deliver_event(PlayerCtx *ctx, float time, float ftime, int frames, int ended)
+{
+       if (!ctx->ev_cb)
+               return;
+       PlayerEvent ev;
+       OLFrameInfo info;
+       olGetFrameInfo(&info);
+       ev.ended = ended;
+       ev.frames = frames;
+       ev.time = time;
+       ev.ftime = ftime;
+       ev.points = info.points;
+       ev.padding_points = info.padding_points;
+       ev.resampled_points = info.resampled_points;
+       ev.resampled_blacks = info.resampled_blacks;
+       ev.objects = info.objects;
+       if (ctx->cur_frame)
+               ev.pts = ctx->cur_frame->pts;
+       else
+               ev.pts = -1;
+       ctx->ev_cb(&ev);
+}
+
+void *display_thread(void *arg)
+{
+       PlayerCtx *ctx = arg;
+       int i;
+
+       OLRenderParams params;
+       memset(&params, 0, sizeof params);
+       params.rate = 48000;
+       params.on_speed = 2.0/100.0;
+       params.off_speed = 2.0/15.0;
+       params.start_wait = 8;
+       params.end_wait = 3;
+       params.snap = 1/120.0;
+       params.render_flags = RENDER_GRAYSCALE;
+       params.min_length = 20;
+       params.start_dwell = 2;
+       params.end_dwell = 2;
+       params.max_framelen = 48000/20.0;
+
+       if(olInit(OL_FRAMES_BUF, 300000) < 0) {
+               printf("OpenLase init failed\n");
+               return NULL;
+       }
+
+       float aspect = av_q2d(ctx->v_stream->sample_aspect_ratio);
+       if (aspect == 0)
+               aspect = ctx->width / (float)ctx->height;
+       else
+               aspect = 1/aspect;
+
+       float iaspect = 1/aspect;
+
+       int maxd = ctx->width > ctx->height ? ctx->width : ctx->height;
+       int mind = ctx->width < ctx->height ? ctx->width : ctx->height;
+
+       g_ctx = ctx;
+       olSetAudioCallback(get_audio);
+       olSetRenderParams(&params);
+
+       OLTraceCtx *trace_ctx;
+       OLTraceParams tparams;
+       OLTraceResult result;
+       memset(&result, 0, sizeof(result));
+
+       tparams.width = ctx->width;
+       tparams.height = ctx->height;
+
+       printf("Resolution: %dx%d\n", ctx->width, ctx->height);
+       olTraceInit(&trace_ctx, &tparams);
+
+       VideoFrame *last = NULL;
+
+       pthread_mutex_lock(&ctx->display_mode_mutex);
+       DisplayMode display_mode = ctx->display_mode;
+       pthread_mutex_unlock(&ctx->display_mode_mutex);
+
+       int inf = 0;
+       int bg_white = -1;
+       float time = 0;
+       int frames = 0;
+       while (display_mode != STOP) {
+               pthread_mutex_lock(&ctx->settings_mutex);
+               PlayerSettings settings = ctx->settings;
+               int settings_changed = ctx->settings_changed;
+               ctx->settings_changed = 0;
+               pthread_mutex_unlock(&ctx->settings_mutex);
+
+               params.min_length = settings.minsize;
+               params.end_dwell = params.start_dwell = settings.dwell;
+               params.off_speed = settings.offspeed * 0.002;
+               params.snap = (settings.snap*2.0)/(float)maxd;
+               params.start_wait = settings.startwait;
+               params.end_wait = settings.endwait;
+               if (settings.minrate == 0)
+                       params.max_framelen = 0;
+               else
+                       params.max_framelen = params.rate / settings.minrate;
+
+               olSetRenderParams(&params);
+
+               olLoadIdentity();
+               if (aspect > 1) {
+                       olSetScissor(-1, -iaspect, 1, iaspect);
+                       olScale(1, iaspect);
+               } else {
+                       olSetScissor(-aspect, -1, aspect, 1);
+                       olScale(aspect, 1);
+               }
+
+               olScale(1 + settings.overscan/100.0, 1 + settings.overscan/100.0);
+               olTranslate(-1.0f, 1.0f);
+               olScale(2.0f/ctx->width, -2.0f/ctx->height);
+
+               if (!ctx->cur_frame || ctx->cur_frame->seekid < 0) {
+                       printf("Dummy frame\n");
+                       float ftime = olRenderFrame(100);
+                       pthread_mutex_lock(&ctx->display_mode_mutex);
+                       display_mode = ctx->display_mode;
+                       pthread_mutex_unlock(&ctx->display_mode_mutex);
+                       if (ctx->cur_frame && ctx->cur_frame->seekid < 0)
+                               deliver_event(ctx, time, ftime, frames, 1);
+                       else
+                               deliver_event(ctx, time, ftime, frames, 0);
+                       continue;
+               }
+
+               if (last != ctx->cur_frame || settings_changed) {
+                       tparams.sigma = settings.blur / 100.0;
+                       if (settings.canny) {
+                               tparams.mode = OL_TRACE_CANNY;
+                               tparams.threshold = settings.threshold;
+                               tparams.threshold2 = settings.threshold2;
+                               bg_white = -1;
+                       } else {
+                               tparams.mode = OL_TRACE_THRESHOLD;
+                               if (settings.splitthreshold) {
+                                       int edge_off = mind * settings.offset / 100;
+                                       int bsum = 0;
+                                       int cnt = 0;
+                                       int c;
+                                       for (c = edge_off; c < (ctx->width-edge_off); c++) {
+                                               bsum += ctx->cur_frame->data[c+edge_off*ctx->cur_frame->stride];
+                                               bsum += ctx->cur_frame->data[c+(ctx->height-edge_off-1)*ctx->cur_frame->stride];
+                                               cnt += 2;
+                                       }
+                                       for (c = edge_off; c < (ctx->height-edge_off); c++) {
+                                               bsum += ctx->cur_frame->data[edge_off+ctx->cur_frame->stride];
+                                               bsum += ctx->cur_frame->data[(c+1)*ctx->cur_frame->stride-1-edge_off];
+                                               cnt += 2;
+                                       }
+                                       bsum /= cnt;
+                                       if (bg_white == -1)
+                                               bg_white = bsum > ((settings.darkval + settings.lightval)/2);
+                                       if (bg_white && bsum < settings.darkval)
+                                               bg_white = 0;
+                                       if (!bg_white && bsum > settings.lightval)
+                                               bg_white = 1;
+                                       if (bg_white)
+                                               tparams.threshold = settings.threshold2;
+                                       else
+                                               tparams.threshold = settings.threshold;
+                               } else {
+                                       tparams.threshold = settings.threshold;
+                               }
+                       }
+                       olTraceReInit(trace_ctx, &tparams);
+                       olTraceFree(&result);
+                       printf("Trace\n");
+                       olTrace(trace_ctx, ctx->cur_frame->data, ctx->cur_frame->stride, &result);
+                       printf("Trace done\n");
+                       inf++;
+                       last = ctx->cur_frame;
+               }
+
+               int i, j;
+               for (i = 0; i < result.count; i++) {
+                       OLTraceObject *o = &result.objects[i];
+                       olBegin(OL_POINTS);
+                       OLTracePoint *p = o->points;
+                       for (j = 0; j < o->count; j++) {
+                               if (j % settings.decimation == 0)
+                                       olVertex(p->x, p->y, C_WHITE);
+                               p++;
+                       }
+                       olEnd();
+               }
+
+               float ftime = olRenderFrame(100);
+               OLFrameInfo info;
+               olGetFrameInfo(&info);
+               frames++;
+               time += ftime;
+               printf("Frame time: %.04f, Cur FPS:%6.02f, Avg FPS:%6.02f, Drift: %7.4f, "
+                               "In %4d, Out %4d Thr %3d/%3d Bg %3d Pts %4d",
+                               ftime, 1/ftime, frames/time, 0.0, inf, frames,
+                               tparams.threshold, tparams.threshold2, 0, info.points);
+               if (info.resampled_points)
+                       printf(" Rp %4d Bp %4d", info.resampled_points, info.resampled_blacks);
+               if (info.padding_points)
+                       printf(" Pad %4d", info.padding_points);
+               printf("\n");
+               deliver_event(ctx, time, ftime, frames, 0);
+
+               pthread_mutex_lock(&ctx->display_mode_mutex);
+               display_mode = ctx->display_mode;
+               pthread_mutex_unlock(&ctx->display_mode_mutex);
+       }
+
+       olTraceDeinit(trace_ctx);
+
+       for(i = 0; i < OL_FRAMES_BUF; i++)
+               olRenderFrame(100);
+
+       olShutdown();
+       return NULL;
+}
+
+int player_init(PlayerCtx *ctx)
+{
+       ctx->display_mode = PAUSE;
+       ctx->settings_changed = 0;
+       pthread_mutex_init(&ctx->display_mode_mutex, NULL);
+
+       if (pthread_create(&ctx->display_thread, NULL, display_thread, ctx) != 0)
+               return -1;
+       return 0;
+}
+
+void playvid_init(void)
+{
+       av_register_all();
+}
+
+int playvid_open(PlayerCtx **octx, const char *filename)
+{
+       PlayerCtx *ctx = malloc(sizeof(PlayerCtx));
+       if (decoder_init(ctx, filename) < 0)
+               return -1;
+       ctx->display_mode = STOP;
+       ctx->cur_frame = NULL;
+       ctx->last_frame_pts = -1;
+       ctx->ev_cb = NULL;
+       *octx = ctx;
+       return 0;
+}
+
+void playvid_play(PlayerCtx *ctx)
+{
+       switch (ctx->display_mode) {
+               case STOP:
+                       player_init(ctx);
+                       // fallthrough
+               case PAUSE:
+                       pthread_mutex_lock(&ctx->display_mode_mutex);
+                       ctx->display_mode = PLAY;
+                       pthread_mutex_unlock(&ctx->display_mode_mutex);
+                       break;
+               case PLAY:
+                       break;
+       }
+}
+
+void playvid_pause(PlayerCtx *ctx)
+{
+       switch (ctx->display_mode) {
+               case STOP:
+                       player_init(ctx);
+                       break;
+               case PLAY:
+                       pthread_mutex_lock(&ctx->display_mode_mutex);
+                       ctx->display_mode = PAUSE;
+                       pthread_mutex_unlock(&ctx->display_mode_mutex);
+                       break;
+               case PAUSE:
+                       break;
+       }
+}
+
+void playvid_stop(PlayerCtx *ctx)
+{
+       if (ctx->display_mode == STOP)
+               return;
+
+       pthread_mutex_lock(&ctx->display_mode_mutex);
+       ctx->display_mode = STOP;
+       pthread_mutex_unlock(&ctx->display_mode_mutex);
+       pthread_join(ctx->display_thread, NULL);
+}
+
+void playvid_skip(PlayerCtx *ctx)
+{
+       if (ctx->display_mode != PAUSE)
+               return;
+
+       ctx->skip_frame = 1;
+}
+
+void playvid_update_settings(PlayerCtx *ctx, PlayerSettings *settings)
+{
+       if (ctx->display_mode != STOP)
+               pthread_mutex_lock(&ctx->settings_mutex);
+       ctx->settings = *settings;
+       ctx->settings_changed = 1;
+       if (ctx->display_mode != STOP)
+               pthread_mutex_unlock(&ctx->settings_mutex);
+}
+
+void playvid_set_eventcb(PlayerCtx* ctx, PlayerEventCb cb)
+{
+       ctx->ev_cb = cb;
+}
+
+double playvid_get_duration(PlayerCtx *ctx)
+{
+       return ctx->duration;
+}
+
+void playvid_seek(PlayerCtx *ctx, double pos)
+{
+       pthread_mutex_lock(&ctx->seek_mutex);
+       ctx->seek_pos = pos;
+       ctx->cur_seekid++;
+       pthread_cond_signal(&ctx->seek_cond);
+       pthread_mutex_unlock(&ctx->seek_mutex);
+}
\ No newline at end of file
diff --git a/tools/qplayvid/qplayvid.h b/tools/qplayvid/qplayvid.h
new file mode 100644 (file)
index 0000000..e5f4587
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2011 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef QPLAYVID_H
+#define QPLAYVID_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct PlayerCtx PlayerCtx;
+
+typedef struct {
+       int canny;
+       int splitthreshold;
+
+       int blur;
+       int threshold;
+       int threshold2;
+       int darkval;
+       int lightval;
+       int offset;
+       int decimation;
+
+       int minsize;
+       int startwait;
+       int endwait;
+       int dwell;
+       int offspeed;
+       int snap;
+       int minrate;
+       int overscan;
+
+       int volume;
+} PlayerSettings;
+
+typedef struct {
+       float time;
+       float ftime;
+       int frames;
+       int objects;
+       int points;
+       int resampled_points;
+       int resampled_blacks;
+       int padding_points;
+       double pts;
+       int ended;
+} PlayerEvent;
+
+typedef void (*PlayerEventCb)(PlayerEvent *ev);
+
+void playvid_init(void);
+int playvid_open(PlayerCtx **octx, const char *filename);
+void playvid_set_eventcb(PlayerCtx *ctx, PlayerEventCb cb);
+void playvid_play(PlayerCtx *ctx);
+void playvid_pause(PlayerCtx *ctx);
+void playvid_stop(PlayerCtx *ctx);
+void playvid_skip(PlayerCtx *ctx);
+void playvid_update_settings(PlayerCtx *ctx, PlayerSettings *settings);
+double playvid_get_duration(PlayerCtx *ctx);
+void playvid_seek(PlayerCtx *ctx, double pos);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif
diff --git a/tools/qplayvid/qplayvid_gui.cpp b/tools/qplayvid/qplayvid_gui.cpp
new file mode 100644 (file)
index 0000000..d339f5f
--- /dev/null
@@ -0,0 +1,508 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2011 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "qplayvid_gui.moc"
+
+#include <QApplication>
+#include <QMessageBox>
+#include <QVBoxLayout>
+#include <QHBoxLayout>
+#include <QFormLayout>
+#include <QLabel>
+#include <QSpinBox>
+#include <QGroupBox>
+#include <QDebug>
+#include <QStatusBar>
+#include <QFile>
+#include <QTextStream>
+
+#define Setting(name, ...) \
+       addSetting(new PlayerSetting(this, #name, settings.name, __VA_ARGS__))
+
+PlayerUI::PlayerUI(QWidget *parent)
+       : QMainWindow(parent)
+       , player(NULL)
+       , playing(false)
+{
+       QWidget *central = new QWidget(this);
+       QVBoxLayout *winbox = new QVBoxLayout();
+
+       resize(800, 0);
+       setWindowTitle("OpenLase Video Player");
+
+       b_playstop = new QPushButton("Play");
+       connect(b_playstop, SIGNAL(clicked(bool)), this, SLOT(playStopClicked()));
+       b_pause = new QPushButton("Pause");
+       b_pause->setCheckable(true);
+       connect(b_pause, SIGNAL(toggled(bool)), this, SLOT(pauseToggled(bool)));
+       b_step = new QPushButton("Skip");
+       b_step->setEnabled(false);
+       connect(b_step, SIGNAL(clicked(bool)), this, SLOT(stepClicked()));
+       b_load = new QPushButton("Load");
+       connect(b_load, SIGNAL(clicked(bool)), this, SLOT(loadSettings()));
+       b_save = new QPushButton("Save");
+       connect(b_save, SIGNAL(clicked(bool)), this, SLOT(saveSettings()));
+
+       loadDefaults();
+
+       sl_time = new QSlider(Qt::Horizontal);
+       sl_time->setMinimum(0);
+       sl_time->setMaximum(100000);
+       connect(sl_time, SIGNAL(sliderPressed()), this, SLOT(timePressed()));
+       connect(sl_time, SIGNAL(sliderReleased()), this, SLOT(timeReleased()));
+       connect(sl_time, SIGNAL(sliderMoved(int)), this, SLOT(timeMoved(int)));
+
+       winbox->addWidget(sl_time);
+
+       QHBoxLayout *controlsbox = new QHBoxLayout();
+       controlsbox->addWidget(b_playstop);
+       controlsbox->addWidget(b_pause);
+       controlsbox->addWidget(b_step);
+       controlsbox->addWidget(b_load);
+       controlsbox->addWidget(b_save);
+       controlsbox->addWidget(new QLabel("Vol:"));
+       controlsbox->addLayout(Setting(volume, 0, 100, 1, false));
+
+       QHBoxLayout *settingsbox = new QHBoxLayout();
+       QFormLayout *tracebox = new QFormLayout();
+       QGroupBox *tracegroup = new QGroupBox("Tracing settings");
+       QHBoxLayout *modebox = new QHBoxLayout();
+
+       r_thresh = new QRadioButton("Threshold");
+       r_canny = new QRadioButton("Canny");
+       c_splitthresh = new QCheckBox("Split threshold");
+
+       connect(r_thresh, SIGNAL(clicked(bool)), this, SLOT(modeChanged()));
+       connect(r_canny, SIGNAL(clicked(bool)), this, SLOT(modeChanged()));
+       connect(c_splitthresh, SIGNAL(clicked(bool)), this, SLOT(splitChanged()));
+
+       modebox->addWidget(r_thresh);
+       modebox->addWidget(r_canny);
+       modebox->addWidget(c_splitthresh);
+       tracebox->addRow("Mode",                        modebox);
+       tracebox->addRow("Blur",                        Setting(blur,           0, 500, 5));
+       tracebox->addRow("Threshold",           Setting(threshold,      0, 500, 1));
+       tracebox->addRow("Threshold 2",         Setting(threshold2, 0, 500, 1));
+       tracebox->addRow("Dark value",          Setting(darkval,        0, 255, 10));
+       tracebox->addRow("Light value",         Setting(lightval,       0, 255, 10));
+       tracebox->addRow("Border offset",       Setting(offset,         0, 50, 5));
+       tracebox->addRow("Decimation",          Setting(decimation, 1, 15, 1));
+       splitChanged();
+       tracegroup->setLayout(tracebox);
+
+       QFormLayout *renderbox = new QFormLayout();
+       QGroupBox *rendergroup = new QGroupBox("Render settings");
+       renderbox->addRow("Min. size",          Setting(minsize,        0, 100, 1));
+       renderbox->addRow("Start wait",         Setting(startwait,      0, 20, 1));
+       renderbox->addRow("End wait",           Setting(endwait,        0, 20, 1));
+       renderbox->addRow("Dwell",                      Setting(dwell,          0, 20, 1));
+       renderbox->addRow("Off speed",          Setting(offspeed,       10, 100, 1));
+       renderbox->addRow("Snap",                       Setting(snap,           0, 100, 1));
+       renderbox->addRow("Min. rate",          Setting(minrate,        0, 40, 1));
+       renderbox->addRow("Overscan",           Setting(overscan,       0, 100, 1));
+       rendergroup->setLayout(renderbox);
+
+       settingsbox->addWidget(tracegroup);
+       settingsbox->addWidget(rendergroup);
+
+       winbox->addLayout(controlsbox);
+       winbox->addLayout(settingsbox);
+
+       sb_fps = new QLabel();
+       sb_afps = new QLabel();
+       sb_objects = new QLabel();
+       sb_points = new QLabel();
+       sb_pts = new QLabel();
+
+       statusBar()->addWidget(sb_fps);
+       statusBar()->addWidget(sb_afps);
+       statusBar()->addWidget(sb_objects);
+       statusBar()->addWidget(sb_points);
+       statusBar()->addWidget(sb_pts);
+
+       updateSettingsUI();
+       central->setLayout(winbox);
+       setCentralWidget(central);
+}
+
+void PlayerUI::modeChanged()
+{
+       if (r_thresh->isChecked()) {
+               settings.canny = false;
+               findSetting("threshold")->setMaximum(255);
+               findSetting("threshold2")->setMaximum(255);
+               c_splitthresh->setEnabled(true);
+               splitChanged();
+       } else {
+               settings.canny = true;
+               c_splitthresh->setEnabled(false);
+               findSetting("threshold")->setMaximum(500);
+               findSetting("threshold2")->setMaximum(500);
+               findSetting("threshold2")->setEnabled(true);
+               findSetting("darkval")->setEnabled(false);
+               findSetting("lightval")->setEnabled(false);
+               findSetting("offset")->setEnabled(false);
+       }
+       updateSettings();
+}
+
+void PlayerUI::splitChanged()
+{
+       bool split = c_splitthresh->isChecked();
+       findSetting("threshold2")->setEnabled(split);
+       findSetting("darkval")->setEnabled(split);
+       findSetting("lightval")->setEnabled(split);
+       findSetting("offset")->setEnabled(split);
+       settings.splitthreshold = split;
+       updateSettings();
+}
+
+PlayerSetting *PlayerUI::findSetting(const QString &name)
+{
+       foreach(PlayerSetting *s, lsettings)
+               if (s->name == name)
+                       return s;
+       qDebug() << "Could not find setting:" << name;
+       return NULL;
+}
+
+QLayout *PlayerUI::addSetting(PlayerSetting *setting)
+{
+       lsettings << setting;
+       connect(setting, SIGNAL(valueChanged(int)), this, SLOT(updateSettings()));
+       return setting->layout;
+}
+
+void PlayerUI::loadDefaults()
+{
+       settings.canny = 1;
+       settings.splitthreshold = 0;
+       settings.blur = 100;
+       settings.threshold = 30;
+       settings.threshold2 = 20;
+       settings.darkval = 96;
+       settings.lightval = 160;
+       settings.offset = 0;
+       settings.decimation = 3;
+       settings.minsize = 10;
+       settings.startwait = 8;
+       settings.endwait = 3;
+       settings.dwell = 2;
+       settings.offspeed = 50;
+       settings.snap = 10;
+       settings.minrate = 15;
+       settings.overscan = 0;
+       settings.volume = 50;
+}
+
+void PlayerUI::updateSettingsUI()
+{
+
+       foreach(PlayerSetting *s, lsettings)
+               s->updateValue();
+
+       c_splitthresh->setChecked(settings.splitthreshold);
+       splitChanged();
+       r_thresh->setChecked(!settings.canny);
+       r_canny->setChecked(settings.canny);
+       modeChanged();
+
+       if (player)
+               playvid_update_settings(player, &this->settings);
+}
+
+void PlayerUI::open(const char *filename)
+{
+       qDebug() << "open:" << filename;
+       this->filename = filename;
+       loadSettings();
+       Q_ASSERT(playvid_open(&player, filename) == 0);
+       playvid_set_eventcb(player, player_event_cb);
+       playvid_update_settings(player, &this->settings);
+       sl_time->setMaximum((int)(1000 * playvid_get_duration(player)));
+}
+
+void PlayerUI::playStopClicked(void)
+{
+       if (!player)
+               return;
+
+       if (playing) {
+               b_playstop->setText("Play");
+               playvid_stop(player);
+               playing = false;
+       } else {
+               b_playstop->setText("Stop");
+               if (b_pause->isChecked())
+                       playvid_pause(player);
+               else
+                       playvid_play(player);
+               playing = true;
+       }
+}
+
+void PlayerUI::pauseToggled(bool pause)
+{
+       if (!player)
+               return;
+       b_step->setEnabled(pause);
+       if (playing) {
+               if (pause)
+                       playvid_pause(player);
+               else
+                       playvid_play(player);
+       }
+}
+
+void PlayerUI::stepClicked(void)
+{
+       if (!player)
+               return;
+
+       if (playing && b_pause->isChecked()) {
+               playvid_skip(player);
+       }
+}
+
+void PlayerUI::updateSettings()
+{
+       if (player)
+               playvid_update_settings(player, &this->settings);
+}
+
+void PlayerUI::playEvent(PlayerEvent *e)
+{
+       qDebug() << "Objects:" << e->objects;
+       sb_fps->setText(QString("FPS: %1").arg(1.0/e->ftime, 0, 'f', 2));
+       sb_afps->setText(QString("Avg: %1").arg(e->frames/e->time, 0, 'f', 2));
+       sb_objects->setText(QString("Obj: %1").arg(e->objects));
+       QString points = QString("Pts: %1").arg(e->points);
+       if (e->padding_points)
+               points += QString(" (pad %1)").arg(e->resampled_points);
+       else if (e->resampled_points)
+               points += QString(" (Rp %1 Bp %2)").arg(e->resampled_points).arg(e->resampled_blacks);
+       sb_points->setText(points);
+       if (!sl_time->isSliderDown()) {
+               if (e->ended) {
+                       if (playing) {
+                               playvid_stop(player);
+                               playing = false;
+                               b_playstop->setText("Play");
+                               sl_time->setValue(0);
+                               playvid_seek(player, 0);
+                       }
+               } else {
+                       sl_time->setValue((int)(e->pts * 1000));
+               }
+       }
+}
+
+void PlayerUI::timePressed()
+{
+       if (!player || !playing)
+               return;
+       if (!b_pause->isChecked())
+               playvid_pause(player);
+}
+
+void PlayerUI::timeReleased()
+{
+       if (!player || !playing)
+               return;
+       if (!b_pause->isChecked()) {
+               playvid_play(player);
+       }
+}
+
+void PlayerUI::timeMoved(int value)
+{
+       if (!player)
+               return;
+       qDebug() << "timeMoved" << value;
+       playvid_seek(player, value / 1000.0);
+}
+
+bool PlayerUI::event(QEvent *e)
+{
+       if (e->type() == QPlayerEvent::type) {
+               QPlayerEvent *ev = dynamic_cast<QPlayerEvent*>(e);
+               playEvent(&ev->data);
+               return true;
+       } else {
+               return QMainWindow::event(e);
+       }
+}
+
+void PlayerUI::loadSettings(void)
+{
+       QFile file(filename + ".cfg");
+       if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+               qDebug() << "Settings load failed";
+               return;
+       }
+       QTextStream ts(&file);
+       QString line;
+       while (!(line = ts.readLine()).isNull()) {
+               QStringList l = line.split("=");
+               if (l.length() != 2)
+                       continue;
+               qDebug() << "Got setting" << l[0] << "value" << l[1];
+               QString name = l[0];
+               int val = l[1].toInt();
+               if (name == "canny") {
+                       settings.canny = val;
+               } else if (name == "splitthreshold") {
+                       settings.splitthreshold = val;
+               } else {
+                       PlayerSetting *s = findSetting(name);
+                       if (!s)
+                               qDebug() << "Unknown setting" << name;
+                       else
+                               s->setValue(val);
+               }
+       }
+       updateSettingsUI();
+       file.close();
+}
+
+void PlayerUI::saveSettings(void)
+{
+       QFile file(filename + ".cfg");
+       if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
+               qDebug() << "Settings save failed";
+               return;
+       }
+       QTextStream ts(&file);
+       ts << QString("canny=%1\n").arg(settings.canny);
+       ts << QString("splitthreshold=%1\n").arg(settings.splitthreshold);
+       foreach(PlayerSetting *s, lsettings)
+               ts << QString("%1=%2\n").arg(s->name).arg(s->value);
+       ts.flush();
+       file.close();
+}
+
+PlayerUI::~PlayerUI()
+{
+       if (player) {
+               playvid_stop(player);
+       }
+}
+
+PlayerSetting::PlayerSetting ( QObject* parent, const QString &name, int& value,
+                                                          int min, int max, int step, bool addbox )
+       : QObject (parent)
+       , value(value)
+       , name(name)
+       , i_value(value)
+       , enabled(true)
+{
+       layout = new QHBoxLayout;
+       slider = new QSlider(Qt::Horizontal);
+       slider->setMinimum(min);
+       slider->setMaximum(max);
+       slider->setSingleStep(step);
+       slider->setPageStep(step * 10);
+       slider->setValue(value);
+       connect(slider, SIGNAL(valueChanged(int)), this, SLOT(setValue(int)));
+       layout->addWidget(slider);
+       if (addbox) {
+               spin = new QSpinBox;
+               spin->setMinimum(min);
+               spin->setMaximum(max);
+               spin->setSingleStep(step);
+               spin->setValue(value);
+               connect(spin, SIGNAL(valueChanged(int)), this, SLOT(setValue(int)));
+               layout->addWidget(spin);
+       } else {
+               spin = NULL;
+       }
+}
+
+void PlayerSetting::setValue(int newValue)
+{
+       if (newValue == this->i_value)
+               return;
+       this->i_value = newValue;
+       updateValue();
+       emit valueChanged(newValue);
+}
+
+void PlayerSetting::updateValue()
+{
+       slider->setValue(this->i_value);
+       if (spin)
+               spin->setValue(this->i_value);
+}
+
+void PlayerSetting::setEnabled(bool enabled)
+{
+       if (enabled == this->enabled)
+               return;
+
+       this->enabled = enabled;
+       slider->setEnabled(enabled);
+       if (spin)
+               spin->setEnabled(enabled);
+}
+
+void PlayerSetting::setMaximum(int max)
+{
+       if (i_value > max)
+               setValue(max);
+       slider->setMaximum(max);
+       if (spin)
+               spin->setMaximum(max);
+}
+
+void PlayerSetting::setMinimum(int min)
+{
+       if (i_value < min)
+               setValue(min);
+       slider->setMinimum(min);
+       if (spin)
+               spin->setMinimum(min);
+}
+
+QPlayerEvent::QPlayerEvent(PlayerEvent data)
+       : QEvent(type)
+       , data(data)
+{
+}
+
+QApplication *app;
+PlayerUI *ui;
+
+void player_event_cb(PlayerEvent *ev)
+{
+       QPlayerEvent *qev = new QPlayerEvent(*ev);
+       app->postEvent(ui, qev);
+}
+
+int main (int argc, char *argv[])
+{
+       app = new QApplication(argc, argv);
+       playvid_init();
+       ui = new PlayerUI();
+       ui->open(argv[1]);
+       ui->show();
+       int ret = app->exec();
+       delete ui;
+       delete app;
+       return ret;
+}
diff --git a/tools/qplayvid/qplayvid_gui.h b/tools/qplayvid/qplayvid_gui.h
new file mode 100644 (file)
index 0000000..9182126
--- /dev/null
@@ -0,0 +1,126 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2011 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef QPLAYVID_GUI_H
+#define QPLAYVID_GUI_H
+
+#include "qplayvid.h"
+
+#include <QMainWindow>
+#include <QPushButton>
+#include <QSlider>
+#include <QSpinBox>
+#include <QRadioButton>
+#include <QCheckBox>
+#include <QLabel>
+#include <qcoreevent.h>
+
+class PlayerSetting : public QObject
+{
+       Q_OBJECT
+
+public:
+       PlayerSetting(QObject *parent, const QString &name, int& value,
+                                 int min, int max, int step = 1, bool addbox = true);
+       const int& value;
+       QLayout *layout;
+       QString name;
+
+       void updateValue();
+       void setMinimum(int min);
+       void setMaximum(int min);
+
+signals:
+       void valueChanged(int newValue);
+
+public slots:
+       void setValue(int value);
+       void setEnabled(bool enabled);
+
+private:
+       int& i_value;
+       bool enabled;
+       QSlider *slider;
+       QSpinBox *spin;
+};
+
+class QPlayerEvent : public QEvent
+{
+public:
+       static const QEvent::Type type = QEvent::User;
+       QPlayerEvent(PlayerEvent data);
+       PlayerEvent data;
+};
+
+class PlayerUI : public QMainWindow
+{
+       Q_OBJECT
+
+public:
+       PlayerUI(QWidget *parent = 0);
+       ~PlayerUI();
+       void open(const char *filename);
+private:
+       QString filename;
+       PlayerCtx *player;
+       bool playing;
+       PlayerSettings settings;
+       QList<PlayerSetting *> lsettings;
+
+       QSlider *sl_time;
+
+       QPushButton *b_playstop;
+       QPushButton *b_pause;
+       QPushButton *b_step;
+       QPushButton *b_load;
+       QPushButton *b_save;
+
+       QRadioButton *r_thresh;
+       QRadioButton *r_canny;
+       QCheckBox *c_splitthresh;
+
+       QLabel *sb_fps;
+       QLabel *sb_afps;
+       QLabel *sb_objects;
+       QLabel *sb_points;
+       QLabel *sb_pts;
+
+       QLayout *addSetting(PlayerSetting *setting);
+       PlayerSetting *findSetting(const QString &name);
+       void loadDefaults(void);
+       void playEvent(PlayerEvent *e);
+       bool event(QEvent *e);
+private slots:
+       void modeChanged();
+       void splitChanged();
+       void updateSettings();
+       void updateSettingsUI();
+       void playStopClicked();
+       void pauseToggled(bool pause);
+       void stepClicked();
+       void timePressed();
+       void timeReleased();
+       void timeMoved(int value);
+       void loadSettings();
+       void saveSettings();
+};
+
+void player_event_cb(PlayerEvent *ev);
+
+#endif
\ No newline at end of file