qplayvid: new Qt-based GUI video player

This commit is contained in:
Hector Martin 2011-03-20 08:45:54 +01:00
parent f1140ec09d
commit ea644d993a
6 changed files with 1704 additions and 0 deletions

View 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)

View file

@ -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()

955
tools/qplayvid/qplayvid.c Normal file
View file

@ -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);
}

83
tools/qplayvid/qplayvid.h Normal file
View file

@ -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

View file

@ -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;
}

View file

@ -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