--- /dev/null
+/*
+ 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(¶ms, 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(¶ms);
+
+ 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(¶ms);
+
+ 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
--- /dev/null
+/*
+ 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;
+}