From 17f5c32a1915194a2adb15cbbb76c7e2a0d75722 Mon Sep 17 00:00:00 2001 From: Stefan Seyfried Date: Sat, 4 May 2013 11:34:24 +0200 Subject: [PATCH 01/12] acinclude: fix for pkg-config 0.28 behaviour Old pkg-config always retured a non-empty string for --cflags. It always contained at least one bogus space. New pkg-config does not do that. Do not rely on --cflags being non-empty. --- acinclude.m4 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/acinclude.m4 b/acinclude.m4 index 0259d90..b64b2a7 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -225,6 +225,7 @@ if $PKG_CONFIG --exists "$2" ; then AC_MSG_RESULT(yes) $1_CFLAGS=$($PKG_CONFIG --cflags "$2") $1_LIBS=$($PKG_CONFIG --libs "$2") + $1_EXISTS=yes else AC_MSG_RESULT(no) fi @@ -235,7 +236,7 @@ AC_SUBST($1_LIBS) AC_DEFUN([TUXBOX_APPS_LIB_PKGCONFIG],[ _TUXBOX_APPS_LIB_PKGCONFIG($1,$2) -if test -z "$$1_CFLAGS" ; then +if test x"$$1_EXISTS" != xyes; then AC_MSG_ERROR([could not find package $2]); fi ]) From 53dc220e636aca085d82d4ce8c8057b2903f7482 Mon Sep 17 00:00:00 2001 From: Stefan Seyfried Date: Sat, 4 May 2013 11:47:15 +0200 Subject: [PATCH 02/12] generic-pc: add somewhat working audio decoder --- generic-pc/Makefile.am | 2 +- generic-pc/audio.cpp | 205 ++++++++++++++++++++++++++++++++++++++--- generic-pc/audio_lib.h | 9 +- 3 files changed, 200 insertions(+), 16 deletions(-) diff --git a/generic-pc/Makefile.am b/generic-pc/Makefile.am index d85bc75..0b8b4f8 100644 --- a/generic-pc/Makefile.am +++ b/generic-pc/Makefile.am @@ -3,8 +3,8 @@ INCLUDES = \ noinst_LTLIBRARIES = libgeneric.la +AM_CPPFLAGS = -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS AM_CXXFLAGS = -fno-rtti -fno-exceptions -fno-strict-aliasing -AM_LDFLAGS = -lpthread libgeneric_la_SOURCES = \ hardware_caps.c \ diff --git a/generic-pc/audio.cpp b/generic-pc/audio.cpp index 98494e3..c68fbd1 100644 --- a/generic-pc/audio.cpp +++ b/generic-pc/audio.cpp @@ -1,32 +1,66 @@ -/* dummy cAudio implementation that does nothing for now */ +/* + * (C) 2010-2013 Stefan Seyfried + * + * 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 of the License, or + * (at your option) any later version. + * + * 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, see . + * + * cAudio implementation with decoder. + * uses libao for output + * ffmpeg for demuxing / decoding + */ #include #include -#include -#include -#include -#include - -#include #include "audio_lib.h" +#include "dmx_lib.h" #include "lt_debug.h" -#define AUDIO_DEVICE "/dev/dvb/adapter0/audio0" -#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_AUDIO, this, args) -#define lt_info(args...) _lt_info(TRIPLE_DEBUG_AUDIO, this, args) +#define lt_debug(args...) _lt_debug(HAL_DEBUG_AUDIO, this, args) +#define lt_info(args...) _lt_info(HAL_DEBUG_AUDIO, this, args) -#include +#include + +extern "C" { +#include +#include +} +/* ffmpeg buf 2k */ +#define INBUF_SIZE 0x0800 +/* my own buf 16k */ +#define DMX_BUF_SZ 0x4000 cAudio * audioDecoder = NULL; +extern cDemux *audioDemux; +static uint8_t *dmxbuf = NULL; +static int bufpos; + +static cAudio *gThiz = NULL; cAudio::cAudio(void *, void *, void *) { + thread_started = false; + dmxbuf = (uint8_t *)malloc(DMX_BUF_SZ); + bufpos = 0; + gThiz = this; + ao_initialize(); } cAudio::~cAudio(void) { closeDevice(); + free(dmxbuf); + ao_shutdown(); } void cAudio::openDevice(void) @@ -53,13 +87,21 @@ int cAudio::setVolume(unsigned int left, unsigned int right) int cAudio::Start(void) { - lt_debug("%s\n", __func__); + lt_info("%s >\n", __func__); + OpenThreads::Thread::start(); + lt_info("%s <\n", __func__); return 0; } int cAudio::Stop(void) { - lt_debug("%s\n", __func__); + lt_info("%s >\n", __func__); + if (thread_started) + { + thread_started = false; + OpenThreads::Thread::join(); + } + lt_info("%s <\n", __func__); return 0; } @@ -78,7 +120,7 @@ void cAudio::SetStreamType(AUDIO_FORMAT type) lt_debug("%s %d\n", __func__, type); }; -int cAudio::setChannel(int channel) +int cAudio::setChannel(int /*channel*/) { return 0; }; @@ -140,3 +182,138 @@ void cAudio::setBypassMode(bool disable) { lt_debug("%s %d\n", __func__, disable); } + +static int _my_read(void *, uint8_t *buf, int buf_size) +{ + return gThiz->my_read(buf, buf_size); +} + +int cAudio::my_read(uint8_t *buf, int buf_size) +{ + int tmp = 0; + if (audioDecoder && bufpos < DMX_BUF_SZ - 4096) { + while (bufpos < buf_size && ++tmp < 20) { /* retry max 20 times */ + int ret = audioDemux->Read(dmxbuf + bufpos, DMX_BUF_SZ - bufpos, 10); + if (ret > 0) + bufpos += ret; + if (! thread_started) + break; + } + } + if (bufpos == 0) + return 0; + //lt_info("%s buf_size %d bufpos %d th %d tmp %d\n", __func__, buf_size, bufpos, thread_started, tmp); + if (bufpos > buf_size) { + memcpy(buf, dmxbuf, buf_size); + memmove(dmxbuf, dmxbuf + buf_size, bufpos - buf_size); + bufpos -= buf_size; + return buf_size; + } + memcpy(buf, dmxbuf, bufpos); + tmp = bufpos; + bufpos = 0; + return tmp; +} + +void cAudio::run() +{ + lt_info("====================== start decoder thread ================================\n"); + /* libavcodec & friends */ + av_register_all(); + + AVCodec *codec; + AVCodecContext *c= NULL; + AVFormatContext *avfc = NULL; + AVInputFormat *inp; + AVFrame *frame; + uint8_t *inbuf = (uint8_t *)av_malloc(INBUF_SIZE); + AVPacket avpkt; + int ret, driver; + /* libao */ + ao_info *ai; + ao_device *adevice; + ao_sample_format sformat; + av_init_packet(&avpkt); + inp = av_find_input_format("mpegts"); + AVIOContext *pIOCtx = avio_alloc_context(inbuf, INBUF_SIZE, // internal Buffer and its size + 0, // bWriteable (1=true,0=false) + NULL, // user data; will be passed to our callback functions + _my_read, // read callback + NULL, // write callback + NULL); // seek callback + avfc = avformat_alloc_context(); + avfc->pb = pIOCtx; + avfc->iformat = inp; + avfc->probesize = 188*5; + thread_started = true; + + if (avformat_open_input(&avfc, NULL, inp, NULL) < 0) { + lt_info("%s: avformat_open_input() failed.\n", __func__); + goto out; + } + ret = avformat_find_stream_info(avfc, NULL); + lt_debug("%s: avformat_find_stream_info: %d\n", __func__, ret); + if (avfc->nb_streams != 1) + { + lt_info("%s: nb_streams: %d, should be 1!\n", __func__, avfc->nb_streams); + goto out; + } + if (avfc->streams[0]->codec->codec_type != AVMEDIA_TYPE_AUDIO) + lt_info("%s: stream 0 no audio codec? 0x%x\n", __func__, avfc->streams[0]->codec->codec_type); + + c = avfc->streams[0]->codec; + codec = avcodec_find_decoder(c->codec_id); + if (!codec) { + lt_info("%s: Codec not found\n", __func__); + goto out; + } + if (avcodec_open2(c, codec, NULL) < 0) { + lt_info("%s: avcodec_open2() failed\n", __func__); + goto out; + } + frame = avcodec_alloc_frame(); + if (!frame) { + lt_info("%s: avcodec_alloc_frame failed\n", __func__); + goto out2; + } + driver = ao_default_driver_id(); + sformat.bits = 16; + sformat.channels = c->channels; + sformat.rate = c->sample_rate; + sformat.byte_format = AO_FMT_NATIVE; + sformat.matrix = 0; + adevice = ao_open_live(driver, &sformat, NULL); + ai = ao_driver_info(driver); + lt_info("libao driver: %d name '%s' short '%s' author '%s'\n", + driver, ai->name, ai->short_name, ai->author); +#if 0 + lt_info(" driver options:"); + for (int i = 0; i < ai->option_count; ++i) + fprintf(stderr, " %s", ai->options[i]); + fprintf(stderr, "\n"); +#endif + lt_info("codec params: sample_fmt %d sample_rate %d channels %d\n", + c->sample_fmt, c->sample_rate, c->channels); + while (thread_started) { + int gotframe = 0; + if (av_read_frame(avfc, &avpkt) < 0) + break; + avcodec_decode_audio4(c, frame, &gotframe, &avpkt); + if (gotframe && thread_started) { + int64_t pts = av_frame_get_best_effort_timestamp(frame); + lt_debug("%s: pts 0x%" PRIx64 " %" PRId64 " %3f\n", __func__, pts, pts, pts/90000.0); + curr_pts = pts; + ao_play(adevice, (char*)frame->extended_data[0], frame->linesize[0]); + } + av_free_packet(&avpkt); + } + ao_close(adevice); /* can take long :-( */ + avcodec_free_frame(&frame); + out2: + avcodec_close(c); + out: + avformat_close_input(&avfc); + av_free(pIOCtx->buffer); + av_free(pIOCtx); + lt_info("======================== end decoder thread ================================\n"); +} diff --git a/generic-pc/audio_lib.h b/generic-pc/audio_lib.h index 871060d..36ad41a 100644 --- a/generic-pc/audio_lib.h +++ b/generic-pc/audio_lib.h @@ -3,6 +3,8 @@ #ifndef _AUDIO_LIB_H_ #define _AUDIO_LIB_H_ +#include +#include #include "../common/cs_types.h" typedef enum @@ -36,7 +38,7 @@ typedef enum AUDIO_FMT_ADVANCED = AUDIO_FMT_MLP } AUDIO_FORMAT; -class cAudio +class cAudio : public OpenThreads::Thread { friend class cPlayback; private: @@ -50,18 +52,22 @@ class cAudio AUDIO_FORMAT StreamType; AUDIO_SYNC_MODE SyncMode; bool started; + bool thread_started; int volume; + int64_t curr_pts; void openDevice(void); void closeDevice(void); int do_mute(bool enable, bool remember); void setBypassMode(bool disable); + void run(); public: /* construct & destruct */ cAudio(void *, void *, void *); ~cAudio(void); + int64_t getPts() { return curr_pts; } void *GetHandle() { return NULL; }; /* shut up */ @@ -92,6 +98,7 @@ class cAudio void SetSpdifDD(bool enable); void ScheduleMute(bool On); void EnableAnalogOut(bool enable); + int my_read(uint8_t *buf, int buf_size); }; #endif From 9541c0ac1ea415705dd1d53bb70578ab2e827822 Mon Sep 17 00:00:00 2001 From: Stefan Seyfried Date: Sat, 4 May 2013 12:04:53 +0200 Subject: [PATCH 03/12] generic-pc: add somewhat working video decoder --- generic-pc/video.cpp | 269 ++++++++++++++++++++++++++++++++++++----- generic-pc/video_lib.h | 32 ++++- 2 files changed, 267 insertions(+), 34 deletions(-) diff --git a/generic-pc/video.cpp b/generic-pc/video.cpp index 4ca5f66..3940686 100644 --- a/generic-pc/video.cpp +++ b/generic-pc/video.cpp @@ -15,57 +15,62 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA + * + * cVideo implementation with decoder. + * uses ffmpeg for demuxing / decoding + * decoded frames are stored in SWFramebuffer class + * + * TODO: buffer handling surely needs some locking... */ -/* this is a dummy implementation with no functionality at all */ - -#include -#include -#include -#include #include -#include -#include -#include - #include #include #include -#include +extern "C" { +#include +#include +} + +/* ffmpeg buf 32k */ +#define INBUF_SIZE 0x8000 +/* my own buf 256k */ +#define DMX_BUF_SZ 0x20000 -#include -#include #include "video_lib.h" -#define VIDEO_DEVICE "/dev/dvb/adapter0/video0" +#include "dmx_lib.h" #include "lt_debug.h" #define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_VIDEO, this, args) #define lt_info(args...) _lt_info(TRIPLE_DEBUG_VIDEO, this, args) -#define lt_debug_c(args...) _lt_debug(TRIPLE_DEBUG_VIDEO, NULL, args) -#define lt_info_c(args...) _lt_info(TRIPLE_DEBUG_VIDEO, NULL, args) -#define fop(cmd, args...) ({ \ - int _r; \ - if (fd >= 0) { \ - if ((_r = ::cmd(fd, args)) < 0) \ - lt_info(#cmd"(fd, "#args")\n"); \ - else \ - lt_debug(#cmd"(fd, "#args")\n");\ - } \ - else { _r = fd; } \ - _r; \ -}) - -cVideo * videoDecoder = NULL; +cVideo *videoDecoder = NULL; +extern cDemux *videoDemux; int system_rev = 0; +static uint8_t *dmxbuf; +static int bufpos; + cVideo::cVideo(int, void *, void *) { - lt_debug("%s\n", __FUNCTION__); + lt_debug("%s\n", __func__); + av_register_all(); + dmxbuf = (uint8_t *)malloc(DMX_BUF_SZ); + bufpos = 0; + thread_running = false; + w_h_changed = false; + dec_w = dec_h = 0; + buf_num = 0; + buf_in = 0; + buf_out = 0; + firstpts = AV_NOPTS_VALUE; } cVideo::~cVideo(void) { + Stop(); + /* ouch :-( */ + videoDecoder = NULL; } int cVideo::setAspectRatio(int, int) @@ -85,11 +90,21 @@ int cVideo::setCroppingMode(int) int cVideo::Start(void *, unsigned short, unsigned short, void *) { + lt_info("%s running %d >\n", __func__, thread_running); + if (!thread_running) + OpenThreads::Thread::start(); + lt_info("%s running %d <\n", __func__, thread_running); return 0; } int cVideo::Stop(bool) { + lt_info("%s running %d >\n", __func__, thread_running); + if (thread_running) { + thread_running = false; + OpenThreads::Thread::join(); + } + lt_info("%s running %d <\n", __func__, thread_running); return 0; } @@ -135,9 +150,9 @@ void cVideo::Pig(int, int, int, int, int, int) void cVideo::getPictureInfo(int &width, int &height, int &rate) { - width = 720; - height = 576; - rate = 50; + width = dec_w; + height = dec_h; + rate = dec_r; } void cVideo::SetSyncMode(AVSYNC_TYPE) @@ -148,3 +163,191 @@ int cVideo::SetStreamType(VIDEO_FORMAT) { return 0; } + +cVideo::SWFramebuffer *cVideo::getDecBuf(void) +{ + if (buf_num == 0) + return NULL; + SWFramebuffer *p = &buffers[buf_out]; + buf_out++; + buf_num--; + buf_out %= VDEC_MAXBUFS; + return p; +} + +static int my_read(void *, uint8_t *buf, int buf_size) +{ + int tmp = 0; + if (videoDecoder && bufpos < DMX_BUF_SZ - 4096) { + while (bufpos < buf_size && ++tmp < 20) { /* retry max 20 times */ + int ret = videoDemux->Read(dmxbuf + bufpos, DMX_BUF_SZ - bufpos, 20); + if (ret > 0) + bufpos += ret; + } + } + if (bufpos == 0) + return 0; + if (bufpos > buf_size) { + memcpy(buf, dmxbuf, buf_size); + memmove(dmxbuf, dmxbuf + buf_size, bufpos - buf_size); + bufpos -= buf_size; + return buf_size; + } + memcpy(buf, dmxbuf, bufpos); + tmp = bufpos; + bufpos = 0; + return tmp; +} + +void cVideo::run(void) +{ + lt_info("====================== start decoder thread ================================\n"); + AVCodec *codec; + AVCodecContext *c= NULL; + AVFormatContext *avfc = NULL; + AVInputFormat *inp; + AVFrame *frame, *rgbframe; + uint8_t *inbuf = (uint8_t *)av_malloc(INBUF_SIZE); + AVPacket avpkt; + + time_t warn_r = 0; /* last read error */ + time_t warn_d = 0; /* last decode error */ + + bufpos = 0; + buf_num = 0; + buf_in = 0; + buf_out = 0; + + firstpts = AV_NOPTS_VALUE; + framecount = 0; + av_init_packet(&avpkt); + inp = av_find_input_format("mpegts"); + AVIOContext *pIOCtx = avio_alloc_context(inbuf, INBUF_SIZE, // internal Buffer and its size + 0, // bWriteable (1=true,0=false) + NULL, // user data; will be passed to our callback functions + my_read, // read callback + NULL, // write callback + NULL); // seek callback + avfc = avformat_alloc_context(); + avfc->pb = pIOCtx; + avfc->iformat = inp; + avfc->probesize = 188*5; + + thread_running = true; + if (avformat_open_input(&avfc, NULL, inp, NULL) < 0) { + lt_info("%s: Could not open input\n", __func__); + goto out; + } + while (avfc->nb_streams < 1) + { + lt_info("%s: nb_streams %d, should be 1 => retry\n", __func__, avfc->nb_streams); + if (av_read_frame(avfc, &avpkt) < 0) + lt_info("%s: av_read_frame < 0\n", __func__); + av_free_packet(&avpkt); + if (! thread_running) + goto out; + } + lt_info("%s: nb_streams %d\n", __func__, avfc->nb_streams); + + if (avfc->streams[0]->codec->codec_type != AVMEDIA_TYPE_VIDEO) + lt_info("%s: no video codec? 0x%x\n", __func__, avfc->streams[0]->codec->codec_type); + + c = avfc->streams[0]->codec; + codec = avcodec_find_decoder(c->codec_id); + if (!codec) { + lt_info("%s: Codec not found\n", __func__); + goto out; + } + if (avcodec_open2(c, codec, NULL) < 0) { + lt_info("%s: Could not open codec\n", __func__); + goto out; + } + frame = avcodec_alloc_frame(); + rgbframe = avcodec_alloc_frame(); + if (!frame || !rgbframe) { + lt_info("%s: Could not allocate video frame\n", __func__); + goto out2; + } + while (thread_running) { + if (av_read_frame(avfc, &avpkt) < 0) { + if (warn_r - time(NULL) > 4) { + lt_info("%s: av_read_frame < 0\n", __func__); + warn_r = time(NULL); + } + usleep(10000); + continue; + } + int got_frame = 0; + int len = avcodec_decode_video2(c, frame, &got_frame, &avpkt); + if (len < 0) { + if (warn_d - time(NULL) > 4) { + lt_info("%s: avcodec_decode_video2 %d\n", __func__, len); + warn_d = time(NULL); + } + av_free_packet(&avpkt); + continue; + } + if (avpkt.size > len) + lt_info("%s: WARN: pkt->size %d != len %d\n", __func__, avpkt.size, len); + if (got_frame) { + unsigned int need = avpicture_get_size(PIX_FMT_RGB32, c->width, c->height); + struct SwsContext *convert = sws_getContext(c->width, c->height, c->pix_fmt, + c->width, c->height, PIX_FMT_RGB32, + SWS_BICUBIC, 0, 0, 0); + if (!convert) + lt_info("%s: ERROR setting up SWS context\n", __func__); + else { + SWFramebuffer *f = &buffers[buf_in]; + if (f->size() < need) + f->resize(need); + avpicture_fill((AVPicture *)rgbframe, &(*f)[0], PIX_FMT_RGB32, + c->width, c->height); + sws_scale(convert, frame->data, frame->linesize, 0, c->height, + rgbframe->data, rgbframe->linesize); + sws_freeContext(convert); + // TODO: locking needed! + if (dec_w != c->width || dec_h != c->height) { + lt_info("%s: pic changed %dx%d -> %dx%d\n", __func__, + dec_w, dec_h, c->width, c->height); + dec_w = c->width; + dec_h = c->height; + w_h_changed = true; + } + f->width(c->width); + f->height(c->height); + f->pts(av_frame_get_best_effort_timestamp(frame)); + buf_in++; + buf_in %= VDEC_MAXBUFS; + buf_num++; + if (buf_num > (VDEC_MAXBUFS - 1)) { + lt_info("%s: buf_num overflow\n", __func__); + buf_out++; + buf_out %= VDEC_MAXBUFS; + buf_num--; + } + if (firstpts == AV_NOPTS_VALUE && f->pts() != AV_NOPTS_VALUE) + firstpts = f->pts(); + } + dec_r = c->time_base.den/(c->time_base.num * c->ticks_per_frame); + framecount++; + lt_debug("%s: time_base: %d/%d, ticks: %d rate: %d pts 0x%" PRIx64 "\n", __func__, + c->time_base.num, c->time_base.den, c->ticks_per_frame, dec_r, + av_frame_get_best_effort_timestamp(frame)); + } + av_free_packet(&avpkt); + } + out2: + avcodec_close(c); + avcodec_free_frame(&frame); + avcodec_free_frame(&rgbframe); + out: + avformat_close_input(&avfc); + av_free(pIOCtx->buffer); + av_free(pIOCtx); + /* reset output buffers */ + bufpos = 0; + buf_num = 0; + buf_in = 0; + buf_out = 0; + lt_info("======================== end decoder thread ================================\n"); +} diff --git a/generic-pc/video_lib.h b/generic-pc/video_lib.h index c3979e0..9847767 100644 --- a/generic-pc/video_lib.h +++ b/generic-pc/video_lib.h @@ -1,6 +1,8 @@ #ifndef _VIDEO_TD_H #define _VIDEO_TD_H +#include +#include #include #include "../common/cs_types.h" @@ -112,9 +114,29 @@ typedef enum } VIDEO_CONTROL; -class cVideo +#define VDEC_MAXBUFS 0x30 +class cVideo : public OpenThreads::Thread { public: + /* called from GL thread */ + class SWFramebuffer : public std::vector + { + public: + SWFramebuffer() : mWidth(0), mHeight(0) {} + void width(int w) { mWidth = w; } + void height(int h) { mHeight = h; } + void pts(uint64_t p) { mPts = p; } + int width() const { return mWidth; } + int height() const { return mHeight; } + int64_t pts() const { return mPts; } + private: + int mWidth; + int mHeight; + int64_t mPts; + }; + int64_t firstpts; + uint64_t framecount; + int buf_in, buf_out, buf_num; /* constructor & destructor */ cVideo(int mode, void *, void *); ~cVideo(void); @@ -163,6 +185,14 @@ class cVideo int CloseVBI(void) { return 0; }; int StartVBI(unsigned short) { return 0; }; int StopVBI(void) { return 0; }; + SWFramebuffer *getDecBuf(void); + private: + void run(); + SWFramebuffer buffers[VDEC_MAXBUFS]; + int dec_w, dec_h; + int dec_r; + bool w_h_changed; + bool thread_running; }; #endif From 06e5987797330ce629bc5e8d7857d409c138518d Mon Sep 17 00:00:00 2001 From: Stefan Seyfried Date: Sat, 4 May 2013 12:25:24 +0200 Subject: [PATCH 04/12] generic-pc: add openGL based framebuffer implementation --- generic-pc/Makefile.am | 1 + generic-pc/glfb.cpp | 508 +++++++++++++++++++++++++++++++++++++++++ generic-pc/glfb.h | 89 ++++++++ include/glfb.h | 6 + 4 files changed, 604 insertions(+) create mode 100644 generic-pc/glfb.cpp create mode 100644 generic-pc/glfb.h create mode 100644 include/glfb.h diff --git a/generic-pc/Makefile.am b/generic-pc/Makefile.am index 0b8b4f8..a5be1e2 100644 --- a/generic-pc/Makefile.am +++ b/generic-pc/Makefile.am @@ -11,6 +11,7 @@ libgeneric_la_SOURCES = \ dmx.cpp \ video.cpp \ audio.cpp \ + glfb.cpp \ init.cpp \ playback.cpp \ pwrmngr.cpp \ diff --git a/generic-pc/glfb.cpp b/generic-pc/glfb.cpp new file mode 100644 index 0000000..e2f62b2 --- /dev/null +++ b/generic-pc/glfb.cpp @@ -0,0 +1,508 @@ +/* + Copyright 2010 Carsten Juttner + Copyright 2012,2013 Stefan Seyfried + + 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 of the License, or + (at your option) any later version. + + 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, see . + + openGL based framebuffer implementation + based on Carjay's neutrino-hd-dvbapi work, see + http://gitorious.org/neutrino-hd/neutrino-hd-dvbapi + + TODO: AV-Sync code is not existent yet + cleanup carjay's crazy 3D stuff :-) + video mode setting (4:3, 16:9, panscan...) +*/ + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include "glfb.h" +#include "video_lib.h" + +#include "lt_debug.h" + +#define lt_debug_c(args...) _lt_debug(HAL_DEBUG_INIT, NULL, args) +#define lt_info_c(args...) _lt_info(HAL_DEBUG_INIT, NULL, args) +#define lt_debug(args...) _lt_debug(HAL_DEBUG_INIT, this, args) +#define lt_info(args...) _lt_info(HAL_DEBUG_INIT, this, args) + + +extern cVideo *videoDecoder; + +static GLFramebuffer *gThiz = 0; /* GLUT does not allow for an arbitrary argument to the render func */ + +GLFramebuffer::GLFramebuffer(int x, int y): mReInit(true), mShutDown(false), mInitDone(false) +{ + mState.width = x; + mState.height = y; + mX = y * 16 / 9; /* hard coded 16:9 initial aspect ratio for now */ + mY = y; + + mState.blit = true; + + /* linux framebuffer compat mode */ + screeninfo.bits_per_pixel = 32; + screeninfo.xres = mState.width; + screeninfo.xres_virtual = screeninfo.xres; + screeninfo.yres = mState.height; + screeninfo.yres_virtual = screeninfo.yres; + screeninfo.blue.length = 8; + screeninfo.blue.offset = 0; + screeninfo.green.length = 8; + screeninfo.green.offset = 8; + screeninfo.red.length = 8; + screeninfo.red.offset = 16; + screeninfo.transp.length = 8; + screeninfo.transp.offset = 24; + + unlink("/tmp/neutrino.input"); + mkfifo("/tmp/neutrino.input", 0600); + input_fd = open("/tmp/neutrino.input", O_RDWR|O_CLOEXEC|O_NONBLOCK); + if (input_fd < 0) + lt_info("%s: could not open /tmp/neutrino.input FIFO: %m\n", __func__); + initKeys(); + OpenThreads::Thread::start(); + while (!mInitDone) + usleep(1); +} + +GLFramebuffer::~GLFramebuffer() +{ + mShutDown = true; + OpenThreads::Thread::join(); + if (input_fd >= 0) + close(input_fd); +} + +void GLFramebuffer::initKeys() +{ + mSpecialMap[GLUT_KEY_UP] = KEY_UP; + mSpecialMap[GLUT_KEY_DOWN] = KEY_DOWN; + mSpecialMap[GLUT_KEY_LEFT] = KEY_LEFT; + mSpecialMap[GLUT_KEY_RIGHT] = KEY_RIGHT; + + mSpecialMap[GLUT_KEY_F1] = KEY_RED; + mSpecialMap[GLUT_KEY_F2] = KEY_GREEN; + mSpecialMap[GLUT_KEY_F3] = KEY_YELLOW; + mSpecialMap[GLUT_KEY_F4] = KEY_BLUE; + + mSpecialMap[GLUT_KEY_PAGE_UP] = KEY_PAGEUP; + mSpecialMap[GLUT_KEY_PAGE_DOWN] = KEY_PAGEDOWN; + + mKeyMap[0x0d] = KEY_OK; + mKeyMap[0x1b] = KEY_EXIT; + mKeyMap['i'] = KEY_INFO; + mKeyMap['m'] = KEY_MENU; + + mKeyMap['+'] = KEY_VOLUMEUP; + mKeyMap['-'] = KEY_VOLUMEDOWN; + mKeyMap['.'] = KEY_MUTE; + mKeyMap['h'] = KEY_HELP; + mKeyMap['p'] = KEY_POWER; + + mKeyMap['0'] = KEY_0; + mKeyMap['1'] = KEY_1; + mKeyMap['2'] = KEY_2; + mKeyMap['3'] = KEY_3; + mKeyMap['4'] = KEY_4; + mKeyMap['5'] = KEY_5; + mKeyMap['6'] = KEY_6; + mKeyMap['7'] = KEY_7; + mKeyMap['8'] = KEY_8; + mKeyMap['9'] = KEY_9; +} + +void GLFramebuffer::run() +{ + setupCtx(); + setupOSDBuffer(); + mInitDone = true; /* signal that setup is finished */ + + /* init the good stuff */ + GLenum err = glewInit(); + if(err == GLEW_OK) + { + if((!GLEW_VERSION_1_5)||(!GLEW_EXT_pixel_buffer_object)||(!GLEW_ARB_texture_non_power_of_two)) + { + lt_info("GLFB: Sorry, your graphics card is not supported. " + "Needs at least OpenGL 1.5, pixel buffer objects and NPOT textures.\n"); + lt_info("incompatible graphics card: %m"); + _exit(1); /* Life is hard */ + } + else + { + gThiz = this; + glutDisplayFunc(GLFramebuffer::rendercb); + glutKeyboardFunc(GLFramebuffer::keyboardcb); + glutSpecialFunc(GLFramebuffer::specialcb); + setupGLObjects(); /* needs GLEW prototypes */ + glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION); + glutMainLoop(); + releaseGLObjects(); + } + } + else + lt_info("GLFB: error initializing glew: %d\n", err); + lt_info("GLFB: GL thread stopping\n"); +} + + +void GLFramebuffer::setupCtx() +{ + int argc = 1; + /* some dummy commandline for GLUT to be happy */ + char const *argv[2] = { "neutrino", 0 }; + lt_info("GLFB: GL thread starting\n"); + glutInit(&argc, const_cast(argv)); + glutInitWindowSize(mX, mY); + glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); + glutCreateWindow("Neutrino"); +} + +void GLFramebuffer::setupOSDBuffer() +{ /* the OSD buffer size can be decoupled from the actual + window size since the GL can blit-stretch with no + trouble at all, ah, the luxury of ignorance... */ + // mMutex.lock(); + if (mState.width && mState.height) + { + /* 32bit FB depth, *2 because tuxtxt uses a shadow buffer */ + int fbmem = mState.width * mState.height * 4 * 2; + mOSDBuffer.resize(fbmem); + lt_info("GLFB: OSD buffer set to %d bytes\n", fbmem); + } +} + +void GLFramebuffer::setupGLObjects() +{ + unsigned char buf[4] = { 0, 0, 0, 0 }; /* 1 black pixel */ + glGenTextures(1, &mState.osdtex); + glGenTextures(1, &mState.displaytex); + glBindTexture(GL_TEXTURE_2D, mState.osdtex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, mState.width, mState.height, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glBindTexture(GL_TEXTURE_2D, mState.displaytex); /* we do not yet know the size so will set that inline */ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glGenBuffers(1, &mState.pbo); + glGenBuffers(1, &mState.displaypbo); + + /* hack to start with black video buffer instead of white */ + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mState.displaypbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(buf), buf, GL_STREAM_DRAW_ARB); + glBindTexture(GL_TEXTURE_2D, mState.displaytex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +} + + +void GLFramebuffer::releaseGLObjects() +{ + glDeleteBuffers(1, &mState.pbo); + glDeleteBuffers(1, &mState.displaypbo); + glDeleteTextures(1, &mState.osdtex); + glDeleteTextures(1, &mState.displaytex); +} + + +/* static */ void GLFramebuffer::rendercb() +{ + gThiz->render(); +} + + +/* static */ void GLFramebuffer::keyboardcb(unsigned char key, int /*x*/, int /*y*/) +{ + lt_debug_c("GLFB::%s: 0x%x\n", __func__, key); + struct input_event ev; + std::map::const_iterator i = gThiz->mKeyMap.find(key); + if (i == gThiz->mKeyMap.end()) + return; + ev.code = i->second; + ev.value = 1; /* key own */ + ev.type = EV_KEY; + gettimeofday(&ev.time, NULL); + lt_debug_c("GLFB::%s: pushing 0x%x\n", __func__, ev.code); + write(gThiz->input_fd, &ev, sizeof(ev)); + ev.value = 0; /* neutrino is stupid, so push key up directly after key down */ + write(gThiz->input_fd, &ev, sizeof(ev)); +} + +/* static */ void GLFramebuffer::specialcb(int key, int /*x*/, int /*y*/) +{ + lt_debug_c("GLFB::%s: 0x%x\n", __func__, key); + struct input_event ev; + std::map::const_iterator i = gThiz->mSpecialMap.find(key); + if (i == gThiz->mSpecialMap.end()) + return; + ev.code = i->second; + ev.value = 1; + ev.type = EV_KEY; + gettimeofday(&ev.time, NULL); + lt_debug_c("GLFB::%s: pushing 0x%x\n", __func__, ev.code); + write(gThiz->input_fd, &ev, sizeof(ev)); + ev.value = 0; + write(gThiz->input_fd, &ev, sizeof(ev)); +} + +int sleep_us = 30000; + +void GLFramebuffer::render() +{ + if (!mReInit) /* for example if window is resized */ + checkReinit(); + + if(mShutDown) + glutLeaveMainLoop(); + + if (mReInit) + { + mReInit = false; + glViewport(0, 0, mX, mY); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + float aspect = static_cast(mX)/mY; + float osdaspect = 1.0/aspect; //(static_cast(mState.width)/mState.height); +// if(!mState.go3d) + { + glOrtho(aspect*-osdaspect, aspect*osdaspect, -1.0, 1.0, -1.0, 1.0 ); + glClearColor(0.0, 0.0, 0.0, 1.0); + } +#if 0 + else + { /* carjay is crazy... :-) */ + gluPerspective(45.0, static_cast(mX)/mY, 0.05, 1000.0); + glTranslatef(0.0, 0.0, -2.0); + glClearColor(0.25, 0.25, 0.25, 1.0); + } +#endif + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glEnable(GL_BLEND); + glEnable(GL_TEXTURE_2D); + glDisable(GL_DEPTH_TEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + bltDisplayBuffer(); /* decoded video stream */ + if (mState.blit) { + /* only blit manually after fb->blit(), this helps to find missed blit() calls */ + mState.blit = false; + lt_debug("GLFB::%s blit!\n", __func__); + bltOSDBuffer(); /* OSD */ + } + + glBindTexture(GL_TEXTURE_2D, mState.osdtex); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +#if 0 + // cube test + if(mState.go3d) + { + glEnable(GL_DEPTH_TEST); + static float ydeg = 0.0; + glPushMatrix(); + glRotatef(ydeg, 0.0, 1.0, 0.0); + glBindTexture(GL_TEXTURE_2D, mState.displaytex); + drawCube(0.5); + glScalef(1.01, 1.01, 1.01); + glBindTexture(GL_TEXTURE_2D, mState.osdtex); + drawCube(0.5); + glPopMatrix(); + ydeg += 0.75f; + } + else +#endif + { + glBindTexture(GL_TEXTURE_2D, mState.displaytex); + drawSquare(1.0); + glBindTexture(GL_TEXTURE_2D, mState.osdtex); + drawSquare(1.0); + } + + glFlush(); + glutSwapBuffers(); + + GLuint err = glGetError(); + if (err != 0) + lt_info("GLFB::%s: GLError:%d 0x%04x\n", __func__, err, err); + + /* this "rate control" is crap and way too naive, feel free to improve */ + if (videoDecoder) { + int tmp, w, h; + videoDecoder->getPictureInfo(w, h, tmp); + if (tmp) { + tmp = 900000/tmp; /* 90% of the sleep time */ + if (abs(tmp - sleep_us) > 10000) { + lt_info("GLFB::%s: sleep_us %d -> %d\n", __func__, sleep_us, tmp); + sleep_us = tmp; + } + } + w = videoDecoder->buf_num; + if (w != 8) + sleep_us += 10*(8 - w); + } + usleep(sleep_us); + glutPostRedisplay(); +} + + +void GLFramebuffer::checkReinit() +{ + int x = glutGet(GLUT_WINDOW_WIDTH); + int y = glutGet(GLUT_WINDOW_HEIGHT); + if ( x != mX || y != mY ) + { + mX = x; + mY = y; + mReInit = true; + } +} + +#if 0 +void GLFramebuffer::drawCube(float size) +{ + GLfloat vertices[] = { + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, -1.0f + }; + + GLubyte indices[] = { + 0, 1, 2, 3, /* front */ + 0, 3, 4, 5, /* right */ + 0, 5, 6, 1, /* top */ + 1, 6, 7, 2, /* left */ + 7, 4, 3, 2, /* bottom */ + 4, 7, 6, 5 /* back */ + }; + + GLfloat texcoords[] = { + 1.0, 0.0, // v0 + 0.0, 0.0, // v1 + 0.0, 1.0, // v2 + 1.0, 1.0, // v3 + 0.0, 1.0, // v4 + 0.0, 0.0, // v5 + 1.0, 0.0, // v6 + 1.0, 1.0 // v7 + }; + + glPushMatrix(); + glScalef(size, size, size); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(3, GL_FLOAT, 0, vertices); + glTexCoordPointer(2, GL_FLOAT, 0, texcoords); + glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, indices); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glPopMatrix(); +} +#endif + +void GLFramebuffer::drawSquare(float size) +{ + GLfloat vertices[] = { + 1.0f, 1.0f, + -1.0f, 1.0f, + -1.0f, -1.0f, + 1.0f, -1.0f, + }; + + GLubyte indices[] = { 0, 1, 2, 3 }; + + GLfloat texcoords[] = { + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + }; + + glPushMatrix(); + glScalef(size, size, size); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(2, GL_FLOAT, 0, vertices); + glTexCoordPointer(2, GL_FLOAT, 0, texcoords); + glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, indices); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glPopMatrix(); +} + + +void GLFramebuffer::bltOSDBuffer() +{ + /* FIXME: copy each time */ + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mState.pbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER, mOSDBuffer.size(), &mOSDBuffer[0], GL_STREAM_DRAW_ARB); + + glBindTexture(GL_TEXTURE_2D, mState.osdtex); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mState.width, mState.height, GL_BGRA, GL_UNSIGNED_BYTE, 0); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +} + +void GLFramebuffer::bltDisplayBuffer() +{ + if (!videoDecoder) /* cannot start yet */ + return; + static bool warn = true; + cVideo::SWFramebuffer *buf = videoDecoder->getDecBuf(); + if (!buf) { + if (warn) + lt_info("GLFB::%s did not get a buffer...\n", __func__); + warn = false; + return; + } + warn = true; + int w = buf->width(), h = buf->height(); + if (w == 0 || h == 0) + return; + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mState.displaypbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER, buf->size(), &(*buf)[0], GL_STREAM_DRAW_ARB); + + glBindTexture(GL_TEXTURE_2D, mState.displaytex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +} + +void GLFramebuffer::clear() +{ + /* clears front and back buffer */ + memset(&mOSDBuffer[0], 0, mOSDBuffer.size()); +} diff --git a/generic-pc/glfb.h b/generic-pc/glfb.h new file mode 100644 index 0000000..2549991 --- /dev/null +++ b/generic-pc/glfb.h @@ -0,0 +1,89 @@ +/* + Copyright 2010 Carsten Juttner + Copyright 2012,2013 Stefan Seyfried + + 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 of the License, or + (at your option) any later version. + + 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, see . +*/ + +#ifndef __glthread__ +#define __glthread__ +#include +#include +#include +#include +#include +#include +#include /* for screeninfo etc. */ + +class GLFramebuffer : public OpenThreads::Thread +{ +public: + GLFramebuffer(int x, int y); + ~GLFramebuffer(); + + void run(); + std::vector *getOSDBuffer() { return &mOSDBuffer; } /* pointer to OSD bounce buffer */ + + int getOSDWidth() { return mState.width; } + int getOSDHeight() { return mState.height; } + void blit() { mState.blit = true; } + + void clear(); + fb_var_screeninfo getScreenInfo() { return screeninfo; } + +private: + fb_var_screeninfo screeninfo; + int mX; /* window size */ + int mY; + bool mReInit; /* setup things for GL */ + bool mShutDown; /* if set main loop is left */ + bool mInitDone; /* condition predicate */ + // OpenThreads::Condition mInitCond; /* condition variable for init */ + // mutable OpenThreads::Mutex mMutex; /* lock our data */ + + std::vector mOSDBuffer; /* silly bounce buffer */ + + std::map mKeyMap; + std::map mSpecialMap; + int input_fd; + + void checkReinit(); /* e.g. in case window was resized */ + static void rendercb(); /* callback for GLUT */ + void render(); /* actual render function */ + static void keyboardcb(unsigned char key, int x, int y); + static void specialcb(int key, int x, int y); + + void initKeys(); /* setup key bindings for window */ + void setupCtx(); /* create the window and make the context current */ + void setupOSDBuffer(); /* create the OSD buffer */ + void setupGLObjects(); /* PBOs, textures and stuff */ + void releaseGLObjects(); + // void drawCube(float size); /* cubes are the building blocks of our society */ + void drawSquare(float size); /* do not be square */ + + struct { + int width; /* width and height, fixed for a framebuffer instance */ + int height; + GLuint osdtex; /* holds the OSD texture */ + GLuint pbo; /* PBO we use for transfer to texture */ + GLuint displaytex; /* holds the display texture */ + GLuint displaypbo; + //int go3d; + bool blit; + } mState; + + void bltOSDBuffer(); + void bltDisplayBuffer(); +}; +#endif diff --git a/include/glfb.h b/include/glfb.h new file mode 100644 index 0000000..037248a --- /dev/null +++ b/include/glfb.h @@ -0,0 +1,6 @@ +#include +#if HAVE_GENERIC_HARDWARE +#include "../generic-pc/glfb.h" +#else +#error glfb.h only works with HAVE_GENERIC_HARDWARE defined +#endif From ae9d6fd668c62c95aaa0bd453b170ab2bbd14708 Mon Sep 17 00:00:00 2001 From: Stefan Seyfried Date: Sat, 4 May 2013 12:30:23 +0200 Subject: [PATCH 05/12] generic-pc: initialize GL framebuffer in init() --- Makefile.am | 8 ++++++++ configure.ac | 6 ++++++ generic-pc/init.cpp | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/Makefile.am b/Makefile.am index 8ff5959..d275149 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,6 +33,14 @@ if BOXTYPE_GENERIC SUBDIRS += generic-pc libstb_hal_la_LIBADD += \ generic-pc/libgeneric.la + +libstb_hal_test_LDADD += \ + -lglut -lGL -lGLU -lGLEW \ + -lOpenThreads \ + @AVFORMAT_LIBS@ \ + @AVUTIL_LIBS@ \ + @AVCODEC_LIBS@ \ + @SWSCALE_LIBS@ endif if BOXTYPE_SPARK SUBDIRS += libspark libeplayer3 diff --git a/configure.ac b/configure.ac index c7ef49e..db9cf86 100644 --- a/configure.ac +++ b/configure.ac @@ -22,6 +22,12 @@ if test x"$BOXTYPE" = x"tripledragon"; then TUXBOX_APPS_LIB_PKGCONFIG(DIRECTFB, directfb) fi +if test x$BOXTYPE = xgeneric; then + TUXBOX_APPS_LIB_PKGCONFIG(AVFORMAT,libavformat) + TUXBOX_APPS_LIB_PKGCONFIG(AVCODEC,libavcodec) + TUXBOX_APPS_LIB_PKGCONFIG(AVUTIL,libavutil) + TUXBOX_APPS_LIB_PKGCONFIG(SWSCALE,libswscale) +fi AC_OUTPUT([ Makefile common/Makefile diff --git a/generic-pc/init.cpp b/generic-pc/init.cpp index 273cc04..4c4481a 100644 --- a/generic-pc/init.cpp +++ b/generic-pc/init.cpp @@ -1,21 +1,28 @@ #include + #include "init_lib.h" #include "lt_debug.h" +#include "glfb.h" #define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_INIT, NULL, args) #define lt_info(args...) _lt_info(TRIPLE_DEBUG_INIT, NULL, args) static bool initialized = false; +GLFramebuffer *glfb = NULL; void init_td_api() { if (!initialized) lt_debug_init(); lt_info("%s begin, initialized=%d, debug=0x%02x\n", __func__, (int)initialized, debuglevel); + if (! glfb) + glfb = new GLFramebuffer(720, 576); /* hard coded to PAL resolution for now */ initialized = true; } void shutdown_td_api() { lt_info("%s, initialized = %d\n", __func__, (int)initialized); + if (glfb) + delete glfb; initialized = false; } From f87bb0afddf14f55232e77d0fa1b570b1b1485fd Mon Sep 17 00:00:00 2001 From: Stefan Seyfried Date: Sat, 4 May 2013 12:32:50 +0200 Subject: [PATCH 06/12] generic-pc/dmx: route audio and video to the SW decoders --- generic-pc/dmx.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/generic-pc/dmx.cpp b/generic-pc/dmx.cpp index 91525fd..e771986 100644 --- a/generic-pc/dmx.cpp +++ b/generic-pc/dmx.cpp @@ -104,6 +104,7 @@ bool cDemux::Open(DMX_CHANNEL_TYPE pes_type, void * /*hVideoBuffer*/, int uBuffe if (fd > -1) lt_info("%s FD ALREADY OPENED? fd = %d\n", __FUNCTION__, fd); + dmx_type = pes_type; if (pes_type != DMX_PSI_CHANNEL) flags |= O_NONBLOCK; @@ -116,7 +117,8 @@ bool cDemux::Open(DMX_CHANNEL_TYPE pes_type, void * /*hVideoBuffer*/, int uBuffe lt_debug("%s #%d pes_type: %s(%d), uBufferSize: %d fd: %d\n", __func__, num, DMX_T[pes_type], pes_type, uBufferSize, fd); - dmx_type = pes_type; + if (dmx_type == DMX_VIDEO_CHANNEL) + uBufferSize = 0x40000; #if 0 if (!pesfds.empty()) { @@ -379,10 +381,12 @@ bool cDemux::pesFilter(const unsigned short pid) p_flt.pes_type = DMX_PES_PCR; break; case DMX_AUDIO_CHANNEL: - p_flt.pes_type = DMX_PES_AUDIO; + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TSDEMUX_TAP; break; case DMX_VIDEO_CHANNEL: - p_flt.pes_type = DMX_PES_VIDEO; + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TSDEMUX_TAP; break; case DMX_PES_CHANNEL: p_flt.pes_type = DMX_PES_OTHER; From 7cff17cb959a6246dfd0596912d6d1b5be994470 Mon Sep 17 00:00:00 2001 From: Stefan Seyfried Date: Sat, 4 May 2013 15:13:18 +0200 Subject: [PATCH 07/12] generic-pc/audio: initialize curr_pts --- generic-pc/audio.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/generic-pc/audio.cpp b/generic-pc/audio.cpp index c68fbd1..bb814e7 100644 --- a/generic-pc/audio.cpp +++ b/generic-pc/audio.cpp @@ -52,6 +52,7 @@ cAudio::cAudio(void *, void *, void *) thread_started = false; dmxbuf = (uint8_t *)malloc(DMX_BUF_SZ); bufpos = 0; + curr_pts = 0; gThiz = this; ao_initialize(); } @@ -233,6 +234,8 @@ void cAudio::run() ao_info *ai; ao_device *adevice; ao_sample_format sformat; + + curr_pts = 0; av_init_packet(&avpkt); inp = av_find_input_format("mpegts"); AVIOContext *pIOCtx = avio_alloc_context(inbuf, INBUF_SIZE, // internal Buffer and its size @@ -300,9 +303,8 @@ void cAudio::run() break; avcodec_decode_audio4(c, frame, &gotframe, &avpkt); if (gotframe && thread_started) { - int64_t pts = av_frame_get_best_effort_timestamp(frame); - lt_debug("%s: pts 0x%" PRIx64 " %" PRId64 " %3f\n", __func__, pts, pts, pts/90000.0); - curr_pts = pts; + curr_pts = av_frame_get_best_effort_timestamp(frame); + lt_debug("%s: pts 0x%" PRIx64 " %3f\n", __func__, curr_pts, curr_pts/90000.0); ao_play(adevice, (char*)frame->extended_data[0], frame->linesize[0]); } av_free_packet(&avpkt); From 569a1495f76383b30ef5d58c46bc70f4bd6f471e Mon Sep 17 00:00:00 2001 From: Stefan Seyfried Date: Sat, 4 May 2013 15:15:21 +0200 Subject: [PATCH 08/12] generic-pc/glfb: add some sort of A/V synchronization --- Makefile.am | 2 +- generic-pc/glfb.cpp | 46 +++++++++++++++++++++++++++++---------------- generic-pc/glfb.h | 1 + 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/Makefile.am b/Makefile.am index d275149..9a1589b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -35,7 +35,7 @@ libstb_hal_la_LIBADD += \ generic-pc/libgeneric.la libstb_hal_test_LDADD += \ - -lglut -lGL -lGLU -lGLEW \ + -lglut -lGL -lGLU -lGLEW -lao \ -lOpenThreads \ @AVFORMAT_LIBS@ \ @AVUTIL_LIBS@ \ diff --git a/generic-pc/glfb.cpp b/generic-pc/glfb.cpp index e2f62b2..04dd899 100644 --- a/generic-pc/glfb.cpp +++ b/generic-pc/glfb.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -40,6 +41,7 @@ #include #include "glfb.h" #include "video_lib.h" +#include "audio_lib.h" #include "lt_debug.h" @@ -50,6 +52,7 @@ extern cVideo *videoDecoder; +extern cAudio *audioDecoder; static GLFramebuffer *gThiz = 0; /* GLUT does not allow for an arbitrary argument to the render func */ @@ -61,6 +64,7 @@ GLFramebuffer::GLFramebuffer(int x, int y): mReInit(true), mShutDown(false), mIn mY = y; mState.blit = true; + last_apts = 0; /* linux framebuffer compat mode */ screeninfo.bits_per_pixel = 32; @@ -352,22 +356,6 @@ void GLFramebuffer::render() GLuint err = glGetError(); if (err != 0) lt_info("GLFB::%s: GLError:%d 0x%04x\n", __func__, err, err); - - /* this "rate control" is crap and way too naive, feel free to improve */ - if (videoDecoder) { - int tmp, w, h; - videoDecoder->getPictureInfo(w, h, tmp); - if (tmp) { - tmp = 900000/tmp; /* 90% of the sleep time */ - if (abs(tmp - sleep_us) > 10000) { - lt_info("GLFB::%s: sleep_us %d -> %d\n", __func__, sleep_us, tmp); - sleep_us = tmp; - } - } - w = videoDecoder->buf_num; - if (w != 8) - sleep_us += 10*(8 - w); - } usleep(sleep_us); glutPostRedisplay(); } @@ -499,6 +487,32 @@ void GLFramebuffer::bltDisplayBuffer() glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + /* "rate control" mechanism starts here... + * this implementation is pretty naive and not working too well, but + * better this than nothing... :-) */ + int64_t apts = 0; + /* 18000 is the magic value for A/V sync in my libao->pulseaudio->intel_hda setup */ + int64_t vpts = buf->pts() + 18000; + if (audioDecoder) + apts = audioDecoder->getPts(); + if (apts != last_apts) { + int rate, dummy1, dummy2; + if (apts < vpts) + sleep_us = (sleep_us * 2 + (vpts - apts)*10/9) / 3; + else if (sleep_us > 1000) + sleep_us -= 1000; + last_apts = apts; + videoDecoder->getPictureInfo(dummy1, dummy2, rate); + if (rate) + rate = 2000000 / rate; /* limit to half the frame rate */ + else + rate = 50000; /* minimum 20 fps */ + if (sleep_us > rate) + sleep_us = rate; + } + lt_debug("vpts: 0x%" PRIx64 " apts: 0x%" PRIx64 " diff: %6.3f sleep_us %d buf %d\n", + buf->pts(), apts, (buf->pts() - apts)/90000.0, sleep_us, videoDecoder->buf_num); } void GLFramebuffer::clear() diff --git a/generic-pc/glfb.h b/generic-pc/glfb.h index 2549991..0bc9f19 100644 --- a/generic-pc/glfb.h +++ b/generic-pc/glfb.h @@ -57,6 +57,7 @@ private: std::map mKeyMap; std::map mSpecialMap; int input_fd; + int64_t last_apts; void checkReinit(); /* e.g. in case window was resized */ static void rendercb(); /* callback for GLUT */ From 8a8849f28e41520f427dee7e1c3b154613474a77 Mon Sep 17 00:00:00 2001 From: Stefan Seyfried Date: Sat, 4 May 2013 17:21:03 +0200 Subject: [PATCH 09/12] generic-pc/video: improve video decoder * add aspect ratio reporting * "correct" buffer PTS by 300ms for better AV-Sync with MPEG2 * remove unneeded stuff --- generic-pc/video.cpp | 54 +++++++++++++++++++++++++++++++++--------- generic-pc/video_lib.h | 9 +++++-- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/generic-pc/video.cpp b/generic-pc/video.cpp index 3940686..cc34a83 100644 --- a/generic-pc/video.cpp +++ b/generic-pc/video.cpp @@ -63,7 +63,7 @@ cVideo::cVideo(int, void *, void *) buf_num = 0; buf_in = 0; buf_out = 0; - firstpts = AV_NOPTS_VALUE; + v_format = VIDEO_FORMAT_MPEG2; } cVideo::~cVideo(void) @@ -80,7 +80,31 @@ int cVideo::setAspectRatio(int, int) int cVideo::getAspectRatio(void) { - return 1; + buf_m.lock(); + int ret = 0; + int w, h, ar; + AVRational a; + if (buf_num == 0) + goto out; + a = buffers[buf_out].AR(); + w = buffers[buf_out].width(); + h = buffers[buf_out].height(); + if (a.den == 0 || h == 0) + goto out; + ar = w * 100 * a.num / h / a.den; + if (ar < 100 || ar > 225) /* < 4:3, > 20:9 */ + ; /* ret = 0: N/A */ + else if (ar < 140) /* 4:3 */ + ret = 1; + else if (ar < 165) /* 14:9 */ + ret = 2; + else if (ar < 200) /* 16:9 */ + ret = 3; + else + ret = 4; /* 20:9 */ + out: + buf_m.unlock(); + return ret; } int cVideo::setCroppingMode(int) @@ -159,19 +183,24 @@ void cVideo::SetSyncMode(AVSYNC_TYPE) { }; -int cVideo::SetStreamType(VIDEO_FORMAT) +int cVideo::SetStreamType(VIDEO_FORMAT v) { + v_format = v; return 0; } cVideo::SWFramebuffer *cVideo::getDecBuf(void) { - if (buf_num == 0) + buf_m.lock(); + if (buf_num == 0) { + buf_m.unlock(); return NULL; + } SWFramebuffer *p = &buffers[buf_out]; buf_out++; buf_num--; buf_out %= VDEC_MAXBUFS; + buf_m.unlock(); return p; } @@ -217,9 +246,8 @@ void cVideo::run(void) buf_num = 0; buf_in = 0; buf_out = 0; + dec_r = 0; - firstpts = AV_NOPTS_VALUE; - framecount = 0; av_init_packet(&avpkt); inp = av_find_input_format("mpegts"); AVIOContext *pIOCtx = avio_alloc_context(inbuf, INBUF_SIZE, // internal Buffer and its size @@ -297,6 +325,7 @@ void cVideo::run(void) if (!convert) lt_info("%s: ERROR setting up SWS context\n", __func__); else { + buf_m.lock(); SWFramebuffer *f = &buffers[buf_in]; if (f->size() < need) f->resize(need); @@ -315,7 +344,12 @@ void cVideo::run(void) } f->width(c->width); f->height(c->height); - f->pts(av_frame_get_best_effort_timestamp(frame)); + int64_t vpts = av_frame_get_best_effort_timestamp(frame); + if (v_format == VIDEO_FORMAT_MPEG2) + vpts += 90000*3/10; /* 300ms */ + f->pts(vpts); + AVRational a = av_guess_sample_aspect_ratio(avfc, avfc->streams[0], frame); + f->AR(a); buf_in++; buf_in %= VDEC_MAXBUFS; buf_num++; @@ -325,11 +359,9 @@ void cVideo::run(void) buf_out %= VDEC_MAXBUFS; buf_num--; } - if (firstpts == AV_NOPTS_VALUE && f->pts() != AV_NOPTS_VALUE) - firstpts = f->pts(); + dec_r = c->time_base.den/(c->time_base.num * c->ticks_per_frame); + buf_m.unlock(); } - dec_r = c->time_base.den/(c->time_base.num * c->ticks_per_frame); - framecount++; lt_debug("%s: time_base: %d/%d, ticks: %d rate: %d pts 0x%" PRIx64 "\n", __func__, c->time_base.num, c->time_base.den, c->ticks_per_frame, dec_r, av_frame_get_best_effort_timestamp(frame)); diff --git a/generic-pc/video_lib.h b/generic-pc/video_lib.h index 9847767..7fbbf30 100644 --- a/generic-pc/video_lib.h +++ b/generic-pc/video_lib.h @@ -2,9 +2,11 @@ #define _VIDEO_TD_H #include +#include #include #include #include "../common/cs_types.h" +#include typedef enum { ANALOG_SD_RGB_CINCH = 0x00, @@ -126,16 +128,17 @@ class cVideo : public OpenThreads::Thread void width(int w) { mWidth = w; } void height(int h) { mHeight = h; } void pts(uint64_t p) { mPts = p; } + void AR(AVRational a) { mAR = a; } int width() const { return mWidth; } int height() const { return mHeight; } int64_t pts() const { return mPts; } + AVRational AR() const { return mAR; } private: int mWidth; int mHeight; int64_t mPts; + AVRational mAR; }; - int64_t firstpts; - uint64_t framecount; int buf_in, buf_out, buf_num; /* constructor & destructor */ cVideo(int mode, void *, void *); @@ -193,6 +196,8 @@ class cVideo : public OpenThreads::Thread int dec_r; bool w_h_changed; bool thread_running; + VIDEO_FORMAT v_format; + OpenThreads::Mutex buf_m; }; #endif From 9c1419c25b4570f6be3d0e459b25eae1b15c4a34 Mon Sep 17 00:00:00 2001 From: Stefan Seyfried Date: Sat, 4 May 2013 17:24:39 +0200 Subject: [PATCH 10/12] GLFB: add video aspect ratio handling --- generic-pc/glfb.cpp | 11 ++++++++--- generic-pc/glfb.h | 4 +++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/generic-pc/glfb.cpp b/generic-pc/glfb.cpp index 04dd899..0826b27 100644 --- a/generic-pc/glfb.cpp +++ b/generic-pc/glfb.cpp @@ -62,6 +62,7 @@ GLFramebuffer::GLFramebuffer(int x, int y): mReInit(true), mShutDown(false), mIn mState.height = y; mX = y * 16 / 9; /* hard coded 16:9 initial aspect ratio for now */ mY = y; + mVA = 1.0; mState.blit = true; last_apts = 0; @@ -345,7 +346,7 @@ void GLFramebuffer::render() #endif { glBindTexture(GL_TEXTURE_2D, mState.displaytex); - drawSquare(1.0); + drawSquare(1.0, mVA / (16.0/9)); glBindTexture(GL_TEXTURE_2D, mState.osdtex); drawSquare(1.0); } @@ -420,7 +421,7 @@ void GLFramebuffer::drawCube(float size) } #endif -void GLFramebuffer::drawSquare(float size) +void GLFramebuffer::drawSquare(float size, float x_factor) { GLfloat vertices[] = { 1.0f, 1.0f, @@ -439,7 +440,7 @@ void GLFramebuffer::drawSquare(float size) }; glPushMatrix(); - glScalef(size, size, size); + glScalef(size * x_factor, size, size); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glVertexPointer(2, GL_FLOAT, 0, vertices); @@ -480,6 +481,10 @@ void GLFramebuffer::bltDisplayBuffer() if (w == 0 || h == 0) return; + AVRational a = buf->AR(); + if (a.den != 0) + mVA = static_cast(w * a.num) / h / a.den; + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mState.displaypbo); glBufferData(GL_PIXEL_UNPACK_BUFFER, buf->size(), &(*buf)[0], GL_STREAM_DRAW_ARB); diff --git a/generic-pc/glfb.h b/generic-pc/glfb.h index 0bc9f19..71c78b2 100644 --- a/generic-pc/glfb.h +++ b/generic-pc/glfb.h @@ -46,6 +46,8 @@ private: fb_var_screeninfo screeninfo; int mX; /* window size */ int mY; + float mVA; /* video aspect ratio */; + bool mReInit; /* setup things for GL */ bool mShutDown; /* if set main loop is left */ bool mInitDone; /* condition predicate */ @@ -71,7 +73,7 @@ private: void setupGLObjects(); /* PBOs, textures and stuff */ void releaseGLObjects(); // void drawCube(float size); /* cubes are the building blocks of our society */ - void drawSquare(float size); /* do not be square */ + void drawSquare(float size, float x_factor = 1); /* do not be square */ struct { int width; /* width and height, fixed for a framebuffer instance */ From 631c48e65bb02d7ba9fa55206ddf350d513aa126 Mon Sep 17 00:00:00 2001 From: Stefan Seyfried Date: Sat, 4 May 2013 17:25:31 +0200 Subject: [PATCH 11/12] GLFB: fix X window at 16:9 aspect ratio --- generic-pc/glfb.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/generic-pc/glfb.cpp b/generic-pc/glfb.cpp index 0826b27..2c0abef 100644 --- a/generic-pc/glfb.cpp +++ b/generic-pc/glfb.cpp @@ -291,10 +291,11 @@ void GLFramebuffer::render() { mReInit = false; glViewport(0, 0, mX, mY); + glutReshapeWindow(mX, mY); glMatrixMode(GL_PROJECTION); glLoadIdentity(); float aspect = static_cast(mX)/mY; - float osdaspect = 1.0/aspect; //(static_cast(mState.width)/mState.height); + float osdaspect = 1.0/(static_cast(16.0)/9); // if(!mState.go3d) { glOrtho(aspect*-osdaspect, aspect*osdaspect, -1.0, 1.0, -1.0, 1.0 ); @@ -370,6 +371,11 @@ void GLFramebuffer::checkReinit() { mX = x; mY = y; + /* fix aspect ratio */ + if (x < mY * 16 / 9) + mX = mY * 16 / 9; + else + mY = mX * 9 / 16; mReInit = true; } } From 3ce7bc213c96853fba2bbe8333c58ef40c6fec71 Mon Sep 17 00:00:00 2001 From: Stefan Seyfried Date: Sat, 4 May 2013 17:27:01 +0200 Subject: [PATCH 12/12] GLFB: add a few sanity checks for sleep interval --- generic-pc/glfb.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/generic-pc/glfb.cpp b/generic-pc/glfb.cpp index 2c0abef..93c48d1 100644 --- a/generic-pc/glfb.cpp +++ b/generic-pc/glfb.cpp @@ -358,7 +358,8 @@ void GLFramebuffer::render() GLuint err = glGetError(); if (err != 0) lt_info("GLFB::%s: GLError:%d 0x%04x\n", __func__, err, err); - usleep(sleep_us); + if (sleep_us > 0) + usleep(sleep_us); glutPostRedisplay(); } @@ -515,12 +516,14 @@ void GLFramebuffer::bltDisplayBuffer() sleep_us -= 1000; last_apts = apts; videoDecoder->getPictureInfo(dummy1, dummy2, rate); - if (rate) + if (rate > 0) rate = 2000000 / rate; /* limit to half the frame rate */ else rate = 50000; /* minimum 20 fps */ if (sleep_us > rate) sleep_us = rate; + else if (sleep_us < 1) + sleep_us = 1; } lt_debug("vpts: 0x%" PRIx64 " apts: 0x%" PRIx64 " diff: %6.3f sleep_us %d buf %d\n", buf->pts(), apts, (buf->pts() - apts)/90000.0, sleep_us, videoDecoder->buf_num);