qplayvid: new Qt-based GUI video player
This commit is contained in:
parent
f1140ec09d
commit
ea644d993a
6 changed files with 1704 additions and 0 deletions
|
@ -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)
|
||||
|
|
30
tools/qplayvid/CMakeLists.txt
Normal file
30
tools/qplayvid/CMakeLists.txt
Normal 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
955
tools/qplayvid/qplayvid.c
Normal 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(¶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);
|
||||
}
|
83
tools/qplayvid/qplayvid.h
Normal file
83
tools/qplayvid/qplayvid.h
Normal 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
|
508
tools/qplayvid/qplayvid_gui.cpp
Normal file
508
tools/qplayvid/qplayvid_gui.cpp
Normal 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;
|
||||
}
|
126
tools/qplayvid/qplayvid_gui.h
Normal file
126
tools/qplayvid/qplayvid_gui.h
Normal 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
|
Loading…
Add table
Reference in a new issue