mirror of
https://projects.vdr-developer.org/git/vdr-plugin-softhddevice.git
synced 2023-10-10 17:16:51 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e10e62dcf7 | ||
|
|
2a1793c98e | ||
|
|
30d4586448 | ||
|
|
aa4debc9c8 | ||
| ac2e10a308 | |||
|
|
c986d285ea | ||
|
|
8612044b9b | ||
|
|
c19b86411a | ||
|
|
9165052d5e | ||
|
|
413983a666 | ||
|
|
f86fa4edd7 | ||
|
|
7f8110557f | ||
|
|
c9b344a3fd | ||
|
|
b41f934c37 | ||
|
|
6058f3da56 | ||
| 689d75b808 | |||
|
|
bd4503f30b | ||
|
|
24ba8175a3 | ||
|
|
fe24cbb182 | ||
|
|
6eff8fa818 | ||
|
|
552a994db3 | ||
|
|
d24f19bc2d | ||
|
|
7b570c507c | ||
|
|
09ba3e2993 | ||
|
|
d0f825f831 | ||
|
|
47d2896468 | ||
|
|
f59425ac57 | ||
|
|
1acdeee913 | ||
|
|
c2938c7ef3 | ||
|
|
d65fe88c83 | ||
|
|
7d3f4f4434 | ||
|
|
acc35fe30c | ||
|
|
ee5804fed7 | ||
|
|
1cbaddf75c | ||
|
|
226760490b | ||
|
|
7931909e28 | ||
|
|
129c139ed7 | ||
|
|
340816d763 | ||
|
|
d6c6818ecf | ||
|
|
181a0bb372 | ||
|
|
f2d4163899 | ||
|
|
4cc98d7937 | ||
|
|
3812fa8d38 | ||
|
|
da5c5cd5fd |
42
ChangeLog
42
ChangeLog
@@ -1,6 +1,46 @@
|
||||
User johns
|
||||
Date:
|
||||
Date: Sat Apr 7 20:21:16 CEST 2012
|
||||
|
||||
Release Version 0.5.0
|
||||
Change audio/video delay with hot-key.
|
||||
Enable/disable/toggle fullscreen with hot-key (Feature #930).
|
||||
|
||||
User: CafeDelMar
|
||||
Date: Thu Apr 5 22:44:06 CEST 2012
|
||||
|
||||
Cutting pixels are now configured for each resolution.
|
||||
|
||||
User johns
|
||||
Date: Thu Apr 5 15:47:59 CEST 2012
|
||||
|
||||
Buffer less video and audio.
|
||||
Fix 100% cpu use, with mp3 plugin.
|
||||
Audio/Video sync rewrite, trick-speed support moved to video.
|
||||
Faster VdpauBlackSurface version.
|
||||
Fix bug: VideoSetPts wrong position for multi frame packets.
|
||||
|
||||
User: CafeDelMar
|
||||
Date: Mon Mar 26 20:45:54 CEST 2012
|
||||
|
||||
Add VideoSkipPixels support.
|
||||
|
||||
User johns
|
||||
Date: Fri Mar 23 18:43:20 CET 2012
|
||||
|
||||
Add optional argument (display) to ATTA svdrp commmand.
|
||||
Wakeup display to show OSD for remote learning mode.
|
||||
Support switching the primary device with svdrp.
|
||||
Disable and reenable screen saver and DPMS.
|
||||
Video source code cleanup.
|
||||
Fix fast backward with some h264 streams.
|
||||
Make soft start sync setup menu configurable.
|
||||
Fix bug: StillPicture NAL end of sequence is 10 and not 0x10.
|
||||
Fix bug: AudioEnqueue crash without sound card.
|
||||
|
||||
User johns
|
||||
Date: Sun Mar 4 22:35:36 CET 2012
|
||||
|
||||
Release Version 0.4.9
|
||||
Experimental ac3 audio drift correction support.
|
||||
Removes LPCM detection from TS parser.
|
||||
Rewrote video/audio start code.
|
||||
|
||||
2
Makefile
2
Makefile
@@ -37,7 +37,7 @@ CXX ?= g++
|
||||
CFLAGS ?= -g -O2 -W -Wall -Wextra -Winit-self \
|
||||
-Wdeclaration-after-statement \
|
||||
-ftree-vectorize -msse3 -flax-vector-conversions
|
||||
CXXFLAGS ?= -g -O2 -W -Wall -Wextra -Woverloaded-virtual
|
||||
CXXFLAGS ?= -g -O2 -W -Wall -Wextra -Werror=overloaded-virtual
|
||||
|
||||
### The directory environment:
|
||||
|
||||
|
||||
53
README.txt
53
README.txt
@@ -20,23 +20,24 @@ $Id$
|
||||
|
||||
A software and GPU emulated HD output device plugin for VDR.
|
||||
|
||||
o Video VA-API/VA-API (with intel, nvidia and amd backend supported)
|
||||
o Video CPU/VA-API
|
||||
o Video VDPAU/VDPAU
|
||||
o Video CPU/VDPAU
|
||||
o Audio FFMpeg/Alsa/Analog
|
||||
o Audio FFMpeg/Alsa/Digital
|
||||
o Audio FFMpeg/OSS/Analog
|
||||
o HDMI/SPDIF Passthrough
|
||||
o VA-API bob software deinterlace
|
||||
o Auto-crop
|
||||
o Video decoder CPU / VA-API / VDPAU
|
||||
o Video output VA-API / VDPAU
|
||||
o Audio FFMpeg / Alsa / Analog
|
||||
o Audio FFMpeg / Alsa / Digital
|
||||
o Audio FFMpeg / OSS / Analog
|
||||
o HDMI/SPDIF pass-through
|
||||
o YaepgHD support
|
||||
o Software deinterlacer Bob (VA-API only)
|
||||
o Autocrop
|
||||
o Grab image (VDPAU only)
|
||||
o Suspend
|
||||
o Letterbox, Stretch and Center cut-out video display modes
|
||||
|
||||
o planned: Video VA-API/Opengl
|
||||
o planned: Video VDPAU/Opengl
|
||||
o planned: Video CPU/Xv
|
||||
o planned: Video CPU/Opengl
|
||||
o planned: Video decoder VA-API Branch: vaapi-ext/staging
|
||||
o planned: Video output XvBA / Opengl / Xv
|
||||
o planned: VA-API grab image
|
||||
o planned: Improved Software Deinterlacer (yadif or/and ffmpeg filters)
|
||||
o planned: Video XvBA/XvBA
|
||||
o planned: software volume, software channel resample
|
||||
o planned: atmo light support
|
||||
|
||||
To compile you must have the 'requires' installed.
|
||||
@@ -137,6 +138,12 @@ Setup: /etc/vdr/setup.conf
|
||||
-1000 .. 1000 noise reduction level (0 off, -1000 max blur,
|
||||
1000 max sharp)
|
||||
|
||||
softhddevice.<res>.CutTopBottom = 0
|
||||
Cut 'n' pixels at at top and bottom of the video picture.
|
||||
|
||||
softhddevice.<res>.CutLeftRight = 0
|
||||
Cut 'n' pixels at at left and right of the video picture.
|
||||
|
||||
softhddevice.AudioDelay = 0
|
||||
+n or -n ms
|
||||
delay audio or delay video
|
||||
@@ -166,10 +173,9 @@ Setup: /etc/vdr/setup.conf
|
||||
32bit RGBA background color
|
||||
(Red * 16777216 + Green * 65536 + Blue * 256 + Alpha)
|
||||
or hex RRGGBBAA
|
||||
grey = 2155905279
|
||||
|
||||
softhddevice.SkipLines = 0
|
||||
skip 'n' lines at top and bottom of the video picture.
|
||||
grey 127 * 16777216 + 127 * 65536 + 127 * 256 => 2139062016
|
||||
in the setup menu this is entered as (24bit RGB and 8bit Alpha)
|
||||
(Red * 65536 + Green * 256 + Blue)
|
||||
|
||||
softhddevice.StudioLevels = 0
|
||||
0 use PC levels (0-255) with vdpau.
|
||||
@@ -186,6 +192,10 @@ Setup: /etc/vdr/setup.conf
|
||||
0 disable 60Hz display mode
|
||||
1 enable 60Hz display mode
|
||||
|
||||
softhddevice.SoftStartSync = 0
|
||||
0 disable soft start of audio/video sync
|
||||
1 enable soft start of audio/video sync
|
||||
|
||||
VideoDisplayFormat = ?
|
||||
0 pan and scan
|
||||
1 letter box
|
||||
@@ -275,6 +285,11 @@ Requires:
|
||||
x11-libs/xvba-video
|
||||
XVBA Backend for Video Acceleration (VA) API
|
||||
http://www.freedesktop.org/wiki/Software/vaapi
|
||||
|
||||
x11-libs/libvdpau
|
||||
VDPAU wrapper and trace libraries
|
||||
http://www.freedesktop.org/wiki/Software/VDPAU
|
||||
|
||||
x11-libs/libxcb,
|
||||
X C-language Bindings library
|
||||
http://xcb.freedesktop.org
|
||||
|
||||
23
Todo
23
Todo
@@ -19,28 +19,24 @@ GNU Affero General Public License for more details.
|
||||
$Id: $
|
||||
|
||||
missing:
|
||||
software deinterlace (yadif, ...)
|
||||
software decoder with software deinterlace
|
||||
more software deinterlace (yadif, ...)
|
||||
more software decoder with software deinterlace
|
||||
suspend output / energie saver: stop and restart X11
|
||||
suspend plugin didn't restore full-screen (is this wanted?)
|
||||
Option deinterlace off / deinterlace force!
|
||||
ColorSpace aren't configurable with the gui.
|
||||
Replay of old vdr 1.6 recordings.
|
||||
svdrp support for hot-keys.
|
||||
|
||||
crash:
|
||||
AudioPlayHandlerThread -> pthread_cond_wait
|
||||
works for me: restart vdr not working, when started x11 was killed.
|
||||
|
||||
video:
|
||||
subtitle not cleared
|
||||
subtitle could be asyncron
|
||||
reduce warnings after channel switch
|
||||
grab image with hardware and better scaling support
|
||||
hard channel switch
|
||||
yaepghd changed position is lost on channel switch
|
||||
pause (live tv) has sometime problems with SAT1 HD Pro7 HD
|
||||
radio show black background
|
||||
radio no need to wait on video buffers
|
||||
starting with radio and own X11 server, shows no video
|
||||
some low-bandwidth tv channels have hiccups.
|
||||
|
||||
vdpau:
|
||||
software decoder path not working
|
||||
@@ -54,8 +50,8 @@ libva:
|
||||
[drm:i915_hangcheck_elapsed] *ERROR* Hangcheck timer elapsed... GPU hung
|
||||
[drm:i915_wait_request] *ERROR* i915_wait_request returns -11 ...
|
||||
|
||||
libva: branch vaapi-ext
|
||||
add support for vaapi-ext
|
||||
libva: branch vaapi-ext / staging
|
||||
add support for vaapi-ext / staging
|
||||
|
||||
libva-intel-driver:
|
||||
deinterlace only supported with vaapi-ext
|
||||
@@ -73,7 +69,6 @@ libva-vdpau-driver:
|
||||
libva-xvba-driver:
|
||||
|
||||
x11:
|
||||
disable screensaver
|
||||
skip multiple configure-notify, handle only the last one.
|
||||
support embedded mode
|
||||
|
||||
@@ -88,6 +83,7 @@ audio:
|
||||
samplerate problem resume/suspend.
|
||||
only wait for video start, if video is running.
|
||||
Not primary device, don't use and block audio/video.
|
||||
multiple open of audio device, reduce them.
|
||||
|
||||
audio/alsa:
|
||||
better downmix of >2 channels on 2 channel hardware
|
||||
@@ -108,7 +104,7 @@ playback of recording
|
||||
replay/pause need 100% cpu (fixed?)
|
||||
|
||||
plugins:
|
||||
mp3 plugin needs 100% cpu (OSD updates?)
|
||||
mp3 plugin needs 100% cpu (bad ::Poll)
|
||||
|
||||
setup:
|
||||
Setup of decoder type.
|
||||
@@ -120,6 +116,7 @@ setup:
|
||||
|
||||
unsorted:
|
||||
stoping vdr while plugin is suspended opens and closes a window.
|
||||
svdrp prim: support plugin names for device numbers.
|
||||
|
||||
future features (not planed for 1.0 - 1.5)
|
||||
|
||||
|
||||
149
audio.c
149
audio.c
@@ -105,20 +105,20 @@ typedef struct _audio_module_
|
||||
{
|
||||
const char *Name; ///< audio output module name
|
||||
|
||||
void (*Thread) (void); ///< module thread handler
|
||||
void (*Enqueue) (const void *, int); ///< enqueue samples for output
|
||||
void (*VideoReady) (void); ///< video ready, start audio
|
||||
void (*FlushBuffers) (void); ///< flush sample buffers
|
||||
void (*Poller) (void); ///< output poller
|
||||
int (*FreeBytes) (void); ///< number of bytes free in buffer
|
||||
int (*UsedBytes) (void); ///< number of bytes used in buffer
|
||||
uint64_t(*GetDelay) (void); ///< get current audio delay
|
||||
void (*SetVolume) (int); ///< set output volume
|
||||
int (*Setup) (int *, int *, int); ///< setup channels, samplerate
|
||||
void (*Play) (void); ///< play
|
||||
void (*Pause) (void); ///< pause
|
||||
void (*Init) (void); ///< initialize audio output module
|
||||
void (*Exit) (void); ///< cleanup audio output module
|
||||
void (*const Thread) (void); ///< module thread handler
|
||||
void (*const Enqueue) (const void *, int); ///< enqueue samples for output
|
||||
void (*const VideoReady) (void); ///< video ready, start audio
|
||||
void (*const FlushBuffers) (void); ///< flush sample buffers
|
||||
void (*const Poller) (void); ///< output poller
|
||||
int (*const FreeBytes) (void); ///< number of bytes free in buffer
|
||||
int (*const UsedBytes) (void); ///< number of bytes used in buffer
|
||||
int64_t(*const GetDelay) (void); ///< get current audio delay
|
||||
void (*const SetVolume) (int); ///< set output volume
|
||||
int (*const Setup) (int *, int *, int); ///< setup channels, samplerate
|
||||
void (*const Play) (void); ///< play
|
||||
void (*const Pause) (void); ///< pause
|
||||
void (*const Init) (void); ///< initialize audio output module
|
||||
void (*const Exit) (void); ///< cleanup audio output module
|
||||
} AudioModule;
|
||||
|
||||
static const AudioModule NoopModule; ///< forward definition of noop module
|
||||
@@ -223,7 +223,7 @@ static void AudioRingInit(void)
|
||||
|
||||
for (i = 0; i < AUDIO_RING_MAX; ++i) {
|
||||
// FIXME:
|
||||
//AlsaRingBuffer = RingBufferNew(48000 * 8 * 2); // ~1s 8ch 16bit
|
||||
//AlsaRingBuffer = RingBufferNew(2 * 48000 * 8 * 2); // ~2s 8ch 16bit
|
||||
}
|
||||
// one slot always reservered
|
||||
AudioRingWrite = 1;
|
||||
@@ -432,11 +432,26 @@ static void AlsaFlushBuffers(void)
|
||||
snd_pcm_state_t state;
|
||||
|
||||
if (AlsaRingBuffer && AlsaPCMHandle) {
|
||||
#ifdef DEBUG
|
||||
const void *r;
|
||||
void *w;
|
||||
#endif
|
||||
|
||||
RingBufferReadAdvance(AlsaRingBuffer,
|
||||
RingBufferUsedBytes(AlsaRingBuffer));
|
||||
#ifdef DEBUG
|
||||
RingBufferGetWritePointer(AlsaRingBuffer, &w);
|
||||
RingBufferGetReadPointer(AlsaRingBuffer, &r);
|
||||
if (r != w) {
|
||||
Fatal(_("audio/alsa: ringbuffer out of sync %zd-%zd\n"),
|
||||
RingBufferGetWritePointer(AlsaRingBuffer, &w),
|
||||
RingBufferGetReadPointer(AlsaRingBuffer, &r));
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
|
||||
state = snd_pcm_state(AlsaPCMHandle);
|
||||
Debug(3, "audio/alsa: flush state %d - %s\n", state,
|
||||
snd_pcm_state_name(state));
|
||||
Debug(3, "audio/alsa: flush state %s\n", snd_pcm_state_name(state));
|
||||
if (state != SND_PCM_STATE_OPEN) {
|
||||
if ((err = snd_pcm_drop(AlsaPCMHandle)) < 0) {
|
||||
Error(_("audio: snd_pcm_drop(): %s\n"), snd_strerror(err));
|
||||
@@ -672,7 +687,8 @@ static void AlsaThread(void)
|
||||
usleep(24 * 1000);
|
||||
continue;
|
||||
}
|
||||
if (AlsaFlushBuffer || AudioPaused) {
|
||||
// timeout or some commands
|
||||
if (!err || AlsaFlushBuffer || AudioPaused) {
|
||||
continue;
|
||||
}
|
||||
if ((err = AlsaPlayRingbuffer())) { // empty / error
|
||||
@@ -701,7 +717,7 @@ static void AlsaThread(void)
|
||||
*/
|
||||
static void AlsaThreadEnqueue(const void *samples, int count)
|
||||
{
|
||||
if (!AlsaRingBuffer || !AlsaPCMHandle || !AudioSampleRate) {
|
||||
if (!AlsaRingBuffer || !AlsaPCMHandle) {
|
||||
Debug(3, "audio/alsa: enqueue not ready\n");
|
||||
return;
|
||||
}
|
||||
@@ -722,19 +738,31 @@ static void AlsaThreadEnqueue(const void *samples, int count)
|
||||
*/
|
||||
static void AlsaVideoReady(void)
|
||||
{
|
||||
if (AudioSampleRate && AudioChannels) {
|
||||
Debug(3, "audio/alsa: start %4zdms video start\n",
|
||||
(RingBufferUsedBytes(AlsaRingBuffer) * 1000)
|
||||
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
|
||||
}
|
||||
|
||||
if (!AudioRunning) {
|
||||
size_t used;
|
||||
|
||||
used = RingBufferUsedBytes(AlsaRingBuffer);
|
||||
// enough video + audio buffered
|
||||
if (AlsaStartThreshold < RingBufferUsedBytes(AlsaRingBuffer)) {
|
||||
if (AlsaStartThreshold < used) {
|
||||
// too much audio buffered, skip it
|
||||
if (AlsaStartThreshold * 2 < used) {
|
||||
Debug(3, "audio/alsa: start %4zdms skip ready\n",
|
||||
((used - AlsaStartThreshold * 2) * 1000)
|
||||
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
|
||||
RingBufferReadAdvance(AlsaRingBuffer,
|
||||
used - AlsaStartThreshold * 2);
|
||||
}
|
||||
AudioRunning = 1;
|
||||
pthread_cond_signal(&AudioStartCond);
|
||||
}
|
||||
}
|
||||
|
||||
if (AudioSampleRate && AudioChannels) {
|
||||
Debug(3, "audio/alsa: start %4zdms video ready\n",
|
||||
(RingBufferUsedBytes(AlsaRingBuffer) * 1000)
|
||||
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -911,17 +939,17 @@ static void AlsaInitMixer(void)
|
||||
**
|
||||
** @todo FIXME: handle the case no audio running
|
||||
*/
|
||||
static uint64_t AlsaGetDelay(void)
|
||||
static int64_t AlsaGetDelay(void)
|
||||
{
|
||||
int err;
|
||||
snd_pcm_sframes_t delay;
|
||||
uint64_t pts;
|
||||
int64_t pts;
|
||||
|
||||
if (!AlsaPCMHandle || !AudioSampleRate) {
|
||||
return 0UL;
|
||||
return 0L;
|
||||
}
|
||||
if (!AudioRunning) { // audio not running
|
||||
return 0UL;
|
||||
return 0L;
|
||||
}
|
||||
// FIXME: thread safe? __assert_fail_base in snd_pcm_delay
|
||||
|
||||
@@ -939,10 +967,10 @@ static uint64_t AlsaGetDelay(void)
|
||||
delay = 0L;
|
||||
}
|
||||
|
||||
pts = ((uint64_t) delay * 90 * 1000) / AudioSampleRate;
|
||||
pts += ((uint64_t) RingBufferUsedBytes(AlsaRingBuffer) * 90 * 1000)
|
||||
pts = ((int64_t) delay * 90 * 1000) / AudioSampleRate;
|
||||
pts += ((int64_t) RingBufferUsedBytes(AlsaRingBuffer) * 90 * 1000)
|
||||
/ (AudioSampleRate * AudioChannels * AudioBytesProSample);
|
||||
Debug(4, "audio/alsa: hw+sw delay %zd %" PRId64 " ms\n",
|
||||
Debug(4, "audio/alsa: hw+sw delay %zd %" PRId64 "ms\n",
|
||||
RingBufferUsedBytes(AlsaRingBuffer), pts / 90);
|
||||
|
||||
return pts;
|
||||
@@ -976,6 +1004,9 @@ static int AlsaSetup(int *freq, int *channels, int use_ac3)
|
||||
#if 1 // easy alsa hw setup way
|
||||
// flush any buffered data
|
||||
AudioFlushBuffers();
|
||||
Debug(3, "audio: %dms flush\n", (AudioUsedBytes() * 1000)
|
||||
/ (!AudioSampleRate + !AudioChannels +
|
||||
AudioSampleRate * AudioChannels * AudioBytesProSample));
|
||||
|
||||
if (1) { // close+open to fix hdmi no sound bugs
|
||||
handle = AlsaPCMHandle;
|
||||
@@ -1146,7 +1177,7 @@ static int AlsaSetup(int *freq, int *channels, int use_ac3)
|
||||
// update buffer
|
||||
|
||||
snd_pcm_get_params(AlsaPCMHandle, &buffer_size, &period_size);
|
||||
Info(_("audio/alsa: buffer size %lu %zdms, period size %lu %zdms\n"),
|
||||
Debug(3, "audio/alsa: buffer size %lu %zdms, period size %lu %zdms\n",
|
||||
buffer_size, snd_pcm_frames_to_bytes(AlsaPCMHandle,
|
||||
buffer_size) * 1000 / (AudioSampleRate * AudioChannels *
|
||||
AudioBytesProSample), period_size,
|
||||
@@ -1171,7 +1202,7 @@ static int AlsaSetup(int *freq, int *channels, int use_ac3)
|
||||
if (AlsaStartThreshold > RingBufferFreeBytes(AlsaRingBuffer)) {
|
||||
AlsaStartThreshold = RingBufferFreeBytes(AlsaRingBuffer);
|
||||
}
|
||||
Info(_("audio/alsa: delay %u ms\n"), (AlsaStartThreshold * 1000)
|
||||
Info(_("audio/alsa: delay %ums\n"), (AlsaStartThreshold * 1000)
|
||||
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
|
||||
|
||||
return ret;
|
||||
@@ -1241,7 +1272,7 @@ static void AlsaInit(void)
|
||||
#else
|
||||
(void)AlsaNoopCallback;
|
||||
#endif
|
||||
AlsaRingBuffer = RingBufferNew(48000 * 8 * 2); // ~1s 8ch 16bit
|
||||
AlsaRingBuffer = RingBufferNew(2 * 48000 * 8 * 2); // ~2s 8ch 16bit
|
||||
|
||||
AlsaInitPCM();
|
||||
AlsaInitMixer();
|
||||
@@ -1449,7 +1480,7 @@ static void OssEnqueue(const void *samples, int count)
|
||||
uint32_t tick;
|
||||
|
||||
tick = GetMsTicks();
|
||||
Debug(4, "audio/oss: %4d %d ms\n", count, tick - last_tick);
|
||||
Debug(4, "audio/oss: %4d %dms\n", count, tick - last_tick);
|
||||
last_tick = tick;
|
||||
#endif
|
||||
|
||||
@@ -1552,7 +1583,7 @@ static void OssThread(void)
|
||||
*/
|
||||
static void OssThreadEnqueue(const void *samples, int count)
|
||||
{
|
||||
if (!OssRingBuffer || OssPcmFildes == -1 || !AudioSampleRate) {
|
||||
if (!OssRingBuffer || OssPcmFildes == -1) {
|
||||
Debug(3, "audio/oss: enqueue not ready\n");
|
||||
return;
|
||||
}
|
||||
@@ -1731,16 +1762,16 @@ static void OssInitMixer(void)
|
||||
**
|
||||
** @returns audio delay in time stamps.
|
||||
*/
|
||||
static uint64_t OssGetDelay(void)
|
||||
static int64_t OssGetDelay(void)
|
||||
{
|
||||
int delay;
|
||||
uint64_t pts;
|
||||
int64_t pts;
|
||||
|
||||
if (OssPcmFildes == -1) { // setup failure
|
||||
return 0UL;
|
||||
return 0L;
|
||||
}
|
||||
if (!AudioRunning) { // audio not running
|
||||
return 0UL;
|
||||
return 0L;
|
||||
}
|
||||
// delay in bytes in kernel buffers
|
||||
delay = -1;
|
||||
@@ -1753,9 +1784,9 @@ static uint64_t OssGetDelay(void)
|
||||
delay = 0;
|
||||
}
|
||||
|
||||
pts = ((uint64_t) (delay + RingBufferUsedBytes(OssRingBuffer)) * 90 * 1000)
|
||||
pts = ((int64_t) (delay + RingBufferUsedBytes(OssRingBuffer)) * 90 * 1000)
|
||||
/ (AudioSampleRate * AudioChannels * AudioBytesProSample);
|
||||
Debug(4, "audio/oss: hw+sw delay %zd %" PRId64 " ms\n",
|
||||
Debug(4, "audio/oss: hw+sw delay %zd %" PRId64 "ms\n",
|
||||
RingBufferUsedBytes(OssRingBuffer), pts / 90);
|
||||
|
||||
return pts;
|
||||
@@ -1864,7 +1895,7 @@ static int OssSetup(int *freq, int *channels, int use_ac3)
|
||||
OssFragmentTime = (bi.fragsize * 1000)
|
||||
/ (AudioSampleRate * AudioChannels * AudioBytesProSample);
|
||||
|
||||
Info(_("audio/oss: buffer size %d %dms, fragment size %d %dms\n"),
|
||||
Debug(3, "audio/oss: buffer size %d %dms, fragment size %d %dms\n",
|
||||
bi.fragsize * bi.fragstotal, (bi.fragsize * bi.fragstotal * 1000)
|
||||
/ (AudioSampleRate * AudioChannels * AudioBytesProSample), bi.fragsize,
|
||||
OssFragmentTime);
|
||||
@@ -1889,7 +1920,7 @@ static int OssSetup(int *freq, int *channels, int use_ac3)
|
||||
OssStartThreshold = RingBufferFreeBytes(OssRingBuffer);
|
||||
}
|
||||
|
||||
Info(_("audio/oss: delay %u ms\n"), (OssStartThreshold * 1000)
|
||||
Info(_("audio/oss: delay %ums\n"), (OssStartThreshold * 1000)
|
||||
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
|
||||
|
||||
return ret;
|
||||
@@ -1914,7 +1945,7 @@ void OssPause(void)
|
||||
*/
|
||||
static void OssInit(void)
|
||||
{
|
||||
OssRingBuffer = RingBufferNew(48000 * 8 * 2); // ~1s 8ch 16bit
|
||||
OssRingBuffer = RingBufferNew(2 * 48000 * 8 * 2); // ~2s 8ch 16bit
|
||||
|
||||
OssInitPCM();
|
||||
OssInitMixer();
|
||||
@@ -2002,9 +2033,9 @@ static int NoopUsedBytes(void)
|
||||
**
|
||||
** @returns audio delay in time stamps.
|
||||
*/
|
||||
static uint64_t NoopGetDelay(void)
|
||||
static int64_t NoopGetDelay(void)
|
||||
{
|
||||
return 0UL;
|
||||
return 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2079,8 +2110,9 @@ static void *AudioPlayHandlerThread(void *dummy)
|
||||
// cond_wait can return, without signal!
|
||||
} while (!AudioRunning);
|
||||
|
||||
Debug(3, "audio: ----> %d ms\n", (AudioUsedBytes() * 1000)
|
||||
/ (AudioSampleRate * AudioChannels * AudioBytesProSample));
|
||||
Debug(3, "audio: ----> %dms start\n", (AudioUsedBytes() * 1000)
|
||||
/ (!AudioSampleRate + !AudioChannels +
|
||||
AudioSampleRate * AudioChannels * AudioBytesProSample));
|
||||
|
||||
pthread_mutex_unlock(&AudioMutex);
|
||||
|
||||
@@ -2115,7 +2147,6 @@ static void *AudioPlayHandlerThread(void *dummy)
|
||||
}
|
||||
#endif
|
||||
|
||||
Debug(3, "audio: play start\n");
|
||||
AudioUsedModule->Thread();
|
||||
}
|
||||
|
||||
@@ -2182,11 +2213,14 @@ static const AudioModule *AudioModules[] = {
|
||||
*/
|
||||
void AudioEnqueue(const void *samples, int count)
|
||||
{
|
||||
if (!AudioSampleRate || !AudioChannels) {
|
||||
return; // not setup
|
||||
}
|
||||
if (0) {
|
||||
static uint32_t last;
|
||||
static uint32_t tick;
|
||||
static uint32_t max = 101;
|
||||
uint64_t delay;
|
||||
int64_t delay;
|
||||
|
||||
delay = AudioGetDelay();
|
||||
tick = GetMsTicks();
|
||||
@@ -2202,7 +2236,7 @@ void AudioEnqueue(const void *samples, int count)
|
||||
// Update audio clock (stupid gcc developers thinks INT64_C is unsigned)
|
||||
if (AudioPTS != (int64_t) INT64_C(0x8000000000000000)) {
|
||||
AudioPTS +=
|
||||
((int64_t) count * 90000) / (AudioSampleRate * AudioChannels *
|
||||
((int64_t) count * 90 * 1000) / (AudioSampleRate * AudioChannels *
|
||||
AudioBytesProSample);
|
||||
}
|
||||
}
|
||||
@@ -2253,7 +2287,7 @@ int AudioUsedBytes(void)
|
||||
**
|
||||
** @returns audio delay in time stamps.
|
||||
*/
|
||||
uint64_t AudioGetDelay(void)
|
||||
int64_t AudioGetDelay(void)
|
||||
{
|
||||
return AudioUsedModule->GetDelay();
|
||||
}
|
||||
@@ -2267,9 +2301,8 @@ void AudioSetClock(int64_t pts)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
if (AudioPTS != pts) {
|
||||
Debug(4, "audio: set clock to %#012" PRIx64 " %#012" PRIx64 " pts\n",
|
||||
AudioPTS, pts);
|
||||
|
||||
Debug(4, "audio: set clock %s -> %s pts\n", Timestamp2String(AudioPTS),
|
||||
Timestamp2String(pts));
|
||||
}
|
||||
#endif
|
||||
AudioPTS = pts;
|
||||
|
||||
2
audio.h
2
audio.h
@@ -32,7 +32,7 @@ extern void AudioFlushBuffers(void); ///< flush audio buffers
|
||||
extern void AudioPoller(void); ///< poll audio events/handling
|
||||
extern int AudioFreeBytes(void); ///< free bytes in audio output
|
||||
extern int AudioUsedBytes(void); ///< used bytes in audio output
|
||||
extern uint64_t AudioGetDelay(void); ///< get current audio delay
|
||||
extern int64_t AudioGetDelay(void); ///< get current audio delay
|
||||
extern void AudioSetClock(int64_t); ///< set audio clock base
|
||||
extern int64_t AudioGetClock(); ///< get current audio clock
|
||||
extern void AudioSetVolume(int); ///< set volume
|
||||
|
||||
35
codec.c
35
codec.c
@@ -558,6 +558,11 @@ void CodecVideoDecode(VideoDecoder * decoder, const AVPacket * avpkt)
|
||||
video_ctx->frame_number, used);
|
||||
}
|
||||
if (used != pkt->size) {
|
||||
// ffmpeg 0.8.7 dislikes our seq_end_h264 and enters endless loop here
|
||||
if (used == 0 && pkt->size == 5 && pkt->data[4] == 0x0A) {
|
||||
Warning("codec: ffmpeg 0.8.x workaround used\n");
|
||||
return;
|
||||
}
|
||||
if (used >= 0 && used < pkt->size) {
|
||||
// some tv channels, produce this
|
||||
Debug(4,
|
||||
@@ -723,8 +728,8 @@ void CodecAudioOpen(AudioDecoder * audio_decoder, const char *name,
|
||||
|
||||
if (audio_codec->capabilities & CODEC_CAP_TRUNCATED) {
|
||||
Debug(3, "codec: audio can use truncated packets\n");
|
||||
// we do not send complete frames
|
||||
audio_decoder->AudioCtx->flags |= CODEC_FLAG_TRUNCATED;
|
||||
// we send only complete frames
|
||||
// audio_decoder->AudioCtx->flags |= CODEC_FLAG_TRUNCATED;
|
||||
}
|
||||
audio_decoder->SampleRate = 0;
|
||||
audio_decoder->Channels = 0;
|
||||
@@ -795,6 +800,10 @@ void CodecSetAudioDownmix(int onoff)
|
||||
** ffmpeg L R C Ls Rs -> alsa L R Ls Rs C
|
||||
** ffmpeg L R C LFE Ls Rs -> alsa L R Ls Rs C LFE
|
||||
** ffmpeg L R C LFE Ls Rs Rl Rr -> alsa L R Ls Rs C LFE Rl Rr
|
||||
**
|
||||
** @param buf[IN,OUT] sample buffer
|
||||
** @param size size of sample buffer in bytes
|
||||
** @param channels number of channels interleaved in sample buffer
|
||||
*/
|
||||
static void CodecReorderAudioFrame(int16_t * buf, int size, int channels)
|
||||
{
|
||||
@@ -931,7 +940,12 @@ static void CodecAudioSetClock(AudioDecoder * audio_decoder, int64_t pts)
|
||||
if (audio_decoder->AvResample && audio_decoder->DriftCorr) {
|
||||
int distance;
|
||||
|
||||
distance = (pts_diff * audio_decoder->HwSampleRate) / (90 * 1000);
|
||||
// try workaround for buggy ffmpeg 0.10
|
||||
if (abs(audio_decoder->DriftCorr) < 2000) {
|
||||
distance = (pts_diff * audio_decoder->HwSampleRate) / (900 * 1000);
|
||||
} else {
|
||||
distance = (pts_diff * audio_decoder->HwSampleRate) / (90 * 1000);
|
||||
}
|
||||
av_resample_compensate(audio_decoder->AvResample,
|
||||
audio_decoder->DriftCorr / 10, distance);
|
||||
}
|
||||
@@ -950,10 +964,6 @@ static void CodecAudioUpdateFormat(AudioDecoder * audio_decoder)
|
||||
int err;
|
||||
int isAC3;
|
||||
|
||||
audio_ctx = audio_decoder->AudioCtx;
|
||||
|
||||
audio_decoder->PassthroughAC3 = CodecPassthroughAC3;
|
||||
|
||||
// FIXME: use swr_convert from swresample (only in ffmpeg!)
|
||||
if (audio_decoder->ReSample) {
|
||||
audio_resample_close(audio_decoder->ReSample);
|
||||
@@ -965,9 +975,16 @@ static void CodecAudioUpdateFormat(AudioDecoder * audio_decoder)
|
||||
audio_decoder->RemainCount = 0;
|
||||
}
|
||||
|
||||
audio_ctx = audio_decoder->AudioCtx;
|
||||
Debug(3, "codec/audio: format change %dHz %d channels %s\n",
|
||||
audio_ctx->sample_rate, audio_ctx->channels,
|
||||
CodecPassthroughAC3 ? "pass-through" : "");
|
||||
|
||||
audio_decoder->SampleRate = audio_ctx->sample_rate;
|
||||
audio_decoder->HwSampleRate = audio_ctx->sample_rate;
|
||||
audio_decoder->Channels = audio_ctx->channels;
|
||||
audio_decoder->PassthroughAC3 = CodecPassthroughAC3;
|
||||
|
||||
// SPDIF/HDMI passthrough
|
||||
if (CodecPassthroughAC3 && audio_ctx->codec_id == CODEC_ID_AC3) {
|
||||
audio_decoder->HwChannels = 2;
|
||||
@@ -1034,8 +1051,7 @@ static void CodecAudioUpdateFormat(AudioDecoder * audio_decoder)
|
||||
**
|
||||
** @param audio_decoder audio decoder data
|
||||
** @param data samples data
|
||||
** @param count number of samples
|
||||
**
|
||||
** @param count number of bytes in sample data
|
||||
*/
|
||||
void CodecAudioEnqueue(AudioDecoder * audio_decoder, int16_t * data, int count)
|
||||
{
|
||||
@@ -1152,7 +1168,6 @@ void CodecAudioDecode(AudioDecoder * audio_decoder, const AVPacket * avpkt)
|
||||
// update audio clock
|
||||
if (avpkt->pts != (int64_t) AV_NOPTS_VALUE) {
|
||||
CodecAudioSetClock(audio_decoder, avpkt->pts);
|
||||
|
||||
}
|
||||
// FIXME: must first play remainings bytes, than change and play new.
|
||||
if (audio_decoder->PassthroughAC3 != CodecPassthroughAC3
|
||||
|
||||
25
misc.h
25
misc.h
@@ -107,6 +107,31 @@ static inline void Syslog(const int level, const char *format, ...)
|
||||
#define Debug(level, fmt...) /* disabled */
|
||||
#endif
|
||||
|
||||
#ifndef AV_NOPTS_VALUE
|
||||
#define AV_NOPTS_VALUE INT64_C(0x8000000000000000)
|
||||
#endif
|
||||
|
||||
/**
|
||||
** Nice time-stamp string.
|
||||
**
|
||||
** @param ts dvb time stamp
|
||||
*/
|
||||
static inline const char *Timestamp2String(int64_t ts)
|
||||
{
|
||||
static char buf[4][16];
|
||||
static int idx;
|
||||
|
||||
if (ts == (int64_t) AV_NOPTS_VALUE) {
|
||||
return "--:--:--.---";
|
||||
}
|
||||
idx = (idx + 1) % 3;
|
||||
snprintf(buf[idx], sizeof(buf[idx]), "%2d:%02d:%02d.%03d",
|
||||
(int)(ts / (90 * 3600000)), (int)((ts / (90 * 60000)) % 60),
|
||||
(int)((ts / (90 * 1000)) % 60), (int)((ts / 90) % 1000));
|
||||
|
||||
return buf[idx];
|
||||
}
|
||||
|
||||
/**
|
||||
** Get ticks in ms.
|
||||
**
|
||||
|
||||
515
softhddev.c
515
softhddev.c
@@ -47,6 +47,10 @@
|
||||
#include "video.h"
|
||||
#include "codec.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
static int H264Dump(const uint8_t * data, int size);
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Variables
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -311,6 +315,12 @@ static inline int FastAc3Check(const uint8_t * p)
|
||||
if (p[1] != 0x77) {
|
||||
return 0;
|
||||
}
|
||||
if ((p[4] & 0xC0) == 0xC0) { // invalid sample rate
|
||||
return 0;
|
||||
}
|
||||
if ((p[4] & 0x3F) > 37) { // invalid frame size
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -334,7 +344,7 @@ static int Ac3Check(const uint8_t * data, int size)
|
||||
|
||||
// crc1 crc1 fscod|frmsizcod
|
||||
fscod = data[4] >> 6;
|
||||
frmsizcod = data[4] & 0x3F;
|
||||
frmsizcod = data[4] & 0x3F; // invalid is checked by fast
|
||||
frame_size = Ac3FrameSizeTable[frmsizcod][fscod] * 2;
|
||||
|
||||
if (frame_size + 2 > size) {
|
||||
@@ -421,12 +431,25 @@ typedef struct _pes_demux_
|
||||
int64_t DTS; ///< decode time stamp
|
||||
} PesDemux;
|
||||
|
||||
///
|
||||
/// Reset packetized elementary stream demuxer.
|
||||
///
|
||||
static void PesReset(PesDemux * pesdx)
|
||||
{
|
||||
pesdx->State = PES_INIT;
|
||||
pesdx->Index = 0;
|
||||
pesdx->Skip = 0;
|
||||
pesdx->StartCode = -1;
|
||||
pesdx->PTS = AV_NOPTS_VALUE;
|
||||
pesdx->DTS = AV_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
///
|
||||
/// Initialize a packetized elementary stream demuxer.
|
||||
///
|
||||
/// @param pesdx packetized elementary stream demuxer
|
||||
///
|
||||
void PesInit(PesDemux * pesdx)
|
||||
static void PesInit(PesDemux * pesdx)
|
||||
{
|
||||
memset(pesdx, 0, sizeof(*pesdx));
|
||||
pesdx->Size = PES_MAX_PAYLOAD;
|
||||
@@ -434,20 +457,7 @@ void PesInit(PesDemux * pesdx)
|
||||
if (!pesdx->Buffer) {
|
||||
Fatal(_("pesdemux: out of memory\n"));
|
||||
}
|
||||
pesdx->PTS = AV_NOPTS_VALUE; // reset
|
||||
pesdx->DTS = AV_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
///
|
||||
/// Reset packetized elementary stream demuxer.
|
||||
///
|
||||
void PesReset(PesDemux * pesdx)
|
||||
{
|
||||
pesdx->State = PES_INIT;
|
||||
pesdx->Index = 0;
|
||||
pesdx->Skip = 0;
|
||||
pesdx->PTS = AV_NOPTS_VALUE;
|
||||
pesdx->DTS = AV_NOPTS_VALUE;
|
||||
PesReset(pesdx);
|
||||
}
|
||||
|
||||
///
|
||||
@@ -458,7 +468,8 @@ void PesReset(PesDemux * pesdx)
|
||||
/// @param size number of payload data bytes
|
||||
/// @param is_start flag, start of pes packet
|
||||
///
|
||||
void PesParse(PesDemux * pesdx, const uint8_t * data, int size, int is_start)
|
||||
static void PesParse(PesDemux * pesdx, const uint8_t * data, int size,
|
||||
int is_start)
|
||||
{
|
||||
const uint8_t *p;
|
||||
const uint8_t *q;
|
||||
@@ -605,6 +616,8 @@ void PesParse(PesDemux * pesdx, const uint8_t * data, int size, int is_start)
|
||||
Debug(3, "pesdemux: pes start code id %#02x\n", code);
|
||||
// FIXME: need to save start code id?
|
||||
pesdx->StartCode = code;
|
||||
// we could have already detect a valid stream type
|
||||
// don't switch to codec 'none'
|
||||
}
|
||||
|
||||
pesdx->State = PES_HEADER;
|
||||
@@ -664,11 +677,11 @@ void PesParse(PesDemux * pesdx, const uint8_t * data, int size, int is_start)
|
||||
// only private stream 1, has sub streams
|
||||
pesdx->State = PES_START;
|
||||
}
|
||||
//pesdx->HeaderIndex = 0;
|
||||
//pesdx->Index = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
#if 0
|
||||
// Played with PlayAudio
|
||||
case PES_LPCM_HEADER: // lpcm header
|
||||
n = pesdx->HeaderSize - pesdx->HeaderIndex;
|
||||
if (n > size) {
|
||||
@@ -745,6 +758,7 @@ void PesParse(PesDemux * pesdx, const uint8_t * data, int size, int is_start)
|
||||
AudioEnqueue(pesdx->Buffer, pesdx->Index);
|
||||
pesdx->Index = 0;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
} while (size > 0);
|
||||
}
|
||||
@@ -782,7 +796,7 @@ static PesDemux PesDemuxAudio[1]; ///< audio demuxer
|
||||
///
|
||||
/// @returns number of bytes consumed from buffer.
|
||||
///
|
||||
int TsDemuxer(TsDemux * tsdx, const uint8_t * data, int size)
|
||||
static int TsDemuxer(TsDemux * tsdx, const uint8_t * data, int size)
|
||||
{
|
||||
const uint8_t *p;
|
||||
|
||||
@@ -864,25 +878,28 @@ int PlayAudio(const uint8_t * data, int size, uint8_t id)
|
||||
|
||||
// channel switch: SetAudioChannelDevice: SetDigitalAudioDevice:
|
||||
|
||||
if (StreamFreezed) { // stream freezed
|
||||
return 0;
|
||||
}
|
||||
if (SkipAudio || !MyAudioDecoder) { // skip audio
|
||||
return size;
|
||||
}
|
||||
|
||||
if (StreamFreezed) { // stream freezed
|
||||
return 0;
|
||||
}
|
||||
if (NewAudioStream) {
|
||||
// FIXME: does this clear the audio ringbuffer?
|
||||
// this clears the audio ringbuffer indirect, open and setup does it
|
||||
CodecAudioClose(MyAudioDecoder);
|
||||
AudioSetBufferTime(0);
|
||||
AudioCodecID = CODEC_ID_NONE;
|
||||
AudioChannelID = -1;
|
||||
NewAudioStream = 0;
|
||||
}
|
||||
// Don't overrun audio buffers on replay
|
||||
// hard limit buffer full: don't overrun audio buffers on replay
|
||||
if (AudioFreeBytes() < AUDIO_MIN_BUFFER_FREE) {
|
||||
return 0;
|
||||
}
|
||||
// soft limit buffer full
|
||||
if (AudioUsedBytes() > AUDIO_MIN_BUFFER_FREE && VideoGetBuffers() > 3) {
|
||||
return 0;
|
||||
}
|
||||
// PES header 0x00 0x00 0x01 ID
|
||||
// ID 0xBD 0xC0-0xCF
|
||||
|
||||
@@ -924,7 +941,7 @@ int PlayAudio(const uint8_t * data, int size, uint8_t id)
|
||||
AudioAvPkt->stream_index = 0;
|
||||
}
|
||||
|
||||
if (AudioChannelID != id) {
|
||||
if (AudioChannelID != id) { // id changed audio track changed
|
||||
AudioChannelID = id;
|
||||
AudioCodecID = CODEC_ID_NONE;
|
||||
}
|
||||
@@ -1063,6 +1080,8 @@ int PlayAudio(const uint8_t * data, int size, uint8_t id)
|
||||
/**
|
||||
** Play transport stream audio packet.
|
||||
**
|
||||
** VDR can have buffered data belonging to previous channel!
|
||||
**
|
||||
** @param data data of exactly one complete TS packet
|
||||
** @param size size of TS packet (always TS_PACKET_SIZE)
|
||||
**
|
||||
@@ -1072,26 +1091,31 @@ int PlayTsAudio(const uint8_t * data, int size)
|
||||
{
|
||||
static TsDemux tsdx[1];
|
||||
|
||||
if (StreamFreezed) { // stream freezed
|
||||
return 0;
|
||||
}
|
||||
if (SkipAudio || !MyAudioDecoder) { // skip audio
|
||||
return size;
|
||||
}
|
||||
// Don't overrun audio buffers on replay
|
||||
if (AudioFreeBytes() < AUDIO_MIN_BUFFER_FREE) {
|
||||
if (StreamFreezed) { // stream freezed
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (NewAudioStream) {
|
||||
// FIXME: does this clear the audio ringbuffer?
|
||||
// this clears the audio ringbuffer indirect, open and setup does it
|
||||
CodecAudioClose(MyAudioDecoder);
|
||||
// max time between audio packets 200ms + 24ms hw buffer
|
||||
AudioSetBufferTime(264);
|
||||
AudioCodecID = CODEC_ID_NONE;
|
||||
AudioChannelID = -1;
|
||||
NewAudioStream = 0;
|
||||
PesReset(PesDemuxAudio);
|
||||
}
|
||||
// hard limit buffer full: don't overrun audio buffers on replay
|
||||
if (AudioFreeBytes() < AUDIO_MIN_BUFFER_FREE) {
|
||||
return 0;
|
||||
}
|
||||
// soft limit buffer full
|
||||
if (AudioUsedBytes() > AUDIO_MIN_BUFFER_FREE && VideoGetBuffers() > 3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return TsDemuxer(tsdx, data, size);
|
||||
}
|
||||
|
||||
@@ -1113,13 +1137,16 @@ void SetVolumeDevice(int volume)
|
||||
|
||||
#include <alsa/iatomic.h> // portable atomic_t
|
||||
|
||||
#ifdef DEBUG
|
||||
uint32_t VideoSwitch; ///< debug video switch ticks
|
||||
#endif
|
||||
static volatile char NewVideoStream; ///< flag new video stream
|
||||
static volatile char ClosingVideoStream; ///< flag closing video stream
|
||||
static VideoHwDecoder *MyHwDecoder; ///< video hw decoder
|
||||
static VideoDecoder *MyVideoDecoder; ///< video decoder
|
||||
static enum CodecID VideoCodecID; ///< current codec id
|
||||
|
||||
static const char *X11DisplayName; ///< x11 display name
|
||||
const char *X11DisplayName; ///< x11 display name
|
||||
static volatile char Usr1Signal; ///< true got usr1 signal
|
||||
|
||||
#define VIDEO_BUFFER_SIZE (512 * 1024) ///< video PES buffer default size
|
||||
@@ -1131,9 +1158,9 @@ static int VideoPacketRead; ///< read pointer
|
||||
static atomic_t VideoPacketsFilled; ///< how many of the buffer is used
|
||||
|
||||
static volatile char VideoClearBuffers; ///< clear video buffers
|
||||
static volatile char VideoClearClose; ///< clear video buffers upto close
|
||||
static volatile char SkipVideo; ///< skip video
|
||||
static volatile char VideoTrickSpeed; ///< current trick speed
|
||||
static volatile char VideoTrickCounter; ///< current trick speed counter
|
||||
static volatile char CurrentTrickSpeed; ///< current trick speed
|
||||
|
||||
#ifdef DEBUG
|
||||
static int VideoMaxPacketSize; ///< biggest used packet buffer
|
||||
@@ -1264,6 +1291,7 @@ static void VideoNextPacket(int codec_id)
|
||||
memset(avpkt->data + avpkt->stream_index, 0, FF_INPUT_BUFFER_PADDING_SIZE);
|
||||
|
||||
avpkt->priv = (void *)(size_t) codec_id;
|
||||
//H264Dump(avpkt->data, avpkt->stream_index);
|
||||
|
||||
// advance packet write
|
||||
VideoPacketWrite = (VideoPacketWrite + 1) % VIDEO_PACKET_MAX;
|
||||
@@ -1321,6 +1349,10 @@ void FixPacketForFFMpeg(VideoDecoder * MyVideoDecoder, AVPacket * avpkt)
|
||||
|
||||
/**
|
||||
** Decode from PES packet ringbuffer.
|
||||
**
|
||||
** @retval 0 packet decoded
|
||||
** @retval 1 stream paused
|
||||
** @retval -1 empty stream
|
||||
*/
|
||||
int VideoDecode(void)
|
||||
{
|
||||
@@ -1341,34 +1373,30 @@ int VideoDecode(void)
|
||||
VideoClearBuffers = 0;
|
||||
return 1;
|
||||
}
|
||||
if (VideoTrickSpeed) {
|
||||
if (VideoTrickCounter++ < VideoTrickSpeed * 2) {
|
||||
usleep(5 * 1000);
|
||||
return 1;
|
||||
}
|
||||
VideoTrickCounter = 0;
|
||||
}
|
||||
|
||||
filled = atomic_read(&VideoPacketsFilled);
|
||||
if (!filled) {
|
||||
return -1;
|
||||
}
|
||||
#if 0
|
||||
int f;
|
||||
// clearing for normal channel switch has no advantage
|
||||
if (VideoClearClose /*|| ClosingVideoStream */ ) {
|
||||
int f;
|
||||
|
||||
// FIXME: flush buffers, if close is in the queue
|
||||
for (f = 0; f < filled; ++f) {
|
||||
avpkt = &VideoPacketRb[(VideoPacketRead + f) % VIDEO_PACKET_MAX];
|
||||
if ((int)(size_t) avpkt->priv == CODEC_ID_NONE) {
|
||||
printf("video: close\n");
|
||||
if (f) {
|
||||
atomic_sub(f, &VideoPacketsFilled);
|
||||
VideoPacketRead = (VideoPacketRead + f) % VIDEO_PACKET_MAX;
|
||||
// flush buffers, if close is in the queue
|
||||
for (f = 0; f < filled; ++f) {
|
||||
avpkt = &VideoPacketRb[(VideoPacketRead + f) % VIDEO_PACKET_MAX];
|
||||
if ((int)(size_t) avpkt->priv == CODEC_ID_NONE) {
|
||||
if (f) {
|
||||
Debug(3, "video: cleared upto close\n");
|
||||
atomic_sub(f, &VideoPacketsFilled);
|
||||
VideoPacketRead = (VideoPacketRead + f) % VIDEO_PACKET_MAX;
|
||||
VideoClearClose = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
ClosingVideoStream = 0;
|
||||
}
|
||||
#endif
|
||||
avpkt = &VideoPacketRb[VideoPacketRead];
|
||||
|
||||
//
|
||||
@@ -1376,11 +1404,13 @@ int VideoDecode(void)
|
||||
//
|
||||
switch ((int)(size_t) avpkt->priv) {
|
||||
case CODEC_ID_NONE:
|
||||
ClosingVideoStream = 0;
|
||||
if (last_codec_id != CODEC_ID_NONE) {
|
||||
last_codec_id = CODEC_ID_NONE;
|
||||
CodecVideoClose(MyVideoDecoder);
|
||||
goto skip;
|
||||
}
|
||||
// FIXME: look if more close are in the queue
|
||||
// size can be zero
|
||||
goto skip;
|
||||
case CODEC_ID_MPEG2VIDEO:
|
||||
@@ -1430,6 +1460,11 @@ int VideoDecode(void)
|
||||
}
|
||||
}
|
||||
|
||||
if (ClosingVideoStream) { // closing don't sync
|
||||
avpkt->pts = AV_NOPTS_VALUE;
|
||||
avpkt->dts = AV_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
if (last_codec_id == CODEC_ID_MPEG2VIDEO) {
|
||||
FixPacketForFFMpeg(MyVideoDecoder, avpkt);
|
||||
} else {
|
||||
@@ -1500,6 +1535,30 @@ static void StopVideo(void)
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
/**
|
||||
** Dump h264 video packet.
|
||||
**
|
||||
** Function to Dump a h264 packet, not needed.
|
||||
*/
|
||||
static int H264Dump(const uint8_t * data, int size)
|
||||
{
|
||||
printf("H264:");
|
||||
do {
|
||||
if (size < 4) {
|
||||
printf("\n");
|
||||
return -1;
|
||||
}
|
||||
if (!data[0] && !data[1] && data[2] == 0x01) {
|
||||
printf("%02x ", data[3]);
|
||||
}
|
||||
++data;
|
||||
--size;
|
||||
} while (size);
|
||||
printf("\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
** Validate mpeg video packet.
|
||||
**
|
||||
@@ -1560,10 +1619,6 @@ int PlayVideo(const uint8_t * data, int size)
|
||||
int z;
|
||||
int l;
|
||||
|
||||
if (Usr1Signal) { // x11 server ready
|
||||
Usr1Signal = 0;
|
||||
StartVideo();
|
||||
}
|
||||
if (!MyVideoDecoder) { // no x11 video started
|
||||
return size;
|
||||
}
|
||||
@@ -1574,7 +1629,7 @@ int PlayVideo(const uint8_t * data, int size)
|
||||
return 0;
|
||||
}
|
||||
if (NewVideoStream) { // channel switched
|
||||
Debug(3, "video: new stream %d\n", GetMsTicks() - VideoSwitch);
|
||||
Debug(3, "video: new stream %dms\n", GetMsTicks() - VideoSwitch);
|
||||
// FIXME: hack to test results
|
||||
if (atomic_read(&VideoPacketsFilled) >= VIDEO_PACKET_MAX - 1) {
|
||||
Debug(3, "video: new video stream lost\n");
|
||||
@@ -1583,6 +1638,9 @@ int PlayVideo(const uint8_t * data, int size)
|
||||
}
|
||||
VideoNextPacket(CODEC_ID_NONE);
|
||||
VideoCodecID = CODEC_ID_NONE;
|
||||
// clear clock until new stream starts
|
||||
VideoSetClock(MyHwDecoder, AV_NOPTS_VALUE);
|
||||
ClosingVideoStream = 1;
|
||||
NewVideoStream = 0;
|
||||
}
|
||||
// must be a PES start code
|
||||
@@ -1590,72 +1648,68 @@ int PlayVideo(const uint8_t * data, int size)
|
||||
Error(_("[softhddev] invalid PES video packet\n"));
|
||||
return size;
|
||||
}
|
||||
n = data[8]; // header size
|
||||
// 0xBE, filler, padding stream
|
||||
if (data[3] == PES_PADDING_STREAM) { // from DVD plugin
|
||||
return size;
|
||||
}
|
||||
|
||||
if (size < 9 + n + 4) { // wrong size
|
||||
n = data[8]; // header size
|
||||
if (size <= 9 + n) { // wrong size
|
||||
if (size == 9 + n) {
|
||||
Warning(_("[softhddev] empty video packet\n"));
|
||||
} else {
|
||||
Error(_("[softhddev] invalid video packet %d bytes\n"), size);
|
||||
Error(_("[softhddev] invalid video packet %d/%d bytes\n"), 9 + n,
|
||||
size);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
// buffer full: needed for replay
|
||||
if (atomic_read(&VideoPacketsFilled) >= VIDEO_PACKET_MAX - 1) {
|
||||
// hard limit buffer full: needed for replay
|
||||
if (atomic_read(&VideoPacketsFilled) >= VIDEO_PACKET_MAX - 3) {
|
||||
return 0;
|
||||
}
|
||||
// soft limit buffer full
|
||||
if (atomic_read(&VideoPacketsFilled) > 3
|
||||
&& AudioUsedBytes() > AUDIO_MIN_BUFFER_FREE) {
|
||||
return 0;
|
||||
}
|
||||
// get pts/dts
|
||||
|
||||
pts = AV_NOPTS_VALUE;
|
||||
if (data[7] & 0x80) {
|
||||
pts =
|
||||
(int64_t) (data[9] & 0x0E) << 29 | data[10] << 22 | (data[11] &
|
||||
0xFE) << 14 | data[12] << 7 | (data[13] & 0xFE) >> 1;
|
||||
#ifdef DEBUG
|
||||
if (!(data[13] & 1) || !(data[11] & 1) || !(data[9] & 1)) {
|
||||
Error(_("[softhddev] invalid pts in video packet\n"));
|
||||
return size;
|
||||
}
|
||||
//Debug(3, "video: pts %#012" PRIx64 "\n", pts);
|
||||
if (data[13] != (((pts & 0x7F) << 1) | 1)) {
|
||||
abort();
|
||||
}
|
||||
if (data[12] != ((pts >> 7) & 0xFF)) {
|
||||
abort();
|
||||
}
|
||||
if (data[11] != ((((pts >> 15) & 0x7F) << 1) | 1)) {
|
||||
abort();
|
||||
}
|
||||
if (data[10] != ((pts >> 22) & 0xFF)) {
|
||||
abort();
|
||||
}
|
||||
if ((data[9] & 0x0F) != (((pts >> 30) << 1) | 1)) {
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
check = data + 9 + n;
|
||||
if (0) {
|
||||
printf("%02x: %02x %02x %02x %02x %02x %02x %02x\n", data[6], check[0],
|
||||
check[1], check[2], check[3], check[4], check[5], check[6]);
|
||||
}
|
||||
#if 1 // FIXME: test code for better h264 detection
|
||||
z = 0;
|
||||
l = size - 9 - n;
|
||||
z = 0;
|
||||
while (!*check) { // count leading zeros
|
||||
if (--l < 4) {
|
||||
if (l < 3) {
|
||||
Warning(_("[softhddev] empty video packet %d bytes\n"), size);
|
||||
return size;
|
||||
z = 0;
|
||||
break;
|
||||
}
|
||||
--l;
|
||||
++check;
|
||||
++z;
|
||||
}
|
||||
|
||||
// H264 Access Unit Delimiter 0x00 0x00 0x00 0x01 0x09
|
||||
// H264 NAL AUD Access Unit Delimiter 0x00 0x00 0x00 0x01 0x09
|
||||
if ((data[6] & 0xC0) == 0x80 && z > 2 && check[0] == 0x01
|
||||
&& check[1] == 0x09) {
|
||||
if (VideoCodecID == CODEC_ID_H264) {
|
||||
if (CurrentTrickSpeed && pts != (int64_t) AV_NOPTS_VALUE) {
|
||||
// H264 NAL End of Sequence
|
||||
static uint8_t seq_end_h264[] =
|
||||
{ 0x00, 0x00, 0x00, 0x01, 0x0A };
|
||||
|
||||
// NAL SPS sequence parameter set
|
||||
if ((check[7] & 0x1F) == 0x07) {
|
||||
VideoNextPacket(CODEC_ID_H264);
|
||||
VideoEnqueue(AV_NOPTS_VALUE, seq_end_h264,
|
||||
sizeof(seq_end_h264));
|
||||
}
|
||||
}
|
||||
VideoNextPacket(CODEC_ID_H264);
|
||||
} else {
|
||||
Debug(3, "video: h264 detected\n");
|
||||
@@ -1683,7 +1737,6 @@ int PlayVideo(const uint8_t * data, int size)
|
||||
return size;
|
||||
}
|
||||
// this happens when vdr sends incomplete packets
|
||||
|
||||
if (VideoCodecID == CODEC_ID_NONE) {
|
||||
Debug(3, "video: not detected\n");
|
||||
return size;
|
||||
@@ -1701,66 +1754,6 @@ int PlayVideo(const uint8_t * data, int size)
|
||||
}
|
||||
|
||||
return size;
|
||||
#else
|
||||
// FIXME: no valid mpeg2/h264 detection yet
|
||||
// FIXME: better skip all zero's >3 && 0x01 0x09 h264, >2 && 0x01 -> mpeg2
|
||||
|
||||
// PES_VIDEO_STREAM 0xE0 or PES start code
|
||||
//(data[6] & 0xC0) != 0x80 ||
|
||||
if ((!check[0] && !check[1] && check[2] == 0x1)) {
|
||||
if (VideoCodecID == CODEC_ID_MPEG2VIDEO) {
|
||||
VideoNextPacket(CODEC_ID_MPEG2VIDEO);
|
||||
} else {
|
||||
Debug(3, "video: mpeg2 detected ID %02x\n", check[3]);
|
||||
VideoCodecID = CODEC_ID_MPEG2VIDEO;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
if (ValidateMpeg(data, size)) {
|
||||
Debug(3, "softhddev/video: invalid mpeg2 video packet\n");
|
||||
}
|
||||
#endif
|
||||
// Access Unit Delimiter
|
||||
} else if ((data[6] & 0xC0) == 0x80 && !check[0] && !check[1]
|
||||
&& !check[2] && check[3] == 0x1 && check[4] == 0x09) {
|
||||
if (VideoCodecID == CODEC_ID_H264) {
|
||||
VideoNextPacket(CODEC_ID_H264);
|
||||
} else {
|
||||
Debug(3, "video: h264 detected\n");
|
||||
VideoCodecID = CODEC_ID_H264;
|
||||
}
|
||||
// Access Unit Delimiter (BBC-HD)
|
||||
// FIXME: the 4 offset are try & error selected
|
||||
} else if ((data[6] & 0xC0) == 0x80 && !check[4 + 0] && !check[4 + 1]
|
||||
&& !check[4 + 2] && check[4 + 3] == 0x1 && check[4 + 4] == 0x09) {
|
||||
if (VideoCodecID == CODEC_ID_H264) {
|
||||
VideoNextPacket(CODEC_ID_H264);
|
||||
} else {
|
||||
Debug(3, "video: h264 detected\n");
|
||||
VideoCodecID = CODEC_ID_H264;
|
||||
}
|
||||
} else {
|
||||
// this happens when vdr sends incomplete packets
|
||||
if (VideoCodecID == CODEC_ID_NONE) {
|
||||
Debug(3, "video: not detected\n");
|
||||
return size;
|
||||
}
|
||||
// incomplete packets produce artefacts after channel switch
|
||||
// packet < 65526 is the last split packet, detect it here for
|
||||
// better latency
|
||||
if (size < 65526 && VideoCodecID == CODEC_ID_MPEG2VIDEO) {
|
||||
// mpeg codec supports incomplete packets
|
||||
// waiting for a full complete packages, increases needed delays
|
||||
VideoEnqueue(pts, check, size - 9 - n);
|
||||
VideoNextPacket(CODEC_ID_MPEG2VIDEO);
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
// SKIP PES header
|
||||
VideoEnqueue(pts, check, size - 9 - n);
|
||||
|
||||
return size;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// call VDR support function
|
||||
@@ -1847,9 +1840,6 @@ uint8_t *GrabImage(int *size, int jpeg, int quality, int width, int height)
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
if (width != -1 && height != -1) {
|
||||
Warning(_("softhddev: scaling unsupported\n"));
|
||||
}
|
||||
return VideoGrab(size, &width, &height, 1);
|
||||
}
|
||||
|
||||
@@ -1866,7 +1856,9 @@ int SetPlayMode(int play_mode)
|
||||
if (MyVideoDecoder) { // tell video parser we have new stream
|
||||
if (VideoCodecID != CODEC_ID_NONE) {
|
||||
NewVideoStream = 1;
|
||||
#ifdef DEBUG
|
||||
VideoSwitch = GetMsTicks();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (MyAudioDecoder) { // tell audio parser we have new stream
|
||||
@@ -1874,14 +1866,39 @@ int SetPlayMode(int play_mode)
|
||||
NewAudioStream = 1;
|
||||
}
|
||||
}
|
||||
if (play_mode == 2 || play_mode == 3) {
|
||||
Debug(3, "softhddev: FIXME: audio only, silence video errors\n");
|
||||
switch (play_mode) {
|
||||
case 1: // audio/video from player
|
||||
break;
|
||||
case 2: // audio only
|
||||
Debug(3, "softhddev: FIXME: audio only, silence video errors\n");
|
||||
VideoSetClock(MyHwDecoder, AV_NOPTS_VALUE);
|
||||
break;
|
||||
case 3: // audio only, black screen
|
||||
Debug(3, "softhddev: FIXME: audio only, silence video errors\n");
|
||||
VideoSetClock(MyHwDecoder, AV_NOPTS_VALUE);
|
||||
break;
|
||||
case 4: // video only
|
||||
break;
|
||||
}
|
||||
|
||||
Play();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
** Gets the current System Time Counter, which can be used to
|
||||
** synchronize audio, video and subtitles.
|
||||
*/
|
||||
int64_t GetSTC(void)
|
||||
{
|
||||
if (MyHwDecoder) {
|
||||
return VideoGetClock(MyHwDecoder);
|
||||
}
|
||||
Error(_("softhddev: %s called without hw decoder\n"), __FUNCTION__);
|
||||
return AV_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
** Set trick play speed.
|
||||
**
|
||||
@@ -1892,8 +1909,13 @@ int SetPlayMode(int play_mode)
|
||||
*/
|
||||
void TrickSpeed(int speed)
|
||||
{
|
||||
VideoTrickSpeed = speed;
|
||||
VideoTrickCounter = 0;
|
||||
CurrentTrickSpeed = speed;
|
||||
if (MyHwDecoder) {
|
||||
VideoSetTrickSpeed(MyHwDecoder, speed);
|
||||
} else {
|
||||
// can happen, during startup
|
||||
Debug(3, "softhddev: %s called without hw decoder\n", __FUNCTION__);
|
||||
}
|
||||
StreamFreezed = 0;
|
||||
}
|
||||
|
||||
@@ -1910,9 +1932,11 @@ void Clear(void)
|
||||
//NewAudioStream = 1;
|
||||
// FIXME: audio avcodec_flush_buffers, video is done by VideoClearBuffers
|
||||
|
||||
// wait for empty buffers
|
||||
for (i = 0; VideoClearBuffers && i < 20; ++i) {
|
||||
usleep(1 * 1000);
|
||||
}
|
||||
Debug(3, "[softhddev]%s: buffers %d\n", __FUNCTION__, VideoGetBuffers());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1920,9 +1944,7 @@ void Clear(void)
|
||||
*/
|
||||
void Play(void)
|
||||
{
|
||||
VideoTrickSpeed = 0;
|
||||
VideoTrickCounter = 0;
|
||||
StreamFreezed = 0;
|
||||
TrickSpeed(0); // normal play
|
||||
SkipAudio = 0;
|
||||
AudioPlay();
|
||||
}
|
||||
@@ -1956,7 +1978,8 @@ void StillPicture(const uint8_t * data, int size)
|
||||
{
|
||||
int i;
|
||||
static uint8_t seq_end_mpeg[] = { 0x00, 0x00, 0x01, 0xB7 };
|
||||
static uint8_t seq_end_h264[] = { 0x00, 0x00, 0x00, 0x01, 0x10 };
|
||||
// H264 NAL End of Sequence
|
||||
static uint8_t seq_end_h264[] = { 0x00, 0x00, 0x00, 0x01, 0x0A };
|
||||
|
||||
// must be a PES start code
|
||||
if (size < 9 || !data || data[0] || data[1] || data[2] != 0x01) {
|
||||
@@ -1968,14 +1991,13 @@ void StillPicture(const uint8_t * data, int size)
|
||||
// FIXME: should detect codec, see PlayVideo
|
||||
Error(_("[softhddev] no codec known for still picture\n"));
|
||||
}
|
||||
//Clear(); // flush video buffers
|
||||
|
||||
// +1 future for deinterlace
|
||||
for (i = -1; i < (VideoCodecID == CODEC_ID_MPEG2VIDEO ? 3 : 17); ++i) {
|
||||
//if ( 1 ) {
|
||||
// FIXME: can check video backend, if a frame was produced.
|
||||
// output for max reference frames
|
||||
for (i = 0; i < (VideoCodecID == CODEC_ID_MPEG2VIDEO ? 3 : 17); ++i) {
|
||||
const uint8_t *split;
|
||||
int n;
|
||||
|
||||
// FIXME: vdr pes recordings sends mixed audio/video
|
||||
if ((data[3] & 0xF0) == 0xE0) { // PES packet
|
||||
split = data;
|
||||
n = size;
|
||||
@@ -1983,15 +2005,31 @@ void StillPicture(const uint8_t * data, int size)
|
||||
do {
|
||||
int len;
|
||||
|
||||
len = (split[4] << 8) + split[5];
|
||||
if (!len || len + 6 > n) {
|
||||
PlayVideo(split, n); // feed remaining bytes
|
||||
#ifdef DEBUG
|
||||
if (split[0] || split[1] || split[2] != 0x01) {
|
||||
Error(_("[softhddev] invalid still video packet\n"));
|
||||
break;
|
||||
}
|
||||
PlayVideo(split, len + 6); // feed it
|
||||
#endif
|
||||
|
||||
len = (split[4] << 8) + split[5];
|
||||
if (!len || len + 6 > n) {
|
||||
// video only
|
||||
if ((data[3] & 0xF0) == 0xE0) {
|
||||
while (!PlayVideo(split, n)) { // feed remaining bytes
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ((data[3] & 0xF0) == 0xE0) {
|
||||
// video only
|
||||
while (!PlayVideo(split, len + 6)) { // feed it
|
||||
}
|
||||
}
|
||||
split += 6 + len;
|
||||
n -= 6 + len;
|
||||
} while (n > 6);
|
||||
|
||||
VideoNextPacket(VideoCodecID); // terminate last packet
|
||||
|
||||
if (VideoCodecID == CODEC_ID_H264) {
|
||||
@@ -2013,25 +2051,61 @@ void StillPicture(const uint8_t * data, int size)
|
||||
VideoNextPacket(VideoCodecID); // terminate last packet
|
||||
}
|
||||
}
|
||||
|
||||
// wait for empty buffers
|
||||
for (i = 0; VideoGetBuffers() && i < 30; ++i) {
|
||||
usleep(10 * 1000);
|
||||
}
|
||||
Debug(3, "[softhddev]%s: buffers %d\n", __FUNCTION__, VideoGetBuffers());
|
||||
}
|
||||
|
||||
/**
|
||||
** Poll if device is ready. Called by replay.
|
||||
**
|
||||
** This function is useless, the return value is ignored and
|
||||
** all buffers are overrun by vdr.
|
||||
**
|
||||
** The dvd plugin is using this correct.
|
||||
**
|
||||
** @param timeout timeout to become ready in ms
|
||||
**
|
||||
** @retval true if ready
|
||||
** @retval false if busy
|
||||
*/
|
||||
int Poll(int timeout)
|
||||
{
|
||||
// buffers are too full
|
||||
if (atomic_read(&VideoPacketsFilled) >= VIDEO_PACKET_MAX * 2 / 3
|
||||
|| AudioFreeBytes() < AUDIO_MIN_BUFFER_FREE * 2) {
|
||||
if (timeout) { // let display thread work
|
||||
usleep(timeout * 1000);
|
||||
// poll is only called during replay, flush buffers after replay
|
||||
VideoClearClose = 1;
|
||||
for (;;) {
|
||||
#if 0
|
||||
int empty;
|
||||
int t;
|
||||
|
||||
// buffers are too full
|
||||
empty = atomic_read(&VideoPacketsFilled) < VIDEO_PACKET_MAX * 1 / 4
|
||||
|| AudioUsedBytes() < AUDIO_MIN_BUFFER_FREE * 2;
|
||||
if (empty || !timeout) {
|
||||
return empty;
|
||||
}
|
||||
return atomic_read(&VideoPacketsFilled) < VIDEO_PACKET_MAX * 2 / 3
|
||||
&& AudioFreeBytes() > AUDIO_MIN_BUFFER_FREE;
|
||||
#else
|
||||
int full;
|
||||
int t;
|
||||
|
||||
// one buffer is full
|
||||
full = AudioFreeBytes() >= AUDIO_MIN_BUFFER_FREE
|
||||
|| atomic_read(&VideoPacketsFilled) < VIDEO_PACKET_MAX - 3;
|
||||
|
||||
if (!full || !timeout) {
|
||||
return !full;
|
||||
}
|
||||
#endif
|
||||
t = 15;
|
||||
if (timeout < t) {
|
||||
t = timeout;
|
||||
}
|
||||
usleep(t * 1000); // let display thread work
|
||||
timeout -= t;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2056,6 +2130,10 @@ int Flush(int timeout)
|
||||
|
||||
/**
|
||||
** Get OSD size and aspect.
|
||||
**
|
||||
** @param width[OUT] width of OSD
|
||||
** @param height[OUT] height of OSD
|
||||
** @param aspect[OUT] aspect ratio (4/3, 16/9, ...) of OSD
|
||||
*/
|
||||
void GetOsdSize(int *width, int *height, double *aspect)
|
||||
{
|
||||
@@ -2087,9 +2165,17 @@ void OsdClose(void)
|
||||
|
||||
/**
|
||||
** Draw an OSD pixmap.
|
||||
**
|
||||
** @param x x-coordinate on screen of argb image
|
||||
** @param y y-coordinate on screen of argb image
|
||||
** @paran height height in pixel of argb image
|
||||
** @paran width width in pixel of argb image
|
||||
** @param argb height * width 32bit ARGB image data
|
||||
*/
|
||||
void OsdDrawARGB(int x, int y, int height, int width, const uint8_t * argb)
|
||||
{
|
||||
// wakeup display for showing remote learning dialog
|
||||
VideoDisplayWakeup();
|
||||
VideoOsdDrawARGB(x, y, height, width, argb);
|
||||
}
|
||||
|
||||
@@ -2106,6 +2192,7 @@ const char *CommandLineHelp(void)
|
||||
" -d display\tdisplay of x11 server (fe. :0.0)\n"
|
||||
" -f\t\tstart with fullscreen window (only with window manager)\n"
|
||||
" -g geometry\tx11 window geometry wxh+x+y\n"
|
||||
" -v device\tvideo device (va-api, vdpau, noop)\n"
|
||||
" -s\t\tstart in suspended mode\n" " -x\t\tstart x11 server\n"
|
||||
" -w workaround\tenable/disable workarounds\n"
|
||||
"\tno-hw-decoder\t\tdisable hw decoder, use software decoder only\n"
|
||||
@@ -2126,7 +2213,7 @@ int ProcessArgs(int argc, char *const argv[])
|
||||
// Parse arguments.
|
||||
//
|
||||
for (;;) {
|
||||
switch (getopt(argc, argv, "-a:c:d:fg:p:sw:x")) {
|
||||
switch (getopt(argc, argv, "-a:c:d:fg:p:sv:w:x")) {
|
||||
case 'a': // audio device for pcm
|
||||
AudioSetDevice(optarg);
|
||||
continue;
|
||||
@@ -2150,6 +2237,13 @@ int ProcessArgs(int argc, char *const argv[])
|
||||
return 0;
|
||||
}
|
||||
continue;
|
||||
case 'v': // video driver
|
||||
VideoSetDevice(optarg);
|
||||
#ifdef USE_VDPAU
|
||||
// FIXME: this is a big hack
|
||||
ConfigVdpauDecoder = !strcasecmp(optarg, "vdpau");
|
||||
#endif
|
||||
continue;
|
||||
case 'x': // x11 server
|
||||
ConfigStartX11Server = 1;
|
||||
continue;
|
||||
@@ -2301,7 +2395,6 @@ void SoftHdDeviceExit(void)
|
||||
StopVideo();
|
||||
|
||||
CodecExit();
|
||||
//VideoPacketExit();
|
||||
|
||||
if (ConfigStartX11Server) {
|
||||
Debug(3, "x-setup: Stop x11 server\n");
|
||||
@@ -2389,11 +2482,23 @@ void Stop(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
** Perform any cleanup or other regular tasks.
|
||||
*/
|
||||
void Housekeeping(void)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
** Main thread hook, periodic called from main thread.
|
||||
*/
|
||||
void MainThreadHook(void)
|
||||
{
|
||||
if (Usr1Signal) { // x11 server ready
|
||||
Usr1Signal = 0;
|
||||
StartVideo();
|
||||
VideoDisplayWakeup();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -2419,30 +2524,26 @@ void Suspend(int video, int audio, int dox11)
|
||||
|
||||
SkipVideo = 1;
|
||||
SkipAudio = 1;
|
||||
pthread_mutex_unlock(&SuspendLockMutex);
|
||||
|
||||
if (audio || video) {
|
||||
pthread_mutex_lock(&SuspendLockMutex);
|
||||
|
||||
if (audio) {
|
||||
AudioExit();
|
||||
if (MyAudioDecoder) {
|
||||
CodecAudioClose(MyAudioDecoder);
|
||||
CodecAudioDelDecoder(MyAudioDecoder);
|
||||
MyAudioDecoder = NULL;
|
||||
}
|
||||
NewAudioStream = 0;
|
||||
av_free_packet(AudioAvPkt);
|
||||
if (audio) {
|
||||
AudioExit();
|
||||
if (MyAudioDecoder) {
|
||||
CodecAudioClose(MyAudioDecoder);
|
||||
CodecAudioDelDecoder(MyAudioDecoder);
|
||||
MyAudioDecoder = NULL;
|
||||
}
|
||||
if (video) {
|
||||
StopVideo();
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&SuspendLockMutex);
|
||||
NewAudioStream = 0;
|
||||
av_free_packet(AudioAvPkt);
|
||||
}
|
||||
if (video) {
|
||||
StopVideo();
|
||||
}
|
||||
|
||||
if (dox11) {
|
||||
// FIXME: stop x11, if started
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&SuspendLockMutex);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,6 +51,8 @@ extern "C"
|
||||
|
||||
/// C plugin set play mode
|
||||
extern int SetPlayMode(int);
|
||||
/// C plugin get current system time counter
|
||||
extern int64_t GetSTC(void);
|
||||
/// C plugin set trick speed
|
||||
extern void TrickSpeed(int);
|
||||
/// C plugin clears all video and audio data from the device
|
||||
@@ -79,6 +81,8 @@ extern "C"
|
||||
extern int Start(void);
|
||||
/// C plugin stop code
|
||||
extern void Stop(void);
|
||||
/// C plugin house keeping
|
||||
extern void Housekeeping(void);
|
||||
/// C plugin main thread hook
|
||||
extern void MainThreadHook(void);
|
||||
|
||||
|
||||
363
softhddevice.cpp
363
softhddevice.cpp
@@ -36,6 +36,8 @@
|
||||
extern "C"
|
||||
{
|
||||
#include "video.h"
|
||||
extern const char *X11DisplayName; ///< x11 display name
|
||||
|
||||
extern void AudioPoller(void);
|
||||
extern void CodecSetAudioPassthrough(int);
|
||||
extern void CodecSetAudioDownmix(int);
|
||||
@@ -43,15 +45,23 @@ extern "C"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
static const char *const VERSION = "0.4.9"
|
||||
/// vdr-plugin version number.
|
||||
/// Makefile extracts the version number for generating the file name
|
||||
/// for the distribution archive.
|
||||
static const char *const VERSION = "0.5.0"
|
||||
#ifdef GIT_REV
|
||||
"-GIT" GIT_REV
|
||||
#endif
|
||||
;
|
||||
|
||||
/// vdr-plugin description.
|
||||
static const char *const DESCRIPTION =
|
||||
trNOOP("A software and GPU emulated HD device");
|
||||
|
||||
/// vdr-plugin text of main menu entry
|
||||
static const char *MAINMENUENTRY = trNOOP("SoftHdDevice");
|
||||
|
||||
/// single instance of softhddevice plugin device.
|
||||
static class cSoftHdDevice *MyDevice;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -67,9 +77,9 @@ static char ConfigMakePrimary; ///< config primary wanted
|
||||
static char ConfigHideMainMenuEntry; ///< config hide main menu entry
|
||||
|
||||
static uint32_t ConfigVideoBackground; ///< config video background color
|
||||
static int ConfigVideoSkipLines; ///< config skip lines top/bottom
|
||||
static int ConfigVideoStudioLevels; ///< config use studio levels
|
||||
static int ConfigVideo60HzMode; ///< config use 60Hz display mode
|
||||
static char ConfigVideoStudioLevels; ///< config use studio levels
|
||||
static char ConfigVideo60HzMode; ///< config use 60Hz display mode
|
||||
static char ConfigVideoSoftStartSync; ///< config use softstart sync
|
||||
|
||||
/// config deinterlace
|
||||
static int ConfigVideoDeinterlace[RESOLUTIONS];
|
||||
@@ -89,6 +99,12 @@ static int ConfigVideoSharpen[RESOLUTIONS];
|
||||
/// config scaling
|
||||
static int ConfigVideoScaling[RESOLUTIONS];
|
||||
|
||||
/// config cut top and bottom pixels
|
||||
static int ConfigVideoCutTopBottom[RESOLUTIONS];
|
||||
|
||||
/// config cut left and right pixels
|
||||
static int ConfigVideoCutLeftRight[RESOLUTIONS];
|
||||
|
||||
static int ConfigVideoAudioDelay; ///< config audio delay
|
||||
static int ConfigAudioPassthrough; ///< config audio pass-through
|
||||
static int ConfigAudioDownmix; ///< config audio downmix
|
||||
@@ -100,11 +116,12 @@ static int ConfigAutoCropTolerance; ///< auto crop detection tolerance
|
||||
static char ConfigSuspendClose; ///< suspend should close devices
|
||||
static char ConfigSuspendX11; ///< suspend should stop x11
|
||||
|
||||
static volatile char DoMakePrimary; ///< flag switch primary
|
||||
static volatile int DoMakePrimary; ///< switch primary device to this
|
||||
|
||||
#define SUSPEND_EXTERNAL -1 ///< play external suspend mode
|
||||
#define SUSPEND_NORMAL 0 ///< normal suspend mode
|
||||
#define SUSPEND_DETACHED 1 ///< detached suspend mode
|
||||
#define NOT_SUSPENDED 0 ///< not suspend mode
|
||||
#define SUSPEND_NORMAL 1 ///< normal suspend mode
|
||||
#define SUSPEND_DETACHED 2 ///< detached suspend mode
|
||||
static char SuspendMode; ///< suspend mode
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -113,18 +130,42 @@ static char SuspendMode; ///< suspend mode
|
||||
// C Callbacks
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
** Soft device plugin remote class.
|
||||
*/
|
||||
class cSoftRemote:public cRemote
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
** Soft device remote class constructor.
|
||||
**
|
||||
** @param name remote name
|
||||
*/
|
||||
cSoftRemote(const char *name):cRemote(name)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
** Put keycode into vdr event queue.
|
||||
**
|
||||
** @param code key code
|
||||
** @param repeat flag key repeated
|
||||
** @param release flag key released
|
||||
*/
|
||||
bool Put(const char *code, bool repeat = false, bool release = false) {
|
||||
return cRemote::Put(code, repeat, release);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
** Feed key press as remote input (called from C part).
|
||||
**
|
||||
** @param keymap target keymap "XKeymap" name
|
||||
** @param key pressed/released key name
|
||||
** @param repeat repeated key flag
|
||||
** @param release released key flag
|
||||
*/
|
||||
extern "C" void FeedKeyPress(const char *keymap, const char *key, int repeat,
|
||||
int release)
|
||||
{
|
||||
@@ -140,7 +181,7 @@ extern "C" void FeedKeyPress(const char *keymap, const char *key, int repeat,
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if remote not already exists, create it
|
||||
if (remote) {
|
||||
csoft = (cSoftRemote *) remote;
|
||||
} else {
|
||||
@@ -166,16 +207,16 @@ extern "C" void FeedKeyPress(const char *keymap, const char *key, int repeat,
|
||||
*/
|
||||
class cSoftOsd:public cOsd
|
||||
{
|
||||
//int Level; ///< level: subtitle
|
||||
|
||||
public:
|
||||
cSoftOsd(int, int, uint);
|
||||
virtual ~ cSoftOsd(void);
|
||||
virtual void Flush(void);
|
||||
virtual void SetActive(bool);
|
||||
static volatile char Dirty; ///< flag force redraw everything
|
||||
|
||||
cSoftOsd(int, int, uint); ///< constructor
|
||||
virtual ~ cSoftOsd(void); ///< destructor
|
||||
virtual void Flush(void); ///< commits all data to the hardware
|
||||
virtual void SetActive(bool); ///< sets OSD to be the active one
|
||||
};
|
||||
|
||||
static volatile char OsdDirty; ///< flag force redraw everything
|
||||
volatile char cSoftOsd::Dirty; ///< flag force redraw everything
|
||||
|
||||
/**
|
||||
** Sets this OSD to be the active one.
|
||||
@@ -194,12 +235,21 @@ void cSoftOsd::SetActive(bool on)
|
||||
}
|
||||
cOsd::SetActive(on);
|
||||
if (on) {
|
||||
OsdDirty = 1;
|
||||
Dirty = 1;
|
||||
} else {
|
||||
OsdClose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
** Constructor OSD.
|
||||
**
|
||||
** Initializes the OSD with the given coordinates.
|
||||
**
|
||||
** @param left x-coordinate of osd on display
|
||||
** @param top y-coordinate of osd on display
|
||||
** @param level level of the osd (smallest is shown)
|
||||
*/
|
||||
cSoftOsd::cSoftOsd(int left, int top, uint level)
|
||||
:cOsd(left, top, level)
|
||||
{
|
||||
@@ -208,10 +258,14 @@ cSoftOsd::cSoftOsd(int left, int top, uint level)
|
||||
OsdHeight(), left, top, level);
|
||||
*/
|
||||
|
||||
//this->Level = level;
|
||||
SetActive(true);
|
||||
}
|
||||
|
||||
/**
|
||||
** OSD Destructor.
|
||||
**
|
||||
** Shuts down the OSD.
|
||||
*/
|
||||
cSoftOsd::~cSoftOsd(void)
|
||||
{
|
||||
//dsyslog("[softhddev]%s:\n", __FUNCTION__);
|
||||
@@ -277,7 +331,7 @@ void cSoftOsd::Flush(void)
|
||||
int y2;
|
||||
|
||||
// get dirty bounding box
|
||||
if (OsdDirty) { // forced complete update
|
||||
if (Dirty) { // forced complete update
|
||||
x1 = 0;
|
||||
y1 = 0;
|
||||
x2 = bitmap->Width() - 1;
|
||||
@@ -323,7 +377,7 @@ void cSoftOsd::Flush(void)
|
||||
// FIXME: reuse argb
|
||||
free(argb);
|
||||
}
|
||||
OsdDirty = 0;
|
||||
cSoftOsd::Dirty = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -419,14 +473,17 @@ class cMenuSetupSoft:public cMenuSetupPage
|
||||
int HideMainMenuEntry;
|
||||
uint32_t Background;
|
||||
uint32_t BackgroundAlpha;
|
||||
int SkipLines;
|
||||
int StudioLevels;
|
||||
int _60HzMode;
|
||||
int SoftStartSync;
|
||||
int Scaling[RESOLUTIONS];
|
||||
int Deinterlace[RESOLUTIONS];
|
||||
int SkipChromaDeinterlace[RESOLUTIONS];
|
||||
int InverseTelecine[RESOLUTIONS];
|
||||
int Denoise[RESOLUTIONS];
|
||||
int Sharpen[RESOLUTIONS];
|
||||
int CutTopBottom[RESOLUTIONS];
|
||||
int CutLeftRight[RESOLUTIONS];
|
||||
int AudioDelay;
|
||||
int AudioPassthrough;
|
||||
int AudioDownmix;
|
||||
@@ -497,12 +554,15 @@ cMenuSetupSoft::cMenuSetupSoft(void)
|
||||
(int *)&Background, 0, 0x00FFFFFF));
|
||||
Add(new cMenuEditIntItem(tr("video background color (Alpha)"),
|
||||
(int *)&BackgroundAlpha, 0, 0xFF));
|
||||
SkipLines = ConfigVideoSkipLines;
|
||||
Add(new cMenuEditIntItem(tr("Skip lines top+bot (pixel)"), &SkipLines, 0,
|
||||
64));
|
||||
StudioLevels = ConfigVideoStudioLevels;
|
||||
Add(new cMenuEditBoolItem(tr("Use studio levels (vdpau only)"),
|
||||
&StudioLevels, trVDR("no"), trVDR("yes")));
|
||||
_60HzMode = ConfigVideo60HzMode;
|
||||
Add(new cMenuEditBoolItem(tr("60hz display mode"), &_60HzMode, trVDR("no"),
|
||||
trVDR("yes")));
|
||||
SoftStartSync = ConfigVideoSoftStartSync;
|
||||
Add(new cMenuEditBoolItem(tr("soft start a/v sync"), &SoftStartSync,
|
||||
trVDR("no"), trVDR("yes")));
|
||||
|
||||
for (i = 0; i < RESOLUTIONS; ++i) {
|
||||
Add(SeparatorItem(resolution[i]));
|
||||
@@ -523,6 +583,13 @@ cMenuSetupSoft::cMenuSetupSoft(void)
|
||||
Sharpen[i] = ConfigVideoSharpen[i];
|
||||
Add(new cMenuEditIntItem(tr("Sharpen (-1000..1000) (vdpau)"),
|
||||
&Sharpen[i], -1000, 1000, tr("blur max"), tr("sharpen max")));
|
||||
|
||||
CutTopBottom[i] = ConfigVideoCutTopBottom[i];
|
||||
Add(new cMenuEditIntItem(tr("Cut top and bottom (pixel)"),
|
||||
&CutTopBottom[i], 0, 250));
|
||||
CutLeftRight[i] = ConfigVideoCutLeftRight[i];
|
||||
Add(new cMenuEditIntItem(tr("Cut left and right (pixel)"),
|
||||
&CutLeftRight[i], 0, 250));
|
||||
}
|
||||
//
|
||||
// audio
|
||||
@@ -576,10 +643,12 @@ void cMenuSetupSoft::Store(void)
|
||||
ConfigVideoBackground = Background << 8 | (BackgroundAlpha & 0xFF);
|
||||
SetupStore("Background", ConfigVideoBackground);
|
||||
VideoSetBackground(ConfigVideoBackground);
|
||||
SetupStore("SkipLines", ConfigVideoSkipLines = SkipLines);
|
||||
VideoSetSkipLines(ConfigVideoSkipLines);
|
||||
SetupStore("StudioLevels", ConfigVideoStudioLevels = StudioLevels);
|
||||
VideoSetStudioLevels(ConfigVideoStudioLevels);
|
||||
SetupStore("60HzMode", ConfigVideo60HzMode = _60HzMode);
|
||||
VideoSet60HzMode(ConfigVideo60HzMode);
|
||||
SetupStore("SoftStartSync", ConfigVideoSoftStartSync = SoftStartSync);
|
||||
VideoSetSoftStartSync(ConfigVideoSoftStartSync);
|
||||
|
||||
for (i = 0; i < RESOLUTIONS; ++i) {
|
||||
char buf[128];
|
||||
@@ -598,6 +667,11 @@ void cMenuSetupSoft::Store(void)
|
||||
SetupStore(buf, ConfigVideoDenoise[i] = Denoise[i]);
|
||||
snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "Sharpen");
|
||||
SetupStore(buf, ConfigVideoSharpen[i] = Sharpen[i]);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "CutTopBottom");
|
||||
SetupStore(buf, ConfigVideoCutTopBottom[i] = CutTopBottom[i]);
|
||||
snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "CutLeftRight");
|
||||
SetupStore(buf, ConfigVideoCutLeftRight[i] = CutLeftRight[i]);
|
||||
}
|
||||
VideoSetScaling(ConfigVideoScaling);
|
||||
VideoSetDeinterlace(ConfigVideoDeinterlace);
|
||||
@@ -605,6 +679,8 @@ void cMenuSetupSoft::Store(void)
|
||||
VideoSetInverseTelecine(ConfigVideoInverseTelecine);
|
||||
VideoSetDenoise(ConfigVideoDenoise);
|
||||
VideoSetSharpen(ConfigVideoSharpen);
|
||||
VideoSetCutTopBottom(ConfigVideoCutTopBottom);
|
||||
VideoSetCutLeftRight(ConfigVideoCutLeftRight);
|
||||
|
||||
SetupStore("AudioDelay", ConfigVideoAudioDelay = AudioDelay);
|
||||
VideoSetAudioDelay(ConfigVideoAudioDelay);
|
||||
@@ -653,23 +729,23 @@ cSoftHdPlayer::~cSoftHdPlayer()
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
** Dummy control for suspend mode.
|
||||
** Dummy control class for suspend mode.
|
||||
*/
|
||||
class cSoftHdControl:public cControl
|
||||
{
|
||||
public:
|
||||
static cSoftHdPlayer *Player; ///< dummy player
|
||||
virtual void Hide(void)
|
||||
virtual void Hide(void) ///< hide control
|
||||
{
|
||||
}
|
||||
virtual eOSState ProcessKey(eKeys);
|
||||
virtual eOSState ProcessKey(eKeys); ///< process input events
|
||||
|
||||
cSoftHdControl(void);
|
||||
cSoftHdControl(void); ///< control constructor
|
||||
|
||||
virtual ~ cSoftHdControl();
|
||||
virtual ~ cSoftHdControl(); ///< control destructor
|
||||
};
|
||||
|
||||
cSoftHdPlayer *cSoftHdControl::Player;
|
||||
cSoftHdPlayer *cSoftHdControl::Player; ///< dummy player instance
|
||||
|
||||
/**
|
||||
** Handle a key event.
|
||||
@@ -686,7 +762,7 @@ eOSState cSoftHdControl::ProcessKey(eKeys key)
|
||||
Player = NULL;
|
||||
}
|
||||
Resume();
|
||||
SuspendMode = 0;
|
||||
SuspendMode = NOT_SUSPENDED;
|
||||
return osEnd;
|
||||
}
|
||||
return osContinue;
|
||||
@@ -711,8 +787,7 @@ cSoftHdControl::~cSoftHdControl()
|
||||
Player = NULL;
|
||||
}
|
||||
|
||||
dsyslog("[softhddev]%s: resume\n", __FUNCTION__);
|
||||
//Resume();
|
||||
dsyslog("[softhddev]%s: dummy player stopped\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@@ -769,6 +844,24 @@ static void HandleHotkey(int code)
|
||||
case 12: // toggle pass-through
|
||||
CodecSetAudioPassthrough(ConfigAudioPassthrough ^= 1);
|
||||
break;
|
||||
case 13: // decrease audio delay
|
||||
ConfigVideoAudioDelay -= 10;
|
||||
VideoSetAudioDelay(ConfigVideoAudioDelay);
|
||||
break;
|
||||
case 14: // increase audio delay
|
||||
ConfigVideoAudioDelay += 10;
|
||||
VideoSetAudioDelay(ConfigVideoAudioDelay);
|
||||
break;
|
||||
|
||||
case 20: // disable full screen
|
||||
VideoSetFullscreen(0);
|
||||
break;
|
||||
case 21: // enable full screen
|
||||
VideoSetFullscreen(1);
|
||||
break;
|
||||
case 22: // toggle full screen
|
||||
VideoSetFullscreen(-1);
|
||||
break;
|
||||
default:
|
||||
esyslog(tr("[softhddev]: hot key %d is not supported\n"), code);
|
||||
break;
|
||||
@@ -827,7 +920,8 @@ eOSState cSoftHdMenu::ProcessKey(eKeys key)
|
||||
|
||||
switch (state) {
|
||||
case osUser1:
|
||||
if (!cSoftHdControl::Player) { // not already suspended
|
||||
// not already suspended
|
||||
if (SuspendMode == NOT_SUSPENDED && !cSoftHdControl::Player) {
|
||||
cControl::Launch(new cSoftHdControl);
|
||||
cControl::Attach();
|
||||
Suspend(ConfigSuspendClose, ConfigSuspendClose,
|
||||
@@ -928,11 +1022,12 @@ void cSoftHdDevice::MakePrimaryDevice(bool on)
|
||||
cDevice::MakePrimaryDevice(on);
|
||||
if (on) {
|
||||
new cSoftOsdProvider();
|
||||
|
||||
if (SuspendMode == SUSPEND_DETACHED) {
|
||||
Resume();
|
||||
SuspendMode = 0;
|
||||
SuspendMode = NOT_SUSPENDED;
|
||||
}
|
||||
} else if (!SuspendMode) {
|
||||
} else if (SuspendMode == NOT_SUSPENDED) {
|
||||
Suspend(1, 1, 0);
|
||||
SuspendMode = SUSPEND_DETACHED;
|
||||
}
|
||||
@@ -952,6 +1047,9 @@ cSpuDecoder *cSoftHdDevice::GetSpuDecoder(void)
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
** Tells whether this device has a MPEG decoder.
|
||||
*/
|
||||
bool cSoftHdDevice::HasDecoder(void) const
|
||||
{
|
||||
return true;
|
||||
@@ -994,12 +1092,12 @@ bool cSoftHdDevice::SetPlayMode(ePlayMode play_mode)
|
||||
break;
|
||||
}
|
||||
|
||||
if (SuspendMode) {
|
||||
if (SuspendMode != NOT_SUSPENDED) {
|
||||
if (SuspendMode != SUSPEND_EXTERNAL) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
Resume();
|
||||
SuspendMode = 0;
|
||||
SuspendMode = NOT_SUSPENDED;
|
||||
}
|
||||
|
||||
return::SetPlayMode(play_mode);
|
||||
@@ -1013,7 +1111,7 @@ int64_t cSoftHdDevice::GetSTC(void)
|
||||
{
|
||||
//dsyslog("[softhddev]%s:\n", __FUNCTION__);
|
||||
|
||||
return::VideoGetClock();
|
||||
return::GetSTC();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1077,6 +1175,9 @@ void cSoftHdDevice::Mute(void)
|
||||
|
||||
/**
|
||||
** Display the given I-frame as a still picture.
|
||||
**
|
||||
** @param data pes or ts data of a frame
|
||||
** @param length length of data area
|
||||
*/
|
||||
void cSoftHdDevice::StillPicture(const uchar * data, int length)
|
||||
{
|
||||
@@ -1096,6 +1197,9 @@ void cSoftHdDevice::StillPicture(const uchar * data, int length)
|
||||
**
|
||||
** @param poller file handles (unused)
|
||||
** @param timeout_ms timeout in ms to become ready
|
||||
**
|
||||
** @retval true if ready
|
||||
** @retval false if busy
|
||||
*/
|
||||
bool cSoftHdDevice::Poll(
|
||||
__attribute__ ((unused)) cPoller & poller, int timeout_ms)
|
||||
@@ -1124,7 +1228,7 @@ bool cSoftHdDevice::Flush(int timeout_ms)
|
||||
** device has an MPEG decoder).
|
||||
*/
|
||||
void cSoftHdDevice:: SetVideoDisplayFormat(eVideoDisplayFormat
|
||||
video_display_format)
|
||||
video_display_format)
|
||||
{
|
||||
static int last = -1;
|
||||
|
||||
@@ -1137,7 +1241,7 @@ void cSoftHdDevice:: SetVideoDisplayFormat(eVideoDisplayFormat
|
||||
last = video_display_format;
|
||||
|
||||
::VideoSetDisplayFormat(video_display_format);
|
||||
OsdDirty = 1;
|
||||
cSoftOsd::Dirty = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1324,7 +1428,7 @@ class cPluginSoftHdDevice:public cPlugin
|
||||
virtual bool Initialize(void);
|
||||
virtual bool Start(void);
|
||||
virtual void Stop(void);
|
||||
// virtual void Housekeeping(void);
|
||||
virtual void Housekeeping(void);
|
||||
virtual void MainThreadHook(void);
|
||||
virtual const char *MainMenuEntry(void);
|
||||
virtual cOsdObject *MainMenuAction(void);
|
||||
@@ -1407,7 +1511,7 @@ bool cPluginSoftHdDevice::Start(void)
|
||||
// Must be done in the main thread
|
||||
dsyslog("[softhddev] makeing softhddevice %d the primary device!",
|
||||
MyDevice->DeviceNumber());
|
||||
DoMakePrimary = 1;
|
||||
DoMakePrimary = MyDevice->DeviceNumber() + 1;
|
||||
} else {
|
||||
isyslog("[softhddev] softhddevice %d is not the primary device!",
|
||||
MyDevice->DeviceNumber());
|
||||
@@ -1423,6 +1527,10 @@ bool cPluginSoftHdDevice::Start(void)
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
** Shutdown plugin. Stop any background activities the plugin is
|
||||
** performing.
|
||||
*/
|
||||
void cPluginSoftHdDevice::Stop(void)
|
||||
{
|
||||
//dsyslog("[softhddev]%s:\n", __FUNCTION__);
|
||||
@@ -1430,8 +1538,6 @@ void cPluginSoftHdDevice::Stop(void)
|
||||
::Stop();
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
/**
|
||||
** Perform any cleanup or other regular tasks.
|
||||
*/
|
||||
@@ -1439,10 +1545,18 @@ void cPluginSoftHdDevice::Housekeeping(void)
|
||||
{
|
||||
dsyslog("[softhddev]%s:\n", __FUNCTION__);
|
||||
|
||||
// ::Housekeeping();
|
||||
}
|
||||
// check if user is inactive, automatic enter suspend mode
|
||||
// FIXME: cControl prevents shutdown, disable this until fixed
|
||||
if (0 && SuspendMode == NOT_SUSPENDED && ShutdownHandler.IsUserInactive()) {
|
||||
// don't overwrite already suspended suspend mode
|
||||
cControl::Launch(new cSoftHdControl);
|
||||
cControl::Attach();
|
||||
Suspend(ConfigSuspendClose, ConfigSuspendClose, ConfigSuspendX11);
|
||||
SuspendMode = SUSPEND_NORMAL;
|
||||
}
|
||||
|
||||
#endif
|
||||
::Housekeeping();
|
||||
}
|
||||
|
||||
/**
|
||||
** Create main menu entry.
|
||||
@@ -1461,21 +1575,6 @@ cOsdObject *cPluginSoftHdDevice::MainMenuAction(void)
|
||||
{
|
||||
//dsyslog("[softhddev]%s:\n", __FUNCTION__);
|
||||
|
||||
#if 0
|
||||
//MyDevice->StopReplay();
|
||||
if (!cSoftHdControl::Player) { // not already suspended
|
||||
cControl::Launch(new cSoftHdControl);
|
||||
cControl::Attach();
|
||||
Suspend(ConfigSuspendClose, ConfigSuspendClose, ConfigSuspendX11);
|
||||
SuspendMode = SUSPEND_NORMAL;
|
||||
if (ShutdownHandler.GetUserInactiveTime()) {
|
||||
dsyslog("[softhddev]%s: set user inactive\n", __FUNCTION__);
|
||||
ShutdownHandler.SetUserInactive();
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
#endif
|
||||
return new cSoftHdMenu("SoftHdDevice");
|
||||
}
|
||||
|
||||
@@ -1487,17 +1586,12 @@ void cPluginSoftHdDevice::MainThreadHook(void)
|
||||
{
|
||||
//dsyslog("[softhddev]%s:\n", __FUNCTION__);
|
||||
|
||||
if (DoMakePrimary && MyDevice) {
|
||||
dsyslog("[softhddev]%s: switching primary device\n", __FUNCTION__);
|
||||
cDevice::SetPrimaryDevice(MyDevice->DeviceNumber() + 1);
|
||||
if (DoMakePrimary) {
|
||||
dsyslog("[softhddev]%s: switching primary device to %d\n",
|
||||
__FUNCTION__, DoMakePrimary);
|
||||
cDevice::SetPrimaryDevice(DoMakePrimary);
|
||||
DoMakePrimary = 0;
|
||||
}
|
||||
// check if user is inactive, automatic enter suspend mode
|
||||
if (ShutdownHandler.IsUserInactive()) {
|
||||
// this is regular called, but guarded against double calls
|
||||
Suspend(ConfigSuspendClose, ConfigSuspendClose, ConfigSuspendX11);
|
||||
SuspendMode = SUSPEND_NORMAL;
|
||||
}
|
||||
|
||||
::MainThreadHook();
|
||||
}
|
||||
@@ -1538,10 +1632,6 @@ bool cPluginSoftHdDevice::SetupParse(const char *name, const char *value)
|
||||
VideoSetBackground(ConfigVideoBackground = strtoul(value, NULL, 0));
|
||||
return true;
|
||||
}
|
||||
if (!strcasecmp(name, "SkipLines")) {
|
||||
VideoSetSkipLines(ConfigVideoSkipLines = atoi(value));
|
||||
return true;
|
||||
}
|
||||
if (!strcasecmp(name, "StudioLevels")) {
|
||||
VideoSetStudioLevels(ConfigVideoStudioLevels = atoi(value));
|
||||
return true;
|
||||
@@ -1550,6 +1640,10 @@ bool cPluginSoftHdDevice::SetupParse(const char *name, const char *value)
|
||||
VideoSet60HzMode(ConfigVideo60HzMode = atoi(value));
|
||||
return true;
|
||||
}
|
||||
if (!strcasecmp(name, "SoftStartSync")) {
|
||||
VideoSetSoftStartSync(ConfigVideoSoftStartSync = atoi(value));
|
||||
return true;
|
||||
}
|
||||
for (i = 0; i < RESOLUTIONS; ++i) {
|
||||
char buf[128];
|
||||
|
||||
@@ -1590,6 +1684,19 @@ bool cPluginSoftHdDevice::SetupParse(const char *name, const char *value)
|
||||
VideoSetSharpen(ConfigVideoSharpen);
|
||||
return true;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "CutTopBottom");
|
||||
if (!strcasecmp(name, buf)) {
|
||||
ConfigVideoCutTopBottom[i] = atoi(value);
|
||||
VideoSetCutTopBottom(ConfigVideoCutTopBottom);
|
||||
return true;
|
||||
}
|
||||
snprintf(buf, sizeof(buf), "%s.%s", Resolution[i], "CutLeftRight");
|
||||
if (!strcasecmp(name, buf)) {
|
||||
ConfigVideoCutLeftRight[i] = atoi(value);
|
||||
VideoSetCutLeftRight(ConfigVideoCutLeftRight);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!strcasecmp(name, "AudioDelay")) {
|
||||
@@ -1647,6 +1754,48 @@ bool cPluginSoftHdDevice::Service(const char *Id, void *Data)
|
||||
// cPlugin SVDRP
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
** SVDRP commands help text.
|
||||
** FIXME: translation?
|
||||
*/
|
||||
static const char *SVDRPHelpText[] = {
|
||||
"SUSP\n" " Suspend plugin.\n\n"
|
||||
" The plugin is suspended to save energie. Depending on the setup\n"
|
||||
" 'softhddevice.Suspend.Close = 0' only the video and audio output\n"
|
||||
" is stopped or with 'softhddevice.Suspend.Close = 1' the video\n"
|
||||
" and audio devices are closed.\n"
|
||||
" If 'softhddevice.Suspend.X11 = 1' is set and the X11 server was\n"
|
||||
" started by the plugin, the X11 server would also be closed.\n"
|
||||
" (Stopping X11 while suspended isn't supported yet)\n",
|
||||
"RESU\n" " Resume plugin.\n\n"
|
||||
" Resume the suspended plugin. The plugin could be suspended by\n"
|
||||
" the command line option '-s' or by a previous SUSP command.\n"
|
||||
" If the x11 server was stopped by the plugin, it will be\n"
|
||||
" restarted.",
|
||||
"DETA\n" " Detach plugin.\n\n"
|
||||
" The plugin will be detached from the audio, video and DVB\n"
|
||||
" devices. Other programs or plugins can use them now.\n",
|
||||
"ATTA <-d display>\n" " Attach plugin.\n\n"
|
||||
" Attach the plugin to audio, video and DVB devices.\n"
|
||||
" Use -d display (f.e. -d :0.0) to use another X11 display.\n",
|
||||
"PRIM <n>\n" " Make <n> the primary device.\n\n"
|
||||
" <n> is the number of device. Without number softhddevice becomes\n"
|
||||
" the primary device. If becoming primary, the plugin is attached\n"
|
||||
" to the devices. If loosing primary, the plugin is detached from\n"
|
||||
" the devices.",
|
||||
"HOTK key\n" " Execute hotkey.\n\n"
|
||||
" key is the hotkey number, following are supported:\n"
|
||||
" 10: disable audio pass-through\n"
|
||||
" 11: enable audio pass-through\n"
|
||||
" 12: toggle audio pass-through\n"
|
||||
" 13: decrease audio delay by 10ms\n"
|
||||
" 14: increase audio delay by 10ms\n"
|
||||
" 20: disable fullscreen\n"
|
||||
" 21: enable fullscreen\n"
|
||||
" 22: toggle fullscreen\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
/**
|
||||
** Return SVDRP commands help pages.
|
||||
**
|
||||
@@ -1655,17 +1804,7 @@ bool cPluginSoftHdDevice::Service(const char *Id, void *Data)
|
||||
*/
|
||||
const char **cPluginSoftHdDevice::SVDRPHelpPages(void)
|
||||
{
|
||||
// FIXME: translation?
|
||||
static const char *text[] = {
|
||||
"SUSP\n" " Suspend plugin.\n",
|
||||
"RESU\n" " Resume plugin.\n",
|
||||
"DETA\n" " Detach plugin.\n",
|
||||
"ATTA\n" " Attach plugin.\n",
|
||||
"HOTK key\n" " Execute hotkey.\n",
|
||||
NULL
|
||||
};
|
||||
|
||||
return text;
|
||||
return SVDRPHelpText;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1676,21 +1815,25 @@ const char **cPluginSoftHdDevice::SVDRPHelpPages(void)
|
||||
** @param reply_code reply code
|
||||
*/
|
||||
cString cPluginSoftHdDevice::SVDRPCommand(const char *command,
|
||||
__attribute__ ((unused)) const char *option,
|
||||
__attribute__ ((unused)) int &reply_code)
|
||||
const char *option, __attribute__ ((unused)) int &reply_code)
|
||||
{
|
||||
if (!strcasecmp(command, "SUSP")) {
|
||||
if (cSoftHdControl::Player) { // already suspended
|
||||
return "SoftHdDevice already suspended";
|
||||
}
|
||||
// should be after suspend, but SetPlayMode resumes
|
||||
Suspend(ConfigSuspendClose, ConfigSuspendClose, ConfigSuspendX11);
|
||||
SuspendMode = SUSPEND_NORMAL;
|
||||
if (SuspendMode != NOT_SUSPENDED) {
|
||||
return "SoftHdDevice already detached";
|
||||
}
|
||||
cControl::Launch(new cSoftHdControl);
|
||||
cControl::Attach();
|
||||
Suspend(ConfigSuspendClose, ConfigSuspendClose, ConfigSuspendX11);
|
||||
SuspendMode = SUSPEND_NORMAL;
|
||||
return "SoftHdDevice is suspended";
|
||||
}
|
||||
if (!strcasecmp(command, "RESU")) {
|
||||
if (SuspendMode == NOT_SUSPENDED) {
|
||||
return "SoftHdDevice already resumed";
|
||||
}
|
||||
if (SuspendMode != SUSPEND_NORMAL) {
|
||||
return "can't resume SoftHdDevice";
|
||||
}
|
||||
@@ -1701,26 +1844,33 @@ cString cPluginSoftHdDevice::SVDRPCommand(const char *command,
|
||||
cControl::Shutdown(); // not need, if not suspended
|
||||
}
|
||||
Resume();
|
||||
SuspendMode = 0;
|
||||
SuspendMode = NOT_SUSPENDED;
|
||||
return "SoftHdDevice is resumed";
|
||||
}
|
||||
if (!strcasecmp(command, "DETA")) {
|
||||
if (SuspendMode == SUSPEND_DETACHED) {
|
||||
return "SoftHdDevice already detached";
|
||||
}
|
||||
if (cSoftHdControl::Player) { // already suspended
|
||||
if (SuspendMode == SUSPEND_DETACHED) {
|
||||
return "SoftHdDevice already detached";
|
||||
}
|
||||
return "can't suspend SoftHdDevice already suspended";
|
||||
}
|
||||
Suspend(1, 1, 0);
|
||||
SuspendMode = SUSPEND_DETACHED;
|
||||
cControl::Launch(new cSoftHdControl);
|
||||
cControl::Attach();
|
||||
Suspend(1, 1, 0);
|
||||
SuspendMode = SUSPEND_DETACHED;
|
||||
return "SoftHdDevice is detached";
|
||||
}
|
||||
if (!strcasecmp(command, "ATTA")) {
|
||||
if (SuspendMode != SUSPEND_DETACHED) {
|
||||
return "can't attach SoftHdDevice not detached";
|
||||
}
|
||||
if (!strncmp(option, "-d ", 3)) {
|
||||
// FIXME: loose memory here
|
||||
X11DisplayName = strdup(option + 3);
|
||||
} else if (!strncmp(option, "-d", 2)) {
|
||||
// FIXME: loose memory here
|
||||
X11DisplayName = strdup(option + 2);
|
||||
}
|
||||
if (ShutdownHandler.GetUserInactiveTime()) {
|
||||
ShutdownHandler.SetUserInactiveTimeout();
|
||||
}
|
||||
@@ -1728,7 +1878,7 @@ cString cPluginSoftHdDevice::SVDRPCommand(const char *command,
|
||||
cControl::Shutdown(); // not need, if not suspended
|
||||
}
|
||||
Resume();
|
||||
SuspendMode = 0;
|
||||
SuspendMode = NOT_SUSPENDED;
|
||||
return "SoftHdDevice is attached";
|
||||
}
|
||||
if (!strcasecmp(command, "HOTK")) {
|
||||
@@ -1738,6 +1888,17 @@ cString cPluginSoftHdDevice::SVDRPCommand(const char *command,
|
||||
HandleHotkey(hotk);
|
||||
return "hot-key executed";
|
||||
}
|
||||
if (!strcasecmp(command, "PRIM")) {
|
||||
int primary;
|
||||
|
||||
primary = strtol(option, NULL, 0);
|
||||
if (!primary && MyDevice) {
|
||||
primary = MyDevice->DeviceNumber() + 1;
|
||||
}
|
||||
dsyslog("[softhddev] switching primary device to %d\n", primary);
|
||||
DoMakePrimary = primary;
|
||||
return "switching primary device requested";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
23
video.h
23
video.h
@@ -35,6 +35,7 @@ typedef struct _video_hw_decoder_ VideoHwDecoder;
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
extern char VideoIgnoreRepeatPict; ///< disable repeat pict warning
|
||||
extern int VideoAudioDelay; ///< audio/video delay
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Prototypes
|
||||
@@ -77,12 +78,18 @@ extern void VideoPollEvent(void);
|
||||
/// Wakeup display handler.
|
||||
extern void VideoDisplayWakeup(void);
|
||||
|
||||
/// Set video device.
|
||||
extern void VideoSetDevice(const char *);
|
||||
|
||||
/// Set video geometry.
|
||||
extern int VideoSetGeometry(const char *);
|
||||
|
||||
/// Set 60Hz display mode.
|
||||
extern void VideoSet60HzMode(int);
|
||||
|
||||
/// Set soft start audio/video sync.
|
||||
extern void VideoSetSoftStartSync(int);
|
||||
|
||||
/// Set video output position.
|
||||
extern void VideoSetOutputPosition(int, int, int, int);
|
||||
|
||||
@@ -113,8 +120,11 @@ extern void VideoSetDenoise(int[]);
|
||||
/// Set sharpen.
|
||||
extern void VideoSetSharpen(int[]);
|
||||
|
||||
/// Set skip lines.
|
||||
extern void VideoSetSkipLines(int);
|
||||
/// Set cut top and bottom.
|
||||
extern void VideoSetCutTopBottom(int[]);
|
||||
|
||||
/// Set cut left and right.
|
||||
extern void VideoSetCutLeftRight(int[]);
|
||||
|
||||
/// Set studio levels.
|
||||
extern void VideoSetStudioLevels(int);
|
||||
@@ -137,7 +147,14 @@ extern void VideoOsdDrawARGB(int, int, int, int, const uint8_t *);
|
||||
/// Get OSD size.
|
||||
extern void VideoGetOsdSize(int *, int *);
|
||||
|
||||
extern int64_t VideoGetClock(void); ///< Get video clock.
|
||||
/// Set video clock.
|
||||
extern void VideoSetClock(VideoHwDecoder *, int64_t);
|
||||
|
||||
/// Get video clock.
|
||||
extern int64_t VideoGetClock(const VideoHwDecoder *);
|
||||
|
||||
/// Set trick play speed.
|
||||
extern void VideoSetTrickSpeed(VideoHwDecoder *, int);
|
||||
|
||||
/// Grab screen.
|
||||
extern uint8_t *VideoGrab(int *, int *, int *, int);
|
||||
|
||||
Reference in New Issue
Block a user