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